summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore59
-rw-r--r--.gitmodules10
-rw-r--r--ABOUT-NLS1379
-rw-r--r--AUTHORS1
-rw-r--r--ChangeLog64
-rw-r--r--Makefile.am13
-rw-r--r--README111
-rw-r--r--README.1st19
-rwxr-xr-xbootstrap26
-rw-r--r--configure.ac283
-rw-r--r--contrib/.gitignore3
-rw-r--r--contrib/Makefile.am67
-rw-r--r--contrib/Makefile.am.in68
-rw-r--r--contrib/auditor-report.tex.j2184
-rw-r--r--contrib/ci/Containerfile68
-rwxr-xr-xcontrib/ci/ci.sh33
-rw-r--r--contrib/ci/jobs/0-codespell/config.ini6
-rw-r--r--contrib/ci/jobs/0-codespell/dictionary.txt47
-rwxr-xr-xcontrib/ci/jobs/0-codespell/job.sh100
-rwxr-xr-xcontrib/ci/jobs/1-build/build.sh14
-rwxr-xr-xcontrib/ci/jobs/1-build/job.sh6
-rw-r--r--contrib/ci/jobs/2-test/config.ini6
-rwxr-xr-xcontrib/ci/jobs/2-test/job.sh6
-rwxr-xr-xcontrib/ci/jobs/2-test/test.sh51
-rw-r--r--contrib/ci/jobs/3-docs/config.ini6
-rwxr-xr-xcontrib/ci/jobs/3-docs/docs.sh11
-rwxr-xr-xcontrib/ci/jobs/3-docs/job.sh6
-rw-r--r--contrib/ci/jobs/4-deb-package/install-fix.patch13
-rwxr-xr-xcontrib/ci/jobs/4-deb-package/job.sh23
-rwxr-xr-xcontrib/ci/jobs/4-deb-package/version.sh17
-rw-r--r--contrib/ci/jobs/5-deploy-package/config.ini5
-rwxr-xr-xcontrib/ci/jobs/5-deploy-package/job.sh14
-rw-r--r--contrib/exchange-pp-v0.rst (renamed from contrib/pp/pp.rst)52
-rw-r--r--contrib/exchange-template/config/exchange-common.conf65
-rw-r--r--contrib/exchange-template/config/exchange-keyup.conf16
-rw-r--r--contrib/exchange-tos-bfh-v0.rst (renamed from contrib/tos/tos.rst)205
-rw-r--r--contrib/exchange-tos-netzbon-v0.rst108
-rw-r--r--contrib/exchange-tos-tops-v0.rst959
-rw-r--r--contrib/exchange-tos-v0.rst278
m---------contrib/gana0
-rwxr-xr-xcontrib/gana-generate.sh41
-rwxr-xr-xcontrib/gana-latest.sh10
-rw-r--r--contrib/gnunet.tag81
-rw-r--r--contrib/kyc-proof-already-done.en.must9
-rw-r--r--contrib/kyc-proof-bad-request.en.must15
-rw-r--r--contrib/kyc-proof-endpoint-unknown.en.must15
-rw-r--r--contrib/kyc-proof-internal-error.en.must16
-rw-r--r--contrib/kyc-proof-target-unknown.en.must15
-rw-r--r--contrib/kycaid-invalid-request.en.must12
-rw-r--r--contrib/locale/de/LC_MESSAGES/exchange-tos-v0.po403
-rw-r--r--contrib/microhttpd.tag102
-rw-r--r--contrib/oauth2-authentication-failure.en.must16
-rw-r--r--contrib/oauth2-authorization-failure-malformed.en.must13
-rw-r--r--contrib/oauth2-authorization-failure.en.must12
-rw-r--r--contrib/oauth2-bad-request.en.must15
-rw-r--r--contrib/oauth2-conversion-failure.en.must28
-rw-r--r--contrib/oauth2-provider-failure.en.must22
-rw-r--r--contrib/packages/fedora/etc-libtalerexchange/taler/overrides.conf1
-rw-r--r--contrib/packages/fedora/etc-libtalerexchange/taler/taler.conf49
-rw-r--r--contrib/packages/fedora/etc-taler-auditor/apache2/sites-available/taler-auditor.conf4
-rw-r--r--contrib/packages/fedora/etc-taler-auditor/nginx/sites-available/taler-auditor18
-rw-r--r--contrib/packages/fedora/etc-taler-auditor/taler/conf.d/auditor-system.conf12
-rw-r--r--contrib/packages/fedora/etc-taler-auditor/taler/secrets/auditor-db.secret.conf10
-rw-r--r--contrib/packages/fedora/etc-taler-exchange/apache2/sites-available/taler-exchange.conf4
-rw-r--r--contrib/packages/fedora/etc-taler-exchange/nginx/sites-available/taler-exchange17
-rw-r--r--contrib/packages/fedora/etc-taler-exchange/taler/conf.d/exchange-business.conf50
-rw-r--r--contrib/packages/fedora/etc-taler-exchange/taler/conf.d/exchange-coins.conf33
-rw-r--r--contrib/packages/fedora/etc-taler-exchange/taler/conf.d/exchange-system.conf13
-rw-r--r--contrib/packages/fedora/etc-taler-exchange/taler/secrets/exchange-accountcredentials-1.secret.conf17
-rw-r--r--contrib/packages/fedora/etc-taler-exchange/taler/secrets/exchange-db.secret.conf10
-rw-r--r--contrib/packages/fedora/taler-auditor.taler-auditor-httpd.service12
-rw-r--r--contrib/packages/fedora/taler-exchange.taler-exchange-aggregator.service18
-rw-r--r--contrib/packages/fedora/taler-exchange.taler-exchange-aggregator@.service17
-rw-r--r--contrib/packages/fedora/taler-exchange.taler-exchange-closer.service18
-rw-r--r--contrib/packages/fedora/taler-exchange.taler-exchange-expire.service18
-rw-r--r--contrib/packages/fedora/taler-exchange.taler-exchange-httpd.service33
-rw-r--r--contrib/packages/fedora/taler-exchange.taler-exchange-httpd@.service27
-rw-r--r--contrib/packages/fedora/taler-exchange.taler-exchange-secmod-cs.service18
-rw-r--r--contrib/packages/fedora/taler-exchange.taler-exchange-secmod-eddsa.service19
-rw-r--r--contrib/packages/fedora/taler-exchange.taler-exchange-secmod-rsa.service18
-rw-r--r--contrib/packages/fedora/taler-exchange.taler-exchange-transfer.service18
-rw-r--r--contrib/packages/fedora/taler-exchange.taler-exchange-wirewatch.service18
-rw-r--r--contrib/packages/fedora/taler-exchange.taler-exchange-wirewatch@.service18
-rw-r--r--contrib/persona-exchange-unauthorized.en.must13
-rw-r--r--contrib/persona-exchange-unpaid.en.must13
-rw-r--r--contrib/persona-invalid-response.en.must13
-rw-r--r--contrib/persona-kyc-failed.en.must20
-rw-r--r--contrib/persona-load-failure.en.must13
-rw-r--r--contrib/persona-logic-failure.en.must13
-rw-r--r--contrib/persona-network-timeout.en.must13
-rw-r--r--contrib/persona-provider-failure.en.must13
-rw-r--r--contrib/pp/.gitignore3
-rw-r--r--contrib/pp/README58
-rw-r--r--contrib/pp/conf.py282
-rw-r--r--contrib/pp/en/0.epubbin13772 -> 0 bytes
-rw-r--r--contrib/pp/en/0.html180
-rw-r--r--contrib/pp/en/0.pdfbin81244 -> 0 bytes
-rw-r--r--contrib/pp/en/0.txt202
-rw-r--r--contrib/pp/en/0.xml167
-rw-r--r--contrib/pp/locale/de/LC_MESSAGES/pp.po283
-rw-r--r--contrib/samples/wire-auditor.json6
-rw-r--r--contrib/sigp/.gitignore3
-rw-r--r--contrib/sigp/Makefile21
-rw-r--r--contrib/sigp/README10
-rw-r--r--contrib/sigp/h.footer3
-rw-r--r--contrib/sigp/h.header31
-rw-r--r--contrib/sigp/h.template6
-rwxr-xr-xcontrib/taler-auditor-dbconfig132
-rwxr-xr-xcontrib/taler-bank-manage-testing38
-rwxr-xr-xcontrib/taler-exchange-dbconfig186
-rwxr-xr-xcontrib/taler-exchange-revoke24
-rwxr-xr-xcontrib/taler-terms-generator295
-rw-r--r--contrib/tos/.gitignore3
-rw-r--r--contrib/tos/Makefile109
-rw-r--r--contrib/tos/README58
-rw-r--r--contrib/tos/conf.py282
-rw-r--r--contrib/tos/en/0.epubbin16629 -> 0 bytes
-rw-r--r--contrib/tos/en/0.html337
-rw-r--r--contrib/tos/en/0.pdfbin99859 -> 0 bytes
-rw-r--r--contrib/tos/en/0.txt381
-rw-r--r--contrib/tos/en/0.xml344
-rw-r--r--contrib/tos/locale/de/LC_MESSAGES/tos.po517
-rwxr-xr-xcontrib/uncrustify-mode.el2
-rw-r--r--contrib/uncrustify.cfg2
-rwxr-xr-xcontrib/uncrustify_precommit7
-rwxr-xr-xcontrib/update-pp.sh14
-rwxr-xr-xcontrib/update-tos.sh15
m---------contrib/wallet-core0
-rw-r--r--debian/.gitignore23
-rw-r--r--debian/README-packaging.md7
-rw-r--r--debian/changelog410
-rw-r--r--debian/control165
-rw-r--r--debian/copyright699
-rw-r--r--debian/etc-libtalerexchange/taler/overrides.conf1
-rw-r--r--debian/etc-libtalerexchange/taler/taler.conf49
-rw-r--r--debian/etc-taler-auditor/apache2/sites-available/taler-auditor.conf4
-rw-r--r--debian/etc-taler-auditor/nginx/sites-available/taler-auditor18
-rw-r--r--debian/etc-taler-auditor/taler/conf.d/auditor-system.conf12
-rw-r--r--debian/etc-taler-auditor/taler/secrets/auditor-db.secret.conf10
-rw-r--r--debian/etc-taler-exchange/apache2/sites-available/taler-exchange.conf4
-rw-r--r--debian/etc-taler-exchange/nginx/sites-available/taler-exchange17
-rw-r--r--debian/etc-taler-exchange/taler/conf.d/exchange-business.conf50
-rw-r--r--debian/etc-taler-exchange/taler/conf.d/exchange-coins.conf33
-rw-r--r--debian/etc-taler-exchange/taler/conf.d/exchange-system.conf13
-rw-r--r--debian/etc-taler-exchange/taler/secrets/exchange-accountcredentials-1.secret.conf17
-rw-r--r--debian/etc-taler-exchange/taler/secrets/exchange-db.secret.conf10
-rw-r--r--debian/libtalerexchange-dev.install32
-rw-r--r--debian/libtalerexchange.dirs1
-rw-r--r--debian/libtalerexchange.install10
-rw-r--r--debian/libtalerexchange.postinst28
-rw-r--r--debian/libtalerexchange.tmpfiles2
-rw-r--r--debian/patches/0001-Dont_copy_license_file.patch22
-rw-r--r--debian/patches/series1
-rw-r--r--debian/po/POTFILES.in1
-rwxr-xr-xdebian/rules70
-rw-r--r--debian/source/format1
-rw-r--r--debian/source/options3
-rw-r--r--debian/taler-auditor.install24
-rw-r--r--debian/taler-auditor.postinst41
-rw-r--r--debian/taler-auditor.postrm29
-rw-r--r--debian/taler-auditor.taler-auditor-httpd.service13
-rw-r--r--debian/taler-auditor.taler-helper-auditor-deposits.service15
-rw-r--r--debian/taler-auditor.tmpfiles2
-rw-r--r--debian/taler-exchange-database.install8
-rw-r--r--debian/taler-exchange-offline.install2
-rw-r--r--debian/taler-exchange-offline.postinst38
-rw-r--r--debian/taler-exchange-offline.taler-exchange-offline.service23
-rw-r--r--debian/taler-exchange-offline.taler-exchange-offline.timer20
-rw-r--r--debian/taler-exchange-offline.tmpfiles2
-rw-r--r--debian/taler-exchange.README.Debian34
-rw-r--r--debian/taler-exchange.docs1
-rw-r--r--debian/taler-exchange.install40
-rw-r--r--debian/taler-exchange.links1
-rw-r--r--debian/taler-exchange.lintan-overrides3
-rw-r--r--debian/taler-exchange.postinst86
-rw-r--r--debian/taler-exchange.postrm50
-rw-r--r--debian/taler-exchange.prerm11
-rw-r--r--debian/taler-exchange.taler-exchange-aggregator.service20
-rw-r--r--debian/taler-exchange.taler-exchange-aggregator@.service24
-rw-r--r--debian/taler-exchange.taler-exchange-closer.service20
-rw-r--r--debian/taler-exchange.taler-exchange-expire.service20
-rw-r--r--debian/taler-exchange.taler-exchange-httpd.service35
-rw-r--r--debian/taler-exchange.taler-exchange-httpd.socket14
-rw-r--r--debian/taler-exchange.taler-exchange-httpd@.service33
-rw-r--r--debian/taler-exchange.taler-exchange-httpd@.socket14
-rw-r--r--debian/taler-exchange.taler-exchange-secmod-cs.service19
-rw-r--r--debian/taler-exchange.taler-exchange-secmod-eddsa.service19
-rw-r--r--debian/taler-exchange.taler-exchange-secmod-rsa.service19
-rw-r--r--debian/taler-exchange.taler-exchange-transfer.service20
-rw-r--r--debian/taler-exchange.taler-exchange-wirewatch.service20
-rw-r--r--debian/taler-exchange.taler-exchange-wirewatch@.service25
-rw-r--r--debian/taler-exchange.taler-exchange.slice7
-rw-r--r--debian/taler-exchange.taler-exchange.target13
-rw-r--r--debian/taler-exchange.tmpfiles8
-rw-r--r--debian/taler-terms-generator.install8
-rw-r--r--debian/upstream/metadata4
-rw-r--r--debian/upstream/signing-key.asc637
-rw-r--r--debian/watch3
-rw-r--r--doc/.gitignore4
-rw-r--r--doc/Makefile.am79
-rw-r--r--doc/audit/report-202005.pdfbin0 -> 129969 bytes
-rw-r--r--doc/audit/response-202005.tex243
-rw-r--r--doc/audit/response-202109.tex291
-rw-r--r--doc/cbdc-es/cbdc-es.tex1283
-rw-r--r--doc/cbdc-es/cbdc.bib566
-rw-r--r--doc/cbdc-es/deposito.pdfbin0 -> 96830 bytes
-rw-r--r--doc/cbdc-es/eshyphexh.tex1367
-rw-r--r--doc/cbdc-es/graphic-es.odpbin0 -> 145669 bytes
-rw-r--r--doc/cbdc-es/retirada.pdfbin0 -> 62191 bytes
-rw-r--r--doc/cbdc-es/taler_figure_1_dora_SPANISH.jpgbin0 -> 44235 bytes
-rw-r--r--doc/cbdc-es/taler_figure_2_dora_SPANISH.jpgbin0 -> 50323 bytes
-rw-r--r--doc/cbdc-it/agsm-mod.bst1375
-rw-r--r--doc/cbdc-it/cbdc-it.bib561
-rw-r--r--doc/cbdc-it/cbdc-it.tex1304
-rw-r--r--doc/cbdc-it/cbdc.bib566
-rw-r--r--doc/cbdc-it/diagramma1-it.pngbin0 -> 108981 bytes
-rw-r--r--doc/cbdc-it/diagramma2-it.pngbin0 -> 122162 bytes
-rw-r--r--doc/cbdc-it/graphics-it.odpbin0 -> 147461 bytes
-rw-r--r--doc/cs/ads/abbreviation.tex48
-rw-r--r--doc/cs/ads/abstract.tex26
-rw-r--r--doc/cs/ads/glossary.tex53
-rw-r--r--doc/cs/ads/header.tex71
-rw-r--r--doc/cs/ads/history.tex12
-rw-r--r--doc/cs/bibliography.bib362
-rw-r--r--doc/cs/bibliography_projekt2.bib442
-rw-r--r--doc/cs/content/1_introduction.tex72
-rw-r--r--doc/cs/content/3_preliminaries.tex1465
-rw-r--r--doc/cs/content/4_1_design.tex459
-rw-r--r--doc/cs/content/4_2_specification.tex790
-rw-r--r--doc/cs/content/4_3_implementation.tex333
-rw-r--r--doc/cs/content/4_execution.tex5
-rw-r--r--doc/cs/content/5_discussion.tex317
-rw-r--r--doc/cs/content/6_conclusion.tex70
-rw-r--r--doc/cs/content/appendix.tex677
-rw-r--r--doc/cs/content/appendix/crypto_implementation.tex279
-rw-r--r--doc/cs/content/appendix/rsa-redesign.tex209
-rw-r--r--doc/cs/content/x_taler.tex373
-rw-r--r--doc/cs/images/bfh_logo.pngbin0 -> 5574 bytes
-rw-r--r--doc/cs/images/diagram-simple.pngbin0 -> 94255 bytes
-rw-r--r--doc/cs/images/logo-2021.pngbin0 -> 31017 bytes
-rw-r--r--doc/cs/images/projectplan.pngbin0 -> 330554 bytes
-rw-r--r--doc/cs/images/taler-exchange.pngbin0 -> 56654 bytes
-rw-r--r--doc/cs/images/taler-merchant.pngbin0 -> 40645 bytes
-rw-r--r--doc/cs/images/taler-pki.pngbin0 -> 79910 bytes
-rw-r--r--doc/cs/images/taler-wallet.pngbin0 -> 50756 bytes
-rw-r--r--doc/cs/images/taler_bigger.pngbin0 -> 259266 bytes
-rw-r--r--doc/cs/images/taler_cut_and_choose.pngbin0 -> 51850 bytes
-rw-r--r--doc/cs/images/taler_refresh_link_threat.pngbin0 -> 56452 bytes
-rw-r--r--doc/cs/images/taler_refresh_transfer_key.pngbin0 -> 27701 bytes
-rw-r--r--doc/cs/thesis.tex93
-rw-r--r--doc/cs/variable.sty15
-rw-r--r--doc/doxygen/taler.doxy2558
-rw-r--r--doc/flows/.gitignore1
-rw-r--r--doc/flows/Makefile3
-rw-r--r--doc/flows/fees-coins.tex39
-rw-r--r--doc/flows/fees-wire.tex30
-rw-r--r--doc/flows/int-deposit.tex52
-rw-r--r--doc/flows/int-pay.tex60
-rw-r--r--doc/flows/int-pull.tex56
-rw-r--r--doc/flows/int-push.tex48
-rw-r--r--doc/flows/int-refund.tex39
-rw-r--r--doc/flows/int-shutdown.tex48
-rw-r--r--doc/flows/int-withdraw.tex49
-rw-r--r--doc/flows/kyc-balance.tex58
-rw-r--r--doc/flows/kyc-deposit.tex85
-rw-r--r--doc/flows/kyc-pull.tex92
-rw-r--r--doc/flows/kyc-push.tex90
-rw-r--r--doc/flows/kyc-withdraw.tex58
-rw-r--r--doc/flows/main.de.tex239
-rw-r--r--doc/flows/main.tex206
-rw-r--r--doc/flows/proc-aml.tex47
-rw-r--r--doc/flows/proc-domestic.tex66
-rw-r--r--doc/flows/proc-kyb.tex98
-rw-r--r--doc/flows/proc-kyc.tex88
-rw-r--r--doc/logos/ai/logotalerv2.ai3474
-rw-r--r--doc/logos/eps/icon_taler.epsbin242881387 -> 0 bytes
-rw-r--r--doc/logos/fonts/OldNewspaperTypes.ttfbin85804 -> 0 bytes
-rw-r--r--doc/logos/fonts/perpetue/Perpetua.ttfbin60216 -> 0 bytes
-rw-r--r--doc/logos/fonts/perpetue/Perpetua_Bold.ttfbin58512 -> 0 bytes
-rw-r--r--doc/logos/fonts/perpetue/Perpetua_Bold_Italic.ttfbin75620 -> 0 bytes
-rw-r--r--doc/logos/fonts/perpetue/Perpetua_Italic.ttfbin76080 -> 0 bytes
-rw-r--r--doc/logos/ico/favicon1616.icobin1150 -> 0 bytes
-rw-r--r--doc/logos/ico/favicon4848.icobin15086 -> 0 bytes
-rw-r--r--doc/logos/png/icon_taler.pngbin1835737 -> 0 bytes
-rw-r--r--doc/logos/png/logo_taler.pngbin377176 -> 0 bytes
-rw-r--r--doc/paper/figs/refresh.tex4
-rw-r--r--doc/paper/offline.tex10
-rw-r--r--doc/paper/postquantum.tex60
-rw-r--r--doc/paper/taler.bib2
-rw-r--r--doc/paper/taler.tex6
-rw-r--r--doc/paper/taler_FC2016.txt4
-rw-r--r--doc/paper/taler_FC2017.txt6
m---------doc/prebuilt0
-rw-r--r--doc/system/.gitignore26
-rw-r--r--doc/system/abstract.tex52
-rw-r--r--doc/system/acknowledgements.tex27
-rw-r--r--doc/system/conclusions.tex227
-rw-r--r--doc/system/cryptocode.sty1813
-rw-r--r--doc/system/diagrams/bitcoin-market-price.pngbin0 -> 41192 bytes
-rw-r--r--doc/system/diagrams/taler-diagram-denom-expiration.pngbin0 -> 15565 bytes
-rw-r--r--doc/system/diagrams/taler-diagram-exchange.pngbin0 -> 61764 bytes
-rw-r--r--doc/system/diagrams/taler-diagram-keyup.pngbin0 -> 46386 bytes
-rw-r--r--doc/system/diagrams/taler-diagram-merchant.pngbin0 -> 33128 bytes
-rw-r--r--doc/system/diagrams/taler-diagram-signatures.pngbin0 -> 57842 bytes
-rw-r--r--doc/system/diagrams/taler-diagram-wallet.pngbin0 -> 39130 bytes
-rw-r--r--doc/system/introduction.tex541
-rw-r--r--doc/system/plots/cpu.pdfbin0 -> 11450 bytes
-rw-r--r--doc/system/plots/dbsize.sql12
-rw-r--r--doc/system/plots/eval-basic.bash20
-rw-r--r--doc/system/plots/eval-latency.bash35
-rw-r--r--doc/system/plots/latencies.pdfbin0 -> 11163 bytes
-rw-r--r--doc/system/plots/latency-deposit.data5
-rw-r--r--doc/system/plots/latency-keys.data5
-rw-r--r--doc/system/plots/latency-refresh-melt.data5
-rw-r--r--doc/system/plots/latency-refresh-reveal.data5
-rw-r--r--doc/system/plots/latency-summary-0.data7
-rw-r--r--doc/system/plots/latency-summary-100.data7
-rw-r--r--doc/system/plots/latency-withdraw.data5
-rw-r--r--doc/system/plots/plot.gnu35
-rw-r--r--doc/system/plots/req-received.data7
-rw-r--r--doc/system/plots/req-sent.data7
-rw-r--r--doc/system/plots/run-latency.bash44
-rw-r--r--doc/system/plots/run.bash10
-rw-r--r--doc/system/plots/set-latency.bash19
-rw-r--r--doc/system/plots/speed.data37
-rw-r--r--doc/system/plots/speed.pdfbin0 -> 8161 bytes
-rw-r--r--doc/system/plots/time_bench_cpu.data39
-rw-r--r--doc/system/plots/time_bench_ops_only.data39
-rw-r--r--doc/system/plots/time_exchange_cpu.data39
-rw-r--r--doc/system/plots/time_real.data40
-rw-r--r--doc/system/ref.bib2825
-rw-r--r--doc/system/snippets/donations.py42
-rw-r--r--doc/system/system.tex99
-rw-r--r--doc/system/taler-arch-full.pdfbin0 -> 293976 bytes
-rw-r--r--doc/system/taler-screenshots/bank-login.pngbin0 -> 84072 bytes
-rw-r--r--doc/system/taler-screenshots/bank-profile.pngbin0 -> 71823 bytes
-rw-r--r--doc/system/taler-screenshots/essay-done.pngbin0 -> 138152 bytes
-rw-r--r--doc/system/taler-screenshots/essay-landing.pngbin0 -> 166803 bytes
-rw-r--r--doc/system/taler-screenshots/essay-pay.pngbin0 -> 44393 bytes
-rw-r--r--doc/system/taler-screenshots/pin-tan.pngbin0 -> 93255 bytes
-rw-r--r--doc/system/taler-screenshots/wallet-install-prompt.pngbin0 -> 44234 bytes
-rw-r--r--doc/system/taler-screenshots/wallet-installed.pngbin0 -> 67606 bytes
-rw-r--r--doc/system/taler-screenshots/withdraw-confirm.pngbin0 -> 63385 bytes
-rw-r--r--doc/system/taler-screenshots/withdraw-done.pngbin0 -> 86578 bytes
-rw-r--r--doc/system/taler/blockchain-accountability.tex1
-rw-r--r--doc/system/taler/coin.dot49
-rw-r--r--doc/system/taler/coin.pdfbin0 -> 17151 bytes
-rw-r--r--doc/system/taler/deposit.dot31
-rw-r--r--doc/system/taler/deposit.pdfbin0 -> 15174 bytes
-rw-r--r--doc/system/taler/design.tex1399
-rw-r--r--doc/system/taler/implementation.tex2432
-rw-r--r--doc/system/taler/reserve.dot14
-rw-r--r--doc/system/taler/reserve.pdfbin0 -> 14395 bytes
-rw-r--r--doc/system/taler/security.tex1729
-rw-r--r--doc/system/taler/snippet-keys.txt62
-rw-r--r--m4/.gitignore6
-rw-r--r--m4/ax_compare_version.m4177
-rw-r--r--m4/ax_lib_postgresql.m4272
-rw-r--r--m4/gettext.m4420
-rw-r--r--m4/iconv.m4271
-rw-r--r--m4/lib-ld.m4119
-rw-r--r--m4/lib-link.m4777
-rw-r--r--m4/lib-prefix.m4224
-rw-r--r--m4/libcurl.m42
-rw-r--r--m4/libgcrypt.m42
-rw-r--r--m4/libgnurl.m4266
-rw-r--r--m4/m4_ax_python_module.m456
-rw-r--r--m4/mhd.m449
-rw-r--r--m4/nls.m432
-rw-r--r--m4/po.m4453
-rw-r--r--m4/progtest.m491
-rw-r--r--po/ChangeLog12
-rw-r--r--po/Makefile.in.in483
-rw-r--r--po/Makevars78
-rw-r--r--po/POTFILES.in2
-rw-r--r--po/Rules-quot58
-rw-r--r--po/boldquot.sed10
-rw-r--r--po/en@boldquot.header25
-rw-r--r--po/en@quot.header22
-rw-r--r--po/insert-header.sin23
-rw-r--r--po/quot.sed6
-rw-r--r--po/remove-potcdate.sin19
-rw-r--r--src/Makefile.am10
-rw-r--r--src/auditor/.gitignore10
-rw-r--r--src/auditor/Makefile.am141
-rw-r--r--src/auditor/auditor-basedb.age1
-rw-r--r--src/auditor/auditor-basedb.feesbin600 -> 0 bytes
-rw-r--r--src/auditor/auditor-basedb.mpub1
-rw-r--r--src/auditor/auditor-basedb.sql4881
-rw-r--r--src/auditor/auditor.conf10
-rw-r--r--src/auditor/batch.conf183
-rwxr-xr-xsrc/auditor/batch.sh235
-rw-r--r--src/auditor/generate-auditor-basedb-template.conf1
-rw-r--r--src/auditor/generate-auditor-basedb.conf222
-rwxr-xr-xsrc/auditor/generate-auditor-basedb.sh242
-rw-r--r--src/auditor/generate-kyc-basedb.conf4
-rwxr-xr-xsrc/auditor/generate-revoke-basedb.sh365
-rw-r--r--src/auditor/generate_auditordb_home/.local/share/taler/exchange-offline/master.priv1
-rw-r--r--src/auditor/report-lib.c605
-rw-r--r--src/auditor/report-lib.h243
-rw-r--r--src/auditor/revoke-basedb.age1
-rw-r--r--src/auditor/revoke-basedb.conf (renamed from src/auditor/test-auditor.conf)19
-rw-r--r--src/auditor/revoke-basedb.feesbin600 -> 0 bytes
-rw-r--r--src/auditor/revoke-basedb.mpub1
-rw-r--r--src/auditor/revoke-basedb.sql4892
-rwxr-xr-xsrc/auditor/setup.sh93
-rw-r--r--src/auditor/taler-auditor-dbinit.c38
-rw-r--r--src/auditor/taler-auditor-exchange.c214
-rw-r--r--src/auditor/taler-auditor-httpd.c615
-rw-r--r--src/auditor/taler-auditor-httpd.h28
-rw-r--r--src/auditor/taler-auditor-httpd_deposit-confirmation-get.c166
-rw-r--r--src/auditor/taler-auditor-httpd_deposit-confirmation-get.h70
-rw-r--r--src/auditor/taler-auditor-httpd_deposit-confirmation.c410
-rw-r--r--src/auditor/taler-auditor-httpd_deposit-confirmation.h3
-rw-r--r--src/auditor/taler-auditor-httpd_exchanges.c118
-rw-r--r--src/auditor/taler-auditor-httpd_mhd.c4
-rw-r--r--src/auditor/taler-auditor-httpd_mhd.h4
-rw-r--r--src/auditor/taler-auditor-sign.c452
-rw-r--r--src/auditor/taler-auditor-sync.c645
-rw-r--r--[-rwxr-xr-x]src/auditor/taler-auditor.in96
-rw-r--r--src/auditor/taler-helper-auditor-aggregation.c1040
-rw-r--r--src/auditor/taler-helper-auditor-coins.c2398
-rw-r--r--src/auditor/taler-helper-auditor-deposits.c377
-rw-r--r--src/auditor/taler-helper-auditor-purses.c1451
-rw-r--r--src/auditor/taler-helper-auditor-render.py12
-rw-r--r--src/auditor/taler-helper-auditor-reserves.c1907
-rw-r--r--src/auditor/taler-helper-auditor-wire.c2258
-rwxr-xr-xsrc/auditor/test-auditor.sh2642
-rwxr-xr-xsrc/auditor/test-kyc.sh751
-rwxr-xr-xsrc/auditor/test-revocation.sh910
-rw-r--r--src/auditor/test-sync-in.conf29
-rw-r--r--src/auditor/test-sync-out.conf29
-rwxr-xr-xsrc/auditor/test-sync.sh167
-rw-r--r--src/auditordb/.gitignore4
-rw-r--r--src/auditordb/0002-auditor_amount_arithmetic_inconsistency.sql26
-rw-r--r--src/auditordb/0002-auditor_bad_sig_losses.sql26
-rw-r--r--src/auditordb/0002-auditor_balances.sql30
-rw-r--r--src/auditordb/0002-auditor_closure_lags.sql27
-rw-r--r--src/auditordb/0002-auditor_coin_inconsistency.sql28
-rw-r--r--src/auditordb/0002-auditor_denomination_key_validity_withdraw_inconsistency.sql26
-rw-r--r--src/auditordb/0002-auditor_denomination_pending.sql34
-rw-r--r--src/auditordb/0002-auditor_denominations_without_sigs.sql27
-rw-r--r--src/auditordb/0002-auditor_deposit_confirmations.sql58
-rw-r--r--src/auditordb/0002-auditor_emergency.sql29
-rw-r--r--src/auditordb/0002-auditor_emergency_by_count.sql30
-rw-r--r--src/auditordb/0002-auditor_exchange_signkeys.sql35
-rw-r--r--src/auditordb/0002-auditor_fee_time_inconsistency.sql26
-rw-r--r--src/auditordb/0002-auditor_historic_denomination_revenue.sql32
-rw-r--r--src/auditordb/0002-auditor_historic_reserve_summary.sql30
-rw-r--r--src/auditordb/0002-auditor_misattribution_in_inconsistency.sql26
-rw-r--r--src/auditordb/0002-auditor_progress.sql26
-rw-r--r--src/auditordb/0002-auditor_purse_not_closed_inconsistencies.sql26
-rw-r--r--src/auditordb/0002-auditor_purses.sql25
-rw-r--r--src/auditordb/0002-auditor_refreshes_hanging.sql25
-rw-r--r--src/auditordb/0002-auditor_reserve_balance_insufficient_inconsistency.sql26
-rw-r--r--src/auditordb/0002-auditor_reserve_balance_summary_wrong_inconsistency.sql26
-rw-r--r--src/auditordb/0002-auditor_reserve_in_inconsistency.sql29
-rw-r--r--src/auditordb/0002-auditor_reserve_not_closed_inconsistency.sql27
-rw-r--r--src/auditordb/0002-auditor_reserves.sql31
-rw-r--r--src/auditordb/0002-auditor_row_inconsistency.sql25
-rw-r--r--src/auditordb/0002-auditor_row_minor_inconsistencies.sql25
-rw-r--r--src/auditordb/0002-auditor_wire_format_inconsistency.sql26
-rw-r--r--src/auditordb/0002-auditor_wire_out_inconsistency.sql26
-rw-r--r--src/auditordb/9999.sql53
-rw-r--r--src/auditordb/Makefile.am97
-rw-r--r--src/auditordb/auditor-0001.sql525
-rw-r--r--src/auditordb/auditor-0002.sql.in46
-rw-r--r--src/auditordb/auditor_do_get_auditor_progress.sql38
-rw-r--r--src/auditordb/auditor_do_get_balance.sql47
-rw-r--r--src/auditordb/auditordb_plugin.c11
-rw-r--r--src/auditordb/drop.sql31
-rw-r--r--src/auditordb/drop0001.sql50
-rw-r--r--src/auditordb/hdr.h0
-rw-r--r--src/auditordb/pg_del_denomination_balance.c47
-rw-r--r--src/auditordb/pg_del_denomination_balance.h40
-rw-r--r--src/auditordb/pg_del_reserve_info.c47
-rw-r--r--src/auditordb/pg_del_reserve_info.h41
-rw-r--r--src/auditordb/pg_delete_deposit_confirmations.c47
-rw-r--r--src/auditordb/pg_delete_deposit_confirmations.h41
-rw-r--r--src/auditordb/pg_delete_pending_deposit.c48
-rw-r--r--src/auditordb/pg_delete_pending_deposit.h44
-rw-r--r--src/auditordb/pg_delete_purse_info.c47
-rw-r--r--src/auditordb/pg_delete_purse_info.h42
-rw-r--r--src/auditordb/pg_get_auditor_progress.c178
-rw-r--r--src/auditordb/pg_get_auditor_progress.h44
-rw-r--r--src/auditordb/pg_get_balance.c183
-rw-r--r--src/auditordb/pg_get_balance.h44
-rw-r--r--src/auditordb/pg_get_denomination_balance.c68
-rw-r--r--src/auditordb/pg_get_denomination_balance.h43
-rw-r--r--src/auditordb/pg_get_deposit_confirmations.c199
-rw-r--r--src/auditordb/pg_get_deposit_confirmations.h48
-rw-r--r--src/auditordb/pg_get_purse_info.c62
-rw-r--r--src/auditordb/pg_get_purse_info.h47
-rw-r--r--src/auditordb/pg_get_reserve_info.c88
-rw-r--r--src/auditordb/pg_get_reserve_info.h49
-rw-r--r--src/auditordb/pg_get_wire_fee_summary.c59
-rw-r--r--src/auditordb/pg_get_wire_fee_summary.h41
-rw-r--r--src/auditordb/pg_helper.h119
-rw-r--r--src/auditordb/pg_insert_auditor_progress.c97
-rw-r--r--src/auditordb/pg_insert_auditor_progress.h46
-rw-r--r--src/auditordb/pg_insert_balance.c97
-rw-r--r--src/auditordb/pg_insert_balance.h45
-rw-r--r--src/auditordb/pg_insert_denomination_balance.c65
-rw-r--r--src/auditordb/pg_insert_denomination_balance.h45
-rw-r--r--src/auditordb/pg_insert_deposit_confirmation.c77
-rw-r--r--src/auditordb/pg_insert_deposit_confirmation.h42
-rw-r--r--src/auditordb/pg_insert_exchange_signkey.c56
-rw-r--r--src/auditordb/pg_insert_exchange_signkey.h41
-rw-r--r--src/auditordb/pg_insert_historic_denom_revenue.c59
-rw-r--r--src/auditordb/pg_insert_historic_denom_revenue.h50
-rw-r--r--src/auditordb/pg_insert_historic_reserve_revenue.c54
-rw-r--r--src/auditordb/pg_insert_historic_reserve_revenue.h45
-rw-r--r--src/auditordb/pg_insert_pending_deposit.c58
-rw-r--r--src/auditordb/pg_insert_pending_deposit.h48
-rw-r--r--src/auditordb/pg_insert_purse_info.c55
-rw-r--r--src/auditordb/pg_insert_purse_info.h45
-rw-r--r--src/auditordb/pg_insert_reserve_info.c79
-rw-r--r--src/auditordb/pg_insert_reserve_info.h48
-rw-r--r--src/auditordb/pg_select_historic_denom_revenue.c146
-rw-r--r--src/auditordb/pg_select_historic_denom_revenue.h43
-rw-r--r--src/auditordb/pg_select_historic_reserve_revenue.c140
-rw-r--r--src/auditordb/pg_select_historic_reserve_revenue.h43
-rw-r--r--src/auditordb/pg_select_pending_deposits.c149
-rw-r--r--src/auditordb/pg_select_pending_deposits.h46
-rw-r--r--src/auditordb/pg_select_purse_expired.c146
-rw-r--r--src/auditordb/pg_select_purse_expired.h43
-rw-r--r--src/auditordb/pg_template.c26
-rw-r--r--src/auditordb/pg_template.h29
-rwxr-xr-xsrc/auditordb/pg_template.sh21
-rw-r--r--src/auditordb/pg_update_auditor_progress.c99
-rw-r--r--src/auditordb/pg_update_auditor_progress.h46
-rw-r--r--src/auditordb/pg_update_balance.c100
-rw-r--r--src/auditordb/pg_update_balance.h47
-rw-r--r--src/auditordb/pg_update_denomination_balance.c62
-rw-r--r--src/auditordb/pg_update_denomination_balance.h45
-rw-r--r--src/auditordb/pg_update_purse_info.c52
-rw-r--r--src/auditordb/pg_update_purse_info.h45
-rw-r--r--src/auditordb/pg_update_reserve_info.c69
-rw-r--r--src/auditordb/pg_update_reserve_info.h46
-rw-r--r--src/auditordb/pg_update_wire_fee_summary.c48
-rw-r--r--src/auditordb/pg_update_wire_fee_summary.h42
-rw-r--r--src/auditordb/plugin_auditordb_postgres.c3319
-rw-r--r--src/auditordb/procedures.sql.in24
-rw-r--r--src/auditordb/restart.sql (renamed from src/auditordb/restart0001.sql)20
-rw-r--r--src/auditordb/test_auditordb.c850
-rw-r--r--src/auditordb/test_auditordb_checkpoints.c386
-rw-r--r--src/auditordb/versioning.sql (renamed from src/exchangedb/exchange-0000.sql)3
-rw-r--r--src/bank-lib/Makefile.am58
-rw-r--r--src/bank-lib/bank_api_admin.c111
-rw-r--r--src/bank-lib/bank_api_common.c13
-rw-r--r--src/bank-lib/bank_api_common.h2
-rw-r--r--src/bank-lib/bank_api_credit.c234
-rw-r--r--src/bank-lib/bank_api_debit.c230
-rw-r--r--src/bank-lib/bank_api_parse.c16
-rw-r--r--src/bank-lib/bank_api_transfer.c115
-rw-r--r--src/bank-lib/fakebank.c1588
-rw-r--r--src/bank-lib/fakebank.h691
-rw-r--r--src/bank-lib/fakebank_api_check.c238
-rw-r--r--src/bank-lib/fakebank_bank.c524
-rw-r--r--src/bank-lib/fakebank_bank.h54
-rw-r--r--src/bank-lib/fakebank_bank_accounts_withdrawals.c101
-rw-r--r--src/bank-lib/fakebank_bank_accounts_withdrawals.h50
-rw-r--r--src/bank-lib/fakebank_bank_get_accounts.c80
-rw-r--r--src/bank-lib/fakebank_bank_get_accounts.h48
-rw-r--r--src/bank-lib/fakebank_bank_get_root.c58
-rw-r--r--src/bank-lib/fakebank_bank_get_root.h44
-rw-r--r--src/bank-lib/fakebank_bank_get_withdrawals.c87
-rw-r--r--src/bank-lib/fakebank_bank_get_withdrawals.h51
-rw-r--r--src/bank-lib/fakebank_bank_post_accounts_withdrawals.c196
-rw-r--r--src/bank-lib/fakebank_bank_post_accounts_withdrawals.h54
-rw-r--r--src/bank-lib/fakebank_bank_post_withdrawals_abort.c74
-rw-r--r--src/bank-lib/fakebank_bank_post_withdrawals_abort.h48
-rw-r--r--src/bank-lib/fakebank_bank_post_withdrawals_confirm.c107
-rw-r--r--src/bank-lib/fakebank_bank_post_withdrawals_confirm.h48
-rw-r--r--src/bank-lib/fakebank_bank_post_withdrawals_id_op.c241
-rw-r--r--src/bank-lib/fakebank_bank_post_withdrawals_id_op.h58
-rw-r--r--src/bank-lib/fakebank_bank_testing_register.c128
-rw-r--r--src/bank-lib/fakebank_bank_testing_register.h53
-rw-r--r--src/bank-lib/fakebank_common_lookup.c103
-rw-r--r--src/bank-lib/fakebank_common_lookup.h62
-rw-r--r--src/bank-lib/fakebank_common_lp.c344
-rw-r--r--src/bank-lib/fakebank_common_lp.h100
-rw-r--r--src/bank-lib/fakebank_common_make_admin_transfer.c117
-rw-r--r--src/bank-lib/fakebank_common_make_admin_transfer.h56
-rw-r--r--src/bank-lib/fakebank_common_parser.c138
-rw-r--r--src/bank-lib/fakebank_common_parser.h50
-rw-r--r--src/bank-lib/fakebank_common_transact.c261
-rw-r--r--src/bank-lib/fakebank_common_transact.h76
-rw-r--r--src/bank-lib/fakebank_stop.c192
-rw-r--r--src/bank-lib/fakebank_tbi.c167
-rw-r--r--src/bank-lib/fakebank_tbi.h54
-rw-r--r--src/bank-lib/fakebank_tbi_get_withdrawal_operation.c141
-rw-r--r--src/bank-lib/fakebank_tbi_get_withdrawal_operation.h51
-rw-r--r--src/bank-lib/fakebank_tbi_post_withdrawal_operation.c231
-rw-r--r--src/bank-lib/fakebank_tbi_post_withdrawal_operation.h53
-rw-r--r--src/bank-lib/fakebank_tbr.c94
-rw-r--r--src/bank-lib/fakebank_tbr.h58
-rw-r--r--src/bank-lib/fakebank_tbr_get_history.c312
-rw-r--r--src/bank-lib/fakebank_tbr_get_history.h52
-rw-r--r--src/bank-lib/fakebank_tbr_get_root.c50
-rw-r--r--src/bank-lib/fakebank_tbr_get_root.h45
-rw-r--r--src/bank-lib/fakebank_twg.c124
-rw-r--r--src/bank-lib/fakebank_twg.h56
-rw-r--r--src/bank-lib/fakebank_twg_admin_add_incoming.c160
-rw-r--r--src/bank-lib/fakebank_twg_admin_add_incoming.h52
-rw-r--r--src/bank-lib/fakebank_twg_get_root.c58
-rw-r--r--src/bank-lib/fakebank_twg_get_root.h46
-rw-r--r--src/bank-lib/fakebank_twg_history.c537
-rw-r--r--src/bank-lib/fakebank_twg_history.h67
-rw-r--r--src/bank-lib/fakebank_twg_transfer.c178
-rw-r--r--src/bank-lib/fakebank_twg_transfer.h55
-rw-r--r--src/bank-lib/taler-exchange-wire-gateway-client.c (renamed from src/bank-lib/taler-bank-transfer.c)364
-rw-r--r--src/bank-lib/taler-fakebank-run.c191
-rw-r--r--src/bank-lib/test_bank.conf10
-rwxr-xr-xsrc/bank-lib/test_bank.sh99
-rw-r--r--src/benchmark/.gitignore3
-rw-r--r--src/benchmark/Makefile.am48
-rw-r--r--src/benchmark/bank-benchmark-cs.conf5
-rw-r--r--src/benchmark/bank-benchmark-rsa.conf5
-rw-r--r--src/benchmark/benchmark-common.conf106
-rw-r--r--src/benchmark/benchmark-cs.conf16
-rw-r--r--src/benchmark/benchmark-rsa.conf16
-rw-r--r--src/benchmark/benchmark.conf151
-rw-r--r--src/benchmark/coins-cs.conf58
-rw-r--r--src/benchmark/coins-rsa.conf63
-rw-r--r--src/benchmark/exchange_benchmark_home/.local/share/taler/exchange/offline-keys/master.priv1
-rw-r--r--src/benchmark/taler-aggregator-benchmark.c658
-rw-r--r--src/benchmark/taler-bank-benchmark.c576
-rw-r--r--src/benchmark/taler-exchange-benchmark.c986
-rw-r--r--src/curl/Makefile.am4
-rw-r--r--src/curl/curl.c103
-rw-r--r--src/exchange-tools/.gitignore6
-rw-r--r--src/exchange-tools/Makefile.am67
-rw-r--r--src/exchange-tools/exchange-offline.conf15
-rw-r--r--src/exchange-tools/key-helper.c129
-rw-r--r--src/exchange-tools/taler-auditor-offline.c1496
-rw-r--r--src/exchange-tools/taler-exchange-dbinit.c132
-rw-r--r--src/exchange-tools/taler-exchange-keycheck.c336
-rw-r--r--src/exchange-tools/taler-exchange-keyup.c1519
-rw-r--r--src/exchange-tools/taler-exchange-offline.c5497
-rw-r--r--src/exchange-tools/taler-exchange-wire.c220
-rw-r--r--src/exchange-tools/test_taler_exchange_httpd.conf130
-rw-r--r--src/exchange-tools/test_taler_exchange_httpd_home/.config/taler/test.json8
-rw-r--r--src/exchange-tools/test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv1
-rwxr-xr-xsrc/exchange-tools/test_taler_exchange_keyup.sh142
-rw-r--r--src/exchange/.gitignore2
-rw-r--r--src/exchange/Makefile.am132
-rw-r--r--src/exchange/exchange.conf118
-rw-r--r--src/exchange/taler-exchange-aggregator.c1525
-rw-r--r--src/exchange/taler-exchange-closer.c326
-rw-r--r--src/exchange/taler-exchange-drain.c431
-rw-r--r--src/exchange/taler-exchange-expire.c500
-rw-r--r--src/exchange/taler-exchange-httpd.c2622
-rw-r--r--src/exchange/taler-exchange-httpd.h215
-rw-r--r--src/exchange/taler-exchange-httpd_age-withdraw.c1019
-rw-r--r--src/exchange/taler-exchange-httpd_age-withdraw.h47
-rw-r--r--src/exchange/taler-exchange-httpd_age-withdraw_reveal.c610
-rw-r--r--src/exchange/taler-exchange-httpd_age-withdraw_reveal.h56
-rw-r--r--src/exchange/taler-exchange-httpd_aml-decision-get.c233
-rw-r--r--src/exchange/taler-exchange-httpd_aml-decision.c358
-rw-r--r--src/exchange/taler-exchange-httpd_aml-decision.h79
-rw-r--r--src/exchange/taler-exchange-httpd_aml-decisions-get.c215
-rw-r--r--src/exchange/taler-exchange-httpd_auditors.c232
-rw-r--r--src/exchange/taler-exchange-httpd_auditors.h (renamed from src/auditor/taler-auditor-httpd_exchanges.h)36
-rw-r--r--src/exchange/taler-exchange-httpd_batch-deposit.c738
-rw-r--r--src/exchange/taler-exchange-httpd_batch-deposit.h (renamed from src/exchange/taler-exchange-httpd_deposit.h)24
-rw-r--r--src/exchange/taler-exchange-httpd_batch-withdraw.c935
-rw-r--r--src/exchange/taler-exchange-httpd_batch-withdraw.h48
-rw-r--r--src/exchange/taler-exchange-httpd_coins_get.c709
-rw-r--r--src/exchange/taler-exchange-httpd_coins_get.h53
-rw-r--r--src/exchange/taler-exchange-httpd_common_deposit.c268
-rw-r--r--src/exchange/taler-exchange-httpd_common_deposit.h130
-rw-r--r--src/exchange/taler-exchange-httpd_common_kyc.c302
-rw-r--r--src/exchange/taler-exchange-httpd_common_kyc.h117
-rw-r--r--src/exchange/taler-exchange-httpd_config.c92
-rw-r--r--src/exchange/taler-exchange-httpd_config.h58
-rw-r--r--src/exchange/taler-exchange-httpd_contract.c99
-rw-r--r--src/exchange/taler-exchange-httpd_contract.h44
-rw-r--r--src/exchange/taler-exchange-httpd_csr.c351
-rw-r--r--src/exchange/taler-exchange-httpd_csr.h56
-rw-r--r--src/exchange/taler-exchange-httpd_db.c198
-rw-r--r--src/exchange/taler-exchange-httpd_db.h61
-rw-r--r--src/exchange/taler-exchange-httpd_deposit.c564
-rw-r--r--src/exchange/taler-exchange-httpd_deposits_get.c610
-rw-r--r--src/exchange/taler-exchange-httpd_deposits_get.h17
-rw-r--r--src/exchange/taler-exchange-httpd_extensions.c442
-rw-r--r--src/exchange/taler-exchange-httpd_extensions.h58
-rw-r--r--src/exchange/taler-exchange-httpd_keys.c4354
-rw-r--r--src/exchange/taler-exchange-httpd_keys.h587
-rw-r--r--src/exchange/taler-exchange-httpd_keystate.c2608
-rw-r--r--src/exchange/taler-exchange-httpd_keystate.h199
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-check.c710
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-check.h49
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-proof.c566
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-proof.h49
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-wallet.c252
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-wallet.h45
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-webhook.c420
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-webhook.h64
-rw-r--r--src/exchange/taler-exchange-httpd_link.c107
-rw-r--r--src/exchange/taler-exchange-httpd_link.h12
-rw-r--r--src/exchange/taler-exchange-httpd_management.h211
-rw-r--r--src/exchange/taler-exchange-httpd_management_aml-officers.c142
-rw-r--r--src/exchange/taler-exchange-httpd_management_auditors.c207
-rw-r--r--src/exchange/taler-exchange-httpd_management_auditors_AP_disable.c196
-rw-r--r--src/exchange/taler-exchange-httpd_management_denominations_HDP_revoke.c94
-rw-r--r--src/exchange/taler-exchange-httpd_management_drain.c195
-rw-r--r--src/exchange/taler-exchange-httpd_management_extensions.c300
-rw-r--r--src/exchange/taler-exchange-httpd_management_global_fees.c261
-rw-r--r--src/exchange/taler-exchange-httpd_management_partners.c132
-rw-r--r--src/exchange/taler-exchange-httpd_management_post_keys.c487
-rw-r--r--src/exchange/taler-exchange-httpd_management_signkey_EP_revoke.c93
-rw-r--r--src/exchange/taler-exchange-httpd_management_wire_disable.c205
-rw-r--r--src/exchange/taler-exchange-httpd_management_wire_enable.c320
-rw-r--r--src/exchange/taler-exchange-httpd_management_wire_fees.c230
-rw-r--r--src/exchange/taler-exchange-httpd_melt.c716
-rw-r--r--src/exchange/taler-exchange-httpd_melt.h8
-rw-r--r--src/exchange/taler-exchange-httpd_metrics.c165
-rw-r--r--src/exchange/taler-exchange-httpd_metrics.h136
-rw-r--r--src/exchange/taler-exchange-httpd_mhd.c36
-rw-r--r--src/exchange/taler-exchange-httpd_mhd.h20
-rw-r--r--src/exchange/taler-exchange-httpd_purses_create.c651
-rw-r--r--src/exchange/taler-exchange-httpd_purses_create.h (renamed from src/exchange/taler-exchange-httpd_wire.h)46
-rw-r--r--src/exchange/taler-exchange-httpd_purses_delete.c141
-rw-r--r--src/exchange/taler-exchange-httpd_purses_delete.h42
-rw-r--r--src/exchange/taler-exchange-httpd_purses_deposit.c507
-rw-r--r--src/exchange/taler-exchange-httpd_purses_deposit.h47
-rw-r--r--src/exchange/taler-exchange-httpd_purses_get.c433
-rw-r--r--src/exchange/taler-exchange-httpd_purses_get.h51
-rw-r--r--src/exchange/taler-exchange-httpd_purses_merge.c696
-rw-r--r--src/exchange/taler-exchange-httpd_purses_merge.h46
-rw-r--r--src/exchange/taler-exchange-httpd_recoup-refresh.c432
-rw-r--r--src/exchange/taler-exchange-httpd_recoup-refresh.h46
-rw-r--r--src/exchange/taler-exchange-httpd_recoup.c611
-rw-r--r--src/exchange/taler-exchange-httpd_recoup.h2
-rw-r--r--src/exchange/taler-exchange-httpd_refreshes_reveal.c1217
-rw-r--r--src/exchange/taler-exchange-httpd_refreshes_reveal.h10
-rw-r--r--src/exchange/taler-exchange-httpd_refund.c509
-rw-r--r--src/exchange/taler-exchange-httpd_refund.h4
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_attest.c385
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_attest.h41
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_close.c448
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_close.h41
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_get.c311
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_get.h24
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_get_attest.c232
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_get_attest.h44
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_history.c517
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_history.h43
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_open.c471
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_open.h41
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_purse.c774
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_purse.h46
-rw-r--r--src/exchange/taler-exchange-httpd_responses.c1018
-rw-r--r--src/exchange/taler-exchange-httpd_responses.h256
-rw-r--r--src/exchange/taler-exchange-httpd_spa.c362
-rw-r--r--src/exchange/taler-exchange-httpd_spa.h49
-rw-r--r--src/exchange/taler-exchange-httpd_terms.c39
-rw-r--r--src/exchange/taler-exchange-httpd_terms.h18
-rw-r--r--src/exchange/taler-exchange-httpd_transfers_get.c429
-rw-r--r--src/exchange/taler-exchange-httpd_transfers_get.h10
-rw-r--r--src/exchange/taler-exchange-httpd_wire.c395
-rw-r--r--src/exchange/taler-exchange-httpd_withdraw.c527
-rw-r--r--src/exchange/taler-exchange-httpd_withdraw.h51
-rwxr-xr-xsrc/exchange/taler-exchange-kyc-aml-pep-trigger.sh7
-rw-r--r--src/exchange/taler-exchange-router.c450
-rw-r--r--src/exchange/taler-exchange-transfer.c717
-rw-r--r--src/exchange/taler-exchange-wirewatch.c1063
-rw-r--r--src/exchange/test_taler_exchange_httpd.conf88
-rw-r--r--src/exchange/test_taler_exchange_httpd.get2
-rwxr-xr-xsrc/exchange/test_taler_exchange_httpd.sh14
-rwxr-xr-xsrc/exchange/test_taler_exchange_httpd_restart.sh116
-rw-r--r--src/exchange/test_taler_exchange_unix.conf82
-rw-r--r--src/exchangedb/.gitignore20
-rw-r--r--src/exchangedb/0002-account_merges.sql139
-rw-r--r--src/exchangedb/0002-age_withdraw.sql157
-rw-r--r--src/exchangedb/0002-aggregation_tracking.sql118
-rw-r--r--src/exchangedb/0002-aggregation_transient.sql71
-rw-r--r--src/exchangedb/0002-aml_history.sql147
-rw-r--r--src/exchangedb/0002-aml_staff.sql40
-rw-r--r--src/exchangedb/0002-aml_status.sql101
-rw-r--r--src/exchangedb/0002-auditor_denom_sigs.sql32
-rw-r--r--src/exchangedb/0002-auditors.sql35
-rw-r--r--src/exchangedb/0002-batch_deposits.sql172
-rw-r--r--src/exchangedb/0002-close_requests.sql178
-rw-r--r--src/exchangedb/0002-coin_deposits.sql157
-rw-r--r--src/exchangedb/0002-coin_history.sql138
-rw-r--r--src/exchangedb/0002-contracts.sql109
-rw-r--r--src/exchangedb/0002-cs_nonce_locks.sql97
-rw-r--r--src/exchangedb/0002-denomination_revocations.sql23
-rw-r--r--src/exchangedb/0002-denominations.sql45
-rw-r--r--src/exchangedb/0002-exchange_sign_keys.sql36
-rw-r--r--src/exchangedb/0002-extensions.sql27
-rw-r--r--src/exchangedb/0002-global_fee.sql37
-rw-r--r--src/exchangedb/0002-history_requests.sql159
-rw-r--r--src/exchangedb/0002-known_coins.sql136
-rw-r--r--src/exchangedb/0002-kyc_alerts.sql27
-rw-r--r--src/exchangedb/0002-kyc_attributes.sql162
-rw-r--r--src/exchangedb/0002-legitimization_processes.sql149
-rw-r--r--src/exchangedb/0002-legitimization_requirements.sql104
-rw-r--r--src/exchangedb/0002-partner_accounts.sql33
-rw-r--r--src/exchangedb/0002-partners.sql49
-rw-r--r--src/exchangedb/0002-policy_details.sql175
-rw-r--r--src/exchangedb/0002-policy_fulfillments.sql101
-rw-r--r--src/exchangedb/0002-prewire.sql116
-rw-r--r--src/exchangedb/0002-profit_drains.sql42
-rw-r--r--src/exchangedb/0002-purse_actions.sql121
-rw-r--r--src/exchangedb/0002-purse_decision.sql143
-rw-r--r--src/exchangedb/0002-purse_deletion.sql110
-rw-r--r--src/exchangedb/0002-purse_deposits.sql176
-rw-r--r--src/exchangedb/0002-purse_merges.sql140
-rw-r--r--src/exchangedb/0002-purse_requests.sql163
-rw-r--r--src/exchangedb/0002-recoup.sql267
-rw-r--r--src/exchangedb/0002-recoup_refresh.sql203
-rw-r--r--src/exchangedb/0002-refresh_commitments.sql166
-rw-r--r--src/exchangedb/0002-refresh_revealed_coins.sql169
-rw-r--r--src/exchangedb/0002-refresh_transfer_keys.sql127
-rw-r--r--src/exchangedb/0002-refunds.sql162
-rw-r--r--src/exchangedb/0002-reserve_history.sql138
-rw-r--r--src/exchangedb/0002-reserves.sql152
-rw-r--r--src/exchangedb/0002-reserves_close.sql151
-rw-r--r--src/exchangedb/0002-reserves_in.sql175
-rw-r--r--src/exchangedb/0002-reserves_open_deposits.sql135
-rw-r--r--src/exchangedb/0002-reserves_open_requests.sql150
-rw-r--r--src/exchangedb/0002-reserves_out.sql173
-rw-r--r--src/exchangedb/0002-revolving_work_shards.sql46
-rw-r--r--src/exchangedb/0002-signkey_revocations.sql23
-rw-r--r--src/exchangedb/0002-wad_in_entries.sql175
-rw-r--r--src/exchangedb/0002-wad_out_entries.sql179
-rw-r--r--src/exchangedb/0002-wads_in.sql107
-rw-r--r--src/exchangedb/0002-wads_out.sql128
-rw-r--r--src/exchangedb/0002-wire_accounts.sql45
-rw-r--r--src/exchangedb/0002-wire_fee.sql34
-rw-r--r--src/exchangedb/0002-wire_out.sql130
-rw-r--r--src/exchangedb/0002-wire_targets.sql89
-rw-r--r--src/exchangedb/0002-work_shards.sql56
-rw-r--r--src/exchangedb/0003-purse_deletion.sql52
-rw-r--r--src/exchangedb/0003-wire_accounts.sql25
-rw-r--r--src/exchangedb/0004-refunds.sql35
-rw-r--r--src/exchangedb/Makefile.am378
-rw-r--r--src/exchangedb/auditor-triggers-0001.sql41
-rw-r--r--src/exchangedb/bench-db-postgres.conf14
-rw-r--r--src/exchangedb/bench_db.c531
-rw-r--r--src/exchangedb/benchmark-0001.sql55
-rw-r--r--src/exchangedb/drop.sql39
-rw-r--r--src/exchangedb/drop0001.sql50
-rw-r--r--src/exchangedb/exchange-0001.sql689
-rw-r--r--src/exchangedb/exchange-0002.sql.in118
-rw-r--r--src/exchangedb/exchange-0003.sql.in25
-rw-r--r--src/exchangedb/exchange-0004.sql.in24
-rw-r--r--src/exchangedb/exchange_do_account_merge.sql15
-rw-r--r--src/exchangedb/exchange_do_age_withdraw.sql165
-rw-r--r--src/exchangedb/exchange_do_amount_specific.sql92
-rw-r--r--src/exchangedb/exchange_do_batch_coin_known.sql469
-rw-r--r--src/exchangedb/exchange_do_batch_reserves_update.sql72
-rw-r--r--src/exchangedb/exchange_do_batch_withdraw.sql126
-rw-r--r--src/exchangedb/exchange_do_batch_withdraw_insert.sql120
-rw-r--r--src/exchangedb/exchange_do_deposit.sql206
-rw-r--r--src/exchangedb/exchange_do_expire_purse.sql98
-rw-r--r--src/exchangedb/exchange_do_gc.sql140
-rw-r--r--src/exchangedb/exchange_do_get_link_data.sql59
-rw-r--r--src/exchangedb/exchange_do_insert_aml_decision.sql127
-rw-r--r--src/exchangedb/exchange_do_insert_aml_officer.sql74
-rw-r--r--src/exchangedb/exchange_do_insert_kyc_attributes.sql114
-rw-r--r--src/exchangedb/exchange_do_insert_or_update_policy_details.sql114
-rw-r--r--src/exchangedb/exchange_do_melt.sql182
-rw-r--r--src/exchangedb/exchange_do_purse_delete.sql118
-rw-r--r--src/exchangedb/exchange_do_purse_deposit.sql267
-rw-r--r--src/exchangedb/exchange_do_purse_merge.sql237
-rw-r--r--src/exchangedb/exchange_do_recoup_by_reserve.sql87
-rw-r--r--src/exchangedb/exchange_do_recoup_to_coin.sql135
-rw-r--r--src/exchangedb/exchange_do_recoup_to_reserve.sql150
-rw-r--r--src/exchangedb/exchange_do_refund.sql205
-rw-r--r--src/exchangedb/exchange_do_reserve_open.sql194
-rw-r--r--src/exchangedb/exchange_do_reserve_open_deposit.sql84
-rw-r--r--src/exchangedb/exchange_do_reserve_purse.sql162
-rw-r--r--src/exchangedb/exchange_do_reserves_in_insert.sql122
-rw-r--r--src/exchangedb/exchange_do_select_deposits_missing_wire.sql73
-rw-r--r--src/exchangedb/exchange_do_select_justification_for_missing_wire.sql102
-rw-r--r--src/exchangedb/exchangedb-postgres.conf7
-rw-r--r--src/exchangedb/exchangedb.conf12
-rw-r--r--src/exchangedb/exchangedb_accounts.c337
-rw-r--r--src/exchangedb/exchangedb_auditorkeys.c344
-rw-r--r--src/exchangedb/exchangedb_denomkeys.c537
-rw-r--r--src/exchangedb/exchangedb_fees.c412
-rw-r--r--src/exchangedb/exchangedb_plugin.c11
-rw-r--r--src/exchangedb/exchangedb_signkeys.c187
-rw-r--r--src/exchangedb/exchangedb_transactions.c82
-rwxr-xr-xsrc/exchangedb/perf-exchangedb-reserves-in-insert-postgres210
-rw-r--r--src/exchangedb/perf_deposits_get_ready.c565
-rw-r--r--src/exchangedb/perf_get_link_data.c543
-rw-r--r--src/exchangedb/perf_reserves_in_insert.c232
-rw-r--r--src/exchangedb/perf_select_refunds_by_coin.c619
-rw-r--r--src/exchangedb/pg_abort_shard.c53
-rw-r--r--src/exchangedb/pg_abort_shard.h43
-rw-r--r--src/exchangedb/pg_activate_signing_key.c58
-rw-r--r--src/exchangedb/pg_activate_signing_key.h44
-rw-r--r--src/exchangedb/pg_add_denomination_key.c86
-rw-r--r--src/exchangedb/pg_add_denomination_key.h46
-rw-r--r--src/exchangedb/pg_add_policy_fulfillment_proof.c159
-rw-r--r--src/exchangedb/pg_add_policy_fulfillment_proof.h39
-rw-r--r--src/exchangedb/pg_aggregate.c205
-rw-r--r--src/exchangedb/pg_aggregate.h46
-rw-r--r--src/exchangedb/pg_batch_ensure_coin_known.c462
-rw-r--r--src/exchangedb/pg_batch_ensure_coin_known.h47
-rw-r--r--src/exchangedb/pg_begin_revolving_shard.c263
-rw-r--r--src/exchangedb/pg_begin_revolving_shard.h49
-rw-r--r--src/exchangedb/pg_begin_shard.c266
-rw-r--r--src/exchangedb/pg_begin_shard.h47
-rw-r--r--src/exchangedb/pg_commit.c58
-rw-r--r--src/exchangedb/pg_commit.h37
-rw-r--r--src/exchangedb/pg_complete_shard.c56
-rw-r--r--src/exchangedb/pg_complete_shard.h43
-rw-r--r--src/exchangedb/pg_compute_shard.c49
-rw-r--r--src/exchangedb/pg_compute_shard.h39
-rw-r--r--src/exchangedb/pg_count_known_coins.c63
-rw-r--r--src/exchangedb/pg_count_known_coins.h39
-rw-r--r--src/exchangedb/pg_create_aggregation_transient.c64
-rw-r--r--src/exchangedb/pg_create_aggregation_transient.h49
-rw-r--r--src/exchangedb/pg_create_tables.c72
-rw-r--r--src/exchangedb/pg_create_tables.h44
-rw-r--r--src/exchangedb/pg_delete_aggregation_transient.c50
-rw-r--r--src/exchangedb/pg_delete_aggregation_transient.h43
-rw-r--r--src/exchangedb/pg_delete_shard_locks.c41
-rw-r--r--src/exchangedb/pg_delete_shard_locks.h38
-rw-r--r--src/exchangedb/pg_do_age_withdraw.c108
-rw-r--r--src/exchangedb/pg_do_age_withdraw.h57
-rw-r--r--src/exchangedb/pg_do_batch_withdraw.c89
-rw-r--r--src/exchangedb/pg_do_batch_withdraw.h59
-rw-r--r--src/exchangedb/pg_do_batch_withdraw_insert.c77
-rw-r--r--src/exchangedb/pg_do_batch_withdraw_insert.h52
-rw-r--r--src/exchangedb/pg_do_deposit.c119
-rw-r--r--src/exchangedb/pg_do_deposit.h51
-rw-r--r--src/exchangedb/pg_do_melt.c82
-rw-r--r--src/exchangedb/pg_do_melt.h49
-rw-r--r--src/exchangedb/pg_do_purse_delete.c64
-rw-r--r--src/exchangedb/pg_do_purse_delete.h49
-rw-r--r--src/exchangedb/pg_do_purse_deposit.c90
-rw-r--r--src/exchangedb/pg_do_purse_deposit.h63
-rw-r--r--src/exchangedb/pg_do_purse_merge.c91
-rw-r--r--src/exchangedb/pg_do_purse_merge.h57
-rw-r--r--src/exchangedb/pg_do_recoup.c85
-rw-r--r--src/exchangedb/pg_do_recoup.h56
-rw-r--r--src/exchangedb/pg_do_recoup_refresh.c79
-rw-r--r--src/exchangedb/pg_do_recoup_refresh.h57
-rw-r--r--src/exchangedb/pg_do_refund.c93
-rw-r--r--src/exchangedb/pg_do_refund.h52
-rw-r--r--src/exchangedb/pg_do_reserve_open.c101
-rw-r--r--src/exchangedb/pg_do_reserve_open.h64
-rw-r--r--src/exchangedb/pg_do_reserve_purse.c121
-rw-r--r--src/exchangedb/pg_do_reserve_purse.h57
-rw-r--r--src/exchangedb/pg_drain_kyc_alert.c59
-rw-r--r--src/exchangedb/pg_drain_kyc_alert.h40
-rw-r--r--src/exchangedb/pg_drop_tables.c58
-rw-r--r--src/exchangedb/pg_drop_tables.h38
-rw-r--r--src/exchangedb/pg_ensure_coin_known.c169
-rw-r--r--src/exchangedb/pg_ensure_coin_known.h45
-rw-r--r--src/exchangedb/pg_event_listen.c53
-rw-r--r--src/exchangedb/pg_event_listen.h45
-rw-r--r--src/exchangedb/pg_event_listen_cancel.c36
-rw-r--r--src/exchangedb/pg_event_listen_cancel.h38
-rw-r--r--src/exchangedb/pg_event_notify.c41
-rw-r--r--src/exchangedb/pg_event_notify.h42
-rw-r--r--src/exchangedb/pg_expire_purse.c69
-rw-r--r--src/exchangedb/pg_expire_purse.h41
-rw-r--r--src/exchangedb/pg_find_aggregation_transient.c150
-rw-r--r--src/exchangedb/pg_find_aggregation_transient.h43
-rw-r--r--src/exchangedb/pg_gc.c80
-rw-r--r--src/exchangedb/pg_gc.h39
-rw-r--r--src/exchangedb/pg_get_age_withdraw.c119
-rw-r--r--src/exchangedb/pg_get_age_withdraw.h45
-rw-r--r--src/exchangedb/pg_get_coin_denomination.c69
-rw-r--r--src/exchangedb/pg_get_coin_denomination.h43
-rw-r--r--src/exchangedb/pg_get_coin_transactions.c1144
-rw-r--r--src/exchangedb/pg_get_coin_transactions.h61
-rw-r--r--src/exchangedb/pg_get_denomination_info.c91
-rw-r--r--src/exchangedb/pg_get_denomination_info.h41
-rw-r--r--src/exchangedb/pg_get_denomination_revocation.c63
-rw-r--r--src/exchangedb/pg_get_denomination_revocation.h45
-rw-r--r--src/exchangedb/pg_get_drain_profit.c76
-rw-r--r--src/exchangedb/pg_get_drain_profit.h52
-rw-r--r--src/exchangedb/pg_get_expired_reserves.c174
-rw-r--r--src/exchangedb/pg_get_expired_reserves.h45
-rw-r--r--src/exchangedb/pg_get_extension_manifest.c67
-rw-r--r--src/exchangedb/pg_get_extension_manifest.h42
-rw-r--r--src/exchangedb/pg_get_global_fee.c86
-rw-r--r--src/exchangedb/pg_get_global_fee.h52
-rw-r--r--src/exchangedb/pg_get_global_fees.c165
-rw-r--r--src/exchangedb/pg_get_global_fees.h41
-rw-r--r--src/exchangedb/pg_get_known_coin.c67
-rw-r--r--src/exchangedb/pg_get_known_coin.h40
-rw-r--r--src/exchangedb/pg_get_link_data.c367
-rw-r--r--src/exchangedb/pg_get_link_data.h45
-rw-r--r--src/exchangedb/pg_get_melt.c124
-rw-r--r--src/exchangedb/pg_get_melt.h44
-rw-r--r--src/exchangedb/pg_get_old_coin_by_h_blind.c64
-rw-r--r--src/exchangedb/pg_get_old_coin_by_h_blind.h45
-rw-r--r--src/exchangedb/pg_get_pending_kyc_requirement_process.c66
-rw-r--r--src/exchangedb/pg_get_pending_kyc_requirement_process.h45
-rw-r--r--src/exchangedb/pg_get_policy_details.c64
-rw-r--r--src/exchangedb/pg_get_policy_details.h40
-rw-r--r--src/exchangedb/pg_get_purse_deposit.c84
-rw-r--r--src/exchangedb/pg_get_purse_deposit.h53
-rw-r--r--src/exchangedb/pg_get_purse_request.c79
-rw-r--r--src/exchangedb/pg_get_purse_request.h57
-rw-r--r--src/exchangedb/pg_get_ready_deposit.c74
-rw-r--r--src/exchangedb/pg_get_ready_deposit.h46
-rw-r--r--src/exchangedb/pg_get_refresh_reveal.c213
-rw-r--r--src/exchangedb/pg_get_refresh_reveal.h44
-rw-r--r--src/exchangedb/pg_get_reserve_balance.c55
-rw-r--r--src/exchangedb/pg_get_reserve_balance.h40
-rw-r--r--src/exchangedb/pg_get_reserve_by_h_blind.c63
-rw-r--r--src/exchangedb/pg_get_reserve_by_h_blind.h44
-rw-r--r--src/exchangedb/pg_get_reserve_history.c936
-rw-r--r--src/exchangedb/pg_get_reserve_history.h57
-rw-r--r--src/exchangedb/pg_get_signature_for_known_coin.c63
-rw-r--r--src/exchangedb/pg_get_signature_for_known_coin.h43
-rw-r--r--src/exchangedb/pg_get_unfinished_close_requests.c166
-rw-r--r--src/exchangedb/pg_get_unfinished_close_requests.h46
-rw-r--r--src/exchangedb/pg_get_wire_accounts.c169
-rw-r--r--src/exchangedb/pg_get_wire_accounts.h42
-rw-r--r--src/exchangedb/pg_get_wire_fee.c73
-rw-r--r--src/exchangedb/pg_get_wire_fee.h49
-rw-r--r--src/exchangedb/pg_get_wire_fees.c147
-rw-r--r--src/exchangedb/pg_get_wire_fees.h44
-rw-r--r--src/exchangedb/pg_get_wire_hash_for_contract.c81
-rw-r--r--src/exchangedb/pg_get_wire_hash_for_contract.h46
-rw-r--r--src/exchangedb/pg_get_withdraw_info.c79
-rw-r--r--src/exchangedb/pg_get_withdraw_info.h43
-rw-r--r--src/exchangedb/pg_have_deposit2.c117
-rw-r--r--src/exchangedb/pg_have_deposit2.h53
-rw-r--r--src/exchangedb/pg_helper.h149
-rw-r--r--src/exchangedb/pg_inject_auditor_triggers.c57
-rw-r--r--src/exchangedb/pg_inject_auditor_triggers.h41
-rw-r--r--src/exchangedb/pg_insert_aml_decision.c97
-rw-r--r--src/exchangedb/pg_insert_aml_decision.h64
-rw-r--r--src/exchangedb/pg_insert_aml_officer.c66
-rw-r--r--src/exchangedb/pg_insert_aml_officer.h56
-rw-r--r--src/exchangedb/pg_insert_auditor.c58
-rw-r--r--src/exchangedb/pg_insert_auditor.h45
-rw-r--r--src/exchangedb/pg_insert_auditor_denom_sig.c61
-rw-r--r--src/exchangedb/pg_insert_auditor_denom_sig.h43
-rw-r--r--src/exchangedb/pg_insert_close_request.c68
-rw-r--r--src/exchangedb/pg_insert_close_request.h52
-rw-r--r--src/exchangedb/pg_insert_contract.c93
-rw-r--r--src/exchangedb/pg_insert_contract.h47
-rw-r--r--src/exchangedb/pg_insert_denomination_info.c101
-rw-r--r--src/exchangedb/pg_insert_denomination_info.h42
-rw-r--r--src/exchangedb/pg_insert_denomination_revocation.c54
-rw-r--r--src/exchangedb/pg_insert_denomination_revocation.h42
-rw-r--r--src/exchangedb/pg_insert_drain_profit.c63
-rw-r--r--src/exchangedb/pg_insert_drain_profit.h50
-rw-r--r--src/exchangedb/pg_insert_global_fee.c137
-rw-r--r--src/exchangedb/pg_insert_global_fee.h50
-rw-r--r--src/exchangedb/pg_insert_kyc_attributes.c110
-rw-r--r--src/exchangedb/pg_insert_kyc_attributes.h69
-rw-r--r--src/exchangedb/pg_insert_kyc_failure.c82
-rw-r--r--src/exchangedb/pg_insert_kyc_failure.h50
-rw-r--r--src/exchangedb/pg_insert_kyc_requirement_for_account.c67
-rw-r--r--src/exchangedb/pg_insert_kyc_requirement_for_account.h47
-rw-r--r--src/exchangedb/pg_insert_kyc_requirement_process.c75
-rw-r--r--src/exchangedb/pg_insert_kyc_requirement_process.h49
-rw-r--r--src/exchangedb/pg_insert_partner.c69
-rw-r--r--src/exchangedb/pg_insert_partner.h51
-rw-r--r--src/exchangedb/pg_insert_purse_request.c126
-rw-r--r--src/exchangedb/pg_insert_purse_request.h61
-rw-r--r--src/exchangedb/pg_insert_records_by_table.c2334
-rw-r--r--src/exchangedb/pg_insert_records_by_table.h43
-rw-r--r--src/exchangedb/pg_insert_refresh_reveal.c109
-rw-r--r--src/exchangedb/pg_insert_refresh_reveal.h51
-rw-r--r--src/exchangedb/pg_insert_refund.c65
-rw-r--r--src/exchangedb/pg_insert_refund.h38
-rw-r--r--src/exchangedb/pg_insert_reserve_closed.c113
-rw-r--r--src/exchangedb/pg_insert_reserve_closed.h51
-rw-r--r--src/exchangedb/pg_insert_reserve_open_deposit.c67
-rw-r--r--src/exchangedb/pg_insert_reserve_open_deposit.h54
-rw-r--r--src/exchangedb/pg_insert_signkey_revocation.c53
-rw-r--r--src/exchangedb/pg_insert_signkey_revocation.h41
-rw-r--r--src/exchangedb/pg_insert_wire.c74
-rw-r--r--src/exchangedb/pg_insert_wire.h55
-rw-r--r--src/exchangedb/pg_insert_wire_fee.c108
-rw-r--r--src/exchangedb/pg_insert_wire_fee.h46
-rw-r--r--src/exchangedb/pg_iterate_active_auditors.c123
-rw-r--r--src/exchangedb/pg_iterate_active_auditors.h42
-rw-r--r--src/exchangedb/pg_iterate_active_signkeys.c144
-rw-r--r--src/exchangedb/pg_iterate_active_signkeys.h43
-rw-r--r--src/exchangedb/pg_iterate_auditor_denominations.c121
-rw-r--r--src/exchangedb/pg_iterate_auditor_denominations.h45
-rw-r--r--src/exchangedb/pg_iterate_denomination_info.c180
-rw-r--r--src/exchangedb/pg_iterate_denomination_info.h41
-rw-r--r--src/exchangedb/pg_iterate_denominations.c172
-rw-r--r--src/exchangedb/pg_iterate_denominations.h44
-rw-r--r--src/exchangedb/pg_iterate_kyc_reference.c129
-rw-r--r--src/exchangedb/pg_iterate_kyc_reference.h46
-rw-r--r--src/exchangedb/pg_iterate_reserve_close_info.c128
-rw-r--r--src/exchangedb/pg_iterate_reserve_close_info.h49
-rw-r--r--src/exchangedb/pg_kyc_provider_account_lookup.c64
-rw-r--r--src/exchangedb/pg_kyc_provider_account_lookup.h48
-rw-r--r--src/exchangedb/pg_lookup_aml_officer.c71
-rw-r--r--src/exchangedb/pg_lookup_aml_officer.h51
-rw-r--r--src/exchangedb/pg_lookup_auditor_status.c61
-rw-r--r--src/exchangedb/pg_lookup_auditor_status.h44
-rw-r--r--src/exchangedb/pg_lookup_auditor_timestamp.c57
-rw-r--r--src/exchangedb/pg_lookup_auditor_timestamp.h41
-rw-r--r--src/exchangedb/pg_lookup_denomination_key.c82
-rw-r--r--src/exchangedb/pg_lookup_denomination_key.h41
-rw-r--r--src/exchangedb/pg_lookup_global_fee_by_time.c182
-rw-r--r--src/exchangedb/pg_lookup_global_fee_by_time.h52
-rw-r--r--src/exchangedb/pg_lookup_kyc_process_by_account.c83
-rw-r--r--src/exchangedb/pg_lookup_kyc_process_by_account.h51
-rw-r--r--src/exchangedb/pg_lookup_kyc_requirement_by_row.c71
-rw-r--r--src/exchangedb/pg_lookup_kyc_requirement_by_row.h47
-rw-r--r--src/exchangedb/pg_lookup_records_by_table.c3607
-rw-r--r--src/exchangedb/pg_lookup_records_by_table.h49
-rw-r--r--src/exchangedb/pg_lookup_serial_by_table.c459
-rw-r--r--src/exchangedb/pg_lookup_serial_by_table.h45
-rw-r--r--src/exchangedb/pg_lookup_signing_key.c64
-rw-r--r--src/exchangedb/pg_lookup_signing_key.h42
-rw-r--r--src/exchangedb/pg_lookup_signkey_revocation.c59
-rw-r--r--src/exchangedb/pg_lookup_signkey_revocation.h42
-rw-r--r--src/exchangedb/pg_lookup_transfer_by_deposit.c239
-rw-r--r--src/exchangedb/pg_lookup_transfer_by_deposit.h63
-rw-r--r--src/exchangedb/pg_lookup_wire_fee_by_time.c156
-rw-r--r--src/exchangedb/pg_lookup_wire_fee_by_time.h76
-rw-r--r--src/exchangedb/pg_lookup_wire_timestamp.c56
-rw-r--r--src/exchangedb/pg_lookup_wire_timestamp.h40
-rw-r--r--src/exchangedb/pg_lookup_wire_transfer.c188
-rw-r--r--src/exchangedb/pg_lookup_wire_transfer.h45
-rw-r--r--src/exchangedb/pg_persist_policy_details.c78
-rw-r--r--src/exchangedb/pg_persist_policy_details.h47
-rw-r--r--src/exchangedb/pg_preflight.c69
-rw-r--r--src/exchangedb/pg_preflight.h44
-rw-r--r--src/exchangedb/pg_profit_drains_get_pending.c78
-rw-r--r--src/exchangedb/pg_profit_drains_get_pending.h52
-rw-r--r--src/exchangedb/pg_profit_drains_set_finished.c49
-rw-r--r--src/exchangedb/pg_profit_drains_set_finished.h40
-rw-r--r--src/exchangedb/pg_release_revolving_shard.c59
-rw-r--r--src/exchangedb/pg_release_revolving_shard.h44
-rw-r--r--src/exchangedb/pg_reserves_get.c61
-rw-r--r--src/exchangedb/pg_reserves_get.h40
-rw-r--r--src/exchangedb/pg_reserves_get_origin.c57
-rw-r--r--src/exchangedb/pg_reserves_get_origin.h41
-rw-r--r--src/exchangedb/pg_reserves_in_insert.c373
-rw-r--r--src/exchangedb/pg_reserves_in_insert.h47
-rw-r--r--src/exchangedb/pg_reserves_update.c53
-rw-r--r--src/exchangedb/pg_reserves_update.h40
-rw-r--r--src/exchangedb/pg_rollback.c50
-rw-r--r--src/exchangedb/pg_rollback.h36
-rw-r--r--src/exchangedb/pg_select_account_merges_above_serial_id.c192
-rw-r--r--src/exchangedb/pg_select_account_merges_above_serial_id.h46
-rw-r--r--src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.c154
-rw-r--r--src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.h48
-rw-r--r--src/exchangedb/pg_select_aggregation_transient.c66
-rw-r--r--src/exchangedb/pg_select_aggregation_transient.h47
-rw-r--r--src/exchangedb/pg_select_aggregations_above_serial.c137
-rw-r--r--src/exchangedb/pg_select_aggregations_above_serial.h47
-rw-r--r--src/exchangedb/pg_select_all_purse_decisions_above_serial_id.c146
-rw-r--r--src/exchangedb/pg_select_all_purse_decisions_above_serial_id.h47
-rw-r--r--src/exchangedb/pg_select_aml_history.c157
-rw-r--r--src/exchangedb/pg_select_aml_history.h46
-rw-r--r--src/exchangedb/pg_select_aml_process.c170
-rw-r--r--src/exchangedb/pg_select_aml_process.h52
-rw-r--r--src/exchangedb/pg_select_aml_threshold.c70
-rw-r--r--src/exchangedb/pg_select_aml_threshold.h48
-rw-r--r--src/exchangedb/pg_select_auditor_denom_sig.c66
-rw-r--r--src/exchangedb/pg_select_auditor_denom_sig.h43
-rw-r--r--src/exchangedb/pg_select_batch_deposits_missing_wire.c144
-rw-r--r--src/exchangedb/pg_select_batch_deposits_missing_wire.h44
-rw-r--r--src/exchangedb/pg_select_coin_deposits_above_serial_id.c204
-rw-r--r--src/exchangedb/pg_select_coin_deposits_above_serial_id.h44
-rw-r--r--src/exchangedb/pg_select_contract.c66
-rw-r--r--src/exchangedb/pg_select_contract.h47
-rw-r--r--src/exchangedb/pg_select_contract_by_purse.c63
-rw-r--r--src/exchangedb/pg_select_contract_by_purse.h42
-rw-r--r--src/exchangedb/pg_select_justification_for_missing_wire.c89
-rw-r--r--src/exchangedb/pg_select_justification_for_missing_wire.h49
-rw-r--r--src/exchangedb/pg_select_kyc_attributes.c156
-rw-r--r--src/exchangedb/pg_select_kyc_attributes.h45
-rw-r--r--src/exchangedb/pg_select_merge_amounts_for_kyc_check.c157
-rw-r--r--src/exchangedb/pg_select_merge_amounts_for_kyc_check.h47
-rw-r--r--src/exchangedb/pg_select_purse.c94
-rw-r--r--src/exchangedb/pg_select_purse.h57
-rw-r--r--src/exchangedb/pg_select_purse_by_merge_pub.c79
-rw-r--r--src/exchangedb/pg_select_purse_by_merge_pub.h54
-rw-r--r--src/exchangedb/pg_select_purse_decisions_above_serial_id.c162
-rw-r--r--src/exchangedb/pg_select_purse_decisions_above_serial_id.h47
-rw-r--r--src/exchangedb/pg_select_purse_deposits_above_serial_id.c201
-rw-r--r--src/exchangedb/pg_select_purse_deposits_above_serial_id.h47
-rw-r--r--src/exchangedb/pg_select_purse_deposits_by_purse.c153
-rw-r--r--src/exchangedb/pg_select_purse_deposits_by_purse.h44
-rw-r--r--src/exchangedb/pg_select_purse_merge.c80
-rw-r--r--src/exchangedb/pg_select_purse_merge.h51
-rw-r--r--src/exchangedb/pg_select_purse_merges_above_serial_id.c190
-rw-r--r--src/exchangedb/pg_select_purse_merges_above_serial_id.h46
-rw-r--r--src/exchangedb/pg_select_purse_requests_above_serial_id.c178
-rw-r--r--src/exchangedb/pg_select_purse_requests_above_serial_id.h47
-rw-r--r--src/exchangedb/pg_select_recoup_above_serial_id.c194
-rw-r--r--src/exchangedb/pg_select_recoup_above_serial_id.h44
-rw-r--r--src/exchangedb/pg_select_recoup_refresh_above_serial_id.c203
-rw-r--r--src/exchangedb/pg_select_recoup_refresh_above_serial_id.h45
-rw-r--r--src/exchangedb/pg_select_refreshes_above_serial_id.c179
-rw-r--r--src/exchangedb/pg_select_refreshes_above_serial_id.h45
-rw-r--r--src/exchangedb/pg_select_refunds_above_serial_id.c223
-rw-r--r--src/exchangedb/pg_select_refunds_above_serial_id.h45
-rw-r--r--src/exchangedb/pg_select_refunds_by_coin.c143
-rw-r--r--src/exchangedb/pg_select_refunds_by_coin.h48
-rw-r--r--src/exchangedb/pg_select_reserve_close_info.c63
-rw-r--r--src/exchangedb/pg_select_reserve_close_info.h49
-rw-r--r--src/exchangedb/pg_select_reserve_closed_above_serial_id.c178
-rw-r--r--src/exchangedb/pg_select_reserve_closed_above_serial_id.h47
-rw-r--r--src/exchangedb/pg_select_reserve_open_above_serial_id.c168
-rw-r--r--src/exchangedb/pg_select_reserve_open_above_serial_id.h47
-rw-r--r--src/exchangedb/pg_select_reserves_in_above_serial_id.c164
-rw-r--r--src/exchangedb/pg_select_reserves_in_above_serial_id.h44
-rw-r--r--src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.c168
-rw-r--r--src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.h46
-rw-r--r--src/exchangedb/pg_select_satisfied_kyc_processes.c141
-rw-r--r--src/exchangedb/pg_select_satisfied_kyc_processes.h47
-rw-r--r--src/exchangedb/pg_select_similar_kyc_attributes.c154
-rw-r--r--src/exchangedb/pg_select_similar_kyc_attributes.h45
-rw-r--r--src/exchangedb/pg_select_wire_out_above_serial_id.c157
-rw-r--r--src/exchangedb/pg_select_wire_out_above_serial_id.h45
-rw-r--r--src/exchangedb/pg_select_wire_out_above_serial_id_by_account.c161
-rw-r--r--src/exchangedb/pg_select_wire_out_above_serial_id_by_account.h47
-rw-r--r--src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.c158
-rw-r--r--src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.h48
-rw-r--r--src/exchangedb/pg_select_withdrawals_above_serial_id.c170
-rw-r--r--src/exchangedb/pg_select_withdrawals_above_serial_id.h45
-rw-r--r--src/exchangedb/pg_set_extension_manifest.c56
-rw-r--r--src/exchangedb/pg_set_extension_manifest.h43
-rw-r--r--src/exchangedb/pg_set_purse_balance.c52
-rw-r--r--src/exchangedb/pg_set_purse_balance.h43
-rw-r--r--src/exchangedb/pg_setup_wire_target.c54
-rw-r--r--src/exchangedb/pg_setup_wire_target.h43
-rw-r--r--src/exchangedb/pg_start.c56
-rw-r--r--src/exchangedb/pg_start.h40
-rw-r--r--src/exchangedb/pg_start_deferred_wire_out.c59
-rw-r--r--src/exchangedb/pg_start_deferred_wire_out.h39
-rw-r--r--src/exchangedb/pg_start_read_committed.c56
-rw-r--r--src/exchangedb/pg_start_read_committed.h39
-rw-r--r--src/exchangedb/pg_start_read_only.c57
-rw-r--r--src/exchangedb/pg_start_read_only.h40
-rw-r--r--src/exchangedb/pg_store_wire_transfer_out.c61
-rw-r--r--src/exchangedb/pg_store_wire_transfer_out.h48
-rw-r--r--src/exchangedb/pg_template.c26
-rw-r--r--src/exchangedb/pg_template.h29
-rwxr-xr-xsrc/exchangedb/pg_template.sh21
-rw-r--r--src/exchangedb/pg_test_aml_officer.c48
-rw-r--r--src/exchangedb/pg_test_aml_officer.h43
-rw-r--r--src/exchangedb/pg_trigger_aml_process.c58
-rw-r--r--src/exchangedb/pg_trigger_aml_process.h45
-rw-r--r--src/exchangedb/pg_update_aggregation_transient.c57
-rw-r--r--src/exchangedb/pg_update_aggregation_transient.h46
-rw-r--r--src/exchangedb/pg_update_auditor.c59
-rw-r--r--src/exchangedb/pg_update_auditor.h47
-rw-r--r--src/exchangedb/pg_update_kyc_process_by_row.c122
-rw-r--r--src/exchangedb/pg_update_kyc_process_by_row.h53
-rw-r--r--src/exchangedb/pg_update_wire.c81
-rw-r--r--src/exchangedb/pg_update_wire.h57
-rw-r--r--src/exchangedb/pg_wire_prepare_data_get.c140
-rw-r--r--src/exchangedb/pg_wire_prepare_data_get.h46
-rw-r--r--src/exchangedb/pg_wire_prepare_data_insert.c54
-rw-r--r--src/exchangedb/pg_wire_prepare_data_insert.h43
-rw-r--r--src/exchangedb/pg_wire_prepare_data_mark_failed.c48
-rw-r--r--src/exchangedb/pg_wire_prepare_data_mark_failed.h40
-rw-r--r--src/exchangedb/pg_wire_prepare_data_mark_finished.c47
-rw-r--r--src/exchangedb/pg_wire_prepare_data_mark_finished.h40
-rw-r--r--src/exchangedb/plugin_exchangedb_common.c104
-rw-r--r--src/exchangedb/plugin_exchangedb_common.h51
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c7887
-rw-r--r--src/exchangedb/procedures.sql.in49
-rw-r--r--src/exchangedb/spi/Makefile9
-rw-r--r--src/exchangedb/spi/README.md37
-rw-r--r--src/exchangedb/spi/own_test.c873
-rw-r--r--src/exchangedb/spi/own_test.control4
-rw-r--r--src/exchangedb/spi/own_test.sql201
-rw-r--r--src/exchangedb/spi/perf_own_test.c25
-rw-r--r--src/exchangedb/spi/pg_aggregate.c411
-rw-r--r--src/exchangedb/test-exchange-db-postgres.conf8
-rwxr-xr-xsrc/exchangedb/test-exchangedb-batch-reserves-in-insert-postgres210
-rwxr-xr-xsrc/exchangedb/test-exchangedb-by-j-postgres210
-rwxr-xr-xsrc/exchangedb/test-exchangedb-populate-link-data-postgres210
-rwxr-xr-xsrc/exchangedb/test-exchangedb-populate-ready-deposit-postgres210
-rwxr-xr-xsrc/exchangedb/test-exchangedb-populate-select-refunds-by-coin-postgres210
-rw-r--r--src/exchangedb/test_exchangedb.c2503
-rw-r--r--src/exchangedb/test_exchangedb_auditors.c169
-rw-r--r--src/exchangedb/test_exchangedb_by_j.c232
-rw-r--r--src/exchangedb/test_exchangedb_denomkeys.c217
-rw-r--r--src/exchangedb/test_exchangedb_fees.c155
-rw-r--r--src/exchangedb/test_exchangedb_signkeys.c99
-rwxr-xr-xsrc/exchangedb/test_idempotency.sh12
-rw-r--r--src/exchangedb/versioning.sql (renamed from src/auditordb/auditor-0000.sql)3
-rw-r--r--src/extensions/Makefile.am34
-rw-r--r--src/extensions/age_restriction/Makefile.am32
-rw-r--r--src/extensions/age_restriction/age_restriction.c256
-rw-r--r--src/extensions/age_restriction_helper.c73
-rw-r--r--src/extensions/extensions.c452
-rw-r--r--src/include/.gitignore1
-rw-r--r--src/include/Makefile.am13
-rw-r--r--src/include/gettext.h71
-rw-r--r--src/include/platform.h247
-rw-r--r--src/include/taler_amount_lib.h168
-rw-r--r--src/include/taler_attributes.h129
-rw-r--r--src/include/taler_auditor_service.h278
-rw-r--r--src/include/taler_auditordb_plugin.h1285
-rw-r--r--src/include/taler_bank_service.h333
-rw-r--r--src/include/taler_crypto_lib.h5367
-rw-r--r--src/include/taler_curl_lib.h22
-rw-r--r--src/include/taler_error_codes.h2160
-rw-r--r--src/include/taler_exchange_service.h6840
-rw-r--r--src/include/taler_exchangedb_lib.h484
-rw-r--r--src/include/taler_exchangedb_plugin.h5788
-rw-r--r--src/include/taler_extensions.h386
-rw-r--r--src/include/taler_extensions_policy.h205
-rw-r--r--src/include/taler_fakebank_lib.h112
-rw-r--r--src/include/taler_json_lib.h685
-rw-r--r--src/include/taler_kyclogic_lib.h374
-rw-r--r--src/include/taler_kyclogic_plugin.h384
-rw-r--r--src/include/taler_mhd_lib.h585
-rw-r--r--src/include/taler_pq_lib.h357
-rw-r--r--src/include/taler_signatures.h1422
-rw-r--r--src/include/taler_sq_lib.h99
-rw-r--r--src/include/taler_templating_lib.h130
-rw-r--r--src/include/taler_testing_lib.h2773
-rw-r--r--src/include/taler_twister_testing_lib.h19
-rw-r--r--src/include/taler_util.h542
-rw-r--r--src/json/Makefile.am26
-rw-r--r--src/json/i18n.c134
-rw-r--r--src/json/json.c847
-rw-r--r--src/json/json_helper.c1738
-rw-r--r--src/json/json_pack.c324
-rw-r--r--src/json/json_wire.c498
-rw-r--r--src/json/test_json.c370
-rw-r--r--src/json/test_json_wire.c62
-rw-r--r--src/kyclogic/Makefile.am144
-rw-r--r--src/kyclogic/kyclogic-kycaid.conf26
-rw-r--r--src/kyclogic/kyclogic-oauth2.conf35
-rw-r--r--src/kyclogic/kyclogic-persona.conf44
-rw-r--r--src/kyclogic/kyclogic.conf15
-rw-r--r--src/kyclogic/kyclogic_api.c1512
-rw-r--r--src/kyclogic/plugin_kyclogic_kycaid.c1480
-rw-r--r--src/kyclogic/plugin_kyclogic_oauth2.c1780
-rw-r--r--src/kyclogic/plugin_kyclogic_persona.c2268
-rw-r--r--src/kyclogic/plugin_kyclogic_template.c468
-rw-r--r--src/kyclogic/sample.conf33
-rwxr-xr-xsrc/kyclogic/taler-exchange-kyc-kycaid-converter.sh90
-rwxr-xr-xsrc/kyclogic/taler-exchange-kyc-oauth2-challenger.sh27
-rwxr-xr-xsrc/kyclogic/taler-exchange-kyc-oauth2-nda.sh30
-rwxr-xr-xsrc/kyclogic/taler-exchange-kyc-oauth2-test-converter.sh31
-rwxr-xr-xsrc/kyclogic/taler-exchange-kyc-persona-converter.sh57
-rw-r--r--src/kyclogic/taler-exchange-kyc-tester.c1646
-rw-r--r--src/lib/.gitignore1
-rw-r--r--src/lib/Makefile.am80
-rw-r--r--src/lib/auditor_api_curl_defaults.c26
-rw-r--r--src/lib/auditor_api_deposit_confirmation.c340
-rw-r--r--src/lib/auditor_api_exchanges.c263
-rw-r--r--src/lib/auditor_api_get_config.c278
-rw-r--r--src/lib/auditor_api_handle.c534
-rw-r--r--src/lib/auditor_api_handle.h59
-rw-r--r--src/lib/exchange_api_add_aml_decision.c246
-rw-r--r--src/lib/exchange_api_age_withdraw.c1125
-rw-r--r--src/lib/exchange_api_age_withdraw_reveal.c477
-rw-r--r--src/lib/exchange_api_auditor_add_denomination.c238
-rw-r--r--src/lib/exchange_api_batch_deposit.c726
-rw-r--r--src/lib/exchange_api_batch_withdraw.c463
-rw-r--r--src/lib/exchange_api_batch_withdraw2.c441
-rw-r--r--src/lib/exchange_api_coins_history.c1230
-rw-r--r--src/lib/exchange_api_common.c1259
-rw-r--r--src/lib/exchange_api_common.h180
-rw-r--r--src/lib/exchange_api_contracts_get.c262
-rw-r--r--src/lib/exchange_api_csr_melt.c320
-rw-r--r--src/lib/exchange_api_csr_withdraw.c281
-rw-r--r--src/lib/exchange_api_curl_defaults.c32
-rw-r--r--src/lib/exchange_api_curl_defaults.h1
-rw-r--r--src/lib/exchange_api_deposit.c727
-rw-r--r--src/lib/exchange_api_deposits_get.c333
-rw-r--r--src/lib/exchange_api_handle.c3333
-rw-r--r--src/lib/exchange_api_handle.h109
-rw-r--r--src/lib/exchange_api_kyc_check.c321
-rw-r--r--src/lib/exchange_api_kyc_proof.c217
-rw-r--r--src/lib/exchange_api_kyc_wallet.c230
-rw-r--r--src/lib/exchange_api_link.c340
-rw-r--r--src/lib/exchange_api_lookup_aml_decision.c417
-rw-r--r--src/lib/exchange_api_lookup_aml_decisions.c376
-rw-r--r--src/lib/exchange_api_management_add_partner.c218
-rw-r--r--src/lib/exchange_api_management_auditor_disable.c219
-rw-r--r--src/lib/exchange_api_management_auditor_enable.c224
-rw-r--r--src/lib/exchange_api_management_drain_profits.c213
-rw-r--r--src/lib/exchange_api_management_get_keys.c426
-rw-r--r--src/lib/exchange_api_management_post_extensions.c213
-rw-r--r--src/lib/exchange_api_management_post_keys.c237
-rw-r--r--src/lib/exchange_api_management_revoke_denomination_key.c222
-rw-r--r--src/lib/exchange_api_management_revoke_signing_key.c212
-rw-r--r--src/lib/exchange_api_management_set_global_fee.c236
-rw-r--r--src/lib/exchange_api_management_set_wire_fee.c228
-rw-r--r--src/lib/exchange_api_management_update_aml_officer.c230
-rw-r--r--src/lib/exchange_api_management_wire_disable.c221
-rw-r--r--src/lib/exchange_api_management_wire_enable.c253
-rw-r--r--src/lib/exchange_api_melt.c623
-rw-r--r--src/lib/exchange_api_purse_create_with_deposit.c656
-rw-r--r--src/lib/exchange_api_purse_create_with_merge.c580
-rw-r--r--src/lib/exchange_api_purse_delete.c243
-rw-r--r--src/lib/exchange_api_purse_deposit.c520
-rw-r--r--src/lib/exchange_api_purse_merge.c454
-rw-r--r--src/lib/exchange_api_purses_get.c302
-rw-r--r--src/lib/exchange_api_recoup.c282
-rw-r--r--src/lib/exchange_api_recoup_refresh.c374
-rw-r--r--src/lib/exchange_api_refresh_common.c714
-rw-r--r--src/lib/exchange_api_refresh_common.h165
-rw-r--r--src/lib/exchange_api_refreshes_reveal.c413
-rw-r--r--src/lib/exchange_api_refund.c464
-rw-r--r--src/lib/exchange_api_reserves_attest.c365
-rw-r--r--src/lib/exchange_api_reserves_close.c373
-rw-r--r--src/lib/exchange_api_reserves_get.c215
-rw-r--r--src/lib/exchange_api_reserves_get_attestable.c276
-rw-r--r--src/lib/exchange_api_reserves_history.c1145
-rw-r--r--src/lib/exchange_api_reserves_open.c567
-rw-r--r--src/lib/exchange_api_stefan.c328
-rw-r--r--src/lib/exchange_api_transfers_get.c286
-rw-r--r--src/lib/exchange_api_wire.c455
-rw-r--r--src/lib/exchange_api_withdraw.c626
-rw-r--r--src/lib/test_stefan.c206
-rw-r--r--src/mhd/Makefile.am12
-rw-r--r--src/mhd/mhd_config.c77
-rw-r--r--src/mhd/mhd_legal.c396
-rw-r--r--src/mhd/mhd_parsing.c524
-rw-r--r--src/mhd/mhd_responses.c290
-rw-r--r--src/mhd/mhd_run.c175
-rw-r--r--src/pq/Makefile.am18
-rw-r--r--src/pq/pq_common.c68
-rw-r--r--src/pq/pq_common.h148
-rw-r--r--src/pq/pq_query_helper.c1322
-rw-r--r--src/pq/pq_result_helper.c1499
-rw-r--r--src/pq/test_pq.c215
-rw-r--r--src/sq/Makefile.am40
-rw-r--r--src/sq/sq_query_helper.c175
-rw-r--r--src/sq/sq_result_helper.c237
-rw-r--r--src/sq/test_sq.c215
-rw-r--r--src/templating/.gitignore3
-rw-r--r--src/templating/AUTHORS38
-rw-r--r--src/templating/CHANGELOG.md161
-rw-r--r--src/templating/LICENSE.txt14
-rw-r--r--src/templating/Makefile.am132
-rw-r--r--src/templating/ORIGIN11
-rw-r--r--src/templating/README.md320
-rwxr-xr-xsrc/templating/dotest.sh26
-rw-r--r--src/templating/meson.build12
-rw-r--r--src/templating/mustach-cjson.c258
-rw-r--r--src/templating/mustach-cjson.h96
-rw-r--r--src/templating/mustach-jansson.c271
-rw-r--r--src/templating/mustach-jansson.h96
-rw-r--r--src/templating/mustach-json-c.c284
-rw-r--r--src/templating/mustach-json-c.h160
-rw-r--r--src/templating/mustach-original-Makefile305
-rw-r--r--src/templating/mustach-tool.c258
-rw-r--r--src/templating/mustach-wrap.c482
-rw-r--r--src/templating/mustach-wrap.h235
-rw-r--r--src/templating/mustach.1.gzbin0 -> 742 bytes
-rw-r--r--src/templating/mustach.1.scd60
-rw-r--r--src/templating/mustach.c561
-rw-r--r--src/templating/mustach.h319
-rw-r--r--src/templating/pkgcfgs35
-rwxr-xr-xsrc/templating/run-original-tests.sh19
-rw-r--r--src/templating/templating_api.c524
-rw-r--r--src/templating/test-specs/test-specs-cjson.ref425
-rw-r--r--src/templating/test-specs/test-specs-jansson.ref429
-rw-r--r--src/templating/test-specs/test-specs-json-c.ref425
-rw-r--r--src/templating/test-specs/test-specs.c520
-rw-r--r--src/templating/test1/.gitignore2
-rw-r--r--src/templating/test1/Makefile8
-rw-r--r--src/templating/test1/json23
-rw-r--r--src/templating/test1/must49
-rw-r--r--src/templating/test1/resu.ref41
-rw-r--r--src/templating/test1/vg.ref14
-rw-r--r--src/templating/test2/.gitignore2
-rw-r--r--src/templating/test2/Makefile8
-rw-r--r--src/templating/test2/json9
-rw-r--r--src/templating/test2/must17
-rw-r--r--src/templating/test2/resu.ref7
-rw-r--r--src/templating/test2/vg.ref14
-rw-r--r--src/templating/test3/.gitignore2
-rw-r--r--src/templating/test3/Makefile8
-rw-r--r--src/templating/test3/json7
-rw-r--r--src/templating/test3/must15
-rw-r--r--src/templating/test3/resu.ref13
-rw-r--r--src/templating/test3/vg.ref14
-rw-r--r--src/templating/test4/.gitignore2
-rw-r--r--src/templating/test4/Makefile8
-rw-r--r--src/templating/test4/json13
-rw-r--r--src/templating/test4/must58
-rw-r--r--src/templating/test4/resu.ref50
-rw-r--r--src/templating/test4/vg.ref14
-rw-r--r--src/templating/test5/.gitignore2
-rw-r--r--src/templating/test5/Makefile8
-rw-r--r--src/templating/test5/json23
-rw-r--r--src/templating/test5/must23
-rw-r--r--src/templating/test5/must214
-rw-r--r--src/templating/test5/must2.mustache1
-rw-r--r--src/templating/test5/must3.mustache17
-rw-r--r--src/templating/test5/resu.ref38
-rw-r--r--src/templating/test5/vg.ref14
-rw-r--r--src/templating/test6/.gitignore3
-rw-r--r--src/templating/test6/Makefile12
-rw-r--r--src/templating/test6/json23
-rw-r--r--src/templating/test6/must43
-rw-r--r--src/templating/test6/resu.ref93
-rw-r--r--src/templating/test6/test-custom-write.c149
-rw-r--r--src/templating/test6/vg.ref14
-rw-r--r--src/templating/test7/Makefile8
-rw-r--r--src/templating/test7/base.mustache2
-rw-r--r--src/templating/test7/json8
-rw-r--r--src/templating/test7/node.mustache4
-rw-r--r--src/templating/test7/resu.ref7
-rw-r--r--src/templating/test7/vg.ref14
-rw-r--r--src/templating/test8/.gitignore2
-rw-r--r--src/templating/test8/Makefile8
-rw-r--r--src/templating/test8/json8
-rw-r--r--src/templating/test8/must6
-rw-r--r--src/templating/test8/resu.ref6
-rw-r--r--src/templating/test8/vg.ref14
-rw-r--r--src/templating/test_mustach_jansson.c125
-rw-r--r--src/testing/.gitignore57
-rw-r--r--src/testing/Makefile.am481
-rw-r--r--src/testing/coins-cs.conf118
-rw-r--r--src/testing/coins-rsa.conf128
-rwxr-xr-xsrc/testing/taler-unified-setup.sh934
-rw-r--r--src/testing/test-taler-exchange-aggregator-postgres.conf101
-rw-r--r--src/testing/test-taler-exchange-wirewatch-postgres.conf87
-rw-r--r--src/testing/test_auditor_api-cs.conf4
-rw-r--r--src/testing/test_auditor_api-rsa.conf4
-rw-r--r--src/testing/test_auditor_api.c287
-rw-r--r--src/testing/test_auditor_api.conf200
-rw-r--r--src/testing/test_auditor_api_expire_reserve_now-cs.conf (renamed from src/testing/test_auditor_api_expire_reserve_now.conf)2
-rw-r--r--src/testing/test_auditor_api_expire_reserve_now-rsa.conf (renamed from src/testing/test_exchange_api_expire_reserve_now.conf)2
-rw-r--r--src/testing/test_auditor_api_version.c48
-rw-r--r--src/testing/test_bank_api.c196
-rw-r--r--src/testing/test_bank_api.conf23
-rw-r--r--src/testing/test_bank_api_fakebank.conf21
-rw-r--r--src/testing/test_bank_api_fakebank_twisted.conf21
-rw-r--r--src/testing/test_bank_api_nexus.conf35
-rw-r--r--src/testing/test_bank_api_pybank.conf17
-rw-r--r--src/testing/test_bank_api_twisted.c205
-rw-r--r--src/testing/test_exchange_api-cs.conf4
-rw-r--r--src/testing/test_exchange_api-rsa.conf4
-rw-r--r--src/testing/test_exchange_api-twisted.conf (renamed from src/testing/test_bank_api_pybank_twisted.conf)36
-rw-r--r--src/testing/test_exchange_api.c806
-rw-r--r--src/testing/test_exchange_api.conf248
-rw-r--r--src/testing/test_exchange_api_age_restriction-cs.conf4
-rw-r--r--src/testing/test_exchange_api_age_restriction-rsa.conf4
-rw-r--r--src/testing/test_exchange_api_age_restriction.c370
-rw-r--r--src/testing/test_exchange_api_age_restriction.conf119
-rw-r--r--src/testing/test_exchange_api_conflicts-cs.conf4
-rw-r--r--src/testing/test_exchange_api_conflicts-rsa.conf4
-rw-r--r--src/testing/test_exchange_api_conflicts.c312
-rw-r--r--src/testing/test_exchange_api_conflicts.conf81
-rw-r--r--src/testing/test_exchange_api_expire_reserve_now-cs.conf4
-rw-r--r--src/testing/test_exchange_api_expire_reserve_now-rsa.conf4
-rw-r--r--src/testing/test_exchange_api_home/.config/taler/account-2.json4
-rw-r--r--src/testing/test_exchange_api_home/.local/share/taler/auditor/offline-keys/auditor.privbin0 -> 32 bytes
-rw-r--r--src/testing/test_exchange_api_home/.local/share/taler/exchange-offline/master.privbin0 -> 32 bytes
-rw-r--r--src/testing/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv1
-rw-r--r--src/testing/test_exchange_api_home/taler/auditor/offline-keys/auditor.priv1
-rw-r--r--src/testing/test_exchange_api_interpreter_on-off.c128
-rw-r--r--src/testing/test_exchange_api_keys_cherry_picking-cs.conf18
-rw-r--r--src/testing/test_exchange_api_keys_cherry_picking-rsa.conf19
-rw-r--r--src/testing/test_exchange_api_keys_cherry_picking.c235
-rw-r--r--src/testing/test_exchange_api_keys_cherry_picking.conf176
-rw-r--r--src/testing/test_exchange_api_keys_cherry_picking_extended.conf5
-rw-r--r--src/testing/test_exchange_api_keys_cherry_picking_extended_2.conf5
-rw-r--r--src/testing/test_exchange_api_keys_cherry_picking_home/.config/taler/x-taler-bank.json4
-rw-r--r--src/testing/test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/offline-keys/master.priv1
-rw-r--r--src/testing/test_exchange_api_overlapping_keys_bug.c99
-rw-r--r--src/testing/test_exchange_api_revocation.c212
-rw-r--r--src/testing/test_exchange_api_twisted-cs.conf4
-rw-r--r--src/testing/test_exchange_api_twisted-rsa.conf4
-rw-r--r--src/testing/test_exchange_api_twisted.c306
-rw-r--r--src/testing/test_exchange_api_twisted.conf189
-rw-r--r--src/testing/test_exchange_management_api.c194
-rw-r--r--src/testing/test_exchange_p2p.c557
-rw-r--r--src/testing/test_kyc_api.c581
-rw-r--r--src/testing/test_kyc_api.conf42
-rw-r--r--src/testing/test_taler_exchange_aggregator.c291
-rw-r--r--src/testing/test_taler_exchange_httpd_home/.config/taler/account-1.json4
-rw-r--r--src/testing/test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv1
-rw-r--r--src/testing/test_taler_exchange_wirewatch.c120
-rw-r--r--src/testing/testing_api_cmd_age_withdraw.c756
-rw-r--r--src/testing/testing_api_cmd_auditor_add.c224
-rw-r--r--src/testing/testing_api_cmd_auditor_add_denom_sig.c254
-rw-r--r--src/testing/testing_api_cmd_auditor_del.c215
-rw-r--r--src/testing/testing_api_cmd_auditor_deposit_confirmation.c265
-rw-r--r--src/testing/testing_api_cmd_auditor_exchanges.c383
-rw-r--r--src/testing/testing_api_cmd_auditor_exec_auditor.c15
-rw-r--r--src/testing/testing_api_cmd_auditor_exec_auditor_dbinit.c12
-rw-r--r--src/testing/testing_api_cmd_bank_admin_add_incoming.c397
-rw-r--r--src/testing/testing_api_cmd_bank_admin_check.c60
-rw-r--r--src/testing/testing_api_cmd_bank_check.c92
-rw-r--r--src/testing/testing_api_cmd_bank_check_empty.c26
-rw-r--r--src/testing/testing_api_cmd_bank_history_credit.c608
-rw-r--r--src/testing/testing_api_cmd_bank_history_debit.c591
-rw-r--r--src/testing/testing_api_cmd_bank_transfer.c97
-rw-r--r--src/testing/testing_api_cmd_batch.c107
-rw-r--r--src/testing/testing_api_cmd_batch_deposit.c656
-rw-r--r--src/testing/testing_api_cmd_batch_withdraw.c557
-rw-r--r--src/testing/testing_api_cmd_check_aml_decision.c270
-rw-r--r--src/testing/testing_api_cmd_check_aml_decisions.c204
-rw-r--r--src/testing/testing_api_cmd_check_keys.c358
-rw-r--r--src/testing/testing_api_cmd_coin_history.c609
-rw-r--r--src/testing/testing_api_cmd_common.c64
-rw-r--r--src/testing/testing_api_cmd_contract_get.c316
-rw-r--r--src/testing/testing_api_cmd_deposit.c536
-rw-r--r--src/testing/testing_api_cmd_deposits_get.c146
-rw-r--r--src/testing/testing_api_cmd_exec_aggregator.c46
-rw-r--r--src/testing/testing_api_cmd_exec_auditor-offline.c163
-rw-r--r--src/testing/testing_api_cmd_exec_auditor-sign.c233
-rw-r--r--src/testing/testing_api_cmd_exec_closer.c48
-rw-r--r--src/testing/testing_api_cmd_exec_expire.c162
-rw-r--r--src/testing/testing_api_cmd_exec_keyup.c236
-rw-r--r--src/testing/testing_api_cmd_exec_router.c161
-rw-r--r--src/testing/testing_api_cmd_exec_transfer.c19
-rw-r--r--src/testing/testing_api_cmd_exec_wget.c158
-rw-r--r--src/testing/testing_api_cmd_exec_wirewatch.c49
-rw-r--r--src/testing/testing_api_cmd_get_auditor.c286
-rw-r--r--src/testing/testing_api_cmd_get_exchange.c411
-rw-r--r--src/testing/testing_api_cmd_insert_deposit.c363
-rw-r--r--src/testing/testing_api_cmd_kyc_check_get.c243
-rw-r--r--src/testing/testing_api_cmd_kyc_proof.c259
-rw-r--r--src/testing/testing_api_cmd_kyc_wallet_get.c290
-rw-r--r--src/testing/testing_api_cmd_oauth.c412
-rw-r--r--src/testing/testing_api_cmd_offline_sign_extensions.c164
-rw-r--r--src/testing/testing_api_cmd_offline_sign_global_fees.c230
-rw-r--r--src/testing/testing_api_cmd_offline_sign_keys.c165
-rw-r--r--src/testing/testing_api_cmd_offline_sign_wire_fees.c182
-rw-r--r--src/testing/testing_api_cmd_purse_create_deposit.c450
-rw-r--r--src/testing/testing_api_cmd_purse_delete.c189
-rw-r--r--src/testing/testing_api_cmd_purse_deposit.c491
-rw-r--r--src/testing/testing_api_cmd_purse_get.c367
-rw-r--r--src/testing/testing_api_cmd_purse_merge.c436
-rw-r--r--src/testing/testing_api_cmd_recoup.c241
-rw-r--r--src/testing/testing_api_cmd_recoup_refresh.c441
-rw-r--r--src/testing/testing_api_cmd_refresh.c851
-rw-r--r--src/testing/testing_api_cmd_refund.c229
-rw-r--r--src/testing/testing_api_cmd_reserve_attest.c263
-rw-r--r--src/testing/testing_api_cmd_reserve_close.c260
-rw-r--r--src/testing/testing_api_cmd_reserve_get.c390
-rw-r--r--src/testing/testing_api_cmd_reserve_get_attestable.c242
-rw-r--r--src/testing/testing_api_cmd_reserve_history.c586
-rw-r--r--src/testing/testing_api_cmd_reserve_open.c349
-rw-r--r--src/testing/testing_api_cmd_reserve_purse.c371
-rw-r--r--src/testing/testing_api_cmd_revoke.c41
-rw-r--r--src/testing/testing_api_cmd_revoke_denom_key.c256
-rw-r--r--src/testing/testing_api_cmd_revoke_sign_key.c256
-rw-r--r--src/testing/testing_api_cmd_run_fakebank.c214
-rw-r--r--src/testing/testing_api_cmd_serialize_keys.c295
-rw-r--r--src/testing/testing_api_cmd_set_officer.c301
-rw-r--r--src/testing/testing_api_cmd_set_wire_fee.c258
-rw-r--r--src/testing/testing_api_cmd_stat.c103
-rw-r--r--src/testing/testing_api_cmd_status.c426
-rw-r--r--src/testing/testing_api_cmd_system_start.c395
-rw-r--r--src/testing/testing_api_cmd_take_aml_decision.c321
-rw-r--r--src/testing/testing_api_cmd_transfer_get.c351
-rw-r--r--src/testing/testing_api_cmd_twister_exec_client.c329
-rw-r--r--src/testing/testing_api_cmd_wire.c133
-rw-r--r--src/testing/testing_api_cmd_wire_add.c244
-rw-r--r--src/testing/testing_api_cmd_wire_del.c220
-rw-r--r--src/testing/testing_api_cmd_withdraw.c446
-rw-r--r--src/testing/testing_api_helpers_auditor.c229
-rw-r--r--src/testing/testing_api_helpers_bank.c503
-rw-r--r--src/testing/testing_api_helpers_exchange.c1000
-rw-r--r--src/testing/testing_api_loop.c978
-rw-r--r--src/testing/testing_api_misc.c394
-rw-r--r--src/testing/testing_api_trait_amount.c76
-rw-r--r--src/testing/testing_api_trait_blinding_key.c77
-rw-r--r--src/testing/testing_api_trait_cmd.c80
-rw-r--r--src/testing/testing_api_trait_coin_priv.c78
-rw-r--r--src/testing/testing_api_trait_contract.c74
-rw-r--r--src/testing/testing_api_trait_denom_pub.c77
-rw-r--r--src/testing/testing_api_trait_denom_sig.c79
-rw-r--r--src/testing/testing_api_trait_exchange_pub.c77
-rw-r--r--src/testing/testing_api_trait_exchange_sig.c77
-rw-r--r--src/testing/testing_api_trait_fresh_coin.c77
-rw-r--r--src/testing/testing_api_trait_json.c123
-rw-r--r--src/testing/testing_api_trait_merchant_key.c127
-rw-r--r--src/testing/testing_api_trait_number.c149
-rw-r--r--src/testing/testing_api_trait_process.c82
-rw-r--r--src/testing/testing_api_trait_reserve_history.c76
-rw-r--r--src/testing/testing_api_trait_reserve_priv.c76
-rw-r--r--src/testing/testing_api_trait_reserve_pub.c78
-rw-r--r--src/testing/testing_api_trait_string.c231
-rw-r--r--src/testing/testing_api_trait_time.c76
-rw-r--r--src/testing/testing_api_trait_wtid.c76
-rw-r--r--src/testing/testing_api_traits.c82
-rw-r--r--src/testing/testing_api_twister_helpers.c53
-rw-r--r--src/testing/valgrind.h7165
-rw-r--r--src/util/.gitignore10
-rw-r--r--src/util/Makefile.am123
-rw-r--r--src/util/age_restriction.c795
-rw-r--r--src/util/aml_signatures.c201
-rw-r--r--src/util/amount.c428
-rw-r--r--src/util/auditor_signatures.c187
-rw-r--r--src/util/bench_age_restriction.c208
-rw-r--r--src/util/config.c472
-rw-r--r--src/util/conversion.c405
-rw-r--r--src/util/crypto.c450
-rw-r--r--src/util/crypto_confirmation.c293
-rw-r--r--src/util/crypto_contract.c661
-rw-r--r--src/util/crypto_helper_common.c51
-rw-r--r--src/util/crypto_helper_common.h41
-rw-r--r--src/util/crypto_helper_cs.c1316
-rw-r--r--src/util/crypto_helper_esign.c555
-rw-r--r--src/util/crypto_helper_rsa.c916
-rw-r--r--src/util/crypto_wire.c159
-rw-r--r--src/util/currencies.conf89
-rw-r--r--src/util/denom.c473
-rwxr-xr-xsrc/util/do_bench_age_restriction8
-rw-r--r--src/util/exchange_signatures.c1894
-rw-r--r--src/util/iban.c317
-rw-r--r--src/util/lang.c73
-rw-r--r--src/util/merchant_signatures.c352
-rw-r--r--src/util/mhd.c10
-rw-r--r--src/util/offline_signatures.c1388
-rw-r--r--src/util/os_installation.c2
-rw-r--r--src/util/paths.conf6
-rw-r--r--src/util/payto.c641
-rw-r--r--src/util/secmod_common.c586
-rw-r--r--src/util/secmod_common.h263
-rw-r--r--src/util/secmod_signatures.c248
-rw-r--r--src/util/taler-config.c73
-rw-r--r--src/util/taler-config.in5
-rw-r--r--src/util/taler-exchange-secmod-cs.c2344
-rw-r--r--src/util/taler-exchange-secmod-cs.conf23
-rw-r--r--src/util/taler-exchange-secmod-cs.h319
-rw-r--r--src/util/taler-exchange-secmod-eddsa.c1213
-rw-r--r--src/util/taler-exchange-secmod-eddsa.conf26
-rw-r--r--src/util/taler-exchange-secmod-eddsa.h202
-rw-r--r--src/util/taler-exchange-secmod-rsa.c2133
-rw-r--r--src/util/taler-exchange-secmod-rsa.conf26
-rw-r--r--src/util/taler-exchange-secmod-rsa.h223
-rw-r--r--src/util/test_age_restriction.c442
-rw-r--r--src/util/test_amount.c98
-rw-r--r--src/util/test_conversion.c149
-rwxr-xr-xsrc/util/test_conversion.sh5
-rw-r--r--src/util/test_crypto.c485
-rw-r--r--src/util/test_helper_cs.c1177
-rw-r--r--src/util/test_helper_cs.conf11
-rw-r--r--src/util/test_helper_eddsa.c557
-rw-r--r--src/util/test_helper_eddsa.conf9
-rw-r--r--src/util/test_helper_rsa.c1002
-rw-r--r--src/util/test_helper_rsa.conf12
-rw-r--r--src/util/test_payto.c75
-rw-r--r--src/util/tv_age_restriction.c271
-rw-r--r--src/util/tv_age_restriction.json9764
-rw-r--r--src/util/url.c178
-rw-r--r--src/util/util.c425
-rw-r--r--src/util/wallet_signatures.c1830
-rw-r--r--src/util/yna.c88
1755 files changed, 308417 insertions, 84048 deletions
diff --git a/.gitignore b/.gitignore
index 09c0fe457..bda987c5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,10 @@
*.gcno
*.gcda
*.mo
+*.blg
+*.bbl
.dirstamp
+m4/*.m4
doc/coverage/
doc/taler-exchange.cps
aclocal.m4
@@ -18,6 +21,7 @@ depcomp
missing
taler_config.h.in
install-sh
+config.cache
config.log
config.status
stamp-h1
@@ -27,12 +31,14 @@ config.sub
libtool
ltmain.sh
test-driver
-m4/
GPATH
GRTAGS
GTAGS
*.swp
-src/lib/test_exchange_api
+.DS_Store
+src/include/taler_error_codes.h
+src/testing/test_exchange_api_rsa
+src/testing/test_exchange_api_cs
doc/doxygen/doxygen_sqlite3.db
src/auditor/taler-auditor-dbinit
src/auditor/taler-auditor-sign
@@ -41,12 +47,10 @@ src/bank-lib/test_bank_api_with_fakebank
src/bank-lib/test_bank_api_with_fakebank_twisted
src/bank-lib/test_bank_api_with_pybank_twisted
src/lib/test_exchange_api
-
src/testing/test_taler_exchange_httpd_home/.local/share/taler/exchange/live-keys/
src/testing/test_taler_exchange_httpd_home/.local/share/taler/exchange/wirefees/
src/testing/test_taler_exchange_httpd_home/.local/share/taler/auditor/
src/testing/test_taler_exchange_httpd_home/.local/share/taler/auditors/
-
src/testing/test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/live-keys/
src/testing/test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/wirefees/
src/testing/test_exchange_api_keys_cherry_picking_home/.local/share/taler/auditor/
@@ -71,6 +75,9 @@ src/exchange-tools/taler-wire
src/exchangedb/perf-exchangedb
src/benchmark/taler-exchange-benchmark
src/benchmark/auditor.in
+src/benchmark/exchange_benchmark_home/.local/share/taler/exchange-offline/
+src/benchmark/exchange_benchmark_home/.local/share/taler/exchange-secmod-eddsa/
+src/benchmark/exchange_benchmark_home/.local/share/taler/exchange-secmod-rsa/
src/benchmark/exchange_benchmark_home/.local/share/taler/exchange/live-keys/*
src/benchmark/exchange_benchmark_home/.local/share/taler/auditors/
src/benchmark/exchange_benchmark_home/.local/share/taler/auditor/
@@ -81,6 +88,8 @@ src/wire-plugins/test_ebics_wireformat
src/wire-plugins/test_wire_plugin
src/wire-plugins/test_wire_plugin_transactions_taler_bank
src/pq/test_pq
+src/sq/test_sq
+src/util/test_age_restriction
src/util/test_amount
src/util/test_crypto
src/util/test_json
@@ -109,8 +118,11 @@ doc/manual/manual.vr
doc/prebuilt/*
contrib/taler-exchange.tag
doxygen-doc/
-src/testing/test_exchange_api_keys_cherry_picking
+src/testing/test_exchange_api_keys_cherry_picking_cs
+src/testing/test_exchange_api_keys_cherry_picking_rsa
+src/auditor/taler-auditor-sync
src/auditor/taler-wire-auditor
+src/auditor/generate_auditordb_home/
contrib/auditor-report.aux
contrib/auditor-report.log
contrib/auditor-report.tex
@@ -118,11 +130,46 @@ contrib/auditor-report.pdf
src/bank-lib/taler-bank-transfer
src/bank-lib/test_bank_api_twisted
src/testing/test_exchange_api
+src/testing/test_auditor_api_cs
+src/testing/test_auditor_api_rsa
+src/testing/test_exchange_api_overlapping_keys_bug_cs
+src/testing/test_exchange_api_overlapping_keys_bug_rsa
+src/testing/test_exchange_api_home/.local/share/taler/exchange/revocations/
src/testing/test_auditor_api
+src/testing/test_auditor_api_version
+src/testing/test_exchange_api_keys_cherry_picking
src/testing/test_exchange_api_overlapping_keys_bug
-src/testing/test_exchange_api_home/.local/share/taler/exchange/revocations/
+src/testing/test_exchange_api_revocation
+src/testing/test_exchange_management_api
src/wire-plugins/test_wire_plugin_legacy_taler_bank
uncrustify.cfg
vgcore.*
tags
/.vscode
+src/testing/test_bank_api_with_nexus
+src/util/taler_error_codes.c
+.deps/
+src/bank-lib/taler-wire-gateway-client
+build-aux/
+src/testing/test_exchange_api_home/.local/share/taler/exchange/offline-keys/secm_tofus.pub
+src/testing/test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/offline-keys/secm_tofus.pub
+src/testing/test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/secm_tofus.pub
+debian/taler-auditor.debhelper.log
+debian/libtalerexchange-dev.debhelper.log
+po/Makevars.template
+po/POTFILES
+po/stamp-po
+po/taler-exchange.pot
+po/remove-potcdate.sed
+src/include/taler_dbevents.h
+src/bank-lib/taler-exchange-wire-gateway-client
+src/exchange/taler-exchange-drain
+src/kyclogic/taler-exchange-kyc-tester
+src/auditor/exchange-httpd-drain.err
+src/templating/libmustach.a
+contrib/tos/conf.py
+contrib/pp/conf.py
+src/auditordb/test_auditordb_checkpoints-postgres
+src/auditordb/test_auditordb_checkpoints_postgres
+/.cache
+/compile_commands.json
diff --git a/.gitmodules b/.gitmodules
index de5e33058..01c07d992 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,7 +1,11 @@
-[submodule "doc/api"]
- path = doc/api
- url = git@git.taler.net:api
[submodule "doc/prebuilt"]
path = doc/prebuilt
url = https://git.taler.net/docs.git
branch = prebuilt
+[submodule "contrib/gana"]
+ path = contrib/gana
+ url = https://git.gnunet.org/gana.git
+[submodule "contrib/wallet-core"]
+ path = contrib/wallet-core
+ url = https://git.taler.net/wallet-core.git
+ branch = prebuilt
diff --git a/ABOUT-NLS b/ABOUT-NLS
new file mode 100644
index 000000000..3cc828658
--- /dev/null
+++ b/ABOUT-NLS
@@ -0,0 +1,1379 @@
+1 Notes on the Free Translation Project
+***************************************
+
+Free software is going international! The Free Translation Project is a
+way to get maintainers of free software, translators, and users all
+together, so that free software will gradually become able to speak many
+languages. A few packages already provide translations for their
+messages.
+
+ If you found this 'ABOUT-NLS' file inside a distribution, you may
+assume that the distributed package does use GNU 'gettext' internally,
+itself available at your nearest GNU archive site. But you do _not_
+need to install GNU 'gettext' prior to configuring, installing or using
+this package with messages translated.
+
+ Installers will find here some useful hints. These notes also
+explain how users should proceed for getting the programs to use the
+available translations. They tell how people wanting to contribute and
+work on translations can contact the appropriate team.
+
+1.1 INSTALL Matters
+===================
+
+Some packages are "localizable" when properly installed; the programs
+they contain can be made to speak your own native language. Most such
+packages use GNU 'gettext'. Other packages have their own ways to
+internationalization, predating GNU 'gettext'.
+
+ By default, this package will be installed to allow translation of
+messages. It will automatically detect whether the system already
+provides the GNU 'gettext' functions. Installers may use special
+options at configuration time for changing the default behaviour. The
+command:
+
+ ./configure --disable-nls
+
+will _totally_ disable translation of messages.
+
+ When you already have GNU 'gettext' installed on your system and run
+configure without an option for your new package, 'configure' will
+probably detect the previously built and installed 'libintl' library and
+will decide to use it. If not, you may have to to use the
+'--with-libintl-prefix' option to tell 'configure' where to look for it.
+
+ Internationalized packages usually have many 'po/LL.po' files, where
+LL gives an ISO 639 two-letter code identifying the language. Unless
+translations have been forbidden at 'configure' time by using the
+'--disable-nls' switch, all available translations are installed
+together with the package. However, the environment variable 'LINGUAS'
+may be set, prior to configuration, to limit the installed set.
+'LINGUAS' should then contain a space separated list of two-letter
+codes, stating which languages are allowed.
+
+1.2 Using This Package
+======================
+
+As a user, if your language has been installed for this package, you
+only have to set the 'LANG' environment variable to the appropriate
+'LL_CC' combination. If you happen to have the 'LC_ALL' or some other
+'LC_xxx' environment variables set, you should unset them before setting
+'LANG', otherwise the setting of 'LANG' will not have the desired
+effect. Here 'LL' is an ISO 639 two-letter language code, and 'CC' is
+an ISO 3166 two-letter country code. For example, let's suppose that
+you speak German and live in Germany. At the shell prompt, merely
+execute 'setenv LANG de_DE' (in 'csh'), 'export LANG; LANG=de_DE' (in
+'sh') or 'export LANG=de_DE' (in 'bash'). This can be done from your
+'.login' or '.profile' file, once and for all.
+
+ You might think that the country code specification is redundant.
+But in fact, some languages have dialects in different countries. For
+example, 'de_AT' is used for Austria, and 'pt_BR' for Brazil. The
+country code serves to distinguish the dialects.
+
+ The locale naming convention of 'LL_CC', with 'LL' denoting the
+language and 'CC' denoting the country, is the one use on systems based
+on GNU libc. On other systems, some variations of this scheme are used,
+such as 'LL' or 'LL_CC.ENCODING'. You can get the list of locales
+supported by your system for your language by running the command
+'locale -a | grep '^LL''.
+
+ Not all programs have translations for all languages. By default, an
+English message is shown in place of a nonexistent translation. If you
+understand other languages, you can set up a priority list of languages.
+This is done through a different environment variable, called
+'LANGUAGE'. GNU 'gettext' gives preference to 'LANGUAGE' over 'LANG'
+for the purpose of message handling, but you still need to have 'LANG'
+set to the primary language; this is required by other parts of the
+system libraries. For example, some Swedish users who would rather read
+translations in German than English for when Swedish is not available,
+set 'LANGUAGE' to 'sv:de' while leaving 'LANG' to 'sv_SE'.
+
+ Special advice for Norwegian users: The language code for Norwegian
+bokma*l changed from 'no' to 'nb' recently (in 2003). During the
+transition period, while some message catalogs for this language are
+installed under 'nb' and some older ones under 'no', it's recommended
+for Norwegian users to set 'LANGUAGE' to 'nb:no' so that both newer and
+older translations are used.
+
+ In the 'LANGUAGE' environment variable, but not in the 'LANG'
+environment variable, 'LL_CC' combinations can be abbreviated as 'LL' to
+denote the language's main dialect. For example, 'de' is equivalent to
+'de_DE' (German as spoken in Germany), and 'pt' to 'pt_PT' (Portuguese
+as spoken in Portugal) in this context.
+
+1.3 Translating Teams
+=====================
+
+For the Free Translation Project to be a success, we need interested
+people who like their own language and write it well, and who are also
+able to synergize with other translators speaking the same language.
+Each translation team has its own mailing list. The up-to-date list of
+teams can be found at the Free Translation Project's homepage,
+'http://translationproject.org/', in the "Teams" area.
+
+ If you'd like to volunteer to _work_ at translating messages, you
+should become a member of the translating team for your own language.
+The subscribing address is _not_ the same as the list itself, it has
+'-request' appended. For example, speakers of Swedish can send a
+message to 'sv-request@li.org', having this message body:
+
+ subscribe
+
+ Keep in mind that team members are expected to participate _actively_
+in translations, or at solving translational difficulties, rather than
+merely lurking around. If your team does not exist yet and you want to
+start one, or if you are unsure about what to do or how to get started,
+please write to 'coordinator@translationproject.org' to reach the
+coordinator for all translator teams.
+
+ The English team is special. It works at improving and uniformizing
+the terminology in use. Proven linguistic skills are praised more than
+programming skills, here.
+
+1.4 Available Packages
+======================
+
+Languages are not equally supported in all packages. The following
+matrix shows the current state of internationalization, as of Jun 2014.
+The matrix shows, in regard of each package, for which languages PO
+files have been submitted to translation coordination, with a
+translation percentage of at least 50%.
+
+ Ready PO files af am an ar as ast az be bg bn bn_IN bs ca crh cs
+ +---------------------------------------------------+
+ a2ps | [] [] [] |
+ aegis | |
+ anubis | |
+ aspell | [] [] [] |
+ bash | [] [] [] |
+ bfd | |
+ binutils | [] |
+ bison | |
+ bison-runtime | [] |
+ buzztrax | [] |
+ ccd2cue | |
+ ccide | |
+ cflow | |
+ clisp | |
+ coreutils | [] [] |
+ cpio | |
+ cppi | |
+ cpplib | [] |
+ cryptsetup | [] |
+ datamash | |
+ denemo | [] [] |
+ dfarc | [] |
+ dialog | [] [] [] |
+ dico | |
+ diffutils | [] |
+ dink | [] |
+ direvent | |
+ doodle | [] |
+ dos2unix | |
+ dos2unix-man | |
+ e2fsprogs | [] [] |
+ enscript | [] |
+ exif | [] |
+ fetchmail | [] [] |
+ findutils | [] |
+ flex | [] |
+ freedink | [] [] |
+ fusionforge | |
+ gas | |
+ gawk | [] |
+ gcal | [] |
+ gcc | |
+ gdbm | |
+ gettext-examples | [] [] [] [] [] |
+ gettext-runtime | [] [] [] |
+ gettext-tools | [] [] |
+ gjay | |
+ glunarclock | [] [] [] |
+ gnubiff | [] |
+ gnubik | [] |
+ gnucash | () () [] |
+ gnuchess | |
+ gnulib | [] |
+ gnunet | |
+ gnunet-gtk | |
+ gold | |
+ gphoto2 | [] |
+ gprof | [] |
+ gramadoir | |
+ grep | [] [] [] |
+ grub | [] |
+ gsasl | |
+ gss | |
+ gst-plugins-bad | [] [] |
+ gst-plugins-base | [] [] [] |
+ gst-plugins-good | [] [] [] |
+ gst-plugins-ugly | [] [] [] |
+ gstreamer | [] [] [] [] |
+ gtick | [] |
+ gtkam | [] [] |
+ gtkspell | [] [] [] [] [] |
+ guix | |
+ guix-packages | |
+ gutenprint | [] |
+ hello | [] |
+ help2man | |
+ help2man-texi | |
+ hylafax | |
+ idutils | |
+ iso_15924 | [] |
+ iso_3166 | [] [] [] [] [] [] [] [] [] [] |
+ iso_3166_2 | |
+ iso_4217 | [] |
+ iso_639 | [] [] [] [] [] [] [] [] [] |
+ iso_639_3 | [] [] |
+ iso_639_5 | |
+ jwhois | |
+ kbd | [] |
+ klavaro | [] [] [] [] [] |
+ ld | [] |
+ leafpad | [] [] [] [] |
+ libc | [] [] [] |
+ libexif | () |
+ libextractor | |
+ libgnutls | [] |
+ libgphoto2 | [] |
+ libgphoto2_port | [] |
+ libgsasl | |
+ libiconv | [] [] |
+ libidn | [] |
+ liferea | [] [] [] [] |
+ lilypond | [] [] |
+ lordsawar | [] |
+ lprng | |
+ lynx | [] [] |
+ m4 | [] |
+ mailfromd | |
+ mailutils | |
+ make | [] |
+ man-db | [] [] |
+ man-db-manpages | |
+ midi-instruments | [] [] [] |
+ minicom | [] |
+ mkisofs | [] |
+ myserver | [] |
+ nano | [] [] [] |
+ opcodes | |
+ parted | [] |
+ pies | |
+ pnmixer | |
+ popt | [] |
+ procps-ng | |
+ procps-ng-man | |
+ psmisc | [] |
+ pspp | [] |
+ pushover | [] |
+ pwdutils | |
+ pyspread | |
+ radius | [] |
+ recode | [] [] [] |
+ recutils | |
+ rpm | |
+ rush | |
+ sarg | |
+ sed | [] [] [] [] |
+ sharutils | [] |
+ shishi | |
+ skribilo | |
+ solfege | [] [] |
+ solfege-manual | |
+ spotmachine | |
+ sudo | [] [] |
+ sudoers | [] [] |
+ sysstat | [] |
+ tar | [] [] [] |
+ texinfo | [] [] |
+ texinfo_document | [] [] |
+ tigervnc | [] |
+ tin | |
+ tin-man | |
+ tracgoogleappsa... | |
+ trader | |
+ util-linux | [] |
+ ve | |
+ vice | |
+ vmm | |
+ vorbis-tools | [] |
+ wastesedge | |
+ wcd | |
+ wcd-man | |
+ wdiff | [] [] |
+ wget | [] |
+ wyslij-po | |
+ xboard | |
+ xdg-user-dirs | [] [] [] [] [] [] [] [] [] [] |
+ xkeyboard-config | [] [] [] |
+ +---------------------------------------------------+
+ af am an ar as ast az be bg bn bn_IN bs ca crh cs
+ 4 0 2 5 3 11 0 8 25 3 3 1 55 4 74
+
+ da de el en en_GB en_ZA eo es et eu fa fi fr
+ +--------------------------------------------------+
+ a2ps | [] [] [] [] [] [] [] [] [] |
+ aegis | [] [] [] [] |
+ anubis | [] [] [] [] [] |
+ aspell | [] [] [] [] [] [] [] |
+ bash | [] [] [] |
+ bfd | [] [] [] [] |
+ binutils | [] [] [] |
+ bison | [] [] [] [] [] [] [] [] |
+ bison-runtime | [] [] [] [] [] [] [] [] |
+ buzztrax | [] [] [] [] |
+ ccd2cue | [] [] [] [] |
+ ccide | [] [] [] [] [] [] |
+ cflow | [] [] [] [] [] |
+ clisp | [] [] [] [] [] |
+ coreutils | [] [] [] [] [] |
+ cpio | [] [] [] [] [] |
+ cppi | [] [] [] [] [] |
+ cpplib | [] [] [] [] [] [] |
+ cryptsetup | [] [] [] [] [] |
+ datamash | [] [] [] [] |
+ denemo | [] |
+ dfarc | [] [] [] [] [] [] |
+ dialog | [] [] [] [] [] [] [] [] [] |
+ dico | [] [] [] [] |
+ diffutils | [] [] [] [] [] [] |
+ dink | [] [] [] [] [] [] |
+ direvent | [] [] [] [] |
+ doodle | [] [] [] [] |
+ dos2unix | [] [] [] [] [] |
+ dos2unix-man | [] [] [] |
+ e2fsprogs | [] [] [] [] [] |
+ enscript | [] [] [] [] [] [] |
+ exif | [] [] [] [] [] [] |
+ fetchmail | [] () [] [] [] [] [] |
+ findutils | [] [] [] [] [] [] [] [] |
+ flex | [] [] [] [] [] [] |
+ freedink | [] [] [] [] [] [] [] [] |
+ fusionforge | [] [] [] |
+ gas | [] [] [] |
+ gawk | [] [] [] [] [] |
+ gcal | [] [] [] [] |
+ gcc | [] |
+ gdbm | [] [] [] [] [] |
+ gettext-examples | [] [] [] [] [] [] [] |
+ gettext-runtime | [] [] [] [] [] [] |
+ gettext-tools | [] [] [] [] [] |
+ gjay | [] [] [] [] |
+ glunarclock | [] [] [] [] [] |
+ gnubiff | () [] [] () |
+ gnubik | [] [] [] [] [] |
+ gnucash | [] () () () () () () |
+ gnuchess | [] [] [] [] |
+ gnulib | [] [] [] [] [] [] [] |
+ gnunet | [] |
+ gnunet-gtk | [] |
+ gold | [] [] [] |
+ gphoto2 | [] () [] [] |
+ gprof | [] [] [] [] [] [] |
+ gramadoir | [] [] [] [] [] |
+ grep | [] [] [] [] [] [] [] |
+ grub | [] [] [] [] [] |
+ gsasl | [] [] [] [] [] |
+ gss | [] [] [] [] [] |
+ gst-plugins-bad | [] [] [] |
+ gst-plugins-base | [] [] [] [] [] [] |
+ gst-plugins-good | [] [] [] [] [] [] [] |
+ gst-plugins-ugly | [] [] [] [] [] [] [] [] |
+ gstreamer | [] [] [] [] [] [] [] |
+ gtick | [] () [] [] [] |
+ gtkam | [] () [] [] [] [] |
+ gtkspell | [] [] [] [] [] [] [] [] |
+ guix | [] [] |
+ guix-packages | |
+ gutenprint | [] [] [] [] |
+ hello | [] [] [] [] [] [] [] [] |
+ help2man | [] [] [] [] [] [] [] |
+ help2man-texi | [] [] [] |
+ hylafax | [] [] |
+ idutils | [] [] [] [] [] |
+ iso_15924 | [] () [] [] () [] () |
+ iso_3166 | [] () [] [] [] [] () [] () |
+ iso_3166_2 | [] () () () |
+ iso_4217 | [] () [] [] [] () [] () |
+ iso_639 | [] () [] [] () [] () |
+ iso_639_3 | () () () |
+ iso_639_5 | () () () |
+ jwhois | [] [] [] [] [] |
+ kbd | [] [] [] [] [] [] |
+ klavaro | [] [] [] [] [] [] [] |
+ ld | [] [] [] [] |
+ leafpad | [] [] [] [] [] [] [] [] |
+ libc | [] [] [] [] [] |
+ libexif | [] [] () [] [] |
+ libextractor | [] |
+ libgnutls | [] [] [] [] |
+ libgphoto2 | [] () [] |
+ libgphoto2_port | [] () [] [] [] [] |
+ libgsasl | [] [] [] [] [] |
+ libiconv | [] [] [] [] [] [] [] |
+ libidn | [] [] [] [] [] |
+ liferea | [] () [] [] [] [] [] |
+ lilypond | [] [] [] [] [] [] |
+ lordsawar | [] [] |
+ lprng | |
+ lynx | [] [] [] [] [] [] |
+ m4 | [] [] [] [] [] [] |
+ mailfromd | [] |
+ mailutils | [] [] [] [] |
+ make | [] [] [] [] [] |
+ man-db | [] [] [] [] |
+ man-db-manpages | [] [] |
+ midi-instruments | [] [] [] [] [] [] [] [] [] |
+ minicom | [] [] [] [] [] |
+ mkisofs | [] [] [] |
+ myserver | [] [] [] [] |
+ nano | [] [] [] [] [] [] [] |
+ opcodes | [] [] [] [] [] |
+ parted | [] [] [] |
+ pies | [] |
+ pnmixer | [] [] |
+ popt | [] [] [] [] [] [] |
+ procps-ng | [] [] |
+ procps-ng-man | [] [] |
+ psmisc | [] [] [] [] [] [] [] |
+ pspp | [] [] [] |
+ pushover | () [] [] [] |
+ pwdutils | [] [] [] |
+ pyspread | [] [] [] |
+ radius | [] [] |
+ recode | [] [] [] [] [] [] [] |
+ recutils | [] [] [] [] |
+ rpm | [] [] [] [] [] |
+ rush | [] [] [] |
+ sarg | [] [] |
+ sed | [] [] [] [] [] [] [] [] |
+ sharutils | [] [] [] [] |
+ shishi | [] [] [] |
+ skribilo | [] [] [] |
+ solfege | [] [] [] [] [] [] [] [] |
+ solfege-manual | [] [] [] [] [] |
+ spotmachine | [] [] [] [] [] |
+ sudo | [] [] [] [] [] [] |
+ sudoers | [] [] [] [] [] [] |
+ sysstat | [] [] [] [] [] [] |
+ tar | [] [] [] [] [] [] [] |
+ texinfo | [] [] [] [] [] |
+ texinfo_document | [] [] [] [] |
+ tigervnc | [] [] [] [] [] [] |
+ tin | [] [] [] [] |
+ tin-man | [] |
+ tracgoogleappsa... | [] [] [] [] [] |
+ trader | [] [] [] [] [] [] |
+ util-linux | [] [] [] [] |
+ ve | [] [] [] [] [] |
+ vice | () () () |
+ vmm | [] [] |
+ vorbis-tools | [] [] [] [] |
+ wastesedge | [] |
+ wcd | [] [] [] [] |
+ wcd-man | [] |
+ wdiff | [] [] [] [] [] [] [] |
+ wget | [] [] [] [] [] [] |
+ wyslij-po | [] [] [] [] |
+ xboard | [] [] [] [] |
+ xdg-user-dirs | [] [] [] [] [] [] [] [] [] [] |
+ xkeyboard-config | [] [] [] [] [] [] [] |
+ +--------------------------------------------------+
+ da de el en en_GB en_ZA eo es et eu fa fi fr
+ 119 131 32 1 6 0 94 95 22 13 4 102 139
+
+ ga gd gl gu he hi hr hu hy ia id is it ja ka kk
+ +-------------------------------------------------+
+ a2ps | [] [] [] [] |
+ aegis | [] |
+ anubis | [] [] [] [] |
+ aspell | [] [] [] [] [] |
+ bash | [] [] [] [] |
+ bfd | [] [] |
+ binutils | [] [] [] |
+ bison | [] |
+ bison-runtime | [] [] [] [] [] [] [] [] |
+ buzztrax | |
+ ccd2cue | [] |
+ ccide | [] [] |
+ cflow | [] [] [] |
+ clisp | |
+ coreutils | [] [] |
+ cpio | [] [] [] [] [] [] |
+ cppi | [] [] [] [] [] |
+ cpplib | [] [] |
+ cryptsetup | [] |
+ datamash | |
+ denemo | [] |
+ dfarc | [] [] [] |
+ dialog | [] [] [] [] [] [] [] [] [] [] |
+ dico | |
+ diffutils | [] [] [] [] |
+ dink | [] |
+ direvent | [] |
+ doodle | [] [] |
+ dos2unix | [] [] |
+ dos2unix-man | |
+ e2fsprogs | [] [] |
+ enscript | [] [] [] |
+ exif | [] [] [] [] [] [] |
+ fetchmail | [] [] [] |
+ findutils | [] [] [] [] [] [] [] |
+ flex | [] |
+ freedink | [] [] [] [] |
+ fusionforge | |
+ gas | [] |
+ gawk | [] () [] |
+ gcal | |
+ gcc | |
+ gdbm | |
+ gettext-examples | [] [] [] [] [] [] [] |
+ gettext-runtime | [] [] [] [] [] [] [] |
+ gettext-tools | [] [] [] |
+ gjay | [] |
+ glunarclock | [] [] [] [] [] [] |
+ gnubiff | [] [] () |
+ gnubik | [] [] [] |
+ gnucash | () () () () () |
+ gnuchess | |
+ gnulib | [] [] [] [] [] |
+ gnunet | |
+ gnunet-gtk | |
+ gold | [] [] |
+ gphoto2 | [] [] [] [] |
+ gprof | [] [] [] [] |
+ gramadoir | [] [] [] |
+ grep | [] [] [] [] [] [] [] |
+ grub | [] [] [] |
+ gsasl | [] [] [] [] [] |
+ gss | [] [] [] [] [] |
+ gst-plugins-bad | [] [] [] |
+ gst-plugins-base | [] [] [] [] |
+ gst-plugins-good | [] [] [] [] [] [] |
+ gst-plugins-ugly | [] [] [] [] [] [] |
+ gstreamer | [] [] [] [] [] |
+ gtick | [] [] [] [] [] |
+ gtkam | [] [] [] [] [] |
+ gtkspell | [] [] [] [] [] [] [] [] [] [] |
+ guix | |
+ guix-packages | |
+ gutenprint | [] [] [] |
+ hello | [] [] [] [] [] |
+ help2man | [] [] [] |
+ help2man-texi | |
+ hylafax | [] |
+ idutils | [] [] |
+ iso_15924 | [] [] [] [] [] [] |
+ iso_3166 | [] [] [] [] [] [] [] [] [] [] [] [] [] |
+ iso_3166_2 | [] [] |
+ iso_4217 | [] [] [] [] [] [] |
+ iso_639 | [] [] [] [] [] [] [] [] [] |
+ iso_639_3 | [] [] |
+ iso_639_5 | |
+ jwhois | [] [] [] [] |
+ kbd | [] [] [] |
+ klavaro | [] [] [] [] [] |
+ ld | [] [] [] [] |
+ leafpad | [] [] [] [] [] [] [] () |
+ libc | [] [] [] [] [] |
+ libexif | [] |
+ libextractor | |
+ libgnutls | [] |
+ libgphoto2 | [] [] |
+ libgphoto2_port | [] [] |
+ libgsasl | [] [] [] [] |
+ libiconv | [] [] [] [] [] [] [] |
+ libidn | [] [] [] [] |
+ liferea | [] [] [] [] [] |
+ lilypond | [] |
+ lordsawar | |
+ lprng | [] |
+ lynx | [] [] [] [] |
+ m4 | [] [] [] [] [] |
+ mailfromd | |
+ mailutils | |
+ make | [] [] [] [] |
+ man-db | [] [] |
+ man-db-manpages | [] [] |
+ midi-instruments | [] [] [] [] [] [] [] [] [] |
+ minicom | [] [] [] |
+ mkisofs | [] [] |
+ myserver | [] |
+ nano | [] [] [] [] [] [] |
+ opcodes | [] [] [] |
+ parted | [] [] [] [] [] |
+ pies | |
+ pnmixer | [] [] |
+ popt | [] [] [] [] [] [] [] [] [] [] |
+ procps-ng | |
+ procps-ng-man | |
+ psmisc | [] [] [] [] |
+ pspp | [] [] |
+ pushover | [] |
+ pwdutils | [] |
+ pyspread | |
+ radius | [] |
+ recode | [] [] [] [] [] [] [] |
+ recutils | |
+ rpm | [] |
+ rush | [] |
+ sarg | |
+ sed | [] [] [] [] [] [] [] |
+ sharutils | |
+ shishi | |
+ skribilo | [] |
+ solfege | [] [] |
+ solfege-manual | |
+ spotmachine | |
+ sudo | [] [] [] [] |
+ sudoers | [] [] [] |
+ sysstat | [] [] [] [] |
+ tar | [] [] [] [] [] [] |
+ texinfo | [] [] [] |
+ texinfo_document | [] [] [] |
+ tigervnc | |
+ tin | |
+ tin-man | |
+ tracgoogleappsa... | [] [] [] [] |
+ trader | [] [] |
+ util-linux | [] |
+ ve | [] |
+ vice | () () |
+ vmm | |
+ vorbis-tools | [] [] |
+ wastesedge | [] |
+ wcd | |
+ wcd-man | |
+ wdiff | [] [] [] |
+ wget | [] [] [] [] |
+ wyslij-po | [] [] [] |
+ xboard | |
+ xdg-user-dirs | [] [] [] [] [] [] [] [] [] [] [] [] [] [] |
+ xkeyboard-config | [] [] [] [] [] [] |
+ +-------------------------------------------------+
+ ga gd gl gu he hi hr hu hy ia id is it ja ka kk
+ 35 2 47 4 8 2 60 71 2 6 81 11 87 57 0 3
+
+ kn ko ku ky lg lt lv mk ml mn mr ms mt nb ne nl
+ +--------------------------------------------------+
+ a2ps | [] [] |
+ aegis | [] |
+ anubis | [] [] [] |
+ aspell | [] [] |
+ bash | [] [] |
+ bfd | |
+ binutils | |
+ bison | [] |
+ bison-runtime | [] [] [] [] [] [] |
+ buzztrax | |
+ ccd2cue | |
+ ccide | [] [] |
+ cflow | [] |
+ clisp | [] |
+ coreutils | [] [] |
+ cpio | [] |
+ cppi | |
+ cpplib | [] |
+ cryptsetup | [] |
+ datamash | [] [] |
+ denemo | |
+ dfarc | [] [] |
+ dialog | [] [] [] [] [] [] |
+ dico | |
+ diffutils | [] [] [] |
+ dink | [] |
+ direvent | [] |
+ doodle | [] |
+ dos2unix | [] [] |
+ dos2unix-man | [] |
+ e2fsprogs | [] |
+ enscript | [] |
+ exif | [] [] [] |
+ fetchmail | [] |
+ findutils | [] [] |
+ flex | [] |
+ freedink | [] [] |
+ fusionforge | |
+ gas | |
+ gawk | [] |
+ gcal | |
+ gcc | |
+ gdbm | |
+ gettext-examples | [] [] [] [] [] [] |
+ gettext-runtime | [] [] [] |
+ gettext-tools | [] |
+ gjay | |
+ glunarclock | [] [] |
+ gnubiff | [] |
+ gnubik | [] [] |
+ gnucash | () () () () () () () [] |
+ gnuchess | [] [] |
+ gnulib | [] |
+ gnunet | |
+ gnunet-gtk | |
+ gold | |
+ gphoto2 | [] |
+ gprof | [] [] |
+ gramadoir | [] |
+ grep | [] [] |
+ grub | [] [] [] |
+ gsasl | [] |
+ gss | |
+ gst-plugins-bad | [] [] [] |
+ gst-plugins-base | [] [] [] |
+ gst-plugins-good | [] [] [] [] |
+ gst-plugins-ugly | [] [] [] [] [] |
+ gstreamer | [] [] [] |
+ gtick | [] |
+ gtkam | [] [] |
+ gtkspell | [] [] [] [] [] [] [] |
+ guix | |
+ guix-packages | |
+ gutenprint | [] |
+ hello | [] [] [] |
+ help2man | [] |
+ help2man-texi | |
+ hylafax | [] |
+ idutils | [] |
+ iso_15924 | () [] [] |
+ iso_3166 | [] [] [] () [] [] [] [] [] [] |
+ iso_3166_2 | () [] |
+ iso_4217 | () [] [] [] |
+ iso_639 | [] [] () [] [] [] [] |
+ iso_639_3 | [] () [] |
+ iso_639_5 | () |
+ jwhois | [] [] |
+ kbd | [] |
+ klavaro | [] [] |
+ ld | |
+ leafpad | [] [] [] [] [] |
+ libc | [] [] |
+ libexif | [] |
+ libextractor | [] |
+ libgnutls | [] [] |
+ libgphoto2 | [] |
+ libgphoto2_port | [] |
+ libgsasl | [] |
+ libiconv | [] [] |
+ libidn | [] |
+ liferea | [] [] [] |
+ lilypond | [] |
+ lordsawar | |
+ lprng | |
+ lynx | [] |
+ m4 | [] |
+ mailfromd | |
+ mailutils | |
+ make | [] [] |
+ man-db | [] |
+ man-db-manpages | [] |
+ midi-instruments | [] [] [] [] [] [] [] |
+ minicom | [] |
+ mkisofs | [] |
+ myserver | |
+ nano | [] [] [] |
+ opcodes | [] |
+ parted | [] [] |
+ pies | |
+ pnmixer | [] |
+ popt | [] [] [] [] [] |
+ procps-ng | |
+ procps-ng-man | |
+ psmisc | [] |
+ pspp | [] [] |
+ pushover | |
+ pwdutils | [] |
+ pyspread | |
+ radius | [] |
+ recode | [] [] |
+ recutils | [] |
+ rpm | [] |
+ rush | [] |
+ sarg | |
+ sed | [] [] |
+ sharutils | [] |
+ shishi | |
+ skribilo | |
+ solfege | [] [] |
+ solfege-manual | [] |
+ spotmachine | [] |
+ sudo | [] [] [] |
+ sudoers | [] [] [] |
+ sysstat | [] [] |
+ tar | [] [] [] |
+ texinfo | [] |
+ texinfo_document | [] |
+ tigervnc | [] |
+ tin | |
+ tin-man | |
+ tracgoogleappsa... | [] [] [] |
+ trader | [] |
+ util-linux | [] |
+ ve | [] |
+ vice | [] |
+ vmm | [] |
+ vorbis-tools | [] |
+ wastesedge | [] |
+ wcd | [] |
+ wcd-man | [] |
+ wdiff | [] |
+ wget | [] [] |
+ wyslij-po | [] |
+ xboard | [] |
+ xdg-user-dirs | [] [] [] [] [] [] [] [] [] [] [] |
+ xkeyboard-config | [] [] [] |
+ +--------------------------------------------------+
+ kn ko ku ky lg lt lv mk ml mn mr ms mt nb ne nl
+ 5 15 4 6 0 13 23 3 3 3 4 11 2 42 1 125
+
+ nn or pa pl ps pt pt_BR ro ru rw sk sl sq sr
+ +------------------------------------------------+
+ a2ps | [] [] [] [] [] [] [] |
+ aegis | [] [] |
+ anubis | [] [] [] |
+ aspell | [] [] [] [] [] [] [] |
+ bash | [] [] [] [] [] [] |
+ bfd | [] [] |
+ binutils | [] [] |
+ bison | [] [] [] |
+ bison-runtime | [] [] [] [] [] [] [] [] |
+ buzztrax | [] |
+ ccd2cue | [] [] |
+ ccide | [] [] [] |
+ cflow | [] [] [] |
+ clisp | [] |
+ coreutils | [] [] [] [] |
+ cpio | [] [] [] |
+ cppi | [] [] [] |
+ cpplib | [] [] [] |
+ cryptsetup | [] [] [] |
+ datamash | [] [] |
+ denemo | |
+ dfarc | [] [] [] |
+ dialog | [] [] [] [] [] [] [] |
+ dico | [] |
+ diffutils | [] [] [] |
+ dink | |
+ direvent | [] [] [] |
+ doodle | [] [] |
+ dos2unix | [] [] [] [] |
+ dos2unix-man | [] [] |
+ e2fsprogs | [] |
+ enscript | [] [] [] [] [] [] |
+ exif | [] [] [] [] [] [] |
+ fetchmail | [] [] [] |
+ findutils | [] [] [] [] [] [] |
+ flex | [] [] [] [] [] |
+ freedink | [] [] [] [] [] |
+ fusionforge | |
+ gas | |
+ gawk | [] |
+ gcal | |
+ gcc | |
+ gdbm | [] [] [] |
+ gettext-examples | [] [] [] [] [] [] [] [] |
+ gettext-runtime | [] [] [] [] [] [] [] [] [] |
+ gettext-tools | [] [] [] [] [] [] [] |
+ gjay | [] |
+ glunarclock | [] [] [] [] [] [] |
+ gnubiff | [] |
+ gnubik | [] [] [] [] |
+ gnucash | () () () () () [] |
+ gnuchess | [] [] |
+ gnulib | [] [] [] [] [] |
+ gnunet | |
+ gnunet-gtk | |
+ gold | |
+ gphoto2 | [] [] [] [] [] |
+ gprof | [] [] [] [] |
+ gramadoir | [] [] |
+ grep | [] [] [] [] [] [] |
+ grub | [] [] [] [] [] |
+ gsasl | [] [] [] |
+ gss | [] [] [] [] |
+ gst-plugins-bad | [] [] [] [] [] |
+ gst-plugins-base | [] [] [] [] [] [] |
+ gst-plugins-good | [] [] [] [] [] [] [] |
+ gst-plugins-ugly | [] [] [] [] [] [] [] |
+ gstreamer | [] [] [] [] [] [] [] |
+ gtick | [] [] [] [] [] |
+ gtkam | [] [] [] [] [] [] |
+ gtkspell | [] [] [] [] [] [] [] [] [] |
+ guix | |
+ guix-packages | |
+ gutenprint | [] [] |
+ hello | [] [] [] [] [] [] |
+ help2man | [] [] [] [] |
+ help2man-texi | [] |
+ hylafax | |
+ idutils | [] [] [] |
+ iso_15924 | [] () [] [] [] [] |
+ iso_3166 | [] [] [] [] () [] [] [] [] [] [] [] [] |
+ iso_3166_2 | [] () [] |
+ iso_4217 | [] [] () [] [] [] [] [] |
+ iso_639 | [] [] [] () [] [] [] [] [] [] |
+ iso_639_3 | [] () |
+ iso_639_5 | () [] |
+ jwhois | [] [] [] [] |
+ kbd | [] [] |
+ klavaro | [] [] [] [] [] |
+ ld | |
+ leafpad | [] [] [] [] [] [] [] [] |
+ libc | [] [] [] |
+ libexif | [] () [] |
+ libextractor | [] |
+ libgnutls | [] |
+ libgphoto2 | [] |
+ libgphoto2_port | [] [] [] [] [] |
+ libgsasl | [] [] [] [] |
+ libiconv | [] [] [] [] [] |
+ libidn | [] [] [] |
+ liferea | [] [] [] [] () [] [] |
+ lilypond | |
+ lordsawar | |
+ lprng | [] |
+ lynx | [] [] |
+ m4 | [] [] [] [] [] |
+ mailfromd | [] |
+ mailutils | [] |
+ make | [] [] [] |
+ man-db | [] [] [] |
+ man-db-manpages | [] [] [] |
+ midi-instruments | [] [] [] [] [] [] [] [] |
+ minicom | [] [] [] [] |
+ mkisofs | [] [] [] |
+ myserver | [] [] |
+ nano | [] [] [] [] [] [] |
+ opcodes | |
+ parted | [] [] [] [] [] [] |
+ pies | [] |
+ pnmixer | [] |
+ popt | [] [] [] [] [] [] |
+ procps-ng | [] |
+ procps-ng-man | [] |
+ psmisc | [] [] [] [] |
+ pspp | [] [] |
+ pushover | |
+ pwdutils | [] |
+ pyspread | [] [] |
+ radius | [] [] |
+ recode | [] [] [] [] [] [] [] [] |
+ recutils | [] [] |
+ rpm | [] |
+ rush | [] [] [] |
+ sarg | [] [] |
+ sed | [] [] [] [] [] [] [] [] |
+ sharutils | [] [] [] |
+ shishi | [] [] |
+ skribilo | [] |
+ solfege | [] [] [] |
+ solfege-manual | [] [] |
+ spotmachine | [] [] |
+ sudo | [] [] [] [] [] [] |
+ sudoers | [] [] [] [] |
+ sysstat | [] [] [] [] [] |
+ tar | [] [] [] [] [] |
+ texinfo | [] [] [] |
+ texinfo_document | [] [] |
+ tigervnc | [] [] [] |
+ tin | [] |
+ tin-man | |
+ tracgoogleappsa... | [] [] [] [] |
+ trader | [] [] |
+ util-linux | [] [] |
+ ve | [] [] [] |
+ vice | |
+ vmm | |
+ vorbis-tools | [] [] [] |
+ wastesedge | |
+ wcd | |
+ wcd-man | |
+ wdiff | [] [] [] [] [] |
+ wget | [] [] [] [] [] |
+ wyslij-po | [] [] [] [] |
+ xboard | [] [] [] |
+ xdg-user-dirs | [] [] [] [] [] [] [] [] [] [] [] [] [] |
+ xkeyboard-config | [] [] [] [] |
+ +------------------------------------------------+
+ nn or pa pl ps pt pt_BR ro ru rw sk sl sq sr
+ 7 3 6 114 1 12 88 32 82 3 40 45 7 101
+
+ sv sw ta te tg th tr uk ur vi wa wo zh_CN
+ +----------------------------------------------+
+ a2ps | [] [] [] [] [] |
+ aegis | [] |
+ anubis | [] [] [] [] |
+ aspell | [] [] [] [] [] |
+ bash | [] [] [] [] |
+ bfd | [] [] [] |
+ binutils | [] [] [] |
+ bison | [] [] [] [] |
+ bison-runtime | [] [] [] [] [] [] |
+ buzztrax | [] [] [] |
+ ccd2cue | [] [] [] |
+ ccide | [] [] [] [] |
+ cflow | [] [] [] [] |
+ clisp | |
+ coreutils | [] [] [] |
+ cpio | [] [] [] [] [] |
+ cppi | [] [] [] [] |
+ cpplib | [] [] [] [] [] |
+ cryptsetup | [] [] [] |
+ datamash | [] [] [] |
+ denemo | [] |
+ dfarc | [] [] |
+ dialog | [] [] [] [] [] [] |
+ dico | [] |
+ diffutils | [] [] [] [] [] |
+ dink | [] |
+ direvent | [] [] |
+ doodle | [] [] |
+ dos2unix | [] [] [] [] |
+ dos2unix-man | [] [] [] |
+ e2fsprogs | [] [] [] [] |
+ enscript | [] [] [] [] |
+ exif | [] [] [] [] [] |
+ fetchmail | [] [] [] [] |
+ findutils | [] [] [] [] [] |
+ flex | [] [] [] [] |
+ freedink | [] [] [] |
+ fusionforge | |
+ gas | [] |
+ gawk | [] [] [] |
+ gcal | [] [] [] |
+ gcc | [] |
+ gdbm | [] [] |
+ gettext-examples | [] [] [] [] [] |
+ gettext-runtime | [] [] [] [] [] |
+ gettext-tools | [] [] [] [] [] |
+ gjay | [] [] [] |
+ glunarclock | [] [] [] [] |
+ gnubiff | [] [] |
+ gnubik | [] [] [] [] |
+ gnucash | () () () () [] |
+ gnuchess | [] [] [] |
+ gnulib | [] [] [] [] |
+ gnunet | |
+ gnunet-gtk | |
+ gold | [] [] |
+ gphoto2 | [] [] [] [] |
+ gprof | [] [] [] [] |
+ gramadoir | [] [] [] |
+ grep | [] [] [] [] [] |
+ grub | [] [] [] [] |
+ gsasl | [] [] [] [] |
+ gss | [] [] [] |
+ gst-plugins-bad | [] [] [] [] [] |
+ gst-plugins-base | [] [] [] [] [] |
+ gst-plugins-good | [] [] [] [] [] |
+ gst-plugins-ugly | [] [] [] [] [] |
+ gstreamer | [] [] [] [] [] |
+ gtick | [] [] [] |
+ gtkam | [] [] [] [] |
+ gtkspell | [] [] [] [] [] [] [] |
+ guix | |
+ guix-packages | |
+ gutenprint | [] [] [] [] |
+ hello | [] [] [] [] [] [] |
+ help2man | [] [] [] |
+ help2man-texi | [] |
+ hylafax | [] |
+ idutils | [] [] [] |
+ iso_15924 | [] () [] [] () [] |
+ iso_3166 | [] [] () [] [] () [] [] |
+ iso_3166_2 | () [] [] () [] |
+ iso_4217 | [] () [] [] () [] |
+ iso_639 | [] [] [] () [] [] () [] [] |
+ iso_639_3 | [] () [] [] () |
+ iso_639_5 | () [] () |
+ jwhois | [] [] [] [] |
+ kbd | [] [] [] [] |
+ klavaro | [] [] [] [] [] [] |
+ ld | [] [] [] [] [] |
+ leafpad | [] [] [] [] [] [] |
+ libc | [] [] [] [] [] |
+ libexif | [] [] () |
+ libextractor | [] [] |
+ libgnutls | [] [] [] [] |
+ libgphoto2 | [] [] [] |
+ libgphoto2_port | [] [] [] [] |
+ libgsasl | [] [] [] [] |
+ libiconv | [] [] [] [] [] |
+ libidn | () [] [] [] |
+ liferea | [] [] [] [] [] |
+ lilypond | [] |
+ lordsawar | |
+ lprng | [] |
+ lynx | [] [] [] [] |
+ m4 | [] [] [] |
+ mailfromd | [] [] |
+ mailutils | [] |
+ make | [] [] [] [] |
+ man-db | [] [] [] |
+ man-db-manpages | [] [] |
+ midi-instruments | [] [] [] [] [] [] |
+ minicom | [] [] |
+ mkisofs | [] [] [] |
+ myserver | [] |
+ nano | [] [] [] [] |
+ opcodes | [] [] [] |
+ parted | [] [] [] [] [] |
+ pies | [] [] |
+ pnmixer | [] [] [] |
+ popt | [] [] [] [] [] [] [] |
+ procps-ng | [] [] |
+ procps-ng-man | [] |
+ psmisc | [] [] [] [] |
+ pspp | [] [] [] |
+ pushover | [] |
+ pwdutils | [] [] |
+ pyspread | [] |
+ radius | [] [] |
+ recode | [] [] [] [] |
+ recutils | [] [] [] |
+ rpm | [] [] [] [] |
+ rush | [] [] |
+ sarg | |
+ sed | [] [] [] [] [] |
+ sharutils | [] [] [] [] |
+ shishi | [] [] |
+ skribilo | [] [] |
+ solfege | [] [] [] [] |
+ solfege-manual | [] |
+ spotmachine | [] [] [] |
+ sudo | [] [] [] [] [] |
+ sudoers | [] [] [] [] |
+ sysstat | [] [] [] [] [] |
+ tar | [] [] [] [] [] |
+ texinfo | [] [] [] |
+ texinfo_document | [] |
+ tigervnc | [] [] [] |
+ tin | [] |
+ tin-man | |
+ tracgoogleappsa... | [] [] [] [] [] |
+ trader | [] |
+ util-linux | [] [] [] [] |
+ ve | [] [] [] [] |
+ vice | () () |
+ vmm | |
+ vorbis-tools | [] [] |
+ wastesedge | |
+ wcd | [] [] [] |
+ wcd-man | [] |
+ wdiff | [] [] [] [] |
+ wget | [] [] [] |
+ wyslij-po | [] [] |
+ xboard | [] [] |
+ xdg-user-dirs | [] [] [] [] [] [] [] [] |
+ xkeyboard-config | [] [] [] [] |
+ +----------------------------------------------+
+ sv sw ta te tg th tr uk ur vi wa wo zh_CN
+ 106 1 4 3 0 13 51 115 1 125 7 1 100
+
+ zh_HK zh_TW
+ +-------------+
+ a2ps | | 30
+ aegis | | 9
+ anubis | | 19
+ aspell | | 29
+ bash | [] | 23
+ bfd | | 11
+ binutils | | 12
+ bison | [] | 18
+ bison-runtime | [] | 38
+ buzztrax | | 9
+ ccd2cue | | 10
+ ccide | | 17
+ cflow | | 16
+ clisp | | 10
+ coreutils | | 18
+ cpio | | 20
+ cppi | | 17
+ cpplib | [] | 19
+ cryptsetup | | 14
+ datamash | | 11
+ denemo | | 5
+ dfarc | | 17
+ dialog | [] | 42
+ dico | | 6
+ diffutils | | 22
+ dink | | 10
+ direvent | | 11
+ doodle | | 12
+ dos2unix | [] | 18
+ dos2unix-man | | 9
+ e2fsprogs | | 15
+ enscript | | 21
+ exif | | 27
+ fetchmail | | 19
+ findutils | | 29
+ flex | [] | 19
+ freedink | | 24
+ fusionforge | | 3
+ gas | | 5
+ gawk | | 13
+ gcal | | 8
+ gcc | | 2
+ gdbm | | 10
+ gettext-examples | [] [] | 40
+ gettext-runtime | [] [] | 35
+ gettext-tools | [] | 24
+ gjay | | 9
+ glunarclock | [] | 27
+ gnubiff | | 9
+ gnubik | | 19
+ gnucash | () | 6
+ gnuchess | | 11
+ gnulib | | 23
+ gnunet | | 1
+ gnunet-gtk | | 1
+ gold | | 7
+ gphoto2 | [] | 19
+ gprof | | 21
+ gramadoir | | 14
+ grep | [] | 31
+ grub | | 21
+ gsasl | [] | 19
+ gss | | 17
+ gst-plugins-bad | | 21
+ gst-plugins-base | | 27
+ gst-plugins-good | | 32
+ gst-plugins-ugly | | 34
+ gstreamer | [] | 32
+ gtick | | 19
+ gtkam | | 24
+ gtkspell | [] [] | 48
+ guix | | 2
+ guix-packages | | 0
+ gutenprint | | 15
+ hello | [] | 30
+ help2man | | 18
+ help2man-texi | | 5
+ hylafax | | 5
+ idutils | | 14
+ iso_15924 | [] | 23
+ iso_3166 | [] [] | 58
+ iso_3166_2 | | 9
+ iso_4217 | [] [] | 28
+ iso_639 | [] [] | 46
+ iso_639_3 | | 10
+ iso_639_5 | | 2
+ jwhois | [] | 20
+ kbd | | 17
+ klavaro | | 30
+ ld | [] | 15
+ leafpad | [] | 39
+ libc | [] | 24
+ libexif | | 10
+ libextractor | | 5
+ libgnutls | | 13
+ libgphoto2 | | 10
+ libgphoto2_port | [] | 19
+ libgsasl | | 18
+ libiconv | [] | 29
+ libidn | | 17
+ liferea | | 29
+ lilypond | | 11
+ lordsawar | | 3
+ lprng | | 3
+ lynx | | 19
+ m4 | [] | 22
+ mailfromd | | 4
+ mailutils | | 6
+ make | | 19
+ man-db | | 15
+ man-db-manpages | | 10
+ midi-instruments | [] | 43
+ minicom | [] | 17
+ mkisofs | | 13
+ myserver | | 9
+ nano | [] | 30
+ opcodes | | 12
+ parted | [] | 23
+ pies | | 4
+ pnmixer | | 9
+ popt | [] | 36
+ procps-ng | | 5
+ procps-ng-man | | 4
+ psmisc | [] | 22
+ pspp | | 13
+ pushover | | 6
+ pwdutils | | 8
+ pyspread | | 6
+ radius | | 9
+ recode | | 31
+ recutils | | 10
+ rpm | [] | 13
+ rush | | 10
+ sarg | | 4
+ sed | [] | 35
+ sharutils | | 13
+ shishi | | 7
+ skribilo | | 7
+ solfege | | 21
+ solfege-manual | | 9
+ spotmachine | | 11
+ sudo | | 26
+ sudoers | | 22
+ sysstat | | 23
+ tar | [] | 30
+ texinfo | | 17
+ texinfo_document | | 13
+ tigervnc | | 14
+ tin | [] | 7
+ tin-man | | 1
+ tracgoogleappsa... | [] | 22
+ trader | | 12
+ util-linux | | 13
+ ve | | 14
+ vice | | 1
+ vmm | | 3
+ vorbis-tools | | 13
+ wastesedge | | 3
+ wcd | | 8
+ wcd-man | | 3
+ wdiff | [] | 23
+ wget | | 21
+ wyslij-po | | 14
+ xboard | | 10
+ xdg-user-dirs | [] [] | 68
+ xkeyboard-config | [] | 28
+ +-------------+
+ 89 teams zh_HK zh_TW
+ 166 domains 7 42 2809
+
+ Some counters in the preceding matrix are higher than the number of
+visible blocks let us expect. This is because a few extra PO files are
+used for implementing regional variants of languages, or language
+dialects.
+
+ For a PO file in the matrix above to be effective, the package to
+which it applies should also have been internationalized and distributed
+as such by its maintainer. There might be an observable lag between the
+mere existence a PO file and its wide availability in a distribution.
+
+ If Jun 2014 seems to be old, you may fetch a more recent copy of this
+'ABOUT-NLS' file on most GNU archive sites. The most up-to-date matrix
+with full percentage details can be found at
+'http://translationproject.org/extra/matrix.html'.
+
+1.5 Using 'gettext' in new packages
+===================================
+
+If you are writing a freely available program and want to
+internationalize it you are welcome to use GNU 'gettext' in your
+package. Of course you have to respect the GNU Lesser General Public
+License which covers the use of the GNU 'gettext' library. This means
+in particular that even non-free programs can use 'libintl' as a shared
+library, whereas only free software can use 'libintl' as a static
+library or use modified versions of 'libintl'.
+
+ Once the sources are changed appropriately and the setup can handle
+the use of 'gettext' the only thing missing are the translations. The
+Free Translation Project is also available for packages which are not
+developed inside the GNU project. Therefore the information given above
+applies also for every other Free Software Project. Contact
+'coordinator@translationproject.org' to make the '.pot' files available
+to the translation teams.
diff --git a/AUTHORS b/AUTHORS
index d85fa5cad..425379868 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -2,4 +2,5 @@ Sree Harsha Totakura <sreeharsha@totakura.in>
Florian Dold <dold@taler.net>
Marcello Stanisci <stanisci@taler.net>
Christian Grothoff <grothoff@taler.net>
+Özgür Kesim <oec-taler@kesim.org>
Benedikt Mueller
diff --git a/ChangeLog b/ChangeLog
index 4e7292b00..f2232f498 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,10 +1,70 @@
+Fri Apr 12 10:43:41 AM CEST 2024
+ Releasing GNU Taler Exchange 0.10.2. -CG
+
+Wed Nov 29 09:06:49 AM JST 2023
+ Creating bugfix release for taler-exchange 0.9.3a. -CG
+
+Sat Nov 5 11:32:45 AM CET 2022
+ Added support for P2P payments.
+ Added support for explicit reserve lifetime control.
+ Added support for age restrictions.
+ Releasing GNU Taler Exchange 0.9.0. -CG
+
+Fri 03 Sep 2021 07:02:05 PM CEST
+ Add experimental aggregator sharding logic. -CG
+
+Sat 28 Aug 2021 05:22:57 PM CEST
+ Fixed various memory leaks.
+ Fixed database initialization sequence to avoid warning on first request.
+ Releasing GNU Taler Exchange 0.8.4. -CG
+
+Fri 13 Aug 2021 10:40:57 PM CEST
+ Add support for long-polling of bank account histories.
+ Add support for event notifications to exchange DB API.
+ Releasing GNU Taler Exchange 0.8.3. -CG
+
+Sun 08 Aug 2021 08:36:21 PM CEST
+ Improved code to use new GNUNET_JSON_PACK API.
+ Improved code to use new GNUNET_TIME APIs.
+ Improved configuration structure with clearer separation
+ of concerns. Created proper Debian package.
+ Added various APIs to improve input validation (mostly
+ for the Taler merchant). Renamed taler-wire-gateway-client
+ to taler-exchange-wire-gateway-client for consistency.
+ Strengthened payto:// validation logic. Fixed bug where
+ suspended /keys requests could eat up all the sockets.
+ Force flushing /keys response if set of auditors changes.
+ Fixed /keys cherry-picking logic.
+ Releasing GNU Taler Exchange 0.8.2. -CG
+
+Mon 05 Apr 2021 07:58:09 PM CEST
+ Add Gettext support (chiefly for error code hints). -CG
+
+Sat 14 Nov 2020 05:47:30 PM CET
+ Modify taler-exchange-transfer to continue even after a
+ wire transfer failed due to the bank refusing it because
+ the target account does not exist. Changed the database
+ to track such failures in the respective table.
+ Opens new issue #6647. -CG
+
+Tue 10 Nov 2020 01:03:22 PM CET
+ Updates to error codes and HTTP status codes for improved
+ consistency. Fixed spelling issues. Ensure main() returns
+ 0 when called with '-h' or '--help'.
+ Releasing GNU Taler Exchange 0.8.1. -CG
+
+Sat 03 Oct 2020 03:59:45 PM CEST
+ Various minor fixes, code cleanup, updates to more recent
+ GNUnet APIs, new error codes, and timetravel test support.
+ Releasing GNU Taler Exchange 0.8.0. -CG
+
Sun 29 Mar 2020 08:53:46 PM CEST
Changed protocol to be more RESTful. Expanded auditor tests.
Completed transition to new wire gateway API for bank interaction.
- Releasing GNU Taler 0.7.0. -CG
+ Releasing GNU Taler Exchange 0.7.0. -CG
Tue 24 Dec 2019 11:09:14 PM CET
- Releasing GNU Taler 0.6.0. -CG
+ Releasing GNU Taler Exchange 0.6.0. -CG
Sat 17 Aug 2019 10:03:38 PM CEST
Remove "currency" field from exchange database, as we only
diff --git a/Makefile.am b/Makefile.am
index 3a422a9e5..83b761a59 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3,20 +3,21 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/include
if DOC_ONLY
if ENABLE_DOC
- SUBDIRS = . contrib doc
+ SUBDIRS = . contrib doc po
else
- SUBDIRS = . contrib
+ SUBDIRS = . contrib po
endif
else
if ENABLE_DOC
- SUBDIRS = . contrib src doc
+ SUBDIRS = . contrib src doc po
else
- SUBDIRS = . contrib src
+ SUBDIRS = . contrib src po
endif
endif
@DX_RULES@
ACLOCAL_AMFLAGS = -I m4
-EXTRA_DIST = \
- AUTHORS
+EXTRA_DIST = build-aux/config.rpath \
+ AUTHORS \
+ README.1st
diff --git a/README b/README
index 99e19b4d6..7b092e0f1 100644
--- a/README
+++ b/README
@@ -13,18 +13,28 @@ spend their digital coins. Naturally, each Merchant is different, but
Taler includes code examples to help Merchants integrate Taler as a
payment system.
-Taler is currently developed by a worldwide group of independent free
-software developers and the DECENTRALISE team at Inria Rennes. Taler
-is free software and a GNU package (https://www.gnu.org/).
+Taler is currently developed by a worldwide group of independent free software
+developers and Taler Systems SA. Taler is free software and an official GNU
+package (https://www.gnu.org/).
-This is an alpha release with a few known bugs, lacking a few
-important features, documentation, testing, performance tuning and an
-external security audit. However, you can run the code and it largely
-works fine. that does not work yet. This package also only includes
-the Taler exchange, not the other components of the system.
+This is an alpha release with a few known bugs, lacking a few important
+features, documentation, testing, performance tuning and an external security
+audit. However, you can run the code and it largely works fine. This package
+also only includes the Taler exchange, not the other components of the system.
Documentation about Taler can be found at https://taler.net/.
-Our bug tracker is at https://gnunet.org/bugs/.
+Our bug tracker is at https://bugs.taler.net/.
+
+
+Joining GNU
+===========
+
+This is a GNU program, developed by the GNU Project and part of the
+GNU Operating System. If you are the author of an awesome program and
+want to join us in writing Free Software, please consider making it an
+official GNU program and become a GNU maintainer. You can find
+instructions on how to do so at http://www.gnu.org/help/evaluation.
+We are looking forward to hacking with you!
Dependencies:
@@ -32,9 +42,9 @@ Dependencies:
These are the direct dependencies for running a Taler exchange:
-- GNUnet >= 0.10.2
-- GNU libmicrohttpd >= 0.9.55
-- Postgres >= 9.5
+- GNUnet >= 0.21.1
+- GNU libmicrohttpd >= 0.9.71
+- PostgreSQL >= 15.0
@@ -52,7 +62,7 @@ src/pq/
-- Postgres-specific utility functions
src/exchangedb/
- -- Exchange database backend (with DB-specific plugins)
+ -- Exchange database backend (with database-specific plugins)
src/exchange/
-- taler exchange server
@@ -60,71 +70,50 @@ src/exchange/
src/exchange-tools/
-- taler exchange helper programs
-src/exchange-lib/
+src/lib/
-- libtalerexchange: C API to issue HTTP requests to exchange
src/auditor/
-- tools to generate reports about financial performance and
to validate that the exchange has been operating correctly
+src/auditordb/
+ -- database logic for the auditor component (with database-specific
+ plugins)
+
src/benchmark/
-- tool to run performance measurements
+src/templating/
+ -- logic to generate HTML pages from templates at runtime
+src/kyclogic/
+ -- core logic and plugins to trigger and manage KYC processes
+ as required by banking regulation
-Getting Started
-===============
-
-The following steps illustrate how to set up a exchange HTTP server.
-They take as a stub for configuring the exchange the content of 'contrib/exchange-template/config/'.
-
-1) Create a 'test/' directory and copy the stubs in it:
-
-mkdir -p test/config/
-cp exchange/contrib/exchange-template/config/* test/config/
-cd test/
+src/bank-lib/
+ -- bank REST client logic and implementation of an in-memory
+ RTGS emulator ("fakebank") for testing.
-2) Create the exchange's master with the tool 'gnunet-ecc':
+src/extensions/
+ -- extensions to the core logic of an exchange
-gnunet-ecc -g1 master.priv
+src/json/
+ -- helper functions for generating and parsing JSON
-3) Edit config/exchange-common.conf by replacing the right value on the line with the
-MASTER_PUBLIC_KEY entry with the fresh generated (ASCII version of) master.priv.
-This ASCII version is obtained by issuing:
+src/mhd/
+ -- helper functions for interacting with GNU libmicrohttpd
-gnunet-ecc -p master.priv
+src/curl/
+ -- helper functions for interacting with libcurl
-4) Generate other exchange related keys ('denomination' and 'signing' keys), by issuing:
-taler-exchange-keyup -m master.priv -o auditor.in
-
-5) A exchange needs a database to operate, so the following instructions relate to
-how to set up PostgreSQL. On debian, the two packages needed are:
-
-* postgresql
-* postgresql-client
-
-For other operating systems, please refer to the relevant documentation.
-
-In this settlement, the exchange will use a database called 'talercheck' and will
-run under the username through which 'taler-exchange-httpd' is launched. Thus assuming
-that this user is 'demo', we need to create a 'demo' role for postgresql and make
-him the owner of 'talercheck' database.
-
-To perform these administrative tasks we have to impersonate the 'postgres' (by default,
-postgres installation assigns privileges to such a user) user, then connect to the running DBMS.
-Issue the following:
-
-su # give your root password
-su - postgres
-psql # this is the command-line client to the DMBS
-# the following lines are SQL
-CREATE USER demo;
-CREATE DATABASE talercheck OWNER demo;
-# quit with CTRL-D
+Getting Started
+===============
-7) If any previous step has been successful, it is now possbile to start up the
-exchange web server (by default it will listen on port 4241); issue:
+Please follow the exchange manual you can view after
+installing using
+$ info taler-exchange
-taler-exchange-httpd -d `pwd` # assuming we did not move outside of the 'test' directory
+or by visiting https://docs.taler.net/.
diff --git a/README.1st b/README.1st
new file mode 100644
index 000000000..e1925d7e7
--- /dev/null
+++ b/README.1st
@@ -0,0 +1,19 @@
+Building Taler
+==============
+
+Contributions are welcome. Please submit bugs you find to
+https://bugs.taler.net/ or our bugs mailinglist. Submit patches via E-Mail to
+taler@gnu.org, formatted with `git format-patch`.
+
+In order to run the unit tests by hand (instead of using "make check"),
+you need to set the environment variable "TALER_PREFIX" to the
+directory where Taler's libraries are installed.
+Before running any testcases, you must complete the installation.
+
+Quick summary:
+
+$ ./configure --prefix=$SOMEWHERE
+$ make
+$ make install
+$ export $GNUNET_PREFIX=$SOMEWHERE
+$ make check
diff --git a/bootstrap b/bootstrap
index 44f929c67..509c8a0c7 100755
--- a/bootstrap
+++ b/bootstrap
@@ -1,11 +1,18 @@
#!/bin/sh
+# This file is in the public domain.
+
+set -eu
if ! git --version >/dev/null; then
echo "git not installed"
exit 1
fi
-git submodule update --init
+echo "$0: Updating submodules"
+echo | git submodule update --init --force --remote
+
+# Generate based on pinned submodule
+./contrib/gana-generate.sh
# This is more portable than `which' but comes with
# the caveat of not(?) properly working on busybox's ash:
@@ -18,11 +25,24 @@ existence()
if existence uncrustify; then
echo "Installing uncrustify hook and configuration"
# Install uncrustify format symlink (if possible)
- ln -s contrib/uncrustify.cfg uncrustify.cfg 2> /dev/null
+ ln -s contrib/uncrustify.cfg uncrustify.cfg 2> /dev/null || true
# Install pre-commit hook (if possible)
- ln -s ../../contrib/uncrustify_precommit .git/hooks/pre-commit 2> /dev/null
+ ln -s ../../contrib/uncrustify_precommit .git/hooks/pre-commit 2> /dev/null || true
else
echo "Uncrustify not detected, hook not installed. Please install uncrustify if you plan on doing development"
fi
+
+# Generate Makefile.am in contrib/
+cd contrib
+rm -f Makefile.am
+find wallet-core/aml-backoffice/ -type f | sort | awk '{print " " $1 " \\" }' > Makefile.am.ext
+# Remove extra '\' at the end of the file
+truncate -s -2 Makefile.am.ext
+cat Makefile.am.in Makefile.am.ext >> Makefile.am
+# Prevent accidental editing of the generated Makefile.am
+chmod -w Makefile.am
+cd ..
+
+echo "$0: Running autoreconf"
autoreconf -fi
diff --git a/configure.ac b/configure.ac
index 51a4646d8..3f8238b42 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
#
# This file is part of TALER
-# Copyright (C) 2014, 2015, 2016, 2017, 2018 Taler Systems SA
+# Copyright (C) 2014-2024 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
@@ -17,9 +17,13 @@
#
#
AC_PREREQ([2.69])
-AC_INIT([taler-exchange], [0.7.0], [taler-bug@gnunet.org])
+AC_INIT([taler-exchange],[0.10.2],[taler-bug@gnunet.org])
+AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_SRCDIR([src/util/util.c])
AC_CONFIG_HEADERS([taler_config.h])
+AC_CANONICAL_TARGET
+AC_CANONICAL_HOST
+AC_CANONICAL_BUILD
# support for non-recursive builds
AM_INIT_AUTOMAKE([subdir-objects 1.9 tar-pax])
@@ -27,8 +31,15 @@ AM_INIT_AUTOMAKE([subdir-objects 1.9 tar-pax])
AM_SILENT_RULES([yes])
AC_CONFIG_MACRO_DIR([m4])
+AC_PROG_AWK
+AC_PROG_CC
+AC_PROG_OBJC
+AC_PROG_INSTALL
+AC_PROG_LN_S
+AC_PROG_MAKE_SET
+AM_PROG_CC_C_O
-LT_INIT
+LT_INIT([disable-static dlopen])
DX_INIT_DOXYGEN([taler-exchange],,,
DX_PS_FEATURE(OFF),
@@ -49,17 +60,21 @@ AM_CONDITIONAL([DOC_ONLY], [test "x$doc_only" = "xyes"])
# Not indented, as most of the file falls under this one...
AS_IF([test "x$doc_only" != xyes],[
-# Checks for programs.
+# Force some CFLAGS
+CFLAGS="-Wall -Wno-address-of-packed-member $CFLAGS"
-AC_PROG_CC
-AC_PROG_CC_C99
+TALER_LIB_LDFLAGS="-export-dynamic -no-undefined"
+TALER_PLUGIN_LDFLAGS="-export-dynamic -avoid-version -module -no-undefined"
+AC_SUBST(TALER_LIB_LDFLAGS)
+AC_SUBST(TALER_PLUGIN_LDFLAGS)
-CFLAGS="-Wall -Wno-address-of-packed-member $CFLAGS"
# Checks for header files.
AC_CHECK_HEADERS([stdint.h stdlib.h string.h unistd.h sys/socket.h sys/un.h netinet/in.h netinet/ip.h])
+AX_PYTHON_MODULE([jinja2],true)
+
# Require minimum libgcrypt version
need_libgcrypt_version=1.6.1
@@ -68,6 +83,7 @@ AC_DEFINE_UNQUOTED([NEED_LIBGCRYPT_VERSION], ["$need_libgcrypt_version"],
AM_PATH_LIBGCRYPT([$need_libgcrypt_version])
+
# should expensive tests be run?
AC_MSG_CHECKING(whether to run expensive tests)
AC_ARG_ENABLE([expensivetests],
@@ -112,6 +128,42 @@ fd = epoll_create1(EPOLL_CLOEXEC);]])],
AC_DEFINE([[HAVE_EPOLL_CREATE1]], [[1]], [Define if you have epoll_create1 function.])]))
+# check for libmicrohttpd
+AC_MSG_CHECKING([for microhttpd])
+AC_ARG_WITH([microhttpd],
+ [AS_HELP_STRING([--with-microhttpd=PFX], [base of microhttpd installation])],
+ [AC_MSG_RESULT([given as $with_microhttpd])],
+ [AC_MSG_RESULT([not given])
+ with_microhttpd=yes])
+AS_CASE([$with_microhttpd],
+ [yes], [],
+ [no], [AC_MSG_ERROR([--with-microhttpd is required])],
+ [LDFLAGS="-L$with_microhttpd/lib $LDFLAGS"
+ CPPFLAGS="-I$with_microhttpd/include $CPPFLAGS"])
+MHD_VERSION_AT_LEAST([0.9.71])
+
+# check for libjansson (Jansson JSON library)
+jansson=0
+AC_MSG_CHECKING([for jansson])
+AC_ARG_WITH([jansson],
+ [AS_HELP_STRING([--with-jansson=PFX], [base of jansson installation])],
+ [AC_MSG_RESULT([given as $with_jansson])],
+ [AC_MSG_RESULT([not given])
+ with_jansson=yes])
+AS_CASE([$with_jansson],
+ [yes], [],
+ [no], [AC_MSG_ERROR([--with-jansson is required])],
+ [LDFLAGS="-L$with_jansson/lib $LDFLAGS"
+ CPPFLAGS="-I$with_jansson/include $CPPFLAGS"])
+AC_CHECK_LIB(jansson,json_dumpb,
+ [AC_CHECK_HEADER([jansson.h],[jansson=1])])
+AS_IF([test $jansson = 0],
+ [AC_MSG_ERROR([[
+***
+*** You need libjansson >= 2.10 to build this program.
+*** ]])])
+
+
# Check for GNUnet's libgnunetutil.
libgnunetutil=0
AC_MSG_CHECKING([for libgnunetutil])
@@ -125,15 +177,12 @@ AS_CASE([$with_gnunet],
[no], [AC_MSG_ERROR([--with-gnunet is required])],
[LDFLAGS="-L$with_gnunet/lib $LDFLAGS"
CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"])
-AC_CHECK_HEADERS([gnunet/platform.h gnunet/gnunet_util_lib.h],
- [AC_CHECK_LIB([gnunetutil], [GNUNET_SCHEDULER_run], libgnunetutil=1)],
- [], [#ifdef HAVE_GNUNET_PLATFORM_H
- #include <gnunet/platform.h>
- #endif])
+AC_CHECK_HEADERS([gnunet/gnunet_util_lib.h],
+ [AC_CHECK_LIB([gnunetutil], [GNUNET_SCHEDULER_run], libgnunetutil=1)])
AS_IF([test $libgnunetutil != 1],
[AC_MSG_ERROR([[
***
-*** You need libgnunetutil to build this program.
+*** You need libgnunetutil >= 0.21.1 to build this program.
*** This library is part of GNUnet, available at
*** https://gnunet.org
*** ]])])
@@ -152,11 +201,8 @@ AS_CASE([$with_gnunet],
[no], [AC_MSG_ERROR([--with-gnunet is required])],
[LDFLAGS="-L$with_gnunet/lib $LDFLAGS"
CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"])
-AC_CHECK_HEADERS([gnunet/platform.h gnunet/gnunet_json_lib.h],
- [AC_CHECK_LIB([gnunetjson], [GNUNET_JSON_parse], libgnunetjson=1)],
- [], [#ifdef HAVE_GNUNET_PLATFORM_H
- #include <gnunet/platform.h>
- #endif])
+AC_CHECK_HEADERS([gnunet/gnunet_json_lib.h],
+ [AC_CHECK_LIB([gnunetjson], [GNUNET_JSON_parse], libgnunetjson=1)])
AS_IF([test $libgnunetjson != 1],
[AC_MSG_ERROR([[
***
@@ -165,43 +211,35 @@ AS_IF([test $libgnunetjson != 1],
*** building GNUnet.
*** ]])])
+# check for gettext
+AM_GNU_GETTEXT([external])
+AM_GNU_GETTEXT_VERSION([0.19.8])
+
-# Save before checking libgnurl/libcurl
+# Save before checking libcurl
CFLAGS_SAVE=$CFLAGS
LDFLAGS_SAVE=$LDFLAGS
LIBS_SAVE=$LIBS
-# check for libgnurl
-# libgnurl
-LIBGNURL_CHECK_CONFIG(,7.34.0,gnurl=1,gnurl=0)
+# check for libcurl
LIBCURL_CHECK_CONFIG(,7.34.0,[curl=1],[curl=0])
+# cURL must support CURLINFO_TLS_SESSION, version >= 7.34
AS_IF([test "x$curl" = x1],[
AC_CHECK_HEADER([curl/curl.h],
- AC_CHECK_DECLS(CURLINFO_TLS_SESSION,[curl=1],[curl=0],[[#include <curl/curl.h>]]),
+ [AC_CHECK_DECLS(CURLINFO_TLS_SESSION,[curl=1],[curl=0],[[#include <curl/curl.h>]])],
[curl=0])
- # need libcurl-gnutls.so, everything else is not acceptable
- AC_CHECK_LIB([curl-gnutls],[curl_easy_getinfo],,[curl=0])
- # cURL must support CURLINFO_TLS_SESSION, version >= 7.34
])
-# libcurl and libgnurl should be mutually exclusive
-AS_IF([test "$gnurl" = 1],
- [AM_CONDITIONAL(HAVE_LIBGNURL, true)
- AC_DEFINE([HAVE_LIBGNURL],[1],[Have libgnurl])
- AM_CONDITIONAL(HAVE_LIBCURL, false)
- AC_DEFINE([HAVE_LIBCURL],[0],[Lacking libcurl])
- [LIBGNURLCURL_LIBS="-lgnurl"]],
- [AS_IF([test "$curl" = 1],
- [AM_CONDITIONAL(HAVE_LIBGNURL, false)
- AC_DEFINE([HAVE_LIBGNURL],[0],[Lacking libgnurl])
- AM_CONDITIONAL(HAVE_LIBCURL, true)
- AC_DEFINE([HAVE_LIBCURL],[1],[Have libcurl])],
- [LIBGNURLCURL_LIBS="-lcurl"]
- [AC_MSG_ERROR([WARNING: No libgnurl/libcurl, taler-bank support will not be compiled])])])
+# libcurl should be mutually exclusive
+AS_IF([test "$curl" = 1],
+ AM_CONDITIONAL(HAVE_LIBCURL, true)
+ AC_DEFINE([HAVE_LIBCURL],[1],[Have libcurl])
+ [LIBCURL_LIBS="-lcurl"],
+ [AC_MSG_ERROR([FATAL: No libcurl])])
-AC_SUBST([LIBGNURLCURL_LIBS])
+AC_SUBST([LIBCURL_LIBS])
# Check for GNUnet's libgnunetcurl.
libgnunetcurl=0
@@ -216,25 +254,37 @@ AS_CASE([$with_gnunet],
[no], [AC_MSG_ERROR([--with-gnunet is required])],
[LDFLAGS="-L$with_gnunet/lib $LDFLAGS"
CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"])
-AC_CHECK_HEADERS([gnunet/platform.h gnunet/gnunet_curl_lib.h],
- [AC_CHECK_LIB([gnunetcurl], [GNUNET_CURL_get_select_info], libgnunetcurl=1)],
- [], [#ifdef HAVE_GNUNET_PLATFORM_H
- #include <gnunet/platform.h>
- #endif])
+AC_CHECK_HEADERS([gnunet/gnunet_curl_lib.h],
+ [AC_CHECK_LIB([gnunetcurl], [GNUNET_CURL_get_select_info], libgnunetcurl=1)])
AS_IF([test $libgnunetcurl != 1],
[AC_MSG_ERROR([[
***
*** You need libgnunetcurl to build this program.
-*** Make sure you have libcurl or libgnurl installed while
+*** Make sure you have libcurl installed while
*** building GNUnet.
*** ]])])
-# Restore after gnurl/curl checks messed up these values
+# Restore after curl checks messed up these values
CFLAGS=$CFLAGS_SAVE
LDFLAGS=$LDFLAGS_SAVE
LIBS=$LIBS_SAVE
+# test for postgres
+AX_LIB_POSTGRESQL([15.0])
+AS_IF([test "x$found_postgresql" = "xyes"],
+ [SAVE_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$POSTGRES_CPPFLAGS $CPPFLAGS"
+ AC_CHECK_HEADERS([libpq-fe.h], [postgres=1], [postgres=0])])
+AS_IF([test "x$postgres" != "x1"],
+ [AC_MSG_ERROR([[
+***
+*** You need libpq(-dev) >= 15.0 to build this program.
+*** ]])])
+AM_CONDITIONAL([HAVE_POSTGRESQL], [test "x$postgres" = "x1"])
+AC_DEFINE_UNQUOTED([HAVE_POSTGRESQL], [$postgres],
+ [Define to 1 if Postgres is available])
+
# Check for GNUnet's libgnunetpq.
libgnunetpq=0
AC_MSG_CHECKING([for libgnunetpq])
@@ -247,92 +297,68 @@ AS_CASE([$with_gnunet],
[yes], [],
[no], [AC_MSG_ERROR([--with-gnunet is required])],
[LDFLAGS="-L$with_gnunet/lib $LDFLAGS"
- CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"])
-AC_CHECK_HEADERS([gnunet/platform.h gnunet/gnunet_pq_lib.h],
- [AC_CHECK_LIB([gnunetpq], [GNUNET_PQ_result_spec_string], libgnunetpq=1)],
- [], [#ifdef HAVE_GNUNET_PLATFORM_H
- #include <gnunet/platform.h>
- #endif])
+ CPPFLAGS="-I$with_gnunet/include ${CPPFLAGS}"])
+CPPFLAGS="${CPPFLAGS} ${POSTGRESQL_CPPFLAGS}"
+AC_CHECK_HEADERS([gnunet/gnunet_pq_lib.h],
+ [AC_CHECK_LIB([gnunetpq], [GNUNET_PQ_query_param_int64], libgnunetpq=1)])
AS_IF([test $libgnunetpq != 1],
[AC_MSG_ERROR([[
***
-*** You need libgnunetpq to build this program.
+*** You need libgnunetpq version >= 6:0:1 to build this program.
*** Make sure you have Postgres installed while
-*** building GNUnet (and that your GNUnet version
-*** is recent!)
+*** building GNUnet (and that your GNUnet version is recent!)
*** ]])])
-
-# check for libmicrohttpd
-microhttpd=0
-AC_MSG_CHECKING([for microhttpd])
-AC_ARG_WITH([microhttpd],
- [AS_HELP_STRING([--with-microhttpd=PFX], [base of microhttpd installation])],
- [AC_MSG_RESULT([given as $with_microhttpd])],
- [AC_MSG_RESULT([not given])
- with_microhttpd=yes])
-AS_CASE([$with_microhttpd],
- [yes], [],
- [no], [AC_MSG_ERROR([--with-microhttpd is required])],
- [LDFLAGS="-L$with_microhttpd/lib $LDFLAGS"
- CPPFLAGS="-I$with_microhttpd/include $CPPFLAGS"])
-AC_CHECK_LIB(microhttpd,MHD_start_daemon,
- [AC_CHECK_HEADER([microhttpd.h],[microhttpd=1])])
-AC_CHECK_DECL([MHD_DAEMON_INFO_CURRENT_CONNECTIONS],,[microhttpd=0],[[#include <microhttpd.h>]])
-AS_IF([test $microhttpd = 0],
- [AC_MSG_ERROR([[
-***
-*** You need libmicrohttpd >= 0.9.39 to build this program.
-*** ]])])
-
-# check for libjansson (Jansson JSON library)
-jansson=0
-AC_MSG_CHECKING([for jansson])
-AC_ARG_WITH([jansson],
- [AS_HELP_STRING([--with-jansson=PFX], [base of jansson installation])],
- [AC_MSG_RESULT([given as $with_jansson])],
- [AC_MSG_RESULT([not given])
- with_jansson=yes])
-AS_CASE([$with_jansson],
- [yes], [],
- [no], [AC_MSG_ERROR([--with-jansson is required])],
- [LDFLAGS="-L$with_jansson/lib $LDFLAGS"
- CPPFLAGS="-I$with_jansson/include $CPPFLAGS"])
-AC_CHECK_LIB(jansson,json_dumpb,
- [AC_CHECK_HEADER([jansson.h],[jansson=1])])
-AS_IF([test $jansson = 0],
- [AC_MSG_ERROR([[
-***
-*** You need libjansson >= 2.10 to build this program.
-*** ]])])
-
-
-# test for postgres
-AX_LIB_POSTGRESQL([9.3])
-AS_IF([test "x$found_postgresql" = "xyes"],[postgres=true])
-
-TALER_LIB_LDFLAGS="-export-dynamic -no-undefined"
-TALER_PLUGIN_LDFLAGS="-export-dynamic -avoid-version -module -no-undefined"
-
-AC_SUBST(TALER_LIB_LDFLAGS)
-AC_SUBST(TALER_PLUGIN_LDFLAGS)
-
CFLAGS_SAVE=$CFLAGS
LDFLAGS_SAVE=$LDFLAGS
LIBS_SAVE="$LIBS"
+# Check for GNUnet's libgnunetsq
+libgnunetsq=0
+AC_MSG_CHECKING([for libgnunetsq])
+AC_CHECK_HEADERS([gnunet/gnunet_sq_lib.h],
+ [AC_CHECK_LIB([gnunetsq], [GNUNET_SQ_result_spec_string], libgnunetsq=1)])
+
-AM_CONDITIONAL(HAVE_POSTGRESQL, test x$postgres = xtrue)
CFLAGS=$CFLAGS_SAVE
LDFLAGS=$LDFLAGS_SAVE
LIBS=$LIBS_SAVE
+# test for sqlite
+sqlite=false
+AC_MSG_CHECKING(for SQLite)
+AC_ARG_WITH(sqlite,
+ [ --with-sqlite=PFX base of SQLite installation],
+ [AC_MSG_RESULT("$with_sqlite")
+ AS_CASE([$with_sqlite],
+ [no],[],
+ [yes],[
+ AC_CHECK_HEADERS(sqlite3.h,
+ sqlite=true)],
+ [
+ LDFLAGS="-L$with_sqlite/lib $LDFLAGS"
+ CPPFLAGS="-I$with_sqlite/include $CPPFLAGS"
+ AC_CHECK_HEADERS(sqlite3.h,
+ EXT_LIB_PATH="-L$with_sqlite/lib $EXT_LIB_PATH"
+ SQLITE_LDFLAGS="-L$with_sqlite/lib"
+ SQLITE_CPPFLAGS="-I$with_sqlite/include"
+ sqlite=true)
+ LDFLAGS=$SAVE_LDFLAGS
+ CPPFLAGS=$SAVE_CPPFLAGS
+ ])
+ ],
+ [AC_MSG_RESULT([--with-sqlite not specified])
+ AC_CHECK_HEADERS(sqlite3.h, sqlite=true)])
+AM_CONDITIONAL(HAVE_SQLITE, [test x$sqlite = xtrue] && [test $libgnunetsq = 1])
+AC_SUBST(SQLITE_CPPFLAGS)
+AC_SUBST(SQLITE_LDFLAGS)
+
# check for libtalertwistertesting
-twistertesting=0
-AC_MSG_CHECKING([for talerwtistertesting])
+talertwister=0
+AC_MSG_CHECKING([for talertwister])
AC_ARG_WITH([twister],
- [AS_HELP_STRING([--with-twister=PFX], [base of libtalertwistertesting])],
+ [AS_HELP_STRING([--with-twister=PFX], [base of libtalertwister])],
[AC_MSG_RESULT([given as $with_twister])],
[AC_MSG_RESULT([not given])
with_twister=yes])
@@ -341,14 +367,10 @@ AS_CASE([$with_twister],
[no], [AC_MSG_WARN([no twister-testing will be compiled])],
[LDFLAGS="-L$with_twister/lib $LDFLAGS"
CPPFLAGS="-I$with_twister/include $CPPFLAGS"])
-AC_CHECK_LIB(talertwistertesting,TALER_TESTING_run_twister,
- [AC_CHECK_HEADER([taler/taler_twister_testing_lib.h],[twistertesting=1],,
- [#ifdef HAVE_GNUNET_PLATFORM_H
- #include <gnunet/platform.h>
- #endif
- ])])
-AM_CONDITIONAL(HAVE_TWISTER, test x$twistertesting = x1)
+AC_CHECK_HEADERS([taler/taler_twister_service.h],
+ [AC_CHECK_LIB([talertwister], [TALER_TWISTER_connect], talertwister=1)])
+AM_CONDITIONAL(HAVE_TWISTER, test x$talertwister = x1)
# should developer logic be compiled (not-for-production code)?
AC_MSG_CHECKING(whether to compile developer logic)
@@ -364,6 +386,11 @@ AS_IF([test "x$enableval" = "xno"], [enable_dev=0])
AC_CHECK_DECL([MHD_OPTION_NOTIFY_CONNECTION],,[enable_dev=0],[[#include <microhttpd.h>]])
AC_DEFINE_UNQUOTED([HAVE_DEVELOPER],[$enable_dev],[1 if developer logic is enabled, 0 otherwise])
+AC_PATH_PROG([JQ], [jq], [no])
+if test "$JQ" = "no"; then
+ AC_MSG_ERROR([jq is required but not found. Please install jq.])
+fi
+
# Adam shostack suggests the following for Windows:
@@ -417,7 +444,7 @@ AC_MSG_CHECKING(for source being under a VCS)
git_version=
AS_IF([test ! "X$gitcommand" = "X"],
[
- git_version=$(cd $srcdir ; git rev-list --full-history --all --abbrev-commit | head -n 1 2>/dev/null)
+ git_version=$(cd $srcdir ; git rev-list -n 1 --abbrev-commit HEAD 2>/dev/null)
])
AS_IF([test "X$git_version" = "X"],
[
@@ -462,8 +489,8 @@ AM_CONDITIONAL([ENABLE_DOC], [test "x$enable_doc" = "xyes"])
AM_CONDITIONAL([HAVE_EXPENSIVE_TESTS], [false])
AM_CONDITIONAL([MHD_HAVE_EPOLL], [false])
AM_CONDITIONAL([HAVE_POSTGRESQL], [false])
+AM_CONDITIONAL([HAVE_SQLITE], [false])
AM_CONDITIONAL([HAVE_LIBCURL], [false])
-AM_CONDITIONAL([HAVE_LIBGNURL], [false])
AM_CONDITIONAL([HAVE_DEVELOPER], [false])
AM_CONDITIONAL([USE_COVERAGE], [false])
AM_CONDITIONAL([ENABLE_DOC], [true])
@@ -476,6 +503,7 @@ AC_CONFIG_FILES([Makefile
contrib/Makefile
doc/Makefile
doc/doxygen/Makefile
+ po/Makefile.in
src/Makefile
src/auditor/Makefile
src/auditordb/Makefile
@@ -484,13 +512,18 @@ AC_CONFIG_FILES([Makefile
src/exchange/Makefile
src/exchangedb/Makefile
src/exchange-tools/Makefile
+ src/extensions/Makefile
+ src/extensions/age_restriction/Makefile
src/lib/Makefile
+ src/kyclogic/Makefile
src/testing/Makefile
src/benchmark/Makefile
src/include/Makefile
src/json/Makefile
src/mhd/Makefile
src/pq/Makefile
+ src/sq/Makefile
+ src/templating/Makefile
src/util/Makefile
])
AC_OUTPUT
diff --git a/contrib/.gitignore b/contrib/.gitignore
new file mode 100644
index 000000000..1e74d37e2
--- /dev/null
+++ b/contrib/.gitignore
@@ -0,0 +1,3 @@
+locale/**/*.pot
+Makefile.am
+Makefile.am.ext
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
deleted file mode 100644
index 2e1160f53..000000000
--- a/contrib/Makefile.am
+++ /dev/null
@@ -1,67 +0,0 @@
-SUBDIRS = .
-
-# English (en)
-tosendir=$(pkgdatadir)/tos/en
-
-# English (en)
-ppendir=$(pkgdatadir)/pp/en
-
-rdatadir=$(pkgdatadir)
-
-tosen_DATA = \
- tos/en/0.txt \
- tos/en/0.pdf \
- tos/en/0.epub \
- tos/en/0.xml \
- tos/en/0.html
-
-ppen_DATA = \
- pp/en/0.txt \
- pp/en/0.pdf \
- pp/en/0.epub \
- pp/en/0.xml \
- pp/en/0.html
-
-rdata_DATA = \
- auditor-report.tex.j2
-
-bin_SCRIPTS = \
- taler-bank-manage-testing \
- taler-exchange-revoke
-
-EXTRA_DIST = \
- $(bin_SCRIPTS) \
- $(tosen_DATA) \
- $(ppen_DATA) \
- update-tos.sh \
- update-pp.sh \
- tos/Makefile \
- tos/README \
- tos/tos.rst \
- tos/conf.py \
- tos/locale/de/LC_MESSAGES/tos.po \
- pp/Makefile \
- pp/README \
- pp/pp.rst \
- pp/conf.py \
- pp/locale/de/LC_MESSAGES/pp.po \
- $(rdata_DATA) \
- coverage.sh \
- gnunet.tag \
- microhttpd.tag
-
-# Change the set of supported languages here. You should
-# also update tos'XX'data and EXTRA_DIST accordingly.
-TOS_LANGUAGES="en de"
-PP_LANGUAGES="en de"
-
-# Change the terms-of-service version (Etag) to generate here!
-# This value should be incremented whenever there is a substantive
-# change in the original text (but not for the translations).
-TOS_VERSION=0
-PP_VERSION=0
-
-update-tos:
- VERSION=$(TOS_VERSION) ./update-tos.sh $(TOS_LANGUAGES)
-update-pp:
- VERSION=$(PP_VERSION) ./update-pp.sh $(PP_LANGUAGES)
diff --git a/contrib/Makefile.am.in b/contrib/Makefile.am.in
new file mode 100644
index 000000000..e278fad2f
--- /dev/null
+++ b/contrib/Makefile.am.in
@@ -0,0 +1,68 @@
+# This file is in the public domain.
+
+SUBDIRS = .
+
+tmplpkgdatadir = $(datadir)/taler/exchange/templates/
+dist_tmplpkgdata_DATA = \
+ kycaid-invalid-request.en.must \
+ kyc-proof-already-done.en.must \
+ kyc-proof-bad-request.en.must \
+ kyc-proof-endpoint-unknown.en.must \
+ kyc-proof-internal-error.en.must \
+ kyc-proof-target-unknown.en.must \
+ oauth2-authentication-failure.en.must \
+ oauth2-authorization-failure.en.must \
+ oauth2-authorization-failure-malformed.en.must \
+ oauth2-bad-request.en.must \
+ oauth2-conversion-failure.en.must \
+ oauth2-provider-failure.en.must \
+ persona-exchange-unauthorized.en.must \
+ persona-load-failure.en.must \
+ persona-exchange-unpaid.en.must \
+ persona-logic-failure.en.must \
+ persona-invalid-response.en.must \
+ persona-network-timeout.en.must \
+ persona-kyc-failed.en.must \
+ persona-provider-failure.en.must
+
+termsdir=$(datadir)/taler/terms/
+terms_DATA = \
+ exchange-tos-v0.rst \
+ exchange-tos-bfh-v0.rst \
+ exchange-pp-v0.rst
+
+install-exec-local:
+ find locale/ -name "*.po"
+ mkdir -p $(DESTDIR)$(datadir)
+ cp --parents -r $$(find locale/ -name "*.po") $(DESTDIR)$(datadir)
+
+rdatadir=$(datadir)/taler/exchange
+rdata_DATA = \
+ auditor-report.tex.j2
+
+bin_SCRIPTS = \
+ taler-auditor-dbconfig \
+ taler-exchange-dbconfig \
+ taler-terms-generator
+
+
+EXTRA_DIST = \
+ locale/de/LC_MESSAGES/exchange-tos-v0.po \
+ $(bin_SCRIPTS) \
+ gana-generate.sh \
+ gana/gnu-taler-error-codes/registry.rec \
+ gana/gnu-taler-error-codes/Makefile \
+ $(terms_DATA) \
+ $(rdata_DATA) \
+ coverage.sh \
+ gnunet.tag \
+ microhttpd.tag \
+ packages
+
+spapkgdatadir = $(prefix)/share/taler/exchange/spa/
+
+# This is for the single-page-app imported from the wallet-core.git
+# prebuilt branch. This MUST be the last line in the
+# Makefile.am.in, as it will be combined with the
+# actual SPA data by 'bootstrap'!
+dist_spapkgdata_DATA = \
diff --git a/contrib/auditor-report.tex.j2 b/contrib/auditor-report.tex.j2
index 5aade17da..bb645520f 100644
--- a/contrib/auditor-report.tex.j2
+++ b/contrib/auditor-report.tex.j2
@@ -1,5 +1,5 @@
% This file is part of TALER
-% Copyright (C) 2016--2019 Taler Systems SA
+% Copyright (C) 2016--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
@@ -88,12 +88,18 @@ In that time, the auditors processed the following table ranges:
& {{ reserves.end_ppr_reserve_in_serial_id }} \\ \hline
Reserves Out (withdraw) & {{ reserves.start_ppr_reserve_out_serial_id }}
& {{ reserves.end_ppr_reserve_out_serial_id }} \\ \hline
- Reserves Recoup & {{ reserves.start_ppr_reserve_recoup_serial_id }}
+ Reserves Recoup & {{ reserves.start_ppr_reserve_recoup_serial_id }}
& {{ reserves.end_ppr_reserve_recoup_serial_id }} \\ \hline
Reserves Close & {{ reserves.start_ppr_reserve_close_serial_id }}
& {{ reserves.end_ppr_reserve_close_serial_id }} \\ \hline
Aggregation & {{ aggregation.start_ppa_wire_out_serial_id }}
& {{ aggregation.end_ppa_wire_out_serial_id }} \\ \hline
+ Aggregation (wire) & {{ wire.start_pp_last_aggregation_serial_id }}
+ & {{ wire.end_pp_last_aggregation_serial_id }} \\ \hline
+ Deposits (wire) & {{ wire.start_pp_last_batch_deposit_id }}
+ & {{ wire.end_pp_last_batch_deposit_id }} \\ \hline
+ Reserves Close (wire) & {{ wire.start_pp_reserve_close_id }}
+ & {{ wire.end_pp_reserve_close_id }} \\ \hline
Coin withdraw & {{ coins.start_ppc_withdraw_serial_id }}
& {{ coins.end_ppc_withdraw_serial_id }} \\ \hline
Coin deposit & {{ coins.start_ppc_deposit_serial_id }}
@@ -102,9 +108,9 @@ In that time, the auditors processed the following table ranges:
& {{ coins.end_ppc_melt_serial_id }} \\ \hline
Coin refund & {{ coins.start_ppc_refund_serial_id }}
& {{ coins.end_ppc_refund_serial_id }} \\ \hline
- Coin recoup & {{ coins.start_ppc_recoup_serial_id }}
+ Coin recoup & {{ coins.start_ppc_recoup_serial_id }}
& {{ coins.end_ppc_recoup_serial_id }} \\ \hline
- Coin recoup refresh & {{ coins.start_ppc_recoup_refresh_serial_id }}
+ Coin recoup refresh & {{ coins.start_ppc_recoup_refresh_serial_id }}
& {{ coins.end_ppc_recoup_refresh_serial_id }} \\
\end{tabular}
\end{center}
@@ -139,6 +145,10 @@ In that time, the wire auditor processed the following table ranges:
{% endif %}
\end{center}
+The total credits to the exchange processed in
+this audit run was {\bf {{ wire.total_wire_in }}}.
+The total debits initiated by the exchange processed in
+this audit run was {\bf {{ wire.total_wire_out }}}.
\section{Operations}
@@ -147,6 +157,16 @@ be {\bf {{ coins.total_escrow_balance }}} (coins)
plus {\bf {{ reserves.total_escrow_balance }}} (reserves).
\noindent
+This should match the final balance computed from
+ingoing and outgoing wire transfers, which is
+{\bf {{ wire.final_balance}} }.
+
+\noindent
+A total of {\bf {{ wire.total_drained}} } in profits
+were transferred (over the lifetime of the exchange)
+to non-escrowed accounts.
+
+\noindent
The active operational risk stands at
{\bf {{ coins.total_active_risk }}}.
@@ -155,9 +175,8 @@ Loss (actualized risk from recoups) is
{\bf {{ coins.total_recoup_loss }}}.
\noindent
-Recoups of non-revoked coins are at
-{\bf {{ coins.total_irregular_recoups }}} (coins)
-plus {\bf {{ reserves.total_irregular_recoups }}} (reserves).
+Losses from irregular reserve operations are at
+{\bf {{ reserves.total_irregular_loss }}} (reserves).
\section{Income}
@@ -193,47 +212,101 @@ load. Small amounts of lag can occur in normal operation.
The total amount the exchange currently lags behind in deposits is
{\bf {{ wire.total_amount_lag }}}.
-Note that some lag is perfectly normal, as tiny amounts that are too small to be wired
-are deferred beyond the due date, hoping that additional transfers will push them above
-the tiny threshold. Below, we report {\em non-tiny} wire transfers that are lagging behind.
+Note that some lag is perfectly normal, as tiny amounts that are too small to
+be wired are deferred beyond the due date, hoping that additional transfers
+will push them above the tiny threshold. Below, we report {\em non-tiny} wire
+transfers that are lagging behind.
% Table generation tested by testcase #1 in test-auditor.sh
{% if wire.lag_details|length() == 0 %}
{\bf No non-tiny wire transfers that are lagging behind detected.}
{% else %}
- \begin{longtable}{l|r|r|c}
- {\bf Deadline} & {\bf Amount} & {\bf Row} & {\bf Claimed done} \\
- \multicolumn{4}{l}{\bf Coin} \\
- \multicolumn{4}{l}{\bf Target account} \\ \hline \hline
+ \begin{longtable}{l|r|r}
+ {\bf Deadline} & {\bf Amount} & {\bf Target account} \\ \hline \hline
\endfirsthead
- {\bf Deadline} & {\bf Amount} & {\bf Row} & {\bf Claimed done} \\
- \multicolumn{4}{l}{\bf Coin} \\
- \multicolumn{4}{l}{\bf Target account} \\ \hline \hline
+ {\bf Deadline} & {\bf Amount} & {\bf Target account} \\ \hline \hline
\endhead
\hline \hline
- {\bf Deadline} & {\bf Amount} & {\bf Row} & {\bf Claimed done} \\
- \multicolumn{4}{l}{\bf Coin} \\
- \multicolumn{4}{l}{\bf Target account} \\
+ {\bf Deadline} & {\bf Amount} & {\bf Target account} \\
\endfoot
\hline \hline
- {\bf Deadline} & {\bf Amount} & {\bf Row} & {\bf Claimed done} \\
- \multicolumn{4}{l}{\bf Coin} \\
- \multicolumn{4}{l}{\bf Target account} \\
+ {\bf Deadline} & {\bf Amount} & {\bf Target account} \\
\caption{Lagging non-tiny transactions.}
\label{table:lag}
\endlastfoot
{% for item in wire.lag_details %}
{{ item.deadline }} &
+ {{ item.total_amount }} &
+ {\tt
+ {% if 'account' in item %}
+ {{ item.account }}
+ {% endif %}
+ } \\ \hline
+{% endfor %}
+ \end{longtable}
+{% endif %}
+
+
+
+{% if wire.lag_kyc_details|length() == 0 %}
+ {\bf No KYC-blocked non-tiny wire transfers that are lagging behind detected.}
+{% else %}
+ \begin{longtable}{l|r|c|r}
+ {\bf Deadline} & {\bf Amount} & {\bf Requirement} & {\bf Target account} \\ \hline \hline
+\endfirsthead
+ {\bf Deadline} & {\bf Amount} & {\bf Requirement} & {\bf Target account} \\ \hline \hline
+\endhead
+ \hline \hline
+ {\bf Deadline} & {\bf Amount} & {\bf Requirement} & {\bf Target account} \\
+\endfoot
+ \hline \hline
+ {\bf Deadline} & {\bf Amount} & {\bf Requirement} & {\bf Target account} \\
+ \caption{Lagging non-tiny transactions due to missing KYC data.}
+ \label{table:lag}
+\endlastfoot
+{% for item in wire.lag_kyc_details %}
+ {{ item.deadline }} &
{{ item.amount }} &
- {{ item.row }} &
- {{ item.claimed_done }} \\
-\nopagebreak
- \multicolumn{4}{l}{ {\tt \small {{ item.coin_pub }} } } \\
+ {{ item.kyc_pending }} &
+ {\tt
+ {% if 'account' in item %}
+ {{ item.account }}
+ {% endif %}
+ } \\ \hline
+{% endfor %}
+ \end{longtable}
+{% endif %}
+
+
+{% if wire.lag_aml_details|length() == 0 %}
+ {\bf No non-tiny wire transfers that are lagging behind due to AML detected.}
+{% else %}
+ \begin{longtable}{l|r|r}
+ {\bf Deadline} & {\bf Amount}/{\bf Limit} & {\bf AML status} \\
+ \multicolumn{3}{l}{\bf Target account} \\ \hline \hline
+\endfirsthead
+ {\bf Deadline} & {\bf Amount}/{\bf Limit} & {\bf AML status} \\
+ \multicolumn{4}{l}{\bf Target account} \\ \hline \hline
+\endhead
+ \hline \hline
+ {\bf Deadline} & {\bf Amount}/{\bf Limit} & {\bf AML status} \\
+ \multicolumn{4}{l}{\bf Target account} \\
+\endfoot
+ \hline \hline
+ {\bf Deadline} & {\bf Amount}/{\bf Limit} & {\bf AML status} \\
+ \multicolumn{4}{l}{\bf Target account} \\
+ \caption{Lagging non-tiny transactions due to AML decisions.}
+ \label{table:lag}
+\endlastfoot
+{% for item in wire.lag_aml_details %}
+ {{ item.deadline }} &
+ {{ item.amount }}/{{ item.aml_limit }} &
+ {{ item.aml_status }} &
\nopagebreak
\multicolumn{4}{l}{ {\tt
- {% if 'payto_uri' in item.account %}
- {{ item.account.payto_uri }}
+ {% if 'account' in item %}
+ {{ item.account }}
{% endif %}
} } \\ \hline
{% endfor %}
@@ -300,8 +373,8 @@ confirmations to the auditor directly, so if the exchange is slow at
synchronizing its database with the auditor, some deposit
confirmations may be known at the auditor only directly. However, any
delta not accounted for by database synchronization delays is an
-indicator of a malicious exchange (or online singing key compromise)
-and should be answered by revoking the exchange's online siging keys.
+indicator of a malicious exchange (or online signing key compromise)
+and should be answered by revoking the exchange's online signing keys.
% TODO: maybe reference PhD thesis on this?
The total amount the exchange currently lags behind is
@@ -595,7 +668,7 @@ compromise resulting in proportional financial losses to the exchange.
\endfoot
\hline
{\bf Total loss} &
- {{ reserves.total_loss_balance_insufficient }} \\
+ {{ reserves.total_irregular_loss }} \\
\caption{Reserves with withdrawals higher than reserve funding.}
\label{table:reserve:balance_insufficient}
\endlastfoot
@@ -764,7 +837,7 @@ invalid and the amount involved should be considered lost.
\endfoot
\hline
\multicolumn{2}{l}{ {\bf Total losses} } &
- {\bf {{ coins.total_bad_sig_loss}} } \\
+ {\bf {{ coins.irregular_loss}} } \\
\caption{Losses from operations performed on coins without proper signatures.}
\label{table:bad_signature_losses}
\endlastfoot
@@ -883,7 +956,7 @@ actually received in some reserves.
{% endif %}
-\subsection{Missattributed incoming wire transfers}
+\subsection{Misattributed incoming wire transfers}
This section lists cases where the sender account record of an
incoming wire transfer differs between the exchange and the bank.
@@ -893,7 +966,7 @@ account.
% Table generation tested by testcase #9 in test-auditor.sh
-{% if wire.missattribution_in_inconsistencies|length() == 0 %}
+{% if wire.misattribution_in_inconsistencies|length() == 0 %}
{\bf All incoming wire transfer sender accounts matched up.}
{% else %}
\begin{longtable}{p{8.5cm}|r}
@@ -908,11 +981,11 @@ account.
\endfoot
\hline
{\bf Total amount} &
- {{ wire.total_missattribution_in}} \\
+ {{ wire.total_misattribution_in}} \\
\caption{Incoming wire transfer sender accounts not matching up.}
\label{table:wire_in:sender_account_inconsistencies}
\endlastfoot
-{% for item in wire.missattribution_in_inconsistencies %}
+{% for item in wire.misattribution_in_inconsistencies %}
{\tt \small \truncate{8.3cm}{ {{ item.reserve_pub }} } } &
{{ item.amount }} \\ \hline
{% endfor %}
@@ -924,7 +997,7 @@ account.
\subsection{Actual outgoing wire transfers} \label{sec:wire_check_out}
-This section highlights cases where the exchange missbehaved
+This section highlights cases where the exchange misbehaved
with respect to outgoing wire transfers.
% Table generation tested by testcase #11 in test-auditor.sh
@@ -967,6 +1040,39 @@ with respect to outgoing wire transfers.
\section{Minor irregularities}
+\subsection{Denominations without auditor signature}
+
+This section highlights denomination keys that lack a proper
+signature from the {\tt taler-auditor-offline} tool. This may be
+legitimate, say in case where the auditor's involvement in the
+exchange business is ending and a new auditor is responsible for
+future denominations. So this must be read with a keen eye on the
+business situation.
+
+
+{% if coins.unsigned_denominations|length() == 0 %}
+ {\bf All denominations officially audited by this auditor.}
+{% else %}
+ \begin{longtable}{p{6cm}|r|r|r}
+ {\bf Denomination} & {\bf Value} & {\bf Start} & {\bf End} \\ \hline \hline
+\endfirsthead
+ {\bf Denomination} & {\bf Value} & {\bf Start} & {\bf End} \\ \hline \hline
+\endhead
+ \hline \hline
+ {\bf Denomination} & {\bf Value} & {\bf Start} & {\bf End} \\ \hline \hline
+\endfoot
+ \caption{Denominations not officially audited by this auditor.}
+ \label{table:denominations:denoms_without_signatures}
+\endlastfoot
+{% for item in coins.unsigned_denominations %}
+ {\tt \tiny {{ item.denomination }} } &
+ {{ item.value }} &
+ {{ item.start_time }} &
+ {{ item.end_time }} \\ \hline
+{% endfor %}
+ \end{longtable}
+{% endif %}
+
\subsection{Incorrect reserve balance summary in database}
This section highlights cases where the reserve balance summary
@@ -1056,7 +1162,7 @@ have a clear financial impact.
{{ item.row }} &
{{ item.diagnostic }} \\
\nopagebreak
- \multicolumn{3}{l}{ {\tiny {\tt \truncate{\textwidth}{ {{ item.wire_offset_hash }} } } } } \\ \hline
+ \multicolumn{3}{l}{ {\tiny {\tt \truncate{\textwidth}{ {{ item.id }} } } } } \\ \hline
{% endfor %}
\end{longtable}
{% endif %}
diff --git a/contrib/ci/Containerfile b/contrib/ci/Containerfile
new file mode 100644
index 000000000..ac31c7ade
--- /dev/null
+++ b/contrib/ci/Containerfile
@@ -0,0 +1,68 @@
+FROM docker.io/library/debian:bookworm
+
+ENV DEBIAN_FRONTEND=noninteractive
+
+RUN apt-get update -yqq && \
+ apt-get install -yqq \
+ autoconf \
+ autopoint \
+ curl \
+ bash \
+ coreutils \
+ git \
+ libcurl4-gnutls-dev \
+ libgcrypt-dev \
+ libidn11-dev \
+ libjansson-dev \
+ libmicrohttpd-dev \
+ libpq-dev \
+ libqrencode-dev \
+ libsodium-dev \
+ libtool \
+ libunistring-dev \
+ make \
+ pkg-config \
+ python3-pip \
+ python3-sphinx \
+ python3-sphinx-rtd-theme \
+ recutils \
+ texinfo \
+ zlib1g-dev \
+ # For mustach testing (optional) \
+ libjson-c-dev \
+ # Debian packaging tools \
+ po-debconf \
+ build-essential \
+ debhelper-compat \
+ devscripts \
+ git-buildpackage \
+ # Documentation dependencies \
+ doxygen \
+ graphviz \
+ pandoc \
+ # Test suite dependencies \
+ jq \
+ postgresql \
+ sudo \
+ wget
+
+# Install Taler (and friends) packages
+RUN curl -sS https://deb.taler.net/apt-nightly/taler-bookworm-ci.sources \
+ | tee /etc/apt/sources.list.d/taler-bookworm-ci.sources
+
+RUN echo '\
+Package: * \n\
+Pin: origin "deb.taler.net" \n\
+Pin-Priority: 999' > /etc/apt/preferences.d/taler
+
+RUN cat /etc/apt/preferences.d/taler && \
+ apt-get update -y && \
+ apt-get install -y \
+ libgnunet-dev \
+ libgnunet \
+ gnunet \
+ && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /workdir
+
+CMD ["bash", "/workdir/ci/ci.sh"]
diff --git a/contrib/ci/ci.sh b/contrib/ci/ci.sh
new file mode 100755
index 000000000..f0be84242
--- /dev/null
+++ b/contrib/ci/ci.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+set -exvuo pipefail
+
+# Use podman, fails if it isn't found in PATH
+OCI_RUNTIME=$(which podman)
+REPO_NAME=$(basename "${PWD}")
+JOB_NAME="${1}"
+JOB_CONTAINER=$((grep CONTAINER_NAME contrib/ci/jobs/${JOB_NAME}/config.ini | cut -d' ' -f 3) || echo "localhost/${REPO_NAME}")
+JOB_ARCH=$((grep CONTAINER_ARCH contrib/ci/jobs/${JOB_NAME}/config.ini | cut -d' ' -f 3) || echo "${2:-amd64}")
+CONTAINER_BUILD=$((grep CONTAINER_BUILD contrib/ci/jobs/${JOB_NAME}/config.ini | cut -d' ' -f 3) || echo "True")
+
+echo "${JOB_CONTAINER}"
+
+if [ "${CONTAINER_BUILD}" = "True" ] ; then
+ "${OCI_RUNTIME}" build \
+ --arch "${JOB_ARCH}" \
+ -t "${JOB_CONTAINER}" \
+ -f contrib/ci/Containerfile .
+fi
+
+"${OCI_RUNTIME}" run \
+ --rm \
+ -ti \
+ --arch "${JOB_ARCH}" \
+ --env CI_COMMIT_REF="$(git rev-parse HEAD)" \
+ --volume "${PWD}":/workdir \
+ --workdir /workdir \
+ "${JOB_CONTAINER}" \
+ contrib/ci/jobs/"${JOB_NAME}"/job.sh
+
+top_dir=$(dirname "${BASH_SOURCE[0]}")
+
+#"${top_dir}"/build.sh
diff --git a/contrib/ci/jobs/0-codespell/config.ini b/contrib/ci/jobs/0-codespell/config.ini
new file mode 100644
index 000000000..bd7d73860
--- /dev/null
+++ b/contrib/ci/jobs/0-codespell/config.ini
@@ -0,0 +1,6 @@
+[build]
+HALT_ON_FAILURE = False
+WARN_ON_FAILURE = True
+CONTAINER_BUILD = False
+CONTAINER_NAME = nixery.dev/shell/codespell
+CONTAINER_ARCH = amd64
diff --git a/contrib/ci/jobs/0-codespell/dictionary.txt b/contrib/ci/jobs/0-codespell/dictionary.txt
new file mode 100644
index 000000000..f4ffa7945
--- /dev/null
+++ b/contrib/ci/jobs/0-codespell/dictionary.txt
@@ -0,0 +1,47 @@
+# List of "words" that codespell should ignore in our sources.
+#
+# Note: The word sensitivity depends on how the to-be-ignored word is
+# spelled in codespell_lib/data/dictionary.txt. F.e. if there is a word
+# 'foo' and you add 'Foo' _here_, codespell will continue to complain
+# about 'Foo'.
+#
+BRE
+ND
+Nd
+TE
+TEH
+UPDATEing
+WAN
+aci
+acn
+ba
+bre
+cant
+clen
+complet
+doas
+ect
+ehr
+fo
+ges
+hel
+ifset
+ist
+keypair
+nam
+nd
+onl
+openin
+ot
+ser
+sie
+som
+sover
+te
+te
+teh
+tha
+ths
+updateing
+wan
+wih
diff --git a/contrib/ci/jobs/0-codespell/job.sh b/contrib/ci/jobs/0-codespell/job.sh
new file mode 100755
index 000000000..64c0e779c
--- /dev/null
+++ b/contrib/ci/jobs/0-codespell/job.sh
@@ -0,0 +1,100 @@
+#!/bin/bash
+set -exuo pipefail
+
+job_dir=$(dirname "${BASH_SOURCE[0]}")
+
+skip=$(cat <<EOF
+ABOUT-NLS
+*/afl-tests/*
+**/auditor/*.sql
+**/templating/test-specs/*
+*.bbl
+*.bib
+*build-aux*
+*.bst
+*.cache/*
+*/cbdc-es.tex
+*/cbdc-it.tex
+*.cls
+configure*
+config.status
+*/contrib/*
+*/contrib/hellos/**
+*.dat
+*.deflate
+*.doc
+*/doc/*
+**/doc/flows/main.de.tex
+*/doc/texinfo.tex
+*.docx
+*.ecc
+*.eot
+*.epgz
+*.eps
+*.epub
+**/ExchangeSelection/example.ts
+*.fee
+*.fees
+*.file
+**/fonts/**
+*.gif
+*/.git/**
+*.gz
+*/i18n/strings.ts
+*.info
+*.jpeg
+*.jpg
+*.??.json
+*.json
+*/keys/*
+*key
+*.latexmkrc
+*libtool*
+*.log
+*/m4/*
+*.m4
+**/*.map
+*.min.js
+*.mp4
+*.odg
+*.ods
+*.odt
+*.pack.js
+*.pdf
+*.png
+*.PNG
+**/pnpm-lock.yaml
+*.po
+*.pptx
+*.priv
+**/rfc.bib
+*.rpath
+**/signing-key.asc
+*.sqlite
+*/src/anastasis-data.ts
+**/*.svg
+*.svg
+*.tag
+**/templating/mustach**
+*/templating/test?/**
+*/testcurl/test_tricky.c
+*/debian/tmp/**
+*/debian/taler-exchange/**
+*/debian/.debhelper/**
+*.tgz
+*.ttf
+*.ttf
+**/valgrind.h
+*/vpn/tests/**
+*.wav
+*.woff
+*.woff2
+*.xcf
+*.xlsx
+*.zkey
+EOF
+);
+
+echo Current directory: `pwd`
+
+codespell -I "${job_dir}"/dictionary.txt -S ${skip//$'\n'/,}
diff --git a/contrib/ci/jobs/1-build/build.sh b/contrib/ci/jobs/1-build/build.sh
new file mode 100755
index 000000000..d3fcfab85
--- /dev/null
+++ b/contrib/ci/jobs/1-build/build.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -exuo pipefail
+
+apt-get update
+apt-get upgrade -yqq
+
+./bootstrap
+./configure CFLAGS="-ggdb -O0" \
+ --enable-logging=verbose \
+ --disable-doc
+
+nump=$(grep processor /proc/cpuinfo | wc -l)
+make -j$(( $nump / 2 ))
+make
diff --git a/contrib/ci/jobs/1-build/job.sh b/contrib/ci/jobs/1-build/job.sh
new file mode 100755
index 000000000..8d79902c5
--- /dev/null
+++ b/contrib/ci/jobs/1-build/job.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -exuo pipefail
+
+job_dir=$(dirname "${BASH_SOURCE[0]}")
+
+"${job_dir}"/build.sh
diff --git a/contrib/ci/jobs/2-test/config.ini b/contrib/ci/jobs/2-test/config.ini
new file mode 100644
index 000000000..49cc8ea8a
--- /dev/null
+++ b/contrib/ci/jobs/2-test/config.ini
@@ -0,0 +1,6 @@
+[build]
+HALT_ON_FAILURE = False
+WARN_ON_FAILURE = True
+CONTAINER_BUILD = True
+CONTAINER_NAME = localhost/exchange
+CONTAINER_ARCH = amd64
diff --git a/contrib/ci/jobs/2-test/job.sh b/contrib/ci/jobs/2-test/job.sh
new file mode 100755
index 000000000..bfb24e335
--- /dev/null
+++ b/contrib/ci/jobs/2-test/job.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -exuo pipefail
+
+job_dir=$(dirname "${BASH_SOURCE[0]}")
+
+"${job_dir}"/test.sh
diff --git a/contrib/ci/jobs/2-test/test.sh b/contrib/ci/jobs/2-test/test.sh
new file mode 100755
index 000000000..a0002f226
--- /dev/null
+++ b/contrib/ci/jobs/2-test/test.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+set -evux
+
+apt-get update
+apt-get upgrade -yqq
+
+./bootstrap
+./configure CFLAGS="-ggdb -O0" \
+ --enable-logging=verbose \
+ --disable-doc
+
+nump=$(grep processor /proc/cpuinfo | wc -l)
+make clean
+make -j$(( $nump / 2 ))
+cd src/templating/
+./run-original-tests.sh
+make clean
+cd -
+make -j$(( $nump / 2 ))
+make install
+
+sudo -u postgres /usr/lib/postgresql/15/bin/postgres -D /etc/postgresql/15/main -h localhost -p 5432 &
+sleep 10
+sudo -u postgres createuser -p 5432 root
+sudo -u postgres createdb -p 5432 -O root talercheck
+
+check_command()
+{
+ # Set LD_LIBRARY_PATH so tests can find the installed libs
+ LD_LIBRARY_PATH=/usr/local/lib PGPORT=5432 make check
+}
+
+print_logs()
+{
+ set +e
+ for i in src/*/test-suite.log
+ do
+ echo "Printing ${i}"
+ cat "$i"
+ for FAILURE in $(grep '^FAIL:' ${i} | cut -d' ' -f2)
+ do
+ echo "Printing $(dirname $i)/${FAILURE}.log"
+ cat "$(dirname $i)/${FAILURE}.log"
+ done
+ done
+}
+
+if ! check_command ; then
+ print_logs
+ exit 1
+fi
diff --git a/contrib/ci/jobs/3-docs/config.ini b/contrib/ci/jobs/3-docs/config.ini
new file mode 100644
index 000000000..49cc8ea8a
--- /dev/null
+++ b/contrib/ci/jobs/3-docs/config.ini
@@ -0,0 +1,6 @@
+[build]
+HALT_ON_FAILURE = False
+WARN_ON_FAILURE = True
+CONTAINER_BUILD = True
+CONTAINER_NAME = localhost/exchange
+CONTAINER_ARCH = amd64
diff --git a/contrib/ci/jobs/3-docs/docs.sh b/contrib/ci/jobs/3-docs/docs.sh
new file mode 100755
index 000000000..fe2b96873
--- /dev/null
+++ b/contrib/ci/jobs/3-docs/docs.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -exuo pipefail
+
+./bootstrap
+./configure --enable-only-doc
+
+pushd ./doc/doxygen/
+
+make full
+
+popd
diff --git a/contrib/ci/jobs/3-docs/job.sh b/contrib/ci/jobs/3-docs/job.sh
new file mode 100755
index 000000000..a72bca4ba
--- /dev/null
+++ b/contrib/ci/jobs/3-docs/job.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -exuo pipefail
+
+job_dir=$(dirname "${BASH_SOURCE[0]}")
+
+"${job_dir}"/docs.sh
diff --git a/contrib/ci/jobs/4-deb-package/install-fix.patch b/contrib/ci/jobs/4-deb-package/install-fix.patch
new file mode 100644
index 000000000..8334c5a7a
--- /dev/null
+++ b/contrib/ci/jobs/4-deb-package/install-fix.patch
@@ -0,0 +1,13 @@
+diff --git a/debian/taler-exchange.install b/debian/taler-exchange.install
+index 631c270b..072c6231 100644
+--- a/debian/taler-exchange.install
++++ b/debian/taler-exchange.install
+@@ -36,6 +36,6 @@ usr/share/taler/exchange/templates/*.must
+ debian/etc-taler-exchange/* etc/
+
+ # Terms of service / privacy policy templates
+-usr/share/taler/exchange/*.rst
++#usr/share/taler/exchange/terms/*.rst
+ # Translations of ToS/PP
+-usr/share/taler/exchange/locale/*/LC_MESSAGES/*.po
++#usr/share/taler/exchange/terms/locale/*/LC_MESSAGES/*.po
diff --git a/contrib/ci/jobs/4-deb-package/job.sh b/contrib/ci/jobs/4-deb-package/job.sh
new file mode 100755
index 000000000..922f8bf63
--- /dev/null
+++ b/contrib/ci/jobs/4-deb-package/job.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+set -exuo pipefail
+# This file is in the public domain.
+# Helper script to build the latest DEB packages in the container.
+
+
+unset LD_LIBRARY_PATH
+
+# Install build-time dependencies.
+# Update apt cache first
+apt-get update
+apt-get upgrade -y
+mk-build-deps --install --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control
+
+export VERSION="$(./contrib/ci/jobs/4-deb-package/version.sh)"
+echo "Building package version ${VERSION}"
+EMAIL=none gbp dch --dch-opt=-b --ignore-branch --debian-tag="%(version)s" --git-author --new-version="${VERSION}"
+./bootstrap
+dpkg-buildpackage -rfakeroot -b -uc -us
+
+ls -alh ../*.deb
+mkdir -p /artifacts/exchange/${CI_COMMIT_REF} # Variable comes from CI environment
+mv ../*.deb /artifacts/exchange/${CI_COMMIT_REF}/
diff --git a/contrib/ci/jobs/4-deb-package/version.sh b/contrib/ci/jobs/4-deb-package/version.sh
new file mode 100755
index 000000000..52031b23a
--- /dev/null
+++ b/contrib/ci/jobs/4-deb-package/version.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+set -ex
+
+BRANCH=$(git name-rev --name-only HEAD)
+if [ -z "${BRANCH}" ]; then
+ exit 1
+else
+ # "Unshallow" our checkout, but only our current branch, and exclude the submodules.
+ git fetch --no-recurse-submodules --tags --depth=1000 origin "${BRANCH}"
+ RECENT_VERSION_TAG=$(git describe --tags --match 'v*.*.*' --exclude '*-dev*' --always --abbrev=0 HEAD || exit 1)
+ commits="$(git rev-list ${RECENT_VERSION_TAG}..HEAD --count)"
+ if [ "${commits}" = "0" ]; then
+ git describe --tag HEAD | sed -r 's/^v//' || exit 1
+ else
+ echo $(echo ${RECENT_VERSION_TAG} | sed -r 's/^v//')-${commits}-$(git rev-parse --short=8 HEAD)
+ fi
+fi
diff --git a/contrib/ci/jobs/5-deploy-package/config.ini b/contrib/ci/jobs/5-deploy-package/config.ini
new file mode 100644
index 000000000..8d6409a6e
--- /dev/null
+++ b/contrib/ci/jobs/5-deploy-package/config.ini
@@ -0,0 +1,5 @@
+[build]
+HALT_ON_FAILURE = True
+WARN_ON_FAILURE = True
+CONTAINER_BUILD = False
+CONTAINER_NAME = nixery.dev/shell/rsync
diff --git a/contrib/ci/jobs/5-deploy-package/job.sh b/contrib/ci/jobs/5-deploy-package/job.sh
new file mode 100755
index 000000000..d39cf9987
--- /dev/null
+++ b/contrib/ci/jobs/5-deploy-package/job.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -exuo pipefail
+
+ARTIFACT_PATH="/artifacts/exchange/${CI_COMMIT_REF}/*.deb"
+
+RSYNC_HOST="taler.host.internal"
+RSYNC_PORT=424242
+RSYNC_PATH="incoming_packages/bookworm-taler-ci/"
+RSYNC_DEST="rsync://${RSYNC_HOST}/${RSYNC_PATH}"
+
+
+rsync -vP \
+ --port ${RSYNC_PORT} \
+ ${ARTIFACT_PATH} ${RSYNC_DEST}
diff --git a/contrib/pp/pp.rst b/contrib/exchange-pp-v0.rst
index d37c10c2b..4800bd4e4 100644
--- a/contrib/pp/pp.rst
+++ b/contrib/exchange-pp-v0.rst
@@ -48,11 +48,11 @@ to our services without at least clear implied consent, and we only process
and retain information with a strict business need. That being said, when
using our Services, we inherently have to collect the following information:
- * Bank account details necessary when receiving funds from you to top-up your wallet or to transfer funds to you when you are being paid via Taler. At the current experimental stage, only the pseudonym and password you entered in the bank demonstrator is stored.
+* Bank account details necessary when receiving funds from you to top-up your wallet or to transfer funds to you when you are being paid via Taler. At the current experimental stage, only the pseudonym and password you entered in the bank demonstrator is stored.
- * The amounts being withdrawn or deposited, with associated unique transaction identifiers and cryptographic signatures authorizing the transaction. Note that for purchases, we cannot identify the buyer from the collected data, so when you spend money, we only receive non-personal information.
+* The amounts being withdrawn or deposited, with associated unique transaction identifiers and cryptographic signatures authorizing the transaction. Note that for purchases, we cannot identify the buyer from the collected data, so when you spend money, we only receive non-personal information.
- * When you contact us. We may collect certain information if you choose to contact us, for example to report a bug or other error with the Taler Wallet. This may include contact information such as your name, email address or phone number depending on the method you choose to contact us.
+* When you contact us. We may collect certain information if you choose to contact us, for example to report a bug or other error with the Taler Wallet. This may include contact information such as your name, email address or phone number depending on the method you choose to contact us.
How we collect and process information
@@ -60,9 +60,9 @@ How we collect and process information
We may process your information for the following reasons:
- * to transfer money as specified by our users (Taler transactions);
- * to assist government entities in linking income to the underlying contract
- * to support you using the Taler Wallet or to improve our Services
+* to transfer money as specified by our users (Taler transactions);
+* to assist government entities in linking income to the underlying contract as required by law and local regulations
+* to support you using the Taler Wallet or to improve our Services
How we share and use the information we gather
@@ -89,8 +89,7 @@ Agents or third party partners
We may provide your Personal Information to our employees, contractors,
agents, service providers, and designees (“Agentsâ€) to enable them to perform
certain services for us exclusively, including: improvement and maintenance of
-our software and Services. By accepting this Privacy Policy, as outlined
-above, you consent to any such transfer.
+our software and Services.
Protection of us and others
@@ -116,6 +115,43 @@ specified in the “How We Use the Information We Gather†section will be
deleted after ninety (90) days.
+What are your data protection rights?
+-------------------------------------
+
+Anastasis would like to make sure you are fully aware of all of your
+data protection rights. Every user is entitled to the following:
+
+**The right to access**: You have the right to request Anastasis for
+ copies of your personal data. We may charge you a small fee for this
+ service.
+
+**The right to rectification**: You have the right to request that
+Anastasis correct any information you believe is inaccurate. You also
+have the right to request Anastasis to complete information you
+believe is incomplete. The right to erasure - You have the right to
+request that Anastasis erase your personal data, under certain
+conditions.
+
+**The right to restrict processing**: You have the right to request
+ that Anastasis restrict the processing of your personal data, under
+ certain conditions.
+
+**The right to object to processing**: You have the right to object to
+ Anastasis's processing of your personal data, under certain
+ conditions.
+
+**The right to data portability**: You have the right to request that
+ Anastasis transfer the data that we have collected to another
+ organization, or directly to you, under certain conditions.
+
+If you make a request, we have one month to respond to you. If you
+would like to exercise any of these rights, please contact us at our
+email: privacy@taler-systems.com
+
+You can always contact your local data protection authority to enforce
+your rights.
+
+
Data retention
--------------
diff --git a/contrib/exchange-template/config/exchange-common.conf b/contrib/exchange-template/config/exchange-common.conf
index 4f17d3ec7..615c3bf72 100644
--- a/contrib/exchange-template/config/exchange-common.conf
+++ b/contrib/exchange-template/config/exchange-common.conf
@@ -26,15 +26,6 @@ DB_CONN_STR = "postgres:///talercheck"
# Accounts must have a payto:// URL
URL = payto://METHOD/DETAILS
-# To be included in /wire, accounts must have a signed wire file
-# Must match URL.
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json
-
-# For access to the account, we need to know the plugin.
-PLUGIN = "taler_bank"
-
-# TBD: authentication data.
-
# Accounts need to be enabled for the aggregator to debit them.
ENABLE_DEBIT = NO
@@ -44,12 +35,6 @@ ENABLE_CREDIT = YES
[account-2]
URL = payto://x-taler-wire/bank/2
-# Response for /wire
-# Must match URL.
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-2.json
-
-PLUGIN = taler_bank
-
# We also may need authentication data.
TALER_BANK_AUTH_METHOD = "none" # or basic
# TALER_TALER_TESTING_BANK_USERNAME = user
@@ -62,53 +47,3 @@ ENABLE_DEBIT = YES
ENABLE_CREDIT = YES
-# Wire fees are specified by wire method, NOT by wire plugin.
-[fees-x-taler-bank]
-# Fees for the forseeable future...
-# If you see this after 2018, update to match the next 10 years...
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
-WIRE-FEE-2027 = EUR:0.01
-
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-CLOSING-FEE-2027 = EUR:0.01
-
-[fees-sepa]
-# Fees for the forseeable future...
-# If you see this after 2018, update to match the next 10 years...
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
-WIRE-FEE-2027 = EUR:0.01
-
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-CLOSING-FEE-2027 = EUR:0.01
diff --git a/contrib/exchange-template/config/exchange-keyup.conf b/contrib/exchange-template/config/exchange-keyup.conf
index 382e121ce..8686e7082 100644
--- a/contrib/exchange-template/config/exchange-keyup.conf
+++ b/contrib/exchange-template/config/exchange-keyup.conf
@@ -1,19 +1,3 @@
-[exchange_keys]
-
-# how long is one signkey valid?
-signkey_duration = 4 weeks
-
-# how long are the signatures with the signkey valid?
-legal_duration = 2 years
-
-# how long do we generate denomination and signing keys
-# ahead of time?
-lookahead_sign = 32 weeks 1 day
-
-# how long do we provide to clients denomination and signing keys
-# ahead of time?
-lookahead_provide = 4 weeks 1 day
-
# Coin definitions are detected because the section
# name begins with "coin_". The rest of the
diff --git a/contrib/tos/tos.rst b/contrib/exchange-tos-bfh-v0.rst
index 2ef5c2c2d..33a2b76f7 100644
--- a/contrib/tos/tos.rst
+++ b/contrib/exchange-tos-bfh-v0.rst
@@ -1,13 +1,25 @@
-Terms Of Service
+Terms of Service
================
-Last Updated: 12.4.2019
+Last update: 26.04.2024
-Welcome! Taler Systems SA (“we,†“our,†or “usâ€) provides a payment service
+Welcome! The ICE research center of the Bern University of Applied Sciences
+in Switzerland (“we,†“our,†or “usâ€) provides an experimental payment service
through our Internet presence (collectively the “Servicesâ€). Before using our
Services, please read the Terms of Service (the “Terms†or the “Agreementâ€)
carefully.
+This is research
+----------------
+
+This is a research experiment. Any funds wired to our Bitcoin address are
+considered a donation to our research group. We may use them to enable
+payments following the GNU Taler protocol, or simply keep them at our
+discretion. The service is experimental and may also be discontinued at
+any time, in which case all remaining funds will definitively be kept by
+the research group.
+
+
Overview
--------
@@ -22,22 +34,24 @@ carefully.
Highlights:
~~~~~~~~~~~
- • You are responsible for keeping the data in your Taler Wallet at all times
+* You are responsible for keeping the data in your Taler Wallet at all times
under your control. Any losses arising from you not being in control of
your private information are your problem.
- • We will try to transfer funds we hold in escrow for our users to any legal
+* We may transfer funds we receive from our users to any legal
recipient to the best of our ability within the limitations of the law and
our implementation. However, the Services offered today are highly
experimental and the set of recipients of funds is severely restricted.
- • For our Services, we may charge transaction fees. The specific fee structure
+ Again, we stress this is a research experiment and technically all funds
+ held by the exchange are owned by the research group of the university.
+* For our Services, we may charge transaction fees. The specific fee structure
is provided based on the Taler protocol and should be shown to you when you
withdraw electronic coins using a Taler Wallet. You agree and understand
that the Taler protocol allows for the fee structure to change.
- • You agree to not intentionally overwhelm our systems with requests and
+* You agree to not intentionally overwhelm our systems with requests and
follow responsible disclosure if you find security issues in our services.
- • We cannot be held accountable for our Services not being available due to
- circumstances beyond our control. If we modify or terminate our services,
- we will try to give you the opportunity to recover your funds. However,
+* We cannot be held accountable for our Services not being available due to
+ any circumstances. If we modify or terminate our services,
+ we may give you the opportunity to recover your funds. However,
given the experimental state of the Services today, this may not be
possible. You are strongly advised to limit your use of the Service
to small-scale experiments expecting total loss of all funds.
@@ -45,7 +59,7 @@ Highlights:
These terms outline approved uses of our Services. The Services and these
Terms are still at an experimental stage. If you have any questions or
comments related to this Agreement, please send us a message to
-legal@taler-systems.com. If you do not agree to this Agreement, you must not
+ice@bfh.ch. If you do not agree to this Agreement, you must not
use our Services.
How you accept this policy
@@ -66,26 +80,36 @@ have last reviewed these Terms.
Services
--------
-We will try to transfer funds that we hold in escrow for our users to any
-legal recipient to the best of our ability and within the limitations of the
-law and our implementation. However, the Services offered today are highly
-experimental and the set of recipients of funds is severely restricted. The
-Taler Wallet can be loaded by exchanging fiat currencies against electronic
-coins. We are providing this exchange service. Once your Taler Wallet is
-loaded with electronic coins they can be spent for purchases if the seller is
-accepting Taler as a means of payment. We are not guaranteeing that any seller
-is accepting Taler at all or a particular seller. The seller or recipient of
+We will try to transfer funds that we receive from users to any legal
+recipient to the best of our ability and within the limitations of the
+law. However, the Services offered today are highly experimental and the set
+of recipients of funds is severely restricted. The Taler Wallet can be loaded
+by exchanging fiat or cryptocurrencies against electronic coins. We are
+providing this exchange service. Once your Taler Wallet is loaded with
+electronic coins they can be spent for purchases if the seller is accepting
+Taler as a means of payment. We are not guaranteeing that any seller is
+accepting Taler at all or a particular seller. The seller or recipient of
deposits of electronic coins must specify the target account, as per the
design of the Taler protocol. They are responsible for following the protocol
and specifying the correct bank account, and are solely liable for any losses
-that may arise from specifying the wrong account. We will allow the government
+that may arise from specifying the wrong account. We may allow the government
to link wire transfers to the underlying contract hash. It is the
responsibility of recipients to preserve the full contracts and to pay
whatever taxes and charges may be applicable. Technical issues may lead to
situations where we are unable to make transfers at all or lead to incorrect
-transfers that cannot be reversed. We will only refuse to execute transfers if
-the transfers are prohibited by a competent legal authority and we are ordered
-to do so.
+transfers that cannot be reversed. We may refuse to execute transfers if the
+transfers are prohibited by a competent legal authority and we are ordered to
+do so.
+
+When using our Services, you agree to not take any action that intentionally
+imposes an unreasonable load on our infrastructure. If you find security
+problems in our Services, you agree to first report them to
+security@taler-systems.com and grant us the right to publish your report. We
+warrant that we will ourselves publicly disclose any issues reported within 3
+months, and that we will not prosecute anyone reporting security issues if
+they did not exploit the issue beyond a proof-of-concept, and followed the
+above responsible disclosure practice.
+
Fees
----
@@ -104,17 +128,14 @@ We reserve the right to provide different types of rewards to users either in
the form of discount for our Services or in any other form at our discretion
and without prior notice to you.
-Eligibility
------------
+Eligibility and Financial self-responsibility
+---------------------------------------------
To be eligible to use our Services, you must be able to form legally binding
contracts or have the permission of your legal guardian. By using our
Services, you represent and warrant that you meet all eligibility requirements
that we outline in these Terms.
-Financial self-responsibility
------------------------------
-
You will be responsible for maintaining the availability, integrity and
confidentiality of the data stored in your wallet. When you setup a Taler
Wallet, you are strongly advised to follow the precautionary measures offered
@@ -131,22 +152,11 @@ modified or unmodified form. However, the GPL is a strong copyleft license,
which means that any derivative works must be distributed under the same
license terms as the original software. If you have any questions, you should
review the GNU GPL’s full terms and conditions at
-https://www.gnu.org/licenses/gpl-3.0.en.html. “Taler†itself is a trademark
+https://www.gnu.org/licenses/. “Taler†itself is a trademark
of Taler Systems SA. You are welcome to use the name in relation to processing
payments using the Taler protocol, assuming your use is compatible with an
official release from the GNU Project that is not older than two years.
-Your use of our services
-------------------------
-
-When using our Services, you agree to not take any action that intentionally
-imposes an unreasonable load on our infrastructure. If you find security
-problems in our Services, you agree to first report them to
-security@taler-systems.com and grant us the right to publish your report. We
-warrant that we will ourselves publicly disclose any issues reported within 3
-months, and that we will not prosecute anyone reporting security issues if
-they did not exploit the issue beyond a proof-of-concept, and followed the
-above responsible disclosure practice.
Limitation of liability & disclaimer of warranties
--------------------------------------------------
@@ -161,23 +171,18 @@ Taler Wallet, including, but not limited to your Taler Wallet coins or backup
encryption keys. You release us from all liability related to any losses,
damages, or claims arising from:
-(a) user error such as forgotten passwords, incorrectly constructed
- transactions;
-(b) server failure or data loss;
-(c) unauthorized access to the Taler Wallet application;
-(d) bugs or other errors in the Taler Wallet software; and
-(e) any unauthorized third party activities, including, but not limited to,
- the use of viruses, phishing, brute forcing, or other means of attack
- against the Taler Wallet. We make no representations concerning any
- Third Party Content contained in or accessed through our Services.
+1) user error such as forgotten passwords, incorrectly constructed transactions;
+2) server failure or data loss;
+3) unauthorized access to the Taler Wallet application;
+4) bugs or other errors in the Taler Wallet software; and
+5) any unauthorized third party activities, including, but not limited to, the use of
+viruses, phishing, brute forcing, or other means of attack against the Taler Wallet. We make no
+representations concerning any Third Party Content contained in or accessed through our Services.
Any other terms, conditions, warranties, or representations associated with
such content, are solely between you and such organizations and/or
individuals.
-Limitation of liability
------------------------
-
To the fullest extent permitted by applicable law, in no event will we or any
of our officers, directors, representatives, agents, servants, counsel,
employees, consultants, lawyers, and other personnel authorized to act,
@@ -185,19 +190,18 @@ acting, or purporting to act on our behalf (collectively the “Taler Partiesâ€
be liable to you under contract, tort, strict liability, negligence, or any
other legal or equitable theory, for:
-(a) any lost profits, data loss, cost of procurement of substitute goods or
- services, or direct, indirect, incidental, special, punitive, compensatory,
- or consequential damages of any kind whatsoever resulting from:
+1) any direct damages or
+2) any lost profits, data loss, cost of procurement of substitute goods or services, or
+direct, indirect, incidental, special, punitive, compensatory, or consequential damages of any kind
+whatsoever resulting from:
- (i) your use of, or conduct in connection with, our services;
- (ii) any unauthorized use of your wallet and/or private key due to your
- failure to maintain the confidentiality of your wallet;
- (iii) any interruption or cessation of transmission to or from the services; or
- (iv) any bugs, viruses, trojan horses, or the like that are found in the Taler
- Wallet software or that may be transmitted to or through our services by
- any third party (regardless of the source of origination), or
-
-(b) any direct damages.
+* your use of, or conduct in connection with, our services;
+* any unauthorized use of your wallet and/or private key due to your failure to maintain the
+confidentiality of your wallet;
+* any interruption or cessation of transmission to or from the services; or
+* any bugs, viruses, trojan horses, or the like that are found in the Taler Wallet software
+or that may be transmitted to or through our services by any third party (regardless of the source
+of origination).
These limitations apply regardless of legal theory, whether based on tort,
strict liability, breach of contract, breach of warranty, or any other legal
@@ -206,9 +210,6 @@ damages. Some jurisdictions do not allow the exclusion or limitation of
liability for consequential or incidental damages, so the above limitation may
not apply to you.
-Warranty disclaimer
--------------------
-
Our services are provided "as is" and without warranty of any kind. To the
maximum extent permitted by law, we disclaim all representations and
warranties, express or implied, relating to the services and underlying
@@ -225,8 +226,8 @@ implied warranties, so the foregoing disclaimers may not apply to you. This
paragraph gives you specific legal rights and you may also have other legal
rights that vary from state to state.
-Indemnity
----------
+Indemnity and Time limitation on claims and Termination
+-------------------------------------------------------
To the extent permitted by applicable law, you agree to defend, indemnify, and
hold harmless the Taler Parties from and against any and all claims, damages,
@@ -236,32 +237,16 @@ the Services; (b) any feedback or submissions you provide to us concerning the
Taler Wallet; (c) your violation of any term of this Agreement; or (d) your
violation of any law, rule, or regulation, or the rights of any third party.
-Time limitation on claims
--------------------------
-
You agree that any claim you may have arising out of or related to your
relationship with us must be filed within one year after such claim arises,
otherwise, your claim in permanently barred.
-Governing law
--------------
-
-No matter where you’re located, the laws of Switzerland will govern these
-Terms. If any provisions of these Terms are inconsistent with any applicable
-law, those provisions will be superseded or modified only to the extent such
-provisions are inconsistent. The parties agree to submit to the ordinary
-courts in Zurich, Switzerland for exclusive jurisdiction of any dispute
-arising out of or related to your use of the Services or your breach of these
-Terms.
-
-Termination
------------
-
In the event of termination concerning your use of our Services, your
obligations under this Agreement will still continue.
-Discontinuance of services
---------------------------
+
+Discontinuance of services and Force majeure
+--------------------------------------------
We may, in our sole discretion and without cost to you, with or without prior
notice, and at any time, modify or discontinue, temporarily or permanently,
@@ -273,24 +258,6 @@ or liable for any loss of funds in the event that we discontinue or depreciate
the Services and your Taler Wallet fails to transfer out the coins within a
three months notification period.
-No waiver
----------
-
-Our failure to exercise or delay in exercising any right, power, or privilege
-under this Agreement shall not operate as a waiver; nor shall any single or
-partial exercise of any right, power, or privilege preclude any other or
-further exercise thereof.
-
-Severability
-------------
-
-If it turns out that any part of this Agreement is invalid, void, or for any
-reason unenforceable, that term will be deemed severable and limited or
-eliminated to the minimum extent necessary.
-
-Force majeure
--------------
-
We shall not be held liable for any delays, failure in performance, or
interruptions of service which result directly or indirectly from any cause or
condition beyond our reasonable control, including but not limited to: any
@@ -301,14 +268,29 @@ services, failure of equipment and/or software, other catastrophe, or any
other occurrence which is beyond our reasonable control and shall not affect
the validity and enforceability of any remaining provisions.
-Assignment
-----------
+
+Governing law, Waivers, Severability and Assignment
+---------------------------------------------------
+
+No matter where you’re located, the laws of Switzerland will govern these
+Terms. If any provisions of these Terms are inconsistent with any applicable
+law, those provisions will be superseded or modified only to the extent such
+provisions are inconsistent. The parties agree to submit to the ordinary
+courts in Bern, Switzerland for exclusive jurisdiction of any dispute
+arising out of or related to your use of the Services or your breach of these
+Terms.
+
+Our failure to exercise or delay in exercising any right, power, or privilege
+under this Agreement shall not operate as a waiver; nor shall any single or
+partial exercise of any right, power, or privilege preclude any other or
+further exercise thereof.
You agree that we may assign any of our rights and/or transfer, sub-contract,
or delegate any of our obligations under these Terms.
-Entire agreement
-----------------
+If it turns out that any part of this Agreement is invalid, void, or for any
+reason unenforceable, that term will be deemed severable and limited or
+eliminated to the minimum extent necessary.
This Agreement sets forth the entire understanding and agreement as to the
subject matter hereof and supersedes any and all prior discussions,
@@ -317,6 +299,7 @@ prior versions of this Agreement) and every nature between us. Except as
provided for above, any modification to this Agreement must be in writing and
must be signed by both parties.
+
Questions or comments
---------------------
diff --git a/contrib/exchange-tos-netzbon-v0.rst b/contrib/exchange-tos-netzbon-v0.rst
new file mode 100644
index 000000000..ee10121b4
--- /dev/null
+++ b/contrib/exchange-tos-netzbon-v0.rst
@@ -0,0 +1,108 @@
+Allgemeine Geschäftsbedingungen für eNetzBon
+============================================
+
+Version vom 26.2.2024
+---------------------
+
+Diese Allgemeinen Geschäftsbedingungen gelten für alle Nutzer und Händler, die das Taler-Bezahlsystem für eNetzBon verwenden.
+
+1. Vertragspartner
+------------------
+
+Diese Allgemeinen Geschäftsbedingungen (AGB) regeln die Nutzung des Taler-Bezahlsystems (nachfolgend "Taler") zwischen Ihnen (nachfolgend "Nutzer") und Taler Operations AG ("wir", "uns").
+
+2. Produktbeschreibung
+----------------------
+
+* Um die angebotenen Dienste wahrnehmen zu können, müssen Nutzer ein Taler-Wallet (elektronische Geldbörse) als Taler-App auf ihrem Smartphone oder als Erweiterung in einem Browser auf dem PC installieren.
+* Es werden keine Daten von zahlenden Nutzern benötigt und es erfolgt auch keine Registrierung oder Kontenanlage der Nutzer. Sie können ihre Waren ohne Preisgabe ihrer Identität erwerben.
+* Die Nutzer zahlen entweder in bar an den Verein Soziale Ökonomie oder an seine Mitglieder (Händler, Verkäufer) oder überweisen von ihrem bestehenden Girokonto bei einer Schweizer Bank an das Bankkonto des Vereins Soziale Ökonomie in der Währung Schweizer Franken (CHF), um dann wertbasierte elektronische Münzen in ihre Taler-Wallets abzuheben. Sie beziehen damit digitale Wertmarken, die fachsprachlich auch Token oder elektronischen Münzen genannt werden.
+* Die elektronischen Münzen werden in der Denomination eNetzBon im Taler-Wallet angezeigt und stellen Repräsentanten der Geldwerte auf dem Verrechnungskonto des Vereins Soziale Ökonomie dar.
+
+3. Digitale Erweiterung des NetzBon mit eNetzBon
+------------------------------------------------
+
+Der Verein Soziale Ökonomie arbeitet an der Digitalisierung des NetzBon mit GNU Taler, einem digitalen Bezahlsystem, das komplett auf Freier Software und quelloffener Software (FLOSS, Free Libre and Open-Source Software) basiert und den Grundsätzen der Sozialen Ökonomie folgt.
+
+Die Taler Operations AG stellt dieses Bezahlsystem dem Verein Soziale Ökonomie zur Verfügung. Die Erweiterung von NetzBon mit eNetzBon soll die Effizienz und Wirtschaftlichkeit des NetzBon verbessern, ohne jedoch die physische Form des NetzBon in naher Zukunft abzuschaffen.
+
+4. Registrierung und Konten
+---------------------------
+
+Es werden keine Daten von zahlenden Nutzern benötigt und auch nicht erfasst. Es erfolgt auch keine Registrierung oder Kontenanlage der Nutzer.
+
+Am eNetzBon-System teilnehmende Betriebe (Händler, Verkäufer) halten beim Verein Soziale Ökonomie interne eNetzBon-Konten, auf welche sie die ihnen übergebenen Bareinzahlungen von Nutzern (Kunden, Käufern) übertragen, damit die Nutzer dann eNetzBon in ihre persönlichen Wallets abheben können.
+
+5. Käufe und Zahlungsbedingungen
+--------------------------------
+
+* Die Nutzer können eNetzBon durch zwei Verfahren erwerben bzw. das Guthaben auf ihrem Wallet erhöhen:
+
+ * a. Per Bareinzahlung durch Nutzung der "Taler Cashier-App" in der Markthalle und in der Buchhandlung, wo ein Nutzer den abzuhebenden Betrag in CHF an das Personal bar übergeben kann und dann durch das Wallet der Betrag in CHF abgehoben und im Wallet in eNetzBon umgetauscht wird.
+ * b. Per Banküberweisung an das PostFinanz-Konto des Vereins Soziale Ökonomie. Das Wallet hilft dabei den Nutzern, den Abhebevorgang einzuleiten und gibt dazu einen Verwendungszweck an, d.h. eine mehrstellige Kombination aus Nummer und Buchstaben, die im Kontoauszug des persönlichen Girokontos des jeweiligen Nutzers als Buchungstext angezeigt wird. Mit diesem Verwendungszweck kann das Wallet den Betrag zuerst in CHF abheben und dann im Wallet in eNetzBon umtauschen.
+
+* Der Preis eines eNetzBon beträgt 1 CHF. Bitte beachten Sie, dass NetzBon nicht rückerstattbar sind, daher müssen sie ausgegeben werden.
+* In der Phase der Markteinführung von eNetzBon werden keine Transaktionskosten von Nutzern erhoben. Bei der Bezahlung mit eNetzBon fallen daher vorerst keine Transaktionsgebühren an. Diese Allgemeinen Geschäftsbedingungen erlauben jedoch die Möglichkeit zukünftiger Änderungen der Gebührenordnung.
+
+6. Nutzung der Taler-App
+------------------------
+
+Die Taler-App ermöglicht keine direkten Interaktionen, sondern dient ausschliesslich dem Bezug und der Verwendung von eNetzBon bei teilnehmenden Geschäften. Spenden sind möglich. Die Nutzer verpflichten sich, die Taler-App gemäss den geltenden Gesetzen und Vorschriften zu verwenden.
+
+Dem Nutzer ist es nur möglich, mit öffentlichen Shops zu interagieren. Mit anderen Privatpersonen kann ein Nutzer nicht interagieren.
+
+7. Verpflichtende Sicherungsmassnahmen der Nutzer
+-------------------------------------------------
+
+Die Nutzer müssen sich darüber im klaren sein, elektronisches Geld wie Bargeld zu behandeln und ebenso zu sichern, d.h. ein Backup der Wallet-Daten anzulegen.
+
+Die Nutzer der Taler-App sind daher verpflichtet, den Zugang zum digitalen Endgerät zu sichern und vor unbefugtem Zugriff zu bewahren. Sie müssen die Wallet-Daten mit einer Sicherungskopie auf einem anderen Gerät speichern. Die Exportfunktion des Wallet hilft dabei, ein Backup der Wallet-Daten anzulegen und zu speichern.
+
+Ein verlorenes Nutzergerät mit einem Wallet darauf ohne Backup der eNetzBon auf einem anderen Gerät oder Datenträger bedeutet einen Totalverlust des Gegenwerts des NetzBon-Guthabens.
+
+8. Datenschutz
+--------------
+
+Die Datenschutzrichtlinien sind in einem separaten Dokument festgelegt, das die Nutzer auch in der Taler-App finden. Der Schutz der persönlichen Daten und finanziellen Informationen hat für uns höchste Priorität.
+
+Daten der Nutzer werden nicht erhoben. Beim Bezahlen mit eNetzBon werden nur Ort, Uhrzeit und der die eNetzBon empfangende Betrieb (Händler, Verkäufer) erhoben.
+
+Die anonymisierten Daten des Kaufs und der Überweisung von NetzBon an den Betrieb werden im Falle einer Untersuchung der Finma erhoben. Dies betrifft jedoch nicht Nutzer, die mit eNetzBon zahlen, sondern die Transaktionen in NetzBon zwischen dem Verein Soziale Ökonomie und den teilnehmenden Betrieben.
+
+9. Streitbeilegung
+------------------
+
+Bei etwaigen Streitigkeiten oder Unstimmigkeiten, die aus der Nutzung von Taler, der Taler-App und eNetzBon entstehen, verpflichten sich die Parteien, zunächst eine gütliche Einigung anzustreben.
+
+Wenn keine Einigung erzielt werden kann, unterliegt die Streitbeilegung den geltenden schweizerischen Gesetzen und der Gerichtsbarkeit von Biel.
+
+10. Haftungsausschluss
+----------------------
+
+UNSER TEXTVORSCHLAG ZU DIESEM PUNKT MUSS NOCH DISKUTIERT WERDEN: Die Taler Operations AG haftet bei der Erfüllung ihrer Verpflichtungen für jedes Verschulden ihrer Mitarbeiter und der Personen, die sie zur Erfüllung ihrer Verpflichtungen hinzuzieht. Soweit die Sonderbedingungen für einzelne Geschäftsbeziehungen oder sonstige Vereinbarungen etwas Abweichendes regeln, gehen diese Regelungen vor.
+
+Hat ein Nutzer des Taler-Bezahlsystems durch schuldhaftes Verhalten - zum Beispiel durch Verletzung von Mitwirkungspflichten wie regelmässige Sicherungen und Vorsichtsmassnahmen - zur Entstehung eines Schadens beigetragen, bestimmt sich nach den Grundsätzen des Mitverschuldens, in welchem Umfang Taler Operations AG und Nutzer den Schaden zu tragen haben.
+
+11. Allgemeine Bestimmungen
+---------------------------
+
+DER TEXTVORSCHLAG VOM VEREIN SOZIALE ÖKONOMIE ZU DIESEM PUNKT MUSS NOCH DISKUTIERT WERDEN: Der Verein Soziale Ökonomie behält sich vor, bei Verletzung von Regeln oder Missbrauch, gewisse Händler-Konten zu löschen.
+
+12. Änderungen der AGB
+----------------------
+
+Die Taler Operations AG behält sich das Recht vor, diese Allgemeinen Geschäftsbedingungen (AGB) ändern zu können. Die Nutzer werden über Änderungen in der Taler-App benachrichtigt.
+
+Die fortgesetzte Nutzung der Taler-App nach Änderungen der AGB gilt als Zustimmung zu den geänderten Bedingungen.
+
+13. Datenschutzbeauftragte
+--------------------------
+
+Den Datenschutzbeauftragten des Vereins Soziale Ökonomie erreichen Sie beim Sitz des Vereins in der Klybeckstrasse 95, 4057 Basel, und per E-Mail an kontakt@sozialeoekonomie.org.
+
+Den Datenschutzbeauftragten der Taler Operations AG erreichen Sie per Post an Taler Operations AG, Höheweg 80, 2502 Biel, und über die unten genannten Kontaktmöglichkeiten.
+
+14. Kontakt
+-----------
+
+Bei Fragen oder Anliegen bezüglich dieser AGB oder der Taler-App erreichen Sie uns per Post an Taler Operations AG, Höheweg 80, 2502 Biel, und über die unten genannten Kontaktmöglichkeiten.
diff --git a/contrib/exchange-tos-tops-v0.rst b/contrib/exchange-tos-tops-v0.rst
new file mode 100644
index 000000000..2217f781c
--- /dev/null
+++ b/contrib/exchange-tos-tops-v0.rst
@@ -0,0 +1,959 @@
+Allgemeine Geschäftsbedingungen für die Nutzung von TALER
+=========================================================
+
+1. Allgemeines
+--------------
+
+1.1. Dienstleistung / Geltungsbereich
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Die Taler Operations AG (nachfolgend "**TALER AG**") ist eine Schweizer Aktiengesellschaft
+mit Sitz in Zürich.
+Die TALER bietet Privatkundinnen und -kunden (nachfolgend "**Kundin und/oder Kunde**")
+unter dem Namen "TALER†eine eigenes Bezahlungssystem an (nachfolgend "TALERâ€).
+Diese Allgemeinen Geschäftsbedingungen (nachfolgend "**AGB**") regeln die Benutzung von
+TALER App und die von der TALER AG über die TALER erbrachten Dienstleistungen.
+Diese AGB gelten als akzeptiert, sobald die Kundin bzw. der Kunde in der TALER App das
+Einverständnis erklärt hat.
+
+[KOMMENTAR SK]
+1. Wenn es wirklich nötig ist, für "Taler Operations AG" eine Abkürzung zu verwenden, dann
+würde "TALER AG" aus Sicht der GNU-Community den Namen von GNU Taler sehr vereinnahmen.
+Mein Vorschlag wäre noch kürzer als TALER AG und birgt keine Verwechslungsgefahr,
+Vereinnahmung oder Anmaßung: TOPS AG
+
+CG: Warum nicht einfach "TOPS" statt "TOPS AG"? AG ist auch nicht relevant.
+
+2. Bitte lasst uns Gendersprache vermeiden und statt "**Kundin und Kunde**" lieber
+"**Nutzer**" verwenden und möglichst im Plural schreiben.
+
+CG: Absolut, Gendersprache ist auslaenderfeindlich weil fuer nicht-Muttersprachler
+ deutlich schwerer. Aber besser *Kunden* statt *Nutzer* weil vmtl. Kunde ein Rechtsbegriff ist.
+
+3. **Zahlungsdienst** wäre IMHO rechtlich und sprachlich besser als "Bezahlungssystem".
+Dieser Begriff entspricht dem englischen 'payment service provider' auch nach ISO 220022.
+
+CG: OK.
+
+4. Ich würde dem Zahlungsdienst keinen "Namen" geben - vor allem nicht TALER, weil das
+sofort mit einer Währung assoziiert wird. Taler ist jedoch keine Währung, sondern ein
+Bezahlsystem, das in unserem Fall in CHF denominiertes e-Geld emittiert, genauer
+gesagt TOPS-CHF. Wenn wir dafür eine Kurzbezeichnung dafür brauchen, dann wäre
+mein Vorschlag **eCHF**.
+
+Ja, bin auch gegen die Benennung, vor allem weil es eben kein *eigenes* System ist,
+nur weil ich einen Web-Server betreibe, besitze ich ja nicht das Web ;-).
+
+5. Weitere Begriffsbestimmungen wären hier sinnvoll:
+- **Zahlungsdienst** bezeichnet die Dienstleistung der Taler Operations AG mit Sitz in
+Biel (Höheweg 80, 2502 Biel, Mitglied in der anerkannten Selbstregulierungsorganisation
+gemäss Art. 24 GwG, dem VQF - Verein zur Qualitätssicherung von Finanzdienstleistungen).
+
+CG: Nein, zu komplex. Weglassen.
+
+- **Selbstregulierung** bezeichnet die Eigenschaft der Taler Operations AG als Mitglied
+einer Selbstregulierungsorganisation wie dem VQF und Betreiberin einer
+Sandbox-Dienstleistung nach Art. 6 Abs. 2 BankV ohne gewerbsmäßige Bankeneigenschaft
+(Nichtbank).
+
+CG: Auch nicht gut. "TOPS ist Mitglied im VQF, einer FINMA-akkreditierten
+Selbstregulierungsorganisation. TOPS wird daher nicht direkt von der FINMA
+beaufsichtigt, sondern betreibt eine Finanzdienstleisung nach Art. 6 Abs. 2
+BankV ohne gewerbsmäßige Bankeneigenschaft (Nichtbank) und somit ohne
+Kundeneinlagensicherung."
+
+- **Taler-Wallet** bezeichnet eine von Taler Operations AG bereitgestellte Software, die
+digitales Bargeld (e-Geld) verwaltet, welches der Zahlungsdienst emittiert und zur
+Zahlung an Begünstigte wieder einlöst.
+- **Nutzer** bezeichnet Inhaber von Taler-Wallets und damit Zahlende bzw. potenziell
+Zahlende.
+
+CG: "Kunden sind Inhaber von durch TOPS signierten und in CHF denominierten Wertmarken
+welche in Taler-Wallets in Eigenverantwortung gespeichert werden und mit denen Kunden
+bezahlen koennen."
+
+- **Begünstigte** bezeichnet Händler, Betriebe, Verkäufer und sonstige Empfänger von
+Ãœberweisungen des Zahlungsdiensts.
+- **Ãœberweisungen von Nutzern** bezeichnet die Ãœberweisungen an den hier angebotenen
+Zahlungsdienst mit dem Ziel des Abhebens von e-Geld in ein persönliches Wallet.
+
+CG: Rest weg-kuerzen:
+
+"", die als
+Publikumseinlagen bei Nichtbanken gelten. Taler Operations AG unterliegt nicht der
+Einlagensicherung und als Nichtbank auch nicht der Bankenaufsicht. Die entgegengenommen
+Gelder werden auf einem Sammelkonto aufbewahrt, um Zahlungen zwischen den Nutzern bzw.
+zwischen Zahlenden (Käufern) und Begünstigen (Verkäufern) auszuführen."" (alles streichen)
+
+- **Überweisungen an Begünstigte** bezeichnet die Überweisungen des Zahlungsdiensts an
+Händler, Betriebe, Verkäufer und sonstige Empfänger, die IBAN-Konten an Finanzinstituten in
+der Schweiz führen müssen, um die Zahlungen ihrer Kunden über den Zahlungsdienst empfangen
+zu können.
+- **Geschäftsbeziehung** bezeichnet die Beziehung zwischen Taler Operations AG und den
+Begünstigten (Händler, Betriebe, Verkäufer und sonstige Empfänger). Sollten für über 12
+Monate keine Transaktionen an die Begünstigen erfolgen, gilt die Geschäftsbeziehung als
+beendet.
+
+CG: Taler Operations AG => TOPS (ueberall!).
+
+[KOMMENTAR SK]
+
+1.2. Zugang zu den TALER Dienstleistungen
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+TALER ist ein System, das bargeldlose Zahlungen über das TALER Zahlungssystem ermöglicht.
+TALER kann von Kundinnen und Kunden verwendet werden, um Zahlungen zwischen TALER Nutzern
+durchzuführen ("**P2P-Zahlung**") und als Zahlungsmittel im stationären Handel, an
+Automaten, online und in Apps bei autorisierten Händlern oder Dienstleistungsanbietern, die
+TALER als Zahlungsmittel akzeptieren (nachfolgend "**Händler**"), eingesetzt werden
+("**P2M-Zahlung**").
+
+CG: nein, nicht noch Haendler einfuehren, einfach bei **Beguenstigten** bleiben!
+
+Die TALER AG kann sodann die Verwendung der TALER App auch im Ausland bei Händlern zulassen,
+die an einem mit dem TALER Zahlungssystem kooperierenden ausländischen Zahlungssystem
+angeschlossen sind. Solche Transaktionen werden vom ausländischen Zahlungssystem an das
+TALER Zahlungssystem weitergeleitet (nachfolgend "**internationale Zahlung**").
+Darüber hinaus bietet die TALER AG verschiedene Mehrwertleistungen an, namentlich die
+Hinterlegung oder Aktivierung von Sichtkarten und Dienstleistungen im Bereich des
+Mobile-Marketing. Diese Mehrwertleistungen erlauben Kundinnen und Kunden u.a., Coupons,
+Stempelkarten und weitere Kampagnen in der TALER App zu erhalten und zu verwalten, Stempel
+zu sammeln und Treuegeschenke, Rabatte und Gutschriften über die TALER App einzulösen.
+
+CG: obiger Paragraph ist kompletter Unsinn, einfach komplett Streichen. Wir machen
+KEINE internationalen Zahlungen, und auch keine "Mehrwertleistungen". Einfach killen!
+
+[NETZBON-NEU]
+Der Verein Soziale Ökonomie arbeitet an der Digitalisierung des NetzBon mit GNU Taler,
+einem digitalen Bezahlsystem, das komplett auf Freier Software und quelloffener Software
+(FLOSS, Free Libre and Open-Source Software) basiert und den Grundsätzen der Sozialen
+Ökonomie folgt. Die Taler Operations AG stellt dieses Bezahlsystem dem Verein Soziale
+Ökonomie zur Verfügung. Die Erweiterung von NetzBon mit eNetzBon soll die Effizienz und
+Wirtschaftlichkeit des NetzBon verbessern, ohne jedoch die physische Form des NetzBon in
+naher Zukunft abzuschaffen.
+[NETZBON-NEU]
+
+[KOMMENTAR SK]
+- Treffender als "P2M-Zahlung" wäre m.E. "C2M-Zahlung" (Customer-to-Merchant), weil die
+Zahlenden im Normalfall keine "Peers" sind, sondern einfach nur deren Kunden. P2P-Zahlungen
+liegen hingegen definitionsgemäß auch vor, wenn Zahlende von ihrem Wallet auf das Wallet
+eines Händlers Coins übertragen (Wallet-Exchange-Wallet).
+- Müssen wir "P2M" als Begriff in den AGB unbedingt verwenden? Bitte diskutieren.
+[KOMMENTAR SK]
+
+CG: Ich sehe gar nicht, das wir ueberhaupt P2P vs. P2M/C2M-Zahlungen in den AGBs unterscheiden muessen/sollten.
+
+
+1.3. Technische Voraussetzungen
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+TALER kann "_____________________". Benötigt wird "__________________________".
+Die Nutzung der Zahlungsfunktion und der Mehrwertleistungen erfordert eine aktive
+Internetverbindung.
+
+[NETZBON-NEU]
+Um die angebotenen Dienste wahrnehmen zu können, müssen Nutzer ein Taler-Wallet
+(elektronische Geldbörse) als Taler-App auf ihrem Smartphone oder als Erweiterung in einem
+Browser auf dem PC installieren.
+[NETZBON-NEU]
+
+CG: Nutzer => Kunden
+
+[KOMMENTAR SK]
+- Mir ist nicht klar, was in die Leerzeilen kommen soll.
+
+CG: Ditto.
+
+- Der Satz darunter ist korrekt, aber "Mehrwertleistungen" ein TWINT-Begriff, den wir
+streichen sollten.
+
+CG: JA!
+
+- Ich empfehle, den Satz von NETZBON zu übernehmen, denn er erwähnt auch die
+Browser-Erweiterungen und spricht allumfassend von "Diensten".
+
+CG: Ja. Bitte noch hinzufuegen: "Kunden sind frei in der Wahl ihrer Taler-Wallet
+ Anwendung. Beguenstigte sind ebenfalls frei in der Wahl ihrer Taler-Wallet
+ Anwendung bzw. ihres Taler-Backends. Verschiedene Loesungen werden von
+ diversen Anbietern bereitgestellt.
+ TOPS macht hier keine Einschraenkungen, und uebernimmt keine Gewaehrleistung.
+ Kunden sind eigenverantwortlich fuer die Sicherheit ihrer Taler-Wallets bzw.
+ Taler-Backends und der darin gespeicherten Wertmarken bzw. Transaktionsdaten."
+[KOMMENTAR SK]
+
+1.4. Registrierung und Identifizierung
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Zur Nutzung von TALER sind die Kundinnen und Kunden verpflichtet, sich in bei TALER zu
+registrieren und die verlangten Informationen zur Verfügung zu stellen. Die TALER AG behält
+sich vor, zur Erfüllung regulatorischer Vorgaben jederzeit weitere Informationen zu
+verlangen. Die registrierte Telefonnummer wird aus Sicherheitsgründen per SMS verifiziert.
+Mit der Registration bestätigt die Kundin bzw. der Kunde, die rechtmässige Nutzerin bzw.
+Nutzer der Telefonnummer und des Smartphones zu sein.
+Bei einer Änderung der bei der Registrierung angegebenen Daten müssen diese unverzüglich in
+TALER aktualisiert werden.
+Die TALER AG behält sich vor, Registrierungsgesuche ohne Angabe von Gründen abzulehnen bzw.
+bereits erfolgte Registrationen wieder rückgängig zu machen.
+
+[NETZBON-NEU]
+Es werden keine Daten von zahlenden Nutzern benötigt und es erfolgt auch keine
+Registrierung oder Kontenanlage der Nutzer. Sie können ihre Waren ohne Preisgabe ihrer
+Identität erwerben.
+
+Am eNetzBon-System teilnehmende Betriebe (Händler, Verkäufer) halten beim Verein Soziale
+Ökonomie interne eNetzBon-Konten, auf welche sie die ihnen übergebenen Bareinzahlungen von
+Nutzern (Kunden, Käufern) übertragen, damit die Nutzer dann eNetzBon in ihre persönlichen
+Wallets abheben können. Sie benötigen diese internen eNetzBon-Konten ebenfalls für den
+Empfang von Zahlungen der Nutzer für Güter (Waren und Dienstleistungen) der Betriebe.
+
+Die Nutzer leisten Ihre Einzahlung an das Bezahlsystem entweder in bar an den Verein
+Soziale Ökonomie oder an seine Mitglieder (Händler, Verkäufer) oder überweisen von ihrem
+bestehenden Girokonto bei einer Schweizer Bank an das Bankkonto des Vereins Soziale Ökonomie
+in der Währung Schweizer Franken (CHF), um dann wertbasierte elektronische Münzen in ihre
+Taler-Wallets abzuheben. Sie beziehen damit digitale Wertmarken, die wie ein
+Gutschein oder Prepaid-Guthaben zu betrachten sind. Sie werden auch als Token
+oder Coins bezeichnet. Die elektronischen Münzen werden in der Komplementärwährung
+"eNetzBon" im Taler-Wallet angezeigt und stellen Repräsentanten der Geldwerte auf dem
+Verrechnungskonto des Vereins Soziale Ökonomie in "NetzBon" dar.
+[NETZBON-NEU]
+
+[KOMMENTAR SK]
+Statt des ersten Absatzes von 1.4.:
+Zur Nutzung des Zahlungsdiensts bei Überweisungen an **Begünstige** von über 15.000
+CHF/Jahr sind diese verpflichtet, sich in bei Taler Operations AG zu registrieren und die
+dabei verlangten Informationen zur Verfügung zu stellen. Die Taler Operations AG behält
+sich vor, zur Erfüllung regulatorischer Vorgaben jederzeit weitere Informationen zu
+verlangen.
+
+Taler Operations AG benötigt zur Registrierung der Händler deren IBAN, Adresse und
+Telefonnummer (AMLA-Akte) oder es gelten die Empfangslimiten für Händler. Zur
+Nutzung des Zahlungsdiensts gehen die Begünstigten eine Geschäftsbeziehung mit Taler
+Operations AG ein. Ab einer bestimmten Umsatzhöhe sind sie verpflichtet, sich zu
+registrieren und die verlangten Informationen zur Verfügung zu stellen.
+
+Es erfolgt keine Registrierung oder Kontenanlage der **Nutzer** bei Taler Operations AG
+oder dem Bezahlsystem bzw. Zahlungsdienst. Es werden jedoch die IBAN-Konten erfasst, von
+denen die eingehenden Überweisungen erfolgen. Die Nutzer brauchen für das Abheben in
+Taler-Wallets eine Schweizer Telefonnummer zum Empfang von TANs, die der Zahlungsdienst
+versendet. Es gelten Höchstabhebegrenzen von 5.000 CHF pro Monat bzw. 15.000 CHF pro Jahr
+für die Nutzer. Bei Zahlungen an andere Nutzer (P2P-Zahlungen) bestehen für Nutzer mit
+Wohnsitz in der Schweiz Limiten von CHF 1.000 pro Monat und CHF 5.000 pro Kalenderjahr für
+das Senden und das Empfangen von e-Geld.
+[KOMMENTAR SK]
+
+CG: Bitte allgemeiner halten, die Grenzwerte CHF sollten gar nicht explizit in
+den AGBs auftauchen! Stattdessen: "Zur Nutzung des Zahlungsdienstes sind
+Kunden und Beguenstigte verpflichtet TOPS bei der Erfuellung regulatorischer
+Vorgaben zu unterstuetzen. Insbesondere kann TOPS Auskunft verlangen ueber die
+Identitaet von wirtschaftlich Beguenstigten. TOPS hat das Recht und die
+Pflicht ggf. Kunden und Beguenstigte von der Nutzung des Systems
+auszuschliessen sollten diese die notwendigen Auskuenfte verweigern oder
+inkorrekte Angaben machen."
+
+
+
+1.5. Geheimhaltung
+~~~~~~~~~~~~~~~~~~
+
+Der Umstand der Geschäftsbeziehung und daraus resultierende Daten (z.B. Name, Wohnort,
+Transaktionsdaten) werden grundsätzlich vertraulich behandelt. Sie können zur Erbringung von
+Dienstleistungen soweit notwendig an den Zahlungsempfänger sowie an weitere Dritte bekannt
+gegeben werden. Die Vertraulichkeit ist sodann zur Wahrung berechtigter Interessen der TALER
+AG, aber insbesondere in folgenden Fällen, aufgehoben:
+* Wahrnehmung gesetzlicher Auskunftspflichten und Erfüllung regulatorischer Vorgaben
+* Inkasso von Forderungen der TALER AG?
+* Gerichtliche Auseinandersetzungen.
+
+[NETZBON-NEU]
+Es werden keine Daten von zahlenden Nutzern benötigt und auch nicht erfasst. Es erfolgt auch
+keine Registrierung oder Kontenanlage der Nutzer.
+[NETZBON-NEU]
+
+[KOMMENTAR SK]
+- Satz 1: Geschäftsbeziehung **mit Begünstigten**
+- Ergänzen: Satz aus NETZBON-NEU
+[KOMMENTAR SK]
+
+CG: "Personenbezogene Daten werden von TOPS nur im Rahmen der zur Erfuellung
+gesetzlicher Verpflichtungen notwendigen Umfang erhoben, verarbeitet,
+aufbewahrt oder weitergegeben. Beim Bezahlvorgang mit e-Geld werden keine
+Daten zur Identitaet vom zahlenden Nutzer erfasst."
+
+
+
+1.6. Support
+~~~~~~~~~~~~
+
+Die TALER AG stellt den Kundinnen und Kunden im Sinne eines technischen Supports über die
+TALER eine Hilfefunktion zur Verfügung. Für die Erbringung dieses Supports können von der
+TALER AG auch Dritte beigezogen werden, an welche hierfür Zugriff auf relevante Daten
+gegeben werden kann.
+
+[KOMMENTAR SK]
+Ich würde hier die Haftung für die Aktionen der Dritten einschränken. Zugriff auf relevante
+Daten hätten dritte Parteien wie z.B. Auditoren oder Behörden übrigens auch nur bei Händlern
+in Bezug auf deren Daten und Umsätze. Bitte diskutieren.
+[KOMMENTAR SK]
+
+CG: Ich wuerde nur das Deutsch korrigieren wollen:
+"TOPS stellt den Nutzern auf Anfrage technischen Support zur Verfügung. An
+der Erbringung dieses Supports können Dritte beteiligt sein. Diese erhalten
+hierfür Zugriff auf notwendige personenbezogene Daten zur Kommunikation mit
+den Nutzern."
+
+
+1.7. Sorgfalts- und andere Pflichten der Kundinnen und Kunden
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Beim Umgang mit TALER sind insbesondere folgende Sorgfaltspflichten durch die Kundinnen und
+Kunden einzuhalten:
+* Das Smartphone, PC, Notebook etc. ist vor unbefugter Benutzung oder Manipulation zu
+schützen (z.B. mittels Geräte- bzw. Displaysperre).
+* Der Code für die Nutzung von TALER ist geheim zu halten, darf keinesfalls an andere
+Personen weitergegeben, oder zusammen mit dem Smartphone aufbewahrt werden.
+* Der gewählte Code darf nicht aus leicht ermittelbaren Kombinationen (Mobile-Nummer,
+Geburtsdatum usw.) bestehen.
+* Im Schadenfall haben die Kundinnen und Kunden nach bestem Wissen zur Aufklärung des
+Falls und zur Schadensminderung beizutragen. Bei strafbaren Handlungen ist Anzeige bei der
+Polizei zu erstatten.
+* Vor jeder Ausführung einer Zahlung sind die Angaben zum Zahlungsempfänger zu
+überprüfen, um Fehltransaktionen zu verhindern.
+* Es ist dafür zu sorgen, dass der Kontakt zur TALER AG nicht abbricht. Kommt es zu
+einem Kontaktabbruch, so kann die TALER AG die ihr entstehenden Kosten für
+Adressnachforschungen, wie auch die besondere Behandlung und Ãœberwachung von
+nachrichtenlosen Vermögenswerten, den Kundinnen und Kunden weiterbelasten. Das Vorgehen bei
+nachrichtenlosen Vermögen und die jeweils geltende Gebührentabelle lässt sich hier einsehen.
+Kontaktlose Geschäftsbeziehungen mit einem Schuldsaldo werden von der TALER AG aufgelöst.
+Die Kundinnen und Kunden sind für die Verwendung (Nutzung) ihres Smartphones verantwortlich
+und tragen sämtliche Folgen, die sich aus der Verwendung der TALER App auf dem Smartphone
+ergeben. Insbesondere werden Handlungen, die eine Drittperson unberechtigt mit der TALER App
+auf dem Smartphone einer Kundin bzw. Kunden vornimmt, der Kundin bzw. dem Kunden
+zugerechnet.
+
+[NETZBON-NEU]
+Die Nutzer müssen sich darüber im klaren sein, elektronisches Geld wie Bargeld zu
+behandeln und ebenso zu sichern, d.h. ein Backup der Wallet-Daten anzulegen. Die Nutzer der
+Taler-App sind daher verpflichtet, den Zugang zum digitalen Endgerät zu sichern und vor
+unbefugtem Zugriff zu bewahren. Sie müssen die Wallet-Daten mit einer Sicherungskopie auf
+einem anderen Gerät speichern. Die Exportfunktion des Wallet hilft dabei, ein Backup der
+Wallet-Daten anzulegen und zu speichern. Ein verlorenes Nutzergerät mit einem Wallet darauf
+ohne Backup der eNetzBon auf einem anderen Gerät oder Datenträger bedeutet einen
+Totalverlust des Gegenwerts des NetzBon-Guthabens.
+[NETZBON-NEU]
+
+[KOMMENTAR SK]
+- Streichen: * Der Code für die Nutzung von TALER ist geheim zu halten...
+- Streichen: * Der gewählte Code darf nicht...
+CG: 2x Ja.
+
+- Streichen: * Es ist dafür zu sorgen, dass... (ist wohl ein TWINT-Rest)
+
+CG: Besser: ummuenzen auf **Beguenstigte**. D.h. wenn wir einem Haendler Geld schulden
+aber z.B. KYC brauchen, liegt es erst einmal beim Haendler dies zu merken (im Backend!)
+und uns zu kontaktieren! Wenn wir nachforschen muessen, wer hinter einer IBAN steckt
+um dann KYC zu bekommen, soll dies ruhig kosten. Der Text zu Schuldsalden muss natuerlich
+raus, Schulden gibt es ja bei uns nicht!
+
+- Stattdessen den Text aus NETZBON-NEU verwenden und ergänzen mit:
+* Es ist dafür zu sorgen, dass sich das Endgerät mit einem darauf installierten Taler-Wallet
+innerhalb eines Jahres nach der letzten Transaktion mit dem Zahlungsdienst über das Internet
+verbindet, ansonsten kann das Guthaben im Wallet verloren werden. Ein Erneuern des Guthabens
+erfolgt regulär einen Monat vor dem Ende der Gültigkeit des elektronischen Bargelds, das ein
+Jahr beträgt.
+
+CG: "das zum Abhebezeitpunkt ca. ein Jahr betraegt" (ca. ist wichtig, wir runden...!).
+[KOMMENTAR SK]
+
+1.8. Nutzung; Missbräuche
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Weicht die Nutzung von TALER erheblich vom üblichen Gebrauch ab oder bestehen Anzeichen
+eines rechts- oder vertragswidrigen Verhaltens, kann die TALER AG die Kundinnen und Kunden
+zur rechts- und vertragskonformen Benutzung anhalten, die Leistungserbringung ohne
+Vorankündigung entschädigungslos ändern, einschränken oder einstellen, den Vertrag frist-
+und entschädigungslos auflösen und gegebenenfalls Schadenersatz sowie die Freistellung von
+Ansprüchen Dritter verlangen. Dasselbe gilt im Falle von unzutreffenden oder unvollständigen
+Angaben der Kunden bei der Registrierung.
+
+[NETZBON-NEU]
+Die Taler-App ermöglicht keine direkten Interaktionen, sondern dient ausschliesslich dem
+Bezug und der Verwendung von eNetzBon bei teilnehmenden Geschäften. Spenden sind möglich.
+Die Nutzer verpflichten sich, die Taler-App gemäss den geltenden Gesetzen und Vorschriften
+zu verwenden. Dem Nutzer ist es nur möglich, mit öffentlichen Shops zu interagieren. Mit
+anderen Privatpersonen kann ein Nutzer nicht interagieren.
+
+Der Verein Soziale Ökonomie behält sich vor, bei Verletzung von Regeln oder Missbrauch
+gewisse Konten zu löschen.
+[NETZBON-NEU]
+
+[KOMMENTAR SK]
+- Satz 1: die Kundinnen und Kunden --> die **Nutzer**
+- Satz 2: Dasselbe gilt im Falle von unzutreffenden oder unvollständigen Angaben der
+**Begünstigten (Händler, Verkäufer)** bei der Registrierung.
+
+CG: Satz 2: bei P2P brauchen wir ggf. auch angaben von Nutzern, wuerde ich bei **Nutzern** lassen.
+
+- In NETZBON-NEU streichen: Mit anderen Privatpersonen kann ein Nutzer nicht interagieren.
+
+CG: Genau, ist auch bei Netzbon *falsch*.
+
+- Ändern: Der Verein Soziale Ökonomie behält sich vor, bei Verletzung von Regeln oder
+Missbrauch Konten **(von Händlern, Betrieben, Verkäufern) zu sperren bzw.** zu löschen.
+[KOMMENTAR SK]
+
+1.9. Haftung
+~~~~~~~~~~~~
+
+Die TALER AG haftet nicht für den Kundinnen und Kunden entstandene Verluste oder Schäden
+aufgrund der Verwendung von TALER, insbesondere nicht für Verluste oder Schäden:
+* aufgrund von Übermittlungsfehlern, technischen Störungen oder Defekten, Ausfällen und
+unberechtigten Zugriffen oder Eingriffen auf das Smartphone;
+* die ganz oder teilweise auf einen Verstoss der Kundinnen und Kunden gegen diese AGB
+oder anwendbare Gesetze zurückzuführen sind;
+* aufgrund einer Störung oder Fehlers von TALER oder der verwendeten Hardware;
+* aufgrund von Störungen, Unterbrechungen (inkl. für Systemwartungsarbeiten) oder
+Ãœberlastungen der relevanten Informatiksysteme bzw. Netze;
+* aufgrund von Zahlungen, die nicht oder verzögert verarbeitet werden;
+* in Bezug auf Mehrwertleistungen;
+
+CG: streichen? Mehrwertleistungen machen wir doch gar nicht?
+
+* die auf Handlungen oder Unterlassungen von Dritten (inkl. Hilfspersonen der TALER AG)
+zurückzuführen sind,
+es sei denn, diese Verluste oder Schäden sind auf grobe Fahrlässigkeit oder vorsätzliches
+Verschulden der TALER AG zurückzuführen. Die TALER AG ersetzt Sach- und Vermögensschäden je
+Schadenereignis bis höchstens CHF 1’000.
+Die Haftung der TALER AG für Folgeschäden, entgangenem Gewinn, Datenverluste ist – soweit
+gesetzlich zulässig – in jedem Fall ausgeschlossen.
+Die Kundin bzw. der Kunde hält die TALER AG schadlos für Schäden oder Verluste, die der
+TALER AG aufgrund der Nichteinhaltung dieser AGB oder gesetzlichen Vorgaben, aufgrund
+fehlerhafter oder unvollständiger Angaben der Kundin bzw. des Kunden oder der Ausführung von
+Anweisungen entstehen.
+
+[KOMMENTAR SK]
+- Vorschlag zur Ergänzung des obigen Absatzes:
+Die Taler Operations AG haftet bei der Erfüllung ihrer Verpflichtungen für jedes Verschulden
+ihrer Mitarbeiter und der Personen, die sie zur Erfüllung ihrer Verpflichtungen hinzuzieht.
+Soweit die Sonderbedingungen für einzelne Geschäftsbeziehungen oder sonstige Vereinbarungen
+etwas Abweichendes regeln, gehen diese Regelungen vor.
+
+Hat ein Nutzer des Taler-Bezahlsystems durch schuldhaftes Verhalten - zum Beispiel durch
+Verletzung von Mitwirkungspflichten wie regelmässige Sicherungen und Vorsichtsmassnahmen -
+zur Entstehung eines Schadens beigetragen, bestimmt sich nach den Grundsätzen des
+Mitverschuldens, in welchem Umfang Taler Operations AG und Nutzer den Schaden zu tragen
+haben.
+
+CG: Nein, weglassen. Hilft uns nicht, nur mehr Text. AGB steht eh nicht ueber Recht und Gesetz.
+
+- Streichen: * in Bezug auf Mehrwertleistungen;
+
+CG: genau.
+[KOMMENTAR SK]
+
+1.10. Kommunikation
+~~~~~~~~~~~~~~~~~~~
+
+Die Kommunikation zwischen der TALER AG und den Kundinnen und Kunden erfolgt grundsätzlich
+über die TALER. Bei Bedarf kann die TALER AG die Kundinnen und Kunden auch ausserhalb der
+TALER App kontaktieren. Eine solche Kommunikation ist nicht zwingend vertraulich oder
+sicher.
+
+[KOMMENTAR SK]
+- Satz 1: Die Kommunikation zwischen der TALER AG und den Begünstigten erfolgt grundsätzlich
+über die TALER AG.
+
+- Satz 2: Bei Bedarf kann die TALER AG die registrierten Begünstigten (Händler, Betriebe,
+Verkäufer) kontaktieren.
+- Ergänzen: Falls eine Kommunikation zwischen der TALER AG und den Nutzern notwendig werden
+sollte, kann diese über ein Finanzinstitut (Bank des überweisenden Girokontos der Nutzer)
+oder/und über die Taler-Apps erfolgen, sofern dies technisch möglich ist.
+[KOMMENTAR SK]
+
+CG: alles falsch. Die Kommunikation von TOPS zu Nutzern erfolgt grundsätzlich
+über Benachrichtigungen im GNU Taler Protokoll. Nutzer sind dafuer verantwortlich auf
+entsprechende Benachrichtigungen zu reagieren. TOPS hat das Recht, Transaktionen
+nicht auszufuehren bis Nutzer auf diesem Weg angeforderte rechtlich notwendige Daten
+bereitstellen.
+
+
+
+1.11. Änderung AGB
+~~~~~~~~~~~~~~~~~~
+
+Die TALER AG kann die AGB jederzeit ändern. Änderungen werden auf geeignete Weise bekannt
+gegeben. Ist die Kundin bzw. der Kunde mit den Änderungen nicht einverstanden, so kann die
+Kundin bzw. der Kunde die TALER App nicht mehr verwenden.
+
+[KOMMENTAR SK]
+Vorschlag statt des obigen Absatzes:
+Die Taler Operations AG behält sich das Recht vor, diese Allgemeinen Geschäftsbedingungen
+(AGB) ändern zu können. Die Nutzer werden über Änderungen in der Taler-App benachrichtigt.
+Die fortgesetzte Nutzung der Taler-App nach Änderungen der AGB gilt als Zustimmung zu den
+geänderten Bedingungen.
+
+Der Zahlungsdienst sendet automatisch Änderungen in den AGB und Datenschutzbestimmungen
+an die Taler-Wallets mit der Notwendigkeit der Bestätigung durch die Nutzer, nach der sie
+die Taler-App weiterverwenden können.
+[KOMMENTAR SK]
+
+CG: "TOPS kann die AGB jederzeit ändern. Änderungen haben nur Wirkung auf nach der
+Änderung bezogene Wertmarken. Korrekte Wallets informieren Nutzer über Änderungen
+vor dem Bezug von neuen Wertmarken. Der Bezug von Wertmarken der TOPS nach
+Änderungen der AGB gilt als Zustimmung zu den geänderten Bedingungen."
+
+weil: du kannt den Leuten nicht verbieten, eine GPL-Wallet nicht zu benutzen wenn sie
+TOPS AGBs nicht zustimmen! Das waere GPL-widrig!
+
+
+1.12. Vorbehalt gesetzlicher Regelungen und Beschränkung der Dienstleistungen
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Allfällige Gesetzesbestimmungen, die den Betrieb und die Benutzung von Smartphones,
+Zahlungssystemen, des Internets und sonstiger dedizierter Infrastruktur regeln, bleiben
+vorbehalten und gelten ab ihrer Inkraftsetzung auch für die vorliegenden Dienstleistungen.
+Die Benutzung der Dienstleistungen aus dem Ausland kann lokalen rechtlichen Restriktionen
+unterliegen oder unter Umständen Regeln des ausländischen Rechts verletzen. Die
+Zahlungsfunktion ist grundsätzlich auf das Hoheitsgebiet der Schweiz beschränkt und darf im
+Ausland nicht in Anspruch genommen werden. Zulässig sind aber internationale Zahlungen über
+ein mit dem TALER Zahlungssystem kooperierendes ausländisches Zahlungssystem.
+
+CG: Letzten Satz hier streichen. Nix international.
+
+Die TALER AG behält sich vor, das Angebot von TALER jederzeit und ohne vorherige Ankündigung
+zu ändern, zu beschränken oder vollständig einzustellen, insbesondere aufgrund rechtlicher
+Anforderungen, technischen Problemen, zwecks Verhinderung von Missbräuchen, auf behördliche
+Anordnung oder aus Sicherheitsgründen.
+Die TALER AG kann nach eigenem Ermessen und ohne vorherige Ankündigung die Nutzung von TALER
+für einzelne Kundinnen und Kunden einschränken oder unterbinden, Zahlungen nicht oder nur
+verzögert verarbeiten, eingehende Zahlungen zurückweisen und das Auf- und Entladen
+beschränken, insbesondere wo dies nach Auffassung der TALER AG aus rechtlichen Gründen oder
+solchen, die die Reputation betreffen, angezeigt ist, bei IT-gestützten Angriffen, bei
+Missbrauch oder bei Betrugsverdacht. Im Verlaufe der Dauer der Geschäftsbeziehung können
+Umstände eintreten, die die TALER AG verpflichten, Vermögenswerte zu sperren, die
+Geschäftsbeziehung einer zuständigen Behörde zu melden oder abzubrechen.
+Die Kundinnen und Kunden sind verpflichtet, der TALER AG auf Verlangen Auskünfte zu
+erteilen, die die TALER AG benötigt, um den gesetzlichen oder internen Abklärungs- oder
+Meldepflichten nachzukommen.
+
+CG: scheint mir vieles vorher gesagtes zu duplizieren. Ggf. oben Text streichen?
+
+1.13. Geistiges Eigentum
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Für die Dauer des Vertrages erhalten die Kundinnen und Kunden das unübertragbare, nicht
+ausschliessliche Recht zur Nutzung von TALER. Inhalt und Umfang dieses Rechts ergeben sich
+aus den vorliegenden AGB. Alle Immaterialgüterrechte verbleiben bei der TALER AG oder den
+berechtigten Dritten.
+
+[KOMMENTAR SK]
+Satz 1: Für die Dauer der **Nutzung** erhalten die Nutzer das unübertragbare, nicht
+ausschliessliche Recht zur Verwendung des Bezahlsystems. Die Vertragspartner (Händler,
+Betriebe, Verkäufer) als Begünstigte erhalten im Fall der Nutzung der P2P- oder
+P2M-Funktion des Bezahlsystems dieses Recht wie alle anderen Nutzer eingeräumt.
+[KOMMENTAR SK]
+
+CG: Wuerde ich alles streichen. IP-Unsinn. Besser:
+
+1.13: Trademark
+~~~~~~~~~~~~~~~
+
+Beguenstigte haben das nicht ausschliessliche Recht das Taler-Logo zu
+nutzen um zu signalisieren, dass sie Zahlungen mit Taler akzeptieren.
+
+
+
+1.14 Datenschutz
+~~~~~~~~~~~~~~~~
+
+Die TALER AG verpflichtet sich hinsichtlich der Beschaffung, Bearbeitung und
+Nutzung der personenbezogenen Daten der Kundinnen und Kunden die Bestimmungen der
+schweizerischen Datenschutzgesetzgebung (insbesondere Bundesgesetz über den Datenschutz,
+DSG, und Verordnung über den Datenschutz, VDSG) einzuhalten.
+
+Alle Systemdaten werden ausschliesslich in der Schweiz gehostet.
+
+CG: ausschliesslich => primaer
+
+Der KYC Prozess wird durch
+einen Dienstleister übernommen, welcher verpflichtet wird, die Daten ebenfalls nach Recht
+und Gesetz von der Schweiz zu sichern.
+
+CG: KYC Prozesse werden ggf. durch Dienstleister gesteuert. Diese sind ebenfalls verpflichtet
+die Daten ebenfalls nach Recht und Gesetz der Schweiz zu sichern.
+
+Die eigentichen Daten des Kernsystems werden auf
+verschlüsselten Festplatten redundant (d.h. mit Backup) gespeichert und sind nur
+autorisiertem Personal zugänglich. Autorisiertes Personal wird von TALER AG einer
+Sicherheitsprüfung unterzogen. Das gesamte Design wurde strikt nach den Grundsätzen
+"Privacy-by-Design†und "Privacy-by-Default†umgesetzt.
+
+>>>
+Taler nutzt blinde Signaturen, damit
+TALER AG nicht lernen kann, welcher legitimierte Nutzer bei welchem Verkäufer einkauft.
+Weitere nicht-blinde digitale Signaturen werden eingesetzt, um alle Transaktionsschritte
+gegenseitig zu bestätigen und auch extern z.B. gegenüber Auditoren überprüfbar zu machen.
+Gleichzeitig werden Hashfunktionen eingesetzt, um Details, die dritte Parteien nicht lernen
+sollen, auch nicht zu exponieren. Von den Käufern werden nur so viele Daten verwendet, wie
+zum Abheben in eine virtuelle Geldbörse (Wallet) notwendig sind. Die dabei bezogenen
+Bankkonten haben bereits bankenseitig eine KYC-Prüfung der Käufer durchgeführt und kennen
+deren Namen und Adressen in Verbindung mit der Bankkontennummer (IBAN). Von den Verkäufern
+sind ebenfalls IBAN-Kontennummern bekannt. Diese können bei Bedarf zuständigen Behörden und
+Auditoren offengelegt werden
+<<<
+CG: obigen >>>Paragraph<<< bitte komplett streichen. Nicht falsch, aber viel zu viele Details.
+
+Weitere Informationen zu den Datenbearbeitungen finden sich in der Datenschutzerklärung auf
+der Webseite der TALER AG (www.TALER.ch).
+
+[NETZBON-NEU]
+Die Datenschutzrichtlinien sind in einem separaten Dokument festgelegt, das die Nutzer auch
+in der Taler-App finden. Der Schutz der persönlichen Daten und finanziellen Informationen
+hat für uns höchste Priorität. Daten der Nutzer werden nicht erhoben. Beim Bezahlen mit
+eNetzBon werden nur Ort, Uhrzeit und der die eNetzBon empfangende Betrieb (Händler,
+Verkäufer) erhoben. Die anonymisierten Daten des Kaufs und der Überweisung von NetzBon an
+den Betrieb werden im Falle einer Untersuchung der Finma erhoben. Dies betrifft jedoch nicht
+Nutzer, die mit eNetzBon zahlen, sondern die Transaktionen in NetzBon zwischen dem Verein
+Soziale Ökonomie und den teilnehmenden Betrieben.
+
+Den Datenschutzbeauftragten des Vereins Soziale Ökonomie erreichen Sie beim Sitz des Vereins
+in der Klybeckstrasse 95, 4057 Basel, und per E-Mail an kontakt@sozialeoekonomie.org.
+
+Den Datenschutzbeauftragten der Taler Operations AG erreichen Sie per Post an Taler
+Operations AG, Höheweg 80, 2502 Biel, und über die unten genannten Kontaktmöglichkeiten.
+[NETZBON-NEU]
+
+[KOMMENTAR SK]
+- Satz 1 von 1.14.: Kundinnen und Kunden --> Begünstigte
+
+CG: Besser: "Nutzer" (weil: beides, Kunden + Beguenstigte)
+
+- Beide Texte können ansonsten nach Korrektur orthografischer Fehler so bleiben und sollten
+auch in den AGB so angezeigt werden. Bitte diskutieren und eventuell kürzen.
+
+CG: Ja, definitiv viel kuerzen (siehe oben).
+[KOMMENTAR SK]
+
+1.15. Dauer und Kündigung
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Die Geschäftsbeziehung zwischen der Kundin bzw. dem Kunden und der TALER AG wird für
+unbestimmte Dauer abgeschlossen.
+Die Kundinnen und Kunden können ihr TALER Guthaben auf TALER jederzeit saldieren und
+schliessen, was als Kündigung gilt. Die TALER AG kann ihrerseits die Geschäftsbeziehung
+jederzeit mit sofortiger Wirkung kündigen. Eine schriftliche Kündigung der TALER AG erfolgt
+an die zuletzt bekanntgegebene (E-Mail-) Adresse der Kundin bzw. des Kunden.
+
+CG: Das ist Unsin, wir haben keine E-mail addressen von Kunden!
+
+Erfolgt während 4 Jahren keine Transaktion, gilt die Geschäftsbeziehung als durch die Kundin
+bzw. den Kunden gekündigt.
+
+[KOMMENTAR SK]
+- Satz 1: Die Geschäftsbeziehung zwischen den Begünstigten (Händler, Betriebe, Verkäufer
+und sonstige Empfänger von Überweisungen des Zahlungsdienst an die begünstigten
+IBAN-Konten) und dem Zahlungsdienstleister wird auf eine unbestimmte Dauer abgeschlossen.
+
+CG: Ja.
+
+- Satz 2: Die Nutzer von Taler-Wallets können das Guthaben jederzeit an die Bankkonten
+zurücküberweisen lassen, von denen die Überweisung der Nutzer an den Zahlungsdienst
+erfolgte, und so das Guthaben auf Null setzen.
+
+CG: Auch OK, wobei "saldieren" ggf. besser ist.
+
+- Satz 3: Die TALER AG kann die Geschäftsbeziehung mit den Begünstigten jederzeit -
+insbesondere in Missbrauchsfällen mit sofortiger Wirkung - kündigen.
+- Satz 4: Eine schriftliche Kündigung der TALER AG erfolgt an eine der zuletzt
+bekanntgegebenen Adressen der Geschäftspartner (z.B. per E-Mail oder Brief).
+- Satz 5: Streichen
+
+CG: Ja, wir brauchen ggf. noch etwas das TOPS bei Betriebsaufgabe die Nutzer ueber
+ das Taler-Protokoll informiert und die Wallets in diesem Fall die Kunden
+ auffordern werden, bestehende Restguthaben zu saldieren. Kunden die dies
+ unterlassen, verlieren dann nach 3 Monaten den Anspruch auf das Restguthaben.
+
+[KOMMENTAR SK]
+
+1.16. Ãœbertragung
+~~~~~~~~~~~~~~~~~
+
+Die TALER AG kann die Vertragsbeziehung mit der Kundin bzw. dem Kunden
+(inkl. einem allfälligen Guthaben) jederzeit und ohne vorgängige Information auf eine andere
+Gesellschaft der TALER Gruppe übertragen.
+
+[KOMMENTAR SK]
+Änderungsvorschlag:
+Die Taler Operations AG kann eine vertraglich geregelte Geschäftsbeziehung jederzeit an
+eine andere Firma ihrer Muttergesellschaft übertragen.
+[KOMMENTAR SK]
+
+CG: Nein, einfach "auf eine andere Gesellschaft uebertragen".
+Gar keine Einschraenkung auf Gruppe/Mutter, bitte!
+
+
+1.17. Anwendbares Recht und Gerichtsstand
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Soweit gesetzlich zulässig, unterstehen alle Rechtsbeziehungen zwischen den Kundinnen und
+Kunden und der TALER AG (inkl. internationalen Zahlungen) ausschliesslich dem materiellen
+schweizerischen Recht, unter Ausschluss von Kollisionsrecht und unter Ausschluss von
+Staatsverträgen.
+Unter dem Vorbehalt von entgegenstehenden, zwingenden gesetzlichen Bestimmungen ist Zürich
+ausschliesslicher Gerichtsstand und Erfüllungsort. Für Kundinnen und Kunden mit Wohnsitz
+ausserhalb der Schweiz ist Zürich sodann auch Betreibungsort.
+
+[NETZBON-NEU]
+Bei etwaigen Streitigkeiten oder Unstimmigkeiten, die aus der Nutzung von Taler, der
+Taler-App und eNetzBon entstehen, verpflichten sich die Parteien, zunächst eine gütliche
+Einigung anzustreben. Wenn keine Einigung erzielt werden kann, unterliegt die
+Streitbeilegung den geltenden schweizerischen Gesetzen und der Gerichtsbarkeit von Biel.
+
+CG: Netzbon is Biel!? Nicht Basel?
+[NETZBON-NEU]
+
+[KOMMENTAR SK]
+- Satz 1: Kundinnen und Kunden --> Nutzern
+
+CG: immer ;-)
+
+- Satz 2: Zürich --> Biel
+
+CG: TOPS: Ich denke Bern, nicht Biel (so was ist doch bestimmt Kantonal!?)
+
+- Satz aus NETZBON-NEU ebenfalls verwenden
+
+CG: OK.
+[KOMMENTAR SK]
+
+2. Zahlungsfunktionen
+---------------------
+
+2.1. Limiten
+~~~~~~~~~~~~
+
+Die Kundinnen und Kunden können bis CHF "___________________".
+Bei Zahlungen an andere TALER Nutzer (P2P-Zahlung) bestehen für Kundinnen und Kunden mit
+Wohnsitz in der Schweiz Limiten von CHF 1‘000 pro Monat und CHF 5‘000 pro Kalenderjahr für
+das Senden und das Empfangen von Geld
+
+CG: 1000/5000 fuer das Abheben von e-Geld von einem Girokonto bzw. fuer den Empfang von
+ P2P Zahlungen zwischen Taler-Wallets mit bestaetigter Schweizer Mobilfunknummer.
+
+Die TALER AG behält sich vor, diese Limite jederzeit zu senken oder zu erhöhen bzw.
+zusätzliche Limite einzuführen, insbesondere aus regulatorischen sowie Sicherheitsgründen.
+
+[KOMMENTAR SK]
+Änderungsvorschlag für Satz 3:
+Die TALER AG behält sich insbesondere aus regulatorischen Gründen vor, die Limite jederzeit
+zu senken oder zu erhöhen. Die Änderung wird in aktualisierten AGB angezeigt, welche die
+Nutzer vor der weiteren Nutzung des Zahlungsdiensts zu bestätigen haben.
+[KOMMENTAR SK]
+
+CG: Das wir die AGB aendern koennen haben wir bereits gesagt, wuerde ich nicht doppeln.
+
+2.2. Aufbuchen
+~~~~~~~~~~~~~~
+
+Das TALER Wallet wird von den Kundinnen und Kunden über die hierfür "_________"vorgesehenen
+Optionen aufgeladen. Es stehen folgende Möglichkeiten zur Verfügung:
+* Zum Aufbuchen der gewünschten Währung und der Geldmenge wählt man in der
+Wallet-Anwendung den von TALER AG betriebenen Exchange, an den man die Gelder vom Girokonto
+überweist und von dem schließlich das Wallet die elektronischen Repräsentanten der
+gewünschten Geldmenge abhebt (sog. Coins)
+Die TALER AG kann weitere Aufladeoptionen einführen oder bestehende Optionen nicht mehr
+anbieten.
+Allfällige mit der Ladung verbundenen Transaktions- oder sonstigen Gebühren sind durch die
+Kundinnen und Kunden zu tragen.
+Das TALER Guthaben wird nicht verzinst. Die Kundinnen und Kunden nehmen zur Kenntnis, dass
+das Guthaben nicht von der Einlagensicherung gedeckt ist.
+Der Verarbeitungsprozess für das Aufladen bzw. Entladen des TALER Guthabens kann je nach
+Ladeoption mehrere Tage Zeit in Anspruch nehmen.
+Die Kundin bzw. der Kunde erteilt für den Fall der Einrichtung der LSV-Anbindung der TALER
+AG die Ermächtigung, einzelne Daten zwecks Bonitätsprüfung an Dritte weitergeben zu können.
+
+CG: Den letzten Satz streichen.
+
+[NETZBON-KOMMENTAR]
+Die Nutzer können eNetzBon durch zwei Verfahren erwerben bzw. das Guthaben auf ihrem Wallet
+erhöhen:
+
+a. Per Bareinzahlung durch Nutzung der "Taler Cashier-App" in der
+Markthalle und in der Buchhandlung, wo ein Nutzer den abzuhebenden Betrag in CHF an das
+Personal bar übergeben kann und dann durch das Wallet der Betrag in CHF abgehoben und im
+Wallet in eNetzBon umgetauscht wird.
+
+b. Per Banküberweisung an das PostFinanz-Konto des Vereins Soziale Ökonomie. Das Wallet
+hilft dabei den Nutzern, den Abhebevorgang einzuleiten und gibt dazu einen Verwendungszweck
+an, d.h. eine mehrstellige Kombination aus Nummer und Buchstaben, die im Kontoauszug des
+persönlichhen Girokontos des jeweiligen Nutzers als Buchungstext angezeigt wird. Mit diesem
+Verwendungszweck kann das Wallet den Betrag zuerst in CHF abheben und dann im Wallet in
+eNetzBon umtauschen.
+
+Der Preis eines eNetzBon beträgt 1 CHF. Bitte beachten Sie, dass NetzBon nicht
+rückerstattbar sind, daher müssen sie ausgegeben werden.
+
+In der Phase der Markteinführung von eNetzBon werden keine Transaktionskosten von Nutzern
+erhoben. Bei der Bezahlung mit eNetzBon fallen daher vorerst keine Transaktionsgebühren an.
+Diese Allgemeinen Geschäftsbedingungen erlauben jedoch die Möglichkeit zukünftiger
+Änderungen der Gebührenordnung.
+[NETZBON-KOMMENTAR]
+
+[KOMMENTAR SK]
+- Satz 3 in 2.2.: Streichen: (sog. Coins)
+- Satz 5: durch die Kundinnen und Kunden --> durch die Nutzer
+- Satz 6: Das TALER Guthaben --> Guthaben der Nutzer in Wallets
+- Satz 7: Kundinnen und Kunden --> Nutzer
+- Satz 8: Aufladen bzw. Entladen des TALER Guthabens --> Erhöhen und Verringern des
+Guthabens im Wallet
+
+CG: alle OK.
+
+- Satz 9: Diesen Satz kann ich nicht interpretieren (was ist LSV-Anbindung?); Kundin bzw.
+der Kunde --> Nutzer
+
+CG: LSV Lastschriftverfahren. Streichen, machen wir *nie*.
+[KOMMENTAR SK]
+
+2.3. Abbuchen
+~~~~~~~~~~~~~
+
+Das Entladen muss "_____________________________________________".
+
+[KOMMENTAR SK]
+Was soll in den Platzhalter kommen?
+[KOMMENTAR SK]
+
+CG: ... auf ein Schweizer Bankkonto erfolgen. Internationale Zahlungen sind nicht erlaubt.
+
+
+2.4. Zahlen mit TALER
+~~~~~~~~~~~~~~~~~~~~~
+
+Die Kundinnen und Kunden können mit dem Smartphone und dem damit verbundenen TALER Wallet an
+entsprechend ausgerüsteten Ladenkassen im Inland, Automaten, im Internet, in anderen Apps,
+durch Hinterlegung als TALER Zahlungsart bei ausgewählten Händlern, bei Mehrwertleistungen
+und an andere TALER Nutzer im Rahmen der geltenden Limiten bezahlen.
+Bei einer Bezahlung wird der entsprechende Betrag direkt vom TALER Wallet abgebucht. Es muss
+mindestes im TALER Wallet in Höhe des Transaktionsbetrags verfügbar sein.
+
+CG: Mehrwertleistungen streichen.
+
+[KOMMENTAR SK]
+Änderungsvorschlag für Satz 1: Die Nutzer können mit dem im Smartphone oder Webbrowser
+installierten Taler-Wallet innerhalb der geltenden Limiten bezahlen bei natürlichen und
+juristischen Personen, die diese Bezahloption akzeptieren und ein Schweizer Bankkonto zum
+Geldempfang führen (z.B. Ladengeschäfte, Webshops, Apps und sonstige Begünstigte).
+[KOMMENTAR SK]
+
+CG: Scheint mir aequivalent, ist mir egal ;-).
+
+
+2.5. Belastung der Bezahlungen
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Die Kundinnen und Kunden anerkennen sämtliche getätigten P2M- und P2P-Zahlungen, welche mit
+dem TALER Wallet von ihrem Smartphone aus erfolgt sind, selbst wenn diese Zahlungen ohne
+ihre Zustimmung erfolgt sind.
+
+[KOMMENTAR SK]
+Es handelt sich bei dem Guthaben auf den Wallets der Nutzer um digitales Bargeld, dessen
+Eigentümerschaft technisch nicht ermittelbar ist. Jeder Teil des Guthabens erscheint in
+Form einer Datei mit alphanumerischen Zeichenfolgen und wird beim Abheben ins Wallet blind
+signiert, wodurch der Signatar keinen Rückschluss auf den Eigentümer des eingelösten
+Guthabens ziehen kann. Ist ein Guthaben eingelöst worden, kann dieses nicht noch ein
+weiteres Mal eingelöst werden. Wer das Guthaben zuerst einlöst, hat den Wert des Guthabens
+zur Zahlung verwendet.
+[KOMMENTAR SK]
+
+CG: Smartphone ist zu einschraenkend (kann ja auch ein Browser oder CLI-wallet sein!)
+CG: KOMMENTAR SK ist zu lang. Eher was von oben zum Thema Eigenverantwortung/Eigenverwahrung
+ runterziehen?
+
+
+2.6. Preise und Drittvergütungen
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Die Installation von TALER und die Nutzung der damit verbundenen Dienstleistungen sind
+grundsätzlich kostenlos.
+
+CG: Streichen. TOPS hat *nichts* mit der Installation zu tun. Die machen ggf. Dritte,
+ und da haben wir auch nichts zu zu sagen, was da die Kosten sind!
+
+Internationale Zahlungen in Fremdwährungen werden automatisch zu einem von einem Dritten
+gestellten Wechselkurs in Schweizer Franken umgerechnet. Die TALER AG kann diesen
+Wechselkurs erhöhen (sog. Mark-up) sowie eine zusätzliche Gebühr für die
+Fremdwährungstransaktion verlangen. Der Mark-up und die Gebühren fliessen alleine der TALER
+AG zu.
+
+CG: Streichen, wir machen keine Konvertierung.
+
+Den Kundinnen und Kunden wird in jedem Fall der finale Betrag in Schweizer Franken
+zur Bestätigung angezeigt. Kommt es zu einer Rückabwicklung einer internationalen Zahlung,
+so wird diese zum dannzumal gestellten Wechselkurs durchgeführt. Die Kundinnen und Kunden
+tragen das entsprechende Wechselkursrisiko.
+
+CG: Streichen, wir machen keine Konvertierung.
+
+TALER kann für die Nutzung von Aufladeoptionen Gebühren erheben. Die Kundinnen und Kunden
+werden in diesem Fall vor der Nutzung der
+kostenpflichtigen Aufladeoption in der TALER App über die zu bezahlenden Gebühren
+informiert.
+
+CG: Archivieren, nur wenn wir das wirklich machen in die AGB aufnehmen!
+
+Änderungen von Preisen und die Einführung neuer Preise werden grundsätzlich in der TALER App
+bekanntgegeben. Eine Anpassung gilt als genehmigt, wenn die Kundin bzw. der Kunde nicht vor
+Inkrafttreten der Änderung den Vertrag kündigt (Ziffer 1.15). Änderungen von Preisen für
+internationale Zahlungen müssen nicht separat bekanntgegeben werden.
+
+CG: Streichen, wir machen keine internationalen Zahlungen.
+
+Den Kundinnen und
+Kunden wird aber immer der Endbetrag in Schweizer Franken inkl. allen Gebühren angezeigt,
+
+
+bevor eine internationale Zahlung bestätigt wird.
+
+CG: Streichen, wir machen keine internationalen Zahlungen.
+
+
+Bei P2M-Zahlungen und der Inanspruchnahme von Mehrwertleistungen erhält die TALER AG unter
+Umständen gewisse Vergütungen von Dritten. Diese Drittvergütungen sind hier detailliert
+beschrieben.
+
+CG: Streichen, wir machen keine Mehrwertleistungen.
+
+Sie erlauben der TALER AG, die Benutzung der TALER App grundsätzlich kostenlos
+anzubieten. **Die Kundin bzw. der Kunde verzichtet auf die Erstattung sämtlicher
+Drittvergütungen, die die TALER AG in der Vergangenheit erhalten hat und in Zukunft erhalten
+könnte.**
+
+CG: Archivieren: wir bekommen keine Drittverguetungen, und *TOPS* bietet die Taler App gar nicht an!
+
+
+[KOMMENTAR SK]
+- Satz 1: --> Die Installation von Taler-Wallets ...
+- Satz 2: Internationale **eingehende** Zahlungen in Fremdwährungen werden automatisch zu
+einem von **der Schweizerischen Nationalbank** festgestellten Wechselkurs in Schweizer
+Franken umgerechnet.
+- Satz 6: Bei Rückbuchungen aus dem Guthaben in Taler-Wallets auf Nutzeranweisung oder bei
+erfolglosem Abheben ins Wallet erfolgen diese in derselben internationalen Fremdwährung wie
+die Eingangszahlung und verringert um anfallende Gebühren für Rücküberweisungen und
+Fremdwährungstransaktionen.
+- Satz 7 streichen
+- Satz 8: TALER --> Taler Operations AG
+- Satz 11: Eine Anpassung gilt als genehmigt, wenn die **Nutzer** die aktualisierten AGB in
+der App akzeptieren. Eine Anpassung gilt als genehmigt, wenn die Begünstigten (Händler,
+Betriebe, Verkäufer) nicht vor Inkrafttreten der Änderung den Vertrag kündigen.
+- Sätze 14 bis Ende: Bitte streichen, denn "Mehrwertleistungen" umfassen bei TWINT z.B.
+“Mobile-Marketing-Kampagnenâ€, Rabatt- und Kundenbindungsprogramme und die "Später
+zahlen"-Funktion. Diese Funktionen sind jedoch für das Taler-Bezahlsystem abträglich und
+dürften sowieso eher von der Seite der Händler angeboten werden.
+
+CG: Genau.
+
+Dankeschön, dass alle Kommentare bis hierhin durchgelesen wurden! Der AGB-Text muss ggf.
+stilistisch noch umformuliert werden, denn er ist zu weiten Teilen identisch mit TWINT-AGB.
+[KOMMENTAR SK]
+
+CG: Stilistische Gleichheit ist IMO kein Problem. Nur muss eben richtig sein ;-).
diff --git a/contrib/exchange-tos-v0.rst b/contrib/exchange-tos-v0.rst
new file mode 100644
index 000000000..1fdb66664
--- /dev/null
+++ b/contrib/exchange-tos-v0.rst
@@ -0,0 +1,278 @@
+Terms of Service
+================
+
+Last update: 26.4.2024
+----------------------
+
+Welcome! Taler Systems SA (“we,†“our,†or “usâ€) provides a payment service
+through our Internet presence (collectively the “Servicesâ€). Before using our
+Services, please read the Terms of Service (the “Terms†or the “Agreementâ€)
+carefully.
+
+Overview
+--------
+
+This section provides a brief summary of the highlights of this
+Agreement. Please note that when you accept this Agreement, you are accepting
+all of the terms and conditions and not just this section. We and possibly
+other third parties provide Internet services which interact with the Taler
+Wallet’s self-hosted personal payment application. When using the Taler Wallet
+to interact with our Services, you are agreeing to our Terms, so please read
+carefully.
+
+Highlights:
+-----------
+
+* You are responsible for keeping the data in your Taler Wallet at all times under your control. Any losses arising from you not being in control of your private information are your problem.
+
+* We will try to transfer funds we hold in escrow for our users to any legal recipient to the best of our ability within the limitations of the law and our implementation. However, the Services offered today are highly experimental and the set of recipients of funds is severely restricted.
+
+* For our Services, we may charge transaction fees. The specific fee structure is provided based on the Taler protocol and should be shown to you when you withdraw electronic coins using a Taler Wallet. You agree and understand that the Taler protocol allows for the fee structure to change.
+
+* You agree to not intentionally overwhelm our systems with requests and follow responsible disclosure if you find security issues in our services.
+
+* We cannot be held accountable for our Services not being available due to circumstances beyond our control. If we modify or terminate our services, we will try to give you the opportunity to recover your funds. However, given the experimental state of the Services today, this may not be possible. You are strongly advised to limit your use of the Service to small-scale experiments expecting total loss of all funds.
+
+These terms outline approved uses of our Services. The Services and these
+Terms are still at an experimental stage. If you have any questions or
+comments related to this Agreement, please send us a message to
+legal@taler-systems.com. If you do not agree to this Agreement, you must not
+use our Services.
+
+How you accept this policy
+--------------------------
+
+By sending funds to us (to top-up your Taler Wallet), you acknowledge that you
+have read, understood, and agreed to these Terms. We reserve the right to
+change these Terms at any time. If you disagree with the change, we may in the
+future offer you with an easy option to recover your unspent funds. However,
+in the current experimental period you acknowledge that this feature is not
+yet available, resulting in your funds being lost unless you accept the new
+Terms. If you continue to use our Services other than to recover your unspent
+funds, your continued use of our Services following any such change will
+signify your acceptance to be bound by the then current Terms. Please check
+the effective date above to determine if there have been any changes since you
+have last reviewed these Terms.
+
+Services
+--------
+
+We will try to transfer funds that we hold in escrow for our users to any
+legal recipient to the best of our ability and within the limitations of the
+law and our implementation. However, the Services offered today are highly
+experimental and the set of recipients of funds is severely restricted. The
+Taler Wallet can be loaded by exchanging fiat currencies against electronic
+coins. We are providing this exchange service. Once your Taler Wallet is
+loaded with electronic coins they can be spent for purchases if the seller is
+accepting Taler as a means of payment. We are not guaranteeing that any seller
+is accepting Taler at all or a particular seller. The seller or recipient of
+deposits of electronic coins must specify the target account, as per the
+design of the Taler protocol. They are responsible for following the protocol
+and specifying the correct bank account, and are solely liable for any losses
+that may arise from specifying the wrong account. We will allow the government
+to link wire transfers to the underlying contract hash. It is the
+responsibility of recipients to preserve the full contracts and to pay
+whatever taxes and charges may be applicable. Technical issues may lead to
+situations where we are unable to make transfers at all or lead to incorrect
+transfers that cannot be reversed. We will only refuse to execute transfers if
+the transfers are prohibited by a competent legal authority and we are ordered
+to do so.
+
+When using our Services, you agree to not take any action that intentionally
+imposes an unreasonable load on our infrastructure. If you find security
+problems in our Services, you agree to first report them to
+security@taler-systems.com and grant us the right to publish your report. We
+warrant that we will ourselves publicly disclose any issues reported within 3
+months, and that we will not prosecute anyone reporting security issues if
+they did not exploit the issue beyond a proof-of-concept, and followed the
+above responsible disclosure practice.
+
+Fees
+----
+
+You agree to pay the fees for exchanges and withdrawals completed via the
+Taler Wallet ("Fees") as defined by us, which we may change from time to
+time. With the exception of wire transfer fees, Taler transaction fees are set
+for any electronic coin at the time of withdrawal and fixed throughout the
+validity period of the respective electronic coin. Your wallet should obtain
+and display applicable fees when withdrawing funds. Fees for coins obtained as
+change may differ from the fees applicable to the original coin. Wire transfer
+fees that are independent from electronic coins may change annually. You
+authorize us to charge or deduct applicable fees owed in connection with
+deposits, exchanges and withdrawals following the rules of the Taler protocol.
+We reserve the right to provide different types of rewards to users either in
+the form of discount for our Services or in any other form at our discretion
+and without prior notice to you.
+
+Eligibility and Financial self-responsibility
+---------------------------------------------
+
+To be eligible to use our Services, you must be able to form legally binding
+contracts or have the permission of your legal guardian. By using our
+Services, you represent and warrant that you meet all eligibility requirements
+that we outline in these Terms.
+
+You will be responsible for maintaining the availability, integrity and
+confidentiality of the data stored in your wallet. When you setup a Taler
+Wallet, you are strongly advised to follow the precautionary measures offered
+by the software to minimize the chances to losse access to or control over
+your Wallet data. We will not be liable for any loss or damage arising from
+your failure to comply with this paragraph.
+
+Copyrights and trademarks
+-------------------------
+
+The Taler Wallet is released under the terms of the GNU General Public License
+(GNU GPL). You have the right to access, use, and share the Taler Wallet, in
+modified or unmodified form. However, the GPL is a strong copyleft license,
+which means that any derivative works must be distributed under the same
+license terms as the original software. If you have any questions, you should
+review the GNU GPL’s full terms and conditions on the GNU GPL Licenses page
+(https://www.gnu.org/licenses/). “Taler†itself is a trademark
+of Taler Systems SA. You are welcome to use the name in relation to processing
+payments based on the Taler protocol, assuming your use is compatible with an
+official release from the GNU Project that is not older than two years.
+
+Limitation of liability & disclaimer of warranties
+--------------------------------------------------
+
+You understand and agree that we have no control over, and no duty to take any
+action regarding: Failures, disruptions, errors, or delays in processing that
+you may experience while using our Services; The risk of failure of hardware,
+software, and Internet connections; The risk of malicious software being
+introduced or found in the software underlying the Taler Wallet; The risk that
+third parties may obtain unauthorized access to information stored within your
+Taler Wallet, including, but not limited to your Taler Wallet coins or backup
+encryption keys. You release us from all liability related to any losses,
+damages, or claims arising from:
+
+1) user error such as forgotten passwords, incorrectly constructed transactions;
+2) server failure or data loss;
+3) unauthorized access to the Taler Wallet application;
+4) bugs or other errors in the Taler Wallet software; and
+5) any unauthorized third party activities, including, but not limited to, the use of
+viruses, phishing, brute forcing, or other means of attack against the Taler Wallet. We make no
+representations concerning any Third Party Content contained in or accessed through our Services.
+
+Any other terms, conditions, warranties, or representations associated with
+such content, are solely between you and such organizations and/or individuals.
+
+To the fullest extent permitted by applicable law, in no event will we or any
+of our officers, directors, representatives, agents, servants, counsel,
+employees, consultants, lawyers, and other personnel authorized to act,
+acting, or purporting to act on our behalf (collectively the “Taler Partiesâ€)
+be liable to you under contract, tort, strict liability, negligence, or any
+other legal or equitable theory, for:
+
+1) any direct damages or
+2) any lost profits, data loss, cost of procurement of substitute goods or services, or
+direct, indirect, incidental, special, punitive, compensatory, or consequential damages of any kind
+whatsoever resulting from:
+
+* your use of, or conduct in connection with, our services;
+* any unauthorized use of your wallet and/or private key due to your failure to maintain the confidentiality of your wallet;
+* any interruption or cessation of transmission to or from the services; or
+* any bugs, viruses, trojan horses, or the like that are found in the Taler Wallet software
+or that may be transmitted to or through our services by any third party (regardless of the source of origination).
+
+These limitations apply regardless of legal theory, whether based on tort,
+strict liability, breach of contract, breach of warranty, or any other legal
+theory, and whether or not we were advised of the possibility of such
+damages. Some jurisdictions do not allow the exclusion or limitation of
+liability for consequential or incidental damages, so the above limitation may
+not apply to you.
+
+Our services are provided "as is" and without warranty of any kind. To the
+maximum extent permitted by law, we disclaim all representations and
+warranties, express or implied, relating to the services and underlying
+software or any content on the services, whether provided or owned by us or by
+any third party, including without limitation, warranties of merchantability,
+fitness for a particular purpose, title, non-infringement, freedom from
+computer virus, and any implied warranties arising from course of dealing,
+course of performance, or usage in trade, all of which are expressly
+disclaimed. In addition, we do not represent or warrant that the content
+accessible via the services is accurate, complete, available, current, free of
+viruses or other harmful components, or that the results of using the services
+will meet your requirements. Some states do not allow the disclaimer of
+implied warranties, so the foregoing disclaimers may not apply to you. This
+paragraph gives you specific legal rights and you may also have other legal
+rights that vary from state to state.
+
+Indemnity and Time limitation on claims and Termination
+-------------------------------------------------------
+
+To the extent permitted by applicable law, you agree to defend, indemnify, and
+hold harmless the Taler Parties from and against any and all claims, damages,
+obligations, losses, liabilities, costs or debt, and expenses (including, but
+not limited to, attorney’s fees) arising from:
+(a) your use of and access to the Services;
+(b) any feedback or submissions you provide to us concerning the Taler Wallet;
+(c) your violation of any term of this Agreement; or
+(d) your violation of any law, rule, or regulation, or the rights of any third party.
+
+You agree that any claim you may have arising out of or related to your
+relationship with us must be filed within one year after such claim arises,
+otherwise, your claim in permanently barred.
+
+In the event of termination concerning your use of our Services, your
+obligations under this Agreement will still continue.
+
+
+Discontinuance of services and Force majeure
+--------------------------------------------
+
+We may, in our sole discretion and without cost to you, with or without prior
+notice, and at any time, modify or discontinue, temporarily or permanently,
+any portion of our Services. We will use the Taler protocol’s provisions to
+notify Wallets if our Services are to be discontinued. It is your
+responsibility to ensure that the Taler Wallet is online at least once every
+three months to observe these notifications. We shall not be held responsible
+or liable for any loss of funds in the event that we discontinue or depreciate
+the Services and your Taler Wallet fails to transfer out the coins within a
+three months notification period.
+
+We shall not be held liable for any delays, failure in performance, or
+interruptions of service which result directly or indirectly from any cause or
+condition beyond our reasonable control, including but not limited to: any
+delay or failure due to any act of God, act of civil or military authorities,
+act of terrorism, civil disturbance, war, strike or other labor dispute, fire,
+interruption in telecommunications or Internet services or network provider
+services, failure of equipment and/or software, other catastrophe, or any
+other occurrence which is beyond our reasonable control and shall not affect
+the validity and enforceability of any remaining provisions.
+
+Governing law, Waivers, Severability and Assignment
+---------------------------------------------------
+
+No matter where you’re located, the laws of Switzerland will govern these
+Terms. If any provisions of these Terms are inconsistent with any applicable
+law, those provisions will be superseded or modified only to the extent such
+provisions are inconsistent. The parties agree to submit to the ordinary
+courts in Zurich, Switzerland for exclusive jurisdiction of any dispute
+arising out of or related to your use of the Services or your breach of these
+Terms.
+
+Our failure to exercise or delay in exercising any right, power, or privilege
+under this Agreement shall not operate as a waiver; nor shall any single or
+partial exercise of any right, power, or privilege preclude any other or
+further exercise thereof.
+
+You agree that we may assign any of our rights and/or transfer, sub-contract,
+or delegate any of our obligations under these Terms.
+
+If it turns out that any part of this Agreement is invalid, void, or for any
+reason unenforceable, that term will be deemed severable and limited or
+eliminated to the minimum extent necessary.
+
+This Agreement sets forth the entire understanding and agreement as to the
+subject matter hereof and supersedes any and all prior discussions,
+agreements, and understandings of any kind (including, without limitation, any
+prior versions of this Agreement) and every nature between us. Except as
+provided for above, any modification to this Agreement must be in writing and
+must be signed by both parties.
+
+Questions or comments
+---------------------
+
+We welcome comments, questions, concerns, or suggestions. Please send us a
+message on our contact page at legal@taler-systems.com.
diff --git a/contrib/gana b/contrib/gana
new file mode 160000
+Subproject 61556908520df557832b04bb5e1ee91c708aeef
diff --git a/contrib/gana-generate.sh b/contrib/gana-generate.sh
new file mode 100755
index 000000000..4679e2003
--- /dev/null
+++ b/contrib/gana-generate.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+# This file is in the public domain.
+#
+# Helper script to recompute error codes based on submodule
+# Run from exchange/ main directory.
+set -eu
+
+domake ()
+{
+ # $1 -- dir under contrib/
+ dir="contrib/$1"
+
+ make -C $dir
+}
+
+ensure ()
+{
+ # $1 -- filename
+ # $2 -- src dir under contrib/
+ # $3 -- dst dir under ./
+ fn="$1"
+ src="contrib/$2/$fn"
+ dst="./$3/$fn"
+
+ if ! diff $src $dst > /dev/null
+ then
+ test ! -f $dst || chmod +w $dst
+ cp $src $dst
+ chmod -w $dst
+ fi
+}
+
+domake gana/gnu-taler-error-codes
+ensure taler_error_codes.c gana/gnu-taler-error-codes src/util
+ensure taler_error_codes.h gana/gnu-taler-error-codes src/include
+
+domake gana/gnu-taler-db-events
+ensure taler_dbevents.h gana/gnu-taler-db-events src/include
+
+domake sigp
+ensure taler_signatures.h sigp src/include
diff --git a/contrib/gana-latest.sh b/contrib/gana-latest.sh
new file mode 100755
index 000000000..e92761acc
--- /dev/null
+++ b/contrib/gana-latest.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+# Helper script to update to latest GANA
+# Run from exchange/ main directory.
+set -eu
+
+cd contrib/gana
+git pull origin master
+cd ../..
+
+exec ./contrib/gana-generate.sh
diff --git a/contrib/gnunet.tag b/contrib/gnunet.tag
index 0add90c24..4cfdbff9c 100644
--- a/contrib/gnunet.tag
+++ b/contrib/gnunet.tag
@@ -18,6 +18,18 @@
</member>
<member kind="define">
<type>#define</type>
+ <name>GNUNET_CRYPTO_BSA_RSA</name>
+ <anchorfile>gnunet_crypto_lib.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>GNUNET_CRYPTO_BSA_CS</name>
+ <anchorfile>gnunet_crypto_lib.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
<name>GNUNET_NO</name>
<anchorfile>gnunet_util_lib.h</anchorfile>
<arglist></arglist>
@@ -31,7 +43,13 @@
<member kind="define">
<type>#define</type>
<name>GNUNET_TIME_UNIT_FOREVER_ABS</name>
- <anchorfile>gnunet_util_lib.h</anchorfile>
+ <anchorfile>gnunet_time_lib.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>GNUNET_TIME_UNIT_ZERO_ABS</name>
+ <anchorfile>gnunet_time_lib.h</anchorfile>
<arglist></arglist>
</member>
</compound>
@@ -83,6 +101,19 @@
<arglist>(n, type)</arglist>
</member>
</compound>
+
+ <compound kind="file">
+ <name>gnunet_strings_lib.h</name>
+ <path></path>
+ <filename>gnunet_strings_lib.h</filename>
+ <member kind="function">
+ <type>#define</type>
+ <name>GNUNET_STRINGS_filename_expand</name>
+ <anchorfile>gnunet_strings_lib.h</anchorfile>
+ <arglist>(const char *name)</arglist>
+ </member>
+ </compound>
+
<compound kind="file">
<name>gnunet_db_lib.h</name>
<path></path>
@@ -99,6 +130,18 @@
<anchorfile>gnunet_db_lib.h</anchorfile>
<arglist></arglist>
</member>
+ <member kind="define">
+ <type>#define</type>
+ <name>GNUNET_DB_STATUS_HARD_ERROR</name>
+ <anchorfile>gnunet_db_lib.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>GNUNET_DB_STATUS_SOFT_ERROR</name>
+ <anchorfile>gnunet_db_lib.h</anchorfile>
+ <arglist></arglist>
+ </member>
</compound>
<compound kind="file">
<name>gnunet_pq_lib.h</name>
@@ -118,6 +161,12 @@
</member>
<member kind="define">
<type>#define</type>
+ <anchorfile>gnunet_sq_lib.h</anchorfile>
+ <name>GNUNET_SQ_result_spec_absolute_time_nbo</name>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
<anchorfile>gnunet_pq_lib.h</anchorfile>
<name>GNUNET_PQ_result_spec_auto_from_type</name>
<arglist>(name, dst)</arglist>
@@ -158,17 +207,47 @@
<anchorfile>gnunet_pq_lib.h</anchorfile>
<arglist>(const struct GNUNET_TIME_AbsoluteNBO *x)</arglist>
</member>
+ <member kind="function">
+ <type>struct GNUNET_SQ_QueryParam</type>
+ <name>GNUNET_SQ_query_param_absolute_time</name>
+ <anchorfile>gnunet_sq_lib.h</anchorfile>
+ <arglist>(const struct GNUNET_TIME_Absolute *x)</arglist>
+ </member>
+ <member kind="function">
+ <type>struct GNUNET_SQ_QueryParam</type>
+ <name>GNUNET_SQ_query_param_absolute_time_nbo</name>
+ <anchorfile>gnunet_sq_lib.h</anchorfile>
+ <arglist>(const struct GNUNET_TIME_Absolute *x)</arglist>
+ </member>
+ <member kind="function">
+ <type>struct GNUNET_SQ_QueryParam</type>
+ <name>GNUNET_PQ_query_param_absolute_time_nbo</name>
+ <anchorfile>gnunet_sq_lib.h</anchorfile>
+ <arglist>(const struct GNUNET_TIME_AbsoluteNBO *x)</arglist>
+ </member>
<member kind="define">
<type>#define</type>
<name>GNUNET_PQ_query_param_end</name>
<anchorfile>gnunet_pq_lib.h</anchorfile>
<arglist></arglist>
</member>
+ <member kind="define">
+ <type>#define</type>
+ <name>GNUNET_SQ_query_param_end</name>
+ <anchorfile>gnunet_sq_lib.h</anchorfile>
+ <arglist></arglist>
+ </member>
<member kind="typedef">
<type>int</type>
<name>GNUNET_PQ_ResultConverter</name>
<anchorfile>gnunet_pq_lib.h</anchorfile>
<arglist>)(void *cls, PGresult *result, int row, const char *fname, size_t *dst_size, void *dst)</arglist>
</member>
+ <member kind="typedef">
+ <type>int</type>
+ <name>GNUNET_SQ_ResultConverter</name>
+ <anchorfile>gnunet_sq_lib.h</anchorfile>
+ <arglist>)(void *cls, sqlite3_stmt *result, unsigned int column, size_t *dst_size, void *dst)</arglist>
+ </member>
</compound>
</tagfile>
diff --git a/contrib/kyc-proof-already-done.en.must b/contrib/kyc-proof-already-done.en.must
new file mode 100644
index 000000000..c676aa931
--- /dev/null
+++ b/contrib/kyc-proof-already-done.en.must
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>200: KYC already completed</title>
+</head>
+<body>
+ The KYC process is already completed.
+ There is nothing left to do here.
+</body>
+</html>
diff --git a/contrib/kyc-proof-bad-request.en.must b/contrib/kyc-proof-bad-request.en.must
new file mode 100644
index 000000000..f55516873
--- /dev/null
+++ b/contrib/kyc-proof-bad-request.en.must
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>400: Malformed client request</title>
+</head>
+<body>
+The client's request was malformed.
+<pre>
+<!-- Taler error code --> {{ code }}:
+<!-- GANA EC hint --> {{ hint }}
+</pre>
+<p>
+<!-- optional human-readable message --> {{ message }}
+</p>
+</body>
+</html>
diff --git a/contrib/kyc-proof-endpoint-unknown.en.must b/contrib/kyc-proof-endpoint-unknown.en.must
new file mode 100644
index 000000000..275f42380
--- /dev/null
+++ b/contrib/kyc-proof-endpoint-unknown.en.must
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>404: Endpoint unknown</title>
+</head>
+<body>
+The given URL does not match any supported endpoint.
+<pre>
+<!-- Taler error code --> {{ code }}:
+<!-- GANA EC hint --> {{ hint }}
+</pre>
+<p>
+<!-- optional human-readable message --> {{ message }}
+</p>
+</body>
+</html>
diff --git a/contrib/kyc-proof-internal-error.en.must b/contrib/kyc-proof-internal-error.en.must
new file mode 100644
index 000000000..ab7845e0d
--- /dev/null
+++ b/contrib/kyc-proof-internal-error.en.must
@@ -0,0 +1,16 @@
+<html>
+<head>
+<title>500: Internal server error</title>
+</head>
+<body>
+ The server experienced an internal error handling
+ the request.
+<pre>
+<!-- Taler error code --> {{ code }}:
+<!-- GANA EC hint --> {{ hint }}
+</pre>
+<p>
+<!-- optional human-readable message --> {{ message }}
+</p>
+</body>
+</html>
diff --git a/contrib/kyc-proof-target-unknown.en.must b/contrib/kyc-proof-target-unknown.en.must
new file mode 100644
index 000000000..1a698506f
--- /dev/null
+++ b/contrib/kyc-proof-target-unknown.en.must
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>404: KYC request target unknown</title>
+</head>
+<body>
+We could not identify the KYC operation requested.
+<pre>
+<!-- Taler error code --> {{ code }}:
+<!-- GANA EC hint --> {{ hint }}
+</pre>
+<p>
+<!-- optional human-readable message --> {{ message }}
+</p>
+</body>
+</html>
diff --git a/contrib/kycaid-invalid-request.en.must b/contrib/kycaid-invalid-request.en.must
new file mode 100644
index 000000000..c33e3883f
--- /dev/null
+++ b/contrib/kycaid-invalid-request.en.must
@@ -0,0 +1,12 @@
+<html>
+<head>
+<title>400: Invalid endpoint for KYCaid</title>
+</head>
+<body>
+This endpoint is not defined for the KYCaid plugin.
+<pre>
+<!-- Taler error code --> {{ code }}:
+<!-- GANA string for EC --> {{ hint }}
+</pre>
+</body>
+</html>
diff --git a/contrib/locale/de/LC_MESSAGES/exchange-tos-v0.po b/contrib/locale/de/LC_MESSAGES/exchange-tos-v0.po
new file mode 100644
index 000000000..1b0d296cf
--- /dev/null
+++ b/contrib/locale/de/LC_MESSAGES/exchange-tos-v0.po
@@ -0,0 +1,403 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2014-2023 Taler Systems SA (GPLv3+ or GFDL 1.3+)
+# This file is distributed under the same license as the tos-v0 package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: tos-v0 tos-v0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-07-20 15:38+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "Terms of Service"
+msgstr "Allgemeine Geschäftsbedingungen"
+
+msgid "Last Updated: 27.11.2023"
+msgstr ""
+
+msgid ""
+"Welcome! Taler Systems SA (“we,†“our,†or “usâ€) provides a payment service "
+"through our Internet presence (collectively the “Servicesâ€). Before using "
+"our Services, please read the Terms of Service (the “Terms†or the "
+"“Agreementâ€) carefully."
+msgstr ""
+
+msgid "Overview"
+msgstr ""
+
+msgid ""
+"This section provides a brief summary of the highlights of this Agreement. "
+"Please note that when you accept this Agreement, you are accepting all of "
+"the terms and conditions and not just this section. We and possibly other "
+"third parties provide Internet services which interact with the Taler "
+"Wallet’s self-hosted personal payment application. When using the Taler "
+"Wallet to interact with our Services, you are agreeing to our Terms, so "
+"please read carefully."
+msgstr ""
+
+msgid "Highlights:"
+msgstr ""
+
+msgid ""
+"You are responsible for keeping the data in your Taler Wallet at all times "
+"under your control. Any losses arising from you not being in control of your "
+"private information are your problem."
+msgstr ""
+
+msgid ""
+"We will try to transfer funds we hold in escrow for our users to any legal "
+"recipient to the best of our ability within the limitations of the law and "
+"our implementation. However, the Services offered today are highly "
+"experimental and the set of recipients of funds is severely restricted."
+msgstr ""
+
+msgid ""
+"For our Services, we may charge transaction fees. The specific fee structure "
+"is provided based on the Taler protocol and should be shown to you when you "
+"withdraw electronic coins using a Taler Wallet. You agree and understand "
+"that the Taler protocol allows for the fee structure to change."
+msgstr ""
+
+msgid ""
+"You agree to not intentionally overwhelm our systems with requests and "
+"follow responsible disclosure if you find security issues in our services."
+msgstr ""
+
+msgid ""
+"We cannot be held accountable for our Services not being available due to "
+"circumstances beyond our control. If we modify or terminate our services, we "
+"will try to give you the opportunity to recover your funds. However, given "
+"the experimental state of the Services today, this may not be possible. You "
+"are strongly advised to limit your use of the Service to small-scale "
+"experiments expecting total loss of all funds."
+msgstr ""
+
+msgid ""
+"These terms outline approved uses of our Services. The Services and these "
+"Terms are still at an experimental stage. If you have any questions or "
+"comments related to this Agreement, please send us a message to legal@taler-"
+"systems.com. If you do not agree to this Agreement, you must not use our "
+"Services."
+msgstr ""
+
+msgid "How you accept this policy"
+msgstr ""
+
+msgid ""
+"By sending funds to us (to top-up your Taler Wallet), you acknowledge that "
+"you have read, understood, and agreed to these Terms. We reserve the right "
+"to change these Terms at any time. If you disagree with the change, we may "
+"in the future offer you with an easy option to recover your unspent funds. "
+"However, in the current experimental period you acknowledge that this "
+"feature is not yet available, resulting in your funds being lost unless you "
+"accept the new Terms. If you continue to use our Services other than to "
+"recover your unspent funds, your continued use of our Services following any "
+"such change will signify your acceptance to be bound by the then current "
+"Terms. Please check the effective date above to determine if there have been "
+"any changes since you have last reviewed these Terms."
+msgstr ""
+
+msgid "Services"
+msgstr ""
+
+msgid ""
+"We will try to transfer funds that we hold in escrow for our users to any "
+"legal recipient to the best of our ability and within the limitations of the "
+"law and our implementation. However, the Services offered today are highly "
+"experimental and the set of recipients of funds is severely restricted. The "
+"Taler Wallet can be loaded by exchanging fiat currencies against electronic "
+"coins. We are providing this exchange service. Once your Taler Wallet is "
+"loaded with electronic coins they can be spent for purchases if the seller "
+"is accepting Taler as a means of payment. We are not guaranteeing that any "
+"seller is accepting Taler at all or a particular seller. The seller or "
+"recipient of deposits of electronic coins must specify the target account, "
+"as per the design of the Taler protocol. They are responsible for following "
+"the protocol and specifying the correct bank account, and are solely liable "
+"for any losses that may arise from specifying the wrong account. We will "
+"allow the government to link wire transfers to the underlying contract hash. "
+"It is the responsibility of recipients to preserve the full contracts and to "
+"pay whatever taxes and charges may be applicable. Technical issues may lead "
+"to situations where we are unable to make transfers at all or lead to "
+"incorrect transfers that cannot be reversed. We will only refuse to execute "
+"transfers if the transfers are prohibited by a competent legal authority and "
+"we are ordered to do so."
+msgstr ""
+
+msgid ""
+"When using our Services, you agree to not take any action that intentionally "
+"imposes an unreasonable load on our infrastructure. If you find security "
+"problems in our Services, you agree to first report them to security@taler-"
+"systems.com and grant us the right to publish your report. We warrant that "
+"we will ourselves publicly disclose any issues reported within 3 months, and "
+"that we will not prosecute anyone reporting security issues if they did not "
+"exploit the issue beyond a proof-of-concept, and followed the above "
+"responsible disclosure practice."
+msgstr ""
+
+msgid "Fees"
+msgstr ""
+
+msgid ""
+"You agree to pay the fees for exchanges and withdrawals completed via the "
+"Taler Wallet (\"Fees\") as defined by us, which we may change from time to "
+"time. With the exception of wire transfer fees, Taler transaction fees are "
+"set for any electronic coin at the time of withdrawal and fixed throughout "
+"the validity period of the respective electronic coin. Your wallet should "
+"obtain and display applicable fees when withdrawing funds. Fees for coins "
+"obtained as change may differ from the fees applicable to the original coin. "
+"Wire transfer fees that are independent from electronic coins may change "
+"annually. You authorize us to charge or deduct applicable fees owed in "
+"connection with deposits, exchanges and withdrawals following the rules of "
+"the Taler protocol. We reserve the right to provide different types of "
+"rewards to users either in the form of discount for our Services or in any "
+"other form at our discretion and without prior notice to you."
+msgstr ""
+
+msgid "Eligibility and Financial self-responsibility"
+msgstr ""
+
+msgid ""
+"To be eligible to use our Services, you must be able to form legally binding "
+"contracts or have the permission of your legal guardian. By using our "
+"Services, you represent and warrant that you meet all eligibility "
+"requirements that we outline in these Terms."
+msgstr ""
+
+msgid ""
+"You will be responsible for maintaining the availability, integrity and "
+"confidentiality of the data stored in your wallet. When you setup a Taler "
+"Wallet, you are strongly advised to follow the precautionary measures "
+"offered by the software to minimize the chances to losse access to or "
+"control over your Wallet data. We will not be liable for any loss or damage "
+"arising from your failure to comply with this paragraph."
+msgstr ""
+
+msgid "Copyrights and trademarks"
+msgstr ""
+
+msgid ""
+"The Taler Wallet is released under the terms of the GNU General Public "
+"License (GNU GPL). You have the right to access, use, and share the Taler "
+"Wallet, in modified or unmodified form. However, the GPL is a strong "
+"copyleft license, which means that any derivative works must be distributed "
+"under the same license terms as the original software. If you have any "
+"questions, you should review the GNU GPL’s full terms and conditions at "
+"https://www.gnu.org/licenses/. “Taler†itself is a trademark "
+"of Taler Systems SA. You are welcome to use the name in relation to "
+"processing payments using the Taler protocol, assuming your use is "
+"compatible with an official release from the GNU Project that is not older "
+"than two years."
+msgstr ""
+
+msgid "Limitation of liability & disclaimer of warranties"
+msgstr ""
+
+msgid ""
+"You understand and agree that we have no control over, and no duty to take "
+"any action regarding: Failures, disruptions, errors, or delays in processing "
+"that you may experience while using our Services; The risk of failure of "
+"hardware, software, and Internet connections; The risk of malicious software "
+"being introduced or found in the software underlying the Taler Wallet; The "
+"risk that third parties may obtain unauthorized access to information stored "
+"within your Taler Wallet, including, but not limited to your Taler Wallet "
+"coins or backup encryption keys. You release us from all liability related "
+"to any losses, damages, or claims arising from:"
+msgstr ""
+
+msgid ""
+"user error such as forgotten passwords, incorrectly constructed transactions;"
+msgstr ""
+
+msgid "server failure or data loss;"
+msgstr ""
+
+msgid "unauthorized access to the Taler Wallet application;"
+msgstr ""
+
+msgid "bugs or other errors in the Taler Wallet software; and"
+msgstr ""
+
+msgid ""
+"any unauthorized third party activities, including, but not limited to, the "
+"use of viruses, phishing, brute forcing, or other means of attack against "
+"the Taler Wallet. We make no representations concerning any Third Party "
+"Content contained in or accessed through our Services."
+msgstr ""
+
+msgid ""
+"Any other terms, conditions, warranties, or representations associated with "
+"such content, are solely between you and such organizations and/or "
+"individuals."
+msgstr ""
+
+msgid ""
+"To the fullest extent permitted by applicable law, in no event will we or "
+"any of our officers, directors, representatives, agents, servants, counsel, "
+"employees, consultants, lawyers, and other personnel authorized to act, "
+"acting, or purporting to act on our behalf (collectively the “Taler "
+"Partiesâ€) be liable to you under contract, tort, strict liability, "
+"negligence, or any other legal or equitable theory, for:"
+msgstr ""
+
+msgid ""
+"any lost profits, data loss, cost of procurement of substitute goods or "
+"services, or direct, indirect, incidental, special, punitive, compensatory, "
+"or consequential damages of any kind whatsoever resulting from:"
+msgstr ""
+
+msgid "your use of, or conduct in connection with, our services;"
+msgstr ""
+
+msgid ""
+"any unauthorized use of your wallet and/or private key due to your failure "
+"to maintain the confidentiality of your wallet;"
+msgstr ""
+
+msgid ""
+"any interruption or cessation of transmission to or from the services; or"
+msgstr ""
+
+msgid ""
+"any bugs, viruses, trojan horses, or the like that are found in the Taler "
+"Wallet software or that may be transmitted to or through our services by any "
+"third party (regardless of the source of origination), or"
+msgstr ""
+
+msgid "any direct damages."
+msgstr ""
+
+msgid ""
+"These limitations apply regardless of legal theory, whether based on tort, "
+"strict liability, breach of contract, breach of warranty, or any other legal "
+"theory, and whether or not we were advised of the possibility of such "
+"damages. Some jurisdictions do not allow the exclusion or limitation of "
+"liability for consequential or incidental damages, so the above limitation "
+"may not apply to you."
+msgstr ""
+
+msgid ""
+"Our services are provided \"as is\" and without warranty of any kind. To the "
+"maximum extent permitted by law, we disclaim all representations and "
+"warranties, express or implied, relating to the services and underlying "
+"software or any content on the services, whether provided or owned by us or "
+"by any third party, including without limitation, warranties of "
+"merchantability, fitness for a particular purpose, title, non-infringement, "
+"freedom from computer virus, and any implied warranties arising from course "
+"of dealing, course of performance, or usage in trade, all of which are "
+"expressly disclaimed. In addition, we do not represent or warrant that the "
+"content accessible via the services is accurate, complete, available, "
+"current, free of viruses or other harmful components, or that the results of "
+"using the services will meet your requirements. Some states do not allow the "
+"disclaimer of implied warranties, so the foregoing disclaimers may not apply "
+"to you. This paragraph gives you specific legal rights and you may also have "
+"other legal rights that vary from state to state."
+msgstr ""
+
+msgid "Indemnity and Time limitation on claims and Termination"
+msgstr ""
+
+msgid ""
+"To the extent permitted by applicable law, you agree to defend, indemnify, "
+"and hold harmless the Taler Parties from and against any and all claims, "
+"damages, obligations, losses, liabilities, costs or debt, and expenses "
+"(including, but not limited to, attorney’s fees) arising from: (a) your use "
+"of and access to the Services; (b) any feedback or submissions you provide "
+"to us concerning the Taler Wallet; (c) your violation of any term of this "
+"Agreement; or (d) your violation of any law, rule, or regulation, or the "
+"rights of any third party."
+msgstr ""
+
+msgid ""
+"You agree that any claim you may have arising out of or related to your "
+"relationship with us must be filed within one year after such claim arises, "
+"otherwise, your claim in permanently barred."
+msgstr ""
+
+msgid ""
+"In the event of termination concerning your use of our Services, your "
+"obligations under this Agreement will still continue."
+msgstr ""
+
+msgid "Discontinuance of services and Force majeure"
+msgstr ""
+
+msgid ""
+"We may, in our sole discretion and without cost to you, with or without "
+"prior notice, and at any time, modify or discontinue, temporarily or "
+"permanently, any portion of our Services. We will use the Taler protocol’s "
+"provisions to notify Wallets if our Services are to be discontinued. It is "
+"your responsibility to ensure that the Taler Wallet is online at least once "
+"every three months to observe these notifications. We shall not be held "
+"responsible or liable for any loss of funds in the event that we discontinue "
+"or depreciate the Services and your Taler Wallet fails to transfer out the "
+"coins within a three months notification period."
+msgstr ""
+
+msgid ""
+"We shall not be held liable for any delays, failure in performance, or "
+"interruptions of service which result directly or indirectly from any cause "
+"or condition beyond our reasonable control, including but not limited to: "
+"any delay or failure due to any act of God, act of civil or military "
+"authorities, act of terrorism, civil disturbance, war, strike or other labor "
+"dispute, fire, interruption in telecommunications or Internet services or "
+"network provider services, failure of equipment and/or software, other "
+"catastrophe, or any other occurrence which is beyond our reasonable control "
+"and shall not affect the validity and enforceability of any remaining "
+"provisions."
+msgstr ""
+
+msgid "Governing law, Waivers, Severability and Assignment"
+msgstr ""
+
+msgid ""
+"No matter where you’re located, the laws of Switzerland will govern these "
+"Terms. If any provisions of these Terms are inconsistent with any applicable "
+"law, those provisions will be superseded or modified only to the extent such "
+"provisions are inconsistent. The parties agree to submit to the ordinary "
+"courts in Zurich, Switzerland for exclusive jurisdiction of any dispute "
+"arising out of or related to your use of the Services or your breach of "
+"these Terms."
+msgstr ""
+
+msgid ""
+"Our failure to exercise or delay in exercising any right, power, or "
+"privilege under this Agreement shall not operate as a waiver; nor shall any "
+"single or partial exercise of any right, power, or privilege preclude any "
+"other or further exercise thereof."
+msgstr ""
+
+msgid ""
+"You agree that we may assign any of our rights and/or transfer, sub-"
+"contract, or delegate any of our obligations under these Terms."
+msgstr ""
+
+msgid ""
+"If it turns out that any part of this Agreement is invalid, void, or for any "
+"reason unenforceable, that term will be deemed severable and limited or "
+"eliminated to the minimum extent necessary."
+msgstr ""
+
+msgid ""
+"This Agreement sets forth the entire understanding and agreement as to the "
+"subject matter hereof and supersedes any and all prior discussions, "
+"agreements, and understandings of any kind (including, without limitation, "
+"any prior versions of this Agreement) and every nature between us. Except as "
+"provided for above, any modification to this Agreement must be in writing "
+"and must be signed by both parties."
+msgstr ""
+
+msgid "Questions or comments"
+msgstr ""
+
+msgid ""
+"We welcome comments, questions, concerns, or suggestions. Please send us a "
+"message on our contact page at legal@taler-systems.com."
+msgstr ""
diff --git a/contrib/microhttpd.tag b/contrib/microhttpd.tag
index 5ae125e0f..5156a1a3a 100644
--- a/contrib/microhttpd.tag
+++ b/contrib/microhttpd.tag
@@ -24,6 +24,12 @@
</member>
<member kind="define">
<type>#define</type>
+ <name>MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
<name>MHD_HTTP_BAD_REQUEST</name>
<anchorfile>microhttpd.h</anchorfile>
<arglist></arglist>
@@ -42,6 +48,12 @@
</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>
<anchorfile>microhttpd.h</anchorfile>
<arglist></arglist>
@@ -66,6 +78,12 @@
</member>
<member kind="define">
<type>#define</type>
+ <name>MHD_HTTP_NETWORK_AUTHENTICATION_REQUIRED</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
<name>MHD_HTTP_GONE</name>
<anchorfile>microhttpd.h</anchorfile>
<arglist></arglist>
@@ -112,6 +130,90 @@
<anchorfile>microhttpd.h</anchorfile>
<arglist></arglist>
</member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_BAD_GATEWAY</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_GATEWAY_TIMEOUT</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_METHOD_NOT_ALLOWED</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_UNAUTHORIZED</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_PAYMENT_REQUIRED</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_NOT_IMPLEMENTED</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_NOT_ACCEPTABLE</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_ALREADY_REPORTED</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_EXPECTATION_FAILED</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_TOO_MANY_REQUESTS</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_METHOD_GET</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_METHOD_POST</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_METHOD_PUT</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
+ <member kind="define">
+ <type>#define</type>
+ <name>MHD_HTTP_METHOD_DELETE</name>
+ <anchorfile>microhttpd.h</anchorfile>
+ <arglist></arglist>
+ </member>
<member kind="typedef">
<type>int</type>
<name>MHD_AccessHandlerCallback</name>
diff --git a/contrib/oauth2-authentication-failure.en.must b/contrib/oauth2-authentication-failure.en.must
new file mode 100644
index 000000000..537423269
--- /dev/null
+++ b/contrib/oauth2-authentication-failure.en.must
@@ -0,0 +1,16 @@
+<html>
+<head>
+<title>403: Authentication by KYC server failed</title>
+</head>
+<body>
+ You failed the authentication check.
+ The transaction remains blocked.
+ Please obtain proper credentials and try again to proceed.
+<pre>
+<!-- as provided by OAuth2.0 server --> {{ error }}:
+<!-- optional, as provided by OAuth2.0 server --> {{ error_description }}
+
+<!-- optional link (render as link if present!), as provided by OAuth2.0 server --> {{ error_uri }}
+</pre>
+</body>
+</html>
diff --git a/contrib/oauth2-authorization-failure-malformed.en.must b/contrib/oauth2-authorization-failure-malformed.en.must
new file mode 100644
index 000000000..6f7552eae
--- /dev/null
+++ b/contrib/oauth2-authorization-failure-malformed.en.must
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>502: OAuth 2.0 service returned malformed response</title>
+</head>
+<body>
+ The KYC backend returned an authorization failure,
+ but additionally provided a malformed error response.
+ <pre>
+<!-- Should we output potentially sensitive data? --> {{ debug }}
+<!-- Optional: server response --> {{ server_response }}
+</pre>
+</body>
+</html>
diff --git a/contrib/oauth2-authorization-failure.en.must b/contrib/oauth2-authorization-failure.en.must
new file mode 100644
index 000000000..c184d10d8
--- /dev/null
+++ b/contrib/oauth2-authorization-failure.en.must
@@ -0,0 +1,12 @@
+<html>
+<head>
+<title>403: KYC server refused access</title>
+</head>
+<body>
+The KYC backend refused the authorization code used by the exchange operator. Please inform the exchange operator about this failure.
+<pre>
+<!-- as provided by OAuth2.0 server --> {{ error }}:
+<!-- as provided by OAuth2.0 server --> {{ error_description }}
+</pre>
+</body>
+</html>
diff --git a/contrib/oauth2-bad-request.en.must b/contrib/oauth2-bad-request.en.must
new file mode 100644
index 000000000..f55516873
--- /dev/null
+++ b/contrib/oauth2-bad-request.en.must
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>400: Malformed client request</title>
+</head>
+<body>
+The client's request was malformed.
+<pre>
+<!-- Taler error code --> {{ code }}:
+<!-- GANA EC hint --> {{ hint }}
+</pre>
+<p>
+<!-- optional human-readable message --> {{ message }}
+</p>
+</body>
+</html>
diff --git a/contrib/oauth2-conversion-failure.en.must b/contrib/oauth2-conversion-failure.en.must
new file mode 100644
index 000000000..e117f5de6
--- /dev/null
+++ b/contrib/oauth2-conversion-failure.en.must
@@ -0,0 +1,28 @@
+<html>
+<head>
+<title>502: Failed to convert KYC data into internal format</title>
+</head>
+<body>
+ The KYC backend returned a response which we then failed
+ to convert into our internal format.
+<pre>
+<!-- Taler Error code --> {{ code }}:
+<!-- Taler EC hint --> {{ hint }}
+</pre>
+<p>
+ <!-- human-readable message --> {{ message}}
+</p>
+<p>
+ Converter command used was: &quot;{{ converter }}&quot;
+</p>
+<p>
+ Converter JSON output:
+<pre>
+ <!-- true if we are in debug mode --> {{ debug }}
+ <!-- optional: attributes returned by script
+ (note: may contain user's private information) --> {{ attributes }}
+</pre>
+</p>
+</pre>
+</body>
+</html>
diff --git a/contrib/oauth2-provider-failure.en.must b/contrib/oauth2-provider-failure.en.must
new file mode 100644
index 000000000..d4c3585bf
--- /dev/null
+++ b/contrib/oauth2-provider-failure.en.must
@@ -0,0 +1,22 @@
+<html>
+<head>
+<title>502: KYC provider had an internal error</title>
+</head>
+<body>
+The KYC OAuth2 backend had an internal error.
+<pre>
+<!-- taler error code --> {{ code }}:
+<!-- GANA hint for EC --> {{ hint }}
+</pre>
+<p>
+ <!-- human readable message with more specifics -->
+ {{ message }}
+</p>
+<pre>
+ <!-- true if we are in debug mode --> {{ debug }}
+ <!-- optional: full response from OAuth provider;
+ may contain private information! -->
+ {{ server_response }}
+</p>
+</body>
+</html>
diff --git a/contrib/packages/fedora/etc-libtalerexchange/taler/overrides.conf b/contrib/packages/fedora/etc-libtalerexchange/taler/overrides.conf
new file mode 100644
index 000000000..60296ead4
--- /dev/null
+++ b/contrib/packages/fedora/etc-libtalerexchange/taler/overrides.conf
@@ -0,0 +1 @@
+# This configuration will be changed by tooling. Do not touch it manually.
diff --git a/contrib/packages/fedora/etc-libtalerexchange/taler/taler.conf b/contrib/packages/fedora/etc-libtalerexchange/taler/taler.conf
new file mode 100644
index 000000000..1c86ccc36
--- /dev/null
+++ b/contrib/packages/fedora/etc-libtalerexchange/taler/taler.conf
@@ -0,0 +1,49 @@
+# Main entry point for the GNU Taler configuration.
+#
+# Structure:
+# - taler.conf is the main configuration entry point
+# used by all Taler components (the file you are currently
+# looking at.
+# - overrides.conf contains configuration overrides that are
+# set by some tools that help with the configuration,
+# and should not be edited by humans. Comments in this file
+# are not preserved.
+# - conf.d/ contains configuration files for
+# Taler components, which can be read by all
+# users of the system and are included by the main
+# configuration.
+# - secrets/ contains configuration snippets
+# with secrets for particular services.
+# These files should have restrictive permissions
+# so that only users of the relevant services
+# can read it. All files in it should end with
+# ".secret.conf".
+
+[taler]
+
+# Currency of the Taler deployment. This setting applies to all Taler
+# components that only support a single currency.
+#currency = KUDOS
+
+# Smallest currency unit handled by the underlying bank system. Taler payments
+# can make payments smaller than this units, but interactions with external
+# 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]
+
+TALER_HOME = /var/lib/taler
+TALER_RUNTIME_DIR = /run/taler
+TALER_CACHE_HOME = /var/cache/taler
+TALER_CONFIG_HOME = /etc/taler
+TALER_DATA_HOME = /var/lib/taler
+
+
+# Inline configurations from all Taler components.
+@inline-matching@ conf.d/*.conf
+
+# Overrides from tools that help with configuration.
+@inline@ overrides.conf
diff --git a/contrib/packages/fedora/etc-taler-auditor/apache2/sites-available/taler-auditor.conf b/contrib/packages/fedora/etc-taler-auditor/apache2/sites-available/taler-auditor.conf
new file mode 100644
index 000000000..f68c59558
--- /dev/null
+++ b/contrib/packages/fedora/etc-taler-auditor/apache2/sites-available/taler-auditor.conf
@@ -0,0 +1,4 @@
+<Location "/taler-auditor/">
+ProxyPass "unix:/var/lib/taler-auditor/auditor.sock|http://example.com/"
+RequestHeader add "X-Forwarded-Proto" "https"
+</Location>
diff --git a/contrib/packages/fedora/etc-taler-auditor/nginx/sites-available/taler-auditor b/contrib/packages/fedora/etc-taler-auditor/nginx/sites-available/taler-auditor
new file mode 100644
index 000000000..f74035d53
--- /dev/null
+++ b/contrib/packages/fedora/etc-taler-auditor/nginx/sites-available/taler-auditor
@@ -0,0 +1,18 @@
+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";
+ }
+} \ No newline at end of file
diff --git a/contrib/packages/fedora/etc-taler-auditor/taler/conf.d/auditor-system.conf b/contrib/packages/fedora/etc-taler-auditor/taler/conf.d/auditor-system.conf
new file mode 100644
index 000000000..3d3aef33a
--- /dev/null
+++ b/contrib/packages/fedora/etc-taler-auditor/taler/conf.d/auditor-system.conf
@@ -0,0 +1,12 @@
+# Read secret sections into configuration, but only
+# if we have permission to do so.
+@inline-secret@ auditordb-postgres ../secrets/auditor-db.secret.conf
+
+[auditor]
+# Debian package is configured to use a reverse proxy with a UNIX
+# domain socket. See nginx/apache configuration files.
+SERVE = UNIX
+UNIXPATH = /var/lib/taler-auditor/auditor.sock
+
+# Only supported database is Postgres right now.
+DATABASE = postgres
diff --git a/contrib/packages/fedora/etc-taler-auditor/taler/secrets/auditor-db.secret.conf b/contrib/packages/fedora/etc-taler-auditor/taler/secrets/auditor-db.secret.conf
new file mode 100644
index 000000000..b81bb817f
--- /dev/null
+++ b/contrib/packages/fedora/etc-taler-auditor/taler/secrets/auditor-db.secret.conf
@@ -0,0 +1,10 @@
+# Database configuration for the Taler auditor.
+
+[auditordb-postgres]
+
+# Typically, there should only be a single line here, of the form:
+
+CONFIG=postgres:///DATABASE
+
+# The details of the URI depend on where the database lives and how
+# access control was configured.
diff --git a/contrib/packages/fedora/etc-taler-exchange/apache2/sites-available/taler-exchange.conf b/contrib/packages/fedora/etc-taler-exchange/apache2/sites-available/taler-exchange.conf
new file mode 100644
index 000000000..3ec14feb2
--- /dev/null
+++ b/contrib/packages/fedora/etc-taler-exchange/apache2/sites-available/taler-exchange.conf
@@ -0,0 +1,4 @@
+<Location "/taler-exchange/">
+ProxyPass "unix:/run/taler/exchange-httpd/exchange-http.sock|http://example.com/"
+RequestHeader add "X-Forwarded-Proto" "https"
+</Location>
diff --git a/contrib/packages/fedora/etc-taler-exchange/nginx/sites-available/taler-exchange b/contrib/packages/fedora/etc-taler-exchange/nginx/sites-available/taler-exchange
new file mode 100644
index 000000000..9b61a32df
--- /dev/null
+++ b/contrib/packages/fedora/etc-taler-exchange/nginx/sites-available/taler-exchange
@@ -0,0 +1,17 @@
+server {
+ listen 80;
+ listen [::]:80;
+
+ 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 "localhost";
+ #proxy_set_header X-Forwarded-Proto "https";
+ }
+}
diff --git a/contrib/packages/fedora/etc-taler-exchange/taler/conf.d/exchange-business.conf b/contrib/packages/fedora/etc-taler-exchange/taler/conf.d/exchange-business.conf
new file mode 100644
index 000000000..d5938f2b1
--- /dev/null
+++ b/contrib/packages/fedora/etc-taler-exchange/taler/conf.d/exchange-business.conf
@@ -0,0 +1,50 @@
+# Configuration for business-level aspects of the exchange.
+
+[exchange]
+
+# Here you MUST add the master public key of the offline system
+# 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 =
+
+# Publicly visible base URL of the exchange.
+# BASE_URL = https://example.com/
+# BASE_URL =
+
+# Here you MUST configure the amount above which transactions are
+# always subject to manual AML review.
+# AML_THRESHOLD =
+
+# Attribute encryption key for storing attributes encrypted
+# in the database. Should be a high-entropy nonce.
+ATTRIBUTE_ENCRYPTION_KEY = SET_ME_PLEASE
+
+# For your terms of service and privacy policy, you should specify
+# an Etag that must be updated whenever there are significant
+# changes to either document. The format is up to you, what matters
+# is that the value is updated and never re-used. See the HTTP
+# specification on Etags.
+# TERMS_ETAG =
+# PRIVACY_ETAG =
+
+SERVE = unix
+UNIXPATH_MODE = 666
+
+# Bank accounts used by the exchange should be specified here:
+[exchange-account-1]
+
+ENABLE_CREDIT = NO
+ENABLE_DEBIT = NO
+
+# Account identifier in the form of an RFC-8905 payto:// URI.
+# For SEPA, looks like payto://sepa/$IBAN?receiver-name=$NAME
+# Make sure to URL-encode spaces in $NAME!
+PAYTO_URI =
+
+# Credentials to access the account are in a separate
+# config file with restricted permissions.
+@inline-secret@ exchange-accountcredentials-1 ../secrets/exchange-accountcredentials-1.secret.conf
+
+
+
diff --git a/contrib/packages/fedora/etc-taler-exchange/taler/conf.d/exchange-coins.conf b/contrib/packages/fedora/etc-taler-exchange/taler/conf.d/exchange-coins.conf
new file mode 100644
index 000000000..8294525cb
--- /dev/null
+++ b/contrib/packages/fedora/etc-taler-exchange/taler/conf.d/exchange-coins.conf
@@ -0,0 +1,33 @@
+#
+# This configuration file specifies the various denominations offered by your
+# exchange.
+#
+# Each denomination must be specified in a sections starting with
+# "coin_".
+#
+# What follows is an example.
+#
+
+# [coin_FOO]
+## Actual value of the coin
+#VALUE = KUDOS:1
+
+## How long will one key be used for withdrawals?
+#DURATION_WITHDRAW = 7 days
+
+## How long do users have to spend their coins?
+#DURATION_SPEND = 2 years
+
+## How long does the exchange keep the proofs around for legal disputes?
+#DURATION_LEGAL = 6 years
+
+## Fees charged. Note that for the lowest denomination, the
+## fee must precisely be the lowest denomination, or zero.
+#FEE_WITHDRAW = KUDOS:0
+#FEE_DEPOSIT = KUDOS:0
+#FEE_REFRESH = KUDOS:0
+#FEE_REFUND = KUDOS:0
+
+## How long should the RSA keys be. Do not change unless you really know
+## what you are doing (consult your local cryptographer first!).
+#RSA_KEYSIZE = 2048
diff --git a/contrib/packages/fedora/etc-taler-exchange/taler/conf.d/exchange-system.conf b/contrib/packages/fedora/etc-taler-exchange/taler/conf.d/exchange-system.conf
new file mode 100644
index 000000000..4ad7e06f6
--- /dev/null
+++ b/contrib/packages/fedora/etc-taler-exchange/taler/conf.d/exchange-system.conf
@@ -0,0 +1,13 @@
+# Configuration settings for system parameters of the exchange.
+
+# Read secret sections into configuration, but only
+# if we have permission to do so.
+@inline-secret@ exchangedb-postgres ../secrets/exchange-db.secret.conf
+
+[exchange]
+
+# Only supported database is Postgres right now.
+DATABASE = postgres
+
+
+
diff --git a/contrib/packages/fedora/etc-taler-exchange/taler/secrets/exchange-accountcredentials-1.secret.conf b/contrib/packages/fedora/etc-taler-exchange/taler/secrets/exchange-accountcredentials-1.secret.conf
new file mode 100644
index 000000000..8c8d14320
--- /dev/null
+++ b/contrib/packages/fedora/etc-taler-exchange/taler/secrets/exchange-accountcredentials-1.secret.conf
@@ -0,0 +1,17 @@
+# This file contains the secret credentials
+# to access the Taler Wire Gateway API (usually
+# provided by LibEuFin) for the exchange accounts.
+#
+# Each exchange-account-* section should have a matching
+# exchange-accountcredentials-* section here.
+#
+# Each of those sections must be imported via @inline-secret@,
+# usually in conf.d/exchange-business.conf.
+
+[exchange-accountcredentials-1]
+
+wire_gateway_auth_method = basic
+password =
+username =
+wire_gateway_url =
+
diff --git a/contrib/packages/fedora/etc-taler-exchange/taler/secrets/exchange-db.secret.conf b/contrib/packages/fedora/etc-taler-exchange/taler/secrets/exchange-db.secret.conf
new file mode 100644
index 000000000..a7a727b62
--- /dev/null
+++ b/contrib/packages/fedora/etc-taler-exchange/taler/secrets/exchange-db.secret.conf
@@ -0,0 +1,10 @@
+# Database configuration for the Taler exchange.
+
+[exchangedb-postgres]
+
+# Typically, there should only be a single line here, of the form:
+
+# CONFIG=postgres:///DATABASE
+
+# The details of the URI depend on where the database lives and how
+# access control was configured.
diff --git a/contrib/packages/fedora/taler-auditor.taler-auditor-httpd.service b/contrib/packages/fedora/taler-auditor.taler-auditor-httpd.service
new file mode 100644
index 000000000..9aefab641
--- /dev/null
+++ b/contrib/packages/fedora/taler-auditor.taler-auditor-httpd.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=GNU Taler payment system auditor REST API
+After=postgres.service network.target
+
+[Service]
+User=taler-auditor-httpd
+Type=simple
+Restart=on-failure
+ExecStart=/usr/bin/taler-auditor-httpd -c /etc/taler/taler.conf
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/packages/fedora/taler-exchange.taler-exchange-aggregator.service b/contrib/packages/fedora/taler-exchange.taler-exchange-aggregator.service
new file mode 100644
index 000000000..246cad5c1
--- /dev/null
+++ b/contrib/packages/fedora/taler-exchange.taler-exchange-aggregator.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange aggregator service
+PartOf=taler-exchange.target
+After=postgres.service
+
+[Service]
+User=taler-exchange-aggregator
+Type=simple
+Restart=always
+RestartSec=1s
+ExecStart=/usr/bin/taler-exchange-aggregator -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/contrib/packages/fedora/taler-exchange.taler-exchange-aggregator@.service b/contrib/packages/fedora/taler-exchange.taler-exchange-aggregator@.service
new file mode 100644
index 000000000..bfc44a9a9
--- /dev/null
+++ b/contrib/packages/fedora/taler-exchange.taler-exchange-aggregator@.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=GNU Taler payment system exchange aggregator service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-aggregator
+Type=simple
+Restart=always
+RestartSec=1s
+ExecStart=/usr/bin/taler-exchange-aggregator -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/contrib/packages/fedora/taler-exchange.taler-exchange-closer.service b/contrib/packages/fedora/taler-exchange.taler-exchange-closer.service
new file mode 100644
index 000000000..97a385c13
--- /dev/null
+++ b/contrib/packages/fedora/taler-exchange.taler-exchange-closer.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange closer service
+PartOf=taler-exchange.target
+After=network.target postgres.service
+
+[Service]
+User=taler-exchange-closer
+Type=simple
+Restart=always
+RestartSec=1s
+ExecStart=/usr/bin/taler-exchange-closer -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/contrib/packages/fedora/taler-exchange.taler-exchange-expire.service b/contrib/packages/fedora/taler-exchange.taler-exchange-expire.service
new file mode 100644
index 000000000..250f210fe
--- /dev/null
+++ b/contrib/packages/fedora/taler-exchange.taler-exchange-expire.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange expire service
+PartOf=taler-exchange.target
+After=postgres.service
+
+[Service]
+User=taler-exchange-expire
+Type=simple
+Restart=always
+RestartSec=1s
+ExecStart=/usr/bin/taler-exchange-expire -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/contrib/packages/fedora/taler-exchange.taler-exchange-httpd.service b/contrib/packages/fedora/taler-exchange.taler-exchange-httpd.service
new file mode 100644
index 000000000..3671bdc7d
--- /dev/null
+++ b/contrib/packages/fedora/taler-exchange.taler-exchange-httpd.service
@@ -0,0 +1,33 @@
+[Unit]
+Description=GNU Taler payment system exchange REST API
+AssertPathExists=/run/taler/exchange-httpd
+Requires=taler-exchange-httpd.socket taler-exchange-secmod-cs.service taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
+After=postgres.service network.target taler-exchange-secmod-cs.service taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-httpd
+Type=simple
+
+# Depending on the configuration, the service process kills itself and then
+# needs to be restarted. Thus no significant delay on restarts.
+Restart=always
+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
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/packages/fedora/taler-exchange.taler-exchange-httpd@.service b/contrib/packages/fedora/taler-exchange.taler-exchange-httpd@.service
new file mode 100644
index 000000000..e0246899c
--- /dev/null
+++ b/contrib/packages/fedora/taler-exchange.taler-exchange-httpd@.service
@@ -0,0 +1,27 @@
+% This is a systemd service template.
+[Unit]
+Description=GNU Taler payment system exchange REST API at %I
+AssertPathExists=/run/taler/exchange-httpd
+Requires=taler-exchange-httpd@%i.socket taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
+After=postgres.service network.target taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-httpd
+Type=simple
+# Depending on the configuration, the service suicides and then
+# needs to be restarted.
+Restart=always
+# Do not dally on restarts.
+RestartSec=1ms
+EnvironmentFile=/etc/environment
+ExecStart=/usr/bin/taler-exchange-httpd -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/packages/fedora/taler-exchange.taler-exchange-secmod-cs.service b/contrib/packages/fedora/taler-exchange.taler-exchange-secmod-cs.service
new file mode 100644
index 000000000..3b5e0745d
--- /dev/null
+++ b/contrib/packages/fedora/taler-exchange.taler-exchange-secmod-cs.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange CS security module
+AssertPathExists=/run/taler/exchange-secmod-cs
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-secmod-cs
+Type=simple
+Restart=always
+RestartSec=100ms
+ExecStart=/usr/bin/taler-exchange-secmod-cs -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+IPAddressDeny=any
+Slice=taler-exchange.slice
diff --git a/contrib/packages/fedora/taler-exchange.taler-exchange-secmod-eddsa.service b/contrib/packages/fedora/taler-exchange.taler-exchange-secmod-eddsa.service
new file mode 100644
index 000000000..e8fba1736
--- /dev/null
+++ b/contrib/packages/fedora/taler-exchange.taler-exchange-secmod-eddsa.service
@@ -0,0 +1,19 @@
+[Unit]
+Description=GNU Taler payment system exchange EdDSA security module
+AssertPathExists=/run/taler/exchange-secmod-eddsa
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-secmod-eddsa
+Type=simple
+Restart=always
+RestartSec=100ms
+ExecStart=/usr/bin/taler-exchange-secmod-eddsa -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+IPAddressDeny=any
+Slice=taler-exchange.slice
+
diff --git a/contrib/packages/fedora/taler-exchange.taler-exchange-secmod-rsa.service b/contrib/packages/fedora/taler-exchange.taler-exchange-secmod-rsa.service
new file mode 100644
index 000000000..10a9585a7
--- /dev/null
+++ b/contrib/packages/fedora/taler-exchange.taler-exchange-secmod-rsa.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange RSA security module
+AssertPathExists=/run/taler/exchange-secmod-rsa
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-secmod-rsa
+Type=simple
+Restart=always
+RestartSec=100ms
+ExecStart=/usr/bin/taler-exchange-secmod-rsa -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+IPAddressDeny=any
+Slice=taler-exchange.slice
diff --git a/contrib/packages/fedora/taler-exchange.taler-exchange-transfer.service b/contrib/packages/fedora/taler-exchange.taler-exchange-transfer.service
new file mode 100644
index 000000000..e26af20d0
--- /dev/null
+++ b/contrib/packages/fedora/taler-exchange.taler-exchange-transfer.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=Taler Exchange Transfer Service
+After=network.target postgres.service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-wire
+Type=simple
+Restart=always
+RestartSec=1s
+ExecStart=/usr/bin/taler-exchange-transfer -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/contrib/packages/fedora/taler-exchange.taler-exchange-wirewatch.service b/contrib/packages/fedora/taler-exchange.taler-exchange-wirewatch.service
new file mode 100644
index 000000000..7b74737b7
--- /dev/null
+++ b/contrib/packages/fedora/taler-exchange.taler-exchange-wirewatch.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange wirewatch service
+After=network.target postgres.service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-wire
+Type=simple
+Restart=always
+RestartSec=1s
+RuntimeMaxSec=3600s
+ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
diff --git a/contrib/packages/fedora/taler-exchange.taler-exchange-wirewatch@.service b/contrib/packages/fedora/taler-exchange.taler-exchange-wirewatch@.service
new file mode 100644
index 000000000..85bb9268b
--- /dev/null
+++ b/contrib/packages/fedora/taler-exchange.taler-exchange-wirewatch@.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange wirewatch service
+After=network.target
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-wire
+Type=simple
+Restart=always
+RestartSec=1s
+ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/contrib/persona-exchange-unauthorized.en.must b/contrib/persona-exchange-unauthorized.en.must
new file mode 100644
index 000000000..9b4858178
--- /dev/null
+++ b/contrib/persona-exchange-unauthorized.en.must
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>KYC server refused access</title>
+</head>
+<body>
+The KYC backend refused the authorization code used by the exchange operator. Please inform the exchange operator about this failure.
+<pre>
+{{ kyc_http_status }}
+{{ kyc_logic }}
+{{ kyc_server_reply }}
+</pre>
+</body>
+</html>
diff --git a/contrib/persona-exchange-unpaid.en.must b/contrib/persona-exchange-unpaid.en.must
new file mode 100644
index 000000000..65fa4f9b7
--- /dev/null
+++ b/contrib/persona-exchange-unpaid.en.must
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>KYC credit exhausted</title>
+</head>
+<body>
+The KYC backend refused the process as the exchange operator's credit balance at the KYC provider is insufficient. Please inform the exchange operator about this failure.
+<pre>
+{{ kyc_http_status }}
+{{ kyc_logic }}
+{{ kyc_server_reply }}
+</pre>
+</body>
+</html>
diff --git a/contrib/persona-invalid-response.en.must b/contrib/persona-invalid-response.en.must
new file mode 100644
index 000000000..a288ae074
--- /dev/null
+++ b/contrib/persona-invalid-response.en.must
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>KYC provider returned unexpected response</title>
+</head>
+<body>
+The KYC backend returned an unexpected response.
+<pre>
+{{ kyc_http_status }}
+{{ kyc_logic }}
+{{ kyc_server_reply }}
+</pre>
+</body>
+</html>
diff --git a/contrib/persona-kyc-failed.en.must b/contrib/persona-kyc-failed.en.must
new file mode 100644
index 000000000..c1e27a821
--- /dev/null
+++ b/contrib/persona-kyc-failed.en.must
@@ -0,0 +1,20 @@
+<html>
+<head>
+<title>KYC authentication failed</title>
+</head>
+<body>
+You failed the KYC check. See below for details.
+<!-- {{kyc_logic}} indicates the type of KYC provider
+ which generated the reply; for now, only
+ "kycaid" is possible. Switch on the
+ {{kyc_logic}} to render results in a provider-specific
+ way. (or introduce new templates per provider?) -->
+<!-- TODO: figure out exactly what the
+ format of 'verifications' is here
+ based on KYCAID documentation and parse
+ that here. -->
+<pre>
+{{ verifications }}
+</pre>
+</body>
+</html> \ No newline at end of file
diff --git a/contrib/persona-load-failure.en.must b/contrib/persona-load-failure.en.must
new file mode 100644
index 000000000..77917c0b3
--- /dev/null
+++ b/contrib/persona-load-failure.en.must
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>KYC provider rate limit reached</title>
+</head>
+<body>
+The KYC backend interaction ran into a rate limit.
+<pre>
+{{ kyc_http_status }}
+{{ kyc_logic }}
+{{ kyc_server_reply }}
+</pre>
+</body>
+</html>
diff --git a/contrib/persona-logic-failure.en.must b/contrib/persona-logic-failure.en.must
new file mode 100644
index 000000000..504cd09c9
--- /dev/null
+++ b/contrib/persona-logic-failure.en.must
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>KYC server interaction failed</title>
+</head>
+<body>
+The KYC backend returned a response indicating a problem with the exchange logic. Please inform the exchange operator about this failure.
+<pre>
+{{ kyc_http_status }}
+{{ kyc_logic }}
+{{ kyc_server_reply }}
+</pre>
+</body>
+</html>
diff --git a/contrib/persona-network-timeout.en.must b/contrib/persona-network-timeout.en.must
new file mode 100644
index 000000000..c1ad79b34
--- /dev/null
+++ b/contrib/persona-network-timeout.en.must
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>KYC provider timeout</title>
+</head>
+<body>
+The KYC backend interaction ran into a timeout.
+<pre>
+{{ kyc_http_status }}
+{{ kyc_logic }}
+{{ kyc_server_reply }}
+</pre>
+</body>
+</html>
diff --git a/contrib/persona-provider-failure.en.must b/contrib/persona-provider-failure.en.must
new file mode 100644
index 000000000..37d1e0f3a
--- /dev/null
+++ b/contrib/persona-provider-failure.en.must
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>KYC provider had an internal error</title>
+</head>
+<body>
+The KYC backend had an internal error.
+<pre>
+{{ kyc_http_status }}
+{{ kyc_logic }}
+{{ kyc_server_reply }}
+</pre>
+</body>
+</html>
diff --git a/contrib/pp/.gitignore b/contrib/pp/.gitignore
deleted file mode 100644
index fb83616eb..000000000
--- a/contrib/pp/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-sphinx.err
-sphinx.log
-_build/
diff --git a/contrib/pp/README b/contrib/pp/README
deleted file mode 100644
index e03b8a059..000000000
--- a/contrib/pp/README
+++ /dev/null
@@ -1,58 +0,0 @@
-This directory contains the privacy policy (template) for exchange
-operators.
-
-
-Dependencies
-============
-
-Generating a new Privacy Policy requires Sphinx, LaTeX with babel
-packages for all supported languages. On Debian, you should
-at least install:
-
-$ apt install python3-sphinx sphinx-intl texlive-lang-german texlive-lang-english
-
-(NOTE: List may be incomplete.)
-
-
-Updating the Privacy Policy
-===========================
-
-The master file with the Privacy Policy is 'pp.rst'.
-
-If you make substantial changes, you MUST change the "PP_VERSION"
-in contrib/Makefile.am to the new Etag.
-
-To begin the translation into other languages after editing the master
-file, run
-
-$ make gettext
-
-to generate the master PO file. Then, run
-
-$ sphinx-intl update -p _build/locale/ -l de -l fr -l it
-
-to update the PO files for the various languages (extend the list of
-languages as necessary). The PO files for the translators are kept
-at locale/$LANG/LC_MESSAGES/pp.po for the language $LANG.
-
-Once all PO files have been updated with new translations, run
-
-$ make update-pp
-
-in the "contrib/" directory to generate all of the formats. The
-respective make rule calls the '../update-pp.sh' script in the
-contrib/ directory, which calls the 'Makefile' in the pp/
-directory for the various supported languages and file formats
-and then moves the generated files to the target directory
-('contrib/pp/$LANG/$VERSION.$FORMAT')
-
-
-Adding a new language
-=====================
-
-To add a new language $LANG, add $LANG to "PP_LANGUAGES" in
-'contrib/Makefile.am' and run
-
-$ sphinx-intl update -p _build/gettext -l $LANG
-
-to generate the PO template.
diff --git a/contrib/pp/conf.py b/contrib/pp/conf.py
deleted file mode 100644
index 9acb9786a..000000000
--- a/contrib/pp/conf.py
+++ /dev/null
@@ -1,282 +0,0 @@
-"""
- This file is part of GNU TALER.
- Copyright (C) 2014-2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2.1, 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 Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-
- @author Florian Dold
- @author Benedikt Muller
- @author Sree Harsha Totakura
- @author Marcello Stanisci
-"""
-# -*- coding: utf-8 -*-
-#
-# neuro documentation build configuration file, created by
-# sphinx-quickstart on Sat May 31 13:11:06 2014.
-#
-# This file is execfile()d with the current directory set to its
-# containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys
-import os
-
-sys.path.append(os.path.abspath('_exts'))
-
-#import taler_sphinx_theme
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-# -- General configuration ------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-needs_sphinx = '1.8.5'
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
-# ones.
-extensions = [
- 'sphinx.ext.todo',
- 'sphinx.ext.imgmath',
-]
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-source_suffix = {
- '.rst': 'restructuredtext',
-}
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'pp'
-
-# General information about the project.
-project = u'pp'
-copyright = u'2014-2020 Taler Systems SA (GPLv3+ or GFDL 1.3+)'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = '0'
-# The full version, including alpha/beta/rc tags.
-release = '0'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-# language = "en de"
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = ['_build', '_exts', 'cf', 'prebuilt']
-
-# The reST default role (used for this markup: `text`) to use for all
-# documents.
-# default_role = "ts:type"
-
-locale_dirs = ['locale/']
-gettext_compact = False
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-# If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
-
-# -- Options for HTML output ----------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'epub'
-
-#html_theme_path = taler_sphinx_theme.html_theme_path()
-
-#html_sidebars = {'**': ['logo-text.html', 'globaltoc.html', 'searchbox.html']}
-
-rst_epilog = ""
-
-html_show_sphinx = False
-
-html_theme_options = {
- # Set the name of the project to appear in the sidebar
- "relbar1": "false",
- "footer": "false",
-}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-html_title = "Taler Privacy Policy"
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-html_short_title = "Privacy Policy"
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-# html_static_path = ['_static']
-
-# Add any extra paths that contain custom files (such as robots.txt or
-# .htaccess) here, relative to this directory. These files are copied
-# directly to the root of the documentation.
-#html_extra_path = []
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-html_show_sphinx = False
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# -- Options for LaTeX output ---------------------------------------------
-
-latex_elements = {
- # The paper size ('letterpaper' or 'a4paper').
- #'papersize': 'letterpaper',
-
- # The font size ('10pt', '11pt' or '12pt').
- #'pointsize': '10pt',
-
- # Additional stuff for the LaTeX preamble.
- #'preamble': '',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title,
-# author, documentclass [howto, manual, or own class]).
-latex_documents = [
- ('pp', 'pp.tex',
- 'Privacy Policy', 'GNU Taler team', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Documents to append as an appendix to all manuals.
-# latex_appendices = ["fdl-1.3"]
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-# -- Options for manual page output ---------------------------------------
-
-# If true, show URL addresses after external links.
-#man_show_urls = False
-
-# -- Options for Texinfo output -------------------------------------------
-
-# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
-
-# If false, no module index is generated.
-#texinfo_domain_indices = True
-
-# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
-
-# If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
-
-
-# -- Options for epub output ----------------------------
-
-epub_basename = "pp"
-
-epub_title = "Privacy Policy"
diff --git a/contrib/pp/en/0.epub b/contrib/pp/en/0.epub
deleted file mode 100644
index 49669a4ac..000000000
--- a/contrib/pp/en/0.epub
+++ /dev/null
Binary files differ
diff --git a/contrib/pp/en/0.html b/contrib/pp/en/0.html
deleted file mode 100644
index 1e73557f5..000000000
--- a/contrib/pp/en/0.html
+++ /dev/null
@@ -1,180 +0,0 @@
-
-
-<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
- <head>
- <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>Privacy Policy &#8212; Taler Privacy Policy</title>
- <link rel="stylesheet" href="_static/epub.css" type="text/css" />
- <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
- <script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
- <script type="text/javascript" src="_static/jquery.js"></script>
- <script type="text/javascript" src="_static/underscore.js"></script>
- <script type="text/javascript" src="_static/doctools.js"></script>
- <script type="text/javascript" src="_static/language_data.js"></script>
- </head><body>
-
- <div class="document">
- <div class="documentwrapper">
- <div class="bodywrapper">
- <div class="body" role="main">
-
- <div class="section" id="privacy-policy">
-<h1>Privacy Policy<a class="headerlink" href="#privacy-policy" title="Permalink to this headline">¶</a></h1>
-<p>Last Updated: 11.12.2019</p>
-<p>This Privacy Policy describes the policies and procedures of Taler Systems SA
-(“we,†“our,†or “usâ€) pertaining to the collection, use, and disclosure of
-your information on our sites and related mobile applications and products we
-offer (the “Services†or “Taler Walletâ€). This Privacy Statement applies to
-your personal data when you use our Services, and does not apply to online
-websites or services that we do not own or control.</p>
-<div class="section" id="overview">
-<h2>Overview<a class="headerlink" href="#overview" title="Permalink to this headline">¶</a></h2>
-<p>Your privacy is important to us. We follow a few fundamental principles: We
-don’t ask you for personally identifiable information (defined below). That
-being said, your contact information, such as your phone number, social media
-handle, or email address (depending on how you contact us), may be collected
-when you communicate with us, for example to report a bug or other error
-related to the Taler Wallet. We don’t share your information with third
-parties except when strictly required to deliver you our Services and
-products, or to comply with the law. If you have any questions or concerns
-about this policy, please reach out to us at <a class="reference external" href="mailto:privacy&#37;&#52;&#48;taler-systems&#46;net">privacy<span>&#64;</span>taler-systems<span>&#46;</span>net</a>.</p>
-</div>
-<div class="section" id="how-you-accept-this-policy">
-<h2>How you accept this policy<a class="headerlink" href="#how-you-accept-this-policy" title="Permalink to this headline">¶</a></h2>
-<p>By using our Services or visiting our sites, you agree to the use, disclosure,
-and procedures outlined in this Privacy Policy.</p>
-</div>
-<div class="section" id="what-personal-information-do-we-collect-from-our-users">
-<h2>What personal information do we collect from our users?<a class="headerlink" href="#what-personal-information-do-we-collect-from-our-users" title="Permalink to this headline">¶</a></h2>
-<p>The information we collect from you falls into two categories: (i) personally
-identifiable information (i.e., data that could potentially identify you as an
-individual) (“Personal Informationâ€), and (ii) non-personally identifiable
-information (i.e., information that cannot be used to identify who you are)
-(“Non-Personal Informationâ€). This Privacy Policy covers both categories and
-will tell you how we might collect and use each type.</p>
-<p>We do our best to not collect any Personal Information from Taler Wallet
-users. We believe that the Taler Wallet never transmits personal information
-to our services without at least clear implied consent, and we only process
-and retain information with a strict business need. That being said, when
-using our Services, we inherently have to collect the following information:</p>
-<blockquote>
-<div><ul class="simple">
-<li>Bank account details necessary when receiving funds from you to top-up your wallet or to transfer funds to you when you are being paid via Taler. At the current experimental stage, only the pseudonym and password you entered in the bank demonstrator is stored.</li>
-<li>The amounts being withdrawn or deposited, with associated unique transaction identifiers and cryptographic signatures authorizing the transaction. Note that for purchases, we cannot identify the buyer from the collected data, so when you spend money, we only receive non-personal information.</li>
-<li>When you contact us. We may collect certain information if you choose to contact us, for example to report a bug or other error with the Taler Wallet. This may include contact information such as your name, email address or phone number depending on the method you choose to contact us.</li>
-</ul>
-</div></blockquote>
-</div>
-<div class="section" id="how-we-collect-and-process-information">
-<h2>How we collect and process information<a class="headerlink" href="#how-we-collect-and-process-information" title="Permalink to this headline">¶</a></h2>
-<p>We may process your information for the following reasons:</p>
-<blockquote>
-<div><ul class="simple">
-<li>to transfer money as specified by our users (Taler transactions);</li>
-<li>to assist government entities in linking income to the underlying contract</li>
-<li>to support you using the Taler Wallet or to improve our Services</li>
-</ul>
-</div></blockquote>
-</div>
-<div class="section" id="how-we-share-and-use-the-information-we-gather">
-<h2>How we share and use the information we gather<a class="headerlink" href="#how-we-share-and-use-the-information-we-gather" title="Permalink to this headline">¶</a></h2>
-<p>We may share your Personal Data or other information about you only if you are
-a merchant receiving income, with your bank, to the degree necessary to
-execute the payment.</p>
-<p>We retain Personal Data to transfer funds to the accounts designated by our
-users. We may retain Personal Data only for as long as mandated by law and
-required for the wire transfers.</p>
-<p>We primarily use the limited information we receive directly from you to
-enhance the Taler Wallet. Some ways we may use your Personal Information are
-to: Contact you when necessary to respond to your comments, answer your
-questions, or obtain additional information on issues related to bugs or
-errors with the Taler Wallet that you reported.</p>
-</div>
-<div class="section" id="agents-or-third-party-partners">
-<h2>Agents or third party partners<a class="headerlink" href="#agents-or-third-party-partners" title="Permalink to this headline">¶</a></h2>
-<p>We may provide your Personal Information to our employees, contractors,
-agents, service providers, and designees (“Agentsâ€) to enable them to perform
-certain services for us exclusively, including: improvement and maintenance of
-our software and Services. By accepting this Privacy Policy, as outlined
-above, you consent to any such transfer.</p>
-</div>
-<div class="section" id="protection-of-us-and-others">
-<h2>Protection of us and others<a class="headerlink" href="#protection-of-us-and-others" title="Permalink to this headline">¶</a></h2>
-<p>We reserve the right to access, read, preserve, and disclose any information
-that we reasonably believe is necessary to comply with the law or a court
-order.</p>
-</div>
-<div class="section" id="what-personal-information-can-i-access-or-change">
-<h2>What personal information can I access or change?<a class="headerlink" href="#what-personal-information-can-i-access-or-change" title="Permalink to this headline">¶</a></h2>
-<p>You can request access to the information we have collected from you. You can
-do this by contacting us at <a class="reference external" href="mailto:privacy&#37;&#52;&#48;taler-systems&#46;net">privacy<span>&#64;</span>taler-systems<span>&#46;</span>net</a>. We will make sure to
-provide you with a copy of the data we process about you. To comply with your
-request, we may ask you to verify your identity. We will fulfill your request
-by sending your copy electronically. For any subsequent access request, we may
-charge you with an administrative fee. If you believe that the information we
-have collected is incorrect, you are welcome to contact us so we can update it
-and keep your data accurate. Any data that is no longer needed for purposes
-specified in the “How We Use the Information We Gather†section will be
-deleted after ninety (90) days.</p>
-</div>
-<div class="section" id="data-retention">
-<h2>Data retention<a class="headerlink" href="#data-retention" title="Permalink to this headline">¶</a></h2>
-<p>If you uninstall the Taler Wallet mobile applications from your device, or
-request that your information be deleted, we still may retain some information
-that you have provided to us to maintain the Taler Wallet or to comply with
-relevant laws.</p>
-</div>
-<div class="section" id="data-security">
-<h2>Data security<a class="headerlink" href="#data-security" title="Permalink to this headline">¶</a></h2>
-<p>We are committed to making sure your information is protected. We employ
-several physical and electronic safeguards to keep your information safe,
-including encrypted user passwords, two factor verification and authentication
-on passwords where possible, and securing connections with industry standard
-transport layer security. You are also welcome to contact us using GnuPG
-encrypted e-mail. Even with all these precautions, we cannot fully guarantee
-against the access, disclosure, alteration, or deletion of data through
-events, including but not limited to hardware or software failure or
-unauthorized use. Any information that you provide to us is done so entirely
-at your own risk.</p>
-</div>
-<div class="section" id="changes-and-updates-to-privacy-policy">
-<h2>Changes and updates to privacy policy<a class="headerlink" href="#changes-and-updates-to-privacy-policy" title="Permalink to this headline">¶</a></h2>
-<p>We reserve the right to update and revise this privacy policy at any time. We
-occasionally review this Privacy Policy to make sure it complies with
-applicable laws and conforms to changes in our business. We may need to update
-this Privacy Policy, and we reserve the right to do so at any time. If we do
-revise this Privacy Policy, we will update the “Effective Date†at the bottom
-of this page so that you can tell if it has changed since your last visit. As
-we generally do not collect contact information and also do not track your
-visits, we will not be able to notify you directly. However, the Taler Wallet
-may inform you about a change in the privacy policy once it detects that the
-policy has changed. Please review this Privacy Policy regularly to ensure that
-you are aware of its terms. Any use of our Services after an amendment to our
-Privacy Policy constitutes your acceptance to the revised or amended
-agreement.</p>
-</div>
-<div class="section" id="international-users-and-visitors">
-<h2>International users and visitors<a class="headerlink" href="#international-users-and-visitors" title="Permalink to this headline">¶</a></h2>
-<p>Our Services are hosted in Switzerland. If you are a user accessing the
-Services from the European Union, Asia, US, or any other region with laws or
-regulations governing personal data collection, use, and disclosure that
-differ from Swiss laws, please be advised that through your continued use of
-the Services, which is governed by Swiss law, you are transferring your
-Personal Information to Switzerland and you consent to that transfer.</p>
-</div>
-<div class="section" id="questions">
-<h2>Questions<a class="headerlink" href="#questions" title="Permalink to this headline">¶</a></h2>
-<p>Please contact us at <a class="reference external" href="mailto:privacy&#37;&#52;&#48;taler-systems&#46;net">privacy<span>&#64;</span>taler-systems<span>&#46;</span>net</a> if you have questions about our
-privacy practices that are not addressed in this Privacy Statement.</p>
-</div>
-</div>
-
-
- </div>
- </div>
- </div>
- <div class="clearer"></div>
- </div>
- </body>
-</html> \ No newline at end of file
diff --git a/contrib/pp/en/0.pdf b/contrib/pp/en/0.pdf
deleted file mode 100644
index f594aa3b3..000000000
--- a/contrib/pp/en/0.pdf
+++ /dev/null
Binary files differ
diff --git a/contrib/pp/en/0.txt b/contrib/pp/en/0.txt
deleted file mode 100644
index a5194d320..000000000
--- a/contrib/pp/en/0.txt
+++ /dev/null
@@ -1,202 +0,0 @@
-Privacy Policy
-**************
-
-Last Updated: 11.12.2019
-
-This Privacy Policy describes the policies and procedures of Taler
-Systems SA (“we,†“our,†or “usâ€) pertaining to the collection, use,
-and disclosure of your information on our sites and related mobile
-applications and products we offer (the “Services†or “Taler Walletâ€).
-This Privacy Statement applies to your personal data when you use our
-Services, and does not apply to online websites or services that we do
-not own or control.
-
-
-Overview
-========
-
-Your privacy is important to us. We follow a few fundamental
-principles: We don’t ask you for personally identifiable information
-(defined below). That being said, your contact information, such as
-your phone number, social media handle, or email address (depending on
-how you contact us), may be collected when you communicate with us,
-for example to report a bug or other error related to the Taler
-Wallet. We don’t share your information with third parties except when
-strictly required to deliver you our Services and products, or to
-comply with the law. If you have any questions or concerns about this
-policy, please reach out to us at privacy@taler-systems.net.
-
-
-How you accept this policy
-==========================
-
-By using our Services or visiting our sites, you agree to the use,
-disclosure, and procedures outlined in this Privacy Policy.
-
-
-What personal information do we collect from our users?
-=======================================================
-
-The information we collect from you falls into two categories: (i)
-personally identifiable information (i.e., data that could potentially
-identify you as an individual) (“Personal Informationâ€), and (ii) non-
-personally identifiable information (i.e., information that cannot be
-used to identify who you are) (“Non-Personal Informationâ€). This
-Privacy Policy covers both categories and will tell you how we might
-collect and use each type.
-
-We do our best to not collect any Personal Information from Taler
-Wallet users. We believe that the Taler Wallet never transmits
-personal information to our services without at least clear implied
-consent, and we only process and retain information with a strict
-business need. That being said, when using our Services, we inherently
-have to collect the following information:
-
- * Bank account details necessary when receiving funds from you to
- top-up your wallet or to transfer funds to you when you are being
- paid via Taler. At the current experimental stage, only the
- pseudonym and password you entered in the bank demonstrator is
- stored.
-
- * The amounts being withdrawn or deposited, with associated unique
- transaction identifiers and cryptographic signatures authorizing
- the transaction. Note that for purchases, we cannot identify the
- buyer from the collected data, so when you spend money, we only
- receive non-personal information.
-
- * When you contact us. We may collect certain information if you
- choose to contact us, for example to report a bug or other error
- with the Taler Wallet. This may include contact information such
- as your name, email address or phone number depending on the
- method you choose to contact us.
-
-
-How we collect and process information
-======================================
-
-We may process your information for the following reasons:
-
- * to transfer money as specified by our users (Taler transactions);
-
- * to assist government entities in linking income to the underlying
- contract
-
- * to support you using the Taler Wallet or to improve our Services
-
-
-How we share and use the information we gather
-==============================================
-
-We may share your Personal Data or other information about you only if
-you are a merchant receiving income, with your bank, to the degree
-necessary to execute the payment.
-
-We retain Personal Data to transfer funds to the accounts designated
-by our users. We may retain Personal Data only for as long as mandated
-by law and required for the wire transfers.
-
-We primarily use the limited information we receive directly from you
-to enhance the Taler Wallet. Some ways we may use your Personal
-Information are to: Contact you when necessary to respond to your
-comments, answer your questions, or obtain additional information on
-issues related to bugs or errors with the Taler Wallet that you
-reported.
-
-
-Agents or third party partners
-==============================
-
-We may provide your Personal Information to our employees,
-contractors, agents, service providers, and designees (“Agentsâ€) to
-enable them to perform certain services for us exclusively, including:
-improvement and maintenance of our software and Services. By accepting
-this Privacy Policy, as outlined above, you consent to any such
-transfer.
-
-
-Protection of us and others
-===========================
-
-We reserve the right to access, read, preserve, and disclose any
-information that we reasonably believe is necessary to comply with the
-law or a court order.
-
-
-What personal information can I access or change?
-=================================================
-
-You can request access to the information we have collected from you.
-You can do this by contacting us at privacy@taler-systems.net. We will
-make sure to provide you with a copy of the data we process about you.
-To comply with your request, we may ask you to verify your identity.
-We will fulfill your request by sending your copy electronically. For
-any subsequent access request, we may charge you with an
-administrative fee. If you believe that the information we have
-collected is incorrect, you are welcome to contact us so we can update
-it and keep your data accurate. Any data that is no longer needed for
-purposes specified in the “How We Use the Information We Gatherâ€
-section will be deleted after ninety (90) days.
-
-
-Data retention
-==============
-
-If you uninstall the Taler Wallet mobile applications from your
-device, or request that your information be deleted, we still may
-retain some information that you have provided to us to maintain the
-Taler Wallet or to comply with relevant laws.
-
-
-Data security
-=============
-
-We are committed to making sure your information is protected. We
-employ several physical and electronic safeguards to keep your
-information safe, including encrypted user passwords, two factor
-verification and authentication on passwords where possible, and
-securing connections with industry standard transport layer security.
-You are also welcome to contact us using GnuPG encrypted e-mail. Even
-with all these precautions, we cannot fully guarantee against the
-access, disclosure, alteration, or deletion of data through events,
-including but not limited to hardware or software failure or
-unauthorized use. Any information that you provide to us is done so
-entirely at your own risk.
-
-
-Changes and updates to privacy policy
-=====================================
-
-We reserve the right to update and revise this privacy policy at any
-time. We occasionally review this Privacy Policy to make sure it
-complies with applicable laws and conforms to changes in our business.
-We may need to update this Privacy Policy, and we reserve the right to
-do so at any time. If we do revise this Privacy Policy, we will update
-the “Effective Date†at the bottom of this page so that you can tell
-if it has changed since your last visit. As we generally do not
-collect contact information and also do not track your visits, we will
-not be able to notify you directly. However, the Taler Wallet may
-inform you about a change in the privacy policy once it detects that
-the policy has changed. Please review this Privacy Policy regularly to
-ensure that you are aware of its terms. Any use of our Services after
-an amendment to our Privacy Policy constitutes your acceptance to the
-revised or amended agreement.
-
-
-International users and visitors
-================================
-
-Our Services are hosted in Switzerland. If you are a user accessing
-the Services from the European Union, Asia, US, or any other region
-with laws or regulations governing personal data collection, use, and
-disclosure that differ from Swiss laws, please be advised that through
-your continued use of the Services, which is governed by Swiss law,
-you are transferring your Personal Information to Switzerland and you
-consent to that transfer.
-
-
-Questions
-=========
-
-Please contact us at privacy@taler-systems.net if you have questions
-about our privacy practices that are not addressed in this Privacy
-Statement.
diff --git a/contrib/pp/en/0.xml b/contrib/pp/en/0.xml
deleted file mode 100644
index 3050c92be..000000000
--- a/contrib/pp/en/0.xml
+++ /dev/null
@@ -1,167 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE document PUBLIC "+//IDN docutils.sourceforge.net//DTD Docutils Generic//EN//XML" "http://docutils.sourceforge.net/docs/ref/docutils.dtd">
-<!-- Generated by Docutils 0.14 -->
-<document source="/home/grothoff/research/taler/exchange/contrib/pp/pp.rst">
- <section ids="privacy-policy" names="privacy\ policy">
- <title>Privacy Policy</title>
- <paragraph>Last Updated: 11.12.2019</paragraph>
- <paragraph>This Privacy Policy describes the policies and procedures of Taler Systems SA
- (“we,†“our,†or “usâ€) pertaining to the collection, use, and disclosure of
- your information on our sites and related mobile applications and products we
- offer (the “Services†or “Taler Walletâ€). This Privacy Statement applies to
- your personal data when you use our Services, and does not apply to online
- websites or services that we do not own or control.</paragraph>
- <section ids="overview" names="overview">
- <title>Overview</title>
- <paragraph>Your privacy is important to us. We follow a few fundamental principles: We
- don’t ask you for personally identifiable information (defined below). That
- being said, your contact information, such as your phone number, social media
- handle, or email address (depending on how you contact us), may be collected
- when you communicate with us, for example to report a bug or other error
- related to the Taler Wallet. We don’t share your information with third
- parties except when strictly required to deliver you our Services and
- products, or to comply with the law. If you have any questions or concerns
- about this policy, please reach out to us at <reference refuri="mailto:privacy@taler-systems.net">privacy@taler-systems.net</reference>.</paragraph>
- </section>
- <section ids="how-you-accept-this-policy" names="how\ you\ accept\ this\ policy">
- <title>How you accept this policy</title>
- <paragraph>By using our Services or visiting our sites, you agree to the use, disclosure,
- and procedures outlined in this Privacy Policy.</paragraph>
- </section>
- <section ids="what-personal-information-do-we-collect-from-our-users" names="what\ personal\ information\ do\ we\ collect\ from\ our\ users?">
- <title>What personal information do we collect from our users?</title>
- <paragraph>The information we collect from you falls into two categories: (i) personally
- identifiable information (i.e., data that could potentially identify you as an
- individual) (“Personal Informationâ€), and (ii) non-personally identifiable
- information (i.e., information that cannot be used to identify who you are)
- (“Non-Personal Informationâ€). This Privacy Policy covers both categories and
- will tell you how we might collect and use each type.</paragraph>
- <paragraph>We do our best to not collect any Personal Information from Taler Wallet
- users. We believe that the Taler Wallet never transmits personal information
- to our services without at least clear implied consent, and we only process
- and retain information with a strict business need. That being said, when
- using our Services, we inherently have to collect the following information:</paragraph>
- <block_quote>
- <bullet_list bullet="*">
- <list_item>
- <paragraph>Bank account details necessary when receiving funds from you to top-up your wallet or to transfer funds to you when you are being paid via Taler. At the current experimental stage, only the pseudonym and password you entered in the bank demonstrator is stored.</paragraph>
- </list_item>
- <list_item>
- <paragraph>The amounts being withdrawn or deposited, with associated unique transaction identifiers and cryptographic signatures authorizing the transaction. Note that for purchases, we cannot identify the buyer from the collected data, so when you spend money, we only receive non-personal information.</paragraph>
- </list_item>
- <list_item>
- <paragraph>When you contact us. We may collect certain information if you choose to contact us, for example to report a bug or other error with the Taler Wallet. This may include contact information such as your name, email address or phone number depending on the method you choose to contact us.</paragraph>
- </list_item>
- </bullet_list>
- </block_quote>
- </section>
- <section ids="how-we-collect-and-process-information" names="how\ we\ collect\ and\ process\ information">
- <title>How we collect and process information</title>
- <paragraph>We may process your information for the following reasons:</paragraph>
- <block_quote>
- <bullet_list bullet="*">
- <list_item>
- <paragraph>to transfer money as specified by our users (Taler transactions);</paragraph>
- </list_item>
- <list_item>
- <paragraph>to assist government entities in linking income to the underlying contract</paragraph>
- </list_item>
- <list_item>
- <paragraph>to support you using the Taler Wallet or to improve our Services</paragraph>
- </list_item>
- </bullet_list>
- </block_quote>
- </section>
- <section ids="how-we-share-and-use-the-information-we-gather" names="how\ we\ share\ and\ use\ the\ information\ we\ gather">
- <title>How we share and use the information we gather</title>
- <paragraph>We may share your Personal Data or other information about you only if you are
- a merchant receiving income, with your bank, to the degree necessary to
- execute the payment.</paragraph>
- <paragraph>We retain Personal Data to transfer funds to the accounts designated by our
- users. We may retain Personal Data only for as long as mandated by law and
- required for the wire transfers.</paragraph>
- <paragraph>We primarily use the limited information we receive directly from you to
- enhance the Taler Wallet. Some ways we may use your Personal Information are
- to: Contact you when necessary to respond to your comments, answer your
- questions, or obtain additional information on issues related to bugs or
- errors with the Taler Wallet that you reported.</paragraph>
- </section>
- <section ids="agents-or-third-party-partners" names="agents\ or\ third\ party\ partners">
- <title>Agents or third party partners</title>
- <paragraph>We may provide your Personal Information to our employees, contractors,
- agents, service providers, and designees (“Agentsâ€) to enable them to perform
- certain services for us exclusively, including: improvement and maintenance of
- our software and Services. By accepting this Privacy Policy, as outlined
- above, you consent to any such transfer.</paragraph>
- </section>
- <section ids="protection-of-us-and-others" names="protection\ of\ us\ and\ others">
- <title>Protection of us and others</title>
- <paragraph>We reserve the right to access, read, preserve, and disclose any information
- that we reasonably believe is necessary to comply with the law or a court
- order.</paragraph>
- </section>
- <section ids="what-personal-information-can-i-access-or-change" names="what\ personal\ information\ can\ i\ access\ or\ change?">
- <title>What personal information can I access or change?</title>
- <paragraph>You can request access to the information we have collected from you. You can
- do this by contacting us at <reference refuri="mailto:privacy@taler-systems.net">privacy@taler-systems.net</reference>. We will make sure to
- provide you with a copy of the data we process about you. To comply with your
- request, we may ask you to verify your identity. We will fulfill your request
- by sending your copy electronically. For any subsequent access request, we may
- charge you with an administrative fee. If you believe that the information we
- have collected is incorrect, you are welcome to contact us so we can update it
- and keep your data accurate. Any data that is no longer needed for purposes
- specified in the “How We Use the Information We Gather†section will be
- deleted after ninety (90) days.</paragraph>
- </section>
- <section ids="data-retention" names="data\ retention">
- <title>Data retention</title>
- <paragraph>If you uninstall the Taler Wallet mobile applications from your device, or
- request that your information be deleted, we still may retain some information
- that you have provided to us to maintain the Taler Wallet or to comply with
- relevant laws.</paragraph>
- </section>
- <section ids="data-security" names="data\ security">
- <title>Data security</title>
- <paragraph>We are committed to making sure your information is protected. We employ
- several physical and electronic safeguards to keep your information safe,
- including encrypted user passwords, two factor verification and authentication
- on passwords where possible, and securing connections with industry standard
- transport layer security. You are also welcome to contact us using GnuPG
- encrypted e-mail. Even with all these precautions, we cannot fully guarantee
- against the access, disclosure, alteration, or deletion of data through
- events, including but not limited to hardware or software failure or
- unauthorized use. Any information that you provide to us is done so entirely
- at your own risk.</paragraph>
- </section>
- <section ids="changes-and-updates-to-privacy-policy" names="changes\ and\ updates\ to\ privacy\ policy">
- <title>Changes and updates to privacy policy</title>
- <paragraph>We reserve the right to update and revise this privacy policy at any time. We
- occasionally review this Privacy Policy to make sure it complies with
- applicable laws and conforms to changes in our business. We may need to update
- this Privacy Policy, and we reserve the right to do so at any time. If we do
- revise this Privacy Policy, we will update the “Effective Date†at the bottom
- of this page so that you can tell if it has changed since your last visit. As
- we generally do not collect contact information and also do not track your
- visits, we will not be able to notify you directly. However, the Taler Wallet
- may inform you about a change in the privacy policy once it detects that the
- policy has changed. Please review this Privacy Policy regularly to ensure that
- you are aware of its terms. Any use of our Services after an amendment to our
- Privacy Policy constitutes your acceptance to the revised or amended
- agreement.</paragraph>
- </section>
- <section ids="international-users-and-visitors" names="international\ users\ and\ visitors">
- <title>International users and visitors</title>
- <paragraph>Our Services are hosted in Switzerland. If you are a user accessing the
- Services from the European Union, Asia, US, or any other region with laws or
- regulations governing personal data collection, use, and disclosure that
- differ from Swiss laws, please be advised that through your continued use of
- the Services, which is governed by Swiss law, you are transferring your
- Personal Information to Switzerland and you consent to that transfer.</paragraph>
- </section>
- <section ids="questions" names="questions">
- <title>Questions</title>
- <paragraph>Please contact us at <reference refuri="mailto:privacy@taler-systems.net">privacy@taler-systems.net</reference> if you have questions about our
- privacy practices that are not addressed in this Privacy Statement.</paragraph>
- </section>
- </section>
-</document>
diff --git a/contrib/pp/locale/de/LC_MESSAGES/pp.po b/contrib/pp/locale/de/LC_MESSAGES/pp.po
deleted file mode 100644
index b95444725..000000000
--- a/contrib/pp/locale/de/LC_MESSAGES/pp.po
+++ /dev/null
@@ -1,283 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) 2014-2020 Taler Systems SA (GPLv3+ or GFDL 1.3+)
-# This file is distributed under the same license as the pp package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: pp 0\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-02-07 00:51+0100\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.6.0\n"
-
-#: ../../pp.rst:2
-msgid "Privacy Policy"
-msgstr ""
-
-#: ../../pp.rst:4
-msgid "Last Updated: 11.12.2019"
-msgstr ""
-
-#: ../../pp.rst:6
-msgid ""
-"This Privacy Policy describes the policies and procedures of Taler "
-"Systems SA (“we,†“our,†or “usâ€) pertaining to the collection, use, and "
-"disclosure of your information on our sites and related mobile "
-"applications and products we offer (the “Services†or “Taler Walletâ€). "
-"This Privacy Statement applies to your personal data when you use our "
-"Services, and does not apply to online websites or services that we do "
-"not own or control."
-msgstr ""
-
-#: ../../pp.rst:15
-msgid "Overview"
-msgstr ""
-
-#: ../../pp.rst:17
-msgid ""
-"Your privacy is important to us. We follow a few fundamental principles: "
-"We don’t ask you for personally identifiable information (defined below)."
-" That being said, your contact information, such as your phone number, "
-"social media handle, or email address (depending on how you contact us), "
-"may be collected when you communicate with us, for example to report a "
-"bug or other error related to the Taler Wallet. We don’t share your "
-"information with third parties except when strictly required to deliver "
-"you our Services and products, or to comply with the law. If you have any"
-" questions or concerns about this policy, please reach out to us at "
-"privacy@taler-systems.net."
-msgstr ""
-
-#: ../../pp.rst:29
-msgid "How you accept this policy"
-msgstr ""
-
-#: ../../pp.rst:31
-msgid ""
-"By using our Services or visiting our sites, you agree to the use, "
-"disclosure, and procedures outlined in this Privacy Policy."
-msgstr ""
-
-#: ../../pp.rst:36
-msgid "What personal information do we collect from our users?"
-msgstr ""
-
-#: ../../pp.rst:38
-msgid ""
-"The information we collect from you falls into two categories: (i) "
-"personally identifiable information (i.e., data that could potentially "
-"identify you as an individual) (“Personal Informationâ€), and (ii) non-"
-"personally identifiable information (i.e., information that cannot be "
-"used to identify who you are) (“Non-Personal Informationâ€). This Privacy "
-"Policy covers both categories and will tell you how we might collect and "
-"use each type."
-msgstr ""
-
-#: ../../pp.rst:45
-msgid ""
-"We do our best to not collect any Personal Information from Taler Wallet "
-"users. We believe that the Taler Wallet never transmits personal "
-"information to our services without at least clear implied consent, and "
-"we only process and retain information with a strict business need. That "
-"being said, when using our Services, we inherently have to collect the "
-"following information:"
-msgstr ""
-
-#: ../../pp.rst:51
-msgid ""
-"Bank account details necessary when receiving funds from you to top-up "
-"your wallet or to transfer funds to you when you are being paid via "
-"Taler. At the current experimental stage, only the pseudonym and password"
-" you entered in the bank demonstrator is stored."
-msgstr ""
-
-#: ../../pp.rst:53
-msgid ""
-"The amounts being withdrawn or deposited, with associated unique "
-"transaction identifiers and cryptographic signatures authorizing the "
-"transaction. Note that for purchases, we cannot identify the buyer from "
-"the collected data, so when you spend money, we only receive non-personal"
-" information."
-msgstr ""
-
-#: ../../pp.rst:55
-msgid ""
-"When you contact us. We may collect certain information if you choose to "
-"contact us, for example to report a bug or other error with the Taler "
-"Wallet. This may include contact information such as your name, email "
-"address or phone number depending on the method you choose to contact us."
-msgstr ""
-
-#: ../../pp.rst:59
-msgid "How we collect and process information"
-msgstr ""
-
-#: ../../pp.rst:61
-msgid "We may process your information for the following reasons:"
-msgstr ""
-
-#: ../../pp.rst:63
-msgid "to transfer money as specified by our users (Taler transactions);"
-msgstr ""
-
-#: ../../pp.rst:64
-msgid "to assist government entities in linking income to the underlying contract"
-msgstr ""
-
-#: ../../pp.rst:65
-msgid "to support you using the Taler Wallet or to improve our Services"
-msgstr ""
-
-#: ../../pp.rst:69
-msgid "How we share and use the information we gather"
-msgstr ""
-
-#: ../../pp.rst:71
-msgid ""
-"We may share your Personal Data or other information about you only if "
-"you are a merchant receiving income, with your bank, to the degree "
-"necessary to execute the payment."
-msgstr ""
-
-#: ../../pp.rst:75
-msgid ""
-"We retain Personal Data to transfer funds to the accounts designated by "
-"our users. We may retain Personal Data only for as long as mandated by "
-"law and required for the wire transfers."
-msgstr ""
-
-#: ../../pp.rst:79
-msgid ""
-"We primarily use the limited information we receive directly from you to "
-"enhance the Taler Wallet. Some ways we may use your Personal Information "
-"are to: Contact you when necessary to respond to your comments, answer "
-"your questions, or obtain additional information on issues related to "
-"bugs or errors with the Taler Wallet that you reported."
-msgstr ""
-
-#: ../../pp.rst:87
-msgid "Agents or third party partners"
-msgstr ""
-
-#: ../../pp.rst:89
-msgid ""
-"We may provide your Personal Information to our employees, contractors, "
-"agents, service providers, and designees (“Agentsâ€) to enable them to "
-"perform certain services for us exclusively, including: improvement and "
-"maintenance of our software and Services. By accepting this Privacy "
-"Policy, as outlined above, you consent to any such transfer."
-msgstr ""
-
-#: ../../pp.rst:97
-msgid "Protection of us and others"
-msgstr ""
-
-#: ../../pp.rst:99
-msgid ""
-"We reserve the right to access, read, preserve, and disclose any "
-"information that we reasonably believe is necessary to comply with the "
-"law or a court order."
-msgstr ""
-
-#: ../../pp.rst:105
-msgid "What personal information can I access or change?"
-msgstr ""
-
-#: ../../pp.rst:107
-msgid ""
-"You can request access to the information we have collected from you. You"
-" can do this by contacting us at privacy@taler-systems.net. We will make "
-"sure to provide you with a copy of the data we process about you. To "
-"comply with your request, we may ask you to verify your identity. We will"
-" fulfill your request by sending your copy electronically. For any "
-"subsequent access request, we may charge you with an administrative fee. "
-"If you believe that the information we have collected is incorrect, you "
-"are welcome to contact us so we can update it and keep your data "
-"accurate. Any data that is no longer needed for purposes specified in the"
-" “How We Use the Information We Gather†section will be deleted after "
-"ninety (90) days."
-msgstr ""
-
-#: ../../pp.rst:120
-msgid "Data retention"
-msgstr ""
-
-#: ../../pp.rst:122
-msgid ""
-"If you uninstall the Taler Wallet mobile applications from your device, "
-"or request that your information be deleted, we still may retain some "
-"information that you have provided to us to maintain the Taler Wallet or "
-"to comply with relevant laws."
-msgstr ""
-
-#: ../../pp.rst:129
-msgid "Data security"
-msgstr ""
-
-#: ../../pp.rst:131
-msgid ""
-"We are committed to making sure your information is protected. We employ "
-"several physical and electronic safeguards to keep your information safe,"
-" including encrypted user passwords, two factor verification and "
-"authentication on passwords where possible, and securing connections with"
-" industry standard transport layer security. You are also welcome to "
-"contact us using GnuPG encrypted e-mail. Even with all these precautions,"
-" we cannot fully guarantee against the access, disclosure, alteration, or"
-" deletion of data through events, including but not limited to hardware "
-"or software failure or unauthorized use. Any information that you provide"
-" to us is done so entirely at your own risk."
-msgstr ""
-
-#: ../../pp.rst:144
-msgid "Changes and updates to privacy policy"
-msgstr ""
-
-#: ../../pp.rst:146
-msgid ""
-"We reserve the right to update and revise this privacy policy at any "
-"time. We occasionally review this Privacy Policy to make sure it complies"
-" with applicable laws and conforms to changes in our business. We may "
-"need to update this Privacy Policy, and we reserve the right to do so at "
-"any time. If we do revise this Privacy Policy, we will update the "
-"“Effective Date†at the bottom of this page so that you can tell if it "
-"has changed since your last visit. As we generally do not collect contact"
-" information and also do not track your visits, we will not be able to "
-"notify you directly. However, the Taler Wallet may inform you about a "
-"change in the privacy policy once it detects that the policy has changed."
-" Please review this Privacy Policy regularly to ensure that you are aware"
-" of its terms. Any use of our Services after an amendment to our Privacy "
-"Policy constitutes your acceptance to the revised or amended agreement."
-msgstr ""
-
-#: ../../pp.rst:162
-msgid "International users and visitors"
-msgstr ""
-
-#: ../../pp.rst:164
-msgid ""
-"Our Services are hosted in Switzerland. If you are a user accessing the "
-"Services from the European Union, Asia, US, or any other region with laws"
-" or regulations governing personal data collection, use, and disclosure "
-"that differ from Swiss laws, please be advised that through your "
-"continued use of the Services, which is governed by Swiss law, you are "
-"transferring your Personal Information to Switzerland and you consent to "
-"that transfer."
-msgstr ""
-
-#: ../../pp.rst:173
-msgid "Questions"
-msgstr ""
-
-#: ../../pp.rst:175
-msgid ""
-"Please contact us at privacy@taler-systems.net if you have questions "
-"about our privacy practices that are not addressed in this Privacy "
-"Statement."
-msgstr ""
-
diff --git a/contrib/samples/wire-auditor.json b/contrib/samples/wire-auditor.json
index ce2baf2e8..905bbfcf2 100644
--- a/contrib/samples/wire-auditor.json
+++ b/contrib/samples/wire-auditor.json
@@ -38,8 +38,8 @@
"value": 5,
"fraction": 1000000
},
- "missattribution_in_inconsistencies": [],
- "total_missattribution_in": {
+ "misattribution_in_inconsistencies": [],
+ "total_misattribution_in": {
"currency": "KUDOS",
"value": 0,
"fraction": 0
@@ -233,4 +233,4 @@
"fraction": 1000000
},
"lag_details": []
-} \ No newline at end of file
+}
diff --git a/contrib/sigp/.gitignore b/contrib/sigp/.gitignore
new file mode 100644
index 000000000..e49376524
--- /dev/null
+++ b/contrib/sigp/.gitignore
@@ -0,0 +1,3 @@
+/registry.rec
+/taler_signatures.h
+/taler_signatures.h.tmp
diff --git a/contrib/sigp/Makefile b/contrib/sigp/Makefile
new file mode 100644
index 000000000..65797fcf4
--- /dev/null
+++ b/contrib/sigp/Makefile
@@ -0,0 +1,21 @@
+FILES = taler_signatures.h
+
+gana = ../gana
+
+
+all: check $(FILES)
+check: registry.rec
+ recfix --check registry.rec
+registry.rec:
+ ln -s $(gana)/gnunet-signatures/registry.rec
+distclean:
+ rm -f *.tmp
+clean:
+ rm -f $(FILES) *.tmp registry.rec
+taler_signatures.h.tmp: registry.rec h.template
+ $(gana)/format.sh h.template 'Package = "GNU Taler"' < registry.rec > $@
+
+taler_signatures.h: h.header taler_signatures.h.tmp h.footer
+ cat h.header taler_signatures.h.tmp h.footer > $@
+
+.PHONY: check clean distclean
diff --git a/contrib/sigp/README b/contrib/sigp/README
new file mode 100644
index 000000000..1037bd44d
--- /dev/null
+++ b/contrib/sigp/README
@@ -0,0 +1,10 @@
+This directory contains bootstrap code to extract info from the
+Signature Purposes database (registry) and format it in various ways.
+It is a peer of ${top_srcdir}/contrib/gana/ (q.v.).
+
+NB: New database entries MUST have field "Package: GNU Taler" if
+ you want them to be visible to the Makefile in this directory.
+
+Don't make changes to registry.rec here (it is a symlink, after all).
+Instead, make them in ../gana/gnunet-signatures/ or from a separate
+checkout of the GANA Git repo (commit from there, too).
diff --git a/contrib/sigp/h.footer b/contrib/sigp/h.footer
new file mode 100644
index 000000000..c0fbf697b
--- /dev/null
+++ b/contrib/sigp/h.footer
@@ -0,0 +1,3 @@
+
+
+#endif
diff --git a/contrib/sigp/h.header b/contrib/sigp/h.header
new file mode 100644
index 000000000..6ed22a63d
--- /dev/null
+++ b/contrib/sigp/h.header
@@ -0,0 +1,31 @@
+/*
+ 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/>
+*/
+/**
+ * @file taler_signatures.h
+ * @brief message formats and signature constants used to define
+ * the binary formats of signatures in Taler
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ *
+ * This file should define the constants and C structs that one needs
+ * to know to implement Taler clients (wallets or merchants or
+ * auditor) that need to produce or verify Taler signatures.
+ */
+#ifndef TALER_SIGNATURES_H
+#define TALER_SIGNATURES_H
+
+
+
diff --git a/contrib/sigp/h.template b/contrib/sigp/h.template
new file mode 100644
index 000000000..66b0317e0
--- /dev/null
+++ b/contrib/sigp/h.template
@@ -0,0 +1,6 @@
+
+
+/**
+ * {{Comment}}
+ */
+#define TALER_SIGNATURE_{{Name}} {{Number}}
diff --git a/contrib/taler-auditor-dbconfig b/contrib/taler-auditor-dbconfig
new file mode 100755
index 000000000..245d6970d
--- /dev/null
+++ b/contrib/taler-auditor-dbconfig
@@ -0,0 +1,132 @@
+#!/bin/bash
+# This file is part of GNU 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 Lesser General Public License as published by the Free Software
+# Foundation; either version 2.1, 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along with
+# TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+#
+# @author Christian Grothoff
+#
+#
+# Error checking on
+set -eu
+
+RESET_DB=0
+SKIP_DBINIT=0
+DBUSER="taler-auditor-httpd"
+DBNAME="auditor"
+CFGFILE="/etc/taler/secrets/auditor-db.secret.conf"
+
+# Parse command-line options
+while getopts ':hn:rsu:' OPTION; do
+ case "$OPTION" in
+ h)
+ echo 'Supported options:'
+ echo " -c FILENAME -- write configuration to FILENAME (default: $CFGFILE)"
+ echo " -n NAME -- user NAME for database name (default: $DBNAME)"
+ echo " -r -- reset database (dangerous)"
+ echo " -s -- skip database initialization"
+ echo " -u USER -- taler-auditor to be run by USER (default: $DBUSER)"
+ exit 0
+ ;;
+ n)
+ DBNAME="$OPTARG"
+ ;;
+ r)
+ RESET_DB="1"
+ ;;
+ s)
+ SKIP_DBINIT="1"
+ ;;
+ u)
+ DBUSER="$OPTARG"
+ ;;
+ ?)
+ exit_fail "Unrecognized command line option"
+ ;;
+ esac
+done
+
+if ! id postgres > /dev/null
+then
+ echo "Could not find 'postgres' user. Please install Postgresql first"
+ exit 1
+fi
+
+if [ "$(id -u)" -ne 0 ]
+then
+ echo "This script must be run as root"
+ exit 1
+fi
+
+if [ 0 = "$SKIP_DBINIT" ]
+then
+ if ! taler-auditor-dbinit -v 2> /dev/null
+ then
+ echo "Required 'taler-auditor-dbinit' not found. Please fix your installation."
+ fi
+fi
+
+if ! id "$DBUSER" > /dev/null
+then
+ echo "Could not find '$DBUSER' user. Please set it up first"
+ exit 1
+fi
+
+if sudo -i -u postgres psql "$DBNAME" < /dev/null 2> /dev/null
+then
+ if [ 1 = "$RESET_DB" ]
+ then
+ echo "Deleting existing database '$DBNAME'." 1>&2
+ sudo -i -u postgres dropdb "$DBNAME"
+ else
+ echo "Database '$DBNAME' already exists, refusing to setup again."
+ echo "Use -r to delete the existing database first (dangerous!)."
+ exit 77
+ fi
+fi
+
+echo "Setting up database user $DBUSER." 1>&2
+
+if ! sudo -i -u postgres createuser "$DBUSER" 2> /dev/null
+then
+ echo "Database user '$DBUSER' already existed. Continuing anyway." 1>&2
+fi
+
+echo "Creating database $DBNAME." 1>&2
+
+if ! sudo -i -u postgres createdb -O "$DBUSER" "$DBNAME"
+then
+ echo "Failed to create database '$DBNAME'"
+ exit 1
+fi
+
+if [ -f "$CFGFILE" ]
+then
+ echo "Adding database configuration to '$CFGFILE'." 1>&2
+ echo -e "[auditordb-postgres]\nCONFIG=postgres:///$DBNAME\n" >> "$CFGFILE"
+else
+ echo "Configuration '$CFGFILE' does not yet exist, creating it." 1>&2
+ mkdir -p "$(dirname "$CFGFILE")"
+ echo -e "[auditordb-postgres]\nCONFIG=postgres:///$DBNAME\n" >> "$CFGFILE"
+ chown "$DBUSER":root "$CFGFILE"
+ chmod 460 "$CFGFILE"
+fi
+
+if [ 0 = "$SKIP_DBINIT" ]
+then
+ echo "Initializing database '$DBNAME'." 1>&2
+ sudo -u "$DBUSER" taler-auditor-dbinit
+fi
+
+echo "Database configuration finished." 1>&2
+
+exit 0
diff --git a/contrib/taler-bank-manage-testing b/contrib/taler-bank-manage-testing
deleted file mode 100755
index 29494e3ad..000000000
--- a/contrib/taler-bank-manage-testing
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/sh
-# This file is in the public domain
-# Wrapper around 'taler-bank-manage' to first configure the required
-# testing accounts before launching the bank properly.
-#
-# Takes 3 arguments:
-# $1: the configuration file name
-# $2: the database name
-# $3: serve-http or serve-uwsgi
-
-set -eu
-
-if [ "$#" -ne 3 ];
-then
- echo "illegal number of parameters"
- exit 1
-fi
-
-# Ensure starting accounts exist
-taler-bank-manage -c $1 --with-db $2 django provide_accounts
-taler-bank-manage -c $1 --with-db $2 django add_bank_account 42
-taler-bank-manage -c $1 --with-db $2 django add_bank_account 43
-
-# This is 'x' hashed by Django
-PW_HASH='pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs='
-
-# hack password hash directly into the database:
-echo "UPDATE auth_user SET password='$PW_HASH'" | psql -Aqt $2
-
-
-# Note that calling
-# taler-bank-manage -c $1 --with-db $2 django changepassword Bank x
-# does not work: (1) it always insists on going interactive, and (2)
-# rejects 'x' as a password.
-
-
-# Now run Django for good
-exec taler-bank-manage -c $1 --with-db $2 $3
diff --git a/contrib/taler-exchange-dbconfig b/contrib/taler-exchange-dbconfig
new file mode 100755
index 000000000..eb90ee721
--- /dev/null
+++ b/contrib/taler-exchange-dbconfig
@@ -0,0 +1,186 @@
+#!/bin/bash
+# This file is part of GNU 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 Lesser General Public License as published by the Free Software
+# Foundation; either version 2.1, 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along with
+# TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+#
+# @author Christian Grothoff
+#
+#
+# Error checking on
+set -eu
+
+RESET_DB=0
+SKIP_DBINIT=0
+FORCE_PERMS=0
+DBUSER="taler-exchange-httpd"
+DBGROUP="taler-exchange-db"
+CFGFILE="/etc/taler/taler.conf"
+
+# Parse command-line options
+while getopts 'c:g:hprsu:' OPTION; do
+ case "$OPTION" in
+ c)
+ CFGFILE="$OPTARG"
+ ;;
+ h)
+ echo 'Supported options:'
+ echo " -c FILENAME -- use configuration FILENAME (default: $CFGFILE)"
+ echo " -g GROUP -- taler-exchange to be run by GROUP (default: $DBGROUP)"
+ echo " -h -- print this help text"
+ echo " -r -- reset database (dangerous)"
+ echo " -p -- force permission setup even without database initialization"
+ echo " -s -- skip database initialization"
+ echo " -u USER -- taler-exchange to be run by USER (default: $DBUSER)"
+ exit 0
+ ;;
+ p)
+ FORCE_PERMS="1"
+ ;;
+ r)
+ RESET_DB="1"
+ ;;
+ s)
+ SKIP_DBINIT="1"
+ ;;
+ u)
+ DBUSER="$OPTARG"
+ ;;
+ ?)
+ echo "Unrecognized command line option" 1>&2
+ exit 1
+ ;;
+ esac
+done
+
+if ! id postgres > /dev/null
+then
+ echo "Could not find 'postgres' user. Please install Postgresql first"
+ exit 1
+fi
+
+if [ "$(id -u)" -ne 0 ]
+then
+ echo "This script must be run as root"
+ exit 1
+fi
+
+if [ 0 = "$SKIP_DBINIT" ]
+then
+ if ! taler-exchange-dbinit -v 2> /dev/null
+ then
+ echo "Required 'taler-exchange-dbinit' not found. Please fix your installation."
+ exit 1
+ fi
+ DBINIT=$(which taler-exchange-dbinit)
+fi
+
+if ! id "$DBUSER" > /dev/null
+then
+ echo "Could not find '$DBUSER' user. Please set it up first"
+ exit 1
+fi
+
+echo "Setting up database user '$DBUSER'." 1>&2
+
+if ! sudo -i -u postgres createuser "$DBUSER" 2> /dev/null
+then
+ echo "Database user '$DBUSER' already existed. Continuing anyway." 1>&2
+fi
+
+DBPATH=$(taler-config \
+ -c "$CFGFILE" \
+ -s exchangedb-postgres \
+ -o CONFIG)
+
+if ! echo "$DBPATH" | grep "postgres://" > /dev/null
+then
+ echo "Invalid database configuration value '$DBPATH'." 1>&2
+ exit 1
+fi
+
+DBNAME=$(echo "$DBPATH" \
+ | sed \
+ -e "s/postgres:\/\/.*\///" \
+ -e "s/?.*//")
+
+if sudo -i -u postgres psql "$DBNAME" < /dev/null 2> /dev/null
+then
+ if [ 1 = "$RESET_DB" ]
+ then
+ echo "Deleting existing database '$DBNAME'." 1>&2
+ if ! sudo -i -u postgres dropdb "$DBNAME"
+ then
+ echo "Failed to delete existing database '$DBNAME'"
+ exit 1
+ fi
+ DO_CREATE=1
+ else
+ echo "Database '$DBNAME' already exists, continuing anyway."
+ DO_CREATE=0
+ fi
+else
+ DO_CREATE=1
+fi
+
+if [ 1 = "$DO_CREATE" ]
+then
+ echo "Creating database '$DBNAME'." 1>&2
+
+ if ! sudo -i -u postgres createdb -O "$DBUSER" "$DBNAME"
+ then
+ echo "Failed to create database '$DBNAME'"
+ exit 1
+ fi
+fi
+
+if [ 0 = "$SKIP_DBINIT" ]
+then
+ echo "Initializing database '$DBNAME'." 1>&2
+ if ! sudo -u "$DBUSER" "$DBINIT" -c "$CFGFILE"
+ then
+ echo "Failed to initialize database schema"
+ exit 1
+ fi
+fi
+
+if [ 0 = "$SKIP_DBINIT" ] || [ 1 = "$FORCE_PERMS" ]
+then
+ DB_GRP="$(getent group "$DBGROUP" | sed -e "s/.*://g" -e "s/,/ /g")"
+ echo "Initializing permissions for '$DB_GRP'." 1>&2
+ for GROUPIE in $DB_GRP
+ do
+ if [ "$GROUPIE" != "$DBUSER" ]
+ then
+ if ! sudo -i -u postgres createuser "$GROUPIE" 2> /dev/null
+ then
+ echo "Database user '$GROUPIE' already existed. Continuing anyway." 1>&2
+ fi
+ echo -e 'GRANT SELECT,INSERT,UPDATE,DELETE ON ALL TABLES IN SCHEMA exchange TO "'"$GROUPIE"'";\n' \
+ 'GRANT USAGE ON ALL SEQUENCES IN SCHEMA exchange TO "'"$GROUPIE"'";\n' \
+ | sudo -u "$DBUSER" psql "$DBNAME"
+ echo -e 'GRANT USAGE ON SCHEMA exchange TO "'"$GROUPIE"'"' \
+ | sudo -u "$DBUSER" psql "$DBNAME"
+ # FIXME: double-check the following GRANTs
+ echo -e 'GRANT USAGE ON SCHEMA _v TO "'"$GROUPIE"'"' \
+ | sudo -u "$DBUSER" psql "$DBNAME"
+ echo -e 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA _v TO "'"$GROUPIE"'"' \
+ | sudo -u "$DBUSER" psql "$DBNAME"
+
+
+ fi
+ done
+fi
+
+echo "Database configuration finished." 1>&2
+
+exit 0
diff --git a/contrib/taler-exchange-revoke b/contrib/taler-exchange-revoke
deleted file mode 100755
index 8e4bc6ed4..000000000
--- a/contrib/taler-exchange-revoke
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/sh
-# This file is in the public domain
-#
-# Used to first revoke a key and then restart the exchange
-# to ensure it notices the revocation.
-#
-# Takes 2 arguments:
-# $1: the configuration file name
-# $2: the denomination key hash (DKH) of the denomination to revoke
-
-set -eu
-
-if [ "$#" -ne 2 ];
-then
- echo "illegal number of parameters"
- exit 1
-fi
-
-taler-exchange-keyup -c $1 -r $2
-
-EXCHANGE_PID=`ps x | grep taler-exchange-httpd | awk '{print $1}'`
-kill -SIGUSR1 $EXCHANGE_PID
-
-exit 0
diff --git a/contrib/taler-terms-generator b/contrib/taler-terms-generator
new file mode 100755
index 000000000..4a25afa4b
--- /dev/null
+++ b/contrib/taler-terms-generator
@@ -0,0 +1,295 @@
+#!/bin/bash
+# This file is part of GNU TALER.
+# 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 Lesser General Public License as published by the Free Software
+# Foundation; either version 2.1, 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along with
+# TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+#
+# @author Florian Dold
+# @author Benedikt Muller
+# @author Sree Harsha Totakura
+# @author Marcello Stanisci
+# @author Christian Grothoff
+#
+#
+# Error checking on
+set -eu
+
+# Call with target language as first argument.
+function make_config() {
+ cat >"${BUILDDIR}/conf.py" <<EOF
+import sys
+import os
+sys.path.append(os.path.abspath('_exts'))
+needs_sphinx = '1.8.5'
+extensions = [
+ 'sphinx.ext.todo',
+ 'sphinx.ext.imgmath',
+ 'sphinx_markdown_builder',
+]
+templates_path = ['_templates']
+source_suffix = {
+ '.rst': 'restructuredtext',
+}
+master_doc = '$VERSION_BASENAME'
+project = u'$VERSION_BASENAME'
+copyright = u'$COPYRIGHT'
+version = '$VERSION_BASENAME'
+release = '$VERSION_BASENAME'
+language = "$LANGUAGE"
+exclude_patterns = ['_build', '_exts', 'cf', 'prebuilt']
+locale_dirs = ['$LOCALE_DIR/']
+gettext_compact = False
+pygments_style = 'sphinx'
+html_theme = 'epub'
+rst_epilog = ""
+html_show_sphinx = False
+html_theme_options = {
+ "relbar1": "false",
+ "footer": "false",
+}
+html_title = "$TITLE"
+html_short_title = "$TITLE"
+html_use_index = True
+html_show_sphinx = False
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #'preamble': '',
+}
+latex_documents = [
+ ('$VERSION_BASENAME', '$VERSION_BASENAME.tex',
+ '$VERSION_BASENAME', '$VERSION_BASENAME', 'manual'),
+]
+epub_basename = "$VERSION_BASENAME"
+epub_title = "$TITLE"
+EOF
+}
+
+# Output file given as first argument to stderr, then exit with a failure.
+function failcat() {
+ cat "$1" 1>&2
+ exit 1
+}
+
+# defaults
+AUTHOR="GNU Taler team"
+VERSION="exchange-tos-v0"
+LOCALE_DIR=$(taler-config -s "PATHS" -o "LOCALEDIR" -f)
+OUTPUT=$(taler-config -s "EXCHANGE" -o "TERMS_DIR" -f)
+PAPER="a4"
+COPYRIGHT="2014-2023 Taler Systems SA (GPLv3+ or GFDL 1.3+)"
+INCREMENTAL=0
+
+# Parse command-line options
+while getopts ':a:C:hKi:l:L:o:p:t:' OPTION; do
+ case "$OPTION" in
+ a)
+ AUTHOR="$OPTARG"
+ ;;
+ C)
+ COPYRIGHT="$OPTARG"
+ ;;
+ h)
+ echo 'Supported options:'
+ echo ' -a AUTHOR -- set author header' "(default: $AUTHOR)"
+ echo ' -C COPYRIGHT -- set copyright header' "(default: $COPYRIGHT)"
+ echo ' -h -- print this help'
+ echo ' -K -- rebuild only if input is older than output'
+ echo ' -i INPUT -- input file to convert' "(default: $VERSION)"
+ echo ' -l LANGUAGE -- target language to add'
+ echo ' -L LOCALE_DIR -- directory with resources for translation' "(default: $LOCALE_DIR)"
+ echo ' -o OUTPUT -- output directory' "(default: $OUTPUT)"
+ echo ' -p PAPER -- paper format' "(default: $PAPER)"
+ echo ' -t TITLE -- title of the document to generate'
+ exit 0
+ ;;
+ l)
+ ADD_LANGUAGE="$OPTARG"
+ ;;
+ L)
+ LOCALE_DIR="$OPTARG"
+ ;;
+ i)
+ VERSION="$OPTARG"
+ ;;
+ o)
+ OUTPUT="$OPTARG"
+ ;;
+ p)
+ PAPER="$OPTARG"
+ case "$PAPER" in
+ a4 | letter) ;;
+ *)
+ echo "Error: Paper format '$PAPER' invalid (use 'a4' or 'letter')" 1>&2
+ exit 1
+ ;;
+ esac
+ ;;
+ t)
+ TITLE="$OPTARG"
+ ;;
+ K)
+ INCREMENTAL=1
+ ;;
+ ?)
+ echo "Unrecognized command line option" 1>&2
+ exit 1
+ ;;
+ esac
+done
+
+if ! which sphinx-build >/dev/null; then
+ echo "Command 'sphinx-build' not found, but required. Please install sphinx." 1>&2
+ exit 1
+fi
+
+if ! which pandoc >/dev/null; then
+ echo "Command 'pandoc' not found, but required. Please install pandoc." 1>&2
+ exit 1
+fi
+
+if ! which gs >/dev/null; then
+ echo "Command 'gs' not found, but required. Please install ghostscript." 1>&2
+ exit 1
+fi
+
+if ! which pdfroff >/dev/null; then
+ echo "Command 'pdfroff' not found, but required. Please install pdfroff/groff." 1>&2
+ exit 1
+fi
+
+if ! which make >/dev/null; then
+ echo "Command 'make' not found, but required. Please install make." 1>&2
+ exit 1
+fi
+
+# We append ".rst" if needed, remove if given on command-line
+# shellcheck disable=SC2001
+VERSION=$(echo "${VERSION}" | sed -e "s/\.rst$//")
+
+# Sometimes we just want the basename, not the directory.
+VERSION_BASENAME=$(basename "${VERSION}")
+
+BUILDDIR=$(mktemp -d /tmp/taler-terms-XXXXXX)
+if [ ! -f "${VERSION}.rst" ]; then
+ echo "Error: File '${VERSION}.rst' not found. Please check '-i' option." 1>&2
+ exit 1
+fi
+
+cp "${VERSION}.rst" "${BUILDDIR}/"
+
+if [ -z ${TITLE+x} ]; then
+ TITLE=$(head -n1 "${VERSION}.rst")
+ echo "Title automatically set to '$TITLE'" 1>&2
+fi
+
+if [ -n "${ADD_LANGUAGE+x}" ]; then
+ if ! echo "${ADD_LANGUAGE}" | grep -e '^..$' >/dev/null; then
+ echo "Error: Invalid language '${ADD_LANGUAGE}'. Two characters (en, de, fr, ...) expected." 1>&2
+ exit 1
+ fi
+ echo "Adding language files for translations to '${ADD_LANGUAGE}'" 1>&2
+ make_config "${ADD_LANGUAGE}"
+ sphinx-build \
+ -b gettext \
+ -D language="${ADD_LANGUAGE}" \
+ -d "${BUILDDIR}/.doctrees" \
+ "${BUILDDIR}" \
+ "${LOCALE_DIR}/${ADD_LANGUAGE}/LC_MESSAGES/" \
+ &>"${BUILDDIR}/add-language.log" ||
+ failcat "${BUILDDIR}/add-language.log"
+ if [ -f "${LOCALE_DIR}/${ADD_LANGUAGE}/LC_MESSAGES/${VERSION_BASENAME}.po" ]; then
+ msgmerge --lang="${ADD_LANGUAGE}" \
+ --no-location \
+ -o "${LOCALE_DIR}/${ADD_LANGUAGE}/LC_MESSAGES/${VERSION_BASENAME}.mrg" \
+ "${LOCALE_DIR}/${ADD_LANGUAGE}/LC_MESSAGES/${VERSION_BASENAME}.po" \
+ "${LOCALE_DIR}/${ADD_LANGUAGE}/LC_MESSAGES/${VERSION_BASENAME}.pot"
+ mv "${LOCALE_DIR}/${ADD_LANGUAGE}/LC_MESSAGES/${VERSION_BASENAME}.mrg" \
+ "${LOCALE_DIR}/${ADD_LANGUAGE}/LC_MESSAGES/${VERSION_BASENAME}.po"
+ else
+ mv "${LOCALE_DIR}/${ADD_LANGUAGE}/LC_MESSAGES/${VERSION_BASENAME}.pot" \
+ "${LOCALE_DIR}/${ADD_LANGUAGE}/LC_MESSAGES/${VERSION_BASENAME}.po"
+ fi
+ rm -f "${LOCALE_DIR}/${ADD_LANGUAGE}/LC_MESSAGES/${VERSION_BASENAME}.pot"
+ echo "Done" 1>&2
+ exit 0
+fi
+
+# As a heuristic for incremental builds, we only check the text output file.
+if [[ $INCREMENTAL -eq 1 ]]; then
+ if [[ "${VERSION}.rst" -ot "${OUTPUT}/buildstamp" ]]; then
+ echo "Not rebuilding, input file $VERSION is older than $OUTPUT/buildstamp."
+ exit 0
+ fi
+fi
+
+# shellcheck disable=SC2086
+for d in en $(ls -d ${LOCALE_DIR}/?? | grep -v "en" 2>/dev/null || true); do
+ LANGUAGE=$(basename "$d")
+ if [ "en" != "${LANGUAGE}" ] && [ ! -f "${LOCALE_DIR}/${LANGUAGE}/LC_MESSAGES/${VERSION_BASENAME}.po" ]; then
+ echo "Skipping language ${LANGUAGE}: no translation for ${VERSION_BASENAME} found."
+ continue
+ fi
+ echo "Generating files at '$OUTPUT' for ETag '$VERSION_BASENAME' and language '${LANGUAGE}' in '${BUILDDIR}':" 1>&2
+
+ make_config "$LANGUAGE"
+ mkdir -p "${OUTPUT}/${LANGUAGE}/"
+
+ LBUILD="sphinx-build -D language=${LANGUAGE} -d ${BUILDDIR}/.doctrees"
+
+ OUTBASE="${OUTPUT}/${LANGUAGE}/${VERSION_BASENAME}"
+
+ echo "$VERSION_BASENAME MD ($LANGUAGE)..." 1>&2
+ $LBUILD \
+ -b markdown \
+ "${BUILDDIR}" \
+ "${BUILDDIR}/md" \
+ &>"${BUILDDIR}/md-sphinx.log" ||
+ failcat "${BUILDDIR}/md-sphinx.log"
+ BUILDFILE_MARKDOWN="${BUILDDIR}/md/${VERSION_BASENAME}.md"
+ cp "$BUILDFILE_MARKDOWN" "${OUTBASE}.md"
+
+ # Convert the generated Markdown (!) to other formats.
+
+ echo "$VERSION_BASENAME PDF ($LANGUAGE)..." 1>&2
+ pandoc \
+ -i "$BUILDFILE_MARKDOWN" \
+ -o "${OUTBASE}.pdf" \
+ --pdf-engine=pdfroff \
+ --shift-heading-level-by=-1
+
+ echo "$VERSION_BASENAME HTML ($LANGUAGE)..." 1>&2
+ # FIXME: Newer versions of pandic should use
+ # --embed-resources --standalone instead of --self-contained
+ pandoc \
+ -i "$BUILDFILE_MARKDOWN" \
+ -o "${OUTBASE}.html" \
+ --self-contained \
+ --shift-heading-level-by=-1
+
+ echo "$VERSION_BASENAME TXT ($LANGUAGE)..." 1>&2
+ pandoc \
+ -i "$BUILDFILE_MARKDOWN" \
+ -o "${OUTBASE}.txt"
+done
+
+if [[ $INCREMENTAL -eq 1 ]]; then
+ touch "${OUTPUT}/buildstamp"
+fi
+
+echo "Done" 1>&2
+exit 0
diff --git a/contrib/tos/.gitignore b/contrib/tos/.gitignore
deleted file mode 100644
index fb83616eb..000000000
--- a/contrib/tos/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-sphinx.err
-sphinx.log
-_build/
diff --git a/contrib/tos/Makefile b/contrib/tos/Makefile
deleted file mode 100644
index ab29543cb..000000000
--- a/contrib/tos/Makefile
+++ /dev/null
@@ -1,109 +0,0 @@
-# Makefile for Sphinx documentation
-#
-# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-PAPER =
-BUILDDIR = _build
-
-# User-friendly check for sphinx-build
-ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
-$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
-endif
-
-# Internal variables.
-PAPEROPT_a4 = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-# the i18n builder cannot share the environment and doctrees with the others
-I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-.PHONY: help clean html json epub latex latexpdf text man doctest gettext
-
-help:
- @echo "Please use \`make <target>' where <target> is one of"
- @echo " html to make a single large HTML file"
- @echo " json to make JSON files"
- @echo " epub to make an epub"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " pdf to make LaTeX files and run them through pdflatex"
- @echo " txt to make text files"
- @echo " man to make manual pages"
- @echo " texinfo to make Texinfo files"
- @echo " info to make Texinfo files and run them through makeinfo"
- @echo " gettext to make PO message catalogs"
- @echo " xml to make Docutils-native XML files"
- @echo " pseudoxml to make pseudoxml-XML files for display purposes"
- @echo " doctest to run all doctests embedded in the documentation (if enabled)"
-
-clean:
- rm -rf $(BUILDDIR)/*
-
-
-# The html-linked builder does not support caching, so we
-# remove all cached state first.
-html:
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML page is in $(BUILDDIR)/html."
-
-json:
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
- @echo
- @echo "Build finished; now you can process the JSON files."
-
-epub:
- $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
- @echo
- @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
- @echo "Run \`make' in that directory to run these through (pdf)latex" \
- "(use \`make latexpdf' here to do that automatically)."
-
-pdf:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/pdf
- @echo "Running LaTeX files through pdflatex..."
- $(MAKE) -C $(BUILDDIR)/pdf all-pdf
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/pdf."
-
-txt:
- $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/txt
- @echo
- @echo "Build finished. The text files are in $(BUILDDIR)/txt."
-
-man:
- $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
- @echo
- @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-texinfo:
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
- @echo
- @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
- @echo "Run \`make' in that directory to run these through makeinfo" \
- "(use \`make info' here to do that automatically)."
-
-info:
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/info
- @echo "Running Texinfo files through makeinfo..."
- make -C $(BUILDDIR)/texinfo info
- @echo "makeinfo finished; the Info files are in $(BUILDDIR)/info."
-
-gettext:
- $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
- @echo
- @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
-
-doctest:
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
- @echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
-
-xml:
- $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
- @echo
- @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
diff --git a/contrib/tos/README b/contrib/tos/README
deleted file mode 100644
index fde5305b6..000000000
--- a/contrib/tos/README
+++ /dev/null
@@ -1,58 +0,0 @@
-This directory contains the terms of service (template) for exchange
-operators.
-
-
-Dependencies
-============
-
-Generating new Terms of Service requires Sphinx, LaTeX with babel
-packages for all supported languages. On Debian, you should
-at least install:
-
-$ apt install python3-sphinx sphinx-intl texlive-lang-german texlive-lang-english
-
-(NOTE: List may be incomplete.)
-
-
-Updating the Terms of Service
-=============================
-
-The master file with the Terms of service is 'tos.rst'.
-
-If you make substantial changes, you MUST change the "TOS_VERSION"
-in contrib/Makefile.am to the new Etag.
-
-To begin the translation into other languages after editing the master
-file, run
-
-$ make gettext
-
-to generate the master PO file. Then, run
-
-$ sphinx-intl update -p _build/locale/ -l de -l fr -l it
-
-to update the PO files for the various languages (extend the list of
-languages as necessary). The PO files for the translators are kept
-at locale/$LANG/LC_MESSAGES/tos.po for the language $LANG.
-
-Once all PO files have been updated with new translations, run
-
-$ make update-tos
-
-in the "contrib/" directory to generate all of the formats. The
-respective make rule calls the '../update-tos.sh' script in the
-contrib/ directory, which calls the 'Makefile' in the tos/
-directory for the various supported languages and file formats
-and then moves the generated files to the target directory
-('contrib/tos/$LANG/$VERSION.$FORMAT')
-
-
-Adding a new language
-=====================
-
-To add a new language $LANG, add $LANG to "TOS_LANGUAGES" in
-'contrib/Makefile.am' and run
-
-$ sphinx-intl update -p _build/gettext -l $LANG
-
-to generate the PO template.
diff --git a/contrib/tos/conf.py b/contrib/tos/conf.py
deleted file mode 100644
index 29392556f..000000000
--- a/contrib/tos/conf.py
+++ /dev/null
@@ -1,282 +0,0 @@
-"""
- This file is part of GNU TALER.
- Copyright (C) 2014-2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2.1, 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 Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-
- @author Florian Dold
- @author Benedikt Muller
- @author Sree Harsha Totakura
- @author Marcello Stanisci
-"""
-# -*- coding: utf-8 -*-
-#
-# neuro documentation build configuration file, created by
-# sphinx-quickstart on Sat May 31 13:11:06 2014.
-#
-# This file is execfile()d with the current directory set to its
-# containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys
-import os
-
-sys.path.append(os.path.abspath('_exts'))
-
-#import taler_sphinx_theme
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-# -- General configuration ------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-needs_sphinx = '1.8.5'
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
-# ones.
-extensions = [
- 'sphinx.ext.todo',
- 'sphinx.ext.imgmath',
-]
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-source_suffix = {
- '.rst': 'restructuredtext',
-}
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'tos'
-
-# General information about the project.
-project = u'tos'
-copyright = u'2014-2020 Taler Systems SA (GPLv3+ or GFDL 1.3+)'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = '0'
-# The full version, including alpha/beta/rc tags.
-release = '0'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-# language = "en de"
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = ['_build', '_exts', 'cf', 'prebuilt']
-
-# The reST default role (used for this markup: `text`) to use for all
-# documents.
-# default_role = "ts:type"
-
-locale_dirs = ['locale/']
-gettext_compact = False
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-# If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
-
-# -- Options for HTML output ----------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'epub'
-
-#html_theme_path = taler_sphinx_theme.html_theme_path()
-
-#html_sidebars = {'**': ['logo-text.html', 'globaltoc.html', 'searchbox.html']}
-
-rst_epilog = ""
-
-html_show_sphinx = False
-
-html_theme_options = {
- # Set the name of the project to appear in the sidebar
- "relbar1": "false",
- "footer": "false",
-}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-html_title = "Taler Terms of Service"
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-html_short_title = "Terms of Service"
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-# html_static_path = ['_static']
-
-# Add any extra paths that contain custom files (such as robots.txt or
-# .htaccess) here, relative to this directory. These files are copied
-# directly to the root of the documentation.
-#html_extra_path = []
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-html_show_sphinx = False
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# -- Options for LaTeX output ---------------------------------------------
-
-latex_elements = {
- # The paper size ('letterpaper' or 'a4paper').
- #'papersize': 'letterpaper',
-
- # The font size ('10pt', '11pt' or '12pt').
- #'pointsize': '10pt',
-
- # Additional stuff for the LaTeX preamble.
- #'preamble': '',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title,
-# author, documentclass [howto, manual, or own class]).
-latex_documents = [
- ('tos', 'tos.tex',
- 'Terms of Service', 'GNU Taler team', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Documents to append as an appendix to all manuals.
-# latex_appendices = ["fdl-1.3"]
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-# -- Options for manual page output ---------------------------------------
-
-# If true, show URL addresses after external links.
-#man_show_urls = False
-
-# -- Options for Texinfo output -------------------------------------------
-
-# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
-
-# If false, no module index is generated.
-#texinfo_domain_indices = True
-
-# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
-
-# If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
-
-
-# -- Options for epub output ----------------------------
-
-epub_basename = "tos"
-
-epub_title = "Terms of Service"
diff --git a/contrib/tos/en/0.epub b/contrib/tos/en/0.epub
deleted file mode 100644
index 5acb8dfca..000000000
--- a/contrib/tos/en/0.epub
+++ /dev/null
Binary files differ
diff --git a/contrib/tos/en/0.html b/contrib/tos/en/0.html
deleted file mode 100644
index 07a3ab401..000000000
--- a/contrib/tos/en/0.html
+++ /dev/null
@@ -1,337 +0,0 @@
-
-
-<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
- <head>
- <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>Terms Of Service &#8212; Taler Terms of Service</title>
- <link rel="stylesheet" href="_static/epub.css" type="text/css" />
- <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
- <script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
- <script type="text/javascript" src="_static/jquery.js"></script>
- <script type="text/javascript" src="_static/underscore.js"></script>
- <script type="text/javascript" src="_static/doctools.js"></script>
- <script type="text/javascript" src="_static/language_data.js"></script>
- </head><body>
-
- <div class="document">
- <div class="documentwrapper">
- <div class="bodywrapper">
- <div class="body" role="main">
-
- <div class="section" id="terms-of-service">
-<h1>Terms Of Service<a class="headerlink" href="#terms-of-service" title="Permalink to this headline">¶</a></h1>
-<p>Last Updated: 12.4.2019</p>
-<p>Welcome! Taler Systems SA (“we,†“our,†or “usâ€) provides a payment service
-through our Internet presence (collectively the “Servicesâ€). Before using our
-Services, please read the Terms of Service (the “Terms†or the “Agreementâ€)
-carefully.</p>
-<div class="section" id="overview">
-<h2>Overview<a class="headerlink" href="#overview" title="Permalink to this headline">¶</a></h2>
-<p>This section provides a brief summary of the highlights of this
-Agreement. Please note that when you accept this Agreement, you are accepting
-all of the terms and conditions and not just this section. We and possibly
-other third parties provide Internet services which interact with the Taler
-Wallet’s self-hosted personal payment application. When using the Taler Wallet
-to interact with our Services, you are agreeing to our Terms, so please read
-carefully.</p>
-<div class="section" id="highlights">
-<h3>Highlights:<a class="headerlink" href="#highlights" title="Permalink to this headline">¶</a></h3>
-<blockquote>
-<div><ul class="simple">
-<li>You are responsible for keeping the data in your Taler Wallet at all times
-under your control. Any losses arising from you not being in control of
-your private information are your problem.</li>
-<li>We will try to transfer funds we hold in escrow for our users to any legal
-recipient to the best of our ability within the limitations of the law and
-our implementation. However, the Services offered today are highly
-experimental and the set of recipients of funds is severely restricted.</li>
-<li>For our Services, we may charge transaction fees. The specific fee structure
-is provided based on the Taler protocol and should be shown to you when you
-withdraw electronic coins using a Taler Wallet. You agree and understand
-that the Taler protocol allows for the fee structure to change.</li>
-<li>You agree to not intentionally overwhelm our systems with requests and
-follow responsible disclosure if you find security issues in our services.</li>
-<li>We cannot be held accountable for our Services not being available due to
-circumstances beyond our control. If we modify or terminate our services,
-we will try to give you the opportunity to recover your funds. However,
-given the experimental state of the Services today, this may not be
-possible. You are strongly advised to limit your use of the Service
-to small-scale experiments expecting total loss of all funds.</li>
-</ul>
-</div></blockquote>
-<p>These terms outline approved uses of our Services. The Services and these
-Terms are still at an experimental stage. If you have any questions or
-comments related to this Agreement, please send us a message to
-<a class="reference external" href="mailto:legal&#37;&#52;&#48;taler-systems&#46;com">legal<span>&#64;</span>taler-systems<span>&#46;</span>com</a>. If you do not agree to this Agreement, you must not
-use our Services.</p>
-</div>
-</div>
-<div class="section" id="how-you-accept-this-policy">
-<h2>How you accept this policy<a class="headerlink" href="#how-you-accept-this-policy" title="Permalink to this headline">¶</a></h2>
-<p>By sending funds to us (to top-up your Taler Wallet), you acknowledge that you
-have read, understood, and agreed to these Terms. We reserve the right to
-change these Terms at any time. If you disagree with the change, we may in the
-future offer you with an easy option to recover your unspent funds. However,
-in the current experimental period you acknowledge that this feature is not
-yet available, resulting in your funds being lost unless you accept the new
-Terms. If you continue to use our Services other than to recover your unspent
-funds, your continued use of our Services following any such change will
-signify your acceptance to be bound by the then current Terms. Please check
-the effective date above to determine if there have been any changes since you
-have last reviewed these Terms.</p>
-</div>
-<div class="section" id="services">
-<h2>Services<a class="headerlink" href="#services" title="Permalink to this headline">¶</a></h2>
-<p>We will try to transfer funds that we hold in escrow for our users to any
-legal recipient to the best of our ability and within the limitations of the
-law and our implementation. However, the Services offered today are highly
-experimental and the set of recipients of funds is severely restricted. The
-Taler Wallet can be loaded by exchanging fiat currencies against electronic
-coins. We are providing this exchange service. Once your Taler Wallet is
-loaded with electronic coins they can be spent for purchases if the seller is
-accepting Taler as a means of payment. We are not guaranteeing that any seller
-is accepting Taler at all or a particular seller. The seller or recipient of
-deposits of electronic coins must specify the target account, as per the
-design of the Taler protocol. They are responsible for following the protocol
-and specifying the correct bank account, and are solely liable for any losses
-that may arise from specifying the wrong account. We will allow the government
-to link wire transfers to the underlying contract hash. It is the
-responsibility of recipients to preserve the full contracts and to pay
-whatever taxes and charges may be applicable. Technical issues may lead to
-situations where we are unable to make transfers at all or lead to incorrect
-transfers that cannot be reversed. We will only refuse to execute transfers if
-the transfers are prohibited by a competent legal authority and we are ordered
-to do so.</p>
-</div>
-<div class="section" id="fees">
-<h2>Fees<a class="headerlink" href="#fees" title="Permalink to this headline">¶</a></h2>
-<p>You agree to pay the fees for exchanges and withdrawals completed via the
-Taler Wallet (“Feesâ€) as defined by us, which we may change from time to
-time. With the exception of wire transfer fees, Taler transaction fees are set
-for any electronic coin at the time of withdrawal and fixed throughout the
-validity period of the respective electronic coin. Your wallet should obtain
-and display applicable fees when withdrawing funds. Fees for coins obtained as
-change may differ from the fees applicable to the original coin. Wire transfer
-fees that are independent from electronic coins may change annually. You
-authorize us to charge or deduct applicable fees owed in connection with
-deposits, exchanges and withdrawals following the rules of the Taler protocol.
-We reserve the right to provide different types of rewards to users either in
-the form of discount for our Services or in any other form at our discretion
-and without prior notice to you.</p>
-</div>
-<div class="section" id="eligibility">
-<h2>Eligibility<a class="headerlink" href="#eligibility" title="Permalink to this headline">¶</a></h2>
-<p>To be eligible to use our Services, you must be able to form legally binding
-contracts or have the permission of your legal guardian. By using our
-Services, you represent and warrant that you meet all eligibility requirements
-that we outline in these Terms.</p>
-</div>
-<div class="section" id="financial-self-responsibility">
-<h2>Financial self-responsibility<a class="headerlink" href="#financial-self-responsibility" title="Permalink to this headline">¶</a></h2>
-<p>You will be responsible for maintaining the availability, integrity and
-confidentiality of the data stored in your wallet. When you setup a Taler
-Wallet, you are strongly advised to follow the precautionary measures offered
-by the software to minimize the chances to losse access to or control over
-your Wallet data. We will not be liable for any loss or damage arising from
-your failure to comply with this paragraph.</p>
-</div>
-<div class="section" id="copyrights-and-trademarks">
-<h2>Copyrights and trademarks<a class="headerlink" href="#copyrights-and-trademarks" title="Permalink to this headline">¶</a></h2>
-<p>The Taler Wallet is released under the terms of the GNU General Public License
-(GNU GPL). You have the right to access, use, and share the Taler Wallet, in
-modified or unmodified form. However, the GPL is a strong copyleft license,
-which means that any derivative works must be distributed under the same
-license terms as the original software. If you have any questions, you should
-review the GNU GPL’s full terms and conditions at
-<a class="reference external" href="https://www.gnu.org/licenses/gpl-3.0.en.html">https://www.gnu.org/licenses/gpl-3.0.en.html</a>. “Taler†itself is a trademark
-of Taler Systems SA. You are welcome to use the name in relation to processing
-payments using the Taler protocol, assuming your use is compatible with an
-official release from the GNU Project that is not older than two years.</p>
-</div>
-<div class="section" id="your-use-of-our-services">
-<h2>Your use of our services<a class="headerlink" href="#your-use-of-our-services" title="Permalink to this headline">¶</a></h2>
-<p>When using our Services, you agree to not take any action that intentionally
-imposes an unreasonable load on our infrastructure. If you find security
-problems in our Services, you agree to first report them to
-<a class="reference external" href="mailto:security&#37;&#52;&#48;taler-systems&#46;com">security<span>&#64;</span>taler-systems<span>&#46;</span>com</a> and grant us the right to publish your report. We
-warrant that we will ourselves publicly disclose any issues reported within 3
-months, and that we will not prosecute anyone reporting security issues if
-they did not exploit the issue beyond a proof-of-concept, and followed the
-above responsible disclosure practice.</p>
-</div>
-<div class="section" id="limitation-of-liability-disclaimer-of-warranties">
-<h2>Limitation of liability &amp; disclaimer of warranties<a class="headerlink" href="#limitation-of-liability-disclaimer-of-warranties" title="Permalink to this headline">¶</a></h2>
-<p>You understand and agree that we have no control over, and no duty to take any
-action regarding: Failures, disruptions, errors, or delays in processing that
-you may experience while using our Services; The risk of failure of hardware,
-software, and Internet connections; The risk of malicious software being
-introduced or found in the software underlying the Taler Wallet; The risk that
-third parties may obtain unauthorized access to information stored within your
-Taler Wallet, including, but not limited to your Taler Wallet coins or backup
-encryption keys. You release us from all liability related to any losses,
-damages, or claims arising from:</p>
-<ol class="loweralpha simple">
-<li>user error such as forgotten passwords, incorrectly constructed
-transactions;</li>
-<li>server failure or data loss;</li>
-<li>unauthorized access to the Taler Wallet application;</li>
-<li>bugs or other errors in the Taler Wallet software; and</li>
-<li>any unauthorized third party activities, including, but not limited to,
-the use of viruses, phishing, brute forcing, or other means of attack
-against the Taler Wallet. We make no representations concerning any
-Third Party Content contained in or accessed through our Services.</li>
-</ol>
-<p>Any other terms, conditions, warranties, or representations associated with
-such content, are solely between you and such organizations and/or
-individuals.</p>
-</div>
-<div class="section" id="limitation-of-liability">
-<h2>Limitation of liability<a class="headerlink" href="#limitation-of-liability" title="Permalink to this headline">¶</a></h2>
-<p>To the fullest extent permitted by applicable law, in no event will we or any
-of our officers, directors, representatives, agents, servants, counsel,
-employees, consultants, lawyers, and other personnel authorized to act,
-acting, or purporting to act on our behalf (collectively the “Taler Partiesâ€)
-be liable to you under contract, tort, strict liability, negligence, or any
-other legal or equitable theory, for:</p>
-<ol class="loweralpha simple">
-<li>any lost profits, data loss, cost of procurement of substitute goods or
-services, or direct, indirect, incidental, special, punitive, compensatory,
-or consequential damages of any kind whatsoever resulting from:</li>
-</ol>
-<blockquote>
-<div><ol class="lowerroman simple">
-<li>your use of, or conduct in connection with, our services;</li>
-<li>any unauthorized use of your wallet and/or private key due to your
-failure to maintain the confidentiality of your wallet;</li>
-<li>any interruption or cessation of transmission to or from the services; or</li>
-<li>any bugs, viruses, trojan horses, or the like that are found in the Taler
-Wallet software or that may be transmitted to or through our services by
-any third party (regardless of the source of origination), or</li>
-</ol>
-</div></blockquote>
-<ol class="loweralpha simple" start="2">
-<li>any direct damages.</li>
-</ol>
-<p>These limitations apply regardless of legal theory, whether based on tort,
-strict liability, breach of contract, breach of warranty, or any other legal
-theory, and whether or not we were advised of the possibility of such
-damages. Some jurisdictions do not allow the exclusion or limitation of
-liability for consequential or incidental damages, so the above limitation may
-not apply to you.</p>
-</div>
-<div class="section" id="warranty-disclaimer">
-<h2>Warranty disclaimer<a class="headerlink" href="#warranty-disclaimer" title="Permalink to this headline">¶</a></h2>
-<p>Our services are provided “as is†and without warranty of any kind. To the
-maximum extent permitted by law, we disclaim all representations and
-warranties, express or implied, relating to the services and underlying
-software or any content on the services, whether provided or owned by us or by
-any third party, including without limitation, warranties of merchantability,
-fitness for a particular purpose, title, non-infringement, freedom from
-computer virus, and any implied warranties arising from course of dealing,
-course of performance, or usage in trade, all of which are expressly
-disclaimed. In addition, we do not represent or warrant that the content
-accessible via the services is accurate, complete, available, current, free of
-viruses or other harmful components, or that the results of using the services
-will meet your requirements. Some states do not allow the disclaimer of
-implied warranties, so the foregoing disclaimers may not apply to you. This
-paragraph gives you specific legal rights and you may also have other legal
-rights that vary from state to state.</p>
-</div>
-<div class="section" id="indemnity">
-<h2>Indemnity<a class="headerlink" href="#indemnity" title="Permalink to this headline">¶</a></h2>
-<p>To the extent permitted by applicable law, you agree to defend, indemnify, and
-hold harmless the Taler Parties from and against any and all claims, damages,
-obligations, losses, liabilities, costs or debt, and expenses (including, but
-not limited to, attorney’s fees) arising from: (a) your use of and access to
-the Services; (b) any feedback or submissions you provide to us concerning the
-Taler Wallet; (c) your violation of any term of this Agreement; or (d) your
-violation of any law, rule, or regulation, or the rights of any third party.</p>
-</div>
-<div class="section" id="time-limitation-on-claims">
-<h2>Time limitation on claims<a class="headerlink" href="#time-limitation-on-claims" title="Permalink to this headline">¶</a></h2>
-<p>You agree that any claim you may have arising out of or related to your
-relationship with us must be filed within one year after such claim arises,
-otherwise, your claim in permanently barred.</p>
-</div>
-<div class="section" id="governing-law">
-<h2>Governing law<a class="headerlink" href="#governing-law" title="Permalink to this headline">¶</a></h2>
-<p>No matter where you’re located, the laws of Switzerland will govern these
-Terms. If any provisions of these Terms are inconsistent with any applicable
-law, those provisions will be superseded or modified only to the extent such
-provisions are inconsistent. The parties agree to submit to the ordinary
-courts in Zurich, Switzerland for exclusive jurisdiction of any dispute
-arising out of or related to your use of the Services or your breach of these
-Terms.</p>
-</div>
-<div class="section" id="termination">
-<h2>Termination<a class="headerlink" href="#termination" title="Permalink to this headline">¶</a></h2>
-<p>In the event of termination concerning your use of our Services, your
-obligations under this Agreement will still continue.</p>
-</div>
-<div class="section" id="discontinuance-of-services">
-<h2>Discontinuance of services<a class="headerlink" href="#discontinuance-of-services" title="Permalink to this headline">¶</a></h2>
-<p>We may, in our sole discretion and without cost to you, with or without prior
-notice, and at any time, modify or discontinue, temporarily or permanently,
-any portion of our Services. We will use the Taler protocol’s provisions to
-notify Wallets if our Services are to be discontinued. It is your
-responsibility to ensure that the Taler Wallet is online at least once every
-three months to observe these notifications. We shall not be held responsible
-or liable for any loss of funds in the event that we discontinue or depreciate
-the Services and your Taler Wallet fails to transfer out the coins within a
-three months notification period.</p>
-</div>
-<div class="section" id="no-waiver">
-<h2>No waiver<a class="headerlink" href="#no-waiver" title="Permalink to this headline">¶</a></h2>
-<p>Our failure to exercise or delay in exercising any right, power, or privilege
-under this Agreement shall not operate as a waiver; nor shall any single or
-partial exercise of any right, power, or privilege preclude any other or
-further exercise thereof.</p>
-</div>
-<div class="section" id="severability">
-<h2>Severability<a class="headerlink" href="#severability" title="Permalink to this headline">¶</a></h2>
-<p>If it turns out that any part of this Agreement is invalid, void, or for any
-reason unenforceable, that term will be deemed severable and limited or
-eliminated to the minimum extent necessary.</p>
-</div>
-<div class="section" id="force-majeure">
-<h2>Force majeure<a class="headerlink" href="#force-majeure" title="Permalink to this headline">¶</a></h2>
-<p>We shall not be held liable for any delays, failure in performance, or
-interruptions of service which result directly or indirectly from any cause or
-condition beyond our reasonable control, including but not limited to: any
-delay or failure due to any act of God, act of civil or military authorities,
-act of terrorism, civil disturbance, war, strike or other labor dispute, fire,
-interruption in telecommunications or Internet services or network provider
-services, failure of equipment and/or software, other catastrophe, or any
-other occurrence which is beyond our reasonable control and shall not affect
-the validity and enforceability of any remaining provisions.</p>
-</div>
-<div class="section" id="assignment">
-<h2>Assignment<a class="headerlink" href="#assignment" title="Permalink to this headline">¶</a></h2>
-<p>You agree that we may assign any of our rights and/or transfer, sub-contract,
-or delegate any of our obligations under these Terms.</p>
-</div>
-<div class="section" id="entire-agreement">
-<h2>Entire agreement<a class="headerlink" href="#entire-agreement" title="Permalink to this headline">¶</a></h2>
-<p>This Agreement sets forth the entire understanding and agreement as to the
-subject matter hereof and supersedes any and all prior discussions,
-agreements, and understandings of any kind (including, without limitation, any
-prior versions of this Agreement) and every nature between us. Except as
-provided for above, any modification to this Agreement must be in writing and
-must be signed by both parties.</p>
-</div>
-<div class="section" id="questions-or-comments">
-<h2>Questions or comments<a class="headerlink" href="#questions-or-comments" title="Permalink to this headline">¶</a></h2>
-<p>We welcome comments, questions, concerns, or suggestions. Please send us a
-message on our contact page at <a class="reference external" href="mailto:legal&#37;&#52;&#48;taler-systems&#46;com">legal<span>&#64;</span>taler-systems<span>&#46;</span>com</a>.</p>
-</div>
-</div>
-
-
- </div>
- </div>
- </div>
- <div class="clearer"></div>
- </div>
- </body>
-</html> \ No newline at end of file
diff --git a/contrib/tos/en/0.pdf b/contrib/tos/en/0.pdf
deleted file mode 100644
index ce3f013c5..000000000
--- a/contrib/tos/en/0.pdf
+++ /dev/null
Binary files differ
diff --git a/contrib/tos/en/0.txt b/contrib/tos/en/0.txt
deleted file mode 100644
index ce0893faf..000000000
--- a/contrib/tos/en/0.txt
+++ /dev/null
@@ -1,381 +0,0 @@
-Terms Of Service
-****************
-
-Last Updated: 12.4.2019
-
-Welcome! Taler Systems SA (“we,†“our,†or “usâ€) provides a payment
-service through our Internet presence (collectively the “Servicesâ€).
-Before using our Services, please read the Terms of Service (the
-“Terms†or the “Agreementâ€) carefully.
-
-
-Overview
-========
-
-This section provides a brief summary of the highlights of this
-Agreement. Please note that when you accept this Agreement, you are
-accepting all of the terms and conditions and not just this section.
-We and possibly other third parties provide Internet services which
-interact with the Taler Wallet’s self-hosted personal payment
-application. When using the Taler Wallet to interact with our
-Services, you are agreeing to our Terms, so please read carefully.
-
-
-Highlights:
------------
-
- * You are responsible for keeping the data in your Taler Wallet at
- all times under your control. Any losses arising from you not
- being in control of your private information are your problem.
-
- * We will try to transfer funds we hold in escrow for our users to
- any legal recipient to the best of our ability within the
- limitations of the law and our implementation. However, the
- Services offered today are highly experimental and the set of
- recipients of funds is severely restricted.
-
- * For our Services, we may charge transaction fees. The specific
- fee structure is provided based on the Taler protocol and should
- be shown to you when you withdraw electronic coins using a Taler
- Wallet. You agree and understand that the Taler protocol allows
- for the fee structure to change.
-
- * You agree to not intentionally overwhelm our systems with
- requests and follow responsible disclosure if you find security
- issues in our services.
-
- * We cannot be held accountable for our Services not being
- available due to circumstances beyond our control. If we modify
- or terminate our services, we will try to give you the
- opportunity to recover your funds. However, given the
- experimental state of the Services today, this may not be
- possible. You are strongly advised to limit your use of the
- Service to small-scale experiments expecting total loss of all
- funds.
-
-These terms outline approved uses of our Services. The Services and
-these Terms are still at an experimental stage. If you have any
-questions or comments related to this Agreement, please send us a
-message to legal@taler-systems.com. If you do not agree to this
-Agreement, you must not use our Services.
-
-
-How you accept this policy
-==========================
-
-By sending funds to us (to top-up your Taler Wallet), you acknowledge
-that you have read, understood, and agreed to these Terms. We reserve
-the right to change these Terms at any time. If you disagree with the
-change, we may in the future offer you with an easy option to recover
-your unspent funds. However, in the current experimental period you
-acknowledge that this feature is not yet available, resulting in your
-funds being lost unless you accept the new Terms. If you continue to
-use our Services other than to recover your unspent funds, your
-continued use of our Services following any such change will signify
-your acceptance to be bound by the then current Terms. Please check
-the effective date above to determine if there have been any changes
-since you have last reviewed these Terms.
-
-
-Services
-========
-
-We will try to transfer funds that we hold in escrow for our users to
-any legal recipient to the best of our ability and within the
-limitations of the law and our implementation. However, the Services
-offered today are highly experimental and the set of recipients of
-funds is severely restricted. The Taler Wallet can be loaded by
-exchanging fiat currencies against electronic coins. We are providing
-this exchange service. Once your Taler Wallet is loaded with
-electronic coins they can be spent for purchases if the seller is
-accepting Taler as a means of payment. We are not guaranteeing that
-any seller is accepting Taler at all or a particular seller. The
-seller or recipient of deposits of electronic coins must specify the
-target account, as per the design of the Taler protocol. They are
-responsible for following the protocol and specifying the correct bank
-account, and are solely liable for any losses that may arise from
-specifying the wrong account. We will allow the government to link
-wire transfers to the underlying contract hash. It is the
-responsibility of recipients to preserve the full contracts and to pay
-whatever taxes and charges may be applicable. Technical issues may
-lead to situations where we are unable to make transfers at all or
-lead to incorrect transfers that cannot be reversed. We will only
-refuse to execute transfers if the transfers are prohibited by a
-competent legal authority and we are ordered to do so.
-
-
-Fees
-====
-
-You agree to pay the fees for exchanges and withdrawals completed via
-the Taler Wallet ("Fees") as defined by us, which we may change from
-time to time. With the exception of wire transfer fees, Taler
-transaction fees are set for any electronic coin at the time of
-withdrawal and fixed throughout the validity period of the respective
-electronic coin. Your wallet should obtain and display applicable fees
-when withdrawing funds. Fees for coins obtained as change may differ
-from the fees applicable to the original coin. Wire transfer fees that
-are independent from electronic coins may change annually. You
-authorize us to charge or deduct applicable fees owed in connection
-with deposits, exchanges and withdrawals following the rules of the
-Taler protocol. We reserve the right to provide different types of
-rewards to users either in the form of discount for our Services or in
-any other form at our discretion and without prior notice to you.
-
-
-Eligibility
-===========
-
-To be eligible to use our Services, you must be able to form legally
-binding contracts or have the permission of your legal guardian. By
-using our Services, you represent and warrant that you meet all
-eligibility requirements that we outline in these Terms.
-
-
-Financial self-responsibility
-=============================
-
-You will be responsible for maintaining the availability, integrity
-and confidentiality of the data stored in your wallet. When you setup
-a Taler Wallet, you are strongly advised to follow the precautionary
-measures offered by the software to minimize the chances to losse
-access to or control over your Wallet data. We will not be liable for
-any loss or damage arising from your failure to comply with this
-paragraph.
-
-
-Copyrights and trademarks
-=========================
-
-The Taler Wallet is released under the terms of the GNU General Public
-License (GNU GPL). You have the right to access, use, and share the
-Taler Wallet, in modified or unmodified form. However, the GPL is a
-strong copyleft license, which means that any derivative works must be
-distributed under the same license terms as the original software. If
-you have any questions, you should review the GNU GPL’s full terms and
-conditions at https://www.gnu.org/licenses/gpl-3.0.en.html. “Talerâ€
-itself is a trademark of Taler Systems SA. You are welcome to use the
-name in relation to processing payments using the Taler protocol,
-assuming your use is compatible with an official release from the GNU
-Project that is not older than two years.
-
-
-Your use of our services
-========================
-
-When using our Services, you agree to not take any action that
-intentionally imposes an unreasonable load on our infrastructure. If
-you find security problems in our Services, you agree to first report
-them to security@taler-systems.com and grant us the right to publish
-your report. We warrant that we will ourselves publicly disclose any
-issues reported within 3 months, and that we will not prosecute anyone
-reporting security issues if they did not exploit the issue beyond a
-proof-of-concept, and followed the above responsible disclosure
-practice.
-
-
-Limitation of liability & disclaimer of warranties
-==================================================
-
-You understand and agree that we have no control over, and no duty to
-take any action regarding: Failures, disruptions, errors, or delays in
-processing that you may experience while using our Services; The risk
-of failure of hardware, software, and Internet connections; The risk
-of malicious software being introduced or found in the software
-underlying the Taler Wallet; The risk that third parties may obtain
-unauthorized access to information stored within your Taler Wallet,
-including, but not limited to your Taler Wallet coins or backup
-encryption keys. You release us from all liability related to any
-losses, damages, or claims arising from:
-
-1. user error such as forgotten passwords, incorrectly constructed
- transactions;
-
-2. server failure or data loss;
-
-3. unauthorized access to the Taler Wallet application;
-
-4. bugs or other errors in the Taler Wallet software; and
-
-5. any unauthorized third party activities, including, but not limited
- to, the use of viruses, phishing, brute forcing, or other means of
- attack against the Taler Wallet. We make no representations
- concerning any Third Party Content contained in or accessed through
- our Services.
-
-Any other terms, conditions, warranties, or representations associated
-with such content, are solely between you and such organizations
-and/or individuals.
-
-
-Limitation of liability
-=======================
-
-To the fullest extent permitted by applicable law, in no event will we
-or any of our officers, directors, representatives, agents, servants,
-counsel, employees, consultants, lawyers, and other personnel
-authorized to act, acting, or purporting to act on our behalf
-(collectively the “Taler Partiesâ€) be liable to you under contract,
-tort, strict liability, negligence, or any other legal or equitable
-theory, for:
-
-1. any lost profits, data loss, cost of procurement of substitute
- goods or services, or direct, indirect, incidental, special,
- punitive, compensatory, or consequential damages of any kind
- whatsoever resulting from:
-
- 1. your use of, or conduct in connection with, our services;
-
- 2. any unauthorized use of your wallet and/or private key due to
- your failure to maintain the confidentiality of your wallet;
-
- 3. any interruption or cessation of transmission to or from the
- services; or
-
- 4. any bugs, viruses, trojan horses, or the like that are found in
- the Taler Wallet software or that may be transmitted to or
- through our services by any third party (regardless of the
- source of origination), or
-
-2. any direct damages.
-
-These limitations apply regardless of legal theory, whether based on
-tort, strict liability, breach of contract, breach of warranty, or any
-other legal theory, and whether or not we were advised of the
-possibility of such damages. Some jurisdictions do not allow the
-exclusion or limitation of liability for consequential or incidental
-damages, so the above limitation may not apply to you.
-
-
-Warranty disclaimer
-===================
-
-Our services are provided "as is" and without warranty of any kind. To
-the maximum extent permitted by law, we disclaim all representations
-and warranties, express or implied, relating to the services and
-underlying software or any content on the services, whether provided
-or owned by us or by any third party, including without limitation,
-warranties of merchantability, fitness for a particular purpose,
-title, non-infringement, freedom from computer virus, and any implied
-warranties arising from course of dealing, course of performance, or
-usage in trade, all of which are expressly disclaimed. In addition, we
-do not represent or warrant that the content accessible via the
-services is accurate, complete, available, current, free of viruses or
-other harmful components, or that the results of using the services
-will meet your requirements. Some states do not allow the disclaimer
-of implied warranties, so the foregoing disclaimers may not apply to
-you. This paragraph gives you specific legal rights and you may also
-have other legal rights that vary from state to state.
-
-
-Indemnity
-=========
-
-To the extent permitted by applicable law, you agree to defend,
-indemnify, and hold harmless the Taler Parties from and against any
-and all claims, damages, obligations, losses, liabilities, costs or
-debt, and expenses (including, but not limited to, attorney’s fees)
-arising from: (a) your use of and access to the Services; (b) any
-feedback or submissions you provide to us concerning the Taler Wallet;
-(c) your violation of any term of this Agreement; or (d) your
-violation of any law, rule, or regulation, or the rights of any third
-party.
-
-
-Time limitation on claims
-=========================
-
-You agree that any claim you may have arising out of or related to
-your relationship with us must be filed within one year after such
-claim arises, otherwise, your claim in permanently barred.
-
-
-Governing law
-=============
-
-No matter where you’re located, the laws of Switzerland will govern
-these Terms. If any provisions of these Terms are inconsistent with
-any applicable law, those provisions will be superseded or modified
-only to the extent such provisions are inconsistent. The parties agree
-to submit to the ordinary courts in Zurich, Switzerland for exclusive
-jurisdiction of any dispute arising out of or related to your use of
-the Services or your breach of these Terms.
-
-
-Termination
-===========
-
-In the event of termination concerning your use of our Services, your
-obligations under this Agreement will still continue.
-
-
-Discontinuance of services
-==========================
-
-We may, in our sole discretion and without cost to you, with or
-without prior notice, and at any time, modify or discontinue,
-temporarily or permanently, any portion of our Services. We will use
-the Taler protocol’s provisions to notify Wallets if our Services are
-to be discontinued. It is your responsibility to ensure that the Taler
-Wallet is online at least once every three months to observe these
-notifications. We shall not be held responsible or liable for any loss
-of funds in the event that we discontinue or depreciate the Services
-and your Taler Wallet fails to transfer out the coins within a three
-months notification period.
-
-
-No waiver
-=========
-
-Our failure to exercise or delay in exercising any right, power, or
-privilege under this Agreement shall not operate as a waiver; nor
-shall any single or partial exercise of any right, power, or privilege
-preclude any other or further exercise thereof.
-
-
-Severability
-============
-
-If it turns out that any part of this Agreement is invalid, void, or
-for any reason unenforceable, that term will be deemed severable and
-limited or eliminated to the minimum extent necessary.
-
-
-Force majeure
-=============
-
-We shall not be held liable for any delays, failure in performance, or
-interruptions of service which result directly or indirectly from any
-cause or condition beyond our reasonable control, including but not
-limited to: any delay or failure due to any act of God, act of civil
-or military authorities, act of terrorism, civil disturbance, war,
-strike or other labor dispute, fire, interruption in
-telecommunications or Internet services or network provider services,
-failure of equipment and/or software, other catastrophe, or any other
-occurrence which is beyond our reasonable control and shall not affect
-the validity and enforceability of any remaining provisions.
-
-
-Assignment
-==========
-
-You agree that we may assign any of our rights and/or transfer, sub-
-contract, or delegate any of our obligations under these Terms.
-
-
-Entire agreement
-================
-
-This Agreement sets forth the entire understanding and agreement as to
-the subject matter hereof and supersedes any and all prior
-discussions, agreements, and understandings of any kind (including,
-without limitation, any prior versions of this Agreement) and every
-nature between us. Except as provided for above, any modification to
-this Agreement must be in writing and must be signed by both parties.
-
-
-Questions or comments
-=====================
-
-We welcome comments, questions, concerns, or suggestions. Please send
-us a message on our contact page at legal@taler-systems.com.
diff --git a/contrib/tos/en/0.xml b/contrib/tos/en/0.xml
deleted file mode 100644
index 75974dc4d..000000000
--- a/contrib/tos/en/0.xml
+++ /dev/null
@@ -1,344 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE document PUBLIC "+//IDN docutils.sourceforge.net//DTD Docutils Generic//EN//XML" "http://docutils.sourceforge.net/docs/ref/docutils.dtd">
-<!-- Generated by Docutils 0.14 -->
-<document source="/home/grothoff/research/taler/exchange/contrib/tos/tos.rst">
- <section ids="terms-of-service" names="terms\ of\ service">
- <title>Terms Of Service</title>
- <paragraph>Last Updated: 12.4.2019</paragraph>
- <paragraph>Welcome! Taler Systems SA (“we,†“our,†or “usâ€) provides a payment service
- through our Internet presence (collectively the “Servicesâ€). Before using our
- Services, please read the Terms of Service (the “Terms†or the “Agreementâ€)
- carefully.</paragraph>
- <section ids="overview" names="overview">
- <title>Overview</title>
- <paragraph>This section provides a brief summary of the highlights of this
- Agreement. Please note that when you accept this Agreement, you are accepting
- all of the terms and conditions and not just this section. We and possibly
- other third parties provide Internet services which interact with the Taler
- Wallet’s self-hosted personal payment application. When using the Taler Wallet
- to interact with our Services, you are agreeing to our Terms, so please read
- carefully.</paragraph>
- <section ids="highlights" names="highlights:">
- <title>Highlights:</title>
- <block_quote>
- <bullet_list bullet="•">
- <list_item>
- <paragraph>You are responsible for keeping the data in your Taler Wallet at all times
- under your control. Any losses arising from you not being in control of
- your private information are your problem.</paragraph>
- </list_item>
- <list_item>
- <paragraph>We will try to transfer funds we hold in escrow for our users to any legal
- recipient to the best of our ability within the limitations of the law and
- our implementation. However, the Services offered today are highly
- experimental and the set of recipients of funds is severely restricted.</paragraph>
- </list_item>
- <list_item>
- <paragraph>For our Services, we may charge transaction fees. The specific fee structure
- is provided based on the Taler protocol and should be shown to you when you
- withdraw electronic coins using a Taler Wallet. You agree and understand
- that the Taler protocol allows for the fee structure to change.</paragraph>
- </list_item>
- <list_item>
- <paragraph>You agree to not intentionally overwhelm our systems with requests and
- follow responsible disclosure if you find security issues in our services.</paragraph>
- </list_item>
- <list_item>
- <paragraph>We cannot be held accountable for our Services not being available due to
- circumstances beyond our control. If we modify or terminate our services,
- we will try to give you the opportunity to recover your funds. However,
- given the experimental state of the Services today, this may not be
- possible. You are strongly advised to limit your use of the Service
- to small-scale experiments expecting total loss of all funds.</paragraph>
- </list_item>
- </bullet_list>
- </block_quote>
- <paragraph>These terms outline approved uses of our Services. The Services and these
- Terms are still at an experimental stage. If you have any questions or
- comments related to this Agreement, please send us a message to
- <reference refuri="mailto:legal@taler-systems.com">legal@taler-systems.com</reference>. If you do not agree to this Agreement, you must not
- use our Services.</paragraph>
- </section>
- </section>
- <section ids="how-you-accept-this-policy" names="how\ you\ accept\ this\ policy">
- <title>How you accept this policy</title>
- <paragraph>By sending funds to us (to top-up your Taler Wallet), you acknowledge that you
- have read, understood, and agreed to these Terms. We reserve the right to
- change these Terms at any time. If you disagree with the change, we may in the
- future offer you with an easy option to recover your unspent funds. However,
- in the current experimental period you acknowledge that this feature is not
- yet available, resulting in your funds being lost unless you accept the new
- Terms. If you continue to use our Services other than to recover your unspent
- funds, your continued use of our Services following any such change will
- signify your acceptance to be bound by the then current Terms. Please check
- the effective date above to determine if there have been any changes since you
- have last reviewed these Terms.</paragraph>
- </section>
- <section ids="services" names="services">
- <title>Services</title>
- <paragraph>We will try to transfer funds that we hold in escrow for our users to any
- legal recipient to the best of our ability and within the limitations of the
- law and our implementation. However, the Services offered today are highly
- experimental and the set of recipients of funds is severely restricted. The
- Taler Wallet can be loaded by exchanging fiat currencies against electronic
- coins. We are providing this exchange service. Once your Taler Wallet is
- loaded with electronic coins they can be spent for purchases if the seller is
- accepting Taler as a means of payment. We are not guaranteeing that any seller
- is accepting Taler at all or a particular seller. The seller or recipient of
- deposits of electronic coins must specify the target account, as per the
- design of the Taler protocol. They are responsible for following the protocol
- and specifying the correct bank account, and are solely liable for any losses
- that may arise from specifying the wrong account. We will allow the government
- to link wire transfers to the underlying contract hash. It is the
- responsibility of recipients to preserve the full contracts and to pay
- whatever taxes and charges may be applicable. Technical issues may lead to
- situations where we are unable to make transfers at all or lead to incorrect
- transfers that cannot be reversed. We will only refuse to execute transfers if
- the transfers are prohibited by a competent legal authority and we are ordered
- to do so.</paragraph>
- </section>
- <section ids="fees" names="fees">
- <title>Fees</title>
- <paragraph>You agree to pay the fees for exchanges and withdrawals completed via the
- Taler Wallet (“Feesâ€) as defined by us, which we may change from time to
- time. With the exception of wire transfer fees, Taler transaction fees are set
- for any electronic coin at the time of withdrawal and fixed throughout the
- validity period of the respective electronic coin. Your wallet should obtain
- and display applicable fees when withdrawing funds. Fees for coins obtained as
- change may differ from the fees applicable to the original coin. Wire transfer
- fees that are independent from electronic coins may change annually. You
- authorize us to charge or deduct applicable fees owed in connection with
- deposits, exchanges and withdrawals following the rules of the Taler protocol.
- We reserve the right to provide different types of rewards to users either in
- the form of discount for our Services or in any other form at our discretion
- and without prior notice to you.</paragraph>
- </section>
- <section ids="eligibility" names="eligibility">
- <title>Eligibility</title>
- <paragraph>To be eligible to use our Services, you must be able to form legally binding
- contracts or have the permission of your legal guardian. By using our
- Services, you represent and warrant that you meet all eligibility requirements
- that we outline in these Terms.</paragraph>
- </section>
- <section ids="financial-self-responsibility" names="financial\ self-responsibility">
- <title>Financial self-responsibility</title>
- <paragraph>You will be responsible for maintaining the availability, integrity and
- confidentiality of the data stored in your wallet. When you setup a Taler
- Wallet, you are strongly advised to follow the precautionary measures offered
- by the software to minimize the chances to losse access to or control over
- your Wallet data. We will not be liable for any loss or damage arising from
- your failure to comply with this paragraph.</paragraph>
- </section>
- <section ids="copyrights-and-trademarks" names="copyrights\ and\ trademarks">
- <title>Copyrights and trademarks</title>
- <paragraph>The Taler Wallet is released under the terms of the GNU General Public License
- (GNU GPL). You have the right to access, use, and share the Taler Wallet, in
- modified or unmodified form. However, the GPL is a strong copyleft license,
- which means that any derivative works must be distributed under the same
- license terms as the original software. If you have any questions, you should
- review the GNU GPL’s full terms and conditions at
- <reference refuri="https://www.gnu.org/licenses/gpl-3.0.en.html">https://www.gnu.org/licenses/gpl-3.0.en.html</reference>. “Taler†itself is a trademark
- of Taler Systems SA. You are welcome to use the name in relation to processing
- payments using the Taler protocol, assuming your use is compatible with an
- official release from the GNU Project that is not older than two years.</paragraph>
- </section>
- <section ids="your-use-of-our-services" names="your\ use\ of\ our\ services">
- <title>Your use of our services</title>
- <paragraph>When using our Services, you agree to not take any action that intentionally
- imposes an unreasonable load on our infrastructure. If you find security
- problems in our Services, you agree to first report them to
- <reference refuri="mailto:security@taler-systems.com">security@taler-systems.com</reference> and grant us the right to publish your report. We
- warrant that we will ourselves publicly disclose any issues reported within 3
- months, and that we will not prosecute anyone reporting security issues if
- they did not exploit the issue beyond a proof-of-concept, and followed the
- above responsible disclosure practice.</paragraph>
- </section>
- <section ids="limitation-of-liability-disclaimer-of-warranties" names="limitation\ of\ liability\ &amp;\ disclaimer\ of\ warranties">
- <title>Limitation of liability &amp; disclaimer of warranties</title>
- <paragraph>You understand and agree that we have no control over, and no duty to take any
- action regarding: Failures, disruptions, errors, or delays in processing that
- you may experience while using our Services; The risk of failure of hardware,
- software, and Internet connections; The risk of malicious software being
- introduced or found in the software underlying the Taler Wallet; The risk that
- third parties may obtain unauthorized access to information stored within your
- Taler Wallet, including, but not limited to your Taler Wallet coins or backup
- encryption keys. You release us from all liability related to any losses,
- damages, or claims arising from:</paragraph>
- <enumerated_list enumtype="loweralpha" prefix="(" suffix=")">
- <list_item>
- <paragraph>user error such as forgotten passwords, incorrectly constructed
- transactions;</paragraph>
- </list_item>
- <list_item>
- <paragraph>server failure or data loss;</paragraph>
- </list_item>
- <list_item>
- <paragraph>unauthorized access to the Taler Wallet application;</paragraph>
- </list_item>
- <list_item>
- <paragraph>bugs or other errors in the Taler Wallet software; and</paragraph>
- </list_item>
- <list_item>
- <paragraph>any unauthorized third party activities, including, but not limited to,
- the use of viruses, phishing, brute forcing, or other means of attack
- against the Taler Wallet. We make no representations concerning any
- Third Party Content contained in or accessed through our Services.</paragraph>
- </list_item>
- </enumerated_list>
- <paragraph>Any other terms, conditions, warranties, or representations associated with
- such content, are solely between you and such organizations and/or
- individuals.</paragraph>
- </section>
- <section ids="limitation-of-liability" names="limitation\ of\ liability">
- <title>Limitation of liability</title>
- <paragraph>To the fullest extent permitted by applicable law, in no event will we or any
- of our officers, directors, representatives, agents, servants, counsel,
- employees, consultants, lawyers, and other personnel authorized to act,
- acting, or purporting to act on our behalf (collectively the “Taler Partiesâ€)
- be liable to you under contract, tort, strict liability, negligence, or any
- other legal or equitable theory, for:</paragraph>
- <enumerated_list enumtype="loweralpha" prefix="(" suffix=")">
- <list_item>
- <paragraph>any lost profits, data loss, cost of procurement of substitute goods or
- services, or direct, indirect, incidental, special, punitive, compensatory,
- or consequential damages of any kind whatsoever resulting from:</paragraph>
- </list_item>
- </enumerated_list>
- <block_quote>
- <enumerated_list enumtype="lowerroman" prefix="(" suffix=")">
- <list_item>
- <paragraph>your use of, or conduct in connection with, our services;</paragraph>
- </list_item>
- <list_item>
- <paragraph>any unauthorized use of your wallet and/or private key due to your
- failure to maintain the confidentiality of your wallet;</paragraph>
- </list_item>
- <list_item>
- <paragraph>any interruption or cessation of transmission to or from the services; or</paragraph>
- </list_item>
- <list_item>
- <paragraph>any bugs, viruses, trojan horses, or the like that are found in the Taler
- Wallet software or that may be transmitted to or through our services by
- any third party (regardless of the source of origination), or</paragraph>
- </list_item>
- </enumerated_list>
- </block_quote>
- <enumerated_list enumtype="loweralpha" prefix="(" start="2" suffix=")">
- <list_item>
- <paragraph>any direct damages.</paragraph>
- </list_item>
- </enumerated_list>
- <paragraph>These limitations apply regardless of legal theory, whether based on tort,
- strict liability, breach of contract, breach of warranty, or any other legal
- theory, and whether or not we were advised of the possibility of such
- damages. Some jurisdictions do not allow the exclusion or limitation of
- liability for consequential or incidental damages, so the above limitation may
- not apply to you.</paragraph>
- </section>
- <section ids="warranty-disclaimer" names="warranty\ disclaimer">
- <title>Warranty disclaimer</title>
- <paragraph>Our services are provided “as is†and without warranty of any kind. To the
- maximum extent permitted by law, we disclaim all representations and
- warranties, express or implied, relating to the services and underlying
- software or any content on the services, whether provided or owned by us or by
- any third party, including without limitation, warranties of merchantability,
- fitness for a particular purpose, title, non-infringement, freedom from
- computer virus, and any implied warranties arising from course of dealing,
- course of performance, or usage in trade, all of which are expressly
- disclaimed. In addition, we do not represent or warrant that the content
- accessible via the services is accurate, complete, available, current, free of
- viruses or other harmful components, or that the results of using the services
- will meet your requirements. Some states do not allow the disclaimer of
- implied warranties, so the foregoing disclaimers may not apply to you. This
- paragraph gives you specific legal rights and you may also have other legal
- rights that vary from state to state.</paragraph>
- </section>
- <section ids="indemnity" names="indemnity">
- <title>Indemnity</title>
- <paragraph>To the extent permitted by applicable law, you agree to defend, indemnify, and
- hold harmless the Taler Parties from and against any and all claims, damages,
- obligations, losses, liabilities, costs or debt, and expenses (including, but
- not limited to, attorney’s fees) arising from: (a) your use of and access to
- the Services; (b) any feedback or submissions you provide to us concerning the
- Taler Wallet; (c) your violation of any term of this Agreement; or (d) your
- violation of any law, rule, or regulation, or the rights of any third party.</paragraph>
- </section>
- <section ids="time-limitation-on-claims" names="time\ limitation\ on\ claims">
- <title>Time limitation on claims</title>
- <paragraph>You agree that any claim you may have arising out of or related to your
- relationship with us must be filed within one year after such claim arises,
- otherwise, your claim in permanently barred.</paragraph>
- </section>
- <section ids="governing-law" names="governing\ law">
- <title>Governing law</title>
- <paragraph>No matter where you’re located, the laws of Switzerland will govern these
- Terms. If any provisions of these Terms are inconsistent with any applicable
- law, those provisions will be superseded or modified only to the extent such
- provisions are inconsistent. The parties agree to submit to the ordinary
- courts in Zurich, Switzerland for exclusive jurisdiction of any dispute
- arising out of or related to your use of the Services or your breach of these
- Terms.</paragraph>
- </section>
- <section ids="termination" names="termination">
- <title>Termination</title>
- <paragraph>In the event of termination concerning your use of our Services, your
- obligations under this Agreement will still continue.</paragraph>
- </section>
- <section ids="discontinuance-of-services" names="discontinuance\ of\ services">
- <title>Discontinuance of services</title>
- <paragraph>We may, in our sole discretion and without cost to you, with or without prior
- notice, and at any time, modify or discontinue, temporarily or permanently,
- any portion of our Services. We will use the Taler protocol’s provisions to
- notify Wallets if our Services are to be discontinued. It is your
- responsibility to ensure that the Taler Wallet is online at least once every
- three months to observe these notifications. We shall not be held responsible
- or liable for any loss of funds in the event that we discontinue or depreciate
- the Services and your Taler Wallet fails to transfer out the coins within a
- three months notification period.</paragraph>
- </section>
- <section ids="no-waiver" names="no\ waiver">
- <title>No waiver</title>
- <paragraph>Our failure to exercise or delay in exercising any right, power, or privilege
- under this Agreement shall not operate as a waiver; nor shall any single or
- partial exercise of any right, power, or privilege preclude any other or
- further exercise thereof.</paragraph>
- </section>
- <section ids="severability" names="severability">
- <title>Severability</title>
- <paragraph>If it turns out that any part of this Agreement is invalid, void, or for any
- reason unenforceable, that term will be deemed severable and limited or
- eliminated to the minimum extent necessary.</paragraph>
- </section>
- <section ids="force-majeure" names="force\ majeure">
- <title>Force majeure</title>
- <paragraph>We shall not be held liable for any delays, failure in performance, or
- interruptions of service which result directly or indirectly from any cause or
- condition beyond our reasonable control, including but not limited to: any
- delay or failure due to any act of God, act of civil or military authorities,
- act of terrorism, civil disturbance, war, strike or other labor dispute, fire,
- interruption in telecommunications or Internet services or network provider
- services, failure of equipment and/or software, other catastrophe, or any
- other occurrence which is beyond our reasonable control and shall not affect
- the validity and enforceability of any remaining provisions.</paragraph>
- </section>
- <section ids="assignment" names="assignment">
- <title>Assignment</title>
- <paragraph>You agree that we may assign any of our rights and/or transfer, sub-contract,
- or delegate any of our obligations under these Terms.</paragraph>
- </section>
- <section ids="entire-agreement" names="entire\ agreement">
- <title>Entire agreement</title>
- <paragraph>This Agreement sets forth the entire understanding and agreement as to the
- subject matter hereof and supersedes any and all prior discussions,
- agreements, and understandings of any kind (including, without limitation, any
- prior versions of this Agreement) and every nature between us. Except as
- provided for above, any modification to this Agreement must be in writing and
- must be signed by both parties.</paragraph>
- </section>
- <section ids="questions-or-comments" names="questions\ or\ comments">
- <title>Questions or comments</title>
- <paragraph>We welcome comments, questions, concerns, or suggestions. Please send us a
- message on our contact page at <reference refuri="mailto:legal@taler-systems.com">legal@taler-systems.com</reference>.</paragraph>
- </section>
- </section>
-</document>
diff --git a/contrib/tos/locale/de/LC_MESSAGES/tos.po b/contrib/tos/locale/de/LC_MESSAGES/tos.po
deleted file mode 100644
index 21aa9c88a..000000000
--- a/contrib/tos/locale/de/LC_MESSAGES/tos.po
+++ /dev/null
@@ -1,517 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) 2014-2020 Taler Systems SA (GPLv3+ or GFDL 1.3+)
-# This file is distributed under the same license as the tos package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: tos 0\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-02-07 01:00+0100\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.6.0\n"
-
-#: ../../tos.rst:2
-msgid "Terms Of Service"
-msgstr ""
-
-#: ../../tos.rst:4
-msgid "Last Updated: 12.4.2019"
-msgstr ""
-
-#: ../../tos.rst:6
-msgid ""
-"Welcome! Taler Systems SA (“we,†“our,†or “usâ€) provides a payment "
-"service through our Internet presence (collectively the “Servicesâ€). "
-"Before using our Services, please read the Terms of Service (the “Terms†"
-"or the “Agreementâ€) carefully."
-msgstr ""
-
-#: ../../tos.rst:12
-msgid "Overview"
-msgstr ""
-
-#: ../../tos.rst:14
-msgid ""
-"This section provides a brief summary of the highlights of this "
-"Agreement. Please note that when you accept this Agreement, you are "
-"accepting all of the terms and conditions and not just this section. We "
-"and possibly other third parties provide Internet services which interact"
-" with the Taler Wallet’s self-hosted personal payment application. When "
-"using the Taler Wallet to interact with our Services, you are agreeing to"
-" our Terms, so please read carefully."
-msgstr ""
-
-#: ../../tos.rst:23
-msgid "Highlights:"
-msgstr ""
-
-#: ../../tos.rst:25
-msgid ""
-"You are responsible for keeping the data in your Taler Wallet at all "
-"times under your control. Any losses arising from you not being in "
-"control of your private information are your problem."
-msgstr ""
-
-#: ../../tos.rst:28
-msgid ""
-"We will try to transfer funds we hold in escrow for our users to any "
-"legal recipient to the best of our ability within the limitations of the "
-"law and our implementation. However, the Services offered today are "
-"highly experimental and the set of recipients of funds is severely "
-"restricted."
-msgstr ""
-
-#: ../../tos.rst:32
-msgid ""
-"For our Services, we may charge transaction fees. The specific fee "
-"structure is provided based on the Taler protocol and should be shown to "
-"you when you withdraw electronic coins using a Taler Wallet. You agree "
-"and understand that the Taler protocol allows for the fee structure to "
-"change."
-msgstr ""
-
-#: ../../tos.rst:36
-msgid ""
-"You agree to not intentionally overwhelm our systems with requests and "
-"follow responsible disclosure if you find security issues in our "
-"services."
-msgstr ""
-
-#: ../../tos.rst:38
-msgid ""
-"We cannot be held accountable for our Services not being available due to"
-" circumstances beyond our control. If we modify or terminate our "
-"services, we will try to give you the opportunity to recover your funds. "
-"However, given the experimental state of the Services today, this may not"
-" be possible. You are strongly advised to limit your use of the Service "
-"to small-scale experiments expecting total loss of all funds."
-msgstr ""
-
-#: ../../tos.rst:45
-msgid ""
-"These terms outline approved uses of our Services. The Services and these"
-" Terms are still at an experimental stage. If you have any questions or "
-"comments related to this Agreement, please send us a message to legal"
-"@taler-systems.com. If you do not agree to this Agreement, you must not "
-"use our Services."
-msgstr ""
-
-#: ../../tos.rst:52
-msgid "How you accept this policy"
-msgstr ""
-
-#: ../../tos.rst:54
-msgid ""
-"By sending funds to us (to top-up your Taler Wallet), you acknowledge "
-"that you have read, understood, and agreed to these Terms. We reserve the"
-" right to change these Terms at any time. If you disagree with the "
-"change, we may in the future offer you with an easy option to recover "
-"your unspent funds. However, in the current experimental period you "
-"acknowledge that this feature is not yet available, resulting in your "
-"funds being lost unless you accept the new Terms. If you continue to use "
-"our Services other than to recover your unspent funds, your continued use"
-" of our Services following any such change will signify your acceptance "
-"to be bound by the then current Terms. Please check the effective date "
-"above to determine if there have been any changes since you have last "
-"reviewed these Terms."
-msgstr ""
-
-#: ../../tos.rst:67
-msgid "Services"
-msgstr ""
-
-#: ../../tos.rst:69
-msgid ""
-"We will try to transfer funds that we hold in escrow for our users to any"
-" legal recipient to the best of our ability and within the limitations of"
-" the law and our implementation. However, the Services offered today are "
-"highly experimental and the set of recipients of funds is severely "
-"restricted. The Taler Wallet can be loaded by exchanging fiat currencies"
-" against electronic coins. We are providing this exchange service. Once "
-"your Taler Wallet is loaded with electronic coins they can be spent for "
-"purchases if the seller is accepting Taler as a means of payment. We are "
-"not guaranteeing that any seller is accepting Taler at all or a "
-"particular seller. The seller or recipient of deposits of electronic "
-"coins must specify the target account, as per the design of the Taler "
-"protocol. They are responsible for following the protocol and specifying "
-"the correct bank account, and are solely liable for any losses that may "
-"arise from specifying the wrong account. We will allow the government to "
-"link wire transfers to the underlying contract hash. It is the "
-"responsibility of recipients to preserve the full contracts and to pay "
-"whatever taxes and charges may be applicable. Technical issues may lead "
-"to situations where we are unable to make transfers at all or lead to "
-"incorrect transfers that cannot be reversed. We will only refuse to "
-"execute transfers if the transfers are prohibited by a competent legal "
-"authority and we are ordered to do so."
-msgstr ""
-
-#: ../../tos.rst:91
-msgid "Fees"
-msgstr ""
-
-#: ../../tos.rst:93
-msgid ""
-"You agree to pay the fees for exchanges and withdrawals completed via the"
-" Taler Wallet (\"Fees\") as defined by us, which we may change from time "
-"to time. With the exception of wire transfer fees, Taler transaction fees"
-" are set for any electronic coin at the time of withdrawal and fixed "
-"throughout the validity period of the respective electronic coin. Your "
-"wallet should obtain and display applicable fees when withdrawing funds. "
-"Fees for coins obtained as change may differ from the fees applicable to "
-"the original coin. Wire transfer fees that are independent from "
-"electronic coins may change annually. You authorize us to charge or "
-"deduct applicable fees owed in connection with deposits, exchanges and "
-"withdrawals following the rules of the Taler protocol. We reserve the "
-"right to provide different types of rewards to users either in the form "
-"of discount for our Services or in any other form at our discretion and "
-"without prior notice to you."
-msgstr ""
-
-#: ../../tos.rst:108
-msgid "Eligibility"
-msgstr ""
-
-#: ../../tos.rst:110
-msgid ""
-"To be eligible to use our Services, you must be able to form legally "
-"binding contracts or have the permission of your legal guardian. By using"
-" our Services, you represent and warrant that you meet all eligibility "
-"requirements that we outline in these Terms."
-msgstr ""
-
-#: ../../tos.rst:116
-msgid "Financial self-responsibility"
-msgstr ""
-
-#: ../../tos.rst:118
-msgid ""
-"You will be responsible for maintaining the availability, integrity and "
-"confidentiality of the data stored in your wallet. When you setup a Taler"
-" Wallet, you are strongly advised to follow the precautionary measures "
-"offered by the software to minimize the chances to losse access to or "
-"control over your Wallet data. We will not be liable for any loss or "
-"damage arising from your failure to comply with this paragraph."
-msgstr ""
-
-#: ../../tos.rst:126
-msgid "Copyrights and trademarks"
-msgstr ""
-
-#: ../../tos.rst:128
-msgid ""
-"The Taler Wallet is released under the terms of the GNU General Public "
-"License (GNU GPL). You have the right to access, use, and share the Taler"
-" Wallet, in modified or unmodified form. However, the GPL is a strong "
-"copyleft license, which means that any derivative works must be "
-"distributed under the same license terms as the original software. If you"
-" have any questions, you should review the GNU GPL’s full terms and "
-"conditions at https://www.gnu.org/licenses/gpl-3.0.en.html. “Taler†"
-"itself is a trademark of Taler Systems SA. You are welcome to use the "
-"name in relation to processing payments using the Taler protocol, "
-"assuming your use is compatible with an official release from the GNU "
-"Project that is not older than two years."
-msgstr ""
-
-#: ../../tos.rst:140
-msgid "Your use of our services"
-msgstr ""
-
-#: ../../tos.rst:142
-msgid ""
-"When using our Services, you agree to not take any action that "
-"intentionally imposes an unreasonable load on our infrastructure. If you "
-"find security problems in our Services, you agree to first report them to"
-" security@taler-systems.com and grant us the right to publish your "
-"report. We warrant that we will ourselves publicly disclose any issues "
-"reported within 3 months, and that we will not prosecute anyone reporting"
-" security issues if they did not exploit the issue beyond a proof-of-"
-"concept, and followed the above responsible disclosure practice."
-msgstr ""
-
-#: ../../tos.rst:152
-msgid "Limitation of liability & disclaimer of warranties"
-msgstr ""
-
-#: ../../tos.rst:154
-msgid ""
-"You understand and agree that we have no control over, and no duty to "
-"take any action regarding: Failures, disruptions, errors, or delays in "
-"processing that you may experience while using our Services; The risk of "
-"failure of hardware, software, and Internet connections; The risk of "
-"malicious software being introduced or found in the software underlying "
-"the Taler Wallet; The risk that third parties may obtain unauthorized "
-"access to information stored within your Taler Wallet, including, but not"
-" limited to your Taler Wallet coins or backup encryption keys. You "
-"release us from all liability related to any losses, damages, or claims "
-"arising from:"
-msgstr ""
-
-#: ../../tos.rst:164
-msgid ""
-"user error such as forgotten passwords, incorrectly constructed "
-"transactions;"
-msgstr ""
-
-#: ../../tos.rst:166
-msgid "server failure or data loss;"
-msgstr ""
-
-#: ../../tos.rst:167
-msgid "unauthorized access to the Taler Wallet application;"
-msgstr ""
-
-#: ../../tos.rst:168
-msgid "bugs or other errors in the Taler Wallet software; and"
-msgstr ""
-
-#: ../../tos.rst:169
-msgid ""
-"any unauthorized third party activities, including, but not limited to, "
-"the use of viruses, phishing, brute forcing, or other means of attack "
-"against the Taler Wallet. We make no representations concerning any Third"
-" Party Content contained in or accessed through our Services."
-msgstr ""
-
-#: ../../tos.rst:174
-msgid ""
-"Any other terms, conditions, warranties, or representations associated "
-"with such content, are solely between you and such organizations and/or "
-"individuals."
-msgstr ""
-
-#: ../../tos.rst:179
-msgid "Limitation of liability"
-msgstr ""
-
-#: ../../tos.rst:181
-msgid ""
-"To the fullest extent permitted by applicable law, in no event will we or"
-" any of our officers, directors, representatives, agents, servants, "
-"counsel, employees, consultants, lawyers, and other personnel authorized "
-"to act, acting, or purporting to act on our behalf (collectively the "
-"“Taler Partiesâ€) be liable to you under contract, tort, strict liability,"
-" negligence, or any other legal or equitable theory, for:"
-msgstr ""
-
-#: ../../tos.rst:188
-msgid ""
-"any lost profits, data loss, cost of procurement of substitute goods or "
-"services, or direct, indirect, incidental, special, punitive, "
-"compensatory, or consequential damages of any kind whatsoever resulting "
-"from:"
-msgstr ""
-
-#: ../../tos.rst:192
-msgid "your use of, or conduct in connection with, our services;"
-msgstr ""
-
-#: ../../tos.rst:193
-msgid ""
-"any unauthorized use of your wallet and/or private key due to your "
-"failure to maintain the confidentiality of your wallet;"
-msgstr ""
-
-#: ../../tos.rst:195
-msgid "any interruption or cessation of transmission to or from the services; or"
-msgstr ""
-
-#: ../../tos.rst:196
-msgid ""
-"any bugs, viruses, trojan horses, or the like that are found in the Taler"
-" Wallet software or that may be transmitted to or through our services by"
-" any third party (regardless of the source of origination), or"
-msgstr ""
-
-#: ../../tos.rst:200
-msgid "any direct damages."
-msgstr ""
-
-#: ../../tos.rst:202
-msgid ""
-"These limitations apply regardless of legal theory, whether based on "
-"tort, strict liability, breach of contract, breach of warranty, or any "
-"other legal theory, and whether or not we were advised of the possibility"
-" of such damages. Some jurisdictions do not allow the exclusion or "
-"limitation of liability for consequential or incidental damages, so the "
-"above limitation may not apply to you."
-msgstr ""
-
-#: ../../tos.rst:210
-msgid "Warranty disclaimer"
-msgstr ""
-
-#: ../../tos.rst:212
-msgid ""
-"Our services are provided \"as is\" and without warranty of any kind. To "
-"the maximum extent permitted by law, we disclaim all representations and "
-"warranties, express or implied, relating to the services and underlying "
-"software or any content on the services, whether provided or owned by us "
-"or by any third party, including without limitation, warranties of "
-"merchantability, fitness for a particular purpose, title, non-"
-"infringement, freedom from computer virus, and any implied warranties "
-"arising from course of dealing, course of performance, or usage in trade,"
-" all of which are expressly disclaimed. In addition, we do not represent "
-"or warrant that the content accessible via the services is accurate, "
-"complete, available, current, free of viruses or other harmful "
-"components, or that the results of using the services will meet your "
-"requirements. Some states do not allow the disclaimer of implied "
-"warranties, so the foregoing disclaimers may not apply to you. This "
-"paragraph gives you specific legal rights and you may also have other "
-"legal rights that vary from state to state."
-msgstr ""
-
-#: ../../tos.rst:229
-msgid "Indemnity"
-msgstr ""
-
-#: ../../tos.rst:231
-msgid ""
-"To the extent permitted by applicable law, you agree to defend, "
-"indemnify, and hold harmless the Taler Parties from and against any and "
-"all claims, damages, obligations, losses, liabilities, costs or debt, and"
-" expenses (including, but not limited to, attorney’s fees) arising from: "
-"(a) your use of and access to the Services; (b) any feedback or "
-"submissions you provide to us concerning the Taler Wallet; (c) your "
-"violation of any term of this Agreement; or (d) your violation of any "
-"law, rule, or regulation, or the rights of any third party."
-msgstr ""
-
-#: ../../tos.rst:240
-msgid "Time limitation on claims"
-msgstr ""
-
-#: ../../tos.rst:242
-msgid ""
-"You agree that any claim you may have arising out of or related to your "
-"relationship with us must be filed within one year after such claim "
-"arises, otherwise, your claim in permanently barred."
-msgstr ""
-
-#: ../../tos.rst:247
-msgid "Governing law"
-msgstr ""
-
-#: ../../tos.rst:249
-msgid ""
-"No matter where you’re located, the laws of Switzerland will govern these"
-" Terms. If any provisions of these Terms are inconsistent with any "
-"applicable law, those provisions will be superseded or modified only to "
-"the extent such provisions are inconsistent. The parties agree to submit "
-"to the ordinary courts in Zurich, Switzerland for exclusive jurisdiction "
-"of any dispute arising out of or related to your use of the Services or "
-"your breach of these Terms."
-msgstr ""
-
-#: ../../tos.rst:258
-msgid "Termination"
-msgstr ""
-
-#: ../../tos.rst:260
-msgid ""
-"In the event of termination concerning your use of our Services, your "
-"obligations under this Agreement will still continue."
-msgstr ""
-
-#: ../../tos.rst:264
-msgid "Discontinuance of services"
-msgstr ""
-
-#: ../../tos.rst:266
-msgid ""
-"We may, in our sole discretion and without cost to you, with or without "
-"prior notice, and at any time, modify or discontinue, temporarily or "
-"permanently, any portion of our Services. We will use the Taler "
-"protocol’s provisions to notify Wallets if our Services are to be "
-"discontinued. It is your responsibility to ensure that the Taler Wallet "
-"is online at least once every three months to observe these "
-"notifications. We shall not be held responsible or liable for any loss of"
-" funds in the event that we discontinue or depreciate the Services and "
-"your Taler Wallet fails to transfer out the coins within a three months "
-"notification period."
-msgstr ""
-
-#: ../../tos.rst:277
-msgid "No waiver"
-msgstr ""
-
-#: ../../tos.rst:279
-msgid ""
-"Our failure to exercise or delay in exercising any right, power, or "
-"privilege under this Agreement shall not operate as a waiver; nor shall "
-"any single or partial exercise of any right, power, or privilege preclude"
-" any other or further exercise thereof."
-msgstr ""
-
-#: ../../tos.rst:285
-msgid "Severability"
-msgstr ""
-
-#: ../../tos.rst:287
-msgid ""
-"If it turns out that any part of this Agreement is invalid, void, or for "
-"any reason unenforceable, that term will be deemed severable and limited "
-"or eliminated to the minimum extent necessary."
-msgstr ""
-
-#: ../../tos.rst:292
-msgid "Force majeure"
-msgstr ""
-
-#: ../../tos.rst:294
-msgid ""
-"We shall not be held liable for any delays, failure in performance, or "
-"interruptions of service which result directly or indirectly from any "
-"cause or condition beyond our reasonable control, including but not "
-"limited to: any delay or failure due to any act of God, act of civil or "
-"military authorities, act of terrorism, civil disturbance, war, strike or"
-" other labor dispute, fire, interruption in telecommunications or "
-"Internet services or network provider services, failure of equipment "
-"and/or software, other catastrophe, or any other occurrence which is "
-"beyond our reasonable control and shall not affect the validity and "
-"enforceability of any remaining provisions."
-msgstr ""
-
-#: ../../tos.rst:305
-msgid "Assignment"
-msgstr ""
-
-#: ../../tos.rst:307
-msgid ""
-"You agree that we may assign any of our rights and/or transfer, sub-"
-"contract, or delegate any of our obligations under these Terms."
-msgstr ""
-
-#: ../../tos.rst:311
-msgid "Entire agreement"
-msgstr ""
-
-#: ../../tos.rst:313
-msgid ""
-"This Agreement sets forth the entire understanding and agreement as to "
-"the subject matter hereof and supersedes any and all prior discussions, "
-"agreements, and understandings of any kind (including, without "
-"limitation, any prior versions of this Agreement) and every nature "
-"between us. Except as provided for above, any modification to this "
-"Agreement must be in writing and must be signed by both parties."
-msgstr ""
-
-#: ../../tos.rst:321
-msgid "Questions or comments"
-msgstr ""
-
-#: ../../tos.rst:323
-msgid ""
-"We welcome comments, questions, concerns, or suggestions. Please send us "
-"a message on our contact page at legal@taler-systems.com."
-msgstr ""
-
-#~ msgid "Indemntiy"
-#~ msgstr ""
-
diff --git a/contrib/uncrustify-mode.el b/contrib/uncrustify-mode.el
index 83868c6a1..cf615b026 100755
--- a/contrib/uncrustify-mode.el
+++ b/contrib/uncrustify-mode.el
@@ -114,7 +114,7 @@
(message "uncrustify error: <%s> <%s>" ret (buffer-string)))
nil))))))
- ;; This goto-line is outside the save-excursion becuase it'd get
+ ;; This goto-line is outside the save-excursion because it'd get
;; removed otherwise. I hate this bug. It makes things so ugly.
(goto-line original-line)
(not result)))
diff --git a/contrib/uncrustify.cfg b/contrib/uncrustify.cfg
index 8c9df2c43..af2d8e69c 100644
--- a/contrib/uncrustify.cfg
+++ b/contrib/uncrustify.cfg
@@ -28,7 +28,7 @@ ls_code_width=true
pos_arith=lead
# Fully parenthesize boolean exprs
-mod_full_paren_if_bool=true
+mod_full_paren_if_bool=false
# Braces should be on their own line
nl_fdef_brace=add
diff --git a/contrib/uncrustify_precommit b/contrib/uncrustify_precommit
index fd29998c3..d0a64ef72 100755
--- a/contrib/uncrustify_precommit
+++ b/contrib/uncrustify_precommit
@@ -1,11 +1,10 @@
#!/bin/sh
# use as .git/hooks/pre-commit
-
exec 1>&2
RET=0
-changed=$(git diff --cached --name-only)
+changed=$(git diff --cached --name-only | grep -v mustach | grep -v templating/test | grep -v valgrind.h)
crustified=""
for f in $changed;
@@ -29,7 +28,7 @@ done
if [ $RET = 1 ];
then
echo "Run"
- echo "uncrustify --no-backup -c uncrustify.cfg ${crustified}"
- echo "before commiting."
+ echo "uncrustify --replace -c uncrustify.cfg ${crustified}"
+ echo "before committing."
fi
exit $RET
diff --git a/contrib/update-pp.sh b/contrib/update-pp.sh
index db31ba18a..728216c58 100755
--- a/contrib/update-pp.sh
+++ b/contrib/update-pp.sh
@@ -14,7 +14,8 @@ cd pp
for l in $@
do
mkdir -p $l
- echo Generating PP for language $l
+ echo "Generating PP for language $l"
+ cat conf.py.in | sed -e "s/%VERSION%/$VERSION/g" > conf.py
# 'f' is for the supported formats, note that the 'make' target
# MUST match the file extension.
for f in html txt pdf epub xml
@@ -22,7 +23,16 @@ do
rm -rf _build
echo " Generating format $f"
make -e SPHINXOPTS="-D language='$l'" $f >>sphinx.log 2>>sphinx.err < /dev/null
- mv _build/$f/pp.$f $l/${VERSION}.$f
+ if test $f = "html"
+ then
+ htmlark -o $l/${VERSION}.$f _build/$f/${VERSION}.$f
+ else
+ mv _build/$f/${VERSION}.$f $l/${VERSION}.$f
+ fi
+ if test $f = "txt"
+ then
+ cp $l/${VERSION}.$f $l/${VERSION}.md
+ fi
done
done
cd ..
diff --git a/contrib/update-tos.sh b/contrib/update-tos.sh
index 47d3af778..dcf9e3919 100755
--- a/contrib/update-tos.sh
+++ b/contrib/update-tos.sh
@@ -14,7 +14,8 @@ cd tos
for l in $@
do
mkdir -p $l
- echo Generating TOS for language $l
+ echo "Generating TOS for language $l"
+ cat conf.py.in | sed -e "s/%VERSION%/$VERSION/g" > conf.py
# 'f' is for the supported formats, note that the 'make' target
# MUST match the file extension.
for f in html txt pdf epub xml
@@ -22,7 +23,17 @@ do
rm -rf _build
echo " Generating format $f"
make -e SPHINXOPTS="-D language='$l'" $f >>sphinx.log 2>>sphinx.err < /dev/null
- mv _build/$f/tos.$f $l/${VERSION}.$f
+ if test $f = "html"
+ then
+ htmlark -o $l/${VERSION}.$f _build/$f/${VERSION}.$f
+ else
+ mv _build/$f/${VERSION}.$f $l/${VERSION}.$f
+ fi
+ if test $f = "txt"
+ then
+ cp $l/${VERSION}.$f $l/${VERSION}.md
+ fi
done
done
cd ..
+echo "Success"
diff --git a/contrib/wallet-core b/contrib/wallet-core
new file mode 160000
+Subproject 240d647da85de6b575d15c37efec04757541e3d
diff --git a/debian/.gitignore b/debian/.gitignore
new file mode 100644
index 000000000..f3ddfd1d2
--- /dev/null
+++ b/debian/.gitignore
@@ -0,0 +1,23 @@
+.debhelper/
+autoreconf.after
+autoreconf.before
+debhelper-build-stamp
+files
+*.log
+libtalerexchange.substvars
+libtalerexchange/
+taler-exchange-dev.substvars
+taler-exchange-dev/
+taler-exchange.substvars
+taler-exchange/
+taler-exchange-database/
+tmp/
+libtalerexchange-dev.substvars
+libtalerexchange-dev/
+taler-auditor.postrm.debhelper
+taler-auditor.substvars
+taler-auditor/
+taler-exchange.postrm.debhelper
+*.debhelper
+*.substvars
+taler-exchange-offline
diff --git a/debian/README-packaging.md b/debian/README-packaging.md
new file mode 100644
index 000000000..ac4274618
--- /dev/null
+++ b/debian/README-packaging.md
@@ -0,0 +1,7 @@
+This file contains some notes about packaging.
+
+## Systemd Units
+
+The main unit file is taler-exchange.service. It is a unit that does not run
+anything, but instead can be used to stop/start all exchange-related services
+at once.
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 000000000..cab5345dc
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,410 @@
+taler-exchange (0.10.2) unstable; urgency=low
+
+ * Updated man pages and other resources for release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Fri, 12 Apr 2024 09:50:12 +0200
+
+taler-exchange (0.10.1) unstable; urgency=low
+
+ * Fixed crash in OTP calculation logic if required amount was not
+ provided.
+ * Fixed HTTP headers for /terms, /config and /keys responses
+ (remove last-modified-since distinguisher, remove duplicate headers)
+ * Improved systemd files (new offline timer job, exchange slice)
+ * Implement exchange protocol v19 (include refunds in transaction
+ aggregation data, expose wire account priorities)
+ * Update mustach to latest version
+
+ -- Christian Grothoff <grothoff@gnu.org> Tue, 9 Apr 2024 09:50:12 +0200
+
+taler-exchange (0.10.0) unstable; urgency=low
+
+ * Fixed major issue where uploading wire data to an
+ exchange twice would result in broken signatures and
+ a permanently non-working account.
+ * Implemented #8000, allowing an exchange to express
+ preferences over the different bank accounts so that
+ users are shown the best choices to withdraw from first.
+ * This version requires a more recent GNUnet (>= 0.21.1).
+
+ -- Christian Grothoff <grothoff@gnu.org> Sat, 9 Mar 2024 21:50:12 +0200
+
+taler-exchange (0.9.4-2) unstable; urgency=low
+
+ * Created new taler-terms-generator package
+ * v0.9.4a bugfix release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Mon, 3 Mar 2024 21:50:12 +0200
+
+taler-exchange (0.9.4-1) unstable; urgency=low
+
+ * Actual v0.9.4 release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Sat, 10 Feb 2024 03:50:12 +0200
+
+taler-exchange (0.9.4) unstable; urgency=low
+
+ * Preparations for v0.9.4 release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Sun, 21 Jan 2024 03:50:12 +0200
+
+taler-exchange (0.9.3-7) unstable; urgency=low
+
+ * Move currencies.conf into libtalerexchange base package.
+
+ -- Christian Grothoff <grothoff@gnu.org> Tue, 15 Dec 2023 18:50:12 -0700
+
+taler-exchange (0.9.3-6) unstable; urgency=low
+
+ * Generate proper markdown in taler-terms-generator.
+ * Return language code for legal terms.
+
+ -- Christian Grothoff <grothoff@gnu.org> Tue, 13 Dec 2023 18:50:12 -0700
+
+taler-exchange (0.9.3-5) unstable; urgency=low
+
+ * More fixes to the database setup automation scripts.
+ * Implement latest /config API in fakebank.
+
+ -- Christian Grothoff <grothoff@gnu.org> Thu, 7 Dec 2023 00:50:12 -0800
+
+taler-exchange (0.9.3-4) unstable; urgency=low
+
+ * Minor hot-fixes to the database setup automation script.
+
+ -- Christian Grothoff <grothoff@gnu.org> Fri, 1 Dec 2023 10:50:12 -0800
+
+taler-exchange (0.9.3-3) unstable; urgency=low
+
+ * This packages the v0.9.3b bugfix release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Wed, 29 Nov 2023 03:50:12 +0200
+
+taler-exchange (0.9.3-2) unstable; urgency=low
+
+ * This packages the v0.9.3a bugfix release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Wed, 29 Nov 2023 03:50:12 +0200
+
+taler-exchange (0.9.3-1) unstable; urgency=low
+
+ * Actual v0.9.3 release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Wed, 27 Sep 2023 03:50:12 +0200
+
+taler-exchange (0.9.3) unstable; urgency=low
+
+ * First work towards packaging v0.9.3.
+
+ -- Christian Grothoff <grothoff@gnu.org> Thu, 7 Sep 2023 23:50:12 +0200
+
+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.
+
+ -- Christian Grothoff <grothoff@gnu.org> Tue, 17 Jan 2023 11:50:12 +0200
+
+taler-exchange (0.9.0) unstable; urgency=low
+
+ * Packaging latest release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Sat, 5 Nov 2022 11:50:12 +0200
+
+taler-exchange (0.8.99-2) unstable; urgency=low
+
+ * Packaging latest pre-release from Git.
+
+ -- Christian Grothoff <grothoff@gnu.org> Mon, 26 Sep 2022 09:50:12 +0200
+
+taler-exchange (0.8.99-1) unstable; urgency=low
+
+ * Updating to latest pre-release from Git.
+
+ -- Christian Grothoff <grothoff@taler.net> Mon, 20 Jun 2022 13:12:58 +0200
+
+taler-exchange (0.8.5-3) unstable; urgency=low
+
+ * Updating to latest Git with minor bugfixes and improvements.
+
+ -- Christian Grothoff <grothoff@taler.net> Tue, 12 Oct 2021 13:12:58 +0200
+
+taler-exchange (0.8.5-2) unstable; urgency=low
+
+ * Updating to latest Git with minor bugfixes and improvements.
+
+ -- Christian Grothoff <grothoff@taler.net> Mon, 27 Sep 2021 13:12:58 +0200
+
+taler-exchange (0.8.5-1) unstable; urgency=low
+
+ * Updating to latest Git with minor bugfixes and improvements.
+
+ -- Christian Grothoff <grothoff@taler.net> Sat, 28 Aug 2021 13:12:58 +0200
+
+taler-exchange (0.8.5) unstable; urgency=low
+
+ * Official release of GNU Taler exchange 0.8.5.
+
+ -- Christian Grothoff <grothoff@taler.net> Sat, 28 Aug 2021 13:12:58 +0200
+
+taler-exchange (0.8.4-1) unstable; urgency=low
+
+ * Updated GANA.
+
+ -- Florian Dold <dold@taler.net> Thu, 26 Aug 2021 16:37:33 +0200
+
+taler-exchange (0.8.4) unstable; urgency=low
+
+ * Official release of GNU Taler exchange 0.8.4.
+
+ -- Florian Dold <dold@taler.net> Tue, 24 Aug 2021 13:12:58 +0200
+
+taler-exchange (0.8.3) unstable; urgency=low
+
+ * Official release of GNU Taler exchange 0.8.3.
+
+ -- Christian Grothoff <grothoff@taler.net> Fri, 13 Aug 2021 23:23:21 +0200
+
+taler-exchange (0.8.2) unstable; urgency=low
+
+ * Official release of GNU Taler exchange 0.8.2.
+
+ -- Christian Grothoff <grothoff@taler.net> Sun, 08 Aug 2021 23:23:21 +0200
+
+taler-exchange (0.8.1-31) unstable; urgency=low
+
+ * Fix dependencies in service definition.
+
+ -- Florian Dold <dold@taler.net> Sat, 07 Aug 2021 23:23:21 +0200
+
+taler-exchange (0.8.1-30) unstable; urgency=low
+
+ * Fix dependencies in service definition.
+ * Minor fixes in upstream code.
+
+ -- Florian Dold <dold@taler.net> Sat, 07 Aug 2021 20:20:33 +0200
+
+taler-exchange (0.8.1-29) unstable; urgency=low
+
+ * Minor fix in gateway client.
+
+ -- Florian Dold <dold@taler.net> Fri, 06 Aug 2021 17:17:46 +0200
+
+taler-exchange (0.8.1-28) unstable; urgency=low
+
+ * Service and configuration fixes.
+
+ -- Florian Dold <dold@taler.net> Fri, 06 Aug 2021 13:29:47 +0200
+
+taler-exchange (0.8.1-27) unstable; urgency=low
+
+ * Update to upstream code with minor bugfixes.
+ * Fix permissions of secret configuration files in /etc.
+
+ -- Florian Dold <dold@taler.net> Thu, 05 Aug 2021 21:36:54 +0200
+
+taler-exchange (0.8.1-26) unstable; urgency=low
+
+ * Search config file location correctly.
+
+ -- Florian Dold <dold@taler.net> Wed, 04 Aug 2021 21:49:42 +0200
+
+taler-exchange (0.8.1-25) unstable; urgency=low
+
+ * Socket permissions.
+
+ -- Florian Dold <dold@taler.net> Wed, 04 Aug 2021 20:54:31 +0200
+
+taler-exchange (0.8.1-24) unstable; urgency=low
+
+ * Service dependencies.
+
+ -- Florian Dold <dold@taler.net> Wed, 04 Aug 2021 20:17:53 +0200
+
+taler-exchange (0.8.1-23) unstable; urgency=low
+
+ * Fix secmod helper permissions.
+
+ -- Florian Dold <dold@taler.net> Wed, 04 Aug 2021 20:01:12 +0200
+
+taler-exchange (0.8.1-22) unstable; urgency=low
+
+ * Fix permissions.
+
+ -- Florian Dold <dold@taler.net> Wed, 04 Aug 2021 19:08:56 +0200
+
+taler-exchange (0.8.1-21) unstable; urgency=low
+
+ * Fix service start assertion.
+
+ -- Florian Dold <dold@taler.net> Wed, 04 Aug 2021 18:54:54 +0200
+
+taler-exchange (0.8.1-20) unstable; urgency=low
+
+ * Reduce service dependencies of taler-exchange-httpd.service.
+
+ -- Florian Dold <dold@taler.net> Wed, 04 Aug 2021 18:44:34 +0200
+
+taler-exchange (0.8.1-19) unstable; urgency=low
+
+ * Changes to configuration structure.
+
+ -- Florian Dold <dold@taler.net> Wed, 04 Aug 2021 16:41:21 +0200
+
+taler-exchange (0.8.1-18) unstable; urgency=low
+
+ * Support debhelper-compat 12.
+
+ -- Florian Dold <dold@taler.net> Sun, 01 Aug 2021 18:42:34 +0200
+
+taler-exchange (0.8.1-17) unstable; urgency=low
+
+ * Fix installation of config files.
+
+ -- Florian Dold <dold@taler.net> Sat, 31 Jul 2021 18:41:20 +0200
+
+taler-exchange (0.8.1-16) unstable; urgency=low
+
+ * Improved default configuration.
+ * Various packaging tweaks.
+
+ -- Florian Dold <dold@taler.net> Sat, 31 Jul 2021 13:17:47 +0200
+
+taler-exchange (0.8.1-15) unstable; urgency=low
+
+ * New Taler amount operations (set zero, ...) added.
+ * New configuration file structure
+ * New taler-exchange-offline package
+
+ -- Florian Dold <dold@taler.net> Mon, 26 Jul 2021 11:21:39 +0200
+
+taler-exchange (0.8.1-14) unstable; urgency=low
+
+ * Expose additional symbols needed in merchant logic.
+
+ -- Christian Grothoff <grothoff@gnu.org> Tue, 20 Jul 2021 14:02:10 +0100
+
+taler-exchange (0.8.1-13) unstable; urgency=low
+
+ * New Taler amount operations (multiply and divide) added.
+
+ -- Christian Grothoff <grothoff@gnu.org> Wed, 14 Jul 2021 14:02:10 +0100
+
+taler-exchange (0.8.1-12) unstable; urgency=low
+
+ * Fix typo in taler-auditor shell script: clean before building.
+
+ -- Christian Grothoff <grothoff@gnu.org> Mon, 28 Jun 2021 14:02:10 +0100
+
+taler-exchange (0.8.1-11) unstable; urgency=low
+
+ * Fix typo in taler-auditor-sync.
+
+ -- Christian Grothoff <grothoff@gnu.org> Sun, 27 Jun 2021 14:02:10 +0100
+
+taler-exchange (0.8.1-10) unstable; urgency=low
+
+ * Improve database performance for taler-exchange-wirewatch.
+ * Update database schema, fix missing indices.
+
+ -- Christian Grothoff <grothoff@gnu.org> Thu, 24 Jun 2021 14:02:10 +0100
+
+taler-exchange (0.8.1-9) unstable; urgency=low
+
+ * Fix #6769: have systemd create exchange UNIX domain socket with nice permissions.
+
+ -- Christian Grothoff <grothoff@gnu.org> Sun, 18 Apr 2021 13:02:10 +0100
+
+taler-exchange (0.8.1-8) unstable; urgency=low
+
+ * Fix minor memory leak.
+
+ -- Christian Grothoff <grothoff@gnu.org> Tue, 16 Feb 2021 13:02:10 +0100
+
+taler-exchange (0.8.1-7) unstable; urgency=medium
+
+ * Avoid picking up libtalerexchange-dev as a dependency of taler-exchange.
+
+ -- Christian Grothoff <grothoff@gnu.org> Mon, 15 Feb 2021 13:02:10 +0100
+
+taler-exchange (0.8.1-6) unstable; urgency=medium
+
+ * Fixed a few memory leaks.
+
+ -- Christian Grothoff <grothoff@gnu.org> Mon, 15 Feb 2021 12:02:10 +0100
+
+taler-exchange (0.8.1-5) unstable; urgency=medium
+
+ * Fixed a few bugs.
+
+ -- Christian Grothoff <grothoff@gnu.org> Sat, 30 Jan 2021 12:02:10 +0100
+
+taler-exchange (0.8.1-4) unstable; urgency=medium
+
+ * Added setup subcommand to taler-exchange-offline.
+ * Fixed conflict between taler-auditor and libtaler exchange packages.
+ * Fixed bad handling of non-C locales.
+ * Updated documentation.
+
+ -- Christian Grothoff <grothoff@gnu.org> Wed, 27 Jan 2021 12:02:10 +0100
+
+taler-exchange (0.8.1-3) unstable; urgency=medium
+
+ * Renamed helper/secmod binaries for consistency.
+ * Protocol improvements, removing unnecessary struct members.
+ * Fixed /management/keys caching logic and key revocation handling.
+ * Implemented taler-auditor-sync.
+ * Misc. other minor improvements.
+
+ -- Christian Grothoff <grothoff@gnu.org> Thu, 21 Jan 2021 12:02:10 +0100
+
+taler-exchange (0.8.1-3) unstable; urgency=medium
+
+ * Fix taler-exchange.postrm crash (prevented uninstall).
+ * Split out taler-auditor package.
+ * Setup user and systemd service for taler-auditor-httpd.
+
+ -- Christian Grothoff <grothoff@gnu.org> Sun, 03 Jan 2020 23:00:00 +0000
+
+taler-exchange (0.8.1-2) unstable; urgency=medium
+
+ * Modify setup to not touch database (too complex anyway).
+ * Fix build of taler-config.
+ * Correct dependencies.
+
+ -- Christian Grothoff <grothoff@gnu.org> Sat, 02 Jan 2020 23:00:00 +0000
+
+taler-exchange (0.8.1-1) unstable; urgency=medium
+
+ * Fixing various minor issues with the package, in particular how systemd units are started.
+
+ -- Christian Grothoff <grothoff@gnu.org> Thu, 31 Dec 2020 23:00:00 +0000
+
+taler-exchange (0.8.1-0) unstable; urgency=medium
+
+ * Initial Release.
+
+ -- Christian Grothoff <grothoff@gnu.org> Thu, 31 Dec 2020 00:00:00 +0000
diff --git a/debian/control b/debian/control
new file mode 100644
index 000000000..cf99dd1ed
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,165 @@
+Source: taler-exchange
+Section: net
+Priority: optional
+Maintainer: Christian Grothoff <grothoff@gnu.org>
+Build-Depends:
+ autoconf (>=2.59),
+ automake (>=1.11.1),
+ autopoint,
+ bash,
+ gcc-12,
+ debhelper-compat (= 12),
+ gettext,
+ libgnunet-dev (>=0.21),
+ libcurl4-gnutls-dev (>=7.35.0) | libcurl4-openssl-dev (>= 7.35.0),
+ libgcrypt20-dev (>=1.8),
+ libgnutls28-dev (>=3.2.12),
+ libidn2-dev,
+ libjansson-dev (>= 2.13),
+ libltdl-dev (>=2.2),
+ libmicrohttpd-dev (>=0.9.71),
+ libpq-dev (>=14),
+ libsodium-dev (>=1.0.11),
+ libunistring-dev (>=0.9.2),
+ po-debconf,
+ texinfo (>=5.2),
+ zlib1g-dev
+Standards-Version: 4.5.0
+Vcs-Git: https://salsa.debian.org/debian/taler-exchange.git
+Vcs-browser: https://salsa.debian.org/debian/taler-exchange
+Homepage: https://taler.net/
+
+Package: libtalerexchange
+Architecture: any
+Pre-Depends:
+ ${misc:Pre-Depends}
+Depends:
+ lsb-base,
+ netbase,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: Libraries to talk to a GNU Taler exchange.
+ The package also contains various files fundamental
+ to all GNU Taler installations, such as the
+ taler-config configuration command-line tool,
+ various base configuration files and associated
+ documentation.
+
+Package: taler-terms-generator
+Architecture: any
+Pre-Depends:
+ ${misc:Pre-Depends}
+Depends:
+ lsb-base,
+ pandoc,
+ groff,
+ ghostscript,
+ ${misc:Depends}
+Description: Tool to generate the terms of service
+ and privacy policy for various languages and data
+ formats. Useful for various GNU Taler components.
+
+Package: taler-exchange-database
+Architecture: any
+Pre-Depends:
+ ${misc:Pre-Depends}
+Depends:
+ lsb-base,
+ netbase,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: Programs and libraries to manage a GNU Taler exchange database.
+ This package contains only the code to setup the
+ (Postgresql) database interaction (taler-exchange-dbinit
+ and associated resource files).
+
+Package: taler-exchange
+Architecture: any
+Pre-Depends:
+ ${misc:Pre-Depends}
+Depends:
+ libtalerexchange (= ${binary:Version}),
+ taler-exchange-database (= ${binary:Version}),
+ adduser,
+ lsb-base,
+ netbase,
+ ucf,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Recommends:
+ taler-exchange-offline (= ${binary:Version}),
+ taler-terms-generator,
+ apache2 | nginx | httpd,
+ postgresql (>=14.0)
+Description: GNU's payment system operator.
+ GNU Taler is the privacy-preserving digital payment
+ system from the GNU project. This package contains the
+ core logic that must be run by the payment service
+ provider or bank to offer payments to consumers and
+ merchants. At least one exchange must be operated
+ per currency.
+ In addition to the core logic, an exchange operator
+ must also have a system running the "offline" logic
+ which is packaged as taler-exchange-offline. It is
+ recommended to keep the "offline" logic on a system
+ that is never connected to the Internet. However, it
+ is also possible to run the "offline" logic directly
+ on the production system, especially for testing.
+ Finally, an exchange operator should also be prepared
+ to run a taler-auditor.
+
+Package: taler-exchange-offline
+Architecture: any
+Pre-Depends:
+ ${misc:Pre-Depends}
+Depends:
+ libtalerexchange (= ${binary:Version}),
+ adduser,
+ lsb-base,
+ netbase,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: Tools for managing the GNU Taler exchange offline keys.
+ A GNU Taler exchange uses an offline key to sign its online
+ keys, fee structure, bank routing information and other meta
+ data. The offline signing key is the root of the Taler PKI
+ that is then embedded in consumer wallets and merchant backends.
+ This package includes the tool to download material to sign
+ from the exchange, create signatures, and upload the resulting
+ signatures to the exchange.
+
+Package: taler-auditor
+Architecture: any
+Pre-Depends:
+ ${misc:Pre-Depends}
+Depends:
+ libtalerexchange (= ${binary:Version}),
+ taler-exchange-database (= ${binary:Version}),
+ adduser,
+ lsb-base,
+ netbase,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: GNU's payment system auditor.
+ GNU Taler is the privacy-preserving digital payment
+ system from the GNU project. This package contains the
+ auditor logic. It verifies that the taler-exchange run
+ by a payment service provider is correctly performing
+ its bank transactions and thus has the correct balance
+ in its escrow account. Each exchange operator is
+ expected to make use of one or more auditors as part
+ of its regulatory compliance.
+
+Package: libtalerexchange-dev
+Section: libdevel
+Architecture: any
+Depends:
+ libtalerexchange (= ${binary:Version}),
+ libgnunet-dev (>=0.21),
+ libgcrypt20-dev (>=1.8),
+ libmicrohttpd-dev (>=0.9.71),
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: libraries to talk to a GNU Taler exchange (development)
+ .
+ This package contains the development files.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 000000000..555d608fa
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,699 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: GNU Taler
+Upstream-Contact: Christian Grothoff <christian@grothoff.org>
+Source: https://taler.net/
+
+Files: *
+Copyright:
+ (C) 2013-2020 Taler Systems SA
+License: AGPL-3+
+Comment: Many contributors are mentioned in AUTHORS
+
+Files: debian/*
+Copyright:
+ (C) 2020 Christian Grothoff <grothoff@gnu.org>
+License: GPL-3+
+
+Files: debian/po/*
+Copyright:
+License: GPL-3+
+
+License: GPL-3+
+ This program 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 of the License, or
+ (at your option) any later version.
+ .
+ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ .
+ The complete text of the GNU General Public License
+ can be found in /usr/share/common-licenses/GPL-3 file.
+
+License: AGPL-3+
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+ .
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+ .
+ Preamble
+ .
+ The GNU Affero General Public License is a free, copyleft license for
+ software and other kinds of works, specifically designed to ensure
+ cooperation with the community in the case of network server software.
+ .
+ The licenses for most software and other practical works are designed
+ to take away your freedom to share and change the works. By contrast,
+ our General Public Licenses are intended to guarantee your freedom to
+ share and change all versions of a program--to make sure it remains free
+ software for all its users.
+ .
+ When we speak of free software, we are referring to freedom, not
+ price. Our General Public Licenses are designed to make sure that you
+ have the freedom to distribute copies of free software (and charge for
+ them if you wish), that you receive source code or can get it if you
+ want it, that you can change the software or use pieces of it in new
+ free programs, and that you know you can do these things.
+ .
+ Developers that use our General Public Licenses protect your rights
+ with two steps: (1) assert copyright on the software, and (2) offer
+ you this License which gives you legal permission to copy, distribute
+ and/or modify the software.
+ .
+ A secondary benefit of defending all users' freedom is that
+ improvements made in alternate versions of the program, if they
+ receive widespread use, become available for other developers to
+ incorporate. Many developers of free software are heartened and
+ encouraged by the resulting cooperation. However, in the case of
+ software used on network servers, this result may fail to come about.
+ The GNU General Public License permits making a modified version and
+ letting the public access it on a server without ever releasing its
+ source code to the public.
+ .
+ The GNU Affero General Public License is designed specifically to
+ ensure that, in such cases, the modified source code becomes available
+ to the community. It requires the operator of a network server to
+ provide the source code of the modified version running there to the
+ users of that server. Therefore, public use of a modified version, on
+ a publicly accessible server, gives the public access to the source
+ code of the modified version.
+ .
+ An older license, called the Affero General Public License and
+ published by Affero, was designed to accomplish similar goals. This is
+ a different license, not a version of the Affero GPL, but Affero has
+ released a new version of the Affero GPL which permits relicensing under
+ this license.
+ .
+ The precise terms and conditions for copying, distribution and
+ modification follow.
+ .
+ TERMS AND CONDITIONS
+ .
+ 0. Definitions.
+ .
+ "This License" refers to version 3 of the GNU Affero General Public License.
+ .
+ "Copyright" also means copyright-like laws that apply to other kinds of
+ works, such as semiconductor masks.
+ .
+ "The Program" refers to any copyrightable work licensed under this
+ License. Each licensee is addressed as "you". "Licensees" and
+ "recipients" may be individuals or organizations.
+ .
+ To "modify" a work means to copy from or adapt all or part of the work
+ in a fashion requiring copyright permission, other than the making of an
+ exact copy. The resulting work is called a "modified version" of the
+ earlier work or a work "based on" the earlier work.
+ .
+ A "covered work" means either the unmodified Program or a work based
+ on the Program.
+ .
+ To "propagate" a work means to do anything with it that, without
+ permission, would make you directly or secondarily liable for
+ infringement under applicable copyright law, except executing it on a
+ computer or modifying a private copy. Propagation includes copying,
+ distribution (with or without modification), making available to the
+ public, and in some countries other activities as well.
+ .
+ To "convey" a work means any kind of propagation that enables other
+ parties to make or receive copies. Mere interaction with a user through
+ a computer network, with no transfer of a copy, is not conveying.
+ .
+ An interactive user interface displays "Appropriate Legal Notices"
+ to the extent that it includes a convenient and prominently visible
+ feature that (1) displays an appropriate copyright notice, and (2)
+ tells the user that there is no warranty for the work (except to the
+ extent that warranties are provided), that licensees may convey the
+ work under this License, and how to view a copy of this License. If
+ the interface presents a list of user commands or options, such as a
+ menu, a prominent item in the list meets this criterion.
+ .
+ 1. Source Code.
+ .
+ The "source code" for a work means the preferred form of the work
+ for making modifications to it. "Object code" means any non-source
+ form of a work.
+ .
+ A "Standard Interface" means an interface that either is an official
+ standard defined by a recognized standards body, or, in the case of
+ interfaces specified for a particular programming language, one that
+ is widely used among developers working in that language.
+ .
+ The "System Libraries" of an executable work include anything, other
+ than the work as a whole, that (a) is included in the normal form of
+ packaging a Major Component, but which is not part of that Major
+ Component, and (b) serves only to enable use of the work with that
+ Major Component, or to implement a Standard Interface for which an
+ implementation is available to the public in source code form. A
+ "Major Component", in this context, means a major essential component
+ (kernel, window system, and so on) of the specific operating system
+ (if any) on which the executable work runs, or a compiler used to
+ produce the work, or an object code interpreter used to run it.
+ .
+ The "Corresponding Source" for a work in object code form means all
+ the source code needed to generate, install, and (for an executable
+ work) run the object code and to modify the work, including scripts to
+ control those activities. However, it does not include the work's
+ System Libraries, or general-purpose tools or generally available free
+ programs which are used unmodified in performing those activities but
+ which are not part of the work. For example, Corresponding Source
+ includes interface definition files associated with source files for
+ the work, and the source code for shared libraries and dynamically
+ linked subprograms that the work is specifically designed to require,
+ such as by intimate data communication or control flow between those
+ subprograms and other parts of the work.
+ .
+ The Corresponding Source need not include anything that users
+ can regenerate automatically from other parts of the Corresponding
+ Source.
+ .
+ The Corresponding Source for a work in source code form is that
+ same work.
+ .
+ 2. Basic Permissions.
+ .
+ All rights granted under this License are granted for the term of
+ copyright on the Program, and are irrevocable provided the stated
+ conditions are met. This License explicitly affirms your unlimited
+ permission to run the unmodified Program. The output from running a
+ covered work is covered by this License only if the output, given its
+ content, constitutes a covered work. This License acknowledges your
+ rights of fair use or other equivalent, as provided by copyright law.
+ .
+ You may make, run and propagate covered works that you do not
+ convey, without conditions so long as your license otherwise remains
+ in force. You may convey covered works to others for the sole purpose
+ of having them make modifications exclusively for you, or provide you
+ with facilities for running those works, provided that you comply with
+ the terms of this License in conveying all material for which you do
+ not control copyright. Those thus making or running the covered works
+ for you must do so exclusively on your behalf, under your direction
+ and control, on terms that prohibit them from making any copies of
+ your copyrighted material outside their relationship with you.
+ .
+ Conveying under any other circumstances is permitted solely under
+ the conditions stated below. Sublicensing is not allowed; section 10
+ makes it unnecessary.
+ .
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+ .
+ No covered work shall be deemed part of an effective technological
+ measure under any applicable law fulfilling obligations under article
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
+ similar laws prohibiting or restricting circumvention of such
+ measures.
+ .
+ When you convey a covered work, you waive any legal power to forbid
+ circumvention of technological measures to the extent such circumvention
+ is effected by exercising rights under this License with respect to
+ the covered work, and you disclaim any intention to limit operation or
+ modification of the work as a means of enforcing, against the work's
+ users, your or third parties' legal rights to forbid circumvention of
+ technological measures.
+ .
+ 4. Conveying Verbatim Copies.
+ .
+ You may convey verbatim copies of the Program's source code as you
+ receive it, in any medium, provided that you conspicuously and
+ appropriately publish on each copy an appropriate copyright notice;
+ keep intact all notices stating that this License and any
+ non-permissive terms added in accord with section 7 apply to the code;
+ keep intact all notices of the absence of any warranty; and give all
+ recipients a copy of this License along with the Program.
+ .
+ You may charge any price or no price for each copy that you convey,
+ and you may offer support or warranty protection for a fee.
+ .
+ 5. Conveying Modified Source Versions.
+ .
+ You may convey a work based on the Program, or the modifications to
+ produce it from the Program, in the form of source code under the
+ terms of section 4, provided that you also meet all of these conditions:
+ .
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+ .
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+ .
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+ .
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+ .
+ A compilation of a covered work with other separate and independent
+ works, which are not by their nature extensions of the covered work,
+ and which are not combined with it such as to form a larger program,
+ in or on a volume of a storage or distribution medium, is called an
+ "aggregate" if the compilation and its resulting copyright are not
+ used to limit the access or legal rights of the compilation's users
+ beyond what the individual works permit. Inclusion of a covered work
+ in an aggregate does not cause this License to apply to the other
+ parts of the aggregate.
+ .
+ 6. Conveying Non-Source Forms.
+ .
+ You may convey a covered work in object code form under the terms
+ of sections 4 and 5, provided that you also convey the
+ machine-readable Corresponding Source under the terms of this License,
+ in one of these ways:
+ .
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+ .
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+ .
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+ .
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+ .
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+ .
+ A separable portion of the object code, whose source code is excluded
+ from the Corresponding Source as a System Library, need not be
+ included in conveying the object code work.
+ .
+ A "User Product" is either (1) a "consumer product", which means any
+ tangible personal property which is normally used for personal, family,
+ or household purposes, or (2) anything designed or sold for incorporation
+ into a dwelling. In determining whether a product is a consumer product,
+ doubtful cases shall be resolved in favor of coverage. For a particular
+ product received by a particular user, "normally used" refers to a
+ typical or common use of that class of product, regardless of the status
+ of the particular user or of the way in which the particular user
+ actually uses, or expects or is expected to use, the product. A product
+ is a consumer product regardless of whether the product has substantial
+ commercial, industrial or non-consumer uses, unless such uses represent
+ the only significant mode of use of the product.
+ .
+ "Installation Information" for a User Product means any methods,
+ procedures, authorization keys, or other information required to install
+ and execute modified versions of a covered work in that User Product from
+ a modified version of its Corresponding Source. The information must
+ suffice to ensure that the continued functioning of the modified object
+ code is in no case prevented or interfered with solely because
+ modification has been made.
+ .
+ If you convey an object code work under this section in, or with, or
+ specifically for use in, a User Product, and the conveying occurs as
+ part of a transaction in which the right of possession and use of the
+ User Product is transferred to the recipient in perpetuity or for a
+ fixed term (regardless of how the transaction is characterized), the
+ Corresponding Source conveyed under this section must be accompanied
+ by the Installation Information. But this requirement does not apply
+ if neither you nor any third party retains the ability to install
+ modified object code on the User Product (for example, the work has
+ been installed in ROM).
+ .
+ The requirement to provide Installation Information does not include a
+ requirement to continue to provide support service, warranty, or updates
+ for a work that has been modified or installed by the recipient, or for
+ the User Product in which it has been modified or installed. Access to a
+ network may be denied when the modification itself materially and
+ adversely affects the operation of the network or violates the rules and
+ protocols for communication across the network.
+ .
+ Corresponding Source conveyed, and Installation Information provided,
+ in accord with this section must be in a format that is publicly
+ documented (and with an implementation available to the public in
+ source code form), and must require no special password or key for
+ unpacking, reading or copying.
+ .
+ 7. Additional Terms.
+ .
+ "Additional permissions" are terms that supplement the terms of this
+ License by making exceptions from one or more of its conditions.
+ Additional permissions that are applicable to the entire Program shall
+ be treated as though they were included in this License, to the extent
+ that they are valid under applicable law. If additional permissions
+ apply only to part of the Program, that part may be used separately
+ under those permissions, but the entire Program remains governed by
+ this License without regard to the additional permissions.
+ .
+ When you convey a copy of a covered work, you may at your option
+ remove any additional permissions from that copy, or from any part of
+ it. (Additional permissions may be written to require their own
+ removal in certain cases when you modify the work.) You may place
+ additional permissions on material, added by you to a covered work,
+ for which you have or can give appropriate copyright permission.
+ .
+ Notwithstanding any other provision of this License, for material you
+ add to a covered work, you may (if authorized by the copyright holders of
+ that material) supplement the terms of this License with terms:
+ .
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+ .
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+ .
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+ .
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+ .
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+ .
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+ .
+ All other non-permissive additional terms are considered "further
+ restrictions" within the meaning of section 10. If the Program as you
+ received it, or any part of it, contains a notice stating that it is
+ governed by this License along with a term that is a further
+ restriction, you may remove that term. If a license document contains
+ a further restriction but permits relicensing or conveying under this
+ License, you may add to a covered work material governed by the terms
+ of that license document, provided that the further restriction does
+ not survive such relicensing or conveying.
+ .
+ If you add terms to a covered work in accord with this section, you
+ must place, in the relevant source files, a statement of the
+ additional terms that apply to those files, or a notice indicating
+ where to find the applicable terms.
+ .
+ Additional terms, permissive or non-permissive, may be stated in the
+ form of a separately written license, or stated as exceptions;
+ the above requirements apply either way.
+ .
+ 8. Termination.
+ .
+ You may not propagate or modify a covered work except as expressly
+ provided under this License. Any attempt otherwise to propagate or
+ modify it is void, and will automatically terminate your rights under
+ this License (including any patent licenses granted under the third
+ paragraph of section 11).
+ .
+ However, if you cease all violation of this License, then your
+ license from a particular copyright holder is reinstated (a)
+ provisionally, unless and until the copyright holder explicitly and
+ finally terminates your license, and (b) permanently, if the copyright
+ holder fails to notify you of the violation by some reasonable means
+ prior to 60 days after the cessation.
+ .
+ Moreover, your license from a particular copyright holder is
+ reinstated permanently if the copyright holder notifies you of the
+ violation by some reasonable means, this is the first time you have
+ received notice of violation of this License (for any work) from that
+ copyright holder, and you cure the violation prior to 30 days after
+ your receipt of the notice.
+ .
+ Termination of your rights under this section does not terminate the
+ licenses of parties who have received copies or rights from you under
+ this License. If your rights have been terminated and not permanently
+ reinstated, you do not qualify to receive new licenses for the same
+ material under section 10.
+ .
+ 9. Acceptance Not Required for Having Copies.
+ .
+ You are not required to accept this License in order to receive or
+ run a copy of the Program. Ancillary propagation of a covered work
+ occurring solely as a consequence of using peer-to-peer transmission
+ to receive a copy likewise does not require acceptance. However,
+ nothing other than this License grants you permission to propagate or
+ modify any covered work. These actions infringe copyright if you do
+ not accept this License. Therefore, by modifying or propagating a
+ covered work, you indicate your acceptance of this License to do so.
+ .
+ 10. Automatic Licensing of Downstream Recipients.
+ .
+ Each time you convey a covered work, the recipient automatically
+ receives a license from the original licensors, to run, modify and
+ propagate that work, subject to this License. You are not responsible
+ for enforcing compliance by third parties with this License.
+ .
+ An "entity transaction" is a transaction transferring control of an
+ organization, or substantially all assets of one, or subdividing an
+ organization, or merging organizations. If propagation of a covered
+ work results from an entity transaction, each party to that
+ transaction who receives a copy of the work also receives whatever
+ licenses to the work the party's predecessor in interest had or could
+ give under the previous paragraph, plus a right to possession of the
+ Corresponding Source of the work from the predecessor in interest, if
+ the predecessor has it or can get it with reasonable efforts.
+ .
+ You may not impose any further restrictions on the exercise of the
+ rights granted or affirmed under this License. For example, you may
+ not impose a license fee, royalty, or other charge for exercise of
+ rights granted under this License, and you may not initiate litigation
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
+ any patent claim is infringed by making, using, selling, offering for
+ sale, or importing the Program or any portion of it.
+ .
+ 11. Patents.
+ .
+ A "contributor" is a copyright holder who authorizes use under this
+ License of the Program or a work on which the Program is based. The
+ work thus licensed is called the contributor's "contributor version".
+ .
+ A contributor's "essential patent claims" are all patent claims
+ owned or controlled by the contributor, whether already acquired or
+ hereafter acquired, that would be infringed by some manner, permitted
+ by this License, of making, using, or selling its contributor version,
+ but do not include claims that would be infringed only as a
+ consequence of further modification of the contributor version. For
+ purposes of this definition, "control" includes the right to grant
+ patent sublicenses in a manner consistent with the requirements of
+ this License.
+ .
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+ patent license under the contributor's essential patent claims, to
+ make, use, sell, offer for sale, import and otherwise run, modify and
+ propagate the contents of its contributor version.
+ .
+ In the following three paragraphs, a "patent license" is any express
+ agreement or commitment, however denominated, not to enforce a patent
+ (such as an express permission to practice a patent or covenant not to
+ sue for patent infringement). To "grant" such a patent license to a
+ party means to make such an agreement or commitment not to enforce a
+ patent against the party.
+ .
+ If you convey a covered work, knowingly relying on a patent license,
+ and the Corresponding Source of the work is not available for anyone
+ to copy, free of charge and under the terms of this License, through a
+ publicly available network server or other readily accessible means,
+ then you must either (1) cause the Corresponding Source to be so
+ available, or (2) arrange to deprive yourself of the benefit of the
+ patent license for this particular work, or (3) arrange, in a manner
+ consistent with the requirements of this License, to extend the patent
+ license to downstream recipients. "Knowingly relying" means you have
+ actual knowledge that, but for the patent license, your conveying the
+ covered work in a country, or your recipient's use of the covered work
+ in a country, would infringe one or more identifiable patents in that
+ country that you have reason to believe are valid.
+ .
+ If, pursuant to or in connection with a single transaction or
+ arrangement, you convey, or propagate by procuring conveyance of, a
+ covered work, and grant a patent license to some of the parties
+ receiving the covered work authorizing them to use, propagate, modify
+ or convey a specific copy of the covered work, then the patent license
+ you grant is automatically extended to all recipients of the covered
+ work and works based on it.
+ .
+ A patent license is "discriminatory" if it does not include within
+ the scope of its coverage, prohibits the exercise of, or is
+ conditioned on the non-exercise of one or more of the rights that are
+ specifically granted under this License. You may not convey a covered
+ work if you are a party to an arrangement with a third party that is
+ in the business of distributing software, under which you make payment
+ to the third party based on the extent of your activity of conveying
+ the work, and under which the third party grants, to any of the
+ parties who would receive the covered work from you, a discriminatory
+ patent license (a) in connection with copies of the covered work
+ conveyed by you (or copies made from those copies), or (b) primarily
+ for and in connection with specific products or compilations that
+ contain the covered work, unless you entered into that arrangement,
+ or that patent license was granted, prior to 28 March 2007.
+ .
+ Nothing in this License shall be construed as excluding or limiting
+ any implied license or other defenses to infringement that may
+ otherwise be available to you under applicable patent law.
+ .
+ 12. No Surrender of Others' Freedom.
+ .
+ If conditions are imposed on you (whether by court order, agreement or
+ otherwise) that contradict the conditions of this License, they do not
+ excuse you from the conditions of this License. If you cannot convey a
+ covered work so as to satisfy simultaneously your obligations under this
+ License and any other pertinent obligations, then as a consequence you may
+ not convey it at all. For example, if you agree to terms that obligate you
+ to collect a royalty for further conveying from those to whom you convey
+ the Program, the only way you could satisfy both those terms and this
+ License would be to refrain entirely from conveying the Program.
+ .
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+ .
+ Notwithstanding any other provision of this License, if you modify the
+ Program, your modified version must prominently offer all users
+ interacting with it remotely through a computer network (if your version
+ supports such interaction) an opportunity to receive the Corresponding
+ Source of your version by providing access to the Corresponding Source
+ from a network server at no charge, through some standard or customary
+ means of facilitating copying of software. This Corresponding Source
+ shall include the Corresponding Source for any work covered by version 3
+ of the GNU General Public License that is incorporated pursuant to the
+ following paragraph.
+ .
+ Notwithstanding any other provision of this License, you have
+ permission to link or combine any covered work with a work licensed
+ under version 3 of the GNU General Public License into a single
+ combined work, and to convey the resulting work. The terms of this
+ License will continue to apply to the part which is the covered work,
+ but the work with which it is combined will remain governed by version
+ 3 of the GNU General Public License.
+ .
+ 14. Revised Versions of this License.
+ .
+ The Free Software Foundation may publish revised and/or new versions of
+ the GNU Affero General Public License from time to time. Such new versions
+ will be similar in spirit to the present version, but may differ in detail to
+ address new problems or concerns.
+ .
+ Each version is given a distinguishing version number. If the
+ Program specifies that a certain numbered version of the GNU Affero General
+ Public License "or any later version" applies to it, you have the
+ option of following the terms and conditions either of that numbered
+ version or of any later version published by the Free Software
+ Foundation. If the Program does not specify a version number of the
+ GNU Affero General Public License, you may choose any version ever published
+ by the Free Software Foundation.
+ .
+ If the Program specifies that a proxy can decide which future
+ versions of the GNU Affero General Public License can be used, that proxy's
+ public statement of acceptance of a version permanently authorizes you
+ to choose that version for the Program.
+ .
+ Later license versions may give you additional or different
+ permissions. However, no additional obligations are imposed on any
+ author or copyright holder as a result of your choosing to follow a
+ later version.
+ .
+ 15. Disclaimer of Warranty.
+ .
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+ .
+ 16. Limitation of Liability.
+ .
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGES.
+ .
+ 17. Interpretation of Sections 15 and 16.
+ .
+ If the disclaimer of warranty and limitation of liability provided
+ above cannot be given local legal effect according to their terms,
+ reviewing courts shall apply local law that most closely approximates
+ an absolute waiver of all civil liability in connection with the
+ Program, unless a warranty or assumption of liability accompanies a
+ copy of the Program in return for a fee.
+ .
+ END OF TERMS AND CONDITIONS
+ .
+ How to Apply These Terms to Your New Programs
+ .
+ If you develop a new program, and you want it to be of the greatest
+ possible use to the public, the best way to achieve this is to make it
+ free software which everyone can redistribute and change under these terms.
+ .
+ To do so, attach the following notices to the program. It is safest
+ to attach them to the start of each source file to most effectively
+ state the exclusion of warranty; and each file should have at least
+ the "copyright" line and a pointer to where the full notice is found.
+ .
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+ .
+ This program 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 of the License, or
+ (at your option) any later version.
+ .
+ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ .
+ Also add information on how to contact you by electronic and paper mail.
+ .
+ If your software can interact with users remotely through a computer
+ network, you should also make sure that it provides a way for users to
+ get its source. For example, if your program is a web application, its
+ interface could display a "Source" link that leads users to an archive
+ of the code. There are many ways you could offer source, and different
+ solutions will be better for different programs; see section 13 for the
+ specific requirements.
+ .
+ You should also get your employer (if you work as a programmer) or school,
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
+ For more information on this, and how to apply and follow the GNU AGPL, see
+ <http://www.gnu.org/licenses/>.
diff --git a/debian/etc-libtalerexchange/taler/overrides.conf b/debian/etc-libtalerexchange/taler/overrides.conf
new file mode 100644
index 000000000..60296ead4
--- /dev/null
+++ b/debian/etc-libtalerexchange/taler/overrides.conf
@@ -0,0 +1 @@
+# This configuration will be changed by tooling. Do not touch it manually.
diff --git a/debian/etc-libtalerexchange/taler/taler.conf b/debian/etc-libtalerexchange/taler/taler.conf
new file mode 100644
index 000000000..2cf815656
--- /dev/null
+++ b/debian/etc-libtalerexchange/taler/taler.conf
@@ -0,0 +1,49 @@
+# Main entry point for the GNU Taler configuration.
+#
+# Structure:
+# - taler.conf is the main configuration entry point
+# used by all Taler components (the file you are currently
+# looking at.
+# - overrides.conf contains configuration overrides that are
+# set by some tools that help with the configuration,
+# and should not be edited by humans. Comments in this file
+# are not preserved.
+# - conf.d/ contains configuration files for
+# Taler components, which can be read by all
+# users of the system and are included by the main
+# configuration.
+# - secrets/ contains configuration snippets
+# with secrets for particular services.
+# These files should have restrictive permissions
+# so that only users of the relevant services
+# can read it. All files in it should end with
+# ".secret.conf".
+
+[taler]
+
+# Currency of the Taler deployment. This setting applies to all Taler
+# components that only support a single currency.
+#currency = KUDOS
+
+# Smallest currency unit handled by the underlying bank system. Taler payments
+# can make payments smaller than this units, but interactions with external
+# 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]
+
+TALER_HOME = /var/lib/taler/
+TALER_RUNTIME_DIR = /run/taler/
+TALER_CACHE_HOME = /var/cache/taler/
+TALER_CONFIG_HOME = /etc/taler/
+TALER_DATA_HOME = /var/lib/taler/
+
+
+# Inline configurations from all Taler components.
+@inline-matching@ conf.d/*.conf
+
+# Overrides from tools that help with configuration.
+@inline@ overrides.conf
diff --git a/debian/etc-taler-auditor/apache2/sites-available/taler-auditor.conf b/debian/etc-taler-auditor/apache2/sites-available/taler-auditor.conf
new file mode 100644
index 000000000..f68c59558
--- /dev/null
+++ b/debian/etc-taler-auditor/apache2/sites-available/taler-auditor.conf
@@ -0,0 +1,4 @@
+<Location "/taler-auditor/">
+ProxyPass "unix:/var/lib/taler-auditor/auditor.sock|http://example.com/"
+RequestHeader add "X-Forwarded-Proto" "https"
+</Location>
diff --git a/debian/etc-taler-auditor/nginx/sites-available/taler-auditor b/debian/etc-taler-auditor/nginx/sites-available/taler-auditor
new file mode 100644
index 000000000..f74035d53
--- /dev/null
+++ b/debian/etc-taler-auditor/nginx/sites-available/taler-auditor
@@ -0,0 +1,18 @@
+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";
+ }
+} \ No newline at end of file
diff --git a/debian/etc-taler-auditor/taler/conf.d/auditor-system.conf b/debian/etc-taler-auditor/taler/conf.d/auditor-system.conf
new file mode 100644
index 000000000..3d3aef33a
--- /dev/null
+++ b/debian/etc-taler-auditor/taler/conf.d/auditor-system.conf
@@ -0,0 +1,12 @@
+# Read secret sections into configuration, but only
+# if we have permission to do so.
+@inline-secret@ auditordb-postgres ../secrets/auditor-db.secret.conf
+
+[auditor]
+# Debian package is configured to use a reverse proxy with a UNIX
+# domain socket. See nginx/apache configuration files.
+SERVE = UNIX
+UNIXPATH = /var/lib/taler-auditor/auditor.sock
+
+# Only supported database is Postgres right now.
+DATABASE = postgres
diff --git a/debian/etc-taler-auditor/taler/secrets/auditor-db.secret.conf b/debian/etc-taler-auditor/taler/secrets/auditor-db.secret.conf
new file mode 100644
index 000000000..1278a563b
--- /dev/null
+++ b/debian/etc-taler-auditor/taler/secrets/auditor-db.secret.conf
@@ -0,0 +1,10 @@
+# Database configuration for the Taler auditor.
+
+[auditordb-postgres]
+
+# Typically, there should only be a single line here, of the form:
+
+CONFIG=postgres:///taler-auditor
+
+# The details of the URI depend on where the database lives and how
+# access control was configured.
diff --git a/debian/etc-taler-exchange/apache2/sites-available/taler-exchange.conf b/debian/etc-taler-exchange/apache2/sites-available/taler-exchange.conf
new file mode 100644
index 000000000..3ec14feb2
--- /dev/null
+++ b/debian/etc-taler-exchange/apache2/sites-available/taler-exchange.conf
@@ -0,0 +1,4 @@
+<Location "/taler-exchange/">
+ProxyPass "unix:/run/taler/exchange-httpd/exchange-http.sock|http://example.com/"
+RequestHeader add "X-Forwarded-Proto" "https"
+</Location>
diff --git a/debian/etc-taler-exchange/nginx/sites-available/taler-exchange b/debian/etc-taler-exchange/nginx/sites-available/taler-exchange
new file mode 100644
index 000000000..9b61a32df
--- /dev/null
+++ b/debian/etc-taler-exchange/nginx/sites-available/taler-exchange
@@ -0,0 +1,17 @@
+server {
+ listen 80;
+ listen [::]:80;
+
+ 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 "localhost";
+ #proxy_set_header X-Forwarded-Proto "https";
+ }
+}
diff --git a/debian/etc-taler-exchange/taler/conf.d/exchange-business.conf b/debian/etc-taler-exchange/taler/conf.d/exchange-business.conf
new file mode 100644
index 000000000..d5938f2b1
--- /dev/null
+++ b/debian/etc-taler-exchange/taler/conf.d/exchange-business.conf
@@ -0,0 +1,50 @@
+# Configuration for business-level aspects of the exchange.
+
+[exchange]
+
+# Here you MUST add the master public key of the offline system
+# 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 =
+
+# Publicly visible base URL of the exchange.
+# BASE_URL = https://example.com/
+# BASE_URL =
+
+# Here you MUST configure the amount above which transactions are
+# always subject to manual AML review.
+# AML_THRESHOLD =
+
+# Attribute encryption key for storing attributes encrypted
+# in the database. Should be a high-entropy nonce.
+ATTRIBUTE_ENCRYPTION_KEY = SET_ME_PLEASE
+
+# For your terms of service and privacy policy, you should specify
+# an Etag that must be updated whenever there are significant
+# changes to either document. The format is up to you, what matters
+# is that the value is updated and never re-used. See the HTTP
+# specification on Etags.
+# TERMS_ETAG =
+# PRIVACY_ETAG =
+
+SERVE = unix
+UNIXPATH_MODE = 666
+
+# Bank accounts used by the exchange should be specified here:
+[exchange-account-1]
+
+ENABLE_CREDIT = NO
+ENABLE_DEBIT = NO
+
+# Account identifier in the form of an RFC-8905 payto:// URI.
+# For SEPA, looks like payto://sepa/$IBAN?receiver-name=$NAME
+# Make sure to URL-encode spaces in $NAME!
+PAYTO_URI =
+
+# Credentials to access the account are in a separate
+# config file with restricted permissions.
+@inline-secret@ exchange-accountcredentials-1 ../secrets/exchange-accountcredentials-1.secret.conf
+
+
+
diff --git a/debian/etc-taler-exchange/taler/conf.d/exchange-coins.conf b/debian/etc-taler-exchange/taler/conf.d/exchange-coins.conf
new file mode 100644
index 000000000..8294525cb
--- /dev/null
+++ b/debian/etc-taler-exchange/taler/conf.d/exchange-coins.conf
@@ -0,0 +1,33 @@
+#
+# This configuration file specifies the various denominations offered by your
+# exchange.
+#
+# Each denomination must be specified in a sections starting with
+# "coin_".
+#
+# What follows is an example.
+#
+
+# [coin_FOO]
+## Actual value of the coin
+#VALUE = KUDOS:1
+
+## How long will one key be used for withdrawals?
+#DURATION_WITHDRAW = 7 days
+
+## How long do users have to spend their coins?
+#DURATION_SPEND = 2 years
+
+## How long does the exchange keep the proofs around for legal disputes?
+#DURATION_LEGAL = 6 years
+
+## Fees charged. Note that for the lowest denomination, the
+## fee must precisely be the lowest denomination, or zero.
+#FEE_WITHDRAW = KUDOS:0
+#FEE_DEPOSIT = KUDOS:0
+#FEE_REFRESH = KUDOS:0
+#FEE_REFUND = KUDOS:0
+
+## How long should the RSA keys be. Do not change unless you really know
+## what you are doing (consult your local cryptographer first!).
+#RSA_KEYSIZE = 2048
diff --git a/debian/etc-taler-exchange/taler/conf.d/exchange-system.conf b/debian/etc-taler-exchange/taler/conf.d/exchange-system.conf
new file mode 100644
index 000000000..4ad7e06f6
--- /dev/null
+++ b/debian/etc-taler-exchange/taler/conf.d/exchange-system.conf
@@ -0,0 +1,13 @@
+# Configuration settings for system parameters of the exchange.
+
+# Read secret sections into configuration, but only
+# if we have permission to do so.
+@inline-secret@ exchangedb-postgres ../secrets/exchange-db.secret.conf
+
+[exchange]
+
+# Only supported database is Postgres right now.
+DATABASE = postgres
+
+
+
diff --git a/debian/etc-taler-exchange/taler/secrets/exchange-accountcredentials-1.secret.conf b/debian/etc-taler-exchange/taler/secrets/exchange-accountcredentials-1.secret.conf
new file mode 100644
index 000000000..8c8d14320
--- /dev/null
+++ b/debian/etc-taler-exchange/taler/secrets/exchange-accountcredentials-1.secret.conf
@@ -0,0 +1,17 @@
+# This file contains the secret credentials
+# to access the Taler Wire Gateway API (usually
+# provided by LibEuFin) for the exchange accounts.
+#
+# Each exchange-account-* section should have a matching
+# exchange-accountcredentials-* section here.
+#
+# Each of those sections must be imported via @inline-secret@,
+# usually in conf.d/exchange-business.conf.
+
+[exchange-accountcredentials-1]
+
+wire_gateway_auth_method = basic
+password =
+username =
+wire_gateway_url =
+
diff --git a/debian/etc-taler-exchange/taler/secrets/exchange-db.secret.conf b/debian/etc-taler-exchange/taler/secrets/exchange-db.secret.conf
new file mode 100644
index 000000000..08c20074c
--- /dev/null
+++ b/debian/etc-taler-exchange/taler/secrets/exchange-db.secret.conf
@@ -0,0 +1,10 @@
+# Database configuration for the Taler exchange.
+
+[exchangedb-postgres]
+
+# Typically, there should only be a single line here, of the form:
+
+CONFIG=postgres:///taler-exchange
+
+# The details of the URI depend on where the database lives and how
+# access control was configured.
diff --git a/debian/libtalerexchange-dev.install b/debian/libtalerexchange-dev.install
new file mode 100644
index 000000000..aa1de818a
--- /dev/null
+++ b/debian/libtalerexchange-dev.install
@@ -0,0 +1,32 @@
+# Benchmarks, only install them for the dev package.
+usr/bin/taler-aggregator-benchmark
+usr/bin/taler-bank-benchmark
+usr/bin/taler-exchange-benchmark
+usr/bin/taler-exchange-kyc-tester
+usr/bin/taler-fakebank-run
+usr/bin/taler-unified-setup.sh
+usr/bin/taler-exchange-kyc-oauth2-test-converter.sh
+
+# Man pages
+usr/share/man/man1/taler-aggregator-benchmark*
+usr/share/man/man1/taler-bank-benchmark*
+usr/share/man/man1/taler-exchange-kyc-tester*
+usr/share/man/man1/taler-exchange-benchmark*
+usr/share/man/man1/taler-fakebank-run*
+usr/share/man/man1/taler-unified-setup*
+
+
+# Headers
+usr/include/taler/*
+
+# Plain .so symlinks
+usr/lib/*/libtaler*.so
+
+# Testing libraries
+usr/lib/*/libtalertesting.so.*
+usr/lib/*/libtalerfakebank.so.*
+usr/lib/*/libtalertesting.so
+usr/lib/*/libtalerfakebank.so
+
+# Documentation
+usr/share/info/taler-developer-manual*
diff --git a/debian/libtalerexchange.dirs b/debian/libtalerexchange.dirs
new file mode 100644
index 000000000..6b2cf9385
--- /dev/null
+++ b/debian/libtalerexchange.dirs
@@ -0,0 +1 @@
+/var/lib/taler
diff --git a/debian/libtalerexchange.install b/debian/libtalerexchange.install
new file mode 100644
index 000000000..f3c52ba8d
--- /dev/null
+++ b/debian/libtalerexchange.install
@@ -0,0 +1,10 @@
+usr/lib/*/libtaler*.so.*
+
+# FIXME: All this should eventually go into taler-base.
+usr/share/taler/config.d/paths.conf
+usr/share/taler/config.d/currencies.conf
+usr/share/taler/config.d/taler.conf
+debian/etc-libtalerexchange/* etc/
+usr/bin/taler-config
+usr/share/man/man5/taler.conf.5
+usr/share/man/man1/taler-config*
diff --git a/debian/libtalerexchange.postinst b/debian/libtalerexchange.postinst
new file mode 100644
index 000000000..40b4be061
--- /dev/null
+++ b/debian/libtalerexchange.postinst
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+set -e
+
+. /usr/share/debconf/confmodule
+
+case "${1}" in
+configure)
+
+ if ! dpkg-statoverride --list /etc/taler/taler.conf >/dev/null 2>&1; then
+ dpkg-statoverride --add --update \
+ root root 644 \
+ /etc/taler/taler.conf
+ fi
+
+ ;;
+
+abort-upgrade | abort-remove | abort-deconfigure) ;;
+
+*)
+ echo "postinst called with unknown argument \`${1}'" >&2
+ exit 1
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/libtalerexchange.tmpfiles b/debian/libtalerexchange.tmpfiles
new file mode 100644
index 000000000..dcac0ba87
--- /dev/null
+++ b/debian/libtalerexchange.tmpfiles
@@ -0,0 +1,2 @@
+#Type Path Mode UID GID Age Argument
+d /run/taler 0755 root root - -
diff --git a/debian/patches/0001-Dont_copy_license_file.patch b/debian/patches/0001-Dont_copy_license_file.patch
new file mode 100644
index 000000000..5b13ab66e
--- /dev/null
+++ b/debian/patches/0001-Dont_copy_license_file.patch
@@ -0,0 +1,22 @@
+From: Bertrand Marc <bmarc@debian.org>
+Date: Sun, 5 Jul 2020 14:58:43 +0200
+Subject: Dont_copy_license_file
+
+---
+ contrib/Makefile.inc | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/contrib/Makefile.inc b/contrib/Makefile.inc
+index a563ef4..c737a07 100644
+--- a/contrib/Makefile.inc
++++ b/contrib/Makefile.inc
+@@ -8,8 +8,7 @@ BUILDCOMMON_SHLIB_FILES = \
+ build-common/sh/lib.sh/existence_python.sh \
+ build-common/sh/lib.sh/msg.sh \
+ build-common/sh/lib.sh/progname.sh \
+- build-common/sh/lib.sh/version_gnunet.sh \
+- build-common/LICENSE
++ build-common/sh/lib.sh/version_gnunet.sh
+
+ BUILDCOMMON_CONF_FILES = \
+ build-common/conf/.dir-locals.el \
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 000000000..f192b8afb
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1 @@
+0001-Dont_copy_license_file.patch
diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in
new file mode 100644
index 000000000..819ba49a6
--- /dev/null
+++ b/debian/po/POTFILES.in
@@ -0,0 +1 @@
+[type: gettext/rfc822deb] taler-exchange.templates
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 000000000..3d8809c50
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,70 @@
+#!/usr/bin/make -f
+
+SHELL := sh -e
+
+include /usr/share/dpkg/architecture.mk
+
+%:
+ dh ${@}
+
+override_dh_builddeb:
+ dh_builddeb -- -Zgzip
+
+override_dh_auto_configure-arch:
+ dh_auto_configure -- --disable-rpath --with-microhttpd=yes $(shell dpkg-buildflags --export=configure)
+
+override_dh_auto_configure-indep:
+
+override_dh_auto_build-indep:
+
+override_dh_auto_test:
+ # Disabling test suite, incomplete
+
+override_dh_auto_install-arch:
+ dh_auto_install
+
+ # Removing useless files
+ rm -f debian/tmp/usr/lib/$(DEB_HOST_MULTIARCH)/*.la \
+ debian/tmp/usr/lib/$(DEB_HOST_MULTIARCH)/taler/*.la \
+ debian/tmp/usr/share/doc/taler/COPYING
+
+override_dh_auto_install-indep:
+
+override_dh_auto_clean:
+ dh_auto_clean
+
+override_dh_installsystemd:
+ # Need to specify units manually, since we have multiple
+ # and dh_installsystemd by default only looks for "<package>.service".
+ dh_installsystemd -ptaler-exchange --name=taler-exchange-httpd --no-start --no-enable --no-stop-on-upgrade
+ dh_installsystemd -ptaler-exchange --name=taler-exchange-aggregator --no-start --no-enable --no-stop-on-upgrade
+ dh_installsystemd -ptaler-exchange --name=taler-exchange-expire --no-start --no-enable --no-stop-on-upgrade
+ dh_installsystemd -ptaler-exchange --name=taler-exchange-transfer --no-start --no-enable --no-stop-on-upgrade
+ dh_installsystemd -ptaler-exchange --name=taler-exchange-wirewatch --no-start --no-enable --no-stop-on-upgrade
+ dh_installsystemd -ptaler-exchange --name=taler-exchange-secmod-cs --no-start --no-enable --no-stop-on-upgrade
+ dh_installsystemd -ptaler-exchange --name=taler-exchange-secmod-eddsa --no-start --no-enable --no-stop-on-upgrade
+ dh_installsystemd -ptaler-exchange --name=taler-exchange-secmod-rsa --no-start --no-enable --no-stop-on-upgrade
+ dh_installsystemd -ptaler-exchange --name=taler-exchange-closer --no-start --no-enable --no-stop-on-upgrade
+ dh_installsystemd -ptaler-exchange --name=taler-exchange --no-start --no-enable --no-stop-on-upgrade
+ dh_installsystemd -ptaler-auditor --name=taler-auditor-httpd --no-start --no-enable --no-stop-on-upgrade
+ dh_installsystemd -ptaler-auditor --name=taler-helper-auditor-deposits --no-start --no-enable --no-stop-on-upgrade
+ dh_installsystemd -ptaler-exchange-offline --name=taler-exchange-offline --no-start --no-enable --no-stop-on-upgrade
+ dh_installsystemd -ptaler-exchange-offline --name=taler-exchange-offline.timer --no-start --no-enable --no-stop-on-upgrade
+ # final invocation to generate daemon reload
+ dh_installsystemd
+
+override_dh_install:
+ dh_install
+# With debhelper-compat=12, we still need to call this manually
+ dh_installtmpfiles
+# Remove files already present in libtalerexchange from main taler-exchange package
+ cd debian/libtalerexchange-dev; find . -type f,l -exec rm -f ../libtalerauditor/{} \;
+ cd debian/libtalerexchange-dev; find . -type f,l -exec rm -f ../taler-exchange/{} \;
+ cd debian/libtalerexchange-dev; find . -type f,l -exec rm -f ../taler-auditor/{} \;
+ cd debian/libtalerexchange-dev; find . -type f,l -exec rm -f ../libtalerexchange/{} \;
+ cd debian/libtalerexchange-dev; find . -type f,l -exec rm -f ../libtalerauditor/{} \;
+ cd debian/taler-auditor; find . -type f,l -exec rm -f ../libtalerauditor/{} \;
+ cd debian/taler-auditor; find . -type f,l -exec rm -f ../libtalerexchange/{} \;
+ cd debian/taler-auditor; find . -type f,l -exec rm -f ../taler-exchange/{} \;
+ cd debian/taler-exchange-database; find . -type f,l -exec rm -f ../taler-exchange/{} \;
+ cd debian/libtalerexchange; find . -type f,l -exec rm -f ../taler-exchange/{} \;
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 000000000..163aaf8d8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/debian/source/options b/debian/source/options
new file mode 100644
index 000000000..e928a9118
--- /dev/null
+++ b/debian/source/options
@@ -0,0 +1,3 @@
+extend-diff-ignore = "^(config\.sub|config\.guess|Makefile)$"
+
+
diff --git a/debian/taler-auditor.install b/debian/taler-auditor.install
new file mode 100644
index 000000000..4f3d5a1b2
--- /dev/null
+++ b/debian/taler-auditor.install
@@ -0,0 +1,24 @@
+usr/bin/taler-auditor
+usr/bin/taler-auditor-dbconfig
+usr/bin/taler-auditor-dbinit
+usr/bin/taler-auditor-httpd
+usr/bin/taler-auditor-offline
+usr/bin/taler-auditor-sync
+usr/bin/taler-helper-auditor-*
+
+usr/lib/*/taler/libtaler_plugin_auditor*.so
+usr/lib/*/libauditor*
+usr/lib/*/libtalerauditordb*
+
+usr/share/man/man1/taler-auditor*
+usr/share/man/man1/taler-helper-auditor*
+
+usr/share/info/taler-auditor*
+
+usr/share/taler/config.d/auditor*
+usr/share/taler/sql/auditor/*
+
+# Configuration
+debian/etc-taler-auditor/* etc/
+
+usr/share/taler/exchange/auditor-report.tex.j2
diff --git a/debian/taler-auditor.postinst b/debian/taler-auditor.postinst
new file mode 100644
index 000000000..847e4aac1
--- /dev/null
+++ b/debian/taler-auditor.postinst
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+set -e
+
+. /usr/share/debconf/confmodule
+
+CONFIG_FILE="/etc/default/taler-auditor"
+TALER_HOME="/var/lib/taler-auditor"
+_USERNAME=taler-auditor-httpd
+_GROUPNAME=taler-auditor-httpd
+
+case "${1}" in
+configure)
+ # Creating taler groups as needed
+ if ! getent group ${_GROUPNAME} >/dev/null; then
+ addgroup --quiet --system ${_GROUPNAME}
+ fi
+ # Creating taler users if needed
+ if ! getent passwd ${_USERNAME} >/dev/null; then
+ adduser --quiet --system --ingroup ${_GROUPNAME} --no-create-home --home ${TALER_HOME} ${_USERNAME}
+ fi
+
+ if ! dpkg-statoverride --list /etc/taler/secrets/auditor-db.secret.conf >/dev/null 2>&1
+ then
+ dpkg-statoverride --add --update \
+ ${_USERNAME} ${_GROUPNAME} 640 \
+ /etc/taler/secrets/auditor-db.secret.conf
+ fi
+
+ ;;
+
+abort-upgrade | abort-remove | abort-deconfigure) ;;
+*)
+ echo "postinst called with unknown argument \`${1}'" >&2
+ exit 1
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/taler-auditor.postrm b/debian/taler-auditor.postrm
new file mode 100644
index 000000000..639e3241e
--- /dev/null
+++ b/debian/taler-auditor.postrm
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+set -e
+
+if [ -f /usr/share/debconf/confmodule ]; then
+ . /usr/share/debconf/confmodule
+fi
+
+_USERNAME=taler-auditor-httpd
+_GROUPNAME=taler-auditor-httpd
+
+case "${1}" in
+purge)
+ dpkg-statoverride --remove \
+ /etc/taler/secrets/auditor-db.secret.conf || true
+ deluser --system --quiet ${_USERNAME} || true
+ delgroup --only-if-empty --quiet ${_GROUPNAME} || true
+ ;;
+
+remove | upgrade | failed-upgrade | abort-install | abort-upgrade | disappear) ;;
+*)
+ echo "postrm called with unknown argument \`${1}'" >&2
+ exit 1
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/taler-auditor.taler-auditor-httpd.service b/debian/taler-auditor.taler-auditor-httpd.service
new file mode 100644
index 000000000..ac68e41c8
--- /dev/null
+++ b/debian/taler-auditor.taler-auditor-httpd.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=GNU Taler payment system auditor REST API
+After=postgres.service network.target
+
+[Service]
+User=taler-auditor-httpd
+Type=simple
+Restart=on-failure
+RestartPreventExitStatus=9
+ExecStart=/usr/bin/taler-auditor-httpd -c /etc/taler/taler.conf -L INFO
+
+[Install]
+WantedBy=multi-user.target
diff --git a/debian/taler-auditor.taler-helper-auditor-deposits.service b/debian/taler-auditor.taler-helper-auditor-deposits.service
new file mode 100644
index 000000000..7185a8d52
--- /dev/null
+++ b/debian/taler-auditor.taler-helper-auditor-deposits.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=GNU Taler auditor helper reporting confirmation deposits
+After=postgres.service
+
+[Service]
+User=taler-auditor-httpd
+Type=simple
+Restart=always
+RestartSec=1s
+RestartPreventExitStatus=9
+ExecStart=/usr/bin/taler-helper-auditor-deposits -c /etc/taler/taler.conf -L INFO
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+RuntimeMaxSec=3600s
diff --git a/debian/taler-auditor.tmpfiles b/debian/taler-auditor.tmpfiles
new file mode 100644
index 000000000..37e214acb
--- /dev/null
+++ b/debian/taler-auditor.tmpfiles
@@ -0,0 +1,2 @@
+#Type Path Mode UID GID Age Argument
+d /run/taler/auditor-httpd 0755 taler-auditor-httpd taler-auditor-httpd - -
diff --git a/debian/taler-exchange-database.install b/debian/taler-exchange-database.install
new file mode 100644
index 000000000..da8b0dc47
--- /dev/null
+++ b/debian/taler-exchange-database.install
@@ -0,0 +1,8 @@
+usr/bin/taler-exchange-dbconfig
+usr/bin/taler-exchange-dbinit
+usr/lib/*/taler/libtaler_plugin_exchange*.so
+usr/share/man/man1/taler-exchange-dbconfig.1
+usr/share/man/man1/taler-exchange-dbinit.1
+usr/share/taler/sql/exchange/*
+usr/share/taler/config.d/exchangedb.conf
+usr/share/taler/config.d/exchangedb-postgres.conf
diff --git a/debian/taler-exchange-offline.install b/debian/taler-exchange-offline.install
new file mode 100644
index 000000000..617715d2c
--- /dev/null
+++ b/debian/taler-exchange-offline.install
@@ -0,0 +1,2 @@
+usr/bin/taler-exchange-offline
+usr/share/man/man1/taler-exchange-offline*
diff --git a/debian/taler-exchange-offline.postinst b/debian/taler-exchange-offline.postinst
new file mode 100644
index 000000000..337bfa5d4
--- /dev/null
+++ b/debian/taler-exchange-offline.postinst
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+set -e
+
+. /usr/share/debconf/confmodule
+
+case "${1}" in
+configure)
+
+ if ! getent group taler-exchange-offline >/dev/null; then
+ addgroup --quiet taler-exchange-offline
+ fi
+
+ if ! getent passwd taler-exchange-offline >/dev/null; then
+ adduser --quiet \
+ --disabled-password \
+ --system \
+ --shell /bin/bash \
+ --home /home/taler-exchange-offline \
+ --ingroup taler-exchange-offline \
+ taler-exchange-offline
+ fi
+
+ ;;
+
+abort-upgrade | abort-remove | abort-deconfigure)
+
+ ;;
+
+*)
+ echo "postinst called with unknown argument \`${1}'" >&2
+ exit 1
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/taler-exchange-offline.taler-exchange-offline.service b/debian/taler-exchange-offline.taler-exchange-offline.service
new file mode 100644
index 000000000..bd1b93bb3
--- /dev/null
+++ b/debian/taler-exchange-offline.taler-exchange-offline.service
@@ -0,0 +1,23 @@
+# This file is in the public domain.
+#
+# This service is expected to be run via the respective
+# timer to ensure that the keys and fees of the exchange
+# are always current.
+#
+# You are expected to edit it to match your desired
+# setup!
+#
+[Unit]
+Description=Daily taler-exchange-offline run
+Documentation=man:taler-exchange-offline(1)
+
+[Service]
+Type=oneshot
+User=taler-exchange-offline
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+
+Environment="REGIO_CURRENCY=$(taler-config -s taler -o CURRENCY)"
+ExecStart=bash -c 'taler-exchange-offline download sign wire-fee now iban "${REGIO_CURRENCY}":0 "${REGIO_CURRENCY}":0 wire-fee now x-taler-bank "${REGIO_CURRENCY}":0 "${REGIO_CURRENCY}":0 global-fee now "${REGIO_CURRENCY}:0" "${REGIO_CURRENCY}:0" "${REGIO_CURRENCY}:0" 4weeks 6years 4 upload'
diff --git a/debian/taler-exchange-offline.taler-exchange-offline.timer b/debian/taler-exchange-offline.taler-exchange-offline.timer
new file mode 100644
index 000000000..5e605e818
--- /dev/null
+++ b/debian/taler-exchange-offline.taler-exchange-offline.timer
@@ -0,0 +1,20 @@
+# This file is in the public domain.
+#
+# Note that this timer is deliberately NOT active
+# by default as it is ONLY applicable if the
+# taler-exchange-offline tool is run on the *online*
+# service and not actually offline. It is provided
+# for convenience in setups that do not use offline
+# signing. You may need to adjust the
+# taler-exchange-offline.service file before using it!
+[Unit]
+Description=taler-exchange-offline maintenance
+Documentation=man:taler-exchange-offline(1)
+
+[Timer]
+OnCalendar=daily
+AccuracySec=12h
+Persistent=true
+
+[Install]
+WantedBy=timers.target
diff --git a/debian/taler-exchange-offline.tmpfiles b/debian/taler-exchange-offline.tmpfiles
new file mode 100644
index 000000000..5f9dcb011
--- /dev/null
+++ b/debian/taler-exchange-offline.tmpfiles
@@ -0,0 +1,2 @@
+#Type Path Mode UID GID Age Argument
+d /var/lib/taler/exchange-offline 0700 taler-exchange-offline taler-exchange-offline - -
diff --git a/debian/taler-exchange.README.Debian b/debian/taler-exchange.README.Debian
new file mode 100644
index 000000000..cce5d9ffb
--- /dev/null
+++ b/debian/taler-exchange.README.Debian
@@ -0,0 +1,34 @@
+taler-exchange
+--------------
+
+Note that the configuration is incomplete, and that Debian cannot launch an
+exchange with this minimal template. You must:
+
+* Configure the Postgres database for the exchange, ideally including
+ remote replication of the database to the auditor.
+* Run `taler-exchange-dbinit` (also after package upgrades).
+* Edit ``/etc/taler-secmod.conf`` to must setup the currency and denominations
+ details.
+* Edit `/etc/taler-wire.conf` to provide details about the bank account access.
+* Run `taler-exchange-offline setup` on your offline system and add
+ the resulting master public key into the ``[exchange]`` section of
+ ``/etc/taler-exchange.conf`` under ``MASTER_PUBLIC_KEY``.
+
+
+None of these are done by the Debian package because we cannot provide the
+required complete configuration details.
+
+
+Once you have done this, you can use the following commands to start, stop or
+restart the Taler exchange:
+
+ # systemctl start taler-exchange-httpd.service
+ # systemctl stop taler-exchange-httpd.service
+ # systemctl restart taler-exchange-httpd.service
+
+To permanently the exchange whenever the system boots, use:
+
+ # systemctl enable taler-exchange-httpd
+
+
+ -- Christian Grothoff <grothoff@gnu.org> Mon 28 Dec 2020 11:37:14 AM CET
diff --git a/debian/taler-exchange.docs b/debian/taler-exchange.docs
new file mode 100644
index 000000000..62deb0497
--- /dev/null
+++ b/debian/taler-exchange.docs
@@ -0,0 +1 @@
+AUTHORS
diff --git a/debian/taler-exchange.install b/debian/taler-exchange.install
new file mode 100644
index 000000000..f8fef2c3b
--- /dev/null
+++ b/debian/taler-exchange.install
@@ -0,0 +1,40 @@
+usr/bin/taler-exchange-aggregator
+usr/bin/taler-exchange-closer
+usr/bin/taler-exchange-drain
+usr/bin/taler-exchange-expire
+usr/bin/taler-exchange-httpd
+usr/bin/taler-exchange-kyc-aml-pep-trigger.sh
+usr/bin/taler-exchange-kyc-oauth2-challenger.sh
+usr/bin/taler-exchange-kyc-kycaid-converter.sh
+usr/bin/taler-exchange-kyc-persona-converter.sh
+usr/bin/taler-exchange-router
+usr/bin/taler-exchange-secmod-cs
+usr/bin/taler-exchange-secmod-eddsa
+usr/bin/taler-exchange-secmod-rsa
+usr/bin/taler-exchange-transfer
+usr/bin/taler-exchange-wirewatch
+usr/bin/taler-exchange-wire-gateway-client
+usr/lib/*/taler/libtaler_plugin_kyclogic_*.so
+usr/lib/*/taler/libtaler_extension_*.so
+usr/share/man/man1/taler-exchange-aggregator*
+usr/share/man/man1/taler-exchange-closer*
+usr/share/man/man1/taler-exchange-dbconfig*
+usr/share/man/man1/taler-exchange-dbinit*
+usr/share/man/man1/taler-exchange-drain*
+usr/share/man/man1/taler-exchange-expire*
+usr/share/man/man1/taler-exchange-httpd*
+usr/share/man/man1/taler-exchange-kyc-aml-pep-trigger*
+usr/share/man/man1/taler-exchange-router*
+usr/share/man/man1/taler-exchange-secmod-cs*
+usr/share/man/man1/taler-exchange-secmod-eddsa*
+usr/share/man/man1/taler-exchange-secmod-rsa*
+usr/share/man/man1/taler-exchange-transfer*
+usr/share/man/man1/taler-exchange-wire-gateway-client*
+usr/share/man/man1/taler-exchange-wirewatch*
+usr/share/info/taler-exchange*
+usr/share/taler/config.d/*
+usr/share/taler/exchange/templates/*.must
+usr/share/taler/exchange/spa/*
+
+# configuration files in /etc/taler
+debian/etc-taler-exchange/* etc/
diff --git a/debian/taler-exchange.links b/debian/taler-exchange.links
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/debian/taler-exchange.links
@@ -0,0 +1 @@
+
diff --git a/debian/taler-exchange.lintan-overrides b/debian/taler-exchange.lintan-overrides
new file mode 100644
index 000000000..b11557a64
--- /dev/null
+++ b/debian/taler-exchange.lintan-overrides
@@ -0,0 +1,3 @@
+# internal libraries are not split out into a dedicated package to avoid
+# micropackaging.
+taler-exchange: package-name-doesnt-match-sonames
diff --git a/debian/taler-exchange.postinst b/debian/taler-exchange.postinst
new file mode 100644
index 000000000..7509a7749
--- /dev/null
+++ b/debian/taler-exchange.postinst
@@ -0,0 +1,86 @@
+#!/bin/bash
+
+set -e
+
+. /usr/share/debconf/confmodule
+
+TALER_HOME="/var/lib/taler"
+_GROUPNAME=taler-exchange-secmod
+_DBGROUPNAME=taler-exchange-db
+_EUSERNAME=taler-exchange-httpd
+_CLOSERUSERNAME=taler-exchange-closer
+_CSECUSERNAME=taler-exchange-secmod-cs
+_RSECUSERNAME=taler-exchange-secmod-rsa
+_ESECUSERNAME=taler-exchange-secmod-eddsa
+_AGGRUSERNAME=taler-exchange-aggregator
+_EXPIUSERNAME=taler-exchange-expire
+_WIREUSERNAME=taler-exchange-wire
+
+case "${1}" in
+configure)
+
+ # Create taler groups as needed
+ if ! getent group ${_GROUPNAME} >/dev/null; then
+ addgroup --quiet --system ${_GROUPNAME}
+ fi
+ if ! getent group ${_DBGROUPNAME} >/dev/null; then
+ addgroup --quiet --system ${_DBGROUPNAME}
+ fi
+
+ # Create taler users if needed
+ 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}
+ fi
+ if ! getent passwd ${_CSECUSERNAME} >/dev/null; then
+ adduser --quiet --system --no-create-home --ingroup ${_GROUPNAME} --home ${TALER_HOME} ${_CSECUSERNAME}
+ fi
+ if ! getent passwd ${_ESECUSERNAME} >/dev/null; then
+ adduser --quiet --system --no-create-home --ingroup ${_GROUPNAME} --home ${TALER_HOME} ${_ESECUSERNAME}
+ fi
+ if ! getent passwd ${_WIREUSERNAME} >/dev/null; then
+ adduser --quiet --system --no-create-home --home ${TALER_HOME} ${_WIREUSERNAME}
+ adduser --quiet ${_WIREUSERNAME} ${_DBGROUPNAME}
+ fi
+ if ! getent passwd ${_CLOSERUSERNAME} >/dev/null; then
+ adduser --quiet --system --no-create-home --home ${TALER_HOME} ${_CLOSERUSERNAME}
+ adduser --quiet ${_CLOSERUSERNAME} ${_DBGROUPNAME}
+ fi
+ if ! getent passwd ${_AGGRUSERNAME} >/dev/null; then
+ adduser --quiet --system --no-create-home --home ${TALER_HOME} ${_AGGRUSERNAME}
+ adduser --quiet ${_AGGRUSERNAME} ${_DBGROUPNAME}
+ fi
+ if ! getent passwd ${_EXPIUSERNAME} >/dev/null; then
+ adduser --quiet --system --no-create-home --home ${TALER_HOME} ${_EXPIUSERNAME}
+ adduser --quiet ${_EXPIUSERNAME} ${_DBGROUPNAME}
+ fi
+
+ if ! dpkg-statoverride --list /etc/taler/secrets/exchange-accountcredentials-1.secret.conf >/dev/null 2>&1; then
+ dpkg-statoverride --add --update \
+ ${_WIREUSERNAME} root 640 \
+ /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
+ dpkg-statoverride --add --update \
+ root ${_DBGROUPNAME} 640 \
+ /etc/taler/secrets/exchange-db.secret.conf
+ fi
+
+ ;;
+
+abort-upgrade | abort-remove | abort-deconfigure) ;;
+
+*)
+ echo "postinst called with unknown argument \`${1}'" >&2
+ exit 1
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/taler-exchange.postrm b/debian/taler-exchange.postrm
new file mode 100644
index 000000000..fcde84b58
--- /dev/null
+++ b/debian/taler-exchange.postrm
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+set -e
+
+_GROUPNAME=taler-exchange-secmod
+_DBGROUPNAME=taler-exchange-db
+_EUSERNAME=taler-exchange-httpd
+_CLOSERUSERNAME=taler-exchange-closer
+_CSECUSERNAME=taler-exchange-secmod-cs
+_RSECUSERNAME=taler-exchange-secmod-rsa
+_ESECUSERNAME=taler-exchange-secmod-eddsa
+_AGGRUSERNAME=taler-exchange-aggregator
+_EXPIUSERNAME=taler-exchange-expire
+_WIREUSERNAME=taler-exchange-wire
+
+
+if [ -f /usr/share/debconf/confmodule ]; then
+ . /usr/share/debconf/confmodule
+fi
+
+case "${1}" in
+purge)
+ rm -rf /var/lib/taler/exchange-offline /var/lib/taler/exchange-secmod-*
+ dpkg-statoverride --remove \
+ /etc/taler/secrets/exchange-accountcredentials-1.secret.conf || true
+ dpkg-statoverride --remove \
+ /etc/taler/secrets/exchange-db.secret.conf || true
+ deluser --quiet --system ${_CSECUSERNAME} || true
+ deluser --quiet --system ${_RSECUSERNAME} || true
+ deluser --quiet --system ${_ESECUSERNAME} || true
+ deluser --quiet --system ${_AGGRUSERNAME} || true
+ deluser --quiet --system ${_EXPIUSERNAME} || true
+ deluser --quiet --system ${_WIREUSERNAME} || true
+ deluser --quiet --system ${_CLOSERUSERNAME} || true
+ deluser --quiet --system ${_EUSERNAME} || true
+ delgroup --only-if-empty --quiet ${_DBGROUPNAME} || true
+ delgroup --only-if-empty --quiet ${_GROUPNAME} || true
+ ;;
+
+remove | upgrade | failed-upgrade | abort-install | abort-upgrade | disappear)
+ ;;
+*)
+ echo "postrm called with unknown argument \`${1}'" >&2
+ exit 1
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/taler-exchange.prerm b/debian/taler-exchange.prerm
new file mode 100644
index 000000000..3ba9986a6
--- /dev/null
+++ b/debian/taler-exchange.prerm
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+set -e
+
+if [ -f /usr/share/debconf/confmodule ];
+then
+ . /usr/share/debconf/confmodule
+fi
+
+db_stop
+exit 0
diff --git a/debian/taler-exchange.taler-exchange-aggregator.service b/debian/taler-exchange.taler-exchange-aggregator.service
new file mode 100644
index 000000000..db297270f
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-aggregator.service
@@ -0,0 +1,20 @@
+[Unit]
+Description=GNU Taler payment system exchange aggregator service
+PartOf=taler-exchange.target
+After=postgres.service
+
+[Service]
+User=taler-exchange-aggregator
+Type=simple
+Restart=always
+RestartMode=direct
+RestartSec=1s
+RestartPreventExitStatus=2 3 4 5 6 9
+ExecStart=/usr/bin/taler-exchange-aggregator -c /etc/taler/taler.conf -L INFO
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/debian/taler-exchange.taler-exchange-aggregator@.service b/debian/taler-exchange.taler-exchange-aggregator@.service
new file mode 100644
index 000000000..b13997ae2
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-aggregator@.service
@@ -0,0 +1,24 @@
+# This is a systemd service template to instantiate
+# the service multiple times for parallelism.
+# We currently don't ship it with the package,
+# but might use it for future high-performance
+# deployments.
+
+[Unit]
+Description=GNU Taler payment system exchange aggregator service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-aggregator
+Type=simple
+Restart=always
+RestartSec=1s
+RestartPreventExitStatus=9
+ExecStart=/usr/bin/taler-exchange-aggregator -c /etc/taler/taler.conf -L INFO
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/debian/taler-exchange.taler-exchange-closer.service b/debian/taler-exchange.taler-exchange-closer.service
new file mode 100644
index 000000000..ba57522b0
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-closer.service
@@ -0,0 +1,20 @@
+[Unit]
+Description=GNU Taler payment system exchange closer service
+PartOf=taler-exchange.target
+After=network.target postgres.service
+
+[Service]
+User=taler-exchange-closer
+Type=simple
+Restart=always
+RestartMode=direct
+RestartSec=1s
+RestartPreventExitStatus=2 3 4 5 6 9
+ExecStart=/usr/bin/taler-exchange-closer -c /etc/taler/taler.conf -L INFO
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/debian/taler-exchange.taler-exchange-expire.service b/debian/taler-exchange.taler-exchange-expire.service
new file mode 100644
index 000000000..8fd9a9f74
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-expire.service
@@ -0,0 +1,20 @@
+[Unit]
+Description=GNU Taler payment system exchange expire service
+PartOf=taler-exchange.target
+After=postgres.service
+
+[Service]
+User=taler-exchange-expire
+Type=simple
+Restart=always
+RestartMode=direct
+RestartSec=1s
+RestartPreventExitStatus=2 3 4 5 6 9
+ExecStart=/usr/bin/taler-exchange-expire -c /etc/taler/taler.conf -L INFO
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/debian/taler-exchange.taler-exchange-httpd.service b/debian/taler-exchange.taler-exchange-httpd.service
new file mode 100644
index 000000000..cbde72522
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-httpd.service
@@ -0,0 +1,35 @@
+[Unit]
+Description=GNU Taler payment system exchange REST API
+AssertPathExists=/run/taler/exchange-httpd
+Requires=taler-exchange-httpd.socket taler-exchange-secmod-cs.service taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
+After=postgres.service network.target taler-exchange-secmod-cs.service taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-httpd
+Type=simple
+
+# Depending on the configuration, the service process kills itself and then
+# needs to be restarted. Thus no significant delay on restarts.
+Restart=always
+RestartMode=direct
+RestartSec=1ms
+RestartPreventExitStatus=2 3 4 5 6 9
+
+# 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 -L INFO
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+
+[Install]
+WantedBy=multi-user.target
diff --git a/debian/taler-exchange.taler-exchange-httpd.socket b/debian/taler-exchange.taler-exchange-httpd.socket
new file mode 100644
index 000000000..adbfb931b
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-httpd.socket
@@ -0,0 +1,14 @@
+[Unit]
+Description=Taler Exchange Socket
+PartOf=taler-exchange-httpd.service
+
+[Socket]
+ListenStream=/run/taler/exchange-httpd/exchange-http.sock
+Accept=no
+Service=taler-exchange-httpd.service
+SocketUser=taler-exchange-httpd
+SocketGroup=www-data
+SocketMode=0660
+
+[Install]
+WantedBy=sockets.target
diff --git a/debian/taler-exchange.taler-exchange-httpd@.service b/debian/taler-exchange.taler-exchange-httpd@.service
new file mode 100644
index 000000000..c4d010b80
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-httpd@.service
@@ -0,0 +1,33 @@
+# This is a systemd service template to instantiate
+# the service multiple times for parallelism.
+# We currently don't ship it with the package,
+# but might use it for future high-performance
+# deployments.
+
+[Unit]
+Description=GNU Taler payment system exchange REST API at %I
+AssertPathExists=/run/taler/exchange-httpd
+Requires=taler-exchange-httpd@%i.socket taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
+After=postgres.service network.target taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-httpd
+Type=simple
+# Depending on the configuration, the service suicides and then
+# needs to be restarted.
+Restart=always
+# Do not dally on restarts.
+RestartSec=1ms
+RestartPreventExitStatus=9
+EnvironmentFile=/etc/environment
+ExecStart=/usr/bin/taler-exchange-httpd -c /etc/taler/taler.conf -L INFO
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+
+[Install]
+WantedBy=multi-user.target
diff --git a/debian/taler-exchange.taler-exchange-httpd@.socket b/debian/taler-exchange.taler-exchange-httpd@.socket
new file mode 100644
index 000000000..e1d6b6bd4
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-httpd@.socket
@@ -0,0 +1,14 @@
+[Unit]
+Description=Taler Exchange Socket at %I
+PartOf=taler-exchange-httpd@%i.service
+
+[Socket]
+ListenStream=80
+Accept=no
+Service=taler-exchange-httpd@%i.service
+SocketUser=taler-exchange-httpd
+SocketGroup=www-data
+SocketMode=0660
+
+[Install]
+WantedBy=sockets.target
diff --git a/debian/taler-exchange.taler-exchange-secmod-cs.service b/debian/taler-exchange.taler-exchange-secmod-cs.service
new file mode 100644
index 000000000..b11c04552
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-secmod-cs.service
@@ -0,0 +1,19 @@
+[Unit]
+Description=GNU Taler payment system exchange CS security module
+AssertPathExists=/run/taler/exchange-secmod-cs
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-secmod-cs
+Type=simple
+Restart=always
+RestartSec=100ms
+RestartPreventExitStatus=9
+ExecStart=/usr/bin/taler-exchange-secmod-cs -c /etc/taler/taler.conf -L INFO
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+IPAddressDeny=any
+Slice=taler-exchange.slice
diff --git a/debian/taler-exchange.taler-exchange-secmod-eddsa.service b/debian/taler-exchange.taler-exchange-secmod-eddsa.service
new file mode 100644
index 000000000..17f1da3f5
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-secmod-eddsa.service
@@ -0,0 +1,19 @@
+[Unit]
+Description=GNU Taler payment system exchange EdDSA security module
+AssertPathExists=/run/taler/exchange-secmod-eddsa
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-secmod-eddsa
+Type=simple
+Restart=always
+RestartSec=100ms
+RestartPreventExitStatus=9
+ExecStart=/usr/bin/taler-exchange-secmod-eddsa -c /etc/taler/taler.conf -L INFO
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+IPAddressDeny=any
+Slice=taler-exchange.slice
diff --git a/debian/taler-exchange.taler-exchange-secmod-rsa.service b/debian/taler-exchange.taler-exchange-secmod-rsa.service
new file mode 100644
index 000000000..854737d03
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-secmod-rsa.service
@@ -0,0 +1,19 @@
+[Unit]
+Description=GNU Taler payment system exchange RSA security module
+AssertPathExists=/run/taler/exchange-secmod-rsa
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-secmod-rsa
+Type=simple
+Restart=always
+RestartSec=100ms
+RestartPreventExitStatus=9
+ExecStart=/usr/bin/taler-exchange-secmod-rsa -c /etc/taler/taler.conf -L INFO
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+IPAddressDeny=any
+Slice=taler-exchange.slice
diff --git a/debian/taler-exchange.taler-exchange-transfer.service b/debian/taler-exchange.taler-exchange-transfer.service
new file mode 100644
index 000000000..ffe2f1955
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-transfer.service
@@ -0,0 +1,20 @@
+[Unit]
+Description=Taler Exchange Transfer Service
+After=network.target postgres.service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-wire
+Type=simple
+Restart=always
+RestartMode=direct
+RestartSec=1s
+RestartPreventExitStatus=2 3 4 5 6 9
+ExecStart=/usr/bin/taler-exchange-transfer -c /etc/taler/taler.conf -L INFO
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/debian/taler-exchange.taler-exchange-wirewatch.service b/debian/taler-exchange.taler-exchange-wirewatch.service
new file mode 100644
index 000000000..40103bb51
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-wirewatch.service
@@ -0,0 +1,20 @@
+[Unit]
+Description=GNU Taler payment system exchange wirewatch service
+After=network.target postgres.service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-wire
+Type=simple
+Restart=always
+RestartMode=direct
+RestartSec=1s
+RestartPreventExitStatus=2 3 4 5 6 9
+RuntimeMaxSec=3600s
+ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf -L INFO
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
diff --git a/debian/taler-exchange.taler-exchange-wirewatch@.service b/debian/taler-exchange.taler-exchange-wirewatch@.service
new file mode 100644
index 000000000..a2836c6b9
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-wirewatch@.service
@@ -0,0 +1,25 @@
+# This is a systemd service template to instantiate
+# the service multiple times for parallelism.
+# We currently don't ship it with the package,
+# but might use it for future high-performance
+# deployments.
+
+[Unit]
+Description=GNU Taler payment system exchange wirewatch service
+After=network.target
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-wire
+Type=simple
+Restart=always
+RestartSec=1s
+RestartPreventExitStatus=9
+ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf -L INFO
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/debian/taler-exchange.taler-exchange.slice b/debian/taler-exchange.taler-exchange.slice
new file mode 100644
index 000000000..b5bb71e2e
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange.slice
@@ -0,0 +1,7 @@
+[Unit]
+Description=Slice for GNU taler exchange processes
+Before=slices.target
+
+[Slice]
+# Add settings that should affect all GNU Taler exchange
+# components here.
diff --git a/debian/taler-exchange.taler-exchange.target b/debian/taler-exchange.taler-exchange.target
new file mode 100644
index 000000000..65ec77c1e
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange.target
@@ -0,0 +1,13 @@
+[Unit]
+Description=GNU Taler exchange
+After=postgres.service network.target
+
+Wants=taler-exchange-httpd.service
+Wants=taler-exchange-wirewatch.service
+Wants=taler-exchange-aggregator.service
+Wants=taler-exchange-closer.service
+Wants=taler-exchange-expire.service
+Wants=taler-exchange-transfer.service
+
+[Install]
+WantedBy=multi-user.target
diff --git a/debian/taler-exchange.tmpfiles b/debian/taler-exchange.tmpfiles
new file mode 100644
index 000000000..c2a796539
--- /dev/null
+++ b/debian/taler-exchange.tmpfiles
@@ -0,0 +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-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 - -
diff --git a/debian/taler-terms-generator.install b/debian/taler-terms-generator.install
new file mode 100644
index 000000000..19972ffc7
--- /dev/null
+++ b/debian/taler-terms-generator.install
@@ -0,0 +1,8 @@
+usr/share/man/man1/taler-terms-generator.1
+usr/bin/taler-terms-generator
+
+# Terms of service / privacy policy templates
+usr/share/taler/terms/*.rst
+
+# Translations of ToS/PP
+usr/share/locale/*/LC_MESSAGES/*.po
diff --git a/debian/upstream/metadata b/debian/upstream/metadata
new file mode 100644
index 000000000..2a281b5e9
--- /dev/null
+++ b/debian/upstream/metadata
@@ -0,0 +1,4 @@
+Bug-Submit: https://bugs.taler.net/
+Documentation: https://docs.taler.net/
+Repository: git.taler.net/exchange.git
+Repository-Browse: https://git.taler.net/exchange.git
diff --git a/debian/upstream/signing-key.asc b/debian/upstream/signing-key.asc
new file mode 100644
index 000000000..d70f731ce
--- /dev/null
+++ b/debian/upstream/signing-key.asc
@@ -0,0 +1,637 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFSG/g0BEADfUtc2WA8+OWiNVuNuaU5CIFB/6Netaem0tXAc5VF8c/Dr/Bbt
+eSG4ZAWgCGioO/sqQ08XbYSdot1/zybFqAaD2Tlz99+GFLDYSMSDv6SkaAww0cGb
+objkAO3h1ojeR8gwj2+V2DuM9VLsmB0ITH3zXlLg1wbDUeIpOtk12DWqOTFN0v6x
+hV3JVdFsMmiM21iyo14FIxZmRTJulrwQFi/LcrUR7kDSjuwv3GzmVy6KSArri6fS
+Zec4os6WJM69+N3kV3SwoWxjikfUodaF+kOMXRyfEDX2ebyvveIvMl2BxNu7JUnF
+Y0AHXnxeNbfkpLCuFnH4cVvK14I+hHOa/JTnF77f7sWb+E0588YLL7geWucJfw94
+OzM1z4l/BLSyYiY3PJWRUHwkY7FV3cQGgTfrvbX3afa9Vi2bKHbgsgnOpe55FFJT
+RhZlGJMrgeNsoRKeivFaSa3HLhkV56VG268IM7iao+soVfeWKTOOSQGVeG6VrY7M
+UjhNfBbYfuSOW9CdF3p3XbI8DF68id0OQRUIihS42+kSGCZVY31Mx8+bZj+7+Qhs
+hZrARdrdmDg5IvJykEpn7aKpfyhf1sCfu/gwrpZ90IcaYoeafk6qWcf8JL+5VYHe
+wWjfZ7pFtlurt+hlrdNbqDQ9oHtIsevbgsPlh40BZ0kv2vLK5b+hQ5gd3QARAQAB
+tCtDaHJpc3RpYW4gR3JvdGhvZmYgPGNocmlzdGlhbkBncm90aG9mZi5vcmc+iQI4
+BBMBAgAiBQJUhv4NAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCTnmvh
+4p/DzBgKEACH0CAulDnMvk5hh9Ndu2QvHDAfKWtoj2NsMFw8YCC+Jb5PqmDL8Ddn
+ddRWrVxEfYf2DnHQI/wiy0HUJaZQstyHUbENtC2kC+HtQAiQlZyb6qL2ByuQfg8Z
+bSJYc7hdwSPRt52qXTMh6TPAzoHEWeEWUmYtQTsRna55A6Zo8HnKzLmspq03kx8w
+MjO/xtfRzToQNNTNh3Yg5F59sMUqiycrJxuF+y2L3jQLphEWg+yXjak3ruX3Rc4H
+pBqdPV36LQ5K+BZp8bzb0Ph2BDZ3t7SFI3SzCAlPl+R+lBtElwe367db+rRo4YPA
+bPchWXgZ7GIq+t7mVr4dffePEkdFVP8obR8mRtnnhx9Jvsi+6HYSsiBZ/csj1kRO
+XdtTrY56nc0maWLqVdvrwDlfrWNZxc7doUWBz0nB7VenzDIuBPCiV+jbafXNtNlu
+drjt0RYGvmnad3TMXxQbJsSmpDjSPAeZfaPtZC77BKt4yY2TvQJL9ZuPh7v59UXB
+wjJAiEP1YacANHExTqk1ShTVy6QNALN0eGifWkogmCtve5rQ1gkqN9TmqnCPGeyZ
+NVzz4j1W/imMRq7+MOVJcpBv0SWDpeFt13efnajdy4xFPUNXVhuIzE2CzcwdAq4f
+KG8QLvFnMN5yUo7kcjxAf4WMFkeuo8ofQNHMcFFvBaqMFWR1I0b347QoQ2hyaXN0
+aWFuIEdyb3Rob2ZmIDxncm90aG9mZkBnbnVuZXQub3JnPokCOAQTAQIAIgUCVIcH
+1gIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQk55r4eKfw8y6NxAAwzuB
+TvWUsBtnVjFas0n5SRdhtUnTUtAJ8z9Qe2Ab+ljao7cA4WG6OLcWYs/kd3tEUoLo
+XFERwmtRFbExKwVPcx1ffqdJhid9dK4bLd0OeKV3UElQFPMLXio4IUaF/k59HZXV
+X6MEXWDR2G/oNUXrg3Ole8mVd/093UDDoODE42W2RgBeammE5gfE6H7r+cbbKqF5
+I9Ie9ahDBGwW5HpI2cGFt+WsJaBXyBFxQDOnemQRw0PkyaE2TfkRgL0s4qxkyoYU
+vdbw7CfeA2qD4lramkJueXAcWGWP1DA0nxpbL5GQ6hnk/mi/7gZ7yztyop2DwrWE
+W1c2hLWida/grGZJwfXg7hu5Ls1RzCPB5Mqg/wmkynOapOWtvLz73G5LqWc7K6iQ
+7v3twg9enCUrcISzO/fglaf4oQ1EvMhogUu+kTn8DqiOp4lsPqLYu6Bonm90CMZ0
+amMQ1G+lDntJrxnK8MXa4p9Urb3FvR1YIa7zeDMfhHNVLO0jnK8m3S+iC4LvczCU
+xSXpj4ri+gBmS5syd5t7k7tdFpKphukY+H1Obe7wczbRXY9xOt+40jB9hYJM9wLY
+a2nePvbTAZbyV6czSb2GdhMwCFyzWDgiOQo4c+Q4LkiASWHNRM04MAj0L+MNEIOW
+opPQ3tuAx2oIbHV6yNy9ZO/JvPJI3bwc7t35hM20MENocmlzdGlhbiBHcm90aG9m
+ZiA8Y2hyaXN0aWFuLmdyb3Rob2ZmQGlucmlhLmZyPokCNgQwAQgAIBYhBNhCO8sy
+bHkHAzkpx5Oea+Hin8PMBQJZpUuHAh0AAAoJEJOea+Hin8PMyskP/jTGxVE4/9Yx
+BbqbfDlm399nP7JPdMK4rD8ERx87mlxoFWHKaRoyOf6pjHWfEGGOFReuDtVlmb5o
+RYflLjo224ehMur+Xudc65X5b5FExqv8maDXKRor2QI7X/JIB8wGxiXWQop3COiL
+lCqmI8a6RtMaoM3n+cxKcDumDNpckDgnWgtUolGsaJx8NmbeS/p4o1TYVsXwf2Du
+gdeoxEJSYUr7gZBxzI2VW0auG89sQ0/iuE5MvXthoYeECMyFazBBhkJWTtLCU+UE
+ZggLa7r1bBFVT0W87cXZ8dWYkWISJos+h70kwnjk9EFTqGlzaCgNG6GX8QqBnhOm
+zIEo7sp+i8PGsv5G0vnQeE8oVg3wxeY1xrUU5f6JBeLmIIoeG5ivC6rFzBGcV1qL
+uQ/mnhuo6SiP6kWXtKKsF2QuJHsDBnnDyLDJX9IVqumXeoTsqM18PJrv4JDOjeBJ
+wSny33ms6vOcub5CEmjrhDWLp7pgTWzIcH0fPqVxS9qop7HtMZOw0lGkyBHQLMjn
+o1/EDDE/FyUCzYhAlkvV0/3kgDSpXWRzKHb5MJmct0Z4HwfD6io4ZWkJUKqrNun9
+oDms16tKqfc3e+bylHHzM3io2rh0BfVgzot9uub8q9WWoeiRh4hwl5OUzs/+f0yA
+ab/9D4yGDi0fFO2tt8zz76UW+tTTRDqdtCVDaHJpc3RpYW4gR3JvdGhvZmYgPGdy
+b3Rob2ZmQGdudS5vcmc+iQJOBBMBCAA4FiEE2EI7yzJseQcDOSnHk55r4eKfw8wF
+AljtD1oCGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQk55r4eKfw8ybEBAA
+ooVs3RYP9sPdDUNdgDXtB6DxlR0kchRlWWr/HR1bRztKiV52atrAextg3PCDcKdB
+G+tdHyCkdLdtFH+wmfPXTfsmr8KiCYdZq3xq/siFSN2jARNShk46fZinosvvieNg
++NOUJXg0QKy5LdgCgWKJzqwK7rS3k/BvEeXsVUGsgJVWF1757cHJPQs/eSs4LjEk
+XT+ga1HuFhM2G9LePbsBVi87Unh2uv+uQuD+Ya8FHlXW2+IMdupTODQdqxtlYO7l
+iPK76h9yxjeCPJ26WZ1UHrG8h1a2wwyTxrpcbMYbMOZW2TjLzLB9H/lGcWN+VomU
+eymr1w9HuUPEMrKn1jNmk7LXWJOS1okvEOyV7NT7EBEJbQpzrdCLP9wUNZTciUsO
+51OO82JlnznWtzQ5DN+XeReTR2rxh/utUZszy8aqyAytkwpxO7dXBr8EOMMjZ62G
+44svOHrDuORfzgozlcRR3EQ9a0uR7nLkF2PM2pSr025ds1OneSKhxXXo2UGRhiaN
+5IqbfpwhHlVywrrCjZYjvvou+O9BWvslcqzBkUsQrU/Umu/XaTx3hRf+UFqmDBdW
+fd2u6nQEP8YR7kL9b/KhA9CH6QDOCo+0RJwj6TRA22R8qvbXXGB3XlQ3X5gvHo7U
+L+HKDhM7RLGfKWEtKGmH+glrWlG/hBdAnj1iadjOp7G0LkNocmlzdGlhbiBHcm90
+aG9mZiA8Y2hyaXN0aWFuLmdyb3Rob2ZmQGJmaC5jaD6JAk4EEwEIADgWIQTYQjvL
+Mmx5BwM5KceTnmvh4p/DzAUCWaVLdAIbAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIX
+gAAKCRCTnmvh4p/DzLdBEACnbI7USar/n5GHIVVu+nA8rw5fs3qHhSVUv7zQiCkC
+kwZS5yGYC4/wo0B6IdEXnEFmijnLhCzcLlwWPqoqzI7ZLbrhxg9duPT+ntBIIuYH
+/+Nr9DIZub5MsKuwSCFcSopch9VFojauBPOnXYfxZr0UI+bY0DpLUu9tgrA6nmJe
+x9Mre5RYS0pxIMv3ZlHXsW+PkJ1dVJisqJ7sr5XfkADTBm+Q94L1F4Jq30ftkan9
+C2zjj8jHurwnKaP+8/bHDQTHxGHpAUc7clw/dS3iuFo79rRerlLdEdLnmziBu1YL
+VwU9CRS5H9GkGbC4XWrobBHaLu12GXZQLgLFiO4JETxkh428yAyXOcPV8YDVORU7
+49xgx+gWSIGAdv4qwjH/xov6JMYGacmzfxWUHmNlW5CBJ7P5Rc6ktKqXffCdiSRw
+NX0F16LeiFxFNeSFFXK3jQfrIDdh2qmcv3bELmgJtMSorBBMecx4XZINXixLT+NO
+Qh8B/pKUXbS9+jvngQORIuDcZxtc81DJP1V7jOU+X9ywpSoX8bJoFDAA36Zn57/Y
+wTxo++6kM4i1WX4XF9NCH5HVlWHDcwQuAOkpEIGV5p0cNbm17VSPrMorr0W4IHeW
+OUoFyOlBGWSmXBRwI0iF+nE1XUj8iKirQ3TaUZrWTZPgt4/+mdCUNqqooj5jytR2
+wLkCDQRUhv4NARAAoi0SvMUnd5XSZVSmbwfge2p9KeGVVcaz99fgrUTgCwfovVd1
+MEXh8FCtxja4xZiuwSGUARuPAXpzhcK1L9vai25GV+y4SALp3wg1/GrsHtEsm+wm
+7AeIq0utXnjfnUzfliIIKwt0aGW/zGp/8rHNKh7JVUo0mPSMQfe+6tE2XOnuGDHj
+1ZyZalmBjVLJYMwsI0tfAzU1fa0MOSnhvyP5TFFj6PWKSajEOsFuIR/zceZFtJbN
+24lbXYwohBDBY2Ajb0y8uYBi/h350UY2mwjKHYM3mxJD3AogWIBz5HD+ueWGUTBp
+KwLYmN7zVxDMdL7FqGonSw9NV1XxJ3IN1DYPPdFKStRIUiSMzyj/pp6410ms+N1M
+tPXDIDdcOcmNHqcnkWqBYHXGi+sYyFpe+825N75dotpEipCnIcTCBjn3RdqFOzT4
++airtL7eOkzmooqtPwvNO+4Uza8+W1PLibXqXWqD0uyi1Wn29asF+uOEfNA4TpTX
+T6Df5B1X88eoHccCpPUhiNqs7dX1ye78m9oicD9IoXj3PZ0le2tHXuFclXjuffpO
+W6Wt+rbqMrFp4LA4H4UXafai9B5F1JMp+xdK+V0YUT0aQSZwdHyvNsGReRnuuZKH
+be0xokpVM+ndra2EpsV0C3csoDOWyu7yjUyFeTfAlYBb8rn8WuLnT8xzSJEAEQEA
+AYkCHwQYAQIACQUCVIb+DQIbDAAKCRCTnmvh4p/DzKGQD/wLhO70IEI06MqaP41i
+m4X7suk4zGOAcBXAcsZONq450CA/WHvoMKFoCPHfoC4e1jsoifG8+emfTQhWKwW3
+a5G/H90a8lY8pH9tqkVUPds5m6fbWf16xkWUQpH8QQyLwhBIF8onclrDWAHPflpn
+Wp+wso1vxN+WRh5vL1k8dpQLUkOBmE1ovl79/z1zzOYDkOWdQ1crU2EbOXalCmOA
+SmiFhWiYk2aosBxbzGX0JKX5NyIUzz56i9vDYqjkDFYcMMx1Z9YXsvTjglMwnIfw
+PmvBBgQlwqg+LOts7XF0ZoBZ3NBLpIES0wheVjXtG/T7kZey7XABVbxK2B4mIRFI
+vXnHbTEGzSyY7hLCshyCMQTDCoHDOKiNZmteqhHU4zXVgyhrxkYG9iIDj9yb6PCj
+aFwgp42rz0lLqTgmpDEIrz1MaCglhTB68wTsHYx3SH+ClNGmgWTa8dS+l/s0hgE+
+WknVGn6ShMkdyYLn3QxTRhZSmRv2hG7AYSemtLxi4lLoJ3kDHLMYAponhzxLYOtc
+8IyNrrRU4Tj4keG2ssHSkC9kDIMqzX53ObGkVWN6Rvu+pmZ9iumrNqI/4PyrPi3m
+OE7ooIkh1L/MEu2cLNWaTG5QmOK0VtYN+3G2qzcjKEpQPIDgRdZ6i7fO6jgb0iy1
+UJUbAoLQgUTaX99KUKeyCuiGUA==
+=17vI
+-----END PGP PUBLIC KEY BLOCK-----
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFj7l1QBEAC7WLUVNL9eQM9EpD4eTTBxs9W8IvCnZs3nT8cNm/a0rMBx4Vfk
++TKtI4tPbJRoM0GPMEUy0cyIztm7kSCcxJTgm8OIjIqloH0kL3YKqryQ0d1NHdgI
+z6zgBKLvbldG8+vQensMQm9D2xCDeBQGWACyyvtXsU35PeTmbW7GmYc9d2bwDWLH
+poO7GdMOQYETP8VOPUxtRoyJ2oSTPkHt/TFIAKEIEuVwPb4e+0XoRNdkdEebcjKv
+FW9hLJG4Cy5ur0GrQs21KlT/Yoz65MgK3jNrb9WJG8XBVAYxUq95FjD88ECIskRY
+KU8PM117MujSCOARh+jYUwG/m4Cz2atP3UOVkBGor21T9GF+KACiO/FTQboout0Y
++mwxyJkWQC+dZyg6oeZDa0mxCj1TO/1o9E/drgrxya3i9P5WVp8Ab6vAV9tk0jtd
+O7gqqqJGwW4hSBbcaYcZrST14EE7Xhc90f4lI8wYB1opC9lNstIbCF/5WPZBr3JQ
+/VQTdqk+b6W2XtpPqrPN9D43/aAlr7phgLlQWoUQYsYTjkx/CvrxK2davLtuvlov
+yzNZzsA/tm7+CBquY1rnaZfy+d61gsPj+9VXYc0edUPCCGPKI5m7XztFCAYRG6av
+yJT0vVsZDaXYwkSrx54D/rLGF/1dBavWApikLQER+CVyzO4dAJ9oGh+XNQARAQAB
+tAxuZzBAZnNmZS5vcmeJAlQEEwEKAD4CGwMFCwkIBwMFFQoJCAsFFgMCAQACHgEC
+F4AWIQSojIrdEpgo1+rALlLiL5u/7jSFiAUCWnZKwAUJBVOD6wAKCRDiL5u/7jSF
+iHgoD/49sgbWjXHVCa2O/bs5OWb0QaF1WMS+4fJkemkuvb4HqyzreSYPim6yIbx8
+/X2MaNeSvOZCMAvLKFfkqWm2B6Jngvxf+ZtVWrK895QXQoOZd5E91qb/zR0g/T5H
+K0GviRWAvL52+P2yfj7tswOq1Txdes+azwd6+yYUC1Fue50q/psoxXhfKib5NjPp
+4OGnx2YotAAdYrMBSQXDd5xYEt2OtzqwlQ8tsU+zeymzicPMEK2HdqBWRub4Y1O2
+bYmQ983iak/mrXBxOx6kbjXZBoMrYG/27JF/W8nrxLLIBqWvuWmnQcjs+5AQETdq
+be5+wsu2hiLtyi576vsvlR3kz4XYntD2Uhe7xJ7uR970suk5/fYSr5XpF0Cj0NCu
+9iFr/VTyFLKW2Wb9oiUbriE4jvlfIgw7JeT1C/3aRkbjyqDd9zwDHIFPQwBca0BL
+pAKyjGjx4QNDiTTZLvj0JL88Deikc/RVqn8AgjsuviVX/5xiQ8wX54UKiz4WpfY7
+ENgBkogg0WF8raxSHihBRlrcA2otlw+UUz64Uw6R4yMmemUEBl6/VMr/vB7+KykP
+Jxek8hb53fHEpCDcmniiwLK3ZaQz/VQ4HarCVVucc/oFB3XZuR55P1zhfcXf9JpS
+I5wvCmpkInPqav1aSKVLFdZY3eOlew7p/1aQNeF+ZaftGdLLX7QObmcwQGtyb3Nv
+cy5vcmeJAkQEMAEKAC4WIQSojIrdEpgo1+rALlLiL5u/7jSFiAUCWyLJ/RAdIGRv
+bWFpbiBleHBpcmVkAAoJEOIvm7/uNIWINNIP/1mJWlL6HfVJUy5L2mzsvdJZ3B/7
+9FZqb0YnP13HzPkPHjPKYgSAHRa8rGqn9UY/1nA5wYqXbnAqZJahO/zik9vd607Q
+kKZuYahqnymQOihNI4eWB99uSIdxrMGwh/HzJVzE8fhZld19az9KcVGf9KOl7Pl8
+be87mP6qkInrEt6QoyvrXwcZU6fOpEDRje4GGGRFry9FyLFE1UcICMpU2thSukym
+ziaamww7HjWtrw55cU7PaY+qWqH+MwLuyqt5jHGEsMaQHhkZCGHJn6sQ7Ci6DyhY
+JpOUgPgDzeTCzLIJrxrAiK6lwGel4aj6JhoXvQaYUZId27l/W9ItrhCRp9kDXza7
+yYoLO0ekqtpN/WpBWuXatwTaLk/zeJFdYdpQ+Hk7dlgTWhTPWhwQaUTbP8jqtjzG
+I7/Gq5EIG6r+6j/WFcKg7D4MrYQel9q9Sgx9oGLEtZgteK0wo+A3U2oIPugXWQeE
+5alpSIurNcsEAFowrGf/qd2JzImoI1zaJQbz0o/h5cxTOuWH3CbbPQ3BBSrM8Sqy
+evMFsfaAOpIqL3n69rlHaxn1cHaRYM8JcazFlp89pbqZ1Q4ZRFp7+8oO+KPThg5g
+sqizrOxKjXnWJX78qczBIEzD+KHGn4avsRgIsT1Ciz+ctjUiJvAZ4bcHpLwY3SFW
+jxpvlruI/XlZm6FjtA9uZzBAbGliZXJ0YWQucHeJAmcEMAEKAFEWIQSojIrdEpgo
+1+rALlLiL5u/7jSFiAUCWUKqDjMdIFRoaXMgZG9tYWluIGlzIHNjaGVkdWxlZCBm
+b3IgdGVybWluYXRpb24gaW4gMjAxOC4ACgkQ4i+bv+40hYiREA//SsIg/4Gfl4B/
+346bW5Gezh/Y0VqinNqFu/XG9HAuQ0AWNxr5hbFWNAZWEb2NUMiy+lMhNtJQYqpY
+Vsxmfcv1lM1xd+kyeOAjEdLI/TnnxrKI+eN8RgWNvtnfDoukOFm+aDP9DiyMdciS
+1GYgy/SrPnp+jxAMvjZ48prZPy8zEAiU0uBvYYlSHt4YqEr2XfJr1Sh2gs/ZYLE0
+2/8HKkEPAWYPk6dqeh1HITA4WOGPq4k+nTK/uHmm8WPVbsz8syOXGudn+vP6X4Lw
+7adoufVTbbr/0KP1N0f5kzk2WVL1y7l24W14ixQWQSH1GwcItj1Oin42JJ2ezHkr
+FVMCCbR8QhJxOlg+VCQkfHsY+gnbQGLW3bcMfQSXpLR8w/octEgOSkDHUTw93aNy
+IBZyZy6IlUVgjsUq+G9naQr2Jj44B7LaRYyanQFbuDT/vZ3nx1k4VvjzIXpMcLGr
+Jiq6keOSqVgVeBdSRcqVKwJEzmwmVVPPWsw1efaOID/CINAHvhe2h13CPFWQCGPS
+pk64NN1vEqxi1uGP4QdBrlLbVweusLNOHo9wksriSbs+Xj47n+Tr1gx1XezRYf13
+VhaaHYolbKYMR8fheCzMNgNw2VuP0RnYgIpVkk3p0roAqoWzlPIUjQnKxVoA9ITY
+MAPefuhM4qhRnW92egjSbHqOMj0PPS60Em5nQHByYWdtYXRpcXVlLnh5eokCVQQw
+AQoAPxYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJZQqpNIR0gVGhpcyBkb21haW4g
+aXMgYmVpbmcgZmFkZWQgb3V0LgAKCRDiL5u/7jSFiNxrD/4n75Ymlm0fLwG4B8U8
+w3xidcxS6yewOyZJKYrVtd7MDFrOHf89FytCvXDj4LbGP/KfCyHTvUBaQEFUYM9g
+1sx7v5m5V5SijHMCg3zJ63Lrxe5/lWM/O6Y5jiRtilOqzDI5CGqEHDohVbzfULZd
+9izyWQcZ3CGye7yrdp6LYDw/cMqgh1d56kuMXYyLcXgrUXeH9wQkaw1TJfWPSCvI
+JfESu/PJQ4TviBXZRfBuvDnjjbdYXvOCR3vQhSSuU8SX7f6wYNEBkWEj4Maj/HWJ
+x+442yzCzYb+Ix4ChJfjCLRtXs9OYcu4jT/9gxkPQISmjpjLGfSG5XcNcHirqxcs
+poT23LkSxB2pvtCw1MwAJHtk2o155Q2ZB+Kfu0fL74A/EpsilOHSym62ELkrude3
+Cy3x3kGFbRkwZd2tXbVlhgbnsl+YznXV+Mmx+pAoPvPtuzy/17yAbqhR5N5u3rC6
+hbZME/YCkoMns4+Dcab8iCxJl/UYYeCGwllbPMlzzvZ2SjRofNJ/5hydGg+dcPN/
+f7Nuh7bwN57vUSEatHdpqehi7avC7v1l6Xgijf1cN7nCM2JvDg9tR5G+fTOnQaU4
+0tpWYQNSpsYdLTs+M7QEzfH+jfS7tEhtdmyUkwWWudzKLPmSIVKYqWNytiJuFXdK
+j5+3tSfFLiCd3/piKn240L+GDbQTbmcwQHByYWdtYXRpcXVlLnh5eokCWAQwAQoA
+QhYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJZUuYtJB0gYWRkcmVzc2VzIHdpbGwg
+YmUgbm8gbG9uZ2VyIGFjdGl2ZQAKCRDiL5u/7jSFiCDvEACM2rM9zS7DUPHQ+dCF
+hH9c6ZcfdFQ0lzCT6DEZJs/PKhX0Ofgm/7BZm5zNotWaPyphHNspFrIv90EwPpBu
+XZfPbmewDpYxOuUMTCnHVMLzIXCmKqro1K6ofi9CvAm1kx0k/xKjB3c7XITnABy6
+sUhEKVojI6f4IgwkSYFZgT6E/GOV5jJYRyVUB49cXYQx5vvWj5yVTmTTDf1IUEsk
+ZpW74+zFt3WA4TJUugAarXPiEPPrcgkHDLAa9oPatyqAd4QU+0oaQuJC4GYfkIHk
+6fxZg0Mw+oKUjJEE0+o/WGv3SWSmd66urRcVxHASI6okeffz5ufGaYJNZTaHMmy6
+ztJJWfplhMe3wxLxZ1S7L3l947GbHgWXrC31kDY1D2LY5/7pr7R38B94CXbX2EIf
+ORWg4eUWvVfuPZ5Ew1TmUwcDS2w4EeS2psGfmUDbne5m4nn0iPPykXtw28ZbH7bN
+n9rTwW3yVIdyiDUvGlV63Os4tHVtbrFADKd1meFaMmg1gagdYof4lB50Vh5ChD6o
+7GkSvOshY7f4KNEfZLqCPTXmJKd3XLfb0eNY1APCYm8GI30K8OT8cdJwy3+zA7Oj
+/Duvo95pCyXx0+4xpo2eM2XPDdeVdMOkpGyFnKo3ApRMW5HHkw7sdpaNDwmtV9Wk
+RMBRAKXkvjnirKO6ZxTaIBUDIrQUa3Jvc29zQGF1dGlzdGljaS5vcmeJAlQEEwEK
+AD4CGwMFCwkIBwMFFQoJCAsFFgMCAQACHgECF4AWIQSojIrdEpgo1+rALlLiL5u/
+7jSFiAUCWnZKwAUJBVOD6wAKCRDiL5u/7jSFiHKcEACK34todRbyC37pvOGKYSU0
+WJGtVGSCxE6fOQzUb+G5n8oq/mLg50IiQL+BPd6flRBABrJ9RDi/z4i6tmgoE8u4
+t7oTj6vuF0XLhzbQ8wUS2CgWMuf7S4s9UN9yUG+zbATPpdYXo+m6hDYJxulmv9VA
+Xwc2k9acspsk9TCRgooHucpj/iTvFO4Qlg8AiVvLRsNd1dB0FMBMOs/Pa0LoZvOr
+oJZlFZdtVKZ7IMsgTfmLpRKrVR3LxJ7S1+7TGI96KGSBmB90QSBSWxwm4nsV5R5S
+q8vEPyb92XLJc1+j5GALcwhzX5gZ6bLqKFAO9OcFhB9ETSuujf4ksmLdntAj+DEI
+I2d+s8bFapg5p9/fVfrT4BExTh3yScOxG1UPAJNTQ/bXGFYKxh2cNzaYdbxli+xe
+nGwZivmpspM594I5dE3GfPdmiTQ7Mm0BTFa7A5xi/ftGICm0xS07UJ5eDWP3gVz/
+XmfVPhh8RUAGJgoOShDOO0CMG8+oYI4SRLMYI0pRB+ujCwRJnROOw4u9ATBVnLgQ
+H134ZNx0P+PIPHHQcotmjtYrko5Wg4hOvVGVjBkD8CCiPn4VhdcgM5RiKBnYe10D
+cEtLpjxTHD81X3X4hziuq70UiW9myBjsyPY5KgozeICN+GmXXdZTJYs6WWudAwgD
+9BA7vpBDKk7JYqdwKTJVU7QUbmcwQGluZm90cm9waXF1ZS5vcmeJAlQEEwEKAD4C
+GwMFCwkIBwMFFQoJCAsFFgMCAQACHgECF4AWIQSojIrdEpgo1+rALlLiL5u/7jSF
+iAUCWnZKwAUJBVOD6wAKCRDiL5u/7jSFiAH1D/0f9ocmx5BsIiQL7j/o0zQrOpi7
+vL7RIT3ZT/UOlOiqBHlvgTAa6FyIfjxrvBDwQp6PiEnCikWmCJcHbYnEeiJoXSui
+TigmzgEbgQ7nzrZGWozwvVsmdH5XpcdGTuR2mJaUYJyNHSvPKL9pneIMGmxI7VIy
+blO8IXGXO+TMre4bGXqTmYCYMPmfqWCTBSorXWsdBth2S23Rum5d2RWK7alVrw5+
+uAgnAo7xz7e/o8P/+UuaFaJixgbyCpf8MC4FTYIU+rayjGbAN80lr1khMrXYvhfM
+9KS5CADrqncBBUZwHA4Bk+Rs9ZEm+q4EJl0lXFf3m+FSN1K4KXAxQQMrDUf/YxD7
+FNqUS9T6KloGZOdRYhZmzz4b1zXT9dCuHjtO2V809M+kpLJbIYu0PKMzyB6Lizli
+1Un0yFKb9CbZ/pVWOMLppKfZ9Sngkz+6Ppng8PMBFwO9eEwNaPP8wFH8IO7PMvqH
+MaoiMnNMeIFfSd1jIYvKdsFb33jcXFG+C4sYi066a83oxTmd9euKs+9jKzQDKqAX
++TxK+UK7W9KVbgx/9jrQ9UrGnucV50fcG2gowZrKaKvYj2jKKiGTCspKufL1fsM+
+WXjKH3M4uNeGKLyfo0c55ACBV1/C7mzHcJNuLYgfFYAgstdSiFZb6T/oN2DA8kp3
+IVirL3252Iw6mTgm/LQXam9icy5uZ0BwcmFnbWF0aXF1ZS54eXqJAloEMAEKAEQW
+IQSojIrdEpgo1+rALlLiL5u/7jSFiAUCWUKpXiYdIFRoaXMgZW1haWwgYWRkcmVz
+cyBoYXMgYmVlbiByZXZva2VkLgAKCRDiL5u/7jSFiH7OD/0T9sIrJiH9/JC3QGzU
+a2iYvMwcHulbODqP+dugZzG0o+GrUWTF41KCpNxsSBaKJhXvpKmzTMArw29Z4fhi
+gMVkW+E459httukG+L4AMGmfr+xQ97wsp8bTDGq6Hi/ifhICCCDI9PKsxTCmdJZQ
+EVkwZqxq/mvpg112rYaDr4TvX6kFIi8mEGt/bNSxRY6WK+NMetr/QLtsWlcDsRB2
+/vuVCo7B7/KdEG4vFPpavaK9BSW9pJhzyjuUEAa2pPj8aXC/TlnhyArRoJxEWyWl
+Tc+Z4w6W4pXGedhFt6aqDbkyWvl3IXuA63nTCRqC0FD5h66Nn0ilfD2XQOBshBrP
+DN8xw5Dddty0b3pFCVrzS6l/mgBumZumLJYGHNCcvEZJO8IlIdhQcGVGwNxqiOLA
+EeCvTEwnRdopwGUHEsqi8x5N4+oTdbeUMlapotIC/wZLNHhUINySj1SW4TAS38/M
+s7wNm2ytXbM3ZHtkKSYaqrh/WfG1YXOhUtexRvajIh+JfNve0B6Z9mv1IQ3xx7t7
+ihzPjQxFJakDT42IudEcrreRYPAIQVtD4KnDyfowHnj6U++d8BZS7F3XrtsvEbN9
+/hL7NmmMUSUd7WO0v+kuCXRNM4tujv/+d9X96UgY5IXGC80KFzkfrXPIm79Grbzl
+AxGw/pIyClyb0dYbg5qfJQMdQrQZY29udGFjdC5uZzBAY3J5cHRvbGFiLm5ldIkC
+VAQTAQoAPgIbAwULCQgHAwUVCgkICwUWAwIBAAIeAQIXgBYhBKiMit0SmCjX6sAu
+UuIvm7/uNIWIBQJadkrABQkFU4PrAAoJEOIvm7/uNIWILaYP/jng5jmn273qBGR5
+UktjTB/K6MDUXPXlL60yfgpgO65Qw1LdfgpuM+sKeukFS/z/tUo2BRiUdJcLoeMY
+Fwcx/5bV4/ZsaYV9++8EZTEgCFazEi6HGcou254QXvisRP0Ig6F2yAoU2UHvvebs
+1UPJXt0KTV3C+CNVA4Tb/sZiVxDCuxnomlIzvtYjM+sw5qjyuj8AO50qDYEBBLxI
+fUVq98bXwV/wE+SNoVxZsPGQIYbd6SNNZ3rOU7rAW6l5GlvMBT/uZ2BleZsbVs5r
+OaGmb8HN63D67eqDR8wIUeCmXv1iokq7qabtI1TJslJ+Ip7cMrglTRS9qmPfPobZ
+syx7wjZ0BPNGUercbRzc8zG9jdAxW4CNxuLBVGhZYV9bjUJKVABET8zao3h4lIpq
+gFJCnh3DTnlm0BQ2wOZj8qHQmrnZo8d9Wc6xwmVegbOAfN26ituwW/wcd6wWqbhs
+AmlpHWjmiFF9shpJK2N6ouwK6r9llMzlXQsP1ysXJhb9xus95vkNW7u1/u9PRGwH
+www0hM0x4c9UheF+pn1nsLfQQPlUeFQxXxrY+dx8eVlPsDvJTAKaY4zPlGdrkBBf
+Yp7u8PL6PxZPwgEXKDrgjanqtQUsQjseyZPbqWZticcWx2cWTQak83MkEat39nIA
+fU+cctUoxdFKCsk95JOwxFl9eIgDtBxuZzBAbm8tcmVwbHkucHJhZ21hdGlxdWUu
+eHl6iQJYBDABCgBCFiEEqIyK3RKYKNfqwC5S4i+bv+40hYgFAllS5i0kHSBhZGRy
+ZXNzZXMgd2lsbCBiZSBubyBsb25nZXIgYWN0aXZlAAoJEOIvm7/uNIWIK5wP/Aja
+q+jDthUY6ODVc3RGgu07oIwBVbcXRn1OPv1gxRDXTSz/8+g5fJL+vq75GxHW3kFj
+mhXZ8VcR3OjzkuBWXeY6bfI3CDg7QcS2rdUHq+fSjJyZVQpVpQOmoCrhV4lm8Cub
+KVbD8mk0H5kJCHEkp2mIBnosTc/7zwVO/oa7qNuMkgVsffzwe5hsA9fTSu1htEUd
+W8t+5Sv3VO/Lg31dxnQFhgZ1BuRQjP/vCjRt+pGC3gpxV28tWwzyj+2xz2iTkOUB
+9UWKPAOzxHdbPgsOMSe4fi2csk5FOSA+UJXlCmq13reBC5t0XfaRG5BoK6bWKRzR
+DPnwoZUKI2Vf8MXIJITC0tk4RzHnWCkArtZCKfOz5QjBPtf1hO1NLr69xULUk1ir
+yq5P9h4VL4k8LY45e+NW7CpITZSuquCfn6+Bs9zIyW/czFppaxWHq/9Q/Z7+IpwV
+IKywfbrJP0cm9OPTyxzLp1TyCLKCpMP2iPUdxLSfM+IIvkLZMPTU+1MCJR8S99Ny
+hXM0jI8W22adumYZtCYWsGveEFCrWmGu6xFRpmkF+5d0YBXnUYToyOPgTQydpDfi
+2OMB82U7ow8wkOBDhN7Ky8+2pfNli/p0/XXacNSQShqNmiZ5kkUpX26Y/ErDmR3l
+DVDQ7A+nEzh0ISXCMRYrHdGtl6KSNhgEQLhCc/C7tB1uZzBAbm8tcmVwbHkuaW5m
+b3Ryb3BpcXVlLm9yZ4kCNgQwAQoAIBYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJa
+QQajAh0gAAoJEOIvm7/uNIWINdYQALg1RmGkiKGeEcun2srGci96rFmE5HiAqfkr
+K+QeDHuX8nUd/uqtVF9L3jYDqyuSyPHRE4JAbT/XmZaXy5rzlJ3LJcJ+EqumeWKh
+1ee1+UXvC6ONH1WASSFmAnX2VySmuzLvTK+L7M0aCyZ/NSGfETSvAD4R3I+LqdPm
+jv/X3SwiO+aiZbuaSw38Kp/E64QOhvj2n0/Z4mcyeMZBw2h+kc4uan6+2P74sfE8
+8dnV9L0eWI8f9MZ/0cN0s/Yey+WfHhSxEjUz8nRwaZk87rJ38kyzUpZUL+EZJavD
+MsT3GvALsspDwsvXUw53hBt/bdql7l68wfV7/A/Rc7JqlvRk1DHNaCTj33Jea/ca
+jTLxwvRTRoK9oc92wdFhChagbztSO/YozNwEUC+qqYhR6n7vVkhq+onCuaL67tIM
+E8HZhdvC4d330026n01FwmC+tDt61QiFugsON8xp+KLulLm0eTZL0NM9e9jPLXlz
+RwcpXzSw2dhz8nRmXgAXKWuKRTO5MXFygXpXnAT0eC7gkpNOBhSnauAiHVXBpGKf
+HdiosfXT4KcslB+GvCsqRnWltXbzowAnW2HZkenRnR03RGUNm4igNmmcfG89QiKr
+tAQURVZ0FytKuRrRNC1zKGz5s/qNdjSU6pm5xHUWz4XBOxIWjf8tXxvgv/WVvpvR
+uXioD2M7tAluZzBAbjAuaXOJAlQEEwEKAD4CGwMFCwkIBwMFFQoJCAsFFgMCAQAC
+HgECF4AWIQSojIrdEpgo1+rALlLiL5u/7jSFiAUCWnZKwAUJBVOD6wAKCRDiL5u/
+7jSFiJUpD/9Ym/U03uqoJX724xdaeXd123dIzbpPlPUA99Apst0KrtE4uBOHJCO0
+1LyJyLz/cDneOJR9Xko8s9qGnhVfjfEJmDxP3ynAsFRQDd1gZWdjqJt8aQB8MlEf
+DvrK3Z85f+oU94nZxhTkUhpWbh3mifhxim4aVidfsZQTWMd3Akz/W09j5UPR+VzO
+APdoqPahb2GSBvsSMeN9G9wCKAsoHOAzw7fIz+CCdRBheJh7SfH8D66RKZVNL334
+DdgLXtlhYkPxhNBhqcJD8oFsu7/p0efRME11YKPT2yqrXkYbv5gSMMpbmvW7YNez
+SGnux0qmGKpw/oCvvEyNuDwmrR9DDXNFtq1hOpf1ii7XZZAyaQHaB+iShVdfIWpP
+q6lkLl4UThoN5ZbEsJuYGiZA05vyw6oWr5x51Ii+keicidGtlljtv2c6HIL942QP
+3vVcOIYtRKAWX3g83MIupHxB68kfXt70l5a+QHxB/rNRnt+CJdi3dcrKOVIWeBUO
+tasPKkNmPUOcHUtKQE9Yd45YKi78QCrmxflsIbbkQBdOkeqPCXN5bC+N+ocPLYXX
+GFi00yjwTTh4XEJDjbPSarXCVtOt1cm/9164WStI5da0DPxqaJdiRgGYxJs94YsF
+QnoG84NqQ4mFWGHXxp3aPpRp+xpFOmy89PPJXd/aE13R7VONGzLtqbQMbmcwQGNy
+YXNoLmN4iQJUBBMBCgA+FiEEqIyK3RKYKNfqwC5S4i+bv+40hYgFAlp6NKACGwMF
+CQVTg+sFCwkIBwMFFQoJCAsFFgMCAQACHgECF4AACgkQ4i+bv+40hYgK5A/7Bwi7
+jwlSEPbsO5FXXb46SdLuQSNV5yscV+HH4i8vrbDXuPNnSgci7eJ4nMvLvx5TNiju
+8f+NFfczv0q3AttTdHNy9qgUI0pUGDktMt5L4ZFJLFCKXYNzjFukAjkm8ynX/MbX
+SLvu1qvZsgZGykcT77qkEVext3QaUOcv8rNJy7kZMF0tvxY+h3wFTdwisg4YRrIZ
+6XWHsbmCvmPCwqxWPIzKZGqlRr7nQyb8rYdnk4Lam+shG8UtJt+2Gk5ZPI0XDB2z
+TkZ14uqFyQV1foIQAhIzwcbjpBWAGUo4Ppo1RSgN/ixShyEwNM5uWUbfdtYUawkT
+pcVnRe74AUCTCHBl7A9FzbWwn8+4xPrfAEfkvZ89GYKq0JxDAXgCjmzmwShcTZjP
+Jc3vBpZLEOTnnNEipseL9Shb4kdK6WUeIlSTkuxYomgidcLRcOYK8+Pze3A1zwOE
+5LSKpDMucvq9bGCveRONQeHCQE+zzY7lUBPohXj0HT215+YX2r4dqz5KYSWjRAXO
+hM7ZfUK7sRlqsnye5iLUVNQj9LJM+LcXHOOiFVpqyHk4iqVYDNsdhjsMu6Xl/uOJ
+j0kHED43ResOMZa4n60bYNSmj8p7ar3eNdTRR6FEYkz9EeWdw0GMVEwceJo7bX1o
+D/VxpTIYX3IYVvna2vX1r23ERN5V3lLx2DrjpzC0Dm5nMEBnbnVuZXQub3JniQJU
+BBMBCgA+AhsDBQsJCAcDBRUKCQgLBRYDAgEAAh4BAheAFiEEqIyK3RKYKNfqwC5S
+4i+bv+40hYgFAlp2SsAFCQVTg+sACgkQ4i+bv+40hYiiYQ//UnNfHDFJ0YwJVmKe
+xyhkt5VX/Sjs79c83d64ULQJSCWNlZYvmozZNitAzNs9lwSLIL3Bz7+uxXe1RlFV
+pPXTZMHNT2ONP9o8bmrSr0tx52RW/8ZcAgdXR9GUCBbYfJkda45qk5mP3igExb4i
+EQ5DjW1yQBe3GTOUjgpClKsgfXd0x4SFDnJYh4NEsT8VTAaAMuFLF+O4rz00woGq
+BXO10P77Vh1XW3b5YGaMmh2JemoaG+9qKHZ6wyk2E8mh582AtBMYXMgMAwtRyCty
+WUlQhDdRNzo7oyspZuWOK0NARouWF3t3JjfNDqTyF2sN06Es+EwG0H9+NiQxcDWD
+WveQG0J7Ny1a0dPiVT//HmTakgFBC6ErPB0r5Z+gHx876EORLw3YBOZzxatbdD+y
+2Dvho9i4WVmh53bB5gLrcmtWsIvHC9L34CrQDi9M5XLNrIcrRnkWttiNsH3LgCZT
+C6PTaXyN99shpRUcuaeK5sIpyf7yEiUBD6Tg2ctF+SifwAoKccTW51wCzfuqFCNg
+BGO98cjdpo//oMMXrQgEFBFEJggrjWX92bMVoS8yBKRyygwoFRadb9VtdE9E3252
+HPxbL9WThhHB79qoYWoDlPVbeyi98sVpr+/rvo/Td4Mj+Zh2BZ+ZVfVQwcMx712T
+cwXQrflzKtg2Zz+gINqFwaM0giu0Dm5nMEByaXNldXAubmV0iQJUBBMBCgA+FiEE
+qIyK3RKYKNfqwC5S4i+bv+40hYgFAlqINbgCGwMFCQVTg+sFCwkIBwMFFQoJCAsF
+FgMCAQACHgECF4AACgkQ4i+bv+40hYhsnA/+OQEYgEwAhmmSx84vUPLyCzDuJNPw
+qjNRH0EjRDWM4ea/iAhnTdYN4GQoJcIS7jZdD0saVCzteWn2cP8Mu68IzcSjOq0t
+jdNSuyM5aCNpd/BV08RM00sOAxGdBiG68cJ2FlYqHYZ47BzxlRtxSKHaC4GxQMtR
+lNIcYdSq1++y26Fb0L8/DhiRmzCCxG76Aklg2zV10Zj7ao0Bk9I/sO3HdWPyxZRQ
+ST0a26bOOonqKT9AQJuupI9qTL2o212KrVTm/pBQBrBG1TFVm25F4vNg13qvVqNo
+nl7f+tWDIo8hh4/+mFP1SSiwop6agO7suRA4GaBRuklaTeYsc2wle5SpZ4dBCYPZ
+OC5wAiBPnNKfr2CZxk7NyPji447UfbMj6SFYhLBfQVvnqCazx6G8YOyjWFFqerbv
+4U61ODscpG5Ri03qkHv2z1roTo+GhvEhf5ru+UgGXh8A4Iu57YOcmqpM85qiwwDQ
++xCRm3xWrqk21q4svxS25Z4DsuXdRGb9qskYdQTFwh57TQ+f1EWdBEDsQAUAA/po
+ZWM7pQ9s4f2HhwdrBy/qvlDSk7GjfVquuKKu2/JzX6L7YIkFZPiQmPBfKmPVK7GX
+fVvyQs5RpPXr+vcoRFHDWx9GfB9OFJzpsKbkMsFw1RgyQ1d5YAoWbIb9XzCJBFmU
+pwTVHO/GxAPdIce0EXg2ZTY3MzBAZ21haWwuY29tiQJFBDABCgAvFiEEqIyK3RKY
+KNfqwC5S4i+bv+40hYgFAltyhdkRHSBhZGRyZXNzIGRlbGV0ZWQACgkQ4i+bv+40
+hYhT4g//Yqu0cvNTgdxvbZ9ckmQvLDAQOiNTkjmMO3C9jMSJRWXYFyz7FXbrutzg
+AZmEW/CkM4zOlXra1A8BrWsAnV5b/j6X91F69TsVbMne1YScftHEs4V8K213XHwk
+J5+4BKLG+IGf3+Hjgx2Tz4tj1jX6TzDJ3+W3nJpCmOO5JDWnQlNweYcv4YjU01aq
+zI3HMuULKYsHvv6XJpcnFiu/MY4+zcJCbDHxmB77itxHaeayTCbXinSTrblvsuzG
+DHornawq53NAGh6YF/9/RljoAqx55AZCUN0crfR5gNOKEx0QTEF7NenifiFxxS8c
+QoR77rPTG19EVP1wQFC44brPeKNphDRKCAa7Mst1gMeWfPFm6Uz4osxYye2dhlNc
+Ho1pU0fCRTE8cHPNmrSyRBQ4Tvfd7gXX7/KnfwGDQZ2Dcmjb167Er1InAho8eYqX
+8xye0kj3pRJAk3VaYPARzPRalEX3rix+BcQTRqm4H11or8e2nMRXrt/B1x1sAYYu
+Tn/x+ut1Atk4NpLrNapDdB+byQt2l0vRqv10zV9ZnQh1AU1wXiaZEwFcSn+KxQVX
+9zsY/LV4kn26RngPreeveXFIXW5FCKNVbNVhJCHR3K0z/egyTU0l5CgsrqbMoW7K
+8ZM/AxSQaNY2b7LmujBbSpLigqPpcfULpZaClp5D5NDPreunQA+0GG5nMEB3ZS5t
+YWtlLnJpdHVhbC5uMC5pc4kCVAQTAQoAPgIbAwULCQgHAwUVCgkICwUWAwIBAAIe
+AQIXgBYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJadkrABQkFU4PrAAoJEOIvm7/u
+NIWIF+kP/RpsEcwAdvWsUi70Eu+ME18bWnH9Io9p3mxz/wDoaTh/Ej+J549hKCMz
+BZAubB8ud189JyYzQv0CUqDbD+1STa3Da8LCz2uAN3Kuo5+gR8m0vCxE7pRjoemM
+f+HYYGoTsUCVKtN54Voar/3htjMq2sQ8bh1HKdkgKSys169Pz20Fo1auz8t0Y05u
+JRJbMMHWplbXEA9aX/VRelIMWjvUOtAFu8Rp/dngAVcwGG+xqb7vm1AM0Mdhm0He
+a08v8HtvdHKHRc3SkHmrVO4WYBHJALSRn/7oTt2DxTARHe6Qp7QBIXN1u7Ajja00
+4K7hmhJixVBZjWolDFg6i2viqkVe+6n0GUcqOs0AEjnkDJKMg2AXzIggo9c3qZmg
+VbcO4xYB2acbusHYSEP+B8tugpNxREWydOU6w18hoVPth0oD7v/gWfY7PmYZnQAu
+CUOKbwz/jT0s69VXqwtaaCi6TjkzYIOFy8zl08stEsgwN0bXujk6X6vZu6u6tkyT
+QJIjObYi1NoFqDtH1UQG3IsQLJ0zN3KoBOB7JjfofF3Ytz4uKdAuA9hVaaAJ/QWn
+EUAAWHKH4KV0arRlYT/GVf0cqkt62NaDxql72u1Fh9B1zx8FCufgLrVlbhyX6RP3
+x9K+Ax9qjLvbCW1+ch1Kl9K4qVLF5MpgorZt1SA/c/UH6Y8e+xiytBlOaWxzIEdp
+bGxtYW5uIDxuZzBAbjAucG0+iQI2BDABCgAgFiEEqIyK3RKYKNfqwC5S4i+bv+40
+hYgFAl0sdMUCHSAACgkQ4i+bv+40hYj9mA//SstcJPuIznSOzxBOncaG2iNTl+8Y
+8OAuXovLF9RIBSz6LCszjdgXdXRuXrTOLKonZFrnCC4T9gPLyu+Mp2GKrwNhN2/s
+bcHJzrWJ3Yr4eaDSHEaiLZHsFQUgAKfZUMWMXT/ATVLsWDPhhB6jGVTk8jjXvoMx
+7ylGDpux4QNl19YSJ6mqFRv46qWkZBlUwKDXBb3QwaLG68rMRRba9phYcKpQVCHf
+7lIXYh1Ds8JoEQcyBkpWolBxl24f9CUgYvPd6ZACNXrodTBr3bGXiCAlceWggKwK
+tbjwoWYCA1OgQpt7F+OeQ3S8i5yHwFpJx8d7/d3fd2lArMRGJ6wliBwuh6fhN4tZ
+T278HqRaHNfXwWt3N2nUGBbYIiurJNSKqTRV5EQykCh06Du36FSfAguq3OAA+Q7/
+uQMBP4eYanhiP1TpPt4HQVIEFV3N914o8DXnifBOhTieW9Uwa+ltUegyiVhTvmH+
+RwHwcp70rWJ+xrV3jS5B8HV8yybx/c7sJioygYhiIQ1SSzuWb1yJT+etYZ4P6D1X
+WdvDFA3zxt7DxrxHarSQGNzbxYSkwkWmQ0LtaZLmEdkgdETZbTgn8xfvdV2g6uQM
+mJizGZvP/u4jD9sLR3nVnFXlL/WrC6lBR3UkHNiMAp3FT0W/HNZBof2KDMHhmhWn
+Q0pOSoRW2XMXvUm0Hk5pbHMgR2lsbG1hbm4gPGdpbGxtYW5uQG4wLmlzPokCNgQw
+AQoAIBYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJdLHTFAh0gAAoJEOIvm7/uNIWI
+7vYP/R/Ds5wvuIXk5d4rgvr5OIJAwDUdYu8NbFITt6wE8o8G1eJFajE6t05xqp7N
+blYqLykQUH4LEajn8B5qDrepgOQEpYlzhFhASWSzE2yh7H6fiynFpDeXGeh1bLsR
+9fswKOAg7Ch4UrN+K3Fx2rt7ivOaRcsMhSK9TLFyR8gcSIUSWE4s+YgGtGGD/IzC
+LL5i36AulJotVfI67N+RxTJieW9y5KPXYzNo/04Yn+aQrQWVn0Hxf1+QWNEGo1c5
+AO2L09Wzy3bpEw4VlckE27YiKcxnqXt5PtoStbhIw7GtRl2CpMZHj6OXJnlzTntx
+/J6A7Ys4MFcjJEu5VhjVDYnKKe1ksxvp9QOONzz3t2VoC443V54QLV9qY1UNxXTZ
+C2zGJ9YgOIfIfIP55kc/x051++rBAWianmIyFs3MqVAPXoIdOXm436TfrI3Uk5b6
++RH08dpkCYbUEoh1y4fjFHj4MUuWyxo1ByghHfn6J0BEF6hfqvL/WfQPhtvUY+K1
+OikdiwGYh/lzjiSuTT+W68ltzM0J4sF35XELAvL/7QutCCBFKXHfto/LtlUjnxnw
+FRoLg1Kq2LuXrKFoeCcbhzJuMF88dKD1Rf2r6hwqmZDLjupP7CsSvN5iC9vtaGz0
+33HkfiKNCh6lQ41UBy57n/iquiHZLZh1Pr4OgadnQIwHhlcFtB5OaWxzIEdpbGxt
+YW5uIDxnaWxsbWFubkBuMC5wbT6JAjYEMAEKACAWIQSojIrdEpgo1+rALlLiL5u/
+7jSFiAUCXSx0xQIdIAAKCRDiL5u/7jSFiP+IEACuGt+Fmo//CdHBB5HM1KSkGtHC
+9VuQAVMCE6RxGHcDrG4vCAeSeUTF/s64F9D2zLEJdBg1WUZbDSEKdsz33CCxEiMp
+XcvWCjI+SSwPs78kKoIhYTwYW9dWE5exfJ7878pMCQMCq/UReZyQptiICW1AwOuM
+7xp3Qbk/VcTYtxC0UJ8VGhr6sEzjgO6JOxKBhCNcLFOD+3O96MXFYuitu/v8Awm4
+Z+XeDe0FH0IALx5+3yWVbj/sPoMx3kgf09BZ59a0KgDrmDPlrdAjavk54DSXfuOm
+GXAOJgBMCRc30rHsGnB2UiiTNZu0BG34s7yc+s1Wdv65i9CJ1OhcmGcLRjNDq13N
+IY7lyRaKsE5XMrj5e6gIr7I8wKONRQZBt8bE2nI8xUkdHBshTfmna1pXkBDO3qRK
+3zK1iyyVYirm6zPjT9KNiQ8AFQquQayYxEqR9RbQOUhcTtz12mopY3+FEz71hvte
+dogldkuPq9hMBpeBNWw2IYYtXPvbuf9soyvn2JhPlY+T5BO1m1Ys9rrAueUUIRb5
+3jZSWCX+sHGatrt7WdV00QxZF57zfeB0axKKLToAE85R1imNEZF1TyRqMzxsQbWO
+HJ4WgA4J3NpdbFpWe+Al47qoLxT6pPJbWiNZpj4p0189itQNEA7g2nfz47LBjZYv
+lyyIXMIlZe4t6rkpO7QjTmlscyBHaWxsbWFubiA8Z2lsbG1hbm5AZ251bmV0Lm9y
+Zz6JAjYEMAEKACAWIQSojIrdEpgo1+rALlLiL5u/7jSFiAUCXSx0xQIdIAAKCRDi
+L5u/7jSFiJBcD/4ilWSQRaxbgWms9PXZ4HTvfrFEb2TTn86BYiYxEN3aT7xk1Tor
+6bzEmLVkhraN2NKgaYgCFt1/Wzzo9N4o9Ojtq6ES3SFx0ZSpnlIU6YaLB++710gj
+mP9KW8sBcGrNBYa0eisZuwqM68GApGDxbdzqo9K7O3alYdf6UsmTal7iWHCRCz4d
+RShxFYWJbOk842rL8e1AWkF6nvWIq+7/eKLYyD5XAIXKw/ZB/hlcdgd8m0eO4pZ7
+s+fhuaVPFryVzAlAnLQRdMStUo8VeGbOqFCfS0ZB2cPt/D5d8xRvnaCeSae3xMLp
+sytCjDFz53fSTx62ZiwqOZoncPmjZDzw26vOaX4Y3fVB6cQKYRGke7V7pagIeH5N
+W60mruk2IVoA/lgOSZmNT7/iVojHpYQnbaIZZRx0a+VjfgtNjcVouo9Hkll14397
+vPRMLszDHmpvG6/WAcFkG8uJjRihqwVzY+gsAKP5r94IQpQ6rKVCJYxmko5xpYOD
+g+FRnt21GQ3XzUnOjdD+9ayPULA8sm3/IYraP6k2hB3SCKhMpEjno8otabv9h0M3
+FlN8qxJbXKWmMS3Ttq1DxpK5JwJ5jaBE9WfeDtXM1cnCju+qzlYSXogje9dc508s
+wSIO1PLzTYFCanCDUKqNaNX5OqvjtUWslKc/hUPdqtptolGvrXiGhnrc1bQpTmls
+cyBHaWxsbWFubiA8Z2lsbG1hbm5AaW5mb3Ryb3BpcXVlLm9yZz6JAjYEMAEKACAW
+IQSojIrdEpgo1+rALlLiL5u/7jSFiAUCXSx0xQIdIAAKCRDiL5u/7jSFiInyD/9m
+/qoOIXFmAKlGjNVs2nYaP6vQCCAzcOm1WrwP6DYoeG1JWjNlFr0kKhJa/mzR0hFY
+b2+49+Er06l7N0txPRcUhoD/8kKTIx8ljX27icd7BpD52XBqLfnX3Th3OHJA5CtZ
+epQ4/bgUqXkHqUPsmVQHyjlq7liYIlZWwMBfA+L6eLF4MQhcnIoHkfVLyHvJ3B07
+VpJAH5wBBC3NEaPTCAeHbXdSY7vVea3kSZZ+dHcmcIcpBcFFTggruGlPdBfUlt8/
+aeRdfAause3xkQ3/n7wTXugYPRcIM1MsDmHuFaADWEx9eEEg0WY+Fb9TD02iwTyj
+n2zISITI4k/FGIW+8bG+qrE5EDxVxeuociVNi76byaPN3Wx2JhDq8nKtfVWLCbZN
+NN0HYpgfoHYUtFR7SCDbHvL0MVW607Alyubxo5n/WN6RC6jxmFaE1CZpl1jVp5cx
+/Uw5NBv5OwQzd/OqLiTXisM0Jou20c777XRL8nTrFniW2ym38xDkC7q17yhDPesU
++00TR0PWO7RFpw1mzxggGXxmuxA+OZeR/fHdc6CpGmEj8YlpeOOoRw/58/w1LwQr
++zKUzuNo0wKvcn+K0rHoSVOX5O3XAxTg2FRpa48YCY2U4kUH6RITHZRg+Eh5LHHs
+Yj4ow1BsIfW9VAzHkt7b0MxdWsUwqFuXtsaMAg2HGLQObmcwQE5ldEJTRC5vcmeJ
+AlcEEwEKAEECGwMFCQVTg+sFCwkIBwMFFQoJCAsFFgMCAQACHgECF4AWIQSojIrd
+Epgo1+rALlLiL5u/7jSFiAUCXSx1iQIZAQAKCRDiL5u/7jSFiIP0D/4i40WiFeRM
+E3l9El3UOE/AsoldFGSfzXtkfad5X9arry4C4UFRFcQ3OtJnGvfW3Nr/mMNa6w+x
+3z4KnQRqhx8hw3dvQXHZQUjOEAQMg2K5ns38Wixk0OD5Mmqv9qyImwm5A7MOZt5p
+uZqGv21YS8BXQsMw+CLXRsapNAqI4N454bDBoA+l34tOiN4jPgG0jBy9aPZKWh+7
+nLHIn/DJ7xxT9Z3P7gSPcv8cwEumiBA4x79Ac9YszhafMiqwkk3b20DGjAyXZmEK
+l2annmwce/u3P8oQd/+hmcprQZMgh1vxA0wm9yQZ2zUZXAYZASVew9v4KdNVJE+g
+wKfJd4TiPrOkKoll91ikGCY38PPfLCyvGobdrE2LW+6ApQtcTrKu8nzkCGdZiyZ/
+SE2gHMOrYAmCpDH6ujD0z76ywo3a3Ta3njId3S+LTPtpBJNq7jRgXJPTw2OVjt23
+7QrM0QbNTvbjy5OR60BSFhvd4F7LCzhM1b/T9P8oFf+uO2hQxtLnNqdYR4idIRFP
+gXjD5dYpEfTOp1dZviiWqXm6jJFsLJy3FtRvtICLzram0nSO3xA0bB0QH6MqDp4c
+4kcZ8JEunBvsOkIVlzT83CGrBCOaydw+1d3wheFQItPOydVrqVtH+0rGHUTpapL7
+BXttYTzl6n7YIpNDqBufZcWhmqBF2FPTWLQWdGVrLm5vLmthdHplQGdtYWlsLmNv
+bYkCVAQTAQoAPhYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJc3Zx9AhsDBQkFU4Pr
+BQsJCAcDBRUKCQgLBRYDAgEAAh4BAheAAAoJEOIvm7/uNIWIZ0YP/RM+jV5mCCf9
+Yj4KIZ7u4FTUQ4k9KqfvEw134efgZAR5n3+3MvG09glEunMaeoGQDZ3E7qHJs9MT
+cSA8RSfXT/Xp5+dDDpobV7TKzV5heTK6+PjsKH1KlvT3mTDcnRFnwRMQaK6BP1KB
+bLTpxW6kDFD50OChVC+iEBunkgplVl593KLLkqeAmuIzThKIbZLOT6H+bGthSu+V
+IGLp8MfVtjw4dflK0TbvyIVQRVxqQPv/egKIaLAQwah+21svfXib2tJ5kxKlFVbC
+K9IpTcBXljiS7ZQo7/Ok1Z4IyySMeA9CpRZjL0zSygNpHwk8Ml8UcNmcvd1Jbe94
+jUOJk6YIOhAs2LkzxoZdzuxx2RrB4kTHBkM+zMKSJGMXspIdelTOYfjOPvS+wb55
+QobWo6tey4z5LEWt1ymeGTs3pCnAY6Y9FNV2x3+MExhFK9AumKXgfTa9H0ew36wf
+Iju4oAMVWHk6nz+Mv5IwwcFGiH4QZNLZj0r0F0dAelngphersaPZLK3sjqlZNY0L
+atQn8BOZoY8n9JN++JPIwq0LZH5k8Fp5mcGcOVNukXghj59kpXDLJTxnlzdqAQ+R
+9ank2vf9K6aEkzfg+7zjd2r/gkaluTfELmu2SE2zOPKQqwPI3+k+LU7TGAMOVFQv
+wnqwjo4JWx53R2sfHzJiLIeefZ2dGtDRtAluZzBAbjAucG2JAlQEEwEKAD4WIQSo
+jIrdEpgo1+rALlLiL5u/7jSFiAUCXSx09AIbAwUJBVOD6wULCQgHAwUVCgkICwUW
+AwIBAAIeAQIXgAAKCRDiL5u/7jSFiH+hD/9Cp3c2QVCVfEF+eKIPwyKB5jgbmY15
+rsBb75xcNAYK73O4DrYn69a0GdiazxYghafQeg8FOLmxF5XB5e1vvzXOTe0cEOaO
+nnsr/0JhTU9CHIaLeOtgNI6XTF3RQ/r4pxmZ/kIpsHzjrc5t5SlOnGdcElkhT5mi
+YpeLywJyBjwJvnTQr6ks8QF4RGgUpp8V04we2vCkdsuSGzcCDm40NyDfEeLa8tCp
+h93almEtDFI3gcXHr7JiAxj0yxVWi0KaejwphwnudxWu8kyfQUEAKmn+J9gOkOXs
+vTMvRc1f3z12QRSWd6DnZzL9EctDbYXgG7F55me1zLBSCuZP7rj3gw1VfW5+XFEH
+Giq8hqf/V/e7Gq6Dtx5NpAj7TCmBOUOAL80Z5jjOaPSBhALlE0RLuFF4OMQm+hg7
+2tZC06AUJQKmEtdxWt96DKe2lD3pNdsb54T3mxjdgHyO6sxDYHeMGu+D6OhP+L1Q
+WnPnkv4hrsjEnbbHUp98nbiGA53NWTLllsHXmWIkeokau1VmF6ad45z+d2Dtjgo4
+kphux0lYAOqHV6zL0DfTT2MyCpm38dHpHEKzAQVaeppeCetg8HXOMdvbe7b5Tsk4
+HQNryD5B2PD3elvD09amc5SAske2z5ZSYKdjDnrNjUhOESRb5eDEE0b9f5wYzQxW
+qV10dXdgccCIfbQNbmcwQHRhbGVyLm5ldIkCVAQTAQoAPhYhBKiMit0SmCjX6sAu
+UuIvm7/uNIWIBQJdLHbsAhsDBQkFU4PrBQsJCAcDBRUKCQgLBRYDAgEAAh4BAheA
+AAoJEOIvm7/uNIWIbAsQALJNb8HvAPNySloMN8tkBy6jjfzndJGq0JkA5rhT4Zu0
+U3rolSXoo3RC4uXK9vWQXAzE0H9csmC0+4JquXOIJ6Rbm5oRRwtX51MdLJKxb+L5
+kuqkkIbFgnqjWNCqAKB5IRnI4nHroqBwi7ZUWAvR9SXsFT7jNFv/DK0stTf7OEs0
+XsZfwnVZlGzn26JSeFrQEYyJB0/ecrcKZVohge0/FMi6LddjfiJqR8dREiW/mNZS
+lzxWyteluxd1CzIhPUCoJfLJu9g1BFZv5kjurVUsoXcNomYvQksT4mNEVj/wwIPk
+yJZZ6vAQHts3kDy1rRSiJCOShG305m+jc98wRBKhr+YJf+bZzBtMf+Rgfgav1ywx
+3fh8kzds+hFCektsPzGp4CgyT4jdn4Rh3QdqjMH5ah5rWbwxA8G0q8duXc7UUsog
+lMmmXiNiUD7Bg/TJoQPmL1Pj+RPgqH2xeVwnSSwDCfr/U2tHxBpgB55KfrH92U18
+EfW4oW+w2UUhaBEZyrruKdaPUhv8gtx8YEEarWKi6w0e31KXmyPvqS5GnjOZ9PX3
+RtBHQkqg7iiUBBPz9TM+kMX5oHuKymv9w7aVMNPmnT6jKlFJur/hkBn9yxfAWWyV
+yEBAuMW04YjSYRbxjbvH+0Bh0cDpK9bTp6maCLnP15IIOmz6a8uH3M3y00wf5elA
+tA5naWxsbWFubkBuMC5pc4kCVAQTAQoAPhYhBKiMit0SmCjX6sAuUuIvm7/uNIWI
+BQJdLHTXAhsDBQkFU4PrBQsJCAcDBRUKCQgLBRYDAgEAAh4BAheAAAoJEOIvm7/u
+NIWI7Q8P/3IB/0RHO/oRz/1bclxGuCJYFZ/xa+SEnnE/XjAYvQAUn1jSfkdC8yRX
+3Lq2s321hjo0xQXgst0zV4adPOfSyGKFoodnO5mFY6a5pYq2CdFGmuQ6pQD+/OvQ
+O4PLLbtC3z6EtA2GGSlXgNTz0krqjaUX9FwBlbzgNoycFQrhVwx9PpXKDlORjgYU
+4m/VjOi0f0yLzZ7BWDrenw45Brv6zZuFjfqxSHV+u6ecGpnEx4GjB7o0w8MGV/v2
+XZm2p591cJ4agmewjQhfBLP6D+fw20ENsTxgvYYCX+k7dTeFRSrKyariSy702ryu
+QYsh3c9+0EDomQOA0PH5/063f7msXNNwn9MsGqXQJ8umHDRnmC0aMdzQG5m8Orsg
+ZnPBRhvQmDuQRUVworotAqqEfoQ2WWFedNxRUgd3dClaDts5Uc65Tzzd8JA+zYIo
+byKqE9fP3vtPhO1y3+BV2W3YLsmgQtVInzPJq0HBjPXuulFN73eA1PJanRT0n8Al
+730Y10CKmxAMP+pQ416OrYUHwFSCAD5v8EL6AET/NgnI4d19imyoAcjR/W4MyNTx
+YXRlQ8v38Av1B6pvNC3/9ktPtyf6RRY8GVhMVQ7DUSLm2JOVx9vQj2jYM9a79i1+
+sx5OfDGnSnFfQgg+koqyiR82KSqwIZImiDhWWyDkVfnWxY9TvuaxtA5naWxsbWFu
+bkBuMC5wbYkCVAQTAQoAPhYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJdLHUDAhsD
+BQkFU4PrBQsJCAcDBRUKCQgLBRYDAgEAAh4BAheAAAoJEOIvm7/uNIWI6ykQALBm
+K4s0xRE8T+MzxoeSV0gM+bQPqcBJW0FzTFHMOw8U0iJU3kDd/DolliEj2qSnyzxW
+Wp5WJVAp9UVyDxRb9MVp5l1qsniAkyeOVknFD6xbMOohY+5AYgwb6TzwIXXyKxCq
+O3nLE/qzTQE8WI4uLU+PXFQW2KkWY1+XzSGDGtP7c25mA+Rsy46Q9KskEe2S5m/j
+/Xt44pTU1AeMMdN4b4PzM8X60tlHAa/mLg06RT838cQEtJvYJn4nUj595f1aNmqW
+2OQ8AYa9AWBTaQRZls+zSQ3pDp0vEn7pvAn4gJ0cC2CLE86HaloIgBYUeXfc78u3
+XXn9RGdvgNJ9wKI541YLaH+GS+tezXP7qHtVeoLjqYve840Oj/D0fA+HqfnR0AjY
+h4aYc034ShiVUlEfWv2JjvRLIn7QegT1GSW17CftVmp7Na9UyYtizYra4bTVTaUJ
+fh2oKAfLwQRaAxq0knboBkFbXlvLBLgLks6o0hok39VWcaikd9veT8c1QlGID7FU
+Inleo/RAMaI1uJyZgCoXtlmCBz2/cOolTn+YeVfa26x4Yemy+habFkKoeVobwl/8
+1LN1q1L2CJn0sPB6xfNwIKEyab3Nx5CeQIuXZIk4Zi+wwkD8Y1K5SlNW/shEpQCd
+/bPrxvf32IOIfTSOnwK93MQc01r1Z2R3BRc664/dtBNnaWxsbWFubkBnbnVuZXQu
+b3JniQJUBBMBCgA+FiEEqIyK3RKYKNfqwC5S4i+bv+40hYgFAl0sdSECGwMFCQVT
+g+sFCwkIBwMFFQoJCAsFFgMCAQACHgECF4AACgkQ4i+bv+40hYgRXBAAm9A7Go5a
+2PINqMnVob2qJ+uwe+CXnvCnAkIagshn/MXZ3s0+SEyDsK8+zpSXw9IZ62kYPaqG
+Ao9FrB2sQXPfrhWw74OKcHG5GcT3ep49PUV2CcmyGzFV+YoJm7lsy4wbrl4i4PpY
+ijrkT6mICwXFjWaKakEbFKlnKDvGHEWvPLCJhWCszsfnfbqcRKwUykmEmwiD/Te+
+B2H4iavQbgzY1y2UPxCKZFWEhp9IMVGaZFW5H/H6XwzAGNWiOBLSsEQenN1C8TQR
+UgLN64oAuFtt2KY5SQCVnI+cHnHZzBq0Mpi5WdLhQfhXZh7lr+EzMuOlvqQg0zTz
+yH9jgXuh0Hi92Hx7ctzFtCK4DPNatMLlxQbo4TAseqBIy0ltQaIqrn0V1Qp0Tvnj
+jIT/maKfcJZrmCQQT5vsEgPNlsaK5R2pNDWjg3QtvK2+AtavSf3/d08fDIlneNlC
+/8E6XfZ0+0jJdEw2axyFymNhD3uDPEZ9Zo9ZCQQ/lwP1fqjVQhxrWLcSWdpGnhlu
+VWd4pJ9VB2HdXdbKYmyNakmFTbmTeu1CwePcS65mzEs4Eoq2CRxf3t8t3525cQWT
+MG6CPm8pRC7iueDVS6O+qnlV6BARr7wBinp9X2+uG2M9+P7JIBioFc7DCG0DIjDE
+ykq8ZGR5E4+Zv1cUBTWsw2N9EjF5rxvDbnO0GWdpbGxtYW5uQGluZm90cm9waXF1
+ZS5vcmeJAlQEEwEKAD4WIQSojIrdEpgo1+rALlLiL5u/7jSFiAUCXSx1VQIbAwUJ
+BVOD6wULCQgHAwUVCgkICwUWAwIBAAIeAQIXgAAKCRDiL5u/7jSFiIO+D/9foiQJ
+w9fpkXQQD6gP5fPZmgKuM0OHRuw6pEF26JgJFxtrDSx5SPbQxs4PlqavmXk0fbtz
+jWHd8w9X661yf890XxnzSSRZNItCfc7P/DZYTK48YN/13qv30zl5UlZv9p7Zca/f
+2C8VKICIJrSgbWNhW9Ctgh8r3pWCHA1rwtOWvwiKoyrQmG1/L9juOH4SlXtbeT/B
+/xxZxwq3EILVZl5rA0zVI9Qf326ElbDfLwxzpFCaSNVPB1HNSyBRSEdMe3s30IQ6
+LYBDM2ZqQJW1LOUhJIiiEyYTMfTpQROmAQ8k7GuGVPt1YekPjPdn7kyZdW/kbJAE
+CLT4isHgdi+O3PVIUUZn2ZZG2abUnPEdDPuT5lKS5FUfBUcWlHX0jtmtMsQwOwJo
+isNo9fS+8TRsWyGrNxO3L9KEu8ZSZK6frGMvJ+y6nJW0F6yTFVikUEMxYoHRZ+A0
+krenG9/ypHEAi54tSAhKLg/t0yae01M9rZn4YuztWdgu8uIEW9JA4tWDXyqJj7r8
+YZ8/A3oK9qiFRXdZ7oS8njnQzmi8fjFTWiTp7l3z9MHn6+DFwYJMN9adx2lgUvHg
+1Cc9fVOuYt6rSTEwURzJSOc+3uFxH1Cc6MI6b6CiZDe2ulHBh726ng+/8rFKhk4o
+z5BtRWCYPwDrkYok5VeAkdOgFvPrxvZXOKuvq7kCDQRY+5dUARAAvoopO5WMdMk0
+DY5su+KR4V3fR2byaCSoZjbirSZanAUj7PYa4qM8EcJ3zj4FsZDPymKPo/J5XIa5
+Cn2mj9K9n9Y3vVf+tUIl6/lKuJxkBsPSqzM/6gltWb0oA5QkBFX7+pZTW2pURbWo
+vpv6H1aMQJoYyBdbSx+Zu5d/6cVPwO1ZXPiNDcWiHdLVeFMNqL4LHdgMk+igzfQP
+22XTOMUB1CCF8umaR3qkrmyZsJZe4XyJQC709QENHqsUQwfz2grGA8pvKo/ZPImT
+sjDydc9EsHzwwTkD0t08b1ft7aseuYjrY7/cP8vWwYUaNTrLwZHcEv91gV0aIjE0
+U2tpV21Vm4p3f15kqRUPfP8lUiI0inFTp3pu/+4cTBJCmASSQyvoSTlwtGdmDgWF
+L+x7Db//xtvIvfmibVcazcL4eoaI9FKqZvMyxySLYXbibBO+G/g4aQHDCycyFOp8
+dtJ/skOb1ADufFHXnzkEUKJ+5H4YN1Qklw9WT9YwvY/vWcOI2chVD+L8x4+H/LMg
+7OBjVQIySlYHK1qvFSFgM2nNhMwxbtUMSpzXrfpJRdUZ76LZu3IqcV+WHCrCwxIZ
+n6CJ5QXC1423B38GsUterRzI7K9ug5Wvq4yuRrAK2MjgcgpOjsVv9wMwxQ4APtR/
+hQbXN4rLAXhrj0QcUMnEP4W7gqBXTVMAEQEAAYkCNgQYAQoAIAIbDBYhBKiMit0S
+mCjX6sAuUuIvm7/uNIWIBQJZOVxvAAoJEOIvm7/uNIWIWsIP/jAYpQcV/fn/DVs2
+vBfGTgFVKyaYIQJvXxVpapcytKWngWwb01K6r7x1d4vIrUbmAhDbA6n9Tv+NWgIY
+H1O67431TZPOP+iXRTSJyRLopcJXY8xrNFd2tviQ4sglo2sH4l8gar9zXOSh68a2
+foSzLb9ZCsaNnvwFagnNXLwzBFGM+v/U0xkwU2xU65sh/TiyQ1DKgG6JQ4v0TtGb
+ae/B0wJlOHtRXyCtrmmTshGLXQsJc8iduHyRo0we+rneG2yhy1BMig8MjQXY+8bR
+P0wVW4WqRrwfykXjAtW6Qx8wU6fz46q+ve2Qhq/Ok9KpHzlGMgP6c6mThAZLW+3c
+GIdQBZlFsZ2KL62C6s8ynbab7C6yCNT7k9zHAstoDNgPORx5m/mArmvUqMdXycLl
+XPwlTAuA8YUsOGxEkmrZW+GGgq53uy+TQGmWqkSeO5uyWSs5yXbRf292PC6ZzAQX
+xHsVFXScqibBU49ELWTqUvDUV7zMOwjQy+8VBSMrXGGsLE5tj5On+HPHi26vri5W
+e5QzYrai384FSRSpdtnqqCysIQGPb+D6AgLJHdtnp+C6/0OULRrRIHF7JfVeuTde
+sc2h5G07wqj7SCNBRZC5VP/0JvpdGUHfa4WGEDUCXEZiMXiIVLMblfS04pAr4qzB
+1vCWqzb8xCxA+wTd8lTDFobV/pV1uQINBFk4U2wBEADgRM0G+Dnl/wlrHNb9sr3/
+yW9tHA8weIbwvfly/NRW6LHSLIPvsLksabVQsYbUH6i2aK2ZlE3Oo+H/R2wrs7dm
+VCo57O4MbZk8Kb0fatN3qhq6g/+bNobVIexS5XN6g5JcmXM4ZzR8Q0rEd46oaxFW
+y8nDSw4RR1d+OU5/Z/LHR1VUTCQKU0Q1Jv//4YFVq/BEf6oj4SU9+/Li9kUo9f++
+i4PaiWyrQDm1FAYtMGW5MBKH3ohO1dlPgqNjdeqTjZfgvCMPdbyV6Xwtz7KVkCR0
++r9u7JefCCKUXL3Ap4VPtjhyCLoRuqJ+ZIp9XR2wf3rVGR6KRcLWPEXLkGfAPCs+
+7uAnfReBxNiWYt+FHuQpeyUld8u8E0G8u9FSf/l25A85QrQK0EUrVHdFc1q8tcCe
+q0EomoIPl7GnwtDIwYmkWtViCz0ivVRvNBUTXvq0XtI/9kLgcBgKfzap8dLeVSXJ
+rUhYlbcOZNnstzkmut1ce8my5TwSRzr2dxgUF8563cM3cdLu+C9bdMWvR/s4xwu6
+Q5opbehdFHd2Hj/Lnqv+xwNKNFkhZCHiyum8L/VKQAsboXgJ7/sB7CHsEcBif73R
+Wj3bFcMnPHHlJgxXB1aOH4kM+y6fF8wW/bGC/9gGiYXzovdbopv3B89oyuT73aoX
+g4TIPz6gv6Bg1OiGpfseGwARAQABiQI2BBgBCgAgAhsgFiEEqIyK3RKYKNfqwC5S
+4i+bv+40hYgFAlk5XBkACgkQ4i+bv+40hYiyTA//XnNjay+uK31lmven3gLQ/BRu
+liydFotXRugBMExTNhBmv279krSpREbvZfFLSXshMcTDclzCY4SrjOgrXfFS8Eqd
+RCnxmqVxBQsS7T2ObAMQQLp1yyFdupT2M7no5bnDWetH9c/CBZP0bZa4Ar3oFZXn
+CKvs2ACef1BJ8f5vgx7noi8bpggUP6uDZF1gyyB2yLZEvlV8AWz3hN1otzkSmVgx
+bvD9d+ugcqOgAMv6JpbxxiNvqMX2CVjjoBeQC5/uKCkJW51NnZzHXtDHqcKKKlbp
++WrkwxjoF05uqRDFXnnCTTC3PUrg1stYkn7M+52dAG8HAFBtReuTZoZ3AL1kdtsp
+XoG8PtWHGzu1NNrPrxCL6KLn5jzjLGkWR17uGhmflKwdvRgrbWO97XhZoHaocaTE
+1pbRBRhJ8bhmkw08Yaatt9qcUqJLPyj0cEeNIU0I5h9n7n1CpTyns3Ow28H4k1p9
+8Sf19UqEOaUIeGtU/Gf/7qbD8q6lmpgZ5/0/7wNoBYM5MdvfMDU0NJJ3qESLiEm6
+lCGq4nq+GsLyFmIXyTeFbsyO/Ion1WJdtrGvEgkQRuVUB8HqsFQ1b4+EJAiBTMsF
+z+yxUcZJNEngQegSMaNyXFptrFrXzZHOXFADYmuTgtk5SbF0V5am4IfineWNSmM+
+nlBNsWyazlZNA3vuAvc=
+=bioM
+-----END PGP PUBLIC KEY BLOCK-----
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFZlTN8BEADIXdWebdUepgP8YkULGh2EClt/q2Nkh5QB+V88ZtWVdEfz6ELb
+KeKE/39yllXso20H56OfWGgcU2SF6EKdT+FDir5pDxM+RQiIjrYHLMj9MG87LBcW
+65PHny6hmXtrfrWISXq7x2Si5G9pMz33jp5Dsx/IMTbTPbdK09b34S9aqIjTkpQ4
+yqByi07nkRcYgSOzx1Dr/7oatKn5/tTRQm9CQ2pqcYYD5Rqg1jcNpKRUWFX/m+LR
+d3iQ6ZF/F2W9hR6BYWRUi3eJOFYX/ngWrSj3q3c3zQgPy7R/4weZRT/WYjwccHyv
+LHbw3YFVLDgM2RAu2q765+3iWrH4RvYxS0eMDan7uK6q3+83KB83ofnH8IEt6PWK
+3tmmQJ1vYbQDSqeLxiptPlOgoQuaJCCAFJaBIwamLZJq0BPmncDzZ3bGksROgV31
+qqFYsdKfyUnKQZZpEVsdpOz1oMK0RSlqW2j759C8E4DrsqCBoBm63lZPQsYp94s4
+gT5W2D3vfPqF3dOht6nByGVYvwh3ildcBtKcU8vctlms+izbb0p94pviM10/vIuu
+AzerB4Pb8qMN8+KuSfIUtTWprD/D0NAPRBpc7Uiv8sSufldNhN+A4GdkkXe409+A
+WGusKMlZO9fP3BYf+J3jDxlbRoVoEyl67dioT0QbFdhOqQt1EjJH9XT77QARAQAB
+tC1NYXJ0aW4gU2NoYW56ZW5iYWNoIDxtc2NoYW56ZW5iYWNoQHBvc3Rlby5kZT6J
+AlcEEwEIAEECGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4ACGQEWIQQ9EQY8EPmN
+FL0k0UcLCZjvhvWbagUCXkKjswUJHDOYZAAKCRALCZjvhvWbakZOEACqn3cj7vG+
+jjzbnWsohTwN9kJnvpRAtWwFw2mPYLRdFkYgBwP7AroDUS9nESzajx/sK4wUrfYk
+C3hK020Bx+Elg48mdSAy9O1/gUDY24rANTnfisqtO2IILsEyd6tJJXa0XHziH3Tz
+dggsCOEEICCOxLOkDi3Syk3P5yUL/OHDkLJ8nv4QJBGjjuuX09CErX2NYQnkqa0Z
+MOecfTtv++jO/jAXGR6Hl3c4lf2udt6fYV9zrtSkcv/NPFvJ7P0GcxA1Xws4OftN
+z/8rgz1TfVuho9mBIvvUKVT17Z80wQCTfaBNkChbHccDzaQPSDRkoG2ohvYrJveP
+lKM9NfMpPqrjceaO+rx+Ft5mBU9uSL8Oo8lJ2sMsxqmEbym1Xxdm96P3D+GNjZ0H
+Gnl26DprWTBHjpGSotV5rzncRh+9oTcvmzkO7hvgUGICHCGeyS3wM7qiiY2M1wHl
+5ChlOv5Ske2oA+EHoMKxJQ2iJpkfeP6rHckHkVD7vDDCaiXUYrfjCb17CSOUHuPq
+sdGbfHyItTM0cWpB5Jq/P6Mi9xymnxVpCeIkB2v05gszzGcF3+hLmRtdTzExilAC
+zmWKXLL/mD2SvnENXLOJ5lzJCD7yQ+KkzMDPqkg4JPeinyT/MX8q2uWKa7pcOHJJ
+9Hb4fMNwvUSsx01JCHrUS96JSssGiroaFLQpTWFydGluIFNjaGFuemVuYmFjaCA8
+c2NoYW56ZW5AZ251bmV0Lm9yZz6JAlQEEwEIAD4WIQQ9EQY8EPmNFL0k0UcLCZjv
+hvWbagUCXJn9KQIbAwUJDSYvygULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAL
+CZjvhvWbaqPbD/sGcPJJKK9siqY2o7w8zh5joKypSsJh2GZEpFMMIsIeVa1EW+ZO
+fXp9YQkO9ezBKxHgtZQxZ9dHIbB8Tn3+VnLJHd9wY0aE0OTPO6GNtADJWbzx6Cec
+75Ddg+WkCiTdn4MpMbF2jQKvy6nPfk7ZSbSL352VNVwrraLBjBJ3aXE+cPzXe76X
+oUc8kkNICvPkMSMljR2ayGk/wd8u8O78GW/8LUMVz2cwHsnbJrEQu5WeI6LVoK/+
+kWpHDjiNhFwIg0ZjgoOl42QOEAYz2lmGAhyBhrKd7qZ0NpSoPfU1InMy9j2AoStE
+hIOgdwepgXp8b71vzJEQiYMDvg6LJiANzohlJjzXQblzdu/gYaRjSNY1Rm3LV1ze
+MrPMqYeFeiQEY6Hic9VrKiXczIYhbHv4lBefwUJkuTk2y237G4CyIvBvXyA0lHDU
+5yumD4GDc66Rvyl9tZqKX+5Hss2dEO86QL+OcTYlPsGWq676c/T8mbdvfz+ENzy1
+iBa77WYFQkDSR1ND1f/GBE1fu7U1PuIDSo7uQarU6WJq3cQ+mtw3ncw9RfOpwqeF
+GTf/2eHAW980zhyo7BIhBYxn+SMvBV96gYVS3f+Ye6vV8C6eWNaOzouIe4+dLOFx
+kZAHkqCD8DaU5XalXwmeRTa5y1rzN9HMxNjfYeFrZu392myFjG55VYYG37kCDQRW
+ZUzfARAAsHyOwNqnALHCpk9i+5BkyiS0tLMpgzN5LC3RzN3Dm2CEyQPWISNrr43a
+FuoTMPEFkqsg0FX6LzVGZvqEtcS2E5DdvuVWJWBK+gLMxUWnm/p21ouhvLNcglYE
+FlvIscYmwVwggwJYm9TBru872gTT7s6NDVSLFXxkPf3hPDWCYeaooKcRzuXZWb85
+E1HFgUOR0uZCfBtPGG/tniyqP64g40gAV88WuyN4vkF9Nh3jfpjgQ8eYzhmWAAso
+EC6pBn8kjek6GnplYqkuy97FR87nXcb4d8zameC8rynlquNk9B4fsAVe36upQPJb
+GMF/VYjc1ubPQeawMrpYWPZboOK+oulSPaH7AQNixBpqSxdVNw9jHNZPSGPq2yPp
+pGTgI1wLWlGQlAEjBcPgEWWzWgW5os4oULn2D8i8S9pi8OhSQiteKZiojRD0q9D7
+TfSXA4XSZ75+uYxi5T3DTSSRa+pEufl5BMphVyJKvqjX+Ek6dCodUzfGE69qfKTM
+Vi3peEUMVMrsM2FoB7BA4l8Z/1UhoF9jD2yrW/+oJEWsWbJcGxsskzHNGr1ntk31
+u/MC+O8O6VFuuTjfpjpbS7rsbZZRtl1u/rhoCRpURz7AillX2hhl+5U4MOnYgZQ3
+c5Xh+5+mD8C0nMGz9pg5+6XK3fRfiN6ajHLcJJeN6bXKN8Pr06MAEQEAAYkCPAQY
+AQgAJgIbDBYhBD0RBjwQ+Y0UvSTRRwsJmO+G9ZtqBQJck7EABQkNHkaxAAoJEAsJ
+mO+G9ZtqMt4QAJznYvhb1P2TXkq4eJ+wt0E5SWilT6+tjIooYA4p8oIDi8nl+nHH
+MIo5IllAYnWXGkaxARVSzA3Ci8CoETX4hGdKnHy7hRvYR2psATapfVts1Ouj9vqu
+0zDpBATJhkom5xgTjWkT1ZgVIEbVHZiNIpSgA2OI4FqpL5rDw7uvMmttyR855s3/
+ufyhAjIXJMC6/8/7JG7Cu4d2pY/tumoeLjks69hUlqsM4RptZij/sC2m0BH5JOY+
+rj8YKGlliBciUbSkoTjOTExQoipLjpwgADmKu85TAL0X0PIqvM23n4K1IjiZjmNl
+9vjOwdtugOH7AYJV3RNjGLRxy4gJP+jlXL7rWEFFvL2WxSRuy1EqMRNzDlx/5xM3
+1PJsmcc6wIhyLDq40m0gdyh43Lk6EeaLjf6+QJrn2+AwTGAc3k0KOu50hLnSHPKZ
+0dYfhqD6iJOkByAc+usyfHNQ2+IQWy/F+AQc+ST0p/A+xiC3D7OHbaZJM+Mmqepc
+aUIt5jJ2IylxPet7yZBfV8f+6NUGGbNJy0Xd6qv2EE3osBMd0XyaEHPSxnSvGJfx
+a7KJQLOr/WpfSJeZglW3fQPWwhAjeEFFBibwso/D6vXxK6x/N8axUyRiJHOmLKNp
+UFEhZpET3FoAMnC3vxYynv3ooYw3oLxl1V2TVHN4s4zlDS7dkAokPX63uQINBFob
+JskBEAC9bcDtSKWB85zmXbIztVQF/73mSJQBZiPfNpQqTiClsQ56qMHIUsqLw2qG
+cgDj2cv8U5NPxoLQc2w6HMqcD9ASmSa6DePUPpADp7HVPZB4GnBcSu4IEjO6dlif
+rH098eBoEIZzU4ghvpDzIBmfBQ5pveUGqvqt/2e2xtJug0FmpuWXYlQlV9Sj4Xar
+s3sPhLekXaRZ7fDULnS14DZRuBMdRNwyhOPz5xFCK1JiahfZ6pALS9xvWyaD1Wa0
+/IhJzIA3vDGR96KJVX/EtnggWuC9csoq8QoIqwxbcbKwlceE5EGSJTpceB63z3s+
+nM2OECGlQlg1oktfLdw37QFyh5uHqEi9kJVconb1Z0vt0WtZmarzYRIJDwoIE2aC
+EM1bmXijQIl/W8elcLDCL7o4m9v2fdYTk+xqJ5x165E6N4xKKL+B5zKTcOocg2rr
+s1hFV/LIRUl/rYB+58WTzvorym14ZdcLiu2/xWa4M4Qc7sIu8Hk69g+zKTS22eRD
+Mo0q96jNGfa/5Qu20Iz8eKK4lDsGpbbRnA7+U1ayxzTV36fxI0L5Ru7spq0rHJ3h
+c88v5IG9RCyxJIug0ZbLX4+P/M0yKNDj73o1nbL81TI1tPsuUFsygN2PN+RowoVN
+vmDoXlKWbT4eMfMiCbw/PCm3ZEVz/m9M3VjoRrb1T6S7DalqXQARAQABiQI8BBgB
+CAAmAhsgFiEEPREGPBD5jRS9JNFHCwmY74b1m2oFAlyTsQoFCQlobNEACgkQCwmY
+74b1m2qDBhAAnIyHlZGTgbiVTVBgjrIEYasPWn+59I/zULVGGe0yEvHzUoAeWoKE
+MudtfIUMb6Ypcoxwo8AHVvSsCSuLWiMDysu6Y3+p9B/iNDVlCU/3eA/BjCpD5ofU
+482Dyv5hpqdfv8nLehBjSnlfLnIf9b4kIAuTI1hM2kQFkM3/Eh4mfB2XJBFQxzS3
+gedWLrZoUp3qUp/BOkIroRPeu2N96d+6a9b35S17GJxWehgVjEwLZyhKCHliOYTk
+k4ibMc964iDSIdjpTAszHj/dMkt82Ovv2Q7IpFB6dhd6Mb3Les02f6lNyTBixud6
+/1ADj4LzyUwYyrlF8Mhjg/vJn++gAPFRqSrY5pwwsqci4Wr1/mgrM9WQd1wnkGZp
+0eM2q598b9fBgNvDnk5N8rCLqxRaxfUrvVEnCb5KbWtAwzp6GJ447KGHQRpfGN2B
+yXXtekurH2tuixSWSVnCwN7oN5hqXxhA60puyVSQlRZ5oqq/DTY5Gl+8HO/6qjaa
+iRD6frB32eB3/eIUHE+HhqMkVKcvoz1PUdjDO+YArRdkdREpQ7OBgqdI5/WkmDez
+DZ8s/8LH7NmWyaDiYmQwZzDJw/286pTn+U0JvAvMU98tSQKD163iYcUprdkMEgWB
+bm9msTujYyUbqJg/epAVjJahjtYwnCFhuJKvoIAlOXAqNksqPDoPwfU=
+=tbKl
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/debian/watch b/debian/watch
new file mode 100644
index 000000000..10ca7c1b9
--- /dev/null
+++ b/debian/watch
@@ -0,0 +1,3 @@
+version=4
+opts="pgpmode=auto, uversionmangle=s/pre/~pre/;s/rc/~rc/" \
+ https://ftp.gnu.org/gnu/@PACKAGE@/@PACKAGE@@ANY_VERSION@@ARCHIVE_EXT@
diff --git a/doc/.gitignore b/doc/.gitignore
index d4fa2be60..f7b3a16cd 100644
--- a/doc/.gitignore
+++ b/doc/.gitignore
@@ -1,7 +1,6 @@
*.aux
*.dvi
*.log
-*.pdf
*.out
*.snm
*.toc
@@ -25,3 +24,6 @@ taler-exchange.html
taler-exchange.fn
taler-exchange.cp
taler-exchange.auxtaler-exchange.cps
+cbdc-es/cbdc-es.pdf
+cbdc-it/cbdc-it.pdf
+audit/response-202109.pdf
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 119c8046a..ca973a1a5 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -2,34 +2,69 @@
SUBDIRS = . doxygen
+AM_MAKEINFOHTMLFLAGS = $(TEXINFO_HTMLFLAGS)
+
+infoimagedir = $(infodir)/images
+
man_MANS = \
- prebuilt/man/taler-auditor.1 \
- prebuilt/man/taler-auditor-dbinit.1 \
- prebuilt/man/taler-auditor-exchange.1 \
- prebuilt/man/taler-auditor-sign.1 \
- prebuilt/man/taler-bank-transfer.1 \
- prebuilt/man/taler-config-generate.1 \
+ prebuilt/man/taler.conf.5 \
+ prebuilt/man/taler-config.1 \
+ prebuilt/man/taler-aggregator-benchmark.1 \
+ prebuilt/man/taler-auditor.1 \
+ prebuilt/man/taler-auditor-dbinit.1 \
+ prebuilt/man/taler-auditor-httpd.1 \
+ prebuilt/man/taler-auditor-offline.1 \
+ prebuilt/man/taler-auditor-sign.1 \
+ prebuilt/man/taler-auditor-sync.1 \
+ prebuilt/man/taler-bank-benchmark.1 \
prebuilt/man/taler-exchange-aggregator.1 \
- prebuilt/man/taler-exchange-dbinit.1 \
- prebuilt/man/taler-exchange-httpd.1 \
- prebuilt/man/taler-exchange-keyup.1 \
- prebuilt/man/taler-exchange-keycheck.1 \
- prebuilt/man/taler-exchange-wire.1 \
- prebuilt/man/taler-exchange-wirewatch.1 \
- prebuilt/man/taler-exchange-benchmark.1 \
- prebuilt/man/taler.conf.5
+ prebuilt/man/taler-exchange-benchmark.1 \
+ prebuilt/man/taler-exchange-closer.1 \
+ prebuilt/man/taler-exchange-dbconfig.1 \
+ prebuilt/man/taler-exchange-dbinit.1 \
+ prebuilt/man/taler-exchange-drain.1 \
+ prebuilt/man/taler-exchange-expire.1 \
+ prebuilt/man/taler-exchange-httpd.1 \
+ prebuilt/man/taler-exchange-kyc-aml-pep-trigger.1 \
+ prebuilt/man/taler-exchange-kyc-tester.1 \
+ prebuilt/man/taler-exchange-offline.1 \
+ prebuilt/man/taler-exchange-router.1\
+ prebuilt/man/taler-exchange-secmod-cs.1\
+ prebuilt/man/taler-exchange-secmod-eddsa.1\
+ prebuilt/man/taler-exchange-secmod-rsa.1 \
+ prebuilt/man/taler-exchange-transfer.1\
+ prebuilt/man/taler-exchange-wire-gateway-client.1\
+ prebuilt/man/taler-exchange-wirewatch.1 \
+ prebuilt/man/taler-fakebank-run.1 \
+ prebuilt/man/taler-helper-auditor-aggregation.1 \
+ prebuilt/man/taler-helper-auditor-coins.1\
+ prebuilt/man/taler-helper-auditor-deposits.1\
+ prebuilt/man/taler-helper-auditor-purses.1\
+ prebuilt/man/taler-helper-auditor-reserves.1\
+ prebuilt/man/taler-helper-auditor-wire.1 \
+ prebuilt/man/taler-terms-generator.1 \
+ prebuilt/man/taler-unified-setup.1
info_TEXINFOS = \
- prebuilt/texinfo/taler-exchange.texi \
- prebuilt/texinfo/taler-bank.texi \
- prebuilt/texinfo/onboarding.texi
+ prebuilt/texinfo/taler-auditor.texi \
+ prebuilt/texinfo/taler-developer-manual.texi \
+ prebuilt/texinfo/taler-exchange.texi
+
+install-info-local:
+ @echo " $(MKDIR_P) '$(DESTDIR)$(infodir)/taler-auditor-figures'"; \
+ $(MKDIR_P) "$(DESTDIR)$(infodir)/taler-auditor-figures" || exit 1; \
+ @echo " $(MKDIR_P) '$(DESTDIR)$(infodir)/taler-exchange-figures'"; \
+ $(MKDIR_P) "$(DESTDIR)$(infodir)/taler-exchange-figures" || exit 1; \
+ echo " $(INSTALL_DATA) auditor-db.png replication.png '$(DESTDIR)$(infodir)/taler-auditor-figures'"; \
+ $(INSTALL_DATA) '$(srcdir)/prebuilt/texinfo/taler-auditor-figures/auditor-db.png' '$(srcdir)/prebuilt/texinfo/taler-auditor-figures/replication.png' "$(DESTDIR)$(infodir)/taler-auditor-figures" || exit 1;
+ echo " $(INSTALL_DATA) kyc-process.png exchange-db.png '$(DESTDIR)$(infodir)/taler-exchange-figures'"; \
+ $(INSTALL_DATA) '$(srcdir)/prebuilt/texinfo/taler-exchange-figures/kyc-process.png' '$(srcdir)/prebuilt/texinfo/taler-exchange-figures/exchange-db.png' "$(DESTDIR)$(infodir)/taler-exchange-figures" || exit 1;
EXTRA_DIST = \
$(man_MANS) \
doxygen/taler.doxy \
$(info_TEXINFOS) \
- prebuilt/texinfo/onboarding-figures/exchange-db.png \
- prebuilt/texinfo/onboarding-figures/arch-api.png \
- prebuilt/texinfo/taler-exchange-figures/exchange-db.png \
- prebuilt/texinfo/taler-bank-figures/exchange-db.png \
- prebuilt/texinfo/taler-bank-figures/arch-api.png
+ prebuilt/texinfo/taler-auditor-figures/auditor-db.png \
+ prebuilt/texinfo/taler-auditor-figures/replication.png \
+ prebuilt/texinfo/taler-exchange-figures/kyc-process.png \
+ prebuilt/texinfo/taler-exchange-figures/exchange-db.png
diff --git a/doc/audit/report-202005.pdf b/doc/audit/report-202005.pdf
new file mode 100644
index 000000000..55d52cdfa
--- /dev/null
+++ b/doc/audit/report-202005.pdf
Binary files differ
diff --git a/doc/audit/response-202005.tex b/doc/audit/response-202005.tex
new file mode 100644
index 000000000..c0bcf18a7
--- /dev/null
+++ b/doc/audit/response-202005.tex
@@ -0,0 +1,243 @@
+\documentclass[11pt]{article}
+\oddsidemargin=0in \evensidemargin=0in
+\textwidth=6.2in \textheight=8.7in
+%\topmargin=-0.2in
+
+\usepackage[ansinew]{inputenc}
+\usepackage{makeidx,amsmath,amssymb,exscale,multicol,epsfig,graphics}
+
+\begin{document}
+\pagestyle{headings}
+\title{Preliminary response to the \\ GNU Taler security audit in Q2/Q3 2020}
+\author{Christian Grothoff \and Florian Dold}
+
+\maketitle
+
+\section{Abstract}
+
+This is the response to the source code audit report CodeBlau
+created for GNU Taler in Q2/Q3 2020.
+
+\section{Management Summary}
+
+We thank CodeBlau for their detailed report and thorough analysis. We are
+particularly impressed that they reported issues against components that were
+not even in-scope, and also that they found an {\em interesting} new corner
+case we had not previously considered. Finally, we also find several of their
+architectural recommendations to strengthen security to be worthwhile, and
+while some were already on our long-term roadmap, we will reprioritize our
+roadmap given their recommendations.
+
+Given our extensive discussions with CodeBlau, we also have the impression
+that they really took the time to understand the system, and look forward
+to working with CodeBlau as a competent auditor for GNU Taler in the future.
+
+\section{Issues in the exchange}
+
+We agree with the issues CodeBlau discovered and both parties believe that
+they have all been addressed.
+
+\section{Issues in the auditor}
+
+We appreciate CodeBlau's extensive list of checks the Taler auditor performs,
+which was previously not documented adequately by us. We agree that the
+auditor still needs more comprehensive documentation.
+
+As for issue \#6416, we agree with the analysis. However, the proposed fix
+of making the primary key include the denomination would create other problems,
+such as the exchange sometimes not having the denomination key (link, refund)
+and the code in various places relying on the assumption of the coin's
+public key being unique. Furthermore, allowing coin key re-use may validate
+a terrible practice. We thus decided it is better to ``fail early'', and
+modified the code to check that the coin public key is ``unique'' during
+deposit, refresh and recoup and ensured that the exchange returns a proof
+of non-uniqueness in case of a violation. The test suite was extended to
+cover the corner case.
+
+\section{Issues in GNUnet}
+
+We agree with the issues CodeBlau discovered and both parties believe that
+they have all been addressed.
+
+\section{General remarks on the code}
+
+We understand that writing the code in another programming language may make
+certain checks for the auditor less work to implement. However, our choice of C
+is based on the advantages that make it superior to contemporary languages for
+our use case: relatively low complexity of the language (compared to C++);
+availability of mature compilers, static and dynamic analysis tools;
+predictable performance; access to stable and battle-tested libraries; and
+future-proofness due to portability to older systems as well as new platforms.
+
+We believe creating a parallel implementation in other languages would provide
+advantages, especially with respect to avoiding ``the implementation is the
+specification''-style issues. However, given limited resources will not make
+this a priority.
+
+We disagree that all modern software development has embraced the idea that
+memory errors are to be handled in ways other than terminating or restarting
+the process. Many programming languages (Erlang, Java) hardly offer any other
+means of handling out-of-memory situations than to terminate the process. We
+also insist that Taler {\em does} handle out-of-memory as it does have code
+that terminates the process (we do {\em not} simply ignore the return value
+from {\tt malloc()} or other allocation functions!). We simply consider that
+terminating the process (which is run by a hypervisor that will restart the
+service) is the correct way to handle out-of-memory situations. We also have
+limits in place that should prevent attackers from causing large amounts of
+memory to be consumed, and also have code to automatically preemptively
+restart the process to guard against memory exhaustion from memory
+fragmentation. Finally, a common problem with abrupt termination may be
+corrupted files. However, the code mostly only reads from files and limits
+writing to the Postgres database. Hence, there is no possibility of corrupt
+files being left behind even in the case of abnormal termination.
+
+
+\section{More specs and documentation code}
+
+We agree with the recommendation that the documentation should be improved,
+and will try to improve it along the lines recommended by CodeBlau.
+
+\section{Protocol change: API for uniformly distributed seeds}
+
+We agree with the suggestion, have made the necessary changes, and both
+parties believe that the suggestion has been implemented.
+
+\section{Reduce code complexity}
+
+\subsection{Reduce global variables}
+
+While we do not disagree with the general goal to have few global variables,
+we also believe that there are cases where global variables make sense.
+
+We have already tried to minimize the scope of variables. The remaining few
+global variables are largely ``read-only'' configuration data. The report does
+not point out specific instances that would be particularly beneficial to
+eliminate. As we continue to work on the code, we will of course evaluate
+whether the removal of a particular global variable would make the code
+cleaner.
+
+Also, we want to point out that all global variables we introduce
+in the exchange are indicated with a prefix {\tt TEH\_} in the code, so they
+are easy to identify as such.
+
+\subsection{Callbacks, type pruning}
+
+We understand that higher order functions in C can be confusing, but this
+is also a common pattern to enable code re-use and asynchronous execution
+which is essential for network applications. We do not believe that we
+use callbacks {\em excessively}. Rewriting the code in another language
+may indeed make this part easier to understand, alas would have other
+disadvantages as pointed out previously.
+
+\subsection{Initializing structs with memset}
+
+Using {\tt memset()} first prevents compiler (or valgrind) warnings about
+using uninitialized memory, possibly hiding bugs. We also do use struct
+initialization in many cases.
+
+The GNUnet-wrappers are generally designed to be ``safer'' or ``stricter''
+variants of the corresponding libc functions, and not merely ``the same''.
+Hence we do not believe that renaming {\tt GNUNET\_malloc} is indicated.
+
+The argument that {\tt memset()}ing first makes the code inherently more
+obvious also seems fallacious, as it would commonly result in dead stores,
+which can confuse developers and produce false-positive warnings from static
+analysis tools.
+
+\subsection{NULL pointer handling}
+
+The problem with the ``goto fail'' style error handling is that it rarely
+results in specific error handling where diagnostics are created that are
+specific to the error. Using this style of programming encourages developers
+to create simplistic error handling, which can result in inappropriate error
+handling logic and also makes it harder to attribute errors to the specific
+cause.
+
+However, we have no prohibition on using this style of error handling either:
+if it is appropriate, develpers should make a case-by-case decision as to how
+to best handle a specific error.
+
+We have made some first changes to how {\tt GNUNET\_free()} works in response
+to the report, and will discuss further changes with the GNUnet development
+team.
+
+\subsection{Hidden security assumptions}
+
+We disagree that the assumptions stated are ``hidden'', as (1) the Taler code
+has its own checks to warrant that the requirements of the {\tt
+ GNUNET\_malloc()} API are satisfied (so enforcement is not limited to the
+abstraction layer), and (2) the maximum allocation size limit is quite clearly
+specified in the GNUnet documentation. Also, the GNUnet-functions are not
+merely an abstraction layer for portability, but they provided extended
+semantics that we rely upon. So it is not like it is possible to swap this
+layer and expect anything to continue to work.
+
+When we use the libjansson library, it is understood that it does not use
+the GNUnet operations, and the code is careful about this distinction.
+
+\subsection{Get rid of boolean function arguments}
+
+We agree that this can make the code more readable, and have in some places
+already changed the code in this way.
+
+\section{Structural Recommendation}
+
+\subsection{Least privilege}
+
+It is wrong to say that GNU Taler has ``no work done'' on privilege separation.
+For example, the {\tt taler-exchange-dbinit} tool is the only tool that requires
+CREATE, ALTER and DROP rights on database tables, thus enusring that the ``main''
+process does not need these rights.
+
+We also already had the {\tt taler-exchange-keyup} tool responsible for
+initializing keys. In response to the audit, we already changed the GNUnet API
+to make sure that tools do not create keys as a side-effect of trying to read
+non-existent key files.
+
+We agree with the recommendation on further privilege separation for access
+to cryptographic keys, and intend to implement this in the near future.
+
+\subsection{File system access}
+
+The auditor helpers actually only read from the file system, only the LaTeX
+invocation to compile the final report to PDF inherently needs write
+access. We do not predict that we will retool LaTeX. Also, the file system
+access is completely uncritical, as the auditor by design runs on a system
+that is separate from the production exchange system.
+
+Because that system will not have {\em any} crypto keys (not even the one of
+the auditor!), CodeBlau is wrong to assume that reading from or writing to the
+file system represents a security threat.
+
+We have started to better document the operational requirements on running the
+auditor.
+
+\subsection{Avoid dlopen}
+
+Taler actually uses {\tt ltdlopen()} from GNU libtool, which provides
+compiler flags to convert the dynamic linkage into static linkage. For
+development, dynamic linkage has many advantages.
+
+We plan to test and document how to build GNU Taler with only static
+linkage, and will recommend this style of deployment for the Taler
+exchange for production.
+
+\subsection{Reduce reliance on PostgreSQL}
+
+CodeBlau's suggestion to use an append-only transaction logging service in
+addition to the PostgreSQL database is a reasonable suggestion for a
+production-grade deployment of GNU Taler, as it would allow partial disaster
+recovery even in the presence of an attacker that has gained write access to
+the exchange's database.
+
+We are currently still investigating whether the transaction logging should be
+implemented directly by the exchange service, or via the database's extensible
+replication mechanism. Any implementation of such an append-only logging
+mechanism must be carefully designed to ensure it does not negatively impact
+the exchange's availability and does not interfere with serializability of
+database transactions. As such we believe that transaction logging can only be
+provided on a best-effort basis. Fortunately, even a best-effort append-only
+transaction log would serve to limit the financial damage incurred by the
+exchange in an active database compromise scenario.
+
+\end{document}
diff --git a/doc/audit/response-202109.tex b/doc/audit/response-202109.tex
new file mode 100644
index 000000000..43ed6a636
--- /dev/null
+++ b/doc/audit/response-202109.tex
@@ -0,0 +1,291 @@
+\documentclass[11pt]{article}
+\oddsidemargin=0in \evensidemargin=0in
+\textwidth=6.2in \textheight=8.7in
+%\topmargin=-0.2in
+
+\usepackage[ansinew]{inputenc}
+\usepackage{makeidx,amsmath,amssymb,exscale,multicol,epsfig,graphics,url}
+
+\begin{document}
+\pagestyle{headings}
+\title{Final response to the \\ GNU Taler security audit in Q2/Q3 2020}
+\author{Christian Grothoff \and Florian Dold}
+
+\maketitle
+
+\section{Abstract}
+
+This is the response to the source code audit report CodeBlau
+created for GNU Taler in Q2/Q3 2020.
+
+\section{Management Summary}
+
+We thank CodeBlau for their detailed report and thorough analysis. We are
+particularly impressed that they reported issues against components that were
+not even in-scope, and also that they found an {\em interesting} new corner
+case we had not previously considered. Finally, we also find several of their
+architectural recommendations to strengthen security to be worthwhile, and
+while some were already on our long-term roadmap, we will reprioritize our
+roadmap given their recommendations.
+
+Given our extensive discussions with CodeBlau, we also have the impression
+that they really took the time to understand the system, and look forward
+to working with CodeBlau as a competent auditor for GNU Taler in the future.
+
+\section{Issues in the exchange}
+
+We agree with the issues CodeBlau discovered and both parties believe that
+they have all been addressed.
+
+\section{Issues in the auditor}
+
+We appreciate CodeBlau's extensive list of checks the Taler auditor performs,
+which was previously not documented adequately by us. We agree that the
+auditor still needs more comprehensive documentation.
+
+As for issue \#6416, we agree with the analysis. However, the proposed fix
+of making the primary key include the denomination would create other problems,
+such as the exchange sometimes not having the denomination key (link, refund)
+and the code in various places relying on the assumption of the coin's
+public key being unique. Furthermore, allowing coin key re-use may validate
+a terrible practice. We thus decided it is better to ``fail early'', and
+modified the code to check that the coin public key is ``unique'' during
+deposit, refresh and recoup and ensured that the exchange returns a proof
+of non-uniqueness in case of a violation. The test suite was extended to
+cover the corner case.
+
+{\bf Update:} We have now also addressed the (``soft'') exchange online
+signing key revocation issue (\#6161) reported originally by CodeBlau.
+The auditor now checks for key revocations before recording deposit
+confirmations. The impact is very minor, as this will merely prevent
+an adversary controlling an exchange online signing key from submitting
+false claims to the auditor.
+
+
+
+\section{Issues in GNUnet}
+
+We agree with the issues CodeBlau discovered and both parties believe that
+they have all been addressed.
+
+\section{General remarks on the code}
+
+We understand that writing the code in another programming language may make
+certain checks for the auditor less work to implement. However, our choice of C
+is based on the advantages that make it superior to contemporary languages for
+our use case: relatively low complexity of the language (compared to C++);
+availability of mature compilers, static and dynamic analysis tools;
+predictable performance; access to stable and battle-tested libraries; and
+future-proofness due to portability to older systems as well as new platforms.
+
+We believe creating a parallel implementation in other languages would provide
+advantages, especially with respect to avoiding ``the implementation is the
+specification''-style issues. However, given limited resources will not make
+this a priority.
+
+We disagree that all modern software development has embraced the idea that
+memory errors are to be handled in ways other than terminating or restarting
+the process. Many programming languages (Erlang, Java) hardly offer any other
+means of handling out-of-memory situations than to terminate the process. We
+also insist that Taler {\em does} handle out-of-memory as it does have code
+that terminates the process (we do {\em not} simply ignore the return value
+from {\tt malloc()} or other allocation functions!). We simply consider that
+terminating the process (which is run by a hypervisor that will restart the
+service) is the correct way to handle out-of-memory situations. We also have
+limits in place that should prevent attackers from causing large amounts of
+memory to be consumed, and also have code to automatically preemptively
+restart the process to guard against memory exhaustion from memory
+fragmentation. Finally, a common problem with abrupt termination may be
+corrupted files. However, the code mostly only reads from files and limits
+writing to the Postgres database. Hence, there is no possibility of corrupt
+files being left behind even in the case of abnormal termination.
+
+
+\section{More specs and documentation code}
+
+We agree with the recommendation that the documentation should be improved,
+and will try to improve it along the lines recommended by CodeBlau.
+
+\section{Protocol change: API for uniformly distributed seeds}
+
+We agree with the suggestion, have made the necessary changes, and both
+parties believe that the suggestion has been implemented.
+
+\section{Reduce code complexity}
+
+\subsection{Reduce global variables}
+
+While we do not disagree with the general goal to have few global variables,
+we also believe that there are cases where global variables make sense.
+
+We have already tried to minimize the scope of variables. The remaining few
+global variables are largely ``read-only'' configuration data. The report does
+not point out specific instances that would be particularly beneficial to
+eliminate. As we continue to work on the code, we will of course evaluate
+whether the removal of a particular global variable would make the code
+cleaner.
+
+Also, we want to point out that all global variables we introduce
+in the exchange are indicated with a prefix {\tt TEH\_} in the code, so they
+are easy to identify as such.
+
+\subsection{Callbacks, type pruning}
+
+We understand that higher order functions in C can be confusing, but this
+is also a common pattern to enable code re-use and asynchronous execution
+which is essential for network applications. We do not believe that we
+use callbacks {\em excessively}. Rewriting the code in another language
+may indeed make this part easier to understand, alas would have other
+disadvantages as pointed out previously.
+
+{\bf Update:} We introduced additional functions to replace
+variadic calls to functions that cannot be type-checked by
+the compiler (like libjansson's {\tt json\_pack()}) with
+type-safe versions (like the new {\tt GNUNET\_JSON\_PACK()}).
+
+
+\subsection{Initializing structs with memset}
+
+Using {\tt memset()} first prevents compiler (or valgrind) warnings about
+using uninitialized memory, possibly hiding bugs. We also do use struct
+initialization in many cases.
+
+The GNUnet-wrappers are generally designed to be ``safer'' or ``stricter''
+variants of the corresponding libc functions, and not merely ``the same''.
+Hence we do not believe that renaming {\tt GNUNET\_malloc} is indicated.
+
+The argument that {\tt memset()}ing first makes the code inherently more
+obvious also seems fallacious, as it would commonly result in dead stores,
+which can confuse developers and produce false-positive warnings from static
+analysis tools.
+
+\subsection{NULL pointer handling}
+
+The problem with the ``goto fail'' style error handling is that it rarely
+results in specific error handling where diagnostics are created that are
+specific to the error. Using this style of programming encourages developers
+to create simplistic error handling, which can result in inappropriate error
+handling logic and also makes it harder to attribute errors to the specific
+cause.
+
+However, we have no prohibition on using this style of error handling either:
+if it is appropriate, develpers should make a case-by-case decision as to how
+to best handle a specific error.
+
+We have made some first changes to how {\tt GNUNET\_free()} works in response
+to the report, and will discuss further changes with the GNUnet development
+team.
+
+\subsection{Hidden security assumptions}
+
+We disagree that the assumptions stated are ``hidden'', as (1) the Taler code
+has its own checks to warrant that the requirements of the {\tt
+ GNUNET\_malloc()} API are satisfied (so enforcement is not limited to the
+abstraction layer), and (2) the maximum allocation size limit is quite clearly
+specified in the GNUnet documentation. Also, the GNUnet-functions are not
+merely an abstraction layer for portability, but they provided extended
+semantics that we rely upon. So it is not like it is possible to swap this
+layer and expect anything to continue to work.
+
+When we use the libjansson library, it is understood that it does not use
+the GNUnet operations, and the code is careful about this distinction.
+
+\subsection{Get rid of boolean function arguments}
+
+We agree that this can make the code more readable, and have in some places
+already changed the code in this way.
+
+\section{Structural Recommendation}
+
+\subsection{Least privilege}
+
+It is wrong to say that GNU Taler has ``no work done'' on privilege separation.
+For example, the {\tt taler-exchange-dbinit} tool is the only tool that requires
+CREATE, ALTER and DROP rights on database tables, thus enusring that the ``main''
+process does not need these rights.
+
+We also already had the {\tt taler-exchange-keyup} tool responsible for
+initializing keys. In response to the audit, we already changed the GNUnet API
+to make sure that tools do not create keys as a side-effect of trying to read
+non-existent key files.
+
+{\bf Update:} We have now implemented full privilege separation for access to the online
+cryptographic signing keys. Details about the design are documented in the
+section ``Exchange crypto helper design'' at \url{https://docs.taler.net/} of
+Chapter 12.
+
+{\bf Update:} In doing so, we also added a new type of signing key, the
+``security module'' signing key. This is used by the newly separated ``security
+module`` processes to sign the public keys that they guard the private keys
+for. The security module signatures are verified by the new
+``taler-exchange-offline`` tool to ensure that even if the {\tt
+taler-exchange-httpd} process is compromised, the offline signature tool would
+refuse to sign new public keys that do not originate from the security
+module(s). The security module public keys can be given in the configuration,
+or are learned TOFU-style.
+
+
+\subsection{File system access}
+
+The auditor helpers actually only read from the file system, only the LaTeX
+invocation to compile the final report to PDF inherently needs write
+access. We do not predict that we will retool LaTeX. Also, the file system
+access is completely uncritical, as the auditor by design runs on a system
+that is separate from the production exchange system.
+
+Because that system will not have {\em any} crypto keys (not even the one of
+the auditor!), CodeBlau is wrong to assume that reading from or writing to the
+file system represents a security threat.
+
+We have started to better document the operational requirements on running the
+auditor.
+
+{\bf Update:} On the exchange side, we have now moved additional information
+from the file system into the database, in particular information about offline signatures
+(including key revocations) and wire fees. This simplifies the deployment and
+the interaction with offline key signing mechanism. The remaining disk accesses are for
+quite fundamental configuration data (which ports to bind to, configuration to
+access the database, etc.), and of course the program logic itself.
+
+{\bf Update:} We have also restructured the configuration such that only
+the {\tt taler-exchange-transfer} and {\tt taler-exchange-wirewatch} programs
+need to have access to the more sensitive bank account configuration data,
+and so that these processes can run as a separate user.
+
+
+\subsection{Avoid dlopen}
+
+Taler actually uses {\tt ltdlopen()} from GNU libtool, which provides
+compiler flags to convert the dynamic linkage into static linkage. For
+development, dynamic linkage has many advantages.
+
+We plan to test and document how to build GNU Taler with only static
+linkage, and will recommend this style of deployment for the Taler
+exchange for production.
+
+\subsection{Reduce reliance on PostgreSQL}
+
+CodeBlau's suggestion to use an append-only transaction logging service in
+addition to the PostgreSQL database is a reasonable suggestion for a
+production-grade deployment of GNU Taler, as it would allow partial disaster
+recovery even in the presence of an attacker that has gained write access to
+the exchange's database.
+
+We are currently still investigating whether the transaction logging should be
+implemented directly by the exchange service, or via the database's extensible
+replication mechanism. Any implementation of such an append-only logging
+mechanism must be carefully designed to ensure it does not negatively impact
+the exchange's availability and does not interfere with serializability of
+database transactions. As such we believe that transaction logging can only be
+provided on a best-effort basis. Fortunately, even a best-effort append-only
+transaction log would serve to limit the financial damage incurred by the
+exchange in an active database compromise scenario.
+
+{\bf Update:} We have tightened the installation instructions for the
+Taler exchange to guide users towards a more restricted Postgres setup,
+tightening which components of the Exchange need what level of access
+to the exchange database.
+
+
+
+\end{document}
diff --git a/doc/cbdc-es/cbdc-es.tex b/doc/cbdc-es/cbdc-es.tex
new file mode 100644
index 000000000..8c87c3e0d
--- /dev/null
+++ b/doc/cbdc-es/cbdc-es.tex
@@ -0,0 +1,1283 @@
+\documentclass[a4paper,10pt]{article} %tamaño de papel y letra ``base''
+\usepackage[utf8]{inputenc}
+\usepackage[T1]{fontenc}
+\usepackage[top=2cm,
+bottom=2cm,
+includefoot,
+left=3cm,
+right=2cm,
+footskip=1cm]{geometry}
+\usepackage{url}
+\IfFileExists{lmodern.sty}{\usepackage{lmodern}}{}
+\usepackage{graphicx}
+\usepackage{mathpazo}
+\usepackage{amsmath}
+\usepackage{mathptmx}
+\usepackage{color}
+\usepackage[utf8]{inputenc}
+\usepackage[T1]{fontenc}
+\usepackage[hidelinks]{hyperref}
+ %\usepackage{natbib,har2nat}
+\usepackage{natbib}
+\usepackage[spanish]{babel}
+\input{eshyphexh.tex}
+ %\renewcommand{\abstractname}{Resumen}
+ %\renewcommand{\refname}{Referencias}
+ % de estas cosas se ocupa \usepackage[spanish]{babel}
+
+
+\title{Cómo Emitir una Moneda Digital del Banco Central}
+\author{David Chaum\footnote{david@chaum.com} \\
+ xx Network \and
+ Christian Grothoff\footnote{christian.grothoff@bfh.ch} \\
+ BFH\footnote{Universidad de Ciencias Aplicadas de Berna}
+ \quad y Proyecto GNU \and
+ Thomas Moser\footnote{thomas.moser@snb.ch}\\
+ Banco Nacional de Suiza}
+\date{Esta versión: octubre 2021 \\
+ Primera versión: mayo 2020}
+
+
+
+\begin{document}
+
+\maketitle
+
+\begin{abstract}
+Con la aparición de Bitcoin y monedas estables propuestas recientemente
+por grandes empresas tecnológicas como Diem (antes Libra), los bancos
+centrales se enfrentan a la creciente competencia de particulares que
+ofrecen su propia alternativa digital al dinero en efectivo. No
+abordamos la cuestión normativa de si un un banco central debería o no
+emitir una moneda digital del banco central (Central Bank Digital
+Currency -- CBDC). Contribuimos en cambio al actual debate de
+investigación mostrando de qué manera un banco central podría hacerlo si
+así lo deseara. Proponemos un sistema basado en tokens sin tecnología de
+libro mayor distribuido, y mostramos que el efectivo electrónico ya
+implementado solo mediante software se puede mejorar para preservar la
+privacidad en las transacciones, cumplir con los requisitos
+reglamentarios de modo convincente y ofrecer un nivel de protección de
+resistencia cuántica contra los riesgos sistémicos que amenazan la
+privacidad. Ni la política monetaria ni la estabilidad financiera se
+verían materialmente afectadas porque una CBDC con este diseño
+replicaría el efectivo físico en lugar de los depósitos bancarios. \\
+JEL: E42, E51, E52, E58, G2
+\\
+Keywords: Monedas digitales, banco central, CBDC, firmas ciegas, monedas
+estables
+\end{abstract}
+
+\vspace{40pt}
+
+\section*{Agradecimientos}
+Agradecemos a Michael Barczay, Roman Baumann, Morten Bech, Nicolas Cuche,
+Florian Dold, Andreas Fuster, Stefan Kügel, Benjamin Müller, Dirk Niepelt,
+Oliver Sigrist, Richard Stallman, Andreas Wehrli, y tres colaboradores
+anónimos por sus comentarios y sugerencias. Las ideas, opiniones,
+investigaciones y conclusiones o recomendaciones expresadas en este
+documento pertenecen estrictamente a los autores. No reflejan
+necesariamente los puntos de vista del Banco Nacional de Suiza (BNS). El
+BNS no asume ninguna responsabilidad por errores u omisiones ni por la
+exactitud de la información contenida en este documento.
+
+Traducción: Javier Sepulveda \& Dora Scilipoti
+
+
+\newpage
+
+%\tableofcontents
+
+\section{Introducción}\label{1.-introducciuxf3n}
+
+Desde la aparición de los ordenadores personales en los años ochenta, y
+especialmente desde que en 1991 la National Science Foundation quitara
+las restricciones al uso de Internet para propósitos comerciales, se ha
+buscado crear dinero digital para realizar pagos en línea. La primera
+propuesta la realizó~\citet{Chaum1983}. A pesar de que tales métodos fueron
+implementados, no prosperaron. Fueron en cambio los sistemas con tarjeta
+de crédito los que se convirtieron en el método dominante para pagos en
+línea. La propuesta de~\citet{Nakamoto} para un sistema P2P de dinero
+digital y el posterior lanzamiento exitoso de Bitcoin desataron una
+nueva era de investigación sobre el tema y desarrollo de dinero digital.
+CoinMarketCap enumera más de 5.000 criptomonedas. Recientemente los
+bancos centrales han empezado a considerar, o al menos estudiar, la
+emisión de monedas digitales~\cite[véase][]{AuerBoehme,AuerCornelli,Boar,Kiff,Mancini-Griffoli}.
+
+Actualmente los bancos centrales emiten dos tipos de dinero: (i)
+reservas en forma de cuentas de liquidación en los bancos centrales para
+determinados participantes del mercado financiero y (ii) moneda en forma
+de billetes disponibles para el público. En consecuencia, la
+bibliografía sobre la moneda digital del banco central (CBDC) distingue
+entre (a) venta de CBDC al por mayor, con acceso limitado, y (b) venta
+de CBDC al por menor, accesible al público \cite[véase, p. ej.][]{Bech}.
+Una CBDC al por mayor sería menos disruptiva para el sistema
+actual debido a que los bancos y los participantes seleccionados del
+mercado financiero ya tienen acceso a dinero digital del banco en forma
+de cuentas del banco central, que utilizan para liquidar pagos
+interbancarios. La cuestión aquí es si la tokenización del dinero de un
+banco central y la tecnología de libro mayor distribuido (Distributed
+Ledger Technology - DLT) ofrecen beneficios netos en comparación con los
+sistemas de liquidación bruta en tiempo real (Real-Time Gross
+Settlement - RTGS). Hasta el momento, la conclusión es que no es así, al
+menos cuando se trata de pagos interbancarios nacionales~\cite[véase][]{Chapman}.
+
+Una CBDC al por menor, que sería una nueva forma de dinero del banco
+central a disposición del público, podría ser más disruptiva para el
+sistema actual, dependiendo de su diseño. Cuanto más compita una CBDC de
+este tipo con los depósitos bancarios comerciales, mayor será la amenaza
+para la financiación bancaria, con un posible impacto adverso en el
+crédito bancario y la actividad económica~\cite[véase][]{Agur}. Sin
+embargo, una CBDC al por menor podría también tener
+beneficios~\cite[véase][]{Bordo,Berentsen,Bindseil,Niepelt,Riksbank,BoE}.
+Poner a disposición de
+todos dinero electrónico del banco central sin riesgo de contrapartida
+podría mejorar la estabilidad y la resistencia del sistema de pago al
+por menor. También podría proporcionar una infraestructura de pago
+neutral para promover la competencia, la eficiencia y la innovación. En
+general, es probable que los costos y beneficios de una CBDC al por
+menor difieran de un país a otro. Para conocer la opinión del Banco
+Nacional de Suiza, que no tiene planes de emitir una CBDC al por
+menor~\cite[véase][]{Jordan}.
+
+El presente documento se centra en una CBDC al por menor, pero no abordamos la
+cuestión de si un banco central \emph{debería o no} emitir una moneda
+CBDC. Nos centramos en cambio en el diseño potencial de una
+CBCD. Recientemente ha habido un creciente interés en el diseño de monedas
+CBCD (\cite[véase p. ej.][]{Allen,BoE}). El diseño que proponemos difiere
+significativamente de otras propuestas. Nuestro sistema se basa en la
+tecnología eCash descrita por Chaum~\cite{Chaum1983,Chaum1990},
+mejorándola. En particular, proponemos un sistema para CBCD basado en tokens y
+solo mediante software, sin blockchain para la DLT. La DLT es un diseño
+interesante en ausencia de un actor principal o si las entidades que
+interactúan no concuerdan en nombrar un actor central de confianza. Sin
+embargo, este no es el caso de una CBCD al por menor emitida por un
+\emph{banco central}. Distribuir el libro mayor del banco central con una
+blockchain solo aumenta los costes de transacción, no proporciona beneficios
+tangibles en una implementación por parte de un banco central. Utilizar la DLT
+para emitir dinero digital puede ser útil si no hay un banco central para
+empezar (p. ej. el proyecto Sovereign de las Islas Marshall) o si la
+intención explícita es prescindir de un banco central
+(p. ej. Bitcoin).\footnote{Puede haber buenos casos de uso para la DLT en el
+caso de infraestructura de mercado financiero, tal como los intercambios
+digitales, donde surge la cuestión de como obtener dinero del banco central en
+la DLT a efectos de liquidación. Sin embargo en esas situaciones, los
+beneficios potenciales de la DLT, por ejemplo menos costes o reconciliación
+automática, no surgen de una emisión descentralizada del dinero del banco
+central.}
+
+La CBCD basada en tokens que se propone aquí permite también la
+preservación de una cualidad clave del dinero físico: la privacidad en
+la transacción. Usualmente se argumenta que las protecciones
+criptográficas para la privacidad exigen tantos recursos computacionales
+que su utilización en dispositivos móviles no es factible~\cite[véase][]{Allen}.
+Si bien esto puede ser cierto en el contexto de la DLT,
+donde la rastreabilidad pública de las transacciones es necesaria para
+prevenir el doble gasto~\cite{Narayanan}, no es cierto para el
+protocolo de firma ciega de tipo Chaum con un banco central que se
+propone en el presente documento. Nuestra CBDC, basada en firmas ciegas
+y arquitectura de dos niveles, garantiza una perfecta privacidad de
+resistencia cuántica en las transacciones, al mismo tiempo que
+proporciona protecciones sociales tales como impedir el lavado de dinero
+(Anti-Money Laundering - AML) y financiar la lucha contra el terrorismo
+(Counter Terrorism Financing -- CFT), protecciones que de hecho tienen
+mayor fuerza que con los billetes.
+
+La privacidad en las transacciones es importante por tres razones.
+Primero, porque protege a los usuarios frente al escrutinio y el abuso
+de vigilancia gubernamental. Los programas de vigilancia masiva son
+problemáticos incluso si las personas creen que no tienen nada que
+esconder, simplemente por la posibilidad de error y abuso,
+particularmente si los programas carecen de transparencia e
+imputabilidad~\cite[véase][]{Solove}. Segundo, porque la privacidad en las
+transacciones protege a los usuarios frente a la explotación de datos por parte
+de los proveedores de servicios de pago.
+Tercero, porque protege a los usuarios frente a la contraparte en la
+transacción, descartando la posibilidad de un posterior comportamiento
+oportunista, o frente a riesgos de seguridad debido a fallos o
+negligencia en la protección de los datos del cliente~\cite[véase][]{Kahn2005}.
+
+Este documento está estructurado como sigue: en la sección 2 explicamos
+la diferencia entre el dinero del banco central y otro dinero. En la
+sección 3 analizamos los diseños de CBDC comunes y simplistas, antes
+de proponer nuestro diseño en la sección 4. Luego comentamos
+consideraciones políticas y normativas (5) y trabajos relacionados (6);
+en fin, concluimos (7).
+
+
+\section{¿Qué es el dinero del banco central?}
+ \label{2.-quuxe9-es-el-dinero-del-banco-central}
+
+El dinero es un activo que puede ser usado para comprar bienes y
+servicios. Para ser considerado dinero, este activo debe ser aceptado
+por otras entidades distintas del emisor. Este es el motivo por el que
+los vales, por ejemplo, no se consideran dinero. El dinero genuino tiene
+que ser aceptado \emph{comúnmente} como medio de intercambio. Si bien el
+dinero tiene otras funciones, por ejemplo como unidad de cuenta y
+depósito de valor, la característica que lo distingue es su función como
+medio de intercambio. Normalmente, la unidad de cuenta (p. ej. cómo se
+cotizan los precios y cómo se registran las deudas) coincide con el
+medio de intercambio por razones de conveniencia. La separación puede
+ocurrir, sin embargo, si el valor del medio de intercambio carece de
+estabilidad en relación a los bienes y servicios
+comercializados.\footnote{Esto puede ocurrir espontáneamente en un entorno
+de alta-inflación, p. ej. cuando los precios se fijan en USD pero los pagos
+se realizan en divisa local. Lo mismo es cierto para los pagos en Bitcoin,
+donde los precios usualmente se fijan en USA u otras divisas locales debido a
+la alta volatilidad de Bitcoin. Una separación también puede ocurrir por el
+diseño, p. ej. en la Unidad de Fomento (UF) de Chile o la Special Drawing Right
+(SDR) del fondo monetario internacional (IMF). Sin embargo, también entonces el
+propósito es tener una unidad de cuenta más estable.} El dinero debe también ser
+un depósito de valor para poder actuar como medio de intercambio, porque
+debe preservar su poder de compra desde el momento en que se recibe
+hasta el momento en que se gasta. Sin embargo, varios otros activos
+sirven como depósito de valor, como por ejemplo acciones, bonos, metales
+preciosos e inmuebles. Por tanto, la característica como depósito de
+valor no es distintiva del dinero.
+
+En la economía moderna, el público usa dos tipos diferentes de dinero:
+(a) dinero estatal y (b) dinero privado. El dinero estatal lo emite
+típicamente un banco central, que actúa como agente del Estado. El
+dinero del banco central está disponible para determinadas instituciones
+financieras en forma de depósitos en el banco central (reservas) y para
+el público en forma de moneda (billetes y monedas), también llamado
+``efectivo''. En una economía moderna con dinero fiduciario, tal dinero
+no tiene valor intrínseco. Legalmente es una obligación del banco
+central, aunque no es canjeable.
+
+En la mayoría de los países, el dinero del banco central se define como
+moneda de curso legal, lo cual significa que debe ser aceptado como pago
+de una deuda monetaria, incluyendo impuestos y multas legales. Si bien
+esto garantiza que el dinero del banco central tenga algún valor, el
+estatus de moneda de curso legal es insuficiente para que el dinero del
+banco central mantenga un valor estable. Más bien, es la política
+monetaria de los bancos centrales la que mantiene el valor del dinero.
+Mantener la estabilidad de los precios, es decir, un valor estable del
+dinero en relación con el valor de los bienes y servicios
+comercializados, es una de las principales responsabilidades de los
+bancos centrales.
+
+En una economía moderna, la mayoría de los pagos se hacen con dinero
+privado emitido por bancos comerciales. Tal dinero se compone de
+depósitos a la vista que la gente tiene en los bancos comerciales. A
+estos depósitos bancarios se puede acceder con cheques, tarjetas de
+débito, tarjetas de crédito, u otros medios para transferir dinero. Son
+una obligación del respectivo banco comercial. Una característica
+fundamental de los depósitos bancarios es que los bancos comerciales
+garantizan la convertibilidad, bajo demanda, en dinero del banco central
+a un precio fijo, es decir, a la par. Los depositantes pueden retirar
+sus fondos en efectivo o transferirlos a una tasa fija de 1:1. Los
+bancos comerciales mantienen estable el valor de su dinero vinculándolo
+al dinero del banco central.
+
+No obstante, en un sistema de reserva fraccionado, un banco comercial
+-- incluso siendo solvente -- puede no contar con la liquidez necesaria
+para cumplir su promesa de convertir los depósitos bancarios en dinero
+del banco central (p. ej. en caso de una caída bancaria) de manera tal
+que los clientes no puedan retirar su dinero. Un banco también puede
+llegar a ser insolvente e ir a la bancarrota, y como resultado los
+clientes pueden perder su dinero. Así, los bancos comerciales están
+regulados para mitigar estos riesgos.
+
+Una diferencia significativa entre el dinero de un banco central y el
+dinero emitido privadamente por un banco comercial es, por lo tanto, que
+este último conlleva un riesgo para la contraparte. Un banco central
+puede siempre cumplir con sus obligaciones usando su propio dinero no
+reembolsable. El dinero del banco central es el único activo monetario
+de una economía nacional sin riesgo crediticio o de liquidez. Por lo
+tanto, es el activo que típicamente se prefiere para los pagos en las
+infraestructuras del mercado financiero (véase p. ej. CPMI-IOSCO
+\emph{Principles for Financial Market Infrastructures}, 2012). Otra
+diferencia es que el dinero del banco central afianza el sistema
+monetario nacional al proporcionar una referencia de valor con la que el
+dinero de los bancos comerciales mantiene una convertibilidad a la par.
+
+Aparte de los bancos comerciales, otra entidades privadas ocasionalmente
+intentan emitir dinero, las criptomonedas son solo el intento más
+reciente. Pero a diferencia de los depósitos bancarios, tal dinero no es
+comúnmente aceptado como medio de intercambio. Esto también sucede con
+Bitcoin, la criptomoneda más aceptada. Un impedimento a su utilidad como
+medio de intercambio es la alta volatilidad de su valor. Una respuesta
+reciente a este problema fue la aparición de las llamadas monedas
+estables. Las monedas estables generalmente intentan estabilizar su
+valor en una de las dos maneras siguientes: o bien imitando a los bancos
+centrales (monedas estables algorítmicas) o bien imitando a los bancos
+comerciales o a los medios de inversión (monedas estables con respaldo
+de activos).\footnote{Para más detalles sobre la taxonomía y descripción
+de las monedas estables véase~\citet{Bullmann}.}
+
+Las ``monedas estables algorítmicas'' dependen de algoritmos para
+regular su suministro. En otras palabras, intentan alcanzar la
+estabilidad de su precio con sus propias ``políticas monetarias
+algorítmicas''. Hay ejemplos de tales monedas estables (p. ej. Nubits),
+pero hasta ahora ninguna ha estabilizado su valor por largo tiempo.
+
+Las monedas estables ``respaldadas con activos'' difieren en función del
+tipo de activos que usan y de los derechos legales que adquieren los
+titulares de monedas estables. Los tipos de activos que típicamente se
+usan son: dinero (reservas del banco central, billetes o depósitos en
+bancos comerciales), productos básicos (p. ej. oro), valores y a veces
+otras criptomonedas. Cuán bien tal esquema estabilice el valor de las
+monedas en relación al activo o los activos subyacentes depende de
+manera crucial de los derechos legales que adquieran los titulares de
+las monedas estables. Si una moneda estable es canjeable a un precio
+fijo (p. ej. 1 moneda = 1 USD, o 1 moneda = 1 onza de oro), tal
+estabilidad teóricamente se conseguirá.\footnote{Si también estabilice o
+no el valor de las monedas estables en relación con los bienes y
+servicios negociados depende de la estabilidad del valor del respectivo
+activo en relación con el valor de los bienes y servicios.} Lo que el esquema
+esencialmente hace es replicar a los bancos comerciales garantizando la
+convertibilidad al activo subyacente a la vista. Sin embargo, a
+diferencia de los depósitos bancarios, que típicamente están solo
+parcialmente respaldados por las reservas monetarias del banco central,
+las monedas estables generalmente están respaldadas completamente por
+las reservas del activo subyacente para evitar el riesgo de liquidez,
+principalmente porque carecen de beneficios públicos tales como el
+soporte de seguros de depósito y prestamistas de última instancia, que
+se aplican en cambio a los bancos regulados.
+
+Las monedas estables respaldadas con dinero se llaman también monedas
+estables fiduciarias. Sin embargo, mantener el 100\% de garantía en
+dinero (billetes o depósitos bancarios) no es muy rentable. En
+consecuencia, los proveedores de monedas estables tienen un incentivo
+para economizar su tenencia de activos y trasladarse hacia un sistema de
+reserva fraccionado, tal como lo hicieron los bancos comerciales.\footnote
+{La incertidumbre sobre si un moneda estable está
+totalmente garantizada puede ser una de las razones por las que una
+moneda estable puede negociarse por debajo de la par en el mercado
+secundario~\cite[véase][]{Lyons}. Este fue
+también históricamente el caso con los billetes cuando eran emitidos
+por los bancos comerciales. Tales billetes solían negociarse con
+diversos descuentos en el mercado secundario antes de que la emisión
+de billetes fuera nacionalizada y transferida al monopolio de los
+bancos centrales.} Esto implica que reducen su tenencia de activos de
+bajo rendimiento al mínimo que se considere necesario para satisfacer el
+requisito de convertibilidad. Añadiendo en cambio activos líquidos de
+alto rendimiento tales como bonos del Estado. Esto mejora la
+rentabilidad pero también incrementa el nivel de riesgo.
+
+Sin embargo, incluso si una moneda estable está garantizada al 100\% por
+un depósito en un banco comercial, sigue expuesta a los riesgos de
+crédito y liquidez del banco subyacente. Este riesgo se puede eliminar
+si los depósitos se mantienen en el banco central para que la moneda
+estable esté respaldada por las reservas del banco central. Tales
+monedas estables han sido llamadas ``CBDC sintéticas''~\cite{Adrian}.
+Es importante señalar, sin embargo, que tales
+monedas estables no son dinero del banco central y por lo tanto no son
+CBDC, ya que no constituyen obligaciones del banco central y, por lo
+tanto, siguen expuestas al riesgo de contraparte, es decir, el riesgo de
+que el emisor de la moneda estable se declare en quiebra.
+
+Si una moneda estable no es canjeable a un precio fijo, su estabilidad
+no está garantizada por el activo subyacente. Si la moneda estable a
+pesar de esto representa una participación en la propiedad del activo
+subyacente, el esquema se asemeja a un fondo de inversión fijo o a un
+fondo cotizado en bolsa (Exchange-Traded Fund - ETF), y se aplican los
+correspondientes riesgos. El valor de la moneda dependerá del valor neto
+de los activos del fondo, pero su valor real puede desviarse. Si hay
+participantes autorizados que puedan crear y canjear monedas estables y
+así actuar como arbitristas, como en el caso de los ETF y como estaba
+previsto para Diem~\cite{Libra}, es probable que la
+desviación sea mínima.
+
+En general, las monedas estables tiene una mayor probabilidad de llegar
+a convertirse en dinero que las criptomonedas, especialmente si se
+regulan adecuadamente. Sin embargo, la disponibilidad de CBDC limitaría
+significativamente su utilidad.
+
+\section{Diseños simplistas de CBDC} \label{3.-diseuxf1os-simplistas-de-cbdc}
+
+Como se ha señalado, una CBDC sería una obligación del banco central.
+Dos posibles diseños que se analizan en la literatura son: (a) una CBDC
+basada en cuentas y (b) una CBDC basada en tokens (o basada en valor).
+Estos diseños corresponden a los dos tipos existentes de dinero de un
+banco central y sus correspondientes sistemas de pago (Kahn \& Roberds
+2008): las reservas de un banco central (en un sistema basado en
+cuentas) y billetes (en un sistema basado en tokens). Un pago se produce
+si un activo monetario se transfiere de un pagador a un beneficiario. En
+un sistema basado en cuentas, una transferencia se produce cobrándole a
+la cuenta del pagador y transfiriendo el crédito a la cuenta del
+beneficiario. En un sistema basado en tokens, la transferencia se
+produce transfiriendo el valor en sí o el token, es decir, un objeto que
+representa el activo monetario. El mejor ejemplo de un token es el
+efectivo -- monedas o billetes. Pagar con efectivo significa entregar
+una moneda o un billete. No es necesario registrar la transferencia, la
+posesión del token es suficiente. Por lo tanto, las partes no están
+obligadas a revelar sus identidades en ningún momento durante la
+transacción, ambas pueden permanecer anónimas. De todas maneras, el
+beneficiario tiene que poder verificar la autenticidad del token. Esta
+es la razón por la que los bancos centrales invierten mucho en elementos
+de seguridad para los billetes.
+
+Ha habido sugerencias de que la distinción entre los sistemas basados en
+cuentas y los sistemas basados en tokens no es aplicable a las monedas
+digitales~\cite{Garratt}. Nosotros tenemos una opinión diferente
+porque creemos que hay una diferencia significativa. La distinción
+fundamental es la información contenida en el activo. En un sistema
+basado en cuentas, los activos (las cuentas) se asocian con los
+historiales de las transacciones, que incluyen todas las operaciones de
+crédito y débito de las cuentas. En un sistema basado en tokens, los
+activos (tokens) incluyen información acerca de su valor y de la entidad
+que emitió el token. Por tanto, la única posibilidad de lograr la
+propiedad de privacidad de la transacción como la que se obtiene con el
+dinero efectivo reside en los sistemas basados en tokens.\footnote
+{Si bien el término ``Bitcoin'' sugiere el uso de tokens, Bitcoin es un
+sistema basado en cuentas. La única diferencia entre un sistema
+tradicional basado en cuentas y una blockchain es que las cuentas no
+se guardan en una base de datos central, sino en una base de datos
+descentralizada del tipo ``solo por anexión''.}
+
+\subsection{CBDC basada en cuentas}\label{cbdc-basada-en-cuentas}
+
+La forma más simple de lanzar una CBDC sería permitir que el público
+tenga cuentas de depósito en el banco central. Esto implica que el banco
+central seria responsable de llevar a cabo verificaciones para conocer a
+sus clientes (Know-Your-Customer - KYC) y asegurar el cumplimiento del
+AML y CFT. Esto incluiría no solo realizar el proceso inicial del KYC,
+sino también autentificar a los clientes para las transacciones
+bancarias, gestionar el fraude y lidiar con los falsos positivos y las
+autenticaciones de los falsos negativos. Dada la limitada presencia
+física de bancos centrales en la sociedad, y el hecho de que la
+autenticación del ciudadano es algo que probablemente en la actualidad
+los bancos no estén preparados para hacer a gran escala, cualquier CBDC
+basada en cuentas requeriría que el banco central delegara estas
+verificaciones. Todo el servicio y mantenimiento de tales cuentas podría
+asignarse a proveedores externos~\cite{Bindseil}, o la legislación
+podría obligar a los bancos comerciales a abrir cuentas bancarias en el
+banco central para sus clientes~\cite{Berentsen}.
+
+Tal CBDC basada en cuentas daría potencialmente a un banco central mucha
+información. Una posible preocupación podría ser que esto permitiera a
+los gobiernos realizar fácilmente vigilancia masiva e imponer sanciones
+a los titulares de cuentas individuales. Su naturaleza centralizada hace
+que tales intervenciones sean económicas y fáciles de aplicar contra
+individuos o grupos. Incluso en las democracias, hay muchos ejemplos de
+abusos de vigilancia dirigidos a críticos y opositores políticos. Se
+podría argumentar que los bancos centrales independientes puedan
+salvaguardar tal información del escrutinio del gobierno y el abuso
+político, pero esto solo abriría una nueva vía para la presión política,
+amenazando la independencia del banco central. Además, la base de datos
+central sería un objetivo importante para los atacantes: incluso el
+acceso de solo lectura a partes de la base de datos podría crear riesgos
+significativos para las personas cuyos datos fueran expuestos.
+
+Proveyendo cuentas bancarias al público, un banco central estaría
+también en competición directa con los bancos comerciales. Esta
+competición implicaría dos riesgos. Primero, podría amenazar la base de
+depósitos de los bancos y, en el extremo, desintermediar el sector
+bancario. Esto podría afectar de manera adversa la disponibilidad de
+crédito para el sector privado y, como resultado, la actividad
+económica~\cite{Agur}. La desintermediación de los bancos también podría
+conducir a la centralización del proceso de asignación de crédito dentro
+del banco central, lo que afectaría negativamente la productividad y el
+crecimiento económico. En segundo lugar, permitir que la gente traslade
+sus depósitos al refugio seguro de un banco central podría acelerar las
+caídas bancarias durante crisis financieras.
+
+Existen sin embargo argumentos contrarios. \citet{Brunnermeier} argumentan
+que la transferencia de fondos desde un
+depósito hacia una cuenta de CBDC conduciría a una sustitución automática de
+la financiación de depósitos por la financiación del banco central,
+simplemente haciendo explicita la garantía implícita del banco central como
+prestamista de última instancia. \citet{Berentsen}
+sostienen que la competencia de los bancos centrales podría incluso tener un
+efecto disciplinario sobre los bancos comerciales y, por lo tanto, incrementar
+la estabilidad del sistema financiero, ya que los bancos comerciales tendrían
+que hacer sus modelos de negocio más seguros para evitar las caídas bancarias.
+
+También hay propuestas para mitigar el riesgo de la desintermediación
+que tienen como objetivo limitar o desincentivar el uso de CBDC como
+depósito de valor. Una propuesta es limitar la cantidad de CBDC que se
+puede poseer. Una segunda propuesta es aplicar una tasa de interés
+ajustable a las cuentas de CBDC, de manera que la remuneración esté
+siempre lo bastante por debajo de la remuneración de las cuentas de los
+bancos comerciales (posiblemente incluyendo un rendimiento negativo)
+para hacer que las CBDC resulten menos atractivas como depósitos de
+valor~\cite{Kumhof,Bindseil}. Además, para disuadir las
+caídas bancarias, \citet{Kumhof} sugieren que las CBDC no
+deberían ser emitidas contra depósitos bancarios, sino solo contra
+valores tales como bonos del Estado. En general, una CBDC basada en
+cuentas requeriría un análisis más profundo de estas cuestiones.
+
+
+\subsection{CBDC basada en tokens y dependiente del hardware}
+\label{cbdc-basada-en-tokens-y-dependiente-del-hardware}
+
+Un banco central podría también emitir tokens electrónicos en lugar de
+cuentas. Técnicamente esto requiere de un sistema para asegurar que los tokens
+electrónicos no se puedan copiar fácilmente. Las funciones físicamente
+imposibles de clonar~\cite[véase][]{Katzenbeisser} y las zonas seguras en
+el hardware~\cite[véase][]{Alves,Pinto} son dos tecnologías potenciales para
+la prevención de la copia digital. Las funciones físicas imposibles de clonar,
+sin embargo, no se pueden intercambiar a través de Internet (eliminando así el
+uso principal de las CBDC), y anteriores funciones de seguridad en el hardware
+para la prevención de copias se han visto comprometidas
+repetidamente~\cite[véase p. ej.][]{Wojtczuk,Johnston,Lapid}.
+
+Una ventaja fundamental de las CBDC basadas en tokens sobre las basadas
+en cuentas del banco central es que los sistemas basados en tokens
+funcionarían sin conexión, es decir, los usuarios podrían intercambiar
+tokens (peer-to-peer) sin involucrar al banco central, lo que protegería
+la privacidad y la libertad de las personas. Sin embargo, la
+desintermediación que se produce cuando los usuarios pueden intercambiar
+tokens electrónicos sin los bancos como intermediarios que realizan los
+controles KYC, AML y CFT dificultarían la limitación de los abusos por
+parte de delincuentes.
+
+Las tarjetas SIM son actualmente las candidatas más extensivamente
+disponibles para un sistema de pago seguro basado en hardware, pero
+estas también conllevan riesgos. La experiencia~\cite[véase p. ej.][]{Soukup,Garcia,Kasper,CCC} sugiere
+que cualquier dispositivo económicamente producible que almacene tokens
+con un valor monetario en posesión de una persona, y que permita
+transacciones sin conexión -- y por tanto el robo de la información que
+contiene -- será el objetivo de ataques de falsificación exitosos tan
+pronto como el valor económico del ataque fuera los suficientemente
+elevado. Tales ataques incluyen usuarios que atacan su propio
+hardware~\cite[véase también]{Allen}. Los sistemas de pago con tarjeta que
+se han desplegado previamente dependen de la resistencia a la
+manipulación en combinación con la detección del fraude para limitar el
+impacto de una situación de peligro. Sin embargo, la detección del
+fraude requiere la habilidad de identificar a los pagadores y seguir la
+pista de los clientes, lo cual no es compatible con la privacidad de la
+transacción.
+
+\section{Diseño de CBDC basado en tokens para salvaguardar la
+privacidad}
+\label{4.-diseuxf1o-de-cbdc-basado-en-tokens-para-salvaguardar-la-privacidad}
+
+La CBDC que se propone aquí es de tipo ``solo software'', simplemente una
+aplicación para teléfonos inteligentes que no requiere ningún hardware
+adicional por parte de los usuarios. La CBDC se basa en eCash y GNU
+Taler. Taler es parte del Proyecto GNU, cuyo fundador, Richard Stallman, acuñó
+el término \emph{Software Libre}, actualmente denominado \emph{Software Libre
+y de Código Abierto} (Free/Libre Open Source Software -- FLOSS).\footnote{Para
+más información sobre GNU, véase \url{https://www.gnu.org} y
+\citet{Stallman}. GNU Taler se publica gratuitamente bajo la Licencia Pública
+General Affero del Proyecto GNU. Otros programas del Proyecto GNU populares
+entre los economistas son «R» y ``GNU Regression, Econometrics and Time-series
+Library'' (GRETL). Un análisis de los beneficios del FLOSS en comparación con
+el software privativo en el campo de la investigación puede consultarse
+en~\citet{Baiocchi}, \citet{Yalta2008} y \citet{Yalta2010}. Sobre el
+licenciamiento de código abierto véase \citet{Lerner}.} Un programa se
+considera ``Software Libre'' si la licencia otorga a los usuarios cuatro
+libertades esenciales: la libertad de ejecutar el programa como deseen, la
+libertad de estudiar el programa y modificarlo, la libertad de redistribuir
+copias del programa y la libertad de distribuir copias de las versiones
+modificadas del programa. El software libre no tiene por qué ser no
+comercial: proporcionar soporte técnico para software es un modelo de negocio
+estándar para el FLOSS.
+
+Dado el gran número de partes interesadas involucradas en una CBDC al
+por menor (el banco central, el sector financiero, comerciantes y
+clientes) y la importancia crítica de la infraestructura, una CBDC al
+por menor debe basarse en el FLOSS. Imponer una solución propietaria que
+requiera la dependencia de un proveedor en particular sería
+probablemente un obstáculo para la adopción desde el principio. Con el
+FLOSS, todas las partes interesadas tienen acceso a cada detalle de la
+solución y el derecho de adaptar el software a sus necesidades. Esto
+conduce a una integración más fácil y una mejor interoperabilidad y
+competencia entre proveedores.\footnote{Sin embargo, puede haber otros
+roles para hardware privado. Por ejemplo, proteger los depósitos de
+claves y ciertas funciones de auditoría, en la medida en que tal
+seguridad pueda demostrarse solo como aditiva, puede ser un área donde
+el hardware dedicado evaluado por solo un número limitado de expertos
+podría tener ventajas.} Además, permite que el banco central cumpla
+con los requisitos de transparencia y responsabilidad. Los beneficios
+del FLOSS para la seguridad son también ampliamente reconocidos. La
+disponibilidad del código fuente y el derecho a modificarlo facilitan la
+detección de fallos y su rápida solución.\footnote{Por ejemplo, un
+boletín de seguridad cibernética emitido por la Agencia de Seguridad
+Nacional de EE. UU. en abril de 2020 insta a los usuarios a priorizar
+el software de código abierto en la selección y el uso de servicios de
+colaboración para la comunicación por Internet: ``El desarrollo de
+código abierto puede proporcionar confiabilidad de que el código está
+escrito para asegurar las mejores prácticas de programación y no es
+probable que introduzca vulnerabilidades o debilidades que puedan
+poner en riesgo a los usuarios y los datos '' (U/OO/134598-20).}
+
+En esta nuestra arquitectura que proponemos todas las interacciones del
+consumidor y el comerciante son con bancos comerciales. Sin embargo, la
+creación de dinero y la base de datos las proporcionan exclusivamente el
+banco central. Los bancos comerciales autentican a los clientes cuando
+retiran CBDC y a los comerciantes/beneficiarios cuando reciben CBDC,
+pero cuando gastan CBDC, los clientes/pagadores solo tienen que
+autorizar sus transacciones y no necesitan identificarse. Esto hace que
+los pagos resulten más baratos, fáciles y rápidos, y evita una fácil
+interferencia con la privacidad~\cite{Dold}. Además, autenticar a los
+clientes cuando retiran CBDC y a los comerciantes/beneficiarios cuando
+reciben CBDC garantiza el cumplimiento del KYC, AML y CFT.
+
+La CBDC que se propone en el presente documento es un auténtico
+instrumento digital al portador porque cuando el usuario retira una suma
+de dinero en forma de número, el número es ``cegado'' u ocultado por el
+teléfono inteligente con un cifrado especial. En el sistema real, una
+moneda es un par de claves pública / privada, y la clave privada solo la
+conoce el propietario de la moneda.\footnote{En Bitcoin, que es un
+sistema basado en cuentas, el par de claves es una cuenta, siendo la
+clave pública la ``dirección'' de la cuenta y por tanto un tipo de
+``identidad'', incluso si se trata de un pseudónimo.} La moneda deriva
+su valor financiero de la firma del banco central en la clave pública de
+la moneda. El banco central hace la firma con su clave privada y dispone
+de múltiples pares de claves de denominación para la firma ciega de
+monedas de diferentes valores. Un comerciante puede utilizar la
+correspondiente ``clave pública'' del banco central para verificar la
+firma. Sin embargo, para asegurarse de que la moneda no haya sido
+copiada y ya canjeada por otro beneficiario (es decir, que no se haya
+``gastado dos veces''), el comerciante debe depositar la moneda para que
+el banco central pueda comparar la moneda con un archivo de monedas
+canjeadas. Debido a que ni el banco comercial ni el banco central ven el
+número de la moneda durante el retiro, más tarde, cuando el comerciante
+deposita la moneda, se desconoce qué usuario la retiró. El cegamiento y
+la privacidad resultante son los que hacen de este tipo de CBDC un
+verdadero instrumento digital al portador.
+
+En el análisis que sigue proporcionamos una introducción de alto nivel a
+la tecnología y demostramos cómo se puede integrar con el sistema
+bancario existente para crear una CBDC. \citet{Dold} describe detalles
+adicionales.
+
+\subsection{Componentes fundamentales}\label{componentes-fundamentales}
+
+A continuación describimos los principales componentes del protocolo,
+incluido el trasfondo matemático para una posible instanciación de las
+primitivas criptográficas utilizadas, para ilustrar cómo podría
+funcionar una implementación. Observamos que existen diseños matemáticos
+alternativos y equivalentes para cada componente, y simplemente
+presentamos los diseños seguros más sencillos de los que tenemos
+conocimiento.
+
+\emph{Firmas digitales.} La idea básica de las firmas digitales en un esquema
+de firma con clave pública es que el propietario de una clave privada es el
+único que puede firmar un mensaje, mientras que la clave pública permite a
+cualquiera verificar la validez de la firma.\footnote{La criptografía de clave
+pública fue introducida por~\citet{Diffie}, y la primera implementación de
+firmas digitales fue introducida por~\citet{Rivest}.} El resultado de la
+función de verificación es la declaración binaria ``verdadero'' o ``falso''. Si el
+mensaje está firmado con la clave privada que pertenece a la clave pública de
+verificación, el resultado es verdadero, de lo contrario es falso. En nuestra
+propuesta, el mensaje es una ``moneda'' o ``billete'' con un número de serie, y la
+firma del banco central confirma su validez. Si bien GNU Taler usa por defecto
+firmas EdDSA modernas~\cite[véase][]{Bernstein2012}, presentamos un esquema de
+firma criptográfica simple basado en el bien estudiado sistema criptográfico
+RSA~\cite{Rivest}.\footnote{Para un análisis de la larga historia del
+criptosistema RSA y un estudio de los ataques al criptosistema RSA,
+consulte~\citet{Boneh}.} Sin embargo, en principio se puede utilizar cualquier
+esquema de firma criptográfica (DSA, ECDSA, EdDSA, RSA, etc.).
+
+Para generar las claves RSA, el firmante elige primero dos grandes e
+independientes números primos $p$ y $q$ y calcula $n = \emph{pq}$
+así como la función totient de Euler
+$\phi(n) = (p - 1)(q - 1)$.
+Entonces, cualquier $e$ con $1 < e < \phi(n)$ y
+$\gcd(e, \phi(n)) = 1$ se puede usar para
+definir una clave pública $(e,n)$. La condición de que el
+máximo común divisor (greatest common divisor - $\gcd$) de $e$ y
+$\phi(n)$ tiene que ser 1 (p. ej., que deben ser
+relativamente primos) asegura que la inversa de
+$e \mod \phi(n)$ existe.
+Esta inversa es la
+correspondiente clave privada $d$. Dado $\phi(n)$, la clave
+privada $d$ se puede calcular usando el algoritmo extendido
+Euclídeo de modo que
+$d \cdot e \equiv 1 \mod \phi(n)$.
+
+Dada la clave privada $d$ y la clave pública $(e, n)$, una firma simple RSA
+$s$ sobre un mensaje $m$ es
+$s \equiv m^{d} \mod n$.
+Para verificar la firma, se calcula
+$m' \equiv s^{e} \mod n$.
+Si $m'$ y $m$ coinciden, la firma es válida, lo que prueba que el
+mensaje fue firmado con la clave privada que pertenece a la clave
+publica de verificación (autenticación de mensaje) y que ese mensaje no
+ha sido cambiado en tránsito (integridad de mensaje). En la práctica,
+las firmas se colocan sobre lo hashes de los mensajes en vez de los
+propios mensajes. Las funciones hash calculan el resumen de los
+mensajes, que son identificadores únicos y cortos para los mensajes.
+Firmar un hash corto es mucho más rápido que firmar un mensaje largo, y
+la mayoría de los algoritmos de firma solo funcionan con entradas
+relativamente cortas.\footnote{En el caso del criptosistema RSA el
+límite de la longitud es $\log_{2}n$ bits.}
+
+\emph{Firmas ciegas.} Usamos firmas ciegas, introducidas
+por~\citet{Chaum1983}, para proteger la privacidad de los compradores. Una
+firma ciega se usa para crear una firma criptográfica para un mensaje sin que
+el firmante conozca el contenido del mensaje que se firma. En nuestra
+propuesta, esto evita que los bancos comerciales y el banco central puedan
+rastrear las compras identificando a los compradores. Nuestra propuesta
+funciona en principio con cualquier esquema de firma ciega, pero la mejor
+solución es la variante basada en RSA descrita por~\citet{Chaum1983}.
+
+El cegamiento lo realizan los clientes, quienes ciegan sus monedas antes
+de transmitirlas al banco central para ser firmadas. Los clientes por
+tanto no necesitan confiar al banco central la protección de su
+privacidad. Además, el cegamiento RSA proveería de protección de la
+privacidad incluso contra ataques informáticos cuánticos. El banco
+central, por su parte, establece múltiples denominaciones de pares de
+claves disponibles para realizar la firma ciega de monedas con
+diferentes valores, y publica/provee las correspondientes claves
+públicas $(e, n)$ para estos valores.
+
+Sea $f$ el valor hash de una moneda y por tanto un identificador único
+para esta moneda. El cliente que retira la moneda primero genera una
+factor ciego aleatorio $b$ y calcula
+$f' \equiv fb^{e} \mod n$
+con la clave pública del banco central para ese valor.
+La moneda cegada $f'$ se transmite luego
+al banco central para ser firmada. El banco central firma $f'$ con su
+clave privada $d$ calculando la firma ciega
+$s' \equiv \left(f' \right)^{d} \mod n$ y devuelve
+$s'$ al cliente.
+El cliente puede entonces des-cegar la firma calculando
+$s \equiv s'b^{- 1} \mod n$.
+Esto funciona porque
+$\left( f' \right)^d = f^db^{ed} = f^db$ y, así,
+multiplicar $s'$ con $b^{- 1}$ produce $f^d$, que es una firma RSA
+válida sobre $f$ como antes:
+$s^e \equiv f^{de} \equiv f \mod n$.
+
+En la propuesta original de Chaum, las monedas eran solo tokens. Sin
+embargo, nosotros queremos que los consumidores puedan realizar
+contratos usando firmas digitales. Para lograrlo, cuando una billetera
+digital retira una moneda, primero crea una clave privada aleatoria
+$c$ y calcula la correspondiente clave publica $C$ de esta moneda
+para crear firmas digitales con esquemas de firma criptográfica
+regulares (como DSA, ECDSA, EdDSA y RSA). Entonces, se deriva $f$
+usando una hash criptográfica de la clave pública $C$, que luego es
+firmada en modalidad ciega por el banco central (usando un factor
+aleatorio ciego actualizado para cada moneda). Ahora el cliente puede
+usar $c$ para firmar compras electrónicamente, gastando así la moneda.
+
+Como se ha señalado anteriormente, el banco central establecería pares
+de claves para los diferentes valores de las monedas y publicaría las
+claves públicas que los clientes podrían usar para retirar dinero. Estas
+claves de denominación, y por tanto las monedas, tendrían una fecha de
+vencimiento antes de la cual deberían ser gastadas o intercambiadas por
+nuevas monedas. A los clientes se les daría una cierta cantidad de
+tiempo durante el cual podrían intercambiar sus monedas. Un proceso
+similar existe para los billetes físicos, donde las series de los
+billetes se renuevan regularmente para que los billetes vayan equipados
+con las últimas características de seguridad, excepto que los billetes
+generalmente permanecen en circulación durante décadas en vez de por
+unos pocos meses o años.\footnote{En Suiza, por ejemplo, el Swiss
+National Bank empezó la eliminación paulatina la serie octava de
+billetes en abril de 2016. Estos billetes fueron puestos en
+circulación al final de los 90. A partir del día 1 de enero de 2020,
+sin embargo, todos los billetes que empiezan por la serie sexta
+emitidos en 1976, así como cualquier futura serie, permanecen válidas
+y se pueden cambiar por billetes actuales de forma indefinida.}
+
+Desde un punto de vista técnico, una fecha de vencimiento tiene dos
+ventajas. Primero, mejora la eficiencia del sistema porque el banco
+central puede descartar entradas vencidas y no tiene que almacenar y
+buscar una lista siempre creciente de monedas (gastadas) para detectar
+el doble gasto. Segundo, reduce los riesgos de seguridad porque el banco
+central no tiene que preocuparse sobre ataques contra sus claves
+($d$) de denominación (privadas) vencidas. Además , incluso si una
+clave privada se ve comprometida, el tiempo durante el cual el atacante
+puede usar la clave es limitado. Además cobrar una comisión por el
+cambio permitiría al banco central implementar tasas de interés
+negativas, si se considera necesario. El banco central podría también
+imponer un límite de conversión por cliente en consideración del AML y
+el CFT (límites de ``efectivo'') o por razones de estabilidad
+financiera (para prevenir el acaparamiento o las caídas bancarias), si
+así se deseara.
+
+\emph{Protocolo de intercambio de claves.} GNU Taler utiliza un
+protocolo de intercambio de claves de manera inusual para proporcionar
+un vínculo entre la moneda original y el cambio (también llamado
+``vuelto'') entregado por esa moneda original. Esto asegura que siempre
+se pueda entregar el cambio sin comprometer la transparencia de los
+ingresos o la privacidad del consumidor. El mismo mecanismo se puede
+usar también para realizar devoluciones anónimas a los clientes. El
+protocolo también maneja fallos en la red y en los componentes,
+asegurando que los pagos se hayan realizado definitivamente o se hayan
+cancelado definitivamente y que todas las partes tengan una prueba
+criptográfica del resultado. Esto es aproximadamente equivalente a los
+intercambios atómicos de los protocolos \emph{interledger} o al
+intercambio justo en sistemas tradicionales de efectivo electrónico.
+
+La construcción matemática más común para un protocolo de intercambio de
+claves es la construcción Diffie-Hellman~\cite{Diffie}. Esta
+permite que dos partes puedan derivar una clave secreta compartida. Para
+hacerlo, comparten dos parámetros del dominio $p$ y $g$, que
+pueden ser públicos, donde $p$ es un número primo grande y $g$
+es una raíz primitiva módulo $p$.\footnote{Un entero $g$ es una raíz
+primitiva módulo $p$ si para cada entero $a$ coprimo a $p$ hay
+algún entero $k$ para el cual
+$g^k \equiv a \mod p$.
+En la práctica, $g$ debería ser tal raíz primitiva $p-1$, que se
+llama también generador, para prevenir ataques de subgrupo tales como ataques
+Pohlig-Hellman~\cite[véase][]{Lim}.} Ahora, las dos partes eligen sus claves
+privadas \emph{a} y \emph{b}, que son dos números enteros grandes. Con estas claves
+privadas y los parámetros del dominio, generan sus respectivas claves
+públicas $A \equiv g^{a} \mod p$ y $B \equiv g^{b} \mod p$.
+Cada una de las partes ahora puede usar su propia clave privada y la
+clave pública de la otra parte para calcular la clave secreta compartida
+$k \equiv \left( g^b \right)^{a} \equiv \left( g^{a} \right)^{b} \equiv g^{\text{ab}} \mod p$.
+\footnote{El mismo mecanismo también se podría usar para garantizar que
+las monedas no se transfieran a un tercero durante el retiro. Para
+lograr esto, los consumidores tendrían que salvaguardar una clave de
+identidad a largo plazo. Luego, el proceso de retiro podría usar la
+misma construcción que usa GNU Taler para obtener el cambio, excepto
+que se usaría la clave de identidad a largo plazo de un cliente en
+lugar de la moneda original cuando se retira de la cuenta bancaria del
+cliente. Sin embargo, si el cliente no proteje la clave de identidad a
+largo plazo las garantías de privacidad podrían quedar anuladas con
+consecuente riesgo de robo de todas las monedas restantes. Dado el
+riesgo limitado en las transferencias a terceros al retirar monedas,
+no está claro si esta mitigación sería una buena compensación.}
+
+Para obtener el cambio (también llamado ``vuelto''), el cliente empieza
+con la clave privada de la moneda $c$. gastada parcialmente. Sea $C$ la
+correspondiente clave pública, p. ej.
+$C = g^{c} \mod p$.
+Cuando la moneda se gastó parcialmente, el banco central grabó en su base de
+datos la transacción en la que se incluye a $C$. Para simplificar, daremos
+por sentado que existe una denominación que coincide exactamente con el
+valor residual. De no ser así, se puede simplemente ejecutar
+repetidamente el protocolo de cambio hasta obtener todo el cambio
+necesario. Sea $(e,n)$ la clave de denominación para el
+cambio que se tiene que emitir.
+
+Para obtener el cambio, el cliente primero crea $\kappa$ claves de
+transferencia privada $t_{i}$ para
+$i \in \left\{ 1,\ldots,\kappa \right\}$ y calcula las
+correspondientes claves públicas $T_{i}$. Estas claves de
+transferencia $\kappa$ son simplemente pares de claves pública-privada
+que permiten al cliente ejecutar localmente el protocolo de intercambio
+de claves -- con el cliente jugando en ambos lados -- $\kappa$ veces
+entre $c$ y cada $t_{i}$. Si se usa Diffie-Hellman para el protocolo de
+intercambio de claves, tendremos
+$T_{i} \equiv g^{t_{i}} \mod p$.
+
+El resultado son tres secretos de transferencia
+$K_{i} \equiv \emph{KX}(c,t_{i})$. El protocolo de
+intercambio de claves se puede usar de diferentes maneras para llegar al
+mismo valor
+$K_{i} \equiv \emph{KX}(C,t_{i}) = \emph{KX}(c,T_{i})$.
+Dada $K_{i}$, el cliente usa una función criptográfica hash $H$ para
+derivar valores
+$(b_{i},c_{i}) \equiv H(K_{i})$, donde
+$b_{i}$ es un factor ciego válido para la clave de denominación
+$(e,n)$ y $c_{i}$ es una clave privada para obtener la
+moneda recién creada como cambio. $c_{i}$ debe ser adecuada tanto para
+crear firmas criptográficas como para su futuro uso con el protocolo de
+intercambio de claves (como $c$, para obtener cambio a partir del cambio).
+Sea $C_{i}$ la clave pública correspondiente a $c_{i}$. El cliente
+solicita entonces al banco central que cree una firma ciega sobre
+$C_{i}$ para $i \in \{ 1,\ldots,\kappa\}$.\footnote{Si se usara el
+criptosistema RSA para firmas ciegas, usaríamos
+$f \equiv \emph{FDH}_{n}(C_{i})$, donde
+$\emph{FDH}_{n}()$ es el hash de dominio completo sobre
+el dominio $n$.} En esta petición, el cliente también se compromete a
+las claves públicas $T_{i}$. La petición es autorizada usando una
+firma hecha con la clave privada $c$.
+
+En lugar de devolver directamente la firma ciega, el banco central
+primero desafía al cliente para comprobar que el cliente haya usado
+correctamente la construcción mencionada anteriormente proveyendo
+$\gamma \in \left\{ 1,\ldots,\kappa \right\}$. El cliente debe
+entonces revelar al banco central la $t_{i}$ para $i \neq \gamma$ .
+El banco central puede entonces calcular
+$K_{i} \equiv \emph{KX}(C,t_{i})$ y derivar los valores
+de $(b_{i},c_{i})$. Si para todas las $i \neq \gamma$
+la $t_{i}$ provista demuestra que el cliente usó la construcción
+correctamente, el banco central devuelve la firma ciega sobre
+$C_{\gamma}$. Si el cliente no provee una prueba correcta, se pierde
+el valor residual de la moneda original. Esto penaliza efectivamente a
+quienes intentan evadir la transparencia de sus ingresos con una tasa de
+impuestos estimada de $1 - \frac{1}{\kappa}$.
+
+Para evitar que un cliente conspire con un comerciante que está tratando
+de ocultar sus ingresos, el banco central permite que cualquiera que
+conozca $C$ pueda obtener, en cualquier momento, los valores de
+$T_{\gamma}$ y las correspondientes firmas ciegas de todas las monedas
+vinculadas a la moneda original $C$. Esto permite que el propietario de la
+moneda original -- que conoce $c$ -- calcule
+$K_{\gamma} \equiv \emph{KX}( c,T_{\gamma})$ y, a partir de
+allí, pueda derivar $(b_{i},c_{i})$ y descifrar la firma
+ciega. En consecuencia, un comerciante que oculte sus ingresos de este
+modo formaría básicamente una unión económica limitada con el cliente en
+lugar de obtener un control exclusivo.
+
+\hypertarget{arquitectura-del-sistema}{%
+\subsection{Arquitectura del sistema}\label{arquitectura-del-sistema}}
+
+El objetivo principal de nuestra arquitectura es asegurar que los bancos
+centrales no tengan que interactuar directamente con los clientes o
+guardar ninguna información sobre ellos, sino simplemente mantener una
+lista de las monedas que se gastan. La autenticación se delega a los
+bancos comerciales, que tienen ya la infraestructura necesaria. Los
+protocolos de retiro y depósito llegan al banco central a través del
+banco comercial como intermediario. Desde el punto de vista del cliente,
+el proceso es análogo a retirar dinero efectivo desde un cajero
+automático. La transacción entre el banco comercial del usuario y el
+banco central tiene lugar en segundo plano. El procedimiento para
+retirar CBDC sería como se muestra en la Figura~\ref{fig:fig1}.
+
+\begin{figure}[h!]
+ \includegraphics[width=\textwidth]{retirada.pdf}
+ \caption{Retiro de CBDC}
+ \label{fig:fig1}
+\end{figure}
+
+Un cliente (1) proporciona autenticación a su banco comercial usando la
+autenticación respectiva del banco comercial y los procedimientos de
+autorización. A continuación, el teléfono (u ordenador) del cliente
+obtiene la clave de denominación $(e, n)$ provista por el banco central
+para ese valor; calcula entonces (2) un par de claves para una moneda,
+con la clave privada c y la clave pública $C$, y elige un factor de cegado
+$b$. A la clave pública de la moneda se le aplica una función hash
+($\to$ $f$) y es cegada ($\to$ $f'$). A continuación, (3) el teléfono
+del cliente envía $f'$ junto con una autorización para retirar la
+moneda y debitar de la cuenta del cliente en el banco comercial a través
+de un canal seguro establecido. El banco comercial entonces (4) debita
+la cantidad en la cuenta de depósito del cliente, (5) autoriza
+digitalmente la petición con la propia firma digital de su sucursal
+bancaria y reenvía la petición y la moneda cegada al banco central para
+su firma. El banco central (6) deduce el valor de la moneda en la cuenta
+del banco comercial, firma la moneda de forma ciega con la clave privada
+del banco central para el valor respectivo, y (7) devuelve la firma
+ciega $s'$ al banco comercial. (8) reenvía la firma ciega $s'$
+a la billetera electrónica del cliente. Finalmente, el teléfono del
+cliente (9) usa $b$ para descifrar la firma ($\to$ $f$) y almacena la
+moneda recién acuñada $(c, s)$.
+
+Cuando se gastan CBDC, el proceso es análogo a pagar al vendedor en
+efectivo. Sin embargo, para asegurar el acuerdo, el vendedor debe
+depositar las monedas. El procedimiento para gastar CBDC se indica en la
+Figura~\ref{fig:fig2}.
+
+Un cliente y un vendedor negocian un contrato comercial, y (1) el
+cliente usa una moneda electrónica para firmar el contrato o factura de
+venta con la clave privada $c$ de la moneda y transmite la firma al
+vendedor. La firma de una moneda en un contrato con una moneda válida es
+una instrucción del cliente para pagar al vendedor que es identificado
+por la cuenta bancaria en el contrato. Los clientes pueden firmar
+contratos con múltiples monedas en caso de que una sola moneda fuera
+insuficiente para pagar la cantidad total. El vendedor (2) valida
+entonces la firma de la moneda sobre el contrato y la firma s del banco
+central sobre $f$ que corresponde a la $C$ de la moneda con las
+respectivas claves públicas y reenvía la moneda firmada (junto con la
+información de la cuenta del vendedor) al banco comercial del vendedor.
+El banco comercial del vendedor (3) confirma que el vendedor es uno de
+sus clientes y envía la moneda firmada al banco central. El banco
+central (4) verifica las firmas y comprueba su base de datos para
+asegurar que la moneda no haya sido previamente gastada. Si todo está en
+orden, (5) el banco central añade la moneda a la lista de monedas
+gastadas, acredita la cuenta del banco comercial en el banco central y
+(6) envía la confirmación al banco comercial a tal efecto. A
+continuación, (7) el banco comercial acredita la cuenta del vendedor e
+(8) informa al vendedor. El vendedor (9) entrega el producto o servicio
+al cliente. Todo el proceso dura solo unos pocos milisegundos.
+
+\begin{figure}[h!]
+ \includegraphics[width=\textwidth]{deposito.pdf}
+ \caption{Gastar y depositar CBDC}
+ \label{fig:fig2}
+\end{figure}
+
+\hypertarget{consideraciones-acerca-de-la-seguridad}{%
+\subsection{Consideraciones acerca de la Seguridad}
+\label{consideraciones-acerca-de-la-seguridad}}
+
+Nuestra propuesta requiere que el banco central opere un servicio en
+línea y una base de datos de alta disponibilidad. Debido a que los
+usuarios pueden copiar las monedas electrónicas, solo los controles en
+línea pueden prevenir eficientemente el doble gasto. Si bien existen
+soluciones teóricas para identificar de manera retroactiva a usuarios
+que se dediquen al doble gasto~\cite[véase][]{Chaum1990}, tales
+soluciones crean un riesgo económico tanto para los usuarios como para
+el banco central, debido al retraso en la identificación de
+transacciones fraudulentas. La detección del doble gasto en línea
+elimina este riesgo, pero a su vez implica que las transacciones serán
+imposibles de realizar si la conexión con el banco central no estará
+disponible.
+
+El banco central también tendrá que proteger la confidencialidad de las
+claves privadas que utiliza para firmar las monedas y otros mensajes del
+protocolo. De manera que si las claves de las firmas del banco central
+se vieran en algún momento comprometidas, como por ejemplo por una
+computadora cuántica, un ataque físico en su centro de datos, o quizás
+por algún nuevo algoritmo imprevisto, los usuarios puedan de forma
+segura, y sin comprometer su privacidad, ser reembolsados con todas las
+monedas que no han gastado. El banco central anunciaría la revocación de
+clave mediante la API (Application Programming Interface), que sería
+detectada por las billeteras e iniciarían el siguiente protocolo de
+actualización: el usuario revela al banco central la clave pública
+$C$ de la moneda, la firma $s$ del banco central, y el factor
+ciego $b$, posibilitando así que el banco central verifique el
+retiro legítimo del usuario y devuelva el valor de la moneda no gastada.
+Para detectar un posible compromiso de esta clave, el banco central
+puede monitorear la base de datos en busca de casos de depósitos que
+superen los retiros.
+
+\subsection{Escalabilidad y Costes}\label{escalabilidad-y-costes}
+
+El esquema que proponemos sería tan eficiente y rentable como los
+modernos sistemas RTGS que utilizan actualmente los bancos centrales.
+
+La escalabilidad se refiere al costo de aumentar la capacidad de
+procesamiento para que se pueda procesar un número cada vez mayor de
+transacciones en un tiempo adecuado para la finalidad. El costo global
+del sistema puede ser bajo, ya que la CBDC que se propone aquí se basa
+en software solamente. Las monedas gastadas deben guardarse hasta que
+caduque el par de claves de denominación que se usó para firmar las
+monedas; por ejemplo, mediante un calendario anual renovable, que
+mantiene limitado el tamaño de la base de datos. La cantidad de potencia
+de procesamiento adicional y ancho de banda necesarios aumenta en la
+misma cantidad por cada transacción, gasto o depósito adicional, porque
+las transacciones son esencialmente independientes una de la otra. Esta
+potencia adicional se logra simplemente añadiendo más hardware,
+comúnmente llamado partición o fragmentación. Con el llamado hash
+consistente, las adiciones de hardware no tienen por qué ser
+disruptivas. Se puede utilizar cualquier tecnología de base de datos
+subyacente.
+
+Más concretamente, la lógica del front-end en el banco central solo tiene que
+realizar unas cuantas operaciones de firma, y un único procesador puede hacer
+miles de operaciones por segundo~\cite[véase][]{Bernstein2020}. Si un solo
+sistema es insuficiente, es fácil desplegar servidores front-end adicionales y
+solicitar a los varios bancos comerciales que balanceen sus peticiones en la
+granja de servidores o que utilicen un balanceador de carga para distribuir
+las peticiones dentro de la infraestructura del banco central.
+
+Los servidores front-end deben comunicarse con una base de datos para
+hacer transacciones y prevenir el doble gasto. Un solo servidor moderno
+para la base de datos debería ser suficiente para manejar de manera
+fiable decenas de miles de estas operaciones por segundo. Las
+operaciones se reparten fácilmente entre varios servidores de bases de
+datos simplemente asignando a cada servidor un rango de valores de los
+que es responsable. Este diseño asegura que las transacciones
+individuales nunca crucen fragmentos. Así, se espera que también los
+sistemas de back-end escalen linealmente con los recursos
+computacionales disponibles, de nuevo partiendo de una línea de base
+alta para un solo sistema.
+
+Los front-end también deben comunicarse con los back-end mediante una
+interconexión. Las interconexiones puede soportar grandes cantidades de
+transacciones por segundo. El tamaño de una transacción individual suele
+ser de 1-10 kilobytes aproximadamente. Así, las interconexiones de un
+centro de datos moderno, con velocidades de conmutación de 400 Gbit/s,
+pueden soportar millones de transacciones por segundo.
+
+En fin, el costo total del sistema es bajo. Es probable que el
+almacenamiento seguro de 1 a 10 kilobytes por transacción durante muchos
+años sea el costo predominante del sistema. Utilizando los precios de
+Amazon Web Services, experimentamos con un prototipo anterior de GNU
+Taler y descubrimos que el costo del sistema (almacenamiento, ancho de
+banda y computación) a escala estaría por debajo de USD 0,0001 por
+transacción (para obtener detalles sobre los datos, consulte~\citet{Dold}).
+
+
+\section{Consideraciones normativas y políticas}
+ \label{5.-consideraciones-normativas-y-poluxedticas}
+
+En el esquema propuesto, los bancos centrales no conocen la identidad de
+los consumidores o comerciantes ni los montos totales de las
+transacciones. Los bancos centrales solo ven cuándo se lanzan las
+monedas electrónicas y cuándo se canjean. Los bancos comerciales siguen
+proporcionando autenticación crucial de clientes y comerciantes y, en
+particular, siguen siendo los guardianes de la información del KYC. Los
+bancos comerciales observan cuándo los comerciantes reciben fondos y
+pueden limitar la cantidad de CBDC por transacción que un comerciante
+individual puede recibir, si así se requiere.
+
+Además, las transacciones están asociadas con los contratos pertinentes
+de los clientes. La transparencia de ingresos que se obtiene permite que
+el sistema cumpla con los requisitos del AML y CFT. Si se detectan
+patrones inusuales de ingresos comerciales, el banco comercial, las
+autoridades fiscales o las fuerzas del orden pueden obtener e
+inspeccionar los contratos comerciales subyacentes a los pagos para
+determinar si la actividad sospechosa es ilegal. La transparencia de los
+ingresos que se obtiene es también una fuerte medida contra la evasión
+fiscal porque los comerciantes no pueden declarar menos ingresos o
+evadir los impuestos sobre las ventas. En general, el sistema implementa
+privacidad por diseño y privacidad por omisión (como lo exige, por
+ejemplo, el Reglamento General de Protección de Datos de la Unión
+Europea). Los comerciantes no infieren inherentemente la identidad de
+sus clientes, los bancos solo tienen la información necesaria sobre las
+actividades de sus propios clientes y los bancos centrales están
+felizmente divorciados del conocimiento detallado de las actividades de
+los ciudadanos.
+
+Por razones reglamentarias, en algunos países existen límites para los
+retiros y pagos en efectivo. Dichas restricciones también podrían
+implementarse para la CBDC en el diseño propuesto. Por ejemplo, se
+podría limitar la cantidad que los consumidores puedan retirar por día,
+o limitar la cantidad total de CBDC que los bancos comerciales puedan
+convertir.
+
+Un problema potencial de estabilidad financiera que a menudo se plantea
+con las CBDC al por menor es la desintermediación del sector bancario.
+En particular, la venta de CBDC al por menor podría facilitar el
+acaparamiento de grandes cantidades de dinero del banco central. Esto
+podría afectar negativamente a la financiación de depósitos de los
+bancos porque el público tendría menos dinero en forma de depósitos
+bancarios. Para los países cuyas monedas sirven como monedas de refugio
+seguro, podría conducir a un aumento de las entradas de capital durante
+períodos de riesgo global, lo que resultaría en presiones adicionales en
+la apreciación del tipo de cambio.
+
+Si bien esto podría representar una preocupación seria en el caso de una
+CBDC basada en cuentas, la preocupación sería menor con una CBDC basada
+en tokens. En primer lugar, acumular una CBDC basada en tokens conlleva
+riesgos de robo o pérdida similares a los de acumular efectivo. Tener
+unos cientos de dólares en un teléfono inteligente es probablemente un
+riesgo aceptable para muchos, pero tener una cantidad muy grande es
+probablemente un riesgo menos aceptable. Por tanto, no esperaríamos un
+acaparamiento significativamente mayor que en el caso del efectivo
+físico.
+
+Sin embargo, si el acaparamiento o la conversión masiva a CBDC de dinero
+proveniente de depósitos bancarios se convirtieran en un problema, los bancos
+centrales tendrían varias opciones. Como se señaló, en el diseño propuesto los
+bancos centrales configuran una fecha de vencimiento para todas las claves de
+firma, lo que implica que en una fecha establecida las monedas firmadas con
+esas claves dejan de ser válidas. Cuando las claves de denominación caducan y
+los clientes tienen que cambiar monedas firmadas con claves de denominación
+antiguas por monedas nuevas, el regulador podría fácilmente imponer un límite
+de conversión por cliente para hacer cumplir un límite estricto a la cantidad
+de CBDC que cualquier individuo puede acumular. Además, los bancos centrales
+podrían cobrar una tarifa si fuera necesario. Una tarifa de actualización de
+este tipo, cuando las monedas están programadas para caducar, implicaría de
+hecho tasas de interés negativas en la CBDC, y haría que la CBDC resultara
+menos atractiva como depósito de valor, tal como sugiere Bindseil (2020). De
+hecho, sería la implementación directa de la idea de Silvio Gesell de aplicar
+un ``impuesto de posesión'' sobre la moneda, al que hace célebremente
+referencia~\citet{Keynes}, y reviven~\citet{Goodfriend}, \citet{Buiter}
+y~\citet{Agarwal}.
+
+En cuanto a las posibles implicaciones para las políticas monetarias, no
+anticipamos efectos materiales porque nuestra CBDC está diseñada para
+replicar el dinero en efectivo en lugar de los depósitos bancarios. La
+emisión, retiro y depósito de nuestra CBCD corresponden exactamente a la
+emisión, retiro y depósito de billetes. Es posible que una CBDC al por
+menor tenga un ritmo de circulación diferente a la del efectivo físico,
+pero esto no sería un problema material para las políticas monetarias.
+
+\hypertarget{trabajos-relacionados}{%
+\section{Trabajos relacionados}\label{6.-trabajos-relacionados}}
+
+Como se señaló anteriormente, la CBDC propuesta en el presente documento se
+basa en eCash y GNU Taler.\footnote{La implementación de eCash por la compañía
+DigiCash en los años noventa está documentada en
+\url{https://www.chaum.com/ecash}.} A partir de la propuesta original de Chaum
+para el efectivo electrónico, la investigación se ha centrado en tres
+cuestiones principales. Primero, en la propuesta original de Chaum las monedas
+tenían un valor fijo y solo podían gastarse en su totalidad. Pagar grandes
+cantidades con monedas denominadas en centavos sería ineficiente, por lo
+que~\citet{Okamoto}, \citet{Camenisch2005}, \citet{Canard} y~\citet{Dold}
+idearon formas de abordar este problema. Estas soluciones involucran
+protocolos para dar cambio o para posibilitar la divisibilidad de las monedas.
+
+Una segunda cuestión es que las transacciones a veces fallan debido a
+caídas de la red, por ejemplo. En este caso, el sistema debe permitir
+que los fondos permanezcan con el consumidor sin impacto negativo sobre
+privacidad. \citet{Camenisch2007} y~\citet{Dold} abordan este tema en
+su propuesta de dinero electrónico respaldado. Varias de las soluciones
+anteriores violan las garantías de privacidad para los clientes que
+utilizan estas funciones, y todas, excepto Taler, violan el requisito de
+transparencia de ingresos.
+
+La tercera cuestión importante, a menudo desatendida, es conservar la
+transparencia de los ingresos y, por lo tanto, el cumplimiento del AML y
+KYC. \citet{Fuchsbauer} diseñaron deliberadamente un sistema que
+posibilita la desintermediación para proporcionar una semántica más
+similar al efectivo. Sin embargo, la desintermediación ilimitada
+generalmente no concuerda con las regulaciones del AML y KYC, ya que no
+permite lograr ningún nivel de responsabilidad. Un ejemplo de tal diseño
+es ZCash, un libro mayor distribuido que oculta a la red la información
+sobre el pagador, el beneficiario y el monto de la transacción, siendo
+por lo tanto el sistema de pago perfecto para la delincuencia en línea.
+Solo Taler ofrece tanto la privacidad constante del cliente como la
+transparencia de los ingresos, al mismo tiempo que proporciona un cambio
+eficiente, intercambios atómicos~\cite[consulte][]{Camenisch2007} y la
+capacidad de restaurar billeteras desde una copia de seguridad.
+
+Con respecto a los sistemas de pago para las CBDC, \citet{Danezis} diseñaron
+un libro mayor escalable con RSCoin. Básicamente es un sistema RTGS que es
+protegido utilizando la misma criptografía que se usa en Bitcoin. Al igual que
+Taler, el diseño utiliza la fragmentación de la base de datos para lograr una
+escalabilidad lineal. Sin embargo, el diseño de~\citet{Danezis} no tiene
+ninguna disposición para la privacidad y carece de consideraciones sobre cómo
+integrar prácticamente el diseño con los sistemas y procesos bancarios
+existentes.
+
+La EUROchain del Banco Central Europeo\cite[véase][]{ECB} es otro
+prototipo para CBDC con libro mayor distribuido. Similar a la
+arquitectura propuesta en el presente documento, la EUROchain utiliza
+una arquitectura de dos niveles donde los bancos comerciales actúan como
+intermediarios. Una diferencia crucial es la manera en que los sistemas
+intentan combinar la privacidad y el cumplimiento del AML. En nuestro
+diseño, los reguladores podrían imponer un límite a la cantidad de
+efectivo electrónico que el titular de una cuenta bancaria puede retirar
+durante un cierto tiempo, mientras que la EUROchain emite un número
+limitado de ``vales de anonimato'' que conceden al receptor un número
+limitado de transacciones sin verificación del AML. Como estos vales
+parecen no tener ninguna relación con ningún token de valor, no queda
+claro de qué manera el diseño evitaría la aparición de un mercado negro
+de ``vales de anonimato''. Además, la noción de anonimato de la
+EUROchain es muy diferente, ya que sus ``vales de anonimato'' simplemente
+eliminan ciertas verificaciones del AML, al mismo tiempo que preservan
+la capacidad de los bancos comerciales de ver cómo los consumidores
+gastan el efectivo electrónico. Mientras que los pagadores usuarios de
+Taler interactúan directamente con los comerciantes para gastar su
+efectivo electrónico, el sistema EUROchain requiere que los pagadores
+instruyan a sus bancos comerciales para que accedan a su CBDC. Por lo
+tanto, la EUROchain no emite tokens de valor directamente a los
+consumidores y, en cambio, depende de que los consumidores se
+autentiquen ellos mismos en sus bancos comerciales para acceder a la
+CBDC que el banco central mantiene efectivamente en custodia. Por lo
+tanto, no está claro qué ventajas de privacidad, rendimiento o seguridad
+tiene la EUROchain sobre el dinero existente en depósito.
+
+\section{Conclusión}\label{7.-conclusiuxf3n}
+
+Con la aparición de Bitcoin y monedas digitales recientemente propuestas
+por grandes empresas tecnológicas como Diem (antes Libra), los bancos
+centrales se enfrentan a una competencia cada vez mayor de actores que
+ofrecen su propia alternativa digital al efectivo físico. Las decisiones
+de los bancos centrales sobre la emisión o no de una CBDC dependen de
+cómo evalúen los beneficios y los riesgos de una CBDC. Estos beneficios
+y riesgos, así como las circunstancias jurisdiccionales específicas que
+definen el alcance de las CBDC al por menor, probablemente difieran de
+un país a otro.
+
+Si un banco central decide emitir una CBDC al por menor, proponemos una
+CBDC basada en tokens que combina la privacidad de las transacciones con
+el cumplimiento del KYC, AML y CFT. Dicha CBDC no competiría con los
+depósitos de los bancos comerciales, sino que reproduciría el efectivo
+físico, lo que limitaría los riesgos de estabilidad financiera y
+políticas monetarias.
+
+Hemos demostrado que el esquema propuesto aquí sería tan eficiente y
+rentable como los sistemas RTGS modernos operados por los bancos
+centrales. Los pagos electrónicos con nuestra CBDC solo necesitarían una
+simple base de datos para las transacciones y cantidades minúsculas de
+ancho de banda. La eficiencia y la rentabilidad, junto con la facilidad
+de uso mejorada para el consumidor provocada por el cambio de la
+autenticación a la autorización, hacen que este esquema sea
+probablemente el primero en respaldar el objetivo largamente previsto de
+los micropagos en línea. Además, el uso de monedas para firmar
+criptográficamente contratos electrónicos permitiría el uso de contratos
+inteligentes. Esto también podría conducir a la aparición de
+aplicaciones completamente nuevas para los sistemas de pago. Aunque
+nuestro sistema no se basa en la DLT, podría integrarse fácilmente con
+dichas tecnologías si así lo requirieran las infraestructuras del
+mercado financiero en el futuro.
+
+Igualmente importante, sin embargo, es que una CBDC al por menor debe
+preservar el efectivo como un bien común respetuoso de la privacidad
+bajo el control individual de los ciudadanos. Esto se puede lograr con
+el esquema propuesto en este documento, y los bancos centrales pueden
+evitar perturbaciones significativas en sus políticas monetarias y
+estabilidad financiera cosechando al mismo tiempo los beneficios de la
+digitalización.
+
+
+\newpage
+%REFERENCIAS
+\bibliographystyle{agsm}
+\bibliography{cbdc}
+
+\end{document}
diff --git a/doc/cbdc-es/cbdc.bib b/doc/cbdc-es/cbdc.bib
new file mode 100644
index 000000000..fe0ea6265
--- /dev/null
+++ b/doc/cbdc-es/cbdc.bib
@@ -0,0 +1,566 @@
+@article{Adrian,
+ author = {Adrian, Tobias and Tommaso Mancini-Griffoli},
+ year = {2019},
+ title = {The Rise of Digital Money},
+ journal = {IMF Fintech Note},
+ volume = {19/01},
+}
+
+@article{Agarwal,
+ author = {Agarwal, Ruchir and Miles S. Kimball},
+ year = {2019},
+ title = {Enabling Deep Negative Rates to Fight Recessions: A Guide},
+ journal = {IMF Working Paper},
+ volume = {19/84},
+}
+
+
+@article{Agur,
+ author = {Agur, Itai and Anil Ari and Giovanni Dell'Ariccia},
+ year = {2019},
+ title = {Designing Central Bank Digital Currencies},
+ journal = {IMF Working Paper},
+ volume = {19/252},
+}
+
+@article{Allen,
+ author = {Allen, Sarah and Srđjan Čapkun and Ittay Eyal and Giulia Fanti and Bryan A. Ford and James Grimmelmann and Ari Juels and Kari Kostiainen and Sarah Meiklejohn and Andrew Miller and Eswar Prasad and Karl Wüst and Fan Zhang},
+ year = {2020},
+ title = {Design Choices for Central Bank Digital Currency: Policy and Technical Considerations},
+ journal = {NBER Working Paper},
+ volume = {27634},
+}
+
+@article{Alves,
+ author = {Alves, Tiago and Don Felton},
+ year = {2004},
+ title = {TrustZone: Integrated hardware and software security},
+ journal = {ARM IQ},
+ volume = {3},
+ number = {4},
+ pages = {18--24},
+}
+
+@article{AuerBoehme,
+ author = {Auer, Raphael and Rainer Böhme},
+ year = {2020},
+ title = {The technology of retail central bank digital currency},
+ journal = {BIS Quarterly Review},
+ month = {March},
+ pages = {85--96},
+}
+
+@article{AuerCornelli,
+ author = {Auer, Raphael and Giulio Cornelli and Jon Frost},
+ year = {2020},
+ title = {Taking stock: ongoing retail {CBDC} projects},
+ journal = {BIS Quarterly Review},
+ month = {March},
+ pages = {97--98},
+}
+
+@booklet{BIS,
+ author = {{Bank for International Settlements}},
+ year = {2018},
+ title = {Central Bank Digital Currencies. Joint Report of the Committee on Payments and Market Infrastructures and Markets Committee},
+}
+
+@booklet{BoE,
+ author = {{Bank of England}},
+ year = {2020},
+ title = {Central Bank Digital Currency: Opportunities, Challenges and Design. Discussion Paper},
+ month = {March},
+}
+
+@article{Baiocchi,
+ author = {Baiocchi, Giovanni and Walter Distaso},
+ year = {2003},
+ title = {{GRETL}: Econometric Software for the {GNU} Generation},
+ journal = {Journal of Applied Econometrics},
+ volume = {18},
+ pages = {105-110},
+}
+
+@article{Bech,
+ author = {Bech, Morten and Rodney Garratt},
+ year = {2017},
+ title = {Central bank cryptocurrencies},
+ journal = {BIS Quarterly Review},
+ month = {September},
+ pages = {55--70},
+}
+
+@article{Berentsen,
+ author = {Berentsen, Aleksander and Fabian Schär},
+ year = {2018},
+ title = {The Case for Central Bank Electronic Money and the Non-case for Central Bank Cryptocurrencies},
+ journal = {Federal Reserve Bank of St. Louis Review},
+ volume = {100},
+ number = {2},
+ pages = {97--106},
+}
+
+@article{Bernstein2020,
+ author = {Bernstein, Daniel J. and Tanja Lange},
+ year = {2020},
+ title = {{eBACS}: {ECRYPT} Benchmarking of Cryptographic Systems},
+ url = {\url{https://bench.cr.yp.to}, accessed 17 March 2020},
+}
+
+@article{Bernstein2012,
+ author = {Bernstein, Daniel J. and Niels Duif and Tanja Lange and Peter Schwabe and Bo-Yin Yang},
+ year = {2012},
+ title = {High-speed high-security signatures},
+ journal = {Journal of Cryptographic Engineering},
+ volume = {2},
+ pages = {77--89},
+}
+
+@InCollection{Bindseil,
+ author = {Bindseil, Ulrich},
+ year = {2020},
+ title = {Tiered {CBDC} and the financial system},
+ publisher = {European Central Bank},
+ series = {ECB Working Paper},
+ number = {2351},
+ month = {January},
+}
+
+@article{Boar,
+ author = {Boar, Codruta and Henry Holden and Amber Wadsworth},
+ year = {2020},
+ title = {Impending arrival - a sequel to the survey on central bank digital currency},
+ journal = {BIS Papers},
+ volume = {107},
+}
+
+@article{Boneh,
+ author = {Boneh, Dan},
+ year = {1999},
+ title = {Twenty Years of Attacks on the {RSA} Cryptosystem},
+ journal = {Notices of the AMS},
+ volume = {42},
+ number = {2},
+ pages = {202--213},
+}
+
+
+@InCollection{Bordo,
+ author = {Bordo, Michael D. and Andrew T. Levin},
+ year = {2017},
+ title = {Central bank digital currency and the future of monetary policy},
+ publisher = {National Bureau of Economic Research},
+ series = {NBER Working Paper Series},
+ number = {23711},
+}
+
+@article{Brunnermeier,
+ author = {Brunnermeier, Markus and Dirk Niepelt},
+ year = {2019},
+ title = {On the Equivalence of Private and Public Money},
+ journal = {Journal of Monetary Economics},
+ volume = {106},
+ pages = {27--41},
+}
+
+@article{Buiter,
+ author = {Buiter, Willem H. and Nikolaos Panigirtzoglou},
+ year = {2003},
+ title = {Overcoming the Zero Bound on Nominal Interest Rates with Negative Interest on Currency: Gesell's Solution},
+ journal = {The Economic Journal},
+ volume = {113},
+ number = {490},
+ pages = {723--746},
+}
+
+@InCollection{Bullmann,
+ author = {Bullmann, Dirk and Jonas Klemm and Andrea Pinna},
+ year = {2019},
+ title = {In search for stability in crypto-assets: are stablecoins the solution?},
+ publisher = {European Central Bank},
+ series = {ECB Occasional Paper Series},
+ number = {230},
+}
+
+@inproceedings{Camenisch2007,
+ author = {Camenisch, Jan and Aanna Lysyanskaya and Mira Meyerovich},
+ year = {2007},
+ title = {Endorsed E-Cash},
+ booktitle = {2007 IEEE Symposium on Security and Privacy (SP'07)},
+ month = {May},
+ pages = {101--115},
+}
+
+@inproceedings{Camenisch2005,
+ author = {Camenisch, Jan and Susan Hohenberger and Anna Lysyanskaya},
+ year = {2005},
+ title = {Compact E-Cash},
+ booktitle = {Advances in Cryptology -- EUROCRYPT 2005: 24th Annual International Conference on the Theory and Applications of Cryptographic Techniques},
+ address = {Aarhus, Denmark},
+ month = {May},
+ day = {22-26},
+ editor = {Ed. by Ronald Cramer},
+ publisher = {Springer-Verlag Berlin Heidelberg},
+}
+
+
+
+@inproceedings{Canard,
+ author = {Canard, Sébastien and Aline Gouget},
+ year = {2007},
+ title = {Divisible e-cash systems can be truly anonymous},
+ booktitle = {Annual International Conference on the Theory and Applications of Cryptographic Techniques},
+ pages = {482--497},
+}
+
+
+
+@misc{CCC,
+ author = {{CCC e.V.}},
+ year = {2017},
+ title = {Chaos Computer Club hacks e-motor charging stations},
+ howpublished = {34c3},
+}
+
+@article{Chapman,
+ author = {Chapman, James and Rodney Garratt and Scott Hendry and Andrew McCormack and Wade McMahon},
+ year = {2017},
+ title = {Project {J}asper: Are Distributed Wholesale Payment Systems Feasible Yet?},
+ journal = {Financial System Review},
+ publisher = {Bank of Canada},
+ month = {June},
+ pages = {59--69},
+}
+
+@inproceedings{Chaum1983,
+ author = {Chaum, David},
+ year = {1983},
+ title = {Blind signatures for untraceable payments},
+ booktitle = {Advances in Cryptology: Proceedings of Crypto `82},
+ pages = {199--203},
+}
+
+@inproceedings{Chaum1990,
+ author = {Chaum, David and Amos Fiat and Moni Naor},
+ year = {1990},
+ title = {Untraceable electronic cash},
+ booktitle = {Advances in Cryptology: Proceedings of CRYPTO '88},
+ pages = {319--327},
+}
+
+@inproceedings{Danezis,
+ author = {Danezis, George and Sarah Meiklejohn},
+ year = {2016},
+ title = {Centrally Banked Cryptocurrencies},
+ booktitle = {23nd Annual Network and Distributed System Security Symposium, NDSS2016},
+ address = {San Diego, California, USA},
+ month = {February},
+ day = {21--24},
+ publisher = {The Internet Society},
+}
+
+@article{Diffie,
+ author = {Diffie, Whitfield and Martin Hellmann},
+ year = {1976},
+ title = {New Directions in Cryptography},
+ journal = {IEEE Trans. on Inf. Theory, IT-22},
+ pages = {644--654},
+}
+
+@phdthesis{Dold,
+ author = {Dold, Florian},
+ year = {2019},
+ title = {The {GNU} {T}aler System: Practical and Provably Secure Electronic Payments. PhD Thesis},
+ school = {University of Rennes 1},
+}
+
+@article{ECB,
+ author = {{European Central Bank}},
+ year = {2019},
+ title = {Exploring anonymity in central bank digital currencies},
+ journal = {In Focus},
+ number = {4},
+ month = {December},
+}
+
+@inproceedings{Fuchsbauer,
+ author = {Fuchsbauer, Georg and David Pointcheval and Damien Vergnaud},
+ year = {2009},
+ title = {Transferable constant-size fair e-cash},
+ booktitle = {International Conference on Cryptology and Network Security},
+ publisher = {Springer-Verlag Berlin Heidelberg},
+ pages = {226--247},
+}
+
+@inproceedings{Garcia,
+ author = {Garcia, Flavio and Gerhard de Koning Gans and Ruben Muijrers and Peter van Rossum and Roel Verdult and Ronny Wichers Schreur and Bart Jacobs},
+ year = {2008},
+ title = {Dismantling MIFARE Classic},
+ booktitle = {European Symposium on Research in Computer Security},
+}
+
+@article{Garratt,
+ author = {Garratt, Rod and Michael Lee and Brendan Malone and Antoine Martin},
+ year = {2020},
+ title = {Token- or Account-Based? A Digital Currency Can Be Both},
+ journal = {Liberty Street Economics},
+ publisher = {Federal Reserve Bank of New York},
+ month = {August},
+ day = {12},
+}
+
+@article{Goodfriend,
+ author = {Goodfriend, Marvin},
+ year = {2000},
+ title = {Overcoming the Zero Bound on Interest Rate Policy},
+ journal = {Journal of Money, Credit, and Banking},
+ volume = {32},
+ number = {4},
+ pages = {1007--1035},
+}
+
+@article{Johnston,
+ author = {Johnston, Casey},
+ year = {2010},
+ title = {PS3 hacked through poor cryptography implementation},
+ journal = {Ars Technica},
+ month = {December},
+ day = {30},
+}
+
+
+
+@Misc{Jordan,
+ note = {Speech given at the 30th anniversary of the WWZ and VBÖ},
+ author = {Jordan, Thomas J.},
+ year = {2019},
+ title = {Currencies, money and digital tokens},
+ publisher = {University of Basel},
+ month = {September},
+ howpublished = {\url{https://www.snb.ch/en/mmr/speeches/id/ref\_20190905\_tjn/source/ref\_20190905\_tjn.en.pdf}},
+}
+
+
+@article{Kahn2009,
+ author = {Kahn, Charles M. and William Roberds},
+ year = {2009},
+ title = {Why Pay? An Introduction to Payments Economics},
+ journal = {Journal of Financial Intermediation},
+ number = {18},
+ pages = {1--23},
+}
+
+@article{Kahn2005,
+ author = {Kahn, Charles M. and James McAndrews and William Roberds},
+ year = {2005},
+ title = {Money is Privacy},
+ journal = {International Economic Review},
+ volume = {46},
+ number = {2},
+ pages = {377--399},
+}
+
+@article{Kasper,
+ author = {Kasper, Timo and Michael Silbermann and Christof Paar},
+ year = {2010},
+ title = {All you can eat or breaking a real-world contactless payment system},
+ journal = {Financial Cryptography and Data Security, Lecture Notes in Computer Science},
+ volume = {6052},
+ pages = {343--50},
+}
+
+@inproceedings{Katzenbeisser,
+ author = {Katzenbeisser, Stefan and Ünal Kocabaş and Vladimir Rožić and Ahmad-Reza Sadeghi and Ingrid Verbauwhede and Christian Wachsmann},
+ year = {2012},
+ title = {{PUF}s: Myth, Fact or Busted? A Security Evaluation of Physically Unclonable Functions ({PUF}s) Cast in Silicon},
+ booktitle = {Cryptographic Hardware and Embedded Systems -- CHES 2012. Lecture Notes in Computer Science},
+ volume = {7428},
+ pages = {283--301},
+}
+
+@book{Keynes,
+ author = {Keynes, John Maynard},
+ year = {1936},
+ title = {The General Theory of Employment, Interest and Money},
+ publisher = {Macmillan},
+}
+
+@article{Kiff,
+ author = {Kiff, John and Jihad Alwazir and Sonja Davidovic and Aquiles Farias and Ashraf Khan and Tanai Khiaonarong and Majid Malaika and Hunter Monroe and Nobu Sugimoto and Hervé Tourpe and Peter Zhou},
+ year = {2020},
+ title = {A Survey of Research on Retail Central Bank Digital Currency},
+ journal = {IMF Working Paper},
+ volume = {20/104},
+}
+
+@InCollection{Kumhof,
+ author = {Kumhof, Michael and Clare Noone},
+ year = {2018},
+ title = {Central bank digital currencies - design principles and balance sheet implications},
+ publisher = {Bank of England},
+ series = {Staff Working Paper},
+ number = {725},
+}
+
+@inproceedings{Lapid,
+ author = {Lapid, Ben and Avishai Wool},
+ year = {2018},
+ title = {Cache-Attacks on the {ARM} TrustZone Implementations of {AES}-256 and {AES}-256-{GCM} via {GPU}-Based Analysis},
+ booktitle = {International Conference on Selected Areas in Cryptography. Lecture Notes in Computer Science},
+ volume = {11349},
+}
+
+@article{Lerner,
+ author = {Lerner, Josh and Jean Tirole},
+ year = {2005},
+ title = {The Scope of Open Source Licensing},
+ journal = {Journal of Law, Economics \& Organization},
+ volume = {21},
+ pages = {20-56},
+}
+
+@misc{Libra,
+ author = {{Libra Association}},
+ year = {2020},
+ title = {Libra White Paper v2.0},
+ url = {\url{https://libra.org/en-US/white-paper}},
+}
+
+@inproceedings{Lim,
+ author = {Lim, Chae Hoon and Phil Joong Lee},
+ year = {1997},
+ title = {A key recovery attack on discrete log-based schemes using a prime order subgroup},
+ booktitle = {CRYPTO 1997. Lecture Notes in Computer Science},
+ volume = {1294},
+}
+
+@InCollection{Lyons,
+ author = {Lyons, Richard K. and Ganesh Viswanath-Natraj},
+ year = {2020},
+ title = {What Keeps Stablecoins Stable?},
+ publisher = {National Bureau of Economic Research},
+ series = {NBER Working Paper Series},
+ number = {27136},
+ month = {May},
+}
+
+@article{Mancini-Griffoli,
+ author = {Mancini-Griffoli, Tommaso and Maria Soledad Martinez Peria and Itai Agur and Anil Ari and John Kiff and Adina Popescu and Celine Rochon},
+ year = {2018},
+ title = {Casting Light on Central Bank Digital Currency},
+ journal = {IMF Staff Discussion Notes},
+ volume = {18/08},
+ publisher = {International Monetary Fund},
+}
+
+@misc{Nakamoto,
+ author = {Nakamoto, Satoshi},
+ year = {2008},
+ title = {Bitcoin: A Peer-to-Peer Electronic Cash System},
+ url = {\url{https://www.bitcoin.com/bitcoin.pdf}},
+}
+
+@book{Narayanan,
+ author = {Narayanan, Arvind and Joseph Bonneau and Edward Felten and Andrew Miller and Steven Goldfeder},
+ year = {2016},
+ title = {Bitcoin and Cryptocurrency Technologies: A Comprehensive Introduction},
+ publisher = {Princeton University Press},
+}
+
+@misc{Niepelt,
+ author = {Niepelt, Dirk},
+ year = {2020},
+ title = {Digital money and central bank digital currency: An executive summary for policymakers},
+ url = {https://voxeu.org/article/digital-money-and-central-bank-digital-currency-executive-summary},
+}
+
+@inproceedings{Okamoto,
+ author = {Okamoto, Tatsuaki},
+ year = {1995},
+ title = {An Efficient Divisible Electronic Cash Scheme},
+ booktitle = {Advances in Cryptology --- CRYPT0'95: 15th Annual International Cryptology Conference Santa Barbara, California, USA, August 27--31, 1995 Proceedings},
+ editor = {Ed. by Don Coppersmith},
+ publisher = {Springer-Verlag Berlin Heidelberg},
+ pages = {438--451},
+}
+
+@article{Pinto,
+ author = {Pinto, S. and N. Santos},
+ year = {2019},
+ title = {Demystifying {ARM} TrustZone: A Comprehensive Survey},
+ journal = {ACM Computing Surveys},
+ volume = {51},
+ number = {6},
+ month = {January},
+ pages = {1--31}
+}
+
+@article{Rivest,
+ author = {Rivest, Ronald L. and Adi Shamir and Leonard Adleman},
+ year = {1978},
+ title = {A Method for Obtaining Digital Signatures and Public Key Cryptosystems},
+ journal = {Comm. ACM},
+ volume = {21},
+ number = {2},
+}
+
+@book{Solove,
+ author = {Solove, Daniel J.},
+ year = {2011},
+ title = {Nothing to Hide: The false tradeoff between privacy and security},
+ publisher = {New Haven \& London: Yale University Press},
+}
+
+@article{Soukup,
+ author = {Soukup, Michael and Bruno Muff},
+ year = {2007},
+ title = {Die {P}ostcard lässt sich fälschen},
+ journal = {Sonntagszeitung},
+ month = {April},
+ day = {22},
+}
+
+@article{Stallman,
+ author = {Stallman, Richard},
+ year = {1985},
+ title = {The {GNU} manifesto},
+ journal = {Dr. Dobb's Journal of Software Tools},
+ volume = {10},
+ number = {3},
+ pages = {30--35},
+}
+
+
+@TechReport{Riksbank,
+ author = {{Sveriges Riksbank}},
+ year = {2020},
+ title = {The {R}iksbank's e-krona project},
+ month = {Feb},
+ institution = {Sveriges Riksbank},
+ url = {\url{https://www.riksbank.se/globalassets/media/rapporter/e-krona/2019/the-riksbanks-e-krona-pilot.pdf}},
+}
+
+@misc{Wojtczuk,
+ author = {Wojtczuk, Rafal and Joanna Rutkowska},
+ year = {2009},
+ title = {Attacking {I}ntel Trusted Execution Technology},
+ howpublished = {BlackHat-DC 2009},
+}
+
+@article{Yalta2010,
+ author = {Yalta, A. Talha and A. Yasemin Yalta},
+ year = {2010},
+ title = {Should Economists Use Open Source Software for Doing Research?},
+ journal = {Computational Economics},
+ volume = {35},
+ pages = {371--394},
+}
+
+@article{Yalta2008,
+ author = {Yalta, A. Talha and Riccardo Lucchetti},
+ year = {2008},
+ title = {The {GNU/L}inux Platform and Freedom Respecting Software for Economists},
+ journal = {Journal of Applied Econometrics},
+ volume = {23},
+ pages = {279-286},
+}
diff --git a/doc/cbdc-es/deposito.pdf b/doc/cbdc-es/deposito.pdf
new file mode 100644
index 000000000..b798478d1
--- /dev/null
+++ b/doc/cbdc-es/deposito.pdf
Binary files differ
diff --git a/doc/cbdc-es/eshyphexh.tex b/doc/cbdc-es/eshyphexh.tex
new file mode 100644
index 000000000..4d4efe024
--- /dev/null
+++ b/doc/cbdc-es/eshyphexh.tex
@@ -0,0 +1,1367 @@
+% Copyright (C) 2019 Javier Bezos, CervanTeX
+% Hyphenation exceptions for Spanish related to the h.
+%
+% Las recientes normas de las Academias de la Lengua establecen la
+% prohibición de partir delante de una hache, salvo cuando se trata de
+% un prefijo productivo. En la practica editorial, dado que la
+% aplicación de este norma puede acarrear dificultades en el ajuste de
+% líneas, se siguen practicando las normas tradicionales, más flexibles.
+% Los nuevos patrones para el español (v. 5.0) se adaptan a lo
+% establecido por las Academias, por lo que se suministra como
+% complemento este archivo de excepciones con los casos más habituales
+% de división ante hache.
+%
+% licence:
+% name: MIT/X11
+% url: https://opensource.org/licenses/MIT
+% text: >
+% Permission is hereby granted, free of charge, to any person obtaining a copy
+% of this software and associated documentation files (the "Software"), to deal
+% in the Software without restriction, including without limitation the rights
+% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+% copies of the Software, and to permit persons to whom the Software is
+% furnished to do so, subject to the following conditions:
+%
+% The above copyright notice and this permission notice shall be included in
+% all copies or substantial portions of the Software.
+%
+% THE SOFTWARE IS PROVIDED “AS ISâ€, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+% SOFTWARE.
+% ==========================================
+
+\hyphenation{
+ad-he-re-cer
+ad-he-ren-cia
+ad-he-ren-cias
+ad-he-ren-te
+ad-he-ren-tes
+ad-he-ri-da
+ad-he-ri-das
+ad-he-ri-do
+ad-he-ri-dos
+ad-he-ri-mos
+ad-he-ri-re-mos
+ad-he-ri-ros
+ad-he-ri-rá
+ad-he-ri-rán
+ad-he-ri-rás
+ad-he-ri-ré
+ad-he-ri-réis
+ad-he-ri-ría
+ad-he-ri-ría-mos
+ad-he-ri-ríais
+ad-he-ri-rían
+ad-he-ri-rías
+ad-he-rid
+ad-he-rir
+ad-he-rir-la
+ad-he-rir-las
+ad-he-rir-le
+ad-he-rir-les
+ad-he-rir-lo
+ad-he-rir-los
+ad-he-rir-me
+ad-he-rir-nos
+ad-he-rir-se
+ad-he-rir-te
+ad-he-ris-te
+ad-he-ris-teis
+ad-he-rí
+ad-he-rí-ros-la
+ad-he-rí-ros-las
+ad-he-rí-ros-le
+ad-he-rí-ros-les
+ad-he-rí-ros-lo
+ad-he-rí-ros-los
+ad-he-ría
+ad-he-ría-mos
+ad-he-ríais
+ad-he-rían
+ad-he-rías
+ad-he-rír-me-la
+ad-he-rír-me-las
+ad-he-rír-me-le
+ad-he-rír-me-les
+ad-he-rír-me-lo
+ad-he-rír-me-los
+ad-he-rír-nos-la
+ad-he-rír-nos-las
+ad-he-rír-nos-le
+ad-he-rír-nos-les
+ad-he-rír-nos-lo
+ad-he-rír-nos-los
+ad-he-rír-se-la
+ad-he-rír-se-las
+ad-he-rír-se-le
+ad-he-rír-se-les
+ad-he-rír-se-lo
+ad-he-rír-se-los
+ad-he-rír-te-la
+ad-he-rír-te-las
+ad-he-rír-te-le
+ad-he-rír-te-les
+ad-he-rír-te-lo
+ad-he-rír-te-los
+ad-he-rís
+ad-he-si-va
+ad-he-si-vas
+ad-he-si-vi-da-des
+ad-he-si-vi-dad
+ad-he-si-vo
+ad-he-si-vos
+ad-he-sio-nes
+ad-he-sión
+ad-hi-ra-mos
+ad-hi-rie-ra
+ad-hi-rie-rais
+ad-hi-rie-ran
+ad-hi-rie-ras
+ad-hi-rie-re
+ad-hi-rie-reis
+ad-hi-rie-ren
+ad-hi-rie-res
+ad-hi-rie-ron
+ad-hi-rie-se
+ad-hi-rie-seis
+ad-hi-rie-sen
+ad-hi-rie-ses
+ad-hi-rien-do
+ad-hi-rié-ra-mos
+ad-hi-rié-re-mos
+ad-hi-rié-se-mos
+ad-hi-rién-do-la
+ad-hi-rién-do-las
+ad-hi-rién-do-le
+ad-hi-rién-do-les
+ad-hi-rién-do-lo
+ad-hi-rién-do-los
+ad-hi-rién-do-me
+ad-hi-rién-do-nos
+ad-hi-rién-do-se
+ad-hi-rién-do-te
+ad-hi-rién-doos
+ad-hi-rió
+ad-hi-ráis
+ad-hie-ra
+ad-hie-ran
+ad-hie-ras
+ad-hie-re
+ad-hie-ren
+ad-hie-res
+ad-hie-ro
+ad-hor-tar
+al-ha-ce-na
+al-ha-ce-nas
+al-ha-di-da
+al-ha-di-das
+al-ha-ja
+al-ha-ja-ba
+al-ha-ja-bais
+al-ha-ja-ban
+al-ha-ja-bas
+al-ha-ja-da
+al-ha-ja-das
+al-ha-ja-do
+al-ha-ja-dos
+al-ha-ja-mos
+al-ha-ja-ra
+al-ha-ja-rais
+al-ha-ja-ran
+al-ha-ja-ras
+al-ha-ja-re
+al-ha-ja-re-mos
+al-ha-ja-reis
+al-ha-ja-ren
+al-ha-ja-res
+al-ha-ja-ron
+al-ha-ja-rá
+al-ha-ja-rán
+al-ha-ja-rás
+al-ha-ja-ré
+al-ha-ja-réis
+al-ha-ja-ría
+al-ha-ja-ría-mos
+al-ha-ja-ríais
+al-ha-ja-rían
+al-ha-ja-rías
+al-ha-ja-se
+al-ha-ja-seis
+al-ha-ja-sen
+al-ha-ja-ses
+al-ha-jad
+al-ha-jan
+al-ha-jan-do
+al-ha-jar
+al-ha-jas
+al-ha-jas-te
+al-ha-jas-teis
+al-ha-je
+al-ha-je-mos
+al-ha-je-ra
+al-ha-je-ro
+al-ha-jen
+al-ha-jes
+al-ha-ji-ta
+al-ha-ji-to
+al-ha-jo
+al-ha-jue-la
+al-ha-jue-las
+al-ha-já-ba-mos
+al-ha-já-ra-mos
+al-ha-já-re-mos
+al-ha-já-se-mos
+al-ha-jáis
+al-ha-jé
+al-ha-jéis
+al-ha-jó
+al-ha-jú
+al-ha-mar
+al-ha-me-les
+al-ha-mel
+al-ha-mí
+al-ha-nía
+al-ha-quín
+al-ha-ra-ca
+al-ha-ra-cas
+al-ha-ra-que-ro
+al-ha-ra-quien-ta
+al-ha-ra-quien-tas
+al-ha-ra-quien-to
+al-ha-ra-quien-tos
+al-ha-re-me
+al-ha-va-ra
+al-hai-te
+al-ham-bra
+al-ham-bri-lla
+al-han-da-les
+al-han-dal
+al-har-ma
+al-har-mas
+al-he-lí
+al-he-ña
+al-he-ña-ba
+al-he-ña-bais
+al-he-ña-ban
+al-he-ña-bas
+al-he-ña-da
+al-he-ña-das
+al-he-ña-do
+al-he-ña-dos
+al-he-ña-mos
+al-he-ña-ra
+al-he-ña-rais
+al-he-ña-ran
+al-he-ña-ras
+al-he-ña-re
+al-he-ña-re-mos
+al-he-ña-reis
+al-he-ña-ren
+al-he-ña-res
+al-he-ña-ron
+al-he-ña-rá
+al-he-ña-rán
+al-he-ña-rás
+al-he-ña-ré
+al-he-ña-réis
+al-he-ña-ría
+al-he-ña-ría-mos
+al-he-ña-ríais
+al-he-ña-rían
+al-he-ña-rías
+al-he-ña-se
+al-he-ña-seis
+al-he-ña-sen
+al-he-ña-ses
+al-he-ñad
+al-he-ñan
+al-he-ñan-do
+al-he-ñar
+al-he-ñar-se
+al-he-ñas
+al-he-ñas-te
+al-he-ñas-teis
+al-he-ñe
+al-he-ñe-mos
+al-he-ñen
+al-he-ñes
+al-he-ño
+al-he-ñá-ba-mos
+al-he-ñá-ra-mos
+al-he-ñá-re-mos
+al-he-ñá-se-mos
+al-he-ñáis
+al-he-ñé
+al-he-ñéis
+al-he-ñó
+al-hen-dal
+al-hi-da-da
+al-ho-ja
+al-ho-lí
+al-ho-lía
+al-ho-rra
+al-ho-rre
+al-ho-rro
+al-ho-rría
+al-ho-rí
+al-ho-rín
+al-hol-va
+al-hol-var
+al-hol-vas
+al-hom-bra
+al-hom-brar
+al-hon-di-gue-ro
+al-hor-za
+al-hoz
+al-hu-ce-ma
+al-hu-ce-mas
+al-hu-ce-mi-lla
+al-hu-ce-mi-llas
+al-hu-ce-ña
+al-hu-ma-jo
+al-hu-ma-jos
+al-hu-rre-ca
+al-hu-rre-cas
+al-há-be-ga
+al-há-me-ga
+al-hár-ga-ma
+al-hó-ci-go
+al-hón-di-ga
+al-hón-di-gas
+an-he-do-nia
+an-he-la
+an-he-la-ba
+an-he-la-bais
+an-he-la-ban
+an-he-la-bas
+an-he-la-cio-nes
+an-he-la-ción
+an-he-la-da
+an-he-la-das
+an-he-la-do
+an-he-la-dos
+an-he-la-mos
+an-he-la-ra
+an-he-la-rais
+an-he-la-ran
+an-he-la-ras
+an-he-la-re
+an-he-la-re-mos
+an-he-la-reis
+an-he-la-ren
+an-he-la-res
+an-he-la-ron
+an-he-la-rá
+an-he-la-rán
+an-he-la-rás
+an-he-la-ré
+an-he-la-réis
+an-he-la-ría
+an-he-la-ría-mos
+an-he-la-ríais
+an-he-la-rían
+an-he-la-rías
+an-he-la-se
+an-he-la-seis
+an-he-la-sen
+an-he-la-ses
+an-he-lad
+an-he-lan
+an-he-lan-do
+an-he-lan-te
+an-he-lan-te
+an-he-lan-tes
+an-he-lan-tes
+an-he-lar
+an-he-las
+an-he-las-te
+an-he-las-teis
+an-he-le
+an-he-le-mos
+an-he-len
+an-he-les
+an-he-li-to
+an-he-lo
+an-he-lo-sa
+an-he-lo-sa-men-te
+an-he-lo-sas
+an-he-lo-so
+an-he-lo-sos
+an-he-los
+an-he-lá-ba-mos
+an-he-lá-ra-mos
+an-he-lá-re-mos
+an-he-lá-se-mos
+an-he-láis
+an-he-lé
+an-he-léis
+an-he-ló
+an-hi-dra
+an-hi-dra-sa
+an-hi-dras
+an-hi-dri-do
+an-hi-dri-ta
+an-hi-dri-tas
+an-hi-dro
+an-hi-dro-sis
+an-hi-dros
+an-hé-li-to
+an-hé-li-tos
+an-hí-dri-da
+an-hí-dri-das
+an-hí-dri-do
+an-hí-dri-dos
+apre-hen-da
+apre-hen-da-mos
+apre-hen-dan
+apre-hen-das
+apre-hen-de
+apre-hen-de-mos
+apre-hen-de-re-mos
+apre-hen-de-rá
+apre-hen-de-rán
+apre-hen-de-rás
+apre-hen-de-ré
+apre-hen-de-réis
+apre-hen-de-ría
+apre-hen-de-ría-mos
+apre-hen-de-ríais
+apre-hen-de-rían
+apre-hen-de-rías
+apre-hen-ded
+apre-hen-den
+apre-hen-der
+apre-hen-der-la
+apre-hen-der-las
+apre-hen-der-le
+apre-hen-der-les
+apre-hen-der-lo
+apre-hen-der-los
+apre-hen-des
+apre-hen-di-da
+apre-hen-di-das
+apre-hen-di-do
+apre-hen-di-dos
+apre-hen-di-mos
+apre-hen-die-ra
+apre-hen-die-rais
+apre-hen-die-ran
+apre-hen-die-ras
+apre-hen-die-re
+apre-hen-die-reis
+apre-hen-die-ren
+apre-hen-die-res
+apre-hen-die-ron
+apre-hen-die-se
+apre-hen-die-seis
+apre-hen-die-sen
+apre-hen-die-ses
+apre-hen-dien-do
+apre-hen-dien-te
+apre-hen-dien-tes
+apre-hen-dis-te
+apre-hen-dis-teis
+apre-hen-dié-ra-mos
+apre-hen-dié-re-mos
+apre-hen-dié-se-mos
+apre-hen-dién-do-la
+apre-hen-dién-do-las
+apre-hen-dién-do-le
+apre-hen-dién-do-les
+apre-hen-dién-do-lo
+apre-hen-dién-do-los
+apre-hen-dió
+apre-hen-do
+apre-hen-dáis
+apre-hen-déis
+apre-hen-dí
+apre-hen-día
+apre-hen-día-mos
+apre-hen-díais
+apre-hen-dían
+apre-hen-días
+apre-hen-si-va
+apre-hen-si-vas
+apre-hen-si-vo
+apre-hen-si-vos
+apre-hen-sio-nes
+apre-hen-sión
+apre-hen-so-ra
+apre-hen-so-ras
+apre-hen-so-res
+apre-hen-so-rio
+apre-hen-sor
+ara-hua-co
+chi-hua-hua
+chi-hua-huas
+chi-hua-hue-ño
+chi-hua-huen-se
+co-he-ta-zo
+co-he-te
+co-he-te-ra
+co-he-te-ras
+co-he-te-ro
+co-he-te-ros
+co-he-te-ría
+co-he-tes
+co-hi-ba-mos
+co-hi-bi-cio-nes
+co-hi-bi-ción
+co-hi-bi-da
+co-hi-bi-das
+co-hi-bi-do
+co-hi-bi-dos
+co-hi-bi-mos
+co-hi-bi-ros
+co-hi-bid
+co-hi-bie-ra
+co-hi-bie-rais
+co-hi-bie-ran
+co-hi-bie-ras
+co-hi-bie-re
+co-hi-bie-reis
+co-hi-bie-ren
+co-hi-bie-res
+co-hi-bie-ron
+co-hi-bie-se
+co-hi-bie-seis
+co-hi-bie-sen
+co-hi-bie-ses
+co-hi-bien-do
+co-hi-bir
+co-hi-bir-la
+co-hi-bir-las
+co-hi-bir-le
+co-hi-bir-les
+co-hi-bir-lo
+co-hi-bir-los
+co-hi-bir-me
+co-hi-bir-nos
+co-hi-bir-se
+co-hi-bir-te
+co-hi-bié-ra-mos
+co-hi-bié-re-mos
+co-hi-bié-se-mos
+co-hi-bién-do-la
+co-hi-bién-do-las
+co-hi-bién-do-le
+co-hi-bién-do-les
+co-hi-bién-do-lo
+co-hi-bién-do-los
+co-hi-bién-do-me
+co-hi-bién-do-nos
+co-hi-bién-do-se
+co-hi-bién-do-te
+co-hi-bién-doos
+co-hi-bió
+co-hi-báis
+co-hi-bía
+co-hi-bía-mos
+co-hi-bíais
+co-hi-bían
+co-hi-bías
+co-hi-bís
+dír-ham
+dír-hem
+ex-ha-la
+ex-ha-la-ba
+ex-ha-la-bais
+ex-ha-la-ban
+ex-ha-la-bas
+ex-ha-la-cio-nes
+ex-ha-la-ción
+ex-ha-la-da
+ex-ha-la-das
+ex-ha-la-do
+ex-ha-la-do-ra
+ex-ha-la-do-ras
+ex-ha-la-do-res
+ex-ha-la-dor
+ex-ha-la-dos
+ex-ha-la-mos
+ex-ha-la-ra
+ex-ha-la-rais
+ex-ha-la-ran
+ex-ha-la-ras
+ex-ha-la-re
+ex-ha-la-re-mos
+ex-ha-la-reis
+ex-ha-la-ren
+ex-ha-la-res
+ex-ha-la-ron
+ex-ha-la-ros
+ex-ha-la-rá
+ex-ha-la-rán
+ex-ha-la-rás
+ex-ha-la-ré
+ex-ha-la-réis
+ex-ha-la-ría
+ex-ha-la-ría-mos
+ex-ha-la-ríais
+ex-ha-la-rían
+ex-ha-la-rías
+ex-ha-la-se
+ex-ha-la-seis
+ex-ha-la-sen
+ex-ha-la-ses
+ex-ha-lad
+ex-ha-lan
+ex-ha-lan-do
+ex-ha-lan-te
+ex-ha-lar
+ex-ha-lar-la
+ex-ha-lar-las
+ex-ha-lar-le
+ex-ha-lar-les
+ex-ha-lar-lo
+ex-ha-lar-los
+ex-ha-lar-me
+ex-ha-lar-nos
+ex-ha-lar-se
+ex-ha-lar-te
+ex-ha-las
+ex-ha-las-te
+ex-ha-las-teis
+ex-ha-le
+ex-ha-le-mos
+ex-ha-len
+ex-ha-les
+ex-ha-lo
+ex-ha-lá-ba-mos
+ex-ha-lá-ra-mos
+ex-ha-lá-re-mos
+ex-ha-lá-ros-la
+ex-ha-lá-ros-las
+ex-ha-lá-ros-lo
+ex-ha-lá-ros-los
+ex-ha-lá-se-mos
+ex-ha-láis
+ex-ha-lán-do-la
+ex-ha-lán-do-las
+ex-ha-lán-do-le
+ex-ha-lán-do-les
+ex-ha-lán-do-lo
+ex-ha-lán-do-los
+ex-ha-lán-do-me
+ex-ha-lán-do-me-la
+ex-ha-lán-do-me-las
+ex-ha-lán-do-me-lo
+ex-ha-lán-do-me-los
+ex-ha-lán-do-nos
+ex-ha-lán-do-nos-la
+ex-ha-lán-do-nos-las
+ex-ha-lán-do-nos-lo
+ex-ha-lán-do-nos-los
+ex-ha-lán-do-se
+ex-ha-lán-do-se-la
+ex-ha-lán-do-se-las
+ex-ha-lán-do-se-lo
+ex-ha-lán-do-se-los
+ex-ha-lán-do-te
+ex-ha-lán-do-te-la
+ex-ha-lán-do-te-las
+ex-ha-lán-do-te-lo
+ex-ha-lán-do-te-los
+ex-ha-lán-doos
+ex-ha-lán-doos-la
+ex-ha-lán-doos-las
+ex-ha-lán-doos-lo
+ex-ha-lán-doos-los
+ex-ha-lár-me-la
+ex-ha-lár-me-las
+ex-ha-lár-me-lo
+ex-ha-lár-me-los
+ex-ha-lár-nos-la
+ex-ha-lár-nos-las
+ex-ha-lár-nos-lo
+ex-ha-lár-nos-los
+ex-ha-lár-se-la
+ex-ha-lár-se-las
+ex-ha-lár-se-lo
+ex-ha-lár-se-los
+ex-ha-lár-te-la
+ex-ha-lár-te-las
+ex-ha-lár-te-lo
+ex-ha-lár-te-los
+ex-ha-lé
+ex-ha-léis
+ex-ha-ló
+ex-haus-ta
+ex-haus-ta-ción
+ex-haus-tas
+ex-haus-ti-va
+ex-haus-ti-va-men-te
+ex-haus-ti-vas
+ex-haus-ti-vi-dad
+ex-haus-ti-vo
+ex-haus-ti-vos
+ex-haus-to
+ex-haus-tos
+ex-hi-ba
+ex-hi-ba-mos
+ex-hi-ban
+ex-hi-bas
+ex-hi-be
+ex-hi-ben
+ex-hi-bes
+ex-hi-bi-cio-nes
+ex-hi-bi-cio-nis-mo
+ex-hi-bi-cio-nis-mos
+ex-hi-bi-cio-nis-ta
+ex-hi-bi-cio-nis-tas
+ex-hi-bi-ción
+ex-hi-bi-da
+ex-hi-bi-das
+ex-hi-bi-do
+ex-hi-bi-dor
+ex-hi-bi-dos
+ex-hi-bi-mos
+ex-hi-bi-re-mos
+ex-hi-bi-ros
+ex-hi-bi-rá
+ex-hi-bi-rán
+ex-hi-bi-rás
+ex-hi-bi-ré
+ex-hi-bi-réis
+ex-hi-bi-ría
+ex-hi-bi-ría-mos
+ex-hi-bi-ríais
+ex-hi-bi-rían
+ex-hi-bi-rías
+ex-hi-bid
+ex-hi-bie-ra
+ex-hi-bie-rais
+ex-hi-bie-ran
+ex-hi-bie-ras
+ex-hi-bie-re
+ex-hi-bie-reis
+ex-hi-bie-ren
+ex-hi-bie-res
+ex-hi-bie-ron
+ex-hi-bie-se
+ex-hi-bie-seis
+ex-hi-bie-sen
+ex-hi-bie-ses
+ex-hi-bien-do
+ex-hi-bir
+ex-hi-bir-la
+ex-hi-bir-las
+ex-hi-bir-le
+ex-hi-bir-les
+ex-hi-bir-lo
+ex-hi-bir-los
+ex-hi-bir-me
+ex-hi-bir-nos
+ex-hi-bir-se
+ex-hi-bir-te
+ex-hi-bis-te
+ex-hi-bis-teis
+ex-hi-bié-ra-mos
+ex-hi-bié-re-mos
+ex-hi-bié-se-mos
+ex-hi-bién-do-la
+ex-hi-bién-do-las
+ex-hi-bién-do-le
+ex-hi-bién-do-les
+ex-hi-bién-do-lo
+ex-hi-bién-do-los
+ex-hi-bién-do-me
+ex-hi-bién-do-me-la
+ex-hi-bién-do-me-las
+ex-hi-bién-do-me-lo
+ex-hi-bién-do-me-los
+ex-hi-bién-do-nos
+ex-hi-bién-do-nos-la
+ex-hi-bién-do-nos-las
+ex-hi-bién-do-nos-lo
+ex-hi-bién-do-nos-los
+ex-hi-bién-do-se
+ex-hi-bién-do-se-la
+ex-hi-bién-do-se-las
+ex-hi-bién-do-se-lo
+ex-hi-bién-do-se-los
+ex-hi-bién-do-te
+ex-hi-bién-do-te-la
+ex-hi-bién-do-te-las
+ex-hi-bién-do-te-lo
+ex-hi-bién-do-te-los
+ex-hi-bién-doos
+ex-hi-bién-doos-la
+ex-hi-bién-doos-las
+ex-hi-bién-doos-lo
+ex-hi-bién-doos-los
+ex-hi-bió
+ex-hi-bo
+ex-hi-báis
+ex-hi-bí
+ex-hi-bí-ros-la
+ex-hi-bí-ros-las
+ex-hi-bí-ros-lo
+ex-hi-bí-ros-los
+ex-hi-bía
+ex-hi-bía-mos
+ex-hi-bíais
+ex-hi-bían
+ex-hi-bías
+ex-hi-bír-me-la
+ex-hi-bír-me-las
+ex-hi-bír-me-lo
+ex-hi-bír-me-los
+ex-hi-bír-nos-la
+ex-hi-bír-nos-las
+ex-hi-bír-nos-lo
+ex-hi-bír-nos-los
+ex-hi-bír-se-la
+ex-hi-bír-se-las
+ex-hi-bír-se-lo
+ex-hi-bír-se-los
+ex-hi-bír-te-la
+ex-hi-bír-te-las
+ex-hi-bír-te-lo
+ex-hi-bír-te-los
+ex-hi-bís
+ex-hor-ta
+ex-hor-ta-ba
+ex-hor-ta-bais
+ex-hor-ta-ban
+ex-hor-ta-bas
+ex-hor-ta-cio-nes
+ex-hor-ta-ción
+ex-hor-ta-da
+ex-hor-ta-das
+ex-hor-ta-do
+ex-hor-ta-do-ra
+ex-hor-ta-do-ras
+ex-hor-ta-do-res
+ex-hor-ta-dor
+ex-hor-ta-dos
+ex-hor-ta-mos
+ex-hor-ta-ra
+ex-hor-ta-rais
+ex-hor-ta-ran
+ex-hor-ta-ras
+ex-hor-ta-re
+ex-hor-ta-re-mos
+ex-hor-ta-reis
+ex-hor-ta-ren
+ex-hor-ta-res
+ex-hor-ta-ron
+ex-hor-ta-ros
+ex-hor-ta-rá
+ex-hor-ta-rán
+ex-hor-ta-rás
+ex-hor-ta-ré
+ex-hor-ta-réis
+ex-hor-ta-ría
+ex-hor-ta-ría-mos
+ex-hor-ta-ríais
+ex-hor-ta-rían
+ex-hor-ta-rías
+ex-hor-ta-se
+ex-hor-ta-seis
+ex-hor-ta-sen
+ex-hor-ta-ses
+ex-hor-ta-ti-va
+ex-hor-ta-ti-va-men-te
+ex-hor-ta-ti-vas
+ex-hor-ta-ti-vo
+ex-hor-ta-ti-vos
+ex-hor-ta-to-ria
+ex-hor-ta-to-rias
+ex-hor-ta-to-rio
+ex-hor-ta-to-rios
+ex-hor-tad
+ex-hor-tan
+ex-hor-tan-do
+ex-hor-tar
+ex-hor-tar-la
+ex-hor-tar-las
+ex-hor-tar-le
+ex-hor-tar-les
+ex-hor-tar-lo
+ex-hor-tar-los
+ex-hor-tar-me
+ex-hor-tar-nos
+ex-hor-tar-se
+ex-hor-tar-te
+ex-hor-tas
+ex-hor-tas-te
+ex-hor-tas-teis
+ex-hor-te
+ex-hor-te-mos
+ex-hor-ten
+ex-hor-tes
+ex-hor-to
+ex-hor-tos
+ex-hor-tá-ba-mos
+ex-hor-tá-ra-mos
+ex-hor-tá-re-mos
+ex-hor-tá-ros-la
+ex-hor-tá-ros-las
+ex-hor-tá-ros-lo
+ex-hor-tá-ros-los
+ex-hor-tá-se-mos
+ex-hor-táis
+ex-hor-tán-do-la
+ex-hor-tán-do-las
+ex-hor-tán-do-le
+ex-hor-tán-do-les
+ex-hor-tán-do-lo
+ex-hor-tán-do-los
+ex-hor-tán-do-me
+ex-hor-tán-do-me-la
+ex-hor-tán-do-me-las
+ex-hor-tán-do-me-lo
+ex-hor-tán-do-me-los
+ex-hor-tán-do-nos
+ex-hor-tán-do-nos-la
+ex-hor-tán-do-nos-las
+ex-hor-tán-do-nos-lo
+ex-hor-tán-do-nos-los
+ex-hor-tán-do-se
+ex-hor-tán-do-se-la
+ex-hor-tán-do-se-las
+ex-hor-tán-do-se-lo
+ex-hor-tán-do-se-los
+ex-hor-tán-do-te
+ex-hor-tán-do-te-la
+ex-hor-tán-do-te-las
+ex-hor-tán-do-te-lo
+ex-hor-tán-do-te-los
+ex-hor-tán-doos
+ex-hor-tán-doos-la
+ex-hor-tán-doos-las
+ex-hor-tán-doos-lo
+ex-hor-tán-doos-los
+ex-hor-tár-me-la
+ex-hor-tár-me-las
+ex-hor-tár-me-lo
+ex-hor-tár-me-los
+ex-hor-tár-nos-la
+ex-hor-tár-nos-las
+ex-hor-tár-nos-lo
+ex-hor-tár-nos-los
+ex-hor-tár-se-la
+ex-hor-tár-se-las
+ex-hor-tár-se-lo
+ex-hor-tár-se-los
+ex-hor-tár-te-la
+ex-hor-tár-te-las
+ex-hor-tár-te-lo
+ex-hor-tár-te-los
+ex-hor-té
+ex-hor-téis
+ex-hor-tó
+ex-hu-ma
+ex-hu-ma-ba
+ex-hu-ma-bais
+ex-hu-ma-ban
+ex-hu-ma-bas
+ex-hu-ma-cio-nes
+ex-hu-ma-ción
+ex-hu-ma-da
+ex-hu-ma-das
+ex-hu-ma-do
+ex-hu-ma-do-ra
+ex-hu-ma-do-ras
+ex-hu-ma-do-res
+ex-hu-ma-dor
+ex-hu-ma-dos
+ex-hu-ma-mos
+ex-hu-ma-ra
+ex-hu-ma-rais
+ex-hu-ma-ran
+ex-hu-ma-ras
+ex-hu-ma-re
+ex-hu-ma-re-mos
+ex-hu-ma-reis
+ex-hu-ma-ren
+ex-hu-ma-res
+ex-hu-ma-ron
+ex-hu-ma-ros
+ex-hu-ma-rá
+ex-hu-ma-rán
+ex-hu-ma-rás
+ex-hu-ma-ré
+ex-hu-ma-réis
+ex-hu-ma-ría
+ex-hu-ma-ría-mos
+ex-hu-ma-ríais
+ex-hu-ma-rían
+ex-hu-ma-rías
+ex-hu-ma-se
+ex-hu-ma-seis
+ex-hu-ma-sen
+ex-hu-ma-ses
+ex-hu-mad
+ex-hu-man
+ex-hu-man-do
+ex-hu-mar
+ex-hu-mar-la
+ex-hu-mar-las
+ex-hu-mar-le
+ex-hu-mar-les
+ex-hu-mar-lo
+ex-hu-mar-los
+ex-hu-mar-me
+ex-hu-mar-nos
+ex-hu-mar-se
+ex-hu-mar-te
+ex-hu-mas
+ex-hu-mas-te
+ex-hu-mas-teis
+ex-hu-me
+ex-hu-me-mos
+ex-hu-men
+ex-hu-mes
+ex-hu-mo
+ex-hu-má-ba-mos
+ex-hu-má-ra-mos
+ex-hu-má-re-mos
+ex-hu-má-se-mos
+ex-hu-máis
+ex-hu-mán-do-la
+ex-hu-mán-do-las
+ex-hu-mán-do-le
+ex-hu-mán-do-les
+ex-hu-mán-do-lo
+ex-hu-mán-do-los
+ex-hu-mán-do-me
+ex-hu-mán-do-nos
+ex-hu-mán-do-se
+ex-hu-mán-do-te
+ex-hu-mán-doos
+ex-hu-mé
+ex-hu-méis
+ex-hu-mó
+fluor-hí-dri-co
+fluor-hí-dri-cos
+in-ha-la
+in-ha-la-ba
+in-ha-la-bais
+in-ha-la-ban
+in-ha-la-bas
+in-ha-la-ble
+in-ha-la-cio-nes
+in-ha-la-ción
+in-ha-la-da
+in-ha-la-das
+in-ha-la-do
+in-ha-la-do-ra
+in-ha-la-do-ras
+in-ha-la-do-res
+in-ha-la-dor
+in-ha-la-dos
+in-ha-la-mos
+in-ha-la-ra
+in-ha-la-rais
+in-ha-la-ran
+in-ha-la-ras
+in-ha-la-re
+in-ha-la-re-mos
+in-ha-la-reis
+in-ha-la-ren
+in-ha-la-res
+in-ha-la-ron
+in-ha-la-rá
+in-ha-la-rán
+in-ha-la-rás
+in-ha-la-ré
+in-ha-la-réis
+in-ha-la-ría
+in-ha-la-ría-mos
+in-ha-la-ríais
+in-ha-la-rían
+in-ha-la-rías
+in-ha-la-se
+in-ha-la-seis
+in-ha-la-sen
+in-ha-la-ses
+in-ha-lad
+in-ha-lan
+in-ha-lan-do
+in-ha-lan-te
+in-ha-lar
+in-ha-lar-la
+in-ha-lar-las
+in-ha-lar-le
+in-ha-lar-les
+in-ha-lar-lo
+in-ha-lar-los
+in-ha-las
+in-ha-las-te
+in-ha-las-teis
+in-ha-le
+in-ha-le-mos
+in-ha-len
+in-ha-les
+in-ha-lla-ble
+in-ha-lo
+in-ha-lá-ba-mos
+in-ha-lá-ra-mos
+in-ha-lá-re-mos
+in-ha-lá-se-mos
+in-ha-láis
+in-ha-lán-do-la
+in-ha-lán-do-las
+in-ha-lán-do-le
+in-ha-lán-do-les
+in-ha-lán-do-lo
+in-ha-lán-do-los
+in-ha-lé
+in-ha-léis
+in-ha-ló
+in-he-ren-te
+in-he-ren-te-men-te
+in-he-ren-tes
+in-he-sión
+in-hes-ta
+in-hes-ta-ba
+in-hes-ta-bais
+in-hes-ta-ban
+in-hes-ta-bas
+in-hes-ta-da
+in-hes-ta-das
+in-hes-ta-do
+in-hes-ta-dos
+in-hes-ta-mos
+in-hes-ta-ra
+in-hes-ta-rais
+in-hes-ta-ran
+in-hes-ta-ras
+in-hes-ta-re
+in-hes-ta-re-mos
+in-hes-ta-reis
+in-hes-ta-ren
+in-hes-ta-res
+in-hes-ta-ron
+in-hes-ta-rá
+in-hes-ta-rán
+in-hes-ta-rás
+in-hes-ta-ré
+in-hes-ta-réis
+in-hes-ta-ría
+in-hes-ta-ría-mos
+in-hes-ta-ríais
+in-hes-ta-rían
+in-hes-ta-rías
+in-hes-ta-se
+in-hes-ta-seis
+in-hes-ta-sen
+in-hes-ta-ses
+in-hes-tad
+in-hes-tan
+in-hes-tan-do
+in-hes-tar
+in-hes-tar-la
+in-hes-tar-las
+in-hes-tar-le
+in-hes-tar-les
+in-hes-tar-lo
+in-hes-tar-los
+in-hes-tas
+in-hes-tas-te
+in-hes-tas-teis
+in-hes-te
+in-hes-te-mos
+in-hes-ten
+in-hes-tes
+in-hes-to
+in-hes-tá-ba-mos
+in-hes-tá-ra-mos
+in-hes-tá-re-mos
+in-hes-tá-se-mos
+in-hes-táis
+in-hes-tán-do-la
+in-hes-tán-do-las
+in-hes-tán-do-le
+in-hes-tán-do-les
+in-hes-tán-do-lo
+in-hes-tán-do-los
+in-hes-té
+in-hes-téis
+in-hes-tó
+in-hi-ba
+in-hi-ba-mos
+in-hi-ban
+in-hi-bas
+in-hi-be
+in-hi-ben
+in-hi-bes
+in-hi-bi-cio-nes
+in-hi-bi-ción
+in-hi-bi-da
+in-hi-bi-das
+in-hi-bi-do
+in-hi-bi-do-ra
+in-hi-bi-do-ra
+in-hi-bi-do-ras
+in-hi-bi-dor
+in-hi-bi-dos
+in-hi-bi-mos
+in-hi-bi-re-mos
+in-hi-bi-ros
+in-hi-bi-rá
+in-hi-bi-rán
+in-hi-bi-rás
+in-hi-bi-ré
+in-hi-bi-réis
+in-hi-bi-ría
+in-hi-bi-ría-mos
+in-hi-bi-ríais
+in-hi-bi-rían
+in-hi-bi-rías
+in-hi-bi-to-ria
+in-hi-bi-to-rio
+in-hi-bid
+in-hi-bie-ra
+in-hi-bie-rais
+in-hi-bie-ran
+in-hi-bie-ras
+in-hi-bie-re
+in-hi-bie-reis
+in-hi-bie-ren
+in-hi-bie-res
+in-hi-bie-ron
+in-hi-bie-se
+in-hi-bie-seis
+in-hi-bie-sen
+in-hi-bie-ses
+in-hi-bien-do
+in-hi-bir
+in-hi-bir-la
+in-hi-bir-las
+in-hi-bir-le
+in-hi-bir-les
+in-hi-bir-lo
+in-hi-bir-los
+in-hi-bir-me
+in-hi-bir-nos
+in-hi-bir-se
+in-hi-bir-te
+in-hi-bis-te
+in-hi-bis-teis
+in-hi-bié-ra-mos
+in-hi-bié-re-mos
+in-hi-bié-se-mos
+in-hi-bién-do-la
+in-hi-bién-do-las
+in-hi-bién-do-le
+in-hi-bién-do-les
+in-hi-bién-do-lo
+in-hi-bién-do-los
+in-hi-bién-do-me
+in-hi-bién-do-nos
+in-hi-bién-do-se
+in-hi-bién-do-te
+in-hi-bién-doos
+in-hi-bió
+in-hi-bo
+in-hi-báis
+in-hi-bí
+in-hi-bía
+in-hi-bía-mos
+in-hi-bíais
+in-hi-bían
+in-hi-bías
+in-hi-bís
+in-hies-ta
+in-hies-tas
+in-hies-to
+in-hies-tos
+in-hu-ma
+in-hu-ma-ba
+in-hu-ma-bais
+in-hu-ma-ban
+in-hu-ma-bas
+in-hu-ma-cio-nes
+in-hu-ma-ción
+in-hu-ma-da
+in-hu-ma-das
+in-hu-ma-do
+in-hu-ma-dos
+in-hu-ma-mos
+in-hu-ma-ni-da-des
+in-hu-ma-ni-dad
+in-hu-ma-ra
+in-hu-ma-rais
+in-hu-ma-ran
+in-hu-ma-ras
+in-hu-ma-re
+in-hu-ma-re-mos
+in-hu-ma-reis
+in-hu-ma-ren
+in-hu-ma-res
+in-hu-ma-ron
+in-hu-ma-rá
+in-hu-ma-rán
+in-hu-ma-rás
+in-hu-ma-ré
+in-hu-ma-réis
+in-hu-ma-ría
+in-hu-ma-ría-mos
+in-hu-ma-ríais
+in-hu-ma-rían
+in-hu-ma-rías
+in-hu-ma-se
+in-hu-ma-seis
+in-hu-ma-sen
+in-hu-ma-ses
+in-hu-mad
+in-hu-man
+in-hu-man-do
+in-hu-mar
+in-hu-mar-la
+in-hu-mar-las
+in-hu-mar-le
+in-hu-mar-les
+in-hu-mar-lo
+in-hu-mar-los
+in-hu-mas
+in-hu-mas-te
+in-hu-mas-teis
+in-hu-me
+in-hu-me-mos
+in-hu-men
+in-hu-mes
+in-hu-mo
+in-hu-má-ba-mos
+in-hu-má-ra-mos
+in-hu-má-re-mos
+in-hu-má-se-mos
+in-hu-máis
+in-hu-mán-do-la
+in-hu-mán-do-las
+in-hu-mán-do-le
+in-hu-mán-do-les
+in-hu-mán-do-lo
+in-hu-mán-do-los
+in-hu-mé
+in-hu-méis
+in-hu-mó
+in-hós-pi-ta
+in-hós-pi-tas
+in-hós-pi-to
+in-hós-pi-tos
+ma-ri-hua-na
+ma-ri-hua-nas
+ma-ri-hua-ne-ro
+men-hi-res
+men-hir
+pa-ri-hue-la
+pa-ri-hue-las
+sa-bi-hon-da
+sa-bi-hon-das
+sa-bi-hon-de-ces
+sa-bi-hon-dez
+sa-bi-hon-do
+sa-bi-hon-dos
+sa-ha-raui
+sa-ha-ria-na
+sa-ha-ria-nas
+sa-ha-ria-nos
+sa-ha-riano} \ No newline at end of file
diff --git a/doc/cbdc-es/graphic-es.odp b/doc/cbdc-es/graphic-es.odp
new file mode 100644
index 000000000..818c2b185
--- /dev/null
+++ b/doc/cbdc-es/graphic-es.odp
Binary files differ
diff --git a/doc/cbdc-es/retirada.pdf b/doc/cbdc-es/retirada.pdf
new file mode 100644
index 000000000..7efc1549e
--- /dev/null
+++ b/doc/cbdc-es/retirada.pdf
Binary files differ
diff --git a/doc/cbdc-es/taler_figure_1_dora_SPANISH.jpg b/doc/cbdc-es/taler_figure_1_dora_SPANISH.jpg
new file mode 100644
index 000000000..0dfd64aea
--- /dev/null
+++ b/doc/cbdc-es/taler_figure_1_dora_SPANISH.jpg
Binary files differ
diff --git a/doc/cbdc-es/taler_figure_2_dora_SPANISH.jpg b/doc/cbdc-es/taler_figure_2_dora_SPANISH.jpg
new file mode 100644
index 000000000..75f476a9c
--- /dev/null
+++ b/doc/cbdc-es/taler_figure_2_dora_SPANISH.jpg
Binary files differ
diff --git a/doc/cbdc-it/agsm-mod.bst b/doc/cbdc-it/agsm-mod.bst
new file mode 100644
index 000000000..ac1bbd052
--- /dev/null
+++ b/doc/cbdc-it/agsm-mod.bst
@@ -0,0 +1,1375 @@
+% BibTeX standard bibliography style `agsm' (one of the harvard family)
+ % version 0.99a for BibTeX versions 0.99a or later, LaTeX version 2.09.
+ % Copyright (C) 1991, all rights reserved.
+ % Copying of this file is authorized only if either
+ % (1) you make absolutely no changes to your copy, including name, or
+ % (2) if you do make changes, you name it something other than
+ % btxbst.doc, plain.bst, unsrt.bst, alpha.bst, abbrv.bst, agsm.bst,
+ % dcu.bst or kluwer.bst.
+ % This restriction helps ensure that all standard styles are identical.
+ % The file harvard.tex has the documentation for this style.
+
+% ACKNOWLEDGEMENT:
+% This document is a modified version of alpha.bst to which it owes much of
+% its functionality.
+
+% AUTHOR
+% Peter Williams, Key Centre for Design Quality, Sydney University
+% e-mail: peterw@archsci.arch.su.oz.au
+
+ENTRY
+ { address
+ author
+ booktitle
+ chapter
+ edition
+ editor
+ howpublished
+ institution
+ journal
+ key
+ month
+ note
+ number
+ organization
+ pages
+ publisher
+ school
+ series
+ title
+ type
+ URL
+ volume
+ year
+ }
+ { field.used etal.allowed etal.required} %%%XXX change
+ { extra.label sort.label list.year }
+
+INTEGERS { output.state before.all mid.sentence after.sentence after.block }
+
+FUNCTION {init.state.consts}
+{ #0 'before.all :=
+ #1 'mid.sentence :=
+ #2 'after.sentence :=
+ #3 'after.block :=
+}
+
+STRINGS { s t f }
+
+FUNCTION {output.nonnull}
+{ 's :=
+ output.state mid.sentence =
+ { ", " * write$ }
+ { output.state after.block =
+ { add.period$ write$
+ newline$
+ "\newblock " write$
+ }
+ { output.state before.all =
+ 'write$
+ { add.period$ " " * write$ }
+ if$
+ }
+ if$
+ mid.sentence 'output.state :=
+ }
+ if$
+ s
+}
+
+FUNCTION {output}
+{ duplicate$ empty$
+ 'pop$
+ 'output.nonnull
+ if$
+}
+
+FUNCTION {output.check}
+{ 't :=
+ duplicate$ empty$
+ { pop$ "empty " t * " in " * cite$ * warning$ }
+ 'output.nonnull
+ if$
+}
+
+FUNCTION {item.check}
+{ 't :=
+ empty$
+ { "empty " t * " in " * cite$ * warning$ }
+ { skip$ }
+ if$
+}
+
+FUNCTION {fin.entry}
+{ add.period$
+ write$
+ newline$
+}
+
+FUNCTION {new.block}
+{ output.state before.all =
+ 'skip$
+ { after.block 'output.state := }
+ if$
+}
+
+FUNCTION {not}
+{ { #0 }
+ { #1 }
+ if$
+}
+
+FUNCTION {and}
+{ 'skip$
+ { pop$ #0 }
+ if$
+}
+
+FUNCTION {or}
+{ { pop$ #1 }
+ 'skip$
+ if$
+}
+
+FUNCTION {field.or.null}
+{ duplicate$ empty$
+ { pop$ "" }
+ 'skip$
+ if$
+}
+
+FUNCTION {emphasize}
+{ duplicate$ empty$
+ { pop$ "" }
+ { "{\em " swap$ * "}" * }
+ if$
+}
+
+FUNCTION {embolden}
+{ duplicate$ empty$
+ { pop$ "" }
+ { "{\bf " swap$ * "}" * }
+ if$
+}
+
+%%ORIGINAL KEPT HERE FOR REFERENCE:
+%%FUNCTION {quote}
+%%{ duplicate$ empty$
+%% { pop$ "" }
+%% { "`" swap$ * "'" * }
+%% if$
+%%}
+
+%%USE GUILLEMETS
+FUNCTION {quote}
+{ duplicate$ empty$
+ { pop$ "" }
+ { "«" swap$ * "»" * }
+ if$
+}
+%%END USE GUILLEMETS
+
+FUNCTION {write.url}
+{ URL empty$
+ { skip$ }
+ { "\newline\harvardurl{" URL * "}" * write$ newline$ }
+ if$
+}
+
+INTEGERS { nameptr namesleft numnames }
+
+FUNCTION {format.names}
+{ 's :=
+ 'f :=
+ #1 'nameptr :=
+ s num.names$ 'numnames :=
+ numnames 'namesleft :=
+ { namesleft #0 > }
+ { s nameptr f format.name$ 't :=
+ nameptr #1 >
+ { namesleft #1 >
+ { ", " * t * }
+ { t "others" =
+ { " et~al." * }
+ { " \harvardand\ " * t * }
+ if$
+ }
+ if$
+ }
+ 't
+ if$
+ nameptr #1 + 'nameptr :=
+ namesleft #1 - 'namesleft :=
+ }
+ while$
+}
+
+FUNCTION {format.authors}
+{ author empty$
+ { "" }
+ { "{vv~}{ll}{, jj}{, f.}" author format.names }
+ if$
+}
+
+FUNCTION {format.editors}
+{ editor empty$
+ { "" }
+ { "{vv~}{ll}{, jj}{, f.}" editor format.names
+ editor num.names$ #1 >
+ { ", eds" * }
+ { ", ed." * }
+ if$
+ }
+ if$
+}
+
+FUNCTION {format.editors.reverse}
+{ editor empty$
+ { "" }
+ { "{f.~}{vv~}{ll}{, jj}" editor format.names
+ editor num.names$ #1 >
+ { ", eds" * }
+ { ", ed." * }
+ if$
+ }
+ if$
+}
+
+%%ORIGINAL KEPT HERE FOR REFERENCE:
+%%FUNCTION {format.title}
+%%{ title empty$
+%% { "" }
+%% { title "t" change.case$ }
+%% if$
+%%}
+
+%%REMOVE SINGLE QUOTES
+FUNCTION {format.title}
+{ title empty$
+ { "" }
+ { title "t" change.case$ quote}
+ if$
+}
+%%END REMOVE SINGLE QUOTES
+
+FUNCTION {n.dashify}
+{ 't :=
+ ""
+ { t empty$ not }
+ { t #1 #1 substring$ "-" =
+ { t #1 #2 substring$ "--" = not
+ { "--" *
+ t #2 global.max$ substring$ 't :=
+ }
+ { { t #1 #1 substring$ "-" = }
+ { "-" *
+ t #2 global.max$ substring$ 't :=
+ }
+ while$
+ }
+ if$
+ }
+ { t #1 #1 substring$ *
+ t #2 global.max$ substring$ 't :=
+ }
+ if$
+ }
+ while$
+}
+
+FUNCTION {format.btitle}
+{ title emphasize
+}
+
+FUNCTION {tie.or.space.connect}
+{ duplicate$ text.length$ #3 <
+ { "~" }
+ { " " }
+ if$
+ swap$ * *
+}
+
+FUNCTION {either.or.check}
+{ empty$
+ 'pop$
+ { "can't use both " swap$ * " fields in " * cite$ * warning$ }
+ if$
+}
+
+FUNCTION {format.bvolume}
+{ volume empty$
+ { "" }
+ { "Vol." volume tie.or.space.connect
+ series empty$
+ 'skip$
+ { " of " * series emphasize * }
+ if$
+ "volume and number" number either.or.check
+ }
+ if$
+}
+
+FUNCTION {format.number.series}
+{ volume empty$
+ { number empty$
+ { series field.or.null }
+ { output.state mid.sentence =
+ { "number" }
+ { "Number" }
+ if$
+ number tie.or.space.connect
+ series empty$
+ { "there's a number but no series in " cite$ * warning$ }
+ { " {\em in} " * series quote * }
+ if$
+ }
+ if$
+ }
+ { "" }
+ if$
+}
+
+FUNCTION {format.edition}
+{ edition empty$
+ { "" }
+ { output.state mid.sentence =
+ { edition "l" change.case$ " edn" * }
+ { edition "t" change.case$ " edn" * }
+ if$
+ }
+ if$
+}
+
+INTEGERS { multiresult }
+
+FUNCTION {multi.page.check}
+{ 't :=
+ #0 'multiresult :=
+ { multiresult not
+ t empty$ not
+ and
+ }
+ { t #1 #1 substring$
+ duplicate$ "-" =
+ swap$ duplicate$ "," =
+ swap$ "+" =
+ or or
+ { #1 'multiresult := }
+ { t #2 global.max$ substring$ 't := }
+ if$
+ }
+ while$
+ multiresult
+}
+
+FUNCTION {format.pages}
+{ pages empty$
+ { "" }
+ { pages multi.page.check
+ { "pp.~" pages n.dashify * }
+ { "p.~" pages * }
+ if$
+ }
+ if$
+}
+
+FUNCTION {format.vol.num.pages}
+{ volume embolden field.or.null
+ number empty$
+ 'skip$
+ { "(" number * ")" * *
+ volume empty$
+ { "there's a number but no volume in " cite$ * warning$ }
+ 'skip$
+ if$
+ }
+ if$
+ pages empty$
+ 'skip$
+ { duplicate$ empty$
+ { pop$ format.pages }
+ { ",~" * pages n.dashify * }
+ if$
+ }
+ if$
+}
+
+FUNCTION {format.chapter.pages}
+{ chapter empty$
+ 'format.pages
+ { type empty$
+ { "chapter" }
+ { type "l" change.case$ }
+ if$
+ chapter tie.or.space.connect
+ pages empty$
+ 'skip$
+ { ", " * format.pages * }
+ if$
+ }
+ if$
+}
+
+%%REMOVE ITALICS FROM WORD "IN"
+FUNCTION {format.in.ed.booktitle}
+{ booktitle empty$
+ { "" }
+ { editor empty$
+ %%{ "{\em in} " booktitle * } ORIGINAL
+ { "{in} " booktitle * }
+ %%{ "{\em in} " format.editors.reverse * ", " * booktitle * } ORIGINAL
+ { "{in} " format.editors.reverse * ", " * booktitle * }
+ if$
+ }
+ if$
+}
+%%END REMOVE ITALICS FROM WORD "IN"
+
+FUNCTION {empty.misc.check}
+{ author empty$ title empty$ howpublished empty$
+ month empty$ year empty$ note empty$
+ and and and and and
+ key empty$ not and
+ { "all relevant fields are empty in " cite$ * warning$ }
+ 'skip$
+ if$
+}
+
+FUNCTION {format.thesis.type}
+{ type empty$
+ 'skip$
+ { pop$
+ type "t" change.case$
+ }
+ if$
+}
+
+FUNCTION {format.tr.number}
+{ type empty$
+ { "Technical Report" }
+ 'type
+ if$
+ number empty$
+ { "t" change.case$ }
+ { number tie.or.space.connect }
+ if$
+}
+
+FUNCTION {format.article.crossref}
+{ key empty$
+ { journal empty$
+ { "need key or journal for " cite$ * " to crossref " * crossref *
+ warning$
+ ""
+ }
+ { "in {\em " journal * "\/} \cite{" * crossref * "}" *}
+ if$
+ }
+ { "{\em in} \citeasnoun{" crossref * "}" * }
+ if$
+
+}
+
+FUNCTION {format.book.crossref}
+{ volume empty$
+ { "empty volume in " cite$ * "'s crossref of " * crossref * warning$
+ "in "
+ }
+ { "Vol." volume tie.or.space.connect
+ " of " *
+ }
+ if$
+ editor empty$
+ editor field.or.null author field.or.null =
+ or
+ { key empty$
+ { series empty$
+ { "need editor, key, or series for " cite$ * " to crossref " *
+ crossref * warning$
+ "" *
+ }
+ { "{\em " * series * "\/} \cite{" * crossref * "}" *}
+ if$
+ }
+ { " \citeasnoun{" * crossref * "}" * }
+ if$
+ }
+ { " \citeasnoun{" * crossref * "}" * }
+ if$
+}
+
+FUNCTION {format.incoll.inproc.crossref}
+{ editor empty$
+ editor field.or.null author field.or.null =
+ or
+ { key empty$
+ { booktitle empty$
+ { "need editor, key, or booktitle for " cite$ * " to crossref " *
+ crossref * warning$
+ ""
+ }
+ { "in {\em " booktitle * "\/}" * " \cite{" * crossref * "}" *}
+
+ if$
+ }
+ { "{\em in} \citeasnoun{" crossref * "}" * }
+ if$
+ }
+ { "{\em in} \citeasnoun{" crossref * "}" * }
+ if$
+
+}
+
+INTEGERS { len }
+
+FUNCTION {chop.word}
+{ 's :=
+ 'len :=
+ s #1 len substring$ =
+ { s len #1 + global.max$ substring$ }
+ 's
+ if$
+}
+
+INTEGERS { ind tsslen }
+
+STRINGS { tss ret rss istr }
+
+FUNCTION {replace.substring}{
+ 'rss :=
+ 'tss :=
+ 'istr :=
+ "" 'ret :=
+ tss text.length$ 'tsslen :=
+ #1 'ind :=
+ { istr ind tsslen substring$ "" = not }
+ { istr ind tsslen substring$ tss =
+ { ret rss * 'ret :=
+ ind tsslen + 'ind :=
+ }
+ { ret istr ind #1 substring$ * 'ret :=
+ ind #1 + 'ind :=
+ }
+ if$
+ }
+ while$
+ ret
+}
+
+FUNCTION {format.lab.names.abbr}
+{ 's :=
+ s num.names$ 'numnames :=
+ numnames #1 >
+ { numnames #2 >
+ { s #1 "{vv~}{ll}" format.name$ " et~al." * }
+ { s #2 "{ff }{vv }{ll}{ jj}" format.name$ "others" =
+ { s #1 "{vv~}{ll}" format.name$ " et~al." * }
+ { s #1 "{vv~}{ll}" format.name$ " \harvardand\ " *
+ s #2 "{vv~}{ll}" format.name$ *
+ }
+ if$
+ }
+ if$
+ }
+ { s #1 "{vv~}{ll}" format.name$ }
+ if$
+}
+
+FUNCTION {format.lab.names.full}
+{ 's :=
+ #1 'nameptr :=
+ s num.names$ 'numnames :=
+ numnames 'namesleft :=
+ { namesleft #0 > }
+ { s nameptr "{vv~}{ll}" format.name$ 't :=
+ nameptr #1 >
+ { namesleft #1 >
+ { ", " * t * }
+ { t "others" =
+ { " et~al." * }
+ { " \harvardand\ " * t * }
+ if$
+ }
+ if$
+ }
+ 't
+ if$
+ nameptr #1 + 'nameptr :=
+ namesleft #1 - 'namesleft :=
+ }
+ while$
+}
+
+INTEGERS { author.field editor.field organization.field title.field key.field }
+
+FUNCTION {init.field.constants}
+{ #0 'author.field :=
+ #1 'editor.field :=
+ #2 'organization.field :=
+ #3 'title.field :=
+ #4 'key.field :=
+}
+
+FUNCTION {make.list.label}
+{ author.field field.used =
+ { format.authors }
+ { editor.field field.used =
+ { format.editors }
+ { organization.field field.used =
+ { "The " #4 organization chop.word #3 text.prefix$ }
+ { title.field field.used =
+ { format.btitle }
+ { key.field field.used =
+ { key #3 text.prefix$ }
+ { "Internal error :001 on " cite$ * " label" * warning$ }
+ if$
+ }
+ if$
+ }
+ if$
+ }
+ if$
+ }
+ if$
+}
+
+FUNCTION {make.full.label}
+{ author.field field.used =
+ { author format.lab.names.full }
+ { editor.field field.used =
+ { editor format.lab.names.full }
+ { organization.field field.used =
+ { "The " #4 organization chop.word #3 text.prefix$ }
+ { title.field field.used =
+ { format.btitle }
+ { key.field field.used =
+ { key #3 text.prefix$ }
+ { "Internal error :001 on " cite$ * " label" * warning$ }
+ if$
+ }
+ if$
+ }
+ if$
+ }
+ if$
+ }
+ if$
+}
+
+FUNCTION {make.abbr.label} %%%XXX change
+{
+ etal.allowed
+ { author.field field.used =
+ { author format.lab.names.abbr }
+ { editor.field field.used =
+ { editor format.lab.names.abbr }
+ { organization.field field.used =
+ { "The " #4 organization chop.word #3 text.prefix$ }
+ { title.field field.used =
+ { format.btitle }
+ { key.field field.used =
+ { key #3 text.prefix$ }
+ {"Internal error :001 on " cite$ * " label" * warning$ }
+ if$
+ }
+ if$
+ }
+ if$
+ }
+ if$
+ }
+ if$
+ }
+ { make.full.label }
+ if$
+}
+
+FUNCTION {output.bibitem}
+{ newline$
+ etal.allowed %%%XXX change
+ etal.required
+ and
+ {
+ "\harvarditem[" write$
+ make.abbr.label write$
+ "]{" write$
+ }
+ {
+ "\harvarditem{" write$
+ }
+ if$
+ make.full.label write$
+ "}{" write$
+ list.year write$
+ "}{" write$
+ cite$ write$
+ "}" write$
+ newline$
+ ""
+ before.all 'output.state :=
+}
+
+FUNCTION {list.label.output}
+{ make.list.label " " * write$
+}
+
+FUNCTION {article}
+{ output.bibitem
+ list.label.output
+ " \harvardyearleft " list.year * "\harvardyearright " * output.nonnull
+ author "author" item.check
+ title.field field.used =
+ { skip$ }
+ { format.title "title" output.check }
+ if$
+ crossref missing$
+ { journal emphasize "journal" duplicate$ item.check
+ " " * format.vol.num.pages * output
+ }
+ { format.article.crossref output.nonnull
+ format.pages output
+ }
+ if$
+ new.block
+ note output
+ fin.entry
+ write.url
+}
+
+FUNCTION {book}
+{ output.bibitem
+ list.label.output
+ " \harvardyearleft " list.year * "\harvardyearright " * output.nonnull
+ author empty$
+ { editor "author and editor" item.check }
+ { crossref missing$
+ { "author and editor" editor either.or.check }
+ 'skip$
+ if$
+ }
+ if$
+ title.field field.used =
+ { skip$ }
+ { format.btitle "title" output.check }
+ if$
+ crossref missing$
+ { format.bvolume output
+ format.number.series output
+ format.edition output
+ publisher "publisher" output.check
+ address output
+ }
+ { format.book.crossref output.nonnull
+ format.edition output
+ }
+ if$
+ new.block
+ note output
+ fin.entry
+ write.url
+}
+
+FUNCTION {booklet}
+{ output.bibitem
+ list.label.output
+ " \harvardyearleft " list.year * "\harvardyearright " * output.nonnull
+ title.field field.used =
+ { skip$ }
+ { format.title "title" output.check }
+ if$
+ howpublished output
+ address output
+ new.block
+ note output
+ fin.entry
+ write.url
+}
+
+FUNCTION {inbook}
+{ output.bibitem
+ list.label.output
+ " \harvardyearleft " list.year * "\harvardyearright " * output.nonnull
+ author empty$
+ { editor "author and editor" item.check }
+ { crossref missing$
+ { "author and editor" editor either.or.check }
+ 'skip$
+ if$
+ }
+ if$
+ title.field field.used =
+ { skip$ }
+ { format.btitle "title" output.check }
+ if$
+ crossref missing$
+ { format.bvolume output
+ format.number.series output
+ format.edition output
+ publisher "publisher" output.check
+ address output
+ }
+ { format.book.crossref output.nonnull
+ format.edition output
+ }
+ if$
+ format.chapter.pages "chapter and pages" output.check
+ new.block
+ note output
+ fin.entry
+ write.url
+}
+
+FUNCTION {incollection}
+{ output.bibitem
+ list.label.output
+ " \harvardyearleft " list.year * "\harvardyearright " * output.nonnull
+ title.field field.used =
+ { skip$ }
+ { format.title "title" output.check }
+ if$
+ author "author" item.check
+ crossref missing$
+ { format.in.ed.booktitle "booktitle" output.check
+ format.edition output
+ format.bvolume output
+ format.number.series output
+ publisher "publisher" output.check
+ address output
+ }
+ { format.incoll.inproc.crossref output.nonnull
+ }
+ if$
+ format.chapter.pages output
+ new.block
+ note output
+ fin.entry
+ write.url
+}
+
+FUNCTION {inproceedings}
+{ output.bibitem
+ list.label.output
+ " \harvardyearleft " list.year * "\harvardyearright " * output.nonnull
+ title.field field.used =
+ { skip$ }
+ { format.title "title" output.check }
+ if$
+ author "author" item.check
+ crossref missing$
+ { format.in.ed.booktitle "booktitle" output.check
+ format.bvolume output
+ format.number.series output
+ address empty$
+ { organization output
+ publisher output
+ }
+ { organization output
+ publisher output
+ address output.nonnull
+ }
+ if$
+ }
+ { format.incoll.inproc.crossref output.nonnull
+ }
+ if$
+ format.pages output
+ new.block
+ note output
+ fin.entry
+ write.url
+}
+
+FUNCTION {conference} { inproceedings }
+
+FUNCTION {manual}
+{ output.bibitem
+ list.label.output
+ " \harvardyearleft " list.year * "\harvardyearright " * output.nonnull
+ title.field field.used =
+ { skip$ }
+ { format.btitle "title" output.check }
+ if$
+ format.edition output
+ author empty$
+ { organization empty$
+ { address output }
+ 'skip$
+ if$
+ }
+ { organization output
+ address output
+ }
+ if$
+ new.block
+ note output
+ fin.entry
+ write.url
+}
+
+FUNCTION {mastersthesis}
+{ output.bibitem
+ list.label.output
+ " \harvardyearleft " list.year * "\harvardyearright " * output.nonnull
+ author "author" item.check
+ title.field field.used =
+ { skip$ }
+ { format.title "title" output.check }
+ if$
+ "Master's thesis" format.thesis.type output.nonnull
+ school "school" output.check
+ address output
+ new.block
+ note output
+ fin.entry
+ write.url
+}
+
+FUNCTION {misc}
+{ output.bibitem
+ list.label.output
+ " \harvardyearleft " list.year * "\harvardyearright " * output.nonnull
+ title.field field.used =
+ { skip$ }
+ { format.title output }
+ if$
+ howpublished output
+ new.block
+ note output
+ fin.entry
+ write.url
+ empty.misc.check
+}
+
+FUNCTION {phdthesis}
+{ output.bibitem
+ list.label.output
+ " \harvardyearleft " list.year * "\harvardyearright " * output.nonnull
+ author "author" item.check
+ title.field field.used =
+ { skip$ }
+ { title "title" output.check }
+ if$
+ "PhD thesis" format.thesis.type output.nonnull
+ school "school" output.check
+ address output
+ new.block
+ note output
+ fin.entry
+ write.url
+}
+
+FUNCTION {proceedings}
+{ output.bibitem
+ list.label.output
+ " \harvardyearleft " list.year * "\harvardyearright " * output.nonnull
+ title.field field.used =
+ { skip$ }
+ { format.btitle "title" output.check }
+ if$
+ format.bvolume output
+ format.number.series output
+ address empty$
+ { editor empty$
+ { skip$ }
+ { organization output
+ }
+ if$
+ publisher output
+ }
+ { editor empty$
+ 'skip$
+ { organization output }
+ if$
+ publisher output
+ address output.nonnull
+ }
+ if$
+ new.block
+ note output
+ fin.entry
+ write.url
+}
+
+FUNCTION {techreport}
+{ output.bibitem
+ list.label.output
+ " \harvardyearleft " list.year * "\harvardyearright " * output.nonnull
+ author "author" item.check
+ title.field field.used =
+ { skip$ }
+ { format.title "title" output.check }
+ if$
+ format.tr.number output.nonnull
+ institution "institution" output.check
+ address output
+ new.block
+ note output
+ fin.entry
+ write.url
+}
+
+FUNCTION {unpublished}
+{ output.bibitem
+ list.label.output
+ " \harvardyearleft " list.year * "\harvardyearright " * output.nonnull
+ author "author" item.check
+ title.field field.used =
+ { skip$ }
+ { format.title "title" output.check }
+ if$
+ new.block
+ note "note" output.check
+ fin.entry
+ write.url
+}
+
+FUNCTION {default.type} { misc }
+
+MACRO {jan} {"January"}
+
+MACRO {feb} {"February"}
+
+MACRO {mar} {"March"}
+
+MACRO {apr} {"April"}
+
+MACRO {may} {"May"}
+
+MACRO {jun} {"June"}
+
+MACRO {jul} {"July"}
+
+MACRO {aug} {"August"}
+
+MACRO {sep} {"September"}
+
+MACRO {oct} {"October"}
+
+MACRO {nov} {"November"}
+
+MACRO {dec} {"December"}
+
+MACRO {acmcs} {"ACM Computing Surveys"}
+
+MACRO {acta} {"Acta Informatica"}
+
+MACRO {cacm} {"Communications of the ACM"}
+
+MACRO {ibmjrd} {"IBM Journal of Research and Development"}
+
+MACRO {ibmsj} {"IBM Systems Journal"}
+
+MACRO {ieeese} {"IEEE Transactions on Software Engineering"}
+
+MACRO {ieeetc} {"IEEE Transactions on Computers"}
+
+MACRO {ieeetcad}
+ {"IEEE Transactions on Computer-Aided Design of Integrated Circuits"}
+
+MACRO {ipl} {"Information Processing Letters"}
+
+MACRO {jacm} {"Journal of the ACM"}
+
+MACRO {jcss} {"Journal of Computer and System Sciences"}
+
+MACRO {scp} {"Science of Computer Programming"}
+
+MACRO {sicomp} {"SIAM Journal on Computing"}
+
+MACRO {tocs} {"ACM Transactions on Computer Systems"}
+
+MACRO {tods} {"ACM Transactions on Database Systems"}
+
+MACRO {tog} {"ACM Transactions on Graphics"}
+
+MACRO {toms} {"ACM Transactions on Mathematical Software"}
+
+MACRO {toois} {"ACM Transactions on Office Information Systems"}
+
+MACRO {toplas} {"ACM Transactions on Programming Languages and Systems"}
+
+MACRO {tcs} {"Theoretical Computer Science"}
+
+READ
+
+EXECUTE {init.field.constants}
+
+FUNCTION {sortify}
+{ purify$
+ "l" change.case$
+}
+
+FUNCTION {sortify.names}
+{ " \harvardand\ " " " replace.substring
+ " et~al." " zzz" replace.substring
+ sortify
+}
+
+FUNCTION {author.key.label}
+{ author empty$
+ { key empty$
+ { title.field 'field.used := }
+ { key.field 'field.used := }
+ if$
+ }
+ { author.field 'field.used := }
+ if$
+}
+
+FUNCTION {author.editor.key.label}
+{ author empty$
+ { editor empty$
+ { key empty$
+ { title.field 'field.used := }
+ { key.field 'field.used := }
+ if$
+ }
+ { editor.field 'field.used := }
+ if$
+ }
+ { author.field 'field.used := }
+ if$
+}
+
+FUNCTION {author.key.organization.label}
+{ author empty$
+ { key empty$
+ { organization empty$
+ { title.field 'field.used := }
+ { organization.field 'field.used := }
+ if$
+ }
+ { key.field 'field.used := }
+ if$
+ }
+ { author.field 'field.used := }
+ if$
+}
+
+FUNCTION {editor.key.organization.label}
+{ editor empty$
+ { key empty$
+ { organization empty$
+ { title.field 'field.used := }
+ { organization.field 'field.used := }
+ if$
+ }
+ { key.field 'field.used := }
+ if$
+ }
+ { editor.field 'field.used := }
+ if$
+}
+
+FUNCTION {sort.format.title}
+{ 't :=
+ "A " #2
+ "An " #3
+ "The " #4 t chop.word
+ chop.word
+ chop.word
+ sortify
+ #1 global.max$ substring$
+}
+
+FUNCTION {calc.label} %%%XXX change
+{ make.abbr.label
+ title.field field.used =
+ { sort.format.title }
+ { sortify.names }
+ if$
+ year field.or.null purify$ #-1 #4 substring$ sortify
+ *
+ 'sort.label :=
+}
+
+FUNCTION {preliminaries} %%%XXX change
+{ type$ "book" =
+ type$ "inbook" =
+ or
+ 'author.editor.key.label
+ { type$ "proceedings" =
+ 'editor.key.organization.label
+ { type$ "manual" =
+ 'author.key.organization.label
+ 'author.key.label
+ if$
+ }
+ if$
+ }
+ if$
+ author.field field.used = %%%XXX change
+ {
+ author num.names$ #2 >
+ { #1 }
+ { #0 }
+ if$
+ 'etal.required :=
+ }
+ {
+ editor.field field.used =
+ {
+ editor num.names$ #2 >
+ { #1 }
+ { #0 }
+ if$
+ }
+ { #0 }
+ if$
+ 'etal.required :=
+ }
+ if$
+ #1 'etal.allowed :=
+}
+
+FUNCTION {first.presort}
+{ calc.label
+ sort.label
+ title.field field.used =
+ { skip$ }
+ { " "
+ *
+ make.list.label sortify.names
+ *
+ " "
+ *
+ title field.or.null
+ sort.format.title
+ *
+ }
+ if$
+ #1 entry.max$ substring$
+ 'sort.key$ :=
+}
+
+ITERATE {preliminaries}
+
+ITERATE {first.presort}
+
+SORT
+
+STRINGS { last.sort.label next.extra last.full.label}
+
+INTEGERS { last.extra.num last.etal.allowed}
+
+FUNCTION {initialize.confusion}
+{ #0 int.to.chr$ 'last.sort.label :=
+ #0 int.to.chr$ 'last.full.label :=
+ #1 'last.etal.allowed :=
+}
+
+FUNCTION {confusion.pass}
+{ last.sort.label sort.label =
+ { last.etal.allowed
+ { last.full.label make.full.label sortify.names =
+ { skip$ }
+ { #0 'etal.allowed :=
+ #0 'last.etal.allowed :=
+ }
+ if$
+ }
+ { #0 'etal.allowed := }
+ if$
+ }
+ { sort.label 'last.sort.label :=
+ make.full.label sortify.names 'last.full.label :=
+ #1 'last.etal.allowed :=
+ }
+ if$
+}
+
+EXECUTE {initialize.confusion}
+
+ITERATE {confusion.pass}
+
+EXECUTE {initialize.confusion}
+
+REVERSE {confusion.pass}
+
+FUNCTION {initialize.last.extra.num}
+{ #0 int.to.chr$ 'last.sort.label :=
+ "" 'next.extra :=
+ #0 'last.extra.num :=
+}
+
+FUNCTION {forward.pass}
+{ last.sort.label sort.label =
+ { last.extra.num #1 + 'last.extra.num :=
+ last.extra.num int.to.chr$ 'extra.label :=
+ }
+ { "a" chr.to.int$ 'last.extra.num :=
+ "" 'extra.label :=
+ sort.label 'last.sort.label :=
+ }
+ if$
+}
+
+FUNCTION {reverse.pass}
+{ next.extra "b" =
+ { "a" 'extra.label := }
+ 'skip$
+ if$
+ year empty$
+ { "n.d." extra.label emphasize * 'list.year := }
+ { year extra.label emphasize * 'list.year := }
+ if$
+ extra.label 'next.extra :=
+}
+
+ITERATE {first.presort}
+
+SORT
+
+EXECUTE {initialize.last.extra.num}
+
+ITERATE {forward.pass}
+
+REVERSE {reverse.pass}
+
+FUNCTION {second.presort}
+{ make.list.label
+ title.field field.used =
+ { sort.format.title }
+ { sortify.names }
+ if$
+ " "
+ *
+ list.year field.or.null sortify
+ *
+ " "
+ *
+ title.field field.used =
+ { skip$ }
+ { title field.or.null
+ sort.format.title
+ *
+ }
+ if$
+ #1 entry.max$ substring$
+ 'sort.key$ :=
+}
+
+ITERATE {second.presort}
+
+SORT
+
+FUNCTION {begin.bib}
+{ preamble$ empty$
+ 'skip$
+ { preamble$ write$ newline$ }
+ if$
+ "\begin{thebibliography}{xx}" write$ newline$
+}
+
+EXECUTE {begin.bib}
+
+EXECUTE {init.state.consts}
+
+ITERATE {call.type$}
+
+FUNCTION {end.bib}
+{ newline$
+ "\end{thebibliography}" write$ newline$
+}
+
+EXECUTE {end.bib}
diff --git a/doc/cbdc-it/cbdc-it.bib b/doc/cbdc-it/cbdc-it.bib
new file mode 100644
index 000000000..639bb0c45
--- /dev/null
+++ b/doc/cbdc-it/cbdc-it.bib
@@ -0,0 +1,561 @@
+%% To be used with modified bibliography style agsm-mod + hyperref + natbib + italian babel
+
+@article{Adrian,
+ author = {Adrian, Tobias and Mancini-Griffoli},
+ year = {2019},
+ title = {{The Rise of Digital Money}},
+ journal = {IMF Fintech Note},
+ volume = {19/01},
+}
+
+@article{Agarwal,
+ author = {Agarwal, Ruchir and Miles S. Kimball},
+ year = {2019},
+ title = {{Enabling Deep Negative Rates to Fight Recessions: A Guide}},
+ journal = {IMF Working Paper},
+ volume = {19/84},
+}
+
+
+@article{Agur,
+ author = {Agur, Itai and Anil Ari and Giovanni Dell'Ariccia},
+ year = {2019},
+ title = {{Designing Central Bank Digital Currencies}},
+ journal = {IMF Working Paper},
+ volume = {19/252},
+}
+
+@article{Allen,
+ author = {Allen, Sarah and Srđjan Čapkun and Ittay Eyal and Giulia Fanti and Bryan A. Ford and James Grimmelmann and Ari Juels and Kari Kostiainen and Sarah Meiklejohn and Andrew Miller and Eswar Prasad and Karl Wüst and Fan Zhang},
+ year = {2020},
+ title = {{Design Choices for Central Bank Digital Currency: Policy and Technical Considerations}},
+ journal = {NBER Working Paper},
+ volume = {27634},
+}
+
+@article{Alves,
+ author = {Alves, Tiago and Don Felton},
+ year = {2004},
+ title = {TrustZone: Integrated hardware and software security},
+ journal = {ARM IQ},
+ volume = {3},
+ number = {4},
+ pages = {18--24},
+}
+
+@article{AuerBoehme,
+ author = {Auer, Raphael and Rainer Böhme},
+ year = {2020},
+ title = {The technology of retail central bank digital currency},
+ journal = {BIS Quarterly Review},
+ month = {March},
+ pages = {85--96},
+}
+
+@article{AuerCornelli,
+ author = {Auer, Raphael and Giulio Cornelli and Jon Frost},
+ year = {2020},
+ title = {{Taking stock: ongoing retail CBDC projects}},
+ journal = {BIS Quarterly Review},
+ month = {March},
+ pages = {97--98},
+}
+
+@booklet{BIS,
+ author = {{Bank for International Settlements}},
+ year = {2018},
+ title = {{Central Bank Digital Currencies. Joint Report of the Committee on Payments and Market Infrastructures and Markets Committee}},
+}
+
+@booklet{BoE,
+ author = {{Bank of England}},
+ year = {2020},
+ title = {{Central Bank Digital Currency: Opportunities, Challenges and Design. Discussion Paper}},
+ month = {March},
+}
+
+@article{Baiocchi,
+ author = {Baiocchi, Giovanni and Walter Distaso},
+ year = {2003},
+ title = {{GRETL: Econometric Software for the GNU Generation}},
+ journal = {Journal of Applied Econometrics},
+ volume = {18},
+ pages = {105-110},
+}
+
+@article{Bech,
+ author = {Bech, Morten and Rodney Garratt},
+ year = {2017},
+ title = {Central bank cryptocurrencies},
+ journal = {BIS Quarterly Review},
+ month = {September},
+ pages = {55--70},
+}
+
+@article{Berentsen,
+ author = {Berentsen, Aleksander and Fabian Schär},
+ year = {2018},
+ title = {{The Case for Central Bank Electronic Money and the Non-case for Central Bank Cryptocurrencies}},
+ journal = {Federal Reserve Bank of St. Louis Review},
+ volume = {100},
+ number = {2},
+ pages = {97--106},
+}
+
+@article{Bernstein2020,
+ author = {Bernstein, Daniel J. and Tanja Lange},
+ year = {2020},
+ title = {{eBACS: ECRYPT Benchmarking of Cryptographic Systems}},
+ url = {\url{https://bench.cr.yp.to}, consultato il 17 marzo 2020},
+}
+
+@article{Bernstein2012,
+ author = {Bernstein, Daniel J. and Niels Duif and Tanja Lange and Peter Schwabe and Bo-Yin Yang},
+ year = {2012},
+ title = {High-speed high-security signatures},
+ journal = {Journal of Cryptographic Engineering},
+ volume = {2},
+ pages = {77--89},
+}
+
+@InCollection{Bindseil,
+ author = {Bindseil, Ulrich},
+ year = {2020},
+ title = {{Tiered CBDC and the financial system}},
+ publisher = {European Central Bank},
+ series = {ECB Working Paper},
+ number = {2351},
+ month = {January},
+}
+
+@article{Boar,
+ author = {Boar, Codruta and Henry Holden and Amber Wadsworth},
+ year = {2020},
+ title = {Impending arrival - a sequel to the survey on central bank digital currency},
+ journal = {BIS Papers},
+ volume = {107},
+}
+
+@article{Boneh,
+ author = {Boneh, Dan},
+ year = {1999},
+ title = {{Twenty Years of Attacks on the RSA Cryptosystem}},
+ journal = {Notices of the AMS},
+ volume = {42},
+ number = {2},
+ pages = {202--213},
+}
+
+@InCollection{Bordo,
+ author = {Bordo, Michael D. and Andrew T. Levin},
+ year = {2017},
+ title = {Central bank digital currency and the future of monetary policy},
+ publisher = {National Bureau of Economic Research},
+ series = {NBER Working Paper Series},
+ number = {23711},
+}
+
+@article{Brunnermeier,
+ author = {Brunnermeier, Markus and Dirk Niepelt},
+ year = {2019},
+ title = {{On the Equivalence of Private and Public Money}},
+ journal = {Journal of Monetary Economics},
+ volume = {106},
+ pages = {27--41},
+}
+
+@article{Buiter,
+ author = {Buiter, Willem H. and Nikolaos Panigirtzoglou},
+ year = {2003},
+ title = {{Overcoming the Zero Bound on Nominal Interest Rates with Negative Interest on Currency: Gesell's Solution}},
+ journal = {The Economic Journal},
+ volume = {113},
+ number = {490},
+ pages = {723--746},
+}
+
+@InCollection{Bullmann,
+ author = {Bullmann, Dirk and Jonas Klemm and Andrea Pinna},
+ year = {2019},
+ title = {In search for stability in crypto-assets: are stablecoins the solution?},
+ publisher = {European Central Bank},
+ series = {ECB Occasional Paper Series},
+ number = {230},
+}
+
+@inproceedings{Camenisch2007,
+ author = {Camenisch, Jan and Aanna Lysyanskaya and Mira Meyerovich},
+ year = {2007},
+ title = {{Endorsed E-Cash}},
+ booktitle = {\textit{2007 IEEE Symposium on Security and Privacy (SP'07)}},
+ month = {May},
+ pages = {101--115},
+}
+
+@inproceedings{Camenisch2005,
+ author = {Camenisch, Jan and Susan Hohenberger and Anna Lysyanskaya},
+ year = {2005},
+ title = {{Compact E-Cash}},
+ booktitle = {\textit{Advances in Cryptology -- EUROCRYPT 2005: 24th Annual International Conference on the Theory and Applications of Cryptographic Techniques}},
+ address = {Aarhus, Denmark},
+ month = {May},
+ day = {22-26},
+ editor = {Ed. di Ronald Cramer},
+ publisher = {Springer-Verlag Berlin Heidelberg},
+}
+
+
+
+@inproceedings{Canard,
+ author = {Canard, Sébastien and Aline Gouget},
+ year = {2007},
+ title = {Divisible e-cash systems can be truly anonymous},
+ booktitle = {\textit{Annual International Conference on the Theory and Applications of Cryptographic Techniques}},
+ pages = {482--497},
+}
+
+@misc{CCC,
+ author = {{CCC e.V.}},
+ year = {2017},
+ title = {{Chaos Computer Club hacks e-motor charging stations}},
+ howpublished = {34c3},
+}
+
+@article{Chapman,
+ author = {Chapman, James and Rodney Garratt and Scott Hendry and Andrew McCormack and Wade McMahon},
+ year = {2017},
+ title = {{Project Jasper: Are Distributed Wholesale Payment Systems Feasible Yet?}},
+ journal = {Financial System Review},
+ publisher = {Bank of Canada},
+ month = {June},
+ pages = {59--69},
+}
+
+@inproceedings{Chaum1983,
+ author = {Chaum, David},
+ year = {1983},
+ title = {Blind signatures for untraceable payments},
+ booktitle = {\textit{Advances in Cryptology: Proceedings of Crypto `82}},
+ pages = {199--203},
+}
+
+@inproceedings{Chaum1990,
+ author = {Chaum, David and Amos Fiat and Moni Naor},
+ year = {1990},
+ title = {Untraceable electronic cash},
+ booktitle = {\textit{Advances in Cryptology: Proceedings of CRYPTO '88}},
+ pages = {319--327},
+}
+
+@inproceedings{Danezis,
+ author = {Danezis, George and Sarah Meiklejohn},
+ year = {2016},
+ title = {{Centrally Banked Cryptocurrencies}},
+ booktitle = {\textit{23nd Annual Network and Distributed System Security Symposium, NDSS2016}},
+ address = {San Diego, California, USA},
+ month = {February},
+ day = {21--24},
+ publisher = {The Internet Society},
+}
+
+@article{Diffie,
+ author = {Diffie, Whitfield and Martin Hellmann},
+ year = {1976},
+ title = {{New Directions in Cryptography}},
+ journal = {IEEE Trans. on Inf. Theory, IT-22},
+ pages = {644--654},
+}
+
+@phdthesis{Dold,
+ author = {Dold, Florian},
+ year = {2019},
+ title = {{The GNU Taler System: Practical and Provably Secure Electronic Payments. PhD Thesis}},
+ school = {University of Rennes 1},
+}
+
+@article{ECB,
+ author = {{European Central Bank}},
+ year = {2019},
+ title = {Exploring anonymity in central bank digital currencies},
+ journal = {In Focus},
+ number = {4},
+ month = {December},
+}
+
+@inproceedings{Fuchsbauer,
+ author = {Fuchsbauer, Georg and David Pointcheval and Damien Vergnaud},
+ year = {2009},
+ title = {Transferable constant-size fair e-cash},
+ booktitle = {International Conference on Cryptology and Network Security},
+ publisher = {Springer-Verlag Berlin Heidelberg},
+ pages = {226--247},
+}
+
+@inproceedings{Garcia,
+ author = {Garcia, Flavio and Gerhard de Koning Gans and Ruben Muijrers and Peter van Rossum and Roel Verdult and Ronny Wichers Schreur and Bart Jacobs},
+ year = {2008},
+ title = {{Dismantling MIFARE Classic}},
+ booktitle = {\textit{European Symposium on Research in Computer Security}},
+}
+
+@article{Garratt,
+ author = {Garratt, Rod and Michael Lee and Brendan Malone and Antoine Martin},
+ year = {2020},
+ title = {{Token- or Account-Based? A Digital Currency Can Be Both}},
+ journal = {Liberty Street Economics},
+ publisher = {Federal Reserve Bank of New York},
+ month = {August},
+ day = {12},
+}
+
+@article{Goodfriend,
+ author = {Goodfriend, Marvin},
+ year = {2000},
+ title = {{Overcoming the Zero Bound on Interest Rate Policy}},
+ journal = {Journal of Money, Credit, and Banking},
+ volume = {32},
+ number = {4},
+ pages = {1007--1035},
+}
+
+@article{Johnston,
+ author = {Johnston, Casey},
+ year = {2010},
+ title = {PS3 hacked through poor cryptography implementation},
+ journal = {Ars Technica},
+ month = {December},
+ day = {30},
+}
+
+@Misc{Jordan,
+ note = {Discorso in occasione del 30º anniversario del Centro di scienze economiche (WWZ) e dell’Associazione degli economisti basilesi (VBÖ)},
+ author = {Jordan, Thomas J.},
+ year = {2019},
+ title = {Valute, moneta e token digitali},
+ publisher = {University of Basel},
+ month = {September},
+ howpublished = {\url{https://www.snb.ch/it/mmr/speeches/id/ref_20190905_tjn/source/ref_20190905_tjn.it.pdf}},
+}
+
+@article{Kahn2009,
+ author = {Kahn, Charles M. and William Roberds},
+ year = {2009},
+ title = {{Why Pay? An Introduction to Payments Economics}},
+ journal = {Journal of Financial Intermediation},
+ number = {18},
+ pages = {1--23},
+}
+
+@article{Kahn2005,
+ author = {Kahn, Charles M. and James McAndrews and William Roberds},
+ year = {2005},
+ title = {{Money is Privacy}},
+ journal = {International Economic Review},
+ volume = {46},
+ number = {2},
+ pages = {377--399},
+}
+
+@article{Kasper,
+ author = {Kasper, Timo and Michael Silbermann and Christof Paar},
+ year = {2010},
+ title = {All you can eat or breaking a real-world contactless payment system},
+ journal = {Financial Cryptography and Data Security, Lecture Notes in Computer Science},
+ volume = {6052},
+ pages = {343--50},
+}
+
+@inproceedings{Katzenbeisser,
+ author = {Katzenbeisser, Stefan and Ünal Kocabaş and Vladimir Rožić and Ahmad-Reza Sadeghi and Ingrid Verbauwhede and Christian Wachsmann},
+ year = {2012},
+ title = {{PUFs: Myth, Fact or Busted? A Security Evaluation of Physically Unclonable Functions (PUFs) Cast in Silicon}},
+ booktitle = {\textit{Cryptographic Hardware and Embedded Systems -- CHES 2012. Lecture Notes in Computer Science}},
+ volume = {7428},
+ pages = {283--301},
+}
+
+@book{Keynes,
+ author = {Keynes, John Maynard},
+ year = {1936},
+ title = {The General Theory of Employment, Interest and Money},
+ publisher = {Macmillan},
+}
+
+@article{Kiff,
+ author = {Kiff, John and Jihad Alwazir and Sonja Davidovic and Aquiles Farias and Ashraf Khan and Tanai Khiaonarong and Majid Malaika and Hunter Monroe and Nobu Sugimoto and Hervé Tourpe and Peter Zhou},
+ year = {2020},
+ title = {{A Survey of Research on Retail Central Bank Digital Currency}},
+ journal = {IMF Working Paper},
+ volume = {20/104},
+}
+
+@InCollection{Kumhof,
+ author = {Kumhof, Michael and Clare Noone},
+ year = {2018},
+ title = {Central bank digital currencies - design principles and balance sheet implications},
+ publisher = {Bank of England},
+ series = {Staff Working Paper},
+ number = {725},
+}
+
+@inproceedings{Lapid,
+ author = {Lapid, Ben and Avishai Wool},
+ year = {2018},
+ title = {{Cache-Attacks on the ARM TrustZone Implementations of AES-256 and AES-256-GCM via GPU-Based Analysis}},
+ booktitle = {\textit{International Conference on Selected Areas in Cryptography. Lecture Notes in Computer Science}},
+ volume = {11349},
+}
+
+@article{Lerner,
+ author = {Lerner, Josh and Jean Tirole},
+ year = {2005},
+ title = {{The Scope of Open Source Licensing}},
+ journal = {Journal of Law, Economics \& Organization},
+ volume = {21},
+ pages = {20-56},
+}
+
+@misc{Libra,
+ author = {{Libra Association}},
+ year = {2020},
+ title = {{Libra White Paper v2.0}},
+ url = {\url{https://libra.org/en-US/white-paper}},
+}
+
+@inproceedings{Lim,
+ author = {Lim, Chae Hoon and Phil Joong Lee},
+ year = {1997},
+ title = {A key recovery attack on discrete log-based schemes using a prime order subgroup},
+ booktitle = {\textit{CRYPTO 1997. Lecture Notes in Computer Science}},
+ volume = {1294},
+}
+
+@InCollection{Lyons,
+ author = {Lyons, Richard K. and Ganesh Viswanath-Natraj},
+ year = {2020},
+ title = {{What Keeps Stablecoins Stable?}},
+ publisher = {National Bureau of Economic Research},
+ series = {NBER Working Paper Series},
+ number = {27136},
+ month = {May},
+}
+
+@article{Mancini-Griffoli,
+ author = {Mancini-Griffoli and Maria Soledad Martinez Peria and Itai Agur and Anil Ari and John Kiff and Adina Popescu and Celine Rochon},
+ year = {2018},
+ title = {{Casting Light on Central Bank Digital Currency}},
+ journal = {IMF Staff Discussion Notes},
+ volume = {18/08},
+ publisher = {International Monetary Fund},
+}
+
+@misc{Nakamoto,
+ author = {Nakamoto, Satoshi},
+ year = {2008},
+ title = {{Bitcoin: A Peer-to-Peer Electronic Cash System}},
+ url = {\url{https://www.bitcoin.com/bitcoin.pdf}},
+}
+
+@book{Narayanan,
+ author = {Narayanan, Arvind and Joseph Bonneau and Edward Felten and Andrew Miller and Steven Goldfeder},
+ year = {2016},
+ title = {Bitcoin and Cryptocurrency Technologies: A Comprehensive Introduction},
+ publisher = {Princeton University Press},
+}
+
+@misc{Niepelt,
+ author = {Niepelt, Dirk},
+ year = {2020},
+ title = {Digital money and central bank digital currency: An executive summary for policymakers},
+ howpublished = {\url{https://voxeu.org/article/digital-money-and-central-bank-digital-currency-executive-summary}},
+}
+
+@inproceedings{Okamoto,
+ author = {Okamoto, Tatsuaki},
+ year = {1995},
+ title = {{An Efficient Divisible Electronic Cash Scheme}},
+ booktitle = {\textit{Advances in Cryptology --- CRYPT0'95: 15th Annual International Cryptology Conference Santa Barbara, California, USA, 27--31 agosto, 1995 Proceedings}},
+ editor = {Ed. di Don Coppersmith},
+ publisher = {Springer-Verlag Berlin Heidelberg},
+ pages = {438--451},
+}
+
+@article{Pinto,
+ author = {Pinto, S. and N. Santos},
+ year = {2019},
+ title = {{Demystifying ARM TrustZone: A Comprehensive Survey}},
+ journal = {ACM Computing Surveys},
+ volume = {51},
+ number = {6},
+ month = {January},
+ pages = {1--31}
+}
+
+@article{Rivest,
+ author = {Rivest, Ronald L. and Adi Shamir and Leonard Adleman},
+ year = {1978},
+ title = {{A Method for Obtaining Digital Signatures and Public Key Cryptosystems}},
+ journal = {Comm. ACM},
+ volume = {21},
+ number = {2},
+}
+
+@book{Solove,
+ author = {Solove, Daniel J.},
+ year = {2011},
+ title = {Nothing to Hide: The false tradeoff between privacy and security},
+ publisher = {New Haven \& London: Yale University Press},
+}
+
+@article{Soukup,
+ author = {Soukup, Michael and Bruno Muff},
+ year = {2007},
+ title = {{Die Postcard lässt sich fälschen}},
+ journal = {Sonntagszeitung},
+ month = {April},
+ day = {22},
+}
+
+@article{Stallman,
+ author = {Stallman, Richard},
+ year = {1985},
+ title = {{The GNU manifesto}},
+ journal = {Dr. Dobb's Journal of Software Tools},
+ volume = {10},
+ number = {3},
+ pages = {30--35},
+}
+
+@TechReport{Riksbank,
+ author = {{Sveriges Riksbank}},
+ year = {2020},
+ title = {{The Riksbank's e-krona project}},
+ month = {February},
+ institution = {Sveriges Riksbank},
+ url = {\url{https://www.riksbank.se/globalassets/media/rapporter/e-krona/2019/the-riksbanks-e-krona-pilot.pdf}},
+}
+
+@misc{Wojtczuk,
+ author = {Wojtczuk, Rafal and Joanna Rutkowska},
+ year = {2009},
+ title = {{Attacking Intel Trusted Execution Technology}},
+ howpublished = {BlackHat-DC 2009},
+}
+
+@article{Yalta2010,
+ author = {Yalta, A. Talha and A. Yasemin Yalta},
+ year = {2010},
+ title = {{Should Economists Use Open Source Software for Doing Research?}},
+ journal = {Computational Economics},
+ volume = {35},
+ pages = {371--394},
+}
+
+@article{Yalta2008,
+ author = {Yalta, A. Talha and Riccardo Lucchetti},
+ year = {2008},
+ title = {{The GNU/Linux Platform and Freedom Respecting Software for Economists}},
+ journal = {Journal of Applied Econometrics},
+ volume = {23},
+ pages = {279-286},
+}
diff --git a/doc/cbdc-it/cbdc-it.tex b/doc/cbdc-it/cbdc-it.tex
new file mode 100644
index 000000000..a5c939086
--- /dev/null
+++ b/doc/cbdc-it/cbdc-it.tex
@@ -0,0 +1,1304 @@
+\documentclass[a4paper]{article}
+\usepackage[T1]{fontenc}
+\usepackage[utf8]{inputenc}
+\usepackage[top=2cm,bottom=2cm]{geometry}
+\usepackage{url}
+\usepackage{amsmath}
+\usepackage{hyperref}
+\usepackage{graphicx}
+\usepackage{natbib}
+\usepackage[italian]{babel}
+\title{Come una banca centrale dovrebbe emettere una moneta digitale}
+\author{David Chaum\footnote{david@chaum.com} \\
+ xx Network \and
+ Christian Grothoff\footnote{christian.grothoff@bfh.ch} \\
+ BFH\footnote{Università di Scienze Applicate di Berna}
+ \ e Progetto GNU \and
+ Thomas Moser\footnote{thomas.moser@snb.ch} \\
+ Banca Nazionale Svizzera}
+\date{Questa versione: febbraio 2022 \\
+ Prima versione: maggio 2020}
+
+\setlength\parskip{5pt plus 1pt} % More space between paragraphs
+\addto\captionsitalian{\renewcommand{\figurename}{Diagramma}}
+\AtBeginDocument{\renewcommand{\harvardand}{\&}}
+\hyphenation{CBDC}
+
+\begin{document}
+
+\maketitle
+
+\begin{abstract}
+Con l'emergere di Bitcoin e delle criptovalute stabili (per es. Diem,
+già nota come Libra) recentemente proposte dai colossi del web, le
+banche centrali affrontano una crescente concorrenza da parte di
+operatori privati che offrono la propria alternativa digitale al
+contante fisico. Non trattiamo qui la questione normativa se una banca
+centrale debba emettere o meno una moneta digitale. Contribuiamo invece
+all'attuale dibattito di ricerca spiegando in che modo una banca centrale
+potrebbe farlo, se lo volesse. Proponiamo un sistema basato su token
+senza tecnologia di registro distribuito, e mostriamo che le monete
+elettroniche emesse in passato, basate solo su software, possono essere
+migliorate per tutelare la privacy nelle transazioni, soddisfare i
+requisiti normativi in modo efficace e offrire un livello di protezione
+resistente ai computer quantistici contro il rischio sistemico per
+la privacy. Né la politica monetaria né la stabilità del sistema
+finanziario sarebbero realmente interessate da questo sistema, dal
+momento che una moneta emessa in questo modo replicherebbe il contante
+fisico anziché i depositi bancari. \\
+
+JEL: E42, E51, E52, E58, G2
+\\
+
+Parole chiave: monete digitali, banca centrale, CBDC, firma cieca (\textit{blind signatures}),
+criptovalute stabili, \textit{stablecoins}
+\end{abstract}
+
+\vspace{40pt}
+
+\section*{Ringraziamenti}
+Vorremmo ringraziare Michael Barczay, Roman Baumann, Morten Bech,
+Nicolas Cuche, Florian Dold, Andreas Fuster, Stefan Kügel, Benjamin
+Müller, Dirk Niepelt, Oliver Sigrist, Richard Stallman, Andreas Wehrli
+e tre collaboratori anonimi per i loro commenti e suggerimenti. Le
+posizioni, le opinioni, i risultati e le conclusioni o raccomandazioni
+espresse in questo documento sono strettamente quelle degli autori.
+Non riflettono necessariamente le posizioni della Banca nazionale
+svizzera (BNS). La BNS declina ogni responsabilità per eventuali
+errori, omissioni o inesattezze che dovessero comparire nel documento.
+
+Traduzione: Dora Scilipoti, con contributi da Luca Saiu
+\newpage
+
+%\tableofcontents
+
+%\bibpunct{(}{)}{ e }{a}{}{,}
+
+\section{Introduzione}\label{1.-introduzione}
+
+Dall'avvento dei personal computer negli anni ottanta, e più
+specificamente da quando nel 1991 la \textit{National Science
+Foundation} revocò le restrizioni sull'uso di Internet per scopi
+commerciali, c'è stata una ricerca sulla creazione di moneta digitale
+per i pagamenti online. La prima proposta è stata quella
+di~\cite{Chaum1983}. Sebbene tali metodi siano stati attuati, non hanno
+preso piede; le carte di credito sono invece diventate il metodo più
+diffuso per i pagamenti online. La proposta di~\cite{Nakamoto} per una
+versione puramente \textit{peer-to-peer} di moneta digitale e il
+conseguente lancio di Bitcoin avvenuto con successo hanno inaugurato
+una nuova era di ricerca e sviluppo di valute digitali. La piattaforma
+CoinMarketCap elenca oltre 5.000 criptovalute. Recentemente le banche
+centrali hanno iniziato a considerare, o almeno a studiare,
+l'emissione di monete digitali~\cite[vedi][]{AuerBoehme,AuerCornelli,Boar,Kiff,Mancini-Griffoli}.
+
+Attualmente, le banche centrali emettono due tipi di moneta: (i)
+riserve sotto forma di conti di regolamento presso le banche centrali,
+destinate solo agli operatori dei mercati finanziari, e (ii) divisa
+disponibile per tutti sotto forma di banconote. Di conseguenza, la
+letteratura sulla moneta digitale di banca centrale (\textit{Central Bank
+Digital Currency} - CBDC) distingue tra (a) CBDC all'ingrosso ad
+accesso ristretto e (b) CBDC al dettaglio disponibile per il
+pubblico~\cite[si veda, ad esempio,][]{Bech}.
+Una CBDC all'ingrosso sarebbe meno destabilizzante per il sistema attuale
+dato che le banche e gli operatori dei mercati finanziari hanno già
+accesso alla moneta digitale della banca centrale sotto forma di conti
+presso questa istituzione, che utilizzano per regolare i pagamenti
+interbancari. La domanda qui è se la tokenizzazione della moneta di banca
+centrale e la tecnologia di registro distribuito (\textit{Distributed Ledger
+Technology} - DLT) offrano vantaggi particolari rispetto ai sistemi con
+regolamento lordo in tempo reale (\textit{Real-Time Gross Settlement} - RTGS)
+esistenti. Finora la risposta è negativa, almeno per i pagamenti
+interbancari nazionali~\cite[vedi][]{Chapman}.
+
+Una CBDC al dettaglio, che sarebbe una nuova forma di moneta di banca
+centrale disponibile per il pubblico, potrebbe essere più destabilizzante
+per il sistema attuale, a seconda di come è progettata. Più una CBDC
+compete con i depositi delle banche commerciali, maggiore è la minaccia
+ai finanziamenti bancari, con effetti potenzialmente negativi sul credito
+bancario e sull'attività economica~\cite[vedi][]{Agur}. Tuttavia, una
+CBDC al dettaglio potrebbe anche essere
+vantaggiosa~\cite[vedi][]{Bordo,Berentsen,Bindseil,Niepelt,Riksbank,BoE}.
+Mettere a disposizione di tutti una moneta elettronica di banca centrale
+esente dal rischio di controparte potrebbe migliorare la stabilità e la
+resilienza del sistema di pagamenti al dettaglio. Potrebbe inoltre fornire
+un'infrastruttura di pagamento neutrale per incoraggiare la concorrenza,
+l'efficienza e l'innovazione. Nel complesso, è probabile che i costi e i
+benefici di una CBDC al dettaglio differiscano da un paese all'altro. Per
+il punto di vista della Banca nazionale svizzera, che non ha in programma
+l'emissione di una CBDC al dettaglio, si veda~\cite{Jordan}.
+
+Nel presente documento analizziamo la CBDC al dettaglio, ma senza
+affrontare la questione se una banca centrale \emph{debba o meno} emetterla.
+Ci concentriamo invece sul possibile design di una CBCD. L'interesse
+per la progettazione di CBDC è recentemente aumentato
+considerevolmente~\cite[si veda, ad esempio,][]{Allen,BoE}. Il design che
+proponiamo differisce notevolmente da altre proposte. Il nostro sistema
+si basa sulla tecnologia eCash descritta da~\cite{Chaum1983} e \cite{Chaum1990},
+migliorandola. In particolare, proponiamo una CBDC basata su token, solo
+con software e senza tecnologia di registro distribuito. La DLT è
+un'architettura interessante in assenza di un operatore centrale o se le
+entità che interagiscono non accettano di nominare un operatore centrale
+fidato. Questo non è certo il caso di una CBDC al dettaglio emessa da una
+\emph{banca centrale}. Distribuire il registro delle transazioni della
+banca centrale con una \textit{blockchain} non fa che aumentare i costi
+di transazione; non porta alcun vantaggio tangibile nell'implementazione
+da parte di una banca centrale. L'utilizzo della DLT per emettere moneta
+digitale può essere utile in assenza di una banca centrale (ad esempio,
+il progetto Sovereign delle Isole Marshall) o se l'intenzione esplicita
+è quella di fare a meno di una banca centrale (ad esempio,
+Bitcoin).\footnote{Potrebbero esserci casi opportuni di utilizzo della
+DLT per le infrastrutture dei mercati finanziari, come gli scambi digitali,
+dove sorge la questione di come incorporare la moneta della banca centrale
+all'interno di una struttura DLT per eseguire i regolamenti. Tuttavia,
+in tali situazioni i potenziali benefici della DLT, ad esempio costi
+inferiori o riconciliazione automatica, non derivano da un'emissione
+decentralizzata di moneta di banca centrale.}
+
+La CBDC basata su token che proponiamo consente anche di preservare
+una caratteristica fondamentale del contante fisico: la privacy nelle
+transazioni. Spesso si sostiene che l'uso della crittografia per la
+tutela della privacy richieda così tanta potenza di calcolo da rendere
+impraticabile la sua implementazione su dispositivi
+portatili~\cite[vedi][]{Allen}. Sebbene questo possa essere vero nel
+caso di una tecnologia di registro distribuito, dove la tracciabilità
+delle transazioni è necessaria per prevenire la doppia spesa~\cite[][]{Narayanan},
+non lo è nel caso proposto in questo documento, dove si ha un protocollo
+di firma cieca di tipo Chaum e la partecipazione di una banca centrale.
+La nostra CBDC, basata su firme cieche e un'architettura a due livelli,
+garantisce una tutela della privacy nelle transazioni perfetta e
+quanto-resistente, fornendo al contempo protezioni sociali che sono di
+fatto più potenti rispetto a quelle delle banconote per la lotta al
+riciclaggio di denaro (\textit{Anti-Money Laundering} - AML) e al
+finanziamento del terrorismo (\textit{Counter Terrorism Financing} - CFT).
+
+La privacy nelle transazioni è importante per tre motivi. In primo luogo,
+protegge gli utenti dal potenziale abuso di monitoraggio e sorveglianza
+da parte dei governi. Anche se si pensa di non avere nulla da nascondere,
+i piani di sorveglianza di massa restano problematici, se non altro per
+il rischio di errori e abusi, soprattutto se condotti senza trasparenza
+e responsabilità~\cite[vedi][]{Solove}. In secondo luogo, la privacy nelle
+transazioni protegge gli utenti dallo sfruttamento dei dati da parte dei
+fornitori di servizi di pagamento. Infine, salvaguarda gli utenti dalla
+controparte nelle transazioni in quanto esclude possibili comportamenti
+opportunistici successivi o rischi per la sicurezza dovuti a negligenza
+o mancata protezione dei dati dei clienti~\cite[vedi][]{Kahn2005}.
+
+Questo documento è strutturato come segue: nella Sezione II si spiega
+la differenza tra la moneta di banca centrale e altri tipi di moneta.
+Nella Sezione III si esaminano i modelli di CBDC tipici e generici prima
+di proporre il nostro progetto nella Sezione IV. Si considerano poi
+gli aspetti normativi e le politiche (V) e il relativo lavoro (VI).
+Infine, si conclude (VII).
+
+
+\section{Cos'è la moneta di banca centrale?}
+ \label{2.-cos'è-la-moneta-di-banca-centrale}
+
+La moneta è un attivo che può essere utilizzato per acquistare beni e
+servizi. Per essere considerato moneta, l'attivo deve essere accettato
+da entità diverse dall'emittente. Ecco perché i voucher, ad esempio,
+non sono considerati moneta. La moneta autentica deve essere
+\emph{comunemente} accettata come mezzo di scambio. Sebbene la moneta
+abbia altre funzioni, ad esempio come unità di conto e riserva di valore,
+la sua caratteristica distintiva è la sua funzione di mezzo di scambio.
+Normalmente l'unità di conto (cioè come avvengono la fissazione dei
+prezzi e la contabilizzazione dei debiti) coincide per ragioni
+pratiche con il mezzo di scambio. Una separazione può tuttavia
+verificarsi se il valore del mezzo di scambio manca di stabilità
+rispetto ai beni e servizi scambiati.\footnote{Ciò può accadere
+spontaneamente in un ambito caratterizzato da un'inflazione elevata,
+ad esempio quando i prezzi sono quotati in USD ma i pagamenti vengono
+effettuati in valuta locale. Lo stesso vale per i pagamenti in Bitcoin,
+dove i prezzi sono solitamente fissati in USD o altre valute locali a
+causa dell'elevata volatilità del Bitcoin. Una separazione può anche
+essere progettata appositamente, come nel caso
+dell'\textit{Unidad de Fomento} (UF) in Cile o i Diritti Speciali di
+Prelievo (DSP) del Fondo Monetario Internazionale (FMI). Tuttavia,
+anche in questi casi lo scopo è quello di avere un'unità di conto più
+stabile.} La moneta deve anche essere una riserva di valore per fungere
+da mezzo di scambio perché deve preservare il suo potere d'acquisto tra
+il momento in cui si riceve e quello in cui si spende. In ogni modo,
+ci sono molti altri attivi che fungono da riserva di valore, come azioni,
+obbligazioni, metalli preziosi e immobili. Pertanto, la caratteristica
+di riserva di valore non è distintiva della moneta.
+
+In un'economia moderna, il pubblico utilizza due tipi diversi di
+moneta: (a) moneta statale e (b) moneta privata. La moneta statale viene
+generalmente emessa dalla banca centrale, che agisce in qualità di
+agente dello Stato. La moneta della banca centrale è disponibile per
+alcune istituzioni finanziarie sotto forma di depositi presso la banca
+centrale (riserve) e per il pubblico sotto forma di valuta (banconote e
+monete), nota anche come «contante». In una economia moderna con valuta
+fiat, tale moneta non ha un valore intrinseco. Legalmente è una passività
+della banca centrale, sebbene non sia rimborsabile. Nella maggior parte
+dei paesi, la moneta della banca centrale è definita come avente corso
+legale, il che significa che deve essere accettata per il pagamento dei
+debiti monetari, comprese le tasse e le sanzioni legali. Sebbene ciò
+garantisca un certo valore alla moneta della banca centrale, lo status
+di corso legale non è sufficiente per mantenere un valore stabile. È la
+politica monetaria della banca centrale che mantiene il valore della
+moneta. Mantenere la stabilità dei prezzi, vale a dire un valore stabile
+della moneta rispetto a quello dei beni e dei servizi scambiati, è
+infatti una delle principali responsabilità delle banche centrali.
+
+La maggior parte dei pagamenti in un'economia moderna vengono effettuati
+con moneta privata emessa dalle banche commerciali ed è costituita da
+depositi bancari a vista che le persone detengono presso queste banche.
+Sono depositi che si posssono utilizzare mediante assegni, carte di
+debito, carte di credito e altri mezzi di trasferimento di denaro e
+costituiscono una passività della banca commerciale di riferimento. Una
+caratteristica fondamentale di questi depositi è che le banche commerciali
+garantiscono la convertibilità su richiesta in moneta della banca centrale
+ad un prezzo fisso, vale a dire, alla pari. I depositanti possono prelevare
+i propri fondi in contante o trasferirli ad un valore fisso di 1:1. Le
+banche commerciali mantengono stabile il valore della propria moneta
+ancorandola a quella della banca centrale.
+
+Tuttavia, in un sistema di riserva frazionaria, una banca commerciale,
+anche se solvibile, potrebbe non avere liquidità a sufficienza per
+onorare la sua promessa di convertire i depositi bancari in moneta
+della banca centrale (ad esempio, nel caso di una corsa agli sportelli)
+in modo tale che i clienti non possano prelevare i propri soldi. Una
+banca può anche diventare insolvente e fallire, e di conseguenza i
+clienti possono perdere denaro. Per questo motivo le banche commerciali
+sono soggette a regolamentazioni volte a mitigare tali rischi.
+
+Una differenza notevole tra la moneta di una banca centrale e la
+moneta privata emessa da una banca commerciale è, pertanto, che
+quest'ultima comporta un rischio di controparte. Una banca centrale
+può sempre adempiere ai suoi obblighi utilizzando la propria moneta
+non rimborsabile. In un'economia nazionale, la moneta della banca
+centrale è l'unico attivo monetario esento da rischi di credito e di
+liquidità. È pertanto l'attivo tipicamente preferito per regolare i
+pagamenti nelle infrastrutture dei mercati finanziari (si veda, per
+esempio, \textit{CPMI-IOSCO Principles for Financial Market
+Infrastructures}, 2012). Un'altra differenza risiede nella capacità
+della moneta della banca centrale di sostenere il sistema monetario
+nazionale fornendo un valore di riferimento con cui la moneta delle
+banche commerciali mantiene la piena convertibilità.
+
+A parte le banche commerciali, altre entità private tentano
+occasionalmente di emettere moneta; le criptovalute sono solo il
+tentativo più recente. Ma a differenza dei depositi bancari, tale
+moneta non è comunemente accettata come mezzo di scambio. Questo vale
+anche per Bitcoin, la criptovaluta più ampiamente accettata. Un
+ostacolo all'utilità delle criptovalute come mezzo di scambio è l'elevata
+volatilità del loro valore. In risposta a questo problema sono emerse
+le criptovalute stabili, cosiddette «stablecoins». Le
+\textit{stablecoin} generalmente tentano di stabilizzare il proprio
+valore in due modi: imitando le banche centrali (\textit{stablecoin}
+algoritmiche) o imitando le banche commerciali e strumenti di
+investimento (\textit{stablecoin} ancorate ad attivi).\footnote{Per una
+tassonomia delle \textit{stablecoin}, si veda~\cite{Bullmann}.}
+
+Le «\textit{stablecoin} algoritmiche» si basano su algoritmi per regolare
+l'offerta della moneta. In altre parole, cercano di stabilizzarne il
+prezzo attraverso una «politica monetaria algoritmica». Esistono
+esempi di tali \textit{stablecoin} (per esempio, Nubits), ma finora nessuna è
+riuscita a stabilizzare il proprio valore per molto tempo.
+
+Le \textit{stablecoin} «ancorate ad attivi» differiscono in base al tipo
+di attivo che utilizzano e ai diritti concessi ai possessori. I tipi di
+attivi generalmente utilizzati sono: valuta (riserve di banche centrali,
+banconote o depositi presso banche commerciali), materie prime (come
+l'oro), titoli e talvolta altre criptovalute. La capacità di un tale
+schema di stabilizzare il valore della moneta rispetto agli attivi
+sottostanti dipende in modo cruciale dai diritti legali acquisiti dai
+detentori della moneta. Se una \textit{stablecoin} è riscattabile ad un
+prezzo fisso (per es. 1 moneta = 1 USD \\ o 1 moneta = 1 oncia d'oro),
+la stabilità si può teoricamente ottenere.\footnote{Se possa stabilizzare
+il valore della \textit{stablecoin} anche rispetto ai beni e servizi
+scambiati dipende essenzialmente da quanto sia stabile il valore degli
+attivi su cui poggia rispetto al valore dei beni e servizi.} Tale strategia
+riproduce essenzialmente quella delle banche commerciali garantendo la
+convertibilità nell'attivo sottostante su richiesta. Tuttavia, a differenza
+dei depositi bancari, che in genere sono coperti solo parzialmente dalle
+riserve della banca centrale, le \textit{stablecoin} sono spesso
+completamente garantite dalle riserve di attivi sottostanti al fine di
+evitare il rischio di liquidità, principalmente perché non dispongono di
+tutele pubbliche tali come l'assicurazione dei depositi e il prestatore
+di ultima istanza che offrono invece le banche regolamentate.
+
+Le \textit{stablecoin} che utilizzano le valute come attivi sono anche
+dette «stablecoin a valuta fiat». Detenere il 100\% delle
+garanzie sotto forma di valuta (banconote o depositi bancari) non risulta però
+molto redditizio. Di conseguenza, i fornitori di \textit{stablecoin} hanno
+un buon motivo per rispiarmiare sugli attivi passando ad un sistema di
+riserva frazionaria, proprio come hanno fatto le banche
+commerciali.\footnote{L'incertezza sulla garanzia delle
+\textit{stablecoin} può essere uno dei motivi per cui vengono scambiate
+al di sotto del loro valore nel mercato parallelo~\cite[vedi][]{Lyons}.
+Casi simili si sono storicamente verificati anche con le banconote, quando
+erano ancora emesse dalle banche commerciali. Le banconote venivano
+scambiate a prezzi scontati nel mercato parallelo prima che l'emissione
+fosse nazionalizzata e trasferita alle banche centrali come monopolio.}
+Ciò comporta la riduzione degli attivi meno redditizi al minimo ritenuto
+necessario per soddisfare il requisito di convertibilità e l'aumento
+degli attivi liquidi a rendimento più elevato come i titoli di stato.
+Questo migliora la redditività ma aumenta nel contempo il livello
+di rischio. Tuttavia, anche se una \textit{stablecoin} fosse garantita
+interamente da depositi presso le banche commerciali, rimarrebbe comunque
+vulnerabile ai rischi di insolvenza del credito e di liquidità della
+relativa banca. Tale rischio può essere evitato effettuando i depositi
+presso la banca centrale in modo che siano le riserve di quest'ultima a
+garantire la \textit{stablecoin}. Tali \textit{stablecoin} sono state
+chiamate «CBDC sintetiche»~\cite[][]{Adrian}. È importante sottolineare che
+queste \textit{stablecoin} non sono moneta di banca centrale e quindi
+non costituiscono una CBDC in quanto non sono registrate come passività
+della banca centrale e, pertanto, rimangono soggette al rischio di
+controparte, ovvero al rischio di fallimento dell'emittente.
+
+Se una \textit{stablecoin} non è rimborsabile ad un prezzo fisso, la sua
+stabilità rispetto all'attivo sottostante non è garantita. Se la
+\textit{stablecoin} rappresenta comunque una quota di proprietà
+dell'attivo sottostante, lo schema ricorda quello di un fondo comune di
+investimento chiuso o di un fondo indicizzato quotato (\textit{Exchange-Traded
+Fund} - ETF) e si applicano i relativi rischi. Il valore
+della moneta dipenderà dal valore patrimoniale netto del fondo, ma il
+suo valore effettivo può variare. Se ci sono partecipanti autorizzati
+a creare e riscattare \textit{stablecoin} e quindi ad agire come
+arbitraggisti, come nel caso degli ETF e come previsto per la
+Diem~\cite[][]{Libra}, la deviazione si presume minima.
+
+Nel complesso, le \textit{stablecoin} hanno maggiori possibilità di
+diventare moneta rispetto alle criptovalute, soprattutto se
+adeguatamente regolamentate, anche se la disponibilità di CBDC
+limiterebbe notevolmente la loro utilità.
+
+\section{Modelli generici di CBDC} \label{3.-modelli-generici-di-cbdc}
+
+Come abbiamo visto, la CBDC sarebbe una passività della banca
+centrale. Due modelli possibili che si trovano nella letteratura
+sull'argomento sono (a) CBDC basata su conti e (b) CBDC basata su
+token (o sul valore). Questi modelli corrispondono ai due tipi
+esistenti di moneta delle banche centrali e ai relativi sistemi di
+pagamento~\cite[][]{Kahn2009}: riserve delle banche centrali
+(sistema basato su conti) e banconote (sistema basato su token). Un
+pagamento si verifica quando un'attivo monetario viene trasferito da un
+pagatore a un beneficiario. In un sistema basato su conti, il
+trasferimento avviene addebitando sul conto del pagatore e
+accreditando sul conto del beneficiario. In un sistema basato su
+token, il trasferimento avviene trasferendo il valore stesso o il
+token, ovvero un oggetto che rappresenta l'attivo monetario. Il miglior
+esempio di token è il contante (monete o banconote). Pagare in contanti
+equivale a consegnare una moneta o una banconota. Non è necessario
+registrare il trasferimento, il semplice possesso del token è
+sufficiente. Pertanto, le parti non sono tenute a rivelare la propria
+identità in nessun momento durante la transazione, entrambe possono
+rimanere anonime. Ciononostante, il beneficiario deve essere in grado di
+verificare l'autenticità del token. Questo è il motivo per cui le
+banche centrali investono notevoli risorse nelle caratteristiche di
+sicurezza delle banconote.
+
+È stato suggerito che la distinzione tra sistemi basati su conti e
+quelli basati su token non sia applicabile alle monete digitali~\cite[][]{Garratt}.
+Noi al contrario riteniamo che ci sia una differenza significativa. La
+differenza essenziale risiede nelle informazioni contenute nell'attivo.
+In un sistema basato su conti, gli attivi (i conti) sono riconducìbili
+ad una cronologia delle transazioni che include tutte le operazioni di
+credito e addebito dei conti. In un sistema basato su token, gli attivi
+(i token) contengono solo informazioni sul valore del token e
+sull'entità che lo ha emesso. I sistemi basati su token sono quindi
+l'unica possibilità per ottenere la stessa privacy nelle transazioni che
+offre il contante.\footnote{Sebbene il termine «Bitcoin» suggerisca
+l'uso di token, Bitcoin è un sistema basato su conti. L'unica differenza
+tra un sistema tradizionale basato su conti e una \textit{blockchain} è
+che i conti non sono conservati in un database centrale ma in un
+database decentralizzato di solo accodamento.}
+
+\subsection{CBDC basata su conti}\label{cbdc-basata-su-conti}
+
+Il modo più semplice per avviare una CBDC sarebbe consentire al
+pubblico di detenere conti deposito presso la banca centrale. Ciò
+comporta che la banca centrale si facesse responsabile dei controlli per
+conoscere i propri clienti (\textit{Know-Your-Customer} - KYC) e di
+garantire la conformità con i requisiti per la lotta al riciclaggio di
+denaro e al finanziamento del terrorismo. Ciò includerebbe non solo la
+gestione del processo iniziale di conoscenza del cliente, ma anche
+l'autenticazione dei clienti per le transazioni bancarie, la gestione
+delle frodi e delle autenticazioni false positive e false negative.
+Data la scarsa presenza fisica delle banche centrali nella società e il
+fatto che probabilmente oggi non siano disposte ad eseguire l'autenticazione
+dei cittadini su larga scala, qualsiasi CBDC basata su conti richiederebbe
+alla banca centrale di delegare questi compiti. Tutti i servizi di
+assistenza e manutenzione di tali conti potrebbero essere affidati ad
+operatori esterni~\cite[][]{Bindseil}, oppure le banche commerciali potrebbero
+essere obbligate per legge ad aprire conti presso la banca centrale per i
+propri clienti~\cite[][]{Berentsen}.
+
+Una CBDC basata su conti darebbe potenzialmente alla banca centrale
+l'accesso a molti dati aggiuntivi. Uno dei motivi di preoccupazione è
+che i governi potrebbero facilmente mettere in atto una sorveglianza
+di massa e imporre sanzioni ai singoli titolari dei conti. La natura
+centralizzata di tali interventi li rende poco costosi e facili da
+applicare nei confronti di persone o gruppi. Ci sono molti esempi di
+sorveglianza abusiva contro critici e oppositori politici, anche nelle
+democrazie. Si potrebbe argomentare che le banche centrali indipendenti
+siano in grado di salvaguardare tali informazioni dall'intrusione del
+governo e dagli abusi politici, ma ciò aprirebbe comunque una nuova
+strada alle pressioni politiche che minacciano l'indipendenza delle
+banche centrali. Inoltre, un database centrale sarebbe un obiettivo
+cospicuo per gli attacchi: anche l'accesso in sola lettura ad una parte
+del database potrebbe creare rischi significativi per le persone i cui
+dati sarebbero esposti.
+
+Se dovessero fornire conti bancari per il pubblico, le banche centrali
+entrerebbero in diretta concorrenza con le banche commerciali, competizione
+che comporterebbe due rischi. In primo luogo, potrebbe minacciare la base
+dei depositi delle banche e, all'estremo, portare alla disintermediazione
+bancaria. Ciò potrebbe influire negativamente sulla disponibilità di
+credito per il settore privato e, di conseguenza, sull'attività
+economica~\cite[][]{Agur}. La disintermediazione delle banche potrebbe anche
+condurre alla centralizzazione dell'allocazione del credito all'interno
+della banca centrale, con ripercussioni negative sulla produttività e
+sulla crescita economica. In secondo luogo, la possibilità per le persone
+di trasferire i propri depositi nel porto sicuro di una banca centrale
+potrebbe accelerare le corse agli sportelli nei periodi di crisi economica.
+
+Vi sono però argomentazioni contrarie. \cite{Brunnermeier}
+sostengono che i trasferimenti di fondi dai depositi ai conti
+CBDC porterebbero alla sostituzione automatica del finanziamento
+mediante depositi con il finanziamento tramite la banca centrale, il
+che andrebbe ad esplicitare la garanzia finora implicita di prestatore
+di ultima istanza delle banche centrali. \cite{Berentsen}
+sostengono che la concorrenza delle banche centrali potrebbe persino
+avere un effetto disciplinare sulle banche commerciali e quindi
+aumentare la stabilità del sistema finanziario, dato che queste ultime
+sarebbero costrette a consolidare la sicurezza dei propri modelli
+economici per eviatare corse agli sportelli.
+
+% References to Kumhof, Bindseil below should render like this:
+% valore (Kumhof &amp;amp; Noone, 2018 e Bindseil, 2020).
+% This was fixed by replacing "," with "and" to separate authors in the bib file.
+% It also fixed {Kumhof} to render as "Kumhof &amp; Noone".
+
+Esistono anche proposte per ridurre il rischio di disintermediazione
+restringendo o scoraggiando l'uso della CBDC come riserva di valore. Una
+delle proposte è di limitare la quantità di CBDC che si può possedere.
+Una seconda proposta consiste nell'applicare un tasso di interesse
+variabile ai conti in CBDC, in modo che il rendimento sia sempre
+sufficientemente inferiore a quello dei conti nelle banche commerciali,
+arrivando eventualmente fino a tassi negativi, in modo da rendere la CBDC
+meno attraente come riserva di valore~\cite[][]{Kumhof,Bindseil}. Oltre a ciò,
+per evitare le corse agli sportelli \citet{Kumhof} suggeriscono che la
+CBDC non dovrebbe essere emessa a fronte di depositi bancari ma solo a
+fronte di obbligazioni come i titoli di stato. Nel complesso, una CBDC
+basata su conti richiederebbe un'analisi più approfondita di queste
+problematiche.
+
+% Back to default style.
+%\bibpunct{(}{)}{ e }{,}{}{,}
+
+
+\subsection{CBDC Basata su token e legata al hardware}
+\label{cbdc-basata-su-token-e-legata-al-hardware}
+
+% References to Wojtczuk,Johnston,Lapid below do not render correctly in pdf. Should be:
+% compromesse (si veda, ad esempio, Wojtczuk &amp;amp; Rutkowska 2009, Johnston 2010 e Lapid &amp;amp; Wool 2018).
+% but we can only either use "," or "e", but not switch AFAIK.
+% This was fixed by replacing "," with "and" to separate authors in the bib file.
+% It also fixed {Katzenbeisser} to render as "Katzenbeisser et al."
+
+In alternativa ai conti deposito, una banca centrale potrebbe emettere
+token elettronici. Tecnicamente ciò richiede un sistema per garantire che
+i token elettronici non possano essere copiati facilmente. Le funzioni
+fisicamente non clonabili~\cite[vedi][]{Katzenbeisser} e le aree
+sicure nell'hardware~\cite[vedi][]{Alves,Pinto} sono due tecnologie
+possibili per la prevenzione della copia digitale. Le funzioni
+fisicamente non clonabili, tuttavia, non possono essere scambiate su
+Internet (eliminando di fatto l'uso principale delle CBDC) e le precedenti
+funzionalità di sicurezza nell'hardware per la prevenzione della copia
+sono state ripetutamente
+compromesse~\cite[si veda, ad esempio,][]{Wojtczuk,Johnston,Lapid}.
+
+Un vantaggio fondamentale delle CBDC basate su token rispetto a quelle
+basate su conti è che i sistemi tokenizzati funzionerebbero offline,
+ovvero, gli utenti potrebbero scambiare token (\textit{peer-to-peer})
+senza coinvolgere la banca centrale, proteggendo così la privacy e la
+libertà delle persone. Tuttavia, la disintermediazione che si verifica
+quando gli utenti possono scambiare token elettronici senza
+intermediari bancari che eseguano i controlli per la conoscenza dei
+clienti e le procedure per la lotta al riciclaggio di denaro e al
+finanziamento del terrorismo renderebbe difficile la lotta alla
+criminalità.
+
+% References to Soukup,Garcia,Kasper,CCC below do not render correctly in pdf. Should be:
+% L’esperienza (si veda, ad esempio, Soukup &amp;amp; Muff 2007, Garcia et al. 2008, Kasper et al. 2010 e CCC e.V. 2017) suggerisce
+% but we can only either use "," or "e", but not switch AFAIK.
+% This was fixed by replacing "," with "and" to separate authors in the bib file.
+
+Le schede SIM sono oggi il mezzo più ampiamente disponibile per un
+sistema di pagamento sicuro basato su hardware, ma comportano anche
+dei rischi. L'esperienza~\cite[si veda, ad esempio,][]{Soukup,Garcia,Kasper,CCC}
+suggerisce che qualsiasi dispositivo economicamente riproducibile in grado
+di memorizzare token con valore monetario, che una persona possa possedere
+e che consenta transazioni offline --- e quindi il furto mediante
+clonazione delle informazioni in esso contenute --- sarà l'obiettivo di
+attacchi di contraffazione riusciti non appena il valore economico
+dell'attacco risulti sostanziale. Tali attacchi provengono anche da
+utenti che forzano il proprio hardware~\cite[vedi][]{Allen}. Per
+limitare l'impatto di una compromissione, i sistemi con carte di pagamento
+che sono stati precedentemente implementati dipendono dalla resistenza
+alle manomissioni in combinazione con il rilevamento delle frodi.
+Tuttavia, il rilevamento delle frodi richiede la capacità di identificare
+i pagatori e tenere traccia dei clienti, il che non è compatibile con la
+privacy nelle transazioni.
+
+\section{Una CBDC basata su token progettata per tutelare la privacy}
+\label{4.-una-cbdc-basata-su-token-progettata-per-tutelare-la-privacy}
+
+La CBDC qui proposta è di tipo «solo software», semplicemente
+un'applicazione per smartphone che non richiede alcun hardware aggiuntivo.
+Il design fa affidamento su eCash e GNU Taler. Taler fa parte del progetto
+GNU, il cui fondatore, Richard Stallman, ha coniato il termine
+«\emph{Software Libero}», ora spesso indicato come \textit{Free/Libre
+and Open Source Software} (FLOSS).\footnote{Per ulteriori informazioni
+su GNU, si veda \url{https://www.gnu.org} e \cite{Stallman}. GNU Taler
+è rilasciato sotto la licenza libera \textit{GNU Affero General Public
+License} del Progetto GNU. Altri programmi del progetto GNU noti tra gli
+economisti sono \textit{R} e \textit{GNU Regression, Econometrics and
+Time-series Library} (GRETL). Per un'analisi dei vantaggi del FLOSS
+rispetto al software proprietario nel campo della ricerca, si
+veda~\cite{Baiocchi}, \cite{Yalta2008} e \cite{Yalta2010}.
+Sulle licenze libere e open source, si veda~\cite{Lerner}.} Il software
+è considerato libero se la sua licenza concede agli utenti quattro libertà
+essenziali: la libertà di eseguire il programma come si desidera, la
+libertà di studiare il programma e modificarlo, la libertà di ridistribuire
+copie del programma e la libertà di distribuire copie delle versioni
+modificate del programma. Il software libero non impedisce la
+commercializzazione; fornire supporto tecnico per il software è un modello
+di business standard per il FLOSS.
+
+Dato il gran numero di parti interessate coinvolte in una CBDC al
+dettaglio (la banca centrale, il settore finanziario, i venditori e
+i clienti) e l'importanza critica dell'infrastruttura, una CBDC al
+dettaglio deve essere basata sul FLOSS. Imporre una soluzione
+proprietaria, che comporta la dipendenza da un fornitore specifico,
+sarebbe probabilmente un ostacolo all'adozione fin dall'inizio. Con il
+FLOSS, tutte le parti interessate hanno accesso a ogni dettaglio della
+soluzione e il diritto di adattare il software alle proprie esigenze.
+Ciò facilita l'integrazione e migliora l'interoperabilità e la
+concorrenza tra i fornitori.\footnote{Tuttavia, l'hardware privato
+potrebbe avere un ruolo da svolgere. La protezione degli archivi delle
+chiavi e di alcune funzioni di controllo, ad esempio, può essere un'area
+dove l'hardware dedicato valutato solo da un numero limitato di esperti
+può presentare dei vantaggi, nella misura in cui tale sicurezza sia solo
+additiva.} Consente inoltre alla banca centrale di soddisfare i requisiti
+di trasparenza e responsabilità. I vantaggi del FLOSS riguardo la
+sicurezza sono anche ampiamente riconosciuti. La disponibilità del codice
+sorgente e la libertà di modificarlo facilitano l'identificazione degli
+errori e la loro rapida correzione. \footnote{Ad esempio, un bollettino
+sulla sicurezza informatica emesso dall'Agenzia per la sicurezza nazionale
+degli Stati Uniti (NSA) nell'aprile 2020 esorta gli utenti a dare la
+priorità al software libero nella scelta e nell'utilizzo dei servizi
+collaborativi per le comunicazioni su Internet: «Lo sviluppo open source
+garantisce trasparenza sulla robustezza del codice e la sua conformità
+alle migliori pratiche di programmazione, evitando l'introduzione di
+vulnerabilità o punti deboli che potrebbero mettere a rischio utenti e
+dati» (U/OO/134598-20).}
+
+Nell'architettura che proponiamo, tutte le interazioni tra consumatori
+e venditori si fanno con le banche commerciali, ma la creazione di moneta
+e il database sono forniti esclusivamente dalla banca centrale. Le banche
+commerciali autenticano i clienti quando ritirano CBDC così come i
+venditori o beneficiari quando le ricevono. Quando spendono CBDC,
+invece, i clienti o pagatori devono solo autorizzare le transazioni senza
+bisogno di identificarsi. I pagamenti risultano più economici, più facili
+e più veloci, evitando al contempo interferenze con la privacy~\cite[][]{Dold}.
+L'autenticazione dei clienti quando ritirano CBDC, nonché dei venditori
+o beneficiari quando le ricevono, consente altresì di adempire alle
+normative sulla conoscenza dei clienti e sulla lotta al riciclaggio di
+denaro e al finanziamento del terrorismo.
+
+La CBDC che si propone in questo documento è un vero e proprio
+strumento digitale al portatore perché quando l'utente preleva una
+somma di denaro sotto forma di numero, tale numero viene «accecato» o
+nascosto dallo smartphone con un'apposita crittografia. Nel sistema
+stesso, una moneta è una coppia di chiavi pubblica-privata dove la
+chiave privata è nota solo al proprietario della moneta.\footnote{In
+Bitcoin, un sistema basato su conti, la coppia di chiavi è un conto
+dove la chiave pubblica rappresenta l'«indirizzo» e quindi una sorta di
+«identità», anche se pseudonimo.} La moneta trae il suo valore
+finanziario dalla firma della banca centrale apposta sulla chiave
+pubblica della moneta. La banca centrale firma con la propria chiave
+privata e detiene più coppie di chiavi di valore per apporre la firma
+cieca su monete di diverso valore unitario. Il venditore può utilizzare
+la corrispondente «chiave pubblica» della banca centrale per verificare
+la firma. Tuttavia, al fine di garantire che la moneta non sia stata
+copiata e già ritirata da un altro beneficiario (cioè che non sia stata
+«spesa due volte»), il venditore deve depositare la moneta affinché la
+banca centrale possa confrontarla con un archivio di monete ritirate.
+Poiché né la banca commerciale né la banca centrale vedono il numero
+della moneta durante il prelievo, in seguito, quando il venditore
+deposita la moneta, non si sa quale utente l'abbia ritirata. L'accecamento
+e la privacy che ne deriva fanno di questa tipologia di CBDC un vero e
+proprio strumento digitale al portatore.
+
+Nell'analisi che segue forniamo una panoramica approfondita della
+tecnologia e mostriamo come si può integrare con il sistema bancario
+esistente per creare una CBDC. \citet{Dold} fornisce ulteriori
+dettagli.
+
+\subsection{Componenti fondamentali}\label{componenti-fondamentali}
+
+Di seguito si descrivono i componenti principali del protocollo, comprese
+le basi matematiche per una delle possibili rappresentazioni delle
+primitive crittografiche utilizzate, allo scopo di illustrare in
+che modo potrebbe funzionare un'implementazione. Considerando che
+esistono altri modelli matematici equivalenti per ciascun componente,
+presentiamo solo la più semplice delle soluzioni sicure a noi note.
+
+\emph{Firme digitali.} L'idea che sta alla base delle firme digitali in
+uno schema di firma a chiave pubblica è quella di garantire che il
+titolare della chiave privata sia l'unico in grado di firmare un
+messaggio, mentre la chiave pubblica consente a chiunque di verificare
+la validità della firma.\footnote{La crittografia a chiave pubblica è
+stata introdotta da~\cite{Diffie} e le prime implementazioni di firme
+digitali sono state quelle di~\cite{Rivest}.} Il risultato della funzione
+di verifica della firma è la dichiarazione binaria «vero» o «falso». Se
+il messaggio è firmato con la chiave privata che appartiene alla chiave
+pubblica di verifica, il risultato è «vero», altrimenti è «falso».
+Nella nostra proposta il messaggio è una moneta o una banconota con un
+numero di serie, e la firma della banca centrale ne attesta la
+validità. Sebbene GNU Taler utilizzi per impostazione predefinita le
+moderne firme EdDSA~\cite[vedi][]{Bernstein2012}, qui presentiamo un
+semplice schema di firma crittografica basato su RSA~\cite[][]{Rivest}, un
+sistema crittografico ben studiato.\footnote{Per un'analisi della
+lunga storia del crittosistema RSA e uno studio degli attacchi a questo
+sistema, si veda~\cite{Boneh}.} Tuttavia, in linea di principio, è
+possibile utilizzare qualsiasi tecnologia di firma crittografica
+(DSA, ECDSA, EdDSA, RSA, ecc.)
+
+
+Per generare una chiave RSA, il firmatario prende prima due grandi
+numeri primi indipendenti $p$ e $q$ e calcola $n = \emph{pq}$,
+nonché la funzione phi di Eulero
+$\phi(n) = (p - 1)(q - 1)$.
+Quindi, si può utilizzare qualsiasi $e$ con $1 < e < \phi(n)$ e
+$\gcd(e, \phi(n)) = 1$ per definire una chiave pubblica $(e,n)$.
+La condizione che il massimo comune denominatore ($\texttt{MCD}$) di $e$ e
+$\phi(n)$ debba essere 1 (cioè, che devono essere
+primi tra loro) assicura che l'inverso di
+$e \mod \phi(n)$ esista.
+Questo inverso è la
+corrispondente chiave privata $d$. Data $\phi(n)$, la chiave
+privata $d$ può essere calcolata mediante l'algoritmo esteso
+di Euclide tale che
+$d \cdot e \equiv 1 \mod \phi(n)$.
+
+Data la chiave privata $d$ e la chiave pubblica $(e, n)$, una semplice
+firma RSA
+$s$ su un messaggio $m$ è
+$s \equiv m^{d} \mod n$.
+Per verificare la firma si calcola
+$m' \equiv s^{e} \mod n$.
+Se $m'$ e $m$ corrispondono, la firma è valida e dimostra che il
+messaggio è stato firmato con la chiave privata che corrisponde alla
+chiave pubblica di verifica (autenticazione del messaggio) e che il
+messaggio non è stato modificato durante il transito (integrità del
+messaggio). In pratica, le firme vengono poste sull'hash dei messaggi
+piuttosto che sui messaggi stessi. Le funzioni di hash calcolano le
+impronte digitali dei messaggi (\textit{digest}), che sono identificatori
+univoci e brevi per i messaggi. Firmare un hash breve è molto più veloce
+che firmare un messaggio di grandi dimensioni, e la maggior parte degli
+algoritmi di firma funzionano solo su input relativamente brevi.\footnote{Nel
+caso del crittosistema RSA, il limite di lunghezza è di
+$\log_{2}n$ bit.}
+
+\emph{Firme cieche.} Utilizziamo le firme cieche introdotte
+da~\cite{Chaum1983} per tutelare la privacy degli acquirenti. Una firma
+cieca viene utilizzata per creare una firma crittografica per un messaggio
+senza rivelare al firmatario il contenuto del messaggio. Nella nostra proposta,
+ciò impedisce alle banche commerciali e alla banca centrale di poter risalire
+all'acquirente tracciando gli acquisti. In linea di principio, la nostra
+proposta funziona con qualsiasi sistema di firma cieca, ma la soluzione migliore
+rimane la variante basata su RSA descritta da~\cite{Chaum1983}.
+
+L'accecamento viene eseguito dai clienti, che accecano le proprie
+monete prima di trasmetterle alla banca centrale per la firma. I
+clienti non devono quindi affidare alla banca centrale la tutela della
+propria privacy. Inoltre, l'accecamento con RSA fornirebbe protezione
+della privacy anche contro gli attacchi informatici quantistici. La
+banca centrale, dal canto suo, predispone più coppie di chiavi di
+valore per apporre la firma cieca su monete di diverso valore
+unitario, e fornisce le corrispondenti chiavi pubbliche
+$(e, n)$ per tali valori.
+
+Sia $f$ il valore di hash di una moneta e quindi l'identificatore
+univoco per questa moneta. Il cliente che preleva la moneta prima
+genera un fattore di accecamento casuale $b$ e calcola
+$f' \equiv fb^{e} \mod n$
+con la chiave pubblica della banca centrale per quel valore.
+La moneta accecata $f'$ viene quindi trasmessa alla banca centrale per
+la firma. La banca centrale firma $f'$ con la sua chiave
+privata $d$ calcolando la firma cieca
+$s' \equiv \left(f' \right)^{d} \mod n$, appone
+la firma $s'$ alla moneta accecata $f'$ e restituisce la coppia
+$(s',f')$ al cliente. Il cliente può quindi rimuovere l'accecamento
+della firma calcolando
+$s \equiv s'b^{- 1} \mod n$.
+Ciò è possibile perché
+$\left( f' \right)^d = f^db^{ed} = f^db$, e quindi
+moltiplicando $s'$ con $b^{- 1}$ si ottiene $f^d$, che è una firma RSA
+valida su $f$ come prima:
+$s^e \equiv f^{de} \equiv f \mod n$.
+
+Nella proposta originale di Chaum, le monete erano dei semplici
+gettoni. Quel che vogliamo, invece, è che i consumatori possano
+utilizzare le firme digitali per stipulare contratti. A tal fine, ogni
+volta che un portafoglio digitale preleva una moneta, in primo luogo
+crea per la moneta una chiave privata casuale $c$ e calcola la
+corrispondente chiave pubblica $C$ per creare firme digitali con i
+normali sistemi di firma crittografica (come DSA, ECDSA, EdDSA e
+RSA). Quindi si deriva $f$ mediante una funzione di hash crittografica
+dalla chiave pubblica $C$, prima che la banca centrale ne apponga la
+firma cieca (utilizzando un nuovo fattore di accecamento casuale per
+ciascuna moneta). Ora il cliente può utilizzare $c$ per firmare
+elettronicamente gli acquisti, spendendo così la moneta.
+
+Come visto sopra, la banca centrale andrebbe a predisporre coppie di
+chiavi diverse per ogni valore unitario di moneta e pubblicherebbe le
+chiavi pubbliche che i clienti userebbero per prelevare denaro. Queste
+chiavi di valore, e quindi le monete, avrebbero una data di scadenza
+prima della quale dovrebbero essere spese o scambiate con monete
+nuove. Ai clienti verrebbe concesso un certo periodo di tempo per
+scambiare le monete. Un processo simile esiste per le banconote
+fisiche, dove le serie di banconote vengono regolarmente rinnovate per
+essere dotate delle più recenti caratteristiche di sicurezza, tranne
+per il fatto che le banconote generalmente rimangono in circolazione
+per decenni anziché per pochi anni o mesi.\footnote{In Svizzera,
+ad esempio, la Banca nazionale svizzera ha iniziato a ritirare dalla
+circolazione l'ottava serie di banconote nell'aprile 2016. Questa serie
+era stata messa in circolazione alla fine degli anni novanta. Dal 1
+gennaio 2020, tuttavia, tutte le banconote a partire dalla sesta serie
+(emesse nel 1976) fino alle serie future restano valide e possono essere
+scambiate a tempo indeterminato con banconote correnti.}
+
+Da un punto di vista tecnico, una data di scadenza offre due vantaggi.
+In primo luogo, migliora l'efficienza del sistema perché la banca
+centrale può cancellare i dati scaduti, evitando così di dover
+archiviare e poi cercare in un elenco sempre crescente di monete
+(spese) per rilevare una doppia spesa. In secondo luogo, riduce i
+rischi per la sicurezza dato che la banca centrale non deve
+preoccuparsi di attacchi alle proprie chiavi (private) di valore ($d$)
+scadute. Inoltre, anche se una chiave privata venisse compromessa, il
+periodo durante il quale l'attaccante può utilizzarla è breve. In aggiunta,
+l'addebito di una commissione di cambio consentirebbe alla banca centrale di
+applicare tassi di interesse negativi, se ritenuto necessario. La banca centrale
+potrebbe anche, se lo desidera, fissare un limite di conversione per cliente in
+considerazione dell'antiriciclaggio e l'antiterrorismo (soglia di «contante») o
+per motivi di stabilità finanziaria (per prevenire accaparramenti e corse agli
+sportelli).
+
+\emph{Protocollo di scambio di chiavi.} GNU Taler utilizza un protocollo
+di scambio di chiavi in un modo particolare per fornire un collegamento
+tra la moneta originale e il resto reso per quella stessa moneta. Ciò
+garantisce che il resto possa sempre essere reso senza compromettere
+la trasparenza del reddito e la privacy dei consumatori. Lo stesso
+meccanismo si può utilizzare per i rimborsi anonimi ai clienti. Il
+protocollo gestisce anche i guasti alla rete e ai componenti,
+assicurando che i pagamenti siano andati a buon fine o siano stati
+definitivamente annullati e che tutte le parti abbiano una prova
+crittografica dell'esito. Questo corrisponde all'incirca agli scambi
+atomici nei protocolli \textit{interledger} o allo scambio equo nei
+tradizionali sistemi \textit{e-cash}.
+
+La costruzione matematica più comune per un protocollo di scambio di
+chiavi è la costruzione~\cite{Diffie}, che
+consente a due parti di derivare una chiave segreta condivisa. A tale
+scopo, condividono due parametri di dominio $p$ e $g$, che possono
+essere pubblici, dove $p$ è un numero primo grande e $g$ è una radice
+primitiva modulo $p$.\footnote{Un intero $g$ è una radice primitiva
+modulo $p$ se per ogni intero $a$ coprimo a $p$ esiste un intero $k$
+per il quale
+$g^k \equiv a \mod p$.
+In pratica, $g$ dovrebbe essere una radice primitiva $(p-1)$-esima, detta
+anche generatore, al fine di prevenire attacchi a sottogruppi come quelli
+Pohlig-Hellman~\cite[vedi][]{Lim}.} Ora, le due parti scelgono le loro
+chiavi private \emph{a} e \emph{b}, che sono due numeri interi grandi.
+Con queste chiavi private e i parametri di dominio, generano le
+rispettive chiavi pubbliche
+$A \equiv g^{a} \mod p$ e $B \equiv g^{b} \mod p$.
+Ciascuna parte può ora utilizzare la propria chiave privata e la chiave
+pubblica dell'altra parte per calcolare la chiave segreta condivisa
+$k \equiv \left( g^b \right)^{a} \equiv \left( g^{a} \right)^{b} \equiv g^{\text{ab}} \mod p$.\footnote{
+Lo stesso meccanismo potrebbe essere utilizzato per garantire
+che le monete non vengano trasferite a terzi durante il prelievo. A
+questo scopo, gli utenti devono salvaguardare una chiave di identità a
+lungo termine. Il processo di prelievo potrebbe quindi essere
+costruito allo stesso modo di quello utilizzato da GNU Taler per dare
+il resto, tranne per il fatto che quando si preleva dal conto bancario
+del cliente verrebbe utilizzata la chiave d'identità a lungo termine
+del cliente al posto della moneta originale. Tuttavia, le garanzie
+sulla privacy potrebbero decadere se il cliente non protegge la chiave
+d'identità a lungo termine, con il conseguente rischio di furto di
+tutte le monete residue. Dato che il rischio nei trasferimenti a terzi
+quando si prelevano monete è basso, non è chiaro se questa riduzione
+del rischio possa essere un buon compromesso.}
+
+Per ottenere il resto, il cliente parte dalla chiave privata della
+moneta parzialmente spesa $c$. Sia $C$ la chiave pubblica corrispondente,
+per esempio
+$C = g^{c} \mod p$.
+Quando la moneta fu parzialmente spesa in precedenza, la banca centrale
+registrò la transazione relativa a $C$ nel proprio database. Per
+semplicità, assumiamo che esista un valore unitario che corrisponda
+esattamente a questo valore residuo. In caso contrario, il protocollo si
+riavvia finché non viene reso tutto il resto. Sia $(e,n)$ la
+chiave di valore per il resto da rendere.
+
+Per ottenere il resto, l'acquirente crea prima $\kappa$ chiavi di
+trasferimento private $t_{i}$ per \\
+$i \in \left\{ 1,\ldots,\kappa \right\}$ e calcola le
+corrispondenti chiavi pubbliche $T_{i}$. Queste chiavi di
+trasferimento $\kappa$ sono semplicemente coppie di chiavi
+pubbliche-private che consentono al cliente di eseguire localmente il
+protocollo di scambio di chiavi, con il cliente che gioca su entrambi
+i lati del processo, $\kappa$ volte tra $c$ e ogni $t_{i}$.
+Se si usa Diffie-Hellman come protocollo per lo scambio di chiavi, si
+ottiene
+$T_{i} \equiv g^{t_{i}} \mod p$.
+
+Il risultato è composto da tre trasferimenti
+$K_{i} \equiv \emph{KX}(c,t_{i})$. Il protocollo di scambio di chiavi
+può essere utilizzato in diversi modi per ottenere lo stesso valore
+$K_{i} \equiv \emph{KX}(C,t_{i}) = \emph{KX}(c,T_{i})$.
+Data $K_{i}$, il cliente utilizza una funzione crittografica hash $H$
+per ricavare i valori
+$(b_{i},c_{i}) \equiv H(K_{i})$, dove
+$b_{i}$ è un fattore di accecamento valido per la chiave di valore
+$(e,n)$ e $c_{i}$
+è una chiave privata per la nuova moneta da ottenere come resto.
+$c_{i}$ deve essere adatta sia per creare firme crittografiche sia per
+un uso futuro con il protocollo di scambio di chiavi
+(come $c$, per ottenere resto a partire dal resto).
+Sia $C_{i}$ la chiave pubblica corrispondente a $c_{i}$.
+Il cliente chiede quindi alla banca centrale di creare una firma cieca su
+$C_{i}$ per $i \in \{ 1,\ldots,\kappa\}$.\footnote{Se dovesse essere
+utilizzato il crittosistema RSA per le firme cieche, useremmo
+$f \equiv \emph{FDH}_{n}(C_{i})$, dove
+$\emph{FDH}_{n}()$
+è l'hash del dominio completo sul dominio $n$.} In questa richiesta, il
+cliente si impegna anche con le chiavi pubbliche
+$T_{i}$.
+La richiesta è autorizzata mediante una firma effettuata con la chiave
+privata $c$.
+
+Invece di restituire direttamente la firma cieca, la banca centrale
+chiede prima al cliente di dimostrare che ha utilizzato correttamente la
+costruzione di cui sopra fornendo
+$\gamma \in \left\{ 1,\ldots,\kappa \right\}$.
+Il cliente deve quindi mostrare alla banca centrale la
+$t_{i}$ per $i \neq \gamma$.
+La banca centrale può quindi calcolare
+$K_{i} \equiv \emph{KX}(C,t_{i})$ e ricavare i valori
+$(b_{i},c_{i})$. Se per tutte le
+$i \neq \gamma$ la $t_{i}$ fornita dimostra che il cliente ha utilizzato
+correttamente la costruzione, la banca centrale restituisce la firma
+cieca su $C_{\gamma}$.
+Se il cliente non fornisce una prova corretta, il valore residuo della
+moneta originale viene perso. Questo penalizza efficacemente coloro che
+tentano di eludere la trasparenza del reddito con un'aliquota fiscale
+stimata di $1 - \frac{1}{\kappa}$.
+
+Per evitare che un cliente cospiri con un venditore che sta tentando di
+evadere il fisco, la banca centrale consente a chiunque
+conosca $C$ di ottenere, in qualsiasi momento, i valori di
+$T_{\gamma}$
+e le firme cieche di tutte le monete collegate alla moneta originaria $C$.
+Ciò permette al possessore della moneta originaria, che conosce $c$, di
+calcolare
+$K_{\gamma} \equiv \emph{KX}( c,T_{\gamma})$
+e da lì ricavare
+$(b_{i},c_{i})$
+per, infine, rimuovere la firma cieca. Di conseguenza, un venditore che
+nasconde il proprio reddito in questo modo formerebbe solo un'accordo
+economico limitato con il cliente invece di ottenere il controllo esclusivo.
+
+\hypertarget{architettura-del-sistema}{%
+\subsection{Architettura del sistema}\label{architettura-del-sistema}}
+
+Uno degli obiettivi principali della nostra architettura è garantire
+che le banche centrali non debbano interagire direttamente con i
+clienti né conservare alcuna informazione su di loro, ma solo tenere
+un elenco delle monete spese. L'autenticazione è delegata alle banche
+commerciali, che dispongono già dell'infrastruttura necessaria. I
+protocolli di prelievo e deposito raggiungono la banca centrale
+tramite una banca commerciale in qualità di intermediaria. Dal punto
+di vista del cliente, il processo è analogo al prelievo di contanti da
+un bancomat. La transazione tra la banca commerciale dell'utente e la
+banca centrale avviene in background. La procedura per il prelievo di
+CBDC è illustrata nel diagramma~\ref{fig:fig1}.
+
+\begin{figure}[h!]
+ \includegraphics[width=\textwidth]{diagramma1-it.png}
+ \caption{Prelievo di CBDC}
+ \label{fig:fig1}
+\end{figure}
+
+Il cliente (1) invia i dati di accesso alla propria banca commerciale
+utilizzando le relative procedure di autenticazione e autorizzazione.
+Quindi il telefono (o il computer) del cliente ottiene la chiave di
+valore pubblica $(e, n)$ fornita dalla banca centrale per quel valore; (2)
+calcola quindi una coppia di chiavi per la moneta, con una chiave
+privata $c$ e una chiave pubblica $C$, e sceglie un fattore di accecamento
+$b$. La chiave pubblica della moneta viene quindi sottoposta a hash \\
+($\to$ $f$) e accecata ($\to$ $f'$). Quindi il dispositivo del cliente (3)
+invia $f'$ insieme all'autorizzazione a prelevare la moneta e ad
+addebitarla dal conto del cliente presso la banca commerciale tramite un
+canale sicuro stabilito. La banca commerciale (4) addebita quindi
+l'importo dal conto deposito del cliente, (5) autorizza digitalmente la
+richiesta utilizzando la firma digitale specifica della propria filiale
+e inoltra la richiesta e la moneta accecata alla banca centrale per la
+firma. La banca centrale (6) sottrae il valore della moneta dal conto
+della banca commerciale, appone la firma cieca sulla moneta
+utilizzando la chiave privata che detiene per il relativo valore e (7)
+restituisce la firma cieca $s'$ alla banca commerciale. La banca
+commerciale (8) inoltra la firma cieca $s'$ al portafoglio elettronico
+del cliente. Infine, il dispositivo del cliente (9) utilizza $b$ per
+rimuovere l'accecamento dalla firma ($\to$ $f$) e salva la moneta appena
+coniata $(c, s)$.
+
+Per spendere CBDC, la procedura è analoga al pagamento in contanti.
+Tuttavia, per consolidare la transazione, il venditore deve depositare
+la moneta. La procedura per spendere CBDC è illustrata nel diagramma 2.
+
+\begin{figure}[h!]
+ \includegraphics[width=\textwidth]{diagramma2-it.png}
+ \caption{Spendere e depositare CBDC}
+ \label{fig:fig2}
+\end{figure}
+
+Un cliente e un venditore negoziano un contratto commerciale. Il
+cliente (1) utilizza una moneta elettronica per firmare il contratto o
+l'atto di vendita con la chiave privata $c$ della moneta e trasmette la
+firma al venditore. La firma di una moneta su un contratto con una
+moneta valida è l'istruzione del cliente di pagare il venditore, che è
+identificato dal conto bancario nel contratto. Se una singola moneta
+non fosse sufficiente per coprire l'importo totale, i clienti possono
+firmare il contratto con più monete. Il venditore (2) convalida quindi
+la firma della moneta sul contratto e la firma $s$ della banca centrale
+su $f$, che corrisponde a quella della moneta $C$ con le rispettive
+chiavi pubbliche, e inoltra la moneta firmata (insieme alle
+informazioni sul conto del venditore) alla banca commerciale del
+venditore. La banca commerciale del venditore (3) conferma che il
+venditore è un suo cliente e inoltra la moneta firmata alla banca
+centrale. La banca centrale (4) verifica le firme e controlla il
+proprio database per accertarsi che la moneta non sia già stata spesa.
+Se tutto è in ordine, la banca centrale (5) aggiunge la moneta
+all'elenco delle monete spese, l'accredita sul conto della banca
+commerciale presso la banca centrale e (6) invia una conferma in tal
+senso alla banca commerciale. Quindi la banca commerciale (7)
+accredita la moneta sul conto del venditore e (8) gli invia una
+notifica. Il venditore (9) consegna il prodotto o servizio al cliente.
+L'intera operazione richiede poche centinaia di millisecondi.
+
+\hypertarget{considerazioni-sulla-sicurezza}{%
+\subsection{Considerazioni sulla sicurezza}
+\label{considerazioni-sulla-sicurezza}}
+
+Nella nostra proposta, occorre che la banca centrale gestisca un
+database e un servizio online ad alta disponibilità. Poiché le monete
+elettroniche possono essere copiate dagli utenti, solo con i controlli
+online si può prevenire in modo efficace la doppia spesa. Sebbene
+nella teoria esistano soluzioni per identificare a posteriori gli
+utenti che effettuano una doppia spesa~\cite[vedi][]{Chaum1990},
+queste soluzioni creano rischi economici sia per gli utenti che per la
+banca centrale a causa del ritardo nell'identificazione di
+transazioni fraudolente. Il rilevamento online della doppia spesa
+elimina questo rischio, ma significa anche che sarà impossibile
+effettuare le transazioni se la connessione Internet alla banca
+centrale non è disponibile.
+
+La banca centrale dovrà anche proteggere la riservatezza delle chiavi
+private che utilizza per firmare le monete e altri messaggi di
+protocollo. Se le chiavi di firma della banca centrale dovessero
+essere compromesse, ad esempio da un computer quantistico, da un
+attacco fisico ai \textit{datacenter} o anche da qualche nuovo algoritmo
+% FIXME:
+% forme alternative:
+% 1) "rimborsare AGLI utenti ... tutte le monete non spese"
+% 2) "rimborsare gli utenti ... DI tutte le monete non spese"
+%FIXED
+imprevisto, è possibile rimborsare agli utenti --- in tutta sicurezza e
+senza compromettere la privacy --- tutte le monete non spese. La banca
+centrale annuncerebbe la revoca della chiave tramite l'\textit{Application
+Programming Interface} (API), che verrebbe rilevata dai portafogli,
+avviando quindi il seguente protocollo di aggiornamento: l'utente
+svela alla banca centrale la chiave pubblica $C$ della moneta, la firma
+$s$ della banca centrale e il fattore di accecamento $b$, consentendo così
+alla banca centrale di verificare il legittimo prelievo dell'utente e
+di rimborsare il valore della moneta non spesa. Per rilevare una
+possibile compromissione della propria chiave, la banca centrale può
+monitorare il database in cerca di depositi che eccedano i prelievi.
+
+\subsection{Scalabilità e costi}\label{scalabilità-e-costi}
+
+Lo schema che proponiamo sarebbe efficiente ed economico quanto i
+moderni sistemi RTGS attualmente utilizzati dalle banche centrali.
+
+La scalabilità si riferisce al costo di aumentare la potenza di
+calcolo in modo che si possa concludere un numero crescente di
+transazioni in tempi adeguati. Il costo complessivo del sistema può
+essere basso in quanto la CBDC qui proposta si basa interamente su
+software. Le monete spese devono essere conservate fino alla scadenza
+della coppia di chiavi di valore utilizzata per firmare le monete, ad
+esempio tramite un ciclo annuale continuo, che mantiene limitata la
+dimensione del database. La potenza di calcolo e la larghezza di banda
+necessarie aumentano della stessa quantità per ogni transazione, spesa
+o deposito addizionali, dato che le transazioni sono intrinsecamente
+indipendenti l'una dall'altra. Questa ulteriore potenza si ottiene
+semplicemente aggiungendo più hardware, una pratica spesso conosciuta
+come partizionamento o \textit{sharding}. Grazie al cosiddetto
+\textit{consistent hashing}, le aggiunte di hardware non risultano
+dirompenti. Si può anche utilizzare qualsiasi tipo di database.
+
+Più nello specifico, la logica del \textit{front-end} presso la banca
+centrale deve solo eseguire poche operazioni di firma, e un singolo
+processore può eseguirne alcune migliaia al secondo~\cite[vedi][]{Bernstein2020}.
+Se un unico sistema non fosse sufficiente, è facile aggiungere altri
+server \textit{front-end} e invitare le varie banche commerciali a
+bilanciare le loro richieste nella \textit{server farm} o
+utilizzare un sistema di bilanciamento del carico per distribuire le
+richieste all'interno dell'infrastruttura della banca centrale.
+
+I server \textit{front-end} devono comunicare con un database per
+effettuare le transazioni e prevenire la doppia spesa. Un unico server
+di database moderno dovrebbe essere in grado di gestire in modo
+affidabile decine di migliaia di operazioni al secondo. Le operazioni
+possono essere facilmente distribuite su più server di database
+semplicemente assegnando a ciascuno un intervallo di valori da
+gestire. Tale configurazione garantisce che le singole transazioni non
+incrocino mai le partizioni. Pertanto, anche i sistemi \textit{back-end}
+dovrebbero scalare in modo lineare con le risorse di calcolo messe a
+disposizione, partendo sempre da una solida base di riferimento per un
+singolo sistema.
+
+I \textit{front-end} devono anche comunicare con i \textit{back-end} per
+mezzo di un'interconnessione. Queste interconnessioni possono
+supportare un gran numero di transazioni al secondo. La dimensione di
+una singola transazione è in genere di circa 1–10 kilobyte. Pertanto,
+i \textit{datacenter} di oggi, che scambiano informazioni a 400 Gbit/s,
+possono supportare milioni di transazioni al secondo.
+
+%FIXME:
+%
+% Sotto appare "Probabilmente + congiuntivo". Suggerirei
+% di cambiarlo con una forma all'indicativo. Qui si trova
+% una discussione a riguardo:
+% https://italian.stackexchange.com/questions/3653/probabilmente-indicativo-o-congiuntivo
+% Not incorrect but FIXED anyway.
+
+Infine, il costo totale del sistema è basso. Il costo principale è
+rappresentato dall'archiviazione a lungo termine di 1–10 kilobyte
+per transazione. Gli esperimenti su un prototipo di GNU Taler che
+utilizzava i prezzi di \textit{Amazon Web Service} hanno stabilito
+che il costo del sistema (archiviazione, larghezza di banda e capacità
+di calcolo) su larga scala sarebbe inferiore a 0,0001 USD per
+transazione~\cite[per i dettagli sui dati, si veda][]{Dold}.
+
+\section{Considerazioni normative e politiche}
+ \label{5.-considerazioni-normative-e-politiche}
+
+Nella soluzione che proponiamo, la banca centrale non conosce
+l'identità dei consumatori o dei venditori né l'importo totale delle
+transazioni, ma vede solo il momento in cui le monete elettroniche vengono
+rilasciate e quando vengono riscattate. Le banche commerciali continuano a
+fornire l'autenticazione cruciale di consumatori e venditori e, in particolare,
+custodiscono le informazioni che acquisiscono per la conoscenza dei clienti
+(KYC). Le banche commerciali osservano quando i venditori ricevono fondi e, se
+necessario, possono limitare la quantità di CBDC per transazione che
+un singolo venditore può ricevere. Inoltre, le transazioni sono
+collegate ai relativi contratti con i clienti. La conseguente
+trasparenza del reddito consente al sistema di soddisfare i requisiti
+delle normative sulla lotta al riciclaggio di denaro e al
+finanziamento del terrorismo (AML e CFT). In caso vengano rilevate
+anomalie nei redditi dei venditori, la banca commerciale e
+l'autorità fiscale o giudiziaria possono ottenere e ispezionare i
+contratti relativi ai pagamenti sospetti al fine di verificarne la
+legittimità. La trasparenza del reddito risultante è anche una forte
+misura contro l'evasione fiscale perché i venditori non possono
+sottodichiarare il proprio reddito o evadere le tasse sulle vendite.
+Nel complesso, il sistema implementa gli approcci~\textit{privacy-by-design}
+e \textit{privacy-by-default} (come richiesto, ad esempio,
+dal Regolamento generale sulla protezione dei dati dell'UE, GDPR). I
+venditori non apprendono necessariamente l'identità dei propri clienti,
+le banche possiedono solo le informazioni necessarie sulle attività dei
+propri clienti e la banca centrale non ha accesso ai dettagli sulle
+attività dei cittadini.
+
+In alcuni paesi le normative impongono limiti per i prelievi e i
+pagamenti in contanti. Tali restrizioni possono essere implementate
+anche per la CBDC nel progetto proposto. Ad esempio, è possibile
+stabilire una soglia per l'importo giornaliero che i consumatori possono
+prelevare, oppure limitare l'importo totale di CBDC che le banche
+commerciali possono convertire.
+
+La disintermediazione del settore bancario è uno dei rischi di
+instabilità finanziaria spesso sollevato per quanto riguarda la CBDC
+al dettaglio. In particolare, una CBDC al dettaglio potrebbe
+facilitare l'accumulo di ingenti somme di denaro della banca
+centrale, il che potrebbe avere un impatto negativo sul finanziamento
+alle banche mediante depositi perché il pubblico deterrebbe meno
+denaro sotto forma di depositi bancari. Per i paesi le cui valute
+fungono da valute rifugio, ciò potrebbe anche portare ad un aumento
+degli afflussi di capitali durante i periodi globali di avversione al
+rischio, dando luogo ad ulteriori pressioni sui tassi di cambio.
+Quello che quindi potrebbe rappresentare un serio problema nel caso di
+una CBDC basata su conti, lo sarebbe molto meno con una CBDC basata
+su token. Innanzitutto, l'accumulo di una CBDC basata su token comporta
+rischi di furto o perdita simili a quelli legati all'accumulo di
+contanti. Tenere poche centinaia di dollari su uno smartphone è
+probabilmente un rischio accettabile per molti, ma detenere una somma
+molto alta è probabilmente un rischio meno accettabile. Pertanto, non
+ci aspettiamo un accaparramento significativamente maggiore rispetto a
+quello del denaro fisico.
+
+Tuttavia, se l'accumulo o la massiccia conversione dei depositi
+bancari in CBDC dovessero destare preoccupazione, la banca centrale
+avrebbe diverse opzioni. Come si è spiegato, secondo il progetto
+proposto le banche centrali fissano una data di scadenza per tutte le
+chiavi di firma, il che implica che in una data prestabilita le monete
+firmate con quelle chiavi diventano non valide. Alla scadenza delle
+chiavi di valore, i consumatori devono scambiare con monete nuove le
+monete che erano state firmate con le vecchie chiavi; l'autorità di
+regolamentazione può facilmente fissare una soglia di conversione per
+cliente per creare un limite rigido alla quantità di CBDC che ogni
+individuo può accumulare. Inoltre, la banca centrale potrebbe addebitare
+commissioni, se necessario. Una commissione di aggiornamento quando le monete
+stanno per scadere significherebbe nella pratica tassi di interesse negativi
+sulla CBDC per limitare il suo fascino come riserva di valore, come
+suggerisce~\cite{Bindseil}. Si tratterebbe infatti della diretta attuazione
+dell'idea di Silvio Gesell di applicare una tassa di possesso sulla moneta,
+notoriamente citata da~\cite{Keynes} e ripresa da~\cite{Goodfriend},
+\cite{Buiter} e~\cite{Agarwal}.
+
+Per quanto riguarda le implicazioni in termini di politica monetaria,
+non dovrebbero esserci cambiamenti reali perché la nostra CBDC è
+progettata per replicare il contante piuttosto che i depositi bancari.
+L'emissione, il prelievo e il deposito della nostra CBDC corrispondono
+esattamente all'emissione, al prelievo e al deposito di banconote. È
+possibile che la velocità di circolazione di una CBDC al dettaglio sia
+diversa da quella del contante fisico, ma questo non dovrebbe
+rappresentare un problema significativo per la politica monetaria.
+
+\hypertarget{lavori-correlati}{%
+\section{Lavori correlati}\label{6.-lavori-correlati}}
+
+Come segnalato in precedenza, la CBDC che si propone in questo documento
+si basa su eCash e GNU Taler.\footnote{L'implementazione di eCash
+da parte della società DigiCash negli anni novanta è documentata su \\
+\url{https://www.chaum.com/ecash}.} A partire dalla proposta originale
+e-Cash di Chaum, la ricerca si è concentrata su tre questioni principali.
+In primo luogo, nella proposta originale di Chaum le monete avevano un
+valore fisso e potevano essere spese solo nella loro totalità. Pagare
+grandi somme con monete denominate in centesimi sarebbe stato poco
+efficiente; quindi~\cite{Okamoto}, \cite{Camenisch2005}, \cite{Canard}
+e~\cite{Dold} idearono modi per affrontare il problema. Queste soluzioni
+comprendono protocolli per dare il resto o rendere divisibili le monete.
+
+Un secondo problema riguarda gli errori nelle transazioni dovuti ad
+interruzioni della rete. In questo caso, il sistema deve garantire che
+i fondi rimangano in possesso del consumatore senza pregiudicare la
+privacy. L'\textit{Endorsed E-Cash} proposto da~\cite{Camenisch2007},
+così come da~\cite{Dold}, affrontano entrambi questo problema. Molte
+delle soluzioni violano le garanzie sulla privacy per i clienti che
+utilizzano queste funzionalità e tutte, tranne Taler, violano il
+requisito della trasparenza del reddito.
+
+La terza questione importante, spesso trascurata, è la trasparenza del
+reddito e quindi la conformità con i requisiti AML e KYC. \cite{Fuchsbauer}
+hanno deliberatamente progettato il loro sistema di disintermediazione
+per fornire una semantica più simile al contante. Tuttavia, la
+disintermediazione totale è in genere difficile da concialiare con le
+normative AML e KYC dato che diventa impossibile raggiungere qualsiasi
+livello di responsabilità. Un esempio di tale architettura è ZCash, un
+registro distribuito (\textit{ledger}) che nasconde dalla rete le
+informazioni sul pagatore, sul beneficiario e sull'importo della
+transazione, rendendolo quindi il sistema di pagamento perfetto per la
+criminalità online. Solo Taler offre sia una privacy costante per i
+clienti che la trasparenza del reddito, fornendo al contempo un sistema
+di resto efficiente, scambi atomici~\cite[vedi][]{Camenisch2007} e la
+possibilità di ripristinare i portafogli dal backup.
+
+Per quanto riguarda i sistemi di pagamento per le CBDC, \cite{Danezis}
+hanno progettato un \textit{ledger} scalabile per RSCoin. Si tratta
+fondamentalmente di un sistema RTGS che viene protetto utilizzando la
+stessa crittografia che si usa in Bitcoin. Come Taler, il design utilizza
+lo \textit{sharding} del database per consentire la scalabilità lineare.
+Tuttavia, la soluzione di Danezis e Meiklejohn non prevede alcuna
+disposizione per la privacy e manca di elementi per l'integrazione
+pratica del design con i sistemi e i processi bancari esistenti.
+
+L'EUROchain della Banca Centrale Europea\cite[vedi][]{ECB} è un altro
+prototipo di CBDC con registro distribuito. Simile all'architettura
+proposta in questo documento, EUROchain utilizza un'architettura a due
+livelli in cui le banche commerciali agiscono come intermediari. Una
+differenza cruciale è il modo in cui i sistemi cercano di combinare
+privacy e conformità con la normativa antiriciclaggio (AML). Mentre nel
+nostro progetto l'autorità di regolamentazione può imporre un limite
+alla somma di denaro elettronico che un titolare di conto bancario può
+prelevare in un determinato periodo di tempo, EUROchain emette un numero
+limitato di «voucher di anonimato» che garantiscono al destinatario un
+numero limitato di transazioni senza controlli AML. Poiché questi voucher
+sembrano essere privi di qualsiasi token di valore, non è chiaro come
+il design possa impedire l'emergere di un mercato nero per i «voucher
+di anonimato». Inoltre, la nozione di anonimato di EUROchain è molto
+diversa, in quanto i loro «voucher di anonimato» eliminano solo alcuni
+controlli AML, preservando la capacità delle banche commerciali di
+sapere in che modo i clienti spendono il denaro elettronico. Laddove chi
+paga utilizzando Taler interagisce direttamente con i venditori per
+spendere il proprio contante elettronico, il sistema EUROchain chiede
+ai pagatori di istruire le proprie banche commerciali per accedere alle
+CBDC. Pertanto, EUROchain non emette direttamente token di valore ai
+consumatori, affida invece ai consumatori il compito di autenticarsi
+presso la propria banca commerciale per accedere alle CBDC che la
+banca centrale detiene effettivamente in deposito a garanzia. Non è
+quindi evidente quali siano i vantaggi di EUROchain in termini di
+privacy, prestazioni o sicurezza rispetto all'attuale denaro in deposito.
+
+\section{Conclusione}\label{7.-conclusione}
+
+Con l'emergere di Bitcoin e valute digitali come Diem (già nota come
+Libra) recentemente proposte dai colossi del web, le banche centrali
+affrontano una crescente concorrenza da parte di operatori privati che
+offrono la propria alternativa digitale al contante fisico. Le decisioni
+delle banche centrali sull'emissione o meno di una CBDC dipendono dalla
+loro valutazione dei benefici e dei rischi di una CBDC. È probabile che
+questi vantaggi e rischi, nonché le circostanze giurisdizionali
+specifiche che definiscono l'ambito di applicazione di una CBDC al
+dettaglio, differiscano da un paese all'altro.
+
+Se una banca centrale decide di emettere una CBDC al dettaglio,
+proponiamo una CBDC basata su token che combina la privacy delle
+transazioni con la conformità alle normative KYC, AML e CFT. Tale CBDC
+non sarebbe in concorrenza con i depositi presso le banche commerciali,
+replicherebbe piuttosto il contante fisico, limitando quindi i rischi di
+stabilità finanziaria e di perturbazione della politica monetaria.
+
+Abbiamo dimostrato che lo schema qui proposto sarebbe efficiente ed
+economico quanto i moderni sistemi RTGS gestiti dalle banche centrali.
+I pagamenti elettronici con la nostra CBDC richiederebbero solo un
+semplice database e minuscole quantità di larghezza di banda per le
+transazioni. L'efficienza e l'economicità di questa soluzione, insieme
+alla maggiore facilità d'uso da parte del consumatore determinata dal
+passaggio dall'autenticazione all'autorizzazione, rendono questo schema
+probabilmente il primo a supportare l'annoso obiettivo dei micropagamenti
+online. Inoltre, l'uso di monete per firmare crittograficamente contratti
+elettronici consente anche l'impiego di contratti intelligenti. Ciò
+potrebbe anche portare all'emergere di applicazioni completamente nuove
+per i sistemi di pagamento. Sebbene il nostro sistema non sia basato su
+DLT, può essere facilmente integrato con tali tecnologie se richiesto
+dalle future infrastrutture del mercato finanziario.
+
+Altrettanto importante, una CBDC al dettaglio deve rimanere, come il
+contante fisico, un bene rispettoso della privacy sotto il controllo
+individuale dei cittadini. Lo schema proposto in questo studio soddisfa
+questo criterio e consente alle banche centrali di evitare gravi sfide
+alla loro politica monetaria e alla stabilità finanziaria sfruttando al
+contempo i vantaggi del passaggio al digitale.
+
+\newpage
+\bibliographystyle{agsm-mod}
+\bibliography{cbdc-it}
+\end{document}
diff --git a/doc/cbdc-it/cbdc.bib b/doc/cbdc-it/cbdc.bib
new file mode 100644
index 000000000..fe0ea6265
--- /dev/null
+++ b/doc/cbdc-it/cbdc.bib
@@ -0,0 +1,566 @@
+@article{Adrian,
+ author = {Adrian, Tobias and Tommaso Mancini-Griffoli},
+ year = {2019},
+ title = {The Rise of Digital Money},
+ journal = {IMF Fintech Note},
+ volume = {19/01},
+}
+
+@article{Agarwal,
+ author = {Agarwal, Ruchir and Miles S. Kimball},
+ year = {2019},
+ title = {Enabling Deep Negative Rates to Fight Recessions: A Guide},
+ journal = {IMF Working Paper},
+ volume = {19/84},
+}
+
+
+@article{Agur,
+ author = {Agur, Itai and Anil Ari and Giovanni Dell'Ariccia},
+ year = {2019},
+ title = {Designing Central Bank Digital Currencies},
+ journal = {IMF Working Paper},
+ volume = {19/252},
+}
+
+@article{Allen,
+ author = {Allen, Sarah and Srđjan Čapkun and Ittay Eyal and Giulia Fanti and Bryan A. Ford and James Grimmelmann and Ari Juels and Kari Kostiainen and Sarah Meiklejohn and Andrew Miller and Eswar Prasad and Karl Wüst and Fan Zhang},
+ year = {2020},
+ title = {Design Choices for Central Bank Digital Currency: Policy and Technical Considerations},
+ journal = {NBER Working Paper},
+ volume = {27634},
+}
+
+@article{Alves,
+ author = {Alves, Tiago and Don Felton},
+ year = {2004},
+ title = {TrustZone: Integrated hardware and software security},
+ journal = {ARM IQ},
+ volume = {3},
+ number = {4},
+ pages = {18--24},
+}
+
+@article{AuerBoehme,
+ author = {Auer, Raphael and Rainer Böhme},
+ year = {2020},
+ title = {The technology of retail central bank digital currency},
+ journal = {BIS Quarterly Review},
+ month = {March},
+ pages = {85--96},
+}
+
+@article{AuerCornelli,
+ author = {Auer, Raphael and Giulio Cornelli and Jon Frost},
+ year = {2020},
+ title = {Taking stock: ongoing retail {CBDC} projects},
+ journal = {BIS Quarterly Review},
+ month = {March},
+ pages = {97--98},
+}
+
+@booklet{BIS,
+ author = {{Bank for International Settlements}},
+ year = {2018},
+ title = {Central Bank Digital Currencies. Joint Report of the Committee on Payments and Market Infrastructures and Markets Committee},
+}
+
+@booklet{BoE,
+ author = {{Bank of England}},
+ year = {2020},
+ title = {Central Bank Digital Currency: Opportunities, Challenges and Design. Discussion Paper},
+ month = {March},
+}
+
+@article{Baiocchi,
+ author = {Baiocchi, Giovanni and Walter Distaso},
+ year = {2003},
+ title = {{GRETL}: Econometric Software for the {GNU} Generation},
+ journal = {Journal of Applied Econometrics},
+ volume = {18},
+ pages = {105-110},
+}
+
+@article{Bech,
+ author = {Bech, Morten and Rodney Garratt},
+ year = {2017},
+ title = {Central bank cryptocurrencies},
+ journal = {BIS Quarterly Review},
+ month = {September},
+ pages = {55--70},
+}
+
+@article{Berentsen,
+ author = {Berentsen, Aleksander and Fabian Schär},
+ year = {2018},
+ title = {The Case for Central Bank Electronic Money and the Non-case for Central Bank Cryptocurrencies},
+ journal = {Federal Reserve Bank of St. Louis Review},
+ volume = {100},
+ number = {2},
+ pages = {97--106},
+}
+
+@article{Bernstein2020,
+ author = {Bernstein, Daniel J. and Tanja Lange},
+ year = {2020},
+ title = {{eBACS}: {ECRYPT} Benchmarking of Cryptographic Systems},
+ url = {\url{https://bench.cr.yp.to}, accessed 17 March 2020},
+}
+
+@article{Bernstein2012,
+ author = {Bernstein, Daniel J. and Niels Duif and Tanja Lange and Peter Schwabe and Bo-Yin Yang},
+ year = {2012},
+ title = {High-speed high-security signatures},
+ journal = {Journal of Cryptographic Engineering},
+ volume = {2},
+ pages = {77--89},
+}
+
+@InCollection{Bindseil,
+ author = {Bindseil, Ulrich},
+ year = {2020},
+ title = {Tiered {CBDC} and the financial system},
+ publisher = {European Central Bank},
+ series = {ECB Working Paper},
+ number = {2351},
+ month = {January},
+}
+
+@article{Boar,
+ author = {Boar, Codruta and Henry Holden and Amber Wadsworth},
+ year = {2020},
+ title = {Impending arrival - a sequel to the survey on central bank digital currency},
+ journal = {BIS Papers},
+ volume = {107},
+}
+
+@article{Boneh,
+ author = {Boneh, Dan},
+ year = {1999},
+ title = {Twenty Years of Attacks on the {RSA} Cryptosystem},
+ journal = {Notices of the AMS},
+ volume = {42},
+ number = {2},
+ pages = {202--213},
+}
+
+
+@InCollection{Bordo,
+ author = {Bordo, Michael D. and Andrew T. Levin},
+ year = {2017},
+ title = {Central bank digital currency and the future of monetary policy},
+ publisher = {National Bureau of Economic Research},
+ series = {NBER Working Paper Series},
+ number = {23711},
+}
+
+@article{Brunnermeier,
+ author = {Brunnermeier, Markus and Dirk Niepelt},
+ year = {2019},
+ title = {On the Equivalence of Private and Public Money},
+ journal = {Journal of Monetary Economics},
+ volume = {106},
+ pages = {27--41},
+}
+
+@article{Buiter,
+ author = {Buiter, Willem H. and Nikolaos Panigirtzoglou},
+ year = {2003},
+ title = {Overcoming the Zero Bound on Nominal Interest Rates with Negative Interest on Currency: Gesell's Solution},
+ journal = {The Economic Journal},
+ volume = {113},
+ number = {490},
+ pages = {723--746},
+}
+
+@InCollection{Bullmann,
+ author = {Bullmann, Dirk and Jonas Klemm and Andrea Pinna},
+ year = {2019},
+ title = {In search for stability in crypto-assets: are stablecoins the solution?},
+ publisher = {European Central Bank},
+ series = {ECB Occasional Paper Series},
+ number = {230},
+}
+
+@inproceedings{Camenisch2007,
+ author = {Camenisch, Jan and Aanna Lysyanskaya and Mira Meyerovich},
+ year = {2007},
+ title = {Endorsed E-Cash},
+ booktitle = {2007 IEEE Symposium on Security and Privacy (SP'07)},
+ month = {May},
+ pages = {101--115},
+}
+
+@inproceedings{Camenisch2005,
+ author = {Camenisch, Jan and Susan Hohenberger and Anna Lysyanskaya},
+ year = {2005},
+ title = {Compact E-Cash},
+ booktitle = {Advances in Cryptology -- EUROCRYPT 2005: 24th Annual International Conference on the Theory and Applications of Cryptographic Techniques},
+ address = {Aarhus, Denmark},
+ month = {May},
+ day = {22-26},
+ editor = {Ed. by Ronald Cramer},
+ publisher = {Springer-Verlag Berlin Heidelberg},
+}
+
+
+
+@inproceedings{Canard,
+ author = {Canard, Sébastien and Aline Gouget},
+ year = {2007},
+ title = {Divisible e-cash systems can be truly anonymous},
+ booktitle = {Annual International Conference on the Theory and Applications of Cryptographic Techniques},
+ pages = {482--497},
+}
+
+
+
+@misc{CCC,
+ author = {{CCC e.V.}},
+ year = {2017},
+ title = {Chaos Computer Club hacks e-motor charging stations},
+ howpublished = {34c3},
+}
+
+@article{Chapman,
+ author = {Chapman, James and Rodney Garratt and Scott Hendry and Andrew McCormack and Wade McMahon},
+ year = {2017},
+ title = {Project {J}asper: Are Distributed Wholesale Payment Systems Feasible Yet?},
+ journal = {Financial System Review},
+ publisher = {Bank of Canada},
+ month = {June},
+ pages = {59--69},
+}
+
+@inproceedings{Chaum1983,
+ author = {Chaum, David},
+ year = {1983},
+ title = {Blind signatures for untraceable payments},
+ booktitle = {Advances in Cryptology: Proceedings of Crypto `82},
+ pages = {199--203},
+}
+
+@inproceedings{Chaum1990,
+ author = {Chaum, David and Amos Fiat and Moni Naor},
+ year = {1990},
+ title = {Untraceable electronic cash},
+ booktitle = {Advances in Cryptology: Proceedings of CRYPTO '88},
+ pages = {319--327},
+}
+
+@inproceedings{Danezis,
+ author = {Danezis, George and Sarah Meiklejohn},
+ year = {2016},
+ title = {Centrally Banked Cryptocurrencies},
+ booktitle = {23nd Annual Network and Distributed System Security Symposium, NDSS2016},
+ address = {San Diego, California, USA},
+ month = {February},
+ day = {21--24},
+ publisher = {The Internet Society},
+}
+
+@article{Diffie,
+ author = {Diffie, Whitfield and Martin Hellmann},
+ year = {1976},
+ title = {New Directions in Cryptography},
+ journal = {IEEE Trans. on Inf. Theory, IT-22},
+ pages = {644--654},
+}
+
+@phdthesis{Dold,
+ author = {Dold, Florian},
+ year = {2019},
+ title = {The {GNU} {T}aler System: Practical and Provably Secure Electronic Payments. PhD Thesis},
+ school = {University of Rennes 1},
+}
+
+@article{ECB,
+ author = {{European Central Bank}},
+ year = {2019},
+ title = {Exploring anonymity in central bank digital currencies},
+ journal = {In Focus},
+ number = {4},
+ month = {December},
+}
+
+@inproceedings{Fuchsbauer,
+ author = {Fuchsbauer, Georg and David Pointcheval and Damien Vergnaud},
+ year = {2009},
+ title = {Transferable constant-size fair e-cash},
+ booktitle = {International Conference on Cryptology and Network Security},
+ publisher = {Springer-Verlag Berlin Heidelberg},
+ pages = {226--247},
+}
+
+@inproceedings{Garcia,
+ author = {Garcia, Flavio and Gerhard de Koning Gans and Ruben Muijrers and Peter van Rossum and Roel Verdult and Ronny Wichers Schreur and Bart Jacobs},
+ year = {2008},
+ title = {Dismantling MIFARE Classic},
+ booktitle = {European Symposium on Research in Computer Security},
+}
+
+@article{Garratt,
+ author = {Garratt, Rod and Michael Lee and Brendan Malone and Antoine Martin},
+ year = {2020},
+ title = {Token- or Account-Based? A Digital Currency Can Be Both},
+ journal = {Liberty Street Economics},
+ publisher = {Federal Reserve Bank of New York},
+ month = {August},
+ day = {12},
+}
+
+@article{Goodfriend,
+ author = {Goodfriend, Marvin},
+ year = {2000},
+ title = {Overcoming the Zero Bound on Interest Rate Policy},
+ journal = {Journal of Money, Credit, and Banking},
+ volume = {32},
+ number = {4},
+ pages = {1007--1035},
+}
+
+@article{Johnston,
+ author = {Johnston, Casey},
+ year = {2010},
+ title = {PS3 hacked through poor cryptography implementation},
+ journal = {Ars Technica},
+ month = {December},
+ day = {30},
+}
+
+
+
+@Misc{Jordan,
+ note = {Speech given at the 30th anniversary of the WWZ and VBÖ},
+ author = {Jordan, Thomas J.},
+ year = {2019},
+ title = {Currencies, money and digital tokens},
+ publisher = {University of Basel},
+ month = {September},
+ howpublished = {\url{https://www.snb.ch/en/mmr/speeches/id/ref\_20190905\_tjn/source/ref\_20190905\_tjn.en.pdf}},
+}
+
+
+@article{Kahn2009,
+ author = {Kahn, Charles M. and William Roberds},
+ year = {2009},
+ title = {Why Pay? An Introduction to Payments Economics},
+ journal = {Journal of Financial Intermediation},
+ number = {18},
+ pages = {1--23},
+}
+
+@article{Kahn2005,
+ author = {Kahn, Charles M. and James McAndrews and William Roberds},
+ year = {2005},
+ title = {Money is Privacy},
+ journal = {International Economic Review},
+ volume = {46},
+ number = {2},
+ pages = {377--399},
+}
+
+@article{Kasper,
+ author = {Kasper, Timo and Michael Silbermann and Christof Paar},
+ year = {2010},
+ title = {All you can eat or breaking a real-world contactless payment system},
+ journal = {Financial Cryptography and Data Security, Lecture Notes in Computer Science},
+ volume = {6052},
+ pages = {343--50},
+}
+
+@inproceedings{Katzenbeisser,
+ author = {Katzenbeisser, Stefan and Ünal Kocabaş and Vladimir Rožić and Ahmad-Reza Sadeghi and Ingrid Verbauwhede and Christian Wachsmann},
+ year = {2012},
+ title = {{PUF}s: Myth, Fact or Busted? A Security Evaluation of Physically Unclonable Functions ({PUF}s) Cast in Silicon},
+ booktitle = {Cryptographic Hardware and Embedded Systems -- CHES 2012. Lecture Notes in Computer Science},
+ volume = {7428},
+ pages = {283--301},
+}
+
+@book{Keynes,
+ author = {Keynes, John Maynard},
+ year = {1936},
+ title = {The General Theory of Employment, Interest and Money},
+ publisher = {Macmillan},
+}
+
+@article{Kiff,
+ author = {Kiff, John and Jihad Alwazir and Sonja Davidovic and Aquiles Farias and Ashraf Khan and Tanai Khiaonarong and Majid Malaika and Hunter Monroe and Nobu Sugimoto and Hervé Tourpe and Peter Zhou},
+ year = {2020},
+ title = {A Survey of Research on Retail Central Bank Digital Currency},
+ journal = {IMF Working Paper},
+ volume = {20/104},
+}
+
+@InCollection{Kumhof,
+ author = {Kumhof, Michael and Clare Noone},
+ year = {2018},
+ title = {Central bank digital currencies - design principles and balance sheet implications},
+ publisher = {Bank of England},
+ series = {Staff Working Paper},
+ number = {725},
+}
+
+@inproceedings{Lapid,
+ author = {Lapid, Ben and Avishai Wool},
+ year = {2018},
+ title = {Cache-Attacks on the {ARM} TrustZone Implementations of {AES}-256 and {AES}-256-{GCM} via {GPU}-Based Analysis},
+ booktitle = {International Conference on Selected Areas in Cryptography. Lecture Notes in Computer Science},
+ volume = {11349},
+}
+
+@article{Lerner,
+ author = {Lerner, Josh and Jean Tirole},
+ year = {2005},
+ title = {The Scope of Open Source Licensing},
+ journal = {Journal of Law, Economics \& Organization},
+ volume = {21},
+ pages = {20-56},
+}
+
+@misc{Libra,
+ author = {{Libra Association}},
+ year = {2020},
+ title = {Libra White Paper v2.0},
+ url = {\url{https://libra.org/en-US/white-paper}},
+}
+
+@inproceedings{Lim,
+ author = {Lim, Chae Hoon and Phil Joong Lee},
+ year = {1997},
+ title = {A key recovery attack on discrete log-based schemes using a prime order subgroup},
+ booktitle = {CRYPTO 1997. Lecture Notes in Computer Science},
+ volume = {1294},
+}
+
+@InCollection{Lyons,
+ author = {Lyons, Richard K. and Ganesh Viswanath-Natraj},
+ year = {2020},
+ title = {What Keeps Stablecoins Stable?},
+ publisher = {National Bureau of Economic Research},
+ series = {NBER Working Paper Series},
+ number = {27136},
+ month = {May},
+}
+
+@article{Mancini-Griffoli,
+ author = {Mancini-Griffoli, Tommaso and Maria Soledad Martinez Peria and Itai Agur and Anil Ari and John Kiff and Adina Popescu and Celine Rochon},
+ year = {2018},
+ title = {Casting Light on Central Bank Digital Currency},
+ journal = {IMF Staff Discussion Notes},
+ volume = {18/08},
+ publisher = {International Monetary Fund},
+}
+
+@misc{Nakamoto,
+ author = {Nakamoto, Satoshi},
+ year = {2008},
+ title = {Bitcoin: A Peer-to-Peer Electronic Cash System},
+ url = {\url{https://www.bitcoin.com/bitcoin.pdf}},
+}
+
+@book{Narayanan,
+ author = {Narayanan, Arvind and Joseph Bonneau and Edward Felten and Andrew Miller and Steven Goldfeder},
+ year = {2016},
+ title = {Bitcoin and Cryptocurrency Technologies: A Comprehensive Introduction},
+ publisher = {Princeton University Press},
+}
+
+@misc{Niepelt,
+ author = {Niepelt, Dirk},
+ year = {2020},
+ title = {Digital money and central bank digital currency: An executive summary for policymakers},
+ url = {https://voxeu.org/article/digital-money-and-central-bank-digital-currency-executive-summary},
+}
+
+@inproceedings{Okamoto,
+ author = {Okamoto, Tatsuaki},
+ year = {1995},
+ title = {An Efficient Divisible Electronic Cash Scheme},
+ booktitle = {Advances in Cryptology --- CRYPT0'95: 15th Annual International Cryptology Conference Santa Barbara, California, USA, August 27--31, 1995 Proceedings},
+ editor = {Ed. by Don Coppersmith},
+ publisher = {Springer-Verlag Berlin Heidelberg},
+ pages = {438--451},
+}
+
+@article{Pinto,
+ author = {Pinto, S. and N. Santos},
+ year = {2019},
+ title = {Demystifying {ARM} TrustZone: A Comprehensive Survey},
+ journal = {ACM Computing Surveys},
+ volume = {51},
+ number = {6},
+ month = {January},
+ pages = {1--31}
+}
+
+@article{Rivest,
+ author = {Rivest, Ronald L. and Adi Shamir and Leonard Adleman},
+ year = {1978},
+ title = {A Method for Obtaining Digital Signatures and Public Key Cryptosystems},
+ journal = {Comm. ACM},
+ volume = {21},
+ number = {2},
+}
+
+@book{Solove,
+ author = {Solove, Daniel J.},
+ year = {2011},
+ title = {Nothing to Hide: The false tradeoff between privacy and security},
+ publisher = {New Haven \& London: Yale University Press},
+}
+
+@article{Soukup,
+ author = {Soukup, Michael and Bruno Muff},
+ year = {2007},
+ title = {Die {P}ostcard lässt sich fälschen},
+ journal = {Sonntagszeitung},
+ month = {April},
+ day = {22},
+}
+
+@article{Stallman,
+ author = {Stallman, Richard},
+ year = {1985},
+ title = {The {GNU} manifesto},
+ journal = {Dr. Dobb's Journal of Software Tools},
+ volume = {10},
+ number = {3},
+ pages = {30--35},
+}
+
+
+@TechReport{Riksbank,
+ author = {{Sveriges Riksbank}},
+ year = {2020},
+ title = {The {R}iksbank's e-krona project},
+ month = {Feb},
+ institution = {Sveriges Riksbank},
+ url = {\url{https://www.riksbank.se/globalassets/media/rapporter/e-krona/2019/the-riksbanks-e-krona-pilot.pdf}},
+}
+
+@misc{Wojtczuk,
+ author = {Wojtczuk, Rafal and Joanna Rutkowska},
+ year = {2009},
+ title = {Attacking {I}ntel Trusted Execution Technology},
+ howpublished = {BlackHat-DC 2009},
+}
+
+@article{Yalta2010,
+ author = {Yalta, A. Talha and A. Yasemin Yalta},
+ year = {2010},
+ title = {Should Economists Use Open Source Software for Doing Research?},
+ journal = {Computational Economics},
+ volume = {35},
+ pages = {371--394},
+}
+
+@article{Yalta2008,
+ author = {Yalta, A. Talha and Riccardo Lucchetti},
+ year = {2008},
+ title = {The {GNU/L}inux Platform and Freedom Respecting Software for Economists},
+ journal = {Journal of Applied Econometrics},
+ volume = {23},
+ pages = {279-286},
+}
diff --git a/doc/cbdc-it/diagramma1-it.png b/doc/cbdc-it/diagramma1-it.png
new file mode 100644
index 000000000..bd38d1df0
--- /dev/null
+++ b/doc/cbdc-it/diagramma1-it.png
Binary files differ
diff --git a/doc/cbdc-it/diagramma2-it.png b/doc/cbdc-it/diagramma2-it.png
new file mode 100644
index 000000000..171cb1045
--- /dev/null
+++ b/doc/cbdc-it/diagramma2-it.png
Binary files differ
diff --git a/doc/cbdc-it/graphics-it.odp b/doc/cbdc-it/graphics-it.odp
new file mode 100644
index 000000000..4c165ad5e
--- /dev/null
+++ b/doc/cbdc-it/graphics-it.odp
Binary files differ
diff --git a/doc/cs/ads/abbreviation.tex b/doc/cs/ads/abbreviation.tex
new file mode 100644
index 000000000..9da168dcc
--- /dev/null
+++ b/doc/cs/ads/abbreviation.tex
@@ -0,0 +1,48 @@
+%!TEX root = ../dokumentation.tex
+\chapter*{Abbreviations}
+\begin{acronym}[YTMMM]
+ \acro{AES}{Advanced Encryption Standard}
+ \acro{AML}{Anti Money Laundering}
+ \acro{API}{Application Programming Interface}
+ \acrodefplural{API}[APIs]{Application Programming Interfaces}
+ \acro{BIP}{Bitcoin Improvement Proposal}
+ \acro{CA}{Certificate Authority}
+ \acro{CDH}{Computational Diffie-Hellman}
+ \acro{CFT}{Combating Financing of Terrorism}
+ \acro{CMA}{Choosen-Message Attack}
+ \acro{CS}{Clause Blind Schnorr Signature Scheme}
+ \acro{CSRF}{Client-Side Request Forgery}
+ \acro{CWE}{Common Weakness Enumeration}
+ \acro{DDH}{Decisional Diffie-Hellman}
+ \acro{DHKE}{Diffie-Hellman key exchange}
+ \acro{DLP}{Discrete Logarithm Problem}
+ \acro{DSA}{Digital Signature Algorithm}
+ \acro{ECC}{Elliptic Curve Cryptography}
+ \acro{ECDH}{Elliptic Curve Diffie Hellman}
+ \acro{EdDSA}{Edwards-curve Digital Signature Algorithm}
+ \acro{EUF}{Existentially Unforgeability}
+ \acro{FDH}{Full-Domain Hash}
+ \acro{GNU AGPL}{GNU Affero General Public License}
+ \acro{GNU GPL}{GNU General Public License}
+ \acro{GNU LGPL}{GNU Lesser General Public License}
+ \acro{IPC}{Inter Process Communication}
+ \acro{JSON}{JavaScript Object Notation}
+ \acro{KDF}{Key Derivation Function}
+ \acro{KYC}{Know Your Customer}
+ \acro{MAC}{Message Authentication Code}
+ \acro{NIST}{National Institute of Standards and Technology}
+ \acro{MK}{Master Key}
+ \acro{PKI}{Public Key Infrastructure}
+ \acro{PRF}{Pseudo Random Function}
+ \acro{PoS}{Point-of-Sales}
+ \acro{PRNG}{Pseudo Random Number Generator}
+ \acro{RNG}{Random Number Generator}
+ \acro{ROS}{Random inhomogeneities in an Overdetermined, Solvable system of linear equations}
+ \acro{RT}{Round-Trip}
+ \acro{RTT}{Round-Trip Time}
+ \acro{SPOF}{Single Point of Failure}
+ \acro{SSRF}{Server-Side Request Forgery}
+ \acro{Taler}{GNU Taler}
+ \acro{TRNG}{True Random Number Generator}
+ \acro{URL}{uniform resource locator}
+\end{acronym}
diff --git a/doc/cs/ads/abstract.tex b/doc/cs/ads/abstract.tex
new file mode 100644
index 000000000..0610eb10b
--- /dev/null
+++ b/doc/cs/ads/abstract.tex
@@ -0,0 +1,26 @@
+\chapter*{Abstract}
+GNU Taler is an intuitive, fast and socially responsible digital payment system implemented as free software.
+While preserving the customers privacy, GNU Taler is still compliant to regulations.
+\\\\
+The goal of this thesis is to improve Taler's performance and provide cipher agility by adding support for Schnorr's blind signatures.
+To achieve this goal, the current state in research for Schnorr signatures needs to be analyzed.
+After choosing a signature scheme, it has to be integrated into the Taler protocols.
+Besides implementing the redesigned protocols in Taler, an implementation of the cryptographic routines is needed.
+\\\\
+The paper "Blind Schnorr
+Signatures and Signed ElGamal Encryption in the Algebraic Group Model" \cite{cryptoeprint:2019:877} from 2019 (updated in 2021) introducing \gls{CSBS} is used as theoretical basis for our improvements.
+The paper explains why simple Blind Schnorr Signatures are broken and how the Clause Schnorr Blind Signature scheme is secured against this attack.\\
+Compared to the currently used \gls{RSABS}, the new scheme has an additional request, two blinding factors instead of one and many calculations are done twice to prevent attacks.
+\\\\
+The Taler protocols were redesigned to support the Clause Blind Schnorr Signature scheme, including slight alterations to ensure \textit{abort-idempotency}, and then further specified.
+Before starting with the implementation of the redesigned protocols, the cryptographic routines for \gls{CSBS} were implemented as part of the thesis. \\
+All of the implemented code is tested and benchmarks are added for the cryptographic routines.
+\\\\
+Multiple results were achieved during this thesis:
+The redesigned protocols Taler protocols with support for \gls{CSBS}, the implementation of the cryptographic routines, the implementation of Talers core protocols and a detailed comparison between \gls{RSABS} and \gls{CSBS}.
+Overall, the \gls{CSBS} are significantly faster, require less disk space, and bandwidth and provide \textit{cipher agility} for Taler.
+
+\section*{Acknowledgement}
+We would like to kindly thank Christian Grothoff (Bern University of Applied Sciences) for his extensive advice, support and very helpful feedback during our whole thesis.\\
+We also kindly thank Jeffrey Burdges (Web 3, Switzerland) for reviewing the proposal containing the redesigned protocols and giving feedback.\\
+Further, we kindly thank Jacob Appelbaum (Bern University of Applied Sciences, Eindhoven University of Technology) for further results for the performance measurements of our cryptographic routines and the insightful conversations.
diff --git a/doc/cs/ads/glossary.tex b/doc/cs/ads/glossary.tex
new file mode 100644
index 000000000..7132f89a5
--- /dev/null
+++ b/doc/cs/ads/glossary.tex
@@ -0,0 +1,53 @@
+%!TEX root = ../thesis.tex
+
+%
+% vorher in Konsole folgendes aufrufen:
+% makeglossaries makeglossaries dokumentation.acn && makeglossaries dokumentation.glo
+%
+
+%
+% Glossareintraege --> reference, name, beschreibung
+% Aufruf mit \gls{...}
+%
+% \newglossaryentry{non-repudiation}{name={non-repudiation},plural={non-repudiation},description={After a message is signed, one can not dispute that a message was signed}}
+% \newglossaryentry{sender_authenticity}{name={sender authenticity},plural={sender authenticity},description={The origin/sender of a message can not be forged}}
+% \newglossaryentry{message_integrity}{name={message integrity},plural={message integrity},description={No unauthorized change to the message can be made, the message is tamperproof}}
+\newglossaryentry{hkdf}{
+ name = {HKDF},
+ description = {The HMAC-based Extract-and-Expand Key Derivation Function is a function that takes potentially weak keying material as input and outputs high entropy keying material. For more information see section \ref{sec:kdf}}
+}
+
+\newglossaryentry{25519}{
+ name = {Curve25519},
+ description = {A popular elliptic curve used in many cryptographic systems based on elliptic curve cryptography. See section \ref{par:curve25519}}
+}
+
+\newglossaryentry{fdh}{
+ name = {FDH},
+ description = {A Full-Domain Hash is a hash function with an image size equal to the original gorup. See section \ref{sec:rsa-fdh}}.
+}
+
+\newglossaryentry{idempotence}{
+ name = {idempotence},
+ description = {Idempotence in the context of computer science is a property to ensure that the state of system will not change, no matter how many times the same request was made. See section \ref{abort-idempotency}}
+}
+
+\newglossaryentry{abort-idempotency}{
+ name = {abort-idempotency},
+ description = {Abort-idempotency is a special case of \gls{idempotence}. On every step in a protocol it needs to be ensured that even on an abort, the same request always receives the same response. See section \ref{abort-idempotency}}
+}
+
+\newglossaryentry{RSABS}{
+ name = {RSA Blind Signatures},
+ description = {Chaums Blind Signature Scheme based on RSA. See section \ref{sec:blind-rsa-sign}}
+}
+
+\newglossaryentry{CSBS}{
+ name = {Clause Blind Schnorr Signatures},
+ description = {A secure variant of Blind Schnorr Signature Schemes introduced in section \ref{sec:clause-blind-schnorr-sig}}
+}
+
+% \newglossaryentry{25519}{
+ % name = {},
+ % description = {}
+% }
diff --git a/doc/cs/ads/header.tex b/doc/cs/ads/header.tex
new file mode 100644
index 000000000..0b53317b5
--- /dev/null
+++ b/doc/cs/ads/header.tex
@@ -0,0 +1,71 @@
+% Hyperlinks
+\usepackage[
+ hidelinks,
+ pdfusetitle,
+]{hyperref}
+
+% Grafiken
+\usepackage{graphicx}
+%Bildpfad
+\graphicspath{{images/}}
+
+% Micro sign
+\usepackage{siunitx}
+
+% Farben
+\usepackage{color}
+\definecolor{LinkColor}{rgb}{0,0,0.2}
+
+% Glossar
+\usepackage[
+ nonumberlist, %keine Seitenzahlen anzeigen
+ %acronym, %ein Abkürzungsverzeichnis erstellen
+ %section, %im Inhaltsverzeichnis auf section-Ebene erscheinen
+ toc, %Einträge im Inhaltsverzeichnis
+]{glossaries}
+\makeglossaries
+\input{ads/glossary}
+
+%Nomenklatur
+\usepackage{nomencl}
+\makenomenclature
+
+%PDF pages
+\usepackage{pdfpages}
+
+%Adjustbox (tikz figures of Taler)
+\usepackage{adjustbox}
+
+%BFH Boxes
+% see BFH example for usage, looks nice!<<
+\LoadBFHModule{listings,terminal,boxes}
+
+%Akronyme
+\usepackage[printonlyused,footnote]{acronym}
+
+% Literaturverweise
+\usepackage[
+ backend=biber,
+ style=alphabetic,
+ %citestyle=authoryear
+]{biblatex}
+\addbibresource{bibliography.bib}
+\addbibresource{bibliography_projekt2.bib}
+
+% TODOs in text
+% documentation: http://tug.ctan.org/macros/latex/contrib/todonotes/todonotes.pdf
+\usepackage{todonotes}
+
+%Crypto Grafiken
+\usepackage{cryptocode}
+%\usepackage{amsmath}
+
+\usepackage{listings}
+\usepackage{xcolor}
+
+\definecolor{mGreen}{rgb}{0,0.6,0}
+\definecolor{mGray}{rgb}{0.5,0.5,0.5}
+\definecolor{mPurple}{rgb}{0.58,0,0.82}
+\definecolor{backgroundColour}{rgb}{0.95,0.95,0.92}
+\definecolor{ApiColor}{HTML}{307FCB}
+\definecolor{whyite}{HTML}{A1C66C} % Needs to be here due to some typo in BFH-CI stuff. Thanks BFH.
diff --git a/doc/cs/ads/history.tex b/doc/cs/ads/history.tex
new file mode 100644
index 000000000..376ee587a
--- /dev/null
+++ b/doc/cs/ads/history.tex
@@ -0,0 +1,12 @@
+\chapter*{Document History}
+\addcontentsline{toc}{chapter}{Document History}
+
+%\begin{center}
+\begin{tabular}{ ||l|l|l|l|| }
+ \hline
+ Version & Date & Comment & Author \\
+ \hline\hline
+ 0.0.1 & 30.09.2021 & Document created & Gian Demarmels \& Lucien Heuzeveldt \\
+ \hline
+\end{tabular}
+%\end{center} \ No newline at end of file
diff --git a/doc/cs/bibliography.bib b/doc/cs/bibliography.bib
new file mode 100644
index 000000000..014958986
--- /dev/null
+++ b/doc/cs/bibliography.bib
@@ -0,0 +1,362 @@
+@misc{project-definition,
+ author = {Dr. Emmanuel Benoist},
+ title = {Adding Schnorr's blind signature in Taler},
+ howpublished = {\url{https://fbi.bfh.ch/fbi/2022/Studienbetrieb/BaThesisHS21/aufgabestellungen/BIE1-1-21-en.html}},
+ year = {2021}
+}
+
+@misc{swot-analysis,
+ author = {Will Kenton},
+ title = {Strength, Weakness, Opportunity, and Threat (SWOT) Analysis},
+ year = {2021},
+ howpublished = {\url{https://www.investopedia.com/terms/s/swot.asp}},
+ note = {[Online; accessed 01-October-2021]}
+}
+
+ @misc{enwiki:1040250156,
+ author = {{Wikipedia contributors}},
+ title = {Project management triangle --- {Wikipedia}{,} The Free Encyclopedia},
+ year = {2021},
+ url = {https://en.wikipedia.org/w/index.php?title=Project_management_triangle&oldid=1040250156},
+ note = {[Online; accessed 1-October-2021]}
+}
+
+@misc{ionos:waterfall_model,
+ author = {ionos.com},
+ title = {Waterfall methodology},
+ year = {2019},
+ url = {https://www.ionos.com/digitalguide/websites/web-development/waterfall-methodology/},
+ note = {[Online; accessed 1-October-2021]}
+}
+
+@misc{schwab:anforderungen,
+ author = {Gerhard Schwab},
+ title = {Lerneinheit 4 - Anforderungen ermitteln},
+ howpublished = {BFH Moodle},
+ year = {2017}
+}
+
+@techreport{rfc2104,
+ shorthand = {RFC2104},
+ author = {H. Krawczyk, M.Bellare, R. Canetti},
+ title = {HMAC: Keyed-Hashing for Message Authentication},
+ howpublished = {Internet Requests for Comments},
+ type = {RFC},
+ number = 2104,
+ year = {1997},
+ issn = {2070-1721},
+ month = {02},
+ publisher = {IETF},
+ institution = {IETF},
+ url = {https://tools.ietf.org/html/rfc2104}
+}
+
+@techreport{rfc5869,
+ shorthand = {RFC5869},
+ author = {H. Krawczyk, P.Eronen},
+ title = {HMAC-based Extract-and-Expand Key Derivation Function (HKDF)},
+ howpublished = {Internet Requests for Comments},
+ type = {RFC},
+ number = 5869,
+ year = {2010},
+ issn = {2070-1721},
+ month = {05},
+ publisher = {IETF},
+ institution = {IETF},
+ url = {https://tools.ietf.org/html/rfc5869}
+}
+
+@misc{cryptoeprint:2019:877,
+ author = {Georg Fuchsbauer and
+ Antoine Plouviez and
+ Yannick Seurin},
+ title = {Blind Schnorr Signatures and Signed ElGamal Encryption in the Algebraic Group Model},
+ howpublished = {Cryptology ePrint Archive, Report 2019/877},
+ year = {2019},
+ note = {\url{https://ia.cr/2019/877} and \url{https://www.youtube.com/watch?v=W-uwVdGeUUs}}
+}
+
+
+@misc{bip:schnorr-bitc,
+ author = {Pieter Wuille, Jonas Nick, Tim Ruffing},
+ title = {Schnorr Signatures for secp256k1},
+ howpublished = {Bitcoin Improvement Proposal, bip-0340},
+ year = {2020},
+ note = {\url{https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki}}
+}
+
+@misc{git:secp256k1-schnorr,
+ author = {Bitcoin Repository},
+ title = {BIP-340 - Module for Schnorr signatures in libsecp256k1},
+ howpublished = {\url{https://github.com/bitcoin/bitcoin/tree/master/src/secp256k1}}
+}
+
+@misc{btc:releasnotes-0.21,
+ author = {Bitcoin.org },
+ title = {0.21.1 Release Notes},
+ howpublished = {\url{https://bitcoin.org/en/releases/0.21.1/}}
+}
+
+@inproceedings{spring:wallet-db-with-observers,
+ author = {Chaum, David
+ and Pedersen, Torben Pryds},
+ editor = {Brickell, Ernest F.},
+ title = {Wallet Databases with Observers},
+ booktitle = {Advances in Cryptology --- CRYPTO' 92},
+ year = {1993},
+ publisher = {Springer Berlin Heidelberg},
+ address = {Berlin, Heidelberg},
+ pages = {89--105},
+ abstract = {Previously there have been essentially only two models for computers that people can use to handle ordinary consumer transactions: (1) the tamper-proof module, such as a smart card, that the person cannot modify or probe; and (2) the personal workstation whose inner working is totally under control of the individual. The first part of this article argues that a particular combination of these two kinds of mechanism can overcome the limitations of each alone, providing both security and correctness for organizations as well as privacy and even anonymity for individuals.},
+ isbn = {978-3-540-48071-6}
+}
+
+@misc{schnorr:perfect-dl-signatures,
+ author = {Claus Peter Schnorr},
+ title = {Enhancing the Security of Perfect Blind DL-Signatures.},
+ howpublished = {Universität Frankfurt},
+ year = {2004},
+ note = {\url{https://www.math.uni-frankfurt.de/~dmst/teaching/SS2012/Vorlesung/EBS5.pdf}}
+}
+
+@misc{wagner:generalized-bday-prob,
+ author = {David Wagner},
+ title = {A Generalized Birthday Problem},
+ howpublished = {University of California Berkeley},
+ year = {2002},
+ note = {\url{https://www.iacr.org/archive/crypto2002/24420288/24420288.pdf}}
+}
+
+@inproceedings{Schnorr01securityof,
+ author = {Claus Peter Schnorr},
+ title = {Security of Blind Discrete Log Signatures against Interactive Attacks},
+ booktitle = {ICICS 2001, LNCS 2229},
+ year = {2001},
+ pages = {1--12},
+ publisher = {Springer-Verlag}
+}
+
+@misc{pic:simple-diagram,
+ author = {GNU Taler},
+ title = {Simple Taler Diagram},
+ year = {[Online; accessed 2-November-2021]},
+ note = {\url{https://taler.net/images/diagram-simple.png}}
+}
+
+@misc{pic:refresh-prot,
+ author = {GNU Taler},
+ title = {Taler Refresh protocol},
+ year = {[Online; accessed 2-November-2021]},
+ note = {\url{https://git.taler.net/marketing.git/plain/presentations/comprehensive/main.pdf}}
+}
+
+@misc{pic:taler-overview,
+ author = {GNU Taler},
+ title = {Operations},
+ howpublished = {\url{https://git.taler.net/marketing.git/plain/presentations/comprehensive/operations.png}},
+ year = {[Online; accessed 2-November-2021]},
+}
+
+@misc{pic:coin-state-machine,
+ author = {GNU Taler},
+ howpublished = {\url{https://git.taler.net/exchange.git/tree/doc/system/taler/coin.pdf}},
+ title = {Coin State Machine},
+ year = {[Online; accessed 13 January 2022]}
+}
+
+@misc{pic:deposit-state-machine,
+ author = {GNU Taler},
+ howpublished = {\url{https://git.taler.net/exchange.git/tree/doc/system/taler/deposit.pdf}},
+ title = {Deposit State Machine},
+ year = {[Online; accessed 13 January 2022]}
+}
+
+@misc{gnunet-git,
+ author = {GNUnet Git Repositories},
+ title = {gnunet.git},
+ howpublished = {\url{https://git.gnunet.org/gnunet.git/}}
+}
+
+@misc{libsodium:finite-field-arithmetic,
+ author = {libsodium documentation},
+ howpublished = {\url{https://doc.libsodium.org/advanced/point-arithmetic}},
+ title = {Finite field arithmetic}
+}
+
+@misc{bernlange:safecurves,
+ author = {Daniel J. Bernstein and Tanja Lange},
+ title = {SafeCurves: choosing safe curves for elliptic-curve cryptography.},
+ howpublished = {\url{https://safecurves.cr.yp.to}},
+ year = {accessed 17 October 2021. }
+}
+
+@misc{matt:unix-domain-sockets,
+ author = {Matt Lim},
+ title = {Getting Started With Unix Domain Sockets},
+ howpublished = {\url{https://medium.com/swlh/getting-started-with-unix-domain-sockets-4472c0db4eb1}},
+ year = {accessed 08 January 2022. }
+}
+
+@misc{rfc7748,
+ shorthand = {RFC7748},
+ series = {Request for Comments},
+ number = 7748,
+ howpublished = {RFC 7748},
+ publisher = {RFC Editor},
+ doi = {10.17487/RFC7748},
+ url = {https://rfc-editor.org/rfc/rfc7748.txt},
+ author = {Adam Langley and Mike Hamburg and Sean Turner},
+ title = {{Elliptic Curves for Security}},
+ pagetotal = 22,
+ year = 2016,
+ month = jan,
+ abstract = {This memo specifies two elliptic curves over prime fields that offer a high level of practical security in cryptographic applications, including Transport Layer Security (TLS). These curves are intended to operate at the \textasciitilde{}128-bit and \textasciitilde{}224-bit security level, respectively, and are generated deterministically based on a list of required properties.}
+}
+
+@misc{ganapati:rsactftool,
+ author = {Ganapati},
+ title = {RsaCtfTool},
+ howpublished = {\url{https://github.com/Ganapati/RsaCtfTool}},
+ year = {accessed 13 January 2022. }
+}
+
+@misc{perez:stoprsa,
+ author = {Ben Perez},
+ title = {Seriously, stop using RSA},
+ howpublished = {\url{https://blog.trailofbits.com/2019/07/08/fuck-rsa/}},
+ year = {accessed 13 January 2022. }
+}
+
+@misc{geeks:rtt,
+ author = {preetikagupta8171},
+ title = {What is RTT(Round Trip Time)?},
+ howpublished = {\url{https://www.geeksforgeeks.org/what-is-rttround-trip-time/}},
+ year = {accessed 13 January 2022. }
+}
+
+@misc{madden:curve25519-clamping,
+ author = {Neil Madden},
+ howpublished = {\url{https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/}},
+ title = {What’s the Curve25519 clamping all about?},
+ year = {2020}
+}
+
+@misc{bern:tweetnacl,
+ author = {Daniel J. Bernstein, Bernard van Gastel, Wesley Janssen},
+ title = {TweetNaCl: a crypto library in 100 tweets.},
+ howpublished = {\url{https://tweetnacl.cr.yp.to/papers.html}},
+ year = {17.09.2014}
+}
+
+@misc{taler-presentation,
+ author = {GNU Taler},
+ howpublished = {\url{https://git.taler.net/marketing.git/tree/presentations/comprehensive/main.pdf}},
+ title = {GNU Taler},
+ year = {2021}
+}
+
+@misc{cryptoeprint:2020:945,
+ author = {Fabrice Benhamouda and
+ Tancrède Lepoint and
+ Julian Loss and
+ Michele Orrù and
+ Mariana Raykova},
+ title = {On the (in)security of ROS},
+ howpublished = {Cryptology ePrint Archive, Report 2020/945},
+ year = {2020},
+ note = {\url{https://ia.cr/2020/945}}
+}
+
+@misc{rfc5246,
+ series = {Request for Comments},
+ number = 5246,
+ howpublished = {RFC 5246},
+ publisher = {RFC Editor},
+ doi = {10.17487/RFC5246},
+ url = {https://rfc-editor.org/rfc/rfc5246.txt},
+ author = {Eric Rescorla and Tim Dierks},
+ title = {{The Transport Layer Security (TLS) Protocol Version 1.2}},
+ pagetotal = 104,
+ year = 2008,
+ month = aug,
+ abstract = {This document specifies Version 1.2 of the Transport Layer Security (TLS) protocol. The TLS protocol provides communications security over the Internet. The protocol allows client/server applications to communicate in a way that is designed to prevent eavesdropping, tampering, or message forgery. {[}STANDARDS-TRACK{]}}
+}
+
+@misc{rfc6071,
+ series = {Request for Comments},
+ number = 6071,
+ howpublished = {RFC 6071},
+ publisher = {RFC Editor},
+ doi = {10.17487/RFC6071},
+ url = {https://rfc-editor.org/rfc/rfc6071.txt},
+ author = {Sheila Frankel and Suresh Krishnan},
+ title = {{IP Security (IPsec) and Internet Key Exchange (IKE) Document Roadmap}},
+ pagetotal = 63,
+ year = 2011,
+ month = feb,
+ abstract = {Over the past few years, the number of RFCs that define and use IPsec and Internet Key Exchange (IKE) has greatly proliferated. This is complicated by the fact that these RFCs originate from numerous IETF working groups: the original IPsec WG, its various spin-offs, and other WGs that use IPsec and/or IKE to protect their protocols' traffic. This document is a snapshot of IPsec- and IKE-related RFCs. It includes a brief description of each RFC, along with background information explaining the motivation and context of IPsec's outgrowths and extensions. It obsoletes RFC 2411, the previous "IP Security Document Roadmap." The obsoleted IPsec roadmap (RFC 2411) briefly described the interrelationship of the various classes of base IPsec documents. The major focus of RFC 2411 was to specify the recommended contents of documents specifying additional encryption and authentication algorithms. This document is not an Internet Standards Track specification; it is published for informational purposes.}
+}
+
+ @misc{enwiki:1055393696,
+ author = {{Wikipedia contributors}},
+ title = {RSA Factoring Challenge --- {Wikipedia}{,} The Free Encyclopedia},
+ year = {2021},
+ howpublished = {\url{https://en.wikipedia.org/w/index.php?title=RSA_Factoring_Challenge&oldid=1055393696}},
+ note = {[Online; accessed 16-January-2022]}
+}
+
+@misc{cryptoeprint:2015:625,
+ author = {Mike Hamburg},
+ title = {Ed448-Goldilocks, a new elliptic curve},
+ howpublished = {Cryptology ePrint Archive, Report 2015/625},
+ year = {2015},
+ note = {\url{https://ia.cr/2015/625}},
+}
+
+@misc{bern:curve25519,
+ author = {Daniel J. Bernstein},
+ title = {Curve25519: new Diffie-Hellman speed records},
+ howpublished = {\url{https://cr.yp.to/ecdh/curve25519-20060209.pdf}},
+ year = {02.09.2006}
+}
+
+@misc{yuchen:idempotence,
+ author = {Yuchen Z.},
+ title = {A Deep Dive Into Idempotence},
+ year = {2021},
+ howpublished = {\url{https://betterprogramming.pub/a-deep-dive-into-idempotence-1a39393df7e6}},
+ note = {[Online; accessed 16-January-2022]}
+}
+
+@misc{tibouchi:attacks-schnorr-nonce,
+ author = {Mehdi Tibouchi},
+ title = {Attacks on Schnorr signatures with biased nonces},
+ howpublished = {\url{https://ecc2017.cs.ru.nl/slides/ecc2017-tibouchi.pdf}},
+ year = {13.11.2017}
+}
+
+@article{wang:bitcoin-ecdsa-vuln,
+author = {Wang, Ziyu and Yu, Hui and Zhang, Zongyang and Piao, Jiaming and Liu, Jianwei},
+year = {2019},
+month = {09},
+pages = {},
+title = {ECDSA weak randomness in Bitcoin},
+volume = {102},
+journal = {Future Generation Computer Systems},
+doi = {10.1016/j.future.2019.08.034}
+}
+
+@misc{buchanan:ps3-ecdsa-vuln,
+ author = {Prof Bill Buchanan OBE},
+ title = {Not Playing Randomly: The Sony PS3 and Bitcoin Crypto Hacks},
+ howpublished = {\url{https://medium.com/asecuritysite-when-bob-met-alice/not-playing-randomly-the-sony-ps3-and-bitcoin-crypto-hacks-c1fe92bea9bc}},
+ year = {12.11.2018}
+}
+
+@misc{gian:nonce-sense,
+ author = {Gian Demarmels},
+ title = {Nonce-Sense - Romhack CTF Crypto Challenge},
+ howpublished = {\url{https://blog.c4pr1c0rn.ch/writeups/romhack_21/nonce_sence.html}},
+ year = {2021},
+ note = {[Online; accessed 19-January-2022]}
+} \ No newline at end of file
diff --git a/doc/cs/bibliography_projekt2.bib b/doc/cs/bibliography_projekt2.bib
new file mode 100644
index 000000000..1f20b8c59
--- /dev/null
+++ b/doc/cs/bibliography_projekt2.bib
@@ -0,0 +1,442 @@
+% see here for standard templates: https://en.wikibooks.org/wiki/LaTeX/Bibliography_Management#Standard_templates
+
+@misc{chaum-grothoff-moser:issue-cdbc,
+ author = {Chaum David, Grothoff Christian, Moser Thomas},
+ title = {How to issue a central bank digital currency},
+ howpublished = {\url{https://www.snb.ch/en/mmr/papers/id/working_paper_2021_03}},
+ year = {2021}
+}
+
+@phdthesis{dold:the-gnu-taler-system,
+ author = {Florian Dold},
+ title = {The GNU Taler System},
+ howpublished ={\url{https://taler.net/papers/thesis-dold-phd-2019.pdf}},
+ school = {Université de Rennes},
+ year = {2019}
+}
+
+@misc{schneier:value-privacy,
+ author = {Bruce Schneier},
+ title = {The Value of Privacy},
+ howpublished = {\url{https://www.schneier.com/blog/archives/2006/05/the_value_of_pr.html}},
+ year = {2006}
+}
+
+@misc{qualcomm:mobile-rng,
+ author = {Liang Kai},
+ title = {Guard your data with the Qualcomm Snapdragon mobile platform},
+ howpublished = {\url{https://www.qualcomm.com/media/documents/files/guard-your-data-with-the-qualcomm-snapdragon-mobile-platform.pdf}},
+ year = {2019}
+}
+
+@misc{chaum:blind-sign,
+ author = {Chaum David},
+ title = {Blind Signatures for Untraceable Payments},
+ howpublished = {\url{https://www.chaum.com/publications/Chaum-blind-signatures.PDF}},
+ year = {1983}
+}
+
+@misc{grothoff-dold:euro-bearer-online,
+ author = {Christian Grothoff, Florian Dold},
+ title = {Why a Digital Euro should be Online-first and Bearer-based},
+ howpublished = {\url{https://taler.net/papers/euro-bearer-online-2021.pdf}},
+ year = {2021}
+}
+
+@misc{website:bigcommerce-payment-fraud,
+ author = {BigCommerce},
+ title = {Payment fraud: What is it and how it can be avoided?},
+ howpublished = {\url{https://www.bigcommerce.com/ecommerce-answers/payment-fraud-what-it-and-how-it-can-be-avoided/}}
+}
+
+@misc{nist:recommendation-for-key-management,
+ author = {Elaine Barker},
+ title = {Recommendation for Key Management},
+ howpublished = {\url{https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-57pt1r5.pdf}},
+ year = {2020}
+}
+
+@misc{taler:snackautomat,
+ author = {Berner Fachhochschule},
+ title = {GNU Taler Snackautomat},
+ howpublished = {\url{https://www.bfh.ch/de/forschung/referenzprojekte/gnu-taler-snackautomat/}}
+}
+
+@book{modernCrypto,
+ author = {Nigel P. Smart},
+ editor = {David Basin, Kenny Paterson},
+ title = {Cryptography Made Simple},
+ publisher = {Springer International Publishing Switzerland AG},
+ year = {2016}
+}
+
+@inbook{Crépeau2005,
+ author = {Cr{\'e}peau, Claude},
+ title = {Cut-and-choose protocols},
+ publisher = {School of Computr Science, McGill University, Montréal (QC), Canada},
+ url = {http://crypto.cs.mcgill.ca/~crepeau/EoC/Cut&Choose.pdf}
+}
+
+% author from github: https://github.com/chaos-eng/chaos-eng.github.io
+@misc{chaos-engineering,
+ author = {chaos-eng},
+ title = {PRINCIPLES OF CHAOS ENGINEERING},
+ howpublished = {\url{https://principlesofchaos.org/}},
+ year = {2019}
+}
+
+@misc{businger:public-key-crytpo,
+ author = {Walter Businger},
+ title = {Skript Public-Key Kryptographie},
+ year = {2021}
+}
+
+@misc{rutishauser:fuzzing,
+ author = {Dobin Rutishauser},
+ title = {Fuzzing},
+ howpublished = {Course material of BFH module Forensics and Code Security},
+ year = {2021}
+}
+
+@misc{codeblau:taler-audit,
+ author = {Code Blau GmbH},
+ title = {Report for the GNU Taler security audit in Q2/Q3 2020},
+ howpublished = {\url{https://taler.net/papers/codeblau-report-2020-q2.pdf}},
+ year = {2020}
+}
+
+@misc{pentest-execution-standard,
+ author = {The Penetration Testing Execution Standard},
+ title = {Main Page},
+ howpublished = {\url{http://www.pentest-standard.org/index.php/Main_Page}}
+}
+
+@misc{owasp:top-ten,
+ author = {OWASP Foundation},
+ title = {OWASP Top Ten},
+ howpublished = {\url{https://owasp.org/www-project-top-ten/}}
+}
+
+@misc{owasp:mobile-top-ten,
+ author = {OWASP Foundation},
+ title = {OWASP Mobile Top 10},
+ howpublished = {\url{https://owasp.org/www-project-mobile-top-10/}}
+}
+
+@misc{owasp:api-security-project,
+ author = {OWASP Foundation},
+ title = {OWASP API Security Project},
+ howpublished = {\url{https://owasp.org/www-project-api-security/}}
+}
+
+@misc{owasp:web-security-testing-guide,
+ author = {OWASP Foundation},
+ title = {OWASP Web Security Testing Guide},
+ howpublished = {\url{https://owasp.org/www-project-web-security-testing-guide/}}
+}
+
+@misc{owasp:mobile-security-testing-guide,
+ author = {OWASP Foundation},
+ title = {OWASP Mobile Security Testing Guide},
+ howpublished = {\url{https://owasp.org/www-project-mobile-security-testing-guide/}}
+}
+
+@misc{owasp:application-security-verification-standard,
+ author = {OWASP Foundation},
+ title = {OWASP Application Security Verification Standard},
+ howpublished = {\url{https://owasp.org/www-project-application-security-verification-standard/}}
+}
+
+@misc{owasp:mobile-application-security-verification-standard,
+ author = {OWASP Foundation},
+ title = {OWASP Mobile Application Security Verification Standard},
+ howpublished = {\url{https://github.com/OWASP/owasp-masvs}}
+}
+
+@misc{osstmm,
+ author = {ISECOM},
+ title = {OSSTMM 3},
+ howpublished = {\url{https://www.isecom.org/OSSTMM.3.pdf}}
+}
+
+@misc{emscripten,
+ author = {Emscripten Contributors},
+ title = {Emscripten documentation},
+ howpublished = {\url{https://emscripten.org/}}
+}
+
+@misc{emscripten:paper,
+ author = {Alon Zakai},
+ title = {Emscripten: an LLVM-to-JavaScript compiler},
+ howpublished = {\url{https://www.researchgate.net/publication/221320724_Emscripten_an_LLVM-to-JavaScript_compiler}},
+ year = {2011}
+}
+
+@misc{cwe,
+ author = {Common Weakness Enumeration},
+ title = {CWE - Common Weakness Enumeration},
+ howpublished = {\url{https://cwe.mitre.org/index.html}}
+}
+
+@misc{cwe:toctou,
+ author = {Common Weakness Enumeration},
+ title = {CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition},
+ howpublished = {\url{https://cwe.mitre.org/data/definitions/367.html}},
+ year = {2021}
+}
+
+@misc{cwe:c-weaknesses,
+ author = {Common Weakness Enumeration},
+ title = {CWE VIEW: Weaknesses in Software Written in C},
+ howpublished = {\url{https://cwe.mitre.org/data/definitions/658.html}}
+}
+
+% ---------- Taler documentation and repos
+@misc{taler-documentation,
+ author = {Taler Systems SA},
+ title = {GNU Taler Documentation},
+ howpublished = {\url{https://docs.taler.net/}}
+}
+
+@misc{taler-documentation:backup-synchronization,
+ author = {Taler Systems SA},
+ title = {Backup and Synchronization Service API},
+ howpublished = {\url{https://docs.taler.net/core/api-sync.html}}
+}
+
+@misc{taler-documentation:auditor-operator-manual,
+ author = {Taler Systems SA},
+ title = {GNU Taler Auditor Operator Manual},
+ howpublished = {\url{https://docs.taler.net/taler-auditor-manual.html}}
+}
+
+@misc{taler-documentation:exchange-operator-manual,
+ author = {Taler Systems SA},
+ title = {GNU Taler Exchange Operator Manual},
+ howpublished = {\url{https://docs.taler.net/taler-exchange-manual.html}}
+}
+
+@misc{taler-documentation:merchant-backend-operator-manual,
+ author = {Taler Systems SA},
+ title = {GNU Taler Merchant Backend Operator Manual},
+ howpublished = {\url{https://docs.taler.net/taler-merchant-manual.html}}
+}
+
+@misc{taler-documentation:merchant-api,
+ author = {Taler Systems SA},
+ title = {GNU Taler Merchant API Tutorial},
+ howpublished = {\url{https://docs.taler.net/taler-merchant-api-tutorial.html}}
+}
+
+@misc{taler-documentation:back-office,
+ author = {Taler Systems SA},
+ title = {Back-office Web service manual},
+ howpublished = {\url{https://docs.taler.net/taler-backoffice-manual.html}}
+}
+
+@misc{taler-documentation:pos-manual,
+ author = {Taler Systems SA},
+ title = {GNU Taler Merchant POS Manual},
+ howpublished = {\url{https://docs.taler.net/taler-merchant-pos-terminal.html}}
+}
+
+@misc{taler-documentation:wallet-developer-manual,
+ author = {Taler Systems SA},
+ title = {GNU Taler Wallet Developer Manual},
+ howpublished = {\url{https://docs.taler.net/taler-wallet.html}}
+}
+
+@misc{taler-documentation:wallet-cli-manual,
+ author = {Taler Systems SA},
+ title = {GNU Taler Wallet CLI Manual},
+ howpublished = {\url{https://docs.taler.net/taler-wallet-cli-manual.html}}
+}
+
+@misc{taler-documentation:,
+ author = {Taler Systems SA},
+ title = {},
+ howpublished = {\url{}}
+}
+
+@misc{taler-documentation:,
+ author = {Taler Systems SA},
+ title = {},
+ howpublished = {\url{}}
+}
+
+@misc{taler-documentation:,
+ author = {Taler Systems SA},
+ title = {},
+ howpublished = {\url{}}
+}
+
+% see https://git.taler.net/
+
+@misc{taler-git,
+ author = {GNU Taler Git Repositories},
+ title = {GNU Taler Git Repositories},
+ howpublished = {\url{https://git.taler.net/}}
+}
+
+@misc{taler-git:exchange,
+ author = {GNU Taler Git Repositories},
+ title = {exchange.git},
+ howpublished = {\url{https://git.taler.net/exchange.git/}}
+}
+
+@misc{taler-git:merchant,
+ author = {GNU Taler Git Repositories},
+ title = {merchant.git},
+ howpublished = {\url{https://git.taler.net/merchant.git/}}
+}
+
+@misc{taler-git:wallet-core,
+ author = {GNU Taler Git Repositories},
+ title = {wallet-core.git},
+ howpublished = {\url{https://git.taler.net/wallet-core.git/}}
+}
+
+@misc{taler-git:auditor,
+ author = {GNU Taler Git Repositories},
+ title = {auditor.git},
+ howpublished = {\url{https://git.taler.net/auditor.git/}}
+}
+
+@misc{taler-git:backoffice,
+ author = {GNU Taler Git Repositories},
+ title = {backoffice.git},
+ howpublished = {\url{https://git.taler.net/backoffice.git/}}
+}
+
+@misc{taler-git:android,
+ author = {GNU Taler Git Repositories},
+ title = {taler-android.git},
+ howpublished = {\url{https://git.taler.net/taler-android.git}}
+}
+
+@misc{taler-git:ios,
+ author = {GNU Taler Git Repositories},
+ title = {taler-ios.git},
+ howpublished = {\url{https://git.taler.net/taler-ios.git/}}
+}
+
+@misc{taler-git:django-payments,
+ author = {GNU Taler Git Repositories},
+ title = {django-payments-taler.git},
+ howpublished = {\url{https://git.taler.net/django-payments-taler.git/}}
+}
+
+@misc{taler-git:woocommerce,
+ author = {GNU Taler Git Repositories},
+ title = {woocommerce-taler.git},
+ howpublished = {\url{https://git.taler.net/woocommerce-taler.git/}}
+}
+
+@misc{taler-git:saleor,
+ author = {GNU Taler Git Repositories},
+ title = {saleor-frontend.git},
+ howpublished = {\url{https://git.taler.net/saleor-frontend.git/}}
+}
+
+@misc{taler-git:merchant-demos,
+ author = {GNU Taler Git Repositories},
+ title = {taler-merchant-demos.git},
+ howpublished = {\url{https://git.taler.net/taler-merchant-demos.git/}}
+}
+
+% ---------- Wikipedia
+@misc{dewiki:205456999,
+ author = {Wikipedia},
+ title = {Know your customer --- Wikipedia{,} Die freie Enzyklopädie},
+ year = {2020},
+ url = {\url{https://de.wikipedia.org/w/index.php?title=Know_your_customer&oldid=205456999}},
+ note = {[Online; Stand 3. April 2021]}
+}
+
+@misc{enwiki:1013094030,
+ author = {{Wikipedia contributors}},
+ title = {EdDSA --- {Wikipedia}{,} The Free Encyclopedia},
+ year = {2021},
+ howpublished = {\url{https://en.wikipedia.org/w/index.php?title=EdDSA&oldid=1013094030}},
+ note = {[Online; accessed 22-April-2021]}
+}
+
+@misc{enwiki:1020240018,
+ author = {{Wikipedia contributors}},
+ title = {Birthday problem --- {Wikipedia}{,} The Free Encyclopedia},
+ year = {2021},
+ howpublished = {\url{https://en.wikipedia.org/w/index.php?title=Birthday_problem&oldid=1020240018}},
+ note = {[Online; accessed 28-April-2021]}
+}
+
+@misc{enwiki:1019272750,
+ author = {{Wikipedia contributors}},
+ title = {Birthday attack --- {Wikipedia}{,} The Free Encyclopedia},
+ year = {2021},
+ howpublished = {\url{https://en.wikipedia.org/w/index.php?title=Birthday_attack&oldid=1019272750}},
+ note = {[Online; accessed 24-April-2021]}
+}
+
+@misc{enwiki:blind-sign,
+ author = {{Wikipedia contributors}},
+ title = {Blind signature --- {Wikipedia}{,} The Free Encyclopedia},
+ year = {2021},
+ howpublished = {\url{https://en.wikipedia.org/w/index.php?title=Blind_signature&oldid=1001105629}},
+ note = {[Online; accessed 12-April-2021]}
+}
+
+@misc{enwiki:1024158358,
+ author = "{Wikipedia contributors}",
+ title = "Scalability --- {Wikipedia}{,} The Free Encyclopedia",
+ year = "2021",
+ howpublished = "\url{https://en.wikipedia.org/w/index.php?title=Scalability&oldid=1024158358}",
+ note = "[Online; accessed 17-June-2021]"
+}
+
+@misc{enwiki:1024197377,
+ author = "{Wikipedia contributors}",
+ title = "Chaos engineering --- {Wikipedia}{,} The Free Encyclopedia",
+ year = "2021",
+ howpublished = "\url{https://en.wikipedia.org/w/index.php?title=Chaos_engineering&oldid=1024197377}",
+ note = "[Online; accessed 17-June-2021]"
+}
+
+@misc{enwiki:1026754635,
+ author = "{Wikipedia contributors}",
+ title = "Replay attack --- {Wikipedia}{,} The Free Encyclopedia",
+ year = "2021",
+ howpublished = "\url{https://en.wikipedia.org/w/index.php?title=Replay_attack&oldid=1026754635}",
+ note = "[Online; accessed 17-June-2021]"
+}
+
+% ---------- RFCs
+@misc{rfc8032,
+ series = {Request for Comments},
+ number = 8032,
+ howpublished = {RFC 8032},
+ publisher = {RFC Editor},
+ doi = {10.17487/RFC8032},
+ url = {https://rfc-editor.org/rfc/rfc8032.txt},
+ author = {Simon Josefsson and Ilari Liusvaara},
+ title = {{Edwards-Curve Digital Signature Algorithm (EdDSA)}},
+ pagetotal = 60,
+ year = 2017,
+ month = jan,
+ abstract = {This document describes elliptic curve signature scheme Edwards-curve Digital Signature Algorithm (EdDSA). The algorithm is instantiated with recommended parameters for the edwards25519 and edwards448 curves. An example implementation and test vectors are provided.},
+}
+
+@misc{rfc6265,
+ series = {Request for Comments},
+ number = 6265,
+ howpublished = {RFC 6265},
+ publisher = {RFC Editor},
+ doi = {10.17487/RFC6265},
+ url = {https://rfc-editor.org/rfc/rfc6265.txt},
+ author = {Adam Barth},
+ title = {{HTTP State Management Mechanism}},
+ pagetotal = 37,
+ year = 2011,
+ month = apr,
+ abstract = {This document defines the HTTP Cookie and Set-Cookie header fields. These header fields can be used by HTTP servers to store state (called cookies) at HTTP user agents, letting the servers maintain a stateful session over the mostly stateless HTTP protocol. Although cookies have many historical infelicities that degrade their security and privacy, the Cookie and Set-Cookie header fields are widely used on the Internet. This document obsoletes RFC 2965. {[}STANDARDS-TRACK{]}},
+}
+
diff --git a/doc/cs/content/1_introduction.tex b/doc/cs/content/1_introduction.tex
new file mode 100644
index 000000000..e0fdaa018
--- /dev/null
+++ b/doc/cs/content/1_introduction.tex
@@ -0,0 +1,72 @@
+\chapter{Introduction}
+
+\section{Motivation}
+Public key cryptography based on elliptic curves allows smaller key sizes compared to other cryptographic systems.
+While still providing equivalent security, the smaller key size leads to huge performance benefits.
+\\
+Blind Signatures are one of the key components upon which Taler's privacy is built upon.
+Our thesis adds support for a modern cryptographic scheme called the Clause Blind Schnorr Signature scheme \cite{cryptoeprint:2019:877}.\\
+Additionally to the benefits of ellicptic curve cryptography, adding a second blind signature scheme makes Taler independent of a single cryptographic scheme and thus provides \textit{cipher agility}.
+
+
+\section{Goals}
+\label{sec:goals}
+The project definition is as follows \cite{project-definition}:
+
+The students will implement the blind Schnorr signature inside Taler.
+Taler is a system for the management of virtual money.
+Taler is based on coins that need to be signed by an exchange (for instance a bank).
+In the actual version of the system, coins are signed by the exchange using Schaum's bind-signature protocol.
+This allows users to have signed coins, without the exchange knowing what it signed.
+This step is fundamental for the privacy protection of the system.
+\\The students have to insert the Schnorr blind signature algorithm inside the protocol for the creation of coins.
+But they also need to change the Taler subsystems where the verification of the signature is done.
+\\The actual Taler system allows people to let an exchange sign a coin for which they do not have the private key.
+This is a security issue (for misuse of coins on the dark-net for instance).
+An optional task for the project is to prevent a user to let an exchange sign a public key when the client does not have access to the corresponding private key.
+\\Here is a list of the tasks that the students must do:
+\begin{itemize}
+ \item Design a protocol integrating Schnorr blind signature in the creation of Taler coins.
+ \item Implement the protocol inside the exchange application and the wallet app.
+ \item Analyze the different Taler subsystems to find where the blind signature is verified.
+ \item Replace verification of the blind signature everywhere it occurs.
+ \item Compare both blind signature systems (Schaum's and Schnorr's), from the point of view of security, privacy protection, speed, \dots
+ \item Write tests for the written software.
+ \item Conduct tests for the written software.
+ \item Transfer the new software the Taler developers team
+\end{itemize}
+Here is a list of optional features:
+\begin{itemize}
+ \item Design a protocol, such that the exchange can verify that the user knows the private key corresponding to the coin that is to be signed.
+ \item Implement that protocol.
+\end{itemize}
+
+\section{Scope}
+\label{sec:scope}
+In scope are all necessary changes on the protocol(s) and components for the following tasks:
+\begin{itemize}
+ \item Research the current state of Blind Schnorr Signature schemes
+ \item Redesign the Taler protocols to support Blind Schnorr signatures
+ \item Add support for a Blind Schnorr Signature Scheme in the exchange, merchant, wallet-core, wallet web-extension and optionally on the android mobile wallet
+ \item design and implement a protocol where the user proves to the exchange the knowledge of the coin that is to be signed (optional)
+\end{itemize}
+
+Out of scope is production readiness of the implementation.
+This is because changes in the protocos and code need to be thoroughly vetted to ensure that no weaknesses or security vulnerabilities were introduced.
+Such an audit is out of scope for the thesis and is recommended to be performed in the future.
+The iOS wallet will not be considered in this work.
+\\
+It is not unusual that a scope changes when a project develops.
+Due to different reasons, the scope needed to be shifted.
+Since there are no libraries supporting \gls{CSBS}, the signature scheme has to be implemented and tested before integrating it into Taler.
+While this is still reasonable to do in this project, it will affect the scope quite a bit.
+The analysis of the optional goal showed, that a good solution that aligns with Taler's goals and properties needs more research and is a whole project by itself.
+
+Scope changes during the project:
+\begin{itemize}
+ \item \textbf{Added:} Implement the cryptographic routines in GNUnet
+ \item \textbf{Removed: } design and implement a protocol where the user proves to the exchange the knowledge of the coin that is to be signed (optional)
+ \item \textbf{Adjusted: } Focus is on the implementation of the exchange protocols (Withdraw, Spend, Refresh and cryptographic utilities)
+ \item \textbf{Adjusted: } Implementation of the refresh protocol and wallet-core are nice-to-have goals
+ \item \textbf{Removed: } The Merchant and the android wallet implementations are out of scope
+\end{itemize}
diff --git a/doc/cs/content/3_preliminaries.tex b/doc/cs/content/3_preliminaries.tex
new file mode 100644
index 000000000..7d7b7ca2f
--- /dev/null
+++ b/doc/cs/content/3_preliminaries.tex
@@ -0,0 +1,1465 @@
+\chapter{Preliminaries}
+\label{chap:preliminaries}
+\section{\acl{Taler} Overview}
+\label{sec:taler-intro}
+This chapter provides an high-level overview of GNU Taler with its core components.
+The purpose of this chapter is to provide all the necessary details to understand this work and is not a specification nor a documentation of GNU Taler.
+For more information on GNU Taler refer to \cite{dold:the-gnu-taler-system} or the GNU Taler documentation \cite{taler-documentation}.
+\\
+Generally, GNU Taler is based on Chaumian e-cash \cite{chaum:blind-sign}.
+The following parts discuss the different entities seen in the figure \ref{fig:simple-diagram}
+
+\subsection{Components}
+\label{sec:taler-components}
+In this section the different components are described as in \cite{dold:the-gnu-taler-system}.
+\begin{figure}[htp]
+ \includegraphics[height=0.6\textwidth]{diagram-simple.png}
+ \centering
+ \caption{GNU Taler simple overview (source: \cite{pic:simple-diagram})}
+ \label{fig:simple-diagram}
+\end{figure}
+
+\subsubsection{Exchange}
+\label{sec:exchange}
+The exchange is the payment service provider for financial transactions between a customer and merchant.
+The exchange holds bank money as reserve for the anonymous digital coins.
+\\
+Details of the exchange's functionality can be found in section 4.3 from Florian Dold's thesis \cite{dold:the-gnu-taler-system} or in the documentation \cite{taler-documentation:exchange-operator-manual}.
+\\The code can be found in the exchange's git repository \cite{taler-git:exchange}.
+
+\subsubsection{Customer (Wallet)}
+A customer holds Taler Coins in his electronic wallet.
+As we see in figure \ref{fig:simple-diagram}, a customer can withdraw coins from the exchange.
+These coins can then be spent with a merchant.
+\\
+Details of the wallet's functionality can be found in section 4.6 from Florian Dold's thesis \cite{dold:the-gnu-taler-system} or in the documentations \cite{taler-documentation:wallet-developer-manual} \cite{taler-documentation:wallet-cli-manual}.
+\\
+Git Repositories:
+\begin{itemize}
+ \item Main repository \cite{taler-git:wallet-core} \\
+ This Repository includes the wallet-core and the implementations for the web extension and CLI.
+ \item Android app \cite{taler-git:android}
+ \item iOS app \cite{taler-git:ios}
+\end{itemize}
+
+\subsubsection{Merchant}
+A merchant accepts Taler Coins in exchange for goods and services.
+The merchant deposits these coins at the exchange and receives bank money in return.
+\\
+Details of the wallet's functionality can be found in section 4.5 from Florian Dold's thesis \cite{dold:the-gnu-taler-system} or in the documentations:
+\begin{itemize}
+ \item Operator manual \cite{taler-documentation:merchant-backend-operator-manual}
+ \item Merchant API \cite{taler-documentation:merchant-api}
+ \item Back-Office \cite{taler-documentation:back-office}
+ \item Point-of-Sales \cite{taler-documentation:pos-manual}
+\end{itemize}
+
+\noindent Git Repositories:
+\begin{itemize}
+ \item Backend: \cite{taler-git:merchant}
+ \item Backoffice: \cite{taler-git:backoffice}
+ \item Point-of-Sales App: \cite{taler-git:android} (part of android repo)
+\end{itemize}
+
+\noindent Merchant Frontend Repositories:
+\begin{itemize}
+ \item Payments with Django: \cite{taler-git:django-payments}
+ \item Wordpress woocommerce plugin: \cite{taler-git:woocommerce}
+ \item Saleor Frontend: \cite{taler-git:saleor}
+ \item Demo Frontends: \cite{taler-git:merchant-demos}
+\end{itemize}
+
+\subsubsection{Auditor}
+The auditors, which are typically run by financial regulators, have the purpose to monitor the behavior of the exchanges to assure that exchanges operate correctly.
+\\
+Details of the auditor's functionality can be found in section 4.4 from Florian Dold's thesis \cite{dold:the-gnu-taler-system} or in the documentation \cite{taler-documentation:auditor-operator-manual}.
+\\Git Repositories:
+\begin{itemize}
+ \item Main repository \cite{taler-git:exchange} (Part of exchange repository, inside ./src/auditor and ./src/auditordb)
+ \item Auditor's public website \cite{taler-git:auditor}
+\end{itemize}
+
+\subsubsection{Bank}
+The banks receive wire transfer instructions from customers and exchanges.
+As long as the banks can make wire transfers to each other, the Taler parties do not have to have the same bank.
+
+\subsection{Taler Step by Step}
+\begin{figure}[htp]
+ \includegraphics[height=0.65\textwidth]{taler_bigger.png}
+ \centering
+ \caption{GNU Taler overview (source: \cite{pic:taler-overview})}
+ \label{fig:taler-overview-big}
+\end{figure}
+This is a high-level overview of what Taler generally does.
+Many details (like privacy of the buyer, income transparency) are left out and are explained in the following sections.
+We see in Figure \ref{fig:taler-overview-big} how Taler works step by step (at a high-level).
+\begin{enumerate}
+ \item The customer decides to withdraw Taler coins. To do this, he goes to his bank and gives the order to pay the exchange.
+ \item The customers bank receives the customers order and makes a wire transfer to the exchanges Bank.
+ \item The exchange has received the money and the customer can now withdraw coins to his wallet.
+ \item The customer can now spend his coins at a merchant or merchants of his choice.
+ \item The merchant can then deposit the coins at the exchange.
+ \item The exchanges bank makes a wire transfer to the merchants bank.
+ \item The merchant has successfully received the money for the goods he sold.
+\end{enumerate}
+
+\subsection{Protocols Overview}
+This section provides a high-level overview of the different Taler protocols.
+The details are here omitted and discussed later.
+
+\subsubsection{Refresh Protocol}
+Taler has a quite interesting protocol to get change.
+The purpose of the protocol is to give unlinkable change.
+When a customer buys something from a merchant, in most situations he does not have the exact sum in coins.
+For this reason, change is needed to provide a convenient payment system.
+A coin can be partially spent.
+When this happens, the exchange and the merchant know that this coin is used for that specific contract.
+If the rest of this coin would be spent in future, one could link these two transactions.
+Therefore, a mechanism to get unlinkable change while still preventing money laundering or tax evasion is needed.
+
+\subsubsection{Refund}
+Taler has a built-in refund functionality.
+Merchants can instruct the exchange to refund a transaction before the refund deadline.
+The customer then refreshes the coin(s) in order for payments to remain unlinkable.
+
+\subsubsection{Payment Fees}
+The exchange can charge fees for withdrawal, refreshing, deposition of coins.
+These fees can depend on the denomination since different denominations can have different storage requirements.
+Merchants are able to cover these costs fully or partially.
+\\Exchanges are also able to aggregate wire transfers to merchants, thus reducing wire transfer fees.
+
+\subsubsection{Tipping}
+Merchants can give customers a small tip.
+This feature can be useful for different use cases, for example a merchant can give a tip when a customer participates in a survey.
+
+\subsubsection{Auditing}
+Financial auditing is built-in to Taler in the form of auditors.
+Auditors have read access to certain exchange databases.
+Their task is to verify that exchange work as expected, see chapter 4.4 in Florian Dold's thesis \cite{dold:the-gnu-taler-system} for more details.
+In future versions, the auditor will provide an interface that can be used by merchants to submit deposit confirmation samples.
+This can be used to detect compromised signing keys or a malicious exchange.
+
+\subsection{Properties}
+\label{sec:taler-properties}
+This section describes Taler's properties.
+
+\subsubsection{Free Software}
+The core components of \acl{Taler} are under the following licenses:
+\begin{itemize}
+ \item exchange \cite{taler-git:exchange}: \ac{GNU AGPL}
+ \item merchant \cite{taler-git:merchant}:
+ \begin{itemize}
+ \item backend: \ac{GNU GPL}v3+, \ac{GNU AGPL}
+ \item library: \ac{GNU LGPL}
+ \end{itemize}
+ \item wallet-core \cite{taler-git:wallet-core}: \ac{GNU GPL}
+\end{itemize}
+
+\newpage
+
+\subsubsection{Buyer Privacy Protection}
+Taler protects the privacy of buyers during the different stages in the lifetime of a coin:
+\begin{enumerate}
+ \item Reserve: The reserve is identified by a key pair (private and public key).
+ This means that the exchange doesn't know the identity of the reserve account holder.
+ Whoever knows the private key is able to withdraw from the corresponding reserve.
+ \item Withdrawal: The withdrawal process is encrypted with TLS and uses a blind signature scheme.
+ Therefore the exchange doesn't know which customer holds which coin.
+ \item Payment: The complete payment process doesn't rely on any information identifying a customer.
+\end{enumerate}
+Beware that an anonymous bi-directional channel is assumed for communication between the customer and the merchant as well as during the retrieval of denomination key from the exchange and change for partially spent coins (between customer and exchange).
+
+\subsubsection{Merchant Taxability}
+Merchant's incomes are transparent to auditors which makes taxation by the state possible.
+\newline
+A buyer could theoretically transfer the private key and signature of a coin directly to the merchant to bypass the exchange.
+However, this is suboptimal for the merchant because the knowledge of the coin doesn't grant him the sole ownership.
+If the customer spends the coin in another transaction before the merchant, the coin is voided before the merchant claims its value, thus rendering this form of payment unusable.
+The same principle holds for change (refreshed coins) because it is linked to the original coin.
+Whoever knows the private key and signature of the original coin can obtain the change and use it before the merchant.
+
+\subsubsection{\acl{AML} and \acl{CFT}}
+Every transaction contains the cryptographic hash of the associated contract.
+This enables the authorities to request the merchant to reveal the transaction details (the contract).
+If the merchant isn't able to reveal the contract, in other words fails to deliver a contract with the same hash which is included in the transaction, he risks punishment or further investigation.
+\\Another aspect for \ac{AML} and \ac{CFT} are \ac{KYC} checks.
+\acl{KYC} checks require certain institutions to verify certain information about their business partners in order to prevent money laundering and terrorism (see \cite{dewiki:205456999}).
+\\\acl{Taler} implements these \ac{KYC} checks:
+\begin{itemize}
+ \item Exchanges know the identities of their customers.
+ \item Merchants might need to pre-register with exchanges (depending on the deployment scenario).
+\end{itemize}
+
+\subsubsection{Payer Fraud Prevention}
+The following definition was taken from the BigCommerce website \cite{website:bigcommerce-payment-fraud}.
+\begin{center}
+ \textit{
+ "Payment fraud is any type of false or illegal transaction completed by a cybercriminal. The perpetrator deprives the victim of funds, personal property, interest or sensitive information via the internet."
+ }
+\end{center}
+Prevention of payment fraud is a design goal for \acl{Taler}.
+
+\subsubsection{Minimal Information Disclosure}
+\acl{Taler} aims to disclose as minimal information as possible.
+This mostly concerns customers, but merchants also profit by keeping financial details hidden from competitors.
+
+\subsubsection{\acl{SPOF} Avoidance}
+\ac{SPOF}s are fatal because a failure in this component can bring the complete system to a halt.
+
+\subsubsection{Offline Payment (unsupported)}
+\acl{Taler} doesn't offer offline payments due to the CAP problem (see chapter "Challenges of offline payments" in \cite{grothoff-dold:euro-bearer-online}).
+
+
+\section{Cryptographic Preliminaries}
+\label{sec:crypto-preliminaries}
+In this section we will cover the necessary preliminaries to understand Taler.
+For this part we took most of the information from Nigel P. Smarts book Cryptography made simple \cite{modernCrypto} and from the course "Applied Cryptography" at the BFH.
+The chapter includes preliminaries of the already implemented cryptographic schemes and the ones that are implemented during this work.
+
+\subsection{Hash Functions}
+As source for this section, page 271-275 were used in \textit{Cryptography made Simple} \cite{modernCrypto}.
+\label{sec:hashfunc}
+In this paper a hash function is always a cryptographic hash function.
+Cryptographic hash function are one-way functions $H()$, which are calculating the hash value $h$ from a message $m$ so that $ h = H(m)$.
+With known input one can easily calculate the hash value.
+The other way around is computationally infeasible.
+\\ Cryptographic hash functions have the following properties.
+
+\subsubsection{(First) Preimage Resistance}
+\label{sec:first-pre-resist}
+It should be hard to find a message with a given hash value.
+For a given output $y$ it is impossible to calculate the input $x$ such that $ h(x) = y$.
+\\ This basically means, a hash function can not be inverted, not even with unlimited computing power.
+Too much information is destroyed by the hash function and there are many values resulting in the same hash.
+
+\subsubsection{Second Preimage Resistance}
+\label{sec:second-pre-resist}
+Given one message, it should be hard to find another message with the same hash value.
+For a given $x_1$ and $h(x_1)$ it is hard to find a $x_2$ such that $h(x_1) = h(x_2)$.
+
+\subsubsection{Collision Resistance}
+\label{sec:col-resist}
+It should be hard to find two messages with the same hash value.
+It is quite obvious that collisions are existent, since there are more possible messages than hash values.
+This is also known as the pigeonhole principle.
+Even if there are hash collisions, it should be hard to find $x_1 \ne x_2$ such that $h(x_1) = h(x_2)$.
+Due to the birthday paradoxon (a detailed description can be found under \cite{enwiki:1019272750}) it is easier to cause a collision of two arbitrary messages than of a specific message.
+
+\subsection{Key Derivation}
+\label{sec:kdf}
+A \ac{KDF} derives one or more cryptographically strong secret keys from initial keying material by using a \acl{PRF}.
+Therefore, input of a \ac{KDF} is some sort of keying material (e.g. from a key exchange).
+Output will be a pseudo-random bit-string, which can be used as new key material.
+
+
+\subsubsection{\acl{PRF}}
+A \ac{PRF} is a deterministic function whose output appears to be random if the input is unknown.
+The output is computationally indistinguishable from a true random source.
+Different PRFs exist, for example \ac{AES} or HMAC could be used as \ac{PRF}.
+In the case of \gls{hkdf}, HMAC is a suitable choice as \ac{PRF}.
+
+\subsubsection{HMAC}
+\label{sec:hmac}
+A \acl{MAC} (\ac{MAC}) provides \textbf{unforgeability}, which means, only a person who knows the key $k$ can compute the MAC.
+Further, a MAC protects the \textbf{message integrity}, since unauthorized changes are being detected.
+Last but not least, \textbf{message authenticity} is provided too, since only a person who knows the key can compute the HMAC.
+However, it does not provide non-repudation because it is a shared secret.
+MACs take a message and a key as input and give the MAC tag as output.
+\\One way to design such MACs is by using a hash function.
+The obvious way one would design such a function would most likely be: $ t = H(k || m || pad)$
+However, this variant would be \textbf{completely insecure} with hash functions based on Merkle-Damgard constructions.
+Because of the structure of such hash functions, it is easy to find $H(M || X)$ for an arbitrary $X$ and a hash value $H(M)$, with that a so called \textit{length-extension} attack is possible.
+\\HMAC prevents this attack by computing the MAC as follows: $t = $ HMAC$_k(m) = H( (k \oplus opad) || H( (k \oplus ipad) || m) ) $
+\\ H() could be any standard hash functions, for example SHA-256, SHA-512 or SHA-3.
+$\oplus$ stands for the XOR operation.
+HMAC is specified in \cite{rfc2104}.
+
+\subsubsection{HKDF}
+\gls{hkdf} follows the \textit{extract-then-expand} paradigm and therefore has two phases.
+In the extract phase, the input keying material is taken and a fixed-length pseudorandom key $K$ is \textit{extracted}.
+This phase is used to generate a high entropy pseudorandom key from potentially weaker input keying material.
+This key $K$ is used in the \textit{expand} phase to output a variable-length, pseudorandom key.
+
+The \gls{hkdf} makes use of HMAC (\ref{sec:hmac}) instantiated with a hashfunction \ref{sec:hashfunc}.
+It takes the input keying material, a salt and the length of output keying material as arguments.
+\gls{hkdf} is specified in \cite{rfc5869}.
+
+\subsection{Digital Signatures}
+\label{sec:sign-schemes}
+As source for this section, page 216-218 were used in \textit{Cryptography made Simple}\cite{modernCrypto}.
+A digital signature is a cryptographic function used to verify the origin and integrity of a message.
+It provides the following properties:
+\begin{itemize}
+ \item Sender authenticity: The origin/sender of a message can not be forged.
+ \item Message integrity: No unauthorized change to the message can be made, the message is tamperproof.
+ \item Non-repudiation: After a message is signed, one can not dispute that a message was signed.
+\end{itemize}
+If verification is successful, only Alice knows her private key and Bob uses Alice's public key to verify, then Bob knows that this message is really from Alice and the message has not been tampered or further modified.
+A digital signature scheme has a message space M, a signature space S and three algorithms:
+\begin{itemize}
+ \item Key generation: $(pk,sk) \gets keyGen()$
+ \item Signature generation: $s \gets $sign$_sk(m)$
+ \item Verification: $ v \gets $verify$_pk(m,s)$ where $v \in {0,1}$
+\end{itemize}
+If the result of the verification algorithm equals 1, a signature for m is called valid.
+\\Digital signatures are publicly verifiable, which means anyone can verify that $(m,s)$ is legitimate.
+
+\subsubsection{Adversary Models \& Provable Security}
+\label{sec:euf-cma}
+Digital Signature schemes are believed to be secure when they are EUF-CMA secure.
+\acl{EUF} (\ac{EUF}) means that given a public key $pk$ the adversary cannot construct a message with a valid signature, except with a negligible probability.
+\acl{CMA} (\ac{CMA}) means that an adversary can ask a signing oracle to produce valid signatures $s' = sign_{sk}(m')$ for arbitrary messages $m' \ne m$.\\
+EUF-CMA is therefore existentially unforgeability under chosen message attack and is a standard security model for digital signatures.
+More details can be found in page 217-218 in \textit{Cryptography made Simple} \cite{modernCrypto}.
+
+
+\subsubsection{RSA-FDH Signature Scheme}
+As source for this section, pages 300-301 and 333-335 were used in \textit{Cryptography made Simple} \cite{modernCrypto}.
+
+\label{sec:rsa-fdh}
+RSA-FDH is a deterministic digital signature scheme which provides authenticity, message integrity and non-repudation.
+The RSA signature scheme (without the full domain hash) is NOT \ac{EUF} secure and is vulnerable to existential forgery attacks.
+RSA-FDH is one possible solution for a EUF-CMA secure scheme. EUF-CMA and its adversary model is further discussed in section \ref{sec:euf-cma}.
+RSA-FDH is EUF-CMA secure under factoring and RSA assumptions.
+More details on the hardness assumptions can be found on page 32-49 in \textit{Cryptography made Simple} \cite{modernCrypto}.
+
+\paragraph{\acl{FDH}}
+A \acl{FDH} is a hash function with an \textbf{image size equal to the size of the RSA modulus}.
+The hashfunction $h()$ used in the RSA-FDH sign (section \ref{sec:rsa-fdh-sign}) and RSA-FDH verify (section \ref{sec:rsa-fdh-sign}) needs to fulfill all the security properties we defined in chapter \ref{sec:hashfunc}.
+This means that the image is a co-domain of the RSA group $\mathbb{Z}_N^*$.
+Provided that the hashfunction has properties of a random oracle, \textbf{RSA-FDH is provably EUF-CMA secure} under the RSA assumption.
+
+\paragraph{RSA Key Generation}
+\label{sec:rsa-keygen}
+The information in this section is from the script of the BFH module \textit{Public Key Cryptography} taught by Prof. Dr. Walter Businger (\cite{businger:public-key-crytpo}).
+The RSA private and public key are generated like this:
+\begin{enumerate}
+ \item Generate two random prime numbers $ p, q $ where $ p \neq q $
+ \item Calculate $ N = pq $
+ \item Calculate $ \lambda = \text{lcm}(p-1, q-1) $
+ \item Randomly choose a number $ d $ which is bigger than $ p $ and $ q $ and where $ \text{gcd}(d, \lambda) = 1 $
+ \item Calculate $ e $, the multiplicative inverse of $ d \mod \lambda $
+ \item The public key is $ (e, N) $, the private key is $ (d, N) $
+ \item Destroy all numbers not included in the private or public key
+\end{enumerate}
+Note that "lcm" stands for least common multiplier and "gcd" means greatest common divisor.
+The original RSA specification uses $ \phi(n) = (p-1)(q-1) $ instead of $ \lambda = \text{lcm}(p-1, q-1) $.
+$ \phi(n) $ is a multiple of $ \lambda $ (for details see \cite{businger:public-key-crytpo}).
+
+\paragraph{Signature Algorithm}
+\label{sec:rsa-fdh-sign}
+The signature can be calculated as following:
+\\ $ s \gets (\text{FDH}(m))^d \mod N$
+
+\paragraph{Verification Algorithm}
+\label{sec:rsa-fdh-verify}
+The signature can be validated as following:
+\\ $ \text{FDH}(m) \gets s^e \mod N$
+
+\subsubsection{Schnorr Signature Scheme}
+\label{sec:schnorr-sig}
+The Schnorr Signature scheme is a randomized signature scheme, which is proven to be EUF-CMA secure under \ac{DLP}.
+More information about the \ac{DLP} can be found in chapter 3 of \textit{Cryptography made Simple} \cite{modernCrypto}.
+In february 2008 the patent expired and Schnorr signatures are now becoming widely deployed. (eg. EdDSA).
+Schnorr signatures gained quite some attraction lately, since Bitcoin has announced to support Schnorr signatures starting from Block 709632 (see \cite{bip:schnorr-bitc}, \cite{btc:releasnotes-0.21}, and \cite{git:secp256k1-schnorr}).
+As reference for the Schnorr signature scheme (and later Clause Blind Schnorr Signature Scheme) we use the paper \textit{Blind Schnorr Signatures and Signed ElGamal Encryption in the Algebraic Group Model} \cite{cryptoeprint:2019:877} as general source for Schnorr related schemes.
+
+\paragraph{Setup}
+We have a Group $\mathbb{G}$ of order $p$ and a generator $G$ of the group.
+Further a Hashfunction $H: \{0,1\}* \rightarrow \mathbb{Z}_p$ is used.
+
+\paragraph{Key Generation}
+The key generation is the same as in \ac{DSA}.
+\begin{enumerate}
+ \item private key is a random integer $x \leftarrow random \in \mathbb{Z}_p$
+ \item public key is $X \leftarrow xG$
+\end{enumerate}
+
+\paragraph{Sign}
+The sign function takes the secret key $x$ and the message $m$ to be signed as argument.
+The interactive version with a signer and a user can be seen in figure \ref{fig:schnorr-sign-protocol}.
+\begin{enumerate}
+ \item choose $r \leftarrow random \in \mathbb{Z}_p $
+ \item calculate $R := rG$
+ \item $ c := H(R,m)$
+ \item $s := r + cx \mod p$
+ \item $\sigma := (R,s)$
+ \item return $\sigma$
+\end{enumerate}
+
+\begin{figure}
+ \begin{equation*}
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{User} & & \text{Signer}
+ \\ \text{knows:} & \text{public parameters:} & \text{knows:}
+ \\ \text{public key } X & \langle p, \mathbb{G}, G, H\rangle & \text{private signing key } x, X := xG
+ \\ & & n \leftarrow random \in \mathbb{Z}_p
+ \\ & & R := nG
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{R} &
+ \\ c := H(R,m)
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{c} &
+ \\ & & s := n+cx \mod p
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{s} &
+ \\ \text{check } sG = R + cX
+ \\ \sigma := \langle R,s \rangle
+ \end{array}
+ \end{equation*}
+ \caption{Schnorr signature protocol with user who wants to sign a message $m$ by the signer}
+ \label{fig:schnorr-sign-protocol}
+\end{figure}
+
+
+\paragraph{Verify}
+The verify function takes the public key $X$, the message $m$ and the signature $\sigma$ as argument.
+\begin{enumerate}
+ \item $ c := H(R,m)$
+ \item check $sG = R + cX$
+ \item return true if check was successful, false otherwise
+\end{enumerate}
+
+The verification holds because $sG = R + cX $ is $ (r + cx)G = rG + cxG$ which is equal.
+
+\subsubsection{\acl{EdDSA}}
+\ac{EdDSA} is a scheme for digital signatures based on twisted Edwards curves and the Schnorr signature scheme.
+The information described here originates from \cite{rfc8032} and \cite{enwiki:1013094030}.
+\ac{EdDSA} is a general algorithm that can be used with different curves. A choice in curves consists of 11 parameters. These are the most important (the others can be found in \cite{rfc8032}:
+\begin{itemize}
+ \item odd prime power q (used to generate elliptic curve over finite field $ \mathbb{F}_q $)
+ \item integer b, where $ 2^{b-1} > q $, describing the bit size of various elements
+ \item cryptographic hash function $ H $ with output size of $ 2b $
+ \item base point on curve $ B $ (generator)
+ \item number $ c $, (either 2 or 3)
+ \item prime number L where $ LB = 0 $ and $2^c*L = \text{number of points on the curve} $
+\end{itemize}
+
+\paragraph{Key Creation}
+\label{sec:eddsa-keygen}
+The private key $ k $ is a random bit-string of length $ b $.
+The public key $ A $ is a point on the curve.
+To generate it, we calculate $ A = sB $ where $ s = H(k)[:b] $ (meaning that we take the $ b $ least significant bits from the output of the hash function as $ s $).
+
+\paragraph{Signature Creation}
+\label{sec:eddsa-signature-creation}
+An \ac{EdDSA} signature of a message $ M $ is composed of $ (R, S )$, which are generated as follows:
+\begin{align*}
+ s & = H(k)[:b]
+ \\r &= H(H(k)[b + 1:2b] \text{ || } M )
+ \\R &= rB
+ \\S &= (r + H(R || A || M) * s) \mod L
+\end{align*}
+Note that $ [:b] $ means taking the $ b $ least significant bits, $ [b + 1:2b] $ means taking the b most significant bits and $ R || A $ means concatenating $ R $ and $ A $.
+
+\paragraph{Signature Verification}
+$ (R, S) $ is the signature, $ M $ is the message, $ A $ is the public key and $c, B $ are curve parameters.
+To verify a signature, the following equation must be satisfied:
+\\$ 2^cSB = 2^cR + 2^cA*H(R || A || M) $
+\\This means that verify() returns 1 if the equation holds and 0 otherwise.
+
+\paragraph{Ed25519}
+\label{par:curve25519}
+Ed25519 is an \ac{EdDSA} based signature scheme and uses \gls{25519} (see \cite{bern:curve25519}), which offers 128 security bits.
+\gls{25519} gets its name from the prime $ 2^{255} - 19 $ and is designed for fast computation and to resist side channel attacks.
+\\These are the most important \ac{EdDSA} parameters for Ed25519 (the others can be found in \cite{rfc8032}):
+\begin{itemize}
+ \item $ q = 2^{255} - 19 $
+ \item $ b = 256 $
+ \item $ H() $: SHA-512
+ \item $ B = (15112221349535400772501151409588531511454012693041857206046113283949847762202, $
+ \\ $46316835694926478169428394003475163141307993866256225615783033603165251855960) $
+ \item $ c = 3 $
+ \item $ L = 2^{252} + 27742317777372353535851937790883648493 $
+\end{itemize}
+\subsection{Blind Signature Schemes}
+\label{sec:blind-sign-schemes}
+\label{sec:blind-sign-perfect-blindness}
+One could think of blind signatures as a message put into an envelope made of carbon paper. The signer stamps his signature on the envelope and due to the properties of a carbon paper, the message is now signed too. (the stamp "stamps" through the envelope on the message).
+The client then can open the envelope, and he possesses a correctly signed message.
+This is achieved by the client by blinding the message with a blinding factor before sending to the signer ("blind()" operation).
+The signer signs the blinded message and returns the signature of the blinded message to the client.
+The client, who possesses the blinding factor can then unblind the signature and gets a signature of the original message ("unblind()" operation).
+The explanation above leads us to the additional security property of a blind signature, the \textit{blindness} of signatures.
+This property requires that a signer cannot link a message/signature pair to a particular execution of the signing protocol \cite{cryptoeprint:2019:877}.\\
+A blind signature scheme is called \textit{perfectly blind} if the generated signature (\textit{unblinded} signature) is statistically independent of the interaction with the signer (\textit{blinded} signature).
+Thus, blind signatures cannot be linked to the signer interaction in an information theoretic sense. \cite{schnorr:perfect-dl-signatures} \cite{spring:wallet-db-with-observers}
+\subsubsection{RSA Blind Signature Scheme}
+\label{sec:blind-rsa-sign}
+As source for this section, the course material from "Applied Cryptography" from BFH and \cite{chaum:blind-sign} were used.
+The process for receiving a valid signature from the exchange uses a blind signature scheme invented by David Chaum (\cite{chaum:blind-sign}) which is based on RSA signatures.
+The process is described in figure \ref{fig:blind-sign}.
+\\Note that Bob (the signer) uses a standard RSA signature and can't verify if the message from Alice is blinded.
+\begin{figure}[htp]
+ \begin{equation*}
+ \begin{array}{ l c l }
+ \text{Alice} & & \text{Bob}
+ \\ \text{knows:} & & \text{knows:}
+ \\ \text{RSA public key } D_B = e, N & & \text{RSA keys } d_B, D_B
+ \\ \text{message } m & &
+ \\ & &
+ \\ \text{blind:} & &
+ \\ r \leftarrow random \in \mathbb{Z}_N^* & &
+ \\ m' = m*r^{e} \mod N & &
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{m'} &
+ \\ & & \text{sign:}
+ \\ & & s' = (m')^{d_B} \mod N
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{s'} &
+ \\ \text{unblind:}& &
+ \\ s = s'*r^{-1} & &
+ \end{array}
+ \end{equation*}
+ \caption{Blind signature scheme}
+ \label{fig:blind-sign}
+\end{figure}
+Mathematically a blind signature works similar to the "naive" RSA signature scheme.
+We consider Alice as the party who wants to have a message $m$ blindly signed by Bob.
+Bob has a public key $D_B = (e, N)$ and his corresponding private key $d_B$ known only by Bob.
+Alice needs to generate a random blinding factor $r\in \mathbb{Z}_N^*$, which needs to remain secret.
+Alice then calculates $m'=m*r^e \mod N$. The blinded value $m'$ will now be sent to Bob by Alice.
+Bob on his side calculates now the signature as usual: $s' = m'^{d_B} \mod N$.
+The signature $s' $ is sent to Alice by Bob. Alice can calculate the signature as following: \\$s = s' * r^{-1}$.
+\\$s$ is a valid signature of $m$, while the signer, Bob, does not know $m$ nor $b$.
+\\We now want to analyze this closer to understand why blind signatures work.
+Let's look at this equation:
+\\$ s' = m'^{d_B} = (m*r^e)^{d_B} = m^{d_B} * (r^e)^{d_B}$.
+\\The interesting part for now is $(r^e)^{d_B}$, since this is $r^1$.
+This means the signature $s'$ we got from Bob is $s' = m^{d_B} * r^1$.
+Now it is quite obvious how the valid signature $s$ can be calculated by multiplying with the inverse of $r$ as in: $ s = m^{d_B} * r^1 * r^{-1} = s' * r^{-1}$.
+
+\paragraph{Blindness}
+\label{par:prop-blindness-rsa}
+\gls{RSABS} are considered \textit{perfectly blind} (see \autoref{sec:blind-rsa-sign}).
+There exist multiple $\langle r, m \rangle$ pairs that matches $m'$ such that $m' = m * r^e \mod N$.
+Thus, \gls{RSABS} achieves \textit{perfect blindness} which cannot be attacked by brute-force or similar attacks.
+Even if a valid $\langle r, m \rangle$ pair is found, the attacker has no possibility to know if it is the correct pair without additional information.
+
+\paragraph{RSA Blinding Attack}
+There are also some possible attacks on this scheme.
+First this is subject to the RSA blinding attack.
+In this attack the property is used, that the signing operation is mathematically equivalent to the encrypt operation in RSA.
+The attacker has a ciphertext $c = m^d$ and he wants to break this message.
+Now, the attacker uses the ciphertext $c$ as "message" in the blind signature scheme above.
+\\$m'' = cr^e \mod n = (m^e \mod n) * r^e \mod n = (mr)^e \mod n$.
+ \\The Attacker then sends the blinded message $m''$ to the signer who blindly signs the blinded message.
+ \\$s' = m''^d \mod n = (mr)^{ed} \mod n = m*r \mod n$.
+\\The attacker recovers the message now with $m = s'*r^{-1} \mod n$.
+\\This attack could be prevented by the use of a padding scheme, however this would break RSA symmetry.
+In blind signatures the RSA symmetry is needed, otherwise it would produce an incorrect value in the unblind operation.
+\\Due to this issue; One should never use the same key for signing and encryption!
+A version of blind signatures, RSA-FDH will be discussed, which solves this issue. \cite{enwiki:blind-sign}
+
+\paragraph{Low Encryption Exponent Attack}
+For this attack a possibly small message $m$ and a small public key $e$ is given.
+If now $c = m^e < n$, one could compute $ m = \sqrt[e]{c} $.
+Similar to the RSA blinding attack, padding could solve the issue, however RSA symmetry is needed.
+To overcome this issue, RSA-FDH blind signatures are introduced in the next chapter.
+
+\subsubsection{RSA-FDH Blind Signatures}
+As source for this section, the course material from "Applied Cryptograhy" from BFH and \cite{chaum:blind-sign} were used.
+\label{sec:blind-sign-fdh}
+Blind signatures are discussed in \ref{sec:blind-sign-schemes}.
+This version is quite similar to the blind signatures already introduced in figure \ref{fig:blind-sign}.
+In addition, the \gls{fdh} introduced in section \ref{sec:rsa-fdh} is used.
+The difference is that the message does not get directly blinded, it gets hashed before with a \acl{FDH}.
+\\Given Alice's message $m$ and Bobs public key $D_B = (e,n)$.
+As in the simple \gls{RSABS}, a random blinding factor $r\in \mathbb{Z}_N^*$ is generated.
+Before the message is blinded, the \acl{FDH} $ f = \text{FDH}(m)$ is calculated, which then is blinded as in $f' = fr^e \mod n$.
+Since the hash function is a \acl{FDH}, $f$ is in the RSA domain $\mathbb{Z}_N^*$.
+Now proceed as in the blind signature scheme introduced in the previous section.
+The blinded hash $f'$ will be transmitted to Bob who then computes the signature $s' = f'^d \mod n$ and sends $s'$ back.
+Alice unblinds $s'$ and gets the valid signature $s = s'r^{-1} \mod n$.
+
+\begin{figure}[htptp]
+ \begin{equation*}
+ \begin{array}{ l c l }
+ \text{Alice} & & \text{Bob}
+ \\ \text{knows:} & & \text{knows:}
+ \\ \text{RSA public key } D_B = e, N & & \text{RSA keys } d_B, D_B
+ \\ \text{message } m & &
+ \\ & &
+ \\ Compute f = FDH(m) & &
+ \\ & &
+ \\ \text{blind:} & &
+ \\ r \leftarrow random \in \mathbb{Z}_N^* & &
+ \\ f' = f*r^{e} \mod N & &
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{f'} &
+ \\ & & \text{sign:}
+ \\ & & s' = (f')^{d_B} \mod N
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{s'} &
+ \\ \text{unblind:}& &
+ \\ s = s'*r^{-1} & &
+ \end{array}
+ \end{equation*}
+ \caption{RSA-FDH blind signatues}
+ \label{fig:rsa-fdh-blind-sign}
+\end{figure}
+
+This version of blind signature is not subject to the attacks introduced in the previous section.
+
+\subsubsection{Blind Schnorr Signature Scheme}
+\label{sec:blind-schnorr-sig}
+The Blind Schnorr Signature Scheme \textbf{is considered broken} and should not be implemented.
+This section is here to explain how blind Schnorr signatures generally work and should help to understand The Clause Blind Schnorr Signature Scheme \ref{sec:clause-blind-schnorr-sig}.
+
+For the signer the calculations are the same as in the original Schnorr Signature Scheme \ref{fig:schnorr-sign-protocol}.
+The exchange chooses a random $n \leftarrow random \in \mathbb{Z}_p$ and calculates $R := nG$ as before.
+In comparison to the Schnorr Signature Scheme (see section \ref{sec:schnorr-sig}) we generate two random blinding factors $\alpha, \beta \leftarrow random \in \mathbb{Z}_p$ to achieve \textit{blindness}.
+The User then calculates $R' := R + \alpha G + \beta X$.
+This $R'$ is then used to calculate $c' := H(R',m)$ and is blinded with $b$ as in $c := c' + \beta \mod p$.
+The challenge $c$ is then blindly signed by the signer $s := n+cx \mod p$.
+The User checks if the signature is valid the same way as in the original protocol.
+Finally the user has to unblind $s$ as in $s' := s + \alpha \mod p$.
+Now the unblinded signature is $\sigma := \langle R',s' \rangle$.
+This scheme is described in figure \ref{fig:schnorr-blind-sign-scheme}.
+More details can be found in the \textit{Blind Schnorr Signatures and Signed ElGamal Encryption in the Algebraic Group Model} paper \cite{cryptoeprint:2019:877}.
+
+%The unblinded signature can be verified with $s'G = R' + c'X$, since following equation is satisfied:\\
+%$s'G = sG + \alpha G = (r + cx)G + \alpha G = R + \alpha G + \beta X + (-\beta + c)X = R' + c'X = R' + H(R',m)X$ \cite{cryptoeprint:2019:877}.\\
+
+To verify the signature, the verifier has to check if the following equation holds:
+\begin{align*}
+ s'G & = R' + c' X
+ \\ &= R' + H(R', m) X
+\end{align*}
+$ s', R' $ together form the signature, $ X $ is the public key and $ m $ is message.
+
+The reason why this works is that the original Schnorr signature verification algorithm remains the same in blind signatures.
+\begin{align*}
+ sG = R + c X
+\end{align*}
+
+By replacing $ s, R, c, $ with the values used in the blind signature scheme (as in figure \ref{fig:schnorr-blind-sign-scheme})
+\begin{align*}
+ \\ s &= s' - \alpha
+ \\ R &= R' - \alpha G - \beta X
+ \\ c &= c' + \beta
+\end{align*}
+
+we receive the following equation:
+\begin{align*}
+ sG & = R + c X
+ \\ (s' - \alpha)G &= R' - \alpha G - \beta X + (c' + \beta)X
+ \\ s'G - \alpha G &= R' - \alpha G + c' X
+ \\ s'G &= R' + c' X
+\end{align*}
+
+\begin{figure}[htp]
+ \begin{equation*}
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{User} & & \text{Signer}
+ \\ \text{knows:} & \text{public parameters:} & \text{knows:}
+ \\ \text{public key } X & \langle p, \mathbb{G}, G, H\rangle & \text{private signing key } x, X := xG
+ \\ & & r \leftarrow random \in \mathbb{Z}_p
+ \\ & & R := rG
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{R} &
+ \\ \alpha, \beta \leftarrow random \in \mathbb{Z}_p
+ \\ R' := R + \alpha G + \beta X
+ \\ c' := H(R',m)
+ \\ c := c' + \beta \mod p
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{c} &
+ \\ & & s := r+cx \mod p
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{s} &
+ \\ \text{check } sG = R + cX
+ \\ s' := s + \alpha \mod p
+ \\ \sigma := \langle R',s' \rangle
+ \end{array}
+ \end{equation*}
+ \caption{The broken Schnorr Blind Signature Scheme}
+ \label{fig:schnorr-blind-sign-scheme}
+\end{figure}
+
+\paragraph{Blindness}
+Blind Schnorr Signatures also achieve \textit{perfect blindness} (\autoref{sec:blind-sign-perfect-blindness}). \cite{spring:wallet-db-with-observers} \cite{cryptoeprint:2019:877}
+
+\paragraph{ROS Problem}
+\label{par:ros-problem}
+The security of Blind Schnorr Signatures relies on an additional hardness assumption, the \textit{\acl{ROS}} or ROS problem. \cite{Schnorr01securityof}
+Solving the \ac{ROS} problem breaks the unforgeability property of blind Schnorr signatures by finding $l + 1$ signatures out of $l$ signing operations.
+David Wagner showed in his paper that the \ac{ROS} problem can be reduced to the $(l+1)$-sum problem and therefore showed that an attack is practicable. \cite{wagner:generalized-bday-prob}
+More details about \ac{ROS} and Wagner's algorithm can also be found in the paper \textit{Blind Schnorr Signatures and Signed ElGamal Encryption in the Algebraic Group Model} \cite{cryptoeprint:2019:877}.
+\\
+Due to the possible attack, Blind Schnorr Signatures are considered \textbf{broken} and should not be used.
+The next section \ref{sec:clause-blind-schnorr-sig} introduces a modified version for which the \ac{ROS} problem is much harder to solve.
+\\
+The \ac{ROS} problem is a recent research topic.
+Recently a paper about the (in)security of \ac{ROS} was published. \cite{cryptoeprint:2020:945}
+The scheme introduced in the next section \ref{sec:clause-blind-schnorr-sig} is considered secure in 2021.
+It is important to keep in mind that the \ac{ROS} problem is much newer and there is open research done.
+
+\subsubsection{Clause Blind Schnorr Signature Scheme}
+\label{sec:clause-blind-schnorr-sig}
+The Clause Blind Schnorr Signature Scheme is a modification of the Blind Schnorr Signature Scheme for which the \ac{ROS} problem is harder to solve.
+The Clause Blind Schnorr Signature Scheme does this by choosing two random values $r_0, r_1$ and calculating $R_0 := r_0G; R_1 := r_1G$.
+The user generates the blinding factors twice $\alpha_0, \alpha_1, \beta_0, \beta_1 \leftarrow random \in \mathbb{Z}_p$.
+The user then calculates the challenges as before $c_0' := H(R_0',m); c_0 := c_0' + \beta_0 \mod p$ and $c_1' := H(R_1',m); c_1 := c_1' + \beta_1 \mod p$.
+After the signer receives the two challenges $c_0$ and $c_1$, the signer randomly chooses $b \leftarrow random \{0, 1\}$ and calculates only $s_b$ as in $s := r_b+c_bx \mod p$.
+The User receives $s, b$ and can unblind the signature to receive his signature $\sigma := \langle R'_b, s'_b \rangle$.
+The verification algorithm remains the same for Clause Blind Schnorr Signature Scheme.
+Figure \ref{fig:clause-blind-schnorr-sign-scheme} shows the Clause Blind Schnorr Signature Scheme.
+More details about the scheme can be found in the paper \textit{Blind Schnorr Signatures and Signed ElGamal Encryption in the Algebraic Group Model} \cite{cryptoeprint:2019:877}.
+
+
+\begin{figure}
+ \begin{equation*}
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{User} & & \text{Signer}
+ \\ \text{knows:} & \text{public parameters:} & \text{knows:}
+ \\ \text{public key } X & \langle p, \mathbb{G}, G, H\rangle & \text{private signing key } x, X := xG
+ \\ & & r_0, r_1 \leftarrow random \in \mathbb{Z}_p
+ \\ & & R_0 := r_0G
+ \\ & & R_1 := r_1G
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{R_0, R_1} &
+ \\ \alpha_0, \alpha_1, \beta_0, \beta_1 \leftarrow random \in \mathbb{Z}_p
+ \\ R_0' := R_0 + \alpha_0 G + \beta_0 X
+ \\ R_1' := R_1 + \alpha_1 G + \beta_1 X
+ \\ c_0' := H(R_0',m)
+ \\ c_1' := H(R_1',m)
+ \\ c_0 := c_0' + \beta_0 \mod p
+ \\ c_1 := c_1' + \beta_1 \mod p
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{c_0, c_1} &
+ \\ & & b \leftarrow random \in \{ 0,1\}
+ \\ & & s := r_b+c_bx \mod p
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{b, s} &
+ \\ \text{check } sG = R + cX
+ \\ s' := s + \alpha_b \mod p
+ \\ \sigma := \langle R_b',s' \rangle
+ \end{array}
+ \end{equation*}
+ \caption{The Clause Schnorr Blind Signature Scheme}
+ \label{fig:clause-blind-schnorr-sign-scheme}
+\end{figure}
+
+\paragraph{Blindness}
+\label{par:prop-blindness-cs}
+\gls{CSBS} also achieve \textit{perfect blindness} as in Schnorr Blind Signatures (see \autoref{sec:blind-rsa-sign}). \cite{cryptoeprint:2019:877}
+
+
+\subsection{Diffie Hellman Key Exchange}
+As source for this section, pages 383-386 were used in \textit{Cryptography made Simple} \cite{modernCrypto}.
+\label{sec:preliminaries-DHKE}
+The \acl{DHKE} is a well proofed and well understood key exchange mechanism.
+\ac{DHKE} relies mainly on the \acl{DLP}.
+\ac{DHKE} is used for key exchange in many protocols today (e.g. TLS cipher suites).
+
+\subsubsection{Hardness Assumptions}
+\label{sec:dlp}
+As already stated, the \ac{DHKE} relies on the assumption that calculating the discrete logarithm is hard.
+The \ac{DLP} is in $G$, where $G$ is a finite abelian group of prime order q.
+This could either be a subgroup of the multiplicative group of a finite field or the set of points on an elliptic curve over a finite field.
+Given $g,h \in G$, find x such that $g^x = h$.
+
+Further, \ac{CDH} and \ac{DDH} are important hardness assumption, which can be reduced to the \ac{DLP}.
+Hardness assumptions are introduced very briefly.
+In this work we believe that these well proofed and well tested hardness assumptions hold.
+(See Chapter 3.1 \textit{Cryptography made Simple} \cite{modernCrypto} for more details on DH hardness assumptions.)
+
+\subsubsection{Protocol}
+Alice and Bob want to securely exchange a key with \ac{DHKE}.
+Alice has a private key $a$ and a corresponding public key $A = g^a \mod p$.
+Bob has a private key $b$ and a corresponding public key $B = g^b \mod p$.
+With elliptic curves, the private key is a multiplication factor for a base point $g$ (see example on page 385 \textit{Cryptography made Simple} \cite{modernCrypto}).
+
+Alice now sends her public key $A$ to Bob.
+Bob can then calculate $k = A^b \mod p = g^{ab} \mod p$ and sends his public key $B$ to Alice.
+Alice can then calculate $k = B^a \mod p = g^{ab} \mod p$.
+Both get the same key $k$ as result of the key exchange.
+Note: This protocol on its own is not an authenticated key exchange, which means that Man-in-the-Middle attacks are possible.
+
+A different way of looking at \ac{DHKE} is by thinking of a lock which can be unlocked by two (private) keys.
+If one of the two private keys are known, one could calculate $k$ on its own.
+Taler's refresh protocol (see \ref{sec:refresh-protocol}) uses \ac{DHKE} in a very interesting way.
+
+\subsection{Cut and Choose Protocol}
+\label{sec:preliminaries-cut-choose}
+A good introduction to cut and choose protocols gives the Paper from Claude Crépeau (\cite{Crépeau2005} References to the important examples can be found in the paper.):
+\begin{center}
+ \textit{
+ "A cut and choose protocol is a two-party protocol in which one party tries to convince another party that some data he sent to the former was honestly constructed according to an agreed upon method.
+ Important examples of cut-and-choose protocols are interactive proofs, interactive arguments, zero-knowledge protocols, witness indistinguishable and witness hiding protocols for proving knowledge of a piece of information that is computationally hard to find.
+ Such a protocol usually carries a small probability that it is successful despite the fact that the desired property is not satisfied.
+ \\\dots\\
+ The expression cut-and-choose was later introduced by David Chaum in analogy to a popular cake sharing problem:
+ Given a complete cake to be shared among two parties distrusting of each other (for reasons of serious appetite).
+ A fair way for them to share the cake is to have one of them cut the cake in two equals hares, and let the other one choose his favourite share.
+ This solution guarantees that it is in the formers best interest to cut the shares as evenly as possible."
+ }
+\end{center}
+
+Talers cut and choose protocol is \textit{zero knowledge}, which means that nothing about the secret is learned.
+The cut and choose protocol used in Taler is explained further when the refresh protocol is discussed (see \ref{sec:refresh-protocol}).
+
+\section{Taler Protocols}
+In section \ref{sec:taler-intro} a brief overview of how \acl{Taler} works is given. All the relevant preliminaries are covered in section \ref{sec:crypto-preliminaries}.
+In this section a closer look at the different protocols is taken.
+
+\subsection{Withdrawal Protocol}
+\label{sec:withdrawal}
+The withdrawal protocol is described in chapter 4.7.2 of \cite{dold:the-gnu-taler-system}.
+Before coins can be withdrawn, the customer generates a reserve key pair $ w_s, W_p \leftarrow Ed25519.KeyGen() $.
+He then transfers a certain amount of money from his bank to the exchange's bank via wire transfer.
+This payment must include the reserve public key $ W_p $.
+The customer will later authorize withdrawals with a signature using his private reserve key.
+%The customer can then authenticate, since he is in possession of the private reserve key.
+As soon as the exchange has received the payment, the withdrawal for coins with a value $ i $ can begin (described in figure \ref{fig:withdrawal-process}).
+
+At this stage the client knows the reserve private key and the public denomination key.
+The customer can then create coins up to the amount included in the wire transfer.
+The coin creation and blind signatures are described in section \ref{sec:blind-sign-fdh}.
+So the client generates a planchet (an Ed25519 key pair) and blinds it.
+This blinded planchet is then signed by the customers private reserve key, to prove that the customer is eligible to withdraw the coin.
+The exchange who receives the blinded planchet and the signature first checks whether the signature is valid with the public reserve key sent with the wire transfer.
+When successful, the exchange blindly signs the planchet, returns the signature and notes the amount withdrawn of the reserve.
+The customer unblinds the signature, checks its validity and persists the coin.
+The state machine of a coin can be seen in figure \ref{fig:coin:states}.
+
+\begin{figure}[htp]
+ \begin{center}
+ \includegraphics[scale=0.58]{coin.pdf}
+ \end{center}
+ \caption{State machine of a coin (source: \cite{pic:coin-state-machine})}
+ \label{fig:coin:states}
+\end{figure}
+
+\begin{figure}[htp]
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ \text{Customer} & & \text{Exchange}
+ \\ \text{knows:} & & \text{knows:}
+ \\ \text{reserve keys } w_s, W_p & & \text{reserve public key } W_p
+ \\ \text{denomination public key } D_p = e, N & & \text{denomination keys } d_s, D_p
+ \\ & &
+ \\\text{generate coin key pair:} & &
+ \\ c_s, C_p \leftarrow Ed25519.KeyGen() & &
+ \\ \text{blind:} & &
+ \\ r \leftarrow random \in \mathbb{Z}_N^* & &
+ \\ m' = \text{FDH}(N, C_p)*r^{e} \mod N & &
+ \\ \text{sign with reserve private key:} & &
+ \\ \rho_W = D_p, m' & &
+ \\ \sigma_W = \text{Ed25519.Sign}(w_s, \rho_W) & &
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{\rho = W_p, \sigma_W, \rho_W} &
+ \\ & & \text{verify if denomination public key}
+ \\ & & \text{is valid}
+ \\ & & \text{check } \text{Ed25519.Verify}(W_p, \rho_W, \sigma_W)
+ \\ & & \text{decrease balance if sufficient}
+ \\ & & \text{sign:}
+ \\ & & \sigma'_c = (m')^{d_s} \mod N
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{\sigma'_c} &
+ \\ \text{unblind:}& &
+ \\ \sigma_c = \sigma'_c*r^{-1} & &
+ \\ \text{verify signature:}& &
+ \\ \text{check } \sigma_c^{e} = \text{FDH}(N, C_p) & &
+ \\ & &
+ \\ \text{resulting coin: } c_s, C_p, \sigma_c, D_p & &
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Withdrawal process}
+ \label{fig:withdrawal-process}
+\end{figure}
+
+\subsubsection{Withdraw Loophole}
+\label{sec:withdraw-loophole}
+The withdraw loophole allows withdraw operations where owner of the resulting coins isn't the owner of the reserve that the coins where withdrawn from.
+It is used for tipping (described in section \ref{sec:tipping}) and can therefore be seen as a feature.
+
+By misusing the withdraw loophole, untaxed and untraceable payments can be performed.
+Figure \ref{fig:withdraw-loophole-exploit} explains how such a payment would work.
+Note that we omitted the parts leading up to the coin creation (contract, agreement of price, number of coins and their denominations).
+This is how it works on a high level:
+\begin{enumerate}
+ \item The malicious merchant generates and blinds coins, which are then transmitted to the customer
+ \item The customer authorizes the withdraw from his reserve by signing the blinded coins with the private key of his reserve, thus generating withdraw confirmations.
+ \item The withdraw confirmations are transmitted to the exchange, which generates the signatures and returns them to the malicious merchant.
+ \item The malicious merchant unblinds the signatures.
+ He is now in possession of the coin, thus the payment is completed.
+\end{enumerate}
+
+\begin{figure}[htp]
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l}
+ % preliminaries
+ \textbf{Customer} & & \textbf{malicious Merchant}
+ \\ \text{knows:} & & \text{knows:}
+ \\ \text{reserve keys } w_s, W_p
+ \\ \text{denomination public key } D_p = \langle e, N \rangle & & \text{denomination public key } D_p = \langle e, N \rangle
+ \\
+ % generate coin
+ \\ & & \text{generate coin key pair:}
+ \\ & & c_s, C_p \leftarrow \text{Ed25519.KeyGen}()
+ % blind
+ \\ & & \text{blind:}
+ \\ & & r \leftarrow random \in \mathbb{Z}_N^*
+ \\ & & m' := \text{FDH}(N, C_p)*r^{e} \mod N
+ % sing with reserve sk
+ \\ & \xleftarrow[\rule{2cm}{0pt}]{m'}
+ \\ \text{sign with reserve private key:}
+ \\ \rho_W := \langle D_p, m' \rangle
+ \\ \sigma_W := \text{Ed25519.Sign}(w_s, \rho_W)
+ \\ & \xrightarrow[\rule{2cm}{0pt}]{ \langle W_p, \sigma_W, \rho_W \rangle }
+ \\
+ \hline
+ \\
+ \textbf{malicious Merchant} & & \textbf{Exchange}
+ \\\text{knows:} & & \text{knows:}
+ \\& & \text{reserve public key } W_p
+ \\ \text{denomination public key } D_p = \langle e, N \rangle & & \text{denomination keys } d_s, D_p
+ \\
+ \\ & \xrightarrow[\rule{2cm}{0pt}]{ \langle W_p, \sigma_W, \rho_W \rangle }
+ \\ & & \langle D_p, m' \rangle := \rho_W
+ \\ & & \text{verify if } D_p \text{ is valid}
+ \\ & & \textbf{check } \text{Ed25519.Verify}(W_p, \rho_W, \sigma_W)
+ \\ & & \text{decrease balance if sufficient}
+ \\ & & \text{sign:}
+ \\ & & \sigma'_c := (m')^{d_s} \mod N
+ \\ & \xleftarrow[\rule{2cm}{0pt}]{\sigma'_c}
+ \\ \text{unblind:}
+ \\ \sigma_c := \sigma'_c*r^{-1}
+ \\ \text{verify signature:}
+ \\ \textbf{check if } \sigma_c = \text{FDH}(N, C_p)
+ \\
+ \\ \text{resulting coin: } \langle c_s, C_p, \sigma_c, D_p \rangle
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Untaxed payment using the withdraw loophole}
+ \label{fig:withdraw-loophole-exploit}
+\end{figure}
+
+\subsection{Payment Process}
+The payment process is divided in two steps described by the spend and deposit protocols.
+Details about the payment process can be found in multiple chapters in \cite{dold:the-gnu-taler-system}:
+Chapter 4.7.3 describes the spend and deposit protocols.
+Chapter 4.1.4 describes more general aspects as well as the contract header and deposit permission structure and details.
+\\On a high level, payment works like this:
+\begin{enumerate}
+ \item The customer submits a shopping cart (one or more items to buy) and commits his intent to buy them.
+ \item The merchant puts together the contract terms containing the necessary information for payment, signs it and sends both to the customer (spend protocol).
+ \item The customer generates a deposit permission and its signature for each coin used in the transaction (spend protocol).
+ \item The customer forwards the deposit permission(s) to the merchant (spend protocol).
+ If the deposit protocol is performed by the customer, this step can be skipped.
+ \item Either the customer or the merchant sends the deposit permission(s) to the exchange (deposit protocol).
+ \item The exchange processes the deposit permission and returns a deposition confirmation when successful (deposit protocol).
+ \item If the deposit protocol was performed by the customer, the deposit confirmation(s) have to be forwarded to the merchant.
+\end{enumerate}
+
+\subsubsection{Spend Protocol}
+The payment process begins when a customer submits a shopping cart (one or more items to buy) and commits his intent to buy them.
+The merchant has a key pair skM, pkM of which the customer knows the public key.
+Note that certain details contained in contract header or deposit permission like merchant \ac{KYC} information, deposit and refund deadlines and fees are left out.
+The deposit state machine can be seen in figure \ref{fig:deposit:states}.
+\begin{figure}[htp]
+ \begin{center}
+ \includegraphics[scale=0.7]{deposit.pdf}
+ \end{center}
+ \caption{State machine of a deposit (source: \cite{pic:deposit-state-machine})}
+ \label{fig:deposit:states}
+\end{figure}
+
+\begin{enumerate}
+ \item The merchant puts together the following information (without transmitting them) and requests payment:
+ \begin{itemize}
+ \item price $ v $
+ \item exchange $E_m$ (multiple possible)
+ \item account $A_m$ at the exchange $E_m$
+ \item info (free form details containing the full contract)
+ \end{itemize}
+ \item The customer generates an Ed25519 claim key pair $ p_s, P_p $ and submits the public key to the merchant.
+ This key can be used by the customer to prove that he didn't copy contract terms from another customer.
+ \item The merchant puts together the contract terms $ \rho $ and signs it with skM, resulting in the signature $ \sigma_P$.
+ \\The contract terms contains:
+ \begin{itemize}
+ \item $E_m$ (exchange)
+ \item $A_m$ (account at exchange $E_m$)
+ \item pkM
+ \item Hash($v$, info)
+ \item $ P_p $
+ \end{itemize}
+ $ \rho_P $ (contract terms), $ \sigma_P$ (contract terms signature), $ v $ (price) and info are submitted to the customer.
+ \item The customer does the following checks:
+ \begin{itemize}
+ \item Is the signature of the contract terms correct?
+ \item Is the public key referenced in the contract terms the same as the one generated in step 2?
+ \item Is the hash of price and info the same as the one in the contract terms?
+ \end{itemize}
+ If all checks are successful, the customer chooses one or more coins to be spent.
+ For each coin, a deposit permission $ \rho_{D} $ and its signature $ \sigma_{D} $ is generated.
+ The deposit permission contains the following information:
+ \begin{itemize}
+ \item Coin public key $ C_p $
+ \item Coin denomination public key pkD
+ \item Coin signature $ \sigma_C $
+ \item Value to be spent for this coin $f$ (greater than zero, up to the residual value of the coin)
+ \item Hash of the contract terms $ \rho_P $
+ \item Account of merchant $A_m$ (at exchange $E_m$)
+ \item Merchant public key pkM
+ \end{itemize}
+ The list of deposit permissions and their signatures is transferred to the merchant who then executed the deposit protocol.
+ Note that the customer is also able to deposit the coins (instead of the merchant), this is used in cases where the merchant doesn't have an internet connection, but the customer does.
+ This can be useful in cases where the merchant becomes unresponsive.
+ The customer can prove that he paid in time.
+ \item The merchant receives the deposit permissions and signatures and uses the deposit protocol to execute the payment.
+\end{enumerate}
+
+Before we continue with the deposit protocol, there are a few interesting details to point out (described in \cite{dold:the-gnu-taler-system} section 4.1.4):
+\begin{itemize}
+ \item The contract terms and the deposit permission are \ac{JSON} objects.
+ \item The contract terms only contains a cryptographic hash of the contract.
+ This improves privacy since the exchange doesn't have to know the full contract details, but still makes it possible to identify the contract in case of a dispute or some form of auditing.
+ \item At the point where the merchant completes step three (submits the contract terms and its signature) to the customer, the customer is able to finish the transaction using the deposit protocol without interaction of the merchant.
+ This means that the merchant at this step must be able to fulfill the contract if the customer completes the payment process.
+\end{itemize}
+
+\subsubsection{Deposit Protocol}
+\label{sec:deposit-protocol}
+As previously mentioned, both parties (customer and merchant) are able to run the deposit protocol.
+In the following description, the term merchant will be used, but could be replaced by customer.
+In cases where there are multiple deposit permissions (meaning that multiple coins are used to pay), the deposit protocol is run separately for each deposit permission.
+\begin{enumerate}
+ \item The merchant submits the deposit permission and its signature to the exchange.
+ \item The exchange runs these checks:
+ \begin{itemize}
+ \item Is the denomination public key referenced in the deposit permission valid (issued by the exchange, lifetime between start and deposit/refresh expiration, not revoked)?
+ \item Is the deposit permission signature $ \sigma_{D} $ a correct signature of the deposit permission $ \rho_{D} $ with the Ed25519 coin public key $ C_p $ referenced in the deposit permission?
+ \item Is there a processed deposit recorded in the exchanges databases based on coin public key and contract terms hash (replay/double spending)?
+ If not, continue with the next check since this is correct and expected behavior.
+ \\If there is, does the recorded deposit permission equal the one we're currently checking?
+ If this is the case, further checks can be skipped and the deposit confirmation signature can be returned to the customer.
+ If not, the process should be terminated because there's something wrong with the transaction.
+ \item Is the signature of the coin valid?
+ \item Is $ f $ (the value to be spent) smaller or equal the residual value of the coin (check for overspending attempt)?
+ \end{itemize}
+ If all checks are successful, the exchange saves the deposit record containing the deposit permission and its signature in a database, subtracts the spent value from the residual value of the coin and schedules the money transfer to the merchant's account $ A_m $ (grouping payments is done to reduce payment fees).
+ \\The exchange calculates a deposit confirmation signature $ \sigma_{DC} $ for the deposit permission with the exchange signing private key and returns them to the merchant.
+ \\This signature is also used to prove that a merchant was the first to receive payment from a certain coin.
+ Without this, an evil exchange could later deny confirming a payment and claim double spending.
+ With the signature, the merchant can prove that the payment was confirmed by the exchange, thus delegating the responsibility (and potential financial loss) for double spending detection to the exchange.
+ \item The merchant checks the signatures of the deposit confirmations with the exchange signing public key.
+\end{enumerate}
+
+It may happen that a payment gets stuck as partially complete, for example when a backup of a wallet is restored and one coin or more have already been spent (\cite{dold:the-gnu-taler-system} chapter 4.1.4).
+In this case, the customer can retry the payment with a different coin.
+If this isn't possible, the payment can be refunded (assuming refunds were enabled for this payment).
+Other scenarios were described in Dold's thesis, but dismissed due to privacy concerns.
+This means that disputes have to be settled aside from Taler when a customer isn't able to fully pay and refunds are disabled.
+
+\subsubsection{Web Payment Scenarios}
+The following methods are Taler-native methods for paying and payment validation.
+They are not identity-based, meaning that they do not require a login or similar techniques.
+Note that other methods could be implemented depending on the scenario.
+
+\begin{itemize}
+ \item \textbf{Resource-based web payment} (\cite{dold:the-gnu-taler-system} chapter 4.1.5):
+ All Taler contract terms contain a fulfillment URL.
+ This can either be a direct link to a digital product (like a movie, a song or a document), or to a confirmation page.
+ When a browser opens a fulfillment URL for a resource that hasn't yet been paid for, the merchant requests payment.
+ The wallet then generates and submits a claim key pair, thus claiming the contract, which then can be paid (if the user accepts the contract).
+ The browser can then retry to navigate to the fulfillment URL, this time submitting the contract order ID as parameter, which the merchant can check if it has been paid (and deliver the content if this is the case).
+ This is known as the extended fulfillment URL
+ \\The wallet stores fulfillment URLs and their associated contracts.
+ Upon receiving a payment request, the wallet searches the stored fulfillment URLs and if it found one, automatically forwards the user to the extended fulfillment URL containing the contract.
+ \item \textbf{Session-bound payments and sharing} (\cite{dold:the-gnu-taler-system} chapter 4.1.6):
+ So far, validating payment is done using the extended fulfillment URL.
+ The problem with this approach is that this URL can be shared, which is a problem for digital content.
+ To make this more difficult, the seller's website assigns the user a session ID (for example using a session cookie) and extends the extended fulfillment URL with a session signature parameter.
+ This parameter can be used by the merchant to check if the user paid for the resource or replayed the payment in this session.
+ \item \textbf{Embedded content} (\cite{dold:the-gnu-taler-system} chapter 4.1.7):
+ When paying to access multiple resources behind a paywall (instead of just one resource), the previously described methods do not work.
+ Dold's thesis suggest two methods:
+ \begin{enumerate}
+ \item A session cookie can be set by accessing the fulfillment URL.
+ When the browser requests a subresource, the merchant can verify the session cookie.
+ \item In this scenario, the fulfillment URL would show the resources behind the paywall.
+ Upon opening the extended fulfillment URL, the merchant's website would add an authentication token to the URLs of the subresources.
+ When accessing a subresource, the merchant can check the authentication tokens validity.
+ \end{enumerate}
+\end{itemize}
+
+\subsection{Refresh Protocol}
+\label{sec:refresh-protocol}
+This section provides a description of the refresh protocol.
+The technical details can be found in 4.7.4 \cite{dold:the-gnu-taler-system}.
+All relevant preliminaries needed to understand the technical details were already introduced in this work.
+
+\subsubsection{Introduction}
+A protocol to refresh coins is needed for many reasons.
+One important reason is giving change.
+Similar to the real world, there are often situations where one does not have the exact amount of coins.
+A change protocol therefore provides a lot of convenience for the users.
+Without such a mechanism it would be quite hard to use. \\
+Giving change is not trivial, since \ac{AML} and \ac{CFT} compliance still needs to hold.
+On the other side, the change still needs to provide privacy for the customer.
+Thus, the change must be unlinkable to the previous (or any) transaction.\\
+Complying with \ac{AML} and \ac{CFT} while preserving the customer's anonymity may sound like a contradiction at first.
+However, Taler has a clever way to solve this problem with the refresh protocol.
+
+The general idea is that the new coin can be derived from the private key of the old coin.
+
+\subsubsection{DH Lock}
+\ac{DHKE} was introduced in section \ref{sec:preliminaries-DHKE}.
+Taler uses \ac{ECDH} as a lock with two possible keys to unlock the shared key.
+To create such a lock, one creates two key pairs $C = cG$ and $T = tG$.
+To unlock now means calculating $k$.
+Both private keys, $c$ and $t$ are now able to calculate $k = tC = t(cG) = c(tG) = cT$ and thus can unlock the lock.
+This $k$ can then be used to derive the private key of the new coin and the corresponding blinding factor.
+
+\subsubsection{Customer Setup}
+
+The customer, which holds the old partially spend coin and knows \\$C_{old} = \text{Ed25519.GetPub}(c_{old})$.
+ A transfer key $T = \text{Ed25519.GetPub}(t)$ is then (randomly) generated by the customer.
+ \\The key pairs $T = \text{Ed25519.GetPub}(t)$ and $C_{old} = \text{Ed25519.GetPub}(c_{old})$ form the lock with two keys that was introduced before.
+ The customer then creates $x = c_{old}, T = tC_{old}$ and derives $c_{new}$, the private key of the new coin and $b_{new}$ the blinding factor of the new key.
+ As usual the customer calculates the coins public key $C_{new} = \text{Ed25519.GetPub}(c_{new})$, hashes the new coin with \gls{fdh} $f_{new} = \text{FDH}(C_{new})$ and blinds the hash $f'_{new} = f_{new}b_{new}^e$.
+ The $f'_{new}$ is then transmitted to the exchange.
+ \\Figure \ref{fig:refresh-derive} shows how the new coin is derived as explained above.
+
+ \begin{figure}[htp]
+ \centering
+ \fbox{%
+ \procedure[codesize=\small]{$\text{RefreshDerive}(s, \langle e,N\rangle, C_p)$}{%
+ t := \text{HKDF}(256, s, \text{"t"}) \\
+ T := \text{Curve25519.GetPub}(t) \\
+ x := \textrm{ECDH-EC}(t, C_p) \\
+ r := \text{SelectSeeded}(x,\mathbb{Z}^*_N)\\
+ c'_s := \text{HKDF}(256,x,"c")\\
+ C'_p := \text{Ed25519.GetPub}(c'_s)\\
+ \overline{m} := r^e * C'_p \mod N\\
+ \pcreturn \langle t, T, x, c_s', C_p', \overline{m}\rangle
+ }
+ }
+ \caption[RefreshDerive algorithm]{The RefreshDerive derives a new coin from a dirty coin with a seed. The DH-Lock is used to create the link used in the linking protocol}
+ \label{fig:refresh-derive}
+ \end{figure}
+
+ Now with the DH Lock the person who is in possession of the old key can always recalculate and thus spend the new coin (as long as it knows the public transfer key $T$).
+ However, there is one last thing: How does the exchange know that the old key is linked to the new one?
+ To comply with \ac{AML} and \ac{CFT}, the exchange wants to ensure that the person who created the new coin is also in possession of the old coin.
+ A link needs to be created in a way that nobody can link the old coin to the new coin, except the person in possession of the old coin.
+ The person in possession of the old coin needs to proof to the exchange that this link was created without revealing the link.
+ This problem is solved with the cut and choose protocol in the next section.
+
+ \begin{figure}[htp]
+ \centering
+ \includegraphics[height=0.5\textwidth]{taler_refresh_transfer_key.png}
+ \caption{Taler refresh protocol, transfer key setup (source: \cite{pic:refresh-prot})}
+ \label{fig:taler-refresh-transfer-key}
+ \end{figure}
+ \newpage
+ \subsubsection{Cut \& Choose}
+ Instead of doing the customer setup once, it is done $n$ times.
+ The customer generates $n$ different transfer keys $t_1, t_2 \dots t_n$.
+ For each key the whole calculations are done and all the blinded coins $f'_1, f'_2 \dots f'_n$ are sent to the exchange together with the old coins public key and signature. \\
+ The exchange responds with a randomly picked number from $1$ to $n$.
+ The customer has to reveal all the transfer keys, \textbf{except the one picked by the exchange.}
+ The exchange makes the same calculations with the revealed private transfer keys (without knowing the private key $c_{old}$).
+ The exchange can now verify whether the customer was honest or not.
+ A evil customer could create a new coin which is not linked to the old coin (without the DH lock).
+ Such attacks will be detected with a high probability in this protocol.
+ Since the $t_x$ picked by the exchange is not checked, an evil customer can win this with a probability of $1/n$.
+ Already with $n=3$ an attack is not in the customers interest due to economic reasons.
+ In 2 out of 3 cases the exchange would detect the attack and would keep the money and the customer would have lost it.
+ The probability can be adjusted with $n$.
+ With increasing size of $n$ the attack becomes even less attractive.
+ When the cut and choose protocol ended successfully, the value of the old coin is set to zero.
+
+ \begin{figure}[htp]
+ \centering
+ \includegraphics[height=0.5\textwidth]{taler_cut_and_choose.png}
+ \caption{Taler refresh protocol, cut and choose (source: \cite{pic:refresh-prot})}
+ \label{fig:taler-cut-and-choose}
+ \end{figure}
+
+ \subsection{Commit Phase}
+ \label{sec:commit-phase-rsa}
+ The refresh protocol is implemented in two phases.
+ The commit phases creates $k$ derives and commits to this values by calculating a hash over the derives.
+ On the exchange's side various checks are done to validate the request.
+ Detailed steps of the commit phase are shown in figure \ref{fig:refresh-part1}.
+
+
+ \begin{figure}
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{Customer} & & \text{Exchange}
+ \\ \text{knows:} & & \text{knows:}
+ \\ \text{denomination public key } D_{p(i)} & & \text{denomination keys } d_{s(i)}, D_{p(i)}
+ \\ \text{coin}_0 = \langle D_{p(0)}, c_s^{(0)}, C_p^{(0)}, \sigma_c^{(0)} \rangle & &
+ % refresh request
+ \\ \text{Select} \langle N_t, e_t\rangle := D_{p(t)} \in D_{p(i)}
+ \\ \textbf{for } i = 1, \dots, \kappa: % generate k derives
+ \\ s_i \rightarrow \{0,1\}^{256} % seed generation
+ \\ X_i := \text{RefreshDerive}(s_i, D_{p(t)}, C_p^{(0)})
+ \\ (t_i, T_i, x_i, c_s^{(i)}, C_p^{(i)}, \overline{m}_i) := X_i
+ \\ \textbf{endfor}
+ \\ h_T := H(T_1, \dots, T_k)
+ \\ h_{\overline{m}} := H(\overline{m}_1, \dots, \overline{m}_k)
+ \\ h_C := H(h_t, h_{\overline{m}})
+ \\ \rho_{RC} := \langle h_C, D_{p(t)}, D_{p(0)}, C_p^{(0)}, \sigma_C^{(0)} \rangle
+ \\ \sigma_{RC} := \text{Ed25519.Sign}(c_s^{(0), \rho_{RC}})
+ \\ \text{Persist refresh-request} \langle \rho_{RC}, \sigma_{RC} \rangle
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{\rho_{RC}, \sigma_{RC}} &
+ % Exchange checks refresh request
+ \\ & & (h_C, D_{p(t)}, D_{p(0)}, C_p^{(0)}, \sigma_C^{(0)} = \rho_{RC})
+ \\ & & \textbf{check} \text{Ed25519.Verify}(C_p^{(0)}, \sigma_{RC}, \rho_{RC})
+ \\ & & x \rightarrow \text{GetOldRefresh}(\rho_{RC})
+ \\ & & \textbf{Comment: }\text{GetOldRefresh} \\(\rho_{RC} \mapsto \{\bot,\gamma\})
+ \\ & & \pcif x = \bot
+ \\ & & v := D(D_{p(t)})
+ \\ & & \langle e_0, N_0 \rangle := D_{p(0)}
+ \\ & & \textbf{check } \text{IsOverspending}(C_p^{(0)}, D_ {p(0)}, v)
+ \\ & & \textbf{check } D_{p(t)} \in \{D_{p(i)}\}
+ \\ & & \textbf{check } \text{FDH}(N_0, C_p^{(0)}) \equiv_{N_0} (\sigma_0^{(0)})^{e_0}
+ \\ & & \text{MarkFractionalSpend}(C_p^{(0)}, v)
+ \\ & & \gamma \leftarrow \{1, \dots, \kappa\}
+ \\ & & \text{Persist refresh-record } \langle \rho_{RC},\gamma \rangle
+ \\ & & \pcelse
+ \\ & & \gamma := x
+ \\ & & \textbf{endif}
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{\gamma} &
+ \\
+ \\
+ \\ & \textit{Continued in figure \ref{fig:refresh-part2}} &
+ %\\ \pcintertext[dotted]{(Continued in Figure)}
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Refresh protocol (commit phase)}
+ \label{fig:refresh-part1}
+ \end{figure}
+
+ \subsection{Reveal Phase}
+ \label{sec:reveal-phase-rsa}
+ In the reveal phase the customer receives $\gamma$ and he reveals the all the seeds to the exchange, except for $s_\gamma$.
+ The exchange can then verify if the customer was honest with probability $1/k$.
+ On success the exchange will return the blinded signature of the new coin and the customer can then unblind and store the coin.
+ The reveal phase is described in figure \ref{fig:refresh-part2}
+
+ \begin{figure}
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{Customer} & & \text{Exchange}
+ \\ & \textit{Continuation of figure \ref{fig:refresh-part1}} &
+ \\
+ \\
+ % Check challenge and send challenge response (reveal not selected msgs)
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{\gamma} &
+ \\ \textbf{check } \text{IsConsistentChallenge}(\rho_{RC}, \gamma)
+ \\ \textbf{Comment: } \text{IsConsistentChallenge}\\(\rho_{RC}, \gamma) \mapsto \{ \bot,\top \}
+ \\
+ \\ \text{Persist refresh-challenge} \langle \rho_{RC}, \gamma \rangle
+ \\ S := \langle s_1, \dots, s_{\gamma-1}, s_{\gamma+1}, \dots,s_x \rangle % all seeds without the gamma seed
+ \\ \rho_L = \langle C_p^{(0)}, D_{p(t)}, T_{\gamma},\overline{m}_\gamma \rangle
+ \\ \rho_{RR} = \langle T_\gamma, \overline{m}_\gamma, S \rangle
+ \\ \sigma_{L} = \text{Ed25519.Sign}(c_s^{(0)}, \rho_{L})
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{\rho_{RR},\rho_L, \sigma_{L}} &
+ % check revealed msgs and sign coin
+ \\ & & \langle T'_\gamma, \overline{m}'_\gamma, S \rangle := \rho_{RR}
+ \\ & & \langle s_1,\dots,s_{\gamma-1},s_{\gamma+1},\dots,s_\kappa \rangle ) := S
+ \\ & & \textbf{check } \text{Ed25519.Verify}(C_p^{(0)}, \sigma_L, \rho_L)
+ \\ & & \pcfor i = 1,\dots, \gamma-1, \gamma+1,\dots, \kappa
+ \\ & & X_i := \text{RefreshDerive}(s_i, D_{p(t)}, C_p^{(0)})
+ \\ & & \langle t_i, T_i, x_i, c_s^{(i)}, C_p^{(i)}, \overline{m}_i \rangle := X_i
+ \\ & & \textbf{endfor}
+ \\ & & h_T' = H(T_1,\dots,T_{\gamma-1},T'_{\gamma},T_{\gamma+1},\dots,T_\kappa)
+ \\ & & h_{\overline{m}}' = H(\overline{m}_1,\dots,\overline{m}_{\gamma-1},\overline{m}'_{\gamma},\overline{m}_{\gamma+1},\dots,\overline{m}_\kappa)
+ \\ & & h_C' = H(h_T', h_{\overline{m}}')
+ \\ & & \textbf{check } h_C = h_C'
+ \\ & & \overline{\sigma}_C^{(\gamma)} := \overline{m}^{d_{s(t)}}
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{\overline{\sigma}_C^{(\gamma)}} &
+ % Check coin signature and persist coin
+ \\ \sigma_C^{(\gamma)} := r^{-1}\overline{\sigma}_C^{(\gamma)}
+ \\ \textbf{check } (\sigma_C^{(\gamma)})^{e_t} \equiv_{N_t} C_p^{(\gamma)}
+ \\ \text{Persist coin} \langle D_{p(t)}, c_s^{(\gamma)}, C_p^{(\gamma)}, \sigma_C^{(\gamma)} \rangle
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Refresh protocol (reveal phase)}
+ \label{fig:refresh-part2}
+ \end{figure}
+
+ \subsubsection{(Un)linkability}
+ \begin{figure}[htp]
+ \centering
+ \includegraphics[height=0.5\textwidth]{taler_refresh_link_threat.png}
+ \caption{Taler refresh protocol, linkability (source: \cite{pic:refresh-prot})}
+ \label{fig:taler-link-threat}
+ \end{figure}
+ The goal of the cut and choose protocol is to ensure with a high probability ($1/n$) that the customer honestly created the new coin.
+ It ensures that the old coin is linked to the new coin via the DH lock.
+
+ With that, the following attack scenario is prevented (with probability $1/n$):\\
+ An third party creates the new coin without the DH lock as described in section \ref{sec:blind-sign-schemes}.
+ The third party sends the blinded new coin to the customer (who possesses the old coin).
+ The customer then signs the new coin by the exchange and sends the blinded signature back to the third party.
+ The third party would then be in possession of a valid new coin, which is not linked to the old coin.
+ As mentioned, such an attack is detected with a high probability by the exchange with the cut and choose protocol described earlier.
+
+ We will now consider the following attack scenario:\\
+ Someone could give the private key of the old coin $c_{old}$ to another person.
+ This other person then can derive a new coin using the refresh protocol.
+ The original customer currently can not recreate the new coin with only the knowledge of the old coins private key $c_{old}$.
+ He would need to know the public key of the transfer key $T_x$ and also the blinded signature of the new coin $f'_{new}$.
+ For this reason the exchange exposes the public transfer key $T_x$ and the blinded new coin $f'_{new}$ for a given old coin $C_{old}$.
+ So anybody who knows the public key of the old coin could ask for the public transfer key and the blinded signature of the new coin.
+ Only a person in possession of the old coins private key $c_{old}$ can recreate the new coin's private key. \\
+This mechanism can not be abused for money laundering anymore, since the original customer could trick this third person and spend the coin faster.
+The linking protocol is described in figure \ref{fig:refresh-link}.
+
+\begin{figure}
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{Customer} & & \text{Exchange}
+ \\ \text{knows:} & & \text{knows:}
+ \\ \text{coin}_0 = \langle D_{p(0)}, c_s^{(0)}, C_p^{(0)}, \sigma_{C}^{(0)} \rangle
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{C_{p(0)}} &
+ \\ & & L := \text{LookupLink}(C_{p(0)})
+ \\ & & \textbf{Comment: } \text{LookupLink}(C_p) \mapsto \{\langle \rho_L^{(i)},
+ \\ & & \sigma_L^{(i)}, \overline{\sigma}_C^{(i)} \rangle\}
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{L} &
+ \\ \pcfor \langle \rho_{L}^{(i)}, \overline{\sigma}_L^{(i)}, \sigma_C^{(i)} \rangle \in L
+ \\ \langle \hat{C}_p^{(i)}, D_{p(t)}^{(i)}, T_\gamma^{(i)}, \overline{m}_\gamma^{(i)} \rangle := \rho_L^{(i)}
+ \\ \langle e_t^{(i)}, N_t^{(i)} \rangle := D_{p(t)}^{(i)}
+ \\ \textbf{check } \hat{C}_p^{(i)} \equiv C_p^{(0)}
+ \\ \textbf{check } \text{Ed25519.Verify}(C_p^{(0)}, \rho_{L}^{(i)}, \sigma_L^{(i)})
+ \\ x_i := \text{ECDH}(c_s^{(0)}, T_{\gamma}^{(i)})
+ \\ r_i := \text{SelectSeeded}(x_i,\mathbb{Z}^*_{N_t})
+ \\ c_s^{(i)} := \text{HKDF}(256,x_i,"c")
+ \\ C_p^{(i)} := \text{Ed25519.GetPub}(c_s^{(i)})
+ \\ \sigma_C^{(i)} := (r_i)^{-1} \cdot \overline{m}_\gamma^{(i)}
+ \\ \textbf{check } (\sigma_C^{(i)})^{e_t^{(i)}} \equiv_{N_t^{(i)}} C_p^{(i)}
+ \\ \text{(Re-)obtain coin} \langle D_{p(t)}^{(i)},c_s^{(i)}, C_p^{(i)}, \sigma_C^{(i)} \rangle
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Linking protocol}
+ \label{fig:refresh-link}
+\end{figure}
+
+
+\subsection{Tipping Protocol}
+\label{sec:tipping}
+Source for this protocol was section 4.1.10 from \cite{dold:the-gnu-taler-system}.\\
+Merchants can give customers a small tip by using the withdraw loophole (described in section \ref{sec:withdraw-loophole}).
+This can be for a variety of different reasons, for example for submitting a survey.
+The merchant needs to create a reserve with the exchange.
+The reserve keys is now used to sign blinded coins generated by the user.
+\begin{enumerate}
+ \item The Merchant triggers the Payment required response with the Taler-Tip header set
+ \item The taler tip header contains information like amount, exchange to use, deadline and more. (details section 4.1.10 \cite{dold:the-gnu-taler-system})
+ \item The customer creates planchets that sum up the amount and blinds the token with the denomination key of the specified exchange and sends the blinded planchets to the merchant.
+ \item The merchant creates withdrawal confirmations (by signing them with the reserver private key) for these planchets and responds with a list of signatures.
+ \item The customer then uses these signatures to create coins as in the withdrawal protocol
+\end{enumerate}
+The received coins are still anonymized and only spendable by the customer.
+
+
+\subsection{Refund Protocol}
+A merchant can undo a deposit on a coin by signing a refund permission.
+The protocol details can be found in section 4.7.5 of \cite{dold:the-gnu-taler-system}.
+Since a refund is mainly done by the merchant, to provide refunds a merchant need to support refunds.
+A refund can be either fully or partially.
+After a refund, the customer is able to spend the coin, but it should be refreshed first to prevent linking of transactions.
+The refund deadline is specified in the contract header, after the deadline the exchange makes a wire transfer with the money to the merchants bank.
+There is a refund fee, which is subtracted from the remaining coin value.
+This also prevents denial of service attacks, or at least makes them economically uninteresting.
+There exists automatic refunds when a payment only partially succeeds for many reasons.
+Refunds are also an important business case for many merchants who want to provide a convenient experience.
+A merchant can for example provide a refund when the customer is not happy with the product.
+Such a refund can be made by the merchant with a signature without the customers consent.
+Now should be clear what the purpose of a refund protocol is, the rest of this section will look at the refund protocol.
+
+
+In the protocol the customer requests a refund from the merchant.
+If the merchant accepts the request, it authorizes the exchange to apply the refund.
+\begin{enumerate}
+ \item The customer asks for a refund for payment $p$ with reason $m$
+ \item The merchant decides whether it accepts the refund or not according to the merchants business rules.
+ \item If accepted, the merchant signs the refund permission with the merchants Ed25519 key and sends it to exchange.
+ \item The exchange checks the signature and refunds the coin(s) and sends a signed confirmation to the merchant.
+ \item The merchant sends the signed confirmation from the exchange to the customer.
+\end{enumerate}
+
+\section{Trust and PKI in Taler}
+In this section Taler's \ac{PKI} is explained and how Taler handles trust.
+This section is included due to the reason that we have to create Schnorr denomination keys to add the Clause Blind Schnorr Signature scheme to Taler.
+Taler uses TLS, however it does not rely on TLS for authenticity or integrity. (More detailed in chapter 4.1.3 of \cite{dold:the-gnu-taler-system})
+
+\subsubsection{Auditor}
+In Taler the auditors serves as trust anchor, and they are identified by a single Ed25519 public key.
+Similar to the list of trusted root \ac{CA} that come with web browsers and operating systems, a wallet comes with a list of trusted auditor certificates.
+In the rest of this section, different parts of Taler and how they are integrated in Taler's \ac{PKI} are discussed.
+The section ends with a discussion about security risks of Taler's trust model.
+For details, refer to chapter 4.1.3 of \cite{dold:the-gnu-taler-system}.
+
+\begin{figure}[htp]
+ \includegraphics[height=0.5\textwidth]{taler-pki.png}
+ \centering
+ \caption{GNU Taler PKI entities (source: \cite{dold:the-gnu-taler-system})}
+ \label{fig:taler-pki}
+\end{figure}
+
+\subsubsection{Exchange}
+The exchange has to expose an API in order to enable customers (wallets), merchants and auditors to access keys and other information.
+An exchange has a long term master key (Ed25519 key) and a base URL.
+The URL and the long term \ac{MK} identifies an exchange.
+The \ac{MK} is only used as an offline signing key and should be stored on an air-gapped machine.
+Further, the exchange has online signing keys (Ed25519 key), which are signed by the exchanges \ac{MK}.
+This \ac{MK} is on his side signed by one or possibly more auditors master key(s).
+The exchange's (online) signing keys are used to sign API responses.
+The denomination keys of an exchange are also signed by the exchanges offline \ac{MK} and the auditors \ac{MK}.
+The bank accounts supported by the exchange for withdrawals and deposits are also signed by the exchanges offline \ac{MK}.
+
+API requests are made to the base URL appending the name of the endpoint (eg. <base-url>/keys)
+The endpoint <base-url>/keys is used to get the exchanges signing keys and other information.
+Similar to the \ac{CA} trust model, the client (customer or merchant) can validate the signature of the keys, with the list of trusted auditor certs.
+
+\subsubsection{Coins}
+As seen in the withdrawal protocol blind signatures are done with RSA public keys (section \ref{sec:blind-rsa-sign}).
+These keys are called denomination keys and they represent the coin value of the signed coins.
+The following information concerning the denomination keys are signed by the exchanges master key (citation from \cite{dold:the-gnu-taler-system} chapter 4.1.3):
+\begin{itemize}
+ \item The RSA public key
+ \item The start date, after which coins of this denomination can be withdrawn and deposited.
+ \item The withdraw expiration date, after which coins cannot be withdrawn anymore, must be after the start date.
+ \item The deposit expiration date, after which coins cannot be deposited anymore, must be after the withdraw expiration date.
+ \item The legal expiration date, after which the exchange can delete all records about operations with coins of this denominations, must be (typically quite a long time!) after the deposit expiration date.
+ \item The fees for a withdraw, deposit, refresh and refund operation with this coin, respectively.
+\end{itemize}
+
+As mentioned, the denomination keys are signed by the exchanges \ac{MK} and also by the auditor.
+
+\subsubsection{Merchant}
+The merchant has one Ed25519 public key.
+With that key the merchant authenticates to the exchange and signs responses to the customer.
+Depending on the jurisdiction, an exchange needs to comply to \ac{KYC} regulations.
+A merchant which accepts payments from all exchanges (audited by a trusted auditor) therefore needs to fulfill \ac{KYC} registration for all accepted exchange separately.
+This is needed to be legally compliant. \\
+Like the customer, also the merchant is configured with a set of trusted auditors and exchanges.
+A merchant only accepts payments with coins of denominations from a trusted exchange which is audited by a trusted auditor.
+
+For this reason Taler separates this service into an isolated service, similar to on-premise or external payment gateways, which are used by most e-commerce shops nowadays.
+
+\subsubsection{Customer}
+A customer has private keys of reserves that they own to authenticate with the exchange.
+The public key was communicated to the exchange with the wire transfer. (A bank however is not part of Taler's \ac{PKI}.)
+A customer is therefore not registered with an exchange.
+
+Further a customer possesses the private keys of his coins and stores them in a digital wallet.
+\subsubsection{Security Discussion}
+Taler's trust model is technically similar to the \ac{CA} trust model we know from TLS certificates.
+The trust anchor lies with the auditors, whose certificates are pre-configured by the merchant or customer respectively.
+However, trust is always somehow attackable.
+That does not mean that there is a security issue in the trust model.
+When the list of trusted auditor certs of a customer/merchant somehow can be manipulated, the trust model breaks for this entity. \\
+One attack scenario would be to attack customers/merchants with a supply-chain attack on the wallets or merchant backends' implementation.
+With software supply-chain attacks on the rise in 2020/21 (although the concept is not new) such an attack could have a big impact. \\
+Since auditor certs are coupled with the wallet (or merchant) implementation, a bank, country, central bank or auditor will most likely publish a wallet and a merchant implementation for the corresponding Taler ecosystem.
+%This would make it possible for the publisher to make changes on the Taler protocol for this specific implementation.
diff --git a/doc/cs/content/4_1_design.tex b/doc/cs/content/4_1_design.tex
new file mode 100644
index 000000000..b23e72050
--- /dev/null
+++ b/doc/cs/content/4_1_design.tex
@@ -0,0 +1,459 @@
+\chapter{Protocol Design}
+\label{chap:design}
+This chapter describes the necessary changes on the protocol level to implement a Blind Schnorr Signature Scheme to Taler.
+
+
+\section{Analysis of Current Protocols}
+The blind RSA signature scheme is only used for coin signatures.
+Note that we omitted protocols (or parts of them) where the coin signature is transmitted, but no other actions using it is performed.
+\\\\
+\label{abort-idempotency}
+An important property to mention here is \textit{\gls{abort-idempotency}}.
+\Gls{idempotence} in the context of computer science is a property to ensure that the state of a system will not change, no matter how many times the same request was made.
+A more in-depth explanation is given within the cited source \cite{yuchen:idempotence}.\\
+\textit{\gls{abort-idempotency}} goes a bit further.
+When the protocol is aborted at any stage, for example due to power cuts or network issues, the protocol still needs to ensure that the same response is sent for the same request.
+This is especially challenging when dealing with random values as we will see in the redesigned protocols in the following sections.
+For \gls{RSABS} it is inherently easier to provide \textit{\gls{abort-idempotency}} since signature creation only needs one round-trip and requires less random values.
+
+The following protocols currently use \gls{RSABS}:
+\begin{itemize}
+ \item \textbf{Withdraw Protocol:}
+ The customer uses the blind signature scheme to blind the coins before transmitting them to the exchange, which blindly signs it (standard RSA signature) and the returns the signatures.
+ After the customer receives the signatures, he unblinds and stores them together with the coins.
+ \\ Components:
+ \begin{itemize}
+ \item Customer
+ \item Exchange
+ \end{itemize}
+ \item \textbf{Deposit Protocol:}
+ During the Deposit, the exchange verifies the coin signature derived using the blind RSA signature scheme.
+ \\ Components:
+ \begin{itemize}
+ \item Exchange
+ \end{itemize}
+ \item \textbf{Refresh Protocol:}
+ The refresh protocol is used to derive a new coin from an old one which was partially spent.
+ Parts of the protocol are similar to the withdraw protocol, but it is more complex due to the added DH lock and cut-and-choose.
+ \\ Components:
+ \begin{itemize}
+ \item Customer
+ \item Exchange
+ \end{itemize}
+ \item \textbf{Tipping:}
+ Tipping is a variation of the withdraw protocol where the message containing the blinded planchets is transmitted to the merchant, who signs them using his reserve private, key and returns the signatures back to the customer.
+ Here, the details from the withdraw protocol apply.
+ \\ Components:
+ \begin{itemize}
+ \item Customer
+ \item Exchange
+ \end{itemize}
+ \item \textbf{Recoup Protocol:}
+ The recoup protocol distinguishes three different cases, which either use the refresh protocol or disclose either the withdraw transcript or refresh protocol transcript to the exchange.
+ \\ Components:
+ \begin{itemize}
+ \item Customer
+ \item Exchange
+ \end{itemize}
+\end{itemize}
+
+
+\section{Protocol Changes}
+The goal of the thesis is to add support for the Clause Blind Schnorr Signature scheme to Taler, besides the existing \gls{RSABS} implementation (see section \ref{sec:blind-rsa-sign}).
+For the design of the \gls{CSBS} the existing protocols with \gls{RSABS} were redesigned.
+
+The goal of the blind signature is to keep the exchange from knowing which coin a user withdraws and thus preventing the exchange linking a coin to a user.
+The biggest impact is on the withdrawal and refresh protocols, but all protocols that include some operation around denomination signatures are affected.
+
+During the thesis the protocols will be redesigned, implemented and the differences to the current version will be outlined.
+These results will be delivered to the Taler team.
+Feedback is very important when (re)designing protocols.
+For that reason the redesigned protocols were discussed and reviewed with Christian Grothoff multiple times.
+
+As signature scheme the Clause Blind Schnorr Signature Scheme described in section \ref{sec:clause-blind-schnorr-sig} was chosen for multiple reasons.
+First of all it is currently considered to be secure (see \cite{cryptoeprint:2019:877}).
+Schnorr Signatures on \gls{25519} are much shorter than RSA signatures.
+This should provide notable performance improvements in speed and storage, and therefore scales better.
+The paper describes a security analysis of the Blind Schnorr Signature scheme and introduces a modification (the "clause" part in the name) that is resistant to Wagner's algorithm (which solves ROS problem).
+
+\Gls{25519} \cite{bern:curve25519} will be used for the implementation because it is a widely accepted curve (see \cite{bernlange:safecurves}, \cite{rfc7748}) and is already used by Taler (Taler uses Ed25519 which is built upon \gls{25519}).
+
+
+\subsection{Withdraw Protocol}
+\label{sec:withdraw-protocol-schnorr}
+The modified protocol using the Clause Blind Schnorr Signature Scheme is described in figures \ref{fig:withdrawal-process-schnorr-1} and \ref{fig:withdrawal-process-schnorr-2}.
+
+The proposed change introduces an additional round trip.
+It must be prevented that the exchange has to track sessions or persist values during the first stage \ref{fig:withdrawal-process-schnorr-1}, while still ensuring \gls{abort-idempotency}.
+In order to ensure \textit{\gls{abort-idempotency}}, the exchange has to generate the same $R_0,R_1$ for the same withdrawal request, while $r_0,r_1$ still needs to be unpredictable for the customer.
+For this reason a withdrawal-nonce combined with a \gls{hkdf} comes into play.
+The redesigned protocol makes extensive use of \gls{hkdf}'s functionality as \ac{PRNG} and one-way function, thus random becomes \textit{unpredictable}.
+
+In the beginning of the protocol, the customer generates a coin key pair.
+Its private key is used to generate the withdraw-nonce $n_w$ and the blinding factors $\alpha_0, \alpha_1, \beta_0, \beta_1$.
+The exchange uses the withdraw nonce together with the reserve key and a long-term secret to generate $r_0, r_1$.
+The coin and denomination private keys can be used as long-term secrets due to the one-way property of the \gls{hkdf}.
+
+Another question evolved around which key to use for the derivation of $ r_0, r_1 $.
+Obvious options are the denomination key or the exchange's online signing key.
+The denomination key was chosen because it has the recopu protocol in place that would handle coin recovery in case of a key compromise and subsequent revocation.
+
+\begin{figure}[htp]
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{Customer} & & \text{Exchange}
+ \\ \text{knows:} & & \text{knows:}
+ \\ \text{reserve keys } w_s, W_p & & \text{reserve public key } W_p
+ \\ \text{denomination public key } D_p & & \text{denomination keys } d_s, D_p
+ \\ & &
+ \\\text{generate withdraw secret:}
+ \\ \omega := randombytes(32)
+ \\ \text{persist } \langle \omega, D_p \rangle
+ \\ n_w := \text{HKDF}(256, \omega, \text{"n"})
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{n_w, D_p} &
+ % generate R
+ \\ & & \text{verify if } D_p \text{ is valid}
+ \\ & & r_0 := \text{HKDF}(256,n_w || d_s, \text{"wr0"})
+ \\ & & r_1 := \text{HKDF}(256,n_w || d_s, \text{"wr1"})
+ \\ & & R_0 := r_0G
+ \\ & & R_1 := r_1G
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{R_0, R_1} &
+ \\ \text{derive coin key pair}:
+ \\ c_s := \text{HKDF}(256, \omega || R_0 || R_1,\text{"cs"})
+ \\ C_p := \text{Ed25519.GetPub}(c_s)
+ % blinding
+ \\ \text{blind:} & &
+ \\ b_s := \text{HKDF}(256, \omega || R_0 || R_1,\text{"b-seed"})
+ \\ \alpha_0 := \text{HKDF}(256, b_s, \text{"a0"})
+ \\ \alpha_1 := \text{HKDF}(256, b_s, \text{"a1"})
+ \\ \beta_0 := \text{HKDF}(256, b_s, \text{"b0"})
+ \\ \beta_1 := \text{HKDF}(256, b_s, \text{"b1"})
+ \\ R'_0 := R_0 + \alpha_0 G + \beta_0 D_p & &
+ \\ R'_1 := R_1 + \alpha_1 G + \beta_1 D_p & &
+ \\ c'_0 := H(R'_0, C_p) & &
+ \\ c'_1 := H(R'_1, C_p) & &
+ \\ c_0 := c'_0 + \beta_0 \mod p & &
+ \\ c_1 := c'_1 + \beta_1 \mod p & &
+ \\
+ \\ & \textit{Continued in figure \ref{fig:withdrawal-process-schnorr-2}} &
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Withdrawal process using Clause Blind Schnorr Signatures part 1}
+ \label{fig:withdrawal-process-schnorr-1}
+\end{figure}
+
+\begin{figure}[htp]
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{Customer} & & \text{Exchange}
+ \\ \text{knows:} & & \text{knows:}
+ \\ \text{reserve keys } w_s, W_p & & \text{reserve public key } W_p
+ \\ \text{denomination public key } D_p & & \text{denomination keys } d_s, D_p
+ \\
+ \\ & \textit{Continuation of figure \ref{fig:withdrawal-process-schnorr-1}} &
+ \\
+ % sign with reserve sk
+ \\ \text{sign with reserve private key:} & &
+ \\ \rho_W := \langle n_w, D_p, c_0, c_1 \rangle & &
+ \\ \sigma_W := \text{Ed25519.Sign}(w_s, \rho_W) & &
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{W_p, \sigma_W, \rho_W} &
+ \\ & & \langle n_w, D_p, c_0, c_1 \rangle := \rho_W
+ % checks done by the exchange
+ \\ & & \text{verify if } D_p \text{ is valid}
+ \\ & & \text{check } \text{Ed25519.Verify}(W_p, \rho_W, \sigma_W)
+ \\ & & b := \text{HKDF}(1,n_w || d_s, \text{"b"})
+ \\ & & s \leftarrow \text{GetWithdraw}(n_w, D_p)
+ \\ & & \textbf{if } s = \bot
+ \\ & & \textbf{check !} \text{NonceReuse} (n_w, D_p, \rho_W)
+ \\ & & r_b := \text{HKDF}(256,n_w || d_s, \text{"r}b\text{"})
+ % sign coin
+ \\ & & s := r_b + c_b d_s \mod p
+ % the following db operations are atomic
+ \\ & & \text{decrease balance if sufficient and}
+ \\ & & \text{persist NonceUse } \langle n_w, D_p, \rho_W \rangle
+ \\ & & \text{persist } \langle D_p, s \rangle
+ \\ & & \textbf{endif}
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{b,s} &
+ % verify signature
+ \\ \text{verify signature:}& &
+ \\ \textbf{check if } sG = R_b + c_b D_p & &
+ % unblind signature
+ \\ \text{unblind:}& &
+ \\ s' := s + \alpha_b \mod p & &
+ \\ \text{verify signature:}& &
+ \\ \textbf{check if } s'G = R'_b + c'_b D_p & &
+ \\ \sigma_C := \langle R'_b, s' \rangle & &
+ \\ \text{resulting coin: } c_s, C_p, \sigma_C, D_p & &
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Withdrawal process using Clause Blind Schnorr Signatures part 2}
+ \label{fig:withdrawal-process-schnorr-2}
+\end{figure}
+
+
+\subsection{Deposit Protocol}
+The deposit protocol remains unchanged, except for the verification of the coin signature.
+To verify the signature, the exchange has to check if the following equation holds:
+\begin{align*}
+ s'G & = R' + c' D_p
+ \\ &= R' + H(R', C_p) D_p
+\end{align*}
+$ s', R' $ together form the signature, $ D_p $ is the denomination public key and $ C_p $ is the coin public key.
+
+Further details regarding the verification process can be found in section \ref{sec:blind-schnorr-sig}.
+
+
+\subsection{Refresh Protocol}
+The refresh protocol blindly signs the new derived coins.
+The replacement of \gls{RSABS} with the Clause Blind Schnorr Signature Scheme (see \ref{sec:clause-blind-schnorr-sig}) makes the refresh protocol a bit more complex.
+
+\subsubsection{RefreshDerive Schnorr}
+The RefreshDerive protocol is described in figure \ref{fig:refresh-derive-schnorr}.
+For this protocol, the main change is that more values need to be derived somehow.
+These blinding factors are also derived from $x$.
+Then the challenges $\overline{c_0}$ and $\overline{c_1}$ are generated as in the Clause Blind Schnorr Signature Scheme.
+
+\begin{figure}[htp]
+ \centering
+ \fbox{%
+ \procedure[codesize=\small]{$\text{RefreshDerive}(t, D_{p(t)}, C_p, R_0, R_1)$}{%
+ T := \text{Curve25519.GetPub}(t) \\
+ x := \textrm{ECDH-EC}(t, C_p) \\
+ c'_s := \text{HKDF}(256, x, \text{"c"}) \\
+ C_p' := \text{Ed25519.GetPub}(c'_s) \\
+ b_s := \text{HKDF}(256, x || R_0 || R_1,\text{"b-seed"}) \\
+ \alpha_0 := \text{HKDF}(256, b_s, \text{"a0"}) \\
+ \alpha_1 := \text{HKDF}(256, b_s, \text{"a1"}) \\
+ \beta_0 := \text{HKDF}(256, b_s, \text{"b0"}) \\
+ \beta_1 := \text{HKDF}(256, b_s, \text{"b1"}) \\
+ R'_0 = R_0 + \alpha_0 G + \beta_0 D_p \\
+ R'_1 = R_1 + \alpha_1 G + \beta_1 D_p \\
+ c'_0 = H(R'_0, C_p') \\
+ c'_1 = H(R'_1, C_p') \\
+ \overline{c_0} = c'_0 + \beta_0 \mod p \\
+ \overline{c_1} = c'_1 + \beta_1 \mod p \\
+ \pcreturn \langle T, c'_s, C_p', \overline{c_0}, \overline{c_1} \rangle
+ }
+ }
+ \caption[RefreshDerive algorithm]{The RefreshDerive replaced with Schnorr blind signature details. As before the uses the seed $s$ on the dirty coin for generating the new coin.
+ The new coin needs to be signed later on with the denomination key.}
+ \label{fig:refresh-derive-schnorr}
+\end{figure}
+
+\subsubsection{Refresh Protocol}
+\label{sec:refresh-protocol}
+In the commit phase (see figure \ref{fig:refresh-commit-part1}) there needs to be requested an $R_0$ and $R_1$ before deriving the new coins.
+There now needs to be calculated two different commit hashes, one for $\overline{c_0}$ and one for $\overline{c_1}$.
+The exchange needs to additionally generate a random $b \leftarrow \{0,1\}$ to choose a $\overline{c_b}$.
+The reveal phase (see figure \ref{fig:refresh-commit-part2}) now is continued only with the chosen $\overline{c_b}$.
+In the reveal phase, the RSA signing and unblinding is exchanged with Schnorr's blind signature counterparts.
+
+\begin{figure}[htp]
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{Customer} & & \text{Exchange}
+ \\ \text{knows:} & & \text{knows:}
+ \\ \text{denomination public key } D_p & & \text{old denomination keys } d_{s(0)} D_{p(0)}
+ \\ \text{coin}_0 = \langle D_{p(0)}, c_s^{(0)}, C_p^{(0)}, \sigma_c^{(0)} \rangle && \text{new denomination keys } d_s, D_P
+ % request r
+ \\ & &
+ \\ n_r := randombytes(32)
+ \\ \text{persist } \langle n_r, D_p \rangle
+ % sign with reserve sk
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{n_r, D_p} &
+ % generate R
+ \\ & & \text{verify if } D_p \text{ is valid}
+ \\ & & r_0 := \text{HKDF}(256, n_r || d_s, \text{"mr0"})
+ \\ & & r_1 := \text{HKDF}(256, n_r || d_s, \text{"mr1"})
+ \\ & & R_0 := r_0G
+ \\ & & R_1 := r_1G
+ \\ & \xleftarrow[\rule{2cm}{0pt}]{R_0, R_1} &
+ % refresh request
+ \\ \textbf{for } i = 1, \dots, \kappa: % generate k derives
+ %\\ s_i \leftarrow \{0,1\}^{256} % seed generation
+ \\ t_i := \text{HKDF}(256, c_s^{(0)}, n_r || R_0 || R_1,\text{"t} i \text{"} ) % seed generation
+ \\ X_i := \text{RefreshDerive}(t_i, D_p, C_p^{(0)}, R_0, R_1)
+ \\ (T_i, c_s^{(i)}, C_p^{(i)}, \overline{c_0}, \overline{c_1}):= X_i
+ \\ \textbf{endfor}
+ \\ h_T := H(T_1, \dots, T_k)
+ \\ h_{\overline{c_0}} := H(\overline{c_{0_1}},\dots, \overline{c}_{0_k})
+ \\ h_{\overline{c_1}} := H(\overline{c_{1_1}},\dots, \overline{c}_{1_k})
+ \\ h_{\overline{c}} := H(h_{\overline{c_0}}, h_{\overline{c_1}}, n_r)
+ \\ h_C := H(h_T, h_{\overline{c}})
+ \\ \rho_{RC} := \langle h_C, D_p, \text{ } D_{p(0)}, C_p^{(0)}, \sigma_C^{(0)} \rangle
+ \\ \sigma_{RC} := \text{Ed25519.Sign}(c_s^{(0)}, \rho_{RC})
+ \\ \text{Persist refresh-request}
+ \\ \langle n_r, R_0, R_1, \rho_{RC}, \sigma_{RC} \rangle
+ \\
+ \\ & \textit{Continued in figure \ref{fig:refresh-commit-part2}} &
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Refresh protocol (commit phase part 1) using Clause Blind Schnorr Signatures}
+ \label{fig:refresh-commit-part1}
+\end{figure}
+
+
+\begin{figure}[htp]
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ \text{Customer} & & \text{Exchange}
+ \\ & \textit{Continuation of}
+ \\ & \textit{figure \ref{fig:refresh-commit-part1}}
+ \\
+ \\ & \xrightarrow[\rule{2cm}{0pt}]{\rho_{RC}, \sigma_{RC}, n_r} &
+ % Exchange checks refresh request
+ \\ & & \langle h_C, D_p, D_{p(0)}, C_p^{(0)}, \sigma_C^{(0)} \rangle := \rho_{RC}
+ \\ & & \textbf{check} \text{ Ed25519.Verify}(C_p^{(0)}, \sigma_{RC}, \rho_{RC})
+ \\
+ \\ & & \gamma \leftarrow \text{GetOldRefresh}(\rho_{RC})
+ \\ & & \textbf{Comment: }\text{GetOldRefresh}(\rho_{RC} \mapsto
+ \\ & & \{\bot, \gamma \})
+ \\ & & \pcif \gamma = \bot
+ \\ & & v := \text{Denomination}(D_p)
+ \\ & & \textbf{check } \text{IsOverspending}(C_p^{(0)}, D_ {p(0)}, v)
+ \\ & & \text{verify if } D_p \text{ is valid}
+ \\ & & \textbf{check !} \text{NonceReuse} (n_r, D_p, \rho_{RC})
+ \\ & & \textbf{check } \text{Schnorr.Verify}(D_{p(0)}, C_p^{(0)}, \sigma_C^{(0)})
+ \\ & & \text{MarkFractionalSpend}(C_p^{(0)}, v)
+ \\ & & \gamma \leftarrow \{1, \dots, \kappa\}
+ \\ & & \text{persist NonceUse } \langle n_r, D_p, \rho_{RC} \rangle
+ \\ & & \text{persist refresh-record } \langle \rho_{RC},\gamma \rangle
+ \\ & \xleftarrow[\rule{2cm}{0pt}]{\gamma} &
+ % Check challenge and send challenge response (reveal not selected msgs)
+ \\ \textbf{check } \text{IsConsistentChallenge}(\rho_{RC}, \gamma)
+ \\ \textbf{Comment: } \text{IsConsistentChallenge}\\(\rho_{RC}, \gamma) \mapsto \{ \bot,\top \}
+ \\
+ \\ \text{Persist refresh-challenge} \langle \rho_{RC}, \gamma \rangle
+ \\ S := \langle t_1, \dots, t_{\gamma-1}, t_{\gamma+1}, \dots,t_\kappa \rangle % all seeds without the gamma seed
+ \\ \rho_L := \langle C_p^{(0)}, D_p, T_{\gamma}, \overline{c_0}_\gamma, \overline{c_1}_\gamma \rangle
+ \\ \rho_{RR} := \langle \rho_L, S \rangle
+ \\ \sigma_{L} := \text{Ed25519.Sign}(c_s^{(0)}, \rho_{L})
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{\rho_{RR},\rho_L, \sigma_{L}} &
+ \\
+ \\ & \textit{Continued in} &
+ \\ & \textit{figure \ref{fig:refresh-reveal-part1}} &
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Refresh protocol (commit phase part 2) using Clause Blind Schnorr Signatures}
+ \label{fig:refresh-commit-part2}
+\end{figure}
+
+\begin{figure}[htp]
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{Customer} & & \text{Exchange}
+ \\ & \textit{Continuation of}
+ \\ & \textit{figure \ref{fig:refresh-commit-part2}}
+ \\
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{\rho_{RR},\rho_L, \sigma_{L}} &
+ % check revealed msgs and sign coin
+ \\ & & \langle C_p^{(0)}, D_p, T_{\gamma}, \overline{c_0}_\gamma, \overline{c_1}_\gamma \rangle := \rho_L
+ \\ & & \langle T'_\gamma, \overline{c_0}_\gamma, \overline{c_1}_\gamma, S \rangle := \rho_{RR}
+ \\ & & \langle t_1,\dots,t_{\gamma-1},t_{\gamma+1},\dots,t_\kappa \rangle := S
+ \\ & & \textbf{check } \text{Ed25519.Verify}(C_p^{(0)}, \sigma_L, \rho_L)
+ \\ & & b := \text{HKDF}(1, n_r || d_{s(i)}, \text{"b"})
+ \\ & & \textbf{for } i = 1,\dots, \gamma-1, \gamma+1,\dots, \kappa
+ \\ & & X_i := \text{RefreshDerive}(t_i, D_p, C_p^{(0)} \\ &&, R_0, R_1)
+ \\ & & \langle T_i, c_s^{(i)}, C_p^{(i)}, \overline{c_1}_i, \overline{c_2}_i \rangle := X_i
+ \\ & & \textbf{endfor}
+ \\ & & h_T' = H(T_1,\dots,T_{\gamma-1},T'_{\gamma},T_{\gamma+1},\dots,T_\kappa)
+ \\ & & h_{\overline{c_0}}' := H(\overline{c_{0_1}},\dots, \overline{c}_{0_k})
+ \\ & & h_{\overline{c_1}}' := H(\overline{c_{1_1}},\dots, \overline{c}_{1_k})
+ \\ & & h_{\overline{c}}' := H(h_{\overline{c_0}}, h_{\overline{c_1}}, n_r)
+ \\ & & h_C' = H(h_T', h_{\overline{c}}')
+ \\ & & \textbf{check } h_C = h_C'
+ \\ & & r_b := \text{HKDF}(256, n_r || d_s, \text{"mr}b\text{"})
+ \\ & & \overline{s}_{C_p}^{(\gamma)} = r_b + \overline{c_{b_\gamma}} d_s \mod p
+ \\ & & \text{persist } \langle \rho_L, \sigma_L, S \rangle
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{b, \overline{s}_C^{(\gamma)}} &
+ % Check coin signature and persist coin
+ % unblind signature
+ \\ \text{unblind:}& &
+ \\ s_C'^{(\gamma)} := \overline{s}_C^{(\gamma)} + \alpha_b \mod p & &
+ \\ \text{verify signature:}& &
+ \\ \textbf{check if } \overline{s'}_C^{(\gamma)}G \equiv R'_b + \overline{c'_0}_\gamma D_p & &
+ \\ \sigma_C^{(\gamma)} := \langle s_{C}'^{(\gamma)},R_b' \rangle
+ \\ \text{Persist coin} \langle D_p, c_s^{(\gamma)}, C_p^{(\gamma)}, \sigma_C^{(\gamma)} \rangle
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Refresh protocol (reveal phase) using Clause Blind Schnorr Signatures}
+ \label{fig:refresh-reveal-part1}
+\end{figure}
+\newpage
+\subsubsection{Linking Protocol}
+\label{sec:refresh-link}
+The beginning of the linking protocol (see figure \ref{fig:refresh-link}) is the same as in the current protocol.
+After the customer received the answer $L$ the only difference is in obtaining the coin.
+To re-obtain the derived coin, the same calculations as in \ref{fig:refresh-derive-schnorr} are made.
+\begin{figure}[htp]
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{Customer} & & \text{Exchange}
+ \\ \text{knows:} & & \text{knows:}
+ \\ \text{coin}_0 = \langle D_{p(0)}, c_s^{(0)}, C_p^{(0)}, \sigma_{C}^{(0)} \rangle
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{C_{p(0)}} &
+ \\ & & L := \text{LookupLink}(C_{p(0)})
+ \\ & & \textbf{Comment: } \text{LookupLink}(C_p^{(0)}) \mapsto
+ \\ & & \{\langle \rho_L^{(i)}, \sigma_L^{(i)}, \overline{\sigma}_C^{(i)}, b \rangle\}
+ %\\ & & \{\langle C_{p(0)}, D_{p(t)},\overline{\sigma}_C^{(i)}, b^{(i)}, R_b^{(i)}\rangle\}
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{L} &
+ \\ \textbf{for } \langle \rho_L^{(i)}, \overline{\sigma}_L^{(i)}, \overline{\sigma}_C^{(i)}, b \rangle\ \in L
+
+ %\\ & & \langle C_p^{(0)}, D_{p(t)}, T_{\gamma}, \overline{c_0}_\gamma, \overline{c_1}_\gamma, n_r \rangle := \rho_L
+ \\ \langle \hat{C}_p^{(i)}, D_p^{(i)}, T_\gamma^{(i)}, \overline{c_0}_\gamma^{(i)}, \overline{c_1}_\gamma^{(i)}, n_r \rangle := \rho_L^{(i)}
+ \\ \langle \overline{s}_C^{(i)}, R_b^{(i)} \rangle := \overline{\sigma}_C^{(i)}
+ \\ \textbf{check } \hat{C}_p^{(i)} \equiv C_p^{(0)}
+ \\ \textbf{check } \text{Ed25519.Verify}(C_p^{(0)}, \rho_{L}^{(i)}, \sigma_L^{(i)})
+ \\ \langle \overline{s}_C^{(i)}, R_b^{(i)} \rangle := \sigma_C^{(i)}
+ \\ x_i := \text{ECDH}(c_s^{(0)}, T_{\gamma}^{(i)})
+ \\ c_s^{(i)} := \text{HKDF}(256, x, \text{"c"})
+ \\ C_p^{(i)} := \text{Ed25519.GetPub}(c_s^{(i)})
+ \\ b_s^{(i)} := \text{HKDF}(256, x_i || R_0^{(i)} || R_1^{(i)},\text{"b-seed"})
+ \\ \alpha_b := \text{HKDF}(256, b_s^{(i)}, \text{"a}b\text{"})
+ \\ \beta_b := \text{HKDF}(256, b_s^{(i)}, \text{"b}b\text{"})
+ \\ {R'}_b^{(i)} = R_b^{(i)} + \alpha_b G + \beta_b D_p^{(i)}
+ \\ c'_b = H(R'_b, C_p^{(i)})
+ \\ c_b = c'_b + \beta_b \mod p
+ \\ s_C'^{(i)} := \overline{s}_C^{(i)} + \alpha_b \mod p
+ \\ \sigma_C^{(i)} := \langle s_C'^{(i)}, R_b' \rangle
+ \\ \textbf{check } s'{_C^{(i)}}G \equiv {R'}_b^{(i)} + c'_b D_p^{(i)}
+ \\ \text{(Re-)obtain coin} \langle D_p^{(i)},c_s^{(i)}, C_p^{(i)}, \sigma_C^{(i)} \rangle
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Linking protocol using Clause Blind Schnorr Signatures}
+ \label{fig:refresh-link}
+\end{figure}
+
+
+\subsection{Tipping}
+Tipping remains unchanged, except for the content of the message $ \rho_W = D_p, c_0, c_1 $ signed by the merchant using its reserve private key.
+
+\subsection{Recoup Protocol}
+The recoup protocol distinguishes three different cases, which all depend on the state of a coin whose denomination key has been revoked.
+The following listing outlines the necessary changes on the protocol, please refer to Dold's documentation section 2.2.1 \cite{dold:the-gnu-taler-system} for details regarding the different cases.
+\begin{itemize}
+ \item \textbf{The revoked coin has never been seen by the exchange}:
+ \\The withdraw transcript (and verification) must be adjusted in order for the exchange to be able to retrace the blinding.
+ \item \textbf{The coin has been partially spent}:
+ \\In this case the refresh protocol will be invoked on the coin.
+ The necessary changes are outlined in \ref{sec:refresh-protocol}.
+ \item \textbf{The revoked coin has never been seen by the exchange and resulted from a refresh operation}:
+ \\The refresh protocol transcript and its blinding factors must be adjusted to consider the changes in the blind signature scheme.
+\end{itemize}
diff --git a/doc/cs/content/4_2_specification.tex b/doc/cs/content/4_2_specification.tex
new file mode 100644
index 000000000..bfbe5edc7
--- /dev/null
+++ b/doc/cs/content/4_2_specification.tex
@@ -0,0 +1,790 @@
+\chapter{Protocol Specification}
+\label{chap:spec}
+The proposed Taler protocols using the Clause Blind Schnorr Signature Scheme will be implemented as an additional option besides the existing \gls{RSABS} variant of the protocol as suggested by Christian Grothoff.
+A Taler Exchange operator should be able to configure whether he wants to use \gls{RSABS} or \gls{CSBS}.
+\\This variant allows to choose the signature scheme globally or per denomination.
+Furthermore, it allows a change of signature scheme in a non-breaking way by revoking (or letting expire) a denomination and offering new denominations with the other scheme.
+\\
+The following key points are specified in this chapter:
+\begin{itemize}
+ \item Architecture of the different components
+ \item Explain and specify needed changes
+ \item Data strucutures
+ \item Public \acp{API}
+ \item Persistence
+ \item Used libraries
+\end{itemize}
+
+\section{Architecture}
+Before specifying the implementation of the different protocols, a deeper understanding of the technical architecture of Talers components is needed.
+this section introduces the architecture of the exchange and wallet components and explains where the needed changes need to be implemented on a high-level.
+
+\subsection{Exchange}
+An introduction to the exchange can be found in section \ref{sec:exchange}.
+An exchange operator needs to run and maintain some additional services besides Taler's exchange.
+Although this is not directly relevant for the implementation, it helps to better understand the environment in which the exchange runs.
+The perspective of an exchange operator can be seen in figure \ref{fig:taler:exchange-operator-architecture}.
+
+\begin{figure}[h!]
+ \begin{adjustbox}{max totalsize={.9\textwidth}{.7\textheight},center}
+ \begin{tikzpicture}
+ \tikzstyle{def} = [node distance= 5em and 6.5em, inner sep=1em, outer sep=.3em];
+ \node (origin) at (0,0) {};
+ \node (exchange) [def,above=of origin,draw]{Exchange};
+ \node (nexus) [def, draw, below right=of exchange] {Nexus};
+ \node (corebanking) [def, draw, below left=of nexus] {Core Banking};
+ \node (nginx) [def, draw, above=of exchange]{Nginx};
+ \node (postgres) [def, draw, below left=of exchange]{Postgres};
+ \node (postgres-nexus) [def, draw, below right=of nexus]{Postgres};
+
+ \tikzstyle{C} = [color=black, line width=1pt]
+
+ \draw [<-, C] (exchange) -- (nginx) node [midway, above, sloped] (TextNode) {REST API};
+ \draw [<-, C] (postgres) -- (exchange) node [midway, above, sloped] (TextNode) {SQL};
+ \draw [<-, C] (postgres-nexus) -- (nexus) node [midway, above, sloped] (TextNode) {SQL};
+ \draw [<-, C] (nexus) -- (exchange) node [midway, above, sloped] (TextNode) {Internal REST API};
+ \draw [<-, C] (corebanking) -- (nexus) node [midway, above, sloped] (TextNode) {EBICS/FinTS};
+
+ \end{tikzpicture}
+ \end{adjustbox}
+ \caption{Taler exchange operator architecture (source: \cite{taler-presentation})}
+ \label{fig:taler:exchange-operator-architecture}
+\end{figure}
+
+The software architecture of the exchange can be seen in figure \ref{fig:taler:exchange-architecture}.
+The API runs under the httpd service, where the API endpoints need to be adjusted/added to incorporate the changes of this thesis.
+The httpd server has no access to the private keys of the denomination and online signing keys.
+Only the corresponding security module can perform operations requiring the private key.
+Further the keys are also managed by these security modules.
+To support \gls{CSBS} a new security module, which performs signature operations, is added.
+To persist the new data structures, the postgres helpers need to be adjusted to serialize/deserialize the new \gls{CSBS} data structures.
+More details on what changes are needed in these places is discussed in the following sections.
+
+\begin{figure}[h!]
+ \begin{center}
+ \begin{tikzpicture}
+ \tikzstyle{def} = [node distance=2em and 2.5em, inner sep=1em, outer sep=.3em];
+ \node (origin) at (0,0) {};
+ \node [blue] (httpd) [def,above=of origin,draw]{httpd};
+ \node (secmod-rsa) [def, draw, right=of httpd] {secmod-rsa};
+ \node (secmod-eddsa) [def, draw, left=of httpd] {secmod-eddsa};
+ \node [blue](postgres) [def, draw, below=of httpd]{Postgres};
+ \node [mGreen] (secmod-cs) [def, draw, left=of postgres]{secmod-cs};
+ \node (aggregator) [def, draw, right=of postgres]{aggregator};
+ \node (transfer) [def, draw, below left=of postgres]{transfer};
+ \node (wirewatch) [def, draw, below right=of postgres]{wirewatch};
+ \node (nexus) [def, draw, below=of postgres]{Nexus};
+
+ \tikzstyle{C} = [color=black, line width=1pt]
+
+ \draw [<->, C] (httpd) -- (postgres) node [midway, above, sloped] (TextNode) {};
+ \draw [<->, C] (httpd) -- (secmod-rsa) node [midway, above, sloped] (TextNode) {};
+ \draw [<->, C] (httpd) -- (secmod-eddsa) node [midway, above, sloped] (TextNode) {};
+ \draw [<->, C] (httpd) -- (secmod-cs) node [midway, above, sloped] (TextNode) {};
+ \draw [<->, C] (aggregator) -- (postgres) node [midway, above, sloped] (TextNode) {};
+ \draw [<->, C] (wirewatch) -- (postgres) node [midway, above, sloped] (TextNode) {};
+ \draw [<->, C] (transfer) -- (postgres) node [midway, above, sloped] (TextNode) {};
+ \draw [->, C] (transfer) -- (nexus) node [midway, above, sloped] (TextNode) {};
+ \draw [<-, C] (wirewatch) -- (nexus) node [midway, above, sloped] (TextNode) {};
+ \end{tikzpicture}
+ \end{center}
+ \caption{Taler exchange architecture (source: \cite{taler-presentation})}
+ \label{fig:taler:exchange-architecture}
+\end{figure}
+
+\subsection{Wallet}
+The architecture of the wallet implementation (as seen in figure \ref{fig:taler:wallet-architecture}) is quite straightforward.
+To add support for \gls{CSBS} in the wallet, the cryptographic routines need to be reimplemented in Typescript.
+Taler uses tweetnacl \cite{bern:tweetnacl} which provides functionality for the group operations.
+There are existing \gls{hkdf} and \gls{fdh} implementations, that can be reused.\\
+Furthermore, the Taler protocols need to be adjusted to support \gls{CSBS} in the wallet-core.
+
+\begin{figure}[h!]
+ \begin{center}
+ \begin{tikzpicture}
+ \tikzstyle{def} = [node distance= 5em and 4.5em, inner sep=1em, outer sep=.3em];
+ \node (origin) at (0,0) {};
+ \node (gui) [def,above=of origin,draw]{wallet-gui};
+ \node [blue](core) [def,below=of gui,draw]{wallet-core};
+ \node (sync) [def, draw, below left=of core] {Sync};
+ \node (taler) [def, draw, below right=of core] {Taler};
+ \node (anastasis) [def, draw, below=of core] {Anastasis};
+
+ \tikzstyle{C} = [color=black, line width=1pt]
+ \draw [<->, C] (gui) -- (core) node [midway, above, sloped] (TextNode) {};
+ \draw [<->, C] (core) -- (sync) node [midway, above, sloped] (TextNode) {Backup};
+ \draw [<->, C] (core) -- (taler) node [midway, above, sloped] (TextNode) {Payment};
+ \draw [<->, C] (core) -- (anastasis) node [midway, above, sloped] (TextNode) {Key Escrow};
+ \end{tikzpicture}
+ \end{center}
+ \caption{Taler wallet architecture (source: \cite{taler-presentation})}
+ \label{fig:taler:wallet-architecture}
+\end{figure}
+
+\section{Persistence}
+The Clause Blind Schnorr Signature scheme is quite different to \gls{RSABS}.
+Despite the differences, the database model does not need to be changed.
+The only change needed an additional type field, specifying whether RSA or CS is used as signature algorithm.
+To persist the new structs introduced with the support for \gls{CSBS}, only the postgres helpers need to support serialization and deserialization of the new structs.
+
+\section{Testing}
+We will partially use test-driven development, meaning that we will write tests (at least for the known good case) before implementing functions, and extend them during and after development.
+This allows us to check the functionality (code section, function(s)) during development, while being able to extend testing whenever we identify new test cases during development.
+
+Test cases can be used to verify different aspects of a functionality.
+These are the ones we will focus on.
+\begin{itemize}
+ \item \textbf{Known good}:
+ Known good cases test whether a functionality works as expected.
+ They are the most useful during development, because they indicate whether the code is working as expected.
+ \item \textbf{Known Bad}:
+ Known bad cases test whether functionality that is known not to work behaves as expected.
+ \item \textbf{Determinism}:
+ This case type checks whether the same input leads to the same output.
+ It is important for code that must work deterministic (same output), non-deterministic (e.g. random output) or based on a state that impacts the functionality.
+ \item \textbf{Performance testing}:
+ Performance testing is used to gather timing information that can be used to identify functionality with long duration, or to compare performance between different implementations or major changes.
+ We will restrict performance testing to the comparison of the Blind RSA Signature Scheme and the Clause Blind Schnorr Signature Scheme.
+\end{itemize}
+
+
+\section{Signature Scheme Operations in GNUnet}
+\label{sec:specification-signature-scheme}
+
+The signature scheme operations implemented are needed in all other parts of the implementation.
+Taler's cryptographic primitives (e.g. \gls{RSABS}, \gls{hkdf}, hash functions) are mostly implemented in GNUnet utils, therefore the Clause Blind Schnorr Signature routines will be implemented in GNUnet too.
+It is important to provide a clear API for the cryptographic routines and to test them thoroughly.
+Libsodium will be used for finite field arithmetic (\cite{libsodium:finite-field-arithmetic}) and for other functionality when available (e.g. for key generation).
+Thus, a widely used and well tested cryptographic library is used for group operations.
+
+For \acl{FDH} and \gls{hkdf} existing implementations provided by GNUnet are used.
+The \gls{hkdf} is used with SHA-512 for the extraction phase and SHA-256 for the expansion phase.
+
+\subsection{Data Structures}
+Libsodium represents Ed25519 points and scalars as 32-byte char arrays.
+To provide a more user-friendly \ac{API}, structs were created to represent each type.
+For example \texttt{struct GNUNET\_CRYPTO\_CsPrivateKey} or \texttt{struct GNUNET\_CRYPTO\_RSecret}
+The main reason is to increase readability and to prevent misusage of the \ac{API}.
+Unlike RSA, our \gls{CSBS} on Ed25519 data structures have a fixed sizes.
+The different data structures can be found in table \ref{tab:datastruct-crypto}.
+
+\begin{table}[ht]
+ \centering
+ \resizebox{0.95\textwidth}{!}{\begin{minipage}{\textwidth}
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Values} & \textbf{Data Structure} & \textbf{Data Type} \\\hline
+ Curve25519 Scalar & {\small GNUNET\_CRYPTO\_Cs25519Scalar} & 32 byte char array\\\hline
+ Curve25519 Point & {\small GNUNET\_CRYPTO\_Cs25519Point} & 32 byte char array\\\hline
+ Private Key & {\small GNUNET\_CRYPTO\_CsPrivateKey} & {\small GNUNET\_CRYPTO\_Cs25519Scalar}\\\hline
+ Public Key & {\small GNUNET\_CRYPTO\_CsPublicKey} & {\small GNUNET\_CRYPTO\_Cs25519Point}\\\hline
+ $\alpha, \beta$ & {\small GNUNET\_CRYPTO\_CsBlindingSecret} & {\footnotesize 2x GNUNET\_CRYPTO\_Cs25519Scalar}\\\hline
+ $r$ & {\small GNUNET\_CRYPTO\_CsRSecret} & {\small GNUNET\_CRYPTO\_Cs25519Scalar}\\\hline
+ $R$ & {\small GNUNET\_CRYPTO\_CsRPublic} & {\small GNUNET\_CRYPTO\_Cs25519Point}\\\hline
+ $c$ & {\small GNUNET\_CRYPTO\_CsC} & {\small GNUNET\_CRYPTO\_Cs25519Scalar}\\\hline
+ $s$ & {\small GNUNET\_CRYPTO\_CsBlindS} & {\small GNUNET\_CRYPTO\_Cs25519Scalar}\\\hline
+ $s'$ & {\small GNUNET\_CRYPTO\_CsS} & {\small GNUNET\_CRYPTO\_Cs25519Scalar}\\\hline
+ $\sigma := \langle s',R' \rangle$ & {\small GNUNET\_CRYPTO\_CsSignature} & {\small GNUNET\_CRYPTO\_Cs25519Scalar}\\
+ & & {\small GNUNET\_CRYPTO\_Cs25519Point}\\\hline
+ Nonce & {\small GNUNET\_CRYPTO\_CsNonce} & 32 byte char array\\\hline
+ \end{tabular}
+ \caption{Data structures for cryptographic routines}
+ \label{tab:datastruct-crypto}
+\end{minipage}}
+\end{table}
+
+
+
+\subsection{Library API}
+The public \ac{API} and related data structures are specified in the C header file \url{src/include/gnunet_crypto_lib.h} in the GNUnet repository \cite{gnunet-git}.
+It was developed in multiple iterations based on feedback from Christian Grothoff.
+The complete C header \ac{API} can be found in the repository.
+This section provides an overview of the implemented crypto API.
+
+Some design decisions need to be explained further:
+\begin{itemize}
+ \item In order to prevent misusage of our implementation and increase readability, the functions that represent different stages in the signature scheme takes different data types as in- and output.
+ Internally most variables are either scalars or curve points (except for nonces, secrets and messages).
+ \item Operations that are performed twice in the Clause Blind Schnorr Signature Scheme (e.g. derivation of $ r $) do not have to be called twice.
+ Instead, the API returns an array of two instead of a single value.\\
+ For these functions, we also optimized the \gls{hkdf} (as proposed by Christian Grothoff).
+ Instead of calling \gls{hkdf} twice (with different salts, e.g. "r0" and "r1"), we call it one time (e.g. with salt "r") and double the output length.
+ \item The cryptographic hash function used to derive $ c' $ (hash of $ R' $ and message) must map the results into the main subgroup for scalars, meaning that it has to be a \gls{fdh} (see \ref{sec:rsa-fdh}).
+\end{itemize}
+
+The following API examples should provide an overview on how the API works and how to use it.
+
+First of all the API must provide functionality to create a \gls{25519} keypair as in listing \ref{lst:crypto-keypair-api}
+
+\begin{lstlisting}[style=bfh-c,language=C, caption={GNUnet create keypair API}, label={lst:crypto-keypair-api}]
+/**
+ * Create a new random private key.
+ *
+ * @param[out] priv where to write the fresh private key
+ */
+void
+GNUNET_CRYPTO_cs_private_key_generate (
+ struct GNUNET_CRYPTO_CsPrivateKey *priv);
+
+
+/**
+ * Extract the public key of the given private key.
+ *
+ * @param priv the private key
+ * @param[out] pub where to write the public key
+ */
+void
+GNUNET_CRYPTO_cs_private_key_get_public (
+ const struct GNUNET_CRYPTO_CsPrivateKey *priv,
+ struct GNUNET_CRYPTO_CsPublicKey *pub);
+\end{lstlisting}
+
+The signer needs an API to generate his secret $r$ and calculate his public point $R$.
+As specified in the redesign of the protocols, the r must not be chosen randomly because we need to provide \textit{\gls{abort-idempotency}}. However, the secret $r$ still needs to be \textit{unpredictable} and look random to the client.
+The r\_derive API derives such a secret $r$ from a nonce and a long-term secret with \gls{hkdf}.
+Further, the API ensures that a caller must generate two secret $r$ as in the Clause Blind Schnorr Signature scheme. This should discourage people from using the unsecure Blind Schnorr Signature scheme. See \ref{lst:crypto-rderive-api}.
+
+
+\begin{lstlisting}[style=bfh-c,language=C, caption={GNUnet r derive API}, label={lst:crypto-rderive-api}]
+ /**
+ * Derive a new secret r pair r0 and r1.
+ * In original papers r is generated randomly
+ * To provide abort-idempotency, r needs to be derived but still needs to be UNPREDICTABLE
+ * To ensure unpredictability a new nonce should be used when a new r needs to be derived.
+ * Uses HKDF internally.
+ * Comment: Can be done in one HKDF shot and split output.
+ *
+ * @param nonce is a random nonce
+ * @param lts is a long-term-secret in form of a private key
+ * @param[out] r array containing derived secrets r0 and r1
+ */
+ void
+ GNUNET_CRYPTO_cs_r_derive (const struct GNUNET_CRYPTO_CsNonce *nonce,
+ const struct GNUNET_CRYPTO_CsPrivateKey *lts,
+ struct GNUNET_CRYPTO_CsRSecret r[2]);
+
+
+/**
+ * Extract the public R of the given secret r.
+ *
+ * @param r_priv the private key
+ * @param[out] r_pub where to write the public key
+ */
+ void
+ GNUNET_CRYPTO_cs_r_get_public (const struct GNUNET_CRYPTO_CsRSecret *r_priv,
+ struct GNUNET_CRYPTO_CsRPublic *r_pub);
+\end{lstlisting}
+
+
+Same as the r\_derive, the blinding secrets are also derived and not generated randomly.
+The blinding secrets are generated by a client who provides a secret as seed to derive the secrets from as in listing \ref{lst:crypto-blinding-secrets-api}.
+
+\begin{lstlisting}[style=bfh-c,language=C, caption={GNUnet blinding secrets derive API}, label={lst:crypto-blinding-secrets-api}]
+/**
+ * Derives new random blinding factors.
+ * In original papers blinding factors are generated randomly
+ * To provide abort-idempotency, blinding factors need to be derived but still need to be UNPREDICTABLE
+ * To ensure unpredictability a new nonce has to be used.
+ * Uses HKDF internally
+ *
+ * @param secret is secret to derive blinding factors
+ * @param secret_len secret length
+ * @param[out] bs array containing the two derivedGNUNET_CRYPTO_CsBlindingSecret
+ */
+void
+GNUNET_CRYPTO_cs_blinding_secrets_derive (
+ const struct GNUNET_CRYPTO_CsNonce *blind_seed,
+ struct GNUNET_CRYPTO_CsBlindingSecret bs[2]);
+\end{lstlisting}
+
+Further the Clause Blind Schnorr API provides an API to calculate the two blinded c of the message with the two public $R$, the blinding factors and the public key as in listing \ref{lst:crypto-calc-c-api}.
+
+\begin{lstlisting}[style=bfh-c,language=C, caption={GNUnet calculate blinded c API}, label={lst:crypto-calc-c-api}]
+/**
+ * Calculate two blinded c's
+ * Comment: One would be insecure due to Wagner's algorithm solving ROS
+ *
+ * @param bs array of the two blinding factor structs each containing alpha and beta
+ * @param r_pub array of the two signer's nonce R
+ * @param pub the public key of the signer
+ * @param msg the message to blind in preparation for signing
+ * @param msg_len length of message msg
+ * @param[out] blinded_c array of the two blinded c's
+ */
+void
+GNUNET_CRYPTO_cs_calc_blinded_c (
+ const struct GNUNET_CRYPTO_CsBlindingSecret bs[2],
+ const struct GNUNET_CRYPTO_CsRPublic r_pub[2],
+ const struct GNUNET_CRYPTO_CsPublicKey *pub,
+ const void *msg,
+ size_t msg_len,
+ struct GNUNET_CRYPTO_CsC blinded_c[2]);
+\end{lstlisting}
+
+The sign function in our API is called sign\_derive, since we derive $b \in \{0,1\}$ from the long-term secret and then calculate the signature scalar of $c_b$.
+See listing \ref{lst:crypto-sign-api}.
+
+\begin{lstlisting}[style=bfh-c,language=C, caption={GNUnet sign API}, label={lst:crypto-sign-api}]
+/**
+ * Sign a blinded c
+ * This function derives b from a nonce and a longterm secret
+ * In original papers b is generated randomly
+ * To provide abort-idempotency, b needs to be derived but still need to be UNPREDICTABLE.
+ * To ensure unpredictability a new nonce has to be used for every signature
+ * HKDF is used internally for derivation
+ * r0 and r1 can be derived prior by using GNUNET_CRYPTO_cs_r_derive
+ *
+ * @param priv private key to use for the signing and as LTS in HKDF
+ * @param r array of the two secret nonce from the signer
+ * @param c array of the two blinded c to sign c_b
+ * @param nonce is a random nonce
+ * @param[out] blinded_signature_scalar where to write the signature
+ * @return 0 or 1 for b (see Clause Blind Signature Scheme)
+ */
+int
+GNUNET_CRYPTO_cs_sign_derive(
+ const struct GNUNET_CRYPTO_CsPrivateKey *priv,
+ const struct GNUNET_CRYPTO_CsRSecret r[2],
+ const struct GNUNET_CRYPTO_CsC c[2],
+ const struct GNUNET_CRYPTO_CsNonce *nonce,
+ struct GNUNET_CRYPTO_CsBlindS *blinded_signature_scalar);
+\end{lstlisting}
+
+The API for the unblind operation can be called with the blinding secrets and the signature scalar received from the signer as in listing \ref{lst:crypto-unblind-api}.
+
+\begin{lstlisting}[style=bfh-c,language=C, caption={GNUnet unblind API}, label={lst:crypto-unblind-api}]
+ /**
+ * Unblind a blind-signed signature using a c that was blinded
+ *
+ * @param blinded_signature_scalar the signature made on the blinded c
+ * @param bs the blinding factors used in the blinding
+ * @param[out] signature_scalar where to write the unblinded signature
+ */
+void
+GNUNET_CRYPTO_cs_unblind (
+ const struct GNUNET_CRYPTO_CsBlindS *blinded_signature_scalar,
+ const struct GNUNET_CRYPTO_CsBlindingSecret *bs,
+ struct GNUNET_CRYPTO_CsS *signature_scalar);
+\end{lstlisting}
+
+The verify API takes the message and its signature with the public key and returns GNUNET\_OK for a valid signature and GNUNET\_SYSERR otherwise.
+See listing \ref{lst:crypto-verify-api}.
+
+\begin{lstlisting}[style=bfh-c,language=C,, caption={GNUnet verify API}, label={lst:crypto-verify-api}]
+ /**
+ * Verify whether the given message corresponds to the given signature and the
+ * signature is valid with respect to the given public key.
+ *
+ * @param sig signature that is being validated
+ * @param pub public key of the signer
+ * @param msg is the message that should be signed by @a sig (message is used to calculate c)
+ * @param msg_len is the message length
+ * @returns #GNUNET_YES on success, #GNUNET_SYSERR if signature invalid
+ */
+ enum GNUNET_GenericReturnValue
+ GNUNET_CRYPTO_cs_verify (const struct GNUNET_CRYPTO_CsSignature *sig,
+ const struct GNUNET_CRYPTO_CsPublicKey *pub,
+ const void *msg,
+ size_t msg_len);
+\end{lstlisting}
+
+\subsection{Testing}
+For digital signature schemes, the most important test case is the \textit{known good} case where a signature is created and successfully validated.
+This test case already tests very much in a digital signature scheme.
+When the signature creation or verification has a bug, a test will not succeed, because the mathematic operations need to be correct to be validated correctly.
+
+The cryptographic operations are further tested for deterministicy (where it applies), meaning that multiple function calls with the same input must lead to the same output.
+
+Since libsodium is used for the finite field arithmetic operations and is a well tested library, many cryptographic tests are already done in libsodium.
+
+The performance is measured in a benchmark to see how performant \gls{CSBS} are in comparison to the RSA Blind Signature Scheme.
+
+\section{Taler Cryptographic Utilities}
+Taler provides utility functions to support cryptographic operations.\\
+This chapter provides an overview of these utility functions and about the functionality they provide.
+
+\subsection{Planchet Creation}
+In crypto.c many utility functions are provided to create planchets (for planchet details see \ref{fig:coin:states}), blinding secrets and much more.
+One difference between \gls{RSABS} and \gls{CSBS} is, that the coin private key and RSA blinding secret can be created at the same point in time, since the RSA blinding secret is created randomly.
+However, for Clause Blind Schnorr secrets an additional step is needed, the public $R_0$ and $R_1$ are required to calculate the blinding seed to derive the secrets.
+
+A planchet in the Clause Blind Schnorr Signature Scheme can be created as followed (implementation details omitted).
+
+\begin{enumerate}
+ \item Create planchet with new \ac{EdDSA} private key
+ \item Derive withdraw nonce
+ \item Request public $R_0, R_1$ from signer
+ \item Derive blinding seed
+ \item Prepare (blind) the planchet
+\end{enumerate}
+
+After the planchet is created, it is sent to the exchange to be signed.
+
+\subsection{Taler CS Security Module}
+The exchange segregates access to the private keys with separate security module processes.
+The security module has sole access to the private keys of the online signing keys and thus, only a security module can create signatures.
+The different \textit{taler-exchange-secmod} processes (separated by signature scheme) are managing the exchanges online signing keys. The RSA denomination keys for example are managed with \textit{taler-exchange-secmod-rsa}.
+
+Now a new \textit{taler-exchange-secmod-cs} needs to be created for managing the \gls{CSBS} denomination keys.
+These security modules run on the same machine as the httpd process and they use UNIX Domain Sockets as method for \acl{IPC}.
+A short introduction about UNIX Domain Sockets can be found in the blog post \cite{matt:unix-domain-sockets}.
+Furthermore, the security modules are used to protect the online signing keys by performing the actual signing operations in the dedicated taler-secmod-cs process.
+This abstraction makes it harder for an attacker who has already compromised the http daemon to gain access to the private keys.
+However, such an attacker would still be able to sign arbitrary messages (see \cite{taler-documentation:exchange-operator-manual}).
+A crypto helper exists for each security module, these functions can be called inside the exchange for operations requiring the private online signing keys.
+The new Clause Schnorr security module and corresponding crypto helper provides the following functionality:
+\begin{itemize}
+ \item Private Key Management and creation
+ \item Request public $R_0, R_1$
+ \item Request a signature of a $c_0,c_1$ pair
+ \item Revoke an online signing key
+\end{itemize}
+
+\subsection{Testing}
+All of the operations have tests and are included in unit tests.
+As a template for testing, the existing RSA tests were used and adjusted for \gls{CSBS}.
+
+
+\section{Denomination Key Management}
+Since we introduce a type of denomination keys, related operations like connection to the \gls{CSBS} security module, making the denominations available for customers, persisting them in the database and offline signing using the exchange's offline signature key have to be extended to incorporate the \acl{CS}.
+
+The exchange offline signer requests the future, not yet signed keys by calling GET \url{/management/keys} as described in table \ref{tab:management-keys-get}. \\\\
+\framebox[1.1\width]{\color{blue}\texttt{GET /management/keys}}
+\begin{table}[ht]
+ \centering
+ \resizebox{0.9\textwidth}{!}{\begin{minipage}{\textwidth}
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{ll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Field} & \textbf{Value} \\
+ future\_denoms & Information about denomination keys \\
+ future\_signkeys & Information about exchange online signing keys \\
+ master\_pub & Exchange's master public key \\
+ denom\_secmod\_public\_key & RSA security module public key \\
+ denom\_secmod\_cs\_public\_key & \gls{CSBS} security module public key \\
+ signkey\_secmod\_public\_key & Online signing security module public key \\
+ \end{tabular}
+ \caption{GET \url{/management/keys} response data}
+ \label{tab:management-keys-get}
+\end{minipage}}
+\end{table}
+
+It then signs the keys and returns them using POST on the same \ac{URL} with the data described in table \ref{tab:management-keys-post}. \\\\
+\framebox[1.1\width]{\color{blue}\texttt{POST /management/keys}}
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{ll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Field} & \textbf{Value} \\
+ denom\_sigs & Denomination key signatures \\
+ signkey\_sigs & Online signing key signatures \\
+ \end{tabular}
+ \caption{POST \url{/management/keys} response data}
+ \label{tab:management-keys-post}
+\end{table}
+
+Wallets can then call GET \url{/keys} to obtain the current denominations and other information, the response is described in table \ref{tab:keys-get}. \\\\
+\framebox[1.1\width]{\color{blue}\texttt{GET /keys}}
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{ll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Field} & \textbf{Value} \\
+ version & Exchange's protocol version \\
+ currency & Currency \\
+ master\_public\_key & Exchange's master public key \\
+ reserve\_closing\_delay & Delay before reserves are closed \\
+ signkeys & Exchange's online signing public keys \\
+ recoup & Revoked keys \\
+ denoms & List of denominations \\
+ auditors & Auditors for this exchange \\
+ list\_issue\_date & Timestamp \\
+ eddsa\_pub & Exchange's online signing public key \\
+ eddsa\_sig & Signature (use "eddsa\_pub" for verification) \\
+ \end{tabular}
+ \caption{GET \url{/keys} response data}
+ \label{tab:keys-get}
+\end{table}
+
+
+\section{New Endpoint for $R$}
+The withdraw and refresh protocols using the Claude Blind Schnorr Signature Scheme introduce an additional round trip.
+In this round trip, the customer requests two $ R $ from the exchange.
+The exchange uses a secret $ r $ to calculate $ R := rG $.
+\\
+In contrast to the plain Clause Blind Schnorr Signature Scheme (see \ref{sec:clause-blind-schnorr-sig}), $ r $ isn't generated randomly but instead derived using a \gls{hkdf} with a nonce from the customer and a denomination private key (secret only known by the exchange).
+This still ensures that the private $ r $ can't be anticipated, but has multiple advantages regarding \gls{abort-idempotency}.
+\Gls{abort-idempotency} means that a withdraw or refresh operation can be aborted in any step and later tried again (using the same values) without yielding a different result.
+The challenge for $ r, R $ regarding \gls{abort-idempotency} is to ensure that the same $ r $ is used during the complete signature creation process.
+
+The only drawback of this process is that we have to ensure that the same nonce and secret aren't used for different withdraw- or refresh-operations.
+This is done during signature creation and will be described in the withdraw protocol section \ref{sec:specification-withdraw}.
+
+
+\subsection{Public APIs and Data Structures}
+This is a new functionality, meaning a new endpoint accessible to customers has to be introduced.
+It will be made available in the exchange HTTP server under \framebox[1.1\width]{\color{blue}\texttt{POST /csr}} and will take the input parameters described in table \ref{tab:csr-request-data} (as \ac{JSON}).
+\begin{table}[ht]
+ \centering
+ \resizebox{0.9\textwidth}{!}{\begin{minipage}{\textwidth}
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Field} & \textbf{Type} & \textbf{Value} \\
+ nonce & String & 32 Bytes encoded in Crockford base32 Hex \\
+ denom\_pub\_hash & String & Denomination Public Key encoded in Crockford base32 Hex \\
+ \end{tabular}
+ \caption{POST \url{/csr} request data}
+ \label{tab:csr-request-data}
+\end{minipage}}
+\end{table}
+
+The exchange will then check the denomination and return one of these HTTP status codes:
+\begin{itemize}
+ \item \textbf{200 (HTTP\_OK)}: Request Successful
+ \item \textbf{400 (BAD\_REQUEST)}: Invalid input parameters
+ \item \textbf{404 (NOT\_FOUND)}: Denomination unknown or not Clause Schnorr
+ \item \textbf{410 (GONE)}: Denomination revoked/expired
+ \item \textbf{412 (PRECONDITION\_FAILED)}: Denomination not yet valid
+\end{itemize}
+
+When the request was successful, the exchange returns the data described in table \ref{tab:csr-response-data} (as \ac{JSON}).
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Field} & \textbf{Type} & \textbf{Value} \\
+ r\_pub\_0 & String & 32 Bytes encoded in Crockford base32 Hex \\
+ r\_pub\_1 & String & 32 Bytes encoded in Crockford base32 Hex \\
+ \end{tabular}
+ \caption{POST \url{/csr} response data}
+ \label{tab:csr-response-data}
+\end{table}
+
+
+\subsection{Persistence}
+This API does not persist anything.
+This is because the resulting $R_0, R_1$ are derived and can be derived in a later step.
+
+
+\section{Withdraw Protocol}
+\label{sec:specification-withdraw}
+The withdraw protocol has been introduced in section \ref{sec:withdrawal}.
+For the \acl{CS} necessary adjustments are described in section \ref{sec:withdraw-protocol-schnorr}.
+
+
+\subsection{Public APIs and Data Structures}
+\label{sec:specification-withdraw-public-api}
+The existing endpoint is available under \texttt{POST /reserves/[reserve]/withdraw} where "reserve" is the reserve public key encoded as Crockford base32.
+It takes the following input parameters described in table \ref{tab:withdraw-request-data} as JSON.\\\\
+\framebox[1.1\width]{\color{blue}\texttt{POST /reserves/[reserve]/withdraw}}
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{ll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Field} & \textbf{Value} \\
+ denom\_pub\_hash & Denomination Public Key \\
+ coin\_ev & RSA blinded coin public key \\
+ reserve\_sig & Signature over the request using the reserve's private key \\
+ \end{tabular}
+ \caption{Withdraw request data}
+ \label{tab:withdraw-request-data}
+\end{table}
+
+In order to facilitate parsing, Christian Grothoff suggested to include the cipher type in the "coin\_ev" field, thus creating a nested \ac{JSON} (as described in table \ref{tab:withdraw-coin-ev-rsa}).
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Field} & \textbf{Type} & \textbf{Value} \\
+ cipher & Integer & Denomination cipher: 1 stands for RSA \\
+ rsa\_blinded\_planchet & String & RSA blinded coin public key \\
+ \end{tabular}
+ \caption{Withdraw "coin\_ev" field (RSA)}
+ \label{tab:withdraw-coin-ev-rsa}
+\end{table}
+
+For the Clause Schnorr implementation, the field "rsa\_blinded\_planchet" will be replaced with the necessary values as described in table \ref{tab:withdraw-coin-ev-cs}.
+\begin{table}[ht]
+ \centering
+ \resizebox{0.85\textwidth}{!}{\begin{minipage}{\textwidth}
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Field} & \textbf{Type} & \textbf{Value} \\
+ cipher & Integer & Denomination cipher: 2 stands for \gls{CSBS} \\
+ cs\_nonce & String & 32 Bytes encoded in Crockford base32 Hex \\
+ cs\_blinded\_c0 & String & 32 Bytes encoded in Crockford base32 Hex \\
+ cs\_blinded\_c1 & String & 32 Bytes encoded in Crockford base32 Hex \\
+ \end{tabular}
+ \caption{Withdraw "coin\_ev" field (\gls{CSBS})}
+ \label{tab:withdraw-coin-ev-cs}
+\end{minipage}}
+\end{table}
+
+The exchange will then process the withdraw request and return one of these HTTP status codes:
+\begin{itemize}
+ \item \textbf{200 (HTTP\_OK)}: Request Successful
+ \item \textbf{400 (BAD\_REQUEST)}: Invalid input parameters (can also happen if denomination cipher doesn't match with cipher in "coin\_ev")
+ \item \textbf{403 (FORBIDDEN)}: Signature contained in "reserve\_sig" invalid
+ \item \textbf{404 (NOT\_FOUND)}: Denomination unknown
+ \item \textbf{410 (GONE)}: Denomination revoked/expired
+ \item \textbf{412 (PRECONDITION\_FAILED)}: Denomination not yet valid
+\end{itemize}
+
+When the request was successful, the exchange returns the RSA signature as JSON (described in table \ref{tab:withdraw-response-rsa}).
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Field} & \textbf{Type} & \textbf{Value} \\
+ cipher & Integer & Denomination cipher: 1 stands for RSA \\
+ blinded\_rsa\_signature & String & RSA signature \\
+ \end{tabular}
+ \caption{Withdraw response (RSA)}
+ \label{tab:withdraw-response-rsa}
+\end{table}
+
+Table \ref{tab:withdraw-response-cs} describes the response for \gls{CSBS}.
+\begin{table}[ht]
+ \centering
+ \resizebox{0.85\textwidth}{!}{\begin{minipage}{\textwidth}
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Field} & \textbf{Type} & \textbf{Value} \\
+ cipher & Integer & Denomination cipher: 2 stands for \gls{CSBS} \\
+ b & Integer & \gls{CSBS} signature session identifier (either 0 or 1) \\
+ s & String & signature scalar (32 Bytes encoded in Crockford base32 Hex) \\
+ \end{tabular}
+ \caption{Withdraw response (\gls{CSBS})}
+ \label{tab:withdraw-response-cs}
+\end{minipage}}
+\end{table}
+
+
+\subsection{Persistence}
+Persistence for withdrawing is implemented in the function \texttt{postgres\_do\_withdraw} in \texttt{src/exchangedb/plugin\_exchangedb\_postgres.c}
+For \gls{CSBS}, persisting the blinded signature must be implemented.
+
+
+\section{Deposit Protocol}
+For the deposit protocol (described in section \ref{sec:deposit-protocol}) only the handling and verification of \gls{CSBS} signatures has to be added.
+
+\subsection{Public APIs and Data Structures}
+Deposit is an existing endpoint available under \texttt{POST /coins/[coin public key]/deposit} where "coin public key" is encoded as Crockford base32.
+Additional parameters are passed as JSON (as described in table \ref{tab:spend-request}).\\\\
+\framebox[1.1\width]{\color{blue}\texttt{POST /coins/[coin public key]/deposit}}
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{ll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Field} & \textbf{Value} \\
+ % cipher & Denomination cipher: 2 stands for \gls{CSBS} \\
+ % b & \gls{CSBS} signature session identifier (either 0 or 1) \\
+ % s & signature scalar (32 Bytes encoded in Crockford base32 Hex) \\
+ merchant\_payto\_uri & Account that is credited \\
+ wire\_salt & Salt used by the merchant \\
+ contribution & Amount to use for payment (for one specific coin) \\
+ denom\_pub\_hash & Denomination public key hash \\
+ ub\_sig & (unblinded) denomination signature of coin \\
+ merchant\_pub & Merchant public key \\
+ h\_contract\_terms & Contract terms hash \\
+ coin\_sig & Deposit permission signature \\
+ timestamp & Timestamp of generation \\
+ refund\_deadline (optional) & Refund deadline \\
+ wire\_transfer\_deadline (optional) & Wire transfer deadline \\
+ \end{tabular}
+ \caption{Spend request}
+ \label{tab:spend-request}
+\end{table}
+
+Relevant field for the \gls{CSBS} implementation is the field "ub\_sig" containing the unblinded denomination signature of the coin.
+For RSA, the (nested) \ac{JSON} is described in table \ref{tab:spend-request-ubsig-rsa}.
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Field} & \textbf{Type} & \textbf{Value} \\
+ cipher & Integer & Denomination cipher: 1 stands for RSA \\
+ rsa\_signature & String & Unblinded RSA signature \\
+ \end{tabular}
+ \caption{ub\_sig (RSA)}
+ \label{tab:spend-request-ubsig-rsa}
+\end{table}
+
+Table \ref{tab:spend-request-ubsig-cs} describes the values in "ub\_sig" required for \gls{CSBS}.
+\begin{table}[ht]
+ \centering
+ \resizebox{0.85\textwidth}{!}{\begin{minipage}{\textwidth}
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Field} & \textbf{Type} & \textbf{Value} \\
+ cipher & Integer & Denomination cipher: 2 stands for \gls{CSBS} \\
+ cs\_signature\_r & String & Curve point $ R' $ (32 Bytes encoded in Crockford base32 Hex) \\
+ cs\_signature\_s & String & Signature scalar (32 Bytes encoded in Crockford base32 Hex) \\
+ \end{tabular}
+ \caption{ub\_sig (\gls{CSBS})}
+ \label{tab:spend-request-ubsig-cs}
+\end{minipage}}
+\end{table}
+
+
+\subsection{Persistence}
+Persistence is handled in the functions \texttt{postgres\_insert\_deposit} and\\ \texttt{postgres\_have\_deposit} located in \url{src/exchangedb/plugin_exchangedb_postgres.c}.
+However, these functions are not containing \gls{CSBS}-specific persistence.
+\\What needs to be adjusted however, is the function \texttt{postgres\_ensure\_coin\_known} called by the function \texttt{TEH\_make\_coin\_known} (located in \url{src/exchange/taler-exchange-httpd_db.c}).
+
+
+% \section{Tipping}
+% \subsection{Public APIs and Data Structures}
+% \subsection{Code Location}
+% \subsection{Persistence}
+% \subsection{Used Libraries}
+
+% \section{Payback Protocol}
+% \subsection{Public APIs and Data Structures}
+% \subsection{Code Location}
+% \subsection{Persistence}
+% \subsection{Used Libraries}
+
+
+% sollte ein Product Backlog das Ziel dieser Phase sein?
diff --git a/doc/cs/content/4_3_implementation.tex b/doc/cs/content/4_3_implementation.tex
new file mode 100644
index 000000000..879e69e8f
--- /dev/null
+++ b/doc/cs/content/4_3_implementation.tex
@@ -0,0 +1,333 @@
+\chapter{Implementation}
+\label{chap:implement}
+This chapter gives an overview on the implementation challenges and discusses special parts in the implementation.
+
+
+\section{Signature Scheme Operations}
+The signature scheme operations are implemented in the GNUnet core repository \cite{gnunet-git} (and have been merged into the master branch).
+This would allow other GNUnet projects to use our implementation of the Clause Blind Schnorr Signature Scheme.
+
+The implementation is done in multiple locations:
+\begin{itemize}
+ \item \texttt{src/include/gnunet\_crypto\_lib.h}:
+ This header file is included when using GNUnet's cryptography implementation.
+ \item \texttt{src/util/crypto\_cs.c}:
+ The functions specified in \texttt{gnunet\_crypto\_lib.h} will be implemented here.
+ \item \texttt{src/util/test\_crypto\_cs.c}:
+ The test cases for the signature scheme will be implemented here.
+ \item \texttt{src/util/perf\_crypto\_cs.c}:
+ This file houses the implementation of a small program that will be used to compare the performance against the blind RSA Signature Scheme.
+\end{itemize}
+
+The specification explaining the \ac{API} can be found in section \ref{sec:specification-signature-scheme}. There are two internal functions that have to be explained further in this section.
+
+The \texttt{map\_to\_scalar\_subgroup} function clamps scalars, which is necessary for values that are derived using a \gls{hkdf}.
+It sets the three least significant bits to zero (making the scalar a multiple of 8), sets the most significant bit to zero and the second-most significant bit to one.
+This process is further described in \cite{rfc7748} and \cite{madden:curve25519-clamping}.
+
+\begin{lstlisting}[style=bfh-c, language=C, caption={Function map\_to\_scalar\_subgroup - Crypto API}, label={lst:map-to-scalar}]
+static void
+map_to_scalar_subgroup (struct GNUNET_CRYPTO_Cs25519Scalar *scalar)
+{
+ scalar->d[0] &= 248;
+ scalar->d[31] &= 127;
+ scalar->d[31] |= 64;
+}
+\end{lstlisting}
+
+Another important function is the \gls{fdh} (see \ref{sec:schnorr-sig}) used to map the message to a scalar.
+GNUnet provides a \gls{fdh} function, which expects libgcrypt's multi precision format.
+A conversion function is provided by GNUnet, which requires the data to be in big endian format.
+Since libsodium uses a little endian representation, the conversion process must include endianness conversion.
+The complete \gls{fdh} including the required conversions is implemented in the function described in listing \ref{lst:cs-fdh}.
+\begin{lstlisting}[style=bfh-c, language=C, caption={Function cs\_full\_domain\_hash - Crypto API}, label={lst:cs-fdh}]
+static void
+cs_full_domain_hash (const struct GNUNET_CRYPTO_CsRPublic *r_dash,
+ const void *msg,
+ size_t msg_len,
+ const struct GNUNET_CRYPTO_CsPublicKey *pub,
+ struct GNUNET_CRYPTO_CsC *c)
+{
+ ...
+\end{lstlisting}
+
+Last but not least, the implementation has one notable performance improvement not mentioned in the redesigned protocols.
+In various steps \gls{hkdf} is used multiple times in a row.
+For example to derive the four blinding secrets $\alpha_0, \alpha_1, \beta_0, \beta_1$.
+The derivation can be done in one \gls{hkdf} call with bigger output size, 128 bit in this case.
+The output then can be split in four parts and then mapped to the ed25519 subgroup.
+This can be done secure, because as explained in \autoref{sec:kdf} a \gls{hkdf} output is truly random.
+
+
+\section{Taler Cryptographic Utilities}
+\begin{bfhNoteBox}
+ Implementation is done in Taler's exchange.
+ From here on the implementation can be found in the exchange git repository \cite{taler-git:exchange}.
+\end{bfhNoteBox}
+The cryptographic utilities of Taler can be found in \texttt{src/util}.
+
+
+The implementation is done in various locations:
+\begin{itemize}
+ \item \texttt{src/include/taler\_crypto\_lib.h}: This header file is included when using Taler's cryptography implementation.
+ The different data structures and functionality are defined here.
+ \item \texttt{src/util/denom.c}: Implement denomination utility functions for \gls{CSBS} cases
+ \item \texttt{src/util/crypto.c}: Adjust all utility functions to support \gls{CSBS}.
+ crypto.c contains many cryptographic utility functions, for example to create planchets or blinding factors.
+ \item \texttt{src/util/test\_crypto.c}: Functionality tests for crypto.c and denom.c
+ \item \texttt{src/include/taler\_signatures.h}: In this header file message formats and signature constants are defined (not modified)
+ \item \texttt{src/util/secmod\_signatures.c}: Utility functions for Taler security module signatures
+\end{itemize}
+
+The security module \texttt{taler-secmod-cs} is implemented here:
+\begin{itemize}
+ \item \texttt{src/util/taler-exchange-secmod-cs.c}: Standalone process to perform private key Clause Blind Schnorr signature operations.
+ \item \texttt{src/util/taler-exchange-secmod-cs.h}: Specification of \ac{IPC} messages for the CS secmod process
+ \item \texttt{src/util/taler-exchange-secmod-cs.conf}: Configuration file for the secmod process
+ \item \texttt{src/util/secmod\_common.c} and \texttt{src/util/secmod\_common.h}: Common functions for the exchange's security modules (not modified)
+\end{itemize}
+
+The corresponding crypto helper, that talks with the security module, and its tests \& benchmarks are implemented here:
+\begin{itemize}
+ \item \texttt{src/util/crypto\_helper\_cs.c}: Utility functions to communicate with the security module
+ \item \texttt{src/util/crypto\_helper\_common.c}: and \texttt{crypto\_helper\_common.h}: Common functions for the exchange security modules (not modified)
+ \item \texttt{src/util/test\_helper\_cs.c}: Tests and benchmarks for the \gls{CSBS} crypto helper
+\end{itemize}
+% Crypto API offene Punkte:
+%Input-validation of points and scalars:
+% describe clamping: https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/
+% Testing: inverse operations, blinded signature test
+
+
+\section{Denomination Key Management}
+For the implementation, the \gls{CSBS} security module had to be connected to the key handling and the \gls{CSBS} denominations had to be integrated:
+\begin{itemize}
+ \item \url{src/exchange/taler-exchange-httpd_keys.h} and \\
+ \url{src/exchange/taler-exchange-httpd_keys.c}: Integrate \gls{CSBS} secmod and denomination key management
+ \item \url{src/exchange-tools/taler-exchange-offline.c}: Implement \gls{CSBS} case for offline signing of denomination keys
+ \item \url{src/include/taler_exchange_service.h}: \\
+ Add \gls{CSBS} secmod public key to struct\\TALER\_EXCHANGE\_FutureKeys
+ \item \url{src/json/json_helper.c}: Implement CS case in function parse\_denom\_pub (used in taler-exchange-offline.c)
+ \item \url{src/json/json_pack.c}: Implement \gls{CSBS} case in function TALER\_JSON\_pack\_denom\_pub (used in taler-exchange-httpd\_keys.c)
+ \item \url{src/pq/pq_query_helper.c}: Implement \gls{CSBS} case in function qconv\_denom\_pub
+ \item \url{src/pq/pq_result_helper.c}: Implement \gls{CSBS} case in function extract\_denom\_pub
+\end{itemize}
+
+In order for the tests to pass, the following changes had to be implemented:
+\begin{itemize}
+ \item \url{src/lib/exchange_api_management_get_keys.c}: Add denom\_secmod\_cs\_public\_key JSON parsing, implement \gls{CSBS} case \\in function TALER\_EXCHANGE\_ManagementGetKeysHandle
+ \item \url{src/testing/.gitignore}: Add paths where \gls{CSBS} keys are stored (secmod-helper)
+ \item \url{src/testing/test_auditor_api.conf}: Add section taler-exchange-secmod-cs
+ \item \url{src/testing/test_exchange_api_keys_cherry_picking.conf}: Add section taler-exchange-secmod-cs
+ \item \url{src/testing/testing_api_helpers_exchange.c}: Add \gls{CSBS} secmod start and stop logic
+\end{itemize}
+
+
+\section{New Endpoint for $R$}
+The new endpoint is available in the exchange's HTTP server under \url{/csr}.
+It parses and checks the input, passes the request for derivation of the two $ R $'s down to the \gls{CSBS} security module and returns them to the requestor.
+The implementation can be found in:
+\begin{itemize}
+ \item \url{src/exchange/taler-exchange-httpd.c}:
+ Definition for the new endpoint, calls the function that handles \url{/csr} requests
+ \item \url{src/exchange/taler-exchange-httpd_responses.h} and \\
+ \url{src/exchange/taler-exchange-httpd_responses.c}: \\
+ Added function TEH\_RESPONSE\_reply\_invalid\_denom\_cipher\_for\_operation that indicates a failure when the endpoint is called for a non-\gls{CSBS} denomination
+ \item \url{src/exchange/taler-exchange-httpd_csr.h} and \\
+ \url{src/exchange/taler-exchange-httpd_csr.c}: \\
+ Implementation of the request handler for the new endpoint
+ \item \url{src/exchange/taler-exchange-httpd_keys.h} and \\
+ \url{src/exchange/taler-exchange-httpd_keys.c}: \\
+ Additional function TEH\_keys\_denomination\_cs\_r\_pub that passes down the request to derive the $ R $ to the taler-exchange-secmod-cs helper
+\end{itemize}
+
+The tests that check the functionality of the procotols are defined in \url{src/testing/} and use code that calls the \ac{API} (located in \url{src/lib/}).
+Since the new endpoint is used during withdrawing coins, testing for the \url{/csr} endpoint is integrated in these protocol tests.
+Therefore, a call to the endpoint was implemented and later integrated into the calls to the withdraw-\ac{API}.
+Code for calling the endpoint is located in these files:
+\begin{itemize}
+ \item \url{src/include/taler_exchange_service.h}: \\
+ Header describing functions and data structures used in withdraw and refresh testing:
+ \begin{itemize}
+ \item struct TALER\_EXCHANGE\_CsRHandle: Handle containing request information
+ \item struct TALER\_EXCHANGE\_CsRResponse: Response details
+ \item function TALER\_EXCHANGE\_CsRCallback: Callback function to deliver the results (used in withdraw and refresh)
+ \item function TALER\_EXCHANGE\_csr: Used to call endpoint
+ \item function TALER\_EXCHANGE\_csr\_cancel: Used to free dynamically allocated resources
+ \end{itemize}
+ \item \url{src/lib/exchange_api_csr.c}: Implementation of \url{/csr} request
+\end{itemize}
+
+
+\section{Withdraw Protocol}
+\label{sec:withdraw-protocol-impl}
+Since this is an existing endpoint, it was adjusted to support \gls{CSBS}.
+Mainly, the in- and output-handling had to be adjusted as described in section \ref{sec:specification-withdraw-public-api}, additional cipher checks for the denomination were added and the \gls{CSBS} for persisting the request in the database was implemented.
+\\\\
+An interesting part of the implementation is the check whether a nonce was already used for this denomination or not (step: $s \leftarrow \text{GetWithdraw}(n_w, D_p)$).
+This step ensures that the same signature will always be returned for a certain nonce.
+Using the same nonce for the same denomination twice without this check would lead to the same random value $r$.
+This is due to derivation of $r := \text{HKDF}(256,n_w || d_s, \text{"r"})$.
+An attacker could then immediately recover the secret key by the following equation: $(h' - h) * x \mod q = s -s' \mod q$ \cite{tibouchi:attacks-schnorr-nonce}.
+There are popular examples of this vulnerability in Sony Playstation 3's or Bitcoins ECDSA implementation \cite{buchanan:ps3-ecdsa-vuln} \cite{wang:bitcoin-ecdsa-vuln}.
+More details on how such a vulnerability can be exploited can be found in one of the author's blog posts \cite{gian:nonce-sense}.\\
+The designed Taler protocols using \gls{CSBS} are preventing this attack by checking the nonce and return the previously generated signature.
+Additionally the denomination's public key is included in this check to prevent another issue explained in section \ref{sec:taler-vuln}.\\
+The check is implemented by persisting a hash value over $n_w$ and $D_p$.
+On every withdrawal \texttt{check\_request\_idempotent()} is called, which checks whether the persisted hash matches with the current $n_w, D_p$ pair.
+
+
+\begin{itemize}
+ \item \url{src/exchange/taler-exchange-httpd_withdraw.c}: Implementation of \gls{CSBS} case for withdraw endpoint
+ \item \url{src/exchange/taler-exchange-httpd_keys.c}: Implement \gls{CSBS} case in function \\
+ TEH\_keys\_denomination\_sign (passes the signature creation down to the crypto helpers)
+ \item \url{src/include/taler_json_lib.h} and \url{src/json/json_helper.c}: \\
+ Add function TALER\_JSON\_spec\_blinded\_planchet
+ \item \url{src/json/json_pack.c}: \\
+ Implement \gls{CSBS} case in function\\ TALER\_JSON\_pack\_blinded\_denom\_sig
+ \item \url{src/pq/pq_query_helper.c}: implement \gls{CSBS} case in functions qconv\_denom\_sig and qconv\_blinded\_denom\_sig
+ \item \url{src/pq/pq_result_helper.c}: Implement \gls{CSBS} case in function extract\_blinded\_denom\_sig
+\end{itemize}
+
+For testing, the \gls{CSBS}-related data structures and procedures as well as the request to the additional endpoint \url{/csr} (before performing the actual withdrawal) were integrated:
+\begin{itemize}
+ \item \url{src/testing/test_exchange_api.c}: Add additional tests for \gls{CSBS} withdraw
+ \item \url{src/include/taler_testing_lib.h}: Specification for functions \\
+ TALER\_TESTING\_cmd\_withdraw\_cs\_amount and \\
+ TALER\_TESTING\_cmd\_withdraw\_cs\_amount\_reuse\_key, add denomination cipher parameter to function TALER\_TESTING\_find\_pk
+ \item \url{src/testing/testing_api_cmd_withdraw.c}: add functions \\
+ TALER\_TESTING\_cmd\_withdraw\_cs\_amount and \\
+ TALER\_TESTING\_cmd\_withdraw\_cs\_amount\_reuse\_key, implement \gls{CSBS}-specific logic for withdraw
+ \item \url{src/testing/testing_api_helpers_exchange.c}:
+ add cipher parameter to function TALER\_TESTING\_find\_pk
+ \item \url{src/lib/exchange_api_withdraw.c}: Implement \gls{CSBS}-specific withdraw logic, integrate \url{/csr} request
+ \item \url{src/lib/exchange_api_withdraw2.c}: implement \gls{CSBS} case
+ \item \url{src/include/taler_json_lib.h} and \url{src/json/json_pack.c}: \\
+ Add function TALER\_JSON\_pack\_blinded\_planchet
+ \item \url{src/json/json_helper.c} implement \gls{CSBS} case in function parse\_blinded\_denom\_sig
+\end{itemize}
+
+\section{Deposit Protocol}
+For deposit, only few changes were necessary because some of the required functionality has already been added for the previously implemented protocols, and only the coin signature verification is \gls{CSBS}-specific in this protocol.
+\begin{itemize}
+ \item \url{/src/exchange/taler-exchange-httpd_deposit.c}: Add check whether denomination cipher and denomination signature cipher are equal
+ \item \url{/src/json/json_helper.c}: Implement \gls{CSBS} case in function parse\_denom\_sig
+ \item \url{/src/pq/pq_result_helper.c}: Implement \gls{CSBS} case in function extract\_denom\_sig
+\end{itemize}
+
+Tests for deposit are implemented here:
+\begin{itemize}
+ \item \url{/src/testing/test_exchange_api.c}: Add tests (see "struct TALER\_TESTING\_Command\ spend\_cs[]") that spend \gls{CSBS} coins withdrawn in tests added for withdrawal
+ \item \url{/src/json/json_pack.c}: Implement \gls{CSBS} case in function TALER\_JSON\_pack\_denom\_sig
+\end{itemize}
+
+\section{Fixing a Minor Security Issue in Taler's RSA Blind Signature Protocols}
+\label{sec:taler-vuln}
+While implementing the nonce check in the \gls{CSBS} protocol (see section \ref{sec:withdraw-protocol-impl}), a minor security issue in Taler's current RSA Blind Signature implementation was detected and fixed.
+The issue was only in the implementation of the current RSA Blind Signature protocols, the fix for this scenario was already implemented in \gls{CSBS} since the beginning.
+
+\subsection{Security Issue}
+\label{sec:taler-vuln-desc}
+
+The redesigned \gls{CSBS} protocols already include the denomination key in the nonce check, which fixes this issue (see \ref{sec:withdraw-protocol-schnorr}).
+In the case of \gls{RSABS}, the current protocol includes an \gls{idempotence} check by persisting the hash value of the blinded coin $m'$.
+On a withdrawal/refresh the \gls{idempotence} check compares if the hash value of $m'$ was seen in the past and returns the 'old' signature on a match.
+This could lead to the following scenario:
+
+\begin{enumerate}
+ \item A broken wallet withdraws a coin with denomination $D_{p_{(1)}}$.
+ \item The wallet sends a request to withdraw the same coin for denomination $D_{p_{(2)}}$.
+ \item The exchange returns the signature for the denomination $D_{p_{(1)}}$ due to the \gls{idempotence} check.
+ \item Since the exchange returned an invalid signature, the customer can file a complaint at the auditor.
+ \item The auditor then has to investigate why the exchange returned invalid signatures.
+ \item The auditor can disprove the complaint by querying the persisted hash used for the \gls{idempotence} check.
+ With the associated denomination public key that is also persisted, the auditor can successfully verify the signature and thus prove that the exchange operated honestly.
+\end{enumerate}
+
+Including the denomination public key into the persisted hash for the \gls{idempotence} check solves this issue.
+If a broken wallet now sends the same coin for more than one denomination, the exchange returns valid signatures in both cases.\\
+While this is still an issue, this case is already handled nicely in Taler since this situation could also occur if a broken value tries to withdraw the same coin with two different blinding factors.
+
+\subsection{Impact}
+The impact of this security vulnerability is considered as very low.
+An auditor investigating such an issue can simply retrace what happened by checking the persisted hash and associated denomination.
+The impact of the issue is, that an auditor needs to investigate an issue, which can be prevented inside the protocol.
+\\
+In the previous section the client was considered a broken wallet.
+While this could be done on purpose by malicious a customer, there is no real motivation for abusing this issue due the easy detection of an auditor.
+
+
+\subsection{Fix}
+Listing \ref{lst:rsa-idempotence} shows the code of calculating the hash for the idempotency check in the RSA case before it was fixed.
+By trying to implement the \gls{CSBS} case, the question came up why the RSA case has not included the denomination key into the check.
+After discussing this issue with Christian Grothoff, the conclusion was to include the denomination public key to prevent the discussed issue.
+
+\begin{lstlisting}[style=bfh-c,language=C, caption={Idempotency check on RSA}, label={lst:rsa-idempotence}]
+ enum GNUNET_GenericReturnValue
+ TALER_coin_ev_hash (const struct TALER_BlindedPlanchet *blinded_planchet,
+ struct TALER_BlindedCoinHash *bch)
+ {
+ switch (blinded_planchet->cipher)
+ {
+ case TALER_DENOMINATION_RSA:
+ GNUNET_CRYPTO_hash (
+ blinded_planchet->details.rsa_blinded_planchet.blinded_msg,
+ blinded_planchet->details.rsa_blinded_planchet.blinded_msg_size,
+ &bch->hash);
+ return GNUNET_OK;
+ case TALER_DENOMINATION_CS:
+ ...
+
+\end{lstlisting}
+
+The issue is fixed by adding a hash of the current denomination key into the calculation of the hash used in the \gls{idempotence} check.
+The applied fix can be seen in listing \ref{lst:fixed-idempotence}.
+
+\begin{lstlisting}[style=bfh-c,language=C, caption={Fixed idempotency check}, label={lst:fixed-idempotence}]
+ enum GNUNET_GenericReturnValue
+ TALER_coin_ev_hash (const struct TALER_BlindedPlanchet *blinded_planchet,
+ const struct TALER_DenominationHash *denom_hash,
+ struct TALER_BlindedCoinHash *bch)
+ {
+ switch (blinded_planchet->cipher)
+ {
+ case TALER_DENOMINATION_RSA:
+ {
+ struct GNUNET_HashContext *hash_context;
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &denom_hash->hash,
+ sizeof(denom_hash->hash));
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ blinded_planchet->details.
+ rsa_blinded_planchet.blinded_msg,
+ blinded_planchet->details.
+ rsa_blinded_planchet.blinded_msg_size);
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &bch->hash);
+ return GNUNET_OK;
+ }
+ case TALER_DENOMINATION_CS:
+ {
+ struct GNUNET_HashContext *hash_context;
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &denom_hash->hash,
+ sizeof(denom_hash->hash));
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &blinded_planchet->details.
+ cs_blinded_planchet.nonce,
+ sizeof (blinded_planchet->details.
+ cs_blinded_planchet.nonce));
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &bch->hash);
+ return GNUNET_OK;
+ }
+ default:
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+\end{lstlisting}
diff --git a/doc/cs/content/4_execution.tex b/doc/cs/content/4_execution.tex
new file mode 100644
index 000000000..675a33439
--- /dev/null
+++ b/doc/cs/content/4_execution.tex
@@ -0,0 +1,5 @@
+\input{content/4_1_design.tex}
+
+\input{content/4_2_specification.tex}
+
+\input{content/4_3_implementation.tex}
diff --git a/doc/cs/content/5_discussion.tex b/doc/cs/content/5_discussion.tex
new file mode 100644
index 000000000..8381273c1
--- /dev/null
+++ b/doc/cs/content/5_discussion.tex
@@ -0,0 +1,317 @@
+\chapter{Discussion}
+\label{chap:disc}
+This chapter analyses the \acl{CS} implementation and compares it to the existing implementation with \gls{RSABS}.
+The comparison will include the schemes itself, performance comparisons and a discussion on the security assumptions.
+For the performance comparison CPU usage, latency, bandwidth and storage space are compared.
+
+\section{Cipher Agility}
+One of the benefits of having another blind signature scheme in Taler is \textit{cipher agility}.
+Cipher agility means that one scheme can substitute another, for example if one scheme gets compromised in the future.
+
+Cipher agility is considered harmful in certain situations.
+TLS 1.2 \cite{rfc5246} and IPSEC/IKEv2 \cite{rfc6071} are good examples on how dangerous cipher agility inside protocols can be.
+There are many ways these protocols can be set up insecure.
+\\\\
+Taler's protocols are built around blind signature schemes.
+Therefore it is crucial to have an additional secure blind signature scheme that works with all Taler protocols.
+As described in section \ref{sec:blind-sign-schemes}, blind signature schemes can vary and may be complex to substitute.
+The \gls{CSBS} implementation provides such an alternative and thus \textit{cipher agility}.
+
+\section{Scheme Comparison}
+\label{chap:disc-scheme-comp}
+Both schemes are explained in the preliminaries chapter (\gls{RSABS} in section \ref{fig:rsa-fdh-blind-sign} and \gls{CSBS} in \ref{fig:clause-blind-schnorr-sign-scheme}).
+\\
+There are multiple differences worth mentioning.
+The first difference is that Schnorr signatures are inherently randomized.
+This is also where the additional step in Schnorr signatures comes from.
+A random number is chosen by the signer for every signature. \\
+In \gls{CSBS} two blinding secrets are used instead of one in \gls{RSABS}.
+On top of that, \gls{CSBS} needs to do most computations for signature creation twice, due to the \ac{ROS} problem (see \ref{par:ros-problem}).
+\\\\
+\textit{\Gls{abort-idempotency}} is a very important property for Taler.
+Ensuring \gls{abort-idempotency} with the \gls{CSBS} scheme is harder than it was with RSA, due to the many random elements in the scheme ($r_0, r_1, \alpha_0, \alpha_1, \beta_0, \beta_1, b$).
+The reason that these values are chosen randomly is the need for \textit{unpredictability}.\\
+In the protocols (see chapter \ref{chap:design}) \gls{hkdf} is extensively used to derive these values instead of randomly generating them.
+That way, the values are still \textit{unpredictable} (due to \gls{hkdf} properties), but now the protocols also ensure \textit{\gls{abort-idempotency}}.
+In comparison to the RSA Blind Signature scheme, this is a clever and elegant solution, but the protocol complexity is increased.
+\\\\
+One could now think that RSA would be much simpler to implement, since the scheme looks easier and more accessible for many.
+This can go horribly wrong and many developers still underestimate implementing RSA.
+There are a lot of attacks on RSA, some examples are listed on the famous tool RsaCtfTool \cite{ganapati:rsactftool}.
+Ben Perez made a popular talk and blog post, about why one should stop using RSA and should preferably use libsodium and \ac{ECC} \cite{perez:stoprsa}.
+Using \gls{RSABS} in Taler is still a reasonable and fine choice.
+Taler uses libgcrypt, a well-known and tested library.
+\\
+To conclude, the \gls{CSBS} protocols might be more complex to understand than the RSA Blind Signature protocols.
+One has to keep in mind that implementing RSA correctly is hard.
+\\
+Another difference worth mentioning is, that the \gls{CSBS} scheme does not need scheme specific configurations, whereas RSA needs a key size specified.
+This is because the implemented \gls{CSBS} version only supports \gls{25519}.
+\\\\
+Furthermore, both schemes provide \textit{perfect blindness}, see paragraph \ref{par:prop-blindness-rsa} for RSA and paragraph \ref{par:prop-blindness-cs} for \gls{CSBS}.
+
+
+\section{Performance Comparison}
+\label{sec:disc-perf-comp}
+This section compares how the two schemes perform regarding CPU usage, latency, bandwidth and space.
+Clause Schnorr has fixed key sizes with 256 bits (32 bytes), which we compare against different RSA key sizes (1024, 2048, 3072 and 4096 bits).
+In terms of security, \gls{CSBS} 256 bit keys could be compared to 3072 bit RSA keys (see \url{https://www.keylength.com/} for more information).
+
+\subsection{CPU Usage}
+Various benchmarks were made on different CPU architectures.
+This section discusses the main results, detailed information about the performance comparison can be found in appendix \ref{chap:app-perf}.
+We thank the Taler team for providing measurements from additional systems and architectures.
+
+Table \ref{tab:comp-cs-vs-rsa-3072} shows how \gls{CSBS} compares to RSA 3072.
+RSA 3072 was chosen for comparison, since they both provide a comparable level of security.
+Both provide about 128 bits of security, which means that roughly $2^{128}$ attempts in average are needed for a successful brute-force attack.\\
+The table shows that \gls{CSBS} has better performance compared to RSA 3072 in all operations.
+The biggest difference can be seen in the key generation.
+In RSA, two random primes are needed, whereas \ac{DLP} algorithms like \gls{CSBS} only need to generate a random value.
+Since key generation is done rarely compared to the other operations, the time needed for key generation does not matter that much.\\
+Furthermore, the blinding in \gls{CSBS} is still faster than blinding in RSA, although in the \gls{CSBS} case the calculation is done twice. Also the derivation of $r_0,r_1$, the generation of $R_0,R_1$ and the derivation of $\alpha_0, \beta_0, \alpha_1, \beta_1$ is included in the measurement for the blinding operation of \gls{CSBS}.
+Signing and blinding operations are much faster in \gls{CSBS}, also \gls{CSBS} signature verification is faster than RSA 3072.
+
+\begin{bfhBox}[BFH-MediumBlue]{Setup}
+ CPU: 8-core AMD Ryzen 7 PRO 5850U \\
+ OS: Ubuntu 21.10 Linux 5.13.0-25-generic \#26-Ubuntu SMP Fri Jan 7 15:48:31 UTC 2022 x86\_64 x86\_64 x86\_64 GNU/Linux \\
+ libsodium version: 1.0.18-1build1 \\
+ libgcrypt version: 1.8.7-5ubuntu2 \\\\
+ Benchmarks with other hardware setups can be found in appendix \ref{chap:app-perf}.
+\end{bfhBox}
+
+\begin{table}[h]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{llr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Operation} & \textbf{Speed} \\\hline
+ CS & 10x key generation & 0.204 ms \\\hline
+ RSA 3072 bit & 10x key generation & 2684 ms \\\hline
+ \hline
+ CS & 10x derive $R_0, R_1$ \& blinding & 3.870 ms \\\hline
+ RSA 3072 bit & 10x blinding & 5 ms \\\hline
+ \hline
+ CS & 10x signing & 0.077 ms \\\hline
+ RSA 3072 bit & 10x signing & 86 ms \\\hline
+ \hline
+ CS & 10x unblinding & 0.001 ms \\\hline
+ RSA 3072 bit & 10x unblinding & 24 ms \\\hline
+ \hline
+ CS & 10x verifying & 1.358 ms \\\hline
+ RSA 3072 bit & 10x verifying & 3.075 ms \\\hline
+ \end{tabular}
+ \caption{Comparison on CS vs. RSA 3072}
+ \label{tab:comp-cs-vs-rsa-3072}
+\end{table}
+
+Table \ref{tab:comp-cs-vs-rsa-1024} shows a comparison between \gls{CSBS} and RSA 1024 bit.
+RSA 1024 is in some situations faster than the \gls{CSBS} implementation.
+Note that 1024 bit keys are not recommended for many use cases, but the highest currently known RSA factorization done is 829 bits \cite{enwiki:1055393696}.
+The following section \ref{sec:disc-risk} explains the risk running RSA 1024 or \gls{CSBS} denominations further.\\
+The blind and unblind operations are running in a wallet implementation, therefore the comparison with RSA 1024 is very interesting for devices with less CPU power.
+Comparison of such hardware can be found in appendix \ref{chap:app-perf}, these comparison results come to the same conclusion.\\
+Although RSA 1024 bit is much faster in the blinding operation, \gls{CSBS} still perform better when calculating the blinding and unblinding operations together.
+\gls{CSBS} unblinding computes only an addition of two scalars $s + \alpha \mod p$, while RSA computes $s * r^{-1}$.
+To conclude, \gls{CSBS} are faster than RSA 1024 bit and provide a better level of security.
+This can be especially useful for wallets running on devices with less CPU power.
+The verification on RSA 1024 is faster than \gls{CSBS}.
+Therefore, it has to be further investigated which algorithm would overall perform better for the exchange or merchants.
+While RSA 1024 bit can compete in certain operations, \gls{CSBS} provide a better level of security and are still faster in most operations.
+
+\begin{table}[h]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{llr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Operation} & \textbf{Speed} \\\hline
+ CS & 10x key generation & 0.204 ms \\\hline
+ RSA 1024 bit & 10x key generation & 126 ms \\\hline
+ \hline
+ CS & 10x derive $R_0, R_1$ \& blinding & 3.870 ms \\\hline
+ RSA 1024 bit & 10x blinding & 1.282 ms \\\hline
+ \hline
+ CS & 10x signing & 0.077 ms \\\hline
+ RSA 1024 bit & 10x signing & 7 ms \\\hline
+ \hline
+ CS & 10x unblinding & 0.001 ms \\\hline
+ RSA 1024 bit & 10x unblinding & 2.991 ms \\\hline
+ \hline
+ CS & 10x verifying & 1.358 ms \\\hline
+ RSA 1024 bit & 10x verifying & 0.876 ms \\\hline
+ \end{tabular}
+ \caption{Comparison on CS vs RSA 1024}
+ \label{tab:comp-cs-vs-rsa-1024}
+\end{table}
+\subsection{Disk Space}
+\begin{bfhWarnBox}
+ These are theoretical calculations, implementations may choose to persist additional values.
+ \end{bfhWarnBox}
+\gls{CSBS} save disk space due to the much smaller key sizes.
+Even more disk space is saved by deriving values with the \gls{hkdf}, these values do not have to be stored.
+\\
+Table \ref{tab:comp-sign-space} shows the disk space comparison of signatures, the private keys alone need even less space with 256 bits per key.
+\\
+The wallet saves a lot of disk space by deriving most of the values.
+In the \gls{CSBS} case a wallet must at least persist the private key $c_s$, $R_0, R_1, s', D_p$, each being 256 bits (32 bytes).
+A wallet needs to persist 150 bytes per coin in total.
+In the RSA Blind Signature case the wallet persists $c_s$, $b$, $\sigma_c$, $D_p$.
+\\Note: for refreshed coins an additional 32 byte value is persisted as seed.\\
+$c_s$ is still a 32 byte value in the RSA case, the other values depend on the RSA key size. (32 byte + 3 * \textit{rsa\_keysize}).
+The disk space comparison for a wallet can be found in \ref{tab:comp-wallet-space}.
+
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lccr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Disk Space} & \textbf{Factor} & \textbf{Disk Space 1M signatures}\\\hline
+ CS & 512 bits & 1x & 64 MB\\\hline
+ RSA 1024 bit & 1024 bits & 2x & 128 MB \\\hline
+ RSA 2048 bit & 2048 bits & 4x & 256 MB\\\hline
+ RSA 3072 bit & 3072 bits & 6x & 384 MB\\\hline
+ RSA 4096 bit & 4096 bits & 8x & 512 MB\\\hline
+ \end{tabular}
+ \caption{Comparison disk space signatures}
+ \label{tab:comp-sign-space}
+\end{table}
+
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lccr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Disk Space} & \textbf{Factor} & \textbf{Disk Space 1M coins}\\\hline
+ CS 256 bits & 150 bytes & 1x & 150 MB\\\hline
+ RSA 1024 bit & 416 bytes & 2.7x & 416 MB \\\hline
+ RSA 2048 bit & 800 bytes & 5.3x & 800 MB\\\hline
+ RSA 3072 bit & 1184 bytes & 7.9x & 1184 MB\\\hline
+ RSA 4096 bit & 1568 bytes & 10.4x & 1568 MB\\\hline
+ \end{tabular}
+ \caption{Comparison disk space wallet}
+ \label{tab:comp-wallet-space}
+\end{table}
+
+\subsection{Bandwidth}
+\begin{bfhWarnBox}
+ These are theoretical calculations, implementations may choose to persist additional values.
+\end{bfhWarnBox}
+The reasons that \gls{CSBS} use less bandwidth is mostly because the signature/key sizes are much smaller.
+The bandwidth improvements for the \texttt{/keys} API is the same as specified in the table with disk space comparison \ref{tab:comp-sign-space}.
+For \gls{CSBS} many calculations are performed twice, therefore also two values are submitted.
+Table \ref{tab:comp-band-withd} compares the bandwidth used in a withdrawal.
+The 32 byte values $2 * n_w, 2 * D_p, R_0, R_1, s,W_p, c_0, c_1, \sigma_W$ as well as an integer $b$ are transmitted for \gls{CSBS}.\\
+For RSA, the values $D_p, m', \sigma'_c$ have the same size as the key size.
+Additionally, the 32 byte values $W_p, \sigma_W$ are transmitted.
+\\\\
+In the refresh protocol the only difference is an additional hash ($h_{C_0}, h_{C_1}$ instead of only $h_C$) sent in the commit phase.
+Depending on the hash size another 32 byte (or 64 byte) value is transmitted.
+
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lccr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Bandwidth used} & \textbf{Factor} & \textbf{1M coins}\\\hline
+ CS 256 bits & 356 bytes & 1x & 324 MB\\\hline
+ RSA 1024 bit & 448 bytes & 1.3x & 448 MB \\\hline
+ RSA 2048 bit & 832 bytes & 2.5x & 832 MB\\\hline
+ RSA 3072 bit & 1216 bytes & 3.75x & 1216 MB\\\hline
+ RSA 4096 bit & 1600 bytes & 4.9x & 1600 MB\\\hline
+ \end{tabular}
+ \caption{Bandwidth comparison withdrawal}
+ \label{tab:comp-band-withd}
+\end{table}
+
+\subsection{Latency}
+This section the notion of \acl{RTT} (see \cite{geeks:rtt}) is used.
+There are many factors that influence the measurement of a \acl{RTT}.
+Following factors can bring huge changes in the value of \ac{RTT}s.
+\begin{itemize}
+ \item Distance
+ \item Transmission medium
+ \item Network hops
+ \item Traffic levels
+ \item Server response time
+\end{itemize}
+All of these factors will vary in reality and are independent of the scheme.\\
+The important comparison here is the number of \ac{RT}s as in table \ref{tab:comp-rtt}.
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lc}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Number of RTs} \\\hline
+ RSA Blind Signatures & 1\\\hline
+ Clause Blind Schnorr Signatures & 2\\\hline
+ \end{tabular}
+ \caption{Comparison of Round-Trips}
+ \label{tab:comp-rtt}
+\end{table}
+
+While creating \gls{RSABS} have one \ac{RT}, \gls{CSBS} need an additional \ac{RT} for requesting the public $R_0, R_1$.
+This means that the time spend for withdrawing is almost \textbf{doubled} (the $ R $ request doesn't have any persistence and therefore requires less time) in comparison to RSA.
+
+A coin should not be spent immediately after withdrawal or refresh.
+Otherwise, an adversary could deanonymize a customer by correlating the timestamps.
+The additional \ac{RT} is a drawback of \gls{CSBS} compared to RSA, but negligible due to the fact that a coin must not be spent immediately.
+
+\section{Security Assumptions}
+\label{sec:disc-sec-assumptions}
+This section discusses the differences regarding the security assumptions of the schemes.
+This section should not explain nor analyze the security assumptions, instead the section focuses on explaining what these assumptions mean and what should be kept in mind about them.
+Read section \ref{sec:sign-schemes} and it's references for more information on the assumptions.
+
+RSA's security assumptions are well known since quite a long time and a lot of research is done.
+Despite being a lot of attacks \cite{ganapati:rsactftool} \cite{perez:stoprsa}, RSA is still considered a secure scheme after decades.\\
+For Schnorr Signatures the \acl{DLP} (see \autoref{sec:dlp}) needs to be hard.
+Also the \ac{DLP} is well-known and is being researched since decades.\\
+However, with Blind Schorr Signatures an additional assumption needs to hold; the \ac{ROS} problem.
+Compared to the other assumptions, \ac{ROS} is relatively new and still a recent research topic.
+A recent paper from 2020 on the (in)security of ROS \cite{cryptoeprint:2020:945} broke many schemes relying on ROS being hard, including Schnorr Blind signatures.
+The paper on which we rely on (updated in 2021) with the Clause Blind Schnorr Signature Scheme \cite{cryptoeprint:2019:877} is considered secure at the time of writing.\\
+
+\section{Risk}
+\label{sec:disc-risk}
+As introduced in \autoref{sec:disc-sec-assumptions}, \gls{CSBS} rely on an additional assumption currently being researched.
+Compared to other schemes, the chosen \gls{CSBS} are very new (published in 2019, updated in 2021).
+While every scheme could potentially be broken, older ones already went through a lot of research and their assumptions are well-known.
+Therefore, the risk that a vulnerability in \gls{CSBS} will be discovered is probably higher than a newly discovered vulnerability breaking RSA.
+
+Unpredictability of $ r $ is a key aspect of the signature creation process of \gls{CSBS}.
+The redesigned Taler protocols solve this by persisting the nonce and denomination key (described in \autoref{sec:withdraw-protocol-impl}) and checking for reuse of this combination before signature creation.
+If this process is malfunctioning (broken implementation, faulty database) or can be circumvented in any way, recovery of a denomination private key is possible.
+
+An exchange operator can still consider using \gls{CSBS} as denomination scheme, as there are multiple benefits (see \autoref{sec:disc-perf-comp}).
+The financial loss in the worst case can be calculated and capped by the validity of a denomination key.
+If a vulnerability in the \gls{CSBS} would be detected, an exchange operator could revoke the corresponding denomination keys and change the scheme to \gls{RSABS}.
+The wallets can then follow the refund protocol to get the money back.
+
+\section{Comparison Conclusion}
+\label{sec:disc-comp-conclusion}
+A detailed comparison of the two blind signature schemes was made.
+This last section interprets the results and concludes the comparison.
+
+\gls{CSBS} on \gls{25519} provide the same security level as \gls{RSABS} with 3072 bit key sizes.
+The implementation of \gls{CSBS} is the clear winner in all performance comparisons with RSA 3072 bits.
+
+1024 bit RSA is faster than the \gls{CSBS} implementation in certain operations.
+The \gls{CSBS} implementation still offers better performance for wallets with less CPU power and provides a much higher level of security (comparable to RSA 3072).
+As further comparisons show, RSA scales very bad the larger the keys get and \gls{CSBS} performs much better overall.
+
+As discussed in the risk section \ref{sec:disc-risk}, \gls{CSBS} have an additional security assumption, which is still a recent research topic.
+\gls{CSBS} provide various benefits and the risk can be calculated and capped.
+An exchange operator who is aware of the discussed risk can use \gls{CSBS} safely.
+\gls{CSBS} are best suited for denominations with low value, where many coins are being withdrawn/refreshed.
diff --git a/doc/cs/content/6_conclusion.tex b/doc/cs/content/6_conclusion.tex
new file mode 100644
index 000000000..8ee12fa5e
--- /dev/null
+++ b/doc/cs/content/6_conclusion.tex
@@ -0,0 +1,70 @@
+\chapter{Conclusion}
+This section provides a summary of this work, presents the results and gives an outlook on future work.
+
+\section{Summary}
+In the beginning of the project good knowledge on the current state in research about Blind Schnorr signatures was needed.
+Therefore, various papers were read and then the paper "Blind Schnorr Signatures and Signed ElGamal Encryption in the Algebraic Group Model" \cite{cryptoeprint:2019:877} was chosen as basis for the redesign of the Taler protocols.\\
+The next step was to analyze the current Taler protocols and understand the required properties including \textit{\gls{abort-idempotency}}.\\
+With the gathered knowledge (see chapter \ref{chap:preliminaries}) the Taler protocols were redesigned to support \gls{CSBS} (see chapter \ref{chap:design}).
+These redesigned protocols were then further specified (chapter \ref{chap:spec}) and then implemented (chapter \ref{chap:implement}).
+The implementation includes the main protocols, key management, cryptographic utilities in Taler and the \gls{CSBS} cryptographic routines.\\
+The \gls{CSBS} scheme was analyzed and compared in detail to the RSA Blind Signature scheme (see \ref{chap:disc}).
+
+
+\section{Results}
+The thesis provides several results to add support for Schnorr's blind signature in Taler, including:
+\begin{itemize}
+ \item Redesigned Taler protocols to support \gls{CSBS}
+ \item Implementation of cryptographic routines
+ \item Implementation of Taler protocols in Exchange
+ \begin{itemize}
+ \item Key Management and security module
+ \item Cryptographic utilities
+ \item Withdraw protocol
+ \item Deposit protocol
+ \end{itemize}
+ \item Comparison and Analysis
+ \begin{itemize}
+ \item Performance (speed, space, latency \& bandwidth)
+ \item Security
+ \item Scheme Comparison
+ \end{itemize}
+ \item Fixing a minor security issue in Taler's current protocols
+\end{itemize}
+The code is tested, and those tests are integrated in the existing testing framework.
+Benchmarks are added for the cryptographic routines and the security module.
+
+\section{Future Work}
+Like in any other project, there is always more that could be done.
+This section provides an outlook on what can be done in future work.
+
+\begin{itemize}
+ \item Implement wallet
+ \item Implementing remaining \gls{CSBS} protocols (refresh, tipping protocol, refund etc.)
+ \item Implementing merchant
+ \item Security audit of CS implementation
+ \item Find a solution for withdraw loophole
+ \item Evaluating \& implementing \gls{CSBS} on other curves
+\end{itemize}
+
+There are some remaining protocols to implement, which were out of scope for this thesis.
+To run \gls{CSBS} in production, these protocols have to be implemented too.
+Further, the merchant needs to support \gls{CSBS} too.
+The merchant implementation can be done fast, as the merchant only verifies denomination signatures in most cases. \\
+Currently, the exchange runs both security modules, the \gls{CSBS} and the RSA security modules.
+To reduce unnecessary overhead, this should be changed so that only one security has to be running.
+To run \gls{CSBS} in production a security audit from an external company is recommended (as done for other parts in the exchange, see \cite{codeblau:taler-audit}).
+A security audit should always be made when implementing big changes like these.\\
+As mentioned in the scope section, the optional goal to find and implement a good solution for the withdraw loophole was dropped.
+This was due to the scope shift and because the analysis of the problem showed that finding a good solution needs more research and is a whole project in itself (see \ref{sec:scope} for more information).\\
+Furthermore, \gls{CSBS} could be implemented on other curves.
+For example Curve448 \cite{cryptoeprint:2015:625} could be used, as it provides 224 bits of security, whereas \gls{25519} \cite{bern:curve25519} provides about 128 bits of security.
+Curve secp256k1 could further improve \gls{CSBS} performance.
+While providing support for Curve448 should not be problematic, a potential implementation for secp256k1 needs further analysis (see \cite{bernlange:safecurves} and \cite{bip:schnorr-bitc} for more information).
+
+\section{Personal Conclusion}
+This thesis includes understanding, analyzing, integrating and implementing a recent academic paper \cite{cryptoeprint:2019:877} containing a modern cryptographic scheme.
+Furthermore, the implementation is done in Taler, an intuitive and modern solution for a social responsible payment system with high ethical standards.
+Although there was a lot of work, we enjoyed working on such a modern and very interesting topic.
+Especially the first successful signature verification and the signature scheme performance benchmarks motivated us to push the implementation and integration into Taler forward.\\
+We are happy to provide an implementation of a modern scheme and making it available as free software.
diff --git a/doc/cs/content/appendix.tex b/doc/cs/content/appendix.tex
new file mode 100644
index 000000000..137a29ba7
--- /dev/null
+++ b/doc/cs/content/appendix.tex
@@ -0,0 +1,677 @@
+\appendix
+
+\chapter{Installation}
+These installation instructions are meant to run the code developed within this thesis for development- and review-purposes.
+For a comprehensive installation instruction follow the Taler documentation
+\cite{taler-documentation}.
+
+\begin{bfhNoteBox}
+ These instructions are used and tested on Ubuntu 21.10.
+\end{bfhNoteBox}
+
+\section{Dependencies and Setup}
+The following dependencies need to be installed for GNUnet and Taler Exchange:
+\setupLinuxPrompt{student}
+\begin{ubuntu}
+sudo apt update
+sudo apt install git curl build-essential gcc automake make \ texinfo autoconf uncrustify libtool pkgconf gettext gnutls-bin \ libcurl4-gnutls-dev libgcrypt20-dev libidn2-dev libjansson-dev \ libnss3-dev sqlite pipenv libltdl-dev libsodium-dev libpq-dev \ autopoint libunistring-dev libextractor-dev libpng-dev \ libpulse-dev libsqlite3-dev recutils python3-jinja2 sqlite yapf3 \ postgresql libpq-dev wget libmicrohttpd-dev
+export LD\_LIBRARY\_PATH=/usr/local/lib
+\end{ubuntu}
+
+\begin{bfhBox}[BFH-MediumBlue]{Install in a container}
+The installation can also be done in a docker or podman container with the ubuntu:21.10 image:
+\setupLinuxPrompt{student}
+\begin{ubuntu}
+podman run -it --name talertest ubuntu:21.10
+\end{ubuntu}
+\end{bfhBox}
+
+\section{Install GNUnet Core}
+GNUnet core is both a dependency of the Taler exchange and where we implemented the Clause Blind Schnorr Signature Scheme.
+\setupLinuxPrompt{student}
+\begin{ubuntu}
+git clone https://git.gnunet.org/gnunet.git
+cd gnunet
+./bootstrap
+./configure --enable-benchmarks --prefix=/usr/local
+make
+make install
+make check # Run optionally to verify installation and run tests
+\end{ubuntu}
+
+To run benchmarks run:
+\setupLinuxPrompt{student}
+\begin{ubuntu}
+./src/util/perf_crypto_cs
+./src/util/perf_crypto_rsa
+\end{ubuntu}
+
+\section{Install Taler Exchange}
+\begin{bfhWarnBox}
+Ensure that the current user has privileges in postgresql.
+One possible way to do this is:\\
+(where [user] has to be replaced with the name of the system user running the tests)
+\setupLinuxPrompt{student}
+\begin{ubuntu}
+service postgresql start
+sudo su
+su - postgres
+psql
+CREATE ROLE [user] LOGIN SUPERUSER;
+CREATE DATABASE [user] OWNER [user];
+exit
+\end{ubuntu}
+\end{bfhWarnBox}
+
+The Taler exchange can be installed as followed:
+\setupLinuxPrompt{student}
+\begin{ubuntu}
+service postgresql start
+createdb talercheck
+git clone https://git.taler.net/exchange.git
+cd exchange
+./bootstrap
+./configure --with-gnunet=/usr/local --prefix=/usr/local
+./make
+./make install
+./make check # Run optionally to verify installation and run tests
+\end{ubuntu}
+
+To execute the security module benchmarks run:
+\setupLinuxPrompt{student}
+\begin{ubuntu}
+cd src/util
+./test_helper_cs
+./test_helper_rsa
+\end{ubuntu}
+
+\chapter{Performance Measurements}
+\label{chap:app-perf}
+
+\section{AMD Ryzen 7 PRO 5850U (Notebook)}
+Detailed comparison of each operation can be found in table \ref{tab:comp-sign-amd-ryzen-7}.
+\begin{bfhBox}[BFH-MediumBlue]{Setup}
+ CPU: 8-core AMD Ryzen 7 PRO 5850U \\
+ Architecture: amd64 \\
+ OS: Ubuntu 21.10 Linux 5.13.0-25-generic \#26-Ubuntu SMP Fri Jan 7 15:48:31 UTC 2022 x86\_64 x86\_64 x86\_64 GNU/Linux \\
+ libsodium:amd64 version: 1.0.18-1build1 \\
+ libgcrypt:amd64 version: 1.8.7-5ubuntu2
+\end{bfhBox}
+
+\begin{table}[h]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{llr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Operation} & \textbf{Speed} \\\hline
+ CS & 10x key generation & 0.204 ms \\\hline
+ RSA 1024 bit & 10x key generation & 126 ms \\\hline
+ RSA 2048 bit & 10x key generation & 903 ms \\\hline
+ RSA 3072 bit & 10x key generation & 2684 ms \\\hline
+ RSA 4096 bit & 10x key generation & 10 000 ms \\\hline
+ \hline
+ CS & 10x r0, r1 derive and R1,R2 calculation & 0.444 ms \\\hline
+ CS & 10x derivation of blinding secrets & 0.094 ms \\\hline
+ CS & 10x blinding & 3.332 ms \\\hline
+ RSA 1024 bit & 10x blinding & 1.282 ms \\\hline
+ RSA 2048 bit & 10x blinding & 3.012 ms \\\hline
+ RSA 3072 bit & 10x blinding & 5 ms \\\hline
+ RSA 4096 bit & 10x blinding & 9 ms \\\hline
+ \hline
+ CS & 10x signing & 0.077 ms \\\hline
+ RSA 1024 bit & 10x signing & 7 ms \\\hline
+ RSA 2048 bit & 10x signing & 34 ms \\\hline
+ RSA 3072 bit & 10x signing & 86 ms \\\hline
+ RSA 4096 bit & 10x signing & 183 ms \\\hline
+ \hline
+ CS & 10x unblinding & 0.001 ms \\\hline
+ RSA 1024 bit & 10x unblinding & 2.991 ms \\\hline
+ RSA 2048 bit & 10x unblinding & 10 ms \\\hline
+ RSA 3072 bit & 10x unblinding & 24 ms \\\hline
+ RSA 4096 bit & 10x unblinding & 44 ms \\\hline
+ \hline
+ CS & 10x verifying & 1.358 ms \\\hline
+ RSA 1024 bit & 10x verifying & 0.876 ms \\\hline
+ RSA 2048 bit & 10x verifying & 1.836 ms \\\hline
+ RSA 3072 bit & 10x verifying & 3.075 ms \\\hline
+ RSA 4096 bit & 10x verifying & 5 ms \\\hline
+ \end{tabular}
+ \caption{Comparison on AMD Ryzen 7}
+ \label{tab:comp-sign-amd-ryzen-7}
+\end{table}
+
+\section{Intel(R) Core(TM) i7-8565U}
+Detailed comparison of each operation can be found in table \ref{tab:comp-sign-intel-i7}.
+\begin{bfhBox}[BFH-MediumBlue]{Setup}
+ CPU: 8-core Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz \\
+ Architecture: amd64 \\
+ OS: Ubuntu 21.10 Linux 5.13.0-25-generic \#26-Ubuntu SMP Fri Jan 7 15:48:31 UTC 2022 x86\_64 x86\_64 x86\_64 GNU/Linux \\
+ libsodium:amd64 version: 1.0.18-1build1 \\
+ libgcrypt:amd64 version: 1.8.7-5ubuntu2
+\end{bfhBox}
+
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{llr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Operation} & \textbf{Speed} \\\hline
+ CS & 10x key generation & 1.05 ms \\\hline
+ RSA 1024 bit & 10x key generation & 189 ms \\\hline
+ RSA 2048 bit & 10x key generation & 1555 ms \\\hline
+ RSA 3072 bit & 10x key generation & 5000 ms \\\hline
+ RSA 4096 bit & 10x key generation & 11 000 ms \\\hline
+ \hline
+ CS & 10x r0, r1 derive and R1,R2 calculation & 2.261 ms \\\hline
+ CS & 10x derivation of blinding secrets & 0.521 ms \\\hline
+ CS & 10x blinding & 13 ms \\\hline
+ RSA 1024 bit & 10x blinding & 2.6 ms \\\hline
+ RSA 2048 bit & 10x blinding & 4.12 ms \\\hline
+ RSA 3072 bit & 10x blinding & 7 ms \\\hline
+ RSA 4096 bit & 10x blinding & 11 ms \\\hline
+ \hline
+ CS & 10x signing & 0.405 ms \\\hline
+ RSA 1024 bit & 10x signing & 9 ms \\\hline
+ RSA 2048 bit & 10x signing & 44 ms \\\hline
+ RSA 3072 bit & 10x signing & 108 ms \\\hline
+ RSA 4096 bit & 10x signing & 216 ms \\\hline
+ \hline
+ CS & 10x unblinding & 0.005 ms \\\hline
+ RSA 1024 bit & 10x unblinding & 3.353 ms \\\hline
+ RSA 2048 bit & 10x unblinding & 12 ms \\\hline
+ RSA 3072 bit & 10x unblinding & 27 ms \\\hline
+ RSA 4096 bit & 10x unblinding & 47 ms \\\hline
+ \hline
+ CS & 10x verifying & 4.413 ms \\\hline
+ RSA 1024 bit & 10x verifying & 1.202 ms \\\hline
+ RSA 2048 bit & 10x verifying & 2.304 ms \\\hline
+ RSA 3072 bit & 10x verifying & 4.094 ms \\\hline
+ RSA 4096 bit & 10x verifying & 6 ms \\\hline
+ \end{tabular}
+ \caption{Comparison on Intel(R) Core(TM) i7-8565U}
+ \label{tab:comp-sign-intel-i7}
+\end{table}
+
+\section{AMD Ryzen Threadripper 1950X 16-Core Processor}
+Detailed comparison of each operation can be found in table \ref{tab:comp-sign-amd-threadripper}.
+\begin{bfhBox}[BFH-MediumBlue]{Setup}
+ CPU: AMD Ryzen Threadripper 1950X 16-Core Processor \\
+ Architecture: amd64 \\
+ OS: Linux 5.13.0-trunk-amd64 \#1 SMP Debian 5.13.12-1~exp1
+ (2021-08-20) x86\_64 GNU/Linux \\
+ libsodium:amd64 version: 1.9.4-5 \\
+ libgcrypt:amd64 version: 1.0.18-1
+\end{bfhBox}
+
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{llr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Operation} & \textbf{Speed} \\\hline
+ CS & 10x key generation & 0.442 ms \\\hline
+ RSA 1024 bit & 10x key generation & 145 ms \\\hline
+ RSA 2048 bit & 10x key generation & 1167 ms \\\hline
+ RSA 3072 bit & 10x key generation & 6000 ms \\\hline
+ RSA 4096 bit & 10x key generation & 11 000 ms \\\hline
+ \hline
+ CS & 10x r0, r1 derive and R1,R2 calculation & 1.043 ms \\\hline
+ CS & 10x derivation of blinding secrets & 0.242 ms \\\hline
+ CS & 10x blinding & 7 ms \\\hline
+ RSA 1024 bit & 10x blinding & 2.258 ms \\\hline
+ RSA 2048 bit & 10x blinding & 4.744 ms \\\hline
+ RSA 3072 bit & 10x blinding & 9 ms \\\hline
+ RSA 4096 bit & 10x blinding & 14 ms \\\hline
+ \hline
+ CS & 10x signing & 0.270 ms \\\hline
+ RSA 1024 bit & 10x signing & 10 ms \\\hline
+ RSA 2048 bit & 10x signing & 47 ms \\\hline
+ RSA 3072 bit & 10x signing & 119 ms \\\hline
+ RSA 4096 bit & 10x signing & 248 ms \\\hline
+ \hline
+ CS & 10x unblinding & 0.003 ms \\\hline
+ RSA 1024 bit & 10x unblinding & 4.086 ms \\\hline
+ RSA 2048 bit & 10x unblinding & 14 ms \\\hline
+ RSA 3072 bit & 10x unblinding & 34 ms \\\hline
+ RSA 4096 bit & 10x unblinding & 60 ms \\\hline
+ \hline
+ CS & 10x verifying & 2.392 ms \\\hline
+ RSA 1024 bit & 10x verifying & 1.137 ms \\\hline
+ RSA 2048 bit & 10x verifying & 2.797 ms \\\hline
+ RSA 3072 bit & 10x verifying & 5 ms \\\hline
+ RSA 4096 bit & 10x verifying & 7 ms \\\hline
+ \end{tabular}
+ \caption{Comparison on AMD Ryzen Threadripper 1950X}
+ \label{tab:comp-sign-amd-threadripper}
+\end{table}
+
+\section{Intel(R) Xeon(R) CPU E5-2630}
+Detailed comparison of each operation can be found in table \ref{tab:comp-sign-intel-xeon}.
+\begin{bfhBox}[BFH-MediumBlue]{Setup}
+ CPU: Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz \\
+ Architecture: amd64 \\
+ OS: Linux 5.10.0-8-amd64 \#1 SMP Debian 5.10.46-4 (2021-08-03) x86\_64\\
+ libsodium:amd64 version: 1.0.18-1\\
+ libgcrypt:amd64 version: 1.8.7-6
+\end{bfhBox}
+
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{llr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Operation} & \textbf{Speed} \\\hline
+ CS & 10x key generation & 0.606 ms \\\hline
+ RSA 1024 bit & 10x key generation & 329 ms \\\hline
+ RSA 2048 bit & 10x key generation & 3210 ms \\\hline
+ RSA 3072 bit & 10x key generation & 12 000 ms \\\hline
+ RSA 4096 bit & 10x key generation & 40 000 ms \\\hline
+ \hline
+ CS & 10x r0, r1 derive and R1,R2 calculation & 1.527 ms \\\hline
+ CS & 10x derivation of blinding secrets & 0.329 ms \\\hline
+ CS & 10x blinding & 9 ms \\\hline
+ RSA 1024 bit & 10x blinding & 4.026 ms \\\hline
+ RSA 2048 bit & 10x blinding & 9 ms \\\hline
+ RSA 3072 bit & 10x blinding & 18 ms \\\hline
+ RSA 4096 bit & 10x blinding & 27 ms \\\hline
+ \hline
+ CS & 10x signing & 0.274 ms \\\hline
+ RSA 1024 bit & 10x signing & 21 ms \\\hline
+ RSA 2048 bit & 10x signing & 96 ms \\\hline
+ RSA 3072 bit & 10x signing & 237 ms \\\hline
+ RSA 4096 bit & 10x signing & 482 ms \\\hline
+ \hline
+ CS & 10x unblinding & 0.004 ms \\\hline
+ RSA 1024 bit & 10x unblinding & 7 ms \\\hline
+ RSA 2048 bit & 10x unblinding & 25 ms \\\hline
+ RSA 3072 bit & 10x unblinding & 58 ms \\\hline
+ RSA 4096 bit & 10x unblinding & 99 ms \\\hline
+ \hline
+ CS & 10x verifying & 4.334 ms \\\hline
+ RSA 1024 bit & 10x verifying & 2.190 ms \\\hline
+ RSA 2048 bit & 10x verifying & 5 ms \\\hline
+ RSA 3072 bit & 10x verifying & 11 ms \\\hline
+ RSA 4096 bit & 10x verifying & 14 ms \\\hline
+ \end{tabular}
+ \caption{Comparison on Intel(R) Xeon(R) CPU E5-2630}
+ \label{tab:comp-sign-intel-xeon}
+\end{table}
+
+\section{Intel(R) Pentium(R) 3558U}
+Detailed comparison of each operation can be found in table \ref{tab:comp-sign-intel-pentium}.
+\begin{bfhBox}[BFH-MediumBlue]{Setup}
+ CPU: Intel(R) Pentium(R) 3558U @ 1.70GHz \\
+ Architecture: amd64 \\
+ OS: Linux 5.10.0-8-amd64 \#1 SMP Debian 5.10.46-3 (2021-07-28) x86\_64\\
+ libsodium:amd64 version: 1.0.18-1\\
+ libgcrypt:amd64 version: 1.8.7-6
+\end{bfhBox}
+
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{llr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Operation} & \textbf{Speed} \\\hline
+ CS & 10x key generation & 0.53 ms \\\hline
+ RSA 1024 bit & 10x key generation & 524 ms \\\hline
+ RSA 2048 bit & 10x key generation & 3357 ms \\\hline
+ RSA 3072 bit & 10x key generation & 15 000 ms \\\hline
+ RSA 4096 bit & 10x key generation & 37 000 ms \\\hline
+ \hline
+ CS & 10x r0, r1 derive and R1,R2 calculation & 1.375 ms \\\hline
+ CS & 10x derivation of blinding secrets & 0.349 ms \\\hline
+ CS & 10x blinding & 8 ms \\\hline
+ RSA 1024 bit & 10x blinding & 4.86 ms \\\hline
+ RSA 2048 bit & 10x blinding & 11 ms \\\hline
+ RSA 3072 bit & 10x blinding & 19 ms \\\hline
+ RSA 4096 bit & 10x blinding & 31 ms \\\hline
+ \hline
+ CS & 10x signing & 0.283 ms \\\hline
+ RSA 1024 bit & 10x signing & 26 ms \\\hline
+ RSA 2048 bit & 10x signing & 117 ms \\\hline
+ RSA 3072 bit & 10x signing & 292 ms \\\hline
+ RSA 4096 bit & 10x signing & 571 ms \\\hline
+ \hline
+ CS & 10x unblinding & 0.003 ms \\\hline
+ RSA 1024 bit & 10x unblinding & 8 ms \\\hline
+ RSA 2048 bit & 10x unblinding & 30 ms \\\hline
+ RSA 3072 bit & 10x unblinding & 67 ms \\\hline
+ RSA 4096 bit & 10x unblinding & 111 ms \\\hline
+ \hline
+ CS & 10x verifying & 3.769 ms \\\hline
+ RSA 1024 bit & 10x verifying & 2.616 ms \\\hline
+ RSA 2048 bit & 10x verifying & 6 ms \\\hline
+ RSA 3072 bit & 10x verifying & 11 ms \\\hline
+ RSA 4096 bit & 10x verifying & 17 ms \\\hline
+ \end{tabular}
+ \caption{Comparison on Intel(R) Pentium(R) 3558U}
+ \label{tab:comp-sign-intel-pentium}
+\end{table}
+
+
+\section{arm64}
+Detailed comparison of each operation can be found in table \ref{tab:comp-sign-arm64}.
+\begin{bfhBox}[BFH-MediumBlue]{Setup}
+ CPU: 8-core arm64\\
+ Architecture: ARM64 \\
+ OS: Linux ten64 5.11.0-31-generic \#33+testsfp1 SMP Mon Aug 23 16:07:41 UTC 2021 aarch64 aarch64 aarch64 GNU/Linux \\
+ libsodium:arm64 version: 1.8.7-2ubuntu2.1 \\
+ libgcrypt:arm64 version: 1.0.18-1
+\end{bfhBox}
+
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{llr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Operation} & \textbf{Speed} \\\hline
+ CS & 10x key generation & 2.896 ms \\\hline
+ RSA 1024 bit & 10x key generation & 839 ms \\\hline
+ RSA 2048 bit & 10x key generation & 8000 ms \\\hline
+ RSA 3072 bit & 10x key generation & 17 000 ms \\\hline
+ RSA 4096 bit & 10x key generation & 82 000 ms \\\hline
+ \hline
+ CS & 10x r0, r1 derive and R1,R2 calculation & 6 ms \\\hline
+ CS & 10x derivation of blinding secrets & 0.713 ms \\\hline
+ CS & 10x blinding & 23 ms \\\hline
+ RSA 1024 bit & 10x blinding & 11 ms \\\hline
+ RSA 2048 bit & 10x blinding & 28 ms \\\hline
+ RSA 3072 bit & 10x blinding & 51 ms \\\hline
+ RSA 4096 bit & 10x blinding & 81 ms \\\hline
+ \hline
+ CS & 10x signing & 0.321 ms \\\hline
+ RSA 1024 bit & 10x signing & 57 ms \\\hline
+ RSA 2048 bit & 10x signing & 263 ms \\\hline
+ RSA 3072 bit & 10x signing & 685 ms \\\hline
+ RSA 4096 bit & 10x signing & 1385 ms \\\hline
+ \hline
+ CS & 10x unblinding & 0.006 ms \\\hline
+ RSA 1024 bit & 10x unblinding & 23 ms \\\hline
+ RSA 2048 bit & 10x unblinding & 79 ms \\\hline
+ RSA 3072 bit & 10x unblinding & 171 ms \\\hline
+ RSA 4096 bit & 10x unblinding & 296 ms \\\hline
+ \hline
+ CS & 10x verifying & 11ms \\\hline
+ RSA 1024 bit & 10x verifying & 5 ms \\\hline
+ RSA 2048 bit & 10x verifying & 15 ms \\\hline
+ RSA 3072 bit & 10x verifying & 27 ms \\\hline
+ RSA 4096 bit & 10x verifying & 45 ms \\\hline
+ \end{tabular}
+ \caption{Comparison on arm64}
+ \label{tab:comp-sign-arm64}
+\end{table}
+
+\section{AMD Ryzen Embedded R1606G}
+Detailed comparison of each operation can be found in table \ref{tab:comp-sign-amd-embedded}.
+\begin{bfhBox}[BFH-MediumBlue]{Setup}
+ CPU: 4-core AMD Ryzen Embedded R1606G with Radeon Vega Gfx\\
+ Architecture: amd64 \\
+ OS: Linux computer 5.13.0-25-generic \#26-Ubuntu SMP Fri Jan 7 15:48:31 UTC 2022 x86\_64 x86\_64 x86\_64 GNU/Linux\\
+ libsodium:amd64 version: 1.8.7-5ubuntu2 \\
+ libgcrypt:amd64 version: 1.0.18-1build1
+\end{bfhBox}
+
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{llr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Operation} & \textbf{Speed} \\\hline
+ CS & 10x key generation & 2.373 ms \\\hline
+ RSA 1024 bit & 10x key generation & 184 ms \\\hline
+ RSA 2048 bit & 10x key generation & 2132 ms \\\hline
+ RSA 3072 bit & 10x key generation & 8000 ms \\\hline
+ RSA 4096 bit & 10x key generation & 21 000 ms \\\hline
+ \hline
+ CS & 10x r0, r1 derive and R1,R2 calculation & 1.09 ms \\\hline
+ CS & 10x derivation of blinding secrets & 0.43 ms \\\hline
+ CS & 10x blinding & 6 ms \\\hline
+ RSA 1024 bit & 10x blinding & 3.886 ms \\\hline
+ RSA 2048 bit & 10x blinding & 7 ms \\\hline
+ RSA 3072 bit & 10x blinding & 14 ms \\\hline
+ RSA 4096 bit & 10x blinding & 23 ms \\\hline
+ \hline
+ CS & 10x signing & 0.379 ms \\\hline
+ RSA 1024 bit & 10x signing & 15 ms \\\hline
+ RSA 2048 bit & 10x signing & 71 ms \\\hline
+ RSA 3072 bit & 10x signing & 177 ms \\\hline
+ RSA 4096 bit & 10x signing & 357 ms \\\hline
+ \hline
+ CS & 10x unblinding & 0.001 ms \\\hline
+ RSA 1024 bit & 10x unblinding & 6 ms \\\hline
+ RSA 2048 bit & 10x unblinding & 24 ms \\\hline
+ RSA 3072 bit & 10x unblinding & 53 ms \\\hline
+ RSA 4096 bit & 10x unblinding & 93 ms \\\hline
+ \hline
+ CS & 10x verifying & 2.610 ms \\\hline
+ RSA 1024 bit & 10x verifying & 2.303 ms \\\hline
+ RSA 2048 bit & 10x verifying & 4.386 ms \\\hline
+ RSA 3072 bit & 10x verifying & 7 ms \\\hline
+ RSA 4096 bit & 10x verifying & 11 ms \\\hline
+ \end{tabular}
+ \caption{Comparison on AMD Ryzen Embedded R1606G}
+ \label{tab:comp-sign-amd-embedded}
+\end{table}
+
+\section{risc64}
+Detailed comparison of each operation can be found in table \ref{tab:comp-sign-risc64}.
+\begin{bfhBox}[BFH-MediumBlue]{Setup}
+ CPU: 4-core risc64 processor\\
+ OS: Linux risc-v-unleashed-000 5.11.0-1022-generic \#23~20.04.1-Ubuntu SMP Thu Oct 21 10:16:27 UTC 2021 riscv64 riscv64 riscv64 GNU/Linux\\
+ libsodium:riscv64 version: 1.8.7-5ubuntu2 \\
+ libgcrypt:riscv64 version: 1.0.18-1build1
+\end{bfhBox}
+
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{llr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Operation} & \textbf{Speed} \\\hline
+ CS & 10x key generation & 4.144 ms \\\hline
+ RSA 1024 bit & 10x key generation & 2923 ms \\\hline
+ RSA 2048 bit & 10x key generation & 28 000 ms \\\hline
+ RSA 3072 bit & 10x key generation & 174 000 ms \\\hline
+ RSA 4096 bit & 10x key generation & 600 000 ms \\\hline
+ \hline
+ CS & 10x r0, r1 derive and R1,R2 calculation & 10 ms \\\hline
+ CS & 10x derivation of blinding secrets & 2.514 ms \\\hline
+ CS & 10x blinding & 72 ms \\\hline
+ RSA 1024 bit & 10x blinding & 37 ms \\\hline
+ RSA 2048 bit & 10x blinding & 93 ms \\\hline
+ RSA 3072 bit & 10x blinding & 170 ms \\\hline
+ RSA 4096 bit & 10x blinding & 277 ms \\\hline
+ \hline
+ CS & 10x signing & 1.697 ms \\\hline
+ RSA 1024 bit & 10x signing & 215 ms \\\hline
+ RSA 2048 bit & 10x signing & 1040 ms \\\hline
+ RSA 3072 bit & 10x signing & 2883 ms \\\hline
+ RSA 4096 bit & 10x signing & 5000 ms \\\hline
+ \hline
+ CS & 10x unblinding & 0.022 ms \\\hline
+ RSA 1024 bit & 10x unblinding & 62 ms \\\hline
+ RSA 2048 bit & 10x unblinding & 150 ms \\\hline
+ RSA 3072 bit & 10x unblinding & 275 ms \\\hline
+ RSA 4096 bit & 10x unblinding & 431 ms \\\hline
+ \hline
+ CS & 10x verifying & 29 ms \\\hline
+ RSA 1024 bit & 10x verifying & 22 ms \\\hline
+ RSA 2048 bit & 10x verifying & 54 ms \\\hline
+ RSA 3072 bit & 10x verifying & 99 ms \\\hline
+ RSA 4096 bit & 10x verifying & 166 ms \\\hline
+ \end{tabular}
+ \caption{Comparison on risc64}
+ \label{tab:comp-sign-risc64}
+\end{table}
+
+\section{POWER9}
+Detailed comparison of each operation can be found in table \ref{tab:comp-sign-POWER9}.
+\begin{bfhBox}[BFH-MediumBlue]{Setup}
+ CPU: 176-core power9\\
+ architecture: pp64le \\
+ OS: Linux power9 5.11.0-34-generic \#36-Ubuntu SMP Thu Aug 26 19:19:54 UTC 2021 ppc64le ppc64le ppc64le GNU/Linux \\
+ libsodium:a::ppc64el version: 1.8.7-2ubuntu2.1 \\
+ libgcrypt::ppc64el version: 1.0.18-1
+\end{bfhBox}
+
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{llr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Operation} & \textbf{Speed} \\\hline
+ CS & 10x key generation & 0.275 ms \\\hline
+ RSA 1024 bit & 10x key generation & 290 ms \\\hline
+ RSA 2048 bit & 10x key generation & 3743 ms \\\hline
+ RSA 3072 bit & 10x key generation & 15 000 ms \\\hline
+ RSA 4096 bit & 10x key generation & 45 000 ms \\\hline
+ \hline
+ CS & 10x r0, r1 derive and R1,R2 calculation & 0.749 ms \\\hline
+ CS & 10x derivation of blinding secrets & 0.267 ms \\\hline
+ CS & 10x blinding & 4.996 ms \\\hline
+ RSA 1024 bit & 10x blinding & 3.952 ms \\\hline
+ RSA 2048 bit & 10x blinding & 10 ms \\\hline
+ RSA 3072 bit & 10x blinding & 17 ms \\\hline
+ RSA 4096 bit & 10x blinding & 27 ms \\\hline
+ \hline
+ CS & 10x signing & 0.221 ms \\\hline
+ RSA 1024 bit & 10x signing & 25 ms \\\hline
+ RSA 2048 bit & 10x signing & 135 ms \\\hline
+ RSA 3072 bit & 10x signing & 381 ms \\\hline
+ RSA 4096 bit & 10x signing & 762 ms \\\hline
+ \hline
+ CS & 10x unblinding & 0.002 ms \\\hline
+ RSA 1024 bit & 10x unblinding & 9 ms \\\hline
+ RSA 2048 bit & 10x unblinding & 34 ms \\\hline
+ RSA 3072 bit & 10x unblinding & 80 ms \\\hline
+ RSA 4096 bit & 10x unblinding & 141 ms \\\hline
+ \hline
+ CS & 10x verifying & 2.458 ms \\\hline
+ RSA 1024 bit & 10x verifying & 2.365 ms \\\hline
+ RSA 2048 bit & 10x verifying & 6 ms \\\hline
+ RSA 3072 bit & 10x verifying & 10 ms \\\hline
+ RSA 4096 bit & 10x verifying & 16 ms \\\hline
+ \end{tabular}
+ \caption{Comparison on POWER9}
+ \label{tab:comp-sign-POWER9}
+\end{table}
+
+\section{ARMv7 Processor}
+Detailed comparison of each operation can be found in table \ref{tab:comp-sign-armv7}.
+\begin{bfhBox}[BFH-MediumBlue]{Setup}
+ CPU: 8-core ARMv7 Processor rev 3 (v7l)
+ Architecture: armv7 \\
+ OS: Linux odroidxu4 4.14.150-odroidxu4 \#2 SMP PREEMPT Mon Oct 28 08:07:45 CET 2019 armv7l GNU/Linux\\
+ libsodium:armhf version: 1.9.4-5 \\
+ libgcrypt:armhf version: 1.0.18-1
+\end{bfhBox}
+
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{llr}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Operation} & \textbf{Speed} \\\hline
+ CS & 10x key generation & 1.719 ms \\\hline
+ RSA 1024 bit & 10x key generation & 1050 ms \\\hline
+ RSA 2048 bit & 10x key generation & 8000 ms \\\hline
+ RSA 3072 bit & 10x key generation & 53 000 ms \\\hline
+ RSA 4096 bit & 10x key generation & 159 000 ms \\\hline
+ \hline
+ CS & 10x r0, r1 derive and R1,R2 calculation & 3.621 ms \\\hline
+ CS & 10x derivation of blinding secrets & 0.514 ms \\\hline
+ CS & 10x blinding & 24 ms \\\hline
+ RSA 1024 bit & 10x blinding & 10 ms \\\hline
+ RSA 2048 bit & 10x blinding & 26 ms \\\hline
+ RSA 3072 bit & 10x blinding & 45 ms \\\hline
+ RSA 4096 bit & 10x blinding & 78 ms \\\hline
+ \hline
+ CS & 10x signing & 0.481 ms \\\hline
+ RSA 1024 bit & 10x signing & 87 ms \\\hline
+ RSA 2048 bit & 10x signing & 385 ms \\\hline
+ RSA 3072 bit & 10x signing & 1038 ms \\\hline
+ RSA 4096 bit & 10x signing & 2073 ms \\\hline
+ \hline
+ CS & 10x unblinding & 0.008 ms \\\hline
+ RSA 1024 bit & 10x unblinding & 26 ms \\\hline
+ RSA 2048 bit & 10x unblinding & 90 ms \\\hline
+ RSA 3072 bit & 10x unblinding & 195 ms \\\hline
+ RSA 4096 bit & 10x unblinding & 344 ms \\\hline
+ \hline
+ CS & 10x verifying & 11 ms \\\hline
+ RSA 1024 bit & 10x verifying & 5 ms \\\hline
+ RSA 2048 bit & 10x verifying & 15 ms \\\hline
+ RSA 3072 bit & 10x verifying & 28 ms \\\hline
+ RSA 4096 bit & 10x verifying & 42 ms \\\hline
+ \end{tabular}
+ \caption{Comparison on ARMv7}
+ \label{tab:comp-sign-armv7}
+\end{table}
+
+
+\section{Performance of the Security Module}
+These performance measurements are only done on one hardware setup.
+The performance tests of the cryptographic routines are more meaningful, the architecture of the Taler exchange could change a lot.
+Furthermore, there could be made performance improvements at costs of security by doing the operations requiring the private keys directly in the httpd process.
+Because of security reasons, the current design with the security module makes a lot of sense.
+It has to be kept in mind that the following performance benchmarks are interesting to see, but could vary a lot with changes inside the codebase.
+The performance of the signatures with the security module can be found in table \ref{tab:comp-sign-full}
+\begin{bfhBox}[BFH-MediumBlue]{Setup}
+ CPU: 8-core AMD Ryzen 7 PRO 5850U \\
+ OS: Ubuntu 21.10 Linux 5.13.0-25-generic \#26-Ubuntu SMP Fri Jan 7 15:48:31 UTC 2022 x86\_64 x86\_64 x86\_64 GNU/Linux \\
+ libsodium version: 1.0.18-1build1 \\
+ libgcrypt version: 1.8.7-5ubuntu2
+\end{bfhBox}
+
+\begin{table}[ht]
+ \centering
+ \colorlet{BFH-table}{BFH-MediumBlue!10}
+ \colorlet{BFH-tablehead}{BFH-MediumBlue!50}
+ \setupBfhTabular
+ \begin{tabular}{lll}
+ \rowcolor{BFH-tablehead}
+ \textbf{Signature Scheme} & \textbf{Test} & \textbf{Speed} \\\hline
+ CS & 100 sequential signature operations & 2.591 ms \\\hline
+ RSA 1024 bit & 100 sequential signature operations & 79 ms \\\hline
+ RSA 2048 bit & 100 sequential signature operations & 350 ms \\\hline
+ RSA 3072 bit & 100 sequential signature operations & 893 ms \\\hline
+ RSA 4092 & 100 sequential signature operations & 1811 ms \\\hline
+ \hline
+ CS & 100 parallel signature operations & 14 ms \\\hline
+ RSA 1024 bit & 100 parallel signature operations & 125 ms \\\hline
+ RSA 2048 bit & 100 parallel signature operations & 573ms \\\hline
+ RSA 3072 bit & 100 parallel signature operations & 1420 ms \\\hline
+ RSA 4092 & 100 parallel signature operations & 3279 ms \\\hline
+ \hline
+ CS & 800 parallel signature operations & 19 ms \\\hline
+ RSA 1024 bit & 800 parallel signature operations & 137 ms \\\hline
+ RSA 2048 bit & 800 parallel signature operations & 653 ms \\\hline
+ RSA 3072 bit & 800 parallel signature operations & 1451 ms \\\hline
+ RSA 4092 & 800 parallel signature operations & 3388 ms \\\hline
+ \end{tabular}
+ \caption{Performance comparison of the security module}
+ \label{tab:comp-sign-full}
+\end{table}
+
+\input{content/appendix/rsa-redesign.tex}
diff --git a/doc/cs/content/appendix/crypto_implementation.tex b/doc/cs/content/appendix/crypto_implementation.tex
new file mode 100644
index 000000000..9ae9b5865
--- /dev/null
+++ b/doc/cs/content/appendix/crypto_implementation.tex
@@ -0,0 +1,279 @@
+\begin{lstlisting}[style=bfh-c,language=C,, caption={Crypto Implementation API}, label={lst:cryptoapi}]
+ #include <sodium.h>
+
+ /**
+ * IMPLEMENTATION NOTICE:
+ *
+ * This is an implementation of the Schnorr, Blind Schnorr and
+ * Clause Blind Schnorr Signature Scheme using Curve25519.
+ * We use libsodium wherever possible.
+ *
+ * Blind Schnorr: The Blind Schnorr Signature Scheme is BROKEN!
+ * Use the Clause Blind Schnorr Signature Scheme instead.
+ *
+ * Clause Blind Schnorr Signature Scheme:
+ * This is a variation of the Blind Schnorr Signature Scheme where all operations
+ * before the signature creation are performed twice.
+ * The signer randomly chooses one of the two sessions and only creates the signature for this session.
+ * Note that the Clause part needs to be implemented by whoever uses this API.
+ * Further details about the Clause Blind Schnorr Signature Scheme can be found here:
+ * https://eprint.iacr.org/2019/877.pdf
+ */
+
+
+ /**
+ * Curve25519 Scalar
+ */
+ struct GNUNET_CRYPTO_Cs25519Scalar
+ {
+ /**
+ * 32 byte scalar
+ */
+ unsigned char d[crypto_core_ed25519_SCALARBYTES];
+ };
+
+
+ /**
+ * Curve25519 point
+ */
+ struct GNUNET_CRYPTO_Cs25519Point
+ {
+ /**
+ * This is a point on the Curve25519.
+ * The x coordinate can be restored using the y coordinate
+ */
+ unsigned char y[crypto_core_ed25519_BYTES];
+ };
+
+
+ /**
+ * The private information of an Schnorr key pair.
+ */
+ struct GNUNET_CRYPTO_CsPrivateKey
+ {
+ struct GNUNET_CRYPTO_Cs25519Scalar scalar;
+ };
+
+
+ /**
+ * The public information of an Schnorr key pair.
+ */
+ struct GNUNET_CRYPTO_CsPublicKey
+ {
+ struct GNUNET_CRYPTO_Cs25519Point point;
+ };
+
+
+ /**
+ * Secret used for blinding (alpha and beta).
+ */
+ struct GNUNET_CRYPTO_CsBlindingSecret
+ {
+ struct GNUNET_CRYPTO_Cs25519Scalar alpha;
+ struct GNUNET_CRYPTO_Cs25519Scalar beta;
+ };
+
+
+ /**
+ * the private r used in the signature
+ */
+ struct GNUNET_CRYPTO_CsRSecret
+ {
+ struct GNUNET_CRYPTO_Cs25519Scalar scalar;
+ };
+
+
+ /**
+ * the public R (derived from r) used in c
+ */
+ struct GNUNET_CRYPTO_CsRPublic
+ {
+ struct GNUNET_CRYPTO_Cs25519Point point;
+ };
+
+ /**
+ * Schnorr c to be signed
+ */
+ struct GNUNET_CRYPTO_CsC
+ {
+ struct GNUNET_CRYPTO_Cs25519Scalar scalar;
+ };
+
+ /**
+ * s in the signature
+ */
+ struct GNUNET_CRYPTO_CsS
+ {
+ struct GNUNET_CRYPTO_Cs25519Scalar scalar;
+ };
+
+ /**
+ * blinded s in the signature
+ */
+ struct GNUNET_CRYPTO_CsBlindS
+ {
+ struct GNUNET_CRYPTO_Cs25519Scalar scalar;
+ };
+
+ /**
+ * CS Signtature containing scalar s and point R
+ */
+ struct GNUNET_CRYPTO_CsSignature
+ {
+ /**
+ * Schnorr signatures are composed of a scalar s and a curve point
+ */
+ struct GNUNET_CRYPTO_CsS s_scalar;
+ struct GNUNET_CRYPTO_Cs25519Point r_point;
+ };
+
+ /**
+ * Nonce
+ */
+ struct GNUNET_CRYPTO_CsNonce
+ {
+ /*a nonce*/
+ unsigned char nonce[256 / 8];
+ };
+
+
+ /**
+ * Create a new random private key.
+ *
+ * @param[out] priv where to write the fresh private key
+ */
+ void
+ GNUNET_CRYPTO_cs_private_key_generate (struct GNUNET_CRYPTO_CsPrivateKey *priv);
+
+
+ /**
+ * Extract the public key of the given private key.
+ *
+ * @param priv the private key
+ * @param[out] pub where to write the public key
+ */
+ void
+ GNUNET_CRYPTO_cs_private_key_get_public (const struct GNUNET_CRYPTO_CsPrivateKey *priv,
+ struct GNUNET_CRYPTO_CsPublicKey *pub);
+
+
+ /**
+ * Derive a new secret r pair r0 and r1.
+ * In original papers r is generated randomly
+ * To provide abort-idempotency, r needs to be derived but still needs to be UNPREDICTABLE
+ * To ensure unpredictability a new nonce should be used when a new r needs to be derived.
+ * Uses HKDF internally.
+ * Comment: Can be done in one HKDF shot and split output.
+ *
+ * @param nonce is a random nonce
+ * @param lts is a long-term-secret in form of a private key
+ * @param[out] r array containing derived secrets r0 and r1
+ */
+ void
+ GNUNET_CRYPTO_cs_r_derive (const struct GNUNET_CRYPTO_CsNonce *nonce,
+ const struct GNUNET_CRYPTO_CsPrivateKey *lts,
+ struct GNUNET_CRYPTO_CsRSecret r[2]);
+
+
+ /**
+ * Extract the public R of the given secret r.
+ *
+ * @param r_priv the private key
+ * @param[out] r_pub where to write the public key
+ */
+ void
+ GNUNET_CRYPTO_cs_r_get_public (const struct GNUNET_CRYPTO_CsRSecret *r_priv,
+ struct GNUNET_CRYPTO_CsRPublic *r_pub);
+
+
+ /**
+ * Derives new random blinding factors.
+ * In original papers blinding factors are generated randomly
+ * To provide abort-idempotency, blinding factors need to be derived but still need to be UNPREDICTABLE
+ * To ensure unpredictability a new nonce has to be used.
+ * Uses HKDF internally
+ *
+ * @param secret is secret to derive blinding factors
+ * @param secret_len secret length
+ * @param[out] bs array containing the two derivedGNUNET_CRYPTO_CsBlindingSecret
+ */
+ void
+ GNUNET_CRYPTO_cs_blinding_secrets_derive (const struct GNUNET_CRYPTO_CsNonce *blind_seed,
+ struct GNUNET_CRYPTO_CsBlindingSecret bs[2]);
+
+
+ /**
+ * Calculate two blinded c's
+ * Comment: One would be insecure due to Wagner's algorithm solving ROS
+ *
+ * @param bs array of the two blinding factor structs each containing alpha and beta
+ * @param r_pub array of the two signer's nonce R
+ * @param pub the public key of the signer
+ * @param msg the message to blind in preparation for signing
+ * @param msg_len length of message msg
+ * @param[out] blinded_c array of the two blinded c's
+ */
+ void
+ GNUNET_CRYPTO_cs_calc_blinded_c (const struct GNUNET_CRYPTO_CsBlindingSecret bs[2],
+ const struct GNUNET_CRYPTO_CsRPublic r_pub[2],
+ const struct GNUNET_CRYPTO_CsPublicKey *pub,
+ const void *msg,
+ size_t msg_len,
+ struct GNUNET_CRYPTO_CsC blinded_c[2]);
+
+
+ /**
+ * Sign a blinded c
+ * This function derives b from a nonce and a longterm secret
+ * In original papers b is generated randomly
+ * To provide abort-idempotency, b needs to be derived but still need to be UNPREDICTABLE.
+ * To ensure unpredictability a new nonce has to be used for every signature
+ * HKDF is used internally for derivation
+ * r0 and r1 can be derived prior by using GNUNET_CRYPTO_cs_r_derive
+ *
+ * @param priv private key to use for the signing and as LTS in HKDF
+ * @param r array of the two secret nonce from the signer
+ * @param c array of the two blinded c to sign c_b
+ * @param nonce is a random nonce
+ * @param[out] blinded_signature_scalar where to write the signature
+ * @return 0 or 1 for b (see Clause Blind Signature Scheme)
+ */
+ int
+ GNUNET_CRYPTO_cs_sign_derive(const struct GNUNET_CRYPTO_CsPrivateKey *priv,
+ const struct GNUNET_CRYPTO_CsRSecret r[2],
+ const struct GNUNET_CRYPTO_CsC c[2],
+ const struct GNUNET_CRYPTO_CsNonce *nonce,
+ struct GNUNET_CRYPTO_CsBlindS *blinded_signature_scalar
+ );
+
+
+ /**
+ * Unblind a blind-signed signature using a c that was blinded
+ *
+ * @param blinded_signature_scalar the signature made on the blinded c
+ * @param bs the blinding factors used in the blinding
+ * @param[out] signature_scalar where to write the unblinded signature
+ */
+ void
+ GNUNET_CRYPTO_cs_unblind (const struct GNUNET_CRYPTO_CsBlindS *blinded_signature_scalar,
+ const struct GNUNET_CRYPTO_CsBlindingSecret *bs,
+ struct GNUNET_CRYPTO_CsS *signature_scalar);
+
+
+ /**
+ * Verify whether the given message corresponds to the given signature and the
+ * signature is valid with respect to the given public key.
+ *
+ * @param sig signature that is being validated
+ * @param pub public key of the signer
+ * @param msg is the message that should be signed by @a sig (message is used to calculate c)
+ * @param msg_len is the message length
+ * @returns #GNUNET_YES on success, #GNUNET_SYSERR if key parameter(s) invalid #GNUNET_NO if signature invalid
+ */
+ enum GNUNET_GenericReturnValue
+ GNUNET_CRYPTO_cs_verify (const struct GNUNET_CRYPTO_CsSignature *sig,
+ const struct GNUNET_CRYPTO_CsPublicKey *pub,
+ const void *msg,
+ size_t msg_len);
+
+\end{lstlisting} \ No newline at end of file
diff --git a/doc/cs/content/appendix/rsa-redesign.tex b/doc/cs/content/appendix/rsa-redesign.tex
new file mode 100644
index 000000000..4f66d907e
--- /dev/null
+++ b/doc/cs/content/appendix/rsa-redesign.tex
@@ -0,0 +1,209 @@
+\chapter{Redesigned RSA Protocols}
+In order to bring the RSA and \gls{CSBS} protocols closer, this chapter describes a variant of the RSA protocols with the same changes as in the \gls{CSBS} versions (where they can be applied).
+
+
+\section{Withdraw Protocol}
+\begin{figure}[htp]
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ \text{Customer} & & \text{Exchange}
+ \\ \text{knows:} & & \text{knows:}
+ \\ \text{reserve keys } w_s, W_p & & \text{reserve public key } W_p
+ \\ \text{denomination public key } D_p = e, N & & \text{denomination keys } d_s, D_p
+ \\ & &
+ \\\text{generate withdraw secret:}
+ \\ \omega := randombytes(32)
+ \\ \text{persist } \langle \omega, D_p \rangle
+ \\\text{derive coin key pair:} & &
+ \\ c_s := \text{HKDF}(256, \omega, \text{"cs"})
+ \\ C_p := \text{Ed25519.GetPub}(c_s)
+ \\ \text{blind:} & &
+ \\ b_s := \text{HKDF}(256, \omega, \text{"b-seed"})
+ \\ r := \text{FDH}(b_s)
+ \\ m' := \text{FDH}(N, C_p)*r^{e} \mod N & &
+ \\ \text{sign with reserve private key:} & &
+ \\ \rho_W := \langle D_p, m' \rangle & &
+ \\ \sigma_W := \text{Ed25519.Sign}(w_s, \rho_W) & &
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{\rho = W_p, \sigma_W, \rho_W} &
+ \\ & & \langle D_p, m' \rangle := \rho_W
+ \\ & & \text{verify if } D_p \text{ is valid}
+ \\ & & \text{check } \text{Ed25519.Verify}(W_p, \rho_W, \sigma_W)
+ \\ & & \sigma'_c = (m')^{d_s} \mod N
+ \\ & & \text{decrease balance if sufficient and}
+ \\ & & \text{persist } \langle D_p, s \rangle
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{\sigma'_c} &
+ \\ \text{unblind:}& &
+ \\ \sigma_c = \sigma'_c*r^{-1} & &
+ \\ \text{verify signature:}& &
+ \\ \textbf{check if } \sigma_c^{e} = \text{FDH}(N, C_p) & &
+ \\ & &
+ \\ \text{resulting coin: } c_s, C_p, \sigma_c, D_p & &
+ \\ & &
+ \\ \text{implementation note: minimum of}
+ \\ \text{persisted values is } \langle \omega, \sigma_c \rangle
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Redesigned RSA withdrawal process}
+ \label{fig:withdrawal-process-rsa-redesign}
+\end{figure}
+
+The changes to the RSA witdhdraw protocol (see \autoref{fig:withdrawal-process-rsa-redesign}) are limited to the derivation of the coin and blinding factor.
+
+
+\section{Refresh Protocol}
+The changes to the refresh protocol are related to the derivation of transfer secrets and subsequent operations, see \autoref{fig:refresh-derive-rsa-redesign}, \autoref{fig:refresh-part1-rsa-redesign} and \autoref{fig:refresh-part2-rsa-redesign}.
+\begin{figure}[htp]
+ \centering
+ \fbox{%
+ \procedure[codesize=\small]{$\text{RefreshDerive}(t, \langle e, N \rangle, C_p)$}{%
+ T := \text{Curve25519.GetPub}(t) \\
+ x := \textrm{ECDH-EC}(t, C_p) \\
+ b_s := \text{HKDF}(256, x, \text{"b-seed"}) \\
+ r := \text{FDH}(b_s) \\
+ c'_s := \text{HKDF}(256,x,"c") \\
+ C'_p := \text{Ed25519.GetPub}(c'_s) \\
+ \overline{m} := r^e * C'_p \mod N \\
+ \pcreturn \langle T, c_s', C_p', \overline{m} \rangle
+ }
+ }
+ \caption{Redesigned RSA RefreshDerive algorithm}
+ \label{fig:refresh-derive-rsa-redesign}
+\end{figure}
+
+\begin{figure}[htp]
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{Customer} & & \text{Exchange}
+ \\ \text{knows:} & & \text{knows:}
+ \\ \text{denomination public key } D_{p(i)} & & \text{denomination keys } d_{s(i)}, D_{p(i)}
+ \\ \text{coin}_0 = \langle D_{p(0)}, c_s^{(0)}, C_p^{(0)}, \sigma_c^{(0)} \rangle & &
+ % refresh request
+ \\ \text{Select} \langle N_t, e_t\rangle := D_{p(t)} \in D_{p(i)}
+ \\ \omega := randombytes(32)
+ \\ \text{persist } \langle \omega, D_{p(t)} \rangle
+ \\ \textbf{for } i = 1, \dots, \kappa: % generate k derives
+ \\ t_i := \text{HKDF}(256, \omega,\text{"t} i \text{"} ) % seed generation
+ \\ X_i := \text{RefreshDerive}(t_i, D_{p(t)}, C_p^{(0)})
+ \\ (T_i, c_s^{(i)}, C_p^{(i)}, \overline{m}_i) := X_i
+ \\ \textbf{endfor}
+ \\ h_T := H(T_1, \dots, T_k)
+ \\ h_{\overline{m}} := H(\overline{m}_1, \dots, \overline{m}_k)
+ \\ h_C := H(h_t, h_{\overline{m}})
+ \\ \rho_{RC} := \langle h_C, D_{p(t)}, D_{p(0)}, C_p^{(0)}, \sigma_C^{(0)} \rangle
+ \\ \sigma_{RC} := \text{Ed25519.Sign}(c_s^{(0)}, \rho_{RC})
+ \\ \text{Persist refresh-request} \langle \omega, \rho_{RC}, \sigma_{RC} \rangle
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{\rho_{RC}, \sigma_{RC}} &
+ % Exchange checks refresh request
+ \\ & & (h_C, D_{p(t)}, D_{p(0)}, C_p^{(0)}, \sigma_C^{(0)} = \rho_{RC})
+ \\ & & \textbf{check} \text{Ed25519.Verify}(C_p^{(0)}, \sigma_{RC}, \rho_{RC})
+ \\ & & x \rightarrow \text{GetOldRefresh}(\rho_{RC})
+ \\ & & \textbf{Comment: }\text{GetOldRefresh} (\rho_{RC} \mapsto \{\bot,\gamma\})
+ \\ & & \pcif x = \bot
+ \\ & & v := \text{Denomination}(D_{p(t)})
+ \\ & & \langle e_0, N_0 \rangle := D_{p(0)}
+ \\ & & \textbf{check } \text{IsOverspending}(C_p^{(0)}, D_ {p(0)}, v)
+ \\ & & \textbf{check } D_{p(t)} \in \{D_{p(i)}\}
+ \\ & & \textbf{check } \text{FDH}(N_0, C_p^{(0)}) \equiv_{N_0} (\sigma_0^{(0)})^{e_0}
+ \\ & & \text{MarkFractionalSpend}(C_p^{(0)}, v)
+ \\ & & \gamma \leftarrow \{1, \dots, \kappa\}
+ \\ & & \text{Persist refresh-record } \langle \rho_{RC},\gamma \rangle
+ \\ & & \pcelse
+ \\ & & \gamma := x
+ \\ & & \textbf{endif}
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{\gamma} &
+ \\
+ \\
+ \\ & \textit{Continued in figure \ref{fig:refresh-part2}} &
+ %\\ \pcintertext[dotted]{(Continued in Figure)}
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Redesigned RSA refresh protocol (commit phase)}
+ \label{fig:refresh-part1-rsa-redesign}
+\end{figure}
+
+\begin{figure}[htp]
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{Customer} & & \text{Exchange}
+ \\ & \textit{Continuation of figure \ref{fig:refresh-part1}} &
+ \\
+ \\
+ % Check challenge and send challenge response (reveal not selected msgs)
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{\gamma} &
+ \\ \textbf{check } \text{IsConsistentChallenge}(\rho_{RC}, \gamma)
+ \\ \textbf{Comment: } \text{IsConsistentChallenge}\\(\rho_{RC}, \gamma) \mapsto \{ \bot,\top \}
+ \\
+ \\ \text{Persist refresh-challenge} \langle \rho_{RC}, \gamma \rangle
+ \\ S := \langle t_1, \dots, t_{\gamma-1}, t_{\gamma+1}, \dots, t_\kappa \rangle % all seeds without the gamma seed
+ \\ \rho_L = \langle C_p^{(0)}, D_{p(t)}, T_{\gamma},\overline{m}_\gamma \rangle
+ \\ \rho_{RR} = \langle T_\gamma, \overline{m}_\gamma, S \rangle
+ \\ \sigma_{L} = \text{Ed25519.Sign}(c_s^{(0)}, \rho_{L})
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{\rho_{RR},\rho_L, \sigma_{L}} &
+ % check revealed msgs and sign coin
+ \\ & & \langle T'_\gamma, \overline{m}'_\gamma, S \rangle := \rho_{RR}
+ \\ & & \langle t_1, \dots, t_{\gamma-1}, t_{\gamma+1}, \dots, t_\kappa \rangle ) := S
+ \\ & & \textbf{check } \text{Ed25519.Verify}(C_p^{(0)}, \sigma_L, \rho_L)
+ \\ & & \textbf{for} i = 1,\dots, \gamma-1, \gamma+1,\dots, \kappa
+ \\ & & X_i := \text{RefreshDerive}(t_i, D_{p(t)}, C_p^{(0)})
+ \\ & & \langle T_i, c_s^{(i)}, C_p^{(i)}, \overline{m}_i \rangle := X_i
+ \\ & & \textbf{endfor}
+ \\ & & h_T' = H(T_1,\dots,T_{\gamma-1},T'_{\gamma},T_{\gamma+1},\dots,T_\kappa)
+ \\ & & h_{\overline{m}}' = H(\overline{m}_1,\dots,\overline{m}_{\gamma-1},\overline{m}'_{\gamma},\overline{m}_{\gamma+1},\dots,\overline{m}_\kappa)
+ \\ & & h_C' = H(h_T', h_{\overline{m}}')
+ \\ & & \textbf{check } h_C = h_C'
+ \\ & & \overline{\sigma}_C^{(\gamma)} := \overline{m}^{d_{s(t)}}
+ \\ & & \text{persist } \langle \rho_L, \sigma_L, S \rangle
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{\overline{\sigma}_C^{(\gamma)}} &
+ % Check coin signature and persist coin
+ \\ \sigma_C^{(\gamma)} := r^{-1}\overline{\sigma}_C^{(\gamma)}
+ \\ \textbf{check if } (\sigma_C^{(\gamma)})^{e_t} \equiv_{N_t} C_p^{(\gamma)}
+ \\ \text{Persist coin} \langle D_{p(t)}, c_s^{(\gamma)}, C_p^{(\gamma)}, \sigma_C^{(\gamma)} \rangle
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Redesigned RSA refresh protocol (reveal phase)}
+ \label{fig:refresh-part2-rsa-redesign}
+\end{figure}
+
+
+\section{Linking Protocol}
+The changes are described in \autoref{fig:refresh-link-rsa-redesign}.
+\begin{figure}[htp]
+ \begin{equation*}
+ \resizebox{1.0\textwidth}{!}{$\displaystyle
+ \begin{array}{ l c l }
+ % preliminaries
+ \text{Customer} & & \text{Exchange}
+ \\ \text{knows:} & & \text{knows:}
+ \\ \text{coin}_0 = \langle D_{p(0)}, c_s^{(0)}, C_p^{(0)}, \sigma_{C}^{(0)} \rangle
+ \\ & \xrightarrow[\rule{2.5cm}{0pt}]{C_{p(0)}} &
+ \\ & & L := \text{LookupLink}(C_{p(0)})
+ \\ & & \textbf{Comment: } \text{LookupLink}(C_p) \mapsto \{\langle \rho_L^{(i)},
+ \\ & & \sigma_L^{(i)}, \overline{\sigma}_C^{(i)} \rangle\}
+ \\ & \xleftarrow[\rule{2.5cm}{0pt}]{L} &
+ \\ \pcfor \langle \rho_{L}^{(i)}, \overline{\sigma}_L^{(i)}, \sigma_C^{(i)} \rangle \in L
+ \\ \langle \hat{C}_p^{(i)}, D_{p(t)}^{(i)}, T_\gamma^{(i)}, \overline{m}_\gamma^{(i)} \rangle := \rho_L^{(i)}
+ \\ \langle e_t^{(i)}, N_t^{(i)} \rangle := D_{p(t)}^{(i)}
+ \\ \textbf{check } \hat{C}_p^{(i)} \equiv C_p^{(0)}
+ \\ \textbf{check } \text{Ed25519.Verify}(C_p^{(0)}, \rho_{L}^{(i)}, \sigma_L^{(i)})
+ \\ x_i := \text{ECDH}(c_s^{(0)}, T_{\gamma}^{(i)})
+ \\ c_s^{(i)} := \text{HKDF}(256,x_i,"c")
+ \\ C_p^{(i)} := \text{Ed25519.GetPub}(c_s^{(i)})
+ \\ b_s^{(i)} := \text{HKDF}(256, x_i, \text{"b-seed"})
+ \\ r_i := \text{FDH}(b_s^{(i)})
+ \\ \sigma_C^{(i)} := (r_i)^{-1} \cdot \overline{m}_\gamma^{(i)}
+ \\ \textbf{check } (\sigma_C^{(i)})^{e_t^{(i)}} \equiv_{N_t^{(i)}} C_p^{(i)}
+ \\ \text{(Re-)obtain coin} \langle D_{p(t)}^{(i)},c_s^{(i)}, C_p^{(i)}, \sigma_C^{(i)} \rangle
+ \end{array}$
+ }
+ \end{equation*}
+ \caption{Redesigned RSA linking protocol}
+ \label{fig:refresh-link-rsa-redesign}
+\end{figure}
diff --git a/doc/cs/content/x_taler.tex b/doc/cs/content/x_taler.tex
new file mode 100644
index 000000000..d04fe84cf
--- /dev/null
+++ b/doc/cs/content/x_taler.tex
@@ -0,0 +1,373 @@
+\chapter{Taler}
+
+\section{Taler Architecture}
+
+\subsection{Auditor}
+The following text is cited from section 4.4 in \cite{dold:the-gnu-taler-system}
+\begin{center}
+ \textit{
+ "The auditor consists of two processes that are regularly run and generate auditing reports.
+ Both processes access the exchange’s database, either directly (for exchange-internal auditing as part if its operational security) or over a replica (in the case of external auditors).
+ The taler-wire-auditor process checks that the incoming and outgoing transfers recorded in the exchange’s database match wire transfers of the underlying bank account.
+ To access the transaction history (typically recorded by the bank), the wire auditor uses a wire plugin, with the same interface and implementation as the exchange’s wire plugins.
+ The taler-auditor process generates a report with the following information:
+ }
+\end{center}
+
+\begin{itemize}
+ \item \textit{Do the operations stored in a reserve’s history match the reserve’s balance?}
+ \item \textit{Did the exchange record outgoing transactions to the right merchant for deposits after the deadline for the payment was reached?}
+ \item \textit{Do operations recorded on coins (deposit, refresh, refund) match the remaining value on the coin?}
+ \item \textit{Do operations respect the expiration of denominations?}
+ \item \textit{For a denomination, is the number of pairwise different coin public keys recorded in deposit/refresh operations smaller or equal to the number of blind signatures recorded in withdraw/refresh operations? If this invariant is violated, the corresponding denomination must be revoked.}
+ \item \textit{What is the income if the exchange from different fees?}
+\end{itemize}
+
+\begin{center}
+ \textit{
+ \dots
+ The auditor exposes a web server with the taler-auditor-httpd process.
+ Currently, it only shows a website that allows the customer to add the auditor to the list of trusted auditors in their wallet.
+ In future versions, the auditor will also have HTTP endpoints that allow merchants to submit samples of deposit confirmations, which will be checked against the deposit permissions in the exchange’s database to detect compromised signing keys or missing writes.
+ Furthermore, in deployments that require the merchant to register with the exchange beforehand, the auditor also offers a list of exchanges it audits, so that the merchant backend can automatically register with all exchanges it transitively trusts."
+ }
+\end{center}
+Some details were left out (see at the dots) and can be read in section 4.4 in \cite{dold:the-gnu-taler-system}
+
+\subsubsection{Technical Details}
+Documentation: \cite{taler-documentation:auditor-operator-manual} \\
+Git Repositories:
+\begin{itemize}
+ \item Main repository: \cite{taler-git:exchange} (Part of exchange repository, inside ./src/auditor and ./src/auditordb)
+ \item Auditor's public website: \cite{taler-git:auditor}
+\end{itemize}
+Language: C \\
+Dependencies:
+\begin{itemize}
+ \item GNUnet >= 0.14.0
+ \item GNU libmicrohttpd >= 0.9.71
+ \item Postgres >= 9.6, including libpq
+ \item libjansson >= 2.7
+ \item libargon2 >= 20171227
+ \item libsodium >= 1.0
+ \item libgcrypt >= 1.6
+ \item libqrencode >= 4.0.0
+ \item libcurl >= 7.26 (or libgnurl >= 7.26)
+ \item GNU libunistring >= 0.9.3
+ \item libsqlite3 >= 3.16.2
+\end{itemize}
+The auditor API is implemented as REST API and uses \ac{JSON} as message format.
+The auditor does not implement HTTPS (TLS), instead it recommends using a HTTP reverse Proxy that offers TLS termination.
+By delegating the responsibility for TLS termination, the auditor implementation becomes lighter.
+
+There are a lot more technical details written in the documentation linked above and in the README files.
+Since Taler is actively developed and technical details could change, we refer to this documentation.
+
+\subsection{Exchange}
+The following text is cited from section 4.3 in \cite{dold:the-gnu-taler-system}
+\begin{center}
+ \textit{
+ "The exchange consists of three independent processes:
+ }
+\end{center}
+\begin{itemize}
+ \item \textit{The taler-exchange-httpd process handles HTTP requests from clients, mainly merchants and wallets.}
+ \item \textit{The taler-exchange-wirewatch process watches for wire transfers to the exchange’s bank account and updates reserves based on that.}
+ \item \textit{The taler-exchange-aggregator process aggregates outgoing transactions to merchants.}
+\end{itemize}
+
+\begin{center}
+ \textit{
+ All three processes exchange data via the same database.
+ Only taler-exchange-httpd needs access to the exchanges online signing keys and denomination keys.
+ The database is accessed via a Taler-specific database abstraction layer.
+ Different databases can be supported via plugins; at the time of writing this, only a PostgreSQL plugin has been implemented.
+ Wire plugins are used as an abstraction to access the account layer that Taler runs on.
+ Specifically, the wirewatch process uses the plugin to monitor incoming transfers, and the aggregator process uses the wire plugin to make wire transfers to merchants.
+ The following APIs are offered by the exchange:
+ }
+
+ \textbf{\textit{Announcing keys, bank accounts and other public information}}\\
+ \textit{
+ The exchange offers the list of denomination keys, signing keys, auditors, supported bank accounts, revoked keys and other general information needed to use the exchange’s services via the /keys and /wire APIs.
+ }
+
+ \textbf{\textit{Reserve status and withdrawal}}\\
+ \textit{
+ After having wired money to the exchange, the status of the reserve can be checked via the /reserve/status API. Since the wire transfer usually takes some time to arrive at the exchange, wallets should periodically poll this API, and initiate a withdrawal with /reserve/withdraw once the exchange received the funds.
+ }
+
+ \textbf{\textit{Deposits and tracking}}\\
+ \textit{
+ Merchants transmit deposit permissions they have received from customers to the exchange via the/deposit API. Since multiple deposits are aggregated into one wire transfer, the merchant additionally can use the exchange’s /track/transfer API that returns the list of deposits for an identifier included in the wire transfer to the merchant, as well as the /track/transaction API to look up which wire transfer included the payment for a given deposit.
+ }
+
+ \textbf{\textit{Refunds}}\\
+ \textit{
+ The refund API (/refund) can “undo†a deposit if the merchant gave their signature, and the aggregation deadline for the payment has not occurred yet.
+ }
+
+ \textbf{\textit{Emergency payback}}\\
+ \textit{
+ The emergency payback API (/payback) allows customers to be compensated for coins whose denomination key has been revoked.
+ Customers must send either a full withdrawal transcript that includes their private blinding factor, or a refresh transcript (of a refresh that had the revoked denominations as one of the targets) that includes blinding factors.
+ In the former case, the reserve is credited, in the latter case, the source coin of the refresh is refunded and can be refreshed again."
+ }
+\end{center}
+
+Additional information for how the exchange generates new denomination and signing keys can be found in the end of section 4.3 of \cite{dold:the-gnu-taler-system}.
+
+\begin{figure}[h!]
+ \centering
+ \includegraphics[height=0.5\textwidth]{taler-exchange.png}
+ \caption{Architecture of the Taler Exchange reference implementation. Source: \cite{dold:the-gnu-taler-system}}
+ \label{fig:taler-arch-exchange}
+\end{figure}
+
+\subsubsection{Technical Details}
+
+Documentation: \cite{taler-documentation:exchange-operator-manual} \\
+Git Repository: Main repository: \cite{taler-git:exchange} \\
+Language: C \\
+Dependencies:
+\begin{itemize}
+ \item GNUnet >= 0.14.0
+ \item GNU libmicrohttpd >= 0.9.71
+ \item Postgres >= 9.6, including libpq
+ \item libjansson >= 2.7
+ \item libargon2 >= 20171227
+ \item libsodium >= 1.0
+ \item libgcrypt >= 1.6
+ \item libqrencode >= 4.0.0
+ \item libcurl >= 7.26 (or libgnurl >= 7.26)
+ \item GNU libunistring >= 0.9.3
+ \item libsqlite3 >= 3.16.2
+\end{itemize}
+The exchange’s API is implemented as REST API and uses \ac{JSON} as message format.
+
+There are a lot more technical details written in the documentation linked above and in the README files.
+Since Taler is actively developed and technical details could change, we refer to this documentation.
+
+\subsection{Merchant}
+The following text is cited from section 4.5 in \cite{dold:the-gnu-taler-system}
+\begin{center}
+ \textit{
+ "The Taler merchant backend is a component that abstracts away the details of processing Taler payments and provides a simple HTTP API.
+ The merchant backend handles cryptographic operations (signature verification, signing), secret management and communication with the exchange.
+ The backend API (see \url{https://docs.taler.net/api/}) is divided into two types of HTTP endpoints:
+ }
+\end{center}
+
+\begin{enumerate}
+ \item \textit{Functionality that is accessed internally by the merchant.
+ These API stypically require authentication and/or are only accessible from within the private network of the merchant.}
+ \item \textit{Functionality that is exposed publicly on the Internet and accessed by the customer’s wallet and browser.}
+\end{enumerate}
+
+\begin{center}
+ \textit{
+ A typical merchant has a storefront component that customers visit with their browser, as well as a back office component that allows the merchant to view information about payments that customers made and that integrates with other components such as order processing and shipping."
+ }
+\end{center}
+
+\subsubsection{Processing Payments}
+\begin{center}
+ \textit{
+ "To process a payment, the storefront first instructs the backend to create an order.
+ The order contains information relevant to the purchase, and is in fact a subset of the information contained in the contract terms.
+ The backend automatically adds missing information to the order details provided by the storefront.
+ The full contract terms can only be signed once the customer provides the claim public key for the contract.\\
+ Each order is uniquely identified by an order ID, which can be chosen by the storefront or automatically generated by the backend.
+ The order ID can be used to query the status of the payment.
+ If the customer did not pay for an order ID yet, the response from the backend includes a payment redirect URL.
+ The storefront can redirect the customer to this payment redirect URL; visiting the URL will trigger the customer’s browser/wallet to prompt for a payment.\\
+ To simplify the implementation of the storefront, the merchant backend can serve a page to the customer’s browser that triggers the payment via the HTTP402 status code and the corresponding headers, and provides a fallback (in the form of a taler:pay link) for loosely integrated browsers.
+ When checking the status of a payment that is not settled yet, the response from the merchant backend will contains a payment redirect URL.
+ The storefront redirects the browser to this URL, which is served by the merchant backend and triggers the payment.
+ \dots "
+ }
+\end{center}
+
+\subsubsection{Back Office APIs}
+\begin{center}
+ \textit{
+ "The back office API allows the merchant to query information about the history and status of payments, as well as correlate wire transfers to the merchant’s bank account with the respective GNU Taler payment.
+ This API is necessary to allow integration with other parts of the merchant’s e-commerce infrastructure."
+ }
+\end{center}
+
+% Nachfolgende Section nicht notwendig.
+% \subsubsection{Example Merchant Frontends}
+% This section is included to provide an overview of the reference implementation for the merchant.
+% This helps to get a better understanding of how a merchant could potentially look like.
+% Note that the actual code could differ from the cited part.
+% The actual code can be found in \url{git.taler.net}
+% \begin{center}
+% \textit{
+% We implemented the following applications using the merchant backend API.
+% }
+
+% \textit{\textbf{Blog Merchant}}\\
+% \textit{
+% The blog merchant’s landing page has a list of article titles with a teaser.
+% When following the link to the article, the customer is asked to pay to view the article.
+% }
+
+% \textit{\textbf{Donations}}\\
+% \textit{
+% The donations frontend allows the customer to select a project to donate to.
+% The fulfillment page shows a donation receipt.
+% }
+
+% \textit{\textbf{Codeless Payments}}\\
+% \textit{
+% The codeless payment frontend is a prototype for a user interface that allows merchants to sell products on their website without having to write code to integrate with the merchant backend.
+% Instead, the merchant uses a web interface to manage products and their available stock.
+% The codeless payment frontend then creates an HTML snippet with a payment button that the merchant can copy-and-paste integrate into their storefront.
+% }
+
+% \textit{\textbf{Survey}}\\
+% \textit{
+% The survey frontend showcases the tipping functionality of GNU Taler.
+% The user fills out a survey and receives a tip for completing it.
+% }
+
+% \textit{\textbf{Back Office}}\\
+% \textit{
+% The example back-office application shows the history and status of payments processed by the merchant.
+% }
+% \end{center}
+
+\begin{figure}[h!]
+ \centering
+ \includegraphics[height=0.5\textwidth]{taler-merchant.png}
+ \caption{Architecture Taler Merchant reference implementation. Source: \cite{dold:the-gnu-taler-system}}
+ \label{fig:taler-arch-merchant}
+\end{figure}
+
+\subsubsection{Technical Details}
+
+Documentation: \cite{taler-documentation:merchant-backend-operator-manual} \\
+API Documentation: \cite{taler-documentation:merchant-api} \\
+Back-office Documentation: \cite{taler-documentation:back-office} \\
+Point-of-Sales Documentation: \cite{taler-documentation:pos-manual} \\
+Git Repositories:
+\begin{itemize}
+\item Backend: \cite{taler-git:merchant}
+\item Backoffice: \cite{taler-git:backoffice}
+\item Point-of-Sales App: \cite{taler-git:android} (part of android repo)
+\end{itemize}
+Language: C (Backend), Kotlin (\ac{PoS}), [Python, Javascript] (Backoffice)\\
+Dependencies:
+\begin{itemize}
+ \item GNUnet >= 0.14.0
+ \item GNU libmicrohttpd >= 0.9.71
+ \item Postgres >= 9.6, including libpq
+ \item libjansson >= 2.7
+ \item libargon2 >= 20171227
+ \item libsodium >= 1.0
+ \item libgcrypt >= 1.6
+ \item libqrencode >= 4.0.0
+ \item libcurl >= 7.26 (or libgnurl >= 7.26)
+ \item GNU libunistring >= 0.9.3
+ \item libsqlite3 >= 3.16.2
+ \item Flask (Backoffice)
+\end{itemize}
+Frontend Repositories:
+\begin{itemize}
+ \item Payments with Django: \cite{taler-git:django-payments}
+ \item Wordpress woocommerce plugin: \cite{taler-git:woocommerce}
+ \item Saleor Frontend: \cite{taler-git:saleor}
+ \item Demo Frontends: \cite{taler-git:merchant-demos}
+\end{itemize}
+The merchant’s API is implemented as REST API and uses \ac{JSON} as message format.
+The \ac{PoS} app is for the merchant to process customer's orders by adding or removing products, calculating the amount owed by the customer or letting the customer make a Taler payment via QR code or NFC.
+The back office Web service allows a merchant to check status of their Taler transactions.
+There are already a lot of demo pages and integrations for different e-commerce solutions.
+
+There are a lot more technical details written in the documentation linked above and in the README files.
+Since Taler is actively developed and technical details could change, we refer to this documentation.
+
+\subsection{Wallet}
+The following text is cited from section 4.6 in \cite{dold:the-gnu-taler-system}
+\begin{center}
+ \textit{
+ "The wallet manages the customer’s reserves and coins, lets the customer view and pay for contracts from merchants. \dots
+ The reference implementation of the GNU Taler wallet is written in the Type-Script language against the WebExtension API, a cross-browser mechanism for browser extensions.
+ The reference wallet is a “tightly integrated†wallet, as it directly hooks into the browser to process responses with the HTTP status code“402 Payment Requiredâ€.\\
+ Many cryptographic operations needed to implement the wallet are not commonly available in a browser environment.
+ We cross-compile the GNU Taler utility library written in C as well as its dependencies (such as libgcrypt) to asm.js (and WebAssembly on supported platforms) using the LLVM-based emscripten toolchain.
+ Cryptographic operations run in an isolated process implemented as a WebWorker.
+ This design allows the relatively slow cryptographic operations to run concurrently in the background in multiple threads.
+ Since the crypto WebWorkers are started on-demand, the wallet only uses minimal resources when not actively used."
+ }
+\end{center}
+
+\subsubsection{Optimizations}
+\begin{center}
+ \textit{
+ "To improve the perceived performance of cryptographic operations, the wallet optimistically creates signatures in the background while the user is looking at the “confirm payment†dialog.
+ If the user does not accept the contract, these signatures are thrown away instead of being sent to the merchant.
+ This effectively hides the latency of the most expensive cryptographic operations, as they are done while the user consciously needs to make a decision on whether to proceed with a payment."
+ }
+\end{center}
+
+\subsubsection{Wallet Detection}
+\begin{center}
+ \textit{
+ " \dots
+ Browser fingerprinting is a concern with any additional APIs made available to websites, either by the browser itself or by browser extensions.
+ Since a website can simply try to trigger a payment to determine whether a tightly integrated Taler wallet is installed, one bit of additional fingerprinting information is already available through the usage of Taler.
+ The dynamic detection methods do not, however, expose any information that is not already available to websites by signaling the wallet through HTTP headers."
+ }
+\end{center}
+
+\subsubsection{Further Wallet Features}
+More information about other Wallet Features like coin selection, wallet liquidation and wallet signaling can be found in sections 4.6.2, 4.6.5 and 4.6.6 of \cite{dold:the-gnu-taler-system}.
+
+\begin{figure}[h!]
+ \centering
+ \includegraphics[height=0.5\textwidth]{taler-wallet.png}
+ \caption{Architecture of the Taler Wallet reference implementation. Source: \cite{dold:the-gnu-taler-system}}
+ \label{fig:taler-wallet-reference-impl}
+\end{figure}
+
+\subsubsection{Technical Details}
+
+Documentation: \cite{taler-documentation:wallet-developer-manual} \\
+Wallet-CLI documentation: \cite{taler-documentation:wallet-cli-manual} \\
+Git Repository:
+\begin{itemize}
+ \item Main repository: \cite{taler-git:wallet-core} \\
+ This Repository includes the wallet-core and the implementations for the web extension and CLI.
+ \item Android app: \cite{taler-git:android}
+ \item iOS app: \cite{taler-git:ios}
+\end{itemize}
+Language: Typescript, Javascript (wallet-core, web extension), Kotlin (Android app), Swift (iOS app)\\
+Dependencies:
+\begin{itemize}
+ \item prettier
+ \item rimraf
+ \item rollup
+ \item typescript
+ \item ava
+ \item esbuild
+ \item axios
+ \item tslib
+ \item cancellationtoken
+ \item minimatch
+ \item source-map-support
+ \item big-integer
+ \item fflate
+ \item esm
+ \item jed
+ \item nyc
+ \item po2json
+ \item typedoc
+ \item api-extractor
+\end{itemize}
+
+There are a lot more technical details written in the documentation linked above and in the README files.
+Since Taler is actively developed and technical details could change, we refer to this documentation.
+
+
diff --git a/doc/cs/images/bfh_logo.png b/doc/cs/images/bfh_logo.png
new file mode 100644
index 000000000..e3aba1175
--- /dev/null
+++ b/doc/cs/images/bfh_logo.png
Binary files differ
diff --git a/doc/cs/images/diagram-simple.png b/doc/cs/images/diagram-simple.png
new file mode 100644
index 000000000..d6ad2921e
--- /dev/null
+++ b/doc/cs/images/diagram-simple.png
Binary files differ
diff --git a/doc/cs/images/logo-2021.png b/doc/cs/images/logo-2021.png
new file mode 100644
index 000000000..ea599bdab
--- /dev/null
+++ b/doc/cs/images/logo-2021.png
Binary files differ
diff --git a/doc/cs/images/projectplan.png b/doc/cs/images/projectplan.png
new file mode 100644
index 000000000..edad872b2
--- /dev/null
+++ b/doc/cs/images/projectplan.png
Binary files differ
diff --git a/doc/cs/images/taler-exchange.png b/doc/cs/images/taler-exchange.png
new file mode 100644
index 000000000..2e898b0ac
--- /dev/null
+++ b/doc/cs/images/taler-exchange.png
Binary files differ
diff --git a/doc/cs/images/taler-merchant.png b/doc/cs/images/taler-merchant.png
new file mode 100644
index 000000000..15b746129
--- /dev/null
+++ b/doc/cs/images/taler-merchant.png
Binary files differ
diff --git a/doc/cs/images/taler-pki.png b/doc/cs/images/taler-pki.png
new file mode 100644
index 000000000..f72d42315
--- /dev/null
+++ b/doc/cs/images/taler-pki.png
Binary files differ
diff --git a/doc/cs/images/taler-wallet.png b/doc/cs/images/taler-wallet.png
new file mode 100644
index 000000000..234d8a0e7
--- /dev/null
+++ b/doc/cs/images/taler-wallet.png
Binary files differ
diff --git a/doc/cs/images/taler_bigger.png b/doc/cs/images/taler_bigger.png
new file mode 100644
index 000000000..cd55fa643
--- /dev/null
+++ b/doc/cs/images/taler_bigger.png
Binary files differ
diff --git a/doc/cs/images/taler_cut_and_choose.png b/doc/cs/images/taler_cut_and_choose.png
new file mode 100644
index 000000000..47ad4d0d0
--- /dev/null
+++ b/doc/cs/images/taler_cut_and_choose.png
Binary files differ
diff --git a/doc/cs/images/taler_refresh_link_threat.png b/doc/cs/images/taler_refresh_link_threat.png
new file mode 100644
index 000000000..03d0804fb
--- /dev/null
+++ b/doc/cs/images/taler_refresh_link_threat.png
Binary files differ
diff --git a/doc/cs/images/taler_refresh_transfer_key.png b/doc/cs/images/taler_refresh_transfer_key.png
new file mode 100644
index 000000000..d75360f28
--- /dev/null
+++ b/doc/cs/images/taler_refresh_transfer_key.png
Binary files differ
diff --git a/doc/cs/thesis.tex b/doc/cs/thesis.tex
new file mode 100644
index 000000000..b6abb0d74
--- /dev/null
+++ b/doc/cs/thesis.tex
@@ -0,0 +1,93 @@
+%============================ MAIN DOCUMENT ================================
+% define document class
+\documentclass[
+ a4paper % paper format
+% ,10.5pt % fontsize
+% ,BCOR=18mm % Binding correction
+% ,twoside
+% ,headings=openright
+ ,bibliography=totoc % If enabled add bibliography to TOC
+ ,class=scrreprt % If removed, makes book format (1 side left, 1 right)
+ ,listof=totoc % If enabled add lists to TOC
+% ,bilingual
+ ,monolingual
+% ,invert-title % Invert the BFH colors
+]{bfhthesis} % KOMA-script report
+
+\input{ads/header.tex}
+
+\begin{document}
+
+\frontmatter
+
+
+\input{variable.sty}
+
+%---------------- BFH tile page -----------------------------------------
+\maketitle
+
+% Abstract
+\input{ads/abstract}
+
+%------------ TABLEOFCONTENTS ----------------
+\tableofcontents
+
+%------------ START MAIN PART ----------------
+\mainmatter
+
+%------------ Introduction
+\input{content/1_introduction.tex}
+
+%------------ Project management
+% \input{content/2_project_management.tex}
+
+%------------ Preliminaries
+\input{content/3_preliminaries.tex}
+
+%------------ Execution
+\input{content/4_execution.tex}
+
+%------------ Discussion
+\input{content/5_discussion.tex}
+
+%------------ Conclusion
+\input{content/6_conclusion.tex}
+
+
+
+%------------ Eidesstattliche Erklärung
+% \includepdf[pages=1]{ads/Erklaerung}
+%------------ History
+% \input{ads/history}
+
+%------------ Abbildungsverzeichnis
+%\cleardoublepage
+\listoffigures
+
+%------------ Tabellenverzeichnis
+%\cleardoublepage
+\listoftables
+
+% TODO
+%------------ Quellcodeverzeichnis
+% \cleardoublepage
+ \phantomsection \label{listoflist}
+ \addcontentsline{toc}{chapter}{List of listings}
+ \lstlistoflistings
+
+%------------ Literaturverzeichnis
+%\cleardoublepage
+\printbibliography
+
+%------------ Abkürzungsverzeichnis
+%\cleardoublepage
+\phantomsection \label{listofacs}
+\addcontentsline{toc}{chapter}{Abbreviations}
+\input{ads/abbreviation}
+
+%------------ Glossar
+\printglossary[style=altlist,title=Glossary]
+
+%------------ Anhang
+\input{content/appendix.tex}
+\end{document}
diff --git a/doc/cs/variable.sty b/doc/cs/variable.sty
new file mode 100644
index 000000000..18ae77a7c
--- /dev/null
+++ b/doc/cs/variable.sty
@@ -0,0 +1,15 @@
+\title{Adding Schnorr's Blind Signature in Taler}
+\subtitle{An improved Taler Blind Signature System}
+\author{{Gian Demarmels}, {Lucien Heuzeveldt}}
+\institution{Bern University of Applied Science}
+\department{Engineering and Computer Sciences}
+\institute{Institute for Cybersecurity and Engineering ICE}
+\version{1.0}
+\titlegraphic{\includegraphics[width=\width]{logo-2021.png}}
+\advisor{Prof. Dr. Emmanuel Benoist}
+\expert{Elektronikingenieur HTL Daniel Voisard}
+\degreeprogram{Bachelor of Science in Computer Science}
+\setupSignature{
+ G. Demarmels={\includegraphics[width=\linewidth]{logo-2021.png}},
+ L. Heuzeveldt={\includegraphics[width=\linewidth]{bfh_logo.png}\vskip-\baselineskip}
+}
diff --git a/doc/doxygen/taler.doxy b/doc/doxygen/taler.doxy
index 425ac22f4..1516811c5 100644
--- a/doc/doxygen/taler.doxy
+++ b/doc/doxygen/taler.doxy
@@ -1,17 +1,144 @@
-# Doxyfile 1.5.6
+# Doxyfile 1.9.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+#
+# Note:
+#
+# Use doxygen to compare the used configuration file with the template
+# configuration file:
+# doxygen -x [configFile]
+# Use doxygen to compare the used configuration file with the template
+# configuration file without replacing the environment variables:
+# doxygen -x_noenv [configFile]
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
PROJECT_NAME = "GNU Taler: Exchange"
-PROJECT_NUMBER = 0.6.0
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER = 0.10.2
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF =
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
PROJECT_LOGO = logo.svg
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
OUTPUT_DIRECTORY = .
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
+# sub-directories (in 2 levels) under the output directory of each output format
+# and will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
+# control the number of sub-directories.
+# The default value is: NO.
+
CREATE_SUBDIRS = YES
+
+# Controls the number of sub-directories that will be created when
+# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every
+# level increment doubles the number of directories, resulting in 4096
+# directories at level 8 which is the default and also the maximum value. The
+# sub-directories are organized in 2 levels, the first level always has a fixed
+# number of 16 directories.
+# Minimum value: 0, maximum value: 8, default value: 8.
+# This tag requires that the tag CREATE_SUBDIRS is set to YES.
+
+# CREATE_SUBDIRS_LEVEL = 8
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,
+# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English
+# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,
+# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with
+# English messages), Korean, Korean-en (Korean with English messages), Latvian,
+# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,
+# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,
+# Swedish, Turkish, Ukrainian and Vietnamese.
+# The default value is: English.
+
OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
ABBREVIATE_BRIEF = "The $name class" \
"The $name widget" \
"The $name file" \
@@ -23,279 +150,2550 @@ ABBREVIATE_BRIEF = "The $name class" \
a \
an \
the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
FULL_PATH_NAMES = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
STRIP_FROM_PATH = ../..
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
STRIP_FROM_INC_PATH = ../../src/include \
src/include \
include
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
JAVADOC_AUTOBRIEF = YES
+
+# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
+# such as
+# /***************
+# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
+# Javadoc-style will behave just like regular comments and it will not be
+# interpreted by doxygen.
+# The default value is: NO.
+
+JAVADOC_BANNER = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
MULTILINE_CPP_IS_BRIEF = NO
+
+# By default Python docstrings are displayed as preformatted text and doxygen's
+# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
+# doxygen's special commands can be used and the contents of the docstring
+# documentation blocks is shown as doxygen documentation.
+# The default value is: YES.
+
+PYTHON_DOCSTRING = YES
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:^^"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". Note that you cannot put \n's in the value part of an alias
+# to insert newlines (in the resulting output). You can put ^^ in the value part
+# of an alias to insert a newline as if a physical newline was in the original
+# file. When you need a literal { or } or , in the value part of an alias you
+# have to escape them by means of a backslash (\), this can lead to conflicts
+# with the commands \{ and \} for these it is advised to use the version @{ and
+# @} or use a double escape (\\{ and \\})
+
ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
OPTIMIZE_OUTPUT_VHDL = NO
+
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files). For instance to make doxygen treat .inc files
+# as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen. When specifying no_extension you should add
+# * to the FILE_PATTERNS.
+#
+# Note see also the list of default file extension mappings.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 5.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS = 5
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
DISTRIBUTE_GROUP_DOC = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
TYPEDEF_HIDES_STRUCT = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use
+# during processing. When set to 0 doxygen will based this on the number of
+# cores available in the system. You can set it explicitly to a value larger
+# than 0 to get more control over the balance between CPU load and processing
+# speed. At this moment only the input processing can be done using multiple
+# threads. Since this is still an experimental feature the default is set to 1,
+# which effectively disables parallel processing. Please report any issues you
+# encounter. Generating dot graphs in parallel is controlled by the
+# DOT_NUM_THREADS setting.
+# Minimum value: 0, maximum value: 32, default value: 1.
+
+NUM_PROC_THREADS = 1
+
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
+# methods of a class will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIV_VIRTUAL = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
EXTRACT_LOCAL_CLASSES = NO
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
EXTRACT_ANON_NSPACES = YES
+
+# If this flag is set to YES, the name of an unnamed parameter in a declaration
+# will be determined by the corresponding definition. By default unnamed
+# parameters remain unnamed in the output.
+# The default value is: YES.
+
+RESOLVE_UNNAMED_PARAMS = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# declarations. If set to NO, these declarations will be included in the
+# documentation.
+# The default value is: NO.
+
HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
INTERNAL_DOCS = YES
+
+# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
+# able to match the capabilities of the underlying filesystem. In case the
+# filesystem is case sensitive (i.e. it supports files in the same directory
+# whose names only differ in casing), the option must be set to YES to properly
+# deal with such files in case they appear in the input. For filesystems that
+# are not case sensitive the option should be set to NO to properly deal with
+# output files written for symbols that only differ in casing, such as for two
+# classes, one named CLASS and the other named Class, and to also support
+# references to files without having to specify the exact matching casing. On
+# Windows (including Cygwin) and MacOS, users should typically set this option
+# to NO, whereas on Linux or other Unix flavors it should typically be set to
+# YES.
+# The default value is: system dependent.
+
CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
HIDE_SCOPE_NAMES = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
+# will show which file needs to be included to use the class.
+# The default value is: YES.
+
+# SHOW_HEADERFILE = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
SHOW_INCLUDE_FILES = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
SORT_MEMBER_DOCS = NO
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
SORT_GROUP_NAMES = YES
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file. See also section "Changing the
+# layout of pages" for information.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
#---------------------------------------------------------------------------
-# configuration options related to warning and progress messages
+# Configuration options related to warning and progress messages
#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
QUIET = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as documenting some parameters in
+# a documented function twice, or documenting parameters that don't exist or
+# using markup commands wrongly.
+# The default value is: YES.
+
WARN_IF_DOC_ERROR = YES
+
+# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
+# function parameter documentation. If set to NO, doxygen will accept that some
+# parameters have no documentation without warning.
+# The default value is: YES.
+
+# WARN_IF_INCOMPLETE_DOC = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong parameter
+# documentation, but not about the absence of documentation. If EXTRACT_ALL is
+# set to YES then this flag will automatically be disabled. See also
+# WARN_IF_INCOMPLETE_DOC
+# The default value is: NO.
+
WARN_NO_PARAMDOC = YES
-WARN_FORMAT = "$file:$line: $text"
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
+# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
+# at the end of the doxygen process doxygen will return with a non-zero status.
+# Possible values are: NO, YES and FAIL_ON_WARNINGS.
+# The default value is: NO.
+
+WARN_AS_ERROR = FAIL_ON_WARNINGS
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# See also: WARN_LINE_FORMAT
+# The default value is: $file:$line: $text.
+
+# WARN_FORMAT = "$file:$line: $text"
+
+# In the $text part of the WARN_FORMAT command it is possible that a reference
+# to a more specific place is given. To make it easier to jump to this place
+# (outside of doxygen) the user can define a custom "cut" / "paste" string.
+# Example:
+# WARN_LINE_FORMAT = "'vi $file +$line'"
+# See also: WARN_FORMAT
+# The default value is: at line $line of file $file.
+
+WARN_LINE_FORMAT = "at line $line of file $file"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr). In case the file specified cannot be opened for writing the
+# warning and error messages are written to standard error. When as file - is
+# specified the warning and error messages are written to standard output
+# (stdout).
+
WARN_LOGFILE =
+
#---------------------------------------------------------------------------
-# configuration options related to the input files
+# Configuration options related to the input files
#---------------------------------------------------------------------------
-INPUT = ../../src ../../contrib ../../doc
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = ../../src
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see:
+# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
+# The default value is: UTF-8.
+
INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# Note the list of default checked file patterns might differ from the list of
+# default file extension mappings.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
+# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
+# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
+# *.vhdl, *.ucf, *.qsf and *.ice.
+
FILE_PATTERNS = *.c \
- *.cc \
- *.cxx \
- *.cpp \
- *.c++ \
- *.d \
- *.java \
- *.ii \
- *.ixx \
- *.ipp \
- *.i++ \
- *.inl \
- *.h \
- *.hh \
- *.hxx \
- *.hpp \
- *.h++ \
- *.idl \
- *.odl \
- *.cs \
- *.php \
- *.php3 \
- *.inc \
- *.m \
- *.mm \
- *.dox \
- *.py \
- *.f90 \
- *.f \
- *.vhd \
- *.vhdl \
- *.C \
- *.CC \
- *.C++ \
- *.II \
- *.I++ \
- *.H \
- *.HH \
- *.H++ \
- *.CS \
- *.PHP \
- *.PHP3 \
- *.M \
- *.MM \
- *.PY \
- *.F90 \
- *.F \
- *.VHD \
- *.VHDL
+ *.h
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
EXCLUDE_SYMLINKS = NO
-EXCLUDE_PATTERNS = */test_* */.svn/* */.git/* */perf_* .*
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS = */test_* \
+ */.git/* \
+ */perf_* \
+ .* \
+ .* \
+ */gnu-taler-error-codes/* \
+ */src/templating/mustach*
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# ANamespace::AClass, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
EXAMPLE_PATTERNS = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
#---------------------------------------------------------------------------
-# configuration options related to source browsing
+# Configuration options related to source browsing
#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
SOURCE_BROWSER = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
INLINE_SOURCES = YES
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# entity all documented functions referencing it will be listed.
+# The default value is: NO.
+
REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
REFERENCES_RELATION = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
VERBATIM_HEADERS = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see:
+# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
+# performance. This can be particularly helpful with template rich C++ code for
+# which doxygen's built-in parser lacks the necessary type information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
+# tag is set to YES then doxygen will add the directory of each input to the
+# include path.
+# The default value is: YES.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_ADD_INC_PATHS = YES
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS =
+
+# If clang assisted parsing is enabled you can provide the clang parser with the
+# path to the directory containing a file called compile_commands.json. This
+# file is the compilation database (see:
+# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
+# options used when the source files were built. This is equivalent to
+# specifying the -p option to a clang tool, such as clang-check. These options
+# will then be passed to the parser. Any options specified with CLANG_OPTIONS
+# will be added as well.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+
+CLANG_DATABASE_PATH =
+
#---------------------------------------------------------------------------
-# configuration options related to the alphabetical class index
+# Configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
ALPHABETICAL_INDEX = YES
-COLS_IN_ALPHA_INDEX = 3
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
IGNORE_PREFIX = TALER_
+
#---------------------------------------------------------------------------
-# configuration options related to the HTML output
+# Configuration options related to the HTML output
#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
HTML_STYLESHEET =
-GENERATE_HTMLHELP = NO
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a color-wheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use gray-scales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via JavaScript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have JavaScript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see:
+# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
+# create a documentation set, doxygen will generate a Makefile in the HTML
+# output directory. Running make will produce the docset in that directory and
+# running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
DOCSET_FEEDNAME = "GNU Taler Source Documentation"
-DOCSET_BUNDLE_ID = net.taler
-HTML_DYNAMIC_SECTIONS = NO
+
+# This tag determines the URL of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+# DOCSET_FEEDURL =
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = net.taler.exchange
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = net.taler
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = "Taler Systems SA"
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# on Windows. In the beginning of 2021 Microsoft took the original page, with
+# a.o. the download links, offline the HTML help workshop was already many years
+# in maintenance mode). You can download the HTML help workshop from the web
+# archives at Installation executable (see:
+# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
+# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the main .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location (absolute path
+# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
+# run qhelpgenerator on the generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
DISABLE_INDEX = NO
-ENUM_VALUES_PER_LINE = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine tune the look of the index (see "Fine-tuning the output"). As an
+# example, the default style sheet generated by doxygen has an example that
+# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
+# Since the tree basically has the same information as the tab index, you could
+# consider setting DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
GENERATE_TREEVIEW = NONE
+
+# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
+# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
+# area (value NO) or if it should extend to the full height of the window (value
+# YES). Setting this to YES gives a layout similar to
+# https://docs.readthedocs.io with more room for contents, but less room for the
+# project logo, title, and description. If either GENERATE_TREEVIEW or
+# DISABLE_INDEX is set to NO, this option has no effect.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+# FULL_SIDEBAR = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
TREEVIEW_WIDTH = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
+# addresses.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+# OBFUSCATE_EMAILS = YES
+
+# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
+# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
+# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
+# the HTML output. These images will generally look nicer at scaled resolutions.
+# Possible values are: png (the default) and svg (looks nicer but requires the
+# pdf2svg or inkscape tool).
+# The default value is: png.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FORMULA_FORMAT = png
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE =
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+# USE_MATHJAX = NO
+
+# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
+# Note that the different versions of MathJax have different requirements with
+# regards to the different settings, so it is possible that also other MathJax
+# settings have to be changed when switching between the different MathJax
+# versions.
+# Possible values are: MathJax_2 and MathJax_3.
+# The default value is: MathJax_2.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_VERSION = MathJax_2
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. For more details about the output format see MathJax
+# version 2 (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
+# (see:
+# http://docs.mathjax.org/en/latest/web/components/output.html).
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility. This is the name for Mathjax version 2, for MathJax version 3
+# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
+# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
+# is the name for Mathjax version 3, for MathJax version 2 this will be
+# translated into HTML-CSS) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment. The default value is:
+# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
+# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH =
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# for MathJax version 2 (see
+# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# For example for MathJax version 3 (see
+# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
+# MATHJAX_EXTENSIONS = ams
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using JavaScript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/). See the section "External Indexing and Searching" for
+# details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
#---------------------------------------------------------------------------
-# configuration options related to the LaTeX output
+# Configuration options related to the LaTeX output
#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
MAKEINDEX_CMD_NAME = makeindex
+
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX. In case there is no backslash (\) as first character
+# it will be automatically added in the LaTeX code.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
COMPACT_LATEX = YES
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
PAPER_TYPE = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
+# the generated LaTeX document. The header should contain everything until the
+# first chapter. If it is left blank doxygen will generate a standard header. It
+# is highly recommended to start with a default header using
+# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
+# and then modify the file new_header.tex. See also section "Doxygen usage" for
+# information on how to generate the default header that doxygen normally uses.
+#
+# Note: Only use a user-defined header if you know what you are doing!
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. The following
+# commands have a special meaning inside the header (and footer): For a
+# description of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
+# the generated LaTeX document. The footer should contain everything after the
+# last chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer. See also section "Doxygen
+# usage" for information on how to generate the default footer that doxygen
+# normally uses. Note: Only use a user-defined footer if you know what you are
+# doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
+# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
+# files. Set this option to YES, to get a higher quality PDF documentation.
+#
+# See also section LATEX_CMD_NAME for selecting the engine.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
LATEX_HIDE_INDICES = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP = NO
+
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY =
+
#---------------------------------------------------------------------------
-# configuration options related to the RTF output
+# Configuration options related to the RTF output
#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
RTF_EXTENSIONS_FILE =
+
#---------------------------------------------------------------------------
-# configuration options related to the man page output
+# Configuration options related to the man page output
#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
MAN_EXTENSION = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
MAN_LINKS = NO
+
#---------------------------------------------------------------------------
-# configuration options related to the XML output
+# Configuration options related to the XML output
#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
XML_PROGRAMLISTING = YES
+
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
#---------------------------------------------------------------------------
-# configuration options for the AutoGen Definitions output
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
GENERATE_AUTOGEN_DEF = NO
+
#---------------------------------------------------------------------------
-# configuration options related to the Perl module output
+# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
PERLMOD_MAKEVAR_PREFIX =
+
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
MACRO_EXPANSION = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
+# RECURSIVE has no effect here.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
INCLUDE_FILE_PATTERNS =
-PREDEFINED = GNUNET_UNUSED="" GNUNET_PACKED=""
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED = GNUNET_UNUSED= \
+ GNUNET_PACKED=
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
SKIP_FUNCTION_MACROS = YES
+
#---------------------------------------------------------------------------
-# Configuration::additions related to external references
+# Configuration options related to external references
#---------------------------------------------------------------------------
-TAGFILES = ../../contrib/gnunet.tag ../../contrib/microhttpd.tag
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES = ../../contrib/gnunet.tag \
+ ../../contrib/microhttpd.tag
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
GENERATE_TAGFILE = taler-exchange.tag
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
EXTERNAL_GROUPS = YES
-PERL_PATH = /usr/bin/perl
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
-CLASS_DIAGRAMS = YES
-MSCGEN_PATH =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: YES.
+
HAVE_DOT = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
+# graph for each documented class showing the direct and indirect inheritance
+# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
+# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
+# to TEXT the direct and indirect inheritance relations will be shown as texts /
+# links.
+# Possible values are: NO, YES, TEXT and GRAPH.
+# The default value is: YES.
+
CLASS_GRAPH = NO
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
COLLABORATION_GRAPH = NO
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies. See also the chapter Grouping
+# in the manual.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
GROUP_GRAPHS = NO
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
+# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
+# tag is set to YES, doxygen will add type and arguments for attributes and
+# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
+# will not generate fields with class member information in the UML graphs. The
+# class diagrams will look similar to the default class diagrams but using UML
+# notation for the relationships.
+# Possible values are: NO, YES and NONE.
+# The default value is: NO.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+DOT_UML_DETAILS = NO
+
+# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
+# to display on a single line. If the actual line length exceeds this threshold
+# significantly it will wrapped across multiple lines. Some heuristics are apply
+# to avoid ugly line breaks.
+# Minimum value: 0, maximum value: 1000, default value: 17.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_WRAP_THRESHOLD = 17
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
CALL_GRAPH = YES
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
CALLER_GRAPH = YES
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
GRAPHICAL_HIERARCHY = NO
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
DIRECTORY_GRAPH = YES
+
+# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
+# of child directories generated in directory dependency graphs by dot.
+# Minimum value: 1, maximum value: 25, default value: 1.
+# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
+
+# DIR_GRAPH_MAX_DEPTH = 1
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd,
+# gif, gif:cairo, gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd,
+# png:cairo, png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
DOT_IMAGE_FORMAT = svg
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
DOTFILE_DIRS =
-DOT_GRAPH_MAX_NODES = 100
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file or to the filename of jar file
+# to be used. If left blank, it is assumed PlantUML is not used or called during
+# a preprocessing step. Doxygen will generate a warning when it encounters a
+# \startuml command in this case and will not generate output for the diagram.
+
+PLANTUML_JAR_PATH =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 1000
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
MAX_DOT_GRAPH_DEPTH = 2
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
DOT_TRANSPARENT = YES
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
+# graphical representation for inheritance and collaboration diagrams is used.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
+# files that are used to generate the various graphs.
+#
+# Note: This setting is not only used for dot files but also for msc temporary
+# files.
+# The default value is: YES.
+
DOT_CLEANUP = YES
-#---------------------------------------------------------------------------
-# Configuration::additions related to the search engine
-#---------------------------------------------------------------------------
-SEARCHENGINE = YES
diff --git a/doc/flows/.gitignore b/doc/flows/.gitignore
new file mode 100644
index 000000000..f0de8a3d5
--- /dev/null
+++ b/doc/flows/.gitignore
@@ -0,0 +1 @@
+main.pdf
diff --git a/doc/flows/Makefile b/doc/flows/Makefile
new file mode 100644
index 000000000..e6af0897c
--- /dev/null
+++ b/doc/flows/Makefile
@@ -0,0 +1,3 @@
+all:
+ pdflatex main.tex
+ pdflatex main.tex
diff --git a/doc/flows/fees-coins.tex b/doc/flows/fees-coins.tex
new file mode 100644
index 000000000..c24f19a28
--- /dev/null
+++ b/doc/flows/fees-coins.tex
@@ -0,0 +1,39 @@
+\section{Fees per coin} \label{sec:fees:coin}
+
+Payments with Taler are always made using coins. Each coin has a specific
+denomination, and an exchange will issue coins in different denominations (in
+the same currency). The fees per coin depend on the operation and the
+denomination.
+
+The primary fee to be paid is a {\bf deposit} fee that is
+charged whenever a coin is fully or partially deposited
+into a bank account or another wallet.
+
+A secondary fee to be paid is a {\bf change} fee that is
+charged whenever a coin partially spent and change must
+be rendered.
+
+Coins also have an {\bf expiration} date of approximately {\bf one year}.
+After the expiration date, coins become worthless. Wallets that are online
+{\bf three months} {\em before} a coin expires will automatically trade any
+such coins for one or more fresh coins with a later expiration date. This
+process is also subject to the {\bf change} fee.
+
+
+\begin{table}[h!]
+ \caption{Fees per coin. Coin denomination values are given in units of CHF 0.01.}
+ \label{table:fees:coins}
+ \begin{center}
+ \begin{tabular}{l|c|r}
+ {\bf Denomination} & {\bf Fee type} & {\bf Amount} \\ \hline \hline
+ $2^{-4}-2^{ 0}$ & deposit & {\em CHF 0.00125} \\
+ $2^{-4}-2^{ 0}$ & change & {\em CHF 0.00125} \\
+ $2^{ 0}-2^{ 3}$ & deposit & {\em CHF 0.00250} \\
+ $2^{ 0}-2^{ 3}$ & change & {\em CHF 0.00125} \\
+ $2^{ 4}-2^{ 8}$ & deposit & {\em CHF 0.005} \\
+ $2^{ 4}-2^{ 8}$ & change & {\em CHF 0.00125} \\
+ $2^{ 8}-2^{12}$ & deposit & {\em CHF 0.01} \\
+ $2^{ 8}-2^{12}$ & change & {\em CHF 0.00125} \\
+ \end{tabular}
+ \end{center}
+\end{table}
diff --git a/doc/flows/fees-wire.tex b/doc/flows/fees-wire.tex
new file mode 100644
index 000000000..214a69021
--- /dev/null
+++ b/doc/flows/fees-wire.tex
@@ -0,0 +1,30 @@
+\section{Fees per wire} \label{sec:fees:wire}
+
+Wire fees apply whenever an exchange needs to initiate a wire transfer to
+another bank account. Wire fees do not apply to every individual payment to a
+merchant, as merchants can choose to {\em aggregate} multiple micropayments
+into one large payment on the wire. Wire fees also do not apply to
+wallet-to-wallet payments within the Taler system.
+
+A {\bf wire} fee is applied when a merchant receives
+an aggregated payment into their bank account.
+
+A {\bf closing} fee is applied when a wallet fails to
+withdraw coins and money has to be sent back to the
+originating bank account.
+
+\begin{table}[h!]
+ \caption{Table with wire fees. Wire fees are set annually.}
+ \label{table:fees:wire}
+ \begin{center}
+ \begin{tabular}{l|c|r}
+ {\bf Year} & {\bf Fee type} & {\bf Amount} \\ \hline \hline
+ 2023 & wire & {\em CHF 0.05} \\
+ 2023 & closing & {\em CHF 0.10} \\
+ 2024 & wire & {\em CHF 0.05} \\
+ 2024 & closing & {\em CHF 0.10} \\
+ 2025 & wire & {\em CHF 0.05} \\
+ 2025 & closing & {\em CHF 0.10} \\
+ \end{tabular}
+ \end{center}
+\end{table}
diff --git a/doc/flows/int-deposit.tex b/doc/flows/int-deposit.tex
new file mode 100644
index 000000000..661f4dccb
--- /dev/null
+++ b/doc/flows/int-deposit.tex
@@ -0,0 +1,52 @@
+\section{Deposit} \label{sec:deposit}
+
+\begin{figure}[h!]
+ \begin{sequencediagram}
+ \newinst{wallet}{\shortstack{Customer wallet \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{exchange}{\shortstack{Taler (exchange) \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{bank}{\shortstack{Retail bank \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] {Checking \\ Accounts};
+ \end{tikzpicture}
+ }}
+ \postlevel
+ \begin{callself}{wallet}{Review deposit fees}{}
+ \end{callself}
+ \mess[0]{wallet}{Deposit {(Coins)}}{exchange}
+ \begin{sdblock}{Acceptable account?}{}
+ \mess[0]{exchange}{{Refuse deposit}}{wallet}
+ \end{sdblock}
+ \begin{sdblock}{KYC/AML required?}{}
+ \begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{}
+ \end{callself}
+ \end{sdblock}
+% \prelevel
+% \prelevel
+% \begin{sdblock}{User abort?}{}
+% \mess[0]{wallet}{{Request abort}}{exchange}
+% \mess[0]{exchange}{{Abort confirmation}}{wallet}
+% \end{sdblock}
+ \mess[0]{exchange}{{Initiate transfer}}{bank}
+
+\end{sequencediagram}
+ \caption{A customer deposits the coins issued by a Taler exchange (payment
+ service provider) into a bank account. Even if the
+ bank account is owned by the same customer, the
+ KYC checks from Section~\ref{sec:kyc:deposit} apply.}
+ \label{fig:int:deposit}
+\end{figure}
+
+We do {\bf not} permit the customer to regain control over their funds {\em
+ unless} they pass the KYC/AML checks. The technical reason is simply that
+the KYC/AML checks happen {\em after} the aggregation logic and at this point
+refunds are no longer permitted. From a compliance perspective, this also
+prevents malicious customers from risk-free probing of the system.
diff --git a/doc/flows/int-pay.tex b/doc/flows/int-pay.tex
new file mode 100644
index 000000000..2968c4c2e
--- /dev/null
+++ b/doc/flows/int-pay.tex
@@ -0,0 +1,60 @@
+\section{Pay} \label{sec:pay}
+
+\begin{figure}[h!]
+ \begin{sequencediagram}
+ \newinst{wallet}{\shortstack{Customer wallet \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
+ \end{tikzpicture}
+ }}
+ \newinst[1]{merchant}{\shortstack{Merchant \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+ \newinst[1]{exchange}{\shortstack{Taler (exchange) \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+ \newinst[1]{bank}{\shortstack{Merchant bank \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] {Commercial \\ Accounts};
+ \end{tikzpicture}
+ }}
+ \postlevel
+ \mess[0]{wallet}{Browse catalog}{merchant}
+ \mess[0]{merchant}{Commercial offer}{wallet}
+ \begin{callself}{wallet}{Review offer}{}
+ \end{callself}
+ \mess[0]{wallet}{Pay {(Coins)}}{merchant}
+ \mess[0]{merchant}{Deposit {(Coins)}}{exchange}
+ \begin{sdblock}{Acceptable account?}{}
+ \mess[0]{exchange}{{Refuse deposit}}{merchant}
+ \mess[0]{merchant}{{Refund purchase}}{wallet}
+ \end{sdblock}
+ \mess[0]{exchange}{{Confirm deposit}}{merchant}
+ \mess[0]{merchant}{Fulfill order}{wallet}
+ \begin{callself}{exchange}{Aggregate transactions}{}
+ \end{callself}
+ \begin{sdblock}{KYC/AML required?}{}
+ \begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{}
+ \end{callself}
+ \end{sdblock}
+ \mess[0]{exchange}{{Initiate transfer}}{bank}
+ \end{sequencediagram}
+ \caption{Payments from a customer to merchant result in
+ depositing coins at the Taler exchange (payment service provider)
+ which then credits the merchant's bank account.
+ The KYC/AML checks are described in Section~\ref{sec:kyc:deposit}}
+ \label{fig:int:pay}
+\end{figure}
+
+{\bf Internal note:} The exchange refusing a deposit immediately based on
+unaccaptable merchant accounts may not be fully implemented (this is a very
+recent feature, after all); especially the merchant then automatically
+refunding the purchase to the customer is certainly missing. However,
+the entire situation only arises when a merchant is incorrectly configured
+and in violation of the terms of service.
diff --git a/doc/flows/int-pull.tex b/doc/flows/int-pull.tex
new file mode 100644
index 000000000..38caef6c8
--- /dev/null
+++ b/doc/flows/int-pull.tex
@@ -0,0 +1,56 @@
+\section{Pull payment (aka invoicing)} \label{sec:pull}
+
+\begin{figure}[h!]
+ \begin{sequencediagram}
+ \newinst{payer}{\shortstack{Payer \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] {Pre-funded \\ Wallet};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{exchange}{\shortstack{Taler (exchange) \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{payee}{\shortstack{Payee \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
+ \end{tikzpicture}
+ }}
+ \postlevel
+ \begin{callself}{payee}{Review pull payment fees}{}
+ \end{callself}
+ \mess[0]{payee}{{Create invoice (Wallet ID)}}{exchange}
+
+ \mess[0]{exchange}{{Invoice ready}}{payee}
+ \mess[0]{payee}{{Send invoice (e.g. via QR code)}}{payer}
+
+ \begin{callself}{payer}{Review invoice}{}
+ \end{callself}
+ \mess[0]{payer}{{Make payment (Coins)}}{exchange}
+
+ \begin{sdblock}{Domestic wallet?}{}
+ \begin{callself}{exchange}{Figure~\ref{fig:proc:domestic}}{}
+ \end{callself}
+ \end{sdblock}
+ \begin{sdblock}{KYC/AML required?}{}
+ \begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{}
+ \end{callself}
+ \end{sdblock}
+
+ \mess[0]{exchange}{{Distribute digital cash}}{payee}
+
+\end{sequencediagram}
+ \caption{Interactions between wallets and Taler exchange
+ in a pull payment. KYC/AML checks are described in
+ Section~\ref{sec:kyc:pull}.}
+ \label{fig:int:pull}
+\end{figure}
+
+We do {\bf not} permit the payer to regain control over their funds, once the
+payment was made they are locked {\em until} the payee passes the KYC/AML
+checks. We only do the AML/KYC process once the funds are locked at the
+exchange. This ensures we know the actual transacted amounts (which may be
+lower than the total amounts requested) and prevents risk-free probing
+attacks.
diff --git a/doc/flows/int-push.tex b/doc/flows/int-push.tex
new file mode 100644
index 000000000..faea50f72
--- /dev/null
+++ b/doc/flows/int-push.tex
@@ -0,0 +1,48 @@
+\section{Push payment} \label{sec:push}
+
+\begin{figure}[h!]
+ \begin{sequencediagram}
+ \newinst{payer}{\shortstack{Payer \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] {Pre-funded \\ Wallet};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{exchange}{\shortstack{Taler (exchange) \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{payee}{\shortstack{Payee \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
+ \end{tikzpicture}
+ }}
+ \postlevel
+ \begin{callself}{payer}{Review push payment fees}{}
+ \end{callself}
+ \mess[0]{payer}{{Push funds (Coins)}}{exchange}
+ \mess[0]{payer}{{Offer payment (e.g. via QR code)}}{payee}
+ \begin{callself}{payee}{Review payment offer}{}
+ \end{callself}
+ \mess[0]{payee}{{Request funds (Wallet ID)}}{exchange}
+ \begin{sdblock}{Domestic wallet?}{}
+ \begin{callself}{exchange}{Figure~\ref{fig:proc:domestic}}{}
+ \end{callself}
+ \end{sdblock}
+ \begin{sdblock}{KYC/AML required?}{}
+ \begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{}
+ \end{callself}
+ \end{sdblock}
+ \mess[0]{exchange}{{Distribute digital cash}}{payee}
+% \postlevel
+ \begin{sdblock}{Payment offer expired?}{}
+ \mess[0]{exchange}{{Return funds}}{payer}
+ \end{sdblock}
+
+\end{sequencediagram}
+ \caption{Interactions between wallets and Taler exchange
+ in a push payment. KYC/AML checks are described
+ in Section~\ref{sec:kyc:push}.}
+ \label{fig:int:push}
+\end{figure}
diff --git a/doc/flows/int-refund.tex b/doc/flows/int-refund.tex
new file mode 100644
index 000000000..3f11e5600
--- /dev/null
+++ b/doc/flows/int-refund.tex
@@ -0,0 +1,39 @@
+\section{Refund}
+
+\begin{figure}[h!]
+ \begin{sequencediagram}
+ \newinst{wallet}{\shortstack{Customer wallet \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{merchant}{\shortstack{Merchant \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{exchange}{\shortstack{Taler (exchange) \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+ \postlevel
+ \begin{callself}{merchant}{Initiate refund}{}
+ \end{callself}
+ \mess[0]{merchant}{{Refund offer (QR code)}}{wallet}
+ \mess[0]{wallet}{Download refund}{merchant}
+ \mess[0]{merchant}{Approve refund}{exchange}
+ \mess[0]{exchange}{Confirm refund}{merchant}
+ \mess[0]{merchant}{Return refund confirmation}{wallet}
+ \end{sequencediagram}
+ \caption{Refund processing when a merchant is unable to fulfill
+ a contract. Refunds must happen {\em before} the
+ exchange has aggregated the original transaction for
+ a bank transfer to the merchant. Furthermore, refunds
+ can only go to the customer who made the original payment
+ and the refund cannot exceed the amount of the original
+ payment.}
+ \label{fig:int:refund}
+\end{figure}
diff --git a/doc/flows/int-shutdown.tex b/doc/flows/int-shutdown.tex
new file mode 100644
index 000000000..e8ee6feed
--- /dev/null
+++ b/doc/flows/int-shutdown.tex
@@ -0,0 +1,48 @@
+\section{Shutdown}
+
+\begin{figure}[h!]
+ \begin{sequencediagram}
+ \newinst{wallet}{\shortstack{Customer wallet \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{exchange}{\shortstack{Taler (exchange) \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{bank}{\shortstack{Customer bank \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] {Checking \\ Accounts};
+ \end{tikzpicture}
+ }}
+ \postlevel
+
+ \begin{callself}{exchange}{Operator initiates shutdown}{}
+ \end{callself}
+ \mess[0]{exchange}{{Shutdown alert}}{wallet}
+ \begin{sdblock}{Bank account known?}{}
+ \begin{callself}{wallet}{Designate bank account}{}
+ \end{callself}
+ \end{sdblock}
+ \mess[0]{wallet}{{Deposit (Coins)}}{exchange}
+ \begin{sdblock}{Acceptable account?}{}
+ \mess[0]{exchange}{{Refuse deposit}}{wallet}
+ \end{sdblock}
+ \begin{sdblock}{KYC/AML required?}{}
+ \begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{}
+ \end{callself}
+ \end{sdblock}
+ \mess[0]{exchange}{{Initiate transfer}}{bank}
+\end{sequencediagram}
+ \caption{Shutdown interactions between customer, Taler exchange (payment
+ service provider) and bank.}
+ \label{fig:int:shutdown}
+\end{figure}
+
+KYC/AML requirements are relaxed in cases where the customer is able to
+cryptographically demonstrate that they previously withdrew these coins from
+the designated checking account. Thus, KYC/AML checks here primarily still
+apply if the customer received the funds via P2P transfers from other wallets.
diff --git a/doc/flows/int-withdraw.tex b/doc/flows/int-withdraw.tex
new file mode 100644
index 000000000..11ae25de9
--- /dev/null
+++ b/doc/flows/int-withdraw.tex
@@ -0,0 +1,49 @@
+\section{Withdraw}
+
+\begin{figure}[h!]
+ \begin{sequencediagram}
+ \newinst{wallet}{\shortstack{Customer wallet \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{exchange}{\shortstack{Taler (exchange) \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{bank}{\shortstack{Customer bank \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] {Checking \\ Accounts};
+ \end{tikzpicture}
+ }}
+ \postlevel
+ \mess[0]{wallet}{Withdraw {(Amount)}}{exchange}
+ \mess[0]{exchange}{{Configuration (ToS, Fees)}}{wallet}
+ \begin{sdblock}{once}{}
+ \begin{callself}{wallet}{Accept ToS}{}
+ \end{callself}
+ \end{sdblock}
+ \begin{callself}{wallet}{Review withdraw fees}{}
+ \end{callself}
+ \mess[0]{wallet}{{Initiate transfer (Amount, Credit account, Wallet ID)}}{bank}
+ \mess[0]{bank}{{Credit (Wallet ID)}}{exchange}
+
+ \begin{sdblock}{Acceptable transfer?}{}
+ \mess[0]{exchange}{{Bounce funds}}{bank}
+ \end{sdblock}
+ \postlevel
+ \mess[0]{exchange}{Confirm wire transfer}{wallet}
+ \mess[0]{wallet}{Request digital cash}{exchange}
+ \mess[0]{exchange}{Distribute digital cash}{wallet}
+ \postlevel
+ \begin{sdblock}{Withdraw period expired?}{}
+ \mess[0]{exchange}{{Return remaining funds}}{bank}
+ \end{sdblock}
+\end{sequencediagram}
+ \caption{Withdraw interactions between customer, Taler exchange (payment
+ service provider) and bank. The amount of digital cash distributed is
+ subject to limits per origin account (see Section~\ref{sec:kyc:withdraw}).}
+ \label{fig:int:withdraw}
+\end{figure}
diff --git a/doc/flows/kyc-balance.tex b/doc/flows/kyc-balance.tex
new file mode 100644
index 000000000..de8953665
--- /dev/null
+++ b/doc/flows/kyc-balance.tex
@@ -0,0 +1,58 @@
+\section{KYC: Balance}
+
+Note: this process is not implemented and would require non-trivial extra work
+if required.
+
+\begin{figure}[h!]
+ \begin{center}
+\begin{tikzpicture}[node distance=1cm,font=\sffamily,
+ start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30},
+ end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30},
+ process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30},
+ failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30},
+ io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30},
+ decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30},
+ arr/.style={very thick,-latex},
+ every edge quotes/.style = {auto, font=\footnotesize, sloped}
+ ]
+ \node (start) [start] {Start};
+ \node (balance) [decision,below=of start,text width=3cm] {Transaction leaves wallet balance below AML threshold?};
+ \node (registered) [decision,below=of balance,text width=3cm] {Wallet has been subject to KYC?};
+ \node (kyc) [process, below=of registered] {KYC process};
+ \node (aml) [process, left=of kyc] {AML process};
+ \node (allow) [end, right=of balance] {Allow};
+ \node (deny) [failed, right=of registered] {Deny};
+ \draw[arr] (start) -> (balance) {};
+ \draw[arr] (balance) -> (registered);
+ \draw (balance) edge["No"] (registered);
+ \draw[arr] (balance) -> (allow);
+ \draw (balance) edge["Yes"] (allow);
+
+ \draw[arr] (registered) -> (kyc);
+ \draw (registered) edge["No"] (kyc);
+ \draw[arr] (registered) -> (deny);
+ \draw (registered) edge["Yes"] (deny);
+
+ \draw[arr] (kyc) -> (deny);
+ \draw (kyc) edge["Failed"] (deny);
+ \draw[arr] (kyc) -> (aml);
+ \draw (kyc) edge["Ok"] (aml);
+
+ \draw[arr] (aml) -> (balance.west);
+ \draw (aml) edge["New threshold"] (balance.west);
+\end{tikzpicture}
+ \end{center}
+ \caption{Regulatory process when a wallet exceeds its AML threshold.
+ When the transfer is denied the transaction (withdraw, P2P transfer)
+ is refused by the wallet.}
+\end{figure}
+
+
+\begin{table}[h!]
+ \caption{Settings for the balance trigger.}
+ \begin{tabular}{l|l|r}
+ {\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline
+ KYC threshold & Amount & {\em 5000 CHF} \\
+ Default AML threshold & Amount & {\em 5000 CHF} \\
+ \end{tabular}
+\end{table}
diff --git a/doc/flows/kyc-deposit.tex b/doc/flows/kyc-deposit.tex
new file mode 100644
index 000000000..0132cebf6
--- /dev/null
+++ b/doc/flows/kyc-deposit.tex
@@ -0,0 +1,85 @@
+\section{KYC: Deposit} \label{sec:kyc:deposit}
+
+\begin{figure}[h!]
+ \begin{center}
+\begin{tikzpicture}[node distance=1cm,font=\sffamily,
+ start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30},
+ end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30},
+ process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30},
+ failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30},
+ io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30},
+ decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30},
+ arr/.style={very thick,-latex},
+ every edge quotes/.style = {auto, font=\footnotesize, sloped}
+ ]
+ \node (start) [start] {Start};
+ \node (country) [decision,below=of start,text width=2.5cm] {Target account in allowed country?};
+ \node (amount) [decision, below=of country,text width=2.5cm] {Target account received less than KYB threshold?};
+ \node (kyc) [process, right=of amount] {KYB process};
+ \node (high) [decision, below=of amount,text width=2.5cm] {Target account received more than its AML threshold?};
+ \node (aml) [process, right=of high] {AML process};
+ \node (dummy) [below right=of aml] {};
+ \node (allow) [end, below right=of dummy] {Allow};
+ \node (deny) [failed, right=of kyc] {Deny};
+ \draw[arr] (start) -> (country) {};
+
+ \draw[arr] (country) -> (amount);
+ \draw (country) edge["Yes"] (amount);
+
+ \draw[arr] (country.east) -> (deny);
+ \draw (country.east) edge["No"] (deny);
+
+ \draw[arr] (amount) -> (high);
+ \draw (amount) edge["Yes"] (high);
+
+ \draw[arr] (amount.east) -> (kyc);
+ \draw (amount.east) edge["No"] (kyc);
+
+ \draw[arr] (kyc) -> (deny);
+ \draw (kyc) edge["Failed"] (deny);
+
+ \draw[arr] (kyc) -> (high);
+ \draw (kyc) edge["Succeeded"] (high);
+
+ \draw[arr] (high.south) -> (allow);
+ \draw (high.south) edge["Yes"] (allow);
+
+ \draw[arr] (high.east) -> (aml);
+ \draw (high.east) edge["No"] (aml);
+
+ \draw[arr] (aml) -> (deny);
+ \draw (aml) edge["Violation"] (deny);
+
+ \draw[arr] (aml) -> (allow);
+ \draw (aml) edge["Ok"] (allow);
+\end{tikzpicture}
+ \end{center}
+ \caption{Regulatory process when depositing digital cash into a bank
+ account. When the transfer is denied, the money is held in escrow
+ until authorities authorize the transfer.}
+\end{figure}
+
+
+\begin{table}[h!]
+ \caption{Settings for the deposit trigger. Note that the operation
+ must satisfy all of the given rules.}
+ \begin{tabular}{l|l|r}
+ {\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline
+ Allowed bank accounts & RFC 8905 RegEx & {\em CH*} \\ \hline
+ KYB deposit threshold & Amount/month & {\em 5000 CHF} \\
+ KYB deposit threshold & Amount/year & {\em 15000 CHF} \\
+ Default AML deposit threshold & Amount/month & {\em 5000 CHF} \\
+ \end{tabular}
+\end{table}
+
+%The KYB deposit threshold of 5'000 \CURRENCY{} per month and than 15'000
+%\CURRENCY{} per year ensure compliance. % with article 48-1b.
+
+Additionally, our terms of service will prohibit businesses to receive
+amounts exceeding 1'000 \CURRENCY{} per transaction.
+%(well below the 15'000 \CURRENCY{} threshold defined in article 24-1c).
+
+SMS-Identification is done by in-house software. KYB data is initially
+obtained and vetted by one of several external KYB providers before
+being passed for manual validation by our own staff who can then
+determine appropriate AML thresholds and set review criteria.
diff --git a/doc/flows/kyc-pull.tex b/doc/flows/kyc-pull.tex
new file mode 100644
index 000000000..4d4f2c852
--- /dev/null
+++ b/doc/flows/kyc-pull.tex
@@ -0,0 +1,92 @@
+\section{KYC/AML: Pull Payment} \label{sec:kyc:pull}
+
+\begin{figure}[h!]
+ \begin{center}
+\begin{tikzpicture}[node distance=0.9cm,font=\sffamily,
+ start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30},
+ end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30},
+ process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30},
+ failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30},
+ io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30},
+ decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30},
+ arr/.style={very thick,-latex},
+ every edge quotes/.style = {auto, font=\footnotesize, sloped}
+ ]
+ \node (start) [start] {Start};
+ \node (wallet) [decision,below=of start,text width=2.5cm] {Wallet linked to (domestic) phone number?};
+ \node (domestic) [process, right=of wallet] {Validate phone number};
+ \node (amount) [decision, below=of wallet,text width=2.5cm] {Wallet received less than KYC threshold from other wallets?};
+ \node (kyc) [process, right=of amount] {KYC process};
+ \node (high) [decision, below=of amount,text width=2.5cm] {Wallet received more than its AML threshold?};
+ \node (aml) [process, right=of high] {AML process};
+ \node (dummy) [below right=of aml] {};
+ \node (allow) [end, below right=of dummy] {Allow invoicing};
+ \node (deny) [failed, right=of kyc] {Deny};
+ \draw[arr] (start) -> (wallet) {};
+
+ \draw[arr] (wallet) -> (amount);
+ \draw (wallet) edge["Yes"] (amount);
+
+ \draw[arr] (wallet.east) -> (domestic);
+ \draw (wallet.east) edge["No"] (domestic);
+
+ \draw[arr] (domestic) -> (amount);
+ \draw (domestic) edge["Confirmed"] (amount);
+
+ \draw[arr] (domestic) -> (deny);
+ \draw (domestic) edge["Failed"] (deny);
+
+ \draw[arr] (amount) -> (high);
+ \draw (amount) edge["Yes"] (high);
+
+ \draw[arr] (amount.east) -> (kyc);
+ \draw (amount.east) edge["No"] (kyc);
+
+ \draw[arr] (kyc) -> (deny);
+ \draw (kyc) edge["Failed"] (deny);
+
+ \draw[arr] (kyc) -> (high);
+ \draw (kyc) edge["Succeeded"] (high);
+
+ \draw[arr] (high.south) -> (allow);
+ \draw (high.south) edge["Yes"] (allow);
+
+ \draw[arr] (high.east) -> (aml);
+ \draw (high.east) edge["No"] (aml);
+
+ \draw[arr] (aml) -> (deny);
+ \draw (aml) edge["Violation"] (deny);
+
+ \draw[arr] (aml) -> (allow);
+ \draw (aml) edge["Ok"] (allow);
+\end{tikzpicture}
+ \end{center}
+ \caption{Regulatory process when receiving payments from another wallet.
+ The threshold depends on the risk profile from the KYC process.
+ When KYC thresholds would be passed, the receiving wallet cannot
+ generate a valid invoice until it has provided the KYC data.
+ When a transfer is denied by AML staff, the money is held in escrow
+ until authorities authorize the transfer.}
+\end{figure}
+
+
+\begin{table}[h!]
+ \caption{Settings for the pull payment trigger. Note that the operation
+ must satisfy all of the given rules.}
+ \begin{tabular}{l|l|r}
+ {\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline
+ Permitted phone numbers & Dialing prefix & {\em +41} \\
+ SMS-Identification & Amount/month & {\em 0 CHF} \\
+ P2P KYC threshold & Amount/month & {\em 5000 CHF} \\
+ P2P KYC threshold & Amount/year & {\em 15000 CHF} \\
+ Default P2P AML threshold & Amount/month & {\em 5000 CHF} \\
+ \end{tabular}
+\end{table}
+
+%The P2P KYC thresholds of 5'000 \CURRENCY{} per month and than 15'000
+%\CURRENCY{} per year ensure compliance with article 49-2c.
+
+SMS-Identification is done by in-house software. KYC data is initially
+obtained and vetted by one of several external KYC providers before
+being passed for manual validation by our own staff who can then
+determine appropriate AML thresholds and set review criteria.
diff --git a/doc/flows/kyc-push.tex b/doc/flows/kyc-push.tex
new file mode 100644
index 000000000..c74b3ce16
--- /dev/null
+++ b/doc/flows/kyc-push.tex
@@ -0,0 +1,90 @@
+\section{KYC/AML: Push Payment} \label{sec:kyc:push}
+
+\begin{figure}[h!]
+ \begin{center}
+\begin{tikzpicture}[node distance=0.9cm,font=\sffamily,
+ start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30},
+ end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30},
+ process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30},
+ failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30},
+ io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30},
+ decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30},
+ arr/.style={very thick,-latex},
+ every edge quotes/.style = {auto, font=\footnotesize, sloped}
+ ]
+ \node (start) [start] {Start};
+ \node (wallet) [decision,below=of start,text width=2.5cm] {Wallet linked to (domestic) phone number?};
+ \node (domestic) [process, right=of wallet] {Validate phone number};
+ \node (amount) [decision, below=of wallet,text width=2.5cm] {Wallet received less than KYC threshold from other wallets?};
+ \node (kyc) [process, right=of amount] {KYC process};
+ \node (high) [decision, below=of amount,text width=2.5cm] {Wallet received more than its AML threshold?};
+ \node (aml) [process, right=of high] {AML process};
+ \node (dummy) [below right=of aml] {};
+ \node (allow) [end, below right=of dummy] {Allow};
+ \node (deny) [failed, right=of kyc] {Deny};
+ \draw[arr] (start) -> (wallet) {};
+
+ \draw[arr] (wallet) -> (amount);
+ \draw (wallet) edge["Yes"] (amount);
+
+ \draw[arr] (wallet.east) -> (domestic);
+ \draw (wallet.east) edge["No"] (domestic);
+
+ \draw[arr] (domestic) -> (amount);
+ \draw (domestic) edge["Confirmed"] (amount);
+
+ \draw[arr] (domestic) -> (deny);
+ \draw (domestic) edge["Failed"] (deny);
+
+ \draw[arr] (amount) -> (high);
+ \draw (amount) edge["Yes"] (high);
+
+ \draw[arr] (amount.east) -> (kyc);
+ \draw (amount.east) edge["No"] (kyc);
+
+ \draw[arr] (kyc) -> (deny);
+ \draw (kyc) edge["Failed"] (deny);
+
+ \draw[arr] (kyc) -> (high);
+ \draw (kyc) edge["Succeeded"] (high);
+
+ \draw[arr] (high.south) -> (allow);
+ \draw (high.south) edge["Yes"] (allow);
+
+ \draw[arr] (high.east) -> (aml);
+ \draw (high.east) edge["No"] (aml);
+
+ \draw[arr] (aml) -> (deny);
+ \draw (aml) edge["Violation"] (deny);
+
+ \draw[arr] (aml) -> (allow);
+ \draw (aml) edge["Ok"] (allow);
+\end{tikzpicture}
+ \end{center}
+ \caption{Regulatory process when receiving payments from another wallet.
+ The threshold depends on the risk profile from the KYC process.
+ When the transfer is denied, the money is held in escrow
+ until authorities authorize the transfer.}
+\end{figure}
+
+
+\begin{table}[h!]
+ \caption{Settings for the push payment trigger. Note that the operation
+ must satisfy all of the given rules.}
+ \begin{tabular}{l|l|r}
+ {\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline
+ Permitted phone numbers & Dialing prefix & {\em +41} \\
+ SMS-Identification & Amount/month & {\em 0 CHF} \\
+ P2P KYC threshold & Amount/month & {\em 5000 CHF} \\
+ P2P KYC threshold & Amount/year & {\em 15000 CHF} \\
+ Default P2P AML threshold & Amount/month & {\em 5000 CHF} \\
+ \end{tabular}
+\end{table}
+
+%The P2P KYC thresholds of 5'000 \CURRENCY{} per month and than 15'000
+%\CURRENCY{} per year ensure compliance. % with article 49-2c.
+
+SMS-Identification is done by in-house software. KYC data is initially
+obtained and vetted by one of several external KYC providers before
+being passed for manual validation by our own staff who can then
+determine appropriate AML thresholds and set review criteria.
diff --git a/doc/flows/kyc-withdraw.tex b/doc/flows/kyc-withdraw.tex
new file mode 100644
index 000000000..b5605618e
--- /dev/null
+++ b/doc/flows/kyc-withdraw.tex
@@ -0,0 +1,58 @@
+\section{KYC: Withdraw} \label{sec:kyc:withdraw}
+
+\begin{figure}[h!]
+ \begin{center}
+\begin{tikzpicture}[node distance=1cm,font=\sffamily,
+ start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30},
+ end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30},
+ process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30},
+ failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30},
+ io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30},
+ decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30},
+ arr/.style={very thick,-latex},
+ every edge quotes/.style = {auto, font=\footnotesize, sloped}
+ ]
+ \node (start) [start] {Start};
+ \node (country) [decision,below=of start,text width=3cm] {Wire transfer originates from allowed country?};
+ \node (amount) [decision, below=of country,text width=3cm] {Transferred less than maximum amount from origin account over last month?};
+ \node (allow) [end, below=of amount] {Allow};
+ \node (deny) [failed, right=of allow] {Deny};
+ \draw[arr] (start) -> (country) {};
+ \draw[arr] (country) -> (amount);
+ \draw (country) edge["Yes"] (amount);
+ \draw[arr] (country.east) -> (deny);
+ \draw (country.east) edge["No"] (deny);
+ \draw[arr] (amount) -> (allow);
+ \draw (amount) edge["Yes"] (allow);
+ \draw[arr] (amount.east) -> (deny);
+ \draw (amount.east) edge["No"] (deny);
+\end{tikzpicture}
+ \end{center}
+ \caption{Regulatory process when withdrawing digital cash from a
+ bank account.
+ If the transfer is denied or the user fails to withdraw the
+ funds for any other reason, the money is automatically returned
+ after the bounce period (see Table~\ref{table:kyc:withdraw:settings}) to
+ the originating bank account.}
+ \label{fig:kyc:withdraw}
+\end{figure}
+
+\begin{table}[h!]
+ \caption{Settings for the withdraw trigger. Note that the operation
+ must satisfy all of the given rules.} \label{table:kyc:withdraw:settings}
+ \begin{tabular}{l|l|r}
+ {\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline
+ Allowed bank accounts & RFC 8905 RegEx & {\em CH*} \\ \hline
+ SMS-Identification & Amount/month & {\em 200 CHF} \\
+ Withdraw limit & Amount/month & {\em 5000 CHF} \\
+ Withdraw limit & Amount/year & {\em 15000 CHF} \\
+ Bounce period & Delay & 1 month \\
+ \end{tabular}
+\end{table}
+
+%The limit of 200 \CURRENCY{} results from article 48-2. Strictly limiting
+%withdrawals to less than 5'000 \CURRENCY{} per month and less than 15'000
+%\CURRENCY{} per year assures compliance with article 48-1c.
+
+SMS-Identification is done by in-house software. Withdraw limits are
+hard and cannot be raised even if the customer is known.
diff --git a/doc/flows/main.de.tex b/doc/flows/main.de.tex
new file mode 100644
index 000000000..5f224007d
--- /dev/null
+++ b/doc/flows/main.de.tex
@@ -0,0 +1,239 @@
+% This is a (partial) translation of main.tex into
+% German. Please keep the structure as parallel as
+% possible when improving / expanding the translation!
+\documentclass[10pt,a4paper,oneside]{book}
+\usepackage[utf8]{inputenc}
+\usepackage{url}
+\usepackage{enumitem}
+\usepackage{graphicx}
+\usepackage{hyperref}
+\usepackage{qrcode}
+\usepackage{pgf-umlsd}
+\usepackage{tikz}
+\usetikzlibrary{shapes,arrows}
+\usetikzlibrary{positioning}
+\usetikzlibrary{calc}
+\usetikzlibrary{quotes}
+\author{Christian Grothoff}
+\title{Flows in the GNU Taler System}
+
+\begin{document}
+
+\tableofcontents
+
+\newcommand\TALER{TALER OPERATIONS AG}
+\newcommand\CURRENCY{CHF}
+\newcommand\LAND{der Schweiz}
+
+\section{Transaktionen im Taler-Bezahlsystem}\label{sec:Transaktionen}
+
+Dieser Abschnitt stellt die Transaktionen im Taler-Bezahlsystem
+vor. Die Grafiken geben wieder, in welcher Reihenfolge die beteiligten
+Parteien interagieren. \\
+F\"ur jede einzelne Transaktion ist die automatische Ausl\"osung von
+Compliance-Prozessen durch den Taler-Exchange einstellbar.
+Die im Rahmen des jeweiligen Compliance-Prozesses erzwungenen
+Pr\"ufschritte beschreibt Abschnitt~\ref{sec:triggers}.
+
+Folgende Transaktionen kommen als Ausl\"oser f\"ur AML- und KYC-Prozesse
+in Betracht:
+\begin{description}[noitemsep]
+ \item[withdraw] Ein Nutzer hebt digitales Bargeld (e-money) in Form von
+ Taler-Coins in ein Taler-Wallet ab
+ \item[reimburse] Ein Nutzer l\"asst den Gegenwert von Taler-Coins vom
+ Taler-Exchange an das urspr\"ungliche IBAN-Bankkonto zur\"uck\"uberweisen
+ \item[pay] Ein Nutzer zahlt zugunsten eines IBAN-Bankkontos des Empf\"angers
+ \item[refund] Ein Verk\"aufer erteilt einem Zahlenden die R\"uckerstattung
+ eines Zahlbetrags
+ \item[push] Ein Nutzer sendet einen Zahlbetrag an ein anderes Taler-Wallet
+ \item[pull] Ein Nutzer stellt einem anderen Taler-Wallet eine Rechnung aus
+ und fordert eine Zahlung von diesem Wallet
+ \item[shutdown] Der Betreiber des Taler-Exchange informiert die Inhaber von
+ Coins, die diese von jenem Exchange abgehoben hatten, dass der Exchange
+ geplant eingestellt und die Gegenwerte der Coins restituiert werden
+\end{description}
+
+Die Nutzer beginnen ein gesch\"aftliches Nutzungsverh\"altnis mit
+\TALER{}, wenn sie ihre Taler-Wallets anweisen, eine Abhebung durchzuf\"uhren.
+Das Taler-Bezahlsystem verwendet jedoch keine Konten, sondern wert-basierte
+Token und explizit keine konten-basierten Geld-\"Aquivalente.
+Taler soll digitales Bargeld sein und erlaubt technisch bedingt
+kein Nachvollziehen der Transaktionen seiner Nutzer, wie es Konten mit
+Eing\"angen und Ausg\"angen von Zahlungen erm\"oglichen w\"urden.
+Es gibt daher kein ``Er\"offnen'' oder ``Schliessen'' von Konten der Nutzer.
+Die Begriffe ``opening'' und ``closing'' lassen sich deshalb auch nicht auf
+das System anwenden oder \"ubertragen. \\
+
+Die Nutzer k\"onnen
+\begin{enumerate}[noitemsep]
+\item die treuh\"andisch verwalteten Einlagen gezielt auf ein bestimmtes
+Bankkonto auszahlen lassen,
+%(siehe Abschnitt~\ref{sec:deposit})
+\item an einen Verk\"aufer zahlen,
+%(siehe Abschnitt~\ref{sec:deposit})
+\item einem anderen Empf\"anger mittels peer-to-peer-Verfahren Coins zukommen
+lassen
+%(siehe Abschnitte~\ref{sec:push} und~\ref{sec:pull})
+\item die Coins in ihrem Wallet, das verloren ging oder zerst\"ort wurde,
+durch Ablauf der G\"ultigkeit entwerten lassen (dies w\"are ebenso der Fall
+bei einer langen Zeit ohne Internet-Anbindung oder ohne Installation),
+\item den Wert der Coins im Wallet durch Zahlung von Geb\"uhren f\"ur
+die Verl\"angerung ihrer G\"ultigkeit langsam verringern lassen.
+%(siehe Abschnitt~\ref{sec:fees:coin})
+\end{enumerate}
+
+Das Taler-Bezahlsystem verwehrt den Nutzern kategorisch die Abhebung
+von h\"oheren Betr\"agen als 5.000 \CURRENCY{} pro Monat bzw. von
+mehr als 15.000 \CURRENCY{} pro Jahr. Damit wird gew\"ahrleistet,
+dass die Nutzer stets unterhalb der Grenzwerte bleiben, ab denen die
+meisten Pr\"ufschritte aufgrund regulatorischer Bestimmungen erforderlich
+werden. \TALER{} stellt dar\"uber hinaus sicher, dass die Nutzer
+ausschliesslich in \LAND{} ans\"assig sind
+(siehe Abschnitt~\ref{sec:proc:domestic}), da auf ihrer Seite ein Bankkonto
+in \LAND{} f\"ur die \"Uberweisungen an den Taler-Exchange und/oder
+eine Telefonnummer mit entsprechender Vorwahl (++41) ben\"otigt werden.
+Zus\"atzlich setzt das Taler-Wallet zu jeder Zeit eine Obergrenze
+von 5.000 \CURRENCY{} auf die Coin-Betr\"age in Summe fest, so dass es
+keine weitere Abhebung \"uber diesen Grenzwert hinaus bewirken kann.
+
+F\"ur {\bf Verk\"aufer} beginnt ein gesch\"aftliches Nutzungsverh\"altnis
+mit \TALER{}, sobald sie Geldeing\"ange auf ihren IBAN-Bankkonten erhalten,
+die als Zahlungen von Nutzern des Taler-Bezahlsystems ausgel\"ost wurden
+(siehe Abschnitt~\ref{sec:deposit}). Sollten die Summen der Eing\"ange
+5.000 \CURRENCY{} pro Monat bzw. 15.000 \CURRENCY{} pro Jahr \"ubersteigen,
+kommt es zu einer KYB-Pr\"ufung, die dem Begriff ``Er\"offnen'' eines
+Kontos entspricht und die eine aktualisierte KYB-Information sowie
+die Pr\"ufung von Sanktionslisten erfordert, sofern der Verk\"aufer
+innerhalb von 24 Monaten wenigstens einen Geldeingang erhielt.
+
+Im Gegensatz zu normalen Nutzern k\"onnen Verk\"aufer im Prinzip
+Zahlungen ohne Limit empfangen. Allerdings m\"ussen diese Transaktionen
+auch wirklich als Eing\"ange auf dem Bankkonto des Unternehmens verzeichnet
+werden (im Kontoauszug). In Abh\"angigkeit von den an das Gesch\"aftskonto
+\"uberwiesenen Betr\"agen wird der Verk\"aufer einer KYB-Pr\"ufung unterzogen
+(siehe Abschnitt~\ref{sec:KYB}). Dies gilt ebenso f\"ur
+Geldw\"asche-\"Uberpr\"ufungen (AML checks).
+
+Das Taler-Bezahlsystem transferiert lediglich Gelder auf die bestehenden
+Bankkonten der Verk\"aufer, die f\"ur ihre G\"uterleistungen Zahlungen
+der Nutzer erhalten, f\"ur die bereits bei der \"Uberweisung von deren
+Kundenkonten eine KYC-Pr\"ufung erfolgte. Daher wird unseres Erachtens
+der Betreiber eines Taler-Exchange keine Mittelherkunft verlangen bzw.
+nachweisen m\"ussen
+\footnote{Wenn Unternehmen das Taler-Bezahlsystem ihrerseits f\"ur
+Zahlungen nutzen wollen, m\"ussen sie genauso wie alle anderen Nutzer
+zuerst Geld von ihrem Bankkonto an einen Taler-Exchange \"uberweisen,
+eine KYC-Pr\"ufung absolvieren und dann ihr Wallet Coins abheben lassen.
+F\"ur die gesch\"aftlichen K\"aufer gelten ebenfalls die Limits wie
+f\"ur alle anderen Nutzer.}.
+
+
+\include{int-withdraw}
+\include{int-deposit}
+\include{int-pay}
+\include{int-refund}
+\include{int-push}
+\include{int-pull}
+\include{int-shutdown}
+
+
+
+\chapter{Regulatory Triggers} \label{chap:triggers}
+
+In this chapter we show decision diagrams for regulatory processes of the
+various core operations of the GNU Taler payment system. In each case, the
+{\bf start} state refers to one of the interactions described in the previous
+chapter. The payment system will then use the process to arrive at an {\bf
+ allow} decision which permits the transaction to go through, or at a {\bf
+ deny} decision which ensures that the funds are not moved.
+
+The specific {\em decisions} (in green) depend on the risk profile and the
+regulatory environment. The tables in each section list the specific values
+that are to be configured.
+
+There are five types if interactions that can trigger regulatory processes:
+
+\begin{description}
+ \item[withdraw] a customer withdraws digital cash from their {\bf bank account}
+ \item[deposit] a merchant's {\bf bank account} is designated to receive a payment in digital cash
+ \item[push] a {\bf wallet} accepts a payment from another wallet
+ \item[pull] a {\bf wallet} requests a payment from another wallet
+ \item[balance] a withdraw or P2P payment causes the balance of a {\bf wallet} to exceed a given threshold
+\end{description}
+
+We note in bold the {\bf anchor} for the regulator process. The anchor is used
+to link the interaction to an identity. Once an identity has been established
+for a particular anchor, that link is considered established for all types of
+activities involving that anchor. A wallet is uniquely identified in the
+system by its unique cryptographic key. A bank account is uniquely identified
+in the system by its (RFC 8905) bank routing data (usually including BIC, IBAN
+and account owner name).
+
+The KYC and AML processes themselves are described in
+Chapter~\ref{chap:regproc}.
+
+\include{kyc-withdraw}
+\include{kyc-deposit}
+\include{kyc-push}
+\include{kyc-pull}
+\include{kyc-balance}
+
+\chapter{Regulatory Processes} \label{chap:regproc}
+
+This chapter describes the interactions between the customer, exchange and
+organizations or staff assisting with regulatory processes designed to ensure
+that customers are residents in the area of operation of the payment service
+provider, are properly identified, and do not engage in money laundering.
+
+The three main regulatory processes are:
+
+\begin{description}
+\item[domestic check] This process establishes that a user is generally
+ eligible to use the payment system. The process checks that the user has an
+ eligible address, but stops short of establishing the user's identity.
+\item[kyc] This process establishes a user's legal identity, possibly
+ using external providers to review documents and check against blacklists.
+\item[aml] The AML process reviews suspicious payment activities for
+ money laundering. Here AML staff reviews all collected information.
+\end{description}
+
+\include{proc-domestic}
+%\include{proc-kyc}
+\include{proc-kyb}
+\include{proc-aml}
+
+\chapter{Fees} \label{chap:fees}
+
+The business model for operating a Taler exchange is to charge transaction
+fees. Fees are charged on certain operations by the exchange. There are two
+types of fees, {\bf wire fees} and {\bf coin fees}. This chapter describes
+the fee structure.
+
+Fixed, amount-independent {\bf wire fees} are charged on wire transfers using
+the core banking system. Details on wire fees are described in
+Section~\ref{sec:fees:wire}.
+
+Coin fees are more complex, as they do not exactly follow neither the usual
+percentage of volume model of other payment systems. Instead, coin fees are
+applied per coin, resulting in a {\em logarithmic} fee structure. As a
+result, the effective fee {\em percentage} for tiny transactions is high (for
+example 50\% for transactions of 0.0025 CHF) while the effective fee
+percentage for large transactions is nominal (for example $\approx$ 0.05\% for
+transactions of $\approx$ 40 CHF). Details on coin fees are described in
+Section~\ref{sec:fees:coin}.
+
+Fees are configurable (and that fee types beyond those described here are
+supported by the software). Thus, the specific fees may be adjusted in the
+future based on business decisions. However, changes to the fees are never
+retroactively applied to coins already in circulation. Wire fees that have
+been publicly announced for a particular time period also cannot be changed.
+Finally, any change to the terms of service must also be explicitly accepted
+by the users before they withdraw additional funds.
+
+
+\include{fees-wire}
+\include{fees-coins}
+%\include{fees-other}
+
+
+\end{document}
diff --git a/doc/flows/main.tex b/doc/flows/main.tex
new file mode 100644
index 000000000..d46d93251
--- /dev/null
+++ b/doc/flows/main.tex
@@ -0,0 +1,206 @@
+\documentclass[10pt,a4paper,oneside]{book}
+\usepackage[utf8]{inputenc}
+\usepackage{url}
+\usepackage{graphicx}
+\usepackage{hyperref}
+\usepackage{qrcode}
+\usepackage{pgf-umlsd}
+\usepackage{tikz}
+\usetikzlibrary{shapes,arrows}
+\usetikzlibrary{positioning}
+\usetikzlibrary{calc}
+\usetikzlibrary{quotes}
+\author{Christian Grothoff}
+\title{Flows in the GNU Taler System}
+
+\newcommand\CURRENCY{CHF}
+
+\begin{document}
+
+\maketitle
+\tableofcontents
+
+\chapter{Interactions} \label{chap:interactions}
+
+This chapter introduces the main payment interactions in the GNU Taler payment
+system. For each interaction, we introduce the parties involved and in which
+order they interact and how. In each interaction it is possible that the
+Taler exchange needs to trigger a compliance process. These regulatory
+riggers are described in more detail in Chapter~\ref{chap:triggers}.
+
+The main interactions of the system are:
+
+\begin{description}
+ \item[withdraw] a customer withdraws digital cash to their wallet
+ \item[deposit] a customer returns digital cash into their bank account
+ \item[pay] a customer pays into bank account of a merchant
+ \item[refund] a merchant decides to return funds to a customer
+ \item[push] a customer sends a payment to another wallet
+ \item[pull] a customer requests a payment from another wallet (effectively sending an invoice)
+ \item[shutdown] the Taler payment system operator informs the customers that the system is being shut down for good
+\end{description}
+
+In the analysis of the legal requirements, it is important to differentiate
+between transactions between wallets (customer-to-customer) and transactions
+where money flows from a wallet into a bank account (customer-to-merchant) as
+these have different limits: When digital coins are used to pay at a business in
+Taler, the business never actually receives usable digital coins but instead
+the amount is always directly credited to their bank account. Depending on
+the transacted amounts, the business will nevertheless be subject to KYB
+(Section~\ref{sec:proc:kyb}) and AML checks.
+
+{\bf Customers} begin their business relationship with us when they withdraw
+digital cash. Taler has no accounts (this is digital cash) and thus there is
+no ``opening'' or ``closing'' of accounts for consumers. Given digital cash,
+the customers can either (1) deposit the funds explicitly into a bank account
+(see Section~\ref{sec:deposit}), (2) pay a merchant (see
+Section~\ref{sec:pay}), (3) pay another customer using a peer-to-peer
+transfer (see Sections~\ref{sec:push} and~\ref{sec:pull}), or (4) the coins
+will expire if the wallet was lost (including offline for a long time or
+uninstalled). Finally, if a wallet remains (occasionally) online but a user
+does simply not spend the coins will (5) diminish in value from the change
+fees (see Section~\ref{sec:fees:coin}) that apply to prevent the coins from
+expiring outright.
+
+For customers, we will categorically limit of digital cash withdrawn per month
+to less than CHF 5'000 per month and less than CHF 15'000 per year, thus
+ensuring that consumers remain below the thresholds where most regulatory
+processes become applicable. Payments between users will be limited
+to receiving less than CHF 2'500 per month and less than CHF 15'000 per year.
+We will ensure that customers are Swiss
+(see Section~\ref{sec:proc:domestic}) by requiring them to have a Swiss bank
+account and/or a Swiss phone number (+41-prefix).
+%Furthermore, the wallet will
+%impose an upper limit of CHF 5000 on its balance at any point in time.
+
+For {\bf merchants}, the Taler equivalent of ``opening'' an account and thus
+establishing an ongoing business relationship is for a business to receive
+payments (see Section~\ref{sec:pay}) exceeding CHF 5'000/month or CHF
+15'000/year. We will consider the account ``open'' (and require up-to-date KYB
+information and check sanction lists) as long as the business has made any
+transactions within the last 24 months.
+
+As we will only transfer money into the existing bank accounts of the
+merchants to compensate them for sales made using the Taler payment system, we
+do not need to check the origin of funds for those merchants as they will only
+receive funds from us.\footnote{Should businesses want to use Taler for
+expenditures, they will need to withdraw digital coins from their bank account
+just like customers, and the limits for customers will continue to apply.}
+
+For individual {\bf transactions}, we will impose a limit of CHF
+1'000/transaction (even though our reading of the regulations would permit
+individual transactions up to CHF 15'000).
+
+The following sections describe the respective processes for each of these
+interactions.
+
+\include{int-withdraw}
+\include{int-deposit}
+\include{int-pay}
+\include{int-refund}
+\include{int-push}
+\include{int-pull}
+\include{int-shutdown}
+
+
+\chapter{Regulatory Triggers} \label{chap:triggers}
+
+In this chapter we show decision diagrams for regulatory processes of the
+various core operations of the GNU Taler payment system. In each case, the
+{\bf start} state refers to one of the interactions described in the previous
+chapter. The payment system will then use the process to arrive at an {\bf
+ allow} decision which permits the transaction to go through, or at a {\bf
+ deny} decision which ensures that the funds are not moved.
+
+The specific {\em decisions} (in green) depend on the risk profile and the
+regulatory environment. The tables in each section list the specific values
+that are to be configured.
+
+There are five types if interactions that can trigger regulatory processes:
+
+\begin{description}
+ \item[withdraw] a customer withdraws digital cash from their {\bf bank account}
+ \item[deposit] a customer or merchant's {\bf bank account} is
+ designated to receive a payment due someone paying with or
+ depositing digital cash
+ \item[push] a {\bf wallet} accepts a payment from another wallet
+ \item[pull] a {\bf wallet} requests a payment from another wallet
+% \item[balance] a withdraw or P2P payment causes the balance of a {\bf wallet} to exceed a given threshold
+\end{description}
+
+We note in bold the {\bf anchor} for the regulator process. The anchor is used
+to link the interaction to an identity. Once an identity has been established
+for a particular anchor, that link is considered established for all types of
+activities involving that anchor. A wallet is uniquely identified in the
+system by its unique cryptographic key. A bank account is uniquely identified
+in the system by its (RFC 8905) bank routing data (usually including BIC, IBAN
+and account owner name).
+
+The KYC and AML processes themselves are described in
+Chapter~\ref{chap:regproc}.
+
+\include{kyc-withdraw}
+\include{kyc-deposit}
+\include{kyc-push}
+\include{kyc-pull}
+%\include{kyc-balance}
+
+\chapter{Regulatory Processes} \label{chap:regproc}
+
+This chapter describes the interactions between the customer, exchange and
+organizations or staff assisting with regulatory processes designed to ensure
+that customers are residents in the area of operation of the payment service
+provider, are properly identified, and do not engage in money laundering.
+
+The three main regulatory processes are:
+
+\begin{description}
+\item[domestic check] This process establishes that a user is generally
+ eligible to use the payment system. The process checks that the user has an
+ eligible address, but stops short of establishing the user's identity.
+\item[kyc] This process establishes a user's legal identity, possibly
+ using external providers to review documents and check against blacklists.
+\item[aml] The AML process reviews suspicious payment activities for
+ money laundering. Here AML staff reviews all collected information.
+\end{description}
+
+\include{proc-domestic}
+\include{proc-kyc}
+\include{proc-kyb}
+\include{proc-aml}
+
+\chapter{Fees} \label{chap:fees}
+
+The business model for operating a Taler exchange is to charge transaction
+fees. Fees are charged on certain operations by the exchange. There are two
+types of fees, {\bf wire fees} and {\bf coin fees}. This chapter describes
+the fee structure.
+
+Fixed, amount-independent {\bf wire fees} are charged on wire transfers using
+the core banking system. Details on wire fees are described in
+Section~\ref{sec:fees:wire}.
+
+Coin fees are more complex, as they do not exactly follow neither the usual
+percentage of volume model of other payment systems. Instead, coin fees are
+applied per coin, resulting in a {\em logarithmic} fee structure. As a
+result, the effective fee {\em percentage} for tiny transactions is high (for
+example 50\% for transactions of 0.0025 CHF) while the effective fee
+percentage for large transactions is nominal (for example $\approx$ 0.05\% for
+transactions of $\approx$ 40 CHF). Details on coin fees are described in
+Section~\ref{sec:fees:coin}.
+
+Fees are configurable (and that fee types beyond those described here are
+supported by the software). Thus, the specific fees may be adjusted in the
+future based on business decisions. However, changes to the fees are never
+retroactively applied to coins already in circulation. Wire fees that have
+been publicly announced for a particular time period also cannot be changed.
+Finally, any change to the terms of service must also be explicitly accepted
+by the users before they withdraw additional funds.
+
+
+\include{fees-wire}
+\include{fees-coins}
+%\include{fees-other}
+
+
+\end{document}
diff --git a/doc/flows/proc-aml.tex b/doc/flows/proc-aml.tex
new file mode 100644
index 000000000..04700faf8
--- /dev/null
+++ b/doc/flows/proc-aml.tex
@@ -0,0 +1,47 @@
+\section{AML process}
+
+\begin{figure}[h!]
+ \begin{sequencediagram}
+ \newinst{wallet}{\shortstack{Customer \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Action};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{exchange}{\shortstack{Taler (exchange) \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{staff}{\shortstack{AML staff \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] { Access \\ Token};
+ \end{tikzpicture}
+ }}
+ \postlevel
+ \mess[0]{wallet}{{Initial action}}{exchange}
+ \begin{callself}{exchange}{Establish AML requirement}{}
+ \end{callself}
+ \begin{callself}{exchange}{Queue AML task}{}
+ \end{callself}
+ \mess[0]{exchange}{Wait for AML}{wallet}
+ \mess[0]{staff}{Request AML work}{exchange}
+ \mess[0]{exchange}{{Open AML task(s)}}{staff}
+ \mess[0]{staff}{Request details}{exchange}
+ \mess[0]{exchange}{KYC/AML data}{staff}
+ \begin{callself}{staff}{Review and decide}{}
+ \end{callself}
+ \mess[0]{staff}{{Decision documentation}}{exchange}
+ \mess[0]{exchange}{AML decision}{wallet}
+ \mess[0]{wallet}{{Retry action}}{exchange}
+\end{sequencediagram}
+ \caption{Deposit interactions between customer, Taler exchange (payment
+ service provider) and the AML staff. The process can be
+ triggered by various {\em actions} described in Chapter~\ref{chap:triggers}.
+ AML staff interactions are cryptographically secured and
+ decisions and the provided reasoning are archived by the exchange.
+ AML staff may interact with the customer (out-of-band)
+ in its decision process.
+ }
+ \label{fig:proc:aml}
+\end{figure}
diff --git a/doc/flows/proc-domestic.tex b/doc/flows/proc-domestic.tex
new file mode 100644
index 000000000..f3a1b7b18
--- /dev/null
+++ b/doc/flows/proc-domestic.tex
@@ -0,0 +1,66 @@
+\section{Domestic wallet check} \label{sec:proc:domestic}
+
+\begin{figure}[h!]
+ \begin{sequencediagram}
+ \newinst{wallet}{\shortstack{Customer wallet \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{exchange}{\shortstack{Taler (exchange) \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{sms}{\shortstack{Address validator}}
+
+ \postlevel
+ \mess[0]{wallet}{{P2P payment (Wallet ID)}}{exchange}
+ \begin{callself}{exchange}{New wallet?}{}
+ \end{callself}
+ \mess[0]{exchange}{Request address validation}{sms}
+ \mess[0]{sms}{Validation process ID}{exchange}
+ \mess[0]{exchange}{Request address validation}{wallet}
+ \mess[0]{wallet}{Send address}{sms}
+ \mess[0]{sms}{{Send confirmation code (to address)}}{wallet}
+ \mess[0]{wallet}{Supply confirmation code}{sms}
+ \mess[0]{sms}{{Confirmed customer address}}{exchange}
+ \mess[0]{exchange}{{Confirm completion}}{wallet}
+ \mess[0]{wallet}{{Retry action}}{exchange}
+\end{sequencediagram}
+ \caption{Deposit interactions between customer, Taler exchange (payment
+ service provider) and external address validation service. The process can be
+ triggered by wallet-to-wallet (P2P) payments described in Chapter~\ref{chap:triggers}.}
+ \label{fig:proc:domestic}
+\end{figure}
+
+Our users have to accept the terms of service which restrict the use of the
+service to domestic customers. For interactions with the core banking system,
+this simply means that we only accept payments from or to domestic bank
+accounts. For P2P payments between wallets, we require that the wallets are
+controlled by a domestic entity. We define domestic entities as those that
+are able to receive messages at a domestic address. Two types of addresses are
+supported:
+
+\begin{itemize}
+\item Control over a domestic {\bf mobile phone number} is established
+ by sending an SMS message with a confirmation code to the MSIN.
+\item Control over a domestic {\bf postal address} is established by
+ sending a letter with a confirmation code to the address.
+\end{itemize}
+
+Depending on the type of address, a validation has a limited validity period,
+as shown in Table~\ref{table:proc:domestic}. When the validity period is
+over, a wallet has to re-do the address validation before they can receive any
+further funds through the service.
+
+\begin{table}[h!]
+ \caption{Restrictions on address validations}
+ \label{table:proc:domestic}
+ \begin{tabular}{l|l|r}
+ {\bf Type} & {\bf Validity period} & {\bf Restricted to} \\ \hline \hline
+ Mobile phone number & 12 months & {\em +41} \\
+ Postal address & 36 months & {\em Switzerland} \\
+ \end{tabular}
+\end{table}
diff --git a/doc/flows/proc-kyb.tex b/doc/flows/proc-kyb.tex
new file mode 100644
index 000000000..6c84f414c
--- /dev/null
+++ b/doc/flows/proc-kyb.tex
@@ -0,0 +1,98 @@
+\section{KYB process} \label{sec:proc:kyb}
+
+\begin{figure}[h!]
+ \begin{sequencediagram}
+ \newinst{merchant}{\shortstack{Merchant \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Action};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{exchange}{\shortstack{Taler (exchange) \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{kyb}{\shortstack{KYB provider \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+
+ \postlevel
+ \mess[0]{merchant}{{Initial action}}{exchange}
+ \begin{callself}{exchange}{Establish KYB requirement}{}
+ \end{callself}
+ \mess[0]{exchange}{Request new KYB process}{kyb}
+ \mess[0]{kyb}{{Process identifier (PI)}}{exchange}
+ \mess[0]{exchange}{{KYB required (PI)}}{merchant}
+ \mess[0]{merchant}{{KYB start (PI)}}{kyb}
+ \mess[0]{kyb}{{Request identity documentation}}{merchant}
+ \mess[0]{merchant}{{Upload identity documentation}}{kyb}
+ \begin{callself}{kyb}{Validate documentation}{}
+ \end{callself}
+ \mess[0]{kyb}{{Share documentation (PI)}}{exchange}
+ \mess[0]{kyb}{{Confirm completion}}{merchant}
+ \mess[0]{merchant}{{Retry action}}{exchange}
+\end{sequencediagram}
+ \caption{Deposit interactions between customer, Taler exchange (payment
+ service provider) and external KYB provider. The process can be
+ triggered by various {\em actions} described in Chapter~\ref{chap:triggers}.}
+ \label{fig:proc:kyb}
+\end{figure}
+
+At the beginning of the KYB process, the user needs to specify whether they
+are an {\bf individual} (not incorporated) or a {\bf business}.\footnote{In
+practice, we expect most owners of bank accounts crossing the KYB threshold to
+be businesses, but in principle such a bank account could be owned by an
+individual operating a business without a separate legal entity.} This then
+determines which types of attributes are collected in the KYB process
+(Table~\ref{table:proc:kyb:individual}
+vs. Table~\ref{table:proc:kyb:business}).
+
+\begin{table}
+ \caption{Information collected for unincorporated individuals}
+ \label{table:proc:kyb:individual}
+ \begin{center}
+ \begin{tabular}{l|c|r}
+ {\bf Type} & {\bf Required} & {\bf Example} \\ \hline \hline
+ Surname & yes & Mustermann \\
+ First name(s) & yes & Max \\
+ Date of birth & yes & 1.1.1980 \\
+ Nationality & yes & Swiss \\
+ Actual address of domicile & yes & Seestrasse 3, 8008 Zuerich \\
+ Phone number & no & +41-123456789 \\
+ E-mail & no & me@example.com \\
+ Identification document & yes & JPG image \\
+ Taxpayer identification & yes & ZPV Nr. 253'123'456 \\
+ \end{tabular}
+ \end{center}
+\end{table}
+
+
+\begin{table}
+ \caption{Information collected for businesses. Information on individals is
+ collected for owners with more than 25\% ownership and for those with
+ signature authority for the business.}
+ \label{table:proc:kyb:business}
+ \begin{center}
+ \begin{tabular}{l|c|r}
+ {\bf Type} & {\bf Required} & {\bf Example} \\ \hline \hline
+ Company name & yes & Mega AG \\
+ Registered office & yes & Seestrasse 4, 8008 Zuerich \\
+ Company identification document & yes & PDF file \\
+ Power of attorney arrangement & yes & PDF file \\
+ Business registration number & yes & \\
+ Business registration document & yes & PDF file \\
+ Registration authority & yes & \\ \hline
+ Authorized person name & yes & Max Mustermann \\
+ Share/authorization certification & yes & PDF file \\
+ Identification document & yes & JPG image \\
+ Date of birth & yes & 1.1.1980 \\
+ Nationality & yes & Swiss \\
+ E-mail & yes & me@example.com \\
+ Phone number & no & +41-123456789 \\
+ \end{tabular}
+ \end{center}
+\end{table}
diff --git a/doc/flows/proc-kyc.tex b/doc/flows/proc-kyc.tex
new file mode 100644
index 000000000..87465f44c
--- /dev/null
+++ b/doc/flows/proc-kyc.tex
@@ -0,0 +1,88 @@
+\section{KYC process}
+
+\begin{figure}[h!]
+ \begin{sequencediagram}
+ \newinst{wallet}{\shortstack{Customer \\
+ \\ \begin{tikzpicture}
+ \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Action};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{exchange}{\shortstack{Taler (exchange) \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+ \newinst[2]{kyc}{\shortstack{KYC provider \\
+ \\ \begin{tikzpicture}[shape aspect=.5]
+ \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
+ \node at (1.5,0) {\shortstack{{{\tiny Database}}}};
+ \end{tikzpicture}
+ }}
+
+ \postlevel
+ \mess[0]{wallet}{{Initial action}}{exchange}
+ \begin{callself}{exchange}{Establish KYC requirement}{}
+ \end{callself}
+ \mess[0]{exchange}{Request new KYC process}{kyc}
+ \mess[0]{kyc}{{Process identifier (PI)}}{exchange}
+ \mess[0]{exchange}{{KYC required (PI)}}{wallet}
+ \mess[0]{wallet}{{KYC start (PI)}}{kyc}
+ \mess[0]{kyc}{{Request identity documentation}}{wallet}
+ \mess[0]{wallet}{{Upload identity documentation}}{kyc}
+ \begin{callself}{kyc}{Validate documentation}{}
+ \end{callself}
+ \mess[0]{kyc}{{Share documentation (PI)}}{exchange}
+ \mess[0]{kyc}{{Confirm completion}}{wallet}
+ \mess[0]{wallet}{{Retry action}}{exchange}
+\end{sequencediagram}
+ \caption{Deposit interactions between customer, Taler exchange (payment
+ service provider) and external KYC provider. The process can be
+ triggered by various {\em actions} described in Chapter~\ref{chap:triggers}.}
+ \label{fig:proc:kyc}
+\end{figure}
+
+At the beginning of the KYC process, the user needs to specify whether they
+are an {\bf individual} or a {\bf business}.\footnote{ In practice, we expect
+most wallet-users to be individuals, but in principle a wallet could be owned
+by a business.} This then determines which types of attributes are collected
+in the KYC process (Table~\ref{table:proc:kyc:individual} vs.
+Table~\ref{table:proc:kyc:business}).
+
+\begin{table}
+ \caption{Information collected for individuals}
+ \label{table:proc:kyc:individual}
+ \begin{center}
+ \begin{tabular}{l|c|r}
+ {\bf Type} & {\bf Required} & {\bf Example} \\ \hline \hline
+ Surname & yes & Mustermann \\
+ First name(s) & yes & Max \\
+ Date of birth & yes & 1.1.1980 \\
+ Nationality & yes & Swiss \\
+ Actual address of domicile & yes & Seestrasse 3, 8008 Zuerich \\
+ Phone number & no & +41-123456789 \\
+ E-mail & no & me@example.com \\
+ Identification document & yes & JPG image \\
+ \end{tabular}
+ \end{center}
+\end{table}
+
+\begin{table}
+ \caption{Information collected for businesses}
+ \label{table:proc:kyc:business}
+ \begin{center}
+ \begin{tabular}{l|c|r}
+ {\bf Type} & {\bf Required} & {\bf Example} \\ \hline \hline
+ Company name & yes & Mega AG \\
+ Registered office & yes & Seestrasse 4, 8008 Zuerich \\
+ Company identification document & yes & PDF file \\ \hline
+ Contact person name & yes & Max Mustermann \\
+ Phone number & no & +41-123456789 \\
+ E-mail & yes & me@example.com \\
+ Identification document & yes & JPG image \\
+ Date of birth & yes & 1.1.1980 \\
+ Nationality & yes & Swiss \\ \hline
+ Power of attorney arrangement & yes & PDF file \\
+ \end{tabular}
+ \end{center}
+\end{table}
diff --git a/doc/logos/ai/logotalerv2.ai b/doc/logos/ai/logotalerv2.ai
deleted file mode 100644
index d474079d3..000000000
--- a/doc/logos/ai/logotalerv2.ai
+++ /dev/null
@@ -1,3474 +0,0 @@
-%PDF-1.5 %âãÏÓ
-1 0 obj <</Metadata 2 0 R/OCProperties<</D<</ON[5 0 R 248 0 R 489 0 R 730 0 R]/Order 731 0 R/RBGroups[]>>/OCGs[5 0 R 248 0 R 489 0 R 730 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <</Length 50398/Subtype/XML/Type/Metadata>>stream
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
-<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27 ">
- <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
- <rdf:Description rdf:about=""
- xmlns:dc="http://purl.org/dc/elements/1.1/">
- <dc:format>application/pdf</dc:format>
- <dc:title>
- <rdf:Alt>
- <rdf:li xml:lang="x-default">logotalerv2</rdf:li>
- </rdf:Alt>
- </dc:title>
- </rdf:Description>
- <rdf:Description rdf:about=""
- xmlns:xmp="http://ns.adobe.com/xap/1.0/"
- xmlns:xmpGImg="http://ns.adobe.com/xap/1.0/g/img/">
- <xmp:CreatorTool>Adobe Illustrator CS6 (Macintosh)</xmp:CreatorTool>
- <xmp:CreateDate>2014-09-16T12:36:31+02:00</xmp:CreateDate>
- <xmp:ModifyDate>2014-09-18T11:52:58+02:00</xmp:ModifyDate>
- <xmp:MetadataDate>2014-09-18T11:52:58+02:00</xmp:MetadataDate>
- <xmp:Thumbnails>
- <rdf:Alt>
- <rdf:li rdf:parseType="Resource">
- <xmpGImg:width>256</xmpGImg:width>
- <xmpGImg:height>192</xmpGImg:height>
- <xmpGImg:format>JPEG</xmpGImg:format>
- <xmpGImg:image>/9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA&#xA;AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK&#xA;DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f&#xA;Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAwAEAAwER&#xA;AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA&#xA;AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB&#xA;UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE&#xA;1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ&#xA;qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy&#xA;obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp&#xA;0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo&#xA;+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7&#xA;FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F&#xA;XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqkHn&#xA;Lz35W8naZ+kdfvktYmqIIftTTMP2Yox8THffsO5AxV4lqP55fmf5v5jyLpUWi6QWMUer6gY2kZqh&#xA;fh9T9yp5OoYBX41BJAxViV/5Y826oyt5w87zrLKob6pPctDGrcI2YKJGSIhGnUHgvVZBtxrgVJrn&#xA;yn5NW48x2ei3VvNBc2trHpV5qFeSTGaJ7hlkeNaHgsg5UGx232CrI28syWOl2d95U8z3OixW0U1x&#xA;eXsdxcMsscdsvFOCSUaQTW8hZBGPhkBPZcKo22/Pf81vImox6Z5yt7fW4CCSwaNLkKkjRN+9gqnJ&#xA;WQ1WROXiRir3TyD+ank7zxal9Gu6XkahrjTpwI7mP5pU8l/ylJGKsuxV2KuxV2KuxV2KuxV2KuxV&#xA;2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KsG/Nb80tO8iaRGwj+va9fkxaRpaVLSyHbmwX4vT&#xA;UkVpuTsPZV4GnlTUNVv280/mTPLqGrXgKWNjs9vzV2UWy/Vnd+akf3SJxUtVyxDpir1Hy9+WWp30&#xA;cE2pXcuk2jxktp9okdvM3xzKpd46rCXhmpJHDRflsALVnej+QfJukqPqOk2yuNzO6CWUnxMj8nr9&#xA;OBKerFEiBFRVQbBQABiqUar5L8p6sjJqGk2txyFC5iUP9DqA4+g4q8y8/wD/ADj/ABan6l/ot3LN&#xA;d8afVL2QyVVRskczfEPYPX5jDavnnUNH8w+Vdb+s2rT6Zq1hJUMKxyxN/Qg/Ij2xQ+nPyQ/O2Dzr&#xA;anSNY4W/ma1QFwKKlyg29WMdiP21+kbdCr1rFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FUv1zzDoWg&#xA;2Rvda1CDTrUbercyLGCfBeR+I+w3xV5Tr3/OVX5c2EjRaZDe6y42EsEQhh2/ypyj/cmKsXk/5zCI&#xA;ciLygzJ2ZtQCn7hbN+vFUXp//OX2ku6/pPyzc20f7TW1zHcEfIOlvX78VeieVPz2/LLzLIlva6st&#xA;leyEBbO/H1ZyT0Cs37pifBXJxVn4IIqOmKuxV2KuxVB6zq9ho2k3mrahJ6VlYwvcXEngkaljQdzt&#xA;sO5xV8iwebtY8yeYtS82eYtHt7zTdYSQaUblDJ9UW0bjAsRhKXCJzkEbvGU5SGvKoIwK96/LnyC9&#xA;rOfNGuxV128QejAzNILSGlFjDPVjIV/vHO5NfE1BKUg/5yM8t2d1odnrJjv7q/tri2t4LW0SeWP0&#xA;DcpLdMyQq1CYUarNtQUG+IV2p3+teTvyj05vKz3c11LJDbQSSQPdyxIUdhyQglalAnJkNK0pWmKq&#xA;HmHzHrHmLyL5R1WeyuYdRGs2T3KRwTKyD05BI7KAWSOp/b26VxVX/MLV/wAx7Tzl5XOjTXcOhNDE&#xA;0v1eD14pbgzFbiO7+AqqegV4s0qcdyvJgFxVOTq3nqf8z/qkd3c2/l+Kzs5fqz2iC1mnlE4uFS5a&#xA;HmxSkLfDLsdqeCqZfmb+W9l5v0kvGqxa5bIfqdz0D9zDJ4q3Y/snfxBVfId4mr+Wddi1KyL2Wpaf&#xA;PUA/C0csbUZHHzBVh9GFD7S/LTzzaedfKdprUAEcrjhdQVqY5V2dT9PT2wqyrFXYq7FXYq7FXYq7&#xA;FXYq7FXYq7FXin5w/wDORVj5Xnm0Dywseo+YEql1cNVre0b+U0/vJR/KDRe+9VxV87vZ+cfO2r/X&#xA;tWubjU76c0V5KudzXjGg+FV8FUAe2BXp/lr/AJxs1y5jSW+WOzQ0NJ2q9P8AUQNT6aY2rMoP+ca9&#xA;LVBz1MBu4W3qPvMgxtUJqf8AzjTCyH6nfQyt2EsbRfipkxtXmPm/8kNf0ZWkltWWEdJk+OL/AIJa&#xA;0+mmKrPIv5vefPy8uI7OZm1PQVID6bcsTwUf8s8hqYj7br7d8VfV/knzz5d856KmraHcerCTxnha&#xA;izQyUqY5UqeLD7j1BIwqn+KuxV5D/wA5Da2fqWg+U4b02V3r94GSQQrcKfqjI0aSozIoR53jqzVF&#xA;AdiK4qlfljyvb3HnOy0WNIRp+jKdTvzayh4Jpmcrb0jiWKCMScfW4JGo6dTVmBV6j5xn1C38pa1P&#xA;pt3Dp+oRWNw9pf3JVYLeVYmKTSlw6hI2+JiykUG4wJfJDfmr+eXJkk/MLRpFBoSs+lUPyrBhQqR/&#xA;mr+cITg/n3S+P8q3Ojgfcbc4qxq5/wCcg/zohuJ7eLzOJIonaPnHa6e6MVNCVZYOLLtsw6jGlT2y&#xA;/OT83b60SVfPNjGDQqlzJpULU7Vje2qp9q40q9/zW/O4kFPP+j1HQtPpJp/yQxV7Z+QPmf8AMbWL&#xA;W4bzXqVtrMTuz2moWrQMhjACFAbdY0PF1bt9OApYt/zkh5PitdYt9bt0CwauhjuaDYXMIFG8P3kf&#xA;/EScIQlP/OLfmyXTfNN75amaltqA9eFSdhLH8LU/1gR92FX1ZirsVdirsVdirsVdirsVdirsVeKf&#xA;85FfnDN5XsF8saBP6fmHUY+VxcofitLZqiqntLJSi9wPi68Tirwz8svy01LzRqaRxoSCec0z14ot&#xA;d3c/51wK+tPKPkbQfK9msNhCGueNJrxwPUc96fyr/kjAlkOKuxV2KrXjSRGSRQ6MKMrCoIPYg4q8&#xA;j/M/8mLDULSbUNFgCyqC01ko2I7mL3/yfu8MNq8D8seZvMH5Z+bI9VsKyWzER31kxIjuIa7o3+UO&#xA;qN2PtUFQ+0vLvmDS/MOh2Wt6XL61hfRiWB+hodirDsysCrDsRhVMcVfOH51W0uqfnbo6TQxT6ZYW&#xA;AhZJ51hj+sSpcTDkxDsmwQ1VSRSo3xVn/wCTmnRxL5hvPTWKZtQ+pFEkkmASyhSJR6kxMjUNd23y&#xA;JS9BvLO1vbSezu4UuLS5jaG4t5VDxyRyAq6OrAhlZTQg4q82uf8AnHL8rJ53l/REMfMk8I4o1UV7&#xA;ABaAY2qn/wBC2flZ/wBWuP8A4CP/AJpxtXyZ5g8tW9p5m8w2donG1s9UvraBadI4bmSNRt4Bckh7&#xA;f+Rn5KeQ/Mnke01LVbFJrqRFLuVQmpHuDgJS9E/6Fs/Kz/q1x/8AAR/804LVlPk78t/LXlEt+hoj&#xA;CjKV9IEBACeRooAA3xVJPz809Lr8tr2cir6fNb3Mfz9UQsf+AmbEK+YfId6+mfmdo1yhp/pQU08H&#xA;BX+OSQ+60bkgbxFcVbxV2KuxV2KuxV2KuxV2KoHXdZstE0W+1i+bhaafBJcznvxiUsQK9zSgHjir&#xA;4WW61Xzt5yu9YvgZLzUrgysgq3EMaRxL34otFX2GBX2T5A8n2vlfy/BZIi/W5AHvZR1aSn2a+C9B&#xA;9/fAljmqf85E/k7pWqXmlX+vmK/0+eS1u4RZ3z8JoXMci8kgZTRlIqCRjSqSf85J/ku/2Nfkb5af&#xA;qJ/7F8aVbL/zkv8AkpC3GXzC8bHcB7DUVNPpt8aVdH/zkn+TEq84tekkUdWXT9RI+8W+NKtb/nJb&#xA;8lU+15gdfnYaiP8AsXxpU88mfnF+XHnTVZdK8s6v9fv4YGupYfq11DSFHSNm5TxRr9qRRStd8Vea&#xA;/n5+XsCE6vaRhYLsn1VA2Saldv8AW6/fhCEu/wCcVvOk1pq2oeSL2T9zOGvNMVj9mWPaeNf9dKPT&#xA;/JPjhV9L4q+WP+ciPNHmLQPzHnstMuWs49RtrO8e4iLLKREJYAlQ3EpUE/ZrXvir1H/nHy5a58j3&#xA;E8knq3EmoTvO9KEu6RuTtQb8siUvTcVdirsVfIFzoBvNb8z3AWtdc1YVp4X0wySHs3/ONIp+W9kP&#xA;BV/VkSl6zirsVYH+eU0cX5W64XNOa26KPFmuYgMQr5M0NTJ570lV3P1qI0Hsa4UPvS3/ALiP/VH6&#xA;sKqmKuxV2KuxV2KuxV2KuxV4/wD85Ta5Jp/5YGxiYq+sXsFo9OvppyuG3+cIB+eKvK/+cb/Lkd95&#xA;nhuZUqlmrXRB8Uoqfc7A4Cr6rwJeJ+YPy0/Me817Uruz8q/lvcWtxdTy29xqFhdPeSRvIzI9y6xl&#xA;WmZTWQjq1cVQI/K381R08o/lcP8At3Xn/VPFXhf54+WvMemedbSy1zT9B026OmxTpbeWYZbezKNP&#xA;Mgd0lCsZiUIY/wAoXwwhDLvyK8l+edY8q38+iaJ5N1O0g1GSBp/M1rcXF4riCFzHG0SsohpIGA/m&#xA;LHviVein8rPzUPXyh+Vp/wC3def9U8CWcflb5J17QrrULvzBoPlLS7uRI4rG48rWklvI0ZLNOlw8&#xA;qKxUssZUL4GvbFWT+dtJTVfK2o2rLyb0mli8ecY5inzpTFXyFpF+3ln80NE1VTwS3voTMen7p39O&#xA;YfTG7DCh9xYVfNX/ADl55fdLry95jjUmMiXT7l+wIPqwj6f3n3Yqjv8AnGDzJGy6hokjgNMi3Vup&#xA;/mj+CQD3IKn6MBV79gS7FXYq+bNI1Cyhl80RSgeodd1ihPvfTZJDO/8AnGw1/Lm0I6UXIlL1jFXY&#xA;q8k/P/UUurfQvKUcnGXVLsXN2RU+naWwPN3ChiFDMGrQ/ZOxxCvKtHn0rzd+dOnNokFv+jrMoxub&#xA;aORPUbioPISpEwChOKjj798kh9cgUAHhirsVdirsVdirsVdirsVdir54/wCcwZWGl+V4duD3Ny58&#xA;aokYH/EzgVd/zi/Egj1CTbkIIwPGhap/ViVe+YEuxV2Kvl3/AJyP003v5s2wArw0K0/G8vP6YQhn&#xA;n/OLVsbbybr8J2467L+NjZnAUvZsVdirTqrqVbdWBBHscVfDP5jLw1Esp4sCSCNiCMKH3hhViP5r&#xA;eSo/OXkbUtFoPrTJ61g5/ZuIvij37An4T7E4q+O/InmTU/KvmaGfi0N9p85WWB6qQyEpJE/hUVU4&#xA;Ffa/lzzBp3mDRrbVtPfnb3C1p+0jD7SNToynY4EplirsVfFesa41r5l80wBqcdb1Xb53spySHvX/&#xA;ADjQa/lrZHxRf1ZEpetYqhdT1Ow0vT7jUdQmW3srVGlnmfYKqip/sHfFXzZf+bbrULrXPzIvY2ij&#xA;9P6tolsJza3cVpG4XnCxVl+PmVdlaodiFr9lihkv/OM3lK9kN95t1RSbu9laQOVCVZ6liFUBQKno&#xA;NsKvoHFXYq7FXYq7FXYq7FXYq7FXhv8Azlvpkk/kbS9RQEix1FVlA6BJ4nXkf9mqj6cVY3/zjBqs&#xA;a6jcWTGhnt24DxaNgaf8DywFX0bgS7FXYq8L/MzTEv8A84nRv2fL9kRX/mMvcIQyb8h7VbXTfNMC&#xA;9E11x/3LrLAUvTsVdiqF1W8Wy0y7u2NBbwvJX/VUnFXxVrlodc856dpMY5NfXcNsAP8Ai6VU7fPC&#xA;h90YVdir5s/5yN/J6dbp/Ovl6DkW31e1jG5Kj+/UeNPtAfPFWB/lT+beo+Vb4UJuLCagu7Jmor9g&#xA;6nfi48foOBX1V5V85+XvNFkLrSLpZSADNbNRZoiezx1qPn0PY4Ep5ir89vPmoGHz95ujrSmtal+N&#xA;3JkkPq7/AJxl/wDJZ2H/ABjT9WRKXo3mLzPoPlywN9rN4lpBuEDGryN/JHGKu7eyjFXhfm384dSu&#xA;9WjvdS0s23lGGUxR2zshuw6ShTePEGYMEZeHFkKbleXMgg0hiei2Oqfmhr1tpljYwWXlzTJBxaKN&#xA;k9VY5JPTkb1GdwAszcU5bVpvQYVfV+haLZaLpcGn2aBIYVoABSp7nFUfirsVdirsVdirsVdirsVd&#xA;irGfzL8qDzZ5F1nQQB693bk2hbYC4iIkhJPYeoi19sVfIH5VeZ7ny55ntpnVkltpaSxNs23wSIQe&#xA;hpUYFfa1nd295aQ3ds4kt50WSJx0KsKg4Eq2KuxV8+/nDqx0783uYbiX0CyH3Xl7hCGW/wDOPV39&#xA;b0PzNcVrz119/lp9kMBS9VxV2KvOvzq81Q6V5cOno4Fze7uK7iJTX/hmFPvxCvGP+cevLcvmT80G&#xA;1yWMvp+go1wzn7JnkBjgX5/akH+rkkPrXFXYqtkjSRGjkUMjCjKdwQcVfO/5tf8AOOHrzz655RKw&#xA;zNWSfTqURm7lOI+EnFXhyal5k8samEu0uNPv7dvgkBaKRSO6OtPwOBXo+hf85IedLKNY5riDUEUB&#xA;R9ci+IAf5cRiJPu1caViXmz8mb/Wdb1rVLTUTNquqSpqcVosMaQ89Rmjkki5md3AiF4vxOi1ofnh&#xA;VnXkC9/MLy75cPlpNWsNHktrVp7doYxeXEsUUpheXYzIIlEcjM6qSApNOmClSjVLzQtN1S6vNdvp&#xA;vMfmO2aezkiuf38sV1G7xBPq0p+xGQsgZiY2WqcQ1DhVNdI8n+evzNv63iPpPlzkGS1ZnkJVT8Hq&#xA;ysAZWChV7A8QTVt8VfRXk7yZo/lbTFstPjANB6ktAGYgUqaYqn+KuxV2KuxV2KuxV2KuxV2KuxV2&#xA;KvlP/nI/8tJ/L3mI+ddJiP6J1SUHUFQbQXjdXP8Akzdf9aviMVZH+Rv5uWsdtHoerS8bZm/0adjt&#xA;E7dVb/IY717H57AhXv4IIqNwehwJbxV8o/8AOT9+bT82LMg056Fa/heXeEIehf8AOJ9wbjyLrkx/&#xA;a12b8LK0GApe2YqlPmXzLpnl7TXvr5wAKiKIH4pG/lX+J7Yq+S/zB836x5u8w/VrVWur++lWGC2i&#xA;BJJY8UjQYUPp/wDKP8vYPIvk630tuL6lOfrOqzruGuHABVT/ACxgBF+Ve5wqzTFXYq7FXYqx/wAy&#xA;+Q/K/mOFo9Vskm5dX6HFXlet/wDOKnla4dn027ltSTUJ1A/HFUJcfkF56L1j81XI+wDIrBHPpMjR&#xA;lnWjMUMScSTtQYquh/5xnvb2NINb8w3d3bJIZVt5JHdA7dWVGYqCfGmKs58rfkX5H0BklW3NzOm/&#xA;OQ7V8aDFXoMMEMEaxwoEjUUVRsAMVX4q7FXYq7FXYq7FXYq7FXYq7FXYq7FUNqmmafqunXGnajAl&#xA;zY3cbRXEEgqrowoQcVfJH5ofkt5i/L+9k1jRBLqHlgkt6yjlLagn7FwB+yO0nTxoeoVOPy3/AD9v&#xA;9Lhjsb7/AEuyWgWKRqOg/wCK3329jt8saV7lon5peS9WjUx362srdYrn93T/AGZ+D/hsFJfL/wDz&#xA;l5qVtJ+Z+mTW0yTRHRIB6kbB1qLu62qtcIQ9G/5xI8w6NYflnqz6hfQ2zNrc7BJHVWK/VLUVC/aP&#xA;TsMSlnPmr89PLumxOmlj63OAaTSVSIe9DR2/DBSvA/MfnjzT531tLKwWbUdRum9OCGIV28FUbKo6&#xA;k9B1OFD3j8lfyPg8nKNc1wrd+aJ0K7UeK0VuqxHvIw2d/oG1SxV63irsVdirsVUJr2CGT025s9Ax&#xA;WOOSQgEkAngrUrQ4qs/Sdt/JP/0jz/8ANGKu/Sdt/JP/ANI8/wDzRirv0nbfyT/9I8//ADRirv0n&#xA;bfyT/wDSPP8A80Yq79J238k//SPP/wA0Yq79J238k/8A0jz/APNGKuXUrUsqkSpyIUF4ZUWrGgHJ&#xA;lA3OKorFXYq7FXYq7FXYq7FXYq7FXYq7FXYq0yqylWAKkUIO4IOKvJPPX/ONXknzDLJe6QzeXtSc&#xA;lma2UPbOx7tbkqF/55svyOKvJtU/5x8/ODRXY6b9X1i3WpRradY34jxSf0t/ZScVSZ/Kf5z2zenJ&#xA;5b1Bm8Y4mkHh9pOQxVEWv5c/nfqhCxaDcQq1KtcPFbgA+Pquh/DArMPLv/OLPmS/kWbzbrUdrAd2&#xA;tbGs0pHgZHCxofkr4Ve5+TPy88o+TbM22g2CW7uAJ7pv3lxLT/fkrfERXfiPhHYYqyPFXYqw/wA2&#xA;+b/N2nawmmeWvKx8xOlsLu9kN9FYrEru6RIvqo/N3MT+A264qi/IXnzS/OOlTXVrDNY31jO9nqul&#xA;XahLm0uY/tRyKPvVhsR71AVZLiqFj/46tx/xgg/4nLiqKxVak0Lu6I6s8dBIoIJUncVHbFWoZ4J0&#xA;LwSLKgZ4yyMGAeNijrUd1dSpHYimKvKfLsn5uyfm9rUd9c6HHp0dlpb3sMUd7K4s2nvzAsBaSNRO&#xA;SriV2Xj9khdiMVes4q87/wCcgLu6s/yp1S7tJWguYLrS5IZoyVZWXVLYggjFWdan/vMn/Ge3/wCT&#xA;6YqisVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVSa1/5TLVP+2dp/8Ayfvc&#xA;VYNp/o6L/wA5D69DGyw2et+WrfV9QJPFBPZXRtfUYn4R+6bc4qyny/L5f1LzJe69Za8uqyXcEcNj&#xA;aRTAwQWsdC7RRqxDmWU8mlpuOKjYbqovzJc3Nrpuv3NqxS6g0tpIHUVIkRZ2Ugb7gjFWC+WPLOkL&#xA;+T2j+ZrSD6v5lXQItSbWYdr2a6lsfUlaafeSb1HY8lcnt3Aoqhvy68s69ceSfJt3Y2+kWVs1vpOo&#xA;T6ohl/SMjMsM95zfgFZ7kmRHq2/I4qxfyP5Y0ZP+cfdS85mASeaLSLW9Ts9YkJe6gmsru5kjEMrV&#xA;eNGeHk6qaMS3IGpxVGfn3LLFp35lNG7IzaR5VRipIJWTWLxHU07MrEEdxirNYvJHkx/zCuNFbRLC&#xA;30i30q2vbfSorWGO2uriW4uIppZokVUmNskUQTmDw9UkbnFXjXmcJDB+demW11Jc6fpt35VtbCN5&#xA;GkEEYv8AmbeMkmixSOyBe1KYq+o9T/3mT/jPb/8AJ9MVSyTzLbWms6zFqd/p9ppel2tncPI83CWH&#xA;6w06u90ZOMUcbekoj3rs1e2KrtM88eStVjuZdL1/Tb+KzjMt5JbXcEywxjq8rI7BF92xVQ8rfoi5&#xA;vtU1ey1j9LS6m8cnESco7e3RfThjiiqeCHiz8qfGxJ6UoqyLFXYq7FXYq7FXYq7FXYq7FXYq7FXY&#xA;q7FXYq7FXYq7FXYqxK+80+WtE87Xses6raaY1zplnJB9cnjgDrDPd+oVMjLXj6i1+eKvG/OX1nzh&#xA;5f8AzP8AzDsY5W0ltJj0Ly5NxZWuLO1l9e8uFBofSeUnie4BxV69baZPc67oV/c+Zra6skkmvNF0&#xA;63t4ofVja2kiokiyOzxxxXHLYUrxOKsmmE0d5LKLdriOaKOMhCmxRnJ5B2TrzxVIdM8m+XdLnhks&#xA;tFuo47Zme0s2umktIGcEMYLSS5a3hNHYD00FATTqcVWaZ5H8s6ZcQzWOiXcUdrIZrSy+ts9lBIa/&#xA;HBZvctbRMORoUjFKmnXFUVHonl+w8tXOgLorW/l+4W5S4s2eERFL53edatNsJHmbYHatBTFUFN5P&#xA;8o6pBrX1jRptQh8wmIas0t164lFu5eFFZrlvSWJ2LIkXEKegxVNNa0ix1r0DfaZd+tbFjbXNtcfV&#xA;LiPnQOEntriKVVfiOShqN3xVKB+XXkoWd7aR+WpIoNS+qfXhFMI2lawnNzbO7pcK5dJmLl68mP2i&#xA;cVZBHBKLWGyitrhI45I29W5mEzUSUSHlI0ssrdNq1+7FXnt75aHmT8wfPuleuluzWvle5jlkiFxH&#xA;6lldXV0iyQlk9RGaEK68hUE74qx7849S8y2Ply/8uX+oadqVpNZpqM62Fm9nJbJYavpqyeqpuboG&#xA;N4bmQtVRQIeorir0rSNJvF822+oal5ih1O7TT7hLOxht44P9HuZrd2mJWSRnUNAqqem5xVluKuxV&#xA;2KuxV2KuxV2KuxV2KuxV2KuxVRu722tER7h+IkdY4wAWZnboFVQWO1SdtgCTsDiqsCCAQag7gjFX&#xA;Yq7FXYq7FUq1ryn5W114X1zRrHVXt6/V2vbaG4MddzwMqtxrTtiqZRwQxwrBHGqQooRIlAChQKBQ&#xA;o2pTtiqX6T5W8saPPNcaTpFlp1xcbTzWlvFA8gry+No1UtvvvirH9W8p+crvzwNXtPMUllov1L6u&#xA;lpGsbNDNzBLLHLFLHJ6ndiVZaUFQdlUd/hrzV/1N97/0i6f/ANk+Ku/w15q/6m+9/wCkXT/+yfFV&#xA;ax0nWLC7S61DV7jWYEVgsUkFuhidqD1VFvHGzHjVe/XFW7/S9Y1G8Nzp+rXOjwcFQqkEDGVlLEsy&#xA;3McjLSoA6V+VMVUP8Neav+pvvf8ApF0//snxV3+GvNX/AFN97/0i6f8A9k+KpfaeTvN8Pniy1qfz&#xA;NPdaVb2kkNzaSLEn1h3aqK0MUUcarH9r1Klz9nYdVWU3ei6PeC6F1YwTi+jSG99SJG9aKMsUjlqP&#xA;jRS7UVttz44qgdH8k+TNFeZ9G0DTtMe5T0rhrO0ggMkZ34OY0XkvscVROj+W/LuiLKujaXaaYs5D&#xA;TCzgitw5HQt6aryO/fFUxxV2KuxV2KuxV2KuxV2KuxV2KuxV2KsV8+NcTLpemx29u63V0JTc3QnZ&#xA;I3tB9YjVUtyjyPI0dOHMBl5V5CqlVk1q0z20LzKiTMimRI2LorEbhXITkoPQ8RXwxVUxV2KuxV2K&#xA;vmLVf+c0Y9P1S8sD5aMhtJ5IDIJwAxjcpWlDStMVQv8A0O/F/wBSw3/SQP8AmnFXf9Dvxf8AUsN/&#xA;0kD/AJpxV3/Q78X/AFLDf9JA/wCacVd/0O/F/wBSw3/SQP8AmnFXf9Dvxf8AUsN/0kD/AJpxV3/Q&#xA;78X/AFLDf9JA/wCacVd/0O/F/wBSw3/SQP8AmnFXf9Dvxf8AUsN/0kD/AJpxV3/Q78X/AFLDf9JA&#xA;/wCacVd/0O/F/wBSw3/SQP8AmnFXf9Dvxf8AUsN/0kD/AJpxV3/Q78X/AFLDf9JA/wCacVd/0O/F&#xA;/wBSw3/SQP8AmnFXpf5I/n0v5n6hqloulHThpsMcpYyepz9VitOgpTjir1zFXYq7FXYq7FXYq7FX&#xA;Yq7FXYq7FWHefdU1OzlRYbu5s7RLG9ulFmsXr3d1bhGhtI3ljnCsylmVVTk9NtlYFVkmjG7OnRC7&#xA;m+sTKXX6xRVMqK5VJCEotXQBjxFPDbFUbirsVdirsVfmP5gsry986apaWUElzdz6hcJBbwo0kjsZ&#xA;moqIoLMT4DFULrXlvzFoUqQ63pd5pc0gJjjvYJbdmA6kCVVJxVLcVRmlKpvPiVXCxTOFYBhySF2W&#xA;oNQaEYqrfX5v99wf9I8H/NGKu+vzf77g/wCkeD/mjFXfX5v99wf9I8H/ADRirOfy1/LbzT55e+ls&#xA;LOA2Flb3TNccLGKt1HayS28I9bhUPKqK5GyqakjrirFNatdV0XVbnStRhtEvbR/TuEiSznVWoCR6&#xA;kIkjald6NsduuKoH6/N/vuD/AKR4P+aMVZR590AeWrjREgeKePV9F07V/jtrfkj3kAeSPaMVAkDc&#xA;fanzxViGqKq3nwqFDRwuQoCjk8Ss1ANhucVQmKuxV9Nf84P/APHf81f8wlt/ycfFX11irsVdirsV&#xA;dirsVdirsVdirsVdirsVdirsVdirsVdir84LK8u7H8ytZvbOZ7e7tTrU1vPGSrxyR21yyOrDcFWF&#xA;QcVZd+VXmLV/PdnrP5beZLubVotTsrm88vSXbmaW11SziaeNopJCXVZERw6g7/SaqvMdF0/SLm21&#xA;O51O++qLZ2xezgRQ8tzcswSKJQSOK7l3fso8SMVQ+k/71t/xguP+TD4qzGTyx5Z0fyh5d13Wlvb6&#xA;bzGbqSGCzmitlt4LSc2xLNJDcmV3dWIHwgDxriqbw/lrpx8q2er6bbXnmKXWLu/isDC6Waw2dg8U&#xA;fqyq6ykyymcUXlRaH7WKo26/LjyXoXkrV9e15r24ntdVsLK1srWWKOX/AEnTjeSW8kxSaNXQy8Xf&#xA;0m3joFHLZVB+XNF07TvMP1vS3mbTNZ8p67f2aXJVpowNOv7aSOR0CK5Sa2cBgoqKGg6YqiPN35ce&#xA;T9J/MC48iWQvxd2kUVxda7c3ULW6QrYrf3U31NLVXpHDzovr9uuKobyl5F8jebrDzNc6ZNqOny+W&#xA;tIvdU+r3UkE5u1t4mMTq0cUQhpLx5xnnsdn2xVT/ADt/3p8lf+Afon/Jg4qwZtI1LVNRkhsIGnkt&#xA;7EXc4Wg4QW1oJppCSQKKiE+/Qb4qk+Kproun6Rc22p3Op331RbO2L2cCKHlublmCRRKCRxXcu79l&#xA;HiRir6F/5wf/AOO/5q/5hLb/AJOPir66xV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV+b&#xA;Q/5T/wAwf6muf9Ql1irIf+cck+rfmUnmCXaw8tafqGq38m1FijtZIxWu28kqjFUyu/IXlWzPlSwu&#xA;ZNMtNN1DTdN1HWtRvrtodQ5ahGJZngTmF4QJJSNfTIYr8XI4q8q0n/etv+MFx/yYfFWZHzN5Y1jy&#xA;foGg6617YzeXDdJbXFjBFdLPBdz/AFgq6yzW3pukjNRhyBB6bbqr280eUtW8oWHlrVhf6bHol5eX&#xA;Gk3drHDfM0F8Yy8NwjyWI5oYVIkVt9/gGKqN95x0Q/l7e+UNPtbmKN9dh1azmndJD6EdnLbMspUJ&#xA;8ZZlYcVp1HbdVFaL528v21haG7S7F7pnlzUtCtIoo43imn1KXUCZJJGkRo0jTUF6IxYg9OuKqvm3&#xA;8z7XVvzVuPOtpZP9Uu4Ybe606cgF4W05LC8i5py2kT1FVvAgkdsVRflHz15F8oad5mtdNh1HUZ/M&#xA;uk3mlm6uY4bc2i3ELBEVI5phNWUpzkJT4V2SppiqR/mD5q0fzImgTWbXKXGlaNp2kSwTQxolbK3C&#xA;SSLKs0hblJXjVF+H3xVvyTo+m6pruspfwidLTyzqN5ArEgLPb6UzxPsRujbj3xVkI8t6HfafpNh5&#xA;W8v+XtUvbyxsl+uXmtumoS6hPbxmeNbL6/aqrrcu8aJ6R5UHWuKq135C8q2Z8qWFzJplppuoabpu&#xA;o61qN9dtDqHLUIxLM8CcwvCBJKRr6ZDFfi5HFWcf84P/APHf81f8wlt/ycfFX11irsVdirsVdirs&#xA;VdirsVdirsVdirsVdirsVdirsVdir81zrlvof5lX2p3NiupWkN/eJdae7tEs8MzSRSxmRasvJHIq&#xA;OmKpp5g/M/SW8uXflvyZ5bj8q6VqjI+st9alv7u6ER5RxNcSqhSJTvwVdz361VQd7580bVrLSjru&#xA;htfavotjFptncx3ZhtpYLYFbf61biJ3kManjWOaPkAK4ql35eeXbXzH5z0rRbrVo9EgvZhG2pSkg&#xA;JsTRSKDm/wBlKkCp64qyfU/JENrqV3aj8xtIhEE0kQhuJtXEycHK8ZKWAHMUo1O+KoX/AAjF/wCX&#xA;L0P/AJHax/2Q4q7/AAjF/wCXL0P/AJHax/2Q4qkiXVxJrf6HN2YYjcG1OpNNc0RQ/D13ozfAv22o&#xA;nTFU7/wjF/5cvQ/+R2sf9kOKu/wjF/5cvQ/+R2sf9kOKpl5c/L+31PXtP0+T8xdKnju50heK0m1U&#xA;zkO1D6YksQnIdfiIHiR1xVIDqUfkbzj5js7W7h8wwyWmpaMNRhdljlW9t3t/XViGqU51I3BIoGpR&#xA;sVb8mebPJHlu+07V5PL9/qOuaZNFd287anHDa/WIHEkbfV1s2k4q6iqmbf2xV17580bVrLSjruht&#xA;favotjFptncx3ZhtpYLYFbf61biJ3kManjWOaPkAK4q9m/5wf/47/mr/AJhLb/k4+KvrrFXYq7FX&#xA;Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX55+Zfya/NafzHqs8PlPVJIZby4eKRbWQqytKxUg06&#xA;EYqln/KlPzc/6lDVf+kWX+mKu/5Up+bn/Uoar/0iy/0xV3/KlPzc/wCpQ1X/AKRZf6Yq7/lSn5uf&#xA;9Shqv/SLL/TFXf8AKlPzc/6lDVf+kWX+mKu/5Up+bn/Uoar/ANIsv9MVRB/KL85jD6R8qatxoFr9&#xA;UfkVHRC/HkV/ya0xVD/8qU/Nz/qUNV/6RZf6Yq7/AJUp+bn/AFKGq/8ASLL/AExV3/KlPzc/6lDV&#xA;f+kWX+mKu/5Up+bn/Uoar/0iy/0xV3/KlPzc/wCpQ1X/AKRZf6Yq7/lSn5uf9Shqv/SLL/TFX0F/&#xA;ziF5E85eWda8xy+YNFvNKiuba3SB7uFog7LI5YLyArSuKvp3FXYq7FXYq7FXYq7FXYq7FXYq7FXY&#xA;q7FXYq7FXYq7FX//2Q==</xmpGImg:image>
- </rdf:li>
- </rdf:Alt>
- </xmp:Thumbnails>
- </rdf:Description>
- <rdf:Description rdf:about=""
- xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
- xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
- xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#">
- <xmpMM:OriginalDocumentID>uuid:AF0436EA903B11DB90DFD5CCDBF73423</xmpMM:OriginalDocumentID>
- <xmpMM:DocumentID>xmp.did:0180117407206811822AA5DFA83DBEE8</xmpMM:DocumentID>
- <xmpMM:InstanceID>uuid:a78d6ca4-ee25-5f4f-9857-efe87b47a9ff</xmpMM:InstanceID>
- <xmpMM:RenditionClass>proof:pdf</xmpMM:RenditionClass>
- <xmpMM:DerivedFrom rdf:parseType="Resource">
- <stRef:instanceID>uuid:d0c80ddf-8d24-9c4a-b5a2-f5403c78eac7</stRef:instanceID>
- <stRef:documentID>xmp.did:F87F11740720681183D1ECDDA24ACDE7</stRef:documentID>
- <stRef:originalDocumentID>uuid:AF0436EA903B11DB90DFD5CCDBF73423</stRef:originalDocumentID>
- <stRef:renditionClass>proof:pdf</stRef:renditionClass>
- </xmpMM:DerivedFrom>
- <xmpMM:History>
- <rdf:Seq>
- <rdf:li rdf:parseType="Resource">
- <stEvt:action>saved</stEvt:action>
- <stEvt:instanceID>xmp.iid:0380117407206811822AABA902D116C4</stEvt:instanceID>
- <stEvt:when>2014-09-11T14:30:24+02:00</stEvt:when>
- <stEvt:softwareAgent>Adobe Illustrator CS6 (Macintosh)</stEvt:softwareAgent>
- <stEvt:changed>/</stEvt:changed>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <stEvt:action>saved</stEvt:action>
- <stEvt:instanceID>xmp.iid:0180117407206811822AA5DFA83DBEE8</stEvt:instanceID>
- <stEvt:when>2014-09-16T12:36:28+02:00</stEvt:when>
- <stEvt:softwareAgent>Adobe Illustrator CS6 (Macintosh)</stEvt:softwareAgent>
- <stEvt:changed>/</stEvt:changed>
- </rdf:li>
- </rdf:Seq>
- </xmpMM:History>
- </rdf:Description>
- <rdf:Description rdf:about=""
- xmlns:illustrator="http://ns.adobe.com/illustrator/1.0/">
- <illustrator:StartupProfile>Video</illustrator:StartupProfile>
- <illustrator:Type>Document</illustrator:Type>
- </rdf:Description>
- <rdf:Description rdf:about=""
- xmlns:xmpTPg="http://ns.adobe.com/xap/1.0/t/pg/"
- xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#"
- xmlns:stFnt="http://ns.adobe.com/xap/1.0/sType/Font#"
- xmlns:xmpG="http://ns.adobe.com/xap/1.0/g/">
- <xmpTPg:NPages>1</xmpTPg:NPages>
- <xmpTPg:HasVisibleTransparency>True</xmpTPg:HasVisibleTransparency>
- <xmpTPg:HasVisibleOverprint>False</xmpTPg:HasVisibleOverprint>
- <xmpTPg:MaxPageSize rdf:parseType="Resource">
- <stDim:w>1920.000000</stDim:w>
- <stDim:h>1080.000000</stDim:h>
- <stDim:unit>Pixels</stDim:unit>
- </xmpTPg:MaxPageSize>
- <xmpTPg:Fonts>
- <rdf:Bag>
- <rdf:li rdf:parseType="Resource">
- <stFnt:fontName>OldNewspaperTypes</stFnt:fontName>
- <stFnt:fontFamily>OldNewspaperTypes</stFnt:fontFamily>
- <stFnt:fontFace>Regular</stFnt:fontFace>
- <stFnt:fontType>TrueType</stFnt:fontType>
- <stFnt:versionString>1.0 2007-02-14</stFnt:versionString>
- <stFnt:composite>False</stFnt:composite>
- <stFnt:fontFileName>OldNewspaperTypes.ttf</stFnt:fontFileName>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <stFnt:fontName>Perpetua</stFnt:fontName>
- <stFnt:fontFamily>Perpetua</stFnt:fontFamily>
- <stFnt:fontFace>Regular</stFnt:fontFace>
- <stFnt:fontType>Open Type</stFnt:fontType>
- <stFnt:versionString>Version 1.76</stFnt:versionString>
- <stFnt:composite>False</stFnt:composite>
- <stFnt:fontFileName>Perpetua.ttf</stFnt:fontFileName>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <stFnt:fontName>SmothBight</stFnt:fontName>
- <stFnt:fontFamily>Smoth Bight</stFnt:fontFamily>
- <stFnt:fontFace>Regular</stFnt:fontFace>
- <stFnt:fontType>Open Type</stFnt:fontType>
- <stFnt:versionString>Version 1.00 (Kustren)</stFnt:versionString>
- <stFnt:composite>False</stFnt:composite>
- <stFnt:fontFileName>Smoth-Bight - Por Kustren.otf</stFnt:fontFileName>
- </rdf:li>
- </rdf:Bag>
- </xmpTPg:Fonts>
- <xmpTPg:PlateNames>
- <rdf:Seq>
- <rdf:li>Cyan</rdf:li>
- <rdf:li>Magenta</rdf:li>
- <rdf:li>Yellow</rdf:li>
- <rdf:li>Black</rdf:li>
- </rdf:Seq>
- </xmpTPg:PlateNames>
- <xmpTPg:SwatchGroups>
- <rdf:Seq>
- <rdf:li rdf:parseType="Resource">
- <xmpG:groupName>Groupe de nuances par défaut</xmpG:groupName>
- <xmpG:groupType>0</xmpG:groupType>
- <xmpG:Colorants>
- <rdf:Seq>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>Blanc</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>255</xmpG:red>
- <xmpG:green>255</xmpG:green>
- <xmpG:blue>255</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>Noir</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>0</xmpG:red>
- <xmpG:green>0</xmpG:green>
- <xmpG:blue>0</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>Rouge RVB</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>255</xmpG:red>
- <xmpG:green>0</xmpG:green>
- <xmpG:blue>0</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>Jaune RVB</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>255</xmpG:red>
- <xmpG:green>255</xmpG:green>
- <xmpG:blue>0</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>Vert RVB</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>0</xmpG:red>
- <xmpG:green>255</xmpG:green>
- <xmpG:blue>0</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>Cyan RVB</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>0</xmpG:red>
- <xmpG:green>255</xmpG:green>
- <xmpG:blue>255</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>Bleu RVB</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>0</xmpG:red>
- <xmpG:green>0</xmpG:green>
- <xmpG:blue>255</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>Magenta RVB</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>255</xmpG:red>
- <xmpG:green>0</xmpG:green>
- <xmpG:blue>255</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=193 V=39 B=45</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>193</xmpG:red>
- <xmpG:green>39</xmpG:green>
- <xmpG:blue>45</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=237 V=28 B=36</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>237</xmpG:red>
- <xmpG:green>28</xmpG:green>
- <xmpG:blue>36</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=241 V=90 B=36</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>241</xmpG:red>
- <xmpG:green>90</xmpG:green>
- <xmpG:blue>36</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=247 V=147 B=30</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>247</xmpG:red>
- <xmpG:green>147</xmpG:green>
- <xmpG:blue>30</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=251 V=176 B=59</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>251</xmpG:red>
- <xmpG:green>176</xmpG:green>
- <xmpG:blue>59</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=252 V=238 B=33</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>252</xmpG:red>
- <xmpG:green>238</xmpG:green>
- <xmpG:blue>33</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=217 V=224 B=33</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>217</xmpG:red>
- <xmpG:green>224</xmpG:green>
- <xmpG:blue>33</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=140 V=198 B=63</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>140</xmpG:red>
- <xmpG:green>198</xmpG:green>
- <xmpG:blue>63</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=57 V=181 B=74</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>57</xmpG:red>
- <xmpG:green>181</xmpG:green>
- <xmpG:blue>74</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=0 V=146 B=69</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>0</xmpG:red>
- <xmpG:green>146</xmpG:green>
- <xmpG:blue>69</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=0 V=104 B=55</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>0</xmpG:red>
- <xmpG:green>104</xmpG:green>
- <xmpG:blue>55</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=34 V=181 B=115</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>34</xmpG:red>
- <xmpG:green>181</xmpG:green>
- <xmpG:blue>115</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=0 V=69 B=157</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>0</xmpG:red>
- <xmpG:green>169</xmpG:green>
- <xmpG:blue>157</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=41 V=71 B=226</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>41</xmpG:red>
- <xmpG:green>171</xmpG:green>
- <xmpG:blue>226</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=0 V=113 B=188</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>0</xmpG:red>
- <xmpG:green>113</xmpG:green>
- <xmpG:blue>188</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=46 V=49 B=146</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>46</xmpG:red>
- <xmpG:green>49</xmpG:green>
- <xmpG:blue>146</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=27 V=20 B=100</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>27</xmpG:red>
- <xmpG:green>20</xmpG:green>
- <xmpG:blue>100</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=102 V=45 B=145</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>102</xmpG:red>
- <xmpG:green>45</xmpG:green>
- <xmpG:blue>145</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=147 V=39 B=143</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>147</xmpG:red>
- <xmpG:green>39</xmpG:green>
- <xmpG:blue>143</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=158 V=0 B=93</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>158</xmpG:red>
- <xmpG:green>0</xmpG:green>
- <xmpG:blue>93</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=212 V=20 B=90</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>212</xmpG:red>
- <xmpG:green>20</xmpG:green>
- <xmpG:blue>90</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=237 V=30 B=121</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>237</xmpG:red>
- <xmpG:green>30</xmpG:green>
- <xmpG:blue>121</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=199 V=178 B=153</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>199</xmpG:red>
- <xmpG:green>178</xmpG:green>
- <xmpG:blue>153</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=153 V=134 B=117</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>153</xmpG:red>
- <xmpG:green>134</xmpG:green>
- <xmpG:blue>117</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=115 V=99 B=87</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>115</xmpG:red>
- <xmpG:green>99</xmpG:green>
- <xmpG:blue>87</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=83 V=71 B=65</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>83</xmpG:red>
- <xmpG:green>71</xmpG:green>
- <xmpG:blue>65</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=198 V=156 B=109</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>198</xmpG:red>
- <xmpG:green>156</xmpG:green>
- <xmpG:blue>109</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=166 V=124 B=82</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>166</xmpG:red>
- <xmpG:green>124</xmpG:green>
- <xmpG:blue>82</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=140 V=98 B=57</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>140</xmpG:red>
- <xmpG:green>98</xmpG:green>
- <xmpG:blue>57</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=117 V=76 B=36</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>117</xmpG:red>
- <xmpG:green>76</xmpG:green>
- <xmpG:blue>36</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=96 V=56 B=19</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>96</xmpG:red>
- <xmpG:green>56</xmpG:green>
- <xmpG:blue>19</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=66 V=33 B=11</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>66</xmpG:red>
- <xmpG:green>33</xmpG:green>
- <xmpG:blue>11</xmpG:blue>
- </rdf:li>
- </rdf:Seq>
- </xmpG:Colorants>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:groupName>Gris</xmpG:groupName>
- <xmpG:groupType>1</xmpG:groupType>
- <xmpG:Colorants>
- <rdf:Seq>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=0 V=0 B=0</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>0</xmpG:red>
- <xmpG:green>0</xmpG:green>
- <xmpG:blue>0</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=26 V=26 B=26</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>26</xmpG:red>
- <xmpG:green>26</xmpG:green>
- <xmpG:blue>26</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=51 V=51 B=51</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>51</xmpG:red>
- <xmpG:green>51</xmpG:green>
- <xmpG:blue>51</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=77 V=77 B=77</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>77</xmpG:red>
- <xmpG:green>77</xmpG:green>
- <xmpG:blue>77</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=102 V=102 B=102</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>102</xmpG:red>
- <xmpG:green>102</xmpG:green>
- <xmpG:blue>102</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=128 V=128 B=128</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>128</xmpG:red>
- <xmpG:green>128</xmpG:green>
- <xmpG:blue>128</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=153 V=153 B=153</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>153</xmpG:red>
- <xmpG:green>153</xmpG:green>
- <xmpG:blue>153</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=179 V=179 B=179</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>179</xmpG:red>
- <xmpG:green>179</xmpG:green>
- <xmpG:blue>179</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=204 V=204 B=204</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>204</xmpG:red>
- <xmpG:green>204</xmpG:green>
- <xmpG:blue>204</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=230 V=230 B=230</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>230</xmpG:red>
- <xmpG:green>230</xmpG:green>
- <xmpG:blue>230</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=242 V=242 B=242</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>242</xmpG:red>
- <xmpG:green>242</xmpG:green>
- <xmpG:blue>242</xmpG:blue>
- </rdf:li>
- </rdf:Seq>
- </xmpG:Colorants>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:groupName>Groupe de couleurs pour films et vidéos</xmpG:groupName>
- <xmpG:groupType>1</xmpG:groupType>
- <xmpG:Colorants>
- <rdf:Seq>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=123 V=112 B=49</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>123</xmpG:red>
- <xmpG:green>112</xmpG:green>
- <xmpG:blue>49</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=199 V=163 B=21</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>199</xmpG:red>
- <xmpG:green>163</xmpG:green>
- <xmpG:blue>21</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=182 V=165 B=85</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>182</xmpG:red>
- <xmpG:green>165</xmpG:green>
- <xmpG:blue>85</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=235 V=208 B=101</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>235</xmpG:red>
- <xmpG:green>208</xmpG:green>
- <xmpG:blue>101</xmpG:blue>
- </rdf:li>
- <rdf:li rdf:parseType="Resource">
- <xmpG:swatchName>R=122 V=111 B=185</xmpG:swatchName>
- <xmpG:mode>RGB</xmpG:mode>
- <xmpG:type>PROCESS</xmpG:type>
- <xmpG:red>122</xmpG:red>
- <xmpG:green>111</xmpG:green>
- <xmpG:blue>185</xmpG:blue>
- </rdf:li>
- </rdf:Seq>
- </xmpG:Colorants>
- </rdf:li>
- </rdf:Seq>
- </xmpTPg:SwatchGroups>
- </rdf:Description>
- <rdf:Description rdf:about=""
- xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
- <pdf:Producer>Adobe PDF library 10.01</pdf:Producer>
- </rdf:Description>
- </rdf:RDF>
-</x:xmpmeta>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-<?xpacket end="w"?> endstream endobj 3 0 obj <</Count 2/Kids[7 0 R 8 0 R]/Type/Pages>> endobj 7 0 obj <</ArtBox[224.515 66.2373 1617.31 1080.0]/BleedBox[0.0 0.0 1920.0 1080.0]/Contents 732 0 R/Group 733 0 R/LastModified(D:20140918115258+02'00')/MediaBox[0.0 0.0 1920.0 1080.0]/Parent 3 0 R/PieceInfo<</Illustrator 734 0 R>>/Resources<</ColorSpace<</CS0 735 0 R/CS1 736 0 R/CS2 737 0 R/CS3 738 0 R/CS4 739 0 R/CS5 740 0 R/CS6 741 0 R/CS7 742 0 R/CS8 743 0 R>>/ExtGState<</GS0 744 0 R/GS1 745 0 R/GS2 746 0 R/GS3 747 0 R/GS4 748 0 R/GS5 749 0 R/GS6 750 0 R/GS7 751 0 R>>/Font<</T1_0 752 0 R/TT0 753 0 R/TT1 754 0 R>>/ProcSet[/PDF/Text/ImageC/ImageI]/Properties<</MC0 730 0 R>>/Shading<</Sh0 755 0 R>>/XObject<</Fm0 756 0 R/Fm1 757 0 R/Fm2 758 0 R/Fm3 759 0 R/Fm4 760 0 R/Fm5 761 0 R/Fm6 762 0 R/Im0 763 0 R/Im1 764 0 R/Im2 765 0 R/Im3 766 0 R/Im4 763 0 R/Im5 767 0 R/Im6 768 0 R/Im7 769 0 R/Im8 770 0 R/Im9 771 0 R>>>>/TrimBox[0.0 0.0 1920.0 1080.0]/Type/Page>> endobj 8 0 obj <</ArtBox[6464.01 6726.74 7856.81 7766.5]/BleedBox[0.0 0.0 14400.0 14400.0]/Contents 772 0 R/Group 773 0 R/LastModified(D:20140918115258+02'00')/MediaBox[0.0 0.0 14400.0 14400.0]/Parent 3 0 R/PieceInfo<</Illustrator 734 0 R>>/Resources<</ColorSpace<</CS0 735 0 R/CS1 736 0 R/CS2 737 0 R/CS3 738 0 R/CS4 739 0 R/CS5 740 0 R/CS6 741 0 R/CS7 742 0 R/CS8 743 0 R>>/ExtGState<</GS0 744 0 R/GS1 745 0 R/GS2 774 0 R/GS3 747 0 R/GS4 775 0 R/GS5 776 0 R/GS6 777 0 R/GS7 778 0 R>>/Font<</T1_0 752 0 R/TT0 753 0 R/TT1 754 0 R>>/ProcSet[/PDF/Text/ImageC/ImageI]/Properties<</MC0 730 0 R>>/Shading<</Sh0 755 0 R>>/XObject<</Fm0 779 0 R/Fm1 780 0 R/Fm2 781 0 R/Fm3 782 0 R/Fm4 783 0 R/Fm5 784 0 R/Fm6 785 0 R/Im0 763 0 R/Im1 764 0 R/Im2 765 0 R/Im3 766 0 R/Im4 763 0 R/Im5 767 0 R/Im6 768 0 R/Im7 769 0 R/Im8 770 0 R/Im9 771 0 R>>>>/TrimBox[0.0 0.0 14400.0 14400.0]/Type/Page>> endobj 772 0 obj <</Filter/FlateDecode/Length 3434>>stream
-H‰Ô—[o%W…ßûWìÇÉÃiïûå‘q†A`KŒ…Ã\‚ì‰Ï·ªût·í&y
-²äsNuWíº¬ZUûê7×îê××Þ½üôÚMW×7Þ]߸`îæúó)ΡG÷-Ï>ãÙ»‡©ù1æà««-¶¹ÇîN5eSš[Ëî›7ÓËéëÉ»³¿ü?-<û½û`O?“½ {W?¿÷îÓ¯¦ßò÷õTKäDß]Ë%Ï%5w¥äç’›«5¦yøàZDâ“œÉcεí’óô\v7ÕÞ
-_²:Z{:¿.`é©èn•ÖçQÛ,×6fŸÃÁ§‹äèÓE†©-šMs‹x³þ,/ç5—ä1*1–y¤2B
-Îów:
-Öcqñb —–£;ßO/_»«›÷Þ=¼w¯^»¥
-?¨za©žaçüàüLfìÒÿÒœ{8£æ·ðe¤8!W,¨!?ôPÿOÁ‡9ñôÔy ‚žæLë—¸KÎÓ)ö<·Ôo¥Jaj:ºH¼.?–§ÍmžX›
-¡¶è²Ÿ+' íçP½Uˆ#Ëo¼­–m%Ä9õ°ÄüÓî(4˜©Ú)uH#º +¬æ±S"Ú/|]´ï¤ÃìyÁ¡ÖÔäx—ˆ2zRäyË3‰Ñ°Ý\C[röÆ´)’7Õ¹FøV˜CÁ;Äu6-ß;: ‰Òªm
-χ!LØzDÍ[HKÀ߬èH!Ö
-+´Ÿð 1CÉ*Ž-<Ê5ø&]aÏ5/ù,
-Ø팦4>¢/šc,kå ¬yÚ3¶EzÊ{Õ„ñMõÅÄIäâíØØiEÀ6æÞº-p]Í NÏ+N¡±!:HF ,RC,)Ö!§U]·¾aËßW¤Í €:VÀÖ ¨­gí˜Í¾+WtHÉu<jŽšŽ˜×ø8ñ%Û’öbTÅ£¶¹¬“ Kþ¥õZ0Eg‹A ã] ª),2I¶‘±Û„ëà È:š9QuƒÅÕÚIa¡B^‚”›òÚaÖÚ`X›™Ã;*-΃¥¹nmšwº½
-ž__)´Î´ ÛTYÐë®^ &vyûåõpJOÇSú÷ŸÒŸ²¨NIû)ß·ÀGþñ¿»¹þ|ÒvÜ·n‚`ê&kÌ ±Ž&7Í5Ћ,Ý,Ûƒ•â6üÙ/+D`X3ìš¹¼ý¨5iç¶:0‘BH6‡ÿðâüæÃ'53R'ÂY•¡ùâ‹»OþtûËÕÍw“6^h’¨Åç¯Ó‹OÿÁ‹qëÜ– ´¼òÄ}ù gµ]]ßxw~p¶Q=úïÎÀ-y·¥yTY ™ÊCùÖ³û‰”Ä ¤p¥ ƒyã¨ëÊ.Ðn ýFÍ©Ã Ðqƒ>w»`µŸuâ·ß¾½ŸÞªÒ/3 wíÆ’ôÃnóò´˜IææérÈÅ«ÓâF:ÌOÝ WG·WN›g›™£h=æ´»{zì¯7o oì5Qp;†Àü§|BÐÌ­{ Þ4°¯tV]Zƒ.yý¬Ûïót‚ÔPÞ߀³z
-»õ··w—ïöH¯.ªlÖËO^_Î^û‹š7[ï­)@#Ëê»IKuxÖZìÁvÕä’ª›Âw¶ÖË¥µÂǵË ¶ëIbeúßuh¨ÿÒO?¸—5Ô%Ëm–o
-ÞÙ9À×ö XçϜǽƒÞÃû
-ym$æµ1øò¹¥ŸÚ½¿Õæ"dùJá~ª#w2J Ý+ºn»éní)û¿|ÑÏÊŸ Ù×PÑDoO¨$‰’ üälOps–f“¥n•÷—òBÐHe²xœáWu°ÓmDÕþѧôå.ïÅnWMe#É­µcÿx¿=˜OA…A<beÕœ”O‚ðB´¾ñI<WB‰j¨1V¡¼TqP)ëö¡l¶6j2_è[zUpv!¬­ÿ
-2$?ܦE͈ʰ(=DE•84šfšdÜÚ|W…ÁÀ…uÍáÀKs8dâeöíZ»};bˆf¼°ç ),¡ºG½¡#+¢2èùÃOìJº}ßu‘)Ævo†ÍnµR –ùQX; ÖN"IÂŒ³RàÈJ!©=:­hÇj —èÛÏ@juaσ2j¢Õ;êŽ.ê5¾AŽ7¨ÔJOì^Uؾïº\[jpƒ‚_®’)·©K
- "ÖÈ,§p´‚Ï2±U? G• cAÝlp^–è0lÑw9×U4ò^³„K÷eš°«l.FhÏ)–O|é#}ŒÝç[Ít¤Ígì{ò(NƹN®6.•Uý1ÌY¥›*j+Î~eï­"¤#öâÌz ”-Á…+VBðîˆå ýtaψáÆì ÖÁ;/hqHA#+…¤öq¶™ŸØ=ßuaOÌ"p›Ö°d¿#6ªP|™ëÒ9ÞXhö£V†Y&³‡¯û`Ùº\¸Á»#–ƒÔêžÃë/$¥ÙÞî–rËó*f›JOä–~j÷~×æš>’éÃ`®UÓ4Š€çTú O¦I›ZÑóĶù8|˜ƒ%ý]2(.¾%.¾ßÆc¥×
-q
-/GS0 gs
-486 0 0 480 6513.1162109 7207.1401367 cm
-/Im0 Do
-Q
- endstream endobj 780 0 obj <</BBox[6557.12 7644.14 6955.12 7246.14]/Group 791 0 R/Length 61/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 792 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 793 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-398 0 0 398 6557.1162109 7246.1401367 cm
-/Im0 Do
-Q
- endstream endobj 781 0 obj <</BBox[6625.12 7519.14 6893.12 7475.14]/Group 794 0 R/Length 60/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 795 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 796 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-268 0 0 44 6625.1162109 7475.1401367 cm
-/Im0 Do
-Q
- endstream endobj 782 0 obj <</BBox[6622.12 7549.14 6896.12 7246.14]/Group 797 0 R/Length 61/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 798 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 799 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-274 0 0 303 6622.1162109 7246.1401367 cm
-/Im0 Do
-Q
- endstream endobj 783 0 obj <</BBox[6647.5 7607.5 6943.5 7516.5]/Group 800 0 R/Length 48/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 801 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 802 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-296 0 0 91 6647.5 7516.5 cm
-/Im0 Do
-Q
- endstream endobj 784 0 obj <</BBox[6485.5 7766.5 6957.5 7240.5]/Group 803 0 R/Length 49/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 804 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 805 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-472 0 0 526 6485.5 7240.5 cm
-/Im0 Do
-Q
- endstream endobj 785 0 obj <</BBox[6522.5 7682.5 7035.5 7180.5]/Group 806 0 R/Length 49/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 807 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 808 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-513 0 0 502 6522.5 7180.5 cm
-/Im0 Do
-Q
- endstream endobj 763 0 obj <</BitsPerComponent 8/ColorSpace 736 0 R/Decode[0.0 255.0]/Filter/FlateDecode/Height 87/Intent/RelativeColorimetric/Length 321/Name/X/SMask 809 0 R/Subtype/Image/Type/XObject/Width 310>>stream
-H‰ìÖgNÃ@@á·u÷ÞûŸadæ"¯xßVzÍìj
-ßsëålÛBX–i†n¦iYBØöùru\ÏÂ(NÒì±é[m.˜°LC×NÇÃ~·Ý¬_Ö›ín8ž4Ý0-!+Ýb«½™-7Nš®éÓ´I¶EWLé¾n5×q¿Ûl_l˯6˜¹ Ét¤^QEªÝûTPæc¢`µÑPOöÿöÂ՞诪
-H‰ì×g{ÚJ€áóc6'9±Ç L½w„
-jH!Tèäßïˆ2#³WâuÚîû\þd›‘t3þú ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ èõ}ù? ì^ؽ>°{}`÷úÀîõÝëûsí¶çýô3ø½ìÄf×z~p›Ÿêø»?xþÑûýÊ÷ÑÓÿx“³ýv/^ïzµk‰Z욣f(×uP6ʲ¦$Ëvf‹åêÈàåò~»˜Ï{÷iÓÜÚòôv]ï ÞÁö‡õN 0Æ·)ÿ»£Óþêv8ž‹ë‘XÞõL c<麆RÕáPQd”$‰¢ <Ïq,Ë0ý>MÓ½^¦û Ë‹²fXîŽïTþäêWœ3«²À±Þ
-žc™>ÝëvÚ­f£^«VÊ¥b!ŸËfÒ•J&â±ès$>yŽÆ“T®ÒbåñÔÓ;¯'óg‰äL]ê7JY*G£àAÐ(±x"™¢¨t&›ËŠ¥r¥Z«7š­v§Ûó¾Ž"BU†ÈSÿŠåjµÙþ»íz9w,$õýPáðÓcèáþîöæóõ§«Ë‹‹ÿ|xÿ÷;Ôû.®nÂÏ *›opÚÔ™/Nïsÿý·XÌì‰Ò¯R©D,Ý^_~üðîÝ¿ÞýýþÃ?/..¯>]¾¹½»=>í¾”ïæ4§öl±÷{S»Ÿ7íÞ`Öy˜¡ºÆHœÊ•«µjÊ+ÏŸ®'ë—GGç©D4S®ÕªÅL*ö†y|-U`æ¡5ã8ñÞÚî›×;cüM+Ãph¥SuÃ4u±Q¬ÔÞŸ>Yü«ÿÜjL‘JúCô CCËÏ2ß´Ú!¨_¼Þýç¾ÿ‰ëÎæ ï›F­SkôTM¼“g²Oofb#MÕ{µ?Ì-€3÷-Ÿ®¿Êî¿lmÒíŽd8§»ŸÞ M;¶)*óŸ±Sþ£ì¾¬4šæFöl·à} OÏ1•n>×w~ÊKÆŸe÷ÅaXÆ{ZøwÐ~=Û›…‚ºú)'óVv?!ït‚àÙ¡ FÞ=üï ¶!5Ê•É[î þ'òìVŠÌ£-Þþ‘ßÚˆž5–Õ†õ‡ÍŠßÎNÓ$ÙùäL¿žeÈíf{
-óî¬Ý=kŽÃr\Ëy=ËÒ]îÙó<;×15ÓFtDnâ׳&šÀÊÚ
-ìNC›ÅÜ1¦Þ´Ãrcâӳм”ôÉl v'y¯%›ÕÜvÑþÄÂr^XÍFÓ@/‹Ø´ÛSm—h_ìÚ>¹±_Ïòôf« ܳ§í7¤Þ êÎî 7‰ÞÔ²w¶ØÂ嬽Ýf½œ»‘é#ŸzÏ—«?m×ÿãÛÛyKžƒVµ£œ®éDÏD[¿Åz vçÞ"wv,ç…õ&Èn¹»@ÄÎ6}rª_Ï´‘Ý°;ïh·œ!;,§U¢v_‰ØY“1–*C¢7žXîì‚ìÖ wjŒ°œ"+Do„^:k° „휩¡c9/¬§ïìàYè°GAvæØ''ùõƦ=»`;ï1;Ò°œ$JDO!»Ø"v]År¢ =UŸX3° v´›Y†6ÄrÂ@ zC͘‚Ý ì–³©¡)XnÀˆž‚ìÜÃïWŸîoÕq{çNÇ*‘ã9Þ§§ŽMì‚ìÐcv4”±ÇrDOŽLgvˆ®HXŽeX¢')úÄ>lŽõéþV·ÆöD“E,Çô¢'Êšv/t°›Û†& X®O÷‰ž€ì¬9Ø:ÚY†* °Ý£‰Þ@RÇÓù
-ìÎ;Øͦã¡Èc¹^·Gôxq8B<°;ÛÃrÝN—èq‚¢›`ì`皺<`±\§Ý!zì@ÖLìííVŽ©É<ƒåÚ­6ÑcxY›8`èh7Ñ$®åZÍÑës’j€]°ƒm¨"Kc¹f£IôhVöìÎÃvCéa¹F½AôzŒ ŒÁ.ØÁÎ+ƒ~ËÕku¢×íä‘vvÓ‘ÌÓ,W«Öˆ^‡æe}
-vövË©.s½6–«VªD¯Ýã$mº
-åF—»`G;¾[/å‰\ʧ—/Õ;œ:Û€ÝYG;®S+æ°\*™"z¹b­Í‚]°½ÝLeÛÕBË%I¢—-T[ÌлóŽvL«’Ï`¹D<Aô2ùJ³vÁvv›Ù°ß,çÒX.‹½t®Ü ìÎ;ÚÑR–ÈÅ¢1Ÿ^¶Tï]°½«ôêÅ …å¢ÏQ¢GeŠµ®ì¬Á]·VH§°Üsä™è¥Ò…jì‚íí¹SÍSI, Gˆ^’ÊWÚ² vçíÚ••Àr^X/AåÊ-° ¶³[;r«œMʼnܓO/žÊ–šØ:Ú5K™d Ë==>½X2Sl€]°½-5ŠéDË=†‰^4‘.ÔEkvgíê*þŒåB!¢÷§ò5° v°ky*Ár÷D/£rUa
-vça»\*Jäîïî}zÑTì^hg·²„j6ùÆrw·wD/üœÌT`Ûe‘',w{sKôž"‰4ؽÐÞn:¨¤ãáG,wóù†è=†ãT™7—`w¶£bá–û|ý™è…Â1°{©ƒ_¦¢OXîúÓ5Ñ{xŠ¦Jœ¹Ü‚ÝiØ.õüxå>]}"z÷ÏI°{¡ÝÒäJÉHèË]]^½»P$Qd' °; Û%·Xîòâ’èÝ>„ã`÷BÝvi²ÅxøþËý›ýºnN%k8\ïn®oww÷Áâî I¸qèýö/™>Ì0Ù­ÊßýûOêÓÍzákì\¢0°Ë¥¢!"çÓ ESY´ih÷xµÓÈ&#Aóz¼D/IfêÛh7Øeá
-rzžèYÞXiåí„ÙÙ- §ÓꈞŎv¢ íîÏVJ1Í rZ–è™mžhqùäîù íxÙYM §QkˆžÉŠv¢ìN—‹Q·Årj•šè-îHa©ƒvÂÆìÌS)UDÏ`F;ÑX»ç»“¥BÄeÒƒœR¡$zz“+œ_ìÜ¢ 1;£ärÑÓÑN´ÝÛó]g1v´ '—ɉžÖà åŽožÐŽß¸ädRÑÓ xC»ÛÎB.äЫAN*‘=µÞÌΡ°q;ÈIæ$DO…vâ±vO7ÇóÙ ]§¹¹Ù9¢§ÔÙ™öáõã+Úñ·S€ÜìÌ,ÑS x#»£v&`ÓÊAnfz†èɵ6ºu€vÂxvNnúŸi¢‡vÄÚ=^¶Ò~«Frl 'ÓX}tsÿ
-íñìˆÜ¯1=´û ÝëãõA“öYÔRûõóÑ“ª-Ýػ쿠/¾'÷óÇONí>lhwµß )³J2ÍÉýøþƒÓ›ž‘¨ÌTª¾;°{C»ñ„v#¹ïß¾ôÐî_bíú—{õeRαtC¹o_¿ôX¼9¥É›¬í\ôЎߤ+÷õËWVíþµÝKÿr·–ô³#ºÜ—©/¬ÞoVaô$ªÛç½g´ã%f÷ejêï©©/h÷ Vã—ÞÅN5á1Èg8º¿Ù
-¢X¬Ý[ÿb³J‡(Ëz çr{¨Pª²ñí„»°Ïëvƒȹ=^_íÄÚ=^nÕ舟òx@ä<^Ê¡«›ç}´Dì¢Êë=óRT Švb½Ûm×Ó± "zDŽòctmë¢ÿ†vüˆ]<ä@qz Gùü¡8Ú‰5²»Úidá€Ïz çó‰t}ûòí]6 úý rþ@0’Ì HC»§ëÝf.  r`(šÊ6vÐn"°ËÓ±p0Hô8¹`(£sh'k÷çéz¯U ã‘Pô@.ŽÄé|s÷
-í„]1ˆ†Ã ráH4‘. HC»ç›ýv)“ŒE" r‘h,™)¶ö®ŸÐNØ1ÙT<=‹Æâ©l íDz·;˜grt"=‹ÅtŽiï_?ýA;~`WÎÓÉxô@.žHÒy´kdw{¸P)¤S‰è\"™JÊó7Ïh'ìªÅ L‚È%St¦XA;‘†v/wG‹µR–N¥@äR4-UÑn"°«3¹4MôˆÎ15´éÝîx©Áä3(Näèt&ÏÔnÑNg·Ü,²é4è\:“-”Kh7ÙÈî¾³Òªs™ è\&›+VšËÇw/h'ˆ³[mWKùlô@.›Ë—ª­´›lh÷zßY›¯1…\Žèqr¹|©µW;h7g·¾PgŠù<è\¾Pdêókh7ÙÈîádc±Q.
- r…b©ÜXXïÜ£0Îns©YaŠEйb‰©47Ðn²‘]ïtk¹UeJ%йÃT[K›'÷¯h'ˆ³Û^i×Ê Ñ#rL¹Ö^ÞB»É†vo½³Õùze
-×÷§Ð{sï½Òîþì{Ùa;“L­wˆõH°¾ížjíÔí|ôúïô ÷ïåu4ß(´“{ØU®¶[Œß^^ ¹—×·ñb«:%ížkíôýròþú
-=Ƚ¾½O–;Í¥Tcç‡Õtôö=Ƚ½¦«½îÑNª±óãz6~‡äÞGãÙú`ø´“ú´»WyÚÌ'£ô 7O曣T´{®µ³”íb:CrãÉt±=™´“{ØÕ¡­î–³Éz›LgËb…´“jì"GÛ¯æÓ)ô 7ÍW{ÕŽh';ý°^ÌfЃÜl¾X4'ªî´{ª±‹]ã¸YÎçЃÜ|±Üu'¦ÔÃî{Æi»Z, ¹Årµ=n\Óî¹Æ.ñMe·^.¡¹åj½S /¡Tc—–ºß¬VЃÜj½Ù«¦ŸÒN
-v¶vØ®×ЃÜz³=hV@;¹Æ. ý¸Ûl ¹ÍvwÔí ;Óî¹Ö.rŒÓ~»…䶻ýÉpBÚÉ=ì.yäÊa·ƒävûƒb8QN;©Æ®ˆ=S=î÷ЃÜþpTM7*h';ßÒN‡ô w8ž4Ë‹i'×Ø•I`ëÊñ=ÈOŠnûqI;©Ö. C= ¹“¢N”Ú=×ØUi蚢@rŠª »´¢ÔÃîZe‘kêª
-=È©šnº!íêì<ËÐ4èANÓ Ë‹²êJ»ç»:}ÛÐuèAN7 Û²švR»[]Äc^'g˜Nç´“kìÎE8–€jõ g˜–°+Î7Ú=×Ú•ièÚ¦ =È™–í† íêì"ϱ,èAβ/JKÚÉ5v1´¾cÛЃœí8¾°»ÐNêÞÇY¸NOrŽÄYE;9Øåqà ¨VrŽë}ÚÝi÷ܽ=ðŠ$ô]zs=?Lòæ4¦]¿Î.σä<?ˆÒ¢¦\k÷1´ïCr~@»oºãHÉâ0èéA.ã¬=Qh×vbh#ÕêA.#Ú ×Ú‰¡M¢0„äÂ(SA»:»"£N¯“‹â´hÏ;Úõƒ]ýi=ÈE1í¾éŽ#¥Ì’¸§¹8Éʺ9i×vbhSÕêA.NRaw¥\gWåi’@rIšŠ© Ý@­8RŠ,íéA.Í
-Ú »Ë§ô —fv7ÚÉuvu™g==Èe¹˜
-Ú Ô·+T«¹,/h7ìÄXy=Èå…øÉÒn¨ÖîchË¢§¹¢SA»žì ¹¢,»™¥]?؉?¼ªìéA®¬ê í†z²ƒäʪꦂvýzvçªêô:¹ª7´ë×Ù]ÏuÕÓƒ\UwSA»~’]£¹ª¦Ý7Ý;¼Ë¹5~­›ëýdiׯgw½œEð{¸‰.WÚ vÿ‚÷Ù¹íñöÚ££]¿¾ÝíúÙ¥íñöv£Ýp÷û½¶k÷òË7~ûqÿT÷ánß|þÛû§úÆè»~ûqÿT´ûy´ûy´ûy´cŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒý¯úO€
-H‰ìÎI
-H‰ìÒ¹€0ÄPîlc¼Û§@óˆô P¤ª
-k¹kµÁ±©µÁuèµÁ°.Ú`ŽAô|Šƒ¦œð ºiƒ
-H‰ì×ù{šØÀñù_îÌt:iÚf3q‰ îû."È&¨È"îé?•#Üû´¹iÚÞû~ŸüáÃñœÃo¿a†a†a†a†a†a†a†a†a†a†a†½¼/ÿ‡¡ÝËC»—‡v/í^Ú½<´{y¿®ÝÓyo~?—±Û·Ý’?h·;¥rß±ÁéÙKÛíf»Ùœ|ôù;^ålßÂ.ðzÉ•9­I«}KÒ‚dÛs’E2MÃË´æ‹Õzã2œcã"^جWK{n†NþHûO›ÎÛ¶ó%Η¾Ö9óc|¥òw±ónþvϳ—!*‰é\ÐLÓ¦“Éx¬’e4’e‰$Š‚ÀóÃ!DZì`À0ý>MÓ½^¦ûÌ€$U3í=ßsù“«wþµZΩ"ñ,ù|ï9@Ÿa–å¸áçA/”åÑHQœ“'“©¦ÍiÓA¶ß=íAÕõüžvO;ç¾Z)eDDrì€éÓ½n§Ýj6êµj¥\*ò¹l&MQ©d"þ‹F"ác‘h,ž¤r•Ö@šŽÞóñz2~ÖŽœ®Št£”¥’ÎAÂᇇ‡ýAÈQã‰dŠ¢Ò™l._(–Ê•j­Þh¶ÚnϹ=,7ä‚*ˆçøßXn6»§ïa÷´]/ç&‘úv(r÷¡»Û›ë«ÏŸ>^~¸¸øûý_ïþüƒôîýÅåÕ]8š ²ù«óåêùïüä÷·Z-¬™Ü¯PÉx,¾»þtñþÝï¿ÿë÷?þ|÷×û¿/.>\~üôùêúæö.tÿ°¿)ßÌ©Öbuð{U»=Ÿ3ì^aÔ9˜÷¡¹ÆHœÊ•«µjÊ+Ï^>ŸOç/‡®—O%b™r|"“Š…ƒ„î_JåydÎpÞkÛ}õ|§M¿jÆc–ÌtÊXÓõ±Ð(Vê¼f-Ÿ¯,'³ÿÒ6T¦H% }™|@SÉ´Ç‘ÛôU³úÁóÝîÛW\{±\9wš´]iL­ÑSt2ðž­ÉžÞš
-4U•­Íá[¶ë•sË^suýQvÿe[nwDmþ|7ãé-œaWÈåå›l”)»/•¦Ù‰µØÏ
-Šf¡¿£¥)€¹f£ zô€iÖí|݈gz ר7@¯Ç 婉vþŽvæTö» W¯ÕA¯Û礉vþŽvÆDâèÈÕª5ÐëЬ4F»€vkc,±½6ÈU+UÐk÷¢ª¯ÐÎ×ÑNWÅA·r•rôZ]F@» ÀN`:M+—Ê ×ìôye¶zB»óŽv3…ï· W*–@¯Ñ¦‡# íüìVÚhH·ê W,A¯Þêq2ÚäÚÉ\¯Y¹B¾
-éÈE#QÐK¥óÕÚt°›KjžJ‚\$½$•«´E í|¹víJŽJ€œ“«— ²åÚ´·ÛÎ¥V9›Š{rž^<•)5´óçÚ5K™ä#È=Ü?€Þc2]l ]@;KlÓ‰È݇îA/– 
-uÞÜ Ýy®]½@Å£ º ^4NåkhÐÑN¨å©ÇÈÝÝÞ^ä1•« ´óv¹TÌ“»½¹õôbÉlíÚÛmL¾šMFà ws}záh"SæÐÎØe‘»¾º½‡H<vAìŒa%߃ÜÕç+л?R%V_£Ýy`G=†C ÷ùÓgÐ …chØÑŽ+S±‡;ûôñèÝ=DSž~B»³À.½¿¹—Aïö>’D» övk-%#¡»üp z7¡p¢ÀÌVhwØ%Âw× ÷áâè]ß…ãh”C÷ûõ¹œ8`´vדÛ䜳DÎ"#2Î9ìñ8àˆã¼ý"ðm»UþÝß#œºÕ·ûí©³ÛÌ3±0Èq½p,•«o_¢ÝX`—KEC¼Åë…¢É,Ú‰Õ·{¼Úid“‘ ÈQ^
-ô‚‘D¦¶…vã]&€œ×ã½@8žF;±v—Ûõt<ä9ÛzþPŒ©n^tÑNo ú@Îírƒž/E;ÑÞí¶jL4À˹œ.^/IU6λ¯h'ˆ·‹øis:œ GûÃh'Zß®{±YeÂ>
-ävèQ¾P²¼þûí„ñv!Ú rv›ô¼tíDãì^»ç•Tö€œÍj=H°kgh7o Ü gµXAÏMùÑN´¾ÝÃïõrÒïuœÅl=—×gWÏî_ÐNoçó8AÎl2ƒžÓC£h»³56A» g2š@Ïá¦c¥•_wh'lÈÎe9£Ázv…v¢qv/÷g«lœrÚ@Π7€žÍé—OÑn¬!;‡äô:=èYh'^ßîî×J)æµ[@N§ÕžÅNnŸßÐn´!;›ä´-è™mh'ÞÀît¹õXM §Qk@Ïdu‡ó‹m´kÈÎb9µJ zF Ú‰ÇÙ=ßž,"n³äTJèÌ®Pn¡}ƒv†ìLzS*” §7¡x=º·çÛöb>ì2ê@N!W€žÎè f篟ÐNаää29èi h'^ß 9 “Ie §Ñ;™¹#´kØN rÒY)è©ÑnBœÝÓõñ|6èЫ@nvfôT:»?Ý:ì<¾¢ÝhÃvJ›™ž=%ÚMh`w4— Øu
-›–LƒžBkó1Í´kÄNJä$?% 'G» qvÃVÚoÓÊAŽ‹èÉ4Všiì_¡°;^v“êÙ½>všŒÏª‘Üï?@Oª¶Ð©úÞe÷íFµ#rß¿}'zh7±¾ÝÕ~ƒ¡-j©„È}ûúèIfUf*YÛíÙ½¡ÝHB»Ü×/_zh÷qvÝ˽zŠ6«f9º¾Ü—Ï_z=¼¥É›¨î\<  q;Nîó§ÏœÚýg=»—îån-I™”3ºžÜ§©Oœ^oZaôÄ+ÛçÏh7š˜Ý§©©¦¦>¡ÝÿÔ[_.vª ¯Q1Mèþá"x¹Á+oý¾G;AìþF»ÿ¯ow¾]‰{ r O÷÷S¦wEÙͳ;´„ïîãá¼ûxøÏ~<Üï>ÞïÙÇÙ=^í7Ú¢–J8¼^_Ž£“̪ÌT²¶{ÙE;AB»Ÿ?z}¹ÚM®o×9h2>«F6Íá ôÞå~J¦¥j ªïõìþ ÝH£vÓ¢÷.'™F»‰õìÞ;‡­´ß¦•ÏÌ€‘›™‘i¬4ÓØ¿z|E»ÑFìfgy½w¹ÙY´›g÷t}4— Øu
-©ôˆœT*×Ú|Ló ƒvÂFìd2)Ñ#r2™í&4°;žÏz¥\zDN.WêìþtëíƶS) Gä
-ÚMˆ³{¾i/äBNƒZ©="§TªõŽ@fîèúé íF¶Ó¨T GäT* ÚM¨owÛ^̇]F­Z zDN­ÖœÁìü1Ú5l§Óh@Èi4:#ډ׳ûó|{²Tˆ¸Mz­ôˆœV«7¹B¹…öÍ3Ú ²3t:Ð#r:ÁŒvâqv/w§ËŨÇbÔëAÈéõF‹;œ_lߢ°!;«É`
-Pn§ôˆœÓé¦ü víìáíñvAÚãr‘s¹<t
-íÆ»b: ‡AÈ…ÃÑS@;±úvÏ×ûs¥L2‰€‘‹DbÉt±¹ÛyB;a`ÇfSñhôˆ\4OeJh'Ö»ÝÁ<›c±è¹X,ÁdÙÖ^çéÚ »ržIÆã Gäâñ$“C;Ñv7‡ •B:•H€‘K$Ré|ynÿúí„]µ˜a’IÐ#rÉ$“)TÐN¬¾ÝËíÑb­”eR)Ð#r©“-VçÐn<°«³¹4Ãë“Εj h'Ò»ÝñRƒÍgôˆÃdòl}ñðíÆ"vËÍr!›Nƒ‘K§³¶±„v" ìîÚ+­J1—É€‘ËdrÅrsùèöí„»Õ¹j)ŸÍ‚‘Ëfó¥JkíDêÛ½Þµ×ækl!—ãõÞår¹[[=F»ñˆÝúB-æó Gäòù"[›_C;‘v÷'‹r©P
-Ë‚‘cÙJ}neûäíÆ"v»k j¹ zD®\®6æWwÐN¤]÷lo}±Y«T@ÈU*µæÂÚîéÚEìö7–Zõjôˆ\µZo-®ï¡Ý¿ìÖÇr³J†á»sRΉœ‡œ”îþ`‹n™ã]§¾w*SO!º%‘ºÞ^^:½Vîååm4Û(NqƒÝ÷ÈNÛ-Æﯯ¬Gr¯¯ïãùV…¤‡]åéûåäãíõHîííc²Øin »AdgVÓÑû;ë‘ÜûûhºÜë°“ÔÚùæq=|°É}|Œg«ƒáÁnXk˜§Í|2±ÉF“ùúhú°öew¯„¥lÓñ˜õHn<ž.6'3¨`7ˆìlu·œM&¬Gr“Él¹U,»a»:t´ýj>²ÉM§óÕNµa'©µ‹\ý°^Ìf¬Gr³Ùb½×œvÃØÎ8n–ó9ë‘Ü|¾Üt7ªî°û^k{æi»Z,Xä‹ÕöhÀNÖÃîû¦²[/—¬GrËåzw2½¸†Ý Ö. ,u¿Y­XäV«Í^1}ØIjíRak‡ízÍz$·^oª$°ÆvŽ~Üm6¬Gr›Íî¨Ù"…Ý°Ö. ]ã´ßnYä¶ÛýIwDz†Ý ²‹\S9ìv¬Gr»ÝA1Ü0ƒÝ°‡Ý%<S=î÷¬GrûýQ5Ýv’Z»"ö-ít8°É'Íô¢vÃØ.°uåxd=’;ÝòãvÃZ»2Ž¡žN¬Gr§“jØìd‘]*\SSÖ#9EÑLG$åvƒZ»* ]SWUÖ#9UÕMW¤°“ô°»VYäY†¦±Éiša¹aZÁnXgçÛ¦®³Ééºi{QV]a7¨µ«ó8pLÃ`=’3 Óña'íaw«‹X¸–Ù鱜i¹Aœ×°ÖÚ‹D¸¶i²É™¦íŠ¸¨o°DvezŽe±ÉY–㉤8ÃnXgù®m³ÉÙ¶ë‡i ;I­Ý¥´ë8¬GrŽãì¤Ýi9Îbá¹==’s=ь٠솱] ßuYä\×q֮ư{êN ^‘„ç±Éy^Æ9ìduvi$|ŸõHÎ÷E”íz»§ÈîsЊ `=’ ¥°“vç%%‹CÑÓ#9Æ͘…$¶km$ë‘œQœÑŠ»§È®´I†¬Gra%Y;Y]‘ÆQ§ÇrQœæ´ÞÁî)¶«¿ìXä>í
-ØI»ó’RfIÜÓ#¹8ÉŠv²Ø®´i³ÉÅqš•´¢Àî©Î®ÊÓ$a=’K’4çvO‘]³¤YÚÓ#¹4+*ØIc»Ë—ë‘ÜÃî;I]]æYOä²¼ä1 »§úvE–±ÉeYQÖ°“ÆvÍ°(òœõH.Ï‹
-vòÈîsЖEO䊲°“õÍŽõHîaw‡$¶k>xUÙÓ#¹²ªa'ï›ë‘ܧî`÷TÏî\UËUÕvò:»ë¹®zz$×üea'o`×ê‘ܧÝvÒîÞå\7µ~­[Óù£vOõì®—sû}¹5]º×vOÝŸð¾:SÛë vòúv·ëWêqÛ£ƒÝS÷û“uí.ûüöiÿVwy7ùÏ¿}Ú¿Õ?ìþÑoŸöo»Ÿ»Ÿ»Ÿ;„B!„B!„B!„B!„B!„Ðÿªÿ
-H‰ìÎ €
-H‰ìÔÉ €0QÖ°–Ÿþ+Qƒs@Ì+`ä“‹
-¢våˆÆGµ_
-H‰ìÎ@
-H‰ìÕË …áz¥Š-­-JáýRMX¹æ˜49ßjV&³€ª""""""ÚŸ”…Ň¿SÙrðóÝMë'c©rfG©
-'3Ýž¦<ˆÃSÖÍ´³‘—SvªîAå[*Û^HLÙÈ+èÎ{,[Uw˜²;лñ°ƒÆ”Ÿ‹™0åøšWL9E@åŠ}S¿å T&"""""ú‹·
-H‰ìͱ
-ÿÿÿ®®®ˆˆˆtttlllfffaaaZZZTTTOOOJJJDDD???;;;555222---)))!!! 
-H‰œ–yTSwÇoÉž•°Ãc [€°5la‘QIBHØADED„ª•2ÖmtFOE.®c­Ö}êÒõ0êè8´׎8GNg¦Óïï÷9÷wïïÝß½÷ó
- 
-V³)gB£0ñiœWו8#©8wÕ©•õ8_Å٥ʨQãüÜ«QÊj@é&»A)/ÇÙgº>'K‚ó
-€x¯Íú·¶Ò-
-¨ꇆ¡Ðnè÷ÐQètº}MA ï —0Óal»Á¾°ŽSàx ¬‚kà&¸^Á£ð>ø0|>_ƒ'á‡ð,ÂG!"F$H:Rˆ”!z¤éF‘Qd?r 9‹\A&‘GÈ ”ˆrQ ¢áhš‹ÊÑ´íE‡Ñ]èaô4zBgÐ×Á–àE#H ‹*B=¡‹0HØIøˆp†p0MxJ$ùD1„˜D, V›‰½Ä­ÄÄãÄKÄ»ÄY‰dEò"EÒI2’ÔEÚBÚGúŒt™4MzN¦‘Èþär!YKî ’÷?%_&ß#¿¢°(®”0J:EAi¤ôQÆ(Ç()Ó”WT6U@ æP+¨íÔ!ê~êêmêæD ¥eÒÔ´å´!ÚïhŸÓ¦h/èº']B/¢éëèÒÓ¿¢?a0nŒhF!ÃÀXÇØÍ8ÅøšñÜŒkæc&5S˜µ™˜6»lö˜Iaº2c˜K™MÌAæ!æEæ#…åÆ’°d¬VÖë(ëk–Íe‹Øél »—½‡}Ž}ŸCâ¸qâ9
-N'çÎ)Î].ÂuæJ¸rî
-î÷ wšGä xR^¯‡÷[ÞoÆœchžgÞ`>bþ‰ù$á»ñ¥ü*~ÿ ÿ:ÿ¥…EŒ…ÒbÅ~‹ËÏ,m,£-•–Ý–,¯Y¾´Â¬â­*­6X[ݱF­=­3­ë­·YŸ±~dó ·‘ÛtÛ´¹i ÛzÚfÙ6Û~`{ÁvÖÎÞ.ÑNg·Åî”Ý#{¾}´}…ý€ý§ö¸‘j‡‡ÏþŠ™c1X6„Æfm“Ž;'_9 œr:œ8Ýq¦:‹ËœœO:ϸ8¸¤¹´¸ìu¹éJq»–»nv=ëúÌMà–ï¶ÊmÜí¾ÀR 4 ö
-n»3Ü£ÜkÜGݯz=Ä•[=¾ô„=ƒ<Ë=G</zÁ^Á^j¯­^—¼ Þ¡ÞZïQïBº0FX'Ü+œòáû¤útøŒû<öuñ-ôÝà{Ö÷µ__•ß˜ß-G”,ê}çïé/÷ñ¿ÀHh 8ðm W 2p[àŸƒ¸AiA«‚Ný#8$X¼?øAˆKHIÈ{!7Ä<q†¸Wüy(!46´-ôãÐaÁa†°ƒa†W†ï ¿¿@°@¹`lÁݧYÄŽˆÉH,²$òýÈÉ(Ç(YÔhÔ7ÑÎÑŠèÑ÷b<b*böÅ<Žõ‹ÕÇ~ûL&Y&9‡Ä%ÆuÇMÄsâsã‡ã¿NpJP%ìM˜I JlN<žDHJIÚtCj'•KwKg’C’—%ŸN¡§d§ §|“ꙪO=–§%§mL»½Ðu¡váx:H—¦oL¿“!ȨÉøC&13#s$ó/Y¢¬–¬³ÙÜìâì=ÙOsbsúrnåºçsOæ1óŠòvç=ËËïÏŸ\ä»hÙ¢óÖê‚#…¤Â¼Â…³‹ãoZ<]TÔUt}‰`IÃ’sK­—V-ý¤˜Y,+>TB(É/ÙSòƒ,]6*›-•–¾W:#—È7Ë*¢ŠÊe¿ò^YDYÙ}U„j£êAyTù`ù#µD=¬þ¶"©b{ųÊôÊ+¬Ê¯: !kJ4Gµm¥ötµ}uCõ%—®K7YV³©fFŸ¢ßY Õ.©=bàá?SŒîÆ•Æ©ºÈº‘ºçõyõ‡Ø Ú† žkï5%4ý¦m–7Ÿlqlio™Z³lG+ÔZÚz²Í¹­³mzyâò]íÔöÊö?uøuôw|¿"űN»ÎåwW&®ÜÛe֥ﺱ*|ÕöÕèjõê‰5k¶¬yÝ­èþ¢Ç¯g°ç‡^yïkEk‡Öþ¸®lÝD_p߶õÄõÚõ×7DmØÕÏîoê¿»1mãál {àûMśΠnßLÝlÜ<9”úO
-¾„¾ÿ¿z¿õÀpÀìÁgÁãÂ_ÂÛÃXÃÔÄQÄÎÅKÅÈÆFÆÃÇAÇ¿È=ȼÉ:ɹÊ8Ê·Ë6˶Ì5̵Í5͵Î6ζÏ7ϸÐ9кÑ<ѾÒ?ÒÁÓDÓÆÔIÔËÕNÕÑÖUÖØ×\×àØdØèÙlÙñÚvÚûÛ€ÜÜŠÝÝ–ÞÞ¢ß)߯à6à½áDáÌâSâÛãcãëäsäüå„æ æ–çç©è2è¼éFéÐê[êåëpëûì†ííœî(î´ï@ïÌðXðåñrñÿòŒóó§ô4ôÂõPõÞömöû÷Šøø¨ù8ùÇúWúçûwüü˜ý)ýºþKþÜÿmÿÿ
-H‰ìÎ
-ÿÿÿ}}}tttlll^^^PPPCCC222((( 
-H‰ìÌ1
-ÿÿÿ”””„„„bbb[[[III444///+++###
-
-
-
-H‰ìÎA
-ÿÿÿtttlllPPPJJJ222(((  endstream endobj 737 0 obj [/Indexed 735 0 R 121 823 0 R] endobj 813 0 obj <</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 316>>/Filter/FlateDecode/Height 345/Intent/RelativeColorimetric/Length 604/Name/X/Subtype/Image/Type/XObject/Width 316>>stream
-H‰ìÐA
-ÿÿÿ¿¿¿½½½¹¹¹µµµ³³³±±±®®®­­­ªªª§§§¦¦¦¢¢¢žžžœœœ›››˜˜˜•••”””’’’‹‹‹‰‰‰‡‡‡†††ƒƒƒ}}}{{{yyywwwvvvtttqqqpppnnnlllkkkhhhgggeeebbb___^^^\\\ZZZYYYWWWVVVTTTRRRQQQOOONNNLLLJJJHHHFFFEEECCCBBB@@@???>>><<<:::888777555444333222111///...---,,,+++)))((('''&&&%%%$$$###"""!!! 
-
-
- 
-H‰ìÌ¡
-ÿÿÿ’’’‚‚‚rrrkkkWWWPPP???444///+++&&&""" 
-H‰ìÎ1
-ÿÿÿ|||tttMMM@@@***!!! endstream endobj 810 0 obj <</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 316>>/Filter/FlateDecode/Height 345/Intent/RelativeColorimetric/Length 603/Name/X/Subtype/Image/Type/XObject/Width 316>>stream
-H‰ìÐÁ
-H‰ìÐ1
-k…µÂZa­°VX+¬Ö
-k…µÂZa­¸µ
-ÿÿÿ···®®®¦¦¦•••†††~~~wwwpppiiibbb\\\VVVQQQKKKFFF@@@<<<666222---)))%%%!!!
-
-
-
-H‰ìÕnãFEÁõý/ Àb¯-QâРΫº
-H‰ì×ÙvâÈ…áv0ó<Ï @Œ IˆÑv¹Êýþot"®ªÓ÷.V§ÿï†[­›‘ý
-Œšÿãc$‰
-ùQ _‡úû?Êðc±'‹I>jàÞŸ†?à6ÿØS<Š?/EõDÆŸL¥Ó™L:J&T |,‚{>Ùmþ‰T&›Ë‹\ö_¸÷âs©ùGeþÙ|¡T¥B^Eà¶H€îT\ç_ªÔêõz­Z)T „·€$€
-™КÚ
-`º<¿< Q¾€ÐÖï`ºr|
- Tkõ†cs:›MÍÉØô:­Fµ\ÈÊü)
-Ùõÿ„' *€îhf9žï»Ž5÷[µRN åÿ¯·ë ˜ÎWZsåøûÃaï;«©Ñ +@¡
-਷p$³¥Fo²t‚ÓËë·×ËÁ·—“A[-ÄS”
-¸÷Wâ³\7@:_mgkÿøòöþþþýõ¼w­™œå< 9)€_'ÀîôúýýŸ~¼=w×%PÌ&ã@c'€<öîô¢ðþör¶Rz)§v
-@[·0_¾€§
-ÀQóÿû噤8²Eᨩ*¬0Þ{d@B$®¼¯6ûßÇ»™‚y=¿§#:*'¿dĹyLëŸeÔ
-˜¯4{‚4ÓtCÇõoÈŸôOÆi —À
-
-€¿^êʘ
-G¢L ÀN$Êçèàß_«óYë}ù5
-¥r¥R)•Z³;˜(sË^­×ÎÒԔɰ‡ “NÆ£a4
-ˆàO?žò¯€ýè›o,«àø®ço}Ï]Ù Ó0Ì܃»q׶uJ€ ®€Ô
-Ž&Ø|¥ÕŸÌŒåÚß߀þ/oïïïo¯/OØ\gihP
-§€,à
-ƒ°ÙR­5`¹9À||~€<ǘIßnTŠY6§@HÿKÐ@ŒI±¹R.`Ž/àõ ÀÍvmi¨þÕÊùLëO €Nßþ(‰Æ≇/@† Øß=<==Üî7¶®@ý«‚ý§T¸øüýCa?‹Å™ó€¸»ãíÝíÁ_Yª8h׊PÿèTÿ¯ÍÅ?åÒ©G0Lò|Úbåíö;ß]êŠÐm”s8þ¡ÿQý¿0èÿ'øþ >Ã$
-Å<ŸI'tP˪¦ëzE‘ŠÔ
- Y
-¹ k€!ÿ» €(Ûù¬ò‘r¹¬V4M«(²Xb@ë…€ß ò4ñ( €_ýBý¸ØxŽ%hçŠØb÷¿fTkÕ*¢^¾D€_PÙuˆbðàŽÅ6@4Ž®%IE¬~Â_«[–U¯Xö¥‚ßb8Ø è¿O!ÿ{Û
-l
-?-‚2Y>}`å±ò‰¼¦éºáÓowzvßqÇÓÅz‡ ðã'èõ|Üo–è~  ö
-üµþ‡þo èÅS\–:º$I¢(”Š$ÚÚe’ÅR
-²ÑìÄk5;½V#€áÛz:Ú­š&Ã¥|Ð}å{AÀ½¨€¿Õé{Óåf·ßïÈôXótëý—ý2mNë‚paÇ6«Á€ÙX$„„Ø;ˆÕ™ÿÿ{Þ>÷
-{¦ÞO3Áž9O¥¦â¸2é¾Ý}¤Ð+œTbB}ã]ýôk‚䉅Áòÿ^p<<…b¯Õè9Ëínç®çãž%: Na°#Ãív³ZÊgR¯©””>Ÿ/‹t0Ôu³;šâ;ìwîf9söžÈÛl·Û¦…¿mÈ[£TÈeðø_¢$¿Ÿå¿d
-@¸è}g<ÆE€+p†
-`LWîágœÏ0N¼ I>
-$hÂ
-ˆÉ'ôoizúÞ2Í6úç²ét*•¤^ñX4BÓ_È/õ¿õÿ.óa
-_Å`DügÓ¢ñ£Ï‚HX6¿xü|ò}]È
-ÿÿÿ
-q
-/GS0 gs
-513 0 0 502 6522.5 7180.5 cm
-/Im0 Do
-Q
- endstream endobj 832 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 833 0 obj <</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 513>>/Filter/FlateDecode/Height 502/Intent/RelativeColorimetric/Length 8911/Name/X/Subtype/Image/Type/XObject/Width 513>>stream
-H‰ì×ÙvâÈ…áv0ó<Ï @Œ IˆÑv¹Êýþot"®ªÓ÷.V§ÿï†[­›‘ý
-Œšÿãc$‰
-ùQ _‡úû?Êðc±'‹I>jàÞŸ†?à6ÿØS<Š?/EõDÆŸL¥Ó™L:J&T |,‚{>Ùmþ‰T&›Ë‹\ö_¸÷âs©ùGeþÙ|¡T¥B^Eà¶H€îT\ç_ªÔêõz­Z)T „·€$€
-™КÚ
-`º<¿< Q¾€ÐÖï`ºr|
- Tkõ†cs:›MÍÉØô:­Fµ\ÈÊü)
-Ùõÿ„' *€îhf9žï»Ž5÷[µRN åÿ¯·ë ˜ÎWZsåøûÃaï;«©Ñ +@¡
-਷p$³¥Fo²t‚ÓËë·×ËÁ·—“A[-ÄS”
-¸÷Wâ³\7@:_mgkÿøòöþþþýõ¼w­™œå< 9)€_'ÀîôúýýŸ~¼=w×%PÌ&ã@c'€<öîô¢ðþör¶Rz)§v
-@[·0_¾€§
-ÀQóÿû噤8²Eᨩ*¬0Þ{d@B$®¼¯6ûßÇ»™‚y=¿§#:*'¿dĹyLëŸeÔ
-˜¯4{‚4ÓtCÇõoÈŸôOÆi —À
-
-€¿^êʘ
-G¢L ÀN$Êçèàß_«óYë}ù5
-¥r¥R)•Z³;˜(sË^­×ÎÒԔɰ‡ “NÆ£a4
-ˆàO?žò¯€ýè›o,«àø®ço}Ï]Ù Ó0Ì܃»q׶uJ€ ®€Ô
-Ž&Ø|¥ÕŸÌŒåÚß߀þ/oïïïo¯/OØ\gihP
-§€,à
-ƒ°ÙR­5`¹9À||~€<ǘIßnTŠY6§@HÿKÐ@ŒI±¹R.`Ž/àõ ÀÍvmi¨þÕÊùLëO €Nßþ(‰Æ≇/@† Øß=<==Üî7¶®@ý«‚ý§T¸øüýCa?‹Å™ó€¸»ãíÝíÁ_Yª8h׊PÿèTÿ¯ÍÅ?åÒ©G0Lò|Úbåíö;ß]êŠÐm”s8þ¡ÿQý¿0èÿ'øþ >Ã$
-Å<ŸI'tP˪¦ëzE‘ŠÔ
- Y
-¹ k€!ÿ» €(Ûù¬ò‘r¹¬V4M«(²Xb@ë…€ß ò4ñ( €_ýBý¸ØxŽ%hçŠØb÷¿fTkÕ*¢^¾D€_PÙuˆbðàŽÅ6@4Ž®%IE¬~Â_«[–U¯Xö¥‚ßb8Ø è¿O!ÿ{Û
-l
-?-‚2Y>}`å±ò‰¼¦éºáÓowzvßqÇÓÅz‡ ðã'èõ|Üo–è~  ö
-üµþ‡þo èÅS\–:º$I¢(”Š$ÚÚe’ÅR
-²ÑìÄk5;½V#€áÛz:Ú­š&Ã¥|Ð}å{AÀ½¨€¿Õé{Óåf·ßïÈôXótëý—ý2mNë‚paÇ6«Á€ÙX$„„Ø;ˆÕ™ÿÿ{Þ>÷
-{¦ÞO3Áž9O¥¦â¸2é¾Ý}¤Ð+œTbB}ã]ýôk‚䉅Áòÿ^p<<…b¯Õè9Ëínç®çãž%: Na°#Ãív³ZÊgR¯©””>Ÿ/‹t0Ôu³;šâ;ìwîf9söžÈÛl·Û¦…¿mÈ[£TÈeðø_¢$¿Ÿå¿d
-@¸è}g<ÆE€+p†
-`LWîágœÏ0N¼ I>
-$hÂ
-ˆÉ'ôoizúÞ2Í6úç²ét*•¤^ñX4BÓ_È/õ¿õÿ.óa
-_Å`DügÓ¢ñ£Ï‚HX6¿xü|ò}]È
-H‰ìÓAnäVDAéþ—Èðä–šìOfýTÄ– óoo
-ë¥ Ýà
-éMH'¸BzÓÒ .‘5.¶úõoøÅ®÷§ÉoyÉ/tá®O~Ï[~™«F=¾ö}oê·|Ëu3ßý¾& j]ºpæ­Û[”íâ]ïßÎÒˆ7Œš¿` ×½xÒ 7̶aÔ#w_}ÆT=rý·Œ²qÓg>!pÎU¿ýŒÐ=iQ¿ù’äEA-Q}Lú¤Œªª&Þt¿¶ª&Þt³¾¨¼­Ëšþ>Sµ’ª•dí$k¥YugIV]§Y“U×aeÕu”UUuEÖJë²ê:ǪºÎ±4«®SÈZimV]§µÒÚ¬º!k'Y+½”PÕ©^þ1Uåtϯ UâŦŽôzUYRµ’¬•d­$k'U+ÉÚIÖN²vÒµ’¬dí$k'];ÉZJÖN²v’µ“¬¥dí$k)Y;ÉÚIÖN²v’µ’¬U•uǪʺ Y+ÉZIÕN²V’µ’¬•eÕu ǚʺƒÃMuÝÀ©ª²w®ª®ÃÉZIÖN²V’µ“¬•ü®tí$k%¿k'];éÚIÖRºvÒµ“¬•ü®•d­$k¥SYuî\UY§Óµ’¬•NfÕu8Y+ù];ÉÚI×N²Vò»v’µ“®d-%k'];éÚIÖRºvÒµ”¬¥tí¤k)]KÉZJ×Nºv’µ“®tí¤k'Y+ù];éÚI×J²vÒµ“®•dí¤k']+ÉÚI×NºvÒµ’¬tí¤k'];ÉÚI×NºVz6«®{‘µ“®t­ôlV]·òtV]wò|V]7r «®û8’U×mʪë.ŽeÕu³êº‡£Yu݃®•gÕu ²vÒµ“®t­t8«®[哬tí¤k'];éÚI×NºvÒµ“®tí¤k'];ÉÚI×Nºv’µ“®dí$k)Y;©ÚIÕR’vÒ³”ª¥d-%k+Y[©ÚJU
-H‰ì—çviEÛV$9çTE‘3BmÏû?ÌœûU!pÏx<=d÷œýÃx ÃÂl{ïùåB!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„ü(|8ðÞ„ü|øŠ÷þ4ä*?‚³³3yø³tþs"Váô\!núÞt~üxPN³? JëùÅÅ¥pqµ¶Ù£g6O¥“˜ƒÕË««k‡Ãq}}uya‹µœž½%ù$Îïý™Éw±µ^;œ.·Ûír9aöÜÊ¥²zŽ$K”¯€Š3ÅþÀžhuº5¯W×½Íå±ê‚RR%Ée‡ógŠýÑ‘¸B«KóúÁ`0à×=nK¬Ì_k<;%Êp»œŽkŠý)Œ´jz ‰ÆbÑpÐ'b/d—^ØãYó Ê>€8»1§•Ø÷þàäk¾.¢*®W·Ç’©t:@¬ëZ)ÂzítAªîó‚! ø¼š<ÉÀþ(|øØ®×.?Ogó…B.“ˆuÍ)b/¯N·GÇ|–(ÇA,
-è"–}o~%ñë'Èk(–Η*F¥\È$Â~ÜNWÐ*[7ŽÆ©t&›ÍHœ!ÖyÅÀ¾ÿÆæI=S/žÂvzü‘d®lÖõj¥ŽuÙ¢×JkV“åb©T*äÒ‰HÀ+¥×wáÔèÙQ§UE/UµºÌ™Œao0ž)UíN»Y-gaŸ†»CZ#1±Z,fµZ5Êùt\žU}ïÿãÿG¥¡¶Ï+©¢ÒE¥‡J_‘ç1†}ádÞhtzý^§iR b§ìVh§2°jÖÍV»Õ¨Vò©¨–ƒø/åS˨åÓÒ Ÿ(¢h¢‚]DÏ¥¢:44]¬¶{ÃÑ°×®Ó0‡àB÷ ÇSÙBÙ¨5Z›^¯×mÕ¬8;.Ùaÿ2NsªÊ§%Ôaù„PMó ´^ævâ6‚YY¯X¦Üè'ÓÉ°[/g°`5—KóúC1ÑjÖ››Áp< nšfÚ=j<Ö%ò§qêô\œª€Z>¡S|úüvµ‹¨ˆ½¸Të5g´úãÙ|6î5+Ù8¼âE¾`4™-T°w»}8dz“´gã!Ýí@‡=˜¥Ü?ÃðU³9ÅÕó&ÔçóA G¢è¡‰DÂ*¢ªÍ\«JäÍö`ºXÎ'ý–‘‹‡|Í#S8£´Þ FÓùrµZÎÇý–iíßkk= Ó{C¬¨ÚÃ×vªaâª|¾ùŒ'’)©¡¹\.+ET†­“í5œ,Öº#¨[Lðšù¼ܱT¾\mvzÃñl±¾ÝlnW³‘Ìix·Ãn5&[í{ ŽÕ²*RÕì…T8 Z>ÐdR|fsRBËP.楈êšç”SóER¥úÍx±Z/gÊkؘ‰lÑÄ•<œÌ—ë»íýýv³DžMœÄò;ËëJ®ê‹‹7µïýüü|øJª²zy ª?
-#¡*ŸP[(:h­Þh6õj¥IDü^O§æ¤KÞd¹F mS¼êºMùJ½Óà ^ÝÞÝïvÛõ\›MD‚ØÏnܪÊd«eh‡ÉB5eõ-¨‘h<™ÎˆÎbéDh«ÕéJ]¹é¶êFѧ8•=þh¦ÜìOW·ð:l›ùD8àóI\Kµ6´.Ö›íîáñééaw§[Ì&£á ß'gµ¦*“º¬y!ÿ.sWn$ 5€UìD‘Š ¦³ùb¹bV«µZ½Þ€Ñ¶í†£ñd2û†qªzªÛˆe+­ÁlµÙ¬•×d$ˆÄÇR£y3œbß_žŸT`F1›JÄ"aœÕ–\—Ý™ÎxDý¯œžH6Ö¥„¨B†J*¤VÌZ£Ùît:]`KWY,—ËÅl<è¨ÂâCÜÜÞ`,kÀë^çÊk4 F$®Áty+a}~y}}}~ÜaÃ: ³\ÈfRIÜÕÑHX:“ýúòdÓÒìoáx"ÙýÔB¶*–j;5‘Êä
-e£Öhu{ýápda],WrÕÞmnת° —~lIM—úÚÎ-¯j! #õ×Þx!ZŸ^^?—§ýV^ÚmÖÌJ©Ïe3éT2ÃLÖ=n×aóˆúmØ'ÒùÛ‰„‚ª©?Ô
-Ë~]Œí¼êâ5ž­àDš/WËÙð¦i–ò9_µV²¼Û?½œÄU^l#~7·«Ål³*¦±”Õk½šK®ãsŠýœXÅVET±Jí~j˜¦ifµVë¨cÂ{‰™uëˆM(}–¿<¨Á¼Y/§CÌÛ¬=‡ƒ±L©ÞNç‹ùdØmVËÅB¡dÔQrVâõõóáäåÖ’–Ø˯ˆ¼×lÔï¶U£TÈe¤×b£÷\Kbéõ[üÚ*¢šÊ@ªQ­#žB³‰Ç¶(í÷è©“*§3±¡k aU´œÕb:굪¥ î&¯Ç£¢©‚Ùê&Óɨßi"{ÅRÙltQ^Á—÷±Ò
-¯2á_À3~„Cj9Ã0†YU~×±æ¢ØoóVe8–HcDš²G»"ü“ýòlj#Í¢ðz
-U0¤Š7~¦ƒ¡1ãFrãñ'ÿ®(Ã
-Ù‰€•a³­ „ö;‘å aÕ«¥\*ò»±8mv
-6•/£waVËŒ×<Ûê`ê‹åjÍãOX,î–ëͬZ>ˆØÅlªº1äŽrœÏ$"A¯ÓvìÏNÄ
-¬J¬R-Vê*^ölŽ¼Ò·Tw'j™kÄIŠ9ß{û¦YG泩XØÏ~c…û}¡X*W”V,äP‡9›è ½1žèïð¦ÓÙdöãó³òaZýy‰ÁÓ
-òé8,Þ¡ûÚïñmÝ¡[å½G“TT¼è!}vµFÐÇF4©ÉŒ'32šˆW°IBmÔª$‡â Ö««+þ(†ŽËç5•Ä¥@¶ ²í³Û Á<±ù%ËÐ… +¬2¾Œtãf­”KÂã×'®/NS…XµCN¥Z“T!Eš,#OÝQzB0ä×g-æ ø§T«Îm»ÕT5›N%âÑ°^#—–Kõå ’%4Å¢ÑX<A²¥
-öS§«¯‡ ˜0uïù»e31`¸Åœk¶Õ¨äÓ±FŒ {
-XóLªëµû2KçÑkº¤Ê7úÈÝ" òQ‘%֥ȥ„úÚíV«¥†P£^«Vœc< AÕå°Y±E.ÌZ …#‘H8
-ƒ¡P$Z,ªR¹Z«3z¹§nZ0¼y¶¸C…âé猅%¿¬a·Ý¨2ñÏÅ€=qÝß÷T­‚µÑîÓÅ’TwôÀ;éº&VQ+´uÓl4õz (+e^©TDÅÍj¦¡€ßçu£_[/-çç{Wpy¼>¿ßïóù¼^¯ÏçÃÊ–ö\
-¦W—&ÕÛGƒº:œÕz}Mº&\dn ÀhÎ@[­áª¸Z£Õéðå)[S»Y-åRÜ9öÓÎѧÅ*qçF‘ G¥–+6åºÙŠZe`P¬˜sFœÚ¥ªífÒɘR^8.NY«C!µ)™Ò€UMöŒAË%ÿá3èÚ´r=L]
-ðz8s©RØሬµæk$@¶œŸ¸j±žëz
-©Æ¹"QH+Ø­H×¥pU…‰Tõ°á.mi¨‡f¤Ôi×®z}}@ʪ$P…ªþÅÄŽ%l'€ W)×aæn@¾¸dJ.“-”ñ€˜¹ü¼ Ö|: ú\vØð)^’•‹s2.²Œ
-Úh¶;´¦ ãUAݬ—fÞ+5¡Ò{¥IˆZ•¥^^’¨eOT2Õ„jþva{|ŠòÅ…©\œ/Ø­¬"±åL¾Tk¶0rñ(0aÁêvh¹¾o®&Uz°L¾­Š G64“)òõ‹•RåpýîíÔÈž©S7#%O¹sS¤óýá ^ž†{®àZøy0xéÌ*týpå`(ŠbW¬Ô±oñ,EÕi³žäªû’$«Íé¦XÓ¹çD ×C1™Ý­6÷÷Zª\5ÝöM½z€ê÷îÍWê®e/ϳ³—Dÿâmÿ\‰_qfAËQäWÖaµŠ/,›Š°¾s¹~0û“ÕãE“™Cµ‹Ì`#öº=ônÄÕJKU²¬^)å³i@ ªÒ©Ô]‹åâ Ïÿè|´½AŸíó÷BF‘U<xQðBÑ*2ZT.“$V]øÝ»ðÑdUIÖl±ÚÀ*4Æ“ Z&ªQ`ñt6ŸcÕPªbÀÅ|&*j’Óñ¢î~‡ó'zü|GÒUâ%ÚË+]—a0l"•N³‹‡ëõ•åBúößøžþÏNa•É
-ªÚÔjÍÛÞðˆc¯KÉ£±Z5œª0à4Ô
-T»ö^]ŽL¨ßSá5 •˜²x r,ÆØc}ßrUɪZ°úîÓ,!6Æx¶¸[.ï3q]ÐöAø¶%RÍ2Uá¿Ò}­ï=3÷=¯9Š$s¥éьѠ¤¸¹OXM¬œ¬n¯¦Z®5Û]Áº\­WX©cнm·pív»ÕlTË ˜oQÕ¤½÷îÍ÷×>ô±'Ój³/NÖõÕ{ïLÚƒe²Jû€VAµÓcKšßë\§£>§aW“ÒÉ®DÇs9•T_ê¯îáª/ÙéTÞA¬ï˜«`¥;ô¶¡C«ª"-–R©×þ-M¥\*Ù9S %U—¤*kÒê?úôöM^mžöŽV…ÕŠ¡˶£ ©.î®KnÕñ Ûn 'åsÙL:•€KW‚k©j¨¯ò'0H„ì%ë±ÞXï«z#Àjsxüáx:_Uðt¤r‹ÅbŽ±:ìÝÞÔÊpßd"έJÖXQý§•úòÏøMM9íï+‹°Íé DÙâ~Ú ¯è¿h€Šý:ìwZõJ!‹©Ñ“[õòð
-_÷%¦œ~¤×|¢W<L¨’Ě̕ëmP%Ôõzƒ[¯– Hud`»vZj1—’­ŠTýÁ€_ûoù—îÇgG+ëµéuN§’倵q;ÏîVëÍýýî~ÃÌáÚe®–òéD4ä×RUÕä HÕ<³ÿ3}üžéÁV»ÓK¬•fg8]¬6Ûíããv»%×Ål<ìwo[7Í:k6 ¼.eÀo*ïÃá^ûQ^ëöã
-û8tðZ¸y%ZƒŠk4†´òHk­Õ/V›ÃåÛ÷÷÷ÿ<ŸvkÛ˜O†½v£VÑ5MÓ­T$^Œ_®° 3Öhí§KÛƒ1üýý×ǯŸo¯—ÃƆ Â
-VKªª–Yä5O¼•[¹¦¨O­ƒñÜt¼ÃåõíÇûûÏï߮ǭcÌF½V½¢©EEV”"2«©d|¹B¹Rp·ÂSouû£éÂt6ûÛ•ó ¦°kÎFÝfU“’(JÄ–J*ìM,x¯A#âk%ÒtNðµF“ÙÒ´×»ãåùõõõåzôµ6*°ÿæžçQ’!²E¥ òL&’u8`Dü!Œ´²Bm½áx:[,Í•»ÙNçËùtðs6ì4ʪ,ò9àø<DV‘ ¢ËRIìõ«¿„ð8¬øÂÉ°¼T­}ë|±4LËv7ÛÝ~¿ó`ž;õ²Z8&Kgè,“C‘•$ÜÏׯþÂ'÷°âôê u</ À4­•ãºk×Y™óI¿]׋Ïf3i€Êd!²‚ ðK£¸’z 7­ñD
-V&ᦚղ,ÓÄjMË4–pµÂƤ)P¥4•J&ÉT:C3l.—cÑ?®ÄkPðg0
-+Ü­yÜ­ƒÉÜ°lǶWàs¹XÌç³Éx
-ZU]•a£°&p2"x~#³ˆ'd:B´_k*Ã
-°0A³Žç(¬»Ã @^Íù¸ßnø#˜¥ïÛRÁfA-æáh ¸\ãЭ µÒô›u³=Ï—Ëyµé°Óð÷%´ÿ+þc_íƒ/•X þNR /Ö„Õ^{{°z½^ÁëÖµæãºY%ž…fMøÍú‡ÂÈo¾ò;>Ÿ2ür¥9±ˆ´¢f…|¾^ŸŸÁëÅuЮéH+šÁ÷°þå_}Õ—îü1TÑxÊUÑH«ë‡õ¸žq\GÝz¹ˆµÞf0qH>+Ñ_wžü¸J¥Zg4·ÖÛ[X‘Ö#´ërÒkVÔ‚Àâ£õw³ÆçuâÆâ)Šd½ÙŸšîöpëõ|Ú{Ž1´«¥ÿjýê/ ültÆbñ‰dšÎ‰jµ=ZØÞጭB·ž{MáN]Wò¹,•Œÿ¥Z Á×'ÉÔ4E³|A«w'†»=^ž_^Ð †´.ƽF¹(rw­d¬5ƒˆRšÎfiD–åDØš` ¯w§+ò
-Õºßnk1é·*ªÄ3TŠh 2謭)Šfrp>_P+­Á̯cÚo׶‰´VK²ÀÂÝÃZ¿úý åv­¦2LN%YVd¹P¤‚¬êµöpfº»ãåzEZ½µmÌÇývMƒr¥ÓDk ¯Ð­ •墦—+•²®i¥’V®5»£Z‡O— Nëʘz-ЊË5%Z Žk’ÊBŸj•z³Õn·[ÍF½^o´:ýñÜt¼ýñtÂCk­"­ Þ™`þê·'ü/P»B\Ù¼¬U[ÝÁh<†ƒ~¯ÛëFÓ…ål¶ûÃ~çùZ›¾ÖL*N¦p Á[S"å$µÚêf Ã4åb>L&SøÍrÖÞv»õÜÿj%åpP½ÆÃËz½;š›öz³Ù¬]{e™(¶l?pV·!,­¡àîUPÊM¸VomzØo½µë
-a’Öp‰<BÁÂýzÄÏötØ®ms>î­a¬ÀB`ÇKgwzþlØóq·q¬ÅdЮéE¢5\À ~ŠC`EoÀ¥ãç—WX‰ÏÇýÖ×Ú©—‹O´†Š[`éœX¬´3ÓÝNçó–a°º2æ µQV%ž%ZÃ
-,œ°Y^*UÛÙi¯½:]]Û2æÓQ¿]/«¥Ó‰X”h %†I,kµÎpº´lÇqV–±˜‡½Nº•h !X4‰óŠ^ï &ó¥a,³É¨ßm7jå’"!­É8Ñ*"~`asb±\ïôG“ét2ö;­zU/)…<çk}"ZÃDäØT†á%µ\o÷Ãá V+º
-Vù\6ƒµ>­!i¯X˜ÄBĶ:Ýn§…'0²ÊÐlÂDk¨ÀVA+xEåD¹T®5šÍ&Œ`Š[MÆae"ZßUd5¯Íò’R*WkµZEGûÁ~X‰ÖЀ›õIÅã‰d*arˆÕË岯•AÍz›ÁDkH¸+HM$’ÉT*Me²,——µ¤•Ô"t+ÞƒcODkˆ¸kEAMS˜ Íäø¼$+Å¢Ra
-£‰ÌàpáŸ7ñd
-tf‘e‘Ø
-CÎÖðqך†á›Ëq\Že°Ë€X!/Šycïq%ZÃÖ
-G+E3‘H¤–±€V´4E‰Ö0ÊiMÃiƒ
-UQdén–…üÂX+‰kxˆ`ÐN ­¢ û¯¦©EY¼GÈÒ·ËÚõ«_˜ð‰ÜxÀZS†‡{µ\­ÕªeM•%‹Z–¦3Ê×Jâ|£Àã£_®YNT´J½Ùj5ëU‹eQTÁjŠh ¾T¤ˆ¢U˜ÎååR¥ÑîözÝv³bE´Ó8¬q¬õ_öËsIq, ÂQÝ]Uxï½F rH  jÞÿYö\ ªwçÏÌþêº7ŸàD|‘™' ×ï® Séëëè= R¸Ù13–ãØ‚-ä2P­1‚…úŠˆF¢Ñh,ŽN¹ÞÒ3nµ^¯ó [-‚!XñÐc°FÑD2™L¥³ùR­ÝÏk~+ðë%€í·P±™`¸¬8(6Щ‰T*LÔ|± åJM¸õv/Š{a³œ3£^«^.dS®ë·WèVÔ©Ù|¡X*•Ë•Z~&†] ¢¬¨²´Û,gô°Û¬sןd¹~!»"¬Ð©¥JµÞh4›­v·?bæË­¨t]S,7¥ú­Z)—NDß^‰]1P‘X^%°i§Ûë÷ûƒÑx2_ò{E;š¦¡«Òv5§‡z9¸’ÆA×h" ƒµÙé©1MÓÌdÆX ˶-CSön2ê6€k<äú§¯&ú…1CƒµÑPÌt6gYn±„—IÖ ût:9ÖQùEÀú5B¸â '×l±ÖêS“9·\­7~»„Õó<×1âÚ©—××_$†¿¿¾¸ÂbŒgÜj³v{Q‚ a=_ΞcDžc†íZ1›Œ½“· ý—_Ûz¶ØÀd…qT-Ç=_|ÿâ_÷–î7+_oÓŸ¾šèÎœd¦XmõÇÀ6«ªé†iŸÜ³½Wû¨ìV3ª‹b8Fb=ÿá|¥Ù£&ÜZѺ³zÿz»]/®¥K[Ž´ª… Y¯Øè±_S9T°ô|µEÊ` z¿ß®ç“yدçÄ®x úõÇ/â:ÝÑ„ÛìоAXï÷›ï9GYXL]ŸíJ¸~{½ ¿ qVÀõ‚°~|Ü|×ÒÄÍ|Üm”È3ŒVÄ‚8ù0ì‚«çßŸwˆaC–“aìg TÖwàšÎ—êíÃnöêqýøüü¸ûž­K<;î!»F‰]qÐÓ¬Öx2“ƒx½SB®P¯×ËÉPw«é—] ÖﯠZ³Fc‰d:[(ƒ_iöÉõŽ¾&hW°+Ýo”s¤]ñPø1½½GcñD*ÉJÕfwÄpa_`»Ï0´ëtÔ©Áv%vÅAG8‚¬šÉå‹¥J­ÙŒgáßä^üëõâ9ÆAÜ°t¯Y†íJ슃»5œ/–+µz³ÝŽ§ÜZ÷ëåröS“¶ x†‘]£äÆ@]ß"è]*VjV§ÛRÌŒ[oEU7í“ëy.`•…ÕŒê6ˆ]qp»ÆÐ\m´{ƒÑ˜™Ì¹åFíhÚÎéäئ®ìÖð4µ*…4±+zØ5•+ÕšÝÅLêšö’ª ËY†¦ì7ÜdØ©³äÆDÀÚ5‘)Tš]¨U6„*«šn˜pµÌ£¦ˆürJõå|°]‰]¿¿^®±d¶Xk÷á[Zñ;ñ7Ô«* «9=hUÑÓD슅\!†ë!3_ò;À¦…Šõä@µº*í …GÝz)—ŠG^ÉvÅAÿË•]má]2,½ÁðŸì …·`×~h× … ×o¯¯.ÕÚz¾z¬f+ìV×±ŽyÏ/¦áÆ!vÅF_SµÙ£¦‹ÍNÖ àzñ}Àžà–!†™a=Ãa»þ铉þ…~ïœ:v¶ä‘aïìûW
-²n{þí~¿\·Óo–s°rH 㤗ÇïKf‹µÎp²Ø†\?>î׳­K<;îÕÃz%1Œ“€ëŸÈ°™Bµ \å蜯÷'×ÍœêÔP½’•ƒ™†M¤®KA1N—ÛÇççpÕÄõlÔ&õŠ¥
-9üå׫gªGwQ½’ÆOO®ùjkÀÀ? ê×»ï2ÏR*ªW²r°Ó3‡ó•fŸfyI³Ü`¿ž]Zφ­JP¯$†qÓƒk*_nô¨Ùj§Žç_¯¾ûx›JYR¯8*Ü9ñT®TïÀ€åŃyò.Ï1”-G÷êÅL¸^ÿôDÿ—^~s­µô|%Ⱥ鸮cjâfNêW!®¯oÑx*[¬6{ÔtÁïUÝ´mSW„ådÐ*“zÅR/?à~&Ò9àÚ2ìJºaèªÈ³t/\¯? WÌôˆáX2+!®ô|É¦©’°š:U²^±Ôï.”ë­Þˆ®;IQUy¿á˜~bÕ+±+fzÚ5“»vÔ„]m÷’¢È"Ø•êÂ7œ õŠŸžvEíÚèô©É|¹DYQ¤=¿˜ ÛÕ|š¬ \½‚]³…J£3OXHaQVUEÚ­Yú+† WÌÄ0<Ãù2Â:åÖÛ½¬>¾¦ù˜Ä0¦
-bø=–Ê•êí=C/“ªaääà®H ã(Ão‘D¦PmõdzåVTuòmËÐda9¶*ù‰a …bøÚµTïRÓåV:öÉõÜ“¥ËÛZ9Ù$‰a Äp<¯´ Ç‹Óñ.þåìÚºÌstÕkÄ+VzÆp±Ö¡f«j8ž½]/ž­K›9Ôë;©WìpB 7z4ËKšå^®7àêZ‡ýj:l“zÅS//hå$såfŸYleÝöWÿìŠ@ê_…oS
-ÕëqµÜ³ïûg×Ò¤ ®WR¯ê±^\%Ít\ï쬣²C1\Í“zÅR{‡Õ£i;ŽmU¾aˆá\’Ô+ŽB\#®ìz'Ž†iúAV3ê?ì×ÇrÛH@Ñ¢˜sÎ9$H‚sƒ’mY²5ÿÿ1Ó Pòvf5Õš{6Ò’U·ÞÃëf9—ŒðyUëO×öÀ˜¯÷Öùr½^NÇír:ìÖKñy¥«zä3ç£k<[íÖé|>÷›ùDïÔŠâõàóªûù*ºfŠN×íþx:¬Ãn=7íZ!MW%9Ï×HBtí ÑUÎëÉ:ÒUiòó*ºF“ÙR½«OæëÝñt¹\ÎÖa³{˜®Jr¹nÏ×T®ÜÔFæRÜÃ×û‡‡û‹µ_™ânâûª"—3®öÙTm÷ùæp¾|z~~z¸Å;Gk”èª'«×/>¯r §ËÝéúôíÇËËoçýrÒo–³tU¨*³Šqµ×po<Û/ß^^ßÞ^_žïëé UÎ&誖Ϭ¡h"c¯áÅötÿüòúë÷ûïק­©·+tUŒÜÁNÖˆ×R½£O–»ó÷Ÿ¿Þÿúëý×ËÓy;£«rnYý2k"“/7ººü¼þéú|Þݺú½n]Õ`/a¯Ï”YÓ¹bµ©é“ÅÖº>ýxýýþþûMΫ©·äÝDWu80,²¦²ùR­¥éÆ|}8?ˆìÛ¯·Ÿß¬õ´ßïœßCWE|<pÂÑx2Í+õVw06—Û£ûýåçË÷§Ë~ihõb:òyîèªÙÕ>™œ¬åªèÚ"ìát}|~~~¼Z›Ù°Sͧ¢AÙõ¿þÁøGDW9®‘X2+”DÖF«£ dØÍÞ:_ï¯k·œôåó5Â9¬ûëG2k¥Vo4[no02ÌÅzw8ZÖa·2‡Ýz1cŸMtU„½†ƒ‘x*[ÃÚlµÛ®Ö—aç«Ív·Û¬fã~K®aÎ&uØŸWHŒk¾T­7ÛN§Ûíj½¾>š˜óåj½ZÎ ½Û(‰5älRÆ­k,™•—pGë9ú}dLgóÅb1›Œzíj!ÛŸWº*Á>›áX*'®â»ª ƒø3OD×ù|65t­Yɧb!>¯ê]ÅÙO*ŽX¾cÃÛ ;ëBtºr.¥«Bœ®‘D¦XkiúxbÎf3Óœš¦)–ðr¹\È®z)›ŒÅÙDWEØ]ƒ‘D¶Tï ÆSÑrµZÚV«µ°Z˜r^骧k4™+7´áÄyÙloäkç–{˜®
-ùìZiöFær³;ÇÇýn»šO†]UsëšÊWZ}c¶Þ¬ÓédYÖÑvØo×tU‘ËåöøC¢kµ=0æëýñt:ÿ +ºŠyýØÃ>º*ÃéûÓÕ’UU¼kx-ï&y'"²«‹®jøèZ°»®¶ûƒœRy1m6›õz¹0¡Öª2qºªÄu÷Ùµ?6—kyoÖ«åb±˜Ïç3s2Ö{íz9—Š…>Ï]ñÑ5_mõFÓ¹|½.æ³édbãñx4ÔûÝv½RÈ$¢!¿×MWU|v­45}<5g¦Ñ‘>è÷z=MÓºV£V.d’ö¸r6)Ãé*Þ¯åz§¯ÄŒŠÕ:íf³ÑhÔëµj¥\̉¬‘ ßËV‡ì*Þ¯Él±Öêöƒˆ*F´R.…B!ŸËfR‰X8è·Ç•®Š÷°/Id
-•z«ÓíŠI­WJQ3-¤R©d"‹U5.×ÇÇS¹b¥ÞÛ·V)å³iY3‹
-‘H8 ˆ%ì¾£«:DW·W|`™|±\­VŬŠªñ¨¨
-Ú¿ÏKVÅ8]ƒ‘x2“+È/j.“ŠGá€ßï»Q=n¹„ɪûp
-„¢ñd:›uŽ¤HHŽ¨çƒ[D½#«bnŽÆIy&‰
-ˆªvÌ.ªªFvõˆ°¡H4Çć5hgub~ú¯&þ­[Ø@0·oØÙÁî;’*Nt•a}¢¬¼~ý~/WÒ—p ëõùü~çIãæIó¸ä*v{DÚÛ“†¬_ƒ +fÖqGÖ¯Ãå¤åMóåð¦ùª¨
-
-q
-/GS0 gs
-472 0 0 526 6485.5 7240.5 cm
-/Im0 Do
-Q
- endstream endobj 838 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 839 0 obj <</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 472>>/Filter/FlateDecode/Height 526/Intent/RelativeColorimetric/Length 11417/Name/X/Subtype/Image/Type/XObject/Width 472>>stream
-H‰ì—çviEÛV$9çTE‘3BmÏû?ÌœûU!pÏx<=d÷œýÃx ÃÂl{ïùåB!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„ü(|8ðÞ„ü|øŠ÷þ4ä*?‚³³3yø³tþs"Váô\!núÞt~üxPN³? JëùÅÅ¥pqµ¶Ù£g6O¥“˜ƒÕË««k‡Ãq}}uya‹µœž½%ù$Îïý™Éw±µ^;œ.·Ûír9aöÜÊ¥²zŽ$K”¯€Š3ÅþÀžhuº5¯W×½Íå±ê‚RR%Ée‡ógŠýÑ‘¸B«KóúÁ`0à×=nK¬Ì_k<;%Êp»œŽkŠý)Œ´jz ‰ÆbÑpÐ'b/d—^ØãYó Ê>€8»1§•Ø÷þàäk¾.¢*®W·Ç’©t:@¬ëZ)ÂzítAªîó‚! ø¼š<ÉÀþ(|øØ®×.?Ogó…B.“ˆuÍ)b/¯N·GÇ|–(ÇA,
-è"–}o~%ñë'Èk(–Η*F¥\È$Â~ÜNWÐ*[7ŽÆ©t&›ÍHœ!ÖyÅÀ¾ÿÆæI=S/žÂvzü‘d®lÖõj¥ŽuÙ¢×JkV“åb©T*äÒ‰HÀ+¥×wáÔèÙQ§UE/UµºÌ™Œao0ž)UíN»Y-gaŸ†»CZ#1±Z,fµZ5Êùt\žU}ïÿãÿG¥¡¶Ï+©¢ÒE¥‡J_‘ç1†}ádÞhtzý^§iR b§ìVh§2°jÖÍV»Õ¨Vò©¨–ƒø/åS˨åÓÒ Ÿ(¢h¢‚]DÏ¥¢:44]¬¶{ÃÑ°×®Ó0‡àB÷ ÇSÙBÙ¨5Z›^¯×mÕ¬8;.Ùaÿ2NsªÊ§%Ôaù„PMó ´^ævâ6‚YY¯X¦Üè'ÓÉ°[/g°`5—KóúC1ÑjÖ››Áp< nšfÚ=j<Ö%ò§qêô\œª€Z>¡S|úüvµ‹¨ˆ½¸Të5g´úãÙ|6î5+Ù8¼âE¾`4™-T°w»}8dz“´gã!Ýí@‡=˜¥Ü?ÃðU³9ÅÕó&ÔçóA G¢è¡‰DÂ*¢ªÍ\«JäÍö`ºXÎ'ý–‘‹‡|Í#S8£´Þ FÓùrµZÎÇý–iíßkk= Ó{C¬¨ÚÃ×vªaâª|¾ùŒ'’)©¡¹\.+ET†­“í5œ,Öº#¨[Lðšù¼ܱT¾\mvzÃñl±¾ÝlnW³‘Ìix·Ãn5&[í{ ŽÕ²*RÕì…T8 Z>ÐdR|fsRBËP.楈êšç”SóER¥úÍx±Z/gÊkؘ‰lÑÄ•<œÌ—ë»íýýv³DžMœÄò;ËëJ®ê‹‹7µïýüü|øJª²zy ª?
-#¡*ŸP[(:h­Þh6õj¥IDü^O§æ¤KÞd¹F mS¼êºMùJ½Óà ^ÝÞÝïvÛõ\›MD‚ØÏnܪÊd«eh‡ÉB5eõ-¨‘h<™ÎˆÎbéDh«ÕéJ]¹é¶êFѧ8•=þh¦ÜìOW·ð:l›ùD8àóI\Kµ6´.Ö›íîáñééaw§[Ì&£á ß'gµ¦*“º¬y!ÿ.sWn$ 5€UìD‘Š ¦³ùb¹bV«µZ½Þ€Ñ¶í†£ñd2û†qªzªÛˆe+­ÁlµÙ¬•×d$ˆÄÇR£y3œbß_žŸT`F1›JÄ"aœÕ–\—Ý™ÎxDý¯œžH6Ö¥„¨B†J*¤VÌZ£Ùît:]`KWY,—ËÅl<è¨ÂâCÜÜÞ`,kÀë^çÊk4 F$®Áty+a}~y}}}~ÜaÃ: ³\ÈfRIÜÕÑHX:“ýúòdÓÒìoáx"ÙýÔB¶*–j;5‘Êä
-e£Öhu{ýápda],WrÕÞmnת° —~lIM—úÚÎ-¯j! #õ×Þx!ZŸ^^?—§ýV^ÚmÖÌJ©Ïe3éT2ÃLÖ=n×aóˆúmØ'ÒùÛ‰„‚ª©?Ô
-Ë~]Œí¼êâ5ž­àDš/WËÙð¦i–ò9_µV²¼Û?½œÄU^l#~7·«Ål³*¦±”Õk½šK®ãsŠýœXÅVET±Jí~j˜¦ifµVë¨cÂ{‰™uëˆM(}–¿<¨Á¼Y/§CÌÛ¬=‡ƒ±L©ÞNç‹ùdØmVËÅB¡dÔQrVâõõóáäåÖ’–Ø˯ˆ¼×lÔï¶U£TÈe¤×b£÷\Kbéõ[üÚ*¢šÊ@ªQ­#žB³‰Ç¶(í÷è©“*§3±¡k aU´œÕb:굪¥ î&¯Ç£¢©‚Ùê&Óɨßi"{ÅRÙltQ^Á—÷±Ò
-¯2á_À3~„Cj9Ã0†YU~×±æ¢ØoóVe8–HcDš²G»"ü“ýòlj#Í¢ðz
-U0¤Š7~¦ƒ¡1ãFrãñ'ÿ®(Ã
-Ù‰€•a³­ „ö;‘å aÕ«¥\*ò»±8mv
-6•/£waVËŒ×<Ûê`ê‹åjÍãOX,î–ëͬZ>ˆØÅlªº1äŽrœÏ$"A¯ÓvìÏNÄ
-¬J¬R-Vê*^ölŽ¼Ò·Tw'j™kÄIŠ9ß{û¦YG泩XØÏ~c…û}¡X*W”V,äP‡9›è ½1žèïð¦ÓÙdöãó³òaZýy‰ÁÓ
-òé8,Þ¡ûÚïñmÝ¡[å½G“TT¼è!}vµFÐÇF4©ÉŒ'32šˆW°IBmÔª$‡â Ö««+þ(†ŽËç5•Ä¥@¶ ²í³Û Á<±ù%ËÐ… +¬2¾Œtãf­”KÂã×'®/NS…XµCN¥Z“T!Eš,#OÝQzB0ä×g-æ ø§T«Îm»ÕT5›N%âÑ°^#—–Kõå ’%4Å¢ÑX<A²¥
-öS§«¯‡ ˜0uïù»e31`¸Åœk¶Õ¨äÓ±FŒ {
-XóLªëµû2KçÑkº¤Ê7úÈÝ" òQ‘%֥ȥ„úÚíV«¥†P£^«Vœc< AÕå°Y±E.ÌZ …#‘H8
-ƒ¡P$Z,ªR¹Z«3z¹§nZ0¼y¶¸C…âé猅%¿¬a·Ý¨2ñÏÅ€=qÝß÷T­‚µÑîÓÅ’TwôÀ;éº&VQ+´uÓl4õz (+e^©TDÅÍj¦¡€ßçu£_[/-çç{Wpy¼>¿ßïóù¼^¯ÏçÃÊ–ö\
-¦W—&ÕÛGƒº:œÕz}Mº&\dn ÀhÎ@[­áª¸Z£Õéðå)[S»Y-åRÜ9öÓÎѧÅ*qçF‘ G¥–+6åºÙŠZe`P¬˜sFœÚ¥ªífÒɘR^8.NY«C!µ)™Ò€UMöŒAË%ÿá3èÚ´r=L]
-ðz8s©RØሬµæk$@¶œŸ¸j±žëz
-©Æ¹"QH+Ø­H×¥pU…‰Tõ°á.mi¨‡f¤Ôi×®z}}@ʪ$P…ªþÅÄŽ%l'€ W)×aæn@¾¸dJ.“-”ñ€˜¹ü¼ Ö|: ú\vØð)^’•‹s2.²Œ
-Úh¶;´¦ ãUAݬ—fÞ+5¡Ò{¥IˆZ•¥^^’¨eOT2Õ„jþva{|ŠòÅ…©\œ/Ø­¬"±åL¾Tk¶0rñ(0aÁêvh¹¾o®&Uz°L¾­Š G64“)òõ‹•RåpýîíÔÈž©S7#%O¹sS¤óýá ^ž†{®àZøy0xéÌ*týpå`(ŠbW¬Ô±oñ,EÕi³žäªû’$«Íé¦XÓ¹çD ×C1™Ý­6÷÷Zª\5ÝöM½z€ê÷îÍWê®e/ϳ³—Dÿâmÿ\‰_qfAËQäWÖaµŠ/,›Š°¾s¹~0û“ÕãE“™Cµ‹Ì`#öº=ônÄÕJKU²¬^)å³i@ ªÒ©Ô]‹åâ Ïÿè|´½AŸíó÷BF‘U<xQðBÑ*2ZT.“$V]øÝ»ðÑdUIÖl±ÚÀ*4Æ“ Z&ªQ`ñt6ŸcÕPªbÀÅ|&*j’Óñ¢î~‡ó'zü|GÒUâ%ÚË+]—a0l"•N³‹‡ëõ•åBúößøžþÏNa•É
-ªÚÔjÍÛÞðˆc¯KÉ£±Z5œª0à4Ô
-T»ö^]ŽL¨ßSá5 •˜²x r,ÆØc}ßrUɪZ°úîÓ,!6Æx¶¸[.ï3q]ÐöAø¶%RÍ2Uá¿Ò}­ï=3÷=¯9Š$s¥éьѠ¤¸¹OXM¬œ¬n¯¦Z®5Û]Áº\­WX©cнm·pív»ÕlTË ˜oQÕ¤½÷îÍ÷×>ô±'Ój³/NÖõÕ{ïLÚƒe²Jû€VAµÓcKšßë\§£>§aW“ÒÉ®DÇs9•T_ê¯îáª/ÙéTÞA¬ï˜«`¥;ô¶¡C«ª"-–R©×þ-M¥\*Ù9S %U—¤*kÒê?úôöM^mžöŽV…ÕŠ¡˶£ ©.î®KnÕñ Ûn 'åsÙL:•€KW‚k©j¨¯ò'0H„ì%ë±ÞXï«z#Àjsxüáx:_Uðt¤r‹ÅbŽ±:ìÝÞÔÊpßd"έJÖXQý§•úòÏøMM9íï+‹°Íé DÙâ~Ú ¯è¿h€Šý:ìwZõJ!‹©Ñ“[õòð
-_÷%¦œ~¤×|¢W<L¨’Ě̕ëmP%Ôõzƒ[¯– Hud`»vZj1—’­ŠTýÁ€_ûoù—îÇgG+ëµéuN§’倵q;ÏîVëÍýýî~ÃÌáÚe®–òéD4ä×RUÕä HÕ<³ÿ3}üžéÁV»ÓK¬•fg8]¬6Ûíããv»%×Ål<ìwo[7Í:k6 ¼.eÀo*ïÃá^ûQ^ëöã
-û8tðZ¸y%ZƒŠk4†´òHk­Õ/V›ÃåÛ÷÷÷ÿ<ŸvkÛ˜O†½v£VÑ5MÓ­T$^Œ_®° 3Öhí§KÛƒ1üýý×ǯŸo¯—ÃƆ Â
-VKªª–Yä5O¼•[¹¦¨O­ƒñÜt¼ÃåõíÇûûÏï߮ǭcÌF½V½¢©EEV”"2«©d|¹B¹Rp·ÂSouû£éÂt6ûÛ•ó ¦°kÎFÝfU“’(JÄ–J*ìM,x¯A#âk%ÒtNðµF“ÙÒ´×»ãåùõõõåzôµ6*°ÿæžçQ’!²E¥ òL&’u8`Dü!Œ´²Bm½áx:[,Í•»ÙNçËùtðs6ì4ʪ,ò9àø<DV‘ ¢ËRIìõ«¿„ð8¬øÂÉ°¼T­}ë|±4LËv7ÛÝ~¿ó`ž;õ²Z8&Kgè,“C‘•$ÜÏׯþÂ'÷°âôê u</ À4­•ãºk×Y™óI¿]׋Ïf3i€Êd!²‚ ðK£¸’z 7­ñD
-V&ᦚղ,ÓÄjMË4–pµÂƤ)P¥4•J&ÉT:C3l.—cÑ?®ÄkPðg0
-+Ü­yÜ­ƒÉÜ°lǶWàs¹XÌç³Éx
-ZU]•a£°&p2"x~#³ˆ'd:B´_k*Ã
-°0A³Žç(¬»Ã @^Íù¸ßnø#˜¥ïÛRÁfA-æáh ¸\ãЭ µÒô›u³=Ï—Ëyµé°Óð÷%´ÿ+þc_íƒ/•X þNR /Ö„Õ^{{°z½^ÁëÖµæãºY%ž…fMøÍú‡ÂÈo¾ò;>Ÿ2ür¥9±ˆ´¢f…|¾^ŸŸÁëÅuЮéH+šÁ÷°þå_}Õ—îü1TÑxÊUÑH«ë‡õ¸žq\GÝz¹ˆµÞf0qH>+Ñ_wžü¸J¥Zg4·ÖÛ[X‘Ö#´ërÒkVÔ‚Àâ£õw³ÆçuâÆâ)Šd½ÙŸšîöpëõ|Ú{Ž1´«¥ÿjýê/ ültÆbñ‰dšÎ‰jµ=ZØÞጭB·ž{MáN]Wò¹,•Œÿ¥Z Á×'ÉÔ4E³|A«w'†»=^ž_^Ð †´.ƽF¹(rw­d¬5ƒˆRšÎfiD–åDØš` ¯w§+ò
-Õºßnk1é·*ªÄ3TŠh 2謭)Šfrp>_P+­Á̯cÚo׶‰´VK²ÀÂÝÃZ¿úý åv­¦2LN%YVd¹P¤‚¬êµöpfº»ãåzEZ½µmÌÇývMƒr¥ÓDk ¯Ð­ •墦—+•²®i¥’V®5»£Z‡O— Nëʘz-ЊË5%Z Žk’ÊBŸj•z³Õn·[ÍF½^o´:ýñÜt¼ýñtÂCk­"­ Þ™`þê·'ü/P»B\Ù¼¬U[ÝÁh<†ƒ~¯ÛëFÓ…ål¶ûÃ~çùZ›¾ÖL*N¦p Á[S"å$µÚêf Ã4åb>L&SøÍrÖÞv»õÜÿj%åpP½ÆÃËz½;š›öz³Ù¬]{e™(¶l?pV·!,­¡àîUPÊM¸VomzØo½µë
-a’Öp‰<BÁÂýzÄÏötØ®ms>î­a¬ÀB`ÇKgwzþlØóq·q¬ÅdЮéE¢5\À ~ŠC`EoÀ¥ãç—WX‰ÏÇýÖ×Ú©—‹O´†Š[`éœX¬´3ÓÝNçó–a°º2æ µQV%ž%ZÃ
-,œ°Y^*UÛÙi¯½:]]Û2æÓQ¿]/«¥Ó‰X”h %†I,kµÎpº´lÇqV–±˜‡½Nº•h !X4‰óŠ^ï &ó¥a,³É¨ßm7jå’"!­É8Ñ*"~`asb±\ïôG“ét2ö;­zU/)…<çk}"ZÃDäØT†á%µ\o÷Ãá V+º
-Vù\6ƒµ>­!i¯X˜ÄBĶ:Ýn§…'0²ÊÐlÂDk¨ÀVA+xEåD¹T®5šÍ&Œ`Š[MÆae"ZßUd5¯Íò’R*WkµZEGûÁ~X‰ÖЀ›õIÅã‰d*arˆÕË岯•AÍz›ÁDkH¸+HM$’ÉT*Me²,——µ¤•Ô"t+ÞƒcODkˆ¸kEAMS˜ Íäø¼$+Å¢Ra
-£‰ÌàpáŸ7ñd
-tf‘e‘Ø
-CÎÖðqך†á›Ëq\Že°Ë€X!/Šycïq%ZÃÖ
-G+E3‘H¤–±€V´4E‰Ö0ÊiMÃiƒ
-UQdén–…üÂX+‰kxˆ`ÐN ­¢ û¯¦©EY¼GÈÒ·ËÚõ«_˜ð‰ÜxÀZS†‡{µ\­ÕªeM•%‹Z–¦3Ê×Jâ|£Àã£_®YNT´J½Ùj5ëU‹eQTÁjŠh ¾T¤ˆ¢U˜ÎååR¥ÑîözÝv³bE´Ó8¬q¬õ_öËsIq, ÂQÝ]Uxï½F rH  jÞÿYö\ ªwçÏÌþêº7ŸàD|‘™' ×ï® Séëëè= R¸Ù13–ãØ‚-ä2P­1‚…úŠˆF¢Ñh,ŽN¹ÞÒ3nµ^¯ó [-‚!XñÐc°FÑD2™L¥³ùR­ÝÏk~+ðë%€í·P±™`¸¬8(6Щ‰T*LÔ|± åJM¸õv/Š{a³œ3£^«^.dS®ë·WèVÔ©Ù|¡X*•Ë•Z~&†] ¢¬¨²´Û,gô°Û¬sןd¹~!»"¬Ð©¥JµÞh4›­v·?bæË­¨t]S,7¥ú­Z)—NDß^‰]1P‘X^%°i§Ûë÷ûƒÑx2_ò{E;š¦¡«Òv5§‡z9¸’ÆA×h" ƒµÙé©1MÓÌdÆX ˶-CSön2ê6€k<äú§¯&ú…1CƒµÑPÌt6gYn±„—IÖ ût:9ÖQùEÀú5B¸â '×l±ÖêS“9·\­7~»„Õó<×1âÚ©—××_$†¿¿¾¸ÂbŒgÜj³v{Q‚ a=_ΞcDžc†íZ1›Œ½“· ý—_Ûz¶ØÀd…qT-Ç=_|ÿâ_÷–î7+_oÓŸ¾šèÎœd¦XmõÇÀ6«ªé†iŸÜ³½Wû¨ìV3ª‹b8Fb=ÿá|¥Ù£&ÜZѺ³zÿz»]/®¥K[Ž´ª… Y¯Øè±_S9T°ô|µEÊ` z¿ß®ç“yدçÄ®x úõÇ/â:ÝÑ„ÛìоAXï÷›ï9GYXL]ŸíJ¸~{½ ¿ qVÀõ‚°~|Ü|×ÒÄÍ|Üm”È3ŒVÄ‚8ù0ì‚«çßŸwˆaC–“aìg TÖwàšÎ—êíÃnöêqýøüü¸ûž­K<;î!»F‰]qÐÓ¬Öx2“ƒx½SB®P¯×ËÉPw«é—] ÖﯠZ³Fc‰d:[(ƒ_iöÉõŽ¾&hW°+Ýo”s¤]ñPø1½½GcñD*ÉJÕfwÄpa_`»Ï0´ëtÔ©Áv%vÅAG8‚¬šÉå‹¥J­ÙŒgáßä^üëõâ9ÆAÜ°t¯Y†íJ슃»5œ/–+µz³ÝŽ§ÜZ÷ëåröS“¶ x†‘]£äÆ@]ß"è]*VjV§ÛRÌŒ[oEU7í“ëy.`•…ÕŒê6ˆ]qp»ÆÐ\m´{ƒÑ˜™Ì¹åFíhÚÎéäئ®ìÖð4µ*…4±+zØ5•+ÕšÝÅLêšö’ª ËY†¦ì7ÜdØ©³äÆDÀÚ5‘)Tš]¨U6„*«šn˜pµÌ£¦ˆürJõå|°]‰]¿¿^®±d¶Xk÷á[Zñ;ñ7Ô«* «9=hUÑÓD슅\!†ë!3_ò;À¦…Šõä@µº*í …GÝz)—ŠG^ÉvÅAÿË•]má]2,½ÁðŸì …·`×~h× … ×o¯¯.ÕÚz¾z¬f+ìV×±ŽyÏ/¦áÆ!vÅF_SµÙ£¦‹ÍNÖ àzñ}Àžà–!†™a=Ãa»þ铉þ…~ïœ:v¶ä‘aïìûW
-²n{þí~¿\·Óo–s°rH 㤗ÇïKf‹µÎp²Ø†\?>î׳­K<;îÕÃz%1Œ“€ëŸÈ°™Bµ \å蜯÷'×ÍœêÔP½’•ƒ™†M¤®KA1N—ÛÇççpÕÄõlÔ&õŠ¥
-9üå׫gªGwQ½’ÆOO®ùjkÀÀ? ê×»ï2ÏR*ªW²r°Ó3‡ó•fŸfyI³Ü`¿ž]Zφ­JP¯$†qÓƒk*_nô¨Ùj§Žç_¯¾ûx›JYR¯8*Ü9ñT®TïÀ€åŃyò.Ï1”-G÷êÅL¸^ÿôDÿ—^~s­µô|%Ⱥ鸮cjâfNêW!®¯oÑx*[¬6{ÔtÁïUÝ´mSW„ådÐ*“zÅR/?à~&Ò9àÚ2ìJºaèªÈ³t/\¯? WÌôˆáX2+!®ô|É¦©’°š:U²^±Ôï.”ë­Þˆ®;IQUy¿á˜~bÕ+±+fzÚ5“»vÔ„]m÷’¢È"Ø•êÂ7œ õŠŸžvEíÚèô©É|¹DYQ¤=¿˜ ÛÕ|š¬ \½‚]³…J£3OXHaQVUEÚ­Yú+† WÌÄ0<Ãù2Â:åÖÛ½¬>¾¦ù˜Ä0¦
-bø=–Ê•êí=C/“ªaääà®H ã(Ão‘D¦PmõdzåVTuòmËÐda9¶*ù‰a …bøÚµTïRÓåV:öÉõÜ“¥ËÛZ9Ù$‰a Äp<¯´ Ç‹Óñ.þåìÚºÌstÕkÄ+VzÆp±Ö¡f«j8ž½]/ž­K›9Ôë;©WìpB 7z4ËKšå^®7àêZ‡ýj:l“zÅS//hå$såfŸYleÝöWÿìŠ@ê_…oS
-ÕëqµÜ³ïûg×Ò¤ ®WR¯ê±^\%Ít\ï쬣²C1\Í“zÅR{‡Õ£i;ŽmU¾aˆá\’Ô+ŽB\#®ìz'Ž†iúAV3ê?ì×ÇrÛH@Ñ¢˜sÎ9$H‚sƒ’mY²5ÿÿ1Ó Pòvf5Õš{6Ò’U·ÞÃëf9—ŒðyUëO×öÀ˜¯÷Öùr½^NÇír:ìÖKñy¥«zä3ç£k<[íÖé|>÷›ùDïÔŠâõàóªûù*ºfŠN×íþx:¬Ãn=7íZ!MW%9Ï×HBtí ÑUÎëÉ:ÒUiòó*ºF“ÙR½«OæëÝñt¹\ÎÖa³{˜®Jr¹nÏ×T®ÜÔFæRÜÃ×û‡‡û‹µ_™ânâûª"—3®öÙTm÷ùæp¾|z~~z¸Å;Gk”èª'«×/>¯r §ËÝéúôíÇËËoçýrÒo–³tU¨*³Šqµ×po<Û/ß^^ßÞ^_žïëé UÎ&誖Ϭ¡h"c¯áÅötÿüòúë÷ûïק­©·+tUŒÜÁNÖˆ×R½£O–»ó÷Ÿ¿Þÿúëý×ËÓy;£«rnYý2k"“/7ººü¼þéú|Þݺú½n]Õ`/a¯Ï”YÓ¹bµ©é“ÅÖº>ýxýýþþûMΫ©·äÝDWu80,²¦²ùR­¥éÆ|}8?ˆìÛ¯·Ÿß¬õ´ßïœßCWE|<pÂÑx2Í+õVw06—Û£ûýåçË÷§Ë~ihõb:òyîèªÙÕ>™œ¬åªèÚ"ìát}|~~~¼Z›Ù°Sͧ¢AÙõ¿þÁøGDW9®‘X2+”DÖF«£ dØÍÞ:_ï¯k·œôåó5Â9¬ûëG2k¥Vo4[no02ÌÅzw8ZÖa·2‡Ýz1cŸMtU„½†ƒ‘x*[ÃÚlµÛ®Ö—aç«Ív·Û¬fã~K®aÎ&uØŸWHŒk¾T­7ÛN§Ûíj½¾>š˜óåj½ZÎ ½Û(‰5älRÆ­k,™•—pGë9ú}dLgóÅb1›Œzíj!ÛŸWº*Á>›áX*'®â»ª ƒø3OD×ù|65t­Yɧb!>¯ê]ÅÙO*ŽX¾cÃÛ ;ëBtºr.¥«Bœ®‘D¦XkiúxbÎf3Óœš¦)–ðr¹\È®z)›ŒÅÙDWEØ]ƒ‘D¶Tï ÆSÑrµZÚV«µ°Z˜r^骧k4™+7´áÄyÙloäkç–{˜®
-ùìZiöFær³;ÇÇýn»šO†]UsëšÊWZ}c¶Þ¬ÓédYÖÑvØo×tU‘ËåöøC¢kµ=0æëýñt:ÿ +ºŠyýØÃ>º*ÃéûÓÕ’UU¼kx-ï&y'"²«‹®jøèZ°»®¶ûƒœRy1m6›õz¹0¡Öª2qºªÄu÷Ùµ?6—kyoÖ«åb±˜Ïç3s2Ö{íz9—Š…>Ï]ñÑ5_mõFÓ¹|½.æ³édbãñx4ÔûÝv½RÈ$¢!¿×MWU|v­45}<5g¦Ñ‘>è÷z=MÓºV£V.d’ö¸r6)Ãé*Þ¯åz§¯ÄŒŠÕ:íf³ÑhÔëµj¥\̉¬‘ ßËV‡ì*Þ¯Él±Öêöƒˆ*F´R.…B!ŸËfR‰X8è·Ç•®Š÷°/Id
-•z«ÓíŠI­WJQ3-¤R©d"‹U5.×ÇÇS¹b¥ÞÛ·V)å³iY3‹
-‘H8 ˆ%ì¾£«:DW·W|`™|±\­VŬŠªñ¨¨
-Ú¿ÏKVÅ8]ƒ‘x2“+È/j.“ŠGá€ßï»Q=n¹„ɪûp
-„¢ñd:›uŽ¤HHŽ¨çƒ[D½#«bnŽÆIy&‰
-ˆªvÌ.ªªFvõˆ°¡H4Çć5hgub~ú¯&þ­[Ø@0·oØÙÁî;’*Nt•a}¢¬¼~ý~/WÒ—p ëõùü~çIãæIó¸ä*v{DÚÛ“†¬_ƒ +fÖqGÖ¯Ãå¤åMóåð¦ùª¨
-
-H‰ì–ÑNÃ@ ›ÿÿi@€RržMì;'ÝyêƒY{‡4êëeŒ1ÆcÌ<¶Oþ>ý²ö¦flc’²“Ž]DàèZÅTã+A–Î6L6—5)˜'|5‚&¹`¢ðÕHšÔ‚i—#jÒ
-æ _Ž=!dMJÁ<áËi橫ÚvJ4sˆÇÿ³Š‹\÷4¨v>g¤
- esÑÓ¸ÏÁ¿³ØT>ÐÌþUÑÑ4g8 ¯m¨éç¨øf–ƒ_÷,¬›'p3ÊÁLkæ‰ÜÌš¡uèÔ†žÐͬÚÇNíæ)«>ÝÎøn²uéÝ< ö´AOKïæ X°§ï›KãvÞO”=Ñ£KÓvÚÛyCO+(òô4QUšìé=EÕyz–¨BOš¨æ^+= Íe­“)ÕÄ«‹VçS쉖ïî©\Ó3<Õk²§TO­ßã,=ÁS'M=MÑÄ»¿·&{JÖdOøžº®Wè穧({bØÞ÷”•6G“ôû)STZX7OšT—ô\OÚî0¨(Ñ*‹cƒ ¨(ÍjZZ45Qª®Iýúƒ”¸ph¦'Ð]ÞÂÇ€.gæøÁÝ¥á-z™áåÑP¥’°¥h8®G—‡3¹(ØÒ`vgœ•CkHÚ»d|ŒòïeÝxÔ8§!·<ÚcŒ1ÆcŒ1ÆcÌ›ò!À
-H‰ì—çWšËÆcCzï½
-()¢€RT `ì&ƶ4ƨIŒÇxÒo’ø'ëèIîZ>ŸÞïÚ3û·÷ÌìçÁƒ{Ýë^÷º×½îu¯{Ýëש tùUÕïÝÓ¦¦ë'ÅþI›ýMÂ)4_QKUðý¿¤Xüÿ‘
-…J¥UE1@t:J!#T-7O°R¢“‰0·ˆóe‚¡Pé &‹Ífsq¹\—Ãf1éTJûm@Á¡4¿ðß®J&ƒÅáñB‘HŒ$‘H¤R©L&“J%"âSɤ'ˆƒ“pp6€â ¥ýæqþ
-©ˆÏaÐP7
-Ž0‘©(¸Ñò¹,õæq~¿PÅÛÚ© 6O$Sjô¦ÎÎ.‹ÅbµZm6»Ýá°Û­]fƒV)r™4r[ë¨狤¨*…L À©í7ŒóUº‰Í—(µ6§»Çãííõùüþ@ ˆðyÜö®LÈE Þ¨p;áà
-Þh2pçqþÔiUœD¦±ø•¾ÓÞÝìG‡†bñx"‘L'“ñ¡HÈ×m3(Bº¶þ!3Ü«œ'VêL»Óé°š *©€M'_ç&àWÓlj‚v¢2!ƒÅՊƇSéñìD.ŸŸœÂÊçÆSÉhŸÇnRKù,j{kË•íý 5\
-ƒ#RèÌön¯ßïó8-•„Çü‡8UPݯßòÏ4TÿR¸ât¶P®ërùÃñTvrz¶Xž¸°¸´ ZZ\(Ï&R±ÇfPŠ8trã¹âv®ìºR¾Tc²{‘h8äsuéÂïâ<¨‚Š ¨€k&÷ïýÕ¯AETœ+Q›¾p239;¿¸²öhcsk{gwwwg{óÉ£•…¹É±¡ »S#å1ñÁ»’\Õí|—~"hl¡BoéFâÃ#ÉØ€ÏiÖHxŒ†8ÕQdÀ ªóh‚šªy«_ä„ÐÉh§²ø2¥'ÏL•×6¶v÷ŽONNŽž?ÛßÝ\_œË„{m¹
-¥J8åRàÉiXôN9Á¹óGG'fæWoï?uþîÓ—oß¾|zwþêhûÑR1ŸŠúF•¨Æ c¢19|1$§Ó ½V%‡ÔXt
-¶‰³‰n?ŽHmv‡ã§Ñˆ×¢•rõçaâCàp¹Ýn§Ýb6¨å"«ÚQµ¹‰\¥5t˜@ÆŽk½;N4¶HÙaóöÇÇò3å¥õ­ý£Sh¨/_>¿sz´¿µ¾XœLÇá2(Ðý‹÷Ó =€6-”Brf 8A›µË¤WËÅ|v=¨füJ(M® <¦ÅréNLB™Ú€ A /
-ý—Õ¤Sˆ(ìq×áe*½©«‚Óaí4êT2àI'ï9A&,Œ™n8‘š(Ö6÷_ÿýáÓÇ÷¯ö6WÎæÓ‰°ÏÕ©•ys¥ЦQr.øÁ^o·RSJ¨êžÑtFe ÿÐØäl©8ç®1²Ó9™ÆhuûB‘!°KÉø`8èuvê•b4º•A6áÔtXœ=>„³/è÷bë)Á %ßeCó“'Qm=¡átnz~uóéÉÙÅÛ·¯÷7WÊ…ìhlÀﶔb.ž›+ömÚhu'ŒDaÖö÷8:õ* ŸUÛ3üH¢0ù2½­7:š›ž››ž¸Ê‰p€ÄÈÞMŒf²¹\.›‰î"So®ýg´uûû£1À™ˆ ½.‹YOüÛrÂL®5Ùº}}áØÈøTyuûàÅÙùù듃­ÕÒT& õº­Fµ Ø6l¾*›ºÝ@79šK$û!5D³fß0Oª³xÃÃÙÂÌ̼wžKNø8‘ˆ:Ù½ý±ÑìÔL±T*ÍMç3Ɉµ°€…:® ƒ+VvX{‚ÑDj|"7‘ͤ’hMTÁjßݧZ=%J õs_$‘.<|´{øâôôųõù©t<èqTº›ØLs®Ò`éDcÙüÔÔdn<ûÝ]:¹]µoÄ3ƒAWO"39==9ž¼äD¼x~Sà~L¤±mZ]]]^(²Ã<D·4Wœ¢¶Ó‡Y¬Xœ-ä2Ã!¯Ã¤Ò®ZÆŸÍ Ÿ{D¡1˜-Žÿ@b|féÉþó““ç{O–f2ñþ^¸SõjYåõ!ŽaoÍN$™ÉÏËóå" ‘Ð`ßÀRIxÜ`ðð`Š§ó…k8¡vÛÔ…lS¡´´öøÉÖÖæÆúòüt6Ùï±è*wbKÅ)l½Ð™Óå…¥å¥…Ò º9ý.Tx‰I­w{AUxxnÍV·?2’/¯ï>Ý^+çGÂ>'4“BZ÷úVí­ÁêAéã&XA=
-%s…b>§ÝVòìdw #oºïº ÔÈøôâÆ‘Xc&s77ùdЮÁkOaÞèï`"jÝæ«6QÁX:_º­TîJù$ 3ÐíoŸ^s2:ÜʦW
-÷×àîƒý„ƆÕ=¡ÊêO*Íûvû¾Ù€y*fÈ ¿…á„b9íòFwøæ7‹uÊ ‚u·¹< 1‹$à·ã„ )(™ß@v>SÛ à”Kmê³ÃuÆ$q˜XLÝ%µÛÞ ¹4](Wj –R6æ5«„û̼ô÷pÝÁä9<^·Ýp!:XŸ›`þý‰¡³ë‡b­#”)7Ú÷ˆé¦%è2(ÿç¤DNÙ\6C;Õal'«øíì©
-H¨‘‰Ùµ}Ñ¥-Ì“yRŸà/%u/ήÈ?9UëÍfý®x´k%GœùI\æ„ÓÀWô§c±Úäôú<£êì3G ì©5óÇ2½;š¯´ŸÛÍZ¥ ¢ó±_uÌî7§àºKgÒ‰Û¢e ˜¦'Fþù]*ß) E3ÀIm &ÁŸ
-×´]ƒœÆI¾d¹<tÂÕ]žTc¦èx¦x[kÞß·jåL„ÒŸŸl-N}b#D'HÔf—ÏïÅx´»œ&6OÎ žx¡vœî•R!›JD @ie§»«rR):©€áÙ 1ê ;¼5¦(–ÓÁ™ÆN§Š··ÅtÈ©•€î&¾ñüð§S™Ö⦙â]½Õ~h7+…„ϬäïbdÎg†Óâ&Wª±Pþ€Ÿ2«%Ç “#¯9ÉÞD8Á8UË…Ìu<ôØ—’ šÃ½œÀÇ]t<™ŒÑ çBg™Lý½ù8±—gBöcÎp¦T©”³êJvŒ¯?ôŠ3×!BèlÞp2Wª€µ€fê¥XšˆŒ £Tä„yüD¦µztÀmÕH¹›d!béåÄt¥úm1“Œ†§YÑh Óý`Ͼã§`,‘ˆø!ƒö9‹ïŠé²!ËlK¯¨hî¶V»+ĽF9UÄ®¯.'3…J½ÃMʵÖÃÓËócó6vê$(¬¶¾’x±Gg÷Ò¡ Ç¦•.YNdžæ7¸çw,׸oêr×QÚïqZõ—2ÁÁÆâô«ü„>q<{­ZéÉöÊ Šnà½0}è„lxzy\ìw׫JЉz]NÌ—£S‹PF.M`Ù›Jãþñåׯ§ûj>æ6€A-M‘ýùsG.r½Ã
-Ó>(B'ÛKè_̸Á¾›˜ãIu®0Ƨ:¨. }0¡âòöÖ¦Fsú¹²+
-1·^ÆÝ„Gëi™lÌœ]Ýã+˜R†ãôðüëß_/õÁK0(‚l‘ÎÀõP.‘hØe¤DÉd"F¥ÉIJ%õ0Nn‡Å¨½ Ž°¹a#Nä }¡Êì1œLúŽ˜ðò(;H: h¾Ö~~yl”’¯¦ãS—ÓW†¨Þ6œ*`(x|Ý=·¹øÃŽÄWØ¡ÉàS*EN½¢—Ú<®C™Îˆ¥syh·QÚGÙÍzRÂ6·!–sHeö0œ(ÓÛ€Þ<`öp"ÛÄ„UË/ÖÁožZ·é í²3ݬðØìåõðìÒâ¦!4Ÿ[ULšÉñ&zÿ 4¾‚T¤NÿŸ§äF(@IÈE±p
-úÙ¶ë|s á4>³¼u,Rh f³I§’ðv×æ&GØ&Ôy ùµc¡L¥Õ›Ìð•^w)Ÿn­Îýýç+i8L
-^àÎÕð‘éª÷ ÷åþ4»²},RÂdûC‘é¢R&ÿö§äÎüfÉNy}>¯×M¹œv+®)°ßõ0´¡/dß Á§{<±âR£U+¥üv¼ÞÇÿدÒîÄ ¸r0‡Ð‰÷}‹Ó³_x½NüììÛìÿÿ%éžd ÏŸô’—©¯*UW×ôŒFìb:…Zw4_ÝÜ?>}ÿþôx³šÝz1m±k$¡Ñ¹[lî¿mýBÁ] È9vÓEÒÑÓËŸ?ýúëÇëóÚÞ÷ŽüßáW ¦d´ØlŸ_^ßÞÞ^_þøýùéñáv½¸è5Ëä¼gž³ø[ƒér½Ù¬¯f£L‰Î~„Ø@Iš™.ÔÚƒÉåêúæööv³^ÎÆ.Èq¢Froq
-uw|¹Z_ƒÐ°CÆ-ÐëùðˆJ"™«´ú“åõÝ–.ë†ØÉ;ô”8c9ѽ’ÌW;0›»‡íöáþîfs½^-ç“a·QÎ%É8‘câ7Rùj«?žÎçÓ‹A§V rì:†•1('_iv³ËÅry9» 2)CÓq:£qZ™RHÓédÜoû„‚‰‰Þ3Ã;·îpr¹\}]½ZLG½Vµ¶tº¬ÞŸáä«íþx¶X^-—‹ù ¼½N³ZÌ& o™¿ Y³@¶Ñq{=¹˜·‹½ RÙbµÙé F£Ñ°ï¶j¥\ÊÔð‰žct!í ’\·ÛnT¨P8dN¸ñ"¢ŒnK5t;$v»h×±t:Ìl<j®\o»ýÁp0è»ÝN»Õ¬WËÅ\ÚƘðrˆÛ%Ýr²ÅJ­^¯UŠDNô¦“•ŽIjÂvr@j´
-ûcÇHˆcBÁf‰‘H4c€¦‰2{dÏ4s TàFÂpùùž,èF(á£Ó#‚´v”ý¼•"…>
-
-S‘ð§bGÊR‘
-· èy‚V|8Ûëz¼ïšr]ø(‹lïùÉâ¡÷ºG9;
-ƒ¯áî÷¹®ÿ.àTq¿Ú ‡'tÂçš:J=Eþ\oŸ—:­ó/ÄÒ4Çÿ 0
-q
-/GS0 gs
-296 0 0 91 6647.5 7516.5 cm
-/Im0 Do
-Q
- endstream endobj 844 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 845 0 obj <</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 296>>/Filter/FlateDecode/Height 91/Intent/RelativeColorimetric/Length 6141/Name/X/Subtype/Image/Type/XObject/Width 296>>stream
-H‰ì—çWšËÆcCzï½
-()¢€RT `ì&ƶ4ƨIŒÇxÒo’ø'ëèIîZ>ŸÞïÚ3û·÷ÌìçÁƒ{Ýë^÷º×½îu¯{Ýëש tùUÕïÝÓ¦¦ë'ÅþI›ýMÂ)4_QKUðý¿¤Xüÿ‘
-…J¥UE1@t:J!#T-7O°R¢“‰0·ˆóe‚¡Pé &‹Ífsq¹\—Ãf1éTJûm@Á¡4¿ðß®J&ƒÅáñB‘HŒ$‘H¤R©L&“J%"âSɤ'ˆƒ“pp6€â ¥ýæqþ
-©ˆÏaÐP7
-Ž0‘©(¸Ñò¹,õæq~¿PÅÛÚ© 6O$Sjô¦ÎÎ.‹ÅbµZm6»Ýá°Û­]fƒV)r™4r[ë¨狤¨*…L À©í7ŒóUº‰Í—(µ6§»Çãííõùüþ@ ˆðyÜö®LÈE Þ¨p;áà
-Þh2pçqþÔiUœD¦±ø•¾ÓÞÝìG‡†bñx"‘L'“ñ¡HÈ×m3(Bº¶þ!3Ü«œ'VêL»Óé°š *©€M'_ç&àWÓlj‚v¢2!ƒÅՊƇSéñìD.ŸŸœÂÊçÆSÉhŸÇnRKù,j{kË•íý 5\
-ƒ#RèÌön¯ßïó8-•„Çü‡8UPݯßòÏ4TÿR¸ât¶P®ërùÃñTvrz¶Xž¸°¸´ ZZ\(Ï&R±ÇfPŠ8trã¹âv®ìºR¾Tc²{‘h8äsuéÂïâ<¨‚Š ¨€k&÷ïýÕ¯AETœ+Q›¾p239;¿¸²öhcsk{gwwwg{óÉ£•…¹É±¡ »S#å1ñÁ»’\Õí|—~"hl¡BoéFâÃ#ÉØ€ÏiÖHxŒ†8ÕQdÀ ªóh‚šªy«_ä„ÐÉh§²ø2¥'ÏL•×6¶v÷ŽONNŽž?ÛßÝ\_œË„{m¹
-¥J8åRàÉiXôN9Á¹óGG'fæWoï?uþîÓ—oß¾|zwþêhûÑR1ŸŠúF•¨Æ c¢19|1$§Ó ½V%‡ÔXt
-¶‰³‰n?ŽHmv‡ã§Ñˆ×¢•rõçaâCàp¹Ýn§Ýb6¨å"«ÚQµ¹‰\¥5t˜@ÆŽk½;N4¶HÙaóöÇÇò3å¥õ­ý£Sh¨/_>¿sz´¿µ¾XœLÇá2(Ðý‹÷Ó =€6-”Brf 8A›µË¤WËÅ|v=¨füJ(M® <¦ÅréNLB™Ú€ A /
-ý—Õ¤Sˆ(ìq×áe*½©«‚Óaí4êT2àI'ï9A&,Œ™n8‘š(Ö6÷_ÿýáÓÇ÷¯ö6WÎæÓ‰°ÏÕ©•ys¥ЦQr.øÁ^o·RSJ¨êžÑtFe ÿÐØäl©8ç®1²Ó9™ÆhuûB‘!°KÉø`8èuvê•b4º•A6áÔtXœ=>„³/è÷bë)Á %ßeCó“'Qm=¡átnz~uóéÉÙÅÛ·¯÷7WÊ…ìhlÀﶔb.ž›+ömÚhu'ŒDaÖö÷8:õ* ŸUÛ3üH¢0ù2½­7:š›ž››ž¸Ê‰p€ÄÈÞMŒf²¹\.›‰î"So®ýg´uûû£1À™ˆ ½.‹YOüÛrÂL®5Ùº}}áØÈøTyuûàÅÙùù듃­ÕÒT& õº­Fµ Ø6l¾*›ºÝ@79šK$û!5D³fß0Oª³xÃÃÙÂÌ̼wžKNø8‘ˆ:Ù½ý±ÑìÔL±T*ÍMç3Ɉµ°€…:® ƒ+VvX{‚ÑDj|"7‘ͤ’hMTÁjßݧZ=%J õs_$‘.<|´{øâôôųõù©t<èqTº›ØLs®Ò`éDcÙüÔÔdn<ûÝ]:¹]µoÄ3ƒAWO"39==9ž¼äD¼x~Sà~L¤±mZ]]]^(²Ã<D·4Wœ¢¶Ó‡Y¬Xœ-ä2Ã!¯Ã¤Ò®ZÆŸÍ Ÿ{D¡1˜-Žÿ@b|féÉþó““ç{O–f2ñþ^¸SõjYåõ!ŽaoÍN$™ÉÏËóå" ‘Ð`ßÀRIxÜ`ðð`Š§ó…k8¡vÛÔ…lS¡´´öøÉÖÖæÆúòüt6Ùï±è*wbKÅ)l½Ð™Óå…¥å¥…Ò º9ý.Tx‰I­w{AUxxnÍV·?2’/¯ï>Ý^+çGÂ>'4“BZ÷úVí­ÁêAéã&XA=
-%s…b>§ÝVòìdw #oºïº ÔÈøôâÆ‘Xc&s77ùdЮÁkOaÞèï`"jÝæ«6QÁX:_º­TîJù$ 3ÐíoŸ^s2:ÜʦW
-÷×àîƒý„ƆÕ=¡ÊêO*Íûvû¾Ù€y*fÈ ¿…á„b9íòFwøæ7‹uÊ ‚u·¹< 1‹$à·ã„ )(™ß@v>SÛ à”Kmê³ÃuÆ$q˜XLÝ%µÛÞ ¹4](Wj –R6æ5«„û̼ô÷pÝÁä9<^·Ýp!:XŸ›`þý‰¡³ë‡b­#”)7Ú÷ˆé¦%è2(ÿç¤DNÙ\6C;Õal'«øíì©
-H¨‘‰Ùµ}Ñ¥-Ì“yRŸà/%u/ήÈ?9UëÍfý®x´k%GœùI\æ„ÓÀWô§c±Úäôú<£êì3G ì©5óÇ2½;š¯´ŸÛÍZ¥ ¢ó±_uÌî7§àºKgÒ‰Û¢e ˜¦'Fþù]*ß) E3ÀIm &ÁŸ
-×´]ƒœÆI¾d¹<tÂÕ]žTc¦èx¦x[kÞß·jåL„ÒŸŸl-N}b#D'HÔf—ÏïÅx´»œ&6OÎ žx¡vœî•R!›JD @ie§»«rR):©€áÙ 1ê ;¼5¦(–ÓÁ™ÆN§Š··ÅtÈ©•€î&¾ñüð§S™Ö⦙â]½Õ~h7+…„ϬäïbdÎg†Óâ&Wª±Pþ€Ÿ2«%Ç “#¯9ÉÞD8Á8UË…Ìu<ôØ—’ šÃ½œÀÇ]t<™ŒÑ çBg™Lý½ù8±—gBöcÎp¦T©”³êJvŒ¯?ôŠ3×!BèlÞp2Wª€µ€fê¥XšˆŒ £Tä„yüD¦µztÀmÕH¹›d!béåÄt¥úm1“Œ†§YÑh Óý`Ͼã§`,‘ˆø!ƒö9‹ïŠé²!ËlK¯¨hî¶V»+ĽF9UÄ®¯.'3…J½ÃMʵÖÃÓËócó6vê$(¬¶¾’x±Gg÷Ò¡ Ç¦•.YNdžæ7¸çw,׸oêr×QÚïqZõ—2ÁÁÆâô«ü„>q<{­ZéÉöÊ Šnà½0}è„lxzy\ìw׫JЉz]NÌ—£S‹PF.M`Ù›Jãþñåׯ§ûj>æ6€A-M‘ýùsG.r½Ã
-Ó>(B'ÛKè_̸Á¾›˜ãIu®0Ƨ:¨. }0¡âòöÖ¦Fsú¹²+
-1·^ÆÝ„Gëi™lÌœ]Ýã+˜R†ãôðüëß_/õÁK0(‚l‘ÎÀõP.‘hØe¤DÉd"F¥ÉIJ%õ0Nn‡Å¨½ Ž°¹a#Nä }¡Êì1œLúŽ˜ðò(;H: h¾Ö~~yl”’¯¦ãS—ÓW†¨Þ6œ*`(x|Ý=·¹øÃŽÄWØ¡ÉàS*EN½¢—Ú<®C™Îˆ¥syh·QÚGÙÍzRÂ6·!–sHeö0œ(ÓÛ€Þ<`öp"ÛÄ„UË/ÖÁožZ·é í²3ݬðØìåõðìÒâ¦!4Ÿ[ULšÉñ&zÿ 4¾‚T¤NÿŸ§äF(@IÈE±p
-úÙ¶ë|s á4>³¼u,Rh f³I§’ðv×æ&GØ&Ôy ùµc¡L¥Õ›Ìð•^w)Ÿn­Îýýç+i8L
-^àÎÕð‘éª÷ ÷åþ4»²},RÂdûC‘é¢R&ÿö§äÎüfÉNy}>¯×M¹œv+®)°ßõ0´¡/dß Á§{<±âR£U+¥üv¼ÞÇÿدÒîÄ ¸r0‡Ð‰÷}‹Ó³_x½NüììÛìÿÿ%éžd ÏŸô’—©¯*UW×ôŒFìb:…Zw4_ÝÜ?>}ÿþôx³šÝz1m±k$¡Ñ¹[lî¿mýBÁ] È9vÓEÒÑÓËŸ?ýúëÇëóÚÞ÷ŽüßáW ¦d´ØlŸ_^ßÞÞ^_þøýùéñáv½¸è5Ëä¼gž³ø[ƒér½Ù¬¯f£L‰Î~„Ø@Iš™.ÔÚƒÉåêúæööv³^ÎÆ.Èq¢Froq
-uw|¹Z_ƒÐ°CÆ-ÐëùðˆJ"™«´ú“åõÝ–.ë†ØÉ;ô”8c9ѽ’ÌW;0›»‡íöáþîfs½^-ç“a·QÎ%É8‘câ7Rùj«?žÎçÓ‹A§V rì:†•1('_iv³ËÅry9» 2)CÓq:£qZ™RHÓédÜoû„‚‰‰Þ3Ã;·îpr¹\}]½ZLG½Vµ¶tº¬ÞŸáä«íþx¶X^-—‹ù ¼½N³ZÌ& o™¿ Y³@¶Ñq{=¹˜·‹½ RÙbµÙé F£Ñ°ï¶j¥\ÊÔð‰žct!í ’\·ÛnT¨P8dN¸ñ"¢ŒnK5t;$v»h×±t:Ìl<j®\o»ýÁp0è»ÝN»Õ¬WËÅ\ÚƘðrˆÛ%Ýr²ÅJ­^¯UŠDNô¦“•ŽIjÂvr@j´
-ûcÇHˆcBÁf‰‘H4c€¦‰2{dÏ4s TàFÂpùùž,èF(á£Ó#‚´v”ý¼•"…>
-
-S‘ð§bGÊR‘
-· èy‚V|8Ûëz¼ïšr]ø(‹lïùÉâ¡÷ºG9;
-ƒ¯áî÷¹®ÿ.àTq¿Ú ‡'tÂçš:J=Eþ\oŸ—:­ó/ÄÒ4Çÿ 0
-H‰ìÐÁ Ã@ÀÀMÿMç}ZÈÄà™
-„fà;¯Çq㊛!‹›!‹#eÈbÈâH²8RŽ”#eÈâH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#5–œÆ‘p¤©±ä4ŽÄXrš±ä0ŽÄXršøuϯõÇã—\ yô“ëO½òéÆMþ½ÿ†,†”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#åH9RŽ”#‹!åH9RŽ,†”#‹!‹!åÈbÈæÇbÈæÇ7øso
-H‰ì—{L“g‡§=PxB)…VZº"é
-"iƒ¬šÐTjš a+‡Óa5®ƒ`R7 x"’Š8 Ø ŒCˈ8E$HäÔ!‹œ©ôû¾ç) è'‡%/þÑíú—·å¾¯þîûyŸ>ú—ù{løDZÿA¬heAÆ&ˆÙ?c« ZÞ+Ú03377·0‚1iæ{ÍB1PÊ{}˜XÇ[š< I ܘC'ïÆÄà‹Ã¬¬Ä`kÒÀÄÚŠ€Ça¡“wc…@–Vˆ-ÑÞäH8™0°?G’ƒ=ѱ²„N ’·#²É|ØIdŠ Í•Î`0ÜLÐ Ý•æB!“ˆ6À Æ|ÓÒ,! v$
-•Ît÷`s<=½LOOÛÃI§RHvá%@ˆ™B$S,¶—Ï×/|fÒ|?_ž›Å ’‰@‰Ø%–Fk‰hL¶7 ‰CvîÚmÒìÚ" ø<o6“æDD,±KB²aŒˆ•™ÆâpùâPiX„,2*:Æ„‰ŽŠ”E„ICÅ|.‡E#ÛYÁ¼6bŒˆ ‰ÊäðüE’ðÈØ ŠÃG¾(MØّÊ„±‘á‘?ä’læC²hF„Ha°¹þb©,N¡<z,%-=#Ó„ÉHOK9vT©ˆ“IÅþ\6ƒB„!Y0Öˆ¨,o¾H%WªÒ³Oœ9›¯VŸ3YÔêü³gNd§«”ò(©ˆïÍ¢’<Æla‘€5bŽ!Ø’él^ D&OJÍÉ+¸T|µ´¬¼¼üg“4VVzµøRA^Nj’\& ä±éd[8€ßÁ‚¡azñÅáqÊÔ\uQiåo×nü^»^ÜZ;ëUÂï7®ýVYZ¤ÎMUÆ…‹ù^L06Ø×FÀbÅYÛ»¸ûìŽ<¨ÊVW\»Ußpï~ãƒõ£i-¬ß¿o¼¯¡þÖµŠbu¶ê`äî
-%чRN_©®oé
-°² ŠA0Ž–ˆ½ ËgÇžÿ±_!MîqÇ3ÿÌmêÔ隦¢k)2t‰4"fzЋ1ÊôàE"8ÿ$ˆˆˆè*ŒÔ‚"‚¢˜^E¢uˆ¨7^ˆftQ¡"&¦’§²Ãy~SãôiV 9OŸ÷åó<à³—ßßo¿=¥ýÁàÓÉ×o òöõäÓÁíWΟýóÔø¨0éÄ ý7w5÷GÝ/8<‚lšl°ìnÞî3±öàᔌÜÒs—Ûïÿ´ÈýöËçJs3Rtÿîp¿ÿ®¶ù‘={|¥²»‰3q@h¤ñHzN©órÛDÚ.;KsÒ#C6•»ß÷4~QÒ_õöQ¨C"I"%;)‘DE„¨âwÇ^¼ÿ/H’-‘Ó%ÎÖ‰´:KNo‰xÞÿþ—m‰˜Oü'‘æ-‘½þ »E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚QûFdì§DÆ~‘/"žHþþ"2ð[ˆÔµ¶õü´HO[kÝo%².‘ ‰tåóúo&âêx21»¸²öqý³ Á>^ÿ¸¶²8;ñd Ç%k½)®kqu÷ŽÏ,,¿ÿði]2ù¦õõOÞ//ÌŒöw»Zꊅˆ^~"~ê`½Áœ–]TÛ|§«oäåôüÒêÚ‡Ÿ<õñÃÚêÒüôË‘¾®;͵EÙifƒ>Xí'G‘ä´SŽšK·;{‡ŸOÍ-¾[}¿ö—§ÖÞ¯¾[œ›z>ÜÛyûRãTZ²,ETBÄz²°ºéfÇã¡g“3o—–WV=µ²¼´øffòÙÐ㎛MÕ…'­BD%C‘qIÇíU7î>}ñjfn~añ­§æçf^½|t÷FcUýxRÜÙ‰xû©4ºXÓ1Û™Šú«íݽCc/&§¦gf_{jvfzjòÅØPowûÕúŠ3¶c¦XFåç-3¥&<&Ñ’•Wæl¾uïaßÐè³ç/Ç'&=51þòù³Ñ¡¾‡÷n5;Ëò²,‰1á¥ÜD|•AaQ ©9Žê†k®Ž‡½ƒCÃ#£OƤžþ;qáÉèÈðÐ`ïÃ×µ†jGNFjBTXÒWV"^Þ¾þÚH£ÙjÏ/w^¼îº×ýèqo_ÿ€Ôà׉Ký}½ußs]¿è,Ï·[ÍÆHm ¿¯·—¼D!ú8“%3·°òÂÅk·Úïvtvu÷ôôÜǤkÝ]wÛo]»x¡²07ÓbŠÓ‡(d&"Z¥­5:>ÅjË+ªt64_½qóö—«ÍS.×Û7o\mnpVåÙ¬)ñÑÒÆ*Ž¬òqý*¥ec0M·ç–W;ë›.5·´z®¥ùRSc½³º¼0Ïž~ÔdRV_¾[«¿:XmL²¤ÛróeUÕ5µuRNL\¬­©®ª(säçÚÒ-IÆh]°Ú_V«{#Cª‰O:jÍ´çä)(t—x®¸ÈQXp&/Çži=š£#"§mĽ‘ˆ!Ñh#bŒ¦‹5#Ëf?y*ûôveŸ:i·eeX-)&cL„V³1"rËF’
-…ðKF† û6ÇÄÛÇÇ×××Ïb»6nKÏùYÈF^bN„Š·€ùAî§ö ù‚ìÛ0Ù`ù™6ŸÞë—þåyí¤½~Yƾí
-q
-/GS0 gs
-274 0 0 303 6622.1162109 7246.1401367 cm
-/Im0 Do
-Q
- endstream endobj 850 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 851 0 obj <</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 274>>/Filter/FlateDecode/Height 303/Intent/RelativeColorimetric/Length 4305/Name/X/Subtype/Image/Type/XObject/Width 274>>stream
-H‰ì—{L“g‡§=PxB)…VZº"é
-"iƒ¬šÐTjš a+‡Óa5®ƒ`R7 x"’Š8 Ø ŒCˈ8E$HäÔ!‹œ©ôû¾ç) è'‡%/þÑíú—·å¾¯þîûyŸ>ú—ù{løDZÿA¬heAÆ&ˆÙ?c« ZÞ+Ú03377·0‚1iæ{ÍB1PÊ{}˜XÇ[š< I ܘC'ïÆÄà‹Ã¬¬Ä`kÒÀÄÚŠ€Ça¡“wc…@–Vˆ-ÑÞäH8™0°?G’ƒ=ѱ²„N ’·#²É|ØIdŠ Í•Î`0ÜLÐ Ý•æB!“ˆ6À Æ|ÓÒ,! v$
-•Ît÷`s<=½LOOÛÃI§RHvá%@ˆ™B$S,¶—Ï×/|fÒ|?_ž›Å ’‰@‰Ø%–Fk‰hL¶7 ‰CvîÚmÒìÚ" ø<o6“æDD,±KB²aŒˆ•™ÆâpùâPiX„,2*:Æ„‰ŽŠ”E„ICÅ|.‡E#ÛYÁ¼6bŒˆ ‰ÊäðüE’ðÈØ ŠÃG¾(MØّÊ„±‘á‘?ä’læC²hF„Ha°¹þb©,N¡<z,%-=#Ó„ÉHOK9vT©ˆ“IÅþ\6ƒB„!Y0Öˆ¨,o¾H%WªÒ³Oœ9›¯VŸ3YÔêü³gNd§«”ò(©ˆïÍ¢’<Æla‘€5bŽ!Ø’él^ D&OJÍÉ+¸T|µ´¬¼¼üg“4VVzµøRA^Nj’\& ä±éd[8€ßÁ‚¡azñÅáqÊÔ\uQiåo×nü^»^ÜZ;ëUÂï7®ýVYZ¤ÎMUÆ…‹ù^L06Ø×FÀbÅYÛ»¸ûìŽ<¨ÊVW\»Ußpï~ãƒõ£i-¬ß¿o¼¯¡þÖµŠbu¶ê`äî
-%чRN_©®oé
-°² ŠA0Ž–ˆ½ ËgÇžÿ±_!MîqÇ3ÿÌmêÔ隦¢k)2t‰4"fzЋ1ÊôàE"8ÿ$ˆˆˆè*ŒÔ‚"‚¢˜^E¢uˆ¨7^ˆftQ¡"&¦’§²Ãy~SãôiV 9OŸ÷åó<à³—ßßo¿=¥ýÁàÓÉ×o òöõäÓÁíWΟýóÔø¨0éÄ ý7w5÷GÝ/8<‚lšl°ìnÞî3±öàᔌÜÒs—Ûïÿ´ÈýöËçJs3Rtÿîp¿ÿ®¶ù‘={|¥²»‰3q@h¤ñHzN©órÛDÚ.;KsÒ#C6•»ß÷4~QÒ_õöQ¨C"I"%;)‘DE„¨âwÇ^¼ÿ/H’-‘Ó%ÎÖ‰´:KNo‰xÞÿþ—m‰˜Oü'‘æ-‘½þ »E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚Q£FŒ"E0Š`Á(‚QûFdì§DÆ~‘/"žHþþ"2ð[ˆÔµ¶õü´HO[kÝo%².‘ ‰tåóúo&âêx21»¸²öqý³ Á>^ÿ¸¶²8;ñd Ç%k½)®kqu÷ŽÏ,,¿ÿði]2ù¦õõOÞ//ÌŒöw»Zꊅˆ^~"~ê`½Áœ–]TÛ|§«oäåôüÒêÚ‡Ÿ<õñÃÚêÒüôË‘¾®;͵EÙifƒ>Xí'G‘ä´SŽšK·;{‡ŸOÍ-¾[}¿ö—§ÖÞ¯¾[œ›z>ÜÛyûRãTZ²,ETBÄz²°ºéfÇã¡g“3o—–WV=µ²¼´øffòÙÐ㎛MÕ…'­BD%C‘qIÇíU7î>}ñjfn~añ­§æçf^½|t÷FcUýxRÜÙ‰xû©4ºXÓ1Û™Šú«íݽCc/&§¦gf_{jvfzjòÅØPowûÕúŠ3¶c¦XFåç-3¥&<&Ñ’•Wæl¾uïaßÐè³ç/Ç'&=51þòù³Ñ¡¾‡÷n5;Ëò²,‰1á¥ÜD|•AaQ ©9Žê†k®Ž‡½ƒCÃ#£OƤžþ;qáÉèÈðÐ`ïÃ×µ†jGNFjBTXÒWV"^Þ¾þÚH£ÙjÏ/w^¼îº×ýèqo_ÿ€Ôà׉Ký}½ußs]¿è,Ï·[ÍÆHm ¿¯·—¼D!ú8“%3·°òÂÅk·Úïvtvu÷ôôÜǤkÝ]wÛo]»x¡²07ÓbŠÓ‡(d&"Z¥­5:>ÅjË+ªt64_½qóö—«ÍS.×Û7o\mnpVåÙ¬)ñÑÒÆ*Ž¬òqý*¥ec0M·ç–W;ë›.5·´z®¥ùRSc½³º¼0Ïž~ÔdRV_¾[«¿:XmL²¤ÛróeUÕ5µuRNL\¬­©®ª(säçÚÒ-IÆh]°Ú_V«{#Cª‰O:jÍ´çä)(t—x®¸ÈQXp&/Çži=š£#"§mĽ‘ˆ!Ñh#bŒ¦‹5#Ëf?y*ûôveŸ:i·eeX-)&cL„V³1"rËF’
-…ðKF† û6ÇÄÛÇÇ×××Ïb»6nKÏùYÈF^bN„Š·€ùAî§ö ù‚ìÛ0Ù`ù™6ŸÞë—þåyí¤½~Yƾí
-H‰ì×± Ã0CAfÿ¥ÓDâa$¥ ÝM@¼Ž ü÷:ˆß”˜¤”˜¤´())-JŠA‹Ò¢´()-J‹Ò¢´(-J‹Ò¢´(-J‹Ò¢’ÍO--J‹ŠK´Ø"Æ’ˆñ-¶ˆ±äâé=Ϲ–88ÆMŠCkÜ—8¯Ç¯°¼
-H‰ì—ÛkY
-èA.W(Ê¢l¥1¡¨cŸR@
-`
-hco5j¥
-ôEOŸŸ¾qÿiùÍÊêÚ‡õjõä¨V×?¬­®¼)?½cúüéh_Àu˜Ò6¸PéL¬'x"ž)ÌÜy¼´\Y«~ü¼ñåOù²ñùcu­²¼ôøÎL!?ô°&ªÑ…™=Ò5prìbéÞ“ò»ÕõO_¿}ßÜÜü )@ ïß¾n|Z_}W~r¯tqìä@×ÖÜtñÏ.šg¤¹?ogóNm¾µ~~k5ßàÛoðæßìÇß ¸
-‰½=BÐÏ»,C‘X‹í#R/di+kwz:y¯ÏçGŸÏËwzœvÖJ
-lw-vdZ=E3ÖvÎfw8ˆ¢Ùm\»•¡)½–Ø«ž C‰¤2™é6`A˜¬6›( LàJ ¢u·
-X °ІZ£Õ‘¤`@˜$uZšC .€ pL  ÃU*µZM ˆ§RáMÀÒ¨b»2¹\¡P(k`ˆ²•Ä”C{K±S  øA%hS Ù
-Eì«¢ncKÈ v >¿òФÉ6 0
-q
-/GS0 gs
-268 0 0 44 6625.1162109 7475.1401367 cm
-/Im0 Do
-Q
- endstream endobj 856 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 857 0 obj <</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 268>>/Filter/FlateDecode/Height 44/Intent/RelativeColorimetric/Length 1219/Name/X/Subtype/Image/Type/XObject/Width 268>>stream
-H‰ì—ÛkY
-èA.W(Ê¢l¥1¡¨cŸR@
-`
-hco5j¥
-ôEOŸŸ¾qÿiùÍÊêÚ‡õjõä¨V×?¬­®¼)?½cúüéh_Àu˜Ò6¸PéL¬'x"ž)ÌÜy¼´\Y«~ü¼ñåOù²ñùcu­²¼ôøÎL!?ô°&ªÑ…™=Ò5prìbéÞ“ò»ÕõO_¿}ßÜÜü )@ ïß¾n|Z_}W~r¯tqìä@×ÖÜtñÏ.šg¤¹?ogóNm¾µ~~k5ßàÛoðæßìÇß ¸
-‰½=BÐÏ»,C‘X‹í#R/di+kwz:y¯ÏçGŸÏËwzœvÖJ
-lw-vdZ=E3ÖvÎfw8ˆ¢Ùm\»•¡)½–Ø«ž C‰¤2™é6`A˜¬6›( LàJ ¢u·
-X °ІZ£Õ‘¤`@˜$uZšC .€ pL  ÃU*µZM ˆ§RáMÀÒ¨b»2¹\¡P(k`ˆ²•Ä”C{K±S  øA%hS Ù
-Eì«¢ncKÈ v >¿òФÉ6 0
-H‰ìÑ ŽÝF
-ÅãHò?òÏ'ôÀ¿ÀÇuÙâV‡šÌ7¢$sL Q’KBT䆵%*rÁžùlk‰Š¼ƒ¤(È3`Š‚|E—ø…¾Á€nð}‹~ÿý‡~þú}úô5»ÑBÿ³ýõ)ôI›Ð7ŸFµ}ñ%ôY‹Ñ÷^F¶}í-ôi‹Ð·ÞF·
-ú«å胯¡ßZ‹~÷:ú±…èko¡O[„¾õ.ú·%èSÐ×MG:†~o.úÍqôƒÑWNAŸ8 }ã,ô3ÐÎD9Œ>p2úÎ!ôy ЗÞF·}ë-ôi Ñ×^F¶}ï%ôYПFµ }ó)ôIÑWBÿ³}øô5úôè[0ôñèG`ôý_ÐgÐ ~¡oð K”â›RÈ”B¦6•°©„M%t*áSŸ:(!ù»ü+À
-H‰ìÖy<ÕùÇñ™{çÞKeß÷õXN–ìǾeÉžÙ²g:gR2¢D#Ë„ìK­!†B²"IÉ’%¡FiêîËçû;ŽsÐ}Ì<îãÎu¯üüÎ?Îïùx>ùd«­¶Ú@}úS­÷¸ ZýÈó±¶\~ñÖüv©ÏVF¹½Ff½¿ÀF‰V
-ð;è÷KýåWô•†Ve½¿Ê¯;ªÃ’™
-•dKäç¶$AA¿ mÀ€ pqóðòòñ 
- ‹P‚k!!A~>^^n.$Ã. Âˆ¶&Ë$["?£O—)h 0`@daQ1 Iœ”´Œ¬,¿‹/++#-…“”&à @!›`$ppm‰üTT‰Ï@b‚™…“‹‡_PXDL\»ääv+)«ª©kššZ”45  u5Ue¥Ý
-òr»
-4
-L‚u'—STRQ'héꛘš[XYÛî³w8àètÈÙÅÕÍÍÍ?]]œ99p°ßgkmeanjbl¨¯«EPWQR”ÃKã€D
-!…žˆ<s..>1éBJjZzFfVvvv
-~fef¤§¥¦\HJŒ;}:2"üD()$ÈßÇÓÝÙq¿­•ùC]Mu äãᄉ,ƒ —ú¦fþ—¥Rpñð ‹IÊÈ)ªhhë›YÚ"/¿À`RèɈ¨èظ„¤äÔ´Œ¬œÜü‚Â+ÅWKJËÊ+*®Qª¨(/+-¹Z|¥° ?7'+#-59)!.6:*âd()8ÀÏËÝù ½…©‘žˆì’–âçád_ ²ÞæùˆZIÁ+ ,Ž“•WRÓÒ ;'7O߀`ÒñðÈèØó‰É3²s/].ºZRV~­²ªº¦¶îf}Cã­¦¦Û”ššn56Ô߬«­©®ª¼V^Vrµèò¥Ü쌋ɉçc£#Ó‚ý}<\a$¦°5%yYœ¸°
-ò²ÓS’ÎÇž9v,$Ð×Dlöšhk(+à¥YðRßF dSX´³Øθ“…“£PT!è™Yí;èêéD<þÕé˜óI
-sëýNîÞ!¡áQ1ñß\ÌÌ+,.©¨ª©khjikïìîé ~:òltlüùÄääÔôôô Jp=599ñ||lôÙÈ €KoOwg{[KS}] äd¤$Åȱo7'{k3#]Xˆ,NTX0:ê@ÖûaýÂ-[À³€JP'« ¬Q:ìH<q*:.ébVÞå«å•ÕuõM-w:ºÀ¡`hxdt ¦àùϼœ›Ÿõêõë×?P‚ëW¯æççfg_΀ÍÀŒŽ  ô÷õÞïê¸Ór«¾öúµÒâ‚ÜŒ”į£#N}Üí·†…¨+ÉÉHˆ  Ȧð ZÀÅ„ÍBBZNI]ÇÐ £"…EÆÄ'§ç‚Ä÷7›ÛÄ£þApŸ˜œ~1ƒÀó_XxóæíÛÅÅwïÞýH ~[\|ûöÍ›…°Á`f^LONŒƒÉ`ÿ£Þû÷Ú[o7ÔU"9éâc"ÃHA¾îNöV¦ZjŠx)1!4ŒKÖFöXeÁ‰fWTÕ20µ²wŠ/OFÅ&¤dæ_)ý®úFcóÎî}bì98¼œ…D€¼ÿáÇ?B¢ Ý€ûïßc4fTæf_¾˜š|$ötu´Èõk%Wò2Sb£ÂH>nŽv{ôÊò0~nVìÀÚÈ+-Ø8y…Ĥä”4t-ìÝ|I@‘˜šu©¸¬²¶þv[Gwï£'C°179,¾Ã 0€?CÁú+mä[èCŒsd2?$ãÏžö÷=èjomªÿ¾²¬èRVjBld1ÀËÅÁÆÌP[m7Þ ¼œÛƒÖbXð ‰KË«h˜Z;¸xÃ"ÉåUuÍwïõôL –€l€=ø¿‘ûûÊ–îRh  M~x5?;3=9>
-"{îÝmn¨­*/¾”™s*4Øï°Ó>Ë=zJrp`ñr²mXU\|B2
-ªÚ†{íÝý‚C#ÎƧ Šëu-í]½žŽ" 8šÞ ²™VàV†‚‚™üøVòš,òlxàL¤¥±®ª¬(?ãBÜ™ð/½]ؘhªÈK‹ ñ­òXïgø_kåE¶PÓ1¶°?ä@<y&îBF~QY¢èî}<8261=3 oÉ ÃÚÇÿOrÿÆ…ŒB&A"‹d‘©‰±§ƒ{»¤¶²ôrnZRläñ¿ÃŽv{´Ud$à…Nã±Q憱ÊB,,÷»x‹ˆI¸˜s¹´ò_Ü—ù?ÕùÇhnsgJ)eI¨„
-a•¤ÅR
-©P²MÔ¡²…\#ʈk%)©#eKٳɚcßÉ> sïÜûÃÜ÷络ãTó˜¹wÊ9ÇçOø<¯çëõ~Î@ÑÛ?8Œeø âC ¿ýÑû
-‰…ìïél •¥ٴ丘ۡ¾ž.glÌŒô´56c<°| }5Gt…K
-ÝXw,tô-)ŽnÞþÁÑqI´ì‚ÒJ„¢ ü4¡€L`‘øÄBø},8‚dzdldp €ÔWW¿ÈLM Þ½ù“Ÿ×y‡“Æú¬ÏÑý1't…Kjþ×pë¡MK°00±´ur¿ùàIJF^qEu=JÅÀ†³d'ñÿsø"$‘I $¤µ±®ª¼0'=9î~D°¿·›#ÅÒX_ñD<xÑ=Èýº"$,xx IÔ gwŸÀ°(jBZVAÙ«ÚÆÖŽž~HÅ;
-Åçñ!2# Ã}ÝíÍoj*J^d<‹pÉÝÉÖÒˇ´ä*a>^Tó¸š)©oyxù„WIJ£îÖG,<|ÃïÆ&ÑrŠ^V74µw÷¡®ÀP ?13ñÙ@|’ÈèðÛÞÎVz]UYAvZ5*,Ðñ
-ÃO_+n,¨ôVqn:ƒ‡¹Ñ~ÍmÊ×­Y)HêŠ ãcþ×píñ @yoTVÛ½ÿ°9ÅÉ9êÆ¢ŽÞÖÝÇb–Q°AB¤¯«^[Y’Gðps<avHw×VE¨\W\òÄ€%µ$%£¸e§î!³ŽnX_$?Ï+yUGoÇX X`µ=»(˜@X2:ÔßÝF¯ÃyD…x_°·65Ü£¡ª°a-ÒÏ®‹3¼|‚" )U ¦Öö¼¢ / ¨î¡1¤¨™±˜UAÁx´<¨wBý½\No¢¯¥¾iãºÕ+ø˜ñ`÷/ÿÉGN)?,)Yåmšz&V§\¼üCïPYY E1bÁ,@P@°Æ†0Ź´Ä‡·ƒý<ÏÚZÞ‡të
-µ9.±sJ¡`lPØ ’2§8{úß~˜HË-®DŽÆY0bÁ6,<þƒ„Eò¨­,ÎI‹¹uÝ×ÝÑÆÔPGÖÕîŠa)¸1 1P0Ôµ ŽZ;¸ù^¿Ÿ–S ;Š™‹³=ÌǃG½¦¢0ëÙã{áÞ® +=M5%<h\qÜRX0ð`ì3²´sùájø½¸gY…5°£>dÁnäc‹àÑßÝÚX]žŸ‘{'äGOgŠù¡½;T±x,…Ûƒ Êœ°Ô·<¼Ë…Ä°`0=áèq%8’šœ‘_^ý¦vg²À Ñ¡¾®–†ªÒ<ZèÊÇÍÞúˆ>Q¡e¼œo+ÒR -å_¹f½<4†‘å)WïÀ›÷ãi¹¥U -]}C£œË½™<~ìíh®«,Ê]…]õ:g ñÐP‘[·Z˜É"·Õ K Š®•VRÓ28jãxÑ/$êQJVQe]SGï ÜœÌ=_¦&ÆGÞö´Ók^æg$Qo]vw8~Do÷Öï6ˆ‹ò-ÆmÅ¡Ûj†¥V¬’Ú¨¢±÷…
-FLBú‹òêƶž·#ãp_p4 ôèsØ»“ãÃý]­ U%¹iO¢Ã!³ƒ:êʲ’b¤­8³<X-¥°e·Þk+(Ùůê[ºú‡Ç&&áÖC›–“Y ‡ñ@÷àôä»ÑÁ¾Ž¦ºŠÂ̧ÔÈ _·3ßïÛ¹Y³§n+¬2þ6ÿ›ÈR2JÛ´ Í(ç~ÀƒQCoI½›œþç¯(œÎâ7¦®~Eõºjk|]–GƒxøÿÃù„©–š¢´¸ÚVœˆUvññ.KÉ©ìÐ5²:}Á7(2£¡®½ñ÷S^¬of}ÀÚí„6/ÌL¦F\¿äjgqhÏöM²bBèä¸ò`TÆ~aÂR6NžþaÑñ4 hï±w¸¤¸…zx}º‚6o¬.ÍM}|7ÄÏÃá8ØJUNjÕŠåœWÌÊ€‹OZYÊÖÅûZÄÃäÌ¢Êú–î´¤¸ER3£>¦°6o®«(xžs3ÀëìISÍ­ßÁ%ˆÊƒ£.V²›4ö¶<ív98*.5·´º±!)vðÿö]¡u…âÑö¦ª$;%6ò'W;óƒ:Û”¤×rZyà0àÊX&•¡ºs¿‰µãEÿðè„ôü—uÍ0¥ÆQ0¸KRÌÇΦšò´'wC®¸Û[éj@yˆ¢ËƒSpcj!\«¡24 LOžý!ðÖÃä,¨o¸1FÉ`p# ôÈxLO¸ên­¯,ÌHŒ¹qÕÓÉæÈ~¬<„–-^È8Иš‡©åÂk6(ªišÛ÷ ºó(5·¬šÞÑ7÷4JŠùfÄcl¨·½ñuIöSjÄ5ïs”cš[֭ƺœp0ИZ).­¤¾ç°Õw¿Ð{ñ¸¥°úƦ·D<ÐíÛ*/-.*È÷ü)sCm5Å k„ñiÅfÄ™`ˆ¬•QÖÐ5>îèé#&)³Y
-¾)ÒRìþпöˆxàeÞÝR_Qêç~Æêðu%iq4­Øƒ–­„¬ÊŽ}&6Î^Ô§Ù%¯1K1ê›Ýßù—°Õ`/l«¢Ì$TŽÇt·+Ëp
-oÆÿo³ýT¸CcvD¸;Yi«Ê‰ ò°3;h~«@4~ùÛL! yµk­¶{LÍ.¤?3¾hÇawG CmY1n`Í{.4cÆLfvAq9ÕåF›¶yžHÉmj¼?ú›¾—Ðð³#?+)ò°›ãÆ5ZÊ2s¸Ø˜h=;°:ÅÌÎ- &«¢mhéä}ö×`F ý™ñ©eù™I‘þ®[Ì –.”ÍÉÊÄ@StàCƒ‹®Œ²Ö G÷€¨äË`ôµtÒ¡_°ãL„ß>‡ «4•æ‰ð‘fÍÜÀº-çlQé…K Ì·ºúG&eݸÍè¥G3&Øñª®¢,/#1ÌwÏæõúK¥àì %ÉÁA·å›3OIc•™Ã>¿ˆ3™ùwžÔ7Ó«£vÀøª®üöõK Ç}\ìþ¡»HAô\’Ý©™Ì ÛJþ}±þúÍ{|Ã3òÊ*ê›;z†~§K3ÆÛÑÞT[^zõbüQï]6ëV€Ù!Š­H>JpAqyu];Ÿc é×oWÔ½Bfü›>ͳ£ØQó¨8÷ü©#û·[i«ÈÌå‡ÅŠ6è€Ð€uJu¹±õNïи WK×6µ¿xO¿fŒîŽ÷ý=¯_V?¼u%íd‡“Åše ç‹€bÅHt`ÐÀ®eh¹Í+8ö\Nñ£`F?]›ÙñÙÑöâÙý›Ù)чݶlX¥¡$5‡—ƒ…èÀ ÁÂÁ ®¹Ú|«{à‰ÔߊV¿|ÝC2ƒnÝÀí|ÛÝÚøô^AVRÄ¡½›MõC’Ó$hð!‚Ûïõ‹L¾\x¿ª± ™ñáO:ŽÆ|ìëjy^y'/ãôqg[Hr1Z c rj:&¶.Ã3óï>mhí~;8L÷fŒÚñ®¯³¹¾âöµ‹q¡vXi)K‹Ò
-‹õÍ\G§ ‚ƒnK˜Aivô´5V’Ÿ ÷Ýmg¢£&+ÆÏoÕô©
-Ç´iÓáââ“аÛí~&«à^Õ‹±:õ£â§²«†Ê²¼K G½1tˆðÍb™9eèÀïÔ,>ie-
-‰»x½ Aƒ øçv|
-ôª¼ô¸#žŽæ++Lä㦆[Pì…k`÷>ïaÆ_«¹°W ˜í¿Çn93Õ ðµ6.¾g³o=ªk&îÔ·…ߪÞ° ²yo·4ÐP”äf£6ÂY¹Ä!¼ÀÔÈ¿Wõ¢èSd¿U=m •e×ÎÇë¨ÉŠÎ¦ºå"„3sð‰H«`S£âykwÿ{¢O}[¤ Ø\û¨(;9Üg—•áR%)!n6&ªÂ·[nAIEMKá`jô wêÛÂ7à›v0:òÒO{lY¯§.;—ÚpŒ¶[Y5]S{×Àˆp|jf|[Èû»È‹¯¤DúºX-[ %ÄCU8P4˜Ø¸a»5´Úåžœ]„þ!üG¿õç×ÈŸÝ¿‘âé¸A‘•á Ec®¬ºžÙàSéyw1„wŠ<a ïïi}^Qš“å·Ûf­Ö‚yÂÔ„~ –Y»øF¦\).¯oÁN˜A–0÷u¾ªyP™ê……ƒŸŠpŒFCn‘þGÏøŒ÷«›:ˆhP ±–û¤47-Úo·íZ­… 씆ƒ a©`øíö‹JÍ)í¶§Ÿ@8Â@þ†£0ótè~'ó•T…cb4¼B2 Ô¼êì$N0¼yÝXy„í15ᘠÛÝ~Ñi¹¥OˆvK©>~$…ãaaÖé£T†ãÓhœÎ,„Ñ
-4>WÏ…ƒ¢Z…¢Á·F Û•¯ß §Pä(µ0$rP¶9`4˜ÑÖø,„i"9üöØR¾9€(’JËŒl\EÑ Zx8ºðpx9¡ÍAI8¦M›þ 3Ÿ¨¬šžÙVϼPÁhfPªOÂ7Ç)!n6Fr#+— „âRCkgßH°5@¡Â£ñ£_÷¿¦Qr`›ÃËqƒ¾º,3ÃŒéäœ*Èp&v^iUS#ñ7îÑ ZÂuÈÅÆh™’$
-Y§
-«·œâ
-›vŒH¹RR· ª4.™ !ž[ÍôÔdDù8˜Èâ8Z~ì<Â󕗛ػJÏ¿WÝÔÙ7HDƒ:ááhkxRš“éële¨©(!ÀÅJÇÃg0€å'&¿x•Åö…%g—?oí!¢A•ÆÂqÿÆ¥¸`wSé9¼ìd•\R½ËÏØno@Ì…ëw«^vÀh[ƒ:aáèik¨(¹r6Üg§¥Áq~NrJ.b8ª·êzœöM¼\ô¨®¥»hä ê„Â1Ø×Ùôì^~zl ëf“åÊp’ÁqÄpV. EMCk—CÑiWËž¾hï}G0œzpŒ ô´Ö—ßÊN:î½mãJ´
-0*ÄðÕ;|ÂÏ^)yÒðú I œª‘¡þî–ºG7³N‡zn]¯«*#BÇá¡bdž/ ö"ª·Äò›œÇßõ¢JîÙ/wªÖ=Žßº¥”"sf™2EdÈ”Sˆ&D E©T
-©ä4g*Jƒ
-…ŠÉX¦Ê’ eCæˆDGçÞûœw­µ÷fï6znkÓ½Ïûý Öz>Ïûû|¿û·k|ÇQ‡Ïcá”RÑ·tò OÌ)«Gê-|?Äãc#ƒ½hɽåsx\Q‚Ÿ}:#cc.+ˆœ¦‰ px4p8¬·?òl(ÏMŽß <.ÄÅ<>Õ©" ~‰:<|ÃKjÛHõÒøïC*¹Ý-UEéÀãmMµäEǧ<U”±!³ÒÐjﯗ"“¡Ãñ ZrIó?²s“®’¤
-:—`“=UH«Úe®«´”Ÿ}!#}q ýv÷’e&6.g®?H/ªF¦<Tø=Uh«zñä~Ð gkÕÒB\“‰í· ØxÅ´×ow÷ ‰{^R×~ÁNUokMqæo7δ]«)'²xÑ$â õ[¥JzæNžI¹å°Qáò
-Ӣю«&#ÌÅBW$mð‰+®ÞààáŸ]ÚÐÑ? ´NANÕ觾vÐqcožwµ3Õ’YÌÊDOdmH*ë[î>‰öÛ°ßâ˜ñŽ›pÛÿˆãFEq>úâ iCXFÍhËþSW£ÓŠª`¿Å7¤ŽÛÕü&?åî%¯=–ú*“‰ƒ¬ y-S;×ó7cŸ¾ªmïûµgÐŽ;ØÛZU”}íôþ­“Šc\:øßNÈ.ƒÚÀ;XÇâ(yöð–7Iô•6¼.ÝÚh†ÚÀ9ãâÈ™Z”µA¥Ñ1x¨p IÝ$qL¾8¨ÖE_ 6p &°8Š(‹CˆŽ8Pm0±ñŠ)ÀµA`(â@Ç![SM9zâ
-÷ ¥êó@gcyNâr©b¥j&Eã R‚Ë! ¥b`å|âJT¨T½C£°RTãh©JŽðÜeþË
- >6ªR…ÜyÌœBÒjF[œ¾“ñV*¢4Ž”ª–Ê‚'÷.{í±ÔW)Uh
-®†É¶ƒçnÄ>}U×+1Ah€RÕZU”uõ¤³µ¡ª” ¨¸J¥àj™Ú»ù„Ä=]ÿ¾Ò "hÅýÔ×VSœsý Vq9©*.MÁMÈ.k쀕ŠÐTÜm&²ÂTwÆŒ™³ç2‚« ³Ñ)¸¹ àBÄ¥Aª¸!>nöfZrÔ—47ø$Vüb¾Ë302%ÿMs÷GXp ¥â–å$Üöóر^{¹(+- 07”õ,÷x]¾÷¤ ²¥Ì X©FãcWSEnRøÅ£ŽuÅyÙ& „27$ÇçF+œDÝ`p¤Dz:™ëÒ @ƒ‘™SPZÕÐzß©kÒ_V·õ¹APh‡28&Ò ?dü ŽÉ,®mïƒsƒ PGaZÔ•ÎV*’æJêÆ6ÏÂñGlÐÁ1ÔÛVý2ýÁµSûÀü“äDæ…Æ,@ƒ[x™ÆZÛCço=|ÇÁG_{mq&Ýù‡Ð˜Ï½d™¦©«wHÜóÒúŽþa87ˆ yþÕ¾z{㬋±º¬59ó-‘Ó2³w÷ Ï.mè€ã¨`óïÃûº’gož;´ÍDCV˜‹å¢ò«Öí8ì–SÖØ iŒFGýëçq·¼]m×j,[ hÌš@ƒ‰•Gt¹özÿ;‰¹åM]QAç ÑPšõ(ÄÛÕÎT¡1ŸLcFƒWLaõÇ£“ò*šº>ŽŒýçAi|Æh„ú¸Ù›jÊQÓøçì¹Ll¼âŠ:›v ˆHÎÓÜ iŒÆ@gCYv|¨¯»½™–œ-l|+tÍ<#S^¼méû¤AHèÑX4 vþ¥Jz»½.ß{RPÙÒ38
-iF@#Ì×};Bc15 Æ…ìü’Êú›÷þt?µ°ê]ÏÐèWHƒ˜| )+ç“W£ÓŠª[{°R’q a¾‡éÓà”R]c½ÿÔµé/kÚú Â2- FfNAé•F[œ¾“Q h|úi”éiÌcæ’Q3²q9ü[æ«ÚvHƒ¸i”çÐ¥1¡Á%$£nlãröFìS@ã¤AX¾†¬ºñ¶ƒ(:HƒÀ@?Sà¥ú™2 hñ¿4ÓÓ€ ÷¯ \?S¾‡‡€”Š•óÉ«ÑiEÕ­½aùìü’Êú›÷þt?µ°ê]BãߧæëNÆÜìüK•ô,v{]¾÷¤ ²¥gpt Ò &ßCƒOb…®¹“g`dÊ‹·-݃#A!Ñh
-õq³7Õ”[2Æ?0<¢Ëµ×;xøßIÌ-oêøü; qà”Æ0F#ÄÛÕÎôOöËÄ«æuãëh‰&Íš'¥RÊPi>IRH¨(!ŠJ!q"RB(¤È,%24i"͉æQ»yžN¹¹®ußß°w»Q§~{Ýu×zŸÿà]Ïz¿Ÿç£©0¦yóH*j›í<ì“ZPÕÛ W~ýúùã;h£"ï}t¨ï![c ¤ ¦ÑmH,Ñ2µs?wëyJ~e#lƒ^ÁÚèh(ÏMzâãj³NC^Œ´Á0Üã<v>qM“í‡|oF¿Ï¯hìì‡mÐ'h}õe9‰Q7Î8o3R—åeÙ;Ÿ˜‚†±­ëÙЧIy ýßü„mÐ!XíõeÙ ‘×OÜj¨¶X”gD   ^1yu£m.gnD%æ”×wôÁ6è“_ÿùùc°·\’q픓•ªœ+‰‘áš6ØxD«n=x:82!»¬¾¶A§€6†@uÅŸÞ…y9Z®Q‘æf%ͦ¶ñ#‰GDNÕÀÊéÔµˆ¸¬r[ïàŸP8褞ÖÚ¢Œ7¯œØ·yµò"!.–¹´mÌ&±r ˨¬±tô
-
-÷©¸´1Û G€n t·Ô|ù{ÿ’ç ½åÒ‚œ£Ú˜ËÂ%´Hyõæ}'®<|“QTÛÚ3
-ªš`ô EþÊs£B|\mÖiÈ‹ñ±31ŽlcÞüK´LìÜünF¿‡úG· ºÑ×N.ÍŽWþÐ60ýÓXgãâõž¡êFæ»ð«'üÉŠp#òGÓ¦rˆþy_ŒÏ*%·Cá KFë†>¢4ò‡¶
-‡ì°p|ƒÂAŸ`ºÑ tãÕ½
-pÐèU8¤–éšïF&n:˜¸ÝpTÑ!  êÀ½uÎ}‡©¶â¨;fâ>O)¨lìì‡mŸQ×ÖXCA|äÀ¥™¸š&Ûù†>MÊ-o€£ŠAno¹$+>âº76pyG \š‰«n´ÍùtpdB6Uô
-ñžÖoEŸÞ>
-ôÚo¹FEV˜™T#Ú``$±ñˆÈªX9žÄFU+À8lƒè G'UìýKž{,ôV€;bRa'±‚Q¥LU5-ÝpTtRõw6U¤ÆܦL*Næ±mPF•½Ç…;/Ò>W5uÁQExpˆ7”ç½¾éçfgª5vR *Em³îçn=Kίhèì‡':ÄÛÉ¥Ùñ‘Ágœ·©Ë‹žTø¨bçWÐXgãâs#*1§¬¾UðTˆ‡t´4P•á5©0Œ3±ñˆÊ­\kítêêãw™Åum½ƒãÄæñêÂôW÷
-;T<“ªáS…¬ªÝn?OΫ
- 8˜'?TÈç`@•C\^ÝÐÚÑë
-ä8A¾…á·/x8X¬V–Áec²6h”cÕFû£þ€ã9e䶞ÈñéÌ[ŒáEŸ†»n7ÕFeƒô›¯A«ÇÃÇkš»àç˜AC5Ø×ÑPYúò`¸%`¸* “ ªrˆÊ©"¿x'&%¯¼¹ÿëgý·=­ßJ²¢B}Ý×K qÿ–áøç
-F[œ |›þ¹ª±£r|Z¡˜¹,÷ý³° Ç6é«È‰ò²OáØ©gbã‘QÖÃFnbvi4ÀifØü2Þ>¾vÚÅÌ[)A®)1œò9H,œ’ŠZÆ6O=zý¡°ª±~Žéû½àkä%Çܹè¹wËlÞNíP¡ÇG®Šþ&óaÏ’ràç˜fð¯Ñ\ó5ã]D°«™Î2i!.Ö©1ÿèÈ]¨¤mbëì}5üÍÇ/ÕMøç€uü“P¿F9øwŽóS“_0JóvøTÍù/ûeâeþ†ñ9£3š¨Hd+¡”%C
-cI’’)$5ÑB"ƒ±¥(k%$%dm…”„""ÊÙ²Ôk_Rü~ßïó<ï"Mñòæïsÿ ŸsÝŸëbbåä•Ußeáàs9é>1øæ˜Ø}£û¶+ƒh°³Þb¯
-”\ln3:쌇ƒÊ#*,ô4`4Æ·ü(Ã1,@~1¹1áÀE>#m R4T¤W.Ñ`w4°p̙Ǿ†Ã ¬U„¾¡¸È'p0ÃèÖ
-‰ÆzqÁ Fcl8°ZÕ=0Œ·Üñß)`k£Á1±h|`Ž °9Jk[½x8&p
-ïjo¨(¼—vÆÁb—º•Ñ ‡c©¨ŒšŽ©­×ÅwóËêð–;ÞCÞû®¹¦$çvTà?6FZJ«‘hü2áh`áøuÞBžå
-šÎF$ƒ ØÐÞ=€ÿªqò§@»m«ñ$#>ÔÛÞ\w£¬?'+UÑÀÂÁIJˆOxêŽ}G݃£S–‹ŠÿUß9Tápøg§\õw>d¸UQRˆwá<ª¢A Ç\6nAñõ»-΄%d "ÇÕ÷Ž¢Ýæß½èekª£&#º”êh á`dbáX²R
-L@×€k)‘ ã¿êÛG¡ð§YÉghÊK,ç¡:H8@8p ˆÉnÜifw*ŠüÕBï>:¾}¨Â{Þ…?í?•5Â|‹X˜¨ú«çÌg‡-wËLä íptà¿ê‡ý©ÎÖºr¨ðÓ',Àðäf›û+µÑø‰¢åŠ¬EE~=õQIM3þ«¾}ØŸêno¬,zðo¤¿‹õ^-%©K8X˜©Ž©å²ñ,ƒ"·8q:4>ãq96:ð^õ‡À
- 6:„×€_eíìw%9«àE}+®Ž±G*·-¯ÊòÓãCÏÀ>ÿÔ$§Æ(ÈèàX%»QÇô˜{PÔ­l´æâ«cô‘¤ÊmAVò?k#m⟚ÔÔ ÀAúUò›w[ØŸ
-‰MËEÔÑ«ƒòFÐ¥ÊmCeQö­¨ ÷c¦:‘>ÿÃTÀ ý*Ø«”´ :ù†ƒš‹¨_”‡ü}?"Ü´Øo{ ½ÍòSÔ§È8^Å% *³a‡ÉaW
-uà8ˆ‡Hãý
-…qÀ_7à2ñuêºf¶ÁP¯ÑÕã@38\ˆ4ü]›ìØ #*À…쾩ùS ôW±p,Yj®žå ï‹1ižU7’LŽã@ >Ô×Ùöº¢(;%*ÈÔÛuâËxÙçÏ™º?…ဿj¨¹ÒÊ@'}ÂâÓóž×4¿#ðçÏ8Ìà}]íUÏrRc.žÒÐT\±„ƒeJvß(?3Ìbœk®P‡±‹_DRæ“rÄä°XÑ;bðšÒ¼»q¡>'Qi,å\
-ñùZ§êÊÁ#þ9º_g#* ´ÜN- JuH)m5°rð‰IÍ)®l
-’+ø±BƒÓF”8€Éa±RÖÚcuâÔ…è[ÙO_ÖÓ%Ž10"ÝmÍwk*J /uŠf§À1 ˜|>; ˜Œª¶áAÇÓ!1·UÔ·èÇh3“#=ì,ô¶(I‹ðsÃ:ECiqü2›™…ƒw™¸ì†íF<s)6-§¸â5ÝáÀvƽä«AÇ-ô·*¯àuŠ–'á@LÎÌ
-{®œÚcë¿}Co 8ÈÏŠx|†çqK-åµ Û²´48‰V¬@Ï’X§®³¬À°¸;
-ñá=€ÑÖX væŒmª² ƃ<;ró‹H#8œ}/ÅÜzPP^ÛÔÞ9ÃqŒ`0z m Õ¥FÐ0~@·MΈC
-Âè!´6T•äg&EN/ b±ÂpH)jìÜgítúâõ³?¯nl#ôÎÜáAœýÝïÞ¼®,ÉËHŠ ô8>­0H8˜á³–RؤcrÈÑûüµ›™ù¥U ­„ž¤éμx`þêë~ÛR_Qœ›žxÀ°¤ø4ÀøÇjyõ?Œ:x_MÊÈ{Vù3PDe€™ÑÑ\÷²(çNBD€»…–Ê´Âõ¬–®”\¯¶}ï{ÏÀÈÄôGÅ`x«ÕÌáARh¶Mµå…ÓâÂýÜl-ô§6.¾r´ ­Ž{DÄßÉ)ø•Ç̉„¿T¡­±¦¬àÁíØ°s®ÇÌõ¶*O; 2Žylœ|B¿Éªn3°°só ¿‡G pyÏŒúV#Äɇø»ªôqVJô%_ç#f»·(¯›v$ÿç¾N£©Þ÷0€×:·sn¥r+!2C.!T†TÊÐ5$CÒ€B†,)Ç¡„"©Èt3Ê1CÈVfÛLÚd.¥i{¬ûýý‡=œÞœu‡ÚÛÿ¥áÕg=Ïóý-[¹šoƒ¸œÊ.ýÃÇÜü#âÓ”×·t ŽL¼†¶Z(·ÙRïggÆiýÏŸç$߸ìëbk¦§¥,»Ið›cK~
-?méA·§9 ì–§ P›jËòÒâ¯\ô8ni¤½]AJd=Âøþ/߃α”‹›G@TZq‡î¡£Nç.]½•–WZ m·¼9:øb·j©˜ŒØ0¿³‡õw«ÉK
-¯_»ŠM0ŒÇ’ViZ;{!,ö^ö£*
-ÜV£“¯ñà<F0ÞÎLŒ`-Uy7:ø¼³­Ù*rùÖ¬ZþW6ÁXDžVK¹V­å–ü»ê®ƒæv.烣ïÜϯ¨o¦ÐÆáéǃãêjž8¥°`Œ½ìï„–zžù‹çIkã}šÊpÙò®^ÉNÌï@¸t7oÕÜob}Ò3àj|jnI Œ9Ϋ+¦Å@Áèi£T=ÊIŽ ÷ww´4ÒÙ¡(SËØ
-ƒé—î&Eu]£#Žn~¡7’2јw¡x ãŠÃê
-³øX¼ÇƒAmª«È¿-åëjÿ“þîm
-?Šð
-ƒñð€ÓJ@Æc·¾¹³Oൄô¼’šg(p\Í}$ëŠ<æéëM£±ºø×Ôø«ž'mLô´Ðdð¯%.[¶Â ?<`Ë×®–SÑÚolåäá{/«ð1¬Wðöàœº"- ðÆ€Sª#3ñz¨Ÿ›£å!]u%Y± l¶ßLyZ¡ñ“QÜ¡chaïz>Gc[¼=¦gñºbú``%Eèn¥ `ÜŠ ôv¶5×ß³MAJTp»í7óÇu‚¢Ròj»ôL­Oœ»›”UPQßÔÙ7üjr†éºb_|0°K
-+©ÞögµåùŒ îNVÆû¡¥à•ÁÃ͆“ÁøèãÁÍÿQb³²†®‘…´ÜâjJk×
-+©’¼Þ‰¹Œ‚a¢·SUžÝ[Šøm·ÏzaI¹­X<\|#ãSrŠ×7uô¾k—m=˜-ÞLÓ†z°’ʼàålÁPWÞ ·›·ùm…âÁñØ‚âaíä~árÌô¼âª†fjß‹Q6õ˜g±˜yÑÛÙTÿ¸('õÖµ`ß³ŽGõvªÁ| ñ²}K‘ÞVø˜ àñ0<lçì›”ñ°´†ÒBíÿƒ@p
-†Åè‹>jsCU1*©P“?›ëk«+m–€ù†ÑRlŽAo«–ÁÓƒo¬WÆGÏøEÞLÎÊ/«¡´" º;„ŒÜQ¤E?µ…RSú0#)6"ÀÛÅþÈ¡}Z°›„ˆùfû–"?¼­ÈxÀq¥®­onsÂÝ/$êVJvAù“ÆÖ.F>>ÿöÍB¯(¸içH‹VJMY~VòÍÈ ß³Ç­LìÞ®(+¾‚æ›ZŠüXâ!´IJ^Uk¯‘…ÝiOÿÐè„TVx|¦ä[€ÌÓ+êóG°x={,ž”d§ÜŠ
-ñs‡’2ÐÑØ*÷£¨ /S08cs<à¸Ú(.«¸ êÊÒÁÅëRXÌíÔœBä}52ï¹Áƒ È×a¢€Šz7‹Yô© Ñ¡þž§í,PI)H‹mà‡ÅÀ‚Á)-E~ŒxÀÛCPDRNyÇžƒ&G]½‘GZäö£ó˜}‡
-‹¯2O_ <of&ÇhC}°˜Å혰K^.–Æz¨¤$„Ö­æÄ``ÆWðöàß
-\R¨¤V.çÐ`ùöX¶‚{ /\W2[Ô´t Ì‘¶Ùù¥ÕO›ÚaÐicÓ¬ ÿ/‘ù?R`k1ñêå`/µõY]eÉÃÌ”„hdqÜÚÌ@WSUA.)¼¤Ðƒ#ƒôx,_ו ˆ„,xì5D°Ñ )™‹+ë[:{†GÆ&§ß` ŸX@þ‡"Ì Šé‰1Ä*êIEQ^ƽø¨Ð‹ÈÂÜ@WKm‹ /YRœ üÃâk¾®+ä!)«Hx¸z_ Š¿w?·¨¼¦¡©­«53³Èm2Ï$¡`P@C öv¶>«¯*-ÈIOŒ‹ ñ÷rq„\ì Y A¾µÜ+8¼¤èßb²®–rÁµË'DxÀ~8ºxù‡DÆÝMÏÎ/©¬¥4·wõ a PYø¨“"t’ÿÄ„þ¿¬so1
-î¾®¶¦†šŠG2Sn߸äwÎù˜•– YId ‡—ã[L>>–!~ÂöÜê˜ó9¿ ˆë ɹ…eUuÍÝd ¼{ÏÁH&†…ù±ÿÆÚ IÀj¿ƒÙžš@©èëjo¦ÔV–äç¤'ÝŒ
- ðu?eoiªOXñÁxs-%/©€±ˆ¬+´çú¦–ö§Ü΄^‹»›š™W„@BaC&¦f ³0œ„n‚Â*óÅo~'
-: DÞ’$tRåKV„ß±¿Å>b‘y9ÔßCmk¢
-r3Sãc"‚ý}ÜN9X™ÒÓÖTS’“ƶ{[ Õî]a1)9EU=û MØwö @R3s J*ªë(Ï[Ú©Ý}@2òjlLf
-"² (
-jŠ`Q1
-Š.ëµ­««íluúôýù~‰µÛ·g$Ï_’o`˜0y}¾É›×àð¿¿D“xú—˜¿2¢ì?ÕÙÖ\_]Yº;GºM²‘Ëqs¶£‘-ˆŸ¿ê¢™jçäêîåÈŒMLgHeu}s[ç©þAÅÈ•kÓ·îÌÍ?Xxøøé_ÁäÅϯ
-ß?˜¿7wG53=y} &qa°6q¬¹¡f¿¼¤ O&I%ÄDòƒü¼9,Ü-Ð,°#jÅX 4èÀÒ721#YSm™l$&A”.Ù‘WP"¯:TwähÛ‰®žÞsçãʱñS7gT·gçîÎß—…BðÐÓÀàþüݹÙÛª™›S7ÆÇ”£ÃŠóçú{»;Û[›êA¢´pg–Tœ*üjËfX׃Åt SÉ$¢16 ìˆZ1(ÜcñÀÒÑ3@'–5M Âß¼å+aªøϲ¼ÝEå•€¤¹õx˜ô¼ U^¿>195}sæ–Ju[“JukææôÔäÄõñ±«ÊÑ‘aÅ…Á³}½=]Ç[›×VWí-)
-q
-/GS0 gs
-398 0 0 398 6557.1162109 7246.1401367 cm
-/Im0 Do
-Q
- endstream endobj 862 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 863 0 obj <</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 398>>/Filter/FlateDecode/Height 398/Intent/RelativeColorimetric/Length 20921/Name/X/Subtype/Image/Type/XObject/Width 398>>stream
-H‰ìÖy<ÕùÇñ™{çÞKeß÷õXN–ìǾeÉžÙ²g:gR2¢D#Ë„ìK­!†B²"IÉ’%¡FiêîËçû;ŽsÐ}Ì<îãÎu¯üüÎ?Îïùx>ùd«­¶Ú@}úS­÷¸ ZýÈó±¶\~ñÖüv©ÏVF¹½Ff½¿ÀF‰V
-ð;è÷KýåWô•†Ve½¿Ê¯;ªÃ’™
-•dKäç¶$AA¿ mÀ€ pqóðòòñ 
- ‹P‚k!!A~>^^n.$Ã. Âˆ¶&Ë$["?£O—)h 0`@daQ1 Iœ”´Œ¬,¿‹/++#-…“”&à @!›`$ppm‰üTT‰Ï@b‚™…“‹‡_PXDL\»ääv+)«ª©kššZ”45  u5Ue¥Ý
-òr»
-4
-L‚u'—STRQ'héꛘš[XYÛî³w8àètÈÙÅÕÍÍÍ?]]œ99p°ßgkmeanjbl¨¯«EPWQR”ÃKã€D
-!…žˆ<s..>1éBJjZzFfVvvv
-~fef¤§¥¦\HJŒ;}:2"üD()$ÈßÇÓÝÙq¿­•ùC]Mu äãᄉ,ƒ —ú¦fþ—¥Rpñð ‹IÊÈ)ªhhë›YÚ"/¿À`RèɈ¨èظ„¤äÔ´Œ¬œÜü‚Â+ÅWKJËÊ+*®Qª¨(/+-¹Z|¥° ?7'+#-59)!.6:*âd()8ÀÏËÝù ½…©‘žˆì’–âçád_ ²ÞæùˆZIÁ+ ,Ž“•WRÓÒ ;'7O߀`ÒñðÈèØó‰É3²s/].ºZRV~­²ªº¦¶îf}Cã­¦¦Û”ššn56Ô߬«­©®ª¼V^Vrµèò¥Ü쌋ɉçc£#Ó‚ý}<\a$¦°5%yYœ¸°
-ò²ÓS’ÎÇž9v,$Ð×Dlöšhk(+à¥YðRßF dSX´³Øθ“…“£PT!è™Yí;èêéD<þÕé˜óI
-sëýNîÞ!¡áQ1ñß\ÌÌ+,.©¨ª©khjikïìîé ~:òltlüùÄääÔôôô Jp=599ñ||lôÙÈ €KoOwg{[KS}] äd¤$Åȱo7'{k3#]Xˆ,NTX0:ê@ÖûaýÂ-[À³€JP'« ¬Q:ìH<q*:.ébVÞå«å•ÕuõM-w:ºÀ¡`hxdt ¦àùϼœ›Ÿõêõë×?P‚ëW¯æççfg_΀ÍÀŒŽ  ô÷õÞïê¸Ór«¾öúµÒâ‚ÜŒ”į£#N}Üí·†…¨+ÉÉHˆ  Ȧð ZÀÅ„ÍBBZNI]ÇÐ £"…EÆÄ'§ç‚Ä÷7›ÛÄ£þApŸ˜œ~1ƒÀó_XxóæíÛÅÅwïÞýH ~[\|ûöÍ›…°Á`f^LONŒƒÉ`ÿ£Þû÷Ú[o7ÔU"9éâc"ÃHA¾îNöV¦ZjŠx)1!4ŒKÖFöXeÁ‰fWTÕ20µ²wŠ/OFÅ&¤dæ_)ý®úFcóÎî}bì98¼œ…D€¼ÿáÇ?B¢ Ý€ûïßc4fTæf_¾˜š|$ötu´Èõk%Wò2Sb£ÂH>nŽv{ôÊò0~nVìÀÚÈ+-Ø8y…Ĥä”4t-ìÝ|I@‘˜šu©¸¬²¶þv[Gwï£'C°179,¾Ã 0€?CÁú+mä[èCŒsd2?$ãÏžö÷=èjomªÿ¾²¬èRVjBld1ÀËÅÁÆÌP[m7Þ ¼œÛƒÖbXð ‰KË«h˜Z;¸xÃ"ÉåUuÍwïõôL –€l€=ø¿‘ûûÊ–îRh  M~x5?;3=9>
-"{îÝmn¨­*/¾”™s*4Øï°Ó>Ë=zJrp`ñr²mXU\|B2
-ªÚ†{íÝý‚C#ÎƧ Šëu-í]½žŽ" 8šÞ ²™VàV†‚‚™üøVòš,òlxàL¤¥±®ª¬(?ãBÜ™ð/½]ؘhªÈK‹ ñ­òXïgø_kåE¶PÓ1¶°?ä@<y&îBF~QY¢èî}<8261=3 oÉ ÃÚÇÿOrÿÆ…ŒB&A"‹d‘©‰±§ƒ{»¤¶²ôrnZRläñ¿ÃŽv{´Ud$à…Nã±Q憱ÊB,,÷»x‹ˆI¸˜s¹´ò_Ü—ù?ÕùÇhnsgJ)eI¨„
-a•¤ÅR
-©P²MÔ¡²…\#ʈk%)©#eKٳɚcßÉ> sïÜûÃÜ÷络ãTó˜¹wÊ9ÇçOø<¯çëõ~Î@ÑÛ?8Œeø âC ¿ýÑû
-‰…ìïél •¥ٴ丘ۡ¾ž.glÌŒô´56c<°| }5Gt…K
-ÝXw,tô-)ŽnÞþÁÑqI´ì‚ÒJ„¢ ü4¡€L`‘øÄBø},8‚dzdldp €ÔWW¿ÈLM Þ½ù“Ÿ×y‡“Æú¬ÏÑý1't…Kjþ×pë¡MK°00±´ur¿ùàIJF^qEu=JÅÀ†³d'ñÿsø"$‘I $¤µ±®ª¼0'=9î~D°¿·›#ÅÒX_ñD<xÑ=Èýº"$,xx IÔ gwŸÀ°(jBZVAÙ«ÚÆÖŽž~HÅ;
-Åçñ!2# Ã}ÝíÍoj*J^d<‹pÉÝÉÖÒˇ´ä*a>^Tó¸š)©oyxù„WIJ£îÖG,<|ÃïÆ&ÑrŠ^V74µw÷¡®ÀP ?13ñÙ@|’ÈèðÛÞÎVz]UYAvZ5*,Ðñ
-ÃO_+n,¨ôVqn:ƒ‡¹Ñ~ÍmÊ×­Y)HêŠ ãcþ×píñ @yoTVÛ½ÿ°9ÅÉ9êÆ¢ŽÞÖÝÇb–Q°AB¤¯«^[Y’Gðps<avHw×VE¨\W\òÄ€%µ$%£¸e§î!³ŽnX_$?Ï+yUGoÇX X`µ=»(˜@X2:ÔßÝF¯ÃyD…x_°·65Ü£¡ª°a-ÒÏ®‹3¼|‚" )U ¦Öö¼¢ / ¨î¡1¤¨™±˜UAÁx´<¨wBý½\No¢¯¥¾iãºÕ+ø˜ñ`÷/ÿÉGN)?,)Yåmšz&V§\¼üCïPYY E1bÁ,@P@°Æ†0Ź´Ä‡·ƒý<ÏÚZÞ‡të
-µ9.±sJ¡`lPØ ’2§8{úß~˜HË-®DŽÆY0bÁ6,<þƒ„Eò¨­,ÎI‹¹uÝ×ÝÑÆÔPGÖÕîŠa)¸1 1P0Ôµ ŽZ;¸ù^¿Ÿ–S ;Š™‹³=ÌǃG½¦¢0ëÙã{áÞ® +=M5%<h\qÜRX0ð`ì3²´sùájø½¸gY…5°£>dÁnäc‹àÑßÝÚX]žŸ‘{'äGOgŠù¡½;T±x,…Ûƒ Êœ°Ô·<¼Ë…Ä°`0=áèq%8’šœ‘_^ý¦vg²À Ñ¡¾®–†ªÒ<ZèÊÇÍÞúˆ>Q¡e¼œo+ÒR -å_¹f½<4†‘å)WïÀ›÷ãi¹¥U -]}C£œË½™<~ìíh®«,Ê]…]õ:g ñÐP‘[·Z˜É"·Õ K Š®•VRÓ28jãxÑ/$êQJVQe]SGï ÜœÌ=_¦&ÆGÞö´Ók^æg$Qo]vw8~Do÷Öï6ˆ‹ò-ÆmÅ¡Ûj†¥V¬’Ú¨¢±÷…
-FLBú‹òêƶž·#ãp_p4 ôèsØ»“ãÃý]­ U%¹iO¢Ã!³ƒ:êʲ’b¤­8³<X-¥°e·Þk+(Ùůê[ºú‡Ç&&áÖC›–“Y ‡ñ@÷àôä»ÑÁ¾Ž¦ºŠÂ̧ÔÈ _·3ßïÛ¹Y³§n+¬2þ6ÿ›ÈR2JÛ´ Í(ç~ÀƒQCoI½›œþç¯(œÎâ7¦®~Eõºjk|]–GƒxøÿÃù„©–š¢´¸ÚVœˆUvññ.KÉ©ìÐ5²:}Á7(2£¡®½ñ÷S^¬of}ÀÚí„6/ÌL¦F\¿äjgqhÏöM²bBèä¸ò`TÆ~aÂR6NžþaÑñ4 hï±w¸¤¸…zx}º‚6o¬.ÍM}|7ÄÏÃá8ØJUNjÕŠåœWÌÊ€‹OZYÊÖÅûZÄÃäÌ¢Êú–î´¤¸ER3£>¦°6o®«(xžs3ÀëìISÍ­ßÁ%ˆÊƒ£.V²›4ö¶<ív98*.5·´º±!)vðÿö]¡u…âÑö¦ª$;%6ò'W;óƒ:Û”¤×rZyà0àÊX&•¡ºs¿‰µãEÿðè„ôü—uÍ0¥ÆQ0¸KRÌÇΦšò´'wC®¸Û[éj@yˆ¢ËƒSpcj!\«¡24 LOžý!ðÖÃä,¨o¸1FÉ`p# ôÈxLO¸ên­¯,ÌHŒ¹qÕÓÉæÈ~¬<„–-^È8Иš‡©åÂk6(ªišÛ÷ ºó(5·¬šÞÑ7÷4JŠùfÄcl¨·½ñuIöSjÄ5ïs”cš[֭ƺœp0ИZ).­¤¾ç°Õw¿Ð{ñ¸¥°úƦ·D<ÐíÛ*/-.*È÷ü)sCm5Å k„ñiÅfÄ™`ˆ¬•QÖÐ5>îèé#&)³Y
-¾)ÒRìþпöˆxàeÞÝR_Qêç~Æêðu%iq4­Øƒ–­„¬ÊŽ}&6Î^Ô§Ù%¯1K1ê›Ýßù—°Õ`/l«¢Ì$TŽÇt·+Ëp
-oÆÿo³ýT¸CcvD¸;Yi«Ê‰ ò°3;h~«@4~ùÛL! yµk­¶{LÍ.¤?3¾hÇawG CmY1n`Í{.4cÆLfvAq9ÕåF›¶yžHÉmj¼?ú›¾—Ðð³#?+)ò°›ãÆ5ZÊ2s¸Ø˜h=;°:ÅÌÎ- &«¢mhéä}ö×`F ý™ñ©eù™I‘þ®[Ì –.”ÍÉÊÄ@StàCƒ‹®Œ²Ö G÷€¨äË`ôµtÒ¡_°ãL„ß>‡ «4•æ‰ð‘fÍÜÀº-çlQé…K Ì·ºúG&eݸÍè¥G3&Øñª®¢,/#1ÌwÏæõúK¥àì %ÉÁA·å›3OIc•™Ã>¿ˆ3™ùwžÔ7Ó«£vÀøª®üöõK Ç}\ìþ¡»HAô\’Ý©™Ì ÛJþ}±þúÍ{|Ã3òÊ*ê›;z†~§K3ÆÛÑÞT[^zõbüQï]6ëV€Ù!Š­H>JpAqyu];Ÿc é×oWÔ½Bfü›>ͳ£ØQó¨8÷ü©#û·[i«ÈÌå‡ÅŠ6è€Ð€uJu¹±õNïи WK×6µ¿xO¿fŒîŽ÷ý=¯_V?¼u%íd‡“Åše ç‹€bÅHt`ÐÀ®eh¹Í+8ö\Nñ£`F?]›ÙñÙÑöâÙý›Ù)чݶlX¥¡$5‡—ƒ…èÀ ÁÂÁ ®¹Ú|«{à‰ÔߊV¿|ÝC2ƒnÝÀí|ÛÝÚøô^AVRÄ¡½›MõC’Ó$hð!‚Ûïõ‹L¾\x¿ª± ™ñáO:ŽÆ|ìëjy^y'/ãôqg[Hr1Z c rj:&¶.Ã3óï>mhí~;8L÷fŒÚñ®¯³¹¾âöµ‹q¡vXi)K‹Ò
-‹õÍ\G§ ‚ƒnK˜Aivô´5V’Ÿ ÷Ýmg¢£&+ÆÏoÕô©
-Ç´iÓáââ“аÛí~&«à^Õ‹±:õ£â§²«†Ê²¼K G½1tˆðÍb™9eèÀïÔ,>ie-
-‰»x½ Aƒ øçv|
-ôª¼ô¸#žŽæ++Lä㦆[Pì…k`÷>ïaÆ_«¹°W ˜í¿Çn93Õ ðµ6.¾g³o=ªk&îÔ·…ߪÞ° ²yo·4ÐP”äf£6ÂY¹Ä!¼ÀÔÈ¿Wõ¢èSd¿U=m •e×ÎÇë¨ÉŠÎ¦ºå"„3sð‰H«`S£âykwÿ{¢O}[¤ Ø\û¨(;9Üg—•áR%)!n6&ªÂ·[nAIEMKá`jô wêÛÂ7à›v0:òÒO{lY¯§.;—ÚpŒ¶[Y5]S{×Àˆp|jf|[Èû»È‹¯¤DúºX-[ %ÄCU8P4˜Ø¸a»5´Úåžœ]„þ!üG¿õç×ÈŸÝ¿‘âé¸A‘•á Ec®¬ºžÙàSéyw1„wŠ<a ïïi}^Qš“å·Ûf­Ö‚yÂÔ„~ –Y»øF¦\).¯oÁN˜A–0÷u¾ªyP™ê……ƒŸŠpŒFCn‘þGÏøŒ÷«›:ˆhP ±–û¤47-Úo·íZ­… 씆ƒ a©`øíö‹JÍ)í¶§Ÿ@8Â@þ†£0ótè~'ó•T…cb4¼B2 Ô¼êì$N0¼yÝXy„í15ᘠÛÝ~Ñi¹¥OˆvK©>~$…ãaaÖé£T†ãÓhœÎ,„Ñ
-4>WÏ…ƒ¢Z…¢Á·F Û•¯ß §Pä(µ0$rP¶9`4˜ÑÖø,„i"9üöØR¾9€(’JËŒl\EÑ Zx8ºðpx9¡ÍAI8¦M›þ 3Ÿ¨¬šžÙVϼPÁhfPªOÂ7Ç)!n6Fr#+— „âRCkgßH°5@¡Â£ñ£_÷¿¦Qr`›ÃËqƒ¾º,3ÃŒéäœ*Èp&v^iUS#ñ7îÑ ZÂuÈÅÆh™’$
-Y§
-«·œâ
-›vŒH¹RR· ª4.™ !ž[ÍôÔdDù8˜Èâ8Z~ì<Â󕗛ػJÏ¿WÝÔÙ7HDƒ:ááhkxRš“éële¨©(!ÀÅJÇÃg0€å'&¿x•Åö…%g—?oí!¢A•ÆÂqÿÆ¥¸`wSé9¼ìd•\R½ËÏØno@Ì…ëw«^vÀh[ƒ:aáèik¨(¹r6Üg§¥Áq~NrJ.b8ª·êzœöM¼\ô¨®¥»hä ê„Â1Ø×Ùôì^~zl ëf“åÊp’ÁqÄpV. EMCk—CÑiWËž¾hï}G0œzpŒ ô´Ö—ßÊN:î½mãJ´
-0*ÄðÕ;|ÂÏ^)yÒðú I œª‘¡þî–ºG7³N‡zn]¯«*#BÇá¡bdž/ ö"ª·Äò›œÇßõ¢JîÙ/wªÖ=Žßº¥”"sf™2EdÈ”Sˆ&D E©T
-©ä4g*Jƒ
-…ŠÉX¦Ê’ eCæˆDGçÞûœw­µ÷fï6znkÓ½Ïûý Öz>Ïûû|¿û·k|ÇQ‡Ïcá”RÑ·tò OÌ)«Gê-|?Äãc#ƒ½hɽåsx\Q‚Ÿ}:#cc.+ˆœ¦‰ px4p8¬·?òl(ÏMŽß <.ÄÅ<>Õ©" ~‰:<|ÃKjÛHõÒøïC*¹Ý-UEéÀãmMµäEǧ<U”±!³ÒÐjﯗ"“¡Ãñ ZrIó?²s“®’¤
-:—`“=UH«Úe®«´”Ÿ}!#}q ýv÷’e&6.g®?H/ªF¦<Tø=Uh«zñä~Ð gkÕÒB\“‰í· ØxÅ´×ow÷ ‰{^R×~ÁNUokMqæo7δ]«)'²xÑ$â õ[¥JzæNžI¹å°Qáò
-Ӣю«&#ÌÅBW$mð‰+®ÞààáŸ]ÚÐÑ? ´NANÕ觾vÐqcožwµ3Õ’YÌÊDOdmH*ë[î>‰öÛ°ßâ˜ñŽ›pÛÿˆãFEq>úâ iCXFÍhËþSW£ÓŠª`¿Å7¤ŽÛÕü&?åî%¯=–ú*“‰ƒ¬ y-S;×ó7cŸ¾ªmïûµgÐŽ;ØÛZU”}íôþ­“Šc\:øßNÈ.ƒÚÀ;XÇâ(yöð–7Iô•6¼.ÝÚh†ÚÀ9ãâÈ™Z”µA¥Ñ1x¨p IÝ$qL¾8¨ÖE_ 6p &°8Š(‹CˆŽ8Pm0±ñŠ)ÀµA`(â@Ç![SM9zâ
-÷ ¥êó@gcyNâr©b¥j&Eã R‚Ë! ¥b`å|âJT¨T½C£°RTãh©JŽðÜeþË
- >6ªR…ÜyÌœBÒjF[œ¾“ñV*¢4Ž”ª–Ê‚'÷.{í±ÔW)Uh
-®†É¶ƒçnÄ>}U×+1Ah€RÕZU”uõ¤³µ¡ª” ¨¸J¥àj™Ú»ù„Ä=]ÿ¾Ò "hÅýÔ×VSœsý Vq9©*.MÁMÈ.k쀕ŠÐTÜm&²ÂTwÆŒ™³ç2‚« ³Ñ)¸¹ àBÄ¥Aª¸!>nöfZrÔ—47ø$Vüb¾Ë302%ÿMs÷GXp ¥â–å$Üöóر^{¹(+- 07”õ,÷x]¾÷¤ ²¥Ì X©FãcWSEnRøÅ£ŽuÅyÙ& „27$ÇçF+œDÝ`p¤Dz:™ëÒ @ƒ‘™SPZÕÐzß©kÒ_V·õ¹APh‡28&Ò ?dü ŽÉ,®mïƒsƒ PGaZÔ•ÎV*’æJêÆ6ÏÂñGlÐÁ1ÔÛVý2ýÁµSûÀü“äDæ…Æ,@ƒ[x™ÆZÛCço=|ÇÁG_{mq&Ýù‡Ð˜Ï½d™¦©«wHÜóÒúŽþa87ˆ yþÕ¾z{㬋±º¬59ó-‘Ó2³w÷ Ï.mè€ã¨`óïÃûº’gož;´ÍDCV˜‹å¢ò«Öí8ì–SÖØ iŒFGýëçq·¼]m×j,[ hÌš@ƒ‰•Gt¹özÿ;‰¹åM]QAç ÑPšõ(ÄÛÕÎT¡1ŸLcFƒWLaõÇ£“ò*šº>ŽŒýçAi|Æh„ú¸Ù›jÊQÓøçì¹Ll¼âŠ:›v ˆHÎÓÜ iŒÆ@gCYv|¨¯»½™–œ-l|+tÍ<#S^¼méû¤AHèÑX4 vþ¥Jz»½.ß{RPÙÒ38
-iF@#Ì×};Bc15 Æ…ìü’Êú›÷þt?µ°ê]ÏÐèWHƒ˜| )+ç“W£ÓŠª[{°R’q a¾‡éÓà”R]c½ÿÔµé/kÚú Â2- FfNAé•F[œ¾“Q h|úi”éiÌcæ’Q3²q9ü[æ«ÚvHƒ¸i”çÐ¥1¡Á%$£nlãröFìS@ã¤AX¾†¬ºñ¶ƒ(:HƒÀ@?Sà¥ú™2 hñ¿4ÓÓ€ ÷¯ \?S¾‡‡€”Š•óÉ«ÑiEÕ­½aùìü’Êú›÷þt?µ°ê]BãߧæëNÆÜìüK•ô,v{]¾÷¤ ²¥gpt Ò &ßCƒOb…®¹“g`dÊ‹·-݃#A!Ñh
-õq³7Õ”[2Æ?0<¢Ëµ×;xøßIÌ-oêøü; qà”Æ0F#ÄÛÕÎôOöËÄ«æuãëh‰&Íš'¥RÊPi>IRH¨(!ŠJ!q"RB(¤È,%24i"͉æQ»yžN¹¹®ußß°w»Q§~{Ýu×zŸÿà]Ïz¿Ÿç£©0¦yóH*j›í<ì“ZPÕÛ W~ýúùã;h£"ï}t¨ï![c ¤ ¦ÑmH,Ñ2µs?wëyJ~e#lƒ^ÁÚèh(ÏMzâãj³NC^Œ´Á0Üã<v>qM“í‡|oF¿Ï¯hìì‡mÐ'h}õe9‰Q7Î8o3R—åeÙ;Ÿ˜‚†±­ëÙЧIy ýßü„mÐ!XíõeÙ ‘×OÜj¨¶X”gD   ^1yu£m.gnD%æ”×wôÁ6è“_ÿùùc°·\’q픓•ªœ+‰‘áš6ØxD«n=x:82!»¬¾¶A§€6†@uÅŸÞ…y9Z®Q‘æf%ͦ¶ñ#‰GDNÕÀÊéÔµˆ¸¬r[ïàŸP8褞ÖÚ¢Œ7¯œØ·yµò"!.–¹´mÌ&±r ˨¬±tô
-
-÷©¸´1Û G€n t·Ô|ù{ÿ’ç ½åÒ‚œ£Ú˜ËÂ%´Hyõæ}'®<|“QTÛÚ3
-ªš`ô EþÊs£B|\mÖiÈ‹ñ±31ŽlcÞüK´LìÜünF¿‡úG· ºÑ×N.ÍŽWþÐ60ýÓXgãâõž¡êFæ»ð«'üÉŠp#òGÓ¦rˆþy_ŒÏ*%·Cá KFë†>¢4ò‡¶
-‡ì°p|ƒÂAŸ`ºÑ tãÕ½
-pÐèU8¤–éšïF&n:˜¸ÝpTÑ!  êÀ½uÎ}‡©¶â¨;fâ>O)¨lìì‡mŸQ×ÖXCA|äÀ¥™¸š&Ûù†>MÊ-o€£ŠAno¹$+>âº76pyG \š‰«n´ÍùtpdB6Uô
-ñžÖoEŸÞ>
-ôÚo¹FEV˜™T#Ú``$±ñˆÈªX9žÄFU+À8lƒè G'UìýKž{,ôV€;bRa'±‚Q¥LU5-ÝpTtRõw6U¤ÆܦL*Næ±mPF•½Ç…;/Ò>W5uÁQExpˆ7”ç½¾éçfgª5vR *Em³îçn=Kίhèì‡':ÄÛÉ¥Ùñ‘Ágœ·©Ë‹žTø¨bçWÐXgãâs#*1§¬¾UðTˆ‡t´4P•á5©0Œ3±ñˆÊ­\kítêêãw™Åum½ƒãÄæñêÂôW÷
-;T<“ªáS…¬ªÝn?OΫ
- 8˜'?TÈç`@•C\^ÝÐÚÑë
-ä8A¾…á·/x8X¬V–Áec²6h”cÕFû£þ€ã9e䶞ÈñéÌ[ŒáEŸ†»n7ÕFeƒô›¯A«ÇÃÇkš»àç˜AC5Ø×ÑPYúò`¸%`¸* “ ªrˆÊ©"¿x'&%¯¼¹ÿëgý·=­ßJ²¢B}Ý×K qÿ–áøç
-F[œ |›þ¹ª±£r|Z¡˜¹,÷ý³° Ç6é«È‰ò²OáØ©gbã‘QÖÃFnbvi4ÀifØü2Þ>¾vÚÅÌ[)A®)1œò9H,œ’ŠZÆ6O=zý¡°ª±~Žéû½àkä%Çܹè¹wËlÞNíP¡ÇG®Šþ&óaÏ’ràç˜fð¯Ñ\ó5ã]D°«™Î2i!.Ö©1ÿèÈ]¨¤mbëì}5üÍÇ/ÕMøç€uü“P¿F9øwŽóS“_0JóvøTÍù/ûeâeþ†ñ9£3š¨Hd+¡”%C
-cI’’)$5ÑB"ƒ±¥(k%$%dm…”„""ÊÙ²Ôk_Rü~ßïó<ï"Mñòæïsÿ ŸsÝŸëbbåä•Ußeáàs9é>1øæ˜Ø}£û¶+ƒh°³Þb¯
-”\ln3:쌇ƒÊ#*,ô4`4Æ·ü(Ã1,@~1¹1áÀE>#m R4T¤W.Ñ`w4°p̙Ǿ†Ã ¬U„¾¡¸È'p0ÃèÖ
-‰ÆzqÁ Fcl8°ZÕ=0Œ·Üñß)`k£Á1±h|`Ž °9Jk[½x8&p
-ïjo¨(¼—vÆÁb—º•Ñ ‡c©¨ŒšŽ©­×ÅwóËêð–;ÞCÞû®¹¦$çvTà?6FZJ«‘hü2áh`áøuÞBžå
-šÎF$ƒ ØÐÞ=€ÿªqò§@»m«ñ$#>ÔÛÞ\w£¬?'+UÑÀÂÁIJˆOxêŽ}G݃£S–‹ŠÿUß9Tápøg§\õw>d¸UQRˆwá<ª¢A Ç\6nAñõ»-΄%d "ÇÕ÷Ž¢Ýæß½èekª£&#º”êh á`dbáX²R
-L@×€k)‘ ã¿êÛG¡ð§YÉghÊK,ç¡:H8@8p ˆÉnÜifw*ŠüÕBï>:¾}¨Â{Þ…?í?•5Â|‹X˜¨ú«çÌg‡-wËLä íptà¿ê‡ý©ÎÖºr¨ðÓ',Àðäf›û+µÑø‰¢åŠ¬EE~=õQIM3þ«¾}ØŸêno¬,zðo¤¿‹õ^-%©K8X˜©Ž©å²ñ,ƒ"·8q:4>ãq96:ð^õ‡À
- 6:„×€_eíìw%9«àE}+®Ž±G*·-¯ÊòÓãCÏÀ>ÿÔ$§Æ(ÈèàX%»QÇô˜{PÔ­l´æâ«cô‘¤ÊmAVò?k#m⟚ÔÔ ÀAúUò›w[ØŸ
-‰MËEÔÑ«ƒòFÐ¥ÊmCeQö­¨ ÷c¦:‘>ÿÃTÀ ý*Ø«”´ :ù†ƒš‹¨_”‡ü}?"Ü´Øo{ ½ÍòSÔ§È8^Å% *³a‡ÉaW
-uà8ˆ‡Hãý
-…qÀ_7à2ñuêºf¶ÁP¯ÑÕã@38\ˆ4ü]›ìØ #*À…쾩ùS ôW±p,Yj®žå ï‹1ižU7’LŽã@ >Ô×Ùöº¢(;%*ÈÔÛuâËxÙçÏ™º?…ဿj¨¹ÒÊ@'}ÂâÓóž×4¿#ðçÏ8Ìà}]íUÏrRc.žÒÐT\±„ƒeJvß(?3Ìbœk®P‡±‹_DRæ“rÄä°XÑ;bðšÒ¼»q¡>'Qi,å\
-ñùZ§êÊÁ#þ9º_g#* ´ÜN- JuH)m5°rð‰IÍ)®l
-’+ø±BƒÓF”8€Éa±RÖÚcuâÔ…è[ÙO_ÖÓ%Ž10"ÝmÍwk*J /uŠf§À1 ˜|>; ˜Œª¶áAÇÓ!1·UÔ·èÇh3“#=ì,ô¶(I‹ðsÃ:ECiqü2›™…ƒw™¸ì†íF<s)6-§¸â5ÝáÀvƽä«AÇ-ô·*¯àuŠ–'á@LÎÌ
-{®œÚcë¿}Co 8ÈÏŠx|†çqK-åµ Û²´48‰V¬@Ï’X§®³¬À°¸;
-ñá=€ÑÖX væŒmª² ƃ<;ró‹H#8œ}/ÅÜzPP^ÛÔÞ9ÃqŒ`0z m Õ¥FÐ0~@·MΈC
-Âè!´6T•äg&EN/ b±ÂpH)jìÜgítúâõ³?¯nl#ôÎÜáAœýÝïÞ¼®,ÉËHŠ ô8>­0H8˜á³–RؤcrÈÑûüµ›™ù¥U ­„ž¤éμx`þêë~ÛR_Qœ›žxÀ°¤ø4ÀøÇjyõ?Œ:x_MÊÈ{Vù3PDe€™ÑÑ\÷²(çNBD€»…–Ê´Âõ¬–®”\¯¶}ï{ÏÀÈÄôGÅ`x«ÕÌáARh¶Mµå…ÓâÂýÜl-ô§6.¾r´ ­Ž{DÄßÉ)ø•Ç̉„¿T¡­±¦¬àÁíØ°s®ÇÌõ¶*O; 2Žylœ|B¿Éªn3°°só ¿‡G pyÏŒúV#Äɇø»ªôqVJô%_ç#f»·(¯›v$ÿç¾N£©Þ÷0€×:·sn¥r+!2C.!T†TÊÐ5$CÒ€B†,)Ç¡„"©Èt3Ê1CÈVfÛLÚd.¥i{¬ûýý‡=œÞœu‡ÚÛÿ¥áÕg=Ïóý-[¹šoƒ¸œÊ.ýÃÇÜü#âÓ”×·t ŽL¼†¶Z(·ÙRïggÆiýÏŸç$߸ìëbk¦§¥,»Ið›cK~
-?méA·§9 ì–§ P›jËòÒâ¯\ô8ni¤½]AJd=Âøþ/߃α”‹›G@TZq‡î¡£Nç.]½•–WZ m·¼9:øb·j©˜ŒØ0¿³‡õw«ÉK
-¯_»ŠM0ŒÇ’ViZ;{!,ö^ö£*
-ÜV£“¯ñà<F0ÞÎLŒ`-Uy7:ø¼³­Ù*rùÖ¬ZþW6ÁXDžVK¹V­å–ü»ê®ƒæv.烣ïÜϯ¨o¦ÐÆáéǃãêjž8¥°`Œ½ìï„–zžù‹çIkã}šÊpÙò®^ÉNÌï@¸t7oÕÜob}Ò3àj|jnI Œ9Ϋ+¦Å@Áèi£T=ÊIŽ ÷ww´4ÒÙ¡(SËØ
-ƒé—î&Eu]£#Žn~¡7’2јw¡x ãŠÃê
-³øX¼ÇƒAmª«È¿-åëjÿ“þîm
-?Šð
-ƒñð€ÓJ@Æc·¾¹³Oൄô¼’šg(p\Í}$ëŠ<æéëM£±ºø×Ôø«ž'mLô´Ðdð¯%.[¶Â ?<`Ë×®–SÑÚolåäá{/«ð1¬Wðöàœº"- ðÆ€Sª#3ñz¨Ÿ›£å!]u%Y± l¶ßLyZ¡ñ“QÜ¡chaïz>Gc[¼=¦gñºbú``%Eèn¥ `ÜŠ ôv¶5×ß³MAJTp»í7óÇu‚¢Ròj»ôL­Oœ»›”UPQßÔÙ7üjr†éºb_|0°K
-+©ÞögµåùŒ îNVÆû¡¥à•ÁÃ͆“ÁøèãÁÍÿQb³²†®‘…´ÜâjJk×
-+©’¼Þ‰¹Œ‚a¢·SUžÝ[Šøm·ÏzaI¹­X<\|#ãSrŠ×7uô¾k—m=˜-ÞLÓ†z°’ʼàålÁPWÞ ·›·ùm…âÁñØ‚âaíä~árÌô¼âª†fjß‹Q6õ˜g±˜yÑÛÙTÿ¸('õÖµ`ß³ŽGõvªÁ| ñ²}K‘ÞVø˜ àñ0<lçì›”ñ°´†ÒBíÿƒ@p
-†Åè‹>jsCU1*©P“?›ëk«+m–€ù†ÑRlŽAo«–ÁÓƒo¬WÆGÏøEÞLÎÊ/«¡´" º;„ŒÜQ¤E?µ…RSú0#)6"ÀÛÅþÈ¡}Z°›„ˆùfû–"?¼­ÈxÀq¥®­onsÂÝ/$êVJvAù“ÆÖ.F>>ÿöÍB¯(¸içH‹VJMY~VòÍÈ ß³Ç­LìÞ®(+¾‚æ›ZŠüXâ!´IJ^Uk¯‘…ÝiOÿÐè„TVx|¦ä[€ÌÓ+êóG°x={,ž”d§ÜŠ
-ñs‡’2ÐÑØ*÷£¨ /S08cs<à¸Ú(.«¸ êÊÒÁÅëRXÌíÔœBä}52ï¹Áƒ È×a¢€Šz7‹Yô© Ñ¡þž§í,PI)H‹mà‡ÅÀ‚Á)-E~ŒxÀÛCPDRNyÇžƒ&G]½‘GZäö£ó˜}‡
-‹¯2O_ <of&ÇhC}°˜Å혰K^.–Æz¨¤$„Ö­æÄ``ÆWðöàß
-\R¨¤V.çÐ`ùöX¶‚{ /\W2[Ô´t Ì‘¶Ùù¥ÕO›ÚaÐicÓ¬ ÿ/‘ù?R`k1ñêå`/µõY]eÉÃÌ”„hdqÜÚÌ@WSUA.)¼¤Ðƒ#ƒôx,_ו ˆ„,xì5D°Ñ )™‹+ë[:{†GÆ&§ß` ŸX@þ‡"Ì Šé‰1Ä*êIEQ^ƽø¨Ð‹ÈÂÜ@WKm‹ /YRœ üÃâk¾®+ä!)«Hx¸z_ Š¿w?·¨¼¦¡©­«53³Èm2Ï$¡`P@C öv¶>«¯*-ÈIOŒ‹ ñ÷rq„\ì Y A¾µÜ+8¼¤èßb²®–rÁµË'DxÀ~8ºxù‡DÆÝMÏÎ/©¬¥4·wõ a PYø¨“"t’ÿÄ„þ¿¬so1
-î¾®¶¦†šŠG2Sn߸äwÎù˜•– YId ‡—ã[L>>–!~ÂöÜê˜ó9¿ ˆë ɹ…eUuÍÝd ¼{ÏÁH&†…ù±ÿÆÚ IÀj¿ƒÙžš@©èëjo¦ÔV–äç¤'ÝŒ
- ðu?eoiªOXñÁxs-%/©€±ˆ¬+´çú¦–ö§Ü΄^‹»›š™W„@BaC&¦f ³0œ„n‚Â*óÅo~'
-: DÞ’$tRåKV„ß±¿Å>b‘y9ÔßCmk¢
-r3Sãc"‚ý}ÜN9X™ÒÓÖTS’“ƶ{[ Õî]a1)9EU=û MØwö @R3s J*ªë(Ï[Ú©Ý}@2òjlLf
-"² (
-jŠ`Q1
-Š.ëµ­««íluúôýù~‰µÛ·g$Ï_’o`˜0y}¾É›×àð¿¿D“xú—˜¿2¢ì?ÕÙÖ\_]Yº;GºM²‘Ëqs¶£‘-ˆŸ¿ê¢™jçäêîåÈŒMLgHeu}s[ç©þAÅÈ•kÓ·îÌÍ?Xxøøé_ÁäÅϯ
-ß?˜¿7wG53=y} &qa°6q¬¹¡f¿¼¤ O&I%ÄDòƒü¼9,Ü-Ð,°#jÅX 4èÀÒ721#YSm™l$&A”.Ù‘WP"¯:TwähÛ‰®žÞsçãʱñS7gT·gçîÎß—…BðÐÓÀàþüݹÙÛª™›S7ÆÇ”£ÃŠóçú{»;Û[›êA¢´pg–Tœ*üjËfX׃Åt SÉ$¢16 ìˆZ1(ÜcñÀÒÑ3@'–5M Âß¼å+aªøϲ¼ÝEå•€¤¹õx˜ô¼ U^¿>195}sæ–Ju[“JukææôÔäÄõñ±«ÊÑ‘aÅ…Á³}½=]Ç[›×VWí-)
-H‰ìÑ ®#7
-úd}ÿ4ôÑ úú©è³!ôí³Ñ#èÓôåÓчCèÛ§¢ÏæÐÏOD_¢ÏŸ„¾G˜€¾xtƒÛÑ/‚Îp+úÜuÐ%nD_»:ÆMè[—C¹ýé‚è$Ãч®‰®2}ç²è0#Ñ_.ŒN3 }äâè<cÐ/.4
-E6(²B•Š¬Pd…"+Ù È
-UV(²B‘ŠlPd…*+Y¡È
-E6¨²B‘Š¬Pdƒ*+Y¡ÈUV(²B‘ªlPd…"TY¡È
-E6¨²B‘ªlPd…"TY¡È
-U6(²B‘ ª¬Pdƒ*+Y¡ÊEV¨²A‘ªlPd…*Y¡ÊEV¨²A‘ªlPdƒ*+Ù Ê
-U6(²B• Š¬Peƒ"TY¡ÊEV¨²A‘ ª¬Peƒ"TY¡ÊE6¨²B• ŠlPe…*Ù Ê
-U6(²A•ªlPeƒ"TY¡ÊU6(²A•ªlPeƒ"TY¡ÊU6(²A•ªlPeƒ"TY¡ÊU6¨²A‘ ª¬Peƒ*TÙ ÈU6¨²B• ªlPeƒ"TÙ Ê
-U6¨²A• ªlPeƒ"TÙ ÊUV¨²A• ªlPeƒ*TÙ ÊU6¨²A• ªlPeƒ*TÙ ÈU6¨²A• ªlPeƒ*TÙ ÊU6¨²A• ªlPeƒG™ªlPeƒ*TÙ ÊU6¨²A• ªlPeƒ*TÙ ÊU6¨²A•U¨²Á£ÌU6¨²A• ªlPeƒ* <ª,ð(³@• ªlPeƒ* <ª,ð(³@• ªlPeƒ* <e>_• ªlPeƒ* <e>_• ªlPeG•~¯\æUÙ ÊD®òþ¬\æóTÙ ÊD®òq>ª\æÓTÙ ÊF.óaªlPeƒ* |¹ÌG©²A•>\æƒTÙ Ê_D.ó1ªlPe/#—ùU6¨²À7‘Ë|„*TYàÛÈe>@• ž¨\æÝ=¹Ê»«²ÀS‘˼¹*<Y¹Ì;{6r•wVe§#—ycU¸¹ÌÛª²Á¥ÊeÞÓµÈUÞÓÅÊeÞÑÕÈUÞQ•.G.ó†ª,ðBä2o§Ê/E.ófª,ðbä2oååÊeÞÇë‘«¼7*—yïD®ò.Þª\æ=¼¹Ê[x3r™·Pe·#—y*—yu#"WyuC*—ymc"Wyiƒ"—yiÃ*—y]ã"WyY#—yYC+—yMc#WyMƒ+—yE£#WyAÃ#—yA7T.ójîˆ\åÅܹÌk¹)r™—r[å2¯ã¾ÈU^Ƒ˼Œ[+—y ÷F®ònŽ\æÜ¹Ì ˜P¹Ì´‘« ›¹Ì¬I‘ËŒšV¹Ìœy‘ËŒ™¹Ê©‘Ë̘¹Ìˆé•Ë<ßüÈežŽˆ\æɘÈUž
-Š\æ™°Èež¬\æYÈÈež„\æ)èÈež€NüúƒãÑÿE¿p8:ïôG£ãþ~â`tÚŸÑ_‹û+úCÑYGÿq$:êŸèGD'ýýÉqè £_9 ó3ô/G¡c~Ž~æ tʯÐ߃ù5úCпCÿs:â÷è‡@'|ýÑîè~Ï¢ÚïyôS£Ó]Aµ-:Ü5ô[›¢³]Eÿµ#ºÙ+è϶C{ ýÚfè\¯¢Û
-ëuôsû K½‡~ot¦wÑÿmŽô>úÁõÑ…Æ _\gúÇ•ÑmF¢¿\f,úÍEÑYF£ÿ\Ýäô§Ë¡ƒÜƒ~u-tûÐÏ.„Nq+úÜEÐîFÿ»:ÂôÅ8:ÀôË,úýyè§AôõSÑgCèÛg£ÿ&Пèϧ£‡Ð·OEŸ ¢¯Ÿ†>šE¿?ý2.p?úá5ÐîE¿»ºÄ}èg×B׸ýêzè"ãÑ®‰®2ýæÂè4ÃÐG.ŽÎ3}âèDï¢ÿÛê ôu[¡c½†~mCt²ËèÃ6Eg»‚þjkt¼'Ñ7í.ø-ú Sп@_sºæ‡èSŽDGý}ÆÉè¶ÿ¡o¨°E…-*lQ`{Ø£¾&Õõ©k’ü-À
-H‰ì×y<ÔûÇñswk)a,c[ckìKö]R‘%…e)¡B…TÔ¡ÅVɉŠD*Ê–¥È–%Ù[,ÙJÛ­{»Û¹Ÿïï7ÃتóxtÛ¹¿×Ÿóûý13ÏÇûû›ùî;"""¢ÿå~ó³›ïwLôåM•ûíEp;M³ý½ß&Æ}ÓÁçûãMk†.ÒûôGzš#Æutï„8³ö|2",&_œÇA– |ýù“¡;àF÷f`Ôó³/6^´Z„‹dYYÙØÙ988¢ÍvnbggceE∛×Ƭ êy!Lß/Z/â]66v ]Ĺ˜‹‹›‡‡DâEñÍv‰DâááæâZ̹h€³³±±"m´íÔóý¡ÿ¯š¦c¾Øv1]n/¿
-5!ýK5E†ã™•Î‹t®ET\Br‰ÔÒe2²²TªÜ'¢Reee–-•^")!.&JAÞH°që)Ô„ô/Ð,Â80òåã'
-‹€.••“WT¢)«¨©khjjiiÏ™––¦¦†ºšŠ2MIQAŽ
-ÞR’b¢"B‚|¼<Ü‹9'©áYÍ$=ßßů´Y„ÙØ1`د 0EL\Rj© U^q¹²ªš†–Ž®ž‘±©™¹…¥•ÕÊ9³²²´073562Ð×]¡­©¡¦B[®(O•lq°&ó3¨™¥‰IÿwbO³bÂÜ$ ùÊÊ)()«ªƒ®¾¡±©…•µÍšµvöŽNœ]\]]Ýf .¸8oprt°_g»ÆfÕJKsScC}]mMuš’<u™ô5:ÀÙÙpi8½‰Iõ˜‰æDÂd!1 ©e²òJ4U ­ú†&f–  ¸\ÜÜ7{nñöñÝáçïíœz5Àßßo‡¯÷V/M]ì{¥…™±žŽ¦š²’N- 8‰›YzrÒóýõüšJŒžÃø†ÉB1Ii9E
-œÝ
- aѲÒA~vrƒþ\“3f£“ZPD\ZVQy‚Ø70$<êè§SÏgfå^¿UR^US×Øü°½³çñSðF¼ã¯.ȾÿáÇ¿Bûdè¸ñý{$Ž¼‘öèÈ0X?{ÒÛÕÑÖÚÔP[]y§¸0ÿ*l:åäñÑaÁ¾^ì
-Êšz&Vk\=}éÄ™W®—ß«mlFÂÏúaÂ/À˜ï¤..øï/Œ œY#êWã/ÆF†A6ÝÔp¿êNQA^vFZò‰Øèð€m.ö«-áä¦É/“äçƒþZÎS›fÌK†§1u¹šŽ¡¹Í:çÍÛB£qâüÂ’
-qk{W/Úð(ž
-¦òý²_BœÂšŠšFìÝÞÒXÿ´ªüaAvzr|LxŸçÅßÏ’LŽêhB å×®EâþΙñócAq)…m* ŽŸ²²wöð
-‰OF)Fˆ›Z;ºûÈCfâûwéþOÜŒ¨i¤É ïÖ¦çµU
-èò’ü¬´»·#C¼\Î[“LŽÒPÙ¶IFJ\DP
-ò§Œwªi“l/ùݼ•˜–UPú¸¦®u1#âoIøcÒŸ€noixV]Q’Ÿ™’s#ÐÛÍÁÚ츾¶p–Fœq?£j›50滚ÊXý€‰¹í¿àȸäôÜâGPÆÍí]}Q3!þf„™H‘¦†1ÖÕ†Ì]¾{;⺯»£ ÉØ@[m'Áïíé\Ó¦rœ¶ YOÆ5ý_?͆¿ñi['OÿÐèø”Ìü‡kê[;{È̈'†0#iÐ0ƆÈý=­ uÕå%y’ã¢Bü=œlÍM ¨ïDÞAœùx€3gi›"k`ÌÇ‹ ÍE0¶sò ‹¹“šUXVY 1Sçé5]Ô‹˜úß4Ðhuƒ¹q —€¸£C<lOgäí5â"KòÏâãAõ<…S8cÆHÖÜ<|³ø.G»Z 3vƌӲ‹@Õ 0¸SO*ÄÄQAãŽ&Ì nyQ âÎJ½Fá ý¬(+%¾|É‚¹3ygpŽ¶i²žÁ;sî‚%ËÅ¥daWcÆ—qbZNqyu}S[5ÆxQO&ÄÄÑý'aîÁÞÎÖƺªò¢ì4
-gsc}mUåM2’¢B‹ÑÜæmÓe†õb!QI™MʪÚúÆæ˜q,f\SßÔÞÝOþ$ÆÍõ“c47zd‹»¾º¼8‡ÎYOkïv…u«W.[4ªm63uY#YÏ[´låêu
-Û÷jé›Û¢>ŽM¼ŸS\QSߌSb<iG7744ww{S} ✈9ÛŽÖTQÚ -õLh›½ãL 2’5²„ô%ÍÃÇH6xsŒŸ7·÷ô"UãOjÄøÄ97××Tà<‡ú{8ZŸ2ÔÑصYVJLi›g:[¯0êêšÎƒd-,&%»y—†Žá)kGÿPØ\ŒŒ‘ª?°c|Ô@#q£‚îï¡qŽñ»tÞÒôè5åM둶ùgñÎàÆ+Œ13y?’õúMÊjŽšZž¿äMg<@0¦©z¢ ~ÙÑľV4ΰÃR¢®û¸Ú[˜èï߃´ k­0ˆ3æ<ÑT¾òQ­.XÖHÖ{öë›XØ»ú\JHÍ.*G}ÌÀ˜EbL?š¸8W—e¥ÄEz9Û’Œt‘¶am Ì›ÍÇžqfò<XÖHÖºF$[g¯Àˆ¸”¬"ø;Ñ]Me<ÑÜþîÑÄMãÜT_õ¨ #ùö
-UÁ߉Õãûˆsw{c]ei^zbLˆ¯›½…±ž¦ÊV9)1¡ÅlgÆF† KÉmQÑÔ3¶°wó ‰ILÏ+­¬k„¿0FÇÄ™ÜßÕÖPû¸$'5>2ÈËÙÆÌðúXa+hqžòöˆ31»¦þÈ̓YdÕ:eõC†f6Î^A‘ñ©9%kÚºúÉìÁ#çar_gË‹šòÂL¤mwËZ{”ä׈A;ÃØæšÆ&Ö&l=k:ïL˜Ö¢’äýú',Ü‘¬3 Ëk^´tö‘‡Ù†1:ç·¯G‡^öv4×W•å§'Fû¸Ø‘Žéì#â c›‡ûG¶°6ÅÖÜ3øfχi½Ùˆdç☞_VUßÜÑûrhôõ[öaŒq†½=þþíØèÐ@w[#ÖvÜÍkžŽV' 8C; ðqfukSm ³kîBÁ•?ËnÙ­‰‚ìyõf–uc[÷ÀÐèØÛ÷ãþú‹m£Ãœ?ü9þîÍع¯ k;#)6Ô×õœ9Ž3Œí%óçðñp³ºµ©¶Æÿ'aqé ÛÕšÙ¹ø„ÄÞÍÀ²îê#Œ½y7Ž‚ÌNŒÿC×ö8ªgÐv¬íÜ´„ˆÀËΞ„vÞ*')ºl1ÂX3ÕÖ<|hv­^¯¸ë=“3WoƧå>¬¬kY£Bg+YÓ©žAÛ O+Š2“oQ⬮¼q­¸ðây,nmš­gÎ…Ù%%§´Wûè)›‹ÞÁ1I…O°¬Ù­™¨gª¶;a…•æÝOˆ ¼ìhuB_s÷f<ÂXØÚ¸’˜
-ÛÛf×>Ýã§wKÍ-…ÕÕI•5›2óÑêùõ«¡žvXaDœ]ìÌ ªn“—bik£Jþ'ªd¾Ù –Š¬–Ù¼{¿ÁɳN^×£Q#C{PÙ²™®í±‘Áþ®–çÕeyiñ×<ÎÖØ¡°V[›%1Ó*¶5ØZ^Iõ !ÉÎÕ/ìvJ42d¼¬ÙUÖô£¯mXa(ÎÐÎI1ÁÞÎÖ¦G´TÀÚ°µgó±`9Ó+™_@H\ZaÇ>]ã3ž×"ï¤ç?ªyù'™r + ÅÚ975.Üÿ’½¹ÑAU%y©•‚ çüÄråL… •¼xùªõŠ`kSkgïØä¬â'Ïš:ˆ SV×D#øGÑ65Î OË $F]v´4>¼Oy£´˜Ð¢¹€™•¬MÝ]¸’Wü,»uïCÒ97<»ªŸ·vsH#3EÛç‘AÛu•%Ù÷n…ú\´65ÐÜ¥¸NBX€Ÿ¥6Ú]Sðîš³PPTjÃvuãžQ‰
-+jÛ{_½ÁÌ9ŒÑÑã<4ÐÝö¢°p·sf¿jïÙ‚Ê™•6ÃîZ$$•¬¡w⬲uv š]ýƒœdâhq!÷v4ÁËHŒ
-ô<oq쪒œäŠ¥ ЛúÂ<É9S*™í.a‰uPÉG~³uõ»—–WVf×ð«7ïÆ9§‘™ÇÆöëÑ!4ÂÀÚɱÁW.X™@9oX#
-Œ5¦6m\ãÝ%³e¶!ÉÞýjÄlëŽ^Úìâ´ GÄyü=aØÚ©„ùB9ëÿ²SAZlÞ`0µ'7fú¸ž¿v—’ê¡cPÉAÑI™ÅOêš»úñìâÐ Gaä>dí‚ô„›nv§ŽîßMÙ`“þGE@æšÁ ãz¥¤ü6u]cË WBnÝË!¶5Øšƒƒü_öËÄê|ãOSݤ#GB–CYŠ²—BEc«\EL“ºèdêÊd©I]
--x¬ ),C¥§…kɾŸc_²„á–šç~¿¿ßùsš;óLWg{îý½ÿ„ïëù~Þ¯7¦„Á«ÝZW^Ê9>줷›£©¾–
-TmÁ^Td¢„Ì2õÕk- wG'¦ç½ªn¢³_k~?5?ƒIØ/ã#ƒÝí •ÏåÞ¼xÄc—­¹áJUù%⽨ dÆ‚Rr½ÅÁxW\Rƃ§eµ-}Cÿ÷× üΘkÓ›^¿,¼{+!ò”/Ùeë&£Uj
-Ò‚¼¨‘¥¡»ÞjÇ>¯€s?^Ϧ—×µu ãךìjO¼êëh®.}’—žìï¹{ûfcå`Q‰‚E%˜Ù!kê™Z;ºyŸ »x#ç!µTòà~­ÙÂpí©_Ɔû;[kËž=ÈHŠ =vp½å:lQ "fd’²¦¾™“û÷—Órw5Ò@%ƒký¿Ö¬`®=62ÐÕVW^LÉJª}ÈÕYTæys 3;d-3[çý>A@®ï¾¬nî`V2~­YA®6,ç‘Ážö†
-jÁÏ©ñç¼öí´Ú «¡$')&"p˜?…lnçLö=äúIiMKg?¨dd~?­ …YÎÀÁhUÏåܸ•µ©ž¦ bþ-d²_p̵Œû@®[»úYÞÅ`å  QíÇwÒà¢rw²1Ó×T& æO!oÜúÍÿؤÌ|(×݈wá•ü{Á UíWE÷~Jˆ òÙïlkf ÅÀ,0
-ö dC
-ÃÁÕn©AUÔi_²³9†YPLû÷ Ç¥dP+h=ˆ\ãÞõ‡å<8Ø@XTOïg\‹ ö#»`˜ePAÈsþrvµ.(¦\óû95,Õ膋*?3)6Äÿ
-µ²‘ŽCžY0ÌÀ´iõåOó~ºphÏöM†ZÊrÄ
-
-‡üEA1OŽôw4UQ)‰‘Þûþja´mžU3r¯yA½^k¹ÃíÈ©è¤ì‡/ª[º
-§L™•)Cežç™mÚÉv¸Õºï°mι©mXö{W¿ç?xßßú<ßÏãtV_CQ‚Ÿƒêì0’z±ó‰ÉïÕ;}ñZPdjAì×舢öø2×٠幉Oîýf{R[MV„‡•a¥ FQ/ai•Ã&Öw%ä”ÕwÂ~ úzµBî춚’×1¡·\-ŽT–„Œ~el¾zá Ì}‚ŸgU·ö€¾^Í MÀ÷4W¤F]»xZo¯œßJ Ø|õÒ1µó xš”÷¾©O˜DüšÚ_ÿ£íìOcƒõeÙ îxX#ÆL¿a`FÕ‹Ži‹ ¬^–n¾c3ßÕ¶À} P^Å:{¤¯µº(ãùogs Eq~öQ/Z6^Q9T½¢R *[z‡?g€z­j`˜§' øî¦÷o’"S•áFÖÔÚåÁLZQÌœBR»5IêÕÐ98> Ôk•à wö@{mI&"`FvnزkŠ¼¢Ä4ôÏ:y?€Õ« ¨5 Ø ñãposeAJdàU‡Sºê?o[5… ¼ž^QZ'l®Ü‹Hzƒ¨¨×ª°ÉqHÀJ³ãÃýÜ­~9¤¼c+'Ý2×Ô,ÊüâŠ8Ãs.7Cb^—Ô ôõªgNÀªÞ¦Gÿ~ÝñŒÞ^y1^6Úe  e.aUí“vž÷Ÿ¥äW4÷ êyÕC°¡®ÆòÜ—ýµ69 ­)NfúeÁLFYBi¿‘…«ïø¬ÒúÎA€2•‚Àüç§ÑþöšâWσ}œÍõ5Äù–·¦fQæ‘QÓ1µ¿™Z­¨‘@½¨XÀ¦‰|OÓ‡<xMÙœÐR‘æb¦ß°t˜I(s (»Ý‹Ï.kèŸêE¥ ­©tM¹œ3Ä)Jð/fåMʲj:föWƒ¢Ò
-«ZÁŠ¢fÎFÖTE~ʳûžv¦Úª2Â\,K‡yÊŽY^¾Ÿ£ ­¨Ï
-â|l ´ëZ»¸ÊžCYFMÇÌáêß›Úßùc‡ 3t™!Íö´=©¥"%ÄÉDG³ØÊž‡ò~#K7€2–  欸‡·\ÎhÈ‹ñ²2lX¿Hÿ‚Q¦¡gæ†Q¶(c+$˜ûZ+ Ržüfcrx׎­[éé_kÖ¬ý×zZeqÅýFÊñ9e @°±’Y˜;ëK3cB|œÏÝ+·‡uÓ"ÇTØÊBÒªÚ¦v^
-p0n\Ì°{­§e`ãSÀžwõ ‹Ï(c)ˆfOŽvÔ•¼~ñà†ãé#ê²"Ü,ô‹‚q/:¦-[¥vk°õ¼™ZXÕÚPÆN Êž™ú8ÜÛüáMâã;îVÇ(Ið³o^ ÌèŒÚÄÊ#*·OßüÒÍÐجR€2¦WöôäØ@{mqÆÿ¾æ`¦£&#ÌÅLO³ŽòWFf#‡ ¤²¦ñ…+÷ž¦T¶ö
-.l:&NhFé˜9\ûýŒâÚŽAå¿
-óh_kUAÊÓ€+Œ7¦fÝ‹WL^Ãà¼ëíð„Ü÷M=çf>”±æi"a¨³þ]fL°·2¦(÷¯9÷Ú¥ibͨ´·ÕmýcŸ€{a+0ÌS#½Íy‰ý¡1µ_qþ…º3·ˆ,ä^Î>!Ȍˆ3À½0•/_þú<=9>ØŽ){SmÈ¿¶Pè_pa¯§e`ãƒÜ똕Ç݈äüÊ–¾Q0£0ò˜*ωóu9‡ø ê_6ä^[¥T´Mí!÷zUR‡º@[!©ªÂÔgˆíÜ.À¾™¢ÊFÝ‹…GTnŸþ9ß9÷(c,¤15ÔU_
-ù× ÇSºj2BœU6Z؛ٶï<d|á·€g©…Õmˆ{”1fÈ¿Z`ÿò»la¨!/ÊËJIe϶Œšî)Gïà˜ÌÒzà^Ø̬µ×¥GzÚšhî’à ¤²ÑÂfå•×0´¸ì÷81¯¢¥w¸&ƒú¾»±,;6ÔÇéÌua.æïW6©°9$wišØzF¥Õ î
-{Aýk´¯µ2?鉿»¥NAŒ’Ê&6—°Œú‘3N>¡±ÙeÝxȽ>”±Ø¿¦‰ãƒµÅÑA^¶'ï’¤ ²g [Lgdéîÿ$)¿²µoô(ll‚ªìážÆòì¸Ð›ÎPeËRPÙó
-ûð [¯ èŒâZ¨°‰ÓÀ½0È¿àÊî‡*;™òÊ&¶,TØÎ>¡qÙå=ÃPa”1¸²gˆ„¡Î:¤²íàÊþ¾eÿ³°“¡ÂîG
- ŒÉÌVvSyÅ•ýµÂî"g@ac4K©lRasÎ6(l¬‡\Ùó,û{•6 ¨<ÎÐòòÄ°Aac:³l'¨²e„9™¾YÙHa3°óoWÖ4±ñ ŒJ†õÌ«ì¤'w.[âäEyX¾UÙkÖ@…½‘‘SHZU÷”£wHlVYc7(lL®ìiâø`GmqzT §‰¦òv~v¤²<ËëhèY¸·Éí38ïæ÷(1¯¢¥6¶CªìîƲ¬ØoÇSºªÒBœŒ¡Ê^à0#g™O\éàqë+‘iE5 °±Re÷µTä%>òs;o°On7 =ͺ…3iG îØ­eêpýÁ‹ÌÒ†n<(ll‡\Ù5Ei‘W¬TçccXð0#g™Ž‰KXV]ïì¥[a ¹š{G& Â¯Œá •ïn(Í|ñ຃©Ö-õ³ ï(#Kwÿˆä‚ª¶±IPØØôÊÿùsb¤·ùCnBØ­KgõÔe…¹-õÕÃL:Ëü;C;*:£¸®sˆ@œù PÆrÊžh«*HŽðw·4B¶Ô‚‡>Ë9¥T´Í.ÞŽvTÏðÄ(lŒzå"a¨³®8#ÚRƇvJð/x˜çÎòž£æ.¾á/߀õ¸²§&†{ -|㢙¶Š” ãƯfòYVÀYyÜ}šRXÝ>0Î2æ3·¥Þ¼ ÷u1?ºç‡yÞY6±ñ
-Š~UR×…'€…ù ‡y| ½º0åé]+#œÂ‡>Ë´›IgÙ;$6»>Ë`Ga?È–"à»êJ^EyÙ˜|ã0ÿ—ýú~ªúJÃ
-(KèM%Ë‚tDMPÑXè ÒÁÂJ¤ƒzïÒAPÔ09ß{¯”ËMtÚ=“óügæ3sžç]WË%¨–! á–Å\]H,ffzrż\Ëò‡Œˆµ<‚j†¬sÉ׊™XËü –M.^¿Ÿ[ÑÔ3Šj†‹y„XÌƇäÿ¬˜¿Ô²´Šž•k@tJ~ªeXB(æÑž¦ŠÜû×/:™‚bæ'[ÌøZƱòˆÊªØy…Äg×¢Z†%ËÅ\•Ÿàj©§"M¾˜A-SÑ2°mݱûà‰Óç"nç”5t£Z†#ËÅ\[œâeg &+ÂÊ[_ÌØø¢cdçß{ä{Gßk÷ž–¿µ<¿ø }Øÿÿ!ópgCYNRĹÓ'ìÞ±•–Š´˜ñµÌÄ)(¥¬cáâù0¯²¹olfÕ2Ê‹ó ˜ß”?½wÍ×ñû#{ÅùØéÖÍ/LÇÂ-,£zÔÆ#(6½°¦m`Õ2$ÁŠyaf¬¯¹2/9ÒÿŒ¹Ž’¤
-²,ŸRåà”úÉÑäð±m[h×*o¢gâ”Vѳr ŒI+¬i@‡\ÁŸR3à”zõìÁKN¦š
-â|쌴«N)¼23—°ŒêQàøŒâºöÁ tHAâ)ÕßR•ÿ0ÊÿŒ¹¶’„
-RPJÍM ¶×¥Ç¹[ëï“âb¦ß´Vy3ïöÝŽŸ>qçñ‹×Ý#Óóè‚)xåw“Cu%™·B¼líß)ÌMªLËÀ¶u‡ü!ã~¼z÷ÉË7=£Ó H¦”§†;J³ÃÎڨɊð°à0eŠUÊ[¶‰í9lâè{í^nESïÛ™…H¦
-ú|MyQYÝð¤OxRNR†1_”ëŠÿDy# Ž…GDVÝÐþlxbvYCç0R†._WÞ„cá‘U3°÷KÌ.Å”ß!eÈò×Êÿ * ïÜÌÎ;,!‹ ü)ÕåŒø`²ÊôÌxe[¯Ð[Y¥õÓH¶|“2—ðNÕ£¶^!·2Kê;†2t!*·å¸`kL™s½² PöDÊf‰œ2=5eÏøÌâ:¤ _ʃíµEéqAîÖz*Ò‚d•÷éÛxÇg eƒWž#(ǺYé©H‘*ScÊÒûô­=‚ã€r;Q1àüñ=Pn«)L‹ t³ÔUÆ”éÖ*3q
-JïÓ³vŠK/ªmœ@Êeié3Ph­.HpµÐQ–à
-i¡¢„*”Р R¨¨H$dÎPdž·qo´E½§užaÛ¶©ã}zžµ¾×¿p­ûs}îÓm7Ðæ¡où?ó˜Ø‰*hZ;û܈{õñKkïàhW[¦»›*?¤Þ¿âíh©¯.+ÄÃÊHßò&6^yÍ ;œÎ^M/¨né[þ´ŒŸüúûç…Dl¬È}ìyÐbšŒ7ajË<ÂK4 ¬¾úðe^Us7yd ´Œ£ü‚ZýFêjø”“}ùäs]UiA.ÂÂ?çѵÌÈÊ#$«®¿ÍÁ;ä~ê‡Ê&"™ZÆSЖ:ë˲Ÿß½tbßeR‹9Yà–ÿ˜h™À-(£¦gaï“ò¾¢±‹Dû ZÆO–‡û;êJ³žÞ pÛ³yíRIÉ-Ïûs!k±´ª®ù~À¨¤œòúÎo£PË f¼äׯÿþø>Ü×^[œ™îwÔÎdµ²?3­å?à–Y8¤T´Íöº_Œ|ö®¬®£´Œ« -õµÕe<¹}ÞÕÖh•¢8;3ÃüÉ-sðK,]cºû˜xâÛ’¯íýÃßÁ+…£@Ô‘ÁÞÖ/_ÇÝðu¶6ÔR]ÄÎ4¹ef>q¥UÆ»Ž\¸ŸQTÓÖ7ZÆS –ÇF{ZªóÓc¯ŸqÚ±AS^„—‰öHÁ-Ïg`f_$¦ ehíì{3îõÇ/­½ƒ#?€dã'pËrwSUÞËWO9Z­×X"ÌÃÊHß2$ÙLl¼"òšv8½›^PÝÒ38^)Rì1
-©«±"7%&Øó Åºå²BÜÓ[få^¢±ÞÊñÔÕ/󪚺Éà•ÂQGêÛ@g}yNRT Ç~s]UiA.ã‚y“Zfdå’]¾Îâ gpLJnEc ´Œ£ -÷wÔ•f=½sÑ}ïfmIN–…Ôò¼Œ.AiU]óýQIÙåõà•ÂQ¨T{mqfB˜ßQ;“5Êü, t-£¯§€¤Šöæ½îwžf•~í
-|1O‘¯™ô«è^ò •¯®†OïSbB¼,õÇåkzËtúuÖ¯F" è.‚ÌòpG]é»g‘—Nì7×U•žA¾èõKo«ýÉËQI9åõ@¿px–¿õ¶Õećùµ3]³Tr&ùšÐ/íÍ{Ý"ß–Ô¶÷ ýÂC –ÇF Y®.Hêãl½i¥¢ß òEÓ/q¥ÕÆ»\ÏßzüºðK+f\„:ËÄÆŠÜ÷¯œr´2Ðáecš&_4ý•_±q‡Ó™ëÓòªšºÉ`˜qÚ,—e?¿èq`«žšŒ7ëôY¦ê+ðuým^Á1)ï?5taÆCh³\œ™îl·éZ•YfÕ/— ´ªÎ–}Ç/F>Í*ýÚÞ7 †ûAg¹§¥ºàÕ£¾Î6Ð,‹Ï<ËÔaæä—P^c²ëÈ…ÛOÞ@ÃÜ †û¡Îru–YhÎ6ËHËð0‹)hmÜétf"fìgb–ßýÛ,S‡™ 惞ÁÑÉ9åõð0dc;ð,À³\”æGe.ÂL³L7ÌËtÌöºD$¾-©m3¦{ŒBîn®ÎOêãlý»Y¦æÕƶ®çnƽúø¹¥ ëAgy «áÓû”{!ÞŽV³Ï2m˜EåWlØþ×é«÷Ss+»Hà—ÂxÐ?ª¯ýkiÖ³ÈK'ö›ëþf–©ÃÌÊ#$»\oëÀ»Ïß•Öuôdc;È5ØÓú¥ðÍ“ÛŽÚ™¬Y*)ÀÉ2ó,ÃÃ<æÅR*kMwó ‹‡©ðKa;°)$bSeÞˇ×Î8í4ÔR›}–ÑafæàW\ih}ù¥*›ˆ$
-@6–3ñGe'Eyܦ¯¾DxöYFÍÀÄÆ+‚þRAÑIÙåõ(²A˘
-ìÞVèJ÷wÛc¦½LZ‹0Û,S)Vnô—róOÈ,ªiëÈÆrhÀ®ÊO‹…þ(£UJâü³Îò¿”Ò*#ŸÐGéùU0²Ç
-÷ʼs)ÈÛi£©¦¼8a^…U6ð/19 £õÛ=ODÍú×ßðʬ“9îu#üØÞ-« ÔdDø¹8æƒ23ð/½U›Ý~Aü«¸ó/8¦X'˜{ ÷¶T"î|ÈÅf…¶¢„ïü
-™ ø—àà_;½O_º“ùöSS÷Ð×o°²Y'Êãƒõ^§cîe¤.+†¹×|®Œû—¨Œš!ð¯cá7RsÞ׶ŒMÂ1Å:ÁgT_kջ箜õCÜKEj¾îEªl²
-Ž¹÷´ ²¹gú …4£ËóþºuñÔV& p/¬²qÿR7Zç°?02!íUY]'S,òŒª)É~vÄÝÞR_UzþîE†ø—²®…ݿиûpL±X@aÿ†Î¨ṳ̈è3wýÓLKQBpÞî…ÁŒøA\^Ódƒ£×ɹc
-ž™ùAÜkzrt ½ö=˜Q[×.•]€{á0ÿ‘VÕ·DÆÔÕ”ÅÕ­}#D3ku¯¯Ã=ÍOÁŒòu±1×Q’œÿŒ¢¨l^lL9yý~;#¯¼ÂÌ*ÁPè¨+ËMKˆÄf”˜Qów/¬²ÿBÆ”Áê-ÿ:ÿèeIM[ÿ(qÂÌ!£\Yø,9.äð; ]e0£¸„òì˜RÔ^a½ËçLtÒ“üOMÝC_!Ì,
-”_¥'^8áé¸ÁŸQl @yvLÉ.]¾v›G@ÄÔœ÷5íf–ŽroË碬ûWÎú¹nZ¥§²ÀE3Ÿ¤’Ž¹Ë¡à˜»O * Ì,
-”_§ÿuÒk‡•‰¦ÂAÎ63'7ALNÝhþãço>Î)­…0³@fþe·Í–úª(Ê r/2Ì\|ÂRʺ+mwûÇÜÃ`†šÍìÌÌügÊvX™.(/pF‘`fcçä!ˆËk¯ß¾…ùÌ#Ä)33óQV“åçþ ”Q˜9¸øE¤Tt-ìvŸhvc÷—ñI33Ž<e§?2VÙœ<‚âòšÌ@³KjÚúF& ÌL ¢^ß¿}êi®,¤Êؘ0K£0û_NÊ|[ÞÐafj¾ž&Ž"(¿JOD ,ñ³(ãc
-|f‡}×eWµ’`†gfFÈ(7U>KŽ;ë,ð“(Ï >óJ[—Cg¢ïdä}¬ï›œþv6s‚ªq´¿½¶47-á /j›f‚¸œ†ÑÚm{†]KyñîsKïðW¸¦˜åÉñ/ÝŸòŸÞ‹ õsÝ´J_õç¿ò,Ì|ÂRÊ:æÖ»ý~ëÏ×eu£Äi(`̶¢&FúÚªKrRoFz:n0¡e’fÄd—®Ùâþ˹+÷³
-+›{† ÌÌ ¾¢»Êßf&]öÝmg¡§*-B Ê(ÌlÌ’JÚfœŒJLË-­mï!BcBpõîm©*Î~x="`ŸÃzcMy*QFÎÌÆÎÉ- &«f`¹ÙÕ/4öÞÓüOÈš‚ÆøWTý‡7ݾtÆÇÙv¥®Š”?5(/ÂŒWHBq™©•£gàù›©/Kª±5;›ÁÁPên®,zþàêoG÷n]k¤!'Nà¡eLÀ
-³õòضÎXSêE 3']S›ö¾|'ã͇ºŽQ"ìl†…Ü×­Õ%9€zí²6×AV7ÕêE‚™ À,$¡°Ì°°k²
-+šº¿ŒÃÎfTH}=ÐQÿ1/3)&ÄQ/uZ¬(Š3£k
-°]Þ§¢ÓrJª[ag3.³}]Yø<%><`ÿvD½$i±¢ÈgÆÖ*`ön~!1I™yëag3,}ý>7-ñbÐAg[ =’zÑeJÓ2µrÜŸò¼¨v6£2·¯ïƆú»oY»\Cž¤^´¹2.`¢2ªz¶Î>AÿHË};›AA<=9†ôuèëˆãû­Ì´•¤„ùi×׋0C:{‰¼†ÑÚ-îþ¡±wag3,ÈSžÓ×>.¶új2bN©ùÌìÜü"RJÚf÷ uöØä4<3}ƒ>婉‘þŽºä¾6}-Ä»˜ƒv}žíl‚˜¬š¾…‹Ïé9 _3ƒõ5ql°«±¢0 ø5èëfÚÊR"üÜìl´<2ÚÙ‹y…–(Ptö‡ºŽþ‘‰)øšé˜|D õ´T•ä<ÆýZ_M–æ}™ ële¬³ggV4v ŽagÓ13?Ð5Ò×^[ö&#)&ÄßÔ×4U/Ò™):ÛÖù`ÐÅÄÇ9%U-=Cøœ‚g¦G§ŒŽ¨Î†OÏî_ Àýš}^0.ÐÙ¨g»ù‡Ä$e¼)«mﯙn™QÍŸße§Þ¼pÊôµÞ¬_Óúʳ <ÛÊq_@صûÏòË:F'¦þ _3=B~ÊȈzõçíèàîök–ã~Mû¾^„ ÚÙ2jz6ÎÞ§.Ü|”ýÌ)øšéäÈß¿ÕFÔ“{qçŽz8l0ÕR’¦O_ãgfG=[^cù{×ÿFßJs
-{ÍðÌ´ö”'Ч FÔõó^;­ÍuUeDèÔ×Ø™ÑΖTÒ2Ýààqô\2§jÛú†ñ× ÏLË`O™ˆ>åâ—`DöÙ½ÉÒP]N\^}]ílQU]sk'¯ÀˆøYåõý#SðÌ4n^ä§|9Äß}ë:cME a>.zõ5~f¤³ÅåÔ ,7¹øE%¤fU6v ŽÁ™@Ѧ]f°§üu¸¯­yÊW~;¶QÊÒ¢Üh_ÓéÈxgsñ K(jƒ9å ^sNIUs÷—1â|Í4 vä)`^ åYâÏŸ8°Óf¥žš¬w1Ýž2~f¤³¹D¥•ÁœÚîqällRÆëÒšÖ^h`´ÍÌàÈÄÑÁ®ÆÊ¢ìÔ„¨ ð”Wªƒ…ô5]Œ½æż`N©ê­´vò<~5ùIÞ‡:ÔÀ¦ák¦UЧ<…šWUINú­è`?7ð”—)"#Š“ž}Ÿél0§Ô VÙ9{ŸŒ¼ž‚mš…l^=­5¥¯3’bÏñØne¦£"M×Eqæ°±s"sJQÓhýžC§/&¤¾(ªhì…¢M£àG&™WòÕðãžNÖ zÊØ•ñ9%"¥¤e²~›;b`i9ÅŸ›pцg¦:$½éo¯/ÏÏJ¹yÒÛÙÎÒ€1O™âÌ‹yÀkVÑYØÙ/·œ÷>Ž_ÏCê$*4¤!£a59h³íˆT•»ŒBq”†Nr¤!#F¤¡êNIu“¢ÒÞ{ï!ÇÃéz¾ßßïw¯êxΓëªû¾û½ÿ…×õy¿_ßИ—YU¨hÃ
-Çü#Á t·Õ—åe$DyŸ;ºW_CIf‚F™‚L3¯à‚ň9¸Ü‹KÍ.ªnBDÇüc!ÿP=m ù¤¤È;Wœm÷oߨ"')Ì71£ŒaÓL10mc«Óžž¥å×ÿ)óøƒAìm‡zrõ4¯µË‰
-ðÌœQÆ(#Ó L\FQ]oÏ'¯›ã_¿/©méB1ã˜Ç
-æ\¼¢Ò+€h<áþ©$RÀÜÝ?„c_†á(CÈ]-µ%ð‡ºyé,ÐkMeÙ 6/
-fÔÀxÅ€hoÝeaçêM$å—×ã˜Ç2ä~
-99&ÌßÝ‘`¬£®(#!~(ŽI„ŒŠ6ÀÌ+
-0kBÌqÌÿ w}Eíš ÙÃÑÊDW]IVR˜@ž ½…yÖß`þ„cþ'!Cî/ò)kS] eYI~䇚 ½™ì2æ¯8æÿò—Ï
-Y@àeÈßÅ\×ÚÕ‡cþ~†É{;›kKóà EyC@3|¨ˆ¤ü²º€ùóóßù ¾~èíh®)ÉÍLŠ/”#-dv†€<6fˆ9¯¬¶¥³oÃŒsòþèioª.þ‘ [ÑBžÌŠ.cb&’rKk›;{QÌø9„ äz¨¿§½±ª(çõ‹È`_7‚‰®ãA ³£»_htRfnIMSGÏÀþQòÕßÝÖPY˜qÇÇÕž`̘G`V˜­
-ñq32d
-f.~a‰e«×mþuß‘Óî¾Áã_½+@Æomš¶îi“œÿ6åéÃ[W.ØLô~Q]¾Xlþ\ Å<}Ä,$¾tåÚM{¬\}‚>My›_ZÓÔÞZ{j»6µ­»Û«Š?d¡\Ÿ?a±[GCE^zÁ¼¹³237ŸÐÂÅ+T5õŒt¾|ã~,çâªÆ¶î¾)íÚôm]_Q˜“¼ëÚïçl‚JIn‘¨àœÙœ ™Œ™c&÷ÜybÒòÊ@µÍƒ] ‹NLÏ)¬¨§mí©Ç™|ÈH[×–åg½Šìçqæè~íëd¤
-U ­f‡/ ç d»ª¡µ³w€uk›ÌrOGK}%XäÔøÈÐk—œí¬öþºuƒ’¼´˜?SN25”qæáŸ/&-§¸~³‰åñsžðœl”×6µwC cIÛ¦ 2RÖÕeá"‡]õp²1ß­·qíj´­á$Oc¾I¦Žó48ÎÜsk/¦½sÿxÎaQñ©os‹+ë[€… ~f½yF1k¤¬+
-ß“Rž?½æílo½oÇ6u•åKÄ[SÚši!cã Z¸6¿ÐÂÅòJë·˜Z‚uö½›”žý±¬º‘¦¶Y†3Ý ƒ²Ö••–sÿ–‡“­…±>Ð.ÙEbó[3w[“ƒµ6<çyàœW­ÑÔçìàâúøùKÒûBPÛжYiž)Œá ³FÊš÷(8À ²¡–ºê
- ]‚s€[3y[“ƒ¶6*aÂØ9›XÚ:y\½u?&XXq%´mlžY€3-ãÞζÆÚr¤¬#ïÞøÃí´9<dE9¨]¼³8™¿­ÉÁZ›“‹‡oÞ)xÎÚ;ͬí½‚ÅÓßå—V7 ó<ÄóLǸ«­©®¢8÷í«„˜A¾žçNöî
-¯ç¡×tç)èOŒÛÉ5åE9I1¡~îŽ6¦†:êʲbdîidbèqæ ¶³ ¨”‚ªæÁã§Î;{E%¤=y_U­Ýè CÎ>NÁâŸh<ˆŒkË‹r3“cÃo^»dki|`¯Ê¶­pZOË 3!Î«× ‰É*«ë˜Z;¸ù†Üõ\PF"Æá›jÅMO0nª­(~ö(%."ðú•sPÖZjŠÒ›7ÂiÍäi†Ì`ÆÛù§¥+×
-ˆlݦ¢qÀÈâìÅ«þa°žsŠÊkÈí(ÏèÃßUS%ÐŒüÀêjºq|d·‹½•‰*kñMë׬€Óyºµ5cXq^¸x9\a¨¶õýjwÙóæ­ØäÌ\äÜÎC£oÆÞ"ç)hfUxÿîwvã¬Ôø¨`7³#º¨¬yW-[4ƒL fþ;Š3çOKWÀ&&«´[ûÐ ´ž#âRå#ç®>Ú«ä<Š{œYÕþ
-R¨¬¹sqΛž™}èµ=—cÁÂEËVò
-KÈÿ¼GÇÐÔÊÞÅ;(2œ‹ÊH­½ÔAXЊû{„fV5ZÇco°qÜ\ÐÕÈØ×Ý錅±¾¦š¢ \Ö¨¬œ?̓L`þŠó<N.\Û¢’ÛTöê1³qpõ çÌœÂ—Õ -=zq¿ÿ^¡ÙbüþÝÛ±×#¯h}]mðí„Œã±ñYË£úZ»”dÅ…á²&ÊúŸÿ˜æA&†g¸ÂàÚæ߸EZAuŸž±ÅG7Ÿ È¸äŒì‚Òªº&8Ä ¸f4÷÷Í"†ãu<Híílm$Á·Ü\ÐÕØø Ö.e9 aXÈËe=ýƒL Ž3\a е½fý&1E5M}äìê›ôàIþ‹ŠzqèoMã¦Æ1FUMééh®¯~Y˜“‰Ý°±ön0äãá^ÂÅ9CÊš9s˜µÖóªµÂâ²J»´ÀÙÆÁÅ+ <&1-ëYQYu}3[ Ð߸ºÇĸ©ÿx‹bŒ«º©®²´ ;#9.Ý\ ã­"1^È3¤¬YC¯múz^Å+("!§ŒœÍ­í=o„ÞNHɄ⮬%ã@ã = ú[H3‹£¦A1îli¬)‘ÿäARlD7ÜÕlÆ«¹áèZÀÁ¸¬gòŒÚF뙓k 7ᬩgdfuþ²‡op$*î¼ârRºF47Íêî¯)Íb:1jêA*Ž1TunVZbLx€—‹ƒÜ\ ctX/@ y&•5kæ0×3>𳬒Ú>]C““¶NnÞáÑ¿¥>ÊA† ÝÍ=úëI³÷4“xxÖ×ÝÑÒPSñâ9TuüíОÎöÖæÆúèæúÄxF•5kpm£õŒÏ0îÕ|‚Ââ2Š;5t³<Åíwÿá“<ØÐuMm½úw|Œ}-éññOB »÷÷t´’k+K!Æé÷îFûz\>oej¤§‰îjºñB6ã‰üûzÆÎ<|ÂbÒÛw¨kë›Y»äîx+æ·ÔÌìç/ÊIõ
-¿—”–ùäYŠtCSk!=4 &¬›©ÍðþsÖú›鼄/F¤Qú@¸BŒz:÷ñÔÄبÐ@ßkÎŽv§ÍŽúb¼MJLXõÊe‹¹~œÏ1küçC8ô\´ ¡¸W¬æØ´y«œâÎ=Úz†Ç-­Ï9¹\ó ‹º›Ý ‘.-¯FÒí]=}PãT#k„ÍÔÆÞYæXù3ý~é"^†ï(¦ö÷vw¶µëkªÊK
-ó §ï'ÄD„Üôñ@ÄæÇ hîVQ£ª^ºh!ç|Ž¹³ÆŸ¦3ZШ¸¹q Å¤ä•U5öë9aimçèìá}ó_ÑñI©stUM=¹¹­¨û©´A°¡cÓµ‘7§›cuúOø·è1z ë/á;48@£ àö–¦†:ReYIa~Î㇩÷âî„û{¹_¶·ÅÄZ{v*ÉInÙ$È¿f%T5œÕ³Æ_˜9¬âÆ7
-4|Z‰JHoûYMc?$Úü´­ýe·ë~¡‘1 IiHº°¤¬’TÛ@nnmïìkÈ5 Úˆ{ À9˜3Õ Uäú=„w
-‹ŒŽOLNÏÈzš›_X\ZVQEª­ì–¶v¬ÝÛ×ßO¡P¨Tm
-4jn€—’WTÙµw¿ÞacK+[ûK.^þÿa·¾žšÊÃ0Žî¸ã:t)Iè„ÞÄ
-Ç;%M”“_TRQ Ô7nvýÖ#cSÓ3³s  ½òìù‹—¯^¯®¾Y[{ûÐ? u¨~
-/åŠ#Bý¼¹ lÇdÐLôu5ÕɪJŠò²ûˆ ãƒ%µZV^QI…¬¦©M506£ƒ4ÇÙëí&E˜.ÊÎÍìÒŠªšºú†¦æ–Ömí7;:»º{zoÝî¿3844<<<44x§ÿö­Þžî®ÎŽ›ím7Z[š›êëjª*J‹‹
-òs³EéÂäDäÇ÷ôpqd³l43c4b5’
-ŒXVêŸñKMÌø+Û _Ýø¤Éêš:HšÆ°aÙ;º¸s½|üƒBÃ"q‰I©€•“—_Px¹¸¤¬¼¢²êjuMm]ýµÆ¦ë Žjm¹ÞÔx­¾®¶¦újUeEyYIñå‚ü¼œ,àMMJˆD†…À‚==\Ù¶L+ºl˜ª­©FFÔèNï%&Œ¿ºýÐhÒbimª¾‘©9Ý
-¨9N®î\ß/ 84<2:6>19Ux.ý¼(3+ûBNnÞÅK…EWŠKJËÊËËËJKŠ¯\º˜—›s!;+St>ýœ0591>6:2<48À—Ï;åæ‚[Z˜èéji a|ÄØÆ5Aü/¶ cû¤Õ4´tô ŒLÌi–VL–ÇÑÙÕƒËóöõ  ŒÄÄÆÅÅ'$&%§œ¦¥gˆD™™™"QFzšðlJrRbB|\\lŒ *2<,48Ðß×›ÇõpuvdÛ±lЂ°¶¦:…¤¢tB{o˜ >Œ¤$'-!­¤B¨©ÈÚ̂ΰl¶ƒ“‹›û).Ï‹Ï÷ñõó
-9-ÄÑQ‘a§C‚ƒüý|}ø|/÷”»›‹“ð2­t 3£`åp¥Ñ†‘°ä&ˆ£ÝIïHKËÊaÔªdŠº†–¶®ž¾!`£e[3O²ììÙG'gW7w®'ÏË›òöâyr=ÜÝ\]œ8l{;ÖI¦µ•%Íx õ©à 'zXFR˜ñá'õwi1µ"²†]#l´lC#cS3s nÉ`XYÛ0Y¶ Îæp8lPµe1m¬­ K:faanfjldh GÕÑÖÒP_Uet¢w€±ñ®0A|øí‘ƩѪedÅØÊpÄ) ­ Ü:ººTªž¾>B7u`‡ÌAÕ±êëëQ©ºº:€« º4_¥Š¸ï^`Bø¿oWz›·ÞÅFÚ*ªª$™L¡¨!tLØ©"V5
-…L&‘TUU.΋í|÷Âߨmiœ³†]#lX¶´ŒŒ¬œœœ¼¼¼‚‚‚"†.V'‰U1VEøž'ee°õÿùÂ?í&„¿]8À¶5¶k ´1n
-q
-/GS0 gs
-486 0 0 480 6513.1162109 7207.1401367 cm
-/Im0 Do
-Q
- endstream endobj 868 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 869 0 obj <</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 486>>/Filter/FlateDecode/Height 480/Intent/RelativeColorimetric/Length 25812/Name/X/Subtype/Image/Type/XObject/Width 486>>stream
-H‰ì×y<ÔûÇñswk)a,c[ckìKö]R‘%…e)¡B…TÔ¡ÅVɉŠD*Ê–¥È–%Ù[,ÙJÛ­{»Û¹Ÿïï7ÃتóxtÛ¹¿×Ÿóûý13ÏÇûû›ùî;"""¢ÿå~ó³›ïwLôåM•ûíEp;M³ý½ß&Æ}ÓÁçûãMk†.ÒûôGzš#Æutï„8³ö|2",&_œÇA– |ýù“¡;àF÷f`Ôó³/6^´Z„‹dYYÙØÙ988¢ÍvnbggceE∛×Ƭ êy!Lß/Z/â]66v ]Ĺ˜‹‹›‡‡DâEñÍv‰DâááæâZ̹h€³³±±"m´íÔóý¡ÿ¯š¦c¾Øv1]n/¿
-5!ýK5E†ã™•Î‹t®ET\Br‰ÔÒe2²²TªÜ'¢Reee–-•^")!.&JAÞH°që)Ô„ô/Ð,Â80òåã'
-‹€.••“WT¢)«¨©khjjiiÏ™––¦¦†ºšŠ2MIQAŽ
-ÞR’b¢"B‚|¼<Ü‹9'©áYÍ$=ßßů´Y„ÙØ1`د 0EL\Rj© U^q¹²ªš†–Ž®ž‘±©™¹…¥•ÕÊ9³²²´073562Ð×]¡­©¡¦B[®(O•lq°&ó3¨™¥‰IÿwbO³bÂÜ$ ùÊÊ)()«ªƒ®¾¡±©…•µÍšµvöŽNœ]\]]Ýf .¸8oprt°_g»ÆfÕJKsScC}]mMuš’<u™ô5:ÀÙÙpi8½‰Iõ˜‰æDÂd!1 ©e²òJ4U ­ú†&f–  ¸\ÜÜ7{nñöñÝáçïíœz5Àßßo‡¯÷V/M]ì{¥…™±žŽ¦š²’N- 8‰›YzrÒóýõüšJŒžÃø†ÉB1Ii9E
-œÝ
- aѲÒA~vrƒþ\“3f£“ZPD\ZVQy‚Ø70$<êè§SÏgfå^¿UR^US×Øü°½³çñSðF¼ã¯.ȾÿáÇ¿Bûdè¸ñý{$Ž¼‘öèÈ0X?{ÒÛÕÑÖÚÔP[]y§¸0ÿ*l:åäñÑaÁ¾^ì
-Êšz&Vk\=}éÄ™W®—ß«mlFÂÏúaÂ/À˜ï¤..øï/Œ œY#êWã/ÆF†A6ÝÔp¿êNQA^vFZò‰Øèð€m.ö«-áä¦É/“äçƒþZÎS›fÌK†§1u¹šŽ¡¹Í:çÍÛB£qâüÂ’
-qk{W/Úð(ž
-¦òý²_BœÂšŠšFìÝÞÒXÿ´ªüaAvzr|LxŸçÅßÏ’LŽêhB å×®EâþΙñócAq)…m* ŽŸ²²wöð
-‰OF)Fˆ›Z;ºûÈCfâûwéþOÜŒ¨i¤É ïÖ¦çµU
-èò’ü¬´»·#C¼\Î[“LŽÒPÙ¶IFJ\DP
-ò§Œwªi“l/ùݼ•˜–UPú¸¦®u1#âoIøcÒŸ€noixV]Q’Ÿ™’s#ÐÛÍÁÚ츾¶p–Fœq?£j›50滚ÊXý€‰¹í¿àȸäôÜâGPÆÍí]}Q3!þf„™H‘¦†1ÖÕ†Ì]¾{;⺯»£ ÉØ@[m'Áïíé\Ó¦rœ¶ YOÆ5ý_?͆¿ñi['OÿÐèø”Ìü‡kê[;{È̈'†0#iÐ0ƆÈý=­ uÕå%y’ã¢Bü=œlÍM ¨ïDÞAœùx€3gi›"k`ÌÇ‹ ÍE0¶sò ‹¹“šUXVY 1Sçé5]Ô‹˜úß4Ðhuƒ¹q —€¸£C<lOgäí5â"KòÏâãAõ<…S8cÆHÖÜ<|³ø.G»Z 3vƌӲ‹@Õ 0¸SO*ÄÄQAãŽ&Ì nyQ âÎJ½Fá ý¬(+%¾|É‚¹3ygpŽ¶i²žÁ;sî‚%ËÅ¥daWcÆ—qbZNqyu}S[5ÆxQO&ÄÄÑý'aîÁÞÎÖƺªò¢ì4
-gsc}mUåM2’¢B‹ÑÜæmÓe†õb!QI™MʪÚúÆæ˜q,f\SßÔÞÝOþ$ÆÍõ“c47zd‹»¾º¼8‡ÎYOkïv…u«W.[4ªm63uY#YÏ[´låêu
-Û÷jé›Û¢>ŽM¼ŸS\QSߌSb<iG7744ww{S} ✈9ÛŽÖTQÚ -õLh›½ãL 2’5²„ô%ÍÃÇH6xsŒŸ7·÷ô"UãOjÄøÄ97××Tà<‡ú{8ZŸ2ÔÑصYVJLi›g:[¯0êêšÎƒd-,&%»y—†Žá)kGÿPØ\ŒŒ‘ª?°c|Ô@#q£‚îï¡qŽñ»tÞÒôè5åM둶ùgñÎàÆ+Œ13y?’õúMÊjŽšZž¿äMg<@0¦©z¢ ~ÙÑľV4ΰÃR¢®û¸Ú[˜èï߃´ k­0ˆ3æ<ÑT¾òQ­.XÖHÖ{öë›XØ»ú\JHÍ.*G}ÌÀ˜EbL?š¸8W—e¥ÄEz9Û’Œt‘¶am Ì›ÍÇžqfò<XÖHÖºF$[g¯Àˆ¸”¬"ø;Ñ]Me<ÑÜþîÑÄMãÜT_õ¨ #ùö
-UÁ߉Õãûˆsw{c]ei^zbLˆ¯›½…±ž¦ÊV9)1¡ÅlgÆF† KÉmQÑÔ3¶°wó ‰ILÏ+­¬k„¿0FÇÄ™ÜßÕÖPû¸$'5>2ÈËÙÆÌðúXa+hqžòöˆ31»¦þÈ̓YdÕ:eõC†f6Î^A‘ñ©9%kÚºúÉìÁ#çar_gË‹šòÂL¤mwËZ{”ä׈A;ÃØæšÆ&Ö&l=k:ïL˜Ö¢’äýú',Ü‘¬3 Ëk^´tö‘‡Ù†1:ç·¯G‡^öv4×W•å§'Fû¸Ø‘Žéì#â c›‡ûG¶°6ÅÖÜ3øfχi½Ùˆdç☞_VUßÜÑûrhôõ[öaŒq†½=þþíØèÐ@w[#ÖvÜÍkžŽV' 8C; ðqfukSm ³kîBÁ•?ËnÙ­‰‚ìyõf–uc[÷ÀÐèØÛ÷ãþú‹m£Ãœ?ü9þîÍع¯ k;#)6Ô×õœ9Ž3Œí%óçðñp³ºµ©¶Æÿ'aqé ÛÕšÙ¹ø„ÄÞÍÀ²îê#Œ½y7Ž‚ÌNŒÿC×ö8ªgÐv¬íÜ´„ˆÀËΞ„vÞ*')ºl1ÂX3ÕÖ<|hv­^¯¸ë=“3WoƧå>¬¬kY£Bg+YÓ©žAÛ O+Š2“oQ⬮¼q­¸ðây,nmš­gÎ…Ù%%§´Wûè)›‹ÞÁ1I…O°¬Ù­™¨gª¶;a…•æÝOˆ ¼ìhuB_s÷f<ÂXØÚ¸’˜
-ÛÛf×>Ýã§wKÍ-…ÕÕI•5›2óÑêùõ«¡žvXaDœ]ìÌ ªn“—bik£Jþ'ªd¾Ù –Š¬–Ù¼{¿ÁɳN^×£Q#C{PÙ²™®í±‘Áþ®–çÕeyiñ×<ÎÖØ¡°V[›%1Ó*¶5ØZ^Iõ !ÉÎÕ/ìvJ42d¼¬ÙUÖô£¯mXa(ÎÐÎI1ÁÞÎÖ¦G´TÀÚ°µgó±`9Ó+™_@H\ZaÇ>]ã3ž×"ï¤ç?ªyù'™r + ÅÚ975.Üÿ’½¹ÑAU%y©•‚ çüÄråL… •¼xùªõŠ`kSkgïØä¬â'Ïš:ˆ SV×D#øGÑ65Î OË $F]v´4>¼Oy£´˜Ð¢¹€™•¬MÝ]¸’Wü,»uïCÒ97<»ªŸ·vsH#3EÛç‘AÛu•%Ù÷n…ú\´65ÐÜ¥¸NBX€Ÿ¥6Ú]Sðîš³PPTjÃvuãžQ‰
-+jÛ{_½ÁÌ9ŒÑÑã<4ÐÝö¢°p·sf¿jïÙ‚Ê™•6ÃîZ$$•¬¡w⬲uv š]ýƒœdâhq!÷v4ÁËHŒ
-ô<oq쪒œäŠ¥ ЛúÂ<É9S*™í.a‰uPÉG~³uõ»—–WVf×ð«7ïÆ9§‘™ÇÆöëÑ!4ÂÀÚɱÁW.X™@9oX#
-Œ5¦6m\ãÝ%³e¶!ÉÞýjÄlëŽ^Úìâ´ GÄyü=aØÚ©„ùB9ëÿ²SAZlÞ`0µ'7fú¸ž¿v—’ê¡cPÉAÑI™ÅOêš»úñìâÐ Gaä>dí‚ô„›nv§ŽîßMÙ`“þGE@æšÁ ãz¥¤ü6u]cË WBnÝË!¶5Øšƒƒü_öËÄê|ãOSݤ#GB–CYŠ²—BEc«\EL“ºèdêÊd©I]
--x¬ ),C¥§…kɾŸc_²„á–šç~¿¿ßùsš;óLWg{îý½ÿ„ïëù~Þ¯7¦„Á«ÝZW^Ê9>줷›£©¾–
-TmÁ^Td¢„Ì2õÕk- wG'¦ç½ªn¢³_k~?5?ƒIØ/ã#ƒÝí •ÏåÞ¼xÄc—­¹áJUù%⽨ dÆ‚Rr½ÅÁxW\Rƃ§eµ-}Cÿ÷× üΘkÓ›^¿,¼{+!ò”/Ùeë&£Uj
-Ò‚¼¨‘¥¡»ÞjÇ>¯€s?^Ϧ—×µu ãךìjO¼êëh®.}’—žìï¹{ûfcå`Q‰‚E%˜Ù!kê™Z;ºyŸ »x#ç!µTòà~­ÙÂpí©_Ɔû;[kËž=ÈHŠ =vp½å:lQ "fd’²¦¾™“û÷—Órw5Ò@%ƒký¿Ö¬`®=62ÐÕVW^LÉJª}ÈÕYTæys 3;d-3[çý>A@®ï¾¬nî`V2~­YA®6,ç‘Ážö†
-jÁÏ©ñç¼öí´Ú «¡$')&"p˜?…lnçLö=äúIiMKg?¨dd~?­ …YÎÀÁhUÏåܸ•µ©ž¦ bþ-d²_p̵Œû@®[»úYÞÅ`å  QíÇwÒà¢rw²1Ó×T& æO!oÜúÍÿؤÌ|(×݈wá•ü{Á UíWE÷~Jˆ òÙïlkf ÅÀ,0
-ö dC
-ÃÁÕn©AUÔi_²³9†YPLû÷ Ç¥dP+h=ˆ\ãÞõ‡å<8Ø@XTOïg\‹ ö#»`˜ePAÈsþrvµ.(¦\óû95,Õ膋*?3)6Äÿ
-µ²‘ŽCžY0ÌÀ´iõåOó~ºphÏöM†ZÊrÄ
-
-‡üEA1OŽôw4UQ)‰‘Þûþja´mžU3r¯yA½^k¹ÃíÈ©è¤ì‡/ª[º
-§L™•)Cežç™mÚÉv¸Õºï°mι©mXö{W¿ç?xßßú<ßÏãtV_CQ‚Ÿƒêì0’z±ó‰ÉïÕ;}ñZPdjAì×舢öø2×٠幉Oîýf{R[MV„‡•a¥ FQ/ai•Ã&Öw%ä”ÕwÂ~ úzµBî춚’×1¡·\-ŽT–„Œ~el¾zá Ì}‚ŸgU·ö€¾^Í MÀ÷4W¤F]»xZo¯œßJ Ø|õÒ1µó xš”÷¾©O˜DüšÚ_ÿ£íìOcƒõeÙ îxX#ÆL¿a`FÕ‹Ži‹ ¬^–n¾c3ßÕ¶À} P^Å:{¤¯µº(ãùogs Eq~öQ/Z6^Q9T½¢R *[z‡?g€z­j`˜§' øî¦÷o’"S•áFÖÔÚåÁLZQÌœBR»5IêÕÐ98> Ôk•à wö@{mI&"`FvnزkŠ¼¢Ä4ôÏ:y?€Õ« ¨5 Ø ñãposeAJdàU‡Sºê?o[5… ¼ž^QZ'l®Ü‹Hzƒ¨¨×ª°ÉqHÀJ³ãÃýÜ­~9¤¼c+'Ý2×Ô,ÊüâŠ8Ãs.7Cb^—Ô ôõªgNÀªÞ¦Gÿ~ÝñŒÞ^y1^6Úe  e.aUí“vž÷Ÿ¥äW4÷ êyÕC°¡®ÆòÜ—ýµ69 ­)NfúeÁLFYBi¿‘…«ïø¬ÒúÎA€2•‚Àüç§ÑþöšâWσ}œÍõ5Äù–·¦fQæ‘QÓ1µ¿™Z­¨‘@½¨XÀ¦‰|OÓ‡<xMÙœÐR‘æb¦ß°t˜I(s (»Ý‹Ï.kèŸêE¥ ­©tM¹œ3Ä)Jð/fåMʲj:föWƒ¢Ò
-«ZÁŠ¢fÎFÖTE~ʳûžv¦Úª2Â\,K‡yÊŽY^¾Ÿ£ ­¨Ï
-â|l ´ëZ»¸ÊžCYFMÇÌáêß›Úßùc‡ 3t™!Íö´=©¥"%ÄÉDG³ØÊž‡ò~#K7€2–  欸‡·\ÎhÈ‹ñ²2lX¿Hÿ‚Q¦¡gæ†Q¶(c+$˜ûZ+ Ržüfcrx׎­[éé_kÖ¬ý×zZeqÅýFÊñ9e @°±’Y˜;ëK3cB|œÏÝ+·‡uÓ"ÇTØÊBÒªÚ¦v^
-p0n\Ì°{­§e`ãSÀžwõ ‹Ï(c)ˆfOŽvÔ•¼~ñà†ãé#ê²"Ü,ô‹‚q/:¦-[¥vk°õ¼™ZXÕÚPÆN Êž™ú8ÜÛüáMâã;îVÇ(Ið³o^ ÌèŒÚÄÊ#*·OßüÒÍÐجR€2¦WöôäØ@{mqÆÿ¾æ`¦£&#ÌÅLO³ŽòWFf#‡ ¤²¦ñ…+÷ž¦T¶ö
-.l:&NhFé˜9\ûýŒâÚŽAå¿
-óh_kUAÊÓ€+Œ7¦fÝ‹WL^Ãà¼ëíð„Ü÷M=çf>”±æi"a¨³þ]fL°·2¦(÷¯9÷Ú¥ibͨ´·ÕmýcŸ€{a+0ÌS#½Íy‰ý¡1µ_qþ…º3·ˆ,ä^Î>!Ȍˆ3À½0•/_þú<=9>ØŽ){SmÈ¿¶Pè_pa¯§e`ãƒÜ똕Ç݈äüÊ–¾Q0£0ò˜*ωóu9‡ø ê_6ä^[¥T´Mí!÷zUR‡º@[!©ªÂÔgˆíÜ.À¾™¢ÊFÝ‹…GTnŸþ9ß9÷(c,¤15ÔU_
-ù× ÇSºj2BœU6Z؛ٶï<d|á·€g©…Õmˆ{”1fÈ¿Z`ÿò»la¨!/ÊËJIe϶Œšî)Gïà˜ÌÒzà^Ø̬µ×¥GzÚšhî’à ¤²ÑÂfå•×0´¸ì÷81¯¢¥w¸&ƒú¾»±,;6ÔÇéÌua.æïW6©°9$wišØzF¥Õ î
-{Aýk´¯µ2?鉿»¥NAŒ’Ê&6—°Œú‘3N>¡±ÙeÝxȽ>”±Ø¿¦‰ãƒµÅÑA^¶'ï’¤ ²g [Lgdéîÿ$)¿²µoô(ll‚ªìážÆòì¸Ð›ÎPeËRPÙó
-ûð [¯ èŒâZ¨°‰ÓÀ½0È¿àÊî‡*;™òÊ&¶,TØÎ>¡qÙå=ÃPa”1¸²gˆ„¡Î:¤²íàÊþ¾eÿ³°“¡ÂîG
- ŒÉÌVvSyÅ•ýµÂî"g@ac4K©lRasÎ6(l¬‡\Ùó,û{•6 ¨<ÎÐòòÄ°Aac:³l'¨²e„9™¾YÙHa3°óoWÖ4±ñ ŒJ†õÌ«ì¤'w.[âäEyX¾UÙkÖ@…½‘‘SHZU÷”£wHlVYc7(lL®ìiâø`GmqzT §‰¦òv~v¤²<ËëhèY¸·Éí38ïæ÷(1¯¢¥6¶CªìîƲ¬ØoÇSºªÒBœŒ¡Ê^à0#g™O\éàqë+‘iE5 °±Re÷µTä%>òs;o°On7 =ͺ…3iG îØ­eêpýÁ‹ÌÒ†n<(ll‡\Ù5Ei‘W¬TçccXð0#g™Ž‰KXV]ïì¥[a ¹š{G& Â¯Œá •ïn(Í|ñ຃©Ö-õ³ ï(#Kwÿˆä‚ª¶±IPØØôÊÿùsb¤·ùCnBØ­KgõÔe…¹-õÕÃL:Ëü;C;*:£¸®sˆ@œù PÆrÊžh«*HŽðw·4B¶Ô‚‡>Ë9¥T´Í.ÞŽvTÏðÄ(lŒzå"a¨³®8#ÚRƇvJð/x˜çÎòž£æ.¾á/߀õ¸²§&†{ -|㢙¶Š” ãƯfòYVÀYyÜ}šRXÝ>0Î2æ3·¥Þ¼ ÷u1?ºç‡yÞY6±ñ
-Š~UR×…'€…ù ‡y| ½º0åé]+#œÂ‡>Ë´›IgÙ;$6»>Ë`Ga?È–"à»êJ^EyÙ˜|ã0ÿ—ýú~ªúJÃ
-(KèM%Ë‚tDMPÑXè ÒÁÂJ¤ƒzïÒAPÔ09ß{¯”ËMtÚ=“óügæ3sžç]WË%¨–! á–Å\]H,ffzrż\Ëò‡Œˆµ<‚j†¬sÉ׊™XËü –M.^¿Ÿ[ÑÔ3Šj†‹y„XÌƇäÿ¬˜¿Ô²´Šž•k@tJ~ªeXB(æÑž¦ŠÜû×/:™‚bæ'[ÌøZƱòˆÊªØy…Äg×¢Z†%ËÅ\•Ÿàj©§"M¾˜A-SÑ2°mݱûà‰Óç"nç”5t£Z†#ËÅ\[œâeg &+ÂÊ[_ÌØø¢cdçß{ä{Gßk÷ž–¿µ<¿ø }Øÿÿ!ópgCYNRĹÓ'ìÞ±•–Š´˜ñµÌÄ)(¥¬cáâù0¯²¹olfÕ2Ê‹ó ˜ß”?½wÍ×ñû#{ÅùØéÖÍ/LÇÂ-,£zÔÆ#(6½°¦m`Õ2$ÁŠyaf¬¯¹2/9ÒÿŒ¹Ž’¤
-²,ŸRåà”úÉÑäð±m[h×*o¢gâ”Vѳr ŒI+¬i@‡\ÁŸR3à”zõìÁKN¦š
-â|쌴«N)¼23—°ŒêQàøŒâºöÁ tHAâ)ÕßR•ÿ0ÊÿŒ¹¶’„
-RPJÍM ¶×¥Ç¹[ëï“âb¦ß´Vy3ïöÝŽŸ>qçñ‹×Ý#Óóè‚)xåw“Cu%™·B¼líß)ÌMªLËÀ¶u‡ü!ã~¼z÷ÉË7=£Ó H¦”§†;J³ÃÎڨɊð°à0eŠUÊ[¶‰í9lâè{í^nESïÛ™…H¦
-ú|MyQYÝð¤OxRNR†1_”ëŠÿDy# Ž…GDVÝÐþlxbvYCç0R†._WÞ„cá‘U3°÷KÌ.Å”ß!eÈò×Êÿ * ïÜÌÎ;,!‹ ü)ÕåŒø`²ÊôÌxe[¯Ð[Y¥õÓH¶|“2—ðNÕ£¶^!·2Kê;†2t!*·å¸`kL™s½² PöDÊf‰œ2=5eÏøÌâ:¤ _ʃíµEéqAîÖz*Ò‚d•÷éÛxÇg eƒWž#(ǺYé©H‘*ScÊÒûô­=‚ã€r;Q1àüñ=Pn«)L‹ t³ÔUÆ”éÖ*3q
-JïÓ³vŠK/ªmœ@Êeié3Ph­.HpµÐQ–à
-i¡¢„*”Р R¨¨H$dÎPdž·qo´E½§užaÛ¶©ã}zžµ¾×¿p­ûs}îÓm7Ðæ¡où?ó˜Ø‰*hZ;û܈{õñKkïàhW[¦»›*?¤Þ¿âíh©¯.+ÄÃÊHßò&6^yÍ ;œÎ^M/¨né[þ´ŒŸüúûç…Dl¬È}ìyÐbšŒ7ajË<ÂK4 ¬¾úðe^Us7yd ´Œ£ü‚ZýFêjø”“}ùäs]UiA.ÂÂ?çѵÌÈÊ#$«®¿ÍÁ;ä~ê‡Ê&"™ZÆSЖ:ë˲Ÿß½tbßeR‹9Yà–ÿ˜h™À-(£¦gaï“ò¾¢±‹Dû ZÆO–‡û;êJ³žÞ pÛ³yíRIÉ-Ïûs!k±´ª®ù~À¨¤œòúÎo£PË f¼äׯÿþø>Ü×^[œ™îwÔÎdµ²?3­å?à–Y8¤T´Íöº_Œ|ö®¬®£´Œ« -õµÕe<¹}ÞÕÖh•¢8;3ÃüÉ-sðK,]cºû˜xâÛ’¯íýÃßÁ+…£@Ô‘ÁÞÖ/_ÇÝðu¶6ÔR]ÄÎ4¹ef>q¥UÆ»Ž\¸ŸQTÓÖ7ZÆS –ÇF{ZªóÓc¯ŸqÚ±AS^„—‰öHÁ-Ïg`f_$¦ ehíì{3îõÇ/­½ƒ#?€dã'pËrwSUÞËWO9Z­×X"ÌÃÊHß2$ÙLl¼"òšv8½›^PÝÒ38^)Rì1
-©«±"7%&Øó Åºå²BÜÓ[få^¢±ÞÊñÔÕ/󪚺Éà•ÂQGêÛ@g}yNRT Ç~s]UiA.ã‚y“Zfdå’]¾Îâ gpLJnEc ´Œ£ -÷wÔ•f=½sÑ}ïfmIN–…Ôò¼Œ.AiU]óýQIÙåõà•ÂQ¨T{mqfB˜ßQ;“5Êü, t-£¯§€¤Šöæ½îwžf•~í
-|1O‘¯™ô«è^ò •¯®†OïSbB¼,õÇåkzËtúuÖ¯F" è.‚ÌòpG]é»g‘—Nì7×U•žA¾èõKo«ýÉËQI9åõ@¿px–¿õ¶Õećùµ3]³Tr&ùšÐ/íÍ{Ý"ß–Ô¶÷ ýÂC –ÇF Y®.Hêãl½i¥¢ß òEÓ/q¥ÕÆ»\ÏßzüºðK+f\„:ËÄÆŠÜ÷¯œr´2Ðáecš&_4ý•_±q‡Ó™ëÓòªšºÉ`˜qÚ,—e?¿èq`«žšŒ7ëôY¦ê+ðuým^Á1)ï?5taÆCh³\œ™îl·éZ•YfÕ/— ´ªÎ–}Ç/F>Í*ýÚÞ7 †ûAg¹§¥ºàÕ£¾Î6Ð,‹Ï<ËÔaæä—P^c²ëÈ…ÛOÞ@ÃÜ †û¡Îru–YhÎ6ËHËð0‹)hmÜétf"fìgb–ßýÛ,S‡™ 惞ÁÑÉ9åõð0dc;ð,À³\”æGe.ÂL³L7ÌËtÌöºD$¾-©m3¦{ŒBîn®ÎOêãlý»Y¦æÕƶ®çnƽúø¹¥ ëAgy «áÓû”{!ÞŽV³Ï2m˜EåWlØþ×é«÷Ss+»Hà—ÂxÐ?ª¯ýkiÖ³ÈK'ö›ëþf–©ÃÌÊ#$»\oëÀ»Ïß•Öuôdc;È5ØÓú¥ðÍ“ÛŽÚ™¬Y*)ÀÉ2ó,ÃÃ<æÅR*kMwó ‹‡©ðKa;°)$bSeÞˇ×Î8í4ÔR›}–ÑafæàW\ih}ù¥*›ˆ$
-@6–3ñGe'Eyܦ¯¾DxöYFÍÀÄÆ+‚þRAÑIÙåõ(²A˘
-ìÞVèJ÷wÛc¦½LZ‹0Û,S)Vnô—róOÈ,ªiëÈÆrhÀ®ÊO‹…þ(£UJâü³Îò¿”Ò*#ŸÐGéùU0²Ç
-÷ʼs)ÈÛi£©¦¼8a^…U6ð/19 £õÛ=ODÍú×ßðʬ“9îu#üØÞ-« ÔdDø¹8æƒ23ð/½U›Ý~Aü«¸ó/8¦X'˜{ ÷¶T"î|ÈÅf…¶¢„ïü
-™ ø—àà_;½O_º“ùöSS÷Ð×o°²Y'Êãƒõ^§cîe¤.+†¹×|®Œû—¨Œš!ð¯cá7RsÞ׶ŒMÂ1Å:ÁgT_kջ箜õCÜKEj¾îEªl²
-Ž¹÷´ ²¹gú …4£ËóþºuñÔV& p/¬²qÿR7Zç°?02!íUY]'S,òŒª)É~vÄÝÞR_UzþîE†ø—²®…ݿиûpL±X@aÿ†Î¨ṳ̈è3wýÓLKQBpÞî…ÁŒøA\^Ódƒ£×ɹc
-ž™ùAÜkzrt ½ö=˜Q[×.•]€{á0ÿ‘VÕ·DÆÔÕ”ÅÕ­}#D3ku¯¯Ã=ÍOÁŒòu±1×Q’œÿŒ¢¨l^lL9yý~;#¯¼ÂÌ*ÁPè¨+ËMKˆÄf”˜Qów/¬²ÿBÆ”Áê-ÿ:ÿèeIM[ÿ(qÂÌ!£\Yø,9.äð; ]e0£¸„òì˜RÔ^a½ËçLtÒ“üOMÝC_!Ì,
-”_¥'^8áé¸ÁŸQl @yvLÉ.]¾v›G@ÄÔœ÷5íf–ŽroË碬ûWÎú¹nZ¥§²ÀE3Ÿ¤’Ž¹Ë¡à˜»O * Ì,
-”_§ÿuÒk‡•‰¦ÂAÎ63'7ALNÝhþãço>Î)­…0³@fþe·Í–úª(Ê r/2Ì\|ÂRʺ+mwûÇÜÃ`†šÍìÌÌügÊvX™.(/pF‘`fcçä!ˆËk¯ß¾…ùÌ#Ä)33óQV“åçþ ”Q˜9¸øE¤Tt-ìvŸhvc÷—ñI33Ž<e§?2VÙœ<‚âòšÌ@³KjÚúF& ÌL ¢^ß¿}êi®,¤Êؘ0K£0û_NÊ|[ÞÐafj¾ž&Ž"(¿JOD ,ñ³(ãc
-|f‡}×eWµ’`†gfFÈ(7U>KŽ;ë,ð“(Ï >óJ[—Cg¢ïdä}¬ï›œþv6s‚ªq´¿½¶47-á /j›f‚¸œ†ÑÚm{†]KyñîsKïðW¸¦˜åÉñ/ÝŸòŸÞ‹ õsÝ´J_õç¿ò,Ì|ÂRÊ:æÖ»ý~ëÏ×eu£Äi(`̶¢&FúÚªKrRoFz:n0¡e’fÄd—®Ùâþ˹+÷³
-+›{† ÌÌ ¾¢»Êßf&]öÝmg¡§*-B Ê(ÌlÌ’JÚfœŒJLË-­mï!BcBpõîm©*Î~x="`ŸÃzcMy*QFÎÌÆÎÉ- &«f`¹ÙÕ/4öÞÓüOÈš‚ÆøWTý‡7ݾtÆÇÙv¥®Š”?5(/ÂŒWHBq™©•£gàù›©/Kª±5;›ÁÁPên®,zþàêoG÷n]k¤!'Nà¡eLÀ
-³õòضÎXSêE 3']S›ö¾|'ã͇ºŽQ"ìl†…Ü×­Õ%9€zí²6×AV7ÕêE‚™ À,$¡°Ì°°k²
-+šº¿ŒÃÎfTH}=ÐQÿ1/3)&ÄQ/uZ¬(Š3£k
-°]Þ§¢ÓrJª[ag3.³}]Yø<%><`ÿvD½$i±¢ÈgÆÖ*`ön~!1I™yëag3,}ý>7-ñbÐAg[ =’zÑeJÓ2µrÜŸò¼¨v6£2·¯ïƆú»oY»\Cž¤^´¹2.`¢2ªz¶Î>AÿHË};›AA<=9†ôuèëˆãû­Ì´•¤„ùi×׋0C:{‰¼†ÑÚ-îþ¡±wag3,ÈSžÓ×>.¶új2bN©ùÌìÜü"RJÚf÷ uöØä4<3}ƒ>婉‘þŽºä¾6}-Ä»˜ƒv}žíl‚˜¬š¾…‹Ïé9 _3ƒõ5ql°«±¢0 ø5èëfÚÊR"üÜìl´<2ÚÙ‹y…–(Ptö‡ºŽþ‘‰)øšé˜|D õ´T•ä<ÆýZ_M–æ}™ ële¬³ggV4v ŽagÓ13?Ð5Ò×^[ö&#)&ÄßÔ×4U/Ò™):ÛÖù`ÐÅÄÇ9%U-=Cøœ‚g¦G§ŒŽ¨Î†OÏî_ Àýš}^0.ÐÙ¨g»ù‡Ä$e¼)«mﯙn™QÍŸße§Þ¼pÊôµÞ¬_Óúʳ <ÛÊq_@صûÏòË:F'¦þ _3=B~ÊȈzõçíèàîök–ã~Mû¾^„ ÚÙ2jz6ÎÞ§.Ü|”ýÌ)øšéäÈß¿ÕFÔ“{qçŽz8l0ÕR’¦O_ãgfG=[^cù{×ÿFßJs
-{ÍðÌ´ö”'Ч FÔõó^;­ÍuUeDèÔ×Ø™ÑΖTÒ2Ýààqô\2§jÛú†ñ× ÏLË`O™ˆ>åâ—`DöÙ½ÉÒP]N\^}]ílQU]sk'¯ÀˆøYåõý#SðÌ4n^ä§|9Äß}ë:cME a>.zõ5~f¤³ÅåÔ ,7¹øE%¤fU6v ŽÁ™@Ѧ]f°§üu¸¯­yÊW~;¶QÊÒ¢Üh_ÓéÈxgsñ K(jƒ9å ^sNIUs÷—1â|Í4 vä)`^ åYâÏŸ8°Óf¥žš¬w1Ýž2~f¤³¹D¥•ÁœÚîqällRÆëÒšÖ^h`´ÍÌàÈÄÑÁ®ÆÊ¢ìÔ„¨ ð”Wªƒ…ô5]Œ½æż`N©ê­´vò<~5ùIÞ‡:ÔÀ¦ák¦UЧ<…šWUINú­è`?7ð”—)"#Š“ž}Ÿél0§Ô VÙ9{ŸŒ¼ž‚mš…l^=­5¥¯3’bÏñØne¦£"M×Eqæ°±s"sJQÓhýžC§/&¤¾(ªhì…¢M£àG&™WòÕðãžNÖ zÊØ•ñ9%"¥¤e²~›;b`i9ÅŸ›pцg¦:$½éo¯/ÏÏJ¹yÒÛÙÎÒ€1O™âÌ‹yÀkVÑYØÙ/·œ÷>Ž_ÏCê$*4¤!£a59h³íˆT•»ŒBq”†Nr¤!#F¤¡êNIu“¢ÒÞ{ï!ÇÃéz¾ßßïw¯êxΓëªû¾û½ÿ…×õy¿_ßИ—YU¨hÃ
-Çü#Á t·Õ—åe$DyŸ;ºW_CIf‚F™‚L3¯à‚ň9¸Ü‹KÍ.ªnBDÇüc!ÿP=m ù¤¤È;Wœm÷oߨ"')Ì71£ŒaÓL10mc«Óžž¥å×ÿ)óøƒAìm‡zrõ4¯µË‰
-ðÌœQÆ(#Ó L\FQ]oÏ'¯›ã_¿/©méB1ã˜Ç
-æ\¼¢Ò+€h<áþ©$RÀÜÝ?„c_†á(CÈ]-µ%ð‡ºyé,ÐkMeÙ 6/
-fÔÀxÅ€hoÝeaçêM$å—×ã˜Ç2ä~
-99&ÌßÝ‘`¬£®(#!~(ŽI„ŒŠ6ÀÌ+
-0kBÌqÌÿ w}Eíš ÙÃÑÊDW]IVR˜@ž ½…yÖß`þ„cþ'!Cî/ò)kS] eYI~䇚 ½™ì2æ¯8æÿò—Ï
-Y@àeÈßÅ\×ÚÕ‡cþ~†É{;›kKóà EyC@3|¨ˆ¤ü²º€ùóóßù ¾~èíh®)ÉÍLŠ/”#-dv†€<6fˆ9¯¬¶¥³oÃŒsòþèioª.þ‘ [ÑBžÌŠ.cb&’rKk›;{QÌø9„ äz¨¿§½±ª(çõ‹È`_7‚‰®ãA ³£»_htRfnIMSGÏÀþQòÕßÝÖPY˜qÇÇÕž`̘G`V˜­
-ñq32d
-f.~a‰e«×mþuß‘Óî¾Áã_½+@Æomš¶îi“œÿ6åéÃ[W.ØLô~Q]¾Xlþ\ Å<}Ä,$¾tåÚM{¬\}‚>My›_ZÓÔÞZ{j»6µ­»Û«Š?d¡\Ÿ?a±[GCE^zÁ¼¹³237ŸÐÂÅ+T5õŒt¾|ã~,çâªÆ¶î¾)íÚôm]_Q˜“¼ëÚïçl‚JIn‘¨àœÙœ ™Œ™c&÷ÜybÒòÊ@µÍƒ] ‹NLÏ)¬¨§mí©Ç™|ÈH[×–åg½Šìçqæè~íëd¤
-U ­f‡/ ç d»ª¡µ³w€uk›ÌrOGK}%XäÔøÈÐk—œí¬öþºuƒ’¼´˜?SN25”qæáŸ/&-§¸~³‰åñsžðœl”×6µwC cIÛ¦ 2RÖÕeá"‡]õp²1ß­·qíj´­á$Oc¾I¦Žó48ÎÜsk/¦½sÿxÎaQñ©os‹+ë[€… ~f½yF1k¤¬+
-ß“Rž?½æílo½oÇ6u•åKÄ[SÚši!cã Z¸6¿ÐÂÅòJë·˜Z‚uö½›”žý±¬º‘¦¶Y†3Ý ƒ²Ö••–sÿ–‡“­…±>Ð.ÙEbó[3w[“ƒµ6<çyàœW­ÑÔçìàâúøùKÒûBPÛжYiž)Œá ³FÊš÷(8À ²¡–ºê
- ]‚s€[3y[“ƒ¶6*aÂØ9›XÚ:y\½u?&XXq%´mlžY€3-ãÞζÆÚr¤¬#ïÞøÃí´9<dE9¨]¼³8™¿­ÉÁZ›“‹‡oÞ)xÎÚ;ͬí½‚ÅÓßå—V7 ó<ÄóLǸ«­©®¢8÷í«„˜A¾žçNöî
-¯ç¡×tç)èOŒÛÉ5åE9I1¡~îŽ6¦†:êʲbdîidbèqæ ¶³ ¨”‚ªæÁã§Î;{E%¤=y_U­Ýè CÎ>NÁâŸh<ˆŒkË‹r3“cÃo^»dki|`¯Ê¶­pZOË 3!Î«× ‰É*«ë˜Z;¸ù†Üõ\PF"Æá›jÅMO0nª­(~ö(%."ðú•sPÖZjŠÒ›7ÂiÍäi†Ì`ÆÛù§¥+×
-ˆlݦ¢qÀÈâìÅ«þa°žsŠÊkÈí(ÏèÃßUS%ÐŒüÀêjºq|d·‹½•‰*kñMë׬€Óyºµ5cXq^¸x9\a¨¶õýjwÙóæ­ØäÌ\äÜÎC£oÆÞ"ç)hfUxÿîwvã¬Ôø¨`7³#º¨¬yW-[4ƒL fþ;Š3çOKWÀ&&«´[ûÐ ´ž#âRå#ç®>Ú«ä<Š{œYÕþ
-R¨¬¹sqΛž™}èµ=—cÁÂEËVò
-KÈÿ¼GÇÐÔÊÞÅ;(2œ‹ÊH­½ÔAXЊû{„fV5ZÇco°qÜ\ÐÕÈØ×Ý錅±¾¦š¢ \Ö¨¬œ?̓L`þŠó<N.\Û¢’ÛTöê1³qpõ çÌœÂ—Õ -=zq¿ÿ^¡ÙbüþÝÛ±×#¯h}]mðí„Œã±ñYË£úZ»”dÅ…á²&ÊúŸÿ˜æA&†g¸ÂàÚæ߸EZAuŸž±ÅG7Ÿ È¸äŒì‚Òªº&8Ä ¸f4÷÷Í"†ãu<Híílm$Á·Ü\ÐÕØø Ö.e9 aXÈËe=ýƒL Ž3\a е½fý&1E5M}äìê›ôàIþ‹ŠzqèoMã¦Æ1FUMééh®¯~Y˜“‰Ý°±ön0äãá^ÂÅ9CÊš9s˜µÖóªµÂâ²J»´ÀÙÆÁÅ+ <&1-ëYQYu}3[ Ð߸ºÇĸ©ÿx‹bŒ«º©®²´ ;#9.Ý\ ã­"1^È3¤¬YC¯múz^Å+("!§ŒœÍ­í=o„ÞNHɄ⮬%ã@ã = ú[H3‹£¦A1îli¬)‘ÿäARlD7ÜÕlÆ«¹áèZÀÁ¸¬gòŒÚF뙓k 7ᬩgdfuþ²‡op$*î¼ârRºF47Íêî¯)Íb:1jêA*Ž1TunVZbLx€—‹ƒÜ\ ctX/@ y&•5kæ0×3>𳬒Ú>]C““¶NnÞáÑ¿¥>ÊA† ÝÍ=úëI³÷4“xxÖ×ÝÑÒPSñâ9TuüíОÎöÖæÆúèæúÄxF•5kpm£õŒÏ0îÕ|‚Ââ2Š;5t³<Åíwÿá“<ØÐuMm½úw|Œ}-éññOB »÷÷t´’k+K!Æé÷îFûz\>oej¤§‰îjºñB6ã‰üûzÆÎ<|ÂbÒÛw¨kë›Y»äîx+æ·ÔÌìç/ÊIõ
-¿—”–ùäYŠtCSk!=4 &¬›©ÍðþsÖú›鼄/F¤Qú@¸BŒz:÷ñÔÄبÐ@ßkÎŽv§ÍŽúb¼MJLXõÊe‹¹~œÏ1küçC8ô\´ ¡¸W¬æØ´y«œâÎ=Úz†Ç-­Ï9¹\ó ‹º›Ý ‘.-¯FÒí]=}PãT#k„ÍÔÆÞYæXù3ý~é"^†ï(¦ö÷vw¶µëkªÊK
-ó §ï'ÄD„Üôñ@ÄæÇ hîVQ£ª^ºh!ç|Ž¹³ÆŸ¦3ZШ¸¹q Å¤ä•U5öë9aimçèìá}ó_ÑñI©stUM=¹¹­¨û©´A°¡cÓµ‘7§›cuúOø·è1z ë/á;48@£ àö–¦†:ReYIa~Î㇩÷âî„û{¹_¶·ÅÄZ{v*ÉInÙ$È¿f%T5œÕ³Æ_˜9¬âÆ7
-4|Z‰JHoûYMc?$Úü´­ýe·ë~¡‘1 IiHº°¤¬’TÛ@nnmïìkÈ5 Úˆ{ À9˜3Õ Uäú=„w
-‹ŒŽOLNÏÈzš›_X\ZVQEª­ì–¶v¬ÝÛ×ßO¡P¨Tm
-4jn€—’WTÙµw¿ÞacK+[ûK.^þÿa·¾žšÊÃ0Žî¸ã:t)Iè„ÞÄ
-Ç;%M”“_TRQ Ô7nvýÖ#cSÓ3³s  ½òìù‹—¯^¯®¾Y[{ûÐ? u¨~
-/åŠ#Bý¼¹ lÇdÐLôu5ÕɪJŠò²ûˆ ãƒ%µZV^QI…¬¦©M506£ƒ4ÇÙëí&E˜.ÊÎÍìÒŠªšºú†¦æ–Ömí7;:»º{zoÝî¿3844<<<44x§ÿö­Þžî®ÎŽ›ím7Z[š›êëjª*J‹‹
-òs³EéÂäDäÇ÷ôpqd³l43c4b5’
-ŒXVêŸñKMÌø+Û _Ýø¤Éêš:HšÆ°aÙ;º¸s½|üƒBÃ"q‰I©€•“—_Px¹¸¤¬¼¢²êjuMm]ýµÆ¦ë Žjm¹ÞÔx­¾®¶¦újUeEyYIñå‚ü¼œ,àMMJˆD†…À‚==\Ù¶L+ºl˜ª­©FFÔèNï%&Œ¿ºýÐhÒbimª¾‘©9Ý
-¨9N®î\ß/ 84<2:6>19Ux.ý¼(3+ûBNnÞÅK…EWŠKJËÊËËËJKŠ¯\º˜—›s!;+St>ýœ0591>6:2<48À—Ï;åæ‚[Z˜èéji a|ÄØÆ5Aü/¶ cû¤Õ4´tô ŒLÌi–VL–ÇÑÙÕƒËóöõ  ŒÄÄÆÅÅ'$&%§œ¦¥gˆD™™™"QFzšðlJrRbB|\\lŒ *2<,48Ðß×›ÇõpuvdÛ±lЂ°¶¦:…¤¢tB{o˜ >Œ¤$'-!­¤B¨©ÈÚ̂ΰl¶ƒ“‹›û).Ï‹Ï÷ñõó
-9-ÄÑQ‘a§C‚ƒüý|}ø|/÷”»›‹“ð2­t 3£`åp¥Ñ†‘°ä&ˆ£ÝIïHKËÊaÔªdŠº†–¶®ž¾!`£e[3O²ììÙG'gW7w®'ÏË›òöâyr=ÜÝ\]œ8l{;ÖI¦µ•%Íx õ©à 'zXFR˜ñá'õwi1µ"²†]#l´lC#cS3s nÉ`XYÛ0Y¶ Îæp8lPµe1m¬­ K:faanfjldh GÕÑÖÒP_Uet¢w€±ñ®0A|øí‘ƩѪedÅØÊpÄ) ­ Ü:ººTªž¾>B7u`‡ÌAÕ±êëëQ©ºº:€« º4_¥Š¸ï^`Bø¿oWz›·ÞÅFÚ*ªª$™L¡¨!tLØ©"V5
-…L&‘TUU.΋í|÷Âߨmiœ³†]#lX¶´ŒŒ¬œœœ¼¼¼‚‚‚"†.V'‰U1VEøž'ee°õÿùÂ?í&„¿]8À¶5¶k ´1n
-H‰¼V{|Sõÿþnš¤´¥äUH›Ûp“KK%-J¡°
-i£
-—0Фm 
-*/EŠ…í„É|T  ˆz« ,ˆ ÈS`2Â>Ûg:…‰n0¦@vn’–çÇ}>ûc¿›sç}Îïœso.€ö¨†E£ÆÚ{ÍZ>w
-w.‰èñ”ÃÆʳ«BS0 H¿)Ë+gù+w]:¹èšJþŒ`ÜŸØ(«\¯Ì&‘-Æ$NÇT§äT1ŒS(H&CÛ1jä(äC¸¢PªnÖ1^õ ÷¢@†Q1Å dEK!PÝ3.¬s÷"¡"F©RǶ‹‹OhŸØA£Õé I;“SL|ª¹³`±Š]ÒÒ»f<ÔÍ–™ÕÝÞ£g¯ìÞ9}úæöëÿ³¼Ÿ˜?h°£Àé2tØðÂ_Œ9ªhô˜±Åãþ¥Û3þW&>ò¨×‡’Ò2ÿ¤É)åS§ULŸQ9sÖìªÇæÌ7ÁãO,|rQõ⧖<½tYÍòÁÚ•«V?³æÙçê~ýüÚu¿yaýo_|éåWê_Ý°qSÃæ-¯½¾õmo¾õ¶ÔøλÛw¼÷~Óïvþþƒ]»?lÞ³÷£}ï?ðÉÁCŸ>rôØñøìä©ÓøüÌÏžûâ|Ë…‹ˆá¨·N•S"O"Äz²Ál,ó±yìIöw€;ÈW4 !E0 V!]è!ô¶Y¬–t+gUY;XuÖ$kŠÕlµY‡Z½VÚ¡+ŠPHî-êÉ× 6†yÉ×BòµŸ|!_zÁ(ð‚öÕï._Zò•Üæ«Œ|±P(ôg Ôª=Zr„!šÞ[àƹ‹¯DuqéŵÚ åæ·¬mÙÐR ´l!¨j™Ùòh‹½¥ÇÙo#ýÄÐ{z|=
-ä’qòDÜ·nk´®¿lÃF<¥ø7ÖâK,ÃjÔâ%¼ŽMø
-Aü KP‡àŸX…uXŽpßãelÅ5\Å¿°oâ à-” kP†OáÇ'8„c8Œ#8Š¯éù Çqoc2¾Ã³8“8…
-Ž!“IF‡»1Ym3Y,OV”N¹›–išï-tw)™î1âï¡Sï¡ÍmôH É%:
-dÇpýU‚^b r¦A‘¢FβrÑ9EJv”y½dQ jÉuÅM%ì»1>Î!:üqY™hŒ‹'4ž0Ò­ld®,Œp.gÿF±í³2%MâÒœ2”Kùµ^BÄòDýmIS¨yå"Y+¦`LR9$u8®0EÊ÷I¨3›ƒ+›4(ñÚÊÄ2ߪœrl„"Í(–ëè”Á¤r¾™ˆ#8BP”Ëá xé.ÕùÄnçp×XšM’Žv§¤µICHcÈ‚¿˜A§qŠ “Á` Õvß)µÈwÇc¤„ƒN‘’3gù`:ŠÑž•9S´
-e¢³ÌW68âÝ!å‡7w‡H¥+ðDYQ’Ä„%Þ%RìÂ1n‡œ˜è+0EÚÞÆñF9Äp¶
-9ƒaä@J cÜ"©æÊ7.‚¥¹áá±xYݶ’”iQ^…ļâåKws|QŽ*Ms2ê]Þ`Ð%
-® 7èk
-U—ˆ‚F 6+^ŠZä&«¦ÐÎZ“äZé‘4Þ
-îš¿ÝÔ[ËŠÌo44ÌgÅæACo\aÃ7‹.C2s~ãÉΉb»‚©Š7=½ENdßÉ_Q.*ÚAúèŠ@~·EͶ¨ëºtáõÐj´œV›Pgج×u0w6sf³XgÚÌ+4 Z˜–unHMå õFu—EÆšÿ0^í±MÜwü~w~ÅËùyŽGí_.&8É9¾Ø ÄIŒÄ!¸4„4Á
-Re…(U€¦`œB mQš
-„z“¥yˆ v~Ÿ¾ªûæì9‡×O•clôw‹÷\ü'/µ/w`ÿÂOÚü5k©µCãñ
-
-7V’µ”ˆ›ÊŠ­+š§ñ³ÛÉ{/FœwÛ«EµØu»/•¨ :ãÖ1+‰yT–¤ ´&©¥t`‚lZÙ„aXy-™OÍ9|Š™!Ò,t 0õ4@§Âœ…î§~‡Ã WÂuËZÑäêºêu³»:±Hà®äÞ§³£wá‹ž)w ³l8 71Œ<_´£h¨hÄ6Î&m
-›ÕzÛQdr8ŠÖ"›,ß‚ˆQŽcòåªd¡ÒP<š›d(¹œ´Ái89TŠJ›Lšüüò\Õ„†#‡5×Ê«Øá•Žzõ¬
-l¹¹Z—Je$xÛ˜ÃAµcFF…Kâla¡ð‹†YÔÃ"–¥¦,ÖF!Š¦2& DAuÄðEá4pºÒ#w–øªüÕþjWîªG Ññú«a§‹FÕ>…’FæÌA¤Ý0´ã|î\ÓØoæ,£½ûOë­ŒI‡å-uÏ;ýíoÕ~§nEyi;ií-Ï7ùÛ¶´‡Õ}C§‹¿
-ñv÷ÊÈöÚ²©y‰k®÷·ù —‹ââ?©uðÀ@õA;®1É‚19£O.ÄNÛ$qý„ÎS 1:nQGéøhf$ˆ s©†´×Ûªô}–‚IÐ3‰BY6†ŸãŠXã©;â¿>ïzÝÝ´Õ+OM¾ÕÛ/W¤x—2ö£YpÃœî·>øéä‰+•0¸1ná`ù3¤‡„KÓšQ½Q>Î2}êýj²€„Žä¢Ü\UÌj:n:k¢L7 ™yɇ¨¨d@Öi˜3å3œÕÙ¬À/ ¿«Éû˜=ÚµëøùÝkNW½üëó¿™~ÕŽ.2³‹Ä¶}?y÷³H“oâ·‰ïÿ5_»ò².
-èÍÝÏu¾xÃ×þtòâé—sÕ¯üx[-¦· ¾"õQŒâ”l3ÁÀ}pEÓÇY–€ž¹D0*yRa€ŒkL±"²ÙÕ¡a4¤†B¿â£Ö“D0_jËL¥»JŒR K‹h\µJÜ»Ñ[Ñý{N¨*æü>r6ýa` Æ®:ô³ÐRŒN çóqœ
-©óV³¹™oàTz8 †ÅáF¾j ºjwq5KÊüüWNÆxyu«Æ­ÿŽÕº¥N‚KÈGÁÅbopµ>d
-9BÅ!_È›4bÁ€È
-UÜ]—¬‡ÛÝj7Œ‡é×'è»Å\q•#Qx·†ð&ø¸/QŸè©A5׃ÌüÀ t þŒž‘ž`k€‘ž  @ =#=@=³Öÿ¤
-Ï\w1óY æ3ô¡'€½$B ë2ÿŒ_Þðë;Nz›ÅÃa_wÓ ý|‹xä¹’Š–‡äìéÓoﯷ€§œlízKü°«c_:»¢ CÒ¡C¨­€]C°Dy0ß<~‚:G‘”1w|$g<‡Ìa´1âVž!¦¸igàE4DS ™Á3}]eFûliP[MM=zøÑGÏ<Û»7†ú'§§'“ÓÓÉ÷oßøÞ7nI©¢áò—áÊv"ÄyI —fÌ€49JeNŽŒQ(Ì(7¡Û†„ùzþcµ27n—
-Kx’jd–Â*ÿ§<ü¶Ýܪ`õ¾šúMÞ&ñH+/ôŠäìw7®^v$Ø)–bìéFo¶”e¼Œ@c5
-ûá,YÒ¡'» ’ZÚüuKÃ6’©W&üØ'dºšö“§Š;¯T½­Éª{á؆ãQO››6Ú`á5m÷77îy5ëZZ‰-49{¿½×_^ÖÈ^ZÛÇÆ|ó?¶ †}BËدÖبŽ+|æÞ½w¼ØÞ‡×ØëÝ{׷ƯÚ+cŒ11±]6‰i®›–Çâ]ã55ëj·ÖÖ¡+BipÄ*×H.¥ UÁ%MK.¢QSE€P¥¢( m$úŠh!X÷›ñn äñ#­ú£ê}wÎœ93sΙsçΩªÝÔ²îëÎÌBøÈuÊ×(‡–4º32æÎt0f7“³ˆl’j2Y­‡š°á²ë_ˆÊ®¶á¿Î¡j?âÜÏÄœêq9YX›+âÈfÔ,fvãÁƒ–†¶e¹¿±>¶ÿ¨|í^M²bUG«{p÷zvÇhçq“‡ˆ=„Ò©­ñóü4¶ÌqËyG2´ 'Ó>áø3Û¬»ÊIi²Ð•°Ÿ¤L–yÁ‡ŸÞïþ(>¶zþcGÿa®ø;§Žh¥Æ˜UF„Nõ¬~h×HV·%?4m?Ô›X²ê}ãËí½]•qR!€2„ÙÀ’Ãõðþê®=l_ûÉžú‹\'½öpã¬VD‰Ùf·_VÍNU5;TE¹ât8Nò(G—âÈÌ`¹óëü,»CqšrmfÕ?¯až4ožªâB•ë߉Œ‰gKö:~cEºÔ-’¥Üºìú'%‘! wçðÄÈÍŸ£g2³lÈ•¬¸†ÅWdï k›ÛTû¾ñ£ºYzÛPç'‡Y¤¶ ÷BµdéÃSRÝ:¿Ì>¤á´Û›*éÝÆo·:Z=ÒÙf%••eó7Î-–‘ééÖFë똡àµÛlZ©j6—x½ZiY™Ç®*‡§¬ì²«G‰f³]1—8! •”)¥ªÝk¶9<Ö¹¾Ïù,¯”& ²ççΛ;1?#K+ñ¢Ž*yy£¾³>É—áËüH¬ø!•DrŸð÷úúô×#RÇÌt™YdOYêù{öŸ_ùWgK_‡h})ÃQÒÀŠ¹³|Ü‹«… áÅ,æ‡ ~&rOÖš¼ÕÑöô‰W·¯`£žÀæuLkîhüMóÆäÞZW|ĽȨh›|g…_1 ¥(¯ô¥ÁÊBü]ÌÕ£Éw: â;½gÆ}îb™{Û÷𔼠Þ6è;íû“1äõzxi^§¦yío~~Ž1T8a5²¼0rqW4¹ð±82˜ñDÁ\Ôrœh ŸYñY*œlÕ (=L\ÉkŵÑÁDl ?q&žµŠÑÒÕZòvÙ‹-% ý ô¦u…’<u°Ê˜³8'?ÙÃâþ‚¥&ÃèøʃÓCµaÛs›˜Yêj*cü‚.N½4ujcVýß(ÃBüùaÝÕQ^ÿø»¿H^}xØb5w¢9GÈs¿‘:ú0Hd¡äÕäU‹5Åÿ×sß’}sR']•[hBé¢Mòiú‘Jô”é.u²´Y¡@ø}J'‚ìy´÷ð1¸ºN€¿ ¸ ì€Ø
- ã¨-èö$òÖ‘«(ˆºm?êÒ›4"Ñ\¾*tâ0ßÿ4%ͦ ið¿Gèyžjø>‚÷”À•ÃŽQù-GŸG=JÕà;¥*š ¿Á7EjµÂ^+á¯eà]KÍáThæ>êjÓ{ˆ¡NªÊó÷xŒ¦}ÅמƱ«±÷,
-VêkBÑþPlWà”Ðz,†¶¢[õHÏ'Oº§7ÜÝ«o ê›C˜kKx ŠB©ð½;P÷튆‚án.?PÙ¶¶eÕÚ–òôbŸÞüï¨D-¦-@ ¢‘0)€v
-÷>BìN´»Q<^#ö p#5µö -SzD­CJ*»T? ¹2ÕJìÓÝ=²ÃÍ­m²±i®­“•Usi™,2Ó^pŒBqÞ ³s†•Ÿ1ÈÔ4Ɔ‰õ‰I|UÎ#¬eÇÔß%ƒ¿4XÒ !—ä™|,#о@Œœ#¦!N«U ´|à7=™W/’ßðd…ûê&ëÒå¾ÔkQЃð=T«2ÓíCДÙn?£ÌªÞFAÓrÇVið |í¼ÙCCîȶßä‹J7 >p¥XÁÿä``Oƒ¡ÀŸÝ#’…t+oôôôíº'â‘ñ;¸q‹$-Åqò#À
-H‰|kxWõÜ;Ï™}Íξ7³ïÇ6›„<XH4!€d©Bˆµˆ¥TZµ­­Õb¥‚µˆ¸(j
-j©Vjµ­ÔV¨m-øø>µýöCýZÁñÌ’òËÙ¹÷ÜsÏëž×Ý
-€¶îÚiˆ%"0›‘îß0¾qôÝg_#~âÛ¸ubÃÃnò
-<Ã[€Ý‡æá¦gqNíÀ]bXÉœ ·±
-¼[ e²†Ü¸"Aü´¡¸Ér¼p“Ñ&Y@VìªÃér{4¯îó‚¡p$ZaÄâ‰d*©œQU]S›­›9+WŸohlšÝÜÒÚ6§0·½cÞüήî ÝÒóÅK–ö.ë[¾båWõWßú¡ÛÖ¬]7°~phxÃÆ‘Mhaó–­£ÛÆÆ? Û­@ÁÎÛwíÞ3ñ‘;>
-wîøìûø]ûï†÷|â“Ÿš„Oæ³÷|îó_¸ï~xà‹ÈýàC_zøË_yäàWýڡǾ~øÈ7àño>qô[ÇàÛÇ¿S:ðÝïüþ~8õä©Ó?úñOž:óôOágÏüü쳿xî—¿zþ…~ ,I£ž60€…0ˆXO!Ø Gàœ€)8ç±¢.ÃU0Ér²‡ÜGÏÒ7 ͈ic¦Ñb‹ÅãKŒi¢$ ¦Cð8‡“p
-ž‚—àU¸ÿ@é¾ÒÃ_–nž–KÚ|ϼl¾m^4ß4o¾n¾f^0Ï›¿5_6cžCªðßß];zíÑK
- ¦g
-l½ý'¹·8EÌSpwäI°³vMíÃèÚÔY"ë¡5¸QÃSct—˜Tw_¢hL“‹†&ncd`¨Ä¦Ê ÓÅ:£Ëû7á¼¢?Vj/†n,‡‹ÅÔÃZzزžÉ"jØ<­asY*¸†L\MQbÒ½ýËúKû:C¥öÎb(3ºJgzûKg:C±b¹ø'Exç&ÿô™<3_U‹î¶šWèaz†1fYšˆ ig>ÏRQ‰WÓ½šJtŸ^`}QF%‚Êê¾()_”Öç
-$“E®D<Ér ùtSA._SmÈÓÃ6§fWÜþL2åmÝÝÊù^&!ó,Ï0‚#Œ$k£²K¢¬bã+’ /q¥Z²1E¬¬…«,çòív¿² Cˆ$=Ôp(¨¸xÏRzVщ¡<G)ËRâäâ^-#IÉLœW£MUL\]¹}ïèb4È‹veÜ‘³q’×íµüйt~NSµ­e¢YÒ¬à¬ÏWÛXÖ¨©¯s{ÊWKƒù'š vXý
-šu^¬V<·ÕÈ$W ˜Œ,Óolʧ¯§9
-”çã²ì°«Ñ°OÝ·¨ødÙ+ËN"H2£8yVà±ÐêÐÜ‚èZÂE*B¬[°i^§J8âô2’$1R²2àÉEÙÁs.‹ËŸcŽ—UAEÖÑ6®Ë¡9]v!OD4”¤ »CxS ’î¶9y™(.q9‰â—óRÇÂkmÿ„
-ÐBnÁq¢Ôyzºè_ ^ƒd5¬'##·Â"¤7:I$àè O@‚ÖCŽõ4çÓä¶!¾Š¶ÀýùÕE‡¿÷ÞêÚ_=•…áØ–¬Îº®ÚU·Le³ÜuË\óP‚ùg4c“Í Âìà!:z :UÑ¡Ž(AvëÒ!ÄnÕ¡„H©ìT`Ó7³)Qà¡.Íã›ùæÍ›ùýùoÞï
-#$îB“ã¨U…Ê9sEÍ ¬‚|¶ -Ü[yTbʇ*ÙK[–áWùüÖ2ù,tù rö0nó«¨B1íûż¹(ÊéÃvì“aú´„"ÙŠ&™Í±·ù+Í¢½fZ9gL“ßðˆ:hêã줟aÆ܉VÜâÊÄ,cã®HFéo»T1*8Æ+sá/¡ ƒvÝ(f_3srX|6M±€nu“Òã2ÝÌkÜÕŽÑ.×zñ¼anb¿xŽ°¸Œ¸ì@™ë<íGqU5 Mt!­ó j—/RçqÚ{E?§Ìeù€1¼@õHʧô%Í|ìÅYú—ó¨87&mýu‘À l¤—K§Ä œxïá\z„6jU³Ã̳…4sxö©Ý*P;¯;(° BÔÑæÕ+¼æ UNÿ…È¥6A; KÿR‡¿ç\²ô²´úB㳸‡ZYzù‘#|æG[§ZG«ZÏEŒ¼‰ùó8zÙ+œߘ‡yÕܼý›câwMä9mbM{ý³É“ÿS³×Â9, S*Ù¿~ yçär#7Ò?VL–wWÈç]†»È}Ï&X%™kï¼èr¸@
-q
-0 g
-/GS0 gs
-15.8993082 0 0 -16.1025677 7632.3847656 6956.671875 cm
-BX /Sh0 sh EX Q
- endstream endobj 891 0 obj <</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>> endobj 892 0 obj <</AntiAlias false/ColorSpace/DeviceGray/Coords[0.0 0.0 0.0 0.0 0.0 1.0]/Domain[0.0 1.0]/Extend[true true]/Function 893 0 R/ShadingType 3>> endobj 893 0 obj <</Bounds[0.210899 0.350209 0.494759 0.604521 0.917524]/Domain[0.0 1.0]/Encode[0.0 1.0 0.0 1.0 0.0 1.0 0.0 1.0 0.0 1.0 0.0 1.0]/FunctionType 3/Functions[894 0 R 894 0 R 894 0 R 894 0 R 895 0 R 896 0 R]>> endobj 894 0 obj <</C0[0.5]/C1[0.5]/Domain[0.0 1.0]/FunctionType 2/N 1.0>> endobj 895 0 obj <</C0[0.5]/C1[0.987469]/Domain[0.0 1.0]/FunctionType 2/N 1.0>> endobj 896 0 obj <</C0[0.987469]/C1[1.0]/Domain[0.0 1.0]/FunctionType 2/N 1.0>> endobj 888 0 obj <</G 897 0 R/S/Luminosity/Type/Mask>> endobj 897 0 obj <</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 898 0 R/Length 86/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
-q
-0 g
-/GS0 gs
-11.9217148 0 0 -12.0797682 7632.2294922 6995.9882813 cm
-BX /Sh0 sh EX Q
- endstream endobj 898 0 obj <</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>> endobj 887 0 obj <</G 899 0 R/S/Luminosity/Type/Mask>> endobj 899 0 obj <</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 900 0 R/Length 88/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
-q
-0 g
-/GS0 gs
-225.9361572 0 0 -225.9361572 7629.4453125 7389.2958984 cm
-BX /Sh0 sh EX Q
- endstream endobj 900 0 obj <</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>> endobj 886 0 obj <</G 901 0 R/S/Luminosity/Type/Mask>> endobj 901 0 obj <</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 902 0 R/Length 84/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
-q
-0 g
-/GS0 gs
-7.9551892 0 0 -8.0455885 7632.2294922 7026.9384766 cm
-BX /Sh0 sh EX Q
- endstream endobj 902 0 obj <</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>> endobj 885 0 obj <</G 903 0 R/S/Luminosity/Type/Mask>> endobj 903 0 obj <</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 904 0 R/Length 86/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
-q
-0 g
-/GS0 gs
-225.9359131 0 0 -225.9359131 6749.46875 7454.5385742 cm
-BX /Sh0 sh EX Q
- endstream endobj 904 0 obj <</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>> endobj 734 0 obj <</LastModified(D:20140918115258+02'00')/Private 905 0 R>> endobj 905 0 obj <</AIMetaData 906 0 R/AIPrivateData1 907 0 R/AIPrivateData2 908 0 R/AIPrivateData3 909 0 R/AIPrivateData4 910 0 R/AIPrivateData5 911 0 R/ContainerVersion 11/CreatorVersion 16/NumBlock 5/RoundtripStreamType 1/RoundtripVersion 16>> endobj 906 0 obj <</Length 960>>stream
-%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 16.0 %%AI8_CreatorVersion: 16.0.0 %%For: (Coraline Lafon) () %%Title: (logotalerv2.ai) %%CreationDate: 18/09/2014 11:52 %%Canvassize: 16383 %%BoundingBox: 224 -1014 1618 26 %%HiResBoundingBox: 224.5146 -1013.7627 1617.3057 26 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 12.0 %AI12_BuildNumber: 682 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Repérage]) %AI3_Cropmarks: 0 -1080 1920 0 %AI3_TemplateBox: 960.5 -540.5 960.5 -540.5 %AI3_TileBox: 582 -828 1316 -252 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 6 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI9_OpenToView: -307 125 0.5 1389 670 18 0 0 163 161 0 0 0 1 1 1 1 1 1 0 %AI5_OpenViewLayers: 7 %%PageOrigin:524 -780 %AI7_GridSettings: 72 8 72 8 1 0 0.8 0.8 0.8 0.9 0.9 0.9 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 907 0 obj <</Length 10404>>stream
-%%BoundingBox: 224 -1014 1618 26 %%HiResBoundingBox: 224.5146 -1013.7627 1617.3057 26 %AI7_Thumbnail: 128 96 8 %%BeginData: 10254 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FDFCFFFDFCFFFDFCFFFDA4FFA8FFA8FFA8FD77FFA87D5227272752 %2752527D7DFD71FFA85227F826F82752277D27527DF8F8F8527DFD6CFFA8 %52F82727527DA87DA8277D52A87D7D7D52F827277DFD69FF5227F8527DA8 %7D7D527D5252527D52FD057DF827F8277DFD66FF27F8277D7D7D52527DA8 %A8FFCAFFFD04A8527D27522727F8277DFD64FF26F852A87D5252A7A8FD05 %FFCFFD05FFA87D27522727F82652FD41FFA8FFA8FD1EFFF8277DA87D7DA8 %FD11FF52522727262752FD3BFFA87D5252FD05275252A8A8FD18FFF8277D %A85252A8FFA8FFA8FFCFFFA8FFA8FFA8FFFFFFA8FFFFFF7D522727F8F852 %FD37FF7D52F827F8F8F827F827F827F8F8F82727A8FD15FF525252A8527D %FFFFA8512752A8FD04FF7D76FFFFA8A8FFCFFFFFFFA852275227277DFD34 %FFA85227F8FD042752275227522752FD0427F8527DFD12FF7DF87DA85252 %CAFFFFCF522752FFA8527DFF7D52CF7DF852FF5227A8CFFFA8522727F827 %A8FD32FF7CF827F82727522752527D527D527D525227522727F82751FD10 %FFA827F8525252A8FD04FFA827A8FFA8F852A87D52FF7D2752FF527DA8FF %FFFF7D522727F852FD30FFA852F8272752275252A8A8FFCFFFFFFFA8FFA8 %7D5252FD0527A8FD0EFF7DF87D5252A8FFA8FFFFFF7D527DFF7D5252CA7D %7DA8FF527DA87D7DFFFFFFA8FF52272727F8A8FD2EFFA827F8FD04277DA8 %FD05FFA8FFCFFFCFFFFFA87D7DFD0427F87DFD0DFF527C7D7D7DFD05FFA8 %FFA8A8A8FFA8FFA8FFA8CFA8FFA8FFCFFFA8FFA8FFFFFF2752272752FD2D %FFA827F827275252FD11FFA87D52522727F8A8FD0BFF7D277DA852FD04FF %52FD07F827F8F8F827F827F827FD05F827A8FFFF7D2727F8277DFD2BFFA8 %27F82727527DFD05FFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFA852522727F8 %A8FD0AFF527DA8527DFD04FF7D522727275227522727F8F8F82727522752 %2751272727A8FFFFA8522727F87DFD2BFF52F82727527DFD16FFA8525227 %27F8FD0AFFF852A8527DFFA8FFFF7D2752272727512752FD05F8FD042752 %2727277DA8FFA8FF52FD0427FD2AFF7DF82727527DFFA8FFCFFFA8FFCFFF %A8FFCFFFA8FFCFFFA8FFCFFFA8FFFFA8525227F827FD08FF7D27527D52FD %05FF7D272727F8FD0527F827F827F827F8272727F82752A8FFFFFFA82752 %2727A8FD29FFF82727527DFD1AFFA8525227277DFD07FF7DF87D527DFFFF %A8FFCFFFFD08A827F82727F87DFD06A8A7A8A8FFA8FFA8272727F87DFD28 %FF5226272752FFFFFFCFFD18FF7D5227F827FD07FF52277D527DFD0EFF7D %F8272727A7FD0DFF275227277DFD27FFA827F82752A8FD04FFA8FFCFFFA8 %FFA8FFA8FFCFFFA8FFA8FFA8FFCFFFA8FFFFFFA87D5227F8A8FD06FF7D52 %A8527DFFFFCFFFA8FFCFFFA8FFCFFFA8FF5227272726A8A8FFCFFFA8FFCF %FFA8FFCFFFA8522727F87DFD27FF7DF827277DFFFFA8FF52FD15F852FFCF %FF7D52272627FD06FF52A87D527DFD0EFF7D275227277DFD0DFF52522727 %52FD27FF27272752A8FD04FFA852FD047DA17DA827F8F8F852A8FD067D52 %A8FD04FF52522727A8FD05FF52272727A8FD04FFA8FFFFFFA8FFFFFFA8FF %5227525227A8CFFFFFFFA8FFFFFFA8FFFFFFA8522727F852FD26FFA827F8 %2752FFFFFFA8FF7D52527D5252527D5227F827F827527D5252527D527DCF %FFFFFFCF7D2727F87DFD05FF52F82752A8FD0EFF7D277D5227A7FD0DFF27 %52272752FD26FF7DF827277DFD05FFA8FD07527D27F8F82727FD0852FD05 %FFA752272752FD05FF7DF827277DFFFFA8FFCFFFA8FFCFFFA8FFCFFF5252 %7D5227A8FFFFA8FFCFFFA8FFCFFFA8FFA8272727F87DA8FD25FF52F8F852 %7DFFA8FFCFFD0AFF7DF827F87DFD08FFCFFFA8FFFFA85252F827FD05FF7D %2727527DFD0EFF7D52A87D52A7FD0CFFA8277D52277DFD26FF52F82752A8 %FD0EFF5227272752FD0DFFA87D272727FD05FFA8F8272752CFFFA8FFFFFF %A8FFFFFFA8FFFFFF52527D7D27A8FFFFA8FFFFFFA8FFFFFFA8FF7D27A87D %F87DFD26FF27272752A8FFA8FFFFFFA8FFFFFFA8FD04FF7DF827F87DFFFF %A8FFFFFFA8FFFFFFA8FFFFFF52522727FD06FF27F82727A8FD0DFF7D52A8 %7D277DFD0CFF5252A82727A8FD26FF52F82752FD0FFF5227522752FD0EFF %7D272727FD06FF52F8272752FFCFFFA8FFCFFFA8FFCFFFA8FFFD045227A8 %A8FFCFFFA8FFCFFFA8FFFFA8277D272727FD27FF27272752A8FFCFFFA8FF %CFFFA8FFCFFFA8FFFF7C2752277DFFFFCFFFA8FFCFFFA8FFCFFFA8FF5252 %F827FD06FFA8F8272752A8FD0CFF7D277D52277DFD0BFF5252A852F87DFD %27FF52F82752FD0FFFFD0552FD0EFF7D272727FD07FF52F827277DFFFFA8 %FFFFFFA8FFFFFFA8FF5227525227A8CFFFFFFFA8FFFFFFA8FFA8277D7DF8 %27A8FD27FF52272752A7FFFFFFA8FFFFFFA8FFFFFFA8FFFF7D277D527DFD %04FFA8FFFFFFA8FFFFFFCFFF5252F827FD07FF7DFD0427A8FD0BFF7D2752 %2727A7FD0AFF5252A8272752FD28FF7DF82727A8FD0EFF52527D7D7CFD0D %FFA87D272752FD08FF27F8272727FFFFFFA8FFCFFFA8FFCFFF52272727F8 %A8FFFFA8FFCFFFA8FFFF7DF87D7D2727A8FD28FF7DF8F82752FFA8FFCFFF %A8FFCFFFA8FFCFFFFF7D52A8527DFFFFA8FFCFFFA8FFCFFFA8FFFFA82727 %F87DFD08FFA82727275252FD0AFF7DF827F827A8FD08FFA8527DA852F87D %FD2AFF27272752A8FD0DFF52527D7D7DFD0DFF7D522727A8FD09FF7DF827 %272752FD05FFA8FFFFFF52FD04F8A8FFFFA8FD04FF7D52A87D7DF852A8FD %2AFF52F82727A8FD04FFA8FFFFFFA8FD04FF7D277D277DFFFFA8FFFFFFA8 %FFFFFFA8FFA852272727FD0BFF7DF827275252A8FD07FF7DF827F8277DFD %06FF5227525252F852A8FD2BFF7D27275252FD0DFFFD0552FD0CFFA85227 %F87DFD0BFFA852F8FD04277DA8FFFFFFA8FF52FD04F8A8CAFFFFFF7D5227 %7D5227F8277DFD2DFF272627527DFFA8FFCFFFA8FFCFFFA8FFFF76275227 %7DFFFFCFFFA8FFCFFFA8FFFFA85252F827A8FD0DFF7DF827275227527DCF %FFFFFF7DF827F827A8FFA8A8272752A87D5227527DFD2EFFA8F827277CA8 %FD0BFF5227522752FD0BFF7D52272752FD0FFF7DF827F8FD04275252A852 %FD04F852525227527DA87D27F8527DFD30FF52F827277DCFFFFFFFA8FFFF %FFA8FFFF7CF852F87CFD04FFA8FFFFFFCFFF7D52272727FD11FFA82727F8 %2727522727277D527D272727FD057D2727277DA8FD31FFA827F827527DFD %0AFF52F8272752FD09FF7D7D2727F8A8FD12FFA85227F827F82727277D7D %7DA87D7D527D52FD04277DA8FD33FF7DF8F827277DA8FFCFFFA8FFCFFFFF %7DF827F87DFFFFA8FFCFFFCFFF7D522727F87DFD15FFA87DFD0427F87D52 %7C525252FD0427527DFD37FF7D272752277DA8FD07FF52F8F8F852FD07FF %7D522751F852FD18FFA87D7D52522752FD0527527DA8A8FD39FF5227F827 %27527DFD06FF7DF8F8F87DFFFFCFFFFFA852522727F852FD1DFFFD07A8FD %3FFF7D27F827275252A8A8FFFFFF52F8F8F852FFFFFFA87D52522727F87D %FD65FF7D27F8272752275252A8A852F8F8F852A87D5252275227F8F87DFD %68FF7DFD0527FD04527DFD06522727F82752FD6CFF5227F8F8F8FD042752 %FD0427F8F82652A8FD6FFFA852522727F827F8FD0427527DFD1CFFA8A8A8 %FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8FFA8FFA8FD3DFF7D %A87D7D7DA8A8FD1EFFA8FD0FFFA8FD09FF52FFFFFFA8FD56FFA8FD19FF7D %2727A8FD06FFA827A8FD58FFA8272752FD08FFA8FD0EFFA8277DFD08FF27 %FFFFFFA8FD55FF7DA8FF2752FD07FFA8A8FFFFA8A8FFA8A8A8FFA8FFA8FF %FF7DA8FFA8FFFFA8FFFFA827A8FFA8FD0BFFA8FD4AFFA8FFFFFFF8FFFFFF %7DA8FFFFA8FFFF7D7D7DA852A8A8A852527D7DFF52FF27FD057D52FF27FF %FFFFA8FD09FF7DFD4BFFA8A8FFFF527DFFFF7DA8FD04FF7D52FFFF5252FF %7D7D27A8FF7DA87D7D27FFA87DFF7DF8A827FFFFA8FFFFA8A8FD05FFA87D %FD4BFFA8FFFFFF5252FD06FFA8FF7D7DFFFF7D52A8A8A852FFFF7DFF7DA8 %27FF7DFFFFFFF8A827FFFFFFA8FF7DFD53FFA8A8FFFF7D52FF52A8A8FD04 %FF7D52FFFF7D27FFFFFFF8FFFF7DA87D7D27FF7D7DFF7DF8A827FFFFA8FF %FF7DA87D527DA8A87DA8A8FD4AFFA8FFFFFF527D52A8FF7DA8FFA8FFFF7D %7D7DA87D7D7DA87DA8FF7DA852FF52FFFF7D7D7D52A852A8FFFFA8FF52A8 %52FFFFFF27FFA87DFD4AFFA8A8FFFF277D527D7D52FD0BFFA8FD05FFA8FD %06FFA8FD04FFA8FD04FF52A8FD047D527D7D7DFD4AFF7DFFFFA827FF27FD %05FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFFFFF52FFFFFFA85252FD4DFF7D277DF8A8FF527DFF7DFD07FFA8FFFF %FFA8FFFFFFA8A8A8FFA8FFFFFFA8FFFFFFA8FD06FF52A8FFA87D52527DA8 %A8FD33FFA8A8FD15FFA87D52FD04FF7D7DFFFFFFA8A8FFFFA8FFFFFFA8FF %FFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFF7DA8FFFFA8FFA87D52FFFF7D %7DFD35FF7DFD21FF5227FD0B7DA8FFFFA852FD0A7D5227FD3FFFA87DA8FD %20FFA8A8FD0CFFA8FFA8FD0BFFA8A8FD3FFF7D7DFD30FFA8FD4DFF7DFD80 %FFA8FD14FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8 %FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8 %FFA8FD31FF7D7D7DFD13FF52F8272727F8272727F8272727F8272727F827 %2727F8272727F8272727F8272727F8272727F8272727F8272727F8272727 %F8272727F8272727F8A8FD2EFFFD04A8FD13FF27FD39F87DFD2FFF7DA8A8 %FD13FF52FD0FF827F8272727F8272727F8272727F827F827F8272727F827 %2727F8272727FD0DF8A8FD45FF27FD0EF827F8F8F827F8F8F827F8F8F827 %F8F8F827F8F8F827FD04F82727F827FD0EF87DFD45FF52FD1DF8525227FD %07F87D52F8F827FD0DF8A8FD45FF27F8F8527D52FD09F827FD0DF827A8FF %52FD07F8527DFD10F8A8FD45FF52F8277D7DA8A8FD07F8272727F8F8F827 %FD09F85252FD08F87D52F8F827FD0DF8A8FD45FF27F8F827F8F8A852F8F8 %F827F8F8F827F8275252F8275227F827525227F82752277D272727522727 %527DFD0DF827F8F8A8FD45FF52F82727F8F852A8F8F87D27F8F827F8277D %2727527D52F8527DFF275227277DA82727525227FF527D52F8F827FD09F8 %7D27F8F8A8FD45FF27F8F827F8F8F8A8FD07F827F8A8F8F8F8A82752527D %52F8F852F87D7DF8F87DF8F87D7D527DFD04F82752FD0AF87DFD45FF52F8 %2727F8F827A827F827F8F8F827F827A827F8F8A827F8F8527DF82752277D %A8F82752F8F8A87D7D52F8F827F82727F8F82727F8F827F8F8F8A8FD45FF %27F8F852F8F8F8A8277DF852F8F8F827F8A82727277D5227277D7DF82752 %277DA827F87D2727A87D527DFD04F82752277D5227277DF852F8F87DFD45 %FF52F82727F8F8277D7D52277D27FD04F827525227F8525227F827F82727 %522752F8F8F852522727275227F827F8FD045227F87D7D277D27F8A8FD45 %FF27F8F827F8F87D52527D2727F8F8F827FD1EF82752F852527D7D7D2727 %F8F8A8FD45FF52F82752F827FFF87D52F8F827F8F8F8272727F8272727F8 %272727F8272727F8272727F8272727F8272727F827F8527DF8F8F852A852 %F8F827F8A8FD45FF27F8F87DA8A827F8F87D5252F8F8F827FD0DF827FD0D %F827F8F82752F8527D7D277D5252F8F8A8FD45FF52F8F8F827FD05F827F8 %F8F827A8A8527D527D527D527D527D7D27F8F8277D527D527D527D527D52 %7DA8A8F8F8F8272727F8F8F827F8F8F8A8FD45FF27FD0EF87D52FD0A2752 %F8F8F8FD0C277D52FD0DF87DFD45FF52FD1BF827F827FD1BF8A8FD45FF27 %FD39F87DFD45FF27FD39F8A8FD45FFFD3A7DA8FD44FFFF %%EndData endstream endobj 908 0 obj <</Length 65536>>stream
-%AI12_CompressedDataxœì½Ûr$7– øþ±m#ͶHÇ^Û¶fdìÑl–$+Uu÷l혌ÊdJœæ%›É¬jõíwìí¹Âw¯!MY¦W‰8üàœƒsüïþ·ï¾ÿêèÝí_¹ƒq3üÝßmï.Îïoï~·¡ÖÍ×WWŸ>ÞßaÓørcâÁŽ¾Î?HǺ¸ûxy{ó;ú‰~<û¿ØÞÞ_]Þ\lÞœ¿¿½ùróÅ—ðÓ/ï¯.àÇ«ÛŸnïϯ.îþbÎ/¿ÔÇÂ8'ç÷ÐÁäÃq:´£ñc~,v8¿ùËùÇ—ÿ?G—´ß~ºywyóÓñí¿ÿnc­ß|eè–hòÆFèð_.ÿpñqÙë ©«;HÑ&ìŸÜßtrûöÓõÅÍýww·o/>~ÜÞ^ÝÞ}üÝfûËùÍæ÷ç?Á/ç›ÿvquuû×ÍñÕùÛ­n9»½¹‡®ß^½ûæâ¯?œ¸¸ûã/.>B—ÿ}óÝÅ݇‹ûOçôåûëÛûŸ/úù¾ºý›‹‹wï^6ÈÑ×ᇳ˫ ÀþõùýÆX¤ÅÑ×Æþpüé†útýãÐ%f‹ÍîšÖŸ>Â|`jø›Ó__CË÷÷÷€0
-z÷—çWï.ß¿?„É\“ yøáîöݧ· /aÄ{î0|ž¿½¾øé|3˜)‚DöI:<ÿ
-h9º& ¯tâÐwDÄÈ@œüàTf`xààä›íñ1s:ž l
-G ³‡Ë[¢:Âr\
-Ál0 qæOý‰ß~Ž`
-ž}ðÞ;o½ñãàÎÜ©;q[wìŽÜà&]pÞYg€†göԞح=dM6ÛdƒõÖY ÔGÖ¨ƒØb(|Á\<¡ÍÂX€g$1‰{JdE¢2EÃLK¡ä–¨8­y¦¡cDÆSZ3sEA¥ŸAúíN‰v[¢ÝQE»B9K”‰rgB9¦Ý$´‹ ífê!ýNã аPqB2)=ÐÑ—”DZ25™ž5E‰¦@Q¤éé
-:r º¹"“êðÀ¡æäLÔÇDêÉò8ý{Dr% Tuq2€¦˜€`,NF$["¬ÀŠ2c |”0È4ØäXÙ"3XZü'Ds õ
-¡¨(æI>¹”B“D–3
-;‡Û*,14äRÌ 'LO;`ÌqÊ¥E@’è%‰(;¥ÅjÈu ”Õβ<‘P•BB[*Œß±F-µeB]?_m¡8ŠQSí„él„Ùë†ì‚e1æY³-£7¤–áŸsi6˜ÆŠµ¿Ý`³«ôÞñÒf=Ù¸zÀ¶ª,«á©¦UkYµv@a&¢ƒ'¢>È, 2ŽÂ˜ñ K“ÔŠñÓhwU¯‰t"¬[3‘êÂ* :G&bi¶€s8œó˜¾Âñs0=&?*¸ö6"ÁÄ¡Iz5É-}°ÓèqnA5¬×±ì”G7õ Æ= ÷½ÚÙݲišy<©ÙhgesV»ÑŽÝŸy›Ö¼U«Úpw4ºãN6ô­¶k…î.žÓ²Ý®Þrd˜÷ÜÕ[î*¿i¹q+ìØvwÊ;Qi±r£DÚ”ú„Y<¨7Õ*úF2 ¬ûÇ·ß-6à ͼf^µo½ ¯Þ†'ëQµ5²ÚT×ì©›]W$R(Ϋ­ˆs:4Û!×JÀ6DYî„ìl¢š]TÇÕ–H–Û©"ÐZzÏ*¡¸¼­ü®¶Ó= Á-¥Ìb?ì¦k«KD^/öï<äŸ-vð o6ÔIŠ¬ï!7{êæ%¤‹È.¶ÂTáê­pJ´†lCµœvo†[n…ccŒ——©0s|¬{â„žLK¯”l4qMËzC-ºA¶GV»âªMqªxç%¨º¹·%ŽÍïÆv½1n¦ë’¶ë­qD×£BÙõvÉ%e{t2SdJ6¸UûÛÌö·Ùåþ¶a±Á-–ým§ìokw¸‡f(®–o[5;#FCµ·P­›ÜÙã¦x|h¹BC›Ûv¹íØçöÊôWøôRØOÙ²£ÚaX”;<KÊbSÊÐîJyÒ¦”·¥ Ë})Õ¶”çmL‘"…¡·7¥Úšòøæ”Åö”¡ÙŸÒìP©6¨¬·¨Ôû•Ê¾dÃÑÐ-fyņ£a½ã¨¡í³· ½=GOØr´sÓÑÐßuÔl:zζ#ò²wì<"ºîÚzTÓu¿Tõix1U_H×go$šš£§ÓuçŠ}
-]gª>a;®×‡èj¥ëƒqô”Áua3å‡çý>P_vÝS¬×Oà”Z·ÉÞ¦}}Û_çK:?uÝIloŠgÝ늙óènrªSS#s¦å‡‹ï–£%—e¹ábåƑ)Ü|lÁ׶|Z^[ºÊw¸S-ÿ0Ô9ÄU
-qgqÞ¹jJ J]~’JáISt‚5' ¦<°1ŽDâÉ‚5ïùáƒcdŒbŒ!Ø‘øyÉÝĺ ì&O1‘)»‚Fõª4
-Ø+ƒä~r1YŠÑ²ØÏjÙy\öEî¬eä€ã\Ïx´H#²‰£%Õºe–].ËNv8ˤ-δ
- ,1Jvf]bø¼"CIžsa·ÌðñBÃEVlhs,;BÇëౄÿ6ŠFŸ]6úÒ­Ø; G‡ ‹ÚÓJG·Ã#©€]É
-Cï¨
-Öš=ÈãÎÝÊ뎯ò%»±ßTb>¹Žc[NžM~f‡Örĵ§yyxr–'(7µcgÚ±úe·¨å
-ðÿ¡¸Û®…~×Î3=Ÿ[~1ãv®yVù…}´¯S€1¬±­K0–ø\K]_TåŒÖqQ|Ñfœ9À¦t«4=â)ã6„¼9=©Ã’gê–_`~ùäùUëS¿ŸY51#iÕD‹üõ!ÂT9WLÌ ½ ÁƒÇ7ÙО#\c]3ÑÔ3
-»/ 2 Ä÷‚4Ü¿-YæüM¥è;ÅZê`šÃ|óP €º2èÕÅíÙÚ©’Õpìi) ê蛆¦ð{WÑ÷£‡ú>¯Fá‰Ňj^tpæðìŒçsžóŽø‡ržOÈz;ÎÐìç=×ùlÉ|þ¯ZZðÊ$ôC¥/:Ït9žZ\ ièßXEÀŽõ±^{ªèÕ¼èpÙ§Uì®ôš€zç
-é">ìc’ (Ÿøp,/D8‘ ÷ Šo9ýAÎ Zpü;h¤t }ø¯Ï‚Øu„—Ø×:l*!ñù Б´÷¼Óf>¢-ÉÚVáqSQwù‡¡Ú33“w™?Ñ“%ç!4XZoñ,M¢,Žm3)±s0ÍQ ¡Ï¹ôV'ÅF)¯%©fRä¨ØúÀØöØ/^}dPR
- :.h Ï%7"bAéFLÔ´Vjׇ õ 2$,êCdæó©ê,Kï8ÚB÷¡J¬ÕIµyË]›m™O*‡Ê´ù–¡“ƒ]iµØ=·#ÛRåZÖµ·½4I,©/_.YVƒdP4‹¢WK™ó)œQá$,âÔ‰Jß «üJ\^*§Áhže>¦=¬v¶@V b.,s{ëÌr§ ‹ÌK{9ˆÓÌâ ˜^ö¥Î¿ðY1QvL6ù—¡9.ÆVÇÅ,ó/ù‘óbæÜ ã¶T»pá•ânùß1aÊ{Á0›HŽ
-ÉbPÖѲ§·„ åEaó«ÂŽÉöÖ¬Êò}aó[¦–o £mLƒìc
-MhF#¸§ÍΘåa©íÛ¦NæÜË28㪘î:Ä5ï©h7G–˜À:Sïƒ9 ‹0ÝÀVåìÝ ïcÛwz·LgÊ'F®šê-Å£·P{`rK,^i½-´ÎU “ãòîaJeÇÚ"Æ)/‡{œÎõµ*Ç–órlKåv÷S¿o"øCµWµdÛº1üÝ;Λ(þ°sûé‹¢LgÖ–¶¡÷Líy}+­wÐZVö0/í
-­×«Zßȉ‰´×
-€f*ùÌwÿÎåžäjö?ôžë°½9Éý…Ø22ÁkMv¢-ÓT#{`à'ÚL=¢îdžªEš­å×´ÏZj§Pê›pMž¡Ýó¸¯Ê\ÅîKc›Ì9 ZŠ¥EXóñ¦0—Ú²¸§€ê$ìDÚlö%Yj°¦@9âIwµ$’/Ö(zaÔ¢þž†òȯ¿†'v£SÎÊË¢øb(¸´ u“ †^ºlŠwŽ…mè~ fñà”â‡ñÅÂ’“€YNÿÌUíÛ‘œ.;ÿ;‘ŠH¹†òñˆ÷¯¾†'v#ììŠt±ËÇ}èQ˜ó˜ê°œT§a’Û2T¯×PF/ß½ÂâZ´ åc$³úÕ×ðÄnt¢Š]sO@9ÂÔ±œÊÉÜgc_Mý1ôç7V¨©?Ê‚
-­šDºù˜ì~¼¸¬6r’ç¤+H5Ö|%òªkh¾îy†'v£ó{0ûƒÖ4Åÿh Ï*µŸóu«ÃøÎ’
-Ë ¶†‡P÷’kxb·ú5{P•OU›GK¥]éì©ùž‹ÚOC±R±e¡gÛ}+æÅH¯ð1j|ˆ¢a  µ&löCPÙIa£•þ‡VˆìõzhÀÓ²ay,”û¬ìó v
-ÎdÊó›ª+³„JÙ^©TY—jýÄ^¯‡Ì;¯(>½^³Òïží
->(jutFµ‹GTÍàA…`Äê˜âÌã–ùŒ‹¾sÉ/oÕ0>ƨŽÉÖst€ì1)kOv Ÿ€º¸Õ≇¢Šg¦.&ë¥Ðž©³Ðsƒ¨àµug¼Suο:rEÚ‹wÏ’¥-Ÿ8ªu&ÂA>§üïLþžÐL|ŽòvqÖò‘ü²¥õ¢×Y{ e|SA`åU~õ:W”«jJãüSùÄÑ‘DvÞQªíGÔTíÎâ\©¡¢žvˆr–ýHÞÜ=ÇÁÌPö·¯’ÏRrL¶ï´’G¯%!?:£ÿ ÊÅjNÇ0Ûå“ÉÎ3Š£Ê  <£ŒV ìɱœCê©ž™«1Oe–—ËãßIG;4,€è0¬L'cñ9e#ï›ä0Ëc=¢šöÅñ–ÈÏQ6À1qÿÇ•–YÊG:ªwáž¹X!Ê‘Ý“ P7·±¢æ£Fôä—âè •Õ§«AWÂl‰Ö)©Ø¤¤êw£‘ ZdÛp,̃ û^´·M1¼$
-P"7&z}®‘Œé‘dL£ÆÉ:á$ŽšB‘úx«©”ñch W>[£¸Öq³Ý‰òt,éWЫ/;%yÞÓß¾C¶ç½qZzß}RØÚJ„3 ÂÖ’û
-¼Hر„ŸS
-5ÍF8Z§U8i|Á¥ïu¥Li. ËŽäº Fë•êŠ¥EÍÒД-UõÅsÜ{yÖ[h¼)¸*iXg×;Ю:Òn®¸¨Ž<â ÎZu¡uªìªèh‰"/·«·ÕÇ
-¯:¢w}Žëðšƒ\{çó/:Æõó–‡'·ü¬WËùIcQV£è±(z0
-RNF¡³QÙ<±­ÎPêŽR«qä€=Oiè‘2–#Rê£èÛ·—·GäÌG¥œ «×‹·¦JÏX©M•Õ±)mqìˆá{F¢µ™÷2OôNâqL‰^ œõ}¿ÆNž^Obê¢Ðåë—÷5 ²u |q4l„£äl)…7"ÓP©*ýÃQ2h
--^LÜ)>øßaù}ÑÀçi¢W¶äìFÌ)næ—¶d|‰ fy ó<Fõ~è—ŽðPà
-~øæö滻˛ûË›Ÿ¾úªŠgÕ? ß|À_ÿòÝùýýÅÝÍï6_œ]___|ür(Ÿ6ÞŒàýmŒÉš´1Ót`MÎ;Å0Õ2…è¿ÿòWøó‰>‰þË/ôõ¿ÂÇÿjo~¿ùó7ïø–?À}ˆ¹¹†ÆÕƒ6oêÖЛj€ncsÿ üÿðèîþäòíýåíÍùÝ/›ßá°›ÃãÛÛ«ÍG_ :~8}wy{÷ÃñùÛ”ýðÇË«‹þpñöþËÍßà ÿü¿Lö[ø/H<=`cÇ|
-†Æp£Ç'LÞ#† ÅDè–2È<ÛÀÀ4Mf¦ái"þ²ÁáPqüŒ„'à-ƺÿBÃ#q&Æ€Íd ÎLL}çÒDˆM°jò`ByÌDÇ’<¹"79¼Á
-°füTÍè–I¨E,©ˆ½Áàÿn ȶɡ„A¤Yœ¾6Ê“Ù
-LÅ÷ñK
-H bQÊ
-Ü^E ICD‰ã0ÙÌ
-ËF%¸š ¥ LvÁ âlD6CcР"‚à‹Aáà1Ž,páWBÍ„ó³4 Ð`©E‹ÄHlQ¡fAòs<¼4*®Ñ3&@¼¢UžƒÊé¶rQ#Ü„ÜaÀô‡Ð£ëé-ÚT 9òƒŽÞ‹²ÂÅ‘XMîÒGÁ` 5 šŸŽ^{KO1BÜ ‹Ÿ°>".À·ÿ‚AïÃ_RçôøbÑk
-ÚâΆGE„
-òÚ/à|Œ8´q@ ‹l4±©³•5éQwBï‰T&Œ 4õ8SëÈ ‚I„ ˜Eäæ°5_p•¯á6dÆF@Þpâ¨!Ĉƒ
-Óæò,ä>äºmâ¨$Ãø OF ðÁÈÛ…ƒò«‹ pæVP¾Ä ÑÚ¡-ÆPàmh¹F!¬c0÷X‚dã6l×ÁxbˆVX
-iˆÖRÂY!±±
-­|n°Ui¨ÂV¥MÃV“m¯Ñ(X EL´mÏñ(m„u2rHLBV¤­qñÖ+´Øí—€HçCšãUh£ ‰RG«¦Èò±«P4‘DÔXXÛÓ„>TªJxt%Æa5R¥x T¡‰‚¹‰SÁŒ¼Ò¦Š#‹£¥':£Ön‚Tèº[”Û£B«d†žâ(AÐ:BJ Ô¥-à2¡0œ„§@ŠSÔ©Nyt!}žcSCˆíÐØÊERºuh
-j LFâÄ60#%O!‰KEµÓ4‡¥0º
-¹‰J%1úJP
-•>¸]sL
-D1J—&$…š ©_"Rè‚äž#R¨ý³kãQ
-ìà!¿Ey‰æ–H*öu
-aãPG ˆ±„¡ÐÂwÞ¶Q(´ÕhH B¡A2RèPbP2EEÙ† 
-áB_À'¢Ž3Á„sˆ~3!Jr˜ÃL°|-®þ&ʲ*ŽdËJ ;ÉØH #6Ác ª2¡áƆ’™Àñõ©2­”Õÿ´ “ml\æÈË»‚L¬´¡­ÇxéîÐ*B}KŽå#A¦ô‚LO
-p4VJ¾‘ ÇJÉ7––&ß8÷Ó|#hIÎi¾Ñ¡tÏFs
-’otN³Î’ot¨p f…4߈TË)µéF‡* PÍ7:Òbp_“o„6ÁK¾‘H¡¼’o—П}›oDC1º’oôhæd´ß4ßèÉΉ¦M8z4¢@¡–„£ÇØÙ,špÕqFÔHÂÑaL"ÙiN8:PT@Qu€q9ô£kŽØ/YÊÒqÂY`¤\­&<k›Ú„#>2P°OŽˆ°ˆœÑ$]@užìœqt°ö²Çèƒf‘’ÄéMÆ—ÉÈ©wÉ8‚¥À¦0gêfŒ6G‡&û4ãèQɬԔ£ÓÐ\“rt7!{HSŽÄÝqI9â„Ðn2ŽÈ³ÑæiÎ8 Á("G„jBöi2ŽírÑ4ãèS
-sÆn‡Õ¥a6Í8:à‘²§šqˀƈ–f~ÀÅ–Œ#ÌVš’qÄnx Õœq¤ÁƒKmÆÑ0_#%ãÆ°—ósÆ(u€UkmÆ\,@79^œq´`(òÈ%ãˆu,ÆébÕŒ£ÅèXFÂiÊÑ­ð'M9rž/Ù6åˆî= ¤>`‚g[òŽŽ(uÉ;b·„e’v´
-M®&3 ÔÄ#ŠŠIkâÑbFcá­‰G1Ã$%ñhAõ¥@ I<’
-ÞMäÓL%>2æQ§(©J 3Ùæ\%Qƒòmš¬$'KÀ¸d+9¨æt%®lÜÄ[–6ZŽùÕ K\¥lkkÂ}f
-â—Œ%Žd³81%gi1%H%qš´Ä$XòmÒÒ¢%ˆ!ø’µ´9Ë2Ò´%‰t$V“¶D»-P5¥¦-Éö
-6ÌiK€‹Íä&miÁ& ˆKÞÒQ®WÒJ¹üöò–k%°¬aWÞòÑ´% œÝíæ-=V±`JgWÞò±Ðß'GàoÚÚÚSÚ² }iÚ²„¾4#©¡¯ò½}•F }iÚ²D´4mÙ¾4mY5mYÂ^š¶l¢^š¶,A/M[–˜—¤-눗f-KÀK³–%Þ¥YË&Ü¥YËí*N¼»4kYǺ4iYB]š´,‘.MZÖ.ÍY–8—æ,KøJs–M”Kr–¥Ms–%Ä%9Ë&À¥9ËßÒœe‰oiβoIʲ·4gÙ·4gYb[š³,¡-ÍY6‘-ÍY–À–æ,K\Ks–MXK“–%ª¥IËÕ’¤eÓÒ¤e‰iiÒ²„´$iÙ´4iYâYš´,ñ,MZ¶á,IZÎÑ,IZ.‚Yœ´,±,ÍZ–P–f-›H–f-K K³–%Ž¥YËE‹²–s‹³–sKÒ–mKÒ–s
-Ð^×_ƒ¤eÁ
-6†¬yÚëèõ©]€l+mBI¦r«~µj!y®Ÿ§[ôÖȵúÖë'1'é¡Ò¤0é½È*Tÿ3®ô¸ëß pÏÂçbÍD¯ÉŠh¸d™3ƒŒ£ |Ú¼Hûõåp<HÌbàaø$l†ËüIØ‚ûµ mà å«LŒ‡zB!ÖÂHYE?54ЯŒ)ù¦xÔ[Í0nE}¨4)Lzorà 3p׿àž…Ï]ü#:¤ÁàyaCJ„ÑñÁò£™"¶À &!‹Xü˜3XL\ó'Ïrjœ?¬„ÉÌoGÀÂëúë$ Ì,AŸõasÆ®LÆ¡Þ§e—…¾¶å6¡EÖlîòÓ¤©
-’aÜŠúPý1¶¨@nøbîú7ܳð¹A‘GåŒ\äCôÕ'á‡<®9%XÅå«Ì„Dß<ŒÕLj±‰¾¡Aþ,€©oh¯·Î¢¼B¾>TÌ-ò+Θ»þÍ
-“Þ[¼4B’ëß pÏÂçþE‹ ±>…0V^>­ÄÉÚúÎ(_]k- ü° ª™Ô‚t6ðôÖY$Wž"°Èï"Ü+äëC¥IaªÕD3fà®3À= Ÿ¿–ÑÁRBlPø›žåÃJ–¬mPë”ø»² Ü(”|¨lPia“®Ü§+n¶öÊBÓ-‰µ'jkOš
-0ckíÉ°• *0\ÿ zETØãöQ•Æüi%BÖ6¨0DùšZ›If‹ šO-<g3OoÅpeæÀ¸ô¡úcl)P¼´A+Æøm
-6qÿsð³|(‚­Ü×D´š°…ú¶N[´ØÓ²FÿcF@qN\#Þ
-8±A@ùêZñ¦ñ™±Æ@=Æ,ÞôVý:Öâ­8p±Aà ò>¹ íÅ
-w|«et…ˆòÕµòMæšlˆZWÌòMoEx%ߊ|Ï "f÷,ßDšÔòM#­cë̔ムoêÌ”˜ë¨ò­rfê1fùVÂ(©‰Ý¤Æÿ*vvéTù_{”o"Š­•ùV é
-åkjå›ÌããJ¾iH`j1ËðJ¾éc‹iY´Á
-{°ýÄ6­E¤4Èæª~Í€,Ž“ÞëE@VÖªŸElå¾Úߪ k½‰aɵY½»¯šv‰ùpS‘|G¬§­ßŠgàšiãÀ~%¥I5iÄb®¥¢ú‹±™ü î~m¾
-%®ÁMEè1<ú57Q'Rîõ*+ÔŽÃ,õÖbÕWQ½£Üà`Wq°£˜q/…r®S(ç¾Ü*qÅÿf|©.¾s‡ÿ¦8¼ºt®üº|—á9‚×>²iãûÞ,ÆÑ2;ñ›ws®ìÄu´G{†\î¹âzÑ'9Õ´m–1覴dÙ®£,Ûeýy9̲]¿/Ûéæ‰Þ&ãÒˆ»{@LOÁÛd,ñ]€/FF}T_z\º6bœ€çø‰F|ÝL4:õÏ«†ñ2¡UƒŽõfõ$³4w^Ï Œ´ßxn-
-ù+Ìõp°†=Dz¬«ûp):[isýàpŒRÜgB\|«&¢Cvšvpp¢hØyC/Ï0l˜ƒí„‹yR›¯,o˯—Œ LΧ鬧¶¬=n*Äs
-ƒQ{ >kP×SYͶ#n[ 
-
-,Ëm$5¿à,Ï“Æ׬’ý“øuf0¦DŽ­ÃFœéÜjQONE\=;•qõ\tœ¦n³F_Érc5éŽ íHZ¶ºq9σÎD— -°ŠÈ‰Â—×e6­ó¤/aZtÂ/–(§DbÕܬ"«!³È¬=T÷@Õ{«u¤bOvŠ$«nÔ…]¯m5zk ïr®KÉXÙF=a'MõÝzg¯­ºuŸ \å‘NìzzˆÒ{+|ö$Þn©^ÏSïlæ¾
-àé½Iìæ³úÎpŠc¢Æqi«ÚÃ{˜åxkñðZß~‰–ëJ Ö³ë ή€•…\ß«¿×ìͬ‡ÙÞw°z½ċ‘ ê li«©Ô£fC‘WÇõT$֌ߨ“Çx¨G‘ås‹ù@ 4à©n¸dÌŠm“:¬nŠ(ö)­U ¯qó–Ád²àl´x
-ÄϯYS;5ñË žú‡`Ñéx¢ï¨ üÁ ¯®¬X>âºn“§È²}¸UîÓsé¬xÐhÄ£Ô¾@”<I7MT’IòçHëmœPŒëÖO~§'tªJ›3x˜9? Âî7éo:£·ã«ž6™5tÚVƒ‚/—ÞÕVòo†ïeØÓñ¦5xx”ä”^Û´†njóù‰\‡F®C¤åx¯%Rì)vˆ:D
-"-Ç{-‘âšH±C¤Ð!RX)ö‰”:DJ"Å‘b‡HËñ^K¤©C¤©C¤Ü!Rîi9Þk‰4­‰4uˆ”;DÊk"M}"™±'íÆž¸ëÉ»žÀ[ŽX%í¡#WãZˆvšBG®†=ËÕБ«±#C{m¡#W×ð½N®ºŽ`õ!ÚksÉêvˆÖЭ±#F{m¡#Z÷E§Ø¡SìÐ)tè:tZŽ·/Ñê:²Õwäh¯Íu„ë’N©C§Ô¡SìÐ)vè´o_Ò5t¤kìHÒ^[èH×}ÑiêÐiêÐ)wè”;tZŽ·°¡'`cG˜öÚBOÀ.Q¡"1w,ƒ×HÓNSîؼg›;¶¥X¶Ü°kø^'`cGÀ¦Ž0íµÅŽ€;lîØzήC&סÓr¼} ØÜ°5,¡C§Ð¡Ór¼} ØØ°©#L{m±#`—tJ:¥b‡N±C§åxû°¹#`kXr‡N¹C§åxû°±#`SG˜öÚbGÀ.éTÄaî ØFÈõ_Oò-GÔGqE7-œ[¾iáÝöõî7õI±ó¤Ø{Rè=)tž´QŸ4už4õž”{OÊ'-GœÉ&¬™$>¶â“׭“ÆR¢?Ëÿ ל4¹ÖÉXOéÁË…™ÏL™8ßgŸ4ÅiUGiÕB f®ë¡‘Ÿ;M3¿j¢7t"[ßuÊüE³kÈ{¡^ü¦ðdÇ•oa÷¿ìy {Ïcî…5âö܇=ý:°ãËø–À—¶Çüýi ýr¼Â6ˈæžà_þj–kþî4=F¬™Þÿ:Lï:\ÿ˜wÝ‹‚öl5ßïüe¨¨füÇœÎ^|°zªYÏàVu=ÞÌëÅÎV#®¸ÏSX†gjîÏkVï4=쩹?ý:Ü;Üÿ˜ëÓ‹Uõb 5÷ïü¥+_sÿcA/„Ó ÔÜ¿gð ¯Æ÷?f*÷b«WÜ¿ç),}çšû˜Ö¸é´=æ‹7&¦èö¼
-³Sƒ;õæÐóAWc® ]³xQ̓Ë*Õ\‡Äê„ɴΡ¨³¸ŽˆíèTðG¸Î3Üú!¾ÿ´~Hê<$v×Iý‡Ì^sÇ“npÓÃWaãŽçhz©C¿&A§ÉuȲ+“á:„ñ"ôÚ\‡2»"ñ®Cß¡C¯Íuˆ³3’ìzäñRôÚ\>«GixºC ´¦F§)v´+;JbôÚb‡@»"y±C Ô!F¯-v´3{JbôÚb@«G骢6E®Õ]¨¶:>³o%ÛªçøÞs\ïA®ó¤åˆ+W=)õž{OŠ'-G\K¹yc{]ôõð·Söks¥N1Wú_ÐJ/
-Å¿ <Qç:Ÿ#ÿöpHÎò×U›<D-úÁM›Þû¦3¶5{­Œ™¨ðÏÇ<’âÏ`­Ú4Ž–*Rµ–žÂè\mÉqÄ…C/¬²SžºÁ^i¤ÊrëýHfQÄÂi20œ·d0eÝ{«[‹o6j†ŽáÀòÆ¢ò0{@ïª6
-¬‰_î\˜jôüŠêm=!é”ì8OolÐþ¶±ƒã÷¯æV|±õ”m‡[_¿ßPÇžÙµ<y d˜Rytä͘õüÚz·r¡~¯q¸ìãñ íñá±ì³„²™Æ~Ю`¬ø÷Ç%*‡^§-
-¼¿˜ßõܤ̔ÌÁ˜+ÓÅâ¢Â+<-_¾_t ü݃ëkÙgh‡T¯ÇüÒ>Ú‡z]òîuÕ¦“!¼Œ³¸Cܹ)û!ËNXAj1äjÄ/;•'>@ßeŸ´ÕøzÜ«è#^˜lÁ½¶£˜ £oÌ:ÒLÉ»NïU‘¨ßk»NÛÊóP‡šŽ½µì öšÞ¯öFýŒeŸ%œË¹,ͺY îiÁ/¬m;]`j·ñÃ×xh/fÉôMÛe6Üøþ±¹n_AÕAïþ̹%“á÷f
-ŽO<‰&ØvS¡ns¸Òm;X͙ڦg M'B«žSÛ¡ÀÇ<·6­Ô4{ˆñ—}V .¦±_ nÅÒ6NÄÒÎá.O2’cr"O5oÎSy:ò†ìf䚆¥M¸G3~úíïÅ‚~íº…Ë>b•íZ5íïKøztڟͶânÜÜí‰Gc|(téø¥³/‡!`êa1dÍãÚ¦ Ôø“-ú¡žÜ²O•ùxmF-;ˆöÀJZvY‚¹œÊ^µ›gP•4¤qÁðTp
-YtóÙ=“îS–Øñ8†9Y²˜`eª)©Æî`°ù]í%aߎù´è¡Ùµ„Úß°uȳ?ëlïZrÉ°µ‰VÌP±¾D[©6ã£íPl$ÁfÏfZö©MÛAÛßW
-úvÉŵ™Y‚übEm§†f—e'DqÛ3—}êìÁ."/û¬€í,Ë:œU/ño†’>Þ¼>W96Æ
-sŽRŸÚ³Ç–}
-¤UûÒ˜XöasDG›ñÛßðØ÷«&k,ï9G66ú‘§¡šOÛ³–}
-¨•¥¸2ã—}*ûn9—}ZHçYìU(ÿ
-ø–‘ÇZóTÎêC{êfÙGá¬â¶ 1¹èQÇ\w‘tÙ§²À¿_¼Âô+$‡Ž96b—ÁWyZÖ­
-ÝËNȺìa! —}êZ…]Ô\öY€Z¦Q¡:š‘¨É¥‰ßèôX<qAf«ŒŠÛcÅÌ3C£µŠ¨³bÏ®ˆy~fâý°£
-¦FûK
-^Ï­sýøçVt¼,úZŠ?q;aï3¿ê:ÛWAÂKâaû’±¦yIñÂóc°µz¯Ÿþìꌧ¢_­ÙŸ‚´ÝE%=s葵¶·4õ â[{vëÀóK²Ù/‰žÖ:½Iº½ kÿŠdØëûS÷ð» ¢ÇòØ{I¿ à·o¯¹ÆßKòÊ/‰)Wº~ü ’ç/ÍD½Þ²y
-Þ}—eóØ¢ÙWžö%1Ðt{I.öùÁñZIÖOIÂù¥´gLþÁ|wßBx„m_±C6à÷¾¹½ùîîòæþò槯¾âfÚ8[ÿ0|óÉüËñݧ?ë8_|sñײ}ÖÆ/ÜK{c? ;Øþ®XÚûU²nܤ0ÞÿU2@<›1Ø€-oÚàÁ`zSîZ·Ð]7À·¾8Ìñy Š ÁhAô‘4ŽJ˜ô"õ‡r¨‡ëPÂ%†qŠ µS¨šéù#ÏÙ<ƒnGzõ†íNÆ—:“—›Í#ÖÍõóQ¨çPÍ¿ÓR`|ßç—/¾âü×8~¹9üþèþÓæ‹ã㣷o?]ÿáöþû~¹ù{å¬? ÊYbÆ
-ŠœÔGNê!'öûȉ=Î =Î }Î =äÄrb9±‡œÔCÎ
-ˆœýÈõi/r½‘Ù&.¿¨°¯¥ù¾¤%iZ#(÷Ùû"-µÙró•Hœ`¬} $XÒŒ¢3øšå‘oúì–­†ˆ€¤ü$áºC¤‚˜)"Gì–ÍÖšXHÍÊ`Â-¬ÜèÀpªU"æ)…E_w5jÝHè
-B[õ­çUF]á@‘ã˱FNÝ\ãÊãÂŒW@ 5r\™FÕ×– W£Ö3ršæ2yÔ4Ox† ÕÈ™¡­úÖó*£®pÀ"õY‚TO$¢ÖÙ±z¡D£5@ËAÄjÕâ`ý€Ô«-äU‹HÙvœFÔFGUGen˜’‰¼Sq©æ¹µ\à\IZøa)â“ðÁ˜¾H“›®Vl‹øv[j3à?a‹÷¸Ÿübhvtš‹ÑÛƒ¡¾R–_°ÑKc
-ÑIOK®87gëdM† ©±Ç ?|ÂwW
-ì)Ƚß»,<I_ï&|3¯3ŠAÄbH!É
-ЧL…SÉa,œHí¶
-³ñDšd
->¬nšÌÀY4 ôÐ
-ÏDÅÉ+Ÿ
-NÒ˜„¡@,í8*ïx°wʬp3ª„†ïrï–Ô "›_žÚ²5†ÓF¥—²54f|gxÃÖiÊf%aakhN9ÇÿLx¤€®oaë *Ef¶Î8#åveë ~œU‚+[g_7¶Îà'£Ü.l?·+Ãd}Xñvž˜£Þ†Æl¦Âð:æTŒØúvcGeNacxH‹ÃÃ
-󬄻ÑÒ™âBv¯™X¸;€ö ª=Â}-͘!—fœ#6‚V)ú<Ú쥑,4yœÔ`‚0E fð¸qŠA–`#Æž¬zeÁÊýsD
-Yî®Hž$E%ý²5úxp
-À45£Ì@I
-ò2
-6% Z¹*Q(Ô<Í
-ଠr¾×OUŸ“@£Å¢ü|Ô)Ì^+k´
-74UT gѤå(/ÃÌ"j¢p¹‰À#¦,GaZ°x~LBwhm±ÄYDÁ
-'tÔ“…¶9e XA‘*Ð󢚠])†</&«ª·~*"j¶âX1ps°jΓÊãRq–©$’ÁŠ¾‡aœ¸Çƒ
-ÀêAˆ†BH0;)9iíî‹z7ÃjË,}È>ÈB±P×l°Ý™àÑŸ´þiùM‰‘°~OyBƒöZÂ!Ö’¥ŽÎ6KFlL£å,3‰&¾4HË AôΩ¿Íñ$$Ã,~TÝyò-]`É©$?ZnöAâ)(°)V†á|¥±€È–pRªqL23rx$úÃRëÊب£"e2^ãÐÌxéJ<
-\óT£/›°fÐk»–hdÊ1JsfC”v’Ô —Á|p,ŠÅ)ãp$ŠÍQú’Â!Ò”=. Žjä‘ÃÔX”õ5줂j™1ÈÉ«Æ®äQ+TÌ$;_ª“M\¸È‘ ™®Ä÷&v¸'è^
-àk ­Ñ€’ôuÆh(stvDyÉñ"Ãtg OhÂiŽ‰‘ ÄJ:Àä. “Dª
-ƒRR^<¨¨¼hú…£7ÊtxæF,$RòÎÍd;S-Å„þwœVÍ­>MqÀ•o*Ñá0<HšT!kRW´ A c Æ‹Â
-Ñ—
-‘s¥¸Ç/ê2ÊF£ðSb"ÀÒ´Yã’`? †‹µ“(0Ú¤õ6)wDzŠò”}‘:£Óà‚„ÎH' ·fU‹Ïꌹy
-É©µ ©KÄæJWpÉŸA«Ç Msiæàq2%P÷¾é?èÔY`©‡®<7F1w { (ðàiN2eõZáŽP%¥’,s•FàŠ¹8vÕ°RUOœr|Ip)]ˆ’ëà :%è5îƒÀƱ ;MQúªw&µŒð-EPÞ•ÂűªËQæ è¶"ÅqVs?&íH…€®„Â]”’ÓÌ5‡F1s}É’Œø¢Ö©ô”ÅU¶¾‘Ö©,½2*n€Ð´¡ä0;`)¼X%PVT3¼U…‚öF§TXù(“ˆyÕ´S&_™faJò00û
-K¤
-Ì…,Iã‘#pëqµÃ
-,@@ÿÿ½ÏùÚ$Af2™d&žß{W’¯œó;ûì³÷>å#¬†ÔÌV·‰µRÈ8
-Úëè؉-©šWÐ*„ M°fLÌuYP){½š1“CªoMm ÍÃVçß°+ªäXcH<€"C,qºåØHĨR@äF®
-z ÚóÕ²’©è+Xs}ª8àÎ|{¯qÂ8à Kh´l•rétP®¡´)ÖJ]5ÝiÙHAP©j-TÑè¼ ®D°VÒýl.æS-'F1ç!8+ƒ‡¬»¨HÒÍ 4iÃcž@0½v;n³h­†ããZãpYÛ³A¡‘È7œÁðãtѱ
-…ë¦ÐZ©c
-QL³Bœ(ú
-o9¦=;$–•yª\‘?Âç/(ó’´¨àêл¸Âþþ§b|©ÿÖ6±wn¢¿t„'s«éž|ø£™buÞ0Æ㯤wø
-YXç%¸Ç ¯€bà5íë»*\›`«0îÏ/tTd\Ô£$o¤7¿"©¤²¸
-¡­°ó$—•–”û*Bè;‘(qœƒ-B‡nÞ»\ QJ,¬¡",Œ’&c!`ôÖˆ…€,ŒÊ0è1-öBÀ 3¶±ÞUærýýK|å,d1 ‹Y ÈbÀÆ€Ie`hSÖ/)\ÿRYÐ7j#Áx™…‚±
-ÆK&ä]0BÛü‚ÁŸßs±Á'–Ý’Œ˜ÓPߺëz"°ÌSà« Þ^š—×{•‚n›X‰ÓƒW¶‚à•­ =Ô~%e¥#Jü%…ATÑÞ5^ëÆlZôÚ´F—{ >EÂlZ=Á¶FAð±EAC!dÞ¢Û†åÁŽ™Ú*êèòÜ4óÑÓÝCh‰(ï%Þ1/xW&Fì@Ћ3coÖ1越ÍYÇàu1d ÎæOëtdŠåùS1hcžïñß"ÆÕÑÜi¢~2¸ñ8yAkY¬ «Á×(Úó‚žœLá).öúS½~o~(I¶ê7Ö{%ƒžÌ«m%«ßØ`ÃQ7_y©ß“ï-òWôó”ÆÞ˜d~;•s¹ªýÈül³’Ÿ‚wlÍË£Ù¬D»É:É3&?´M Qlò‹<ð¨ WoÄ’'ž\â/)ëDõ¢Ñìíî•WtóŽñ§ÍXqÞͽ'Ú-[Ð)•˜±lA×(¹âhé}AsPQ¼5,i€Ö ÚŒÜUVRüz<rqýÏ’ûÇzÆ_Äq èpž²z½¾ÞëUæ%.z°óø*|c‚‡í`– 8øzáÁé•_âᬙã–úϦ­Š•ÁÇŽäÚz¯‹ßWìõ\¤v}ò=þü~ ºRö õ^³àý#‡&š¯ïOª¬2ÞUÿ“ Aëcž
-¼Þÿm=îòùý¡,(÷7Xdâ)ö…`ä"³EÄÛÓSY^îó'ùƒ7NÑÅklÙL”…BùnÙLð5bËf¢¦%¢¼—4þe3ùnÙ ôÔBì­›i춯ў¨g g¢w|
-Ú…3!tš¨_8Óx\‡üF·p&øE»qf gØ™(“.²Z¦/œ Á¬D»Éot g‚¯Q´›|¶p&*Í^£^8Bï‰vËÖèÎ_£XÉ×ñQì,ÿ ¡)Cíf Ô”ý¼e…^d0ö z ÒXcÄîA%±Ó‘+GÖ¡1Ù
-"^Znh9¦F¶µñ™T?çÈFëÜZžÔPf¶Gô0´S˜c6.Fl\кÊl³qÝËJÊÿòNœ·¬äbñ ³oQcߘÇì[Èöí/íÀ1ûKöùo̾mßzøK*é4’·8òŒ#tåëd^:¦f Y'
-¡©åNtåY'b(°õ.òÑdiR‰/èÒÑû±¾äÄ>$æÜ÷Ý ¡Ñ¢üCbr£ûX5ú |H¬~½U–×Á2©‹z ±·¤2”:ýEUF«](Þë(h·£ñ|v'”>å6¬„O=£Ì[Ò·cÅ¢•q
-k=;émà
-±ZøëJÐ'®xýðKH©uÇõo_Ġͧ翾¢Ê¦…¬ëë½RdÔÜ!‚ äjuó‘6%”‰¬í|íFíBŠáÝ0ß/*}¿Fí5&ԛ_é÷” ¨ :Í+Dðçðš$tWZRì !©~côzéfYC³ªßȼ'æ=EÎ{bÎSÄœ§T£+Ç®÷Dã°™‚˜)¨…kÁ¼%Û[bYõP['è‘~TðaTC؃à+r‘ÝÃΊˆQ]‘‹,œpVDŠf/{ÔE®tV„o€Š„šVú±ÓSæ«Qä­~in,¡rí±ßW1Ðã»XʆÐBƒ·sQ>„_6„F[E¢|­U\ÆцFCqØÚxFÐØIU‡³6D}m vᣬ#nBb§I"WŽØS‹Z壣{piüßœ öÛ‘yÎ;Ö¾=Ûè?=Ë>ؽVž·>ŽWÓO–$Øf#?ßïÌËcÀNFûØÖn ⣺±gø‹Øçå¢ÑpüFiT¾o½nl ÛGŠÄ7š3Eâƒvh£ýP‘zIYÕsÔÿÖಱzGí[(ú§cçŠÀ½ïh78O
-ˆRc5±¼šÁü>¤ô¿k£}±Ô£‚ÞFk]*¶=ðFiÅ3ïò–õð•E>/;ÓÑ‚+èÉ„
-Ožß;8´¬fÀ=õÞæäí1]Ö£²8ÿ?±gTܪ‹s1u‹IuëƒêÆ3ã£Ú–ÔÀ UH<„›½ÓÊ<ÅåwÿÅ‹èÑþZî*`I“†HšÔÆ;›*a”øÜ,sØ@d5^¢ßM-”ÔNgÙ2ÐZ”£aËÐh>c%s.9h/—Vû?!ø¸Ž;ê}
-®¦š±\d†=™¥àb ŠWÌòpÑ•‡£ñOBa™×[œ
- ¼n¦ SRŒÑ(ö:
-ϘËyKýGFî ×æyʽ=ʼ`s‹óÇ¿ $ð®(VÆçtëû§2¨‰¿‰/ø*½9ÈZ™oŠQ¡±¯^åÜAØSQ|;—D·¡¹«¬¤(x†\ÌÖ³uÅw¼ïºb¶¬8–‡àAÅúªb¶5‚yoZeY^¥´(§ùØ*º˜™º!vŠ‘dp-rû,ûç‰Ø´’â%Åùë—t«L‚ÈÔSYá)ô6‚oÁÇ \›`+q‘œ­£*㢼&Á'v"™ÃKÁ×d¬¯ „}ÆÕQ]£ÞP–ñ[—ÇRBç"ëHØùÄQ6™Q/ç7¯ÛgúÝ“
-‚¼
-bô
-¾"Á] 1x5¦ƒQJÜ[qñž»½
-á]ú#„ýÒÿí
-
-ÛÖÙ #ÔØ;F›käçhWyä`ÄÏžŽˆËTü“0‡íil›+‘(;R1’Q`У
-‹YÈ¢@²(EQ½R;ö¢À «›A`}‚ˆ…, da kòµC Û$öæ¹ÜîÅV8ˆ"%¹ýKŠÂ#È¡“ñTœä-ô;ÿ׿”<C¢J_”Wâk‹ŒTx]ëŸs•yó s–ÿ;Žs%Âÿ2ÇÆUÂ?âdïâÈÿeŽAøa$ˆÆº$W?×ÐlÎU€Wÿ'Ž×ܯ*.^ps²¤ºŠœU\¼ê–d‘wñ¢[ä%x¬bU³~OŽ‹G‘*J–(Dp—¢ÙWÅãSEQ0Ÿêü¼cˆâQ&ÀóRe*¯«.ëÉñðhA“ðOÆë-IrœYHK”gÖÙõ´í÷'Wc'%.)”¨mz1¶t«°ÌSàó‚FãÜœ&(À³À¹5]–A ñ¼j´ƒùߤÂ8QpóªF
-Ñ]¼äV±Š¢æ–5MtÅ«‚,¹u—\ª¢ˆn]Ÿ2‹â@¨ê‚€Ï×yM¨á>‘Óܧ+pŸÄkšêJÊ9¼š‡Çµ ¼êÉãû$·È)Š+)˜÷ r Í©*ª o†÷Ýš•”DÚ@S)áDŠâ8· Êšª;Ûd @Çé*2l5¹î–$QTœ"[3ª·´¯&Hö몽?ùB§‡Ðx¼ dð¼ ª«Jï’ÝЄ¾Vv‹¯!+¢äʦ — "¶žÀ» Dsó¼À+5Þ(Ãå¨Ûª"«n^ät¤*¨p¢ê’H‰j¾QáºFß(H@URpoÄkxEÄä¨ŠÛ †Ä­
-œ&CõÝ
-ô0$š^e ~PuNUð^Ä’ã/+:ùA½ÎôÄqVQQG,²ep“*j T
-Ï‹"ÊT…Jâ±W‹ºŒxM— Š(h<½Ï®ÈPZü£¨r<<@‰&áý2¨:ü
-9x*XO·®J
-OhæQá6]HC(X`–“9Ín-¼ l¶@”N„F ‡fU¢mfRÒðÕdÔCƒt`ñd4…ðÔz+ô]|¸(ŠÐ³_A ÀêHäb"€Ç€®à凖̓¢9' ð¯æŠãR&0°À­êÖdúƒF€yNDÕ°ò–
-‡rIÈK 8¨ø²a„¡¨`§xr‘B MˆÎ"$EÖ[†Ôˆš¾yu£Až„mÒ†àc‚:"A-\vSHnÔÑU½Á°ۥøª#(ïJ,…Á¯üï?qœ1òäÿ2ãÄ@÷AÀ96N­èÈFGÐ1h1e`B9â•Éº¨:ü+£«é:Èß©ÑQ4P=CŽ…̃Gg>ÀúÝ~ Üd áù¼@¢O¿h2ô)ûM–-5-Œ%²*`<¦z…òãÊãxe9 š¦L1ÚZ51”Àž¬‹öëMZ1º’)ŠÇÐE4b êj ³OšÂ?Éš®9*kJòãÌY"«æsª× kËEÏJ¬æ®v/Àá4L¡ˆ^ æòǥƵÉíXVœi“Ë© à]ù˜¨,.p•ð”z]E%ô¯mr!ÂÏu®ñ äo¦¢Û‰
-ÐååÜÄ°®*Pà)èN¡€ø&
-öÊVoÄmdh÷oTYåpøÝÄOC/ §Ë“›Œãá9"p
-nT‰Cg€Ç‰ÐpBÀR¹IH/B;É$
-»$Tq9ežú ¢N†l@
-äh•—sšE5͉ñÆxE3:3¨ bð> žä}è€A$ÈÑXKE¼îx•§Ý•‡!†X¸OÅ@Ë ^ þ¡
- &uT\´0ªLyÓt7ñŠá. l Æ…Ø4Za)" :iYxŽ&Rô)b â55è¬Ñ  Ÿƒ=!´á4Ò„·(*:e"©<HGæiàC"ô4@õ1£¦(…§ý bdF8âe¡g$vuho£²Ê£Ô1Ɉ!/°¤¡ÏmKÀ ªŠ.÷2ÅMs ðtU7ú—¢)TÂ?
-LºH‚xh9¹ø’‘
-ÇKu’ë1 €È#-<ÆT&¥¥ ‹#MÍ8D˜m¦#ªCˆMÃÑŠÙ[^5Æ6|¾Hòû0H¨4Ä%VGUȈkÙ&´p"–ñÁ¨)©¼€IY4Œ¥€ãY<éú2Ò,©†¾£ß€
-,3fEG˯Q»ÚŠm?ɵ5ÒS`È°§™1+Ø^TˆJF“ᎠÔ¢:£ƒq"Ê HºÖ×ôlŒ Ÿ"®ÁRãœ
-ÒD28Äí$&_7$Õ­‡ŽV‡£[Ôè*m+$Î0[0
-c :u²ÑvDx³Uà!à3ˆ$º5s–`{ˆ‹AXÑqüøádHƒÐÈw€vpÉGhTÁP"Í' nð†Ú4èÌM»Y† -³Lû¼mËÀFazË!ƒÎ/“ì©õ0ÉH9ß)Ó´‹]2l6ZGLÈÉ(°®0*‡› ‘ Óµtq¼Â>¡ëÔ×àQ™Éx'c‚ÛÚÁPtžŽ`hìHGÂQ S·~ªƒG>NPÈä&µÐ"aÏ$­ ºƒü.‘PÙî¾È<²T´’ Ð1a„J‰óB{!¹ÉIÔº€õPÑÇ>A´û½ÂS7
-;B>N8’”\<†$„sÕ˜aæ´¡Š
-IžC´E=.0Š"Oœw™ò~‘„7 ò*ðÀø£ùéؼ½4™XW°¸¢*q*£„Ñ+6ÀQŸ¯ ÉdB½…SðÕi¢Ï¥Î¯ÒñŽO…èP^œß%–]¢ö ‚ŽX2ÑáeäA›~F*ÔD
-4â¦ÈT)±GèNˆ˜¬ Fš P™N•Ñ«%~ö]`LGÇGix†‘õq
-OÃ[h52*8±΄€Þ= #æïùq‚âšS}à\¡ahãÉP0¢ƒ _hãLÕЬ@ýM5Ö9£¿˜ŠNl>f21Äy}0-dæŒäÀ½Ä.&ò*Ö -Þ~y‰@mO‚{Õ)Ði|@f4©HÀ°D$Ý’sVP
-ÅŒÜ0åD'²@ÄË’tl…§Ýse¤éÎîA Ôq惒7„`X'sm’‘F“5š‹@qžÀuÃï "Î7æ§ˆ× ©ÔÉ25>– Ÿ¬="é=ëшQ¬§à¡ø&ÞñÍÒ`¢ ç’BãwI'3²D@ˆgC£Y’"D·n&]sø¯dºHñ
-)G½)I6bS°ov•EH»€.˜ Ñ;©¢ÝFhXå'ï¢!G}7(:¥g†0˜ÃÊ‘ ^
-]ˆY+LHÁïd’œ2‘N1 ëèèáú*¾ED•ÖxF2gªäñ$c0%˜ùÅA]Åæ•ðD\C7›OáL¿Œ¬À‚B÷&¾úeÄ$`B¦9\2O!+Ô$‘į±¡›N
-—­bp¦ðÄ"o0oà8š¥£™AžøJ<OÒpÅD2IÅÓ
-_¯¦ûìÇëTµ±**æÛ¡CV¯]2Yú’Xå‘yš%$wcÂÌ9}1T*Ñ\‰=y\$*pFzŠCß&­'M¢c–d“Ñ‚ƒ ÓTMbã­.n!—Ih„8TA’°D!I´€·Lí¹™ä‰Œfܱtľ’›ec½'tIPZ¬ò+â$ŒâÌÇñÁ’$è äF,ë Š/Ó0
-BL$ºÖ¼HBœÍP$:íH\¥d’×Tp9‰&0{ 3ÛpFA¸‚û$v3\†‚Í…Öƒ'YÇ ›‰ÃI;ôÂd¦ˆ„Ìqbh
-^$YÁÌÑH+£a¸‰¸¶+¬âb¬Úò‚K\•“ž£Y[QuE²JÜC{°52
-g"I·Š˜R×±Á}Ã…’†”ºNg™pa2FûI¯âØ…SÕ5ß®€KK"Ì ÑìSñœqÂ't``×.p/^ º`(ux**®qÅ»DÓý2YNp»9ŒÝD(Œ+.bÑÈ
-ç ®è]}š"SÂ^'° å–¯C?Où(s]ARI‰ßÕ¶wñoY…·€.$°ÄÉ~_i©?€ îæ+÷äùÍ«3ÿþSIþºM.ϹúÁ?dAŸßÛÑü…˜ÓÿxÊÉƃ\("ïÊì‰åÁ#xÉ vã]™ýA0ÔÒ‡xFEà 44â±d\9âÂW¡È¼ÒüWs¨tIü Õ†”¨›§ÂÓgü@Åã2{_ö7†A“«›6kÞ¢Åõ€¢×´hѼy³¦×]{ÍUq—7ù‡YÖÿùß\ÒäÒË㮼úšk¯½®iÓfÍš5'háÞyÍ¥uÁÍ¥ÈM•§‡
-í_عyÝêç½;¿/w­Á0.«zýüÄÒ—6¥„·³g«ï*0vœ¢8yòÄñ_ŽþþÛŸ®}uÁôqyÆ6ºøròüÕnÿ‹. ¬¶vüwƒÙßÎÍ'èŽ
-羘ŸÝ1?Ž9|è‡ß~µyÃÛ/?;uÌð^îk †å´âIóV®ÛºçЯgÎ7>ŠÍntåàØþ@U•0{ü×_È£C‡ÈΠææ.Šˆýû÷ï3±wÏ·;¿Üüñ{¯½8ç‘òìñéE?³üý/výpìäoçéúÿ Ÿ:ß%p3ÖÞJ)òFXÞ€ê[€ˆ²þL·nýøýwû÷íùv7nqíj_¬]Š;(¶oß¾mÛ¶­ˆmÛà—­›7müà­å Ÿ˜X:´;ÝNG·Lœóò»Ÿ}µÿð/'Ïœµšµëcîs:K·ÂkÏl;w¦Žpú´ÃB¢umÄ oЗ¨º‰Í± õ»Èì®o¾Ú±mËæÏ57nÜøÑGѶd¯-î¶]÷Áï>ø`ý‡àëÞ{kåKÏμ¯hHò&Ãâ
-½Þ‚‚‚|rÄègnΰ¬ÌôAýz&vî¨kªª* ‰¼»­ëÆV-[:kÑüºËÿÖôÎÎý†({Ï}÷ÞSYœŸÑS¹Å`øêö݇ß7ãéç—.mxeÉ Àü<ö¨Ãú8¢ixHÍ£ÍzrÎ3ó.B¦W¬\¹
-è0µ¼FC1µsÀì ÏU·sÁc2Á$Ð>Ã,:6
-0‘c*ñt£¼Üì¡YYx&”…
-=ÙYC }MëãÊÎÈ’5,'¯Ð_ZLO¸÷¾ûx
-†ô–nhØÆ:þÙ¢šØ³w¯n¥;®7¾Eî‘–S8ªhTaö Äø분1+oá’»wOJnoa0|yk©Û aù#|…žÌ¾ w\Õ°Œy\vS¼Ú%)¹«Î·iþ/*ºÜ%&2¼À›—ÖS5³ µÄ¥-ÛÉ ]»vÖ:Üf1|³Ø/3Ç“—›5°›ÔúŠ†-`Ì£É wzç. ŠûÖf×ÝÌwM<løðìÌþI¢K3ÔMZÜÎk :Êñ·X ßÄuí›>4'gØà”DáfÆpxøW‹6œÒ1A—âoiz Ã]ú¤eegMOéÊßÌ>ÑþÕü¶²ÞQ“Ú·dxȰ쬴¾Œá°ñ¯æ·â‡ŠUÑÉp‡Î}R‡ FfV"<ü«Ù­ñ0,ÜÙúº* gg¥ƒv1_"<ü«Ù-ñ’ª)®* ƒÜ/Iºåʆ-`Ì«2ÌuÆ‘.gXFÿnÊíMÿñçO`øs\Òì–ö„ávN†Ñ—ÈÉÍÎØCowý¿¶„±Ê°ªð ƒ?œ;<'+µWB|«Æ—´­W†U®Æð°ážÜ¡é}:w¸©Îf­þš¸¤i cÔœíÉódf!GØ@†…* c^"#'/?/'³’Àá¡f†ûeää 2 YjÍ —4m]a!±fnA¡×3tPwùÆpX¨™á¤þC†{Gæ Kí!ß‚º°P×»„¤YžB߈ü촞ʭŒá°P#Ãbò€¬¼Â‘¾‚œôž*c8<Ø ßìdxàм#GzsÓ{©·1†ÃBÍ w84ß7jdaîàÞc8<ÔÈ0ÎæŒô1<f“Ía¡&†[ ûGxÃaã §f{Gù}žŒ>zÆpX¨™áî„á"_^&c8\\ˆáï¨bÆp] F†e`¸Ð_\42/³/c8LØ™Ÿ
-ÊpQIñ(Æpø sÍy ÂpîÆpÝ Ió[«1|‹Áp c¸ФÅmnIe G ÿײM\óS3ÃY)Œáðð«o¼ƒ“µj §Ã¥”áÛÃáàŸ×ÜÔ–1IüóÚ›ÛñŠ¦3†#…K®súî\·f0\\Zâg ‡ `øNA­Ê°Â®3˜ «Œáá’ëZ×Ìðpc¸NpIS“á;Ãc8Ò@†Åê ÷d ×(Ãkf¸”1>Ñc8Ò` GŒáHƒ1i0†# Æp¤ÁŽ4Ñc8Ò` GŒáHƒ1i0†# Æp¤ÁŽ4Ñc8Ò` GŒáHƒ1i0†# Æp¤ÁŽ4цÁpGÆp¤ÀŽ4Ñc8Ò` GŒáHHa GŒáHƒ1i0†# Æp¤AÖÑc8Ò` GŒáHÃbXd G&ÚhŸŽ1\—  '0†#<XTÒÑ0/éÈ°ýPÆp]‚|ÄÖ%Æpd€b•;vêÄŽ€a·Ü±s'ç±Ãu ü ¶’й3~Õ1 4iцS;ué’€ Ÿ\e ×%š\;¯uîÚ5AqßÚŒ1\Ú²­Ø±Kb×N*c8"øûU7Ý)'$&%vV;0†#^Û:^éœÔ-¹‹Öá6ÆppIÓ[;h]“»wëªs·5g ×=ÀYã;&vïÑ=QçÛ0†#€µ¸]HHêѳ{bGÆpDÐäú;ÄNÉ={õHJÚ4oB…Œá:D“ëÛJ»õêÝ39A¸½c¸îÑ䆶r—î½ûôêÖId GMnh§tíѧOoÆp„Ф%0ܳOßÞÝ;‹w0†#€&-ïT{öMéÓ½‹tÇõŒáº2œÔ+%¥oÆpdФe{-©wJ¿¾=»ÊmÃ@“VíµäÞýú§ÔÈ0;¥<|\Úª½žÜ§ÿ€~½•v7Ø ³“öë
-—¶Š×»õ†{3†#ƒKoŒïؽï€ýû$©w2†#€Kotwìž2pЀ>ÉÚ- ³¯öÔ €á„ý¥L馷ou)2†ë—ÞØ¡SÏþ©iƒúuïÏŽ
-ý%Å#Ãáâò[äz
-
-<Cu—o¡ _n0\LÖÃá
-‡Û¶ƒ¡V {FøýÎ1 BéäCá‘Þ\{üc¨âÃ>dØŽ-(Ãy#Fú¼9é=Ãaî“™ç+*rÆÇÈð€,O¡oDAvZ…1ânÓá‘U’ ñxGæC¤gÆÒ µ‚“akFî²›…¤þC†z1[!µf ‡¸6„áââQùY†ùÄ~™¹ùÞ‚áYvÆ¡V
-”áQU¾ œíÉódN±f>j…
-ž9dðÀÞ¹›™;l†½È0]xi«øŽ=ú§ed¤è™à¾‘9káà‚ wï—6xpZ¿öºm†Z2ì¯Êp{½{¿ÔôôÔ”îzû–ŒápP3Ã-ÛëÝR¥§JéfïŸa¨jd¸ 2ÜwPZÚÀ¾ÉŒá0 §ÔÄ°–Üw`ZêÀ>Éö.;†Zá ߉ §¦蓤¶c ‡… 3Üg
-&Ã¥ŒááOîÏ® \˜áÞÃmÃaáOØ¿0|=c8ÔÌð Œá:ß1<1\` G`¸e¸c8l\”a™1Ñc8Ò` GŒáHƒ1i0†# Æp¤ÁŽ4.ÆpWÆp˜` GŒáHƒ1i0†# Æp¤ÁŽ4Ñc8Ò` GÃEŒáÈÀ`¸”1)0†# Æp¤ÁŽ4Ñc8Ò¸ÃlŽ#\0†# Æp¤ÁŽ4ÑaØËŽцÍpá0Æp$€ õQ†;1†ëq·;¾ƒíã¨s8îo3|'c¸®w{axt ³Ý^áî ]ÌŽ jf˜ík®;† ‹ÃÕÀð0`¸¬xDv5†Ùéu
-}s‹Ç?0q⽕¾ÌnñÃz <dHÆÀ^ÜìÓ^a¡…:° ü¾‡'=òÀØQYÝã¯!Bòå©A™C‡f¦öéÜ}ž.,ÜÔ%Ó÷CS§O}èî¢a=ÝÃ7q]ú¤eegMOéÊßÌqúæU>8í±ÇgN¾·4§W‡k‰”|Årð°ÜáÙ™ýÙgBÃÃM]³JîŸ6köì'¦?P6¼g2,$öÏÌÉË÷ Mí¡ÜʾíU{´è˜î¿wúSÏΟ÷ôcWæ¥×1ùæx–Ç;Â;<£oÂíWÕ[yš\Ý´Yó-®ÜP§À'¶hѼÙuWGʹorõuFÑÉ [¶lÕê&×­í3‹î›ñôs‹_\4ÿ©)ã¼ýDƒáÖR÷ÔlïHÿ(oöÀÄøkÃÿ¥H]s(Á€kÙªu;^VõŽ :uFt©+àÃ:uJ訫2ßÎucKƒðº-úÍí8É,:¼±kbbR·½ûeT>ôø¼_^±bÙ¢¹3&Œ 5%|\q‹Òsðp_ÉèRÁÞÒ a´%4fËV7¶†÷+ª¦izMèHèÔ¹KR÷¾Ó2†d –“““K1<<OÉÉÉ6,kHFÚ€>Ý»t/­±4¡‹eOìÖg@êà̬¡Pt,³'/¿ Ðç=nâ̹‹^Yýæ[o¬Z¶àñûG ’›Q†oÕzg—[^”—žØîÏ 12ÚŒj'j·%ÕÁ®]Iköì; 5=##3sH ÈÊÊŠ%žW8²xtŘqãï¾çž &Ükà¾ÚÃ|Ä„ ÷Üs÷ø±•å£‹|Þ¼á@÷СðÚ¬šŠ
-²Œ²çz
-|E¥å•cÇaÑ¡Ä÷?ðàć'O{ô©ù/.ãÖ­{ïíW=9±(M¡ Çݦ§ óA,}ß„1ÅžAn®YKAA[¹ÚueTOЊmIÔ0;‡´åp'//ß;bTQéèòòŠŠÊj;víÞû'>2eúÌÇf=ñäSOÍž=ðt]
-lKTCT >úè£7~ úégŸmÚô¹‰/°yó`JåÚýíž½ûöï?pà;À÷u|Ôû÷íÝóí®_õåd{ËæÍ›¿fÙS¤‹åþá‡:|äèO?ÿòëñãÇ9úãÞ¿µø±±C®§ ·ï–U Žò KW¬ZµjÅË/½°
-’i{`¸bÆâ5›vb ×Οþ ŽÙ†wiIn Ïd ×,w8»Ó ŒáHÀá_ÏŽÐþü­¢·` G
-oúÃÿ:$–UtóåWøJŠ=eã]AÄ»:$•”ø]m{뮕ÿýïxW?Où¨»ÚÃàðÿéð?ÝÕöß®Ì |ü𸾬‚^d=!ÅW<Ê[`ÜI˜[åQœ‹wq.þ›9~é¿Œ„_ƺxÎÕÏ54›s€8ó?ðø§ N1^šnÜIÄã«þµMn%ü¯þ‡s €ÿJ®~äçB,*ºžÓ]¼*¹u^â]E •U·$£Ls‹š¦ºâeÅ­ Þ­È‚èŠçUÁ-k<Šd7/¼+n±âæË‚[”/äÝ'À3xxGn•è­ä'MÕtz/\ (½ƒ×TÙe 47ø%’ëwÚ¯ÐÝ‚"+.,…ªC!t7Ç벫z-ñ¶»Hë$–â?
-ïÖuÌénYæ4¯nN%eæy£@/ê
-ˆ,¦o“%7'p2qªF/a¡ ¼ìV(F¼oE¢[ST(2Þ,IªH/“x¨%Þ£rº!–x /ÕÜ’FÞ·ÃkD*uM¢%TtݺîÒ‹ª[eÈŽ*I.Ip«¢ù@^P]<–ѼUssºbƒgCa$.€††Úó-·² ë¬Kæ{¡Ù]šx|‰&º$¨†eæyh ZKÆj€Dq+àpÒ&•B™¬+ðnl:¨âyh1Yá©D„:ã£u§/ä¸ú°R´^&rØ>Pqh:¸±zÛâIy¤ùÛ¦{Š¼®Â2OÏ[\áRþM:R¼„Šv+ó¿IØeDP.Q íDlX#?i¨î<¾Þ¯c˪–M„2©àÃÂ-ºæÊ$½ HÔ8 õAÔÜŠ†E«ñŠ¦Ã…šâÒD¨•Ž}!) A/P@5T„Ø5ÞÍ+*Pí­êÐDÐ{UWQjŽGÕ€Û4è¹²t¡âsšà–D(œªÉœ[ä¡øI¡_Ñ5(
-UU7)ÐÔ¤øwë“”„V)Ý°Ÿ5˜^•yè`,]‰•%.j®}ÿõþÛÕ!µ¢ÌW\h™Ó‚’<o.ÕyåÞ²1ނܾÞñ¹ô¢r‡‘…&O,.)vI²HÌc%üG çê‡6²Š9ý3C\æÀÍI"öUú%Ú™¢@)¶éË`
-£·ÈñUhµ…Žnè–É@ãÐ= ,y€^Q1/©Uh…¢hÔ1°n•jÝÃ9ˆµ…fB³Þö+,vì¢8UÖ*t€-3+g+¼EÝ/l‘ÃUãϤ¥ðœÀ¬-3k-À
-áò
-b¬EÆMÓ¿ƒÁMÆÓ 4NÖ9ÝWë6Ó÷t<ÚEpz®$6ÕŒ§YBº5Œ®/Ppˆ×ÉjÃ9½VKètZ7[U·_a‰Ì¢$×Ä›E©é]9µef­M÷Í"Ætò|šÞ ƒOÓi´n3=KŸ¶ÈÁ§%tòi íÊZ/°)±ŠáäÓ*®ƒO«NÖ½V½­X™Õ«æŽ:¹´efMŸÍ$Åtìl*MÿÏfÒtÍ{ 7Ò~¨%°I4E
-M‘U?ë¹Ö»ôY%t°gÕºӪ©õtKâ`¯GÕ¼ÎÀ¾mÊ,}1Ü2K¥ ßÍf¸xšä`ÏôÍ{LWÑ~ª-±ù³d-™UGëÙÖû
-h–ÒÙŸÍš8º³Q[Go6$¹
-OUK¶È¨©éˆT˜¾šÅžéÐYä™>Ÿ!0Bë¶ÀbÎÙÄY"³fÖcÍÊ[/¶Y³
-g“fßY4l lƪñRÕ‡tf‹Œú™þ–QÓ%³3ý6‹0Óµ3¦ïg=ÐX„Y"›0KdÖËz¬YsëÅ6aVál¬â›"«‚æƒ-MX5^ª¹ŠÆ2£†¦[e0`z^e¦{fQfzp†Àtñ¬Ú‹2KdSf‰ÌšY5ën½Ø¦Ì*œM™U|K5­šO¶%6iÕ¹!I!Ó›q2fËLÆLwɤÌô©ì~iº^i–fÞcºpöSm‰Å›-³‰³eVý¬g[Xïwpg•ÒAžUëN«¶ÖÓ-‰ƒ½j<¥Ä%åÅÕ”ÿ–hþ[¬1ý
-¸/ á.¸.(‚j
-êËó;çdU}]_Ûê óâUΨ]¿ï—§²²²2Ož<yÒáe-¬rî>¡cÜöz±Ë¸ñ&ÚEÏÿIk{PAvWŸP·YÜ µÐQö­›šIÍ,˜†…CZ}Ïmõ}Ÿ,ãG4g¡ýSÿ›äS`þgþ¦BõC‰hiv{Ó“EIµ0¸:€·Jÿé×[ôé¿Í Aƒ¶Ðw«Mz:Êñß6ߺÿÀAƒ¶5Ì>[üº"‰°Í mÿ`þjÓþéîCF8êàQŽØoðNî–ͪp£Öà÷{;pÔ!‡|Ј!»'ý7mrDô÷‹wÛwÄA[æf͘›ôKÌÍ:xôèIföBfÿ+³_3憛eÆs£¿ÛgtG}ÚɧžvêÉÓê‡î¿ãÀòc‡=h—ãŽ9á¤SO?ý´“§N>òྲྀ·FŠåÚ:<êÈISgœvƧ͘6ù¨CöÞ~@9³O¼ç¨#'O³Ìƒ÷n"sþɞç™MenØ7Ýëà£~ŒLóDißÒܨÿö{rT]˜æÙGí™sƒþ;Ð1íœKg͹þ†ë¯¹ò’³§u¸sÿ²Ñeû>ö” .»úú¹so˜så̳N8rÄNýþµ„¹q¼ßø®3g^9ç†o4ÌKΚrÔÈÿêW&sãxÿ#N8Ë2gž5¥™ÌÍ·6!ÇìA&1§œuɬk¨LóD†ýn‹âýþkäQSÏ62ç2óLÃÜ~süö›xøѧþåº;<üØc>8ï¶9—œ2QamxØíœtö7ÝóÀ£ >öм[¯¹øä¨ú?ý«oÿÐã‹=þðüÛæ̜ќyâE×Ü6ÿab>4¿¹Ìœ}<ébeö$s#gÈ‘'_<綽9¤ “˜GÙ®¤ûM4¬sÆÌkoÏd^tÒ‘û2sË]Çü׿ßÿäó¯¼þÆk//Y8î%ÓÛ¥ì5üv §]yûÃÏ,}íÍ7_yÉãón¸xêØ7ïNüutÐñ^ïÂç^yã­·Þxå9#sæ´±;—Èüu<ªë¢ëç óåç6•¹éNc¦^|ü^È$æ̹ó=ÿÃesŒœ‚QH[ìrØôKn\ 2ͳ_Ñ9/£Ï>Çœ?÷ů¿ÿá'Ÿ|¼âÝWžš?ç쉃·*©À¾C&_ü÷G^xëƒ?ýô“•ï¾üä½×œÕQÆÜb× g\},[ùɪUŸ¬\öÊSóæœÝÙœù”2{¹ÕàŽ³®™÷T/dn5¸óì9óŸ~uÙ‡Ÿþp™†¹GkwæÖ{}ε ž~õ½LæÕgÅÌþç\~÷Óo¬üü«µk¿^½êýWÞ6³>¤_I9íÊy‹ßþø˯׮ûzõ§ï¿üø-OÒ·;±Ï>Ç^tËã//_µfíºuk׬ZþÊÂ[gN.“ÙgŸIßòø+Â4wo*³ß~õ™·.|¥2 ó’Û¾ºü³!óñ[/ž¼osÿã.½}Ñ«d2»å¢IÌÜæ széƒ/¢èÛ¯?}wɼ+¦PRƒ>õºG^]¹úa¾³øÞ˦ +a6åòyKÞ]Å1y¾ýzUS™†O¹"Ï$™——Ê8bÚ•ó—,ë…L0Ÿ[ö#e6aŽœ>kÁsË>Ë˼섡ýé·mGŸqã¢7?]+;àW¯|ùÁ«O:`›’
-lsÖÍO¾½j2W¼ôÀU'ŽظÍ'_óÐË+×|›É|¨\f7fs™æ=?üJodóÃ'“ž½„9è S®%™ßY™K­Ìö±çÜú̲/4¼ÐÚO^ìúSTRí‡ýùögßÿRc|ýñk^{JsÛƒO»áñ7ôô$³‘Ù“ÌÓoXøfïdž>×0×ý8™×•Ë<¤¹ÌíjçÝùÜZ·ß­ûìíE7ž~ð¶%¸ÝáçßõüŠŒ¹ê­…7œVÆl}æMO¾ó¹ ùôÍgo?Q.Ó0oÎ3IæÜ&2Ϻ¹Af“r62ÿ!2ÍÇ÷TƒL}öíÆ]p÷ +¿ÒŸ¾ýâݧn>st[I:ã.¼çÅŒùÍçï<yÓ‡”Ü®ýгoyz™mª=È$æ39fO2Ϲå™÷¾ü®72‹Ìõ-ÓѽK?üÚ
-ùrÙÓ?kLió£¯ ·;«ôvcϽíÙ÷W÷ý2»1{”¹¸×2ÿ2¥ªñÏ{é#Û|·ú½go9{L{IºG˜Ío·é,—,·ßz2™,³ìaªÌõWN÷ˆ™ó_þ8Wï?{ë9‡–W`ó½gÌíJ vÞK>øê»ï—ÙÙ“Ìçz-ó¹ŸQfcµ¬Y¾ø¶R½¹]S™M
-ö ÊüAØÞ¬Ï-¯À 34´Õ&£}EÃÖÊìÆlþýÓÊl¬–Ú
-<ÿŸTæh½þ„Jþ™åŸÛÏ,³[¾ß¤ø)}`3™Ý˜_öºÃ_Óë=ˤjy©×£pŽÙ“zðcGážÔ˜2™Í^_ïev…¨Lzà×™fíª‘Ù\A-ѯšµÕ± úUÓ¹@7™ÍÛÕÏ,³t&R>•+219ó^ÏDz3g‘)R¯æ,ËžîÝ<èÉìåÜ*/ÓÌpósáæÕÒÈ\÷Ù[‹æ–N½çÂ=Tuÿ¦2{;kî½Ìse>ñÃeb,_¾æûo‡¡hÉrûØ 4ªÄøÓhi.³‘id>z]©é©»Ì7ÞpÚ¨Ÿ*³h·ùôÇȤ֙³’¥«üvÄÌ[×|øÊÃל|`‰I“­|ŸôÊÊW`~»¦©9·ÑrøÕG¯>2gF/îÞ£ÌÓ{-óô¦2·sæM‹ÞüDº¶o¾ü`éW•[¤‹ÌuŸ¿ÿü‚YÓG”[¤¯~𥫿ÉË<qd‹tŽ¹ö³÷ž›Å´á¥2g\óÐË–¹î‹å/Ü7»ÙÝ‹2—™SËd:¨»Ìò'T¼{Aæ Q§\ûÐKË?G<öuk>~ûÙ{ÊW:,“¾]ûå‡o<u×_»x] ˜ú›rù½‹ß‘ÅEæ ¥«'ç^&Ùz¿]ûÅÊן¸ãÒãö/‘9`ÄÔ+æ©Ìo×­þèͧ›Ü½÷2Ž˜ÖK™–Y"“JvÏÓ¯¯Xõ嚯VþÑ»K¥5¬ÒU¹S/¿ûé×?øôË5k¾\µòíç¾éÂcËÖ°hUîï.]öñçk¾új dþý¢Ie2i¥ô–G—¾ûÑç«I抷ž{`îyÚ»Owf?b>öË\ýÙ‡ï¼ððM³OÉÝe¾¹¤©LZ•ë•ÌŒieÞ 2ûî7éÂxöÕw?X¹rÅ{o¾¸èž«ÏêÜsë’
-ì;„™ï,_±bù»¯?ÿø]³Ïì\ÂÜ|× §Ï¾{á‹o¾·âÃUféÊlëŽ:óª»æ+W,çµ%Ü~Å©GìÞZÆì8óê{AæÊ–½ñüãwÎ:ýȲ5ÜÞËÜzÏγ¯¹wÑÒ·TæcÍdn½çDbªÌW—<r›•Ùú‡ §^ö÷û.~ñ¥—–>÷Ô#w_7sZMåm(1,|öù¥K_XüăwιhÊa»lÙøëè ãΟs×ÃO=·ôå—Y&ù”yì8ú„ ¯½ëá'—¼hd>»èþÛ®:ï¸Cv,ó"ØqŒaÞ ™/½¸äɇî¼æ‚®Ñ;•2{+s‹]ÆN½øº»Q™ÞqõùÇ.cn¹ËaÓ ÓÜ2ÞÛì?×fæf;Žš|Öß®»íÞûxðþùwÝ|Õŧ=rûMK*Ð0'ù×ën½gÁý÷ßwïí7κðäÎi³ïÆM;ÖwÎàÁ ™Í覧æSËvûN˜~Á¬ï˜wß÷/¸ç¶ë/ÿóÔ#öÕs_™'^8û¦;çßÿàƒ÷Ï»cî•çOŸ0¤³w2ÿ#ÚqÒEWådž7íˆR™ÿ ë<™˜,óÖë/?wÊø}ùô”ÿíì9öØ“ÏyÙ¬«®š}Å_/<sêÄQ{´ÿïîBˆyè±'Ÿ3óoWΞ=ëòKÏ?}JçA»—2í7þøÓÎÿ˳¯¾úª+Ifç¿ø«æféÐ#ºN;ÿÒËgÍž}åe3ÿ|ÊqG Ûaë2Ÿ+ÃœÐuúF¦)§¹ûiÇO¾cŸ&ÌLæßz¹aßFuÂ,Ó<Ñy§7~èïJ™ýv> cŠaŠÌsgL·¿0ÿ¥5|รë'L;ñħvÛ1vønÞoÿ¥»à ‰9ù„iÓ§O›rÜ1G:lWoË2æ¿öI÷9xŸŽ›zâI'±Ìa¿ß®¤Ò3l¿Ï!†9eÚ‰Ó§Pÿãï»CÿRïDÃÜ÷ Ç?uú‰'NŸrÜŸ&ŒÒœÙK™ ØaÈè#U¦aŽÚ;í[êI¸Ñ6;=ô(0ÌÉG;h¯D™lÚ–îºçaStøÐ}ïo³I™31·%æÐá#GŽ¶ÿ>hÎÜp³í~·ûÞûyÀ,3ø›²šþ_ÿëW›s?ºûˆaûíµûŽþÖåÞÄÜ2q÷½÷è‘ÙK™-[8™ÌýöÚmûöÍʾ“þ­_ø_~“d²ç®æ¯6Þrë~Üfà€þ}·Úâ7Md³oCý~æ&¿ícdn³ÍÀßÃla&Éìקuóo¾s`½3Ûƒøÿ±ùVüèû÷ÛzË=É«T¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©JUªR•ªT¥*U©J?*ýj“ßö°Í m·ÝvÐ6úþv“_5e¶lºU¿†Øf˜ýû|/sfö,s#–ÙÆ2·ÜxÃ^Éì7ßj“–¦D¾;={/™"³5Çü—ÛvØcȈƒ=zôÁ²Çm›nP*bƒÍœï?rÔ!cÆsßÝ·íÆÿRÊÜpsw§=•yÐð}÷hÎÜÂý¯=‡0j´ÈÜ-øëò
-ì7ôø¿Ü¾ð¥wW~ú™a¾ÿúâû¯ÿó÷Úºäv»M8óšyO½úÞ‡Äü˜dÞpޟʘ[ î<çÚϼöÞG«>ûœd>{ßõç½g9óì9óŸ~õ½>5L’yÿ åw'æµ ž¦»¯ZõÉÊe¯<5oÎÙƒ·êÎÜz¯£Ï5wϘOÏ¿öœ‰ew'æuTNf¾ûÊS÷^sVÇ­ôÛ€‘ÓgÍ_òÎÇ_~½nݺ¯>_ùú“wþåø¡ýK*pà']ußóË>]½vÝ7†¹âµ'î¸ô¸ýúu'öÙgÒÅ·.|eùgkÖ~ó•¹ ³ß~õKn_ôêŸ}µî‘y{¹LbÞ–c™wüå¸f2Á\³vݺµ«W½ÿòã·Îœ<¤o sÿã.½Ý2׬ZþÊÂÛ.©—Þ}ÿã/½ý‰×ò2o¹xÒ¾9èàS¯ôÕW󥵟¿ÿü}³¦XRÛrúÜÇ_ÿh0?{ï¹ùWN> ;qÀ°)WÌ[²lÕ×ß‚¹®¹Ì#¦]¹à¹÷>[û]NæÔ2™ÄœÿܲϾþΖsÁ¬i#zbâîß~ý黋ï½|Ê°2¦i; rÌUï.™wEù݉ù¼–ÓÈ|gñ½—M†fÖ6欛Ÿ|{Õ:.Øw_}øÊÃsf¸MI¶zö-O½û™2×|øòCל|@ s›O¾æ¡—W®ùVe~ÔLæ ƒfÌyø•×ñÛd™=”³Qæê/=xÕIe2»1W6»û ƒN¹ö‘¢Ì®:q$šDûa¾ýÙ÷¿Ô‡]ûé› o8mÔ ’
-l¯wÇ’å«3æ]jsÛƒO»áñ7>]«»®©LjÔ ßüT߉È<¨Tf#óÍÇ›Èl`~ýñk^wJ©ÌÆ»òz³'sUAæµ"s»ÃÏ¿ëùöÍ~óù;OÜtÆ!Û–Tàv‡_p÷ó+¾²ÌÏÞ^4÷ôƒK˜m£Ï¼éÉw>ÿæûeRó*ÏìA¦ùPdÞØTfž¹nÕ[ çžV*“™ßæîþÄåw§r¾›1Iæ "Ówá=/®üZúö‹eOß|Ö趒
-tÆ]tÏ‹æ˜ï<yÓ™‡”0ÛÍ·þÌ2Û¨{Ù~è9·<óÞ—ßõFf#sÙS½“iªúÉò××]æ»OÝ|æ•éŒ¿èÞ¥ÙjùîË÷ž¹åì1¥8þây/eÌo¿\öôßËaì¹·=ûþêï¾_&1ç˜=Ë\üãdRµôJ&îÞ{™RÕîT-¶¿únõûÏÞzΘö’
-t˜9ÿ¥×f·köÛ™nuÉrÛ+ô ³‘¹~dÒKéÌ÷ž5wïµL­À™ó_Ϊå»5ï/¾õœC›U`ž¹[z»óîx¾û~™Ý˜ëOfi62é¥üP™Ý*pùâÛÎíe6}_ÛLfIÁև̞^ÊŠ™?øE»f.èe60›DµóîìV°ò‡md®™M_Ê?DæOªÀ[ÖC6{Øó×Cþ#dþ”O¸Ù§QVåŸF÷‚ý™½}ØõZÎ?ˆ|¹:üFáŸEfo‘/‹ƒÈK½éÙº«1Ít6ºÝ’n£[³ªÎ3ÿ_”éÕãéfs½½›~Õ¼ ŒmЯþ”‰™È‡½šÊ™2éÅL¤‡©\ó—ÙÃœ¥A¦‰;ã.¸û…•_5ܮ̘ÐÀÄ$½têMsá§ssážf£Eæ/.³‰Ñ£L¦h€Y²|uöLO¥Ö˜"sí'¯715ZcÄœÕÔnóÙ7?BfÓr62ÿqå̘_’™ÈLë¼õ™e_èo_}ôê#M ªícÏÉ1¿]³òå¯.7TÂø‰nšËܶÈìAæ¶ÿd2_²2·5•»èÍO¤k[÷Åòî›ÝĤ?úŒZæÚÏ–=7ÿŠ©ÃK˜ÛpòÕ¾´BV Xf¹IŸl×9fO2ÉÊÝ ³¼œ?Mfy98#Ï423ãÿ6£fÌyè¥åŸ¯5ø·ëVôæÓwýµ«tQ‰¬ê.]þù×FÌ7_±òuZTÚ¿„ÙØ”Ëî}öíO°zÒ£Ìç^~ïbÃ\Gw_ÛƒLüâÞÅï¬O™_ö sÄ´+æ“dšg§Å¯KdñkÀˆ)—ßýÔë|úåš5«?ûð¾é‚cö)YÃ2÷›rÙÝO½öÁ§_¬^ýŧ+Þ\B‹•e  }ö9ö›]úîGŸ¯þê«eö2ù¢¿3sÍš/{’i˜ßòèÒe÷FæÅ™ÌU?@æÛÏ?tãù¥2ûíWŸyËcK—‘Lóì¼ÉKºÙgßc.˜{ÿ3¯¾³|ÅÊ–½þücwÎ:ýH^ñl¬—}9ÿ†ûŸyåí÷?øàýw^]üÈmWœZº¿ù®Gœ6ë®…/¼ùÞŠ•FæÍeÒªþUw/|áe¬X±¼'™†yæUw/zÑÈü°—2ßû`%É\ò=2¾(å|ý¹Çî¸ò´ e2·Þ³ó¬«ï¡»›rÒ³?|ëe3Æíú[úmËÝÆÏøëÍ ö…¥K_\òăw\}þñ£wܼ¤·ÜmÜŒ¿Þ4ÿñgžñÅçŸ]xÿm³ÿ\?¸ŒùëèÀúy×ÜùГÏ-}é¥enºãè® ææ’—.íQ&3ïzø)–ùä?^æâ'¸ýªóŽ;¤Œ¹ÅÎc§^tÝÝ?™Ï<~ß-Wž3é ž §#ÿtÚ¥×ÞrÏ‚û¸ïÞÛç^qÞ´#†øÿ^R'#ÿxÚ¥sn¹{þ}÷Í¿ûÖë/;wÊø}½2fŸ=jSλrîóî
-•³kRça#v·R½–-ÝöØwèð#G Ûo¯Ý¶o߬™?.˜ûì?|ĈáC‡ì¹ksæ÷ wÙsÈ°#¿Oæ†Ì:â{e2s¿õ “Ë9tÈàƒ¾Íü†[¶pvØ}oº»‘¹ïþËïó›\KmÙ´µOÿЯÏo{ò¶Ìýûm½eÌÛl«¾ýöF&˜½–9`=ÈD9û÷Ýj³&>ÃH-›ü¶O?T1KýX«T¥Ÿ=õÓ¯ßnßu줱-ýÆðó¯~;Çì¿Ç;iÜ1Ø×ü³ÝÎÇLÚuBmÒ„£'Ž=¦«u{¹­ÛírôÑ­wœ¶î>ùþ§«uŸ±ÇõŸ­Ûšgþ1ÿnþI[þgë#Iü ó/Ã?f“¬„½'L<jÜá’Ç4ˆrZÝV§Õ3ÿ> Ë\ìi.Ž4ǵºNë>­£qZ7ðÿmþeþsxK$7.9w5þÚoÌ6“Í¿Ú\?mw?m5ùí±ë¤­¸ÓžFý¸í^ì…æ//i}‡þ
-‚vÇ+`¿Gþ0jO’(Îá{3ž¶G~dDn{¥ná^9LÊÅr3ÜHr¼Ø¹nþŠcƒçï™R:qÒš/—b,7{ÅYnöÌ™Œ¬n²{e˜–ë÷Mê—äN4ÿ8æuO<zbkòJÜÖÿˆÿìJYÓ¸Ýõâ¼ÄÎ"lož¦í~˜…2:^»¹YÐí9Ý =ý¨Ûsºq{ìQA†›¶§aT¬Ó +>g†çŸ³€Ûw›Ý+kY¹Ší({ŽB;ÊÙŠÈ*ÆÞ)åQ÷šmþNÚ°Ýsƒ¤ñ5d°­›0n‚ÐÏWaä´»iBb¿Ý‰Ün-Û·ØÖâÄ4ŸÂ°H¡þ-Z¨ý ÕÉnaë-+L¡æ³¢*>{H+ «
-{›”¯øîuÙCÅ»~»ï¸Ý*>ƒsz{è{^¡í&í^R€P
-]g·úl^ùF|ä:ACÝg¨Ö‰µû ¿ ©8Ïo¼4m¨x×i£ ±×1­Óõ }qØ'¡åî!ùJÏÐ|çP©‹ìZaYIòž•:_ßÙÓ)šÕ€Þ!‡ä*»{ý5¯ëØi½ ±®3Tk"2m"õó5™æøuzí‘ÉÐP×釽ÄíQšx¹;dH¾®34_×9Tj"»ƒÖVV’|]g¥Î×uötŠf5 wÈ!¹ºî^=Œ°®×øøÞò•ƒµ.\ÓP<'__®cº¾°±¶Ó =Lý¤¡¶S£NMÒÆo'Còµ¡ùÚΡRÙ´¾²’äk;+u¾¶³§³ßEVz‹<”«ï’:ì¡ÂÃÀ Ô^·
-Ï`[ᦵ¸žãækÜ4)?.@(€éZC§ð&ðLTÈ°
-k«MUÝB«^\¨qU mVÕ²s5žA…·p±Æ-œU…½IVa¶0Å·/Ô¸}>›ßÖ½‰E
-ÕÝ­{¡žk;Cµ>ToÕ*Sõ6_Ùª çëZfÍ'*u&ØùjV°PÉ
-Ú§·²m Ù*Ø–µP¿ö‰lnûÔö)Ôo·:ë…Þ؃(jÛ›¨¦¶IŠ›¯_(»IP¨_Õ‰5ŸªÍ™ä É×°E UlQ[V¾­%[Šb#Öò{ }ª\§!Ožë3iè2êíû•íBg Ôƒ*£RQª±æêWUÛ\õªþ+*ÈVhäêÖ‚ùªµ >·­UcoŸ¯W[Ì|µÚGQÐ>¬
-·@¾N»ÕÓ÷ëÔ…*Í@yzÕ9¥vT1ÍU©j°¹*U5W Õƒ­Ð ÈU©óUjA}j+ZëÅÞ>_¥¶˜ù*µ¢ }Xn|•v«§^¨Î…:Í¡òüªZJý¨þ™«TUTs•ªÚ¬@ªîZ¡«T æ+Õ‚úÜV´ÖŒ½}¾Rm1ó•jÅ6qû´*=CòÕÚ½®z¡!ë5Cµ^UmÔŠUí2ߨ"š«Z«¯j>Ui3É’«Ý ÍWo†Ú°òm-ÙRjØ–·PÅö©lnûäö)Ôq·zk^ÇÛ5¾Äo cÆòŸwܺóäIG·²ñÂÿŒûÏÖí†N:fÂÄñÖ8øчC&ú!‡;î˜ú¸ÃÇì5®k “ŽÍ™ì݆4¹;¸v`çÁ®C‹C»:;º×Á˜]ÆŸ0QZ+xøú±“&3î?[œÖÉê\Ëdû?‡¬ÇNë®æŸÿn1 75ã^Øjþ0Êã?4_½õÐI"óGì˜8q:¦¦Í¡—個«nëžæòHCð½0i=®5(®=´™×ÈF£ FadÚp[趧iìZÈrÚÌ í„AÜÚRdZEÛÎnÚLGK‚0È ]*;Í È¤ió i†ÔÁrL“h`¥æNö–6ŸEj&ŸcºÙ0)²ÏÌ6HQJaÛ52Ã0´
-MC¨q.£œ8©C-Ž^/V4—E8[™×´{,ã€r£@½ÅBm¦.ÓORÊ*ß4b3Þ³ô 5O¥
-°d7@/¤œˆ>ó «#sí'¶ÎXS1„B­ Ç"uf9IXd郅IF¢Ð„*ƒjä¢;lgm6˧—µÒ<+ …:WîÀq‚b¹ý k–aKÈåñáÓ•/uà;¤í%›u9(r-e Ã"“Ió]Ór†›eÊ V›R C9žÑ®RÅLEGKã\e‹–cùÙ ö ù_ÂîM‚Ô™EƒT +äQW´2̹‚¶8Ôkz>KVsQú(ŠÓ(ÍhÄ
-V’žÊ\€ŽV¯E£ eŽ‘¸<xØÑ<ae
-J…iòìÌ|ì
-°d@æs3jOœˆzÉ=Šb@èk 0G÷EsdZRÖ•™aó¾ÀveF¿rRRžBÌã :™bV‹‚ºkLj‰Õf^D¶Åp3óUR÷ê@U0­ Šõšû¦[/0b¬\˜1‘â!E8õèE–8<P7ˆ9ýÆm$´ƒáóγÌàHƒšÕ 4_huÂØÌ‘±ÌˆE˜r»Q*€‡`å
-ÙnjYÐ »@r#ŸtîØ Ž/* kÊ—a¬•ÅÒãDÔª4ŸED{˜™gÁ˜a¦!– `t‡À\LŒëæ1¯¶X—J©§0%MóÁ,“V•/1/ŽYÊ3š1ÕY—è©›QxÔ’ ;~ z$לiQwFeK0†¦f
-é Rêô2÷OÒÂø(H¡³!Í(ò`¹ [ Su`z$2.ðÌFòÈu Y\7ñò ×|“Ôgº´ÜÐÅš<*D
- •+„U…)¨N±ÿ·å`e-Æ2T
-ÑyXÑ“…)ÍdŠH£¹7uÓ¢ù(`¦Ô5-­ÀIê”Æ5Íe‘e HéÀ
-OÈQêÎsHÙa󢩺×YÉd2Í?Î •dÍ‹c[‰(!#ù鬠º¢U3ÃS Ë¡‰•çF˜Ç‘ß,,ÞŠµ‘
-á£wò‡-ü¦ßiE[!Ë!Œ’ŒžáÃæåg,Ÿ–²ièHÍì%1Û`â{ðô0-\¡Cþò¡±ô‡uÇ|¸§ŒSºŠÜžH¯È†¡@Í%]k+N›—-r°Â˜øFK`#¹\ûIÌxæž0  /)3ˆ—ÄY£7?ðpœli¡º¹môÊÉš.ˆ…U_ñæ¨`À è Z~”Õ2UO"ÌJhm:Áƹv‹á
- „œ»qèÍ“OˆìÁ_ų©rðïÈLŠäªGËåðâ/ Z…¤Å
-êÄqÑcç9bz3_¾nÍeSjWZ`føqeI\ß…2ò¦qWtàH{múží`¼° ¹®#ÏïXüeí\ý­ø @é2c˜³Z»´j±p+£²E¼lÂKpJ²ˆa9(ˆãçhÜŒCôÚP7`ÓÆ+S¤Ö’aôMG€¯ÆôüqJNDlcKXàÚ©‘C4—‘î,ü帼HÖ­$es¼Ô˜ÃúeAx,—?¯õ /¿‘Ë 3ÄAcO¾ˆ#v!…2’éŸÐù’ÝAÆ …ÚÄàc#Vƒ€µD4có:ì7@ÃbŒÅoÅØ3È}d,‰iäÄ2rÓÁ¶—æ ÖM‹ÁŠ”^“‘–ÇÁ‹x-ÍY3èu ¶þLÉØÎF’
-Ùƒ.g–dÈÿßKB‘åÐZ™x¶úØ$Ç£*ÀÀCž9’E˜åż`œ±Ì Kck`NSŒ8Òëç‰<¯Àˆ\vŒ"µDsÈ5ç0*S Ÿ7¨.T§ä;ÇŸŽ¨3Ê‚>Dv€.°Â!#$¦Ëh}ìsEÓY †\‘ÇÃ8¯\+G:8fŽWäøŽ‡êqaÁâÛ3F~²éS# Ò #–øaöò©Ñ¥¾^×'qÃ4k–c‘:Xa’“eŠ>Š
-ÇtùŽPI…L°w´ ǃ%‡!
- |´2rðó²\‘l±Ÿfï‡XŠï‡|}£ #ß½,pÛIvdH¡-ÐÌ‹óïÐt¾QˆÑ+œ,J4T=cZ£Db†q?—Ë"ôô8VD]YÇíÑÌØË™ŠÊ±O®Œ4£MØgëü´iÜc`Iæ£ œ@•d‘:³<öÙVùwp÷Φfª
-Ïg°$ÿÌ„xžû@$_p,I3¤†|4/²ŒÅÖ,Ò•rù‘|~àYNB/h.ªÑ3ƒ˜ñØ%“œ8œ(À^œ{j2ð‹Ár„Qh¯$F
-žÆ€/Sz‹…ÚàfHJ[@'†Ë8«’ ¹ŽÓ-4âVK²I3ÏÅ–OK‰Q¾ä4ü¬äÂ(”Ü·_µ¾?Z±‰yÆäÃ%”i¾ï†9 3PÁDe´$j†6§"5d43Ó Ï"µîÄÂã‚)D¹7AkL8æ²ÈOwO5zO§«×žÇû3z©M®_}ND»a´"’ ê²Úhl:uþæiY3hÁ‰\‡³±aÚBmnľ
-Ü]‡µ`Œ/¥ÞÉeGΨÍé5EÒ›é íC5mÍ12¤àþʉcDÈ1ä ÎHáï$I9p‘²hÚm¾Rs»ÈT¤|ßqˆ(E4°F~F²ß0HHÇðµP!
-Ô¹s8êüŒd.”y]~‘Q|%—FWR° %ÅÍÎ* ã­kŸ!$‹ð§,œcÅ.Í4]>¤‡‰üÔ³åÐNrõà#RdZWªú¬°º§
-°ä$õøƒbCƒ’,Â,ìÀpiæùrÿ ö9£¬% É"”ѼÖ4ðò,r™AЉvûZ,ÔF³rÒì
-²zf£Rá »©Ñr½F,ô^Šcb>¹! j:ÓwiY2Èa({ Õñz‡^Ÿ
-ßßhµNârÇÆnfšË",Éwý°È
-<óŽ¨ÿåÀs¼àjÊ••ÊíΡÏ7qy[€›h³ —"ž\p‘Í ùˆ. 6¬\ºZ·ÜîÈa—ü°Œh3´¤¨·X¨ ï)â| Ìý†áߦǮ¿\ÌÀAØ!†,‡> ØãÄ0IšaÄJÌ€®ÎÇXa&cfîa®&Ç¥ÐÈA/G¦ ¾?+¯ŠÑÍR¡Ìèð#EIšZ †™Ùc‘#ÏO®Rèp%—E8Æ?—l欔º4måNå@€ÇuÄë‚BŠØJä’‚NÊoZ›|/T,rsÓ’Eè½…4Iò¬H>ÝÈöD
-‘Û(bò u¡rðZk$ÅA€:ç "~[aÆÉ"Ì
-]Z+4êoŠ"Õ%Œ- Å$ý·Í%ÁäëûUÔŸ¢ÀbŽÚ¸Ö } öÃæ
-lò3*°æ;5“]·5‘%¹Î ¢‚º¤Îñç Çƈ6ã£?¢ÍÖ°V1Po±ºš$ äs6¹Žñ¦Q†Ð(„îÕa(ÅövÍeþ0a¨+° ŠÄbs–Ž<÷B÷€_¨Ícnô¼ù!c¥ì‰¹½’™áY€Ëƒ}˜Nð=¼0—IÎÏó|ìBJ=‰CQNà^º^^¡è)È£&‘ežÄÍœ‡K|¬òæx¼D<rao,täx©
-HÐÍ<+à`¨üé“&÷5X6{´‘òÆ*“‹xHí¼õψ–
-@{ÚH;âÐûd¬ åJ
-2’Eø~ˆŸLêQ
-ý,¡gþè¹ÛH0Z¸É"¢¬“ªS`EØaæÍؽ¬å{qØ©‹‡ÀHž'y›è! 4¤§¸—Ÿ:Ä^š]ó#pÉÑÏ´JֳͬŸv|(Po±ô[zñ|3'ñ`íÃ8 «'
-p¾H¬%‡£Y“æY.‹ðôævªô0bÝq`ÑðÁ/pvl‹°¿×æ²kå^ì†Eü³1x¢ð§×l,–öàNľΓŽ ›ª¥(â`
-ˆÅ´†n¬8®ÞŒ¦){
-t
-Ȝ
-ÓÓËþYYƧ׳¿ÌÉá? €¬U¥~X䤲jÀ‡·h.‹ðZQÀÛ9b*’þ@ADxÖâE~l!ËiKѬujƒÈË4yÀ1Ê'PdNoEŽÃÖøTö%Ø\áJU(c™ùP˜à5‘´èFØe¡O
-[8f{sÌ{ri‚¨$‹pFÄ#:/¤à¹`L Ý8ˆ·!dIa3†©X<¬0²å´!â§'Ö_ŸL c´›¼“øî½û2,Ç"T,Z¼<K  *t²€ßŒô""'FŸw'úp·Ï jfbK¡B$"®ï³{½µ Ñn(iü®âTv¸
-ÔÁaÿ\l’@g/$‹ðwÀ›W3–øÈé'Å·ã© éò‰Ÿd$‹Ô¥3”z‰0uf©ç0”-ÇÔõEÐ ysbB
-@îc±Ôe*¶¥”L´´$¦$‹¬÷‘„âýúÁ˜Y÷÷~ñ‘„,ôµÐ»#[G'EÃ\e)\C«Ðême…H$_Óر°ïÂyÙ·×´Q7¦?©Úô\J±
-ð18l5¤Ëv™NlhwÔøêðüÕÒ,„­å–çŠd<ìô@ô(ÝWCk9\ËXô°GÛ—˜8tM¡¯Ñ¿Ç6XÅiÄŠ¥B–Cá0hs"Hd:vý sÝ£ij‚/D¨--Ù\ ŒßÁÖd~”C±ÛŸ-ˆ4ßÇqYf…j\‰l-¶y;åXÔ sŒå ‚×âI ~‚<y—9‹Bmžzç$ª]k.‹Ô6¤g$ZÛ㥦ÌúMß:XÅh”ïép¡¶KÉh!Ìî@–:òpèÆΊe,x0ñ.3
-ÙF==YY}‰ B«úrà¡,TKu˱X¾=VùZж¸HBZ!C¦"YÀ@á#ÞåAÎßØÁß™ÉæâJ0nh9Lí®Ä/k“íÈ®g¦eA8&‰B±‰H‘N²+i‘“zh‡ëÍ íšFÈR) ½©NÄ–A°E˜pŽ—&w€%Ã'Ê—ƒ»
-™Q%‰„ò¢ho!¯1$B!Ë¡Ê„ú×Õ‚%?¸$(F@­d¢R¹èܲzˆ0Å:G[,ÁÜÉSŒâñ"V‘†ž@¤ƒµð@8îP¨/NYøúl´Ž¸méu wcý8‘QC9€d!c‰@ˆ˜;6Þ"Ÿ£C~§xñ¡³Ú\©![ˆÃ ”bÅ’-ŽqÄÁ‚LoêZÈrДhsðúvNõ¨Ú}BAQp
-¤žõ¤à/6ðsôi3Ú!" ü¬x+B¦4„] ÙÐÆ«–ëY×C|ì?'g®²ùáI%‚\‘Aß0Îî¡ û2(D»QÈÍ3•x…®^£×t_ÍÖ–÷óm`e‚‚Îa•ÂfЙAm—ƒ|‡_+æ"Ô¹­)5#Æ>:½ u« ð]O;?È9îèr@…Ìû
-x}9âxÒ6“5“—Œ,'ˆqþŽëó‘ˆ#à d+c°c£àÓèdL|šås¡@5<þù‰1ÀJF Õ8î ”2Îx»ÇñŠß¤TÇÃùK”•Á–f!Q”"ŽIð!”.àúWŒóÄÍÅ0r›ÑB(]È#[¢Ÿ¸ƒ£½üÄ"»³î$ŒŒd»€EËÅr#y9óryÌ#¤Žk!Ë¡Yëò¦LWTŒ‚~ §§~:”¨ã\4Aá“„c†Ú5K³¨1.Çq¤TÕ†há×QÆvQÁ9¶-b&S¸0É!ZãÂàŒØO%M±6 m“fŸ8|–´Ò®µb9’Šítú@LÑ`¸9uò6åùhCHaåƒwœZ–'„ÒuµØøÎ
-‡Ã^d‘¿$·
-YŽùô(Òˆ*dçËl“ra<Ší|”Œ9È6ïE‘,"eà
-Z£‰ŽR,`ÄÆì׃†
-=Û
-ƒ°ð­8Ô ¹0®ÏGåQ¢[·R ®¶³¯Ã"8à&źÄÐò‚âÀþ.<J:-àqN9#B¡TŽˆå€hòÚ¦ÍäzâØHÒÈhË¢Ò$b¥XYü<¨zM/VO jGeèõz6éçÆÙ4ð}LcúÁTÕ)h£ÓUô3Ž[rfšé è»$m1ûy+7¶_Ónuòí¢~ÛÈŽ¸‘…%:¬!à}=̾¤³OdW°B
- X+—ø5˜âïT¡:Q¼^£òpLC§EÈTéóŠ}ä©«+ö$Beé5÷G¤³Tb+Åu6»¼‹ Ý7{•)‚Ë 6i Pgíð
-–Cc¾)
-½Íe‘š<M”"c¨)Ônäç<°”d‘:XaÄ/3c™ç„³•ÄÍÀãP46…,G”ó‘ŸTW««±@ÌÈç+‚͉ˆgª.ù6E¸öŸ]« ’v@DfäýZ-É"u)Bù(©­—#+d9Vê°Ç Œ,Q㢲+:612‰ƒ]
-d9miNõZŸÆKý´‹î]»;5 õsz
-K4yŠjGã³{1BÇ_,5¼i&j-vg¤]CTk’| ÇÍ Pü²9”IÁ©*)¦ÇèquœLáÏ¢ ¯ÇñÊáLÁ7fÒry÷^gî
-õ²X»Ž³Ób=.F°S/#Y¤•ÆC^ºæ5£„O;dG> p牺F+m×õ9•ØOñ\öRD3çÓ.5—Ej4Ü8‘¨Gì’H +PArØ=Œ5
- ÐÖd ìd(•À¡jgI©·ØÝs1bVøZÎÔm0|°ó¼¬!Û=xŠP1)0,â•…Üm`3]r¯Pc/ˆ¤ö|®@ÚCÀ ¥d°ðà$ËmÚ””®×ô5$ƒ \ó¦Â"K–d{¹ËQÍ-PÃÍðgžyÇàz¥¹,R“‚ó‘׌ UY!ËiC<(ŽdLÛoá(ãd1h¯J:±‡O#‡a)d½ÞoÛnpζç7ý²ƒ7›Š`ýásS:-B4¼ç;Z(XŒØÓ¸¢ÉK˜âJAøáHÍP ŽÒ‚*ôxA“W#ÐßñÐ#)ø
-æQ
-Q2aQ–
-ù’è5ßÅKenE&ÏŽÄrrí—óK±8ÜÕ¢ÛðQÍq 
-k–A[L"YaàfJÀñ¯€(ƒ²¸²eÁÒSøÎbâÞ „,gŠ(ƒÆ>„íDû€7&@ÔCàåËi^a5 äé³'KëÖ]
-³ë†ÙIæX8á`v:‰QŠd]Ÿ‚cÑ¡ù5b]_ⱺЩ[•b^×$àŒr°8±'-D¤ðµKù=Zº }«áwî\éBŽZ—UZ&Iué1E<‰ Ì×(/p*ƒV°èh”„×k¬Þà “gÀÿ›œôÐ$‹dÙ¹/ˆížz8×µˆ2È«›] À\ª­Z¤8ÛÐÃ"lmÀÉVˆ“í‡DË3ôcK¤e¬$×x&,Ö72xÖ•Í¡´®¸(±œŠG (‹€¸IO*Y,€¥láŽÅ)©ƒŸ’õ6Ô :-OzÖë@êÊ.ÎÂ8ŠcPÂfçÃýgÃQ[ErO‘6X ãÓ‹ããy þ2©g¯ÑÀqôI#ÇÈ’ß ºÞ¡¾à€:z^&¤¸l©½ä‘ædÀ -Q z]'¿ËŒFV?¶l(b÷ãt´hH ‘+ö49|.Ìë°ÿîÉyîöÞSãŠ;t,GÞY„ûµ·ÿ&ç‹=tɬƼa\e`ND[ºp7t䚊°¦Xc
-UŠcs‹Œ¶Z@ÀZ Ž¡óùü'„A" ®^×[ñÄ7*ÊoæD0ŸSÄ"ráÓkä‚u+¸"Ƕ·)ìãåÚ!!´€«ˆ2l»d5ÌÍC¤‡98‹£\¼hôº†,¥‘<Þµçy:½H#5é TDpqP)_×Zi‹Ä#­ƒ Á¼BaúÊykå¡[¯!»¶ó DË f °Y,€<1ÇÁby®"y7ZV‘žì‰·µ&Y,
-3Šê$Fâá('à
-'æ`öðõÇi#„ÐvEè:dßa4‹ –•®Ç 8r€+ø±ó—‚–O‹¿òÑAØ1I§GòMŽO Ã}~·@”Aö%²§s&9&F OÅq=ŽcN!ÅoQ6&„?ta-ŦšÓvöÿÎqt˵® íGÒ9­M(€‰@‘q0‹B[„Chí±kǹ¥Müb‡ N³X Fyp¦¹ÐÑ0C¥•3W¶
-­H®ÿz ©|PfÆþ04ƒ 3 ømÀï¥lŠÖëz‹ïðÈŸc$s‹Î;³,¨Qœ6„“ž` ³Íi«IÎ1¹äìÍbH‰È k³8úÔwøëF°h
-« ˜òò†ð' "øc?…ì¾\×(‹ý¸ÀÐ ”Ø i
-¥Y,€<| QÀQ¡:€ð†#Ä*Å(}fwGdC¤Ïý…2,`2yú•ÀH㓪ÁÌÂÄ^£l©FºA|ý’àCdû‰púŠ"Ø à`
-ué±¥š!º3†ÏˆÍÅDSÏ^×( ûäe ùèQv%‹L_&z9ì°@Ñ`ªŸ‡Ú%klX¡y÷-ÕP :²È%nÂÙh“jeX`=o!]‘>¶‚zK1ôè3ÿg80¡¡Còy, ¥Q)ô1±1CñŸÖ“8yz]gsGìæØ÷`w½j ©Ÿ"4{ØâÀÃ
-两I*âééT/D³X
-±Ë~Š-ŸÇ¡8ª
- ¡X
-
-+BÔ…â…Ò@MR„VX}^ng/GÍÒ–T$Ó:ÎãÉqðâxü,ë5²p,ªUÁ˜QL5"‰jÑiò”VLŠ93ï›J8î^”m­Jø³²Œ˜µ
-à{N–Å5Þ}í&1*\âCÂ[‹öTÓ¦xýÍ“p‡¼K@É¥ã„bz8M6Æ%E0à¡k>¢Å /
-ÒÆKj.WJÄÚW$Q§:yˆü8ºšR,€,{²ý¤ƒ‘0±ˆ2àæè‹c4ùÑ–suDâÍc> ŽŸ²[Z§d±@wkEIƒY ðž«ÌßÉÕ£p¾kXŠ°­Îƒ½=ÇÁŒÖ—½¨:ùÓk”„ÃnŽÌG\bÊ1äIʇù½æÕ0 ®íàaK˜°šÊ‡«#úDÄÇlë)Lr* kä0ÃÞ{¼žÂÊ[d”bäJd˜×9¶P®yŸ56€BEåýpŽúo¨Ë®éwDo˜·áÈE”ldÐì¢ecDût‹šÇ‹ÆA~‡šø.Êi¥X
-Á¥ƒo†8E.Çî³×XvöÄcÊ”¡×èXáû£î…nÂGaêuGKßË3Ø/šÂ48žÍ¡×5vÿIÜŒ‘Š¹"•ðuòþ÷€7©Òê$Y)´¾ŽnF:Ƙw†sXBÞ*œbÃ9#t0>Ô®ëB²X
-•jQ€Ãw J‡kÃ(;äH®×è)Ov;ÊÁÌè¹q™GáT|ÙÀ‹àŠÐÂ<ûuÓ–Œ ||™5tÿ>ïçŽËö-2½8‚AplÔ@N‘SŠêþãGrÜ í¿E”AÞú.oQK% _$|;,‚½P|ºäzíàõt•bïð»ý¥;øÀå0ôùÓ¼®“ˆ·\2¢ ÚE†Êî"
-VÓcÑ*
-ÿêFöºÞ±,éÎ@ 17e/ç@<à# ¾·+«Á¾œm¸r°¡BˇżôЦk×^Ö(cY{W†¨-7ül3!ˆ÷çË-¥X N
-´ñ†?á©z))³=IJ.#Ê€žÊ'ø| ;}­¢Á+B1ìp lŸ}ìÒ˜ã©ÔÏKìu­EÚ)plT?åq8æõAsÓJ˜\Ö[@ì3v¸ÂCaM2•À=x¨ÁQ‡w…“‘Ïñ˨8XÞÅC‰ŠÌˆŸpG‡=´ÏN³X
-mÑ)^hï-2ã(ÐÐ  õ@ÇÌpú*QÁ,@Á">»Å(bô<” ße†ÌÚgl” €Çóø £ŠY€#hœÅÂKOXB Jäâó ³à Ê0ˆh‰G÷pá$r¸ºèLBN?7ݨbèÖå¶xó £ 2…Þ gôíV¨Ý0†R+¼
-àÑmÐ4‹S·¤ž+™˜%!V?ä¡Åq³†fZÓÄ÷ è •@MEC+ƒ0:aÄÈέKB¬~e2²Ûøt)£šJ1P@ chØIôe8~FÄ (~OPBq"J+0H)êÛŒt¼‘±*Œn›Dq!.›š%°(Û«oÓÁŸà"x7>…À8ŠÞ ˆoÃ%ÜH¡ž‰ƒ4´=¡_WÎêŠ fÚ«Æêçë0$ÞÌ ¢ÿPðãl<£½Rø`gã, ¼3ŠÀçŒñ Úçh”Àó‰ÑžuT¿=FË®”k¡-~ú3²,hÙŒ-üŒ
-}ºjT1 pü¨_L²†Œfu‹Æ°}ím
-äÀà’ÍgdðJ-ÜÐcÄ,@ö’Õ?‹‚÷À¢evýãG˜šC¹ÀxFêÜKûOñ¬ùŒz‹J`šŸ`ˆ ’1­ê• ì4Þý-:`Ï(bôNd³ø,âƒõðsö%œ-ú‡P²f ãÕÀ«d‹üƒ. Oã`›èŽqd°8Ñ1 ¾`6’›0hqÒ¸^û Œù ÛЯÒÁK3F³@7`ˆ÷h‹4Ú“Í@±f‰÷= ÅKÝÆà :q4)P6ÑÉÒè®)XÂãëqP cÜžŽ. ¢Y³ŠYÐÃhÞ€…vaðú÷]ðS*Ä,À›,йx®ŒuñÇÚºüÑ|FV• [ôPÀÊÐx6‹½fŒ‹ŸõÝц/VGF
-.êø¦f /Бi¼è?N4€[5tm¬?¢ ­à[ïÑb \eÔ Ðy€ ºj–ÂIž…ÚN/1 Æ"Y}µ­k™EÆJÜžÄ3”±JBéàë”2ÄsѼIæN´»
-Ïù‰´|Î ¬´/ò5˜=ÒÙ”©ÿïÿÎáÈ`rÿÿ ÿ!Ñ_âPorÚÄ ”×fâaÖ'Ä^x=8ü„þ_óÅŸ]Ó
-ÊÛä·ÿטQà‘²El—òŒI:±ÿ°:©"6Ðsõ^çšñ„ç?—çIx ÝuO_7t†ÞÙëÙLšÊ}ÐìöF¯Ã †h`IôM!¢ÎâG0"bçˆôßv¨2cì†ûí;¸Äð[ÏâûgøVĆ¹´›…GL†ï°ˆß
-½˜Ô4½"hÑê¯
-€"2èdn0
-lj¤ð#ÙbÇš\bÔé É„Œ-÷ß¹tGZ¨šlkËÚb{ã.˜ÚR—“Š<.GVgÀôÝ›¨½ñ”Ânœ j,¯u°]ýŸkàk Åáw÷S ý?Ù ˆO¨ú!’s(KõË“‡£óöú×á§> ï§áý§ÞߟCü´É?›ü'Ýÿ`[LnûùLýѧG!Sz´ó ŽôYb*ÂdÃ/9H" ì•­9’úê;Ãíu-jÓ.ÐñÐü=P$wtîÌ0ÑÝɬ·*®¿~U}ÙMö£­ßñj’wÑ›hhó«ÑŸîd…
-Ì-ž_àí'p„iÐú’6Þ€©Ú7«Më-´Þ†«4—m£`µëÍJZGFrßeYÿÝI6ús‹Ð§Fÿ}ÞÔÎQèÚŠ$øuÁˆ -BÔ@¹pðŠBøƒbþ`9xÉØïÒ4ŸŠüS‘*ò Eþ¹‰èóõxdý¹œýO_Îþ4ΟÆùŸhœ?·Ž}FYŸ¶öÓÖ~ÚÚO[ûik­­ý‹Ÿš÷ik?mí§&þÔÄÿMüßÀý·?>íÓå¿ZJ©Ÿ,¥Ÿ'ŸýSO>ûe’òiŸ?íó¿Å>gT=ƒ"Í0†¾l[¨Šü׶Öÿ[û¯5Qµsw_]Z3›¾·4Éÿ¹·ló{´Íá6’?Ra¨™º>ø‡ ýoúS>¾W>¸•||*ïþß$ ¯”›ŸHÓ©¼x.4?翳ãüPJº(íTE jžm!xWÁã+ŒÆ;š#„0ˆ¬9[ˆÉ0)rô«¸þ²Á]å<OR(‘AQÁ£½ç<-°ŒîP Å Óß’þ´-çÿHõ·Naýö¿ÏÔÁ§ü æ¦?SºPJ—ý8£K~ft¥6®k*<eA†DûTÅ_UüOˆ±àï]vÃÜo˜à—…6E¯$pyï'Þœ6`‰6ø•¢Â[D…ýe¢‚-P~õíÛW[UZŒÖnh *–êJ³Í5u¨É‹…28Öy 1,˜'»4
-?äüZëö+&²÷
-Ê{©*šÑbXd8Cr” šæÂ5ÔÕP¶5Úi!‚†q._dI‘cmÆò„¾V3+4Çð,Ã[\~ Ö–À¬¡Xžàiäm…I–fõ~Ì|•f`‘fÄO g[SSC^Y€By-Jp<göÃVUgKÉNr‚ˆV,Dž £Šæht'pºøŸ•»Šî»?ðy:ˆZqR¤mí8-ÚÒq†µ˜CËK‘$¦¬MÁê„(ðÉ€‰ÓÛK(í8%€vhÎlޱ͢~ <-¸!ãnÚìÐFC ¨‰Í†X‚Cƒµ ^4£ ¬°ÆA$šÙì¼
-²HÃÿ!;ƒèf´DA*шL´ÞÀF…[)š¦ôF1•xP/ù®!‘ðÈÒK1†¬ !’øø$`æ
-‹¦ ÁŠÿŽM„=⌆x@lÈ`bŠ,–
--"… B^®©wˆí1h€ îÒØPp˜ƒ  E
-<‹æ …V_u§q»ºÀ#y‡Z$ƒ^,ÐÄH)€)„·>Âî¨6³Öt gXUè"OÀI8»fð¥EÔMP¢nw,zÅÐT,f‚1uxCunéíÐ`’´ Í3 Š T8¦Ø¶q–74L’@Ö´ãph"mZ-^Äýhù¶@ª)¤Æ ŠàDX"`e%!(ƒbâZû‰4K Ϙd(ž£·4;VŸ
-f‡wÏ 7ø],øÆrë,×;JÓ‹Àj·‰¨J#m‹'ÎK#ï8hCÞ®ÅuÞ•…Þ€]·‡ùÛì˜, ëúUeø‰±—è@} è6þ.òòë@׌p ÐÛÚÚŠ
-“  9ô¶´«lñྨ­-š&,6VÞ‚¦,`0^~_¨‚M)T˜¦¡“ODu8žÁŽ&¬8’ÇÞ9ô¤È?t- Å…‚"hZw૲Ⱥ/†"ÐŒcAWÐVoò2ÿÕ}4è%°Põ°†*ç(‚°^E*–%€‚£ÎäѧmÿÀMòhÂA'›7U$+2Hw1,T´ÈãXkN@ö‚1ÿµeøª/CÏ
- ®;•¢Ñ£í†í'P¸e™2b(`á)$A…†|7 ®)^î°Òßü¯!„,×ÓL]ÍeøYO]pX[Øæ0o5P&Ó…M^Ú¾(ýßþ§®§"0•š< à‡c$l+MÀ¦†Ö t"a_I«3IRˆ•$ì cRˆ6X 1d_Eç#ñ<‰µ½éÓíÓ†cÃAYûJ¢€Äã¢îucþ(ìÑpѤáÀ ô
-½1We±ÜN½moÜز²sÅfïgŠ[¯ßeÍòg;AøUóë´«N`3ÿω·4…
-žÍ¥ôD–ûy°lK“Ê™¢—øRM§4Y _ø]$¥iÒ×mò@{Ù€ª¯# U\Pló6À×jš‚Ö{a#¼Ž@?m«ªïýr={ëÀ+€T[jÒI™À½ÛGtÁL,†6,ꩆ^"‚Ð'Ô<aéCCž´ÔÆúSW
-ì8zKc>¾
-i¢Çk¥¡®*iý¬ „v²\w1†‰¶E)Q$ì ÿýˆVÄÑ´¢¾VÉÁüzˆçb£GÑ+ÄQ´ªq+~Gíï&]Ìæ5 äéc¿oüö?´¦“øSõ«<›Á„ôöøÁD"mæDhÊËÕÜtÀ.…i¶º&/€¥±i½L+[U^Œl i±”ël ÙÀŠkÐÄFÚj9_-?¨cìTØÕ¹Š4® æ¨«óÕÛ ~…‰Ú•& y¾š,ÌÖ¶'kf¥iúõKÛÓ¼¡ G[óÜò¶¥Î-ï"7ÕÊ%ܱk¦¦üw:™×!@KMé®–x!rS?üâ&~Bû¨ÞH™ôå6yd¼…–_ç:q¼îÙâù‹¤-b–Ãõ¬ _Ö„Då‹=p3‹´€Á¿=ufêL>‚0ð®)x™ÙÇ”1 ’`þ踺ʬ:J16 `BcÍòñø¬Ð?‰ý¿“äAJ5~Eê¢t‡Ãÿ«§‰ýrôT‡ ²DÃáõV‹¥:ýs5Ù¯“ÃèB‚ öÐ)SìXqüåóôå/Ô•Â,] þó¶Æò4XL”Þß]SfáÚ°î´îåðQÜý³U/RI¦ˆùÆóõ˜ñ|ý³ÇCÒ"æEò£ÑüGéëC?‘ø'Š
-¨9ümù|…m8NMyR”–@CUÔž4iÄ…e­d, „¬•²VHëëL @¬º©&™0&,vh—üj21ô¢~”x‹+ÐÏñ‡fta­“ÎüôN5¿´a¨îõ2ÎN¸Ú\ê™:yý=ÕNؼԓS³ád[Ýo(W¸´‹>¶±Xá`õlBb5
-Œ°··mEþ vVY,×2iõ8‹K³¥b.Œ´øîi§»±B÷U Œn寑NZóD< ‡,{©‚šÜà!ÅQ$s U‹¬Ñ{›…s®iuáa òr-–p¯…r?Ÿ‘¤mÚ£Ý ^Ï”¥!as(ùCÜ°VÔ2Ó¹WF )3Æg…‡X‰bE+'×î+I=7‹žnYÄ›îëƼꌔި®©e"›~ ¹ 27íÊý;,à©O”™l[ ¯Uïgß.à%pü6èeêkÎPq^­$8÷mù‹<1&›Àï_Dr‹üR{àZë<Ù~rU£¨³¢%±¹ŒÍ®e8_›ßçm¢íÏ÷Ö½«
-
-¦¦je‘*5—£@‚s®òYæ´“~x:'®ì€¸èîlì”|ûêþ!ÕÊ„Ûû‘Zá¢É³qþ1]„§lеÊ{ ýÁ‰ +ÿ>;xìðéIrr¤GË̈¿#7ÈñâÎöÈÊ[òìÜÓÁí€./2OÃ'ür¿eKý’=^OSÍs†ûp#õW'.ñÕèåzì•73bžcg)í¤/ƒ/dÆsÏÈ+¢}áÅz=i )Ü 2˜I""ñšrúUž.úé‰ëÜÒ«T¥é~ƒý÷%c#úÄÅÅÚÉÔ¬ç™âÕX„Ÿ>Äž,tJë•ÈÀ8Jš-ö²‹6 ï‘ùMô£J&"þ’Õ¸7”Ó¾>Å#¸­¸’™Ò™³“ Šìð¥tÏ9|F}
-œµû÷Qªë|DÍ&f.0 çwB–Üsîjé”H}\HÍv¿BÎj6"¹óöÀ±pðÅjœ¸ˆ®£Ä ßDþLÿuÖÉ•1x&˜{ÁQ·T ˆî H$rA*{>Œëítâg±þëåâ¤ÙaÐ^-ÍêX
-Í·i9U{JWó™Á…@ån?ŸéOo‘òÜÁƒBzÂÅ;붹e%~—.¶œñ­>œ¸@/äZ¶0vÉ
-ÏÀ'd½œÅ1\]¹ÎN\ùçeËŸŒuû:Dµ³Ãï{°µLºnúzÓpÃ^eW0 ¼‹:]û‡â$¯°— <¥€%‚&ÓÏrª1óe2}-ñ
- ˆ¯õ¶5VKdL¦»Ã¬ìÏ5öùQl©×¨§™ÃáE‚ãü bÝèˆG1ˉë×E-ë˜jþ_µ1 çc1r 1˜Z/{êνð^'LÌ CÏDûÓñ æŒíõ¤â/ç³¾~yƒN¸©í0Œåã@úw‡ÑØOþ þî0:´vÉQXfåÇÝŠÙršó
-Q뙵5¯bÄyì%=™µ3Ä@Ì”tî¯n]©Ú<]L3«4A¯H¦9¤aÀ/÷O)1V™¯LqO¼ð|Ë/6K°¾œ‡¯çmé͘f->+ß:€ììf‚&
-³YµWªYè¿‚°)®QÉk»ˆ:z<Üǽ+Jqµ{€_œ+De”!ƒÞŒˆ¨Í.fôaÌ'®]¸7fC ¹4hå!|â2ðù§G¿3Ÿ›­^(—8Öm |¯r‘¾ª'AÄ~;½´ðàê²ÙF"§¿0튪™Rvrdã
-”ÞÊ\_礗a kß Á/, w‘5ï-Èø â"÷–½¡×ÚYOV4'zÛ\Êí2rsK^àÜ]C‰ArsþZ;2-c±€›³÷Äõ~þCÙ Íÿ²5wÌ^KGàìÍ€ »
-y·šµÎÞÐ}ä'tDc‘ ø¹ÁÚ@¸BóçôS²}º\bñ‹—[oÙHžŽ„Éb;Z€0áÈFÆ´*‹½UÄè„ }ÄqÚt;Ø|6È¢¤`¿ÚÒw[_Z-V§±(}bqÛI5Z‡Ü 7_Â_¯ÖÆ°ÆÆÉ¡™ )ih›2ë]ùâ0©#:Ý(AÇšîlÞ¹Deé‰àñYУìY.ë£&[Ù‹ÅU06(v‡Éh'ÒIµ²CŠú£ü‡<Ç…;ó&µ¸ÎƤê¹nËãßÑùpJ* ‡j3[¸M²år‹L‡¢òÜtn8ìG´Ü§Mèõ9ÝO€)ÖâéH/VÙp£JF/_ÇóyÅ9ò³Œžh×Ë*¡·f,íj•Í®õ À}õ¬Nšœ®¬í+â àªo˜‡Ý½´×å!x?4+#žÍ¶@°E¶±…»—íˆéIÄG¸Ã, eŽÃÍ9Ò }3$G×0[2¼ ¼
-GNè$‘ì,É#µ¦ ˆA6Éä“ÃøÍV
-ðÖeÕ–úÛÔc×| QÑN'_+`äÓÎFÈ ©üÒŽáœX¤ˆ³Ë¹%9‹ˆ•öœµ²~%ÒþA9 çlÏ­¤ÔQ ʘ9öQÞ`Žîvä€Sšî¥jå¢]Ÿ[Hj£KWŽ\ïV¾Iõ5ɽºÀ|ɉª’{º]ƒ€&t»ü…»®Ý¿b“Ъäö¶÷-cj®ÇùØtÕçSøYé*€L†­4T½ÌøÓ»\èV¦™²p_y
-¢\ø¹·²tf“ìêÿŒÜÄg¶âǺÑ1”ä ³mi‡oO*`RM§ûš ¯2£dXÀI¦“RS¯•W¦¨ßUàìǿHs0EáÉõVe­·Jl`¾€Š.öQÔb¤dïÄdç*àF»¦
-é¤\@ýqà{6Ù~|óz̽ôJð1†Ó´;‡5 ܺëEác=¦„+z~x°í³”P€_¾8|ž|ÁNÁo?r4
-®ñ% ~/aí˜H­|De³Z€!½Ígdí >^ÃÇPî%ñæ…?$ðGdÓÔ¨«¡G4ôŒc§ë÷@«>Æ¿ù1á'ù³ì*¦+ç~: ÕS­·Ð7i?hí}ÌuO£÷p;+[^ÑOˆdˆ˜ÀSDĤœö•-ÑU*{z%^ÚdãÛ7 _Z’ã.”!×
-ÝÞQ9{(Mƒ'.ø±2ïz 3Ï?¾Ã…–·§ ÓéxF… 6(¾
-çRãÊ=¸Eµó°BÂ@–ä*ùKŽ>]õ¾}‹Œ/Êö@b©ñ:…fQ}cÑ°| 0­a¨øÜÝ ŸF9ªHy`øé=y½Õ›*P!ÅÐoÁUª|ë>üÛk ¯î‘i>q!㜞°®î†¦áôp\]
-AÙ¶»í÷cÔî f/í§T kwñ`êºã5Ⱦ'JìÞóɽݗkªv9¶®i{°ýÔ¶‡r¯öp7ä³G†JÜNLëWvrÁËvÚ¹<µÓ«kÎκÏ.ì\ÐùdçÉgÍ.$«´]Ì3E{ôÚþdõz€Cö¸»ÃÛ‰‹šý¼Ú“32`OGýY{æÞùlϹµS{¾þ–´]ó{©6wÚË¡UÊ^‘\ÏöËó€Ï^ws%ûU7¥Ø›¥*c¿&n®ííÕ«Ã~£sö»çø`y¸¹æíOW“ûK‹ Ú»íFÓÞ¿™ŸÚýDÅ>R¥¥}ì&ŠöiôöÍ®ÖÂû[¯óf_©¢Ã^’Vç<]u¸ÓN·Ã;º¹v2ç„#´p½8ˆªsÐduzâr°¯Ñ ‡péñ:bÑÙƒ#á‘ÎIíöÍ‘5šŽüS™q”KGå¡XsÔúUÆј\Í×ËvËqK€vR‡ã¹ivt§
-NÖMœÂTZ8Ï^bg²ñzçÌ^^ÔœÅ&‘wV:㸳þpÅ8[¯ñóÖéó8ŹÃ)Õ%Í)›Sç+U~=q9gÌȹp§†§ŽÚùàÔÌ OO…Ñ)+¿ž²öëé©øðüvš(Ïí§™,é9-Ò©Ði5ÐaOÁEü´ŽN"íú©uÞŸªåÑéøe±:/ a—=¶¾ŒË}sYqVàó]J3·L\g—î„+M š®Âèj䪖ã>W“']·Žåëi2~sõ_ÎõúüRwͽ©ÛÞ—·gÜ¿r‡ÝÓ¹›á1w´NÞ»“JÊ{âr¸vÙ]½ÏÜ-w0î¾»*tÝ¡0î¡ÌÞ»gÙNؽ
-ù:÷¢ò„äȇi?†=±«Øƒ']VO©Öì{ê‰sOGñÎ=O«QÕ3ànži¥ôìYŽ“ç@ÿyq»7<o¼œÈE½ñWaéÍåc7Þ*ЊÞÖ¬éö><H=oÿb^ñNÒç]¦ËŸ'-÷|‘L îã+•„ïüiöÞ² _=<îûn.2ß‹j/û”D `ñ½MÖïʾüa÷½ÓÏ=fßüçEfì/Ò§²ÿÊ­Jþ[U~òw'Òƒ¬<Þû—êó}Àç>(A}ÄŠŽn ûB —Áì$ЩÞhɱp^«B8°t4øWÐûìL©|é2xRE^ Öz gðö±N{/t.8íŽ;!Ǭ5…\‰@ˆ†R¡Ôå['Tž ÔP›} C/jhnŒB«á¤ï«aÎÝ„“£9À.·½p»rv–Ê×Åðäb6‰8.è³H¨ÑèF„¾“dVŧH-º¤#w×O ˆlä­þÔ%¼ÞÔA_;ÇÄ9+剋eÝI´‡Ùѽ¢Ybð’®’Dä$yé‹ñ¦oD–$¢J^«<EvÉŒJΊõ6åzSR÷¨„š›PåÌÓ-u㨾Üä©·ŠÓCû²ÍÍ1¾:þ\Óu> âOñ4I¿V†ãèÔˆYfLüŒw3·sã?]2òíé‚YЧ 60ö/Y±Ã;ØB&ãb¯#5Û ö-èç9¿?}Î äK‘˧#M®Õh>s݉w½‰·nÞÿÀp¼èëç=+<•oøv‚ó}Mõó‹>´-ÕZG8+çB9“f„»DìRPÊÉWÑYKR"%—ëbÚÝž‹W¹Ñ™ø"Ÿ‚ù.œ£~åö*Í;ì@g¢·òhUZñì‰+vZž¨1&SÈDzYÏ"ÖÊ>VbýÎ…'¶”ˆÎáu²gÉÌ«rVõJg’ðà;{ܽÄCç·™xÂß÷Å/¥Q/þÜ>-Çç,ÊZ"ží>&.ËþRâù®
-¸Ÿ˜+s÷y(”Ÿ'.µÇóÚ¼R?—òÁì¹6—Äd¤Vˆ$S<íI6½.{²?ÏSö§ÇqŠ–î•T®;Lu†AêÕ#@Œ9~M‹W«iº<'´ôSºàLϵ^
-FÕ»iÎW¹' ÿ}™>Ý÷Ÿº‰_¼gȸ”»‡‡î<ñh »O\±Ëh÷ñúªYyT[þ‰‘8çSÍ~7xRxÿõsøú:÷\ò’üsïfxñ1¥ÕKfFŽ_žî—=é´Ô}ΓíŽt—¬7¥U©~Õ5êõn»ß®w5·Ñ.—íW¯¹ ï{j¹$õ¹àðµÕ#´þ´}ï“™ ‚•ëñ»´<ÉĺøÖÔ:7«Áx¤‡4E—†µ¦ûy8^¨ö}5‹ê‘i{4kšÂ<øbÊÕuô^™]V€÷ùÊÕ{¥×æC`ú:–âcÑ=éÛÙ?^¼Î¥ÉYîJ˜Ü®Hy긙&¦É³ætúDå/f¯è™å<äì!âj/Õ‹"ÝQGÝÄÙœ"jÎy½ùü2Wp­ïMxI°o’äÐìtXÖ’áǶöìç Ÿk nDµïâÕuhûÁQ7»>ØTfãÉbî­Š7Žú)ÍÆ6xï½åŒëÍF[mÏ¥™q [ì=æ\1Ÿs³¾Q v’äsZ*3x2³ñî}ëh-Ùì 8ow#D$P ÎGKþ¢˜³«(m¾¸2¡1ú¼µLgba ·pHÙq›0ßRxƒÙ}Ê„·G¯wÀ‡ ú’ ¢¥òø« "N„>ñ¹ Z˜+„”9¯½$ˆâËÅ9ì’€>xÉÊZz• T+l¹doo³ãpdÁ£uw0×ɸnSEnP1i+¡€fÙ‹)Ån³¨YÖžñÕp§Ðß©
-ô,gB~Ö¢¼âí)`9³pöL{X €YXéD¥U£Y¡@wbÎx,Lt\;qiZlq§=Æê5"Â4½VF ¤ò¹U‚;‘>ÓŽH9ê5bGHÁX6ÑB¤]í)èìFz™ö9¯ç“Ê.¤ ç—Þ…x—
-SÝ4¶¸ÖzÔDê{ÏSG‡v±Kâ £¾ãjô4¡#­{<[\eZ|a‚‚ÙÔÍm"}ÔÛÝÆR(Émô™½î#;‘>…Z—{‘ò‘AÓ³é™Ý§-¢ É»ÇZ.€.¿ï.<‰[rRvôÂRˆE¥ÇÀYlº=Vƒ«ë|ãVÕ]H‰|ù*½)çt±{ŽìËŽ±2·ÏD^mµv#-œÆÝyt½iáJho!EX0‹î=<ÂHsãü¾…'\@¤þwH‹ÏSöÍ?'
-Ñ9SûÞ^.—Ù÷¶NÜ9ùGƒbïß¿7wþ=µxâñuÜ÷V$ž®côÖÛ5Å’Ä3÷,î©ý¬‘a™ÝóVò’â UÝó¶ ´%™v?å÷½o’å+ÇÅž·=•¼SÊž·}yûZvï£û4kÅΖw»kS‹ºÝ㻸Do)OÂ[Ü|›²‡‹É~kj>“bÔrb?c.^-ï3!Ãyû­@Z]\býƒã3&Ø€:) äNN#Õ“‹§¯è—UiÚ9éJF–žL¾uÎßfïò·­ì]î, ʈ|&îe2éH9°Žß€fpM`ohŒÏ‚ÙI{P(ˆ´Œsî­þX¤*Ì<D$~³‚sã¨ÇÁ™©:#J¼ësÈ‘[WÑú†ÂÕ”'^!Պ⋺5‘¢(‰ÁHG]+\+Ræöf?RG¾Ù‹Å9þ˜u¬À¹‘Ž´éˤ…hAÚw»©%!ÿß+½E`èýÇ ¤…Éy^+R¦yºF
-âA2¼á™h1¡÷¿)ç„ÞÿÃn¤Ìíý.¤H'#O©]cÕ ¼ÿ½H¡÷ß߇TFHMlc¬ÀJ_ÝìG
-}Š½ä…>ÅóR8+u´Wï¸êŒuôè—.çÑ#ábk84÷÷A2•£Zd|u ‡µUá6³BÚ¯fNÝü
-ÄÚžûÌ:‹ƒ|8¬\ Ê®g|’ ^“AóϽ5X׳lóMŸÿ%0ƒM4,)ÄDÝ­ÂîAo¼™´ª&ˆþ‚u¹ÐÈÎŽÕÞLãÞd¡†©{òh+û” ×uðèv餋ǬçÌŒ‰ €ï³dîqQ° ×ì0Ä’È‚USûí[¹>0 ™ š7Ðåtd$g]ðÈs¢d’vuÊÚî”Ñ›\ÿÑéIîJ"’¯Ê :ú£ÅÕ;ÇW·߉Ëfã0¡Ÿ\ýˆÏ+¿1>Óàñ­c1]¨L&ï!ÖÑü‹Öò©[±ˆu ±àÂ~rŒ¸…Ûù÷KÖZ®ô± É"¤ãö)¿E÷-!ÝäËwRž8V39¥ bÍ7òîaõê¹ Î7˜£ß£z€ÛÃFÁÒ„Ùå Õãs#©ÜM»ÛàrkV¾ëÒíèN»]+ v¹‡¶}ŸÚÞ7+qè²chçd¹òƒCÃV .ä<‚Ôð[X7^MØIãB}ºkTØŠ­eÞGµ[·?åy1î|ÿ€6%ÈΦ wËy¡ìÞòÕ7Ø”l‘Å´•ßG˜çýVš—×]r,’Œ#”ußÕØfS–Y‡räüݼëRoß5ïöͺ<L!•ô¹¿›—Ï+ðõªAS+y÷ˆ…”§Wéò^vnØ}½ó8ý~&ƒñmjÁÆ‚i¯Þ%k¿{í¾”'™ðýžAÖ®vya{Y¼áÕ„ –Œí°®=Võ€QÝ`ñ±Ìê{|Æ5 ÛZuÛ,Žão¿@J§žâzO›s,Ú?"V¼ë=¢KTúN¼Øg+ÍþãÒEçÞ.­Y‡þœ¸>äàøô cá<‚X'>r鎞Ÿý!KÏ×[M­}˜ïnìUnÛ/<_5¶iP~l€/¨±çíæ'Ql‡W÷ãD½w?b[íû©çµÖy˜eâm+p¥Ò7O‹ß㽡׷'|çÐî6ÐE¸ÂR<ì^Ÿ¬ì¾ÐØñ“fe®‚”¿'Pöí¥}+W¶hcz}GRç;Â> m¬ž Îsøè4¾±ìR
-›óeOW6;rtÔ²îˆuõ wå-pTG¨­Žìö-? ÉÓ~;2Œ¾™>Ì–‰Z&[öî£È°Kû¶7™›Òt¯oÐýy-- åŽG°'©A=.föý>Ì.õ±o‚¼–>r;tßò˜N-¿GÚ€—~šx-m)€ãý½ã[<Çjlë~˜]Úå+’ÚÝž<‹7»$ýé×üϪÑö„üÿÙ{øؾ²î÷ö‹^¾üéœI2Éô™=“I2)“I¦§Ÿœ$'9éíä$99é‚
-¨4ô”*"¯ ÒE¥iRDº EšÝ«÷z›w­µ÷Ì.³Û”œ•ü¾ÿ‡$3³gïõ¬õ[íyž¥ô…ÓøÌÉ‹dð¶xTM÷æ¥ÏbTXU6qƒi-*2¯®‰«
-KÝÄC• |˜5pÑúf³nËÕ¥XkOs³Óµ„ýa“yž½%ÙR_9 ˆ V+$ÖK²äÑ„vÍ£)Ú¾Íe’ýáØÀìôXUËRSÞJâ(RïºÏ0ù–ÙùÖ¯î´}ªéºA/6 Ÿ©½†uZ,q«µ>­¬× ›®×(kr‹f­A3˜–škÏä©f(˜q[,&²ËÓ™¸Å`úp¤î5³&7¹Ámz?vFÂôÍ#ÚnP1Rª¶#<Qw„µµ—™~¯uQ›ÖóÃMç§Üå±½M†ä.u¿gñ@M•nûRÝé¨W
-ËjòÜìOä'÷ {»òê¨Ýõ*z1ÓõV½ÕV£™¹XõOSSi£li²i]¥VózlÔø&×ñÕÛrÒlÞë¯û:ô*ë4¹«¹N°ºµÿr¿§òê,],TãÅ*—Ž^igD5✛f{ Øa —*w7²×^3´ÑÝÐû2Ùô1Úã3DÐŒjw¢¬†Ÿ%E“z1õà½L]â^¢‘¿õÔ¨hš’¹™F(Ú¬f·:¦3{µ­häb6­Ér‰\¬nE£
-s²«»íÓxc%²ßö+½ìßbþ®cGC¬ïF+5ì½²ë„õ¦õâÖË©IcÚWªFÊNèdÏo2ƒVn†uÇ(’£WÙ»ƒÎy|Z§.ò7;†°Þ€M3ˆ¢… YŽÌæfÍEV\·+³³†£zs‘ÕÛ{¥«Ýû¡Ô˜çå¹´X“k P«©šŒ¯"*ŒñuªÚ^E»3RëuÌ'ÔM¶¯cåg6$Wµ}v±†­U·“n7ïg}¡JÇÖšGêßÓJzSÓÖ4ã£s¬ªV³Ø·ö†äbUï Ö`éÅêßßšãèÅÖšÇÒ‹‘ëؼZßM<ˆØuª[¨S¬'k®#TÕ÷…Ä`AÓõä*zC©/ìñ*úÂOHÕ×’Y85t¢ã1ÙyJv¤1|þ`x_ª¢”§Æ»od²P÷Zµ|)b—†´nv1ömäkmRbÕÏõk2±å¬ý™ª0ÏÉK׺+W6Üé4·dQ#šÜæÍKÓ1E‡æ惚IZ…ç„õz²aÇ4o6I³ò\«˜ï“;»c¸ª²q¥C±FyÛÒ‹w-¥Õ›»7ôb5Tb£›on«fˆá7±dÉùÖb V¼˜Ac_?1s¾UÖÅn‚éM-¡ìøªª×.îÐ47¹Ýê,Ù€æÒ8NNÜÉß][É´lžes‰ÖÔrn:9™5Œ kr7&†ÎoAWŽª3†Î<‚ŽF 6"†®òK•t:Ñ‚5ÅЙGÐI9UꎡÓûR9‚®4ã«7†Î<‚®”S¥Þ:Ý/-GÐéG VCgA'îðÖCgAÇrw4 †Î<‚Žõ/ ˆ¡+I†~fkálöfêê¢ço tTã-iVzÉMY9nØŠ—R-1±‘=k2êѯÁ‭•ÞÉöª|É÷ø2j‡ÚËIe£Ž’úxÅ”Ë$Ì–?žª6Í_ÈŬü°l?ßüa“[ï «>óªÙTÍM­˜‡‰ßREä£ÕÊ•É-‡ÌÉ‘\VAsö´f9«·v¨ã9l@5a8á6vÑñek\6Ö{46£µöR³x´ÅëêMn«`7µ'dM‰‡4~¿Îcìf±àÕdc©*k:æ·³áÀÔ’Œ‰3ˆr"b9 ¡—ªŒÏ’ýÆ­/¦vÁJ®G4ƒÃ¥âª5y=zfÕz+'½ú¾=ä¦V,\(m†˜æ¤5¥ÆÌíédÔð!i{1X:3\8¤#Ý€z™$O &«¼¶j0QÕŽÕfÞ¤g3‹+ÛE~û^÷iÃâø–Ï,|àíúÙåµÛ{•#/ŸÚÞø¦¶ ƒ`ì„*Vàóf>ðS:!šÆö³ö·Çg9’¯"ŽO³‚YKe¼ÓéÅ,lª¨Y³ÚóŠ=‹j.foS]‰ï^ÔPbºþþµ=d¬¾S/üŠ)"UŽNÛ½±ºz<fk,[°ÚõiâÁ´ë––ñr•—¨¸@¥àh#Yë,[÷vÁîdO^O6šìØž§] Í*s‹å%’'‹Ô(%;ÆŒè‡ÊUi¡Š“9aˆ›es-†ITšíõÃÂ(%åLGFÅa6r¡ÔaãŒÖÆ5®üžÌ±6*védûÔî4Ó¬&“®³ªe ãH-š‰¢šåãˆ(³!FUã±ÝbU-^.¥Êñ)§¶Æ”“Öƒ¨ör2XÞÑTË•+;‘q–·$Î_ŠÖ¹ŒÔ³³[Ò©Mf½˜I9Uµ"cèNoªºÕ-©WdúO+swФF¬ÈìË+25{©ÅZ{<­vWd¤^£iÃpcVdØ|¸n"òhÃGSøZ[…¡Õ±"#G ŸÕ½"CÃÐt݃+"R-ãójY‘ѬóÓø<{á9VÁ9¤lägZ“myI„+ˇ#êÁ²®ß¸ÁòHU^ìF
-ÓsÝÂÉîD#60ÓÕn¼SբΈ¹çl“ÛÎÚ#y4o=Nrbÿ2b˜9¬Ê¸:×M´ e\]mN³Ê«³p9±WWB¢)lí BƒëŒ“TU´^6¶4sÕ#Ù®uÕ#³¨ šŽÎpþÒÐx8Óuþ†ÅÃU“¬öx8ÕªB9"®Ññpµx¨V§ï××èx¸ú£íx6Ùˆ¯?N\Šˆkt<œ¼£Œˆ«sÏ­"Î8K@#ãášt#âtc
-6"Mk …Ó®Ùs»#ý§‰0Uf„·è‘çµÕf„WÇ*R לP^êH»ý£»¢hc™^ìÔÆZ´­1̼6Épm› l–DƒØllá˜eç¥R6ìò˜ŽY7««—6×·µ;«zuTêä!ûËì¡ó}ÊÃ=S­ì8÷.2^u»ƒ+.ßH¯à
-d¦nº³Ë7šØ)âÓ.ßô`'ýi’¾3í
-Ž¬Ä#á¹½.©;ê?ØSÞriÅIìæ1
-7ÙÞܘr’»y›~}ö‘Ó»%U¸Ú‘3>õ¹oV‡Ø}>­s¸Ž²ÍB·<gDqKôYj??Î~{‰ÜíX´uSÊUÏê 3Zé­=š®Êu˜£ét†îY–9¿¡ÑtFãäÆFÓé­ Êí¥QÑtzÿê#­¢éôÖⵞöõGÓéÅÒiò5 šNïRö¼ «‰¦«ºG®)šNïµÑÑtÆ;#Œ¦Ó‹¥³òí©>šNo­Ýз§æh:½X:ë¼
-ÕFÓé n¤|J Œ¦Ó‹¥«È6_w4â–*27.šNopªYém@4žýT'L5$šN/–NÿLáz¢éô.Õänt4«?šN/–®þÓFÓUYb5FÓÄX58š®â뱂zlÙˆh:õÄX:EÜ h:½Ý’òH©aÑt&³×FÓîŒ44šÎ¤i`4ÑÚxc£éô
-£tNbã¢éôF‘:çñÕ?ì9ÑL
-èC«z¶¸©ò-Y†®«nÉôt¹ÊChk»¥Žê²œ™”“éé³:
-crS5HfZ:/©tS±ÖÄ^X=#ÖΈ*ݬí¬Ì5¹mtg#fÍÜ_¢ÉþŠqÇÜéœ.WyÐ]õ¦9æÎèüJ«@ºdÏÖtWãÉkºæ§Ì˜†)UqÌÉŠ¢|Ð]m Š²‘vÞkNfkï˜;‹Ø7[›G¤_h1f;y®Fgq8Ò %òh ˆå±å‹¡ªŸ:‘õ=×]v}ë'±yL3¯¬1ξï¯a_IcèŒ8ìÇZG¥Ùs"£¥Óe«&Û<aËS9Q¦‘x–:7”AWG—æë
-ì’¼¡ÈvX¬Ûõ†bcþFyC±èÿºë)犯^|!ÛQµ—Z¯cœ\ºJ“­ëTs¶«¡g½N½-P¼ŠA$»:RØ:–]¼Xµ‘Š3 ´µ»[žŠÀÚÝ-‹½$½µ5ý¬€ôb¶ÆBv¾v·|Z_ë¾Ë°(oÜ3,Jãó^M}¨g+·téߌ<¨ fâF>Ôž#µE¦Ì4îÙFž`8c#ÁFÛŸ=¨þ êŠY;x­îỊ©Wg5ש?K€xzO«¥S; ©gIÕùª’b3Áæ/{U6Cò7› +ðéÆœpG¿¥QÍÐø„»êÏ­®å„;ýÈǺ›¡æ„»úNʳOdyR^CN¸Óf9«õ:æ~{ÊþÅ*>¤öî Î{-Ÿq§h9Õ8ZD̲šçØe3Re`­Ðl+Vœ%YÖ
-ÍuŸ’.F>n™'ß²ùh–,Çnž+všW³9"n6$°ö¦ž£“î\Ìú:Æyátƒ˜˜] âÛ+œkª<.On„â®h»Žs –š1ïÀl1ÑÝU…1™†äÌ1±^Ì~Sé!× Ó¶3?%ƒ0¦öþ;6ÜíLÛé¦ÏF«´qלáܽúyåÊqõAL+ðìôD“ž¶º!¹%:û6\U¨>Æu]»ã*þ ͳ6Ù‹qšÛ<32¬(M•ŠÉ‰tÕùÙÉE©7[?iØqäRtùØzEÑNŒëú‰yjªjÆ0éÅò¤áâ¤ÞÙ‚+ös'´ÏyM—gõlãJnÉNh2.,±{DÉøØ=Ýï“l0zr|=m•Bô†ó4¶ošþ“jr»ü¡aØ—fÑ}¡Øh¢µl¼6ÍíI?©BÓŽO÷±²}ªBÓ:š£= åÚ…ú¶6Ãx¸ã³Õqž¿/º­=qŽ)Lé6ãÓõ:æo™s·+~i$w{ü†ªŽiÎa†W6Œ¾tÓ춡Š/#¹QŽ-w¶dÔÆþ%Z÷ËOª‰‡óá‘âíª”_†—ˆèÆþ‰QŽSûÑe£ <m¸¡èwQ.à“ã/ÍVg ¿´}ËÓuÇ(/¤>÷M†7Únø¥Ç'3I—á—ºRKÁi¥gùÚ­DéØO’!¼é[›û*ë¼oùlÿžùûX|egËÑrvnÂòŠñ;R½“ºI"³8¨t–Vd<ûr×Yö¸Ë R¹£³é6©Ý ’N1³ë8iÚ7gè\+§·Äd¼Bbr*ž‘k•á-éîïgªt­2lÊXeη¹E•1;E¹kc%i!cõÊÐtŠ¹X¦J×*“
-Ðn  ¸_9£oä†ÊgãD¶ÊÇže·x·ò)VÎñP>ÃÙz(Ÿ©Ž5ìP>¶Îoï¼:åSŒúÎñP>ËÓ²r(Ÿùù•uʧ¸%¶ª°eØ~SâýX…÷Ÿjg¹êSýêÉ ¥<ׯf_¸ªÎõ3 707”É©~ö½mÍÏõ³ë§Tß¹~Š¨»úsCžëg¾*¤‰×z®Ÿù©~šÜP5ŸëgR“ÙèÂ:å³sýÌEzC5â\?ýÇ-êg²¦dw‚ÏÎõ3·d“Ý]‹sýÌ­|~eçú•¢îpˆÉ¹~æ¤êÅê8×Oe¦ŠSýjŠ¯¬æð6æ5I¬6üHasuð³hĹ~Fþ7â©~ ˆJ³±,l3•å¹~æS]tÍçú©KÛÒ ºÆsý̯b|_uçú]ÅooÞæ¹~æWijй~æ*rDj}çú©†š§ú‰§3Ô®_¹êžê§ÎCRû¹~æ[‘¥XžzÏõ3?ÕOÑ*ë:×ÏÜášÍ,p®Ÿù©~åî:Ïõ3ßè2Š­ö\?]'úò©~uŸÇgkwØÆy|ugñÎãkÀ¹~æƒxÛÑ•çúYÆ=˜xÛÖx®ŸÑH^<Õ¯"Û|çúéŶiwë?×Ï|8_ž¿Ôy®Ÿù~sé,ÎzÏõ+•¶~ŠåêÏã«ÅICï<¾ú›¡öT¿zÎã³?—6ÎÞPy®_UGñUD¤6â\?É-ÀàT?:kÄbæ§úU•Vû0G[Çj=×O÷¾Ê³êÚ³6©Ïõ«y­¯ªsý̯B}q®Ÿù Èþy|õDá–Õ²â\?õžqU1O§ú1ÿ±œë§ú–ŠSý,WàmžëgâÞ¥ìÅê<×ÏÜÑ©¼:Zç¹~šÓLÛòÂÙ‰yRžëW˺¥ºÄìœëgk^Y÷¹~IÓSýjðÔ=×Ï|`HÛ~#Îõ3ì$V}®Ÿi</>Kýçú•RÿT?Åx¬®sý '᥵ñ†œëgYb 9×ÏozªŸntm çú™wúQœÕŸëgÖ9lŠ#Xu÷°uj2F“úÝ$¦Š¼R·gzÂòÂn¹¯œé1iÎæŽ÷š°EUm:”ê˜âÀ±ÂêdZnöª%/ÿ=e!°0rÔZó½´Tž¥KI[ÙJ'§všÜ©ðißð Ð}óF´=Ùœao¢ñTÿôäڱ˽èosÑ… WûráŽ+Ô¿;èïM%ü}½Sóþ齃H6»Ždwƒ=‘ÜÈõL$w°3É_O#£Åñ•ÈèþímRbw·îDnŒÅ‘éâÎldæäÎíÈläôndv¹ÿ,r38ë‰Ü
-Ìx#KëÍ‘åå¹Û‘•©Øid56×Yó\?>>ΆOzÏ„{³Çg+Ýž“ö®6: ˜:¥‘íMîàÎfa¢0ž¸š]ºµíòµ¹ç&[»{ï¹'oä‡Ûîì¹››áqOËÝ w±#Ñ6±¾;—éó•B]gÃŽñUf1ìm07=펴nm’¿M0©\é•ìÂâKONh@é¨+84ß¡8R:G°gÈ °z:HaœEVû–½ÇÇãA“ÛäY;¼sþ¾X{*’Šäno Gòãû'-Ëë4rÕ%}_ò(ëïï_ˆ„s+.“x=’íZa‡üEÂ[ŠS2ÕͧMUÑ<ûòz«âÑäÞGY§m.ïÎݤ+xïú"‹® ^wùoyR®ÖL÷ Á¦ÿôº‚ƒq¯+nw»+âišä×YújÆå;j!ϼvÔ]ªÝ{t‘ÓO´Uð¥…ÞôÎZX Ï·Bjrï¨ë˜*—Žu“Ÿn
-íí$ùiöˆMð#á½æût¬5±‰xBaö+•GôÓî–|¢àî¼Ý~䧿úÙ¯ô´¬Oþ!(½>Ú!]‚èÁivcï( Ç‹‘ÁýƒÑ“Á‘›7—"…õâ\nÅ»0""÷VK·9âóg§P,
-´{½üµ‹â¥a³üRHùÂvéû’£Šï#³¤Ã!©`ò=^bºodÓãé)ý-ågonrÓ·/ Ùåž¾ÒK¹`ù:ËÌóüm4,—Âù‰]ö“²;u‘_g¢ôÚ>¢w§ éD¦è[䟮{ä×bP>óñÌ'ÕÉÑ{ù‚¹òëT$ºt².6é¯sâecÓ÷D[E½­!¯d¿ùb€¾êWZ~"$}Kl¨;ÖÝ»µì½¾Ó™›ºÛÝÒ­˜ù'sgw™Ž‹Ó®$•È§Âz×kr×~Å™ˆ|ÅΙþžôàL[ïjúN×Èáàôཛ¤»MD‡|ËMnVY¢Þ¹’á§cþìì\ÚeUißõÑ@Ù`k¤m„öÉߦB´ˆ4€Æ+þ´Ö<d?‘"¿ác?QïÁ[±ªÌ“é@_‘ý$6‹ðúvœ¾‰~Æ}—4š™M$«u·³ȵ=âOk­é~énöÖ¢å»Y'—Ÿhr—^Úî(µÊ[ñò³¬ÇfrtL¸J^è¼Ì„×úÚz¶ïµÏäú²»Š.OÜÖÉÏ]—– ÄÊ1‰¥ñÓhŸ²6ž.„é=ÒÚ6èÊ.ûÅŸ÷}ºc‹Íû³š»é¼µ_¸›¹í>!£ñTßÀ\ÇàÈ\¤…¼i)NF×Eшµí¸ÛX[T"ÒÊí`R”J-ËšZAÙŒ¯BCÕ
-ºXVЪ–îÀÑá„ø]ÕEnc3ñ49#ýÔÄ'¶|b“+×VÅi2xzPj<Â.¸™]c•”êfˆª¡G<³tëdžSð•ÕRhG3Á?Åþ@©
-Pñ$c/ò·tˆŽõæÄ… òWšrÄsaýu&"­šzPöONvxèŒ(¤»d{ Rå’)¦!tx6uD~-†#ñõ‘
-}=LG2Ã}ò– ¨eYÅU ÑŒù~Ÿ@W´“÷3 Šc¯ürˆvãb7!ä{¨OÎ8ù§k¶^ÝÜ’ô.?%ÈWnr“á:#*˜òé
-µcBi :Ú§ÕV.wGn{&zbƒ}ÙqrùžnÒöWû¹hfshŒUÓR°üð~©lÛT¡~+Çm‘‘ôZ&6x|›¦Ü<èïä'RÌcIgÊUÌÅŠä[:ÓÑUWd„4¾ùh¬­/’‹zS­›ßìŽz7ƒCD7wÈh|ïngx'žSâêƾUN%ö¢™Âv†\g|Ì?õF¢ží¹ddx>勵ùúòáùåbl03—ˆl­{…@ÏD?¹@¦KŒÎ ’FèHIõÁèêøõQRSùèÊF®ËðƒU}Œž\,ÐÛÛMz!õûêvƳlõ!MÝŸªúÝß?Ø“PŒêƒê‚éV}Ÿ­‘šlñA»† ³7±I•3:Â
-x¥ Têù9[ oŽÜžmMG³}KÓ4 ­3æÞl%Unª¯›öq·ˆÌì°2Tú‰N®tÇô´5E½1æQˆµÝÜHÆÚ\ R¢ñb:Ö–Y[õ—OŠ&3ãÍŒv@ -yúi{IG½)!68+D³¾q£ì¦Š%ÒÝa#¸ò£ûyr+m]B^˜JqÙ Óg™‘n´þ†¤ ê•ÞdK_€|"ÝO £_År¤íg}Ó7ÊŪ×tcêçï$‘#Møæt71“/Þit,ÜN¯õÇÚÆoN“ä…Èði±OxŠ}´½x»»…õÑ…Lt5[èTPˆµÅúÒÑÌ~vRóÁª>F÷÷ >Hï¶ÈÖÔ„[ ½ªfH…Ýëé&#ëý#%¦ûAUÁ¨?ا*[#m¿šÚ5D¹‡èT¬'·[Hýð¤Ð'ltsdʵ!UW>¹xþ˜Ìs6io·x"þ¤gQ)£ÓäöúÒBÁÝ9A*çtHØ(¶’Š}ÔZ$Õ´ )˜I’jz«<óT–¶Ù!a}y[¦ÒDæé€6@z®ëacjh”y¨v‡éÝvEF<‡Ôƒ~:ͺ"YÒR¯Ë^Piê"—½N$eo†tyÁí|4³Œ…ÇóSá@1­ºìÀ‘Œa¹èh?é±ö³ÄúdðC/;^Ö··I§7wÃ_zýp„†4‰öuf… wk‘ˆT¸ÝÞ«ûÃ4~',¬ž Rëwu^'Ãï­8±[©y‰} 
-ÜŠ’oY ‡ÇºïJ,Ì„ÈýôôÓ1¡/6¸JÊ3»8Öõ¹EÒCLÃc·C½äùÒÂFª=Ê&$,gJ¬mµ'µILôjŸ¦ç¼g6&–űgI®v¥ùðÄVNX"òhŽLq–Slü§=ÅòÓ\Õ
-¹›$±Ëúhq€=yôT’Ê:Q‰Øõ^ö:{•Üür”¼Jî{}t¹Í]yU oö[ùúâäÕ\26èëÌ™¹ÕG^í uq-j; ýKéub’¾„æõÊW»ÿ^ºŸô ÍZv¹ðÎä(‘°Ìn”±u„tP£9rמ 3yuÆ#ßN‡ü¯¯Iµ¹ÝÜÍÜ Û|5C:òþ0ž‰5ºÉO]AÚ˜s4ö-Þ“Ž Ž-Ç"#='´v‚Ùö{…üöa¼½?Þ½(¿š
-£a«W—wIíöô²Z“oHG8º¥&L52ET`¹ÁyIõšÎ“QÑa†´ÞÅPéÕe–U"@äq¢‡6C1«ftAç9>¶½C[e*ÂH™âmLŸ2¤!â  CÝ᱃žTx'™ÊÐî.Xo’î66^:Œmíè&©µK^¢ ÙrsñyŒždtuìpƒÕÞòî[#j-邤“SCB°µHr"Iúýà(ËD±Þ¹=ºLÚïfû0NDbî“|Ô» uzüÖ8kY´]%äÒÛ{È£í’ñÏøô¸ùÇh\ËÉ8ñx•èëQë( +?æíí'ú³œ"*p½‹}Œå"¨òƒê‚I¨>F:LE±”>FëXMÔùXªX”f`3 eÁTJ*MWf<Âh#_º¥·M‡H]Ìçúè„sQï¨N-m°¥“¸væXúÕ§™zŠÛý7²›‘M÷½ÌñqÿF›z˶úFÄ Qööhzz½C!°¿µuž•.±ªÊ¿J/p¶>yŠvŸ±ålWÛ­è°*ŽýÕÕ?¿_Ú8™Q¥4¤‹(ó“ÊKÄrŠA>û[¬¸ _à†xté ;R&lö.®fæVÚI—8ÔE[ª‡Òv„.­x¥¿­ù¤¿í5û邦¼Ìµ©Xã—`è3Ÿ­ör¶Æø_ê¦oè“?'f}¤+Ùì¯g`á®b;ŠÌ~<RþÇÞý©ršCšq0Ñ\Úk˜§·ÙÌòª³–²Ó1̹ÂYqäxh~¡´÷¿ãYoZ$uµ#OîF©T'tP*±3—¼áÐävuœ,´•v¼sô¥ù~”ûTìàz±R1E¸û\«¥"èËE@ïfO•&æ‚ !sr£\‹ÊB8[˜(ÂœºæEÀrÆïÄR×=©Ö&T…ÀN<·c».¢I¬‹`@¦‘õbíòŽ” ¡°jÞ±QbÓ§©wMìIE=”E°1ÁbxK5A]’ƒw9•¨~U"—0¾
-ÈúSöãVø.)²i©ý?•}—ØO‰òP’q„“Ìo„H})fèî”@îk%ÞÉÝñÑ-1Öµ„ËÑOñé¢e á–“>ÿLïqÙMì@Òδ‰Gº´fº]®¶þ‰.ºôwªpgÕ:‘)ó—¼ïDß»ÌÆDT*»½ýÎ| åÊž€’·-©é»WŒŸÎæ|Þdo×l°y,·:æNhVüe÷÷Òq¡]µB;¼Ï¼«>½CÊ Ø²ß ƒ]o†63òôlÞ˜—\¢Ôb&Û{ÅD&åj(ùÀGWûFHk›œJÅg7{##=»)ÒÆ& öûºò¸z×t½CS³c̉L~¡ÉÍ^JæÏhŒä ·$8Ûƒ¢„%÷îú#Ãë£iR
-k
-Òû’Rà“ênȳDwÎXRÆ„¼<)­ùÎí­)Çhš‚™
-ö‰6g¡DcÓîdx'žNÒ-y¿°Ñº6@:­þ ûzº3"ßÀÜÞ¬?y«¥… iv£åCX*¦f{è>ùé&ÒDãý1÷Æ~Jl
-±6_,O7ç‹QogÏÿˆUN#ÞÞ^Ú3 Dn/ŸDéöɹǮ¡hÖ»ÖGnŽnVMOJqqÁSöÑóR÷Ý1'uú´H=øº|Qop¹—(~y6çi݇nsÙÓ¾P±UÚRHtúf²Çëy/Ó@:¶¬V5UÝqir/á“Ç¥Ô¶ö‘©é¸T öÛbƒªcÞáîë‰î|nðzzIÛè/åï_Ï øoLg<›·¢-C1¶î¬'2ÉT£Ã¨÷Ö ÆÑ1ÿú\„t“ÔѶSÙäîÞpg ïri,?dé8y+ï4)‡»K¥™…êŠ×s«³ó錿ۛËeÇf»•ûKâów‰%=y #?-±€4Š¦~µ%©‹Uö/åŠ&î†)m²yž6)åH0`c^Z€m§.æÒÅö‘+ºJ1cK13Úõy †t¬?Ë#ŽÅÊ»™O0¿Z9º¶ãˆ–Ž‡ÕUÉ­TtWÖ|0T‘Õíü¶°Fîa!;]:9íP}ýC¦0ËÙèjïfË?[^_¨t5íJ ëË !^ËBtSÏ‘QÆ®NòU ô[ÆGÅ讈't«Â.d*µ4BnÀMT0Øß%ä··û‰„鹓qÜ1
-/•'áû¥
-t²>NçéstÆ>N~u‘Y’Ë»z;DW&èK+ÔS;ic
-¯3“j‘$3™@*>9í‡é´e”"ìDAZN 4dê-:ÞÓ®¬O²>õÆÍûK5Ô-¯iÂí?ŽdûvCQOb¾U3%–bxÕ“â
-Ù+Ñ4Z£yz:réPöëé…[twKYb²E©`ºŽK^×ÔSNÑr&ç多 eÅAYò4y$
-¤D¤wûɤ1;LÕQœôâ‡d®vº'HêqOy@TTO…½¤êŽ2¯8šóœL¸î¸;£Þ³Íu6N¶ž¯ß ‡wÖ×Xçß-º\tVÊZÌc©„‘Žæ#óïiì•¿å%Cš…¼¸òZ–0öNé}Êï‹ y!ÕKÙzÍ礒"ý« ÎÚF“e/˜!íJÃn{!©ÿõóK~êRLæÑåõ×ωu,Gg¥]dä>‹®ÜJuF—N=½T©\±6ïD î`‡Ñ¡XŒ¼Ð펮Üí¦úÓÏ>¦˜½&[¦ƒÔC3j»ÍÜÉè‚:v)çéT0<èé%M·¿Šg<r{¬u‚L¨·[°‘ ߨŸäuh¤ÎvK´GžSÿ?2…ŠiꊥÁ¤íŸ.D¨Cc>6è‹déxlû0ª7…U ËPZ-¬ J1·©³jè,Tz®àÖȤ˟?ÊŠñ(ò¬,xïúýiA õóú†ô}#âûZ3]k®æô`3µ¾qpaåä²eÝ.JJÞÒxò@šÇ‘ñø–üÐQ']lOÐeNR&wÛ"Ä'qA—i[Ù)œe6~K×»²->‹8,Q¶ü´6zè«‘èJÑ¥íŽvôÍÌ×KºÎæçVB é
-¼jY½¹ÚN"‹¶†yÚUÍ›ØÔ¦ö‹b¹ã!½Á»¼ŠqâZjÏ”çÝÊ™q[o¸´±Þ­˜êO¦hðC RÏ|¿ëØEž!äj»(Ú˜“Wd8$ØœPšøè?9‡–í¥TuU駠ábªåRjÏIåªæY§èoÙO0cd¸4Rˆ å0Vt2Z•{'±o*tÅ2d¶¼9@êŸ Þ|¾a”°½:ÖGgÈä+½
-¡¾™| ŒIþº£è ©ññ?õ’¥C2Ô¤;€l7a–-±¥høcT9¹V¼s«[þ>2ÞÌ%˜³øÌšNtieXžFÒ˜Ù0ËÞPõÜ^ñõw:¢ží}Q2Ð~}û€/õ^í"ÊßÚÓ‘ð'Ï¢îh¶w¿Ÿ‡´/9¦9":ËL‚Üͤ ýºÓ:ÀNÞ6
- òÓ½>òB¼•ö91öÙRîåÅÚWLÑñØB0ÍL¹¨ìQ$ÖÊF½­ô€¨Ån ¡!’2³ažè‘a-Iîv`R\‡`ƒîdËP šX$Ì\žÖýuÿŠ ÏÏeé«^—äéOD½].æ“îµO“ÇÜèûEÈŠhäIº~»o[­Ò@Pï-ÒÊ•˜<ƒº}K3 iµËSÞkY¤nôdD"Ì ’ÕͶ0¡0„é²D¹«ba¢÷l«5•
-eÆÒKù©¹ÁBìÎõìÍTlÙp­¶"–§bB
- I7ZÆ+†±d:Óe>ˆ7ùõ0Þ# ã§|ƒªx º’¿qÏ\ÖšÜÖ{D¡Úe >“«H=¬³eâ^ºjŸ¡ÁKE£=¢v"W­ƒ¢Ûorï @º¡·CÄú£ÑñäN¬ìWÜ)ø÷&2,pDg´ª£0Ñ$âµè¤­²s3C~.ØØ#òÓ(¸4]@ŠCíõÃ…þÈížµti =ÝÜ!e¢‘²MåS
-+/–j«Šw?–‹ ŽL² 2¶jÍ°˜‰X›«fÒÛÉOéld­4ÞÞ]S»Œ÷ÑÈ£a!°â뢫BÃäé ÝÔ¢.Üýti亴ˆ«],ÍúHñ†úÙêh(@É·Ü@ϬDs7\‰Ýâ~ík ùN¶»F£5Rz"ÄÖ`+w¶‡¾•_q+ÝC{š”°‘JŒ26:÷3uìòäf"&»<+gnÒå —=ºµ#Ü–`c2Èùäõdý,ŠZ%V­K.[”r§ë/[„j^¶ ÙU”±§û ´&ŽÑP¾ÑI ³¤éë*uËÄ‚¤UÊ£Îb„ÁH*  –×ëÖ·ý~º•Öëf<~£m¶¯é³±Ý­ûýµmx+w•òK¹‡©<¨)¶»Å}ä9ñ´XªcÔu£“
-®xNg‡éÆM&¼³>“†ƒ«Y:ØÊ¥hhH<ºšÈG†÷¦Bä}‘"ý–aºÖ7_R,­]¼½½´`ºØ@¬­o(Oý²4qLÅ*«Í ¶½ÃêXC7x:wHiùÑÝfy$$í$ZM)Å!‰“ùþy1ñF9‰¯4ß'U\ÊVÓã¥W£†íb³W+±ÓÍÕ!uòxÌDìür6¢rú!9Ö[®wCñÓÉÖ­üÒ­õ]i—yn¯…ŠþKïA•eã–«mn­ƒ…´(g¼,º¤4ã%×>í“w44»Eæ{EM Ú-2ß+Rg Ò7„Ý¢Š½"–6±´WTjû6w‹è·ø”Ö÷Fé„s™®£µvQAÐ0üê³RÚ+2Zƒ­v·È|¯H‘ᰮݢòø^w¯HÎ>gc·h¡ŸÌÄG“¤':5»Et¯h™:{DÅÙkY™ç
-J3¨ÑšZ‚ùl—Õ̃†-7°Y ù§ÿ­/ˆv¡s¬’ õxŽJ‚”
-ÒÄÙ^*W·üÉÀìqiÓ¶«Oôñ"êµ {DÁíåÔ¢<m’ºHSY§ÁY#^¥*%N©ZŽø•õSÉ‘R6Ò½ÂRþÇ\H‘_·5±wwènó½é+ƒ­Å;¥Ú¯`‡¤Ä—DÇä•Rè©&aòx[T/)帘c“v»â•ÙØr\œÛ ëËî 4 üùdSW4&\ëêŒ ×ÂSgw·Ž'Žw¶wî] 4õ6…‹‚0soó w¼µ5½õäÓÌÁÆÙþÖ½Ók=׃7ÒÅbw<³µq°¹u- n‹tiv©õóÁ)g&ârEæv"¿Wh™ê_ËÜŽÌ'+ÜB²ÓQšºÔËA‡nHápɽýuœºdhÓQW0q#F»QÝ“dªt«sÿV_p5wÐu3ÙÔ,1wˆÝ£ÁBçêxæl-?šK´æVS}}“ìì:£¤w¶ž¥ýpúˆ.?Äg¡+1› âB¼§p`³:1„¼´T±ÆZ­¯%³u<t–õÞ”çòZYbu:O~öæÐÖT*|²Ý. Þ!“›Ù©ðiä 4ÆSø(T‘›»o¼CÎKž¥5Óv¼©”8%e^ŒÌ8ä…Wp0¶)º7V“‘[3ÐirÛêÔ3СKCY|“]^ùå$ûýŠG2T$ƒ]RvÉbX©½Ûƒ¥&UúXÚ]åû}Šáâ›É\\• [ñÝùbˆê.õYô‹²t²{ƲȲ_åô±åk¯°ã[ÈßrlDâ“eŸjm·üÒMy‚˜vr",zè¡ä×vYŸÔáN.ÄÂc7öhÆ–i?]ja‚ÔŸÏÊòU•ãÒ-RbövVäd„Ú.3Q:ÎÀd§FNoh능ee•sUª}k:SÜìwUzMGYoA:}–œË{w¥Eª^ókŠ<ÜŠêµ>!Žg¤qæúLXªöës:kõ’Ÿ©»]_Ž&Å(±ÕúZŒ.Á‰?ov°KH¦Ý›
-Šƒ”E~+{÷"¥Ÿ–Åð¦¼_K^ØŒ)^XvÅVJÉÆWÇŽÜÒ³4,à©î4R´— ©²œ.G$¸Ó5¡ôÓ¦<,ÛzFûÁRÔÙ’LD»"ÃyœŽ|:5+ÅÖi”MÖü™üû–rgtùýÎîœ+0»œ¥kws.ïø-¤›g=“bÏðL«Òw‡ê5¾6óí y0VÞÐX;¢:7O‡D´°<!¶BVÞΑ<‰Zú%µ¤¹j9ÀÆÛíÑö”k‰™]L `‘òltÞ™; ‰ËV;?›E³õZ½cÙü\c}¶ª$ þJÒ*M÷¤& ÑÕæâ!“TºH'X{lhE¦ø][åùd¤œ5{S3iÔlñt ’猧»ìáRpowÐc.7t|Õš* ïÈŒQO…bË“F鈕ò¡ÕyÝO==ú½¯r›b êݧ)-=ËbÐRŠPW¦ Ñš¬L–JÓwö‘· SDR[F辨˜;¹=æ¢'òÔéÀGÝ’‘­õѼ&Yª^ªTš
-X‘,uš
-I(E¯*_6GÉè:M*|çvÇí]#Ô¡†Ëô$ÉÅܾò«iѳävÏf¼š¢«í4ðÍôuùÚÑÌÙByȹ Íñ‹ …ƒñØàx"%º‚yºÐçWg-HuØ€¿šd©RRÙuÁ´ÉRKÆsò ­,?+[RØŒ3Ùª³í–\¯X®Ý‰ya£uŸ:qÇÜä1Éü¦à—Ò¢®ÓýÚ´;BÝKŠ4µiá$Í\ïÈëišÞ{?#»7²<Îd‚±›#ug½+6èk/H©™x)賤 “´îUóÜæÒ)šý7•ìiA Ñ•ÂI!²uº£­ H¹µ7“$%Û™c æižÁs©A÷Ï ±{T°[忳“?XÕÇØr¢þ‰õ7¦˜ß‘´~häZfý1ê©÷AUÁ¨?Ø¥*˜®”ëWóA»†¨-ôi±?Ö–£c=꧶9^Ÿ
-ÛÍ£®{h5ͦ»%-^HÒ:/ç˜í'#ªe¡üj=!ZÎ_;LõÎSΛ¤Qda–û–m#–²ß–³ÈÒ«]åܸ):4ˆY½zÔš£6HŠ’iòý+›c,‡,m• }9ÒØûF˜Q µÔ1ïFPûª”c6M# ‡ö^íe½˜œ¿v?MW#QïTpPX¿ã§Ÿö•rßnfÉ›Û<ÂFˆ®r¬ lÏJ›WçUq+Az=¿½IƒUF‡˜ë/uYH•ßfúõ“ÑÌf$C_¨CŠÜ¨¯¨|uÁDzF“_[çzÈgOF)4õ¤pš\1;N¥ÐWʦ{3õ¦ÚcbæàÊ.ùªPë`ÔòÑíÝn/õK$u±oD)„=4TFœç¨{c|÷v^ò; ÑÕv­ùF”M8塯r[D¹Ã&ûÜ)bç+Ž.-ÇlŽ+††­Ùåx9èõŠD¤Ñ“ 2éF÷ˆ6©ëÔs°/1Y™ËÔ—u÷KS|ÚŒ©Ç}Añ¸@1åª'1S¥\ílq¹‡Z3å´“¥¼­ÊK …—rsyAá“&ÝEŸ±{áäL–õ/t°˜dcÕÒߨ Aé¯óÇá‰{þXk_ß*v²£jG¶æ<š´ßg1™hÔ¯ŸOUùòhÊé® óeÑäcf¹Ãä«)Ÿ*ݹ%E ¦ÒZVË[%Â-e!»QæSÝë>´›OUJm*gTe… g`«*ï£bÅ°Tâ—² vª3‰B ~à•IeÍSÊŠŽ
-%ÿØžÉÓzò©ÒB¯œšW.Å&·íÔ¼¤‹òÕXK.·…f¿Þ]Ø¿‡OÀðìÐëK¤CÁzš”¸½®Çt…Þg^§I{1Ê,]"íöë^ÂfQÒMÍ u’Þµ¾qù±…»B·ò‡éˆå=¡&·ñ][‹»ÄIs¾0TºDg‹oá8v¤x_~èÈ+½/·+Èï£Q*«ª:¶µ¶Ò^WÛÚÝòÔ[ÇF;BõÜ­cË©H}u¬ýŽ§Þ:Fs Ô–ÊZºÀ¨?Pªc&UDy‰ŠÇXî²S”fuŒnvÕc ºß%6WVb†%az æçmï¤^¬â4ð¢kЉ²\¥éÙíWªüaOu»É­½]›ªë1
-Áœ¶eˆ%fÿ1
-£Bõ÷ÀÆüåK LE-Úçq2,”/Qè8 Íô).°¾ìÖíHl·$X,Am÷ ©¥°~(ÔW©ÈL5XŸZÒ¡zú!ê¢Ö™®¯‰o ”¿&RÊ_¯Ï –]¡Èä‹(5gJw]™ÿÊÛ™M/ 'JNÜñTP僬\¤W¥É+Þnr“¶R·g¦Ç³7ÓÍs䊃ã©ÛS³½Bw({¼´>A^èZ,ÄÂÛ~9GžýHƒq¶×+æ|2$AU&ðÄfFG2`žÀ=ÀÆu´Ç*å¸IÃ¥¾qAÌ bFP>¸VH …tƒ4[^LGeäɢ̛ǞYö“¥ÇJZzÊúëõ“mrÛ𔥋(QÙò så•FJ¥í¡ê³åyüN§QêÈYLGF<»†ۗakã=ýd><=fßõÕ4‹‹NEÈ€E¿A@KB7|‡|áyõ4PŒÐ
-’³ª°µ 9F@ñθòûäü#•™ø*òðåïº4!
-t/ÉNB+Å×wˆ U¼]¾áŠ¯gyøÖ×iÅM’£­½mtÿel»FæGYMCÚØÞÑ©˜þehEæSOàò¾ÔQ9&u 6Øa‡EÎÐt!qq/°½ÏVëèÑqB‚Æ®´g¡Y†ÅYe¯×ðx~4Iž`m”å®!2ºe’/&ÙA[QŸëz‘.!ŠçË­fWG£ÞΓ¬°ÖÚ^Ðy5¹+ìÖ~íÖ˜ÀTùØâ“Sê42Fÿ™£ÿÐÜ.ÝË4IÇxqª%—©b¨”°KG‰Ëy-ëSk²ŸfZ ²Ò)æˆìà¶,ù–Ö¾Y.%!©Ü.¯5;¨¦âÊÀûSyÒ!Td…p†Xx»²äM¶TYÏíÅà³P[y²ÌÒXfC¡»<Ë'ƒ¥­žù(M!U 1J«¦òخėÔR
-—ÂÁ‰ZÊáàâþ‹FÁnÞSΰZQ:Ê‹…•Ï'g¸•¶Nmš¿q¯¦ ʯ Ó³G­lÙ¿õ+ß
-¨«Êê4|%Ön÷©Û¤ûb›¾aš¡‹†µ¦’T§Têåv6F„…¾Q(ÐE¼‰fRAñÜ;R³ŠC,:L“4‹¶Jš6K•ƒÁ§ÎÁ²f ¦:¬!i³ô¼þ!Ï J›¥—4‹%ºihÚ,½¤Y’&ÛL›åaÁ R(ƒŸ¢¾áñ aR­&·a^Ö†%Õj ²CÞšt@ϼÉm+éÀäú±Î¹×V9¼ÙøŒ¢;·ŸM•äÐ=êOAw~Êñ{æ¨etŸ´v¡—?RX;™l±žY'͕àY_yNIsµÕv’æ’jº—«1 º¤0ÉÉöö“æªSÓŠ¡{±¶lg–Xw”Ì‘ç{ØßhžÎý ;Çjó…UvÐtuÔ~Â2Ýdšåâ~UÚ¨þ€œ6JJ©Q‡k—ß©ú>wG9?ÙjeÙiB¬iZ;U,žè;j#OùõnùéæøMñS}= ±Þ\diÆè¹Ï]DŒýIæuÓÛ^JvïŒz9uKÓ™ÞhsÌ}V¤‘““ –Ò’žÃç9ÙÞ}Éw'^Ê6¦ Ú¦ tÑ®ñ0Îf¯SC4"2%ÝÁ¸x€ópÜß'F·z`2™5í¥È<þæR_9r{@ȵgÙt†z|Ä##=ÛùðN²¿}"B~f™Ì}Zz’tÕ+"~ =$½#]ÊR^z-­çˆÞËÔy\Š‰Iµ”kìúoa-ƒ´c6¶\?fƒD)ÿŠ&›°*Sõ ÉyêìFj“]?—ÈA)T°1õ†îôJøºê Ýé•ßR·C½¡;=MÚÓ0‡zCwz:†i˜C½á˜AÖ±8ÔºÓ75Ò¡ÞÐ^ônC½¡;½*؇zCwzé0‹Æ8ÔºÓÓ:Ö0‡zCwú&ñtuRÛS Ò–OÄÀŸæɶ²N”Ä%Ô.¥•Ñ_1ŠP\Š÷øi¨:•½B@9$Ÿ™¥Ým @R´¯ ±3³Þ¢/„Õ£qµ™=K!¢3f¤aõ4ºñ4y¤ ˆÕ±Ðâßf˜[ä–=Ö™¢•C8“£‘R¤ßˆ >ÔN¯$g}¯x.½tP”¤Còš§êÅ‹Ön) ±Ì ⎀|íÂÀU˜ë>i8H˜?Ÿlê%­«(+Ù{›ÊðÀ&·›üåÆÖéÙ!}C|ehk{çÞèÚS¶Ž›„kâòý·+qMˆv_‹Æãä—8ýëèz“'½v÷èlëšà½6z¯É½<>ÍìlœîÜ[;~ʵú§¹±Ñ™bæZÏ5éÍ+äͽ×<ä~"+äíä5/J\!÷¸Ò¹6Hþ7÷$òõƒ‡ä§ ò¿mò¿©¦Èµˆt/ô¿9ú6AúÓÜSÈ/Ãä‡ÝkÑÐ%·ù¤kBäÚص…¥ÈµMzÁ©¦îx"Ôé¼"B4ÔÙ%$®í7E£qíGå?vuu‡º]qò·ò§õþ¦üðzÓ}ˆCò
-€¼›
-oqZx×
-
-oÑ­Þ%
-•Š Ñ
-
-ÞÆ
-
-oÉõ»
-Þ’
-x×V
-€ ïÖ
-
-ãÓ kwÎ$¿þÍð®÷~èÏ>ñ™ÏQ ÿÍw¾Gšðß— Ìì+[W´àÿg…ÁKÆf¶¦¦þÇø»ÿðûß#–&mú³Ÿþó~ð=ïzû›^÷šW¼äÏyÆSO÷·WçoŒåŽGümÍÌ#‰p“©ì¬FcãG=ŽôÆ¡ŽÄ@vdjnåöþéSŸñÑÄïx÷û>Lñ¾ôÕoÐ6ü#já²KöÕ·®Q˜˜¾¹Ec+LÍõ¿ÿÝoóë_ù«Ï“&ý¡÷í~ýk^ñâ<ûéO9ÞÛ\¾99œîï& ºå‰%ÂM;hØYFmãG<êqDª#ñÞ¡ÂÄÌâÆîÑ“Ÿþìç¿X4ñû?ü±O}ö/¿üµo~û»ßÿ¡daÚ3—ìkòÿ?{o—㺾ýÿìýÛÃZ‹¥ež¥‰4h M”!eJQQ(ŠTÒ(C‘¤ÁT†*)
-…P2•hŽ¢”Ì‹µöÞïþ¿Þÿu]÷}?CB¨îû©ó»?kÖN{-žã9ó8Ï뺟ڢ8­5#5OiäÞuªÊJŠòo^¿|!-)>:2<tëF/WÇ¥Vf¨ Ç«)ÊbãÔ¸7Òxø(9eÍISŒç.´]áì¹! dOdt|R®b,qõã' MÍ/_·PXHß{T@nA©yJ¿lFæý¸ºüAQú|jbÜ‘»ƒ7ûº;Ù£‚žn0QCINzjР3M Qä-¯¢¥kdbfµl¥›ЮýGŽ'¦ž'?¬¨®}ú¬ ñ»ß‰Kc…©n7}[Ñš’š¯ôïoß¼jn¢…ÎÏ»v)ãÌÉcQ{wnòvu\b9×xŠŽ¦²œÌ¨áHçÞ sëkëM55_d¿ÚcC@høÁØ)éY×òòq#‰±O¿{ßšÂíªo«R¥±{ã’~ƒ½û)º¤ðÎìÌsÉ Ñ‘¸ ×®²³FÆ­«¥"?zÔðÁÂõÌöëÍ
-Âý˜h¬Š4žea³ÂÅkÓ¶ûÄ%½˜{§°5‘ø-#1íÒ«p ©yJã’¦„F]UvÿÞ­äܧPA‡nôt¶_ln:UOû»c9ãBþTãi³,l\}ü·ïŽŒ9‘’qéú­»÷K«p/”¸3n©ô'B×=ª|X\w-ë\rüÑ»‚ü¼]VØXÌB:«bIÆsU7´mʬñ„äjFãÙ l׬Û~èXbÚ…ìwP3®©«oj¦ú?BwšÂBJS%Í…±úZìÜ·QAŸ9{pO°¿«ƒ­Å¬i”Î$oã=I·³mʬñ.“ÌÇ»­Ûq8.é\Öµ›wï—U=~ÚØüRXbvTZ@hÆ^¿|þìÉãÊÒâü¼«ÓGE„lY·ÆÑvÁìiØ·e°ÎbxÖ½l›6k¼¯ÃËàÌEiì»%tï‘øÓé—rn>@eŒœ MøFÍ®Ä|¡?ò„Æ©97)è»·®#ãŽ;¼7tË:7¤3öm9ñ¡ûŠõÂíù¯ÝEg¢16ëŸ{‰õ8T\FçêYDã
-›5jÈòª&›-ZîêC2¥qyMݳ篰U“2æ´Ä‚ÆÆMt®)»_@êùpDˆ¿·³ÕÜézZÊc¥°m÷ú¥K§0&uýÒ ›µÔXe-½és­ìœ½ýC"£Ì%¨1¶êÿŠ‚Ʀ ±qãýüO縨ðí›<œ–.˜e¤£m{`ßÞ=&)¬+Ê,PÈ}b³ÖÐ1šµ`©“ǦíáQq|›)yVͶ‚mƒgÜx´âéŒrXzòñƒ»·mXëh3&¶m”¶q
-cvžl«ÒÎ0§.”¬±YÏœoã¸vöÝ'§_ÎÍÇýX@c)c><ãй8?÷Ò¹ÄØ;Ö¹Ú[cÛFi{ø ~b]³œ
-yÐp”¬±Y[Û»® Øq 6ñÜ¥\4;ñ½šÑ˜mݾžqót®.+ºs=3íäѽ!þžÈ¶MQÚV )¬ –³P!‹Ë(¨M24Efíé²÷èÉ´ÌëwŠÐì$êZèÜPWUZxëê…”ø¨=AÖ:,636ÐV+9¢ –³`GF…¬¬­ol¶Øa토=Qñ)®Þ*,­B³SÐ#¤óËçõµ•îæ]IO:¹#ÀÇÅÎröT”ÂFóʹ«ÌÎTìÂ32îÈ2
-ê“Œf[Ú¹øìˆ<–”~%ïîƒÊÚúç/»†ÆAß¼lzú¨â~~N¶í`?•¶æ3'W‹ºs_þì̶F? åÖÿøé—Þ}P´–UB…<ßf¥‡_06묜üûž6½|Óe4Æðtþóû×/ŸÔ”ݾv!%îÐî@_×åVs¨rFa¯Âº‚kÓnýsO±~ƒQ´VÃ…lïê¸ëP\Ê…k·‹Êjž4¾xýîß]Gc Öåm4?¿÷º¹¡¶ŠØvbÌþPÏUKp9£î<| U΢îÚŒ[£Ø5`è¨1ã´ôg˜áBÝ“H̺ª¶¡ùõ»÷þû?x>î*cˆÎxöÇû·/›ê‰mg¦&Þ³mýRÎ(lÜ_¬×Ï¢îÚŒ[“ùIJNu‚ᬅv.¾»£Îd³®ozùöýd_Ý¥4þ|ÛþnÏȶ«QÚÎÎHŽ=¶ÙkêÎÚJ²è&Ê23nÝK Ç.E Ýéó;ºû…ì‹IÊȾYXZÌ7äÿt)³æ#Ôž‘mW–äf¥ Ëy¶ÑD59©ƒDܵynÝg
-~{Ù„]ûú…䘽A¾ÎKçÏ 3ç'*JäŸzöFázŒÒø)³­<6ïŒ:qö
-•­‘[wãB¦à…0ìÚ5¥÷n æ¼?dã;‹™z
-Ò8js{¢bDë?d”¬Ê#œ»v9•qõNIe [³ýR³ ÂþxÿæEÃãò¢›—Óâ†ù­]¾ÐÄ@KQfÄ ¾œž¨È˜LMP(\O3³E¹+":éÂõ‚‡ÕO›^u{·fÀåÌdíºÊû·³ÏˆÚ¹ÙÃÁjÖíq£GæòDE‹L&(95󗺬߾ïXJfî½ÒGõͯÁ­y0®ýáÝ«¦'U%ù×2NÙàµrÑÃñÊcÐDÕMTœ”YPdyu=c »5CÄ¥^Ê+B-ùÅpkè¬7$ÏŸÖ<,ȹèãd3wêDf¢â¢Ì|‘ј¬¡?s½›_ØÁ„4”»*jQKÆ›pk>LÖþýMsý£Ò{¹™§cQÔ^mkF&*ÞàÌ-™EFc²‰å
-w®Ïeß.©zÂkÉàÖ|¨ jÎhr~\^˜—uæøþàõ.KÍgèªÉ‘à ÎÉ,,²©¥ƒÇ–](\_ËPýôùkzÝ ÂkÎ(ƒÕVß¼œŠ'*c=uy.ÊÜRd+Ï€=G“ΣpW!¼ÜÅö Ë-˜æŒ2‰ÚWÎ&à‰Ê~ÁL} y©á\“YXäɳ¬½¶†G'_Äạ™ä.hÉ­Ád0*jß¹š~2j§¿û
-K}MZfÎD0!‘µÈ+½ÃcNgÞ(,{üìÅÛ÷BîúÌ‚Gíêd¢ÚµÅÃÁÒÔ€‘™+I»5‘#bOgå–×>#ár×gá-HPÔFÕõóIG÷x:X12se "kÍODNÉÊ+Â/\³ýrr~ÔnnÀÕÅäèð­^Ž”ÌÃpdn¦w×üžŒDÞ‹E.®¨k|õÂõ× £öŸïߢ‰ª¬ðFæéFf”´ÑÜÌe's@ÑBäK7‹+ëИ áúëð¢öÛ—ÏjË‹òÌXf”´%‡ö';í¿ü…M•y§P†áJPä'M¯`‚j¼‰êec]EQ^VJLÄV/”´Õå$†öC2ÿíY-fêÒ@O±þÃ$åÑœ,$25&C¸nü‰Š‘9|«§ƒåL=µ±£†ôýõgvÇfTÊäfHÿ¡’òú¦VŽ$x ˆ TÛ`&*48×Uçe¡Þà±b±®ª¬ø`–e&3¹ß 9u}K/A‘aò ðg,3îÍÑ{¶¸Û[̘¤2fä rIˆ-™©Aùç_û5VMo¦¥ƒçÖp”®AäÌÈ´3“îò_kg>m¢òèû°·á‰<X\VUÇxÁ
-€ð˜Óh„â‹ »¶CδÌ7.&Ùé·f©ÙÔñãd†d¶#¬ˆŒå>ƒFŽQ™4ÃÂÞ}ËžhùûáËÜXW^˜{þTTØFÛ¹†ÚŠÒÃ0cs§«LÊGŒVš0m¾›ÿ®£É™yEu ò÷ÁÈŒ’vmÙ½ë'†®_m3gŠ–‚Ô°þbdlîlϦâ5”‡Kod¶tßÎ#Io"‘_QéDþVx2¿|ö¸´àZzBd°¯Ó¢YòôØÜÙ­‹Œâ5”¥´¦ÌµuÙuê|naym#ˆüÝ0 ÉüèaþÕ³qûƒ|VZ™è©³36SÉ ÍPhPÖœ<{ñêõ!Od\¿WVÛHÖš ò÷AÉü¯o_4Ô<¸s%õøÞ@/‡…3ñØLæ©NmÍ‚3”º¾©õÊuÛ$œ»VPúøÙËw° ùx;íæúê’Û—ÏĆx,Çcóè‘™ÓæNS™‰×h†ÒEƒ²×¶}qiÙùQ"ÃZóû¡e~ÿ¦ùiÕý›Y§£wo^k7Ú%|ÚÜ™ Œ$/*^+£j¹G@ıÔ+·Ô4¼xûá_ òAÉüçû7ÏŸTçeRcó<#mE*hwZk¦›$^O˜:ßníæÝÑ)—n•T×7¿…K? #ó릺ŠÂÜ xlv¶™3YS^íÎJ`ÉKAËpîW<CeæW=}þæ=ˆüã™ÿýçïhl.¿—“qâ`š§LõQÐ&ÓšÉ œ¼$ä4 P¼ÞuêBnQÅ“ç¯ßÿ "·Xfj;‚Çæsñûƒ¼-QÐ&?uJk¦š2•¼ôL¬V® ŽÄ3Ty-^yÈí#3›³ÓŽïÝê¹ÜbúDe™xÕÙ žM5åžbHòZ°Çë³WÑ Å߆°ýuxcs} š§Rböl^»ÌÌh<N`Ñš™¦Ü˜ô¸ñ8ym M½rçᣆ—ï@ävƒlGÐØLÍS™ÉGvnr±Å LbHg´f~S–ל2×ÖÕo×ÑÓY8^“
-Dn/xc3š§Šn\8u(t=J`zjdÖÑ­Y )«é›.rZ•xñFqåÓæ7x†Bƒ2Û/OWž§~GóTù½ëé ‚¼ORÝñ­Y¨)/tôÙ~ !#ç>l„ªáÏS(h_M;±ÕÃÞ|*Õš©s‹S™¬¯QS–R?ÕÜÞcëÞãLò‚xÝÞð‚vCÍ”À¢wû¯Y2wŠ¦œÄà¾d¡ÝQÅÜ£Y_÷,!‡šò’5þ»£S.ß~ðè$¯Ž€ÈL‚võý¼Ì¤Ãa¨Ö<r`G.´é‹¿ )«¦‡šò†°ÃI™7ï“ä"w
-îÕ4”³]mg¨Sûìö+ffT1FUÏt±‹ßîʯñfDîHk¦<ûòìPß•–Æ“”¤Éd{3½Ð¨,¥8aúŸàC‰™(_S~ "wô8…<åì„ývf†Zò£ã{#íÀø£²Æä¹K×ìKÏ)Äùš¢Ø~º|Ï.+ÈNÙíç²ØTOµ=‡f\Ê$záQÙÚiÃŽ£)WòKñ!øu§Áóìš’[™‰‡‚}LŸ ˆ§Ú'€ F/£ùöžA‘'/äݯnx ~Ý™Pžw#…9éq{Ö.;Y}¬x{0Áè5ËÊÇҮݭxÂìCØþÓw(ÏÆGÍ¥ù—SŽîØàdEXûlÀø[/½×…D%eÝ~ð¨ñ5Üè\hÏ~ÙP}?ïÂI44ÛÏ7Ô’5¨]Š™~¾o½¨èŸžSTUÿâÝ°é\˜}ö“
-<4“
-`w.Ÿ>¶~åÂÆI ýáu6Sʣ䴌̗{o?˜˜y‹Ž^à×?€ß8Ÿ°«û²¹“5ÆŽHÅ–rÿa2*º¦‹]ýß»^Xù”Š^ r§C°çuåÙg¢wnt²ž‰¦©¡ý~l5Â+eyí©>!QÉ—îÀÖ‹=è ¯ž=*¹yñdd§½™¡¦œøMSL)­¢7ËfÍ–ˆ¸ô\4E½|Ñ‹%x°Š{×ð4å¼ÈDGYæÇV#t)¦Jy]èáÓ—óËêž¿­[0°ÆÇÔ4å½ÜÜHKþ‡V#T)÷F¥¬ª7ËvÍ–½ñ¹Å°ÀfâÙdš*¼~îx¸¿«©®ŠÌ°þß_Ì¥<m£oè‘ÓWp)Ã#›LS—’£B|,¦jÿH1 –òl[·
-ß'È¿|úpèº+æOJ9á<”2XäfÄïÝBó÷ÆìÖJ¹œ*e™MèiJ°˜É ä÷,Àø³rk¥ÌöŸ´;CMSørÐýñ›qÌVÆ× ÈÑÔw•ò ^À&¥Ü ¥Ì>?
-sˆÏ
-s#M9ñb¿|ó9³ÐÚËÖm‹pÀfûÏÙ½á3êÌ(fû»,6ÑQ’ÆGSßjÙBlÇuPÊ\0º˜ÑÌì½|¾¡ÆØ‘ľù
->}Ed˜Œ
-³ö‚Ræt17Tåœ;¾ÇÏÙzæÄqRCúôúÆüÕ£¾¸IΕÉ[`í¥Ì>L1×–ÞÉJ<äi7o²ú˜zã0Eî`£R–VÖ5µqÝ¥Ì-p1£™¹¾ªðúÙØ],gLP”ÜçÛ6#äqŠ_ÄŠÕ42_áBFA)s³?¼izüðVæ©îKçè«ŽþÖ5'É^½ú‘Ršd²ÈÅ?<.FA)s²
-öO½úEcÔ,[·€ý'.Ü|𸠗2œ+sª˜_5Tçœ;¶gÓj«o¦˜ì5r¬†áü>¡GR²ïV<}A]ÜdûÏ0ð7#·³#·yaªíù‹Ÿ½&[;£1ŠlDè;ØlÿÙ
-®œ>⽜ä¯þTþj“a£ì%¥¤cj³e¯‹·RÙ J™[ð†©âÜôã$Wô[›,›¾ÔGö^˽CøÙ J™cÐÃÔó:|2è¾d¶žŠtÛFfÊ°$¡0~†Õj¿=ÇÓsï×P²B)s ꘟL¡üæë`n¨!;r@[,›1li½ÙKÜñÞëN)d/nÂä¯G%yçã#ü]¬'¢‘¹-–Í Ë²†æ¾aÑ©×
-«êa…ÍM¨üÕü¤<ÿrÒ¡ esôñ‘Å×-›6ìÁxXvñˆ?ŸW{/®ÂÛ]O‹Ù¹ÞÑÂHsl[,›6ìa2*ús–yJºœ_þ¤ö^Ü„ÚQ#sÂÞÍ.‹fNT”lƒe󶛚FŽëwƤ]/ªnx{/Ž‚Š™ŒÌå—“m÷D–­ÚË0왋\6ïM *àú
-†eÎÂXvEÁ•6[vk†]û†eîò=–ͬD˜„ †Íux–-²¿fÙ¼¶†‘¹£ï’°Á°9Pʦ#_ÙeS‡ŽƒF)LÀ÷"âÏCÂæ:–³Ã×ÑÜHã+Çøé¨öìƒ/|áöÁ¤KùåOÀ°9Àbï²­'(ŒDÝùl[&7tǨO™¿b]ØQ¼Ãn
-/¾bÏæ×4¾† _‡¹1Ry/;åp°—Ý\¼þ"³T«™nË£äÇÏ°BsTÂ…›™ÅÛàóËþ𺱦8çl,YáYê³·e|Û^IÇÔvm`d"š£ž¾øýO0lŽ#xbf)«ãåG}¶1óÛ²Á<{ï#g®Â%PY¼xŠf©ÄÈÀµ¶¦:J’ƒûôl½1óÚ²¦‘ÅÊ »ŽË½ÿ¨îasþ,uõÌ‘oûy_hÌmÙÚyóÞ„‹·Ö5¿…9ŠóPùMã£û¹çŽíÚ°ÒÂHóó™™–I[Þv0‰œGÁ%YêmsÝÃ[önv¶þBcþ¤-ÃzST f)r.•tpݘ[Ÿ˜…¦eº-ÃãQ"À’ók™Ybó¦å[xZ†¶,й‰nÌ_š˜ùKlS[7Þ´ mY 3š˜oñ&æÖWÙÔÙ2¹ D-±¯Þƒ¶,*ð3™˜ÝlL?·Ê&ók¿ácÔñóQôÚ²hÀkÌ÷®Ò«lüˆk+gÌ8|á;ºrZÓ,üÂñe Xb‹
-ÌÄŒWÙ1;}Ìñs¿_?ýTtúʾ£»xMÀ~r¶üâ,±EþMÎóñá~N–Ó´äÈ}ÝÖTîÙgˆ”’îì¥A‡N_¹[YÿÚ²ˆ@VÙï^à3æ“û\Í$—¿>‰_‚;‘å>¡GSñ]8[Hcþýe}ÅÝ+ɇ‚Ü—ÌÒU’j%~Qá‹Ú‰à£Šô%šÞ@[Hc~ÿúYuѵԣ¡Ô^¤•øE‡/|±Ïj5µ©ƒ+_¢ƒÀ^ß$07dâW •Â¾Ø;‘ïE¨›$~-œ¦…—j¹ý"áë·Á(|ÍBá+êt6
-_°øñ+óäþ-®‹ðÝûO¶_¼Í9‚ð%rðâs,ÕÚö‹ŠØxó5ÅÜaýÎXê@
-—È@Å/ê¾Èá`Ïesô”¥†´¼Ä;ŽÕšºÐiSxÜyúú&´eA`û•¶n¹ÙäV©ˆMŽ­]6ï;™y›<UáKd ¶_MKn¤“ÃGCOC6u¸Œ#¶‰ 9v,¨xJžª€¶,"ÐOXÔ=¼u²[1óî|1û^%;Šôu]²÷mq±&GÌ›ì=þB=¸Œ—½CŽ¦^ƒˆ-b†l|Äl2IQbØ/‚£³Å­6Ùlź1gÉUlˆØ"/dßË>µÝcé,ÝOnøa•ƒ”†¡…ã†ÝÇ3ò<†Ãe‘‚~Z
-òDêÑoê"~Xÿ)2De<Hi¢AÊ/<¶Ø¢ ÙäêýÙ˜ëV˜MVÝb”<«pÞ¼ï¤à
-hAo²?ÈË8¾›¥Z¨L=<£0œUû›0H‰¼Qê&¥69-œª9ßÖV™œHá[ºk·L¾g"ï¼âv扽›­¦k˵8•¢Çeê:ÐöÃ)ä™V¤D
-z”·uOÀ§Rò£„æô¥/Þ¸ 'R¢u[—>•ÂóD<0 œ=
-œ;š-_s6Æe‘Ch`ÆW¿tZœ=ÒçŽÃF«O™ï°~'¾ôõ¸ N¤D ÞÀŒ‰ ö¢Î[ªLž«ÀWû6îŽËÈ{
-•©ŽÑ™µÄc{>¬€5¶Èññãÿ%;Qy÷JòÁmk?Yd Ýà$GRÌaÛ¿s í.²‘‡h„ï÷ñUž½Ô3øð™k ²B­8ùǦ“Ƶ¦2>^^æ|ä ÿž.Û¿s í0ÇU…WS¢¶{C©OU"¥¬7g™WÈ|»T9ZSYàPŠùä¤2u‰€÷Q_lÿÆo€wôx5å0¾‘-|À,xUÄÞ;4:R.ˆ_W™z~Æ`ž½OXtùØ>¸*"jðU>sx»g+*ó>ˆ€¹¨‹_S™¹öÅJ
-T=Ú 2õ,Üü¾ôÇMÀµ/‘ƒ÷¤Ôµ/«<e>þ8Ýsp¹O$ù•×ƒÊ¢
-¨Ü
-|u÷Î.À×U†Ó
-ÑçË* Ÿ<†ÂÉ£ˆòå3©On\/ª[¢G›T,¥¬;{™ÜUÚpW„¾Ý÷¾D–¶Ýûâßá„›º¢HïpJ*éÀ}lÑ¥ ÷±ÿÏVˆ:mx¶‚zž“aÚðœQYžyaÚòÌ#óó àùeQåëÏ/Ãgˆ>_ÿ,ø\ѧMŸ+Ÿ$â|õ3‚àó¾º
-ÿˆ„ ·Ja“-BPá‹\í‹Ý¹ÞÑÜPýÓˆMT&ç²ø‚߆ݼ«_¿D*|‘ÏúJ=êm?Ï@Uf¨àNáM6þd·åøÓ ®Õ@üxáëî•äCøÒ—®’Ô§áKàs‚È¥ ˆ_¢?|eÜàºh&
-_ƒ> _‚ñËâ—ÈAm¾ž×> Â—å4-|èç᫵ø DÍ×Ù˜¾æS˜ðõ©ÊñkŽ_°ý¨[ºøpùjÊá`/ê
-Á§áK0~Ì[îÛ/‘·eò€zþ¥D|¸ÜúæK0~QĠ’¯Ü­¨‡ÃGÑ€÷P+~tÙßÙŠ.·¾xñ‹| kÀþ“™wÈá#4fîóQ`'BŽ4dñ-ÝOÂ/~«5 >Ƈ~¢¯-ãáB¨È'ÇŽñ‹:|œïà»#æ,þ”lhÌ¢
-‹Æ¸Âµ ŽCö‹'hŽJ:¸Í}Él]eêñôÖÛ2š¥Ü·LºŒ¯esžaãË@hŽ²6&W¾>Û–ÿ‡?K)L0¶vÁׂn>xL_™¹ mØø“&Òbvø:š“9ª÷çÛ2o–!«idáè»#†¬¿À²¹ mØø<*ùPDz9ú*2CÉõ•)Ë–QÑŸ³ ¯¿.€esšüÛ›önvY4s¢¢=G}Veú“'ÈúËeó^lÙµôU™‹öÙ²øÒKîè~Þ°Ö_ز×ïŒ9 –Ímî(¸’|h»'2lUê2s“Y¶'X6×ù(xR±w³k[ »uˆÅwù.ÃþÔ²qʆ]6gJØm5l˦S6^Œ]6?r•/aK~Õ°…,›,Fâ©]6Üá$äÐñý«²i»aó-[VÃÐÜÁï² «àÆGÁÙ :–ç_NbV"m0lþbDZEoö÷møø‘wcdæ({QÏT”äᶋµq[¶€e’P?Ãjµßžãé¹÷k_ÃÈÌA˜["øÐ1:Ì×ÁÜPC¶M†ÍXvÿ²êSÌ–{‡Iɾ[#3a†åºÒ;Y‰‘îKfëáZÛ`Ø|ËÆ7FlÖì?qñÖC*es *{½~Vs?7ýøžM«­fŒWÔÃf,[l ¸¼ö´+7ìŠ={½¨ªáä/ÎAe¯O+îf§ñ^n6Eýk‡ŽBÅüw|û~´ªÁÜežA“.Ý)ƒüÅ=˜ì…‡åûÖؘê(I i›aӖݳÏ`I<2;û‡Çeܸ_óì5ì¿8†@öÂÃòÊÓȵ¾66eÙÔ–SÃpþ
-ŸP~þ‚ý‡Ê^·áaYu4þȘÏ^ëû´˜"׿tgÙºáüEŽ,`ÿÅ)è½×³šbœ½üV[O@Ã2¹Ö×6•{ü•ŒÌ£PþZ¸rÃΘ4jÿ}Ï!¨RÆ{¯‚+§©ì¥!;‚–Û¢2scwøh5”¿<¶L̺]Š¯ŒÀ0Åè1ªï½önqÅÙ«­Ã2ß²yù ï¿ÎåÃù#·`ƨÊ{WÏ óu´˜ªõ Ù‹²lÞ‘ÞG%_Î/{ÇàQo]<y p-Þ{É k{öâ3µÿ2Yìº9"†)ŽA®1êzZ쮫,§Wü[›³UÌ¥ö_rZFæ넇)™}èëÏkKo£1*ÈÓn®ÚfïÕV•™ý×0=<Lí;qáæƒG¯?@1sj#‚¯äœCc”³µñÄq’CÚ>F XvOj˜Z°rýŽèÔ«÷*¡˜¹UÊoŸ×•å_JŽ
-¦Æ(|æØöìEY6Î_x˜RÕŸ³Ô=ðÀ©Lúd
-Š™ðJ¹87=.Âße±‰Ž’£>ý¨ä¶sŸÁŠfX:mÄ'SÔfŠ™}JùòéÃ!>+Ì4é1ês³~©˜{1F}ò<;Ï ¼yX ÅÌ èR~VsÿFF<Ùˆè*Kã%P̽ú ‘GmFŽË)‚bæ¥|åô‘Ðux#2
-_øFæ/ü,FmF콶JʺS
-ÅÌ>¶(å5¶³˜È7e/^1ÿD6#“f.r&kN\̳ن¬½JÙÑbª¶<*åo£˜b¦7#c5 ç/'ÅŒ;3ÌÌ,Ój)Þÿ×oÛˆ3ÞŒ •VÒ1Yä‚:3ŠÙ•õ/ÞÁ6›UÈÂ¥¼`Ú÷—2eÙÿøå·ârT1£˜fæFØf³
-9Wþó÷—xVn‡R¦†)¼æ$Åìì·;6íÚ=¼Í†bfì×ÿþð†ž•IÀF¥üç-‹ùŸ¸˜Qg6³÷ Š<uñfÉ#¦˜Af6à•rU^{mYcC•ò7žS´R̨3Oši½z㮘ԫwËñ9ó¿ÿ žÍ$z}xÓT[zçRrTˆÃlÁb8r¬ÆäyËÜ÷Ÿ¸w¿æÙ+X°}ÛëE}eáõsÇÃ7ãµ—ŠÌ÷we~1ã™yÜÄ–«Öï8šr%¿¬î9¬FXâ#sÛëñƒ[Y‰‡‚½W˜iý`)31[lÀˆ1js–¸ìÏÈ-®n€='KÐSTóÓŠ{×Òbwû9/2ÑQÆk¯(eRÌÅÅ<DRqü´ŽëB'_Â{Nz52w.tôzõ¬¦äæÅ“‘Ažöf†šrâ?XÊô MVÕ›eãº9üø¹ë…ø:0àMQåÙg¢wnt²š9IIzè÷m°…dfΙ嵧š¯ðÞ~(1óÖjšÏîd¨R~÷²¾ºøÆù„}[ݗͬ1vä@±_~¬”™£©Þý‡Ë(ëÒ«‘«w+Ð4¬³a¢WÓã‡w.>ê»rጠãÈ‘,e`rš†fvÛœDÓ0àE/2EEl^c;[_m̈bßq®ÜZ1ÿýg²çœdlµjš¦.çã
-ÚSÍ—{n;pâübðìÎBد…o^³dÎdõ±¼vû¨L0²Ó™iå´>ìHò¥ÛàÙùß¼Å~}ùu×ró©ã%‡öýᶰÊÌL|¬Æä9KÖl?žÝià¦,ä×NÖ3uTFo—­W ™Él(šÍ—{1žýöƒÌ iÊx]WVÀók ä×}zþ³¶^B2ó†fk§ Bž ­¹¡üúÞ_åf |üzÚxE)jTþñ­—ÊxhþgÏ>ƒF xvAYÞgCkî@>ÒCÔˆš’[YIt¾VÝ>§ŸÊüWʳ¥(ÏF9;#·ï³?€gw ÿ/¢^7Ö–æg§Æîñ_cËøu»F/FfÏžiµj}Ø᤬[%5 /éq
-dîpS&CÔ“ŠÂœôøýžt¾î
-ÚÓ-ìÝ·ì‰>•W\ùdþ^>ò·!µå…¹çOE…mt±;EKAjXœ¼:±)Ó*S;0*hë/Xî2ÿ|‘›ê*
-o\Ä3Ôš¥fFãI¼îÈ‹_–™ÚcÕôf.tðÜ“’udþNh‘ñÊ«®¢(/3ùè.7»ùÓ&(1ñº3“—ÌÿDA{ˆš§L,¼¶FĦ\™¿:xa‘ŸTçeŽÞ³ÅÝÍP*cFòâuç«L'°_z÷G󔆾©•£W Èü½|"rLx€ÇŠÆ:h†ÌB¼'°öDó› L­Wz f„¢D¾™•¾Õ ÊzjìÌPŸÈüSO±ä4 fQ2Ÿ’–mY†Ð"_J‰Øêå`i¢¯.'1ÍP¸ØlEeZf46#™'c™·ÌßÙ]Séš9ÐËÑÊT_ Êý‘ÈlÄëOdîõ™áè¢M0"¿C##²÷JkSMyI4(ãŠxÝBæÐ2kµ&3D~ ú
-o¼ê*Pð¢Eže © 5 ʬÅka™ÿö‰Ìt#;m¸=òe˜£Æw/ñœÌy2yø€ß¸ òe®k"‘ óøÈ;O~ù¬¶¼PB"³7(Uf<PeåWÔáóf¸ öÈ…¯ÿ¼}ñìqYáL4'oõùœ¹u™#°ÌEµ/ß1Wþ@çOav!ïß47<*½—{19‰ì((2›3”­ÊŒ.
-ËkŸ½|KÉ åü)ô˜üçïošëk\?ŸttO€§ƒ•©÷DnMf¯­á1§3o–=nxñæ= έ“_?Zý ÿZÆ©#»¶x8XrSä2ã¹ÙÉ|1÷né£úæ7x?͹%ü ªéIUÉ«é'£vú»¯°4Ñç¦È-e60µrð Øs4éüõ‚‡5OŸ£Á2X xáÉ•÷o_9›p0Ìo­ý‚™úòÜùS™-<¶ì:|*ãZ~Iœ!ƒµ€Ê]ÿù“LPÅ7/§ÆE†n\cga¬§./9Œ›"·”YßdárwÿQ'Îeß¾_Y×H24gtKFáúEÃãò¼¬3Ç÷¯wYj>CWMŽ»" Ë,¯¡?s½Û¦°ƒñi—o•?~Fe0pmÂG~îj®'ÔéؽA¾«mͦMR+1´¿XOŽŠ,(ó0Iyu=c‹e®‚÷Oɺq¯ e°×¼ugw×™^…à–Ü„ÂuÁõ IÑá>«læN¨";ŠˆÌWkÊ,§¦3}þgß ½1Ér
-V?å5çî^Î<·~ûå.®OÙàµrÑÃñÊcćôëÍe‘y2÷ë?Tb¬ê¤©ólœ|¶î9š˜qõN iÎàÚný¦µäâ[WΞ8´s³»ƒÕ¬)ÚãFÜ—œ'sWdþµ±þCFÉ*O0š³ÈÑsË®¨g¯Ü*.Ü€&ç?þݽ³6ß­_?¯¯)½w# ‡ëMnö M ´eF êû+×EæËÜ»ßñÑJÚ“gY®Xë—ŠšsiMýó×ïºuÖþ(èÖOªä Üµoû×eh‚ÒP>°Ï¯?s^dFæþһ2Šš(jÛ¡ ¶/öôÅœ‚UO]»ûéÌ2qk4%ß¾š‘x4<pÝê%ó§ë¨ÉI!‘{ýü¿q^d¾Ì¿öˆ&*uÝó—¬^·-7çÛŵ ÍÌ"¬Û•3]È$[?ZSZ˜w9-!jׯ•‹Q¸F“±Èá¼È<™¦¢6Î`‹Wz¡æœv)¹6ÊÚo?tÇr¦Wšÿþãý›uÄ­SŽõ[»Âj6
-×ô…Çd™–ùoÿ@2÷Š3˜ál+ÔœC;}ñz>ÊÚÏ^¼é~å,Pȯ©lž´ÞÕnÊ]ãF‹î‡Çd² ‘‰ÌôD…3Ø8-Ôœ×E=•ž}«¨ìQýs4:w¯r¦:2»šžT?¼wãvë
-Ù›„CX¹`9wyÛþHGkªqìºyåÜÉ#áÛ|]íšNF-Yb(»DKdÁæ<`¨„¬òøɦ ì\|÷>‘véÆ]~9w}ÛêÈÏj+Kòs2ÏÄܱÅkrk=MEñ!ýHî‰p- nÎÿË4g49ëÍ0³YéévàXòy¦œ™Ù¹ ëL4þÿÆP…\VDb×Þàn+¬çMÂS2nÉ¢”»éA¶Úÿ 7çáRrªf[Ù¯Y¿-ü¯œÑìü*ç.jÛy©‹DkTÈ9™©ñ‡vmõq^ja2Y¹õ°¢Ø’ùð›ó€a²JÚÆ涫¼6SåŒÂ6š_¾}ßum›Ñ˜,»^4>©F9;#)f_ˆŸ»ãâyÓu5eFé/&’-™¯9‹õ<RFA]gê«åk6árFa»¤¯Âp
-ë’i›×‰Y×?ª¸;rBÔî@_»…³ '¨Rn-B«ÖáMνû¢¬=…°™æKœp9Ç&gdß*,­~ÒˆR؇u½öL5d’¬‰YW=¸›w%=1f_¨ŸÇJ³úZãÆŒBÙšçÖ"+2Ýœ©}gÿ!â£5t¦Í±^ŽºóžÃ ©™9ù÷+Õ Øv—ÑY¨!#³F©ëöµ )q‡vúºÚ[ÎF±K^z$^w‰¶[3ЮËy*g•ñ“MP9{ú‡îIL¿œw÷²mœ¶»R{æiŒ2JÖĬ³ÒNÝŒ y¾±¾¶Š]û l-ânÍ@¹6†ÒålµÜÕ7p÷¡¸” (…•Vã´M·ç. ³ Æo_>¯¯­$ft,rG€‹.du»~ëõ³è»5íÚÿø΃FHáržin»ÒÃ/xïÑ“iY9wÐðü”´ç®0= iüêyC]Uiá­«Râ£ömXë°Ø ²²¬äðTìênÍ@¹6]Σp9Omiçâ°ã@lâ¹Ë7
-J*£öŒ—a<ESè?««.+ºs=›uˆ¿§ÓÒ¦†“ÔdÄQ!ãØÕ5ÜšA¸œQØF³³Ùb‡µ¶!Û>}>ûæ=Òž_¾¥uÑ‚þDãš²âüÜKçcìXçjo=wºž–*äA]®)èrþ‰êÎhvždhº`©“§_pÄ‘„3¯¡©ªªÇ0¬óþ+‚ÆýQXãf¬ñýüÜËéÉÇîÞ¶a­£Íü™“'¨¢hÝ% ™B¨œ‡KÊ*iéMŸkeïâ³%l4jÏ×ï•¡FtF9LÔŒ›’XHãòû7®dœŽ‹
-߾əõ,# Å1(Z r™‘™tg4;”–W0Ùxþb·õ[wF¢ö|)7¿¸¬×3Îad®•‚fÊø¿ÿá{5­qüáˆog;+lÖÊc¥F FÑšrWsk~9÷î;pJaض-–¬tß´ûÐñäôËXçj¤óË7ï¨ùY$
-šgÕÿù÷Ÿ5>Ÿdoè×å‹ÌŒ±YˈЧ 2‘™Ìν~ë?¥0%-Ýi³.Ãí9<*îtÆåXçÚú¦¯±Î"`ÜyVýŸ!ß¼D³êÇDã¬ñº5‹ÍM '©c³ÔW ÏÈ]±# Òƒ¿
-ë3`ˆ¸´œÊxýs­í½ýC"Ç#sï•V=~ÚØŒ´qsQhžUãvüþѸe.äÕXã°
-Šiãþ¤ Ùš’˜85)clÕÏž<ªxPx;çR:Ñx Ñxö4¤±¼Ì¨aƒú‰õê&fÍ£϶q{:RZNYKwê,¤³«𞃱'Ïœ¿’{§ðA…@A3B³l݉‰Sÿë\ÆĪËKîÞºžu.9î0Î\ŒÆªò£±Æ¤!w³æÞI{*.#¯¢­‡u^áâí´ëÀÑ„ÓéȸïÞ/#M:t ¡ÙPú#Ϩ‰ÄØ©_ã2~\YZœŸwõbZâñ¨ˆ”«4>…®ž?1ɺ‰ü?ŒmãöÜK¬ß a£(MÍ/wöܶ÷06îìùEQA×7QÎM Í÷îÎTZ ˆi‰±S77’2FV}ùü™“±÷ûû¸: ÌÅhŒƒuOÜ»“YóéÁkÏ$†µtL̬íV­õݲç`̉”ŒK¸ Q‡~‚œ»¥Ð§ôGAŸæIüª¹©¾îQåÃ₼kȪãØäçí²ÂÆg®O4îVf͇·$!1lÐðQ2rÊš:SŒçZ.q\ƒŒ{çþ#qIg/f£]R^ýøé3Jè$Œu–Ò?~RĨ‰ž<ª*»÷*ãÔSÇ¢ö†nôt¶_lnŠs5­qo»¥Èÿ#Øž‰ÎÃFIË)iL4˜>ÛÂf¹³Ç†€ÐðC±'RÒ³®åå=@B£ŠFÖÂUÒ¸Kó•nw­ùS
-S¿Ç½¸¹±áÉ㪲’Â;7²3Ï%'DGîÞì»v•µ™‰‘®h, Ýž)‘o‹KÉŽSô|ëe+Ý|üƒví?r<1õüåë7 h¡Q{ý—ôŸ‚5Í“º=´þ($0_aRÄ/ž7"£&#§NO9‰ÊxGà&oWÇ%–s§èh*ËÉÌóè!¬óÀ¡#%Ç(¨haã^h»ÂÙsC@ÈžÈèø¤´ Hèü¢’²ªGuõÏž¿À%ý X<¥‰Ô|­¿Kmÿ· À”Âïßá"njxZ[SYzÿ’øRFjâñ#ûwmßìëîd¿Øbötƒ‰JrÒhvîÇÝ\c _g”ÃÄú2BB†÷´Y¸ Q‡Þq0&!9íüåkywîÝ/­ %Í(kš’š§µ Ø_¼Å÷Rÿ
-YµÞx5EY)q2ƒÆ-áéŒçªÞ}ú&.-«¨¦­kh<wÍòÕî¾þA;öŠ‰OJ=Ÿu5÷öÝ¢eU5µOi¥ßñ¤æiýµ¿O]J_¾ÀÈ¥_¿D
-7<AE\VRTp+';3ý –8<,ÐÏÇm•*ã“'i©(Œ‘9t hütþ¹goʸGË+ã‚65³Zâàìá»9hGÄÁè¸Ä3é¯\Ç%ý°¼ê¥ôË׌ԔÖHlžÚŒÞ­hÎÿú;ÿKËKéKÆ%ÜÜô¬)\…Šûôå gOŸ<~ä@xØ6¿uî«—Û.œ‡Êx‚º’œŒÄð!úŠýúËO qëP:ã= nÐȸ%=VQU[gÊŒÙæÖK]<|ý·……G9vy7*é[ù…XéšÚ'õÏšÔ¤ª±ÖXlžÚDïÿò5'ªóþžþ5ò}X],/£ï"pcÃÓºÇÕ•eŠïåßÌÉF>”µwh –xÅ«ù¦Ó&OÒDeŒ­ºŸÞx_ žÎ¸AcãD
-ZI}¼ž¡ñ‹EË]Ü×ù†ìÞŸ˜rîÂ%¬ô½âeÕÔ MÍHë×´Ø´ÚXo¢8­9Qþ;òUüËøÛˆºH^Jß—ÍÏ›žak*Ë–ݽs3çê¥ gSNÅEÜ»38`£÷Z"ñ¬StµÕÆ•‘1Y5ŠÕ ñWèÁ7n’¸qA£ÑJAEc‚¾‘ñTÑ+V¯õÞ¸eûŽð‡cÏ`¥snÞ¹[tÿaYEUÍã:¤5ªkJl¤6–û=KŽ4ç©N©Šuýÿ"ú$.RËû¢ùy#Ò÷Ií£êÊòÒ’¢{ù·n\¿’uþlJb|ìá{‚6¯÷Z³j¹­%’ØPw¼º’ühÉ‘¨Œ‘U£vŒÏ@ã¯Ð² Q‡!¢˜ŠæD$ôìùV¶ö«\=|ý¶ïŒ@æ˜rö|Ö•k¹7ï—`­«!±Ÿ6<kDj#¹_¾zõú5’üÍ[":Vý=QËŠt}ƒ~õÕ«—H\T½DÞºÇHߊ²‡÷)³/]HO=}*>æpdÄÎà­›Ö¹»8Ú-^hf:}Šîx eäÔ£†“2f¬4n|qC;·„ÌX$ô=Ãé¦ó,Zæ°ÚÍk½`È®ˆÈÃ1ñ'“SÏϼ|5çÆ­;÷Šî?@bWV×µŸÖ74<{ö¬±±©é9ªq¢ú+¢*ªÙçMMèWêŸukˆ¼¾·oæÓN'&;zhøÎà@ÿõ^nN+–Z[Ì5™6Yg¼º²‚¬ô(äÔýûˆõú•1ϪAã¯Ó£EAc熄–UPV¯3yêÌ9æ–6vŽÎk½7øï ßèhlüɤ3ié2/g_ÏÍÃb#µKËË+*«ªª«kæµµDu${C=Qµ¶éZS]]UUYQ^^ŠÔ-.¼WpçV^îµìK™çÏ¥b££Dì
-ðóõrsv´³±œ?ÛØH’–š’ü$ñPÚ©¡Œ¿‚þçϔЃ°u‘WRÓšˆKz®2oGg7/_¿€ P$õÁÃÑÇÖ§SÏf\ȼ”} ©}óöü‚»÷
- ‹Š‹ï— ÑË°ê¤j’µä~qqQaá½»ùwnßÌ˽Žå½qé{2>ö(8lûVÿ >kœìl¬ÌçšLŸ¢7ASuœÜh)JbâÔ¸C=Zz”ÔhäÝêÚ“ôf˜Î³°²±spru÷^ï·e[pØ®ð}‘QGcŽ'œL<}&í\Æ…‹Y—._ɾzõÚõœÜy7oÕ1XÕ[7ónäæ\¿võjö•Ë—².^È8—vË{<æhTä¾ð¡A”ÀÎ+—/]l‰6ÔŸ¤|z¬Œ¤8êŔĴSC'Dg¹„î?pònTÒãT5Æë ¥çš[.ZbïèäºÖsÝ¿-ÛCwìŽØyèHtìñø'“’O§œ9“švö\Æù ™Xu R5óÂùŒsgÓRÏœI9œ”xòDüñØè#‡"÷EìÞ²=pó¦õÞ´ÀVófÏœ†¯¡2NnŒ´Äˆaƒöë#ök ‰Aãï£GK¡ëÓoÀà¡#Ä%edå•Ò“ô§L›9kžùÂE¶Ë–;:¹¸yxû"±·‡íܾwßȃ‡¢9{<.áĩĤ¤ää䤤ÄS'âŽÇÆD=r8êÐÁÈûö†ïÞ´u‹ß_o7'{,°Ùl“éFºµÔUÆÉËâ":h
-Cw<=>Uš–ºÖÕ5Wöè1²rò
-ŠŠã”””UTTÕÔ5´´‘ê'Mš4©ª­¥¡®¦ª¢¢¬¤4NQQA^NvÌh)ÉQâ#‡Cúì-š'0iÄ|…AâŽGHiJj\Õ½~¥ÅîL|R{’{”„„¤¤”´4},RÉŽP@ªŽÅ²JKKIJJHŒBâŽ@êÁåÛ¯oJ_aAá·¯4#5¥5_l¬ö€ <xÈ¡Xt¢:’‰ŠUŲ2dðàAƒ€Õ¥ä%õ‹ôf FiJj¢5ªk,6ªìž½zýÚ»wo11±ß~û­V}­*‘µúUô=è;íEª÷矰¾èŸÔR`P˜=(­I]±‘ÚDn$8¢'V]ŒV•ÈÚÿ2þ6,.®^,/¥/Ì-zhM‹ÕÆrʼnæHt¢:ÍODÖâ_¤¾íoD]ž¼´¾ 0Çè!$6Q›èMøÛßø²óD%²¨oþ‹ ¼ 0‡é!È_xü•A@Tž®BÚ‚¾"DÖøËgTu»­j
-º
-|iY/U-5E5%%m/ªh©(j(i© üÓø_âý[—õâQYCYQ[SESœÿOC_QS×Rø—2_YÖ‹ÿ{c¾ÆÿðþQ­üA—õ2XÚK‰~- Ð+4f¾³ó’Õövâ+Ü–Ø9Ú;»‹k(ËÒß L¿l̬襢‚þIªêÚä+
-¼ÿEÿµ˜•²š¸Áê^ËÑ¿ÊÀ ×ü^ÚâcdÅX ¿#ØõÒ ¿ mÓª‚_þf=¤m~@i›ïÖDÚæûU‘¶qFÓwsŸâ¸ÌÝÑÅy‰›øxêKnK¨¿£?]\ßÃÝEœz‡;®³—7ÏÝÍÑy…¸<úõ âcôí\–ÚÛèO׶™µt­½›§½±½ õMke©oCaÝõ]œÅÕUU‰HT+HhEMuZmT*¤¦ŒìÇSk`¿ÂÑ™.0$ úž©½ÆM±÷t\f?wªøÓ^V¼7‹ª²¦¢†²†Š¸‚¶†"zh‹/WÕÖ"¡÷€ÃW¾ÿ
-óíÌ_è¿ñ{R“üÕÐÐ7õ’–&¿+lãÅÕ4ÕU”Õ{-˜þó÷=ü
-Àl¿õ
-
-Û¯
-
-
-
-
-
-
-
-
-
-
-
-
-àT
-
-
-p*
-
-
-
-
-
-E†ÚBI¨ H”!["”æ¨$ Š¢”’!’"„4ÈÐH¥yÜûœsïÿîßÿóYk}âûµ ïç}Ü»%u³žß÷ûõ~ÖÀ% S
-š
-š
-
-Ý-§›Ð}Ù
-
-ú¡Û
-º[Bß…n3
-
-Ý5ôt¿Ö
-
-à0t¿ 
-¦¨¨ªihjéèêMÕ×700d`` ¯?UOWGKSCMUe
-#?INVZJB\LDXH€Ÿo,RÒžº?õ¾C6T`”ˆñ¢ÈƒŒÜ¤ÉŠÊ*ȶ®ž¾Á4#“é¦f3gYX沶¶a`mm5ÛÒbÖL3Óé&FÓ ôõtµ‘eÅÉ“ädÑñB¸JFñ°!m`W‹‚01Ž)B^AYE]S[OßÐxº©ù,‹ÙÖ¶s~›gï8ßi¡ó"W77·Åô_W—EÎ æ;ÚÏûmŽ­õl‹Yæ¦Ó õõ´5ÕU”ä‘’ ¨JùÙŒ€V´V1|Ï(ÊÄIB„ªº–®þ4“æ³mæÚÙ;:-\äºxé²åž+Wyy¯ñ]ëççÏÀÏo­ïo¯U+=—/[ºØuÑB'G{»¹6³-Ìg˜LÓ×ÕÂJ&ÉI£"¡Œ ®BX|§bô>~AÂÄÄÉJ*„ˆéf³fÛþf?¡‹ÛRäÀËÇ×Ï}`pÈï7…GDFEEoa¾iãï!Áëýý|}¼—¥n. çÛÿf3{–R¢£©6EQ^NZ’0‚r„§º¯}´£B@HD\›PÕОjhbŠDØ98-Zìî±ÊÛ×? ($4,|sÔ–˜í;wí‰Û þ`¡C‡1è¿ ãì‹Ý³kçö˜-Q›ÃÃBC‚ü}½Wy¸/^ääð›í왦Æz¨H° 1!T"L!8Ôm.ð,ËR!,".%;IQE]{ê´éæ–6¿aËVx­ñغistÌŽ]{ãöÇ'$>št<ùä©3©igÓÓ3¤§ŸMK=sêdòñ¤£‡â÷Çíݵ#&zó¦Ð þkV¯X¶xÑû¹Öf&†ÈÈ…I²’â"Bß ¡ûÒô6d‹j­Bt‚”œ¼’ª¦®¡ 2aç¸ÐÍÝsõÿÀ°ÍÑÛvîÝèð±)§Î¤Í8Ÿy1ërvNn^þÕ‚‚BWóórs²/g]Ì<Ÿq6íÌ©”ÇŠß·wç¶èÍa!þkVy,uEEbmjDWSUI^Nj‚ˆ0)dØÐÁéƒUC‡±©˜¬¬¦=ÕhÆ,ë߉e+½ýˆ-ÛwÇ8t$)åtjúù Y—¯äæ^»^|³¤ôVYyÅíÊÊ*••·+ÊËn•–Ü,¾~­° ?÷Êå¬ çÓSO''9t v׶èˆÁë}½<Ý‘‘¹V³fL›ª¥¦< A- …úðaD ª†Å^#Gñòñ ‰*ÔµõÍ,mç-pu_áíø{xÔ¶]HÄÑã'‘‡‹—sò
-®]¿Qr«¼¢²ªúNͽûjÖÕÕ7 1øúºº‡µîß«¹S]UYQ~«äÆõky9—/žO?sòø‘„ý{wn Û°ÖËs©Ë‚y¶–¦FúÚjÊò²Râ"‚ãƾa¹Àe1vœàx”ò¨*ŠÙs.^¾Ú7 dSÔöÝû9~òLzfVv^AQqÉ­ŠÊjä
-r³³’”¤ÄøØ]1ÈÈzßUËÝÚϱ43F¢$/#!†;*¡¬¡ûbq¦ Ô¢PY %!#¯¤¦5•Pá¼ÄÓË/hãæ˜]ûŽœ8uöüÅìÜ‚¢¥åÈÃýÚºúÆÇO‚ftý_µ¼~ýæíÛwïÞ¿ÿzûÝ»·oß¼~Ýò
-¹iFbž<n¬¯«½_S}»¼ôFÑÕÜË2RO&ŽÝ¹ushŸ—Çbg‡9¨Bô4U'JãŽEÈ ðÁrZÔ¢,&*¨hê™Z*¼ýƒÃ¢¶ï=pø82q)'ÿZ1q÷þCäáéó¦/_
-Ðõÿøñӧϟ¿|ùúõë7èW_¾|þüéÓÇÈ !æÕËMÏŸ"'ïß­¾]Vr½0/ûâ9d$ñÀžíQaÁþÞž‹ÚÛZÌ0ÔUWž,+‰ „wÕ°²6.„pY(«ëΰ°µ_ˆT¬Û°)zGìÁ£É©ç.^É¿vãVEU ñäòÐòú ²ð+ üñÇŸþùoÄØÁï@ïÿãB óYyóºåesÓ3¤¤öÞÊòÒbdäBÆ™ä#cwDo
-ö÷òps²³ži¢¯¥Š
-D|¼à8¢a d­]ð ‰JÊNVÑœjlnmçäæáåTìŒK8v2íüåÜÂâÒŠê»êPE`oÞ¾|%þ‹ø‚ÿe‡|þMB áIÁNÞ¾AJž?}Ôðð~MUyÉõ‚ÜKçÓRŽ%Äíˆ ö[½ÌÅq®¥é4] e” ¢BÛ»‹ÑÈ…˜¤œ‚ª¶Á ‹9Ž.ËVû‡‘*Îffç_»YVYƒL ’@EPÒqáÿ?’ÿk õ^†¬I!|üðîíëW/šž>FFîT–ݼ–w9óìÉcc·Gm X³bÉÂy63M¦jª †%*Ä?`}´q!,&9QQMgš©•ÓâkB#·Çb²ó‹JÊ«ïÖ6<Æ&Pkúø ‹ =Ø üÕ>ìfR'ß¾¢*yOyT_{•HQ~vfZÊÑø=1!ë¼–»ÎŸk9Ã@[UANR¬º¯!ÇhÝ£HºFæÖöÎî«ý6DÄì‰?š’–‰UTTß{Øøäù‹W¯‘‰Ï¤ B›†ï/?õHñ¼RH%ØÈÒHóó' ïUW !—ϧž8¼WtX ïŠ%NvVfÓtÔ'¢@gó1PÊFêÈ…ƒËrïõ¡Q;ö%žH=™©¢éEË¢&°ˆÿ0D´ÕÐéÓÞm¥0”à"ÁF>¼{Óòâù“F$¤üæµÜ¬ŒSI÷n‹ñ_íîlomn¤Kø êÏW¤]‘M
-ïDvS.fÚ8¸zø†mÙôTzVîµ›åXÅSÔŸÞ¢¢@5A”Äw"ºñ~k%”T#(GÞ¿myÙ„„Ü­*+.¸r!-ùðþQ¡ë½—»8ØP>ˆ<ÇûÇ€hWd“Â{71ÓR.l]=×mŠÙ›püÌùì«ÅeUwqU¼|M¨ ºª ÒD÷=´ã„*ÊÈWBª†5·K‹ò.eœ:¿{KX ‡«ƒÍLìCûàÅû`ÿoWT“Âg ¼Ø…Î Â…oPø¶ØÄä´‹9×J*î<hxüüªŠO ¸(8%¢­F0…¼ii~ö¨î~uÙ‚ìÌÔã {c6­ñtu$êCANBD—ÇÇ?úµF“ÎÃË'("!§€³Û»ßw8%=+ïú­ª{=kÆYA¨Àý‰UÑ®¦wo^5=i¨­¹]r-÷bZrbì¶pìõ«i:ª
-²Æ Œ=²·+faŒ=V`üYUi(»±‹ÂÅ¥üâ²êûõ¸C½kGçM´2Â&ä
-uÔ±PÜ«ºu=/+=åpöááâ`mf¨­"/-ŽÇ«þÜ®° \#xÐ %.-¯¢mhfíàâ±&8b;r‘qéêò;µ O›[Þ~øôåÙ ˜ý‰{*Z¡„ Ez=jXÅù—˜>Ù[™h*O’b´«~XdaüëW´íñ ¢ðVÖÔŸa5o‘Çš Ü£N.jjŸ½ Ë¢—U´‚3„Q ÍOkï”߸JùôYælg9}ªº"Š²]õÃò`¬h’âGMJQ]ÏÄÒÎy™O ‘—¯Þ¨¨©}D¸ÀeAÄvïª` iU ï^¿xÖX[CúHŒ ðZê4w–‘ŽêdÜ®xFô»ò`/ŸjR:F3ç8-õ
-‹‰MDy] è~ùú=nQìeÑ«*¾‚ „ðñˆò‘|hï–PÿU‹mÌ µ”'IŠ
-ò±Êƒî«ÜE£* 4I)i˜Z;º­ôݲ÷Prk¸E1Ë‚­„àÁ ëýë—„âü¬³'îŽ
-Yëé2o6nWhºÂiN”G?éV¬Q
-ÆdU]Ô¤yø†Dí>xâlV~q9îQ/ß.˜eA›ŠV>þ7,†;åÅyS“ìˆòqwš;ÓMWRý«<¨.…v ”¸0 Ím,õŒØq )õb^1š£Xuñ?´— fÃbúh¬­.+ÊÉ<}$.&ljWÖ¦údyàáª?è »Q‚da̶w]á¿qkÜ‘S™9EeÕhŽjë‚n ˜ ‹òñâYêÒÂ쌔C{¢C|=ÙYëå1íý Ì©.…–o~á DaÌqZæ¹;!9#»°´ê~š£ú¦ ‚V>Þ½n~ZïöÍ«Yi¨]…z-oC”‡¸ð8޾߭]jĨ±¢Rò*(1ì]W® Û¶ÿXjVþÍÛ÷êŸ6¿~×w]`Ø}||×Òô¸®¦üz.jW±[Cý<QyiO™$)"0fTïVl]JH\FACßÌv»Ï†¨=‰'Ïç\/¯yø¸©í}Ù†éãÏoŸ?¼}õüQmõ­Âìô䃻6y/™o=cªÚdi1!¾Ñd·ê£³[—/1QYÛÈÂÎe.Œ¤´K¥UŸ¿zûá3Ú/ú´ öòÍ»_?xóâiýÛ7ò/ž9‡ÊÃÃù·™†šJrݪo†Gë.¥ª7ÃzþRïàH\¹Åw럾xóþóW´ëᙶ/»À>ð>øÇ×OïZš?¬)+ºr.9aWDàêųMtUˆnÕWg+"2~ù×°¸KÉ*j˜ÏuöðÛCFuí#Ô¤>}ýŸô}ÿÕ®þ‹ãµ«Æ•%W³PylùÝw™“­™¾º‚´ž­ú¢ÄÆÇ;u©)ÚÆ–ön«"v%¤à¸׀¶½_¾õñÀh {| i÷ Jó¢+Éñ;6­[áb7kš–’ìa¼ ö¹ð`FƪK¹¯ Ù{45 Jï÷ŸÈ&Õ_\`Èø ÚJóU7ó/œ>¼'*Ø{ êV:S&JŒçï{áÁŠ ´ñ)¨ã.åé¶=þDÆ•ëåw럽|‹'©þÒ¤Ø`ÆÇ7"ÍëjÊ®]>›´?&tír'[Ó©jhÄáѧ6† "2”´Œ,æ¹®
-ܼ;ñÔ…ü›UQ…A5)º/ðÏAµ+<]áòh¼ûFîù”„áëV,úm¦†‚L_ RÚ2Æ £ÈÐ1±r\ê³aKÜÑ´K…·jêÐ(õFÿjR,Z—Ç“‡Õ¥Ygï‰ òr³·4Bá!Ž7¾¢ƒ1LD[†$Š S[§åk7n;p"#Å7Ú1Þ1
-£?ºÀ0Êã¯h¸zÖp·¼(ûlÒ¾­!kÜç[á!<nôȾ¡ƒ¹óò‹HMV×7Ÿ»hÅúð]‡N^È/©ª}Üüí{(1úa“bÁVï_7=zPy#÷\rüö0?…¶¦zª“$‰,ï:(x˜•VÐ0œ5ÏmuPÔÞ#©d—"⛥úkaPåw²[]½x*qWÄú•‹æšë«O–!G+šuPk–!&£¨idé°Ä'd˾¤ô+×q—B ß7F—¢û‚ö ª<È0V·ìÚ¥Ô£{£‚V»Í›e¨¡ G+ºu0e ÉVVIÛx¶£»oè¶øäs¹7*]Šßt_ÎÃ(Ô­ZšÐluýJ:Ÿ%ö–Ó4û€† ´fˆË*k›X/Xî¶#áä…«¥wêž .õõ~ß­a”Ç_Ðlõ¸¶êfÞùäøm¡¾îŽ³µeÄhÖAÊŠd‰ËMÑ™nãä±.|Wâé¬Â2´ñ1f©Q$Ìnõñí˧uwJ¯^8™°#ÌoÙ|+cm%šuP2FŒâš0qŠî Û…+ÖoÞs$õrŠ ´ñáYj€t)D·Â³†»e…Ygw…û{,°6¡YK†ð„‰*z¦sœWDî=vöÊõÛ5ão
-¤C‡6ÄqS†ùo®^Á[öŸ8ŸWr§å7«K1 ÂãO/Ÿ>¬º™›~,62€N cQ€cn^¶H>Ÿ_ZSÿ å÷” º/w`„Ç·Ïï^=­«¾™›‘D«JS†wH̫̔¥5 Ï[Þ3ó›îËÆ5XYþêYݬ#®Ž^<³bÊŸ¨‚2ˈO¹põÖ݆ç¯? |¬,G{ù³ú;%yHG¡ƒå½w„HÊÆ3F-}zf(3ew›Œ¶òµs´zßò¼½w¢KÝ\B2Äd§èšÎuñÚ@ʸ‡eàÉv`S­¡F+4é’:ÎQÍ
-öÞ’1rŒ€˜¬²ÎŒ9‹Vo=@Êh~óq°È`ŒV,Tv 5PQZ”Ÿ—ºýÄu¤ ^1%鶋Vma—1p'Û¶P:þdéˆ\ïédm¬¥(-ÂOÜœåz¯B¥ñÏ ÉË/*£¤=ÝÆyePôþäÌ«ƒOF»:6¯÷X`e¬© 5~ÒÁõ9ËøeèˆÑü"ÒŠZ&Ö WFí;‘‰¦)vt_¦Þ‚X<X:r3ÐV¾Îcþl#É’ãùF çöÚAŽS#F/¥ ilåäwü|>’Ñ4ød´Õ·ò½þË- Õå%„Æò ÿ•«ÑÁ:‘œ¬a4{ÇúÈؤshohz=e´£ãèžp?w‡Yª'2Ö®Ù gÛ±BòꆖŽËý#öËÈ+Á2> F­t<««¾™söÈ®0ß%ó̧ªÈᵃ›IN$8šmÑ
-®ª?ËÞÝ/|ÏÑôÜ’;õÏ« ¦¼>««ºq%-qg¨Ûo¦ºÊ2hÎåb’}jè4ÛÊNÑ3Ÿ·Ä7lב³97«ëŸ·à |PÊ`×ñêéêâËgmñr™3­"h°âV’3\DZIÇt®›OèŽÄÔ+7ªëž2þgpÊ`éøŒtÔÞ.ºtêàÖà•ÎÖÆš“%…ñ`ÅèÀ¡SZ&¶‹V‡lK8}ùzå篈SÛÁ*ƒµ•~÷òɃŠkSöGx.˜=M}Ò4X ãJt¡A&¸‘•ÓŠ -ñ'³Šn×"Ÿµ Ö™Õçw/ß/+È<·yÝ2‡Yúªrâ¼#¹dhŒä@ n`á¸|}Ô¾ä …ž¼|Ç1hmP:þýíÓÛæGwoågÛ³ií;3=œä܈Fhð‹ ¾tmøÞ¤sWËî=zAÈøGèøøñMSCMIÎÙÃ;C½]q’KçFt°BCQ{Æ\WŸ»Ž¤ç–Þml~ûéÛ¿½ ¦Ž¯_?¯¯¾‘}&aÛ†UÎÖFÄNÎéè` ckçU¶BãÔú¦7¿ è{à]†ÔAl+‹²Nˆð˜o‰vrÎGG«Ð°œï¸åÀÉK×+ñl;X·¾ï t ¥üý+4XfžˆÝìïnoNFy~È1Äñ
- e=s{wÿÍq'ˆG³-È`ÀX;Þ½xt%ùÑÝakÜæÎÐVæ#¬8UC†ÇS|ÂR
-(4ÜÖ„í>š‘ëÞcÖ8E÷•è:ˆÁª±æfNZâö2:&ròÀŠêSc'Èk¡ÐÙž˜–SRC$8È`Ϲd’×U_:¿E‡…¾ŠœcÌåˆ j¸“SÑ·pôˆŽ?u©¸ªîùë_ñ82PIþõCËÓÚŠÂ 'b#ü–Ú™é*Iã1—C½Šœ§ðp«¤cf·Ô/"‡FíSf‚Ó}úd’ã#’ÇDtìÚèíbk¢9YB÷*NÌUŒ>…†[Mc[ïÐ]l¡ Þ*ÉÉ踒š¼b¥¡ªœ˜
-Ê<%7ÿƒÐh 3:^<º[š›–¸mÃJ§Ù¸Wqb®Â«±÷ɪà>s ·wɳBö £ãÛ§7MõÕÅ—NîZ·ŒèUãùz|^…#ŸO K)ê˜Ú-%úÔõªú¦7°iühëøÐò¬¶‚êUh®"wÀžá¼â“Ô¬yoÜ…ú9Üþû¿?€:?|Æ\¢Wá¹Ê@E–8¯êIq0V 婳=·&¤’}ê l?†9æ65½*Ò©©Ž¢¤0>[ïA3WÉZÓç.^— }ª V_?’½êØîÞ‹¬ÔñÙzO‚—árª†³®
-Ùq$ã*š§pŸ‚á¶#ˆèÀ½
-ÍU9© [=gê)÷,ÈÙ#­ë¢ãOg£½ÍS¸OŒ Ç\<W¡09.Â×mäÔãnݲÁá6.>a{Žg^»]÷úTçP½êC Úó3ŽìYéd©¯"+2nTw‹ƒµ…ã÷ B«Fî­{_Á<Õ¨^õîEcÍÍìS¢PÏÐV`näÝ+¡#xÑ®IF8Z5ªšß~þæ©Îaì€ÏÞ.ÌLÚêål…6rÑqÝ<®¢¦Ûq"x w¢"­ø|
-úT§P;àûWhéÈI=¸%`Ù<3ÉîsºUÐÆ[xÔáÔª2:‡:¯z‹‚¼è≽a>‹¬§©É‰òw«8ˆÒ>jžn­œ½Bw'eNž£Óý¹ö}XA~¿,ï졘@s]Ån£4$tÌì—l9˜šSJF8ô©®AùçwÍh#ÏJŽ _ãbc¤6Q¬;ÅA•ZüÔ¦Y/ò Û{âbÚÂÉ]‚ ò¯ŸÕ–ç§'n "‹C¸ÅÁ, E]sÀ˜Cgóʨ-J£‹°¦Ü;Å—RâÂ׸Ú©£âý³ÅÁ( 195´ø­ MÎ*FÓ-±…ƒŒ®Bmä¸8®¦Þìé8³[ÅѺ4‚¶%¦ç—×>{ýñDøO@9>®ª¹Š#Â×Õ¶;ÅѪ4\ׄǥ\*¾ÓÓíÏò×_Œâ¨¸šqx{7‹£miN¿ŠKãy÷•îϱÿÀ^—O²Šã§Æ*Æ.J¥*5^¾ÿþ“à 'Šã!.FrüÜÎAÞ»Æw¥2~ŠÖÉîëúó;q_•†¬ê4kŸM±P݆*Ž7TqyþôBN<µ3‚WPBAÛÌ~y` 5PáÒ
-*|Ÿ£¹¾êZæ±!+æ“Ç#(Ç;r© Ÿ¤9ÝÎ}=oóËâ»ápxÛ}ˆ£Üo[žÜ+É>µ?Â×Õ ¹"|<Ã:kUäNd¸Å‚U¡»_¼~§ñå{(AÞ!ÛTw» ãð¶ÀåóLµ&OèBŽ“{8•á~‘ñgˆñ6¿žA=>Bl€hÈõZh©ß•'÷p^Á “µÍ<‚w=­ª·P=¸CŽÏñ{0zÊqõ‰bîãxÙ öð)³Q†ŸDãmÏal€ ÕE’PŽãÛïãÔ²!6QÃ䷥뷠 ¯xøü-<›ÐS¨!·åÉýÒ+(Ç׸XªÈˆt²3— EÝ™óñ~2œ3C.•ã‰1ˈ}œz\¡£ ÇJFÅÐÚÅ7b?ÚÃïC†s4äR9^œubÚÇ-ô”¤ˆ•ãÇ­ŠlTübxÙXs8£àvd8'`æøƒ2´Gú¹Ù+GG­ŠjT’Šz³Ð²±çDÖšG¯>à‡¨ 4zuXÕÜPuíü‘íAö¦ZÌ£ÃÅ«Q¹®Ý|
-¯~î1‰Ш8µr<H´*?7|;ž8«j78ÈÃt “uÌW„ì:~±˜œ¨ Ã9¹r­
-OUËçÍÐœÔÁY1ߎ’RÖŸíì¾ïÔ•Râøg`œŽ<¾{ãRòžÐU fê*Hþ(8ˆù–‡ODVÍhÎbÿ脳W+êš Qq²UáƒÜÜ36¯q±2˜"-<æÁAÍ·â“4Mçyï8–YTÝø&*ÎÁX
-bƒÓ3.
-ŽŠü´ƒQTp´·q´Š òXä1ća;é88˜ÛF«Ø€/à(ÌÃ28~¼q´Ú6˜±Ç"œ… ´q”27‰v‚ƒ¼ ‹©`Ûà"Ìà 6_ëöªˆ{£Æ‰MÒœaï¼óØ…ëw`Ûà<Ìà¨,¤ŽªðíØïîqàÇ÷6tf.X…©JîÁ¶Áy>ª:¶3ØÞºÇÑææ8âB’JSg;ûDì? ‡TÜqTu¯ärÊÞ«æÏÔ!¿tà;8ÄeT¦Ù¸ùá{Äcmœ†8ªúüöymYÎéý>ΖÄÍñ61Îqr÷Ûväü5|oã+ħ!‚ãË»æºÛWÏ&Dû¹Ù´·ÿadˆ›ÚSG†[ȧûß?° ‚ãëû—äþ×~Œ›8ñ€ÂÌùdˆ“G†œ†íà0 Å8ù¨O[ä®ÒTKgŸðý§s Ĺ[Œ'ï ]9ß\{2ÞÆ[ Ul›¸ëÚ¨ƒiùâÜŒñ7ÏÜÊ9µo“÷Bbo3TQOʨÍY²oâÄ.„8ç¡b¼©®mã›}]¬ô•¥Ú UŒ‡wðHµ,pûÑLØĹŠqb¯¯,H?´Å±Í´ï†*Òc¤Úu<ëÆÝ'-°‰sêÁ‘—Õ×Îa UøùOÖ§Tü⓵Íç¯
-Ý›’Fª7Ÿá81N UŽí^>oºÆÄ6·8¨S* =‹…Þ›öι#·À‡êh¨zrï楻ñ½q-ꤊÍ5àêÏ^´fóÔ¼rê™Oˆ ŽÃxþó~ivJìÆU ðIUëNÌ×К:¥ªo†›Üz¨Š8©ÚNŽ¸­Ï Û ¸ç
-«È'pa¤â<mFÜE³õÛœòwòÌPÍä7w<àÝ/ÜàŒNxÄ%Ï ÛÜþc<½3Qcú¼åÁŒ3Cp¹sÄÅç†[×/™c¤*3žýñO溡eê°"d÷‰KÔÓé0RqÖí¿¢Ì£Û‰§xˆS\ÆÂAž§ó‹Ë³Ög°np ráhaâ¶Y8 â<ß_wêÊ­ÏßÀºÁ%Ú.¦Z­ÏÔËŸ"^þÂñMqêY*q.À\8J²Sö†®šoN>ÝÚuwcM,Ü…u¦~åT\Øj´þ)LÀëÓùÀˆ²¾•‹oäÁ´|Xþ¸¹p¼mzX–Ûîú‡mŒà’R&î5¡U¼²înp Æú‡Ÿ7<@>6Òê~ë`ÄÆÍË!â©OXþ¸¹þ½k®«ÈOc,ãøYÜ66dT¦Ù#×à`„‹0—ñÛWÏŒZûÝÝ?¶cª¥ëcŽœ/ª†ƒîA=Äó¢à/©1À6F0lPß{‡x>Ý=puF¸¹Œ“6Eû¹YLimƒù ¡Ý² Ç.À17aT5TfÚâO¶µAΘç ©»â°Šs‡öl°q[=£À<4„c*nÁ<Ä-ÌHÄO´>Rg<„+¯eæ¸ò÷=ÉÄcŸpLÅ5ºbƒzbd#þÌψCC©¸ËƹÄ-ëÚ·‰žµÀ‹8Â¥ž\¡Säº.õü~$ ¹Fç6¨uü€ú™Ür8Pç&¬§ ÛµAÜÞ”PÄ_.@¨?„u.Ò5JS-Áí î6úЩúØ€ïU:·nïÛ__¢+6àd¤·èŠ 85ì-:?5„õÞ£+6ànSoÑ…»Mp'¶·èôN,<¥Ð‹tò”<ÁÓ«tò<ÝÖ«tåé6xò³·è“ŸðTt¯Ñ…§¢á+zοb
-³éô«0á+”{‘.|…2|õ~oÑ¥¯Þ‡ïlÑKtá;[Àw}é5:ý®/ð‘z‘N¿#|·°^¤Kß- ¾“^/Ñ•ï¤ße²·èÊw™„ïÀÚ[tá;°Âw'î-ºò݉á;w÷]øÎÝð]í{.|W{ø‰½FâC«Ÿ†?ÒŒ[tí§¡0Rñãþ²á'q‰.ý¤ ÆOÑÒbUpnÈ ºöS´Ø†*÷Àmø‘*8©â]ú sÌ¡Ješíbÿ-‡Ò *ᤊté§/’C~ˆ‡yR?Кtõ'“R'Uº³œV“?ìnÆrž.ÿÔ^êÖ¸–™ÃŠˆq.ÑÕŸhMÝŒ%ÏF?(bœÃtñ§½³Å¸Íbø!Ê\‚¸¹bã~iöÉØ0/'bÿ.Ä™1NÜpÂ?`™2‡¡bƒxzçø®O3-âɶ6!ÎŒqòçö†îM¾L=ÅÁÁA˜±Aî~ÄÓ;mnn°Å8q‹ñߎc®Ãs#‡ùiñÒ´»û1bœør3|¨¾%‘ØÿÞÁþÇQÈØÀ÷įœŒÛä…p%Û‹ Æ¡:þ"+¼ÿ¡Žq!88Ç_lG†(6V8˜i·Ô¡:±ÿǸDpÀÝXŽÂŠÂŽcƒ±ÿ‘Á±<hÇÑLÆÁ!´*NA=JEmTl°?ôÙNp¨Ù¶Þ8 88ãê~ sÛøAl°‡þlgŸð}§®àoþ­ŠsP‹‡TI;7üxÛ` ´q˜;®ü}÷ñ¬âšÇðà!çÛwÍõ•Ô!þZvcƒ¹qˆNÔ0ùÍ= æð¹ÂÊúÄŒ ÅÁÏ|Ö–åž9°y‹Õ©¨àÀ_7€ª ­]}©G8óXäîKÉ{BW-˜©«@<.Ò® "8†ó
-J(êÎZ°͸—nÞ}Ò­ŠS°‹œ?²=hù<êÞF»±A=82ŠŸšq·9xÆ Zg ÕÛçh¾M;åçf3MUvüØïîm´žqùÐŒ;ÍÆÍ/*>5jU0Uq
-¹l|xù¨¦8ëÄžÐÕNz4*F«â”TÔ9åﻎ_¼~§ñ%¬=‡Z6šênd$Æ,³›¡ÙñDE±
-:8Ç·9Wx»®é-œ«wò,ýÓë§n妌òG®1QìG'|Whå sÜmmäÓWJïÁÛãmcõõ‹'öl\íÔÅ '‹ã¿ü:ç¸Þ¬hOÊ,"ÎÕáþx7¡J·åùgm p·›Á:0ìÌ#ÇÅ&i˜Ì]º.ú`*1äÂýñîBÝf"ÇÛØM>‹¬º²‡³µ*rWÖ·\èµqω‹×«_ÀM§nÂØüž?¬¸šž¸-h¹½™¶¹‡w–ád«Âûøh òZ3È!7¯¬öl€Ý„µùݼ|2.| oåD;ßÃÙ‹cŸˆŒŠ¡Õ"ï°½ÉYÅw_¼‡âèdi|F¥q»àÜ‘íÁžŽæäxÛµFÅØÇñ«mf¿<0&1=¿Š£›P¥ñêñݛ٧öoöu³5"Ž¨º–áTqC®¬ê4kŸM±)—nÔ<zIèø˜¥Q‡JãèŽ hóÓS’Ó¥ñ–Õªˆûã
-:æAÛ§_eì?G›ÒX»xŽ1* þ.Ž·T«BC.µÚ¸¬ ƒâè&ÌŠ*•ógáÒèÚæÇ^CÑ(©¨û]q@ÿÌ]ƒY&“ðØŽo3µSÃGñ‹áâp%ŠUx!‡ ÿ k8Ú5Ð@E”ÆÔ.Šü¸8¨±ê#~´
-Š£«üÅZÃo^f•†ÀÏ•F›â@É‹vŽê†æwð¼ÂÏ€d‡·ÏОqd;¨º‘ßG` ^È<m¡îs€Ž®@DøŸ_Þ½h¬¹qéä¾_·9ÆÝ) Vq ±jšõ"Ÿ°½'.UÕ7½ýüÇaÊí"¸OýçÛÇ×øð6ýð6´†Ïì^i°‡‚Ž™ý²€-SsJï1o‚ŽÎ!Kãó»æ†êâ¬ä¸ð5®¶Ô®ñ³¥Á,´s¨Z9{…îNÊ,¼]‡ïCw *Â?´<}P–wöPL‡ƒ¹n7KƒU
-Ú¦vKý£œÎ¾Yó¦Ü®BFøç·MõUEOìÝäãbm¤ö“kx›â@ ¹ˆ¬Š¥ÓªG2Ð
-ø´åã7èU]€èShº}õø^iNj–€eöf:øð¶[¥AÇ^Á òšÓç.^—’uM¹dC¯ê2Âñâw»03iw¨—³•!q_£[¥Á(¾ñÒÊSg9zÅJ˽E9ôªÎ`›nofŸ:å¿ÔÎT[A¢Û¥Á8Ê埤ŽV@Ÿ°=Ç3¯ANnä ãÇ°Exy~Æ‘!+,õUdEº]DqüÇXa)E³yîë¢ãq7¾ 6rX::‚ÚÂß ¿Ž¦[¼ø™hÊOäíò-¿vuà;äüxʽ
-rj#‡^ÕTŸÂg·8·z ÅOYz<Ï°î–ÆßئÜÉZd'g]G9ôªŽ¡úÔÇ–gµçíÞè½ÈÚH}¢¸
-ÍU¯ÞçUmù‹:Ÿz÷ÍS¸OmXé4mábü£{áŒâ`läJºhé zUQUÝó×ø¼
-¢ã;þú?b¸ýð
-í}TŸB«ÆdæÞ3Tæ“SŽ*(&áÌ´6¿¥ÎÖA;84þçßß>¾~^‡ö¾”ý‘ë܉>%Â×ý-¼MqPK‡¼&êUÞ¡»Žfäߺ÷ø%DÇ÷0‡ÛæÆš›WRbð<…ûTWV:ˆ¥c¼”’Ž™ÝR¿ˆØ
-É1¶ŽÖ0C ··ò3ŽîÚèíbËèS=Z5Øt0{•Š¾…£G@tü©KÅDtÀ I+þ"7 |XX[QxáDl„ßR;3bžÂ}ª+_®Ñls•‘µóªí‰hÌ%¢¶vˆÿ÷W"4Š/Šßà1ßB_…CóKÇ?ˆ¹JXJA{Æ\·5a»Ù¢t0 §¾‘¡‘“–¸=d•³µ‘¹÷q"Â:p¯Â; Œ²ž¹½»ÿæ8ž[è  oDhì[ã6w†¶‚”0±÷q¦O6È^Å+ >¹ó=·8yézeÝ3f’ƒ2ÁÿüòþÕ“…™'b7û£áVOY†8ŸâXŸ¢tà^5¹Æ(:6l;”zåÆú¦7h $vòÁ®ƒJð/Zž=¬,Ê:y …†¥*>GçÈÞ×JÇßñysQt¸úlÜu$=·ô.‘äx°ì:HD‚×WßÈ>“°mBc9p>ÕÖcÌ•¢g>oéÚð½Iç®–Ý#Ï}’S2¾}|ÓÔPS’söðÎPo×9Óµ¥ðpûë?9Ù§˜:Ñaá¸|}Ô¾d"Éa°"d"hœzt%ø±=›Ö.±3#Cƒn9kƒ=:Ô¬œVm‰?™Ut»çr¤ bœz|¿¬ óDÜæuËfé«Êq#4(ÌèPÐ2±]´:d[™Ëh'GƒÕàžs± r¶}‰Æ©kSöGx.˜=M}Òn„eƒŠŽq"ÒJ:¦sÝ|Bw$¦]¹Q]ÿüõ žsÿb,ï_=­½]téÔÁ­Á+­5'K
-s%4Øt Á+ F$ùß°]GÎæܼSO¬­† ´h<}XU|ùÌ¡í!^.8Á¥EÆq'4˜:PtŒ#ˆ’\–ƒ»_øžcy¥5 híÀ- F,-Ïêªn\IKÜêãö›©.Npêa6®È £ƒ‘䆖ó—¯Û{ü\þ-´v¼ùômPê d[_]õÍœ³Gv…ù.™g>UENL€³ÇSßÛ ¬Fñ KNÖ0²ZàµïD&±v¼”:Ød<¯¿s37ýèžp?w‡YhŸ 8†8žâJh°ë@IŽ+cë…+£÷§\(,¿ÿxPêøNƱ½þË- Õ‰œk ΦãŸ(ÉGó‹H+jO·u^¼5þäÅk¿|7èt´–Q’›qlïæuógiL–$vp.†KÇ¿ðN.*£¬3cŽËê 1O]*º—òA¦ƒÚ32ò2’b7¯÷X`e¬© 5SÜLp¦"ÉGŽÁs®®é\Wïß·'œ&u°šÕ`ðÑŽŒÈõžNÖÆZh¶åG2¸™àLÔ`…æ\9=s»Åh <tæ2Ò³ƒ±w |Œ œ%#.2`ÅBk´h ÙSÜ Ö:Æ
-M@kÇÌyKÖlÜ™ÈÐ×Àÿ :gS çH6&ÚJ2¢¼#¹<NµÖç\¤c’šÁ,û¥¾aHÇ¥¢ŠûšÑø'>BèáA¡“K£2¢°ŒéH^4H½aƒmíÀ[ …Öqèô¥kå÷›^ø2t0d|Fx;2xzQ›Žñ’”Ž;Ne–Ý%tüñŸÿØ£us >«C{•6Óu(¿ö¢ ÖÚ1n<ZÊ ¡Ûž¼PpënÃó–÷\Ç_Œ;}Ÿß½zZWeĶ‘Ñ ³mkxí –r¬Ã~‰ÏïÛâS2¯–ÖÔ?kyÿù¼xÖñéÝ˧«ðN¯ Æ`EéP7œ5o±wÈÖÉçóKîÔ={õîóÀ]<kÆ×o_<©­º‘“Ž6ðõ´Ê`ꉛ•¼ºÁL;7¯à-ûŽŸË½Yýð)^ˉIwà••ß~ùø¦ùñƒÛÅWÎE2<Ùœmt¨é›ÿæ²*(*.)=çFeíbñ€áÁˆ ´f¼nzt¿¢èrÚ‘=ë<œ¬Mh•ÑªYILRj:gÑʀȽÇÎ^¹~ûZ<X£ÕÀñÁŒ 4Ù>o¸[víÒ™Ä]áþ h—Á¦ƒOxÂDݶÎ+ÖoÞs$õrQZ<P–“á1pʃڿÿüúéÝ«gõ5·
-.ž:´3ÌoÙ|+cÚe°tŒâš 7Egº“Ǻð]‰§ñâQ²üÓ€êV1V>"¿V—äg¦Üêëî8ÛXK‘vLøD\VYÛÄzÁr¿° '/\-½óðÉ‹7Q·(³£KýñùÃëæG*oädœØ¿5Äg‰ƒ¥‘¦¢´(í2(Ä™• ˜¬’¶ñlGwßÐmñÉçroT¢ðxýw«0[1
-ãO´e´<o¼WQ”}öX\t°—Û¼Y†
-}Bµ"<cÄd5,–ø„lÙ—”ž}½â^ÃsönÕŸ}P³Ôþøòñí˧u5·
-³ÎÞ³9`¥Ëoæê“¥DøyGÒ/ƒÔñR‡¨´‚ÚÝVEî=’z©ðÖº§/ðlÕßÃœ* b–zÝü¸¶êf~æÉ„›Öy:Ï1ª*/9žŸ·7Om;×1l$/¿ZËõÍç.Z±>|ס“™y7Q·B³Úûuy‰AÍR¸K£È8°-tí²ÖÓuU&JŒ7ºÈÀ:ð‰î¿°´ªê™Ú:-_»qÛãéWŠÊÑlõòíGVyô?¬ÂøòáÍ ¢K]J;½Á{‰Ãlcme¹ B|Äm×>!ãoÌ»h-–˜8EÇÄÊq©Ï†èØ#g²
-J«k7¿F«Yý®]ýERDa´4=z€ºÔ…S‰»7¬rµ›5MM¶‚cGõ%ì{ št•´¦YÌs]±+!å|n1
-sFyô¿vÅ–¸0êï–]É8¿=ÌßÃyŽ™¾údb˜Þ§d°íhÒ•VP70Ÿ³ÐÃ/4fRó‡¸<ðpÕÏÚáâÿC.þ £¶ª¤ ë êR!kÜç[O×S$)ÂdüúKŸ’ÁZ<Ðh%"…ÂcºµãRïàÈ=‰§2s‹oãò@ÃÕ·3ÚUðñ3½…Qq=ç\J®ˆ€Unó,pd£&Û>%ƒ¹x ,7^BNYÛÈÂÎeź°íŽŸ½\ˆÒ Wh÷è?íŠáŸ×~|‹F©¸0ÒŽí‹ õópžkn ¡(#ÞÇò› Æh…ÃCLFA]ßÌÖÉ}͆(²<*îÖ£Ýãýg²]õ}ÌÀ šTó㺚r\‡vGy/q´ž¡§*/%*Ð×ò›VxˆJÉ«èšXÚ»®\¿iû¤³—
-J«4>õöÛtÕw}ALRD“j¸wûæÕ¬TTýW¸ØY .…¶ ~Þ>,˜áÁË/<ANIÓÐ|ŽÓ2¢<NžÏ¹^^óð1jWxºêÛ>ZjRj«o]ËÎHNØìã¾ÀÖL_CèR}22X0÷òÑ|h¶šŒÂ|¶½ÛÊuaÛöKÍÊ¿yû^ýÓæ×ï>õiL80Ð$E4©ÜÌÓGâ¶â˜gi¬£Ò×»ÅÆ;Ïþñ•µˆòð ŽÜœ‘]XZu¿áÙ 4íöYì.>½{Ýü´žhRiÇãwDz/E…a ©„f©>Þ¥PÝ
-—‡*5\®+ü7n;r*3§¨¬º¶ñÙË>êã¯V.Þ¿~ñ¬áAUiavFÊ¡=Ñ!k=ÙYë¢øìó]ŠÙ­È0!ËÃvÁRïÀˆ’R/æ—ß©}ô¾ „TÁrñòYcmuYQnR1aëV-v´65ÐP’CñÍZ¿û¸ f·úu8Z=„ÄQz áÊn‘‡oHÔîƒ'Îfå—×`o˜>úB0ÊÍQ jï”ç]LM:°#"ÈÇ}áÜYF(1¤Å¨øîó]ŠÙ­å†+SkG·•þ¡[öJN¿tõFEÍCV}ü÷i/f‹B3í7†‹šòâü¬³'îŽ
-Yëé2oöô©êŠ²Paàøî]ŠA«ò“–WÑ1š9Çi©W@XLlbJkhÿø/³@èò³Eý÷ßÈÅÇw(/°‹W/¥'Ú»%Ô5)3C-åIR¢‚l…ÑOdü½<Ðp5AVQ]µ+çe>áÛâ§d\Æ>P¿zñíßþÄç%Œé]!l*P‹úú™pÑH¹HIŒ ðZê„›”êdqa”Daô—.Å€Uh÷•œ¨¬©?ÃjÞ"5AØÇÉ T(? Ÿ¿â†Å*Þò3-ȲøôámKóÓF”„‹ÃqÛÂ}–9ÛYâ&%'!"0¶?¡ƒ®Ðî!,ŽÚ•¶¡™µƒ‹Çšàˆí¸>°;µ O›[Þ~À «—…üõ]Y¼ó²éIýýê²â|ÊEÐEöV¦š¸I ññâQª?%;CX»ÇXÔ®Tu¦™Û8¸zúGà~•~)¿¸¬ú~ý“¦—d ŽÕ;B( TY ¸¨»Wuëz^Ó…‹ƒµ™¡6š¤p“5¢Ÿc÷ÎÃË'ˆ¦+5]#sGìƒÈô¬¼ë·ªî¡@onyó¾µnù«­
-"-Þ¼jzÒP[s»äZîÅ´äÄXìÂÓÕÁÆ|šŽªš¤È&…wŒ~Y$ÌòÁƒ¦+QI9Eäc¦-öò#619íbε’Š;?Ñòöý'BÈZ á v,ïß´4£²@-êFAvfêñ„½1›° Gs#]5‚Œ&Õ ƒ„(”æÃÐt…}LTT§|¬ Ú³7áø™óÙW‹Ëªî>lÄ‹]»‘;ù‹Í*
-–
-Ô¡ž?ixPs»´(ïRÆ©cñ»·„úx º˜‰\(ÊIŠ
-ãåéçMŠÉF»6M»Bb””>a[vÇ=•ž•{ífyõ½‡O !¨e‘¡Î0ÂTÒ'Ì?ÛÚÄ·/„
-ÜïV•\¹–|xÿΨÐõÞË]ˆºPœˆ]ÑÏ›‹!Œåc8ö!Lù@yî²Ü{}hÔŽ}‰'RÏ_Î/*©¨¾_‡…´`!_¾ýÁ2B(a9éŠö&þ4Ñ° ”Ú_Ql¿{ƒ«¢ñá½êò›×r³2N%Ü»-"ĵ»³½5åBL…÷ÈaŒIj
-rT]P.D“bÑÆÊ9Umƒs]–­ö ‹ÞG
-AR^u·¶þÑÓç/‘w>RJH'H
-¶BjaŠaA½ÿ HáñùÓÒÄ³Ç ïß©,»y-ïræÙ“ÇÆnÚ°fÅ’…ólfšLÕT™,‹ò¢µ‹%ão­}à<•”¬¢9ÕØÜÚÎÉÍÃÛæ-¤ÌËy×n–UÞ¹ÿ°i~Ù‚”¼ÇJ°,Y¡´PfØ ßIHÀ ñçØ!âÝ¢&°‰T× r/OK9–·#:,Øoõ2ǹ–¦Ót5”åe$ˆìÀ.0­} yWBF^Y]Çp†…­ýÂ%žÞë(!)iç/å\/)¯¼s¯¶®ñ1RòâUËä× *”¯ß-È a†ü®? ß¾¢røüé#òðöM .‰'H¥Å…yÙ2Î$9»#zS°¿—‡›“õL}-UʼnÒâã¾ L‚ãÅ¥&*¨hê™ZÎqp^â郄Dïˆ=x$ùLÆ…ì¼Âë%e·«kî×Ö5<zò 9yùê5’‚­|ü„½ 1È è×_°ƒO±¤áõ«—/šŸcuîÝ©Ä&ò¯\<—z2)ñÀžíQaÁþÞž‹ÚÛZÌ0ÔUWF-JLX€Í´Þ†å탼|¸@”Ô´ôÍfB¼ýƒÃ¢¶ï9˜”’šqárîÕkÅ%eUwîb'‘”&l¥å5ò‚żÿúR€¼nÁš†Ç uµ÷ïÞ©¬¸uózA^öÅsi'ŽÝ¹ushŸ—Çbg‡9–¦Fzš*¨,&ˆñw½ÁàÃôÏKF%
-DV^YM›)ÄË/(tóÖ±ñ‰Ç’OŸ=ñrN~áõ¥e•Õ5÷î×>DV=~òyAb^¼xÉýª¹ 9xúäñ#dáaíý{5Õ•e¥7ŠPI\ºjâðÁ¸]1‘aÖû®Zî¶Ð~Ž¥™ñT-5%”¸,ˆ5h\`X>pÃâåã™ %7YYb¿Ðmùª5ë7„Enݹw‘¤”Óiç2³²sò ŠŠo–Ü*¯¨¬ºSs÷þ䥮¾ÑH‚߬¯CÜ¿[s§ª²¢üVÉÍ⢂üœì¬ÌŒ´SÉÇãc‘‰M!k½<—º,˜g‹ªB_[MY^VJ\DpQD‹4.0¤ªañŒƒ;Öi†KÛy \–z®ö]½ )9˜xô8r’~þ¥으ü‚kEÅ7JJo••WÜ®¬¬bQYy»¢¼ìViÉâ¢kùy9Ù—.œOO;rühbü¾=;¶nÛ€L¬pwur˜k5kÆ4TÊ“å¤p‡Bi1r8[‹,.0CØ  ‡…à
-QÓš:mÆL«¹öN.K=Vùølصuû®½ûâ9v"åÔ™´ôs™³.gçäæå_-((dQPp5?/7'ûrÖÅÌséigN¥œ8väPü¾½»¶oÜô{Ð:_/Ow×…¿Y[˜èjª*Éc¸C β`Á*ÖBä•T5u ŒMgYÍ™7ßÙÍÝs•ÏÚuA!Ã#·Äìؽ7îÀÁC‡;ž|òÔéÔ´³ééˆsçÎáÿ¤§ŸMK=}êdòñcG:x nïî1["Ã7†®óõ^¹|‰‹aÂÄPOK}ŠÂ$YIq!R•ƒÔ†ð
-¤•qIÙI
-SÔ´‘3-m~³Ÿïìºd™ç*o_ÿ€ ÐM‘Ñ[·íص'6nÿøƒ ‡J$Ao%Œ?°?.vϮ۶FGFl
-
-ð÷õ^å¹l‰ËBÇys¬faÚê*ŠòrÒb¸A}§bpºÀ ù^
-õñbÒròŠSÔ4u¦š˜Î´´žcç0¡Ëâ¥Ë=Wyùøú­ ÚðûÆMá‘QQÑ[XDGEEF„oÚøû† Àõ~¾>^«<—/]ì²p¾ƒ­•…ùt#]-Ò„¤¸¨° ÿX^¦
-œƒ¶,X|/„½¨D$¤Q(©¨#%FÓMgZXÙÌERœœ]Ü–¸/÷\¹ÊË{ïÚµ~þˆuëÖáÿø­]ë»ÆÛkÕJÏåîKÜ\œ†¹6V3MMŒ ô´5T•Y&øÆ Øn£b°» h+dä(\"ȈØI¹I“•¦¨ªkéèL3žnj>ËÒÊfÎ\;{ÇùN ¹¸º¹¹-&Ao¹º,r^è4ßÑÞnî+ËYæ¦Ó§èéh©«NQœ<QVJB\›Ë;
-Å0PÑ>­… C%2Šw ß8!B‰4v¢¨¬¢¦¡¥­;UßÐÈxú 3óY–³­¬­mXX[[Ͷ´˜en6cº±‘¡þT]m- 5eÅÉ“ädÑñBdMðŒ$L€ŠÃ‚¦,ʪB‰ ðxQ± RÒ²'MVPTž¢ª¦®‰¼èMÕ×700da` ¯?U9ÐTWS¢¬¨0yÒDYi)‰ b„ˆqcÇŒfÔaTtK*Â¥d,v‚êDTL|‚¤”´Œ¬ÜDyäEIIYy
-;ÊÊJJÈüD9Yi)É âb¢"ÂB‚ãø#Û1*:`»R ®žQ”~le¼ˆ(ö"!))%%%ÍýJRR;-ðc ¼È)Lt–†Ò ’Â3j4²‚µ /‚‚BaâmAAä
-ÖB ƒÀøzï(Ô“°l¨BÄ?`¢» ÂÒ
-ª¤yÁfZß5 K@CX <°D€‰ž0„Í !—
-à Á¯ ï  gü%t2„!CZY!µPfZÁø¿·²
-
-
-
-
-
- 6løðá#0èÓ6l(º*¿à«Ñö¥A¼(Е:løHžQ£Gó"Æ Pðç6zô(ž‘Ç ÅW£íK_
-|%FðŒÃ7Ž_@P!< ÁŸ™ 
-SÔµtôô (úz:ZêSä¤Ä„øÐÅøêCØ_CGŒæ—QPÕÒ7š1Ób¶•µÍ
-2âÂ|£G e{a ‚_<c…Äe•4õ§[ØØ9:-rqus[<
-ÞºÿøÙ¬ÜÂë7JJKo 8JKKn\/ÌÍ:{|ÿÖàU ­ UeEøxZ]‹a£Æ‰MT7¶uõÙ¸3ñÌżë¥å•UÕw ÕU•å¥×ó.žIܹÑÇÕÖX}¢Ø¸QÃZ_ ~±I&s¯ ßsìlvaIEUÍÝ{÷ïß0 @ŸÐ½»5U%…Ùgí _»x®‰Æ$1~¸?¾P#Ð;¿ï©0k}?kÁ ΘÁa7cíf°³³ïìp–Ã:Ë3>Öá/œý²î {ßÝ {Epñï­ï§Â½eö‹Ï´yiÀ³(Ì—<£ÔêjÀ³kß]AAG×
-]Åø»—áÿÃë]…®í½•«ÜµÊü¿í'ÂÍÿÚ^3ñ§¹•õU¥••5U®’ŠÊšÒòÚŠz×µøqMeyiUm½ÇOÛñÓï‹Ý3¹Ý›\kö¶å涹Ğ ¬ÝT?й÷ºÁ£ãƒû.<1 îtØ…2ë#Xÿwy]MUuUMuy­«lÓȘ«`?¾bppáäÃT{©`ëk÷ïŶ Œ;042°uøº^x« /ÜÐM#×íÙ78`<…›ÖlÙ3Z8u_6\?¸ï˜ñ@þg¤wÁ3V]Ymf ?YUZ[m>5åÆ“cN<¡ƒ âfË ¶ob¦àùÂ}6†—­Ú7ؽ±Åµ½#|çÄs[«(­)¯©p•”×——VV××»®rUÔV¹*WUÏùÁ™îküÑäý'ÿ»ÎU‰»#cãoVáÿp§ðÜ\s»Œ½kpUTÕ¯ª©ß¾)D÷G䉈ˆ¬t/‰üM!š ö…H +C¤€}!RÃÊ)`kˆf‹•!RÀÊ)`kˆf‹•!RÀÖÍ[C4[l Ñl±2D
-Ø¢Ùbkˆf‹­!š-¶†h¶Ø¢Ùbkˆf‹­!š-¶†h¶Ø¢Ùbkˆf‹­!š-¶†h¶Ø¢Ùbkˆf‹­!š-¶†h¶þçBº7‡h¡ûŸ)toÑBÇÖÍ[C4[S[ÃâIx­ [C$1]kX"¯$•aqˆ¦š±2l ‘…/­aqˆÜ|¬ »Ct ¿<h–”+ÃîÐEhî}aƒèb`GSè.šÃ…î˜ÉitÏhÛ阜I÷¼¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖K÷Ô¶‘îhɱtOm鎖œL÷춅îPÉátOp[è•N÷·…îPÉátOp[è•œO÷÷3ÝqÒEA÷4÷3ÝqÒÅB÷L÷ÝAÒÅE÷|÷ÝÒEG÷”÷ÝÒÅH÷¬ŸÝáÑEM÷ôŸ5Ýt÷`tGEtÝ…˜îxˆf »"tÇ@DDD*t¯ hñYrÑñ!¥i*a,3,¿(˜»:‹×@Œ4–/_±bE€)ÐÑÄ>bg`ŒP¼L#È!(88$$$Ôñ°“ÁÁAÈf…‘‰uš˜y‡„…¯Œˆˆ„(G3ö0"bexXHp‘‰ušy„†GDÅÄÆÅ'$B’ƒû—jdbDráY¶"yDÆÄ'&§¦»2³²²² ;˜éJOMNŒ‰D&+–yN’‰@Â"¢ã“Ó2sò ‹JJKË®´´¤¨0?'3-9>:"Ì Y€@bÓ²òŠÊ*kêW¯^ãh«W76ÔÕT–åe¥%Æ ’
-²3RS’ý¶E3}~ÄîÏEÇÄ&¤dT¬¾|çðé;ÿî‘ï<÷“_üz†D~ý‹Ÿ<÷GþîÎÓÃ;/_]Q™’í¿Mšé3F6- S/.%»¤~cïÐÉÛÿæá'ÿýǯÿú=i"ïýúõÿû“ÿÍí'‡z7Ö—d§ÄáÙ óçFÉ?‡69Mlú¬¢QǨ„ŒÂêõ=WŸ¸í‹_ÿW3‘?Hùƒ™È¿~ý‹·¸ºg}uaFB”1»ý·I3~Vñ[?ÏŠ:†FĦæU®íÞý­÷~íÛÏþèµ·gHäí×~ôì·¿vï­×ïï^[™—‹žM¿šáó¬—Ø÷™çeæš8&%·¼¹kßñßû>'ò÷~üø¾®æòÜó¼ÃÜ~¿òý“àþe¬‰Ã¢“sV5mÝ7þñ/Ì"‘/|||ßÖ¦U9ÉÑabQé²4l²Ä8q
- JÊF"{gÈ^$’hœwèØ~,1®ÀˆD:÷Žß2«DnßÛ9™ˆ÷ãߢ4™HÙ¥DÖ”M&¢{Gü†‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆ±b"VLÄŠ‰X1+&bÅD¬˜ˆÕ”D~àS"?¸xyˆ·HþŸ;‘'.ŠDŽÝò…‡|Nä¡/Ürì¢JäψĚ ~òß¾È9ÿÐϼôó·~ûû?þù¿H¬þû¿ÿüÇßÿö­Ÿ¿ôÌwt"‰F"{ŽÝ|þÁo=ýâ«o¾ûþú32™âÏþÓï¿ûæ«/>ý­Ïß|l‘H¢ó LÌ*[ݱ{ìÜçxüû/¼ò«wÞûýü“7üà÷ï½ó«W^øþã|îÜØîŽÕeY‰‘¡NL¤tõ–£g?ûÕǾ÷×ñÖoÞ{ÿ÷ðæ÷ï¿÷›·~ñò¿÷ØW?{öèÀ–Õ¥ŽL$ÄH¤±½ô̧¿ò§žûÑ«¿|ëwûž7¿}÷·~ùêž{ê_ùô™ÑþöF#‘&’YÒ°yçÈwé¿ýôþäÕ_üêÍ·Þöæ­7õ‹WòŸOû¿t÷#;77”d&8.‘e!ñ®¢º¶í×Üpû½>öÔþóG/¿òêÏ_óæ篾òòþóO=öཷßpÍö¶º"W|DHÀ2‡%—^X³¾gpüÜ=÷=üøSO?÷Ã^|éGÞ¼ôâ ?|îé§ø¾{Îö¬¯)L‹vZ"+‚WƦæW6o=}Çù¯<üØ·ŸúÞ÷Ÿ~æð¬'ãÏ<ýýï=õíÇþÊù;Nlm®ÌO]¼ÂQ‰,Y¶"(<&9§¬qsßÁñÞyþ¾ÿñ=þ­'àÛ2~ô­ÇûÆ?>xßù;?:~°oscYNrLxЊeKœ•H`XTbfQͺ®þá½ãž{¿ô•¯>ðàC=ôVøÙƒ|õ+_º÷ž;>zb¸¿k]MQfbTX ÃÁ¢‡Ö´¼òƶžÝÃã§ÏÝ~÷§?û¹óç¿àÍùóŸûì§ï¾ýÜéñáÝ=måyi8°KVç$b¾ü£6YEÕM›{úŽŽßp㙳çn¾Å»›Ï=sã ã£û{67Ue¡4ÁŽzñ‡Ö ÐÈø´œ’š¦¶®¾ÁkFFŽƒq+ã‡cGGG®èëjkª)ÉI‹ rÔÕ<“$:1=¯¤ºqÝæ­=ÛwöìÞ³×»=»úwnïÙºy]cuI^zb´1Eœt1$Æ$‰ˆIJÏ)*¯il^߶¹}KGçt:¶´on[ßÜXS^”“ž!¦ˆ“1jƒI“˜–•WTVYS×иzõšé¬^ÝØPWSYV”—•–fLG•fb’"’èøä´Ìœü¢’ÒҲ镖–æçd¦%ÇG#@ÇMw$¡á‘1ñ‰É©é®Ì¬¬¬ìéàÏ2]é©É‰ñ1‘á¡Ž Äè " B&Q1±qñ ‰äñG ñq±1QÈ#(
-û2ÿG Ýݲ‹Z §NQK¹,mr7ȳ8îY'æ[DDD¤)***ÚcŠõgŠ7%˜=$™’M)¦TSš)Ý”ar™2MY“²'‰ÿ‰?Â}pgü] ˆñ±Ø<l6vÄR^ß6>Î|÷OD† A!ì>¶»&öÅs²=¸wAì…Ø!ˆ@D8"(šÐ3R²\„ïùtˆ'H<YØ6ñôáyô<€¸î…çñÁr@˜šÃt}gg½–TTR̺¹wÐÝ>Q:wÝDËD¿D³D§ÄTÄôËÉÉÉÍÍÍËËËÏÏ/(((,,,***...)))---+++//¯¨¨¨¬¬¬ªªª®®®­­­««khhh4­Y³¦©©©¹¹¹¥¥eݺu6lhmmm3mžþ÷Á=ñWðw1†Åca°mØfìvÓ³¹¾¯||ý]´a"=…L°ûØ_ì#¶
-Û†-tï‘dwÜ{„ðWþ.’!+$†1±›ÈI"O<
-²Å^#g¤ÌñÐÈ!à¹À3‚çEÜ7q4Çqp_TÞÝt¿t\%ê|1w»ž"1[ »} ³zbZ¶··wtttvvvuuuww÷ôôlÛ¶­···¯¯oÇŽ»víêïïØk<pàÀÁƒ:4<<<â?Áá>¸?þ:FÃàx <.¶›„mÖcï
-¯¶îs“é^juwË.Óž L©ð¼ÏŠ²ÏóY­¸-æ›8{ÅCàqÅñ\¬NñsÌXÑYÌ^ŒŒ?ÅŽx6nÆ«a¾\ùÁ ÜÉ`g±UxD±F°©Øk÷Vá†DœäbãÝǽù<“uWç³óÀÇÎúѼª,æ’˜EžGo7ü„9‰àåÅë f2&§û¥vÆGññš­x‘Åc‰£„X9àAQ"ü›754qF0ymÙ÷ãÃÔ•ù"º2,§»[v™ç;LT”KA±2Äkš%Îj}i«/ﶈ¿(^dñúˆ×V,YñR‹ÂâOçæ/^º»eݹ.>xÅA›Ä›)Xmbq(.ƒXÎ$ï ÏØ\q­%ÅÙ¢8UÇB?dagEw·ì¢;×E õç‰øOÏ+Q3¶ÕÇO8ˆ…1N*
-
-*MX„ãX¡{¿ÝÝ"Í<'NýÄÏκ_"§k«¸ææËg“Dg±ðÆɬ¸lq1¼‹AäGžÅKž¸X––æ~£vê…qÉ'$åÍÅEEE‰Î–””à?ñ¾‚ÍŠ¥³â-Ô*&&ÆÝÙÛjùHðtÍÅÅÚ8//Ëcü'îÌ×Y¢Y±¬Ñ>¼âÕV|Y,kC<Þzöú~7ysEgããã³²²rrrpâ̵1ÑlyvVÔÕÃK-þ-sVÞV÷ç äÍŠ܃ã”Y|·=¯Aéƒhp÷eéÒ¥“ŸÞÇ«¡X»;+i«ø —¸!o®ø˜·xËoü¬½Ý/µºÃ ZDYÜŸ;«j+^aÝïïX>ii«'IsݯÔ⣉îZ‰h±³D>²¼÷‡æŠýä~[V¼ÈÊÛêù¹\Isݯ¶b@Œ/þÑ
-×ÆD¾»d
-ÏøZ:ëµ­â_=X>Q?µ¹á¿C¼í‹‡°|JwD‹ÀÔκ?™/:ëþ·ÆÓµUü%¯ÿÆÒ\÷…hŒ,>®oyhÝa-S;ëybbþîq*ÿ…’Åæþ‡lî7|ñ
-‹ÃÂÔëƒh˜ÚYñRëÙYÏì/ù÷¿3þûSwgñ"ëõtëƒhw§ŸâEvÆ­ïKsÅ{¾îÎN}\Ýa-ÓuµÿGtvÆß­áËo/dg‰æNÞYñÉ%4nÆß„3ãokÁ ÊýYÙY"5^;+~áŒøµâEvÆß[5ãïY îW‰kPì,‘IgÅ(Íó7LJ~Ëœ¼¹âÇyv–× ˆL-¬û»‰/€5íŒmõñwb(qJÈΩñÚYÏ‹Æba<ãopñ·’b åî¬ø-‹ì,ÑlÉ;5-7ãï[žñ÷ ‹ß«,.C‰O³³D
-¦ë¬¸hiþnp4nÆߎ>ãoÇ ±æ—yá”–%Ræµ³âq
-EóüZ“é¾Ë`ÆßáAÜ öú­î0ˆ©oΊ7zÜß29ã7øòíJ\†
- ag‰Ôxí¬û¢qtt´XÏø=A3~oÁPâ2”x»‡%R ïlLLŒø=03~«×Œßx…A0”¸ êõcºÃ Z¦vV¼9+.ã T¼ÈÎø|3~Wçwʳ³DʼvV¼Ñ#.@%N~›¤ä3Å÷´Ê›‹AÜ «`g‰fkºÎŠ7zâââ°¦Eã$m_„-nHš‹A0”¸ .>VÁÎÍ–×Ί7zP®øøx¬iÅW6O×VñÍõ↤¹âK«Åe(÷[´ì,ÑlI:‹r%$$`M‹ÆIÚ
-¹¹¹â†¤¹C‰ËPî_ºÈÎÍ–×΋‹Æ8Åš“´Ä7MË›‹A0”8¥¿Ù˜%R0µ³¨’û@Äwä¡q’¶B~~¾¸!i.ÁP¢³xv–HÁtoô$%%aM‹ÆIÚ
-↤¹C‰ËP‘‘‘ì,‘¯oô \ÉÉÉXÓŠ¯±›®­PXX(nHš‹A0”¸ %Þ¢]±b;K4[’΢\â;ßÑ8I[¡¨¨HÜ4ƒ`(q***Š%Rãµ³â ÙQ®ÔÔT¬iÑ8I[¡¸¸XÜ4ƒ`(q*::ÁÎ)ðÚYñF¸
-ååå↤¹C‰ËPâ £ù]–D
-¼v6""µB¹233±¦Eã$m…ŠŠ
-qCÒ\ ‚¡Äe¨øøx<;K¤`jgQ%ñFÊ•••…5-'i+TVVŠ’æb %.C%$$à!ØY"ÓuµB¹²³³±¦Eã$m…ªª*qCÒ\ ‚¡Äe¨ÄÄDv–H¥°(ª%.çää`M‹ÆIÚ
-ÕÕÕ↤¹C‰ËPÈÎÍ–×Ί7gQ®ÜÜ\¬iÑ8I[¡¶¶VÜ4ƒ`(q*)) ÁÎ)ðÚÙ˜˜Ô
-åÊËËÚ“´êêêÄ Is1†—¡’““ñì,‘‚é:‹Z¡\ùùùXÓ¢q’¶BCCƒ¸!i.ÁPâ2TJJJll,;K¤`jgQ%
-µB¹
-
-
-°¦Eã$m…ÆÆFqCÒ\ ‚¡Äe¨ÔÔT<DPP;K4[ÓuµB¹
- ±¦Eã$mõ$i.ÁPâ2TZZ;K¤fºÎ¢V(WQQÖ´hœ¼­kÖ¬™±¹C‰ËPéééqqqè¬å«t‡A´L×YÔ
-å*..Æš“´šššÄ Is1†—¡222ØY"5S;‹*¡P¨ÊURR‚5-'i+477‹’æb %.C¹\®øøøàà`v–h¶¦ë,j…r•––bM‹ÆIÚ
----↤¹C‰ËP™™™ì,‘KgQ"t…B­P®²²2¬iݯ°^Û
-ëÖ­7$ÍÅ J\†ÊÊÊJHH ag‰fkºÎ¢V(Wyy9Ö´ò¶Â† Ä ys1”¸ •ÍΩ™ÚY,YQ(Ô
-媨¨ cI[¡µµUÜ4ƒ`(q*'''11µ|Í–î0ˆé:‹Z¡\•••XÓ¢q’¶B[[›¸!i.ÁPâ2Tnn.;K¤fjgQ%
-µB¹ªªªÄ‹¬¤­°yófqCÒ\ ‚¡Äe¨¼¼<<Dhh(;K4[ÓuµB¹ª««Q44NÒVO’æb %.Cåçç³³Dj,E‰DgQ+”«¶¶kZ4NÞÖööö›‹A0”¸ UPP””ÄÎ)˜®³¨ÊUWW‡5-'i+tttˆ’æb %.C²³Dj¦vUB¡P+”«¡¡kZ4NÒVèìì7$ÍÅ J\†***JNN ³|Í–î0ˆé:‹Z¡\8Åš“´ºººÄ Is1†—¡Š‹‹ÙY"5^;‹B¡Vâ¢1Ö´îWX¯m…îînqCÒ\ ‚¡Äe¨’’’”””ððpv–h¶$E¹Ö¬Yƒ5­¼­ÐÓÓ#nÈ›‹¡Äe(v–H™¥³âKñP(Ô
-åjjj cI[aÛ¶m↤¹C‰ËP¥¥¥©©©ì,‘IgQ®ææf¬iÑ8I[¡··WÜ4ƒ`(qŠ%R6µ³¨
-…Z¡\---âEVÒVèëë7$ÍÅ J\†*++KKK[¹r¥åë,u‡A´H:+.@¡hhœ¤­°cÇqCÒ\ ‚¡Äe(v–H™×΢P¨ʵaìiÑ8I[a×®]↤¹C‰ËPåååéé颳ž¿ÆMwD‹€¤³(Wkk+Ö´hœ¤­Ðßß/nHš‹A0”¸ ÅÎ)³tV|ù,
-…Z¡\mmmXÓ¢q’¶ÂÀÀ€¸!i.ÁPâ2TEEEFFÆÔ¯³ÔÑ" é,Ê…3P¬iÑ8I[aï޽↤¹C‰ËPì,‘2Ï¢>¢³(j%.cM‹ÆIÚêIÒ\ ‚¡Äe¨ÊÊJ—Ë5õë,u‡A´H:‹rµ··cM‹ÆÉÛ:888cs1†—¡ØY"e–΢DX²¢P¨ÊÕÑÑ5-'i+8p@Ü4ƒ`(qªªª*33“%R é,ÊÕÙÙ‰5-'i+<xPÜ4ƒ`(qŠ%R6µ³¨
-…Z¡\]]]XÓ¢q’¶Â¡C‡Ä Is1†—¡ª««³²²¦~Õ»î0ˆyg»»»±¦u¿Âzm+ ‹’æb %.C±³Dʼv…B­P®žž¬iåm…‘‘qCÞ\ …1lmmmvvvtt4:ëùkÉu‡A´È;»mÛ6±0–´FGGÅ Is1†Â€ì,Ñ\X:‹aÉŠB¡V(Woo/Ö´hœ¤­päÈqCÒ\ ‚¡0`{{{]]]NNNLLŒåë,u‡A´È;Û××'^d%m…±±1qCÒ\ ‚¡0 ;K4žE}ÐY,YQ(Ô
-åÚ±cŠ†ÆIÚ
-ããã↤¹CaÀŽŽŽ†††ÜÜÜØØXv–h¶äݵkÖ´hœ¤­püøqqCÒ\ ‚¡0 ;K4–΢DX²¢P¨ÊÕßß5-'i+œ8qBÜ4ƒ`( ØÙÙÙØؘ——‡Î{þZrÝa-òÎ `M‹ÆIÚ
-'Ož7$ÍÅ 
-²³Ds1µ³¨
-…Z¡\8Åš“´N:%nHš‹A0ìêêÂàùùùqqq–¯³ÔÑ"0cg±¦Eã$m…Ó§O‹’æb ÅÎÍ‘ggQ,VÑY
-µB¹±¦Eã$m…›nºIÜ4ƒ`( ØÝݽfÍš‚‚‚øøxv–h¶fì,Ö´hœ¤­pæÌqCÒ\ ‚¡ØY¢9²t%Â’…B­P®`M‹ÆIÚ
-gÏž7$ÍÅ 
-öôô455&$$„††._¾ÜýÏt‡A´ÌØY¬iÑ8I[áܹs↤¹C±³Ds4µ³X²¢P¨ÊuðàA¬iÑ8I[á–[n7$ÍÅ 
-nÛ¶­¹¹¹¨¨(11‘%š­;‹5-'i+Üzë­â†¤¹C±³DsäÙYÔ%Â’…B­P®C‡aM‹ÆIÚ
-·Ýv›¸!i.ÁP°···¥¥¥¸¸_ÍÎùnÆÎbM‹ÆIÚ
-ŸøÄ'Ä Is1†bg‰æÈ]XGtUB¡P+”kxxkZ4NÒV¸ë®»Ä Is1†Â€}}}ëÖ­+))IJJ
-÷üúÝa-¾t“´>ùÉOŠ’æbv–hî,ÅbE¡P+”kddç¡hœ¤­ð©O}JÜ4ƒ`( ¸cÇŽ 6”––&''³³D³åKgÑ8I[á3ŸùŒ¸!i.ag‰æ㳨J„%+
-…Z¡\£££7Ýt'i+|þóŸ7$ÍÅ 
-îÚµ«µµµ¬¬,%%eåÊ•îö®; ¢EÀ—΢q’¶ÂùóçÅ Is1;K4wS;‹%+
-…Z¡\GŽ9sæ 'i+Ü{ï½â†¤¹CaÀþþþ¶¶¶òòòÔÔTv–h¶|쬤­ð·û·â†¤¹ì,‘_xvõA‰°dE¡P+”kllììÙ³x•”´Õ“¤¹CaÀÍ›7WTT¤¥¥‰¯fg‰|çcgåm½ï¾ûfl.;Käî΢8¢³X²¢P¨Ê5>>~îÜ9¬l%m…/ùË↤¹CaÀ½{÷bðÊÊÊôôôÈÈÈÀÀ@÷?{×Ñ"à{g§k+Üÿýâ†×æ²³D~dé,«X²¢P¨Êuüøñ[n¹g£ž…µ´xàqÃksEm1†Â€¶½½½ªª*##ÃÝYñ±
-Ýa-³íìԶƒ>(nXšËÎùggQ”KV
-µB¹Nœ8që­·~æ3Ÿñ,¬¥­ðµ¯}MÜ°4׳¶CaÀÁÁÁŽŽŽêêj—ËÄÎùN­³žm…‡~XÜp7—%²‰»³(Žè,–¬(j…r<yò¶Ûnûüç??µ°î¶Â#<"n¸›;µ¶CaÀtvvÖÖÖfffFGG£³îö®; ¢E`.m…ú§7DmÙY"û¸?P΢>X¬bÉŠB¡V(שS§>ñ‰Oœ?~ºÂþÓ…$µÅ 
-<x°«««®®.+++&&&88˜%ò_:ûøã³³DóÃÒY,V±dE¡P+”ëôéÓwÝu×½÷Þ+/ì·¾õ­k‹A0<tèPwwwCCCvvvlllHH;Kä;¾Î-.<Ÿ%Z\xݘhqáû³D‹ ?E´¸ðóÆD‹ ÿ]ÑâÂ?K´¸X:ËßSA´ÀùÞYþ>(¢…ÀÝYþÞE¢EÁÇÎò÷-žå÷-|>v–ß×C´@Lí,¿h!ó¥³üþY¢…ó³üžw¢…ϗΞ;wîV“¤¹’¶ŠÂbv–hî, Eg‹‹‹[ZZz{{‡‡‡O:uöìÙ[L’æJÚ*
-‹A0Ä¡
-‹A0;K4GS;WPP°fÍšîîîŒaM{Ê$i®¤­¢°Ca@
-p@Àa‹pv–h¶fìì‘#G°¦=i’4WÒVQX ‚¡ØY¢9²t6888666??¿±±±««kpppttkÚ&Is%m…Å 
-âP€ X„ãáþïì,‘/fììÈÈÖ´ÇM’æJÚ*
-‹A0;K4Gž]ºtiPP:›——‡ÎvvvîÝ»wxxkÚq“¤¹’¶ŠÂb …q(Àà8,`ÎÎÍÖŒ=tèÖ´c&Is%m…Å Š%š£©‰‰ÉÍÍmhhèèè8xð Ö´GL’æJÚ*
-‹A0Ä¡
-p@ÀaÅ!ÂýÞÙY"_È;»cÇœbM;l’4WÒVQX ‚¡0 ;K4ž]²d :•][[»yóæ¾¾> kÚC&Is%m…Å 
-âP€ X„³³D³%ïlooïÀÀ
-‹A0dg‰æÂÒÙ€€€ÈÈȬ¬¬êêjtvÛ¶mýýýâ¥$Í•´Uƒ`( ˆaq@Àa‹p"ØY¢Y‘w¶§§g×®]¨Û IÒ\I[EaCa@v–h.¼v633³ªªª­­­»»{ÇŽXÓŠÒIš+o+` …ÑYpXÀ"uÿv–ÈòÎvuuõõõ‰å±›×æJÚ*
-‹A0dg‰æbjg#""\.Weeekkkgggoo/Ö´&Is%m…Å 
-âP€ 88àáØY¢Y‘tvÆ Û¶mÚ¶ß$i®¤­¢°Ca@
-ØY"e–ήX±båÊ•ëÖ­kooïééÁšv—IÒ\I[Ea1†Â€8à€€Ã;K¤@ÒÙ––œ{vwwcM»Ã$oîtm…Å âì,‘²K.$:›žž^^^ÞÜÜŒŠuuuaMÛg’4WÒVQX ".@áP€ â £ÙY¢Y‘t¶©©©­­­³³kÚ^“¤¹’¶ŠÂbq
-‡v–H™¥³Ë—/OKK+++[³fMkkkGGÖ´ÛL’æJÚ*
-‹AÄ(
-p@ÀaÏ/þ`g‰|!élccㆠÚÛÛ±¦í1Iš+i«(, p(`g‰”yíljjjii):»nÝ:œbMÛm’4WÒVQX ".@aXpXõ|tÝa-’Î644ˆKÇXÓv™$Í•´Uƒˆ Pì,Ñ\LílXXXJJJIII]]]sss[[Ö´&Is%m…Å â8 à°€ƒŽ%šIgkkk›ššZ[[ÅK-Hš+i«(, p(`g‰”Y:»lÙ²ÐÐÐäääâââêêê5kÖlØ°uk7Iš+i«(,ˆ P8à€€Ã;K¤@ÒÙªª*q
-kZQ:Isåm ".@áPÀÎ)óÚÙ¤¤¤¢¢¢ÊÊJT¬¥¥E,ݼ6WÒVQX ".@áP€ â £ÙY¢Y™®³……… ÍÍÍXÓ¶™$Í•´Uƒˆ P8à€ÀΩ™ÚÙÄÄÄ‚‚‚òòòººº¦¦&¬i[M’æJÚ*
-‹AÄ(
-p@ÀaÏ_HÎÎùbºÎæçç—••ÕÖÖ®Y³kÚ &Is%m…Å â8 °³Dj,]ºt©èl^^^iiiuu5Î@±¦]g’7wº¶ŠÂbq
-‡ðì,‘‚é:›››[RR".cMÛb’4WÒVQX ".@áP€;K¤fjgƒƒƒrrrŠ‹‹+++°¦m6Iš+i«(, p(Àµ|‘%;Kä‹é:›]TTTQQQWW'^jAÒ\I[Ea1ˆ¸
-
-ŠÏÊÊ*,,,//¯­­EÝÖ˜$Í•´UÄ(
-p@Àaõü…äì,‘/¦ëlfffAAAYYYuu5Ö´¢t’æÊÛ
-D\€Â¡
-‹AÄ(
-p@`g‰ÔLíl@@@LLLrrrfff~~~II Ö´•&Is%m…Å â8  ³–/²dg‰|1]g“’’\.W^^^qq1Ö´&Is%m…Å â8 à!ØY"^;˜˜˜‘‘‘››[TT„5m¹IÒ\I[Ea1ˆ¸
-‹AÄ(
-ÐÙ©_dÉÎùbºÎÆÇǧ¦¦feeåççcM[b’4WÒVQX ".@áP€;K¤fjgW¬X—’’’™™™——‡5m±IÒ\I[Ea1ˆ¸
-¦vV|¥x‹6---++ kÚ|“¤¹’¶ŠÂbq
-Ã ‡°|‘%;Kä ¯ ‹ŠŠJHHHMMÍÌÌÄš6Ï$i®¤­¢°D\€Bgq@ÀC°³D
-¼v644T¼Ý“’’âr¹°¦Í5Iš+i«(, p(ÀÁÎ)t6...999##kÚ“¤¹’¶ŠÂbq
-‡ ÎΩñÚÙˆˆqé8==kÚl“¤¹’¶ŠÂbq
-‡taù‚-v–ÈÓuV¼Ý#.CaM›e’4WÒVQX ".@aXØY"5S;»lÙ²àà`tV¼Ý“ššŠ5m¦IÒ\I[Ea1ˆ¸
-¼v6((H¼Ý“’’‚5­Ë$i®¤­¢°D\€Â¡
-$ŠŠŠONNÆš6Ã$i®¤­¢°D\€Â¡
-¦ë¬x»G\†Âš6Í$i®¤­¢°DœÌbXð–/Øbg‰|áµ³è¬x»EÚ6Õ$i®¤­¢°ÄÝY Ž‡`g‰LíìÒ¥KQ(ñvOLLLBBÖ´)&Is%m…Å âì,‘2yg£££ãããÅK-Hš+i«(, 0,ÇCX¾D€%ò…×ήX±"88X\:Ž‹‹CÝ’L’æJÚ*
- â”è,‚%R0µ³K–,A¡ÄÛ=â2Ö´¢t’æÊÛ
-DœÌbXØY"5^;»|ùrtV¼Ýƒ¢‰å±›×æJÚ*
-‹AÜoÎZ~± ;Kä‹é:+Þˆˆ‰‰Áš6Á$i®¤­¢°D\€Â¡€%R&טּ %^jAÒ\I[Ea1ˆ¸
-‹AÄÉ,†õúæ,;Kä‹é:ëyéu‹2Iš+i«(,;Kä^;ë~»G\†ÂšV”NÞ\I[ƒˆ“ÙéÞèag‰|!é¬ûÒ±x©uóÚ\I[Ea1;K4wÓuÖ}é8,, u‹0Iš+i«(,¸/@±³DÊä—¡ÄK­¼¹ò¶ŠYq2;Ý›³ì,‘/$u_:vwVÒ\I[EaÙY"¿ðÚÙK&ßî—¡°¦¥“7WÒVÀ âdÃzý@;Kä IgÝ—ŽÅK­›×æJÚ*
-‹AÜõúF;Kä ygÅe(Ô-Ì$i®¤­¢°à¾
-ëÙY¯ ØY"_È;+.CaM+J'o®¤­€AÜ ØY"e¾tV¼Ôºym®¤­¢°„%š;IgÝ—¡P·`“¤¹’¶ŠÂºÆì,Ñ\L×ÙK<.‹—Ú›+i«x‘u_€bg‰”ͪ³’æJÚ*
-ëÙÙéQwD‹€¤³îSZQÛ›+i«(¬8™eg‰æbV•4WÒVQXwg§[³³D¾˜±³by,Hš+o« ¿
-ôMöD÷“°@1ûèð ‡îça!b2öÑ;ÛÝÏÃÅdì£{Ê/ºŸ„ŠÉØG÷”_t? tÑÑ=å
-ÝÏ]DtOöD÷SAÝ“}aÑýlÐÅB÷L_pt?!tQÐ=Í"ÝÏÉ„µ1ä_zgø‚µpž½[BvÐ5«…ò¤èÚ ²–ù¼¸hRæÈVZ¦ñ¢£ýy™ÿ ûh™Ã‹‘ö§fþ7€ì£e/FÚŸ-@~§k/RÚŸ#-@þ¥kö.R áiÒµ ä/ºfïâ¥ýÉÒ¸äz'ð"¥ýùÒ¸äz'ðb´ž/½Û@s§w/F áùÒ» 4wzçðb´@ž/½›As¤q/R äùÒ»4G'ð"µ@ž/½›As¤q/R äùÒ»4G'ð"µ@ž/½›1?œº§šf@ž2›1¼›ó>ab<_º6c~8xOçqª:Êy¾tmÆ<X8™ûݼÌPÇZ Ï×üoÆüи§v?¢]3ò¢aÓó2Ûçkž7chÜS»Ño“ï"æ÷'EíùšÏÍ°l’}#kÙÙyx¸9N9üþ¼¨=Yó³Óm­ƒÏÛþÎÃcù²kä#?>/sy²ìÞ ùöØ7òüì¯Ý7«]#ßùåÙŸû“eÇfø¾=¶nÓþÎÃc©íùnîÓ`îÏ”¿¶Aycæsgç²Ëv?ÜÜ÷‹ÌÛ4ð×رó?'õî 'àô """"""""""""""""""""""""""""""""º˜©ý" """""5Kà/·&éݦf‰œŸÆöÓÆjbîÂR‹e“p{.»è1øâŽJìYîiÅ$Ü6²RÜC‘Ñ…‘û{懱'FF+ÿ"hR``
-
- ádDµlö;8ñƒcô 1ŒÂ8 €±'ÆŽ‡„…¯ŒˆŒŒŒ¢££c ÑQ‘+ÃB‚U‚ƒã5B7W \»‰=  ˆŠ‰‹OHH4$%%%''§¤¤$''%ÄÅDE„…ÌzÍÁÌÁ#< œý8
-ä>·š§3!£!±)ÙÅÕk6nííßwpøðѱã'NÞpêÔ©N~èúcGG†öíê¹|m]injœQ¼ÉË’¿ì\€±c+VXNJÄdJÈÈÇ“ÐÖÑÕ½µ½µ©¦$çÂq&íâ„ Ä8 1Î
-+×´u‰œÚ[j‹³’£Ã=ûkÄ'NVUTVV–—æºRb"&g”û£ã’R3²ró
- ?Ï˃ڗShdBz^imÓÆŽ+w ì:|ìäM˜PwÞyÇ­go:yìðunÄëTnšqü5·g)怱ÑñÉعÂbœ ––ä¸Rc#=ƒZj¾J¤T4âÅôª~/9MÄŸâÊ5N.[½f͚ƆšŠ’‚ì´D#(ó\Ðœuæ#¦däMĹªäÒüìŒälë¹µxÆ#â°Ì¬lXÛ¶åŠíWí=8rì†ÜüW·ßv빜9°gç•mkë+.ÍJ ò¥sÀØhcç*jp>XW[UŽ]KO2‚šÜfcu—–·ªaCǶýWí@ï.ÈI,ÙââR2óK*ë×´lÀéÒ¦ë×6Ö–_š“žh,ÝÅ3cœ†qfæ—W×q®nl¨5O=SÌst'”X?­ŒIrå—V_Ö¼asgOßî#ã§Îœ»å–s¹áØÈÕÛ»Û[›*‹sÓ£ÍõáÒ‰Ó[c£óK*pNزn=ÖÚ Õ«.ÍÉHŠpo3î¼26%§´n]{Oß®]}WXsg€bɾz][ûÖîžžžîÎ˱"5–î QáfêKÝ÷Ë/­jhZ׊8ÛZ×77ÖV秞æÝlÍÉ<KÍ*(­ª_½¶õò®Þ£Ç?|æcûÈ©ã‡ô÷nÝ´nM]eI¾+Å8]až|Ml4Ò­Bº›Ú·tl¹¼m}vÍHÓ}úf¦“œ]\»vs÷ö;zñzWó—œÌ:ˆç©¬¶©µ½»wÇUýýý»ú®Üº©¥Á˜ÂqfÕÍg&<:1=¯¤ºq]Û–®+z®èÞºe“ñ˜Æ389ïlÊÉý|&gô˜Ï«[Ú:·ï¹æè‰ßtÓ‡¯?2´»·sãÚ˪WMÌn±1K'ÂŹsÕe-mÝWöönÃùòƵ •EÙ©ñ‘“§oâe ƒ¢ê¦¶­Ûúú¶umúKNâõÀ\¿¥çâøØÖiž68p`poÿöîÍ-õxésxÙÒ‰3ŬK+/[yÖbW]µs{ÏÖÍë×Ô®*ÈÄSj=eôwNfïã’Ò2s ‹WU74·uíØí±:õ¡±k÷ïغ±©ÇÔWÊÄ«¨8½-,ohÙ´õÊ8s¸
-‹HÌœ¾á40D\7A8Á3k6v^¹ÝKNÆtÂiS‘qÚ´½ÿÁC×><2<4¸»¯{SSMqöÄ1qÙÄ™bniffßÀÞýƒû÷öï0Žœ ÆsåýZßrןÌx¼Ü–T6´\~åÀБã'NŒ¸òòµõå˜Liɯ¾“§·¹%5Æàjc …ÚÒÉ—E‘“ñ‚—QX¹ºµ£gûv‘SvrÌDNKÿr:ÐÚi,sGŽ;6vôððÕ»·»Ï•ð’·Ìxµ3î(ÖÃC‡ 0qóºË*Å%ˆi®ù-'±.Áò-)%=+gö­];GÆŽ܉õeeQ®Ë¸\2qÂåqzÛr9]ØêááCCƒÛÓ’K±Üž\ŽŠœŒÝ9m^[[â‘
-¼2&%»¤¶eKïîƒ8¹¼þĉ×ÚUÏ榪K͵{€™ÖyÆÊ~sÏUƒ×Œ><<´`"'ûçÓÄr8È|·%>)53¿¬®eËö}‡Ž;vôоí[ZêJó\ÉæIÄä;SFNæµcß±í*ãÑ#£#‡¶mY7q]!`¹—œúz»/o©+ÉI‰A.5¯VħçW¬n»¢ÿÀÈØõºáã*Åøu#w÷ni™Oä”UT³ÖôÚÑ#£×í¿ª·³­¹®¼0kò%Æιy>‹³K$›˜–]TÕtù¶=׌Ž^³gÛåMUEY©Fçܧ﯑18¦V­ÆAe
-$C­>ñg“oþý·ï¿üäí-ÿÓíÏ庾£¼&æ·î}xø¥éüõïÿü׿ÿóïýíGéiî,ºG}Uf¤sõº+Ò¹MSN?ÿ<ùm3
-QÌ ÿ£Ï¾úöO?ýåof`ö˜Fý_<zÛ–“Ì­\»zկ̦œšüô©é)'/ÕM¤¥õèÉgŸ>•ÆΦ§ùtÌ4ûM.OûŸ§Ù|¬å
-34%*&ûÔ§¦AúD³w?üü›MßøŸùîËÇï¸I ·dé­âr²“!æódw?þôgñ£éš~èK9]¿zõÚ ÛÓŠÏ®¼óÑ'Ò*z ü–?ã×+Ëöî=4_ñø“?|ïûò ¡èÙÅå|9ͬ¼ûÑãO螘=á*Q9¹ïI:>ÿîÏ3}™Ï=^Ê€]"vµPN¦×Ó3œ:ðé³ç2©"^šaÙgfôº1—µÍy¾œ"²Êãþ{}ôÁ»Å ¼™vSÝûÇ’sk»wßyïƒ?üàýwÜ=°éiÜŸ€óÊ©Ë VöÌ ™éØ{³x%/§Æö¾‘©Óˆ=6]ì?ý`>vB)5^\N~ïŽH{÷ÎÇOŸ}ùü믟?ÿêË?üþó§Þ3ƒÓllXò¸WNÞHpûöÛ²`eO¬,ûËÔ†&§çVwîÉ2¾wìs©•9©vö)—+'[AS‹›æ…Þ½wk]ÂÑÖ PNî¹Çòν÷?ýÂüð_ýþÐ ÌÛ3Ù´¶¨œòIeqûλ}zøÙç’Wž|úÉÇÞhÃ4çv¦+§æ.yJ²¶sp÷î½ÍEÿÁòâÅ>oŠLNÏ.oîîß¹k¾jokuAtö´5º‰%oÞ×­¤2_tçVñ •¶œL~’çÂsëæ“ýñ§OŸº±è’íò“¿ú˽ç;Þûð£>üðý÷¾ûà¾4S&ýËÌꮽ«m2_Ë,¬míìn¯/åüb¿rùÒ¥Kùçb}²>tauc{woow{cu!7{k^]O«Ú}îfW6¶w¶Š_¨tÝ÷ôÞ=¹4?Ñîíûï<|øîÛwom.yY¢ð¼ÕƒëZ̧D–tÞº}÷Þ½{woìßÚÝÞ\[šMÅFì7ØQ¾WücñÌÜÒÊÊÒ|¶ð”·09ZSßÜÙ§g–W×ÖÖV–æ²ÓQó2mÞZì·¼~KGßptzfaiÙ¼PbÂ~ÜJÚ}ò Ø•¹é¹åõ-Öûvzݳð+þ*WWÚz‡&ÌG`e}skksc}ueyia.—NLŽ ô´Ù“M+n]ôÐØÔt&—ˤâ‘W­_õÖ¯ŽNÆSÙ™Ù¹¹™lʾL»¿`Åö´ÌWµÈò‚x*“IOO½PiŠ©xA¼y·Q·ÎaÉ.‰ õ–Æ–™Ÿ¾£wh|j:;;7?77›Ëš÷žŒÇ쪷€äêeûx²*Tßb^vd"³‹ZìËUåkq¾ Ú»Ãn%J2™˜ŠŽEú{Úók:ŠW~˜/ŠF'e¡Š}¡’î),ÖjïîÈ»MØ·;)o·£¥¡¶xo‹,rñv G§â‰x|*:91>6:<4îélµÏ®ºù§k7nÖ6´tt‡#ÃÃvý—y¹¢éÇÂþšÖŽóE#cv¡Ø@_W›Ì,]/^—fWõ„††eáSÉæ1‹ËÉ­«o2o¤o`(2<22â~ê¶æ|1TÚ€Ý#?þpļñþ°·ÀÍ|½_L® ì"¸Ž®£«£µ¹þH©ÿÎ.¦º^å=jíé ÷÷Û0öaØuoõ“¿œ®¶Á®ß“Õ{òL±ä[Šü‚’U’­í²’Э%ôw8ÏÒûU×à-:ì‘7ÞÑÞÖÚêL=u‹*ížµÖVù{·C­èåò_$+/[ZeG›y)YÃXS´çÏ_žév¿µ³ÐµDõ–¿S®¶Þ¼·4Õ®M UWÙŸëÈÂð·Ž.bmi–ýŒ²žY4yÖg¼ªê·D·ÎâÑŸ®°’·ÆnÓ3/ÕØ [#,Œ.¼’]_\’­“¥ßÉç?Å“¥îæÔÖš¹¶xæÑï…ßvQt]­Ýk×Ç{[dí㥢—µ;jó;h_]@Ÿ_go÷ڳܹh½za¹úy¯‡>¶ ü¼v+¯ãí¡þÙŠŸ/²¯ºáï·p{¥/å“™÷²Þí_Úámmðÿíc–Ïû¯ä^ÊßþPÒ]Sù÷á¶_µ;#äíÿÙ-êþçwœíÍ9²ÃçÊ•_Øp^ü»;æŸýùvšÒ—’÷f‹øõ3~¶ Èßö³LE;Æ~eÃXÑ?~å—:øõíY%äoi+úqßÖ«[¼.ýò—_zůýã¯Ù.v‚×)‘“ýPÇ~é¯}ñÉ~¶“¿Ô¯¿N
-ä›
-§²ÉpÚüʼâÇý½üÖÿÿ¿¹ð´ù’DÖ~uÊüÏ|Q(±ÿ¾ÔµÙp6•Ê%fBwnj_œ
-\LÚ5£ý.í:@ÒþØ„vUF€ix‹@»#ð´?Â@€iW_\Úd ¨´ë..í30ÚUö‡íúŠ‹Iûs ƒvMÅE¦ýéÊvEEÐþ˜eJ»j¢‚hز£])Qq´?ò@yÑ®‘¨DÚŸz \h×ET.íÏ>P´+"*—ög( ÚMûã(Ó®‚E}Z~@vå»È(ù7Rš<PV´«ÝEFÞÔù}β¥]í. ¢ó[œaéåO»Â]Ú1<B»0΀v%¥]áI;ho@»¨NI»Ø€ÒÑ®måK;2çE»\OD»€Ñ®jåH;&š´Ë¾@»$€Ñ®jåE;e‡X
-
-[..ýÓºÞ¨®mlíì  ö÷uµ7ׇnJ`¯]¯ºYS[oò³TåN££­¥©^K…ÕöJ‹¹?1iØT×–ŽÞÈÈèÈð`_Wkc­ô®›°J«ÛÒÚÞÙÕÓÛ÷Iu6½y
-«ç˜hE½9†·$ ߬mlëNŽ öv4×K+zƵÙDµ[ªòP$ìïíjkª“
-K\UGôr!œW½Y%7­tÙeL®kîì‹ŒGã‰xl|8ÜÕÚ2ý^“„MXÛ:$ªCãcããã£Ã½ò·¶ÂjÿŒ•¦R? W½x^—¡¨?­$ãù{“†Z»F£‰T:•ˆö´™D|SÚVÖΞ>Õ±‰hl*>èi—
-K".©B5uÍOÝ°ñ4Q™T’y%7½"CÔªPc{ïÐx<•ÍeSñ‰¡^9óÕfìÓÚÙˆN%¦S©TrjÂUçªkŒaK¦¸žÚÁ§ h•‹§Z¨5ƒQWª¹Y%sGW¤ymêèŽ&³³s³ÙääpŸi`CÕÕ¡ºÆ– ëØd,1ÉÎÌä2Ó±±AöZ›ˆyºS
-Å1½"1µÔųV&êÍ0Ô‡zQ ¬Ì5™æµt*=3¿0?“Š„;M\Í754·w‡GL»›L›˜›¿Í˜°‡;[êkªÌ6ÿt€àž›KùYzoF×N㻀644655›€¶¶µ›qhWW—ˆÚÑÌ5×ú–®±xfnqia6=5ÚßÙÒPª•,ÜgÃ:ÉÍ-,-//-̤§Æ\û{Ã5Ï<•=G®ª^¾\S7MÔXÏήî†ö÷÷‡í¼R½ÔØë2ÙÔÚ=4‘Ì™Ð-ÎeL\»ZêjM_¸£g`x<–HegæWV×ÖV—çs’§MܽÊî?¹ûù œÚ‘ù†ËöÉøu›{MPML›]<Míî¶Ó
-ý21†‡d *óJ7nÜ 5´õD&§g—W–æm\[M5ojí
-™^rvvaie}csscmÉÔç1Ó%–Ï„éy¹'wW¯ð¼ý¬¼2‰ä½ä+j£LutÚú)Ô ¨ƒNLFc1;ýÐ×ÕÖXg:OU7Cm½‘hjviÅTÈL|LâZ_/¦‘ÉDÊäàåÕõÍí­•©°á®¶fÓ>ËÃZïÑÝUù ¡ý ŽÎÚj¾¢¶ÉQŸ„s(RЩ©DR†+ÓÉ©ÉÑ!—NMW¹¶±½o8–ž[^5qÍÆǺZ›¤ºF&â&¬‹+k[;»{{;[ë¶Â…»Û[›åñŽ÷ìÎö¬é!ÿ&~Þ-¬N¹êž¼Ô˜6Q‚ÚÑ馈FÆÆÇ'&&'£2§ Mg²¹™ÙÙÙ™l:µ3 &*5uMá‘©ÌüòÚÚŠkw[³©ñ=ƒ£±éìœÉÁ›Û»{·öoííØ
-
-÷tu´µšnµ nµ7fºL'ê´Š»H×S2Uµ±ÉÕTÔ™!Š'‰¤áEtFÆ*‹KKK‹ó3™„°4˜êVS×Ü5q]1q]°qmoinn“êšÈÌ-­Je½µpppkwË´°™Dtlx0Ü×ÓmúÕím­2fª­±óWéDJ¡‹äOiUM£ÚlÚÔ®ž¾~7A”L¥³Ùœã"º¸´,½ÚõµÕ;`1õ²Ñ´’¡z¾Æ³ .®‰ñÁžŽVS륺¦f%¬{û·ý½í ùÖdlbl$28Ðîëíéîì09¹¾¶¦ÚOÇt¢ÞŒ×Eº’ï"É ’ý6{^d2Wæ‡r³ÊÅa*©„t}}ccskkksÝäÛäDDžÜÈ@µ¥³¬(®C}]¦Ý-Õ5;¿²±-•õö;wnÜÚÙ\[^˜É¦S¦ï56:2èï3ƒ¦Ù›ÒC¾úêdüšBUõÒn]½]›b4ÚVÕVÕȈ‹ªq.›ºiÆœ«++¶–šqÊÖÖ¶éÕîìlo®-ÎLGm"®­­mhé*Ä5gâî6ƒÝ^©®³Kk[»·LTïÞ½+qÝÝÚX]^˜3 tj:ŸŠÅ&¥æôõtÊD‡t‘«\h/_&°'à%à+WŽôZ}~«*Q•Y?éîoš(n›Ê¹±ak©è®eâº4›Š¹ªy)×Ѹé7­š˜É8f ·»»·ØU×/¬.®Û›ë«+K‹ ó³3¹l6“N%㱉QY©³M õvÞ¹°Y»ÜÊ[QT婨]:(ãÓ®nÑÕ%ÓRUGÇíƒSY—Ö6¤ {ëÖÞîζ0Ý3lXwl\Ó±ÑîB\§Ò¦ã»"JÑQÓ)êëNÏ,šêº/a½'qÝßÛÝÞÚØX—$°¼,Ñ›•¤ì"ÛÓÙ.}¨zéKd ìkxQõ†¨µõ^SjƧ–ùÏà ªÏ$S6¬›®¯s`'&»Õ[6®& Kûº8ãÕ×z‰kgxÄt‘––—æ³Ó±±È@¿é|ML¥g—Ö·÷ö‹ª«|³G⻶º¼8?k"7ÙX?v\+dþ˜ÀþŠ¢¨Þ´«|[ü% #£ccc£B¦òcÔIÂÛRÍ\_G¢iBzK~±cóÚÊÒ\ÖιúÚÜÑ™Lfçf³ÉØøðÐà`dtÒ r–%®wüW’ow´T{ùˆÈkÍçÒÉ©èøhd°¿OƵ&ËrTV-þšW£jªª]Á0:n'ŒXÌü7.!M§3fœ:;o†œ®šI4luIØVU3ÊY^œË¥¦Æ#2ãTW[[ßÔÞ386•ÊÍÎÍæÒ‰˜©{C‘á±hÒ ^ýŠ/¯ãj«‰«dø}ã–ù#Ó‘Zš7ÉØDÖ~üÞ1ËQM¾²Ú¨šÜÑÕkR䘴£I ¤‘2dÞ!73#CÕ™¹‰«ôbo{‰xÇÚ–šj;=¦?›ŠOŽØùûÚyÝÖ®þáÉD*›Í¦§ã“&<‘ÈÈx4‘ž55_f¥¡Þ³/ ]0W7ž5¯.s‹&°³¹ŒéAIh‡‡Ld;Zí3–£þ‚Kþ^·*U“€MU5%n'd¶Á˜µæææ û›¹7ó'uÊÕ&OÓ ®›œ¹8?7#1˜šìël•y Ób7w˜Qͤ©óÓÉxtbÔŒJMÏz2>usÃ’x%¨ë2p2ýl[…ïÜ‘&w_:×+K ¦¦g%´±I™‹êélmª“]TØcUV»„L¢õ&ædþÈttdZP~e±`#;ok6Û¶Ž­K@$ýÎÛ‘§©XÞYQ]]#¶ohÔô»Ì€t|Tš×!©°ñTÖ$õe™Ð0ä–—WV×7üÀº‘ib—\ßØTwÓ9êïéhµÏÞ©°?g+« ««¬-Õá±ISUMa{S‚Ϊ³bkã¬L/,zÁpJ4\Ì¥ÜSv.ah@öKÿ¦Ê.Pkëê¶ý°a™ ì—a“ä…tvfnÞûÜ,º©+YØ[.Ë€ÖûxÙoŸ…»åmËQ¦Ð ®²åÞÙÛoº¨S2ç`ªãšÌF¶Nº
-Y˜¶³ˆ&9K®ž1iR‚—ÈõË ‘ «¬S4ÿ@s[—]ü=d‚Ú×kÈ€xt\†ÃþS 󉙱š%ûÜÎŽ·½°šŠl>?fГ“Ùã‘Á^“ãkYfüªüDÄu/›ê42³3IòÛ5yNQëi*ŒÄ/#Ýb‘Íš_Ú®Õt2¹ë€<Šél÷F#v¿†]RÚÑÕ#íéîê²kf¼YI3€2ã'yl›r=4Óá–VwSþm;fZtO 3þ5£Ùxtl(ÜÕÖdñUö)š4t‹îÛ»ÂC¦_#ϸ—¥Då!·cpÁݲ};G`úDÓIO"ÇÝ@(:91>& §©ŒÝ2{ÐÒäÏ\ÍwËZe£dG{{›ÌL¶utÊ„³&»Uv<eÉÓ’›—Wì“¡eÉï&S/ÙõOòÉÊ¥ѱH7û=^q4ªÒ´Ú°F™™…åU‰ªL2¸i†¢°ÚÚ* h|*F'''L(Ý´ÅÈÈ°{ðbcÚ&ÃeŽÞ{îg…:Ù ÙÜÜÔÔÔØ( e)TÛ°[E#ÓcvÝ…äfÿi‘é³ÞSóììÓX$L\_Q<ìæ—ÚM–ÙMJT½IoãfôV\euÝ"÷-bÚÊéõ÷‡íƒR?¦v{r~Á¿·-]>B²*UԺ婲ظUê­·_¦+e]Ô_ZIõ2ó?mÆ\öñѪdŒ™Ì´í8uÙŨäaOalSeŸÚ4Ùái[ãé™o’`ß‹«S®»ü'=Û/’í4¢®©”Õ²°AVƒY’tÕ?F"ÿô¯À=Ö-×®j4 °‰°ÌvMDeÚÒ4Ú65Û.ºt»]u—íY- µì§ôMDÔ¸u-¦7Mgç—eRÏUV[[·m«jz£n\j†Ž“¶³+1õÖ¨¸ÅýöÜ{Š€[CxÃ>#-¬!ôÄ5·ÇSå+‘®is½e«nƒÝø„1nLÈÊñ“—ì3€˜ŒeœSÃ8ÇãUVÛÜym캖±hBª«}ö&Ó²2å+s½nVpÖK½ùÙpo¬ç7µ³Ñd\Ù‹cSkÈ_êÖãëYìí»ó7ßyçÀÈ"\Ye.Í®¿†Êîˆò¦¦dªÄ{h7b†¯-ÌKø¼‰¯{jÛ˜ ÊºB3n5­ëªÿLÕŸÁw—ÆýI÷|ÏÈÕÎ/«Þ¼YéÕ®foICa ÞÑ3¸l€%¸Þ0ùv·Å~âzû¬þ7°ÎÈì²ÔÖ¡pO‡Ì#š4Lóúʬ¡ãáž•ÉsÕÄtÖôšÜ*Þ]7W»šï
-GüÒ&¸K?—_u~%âš4¼nù£ÉæŸNÓ±“Í”1™øöÂ*›º¨®®¿d[ÖêZ{æŽÝ hgz¬´¬D“YžM¯ªÚ)ˆ„Làçƒê­À¯ñº»×üêyùò«ý•Òþ•àÚæ×ffÿP'Y:×ØäÇÇí¶îBX+¼º^Ê÷—¤e•Íÿ½ý;ÅoÚ,Y–ö¦ïìàßUUÛ–MÊ~aù‰­§7ýCòõódýÅ·æ'èË~û›ßßeWºJ¯­³GVÍ™ñU¯„µÁ;³­ÒÚ²ÚöÉ͸ld›™“gªÒ5J›ÀÎÌy«õíSìüCì¢åbGº»GÂù[}©º®òz§&ºî²I0¦Êöô…ÃÒoo-,ƒ©è¸º°Ú!«›]rýûä&Ç´q—'é³þPÕ$à°©G–wæ»»ù žÝ»,
-ïåÂF?ÇÈ!OÞ†i/¬•]]ý™;dµŸ{{¢ƒØØÉõU»IBi¤äI<·Uu@ZUÙBá-Çösoþ,÷s)T¿âæš¹æ¶MÊvÜê k>¬öQY£UY‘²a]][—Ét».LÎÈFªxÌ-ýëµ ØmŸ(Þã'ßó}ÓÅ9Ù¸eƒfc£aùGcVrXßrç‰zg%I]•….ò¼Ä>ûZ·³³2¡¶ÓöÖsÖý|»ÓyÕ{ß—
-×uãm¹¶ÖåŽ
-_îoÃzÕkÇ6’M]u]¤åUÛý•úšINÙ}N²ÑÉ.vh÷ð«ÛKY”ùÖ¶ø©»8Ž„UVDøË—fäÙ¦]Yb—---Ìy+ê‡ìÖDo±ƒ·­x;±Êà­Âòæ¤üƒÜ*8¬®DìYÝö¤¤!·|I–šZËvÌœ¬§—-0a÷Õ-v¨)“íÿ—
-§–ø'/VôÝùŽpumƒÑ1ìm¼•†«ö œ,ßÌed±¶Ý³Öáõ8½ÿåq\GaèsÌ<•ÆŸbª²a•eÙ »(meUvolxSÀvRb:ì³cÕF·ØáHÖþYŠîËÊwɵߒŽK…Gr^X£I»¸Þm?ö6Gùˆå)ÉPXV†zUõ†·uX½ªæå»Ç¥é—©|®ª©m”°ŽÅ¦s Ëþ– »JMvGÙ]/² "2ÐÛÕÞÒXçp¹EUœÅŒeÀ=i­³autÈi:…-ª²MÍ [eÄêö¼Ô{KCË&£Haµ‹le³IØí ¿uppà­G[_[¶kÀbU»@_––Ôù3tDµ ¹Ë¼Uö½#Ñdnqm{ïàŽÛ쿳-a•=ª 9PÇ-/´iتITËÐÑ°Ú3³ó«[{·ïÞ¿ÿîm·•Iš–°JTe:«{ªITËN~¢ßðÛÓ?<OÏ.oìÜ{ûÁƒ·ïÝÙ—³tÜBÜØ„l½°‹»û¼¸²f¨\åÃÚÐÔf±]áµí[wî?xç÷îÜÚÙt]¦¸Lz öM\ý¥›„µ¹{þ$¬òü¦Èϱ±sp÷íwÞyçí{·÷¶ÖÜI“òŒ5Ü×'ëd¥7q-c—
-÷üÉ*¦¡Ñ‰©i9ýj×ÄõÁ;îßÙßÙXžÏÙyþ!™vGÛ¸¶×rå5®7kòa¹•ü;ûwî½ýöý»»›« ¹T|ÒžtÕÝÕéNÝꓵÔ×råÌ]¯’Ì
-[ÛØÖ=à³”#J·MgØDUÎýŽÉ^ôöæÂ,þÌs{ï <™›_Z‘«ì-Ùr>ô˜Ý‹.€Tü}Pâ¯îohéì—Åýó r»¼wI ½ò–°N~xckW82KÊQÙŒGU¯;®ÇžÿAXƒã’«°ölwd<*×Ý&å$÷Q¹N®Ëî­ªö¸ÖÀ¸TX
-ÓÜnÏò‰My¹‡Ýuru!®F ÿþ'9ö¥ÑÒ4:>11>j3p‡»NŽKù‚ÆÛãî]öO¶±ÇóÛ}rÊËuÓe"¬ÁQ´½ÜÝiÏéòŽq¯+ÞÞªývq2þEmv‡=xÉ^üÙ–c¡{ý]Ë×Ù1,þÝ)×ìõ{²{ÃÞ©ÝÞÕÓÛ×ÛÓmÚVka Âéî:yÿFy¹žº[#õ.I!‹Ɇw:š¿1GÛa¸S+¶MáîËƦ¦Æ†úzÝú†üµã uo8þÝ)5µõMÍ&Žö6OÚzX·ýªÁMGÖ ‘ÆÕ»;Å6¨]]í~d¼Ûä½ýèÄ5ŽúbïN±·,tw¶ùUÖ¨«õF®,‚¡/ù)a7᮶—Kxë¥ 
-ÕTW1{Åç¾\ñá¸mt£î^O{»}êMÂù£ í¡/ö|w{wÊðØÄääĘ»_YºÁµ¶²^gú0¼YC;mhõ®væ†Ç£Sñø«gÖ€8:k('z›N[—ÛŸN¥¦± ¬Ì2Ù þ5 öŒöP($g¾ØÍÌÑD*“•kÑcrC·ibëCÕÍù饚Ú:3B•)¦–6¹yy"žÊÎÎÍͺ¥½]mÍ µ¬
-ÿ×P½\nÓÚÚÖÖÞÙ-×1LMçæ—æÝE8²¦´1¿JMû]ã5¼µiöÆåöŽ®în¹Ì³PnÙÈÎ-.¯®®ÈF9i ·Ó‘Çq/P|‡kwo¸`ppphxt"–”sh×66äÀ—ìtl,öŽñ! ¿¨ß X{ƒî<Ÿñ‰h<鎖“|ì‘–îìCï²{íw×ÈŸØoïןŒÆ¦¦â‰¤é2-¬¬oíÈÉ[kKs™„k#·DÑ^ÞÁ¹Ãu:•NÛû>%¬ö
-Aua]šÏMÇƼçsT×@ðâš?©4cÞØ”†uÇm— O ÷³ª)@ŽÆuÊ꺳ëîKßr×m˜ê:è=Nçù\ Y®fŸ¦ÛÑÍž¶ºüí½ ‰Éoq)Õ5 òý¦Žž‘Iw«‰ëþÁÁ ìŽé /˜4ì-Bt­«ö[Æ .ë“©°Û{·n›ÀînI\ÍØÕ-“`f80òó²Îßôœ$¯mîÜ’yá;ƒ¸`zã=mM’†i]¢è8Ÿv9ã=¿þÅÄõöþ­ÍU7…Ø•_þ¢ýŽq"…ã·Zl`§R3 «¦ÂJ\nín®zèZ˜ñ”‚µ9ã}xÒ> Û¶^oïïnÙ¯¬“ž¢•ˆ²çÊ_
-¤£WHJNÍx7\ݽ³¿»±d ®#9Ø…µÆm£“$—×ý~ä`‰$q /¬rYm}s›œ/Ík!®[˳^\¯_e GP¸£¸ä²AÖ&wÓ Ü ¹º¹»ïгÁ H¿‰¸‡à\¯ª6amlië´»­¦íbDÓoº} ×3gã£r [®‚#†©»mPn0‹ŒEí´„œî/ÏÕe§úpŸìTçqN`¸­V¦muauwHNÄL`çíFu§z¿S]û ãDÜ©þU5µ M­öjÐðÀÐð˜67·¸¼º¶*WÒyǘÒŽÂå ­r9h_¸`(226K¤²³óE[$Y<(nÇ«œ'Ñ.•µ`pp(2<*Îäffgs™dT.á`§zø·\Õ7µuöôõ E"‘á‘ÑñÉ©Ät:“ͤ“±ñˆYݦ€ðŸÎ5´¸»|GœÑ1{ýët*•JNMŽ ö䟾×@ð5Éüaï€iWDZ1óŸ‰è”‰ëôt2.ºv[û ãD
-Ç\ÊõñÉh,µb6¬)×èX$Ì]HS´•y`x<:•H&“‰D<‘H˜$œN§S×!ïªA…†ÛCgŸ¦Eã&–™LÚÊd²F&•úJ\&¿7²+<<1åF63ùUÖu‡%×
-GçõÙéáhÔTÑá¡Áþþp8Ü××ÛÓÝÕÑjÂZSuý*i88ò›#;ì㜱1TSE»»:;Œöö¶Ö–æÆúÚêªë¶º×€Èofn—ǯ‘ˆ©©}Ýí&šMFcccC}]m a ÿ9]ckGw_¸¿ßTÕζ–&‰fmmȨ©©¾YuÃ$áËo×àȯƒi–5¦==¦®š¨Ö…L4oVY7n\¿v•°Œ‹kUM]Csk»´¨­Íu¡ê›7®_¿æ1A½rY’0a üEH M--®“TsSªèßeÔ·kÀx¶:TWß Ý$“‚oÞ0QµÁô]"ªAãïϹYª­««5 k• « fžöÛÄ›ò{£êfµéûV»|ù-Bpþ!&²Òû½~ý*½¤ Á ìÕk×®_wCf .„Kî$ZoHCX/ ¬©³Î[„õâ¸äB˘æÂaLsQU
-erÙØt2—1˜1Ë΄³Éd,H$ÃÑd.KfÒ‰p&;K¤“æ[§³©X25 ? e’Ó±\<1Ž¦2¹XÒüm:ž‹¥g²æ;ÓñTl&™™§äŸJÅå‹Ò™X2‘J™ïœ6ßϥ͟ų±t<‘'ÍÛ˜‰O›/KÎ$b¹d&žžÎƒ霼ݙ¬y ó²æ;s™X&1-7ÿzfZþQó ‰T8šÉÆæûŽù1Ÿ„ž‡N„æÂSk_?ÿìÙógÏ?¿óøå®ü¤œWž¿|öøËg¿5cþt.š Ž…¾zPô«È#)ÞõgO^>ûúùã?š‚Ž<¦c‰™ôL&1&±x!¯0ºººòäÉw_ÝûúåcùÚ1óš‘GsæÿB6OCïEå% ±Ž<Š‰¶÷û¢xG¥^‰¸{Sö›MÔ#ŽräÑoˆ³ùæSG:òèô±6ß{ÊhG=—â˜BÿyÌ#Žº¡c£lþèÅc/à+;á•ï^~vöÙO‡…ÀÛ ®<ýúÓÃG+;3núíá‹ïŸ>Ú;üñ‘û¢oݧá ?7 óªÏ¿~NOgíç;óI2ÜTïX6mkxBêx<¼k~ù{ó›?…ñð~øƒâá§æÞs‰fóÙ—‡³î—«‡Ÿ?{îeói4ß¿šZ?üþÙ“Ã{[«á‡¡¼ô‘013áJçÂ…ÓæcŽ'åcüű_ ¿÷¿Èÿo.<m¾&‘µ_ž2ÿ3_ŠDì;<8ÎfMü¡‡;7ÿ
-
-
-¥þ…v
-
-¯K!¤ðÚQ‘W×eŠR3xí®È+ÆëE©%¼öXäUâui"„Ô^{,òÊðº(Bj¯ýyx]ˆ!µŠ×Þ‹œ
-¯‹!¤¶ñÚ‡‘*ñºàBê¯=©¯K !¤NðÚ™‘—ÃëòB©+¼viä%ðº°Bê ¯½9^BH}âµo#nx]:!uŽ×NŽTÆërAy+ðÚÕ‘Cx]!o^û<RÀë‚@yñÚó…×¥€ò6âµç#tþ„ÏðÚÿ½Õxù„·¯½à[Š×ÙN!
-¯}áÛ…×¹M!‡ðÚ)Ö!^g)!„œ¯ýe]áufBÈËáµ×¬¼ÎIB©¯}g=àuBH•xí>k¯sBN…×N´Vñ:ß!äàµ+­1¼Î.ByÅxíVk
-¯ýë×™C!gŽ×ŽöÃë !„ׇ××3¼NxBy#ðÚ¿>¼NiByñÚ7Ÿ9^'0!„¼Ñxí¤Ï¯•Bj¯öKàuRBH}âµwwÃë´!„·¯]>}>!„¼YÐ?B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„ò!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„BHMÐÐàõ¯h°áõ»×ŽÊösXÞ:$÷Ï(”¯_Š¼6TþŸ?áÂ…‹
-ùßx©©YÓt‰Eà­Bùÿ Èþ–Ö¶6Ÿ¯­­µ¥Y¹bEàõë‘3¦ÿÍ­¾öŽNÐÑ^V¼~Ar¶¨ü¿ˆüoïìêé=]ª*–€zG9
- ©Å×ÙÓ?8<
-†‡Tðµ©z€% Þ)8€¶Žžþ¡Ññ Ã0ÆÇFû{»;Û}pU%àõK’³àÂŦ–ö®¾¡Ñ‰©é™™é)c|txP;Vø
-ÀâòÊêÊR._Nõv´©~
-1f‚ ±D:›ÍeÓº8!ÀF:€ú¥8¤K…P
-5
-ñ
-(€/ÐÔ5º
-@nie è}¡"¡™qz€:§¸"´wpLm +»B©éàéD4Ìùàu5¼Ïš žËçÔ¶@ñè|ÐÚ–­€z¥4D- Æ“éL&“.î.Û° Ž‘°­°/`,‘L#÷ÈþùP
-ìòÑÔ1‡6‡Ÿ‹&Ru8„Z6[ÈÿvVÇ4X{ƒÛ€hdR*þ÷OMŒð”°:ùîB¡ høñd6¿´´˜Ë$cjAè¸8€&î W¯¨üW{ë6 ÞBm
-³²¼˜SÛBùU @M 䊰º¥X¨}'üj2ØòêúúÚŠÞfvrl¨¯›@Ó`Þ«7…Id—V7677Ö–s©X˜{ÃÖ;:ÿ
-Hà–îÔëƒÓRp@}RT›ª=2‹+ë›—/_Þå€~éä(P}RÌ=4>¥N‡Ógƒ¬­­®,fS±Â¶ ­Í¬
- .°¦}‡ýsóÕá;
-CáH<_Y·/çZàºB¯ü‘ijØßZˆÅ“ ‹.,,Db‰Ì¢¬Þ¥ ¶µÀìªlù?P˜ö¡öÿÉçÕ ¦i&RÙÅÕõÍ­­M9¤x8 §Õ ¥½ÔY°p4‘ÑAÿêŠÚ8•Jerˆ7õvÙd4T\
-ÊY
-Z7
-@V‚²P_ÏQ{€ùÃfvåòîõîè
-Ë@˜ÿ5ŒµóC‘?r¿­Íd5¨š>­v„U‡‚­.e ÉÑîbõÏü¯]
-‹ÿK ø»¤—‚¶w€v]Ôz`Ù2Ë/æ3I½la
-«ÿ/©?š››ô Jþ]=}ýý}½Ýím­-h èBc“3³ÀìŒî
-²Ö±¶h—OÑ­2ydl|bbBmúÔ¯]€ª´Ø G•Šf
-^ëXÛ¾£Î×»¿ oQ÷#û'&§¦§d׿‚ ð
-݃ê¯ì
-þÕ$/Õÿ35˜[Ðõ¿Êÿí+`ûòÆz!ÿçƒzõ‡:Š}ÿµNiÒ÷¥&íýEý*û“iµë#ôeggG€U݈„å[ÀëAª¥0û»´âo`d\;ÿX"•É-.©úk[€­Mµùg"¶ {‚‡¸ú«(ü§ç|·ŽýÑu¿å•µuÿWv®èÍ¿ÿªú/ ú8ø_Ó4”þ“9¿²ÿ»
-ý“¿äþ¦ŠÿT
-ý¿Åî?íý9ùçÌ)_¡[ȼó…8'Iÿ#…G?-‡=ËtÞŽ.ýc†ùIfõöÏWß}÷Ý«»ªíü‡üep®”ÿÖ€×’o)VE­²¬DiþÝùckà²T8潰ɃZÙ§&ô÷ ©¾„…íŸ*
-ÀŽV¿êýϤL5þkè½_ÚZxèk 88ÛX8ÝBïÏR˜‚ï¾ wCqWŸ‹.éO¯÷êTk}ûƒÃ£z¾pNŽÑÇ?\¿®ÏÿØÚ”Á¿´Ì“­¿
-ùOùŸ1ÖúÜÂ’ ½B³¥ ]µ^O-Â)ÌÆr0P˜Þ¡4¯ùÊÖ>*ïõqÏÈü!•ûÆ”šï¯:ÿÔ[ÊÈù/[zì/­:ÿ3†Úú«¸÷ óÿ¬‘¹ùª…î“EW…%š¸Vù×ÓÓÝÕ)3r+öÄYã;R‚ÚåõñIÖ«ežã:÷gƒ¡ð|ùŸWç¿ì\}WE
- «£´õ ÀÙ"§ó5ËÂkY7Я–i*õÊj-µ@×:‘µÁödi€GÏî‘žýêé~kçŒZì០(í#÷õœo9ðQü!ú×=¿)É~ûØóÿµ ç2gŽ ÖhŽŽO¨¥Zj@Þ:ípc¿ÐÇ«Ï÷Ö‹ú5øÏظ
-¢j¹r™O$’©t6¯Ç~Õ©¿— =zäÙ/c?ìûmèÅYM-zi†Z›5335i ¨Eš¨¶gÔÝ™Éq}"®ÎmìÆ÷úGÆáë§&' åóÕ?µÎ#¨“ž¡ýDR-÷Qk>d
-YͤQ@q¹Ê@÷sù¼ú7„þQ|£ž÷UÜ‚òè µ³wxÂ?‰C¯ñX$œ™Fø6+"Ž£~öÃýªen-ÑWèN^µ#j=½g^ïá—Œ›QµÂOMó[ZZÊçËqµäË*
-
-@<…Ú:«Î朞PÛ4tuê^{G‘îå5¦gÕ
-
-ë=(~/Ñ ½gxªP
-ÝÜÞ+`ûê»r"Ÿ½
-ü”_X@ã_%ª}þÔ$uú“îClSóLø¿HOp±
-À†lÚpEÊÀj¡§7­õWÔLîdl-{´òÔØAZwªüWmÿ€Þð[ÍñíPǾš
-í>¶û=§P
-
-T ¨ À†U
-½ýœåù&¢ @S[WÿèTpAMâͪ~=åÏ 5-< æeôæýyÔöhágÔ
-¾pHý32 <¨r—ßÓÝÝ­×–u–†ù9Ô÷#“‚;z‡&f ö„š¬ý¹1 ÙÃ7¡—s¨ þªk_9ü _— ]áO#`”Ñ}Uã«•¥jqi[[i —M¾7—â²€þÃ,. Pù?¦ŽoC ¨©þ…!À}×\pVUø£…lLUùznOg»>æ[¯.·õö±É÷SXÔÑ38jL«3›‚2[kTÏûÒû9 ªA5íCã/Lè@}/3u•¯[{r¼§îñ³÷ö1ûßXäܶ_g¯ZØw®ýù¨:µ[MúTK<'§§õíIC@kodHÕ÷…EäÝ…*¿ï7õ»@ñ×Å“;{úõj~µ8xH-—C|õÞÊÉZóAõ‘×.Ë
-U~1à»ÀÞ¾Z¢pt£Ú¼]mã3¨wè-îQ˜ù¯ÛuEÔÉÝ]ª‰§7’)Pªòϳ·¯¶³ûÔñ zCíÐ; Á¼Þà©[ëÞY¢C{:Ï-ʪ|æ~íPÜ"ª¹¥MöRž½M¢ùV½Lo„[mÅ €Ô|qøm›
-ZU>3¾¶(n§Vz·qÍÍ¥âZì7ššŠ;^¼`ß,€U~íRÜã϶1ä¥ÆÆÒ†—ý½Q¶’¼`;Ä—U~cíjÛÖó%Ï*¿(íî\æÔÏþÛ¹rÍ{ýâäUQÚö«Ì©7ú _ÏTÌåÊxýªä aþB!„B!„B!„B!„B!„B!„B!„B!„B!¤fñû·\¾÷âžï£Ÿ}7µ‡¿ßº÷Õ‹ýç¾ë¾¯}Ñõ]Ó¼½ÿÓFÞgÑíçûû<ø˜÷ÅÔßîýd÷àÁþOåï¼øâùwÅ¿Gן«‡>ZÆsëÏðϦÓÿûè;_ÆŒGbÉLÒXˆ›‰HÊLÆŸø2©x$ϘÆB"ž‰¤ãÉ”‘Îá_s™¸±Tÿ˜L¦TÖŒÄr‰nåÌH6Ë÷}Ét6’ʦsÆ‚:(™I¥D2Éš±>—ÁÙlÒHÄ’0›Á§©t$‡‹û¾x:I&30OÆa.›1ñD$–Éâ=L3‹/ͤd"ûxL6’Æ¿ãÁT2 «ê'$r‘˜™2tF½O*c,är‘d,‘5*üÌû¾ßGaߢÝüâàÇ<|ÿÞ‹GH¯¸Ué¼~ðâñ½§ï}…ÁÝEŸ/gCÆGw}Ú®ü{*y/?¾ÿâñ÷ž‡„öïR‘ÒÈL†T^<W‚ë÷ïý“[_¼¸§>‚MÿÞ"þïÓyñÀ—.U&Kyíß‹ÊíÂßmùíßK”帼”~¹îß«˜Ë¸]}>û÷ªÏiÿ^Õyíß«:·ý{*9Â*Ñæ9Þ©b®r¨b.ãÖó{… _ß5Ö¿~ñ…!‚}ü÷K¯Áõ_|¾¿·¾›Û»ñùWûÏ¿Ù°÷îþw{ò¡¯¤4¼d¹1aõà‹#•Èê‚3÷5Šn—qqã°ÒcÆU\üÜúÖ0cÆ{Æþ&f<Àíná‡>3‘‹G2ñr/C§Í
-‰™Èf# Ӵߎ¤õÉ\É›>tO¥¹úÜ}ß¡»•ýúiËbÜ,Z,}wñžz:—õÌ¡O&c±#í÷Jßm»[zË’ÅÒ¯9ú»ïû~ìåö¿Ñë_¼¸µÿ‹çöˆ2#±D*.i®.ÓF6¡”@ ̤ÕïÎårFôÖþ½§ïÝCöþÏÁÍõÝ+Ïï=x¼ðâö¿xþù§ÐÑ‚ƒ÷ùàÅwO÷÷JoqÈ‹˜Ê‹l|î‹r}ã|YðÃ8‹ÆÃÂwi3Tø€Y( Å?7âÙ ËÅPåM,bfi)HÓ´ý&n¥#ñL*)Y£Ž‹;|/I'át±(ÝU ÙÛ ÆG¦™8œ3…{ºTàá\2wø“x8 ge7h»gûjÛ]ë%KK?æ课_–¶æ1i{²Ò S%9•2sú³ ppÉT*wèŠåzã'(š:X>áðêß·,¾ö•þ ž^ÛŸîçårcÿáãƒB$‰ð™+¾èåýoßß¿ueÃøèºïGÖã&•¨
-
-¿U³×/òúhäm¨Ž-¬Ìë!„B!„B!„B!„B!uÎëÞ’B!„r'E„ÿÊÛ 4Hƒo‚AB!„BÈ yÏøúÿŸSÄŒêÏHÎŒ'3I#Éd3éx
-i3—Mdp‘Ë$Ó™¢­{ÖA`¹TF‹$siÓv@Xá¾1ã }>n'"é„™u¾EÌœ™,ÞGbñdÒåþ!3Ùl$•JùRÛíÂ;>uxwõù«Qyáœf–HÆ2©t<Wsg™Å3I]ÌLcýŠÞ-_,’Í¥“ñ¸*^™T.––âeæÔE.Ï䲸ˆÅ2Ùd¢ÂÅGëå¥ù*.þâ`Ʋã[ÃŒï?ú›˜ñ
-éd¿:RØìÉ ª
- Â[«ã!­3ñCðJÒ>†4Kg¥&JO™©C¿?aÀ[d%TÂ(hYº™ÎDÙTÂþ+l,å‘ͤífé»K©j{ÉŠ7­·tJþÊ!ÂBBes*—°9÷õû÷¿þÉ­/^ÜSŸ=”g¡íJyXéç;žñù’¯P^lÄIãuUfÌ\*…ÛL%søKºø®éX?Áv3‰*$7RIC\å^ÙœOL%b‘D,–°ÝºïK™¹H*“IÛ?‡!+©Q4VºSüNõ¤u)44lÆT:'QÙ¾´x –^Îú\éXÖŽþÒû¨DC1T¸(R}±B*™úH+«†ô)ãq3‹`+—ˆC¤™„®aÌH&¨tóšífV=“«Pz¼âMÛ㟿„¹WŠI¸±t/b¢Æ‹¤‘íxgE(µøý¶»×lwQ›#ݳJa%oÚŸ}õ¢yæõb¼êzѪUaJ¥•{HªÜM£erªZƒŒ¤’YûÍk¾í‚??Tøî2„ƒ‹k±d$i­—nªÚ.ÉÅ(ݹ ²$—•Òeâf.~øf¥ï·}2÷š>l±xËöÍ)å®uào{ÇŠ7Kïxòš±Rºù÷*¾ù¡Ú1žµ²¡*‚Kjy–,%²¨ñÑl:Q” }2’É%ýâd w6›¶%—j'Açû­J_mû\)›löJ7K_\JUûO¬t³ôŠ¯·j¬”ò¨+%À+¯ å T9–{uT~‘\.Yx D ºÚ+ÝD’gãx·TZ×7ªÈeÍH*n¦t=¢a骠Ú)D–>‡ªÊ”_jY+Ý*~«zÔº‰R’È&²†ÍZRå="VÛ·oáÑÒÛYŸ+ýËÚÑßZ¬øÌ´‰P%¦šò |<K¥‘¶»IÄbfJD<¦¢2bÝ¤Ë f3YUÛª*»xë¾ÏŒ£&2™œís¨+"©lÊnͺe}-µnÆU4g¦ð¹’5\šyèk­{x¸ô~¥O–~GÉb…_|ß·ñ¹UD6©bôáÁªƒÆÃç÷<Þ?xa¤Í­ Éåχ¾x®2‘Žë; Ößlo*ß…<Øø dˆoÙÐAG6Wíwh6–Õí÷d.™È)3ÙÒIÕ£ô óÕ2á œ’™Ê$·ý#TPR…"¶»ÉT¯Ëf¡H<­»‰L$–†%y-åJ÷TÆ©#ëñýöOÆcifÕ
-¤¹´únDEI$ûß 3fd3GÎ}¯øYõo…ÏþsüÉï ÍO~'„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„zãBꯕtVx®„¼¼VÒYáuº¾444xý
-õ×J:+¼N×:¡AsNs^sQÓ¨ijj’ ¹)˃^¿~=൒Î
-¯Óµ8V} EÓ¦i×tiz5r!7åòayPŒPŧÁk%^§ë‡ˆQd(éUÔÖŒi&5~MH³ ‰Çãr!7åòayPŒˆÁŠZ‹~E¹Ôl^+é¬ð:]½ÇE’"F‘¡P¤'¢K§ÓKKKkkkÛÛÛïjnjîh>Õ|®yüø±\ÈMù€|X„Ø5Ø9‹EÂ"^‘-ë„×J:+¼NWxU’1Š ¡ÁgÏž}ûí·÷w÷ý÷ßÿæ7¿ùÃþðOÿôOùË_þú׿þ×ýþÄ5îà>þŸÁ'ñy<%úåŠf)Ø*ðZIg…×éê1¯P­Nµ§ˆW.\jRªò”x­¤³Âët}³plu-P‘³\TÝ¥$Åk%^§ëÍ+éÅ-»ô±G÷•àµ’Î
-¯Óµæ9¡Š©¾³Æk%^§kÝR¦\*ñ¬ñZIg…×éJÈ«Ák%B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„ÚãB!„B!„B!„B!„B!„BycñzÆõë…©V Lµj`ªUS­˜jÕÀT«¦Z50Õª©V Lµj`ªUS­˜jÕðªRB!„B!„B!„B!„B!„âŽß¿uðàò½÷|íøü뻩=üýÖ½¯^ì?÷]÷}èóïEן¿¸üøþ‹Ç_Ü{þ‘Ç­àúƒ/>ß7äcÿï~Ȉ~ðâùデFp}×Øüâà›ýç_áóÆöã§ø„qkÿ«¯Ÿ¾óxvÿ÷Å Óˆéÿ}ôOÿç/mCÆGwñÏf<“Œ$ÌDÊXHÄÌH6e¦Ÿ¨û)ÓŒ$SiÓvÿî_¯ü–¸õü^ñ…wõ¯_|Qé•õKò“öÖws{7>ÿjÿù7ûöÞÝÿnO>ô•¼{´üÊÿnfÓÉT223Ft÷à…|€”ý`ÿA¨ø5©Ê–‚ï°þàÞ3¼ÛÞ³§_?||°÷þÓ¯îÚÒ5:ü¢»_½¸wpOåÝÞîå½÷î= ý-[?Ý¿ÿµú¢ÃزÂÄÇUñLB'ÿ×Væ˜*st‘@6îçårc/W(È/|æŠ/zyÿ›Ç÷÷o]Ù0>ºîû‘õ¸O$#éD.m,ÄãÙH<—Kc$k‰dJeú£c?¬þ­ø@ñ¿Y#¡>ŸÑ&ñ?|Èç÷ë7S?.o$â™X:áûh·Åë©ñ„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„R ïR§x­­³Âët%ä¬ðZ[g…×éJÈYᵶÎ
-¯Ó•³Âkm^§+!g…×Ú:+¼NWBÎ
-¯µuVx®„œ^kë¬ð:]듆—ÇëW®C¼ÖÖYáuº¾zÜ¥qîç5Ö… òù“Ðé[ÊLÙ9¡AyüÂ… ò§\¸¿ðÉ¿Åë¬{Åx­­³â,Òê”åÙ*v.Úh<Â%M6š5-6Zm´i|ÇÏàÃxñ]xK)Õý w•YËOÆO“ß‚—‘koo·.,ÊÞ\^Þž’8öä²'ãÑD¶gÁËþO<†×Ú:+ª“áÑ"Z&®25Y
-² ÇÒ‹¶ŽŽŽNMWWW·¦GÓk£OӯРÚÒ kF4£š1͸fBch&5SE¦‹È_ñOø >Œgaöñx=¼6~H™xOîmŽ-ùö%‘bH%$ ~;^¿ ?ÄzÿéJX?A~…ü ‰ "‰# %‰& hORIdIpI|{vHIfáõ$ûvïa9 ËQØýC™C8êœôNÍV©HÒ*<§Ô ¥>%7Q™èK”%š5¡øÍÌÌøýþÙÙÙ@  C¡P8žŸŸ_XXˆD"ÑhÔ4Íx<žH$’Éd*•Êd2Ùl6ŸÏ/iVVVVWW×ÖÖ666677·¶¶¶··w4WÀ?á3ø$Á³0³ø.¼
-)³ø™HC¤$Ò_„´Å¯F:#µ‘æøv¤?yA¾ˆs°œ›xñâ,á‹ä-¥¿K‚‹œßfÍâçC§H)0H@K}o¦ô¬²zýúõ7nܼyóÖ­[·oß¾sçÎÝ»w?þøãO>ùäÓO?ýì³Ïööö>×ìïï?|øðÑ£GOž<yúôé ÜÁ?á3ø<‡5Çá{ñx%¼Þ¿)€†$’‚tò¸ýØøS.ðI”[¤<Riˆ$âçã·ËÇëágââ…ñÚxùgš/¿üâçà×á&~)þ¿Z~>Ò¿i‚‘>ø™0‚CºÁ Ò)i©þM»È©$m–£úõZ[g…K=[VÉÖM k—¹(]²ŠàóR·â+¤àáO|‹È߈o·d‹¤8ª>—öõIÚvÁ"ðëð[ðCððñVóy+ÜÁ¿âŸð£ì?G~Q­°ŠÚÖj›8Uµ^kë¬pl ‘ðkkÏŠØ_s«V®¥¼Ië_ïpû(±¢Y”^XÆ¿â‡Øwloر=?ÒׄdÁ/Å+á뤆…ˆðžø±Ö+áBRC¹xsËé½æ–¬%=¶g_'Ôì+äÍìU–²$¥Èî½-pÿ„2  ÆxQ¿ $£pZUí±ßr’>[©d¡q6 šƒ‚pïv4Ť9#´ý»ê¾gدµuV¼æd¬uPP!R„‚¢n‚Š¥U{µ;Ú"OY•¬„¾¨j!Xýž¦ñZ[g…×éZ{ Æ d0Ñ&‚Cé)k¸Œ »+WúŠ!Rø„”_„G(Ø—Åkm^§kMYI;Ú{¢ŽUë±3
-¯S‚Úà¨fepV:¡2©d=ƒÏý¬:ûò2ÜCÍR5+=Òe(érb¦œÓê¢\X&­ ÷P³„T“fe *CX‹ŠÒE­r¶\8)`MZé:–!Zj–*¨¨Yè¾ 2„µrj³“Zåäz¹pR®œX&mww75KÈipÑ,ô•!¬EEé¢Và÷ûåÂI¹° á±t˦‹Ô,!UPQ³Ð”tCe²í¿‹Zœ4í¢\X€醒áj–ê8ªY¨É:DÎÈCEé¢VäÂI¹°
-‚Á \8)`MZé:nmm¥f ©ŽŠš•è *CX+'Ù9©„B!¹pR.,Àš´ýýý¢Ù‹/R³„T‹f¡/9ó¥‹ZA8– 'åÂì I;00ÐÕÕÕÖÖFÍR5 MÉvPÂZT”.jóóórá¤\X€4iÑ@–áj–ꨨYè‘(„µ¨(]Ô
-äÂI¹°
-‰„\8)`MÚ±±±j–ªqÒ,”}AekQQº¨$“I¹pR.,Àš´ÒuÜÕÕEÍRe‚…Ž &hJ:¡2„µ¨(]Ô
-R©”\8)`MÚ‰‰ ¢½té5KHTÔ¬ ÎB_PÂZT”.j™LF.œ” °ƒ&­aCCC===Ô,!ÕQQ³Ð”}AekQQº¨d³Y¹pR.,Àš´“““2ÜCÍRNš…² /¨ a-*Jµ‚|>/NÊ…ØA“vjjjdd„š%¤jŽjj‚¦ ,è *CX‹ŠÒE­`iiI.œ” °ƒ&íôôôèèh___SS5KH8iÊ‚¾ 2„µ¨(]ÔjÇI¹°
-eA_P™Æ.jÛÛÛrá¤\ ѤŸŸŸ™™Í–³åuJR8iÊ‚¾ 2„µ¨(]Ô
-vvväÂI¹°
-eA_PÂZT”.j7nÜ 'å‚„ljD"ŒŒ´µµ•³åuJR8iÊ‚¾ 2„µ¨(]Ô
-nÞ¼)NÊ…صd29??OÍR55‹ØÊ’Nc„µV [Q­àÖ­[rá¤\X€4iS©ÔÂÂÂèè¨Ïç£f ©ÍB_PÂZwµ‚Û·oË…‹raMÚL&‰D¨YBª¦L³r(bWÔ†ÐT&±‹ZÁ;wäÂI¹£I›Íf£ÑèØØ5KHu¸hú‚ÊÖ¢¢tQ+¸{÷®\8)`MÚ|>OÍrŽjjBìŠú‚ʤ’uQ+øøãåÂI¹RÕ¢I‹²išãããíííeÇYz„Ô.š•(h ¥‹ZÁ'Ÿ|"NÊ…iÒR³„œ’ŠšEìŠú‚ÊÖ¢¢tQ+øôÓOåÂI¹°
->ÿüs¹pR.,À¬mllP³„œ»`¡ Ñ,bWD°ÒiŒ°¥‹Zí8)`MÚÍÍÍd2999yôZ¯S‚ÚÀE³ÐT†°¥»Z÷÷÷Ý• °ƒ&íÖÖ5KÈi(Ó,t„¨±+"Xè *CX‹ŠÒE­àáÇrá¤\X€4i···S©ÔÔÔ5KHu¸hú‚ÊÖ¢¢tQ+xôè‘\8)`MÚj–ÓpT³PbWD°ÐT†°¥‹ZÁ“'OäÂI¹°
-ž={&NÊ•ðMZj–SR¦YèQ+bWD°Ð,T†°¥‹ZÁ—_~)NÊ…ØA“öúõëù|Þï÷÷ôô”géuJR¸k*“JÖE­àÅ‹rá¤\©jѤ¥f 9%vÍBAÐ,¢VÄ®ˆ`ÇBeÐ*Jµ‚o¾ùF.œ” Ò¤½qãÆÒÒÒììloo/5KH¸k*CX‹ŠÒE­àÛo¿• 'åÂì IKÍrJÊ4 !jEìŠq,T†°¥‹ZÁwß}'NÊ…ØA“öæÍ›Ðl €f›››íÛ’{„Ôîš…ÊÖ¢¢tQ+øÙÏ~&NÊ… ©YBNÉQÍBMˆ]¡,ıPÂZT”.j?ÿùÏåÂI¹°
-~ùË_Ê…“ravФ½}ûöêêj(êïï§f ©‚c5‹°¥‹ZÁ?üÃ?È…“rav¨YBNO™f¡#D­ˆ]Á"Ž…ÊÖ¢¢tQ+øþûïåÂI¹°
-þú׿ÊE™r©YB^9vÍBAТVÄ®ˆ`¡Y¨ Zƒâì‚-S+ø÷ÿw¹(S®%Û?þñ¿ùÍo~ö³Ÿ=|øðÆ™LÆ0Œ®®®¦¦&j–—¢:ÍÚÕ
-þã?þC.,åR³„œ–f¡Ñ,¢VÄ®ˆ`ÇBeÐwT°–ZÁþçÊ…¥Ü2ÙþéOúíoûóŸÿüÑ£G7oÞÌf³“““ÝÝÝЬµìÝë” ¤68fE­à¿ÿû¿åBdKÍrvX* Y(ñ*¢VÄ®ˆ`ÇBeÐç$Øÿ>Œ“lÿüç?ÿîw¿ûÅ/~ñäÉ“[·nåóù©©©žžžææfj–—â•höþç¨YB^ešE¼Š¨±+"XıP´Ź öÿ÷Ýe‹ðø÷¿ÿý/ù˧OŸÞ¾}{iiizzº···¥¥…š%ä¥`=KHmÁö,!µû ©-8>KHmÁyP„ÔœoLHmÁu=„Ô\?KHmQ¦YîSAÈÎÉ5Ëý y°4Ë} © N¨YîoLÈ‚]³<G€7Ÿj–çõò†pT³<7™“h–çÏòæ`×,Ïy'äÍç$šýá‡~­qQ®“ZåAX f y%”i¶µµšŸŸßØظ{÷îÓ§OþóŸÿý÷¿Ò¸(×I­ò ,@ûð
--ÂÚM»r+ªU”À
-aí ÆE¹.jK§1¼ìS³„TGEÍ^¸pš•áhMÂc‹ŠÊuR«
-° PN=Ô,!'Ä]³Ò ŵi\”ë¤VyP³.=Ô,!'ÄI³ÖpZ©jU®“Z¥’•Æ,5KÈ)qѬÕuliÖE¹Nj=ªÙŠYj–â®Yé†BXÛ¢qW®“ZÆ.ÆÔ,!'ä$š•ªÖ¢¢rÔ*À‚t@Q³„œÍZÝPP\³ÆE¹Nj•%0¦f 9=Nš}ÇÖu,Uí±ÊuR«T²V5KÈix)ͺ(×I­G5ëôu^§!µ‹f­&­ÈöXå:©U ;Ô,!§ç¥4ë¢\'µÚ5ëÒ˜¥f 9!ÇjVÂcÁE¹.j`‡š%äô¸kVš´RÕ«\'µZ15KÈé©B³•ë¤V»f]:©YBNˆ‹fß±…ÇmTÔoEZH%ëÒEÍrBŽÕ,„&²µpÑo™N-D°.•,5KÈ q׬]¶e\pæè‡E°Ô,!§ç$šÙ–qT˜v…–q¬`©YBNȱšµ+× K•Nœä+¼N BjƒjöXEŸÞˆ×)AHmpz­½*¼N Bj¯•ZÂë” ¤6ðZ©%¼N Bj¯•ZÂë” ¤6ðZ©%¼N B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!gÈ;„B!„B!„B!„B!„B!„r
-¼žíL»êaÚUÓ®z˜vÕô«¦]õüÿöî¶'Žr àøZ› ¦±IŒ½!Yô0Ìóƒ}a ¨g{l!´Fb£›“íÒ,CßùÂàçð+z®Ùaa–‡V¯H§µÿŸa]î½÷?7Ën£ÚéÑNvz´Ó£íôh§G;=ÚéÑNvz´Óû»Ú½
-çǧÙîïY+´Ó^éÜ˞̟}&Ÿý˜o”4~‡Úšseó¾¬ÉЋ†ëLV˜kV¬rZŽwåÊÿdèã:æžyô­cº2¼±.;–ë§r ½òОg‡®Ë
-sý$²]ǫʘs¼el'IŒAµÝ–U ][¾Þûxa4ÚãÉcÆÊ{§©í‡QPß2p‚3{¬Õ»6z2Ë“=ž<›³Ï{ËÚ¾`AÖ¿7‹÷÷Šõ|koÐÍ»ÃâØNè%Us¹ê‡&ñÜÄöå§ÈÄ¡ìÝsƒÀ,®çYï^& ãG¹iÝYj1Ⱥ»y¿x¸½7x\ÝtÎbù<({²ŽgqêÅG^o–7-çè¨/oy¦õU¿Ÿ=– î=†‰Üù£ Ü£2º\Þ‘û./¿f9²ÆŽM`AèWë"¶ýDžìɨŒùòC§åXùü£hlLŽMxQµ.ŽGýÔö¼jËã=ú”ÍÆŽÍÑØp]$¶Çn}ËÀñìÀõÜúkcµÇ®žÌòd'ÏæìóÞ:U×}NÝ?·†Ý]×N=·zÉ_p=™MšŒÍa´²—Ëâ%V¨¼Ú]ô›ãõÊp`Ü&¯õÃ7<ò&%ÿ¤ººœËo¤£w?rÙæ kq%º»•¯±l6î[ŽïîúòSºN,kΑ·išoMšòŠÙøþ¹›–·U›]¿Ü6Þ-0å^knn8§òwÙ'ÆM’Ø·6Úoÿµÿµ
-žK7.3Y`®Y±Ê%vj5Þ•+ÿ“¡Œë˜{æÑ·ŽéÊðƺ\ìXrŒ<;I‚òØE±xI¹¾äˆÊr ’ú¨ëÇÑhË8°ÓÔñÆÆ¿Ún˪ÊÞ£((ï}²GYG{<yìјÜ;p\;öœ¸¾eà$göX«=vmôx–µ=ž<›³Ï{ËÚ¾`AÖ¿7‹÷÷Šõ|koÐÍ»ÃâØNìUs¹êú&q¢ÐŽ¼ 4±y¶ã{±Y\ϳ޽LÆrÓº³Ôþbuwó~ñp{oð¸ºéœÅ óyPöd ÏâÔk¼Ü,oZÎÑQ_þÞòLë«~?{,Ü9z ¹óG¸ÇçB®.—wä¾ËËÇ/YŽ,¤±c˳òüj]„v†imTŽ¶ülËOO9æÚ^Ù¹>Vw9Ãeq<( $NÜòÎ';ŒååÏ‹ã±C3X‰ëQ}ËÀ‘{§AZßcmìä¡kƒÇs¬íð乜}Ö[§ÚºÏiûçVðºÚIšúÃmÜÈv/›Ãh]/?–¥9<F¬Oy­»è÷Æë•áo=}zØò+ЫNw/]"ó­IS¾M9}úôó6-o«6¯.Ÿút7•·<œ>
-ª“0–'– ü0¬—'8¼ÿÒœ×ÐM¢ ¢Ð_Á3ú—zš+×vÊã5<ÍÕðlŸæêxÓ£Ó\ÉæÕåóOså…®Ëi®
-H‰Ô—KI…÷õ+ri/nv¾KÜ6# ‚n K€¹ø1¨Û#»Ïw²ž}=È6«A–ûVEeDFFœ8yõ›ksõëkgž=¿6ÓÕõ3×7ÆææúÛ)Xß‚ùoßðííÃÔr·ÅSª¡™S‰É„m­É||==›>LÎx×øÓƒ3§ñȇߛ÷ãÓ7²äeéêç÷Î<ÿ~ú-ÿ>L¡ì6S{².6s¿IJuÖ¥fbK69oré6Çl²ë¶—¶½Ÿ§KÉÝTbæi—Ô(·ûfs}_w=O—’»MÒ}³.T³ÚôANasdìžl¢»iu­ç[í^Fà¼ÄŒxÅ+„l{ÌÝGoÿNGÁ²aÍ«z®rä~zöÒ\ݼsæáyñÒ̱þòù9Açãl¯eüú›«1gÔÌì‘7¥G›<ð¨Ýë0].è“þ²•·‘'NêCFÐXŽ÷ËCØ%çéX%ûªXŠm% ­'…õeþZÍfáÂzØ(-®¬·i»aôÍ°·—‘ ;t‘FI£wC«UsµÅ—à]7Í9[È|,UѸúÅ
-û¯IDü±íÊØ.¦Ì~iÛ/W±ŸÿúýҺ߳Û=ûãßÈøÕí­Ò}ûf¢ˆ$å'ùb»óÉ´.]ˆæö~zrk^™;óÚ|4Ooÿ6½¸ýœŸ:<YˆiñÀÏ8Û|î&E›‹)ØÞC Íåh¢„ª*Ã.çÙ+9“,Ä•¥‹oC±ö’L²žÅ(f8¢lK‹æ+i6ë=E¡¹†fHÍ
-Ùå‚f¡
-z0M¥Ý`FiþKšÞ[ŽÍ¦üÊ™*aÇZ²718©4Ó ¿o‰3¹Ô``ê¾£è¬/´gÜO¸ð»ö–½mÍÏáÿËÐmœ<%»M­sÒØ(áÔA=6ŸH°´{ƒ'Ž$Ý;éo]¡ÐQQ*9;¤ïˆ–Çá˜ÄØ+,µÝ×C—”€‹-. B­ÏxÆØŽ}ûV©xÃG¤;XZËdkÑM6Öä ¤×¢Î›Š%E¨rºæÿl¨B…ØFQŒxâÁw|È]Ç-ÞfG0E½©¶yÛ÷Ҹئԣ(ĺÚ] ü:.§kKâ꜡ï‡j¶¥Æ.¨F—šlId`ƒkj`ÝÆ„
->©ã® Áp»ä†Ç<ÄZˆ­ÇjÒgYඦ24ÿ=0
-7ªàÄCîä‰\—B@Á!t†¿Ç›ŸÃt?T~‚—3ª^…à…M¼D5U›s(Ò·PîgU°V)!ñp8¢
-"¹XL‚‰}€zÃf›ýýÇP¤Aj†'^;¯ª¤ tR#V EÜ >,š9s úNTÍñF vÔ…¬ø ÇiÇ‘6ìr1(÷d‡<êŒè<%…°¥*ÍÞçtþjQô1à"GIÀ»Pu(2LT“AЛ=ÍXŠß-±ÉÀÄœºH€ÈÛÀz<@6L¤”.Ú›x§ù½LY’|sòl]! ¯Ã’ÌM>²9fŨs‰\p‰øœÿ
-(3õš|š³ô÷”]¼&û‚ÍâÃÀdmã„êd´Ë¬P¥Òa2µ6<U8ñœ˜Œ
-Jn Äi‚îΆûLåŒHöË̈Y]YÃ8‡ŽÄîBµ ÝÙ´·à‰Ö¡Î”**Ts»f¼Bo ‘,/öŒ~¯]šL , ìR25ÌRæ*u9n8t, ÿ%Ñ~Š
-Íõ=ÈôƒJùªÉ”>T›¶×·a¢¤.Œ`ëÝÅphv|©j²P‡‚ªŽUÄ~Aµ é
-´Pp¢ýSYºÝï–Fé4@ð¡ú4údAÒ†˜ uQ«šlñöH­†GýÙÇ®¦FçòcÀVo¯ªÄìàs~tÝ—“s;kLQè”Ë6Fq2æp öÈŠOÇ–<´ jìJà€é˜dZgÅIUëèaQê#ê'¾î#–6ê3•-ðô*4>0 ¡ËCÎ<èžÚçÖóOéŠÝH:e¼hGý¹Ù(Ä”UõÊJ™ïˆ-$¿{<ØÆëWI°® ¬ÞEØÐÄîJFko˜W,‚óô‰èN¢ª&³«Q& ڹÝD°}#»aÆ–Çþ,‚£?‹Hþ¬ÇØÔÖƒn†/‚!;´‰PóE¨ìþ(Òí4H­3q"
-Å×gãbqQƒýQJæö9y=jg‚,ªä&dìžÌ.“…ó @L4cW×’xdâå–H7EðâÔ¢êéÈòÉRåìBÝ ü¹¶E+¡výo;YÖªy‚Vˆò,£˜¹§/ì¾ô~[W˜4»ã´{FAj.£“@‰øµA‹Ò'3%øw:ÛÑ
-Þ($‘Á¾Ñû¢•
-ùÒµTiŒÁó$²bÄæ†Æ†¼mw±òo±
-¹ârÕ ªïGt406܈FúµAê,[kADÙRo$Dú…öT;$OYgOV (&ÖEnwv¶dÎ ½ Â$‚š}Ü,:ý錛3IÞèÍnéfï@°™Ž”ê aÐ í7!¸ª§3¥s‘C`‡ž7w ØÝ1H.Å+ã‚
-o¥„G¯±h~w7|ã*«.eU!ÉFd:r'Kµõî~ICq$˜ûPUžÓ#¬²)P›ê¬Á
-q
-/GS0 gs
-486 0 0 480 273.6162109 546.6401367 cm
-/Im0 Do
-Q
- endstream endobj 757 0 obj <</BBox[317.616 983.64 715.616 585.64]/Group 915 0 R/Length 59/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 916 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 793 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-398 0 0 398 317.6162109 585.6401367 cm
-/Im0 Do
-Q
- endstream endobj 758 0 obj <</BBox[385.616 858.64 653.616 814.64]/Group 917 0 R/Length 58/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 918 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 796 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-268 0 0 44 385.6162109 814.6401367 cm
-/Im0 Do
-Q
- endstream endobj 759 0 obj <</BBox[382.616 888.64 656.616 585.64]/Group 919 0 R/Length 59/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 920 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 799 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-274 0 0 303 382.6162109 585.6401367 cm
-/Im0 Do
-Q
- endstream endobj 760 0 obj <</BBox[408.0 947.0 704.0 856.0]/Group 921 0 R/Length 42/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 922 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 802 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-296 0 0 91 408 856 cm
-/Im0 Do
-Q
- endstream endobj 761 0 obj <</BBox[246.0 1106.0 718.0 580.0]/Group 923 0 R/Length 43/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 924 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 805 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-472 0 0 526 246 580 cm
-/Im0 Do
-Q
- endstream endobj 762 0 obj <</BBox[283.0 1022.0 796.0 520.0]/Group 925 0 R/Length 43/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 926 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 808 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-513 0 0 502 283 520 cm
-/Im0 Do
-Q
- endstream endobj 925 0 obj <</I false/K false/S/Transparency/Type/Group>> endobj 926 0 obj <</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 927 0 R/Type/ExtGState/ca 1.0/op false>> endobj 927 0 obj <</BC 928 0 R/G 929 0 R/S/Luminosity/Type/Mask>> endobj 928 0 obj [0.0 0.0 0.0] endobj 929 0 obj <</BBox[283.0 1022.0 796.0 520.0]/Group 930 0 R/Length 43/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 833 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-513 0 0 502 283 520 cm
-/Im0 Do
-Q
- endstream endobj 930 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 923 0 obj <</I false/K false/S/Transparency/Type/Group>> endobj 924 0 obj <</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 931 0 R/Type/ExtGState/ca 1.0/op false>> endobj 931 0 obj <</BC 932 0 R/G 933 0 R/S/Luminosity/Type/Mask>> endobj 932 0 obj [0.0 0.0 0.0] endobj 933 0 obj <</BBox[246.0 1106.0 718.0 580.0]/Group 934 0 R/Length 43/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 839 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-472 0 0 526 246 580 cm
-/Im0 Do
-Q
- endstream endobj 934 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 921 0 obj <</I false/K false/S/Transparency/Type/Group>> endobj 922 0 obj <</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 935 0 R/Type/ExtGState/ca 1.0/op false>> endobj 935 0 obj <</BC 936 0 R/G 937 0 R/S/Luminosity/Type/Mask>> endobj 936 0 obj [0.0 0.0 0.0] endobj 937 0 obj <</BBox[408.0 947.0 704.0 856.0]/Group 938 0 R/Length 42/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 845 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-296 0 0 91 408 856 cm
-/Im0 Do
-Q
- endstream endobj 938 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 919 0 obj <</I false/K false/S/Transparency/Type/Group>> endobj 920 0 obj <</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 939 0 R/Type/ExtGState/ca 1.0/op false>> endobj 939 0 obj <</BC 940 0 R/G 941 0 R/S/Luminosity/Type/Mask>> endobj 940 0 obj [0.0 0.0 0.0] endobj 941 0 obj <</BBox[382.616 888.64 656.616 585.64]/Group 942 0 R/Length 59/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 851 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-274 0 0 303 382.6162109 585.6401367 cm
-/Im0 Do
-Q
- endstream endobj 942 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 917 0 obj <</I false/K false/S/Transparency/Type/Group>> endobj 918 0 obj <</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 943 0 R/Type/ExtGState/ca 1.0/op false>> endobj 943 0 obj <</BC 944 0 R/G 945 0 R/S/Luminosity/Type/Mask>> endobj 944 0 obj [0.0 0.0 0.0] endobj 945 0 obj <</BBox[385.616 858.64 653.616 814.64]/Group 946 0 R/Length 58/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 857 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-268 0 0 44 385.6162109 814.6401367 cm
-/Im0 Do
-Q
- endstream endobj 946 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 915 0 obj <</I false/K false/S/Transparency/Type/Group>> endobj 916 0 obj <</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 947 0 R/Type/ExtGState/ca 1.0/op false>> endobj 947 0 obj <</BC 948 0 R/G 949 0 R/S/Luminosity/Type/Mask>> endobj 948 0 obj [0.0 0.0 0.0] endobj 949 0 obj <</BBox[317.616 983.64 715.616 585.64]/Group 950 0 R/Length 59/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 863 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-398 0 0 398 317.6162109 585.6401367 cm
-/Im0 Do
-Q
- endstream endobj 950 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 913 0 obj <</I false/K false/S/Transparency/Type/Group>> endobj 914 0 obj <</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 951 0 R/Type/ExtGState/ca 1.0/op false>> endobj 951 0 obj <</BC 952 0 R/G 953 0 R/S/Luminosity/Type/Mask>> endobj 952 0 obj [0.0 0.0 0.0] endobj 953 0 obj <</BBox[273.616 1026.64 759.616 546.64]/Group 954 0 R/Length 59/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 869 0 R>>>>/Subtype/Form>>stream
-q
-/GS0 gs
-486 0 0 480 273.6162109 546.6401367 cm
-/Im0 Do
-Q
- endstream endobj 954 0 obj <</CS 735 0 R/I false/K false/S/Transparency/Type/Group>> endobj 746 0 obj <</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 955 0 R/Type/ExtGState/ca 1.0/op false>> endobj 748 0 obj <</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 956 0 R/Type/ExtGState/ca 1.0/op false>> endobj 749 0 obj <</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 957 0 R/Type/ExtGState/ca 1.0/op false>> endobj 750 0 obj <</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 958 0 R/Type/ExtGState/ca 1.0/op false>> endobj 751 0 obj <</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 959 0 R/Type/ExtGState/ca 1.0/op false>> endobj 959 0 obj <</G 960 0 R/S/Luminosity/Type/Mask>> endobj 960 0 obj <</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 961 0 R/Length 84/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
-q
-0 g
-/GS0 gs
-15.8993082 0 0 -16.1025677 1392.8847656 296.171875 cm
-BX /Sh0 sh EX Q
- endstream endobj 961 0 obj <</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>> endobj 958 0 obj <</G 962 0 R/S/Luminosity/Type/Mask>> endobj 962 0 obj <</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 963 0 R/Length 85/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
-q
-0 g
-/GS0 gs
-11.9217148 0 0 -12.0797682 1392.7294922 335.4882812 cm
-BX /Sh0 sh EX Q
- endstream endobj 963 0 obj <</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>> endobj 957 0 obj <</G 964 0 R/S/Luminosity/Type/Mask>> endobj 964 0 obj <</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 965 0 R/Length 87/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
-q
-0 g
-/GS0 gs
-225.9361572 0 0 -225.9361572 1389.9453125 728.7958984 cm
-BX /Sh0 sh EX Q
- endstream endobj 965 0 obj <</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>> endobj 956 0 obj <</G 966 0 R/S/Luminosity/Type/Mask>> endobj 966 0 obj <</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 967 0 R/Length 83/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
-q
-0 g
-/GS0 gs
-7.9551892 0 0 -8.0455885 1392.7294922 366.4384766 cm
-BX /Sh0 sh EX Q
- endstream endobj 967 0 obj <</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>> endobj 955 0 obj <</G 968 0 R/S/Luminosity/Type/Mask>> endobj 968 0 obj <</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 969 0 R/Length 84/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
-q
-0 g
-/GS0 gs
-225.9359131 0 0 -225.9359131 509.96875 794.0385742 cm
-BX /Sh0 sh EX Q
- endstream endobj 969 0 obj <</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>> endobj 912 0 obj [/ICCBased 819 0 R] endobj 5 0 obj <</Intent 154 0 R/Name(Calque 1)/Type/OCG/Usage 155 0 R>> endobj 248 0 obj <</Intent 395 0 R/Name(Calque 1)/Type/OCG/Usage 396 0 R>> endobj 489 0 obj <</Intent 636 0 R/Name(Calque 1)/Type/OCG/Usage 637 0 R>> endobj 636 0 obj [/View/Design] endobj 637 0 obj <</CreatorInfo<</Creator(Adobe Illustrator 16.0)/Subtype/Artwork>>>> endobj 395 0 obj [/View/Design] endobj 396 0 obj <</CreatorInfo<</Creator(Adobe Illustrator 16.0)/Subtype/Artwork>>>> endobj 154 0 obj [/View/Design] endobj 155 0 obj <</CreatorInfo<</Creator(Adobe Illustrator 16.0)/Subtype/Artwork>>>> endobj 731 0 obj [730 0 R] endobj 970 0 obj <</CreationDate(D:20140916123631+02'00')/Creator(Adobe Illustrator CS6 \(Macintosh\))/ModDate(D:20140918115258+02'00')/Producer(Adobe PDF library 10.01)/Title(logotalerv2)>> endobj xref 0 971 0000000004 65535 f
-0000000016 00000 n
-0000000194 00000 n
-0000050669 00000 n
-0000000006 00000 f
-0000539738 00000 n
-0000000009 00000 f
-0000050726 00000 n
-0000051599 00000 n
-0000000010 00000 f
-0000000011 00000 f
-0000000012 00000 f
-0000000013 00000 f
-0000000014 00000 f
-0000000015 00000 f
-0000000016 00000 f
-0000000017 00000 f
-0000000018 00000 f
-0000000019 00000 f
-0000000020 00000 f
-0000000021 00000 f
-0000000022 00000 f
-0000000023 00000 f
-0000000024 00000 f
-0000000025 00000 f
-0000000026 00000 f
-0000000027 00000 f
-0000000028 00000 f
-0000000029 00000 f
-0000000030 00000 f
-0000000031 00000 f
-0000000032 00000 f
-0000000033 00000 f
-0000000034 00000 f
-0000000035 00000 f
-0000000036 00000 f
-0000000037 00000 f
-0000000038 00000 f
-0000000039 00000 f
-0000000040 00000 f
-0000000041 00000 f
-0000000042 00000 f
-0000000043 00000 f
-0000000044 00000 f
-0000000045 00000 f
-0000000046 00000 f
-0000000047 00000 f
-0000000048 00000 f
-0000000049 00000 f
-0000000050 00000 f
-0000000051 00000 f
-0000000052 00000 f
-0000000053 00000 f
-0000000054 00000 f
-0000000055 00000 f
-0000000056 00000 f
-0000000057 00000 f
-0000000058 00000 f
-0000000059 00000 f
-0000000060 00000 f
-0000000061 00000 f
-0000000062 00000 f
-0000000063 00000 f
-0000000064 00000 f
-0000000065 00000 f
-0000000066 00000 f
-0000000067 00000 f
-0000000068 00000 f
-0000000069 00000 f
-0000000070 00000 f
-0000000071 00000 f
-0000000072 00000 f
-0000000073 00000 f
-0000000074 00000 f
-0000000075 00000 f
-0000000076 00000 f
-0000000077 00000 f
-0000000078 00000 f
-0000000079 00000 f
-0000000080 00000 f
-0000000081 00000 f
-0000000082 00000 f
-0000000083 00000 f
-0000000084 00000 f
-0000000085 00000 f
-0000000086 00000 f
-0000000087 00000 f
-0000000088 00000 f
-0000000089 00000 f
-0000000090 00000 f
-0000000091 00000 f
-0000000092 00000 f
-0000000093 00000 f
-0000000094 00000 f
-0000000095 00000 f
-0000000096 00000 f
-0000000097 00000 f
-0000000098 00000 f
-0000000099 00000 f
-0000000100 00000 f
-0000000101 00000 f
-0000000102 00000 f
-0000000103 00000 f
-0000000104 00000 f
-0000000105 00000 f
-0000000106 00000 f
-0000000107 00000 f
-0000000108 00000 f
-0000000109 00000 f
-0000000110 00000 f
-0000000111 00000 f
-0000000112 00000 f
-0000000113 00000 f
-0000000114 00000 f
-0000000115 00000 f
-0000000116 00000 f
-0000000117 00000 f
-0000000118 00000 f
-0000000119 00000 f
-0000000120 00000 f
-0000000121 00000 f
-0000000122 00000 f
-0000000123 00000 f
-0000000124 00000 f
-0000000125 00000 f
-0000000126 00000 f
-0000000127 00000 f
-0000000128 00000 f
-0000000129 00000 f
-0000000130 00000 f
-0000000131 00000 f
-0000000132 00000 f
-0000000133 00000 f
-0000000134 00000 f
-0000000135 00000 f
-0000000136 00000 f
-0000000137 00000 f
-0000000138 00000 f
-0000000139 00000 f
-0000000140 00000 f
-0000000141 00000 f
-0000000142 00000 f
-0000000143 00000 f
-0000000144 00000 f
-0000000145 00000 f
-0000000146 00000 f
-0000000147 00000 f
-0000000148 00000 f
-0000000149 00000 f
-0000000150 00000 f
-0000000151 00000 f
-0000000152 00000 f
-0000000153 00000 f
-0000000156 00000 f
-0000540197 00000 n
-0000540229 00000 n
-0000000157 00000 f
-0000000158 00000 f
-0000000159 00000 f
-0000000160 00000 f
-0000000161 00000 f
-0000000162 00000 f
-0000000163 00000 f
-0000000164 00000 f
-0000000165 00000 f
-0000000166 00000 f
-0000000167 00000 f
-0000000168 00000 f
-0000000169 00000 f
-0000000170 00000 f
-0000000171 00000 f
-0000000172 00000 f
-0000000173 00000 f
-0000000174 00000 f
-0000000175 00000 f
-0000000176 00000 f
-0000000177 00000 f
-0000000178 00000 f
-0000000179 00000 f
-0000000180 00000 f
-0000000181 00000 f
-0000000182 00000 f
-0000000183 00000 f
-0000000184 00000 f
-0000000185 00000 f
-0000000186 00000 f
-0000000187 00000 f
-0000000188 00000 f
-0000000189 00000 f
-0000000190 00000 f
-0000000191 00000 f
-0000000192 00000 f
-0000000193 00000 f
-0000000194 00000 f
-0000000195 00000 f
-0000000196 00000 f
-0000000197 00000 f
-0000000198 00000 f
-0000000199 00000 f
-0000000200 00000 f
-0000000201 00000 f
-0000000202 00000 f
-0000000203 00000 f
-0000000204 00000 f
-0000000205 00000 f
-0000000206 00000 f
-0000000207 00000 f
-0000000208 00000 f
-0000000209 00000 f
-0000000210 00000 f
-0000000211 00000 f
-0000000212 00000 f
-0000000213 00000 f
-0000000214 00000 f
-0000000215 00000 f
-0000000216 00000 f
-0000000217 00000 f
-0000000218 00000 f
-0000000219 00000 f
-0000000220 00000 f
-0000000221 00000 f
-0000000222 00000 f
-0000000223 00000 f
-0000000224 00000 f
-0000000225 00000 f
-0000000226 00000 f
-0000000227 00000 f
-0000000228 00000 f
-0000000229 00000 f
-0000000230 00000 f
-0000000231 00000 f
-0000000232 00000 f
-0000000233 00000 f
-0000000234 00000 f
-0000000235 00000 f
-0000000236 00000 f
-0000000237 00000 f
-0000000238 00000 f
-0000000239 00000 f
-0000000240 00000 f
-0000000241 00000 f
-0000000242 00000 f
-0000000243 00000 f
-0000000244 00000 f
-0000000245 00000 f
-0000000246 00000 f
-0000000247 00000 f
-0000000249 00000 f
-0000539811 00000 n
-0000000250 00000 f
-0000000251 00000 f
-0000000252 00000 f
-0000000253 00000 f
-0000000254 00000 f
-0000000255 00000 f
-0000000256 00000 f
-0000000257 00000 f
-0000000258 00000 f
-0000000259 00000 f
-0000000260 00000 f
-0000000261 00000 f
-0000000262 00000 f
-0000000263 00000 f
-0000000264 00000 f
-0000000265 00000 f
-0000000266 00000 f
-0000000267 00000 f
-0000000268 00000 f
-0000000269 00000 f
-0000000270 00000 f
-0000000271 00000 f
-0000000272 00000 f
-0000000273 00000 f
-0000000274 00000 f
-0000000275 00000 f
-0000000276 00000 f
-0000000277 00000 f
-0000000278 00000 f
-0000000279 00000 f
-0000000280 00000 f
-0000000281 00000 f
-0000000282 00000 f
-0000000283 00000 f
-0000000284 00000 f
-0000000285 00000 f
-0000000286 00000 f
-0000000287 00000 f
-0000000288 00000 f
-0000000289 00000 f
-0000000290 00000 f
-0000000291 00000 f
-0000000292 00000 f
-0000000293 00000 f
-0000000294 00000 f
-0000000295 00000 f
-0000000296 00000 f
-0000000297 00000 f
-0000000298 00000 f
-0000000299 00000 f
-0000000300 00000 f
-0000000301 00000 f
-0000000302 00000 f
-0000000303 00000 f
-0000000304 00000 f
-0000000305 00000 f
-0000000306 00000 f
-0000000307 00000 f
-0000000308 00000 f
-0000000309 00000 f
-0000000310 00000 f
-0000000311 00000 f
-0000000312 00000 f
-0000000313 00000 f
-0000000314 00000 f
-0000000315 00000 f
-0000000316 00000 f
-0000000317 00000 f
-0000000318 00000 f
-0000000319 00000 f
-0000000320 00000 f
-0000000321 00000 f
-0000000322 00000 f
-0000000323 00000 f
-0000000324 00000 f
-0000000325 00000 f
-0000000326 00000 f
-0000000327 00000 f
-0000000328 00000 f
-0000000329 00000 f
-0000000330 00000 f
-0000000331 00000 f
-0000000332 00000 f
-0000000333 00000 f
-0000000334 00000 f
-0000000335 00000 f
-0000000336 00000 f
-0000000337 00000 f
-0000000338 00000 f
-0000000339 00000 f
-0000000340 00000 f
-0000000341 00000 f
-0000000342 00000 f
-0000000343 00000 f
-0000000344 00000 f
-0000000345 00000 f
-0000000346 00000 f
-0000000347 00000 f
-0000000348 00000 f
-0000000349 00000 f
-0000000350 00000 f
-0000000351 00000 f
-0000000352 00000 f
-0000000353 00000 f
-0000000354 00000 f
-0000000355 00000 f
-0000000356 00000 f
-0000000357 00000 f
-0000000358 00000 f
-0000000359 00000 f
-0000000360 00000 f
-0000000361 00000 f
-0000000362 00000 f
-0000000363 00000 f
-0000000364 00000 f
-0000000365 00000 f
-0000000366 00000 f
-0000000367 00000 f
-0000000368 00000 f
-0000000369 00000 f
-0000000370 00000 f
-0000000371 00000 f
-0000000372 00000 f
-0000000373 00000 f
-0000000374 00000 f
-0000000375 00000 f
-0000000376 00000 f
-0000000377 00000 f
-0000000378 00000 f
-0000000379 00000 f
-0000000380 00000 f
-0000000381 00000 f
-0000000382 00000 f
-0000000383 00000 f
-0000000384 00000 f
-0000000385 00000 f
-0000000386 00000 f
-0000000387 00000 f
-0000000388 00000 f
-0000000389 00000 f
-0000000390 00000 f
-0000000391 00000 f
-0000000392 00000 f
-0000000393 00000 f
-0000000394 00000 f
-0000000397 00000 f
-0000540079 00000 n
-0000540111 00000 n
-0000000398 00000 f
-0000000399 00000 f
-0000000400 00000 f
-0000000401 00000 f
-0000000402 00000 f
-0000000403 00000 f
-0000000404 00000 f
-0000000405 00000 f
-0000000406 00000 f
-0000000407 00000 f
-0000000408 00000 f
-0000000409 00000 f
-0000000410 00000 f
-0000000411 00000 f
-0000000412 00000 f
-0000000413 00000 f
-0000000414 00000 f
-0000000415 00000 f
-0000000416 00000 f
-0000000417 00000 f
-0000000418 00000 f
-0000000419 00000 f
-0000000420 00000 f
-0000000421 00000 f
-0000000422 00000 f
-0000000423 00000 f
-0000000424 00000 f
-0000000425 00000 f
-0000000426 00000 f
-0000000427 00000 f
-0000000428 00000 f
-0000000429 00000 f
-0000000430 00000 f
-0000000431 00000 f
-0000000432 00000 f
-0000000433 00000 f
-0000000434 00000 f
-0000000435 00000 f
-0000000436 00000 f
-0000000437 00000 f
-0000000438 00000 f
-0000000439 00000 f
-0000000440 00000 f
-0000000441 00000 f
-0000000442 00000 f
-0000000443 00000 f
-0000000444 00000 f
-0000000445 00000 f
-0000000446 00000 f
-0000000447 00000 f
-0000000448 00000 f
-0000000449 00000 f
-0000000450 00000 f
-0000000451 00000 f
-0000000452 00000 f
-0000000453 00000 f
-0000000454 00000 f
-0000000455 00000 f
-0000000456 00000 f
-0000000457 00000 f
-0000000458 00000 f
-0000000459 00000 f
-0000000460 00000 f
-0000000461 00000 f
-0000000462 00000 f
-0000000463 00000 f
-0000000464 00000 f
-0000000465 00000 f
-0000000466 00000 f
-0000000467 00000 f
-0000000468 00000 f
-0000000469 00000 f
-0000000470 00000 f
-0000000471 00000 f
-0000000472 00000 f
-0000000473 00000 f
-0000000474 00000 f
-0000000475 00000 f
-0000000476 00000 f
-0000000477 00000 f
-0000000478 00000 f
-0000000479 00000 f
-0000000480 00000 f
-0000000481 00000 f
-0000000482 00000 f
-0000000483 00000 f
-0000000484 00000 f
-0000000485 00000 f
-0000000486 00000 f
-0000000487 00000 f
-0000000488 00000 f
-0000000000 00000 f
-0000539886 00000 n
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000539961 00000 n
-0000539993 00000 n
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000000000 00000 f
-0000255776 00000 n
-0000540315 00000 n
-0000526925 00000 n
-0000530281 00000 n
-0000272389 00000 n
-0000072965 00000 n
-0000080180 00000 n
-0000077135 00000 n
-0000078917 00000 n
-0000078454 00000 n
-0000076711 00000 n
-0000076244 00000 n
-0000075807 00000 n
-0000072621 00000 n
-0000268801 00000 n
-0000268915 00000 n
-0000536963 00000 n
-0000100986 00000 n
-0000537080 00000 n
-0000537197 00000 n
-0000537314 00000 n
-0000537431 00000 n
-0000255969 00000 n
-0000257133 00000 n
-0000257476 00000 n
-0000254768 00000 n
-0000530345 00000 n
-0000530669 00000 n
-0000530992 00000 n
-0000531314 00000 n
-0000531637 00000 n
-0000531937 00000 n
-0000532239 00000 n
-0000058286 00000 n
-0000058829 00000 n
-0000064795 00000 n
-0000065059 00000 n
-0000065376 00000 n
-0000071367 00000 n
-0000071636 00000 n
-0000071964 00000 n
-0000072239 00000 n
-0000052478 00000 n
-0000055983 00000 n
-0000269032 00000 n
-0000269149 00000 n
-0000269266 00000 n
-0000269383 00000 n
-0000269500 00000 n
-0000056047 00000 n
-0000056374 00000 n
-0000056701 00000 n
-0000057027 00000 n
-0000057354 00000 n
-0000057664 00000 n
-0000057975 00000 n
-0000526888 00000 n
-0000199803 00000 n
-0000081996 00000 n
-0000228123 00000 n
-0000199867 00000 n
-0000155074 00000 n
-0000178049 00000 n
-0000155138 00000 n
-0000151139 00000 n
-0000153025 00000 n
-0000151203 00000 n
-0000140569 00000 n
-0000146002 00000 n
-0000140633 00000 n
-0000126432 00000 n
-0000133614 00000 n
-0000126496 00000 n
-0000101099 00000 n
-0000114198 00000 n
-0000101163 00000 n
-0000080732 00000 n
-0000091259 00000 n
-0000080796 00000 n
-0000080227 00000 n
-0000079327 00000 n
-0000078963 00000 n
-0000078501 00000 n
-0000077183 00000 n
-0000076758 00000 n
-0000076291 00000 n
-0000075854 00000 n
-0000072668 00000 n
-0000073002 00000 n
-0000073158 00000 n
-0000076140 00000 n
-0000076586 00000 n
-0000077043 00000 n
-0000078037 00000 n
-0000078795 00000 n
-0000079247 00000 n
-0000080558 00000 n
-0000082042 00000 n
-0000091204 00000 n
-0000091375 00000 n
-0000091441 00000 n
-0000091472 00000 n
-0000091749 00000 n
-0000091824 00000 n
-0000102529 00000 n
-0000114314 00000 n
-0000114380 00000 n
-0000114411 00000 n
-0000114688 00000 n
-0000114763 00000 n
-0000127223 00000 n
-0000133730 00000 n
-0000133796 00000 n
-0000133827 00000 n
-0000134103 00000 n
-0000134178 00000 n
-0000141446 00000 n
-0000146118 00000 n
-0000146184 00000 n
-0000146215 00000 n
-0000146508 00000 n
-0000146583 00000 n
-0000151556 00000 n
-0000153141 00000 n
-0000153207 00000 n
-0000153238 00000 n
-0000153530 00000 n
-0000153605 00000 n
-0000156876 00000 n
-0000178165 00000 n
-0000178231 00000 n
-0000178262 00000 n
-0000178555 00000 n
-0000178630 00000 n
-0000202059 00000 n
-0000228239 00000 n
-0000228305 00000 n
-0000228336 00000 n
-0000228629 00000 n
-0000228704 00000 n
-0000254922 00000 n
-0000255143 00000 n
-0000255262 00000 n
-0000255367 00000 n
-0000255458 00000 n
-0000255549 00000 n
-0000255655 00000 n
-0000255851 00000 n
-0000255883 00000 n
-0000268483 00000 n
-0000268571 00000 n
-0000265290 00000 n
-0000257856 00000 n
-0000258107 00000 n
-0000265561 00000 n
-0000271957 00000 n
-0000271527 00000 n
-0000271093 00000 n
-0000270661 00000 n
-0000269617 00000 n
-0000269672 00000 n
-0000269970 00000 n
-0000270048 00000 n
-0000270205 00000 n
-0000270426 00000 n
-0000270501 00000 n
-0000270581 00000 n
-0000270716 00000 n
-0000271015 00000 n
-0000271148 00000 n
-0000271449 00000 n
-0000271582 00000 n
-0000271879 00000 n
-0000272012 00000 n
-0000272311 00000 n
-0000272465 00000 n
-0000272712 00000 n
-0000273723 00000 n
-0000284180 00000 n
-0000349769 00000 n
-0000415358 00000 n
-0000480947 00000 n
-0000539701 00000 n
-0000536321 00000 n
-0000536385 00000 n
-0000535680 00000 n
-0000535744 00000 n
-0000535040 00000 n
-0000535104 00000 n
-0000534399 00000 n
-0000534463 00000 n
-0000533781 00000 n
-0000533845 00000 n
-0000533161 00000 n
-0000533225 00000 n
-0000532541 00000 n
-0000532605 00000 n
-0000532721 00000 n
-0000532787 00000 n
-0000532818 00000 n
-0000533086 00000 n
-0000533341 00000 n
-0000533407 00000 n
-0000533438 00000 n
-0000533706 00000 n
-0000533961 00000 n
-0000534027 00000 n
-0000534058 00000 n
-0000534324 00000 n
-0000534579 00000 n
-0000534645 00000 n
-0000534676 00000 n
-0000534965 00000 n
-0000535220 00000 n
-0000535286 00000 n
-0000535317 00000 n
-0000535605 00000 n
-0000535860 00000 n
-0000535926 00000 n
-0000535957 00000 n
-0000536246 00000 n
-0000536501 00000 n
-0000536567 00000 n
-0000536598 00000 n
-0000536888 00000 n
-0000539271 00000 n
-0000538842 00000 n
-0000538409 00000 n
-0000537978 00000 n
-0000537548 00000 n
-0000537603 00000 n
-0000537900 00000 n
-0000538033 00000 n
-0000538331 00000 n
-0000538464 00000 n
-0000538764 00000 n
-0000538897 00000 n
-0000539193 00000 n
-0000539326 00000 n
-0000539623 00000 n
-0000540342 00000 n
-trailer <</Size 971/Root 1 0 R/Info 970 0 R/ID[<07511D2C75694DC89622C9F4B35B7624><8DE5252BE861447E9DAABAECF80D68A1>]>> startxref 540533 %%EOF \ No newline at end of file
diff --git a/doc/logos/eps/icon_taler.eps b/doc/logos/eps/icon_taler.eps
deleted file mode 100644
index 79ab648a6..000000000
--- a/doc/logos/eps/icon_taler.eps
+++ /dev/null
Binary files differ
diff --git a/doc/logos/fonts/OldNewspaperTypes.ttf b/doc/logos/fonts/OldNewspaperTypes.ttf
deleted file mode 100644
index 7b9cf31b9..000000000
--- a/doc/logos/fonts/OldNewspaperTypes.ttf
+++ /dev/null
Binary files differ
diff --git a/doc/logos/fonts/perpetue/Perpetua.ttf b/doc/logos/fonts/perpetue/Perpetua.ttf
deleted file mode 100644
index 846b3dca6..000000000
--- a/doc/logos/fonts/perpetue/Perpetua.ttf
+++ /dev/null
Binary files differ
diff --git a/doc/logos/fonts/perpetue/Perpetua_Bold.ttf b/doc/logos/fonts/perpetue/Perpetua_Bold.ttf
deleted file mode 100644
index c73833dbb..000000000
--- a/doc/logos/fonts/perpetue/Perpetua_Bold.ttf
+++ /dev/null
Binary files differ
diff --git a/doc/logos/fonts/perpetue/Perpetua_Bold_Italic.ttf b/doc/logos/fonts/perpetue/Perpetua_Bold_Italic.ttf
deleted file mode 100644
index 3882fe928..000000000
--- a/doc/logos/fonts/perpetue/Perpetua_Bold_Italic.ttf
+++ /dev/null
Binary files differ
diff --git a/doc/logos/fonts/perpetue/Perpetua_Italic.ttf b/doc/logos/fonts/perpetue/Perpetua_Italic.ttf
deleted file mode 100644
index e4f295ed1..000000000
--- a/doc/logos/fonts/perpetue/Perpetua_Italic.ttf
+++ /dev/null
Binary files differ
diff --git a/doc/logos/ico/favicon1616.ico b/doc/logos/ico/favicon1616.ico
deleted file mode 100644
index 141b93d3f..000000000
--- a/doc/logos/ico/favicon1616.ico
+++ /dev/null
Binary files differ
diff --git a/doc/logos/ico/favicon4848.ico b/doc/logos/ico/favicon4848.ico
deleted file mode 100644
index 3c04b1266..000000000
--- a/doc/logos/ico/favicon4848.ico
+++ /dev/null
Binary files differ
diff --git a/doc/logos/png/icon_taler.png b/doc/logos/png/icon_taler.png
deleted file mode 100644
index b1dd9a587..000000000
--- a/doc/logos/png/icon_taler.png
+++ /dev/null
Binary files differ
diff --git a/doc/logos/png/logo_taler.png b/doc/logos/png/logo_taler.png
deleted file mode 100644
index bdffc3c9c..000000000
--- a/doc/logos/png/logo_taler.png
+++ /dev/null
Binary files differ
diff --git a/doc/paper/figs/refresh.tex b/doc/paper/figs/refresh.tex
index 6908527e1..7575e4530 100644
--- a/doc/paper/figs/refresh.tex
+++ b/doc/paper/figs/refresh.tex
@@ -134,7 +134,7 @@
\item[$\beta_\gamma$] $:= \big[ B_\gamma^{(i)} \big]_i$
\item[$\cal S$] $:= \left[ S_{DK^{(i)}}( B_\gamma^{(i)} ) \right]_i$ \\ \smallskip
- \item[$Z$] Cut-and-choose missmatch information
+ \item[$Z$] Cut-and-choose mismatch information
\end{description}
\end{minipage}
\end{figure}
@@ -165,7 +165,7 @@
minimum height = 10cm
] (h2) at (4, 0) {};
\node[above = 0cm of h1] {Customer};
- \node[above = 0cm of h2] {Exchagne};
+ \node[above = 0cm of h2] {Exchange};
\path[->, color = MidnightBlue, very thick, >=stealth]
(-5, 4.5) edge
diff --git a/doc/paper/offline.tex b/doc/paper/offline.tex
index bc4ac0abd..d4ddeb1db 100644
--- a/doc/paper/offline.tex
+++ b/doc/paper/offline.tex
@@ -74,11 +74,11 @@ $n_\mu$ denote the maximum number of coins returned by a refresh.
\smallskip
-Let $\iota$ denote a coin idetity paramater that
+Let $\iota$ denote a coin idetity parameter that
links together the different commitments but must reemain secret
from the exchange.
-Let $n_\nu$ denote the identity security paramater.
+Let $n_\nu$ denote the identity security parameter.
An online coin's identity commitment $\Nu$ is the empty string.
In the offline coin case, we begin with a reserve public key $R$
and a private identity commitment seed $\nu$.
@@ -97,8 +97,8 @@ A coin $(C,\Nu,S)$ consists of
an optional set of offline identity commitments $\Nu = \{\Nu_k | k \in \Gamma \}$
an RSA-FDH signature $S = S_d(\FDH(C) * \Pi_{k \in \Gamma} \FDH(\Nu_k))$ by a denomination key $d$.
A coin is spent by signing a contract with $C$. The contract must
-specify the recipiant merchant and what portion of the value denoted
-by the denomination $d$ they recieve.
+specify the recipient merchant and what portion of the value denoted
+by the denomination $d$ they receive.
There was of course a blinding factor $b$ used in the creation of
the coin's signature $S$. In addition, there was a private seed $s$
@@ -114,7 +114,7 @@ We generate $\nu = H("Offline" || s)$ from $s$ as well,
We begin refresh with a possibly tainted coin $(C,S)$ whose value
we wish to save by refreshing it into untainted coins.
-In the change sitaution, our coin $(C,\Nu,S)$ was partially spent and
+In the change situation, our coin $(C,\Nu,S)$ was partially spent and
retains only a part of the value determined by the denominaton $d$.
For $x$ amongst the symbols $c$, $C$, $b$, and $s$,
diff --git a/doc/paper/postquantum.tex b/doc/paper/postquantum.tex
index 9a4f2e9a8..9fc73205b 100644
--- a/doc/paper/postquantum.tex
+++ b/doc/paper/postquantum.tex
@@ -95,7 +95,7 @@ when coins are double spent \cite{B??}.
Importantly, there are reasons why exchanges must replace coins that
do not involve actual financial transactons, like to reissue a coin
before the exchange rotates the denomination key that signed it, or
-protect users' anonymity after a merchant recieves a coin, but fails
+protect users' anonymity after a merchant receives a coin, but fails
to process it or deliver good.
In Taler, coins can be partially spent by signing with the coin's key
@@ -111,7 +111,7 @@ as well as for coin replacement due to denomination key roration.
If this protocol were simply a second transaction, then customers
would retain information theoreticaly secure anonymity.
-In Taler however, we require that the exchange learns acurate income
+In Taler however, we require that the exchange learns accurate income
information for merchants. If we use a regular transaction, then
a customer could conspire to help the merchant hide their income
\cite[]{Taler??}.
@@ -138,14 +138,14 @@ These provide strong post-quantum security so long as the underlying
scheme remains secure; however, these schemes' youth leaves them
relatively untested.
-Second, we propose a hash based scheme whose anonymity garentee needs
+Second, we propose a hash based scheme whose anonymity guarantee needs
only the one-way assumption on our hash function. In this scheme,
-the vible security paramater is numerically far smaller than in the
+the vible security parameter is numerically far smaller than in the
key exchange systems, but covers query complexity which we believe
suffices.
We describe this hash based proof-of-encryption-to-self scheme to
-align the discription of all our schemes.
+align the description of all our schemes.
...
@@ -191,9 +191,9 @@ We label place holders $\eta$, $\lambda$, $\Lambda$, $\mu$, and $\Mu$
for key material involved in post-quantum operations.
We view $\Lambda$ and $\Mu$ as public keys with respective
private keys $\lambda$ and $\mu$, and
-$\eta$ as the symetric key resulting from the key exchange between them.
+$\eta$ as the symmetric key resulting from the key exchange between them.
-We need effeciently computable functions
+We need efficiently computable functions
$\CPK$, $\CSK$, $\LPK$, $\LSK$, $\KEX_2$ and $\KEX_3$ such that
\begin{itemize}
\item $\mu = \CSK(s)$ for a random bitstring $s$,
@@ -216,10 +216,10 @@ A coin $(C,\Mu,S)$ consists of
a post-quantum public key $\Mu$, and
an RSA-FDH signature $S = S_d(C || \Mu)$ by a denomination key $d$.
A coin is spent by signing a contract with $C$. The contract must
-specify the recipiant merchant and what portion of the value denoted
-by the denomination $d$ they recieve.
+specify the recipient merchant and what portion of the value denoted
+by the denomination $d$ they receive.
If $\Mu$ is large, we may replace it by $H(C || \Mu)$ to make signing
-contracts more efficent.
+contracts more efficient.
There was of course a blinding factor $b$ used in the creation of
the coin's signature $S$. In addition, there was a private seed $s$
@@ -234,7 +234,7 @@ $$ c = H(\textrm{"Ed25519"} || s)
We begin refresh with a possibly tainted coin $(C,\Mu,S)$ that
we wish to refresh into $n \le \theta$ untainted coins.
-In the change sitaution, our coin $(C,\Mu,S)$ was partially spent and
+In the change situation, our coin $(C,\Mu,S)$ was partially spent and
retains only a part of the value determined by the denominaton $d$.
There is usually no denomination that matchets this risidual value
so we must refresh from one coin into $n \le \theta$.
@@ -291,8 +291,8 @@ In other words, $c'$, $\mu'$, and $b_j$ are derived from $s_j$,
\item For $j = 1 \cdots \kappa$ except $\gamma$:
\begin{itemize}
\item Create a proof $\lambda_j^{\textrm{proof}}$ that
- $\lambda_j$ is compatable with $\Lambda_j$ and $\Mu$.
- \item Set a responce tuple
+ $\lambda_j$ is compatible with $\Lambda_j$ and $\Mu$.
+ \item Set a response tuple
$R_j = (\zeta_j,l_j,\lambda_j,\lambda_j^{\textrm{proof}})$.
\end{itemize}
\item Send $S_C(R_j \quad\textrm{for}\quad j \ne \gamma )$.
@@ -321,7 +321,7 @@ We could optionally save long-term storage space by
replacing $\Gamma_*$ with both $\Gamma_{\gamma,0}$ and
$S_C(\Eta_{j,i} \quad\textrm{for}\quad j \ne \gamma )$.
It's clear this requires the wallet send that signature in some phase,
-but also the wallet must accept a phase 2 responce to a phase 1 request.
+but also the wallet must accept a phase 2 response to a phase 1 request.
\smallskip
@@ -356,7 +356,7 @@ This rigidity makes constructing signature schemes with SIDH hard
\cite{??SIDHsig??}, but does not impact our use case.
We let $\mu$ and $\Mu$ be the SIDH 2-torsion private and public keys,
-repectively. We simlarly let $\lambda$ and $\Lambda$ be the
+respectively. We similarly let $\lambda$ and $\Lambda$ be the
SIDH 3-torsion private and public keys.
We envision the 2-torsion secret key generation function $\CSK(s)$
@@ -396,7 +396,7 @@ groups \cite{??,??}, but also a reasuring relationship with NP-hard
problems.
We again let $\mu$ and $\Mu$ denote the Alice (initator) side the
-private and public keys, repectively. We likewise let $\lambda$
+private and public keys, respectively. We likewise let $\lambda$
and $\Lambda$ be the Bob (respondent) private and public keys.
% DO IT?
Again now, $\CPK$, $\CSK$, $\LPK$, $\LSK$, $\KEX_2$ and $\KEX_3$
@@ -407,12 +407,12 @@ the Ring-LWE key exchange itself being broken because $\lambda_j$
and $\Lambda_j$ are constructed using the public key $\Mu$.
First, the polynomial $a$ commonly depends upon $\Mu$, like in
-\cite{NewHope}, so unlinkability explicity depends upon the Ring-LWE
+\cite{NewHope}, so unlinkability explicitly depends upon the Ring-LWE
problem\cite{}. [[ PROOF ??? ]]
Second, the reconciliation information in $\Lambda$ might leak
additional information about $\lambda$.
-[[ LITTERATURE ADDRESSES THIS POINT ??? ]]
+[[ LITERATURE ADDRESSES THIS POINT ??? ]]
Ring-LWE key exchanges require that both Alice and Bob's keys be
ephemeral because the success or failure of the key exchange
@@ -423,7 +423,7 @@ schemes\cite{??RLWEsig??}, and this situation impacts us as well.
A Taler wallet should control both sides during the refresh protocol,
which produces an interesting connundrum.
An honest wallet could ensure that the key exchange always succeeds.
-If wallets were honest, then one could tune the Ring-LWE paramaters
+If wallets were honest, then one could tune the Ring-LWE parameters
to leave the probability of failure rather high,
saving the exchange bandwidth, storage, and verification time.
A dishonest wallet and merchant could conversely search the key space
@@ -432,25 +432,25 @@ merchant in tax evasion.
[[ IS THE FOLLOWING IMPOSSIBLE ??? ]]
-If possible, we should tune the Ring-LWE paramaters to reduce costs
+If possible, we should tune the Ring-LWE parameters to reduce costs
to the exchange, and boost the unlinkability for the users, while
simultaniously
% \smallskip
% \subsection{Comparson}
-At present, the SIDH implemention in \cite{SIDH16} requires about
+At present, the SIDH implementation in \cite{SIDH16} requires about
one third the key material and 100?? times as much CPU time as the
-Ring-LWE implemention in \cite{NewHope}.
+Ring-LWE implementation in \cite{NewHope}.
[[ We believe this provides a strong reason to continue exploring
-paramater choices for Ring-LWE key exchange along with protocol tweaks.
+parameter choices for Ring-LWE key exchange along with protocol tweaks.
... ]]
\section{Hashed-based one-sided public keys}
We now define our hash-based encryption scheme.
-Let $\delta$ denote our query security paramater and
+Let $\delta$ denote our query security parameter and
let $\mu$ be a bit string.
For $j \le \kappa$, we define a Merkle tree $T_j$ of height $\delta$
with leaves $\eta_j = H(\mu || "YeyCoins!" || t || j)$
@@ -500,8 +500,8 @@ an attacker to pursue $\eta_j$ alone unless they expect to break
curve25519 in the future, either through mathematical advances or
by building a quantum computer.
-We therefore view $\delta$ as a query complexity paramater whose
-optimial setting depends upo nthe strength of the overall protocoll.
+We therefore view $\delta$ as a query complexity parameter whose
+optimial setting depends upo nthe strength of the overall protocol.
\smallskip
@@ -510,7 +510,7 @@ We can magnify the effective $\delta$ by using multiple $\eta_j$.
... analysis ...
% multiple withdrawals
-We believe this provides sufficent post-quantum security for
+We believe this provides sufficient post-quantum security for
refreshing change.
@@ -518,11 +518,11 @@ refreshing change.
We noted in \S\ref{subsec:withdrawal} above that exchange might
require that initial withdrawals employs a refresh-like operation.
-In this scenarion, we refresh from a pseudo-coin $(C,\Mu)$ where
+In this scenario, we refresh from a pseudo-coin $(C,\Mu)$ where
$C$ is the user's reserve key \cite[??]{Taler} and
$\Mu$ s a post-quantum public key kept with $C$.
As a result, our hash-based scheme should increase the security
-paramater $\delta$ to allow a query for every withdrawal operation.
+parameter $\delta$ to allow a query for every withdrawal operation.
Instead, ...
[[ ??? we propose using a Merkle tree of Alice side Ring-LWE keys,
@@ -565,7 +565,7 @@ Crazy pants ideas :
Use a larger Mrkle tree with start points seeded throughout
-Use a Merkle tree of SWIFFT hash functions becuase
+Use a Merkle tree of SWIFFT hash functions because
their additive homomorphic property lets you keep the form of a polynomial
diff --git a/doc/paper/taler.bib b/doc/paper/taler.bib
index a46c9384c..14b57092a 100644
--- a/doc/paper/taler.bib
+++ b/doc/paper/taler.bib
@@ -413,7 +413,7 @@ issn="1432-1378",
volume="16",
number="3",
pages="185--215",
- abstract="We introduce a new class of computational problems which we call the ``one-more-RSA-inversion'' problems. Our main result is that two problems in this class, which we call the chosen-target and known-target inversion problems, respectively, have polynomially equivalent computational complexity. We show how this leads to a proof of security for Chaum's RSA-based blind signature scheme in the random oracle model based on the assumed hardness of either of these problems. We define and prove analogous results for ``one-more-discrete-logarithm'' problems. Since the appearence of the preliminary version of this paper, the new problems we have introduced have found other uses as well.",
+ abstract="We introduce a new class of computational problems which we call the ``one-more-RSA-inversion'' problems. Our main result is that two problems in this class, which we call the chosen-target and known-target inversion problems, respectively, have polynomially equivalent computational complexity. We show how this leads to a proof of security for Chaum's RSA-based blind signature scheme in the random oracle model based on the assumed hardness of either of these problems. We define and prove analogous results for ``one-more-discrete-logarithm'' problems. Since the appearance of the preliminary version of this paper, the new problems we have introduced have found other uses as well.",
issn="1432-1378",
doi="10.1007/s00145-002-0120-1",
doi_url="http://dx.doi.org/10.1007/s00145-002-0120-1",
diff --git a/doc/paper/taler.tex b/doc/paper/taler.tex
index e1a120c9a..b462a4d69 100644
--- a/doc/paper/taler.tex
+++ b/doc/paper/taler.tex
@@ -487,7 +487,7 @@ and another time for refunding the remaining amount without losing anonymity.
Unfortunately this approach cannot be used for a general-purpose payment
system, since the refund operation of Rupp et al. allows transferring money
in a way that hides income from taxation. Refunding a coin into a wallet that
-didn't withdraw the coin is possible in their system, but consitutes a
+didn't withdraw the coin is possible in their system, but constitutes a
transaction between two parties that is not recognized by the system for the
purpose of income taxation.
@@ -1243,7 +1243,7 @@ certification process.
We assume the exchange operates honestly when discussing taxability.
We feel this assumption is warranted mostly because a Taler exchange
requires licenses to operate as a financial institution, which it
-risks loosing if it knowingly facilitates tax evasion.
+risks losing if it knowingly facilitates tax evasion.
We also expect an auditor monitors the exchange similarly to how
government regulators monitor financial institutions.
In fact, our auditor software component gives the auditor read access
@@ -1772,7 +1772,7 @@ currency. A tax auditor can then request the merchant to reveal
(meaningful) details about the business transaction ($\mathcal{D}$,
$a$, $p$, $r$), including proof that applicable taxes were paid.
-If a merchant is not able to provide theses values, they can be
+If a merchant is not able to provide these values, they can be
subjected to financial penalties by the state in relation to the
amount transferred by the traditional currency transfer.
diff --git a/doc/paper/taler_FC2016.txt b/doc/paper/taler_FC2016.txt
index 80e590c38..176e9c750 100644
--- a/doc/paper/taler_FC2016.txt
+++ b/doc/paper/taler_FC2016.txt
@@ -118,7 +118,7 @@ is less a currency and more an open protocol for creating new
currencies. So what? And why do altcoins become a ponzi scheme? (Noting
that you do not say that they might become one, rather that they do).
-> We have adjusted that langauge, as some like Dogecoin have removed
+> We have adjusted that language, as some like Dogecoin have removed
> the 21 billion BTC cap to reduce the ponzi-like tendencies.
> There remains a large trend towards ponzi schemes in the altcoin
> world however, amusingly noted by https://ponzico.win/ and
@@ -307,7 +307,7 @@ scheme suggests that a any transfers of value should be taxed. However,
the issuing protocol in 4.1 can be abused to transfer a coin, without
paying tax, and in an unlikable manner.
-> Technically 4.1 is not transfering a coin, as it is issuing a coin.
+> Technically 4.1 is not transferring a coin, as it is issuing a coin.
> Again, the loophole is/was discussed in the paper.
The party withdrawing the coin
diff --git a/doc/paper/taler_FC2017.txt b/doc/paper/taler_FC2017.txt
index 66f8560ad..40070be48 100644
--- a/doc/paper/taler_FC2017.txt
+++ b/doc/paper/taler_FC2017.txt
@@ -184,7 +184,7 @@ Specific comments:
> We added remarks on the level of anonymity that Zerocash achieves.
> We suspect Zerocash's inherent scaling issues limit its anonymity
-> for normal purchases, as compaired to that a large Taler exchange
+> for normal purchases, as compared to that a large Taler exchange
> provides. We mention that Zerocash is likely to provide better
> anonymtiy for large transactions that do not need to be cashed out.
@@ -220,11 +220,11 @@ wanting this features is that it enables refunds from a merchant that
later can be refreshed into "clean" coins that are unlinkable to the
refunded coins. The protocol is based on what appears to be a standard
cut-and-choose approach, which does not appear to be particularly
-novel. On the postive side, the problem appears a natural and if it
+novel. On the positive side, the problem appears a natural and if it
hasn't been done before certainly useful. On the negative side, since
the paper does not contain any formal definitions, or even semi-formal
specifications of the desiderata, it is very hard to understand what
-actually is acheived. Furthermore, no proofs of security are given,
+actually is achieved. Furthermore, no proofs of security are given,
and even the protocol is hard to fully understand. As such, I would
suggest the authors to first formalize their approach and
resubmitting.
diff --git a/doc/prebuilt b/doc/prebuilt
-Subproject eef86710c7deade01361f8985fd9a6fe6a21e8f
+Subproject b8d2d2fa2ed2a771880f451725176f256583cb2
diff --git a/doc/system/.gitignore b/doc/system/.gitignore
new file mode 100644
index 000000000..368efd05f
--- /dev/null
+++ b/doc/system/.gitignore
@@ -0,0 +1,26 @@
+thesis.pdf
+thesis-*.pdf
+system.pdf
+system-*.pdf
+thesis.out
+summary/summary-english.pdf
+*-converted-to.pdf
+*.log
+*.toc
+*.run.xml
+*.ind
+*.ilg
+*.fls
+*.fdb_latexmk
+*.aux
+*.idx
+*.bbl
+*.bcf
+*.blg
+*.lof
+*.maf
+*.mt*
+
+bench/results/
+bench/plots/
+bench/stats
diff --git a/doc/system/abstract.tex b/doc/system/abstract.tex
new file mode 100644
index 000000000..fcc44c7d0
--- /dev/null
+++ b/doc/system/abstract.tex
@@ -0,0 +1,52 @@
+\chapter{Abstract}
+%As our society becomes more and more digitalized, an electronic version of cash
+%becomes inevitable. The design of payment systems is not just a technological
+%matter, but has far-reaching sociopolitical consequences.
+\begin{samepage}
+We describe the design and implementation of GNU Taler, an electronic payment
+system based on an extension of Chaumian online e-cash with efficient change.
+In addition to anonymity for customers, it provides the novel notion of
+\emph{income transparency}, which guarantees that merchants can reliably
+receive a payment from an untrusted payer only when their income from the
+payment is visible to tax authorities.
+
+Income transparency is achieved by the introduction of a \emph{refresh
+protocol}, which gives anonymous change for a partially spent coin without
+introducing a tax evasion loophole. In addition to income transparency, the
+refresh protocol can be used to implement Camenisch-style \emph{atomic swaps}, and to
+preserve anonymity in the presence of protocol \emph{aborts} and crash faults with
+data loss by participants.
+
+Furthermore, we show the provable security of our income-transparent anonymous
+e-cash, which, in addition to the usual \emph{anonymity} and
+\emph{unforgeability} properties of e-cash, also formally models
+\emph{conservation} of funds and income transparency.
+
+Our implementation of GNU Taler is usable by non-expert users and integrates
+with the modern Web architecture. Our payment platform addresses a range of
+practical issues, such as tipping customers, providing refunds, integrating
+with banks and know-your-customer (KYC) checks, as well as Web platform
+security and reliability requirements. On a single machine, we achieve
+transaction rates that rival those of global, commercial credit card
+processors. We increase the robustness of the exchange---the component that
+keeps bank money in escrow in exchange for e-cash---by adding an auditor
+component, which verifies the correct operation of the system and allows to
+detect a compromise or misbehavior of the exchange early.
+
+Just like bank accounts have reason to exist besides bank notes, e-cash only
+serves as part of a whole payment system stack. Distributed ledgers have
+recently gained immense popularity as potential replacement for parts of the
+traditional financial industry. While cryptocurrencies based on proof-of-work
+such as Bitcoin have yet to scale to be useful as a replacement for established
+payment systems, other more efficient systems based on blockchains with more
+classical consensus algorithms might still have promising applications in the
+financial industry.
+
+We design, implement and analyze the performance of \emph{Byzantine Set Union
+Consensus} (BSC), a Byzantine consensus protocol that agrees on a (super-)set
+of elements at once, instead of sequentially agreeing on the individual
+elements of a set. While BSC is interesting in itself, it can also be used as
+a building block for permissioned blockchains, where---just like in
+Nakamoto-style consensus---whole blocks of transactions are agreed upon at once,
+increasing the transaction rate.
+\end{samepage}
diff --git a/doc/system/acknowledgements.tex b/doc/system/acknowledgements.tex
new file mode 100644
index 000000000..8b592e4e9
--- /dev/null
+++ b/doc/system/acknowledgements.tex
@@ -0,0 +1,27 @@
+\chapter*{Acknowledgements}
+
+The book is based on Florian Dold's PhD thesis. We have removed the parts that
+are not crucial for GNU Taler. Also, unlike the thesis, the GNU Taler project
+continues to be updated, so this books is expected to be kept up-to-date with
+the latest developments. Nevertheless, Florian Dold's contribution to the
+text remains fundamental.
+
+We would like to thank Moritz Bartl for helping with the funding for Florian's
+thesis and GNU Taler in general. Bruno Haible provided generous support for
+the GNU Taler team to visit meetings of the W3C's Web Payment Working Group.
+We also thank Ashoka, the Tor project and the Donaukurier for their support.
+
+This work benefits from the financial support of the Brittany Region (ARED
+9174) and the Renewable Freedom Foundation (RFF).
+
+We thank the Bern University of Applied Sciences for continuing to host the
+GNU Taler servers.
+
+We thank to Cristina Onete and Jeff Burdges for their collaboration on the
+provable security of GNU Taler.
+
+We are grateful to the GNU project, in particular Richard Stallman, for their
+support of this project. We also thank all GNUnet developers and GNU Guix
+developers, especially Hartmut Goebel, Nils Gillmann, Gabor Toth, Ludovic
+Courtès and Andreas Enge.
+
diff --git a/doc/system/conclusions.tex b/doc/system/conclusions.tex
new file mode 100644
index 000000000..c6ec87914
--- /dev/null
+++ b/doc/system/conclusions.tex
@@ -0,0 +1,227 @@
+\chapter{Future Work}\label{chapter:future-work}
+We now discuss future work that builds upon the results presented so far.
+
+
+\subsection*{Standard Model}
+Our current instantiation of the Taler protocol relies heavily on hash
+functions. Since the result by Canetti and others \cite{canetti2004random}
+about the theoretical impossibility of securely instantiating protocols that
+rely on the random oracle assumption for their security, a vast amount of
+literature has been devoted to find instantiations of interesting protocols in
+the standard model \cite{koblitz2015random}. The Taler protocol syntax could
+likely be also instantiated securely in the standard model, based existing on
+blind signature schemes in the standard model. The trade-off however is that
+while removing the random oracle assumption, typically other less well known
+assumptions must be made.
+
+\subsection*{Post-Quantum security}
+The possibility of post-quantum computers breaking the security of established
+cryptographic primitives has lately received a lot of attention from
+cryptographers. While currently most schemes with post-quantum security are impractical,
+it might be worthwhile to further investigate their application to e-cash, based
+on existing work such as \cite{zhang2018new}.
+
+\subsection*{Applications to network incentives}
+Some peer-to-peer networking protocols (such as onion routing
+\cite{dingledine2004tor}) do not have inherent incentives and rely on
+volunteers to provide infrastructure. In future work, we want to look at
+adding incentives in the form of Taler payments to a peer-to-peer networking
+platform such as GNUnet.
+
+\subsection*{Smart(er) Contracts and Auctions}
+Contract terms in Taler are relatively limited. There are some interesting
+secure multiparty computations, such as privacy-preserving auctions
+\cite{brandt2006obtain} that could be offered by exchanges as a fixed smart
+contract. This would allow a full privacy-preserving auction platform, as
+current auction protocols only output the winner of a privacy-preserving
+auction but do not address the required anonymous payments.
+
+
+\subsection*{Backup and Sync}\label{sec:future-work-backup-sync}
+Synchronization of wallets between multiple devices is a useful feature, but a
+na\"ive implementation endangers privacy. A carefully designed protocol for
+backup and synchronization must make sure that the hosting service for the
+wallet's data cannot collaborate with the exchange and merchants to deanonymize
+users or transactions. Thus when spending coins for a payment, devices should
+not have to synchronously talk to their backup/sync provider. This creates the
+challenge of allocating the total available balance to individual devices in a
+way that fits the customer's spending pattern, and only require synchronous
+communication at fixed intervals or when really necessary to re-allocate coins.
+
+Another possible approach might be to use Private Information Retrieval (PIR)
+\cite{goldberg2007improving} to access backup and synchronization information.
+
+
+\subsection*{Machine-Verified Proofs}
+We currently model only a subset of the GNU Taler protocol formally, and proofs
+are handwritten and verified by humans. A tool such as CryptoVerif
+\cite{blanchet2007cryptoverif} can allow a higher coverage and computer-checked
+proofs, and would allow protocol changes to be validated in shorter time.
+
+\subsection*{Coin Restrictions / ``Taler for Children''}
+By designating certain denominations for different purposes, GNU Taler could be
+used to implement a very simple form of anonymous credentials
+\cite{paquin2011u,camenisch2004signature}, which then could be used to
+implement a Taler wallet specifically aimed at children, in order to teach them
+responsible and autonomous spending behavior, while granting them privacy and
+at the same time preventing them from making age-inappropriate purchases
+online, as the discretion of parents.
+
+%\subsection*{gnunet-blockchain / deployment of the full stack payment system}
+%=> no, talk more about integration with real banks, KYC
+%
+%\subsection*{P2P payments}
+%
+%\subsection*{NFC Wallet}
+%
+%\subsection*{large, scalable deployment}
+%I.e. sharding, db replication, load balancer(s)
+%
+%\subsection*{Hardware security module for exchange}
+%
+%\subsection*{Bitcoin/Blockchain integration}
+%
+%\subsection*{UX study and improvements}
+%(including tracking/planning of spending)
+%
+%\subsection*{News Distribution}
+
+\chapter{Conclusion}\label{chapter:conclusion}
+
+% sources and inspirations
+% https://www.bis.org/publ/arpdf/ar2018e5.pdf
+% https://www.bis.org/publ/qtrpdf/r_qt1709f.pdf
+% http://andolfatto.blogspot.com/2015/02/fedcoin-on-desirability-of-government.html
+
+This book presented GNU Taler, an efficient protocol for
+value-based electronic payment systems with focus on security and
+privacy. While we believe our approach to be socially and economically beneficial, a
+technological impact analysis is in order prior to adopting new
+systems that have broad economic and socio-political implications.
+
+Currencies serve three key functions in society:~\cite{mankiw2010macroeconomics}
+\begin{enumerate}
+\item As a unit for measurement of value,
+\item a medium of exchange, and
+\item a store of value.
+\end{enumerate}
+How do the various methods measure up to these requirements?
+
+\section{Cryptocurrencies vs. Central-Bank-Issued Currencies}
+
+\begin{figure}
+\centering
+\includegraphics[width=\textwidth]{diagrams/bitcoin-market-price.png}
+ \caption[Historical market price of Bitcoin.]{Historical market price (in
+ USD) of Bitcoin across major exchanges (Source:~\url{https://blockchain.com}).}
+\label{fig:volatility}
+\end{figure}
+
+Cryptocurrencies generally fail to achieve the required stability to serve as a
+reasonable unit of measurement (Figure~\ref{fig:volatility}). The volatility
+of cyptocurrencies is caused by a combination of a lack of institutions that
+could intervene to dampen fluctuations and a comparatively limited liquidity
+in the respective
+markets. The latter is exacerbated by the limited ability of decentralized
+cryptocurrencies to handle large transaction volumes, despite their extreme
+levels of resource consumption. As a result, the utility of decentralized
+cryptocurrencies is limited to highly speculative investments and to the
+facilitation of criminal transactions.
+
+With respect to privacy, completely decentralized cryptocurrencies
+provide either too much or too little anonymity. Transparent
+cryptocurrencies create the spectre of discriminatory pricing, while
+especially for privacy-enhanced cryptocurrencies the lack of
+regulation creates an attractive environment for fraud and criminal
+activity from tax evasion to financing of terrorism.
+
+These problems are easily addressed by combining the register (or
+ledger) with a central bank providing a regulatory framework and
+monetary policy, including anti-money-laundering and
+know-your-customer enforcement.
+
+\section{Electronic Payments}
+
+Day-to-day payments using registers are expensive and inconvenient.
+Using a register requires users to {\em identify} themselves to {\em
+ authorize} transactions, and the use of register-based banking
+systems tends to be more expensive than the direct exchange of
+physical cash. However, with the ongoing digitalization of daily life
+where a significant number of transactions is realized over networks,
+some form of electronic payments remain inevitable.
+
+The current alternative to (centrally banked) electronic cash are a
+payment systems under full control of oligopoly companies such as
+Google, Apple, Facebook or Visa. The resulting oligopolies are
+anti-competitive. In addition to excessive fees, they sometimes even
+refuse to process payments with certain types of legal businesses,
+which then are often ruined due to lack of alternatives. Combining
+payment services with companies where the core business model is
+advertising is also particularly damaging for privacy. Finally, the
+sheer size of these companies creates systemic risks, just as their
+global scale creates challenges for regulation.
+
+As GNU Taler is free software, even without backing by a central bank,
+Taler would not suffer from these drawbacks arising from the use of
+proprietary technology.
+
+Furthermore, Taler-style electronic cash comes
+with some unique benefits:
+\begin{itemize}
+ \item improved income transparency compared to cash and traditional
+ Chaum-style e-cash,
+ \item anonymity for payers,
+ \item avoidance of enticement towards consumer debt --- especially
+ compared to credit cards, and
+ \item support of new business models and Internet security
+ mechanisms which require (anonymous) micro-transactions.
+\end{itemize}
+
+Central banks are carefully considering what might be the right
+technology to implement an electronic version of their centrally
+banked currency, and with Taler we hope to address most of their concerns.
+Nevertheless, all electronic payment systems, including Taler even
+when backed by central-bank-issued currencies, come with their own
+inherent set of risks:~\cite{riksbank2017riksbank}
+
+\begin{itemize}
+ \item increased risk of a bank run: in a banking crisis,
+ as it is easier to withdraw large amounts of digital
+ cash quickly --- even from remote locations;
+ \item increased volatility due to foreign holdings that would
+ not be as easily possible with physical cash;
+ \item increased risk of theft and disruption: while physical
+ cash can also be stolen (and likely with much less effort), it is
+ difficult to transport in volume~\cite{force2015money}, the
+ risk is increased with computers because attacks scale \cite{hammer2018billion}, and
+ generally many small incidents are socially preferable over a
+ tiny number of very large-scale incidents; and
+ \item unavailability in crisis situations without electricity and Internet
+ connectivity.
+\end{itemize}
+
+
+We believe that in the case of Taler, some of the risks mentioned
+above can be mitigated:
+\begin{itemize}
+ \item Volatility due to foreign holdings and the resulting increased
+ risk of bank runs can be reduced by putting limits on the amount of
+ electronic coins that customers can withdraw. Limiting the
+ validity periods of coins is another method that can help
+ disincentivize the use of Taler as a value store.
+ \item The use of open standards and reference implementations enables
+ white-hat security research around GNU Taler, which together with
+ good operational security procedures and the possibility of
+ competing providers should reduce the risks from attacks.
+ \item GNU Taler can co-exist with physical cash, and might even
+ help revive the use of cash if it succeeds in reducing credit
+ card use online thereby eliminating a key reason for people to
+ have credit cards.
+\end{itemize}
+
+Unlike cryptocurrencies, Taler does not prescribe a solution for monetary
+policy or just taxation, as we believe these issues need to be subject to
+continuous political debate and cannot be ``solved'' by simplistic algorithms.
+What we offer to society is an open and free (as in free speech) system with
+mechanisms to audit merchants' income, instead of proprietary systems
+controlled by a few oligopoly companies.
diff --git a/doc/system/cryptocode.sty b/doc/system/cryptocode.sty
new file mode 100644
index 000000000..b6b4f1505
--- /dev/null
+++ b/doc/system/cryptocode.sty
@@ -0,0 +1,1813 @@
+ %% Copyright 2015 Arno Mittelbach
+ %
+ % This work may be distributed and/or modified under the
+ % conditions of the LaTeX Project Public License, either version 1.3
+ % of this license or (at your option) any later version.
+ % The latest version of this license is in
+ % http://www.latex-project.org/lppl.txt
+ % and version 1.3 or later is part of all distributions of LaTeX
+ % version 2005/12/01 or later.
+ %
+ % This work has the LPPL maintenance status `maintained'.
+ %
+ % The Current Maintainer of this work is Arno Mittelbach.
+ %
+ % This work consists of the files cryptocode.tex and cryptocode.sty
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{cryptocode}
+ [2015/04/22 v0.1 Cryptocode LaTeX package for writing pseudocode in cryptographic settings]
+
+
+\def\hi{Hello, this is Arno's crypto code package. }
+\let\myDate\date
+
+\RequirePackage{amsmath}
+\RequirePackage{mathtools}
+%\usepackage{l3tl-analysis} % uncomment for debugging
+
+%%%
+% option modes
+\newif\ifpc@orderofgrowth
+\newif\ifpc@algorithmstyle
+\newif\ifpc@amsfonts
+\newif\ifpc@advantage
+\newif\ifpc@primitives
+
+%%%
+%
+\DeclareOption{operators}{
+ \providecommand{\sample}{\hskip2.3pt{\gets\!\!\mbox{\tiny${\$}$\normalsize}}\,}
+
+ \providecommand{\floor}[1]{\ensuremath{\left\lfloor #1\right\rfloor}}
+ \providecommand{\ceil}[1]{\ensuremath{\left\lceil #1\right\rceil}}
+ \providecommand{\Angle}[1]{\ensuremath{\left\langle #1\right\rangle}}
+ \providecommand{\abs}[1]{\ensuremath{\left\lvert #1 \right\rvert}}
+ \providecommand{\norm}[1]{\ensuremath{\left\|#1\right\|}}
+ \providecommand{\concat}{\ensuremath{\|}}
+
+ \providecommand{\emptystring}{\ensuremath{\varepsilon}}
+}
+
+\DeclareOption{adversary}{
+ \providecommand{\pcadvstyle}[1]{\mathcal{#1}}
+
+ \providecommand{\adv}{\ensuremath{\pcadvstyle{A}}}
+ \providecommand{\bdv}{\ensuremath{\pcadvstyle{B}}}
+ \providecommand{\cdv}{\ensuremath{\pcadvstyle{C}}}
+ \providecommand{\ddv}{\ensuremath{\pcadvstyle{D}}}
+ \providecommand{\mdv}{\ensuremath{\pcadvstyle{M}}}
+ \providecommand{\pdv}{\ensuremath{\pcadvstyle{P}}}
+ \providecommand{\rdv}{\ensuremath{\pcadvstyle{R}}}
+ \providecommand{\sdv}{\ensuremath{\pcadvstyle{S}}}
+}
+
+\DeclareOption{landau}{
+ \pc@orderofgrowthtrue
+
+ \providecommand{\bigO}[1]{\ensuremath{\mathcal{O}\olrk{#1}}}
+ \providecommand{\smallO}[1]{\ensuremath{\text{o}\olrk{#1}}}
+ \providecommand{\bigOmega}[1]{\ensuremath{\Omega\olrk{#1}}}
+ \providecommand{\smallOmega}[1]{\ensuremath{\omega\olrk{#1}}}
+ \providecommand{\bigsmallO}[1]{\ensuremath{\Theta\olrk{#1}}}
+}
+
+\DeclareOption{probability}{
+ \pc@orderofgrowthtrue
+ \pc@amsfontstrue
+
+ \providecommand{\probname}{Pr}
+ \providecommand{\expectationname}{\ensuremath{\mathbb{E}}}
+ \providecommand{\supportname}{Supp}
+
+ \providecommand{\prob}[1]{\ensuremath{\operatorname{\probname}\elrk{#1}}}
+ \providecommand{\probsub}[2]{\ensuremath{\operatorname{\probname}_{#1}\elrk{#2}}}
+ \providecommand{\condprob}[2]{\ensuremath{\prob{#1\,\left|\,#2\vphantom{#1}\right.}}}
+ \providecommand{\condprobsub}[3]{\ensuremath{\probsub{#1}{#2\,\left|\,#3\vphantom{#1}\right.}}}
+
+ \providecommand{\expect}[1]{\ensuremath{\operatorname{\expectationname}\elrk{#1}}}
+ \providecommand{\expsub}[2]{\ensuremath{\operatorname{\expectationname}_{#1}\elrk{#2}}}
+ \providecommand{\condexp}[2]{\ensuremath{\expect{#1\,\left|\,#2\vphantom{#1}\right.}}}
+ \providecommand{\condexpsub}[3]{\ensuremath{\expsub{#1}{#2\,\left|\,#3\vphantom{#1}\right.}}}
+
+ \providecommand{\supp}[1]{ \ensuremath{\operatorname{Supp}\olrk{#1}}}
+
+ \providecommand{\entropy}[1]{\ensuremath{\operatorname{H}\olrk{#1}}}
+ \providecommand{\minentropy}[1]{\ensuremath{\operatorname{H_\infty}\olrk{#1}}}
+ \providecommand{\condminentropy}[2]{\ensuremath{\operatorname{\tilde{H}_\infty}\olrk{#1|#2}}}
+}
+
+\DeclareOption{sets}{
+ \pc@orderofgrowthtrue
+ \pc@amsfontstrue
+
+ \providecommand\NN{\mathbb{N}}
+ \providecommand\ZZ{\mathbb{Z}}
+ \providecommand\CC{\mathbb{C}}
+ \providecommand\QQ{\mathbb{Q}}
+ \providecommand\RR{\mathbb{R}}
+ \providecommand\PP{\mathbb{P}}
+ \providecommand\FF{\mathbb{F}}
+ \providecommand\GG{\mathbb{G}}
+
+ \providecommand{\set}[1]{\ensuremath{\clrk{#1}}}
+ \providecommand{\sequence}[1]{\ensuremath{\olrk{#1}}}
+ \providecommand{\bin}{\ensuremath{\{0,1\}}}
+}
+
+\DeclareOption{noamsfonts}{
+ \pc@amsfontsfalse
+}
+
+
+\DeclareOption{notions}{
+ \providecommand{\pcnotionstyle}[1]{\ensuremath{\mathrm{#1}}}
+
+ \providecommand{\indcpa}{\pcnotionstyle{IND\pcmathhyphen{}CPA}}
+ \providecommand{\indcca}{\pcnotionstyle{IND\pcmathhyphen{}CCA}}
+ \providecommand{\indccai}{\pcnotionstyle{IND\pcmathhyphen{}CCA1}}
+ \providecommand{\indccaii}{\pcnotionstyle{IND\pcmathhyphen{}CCA2}}
+ \providecommand{\priv}{\pcnotionstyle{PRIV}}
+ \providecommand{\ind}{\pcnotionstyle{IND}}
+ \providecommand{\indcda}{\pcnotionstyle{IND\pcmathhyphen{}CDA}}
+ \providecommand{\prvcda}{\pcnotionstyle{PRV\pcmathhyphen{}CDA}}
+ \providecommand{\prvrcda}{\pcnotionstyle{PRV\$\pcmathhyphen{}CDA}}
+ \providecommand{\kiae}{\pcnotionstyle{KIAE}}
+ \providecommand{\kdae}{\pcnotionstyle{KDAE}}
+ \providecommand{\mle}{\pcnotionstyle{MLE}}
+ \providecommand{\uce}{\pcnotionstyle{UCE}}
+}
+
+\DeclareOption{logic}{
+ \providecommand{\AND}{\ensuremath{\mathrm{AND}}}
+ \providecommand{\OR}{\ensuremath{\mathrm{OR}}}
+ \providecommand{\NOT}{\ensuremath{\mathrm{NOT}}}
+ \providecommand{\xor}{\ensuremath{\oplus}}
+ \providecommand{\false}{\mathsf{false}}
+ \providecommand{\true}{\mathsf{true}}
+}
+
+
+% Function Families
+\DeclareOption{ff}{
+ \pc@algorithmstyletrue
+
+ \providecommand{\kgen}{\pcalgostyle{KGen}}
+ \providecommand{\pgen}{\pcalgostyle{Pgen}}
+ \providecommand{\eval}{\pcalgostyle{Eval}}
+
+ \providecommand{\il}{\pcalgostyle{il}}
+ \providecommand{\ol}{\pcalgostyle{ol}}
+ \providecommand{\kl}{\pcalgostyle{kl}}
+ \providecommand{\nl}{\pcalgostyle{nl}}
+ \providecommand{\rl}{\pcalgostyle{rl}}
+}
+
+% Machine Model
+\DeclareOption{mm}{
+ \pc@algorithmstyletrue
+
+ \providecommand{\pcmachinemodelstyle}[1]{\ensuremath{\mathsf{#1}}}
+
+ \providecommand{\CRKT}{\pcmachinemodelstyle{C}}
+ \providecommand{\TM}{\pcmachinemodelstyle{M}}
+ \providecommand{\PROG}{\pcmachinemodelstyle{P}}
+
+ \providecommand{\uTM}{\pcmachinemodelstyle{UM}}
+ \providecommand{\uC}{\pcmachinemodelstyle{UC}}
+ \providecommand{\uP}{\pcmachinemodelstyle{UEval}}
+
+ \providecommand{\csize}{\pcmachinemodelstyle{size}}
+ \providecommand{\tmtime}{\pcmachinemodelstyle{time}}
+ \providecommand{\ppt}{\pcalgostyle{PPT}}
+}
+
+\DeclareOption{advantage}{
+ \pc@advantagetrue
+}
+
+\DeclareOption{primitives}{
+ \pc@primitivestrue
+ \pc@algorithmstyletrue
+}
+
+\DeclareOption{events}{
+ \providecommand{\event}[1]{\ensuremath{\mathsf{#1}}}
+ \providecommand{\nevent}[1]{\ensuremath{\overline{\event{#1}}}}
+
+ \providecommand{\bad}{\ensuremath{\event{bad}}}
+}
+
+\DeclareOption{complexity}{
+ \providecommand{\pccomplexitystyle}[1]{\ensuremath{\mathsf{#1}}}
+
+ \providecommand{\npol}{\pccomplexitystyle{NP}}
+ \providecommand{\conpol}{\ensuremath{\mathsf{co}}\pccomplexitystyle{NP}}
+ \providecommand{\pol}{\pccomplexitystyle{P}}
+ \providecommand{\bpp}{\pccomplexitystyle{BPP}}
+ \providecommand{\ppoly}{\ensuremath{\pol/\mathrm{poly}}}
+
+ \providecommand{\AM}{\pccomplexitystyle{AM}}
+ \providecommand{\coAM}{\ensuremath{\mathsf{co}}\pccomplexitystyle{AM}}
+
+ \providecommand{\AC}[1]{\ensuremath{\ifthenelse{\equal{#1}{}}{\pccomplexitystyle{AC}}{\pccomplexitystyle{AC}^{#1}}}}
+ \providecommand{\NC}[1]{\ensuremath{\ifthenelse{\equal{#1}{}}{\pccomplexitystyle{NC}}{\pccomplexitystyle{NC}^{#1}}}}
+ \providecommand{\TC}[1]{\ensuremath{\ifthenelse{\equal{#1}{}}{\pccomplexitystyle{TC}}{\pccomplexitystyle{TC}^{#1}}}}
+}
+
+\DeclareOption{asymptotics}{
+ \pc@orderofgrowthtrue
+
+ \providecommand{\pcpolynomialstyle}[1]{\mathsf{#1}}
+
+ \providecommand{\negl}[1][\secpar]{\ensuremath{\pcpolynomialstyle{negl}\ifthenelse{\equal{#1}{}}{}{\olrk{#1}}}}
+ \providecommand{\poly}[1][\secpar]{\ensuremath{\pcpolynomialstyle{poly}\ifthenelse{\equal{#1}{}}{}{\olrk{#1}}}}
+
+ \providecommand{\pp}{\ensuremath{\pcpolynomialstyle{p}}}
+ \providecommand{\qq}{\ensuremath{\pcpolynomialstyle{q}}}
+}
+
+\DeclareOption{keys}{
+ \providecommand{\pckeystyle}[1]{\ensuremath{\mathsf{#1}}}
+
+ \providecommand{\pk}{\pckeystyle{pk}}
+ \providecommand{\vk}{\pckeystyle{vk}}
+ \providecommand{\sk}{\pckeystyle{sk}}
+ \providecommand{\key}{\pckeystyle{k}}
+ \providecommand{\hk}{\pckeystyle{hk}}
+ \providecommand{\gk}{\pckeystyle{gk}}
+ \providecommand{\fk}{\pckeystyle{fk}}
+
+ \providecommand{\st}{\pckeystyle{st}}
+ \providecommand{\state}{\pckeystyle{state}}
+}
+
+\DeclareOption{n}{
+ \providecommand{\secpar}{\ensuremath{n}}
+ \providecommand{\secparam}{\ensuremath{1^\secpar}}
+}
+
+\DeclareOption{lambda}{
+ \renewcommand{\secpar}{\ensuremath{\lambda}}
+ \renewcommand{\secparam}{\ensuremath{1^\secpar}}
+}
+
+\DeclareOption*{%
+ \PackageError{crypto code}{Unknown option ‘\CurrentOption’}%
+}
+
+\ExecuteOptions{n}
+
+\ProcessOptions\relax
+
+%amsfonts
+\ifpc@amsfonts
+ \RequirePackage{amsfonts}
+\fi
+\RequirePackage{xcolor}
+\RequirePackage{calc}
+\RequirePackage{tikz}
+\usetikzlibrary{positioning,calc}
+\RequirePackage{ifthen}
+\RequirePackage{xargs}
+\RequirePackage{pgf}
+%\RequirePackage{mathabx}
+\RequirePackage{forloop}
+\RequirePackage{array}
+\RequirePackage{xparse}
+%\RequirePackage{l3regex}
+\RequirePackage{expl3}
+\RequirePackage{pbox}
+\RequirePackage{varwidth}
+\RequirePackage{suffix}
+\RequirePackage{etoolbox}
+\RequirePackage{etex}
+%\RequirePackage{etextools}
+\RequirePackage{environ}
+%\RequirePackage{xspace}
+\RequirePackage{xkeyval}
+
+\ifpc@advantage
+ \newcommand{\pcadvantagesuperstyle}[1]{\mathrm{\MakeLowercase{#1}}}
+ \newcommand{\pcadvantagesubstyle}[1]{#1}
+ \newcommandx*{\advantage}[3][3=(\secpar)]{\ensuremath{\mathsf{Adv}^{\pcadvantagesuperstyle{#1}}_{\pcadvantagesubstyle{#2}}#3}}
+\fi
+
+\ifpc@primitives
+ % zero knowledge
+ \providecommand{\prover}{\pcalgostyle{P}}
+ \providecommand{\verifier}{\pcalgostyle{V}}
+ \providecommand{\nizk}{\pcalgostyle{NIZK}}
+
+ % hash
+ \providecommand{\hash}{\pcalgostyle{H}}
+ \providecommand{\gash}{\pcalgostyle{G}}
+ \providecommand{\fash}{\pcalgostyle{F}}
+
+ % encryption
+ \providecommand{\enc}{\pcalgostyle{Enc}}
+ \providecommand{\dec}{\pcalgostyle{Dec}}
+
+ % signatures
+ \providecommand{\sig}{\pcalgostyle{Sig}}
+ \providecommand{\verify}{\pcalgostyle{Vf}}
+
+ % obfuscation
+ \providecommand{\obf}{\pcalgostyle{O}}
+ \providecommand{\iO}{\pcalgostyle{iO}}
+ \providecommand{\diO}{\pcalgostyle{diO}}
+
+ % PRF, PRG
+ \providecommand{\prf}{\pcalgostyle{PRF}}
+ \providecommand{\prg}{\pcalgostyle{PRG}}
+
+ % Mac
+ \providecommand{\mac}{\pcalgostyle{MAC}}
+
+ % puncture
+ \providecommand{\puncture}{\pcalgostyle{Puncture}}
+
+ % Misc
+ \providecommand{\source}{\pcalgostyle{S}}
+ \providecommand{\predictor}{\pcalgostyle{P}}
+ \providecommand{\sam}{\pcalgostyle{Sam}}
+ \providecommand{\dist}{\pcalgostyle{D}}
+ \providecommand{\distinguisher}{\pcalgostyle{Dist}}
+ \providecommand{\simulator}{\pcalgostyle{Sim}}
+ \providecommand{\ext}{\pcalgostyle{Ext}}
+ \providecommand{\extractor}{\ext}
+\fi
+
+%%
+% math hyphen
+\mathchardef\pcmathhyphen ="2D
+
+%%%
+% order of growth helper
+\ifpc@orderofgrowth
+\providecommand{\olrk}[1]{\ifx\nursymbol#1\else\!\!\mskip4.5mu plus 0.5mu\left(\mskip0.5mu plus0.5mu #1\mskip1.5mu plus0.5mu \right)\fi}
+
+\providecommand{\elrk}[1]{\ifx\nursymbol#1\else\!\!\mskip4.5mu plus0.5mu\left[\mskip0.5mu plus0.5mu #1\mskip1.5mu plus0.5mu \right]\fi}
+
+\providecommand{\clrk}[1]{\ifx\nursymbol#1\else\!\!\mskip4.5mu plus0.5mu\left\{\mskip0.5mu plus0.5mu #1\mskip1.5mu plus0.5mu \right\}\fi}
+\fi
+
+\ifpc@algorithmstyle
+ \providecommand{\pcalgostyle}[1]{\ensuremath{\mathsf{#1}}}
+\fi
+
+%%%
+% create command to measure width of align
+%
+\newcommand{\@settowidthofalign}[2]{%
+ \setbox\z@=\vbox{\@pseudocodecodesize
+ \begin{flalign*}
+ #2
+ \ifmeasuring@\else\global\let\got@maxcolwd\maxcolumn@widths\fi
+ \end{flalign*}
+ }%
+ \begingroup
+ \def\or{+}\edef\x{\endgroup#1=\dimexpr\got@maxcolwd\relax}\x}
+
+\newcommand{\@settowidthofaligned}[2]{%
+\settowidth{#1}{\@pseudocodesubcodesize$\begin{aligned}#2\end{aligned}$}}
+
+% check for draft mode
+\def\@pc@ifdraft{\ifdim\overfullrule>\z@
+ \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}
+
+% run stuff in an empty box
+\newcommand{\@pcexecuteblindly}[1]{%
+ \setbox\z@=\vbox{#1 }}
+
+% copy label command
+\AtBeginDocument{
+ \let\pc@original@label\ltx@label
+}
+
+
+%%%%%%
+\newcommand*{\@pc@globaladdtolength}[2]{%
+\addtolength{#1}{#2}%
+\global#1=#1\relax}
+
+\newcommand*{\@pc@globalsetlength}[2]{%
+\setlength{#1}{#2}%
+\global#1=#1\relax}
+
+
+
+%%%%%
+% spaces before and after pseudo codes (left and right)
+\providecommand{\beforepcskip}{2pt}
+\providecommand{\afterpcskip}{0pt}
+
+%%%
+% a global counter of the number of times the pseudocode command was triggered
+\newcounter{@pc@global@pc@cnt}
+\newcounter{@pc@global@pc@nestcnt}
+
+%%%
+% Fix hyperref package.. gnarl http://tex.stackexchange.com/questions/130319/incompatibility-between-etoolbox-and-hyperref
+\providecommand{\pcfixhyperref}{
+\global\let\textlabel\label
+\global\let\pc@original@label\textlabel
+%\global\let\pc@original@label\relax
+%\global\let\label\relax
+}
+
+\newcounter{@spacecounter}
+\providecommand{\spacetoindent}{1}
+\newenvironment{@withspaces}
+ {\obeyspaces\begingroup\lccode`~=` \lowercase{\endgroup\let~}\ }
+ {}
+
+%%%%%%%%%%%%%%
+% a latex3 string substitution.
+\ExplSyntaxOn
+\tl_new:N \l_pc_strsub_input_tl
+\tl_new:N \l_pc_strsub_search_tl
+\tl_new:N \l_pc_strsub_replace_tl
+
+\NewDocumentCommand{\@pc@stringsubstitution}{mmm}
+ {
+ \tl_set:Nn \l_pc_strsub_input_tl { #1 }
+ \tl_set:Nn \l_pc_strsub_search_tl { #2 }
+ \tl_set:Nn \l_pc_strsub_replace_tl { #3 }
+% \tl_show_analysis:N \l_pc_strsub_input_tl % uncomment for debugging
+% \tl_show_analysis:N \l_pc_strsub_search_tl % uncomment for debugging
+% \tl_show_analysis:N \l_pc_strsub_replace_tl % uncomment for debugging
+ \regex_replace_all:nnN
+ { (\W)\u{l_pc_strsub_search_tl} } %only match if keyword does not have a word character preceding
+ { \1\u{l_pc_strsub_replace_tl} }
+ \l_pc_strsub_input_tl
+ % \tl_show_analysis:N \l_tmpa_tl % uncomment for debugging
+ \tl_use:N \l_pc_strsub_input_tl
+ }
+
+ % same as \@pc@stringsubstitution but without requiring the extra non word character
+ \NewDocumentCommand{\@pc@spacesubstitution}{mmm}
+ {
+ \tl_set:Nn \l_pc_strsub_input_tl { #1 }
+ \tl_set:Nn \l_pc_strsub_search_tl { #2 }
+ \tl_set:Nn \l_pc_strsub_replace_tl { #3 }
+% \tl_show_analysis:N \l_pc_strsub_input_tl % uncomment for debugging
+% \tl_show_analysis:N \l_pc_strsub_search_tl % uncomment for debugging
+% \tl_show_analysis:N \l_pc_strsub_replace_tl % uncomment for debugging
+ \regex_replace_all:nnN
+ { \u{l_pc_strsub_search_tl} }
+ { \u{l_pc_strsub_replace_tl} }
+ \l_pc_strsub_input_tl
+ % \tl_show_analysis:N \l_tmpa_tl % uncomment for debugging
+ \tl_use:N \l_pc_strsub_input_tl
+ }
+
+
+\ExplSyntaxOff
+
+%%%%%%%%
+% line numbers
+%%%%%%%%
+% The following commands handle line numbering within the pseudocode command. The
+% pseudocode command itself does need to do some counter magic
+\newcounter{pclinenumber}
+\newcounter{Hpclinenumber} % make hyperref happy
+\newcounter{@pclinenumber}
+\newcounter{H@pclinenumber} % make hyperref happy
+\newcounter{@pclinenumbertmp}
+\newcounter{pcgamecounter}
+\newcounter{Hpcgamecounter}
+\newcounter{pcrlinenumber}
+\newcounter{Hpcrlinenumber}
+\newcounter{@pcrlinenumbertmp}
+
+% separators
+\providecommand{\pclnseparator}{:}
+\providecommand{\pcrlnseparator}{\hspace{1pt}}
+
+% spacing for linenumbers
+\providecommand{\pclnspace}{0pt}
+\providecommand{\pclnrspace}{5pt}
+
+\renewcommand{\the@pclinenumber}{\thepclinenumber}
+\providecommand{\@pcln}{%
+\refstepcounter{@pclinenumber}%
+\stepcounter{H@pclinenumber}%
+}
+
+% left align line numbers
+\providecommand{\pcln}[1][]{%
+\refstepcounter{pclinenumber}%
+\stepcounter{Hpclinenumber}%
+\ifthenelse{\equal{#1}{}}{}{%
+%keep hyperref happy
+\ifmeasuring@\else\pc@original@label{#1}\fi%
+}%
+\hspace{\pclnspace}\text{\scriptsize\arabic{pclinenumber}}\pclnseparator\quad}%
+
+
+% right align line numbers (same counter)
+\providecommand{\pclnr}{%
+\refstepcounter{pclinenumber}%
+\quad\text{\scriptsize\pcrlnseparator\arabic{pclinenumber}}\hspace{5pt}}
+
+% right align line numbers different counter
+\providecommand{\pcrln}{
+\refstepcounter{pcrlinenumber}%
+\stepcounter{Hpcrlinenumber}%
+\text{\scriptsize\pcrlnseparator\arabic{pcrlinenumber}}\hspace{\pclnrspace}}
+
+
+%%%
+% indentation
+\newlength{\@pcindentwidth}
+\providecommand{\pcind}[1][1]{%
+\setlength{\@pcindentwidth}{\widthof{\ensuremath{\quad}}*#1}%
+\ensuremath{\mathmakebox[\@pcindentwidth]{}}}
+
+
+% create length
+\newlength{\@pc@minipage@length}
+\newlength{\@pc@alt@minipage@length}
+
+% backward games
+\newcommand{\@withingame}{false}
+\newcommand{\@withinbxgame}{false}
+\newcommand{\@bxgameheader}{}
+
+
+%%%%%%%%%%%%
+% The pseudocode Command
+%%%%%
+\newlength{\@pc@length@tmp@width@vstack}
+
+
+\newcommand{\@pc@beginnewline}{%
+\@pseudocodelinenumber\@pc@and\@pcln%
+%checkspace
+\ifthenelse{\equal{\@pseudocodespace}{auto}}%
+{\expandafter\pcind\expandafter[\value{@pc@indentationlevel}]}%
+{}%
+%beginmode
+\@pc@modebegin}
+\newcommand{\@pc@and}{&}
+\newcommand{\@pc@and@wrap@end}{\@pc@modeend&}
+\newcommand{\@pc@and@wrap@start}{\@pc@beginnewline}
+\newcommand{\pctabname}{>}
+\newcommand{\pcdbltabname}{<}
+\newcommand{\pcindentname}{t}
+
+
+
+\newcommand*\@pseudocodehead{}
+\newcommand*\@pseudocodewidth{}
+\newcommand*\@pseudocodexshift{0pt}
+\newcommand*\@pseudocodeyshift{0pt}
+\newcommand*\@pseudocodelinenumber{}
+\newcommand*\@pseudocodebeforeskip{0ex}
+\newcommand*\@pseudocodeafterskip{0ex}
+\newcommand*\@pseudocodelnstart{0}
+\newcommand*\@pseudocodelnstartright{0}
+\newcommand*\@pseudocodesyntaxhighlighting{}
+\newcommand*\@pseudocodenodraft{false}
+
+\newcommand*\@pseudocodeheadlinesep{0em}
+\newcommand*\@pseudocodebodylinesep{-0.5\baselineskip}
+
+\newcommand*\@pseudocodecolsep{0em}
+\newcommand*\@pseudocodeaddtolength{2pt}
+
+\newcommand*\@pseudocodecodesize{\small}
+\newcommand*\@pseudocodesubcodesize{\footnotesize}
+
+%%%%%%%%%%%%%%
+% Define keywords for the automatic syntax highlighting
+% the accompanying add provides additional keywords.
+% The space version for automatic spacing
+\newcommand*\@pseudocodekeywordsindent{for ,foreach ,if ,repeat ,while }
+\newcommand*\@pseudocodekeywordsunindent{endfor,endforeach,fi,endif,until ,endwhile}
+\newcommand*\@pseudocodekeywordsuninindent{else if,elseif, else}
+\newcommand*\@pseudocodekeywords{return ,{ do }, in ,new ,null ,null,true ,true,{ to },false ,false,{ then },done ,done}
+\newcommand*\@pseudocodeaddkeywords{}
+\newcommand*\@pseudocodealtkeywords{}
+\begin{@withspaces}
+\global\def\@pseudocodekeywordsspace{for,endfor,foreach,endforeach,return,do,in,new,if,null,true,until,to,false,then,repeat,else if,elseif,while,endwhile,else,done,fi,endif}
+\end{@withspaces}
+
+
+\define@key{pseudocode}{codesize}[\small]{\renewcommand*\@pseudocodecodesize{#1}}
+\define@key{pseudocode}{subcodesize}[\small]{\renewcommand*\@pseudocodesubcodesize{#1}}
+\define@key{pseudocode}{head}[]{\renewcommand*\@pseudocodehead{#1}}
+\define@key{pseudocode}{width}[]{\renewcommand*\@pseudocodewidth{#1}}
+\define@key{pseudocode}{xshift}[]{\renewcommand*\@pseudocodexshift{#1}}
+\define@key{pseudocode}{yshift}[]{\renewcommand*\@pseudocodeyshift{#1}}
+\define@key{pseudocode}{linenumbering}[on]{\ifthenelse{\equal{#1}{on}}{\renewcommand*\@pseudocodelinenumber{\pcln}}{\renewcommand*\@pseudocodelinenumber{}}}
+\define@key{pseudocode}{beforeskip}[]{\renewcommand*\@pseudocodebeforeskip{#1}}
+\define@key{pseudocode}{afterskip}[]{\renewcommand*\@pseudocodeafterskip{#1}}
+\define@key{pseudocode}{lnstart}[0]{\renewcommand*\@pseudocodelnstart{#1}}
+\define@key{pseudocode}{lnstartright}[0]{\renewcommand*\@pseudocodelnstartright{#1}}
+\define@key{pseudocode}{colsep}[0em]{\renewcommand*\@pseudocodecolsep{#1}}
+\define@key{pseudocode}{headlinesep}[0em]{\renewcommand*\@pseudocodeheadlinesep{#1}}
+\define@key{pseudocode}{bodylinesep}[0em]{\renewcommand*\@pseudocodebodylinesep{#1}}
+\define@key{pseudocode}{addtolength}[2pt]{\renewcommand*\@pseudocodeaddtolength{#1}}
+\define@key{pseudocode}{mode}[math]{%
+\ifthenelse{\equal{#1}{text}}{%
+\renewcommand*\@pc@modebegin{\begin{varwidth}{\textwidth}%
+%introduce line magic for text mode
+\let\@pc@lb\\%
+\renewcommandx*{\\}[2][1=,2=]{\@pc@modeend\@pc@and \ifthenelse{\equal{####1}{}}{\@pc@lb}{\@pc@lb[####1]}####2 \@pc@beginnewline}%
+\def\pclb{\let\\\@pc@lb\relax\@pc@modeend\\}%
+}%
+\renewcommand*\@pc@modeend{\end{varwidth}}
+}{}%
+}
+\define@key{pseudocode}{nodraft}[true]{\renewcommand*\@pseudocodenodraft{#1}}
+\define@key{pseudocode}{keywords}[]{\renewcommand*\@pseudocodekeywords{#1}}
+\define@key{pseudocode}{keywordsindent}[]{\renewcommand*\@pseudocodekeywordsindent{#1}}
+\define@key{pseudocode}{keywordsunindent}[]{\renewcommand*\@pseudocodekeywordsunindent{#1}}
+\define@key{pseudocode}{keywordsuninindent}[]{\renewcommand*\@pseudocodekeywordsuninindent{#1}}
+\define@key{pseudocode}{addkeywords}[]{\renewcommand*\@pseudocodeaddkeywords{#1}}
+\define@key{pseudocode}{altkeywords}[]{\renewcommand*\@pseudocodealtkeywords{#1}}
+\define@key{pseudocode}{syntaxhighlight}[]{\renewcommand*\@pseudocodesyntaxhighlighting{#1}}
+
+\newcommand{\@pc@modebegin}{}
+\newcommand{\@pc@modeend}{}
+\newcommand{\@pc@thecontent}{}
+
+\newcommand{\@pc@syntaxhighlight}[1]{%
+\ifthenelse{\equal{\@pseudocodesyntaxhighlighting}{auto}}{%
+\def\@shtmp{#1}% first step
+\ifthenelse{\equal{\@pseudocodespace}{keep}}
+ {\edef\@tmpkeywords{\@pseudocodekeywordsspace,\@pseudocodeaddkeywords}}
+ {\ifthenelse{\equal{\@pseudocodespace}{auto}}
+ {\edef\@tmpkeywords{\@pseudocodekeywords,\@pseudocodeaddkeywords}}
+ {\edef\@tmpkeywords{\@pseudocodekeywords,\@pseudocodekeywordsindent,\@pseudocodekeywordsunindent,\@pseudocodekeywordsuninindent,\@pseudocodeaddkeywords}}}
+\foreach \@pckw in \@tmpkeywords{%
+\ifthenelse{\equal{\@pckw}{}}{}{%
+% we are doing a simple strsub and storing the result (globally) in @shtmp
+\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \gdef\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \@shtmp\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ {\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \@pc@stringsubstitution\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ {\expandafter\expandafter\expandafter\@shtmp\expandafter\expandafter\expandafter
+ }\expandafter\expandafter\expandafter{\expandafter\@pckw\expandafter}\expandafter{\expandafter\@pc@highlight\expandafter{\@pckw}}}%
+}% alt keywords
+}%
+\foreach \@pckw in \@pseudocodealtkeywords{%
+\ifthenelse{\equal{\@pckw}{}}{}{%
+% we are doing a simple strsub and storing the result (globally) in @shtmp
+\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \gdef\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \@shtmp\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ {\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \@pc@stringsubstitution\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ {\expandafter\expandafter\expandafter\@shtmp\expandafter\expandafter\expandafter
+ }\expandafter\expandafter\expandafter{\expandafter\@pckw\expandafter}\expandafter{\expandafter\@pc@althighlight\expandafter{\@pckw}}}%
+}%
+}%
+%%%%
+% if automatic spacing
+\ifthenelse{\equal{\@pseudocodespace}{auto}}
+{%
+\foreach \@pckw in \@pseudocodekeywordsindent{% indentation keywords
+\ifthenelse{\equal{\@pckw}{}}{}{%
+% we are doing a simple strsub and storing the result (globally) in @shtmp
+\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \gdef\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \@shtmp\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ {\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \@pc@stringsubstitution\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ {\expandafter\expandafter\expandafter\@shtmp\expandafter\expandafter\expandafter
+ }\expandafter\expandafter\expandafter{\expandafter\@pckw\expandafter}\expandafter{\expandafter\@pc@highlightindent\expandafter{\@pckw}}}%
+}}%
+\foreach \@pckw in \@pseudocodekeywordsunindent{% unindentation keywords
+\ifthenelse{\equal{\@pckw}{}}{}{%
+% we are doing a simple strsub and storing the result (globally) in @shtmp
+\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \gdef\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \@shtmp\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ {\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \@pc@stringsubstitution\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ {\expandafter\expandafter\expandafter\@shtmp\expandafter\expandafter\expandafter
+ }\expandafter\expandafter\expandafter{\expandafter\@pckw\expandafter}\expandafter{\expandafter\@pc@highlightunindent\expandafter{\@pckw}}}%
+}}%
+\foreach \@pckw in \@pseudocodekeywordsuninindent{% uninindentation keywords
+\ifthenelse{\equal{\@pckw}{}}{}{%
+% we are doing a simple strsub and storing the result (globally) in @shtmp
+\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \gdef\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \@shtmp\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ {\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ \@pc@stringsubstitution\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
+ {\expandafter\expandafter\expandafter\@shtmp\expandafter\expandafter\expandafter
+ }\expandafter\expandafter\expandafter{\expandafter\@pckw\expandafter}\expandafter{\expandafter\@pc@highlightuninindent\expandafter{\@pckw}}}%
+}}%
+}{}%
+% return result
+\@shtmp%
+}{#1}% nothing to highlight
+}
+
+\newcommand{\@pc@highlight}[1]{%
+\ifthenelse{\equal{\@pseudocodespace}{keep}}
+ {\highlightkeyword[]{#1}}%
+ {\highlightkeyword[]{\@pc@spacesubstitution{#1}{ }{~}}}%
+}
+
+\newcommand{\@pc@highlightindent}[1]{%
+\@pc@increaseindent\@pc@highlight{#1}%
+}
+
+\newcommand{\@pc@highlightunindent}[1]{%
+\@pc@decreaseindent\@pc@highlight{#1}%
+}
+
+\newcommand{\@pc@highlightuninindent}[1]{%
+\@pc@tmpdecreaseindent\@pc@highlight{#1}%
+}
+
+\newcommand{\@pc@althighlight}[1]{%
+\ifthenelse{\equal{\@pseudocodespace}{keep}}
+ {\highlightaltkeyword{#1}}%
+ {\highlightaltkeyword{\@pc@spacesubstitution{#1}{ }{~}}}%
+}
+
+%%%%%%%%%%%%%%%%%
+% Allow for spacing
+\newcommand{\@withinspaces}{false}%
+\newcommand{\@keepspaces}{%
+\renewcommand{\@withinspaces}{true}\@withspaces%
+}
+\newcommand{\@pc@endgroupafterpc}{}
+
+\newcommand*\@pseudocodespace{}
+\define@key{pcspace}{space}[]{\ifthenelse{\equal{#1}{keep}}{\@keepspaces}{}\renewcommand*\@pseudocodespace{#1}}
+
+%%% automatic indentation
+\newcounter{@pc@indentationlevel}
+\newcommand{\@pc@increaseindent}{\addtocounter{@pc@indentationlevel}{1}}
+\newcommand{\@pc@decreaseindent}{\ifthenelse{\equal{\@pseudocodespace}{auto}}{\pcind[-1]}{}\addtocounter{@pc@indentationlevel}{-1}}
+\newcommand{\@pc@tmpdecreaseindent}{\ifthenelse{\equal{\@pseudocodespace}{auto}}{\pcind[-1]}{}}
+
+% store original halign
+\let\@pc@halign\halign%
+
+%% Check if the pseudocode command is called with an optional argument
+\providecommand{\pseudocode}{%
+\begingroup%
+\renewcommand{\@withinspaces}{false}%
+\@ifnextchar[%]
+ {\@pseudocodeA}%
+ {\@pseudocode[]}%
+}
+
+\def\@pseudocodeA[#1]{%
+\setkeys*{pcspace}{#1}%test if there is a space assignment within the keys .. make the necessary arrangements and call the actual method
+\@pseudocode[#1]%
+}
+
+\def\@pseudocode[#1]#2{%
+\begingroup%
+\setkeys{pseudocode}[space]{#1}%ignore the space key.
+% check draft mode and disable syntax highlighting
+\@pc@ifdraft{\ifthenelse{\equal{\@pseudocodenodraft}{true}}{}{\renewcommand\@pseudocodesyntaxhighlighting{}}}{}%
+%
+%
+\addtocounter{@pc@global@pc@nestcnt}{1}%
+% allow for tikz usage
+\@pc@ensureremember%
+%
+% create tabbing command
+\ifcsname \pctabname\endcsname%
+\expandafter\renewcommand\csname \pctabname\endcsname{\@pc@modeend&\@pc@modebegin}%
+\else%
+\expandafter\newcommand\csname \pctabname\endcsname{\@pc@modeend&\@pc@modebegin}%
+\fi%
+\ifcsname \pcdbltabname\endcsname%
+\expandafter\renewcommand\csname \pcdbltabname\endcsname{\@pc@modeend&&\@pc@modebegin}%
+\else%
+\expandafter\newcommand\csname \pcdbltabname\endcsname{\@pc@modeend&&\@pc@modebegin}%
+\fi%
+% create indent command
+\expandafter\let\csname \pcindentname\endcsname\pcind%
+%
+%store and wrap (do syntax highlighting) argument
+\renewcommand{\@pc@thecontent}{\@pc@and@wrap@start\@pc@syntaxhighlight{#2}\@pc@and@wrap@end}%
+%
+%take care of counters
+\stepcounter{@pc@global@pc@cnt}%
+\setcounter{pclinenumber}{\@pseudocodelnstart}%
+\setcounter{pcrlinenumber}{\@pseudocodelnstartright}%
+\setlength{\@pc@minipage@length}{0pt}%
+\setlength{\@pc@alt@minipage@length}{0pt}%
+\setcounter{@pclinenumbertmp}{\value{pclinenumber}}%
+\setcounter{@pcrlinenumbertmp}{\value{pcrlinenumber}}%
+%
+% vertical space
+\vspace{\@pseudocodeyshift}%
+%\setlength{\abovedisplayskip}{0pt}%
+%\setlength{\belowdisplayskip}{0pt}%
+%\setlength{\abovedisplayshortskip}{0pt}%
+%\setlength{\belowdisplayshortskip}{0pt}%
+%
+%
+% line magic
+\ifthenelse{\value{@pc@global@pc@nestcnt}=1}{%
+\let\@pc@halign\halign%
+\def\halign{%
+\renewcommand{\label}[1]{\ifmeasuring@\else\pc@original@label{####1}\fi}%
+\let\@pc@lb\\%
+\renewcommandx*{\\}[2][1=,2=]{\@pc@modeend\@pc@and \ifthenelse{\equal{####1}{}}{\@pc@lb}{\@pc@lb[####1]}####2 \@pc@beginnewline}%
+\def\pclb{\let\\\@pc@lb\relax\@pc@modeend\\}%
+\@pc@halign}%
+}{}%
+%
+%align column separation
+\renewcommand*{\minalignsep}{\@pseudocodecolsep}%
+%
+% if no width is set compute width and store in circuitlength
+\ifthenelse{\equal{\@pseudocodewidth}{}}{%
+% compute length of pseudocode
+\ifthenelse{\value{@pcsubprogstep}=0}{%
+\@settowidthofalign{\@pc@minipage@length}{\@pc@thecontent}%
+}{%
+\@settowidthofaligned{\@pc@minipage@length}{\@pc@thecontent}%
+}%
+%compute length of header
+\addtolength{\@pc@alt@minipage@length}{\widthof{\@pseudocodehead}}%
+% use header length if longer and add some points for good measure
+\ifdim\@pc@alt@minipage@length>\@pc@minipage@length%
+\setlength{\@pc@minipage@length}{\@pc@alt@minipage@length}%
+\fi%
+\addtolength{\@pc@minipage@length}{\@pseudocodeaddtolength}%
+}{\addtolength{\@pc@minipage@length}{\@pseudocodewidth}}%
+% reset counter
+\setcounter{pclinenumber}{\value{@pclinenumbertmp}}%
+\setcounter{pcrlinenumber}{\value{@pcrlinenumbertmp}}%
+\setcounter{@pc@indentationlevel}{0}%
+% begin actual output
+%
+%
+%do the actual mini page
+\hspace{\@pseudocodexshift}%
+\begin{minipage}[t]{\@pc@minipage@length}%
+\ifthenelse{\value{@pcsubprogstep}=0}{%
+\pc@display@pseudocode{\@pseudocodehead}{\@pc@thecontent}%
+}{% if sub procedure
+\pc@display@subcode{\@pseudocodehead}{\@pc@thecontent}%
+}%
+\end{minipage}%
+\hspace{\afterpcskip}%
+% tikz usage
+\@pc@releaseremember%
+\addtocounter{@pc@global@pc@nestcnt}{-1}%
+\endgroup%
+% close spacing and potentially a single group generated by the space tester
+\ifthenelse{\equal{\@withinspaces}{true}}{\end@withspaces}{}%
+\endgroup%
+}
+
+\newcommand{\@pc@gameheader}[2]{%
+\tikz{\gdef\i{\thepcgamecounter}%
+\node[anchor=base,#2,inner sep=0.05em,outer sep=0] (gamenode\i) {#1\vphantom{$\sum^A_{A_b}$}};
+\ifthenelse{\equal{\@withinbxgame}{true}}
+ {\node[draw,anchor=base, above=0.1cm of gamenode\i] (bgamenode\i) {\@bxgameheader\vphantom{$\sum^A_{A_b}$}};}
+ {}%
+}%
+}
+
+\let\pclb\relax
+%
+\newcommand{\pc@display@pseudocode}[2]{%
+\ifthenelse{\equal{#1}{}}{\vspace{-1\baselineskip}\@pseudocodecodesize}{%
+\ifthenelse{\equal{\@withingame}{true}}{%
+\@pc@gameheader{#1}{}\ifthenelse{\equal{\@pc@secondheader}{true}}{\addtocounter{pcgamecounter}{1}\@pc@gameheader{#1}{draw}}{}%
+\vspace{0.2em}%
+}{#1\vphantom{$\sum^A_{A_b}$}}%
+\vspace{\@pseudocodeheadlinesep}\hrule\vspace{\@pseudocodebodylinesep}\@pseudocodecodesize}%
+\begin{flalign*}#2\end{flalign*}%
+}
+
+
+\newcommand{\pc@display@subcode}[2]{%
+\begingroup%
+\ifthenelse{\equal{#1}{}}{}{#1\vphantom{$\sum^A_{A_b}$} %
+\vspace{\@pseudocodeheadlinesep}\hrule \vspace{\baselineskip}\vspace{\@pseudocodebodylinesep}}%
+\@pseudocodesubcodesize%
+$\begin{aligned}#2\end{aligned}$%
+\endgroup%
+}
+
+
+\newcommand{\@pc@gettikzwidth}[2]{ % #1 = width, #2 = height
+ \pgfextractx{\@tempdima}{\pgfpointdiff{\pgfpointanchor{current bounding box}{south west}}
+ {\pgfpointanchor{current bounding box}{north east}}}
+ \global#1=\@tempdima
+ \pgfextracty{\@tempdima}{\pgfpointdiff{\pgfpointanchor{current bounding box}{south west}}
+ {\pgfpointanchor{current bounding box}{north east}}}
+ \global#2=\@tempdima
+}
+
+
+%%%%%%%%%%%%%%%%%%%
+% remember pictues
+\newcounter{@pc@remember}
+
+\newcommand{\@pc@ensureremember}{%
+\ifthenelse{\value{@pc@remember}=0}{\tikzstyle{every picture}+=[remember picture]}{}%
+\addtocounter{@pc@remember}{1}
+}
+
+\newcommand{\@pc@releaseremember}{%
+\addtocounter{@pc@remember}{-1}%
+\ifthenelse{\value{@pc@remember}=0}{\tikzstyle{every picture}-=[remember picture]}{}%
+}
+
+
+%%%%%%%%%%%%%%%%%%%
+% pcimage
+\newenvironment{pcimage}{%
+\begingroup\@pc@ensureremember%
+}{%
+\@pc@releaseremember\endgroup%
+}
+
+\newcommand*\@pcnodecontent{}
+\newcommand*\@pcnodestyle{}
+\newcommand*\@pcnodedraw{}
+\define@key{pcnode}{content}[]{\renewcommand*\@pcnodecontent{#1}}
+\define@key{pcnode}{style}[]{\renewcommand*\@pcnodestyle{#1}}
+\define@key{pcnode}{draw}[]{\renewcommand*\@pcnodedraw{#1}}
+
+\newcommandx*{\pcnode}[2][2=]{%
+\begingroup\setkeys{pcnode}{#2}%
+\tikzset{PCNODE-STYLE/.style/.expand once=\@pcnodestyle}%
+\begin{tikzpicture}[inner sep=0ex,baseline=0pt]%
+\node[PCNODE-STYLE] (#1) {\@pcnodecontent}; %
+\end{tikzpicture}%
+\ifdefempty{\@pcnodedraw}{}{%
+\begin{tikzpicture}[overlay,inner sep=0ex,baseline=0pt]\@pcnodedraw\end{tikzpicture}
+}%
+\endgroup}
+
+\newcommandx*{\pcdraw}[1]{%
+\begin{tikzpicture}[overlay,inner sep=0ex,baseline=0pt]
+#1
+\end{tikzpicture}}
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%
+% Reductions
+\newcommand{\@bb@lastbox}{}
+\newcommand{\@bb@lastoracle}{}
+\newcommand{\@bb@lastchallenger}{}
+
+
+\newlength{\@bb@message@hoffset}
+\newlength{\@bb@query@hoffset}
+\newlength{\@bb@oraclequery@hoffset}
+\newlength{\@bb@challengerquery@hoffset}
+
+\newcounter{@bb@oracle@cnt}
+\newcounter{@bb@oracle@nestcnt}
+\newcounter{@bb@challenger@cnt}
+\newcounter{@bb@challenger@nestcnt}
+
+\newcounter{@bb@env@nestcnt}
+
+\newcommand{\bbroraclenodenameprefix}{ora-}
+\newcommand{\bbrchallengernodenameprefix}{challenger-}
+\newcommand{\bbrenvnodenameprefix}{env-}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Black Box Reduction Enviconment
+\newenvironmentx{bbrenv}[3][1=0pt,3=0pt]{%
+\addtocounter{@bb@env@nestcnt}{1}%
+\renewcommand{\@bb@lastbox}{#2}%
+%
+% reset lengths
+\setlength{\@bb@message@hoffset}{0pt}%
+\setlength{\@bb@query@hoffset}{0pt}%
+\@pc@globalsetlength{\@bb@oraclequery@hoffset}{0pt}%
+\@pc@globalsetlength{\@bb@challengerquery@hoffset}{0pt}%
+%
+%reset oracle counter and oracle query offset
+\ifthenelse{\value{@bb@oracle@nestcnt}=0}
+ {\setcounter{@bb@oracle@cnt}{0}}{}%
+\ifthenelse{\value{@bb@challenger@nestcnt}=0}
+ {\setcounter{@bb@challenger@cnt}{0}}{}%
+%
+\def\@bbendskip{#3}%
+\vspace{#1}%
+\ifthenelse{\value{@bb@env@nestcnt}=1}
+ {\@pc@ensureremember%
+\begin{tikzpicture}
+}{\tikz\bgroup}
+}{%
+\ifthenelse{\value{@bb@env@nestcnt}=1}
+{\end{tikzpicture}%
+\@pc@releaseremember%
+}{\egroup}%
+\vspace{\@bbendskip}%
+\addtocounter{@bb@env@nestcnt}{-1}%
+}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% black box reduction box
+% option keys
+\newcommand*\bbrboxname{}
+\newcommand*\bbrboxnamepos{right}
+\newcommand*\bbrboxnamestyle{}
+\newcommand*\@bbrboxnamepos{below=0.5ex of \@bb@lastbox.north east,anchor=north east}
+\newcommand*\@bbrboxnameposoffset{below left=0cm of phantomname.north west}
+\newcommand*\bbrboxstyle{draw}
+\newcommand*\bbrboxafterskip{}
+\newcommand*\bbrboxminwidth{2cm}
+\newcommand*\bbrboxxshift{0cm}
+\newcommand*\bbrboxyshift{0cm}
+\define@key{bbrbox}{name}[]{\renewcommand*\bbrboxname{#1}}
+\define@key{bbrbox}{namestyle}[]{\renewcommand*\bbrboxnamestyle{#1}}
+\define@key{bbrbox}{namepos}[]{\renewcommand*\bbrboxnamepos{#1}}
+\define@key{bbrbox}{style}[draw]{\renewcommand*\bbrboxstyle{#1}}
+\define@key{bbrbox}{minwidth}[]{\renewcommand*\bbrboxminwidth{#1}}
+\define@key{bbrbox}{minheight}[]{\renewcommand*\bbrboxafterskip{#1}}
+\define@key{bbrbox}{xshift}[]{\renewcommand*\bbrboxxshift{#1}}
+\define@key{bbrbox}{yshift}[]{\renewcommand*\bbrboxyshift{#1}}
+
+
+\NewEnviron{bbrbox}[1][]{%
+\setkeys{bbrbox}{#1}%
+
+\ifthenelse{\equal{\bbrboxnamepos}{center}}
+ {\renewcommand{\@bbrboxnamepos}{below=0.5ex of \@bb@lastbox.north,anchor=north}}{}
+\ifthenelse{\equal{\bbrboxnamepos}{left}}
+ {\renewcommand{\@bbrboxnamepos}{below=0.5ex of \@bb@lastbox.north west,anchor=north west}\renewcommand{\@bbrboxnameposoffset}{below left=1\baselineskip of phantomname.south west}}{}
+\ifthenelse{\equal{\bbrboxnamepos}{top right}}
+ {\renewcommand{\@bbrboxnamepos}{above=0cm of \@bb@lastbox.north east,anchor=south east}}{}
+\ifthenelse{\equal{\bbrboxnamepos}{top center}}
+ {\renewcommand{\@bbrboxnamepos}{above=0cm of \@bb@lastbox.north,anchor=south}}{}
+\ifthenelse{\equal{\bbrboxnamepos}{top left}}
+ {\renewcommand{\@bbrboxnamepos}{above=0cm of \@bb@lastbox.north west,anchor=south west}}{}
+
+
+\tikzset{BBRBOXSTYLE/.style/.expand once=\bbrboxstyle}%
+\tikzset{BBRBOXNAMEPOS/.style/.expand once=\@bbrboxnamepos}%
+\tikzset{BBRBOXNAMESTYLE/.style/.expand once=\bbrboxnamestyle}%
+\tikzset{BBRBOXNAMEPOSOFFSET/.style/.expand once=\@bbrboxnameposoffset}%
+
+\node[inner sep=0pt,outer sep=0pt] (\@bb@lastbox-tmpouter) {};
+\node[inner sep=.3333em,anchor=north,BBRBOXSTYLE,below right=\bbrboxyshift and \bbrboxxshift of \@bb@lastbox-tmpouter] (\@bb@lastbox) \bgroup
+ \tikz{
+ \node[inner sep=0pt,outer sep=0pt] (phantomname) {}; %minimum width
+ \node[BBRBOXNAMEPOSOFFSET] (inner) {\begin{varwidth}{2\linewidth}\BODY\end{varwidth}};
+ \ifthenelse{\equal{\bbrboxafterskip}{}}{}{
+ \node[below=0cm of inner,minimum height=\bbrboxafterskip] {};
+ }
+ \node[inner sep=0pt,outer sep=0pt,at=(inner.south west)] () {\phantom{\hspace{\bbrboxminwidth}}}; %minimum width
+ }
+\egroup;
+\node[BBRBOXNAMEPOS,BBRBOXNAMESTYLE, inner sep=0.2ex, outer sep=0pt, overlay] () {\bbrboxname};
+}
+
+
+\newcommand*\bbroraclevdistance{\baselineskip}
+\newcommand*\bbroraclehdistance{1.5cm}
+\define@key{bbroracle}{distance}[]{\renewcommand*\bbroraclehdistance{#1}}
+\define@key{bbroracle}{hdistance}[]{\renewcommand*\bbroraclehdistance{#1}}
+\define@key{bbroracle}{vdistance}[]{\renewcommand*\bbroraclevdistance{#1}}
+
+
+% ORACLES
+\newenvironmentx{bbroracle}[2][2=]{%
+ \begingroup
+ \setkeys{bbroracle}{#2}
+ %add to nesting cout
+ \addtocounter{@bb@oracle@nestcnt}{1}
+ %if first oracle, then put it to the right, else stack them vertically
+ \addtocounter{@bb@oracle@cnt}{1}
+ \ifthenelse{\value{@bb@oracle@cnt}=1}{
+ \setlength{\@bb@tmplength@b}{\bbroraclevdistance-\baselineskip}
+ \node[inner sep=0pt,below right=\@bb@tmplength@b and \bbroraclehdistance of \@bb@lastbox.north east,anchor=north west] (\bbroraclenodenameprefix#1) \bgroup
+ }{
+% compute distance of top of last box to bottom of last oracle
+ \coordinate (@bbtmpcoord) at (\@bb@lastbox.north east);
+ \path (@bbtmpcoord);
+ \pgfgetlastxy{\XCoord}{\YCoordA}
+ \coordinate (@bbtmpcoord) at (\bbroraclenodenameprefix \@bb@lastoracle.south west);
+ \path (@bbtmpcoord);
+ \pgfgetlastxy{\XCoord}{\YCoordB}
+ \setlength{\@bb@tmplength@b}{\YCoordA-\YCoordB+\bbroraclevdistance}
+ \node[inner sep=0pt,below right=\@bb@tmplength@b and \bbroraclehdistance of \@bb@lastbox.north east,anchor=north west] (\bbroraclenodenameprefix#1) \bgroup
+ }
+ \global\def\@bb@lastoracle{#1}
+ \begin{bbrenv}{#1}
+}{
+ \end{bbrenv}
+ \egroup;
+
+ \addtocounter{@bb@oracle@nestcnt}{-1}
+ \endgroup
+}
+
+
+\newcommand*\bbrchallengerhdistance{1.5cm}
+\newcommand*\bbrchallengervdistance{\baselineskip}
+\define@key{bbrchallenger}{distance}[]{\renewcommand*\bbrchallengerhdistance{#1}}
+\define@key{bbrchallenger}{hdistance}[]{\renewcommand*\bbrchallengerhdistance{#1}}
+\define@key{bbrchallenger}{vdistance}[]{\renewcommand*\bbrchallengervdistance{#1}}
+
+
+% Challenger
+\newenvironmentx{bbrchallenger}[2][2=]{%
+\begingroup%
+\setkeys{bbrchallenger}{#2}%
+%add to nesting cout
+\addtocounter{@bb@challenger@nestcnt}{1}%
+%if first oracle, then put it to the right, else stack them vertically
+\addtocounter{@bb@challenger@cnt}{1}%
+\ifthenelse{\value{@bb@challenger@cnt}=1}{%
+\setlength{\@bb@tmplength@b}{\bbrchallengervdistance-\baselineskip}%
+\node[inner sep=0pt,below left=\@bb@tmplength@b and \bbrchallengerhdistance of \@bb@lastbox.north west,anchor=north east] (\bbrchallengernodenameprefix#1) \bgroup%
+}{%
+\coordinate (@bbtmpcoord) at (\@bb@lastbox.north west);%
+\path (@bbtmpcoord);%
+\pgfgetlastxy{\XCoord}{\YCoordA}%
+\coordinate (@bbtmpcoord) at (\bbrchallengernodenameprefix \@bb@lastchallenger.south east);%
+\path (@bbtmpcoord);%
+\pgfgetlastxy{\XCoord}{\YCoordB}%
+\setlength{\@bb@tmplength@b}{\YCoordA-\YCoordB+\bbrchallengervdistance}%
+\node[inner sep=0pt,below left=\@bb@tmplength@b and \bbrchallengerhdistance of \@bb@lastbox.north west,anchor=north east] (\bbrchallengernodenameprefix#1) \bgroup%
+}%
+\global\def\@bb@lastchallenger{#1}
+\begin{bbrenv}{#1}%
+}{
+\end{bbrenv}%
+\egroup;%
+\addtocounter{@bb@challenger@nestcnt}{-1}%
+\endgroup%
+\let\msgfrom\bbrchallengerqueryto%
+}
+
+
+
+\newcommandx{\bbrinput}[2][1=0.75cm]{%
+\node[above right=#1 of \@bb@lastbox.north west] (input) {#2};
+\draw[->] (input) --++ (0,-0.75cm);
+}
+
+\newcommandx{\bbroutput}[2][1=0.75cm]{%
+\node[below right=#1 of \@bb@lastbox.south west] (output) {#2};
+\draw[<-] (output) --++ (0,#1);
+}
+
+%%%%%%%%%%%
+% communication
+%temporary lengths
+\newlength{\@bb@com@tmpoffset}
+\newlength{\@bb@tmplength@b}
+\newcommand{\@bb@firstmessageheight}{0.25\baselineskip}
+\newcommand{\@bb@msglength}{1.25cm}
+\newcommand{\@bb@qrylength}{1.5cm}
+
+%keys
+\newcommand*\bbrcomnsidestyle{}
+\newcommand*\bbrcomtopstyle{}
+\newcommand*\bbrcombottomstyle{}
+\newcommand*\bbrcomside{}
+\newcommand*\bbrcomtop{}
+\newcommand*\bbrcombottom{}
+\newcommand*\bbrcomedgestyle{}
+\newcommand*\bbrcomlength{1.25cm}
+\newcommand*\bbrcomtopname{bbrcomtop}
+\newcommand*\bbrcombottomname{bbrcombottom}
+\newcommand*\bbrcomsidename{bbrcomside}
+\newcommand*\bbrcomosidename{bbrcomoside}
+\newcommand*\bbrcombeforeskip{0pt}
+\newcommand*\bbrcomafterskip{0pt}
+\define@key{bbrcom}{sidestyle}[]{\renewcommand*\bbrcomnsidestyle{#1}}
+\define@key{bbrcom}{topstyle}[]{\renewcommand*\bbrcomtopstyle{#1}}
+\define@key{bbrcom}{bottomstyle}[]{\renewcommand*\bbrcombottomstyle{#1}}
+\define@key{bbrcom}{side}[]{\renewcommand*\bbrcomside{#1}}
+\define@key{bbrcom}{top}[]{\renewcommand*\bbrcomtop{#1}}
+\define@key{bbrcom}{bottom}[]{\renewcommand*\bbrcombottom{#1}}
+\define@key{bbrcom}{edgestyle}[]{\renewcommand*\bbrcomedgestyle{#1}}
+\define@key{bbrcom}{length}[]{\renewcommand*\bbrcomlength{#1}}
+\define@key{bbrcom}{topname}[]{\renewcommand*\bbrcomtopname{#1}}
+\define@key{bbrcom}{bottomname}[]{\renewcommand*\bbrcombottomname{#1}}
+\define@key{bbrcom}{sidename}[]{\renewcommand*\bbrcomsidename{#1}}
+\define@key{bbrcom}{osidename}[]{\renewcommand*\bbrcomosidename{#1}}
+\define@key{bbrcom}{beforeskip}[]{\renewcommand*\bbrcombeforeskip{#1}}
+\define@key{bbrcom}{afterskip}[]{\renewcommand*\bbrcomafterskip{#1}}
+
+\newcommand*\bbrbasenodestyle{}
+\newcommand*\bbrbasenodename{bbrtmpname}
+\define@key{bbrabase}{nodestyle}[]{\renewcommand*\bbrbasenodestyle{#1}}
+\define@key{bbrabase}{nodename}[]{\renewcommand*\bbrbasenodename{#1}}
+
+
+\newcommand{\@bb@comsetup}[3]{
+ %load keys
+ \begingroup % for local keys
+
+ \setkeys{bbrcom}{#1}%
+
+ %set styles
+ \tikzset{BBRCOM-SIDESTYLE/.style/.expand once=\bbrcomnsidestyle}%
+ \tikzset{BBRCOM-TOPSTYLE/.style/.expand once=\bbrcomtopstyle}%
+ \tikzset{BBRCOM-BOTTOMSTYLE/.style/.expand once=\bbrcombottomstyle}%
+ \tikzset{BBRCOM-EDGESTYLE/.style/.expand once=\bbrcomedgestyle}%
+
+ % increase space
+ \ifthenelse{\lengthtest{#2<\@bb@firstmessageheight}}
+ {#3{\@bb@firstmessageheight}}
+ {
+ #3{0.75\baselineskip}
+ \ifthenelse{\equal{\bbrcomtop}{}}{}{#3{0.75\baselineskip}}
+ }
+
+ \setlength{\@bb@com@tmpoffset}{#2+\bbrcombeforeskip}%
+}
+
+\newcommand{\@bb@comfinalize}[1]{
+ \ifthenelse{\equal{\bbrcombottom}{}}{}{#1{\baselineskip}}
+ #1{\bbrcomafterskip}
+ \endgroup
+}
+
+\newcommand{\@bbrmsg}[7]{
+ \@bb@comsetup{#1}{#6}{#7}
+ %
+ \node[#3=\@bb@com@tmpoffset and \bbrcomlength of \@bb@lastbox.#4,BBRCOM-SIDESTYLE] (\bbrcomsidename) {\bbrcomside};
+ \path[#2] (\bbrcomsidename) edge[BBRCOM-EDGESTYLE] node[above,BBRCOM-TOPSTYLE] (\bbrcomtopname) {\bbrcomtop} node[below,BBRCOM-BOTTOMSTYLE] (\bbrcombottomname) {\bbrcombottom} (\@bb@lastbox.#5|-\bbrcomsidename) node[inner sep=0pt,outer sep=0pt] (\bbrcomosidename) {};
+ %
+ \@bb@comfinalize{#7}
+}
+
+\newcommandx{\bbrmsgto}[1]{%
+\@bbrmsg{#1}{->}{below left}{north west}{west}{\@bb@message@hoffset}{\bbrmsgspace}
+}
+\newcommandx{\bbrmsgfrom}[1]{%
+\@bbrmsg{#1}{<-}{below left}{north west}{west}{\@bb@message@hoffset}{\bbrmsgspace}
+}
+\newcommandx{\bbrqryto}[1]{%
+\@bbrmsg{#1}{<-}{below right}{north east}{east}{\@bb@query@hoffset}{\bbrqryspace}
+}
+\newcommandx{\bbrqryfrom}[1]{%
+\@bbrmsg{#1}{->}{below right}{north east}{east}{\@bb@query@hoffset}{\bbrqryspace}
+}
+
+\newcommand{\@bbroracleqry}[4]{
+ \@bb@comsetup{#1}{#3}{#4}
+ %
+
+ \path[#2] (\@bb@lastoracle.north west) -- ++ (0,-\@bb@com@tmpoffset) node[inner sep=0pt,outer sep=0pt] (\bbrcomsidename){} edge[BBRCOM-EDGESTYLE] node[above,BBRCOM-TOPSTYLE] (\bbrcomtopname) {\bbrcomtop} node[below,BBRCOM-BOTTOMSTYLE] (\bbrcombottomname) {\bbrcombottom} (\@bb@lastbox.east|-\bbrcomsidename) node[inner sep=0pt,outer sep=0pt] (\bbrcomosidename) {};
+ %
+ \@bb@comfinalize{#4}
+}
+
+\newcommand{\bbroracleqryfrom}[1]{
+ \@bbroracleqry{#1}{->}{\@bb@oraclequery@hoffset}{\bbroracleqryspace}
+}
+
+\newcommand{\bbroracleqryto}[1]{
+ \@bbroracleqry{#1}{<-}{\@bb@oraclequery@hoffset}{\bbroracleqryspace}
+}
+
+\newcommand{\@bbrchallengerqry}[4]{
+ \@bb@comsetup{#1}{#3}{#4}
+ %
+
+ \path[#2] (\@bb@lastchallenger.north east) -- ++ (0,-\@bb@com@tmpoffset) node[inner sep=0pt,outer sep=0pt] (\bbrcomsidename){} edge[BBRCOM-EDGESTYLE] node[above,BBRCOM-TOPSTYLE] (\bbrcomtopname) {\bbrcomtop} node[below,BBRCOM-BOTTOMSTYLE] (\bbrcombottomname) {\bbrcombottom} (\@bb@lastbox.west|-\bbrcomsidename) node[inner sep=0pt,outer sep=0pt] (\bbrcomosidename) {};
+ %
+ \@bb@comfinalize{#4}
+}
+
+\newcommand{\bbrchallengerqryfrom}[1]{
+ \@bbrchallengerqry{#1}{<-}{\@bb@challengerquery@hoffset}{\bbrchallengerqryspace}
+}
+
+\newcommand{\bbrchallengerqryto}[1]{
+ \@bbrchallengerqry{#1}{->}{\@bb@challengerquery@hoffset}{\bbrchallengerqryspace}
+}
+
+
+
+\newcommand*\bbrcomloopleft{}
+\newcommand*\bbrcomloopright{}
+\newcommand*\bbrcomloopcenter{}
+\define@key{bbrcomloop}{left}[]{\renewcommand*\bbrcomloopleft{#1}}
+\define@key{bbrcomloop}{right}[]{\renewcommand*\bbrcomloopright{#1}}
+\define@key{bbrcomloop}{center}[]{\renewcommand*\bbrcomloopcenter{#1}}
+
+\newcommand{\bbrloop}[3]{
+ \begingroup % for local keys
+ \setkeys{bbrcomloop}{#3}%
+
+ \path[->] (#1) edge[bend right=50] node[midway,left] (bbrleft) {\bbrcomloopleft} (#2);
+ \path[->] (#2) edge[bend right=50] node[midway,right] (bbrright) {\bbrcomloopright} (#1);
+ \node[at=($(bbrleft.north west)!0.5!(bbrright.north east)$),anchor=north]() {\bbrcomloopcenter};
+
+ \endgroup
+}
+
+\newcommand*\bbrintertexthoffset{1.5cm}
+\define@key{bbrintertext}{xshift}[]{\renewcommand*\bbrintertexthoffset{#1}}
+
+\newcommand{\@bb@intertextsetup}[1]{
+ %load keys
+ \begingroup % for local keys
+
+ \setkeys{bbrcom,bbrabase,bbrintertext}{#1}%
+
+ \tikzset{BBRBASE-NODESTYLE/.style/.expand once=\bbrbasenodestyle}%
+}
+
+\newcommand{\@bb@intertextfinalize}[1]{
+ #1{\bbrcomafterskip}
+ \endgroup
+}
+
+\newcommand{\@bbrintertext}[6]{
+ \@bb@intertextsetup{#1}
+
+ %introduce space
+ #5{\baselineskip}
+
+ %compute offset
+ \setlength{\@bb@com@tmpoffset}{#4+\@bb@firstmessageheight+\bbrcombeforeskip}%
+
+ %
+ \node[#2=\@bb@com@tmpoffset and \bbrintertexthoffset of \@bb@lastbox.#3,BBRBASE-NODESTYLE] (\bbrbasenodename) {#6};
+ %
+ % compute height of node
+ \coordinate (@bbtmpcoord) at (\bbrbasenodename.north);
+ \path (@bbtmpcoord);
+ \pgfgetlastxy{\XCoord}{\YCoordA}
+ \coordinate (@bbtmpcoord) at (\bbrbasenodename.south);
+ \path (@bbtmpcoord);
+ \pgfgetlastxy{\XCoord}{\YCoordB}
+
+ % update hoffset
+ \setlength{\@bb@tmplength@b}{\YCoordA-\YCoordB}
+ #5{\the\@bb@tmplength@b}
+
+ \@bb@intertextfinalize{#5}
+}
+
+\newcommand{\bbrmsgtxt}[2][]{
+ \@bbrintertext{#1}{below left}{north west}{\@bb@message@hoffset}{\bbrmsgspace}{#2}
+}
+
+\newcommand{\bbrqrytxt}[2][]{
+ \@bbrintertext{#1}{below right}{north east}{\@bb@query@hoffset}{\bbrqryspace}{#2}
+}
+
+\newcommand{\bbrchallengertxt}[2][]{
+\begingroup
+\setlength{\@bb@tmplength@b}{\bbrchallengerhdistance/2}%
+\renewcommand{\bbrintertexthoffset}{\the\@bb@tmplength@b}%
+ \@bbrintertext{#1}{below left}{north west}{\@bb@challengerquery@hoffset}{\bbrchallengerqryspace}{#2}
+\endgroup
+}
+
+\newcommand{\bbroracletxt}[2][]{
+\begingroup
+\setlength{\@bb@tmplength@b}{\bbroraclehdistance/2}%
+\renewcommand{\bbrintertexthoffset}{\the\@bb@tmplength@b}%
+ \@bbrintertext{#1}{below left}{north west}{\@bb@oraclequery@hoffset}{\bbroracleqryspace}{#2}
+\endgroup
+}
+
+\newcommand{\bbrmsgspace}[1]{
+\@pc@globaladdtolength{\@bb@message@hoffset}{#1}
+}
+
+\newcommand{\bbrqryspace}[1]{
+\@pc@globaladdtolength{\@bb@query@hoffset}{#1}
+}
+
+\newcommand{\bbroracleqryspace}[1]{
+\@pc@globaladdtolength{\@bb@oraclequery@hoffset}{#1}
+}
+
+\newcommand{\bbrchallengerqryspace}[1]{
+\@pc@globaladdtolength{\@bb@challengerquery@hoffset}{#1}
+}
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% stacking
+\providecommand{\pccenteraboveskip}{0.5\baselineskip}
+\providecommand{\pccenterbelowskip}{0.5\baselineskip}
+\newenvironment{pccenter}{%
+\setlength\topsep{0pt}\setlength\parskip{0pt}%
+\begin{center}\vspace{\pccenteraboveskip}
+}{%
+\vspace{\pccenteraboveskip}%
+\end{center}}
+
+
+%%%%%%%%%%%%%%%%%%%%%
+% horizontal stacking
+% space before hstacks
+\newlength{\pcbeforehstackskip}
+
+\NewEnviron{pchstack}[1][]{%
+% write out content
+\ifthenelse{\equal{#1}{center}}{%
+\begin{pccenter}\mbox{\hspace{\pcbeforehstackskip}\BODY}\end{pccenter}}{%
+\mbox{\hspace{\pcbeforehstackskip}\BODY}}%
+}
+
+
+%%%%%%%%%%%%%%%%%%%%%%%
+\NewEnviron{pcvstack}[1][]{%
+% display minipage
+\ifthenelse{\equal{#1}{center}}{\begin{pccenter}}{}%
+\begin{varwidth}[t]{2\linewidth}\hspace{0pt}\BODY\end{varwidth}% hspace needed for proper vertical positioning .. strange as it is.
+\ifthenelse{\equal{#1}{center}}{\end{pccenter}}{}%
+}
+
+
+% spacing for stacking
+\newcommand{\pchspace}[1][1em]{\hspace{#1}}
+\newcommand{\pcvspace}[1][\baselineskip]{\par\vspace{#1}}
+
+
+
+
+
+%%%%
+% subprocedures
+\newcounter{@pcsubprogcnt1}
+\newcounter{@pcrsubprogcnt1}
+\newcounter{@pcsubprogcnt2}
+\newcounter{@pcrsubprogcnt2}
+\newcounter{@pcsubprogcnt3}
+\newcounter{@pcrsubprogcnt3}
+\newcounter{@pcsubprogcnt4}
+\newcounter{@pcrsubprogcnt4}
+\newcounter{@pcsubprogcnt5}
+\newcounter{@pcrsubprogcnt5}
+\newcounter{@pcsubprogcnt6}
+\newcounter{@pcrsubprogcnt6}
+\newcounter{@pcsubprogcnt7}
+\newcounter{@pcrsubprogcnt7}
+\newcounter{@pcsubprogcnt8}
+\newcounter{@pcrsubprogcnt8}
+\newcounter{@pcsubprogcnt9}
+\newcounter{@pcrsubprogcnt9}
+\newcounter{@pcsubprogstep}
+
+\newenvironment{subprocedure}{%
+\addtocounter{@pcsubprogstep}{1}%
+% store old counter values
+\setcounter{@pcsubprogcnt\the@pcsubprogstep}{\value{pclinenumber}}%
+\setcounter{@pcrsubprogcnt\the@pcsubprogstep}{\value{pcrlinenumber}}%
+}{%
+\setcounter{pclinenumber}{\value{@pcsubprogcnt\the@pcsubprogstep}}%
+\setcounter{pcrlinenumber}{\value{@pcrsubprogcnt\the@pcsubprogstep}}%
+\addtocounter{@pcsubprogstep}{-1}}
+
+
+%%%%%
+% parameter reordering
+\def\@pseudocodeB#1#2[#3]#4{\setkeys*{pcspace}{#2,#3}\@pseudocode[head={#1#4},#2,#3]}
+\def\@pseudocodeC#1#2#3{\setkeys*{pcspace}{#2}\@pseudocode[head={#1#3},#2]}
+%for no headers
+\def\@pseudocodeE#1#2[#3]{\setkeys*{pcspace}{#2,#3}\@pseudocode[head={#1},#2,#3]}
+\def\@pseudocodeF#1#2{\setkeys*{pcspace}{#2}\@pseudocode[head={#1},#2]}
+
+%%%%%%%%%
+% Define pseudocode command:
+% #1 name
+% #2 code to execute after begingroup
+% #3 head prefix
+% #4 other config
+\newcommand{\createprocedurecommand}[4]{
+ \expandafter\gdef\csname #1\endcsname{%
+\begingroup%
+\renewcommand{\@withinspaces}{false}%
+#2%
+\@ifnextchar[%]
+ {\@pseudocodeB{#3}{#4}}
+ {\@pseudocodeC{#3}{#4}}%
+}%
+}
+
+\newcommand{\createpseudocodecommand}[4]{
+ \expandafter\gdef\csname #1\endcsname{%
+\begingroup%
+\renewcommand{\@withinspaces}{false}%
+#2%
+\@ifnextchar[%]
+ {\@pseudocodeE{#3}{#4}}
+ {\@pseudocodeF{#3}{#4}}%
+}%
+}
+
+
+%%%%%%
+% create procedure
+\createprocedurecommand{procedure}{}{}{}
+
+%%%
+% send message
+\newcommand{\pcshortmessageoffset}{0.5cm}
+\newcommand{\pcdefaultmessagelength}{3.5cm}
+\newcommand{\pcdefaultlongmessagelength}{6cm}
+\newcommand{\pcbeforemessageskip}{0pt}
+\newcommand{\pcaftermessageskip}{10pt}
+\newlength{\pcmessagearrow}
+
+\newcommand*\@pcsendmessagelength{\pcdefaultmessagelength}
+\newcommand*\@pcsendmessagecol{}
+\newcommand*\@pcsendmessagewidth{}
+\newcommand*\@pcsendmessagestyle{}
+\newcommand*\@pcsendmessagetop{}
+\newcommand*\@pcsendmessagebottom{}
+\newcommand*\@pcsendmessageright{}
+\newcommand*\@pcsendmessageleft{}
+\newcommand*\@pcsendmessagetopname{t}
+\newcommand*\@pcsendmessagebottomname{b}
+\newcommand*\@pcsendmessagerightname{r}
+\newcommand*\@pcsendmessageleftname{l}
+\newcommand*\@pcsendmessagetopstyle{}
+\newcommand*\@pcsendmessagebottomstyle{}
+\newcommand*\@pcsendmessagerightstyle{}
+\newcommand*\@pcsendmessageleftstyle{}
+\newcommand*\@pcsendmessagebeforeskip{\pcbeforemessageskip}
+\newcommand*\@pcsendmessageafterskip{\pcaftermessageskip}
+\define@key{pcsendmessage}{centercol}[]{\renewcommand*\@pcsendmessagecol{#1}}
+\define@key{pcsendmessage}{width}[]{\renewcommand*\@pcsendmessagewidth{#1}}
+\define@key{pcsendmessage}{style}[]{\renewcommand*\@pcsendmessagestyle{#1}}
+\define@key{pcsendmessage}{length}[]{\renewcommand*\@pcsendmessagelength{#1}}
+\define@key{pcsendmessage}{top}[]{\renewcommand*\@pcsendmessagetop{#1}}
+\define@key{pcsendmessage}{bottom}[]{\renewcommand*\@pcsendmessagebottom{#1}}
+\define@key{pcsendmessage}{right}[]{\renewcommand*\@pcsendmessageright{#1}}
+\define@key{pcsendmessage}{left}[]{\renewcommand*\@pcsendmessageleft{#1}}
+\define@key{pcsendmessage}{topname}[]{\renewcommand*\@pcsendmessagetopname{#1}}
+\define@key{pcsendmessage}{bottomname}[]{\renewcommand*\@pcsendmessagebottomname{#1}}
+\define@key{pcsendmessage}{rightname}[]{\renewcommand*\@pcsendmessagerightname{#1}}
+\define@key{pcsendmessage}{leftname}[]{\renewcommand*\@pcsendmessageleftname{#1}}
+\define@key{pcsendmessage}{topstyle}[]{\renewcommand*\@pcsendmessagetopstyle{#1}}
+\define@key{pcsendmessage}{bottomstyle}[]{\renewcommand*\@pcsendmessagebottomstyle{#1}}
+\define@key{pcsendmessage}{rightstyle}[]{\renewcommand*\@pcsendmessagerightstyle{#1}}
+\define@key{pcsendmessage}{leftstyle}[]{\renewcommand*\@pcsendmessageleftstyle{#1}}
+\define@key{pcsendmessage}{beforeskip}[]{\renewcommand*\@pcsendmessagebeforeskip{#1}}
+\define@key{pcsendmessage}{afterskip}[]{\renewcommand*\@pcsendmessageafterskip{#1}}
+
+
+\newcommand{\centerincol}[2]{%
+\ifmeasuring@%
+#2%
+\else%
+\makebox[\ifcase\expandafter #1\maxcolumn@widths\fi]{$\displaystyle#2$}%
+\fi%
+}
+
+\newcommand{\@do@sendmessage}[1]{%
+\ifthenelse{\equal{\@pcsendmessagecol}{}}{%
+\ifthenelse{\equal{\@pcsendmessagewidth}{}}{#1}{% we have some width
+\makebox[\@pcsendmessagewidth]{$\displaystyle#1$}%
+}}{%we know the column to center on
+\centerincol{\@pcsendmessagecol}{#1}%
+}%
+}
+
+\newcommandx*{\sendmessage}[2]{%
+\begingroup\setkeys{pcsendmessage}{#2}%
+\tikzset{PCSENDMSG-PATH-STYLE/.style/.expand once=\@pcsendmessagestyle}%
+\tikzset{PCSENDMSG-TOP-STYLE/.style/.expand once=\@pcsendmessagetopstyle}%
+\tikzset{PCSENDMSG-BOTTOM-STYLE/.style/.expand once=\@pcsendmessagebottomstyle}%
+\tikzset{PCSENDMSG-LEFT-STYLE/.style/.expand once=\@pcsendmessageleftstyle}%
+\tikzset{PCSENDMSG-RIGHT-STYLE/.style/.expand once=\@pcsendmessagerightstyle}%
+%restore halign
+%
+\hspace{\@pcsendmessagebeforeskip}%
+\begin{varwidth}{\linewidth}
+\@do@sendmessage{
+ \begin{tikzpicture}%
+ \node[PCSENDMSG-LEFT-STYLE] (\@pcsendmessageleftname) {\@pcsendmessageleft};
+ \node[right=\@pcsendmessagelength of \@pcsendmessageleftname,PCSENDMSG-RIGHT-STYLE] (\@pcsendmessagerightname) {\@pcsendmessageright};
+ \path[#1,PCSENDMSG-PATH-STYLE] (\@pcsendmessageleftname) edge[] node[above,PCSENDMSG-TOP-STYLE] (\@pcsendmessagetopname) {\@pcsendmessagetop} node[below,PCSENDMSG-BOTTOM-STYLE] (\@pcsendmessagebottomname) {\@pcsendmessagebottom} (\@pcsendmessagerightname);
+ \end{tikzpicture}%
+}%
+\end{varwidth}
+\hspace{\@pcsendmessageafterskip}%
+\endgroup%
+}
+
+\newcommandx*{\sendmessageright}[2][1=->]{%
+\sendmessage{#1}{#2}%
+}
+
+\newcommandx*{\sendmessageleft}[2][1=<-]{%
+\sendmessage{#1}{#2}%
+}
+
+\WithSuffix\newcommand\sendmessageleft*[2][\pcdefaultmessagelength]{%
+\begingroup%
+\renewcommand{\@pcsendmessagetop}{\let\halign\@pc@halign$\begin{aligned}#2\end{aligned}$}%
+\sendmessage{<-}{length=#1}%
+\endgroup%
+}
+
+
+\WithSuffix\newcommand\sendmessageright*[2][\pcdefaultmessagelength]{%
+\begingroup%
+\renewcommand{\@pcsendmessagetop}{\let\halign\@pc@halign$\begin{aligned}#2\end{aligned}$}%
+\sendmessage{->}{length=#1}%
+\endgroup%
+}
+
+
+
+\DeclareExpandableDocumentCommand{\sendmessagerightx}{O{\pcdefaultlongmessagelength}mO{}m}{%
+\multicolumn{#2}{c}{\ensuremath{\hspace{\pcbeforemessageskip}\xrightarrow[\begin{aligned}#3\end{aligned}]{\mathmakebox[#1]{\begin{aligned}#4\end{aligned}}}\hspace{\pcaftermessageskip}}}
+}
+
+\DeclareExpandableDocumentCommand{\sendmessageleftx}{O{\pcdefaultlongmessagelength}mO{}m}{%
+\multicolumn{#2}{c}{\ensuremath{\hspace{\pcbeforemessageskip}\xleftarrow[\begin{aligned}#3\end{aligned}]{\mathmakebox[#1]{\begin{aligned}#4\end{aligned}}}\hspace{\pcaftermessageskip}}}
+}
+
+%%%
+% Division
+\DeclareExpandableDocumentCommand{\pcintertext}{O{}m}{\intertext{%
+\ifthenelse{\equal{#1}{center}}{\makebox[\linewidth][c]{#2}}{}%
+\ifthenelse{\equal{#1}{dotted}}{\dotfill#2\dotfill}{}%
+\ifthenelse{\equal{#1}{}}{#2}{}%
+}\@pc@beginnewline}
+
+
+
+%%%
+% Games
+%
+\newcounter{pcstartgamecounter}
+
+\newcommand*{\pcgamename}{\ensuremath{\mathsf{Game}}}
+\newcommand*{\gameprocedurearg}{\ensuremath{(\secpar)}}
+\newcommand*\@pcgameproofgamenr{0}
+\define@key{pcgameproof}{nr}[]{\renewcommand*\@pcgameproofgamenr{#1}}
+\define@key{pcgameproof}{name}[]{\renewcommand*\pcgamename{\ensuremath{#1}}}
+\define@key{pcgameproof}{arg}[]{\renewcommand*\gameprocedurearg{\ensuremath{#1}}}
+
+\newenvironment{gameproof}[1][]{%
+\begingroup%
+\setkeys{pcgameproof}{#1}
+\@pc@ensureremember%
+\setcounter{pcgamecounter}{\@pcgameproofgamenr}%
+\setcounter{pcstartgamecounter}{\@pcgameproofgamenr}\stepcounter{pcstartgamecounter}%
+}{\@pc@releaseremember\endgroup}
+
+\createpseudocodecommand{gameprocedure}
+ {\addtocounter{pcgamecounter}{1}\renewcommand{\@withingame}{true}}
+ {\ensuremath{\pcgamename_{\thepcgamecounter}\gameprocedurearg}}
+ {}
+
+\def\@bxgame@pseudocodeA[#1]#2#3{\setkeys*{pcspace}{#1}\renewcommand{\@bxgameheader}{$\pcgamename_{#2}$\gameprocedurearg}%
+\@pseudocode[head=\ensuremath{\pcgamename_{\thepcgamecounter}\gameprocedurearg},#1]{#3}}
+\def\@bxgame@pseudocodeB#1#2{\renewcommand{\@bxgameheader}{$\pcgamename_{#1}$\gameprocedurearg}%
+\@pseudocode[head=\ensuremath{\pcgamename_{\thepcgamecounter}\gameprocedurearg}]{#2}}
+
+\newcommand{\bxgameprocedure}{
+\begingroup%
+\renewcommand{\@withinspaces}{false}%
+\renewcommand{\@withingame}{true}%
+\renewcommand{\@withinbxgame}{true}%
+\stepcounter{pcgamecounter}%
+\@ifnextchar[%]
+ {\@bxgame@pseudocodeA}
+ {\@bxgame@pseudocodeB}%
+}
+
+\newcommand{\@pc@secondheader}{}
+
+%tbx top boxed
+\createpseudocodecommand{tbxgameprocedure}
+ {\addtocounter{pcgamecounter}{1}\renewcommand{\@withingame}{true}%%
+\renewcommand{\@pc@secondheader}{true}}
+ {\ensuremath{\pcgamename_{\thepcgamecounter}\gameprocedurearg}}
+{}
+
+
+\newcommand*\@pcgamehopnodestyle{}
+\newcommand*\@pcgamehopedgestyle{bend left}
+\newcommand*\@pcgamehoppathestyle{}
+\newcommand*\@pcgamehophint{}
+\newcommand*\@pcgamehoplength{1.5cm}
+\define@key{pcgamehop}{nodestyle}[]{\renewcommand*\@pcgamehopnodestyle{#1}}
+\define@key{pcgamehop}{edgestyle}[]{\renewcommand*\@pcgamehopedgestyle{#1}}
+\define@key{pcgamehop}{pathstyle}[]{\renewcommand*\@pcgamehoppathestyle{#1}}
+\define@key{pcgamehop}{hint}[]{\renewcommand*\@pcgamehophint{#1}}
+\define@key{pcgamehop}{length}[]{\renewcommand*\@pcgamehoplength{#1}}
+
+
+\newcommand{\@pc@setupgamehop}[1]{
+\begingroup\setkeys{pcgamehop}{#1}%
+\tikzset{GAMEHOP-PATH-STYLE/.style/.expand once=\@pcgamehoppathestyle}%
+\tikzset{GAMEHOP-NODE-STYLE/.style/.expand once=\@pcgamehopnodestyle}%
+\tikzset{GAMEHOP-EDGE-STYLE/.style/.expand once=\@pcgamehopedgestyle}%
+}
+
+\newcommand{\@pc@finalizegamehop}{
+\endgroup
+}
+
+\newcommandx*{\addgamehop}[3]{%
+\begingroup
+\ifthenelse{#1<#2}{}{\renewcommand*\@pcgamehopedgestyle{bend right}}
+\@pc@setupgamehop{#3}
+\begin{tikzpicture}[overlay]
+\ifthenelse{#1<#2}{
+ \path[->,GAMEHOP-PATH-STYLE] (gamenode#1) edge[GAMEHOP-EDGE-STYLE] node[above,GAMEHOP-NODE-STYLE] {\@pcgamehophint} (gamenode#2);
+}{
+ \path[->,GAMEHOP-PATH-STYLE] (bgamenode#1) edge[GAMEHOP-EDGE-STYLE] node[above,GAMEHOP-NODE-STYLE] {\@pcgamehophint} (bgamenode#2);
+}
+\end{tikzpicture}
+\@pc@finalizegamehop
+\endgroup
+}
+\newcommandx*{\addstartgamehop}[2][1=\thepcstartgamecounter]{%
+\@pc@setupgamehop{#2}
+\begin{tikzpicture}[overlay]
+ \node[left=\@pcgamehoplength of gamenode#1] (tmpgamenode0) {};
+ \path[->,GAMEHOP-PATH-STYLE] (tmpgamenode0) edge[GAMEHOP-EDGE-STYLE] node[above,GAMEHOP-NODE-STYLE] {\@pcgamehophint} (gamenode#1);
+\end{tikzpicture}
+\@pc@finalizegamehop
+}
+\newcommandx*{\addendgamehop}[2][1=\thepcgamecounter]{%
+\@pc@setupgamehop{#2}
+\begin{tikzpicture}[overlay]
+ \node[right=\@pcgamehoplength of gamenode#1] (tmpgamenode#1) {};
+ \path[->,GAMEHOP-PATH-STYLE] (gamenode#1) edge[GAMEHOP-EDGE-STYLE] node[above,GAMEHOP-NODE-STYLE] {\@pcgamehophint} (tmpgamenode#1);
+\end{tikzpicture}
+\@pc@finalizegamehop
+}
+\newcommandx*{\addbxgamehop}[3]{%
+\@pc@setupgamehop{#3}
+\begin{tikzpicture}[overlay]
+ \path[->,GAMEHOP-PATH-STYLE] (bgamenode#1) edge[GAMEHOP-EDGE-STYLE] node[above,GAMEHOP-NODE-STYLE]] {\@pcgamehophint} (bgamenode#2);
+\end{tikzpicture}
+\@pc@finalizegamehop
+}
+\newcommandx*{\addloopgamehop}[2][1=\thepcgamecounter]{%
+\@pc@setupgamehop{#2}
+\begin{tikzpicture}[overlay]
+ \node (looptemp1) [right=0.5cm of gamenode#1] {};
+ \draw[->,GAMEHOP-PATH-STYLE] (gamenode#1) -- (looptemp1|-gamenode#1) -- node[right,GAMEHOP-NODE-STYLE] {\@pcgamehophint} (looptemp1|-bgamenode#1)-- (bgamenode#1);
+\end{tikzpicture}
+\@pc@finalizegamehop
+}
+
+
+
+%%%%%%%%
+% basic pseudocode constants
+
+\newcommand{\highlightkeyword}[2][\ ]{\ensuremath{\mathbf{#2}}#1}
+\newcommand{\highlightaltkeyword}[1]{\ensuremath{\mathsf{#1}}}
+
+\newcommand{\pcglobvar}{\highlightkeyword{gbl}}
+\newcommand{\pcnew}{\highlightkeyword{new}}
+\newcommand{\pcwhile}{\@pc@increaseindent\highlightkeyword{while}}
+\newcommand{\pcendwhile}{\@pc@decreaseindent\highlightkeyword{endwhile}}
+\newcommandx*{\pcdo}[2][1=\ ,2=]{#1\highlightkeyword[#2]{do}}
+\newcommand{\pcif}{\@pc@increaseindent\highlightkeyword{if}}
+\newcommand{\pcelse}{\@pc@tmpdecreaseindent\highlightkeyword{else}}
+\newcommand{\pcelseif}{\@pc@tmpdecreaseindent\highlightkeyword{else if}}
+\newcommand{\pcfi}{\@pc@decreaseindent\highlightkeyword{fi}}
+\newcommand{\pcendif}{\@pc@decreaseindent\highlightkeyword{endif}}
+\newcommand{\pcendfor}{\@pc@decreaseindent\highlightkeyword{endfor}}
+\newcommandx*{\pcthen}[2][1=\ ,2=]{#1\highlightkeyword[#2]{then}}
+\newcommand{\pcreturn}{\highlightkeyword{return}}
+\newcommandx*{\pcin}[2][1=\ ,2=]{#1\highlightkeyword[#2]{in}}
+\newcommand{\pcfor}{\@pc@increaseindent\highlightkeyword{for}}
+\newcommand{\pcrepeat}[1]{\@pc@increaseindent\ensuremath{\highlightkeyword{repeat} #1\ \highlightkeyword{times}}}
+\newcommand{\pcrepeatuntil}[2]{\ensuremath{\highlightkeyword{repeat}\ #1\ \highlightkeyword{until}\ #2}}
+\newcommand{\pcforeach}{\@pc@increaseindent\highlightkeyword{foreach}}
+\newcommand{\pcendforeach}{\@pc@decreaseindent\highlightkeyword{endforeach}}
+\newcommand{\pcuntil}{\@pc@decreaseindent\highlightkeyword{until}}
+\newcommand{\pccontinue}{\highlightkeyword{continue}}
+\newcommand{\pcfalse}{\highlightkeyword{false}}
+\newcommand{\pctrue}{\highlightkeyword{true}}
+\newcommand{\pcnull}{\highlightkeyword{null}}
+\newcommand{\pccomment}[1]{{\mbox{/\!\!/ } \text{\scriptsize#1}}}
+\newcommand{\pcdone}{\highlightkeyword{done}}
+\newcommand{\pcparse}{\highlightkeyword{parse}}
+
+%%%
+% highlighting
+\definecolor{highlight-gray}{gray}{0.90}
+\newcommand{\gamechange}[2][highlight-gray]{%
+{\setlength{\fboxsep}{0pt}\colorbox{#1}{\ifmmode$\displaystyle#2$\else#2\fi}}
+}
+
+%%%
+% boxing
+\newcommand{\pcbox}[1]{%
+{\setlength{\fboxsep}{3pt}\fbox{$\displaystyle#1$}}
+}
+
+\endinput
diff --git a/doc/system/diagrams/bitcoin-market-price.png b/doc/system/diagrams/bitcoin-market-price.png
new file mode 100644
index 000000000..7fbb351e8
--- /dev/null
+++ b/doc/system/diagrams/bitcoin-market-price.png
Binary files differ
diff --git a/doc/system/diagrams/taler-diagram-denom-expiration.png b/doc/system/diagrams/taler-diagram-denom-expiration.png
new file mode 100644
index 000000000..700451a46
--- /dev/null
+++ b/doc/system/diagrams/taler-diagram-denom-expiration.png
Binary files differ
diff --git a/doc/system/diagrams/taler-diagram-exchange.png b/doc/system/diagrams/taler-diagram-exchange.png
new file mode 100644
index 000000000..7e2fcdd42
--- /dev/null
+++ b/doc/system/diagrams/taler-diagram-exchange.png
Binary files differ
diff --git a/doc/system/diagrams/taler-diagram-keyup.png b/doc/system/diagrams/taler-diagram-keyup.png
new file mode 100644
index 000000000..7a32788f0
--- /dev/null
+++ b/doc/system/diagrams/taler-diagram-keyup.png
Binary files differ
diff --git a/doc/system/diagrams/taler-diagram-merchant.png b/doc/system/diagrams/taler-diagram-merchant.png
new file mode 100644
index 000000000..c24dd144f
--- /dev/null
+++ b/doc/system/diagrams/taler-diagram-merchant.png
Binary files differ
diff --git a/doc/system/diagrams/taler-diagram-signatures.png b/doc/system/diagrams/taler-diagram-signatures.png
new file mode 100644
index 000000000..507715355
--- /dev/null
+++ b/doc/system/diagrams/taler-diagram-signatures.png
Binary files differ
diff --git a/doc/system/diagrams/taler-diagram-wallet.png b/doc/system/diagrams/taler-diagram-wallet.png
new file mode 100644
index 000000000..cbe76a2c3
--- /dev/null
+++ b/doc/system/diagrams/taler-diagram-wallet.png
Binary files differ
diff --git a/doc/system/introduction.tex b/doc/system/introduction.tex
new file mode 100644
index 000000000..9604f056a
--- /dev/null
+++ b/doc/system/introduction.tex
@@ -0,0 +1,541 @@
+\chapter{Introduction}\label{chapter:introduction}
+
+New networking and cryptographic protocols can substantially improve
+electronic online payment systems. This book is about the design,
+implementation and security analysis of GNU
+Taler\footnote{\url{https://taler.net/}}, a privacy-friendly payment
+system that is designed to be practical for usage as an online
+(micro-)payment method, and at the same time socially and ethically
+responsible.
+
+Payment systems can generally be divided into two types: Register-based
+and value-based~\cite{riksbank2017riksbank}. A register-based system
+associates value with identities (e.g., bank account balances with
+customers), while a value-based system associates value with typically
+anonymous, digital or physical tokens (such as cash or prepaid credit
+cards). In practice, these two types of systems are combined, as
+different layers have different (and often conflicting) requirements:
+the payment system used to pay for a cappuccino in a coffee shop is
+most likely not suitable to buy real estate. Value-based payment
+systems typically provide more anonymity and convenience but also more
+risk to consumers (as they are responsible to secure the values they
+hold), while register-based systems shift risks to the payment service
+provider who has to authenticate consumers and ensure the integrity of
+the register.
+
+This book explains GNU Taler, a design and implementation of a value-based
+payment system, discussing in-depth how to create a practical,
+privacy-preserving and secure (micro-)payment protocol that integrates nicely
+with the modern web. Our value-based payment protocol can in principle
+operate on top of any existing register-based system.
+
+GNU Taler is an official package of the GNU
+project\footnote{\url{https://gnu.org/}}. Our free software implementation is
+freely available from the GNU mirrors.
+
+
+\section{Design Goals for GNU Taler}
+
+The design of payment systems shapes economies and societies
+\cite{zandi2013impact,dalebrant2016monetary}. Payment systems with high
+transaction costs create an economic burden. Predominantly cash-based
+societies provide some degree of anonymity for their citizens, but can fail to
+provide a sound foundation for taxation, facilitate corruption
+\cite{singh2017does} and thus risk creating weak governments. On the other
+hand, systems with too much surveillance eliminate personal freedom.
+
+As the Internet has no standardized payment system, especially not one
+that is capable of quickly, efficiently and securely settling small
+transactions (so-called micropayments), the majority of content on the web is
+financed by advertisements. As a result, advertising (and by
+implication, collecting data on users) has been a dominant business
+model on the Internet. This has not only resulted in a loss of
+independence of publishers---who need to cater to the needs
+of advertisers---but also in a situation where micro-targeted ads
+are so wide-spread, that they have been suspected to have influenced
+multiple major elections~\cite{persily2017election}. Ads are also a
+vector for malware \cite{provos2007ghost}. Due to the prevalence of
+ad blockers, ads are also not guaranteed to be a sustainable business
+model.
+
+In the world of online payments, credit cards and a sprawling number
+of smaller, proprietary payment processors are currently dominant, and
+market shares vary widely between different
+countries~\cite{adyen2016global,paypers2016ecommerce}. The resulting
+fragmentation again increases social costs: online shops can either
+choose to invest in implementing many proprietary protocols, or only
+implement the most popular ones, thereby reinforcing the dominance of
+a handful of proprietary payment systems.
+
+Considering these and other social implications of payment systems, we
+started the development of GNU Taler with a set of high-level design
+goals that fit our social agenda. They are ranked by the importance
+we give to them, and when a trade-off must be made, the one that
+supports the more highly ranked goal is preferred:
+
+% what about micropayments -> part of 'efficient'
+\begin{enumerate}
+ \item \textbf{GNU Taler must be implemented as free software.}
+
+ Free refers to ``free as in free speech'', as opposed to ``free as in free beer''.
+ More specifically, the four essential freedoms of free software
+ \cite{stallman2002essays} must be respected, namely users must have the
+ freedom to (1) run the software, (2) study and modify it, (3) redistribute
+ copies, and (4) distribute copies of the modified version.
+
+ For merchants this prevents vendor lock-in, as another payment provider can
+ take over, should the current one provide inadequate quality of service.
+ As the software of
+ the payment provider itself is free, smaller or disadvantaged countries or
+ organizations can run the payment system without being controlled by a
+ foreign company. Customers benefit from this freedom, as the wallet
+ software can be made to run on a variety of platforms, and user-hostile
+ features such as tracking or telemetry could easily be removed from
+ wallet software.
+
+ This rules out the mandatory usage of specialized
+ hardware such as smart cards or other hardware security modules, as the
+ software they run cannot be modified by the user. These components can,
+ however, be voluntarily used by merchants, customers or payment processors
+ to increase their operational security.
+
+ \item \textbf{GNU Taler must protect the privacy of buyers.}\label{item:privacy}
+
+ Privacy should be guaranteed via technical measures, as opposed to mere
+ policies. Especially with micropayments for online content, a
+ disproportionate amount of rather private data about buyers would be revealed, if
+ the payment system does not have privacy protections.
+
+%Especially if a payment system is to be used for microtransactions for online
+%content, the privacy of buyers becomes important: if micropayments were more
+%commonplace, the transaction data could be used to collect extremely detailed
+%profiles of users. Unfortunately practically no commercially used payment
+%system has strong anonymity guarantees.
+
+ In legislations with data protection regulations (such as the recently introduced GDPR in Europe \cite{voigt2017eu}),
+ merchants benefit from this as well, as no data breach of customers can happen if this information
+ is, by design, not collected in the first place. Obviously some private data, such as the shipping
+ address for a physical delivery, must still be collected according to business needs.
+
+ The security of the payment systems also benefits from this, as the model
+ shifts from authentication of customers to mere authorization of payments.
+ This approach rules out whole classes of attacks such as phishing \cite{garera2007framework} or credit
+ card fraud \cite{sahin2010overview}.
+
+ \item \textbf{GNU Taler must enable the state to tax income and crack down on
+ illegal business activities.}
+
+ % FIXME: provide broader ethical justification!
+ As a payment system must still be legal to operate and use, it must comply
+ with these requirements. Furthermore, we consider levying of taxes as
+ beneficial to society.
+
+ \item \textbf{GNU Taler must prevent payment fraud.}
+
+ This imposes requirements on the security of the system, as well as on the
+ general design, as payment fraud can also happen through misleading user
+ interface design or the lack of cryptographic evidence for certain
+ processes.
+
+ \item \textbf{GNU Taler must only disclose the minimal amount of information
+ necessary.}
+
+ The reason behind this goal is similar to (\ref{item:privacy}). The
+ privacy of buyers is given priority, but other parties such as merchants
+ still benefit from it, for example, by keeping details about the merchant's financials
+ hidden from competitors.
+
+
+ \item \textbf{GNU Taler must be usable.}
+
+ Specifically it must be usable for non-expert customers. Usability also
+ applies to the integration with merchants, and informs choices about the
+ architecture, such as encapsulating procedures that require cryptographic
+ operations into an isolated component with a simple API.
+
+ \item \textbf{GNU Taler must be efficient.}
+
+ % FIXME: provide broader ethical justification (environmental impact,
+ % social cost, opportunity cost of lack of micropayments)
+ Approaches such as proof-of-work are ruled out by this requirement. Efficiency is
+ necessary for GNU Taler to be used for micropayments.
+
+ \item \textbf{GNU Taler must avoid single points of failure.}
+
+ While the design we present later is rather centralized, avoiding single
+ points of failure is still a goal. This manifests in architectural choices such as
+ the isolation of certain components, and auditing procedures.
+
+ \item \textbf{GNU Taler must foster competition.}
+
+ It must be relatively easy for competitors to join the systems. While the
+ barriers for this in traditional financial systems are rather high, the
+ technical burden for new competitors to join must be minimized. Another
+ design choice that supports this is to split the whole system into smaller
+ components that can be operated, developed and improved upon independently,
+ instead of having one completely monolithic system.
+
+\end{enumerate}
+
+
+
+\section{Features of Value-based Payment Systems}\label{sec:intro:features}
+
+There are many different possible features that have been proposed for
+value-based (sometimes called token-based) payment systems in the past. While we
+will discuss existing work on e-cash in more detail in
+Section~\ref{sec:related-work:e-cash}, we will begin by a brief
+summary of the possible features that value-based payment systems
+could provide, and clarify which high-level features we chose to adopt
+for GNU Taler.
+
+% EXPLAIN: in traditional (online) e-cash, spending is never
+% bound to a contract identifier
+
+%\subsubsection*{Different signature schemes and zero-knowledge proofs}
+%Since Chaum's original blind signature scheme based on RSA, many variations
+%using other cryptographic primitives have been developed. Some newer e-cash
+%schemes do not use blind signatures, but rely on zero-knowledge proofs instead.
+%
+%In GNU Taler, we opt for an RSA-based blind signature scheme, due to the low
+%complexity, relatively clear security assumptions and small number of
+%communication rounds compared to other protocols.
+
+\subsection{Offline vs Online Payments}
+
+Anonymous digital cash schemes since Chaum~\cite{chaum1983blind} were frequently designed to allow
+the merchant to be offline during the transaction, by providing a means to
+deanonymize customers involved in double-spending, typically by encoding the
+customer's identity into their coins in a way that makes it only possible to
+decode the identity with two spending transcripts.
+
+This approach is problematic in practice, as customers that restore a wallet
+from backup might accidentally double-spend and would then face punishment for
+it. Enforcing punishment for double-spenders can be rather difficult as well,
+since the double-spender might have signed up with a false identity or might
+already have fled to another country, and a large number of merchants might already
+have been defrauded with the same coins.
+
+Should the issuer of e-cash be compromised, an attacker could issue coins that
+fail to identify a culprit or even blame somebody else when they are
+double-spent. In an offline e-cash system, the detection of such an event is
+greatly delayed compared to systems with online spending, which can immediately
+detect when more coins are spent than were issued.
+
+Thus, in GNU Taler, we decided that all coins must be immediately
+deposited online during a purchase. Only either a merchant or a customer
+needs to be online, since one of the two can forward messages to the
+payment service provider for the other.
+
+\subsection{Change and Divisibility}
+
+Customers do not always have the right set of coins available to exactly cover
+the amount to be paid to a merchant. With physical cash, the store would
+give the customer change. For e-cash, the situation is more complex, as
+the customer would have to make sure that the change has not already been
+spent, does not violate their anonymity and the merchant does not have a
+digital ``copy'' of the change tokens that the merchant can spend before the customer. Note
+that it would be unwise to always withdraw the correct amount of e-cash
+directly before a purchase, as it creates a temporal correlation between the
+non-anonymous withdrawal event and the spending event.
+
+Most modern e-cash schemes instead deal with exact spending by providing
+\emph{divisibility} of coins, where the customer can decide to only spend part
+of a coin. A significant chunk of the e-cash literature has been concerned
+with developing schemes that allow the individual, divided parts of a coin to
+be unlinkable (thus preserving anonymity) and to optimize the storage costs for
+wallets and the communication cost of withdrawals.
+
+The current state of the art for divisible e-cash~\cite{pointcheval2017cut}
+achieves constant-time withdrawal and wallet storage cost for coins that can be
+split into an arbitrary but fixed (as a system parameter) number of pieces. A
+continuous ``chunk'' of the smallest pieces of a coin can be spent with
+constant-time communication complexity.
+
+While this sounds attractive in theory, these results are mostly of academic
+interest, as the storage and/or computational complexity for the party that is
+checking for double spending of coins remains enormous: each smallest piece of
+every coin needs to be recorded and checked individually. When paying
+$\$10.00$ with a coin that supports division into cent pieces, $1000$
+individual coin pieces must be checked for double spending and recorded,
+possibliy in compressed form to trade storage costs for more computation.
+
+For GNU Taler, we use a rather simple and practical approach, made possible by
+requiring participants to be online during spending: coins can be fractionally
+spent without having divisible, unlinkable parts. The remaining value on a coin
+that was spend (and thus revealed) can be used to withdraw fresh, unlinkable
+coins. The protocol to obtain change takes additional measures to ensure that
+it cannot be misused to facilitate untaxed transactions. Giving change for
+e-cash has been proposed before \cite{brickell1995trustee,tracz2001fair}, but
+to the best of our knowledge, the idea of income-transparent change is novel.
+
+\subsection{Anonymity Control}
+
+Some proposed e-cash protocols contain mechanisms to allow selective
+deanonymization of transactions for scenarios involving crime
+\cite{sander1999escrow}, specifically blackmailing and tax evasion.
+
+Unfortunately this does not really work as a countermeasure against
+blackmailing in practice. As noted in the paper that initially described such
+a mechanism for blind signatures \cite{stadler1995fair}, a blackmailer could
+simply request to be paid directly with plain, blindly signed coins, and
+thereby completely circumvent the threat of revocable anonymity.
+
+GNU Taler provides \emph{income transparency} as a measure against tax evasion.
+We furthermore describe a different approach in a blackmailing scenario in
+Section~\ref{sec:design:blackmailing}, which we believe is more practical in
+dissuading blackmailers in practice.
+
+\subsection{User Suspension}
+
+Anonymous user suspension \cite{au2011electronic} has been proposed as
+another mechanism to punish users suspected in illicit activities by
+preventing then from making further transactions until the suspension is
+lifted. Anonymous suspension is based on transactions; the user
+involved in a particular transaction is suspended, but their identity is not
+revealed.
+
+While the approach is interesting, it is not practical, as it requires
+a single permanent key pair to be associated with each user. If a
+user claims they lost their private key and requests a new key pair,
+their suspension would be effectively lifted. Suspending users from a
+dominant payment system is also socially problematic, as excluding
+them from most commercial activities would likely be a
+disproportionate and cruel punishment.
+
+\subsection{Transferability}
+
+Transferability is a feature of certain e-cash systems that allows
+transfer of e-cash between two parties without breaking anonymity
+properties \cite{fuchsbauer2009transferable}. Contemporary systems
+that offer this type of disintermediation attract criminal
+activity~\cite{richet2016extortion}.
+
+GNU Taler specifically provides roughly the \emph{opposite} of this property,
+namely \emph{income transparency}, to guarantee that e-cash is not easily
+abused for tax evasion. Mutually trusting users, however, can share ownership
+of a coin.
+
+\subsection{Atomic Swaps}
+
+Atomic swaps (often called ``fair exchange'' in the e-cash literature) are a
+feature of some e-cash systems that allows e-cash
+to be exchanged against some service or (digital) product, with a trusted third
+party ensuring that the payee receives the payment if and only if they correctly
+provided the merchandise.
+
+GNU Taler supports Camenisch-style atomic swaps~\cite{camenisch2007endorsed},
+as explained in Section~\ref{sec:security:atomic-swaps}.
+
+\subsection{Refunds}
+
+GNU Taler allows merchants to provide refunds to customers during a limited
+time after the coins for the payment were deposited. The merchant signs a
+statement that effectively allows the customer to reclaim a previously spent
+coin. Customers can request anonymous change for the reclaimed amount.
+
+While this is a rather simple extension, we are not aware of any other e-cash
+system that supports refunds.
+
+
+\section{User Experience and Performance} \label{sec:intro:ux}
+
+For adoption of a payment system, the user experience is critical. Thus,
+before diving into {\em how} GNU Taler is implemented, we begin by
+showing how GNU Taler {\em looks} from the perspective of an end user in the
+context of web payments, in a desktop browser (Chromium).
+
+To use GNU Taler, the user must first install a browser extension
+(Figure~\ref{fig:ux:install-prompt}). Once installed, the user can
+open a pop-up window by clicking on the Taler logo, to see the
+initially empty wallet balance (Figure~\ref{fig:ux:installed}).
+
+The customer logs into their online bank---a simple demo bank in our case--to
+withdraw digital cash from their bank account into their wallet (Figures~%
+\ref{fig:ux:bank-login} and~\ref{fig:ux:bank-profile}). Our demo uses
+\textsc{Kudos} as an imaginary currency. Before the user is asked to confirm,
+they are given the option to view details about or change the default exchange
+provider, the GNU Taler payment service provider (Figure~\ref{fig:ux:select-exchange}).
+
+With a real bank, a second factor (such as a mobile TAN) would now be requested
+from the user. Our demo instead asks the user to solve a simple CAPTCHA
+(Figure~\ref{fig:ux:pin-tan}). The amount withdrawn---minus withdrawal
+fees---is now available as e-cash in the wallet (Figure~%
+\ref{fig:ux:withdraw-done}).
+
+The customer can now go to an online shop to spend their digital cash. We've
+implemented a shop that sells single chapters from Richard Stallman's essay
+collection ``Free Software, Free Society'' \cite{stallman2002essays} (Figure~%
+\ref{fig:ux:essay-landing}). The user selects an essay, and is then
+immediately presented with a confirmation page rendered by the wallet (Figure~\ref{fig:ux:essay-pay}).
+After paying, the user can immediately read the article (Figure~\ref{fig:ux:essay-done}).
+
+Our benchmarks, discussed in Chapter \ref{chapter:implementation} show that a
+single machine can support around 1000 payments per second, and our
+implementation is easily amenable to further scaling.
+
+The extra computation required in the customer's wallet is in the order of a
+few hundred milliseconds even on typical mobile or tablet devices, and thus
+barely noticeable.
+
+\begin{figure}
+\centering
+\includegraphics[width=\textwidth]{taler-screenshots/wallet-install-prompt.png}
+\caption{The user is prompted to install the wallet.}
+\label{fig:ux:install-prompt}
+\end{figure}
+
+\begin{figure}
+\centering
+\includegraphics[width=\textwidth]{taler-screenshots/wallet-installed.png}
+\caption{The wallet popup shows an empty balance.}
+\label{fig:ux:installed}
+\end{figure}
+
+\begin{figure}
+\centering
+\includegraphics[width=\textwidth]{taler-screenshots/bank-login.png}
+\caption{The bank asks for login details.}
+\label{fig:ux:bank-login}
+\end{figure}
+
+\begin{figure}
+\centering
+\includegraphics[width=\textwidth]{taler-screenshots/bank-profile.png}
+\caption{Account page of the demo bank.}
+\label{fig:ux:bank-profile}
+\end{figure}
+
+\begin{figure}
+\centering
+\includegraphics[width=\textwidth]{taler-screenshots/withdraw-confirm.png}
+\caption{Exchange selection dialog in the wallet.}
+\label{fig:ux:select-exchange}
+\end{figure}
+
+\begin{figure}
+\centering
+\includegraphics[width=\textwidth]{taler-screenshots/pin-tan.png}
+\caption{PIN/TAN dialog of the demo bank.}
+\label{fig:ux:pin-tan}
+\end{figure}
+
+\begin{figure}
+\centering
+\includegraphics[width=\textwidth]{taler-screenshots/withdraw-done.png}
+\caption{After a successful withdrawal, the balance is shown in the wallet.}
+\label{fig:ux:withdraw-done}
+\end{figure}
+
+\begin{figure}
+\centering
+\includegraphics[width=\textwidth]{taler-screenshots/essay-landing.png}
+\caption{Landing page of a store that sells essays.}
+\label{fig:ux:essay-landing}
+\end{figure}
+
+\begin{figure}
+\centering
+\includegraphics[width=\textwidth]{taler-screenshots/essay-pay.png}
+\caption[Payment prompt for an essay.]{Payment prompt for an essay. Rendered by the wallet.}
+\label{fig:ux:essay-pay}
+\end{figure}
+
+\begin{figure}
+\centering
+\includegraphics[width=\textwidth]{taler-screenshots/essay-done.png}
+\caption{Essay successfully purchased by the user.}
+\label{fig:ux:essay-done}
+\end{figure}
+
+%\begin{figure}
+%\begin{subfigure}{.5\textwidth}
+% \centering
+% \includegraphics[width=.8\linewidth]{taler-screenshots/wallet-installed.png}
+% \caption{1a}
+% \label{fig:sfig1}
+%\end{subfigure}%
+%\begin{subfigure}{.5\textwidth}
+% \centering
+% \includegraphics[width=.8\linewidth]{taler-screenshots/bank-login.png}
+% \caption{1b}
+% \label{fig:sfig2}
+%\end{subfigure}
+%\caption{plots of....}
+%\label{fig:fig}
+%\end{figure}
+
+% FIXME: perf results
+
+\section{The Technical Foundation: Anonymous E-Cash} \label{sec:intro:ecash}
+GNU Taler is based on anonymous e-cash. Anonymous e-cash was invented by David
+Chaum in the 1980s \cite{chaum1983blind}. The idea behind Chaumian e-cash is
+both simple and ingenious, and can be best illustrated
+with the carbon paper\footnote{%
+ Carbon paper is a paper coated with pigment (originally carbon) on one side.
+ When put face-down between two sheets of normal paper, the pressure from
+ writing with a pen or typewriter on the first layer causes pigment to be
+ deposited on the paper beneath, allowing a copy to be made.
+} analogy: A long, random serial number is generated, for example, by throwing
+a die a few dozen times, and written on a piece of paper. A carbon paper is
+placed on top, with the pigmented side facing down, and both pieces of paper
+are put into an opaque envelope. The envelope is now sealed and brought to a
+bank. The bank draws a signature on the outside of the envelope, which presses
+through to the piece of paper with the serial number. In exchange for the
+signed envelope, the bank deducts a fixed amount (say five dollars) from the
+customer's bank account. Under the (admittedly rather strong) assumption that
+the bank's signature cannot be forged, the signed piece of paper with the serial
+number is now an untraceable bank note worth five dollars, as the bank signed
+it without seeing the serial number inside the envelope! Since the signed
+paper can be easily copied, merchants that accept it as payment must check the
+bank's signature, call the bank and transmit the serial number. The bank keeps
+a register of all serial numbers that have been used as payment before. If the
+serial number is already in the bank's register, the bank informs the merchant
+about the attempted double spending, and the merchant then rejects the payment.
+
+The digital analogue of this process is called a \emph{blind signature}, where
+the signer knows that it gave a digital signature, but does not know the
+contents of the message that it signed.
+
+In this document, we use \emph{coin} to refer to a token of value in an e-cash
+system. Note that the analogy of a coin does not always hold up, as certain
+types of operations possible in some e-cash schemes, such as partial spending,
+divisibility, etc., do not transfer to physical coins.
+
+
+%\subsection{Security Properties}\label{sec:intro:security}
+
+We have the following security and correctness properties for GNU Taler
+(formally defined in Chapter~\ref{chapter:security}):
+\begin{itemize}
+ \item \emph{Anonymity} guarantees that transactions cannot be correlated with withdrawals or
+ other transactions made by the same customer.
+ \item \emph{Unforgeability} guarantees that users cannot spend more e-cash than they withdrew.
+ \item \emph{Conservation} guarantees that customers do not lose money due to
+ interrupted protocols or malicious merchants; they can always obtain
+ anonymous change or a proof of successful spending.
+ \item \emph{Income transparency} guarantees that mutually distrusting parties
+ are unable to reliably transfer e-cash between them without the income of
+ participants being visible to tax auditors.
+\end{itemize}
+
+While anonymity and unforgeability are common properties of e-cash, we are not
+aware of any other treatments of income transparency and conservation.
+
+
+\section{Roadmap}
+
+Chapter \ref{chapter:design} describes the high-level design of GNU Taler, and
+compares it to payment systems found in the academic literature and real-world
+usage. Chapter \ref{chapter:security} first gives a gentle introduction to
+provable security (which can be skipped by readers with a background in
+cryptography), and then defines security properties for income-transparent,
+anonymous e-cash. The cryptographic protocols for GNU Taler are defined in
+detail, and proofs are given that our protocols satisfy the security
+properties defined earlier. In Chapter \ref{chapter:implementation}, the
+implementation of GNU Taler is described, and the performance and scalability
+is evaluated. Chapter \ref{chapter:future-work} discusses future work and
+missing pieces to deploy GNU Taler in production. Chapter
+\ref{chapter:conclusion} concludes with an outlook on the potential impact and
+practical relevance of this work.
+
diff --git a/doc/system/plots/cpu.pdf b/doc/system/plots/cpu.pdf
new file mode 100644
index 000000000..6ac08a2b5
--- /dev/null
+++ b/doc/system/plots/cpu.pdf
Binary files differ
diff --git a/doc/system/plots/dbsize.sql b/doc/system/plots/dbsize.sql
new file mode 100644
index 000000000..4f4b23886
--- /dev/null
+++ b/doc/system/plots/dbsize.sql
@@ -0,0 +1,12 @@
+create temporary view sizes as
+ select table_name as n,
+ pg_relation_size(quote_ident(table_name)) / 1024.0 / 1024.0 as s_tbl,
+ pg_indexes_size(quote_ident(table_name)) / 1024.0 / 1024.0 as s_idx
+ from information_schema.tables
+ where table_schema = 'public';
+
+
+select n, s_tbl, s_idx, s_tbl + s_idx from sizes where (s_tbl) != 0
+order by (s_tbl + s_idx);
+
+select sum(s_tbl), sum(s_idx), sum(s_tbl + s_idx) from sizes where s_tbl != 0;
diff --git a/doc/system/plots/eval-basic.bash b/doc/system/plots/eval-basic.bash
new file mode 100644
index 000000000..22888be80
--- /dev/null
+++ b/doc/system/plots/eval-basic.bash
@@ -0,0 +1,20 @@
+#/usr/bin/env bash
+
+for x in 1 $(seq 10 10 190) $(seq 200 100 2000); do
+ cat results/stats-$x/stats/taler-exchange-* | awk -v n=$x '{ print n, int(($3 + $5) / 96) }'
+done | sort -n > plots/time_exchange_cpu.data
+
+tail results/stats-*/benchmark.log | awk '/RAW/ { printf "%d %d\n", $4, $5 }' | sort -n > plots/time_real.data
+
+tail results/stats-*/benchmark.log | awk '/RAW/ { printf "%d %f\n", $4, (($4 * 1000)/($5/1000/1000)) }' | sort -n > plots/speed.data
+
+for x in 1 $(seq 10 10 190) $(seq 200 100 2000); do
+ tail results/stats-$x/benchmark.log | awk -v n=$x '/cpu time/ { print n, int(($4 + $6) / 96) }'
+done | sort -n > plots/time_bench_cpu.data
+
+
+for x in 1 $(seq 10 10 190) $(seq 200 100 2000); do
+ awk -f ~/code/gnunet/contrib/benchmark/collect.awk baseline.txt results/stats-$x/stats/gnunet-benchmark-ops-thread* \
+ | grep total_ops_adjusted_ms \
+ | awk -v n=$x '{ print n, int($2 / 96) }'
+done | sort -n > plots/time_bench_ops_only.data
diff --git a/doc/system/plots/eval-latency.bash b/doc/system/plots/eval-latency.bash
new file mode 100644
index 000000000..684f36b7a
--- /dev/null
+++ b/doc/system/plots/eval-latency.bash
@@ -0,0 +1,35 @@
+#/usr/bin/env bash
+
+set -eu
+
+mkdir -p plots
+
+do_eval() {
+ e=$1
+ out=$2
+ for x in 0 50 100 150 200; do
+ awk -f ~/repos/gnunet/contrib/benchmark/collect.awk results/latency-$x/stats/gnunet-benchmark-urls-*.txt \
+ | fgrep "$1" | fgrep "status 200" | awk -v x=$x '{ print x, $10/1000 }'
+ done | sort -n > plots/latency-$out.data
+}
+
+
+awk -f ~/repos/gnunet/contrib/benchmark/collect.awk results/latency-0/stats/gnunet-benchmark-urls-*.txt \
+ | fgrep "status 200" | awk '{ print $2, $10/1000 }' > plots/latency-summary-0.data
+
+awk -f ~/repos/gnunet/contrib/benchmark/collect.awk results/latency-100/stats/gnunet-benchmark-urls-*.txt \
+ | fgrep "status 200" | awk '{ print $2, $10/1000 }' > plots/latency-summary-100.data
+
+do_eval '/refresh/melt' 'refresh-melt'
+do_eval '/refresh/reveal' 'refresh-reveal'
+do_eval '/deposit' 'deposit'
+do_eval '/reserve/withdraw' 'withdraw'
+do_eval '/keys' 'keys'
+
+awk -f ~/repos/gnunet/contrib/benchmark/collect.awk results/latency-*/stats/gnunet-benchmark-urls-*.txt \
+ | fgrep "status 200" | awk '{ print $2, $16/1000 }' \
+ > plots/req-sent.data
+
+awk -f ~/repos/gnunet/contrib/benchmark/collect.awk results/latency-*/stats/gnunet-benchmark-urls-*.txt \
+ | fgrep "status 200" | awk '{ print $2, $18/1000 }' \
+ > plots/req-received.data
diff --git a/doc/system/plots/latencies.pdf b/doc/system/plots/latencies.pdf
new file mode 100644
index 000000000..f85609606
--- /dev/null
+++ b/doc/system/plots/latencies.pdf
Binary files differ
diff --git a/doc/system/plots/latency-deposit.data b/doc/system/plots/latency-deposit.data
new file mode 100644
index 000000000..b53b3f66d
--- /dev/null
+++ b/doc/system/plots/latency-deposit.data
@@ -0,0 +1,5 @@
+0 22.3593
+50 122.833
+100 223.217
+150 323.787
+200 424.537
diff --git a/doc/system/plots/latency-keys.data b/doc/system/plots/latency-keys.data
new file mode 100644
index 000000000..028a4ff90
--- /dev/null
+++ b/doc/system/plots/latency-keys.data
@@ -0,0 +1,5 @@
+0 1.139
+50 101.446
+100 201.251
+150 301.335
+200 401.399
diff --git a/doc/system/plots/latency-refresh-melt.data b/doc/system/plots/latency-refresh-melt.data
new file mode 100644
index 000000000..991464a8a
--- /dev/null
+++ b/doc/system/plots/latency-refresh-melt.data
@@ -0,0 +1,5 @@
+0 20.7065
+50 121.796
+100 223.9
+150 323.459
+200 422.474
diff --git a/doc/system/plots/latency-refresh-reveal.data b/doc/system/plots/latency-refresh-reveal.data
new file mode 100644
index 000000000..989c611f6
--- /dev/null
+++ b/doc/system/plots/latency-refresh-reveal.data
@@ -0,0 +1,5 @@
+0 63.6377
+50 264.969
+100 466.303
+150 665.626
+200 862.193
diff --git a/doc/system/plots/latency-summary-0.data b/doc/system/plots/latency-summary-0.data
new file mode 100644
index 000000000..8ba510704
--- /dev/null
+++ b/doc/system/plots/latency-summary-0.data
@@ -0,0 +1,7 @@
+http://192.168.42.1:8081/deposit 22.3593
+http://192.168.42.1:8081/keys 1.139
+http://192.168.42.1:8081/reserve/withdraw 22.675
+http://192.168.42.1:8081/refresh/link 7.12287
+http://192.168.42.1:8081/refresh/reveal 63.6377
+http://192.168.42.1:8081/refresh/melt 20.7065
+http://192.168.42.2:8081/admin/add/incoming 0.501
diff --git a/doc/system/plots/latency-summary-100.data b/doc/system/plots/latency-summary-100.data
new file mode 100644
index 000000000..4b282e66a
--- /dev/null
+++ b/doc/system/plots/latency-summary-100.data
@@ -0,0 +1,7 @@
+http://192.168.42.1:8081/deposit 223.217
+http://192.168.42.1:8081/keys 201.251
+http://192.168.42.1:8081/reserve/withdraw 222.458
+http://192.168.42.1:8081/refresh/link 205.331
+http://192.168.42.1:8081/refresh/reveal 466.303
+http://192.168.42.1:8081/refresh/melt 223.9
+http://192.168.42.2:8081/admin/add/incoming 0.485
diff --git a/doc/system/plots/latency-withdraw.data b/doc/system/plots/latency-withdraw.data
new file mode 100644
index 000000000..5f258090b
--- /dev/null
+++ b/doc/system/plots/latency-withdraw.data
@@ -0,0 +1,5 @@
+0 22.675
+50 123.066
+100 222.458
+150 322.701
+200 423.749
diff --git a/doc/system/plots/plot.gnu b/doc/system/plots/plot.gnu
new file mode 100644
index 000000000..120db2786
--- /dev/null
+++ b/doc/system/plots/plot.gnu
@@ -0,0 +1,35 @@
+set terminal pdf monochrome
+
+set nokey
+set output 'speed.pdf'
+set ylabel "coins per second"
+set xlabel "parallel clients"
+plot "speed.data" with lines lw 1
+
+set key top left Left reverse
+set output 'cpu.pdf'
+set ylabel "CPU time (us)"
+set xlabel "parallel clients"
+plot "time_real.data" with lines lw 1 title "wall clock", \
+ "time_bench_cpu.data" with lines lw 1 title "benchmark CPU / 96", \
+ "time_exchange_cpu.data" with lines lw 1 title "exchange CPU / 96", \
+ "time_bench_ops_only.data" with lines lw 1 title "exchange crypto / 96"
+set nokey
+
+
+set output 'latencies.pdf'
+set multiplot layout 2, 3
+set xlabel "delay" font ",10"
+set ylabel "latency" font ",10"
+set xtics font ",10"
+set ytics font ",10"
+set title "/refresh/melt"
+plot "latency-refresh-melt.data" with lines lw 1
+set title "/refresh/reveal"
+plot "latency-refresh-reveal.data" with lines lw 1
+set title "/keys"
+plot "latency-keys.data" with lines lw 1
+set title "/reserve/withdraw"
+plot "latency-withdraw.data" with lines lw 1
+set title "/deposit"
+plot "latency-deposit.data" with lines lw 1
diff --git a/doc/system/plots/req-received.data b/doc/system/plots/req-received.data
new file mode 100644
index 000000000..d26da40f1
--- /dev/null
+++ b/doc/system/plots/req-received.data
@@ -0,0 +1,7 @@
+http://192.168.42.1:8081/deposit 0.339055
+http://192.168.42.1:8081/keys 3.748
+http://192.168.42.1:8081/reserve/withdraw 0.722583
+http://192.168.42.1:8081/refresh/link 3.15716
+http://192.168.42.1:8081/refresh/reveal 2.10851
+http://192.168.42.1:8081/refresh/melt 0.35252
+http://192.168.42.2:8081/admin/add/incoming 0.177
diff --git a/doc/system/plots/req-sent.data b/doc/system/plots/req-sent.data
new file mode 100644
index 000000000..40f524d30
--- /dev/null
+++ b/doc/system/plots/req-sent.data
@@ -0,0 +1,7 @@
+http://192.168.42.1:8081/deposit 1.36432
+http://192.168.42.1:8081/keys 0.145
+http://192.168.42.1:8081/reserve/withdraw 0.711581
+http://192.168.42.1:8081/refresh/link 0.215
+http://192.168.42.1:8081/refresh/reveal 1.42
+http://192.168.42.1:8081/refresh/melt 1.06365
+http://192.168.42.2:8081/admin/add/incoming 0.386
diff --git a/doc/system/plots/run-latency.bash b/doc/system/plots/run-latency.bash
new file mode 100644
index 000000000..77b375db6
--- /dev/null
+++ b/doc/system/plots/run-latency.bash
@@ -0,0 +1,44 @@
+#/usr/bin/env bash
+
+# This is intended to be run with SSH agent forwarding,
+# so we can log in as root to adjust artificial delay.
+
+set -eu
+
+which taler-exchange-benchmark
+
+# check that we can log in at least!
+ssh root@gv.taler.net true
+ssh root@firefly.gnunet.org true
+
+ssh root@gv.taler.net tc qdisc delete dev enp4s0f0 root || true
+ssh root@firefly.gnunet.org tc qdisc delete dev eno2 root || true
+
+ssh root@gv.taler.net "echo 3 > /proc/sys/net/ipv4/tcp_fastopen"
+ssh root@firefly.gnunet.org "echo 3 > /proc/sys/net/ipv4/tcp_fastopen"
+
+# warm up TCP fast open cookies
+taler-exchange-benchmark -c benchmark-remote-gv.conf -m client -p 1 -n 5 >> benchmark-latency.log 2>&1
+
+export GNUNET_BENCHMARK_DIR=$(readlink -f ./stats)
+
+for x in 0 50 100 150 200; do
+ echo running with one-sided delay of $x
+ result_dir="results/latency-$x"
+ if [[ -d "$result_dir" ]]; then
+ echo "skipping because results exist"
+ continue
+ fi
+
+ ssh root@gv.taler.net tc qdisc add dev enp4s0f0 root netem delay "${x}ms"
+ ssh root@firefly.gnunet.org tc qdisc add dev eno2 root netem delay "${x}ms"
+
+ rm -rf stats
+ taler-exchange-benchmark -c benchmark-remote-gv.conf -m client -p 1 -n 200 >> benchmark-latency.log 2>&1
+ echo "### Finished latency run for ${x}ms" >> benchmark-latency.log
+ mkdir -p "$result_dir"
+ cp -a stats "$result_dir/"
+
+ ssh root@gv.taler.net tc qdisc delete dev enp4s0f0 root
+ ssh root@firefly.gnunet.org tc qdisc delete dev eno2 root
+done
diff --git a/doc/system/plots/run.bash b/doc/system/plots/run.bash
new file mode 100644
index 000000000..d11f5f323
--- /dev/null
+++ b/doc/system/plots/run.bash
@@ -0,0 +1,10 @@
+#/usr/bin/env bash
+
+for x in $(seq 10 10 190) $(seq 200 100 2000); do
+ echo running with $x clients
+ rm -rf stats
+ taler-exchange-benchmark -c benchmark-local.conf -p $x -n 1000 >& /dev/shm/benchmark.log
+ mkdir -p "results/stats-$x"
+ cp -a stats "results/stats-$x"/
+ cp /dev/shm/benchmark.log "results/stats-$x/"
+done
diff --git a/doc/system/plots/set-latency.bash b/doc/system/plots/set-latency.bash
new file mode 100644
index 000000000..793d46c23
--- /dev/null
+++ b/doc/system/plots/set-latency.bash
@@ -0,0 +1,19 @@
+#/usr/bin/env bash
+
+# This is intended to be run with SSH agent forwarding,
+# so we can log in as root to adjust artificial delay.
+
+set -eu
+
+echo "setting latency to $1"
+
+# check that we can log in at least!
+ssh root@gv.taler.net true
+ssh root@firefly.gnunet.org true
+
+ssh root@gv.taler.net tc qdisc delete dev enp4s0f0 root || true
+ssh root@firefly.gnunet.org tc qdisc delete dev eno2 root || true
+
+ssh root@gv.taler.net tc qdisc add dev enp4s0f0 root netem delay "${1}ms"
+ssh root@firefly.gnunet.org tc qdisc add dev eno2 root netem delay "${1}ms"
+
diff --git a/doc/system/plots/speed.data b/doc/system/plots/speed.data
new file mode 100644
index 000000000..5690b84ec
--- /dev/null
+++ b/doc/system/plots/speed.data
@@ -0,0 +1,37 @@
+1 1.104439
+10 92.290911
+20 180.087087
+30 255.700284
+40 344.687076
+50 438.485028
+60 515.618333
+70 568.431831
+80 639.706303
+100 673.724088
+110 676.973144
+120 671.559308
+130 694.295694
+140 664.765652
+150 638.751296
+160 673.683504
+170 674.329287
+180 669.691392
+190 638.637718
+200 699.212198
+300 675.841986
+400 656.455187
+500 714.911636
+600 738.661570
+700 699.990279
+800 708.218566
+1000 735.599016
+1100 700.423479
+1200 687.508367
+1300 696.931102
+1400 698.255900
+1500 696.575458
+1600 737.278906
+1700 718.587847
+1800 691.539112
+1900 736.039940
+2000 742.994853
diff --git a/doc/system/plots/speed.pdf b/doc/system/plots/speed.pdf
new file mode 100644
index 000000000..b809c1d21
--- /dev/null
+++ b/doc/system/plots/speed.pdf
Binary files differ
diff --git a/doc/system/plots/time_bench_cpu.data b/doc/system/plots/time_bench_cpu.data
new file mode 100644
index 000000000..7cfeb813f
--- /dev/null
+++ b/doc/system/plots/time_bench_cpu.data
@@ -0,0 +1,39 @@
+1 9801666
+10 11386875
+20 23130250
+30 36564875
+40 47727458
+50 58359958
+60 70447500
+70 84446916
+80 93801750
+90 106124375
+100 119029750
+110 129536541
+120 147174791
+130 154257625
+140 174573916
+150 192325541
+160 194480625
+170 206233000
+180 214591541
+190 239929750
+200 236358375
+300 348233916
+400 495046791
+500 579896000
+600 689094875
+700 830684375
+800 957190833
+900 1058149291
+1000 1154518791
+1100 1325087916
+1200 1502792333
+1300 1610584958
+1400 1712165458
+1500 1838840458
+1600 1881089500
+1700 2023251583
+1800 2209685583
+1900 2241094458
+2000 2351564083
diff --git a/doc/system/plots/time_bench_ops_only.data b/doc/system/plots/time_bench_ops_only.data
new file mode 100644
index 000000000..558fd5bc8
--- /dev/null
+++ b/doc/system/plots/time_bench_ops_only.data
@@ -0,0 +1,39 @@
+1 2509331
+10 2564859
+20 5002341
+30 7865777
+40 10073982
+50 12128759
+60 14693754
+70 17792025
+80 19538636
+90 21980148
+100 24423023
+110 26545671
+120 30144030
+130 31522690
+140 35732386
+150 39585595
+160 39812006
+170 42203541
+180 44053474
+190 49793400
+200 48356499
+300 74601183
+400 105393510
+500 123044026
+600 145506335
+700 176345850
+800 200698466
+900 221478860
+1000 239238872
+1100 276518348
+1200 321194002
+1300 340475242
+1400 360182556
+1500 387822458
+1600 393044377
+1700 428264745
+1800 469067124
+1900 469026116
+2000 486510753
diff --git a/doc/system/plots/time_exchange_cpu.data b/doc/system/plots/time_exchange_cpu.data
new file mode 100644
index 000000000..137929124
--- /dev/null
+++ b/doc/system/plots/time_exchange_cpu.data
@@ -0,0 +1,39 @@
+1 4769125
+10 4958166
+20 9639333
+30 14984541
+40 19394166
+50 23851208
+60 28914708
+70 34698375
+80 38456250
+90 43448500
+100 48580291
+110 52942000
+120 59859458
+130 62778708
+140 70788500
+150 78093250
+160 78634750
+170 83169416
+180 86460000
+190 96958916
+200 94814958
+300 138324083
+400 194283541
+500 227209291
+600 267426291
+700 322986833
+800 368918208
+900 406839708
+1000 440552708
+1100 518428416
+1200 591421708
+1300 631228208
+1400 663142625
+1500 713897625
+1600 726439583
+1700 783727750
+1800 858787125
+1900 863532291
+2000 895376500
diff --git a/doc/system/plots/time_real.data b/doc/system/plots/time_real.data
new file mode 100644
index 000000000..7f518b73a
--- /dev/null
+++ b/doc/system/plots/time_real.data
@@ -0,0 +1,40 @@
+0 0
+1 905437353
+10 108353032
+20 111057380
+30 117324860
+40 116047287
+50 114028979
+60 116365141
+70 123145813
+80 125057389
+90 136314756
+100 148428714
+110 162487982
+120 178688611
+130 187240107
+140 210600532
+150 234833183
+160 237500249
+170 252102353
+180 268780519
+190 297508266
+200 286036200
+300 443890741
+400 609333292
+500 699387134
+600 812279973
+700 1000013888
+800 1129594787
+900 1251266347
+1000 1359436294
+1100 1570478479
+1200 1745433303
+1300 1865320684
+1400 2004995589
+1500 2153391973
+1600 2170142109
+1700 2365751116
+1800 2602889653
+1900 2581381658
+2000 2691808686
diff --git a/doc/system/ref.bib b/doc/system/ref.bib
new file mode 100644
index 000000000..4757ea713
--- /dev/null
+++ b/doc/system/ref.bib
@@ -0,0 +1,2825 @@
+@inproceedings{clement2009making,
+ author = {Clement, Allen and Wong, Edmund and Alvisi, Lorenzo and Dahlin, Mike and Marchetti, Mirco},
+ title = {Making Byzantine Fault Tolerant Systems Tolerate Byzantine Faults},
+ booktitle = {Proceedings of the 6th USENIX Symposium on Networked Systems Design and Implementation},
+ series = {NSDI'09},
+ year = {2009},
+ location = {Boston, Massachusetts},
+ pages = {153--168},
+ numpages = {16},
+ url = {http://dl.acm.org/citation.cfm?id=1558977.1558988},
+ acmid = {1558988},
+ publisher = {USENIX Association},
+ address = {Berkeley, CA, USA},
+}
+
+@article{fischer1985impossibility,
+ title={Impossibility of distributed consensus with one faulty process},
+ author={Fischer, Michael J and Lynch, Nancy A and Paterson, Michael S},
+ journal={Journal of the ACM (JACM)},
+ volume={32},
+ number={2},
+ pages={374--382},
+ year={1985},
+ publisher={ACM}
+}
+
+@Misc{cosmos,
+ author = {Jae Kwon and Ethan Buchman},
+ title = {Cosmos: A Network of Distributed Ledgers},
+ howpublished = {\url{https://cosmos.network/whitepaper}},
+ year = {2016},
+ note = {Accessed 22 Feb 2017},
+}
+
+@InProceedings{gns2014wachs,
+ author = {Wachs, Matthias and Schanzenbach, Martin and Grothoff, Christian},
+ title = {A Censorship-Resistant, Privacy-Enhancing and Fully Decentralized Name System},
+ booktitle = {Proceedings of the 13th International Conference on Cryptology and Network Security - Volume 8813},
+ year = {2014},
+ isbn = {978-3-319-12279-3},
+ pages = {127--142},
+ numpages = {16},
+ url = {http://dx.doi.org/10.1007/978-3-319-12280-9_9},
+ doi = {10.1007/978-3-319-12280-9_9},
+ acmid = {2769431},
+ publisher = {Springer-Verlag New York, Inc.},
+ address = {New York, NY, USA},
+}
+
+
+@Misc{gnunet-www,
+ label = "GNUNET",
+ title = "{The GNUnet Project}",
+ howpublished = {\url{https://gnunet.org/}},
+ note = {Accessed 28 Feb 2017},
+}
+
+@Misc{gnunet-git,
+ title = "{The GNUnet Project Git Repository}",
+ howpublished = {\url{git://gnunet.org/git/gnunet}},
+ note = {Accessed 28 Feb 2017},
+}
+
+@article{ben2010simple,
+ title={Simple gradecast based algorithms},
+ author={Ben-Or, Michael and Dolev, Danny and Hoch, Ezra N},
+ journal={arXiv preprint arXiv:1007.1049},
+ year={2010}
+}
+
+
+@incollection{ben2010brief,
+ title={Brief announcement: simple gradecast based algorithms},
+ author={Ben-Or, Michael and Dolev, Danny and Hoch, Ezra N},
+ booktitle={Distributed Computing},
+ pages={194--197},
+ year={2010},
+ publisher={Springer}
+}
+
+
+@phdthesis{feldman1988optimalphd,
+ title={Optimal algorithms for Byzantine agreement},
+ author={Feldman, Paul Neil},
+ year={1988},
+ school={Massachusetts Institute of Technology}
+}
+
+@inproceedings{feldman1988optimal,
+ author = {Feldman, Paul and Micali, Silvio},
+ title = {Optimal Algorithms for Byzantine Agreement},
+ booktitle = {Proceedings of the Twentieth Annual ACM Symposium on Theory of Computing},
+ series = {STOC '88},
+ year = {1988},
+ isbn = {0-89791-264-0},
+ location = {Chicago, Illinois, USA},
+ pages = {148--161},
+ numpages = {14},
+ url = {http://doi.acm.org/10.1145/62212.62225},
+ doi = {10.1145/62212.62225},
+ acmid = {62225},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+}
+
+
+@article{eppstein2011difference,
+ author = {Eppstein, David and Goodrich, Michael T. and Uyeda, Frank and Varghese, George},
+ title = {What's the Difference?: Efficient Set Reconciliation Without Prior Context},
+ journal = {SIGCOMM Comput. Commun. Rev.},
+ issue_date = {August 2011},
+ volume = {41},
+ number = {4},
+ month = {8},
+ year = {2011},
+ issn = {0146-4833},
+ pages = {218--229},
+ numpages = {12},
+ url = {http://doi.acm.org/10.1145/2043164.2018462},
+ doi = {10.1145/2043164.2018462},
+ acmid = {2018462},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+ keywords = {difference digest, invertible bloom filter, set difference},
+}
+
+
+@article{dwork1988consensus,
+ title={Consensus in the presence of partial synchrony},
+ author={Dwork, Cynthia and Lynch, Nancy and Stockmeyer, Larry},
+ journal={Journal of the ACM (JACM)},
+ volume={35},
+ number={2},
+ pages={288--323},
+ year={1988},
+ publisher={ACM}
+}
+
+
+@inproceedings{fitzi2006optimally,
+ author = {Fitzi, Matthias and Hirt, Martin},
+ title = {Optimally Efficient Multi-valued Byzantine Agreement},
+ booktitle = {Proceedings of the Twenty-fifth Annual ACM Symposium on Principles of Distributed Computing},
+ series = {PODC '06},
+ year = {2006},
+ isbn = {1-59593-384-0},
+ location = {Denver, Colorado, USA},
+ pages = {163--168},
+ numpages = {6},
+ url = {http://doi.acm.org/10.1145/1146381.1146407},
+ doi = {10.1145/1146381.1146407},
+ acmid = {1146407},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+ keywords = {byzantine agreement, communication complexity, cryptographic security, information-theoretic security},
+}
+
+
+% Problem: Really, really complex and not that efficient.
+@inproceedings{abraham2008almost,
+ title={An almost-surely terminating polynomial protocol for asynchronous byzantine agreement with optimal resilience},
+ author={Abraham, Ittai and Dolev, Danny and Halpern, Joseph Y},
+ booktitle={Proceedings of the twenty-seventh ACM symposium on Principles of distributed computing},
+ pages={405--414},
+ year={2008},
+ organization={ACM}
+}
+
+
+% Followup tp abraham2008almost
+% Problem: Requires some nasty hardware trusted
+% computing stuff?
+@incollection{abraham2010fast,
+ title={Fast asynchronous consensus with optimal resilience},
+ author={Abraham, Ittai and Aguilera, Marcos K and Malkhi, Dahlia},
+ booktitle={Distributed Computing},
+ pages={4--19},
+ year={2010},
+ publisher={Springer}
+}
+
+
+% Really nice summary of complexity bounds
+% and approaches to asynchrony
+@techreport{dutta2005best,
+ title={Best-case complexity of asynchronous Byzantine consensus},
+ author={Dutta, Partha and Guerraoui, Rachid and Vukolic, Marko},
+ year={2005},
+ institution={Technical Report EPFL/IC/200499, EPFL}
+}
+
+
+@inproceedings{castro1999practical,
+ author = {Miguel Castro and Barbara Liskov},
+ title = {Practical Byzantine Fault Tolerance},
+ booktitle = {Third Symposium on Operating Systems Design and
+ Implementation (OSDI)},
+ publisher = {USENIX Association, Co-sponsored by IEEE TCOS and ACM SIGOPS},
+ address = {New Orleans, Louisiana},
+ month = {2},
+ volume={99},
+ pages={173--186},
+ year = {1999}
+}
+
+
+@article{cramer1997secure,
+ title={A secure and optimally efficient multi-authority election scheme},
+ author={Cramer, Ronald and Gennaro, Rosario and Schoenmakers, Berry},
+ journal={European transactions on Telecommunications},
+ volume={8},
+ number={5},
+ pages={481--490},
+ year={1997},
+ publisher={Wiley Online Library}
+}
+
+
+@article{castro2002practical,
+ title={Practical Byzantine fault tolerance and proactive recovery},
+ author={Castro, Miguel and Liskov, Barbara},
+ journal={ACM Transactions on Computer Systems (TOCS)},
+ volume={20},
+ number={4},
+ pages={398--461},
+ year={2002},
+ publisher={ACM}
+}
+
+
+@article{lamport1982byzantine,
+ title={The Byzantine generals problem},
+ author={Lamport, Leslie and Shostak, Robert and Pease, Marshall},
+ journal={ACM Transactions on Programming Languages and Systems (TOPLAS)},
+ volume={4},
+ number={3},
+ pages={382--401},
+ year={1982},
+ publisher={ACM}
+}
+
+
+
+@article{schneider1990implementing,
+ title={Implementing fault-tolerant services using the state machine approach: A tutorial},
+ author={Schneider, Fred B},
+ journal={ACM Computing Surveys (CSUR)},
+ volume={22},
+ number={4},
+ pages={299--319},
+ year={1990},
+ publisher={ACM}
+}
+
+
+@inproceedings{ongaro2014search,
+ title={In search of an understandable consensus algorithm},
+ author={Ongaro, Diego and Ousterhout, John},
+ booktitle={Proc. USENIX Annual Technical Conference},
+ pages={305--320},
+ year={2014}
+}
+
+
+
+% Very important, highlights the
+% consensus part of Paxos/PBFT
+@incollection{lampson1996build,
+ title={How to build a highly available system using consensus},
+ author={Lampson, Butler W},
+ booktitle={Distributed Algorithms},
+ pages={1--17},
+ year={1996},
+ publisher={Springer}
+}
+
+
+@article{van2014vive,
+ title={Vive la diff{\'e}rence: Paxos vs. Viewstamped Replication vs. Zab},
+ author={Van Renesse, Robbert and Schiper, Nicolas and Schneider, Fred B},
+ year={2014},
+ publisher={IEEE}
+}
+
+
+
+% Problem: Very complex assumptions
+% Cachin seems much more practical, even if he uses signatures.
+@article{kapron2010fast,
+ author = {Kapron, Bruce M. and Kempe, David and King, Valerie and Saia, Jared and Sanwalani, Vishal},
+ title = {Fast Asynchronous Byzantine Agreement and Leader Election with Full Information},
+ journal = {ACM Trans. Algorithms},
+ issue_date = {August 2010},
+ volume = {6},
+ number = {4},
+ month = {9},
+ year = {2010},
+ issn = {1549-6325},
+ pages = {68:1--68:28},
+ articleno = {68},
+ numpages = {28},
+ url = {http://doi.acm.org/10.1145/1824777.1824788},
+ doi = {10.1145/1824777.1824788},
+ acmid = {1824788},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+ keywords = {Byzantine agreement, Monte Carlo algorithms, asynchronous communication, distributed algorithms, probabilistic method},
+}
+
+
+% Nice for future work section,
+% could be applied to consensus
+@article{mitzenmacher2013simple,
+ title={Simple Multi-Party Set Reconciliation},
+ author={Mitzenmacher, Michael and Pagh, Rasmus},
+ journal={arXiv preprint arXiv:1311.2037},
+ year={2013}
+}
+
+
+% Has great arguments for (against!) the complexity
+% of the state machine approach.
+@article{aublin2015next,
+ author = {Aublin, Pierre-Louis and Guerraoui, Rachid and Kne\v{z}evi\'{c}, Nikola and Qu{\'e}ma, Vivien and Vukoli\'{c}, Marko},
+ title = {The Next 700 BFT Protocols},
+ journal = {ACM Trans. Comput. Syst.},
+ issue_date = {January 2015},
+ volume = {32},
+ number = {4},
+ month = {1},
+ year = {2015},
+ issn = {0734-2071},
+ pages = {12:1--12:45},
+ articleno = {12},
+ numpages = {45},
+ url = {http://doi.acm.org/10.1145/2658994},
+ doi = {10.1145/2658994},
+ acmid = {2658994},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+ keywords = {Abstract, Byzantine, composability, fault tolerance, optimization, robustness},
+}
+
+
+% Good complexity comparison
+% for async case
+@inproceedings{mostefaoui2014signature,
+ author = {Mostefaoui, Achour and Moumen, Hamouma and Raynal, Michel},
+ title = {Signature-free Asynchronous Byzantine Consensus with {$t < n/3$} and {$O(n^2)$} Messages},
+ booktitle = {Proceedings of the 2014 ACM Symposium on Principles of Distributed Computing},
+ series = {PODC '14},
+ year = {2014},
+ isbn = {978-1-4503-2944-6},
+ location = {Paris, France},
+ pages = {2--9},
+ numpages = {8},
+ url = {http://doi.acm.org/10.1145/2611462.2611468},
+ doi = {10.1145/2611462.2611468},
+ acmid = {2611468},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+ keywords = {abstraction, asynchronous message-passing system, broadcast abstraction, byzantine process, common coin, consensus, distributed algorithm, optimal resilience, randomized algorithm, signature-free algorithm, simplicity},
+}
+
+
+% Failure detectors, overview
+@inbook{guerraoui2000consensus,
+ author="Guerraoui, Rachid
+ and Hurfinn, Michel
+ and Mostefaoui, Achour
+ and Oliveira, Riucarlos
+ and Raynal, Michel
+ and Schiper, Andre",
+ editor="Krakowiak, Sacha
+ and Shrivastava, Santosh",
+ title="Consensus in Asynchronous Distributed Systems: A Concise Guided Tour",
+ bookTitle="Advances in Distributed Systems: Advanced Distributed Computing: From Algorithms to Systems",
+ year="2000",
+ publisher="Springer Berlin Heidelberg",
+ address="Berlin, Heidelberg",
+ pages="33--47",
+ abstract="It is now recognized that the Consensus problem is a fundamental problem when one has to design and implement reliable asynchronous distributed systems. This chapter is on the Consensus problem. It studies Consensus in two failure models, namely, the Crash/no Recovery model and the Crash/Recovery model. The assumptions related to the detection of failures that are required to solve Consensus in a given model are particularly emphasized.",
+ isbn="978-3-540-46475-4",
+ doi="10.1007/3-540-46475-1_2",
+ url="https://doi.org/10.1007/3-540-46475-1_2"
+}
+
+
+% Good future work to implement this?
+@article{bouzidminimal,
+ title={Minimal Synchrony for Asynchronous Byzantine Consensus},
+ year={2015},
+ author={Bouzid, Zohir and Mostefaoui, Achour and Raynal, Michel},
+ publisher={Collection des Publications Internes de l'Irisa}
+}
+
+
+@incollection{lamport2011brief,
+ title={Brief announcement: leaderless byzantine paxos},
+ author={Lamport, Leslie},
+ booktitle={Distributed Computing},
+ pages={141--142},
+ year={2011},
+ publisher={Springer}
+}
+
+
+
+
+
+% Mention that we don't need early
+% stopping in voting (because of of fairness? property)
+@article{dolev1990early,
+ author = {Dolev, Danny and Reischuk, Ruediger and Strong, H. Raymond},
+ title = {Early Stopping in Byzantine Agreement},
+ journal = {J. ACM},
+ issue_date = {Oct. 1990},
+ volume = {37},
+ number = {4},
+ month = {10},
+ year = {1990},
+ issn = {0004-5411},
+ pages = {720--741},
+ numpages = {22},
+ url = {http://doi.acm.org/10.1145/96559.96565},
+ doi = {10.1145/96559.96565},
+ acmid = {96565},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+}
+
+
+% seminal
+@article{lamport1998part,
+ title={The part-time parliament},
+ author={Lamport, Leslie},
+ journal={ACM Transactions on Computer Systems (TOCS)},
+ volume={16},
+ number={2},
+ pages={133--169},
+ year={1998},
+ publisher={ACM}
+}
+
+
+% follow-up to seminal paper
+@article{lamport2001paxos,
+ title={Paxos made simple},
+ author={Lamport, Leslie},
+ journal={ACM Sigact News},
+ volume={32},
+ number={4},
+ pages={18--25},
+ year={2001}
+}
+
+
+% Important since it mentions other approaches
+% to the bulletin board stuff.
+@mastersthesis{peters2005secure,
+ type={Master's Thesis},
+ title={A Secure Bulletin Board},
+ author={Peters, RA},
+ school={Technische Universiteit Eindhoven},
+ year={2005}
+}
+
+@Mastersthesis{dold2014crypto,
+ author={Dold, Florian},
+ school={Technische Universit\"at M\"unchen},
+ type={Bachelor's Thesis},
+ title={Cryptographically Secure, Distributed Electronic Voting},
+ year={2014}
+}
+
+
+
+@inproceedings{pedersen1991threshold,
+ title={A threshold cryptosystem without a trusted party},
+ author={Pedersen, Torben Pryds},
+ booktitle={Advances in Cryptology—EUROCRYPT’91},
+ pages={522--526},
+ year={1991},
+ organization={Springer}
+}
+
+
+
+@Inbook{fouque2001one,
+ author="Fouque, Pierre-Alain
+ and Stern, Jacques",
+ editor="Kim, Kwangjo",
+ title="One Round Threshold Discrete-Log Key Generation without Private Channels",
+ bookTitle="Public Key Cryptography: 4th International Workshop on Practice and Theory in Public Key Cryptosystems, PKC 2001 Cheju Island, Korea, February 13--15, 2001 Proceedings",
+ year="2001",
+ publisher="Springer Berlin Heidelberg",
+ address="Berlin, Heidelberg",
+ pages="300--316",
+ abstract="Pedersen designed the first scheme for generating Discrete- Log keys without any trusted dealer in 1991. As this protocol is simple and efficient, it appeared to be very attractive. For a long time, this robust algorithm has been trusted as being secure. However, in 1999, Gennaro et al. proved that one of the requirements is not guaranteed : more precisely, the property that the key is uniformly distributed in the key space. Their main objective was to repair the security flaw without sacrificing on efficiency. As a result, the protocol became secure but somehow unpractical. In particular, the ``complaint phase'', in which cheaters are thrown out, makes the scheme overly complex and difficult to deal with in practical situations. In order to avoid this phase and other drawbacks such as the initialization phase where private channels have to be created, we present a one round scheme which generates a discrete-log key with public channels only. Finally, we show how to improve the efficiency of our algorithm when the number of servers increases.",
+ isbn="978-3-540-44586-9",
+ doi="10.1007/3-540-44586-2_22",
+ url="https://doi.org/10.1007/3-540-44586-2_22"
+}
+
+
+@incollection{aguilera2010stumbling,
+ author = {Aguilera, Marcos K.},
+ chapter = {Stumbling over Consensus Research: Misunderstandings and Issues},
+ title = {Replication},
+ editor = {Charron-Bost, Bernadette and Pedone, Fernando and Schiper, Andr{\'e}},
+ year = {2010},
+ %isbn = {3-642-11293-5, 978-3-642-11293-5},
+ pages = {59--72},
+ numpages = {14},
+ url = {http://dl.acm.org/citation.cfm?id=2172338.2172342},
+ acmid = {2172342},
+ publisher = {Springer-Verlag},
+ address = {Berlin, Heidelberg},
+}
+
+
+% Good overview of (some) complexity results
+@article{coan1992modular,
+ title={Modular construction of a Byzantine agreement protocol with optimal message bit complexity},
+ author={Coan, Brian A and Welch, Jennifer L},
+ journal={Information and Computation},
+ volume={97},
+ number={1},
+ pages={61--85},
+ year={1992},
+ publisher={Elsevier}
+}
+
+
+
+% good intro and thoughts on paxos / pbft
+@article{martin2006fast,
+ title={Fast byzantine consensus},
+ author={Martin, Jean-Philippe and Alvisi, Lorenzo},
+ journal={Dependable and Secure Computing, IEEE Transactions on},
+ volume={3},
+ number={3},
+ pages={202--215},
+ year={2006},
+ publisher={IEEE}
+}
+
+
+
+% Important, since it introduced it, according to ben2006byzantine
+@article{pease1980reaching,
+ title={Reaching agreement in the presence of faults},
+ author={Pease, Marshall and Shostak, Robert and Lamport, Leslie},
+ journal={Journal of the ACM (JACM)},
+ volume={27},
+ number={2},
+ pages={228--234},
+ year={1980},
+ publisher={ACM}
+}
+
+
+@inproceedings{ben2006byzantine,
+ title={Byzantine agreement in the full-information model in O (log n) rounds},
+ author={Ben-Or, Michael and Pavlov, Elan and Vaikuntanathan, Vinod},
+ booktitle={Proceedings of the thirty-eighth annual ACM symposium on Theory of computing},
+ pages={179--186},
+ year={2006},
+ organization={ACM}
+}
+
+
+
+% Seems like then best contender for
+% real async consensus
+@article{cachin2005random,
+ title={Random oracles in Constantinople: Practical asynchronous Byzantine agreement using cryptography},
+ author={Cachin, Christian and Kursawe, Klaus and Shoup, Victor},
+ journal={Journal of Cryptology},
+ volume={18},
+ number={3},
+ pages={219--246},
+ year={2005},
+ publisher={Springer}
+}
+
+
+
+% Seems like THE citation for SMC
+@article{goldreich1998secure,
+ title={Secure multi-party computation},
+ author={Goldreich, Oded},
+ journal={Manuscript. Preliminary version},
+ year={1998},
+ publisher={Citeseer}
+}
+
+
+
+@book{waldo1997note,
+ title={A note on distributed computing},
+ author={Waldo, Jim and Wyant, Geoff and Wollrath, Ann and Kendall, Sam},
+ year={1997},
+ publisher={Springer}
+}
+
+
+% one synchronous link is enough ...
+% also has some nice reductions ....
+@INPROCEEDINGS{aguilera2004communication,
+ author = {Marcos K. Aguilera and Carole Delporte-gallet and Hugues Fauconnier and Sam Toueg},
+ title = {Communication-efficient leader election and consensus with limited link synchrony},
+ booktitle = {In PODC},
+ year = {2004},
+ pages = {328--337},
+ publisher = {ACM Press}
+}
+
+
+@article{dolev1987minimal,
+ title={On the minimal synchronism needed for distributed consensus},
+ author={Dolev, Danny and Dwork, Cynthia and Stockmeyer, Larry},
+ journal={Journal of the ACM (JACM)},
+ volume={34},
+ number={1},
+ pages={77--97},
+ year={1987},
+ publisher={ACM}
+}
+
+
+@inproceedings{reiter1995rampart,
+ author = {Reiter, Michael K.},
+ title = {The Rampart Toolkit for Building High-Integrity Services},
+ booktitle = {Selected Papers from the International Workshop on Theory and Practice in Distributed Systems},
+ year = {1995},
+ isbn = {3-540-60042-6},
+ pages = {99--110},
+ numpages = {12},
+ url = {http://dl.acm.org/citation.cfm?id=647369.723763},
+ acmid = {723763},
+ publisher = {Springer-Verlag},
+ address = {London, UK, UK},
+}
+
+
+@inproceedings{kihlstrom1998securering,
+ author = {Kihlstrom, Kim Potter and Moser, L. E. and Melliar-Smith, P. M.},
+ title = {The SecureRing Protocols for Securing Group Communication},
+ booktitle = {Proceedings of the Thirty-First Annual Hawaii International Conference on System Sciences - Volume 3},
+ series = {HICSS '98},
+ year = {1998},
+ isbn = {0-8186-8239-6},
+ pages = {317--},
+ url = {http://dx.doi.org/10.1109/HICSS.1998.656294},
+ doi = {10.1109/HICSS.1998.656294},
+ acmid = {798823},
+ publisher = {IEEE Computer Society},
+ address = {Washington, DC, USA},
+}
+
+
+
+
+
+@article{minsky2003set,
+ title={Set reconciliation with nearly optimal communication complexity},
+ author={Minsky, Yaron and Trachtenberg, Ari and Zippel, Richard},
+ journal={Information Theory, IEEE Transactions on},
+ volume={49},
+ number={9},
+ pages={2213--2218},
+ year={2003},
+ publisher={IEEE}
+}
+
+
+
+@article{bloom1970space,
+ title={Space/time trade-offs in hash coding with allowable errors},
+ author={Bloom, Burton H},
+ journal={Communications of the ACM},
+ volume={13},
+ number={7},
+ pages={422--426},
+ year={1970},
+ publisher={ACM}
+}
+
+
+@article{hadzilacos1994modular,
+ title={A modular approach to fault-tolerant broadcasts and related problems},
+ author={Hadzilacos, Vassos and Toueg, Sam},
+ year={1994},
+ publisher={Cornell University, Department of Computer Science}
+}
+
+
+
+% problem: shared memory required
+@article{aspnes1998lower,
+ title={Lower bounds for distributed coin-flipping and randomized consensus},
+ author={Aspnes, James},
+ journal={Journal of the ACM (JACM)},
+ volume={45},
+ number={3},
+ pages={415--450},
+ year={1998},
+ publisher={ACM}
+}
+
+
+% strong connection between SMC and consensus
+@Inbook{saia2015recent,
+ author="Saia, Jared
+ and Zamani, Mahdi",
+ editor="Italiano, Giuseppe F.
+ and Margaria-Steffen, Tiziana
+ and Pokorn{\'y}, Jaroslav
+ and Quisquater, Jean-Jacques
+ and Wattenhofer, Roger",
+ title="Recent Results in Scalable Multi-Party Computation",
+ bookTitle="SOFSEM 2015: Theory and Practice of Computer Science: 41st International Conference on Current Trends in Theory and Practice of Computer Science, Pec pod Sn{\v{e}}{\v{z}}kou, Czech Republic, January 24-29, 2015. Proceedings",
+ year="2015",
+ publisher="Springer Berlin Heidelberg",
+ address="Berlin, Heidelberg",
+ pages="24--44",
+ abstract="Secure multi-party computation (MPC) allows multiple parties to compute a known function over inputs held by each party, without any party having to reveal its private input. Unfortunately, traditional MPC algorithms do not scale well to large numbers of parties. In this paper, we describe several recent MPC algorithms that are designed to handle large networks. All of these algorithms rely on recent techniques from the Byzantine agreement literature on forming and using quorums. Informally, a quorum is a small set of parties, most of which are trustworthy. We describe the advantages and disadvantages of these scalable algorithms, and we propose new ideas for improving practicality of current techniques. Finally, we conduct simulations to measure bandwidth cost for several current MPC algorithms.",
+ isbn="978-3-662-46078-8",
+ doi="10.1007/978-3-662-46078-8_3",
+ url="https://doi.org/10.1007/978-3-662-46078-8_3"
+}
+
+
+% argues that SMC does not need consensus.
+% some of the definitions (abort) look suspiciously
+% close to gradecasts
+@article{goldwasser2005secure,
+ title={Secure multi-party computation without agreement},
+ author={Goldwasser, Shafi and Lindell, Yehuda},
+ journal={Journal of Cryptology},
+ volume={18},
+ number={3},
+ pages={247--287},
+ year={2005},
+ publisher={Springer}
+}
+
+
+% This one got a Dijkstra award in 2015, so I should cite it.
+@inproceedings{ben1983another,
+ title={Another advantage of free choice (extended abstract): Completely asynchronous agreement protocols},
+ author={Ben-Or, Michael},
+ booktitle={Proceedings of the second annual ACM symposium on Principles of distributed computing},
+ pages={27--30},
+ year={1983},
+ organization={ACM}
+}
+
+
+
+% Another Dijkstra price, should be cited as
+% the main thing for failure detectors
+% Oh, but: Only crash-faults ...
+@article{chandra1996unreliable,
+ title={Unreliable failure detectors for reliable distributed systems},
+ author={Chandra, Tushar Deepak and Toueg, Sam},
+ journal={Journal of the ACM (JACM)},
+ volume={43},
+ number={2},
+ pages={225--267},
+ year={1996},
+ publisher={ACM}
+}
+
+
+@incollection{bonomi2006improved,
+ title={An improved construction for counting bloom filters},
+ author={Bonomi, Flavio and Mitzenmacher, Michael and Panigrahy, Rina and Singh, Sushil and Varghese, George},
+ booktitle={Algorithms--ESA 2006},
+ pages={684--695},
+ year={2006},
+ publisher={Springer}
+}
+
+
+
+% Very good overview of bloom filters and advanced
+% stuff you can do with them.
+@article{tarkoma2012theory,
+ title={Theory and practice of bloom filters for distributed systems},
+ author={Tarkoma, Sasu and Rothenberg, Christian Esteve and Lagerspetz, Eemil},
+ journal={Communications Surveys \& Tutorials, IEEE},
+ volume={14},
+ number={1},
+ pages={131--155},
+ year={2012},
+ publisher={IEEE}
+}
+
+
+@article{neiger1994distributed,
+ title={Distributed consensus revisited},
+ author={Neiger, Gil},
+ journal={Information Processing Letters},
+ volume={49},
+ number={4},
+ pages={195--201},
+ year={1994},
+ publisher={Elsevier}
+}
+
+
+
+@techreport{miller2014anonymous,
+ title={Anonymous byzantine consensus from moderately-hard puzzles: A model for bitcoin},
+ author={Miller, Andrew and LaViola Jr, Joseph J},
+ number={CS-TR-14-01},
+ year={2014},
+ month={4},
+ institution={University of Central Florida}
+}
+
+
+@inbook{garay2015bitcoin,
+ author="Garay, Juan
+ and Kiayias, Aggelos
+ and Leonardos, Nikos",
+ editor="Oswald, Elisabeth
+ and Fischlin, Marc",
+ title="The Bitcoin Backbone Protocol: Analysis and Applications",
+ bookTitle="Advances in Cryptology - EUROCRYPT 2015: 34th Annual International Conference on the Theory and Applications of Cryptographic Techniques, Sofia, Bulgaria, April 26-30, 2015, Proceedings, Part II",
+ year="2015",
+ publisher="Springer Berlin Heidelberg",
+ address="Berlin, Heidelberg",
+ pages="281--310",
+ abstract="Bitcoin is the first and most popular decentralized cryptocurrency to date. In this work, we extract and analyze the core of the Bitcoin protocol, which we term the Bitcoin backbone, and prove two of its fundamental properties which we call common prefix and chain quality in the static setting where the number of players remains fixed. Our proofs hinge on appropriate and novel assumptions on the ``hashing power'' of the adversary relative to network synchronicity; we show our results to be tight under high synchronization.",
+ isbn="978-3-662-46803-6",
+ doi="10.1007/978-3-662-46803-6_10",
+ url="https://doi.org/10.1007/978-3-662-46803-6_10"
+}
+
+
+@article{schwartz2014ripple,
+ title={The Ripple protocol consensus algorithm},
+ author={Schwartz, David and Youngs, Noah and Britto, Arthur},
+ journal={Ripple Labs Inc White Paper},
+ year={2014}
+}
+
+
+@mastersthesis {totakura2013large,
+ title = {Large Scale Distributed Evaluation of Peer-to-Peer Protocols},
+ volume = {Master of Science},
+ year = {2013},
+ month = {6},
+ pages = {76},
+ school = {Technische Universit\"at M\"unchen},
+ type = {Master's Thesis},
+ address = {Garching bei M\"unchen},
+ keywords = {emulation, GNUnet, large scale testing, protocol evaluation, testbed},
+ author = {Totakura, Sree Harsha}
+}
+
+
+@book{okasaki1999purely,
+ author = {Okasaki, Chris},
+ title = {Purely Functional Data Structures},
+ year = {1998},
+ isbn = {0-521-63124-6},
+ publisher = {Cambridge University Press},
+ address = {New York, NY, USA},
+}
+
+
+@inproceedings{attiya1984asynchronous,
+ author = {Attiya, Chagit and Dolev, Danny and Gil, Joseph},
+ title = {Asynchronous Byzantine Consensus},
+ booktitle = {Proceedings of the Third Annual ACM Symposium on Principles of Distributed Computing},
+ series = {PODC '84},
+ year = {1984},
+ isbn = {0-89791-143-1},
+ location = {Vancouver, British Columbia, Canada},
+ pages = {119--133},
+ numpages = {15},
+ url = {http://doi.acm.org/10.1145/800222.806740},
+ doi = {10.1145/800222.806740},
+ acmid = {806740},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+}
+
+
+
+@article{deutsch1996gzip,
+ title={GZIP file format specification version 4.3},
+ author={Deutsch, L Peter},
+ year={1996}
+}
+
+
+@inproceedings{polot2014cadet,
+ author={B. Polot and C. Grothoff},
+ booktitle={2014 13th Annual Mediterranean Ad Hoc Networking Workshop (MED-HOC-NET)},
+ title={CADET: Confidential ad-hoc decentralized end-to-end transport},
+ year={2014},
+ pages={71-78},
+ keywords={Internet;ad hoc networks;computer network performance evaluation;computer network security;telecommunication network routing;telecommunication network topology;transport protocols;CADET;Internet-usage;ad-hoc wireless networks;authenticated data transfer;confidential ad-hoc decentralized end-to-end transport;confidential data transfer;decentralized networks;friend-to-friend networks;high-speed low-latency networks;network topologies;performance evaluation;restricted-route scenarios;transport protocol;Ad hoc networks;IP networks;Network topology;Peer-to-peer computing;Protocols;Routing;Topology},
+ doi={10.1109/MedHocNet.2014.6849107},
+ month={6},
+}
+
+
+
+@book{benaloh1987verifiable,
+ title={Verifiable secret-ballot elections},
+ author={Benaloh, Josh Daniel Cohen},
+ year={1987},
+ publisher={Yale University. Department of Computer Science}
+}
+
+
+@inproceedings{bessani2014state,
+ title={State machine replication for the masses with BFT-SMaRt},
+ author={Bessani, Alysson and Sousa, Jo{\~a}o and Alchieri, Eduardo EP},
+ booktitle={Dependable Systems and Networks (DSN), 2014 44th Annual IEEE/IFIP International Conference on},
+ pages={355--362},
+ year={2014},
+ organization={IEEE}
+}
+
+
+@techreport{fischer1981lower,
+ title={A lower bound for the time to assure interactive consistency},
+ author={Fischer, Michael J and Lynch, Nancy A},
+ year={1981},
+ institution={DTIC Document}
+}
+
+@article{de2001k,
+ title={On k-set consensus problems in asynchronous systems},
+ author={De Prisco, Roberto and Malkhi, Dahlia and Reiter, Michael},
+ journal={Parallel and Distributed Systems, IEEE Transactions on},
+ volume={12},
+ number={1},
+ pages={7--21},
+ year={2001},
+ publisher={IEEE}
+}
+
+
+@inproceedings{malpani2000leader,
+ author = {Malpani, Navneet and Welch, Jennifer L. and Vaidya, Nitin},
+ title = {Leader Election Algorithms for Mobile Ad Hoc Networks},
+ booktitle = {Proceedings of the 4th International Workshop on Discrete Algorithms and Methods for Mobile Computing and Communications},
+ series = {DIALM '00},
+ year = {2000},
+ isbn = {1-58113-301-4},
+ location = {Boston, Massachusetts, USA},
+ pages = {96--103},
+ numpages = {8},
+ url = {http://doi.acm.org/10.1145/345848.345871},
+ doi = {10.1145/345848.345871},
+ acmid = {345871},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+}
+
+
+@article{fischer1986easy,
+ title={Easy impossibility proofs for distributed consensus problems},
+ author={Fischer, Michael J and Lynch, Nancy A and Merritt, Michael},
+ journal={Distributed Computing},
+ volume={1},
+ number={1},
+ pages={26--39},
+ year={1986},
+ publisher={Springer}
+}
+
+@inproceedings{Miller:2016:HBB:2976749.2978399,
+ author = {Miller, Andrew and Xia, Yu and Croman, Kyle and Shi, Elaine and Song, Dawn},
+ title = {The Honey Badger of BFT Protocols},
+ booktitle = {Proceedings of the 2016 ACM SIGSAC Conference on Computer and Communications Security},
+ series = {CCS '16},
+ year = {2016},
+ isbn = {978-1-4503-4139-4},
+ location = {Vienna, Austria},
+ pages = {31--42},
+ numpages = {12},
+ url = {http://doi.acm.org/10.1145/2976749.2978399},
+ doi = {10.1145/2976749.2978399},
+ acmid = {2978399},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+ keywords = {BFT, asynchronous, atomic broadcast, blockchain},
+}
+
+
+@misc{cryptoeprint:2016:199,
+ author = {Andrew Miller and Yu Xia and Kyle Croman and Elaine Shi and Dawn Song},
+ title = {The Honey Badger of BFT Protocols},
+ howpublished = {Cryptology ePrint Archive, Report 2016/199},
+ year = {2016},
+ note = {\url{http://eprint.iacr.org/2016/199}},
+}
+
+@misc{cryptoeprint:2016:1067,
+ author = {Ewa Syta and Philipp Jovanovic and Eleftherios Kokoris Kogias and Nicolas Gailly and Linus Gasser and Ismail Khoffi and Michael J. Fischer and Bryan Ford},
+ title = {Scalable Bias-Resistant Distributed Randomness},
+ howpublished = {Cryptology ePrint Archive, Report 2016/1067},
+ year = {2016},
+ note = {\url{http://eprint.iacr.org/2016/1067}, Accessed 22 Feb 2017},
+}
+
+@article{abd2005fault,
+ title={Fault-scalable Byzantine fault-tolerant services},
+ author={Abd-El-Malek, Michael and Ganger, Gregory R and Goodson, Garth R and Reiter, Michael K and Wylie, Jay J},
+ journal={ACM SIGOPS Operating Systems Review},
+ volume={39},
+ number={5},
+ pages={59--74},
+ year={2005},
+ publisher={ACM}
+}
+
+
+@inproceedings{kotla2007zyzzyva,
+ author = {Kotla, Ramakrishna and Alvisi, Lorenzo and Dahlin, Mike and Clement, Allen and Wong, Edmund},
+ title = {Zyzzyva: Speculative Byzantine Fault Tolerance},
+ booktitle = {Proceedings of Twenty-first ACM SIGOPS Symposium on Operating Systems Principles},
+ series = {SOSP '07},
+ year = {2007},
+ isbn = {978-1-59593-591-5},
+ location = {Stevenson, Washington, USA},
+ pages = {45--58},
+ numpages = {14},
+ url = {http://doi.acm.org/10.1145/1294261.1294267},
+ doi = {10.1145/1294261.1294267},
+ acmid = {1294267},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+ keywords = {byzantine fault tolerance, output commit, replication, speculative execution},
+}
+
+
+@article{nakamoto2008bitcoin,
+ title={Bitcoin: A peer-to-peer electronic cash system},
+ author={Nakamoto, Satoshi},
+ journal={Consulted},
+ volume={1},
+ number={2012},
+ pages={28},
+ year={2008}
+}
+
+
+@incollection{rink2013mixed,
+ year={2013},
+ isbn={978-3-642-35842-5},
+ booktitle={SOFSEM 2013: Theory and Practice of Computer Science},
+ volume={7741},
+ series={Lecture Notes in Computer Science},
+ editor={van Emde Boas, Peter and Groen, FransC.A. and Italiano, GiuseppeF. and Nawrocki, Jerzy and Sack, Harald},
+ doi={10.1007/978-3-642-35843-2_31},
+ title={Mixed Hypergraphs for Linear-Time Construction of Denser Hashing-Based Data Structures},
+ url={http://dx.doi.org/10.1007/978-3-642-35843-2_31},
+ publisher={Springer Berlin Heidelberg},
+ author={Rink, Michael},
+ pages={356-368},
+ language={English}
+}
+
+
+@inproceedings{goodrich2011invertible,
+ title={Invertible bloom lookup tables},
+ author={Goodrich, Michael T and Mitzenmacher, Michael},
+ booktitle={Communication, Control, and Computing (Allerton), 2011 49th Annual Allerton Conference on},
+ pages={792--799},
+ year={2011},
+ organization={IEEE}
+}
+
+
+@article{li2011theory,
+ title={Theory and applications of b-bit minwise hashing},
+ author={Li, Ping and K{\"o}nig, Arnd Christian},
+ journal={Communications of the ACM},
+ volume={54},
+ number={8},
+ pages={101--109},
+ year={2011},
+ publisher={ACM}
+}
+
+@inproceedings{adida2008helios,
+ author = {Adida, Ben},
+ title = {Helios: Web-based Open-audit Voting},
+ booktitle = {Proceedings of the 17th Conference on Security Symposium},
+ series = {SS'08},
+ year = {2008},
+ location = {San Jose, CA},
+ pages = {335--348},
+ numpages = {14},
+ url = {http://dl.acm.org/citation.cfm?id=1496711.1496734},
+ acmid = {1496734},
+ publisher = {USENIX Association},
+ address = {Berkeley, CA, USA},
+}
+
+
+@article{desmedt1994threshold,
+ title={Threshold cryptography},
+ author={Desmedt, Yvo G},
+ journal={European Transactions on Telecommunications},
+ volume={5},
+ number={4},
+ pages={449--458},
+ year={1994},
+ publisher={Wiley Online Library}
+}
+
+
+@article{shamir1979share,
+ title={How to share a secret},
+ author={Shamir, Adi},
+ journal={Communications of the ACM},
+ volume={22},
+ number={11},
+ pages={612--613},
+ year={1979},
+ publisher={ACM}
+}
+
+% Cite some of the voting stuff
+% what else is there about set reconciliation?
+
+
+
+% Just another SMC protocol that requires agreement
+% on potentially large sets.
+@incollection{bogetoft2009secure,
+ author = {Bogetoft, Peter and Christensen, Dan Lund and Damg{\aa}rd, Ivan and Geisler, Martin and Jakobsen, Thomas and Kr{\o}igaard, Mikkel and Nielsen, Janus Dam and Nielsen, Jesper Buus and Nielsen, Kurt and Pagter, Jakob and Schwartzbach, Michael and Toft, Tomas},
+ chapter = {Secure Multiparty Computation Goes Live},
+ title = {Financial Cryptography and Data Security},
+ editor = {Dingledine, Roger and Golle, Philippe},
+ year = {2009},
+ isbn = {978-3-642-03548-7},
+ pages = {325--343},
+ numpages = {19},
+ url = {http://dx.doi.org/10.1007/978-3-642-03549-4_20},
+ doi = {10.1007/978-3-642-03549-4_20},
+ acmid = {1602018},
+ publisher = {Springer-Verlag},
+ address = {Berlin, Heidelberg},
+}
+
+
+@inproceedings{evans2012efficient,
+ title={Efficient and secure decentralized network size estimation},
+ author={Evans, Nathan and Polot, Bartlomiej and Grothoff, Christian},
+ booktitle={Proceedings of the 11th international IFIP TC 6 conference on Networking-Volume Part I},
+ pages={304--317},
+ year={2012},
+ organization={Springer-Verlag}
+}
+
+
+@misc{green2016bolt,
+ author = {Matthew Green and Ian Miers},
+ title = {Bolt: Anonymous Payment Channels for Decentralized Currencies},
+ howpublished = {Cryptology ePrint Archive, Report 2016/701},
+ year = {2016},
+ note = {\url{http://eprint.iacr.org/2016/701}},
+}
+
+
+
+@inproceedings{3DSsucks,
+ author = {Murdoch, Steven J. and Anderson, Ross},
+ title = {Verified by Visa and Mastercard Securecode: Or, How Not to Design Authentication},
+ booktitle = {Proceedings of the 14th International Conference on Financial Cryptography and Data Security},
+ series = {FC'10},
+ year = {2010},
+ %isbn = {3-642-14576-0, 978-3-642-14576-6},
+ location = {Tenerife, Spain},
+ pages = {336--342},
+ numpages = {7},
+ doi_url = {http://dx.doi.org/10.1007/978-3-642-14577-3_27},
+ doi = {10.1007/978-3-642-14577-3_27},
+ acmid = {2163598},
+ publisher = {Springer-Verlag},
+ address = {Berlin, Heidelberg},
+ url = {https://www.cl.cam.ac.uk/~rja14/Papers/fc10vbvsecurecode.pdf}
+}
+
+
+@Inbook{izabachene2013divisible,
+ author="Izabach{\`e}ne, Malika
+ and Libert, Beno{\^i}t",
+ editor="Abdalla, Michel
+ and Lange, Tanja",
+ title="Divisible E-Cash in the Standard Model",
+ bookTitle="Pairing-Based Cryptography -- Pairing 2012: 5th International Conference, Cologne, Germany, May 16-18, 2012, Revised Selected Papers",
+ year="2013",
+ publisher="Springer Berlin Heidelberg",
+ address="Berlin, Heidelberg",
+ pages="314--332",
+ abstract="Off-line e-cash systems are the digital analogue of regular cash. One of the main desirable properties is anonymity: spending a coin should not reveal the identity of the spender and, at the same time, users should not be able to double-spend coins without being detected. Compact e-cash systems make it possible to store a wallet of O(2 L ) coins using O(L{\thinspace}+{\thinspace}$\lambda$) bits, where $\lambda$ is the security parameter. They are called divisible whenever the user has the flexibility of spending an amount of 2ℓ, for some ℓ{\thinspace}≤{\thinspace}L, more efficiently than by repeatedly spending individual coins. This paper presents the first construction of divisible e-cash in the standard model (i.e., without the random oracle heuristic). The scheme allows a user to obtain a wallet of 2 L coins by running a withdrawal protocol with the bank. Our construction is built on the traditional binary tree approach, where the wallet is organized in such a way that the monetary value of a coin depends on how deep the coin is in the tree.",
+ isbn="978-3-642-36334-4",
+ doi="10.1007/978-3-642-36334-4_20",
+ url="https://doi.org/10.1007/978-3-642-36334-4_20"
+}
+
+
+@Inbook{pointcheval1996provably,
+author="Pointcheval, David
+and Stern, Jacques",
+editor="Kim, Kwangjo
+and Matsumoto, Tsutomu",
+title="Provably secure blind signature schemes",
+bookTitle="Advances in Cryptology --- ASIACRYPT '96: International Conference on the Theory and Applications of Cryptology and Information Security Kyongju, Korea, November 3--7, 1996 Proceedings",
+year="1996",
+publisher="Springer Berlin Heidelberg",
+address="Berlin, Heidelberg",
+pages="252--265",
+abstract="In this paper, we give a provably secure design for blind signatures, the most important ingredient for anonymity in off-line electronic cash systems. Previous examples of blind signature schemes were constructed from traditional signature schemes with only the additional proof of blindness. The design of some of the underlying signature schemes can be validated by a proof in the so-called random oracle model, but the security of the original signature scheme does not, by itself, imply the security of the blind version. In this paper, we first propose a definition of security for blind signatures, with application to electronic cash. Next, we focus on a specific example which can be successfully transformed in a provably secure blind signature scheme.",
+isbn="978-3-540-70707-3",
+doi="10.1007/BFb0034852",
+url="https://doi.org/10.1007/BFb0034852"
+}
+
+
+@Article{bellare2003onemore,
+author="Bellare
+and Namprempre
+and Pointcheval
+and Semanko",
+title="The One-More-RSA-Inversion Problems and the Security of Chaum's Blind Signature Scheme ",
+journal="Journal of Cryptology",
+year="2003",
+month={6},
+day="01",
+volume="16",
+number="3",
+pages="185--215",
+abstract="We introduce a new class of computational problems which we call the ``one-more-RSA-inversion'' problems. Our main result is that two problems in this class, which we call the chosen-target and known-target inversion problems, respectively, have polynomially equivalent computational complexity. We show how this leads to a proof of security for Chaum's RSA-based blind signature scheme in the random oracle model based on the assumed hardness of either of these problems. We define and prove analogous results for ``one-more-discrete-logarithm'' problems. Since the appearence of the preliminary version of this paper, the new problems we have introduced have found other uses as well.",
+issn="1432-1378",
+doi="10.1007/s00145-002-0120-1",
+url="https://doi.org/10.1007/s00145-002-0120-1"
+}
+
+
+@InProceedings{fc2014murdoch,
+ author = {Stephen Murdoch and Ross Anderson},
+ title = {Security Protocols and Evidence: Where Many Payment Systems Fail},
+ booktitle = {Financial Cryptography and Data Security},
+ year = {2014},
+}
+
+
+
+@Inbook{pointcheval2017cut,
+ author="Pointcheval, David
+ and Sanders, Olivier
+ and Traor{\'e}, Jacques",
+ editor="Fehr, Serge",
+ title="Cut Down the Tree to Achieve Constant Complexity in Divisible E-cash",
+ bookTitle="Public-Key Cryptography -- PKC 2017: 20th IACR International Conference on Practice and Theory in Public-Key Cryptography, Amsterdam, The Netherlands, March 28-31, 2017, Proceedings, Part I",
+ year="2017",
+ publisher="Springer Berlin Heidelberg",
+ address="Berlin, Heidelberg",
+ pages="61--90",
+ abstract="Divisible e-cash, proposed in 1991 by Okamoto and Ohta, addresses a practical concern of electronic money, the problem of paying the exact amount. Users of such systems can indeed withdraw coins of a large value N and then divide it into many pieces of any desired values {\$}{\$}V{\backslash}le N{\$}{\$} . Such a primitive therefore allows to avoid the use of several denominations or change issues. Since its introduction, many constructions have been proposed but all of them make use of the same framework: they associate each coin with a binary tree, which implies, at least, a logarithmic complexity for the spendings.",
+ isbn="978-3-662-54365-8",
+ doi="10.1007/978-3-662-54365-8_4",
+ url="https://doi.org/10.1007/978-3-662-54365-8_4"
+}
+
+
+
+@Inbook{canard2015divisible,
+ author="Canard, S{\'e}bastien
+ and Pointcheval, David
+ and Sanders, Olivier
+ and Traor{\'e}, Jacques",
+ editor="Katz, Jonathan",
+ title="Divisible E-Cash Made Practical",
+ bookTitle="Public-Key Cryptography -- PKC 2015: 18th IACR International Conference on Practice and Theory in Public-Key Cryptography, Gaithersburg, MD, USA, March 30 -- April 1, 2015, Proceedings",
+ year="2015",
+ publisher="Springer Berlin Heidelberg",
+ address="Berlin, Heidelberg",
+ pages="77--100",
+ abstract="Divisible E-cash systems allow users to withdraw a unique coin of value {\$}{\$}2^n{\$}{\$} from a bank, but then to spend it in several times to distinct merchants. In such a system, whereas users want anonymity of their transactions, the bank wants to prevent, or at least detect, double-spending, and trace the defrauders. While this primitive was introduced two decades ago, quite a few (really) anonymous constructions have been introduced. In addition, all but one were just proven secure in the random oracle model, but still with either weak security models or quite complex settings and thus costly constructions. The unique proposal, secure in the standard model, appeared recently and is unpractical. As evidence, the authors left the construction of an efficient scheme secure in this model as an open problem.",
+ isbn="978-3-662-46447-2",
+ doi="10.1007/978-3-662-46447-2_4",
+ url="https://doi.org/10.1007/978-3-662-46447-2_4"
+}
+
+
+
+@Inbook{camenisch2005compact,
+ author="Camenisch, Jan
+ and Hohenberger, Susan
+ and Lysyanskaya, Anna",
+ editor="Cramer, Ronald",
+ title="Compact E-Cash",
+ bookTitle="Advances in Cryptology -- EUROCRYPT 2005: 24th Annual International Conference on the Theory and Applications of Cryptographic Techniques, Aarhus, Denmark, May 22-26, 2005. Proceedings",
+ year="2005",
+ publisher="Springer Berlin Heidelberg",
+ address="Berlin, Heidelberg",
+ pages="302--321",
+ isbn="978-3-540-32055-5",
+ doi="10.1007/11426639_18",
+ url="https://doi.org/10.1007/11426639_18"
+}
+
+
+@misc{maertens2015practical,
+ author = {Patrick Märtens},
+ title = {Practical Compact E-Cash with Arbitrary Wallet Size},
+ howpublished = {Cryptology ePrint Archive, Report 2015/086},
+ year = {2015},
+ note = {\url{http://eprint.iacr.org/2015/086}},
+}
+
+@Inbook{canard2015scalable,
+author="Canard, S{\'e}bastien
+and Pointcheval, David
+and Sanders, Olivier
+and Traor{\'e}, Jacques",
+editor="Malkin, Tal
+and Kolesnikov, Vladimir
+and Lewko, Allison Bishop
+and Polychronakis, Michalis",
+title="Scalable Divisible E-cash",
+bookTitle="Applied Cryptography and Network Security: 13th International Conference, ACNS 2015, New York, NY, USA, June 2-5, 2015, Revised Selected Papers",
+year="2015",
+publisher="Springer International Publishing",
+address="Cham",
+pages="287--306",
+abstract="Divisible E-cash has been introduced twenty years ago but no construction is both fully secure in the standard model and efficiently scalable. In this paper, we fill this gap by providing an anonymous divisible E-cash construction with constant-time withdrawal and spending protocols. Moreover, the deposit protocol is constant-time for the merchant, whatever the spent value is. It just has to compute and store {\$}{\$}2^l{\$}{\$} serial numbers when a value {\$}{\$}2^l{\$}{\$} is deposited, compared to {\$}{\$}2^n{\$}{\$} serial numbers whatever the spent amount (where {\$}{\$}2^n{\$}{\$} is the global value of the coin) in the recent state-of-the-art paper. This makes a very huge difference when coins are spent in several times.",
+isbn="978-3-319-28166-7",
+doi="10.1007/978-3-319-28166-7_14",
+url="https://doi.org/10.1007/978-3-319-28166-7_14"
+}
+
+
+
+
+@Inbook{okamoto1995efficient,
+author="Okamoto, Tatsuaki",
+editor="Coppersmith, Don",
+title="An Efficient Divisible Electronic Cash Scheme",
+bookTitle="Advances in Cryptology --- CRYPT0' 95: 15th Annual International Cryptology Conference Santa Barbara, California, USA, August 27--31, 1995 Proceedings",
+year="1995",
+publisher="Springer Berlin Heidelberg",
+address="Berlin, Heidelberg",
+pages="438--451",
+abstract="Recently, several ``divisible'' untraceable off-line electronic cash schemes have been presented [8, 11, 19, 20]. This paper presents the first practical ``divisible'' untraceable1 off-line cash scheme that is ``single-term''2 in which every procedure can be executed in the order of log N, where N is the precision of divisibility, i.e., N = (the total coin value)/(minimum divisible unit value). Therefore, our ``divisible'' off-line cash scheme is more efficient and practical than the previous schemes. For example, when N = 217 (e.g., the total value is about {\$} 1000, and the minimum divisible unit is 1 cent), our scheme requires only about 1 Kbyte of data be transfered from a customer to a shop for one payment and about 20 modular exponentiations for one payment, while all previous divisible cash schemes require more than several Kbytes of transfered data and more than 200 modular exponentiations for one payment.",
+isbn="978-3-540-44750-4",
+doi="10.1007/3-540-44750-4_35",
+url="https://doi.org/10.1007/3-540-44750-4_35"
+}
+
+@techreport{brands1993efficient,
+ author = {Brands, Stefan A.},
+ title = {An Efficient Off-line Electronic Cash System Based On The Representation Problem.},
+ year = {1993},
+ source = {http://www.ncstrl.org:8900/ncstrl/servlet/search?formname=detail\&id=oai%3Ancstrlh%3Aercim_cwi%3Aercim.cwi%2F%2FCS-R9323},
+ publisher = {CWI (Centre for Mathematics and Computer Science)},
+ address = {Amsterdam, The Netherlands, The Netherlands},
+}
+
+
+
+@inproceedings{tracz2001fair,
+ author = {Tracz, Robert and Wrona, Konrad},
+ title = {Fair Electronic Cash Withdrawal and Change Return for Wireless Networks},
+ booktitle = {Proceedings of the 1st International Workshop on Mobile Commerce},
+ series = {WMC '01},
+ year = {2001},
+ isbn = {1-58113-376-6},
+ location = {Rome, Italy},
+ pages = {14--19},
+ numpages = {6},
+ url = {http://doi.acm.org/10.1145/381461.381464},
+ doi = {10.1145/381461.381464},
+ acmid = {381464},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+ keywords = {electronic commerce, payment systems, wireless applications},
+}
+
+
+@inproceedings{schoenmakers1997security,
+ author = {Schoenmakers, Berry},
+ title = {Security Aspects of the Ecash(TM) Payment System},
+ booktitle = {State of the Art in Applied Cryptography, Course on Computer Security and Industrial Cryptography - Revised Lectures},
+ year = {1998},
+ isbn = {3-540-65474-7},
+ location = {Leuven, Belgium},
+ pages = {338--352},
+ numpages = {15},
+ url = {http://dl.acm.org/citation.cfm?id=647443.726912},
+ acmid = {726912},
+ publisher = {Springer-Verlag},
+ address = {London, UK, UK},
+}
+
+
+
+
+
+@Inbook{chaum1983blind,
+ author="Chaum, David",
+ editor="Chaum, David
+ and Rivest, Ronald L.
+ and Sherman, Alan T.",
+ title="Blind Signatures for Untraceable Payments",
+ bookTitle="Advances in Cryptology: Proceedings of Crypto 82",
+ year="1983",
+ publisher="Springer US",
+ address="Boston, MA",
+ pages="199--203",
+ abstract="Automation of the way we pay for goods and services is already underway, as can be seen by the variety and growth of electronic banking services available to consumers. The ultimate structure of the new electronic payments system may have a substantial impact on personal privacy as well as on the nature and extent of criminal use of payments. Ideally a new payments system should address both of these seemingly conflicting sets of concerns.",
+ isbn="978-1-4757-0602-4",
+ doi="10.1007/978-1-4757-0602-4_18",
+ url="https://doi.org/10.1007/978-1-4757-0602-4_18"
+}
+
+
+
+@Inbook{chaum1990untraceable,
+ author="Chaum, David
+ and Fiat, Amos
+ and Naor, Moni",
+ editor="Goldwasser, Shafi",
+ title="Untraceable Electronic Cash",
+ bookTitle="Advances in Cryptology --- CRYPTO' 88: Proceedings",
+ year="1990",
+ publisher="Springer New York",
+ address="New York, NY",
+ pages="319--327",
+ abstract="The use of credit cards today is an act of faith on the part of all concerned. Each party is vulnerable to fraud by the others, and the cardholder in particular has no protection against surveillance.",
+ isbn="978-0-387-34799-8",
+ doi="10.1007/0-387-34799-2_25",
+ url="https://doi.org/10.1007/0-387-34799-2_25"
+}
+
+
+@INPROCEEDINGS{camenisch2007endorsed,
+ author={J. Camenisch and A. Lysyanskaya and M. Meyerovich},
+ booktitle={2007 IEEE Symposium on Security and Privacy (SP '07)},
+ title={Endorsed E-Cash},
+ year={2007},
+ pages={101-115},
+ keywords={electronic money;protocols;e-cash;electronic cash scheme;fair exchange protocol;lightweight endorsement;onion routing;Authentication;Cryptographic protocols;Cryptography;Digital signatures;Explosions;Information security;Merchandise;Privacy;Routing},
+ doi={10.1109/SP.2007.15},
+ ISSN={1081-6011},
+ month={5},
+}
+
+
+
+@inproceedings{danezis2016rscoin,
+ author = {George Danezis and
+ Sarah Meiklejohn},
+ title = {Centrally Banked Cryptocurrencies},
+ booktitle = {23nd Annual Network and Distributed System Security Symposium, {NDSS}
+ 2016, San Diego, California, USA, February 21-24, 2016},
+ year = {2016},
+ publisher = {The Internet Society},
+}
+
+
+
+@Misc{fatf1997,
+ title = {FATF-IX report on money laundering typologies},
+ howpublished = {\url{http://www.fatf-gafi.org/media/fatf/documents/reports/1996\%201997\%20ENG.pdf}},
+ month = {2},
+ year = {1998},
+}
+
+@article{bellare2003one,
+ title={The One-More-RSA-Inversion Problems and the Security of Chaum's Blind Signature Scheme.},
+ author={Bellare, Mihir and Namprempre, Chanathip and Pointcheval, David and Semanko, Michael},
+ journal={Journal of Cryptology},
+ volume={16},
+ number={3},
+ year={2003},
+ publisher={Springer}
+}
+
+
+@inbook{RSA-FDH-KTIvCTI,
+ author="Bellare, Mihir and Namprempre, Chanathip and Pointcheval, David and Semanko, Michael",
+ editor="Syverson, Paul",
+ chapter="The Power of RSA Inversion Oracles and the Security of Chaum's RSA-Based Blind Signature Scheme",
+ title="Financial Cryptography: 5th International Conference",
+ year="2002",
+ publisher="Springer",
+ address="Berlin, Heidelberg",
+ pages="319--338",
+ isbn="978-3-540-46088-6",
+ doi="10.1007/3-540-46088-8_25",
+ url="https://www.di.ens.fr/~pointche/Documents/Papers/2001_fcA.pdf"
+}
+
+@misc{LightningNetwork,
+ author = {Joseph Poon and Thaddeus Dryja},
+ title = {The Bitcoin Lightning Network: Scalable Off-Chain Instant Payments},
+ month = {1},
+ year = {2016},
+ note = {\url{https://lightning.network/lightning-network-paper.pdf}},
+}
+
+
+@misc{RippleFined:FinCEN,
+ author = {Steve Hudak},
+ title = {FinCEN Fines Ripple Labs Inc. in First Civil Enforcement Action Against a Virtual Currency Exchanger},
+ month = {5},
+ day = {5},
+ year = {2015},
+ note = {\url{https://www.fincen.gov/news/news-releases/fincen-fines-ripple-labs-inc-first-civil-enforcement-action-against-virtual}},
+}
+
+@misc{RippleFined:ArsTechnica,
+ author = {Megan Geuss},
+ title = {Cryptocurrency maker Ripple Labs fined \$700K for flouting financial regs. Virtual currency Wild West is done, registration as a Money Services Business required.},
+ month = {5},
+ day = {5},
+ year = {2015},
+ note = {\url{https://arstechnica.com/tech-policy/2015/05/cryptocurrency-maker-ripple-labs-fined-700k-for-flouting-financial-regs/}},
+ url_coindesk = {http://www.coindesk.com/fincen-fines-ripple-labs-700000-bank-secrecy-act/}
+}
+
+@misc{RippleFined:CoinDesk,
+ author = {Stan Higgins},
+ title = {FinCEN Fines Ripple Labs for Bank Secrecy Act Violations},
+ month = {5},
+ day = {5},
+ year = {2015},
+ note = {\url{http://www.coindesk.com/fincen-fines-ripple-labs-700000-bank-secrecy-act/}},
+}
+
+
+@misc{rfc6818,
+ author="P. Yee",
+ title="{Updates to the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile}",
+ howpublished="RFC 6818 (Proposed Standard)",
+ series="Internet Request for Comments",
+ type="RFC",
+ number="6818",
+ pages="1--8",
+ year=2013,
+ month={1},
+ issn="2070-1721",
+ publisher="RFC Editor",
+ institution="RFC Editor",
+ organization="RFC Editor",
+ address="Fremont, CA, USA",
+ url="https://www.rfc-editor.org/rfc/rfc6818.txt",
+ key="RFC 6818",
+ abstract={This document updates RFC 5280, the ``Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile''. This document changes the set of acceptable encoding methods for the explicitText field of the user notice policy qualifier and clarifies the rules for converting internationalized domain name labels to ASCII. This document also provides some clarifications on the use of self-signed certificates, trust anchors, and some updated security considerations. [STANDARDS-TRACK]},
+ keywords="",
+ doi="10.17487/RFC6818",
+}
+
+
+
+@inproceedings{rivest2004peppercoin,
+ title={Peppercoin micropayments},
+ author={Rivest, Ronald L},
+ booktitle={Financial Cryptography},
+ pages={2--8},
+ year={2004},
+ organization={Springer}
+}
+
+
+@inproceedings{Camenisch05compacte-cash,
+ author = {Jan Camenisch and Susan Hohenberger and Anna Lysyanskaya},
+ title = {Compact e-cash},
+ booktitle = {In EUROCRYPT, volume 3494 of LNCS},
+ year = {2005},
+ pages = {302--321},
+ publisher = {Springer-Verlag},
+ url = {http://cs.brown.edu/~anna/papers/chl05-full.pdf},
+ url_citeseerx = {http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.136.4640}
+}
+
+
+
+@article{martens2015practical,
+ title={Practical Divisible E-Cash.},
+ author={M{\"a}rtens, Patrick},
+ journal={IACR Cryptology ePrint Archive},
+ volume={2015},
+ pages={318},
+ year={2015}
+}
+
+@misc{Martens2015,
+ title = {Practical Compact E-Cash with Arbitrary Wallet Size},
+ author = {Patrick M{\"a}rtens},
+ howpublished = {IACR Cryptology ePrint Archive 2015/086},
+ year = {2015},
+ note = {\url{http://eprint.iacr.org/2015/086}},
+}
+
+
+@inproceedings{bensasson2014zerocash,
+ author = {Eli Ben-Sasson and Alessandro Chiesa and Christina Garman and Matthew Green and Ian Miers and Eran Tromer and Madars Virza},
+ title = {Zerocash: Decentralized Anonymous Payments from Bitcoin},
+ booktitle = {IEEE Symposium on Security \& Privacy},
+ year = {2014},
+}
+
+
+@book{molander1998cyberpayments,
+ title={Cyberpayments and money laundering: Problems and promise},
+ author={Molander, Roger C and Mussington, David A and Mussington, David and Wilson, Peter A},
+ volume={965},
+ year={1998},
+ publisher={Rand Corporation}
+}
+
+
+@InProceedings{sander1999escrow,
+ author = {Tomas Sander and Amnon Ta-Shma},
+ title = {On Anonymous Electronic Cash and Crime},
+ booktitle = {ISW'99},
+ year = {1999},
+ series = {LNCS 1729},
+ pages = {202--206},
+}
+
+@inproceedings{stadler1995fair,
+ title={Fair blind signatures},
+ author={Stadler, Markus and Piveteau, Jean-Marc and Camenisch, Jan},
+ booktitle={International Conference on the Theory and Applications of Cryptographic Techniques},
+ pages={209--219},
+ year={1995},
+ organization={Springer}
+}
+
+@Article{solms1992perfect,
+ author = {Sebastiaan H. von Solms and David Naccache},
+ title = {On blind signatures and perfect crimes},
+ journal = {Computers \& Security},
+ year = {1992},
+ volume = {11},
+ number = {6},
+ pages = {581--583},
+}
+
+
+@Misc{guardian2015cap,
+ author = {Rupert Jones},
+ title = {Cap on card fees could lead to lower prices for consumers},
+ howpublished = {\url{http://www.theguardian.com/money/2015/jul/27/cap-on-card-fees-retailers}},
+ month = {7},
+ year = {2015},
+}
+
+
+@Misc{crinkey2011rundle,
+ author = {Guy Rundle},
+ title = {The humble credit card is now a political tool},
+ howpublished = {\url{http://www.crikey.com.au/2011/10/25/rundle-humble-credit-card-now-a-political-tool-just-ask-wikileaks/}},
+ month = {10},
+ year = {2011},
+}
+
+
+@unpublished{cryptonote,
+ author = {van Saberhagen, Nicolas},
+ month = {10},
+ posted-at = {2016-09-18 11:44:05},
+ priority = {2},
+ title = {{CryptoNote v 2.0}},
+ url = {https://cryptonote.org/whitepaper.pdf},
+ year = {2013}
+}
+
+
+@inproceedings{rupp2013p4r,
+ title={P4R: Privacy-preserving pre-payments with refunds for transportation systems},
+ author={Rupp, Andy and Hinterw{\"a}lder, Gesine and Baldimtsi, Foteini and Paar, Christof},
+ booktitle={International Conference on Financial Cryptography and Data Security},
+ pages={205--212},
+ year={2013},
+ organization={Springer}
+}
+
+
+@inproceedings{dingledine2004tor,
+ title = {Tor: The Second-Generation Onion Router},
+ author = {Roger Dingledine and Nick Mathewson and Paul Syverson},
+ booktitle = {Proceedings of the 13th USENIX Security Symposium},
+ year = {2004},
+ month = {8},
+ www_important = {1},
+ www_tags = {selected},
+ www_html_url = {https://www.torproject.org/svn/trunk/doc/design-paper/tor-design.html},
+ www_pdf_url = {https://www.torproject.org/svn/trunk/doc/design-paper/tor-design.pdf},
+ www_section = {Anonymous communication},
+}
+
+
+@Misc{greece2015cash,
+ author = {Reuters},
+ title = {Greek council recommends 60 euro limit on ATM withdrawals from Tuesday},
+ howpublished = {\url{http://www.reuters.com/article/2015/06/28/eurozone-greece-limits-idUSA8N0Z302P20150628}},
+ month = {6},
+ year = {2015},
+}
+
+@Misc{france2015cash,
+ author = {Heinz-Peter Bader},
+ title = {France steps up monitoring of cash payments to fight low-cost terrorism},
+ howpublished = {\url{http://www.reuters.com/article/2015/03/18/us-france-security-financing-idUSKBN0ME14720150318}},
+ month = {3},
+ year = {2015},
+}
+
+
+@article{dent2008extensions,
+ title={Extensions to Chaum's Blind Signature Scheme and OpenCoin Requirements},
+ author={Dent, AW and Paterson, KG and Wild, PR},
+ year={2008}
+}
+
+@article{dent2008preliminary,
+ title={Preliminary Report on Chaum's Online E-Cash Architecture},
+ author={Dent, AW and Paterson, KG and Wild, PR},
+ journal={Royal Holloway, University of London},
+ year={2008}
+}
+
+
+@Misc{ibi2014,
+ author = {{ibi research}},
+ title = {Digitalisierung der Gesellschaft 2014 --- Aktuelle Einsch\"atzungen und Trends},
+ howpublished = {\url{http://www.ecommerce-leitfaden.de/digitalisierung-der-gesellschaft-2014.html}},
+ year = {2014},
+}
+
+@inproceedings{fujisaki-okamoto,
+ title={Secure integration of asymmetric and symmetric encryption schemes},
+ author={Fujisaki, Eiichiro and Okamoto, Tatsuaki},
+ booktitle={Annual International Cryptology Conference},
+ pages={537--554},
+ year={1999},
+ organization={Springer}
+}
+
+@article{bernstein2012high,
+ title={High-speed high-security signatures},
+ author={Bernstein, Daniel J and Duif, Niels and Lange, Tanja and Schwabe, Peter and Yang, Bo-Yin},
+ journal={Journal of Cryptographic Engineering},
+ volume={2},
+ number={2},
+ pages={77--89},
+ year={2012},
+ publisher={Springer}
+}
+
+
+@inproceedings{bernstein2006curve25519,
+ title={Curve25519: new Diffie-Hellman speed records},
+ author={Bernstein, Daniel J},
+ booktitle={International Workshop on Public Key Cryptography},
+ pages={207--228},
+ year={2006},
+ organization={Springer}
+}
+
+@techreport{pagnia1999impossibility,
+ title={On the impossibility of fair exchange without a trusted third party},
+ author={Pagnia, Henning and G{\"a}rtner, Felix C},
+ year={1999},
+ institution={Technical Report TUD-BS-1999-02, Darmstadt University of Technology, Department of Computer Science, Darmstadt, Germany}
+}
+
+@book{katz1996handbook,
+ title={Handbook of applied cryptography},
+ author={Katz, Jonathan and Menezes, Alfred J and Van Oorschot, Paul C and Vanstone, Scott A},
+ year={1996},
+ publisher={CRC press}
+}
+
+
+% ===== PROVABLE SECURITY =====
+
+% see also https://www.baigneres.net/downloads/2007_provable_security.pdf
+
+@article{koblitz2007another,
+ title={Another look at" provable security"},
+ author={Koblitz, Neal and Menezes, Alfred J},
+ journal={Journal of Cryptology},
+ volume={20},
+ number={1},
+ pages={3--37},
+ year={2007},
+ publisher={Springer}
+}
+
+@incollection{pointcheval2005provable,
+ title={Provable security for public key schemes},
+ author={Pointcheval, David},
+ booktitle={Contemporary cryptology},
+ pages={133--190},
+ year={2005},
+ publisher={Springer}
+}
+
+@article{shoup2004sequences,
+ title={Sequences of games: a tool for taming complexity in security proofs.},
+ author={Shoup, Victor},
+ journal={IACR Cryptology ePrint Archive},
+ volume={2004},
+ pages={332},
+ year={2004}
+}
+
+
+@inproceedings{coron2000exact,
+ title={On the exact security of full domain hash},
+ author={Coron, Jean-S{\'e}bastien},
+ booktitle={Annual International Cryptology Conference},
+ pages={229--235},
+ year={2000},
+ organization={Springer}
+}
+
+@inproceedings{damgaard2007proof,
+ title={A “proof-reading†of some issues in cryptography},
+ author={Damg{\aa}rd, Ivan},
+ booktitle={International Colloquium on Automata, Languages, and Programming},
+ pages={2--11},
+ year={2007},
+ organization={Springer}
+}
+
+@article{koblitz2010brave,
+ title={The brave new world of bodacious assumptions in cryptography},
+ author={Koblitz, Neal and Menezes, Alfred},
+ journal={Notices of the American Mathematical Society},
+ volume={57},
+ number={3},
+ pages={357--365},
+ year={2010}
+}
+
+@inproceedings{bellare1993random,
+ title={Random oracles are practical: A paradigm for designing efficient protocols},
+ author={Bellare, Mihir and Rogaway, Phillip},
+ booktitle={Proceedings of the 1st ACM conference on Computer and communications security},
+ pages={62--73},
+ year={1993},
+ organization={ACM}
+}
+
+@article{koblitz2015random,
+ title={The random oracle model: a twenty-year retrospective},
+ author={Koblitz, Neal and Menezes, Alfred J},
+ journal={Designs, Codes and Cryptography},
+ volume={77},
+ number={2-3},
+ pages={587--610},
+ year={2015},
+ publisher={Springer}
+}
+
+@article{canetti2004random,
+ title={The random oracle methodology, revisited},
+ author={Canetti, Ran and Goldreich, Oded and Halevi, Shai},
+ journal={Journal of the ACM (JACM)},
+ volume={51},
+ number={4},
+ pages={557--594},
+ year={2004},
+ publisher={ACM}
+}
+
+@inproceedings{dreier2015formal,
+ title={Formal analysis of e-cash protocols},
+ author={Dreier, Jannik and Kassem, Ali and Lafourcade, Pascal},
+ booktitle={e-Business and Telecommunications (ICETE), 2015 12th International Joint Conference on},
+ volume={4},
+ pages={65--75},
+ year={2015},
+ organization={IEEE}
+}
+
+@inproceedings{brickell1995trustee,
+ title={Trustee-based Tracing Extensions to Anonymous Cash and the Making of Anonymous Change.},
+ author={Brickell, Ernest F and Gemmell, Peter and Kravitz, David W},
+ booktitle={SODA},
+ volume={95},
+ pages={457--466},
+ year={1995}
+}
+
+
+
+% ===== CRYPTO BASICS =====
+
+@inproceedings{boneh1998decision,
+ title={The decision diffie-hellman problem},
+ author={Boneh, Dan},
+ booktitle={International Algorithmic Number Theory Symposium},
+ pages={48--63},
+ year={1998},
+ organization={Springer}
+}
+
+
+@article{goldwasser1988digital,
+ title={A digital signature scheme secure against adaptive chosen-message attacks},
+ author={Goldwasser, Shafi and Micali, Silvio and Rivest, Ronald L},
+ journal={SIAM Journal on Computing},
+ volume={17},
+ number={2},
+ pages={281--308},
+ year={1988},
+ publisher={SIAM}
+}
+
+
+@inproceedings{bellare1998relations,
+ title={Relations among notions of security for public-key encryption schemes},
+ author={Bellare, Mihir and Desai, Anand and Pointcheval, David and Rogaway, Phillip},
+ booktitle={Annual International Cryptology Conference},
+ pages={26--45},
+ year={1998},
+ organization={Springer}
+}
+
+
+@inproceedings{blanchet2006automated,
+ title={Automated security proofs with sequences of games},
+ author={Blanchet, Bruno and Pointcheval, David},
+ booktitle={Annual International Cryptology Conference},
+ pages={537--554},
+ year={2006},
+ organization={Springer}
+}
+
+
+@inproceedings{bellare2006code,
+ title={Code-based game-playing proofs and the security of triple encryption},
+ author={Bellare, Mihir and Rogaway, Phillip},
+ booktitle={Advances in Cryptology--EUROCRYPT},
+ volume={4004},
+ pages={10},
+ year={2006}
+}
+
+@inproceedings{fischlin2009security,
+ title={Security of blind signatures under aborts},
+ author={Fischlin, Marc and Schr{\"o}der, Dominique},
+ booktitle={International Workshop on Public Key Cryptography},
+ pages={297--316},
+ year={2009},
+ organization={Springer}
+}
+
+
+@incollection{lindell2017simulate,
+ title={How to simulate it--a tutorial on the simulation proof technique},
+ author={Lindell, Yehuda},
+ booktitle={Tutorials on the Foundations of Cryptography},
+ pages={277--346},
+ year={2017},
+ publisher={Springer}
+}
+
+@book{guo2018introduction,
+ title={Introduction to Security Reduction},
+ author={Guo, Fuchun and Susilo, Willy and Mu, Yi},
+ year={2018},
+ publisher={Springer}
+}
+
+@book{stallman2002essays,
+ title={Free software, free society: Selected essays of Richard M. Stallman},
+ author={Stallman, Richard},
+ year={2002},
+ publisher={Lulu.com}
+}
+
+
+@misc{adyen2016global,
+ title={The Global E-Commerce Payments Guide},
+ author={{Adyen}},
+ year={2016}
+}
+
+@article{paypers2016ecommerce,
+ title={Ecommerce Payment Methods Report 2016},
+ author={Lupu, Sebastian and Mual, Melisande and van Stiphout, Mees},
+ year={2016}
+}
+
+
+@inproceedings{beikverdi2015trend,
+ title={Trend of centralization in Bitcoin's distributed network},
+ author={Beikverdi, Alireza and Song, JooSeok},
+ booktitle={Software Engineering, Artificial Intelligence, Networking and Parallel/Distributed Computing (SNPD), 2015 16th IEEE/ACIS International Conference on},
+ pages={1--6},
+ year={2015},
+ organization={IEEE}
+}
+
+@article{bohme2015bitcoin,
+ title={Bitcoin: Economics, technology, and governance},
+ author={B{\"o}hme, Rainer and Christin, Nicolas and Edelman, Benjamin and Moore, Tyler},
+ journal={Journal of Economic Perspectives},
+ volume={29},
+ number={2},
+ pages={213--38},
+ year={2015}
+}
+
+
+@article{provos2007ghost,
+ title={The Ghost in the Browser: Analysis of Web-based Malware.},
+ author={Provos, Niels and McNamee, Dean and Mavrommatis, Panayiotis and Wang, Ke and Modadugu, Nagendra and others},
+ journal={HotBots},
+ volume={7},
+ pages={4--4},
+ year={2007}
+}
+
+@misc{riksbank2017riksbank,
+ title={The Riksbank’s e-krona project},
+ author={Riksbank, Sveriges},
+ year={2017},
+ publisher={Report}
+}
+
+@inproceedings{fuchsbauer2009transferable,
+title={Transferable constant-size fair e-cash},
+author={Fuchsbauer, Georg and Pointcheval, David and Vergnaud, Damien},
+booktitle={International Conference on Cryptology and Network Security},
+pages={226--247},
+year={2009},
+organization={Springer}
+}
+
+@inproceedings{au2011electronic,
+ title={Electronic cash with anonymous user suspension},
+ author={Au, Man Ho and Susilo, Willy and Mu, Yi},
+ booktitle={Australasian Conference on Information Security and Privacy},
+ pages={172--188},
+ year={2011},
+ organization={Springer}
+}
+
+@article{schroder2017security,
+ title={Security of blind signatures revisited},
+ author={Schr{\"o}der, Dominique and Unruh, Dominique},
+ journal={Journal of Cryptology},
+ volume={30},
+ number={2},
+ pages={470--494},
+ year={2017},
+ publisher={Springer}
+}
+
+@inproceedings{camenisch2004signature,
+ title={Signature schemes and anonymous credentials from bilinear maps},
+ author={Camenisch, Jan and Lysyanskaya, Anna},
+ booktitle={Annual International Cryptology Conference},
+ pages={56--72},
+ year={2004},
+ organization={Springer}
+}
+
+@article{paquin2011u,
+ title={U-prove cryptographic specification v1. 1},
+ author={Paquin, Christian and Zaverucha, Greg},
+ journal={Technical Report, Microsoft Corporation},
+ year={2011}
+}
+
+@misc{next1999digicash,
+ publisher={NEXT Magazine},
+ year={1999},
+ title={How DigiCash Blew Everything},
+ author={{Anonymous}}
+}
+
+@inproceedings{canard2007divisible,
+ title={Divisible e-cash systems can be truly anonymous},
+ author={Canard, S{\'e}bastien and Gouget, Aline},
+ booktitle={Annual International Conference on the Theory and Applications of Cryptographic Techniques},
+ pages={482--497},
+ year={2007},
+ organization={Springer}
+}
+
+@inproceedings{canard2006handy,
+ title={A handy multi-coupon system},
+ author={Canard, S{\'e}bastien and Gouget, Aline and Hufschmitt, Emeline},
+ booktitle={International Conference on Applied Cryptography and Network Security},
+ pages={66--81},
+ year={2006},
+ organization={Springer}
+}
+
+
+@Article{batten2018offline,
+ author="Batten, Lynn and Yi, Xun",
+ title="Off-line digital cash schemes providing untraceability, anonymity and change",
+ journal="Electronic Commerce Research",
+ year={2018},
+ month={1},
+ day={27},
+ issn="1572-9362",
+ doi="10.1007/s10660-018-9289-8",
+ url="https://doi.org/10.1007/s10660-018-9289-8"
+}
+
+@inproceedings{chaum1992wallet,
+ title={Wallet databases with observers},
+ author={Chaum, David and Pedersen, Torben Pryds},
+ booktitle={Annual International Cryptology Conference},
+ pages={89--105},
+ year={1992},
+ organization={Springer}
+}
+
+@inproceedings{davida1997anonymity,
+ title={Anonymity control in e-cash systems},
+ author={Davida, George and Frankel, Yair and Tsiounis, Yiannis and Yung, Moti},
+ booktitle={International Conference on Financial Cryptography},
+ pages={1--16},
+ year={1997},
+ organization={Springer}
+}
+
+
+@inproceedings{chaum1989efficient,
+ title={Efficient offline electronic checks},
+ author={Chaum, David and den Boer, Bert and van Heyst, Eug{\`e}ne and Mj{\o}lsnes, Stig and Steenbeek, Adri},
+ booktitle={Workshop on the theory and application of of cryptographic techniques},
+ pages={294--301},
+ year={1989},
+ organization={Springer}
+}
+
+@article{pointcheval2000security,
+ title={Security arguments for digital signatures and blind signatures},
+ author={Pointcheval, David and Stern, Jacques},
+ journal={Journal of cryptology},
+ volume={13},
+ number={3},
+ pages={361--396},
+ year={2000},
+ publisher={Springer}
+}
+
+@inproceedings{damgaard1988payment,
+ title={Payment systems and credential mechanisms with provable security against abuse by individuals},
+ author={Damg{\aa}rd, Ivan Bjerre},
+ booktitle={Conference on the Theory and Application of Cryptography},
+ pages={328--335},
+ year={1988},
+ organization={Springer}
+}
+
+@inproceedings{haber1990time,
+ title={How to time-stamp a digital document},
+ author={Haber, Stuart and Stornetta, W Scott},
+ booktitle={Conference on the Theory and Application of Cryptography},
+ pages={437--455},
+ year={1990},
+ organization={Springer}
+}
+
+
+@article{wust2017you,
+ title={Do you need a Blockchain?},
+ author={W{\"u}st, Karl and Gervais, Arthur},
+ journal={IACR Cryptology ePrint Archive},
+ volume={2017},
+ pages={375},
+ year={2017}
+}
+
+@inproceedings{pedersen1996electronic,
+ title={Electronic payments of small amounts},
+ author={Pedersen, Torben P},
+ booktitle={International Workshop on Security Protocols},
+ pages={59--68},
+ year={1996},
+ organization={Springer}
+}
+
+@article{poon2016bitcoin,
+ title={The bitcoin lightning network: Scalable off-chain instant payments},
+ author={Poon, Joseph and Dryja, Thaddeus},
+ journal={draft version 0.5},
+ pages={14},
+ year={2016}
+}
+
+@misc{poon2017plasma,
+ title={Plasma: Scalable autonomous smart contracts},
+ author={Poon, Joseph and Buterin, Vitalik},
+ howpublished={White paper},
+ year={2017}
+}
+
+@article{eyal2018majority,
+ title={Majority is not enough: Bitcoin mining is vulnerable},
+ author={Eyal, Ittay and Sirer, Emin G{\"u}n},
+ journal={Communications of the ACM},
+ volume={61},
+ number={7},
+ pages={95--102},
+ year={2018},
+ publisher={ACM}
+}
+
+@inproceedings{vukolic2015quest,
+ title={The quest for scalable blockchain fabric: Proof-of-work vs. BFT replication},
+ author={Vukoli{\'c}, Marko},
+ booktitle={International Workshop on Open Problems in Network Security},
+ pages={112--125},
+ year={2015},
+ organization={Springer}
+}
+
+@inproceedings{eyal2016bitcoin,
+ title={Bitcoin-NG: A Scalable Blockchain Protocol.},
+ author={Eyal, Ittay and Gencer, Adem Efe and Sirer, Emin G{\"u}n and Van Renesse, Robbert},
+ booktitle={NSDI},
+ pages={45--59},
+ year={2016}
+}
+
+
+@inproceedings{bentov2016cryptocurrencies,
+ title={Cryptocurrencies without proof of work},
+ author={Bentov, Iddo and Gabizon, Ariel and Mizrahi, Alex},
+ booktitle={International Conference on Financial Cryptography and Data Security},
+ pages={142--157},
+ year={2016},
+ organization={Springer}
+}
+
+@inproceedings{gilad2017algorand,
+ title={Algorand: Scaling byzantine agreements for cryptocurrencies},
+ author={Gilad, Yossi and Hemo, Rotem and Micali, Silvio and Vlachos, Georgios and Zeldovich, Nickolai},
+ booktitle={Proceedings of the 26th Symposium on Operating Systems Principles},
+ pages={51--68},
+ year={2017},
+ organization={ACM}
+}
+
+@misc{kwon2014tendermint,
+ title={Tendermint: Consensus without mining},
+ author={Kwon, Jae},
+ note={Draft v. 0.6, fall},
+ year={2014}
+}
+
+
+@misc{rocket2018snowflake,
+ title={Snowflake to Avalanche: A Novel Metastable Consensus Protocol Family for Cryptocurrencies},
+ author={{Team Rocket}},
+ howpublished={IPFS},
+ year={2018}
+}
+
+@inproceedings{androulaki2018hyperledger,
+ title={Hyperledger fabric: a distributed operating system for permissioned blockchains},
+ author={Androulaki, Elli and Barger, Artem and Bortnikov, Vita and Cachin, Christian and Christidis, Konstantinos and De Caro, Angelo and Enyeart, David and Ferris, Christopher and Laventman, Gennady and Manevich, Yacov and others},
+ booktitle={Proceedings of the Thirteenth EuroSys Conference},
+ pages={30},
+ year={2018},
+ organization={ACM}
+}
+
+@article{wood2014ethereum,
+ title={Ethereum: A secure decentralised generalised transaction ledger},
+ author={Wood, Gavin},
+ journal={Ethereum project yellow paper},
+ volume={151},
+ pages={1--32},
+ year={2014}
+}
+
+@article{reijers2016governance,
+ title={Governance in blockchain technologies \& social contract theories},
+ author={Reijers, Wessel and O'Brolch{\'a}in, Fiachra and Haynes, Paul},
+ journal={Ledger},
+ volume={1},
+ pages={134--151},
+ year={2016}
+}
+
+@article{levy2017book,
+ title={Book-smart, not street-smart: blockchain-based smart contracts and the social workings of law},
+ author={Levy, Karen EC},
+ journal={Engaging Science, Technology, and Society},
+ volume={3},
+ pages={1--15},
+ year={2017}
+}
+
+@incollection{reid2013analysis,
+ title={An analysis of anonymity in the bitcoin system},
+ author={Reid, Fergal and Harrigan, Martin},
+ booktitle={Security and privacy in social networks},
+ pages={197--223},
+ year={2013},
+ publisher={Springer}
+}
+
+@inproceedings{bonneau2014mixcoin,
+ title={Mixcoin: Anonymity for Bitcoin with accountable mixes},
+ author={Bonneau, Joseph and Narayanan, Arvind and Miller, Andrew and Clark, Jeremy and Kroll, Joshua A and Felten, Edward W},
+ booktitle={International Conference on Financial Cryptography and Data Security},
+ pages={486--504},
+ year={2014},
+ organization={Springer}
+}
+
+@inproceedings{heilman2017tumblebit,
+ title={TumbleBit: An untrusted Bitcoin-compatible anonymous payment hub},
+ author={Heilman, Ethan and Alshenibr, Leen and Baldimtsi, Foteini and Scafuro, Alessandra and Goldberg, Sharon},
+ booktitle={Network and Distributed System Security Symposium},
+ year={2017}
+}
+
+@inproceedings{sun2017ringct,
+ title={RingCT 2.0: a compact accumulator-based (linkable ring signature) protocol for blockchain cryptocurrency monero},
+ author={Sun, Shi-Feng and Au, Man Ho and Liu, Joseph K and Yuen, Tsz Hon},
+ booktitle={European Symposium on Research in Computer Security},
+ pages={456--474},
+ year={2017},
+ organization={Springer}
+}
+
+@inproceedings{wahby2018doubly,
+ title={Doubly-efficient zkSNARKs without trusted setup},
+ author={Wahby, Riad S and Tzialla, Ioanna and Shelat, Abhi and Thaler, Justin and Walfish, Michael},
+ booktitle={2018 IEEE Symposium on Security and Privacy (SP)},
+ pages={926--943},
+ year={2018},
+ organization={IEEE}
+}
+
+@article{ben2018scalable,
+ title={Scalable, transparent, and post-quantum secure computational integrity},
+ author={Ben-Sasson, Eli and Bentov, Iddo and Horesh, Yinon and Riabzev, Michael},
+ journal={Cryptol. ePrint Arch., Tech. Rep},
+ volume={46},
+ pages={2018},
+ year={2018}
+}
+
+@inproceedings{garman2016accountable,
+ title={Accountable privacy for decentralized anonymous payments},
+ author={Garman, Christina and Green, Matthew and Miers, Ian},
+ booktitle={International Conference on Financial Cryptography and Data Security},
+ pages={81--98},
+ year={2016},
+ organization={Springer}
+}
+
+@online{crockford_base32,
+author = {Crockford, Douglas},
+title = {Base32 Encoding},
+url = {https://www.crockford.com/wrmg/base32.html}
+}
+
+@misc{rfc4634,
+ series = {Request for Comments},
+ number = 4634,
+ howpublished = {RFC 4634},
+ publisher = {RFC Editor},
+ doi = {10.17487/RFC4634},
+ url = {https://rfc-editor.org/rfc/rfc4634.txt},
+ author = {Tony Hansen and Donald E. Eastlake 3rd},
+ title = {{US Secure Hash Algorithms (SHA and HMAC-SHA)}},
+ pagetotal = 108,
+ year = 2006,
+ month = aug,
+}
+
+@misc{rfc5869,
+ series = {Request for Comments},
+ number = 5869,
+ howpublished = {RFC 5869},
+ publisher = {RFC Editor},
+ doi = {10.17487/RFC5869},
+ url = {https://rfc-editor.org/rfc/rfc5869.txt},
+ author = {Dr. Hugo Krawczyk and Pasi Eronen},
+ title = {{HMAC-based Extract-and-Expand Key Derivation Function (HKDF)}},
+ pagetotal = 14,
+ year = 2010,
+ month = may,
+}
+
+
+@inproceedings{boldyreva2003threshold,
+ title={Threshold signatures, multisignatures and blind signatures based on the gap-Diffie-Hellman-group signature scheme},
+ author={Boldyreva, Alexandra},
+ booktitle={International Workshop on Public Key Cryptography},
+ pages={31--46},
+ year={2003},
+ organization={Springer}
+}
+
+
+@article{zhang2018new,
+ title={A New Post-Quantum Blind Signature From Lattice Assumptions},
+ author={Zhang, Pingyuan and Jiang, Han and Zheng, Zhihua and Hu, Peichu and Xu, Qiuliang},
+ journal={IEEE Access},
+ volume={6},
+ pages={27251--27258},
+ year={2018},
+ publisher={IEEE}
+}
+
+
+@article{brandt2006obtain,
+ title={How to obtain full privacy in auctions},
+ author={Brandt, Felix},
+ journal={International Journal of Information Security},
+ volume={5},
+ number={4},
+ pages={201--216},
+ year={2006},
+ publisher={Springer}
+}
+
+@inproceedings{blanchet2007cryptoverif,
+ title={CryptoVerif: Computationally sound mechanized prover for cryptographic protocols},
+ author={Blanchet, Bruno},
+ booktitle={Dagstuhl seminar “Formal Protocol Verification Applied},
+ volume={117},
+ year={2007}
+}
+
+@article{tolia2006quantifying,
+ title={Quantifying interactive user experience on thin clients},
+ author={Tolia, Niraj and Andersen, David G and Satyanarayanan, Mahadev},
+ journal={Computer},
+ number={3},
+ pages={46--52},
+ year={2006},
+ publisher={IEEE}
+}
+
+@inproceedings{abe2000provably,
+ title={Provably secure partially blind signatures},
+ author={Abe, Masayuki and Okamoto, Tatsuaki},
+ booktitle={Annual International Cryptology Conference},
+ pages={271--286},
+ year={2000},
+ organization={Springer}
+}
+
+@inproceedings{bellare1996exact,
+ title={The exact security of digital signatures-How to sign with RSA and Rabin},
+ author={Bellare, Mihir and Rogaway, Phillip},
+ booktitle={International Conference on the Theory and Applications of Cryptographic Techniques},
+ pages={399--416},
+ year={1996},
+ organization={Springer}
+}
+
+@book{fielding2000architectural,
+ title={Architectural styles and the design of network-based software architectures},
+ author={Fielding, Roy T and Taylor, Richard N},
+ volume={7},
+ year={2000},
+ publisher={University of California, Irvine Doctoral dissertation}
+}
+
+@article{rfc8259,
+ author = {Tim Bray},
+ title = {The JavaScript Object Notation {(JSON)} Data Interchange Format},
+ journal = {{RFC}},
+ volume = {8259},
+ pages = {1--16},
+ year = {2017},
+ url = {https://doi.org/10.17487/RFC8259},
+ doi = {10.17487/RFC8259},
+}
+
+@misc{rfc7049,
+ series = {Request for Comments},
+ number = 7049,
+ howpublished = {RFC 7049},
+ publisher = {RFC Editor},
+ doi = {10.17487/RFC7049},
+ url = {https://rfc-editor.org/rfc/rfc7049.txt},
+ author = {Carsten Bormann and Paul E. Hoffman},
+ title = {{Concise Binary Object Representation (CBOR)}},
+ pagetotal = 54,
+ year = 2013,
+ month = {10},
+ abstract = {The Concise Binary Object Representation (CBOR) is a data format whose design goals include the possibility of extremely small code size, fairly small message size, and extensibility without the need for version negotiation. These design goals make it different from earlier binary serializations such as ASN.1 and MessagePack.},
+}
+
+@misc{rfc5246,
+ series = {Request for Comments},
+ number = 5246,
+ howpublished = {RFC 5246},
+ publisher = {RFC Editor},
+ doi = {10.17487/RFC5246},
+ url = {https://rfc-editor.org/rfc/rfc5246.txt},
+ author = {Eric Rescorla and Tim Dierks},
+ title = {{The Transport Layer Security (TLS) Protocol Version 1.2}},
+ pagetotal = 104,
+ year = 2008,
+ month = {8},
+ abstract = {This document specifies Version 1.2 of the Transport Layer Security (TLS) protocol. The TLS protocol provides communications security over the Internet. The protocol allows client/server applications to communicate in a way that is designed to prevent eavesdropping, tampering, or message forgery. {[}STANDARDS-TRACK{]}},
+}
+
+@misc{rfc6454,
+ series = {Request for Comments},
+ number = 6454,
+ howpublished = {RFC 6454},
+ publisher = {RFC Editor},
+ doi = {10.17487/RFC6454},
+ url = {https://rfc-editor.org/rfc/rfc6454.txt},
+ author = {Adam Barth},
+ title = {{The Web Origin Concept}},
+ pagetotal = 20,
+ year = 2011,
+ month = {12},
+ abstract = {This document defines the concept of an "origin", which is often used as the scope of authority or privilege by user agents. Typically, user agents isolate content retrieved from different origins to prevent malicious web site operators from interfering with the operation of benign web sites. In addition to outlining the principles that underlie the concept of origin, this document details how to determine the origin of a URI and how to serialize an origin into a string. It also defines an HTTP header field, named "Origin", that indicates which origins are associated with an HTTP request. {[}STANDARDS-TRACK{]}},
+}
+
+
+@misc{rfc6838,
+ series = {Request for Comments},
+ number = 6838,
+ howpublished = {RFC 6838},
+ publisher = {RFC Editor},
+ doi = {10.17487/RFC6838},
+ url = {https://rfc-editor.org/rfc/rfc6838.txt},
+ author = {Ned Freed and Dr. John C. Klensin and Tony Hansen},
+ title = {{Media Type Specifications and Registration Procedures}},
+ pagetotal = 32,
+ year = 2013,
+ month = {1},
+ abstract = {This document defines procedures for the specification and registration of media types for use in HTTP, MIME, and other Internet protocols. This memo documents an Internet Best Current Practice.},
+}
+
+@misc{rfc7413,
+ series = {Request for Comments},
+ number = 7413,
+ howpublished = {RFC 7413},
+ publisher = {RFC Editor},
+ doi = {10.17487/RFC7413},
+ url = {https://rfc-editor.org/rfc/rfc7413.txt},
+ author = {Yuchung Cheng and Jerry Chu and Sivasankar Radhakrishnan and Arvind Jain},
+ title = {{TCP Fast Open}},
+ pagetotal = 26,
+ year = 2014,
+ month = {12},
+ abstract = {This document describes an experimental TCP mechanism called TCP Fast Open (TFO). TFO allows data to be carried in the SYN and SYN-ACK packets and consumed by the receiving end during the initial connection handshake, and saves up to one full round-trip time (RTT) compared to the standard TCP, which requires a three-way handshake (3WHS) to complete before data can be exchanged. However, TFO deviates from the standard TCP semantics, since the data in the SYN could be replayed to an application in some rare circumstances. Applications should not use TFO unless they can tolerate this issue, as detailed in the Applicability section.},
+}
+
+
+@inproceedings{goldberg2007improving,
+ title={Improving the robustness of private information retrieval},
+ author={Goldberg, Ian},
+ booktitle={Security and Privacy, 2007. SP'07. IEEE Symposium on},
+ pages={131--148},
+ year={2007},
+ organization={IEEE}
+}
+
+@article{persily2017election,
+ title={The 2016 US Election: Can democracy survive the internet?},
+ author={Persily, Nathaniel},
+ journal={Journal of democracy},
+ volume={28},
+ number={2},
+ pages={63--76},
+ year={2017},
+ publisher={Johns Hopkins University Press}
+}
+
+@article{richet2016extortion,
+ title={Extortion on the internet: the rise of crypto-ransomware},
+ author={Richet, Jean-Loup},
+ journal={Harvard},
+ year={2016}
+}
+
+@article{jawaheri2018small,
+ title={When A Small Leak Sinks A Great Ship: Deanonymizing Tor Hidden Service Users Through Bitcoin Transactions Analysis},
+ author={Jawaheri, Husam Al and Sabah, Mashael Al and Boshmaf, Yazan and Erbad, Aimen},
+ journal={arXiv preprint arXiv:1801.07501},
+ year={2018}
+}
+
+@inproceedings{meiklejohn2013fistful,
+ title={A fistful of bitcoins: characterizing payments among men with no names},
+ author={Meiklejohn, Sarah and Pomarole, Marjori and Jordan, Grant and Levchenko, Kirill and McCoy, Damon and Voelker, Geoffrey M and Savage, Stefan},
+ booktitle={Proceedings of the 2013 conference on Internet measurement conference},
+ pages={127--140},
+ year={2013},
+ organization={ACM}
+}
+
+
+@article{luu2016challenge,
+ title={The challenge of Bitcoin pseudo-anonymity to computer forensics},
+ author={Luu, Jason and Imwinkelried, Edward J},
+ journal={Criminal Law Bulletin},
+ volume={52},
+ number={1},
+ year={2016}
+}
+
+@article{shrier2016blockchain,
+ title={Blockchain \& infrastructure (identity, data security)},
+ author={Shrier, David and Wu, Weige and Pentland, Alex},
+ journal={Massachusetts Institute of Technology-Connection Science},
+ volume={1},
+ number={3},
+ year={2016}
+}
+
+@article{hsueh1997fault,
+ title={Fault injection techniques and tools},
+ author={Hsueh, Mei-Chen and Tsai, Timothy K and Iyer, Ravishankar K},
+ journal={Computer},
+ volume={30},
+ number={4},
+ pages={75--82},
+ year={1997},
+ publisher={IEEE}
+}
+
+@incollection{lomne2011side,
+ title={Side channel attacks},
+ author={Lomne, Victor and Dehaboui, A and Maurine, Philippe and Torres, L and Robert, M},
+ booktitle={Security trends for FPGAS},
+ pages={47--72},
+ year={2011},
+ publisher={Springer}
+}
+
+
+@misc{force2015money,
+ title={Money laundering through the physical transportation of cash},
+ author={Force, Financial Action Task and East, Middle and Force, North Africa Financial Action Task},
+ year={2015},
+ publisher={October}
+}
+
+@article{hammer2018billion,
+ title={The Billion-Dollar Bank Job},
+ author={Hammer, Joshua},
+ year={2018},
+ journal={The New York Times Magazine}
+}
+
+
+@book {mankiw2010macroeconomics,
+ title = {Macroeconomics, 7th Edition},
+ year = {2010},
+ publisher = {Worth Publishers},
+ organization = {Worth Publishers},
+ author = {N.G. Mankiw}
+}
+
+@article{dold2017byzantine,
+ author="Dold, Florian and Grothoff, Christian",
+ title="Byzantine set-union consensus using efficient set reconciliation",
+ journal="EURASIP Journal on Information Security",
+ year="2017",
+ month={7},
+ day="27",
+ volume="2017",
+ number="1",
+ pages="14",
+ issn="2510-523X",
+ doi="10.1186/s13635-017-0066-3",
+ url="https://doi.org/10.1186/s13635-017-0066-3"
+}
+
+@article{zandi2013impact,
+ title={The impact of electronic payments on economic growth},
+ author={Zandi, Mark and Singh, Virendra and Irving, Justin},
+ journal={Moody’s Analytics: Economic and Consumer Credit Analytics},
+ volume={217},
+ year={2013}
+}
+
+@article{dalebrant2016monetary,
+ title={The Monetary Policy Effects of Sweden’s Transition Towards a Cashless Society: An Econometric Analysis},
+ author={Dalebrant, Ther{\'e}se},
+ year={2016}
+}
+
+@article{singh2017does,
+ title={Does easy availability of cash affect corruption? Evidence from a panel of countries},
+ author={Singh, Sunny Kumar and Bhattacharya, Kaushik},
+ journal={Economic Systems},
+ volume={41},
+ number={2},
+ pages={236--247},
+ year={2017},
+ publisher={Elsevier}
+}
+
+@book{voigt2017eu,
+ title={The EU General Data Protection Regulation (GDPR)},
+ author={Voigt, Paul and Von dem Bussche, Axel},
+ volume={18},
+ year={2017},
+ publisher={Springer}
+}
+
+@inproceedings{garera2007framework,
+ title={A framework for detection and measurement of phishing attacks},
+ author={Garera, Sujata and Provos, Niels and Chew, Monica and Rubin, Aviel D},
+ booktitle={Proceedings of the 2007 ACM workshop on Recurring malcode},
+ pages={1--8},
+ year={2007},
+ organization={ACM}
+}
+
+@inproceedings{sahin2010overview,
+ title={An overview of business domains where fraud can take place, and a survey of various fraud detection techniques},
+ author={Sahin, Y and Duman, E},
+ booktitle={Proceedings of the 1st international symposium on computing in science and engineering, Aydin, Turkey},
+ year={2010}
+}
+
+@article{danezis2018blockmania,
+ title={Blockmania: from Block DAGs to Consensus},
+ author={Danezis, George and Hrycyszyn, David},
+ journal={arXiv preprint arXiv:1809.01620},
+ year={2018}
+}
+
+@inproceedings{johnson2013users,
+ title={Users get routed: Traffic correlation on Tor by realistic adversaries},
+ author={Johnson, Aaron and Wacek, Chris and Jansen, Rob and Sherr, Micah and Syverson, Paul},
+ booktitle={Proceedings of the 2013 ACM SIGSAC conference on Computer \& communications security},
+ pages={337--348},
+ year={2013},
+ organization={ACM}
+}
+
+
+@article{arner2018identity,
+ title={The Identity Challenge in Finance: From Analogue Identity to Digitized Identification to Digital KYC Utilities},
+ author={Arner, Douglas W and Zetzsche, Dirk A and Buckley, Ross P and Barberis, Janos Nathan},
+ journal={European Banking Institute},
+ year={2018}
+}
+
+@inproceedings{zakai2011emscripten,
+ title={Emscripten: an LLVM-to-JavaScript compiler},
+ author={Zakai, Alon},
+ booktitle={Proceedings of the ACM international conference companion on Object oriented programming systems languages and applications companion},
+ pages={301--312},
+ year={2011},
+ organization={ACM}
+}
+
+@inproceedings{mulazzani2013fast,
+ title={Fast and reliable browser identification with javascript engine fingerprinting},
+ author={Mulazzani, Martin and Reschl, Philipp and Huber, Markus and Leithner, Manuel and Schrittwieser, Sebastian and Weippl, Edgar and Wien, FC},
+ booktitle={Web 2.0 Workshop on Security and Privacy (W2SP)},
+ volume={5},
+ year={2013},
+ organization={Citeseer}
+}
+
+@misc{sheets1998level,
+ label={CSS},
+ title={{Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification}},
+ publisher={W3C},
+ year={2011},
+ editor={Bos, Bert}
+}
+
+@article{walch2019deconstructing,
+ title={Deconstructing'Decentralization': Exploring the Core Claim of Crypto Systems},
+ author={Walch, Angela},
+ journal={Crypto Assets: Legal and Monetary Perspectives (OUP, forthcoming 2019)},
+ year={2019}
+}
+
+
+@inproceedings{goldwasser1982probabilistic,
+ title={Probabilistic encryption \& how to play mental poker keeping secret all partial information},
+ author={Goldwasser, Shafi and Micali, Silvio},
+ booktitle={Proceedings of the fourteenth annual ACM symposium on Theory of computing},
+ pages={365--377},
+ year={1982},
+ organization={ACM}
+}
+
+@article{goldwasser1989knowledge,
+ title={The knowledge complexity of interactive proof systems},
+ author={Goldwasser, Shafi and Micali, Silvio and Rackoff, Charles},
+ journal={SIAM Journal on computing},
+ volume={18},
+ number={1},
+ pages={186--208},
+ year={1989},
+ publisher={SIAM}
+}
+
+
diff --git a/doc/system/snippets/donations.py b/doc/system/snippets/donations.py
new file mode 100644
index 000000000..217e4c700
--- /dev/null
+++ b/doc/system/snippets/donations.py
@@ -0,0 +1,42 @@
+@app.route("/donate")
+def donate():
+ donation_amount = expect_parameter("donation_amount")
+ donation_donor = expect_parameter("donation_donor")
+ fulfillment_url = flask.url_for("fulfillment", _external=True)
+ order = dict(
+ amount=donation_amount,
+ extra=dict(donor=donation_donor, amount=donation_amount),
+ fulfillment_url=fulfillment_url,
+ summary="Donation to the GNU Taler project",
+ )
+ # ask backend to create new order
+ order_resp = backend_post("order", dict(order=order))
+ order_id = order_resp["order_id"]
+ return flask.redirect(flask.url_for("fulfillment", order_id=order_id))
+
+
+@app.route("/receipt")
+def fulfillment():
+ order_id = expect_parameter("order_id")
+ pay_params = dict(order_id=order_id)
+
+ # ask backend for status of payment
+ pay_status = backend_get("check-payment", pay_params)
+
+ if pay_status.get("payment_redirect_url"):
+ return flask.redirect(pay_status["payment_redirect_url"])
+
+ if pay_status.get("paid"):
+ # The "extra" field in the contract terms can be used
+ # by the merchant for free-form data, interpreted
+ # by the merchant (avoids additional database access)
+ extra = pay_status["contract_terms"]["extra"]
+ return flask.render_template(
+ "templates/fulfillment.html",
+ donation_amount=extra["amount"],
+ donation_donor=extra["donor"],
+ order_id=order_id,
+ currency=CURRENCY)
+
+ # no pay_redirect but article not paid, this should never happen!
+ err_abort(500, message="Internal error, invariant failed", json=pay_status)
diff --git a/doc/system/system.tex b/doc/system/system.tex
new file mode 100644
index 000000000..16782212a
--- /dev/null
+++ b/doc/system/system.tex
@@ -0,0 +1,99 @@
+\documentclass[10pt]{book}
+\usepackage[utf8]{inputenc}
+\usepackage{titlesec}
+\usepackage{xspace}
+\usepackage{microtype}
+\usepackage{amssymb,amsmath,amsthm}
+\newtheorem{theorem}{Theorem}
+\newtheorem{lemma}{Lemma}
+\usepackage{url}
+\usepackage{graphicx}
+\usepackage{caption}
+\usepackage{subcaption}
+\usepackage{enumitem}
+\usepackage{hyperref}
+% bold math
+\usepackage{bm}
+\usepackage{booktabs}
+\usepackage{adjustbox}
+\usepackage{array}
+\usepackage{verbatim}
+\usepackage{listings}
+\usepackage{multicol}
+
+
+% stuff like \ding for symbols
+\usepackage{pifont}
+
+\usepackage[
+ natbib=true,
+ style=alphabetic,
+ backref=true,
+ doi=false,
+ isbn=false,
+ maxbibnames=99,
+]{biblatex}
+\addbibresource{ref.bib}
+
+\usepackage{epsfig}
+\usepackage{textpos}
+\usepackage{ifthen}
+\usepackage{makeidx}
+\usepackage{float}
+\usepackage{calc}
+\usepackage{vmargin}
+\usepackage{letterspace}
+\usepackage[pass]{geometry}
+
+\usepackage{eurosym}
+\usepackage{bytefield}
+\usepackage[binary-units,detect-weight=true,detect-family=true]{siunitx}
+\usepackage[usenames,dvipsnames,svgnames,table]{xcolor}
+\usepackage{qrcode}
+\usepackage{cryptocode}
+\usepackage{tikz}
+\usetikzlibrary{shapes,arrows}
+\usetikzlibrary{positioning}
+\usetikzlibrary{calc}
+\usepackage{mdframed}
+% Typography
+\usepackage[sc,osf]{mathpazo}
+\linespread{1.05}
+\usepackage[scaled]{helvet} % ss
+\usepackage{courier} % tt
+\normalfont
+\usepackage[T1]{fontenc}
+
+\usepackage{pdfpages}
+
+\author{Florian Dold \and The GNU Taler Developers}
+\title{The GNU Taler System}
+
+\begin{document}
+
+\newcommand{\astfootnote}[1]{
+\let\oldthefootnote=\thefootnote%
+\setcounter{footnote}{0}%
+\renewcommand{\thefootnote}{\fnsymbol{footnote}}%
+\footnote{#1}%
+\let\thefootnote=\oldthefootnote%
+}
+
+\maketitle
+\frontmatter
+\include{abstract}
+\include{acknowledgements}
+\tableofcontents
+\listoffigures
+\mainmatter
+
+
+\include{introduction}
+\include{taler/design}
+\include{taler/security}
+\include{taler/implementation}
+\include{conclusions}
+
+\printbibliography[heading=bibintoc]
+
+\end{document}
diff --git a/doc/system/taler-arch-full.pdf b/doc/system/taler-arch-full.pdf
new file mode 100644
index 000000000..9c3d8f2c4
--- /dev/null
+++ b/doc/system/taler-arch-full.pdf
Binary files differ
diff --git a/doc/system/taler-screenshots/bank-login.png b/doc/system/taler-screenshots/bank-login.png
new file mode 100644
index 000000000..7b77f9069
--- /dev/null
+++ b/doc/system/taler-screenshots/bank-login.png
Binary files differ
diff --git a/doc/system/taler-screenshots/bank-profile.png b/doc/system/taler-screenshots/bank-profile.png
new file mode 100644
index 000000000..4015c632e
--- /dev/null
+++ b/doc/system/taler-screenshots/bank-profile.png
Binary files differ
diff --git a/doc/system/taler-screenshots/essay-done.png b/doc/system/taler-screenshots/essay-done.png
new file mode 100644
index 000000000..93582a02b
--- /dev/null
+++ b/doc/system/taler-screenshots/essay-done.png
Binary files differ
diff --git a/doc/system/taler-screenshots/essay-landing.png b/doc/system/taler-screenshots/essay-landing.png
new file mode 100644
index 000000000..e2fd09db5
--- /dev/null
+++ b/doc/system/taler-screenshots/essay-landing.png
Binary files differ
diff --git a/doc/system/taler-screenshots/essay-pay.png b/doc/system/taler-screenshots/essay-pay.png
new file mode 100644
index 000000000..a9174739c
--- /dev/null
+++ b/doc/system/taler-screenshots/essay-pay.png
Binary files differ
diff --git a/doc/system/taler-screenshots/pin-tan.png b/doc/system/taler-screenshots/pin-tan.png
new file mode 100644
index 000000000..8642b11f4
--- /dev/null
+++ b/doc/system/taler-screenshots/pin-tan.png
Binary files differ
diff --git a/doc/system/taler-screenshots/wallet-install-prompt.png b/doc/system/taler-screenshots/wallet-install-prompt.png
new file mode 100644
index 000000000..b9293c800
--- /dev/null
+++ b/doc/system/taler-screenshots/wallet-install-prompt.png
Binary files differ
diff --git a/doc/system/taler-screenshots/wallet-installed.png b/doc/system/taler-screenshots/wallet-installed.png
new file mode 100644
index 000000000..a5212aa39
--- /dev/null
+++ b/doc/system/taler-screenshots/wallet-installed.png
Binary files differ
diff --git a/doc/system/taler-screenshots/withdraw-confirm.png b/doc/system/taler-screenshots/withdraw-confirm.png
new file mode 100644
index 000000000..ba25fd4b6
--- /dev/null
+++ b/doc/system/taler-screenshots/withdraw-confirm.png
Binary files differ
diff --git a/doc/system/taler-screenshots/withdraw-done.png b/doc/system/taler-screenshots/withdraw-done.png
new file mode 100644
index 000000000..a2ed470f6
--- /dev/null
+++ b/doc/system/taler-screenshots/withdraw-done.png
Binary files differ
diff --git a/doc/system/taler/blockchain-accountability.tex b/doc/system/taler/blockchain-accountability.tex
new file mode 100644
index 000000000..3c72a24a6
--- /dev/null
+++ b/doc/system/taler/blockchain-accountability.tex
@@ -0,0 +1 @@
+\chapter{Exchange-accountable e-cash}
diff --git a/doc/system/taler/coin.dot b/doc/system/taler/coin.dot
new file mode 100644
index 000000000..3112a1b65
--- /dev/null
+++ b/doc/system/taler/coin.dot
@@ -0,0 +1,49 @@
+digraph Coin {
+
+ planchet [color=blue, shape="box"];
+ fresh [color=blue, label="fresh coin", shape="box"];
+ rs [color=blue, label="refresh session", shape="box"];
+ partial [color=blue, label="dirty coin", shape="box"];
+ revoked [color=blue, label="revoked coin", shape="box"];
+ zombie [color=blue, label="zombie coin", shape="box"];
+ spent [color=blue, label="spent coin", shape="box"];
+ wired [color=blue, label="wired coin", shape="doublecircle"];
+ expired [color=blue, label="expired coin", shape="doublecircle"];
+
+ subgraph {
+ rank = same; spent; expired;
+ }
+
+ subgraph {
+ withdraw; melt;
+ }
+
+ subgraph {
+ rank = same; melt; reveal; rs;
+ }
+
+
+ planchet->withdraw;
+ planchet->melt;
+ withdraw->fresh;
+ fresh->deposit;
+ fresh->melt;
+ deposit->partial;
+ deposit->spent;
+ melt->rs;
+ rs->reveal;
+ reveal->fresh;
+ melt->partial;
+ melt->spent;
+ spent->refund;
+ refund->partial;
+ spent->wired [style=dotted];
+ partial->expired [style=dotted];
+ partial->melt;
+ partial->deposit [color=red];
+ fresh->expired [style=dotted];
+ fresh->revoked [style=dotted];
+ revoked->recoup;
+ recoup->zombie;
+ zombie->melt;
+}
diff --git a/doc/system/taler/coin.pdf b/doc/system/taler/coin.pdf
new file mode 100644
index 000000000..dcd47292e
--- /dev/null
+++ b/doc/system/taler/coin.pdf
Binary files differ
diff --git a/doc/system/taler/deposit.dot b/doc/system/taler/deposit.dot
new file mode 100644
index 000000000..bfe8c3bdf
--- /dev/null
+++ b/doc/system/taler/deposit.dot
@@ -0,0 +1,31 @@
+digraph Deposit {
+
+ deposited [color=blue, label="deposit created", shape="box"];
+ ready [color=blue, label="deposit ready", shape="box"];
+ due [color=blue, label="deposit due", shape="box"];
+ tiny [color=blue, label="deposit tiny", shape="box"];
+ done [color=blue, label="deposit done", shape="doublecircle"];
+ wtid [color=blue, label="pending transfer", shape="box"];
+ finished [color=blue, label="finished transfer", shape="doublecircle"];
+
+ subgraph {
+ rank = same; due; tiny;
+ }
+
+ pay->deposited;
+ deposited->ready [style=dotted];
+ deposited->refund;
+ refund->deposited;
+ refund->ready;
+ refund->done;
+ ready->due [style=dotted];
+ ready->refund;
+ aggregate->tiny;
+ due->aggregate;
+ ready->aggregate;
+ tiny->aggregate;
+ aggregate->done;
+ aggregate->wtid;
+ wtid->transfer;
+ transfer->finished;
+}
diff --git a/doc/system/taler/deposit.pdf b/doc/system/taler/deposit.pdf
new file mode 100644
index 000000000..f66e3bd5a
--- /dev/null
+++ b/doc/system/taler/deposit.pdf
Binary files differ
diff --git a/doc/system/taler/design.tex b/doc/system/taler/design.tex
new file mode 100644
index 000000000..fd3cb746b
--- /dev/null
+++ b/doc/system/taler/design.tex
@@ -0,0 +1,1399 @@
+
+% FIXME: maybe move things around a bit, structure it better into
+% sections and subsections?
+
+% FIXME: talk about amounts and anonymity
+
+
+\chapter{GNU Taler, an Income-Transparent Anonymous E-Cash System}\label{chapter:design}
+
+This chapter gives a high-level overview of the design of GNU Taler, based on
+the requirements discussed in Chapter~\ref{chapter:introduction}. The
+cryptographic protocols and security properties are described and analyzed in detail in
+Chapter~\ref{chapter:security}. A complete implementation with focus on of Web
+payments is discussed in Chapter~\ref{chapter:implementation}.
+
+
+\section{Design of GNU Taler}
+
+GNU Taler is based on the idea of Chaumian e-cash \cite{chaum1983blind}, with
+some differences and additions explained in the following sections. Other
+variants and extensions of anonymous e-cash and blind signatures are discussed in
+Section~\ref{sec:related-work:e-cash}.
+
+
+\subsection{Entities and Trust Model}
+GNU Taler consists of the following entities (see \ref{fig:taler-arch}):
+\begin{itemize}
+ \item The \emph{exchanges} serve as payment service provider for a
+ financial transaction between a customer and a merchant. They hold bank money
+ in escrow in exchange for anonymous digital \emph{coins}.
+ \item The \emph{customers} keep e-cash in their electronic \emph{wallets}.
+ \item The \emph{merchants} accept digital coins in exchange for digital or physical
+ goods and services. The digital coins can be deposited with the exchange,
+ in exchange for bank money.
+ \item The \emph{banks} receive wire transfer instructions from customers
+ and exchanges. A customer, merchant and exchange involved in one
+ GNU Taler payment do not need to have accounts with the same bank,
+ as long as wire transfers can be made between the respective banks.
+ \item The \emph{auditors}, typically run by trusted financial regulators,
+ monitor the behavior of exchanges to assure customers and merchants that
+ exchanges operate correctly.
+\end{itemize}
+
+\begin{figure}
+ \begin{center}
+ \begin{tikzpicture}
+ \tikzstyle{def} = [node distance= 5em and 6.5em, inner sep=1em, outer sep=.3em];
+ \node (origin) at (0,0) {};
+ \node (exchange) [def,above=of origin,draw]{Exchange};
+ \node (customer) [def, draw, below left=of origin] {Customer};
+ \node (merchant) [def, draw, below right=of origin] {Merchant};
+ \node (auditor) [def, draw, above right=of origin]{Auditor};
+
+ \tikzstyle{C} = [color=black, line width=1pt]
+
+ \draw [<-, C] (customer) -- (exchange) node [midway, above, sloped] (TextNode) {withdraw coins};
+ \draw [<-, C] (exchange) -- (merchant) node [midway, above, sloped] (TextNode) {deposit coins};
+ \draw [<-, C] (merchant) -- (customer) node [midway, above, sloped] (TextNode) {spend coins};
+ \draw [<-, C] (exchange) -- (auditor) node [midway, above, sloped] (TextNode) {verify};
+
+ \end{tikzpicture}
+ \end{center}
+ \caption[High-level overview of GNU Taler components.]{High-level overview of the different components of GNU Taler, banks are omitted.}
+ \label{fig:taler-arch}
+\end{figure}
+
+In GNU Taler, the exchanges can be separate entities from the banks. This fosters
+competition between exchanges, and allows Taler to be deployed in an
+environment with legacy banks that do not support Taler directly.
+
+If a customer wants to pay a merchant, the customer needs to hold coins at an
+exchange that the merchant trusts. To make the selection of trusted exchanges
+simpler, merchants and customers can choose to automatically trust all
+exchanges audited by a certain auditor.
+
+The exchange is trusted to hold funds of its customers in escrow and to make
+payments to merchants when digital coins are deposited. Customer and merchant
+can have assurances about the exchange's liquidity and operation though the
+auditor, which would typically be run by financial regulators or other trusted
+third parties.
+
+\subsection{System Assumptions}
+
+We assume that an anonymous, bi-directional communication channel\footnote{%
+An anonymization layer like Tor \cite{dingledine2004tor} can provide a
+practical approximation of such a communication channel, but does not provide
+perfect anonymity \cite{johnson2013users}.
+} is used for
+all communication between the customer and the merchant, as well as for
+obtaining unlinkable change for partially spent coins from the exchange and for
+retrieving the exchange's public keys used in verifying and blindly signing
+coins. The withdrawal protocol, on the other hand, does not require an
+anonymous channel to preserve the anonymity of electronic coins.
+
+During withdrawal, the exchange knows the identity of the withdrawing customer,
+as there are laws, or bank policies, that limit the amount of cash that an
+individual customer can withdraw in a given time
+period~\cite{france2015cash,greece2015cash}. GNU Taler is thus only anonymous with
+respect to \emph{payments}. While the exchange does know their customer (KYC),
+it is unable to link the known identity of the customer that withdrew anonymous
+digital coins to the \emph{purchase} performed later at the merchant.
+
+While customers can make untraceable digital cash payments, the exchange will
+always learn the merchants' identity, which is necessary to credit their
+accounts. This information can also be used for taxation, and GNU Taler
+deliberately exposes these events as anchors for tax audits on merchants'
+income. Note that while GNU Taler \emph{enables} taxation, it does not
+\emph{implement} any automatic taxation.
+
+GNU Taler assumes that each participant has full control over their
+system%
+\footnote{%
+ Full control goes both ways: it gives the customer the freedom to run their own software,
+ but also means that the behavior of fraudulent customers cannot be restricted by
+ simpler technical means such as keeping balances on tamper-proof smart cards,
+ and thus can lead to an overall more complex system.
+}. We assume the contact information of the exchange is known to
+both customer and merchant from the start, and the customer
+can authenticate the merchant, for example, by using X.509
+certificates~\cite{rfc6818}. A GNU Taler merchant is expected to deliver
+the service or goods to the customer upon receiving payment. The
+customer can seek legal relief to achieve this, as the customer
+receives cryptographic evidence of the contract and the associated
+payment.
+
+% FIXME: who needs to be trusted for anonymity?
+
+\subsection{Reserves}
+
+A \emph{reserve} refers to a customer's non-anonymous funds at an exchange,
+identified by a reserve public key. Suppose a customer wants to convert money
+into anonymized digital coins. To do that, the customer first creates a
+reserve private/public key pair, and then transfers money via their bank to the
+exchange. The wire transfer instruction to the bank must include the reserve
+public key. To withdraw coins from a reserve, the customer authenticates
+themselves using the corresponding reserve private key.
+
+Typically, each wire transfer is made with a fresh reserve public key and thus
+creates a new reserve, but making another wire transfer with the same reserve
+public key simply adds funds to the existing reserve. Even after all funds
+have been withdrawn from a reserve, customers should keep the reserve key pair
+until all coins from the corresponding reserve have been spent, as in the event
+of a denomination key revocation (see Section \ref{sec:revocation-recoup}) the
+customer needs this key to recover coins of revoked denominations.
+
+The exchange automatically transfers back to the customer's bank account any
+funds that have been left in a reserve for an extended amount of time, allowing
+customers that lost their reserve private key to eventually recover their
+funds. If a wire transfer to the exchange does not include a valid reserve public key,
+the exchange transfers the money back to the sender.
+
+Figure~\ref{fig:reserve:states} illustrates the state machine for a reserve.
+Long-terms states are shown in boxes, while actions are in circles. The final
+state is in a double-circle. A reserve is first {\em filled} by a wire
+transfer. The amount in it is reduced by withdraw operations. If the balance
+reaches zero, the reserve is {\em drained}. If a reserve is not drained after
+a certain amount of time, it is automatically closed. A reserve can also be
+{\em refilled} via a recoup action (see Section~\ref{sec:revocation-recoup}) in case
+that the denomination of an unspent coin that was withdrawn from the reserve
+is revoked.
+
+\begin{figure}
+ \begin{center}
+ \includegraphics{taler/reserve.pdf}
+ \end{center}
+ \caption{State machine of a reserve.}
+ \label{fig:reserve:states}
+\end{figure}
+
+Instead of requiring the customer to manually generate reserve key pairs and
+copy them onto a wire transfer form, banks can offer tight integration with the
+GNU Taler wallet software. In this scenario, the bank's website or banking app
+provides a ``withdraw to GNU Taler wallet'' action. After selecting this
+action, the user is asked to choose the amount to withdraw from their bank
+account into the wallet. The bank then instructs the GNU Taler wallet software
+to create record of the corresponding reserve; this request contains the anticipated amount, the
+reserve key pair and the URL of the exchange to be used. When invoked by the
+bank, the wallet asks the customer to select an exchange and to confirm the
+reserve creation. The exchange chosen by the customer must support the wire
+transfer method used by the bank, which will be automatically checked by the
+wallet. Typically, an exchange is already selected by default, as banks can
+suggest a default exchange provider to the wallet, and additionally wallets
+have a pre-defined list of trusted exchange providers. Subsequently, the wallet
+hands the reserve public key and the bank account information of the selected
+exchange back to the bank. The bank---typically after asking for a second authentication
+factor from the customer---will then trigger a wire transfer to the exchange with
+the information obtained from the wallet.
+
+When the customer's bank does not offer tight integration with GNU Taler, the
+customer can still manually instruct their wallet to create a reserve. The public
+key must then be included in a bank transaction to the exchange. When the
+customer's banking app supports pre-filling wire transfer details from a URL or
+a QR code, the wallet can generate such a URL or QR code that includes the
+pre-filled bank account details of the exchange as well as the reserve public
+key. The customer clicks on this link or scans the QR code to invoke their
+banking app with pre-filled transaction details. Since there currently is no
+standardized format for pre-filled wire transfer details, we are proposing the
+\texttt{payto://} URI format explained in
+Section~\ref{implementation:wire-method-identifiers}, currently under review
+for acceptance as an IETF Internet Standard.
+
+
+% FIXME: withdrawal strategy, coin selection
+
+\subsection{Coins and Denominations}
+
+Unlike plain Chaumian e-cash, where a coin just contains a serial number, a
+\emph{coin} in Taler is a public/private key pair where the private key is only
+known to the owner of the coin.
+
+A coin derives its financial value from a blind signature on the coin's
+public key. The exchange has multiple \emph{denomination key} pairs available
+for blind-signing coins of different financial values. Other approaches for representing
+different denominations are discussed in Section~\ref{design:related-different-denominations}.
+
+Denomination keys have an expiration date, before which any coins signed with
+it must be spent or exchanged into newer coins using the refresh protocol
+explained in Section \ref{sec:design-refresh}. This allows the exchange to
+eventually discard records of old transactions, thus limiting the records that
+the exchange must retain and search to detect double-spending attempts. If a
+denomination's private key were to be compromised, the exchange can detect this
+once more coins are redeemed than the total that was signed into existence
+using that denomination key. Should such an incident occur, the exchange can allow authentic
+customers to redeem their unspent coins that were signed with the compromised
+private key, while refusing further deposits involving coins signed by the
+compromised denomination key (see Section~\ref{sec:revocation-recoup}). As a result, the
+financial damage of losing a private signing key is limited to at most the
+amount originally signed with that key, and denomination key rotation can be
+used to bound that risk.
+
+To prevent the exchange from deanonymizing users by signing each coin with a
+fresh denomination key, exchanges publicly announce their denomination keys
+in advance with validity periods that imply sufficiently strong anonymity sets.
+These announcements are expected to be signed with an offline long-term
+private \emph{master signing key} of the exchange and the auditor.
+Customers should obtain these announcements using an anonymous
+communication channel.
+
+After a coin is issued, the customer is the only entity that knows the
+private key of the coin, making them the \emph{owner} of the coin. Due
+to the use of blind signatures, the exchange does not learn the
+public key during the withdrawal process. If the private key is
+shared with others, they become co-owners of the coin. Knowledge of
+the private key of the coin and the signature over the coin's public
+key by an exchange's denomination key enables spending the
+coin.
+
+\subsection{Partial Spending and Unlinkable Change}
+
+Customers are not required to have exact change ready when making a payment.
+In fact, it should be encouraged to withdraw a larger amount of e-cash
+beforehand, as this blurs the correlation between the non-anonymous withdrawal
+event and the anonymous spending event, increasing the anonymity set.
+
+A customer spends a coin at a merchant by cryptographically signing a
+\emph{deposit permission} with the coin's private key. A deposit permission
+contains the hash of the \emph{contract terms}, i.e., the details of the
+purchase agreement between the customer and merchant. Coins can be
+\emph{partially} spent, and a deposit permission specifies the fraction of the
+coin's value to be paid to the merchant. As digital coins are trivial to copy,
+the merchant must immediately deposit them with the exchange, in order to get a
+deposit confirmation or an error that indicates double spending.
+
+When a coin is used in a completed or attempted/aborted payment, the coin's
+public key is revealed to the merchant/exchange, and further payments with the
+remaining amount would be linkable to the first spending event. To obtain
+unlinkable change for a partially spent (or otherwise revealed coin), GNU
+Taler introduces the \emph{refresh protocol}, which consists of three steps:
+\emph{melt}, \emph{reveal} and \emph{link}. The refresh protocol allows the
+customer to obtain new coins for the remaining amount on a coin. The old coin
+is marked as spent after it has been melted, while the reveal step generates
+the fresh coins. Using blind signatures to withdraw the refreshed coins makes
+them unlinkable from the old coin.
+
+% FIXME: talk about logarithmic time, simulation
+
+\subsection{Refreshing and Taxability}\label{sec:design-refresh}
+% FIXME: maybe put section on how to avoid withdraw loophole here!
+One goal of GNU Taler is to make merchants' income transparent to state auditors,
+so that income can be taxed appropriately. Naively implemented, however, a simple
+refresh protocol could be used to evade taxes: the payee of an untaxed
+transaction would generate the private keys for the coins that result from
+refreshing a (partially spent) old coin, and send the corresponding public keys
+to the payer. The payer would execute the refresh protocol, provide the
+payee's coin public keys for blind signing, and provide the signatures to the
+payee, who would now have exclusive control over the coins.
+
+To remedy this, the refresh protocol introduces a \emph{link threat}: coins are
+refreshed in such a way that the owner of the old coin can always obtain the
+private key and exchange's signature on the new coins resulting from refreshes,
+using a separate \emph{linking protocol}. This introduces a threat to
+merchants that try to obtain untaxed income. Until the coins are finally
+deposited at the exchange, the customer can always re-gain ownership of them
+and could deposit them before the merchant gets a chance to do so. This
+disincentivizes the circulation of unreported income among untrusted parties in
+the system.
+
+In our implementation of the refresh and linking protocols, there is a
+non-negligible success chance ($\frac{1}{\kappa}$, depending on system parameter
+$\kappa$, typically $\ge 3$) for attempts to cheat during the refresh protocol,
+resulting in refreshed coins that cannot be recovered from the old coin via the
+linking protocol. Cheating during refresh, however, is still not
+\emph{profitable}, as an unsuccessful attempt results in completely losing the
+amount that was intended to be refreshed.
+
+% FIXME: mention that we don't want to use DRM/HSMs for this
+
+For purposes of anti-money-laundering and taxation, a more detailed audit of
+the merchant's transactions can be desirable. A government tax authority can
+request the merchant to reveal the business agreement details that match the
+contract terms hash recorded with the exchange. If a merchant is not able to
+provide these values, they can be subjected to financial penalties by the
+state in relation to the amount transferred by the traditional currency
+transfer.
+
+\subsection{Transactions vs. Sharing}
+
+Sharing---in contrast to a transaction---happens when mutually trusted parties
+simultaneously have access to the private keys and signatures on coins.
+Sharing is not considered a transaction, as subsequently both parties have equal control
+over the funds. A useful application for sharing are peer-to-peer payments
+between mutually trusting parties, such as families and friends.
+
+\subsection{Aggregation}
+
+For each payment, the merchant can specify a deadline before which the exchange
+must issue a wire transfer to the merchant's bank account. Before this
+deadline occurs, multiple payments from deposited coins to the same merchant
+can be \emph{aggregated} into one bigger payment. This reduces transaction
+costs from underlying banking systems, which often charge a fixed fee per
+transaction. To incentivize merchants to choose a longer wire transfer
+deadline, the exchange can charge the merchant a fee per aggregated wire
+transfer.
+
+Figure~\ref{fig:deposit:states} illustrates the state machine for processing
+deposits. Long-terms states are shown in boxes, while actions are in circles.
+The final state is in a double-circle. Dashed arrows show transitions based
+on timing and not external actions. A deposit is first {\em created} when a
+wallet makes a payment. A deposit comes with a {\em refund deadline}, and the
+wire transfer must not happen before that deadline. Once the refund deadline
+has passed, the deposit becomes {\em ready}. Even if a deposit is ready, it
+is not automatically wired. In fact, deposits may still be {\em refunded} in
+this state. A refund may be full (resulting in the deposit being {\em done})
+or partial, in which case the remaining value is left in the same deposit
+state. A deposit comes with a second deadline, the {\em wire deadline}. Once
+that deadline has passed, the deposit is {\em due} and must be {\em
+ aggregated}. Aggregation combines {\bf all} deposits that are {\em due},
+{\em tiny} or {\em ready} into one wire transfer. However, the amount of even
+an aggregated deposit may be too small to be executed by the banking
+system. In this case, the deposit transitions into the special state {\em
+ tiny} until the aggregated amount meets the amount threshold. Once
+aggregated, the deposits are {\em done}. A wire transfer is first prepared
+and then {\em pending}. The transfer is {\em finished} once the bank has
+confirmed the {\em transfer}.
+
+\begin{figure}
+ \begin{center}
+ \includegraphics[scale=0.8]{taler/deposit.pdf}
+ \end{center}
+ \caption{State machine of a deposit.}
+ \label{fig:deposit:states}
+\end{figure}
+
+
+\subsection{Refunds}
+
+The aggregation period also opens the opportunity for cheap \emph{refunds}. If
+a customer is not happy with their product, the merchant can instruct the
+exchange to give the customer a refund before the wire transfer deadline has
+occurred. This effectively ``undoes'' the deposit of the coin, and restores the
+available amount left on it. The refresh protocol is then used by the customer
+on the coins involved in a refund, so that payments remain unlinkable.
+
+% FIXME: mention EU customer laws / 14 weeks?
+
+\subsection{Fees}
+
+In order to subsidize the operation of the exchange and enable a sustainable
+business model, the exchange can charge fees for most operations. For
+withdrawal, refreshing, deposit and refunds, the fee is dependent on the denomination,
+as different denominations might have different key sizes, security and storage
+requirements.
+
+Most payment systems hide fees from the customer by putting them to the merchant.
+This is also possible with Taler. As different exchanges (and denominations)
+can charge different fees, the merchant can specify a maximum amount of fees it
+is willing to cover. Fees exceeding this amount must be explicitly paid by the
+customer.
+
+Another consideration for fees is the prevention of denial-of-service attacks.
+To make ``useless'' operations, such as repeated refreshing on coins
+(causing the exchange to use relatively expensive storage), unattractive to an
+adversary, these operations must charge a fee. Again, for every refresh
+following a payment, the merchant can cover the costs up to a limit set by the
+merchant, effectively hiding the fees from the customer.
+
+Yet another type of fee are the \emph{wire transfer fees}, which are charged
+by the exchange for every wire transfer to a merchant in order to compensate for
+the cost of making a transaction in the underlying bank system. The wire
+transfer fees encourage merchants to choose longer aggregation periods, as the
+fee is charged per transaction and independent of the amount.
+
+Merchants can also specify the maximum wire fee they are willing to cover for
+customers, along with an \emph{amortization rate} for the wire fees. In case
+the wire fees for a payment exceed the merchant's chosen maximum, the customer
+must additionally pay the excess fee divided by the amortization rate. The
+merchant should set amortization rate to the expected number of transactions
+per wire transfer aggregation window. This allows the merchant to adjust
+the total expected amount that it needs to pay for wire fees.
+
+
+\subsection{The Withdraw Loophole and Tipping}\label{taler:design:tipping}
+
+The withdraw protocol can be (ab)used to illicitly transfer money, when the
+receiver generates the coin's secret key, and gives the public key to the party
+executing the withdraw protocol. We call this the ``withdraw loophole''. This
+is only possible for one ``hop'', as money can still not circulate among
+mutually distrusted parties, due to the properties of the refresh protocol.
+
+A ``benevolent'' use of the withdraw loophole is \emph{tipping}, where merchants give
+small rewards to customers (for example, for filling out a survey or installing
+an application), without any contractual obligations or digitally signed
+agreement.
+
+% FIXME: argue that this can't be done on scale for money laundering
+
+\subsubsection{Fixing the Withdraw Loophole}\label{taler:design:fixing-withdraw-loophole}
+
+In order to discourage the usage of the withdraw loophole for untaxed payments,
+the following approach would be possible: Normal withdraw operations and
+unregistered reserves are disabled, except for special tip reserves that are
+registered by the merchant as part of a tipping campaign. Customers are
+required to pre-register at the exchange and obtain a special withdraw key pair
+against a small safety deposit. Customer obtain new coins via a refresh
+operation from the withdraw key to a new coin. If customers want to abuse
+Taler for untaxed payments, they either need to risk losing money by lying
+during the execution of the refresh protocol, or share their reserve private
+key with the payee. In order to discourage the latter, the exchanges gives the
+safety deposit to the first participant who reports the corresponding private
+key as being used in an illicit transaction, and requires a new safety deposit
+before the customer is allowed to withdraw again.
+
+However since the withdraw loophole allows only one additional ``payment'' (without any
+cryptographic evidence that can be used in disputes) before the coin must be deposited,
+these additional mitigations might not even be justified considering their additional cost.
+
+
+\section{Auditing}
+
+The auditor is a component of GNU Taler which would typically be deployed by a
+financial regulator, fulfilling the following functionality:
+
+\begin{itemize}
+ \item It regularly examines the exchange's database and
+ bank transaction history to detect discrepancies.
+ \item It accepts samples of certain protocol responses that merchants
+ received from an audited exchange, to verify that what the exchange signed
+ corresponds to what it stored in its database.
+ \item It certifies exchanges that fulfill the operational and financial requirements
+ demanded by regulators.
+ \item It regularly runs anonymous checks to ensure that the required protocol
+ endpoints of the exchange are available.
+ \item In some deployment scenarios, merchants need to pre-register with exchanges to fulfill know-your-customer (KYC) requirements.
+ The auditor provides a list of certified exchanges to merchants,
+ to which the merchant then can automatically KYC-register.
+ \item It provides customers with an interface to submit cryptographic proof that an exchange
+ misbehaved. If a customer claims that the exchange denies service, it can execute a request on
+ behalf of the customer.
+\end{itemize}
+
+%An exchange operator would typically run their own instance of the auditor software,
+%to ensure correct operation.
+
+% FIXME: live auditing
+
+% FIXME: discuss indian merchant scenario
+
+\subsection{Exchange Compromise Modes}
+
+The exchange is an attractive target for hackers and insider threats. We now
+discuss different ways that the exchange can be compromised, how to reduce the
+likelihood of such a compromise, and how to detect and react to such an event
+if it happens.
+
+\subsubsection{Compromise of Denomination Keys and Revocation}\label{sec:revocation-recoup}
+
+When a denomination key pair is compromised, an attacker can ``print money'' by
+using it to sign coins of that denomination. An exchange (or its auditor) can
+detect this when the number of deposits for a certain denomination exceed the
+number of withdrawals for that same denomination.
+
+We allow the exchange to revoke denomination keys, and wallets periodically
+check for such revocations. We call a coin of a revoked denomination a revoked
+coin. If a denomination key has been revoked, the wallets use the
+\emph{recoup} protocol to recover funds from coins of revoked denominations.
+Once a denomination is revoked, new coins of this denomination can't be
+withdrawn or used as the target denomination for a refresh operation. A revoked
+coin cannot be spent, and can only be refreshed if its public key was recorded
+in the exchange's database (as spending/refresh operations) before it was
+revoked.
+
+The following cases are possible for recoup:
+\begin{enumerate}
+ \item The revoked coin has never been seen by the exchange before, but the
+ customer can prove via a withdraw protocol transcript and blinding factor
+ that the coin resulted from a legitimate withdrawal from a reserve. In
+ this case, the exchange credits the reserve that was used to withdraw the
+ coin with the value of the revoked coin.
+ \item The coin has been partially spent. In this case, the exchange allows
+ the remaining amount on the coin to be refreshed into fresh coins of
+ non-revoked denominations.
+ \item The revoked coin $C_R$ has never been seen by the exchange before, was
+ obtained via the refresh protocol, and the exchange has an existing record
+ of either a deposit or refresh for the ancestor coin $C_A$ that was
+ refreshed into the revoked coin $C_R$. If the customer can prove this by
+ showing a corresponding refresh protocol transcript and blinding factors, the exchange credits
+ the remaining value of $C_R$ on $C_A$. It is explicitly permitted for $C_A$
+ to be revoked as well. The customer can then obtain back their funds by
+ refreshing $C_A$.
+\end{enumerate}
+
+These rules limit the maximum financial damage that the exchange can incur from
+a compromised denomination key $D$ to $2nv$, with $n$ being the
+maximum number of $D$-coins simultaneously in circulation and $v$ the financial
+value of a single $D$-coin. Say denomination $D$ was withdrawn by
+legitimate users $n$ times. As soon as the exchange sees more
+than $n$ pairwise different $D$-coins, it must immediately
+revoke $D$. An attacker can thus at most gain $nv$ by either
+refreshing into other non-revoked denominations or spending the forged $D$-coins.
+The legitimate users can then request a recoup for their coins, resulting in
+a total financial damage of at most $2nv$.
+
+With one rare exception, the recoup protocol does not negatively impact the
+anonymity of customers. We show this by looking at the three different cases
+for recoup on a revoked coin. Specifically, in case (1), the coin obtained
+from the credited reserve is blindly signed, in case (2) the refresh protocol
+guarantees unlinkability of the non-revoked change, and in case (3) the revoked
+coin $C_R$ is assumed to be fresh. If $C_R$ from case (3) has been seen by a
+merchant before in an aborted/unfinished transaction, this transaction would be
+linkable to transactions on $C_A$. Thus, anonymity is not preserved when an
+aborted transaction coincides with revoked denomination, which should be rare
+in practice.
+
+Unlike most other operations, the
+recoup protocol does not incur any transaction fees. The primary use of the
+protocol is to limit the financial loss in cases where an audit reveals that
+the exchange's private keys were compromised, and to automatically pay back
+balances held in a customers' wallet if an exchange ever goes out of business.
+
+To limit the damage of a compromise, the exchange can employ a hardware
+security module that contains the denomination secret keys, and is
+pre-programmed with a limit on the number of signatures it can produce. This
+might be mandated by certain auditors, who will also audit the operational
+security of an exchange as part of the certification process.
+
+
+
+\subsubsection{Compromise of Signing Keys} \label{sec:signkey:compromise}
+
+When a signing key is compromised, the attacker can pretend to be a
+merchant and forge deposit confirmations. To forge a deposit
+confirmation, the attacker also needs to get a customer to sign a
+contract from the adversary (which should include the adversary's
+banking details) with a valid coin. The attack here is that the
+customer is allowed to have spent the coin already. Thus, a deposit of
+the resulting deposit permission would result in a rejection from the
+exchange due to double spending. By forging the deposit confirmation
+using the compromised signing key, the attacker can thus claim in
+court that they properly deposited the coin first and demand payment
+from the exchange.
+
+We note that indeed an evil exchange could simply fail to record
+deposit permissions in its database and then fail to execute them.
+Thus, given a merchant presenting a deposit confirmation, we need
+a way to establish whether this is a case of an evil exchange that
+should be compelled to pay, or a case of a compromised signing key
+and where payouts (and thus financial damage to the exchange)
+can legitimately be limited.
+
+To limit the financial damage of a compromised signing key, merchants
+must be required to work with auditors to perform a
+\emph{probabilistic deposit auditing} of the exchange. Here, the goal
+is to help detect the compromise of a signing key by making sure that
+the exchange does indeed properly record deposit confirmations.
+However, double-checking with the auditor if every deposit
+confirmation is recorded in the exchange's database would be too
+expensive and time-consuming. Fortunately, a probabilistic method
+where merchants only send a small fraction of their deposit
+confirmations to the auditor suffices. Then, if the auditor sees a
+deposit confirmation that is not recorded in the exchange's database
+(possibly after performing the next synchronization with the
+exchange's database), it signals the exchange that the signing key has
+been compromised.
+
+At this point, the signing key must be revoked and the exchange will
+be required to investigate the security of its systems and address the
+issue before resuming normal operations.
+%
+%If the exchange had separate short-term signing keys just for signing deposit
+%confirmations, it could also employ hardware security modules with a counter,
+%and check if the value of the counter matches matches the deposit confirmations
+%recorded in the database.
+
+Still, at this point various actors (including the attacker) could still
+step forward with deposit confirmations signed by the revoked key and
+claim that the exchange owes them for their deposits. Simply revoking
+a signing key cannot lift the exchange's payment obligations, and the
+attacker could have signed an unlimited number of such deposit confirmations
+with the compromised key. However, in contrast to honest merchants, the
+attacker will not have participated {\em proportionally} in the auditor's
+probabilistic deposit auditing scheme for those deposit confirmations:
+in that case, the key compromise would have been detected and the key
+revoked.
+
+The exchange must still pay all deposit permissions it signed for
+coins that were not double-spent. However, for all coins where
+multiple merchants claim that they have a deposit confirmation, the
+exchange will pay the merchants proportionate to the fraction of the
+coins that they reported to the auditor as part of probabilistic
+deposit auditing. For example, if 1\% of deposits must be reported to
+the auditor according to the protocol, a merchant might be paid at
+most say 100+X times the number of reported deposits where $X>0$
+serves to ensure proper payout despite the probabilistic nature of the
+reporting. As a result, honest merchants have an {\em incentive} to
+correctly report the deposit confirmations to the auditor.
+
+Given this scheme, the attacker can only report a small number of
+deposit confirmations to the auditor before triggering the signing key
+compromise detection. Suppose again that 1\% of deposit confirmations
+are reported by honest merchants, then the attacker can only expect to
+submit 100 deposit permissions created by the compromised signing key
+before being detected. The attacker's expected financial benefit from
+the key compromise would then be the value of $(100+X) \cdot 100$
+deposit permissions.
+
+Thus, the financial benefit to the attacker can be limited by
+probabilistic deposit auditing, and honest merchants have proper
+incentives to participate in the process.
+
+\subsubsection{Compromise of the Database}
+
+If an adversary would be able to modify the exchange, this would be detected
+rather quickly by the auditor, provided that the database has appropriate
+integrity mechanisms. An attacker could also prevent database updates to block
+the recording of spend operations, and then double spend. This is effectively
+equivalent to the compromise of signing keys, and can be detected with the same
+strategies.
+
+\subsubsection{Compromise of the Master Key}
+
+If the master key was compromised, an attacker could de-anonymize customers by
+announcing different sets of denomination keys to each of them. If the
+exchange was audited, this would be detected quickly, as these denominations
+will not be signed by auditors.
+
+\subsection{Cryptographic Proof}
+
+We use the term ``proof'' in many places as the protocol provides cryptographic
+proofs of which parties behave correctly or incorrectly. However,
+as~\cite{fc2014murdoch} point out, in practice financial systems need to
+provide evidence that holds up in courts. Taler's implementation is designed
+to export evidence and upholds the core principles described
+in~\cite{fc2014murdoch}. In particular, in providing the cryptographic proofs
+as evidence none of the participants have to disclose their core secrets.
+
+\subsection{Perfect Crime Scenarios}\label{sec:design:blackmailing}
+
+GNU Taler can be slightly modified to thwart blackmailing or kidnapping
+attempts by criminals who intend to use the anonymity properties of the system
+and demand to be paid ransom in anonymous e-cash.
+
+Our modification incurs a slight penalty on the latency for customers during normal use and
+requires slightly more data to be stored in the exchange's database, and thus
+should only be used in deployments where resistance against perfect crime
+scenarios is necessary. A payment system for a school cafeteria likely does
+not need these extra measures.
+
+The following modifications are made:
+\begin{enumerate}
+ \item Coins can now only be used in either a transaction or in a refresh operations, not a mix of both.
+ Effectively, the customer's wallet then needs to use the refresh protocol to prepare exact change
+ before a transaction is made, and that transaction is made with exact change.
+
+ This change is necessary to preserve anonymity in face of the second modification, but increases
+ storage requirements and latency.
+ \item The recoup protocol is changed so that a coin obtained
+ via refreshing must be recovered differently when revoked: to recover a revoked coin
+ obtained via refreshing, the customer needs to show the transcripts for the
+ chain of all refresh operations and the initial withdrawal operation
+ (including the blinding factor). Refreshes on revoked coins are not
+ allowed anymore.
+\end{enumerate}
+
+After an attacker has been paid ransom, the exchange simply revokes all currently offered denominations
+and registers a new set of denomination with the auditor.
+Reserves used to pay the attacker are marked as blocked in the exchange's
+database. Normal users can use the recoup protocol to obtain back the money
+they've previously had in revoked denominations. The attacker can try to
+recover funds via the (now modified) recoup protocol, but this attempt will
+not be successful, as the initial reserve is blocked. The criminal could also
+try to spend the e-cash anonymously before it is revoked, but this is likely
+difficult for large amounts, and furthermore due to income transparency all
+transactions made between the payment of the ransom and the revocation can be
+traced back to merchants that might be complicit in laundering the ransom
+payment.
+
+Honest customers can always use the recoup protocol to transfer the funds to
+the initial reserve. Due to modification (1), unlinkability of transactions is
+not affected, as only coins that were purely used for refreshing can now be
+correlated.
+
+We believe that our approach is more practical than the approaches based on
+tracing, since in a scheme with tracing, the attacker can always ask for a
+plain blind signature. With our approach, the attacker will always lose funds
+that they cannot immediately spend. Unfortunately our approach is limited to a
+kidnapping scenario, and not applicable in those blackmail scenarios where the
+attacker can do damage after they find out that their funds have been erased.
+
+\subsection{Summary}
+
+Figure~\ref{fig:coin:states} illustrates the overall state machine for processing
+coins. Long-terms states are shown in boxes, while actions are in circles.
+The final state is in a double-circle. Dashed arrows show transitions based
+on timing and not external actions. The red arrow shows an action that is
+allowed by the exchange but should never be done by wallets as it would
+break unlinkability.
+
+A coin begins as an unsigned {\em planchet}, which is either signed as part of
+the {\em withdraw} protocol or the refresh protocol. The most common scenario
+is that the {\em fresh coin} is {\em deposited}. This payment creates a
+deposit (see Figure~\ref{fig:deposit:states}) and either a {\em dirty coin}
+(if the payment was for a fraction of the coin's value) or a {\em spent coin}.
+A spent coin can be {\em refunded} by the merchant, creating a {\em dirty
+ coin}. Once the exchange has aggregated a coin and wired the amount to the
+merchant, a coin can no longer be refunded.
+
+A {\em fresh coin} may also be subject to key {\em revocation}, at which point
+the wallet ends up with a {\em revoked coin}. At this point, the wallet can
+use the {\em recoup} protocol to recover the value of the coin. If the coin
+originated from a {\em withdraw} operation, the value is added back into the
+reserve, which is {\em filled} in the process (see
+Figure~\ref{fig:reserve:states}). If the coin originated from the {\em
+ refresh} operation, this results in the old coin turning into a {\em zombie
+ coin}, which can be refreshed again.
+
+Dirty coins and fresh coins can be {\em melted}. Dirty coins should always be
+melted automatically by the wallet as soon as possible as this is the only
+good way to use them while preserving unlinkability. A wallet should also
+automatically {\em melt} any {\em fresh coins} that are in danger of their
+denomination key nearing its (deposit) {\em expiration} time. If a wallet
+fails to do so, coins may {\em expire}, resulting in a loss for the coin's
+owner. Dirty coins can also expire. In practice, this happens if the melt fee
+exceeds the residual value of the dirty coin. To {\em melt} a coin, the
+wallet must commit to one or more {\em planchets} and then demonstrate honesty
+when the commitment made for the {\em refresh session} is checked during the
+{\em reveal} step. If the wallet was honest, {\em reveal} yields {\em fresh
+ coins}.
+
+\begin{figure}
+ \begin{center}
+ \includegraphics[scale=0.65]{taler/coin.pdf}
+ \end{center}
+ \caption{State machine of a coin.}
+ \label{fig:coin:states}
+\end{figure}
+
+
+
+
+
+\section{Related Work}
+% FIXME: Stuff to review/include:
+% Blindly Signed Contracts: Anonymous On-Blockchain and Off-Blockchain Bitcoin Transactions
+% zcash taxability stuff
+
+\subsection{Anonymous E-Cash}\label{sec:related-work:e-cash}
+
+Chaum's seminal paper \cite{chaum1983blind} introduced blind signatures and
+demonstrated how to use them for online e-cash. Later work
+\cite{chaum1989efficient,chaum1990untraceable} introduced offline spending, where additional
+information is encoded into coins in such a way that double spending reveals
+the culprit's identity.
+
+Okamoto \cite{okamoto1995efficient} introduced the first efficient offline
+e-cash scheme with divisibility, a feature that allows a single coin to be
+spent in parts. With Okamoto's protocol, different spending operations that
+used parts of the same coin were linkable. An unlinkable version of
+divisible e-cash was first presented by Canard~\cite{canard2007divisible}.
+
+Camenisch's compact e-cash \cite{camenisch2005compact} allows wallets with $2^\ell$ coins to be stored
+and withdrawn with storage, computation and computational costs in $\mathcal{O}(\ell)$.
+Each coin in the wallet, however, still needs to be spent separately.
+
+The protocol that can currently be considered the state-of-the-art for efficient
+offline e-cash was introduced by Pointcheval et al. \cite{pointcheval2017cut}.
+It allows constant-time withdrawal of a divisible coin, and constant-time
+spending of a continuous ``chunk'' of a coin. While the pre-determined number
+of divisions of a coin is independent from the storage, bandwidth and
+computational complexity of the wallet, the exchange needs to check for
+double-spending at the finest granularity. Thus, highly divisible coins incur
+large storage and computational costs for the exchange.
+
+An e-cash system with multiple denominations with different financial values
+was proposed by Canard and Gouget~\cite{canard2006handy} in the context of a divisible
+coupon system.
+
+One of the earliest mentions of an explicit change protocol can be found in
+\cite{brickell1995trustee}. Ian Goldberg's HINDE system is another design that
+allows the merchant to provide change, but the mechanism could be abused to
+hide income from taxation.\footnote{Description based on personal
+communication. HINDE was never published, but supposedly publicly discussed at
+Financial Crypto '98.} Another online e-cash protocol with change was proposed
+by Tracz \cite{tracz2001fair}. The use of an anonymous change protocol (called
+a ``refund'' in their context) for fractional payments has also been suggested
+for a public transit fees payment system \cite{rupp2013p4r}. Change protocols
+for offline e-cash were recently proposed \cite{batten2018offline}. To the
+best of our knowledge, no change protocol with protections against tax evasion
+has been proposed so far, and all change protocols suggested so far can be
+(ab)used to make a payment into another wallet.
+
+Transferable e-cash allows the transfer of funds between customers without
+using the exchange as in intermediary \cite{fuchsbauer2009transferable}.
+
+Chaum also proposed wallets with observers \cite{chaum1992wallet} as a
+mechanism against double spending. The observer is a tamper-proof hardware
+security module that prevents double-spending, while at the same time being
+unable to de-anonymize the user.
+
+Various works propose mechanisms to selectively de-anonymize customers or
+transactions that are suspected of criminal activities
+\cite{stadler1995fair,davida1997anonymity}. Another approach suspends
+customers that were involved in a particular transaction, while keeping the
+customer anonymous \cite{au2011electronic}.
+
+One of the first formal treatments of the provable security of e-cash was given
+in \cite{damgaard2007proof}. The first complete security definition for blind
+signatures was given by Pointcheval \cite{pointcheval1996provably} and applied
+to RSA signatures later \cite{pointcheval2000security}. While the security
+proof of RSA signatures requires the random oracle model, many blind signature
+schemes are provably secure in the standard model
+\cite{izabachene2013divisible,pointcheval2017cut}. While most literature
+provides only ``human-verified'' security arguments, the security of a simple
+e-cash scheme has been successfully modeled in
+ProVerif~\cite{dreier2015formal}, albeit only in the symbolic model.
+
+\subsubsection{Implementations}
+DigiCash was the first commercial implementation of Chaum's e-cash. It
+ultimately failed to be widely adopted, and the company filed for bankruptcy in
+1998. Some details of the implementation are available
+\cite{schoenmakers1997security}. In addition to Chaum's infamously paranoid
+management style \cite{next1999digicash}, reasons for DigiCash's failure could
+have been the following:
+
+\begin{itemize}
+ \item DigiCash did not allow account-less operations. To use DigiCash,
+ customers had to sign up with a bank that natively supports DigiCash.
+ \item DigiCash did not support change or partial spending, negating a lot of
+ the convenience and security of e-cash by requiring frequent withdrawals
+ from the customer's bank account.
+ \item The technology used by DigiCash was protected by patents,
+ which stifled innovation from competitors.
+ \item Chaum's published design does not clearly limit the financial damage an
+ exchange might suffer from the disclosure of its private online signing key.
+\end{itemize}
+
+To our knowledge, the only publicly available effort to implement anonymous
+e-cash is Opencoin~\cite{dent2008extensions}. However, Opencoin is neither
+actively developed nor used, and it is not clear to what degree the
+implementation is even complete. Only a partial description of the Opencoin
+protocol is available to date.
+
+
+\subsubsection{Representing Denominations}\label{design:related-different-denominations}
+
+For GNU Taler, we chose to represent denominations of different values by a
+different public key for every denomination, together with a mapping from
+public key to financial value and auxiliary information about fees and
+expiration dates. This approach has the advantage that coins of higher denominations
+can be signed by denominations with a larger key size.
+
+Schoenmakers~\cite{schoenmakers1997security} proposes an optimized
+implementation of multiple denomination that specifically works with RSA keys,
+which encodes the denomination in the public exponent $e$ of the RSA public
+key, while the modulus $N$ stays the same for all denominations. An advantage
+of this scheme is the reduced size of the public keys for a set of
+denominations. As this encoding is specific to RSA, it would be difficult for
+future versions of this protocol to switch to different blind signature
+primitives. More importantly, factoring $N$ would lead to a compromise of all
+denominations instead of just one.
+
+Partially blind signatures can be used to represent multiple denominations
+by blindly signing the coin's serial number and including the financial value of the coin
+in the common information seen by both the signer and signee \cite{abe2000provably}.
+
+The compact e-cash scheme of Märtens~\cite{maertens2015practical} allows
+constant-time withdrawal of wallets with an arbitrary number of coins, as long
+as the number of coins is smaller than some system parameter. This approach
+effectively dispenses with the need to have different denominations.
+
+
+\subsubsection{Comparison}
+
+\newcommand\YES{\ding{51}} % {\checkmark}
+\newcommand\NO{\ding{55}}
+
+\newcommand*\rot{\multicolumn{1}{R{45}{1em}}}% no optional argument here, please!
+%\newcommand*\rot{}% no optional argument here, please!
+
+
+\newcolumntype{H}{>{\setbox0=\hbox\bgroup}c<{\egroup}@{}}
+
+\newcolumntype{R}[2]{%
+ >{\adjustbox{angle=#1,lap=\width-(#2)}\bgroup}%
+ l%
+ <{\egroup}%
+}
+
+{\footnotesize
+\begin{tabular}{r|ccccccccccc}
+&
+\rot{Year} &
+\rot{Implementation} &
+%
+\rot{Offline spending} &
+\rot{Safe aborts/backups} &
+\rot{Key expiration} &
+%
+\rot{Income transparency} &
+%
+% \rot{Withdrawal cost} & \rot{Deposit cost} &
+\rot{No trusted setup} &
+\rot{Storage for wallet} &
+\rot{Storage for exchange} &
+%
+\rot{Change/Divisibility} &
+\rot{Receipts \& Refunds}
+\\ \hline
+Chaum \cite{chaum1983blind}
+& 1983 & P
+& \NO & \NO & ?
+& ?
+% & $\log n$ & $\log n$
+& \YES & $\log n$ & $\log n$
+& \NO & \NO
+\\
+DigiCash \cite{schoenmakers1997security}
+& 1990 & P
+& \NO & \YES & \YES
+& \NO
+& \YES & $\log n$ & $\log n$
+& \NO & \NO
+\\
+Offline Chaum \cite{chaum1990untraceable}
+& 1990 & ?
+& \YES & \NO & ?
+& ?
+& \YES & $\log n$ & $\log n$
+& \NO & \NO
+\\
+Tracz \cite{tracz2001fair} % HINDE
+& 2001 & E
+& \NO & \YES & ?
+& \NO
+& \YES & $\log n$ & $\log n$
+& Online & \NO
+\\
+Compact \cite{camenisch2005compact}
+& 2005 & \NO
+& \YES & \NO & ?
+& ?
+& \YES & $\log n$ & $n$ % We're guessing trustless anonymity because not trusted setup
+& Offline & \NO
+% \\
+% Martens \cite{maertens2015}
+% & 2015 & \NO
+% & \NO & \NO & %?
+% & \NO & S & \NO
+% % & $\log n$ & $\log n$
+% & \YES & \NO & W % We're guessing trustless anonymity because not trusted setup
+% & OFF & \NO
+\\
+Divisible \cite{pointcheval2017cut}
+& 2017 & \NO
+& \YES & \NO & ?
+& ?
+& \NO & $1$ & $n$
+& Offline & \NO
+\\
+GNU Taler
+& 2017 & FS
+& \NO & \YES & \YES
+& \YES
+% & $\log n$ & $\log n$
+& \YES & $\log n$ & $\log n$
+& Online & \YES
+\\ \hline
+\end{tabular}
+}
+
+
+\begin{itemize}
+ \item \textbf{Implementation.}
+ Is there an implementation? Is it proprietary (P), experimental (E), or free software (FS).
+ \item \textbf{Offline Spending}
+ Can spending happen offline with delayed detection of double spenders, or
+ is double spending detected immediately during spending?
+ \item \textbf{Safe abort/backup.}
+ Is anonymity preserved in the presence of interrupted operations
+ or restoration from backups? Inherently conflicts with offline double
+ spending detection in all approaches that we are aware of.
+ We specify ``\YES'' also for schemes that do not explicitly treat aborts/backup,
+ but would allow a safe implementation when aborts/backups happen.
+ \item \textbf{Key expiration.}
+ We specify ``?'' for schemes that do not explicitly discuss key expiration,
+ but do not fundamentally conflict with the concept.
+ \item \textbf{Income transparency.}
+ We specify ``\YES'' if income transparency is supported, ``\NO'' if some feature of
+ the scheme conflicts with income transparency and ``?'' if it might be possible
+ to add income transparency.
+ \item \textbf{No trusted setup.}
+ In a trusted setup, some parameters and cryptographic keys are generated
+ by a trusted third party. A compromise of the trusted setup phase can mean loss
+ of anonymity.
+ \item \textbf{Storage for wallet/exchange.}
+ The expected storage for coins adding up to arbitrary value $n$ is specified,
+ with some reasonable upper bound for $n$.
+ \item \textbf{Change/Divisibility.}
+ Can customers pay without possessing exact change? If so, is it handled
+ by giving change online (Onl.) or by divisible coins that support offline
+ operation (Off.)?
+ \item \textbf{Receipts \& Refunds.}
+ The customer either can prove that they paid for
+ a contract, or they can get their (unlinkable) money back.
+ Also merchants can issue refunds for completed transactions.
+ These operations must not introduce linkability or otherwise
+ compromise the customer's anonymity.
+\end{itemize}
+
+
+\subsection{Blockchains}
+The term ``blockchain'' refers to a wide variety of protocols and systems concerned with
+maintaining a ledger---typically involving financial transactions---in a
+distributed and decentralized manner.\footnote{Even though there is a centralization tendency
+from various sources in practice \cite{walch2019deconstructing}.}
+
+The first and most prominent system that would be categorized as a
+``blockchain'' today\footnote{The paper that introduces Bitcoin does not
+mention the term ``blockchain''} is Bitcoin \cite{nakamoto2008bitcoin},
+published by an individual or group under the alias ``Satoshi Nakamoto''. The
+document timestamping service described in \cite{haber1990time} could be seen
+as an even earlier blockchain that predates Bitcoin by about 13
+years and is still in use today.
+
+As the name implies, blockchains are made up of a chain of blocks, each block
+containing updates to the ledger and the hash code of its predecessor block. The
+chain terminates in a ``genesis block'' that determines the initial state of
+the ledger.
+
+Some of the most important decisions for the design of blockchains are the following:
+\begin{itemize}
+ \item The \emph{consensus mechanism}, which determines how the participants
+ agree on the current state of the ledger.
+
+ In the simplest possible blockchain, a trusted authority would validate
+ transactions and publish new blocks as the head of the chain. In order to
+ increase fault tolerance, multiple trusted authorities can use Byzantine
+ consensus to agree on transactions. With classical Byzantine consensus
+ protocols, this makes the system robust with a malicious minority of up to
+ $1/3$ of nodes. While fast and appropriate for some applications,
+ classical Byzantine consensus only works with a known set of participants
+ and does not scale well to many nodes.
+
+ Bitcoin instead uses Proof-of-Work (PoW) consensus, where the head of the
+ chain that determines the current ledger state is chosen as the block that
+ provably took the most ``work'' to construct, including the accumulated
+ work of ancestor blocks. The work consists of finding a hash preimage $n
+ \Vert c$, where $c$ are the contents of the block and $n$ is a nonce, such
+ that the hash $H(n \Vert c)$ ends with a certain number of zeroes (as
+ determined by the difficulty derived from previous blocks). Under the
+ random oracle, the only way to find such a nonce is by trial-and-error.
+ This nonce proves to a verifier that the creator of the block spent
+ computational resources to construct it, and the correctness is easily
+ verified by computing $H(n \Vert c)$. The creator of a block is rewarded
+ with a mining reward and transaction fees for transactions within the
+ block.
+
+ PoW consensus is not final: an adversary with enough computational power
+ can create an alternate chain branching off an earlier block. Once this
+ alternative, longer chain is published, the state represented by the
+ earlier branch is discarded. This creates a potential for financial fraud,
+ where an earlier transaction is reversed by publishing an alternate history
+ that does not contain it. While it was originally believed that PoW
+ consensus process is resistant against attackers that have less than a
+ $51\%$ majority of computational power, closer analysis has shown that a
+ $21\%$ majority sufficies \cite{eyal2018majority}.
+
+ A major advantage of PoW consensus is that the participants need not be
+ known beforehand, and that Sybil attacks are impossible since consensus
+ decisions are only dependent on the available computational power, and not
+ on the number of participants.
+
+ In practice, PoW consensus is rather slow: Bitcoin can currently support
+ 3-7 transactions per second on a global scale. Some efforts have been made
+ to improve Bitcoin's efficiency \cite{eyal2016bitcoin,vukolic2015quest},
+ but overall PoW consensus needs to balance speed against security.
+
+ Proof-of-Stake (PoS) is a different type of consensus protocol for
+ blockchains, which intends to securely reach consensus without depleting
+ scarce resources such as energy for computation
+ \cite{bentov2016cryptocurrencies,kwon2014tendermint}. Blocks are created
+ by randomly selected validators, which obtain a reward for serving as a
+ validator. To avoid Sybil attacks and create economic incentives for good
+ behavior, the probability to get selected as a validator is proportional to
+ one's wealth on the respective blockchain. Realizing PoS has some
+ practical challenges with respect to economic incentives: As blocks do not
+ take work to create, validators can potentially benefit from creating
+ forks, instead of validating on just one chain.
+
+ Algorand \cite{gilad2017algorand} avoids some of the problems with PoW
+ consensus by combining some of the ideas of PoW with classical Byzantine
+ consensus protocols. Their proposed system does not have any incentives
+ for validators.
+
+ Avalanche \cite{rocket2018snowflake} has been proposed as a scalable
+ Byzantine Consensus algorithm for use with blockchains. It is based on a
+ gossip protocol and is only shown to work in the synchronous model.
+
+ \item Membership and visibility. Blockchains such as Bitcoin or Ethereum with
+ public membership and public visibility are called \emph{permissionless blockchains}.
+ Opposed to that, \emph{permissioned} blockchains have been proposed for usage in
+ banking, health and asset tracking applications \cite{androulaki2018hyperledger}.
+
+ \item Monetary policy and wealth accumulation.
+ Blockchains that are used as cryptocurrencies come with their own monetary
+ policy. In the case of Bitcoin, the currency supply is limited, and due to
+ difficulty increase in mining the currency is deflationary. Other
+ cryptocurrencies such as duniter\footnote{See
+ \url{https://duniter.org/}.} have been proposed with built-in rules for
+ inflation, and a basic income mechanism for participants.
+
+ \item Expressivity of transactions. Transactions in Bitcoin are small programs
+ in a stack-based programming language that are guaranteed to terminate.
+ Ethereum \cite{wood2014ethereum} takes this idea further and allows smart contracts with
+ Turing-complete computation and access to external oracles.
+
+ \item Governance. Blockchain governance \cite{reijers2016governance,levy2017book} is a
+ topic that received relatively little attention so far. As blockchains
+ interact with existing legal and social systems across national borders,
+ different sources of ``truth'' must be reconciled.
+
+ Furthermore, consensus is not just internal to the operation of
+ blockchains, but also external in the development of the technology.
+ Currently small groups of developers create the rules for the operation of
+ blockchains, and likewise have the power to change them. There is
+ currently very little research on social and technological processes to
+ find a ``meta-consensus'' on the rules that govern such systems, and how
+ these rules can be adapted and changed in a consensus process.
+
+ \item Anonymity and Zero-Knowledge Proofs. Bitcoin transactions are only
+ pseudoymous, the full transaction history is publicly available and leads
+ to reduced anonymity in practice \cite{reid2013analysis}. Tumblers
+ \cite{bonneau2014mixcoin,heilman2017tumblebit} are an approach to increase
+ the anonymity in Bitcoin-style cryptocurrencies by creating additional
+ transactions to cover up the real owner and sources of funds. While newer tumblers
+ such as TumbleBit \cite{heilman2017tumblebit} provide rather strong security guarantees,
+ mixing incurs transaction costs.
+
+ Some cryptocurrencies have direct support for anonymous transactions
+ \cite{sun2017ringct}. ZeroCash \cite{bensasson2014zerocash} uses
+ zero-knowledge proofs to hide the sender, receiver and amount of a
+ transaction. While ZeroCash currently relies on a trusted setup for
+ unforgeability of its currency, more recent proposals dispense with that
+ requirement \cite{ben2018scalable,wahby2018doubly}. As the anonymity
+ provided by ZeroCash facilitates tax evasion and use in other crimes, an
+ additional, optional layer for privacy-preserving policy for taxation,
+ spending limits and identity escrow has been proposed
+ \cite{garman2016accountable}.
+\end{itemize}
+
+Practical guidance on what kind of blockchain is appropriate for an
+application, and if a blockchain is required in the first place, can be found
+in \cite{wust2017you}.
+
+
+\subsection{Approaches to Micropayments}
+Micropayments refer to payments of very small value. Microtransactions would
+not be feasible in traditional payment systems due to high transaction costs,
+which might even exceed that value that is to be transferred.
+
+\subsubsection{Peppercoin}
+
+Peppercoin~\cite{rivest2004peppercoin} is a microdonation protocol.
+The main idea of the protocol is to reduce transaction costs by
+minimizing the number of transactions that are processed directly by
+the exchange. Instead of always paying, the customer ``gambles'' with the
+merchant for each microdonation. Only if the merchant wins, the
+microdonation is upgraded to a macropayment to be deposited at the
+exchange. Peppercoin does not provide customer-anonymity. The proposed
+statistical method by which exchanges detect fraudulent cooperation between
+customers and merchants at the expense of the exchange not only creates
+legal risks for the exchange, but would also require that the exchange learns
+about microdonations where the merchant did not get upgraded to a
+macropayment. It is therefore unclear how Peppercoin would actually
+reduce the computational burden on the exchange.
+
+\subsubsection{Tick Payments}
+% FIXME: also works off-line
+Tick payments were proposed by Pedersen \cite{pedersen1996electronic} as a
+general technique to amortize the cost for small, recurring payments to the
+same payee. The payer first makes an up-front deposit as one larger payment
+that involves the payment processor. To make a micropayment, the payer sends a
+message to the payee that authorizes the payee to claim a fraction of this
+deposit. Each further micropayment simply increases the fraction of the
+deposit that can be claimed, and only requires communication between payer and
+payee. The payee only needs to show the last message received from the payer
+to the payment processor in order to receive the accumulated amounts received
+through tick payments.
+
+\subsubsection{Payment Channels and Lightning Network}
+The Lightning Network \cite{poon2016bitcoin} is a proposed payment system that
+is meant to run on top of Bitcoin and enable faster, cheaper
+(micro-)transactions. It is based on establishing \emph{payment channels}
+between Bitcoin nodes. A payment channel is essentially a tick payment where
+the deposit and settlement happens on a blockchain. The goal of the
+Lightning network is to route a payment between two arbitrary nodes by finding a
+path that connects the two routes through payment channels. The protocol is
+designed in such a way that a node on the path between the initial sender and
+final receiver can only receive a payment on a payment channel if it correctly
+forwards it to the next node.
+
+Experimental deployments of the Lightning network recently suffered heavily
+from denial-of-service attacks. % FIXME: citation needed!
+
+BOLT \cite{green2016bolt} is an anonymous payment channel for ZeroCash, and is
+intended to be used as a building block for a second-layer payment protocol
+like the Lightning Network.
+
+\subsubsection{Side-chains}
+% FIXME: what about polkadot?
+Side-chains are an alternative approach to improve the scalability of
+blockchains, intended to be useful in conjunction with arbitrary smart
+contracts. The approach currently developed by the Ethereum project is
+described in the Plasma white paper \cite{poon2017plasma}. Side-chains are
+separate blockchains, possibly with different rules and even consensus
+protocols than the main chain. Side-chains operate in parallel to the main
+Ethereum chain, and regularly publish ``pointers'' to the current head of the
+sidechain on the main chain. Funds can be moved from the main chain to the
+side-chain, and subsequently be moved off the side-chain by performing an
+``exit'', during which the main chain verifies claims to funds on the
+side-chain according to the side-chain's rules.
+
+At the time of writing, Plasma is not yet implemented. Potential problems with
+Plasma include the high costs of exits, lack of access to data needed to verify
+exit claims, and associated potential for denial-of-service attacks.
+
+
+%\subsection{Other Payment Systems}
+
+%\subsubsection{Credit Card Payments}
+
+\subsection{Walled Garden Payment Systems}
+
+Walled garden payment systems offer ease of use by processing payments using a
+trusted payment service provider. Here, the customer authenticates to the
+trusted service, and instructs the payment provider to execute a transaction on
+their behalf. In these payment systems, the provider basically acts like a
+bank with accounts carrying balances for the various users. In contrast to
+traditional banking systems, both customers and merchants are forced to have an
+account with the same provider. Each user must take the effort to establish
+his identity with a service provider to create an account. Merchants and
+customers obtain the best interoperability in return for their account creation
+efforts if they start with the biggest providers. As a result, there are a few
+dominating walled garden providers, with AliPay, ApplePay, GooglePay,
+SamsungPay and PayPal being the current oligopoly.
+%In this paper, we
+%will use PayPal as a representative example for our discussion of these payment
+%systems.
+
+As with card payment systems, these oligopolies are politically
+dangerous~\cite{crinkey2011rundle}, and the lack of competition
+can result in excessive profit taking that may require political
+solutions~\cite{guardian2015cap} to the resulting market
+ failure. The use of non-standard proprietary interfaces to
+the payment processing service of these providers serves to reinforce
+the customer lock-in.
+
+%TODO: discuss popularity/abundance of digital wallets in other countries (India!)
+%and different requirements (connectivity)
+
+%\subsubsection{Ripple}
+
+\subsection{Web Integration}
+
+Finally, we will discuss software solutions to web payments. We
+consider other types of payments, including general payments and in
+particular hardware solutions as out of scope for this thesis.
+
+\subsubsection{Web Payments API}
+The Web Payments API\footnote{See \url{https://www.w3.org/TR/payment-request/}}
+is a JavaScript API offered by browsers, and currently still under development.
+It allows merchant to offer a uniform checkout experience across different
+payment systems. Unlike GNU Taler, the Web Payments API is only concerned with
+aspects of the checkout process, such as display of a payment request,
+selection of a shipping address and selection of a payment method.
+
+Currently only basic-card is supported across popular browsers.
+
+The Payment Handler API\footnote{See
+\url{https://www.w3.org/TR/payment-handler/}} supports the registration of
+user-defined payment method handlers. Unfortunately the only way to add
+payment method handlers is via an HTTPS URL. This leaks all information to the
+payment service provider and precludes the implementation of privacy-preserving
+payment system handlers.
+
+In order to integrate Taler as a payment method, browsers would need to either
+offer Taler as a native, built-in payment method or allow an extension to
+register web payment handlers.
+
+
+The Web Payments Working Group discontinued work on a HTTP-based API for
+machine-to-machine payments.\footnote{See
+\url{https://www.w3.org/TR/webpayments-http-api/}.}
+
+\subsubsection{Payment Pointers}
+Payment pointers are a proposed standard syntax for accounts that are able to
+receive payments. Unlike \texttt{payto://} URIs ( discussed in
+Section~\ref{implementation:wire-method-identifiers}), payment pointers do not
+follow the generic URI syntax and only specify a \emph{pointer} to the
+receiver's bank account in form of a HTTPS URI. Payment pointers do not
+specify any mechanism for the payment, but instead direct the user's browser to
+a website to carry out the payment.
+
+\subsubsection{3-D Secure}
+3-D Secure is a complex and widely deployed protocol that is intended to add an
+additional security layer on top of credit and debit card transactions.
+
+The 3-D Secure protocol requires the use of inline frames on the HTML page of
+the merchant for extended verification/authentication of the user. This makes
+it hard or sometimes -- such as when using a mobile browser -- even impossible
+to tell whether the inline frame is legitimate or an attempt to steal
+information from the user.
+
+Traditionally, merchants bear most of the financial risk, and a key
+``feature'' of the 3DS process compared to traditional card payments
+is to shift dispute {\em liability} to the issuer of the card---who
+may then try to shift it to the customer \cite[\S2.4]{3DSsucks}.
+%
+% online vs offline vs swipe vs chip vs NFC ???
+% extended verification
+%
+Even in cases where the issuer or the merchant remain legally first in
+line for liabilities, there are still risks customers incur from the
+card dispute procedures, such as neither them nor the payment
+processor noticing fraudulent transactions, or them noticing
+fraudulent transactions past the {\em deadline} until which their bank
+would reimburse them. The customer also typically only has a
+merchant-generated comment and the amount paid in their credit card
+statement as a proof for the transaction. Thus, the use of credit
+cards online does not generate any cryptographically {\em verifiable}
+electronic receipts for the customer, which theoretically enables
+malicious merchants to later change the terms of the contract.
+
+Beyond these primary issues, customers face secondary risks of
+identity theft from the personal details exposed by the authentication
+procedures. In this case, even if the financial damages are ultimately
+covered by the bank, the customer always has to deal with the procedure
+of {\em notifying} the bank in the first place. As a result,
+customers must remain wary about using their cards, which limits their
+online shopping~\cite[p. 50]{ibi2014}.
+
+\subsubsection{Other Proprietary Payment APIs}
+The Electronic Payment Standard URI scheme \texttt{epspayment:} is a
+proprietary/unregistered URI scheme used by predominantly Austrian banks and
+merchants to trigger payments from within websites on mobile devices.
+Merchants can register an invoice with a central server. The user's banking
+app is associated with the \texttt{epspayment} URI scheme and will open to
+settle the invoice. It lies conceptually between \texttt{payto://} and
+\texttt{taler:pay} (see Section~\ref{sec:loose-browser-integration}). A
+technical problem of \texttt{epspayment} is that when a user has multiple bank
+accounts at different banks that support \texttt{epspayment}, some platforms
+decide non-deterministically and without asking the user which application to
+launch. Thus, a user with two banking applications on their phone can often not
+chose which bank account is used for the payment. If \texttt{payto} were
+widely supported, the problem of registering/choosing bank accounts for payment
+methods could be centrally addressed by the browser / operating system.
+
+PayPal is a very popular, completely proprietary payment system provider. Its offer-based
+API is similar in the level of abstraction to Taler's reference merchant backend API.
+
+LaterPay is a proprietary payment system for online content as well as
+donations. It offers similar functionality to session-bound payments in Taler.
+LaterPay does not provide any anonymity.
+
+
+% FIXME: mention these
+% Stripe
+% paydirekt
+% mention this somewhere: https://www.focus.de/finanzen/banken/paydirekt-so-reagieren-kunden-auf-die-sparkassen-plaene_id_7511393.html
+% but not in this section. good argument for anonymity
diff --git a/doc/system/taler/implementation.tex b/doc/system/taler/implementation.tex
new file mode 100644
index 000000000..e7d8b9dc7
--- /dev/null
+++ b/doc/system/taler/implementation.tex
@@ -0,0 +1,2432 @@
+\chapter{Implementation of GNU Taler}\label{chapter:implementation}
+
+This chapter describes the implementation of GNU Taler in detail. Concrete
+design decisions, protocol details and our reference implementation are
+discussed.
+
+We implemented the GNU Taler protocol in the context of a payment system for
+the web, as shown in Figure~\ref{fig:taler-arch}. The system was designed for
+real-world usage with current web technologies and within existing
+financial systems.
+
+The following technical goals and constraints influenced the design of the
+concrete protocol and implementation:
+\begin{itemize}
+ \item The implementation should allow payments in browsers with hardened
+ security settings. In particular, it must be possible to make a payment
+ without executing JavaScript on a merchant's website and without having to
+ store (session-)cookies or requiring a login.
+ \item Cryptographic evidence should be available to all parties in case of a
+ dispute.
+ \item In addition to the guarantees provided by the GNU Taler protocol, the
+ implementation must take care to not introduce additional threats to
+ security and privacy. Features that trade privacy for convenience should
+ be clearly communicated to the user, and the user must have the choice to
+ deactivate them. Integration with the web should minimize the potential
+ for additional user tracking.
+ \item The integration for merchants must be simple. In particular, merchants
+ should not have to write code involving cryptographic operations or have to
+ manage Taler-specific secrets in their own application processes.
+ \item The web integration must not be specific to a single browser platform, but
+ instead must be able to use the lowest common denominator of what is
+ currently available. User experience enhancements supported for only
+ specific platforms are possible, but fallbacks must be provided for other
+ platforms.
+ \item URLs should be clean, user-friendly and must have the expected
+ semantics when sharing them with others or revisiting them after a session
+ expired.
+ \item Multiple currencies must be supported. Conversion between
+ different currencies is out of scope.
+ \item The implementation should offer flexibility with regards to what
+ context or applications it can be used for. In particular, the
+ implementation must make it possible to provide plugins for different
+ underlying banking systems and provide hooks to deal with different
+ regulatory requirements.
+ \item The implementation must be robust against network failures and crash
+ faults, and recover as gracefully as possible from data loss. Operations
+ must be idempotent if possible, e.g., accidentally clicking a payment button twice should
+ only result in one payment, and refreshing a page should not lead to
+ failures in the payment process.
+ \item Authorization should be preferred to authentication. In particular,
+ there should be no situations in which the user must enter confidential
+ information on a page that cannot be clearly identified as secure.
+ \item No flickering or unnecessary redirects. To complete a payment, the
+ number of request, especially in the user's navigation context, should be
+ minimized.
+ \item While the implementation should integrate well with browsers, it must
+ be possible to request and make payments without a browser. This makes at
+ least part of the implementation completely independent of the extremely
+ complex browser standards, and makes Taler usable for machine-to-machine
+ payments.
+ %\item Backwards compatibility (with what?)
+\end{itemize}
+
+We now recapitulate how a GNU Taler payment works, with some more details
+specific to the implementation.
+
+By instructing their bank to send money to an exchange, the customer creates a
+(non-anonymous) balance, called a \emph{reserve}, at the exchange. Once the
+exchange has received and processed the bank transfer, the customer's
+\emph{wallet} automatically \emph{drains} the reserve by withdrawing coins from
+it until the reserve is empty. Withdrawing immediately before a purchase should
+be avoided, as it decreases the customer's anonymity set by creating a
+correlation between the non-anonymous withdrawal and the spending.
+
+To withdraw coins from the exchange, the customer's wallet authenticates itself
+using an Ed25519 private key for the customer's reserve. The customer must
+include the corresponding reserve public key in the payment instruction from
+the customer's bank to the exchange's bank that funded their reserve. With a
+bank that directly supports Taler on their online banking website, this process
+is streamlined for the user, since the wallet automatically creates the key
+pair for the reserve and adds the public key to the payment instruction.
+
+
+While browsing a merchant's website, the website can signal the wallet to
+request a payment from a user. The user is then asked to confirm or reject this
+proposal. If the user accepts, the wallet spends coins with the merchant. The
+merchant deposits coins received from the customer's wallet at the exchange.
+Since bank transfers are usually costly, the exchange delays and aggregates
+multiple deposits into a bigger wire transfer. This allows GNU Taler to be
+used even for microtransactions of amounts smaller than usually handled by the
+underlying banking system.
+
+\begin{figure}
+ \includegraphics[width=\columnwidth]{taler-arch-full.pdf}
+ \caption[Components of GNU Taler in the context of a banking system.]{The different components of the Taler system in the
+ context of a banking system providing money creation,
+ wire transfers and authentication. (Auditor omitted.)}
+ \label{fig:taler-arch-full}
+\end{figure}
+
+As shown in Figure~\ref{fig:taler-arch-full}, the merchant is internally split
+into multiple components. The implementation of the Taler protocol and
+cryptographic operations is isolated into a separate component, called the
+\emph{merchant backend}, which the merchant accesses through an API or software
+development kit (SDK) in the programming language of their choice.
+
+Our implementations of the exchange (70,000 LOC) and merchant backend
+(20,000 LOC) are written in C using PostgreSQL as the database and
+libgcrypt for cryptographic operations. The \emph{wallet} (10,000
+LOC) is implemented in TypeScript as a cross-browser extension using
+the WebExtensions API, which is available for a majority of widely
+used browsers. It also uses libgcrypt (compiled to JavaScript) for
+cryptographic operations as the required primitives are not yet
+natively supported by web browsers. Sample merchant websites (1,000
+LOC) and an example bank (2,000 LOC) with tight Taler integration are
+provided in Python.
+
+The code is available at \url{https://git.taler.net/} and a demo
+is publicly available at \url{https://demo.taler.net/}.
+
+\section{Overview}
+
+We provide a high-level overview over the implementation,
+before discussing the respective components in detail.
+
+\subsection{Taler APIs}
+The components of Taler communicate over an HTTP-based, RESTful\footnote{
+Some REST purists might disagree, because the Taler APIs do not follow
+all REST principles religiously. In particular, the HATEOAS principle is not followed.
+} \cite{fielding2000architectural}
+API. All request payloads and responses are JSON \cite{rfc8259} documents.
+
+Binary data (such as key material, signatures and hashes) is encoded as a
+base32-crockford \cite{crockford_base32} string. Base32-crockford is a simple,
+case-insensitive encoding of binary data into a subset of the ASCII alphabet
+that encodes 5 bits per character. While this is not the most space-efficient
+encoding, it is relatively resilient against human transcription errors.
+
+Financial amounts are treated as fixed-point decimal numbers. The
+implementation internally uses a pair of integers $(v,f)$ with value part $0
+\le v \le 2^{52}$ and fractional part $0 \le f < 10^8$ to represent the amount
+$a = v + f\cdot 10^{-8}$. This representation was chosen as the smallest
+representable amount is equal to one Satoshi (the smallest representable amount
+in Bitcoin), and the largest possible value part (besides being large enough
+for typical financial applications) is still accurately representable in 64-bit
+IEEE 754 floating point numbers. These constraints are useful as some
+languages such as JavaScript\footnote{Big integers are currently in the process
+of being added to the JavaScript language standard.} provide IEEE 753 floating
+point numbers as the only numeric type. More importantly, fixed-point decimal
+numbers allow exact representation of decimal values (say \EUR{0.10}), which
+is not possible with floating point numbers but essential in financial applications.
+
+Signatures are made over custom binary representations of the respective
+values, prefixed with a 64-bit tag consisting of the size of the message (32
+bits) and an integer tag (32 bits) uniquely identifying the purpose of the message.
+To sign a free-form JSON object, a canonical representation as a string is
+created by removing all white space and sorting objects' fields.
+
+In the future, more space-efficient representations (such as BSON\footnote{http://bsonspec.org/} or CBOR \cite{rfc7049})
+could be used. The representation can be negotiated between client and server
+in a backwards-compatible way with the HTTP ``Accept'' header.
+
+% signatures!
+
+\subsection{Cryptographic Algorithms}
+The following cryptographic primitives are used by Taler:
+\begin{itemize}
+ \item SHA512 \cite{rfc4634} as a cryptographic hash function
+ \item Ed25519 \cite{bernstein2006curve25519} for non-blind signing operations
+ \item Curve25519 \cite{bernstein2006curve25519} for the refreshing operation
+ \item HKDF \cite{rfc5869} as a key derivation function for the refreshing operation
+ \item FDH-RSA blind signatures \cite{bellare2003onemore}
+\end{itemize}
+
+We chose these primitives as they are simple, cheap enough and relatively well
+studied. Note that other signature schemes that have the syntax and properties
+described in Section~\ref{sec:crypto:instantiation}, such as
+\cite{boldyreva2003threshold}, could be used instead of FDH-RSA.
+
+\subsection{Entities and Public Key Infrastructure}
+
+\begin{figure}
+ \includegraphics[width=\textwidth]{diagrams/taler-diagram-signatures.png}
+ \caption[Entities/PKI in Taler]{Entities/PKI in Taler. Solid arrows denote signatures, dotted arrows denote blind signatures.}
+\end{figure}
+
+The public key infrastructure (PKI) used by Taler is orthogonal to the PKI used
+by TLS \cite{rfc5246}. While TLS is used as the transport layer for Taler API
+messages, we do not rely on TLS for authenticity or integrity of API queries
+and responses. We do rely on TLS for the confidentiality of digital business
+contracts and the authenticity, integrity and confidentiality of digital
+product delivery. For the anonymity properties to hold, the customer must
+access the merchant and exchange through an anonymity layer (approximated
+by practical implementations like Tor \cite{dingledine2004tor}).
+
+In the case of merchants, we cannot use a trusted auditor or exchange as a
+trust anchor, since merchants are not required to register within our PKI to
+accept Taler payments. Here we rely on TLS instead: The merchant is required
+to include their Taler-specific merchant public key in their TLS certificate.
+If a merchant fails to do this, the wallet will show a warning when asking the
+user to confirm a payment.
+
+\subsubsection{Auditor}
+Auditors serve as trust anchors for Taler, and are identified by a single Ed25519 public key.
+Wallet implementations come with a pre-defined list of trusted auditors, similar to the certificate
+store of browsers or operating systems.
+
+\subsubsection{Exchange}
+An exchange is identified by a long term Ed25519 master key and the exchange's
+base URL. The master key is used as an offline signing key, typically stored
+on an air-gapped machine. API requests to the exchange are made by appending
+the name of the endpoint to the base URL.
+
+The exchange uses the master key to sign the following data offline:
+\begin{itemize}
+ \item The exchange's online Ed25519 signing keys. The online signing keys
+ are used to sign API responses from the exchange. Each signing key has a
+ validity period.
+ \item The denominations offered by the exchange (explained further in Section~\ref{sec:implementation:denoms}).
+ \item The bank accounts supported by the exchange (for withdrawals and deposits) and associated fees.
+\end{itemize}
+
+% FIXME: maybe put this later?
+The \texttt{<base-url>/keys} HTTP endpoint of the exchange is used by wallets
+and merchants to obtain the exchange's signing keys, currently offered
+denominations and other details. In order to reduce traffic, clients can also
+request only signing keys and denominations that were created after a specific
+time. The response to \texttt{/keys} is signed by a currently active signing
+key, so that customers would have proof in case the exchange gave different sets of
+denomination keys to different customers in an attempt to deanonymize them.
+
+
+\begin{figure}
+ \begin{multicols}{2}
+ \lstinputlisting[language=C,basicstyle=\ttfamily\tiny,numbers=left]{taler/snippet-keys.txt}
+ \end{multicols}
+ \caption{Example response for /keys}
+\end{figure}
+
+
+\subsubsection{Coins and Denominations}\label{sec:implementation:denoms}
+
+Denominations are the RSA public keys used to blindly sign coins of a fixed amount, together with information about their
+validity and associated fees. The following information is signed by the exchanges master key for every denomination:
+\begin{itemize}
+ \item The RSA public key.
+ \item The start date, after which coins of this denomination can be withdrawn and deposited.
+ \item The withdraw expiration date, after which coins cannot be withdrawn anymore, must be after the start date.
+ \item The deposit expiration date, after which coins cannot be deposited anymore, must be after the withdraw expiration date.
+ \item The legal expiration date, after which the exchange can delete all records about operations with coins of this denominations,
+ must be (typically quite a long time!) after the deposit expiration date.
+ \item The fees for a withdraw, deposit, refresh and refund operation with this coin, respectively.
+\end{itemize}
+
+\begin{figure}
+ \centering
+ \includegraphics[width=0.7\textwidth]{diagrams/taler-diagram-denom-expiration.png}
+ \caption{A denomination's lifetime.}
+\end{figure}
+
+An exchange can be audited by zero, one or multiple auditors. An auditor must
+monitor all denominations currently offered by the exchange, and an audit of a
+subset of denominations is not intended in the current design. To allow
+customers of an exchange to confirm that it is audited properly, the auditor
+signs an auditing request from the exchange, containing basic information about
+the exchange as well as all keys offered during the auditing period. In
+addition to the full auditing request, the auditor also signs an individual
+certificate for each denomination individually, allowing clients of the
+exchange to incrementally verify newly offered denominations.
+
+\subsubsection{Merchant}
+The merchant has one Ed25519 key pair that is used to sign responses to the
+customer and authenticate some requests to the exchange. Depending on the
+legislation that applies to a particular GNU Taler deployment, merchants might
+not need to establish an a priori relationship with the exchange, but instead
+send their bank account information during or after the first deposit of a
+payment from a customer.
+
+% FIXME: we never write here that the merchant accepts payments from all trusted auditors/exchanges
+% automatically
+% FIXME: citation for this?
+% FIXME: are there jurisdictions where KYC would apply to the exchange's customer?
+In some jurisdictions, exchanges are required to follow know-your-customer
+(KYC) regulations and to verify the identity of merchants \cite{arner2018identity} using that particular
+exchange for deposits. Typically, the identity of a merchant only has to be
+verified if a merchant exceeds a certain threshold of transactions in a given
+time span. As the KYC registration process can be costly to the exchange, this
+requirement is somewhat at odds with merchants accepting payments from all
+exchanges audited by a trusted auditor, since KYC registration needs to be done
+at every exchange separately. It is, however, unavoidable to run a legally
+compliant payment system.
+
+A merchant is typically configured with a set of trusted auditors and
+exchanges, and consequently accepts payments with coins of denominations from a
+trusted exchange and denominations audited by a trusted auditor.
+
+In order to make the deployment of Taler easier and more secure, the parts that
+deal with the merchant's private key and cryptographic operations are isolated
+into a separate service (the merchant backend) with a well-defined RESTful HTTP API.
+This concept is similar to payment gateways used commonly for credit card
+payments. The merchant backend can be deployed on-premise by the online shop,
+or run by a third party provider that is fully trusted by the merchant.
+
+\subsubsection{Bank}
+Since the banks are third parties that are not directly part of Taler, they do
+not participate directly in Taler's PKI.
+
+\subsubsection{Customer}
+Customers are not registered with an exchange, instead they use the private
+keys of reserves that they own to authenticate with the exchange. The exchange
+knows the reserve's public key from the subject/instruction data of the wire
+transfer. Wire transfers that do not contain a valid public key are
+automatically reversed.
+
+
+\subsection{Payments}
+
+\newlength{\maxheight}
+\setlength{\maxheight}{\heightof{\small W}}
+\newcommand{\bffmt}[1]{%
+ \footnotesize
+ \centering
+ \raisebox{0pt}[\maxheight][0pt]{#1}%
+}
+
+\begin{figure}
+\centering
+\begin{bytefield}[bitwidth=0.2em,bitheight=3ex,boxformatting=\bffmt]{128}
+ \bitheader{0,31,63,95,127} \\
+ \bitbox{32}{size} & \bitbox{32}{purpose} & \bitbox{64}{timestamp} \\
+ \wordbox{2}{merchant public key} \\
+ \wordbox{4}{contract terms hash} \\
+ \bitbox{64}{deposit deadline} & \bitbox{64}{refund deadline} \\
+ \wordbox{4}{KYC / account info hash}
+\end{bytefield}
+\caption{The contract header that is signed by the merchant.}
+\end{figure}
+
+\begin{figure}
+\centering
+\begin{bytefield}[bitwidth=0.2em,bitheight=3ex,boxformatting=\bffmt]{128}
+ \bitheader{0,31,63,95,127} \\
+ \bitbox{32}{size} & \bitbox{32}{purpose} & \bitbox{64}{timestamp} \\
+ \wordbox{4}{contract header hash} \\
+ \wordbox{2}{coin public key} \\
+ \bitbox[lrt]{128}{contributed amount} \\
+ \bitbox[lrb]{64}{} & \bitbox[ltr]{64}{} \\
+ \bitbox[lrb]{128}{deposit fee} \\
+\end{bytefield}
+\caption{The deposit permission signed by the customer's wallet.}
+\end{figure}
+
+
+Payments in Taler are based on \emph{contract terms}, a JSON object that
+describes the subject and modalities of a business transaction. The
+cryptographic hash of such a contract terms object can be used as a globally
+unique identifier for the business transaction. Merchants must sign the
+contract terms before sending them to the customer, allowing a customer to
+prove in case of a dispute the obligations of the merchant resulting from the
+payment.
+
+Unless a third party needs to get involved in a dispute, it is sufficient (and
+desirable for data minimization) that only the merchant and the customer know
+the full content of the contract terms. The exchange, however, must still
+know the parts of the contract terms that specify payment modalities, such as
+the refund policy, micropayment aggregation deadline and the merchant's KYC
+registration data (typically a hash to prove the KYC enrollment of the
+merchant).
+
+Thus, the merchant's signature is made over the \emph{contract header},
+which contains the contract terms hash, as well as the payment modalities.
+
+In addition to the data provided by the merchant, the contract terms contain a
+\emph{claim\_pub} field whose value is provided by the customer.
+This field is an Ed25519 public key, and the customer can use the corresponding
+private key to prove that they have indeed obtained the individual contract
+terms from the merchant, and did not copy contract terms that the merchant gave
+to another customer. Note that this key is not a permanent identity of the
+customer, but should be freshly generated for each payment.
+
+The signed contract header is created by the merchant's backend from an
+\emph{order}, which is the ``blueprint'' for the contract terms. The order is
+generated by the merchant's frontend and contains a subset of the data
+contained in the contract terms. Missing data (in particular the merchant's
+bank account information, public key and accepted auditors/exchanges) and
+the claim public key obtained from the customer is automatically added by the merchant
+backend. This allows applications to process payments without having to
+specify Taler-internal details. In fact, the smallest possible order only
+needs to contain two fields: the amount to be paid and a human-readable
+summary of the payment's subject.
+
+An order contains an \emph{order ID}, which is an identifier that is unique
+within a given merchant and can be a human-friendly identifier such as a
+booking number. If the order ID is not manually provided, it is automatically
+filled in by the merchant backend. It can be used to refer to the payment
+associated with the order without knowing the contract terms hash, which is
+only available once the customer has provided their claim public key.
+
+To initiate a payment, the merchant sends the customer an \emph{unclaimed}
+contract terms URL. The customer can download and thereby claim ownership of
+the contract by appending their claim public key $p$ as a query parameter to the unclaimed
+contract terms URL and making an HTTP \texttt{GET} request to the resulting URL.
+The customer must then verify that the resulting contract terms are signed
+correctly by the merchant and that the contract terms contain their claim public key $p$.
+A malicious customer could try to claim other customers' contracts by guessing
+contract term URLs and appending their own claim public key. For products that have
+limited availability, the unclaimed contract URL must have enough entropy so
+that malicious customers are not able to guess them and claim them before the
+honest customer.\footnote{Note that this URL cannot be protected by a session
+cookie, as it might be requested from a different session context than the
+user's browser, namely in the wallet.}
+
+% FIXME: put this in a sidebox?
+To give an example, an online shop for concert tickets might allow users to put
+themselves on a waiting list, and will send them an email once a ticket
+becomes available. The contract terms URL that allows the customer to purchase
+the ticket (once they have visited a link in this email), should contain an
+unguessable nonce, as otherwise an attacker might be able to predict the URL
+and claim the contract for the concert ticket before the customer's wallet can.
+
+In order to settle the payment, the customer must sign a \emph{deposit
+permission} for each coin that comprises the payment. The deposit permission
+is a message signed by the coin's private key, containing
+\begin{itemize}
+ \item the amount contributed by this coin to the payment,
+ \item the merchant's public key
+ \item the contract header together with the merchant's signature on it,
+ \item the time at which the deposit permission was signed.
+\end{itemize}
+
+After constructing the deposit permissions for a contract, the customer sends
+them to the merchant by doing an HTTP \texttt{POST} request to the
+\texttt{pay\_url} indicated by the merchant in the contract terms. The
+merchant individually \emph{deposits} each deposit permission with the
+exchange.
+
+The merchant responds with a payment confirmation to the customer after it has
+successfully deposited the customer's coins with the exchange. The payment
+confirmation can be used by the customer to prove that they completed the
+payment before the payment deadline indicated in the contract terms.
+
+Note that the depositing multiple coins with the exchange deliberately does not
+have transactional semantics. Instead, each coin is deposited in an individual
+transaction. This allows the exchange to be horizontally scaled (as discussed
+in Section~\ref{sec:implementation-improvements}) more easily, as deposit
+transaction might otherwise have to span multiple database shards.
+
+The lack of transactional semantics, however, means that it must be possible to
+recover from partially completed payments. There are several cases: If one of
+the coins that the customer submitted as payment to the merchant is invalid
+(e.g., because the wallet's state was restored from a backup), the customer can
+re-try the partially completed payment and provide a different coin instead.
+If that is not possible or desired by the customer, the merchant may voluntarily give a
+refund on the coins that have been previously deposited. The reference
+implementation of the merchant backend offers refunds for partially completed
+payments automatically.
+
+% FIXME: explain why!
+If refunds were disabled for the payment, the merchant does not cooperate in
+giving refunds for a partially completed payment, or becomes unresponsive after
+partially depositing the customer's coin, the customer has two options: They
+can either complete the deposits on the merchant's behalf, and then use the
+deposit permissions to prove (either to the merchant or to a court) that they
+completed the payment.
+
+% FIXME: put this in info box?
+Another possibility would be to allow customers to request refunds for partially
+completed payments themselves, directly from the exchange.
+This requires that the merchant additionally
+includes the amount to be paid for the contract in the contract header, as the
+exchange needs to know that amount to decide if a payment with multiple coins
+is complete. We do not implement this approach, since it implies that the
+exchange always learns the exact prices of products that the merchant sells, as
+opposed to just the merchant's total revenue.
+
+The customer could also reveal the contract terms to the exchange to prove that
+a payment is incomplete, but this is undesirable for privacy reasons, as the
+exchange should not learn about the full details of the business agreement
+between customer and merchant.
+
+\subsection{Resource-based Web Payments}
+In order to integrate natively with the concepts and architecture of the web,
+Taler supports paying for a web resource in the form of a URL. In fact all
+Taler contract terms contain a \emph{fulfillment URL}, which identifies the
+resource that is being paid for. If the customer is not paying for a digital
+product (such as an movie, song or article), the fulfillment URL can point to a
+confirmation page that shows further information, such as a receipt for a
+donation or shipment tracking information for a physical purchase. A
+fulfillment URL does not necessarily refer to a single item, but could also
+represent a collection such as a shopping basket.
+
+The following steps illustrate a typical payment with the online shop
+\nolinkurl{alice-shop.example.com}.
+
+\newcommand{\contl}[0]{\mbox{\textcolor{blue}{$\hookrightarrow$}\space}}
+
+\lstdefinelanguage{none}{
+ identifierstyle=
+}
+\lstdefinestyle{myhttp}{
+ breaklines=true,
+ breakindent=3em,
+ escapechar=*,
+ postbreak=\contl,
+ basicstyle=\ttfamily,
+ showspaces=true,
+}
+
+\begin{enumerate}
+ \item The user opens the shop's page and navigates to a paid resource, such
+ as \nolinkurl{https://alice-shop.example.com/essay-24.pdf}.
+ \item The shop sends a response with HTTP status ``402 Payment Required''
+ with the headers (\contl marks a continued line)
+\begin{lstlisting}[style=myhttp]
+Taler-Contract-Url: https://alice-shop.example.com/*\break\contl*contract?product=essay-24.pdf
+Taler-Resource-Url: https://alice-shop.example.com/*\break\contl*essay-24.pdf
+\end{lstlisting}
+ \item Since the user's wallet does not yet contain contract terms with the
+ fulfillment URL \nolinkurl{https://alice-shop.example.com/esasy-24.pdf}
+ that matches the resources URL, it claims the contract by generating a
+ claim key pair $(s, p)$ and requesting the contract URL with the claim
+ public key $p$ as additional parameter:
+ \nolinkurl{https://alice-shop.example.com/contract?product=essay-24.pdf\&claim_pub=}$p$.
+ \item The wallet displays the contract terms to the customer and asks them to
+ accept or decline. If the customer accepted the contract, the wallet sends
+ a payment to the merchant. After the merchant received a valid payment,
+ it marks the corresponding order as paid.
+ \item The wallet constructs the extended fulfillment URL by adding the order
+ id from the contract as an additional parameter and navigates the browser
+ to the resulting URL
+ \nolinkurl{https://alice-shop.example.com/esasy-24.pdf?order\_id=...}.
+ \item The shop receives the request to the extended fulfillment URL and
+ checks if the payment corresponding to the order ID was completed. In case
+ the payment was successful, it serves the purchased content.
+\end{enumerate}
+
+To avoid checking the status of the payment every time, the merchant can
+instead set a session cookie (signed/encrypted by the merchant) in the user's
+browser which indicates that \texttt{essay-24.pdf} has been purchased.
+
+The resource-based payment mechanism must also handle the situation where a
+customer navigates again to a resource that they already paid for, without
+directly navigating to the extended fulfillment URL. In case no session cookie
+was set for the purchase or the cookie was deleted / has expired, the customer would
+be prompted for a payment again. To avoid this, the wallet tries to find an
+existing contract whose plain fulfillment URL matches the resource URL
+specified in the merchant's HTTP 402 response. If such an existing payment was
+found, the wallet instead redirects the user to the extended fulfillment URL
+for this contract, instead of downloading the new contract terms and prompting
+for payment.
+
+In the example given above, the URL that triggers the payment is the same as the fulfillment URL.
+This may not always the case in practice. When the merchant backend is hosted by a third
+party, say \nolinkurl{https://bob.example.com/}, the page that triggers the payment
+even has a different origin, i.e., the scheme, host or port may differ \cite{rfc6454}.
+
+This cross-origin operation presents a potential privacy risk if not
+implemented carefully.
+To check whether a user has already paid for a particular
+resource with URL $u$, an arbitrary website could send an HTTP 402 response with
+the ``Taler-Resource-Url'' header set to $u$ and the ``Taler-Contract-Url''
+set to a URL pointing to the attacker's server. If the user paid for $u$, the
+wallet will navigate to the extended fulfillment URL corresponding to $u$.
+Otherwise, the wallet will try to download a contract from the URL given by the
+attacker. In order to prevent this attack on privacy, the wallet must only
+redirect to $u$ if the origin of the page responding with HTTP 402 is the same
+origin as either the $u$ or the pay URL.\footnote{This type of countermeasure is well
+known in browsers as the same origin policy, as also outlined in \cite{rfc6454}.}
+
+\subsubsection{Loose Browser Integration}\label{sec:loose-browser-integration}
+
+The payment process we just described does not directly work in browsers that do not
+have native Taler integration, as the browser (or at least a browser extension)
+would have to handle the HTTP status code 402 and handle the Taler-specific headers correctly.
+We now define a fallback, which is transparently implemented in the reference merchant backend.
+
+In addition to indicating that a payment is required for a resource in the HTTP status code and header,
+the merchant includes a fallback URL in the body of the ``402 Payment Required'' response. This URL must have the custom URL scheme
+\texttt{taler}, and contains the contract terms URL (and other Taler-specific settings normally specified in headers)
+as parameters. The above payment would include a link (labeled, e.g., ``Pay with GNU Taler'') to the following URL, encoding
+the same information as the headers:
+\begin{lstlisting}[style=myhttp]
+taler:pay?*\break\contl*contract_url=*\break\contl*https%3A%2F%2Falice-shop.example.com%2Fcontract%3Fproduct%3Dessay-24.pdf*\break\contl*&resource_url=*\break\contl*https%3A%2F%2Falice-shop.example.com%2Fessay-24.pdf
+\end{lstlisting}
+
+This fallback can be disabled for requests from user agents that are known to
+natively support GNU Taler.
+
+GNU Taler wallet applications register themselves as a handler for the
+\texttt{taler} URI scheme, and thus following a \texttt{taler:pay} link opens
+the dedicated wallet, even if GNU Taler is not supported by the browser or a
+browser extension. Registration a custom protocol handler for a URI scheme is
+possible on all modern platforms with web browsers that we are aware of.
+
+Note that wallets communicating with the merchant do so from a different
+browsing context, and thus the merchant backend cannot rely on cookies that
+were set in the customer's browser when using the shop page.
+
+We chose HTTP headers as the primary means of signaling to the wallet (instead
+of relying on, e.g., a new content media type), as it allows the fallback content
+to be an HTML page that can be rendered by all browsers. Furthermore,
+current browser extension mechanism allow intercepting headers synchronously
+before the rendering of the page is started, avoiding visible flickering caused by
+intermediate page loads.
+
+\subsection{Session-bound Payments and Sharing}
+As we described the payment protocol so far, an extended fulfillment URL
+is
+not bound to a browser session. When sharing an extended fulfillment
+URL, another user would get access to the same content. This might be appropriate
+for some types of fulfillment pages (such as a donation receipt), but is generally not
+appropriate when digital content is sold. Even though it is trivial to share digital content
+unless digital restrictions management (DRM) is employed, the ability to share
+links might set the bar for sharing too low.
+
+While the validity of a fulfillment URL could be limited to a certain time,
+browser session or IP address, this would be too restrictive for scenarios where
+the user wants to purchase permanent access to the content.
+
+As a compromise, Taler provides \emph{session-bound} payments. For
+session-bound payments, the seller's website assigns the user a random session
+ID, for example, via a session cookie. The extended fulfillment URL for
+session-bound payments is constructed by additionally specifying the URL
+parameter \texttt{session\_sig}, which contains proof that the user completed
+(or re-played) the payment under their current session ID.
+
+To initiate a session-bound payment, the HTTP 402 response must additionally
+contain the ``Taler-Session-Id'' header, which will cause the wallet to
+additionally obtain a signature on the session ID from the merchant's pay URL,
+by additionally sending the session ID when executing (or re-playing) the
+payment.
+As an optimization, instead of re-playing the full payment, the wallet can also
+send the session ID together with the payment receipt it obtained from the
+completed payment with different session ID.
+
+Before serving paid content to the user, the merchant simply checks if the
+session signature matches the assigned session and contract terms. To simplify
+the implementation of the frontend, this signature check can be implemented as
+a request to the GNU Taler backend. Using session signatures instead of storing
+all completed session-bound payments in the merchant's database saves storage.
+
+While the coins used for the payment or the payment receipt could be shared
+with other wallets, it is a higher barrier than just sharing a URL. Furthermore, the
+merchant could restrict the rate at which new sessions can be created for the
+same contract terms and restrict a session to one IP address, limiting sharing.
+
+For the situation where a user accesses a session-bound paid resource and
+neither has a corresponding contract in their wallet nor does the merchant
+provide a contract URL to buy access to the resource, the merchant can specify
+an \emph{offer URL} in the ``Taler-Offer-Url'' header. If the wallet is not
+able to take any other steps to complete the payment, it will redirect the user
+to the offer URL. As the name suggests, the offer URL can point to a page with
+alternative offers for the resource, or some other explanation as to why the
+resource is not available anymore.
+
+\subsection{Embedded Content}
+So far we only considered paying for a single, top-level resource,
+namely the fulfillment URL. In practice, however, websites are composed of
+many subresources such as embedded images and videos.
+
+We describe two techniques to ``paywall'' subresources behind a GNU Taler
+payment. Many other approaches and variations are possible.
+\begin{enumerate}
+ \item Visiting the fulfillment URL can set a session cookie. When a
+ subresource is requested, the server will check that the customer has the
+ correct session cookie set.
+ \item When serving the fulfillment page, the merchant can add an additional
+ authentication token to the URLs of subresources. When the subresource is
+ requested, the validity of the authentication token is checked. If the
+ merchant itself (instead of a Content Delivery Network that supports token
+ authentication) is serving the paid subresource, the order ID and session
+ signature can also be used as the authentication token.
+\end{enumerate}
+
+It would technically be possible to allow contract terms to refer to multiple
+resources that are being purchased by including a list or pattern that defines
+a set of URLs. The browser would then automatically include information to
+identify the completed payment in the request for the subresource. We
+deliberately do not implement this approach, as it would require deeper
+integration in the browser than possible on many platforms. If not restricted
+carefully, this feature could also be used as an additional method to track the
+user across the merchant's website.
+
+\subsection{Contract Terms}
+The contract terms, only seen by the customer and the merchant (except when a tax audit of the merchant is requested)
+contain the following information:
+\begin{itemize}
+ \item The total amount to be paid,
+ \item the \texttt{pay\_url}, an HTTP endpoint that receives the payment,
+ \item the deadline until the merchant accepts the payment (repeated in the signed contract header),
+ \item the deadline for refunds (repeated in the signed contract header),
+ \item the claim public key provided by the customer, used to prove they have claimed the contract terms,
+ \item the order ID, which is a short, human-friendly identifier for the contract terms within
+ the merchant,
+ \item the \texttt{fulfillment\_url}, which identifies the resources that is being paid for,
+ \item a human-readable summary and product list,
+ \item the fees covered by the merchant (if the fees for the payment exceed this value, the
+ customer must explicitly pay the additional fees),
+ \item depending on the underlying payment system, KYC registration information
+ or other payment-related data that needs to be passed on to the exchange (repeated in the signed contract header),
+ \item the list of exchanges and auditors that the merchants accepts for the payment,
+ \item information about the merchant, including the merchant public key and contact information.
+\end{itemize}
+
+
+\subsection{Refunds}
+By signing a \emph{refund permission}, the merchant can ``undo'' a deposit on a
+coin, either fully or partially. The customer can then spend (or refresh) the
+refunded value of the coin again. A refund is only possible before the refund
+deadline (specified in the contract header). After the refund deadline has
+passed (and before the deposit deadline) the exchange makes a bank transfer the
+merchant with the aggregated value from deposits, a refund after this point
+would require a bank transfer back from the merchant to the exchange.
+
+Each individual refund on each coin incurs fees; the
+refund fee is subtracted from the amount given back to the customer and kept by
+the exchange.
+
+Typically a refund serves either one of the following purposes:
+\begin{itemize}
+ \item An automatic refund is given to the customer when a payment only
+ partially succeeded. This can happen when a customer's wallet accidentally
+ double-spends, which is possible even with non-malicious customers and caused by data
+ loss or delayed/failed synchronization between the same user's wallet on
+ multiple devices. In these cases, the user can choose to re-try the
+ payment with different, unspent coins (if available) or to ask for a refund
+ from the merchant.
+ \item A voluntary refund can be given at the discretion of the merchant,
+ for example, when the customer is not happy with their purchase.
+\end{itemize}
+Refunds require a signature by the merchant, but no consent from the customer.
+
+A customer is notified of a refund with the HTTP 402 Payment Required status
+code and the ``Taler-Refund'' header. The value of the refund header is a
+URL. An HTTP \texttt{GET} request on that URL will return a list of refund confirmations that the
+merchant received from the exchange.
+
+\subsection{Tipping}
+Tipping in Taler uses the ``withdraw loophole'' (see \ref{taler:design:tipping}) to allow the
+merchant\footnote{We still use the term ``merchant'', since donations use the same software component
+as the merchant, but ``donor'' would be more accurate.} to donate small amounts (without any associated contract terms or legal
+obligations) into the user's wallet.
+
+To be able to give tips, the merchant must create a reserve with an exchange. The reserve private key
+is used to sign blinded coins generated by the user that is being given the tip.
+
+The merchant triggers the wallet by returning an HTTP 402 Payment Required
+response that includes the ``Taler-Tip'' header. The value of the tip header (called the
+tip token) contains
+\begin{itemize}
+ \item the amount of the tip,
+ \item the exchange to use,
+ \item a URL to redirect after processing the tip,
+ \item a deadline for picking up the tip,
+ \item a merchant-internal unique ID for the tip, and
+ \item the \emph{pickup URL} for the tip.
+\end{itemize}
+Upon receiving the tip token, the wallet creates coin planchets that sum up to at most
+the amount specified in the tip token, with denominations offered by the exchange specified in the tip token.
+
+The list of planchets is then sent to the merchant via an HTTP \texttt{POST}
+request to the tip-pickup URL. The merchant creates a withdrawal confirmation
+signature for each planchet, using the private key of the tipping reserve, and
+responds to the HTTP \texttt{POST} request with the resulting list of
+signatures. The user then uses these signatures in the normal withdrawal
+protocol with the exchange to obtain coins ``paid for'' by the merchant, but
+anonymized and only spendable by the customer.
+
+
+\section{Bank Integration}
+In order to use Taler for real-world payments, it must be integrated with the
+existing banking system. Banks can choose to tightly integrate with Taler and
+offer the ability to withdraw coins on their website. Even existing banks can
+be used to withdraw coins via a manual bank transfer to the exchange, with the
+only requirement that the 52 character alphanumeric, case-insensitive encoding
+of the reserve public key can be included in the transaction without
+modification other than case folding and white space
+normalization.\footnote{Some banking systems specify that the subject of the
+can be changed, and provide an additional machine-readable ``instruction''
+field. }
+
+\subsection{Wire Method Identifiers}\label{implementation:wire-method-identifiers}
+We introduce a new URI scheme \texttt{payto}, which is used in Taler to
+identify target accounts across a wide variety of payment systems with uniform
+syntax.
+
+In in its simplest form, a \texttt{payto} URI identifies one account of a particular payment system:
+
+\begin{center}
+ \texttt{'payto://' TYPE '/' ACCOUNT }
+\end{center}
+
+When opening a \texttt{payto} URI, the default action is to open an application
+that can handle payments with the given type of payment system, with the target
+account pre-filled. In its extended form, a \texttt{payto} URL can also specify
+additional information for a payment in the query parameters of the URI.
+
+In the generic syntax for URIs, the payment system type corresponds to the
+authority, the account corresponds to the path, and additional parameters for
+the payment correspond to the query parameters. Conforming to the generic URI
+syntax makes parsing of \texttt{payto} URIs trivial with existing parsers.
+
+Formally, a \texttt{payto} URI is an encoding of a partially filled out pro
+forma invoice. The full specification of the \texttt{payto} URI is RFC XXXX. % FIXME!
+
+In the implementation of Taler, \texttt{payto} URIs are used in various places:
+\begin{enumerate}
+ \item The exchange lists the different ways it can accept money as \texttt{payto} URIs.
+ If the exchange uses payment methods that do not have tight Taler integration.
+ \item In order to withdraw money from an exchange that uses a bank account type that
+ does not typically have tight Taler integration, the wallet can generate a link and a QR code
+ that already contains the reserve public key. When scanning the QR code with a mobile device that
+ has an appropriate banking app installed, a bank transfer form can be pre-filled and the user only has to confirm the
+ transfer to the exchange.
+ \item The merchant specifies the account it wishes to be paid on as a \texttt{payto} URI, both in
+ the configuration of the merchant backend as well as in communication with the exchange.
+\end{enumerate}
+
+A major advantage of encoding payment targets as URIs is that URI schemes can be registered
+with an application on most platforms, and will be ``clickable'' in most applications and open the right
+application when scanned as a QR code. This is especially useful for the first use case listed above; the other use cases
+could be covered by defining a media type instead \cite{rfc6838}.
+
+% FIXME: put into side box
+As an example, the following QR code would open a banking application that supports SEPA payments,
+pre-filled with a 15\EUR{} donation to the bank account of GNUnet:
+
+\begin{center}
+\qrcode[hyperlink,height=5em]{payto://sepa/DE67830654080004822650?amount=EUR:15}
+\end{center}
+
+\subsection{Demo Bank}
+For demonstration purposes and integration testing, we use our toy bank
+implementation\footnote{\url{https://git.taler.net/bank.git}}, which might be
+used in the future for regional currencies or accounting systems (e.g., for a
+company cafeteria). The payment type identifier is \texttt{taler-bank}. The
+authority part encodes the base URL of the bank, and the path must be the
+decimal representation of a single integer between $1$ and $2^{52}$, denoting
+the internal demo bank account number.
+
+\subsection{EBICS and SEPA}
+The Electronic Banking Internet Communication Standard\footnote{\url{http://www.ebics.org}} (EBICS) is a standard
+for communicating with banks, and is widely used in Germany, France and
+Switzerland, which are part of the Single European Payment Area (SEPA). EBICS
+itself is just a container format. A commonly supported payload for EBICS is
+ISO 2022, which defines messages for banking-related business processes.
+
+Integration of GNU Taler with EBICS is currently under development, and would
+allow Taler to be easily deployed in many European countries, provided that the
+exchange provider can obtain the required banking license.
+
+\subsection{Blockchain Integration}
+Blockchains such as Bitcoin could also be used as the underlying financial
+system for GNU Taler, provided that merchants and customers trust the exchange to be honest.
+
+With blockchains that allow more complex smart contracts, the auditing
+functionality could be implemented by the blockchain itself. In particular,
+the exchange can be incentivized to operate correctly by requiring an initial
+safety deposit to the auditing smart contract, which is distributed to
+defrauded participants if misbehavior of the exchange is detected.
+
+\section{Exchange}
+
+\begin{figure}
+ \includegraphics[width=\textwidth]{diagrams/taler-diagram-exchange.png}
+ \caption{Architecture of the exchange reference implementation}
+\end{figure}
+
+The exchange consists of three independent processes:
+\begin{itemize}
+ \item The \texttt{taler-exchange-httpd} process handles HTTP requests from clients,
+ mainly merchants and wallets.
+ \item The \texttt{taler-exchange-wirewatch} process watches for wire transfers
+ to the exchange's bank account and updates reserves based on that.
+ \item The \texttt{taler-exchange-aggregator} process aggregates outgoing transactions
+ to merchants.
+\end{itemize}
+All three processes exchange data via the same database. Only
+\texttt{taler-exchange-httpd} needs access to the exchanges online signing keys
+and denomination keys.
+
+The database is accessed via a Taler-specific database abstraction layer.
+Different databases can be supported via plugins; at the time of writing this,
+only a PostgreSQL plugin has been implemented.
+
+Wire plugins are used as an abstraction to access the account layer that Taler
+runs on. Specifically, the \textit{wirewatch} process uses the plugin to monitor
+incoming transfers, and the aggregator process uses the wire plugin to make
+wire transfers to merchants.
+
+The following APIs are offered by the exchange:
+\begin{description}
+ \item[Announcing keys, bank accounts and other public information] The
+ exchange offers the list of denomination keys, signing keys, auditors,
+ supported bank accounts, revoked keys and other general information needed
+ to use the exchange's services via the \texttt{/keys} and \texttt{/wire}
+ APIs.
+ \item[Obtaining entropy] As we cannot be sure that all client-devices have
+ an adequate random number generator, the exchange offers the \texttt{/seed}
+ endpoint to download some high-entropy value. Clients should mix this
+ seed with their own, locally-generated entropy into an entropy pool.
+ \item[Reserve status and withdrawal] After having wired money to the exchange,
+ the status of the reserve can be checked via the \texttt{/reserve/\$RESERVE\_PUB/status} API. Since
+ the wire transfer usually takes some time to arrive at the exchange, wallets should periodically
+ poll this API, and initiate a withdrawal with \texttt{/reserve/\$RESERVE\_PUB/withdraw} once the exchange received the funds.
+ \item[Deposits and tracking] Merchants transmit deposit permissions they have received from customers
+ to the exchange via the \texttt{/coins/\$COIN\_PUB/deposit} API. Since multiple deposits are aggregated into one wire transfer,
+ the merchant additionally can use the exchange's \texttt{/transfers/\$WTID} API that returns the list of deposits for a wire transfer
+ identifier (WTID) included in the wire transfer to the merchant, as well as the \texttt{/deposits/\$H\_WIRE/\$MERCHANT\_PUB/\$H\_CONTRACT\_TERMS/\$COIN\_PUB} API to look up
+ which wire transfer included the payment for a given deposit.
+ \item[Refresh] Refreshing consists of two stages. First, using \texttt{/coins/\$COIN\_PUB/melt} an old, possibly dirty coin is melted and thus devaluted. The commitment made by the wallet during the melt and the resulting $\gamma$-challenge from the exchange are associated with a {\em refresh session}. Then, using \texttt{/refreshes/\$RCH/reveal} the wallet can answer the challenge and obtain fresh coins as change. Finally, \texttt{/coins/\$COIN\_PUB/link} provides the link deterrent against refresh abuse.
+ \item[Refunds] The refund API (\texttt{/coins/\$COIN\_PUB/refund}) can ``undo'' a deposit if the merchant gave their signature, and the aggregation deadline
+ for the payment has not occurred yet.
+ \item[Recoup] The recoup API (\texttt{/coins/\$COIN\_PUB/recoup}) allows customers to be compensated
+ for coins whose denomination key has been revoked. Customers must send either a full withdrawal transcript that
+ includes their private blinding factor, or a refresh transcript (of a refresh that had the revoked denominations as one of the targets)
+ that includes blinding factors. In the former case, the reserve is credited, in the latter case, the source coin of the
+ refresh is refunded and can be refreshed again.
+\end{description}
+
+New denomination and signing keys are generated and signed with the exchange's master
+secret key using the \texttt{taler-exchange-keyup} utility, according to a key schedule
+defined in the exchange's configuration. This process should be done on an air-gapped
+offline machine that has access to the exchange's master signing key.
+
+Generating new keys with \texttt{taler-exchange-keyup} also generates an
+auditing request file, which the exchange should send its auditors. The auditors then
+certify these keys with the \texttt{taler-auditor-sign} tool.
+
+\begin{figure}
+ \includegraphics[width=\textwidth]{diagrams/taler-diagram-keyup.png}
+ \caption{Data flow for updating the exchange's keys.}
+ \label{figure:keyup}
+\end{figure}
+
+This process is illustrated in Figure~\ref{figure:keyup}.
+
+
+\section{Auditor}
+
+The auditor consists of several main components:
+\begin{itemize}
+ \item the \texttt{taler-auditor-dbinit} tool to setup,
+ upgrade or garbage-collect an auditor's database,
+ \item the \texttt{taler-auditor-exchange} tool to add an
+ exchange to the list of audited exchanges,
+ \item the \texttt{taler-auditor-sign} tool to sign an exchange's
+ keys to affirm that the auditor is auditing this exchange,
+ \item an HTTP service (\texttt{taler-auditor-httpd}) which
+ receives deposit confirmations from merchants, and
+ \item the \texttt{taler-auditor} script which must be regularly
+ run to generate audit reports.
+\end{itemize}
+
+\subsection{Database synchronization}
+
+FIXME: describe issue of how to synchronize exchange and auditor
+databases, and how we solved it (once we did solve it!) here.
+
+\subsection{The \texttt{taler-auditor} tool}
+
+The \texttt{taler-auditor} script uses several helper processes. These helper
+processes access the exchange's database, either directly (for
+exchange-internal auditing as part if its operational security) or over a
+replica (in the case of external auditors).
+
+The \texttt{taler-auditor} script ultimately generates a report with the
+following information:
+\begin{itemize}
+ \item Do the operations stored in a reserve's history match the reserve's balance?
+ \item Did the exchange record outgoing transactions to the right merchant for
+ deposits after the deadline for the payment was reached?
+ \item Do operations recorded on coins (deposit, refresh, refund) match the remaining
+ value on the coin?
+ \item Do operations respect the expiration of denominations?
+ \item For a denomination, is the number of pairwise different coin public
+ keys recorded in deposit/refresh operations smaller or equal to the number
+ of blind signatures recorded in withdraw/refresh operations?
+ If this invariant is violated, the corresponding denomination must be revoked.
+ %\item Are signatures made by the exchange correct? (no, apparently we don't store signatures)
+ \item What is the income if the exchange from different fees?
+\end{itemize}
+
+\subsubsection{Report generation}
+
+The \texttt{taler-auditor} script invokes its helper processes, each of
+which generates a JSON file with the key findings. The master script then
+uses Jinja2 templating to fill a LaTeX template with the key findings, and
+runs \texttt{pdflatex} to generate the final PDF.
+
+It is also possible to run the helper processes manually, and given that only
+one of them requires read-only access to the bank account of the exchange,
+this may be useful to improve parallelism or enhance privilege
+separation. Thus, \texttt{taler-auditor} is really only a convenience script.
+
+\subsubsection{Incremental processing}
+
+The operation of all auditor helper processes is incremental. There is a separate
+database to checkpoint the auditing progress and to store intermediate results
+for the incremental computation. Most database tables used by the exchange are
+append-only: rows are only added but never removed or changed. Tables that
+are destructively modified by the exchange only store cached computations based
+on the append-only tables. Each append-only table has a monotonically
+increasing row ID. Thus, the auditor's checkpoint simply consists of the set of
+row IDs that were last seen.
+
+\subsubsection{The \texttt{taler-helper-auditor-aggregation}}
+
+This tool checks that the exchange properly aggregates
+individual deposits into wire transfers
+(see Figure~\ref{fig:deposit:states}).
+
+The list of invariants checked by this tool thus includes:
+\begin{itemize}
+\item That the fees charged by the exchange are those
+ the exchange provided to the auditor earlier, and that the
+ fee calculations (deposit fee, refund fee, wire fee)
+ are correct. Refunds are relevant because refunded amounts
+ are not included in the aggregate balance.
+\item The sanity of fees, as fees may not exceed the contribution
+ of a coin (so the deposit fee cannot be larger than the
+ deposited value, and the wire fee cannot exceed the
+ wired amount). Similarly, a coin cannot receive refunds
+ that exceed the deposited value of the coin, and the
+ deposit value must not exceed the coin's denomination value.
+\item That the start and end dates for the wire
+ fee structure are sane, that is cover the timeframe without
+ overlap or gaps.
+\item That denomination signatures on the coins are valid
+ and match denomination keys known to the auditor.
+\item That the target account of the outgoing aggregate wire
+ transfer is well-formed and matches the account specified
+ in the deposit.
+\item That coins that have been claimed in an aggregation have
+ a supporting history.
+\item That coins which should be aggregated are listed in an
+ aggregation list, and that the timestamps match the
+ expected dates.
+\end{itemize}
+
+
+\subsubsection{The \texttt{taler-helper-auditor-coins}}
+
+This helper focuses on checking the history of individual coins (as described
+in Figure~\ref{fig:coin:states}), ensuring that the coin is not double-spent
+(or over-spent) and that refreshes, refunds and recoups are processed
+properly.
+
+Additionally, this tool includes checks for denomination key abuse by
+verifying that the value and number of coins deposited in any denomination
+does not exceed the value and number of coins issued in that denomination.
+
+Finally, the auditor will also complain if the exchange processes
+denominations that it did not properly report (with fee structure) to the
+auditor.
+
+The list of invariants checked by this tool thus includes:
+\begin{itemize}
+\item Testing for an
+ emergency on denominations because the value or number
+ of coins deposited exceeds the value or number of coins
+ issued; if this happens, the exchange should revoke the
+ respective denomination.
+\item Checking for arithmetic inconsistencies from exchanges
+ not properly calculating balances or fees during the
+ various coin operations (withdraw, deposit, melt, refund);
+\item That signatures are correct for denomination key revocation,
+ coin denominations,
+ and coin operations (deposit, melt, refund, recoup)
+\item That denomination keys are known to the auditor.
+\item That denomination keys were actually revoked if a recoup
+ is granted.
+\item Whether there exists refresh sessions from coins that
+ have been melted but not (yet) revealed
+ (this can be harmless and no fault of the exchange, but
+ could also be indicative of an exchange failing to process
+ certain requests in a timely fashion).
+\item That the refund deadline is not after
+ the wire deadline (while harmless, such a deposit
+ makes inconsistent requirements and should have been
+ rejected by the exchange).
+\end{itemize}
+
+
+\subsubsection{The \texttt{taler-helper-auditor-deposits}}
+
+This tool verifies that the deposit confirmations reported by merchants
+directly to the auditor are also included in the database we got from the
+exchange. This is to ensure that the exchange cannot defraud merchants by
+simply not reporting deposits to the auditor or an
+exchange signing key being compromised (as described in
+Section~\label{sec:signkey:compromise}).
+
+\subsubsection{The \texttt{taler-helper-auditor-reserves}}
+
+This figure checks the exchange's processing of the
+balance of an individual reserve, as described
+in Figure~\ref{fig:reserve:states}.
+
+The list of invariants checked by this tool thus includes:
+\begin{itemize}
+\item Correctness of the signatures that legitimized
+ withdraw and recoup operations.
+\item Correct calculation of the reserve balance given
+ the history of operations (incoming wire transfers,
+ withdraws, recoups and closing operations)
+ involving the reserve.
+\item That the exchange closed reserves when required,
+ and that the exchange wired the funds back to the
+ correct (originating) wire account.
+\item Knowledge of the auditor of the denomination keys
+ involved in withdraw operations and of the
+ applicable closing fee.
+\item That denomination keys were valid for use in a
+ withdraw operation at the reported time of withdrawal.
+\item That denomination keys were eligible for recoup
+ at the time of a recoup.
+\end{itemize}
+
+
+\subsubsection{The \texttt{taler-helper-auditor-wire}}
+
+This helper process checks that the incoming and outgoing transfers recorded
+in the exchange's database match wire transfers of the underlying bank
+account. To access the transaction history (typically recorded by the bank),
+the wire auditor helper is special in that it must be provided the necessary
+credentials to access the exchange's bank account. In a production setting,
+this will typically require the configuration and operation of a Nexus
+instance (of LibEuFin) at the auditor.
+
+The actual logic of the wire auditor is pretty boring: it goes over all bank
+transactions that are in the exchange's database, and verifies that they are
+present in the records from the bank, and then it goes over all bank
+transactions reported by the bank, and again checks that they are also in the
+exchange's database. This applies for both incoming and outgoing wire
+transfers. The tool reports any inconsistencies, be they in terms of wire
+transfer subject, bank accounts involved, amount that was transferred, or
+timestamp.
+
+For incoming wire transfers, this check protects against the following
+failures: An exchange reporting the wrong amount may wrongfully allow or
+refuse the withdrawal of coins from a reserve. The wrong wire transfer subject
+might allow the wrong wallet to withdraw, and reject the rightful owner. The
+wrong bank account could result in the wrong recipient receiving funds if the
+reserve is closed. Timestamp differences are usually pretty harmless, and
+small differences may even occur due to rounding or clock synchronization
+issues. However, they are still reported as they may be indicative of other
+problems.
+
+For outgoing wire transfers, the implications arising from an exchange making
+the wrong wire transfers should be obvious.
+
+The list of invariants checked by this tool thus includes:
+\begin{itemize}
+\item The exchange correctly listing all incoming wire transfers.
+\item The bank/Nexus having correctly suppressed incoming wire
+ transfers with non-unique wire transfer subjects, and having
+ assigned each wire transfer a unique row ID/offset.
+\item The exchange correctly listing all outgoing wire transfers
+ including having the appropriate justifications (aggregation
+ or reserve closure) for the respective amounts and target accounts.
+\item Wire transfers that the exchange has failed to execute that
+ were due. Note that small delays here can be normal as
+ wire transfers may be in flight.
+\end{itemize}
+
+
+\subsection{The Auditor's HTTP service}
+
+The auditor exposes a web server with the \texttt{taler-auditor-httpd}
+process. Currently, it shows a website that allows the customer to add the
+auditor to the list of trusted auditors in their wallet.
+
+It also exposes an endpoint for merchants to submit deposit confirmations.
+These merchant-submitted deposit confirmations are checked against the deposit
+permissions in the exchange's database to detect compromised signing keys or
+missing writes, as described in
+Section~\ref{sec:compromised-signing-key-detection}.
+
+In the future, we plan for the auditor to expose additional endpoints where
+wallets and merchant backends can submit (cryptographic) proofs of
+misbehavior from an exchange. The goal would be to automatically verify the
+proofs, take corrective action by including the information in the audit
+report and possibly even compensating the victim.
+
+
+\section{Merchant Backend}
+
+\begin{figure}
+ \includegraphics[width=\textwidth]{diagrams/taler-diagram-merchant.png}
+ \caption{Architecture of the merchant reference implementation}
+\end{figure}
+
+The Taler merchant backend is a component that abstracts away the details of
+processing Taler payments and provides a simple HTTP API. The merchant backend
+handles cryptographic operations (signature verification, signing), secret
+management and communication with the exchange.
+
+The backend API\footnote{See \url{https://docs.taler.net/api/} for the full documentation}
+is divided into two types of HTTP endpoints:
+\begin{enumerate}
+ \item Functionality that is accessed internally by the merchant. These APIs typically
+ require authentication and/or are only accessible from within the private
+ network of the merchant.
+ \item Functionality that is exposed publicly on the Internet and accessed by the customer's wallet and browser.
+ % FIXME: talk about proxying
+\end{enumerate}
+
+A typical merchant has a \emph{storefront} component that customers visit with
+their browser, as well as a \emph{back office} component that allows the
+merchant to view information about payments that customers made and that integrates
+with other components such as order processing and shipping.
+
+\subsection{Processing payments}\label{sec:processing-payments}
+
+To process a payment, the storefront first instructs the backend to create an
+\emph{order}. The order contains information relevant to the purchase, and is
+in fact a subset of the information contained in the contract terms. The
+backend automatically adds missing information to the order details provided by
+the storefront. The full contract terms can only be signed once the customer
+provides the claim public key for the contract.
+
+Each order is uniquely identified by an order ID, which can be chosen by the
+storefront or automatically generated by the backend.
+
+The order ID can be used to query the status of the payment. If the customer
+did not pay for an order ID yet, the response from the backend includes a
+payment redirect URL. The storefront can redirect the customer to this
+payment redirect URL; visiting the URL will trigger the customer's
+browser/wallet to prompt for a payment.
+
+To simplify the implementation of the storefront, the merchant backend can
+serve a page to the customer's browser that triggers the payment via the HTTP
+402 status code and the corresponding headers, and provides a fallback (in the
+form of a \texttt{taler:pay} link) for loosely integrated browsers.
+When checking the status of a payment that is not settled yet, the response from the merchant backend
+will contains a payment redirect URL. The storefront redirects the browser to this URL,
+which is served by the merchant backend and triggers the payment.
+
+The code snippet shown in Figure~\ref{fig:merchant-donations-code} implements
+the core functionality of a merchant frontend that prompts the customer for a
+donation (upon visiting \texttt{/donate} with the right query parameters) and
+shows a donation receipt on the fulfillment page with URL \texttt{/receipt}.
+The code snippet is written in Python and uses the Flask library\footnote{\url{http://flask.pocoo.org/}} to process HTTP requests.
+The helper functions \texttt{backend\_post}
+and \texttt{backend\_get} make an HTTP \texttt{POST}/\texttt{GET} request to the merchant backend, respectively,
+with the given request body / query parameters.
+
+\begin{figure}
+\lstinputlisting[language=Python,basicstyle=\footnotesize]{snippets/donations.py}
+\caption[Code snippet for merchant frontend]{Code snippet with core functionality of a merchant frontend to accept donations.}
+\label{fig:merchant-donations-code}
+\end{figure}
+
+
+\subsection{Back Office APIs}
+
+The back office API allows the merchant to query information about the history
+and status of payments, as well as correlate wire transfers to the merchant's
+bank account with the respective GNU Taler payment. This API is necessary to
+allow integration with other parts of the merchant's e-commerce infrastructure.
+
+%\subsection{Instances}
+%Merchant instances allow one deployment of the merchant backend to host more
+%than one logical merchant.
+
+\subsection{Example Merchant Frontends}
+
+We implemented the following applications using the merchant backend API.
+
+\begin{description}
+ \item[Blog Merchant] The blog merchant's landing page has a list of article titles with a teaser.
+ When following the link to the article, the customer is asked to pay to view the article.
+ \item[Donations] The donations frontend allows the customer to select a project to donate to.
+ The fulfillment page shows a donation receipt.
+ \item[Codeless Payments] The codeless payment frontend is a prototype for a
+ user interface that allows merchants to sell products on their website
+ without having to write code to integrate with the merchant backend.
+ Instead, the merchant uses a web interface to manage products and their
+ available stock. The codeless payment frontend then creates an HTML snippet with a payment
+ button that the merchant can copy-and-paste integrate into their storefront.
+ \item[Survey] The survey frontend showcases the tipping functionality of GNU Taler.
+ The user fills out a survey and receives a tip for completing it.
+ \item[Back office] The example back-office application shows the history and
+ status of payments processed by the merchant.
+\end{description}
+
+The code for these examples is available at \url{https://git.taler.net/} in the
+repositories \texttt{blog}, \texttt{donations}, \texttt{codeless}, \texttt{survey}
+and \texttt{backoffice} respectively.
+
+
+\section{Wallet}
+
+\begin{figure}
+ \includegraphics[width=\textwidth]{diagrams/taler-diagram-wallet.png}
+ \caption{Architecture of the wallet reference implementation}
+\end{figure}
+
+The wallet manages the customer's reserves and coins, lets the customer view
+and pay for contracts from merchants. It can be seen in operation in
+Section~\ref{sec:intro:ux}.
+
+The reference implementation of the GNU Taler wallet is written in the
+TypeScript language against the WebExtension API%
+\footnote{\url{https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions}}, a cross-browser mechanism for
+browser extensions. The reference wallet is a ``tightly integrated'' wallet, as it directly hooks into
+the browser to process responses with the HTTP status code ``402 Payment Required''.
+
+Many cryptographic operations needed to implement the wallet are not commonly
+available in a browser environment. We cross-compile the GNU Taler utility
+library written in C as well as its dependencies (such as libgcrypt) to asm.js
+(and WebAssembly on supported platforms) using the LLVM-based emscripten
+toolchain \cite{zakai2011emscripten}.
+
+Cryptographic operations run in an isolated process implemented as a
+WebWorker.\footnote{\url{https://html.spec.whatwg.org/}} This design allows
+the relatively slow cryptographic operations to run concurrently in the
+background in multiple threads. Since the crypto WebWorkers are started on-demand,
+the wallet only uses minimal resources when not actively used.
+
+\subsection{Optimizations}\label{sec:wallet-optimizations}
+To improve the perceived performance of cryptographic operations,
+the wallet optimistically creates signatures in the background
+while the user is looking at the ``confirm payment'' dialog. If the user does
+not accept the contract, these signatures are thrown away instead of being sent
+to the merchant. This effectively hides the latency of the
+most expensive cryptographic operations, as they are done while the user
+consciously needs to make a decision on whether to proceed with a payment.
+
+
+\subsection{Coin Selection}
+The wallet hides the implementation details of fractionally spending different
+denomination from the user, and automatically selects which denominations to
+use for withdrawing a given amount, as well as which existing coins to
+(partially) spend for a payment.
+
+Denominations for withdrawal are greedily selected, starting with the largest
+denomination that fits into the remaining amount to withdraw. Coin selection
+for spending proceeds similarly, but first checks if there is a single coin
+that can be partially spent to cover the whole amount. After each payment, the
+wallet automatically refreshes coins with a remaining amount large enough to be
+refreshed. We discuss a simple simulation of the current coin selection algorithm
+in Section~\ref{sec:coins-per-transaction}.
+
+A more advanced coin selection would also consider the fee structure of the
+exchange, minimizing the number of coins as well as the fees incurred by the
+various operations. The wallet could additionally learn typical amounts that
+the user spends, and adjust withdrawn denominations accordingly to further
+minimize costs. An opposing concern to the financial cost is the anonymity of
+customers, which is improved when the spending behavior of wallets is as
+similar as possible.
+
+% FIXME: what about anonymity effects of coin selection?
+
+\subsection{Wallet Detection}
+When websites such as merchants or banks try to signal the Taler wallet---for example,
+to request a payment or trigger reserve creation---it is possible that the
+customer simply has no Taler wallet installed. To accommodate for this situation in a
+user-friendly way, the HTTP response containing signaling to wallet should
+contain as response body an HTML page with (1) a \texttt{taler:} link to
+manually open loosely integrated wallets and (2) instructions on how to install
+a Taler wallet if the user does not already have one.
+
+It might seem useful to dynamically update page content depending on whether
+the Taler wallet is installed, for example, to hide or show a ``Pay with Taler''
+or ``Withdraw to Taler wallet'' option. This functionality cannot be provided
+in general, as only the definitive presence of a wallet can be detected, but
+not its absence when the wallet is only loosely integrated in the user's
+browser via a handler for the \texttt{taler:} URI scheme.
+
+We nevertheless consider the ability to know whether a customer has definitely
+installed a Taler wallet useful (if only for the user to confirm that the
+installation was successful), and expose two APIs to query this. The first one
+is JavaScript-based and allows to register a callback for the when
+presence/absence of the wallet is detected. The second method works without
+any JavaScript on the merchant's page, and uses CSS~\cite{sheets1998level} to dynamically show/hide
+element on the page marked with the special \texttt{taler-installed-show} and
+\texttt{taler-installed-hide} CSS classes, whose visibility is changed when
+a wallet browser extension is loaded.
+
+Browser fingerprinting \cite{mulazzani2013fast} is a concern with any
+additional APIs made available to websites, either by the browser itself or by
+browser extensions. Since a website can simply try to trigger a payment to
+determine whether a tightly integrated Taler wallet is installed, one bit of
+additional fingerprinting information is already available through the usage of
+Taler. The dynamic detection methods do not, however, expose any information
+that is not already available to websites by signaling the wallet through HTTP
+headers.
+
+\subsection{Backup and Synchronization}
+While users can manually import and export the state of the wallet, at the time
+of writing this, automatic backup and synchronization between wallets is not
+implemented yet. We discuss the challenges with implementing backup and
+synchronization in a privacy-preserving manner in
+Chapter~\ref{sec:future-work-backup-sync}.
+
+
+\subsection{Wallet Liquidation}
+If a customer wishes to stop using GNU Taler, they can deposit the remaining
+coins in their wallet back to their own bank account. We call this process
+\emph{liquidation}.
+
+In deployments with relatively lenient KYC regulation, the normal deposit
+functionality used by merchants is used for wallet liquidation. The wallet
+simply acts as a merchant for one transaction, and asks the exchange to deposit
+the coins into the customer's bank account.
+
+However in deployments with strict KYC regulations, the customer would first
+have to register and complete a KYC registration procedure with the exchange.
+To avoid this, liquidation can be implemented as a modified deposit, which
+restricts the payment to the bank account that was used to create a reserve of
+the customer.
+
+The exchange cannot verify that a coin that is being liquidated actually
+originated the reserve that the customer claims it originated from, unless the
+user reveals the protocol transcripts for withdrawal and refresh operations on
+that coin, violating their privacy. Instead, each reserve tracks the amount
+that was liquidated into it, and the exchange rejects a liquidation request if
+the liquidated amount exceeds the amount that was put into the reserve. Note
+that liquidation does not refill the funds of a reserve, but instead triggers a
+bank transfer of the liquidated amount to the bank account that
+created the reserve.
+
+
+\subsection{Wallet Signaling}
+We now define more precisely the algorithm that the wallet executes when a
+website signals to that wallet that an operation related to payments should be
+triggered, either by opening a \texttt{taler:pay} URL or by responding
+with HTTP 402 and at least one Taler-specific header.
+
+% FIXME: need to specify what happens when gateway_origin="", for example when triggered
+% via URL
+The steps to process a payment trigger are as follows. The algorithm takes the
+following parameters: \texttt{current\_url} (the URL of the page that
+raises the 402 status or \texttt{null} if triggered by a \texttt{taler:pay} URL),
+\texttt{contract\_url}, \texttt{resource\_url}, \texttt{session\_id},
+\texttt{offer\_url}, \texttt{refund\_url}, \texttt{tip\_token} (from the
+``Taler-\dots'' headers or \emph{taler:pay} URL parameters respectively)
+\begin{enumerate}
+ \item If \texttt{resource\_url} a non-empty string, set \texttt{target\_url} to \texttt{resource\_url},
+ otherwise set \texttt{target\_url} to \texttt{current\_url}.
+ \item If \texttt{target\_url} is empty, stop.
+ \item If there is an existing payment $p$ whose
+ fulfillment URL equals \texttt{target\_url} and either \texttt{current\_url} is \texttt{null}
+ or \texttt{current\_url} has the same origin as
+ either the fulfillment URL or payment URL in the contract terms, then:
+ \begin{enumerate}[label*=\arabic*.]
+ \item If \texttt{session\_id} is non-empty and the last session ID for payment $p$ was recorded
+ in the wallet with session signature $sig$, construct a fulfillment instance URL from $sig$
+ and the order ID of $p$.
+ \item Otherwise, construct an extended fulfillment URL from the order ID of $p$.
+ \item Navigate to the extended fulfillment URL constructed in the previous step and stop.
+ \end{enumerate}
+ \item If \texttt{contract\_url} is a non-empty URL, execute the steps for
+ processing a contract URL (with \texttt{session\_id}) and stop.
+ \item If \texttt{offer\_url} is a non-empty URL, navigate to it and stop.
+ \item If \texttt{refund\_url} is a non-empty URL, process the refund and stop.
+ \item If \texttt{tip\_url} is a non-empty URL, process the tip and stop.
+\end{enumerate}
+
+For interactive web applications that request payments, such as games or single
+page apps (SPAs), the payments initiated by navigating to a page with HTTP
+status code 402 are not appropriate, since the state of the web application is
+destroyed by the navigation. Instead the wallet can offer a JavaScript-based
+API, exposed as a single function with a subset of the parameters of the
+402-based payment: \texttt{contract\_url}, \texttt{resource\_url},
+\texttt{session\_id} \texttt{refund\_url}, \texttt{offer\_url},
+\texttt{tip\_token}. Instead of navigating away, the result of the operation
+is returned as a JavaScript promise (either a payment receipt, refund
+confirmation, tip success status or error). If user input is required (e.g., to
+ask for a confirmation for a payment), the page's status must not be destroyed.
+Instead, an overlay or separate tab/window displays the prompt to the user.
+% FIXME: should be specify the full algorithm for JS payments?
+
+% FIXME talk about clickjacking
+
+
+
+\newcommand\inecc{\in \mathbb{Z}_{|\mathbb{E}|}}
+\newcommand\inept{\in {\mathbb{E}}}
+\newcommand\inrsa{\in \mathbb{Z}_{|\mathrm{dom}(\FDH_K)|}}
+
+\section{Cryptographic Protocols}
+
+\def\HKDF{\textrm{HKDF}}
+\def\KDF{\textrm{KDF}}
+\def\FDH{\textrm{FDH}}
+\newcommand{\iseq}{\stackrel{?}{=}}
+\newcommand{\iseqv}{\stackrel{?}{\equiv}}
+\newcommand{\pccheck}{\mathbf{check}\ }
+
+In this section, we describe the main cryptographic protocols for Taler in more
+detail. The more abstract, high-level protocols from
+Section~\ref{sec:crypto:instantiation} are instantiated and and embedded in
+concrete protocol diagrams that can hopefully serve as a reference for
+implementors.
+
+For ease of presentation, we do not provide a bit-level description of the
+cryptographic protocol. Some details from the implementation are left out,
+such as fees, additional timestamps in messages and checks for the expiration
+of denominations. Furthermore, we do not specify the exact responses in the
+error cases, which in the actual implementation should include signatures that
+could be used during a legal dispute. Similarly, the actual implementation
+contains some additional signatures on messages sent that allow to prove to a
+third party that a participant did not follow the protocol.
+
+As we are dealing with financial transactions, we explicitly describe whenever
+entities need to safely write data to persistent storage. As long as the data
+persists, the protocol can be safely resumed at any step. Persisting data is
+cumulative, that is an additional persist operation does not erase the
+previously stored information.
+
+The implementation also records additional entries in the exchange's database
+that are needed for auditing.
+
+\subsection{Preliminaries}
+In our protocol definitions, we write $\mathbf{check}\ \mathrm{COND}$ to abort
+the protocol with an error if the condition $\mathrm{COND}$ is false.
+
+We use the following algorithms:
+\begin{itemize}
+\item $\algo{Ed25519.Keygen}() \mapsto \langle \V{sk}, \V{pk} \rangle$
+ to generate an Ed25519 key pair.
+ \item $\algo{Ed25519.GetPub}(\V{sk}) \mapsto \V{pk}$ to derive the public key from
+ an Ed25519 public key.
+ \item $\algo{Ed25519.Sign}(\V{sk}, m) \mapsto \sigma$ to create a signature $\sigma$
+ on message $m$ using secret key $\V{sk}$.
+ \item $\algo{Ed25519.Verify}(\V{pk}, \sigma, m) \mapsto b$ to check if $\sigma$ is
+ a valid signature from $\V{pk}$ on message $m$.
+ \item $\mathrm{HKDF}(n, k, s) \mapsto m$ is the HMAC-based key derivation function \cite{rfc5869},
+ producing an output $m$ of $n$ bits from the input key material $k$ and salt $s$.
+\end{itemize}
+
+We write $\mathbb{Z}^*_N$ for the multiplicative group of integers modulo $N$.
+Given an $r \in \mathbb{Z}^*_N$, we write $r^{-1}$ for the multiplicative
+inverse modulo $N$ of $r$.
+
+We write $H(m)$ for the SHA-512 hash of a bit string.
+
+We write $\FDH(N,m)$ for the full domain hash that maps the bit string $m$ to
+an element of $\mathbb{Z}^*_N$. Specifically, $\FDH(N,m)$ is computed by
+first computing $H(m)$. Let $b := \lceil \log_2 N\rceil$. The full domain
+hash is then computed by iteratively computing a HKDF to obtain $b$ bits of
+output until the $b$-bit value is below $N$. The inputs to the HKDF are a
+``secret key'', a fixed context plus a 16-bit counter (in big endian) as a
+context chunk that is incremented until the computation succeeds. For the
+source key material, we use a binary encoding of the public RSA key with
+modulus $N$.\footnote{So technically, it is $\FDH(N,e,m)$, but we use the
+ simplified notation $\FDH(N,m)$.} Here, the public RSA key is encoded by
+first expressing the number of bits of the modulus and the public exponent as
+16-bit numbers in big endian, followed by the two numbers (again in unsigned
+big endian encoding).\footnote{See
+ \texttt{GNUNET\_CRYPTO\_rsa\_public\_key\_encode()}.} For the context, the
+C-string ``RSA-FDA FTpsW!'' (without 0-termination) is used. For the KDF, we
+instantiate the HKDF described in RFC 5869~\cite{rfc5869} using HMAC-SHA512 as
+XTR and HMAC-SHA256 as PRF*.\footnote{As suggested in
+ \url{http://eprint.iacr.org/2010/264.pdf}} Let the result of the first
+successful iteration of the HKDF function be $r$ with $0 \le r < N$. Then, to
+protect against a malicious exchange when blinding values, the $FDH(N,m)$
+function checks that $\gcd(r,n) = 1$. If not, the $\FDH(n,m)$ calculation
+fails because $n$ is determined to be malicious.
+
+The expression $x \randsel X$ denotes uniform random selection of an element
+$x$ from set $X$. We use $\algo{SelectSeeded}(s, X) \mapsto x$ for pseudo-random uniform
+selection of an element $x$ from set $X$ and seed $s$. Here, the result is deterministic for fixed inputs $s$ and $X$.
+
+The exchange's denomination signing key pairs $\{\langle \V{skD}_i, \V{pkD}_i \rangle \}$ are RSA keys pairs,
+and thus $\V{pkD}_i = \langle e_i, N_i \rangle$, $\V{skD_i} = d_i$. We write $D(\V{pkD}_i)$ for the
+financial value of the denomination $\V{pkD}_i$.
+
+% FIXME: explain RSA keys of exchange
+
+
+\subsection{Withdrawing}
+The withdrawal protocol is defined in Figure~\ref{fig:withdraw-protocol}.
+The following additional algorithms are used, which we only define informally here:
+\begin{itemize}
+ \item $\algo{CreateBalance}(W_p, v) \mapsto \bot$ is used by the exchange,
+ and has the side-effect of creating a reserve record with balance $v$
+ and reserve public key (effectively the identifier of the reserve) $W_p$.
+ \item $\algo{GetWithdrawR}(\rho) \mapsto \{\bot,\overline{\sigma}_C\}$
+ is used by the exchange, and checks
+ if there is an existing withdraw request $\rho$. If the existing request
+ exists, the existing blind signature $\overline{\sigma}_C$ over
+ coin $C$ is returned. On a fresh request, $\bot$ is
+ returned.
+ \item $\algo{BalanceSufficient}(W_s,\V{pkD}_t) \mapsto b$ is used by the exchange, and
+ returns true if the balance in the reserve identified by $W_s$ is sufficient to
+ withdraw at least one coin if denomination $\V{pkD}_t$.
+ \item $\algo{DecreaseBalance}(W_s,\V{pkD}_t) \mapsto \bot$ is used by the exchange, and
+ decreases the amount left in the reserve identified by $W_s$ by the amount $D(\V{pkD}_t)$
+ that the denomination $\V{pkD}_t$ represents.
+\end{itemize}
+
+\begin{figure}
+\centering
+\fbox{%
+\pseudocode[codesize=\small]{%
+\textbf{Customer} \< \< \textbf{Exchange} \\
+ \text{Knows } \{ \langle e_i,N_i \rangle \} = \{\V{pkD}_i\} \< \< \text{Knows } \{ \langle \V{skD}_i, \V{pkD}_i \rangle \} \pclb
+\pcintertext[dotted]{Create Reserve}
+\langle w_s, W_p \rangle \leftarrow \algo{Ed25519.Keygen}() \< \< \\
+\text{Persist reserve } \langle w_s,v \rangle \< \< \\
+ \< \sendmessageright{top={Bank transfer}, bottom={(subject: $W_p$, amount: $v$)},style=dashed} \< \\
+ \< \< \algo{CreateBalance}(W_p, v) \pclb
+\pcintertext[dotted]{Prepare Withdraw}
+\text{Choose $t$ with $\V{pkD}_t \in \{\V{pkD}_i\}$} \< \< \\
+\langle c_s, C_p \rangle \leftarrow \algo{Ed25519.Keygen}() \< \< \\
+ r \randsel \mathbb{Z}^*_N \< \< \\
+ \text{Persist planchet } \langle c_s, r \rangle \< \< \pclb
+\pcintertext[dotted]{Execute Withdraw}
+\overline{m} := \FDH(N_t, C_p) \cdot r^{e_t} \bmod N_t \< \< \\
+\rho_W := \langle \V{pkD}_t, \overline{m} \rangle \< \< \\
+\sigma_W := \algo{Ed25519.Sign}(w_s, \rho_W) \< \< \\
+\< \sendmessageright*{\rho := \langle W_p, \sigma_W, \rho_W \rangle} \< \\
+ \< \< \pccheck \V{pkD}_t \in \{ \V{pkD}_i \} \\
+\< \< \pccheck \algo{Ed25519.Verify}(W_p,\rho_W,\sigma_W) \\
+\< \< x \leftarrow \algo{GetWithdraw}(\rho) \\
+\< \< \pcif x \iseq \bot \\
+\< \< \t \pccheck \algo{BalanceSufficient}(W_p,\V{pkD}_t) \\
+\< \< \t \algo{DecreaseBalance}(W_p, \V{pkD}_t) \\
+\< \< \t \text{Persist withdrawal $\rho$} \\
+\< \< \t \overline{\sigma}_C := (\overline{m})^{\V{skD}_t} \bmod N_t \\
+\< \< \pcelse \\
+\< \< \t \overline{\sigma}_C := x \\
+\< \sendmessageleft*{\overline{\sigma}_C} \< \\
+\sigma_C := r^{-1}\overline{\sigma}_C \< \< \\
+\pccheck \sigma_C^{e_t} \iseqv_{N_t} \FDH(N_t, C_p) \< \< \\
+\text{Persist coin $\langle \V{pkD}_t, c_s, C_p, \sigma_C \rangle$} \< \< \\
+}
+}
+\caption[Withdraw protocol diagram.]{Withdrawal protocol diagram.}
+\label{fig:withdraw-protocol}
+\end{figure}
+
+\subsection{Payment transactions}
+The payment protocol is defined in two parts. First, the spend protocol in
+Figure~\ref{fig:payment-spend} defines the interaction between a merchant and
+a customer. The customer obtains the contract terms (as $\rho_P$) from the
+merchant, and sends the merchant deposit permissions as a payment. The deposit protocol
+in Figure~\ref{fig:payment-deposit} defines how subsequently the merchant sends the
+deposit permissions to the exchange to detect double-spending and ultimately
+to receive a bank transfer from the exchange.
+
+Note that in practice the customer can also execute the deposit
+protocol on behalf of the merchant. This is useful in situations where
+the customer has network connectivity but the merchant does not. It
+also allows the customer to complete a payment before the payment
+deadline if a merchant unexpectedly becomes unresponsive, allowing the
+customer to later prove that they paid on time.
+
+We limit the description to one exchange here, but in practice, the merchant
+communicates to the customer the exchanges that it supports, in addition to the
+account information $A_M$ that might differ between exchanges.
+
+We use the following algorithms, defined informally here:
+\begin{itemize}
+ \item $\algo{SelectPayCoins}(v, E_M) \mapsto \{ \langle \V{coin}_i, f_i \rangle \}$ selects
+ fresh coins (signed with denomination keys from exchange $E_M$)
+ to pay for the amount $v$. The result is a set of coins
+ together with the fraction of each coin that must be spent such that
+ the amounts contributed by all coins sum up to $v$.
+ \item $\algo{MarkDirty}(\V{coin}, f) \mapsto \bot$ subtracts the fraction $f$ from the
+ available amount left on a coin, and marks the coin as dirty (to trigger refreshing
+ in case $f$ is below the denomination value). Thus, assuming the coin has
+ any residual value, the customer's wallet will do a refresh on $\V{coin}$
+ and not use it for further payments. This provides unlinkability of transactions
+ made with change arising from paying with fractions of a coin's denomination.
+ \item $\algo{Deposit}(E_M, D_i) \mapsto b$ executes the second part of the payment protocol
+ (i.e., the deposit) with exchange $E_M$, using deposit permission $D_i$.
+ \item $\algo{GetDeposit}(C_p, h) \mapsto \{\bot,\rho_{(D,i)}\}$ checks in the exchange's database
+ for an existing processed deposit permission on coin $C_p$ for the contract
+ identified by $h$. The algorithm returns the existing deposit permission $\rho_{(D,i)}$, or $\bot$ if a
+ matching deposit permission is not recorded.
+ \item $\algo{IsOverspending}(C_p, \V{pkD}, f) \mapsto b$ checks in the exchange's database
+ if there if at least the fraction $f$ of the coin $C_p$ of denomination $\V{pkD}$ is still available
+ for use, based on existing spend/withdraw records of the exchange.
+ \item $\algo{MarkFractionalSpend}(C_p, f) \mapsto \bot$ adds a spending
+ record to the exchanges database, indicating
+ that fraction $f$ of coin $C_p$ has been spent (in addition to
+ existing spending/refreshing records).
+ \item $\algo{ScheduleBankTransfer}(A_M, f, \V{pkD}, h_c) \mapsto \bot$
+ schedules a bank transfer from the exchange to
+ the account identified by $A_M$, for subject $h_c$ and for the amount $f\cdot D(\V{pkD})$.
+ % NOTE: actual implementation avoids multiplication (messy!) and would
+ % simply require f \le D(\V{pkD})!
+\end{itemize}
+
+
+\begin{figure}
+\centering
+\fbox{%
+\pseudocode[codesize=\footnotesize]{%
+\textbf{Customer} \< \< \textbf{Merchant} \\
+\text{Knows } \V{pkM} \< \< \text{Knows } \langle \V{pkM}, \V{skM} \rangle \\
+\< \sendmessageright{top={Select product/service},style=dashed} \< \\
+\< \< \text{Determine:} \\
+\< \< \text{$\bullet$ } v \text{ (price) } \\
+\< \< \text{$\bullet$ } E_M \text{ (exchange) } \\
+\< \< \text{$\bullet$ } A_M \text{ (acct.) } \\
+\< \< \text{$\bullet$ } \V{info} \text{ (free-form details) } \\
+\< \sendmessageleft{top={Request payment},style=dashed} \< \\
+\langle p_s, P_p \rangle \leftarrow \algo{Ed25519.Keygen}() \< \< \\
+\text{Persist ownership identifier } p_s \< \< \\
+\< \sendmessageright*{P_p} \< \\
+ \< \< \rho_P := \langle E_M, A_M, \V{pkM}, H(\langle v, \V{info}\rangle), P_p \rangle \\
+\< \< \sigma_P := \V{Ed25519.Sign}(\V{skM}, \rho_P) \\
+\< \sendmessageleft*{\rho_P, \sigma_P, v, \V{info}} \< \\
+ \langle M, A_M, \V{pkM}, h', P_p' \rangle := \rho_P \< \< \\
+\pccheck \algo{Ed25519.Verify}(pkM, \sigma_P, \rho_P) \< \< \\
+\pccheck P_p' \iseq P_p \< \< \\
+\pccheck h' \iseq H(\langle v, \V{info} \rangle ) \< \< \\
+\V{cf} \leftarrow \algo{SelectPayCoins}(v, E_M) \< \< \\
+\pcfor \langle \V{coin_i,f_i} \rangle \in \V{cf} \< \< \\
+\t \algo{MarkDirty}(\V{coin}_i, f_i) \< \< \\
+\t \langle c_s, C_p, \V{pkD}, \sigma_C \rangle := \V{coin}_i \< \< \\
+ \t \rho_{(D,i)} := \langle C_p, \V{pkD}, \sigma_C, f_i, H(\rho_P), A_M, \V{pkM} \rangle \< \< \\
+\t \sigma_{(D,i)} := \V{Ed25519.Sign}(c_s, \rho_{(D,i)}) \< \< \\
+ \text{Persist } \langle \sigma_P, \V{cf}, \rho_P, \rho_{(D,i)}, \sigma_{(D,i)}, v, \V{info} \rangle \< \<\\
+\< \sendmessageright*{ \mathcal{D} := \{\langle \rho_{(D,i)}, \sigma_{(D,i)}\rangle \}} \< \\
+\< \< \pcfor D_i \in \mathcal{D} \\
+\< \< \t \pccheck \algo{Deposit}(E_M, D_i) \\
+}
+}
+\caption[Spend protocol diagram.]{Spend Protocol executed between customer and merchant for the purchase
+ of an article of price $v$ using coins from exchange $E_M$. The merchant has
+ provided his account details to the exchange under an identifier $A_M$.
+ The customer can identify themselves as the one who received the offer
+ using $p_s$.
+ %This prevents multiple customers from legitimately paying for the
+ %same article and potentially exhausting stocks.
+}
+\label{fig:payment-spend}
+\end{figure}
+
+\begin{figure}
+\centering
+\fbox{%
+\pseudocode[codesize=\small]{%
+\textbf{Customer/Merchant} \< \< \textbf{Exchange} \\
+\text{Knows } \V{pkESig} \< \< \text{Knows } \V{skESig}, \V{pkESig}, \{ \V{pkD}_i \} \\
+\text{Knows } D_i = \langle \rho_{(D,i)}, \sigma_{(D,i)} \rangle \< \< \\
+\< \sendmessageright*{ D_i } \< \\
+\< \< \langle \rho_{(D,i)}, \sigma_{(D,i)} \rangle := D_i \\
+ \< \< \langle C_p, \V{pkD}, \sigma_C, f_i, h, A_M, \V{pkM} \rangle := \rho_{(D,i)} \\
+\< \< \pccheck \V{pkD} \in \{ \V{pkD}_i \} \\
+\< \< \langle e, N \rangle := \V{pkD} \\
+\< \< \pccheck \algo{Ed25519.Verify}(C_p, \sigma_{(D,i)}, \rho_{(D,i)}) \\
+\< \< x \leftarrow \algo{GetDeposit}(C_p, h) \\
+\< \< \pcif x \iseq \bot \\
+\< \< \t \pccheck \sigma_C^{e} \iseqv_N \FDH(N, C_p) \\
+\< \< \t \pccheck \neg\algo{IsOverspending}(C_p, \V{pkD}, f) \\
+\< \< \t \text{Persist deposit-record } D_i \\
+\< \< \t \algo{MarkFractionalSpend}(C_p, f) \\
+\< \< \t \algo{ScheduleBankTransfer}(A_M, f, \V{pkD}, h_c) \\
+\< \< \pcelse \\
+\< \< \t \pccheck x \iseq \rho_{(D,i)} \\
+\< \< \sigma_{DC} \leftarrow \algo{Ed25519.Sign}(\V{pkESig}, \rho_{(D,i)}) \\
+\< \sendmessageleft*{ \sigma_{DC} } \< \\
+\pccheck \algo{Ed25519.Verify} \\{}\qquad(\V{pkESig}, \sigma_{DC}, \rho_{(D,i)}) \< \< \\
+}
+}
+\caption[Deposit protocol diagram.]{Deposit Protocol run for each deposited coin $D_i \in {\cal D}$ with the
+ exchange that signed the coin.}
+\label{fig:payment-deposit}
+\end{figure}
+
+
+\subsection{Refreshing and Linking}
+The refresh protocol is defined in Figures~\ref{fig:refresh-part1} and
+\ref{fig:refresh-part2}. The refresh protocol allows the customer to
+obtain change for the remaining fraction of the value of a coin. The
+change is generated as a fresh coin that is unlinkable to the dirty
+coin to anyone except for the owner of the dirty coin.
+
+A na\"ive implementation of a refresh protocol that just gives the customer a
+new coin could be used for peer-to-peer transactions that hides income from tax
+authorities. Thus, (with probability $(1-1/\kappa)$) the refresh protocol
+records information that allows the owner of the original coin to obtain the
+refreshed coin from the original coin via the linking protocol (illustrated in
+Figure~\ref{fig:link}).
+
+We use the following algorithms, defined informally here:
+\begin{itemize}
+ \item \algo{RefreshDerive} is defined in Figure~\ref{fig:refresh-derive}.
+ \item $\algo{GetOldRefresh}(\rho_{RC}) \mapsto \{\bot,\gamma\}$ returns the past
+ choice of $\gamma$ if $\rho_{RC}$ is a refresh commit message that has been seen before,
+ and $\bot$ otherwise.
+ \item $\algo{IsConsistentChallenge}(\rho_{RC}, \gamma) \mapsto \{ \bot,\top \}$ returns
+ $\top$ if no refresh-challenge has been persisted for the refresh operation by commitment
+ $\rho_{RC}$ or $\gamma$ is consistent with the persisted (and thus previously received) challenge;
+ returns $\bot$ otherwise.
+ \item $\algo{LookupLink}(C_p) \mapsto \{ \langle \rho_{L}^{(i)}, \sigma_L^{(i)},
+ \overline{\sigma}_C^{(i)} \rangle \}$ looks up refresh records on coin with public key $C_p$ in
+ the exchange's database and returns the linking message $\rho_L^{(i)}$, linking
+ signature $\sigma_L^{(i)}$ and blinded signature $\overline{\sigma}_C^{(i)}$ for each refresh
+ record $i$.
+\end{itemize}
+
+
+\begin{figure}
+\centering
+\fbox{%
+\procedure[codesize=\small]{$\algo{RefreshDerive}(s, \langle e, N \rangle, C_p)$}{%
+t := \HKDF(256, s, \texttt{"t"}) \\
+T := \algo{Curve25519.GetPub}(t) \\
+x := \textrm{ECDH-EC}(t, C_p) \\
+r := \algo{SelectSeeded}(x, \mathbb{Z}^*_{N}) \\
+c_s := \HKDF(256, x, \texttt{"c"}) \\
+C_p := \algo{Ed25519.GetPub}(c_s) \\
+\overline{m} := r^{e}\cdot C_p \mod N \\
+\pcreturn \langle t, T, x, c_s, C_p, \overline{m} \rangle
+}
+}
+\caption[RefreshDerive algorithm]{The RefreshDerive algorithm running with the seed $s$ on dirty coin $C_p$ to
+ generate a fresh coin to be later signed with denomination key $pkD := \langle e,N\rangle$.}
+\label{fig:refresh-derive}
+\end{figure}
+
+
+\begin{figure}
+\centerline{
+\fbox{%
+\pseudocode[codesize=\footnotesize]{%
+\textbf{Customer} \< \< \textbf{Exchange} \\
+\text{Knows } \{ \V{pkD}_i \} \< \< \text{Knows } \{\langle \V{skD}_i, \V{pkD}_i \rangle \} \\
+\text{Knows } \V{coin}_0 = \langle \V{pkD}_0, c_s^{(0)}, C_p^{(0)}, \sigma_{C}^{(0)} \rangle \< \< \\
+\text{Select } \langle N_t, e_t \rangle := \V{pkD}_t \in \{ \V{pkD}_i \} \< \< \\
+\pcfor i = 1,\dots,\kappa \< \< \\
+\t s_i \randsel \{0,1\}^{256} \< \< \\
+\t X_i := \algo{RefreshDerive}(s_i, \V{pkD}_t, C_p^{(0)}) \< \< \\
+\t (t_i, T_i, x_i, c_s^{(i)}, C_p^{(i)}, \overline{m}_i) := X_i \< \< \\
+h_T := H(T_1,\dots,T_\kappa) \< \< \\
+h_{\overline{m}} := H(\overline{m}_1,\dots,\overline{m}_\kappa) \< \< \\
+h_C := H(h_t, h_{\overline{m}}) \< \< \\
+\rho_{RC} := \langle h_C, \V{pkD}_t, \V{pkD}_0, C_p^{(0)}, \sigma_{C}^{(0)} \rangle \< \< \\
+\sigma_{RC} := \algo{Ed25519.Sign}(c_s^{(0)}, \rho_{RC}) \< \< \\
+\text{Persist refresh-request } \langle \rho_{RC}, \sigma_{RC} \rangle \< \< \\
+\< \sendmessageright*{ \rho_{RC}, \sigma_{RC} } \< \\
+\< \< (h_C, \V{pkD}_t, \V{pkD}_0, C_p^{(0)}, \sigma_{C}^{(0)}) := \rho_{RC} \\
+\< \< \pccheck \algo{Ed25519.Verify}(C_p^{(0)}, \sigma_{RC}, \rho_{RC}) \\
+\< \< x \leftarrow \algo{GetOldRefresh}(\rho_{RC}) \\
+\< \< \pcif x \iseq \bot \\
+\< \< \t v := D(\V{pkD}_t) \\
+\< \< \t \langle e_0, N_0 \rangle := \V{pkD}_0 \\
+\< \< \t \pccheck \neg\algo{IsOverspending}(C_p^{(0)}, \V{pkD}_0, v) \\
+\< \< \t \pccheck \V{pkD}_t \in \{ \V{pkD}_i \} \\
+\< \< \t \pccheck \FDH(N_0, C_p^{(0)}) \iseqv_{N_0} (\sigma_0^{(0)})^{e_0} \\
+\< \< \t \algo{MarkFractionalSpend}(C_p^{(0)}, v) \\
+\< \< \t \gamma \randsel \{1,\dots,\kappa\} \\
+\< \< \t \text{Persist refresh-record } \langle \rho_{RC},\gamma \rangle \\
+\< \< \pcelse \\
+\< \< \t \gamma := x \\
+\< \sendmessageleft*{ \gamma } \< \pclb
+\pcintertext[dotted]{(Continued in Figure~\ref{fig:refresh-part2})}
+}}}
+\caption{Refresh Protocol (Commit Phase)}
+\label{fig:refresh-part1}
+\end{figure}
+
+\begin{figure}
+\centerline{
+\fbox{%
+\pseudocode[codesize=\footnotesize]{%
+\textbf{Customer} \< \< \textbf{Exchange} \pclb
+\pcintertext[dotted]{(Continuation of \ref{fig:refresh-part1})} \\
+\< \sendmessageleft*{ \gamma } \< \\
+\pccheck \algo{IsConsistentChallenge}(\rho_{RC}, \gamma) \< \< \\
+\text{Persist refresh-challenge $\langle \rho_{RC}, \gamma \rangle$} \< \< \\
+S := \langle s_1,\dots,s_{\gamma-1},s_{\gamma+1},\dots,s_\kappa \rangle \< \< \\
+\rho_{L} = \langle C_p^{(0)}, \V{pkD}_t, T_\gamma, \overline{m}_\gamma \rangle \< \< \\
+\rho_{RR} = \langle T_\gamma, \overline{m}_\gamma, S \rangle \< \< \\
+\sigma_{L} = \algo{Ed25519.Sign}(c_s^{(0)}, \rho_{L}) \< \< \\
+\< \sendmessageright*{ \rho_{RR}, \rho_{L}, \sigma_{L} } \< \\
+\< \< \langle T'_\gamma, \overline{m}'_\gamma, S \rangle := \rho_{RR} \\
+\< \< \langle s_1,\dots,s_{\gamma-1},s_{\gamma+1},\dots,s_\kappa \rangle ) := S \\
+\< \< \pccheck \algo{Ed25519.Verify}(C_p^{(0)}, \sigma_L, \rho_L) \\
+\< \< \pcfor i = 1,\dots,\gamma-1,\gamma+1,\dots,\kappa \\
+\< \< \t X_i := \algo{RefreshDerive}(s_i, \V{pkD}_t, C_p^{(0)}) \\
+\< \< \t \langle t_i, T_i, x_i, c_s^{(i)}, C_p^{(i)}, \overline{m}_i \rangle := X_i \\
+\< \< h_T' = H(T_1,\dots,T_{\gamma-1},T'_{\gamma},T_{\gamma+1},\dots,T_\kappa) \\
+\< \< h_{\overline{m}}' = H(\overline{m}_1,\dots,\overline{m}_{\gamma-1},\overline{m}'_{\gamma},\overline{m}_{\gamma+1},\dots,\overline{m}_\kappa) \\
+\< \< h_C' = H(h_T', h_{\overline{m}}') \\
+\< \< \pccheck h_C \iseq h_C' \\
+\< \< \overline{\sigma}_C^{(\gamma)} := \overline{m}^{skD_t} \\
+\< \sendmessageleft*{\overline{\sigma}_C^{(\gamma)}} \< \\
+\sigma_C^{(\gamma)} := r^{-1}\overline{\sigma}_C^{(\gamma)} \< \< \\
+\pccheck (\sigma_C^{(\gamma)})^{e_t} \iseqv_{N_t} C_p^{(\gamma)} \< \< \\
+\text{Persist coin $\langle \V{pkD}_t, c_s^{(\gamma)}, C_p^{(\gamma)}, \sigma_C^{(\gamma)} \rangle$} \< \< \\
+}}}
+\caption{Refresh Protocol (Reveal Phase)}
+\label{fig:refresh-part2}
+\end{figure}
+
+\begin{figure}
+\centering
+\fbox{%
+\pseudocode[codesize=\footnotesize]{%
+\textbf{Customer} \< \< \textbf{Exchange} \\
+\text{Knows } \V{coin}_0 = \langle \V{pkD}_0, c_s^{(0)}, C_p^{(0)}, \sigma_{C}^{(0)} \rangle \< \< \\
+\< \sendmessageright*{C_p^{(0)}} \< \\
+\< \< L := \algo{LookupLink}(C_p^{(0)}) \\
+\< \sendmessageleft*{L} \< \\
+\pcfor \langle \rho_{L}^{(i)}, \overline{\sigma}_L^{(i)}, \sigma_C^{(i)} \rangle \in L \< \< \\
+\t \langle \hat{C}_p^{(i)}, \V{pkD}_t^{(i)}, T_\gamma^{(i)}, \overline{m}_\gamma^{(i)} \rangle := \rho_L^{(i)} \< \< \\
+\t \langle e_t^{(i)}, N_t^{(i)} \rangle := \V{pkD}_t^{(i)} \< \< \\
+\t \pccheck \hat{C}_p^{(i)} \iseq C_p^{(0)} \< \< \\
+\t \pccheck \algo{Ed25519.Verify}(C_p^{(0)}, \rho_{L}^{(i)}, \sigma_L^{(i)})\< \< \\
+\t x_i := \algo{ECDH}(c_s^{(0)}, T_\gamma^{(i)}) \< \< \\
+\t r_i := \algo{SelectSeeded}(x_i, \mathbb{Z}^*_{N_t}) \\
+\t c_s^{(i)} := \HKDF(256, x_i, \texttt{"c"}) \\
+\t C_p^{(i)} := \algo{Ed25519.GetPub}(c_s^{(i)}) \\
+\t \sigma_C^{(i)} := (r_i)^{-1} \cdot \overline{m}_\gamma^{(i)} \\
+\t \pccheck (\sigma_C^{(i)})^{e_t^{(i)}} \iseqv_{N_t^{(i)}} C_p^{(i)} \\
+\t \text{(Re-)obtain coin } \langle \V{pkD}_t^{(i)}, c_s^{(i)}, C_p^{(i)}, \sigma_C^{(i)} \rangle
+}
+}
+\caption{Linking protocol}
+\label{fig:link}
+\end{figure}
+
+\clearpage
+
+\subsection{Refunds}
+The refund protocol is defined in Figure~\ref{fig:refund}. The customer
+requests from the merchant that a deposit should be ``reversed'', and if the
+merchants allows the refund, it authorizes the exchange to apply the refund and
+sends the refund confirmation back to the customer. Note that in practice,
+refunds are only possible before the refund deadline, which is not considered
+here.
+
+We use the following algorithms, defined informally here:
+\begin{itemize}
+ \item $\algo{ShouldRefund}(\rho_P, m) \mapsto \{ \top, \bot \}$ is used by the merchant to
+ check whether a refund with reason $m$ should be given for the purchase identified by the
+ contract terms $\rho_P$. The decision is made according to the merchant's business rules.
+ \item $\algo{LookupDeposits}(\rho_P, m) \mapsto \{ \langle \rho_{(D,i)},
+ \sigma_{(D,i)} \rangle \}$ is used by the merchant to retrieve deposit
+ permissions that were previously sent by the customer and already deposited
+ with the exchange.
+ \item $\algo{RefundDeposit}(C_p, h, f, \V{pkM})$ is used by the exchange to
+ modify its database. It (partially) reverses the amount $f$ of a deposit
+ of coin $C_p$ to the merchant $\V{pkM}$ for the contract identified by $h$.
+ The procedure is idempotent, and subsequent invocations with a larger $f$
+ increase the refund.
+\end{itemize}
+
+\begin{figure}
+\centerline{
+\fbox{%
+\pseudocode[codesize=\footnotesize]{%
+\< \< \pclb
+\pcintertext[dotted]{Request refund} \\
+\textbf{Customer} \< \< \textbf{Merchant} \\
+\text{Knows } \V{pkM}, \V{pkESig} \< \< \text{Knows } \langle \V{pkM}, \V{skM} \rangle, \V{pkESig} \\
+\< \sendmessageright{top={Ask for refund},bottom={(Payment $\rho_P$, reason $m$)},style=dashed} \< \\
+\< \< \pccheck \algo{ShouldRefund}(\rho_P,m) \pclb
+\pcintertext[dotted]{Execute refund} \\
+\textbf{Exchange} \< \< \textbf{Merchant} \\
+\text{Knows } \langle \V{skESig}, \V{pkESig} \rangle \< \< \\
+ \< \< \pcfor \langle \rho_{(D,i)}, \cdot \rangle \in \algo{LookupDeposits}(\rho_P) \\
+ \< \< \t \rho_{(X,i)} := \langle \mathtt{"refund"}, \rho_D \rangle \\
+ \< \< \t \sigma_{(X,i)} := \algo{Ed25519.Sign}(\V{skM}, \rho_{(X,i)}) \\
+ \< \sendmessageleft*{X := \{ \rho_{(X,i)}, \sigma_{(X,i)} \} } \< \\
+\pcfor \langle \rho_{(X,i)}, \sigma_{(X,i)} \rangle \in X \< \< \\
+\t \pccheck \langle \mathtt{"refund"}, \rho_D \rangle := \rho_X \< \< \\
+\t \pccheck \langle C_p, \V{pkD}, \sigma_C, f, h, A_M, \V{pkM} \rangle := \rho_D \\
+\t \pccheck \algo{Ed25519.Verify}(\V{pkM}, \rho_X, \sigma_X) \< \< \\
+\t \algo{RefundDeposit}(C_p, h, f, \V{pkM}) \< \< \\
+\t \rho_{(XC,i)} := \langle \mathtt{"refunded"}, \rho_D \rangle \< \< \\
+\t \sigma_{(XC,i)} := \algo{Ed25519.Sign}(\V{skESig}, \rho_{(XC,i)}) \< \< \\
+\< \sendmessageright*{XC := \{ \rho_{(XC,i)}, \sigma_{(XC,i)} \} } \< \pclb
+\pcintertext[dotted]{Confirm refund} \\
+\textbf{Customer} \< \< \textbf{Merchant} \\
+\< \sendmessageleft*{XC} \< \\
+\pcfor \langle \rho_{(XC,i)}, \sigma_{(XC,i)} \rangle \in XC \< \< \\
+ \t \pccheck \algo{Ed25519.Verify}(\V{pkESig}, \rho_{(XC,i)}, \sigma_{(XC,i)}) \< \< \\
+}
+}
+}
+\caption{Refund protocol}
+\label{fig:refund}
+\end{figure}
+
+\clearpage
+\section{Experimental results}
+We now evaluate the performance of the core components of the reference
+implementation of GNU Taler. No separate benchmarks are provided for the
+merchant backend, as the work done by the merchant per transaction is
+relatively negligible compared to the work done by the exchange, and one exchange needs
+to provide service many merchants and all of their customers. Thus, the exchange
+is the bottleneck for the performance of the system.
+
+We provide a microbenchmark for the performance of cryptographic operations in
+the wallet (Table~\ref{table:wallet-benchmark}. Even on a low-end smartphone
+device, the most expensive cryptographic operations remain well under
+$150ms$, a threshold for user-interface latency under which user happiness and
+productivity is considered to be unaffected \cite{tolia2006quantifying}.
+
+\begin{table}
+ \centering
+ \begin{subtable}[t]{0.4\linewidth}
+ \centering{
+ \begin{tabular}{lr}
+ \toprule
+ Operation & Time (ms) \\
+ \midrule
+ eddsa create & 9.69 \\
+ eddsa sign & 22.31 \\
+ eddsa verify & 19.28 \\
+ hash big & 0.05 \\
+ hash small & 0.13 \\
+ rsa 2048 blind & 3.35 \\
+ rsa 2048 unblind & 4.94 \\
+ rsa 2048 verify & 1.97 \\
+ rsa 4096 blind & 10.38 \\
+ rsa 4096 unblind & 16.13 \\
+ rsa 4096 verify & 6.57 \\
+ \bottomrule
+ \end{tabular}}
+ \caption{Wallet microbenchmark on a Laptop (Intel i7-4600U) with Firefox}
+ \end{subtable}
+ \qquad
+ \begin{subtable}[t]{0.4\linewidth}
+ \centering{
+ \begin{tabular}{lr}
+ \toprule
+ Operation & Time (ms) \\
+ \midrule
+ eddsa create & 34.80 \\
+ eddsa sign & 78.55 \\
+ eddsa verify & 72.50 \\
+ hash big & 0.51 \\
+ hash small & 1.37 \\
+ rsa 2048 blind & 14.35 \\
+ rsa 2048 unblind & 19.78 \\
+ rsa 2048 verify & 9.10 \\
+ rsa 4096 blind & 47.86 \\
+ rsa 4096 unblind & 69.91 \\
+ rsa 4096 verify & 29.02 \\
+ \bottomrule
+ \end{tabular}}
+ \caption{Wallet microbenchmark on Android Moto G3 with Firefox}
+ \end{subtable}
+ \caption{Wallet microbenchmarks}
+ \label{table:wallet-benchmark}
+\end{table}
+
+
+We implemented a benchmarking tool that starts a single (multi-threaded)
+exchange and a bank process for the taler-test wire transfer protocol. It then
+generates workload on the exchange with a configurable number of concurrent
+clients and operations. The benchmarking tool is able to run the exchange on a
+different machine (via SSH\footnote{\url{https://www.openssh.com/}}) than the benchmark driver, mock bank and clients.
+At the end, the benchmark outputs the number of deposited coins per second and
+latency statistics.
+
+\subsection{Hardware Setup}
+We used two server machines (\texttt{firefly} and \texttt{gv}) with the following
+hardware specifications for our tests:
+\begin{itemize}
+ \item \texttt{firefly} has a 96-core AMD EPYC 7451 CPU and 256GiB DDR4\@2667 MHz RAM.
+ \item \texttt{gv} has a 16-core Intel(R) Xeon X5550 (2.67GHz) CPU and 128GiB DDR3\@1333 MHz RAM.
+\end{itemize}
+
+We used $2048$-bit RSA denomination keys for all of our exchange benchmarks. We
+used a development version of the exchange (with git commit hash
+5fbda29b76c24d\dots). PostgreSQL version 11.3 was used as the database.
+As our server machines have only slower hard-disk drives instead of faster solid-state drives,
+we ran the benchmarks with an in-memory database.
+
+
+\subsection{Coins Per Transaction}\label{sec:coins-per-transaction}
+The transaction rate is an important characteristic of a payment system. Since
+GNU Taler internally operates on the level of coins instead of transactions, we
+need to define what actually consititutes one transaction in our measurements.
+This includes both how many coins are used per transaction on average, as well
+as how often refresh operations are run.
+
+We ran a simple simulation to determine rather conservative upper bounds for
+the parameters that characterize the average transaction.
+
+In the simulation, thirteen denominations of values $2^0,\dots,2^{12}$ are
+available. Customers repeatedly select a random value to be spent between $4$ and $5000$.
+When customers do not have enough coins for a transaction, they withdraw a
+uniform random amount between the minimum amount to complete the transaction
+and $10000$. The denominations selected for withdrawal are chosen by greedy
+selection of the largest possible denomination. When spending, the customer
+first tries to use one coin, namely the smallest coin larger than the
+requested amount. If no such coin exists in the customer's wallet, the
+customer pays with multiple coins, spending smallest coins first.
+
+Choosing a random uniform amount for withdrawal could be considered
+unrealistic, as customers in practice likely would select from a fixed list of
+common withdrawal amounts, just like most ATMs operate.
+Thus, we also implemented a variation of the simulation that withdraws a constant
+amount of $1250$ (i.e., $1/4$ of the maximum transaction value) if it is sufficient
+for the transaction, and the exact required amount otherwise.
+
+We obtained the following results for the number of average operations
+executed for one ``business transaction'':
+
+\begin{table}[H]
+\centering
+\begin{tabular}{lSS}
+ \toprule
+ & {random withdraw} & {constant withdraw} \\
+ \midrule
+ \#spend operations & 8.3 & 7.0 \\
+ \#refresh operations & 1.3 & 0.51 \\
+ \#refresh output coins & 4.2 & 3.62 \\
+ \bottomrule
+\end{tabular}
+\end{table}
+
+Based on these results, we chose the parameters for our benchmark: for every
+spend operation we run a refresh operation with probability $1/10$, where each
+refresh operation produces $4$ output coins. In order to arrive at the
+transaction rate, the rate of spend operations should be divided by $10$.
+
+Note that this is a rather conservative analysis. In practice, the coin
+selection for withdrawal/spending can use more sophisticated optimization
+algorithms, rather than using greedy selection. Furthermore, we expect that the
+amounts paid in real-world transactions will have more predictable
+distributions, and thus the offered denominations can be adjusted to typical
+amounts.
+
+\subsubsection{Baseline Sequential Resource Usage}
+To obtain a baseline for the resource usage of the exchange, we ran the benchmark on
+\texttt{firefly} with a single client that executes sequential requests to
+withdraw and spend $10000$ coins, with $10\%$ refresh probability.
+
+% FIXME: talk about TFO, compression and keep-alive
+
+Table~\ref{table:benchmark:ops-baseline} shows the time used for cryptographic
+operations, together with the number of times they are executed by the clients
+(plus the mock bank and benchmark setup) and exchange, respectively. Note that
+while we measured the wall-clock time for these operations, the averages should
+correspond to the actual CPU time required for the respective operations, as
+the benchmark with one client runs significantly fewer processes/threads than
+the number of available CPUs on our machine.
+
+The benchmark completed in $15.10$ minutes on $\texttt{firefly}$. We obtained the total CPU usage of
+the benchmark testbed and exchange. The refresh operations are rather slow in comparison
+to spends and deposits, as the benchmark with a refresh probability of $0\%$ only took $8.84$
+minutes to complete.
+
+\begin{table}
+ \centering
+ \begin{tabular}{lSSS}
+ \toprule
+ Operation & {Time/Op (\si{\micro\second})} & {Count (exchange)} & {Count (clients)} \\
+ \midrule
+ecdh eddsa & 1338.62 & 2430 & 3645 \\
+ecdhe key create & 1825.38 & 0 & 3645 \\
+ecdhe key get public & 1272.64 & 2430 & 4860 \\
+eddsa ecdh & 1301.78 & 0 & 4860 \\
+eddsa key create & 1896.27 & 0 & 12180 \\
+eddsa key get public & 1729.69 & 9720 & 80340 \\
+eddsa sign & 5182.33 & 13393 & 25608 \\
+eddsa verify & 3976.96 & 25586 & 25627 \\
+hash & 1.41 & 165608 & 169445 \\
+hash context finish & 0.28 & 1215 & 1227 \\
+hash context read & 0.81 & 25515 & 25655 \\
+hash context start & 11.38 & 1215 & 1227 \\
+hkdf & 40.61 & 65057 & 193506 \\
+rsa blind & 695.25 & 9720 & 31633 \\
+rsa private key get public & 5.30 & 0 & 40 \\
+rsa sign blinded & 5284.88 & 17053 & 0 \\
+rsa unblind & 1348.62 & 0 & 21898 \\
+rsa verify & 421.19 & 13393 & 29216 \\
+ \bottomrule
+ \end{tabular}
+ \caption{Cryptographic operations in the benchmark with one client and $10000$ operations.}
+ \label{table:benchmark:ops-baseline}
+\end{table}
+
+
+\begin{table}
+ \centering
+ \begin{tabular}{lSSS}
+ \toprule
+ \textbf{Relation} &
+ {\textbf{Table (\si{\mebi\byte})}} &
+ {\textbf{Indexes (\si{\mebi\byte})}} &
+ {\textbf{Total (\si{\mebi\byte})}} \\
+ \midrule
+ denominations & 0.02 & 0.03 & 0.05 \\
+ reserves\_in & 0.01 & 0.08 & 0.09 \\
+ reserves & 0.02 & 0.25 & 0.27 \\
+ refresh\_commitments & 0.36 & 0.28 & 0.64 \\
+ refresh\_transfer\_keys & 0.38 & 0.34 & 0.73 \\
+ refresh\_revealed\_coins & 4.19 & 0.91 & 5.14 \\
+ known\_coins & 7.37 & 0.70 & 8.07 \\
+ deposits & 4.85 & 6.80 & 11.66 \\
+ reserves\_out & 8.95 & 4.48 & 13.43 \\
+ \midrule
+ \emph{Sum} & 26.14 & 13.88 & 40.02 \\
+ \bottomrule
+ \end{tabular}
+ \caption{Space usage by database table for $10000$ deposits with $10\%$ refresh probability.}
+ \label{table:exchange-db-size}
+\end{table}
+
+The size of the exchange's database after the experiment (starting from an empty database)
+is shown in Table~\ref{table:exchange-db-size}.
+We measured the size of tables and indexes using the \texttt{pg\_relation\_size} /
+\texttt{pg\_indexes\_size} functions of PostgreSQL.
+
+We observe that even though the refresh operations account for about half of
+the time taken by the benchmark, they contribute to only $\approx 16\%$ of the
+database's size. The computational costs for refresh are higher than the
+storage costs (compared to other operations), as the database stores only needs
+to store one commitment, one transfer key and the blinded coins that are
+actually signed.
+
+In our sequential baseline benchmark run, only one reserve was used to withdraw
+coins, and thus the tables that store the reserves are very small. In
+practice, information for multiple reserves would be tracked for each active
+cutomers.
+
+The TCP/IP network traffic between the exchange, clients and the mock bank was
+$\SI{57.95}{\mebi\byte}$, measured by the Linux kernel's statistics for
+transmitted/received bytes on the relevant network interface. As expected, the
+traffic is larger than the size of the database, since some data (such as
+signatures) is only verified/generated and not stored in the database.
+
+\subsection{Transaction Rate and Scalability}
+Figure~\ref{fig:benchmark-throughput} shows the mean time taken to process one
+coin for different numbers of parallel clients. With increasing parallelism,
+the throughput continues to rise roughly until after the number of parallel
+clients saturates the number of available CPU cores (96). There is no
+significant decrease in throughput even when the system is under rather high
+load, as clients whose requests cannot be handled in parallel are either
+waiting in the exchange's listen backlog or waiting in a retry timeout
+(with randomized, truncated, exponential back-off) after being refused when the
+exchange's listen backlog is full.
+
+
+Figure~\ref{fig:benchmark-cpu} shows the CPU time (sum of user and system time)
+of both the exchange and the whole benchmark testbed (including the exchange)
+in relation to the wall-clock time the benchmark took to complete.
+We can see that the gap between the wall-clock time and CPU time used by the
+benchmark grows with an increase in the number of parallel clients. This can
+be explained by the CPU usage of the database (whose CPU usage is not measured
+as part of the benchmark). With a growing number of parallel transactions, the
+database runs into an increasing number of failed commits due to read/write
+conflicts, leading to retries of the corresponding transactions.
+
+To estimate the time taken up by cryptographic operations in the exchange, we
+first measured a baseline with a single client, where the wall-clock time for
+cryptographic operations is very close to the actual CPU time, as virtually no
+context switching occurs. We then extrapolated these timings to experiment
+runs with parallelism by counting the number of times each operation is
+executed and multiplying with the baseline. As seen in the dot-and-dash line
+in Figure~\ref{fig:benchmark-cpu}, by our extrapolation slightly more than half
+of the time is spent in cryptographic routines.
+
+We furthermore observe in Figure~\ref{fig:benchmark-cpu} that under full load,
+less than $1/3$ of the CPU time is spent by the exchange. A majority of the
+CPU time in the benchmark is used by the simulation of clients.
+As we did not have a machine available that is powerful enough to generate
+traffic that can saturate a single exchange running on \texttt{firefly}, we
+estimate the throughput that would be possible if the machine only ran the
+exchange. The highest rate of spends was $780$ per second. Thus, the
+theoretically achievable transaction rate on our single test machine (and a
+dedicated machine for the database) would be $780 \cdot 3 / 10 = 234$ transactions
+per second under the relatively pessimistic assumptions we made about what
+constitutes a transaction.
+
+If a GNU Taler deployment was used to pay for items of fixed price (e.g., online
+news articles), the overhead of multiple coins and refresh operations (which
+accounts for $\approx 50\%$ of spent time as measured earlier) and multiple
+coins per payment would vanish, giving an estimated maximum transaction rate of
+$742 \cdot 2 = 1484$ transactions per second.
+
+\begin{figure}
+ \includegraphics[width=\textwidth]{plots/speed.pdf}
+ \caption[Coin throughput.]{Coin throughput in relation to number of parallel clients, with $1000$ coins per client per experiment run.}
+ \label{fig:benchmark-throughput}
+\end{figure}
+
+\begin{figure}
+ \includegraphics[width=\textwidth]{plots/cpu.pdf}
+ \caption[Comparison of components' CPU usage for the benchmark.]{Comparison of real time, the CPU time for the exchange and the whole benchmark.}
+ \label{fig:benchmark-cpu}
+\end{figure}
+
+\subsection{Latency}
+We connected \texttt{firefly} and \texttt{gv} directly with a patch cable, and
+introduced artificial network latency by configuring the Linux packet scheduler
+with the \texttt{tc} tool. The goal of this experiment was to observe the
+network latency characteristics of the implementation. Note that we do not consider
+the overhead of TLS in our experiments, as we assume that TLS traffic is
+already terminated before it reaches the exchange service, and exchanges can be
+operated securely even without TLS.
+
+The comparison between no additional delay and a \SI{100}{\milli\second} delay
+is shown in Table~\ref{table:latency}. TCP Fast Open~\cite{rfc7413} was
+enabled on both \texttt{gv} and \texttt{firefly}. Since for all operations
+except \texttt{/refresh/reveal}, both request and response fit into one TCP
+segment, these operations complete within one round-trip time. This explains the
+additional delay of $\approx \SI{200}{\milli\second}$ when the artificial delay
+is introduced. Without TCP Fast Open, we would observe an extra round trip for
+the SYN and SYN/ACK packages without any payload. The \texttt{/refresh/reveal}
+operation takes an extra roundtrip due to the relatively large size of the
+request (as show in Table~\ref{table:api-size}), which exceeds the MTU of 1500
+for the link between \texttt{gv} and \texttt{firefly}, and thus does not fit
+into the first TCP Fast Open packet.
+
+Figure~\ref{fig:latencies} shows the latency for the exchange's HTTP endpoints
+in relation to different network delays. As expected, the additional delay
+grows linearly for a single client. We note that in larger benchmarks with
+multiple parallel clients, the effect of additional delay would likely not just
+be linear, due to timeouts raised by clients.
+
+\newcommand{\specialcell}[2][c]{%
+ \begin{tabular}[#1]{@{}c@{}}#2\end{tabular}}
+
+\begin{table}
+ \centering
+ \begin{tabular}{lSSSS}
+ \toprule
+ Endpoint &
+ {\specialcell[c]{Base latency\\(\si{\milli\second})}} &
+ {\specialcell[c]{Latency with\\\SI{100}{\milli\second} delay\\(\si{\milli\second})}} \\
+ \midrule
+ \texttt{/keys} & 1.14 & 201.25 \\
+ \texttt{/reserve/withdraw} & 22.68 & 222.46 \\
+ \texttt{/deposit} & 22.36 & 223.22 \\
+ \texttt{/refresh/melt} & 20.71 & 223.9 \\
+ \texttt{/refresh/reveal} & 63.64 & 466.30 \\
+ \bottomrule
+ \end{tabular}
+ \caption{Effects of \SI{100}{\milli\second} symmetric network delay on total latency.}
+ \label{table:latency}
+\end{table}
+
+\begin{table}
+ \centering
+ \begin{tabular}{lSSSS}
+ \toprule
+ Endpoint &
+ {\specialcell[c]{Request size\\2048-bit RSA\\(\si{\kilo\byte})}} &
+ {\specialcell[c]{Response size\\2048-bit RSA\\(\si{\kilo\byte})}} &
+ {\specialcell[c]{Request size\\1024-bit RSA\\(\si{\kilo\byte})}} &
+ {\specialcell[c]{Response size\\1024-bit RSA\\(\si{\kilo\byte})}} \\
+ \midrule
+ \texttt{/keys} & 0.14 & 3.75 & 0.14 & 3.43 \\
+ \texttt{/reserve/withdraw} & 0.73 & 0.71 & 0.60 & 0.49 \\
+ \texttt{/deposit} & 1.40 & 0.34 & 1.14 & 0.24 \\
+ \texttt{/refresh/melt} & 1.06 & 0.35 & 0.85 & 0.35 \\
+ \texttt{/refresh/reveal} & 1.67 & 2.11 & 1.16 & 1.23 \\
+ \bottomrule
+ \end{tabular}
+ \caption[Request and response sizes for the exchange's API.]{Request and response sizes for the exchange's API.
+ In addition to the sizes for 2048-bit RSA keys (used throughout the benchmark), the sizes for 1024-bit RSA keys are also provided.}
+ \label{table:api-size}
+\end{table}
+
+\begin{figure}
+ \includegraphics[width=\textwidth]{plots/latencies.pdf}
+ \caption[Effect of artificial network delay on exchange's latency.]{Effect of artificial network delay on exchange's latency.}
+ \label{fig:latencies}
+\end{figure}
+
+% Missing benchmarks:
+% overall Taler tx/s
+% db io+tx/s
+% If I have time:
+% traffic/storage depending on key size?
+
+
+\section{Current Limitations and Future Improvements}\label{sec:implementation-improvements}
+Currently the auditor does not support taking samples of deposit confirmations that
+merchants receive. The API and user interface to receive and process proofs
+of misbehavior of the exchange/merchant generated by the wallet is not implemented yet.
+
+As a real-world deployment of electronic cash has rather high requirements for
+the operational security, the usage of hardware security modules for generation
+of signatures should be considered. Currently, a single process has access to
+all key material. For a lower-cost improvement that decreases the performance
+of the system, a threshold signature scheme could be used.
+
+The current implementation is focused on web payments. To use GNU Taler for
+payments in brick-and-mortar stores, hardware wallets and smartphone apps for
+devices with near-field-communication (NFC) must be developed. In some
+scenarios, either the customer or the merchant might not have an Internet
+connection, and this must be considered in the protocol design. In typical
+western brick-and-mortar stores, it is currently more likely that the merchant
+has Internet connectivity, and thus the protocol must allow operations of the
+wallet (such as refreshing) to be securely routed over the merchant's
+connection. In other scenarios, typically in developing countries, the
+merchant (for example, a street vendor) might not have Internet connection. If
+the vendor has a smartphone, the connection to the merchant can be routed
+through the customer. In other cases, street vendors only have a ``dumb
+phone'' that can receive text messages, and the payment goes through a provider
+trusted by the merchant that sends text messages as confirmation for payments.
+All these possibilities must be considered both from the perspective of the procotol and APIs
+as well as the user experience.
+
+% FIXME: explain that exchange does threading
+
+Our experiments were only done with single exchange process and a single
+database on the same machine. There are various ways to horizontally scale the
+exchange:
+\begin{itemize}
+ \item Multiple exchange processes can be run on multiple machines and access
+ the database that runs a separate machine. Requests are directed to the
+ machines running the exchange process via a load balancer. In this
+ scenario, the throughput of the database is likely to be the bottleneck.
+ \item To avoid having the database as a bottleneck, the contents can be
+ partitioned into shards. For this technique to be effective, data in the
+ shards should not have any dependencies in other shards. A natural way to
+ do sharding for the Taler exchange is to give each shard the sole
+ responsibility for a subset of all available denominations.
+ \item If the transaction volume on one denomination is too high to handle for
+ a single shard, transactions can be further partitioned based on the coin's
+ public key. Each would maintain the database of spent/refreshed coins for
+ a subset of all possible coin public keys. This approach has been
+ suggested for a centrally-banked cryprocurrency by Danezis and Meiklejohn
+ \cite{danezis2016rscoin}.
+\end{itemize}
+
+% paranoid wallet (permissions!)
+
+% FIXME: I want to mention PADs for auditing/transparency somewhere, just
+% because they're cool
+
+
+% FIXME: coin locking not implemented!
diff --git a/doc/system/taler/reserve.dot b/doc/system/taler/reserve.dot
new file mode 100644
index 000000000..af6e3e86d
--- /dev/null
+++ b/doc/system/taler/reserve.dot
@@ -0,0 +1,14 @@
+digraph Reserve {
+
+ filled [color=blue, label="filled reserve", shape="box"];
+ drained [color=blue, label="drained reserve", shape="doublecircle"];
+
+ transfer->filled;
+ recoup->filled;
+ filled->withdraw;
+ withdraw->drained;
+ withdraw->filled;
+ filled->close;
+ close->drained;
+ drained->recoup;
+}
diff --git a/doc/system/taler/reserve.pdf b/doc/system/taler/reserve.pdf
new file mode 100644
index 000000000..5225bdedb
--- /dev/null
+++ b/doc/system/taler/reserve.pdf
Binary files differ
diff --git a/doc/system/taler/security.tex b/doc/system/taler/security.tex
new file mode 100644
index 000000000..cf0128a0c
--- /dev/null
+++ b/doc/system/taler/security.tex
@@ -0,0 +1,1729 @@
+\chapter{Security of Income-Transparent Anonymous E-Cash}\label{chapter:security}
+
+\def\Z{\mathbb{Z}}
+
+\def\mathperiod{.}
+\def\mathcomma{,}
+
+\newcommand*\ST[5]%
+{\left#1\,#4\vphantom{#5} \;\right#2 \left. #5 \vphantom{#4}\,\right#3}
+
+% uniform random selection from set
+\newcommand{\randsel}[0]{\ensuremath{\xleftarrow{\text{\$}}}}
+
+\newcommand{\Exp}[1]{\ensuremath{E\left[#1\right]}}
+
+% oracles
+\newcommand{\ora}[1]{\ensuremath{\mathcal{O}\mathsf{#1}}}
+% oracle set
+\newcommand{\oraSet}[1]{\ensuremath{\mathcal{O}\textsc{#1}}}
+% algorithm
+\newcommand{\algo}[1]{\ensuremath{\mathsf{#1}}}
+% party
+\newcommand{\prt}[1]{\ensuremath{\mathcal{#1}}}
+% long-form variable
+\let\V\relax % clashes with siunitx volt
+\newcommand{\V}[1]{\ensuremath{\mathsf{#1}}}
+
+% probability with square brackets of the right size
+\newcommand{\Prb}[1]{\ensuremath{\Pr\left [#1 \right ]}}
+
+\newcommand{\mycomment}[1]{~\\ {\small \textcolor{blue}{({#1})}}}
+
+\theoremstyle{definition}
+\newtheorem{definition}{Definition}[section]
+\theoremstyle{corollary}
+\newtheorem{corollary}{Corollary}[section]
+
+
+%%%%%%%%%
+% TODOS
+%%%%%%%%
+%
+% * our theorems don't really mention the security parameters "in the output",
+% shouldn't we be able to talk about the bounds of the reduction?
+
+We so far discussed Taler's protocols and security properties only informally.
+In this chapter, we model a slightly simplified version of the system that we
+have implemented (see Chapter~\ref{chapter:implementation}), make our desired
+security properties more precise, and prove that our protocol instantiation
+satisfies those properties.
+
+\section{Introduction to Provable Security}
+Provable security
+\cite{goldwasser1982probabilistic,pointcheval2005provable,shoup2004sequences,coron2000exact} is a common
+approach for constructing formal arguments that support the security of a cryptographic
+protocol with respect to specific security properties and underlying
+assumptions on cryptographic primitives.
+
+The adversary we consider is computationally bounded, i.e., the run time is
+typically restricted to be polynomial in the security parameters (such as key
+length) of the protocol.
+
+Contrary to what the name might suggest, a protocol that is ``provably secure''
+is not necessarily secure in practice
+\cite{koblitz2007another,damgaard2007proof}. Instead, provable security
+results are typically based on reductions of the form \emph{``if there is an
+effective adversary~$\mathcal{A}$ against my protocol $P$, then I can use
+$\mathcal{A}$ to construct an effective adversary~$\mathcal{A}'$ against
+$Q$''} where $Q$ is a protocol or primitive that is assumed to be secure or a
+computational problem that is assumed to be hard. The practical value of a
+security proof depends on various factors:
+\begin{itemize}
+ \item How well-studied is $Q$? Some branches of cryptography, for example,
+ some pairing-based constructions, rely on rather complex and exotic
+ underlying problems that are assumed to be hard (but might not be)
+ \cite{koblitz2010brave}.
+ \item How tight is the reduction of $Q$ to $P$? A security proof may only
+ show that if $P$ can be solved in time $T$, the underlying problem $Q$ can
+ be solved (using the hypothetical $\mathcal{A}$) in time, e.g., $f(T) = T^2$.
+ In practice, this might mean that for $P$ to be secure, it needs to be deployed
+ with a much larger key size or security parameter than $Q$ to be secure.
+ \item What other assumptions are used in the reduction? A common and useful but
+ somewhat controversial
+ assumption is the Random Oracle Model (ROM) \cite{bellare1993random}, where
+ the usage of hash functions in a protocol is replaced with queries to a
+ black box (called the Random Oracle), which is effectively a trusted third
+ party that returns a truly random value for each input. Subsequent queries
+ to the Random Oracle with the same value return the same result. While
+ many consider ROM a practical assumption
+ \cite{koblitz2015random,bellare1993random}, it has been shown that there
+ exist carefully constructed protocols that are secure under the ROM, but
+ are insecure with any concrete hash function \cite{canetti2004random}. It
+ is an open question whether this result carries over to practical
+ protocols, or just certain classes of artificially constructed protocols of
+ theoretical interest.
+\end{itemize}
+Furthermore, a provably secure protocol does not always lend itself easily to a secure
+implementation, since side channels and fault injection attacks \cite{hsueh1997fault,lomne2011side} are
+usually not modeled. Finally, the security properties stated might
+not be sufficient or complete for the application.
+
+For our purposes, we focus on game-based provable security
+\cite{bellare2006code,pointcheval2005provable,shoup2004sequences,guo2018introduction}
+as opposed to simulation-based provable security \cite{goldwasser1989knowledge,lindell2017simulate},
+which is another approach to provable security typically used for
+zero-knowledge proofs and secure multiparty computation protocols.
+
+\subsection{Algorithms, Oracles and Games}
+In order to analyze the security of a protocol, the protocol and its desired
+security properties against an adversary with specific capabilities must first
+be modeled formally. This part is independent of a concrete instantiation of
+the protocol; the protocol is only described on a syntactic level.
+
+The possible operations of a protocol (i.e., the protocol syntax) are abstractly
+defined as the signatures of \emph{algorithms}. Later, the protocol will be
+instantiated by providing a concrete implementation (formally a program for a
+probabilistic Turing machine) of each algorithm. A typical public key signature
+scheme, for example, might consist of the following algorithms:
+\begin{itemize}
+ \item $\algo{KeyGen}(1^\lambda) \mapsto (\V{sk}, \V{pk})$, a probabilistic algorithm
+ which on input $1^\lambda$ generates a fresh key pair consisting of secret key $\V{sk}$ of length $\lambda$ and
+ and the corresponding public key $\V{pk}$. Note that $1^\lambda$ is the unary representation of $\lambda$.\footnote{%
+ This formality ensures that the size of the input of the Turing machine program implementing the algorithm will
+ be as least as big as the security parameter. Otherwise the run-time complexity cannot be directly expressed
+ in relation to the size of the input tape.}
+ \item $\algo{Sign}(\V{sk}, m) \mapsto \sigma$, an algorithm
+ that signs the bit string $m$ with secret key $\V{sk}$ to output the signature $\sigma$.
+ \item $\algo{Verify}(\V{pk}, \sigma, m) \mapsto b$, an algorithm
+ that determines whether $\sigma$ is a valid signature on $m$ made with the secret key corresponding to the
+ public key $\V{pk}$. It outputs the flag $b \in \{0, 1\}$ to indicate whether the signature
+ was valid (return value $1$) or invalid (return value $0$).
+\end{itemize}
+The abstract syntax could be instantiated with various concrete signature protocols.
+
+In addition to the computational power given to the adversary, the capabilities
+of the adversary are defined via oracles. The oracles can be thought of as the
+API\footnote{In the modern sense of application programming interface (API),
+where some system exposes a service with well-defined semantics.} that is given
+to the adversary and allows the adversary to interact with the environment it
+is running in. Unlike the algorithms, which the adversary has free access to,
+the access to oracles is often restricted, and oracles can keep state that is
+not accessible directly to the adversary. Oracles typically allow the
+adversary to access information that it normally would not have direct access
+to, or to trigger operations in the environment running the protocol.
+
+Formally, oracles are an extension to the Turing machine that runs the
+adversary, which allow the adversary to submit queries to interact with the
+environment that is running the protocol.
+
+
+For a signature scheme, the adversary could be given access to an \ora{Sign}
+oracle, which the adversary uses to make the system produce signatures, with
+secret keys that the adversary does not have direct access to. Different
+definitions of \ora{Sign} lead to different capabilities of the adversary and
+thus to different security properties later on:
+\begin{itemize}
+ \item If the signing oracle $\ora{Sign}(m)$ is defined to take a message $m$ and return
+ a signature $\sigma$ on that message, the adversary gains the power to do chosen message attacks.
+ \item If $\ora{Sign}(\cdot)$ was defined to return a pair $(\sigma, m)$ of a signature $\sigma$
+ on a random message $m$, the power of the adversary would be reduced to a known message attack.
+\end{itemize}
+
+While oracles are used to describe the possible interactions with a system, it
+is more convenient to describe complex, multi-round interactions involving
+multiple parties as a special form of an algorithm, called an \emph{interactive
+protocol}, that takes the identifiers of communicating parties and their
+(private) inputs as a parameter, and orchestrates the interaction between them.
+The adversary will then have an oracle to start an instance of that particular
+interactive protocol and (if desired by the security property being modeled)
+the ability to drop, modify or inject messages in the interaction. The
+typically more cumbersome alternative would be to introduce one algorithm and
+oracle for every individual interaction step.
+
+Security properties are defined via \emph{games}, which are experiments that
+challenge the adversary to act in a way that would break the desired security
+property. Games usually consist multiple phases, starting with the setup phase
+where the challenger generates the parameters (such as encryption keys) for the
+game. In the subsequent query/response phase, the adversary is given some of
+the parameters (typically including public keys but excluding secrets) from the
+setup phase, and runs with access to oracles. The challenger\footnote{ The
+challenger is conceptually the party or environment that runs the
+game/experiment.} answers oracle queries during that phase. After the
+adversary's program terminates, the challenger invokes the adversary again with
+a challenge. The adversary must now compute a final response to the
+challenger, sometimes with access to oracles. Depending on the answer, the
+challenger decides if the adversary wins the game or not, i.e., the game returns
+$0$ if the adversary loses and $1$ if the adversary wins.
+
+A game for the existential unforgeability of signatures could be formulated like this:
+
+\bigskip
+\noindent $\mathit{Exp}_{\prt{A}}^{EUF}(1^\lambda)$:
+\vspace{-0.5\topsep}
+\begin{enumerate}
+ \setlength\itemsep{0em}
+ \item $(\V{sk}, \V{pk}) \leftarrow \algo{KeyGen}(1^\lambda)$
+ \item $(\sigma, m) \leftarrow \prt{A}^{\ora{Sign(\cdot)}}(\V{pk})$
+
+ (Run the adversary with input $\V{pk}$ and access to the $\ora{Sign}$ oracle.)
+ \item If the adversary has called $\ora{Sign}(\cdot)$ with $m$ as argument,
+ return $0$.
+ \item Return $\algo{Verify}(\V{pk}, \sigma, m)$.
+\end{enumerate}
+Here the adversary is run once, with access to the signing oracle. Depending
+on which definition of $\ora{Sign}$ is chosen, the game models existential
+unforgeability under chosen message attack (EUF-CMA) or existential
+unforgeability under known message attack (EUF-KMA)
+
+The following modification to the game would model selective unforgeability
+(SUF-CMA / SUF-KMA):
+
+\bigskip
+\noindent $\mathit{Exp}_{\prt{A}}^{SUF}(1^\lambda)$:
+\vspace{-0.5\topsep}
+\begin{enumerate}
+ \setlength\itemsep{0em}
+ \item $m \leftarrow \prt{A}()$
+ \item $(\V{sk}, \V{pk}) \leftarrow \algo{KeyGen}(1^\lambda)$
+ \item $\sigma \leftarrow \prt{A}^{\ora{Sign(\cdot)}}(\V{pk}, m)$
+ \item If the adversary has called $\ora{Sign}(\cdot)$ with $m$ as argument,
+ return $0$.
+ \item Return $\algo{Verify}(\V{pk}, \sigma, m)$.
+\end{enumerate}
+Here the adversary has to choose a message to forge a signature for before
+knowing the message verification key.
+
+After having defined the game, we can now define a security property based on
+the probability of the adversary winning the game: we say that a signature
+scheme is secure against existential unforgeability attacks if for every
+adversary~\prt{A} (i.e., a polynomial-time probabilistic Turing machine
+program), the success probability
+\begin{equation*}
+ \Prb{\mathit{Exp}_{\prt{A}}^{EUF}(1^\lambda) = 1 }
+\end{equation*}
+of \prt{A} in the EUF game is \emph{negligible} (i.e., grows less fast with
+$\lambda$ than the inverse of any polynomial in $\lambda$).
+
+Note that the EUF and SUF games are related in the following way: an adversary
+against the SUF game can be easily transformed into an adversary against the
+EUF game, while the converse does not necessarily hold.
+
+Often security properties are defined in terms of the \emph{advantage} of the
+adversary. The advantage is a measure of how likely the adversary is to win
+against the real cryptographic protocol, compared to a perfectly secure version
+of the protocol. For example, let $\mathit{Exp}_{\prt{A}}^{BIT}()$ be a game
+where the adversary has to guess the next bit in the output of a pseudo-random number
+generator (PRNG). The idealized functionality would be a real random number generator,
+where the adversary's chance of a correct guess is $1/2$. Thus, the adversary's advantage is
+\begin{equation*}
+ \left|\Prb{\mathit{Exp}_{\prt{A}}^{BIT}()} - 1/2\right|.
+\end{equation*}
+Note that the definition of advantage depends on the game. The above
+definition, for example, would not work if the adversary had a way to
+``voluntarily'' lose the game by querying an oracle in a forbidden way
+
+
+\subsection{Assumptions, Reductions and Game Hopping}
+The goal of a security proof is to transform an attacker against
+the protocol under consideration into an attacker against the security
+of an underlying assumption. Typical examples for common assumptions might be:
+\begin{itemize}
+ \item the difficulty of the decisional/computational Diffie--Hellman problem (nicely described by~\cite{boneh1998decision})
+ \item existential unforgeability under chosen message attack (EUF-CMA) of a signature scheme \cite{goldwasser1988digital}
+ \item indistinguishability against chosen-plaintext attacks (IND-CPA) of a symmetric
+ encryption algorithm \cite{bellare1998relations}
+\end{itemize}
+
+To construct a reduction from an adversary \prt{A} against $P$ to an adversary
+against $Q$, it is necessary to specify a program $R$ that both interacts as an
+adversary with the challenger for $Q$, but at the same time acts as a
+challenger for the adversary against $P$. Most importantly, $R$ can chose how
+to respond to oracle queries from the adversary, as long as $R$ faithfully
+simulates a challenger for $P$. The reduction must be efficient, i.e., $R$ must
+still be a polynomial-time algorithm.
+
+A well-known example for a non-trivial reduction proof is the security proof of
+FDH-RSA signatures \cite{coron2000exact}.
+% FIXME: I think there's better reference, pointcheval maybe?
+
+In practice, reduction proofs are often complex and hard to verify.
+Game hopping has become a popular technique to manage the complexity of
+security proofs. The idea behind game hopping proofs is to make a sequence of
+small modifications starting from initial game, until you arrive at a game
+where the success probability for the adversary becomes obvious, for example,
+because the winning state for the adversary becomes unreachable in the code
+that defines the final game, or because all values the adversary can observe to
+make a decision are drawn from a truly random and uniform distribution. Each
+hop modifies the game in a way such that the success probability of game
+$\mathbb{G}_n$ and game $\mathbb{G}_{n+1}$ is negligibly close.
+
+Useful techniques for hops are, for example:
+\begin{itemize}
+ \item Bridging hops, where the game is syntactically changed but remains
+ semantically equivalent, i.e., $\Prb{\mathbb{G}_n = 1} = \Prb{\mathbb{G}_n = 1}$.
+ \item Indistinguishability hops, where some distribution is changed in a way that
+ an adversary that could distinguish between two adjacent games could be turned
+ into an adversary that distinguishes the two distributions. If the success probability
+ to distinguish between those two distributions is $\epsilon$, then
+ $\left|\Prb{\mathbb{G}_n = 1} - \Prb{\mathbb{G}_n = 1}\right| \le \epsilon$
+ \item Hops based on small failure events. Here adjacent games proceed
+ identically, until in one of the games a detectable failure event $F$ (such
+ as an adversary visibly forging a signature) occurs. Both games most proceed
+ the same if $F$ does not occur. Then it is easy to show \cite{shoup2004sequences}
+ that $\left|\Prb{\mathbb{G}_n = 1} - \Prb{\mathbb{G}_n = 1}\right| \le \Prb{F}$
+\end{itemize}
+
+A tutorial introduction to game hopping is given by Shoup \cite{shoup2004sequences}, while a more formal
+treatment with a focus on ``games as code'' can be found in \cite{bellare2006code}. A
+version of the FDH-RSA security proof based on game hopping was generated with an
+automated theorem prover by Blanchet and Pointcheval \cite{blanchet2006automated}.
+
+% restore-from-backup
+
+% TODO:
+% - note about double spending vs overspending
+
+\subsection{Notation}
+We prefix public and secret keys with $\V{pk}$ and $\V{sk}$, and write $x
+\randsel S$ to randomly select an element $x$ from the set $S$ with uniform
+probability.
+
+\section{Model and Syntax for Taler}
+
+We consider a payment system with a single, static exchange and multiple,
+dynamically created customers and merchants. The subset of the full Taler
+protocol that we model includes withdrawing digital coins, spending them with
+merchants and subsequently depositing them at the exchange, as well as
+obtaining unlinkable change for partially spent coins with an online
+``refresh'' protocol.
+
+The exchange offers digital coins in multiple denominations, and every
+denomination has an associated financial value; this mapping is not chosen by
+the adversary but is a system parameter. We mostly ignore the denomination
+values here, including their impact on anonymity, in keeping with existing
+literature \cite{camenisch2007endorsed,pointcheval2017cut}. For anonymity, we
+believe this amounts to assuming that all customers have similar financial
+behavior. We note logarithmic storage, computation and bandwidth demands
+denominations distributed by powers of a fixed constant, like two.
+
+We do not model fees taken by the exchange. Reserves\footnote{%
+ ``Reserve'' is Taler's terminology for funds submitted to the exchange that
+ can be converted to digital coins.
+}
+are also omitted.
+Instead of maintaining a reserve balance, withdrawals of different
+denominations are tracked, effectively assuming every customer has unlimited funds.
+
+Coins can be partially spent by specifying a fraction $0 < f \le 1$ of the
+total value associated with the coin's denomination. Unlinkable change below
+the smallest denomination cannot be given. In
+practice the unspendable, residual value should be seen as an additional fee
+charged by the exchange.
+
+Spending multiple coins is modeled non-atomically: to spend (fractions
+of) multiple coins, they must be spent one-by-one. The individual
+spend/deposit operations are correlated by a unique identifier for the
+transaction. In practice, this identifier is the hash $\V{transactionId} =
+H(\V{contractTerms})$ of the contract terms\footnote{The contract terms
+are a digital representation of an individual offer for a certain product or service the merchant sells
+for a certain price.}. Contract terms include a nonce to make them
+unique, that merchant and customer agreed upon. Note that this transaction
+identifier and the correlation between multiple spend operations for one
+payment need not be disclosed to the exchange (it might, however, be necessary
+to reveal during a detailed tax audit of the merchant): When spending the $i$-th coin
+for the transaction with the identifier $\V{transactionId}$, messages to the
+exchange would only contain $H(i \Vert \V{transactionId})$. This is preferable
+for merchants that might not want to disclose to the exchange the individual
+prices of products they sell to customers, but only the total transaction
+volume over time. For simplicity, we do not include this extra feature in our
+model.
+
+Our system model tracks the total amount ($\equiv$ financial value) of coins
+withdrawn by each customer.
+Customers are identified by their public key $\V{pkCustomer}$. Every
+customer's wallet keeps track of the following data:
+\begin{itemize}
+ \item $\V{wallet}[\V{pkCustomer}]$ contains sets of the customer's coin records,
+ which individually consist of the coin key pair, denomination and exchange's signature.
+ \item $\V{acceptedContracts}[\V{pkCustomer}]$ contains the sets of
+ transaction identifiers accepted by the customer during spending
+ operations, together with coins spent for it and their contributions $0 < f
+ \le 1$.
+ \item $\V{withdrawIds}[\V{pkCustomer}]$ contains the withdraw identifiers of
+ all withdraw operations that were created for this customer.
+ \item $\V{refreshIds}[\V{pkCustomer}]$ contains the refresh identifiers of
+ all refresh operations that were created for this customer.
+\end{itemize}
+
+
+The exchange in our model keeps track of the following data:
+\begin{itemize}
+ \item $\V{withdrawn}[\V{pkCustomer}]$ contains the total amount withdrawn by
+ each customer, i.e., the sum of the financial value of the denominations for
+ all coins that were withdrawn by $\V{pkCustomer}$.
+ \item The overspending database of the exchange is modeled by
+ $\V{deposited}[\V{pkCoin}]$ and $\V{refreshed}[\V{pkCoin}]$, which record
+ deposit and refresh operations respectively on each coin. Note that since
+ partial deposits and multiple refreshes to smaller denominations are
+ possible, one deposit and multiple refresh operations can be recorded for a
+ single coin.
+\end{itemize}
+
+We say that a coin is \emph{fresh} if it appears in neither the $\V{deposited}$
+or $\V{refreshed}$ lists nor in $\V{acceptedContracts}$. We say that a coin is
+being $\V{overspent}$ if recording an operation in $\V{deposited}$ or
+$\V{refreshed}$ would cause the total spent value from both lists to exceed
+the value of the coin's denomination.
+Note that the adversary does not have direct read or write access to these
+values; instead the adversary needs to use the oracles (defined later) to
+interact with the system.
+
+We parameterize our system with two security parameters: The general security
+parameter $\lambda$, and the refresh security parameter $\kappa$. While
+$\lambda$ determines the length of keys and thus the security level, using a
+larger $\kappa$ will only decrease the success chance of malicious merchants
+conspiring with customers to obtain unreported (and thus untaxable) income.
+
+\subsection{Algorithms}\label{sec:security-taler-syntax}
+
+The Taler e-cash scheme is modeled by the following probabilistic\footnote{Our
+Taler instantiations are not necessarily probabilistic (except, e.g., key
+generation), but we do not want to prohibit this for other instantiations}
+polynomial-time algorithms and interactive protocols. The notation $P(X_1,\dots,X_n)$
+stands for a party $P \in \{\prt{E}, \prt{C}, \prt{M}\}$ (Exchange, Customer
+and Merchant respectively) in an interactive protocol, with $X_1,\dots,X_n$
+being the (possibly private) inputs contributed by the party to the protocol.
+Interactive protocols can access the state maintained by party $P$.
+
+While the adversary can freely execute the interactive protocols by creating
+their own parties, the adversary is not given direct access to the private data
+of parties maintained by the challenger in the security games we define later.
+
+\begin{itemize}
+ \item $\algo{ExchangeKeygen}(1^{\lambda}, 1^{\kappa}, \mathfrak{D}) \mapsto (\V{sksE}, \V{pksE})$:
+ Algorithm executed to generate keys for the exchange, with general security
+ parameter $\lambda$ and refresh security parameter $\kappa$, both given as
+ unary numbers. The denomination specification $\mathfrak{D} = d_1,\dots,d_n$ is a
+ finite sequence of positive rational numbers that defines the financial
+ value of each generated denomination key pair. We henceforth use $\mathfrak{D}$ to
+ refer to some appropriate denomination specification, but our analysis is
+ independent of a particular choice of $\mathfrak{D}$.
+
+ The algorithm generates the exchange's master signing key pair
+ $(\V{skESig}, \V{pkESig})$ and denomination secret and public keys
+ $(\V{skD}_1, \dots, \V{skD}_n), (\V{pkD}_1, \dots, \V{pkD}_n)$. We write
+ $D(\V{pkD}_i)$, where $D : \{\V{pkD}_i\} \rightarrow \mathfrak{D}$ to look
+ up the financial value of denomination $\V{pkD_i}$.
+
+ We collectively refer to the exchange's secrets by $\V{sksE}$ and to the exchange's
+ public keys together with $\mathfrak{D}$ by $\V{pksE}$.
+
+ \item $\algo{CustomerKeygen}(1^\lambda,1^\kappa) \mapsto (\V{skCustomer}, \V{pkCustomer})$:
+ Key generation algorithm for customers with security parameters $\lambda$
+ and $\kappa$.
+
+ \item $\algo{MerchantKeygen}(1^\lambda,1^\kappa) \mapsto (\V{skMerchant},
+ \V{pkMerchant})$: Key generation algorithm for merchants. Typically the
+ same as \algo{CustomerKeygen}.
+
+ \item $\algo{WithdrawRequest}(\prt{E}(\V{sksE}, \V{pkCustomer}),
+ \prt{C}(\V{skCustomer}, \V{pksE}, \V{pkD})) \mapsto (\mathcal{T}_{WR},
+ \V{wid})$: Interactive protocol between the exchange and a customer that
+ initiates withdrawing a single coin of a particular denomination.
+
+ The customer obtains a withdraw identifier $\V{wid}$ from the protocol
+ execution and stores it in $\V{withdrawIds}[\V{pkCustomer}]$.
+
+ The \algo{WithdrawRequest} protocol only initiates a withdrawal. The coin
+ is only obtained and stored in the customer's wallet by executing the
+ \algo{WithdrawPickup} protocol on the withdraw identifier \V{wid}.
+
+ The customer and exchange persistently store additional state (if required
+ by the instantiation) such that the customer can use
+ $\algo{WithdrawPickup}$ to complete withdrawal or to complete a previously
+ interrupted or unfinished withdrawal.
+
+ Returns a protocol transcript $\mathcal{T}_{WR}$ of all messages exchanged
+ between the exchange and customer, as well as the withdraw identifier
+ \V{wid}.
+
+ \item $\algo{WithdrawPickup}(\prt{E}(\V{sksE}, \V{pkCustomer}),
+ \prt{C}(\V{skCustomer}, \V{pksE}, \V{wid})) \mapsto (\mathcal{T}_{WP},
+ \V{coin})$: Interactive protocol between the exchange and a customer to
+ obtain the coin from a withdraw operation started with
+ $\algo{WithdrawRequest}$, identified by the withdraw identifier $\V{wid}$.
+
+ The first time $\algo{WithdrawPickup}$ is run with a particular withdraw
+ identifier $\V{wid}$, the exchange increments
+ $\V{withdrawn}[\V{pkCustomer}]$ by $D(\V{pkD})$, where $\V{pkD}$ is the
+ denomination requested in the corresponding $\algo{WithdrawRequest}$
+ execution. How exactly $\V{pkD}$ is restored depends on the particular instantiation.
+
+ The resulting coin
+ \[ \V{coin} = (\V{skCoin}, \V{pkCoin}, \V{pkD}, \V{coinCert}), \]
+ consisting of secret key $\V{skCoin}$, public key
+ $\V{pkCoin}$, denomination public key $\V{pkD}$ and certificate $\V{coinCert}$ from the exchange, is stored
+ in the customers wallet $\V{wallet}[\V{pkCustomer}]$.
+
+ Executing the $\algo{WithdrawPickup}$ protocol multiple times with the same
+ customer and the same withdraw identifier does not result in any change of
+ the customer's withdraw balance $\V{withdrawn}[\V{pkCustomer}]$,
+ and results in (re-)adding the same coin to the customer's wallet.
+
+ Returns a protocol transcript $\mathcal{T}_{WP}$ of all messages exchanged
+ between the exchange and customer.
+
+ \item $\algo{Spend}(\V{transactionId}, f, \V{coin}, \V{pkMerchant}) \mapsto \V{depositPermission}$:
+ Algorithm to produce and sign a deposit permission \V{depositPermission}
+ for a coin under a particular transaction identifier. The fraction $0 < f \le 1$ determines the
+ fraction of the coin's initial value that will be spent.
+
+ The contents of the deposit permission depend on the instantiation, but it
+ must be possible to derive the public coin identifier $\V{pkCoin}$ from
+ them.
+
+ \item $\algo{Deposit}(\prt{E}(\V{sksE}, \V{pkMerchant}), \prt{M}(\V{skMerchant}, \V{pksE}, \V{depositPermission})) \mapsto \mathcal{T}_D$:
+ Interactive protocol between the exchange and a merchant.
+
+ From the deposit permission we obtain the $\V{pkCoin}$ of the coin to be
+ deposited. If $\V{pkCoin}$ is being overspent, the protocol is aborted with
+ an error message to the merchant.
+
+ On success, we add $\V{depositPermission}$ to $\V{deposited}[\V{pkCoin}]$.
+
+ Returns a protocol transcript $\mathcal{T}_D$ of all messages exchanged
+ between the exchange and merchant.
+
+ \item $\algo{RefreshRequest}(\prt{E}(\V{sksE}), \prt{C}(\V{pkCustomer}, \V{pksE}, \V{coin}_0, \V{pkD}_u))
+ \rightarrow (\mathcal{T}_{RR}, \V{rid})$
+ Interactive protocol between exchange and customer that initiates a refresh
+ of $\V{coin}_0$. Together with $\algo{RefreshPickup}$, it allows the
+ customer to convert $D(\V{pkD}_u)$ of the remaining value on coin \[
+ \V{coin}_0 = (\V{skCoin}_0, \V{pkCoin}_0, \V{pkD}_0, \V{coinCert}_0) \]
+ into a new, unlinkable coin $\V{coin}_u$ of denomination $\V{pkD}_u$.
+
+ Multiple refreshes on the same coin are allowed, but each run subtracts the
+ respective financial value of $\V{coin}_u$ from the remaining value of
+ $\V{coin}_0$.
+
+ The customer only records the refresh operation identifier $\V{rid}$ in
+ $\V{refreshIds}[\V{pkCustomer}]$, but does not yet obtain the new coin. To
+ obtain the new coin, \algo{RefreshPickup} must be used.
+
+ Returns the protocol transcript $\mathcal{T}_{RR}$ and a refresh identifier $\V{rid}$.
+
+ \item $\algo{RefreshPickup}(\prt{E}(\V{sksE}, \V{pkCustomer}),
+ \prt{C}(\V{skCustomer}, \V{pksE}, \V{rid})) \rightarrow (\mathcal{T}_{RP}, \V{coin}_u)$:
+ Interactive protocol between exchange and customer to obtain the new coin
+ for a refresh operation previously started with \algo{RefreshRequest},
+ identified by the refresh identifier $\V{rid}$.
+
+ The exchange learns the target denomination $\V{pkD}_u$ and signed
+ source coin $(\V{pkCoin}_0, \V{pkD}_0, \V{coinCert}_0)$. If the source
+ coin is invalid, the exchange aborts the protocol.
+
+ The first time \algo{RefreshPickup} is run for a particular refresh
+ identifier, the exchange records a refresh operation of value
+ $D(\V{pkD}_u)$ in $\V{refreshed}[\V{pkCoin}_0]$. If $\V{pkCoin}_0$ is
+ being overspent, the refresh operation is not recorded in
+ $\V{refreshed}[\V{pkCoin}_0]$, the exchange sends the customer the protocol
+ transcript of the previous deposits and refreshes and aborts the protocol.
+
+ If the customer \prt{C} plays honestly in \algo{RefreshRequest} and
+ \V{RefreshPickup}, the unlinkable coin $\V{coin}_u$ they obtain as change
+ will be stored in their wallet $\V{wallet}[\V{pkCustomer}]$. If \prt{C} is
+ caught playing dishonestly, the \algo{RefreshPickup} protocol aborts.
+
+ An honest customer must be able to repeat a \algo{RefreshPickup} with the
+ same $\V{rid}$ multiple times and (re-)obtain the same coin, even if
+ previous $\algo{RefreshPickup}$ executions were aborted.
+
+ Returns a protocol transcript $\mathcal{T}_{RP}$.
+
+ \item $\algo{Link}(\prt{E}(\V{sksE}), \prt{C}(\V{skCustomer}, \V{pksE}, \V{coin}_0)) \rightarrow (\mathcal{T}, (\V{coin}_1, \dots, \V{coin}_n))$:
+ Interactive protocol between exchange and customer. If $\V{coin}_0$ is a
+ coin that was refreshed, the customer can recompute all the coins obtained
+ from previous refreshes on $\V{coin}_0$, with data obtained from the
+ exchange during the protocol. These coins are added to the customer's
+ wallet $\V{wallet}[\V{pkCustomer}]$ and returned together with the protocol
+ transcript.
+
+\end{itemize}
+
+\subsection{Oracles}
+We now specify how the adversary can interact with the system by defining
+oracles. Oracles are queried by the adversary, and upon a query the challenger
+will act according to the oracle's specification. Note that the adversary for
+the different security games is run with specific oracles, and does not
+necessarily have access to all oracles simultaneously.
+
+We refer to customers in the parameters to an oracle query simply by their
+public key. The adversary needs the ability to refer to coins to trigger
+operations such as spending and refresh, but to model anonymity we cannot give
+the adversary access to the coins' public keys directly. Therefore we allow
+the adversary to use the (successful) transcripts of the withdraw, refresh and
+link protocols to indirectly refer to coins. We refer to this as a coin handle
+$\mathcal{H}$. Since the execution of a link protocol results in a transcript
+$\mathcal{T}$ that can contain multiple coins, the adversary needs to select a
+particular coin from the transcript via the index $i$ as $\mathcal{H} =
+(\mathcal{T}, i)$. The respective oracle tries to find the coin that resulted
+from the transcript given by the adversary. If the transcript has not been
+seen before in the execution of a link, refresh or withdraw protocol; or the
+index for a link transcript is invalid, the oracle returns an error to the
+adversary.
+
+In oracles that trigger the execution of one of the interactive protocols
+defined in Section \ref{sec:security-taler-syntax}, we give the adversary the
+ability to actively control the communication channels between the exchange,
+customers and merchants; i.e., the adversary can effectively record, drop,
+modify and inject messages during the execution of the interactive protocol.
+Note that this allows the adversary to leave the execution of an interactive
+protocol in an unfinished state, where one or more parties are still waiting
+for messages. We use $\mathcal{I}$ to refer to a handle to interactive
+protocols where the adversary can send and receive messages.
+
+\begin{itemize}
+ \item $\ora{AddCustomer}() \mapsto \V{pkCustomer}$:
+ Generates a key pair $(\V{skCustomer}, \V{pkCustomer})$ using the
+ \algo{CustomerKeygen} algorithm, and sets
+ \begin{align*}
+ \V{withdrawn}[\V{pkCustomer}] &:= 0\\
+ \V{acceptedContracts}[\V{pkCustomer}] &:= \{ \}\\
+ \V{wallet}[\V{pkCustomer}] &:= \{\} \\
+ \V{withdrawIds}[\V{pkCustomer}] &:= \{\} \\
+ \V{refreshIds}[\V{pkCustomer}] &:= \{\}.
+ \end{align*}
+ Returns the public key of the newly created customer.
+
+ \item $\ora{AddMerchant}() \mapsto \V{pkMerchant}$:
+ Generate a key pair $(\V{skMerchant}, \V{pkMerchant})$ using the
+ \algo{MerchantKeygen} algorithm.
+
+ Returns the public key of the newly created merchant.
+
+ \item $\ora{SendMessage}(\mathcal{I}, P_1, P_2, m) \mapsto ()$:
+ Send message $m$ on the channel from party $P_1$ to party $P_2$ in the
+ execution of interactive protocol $\mathcal{I}$. The oracle does not have
+ a return value.
+
+ \item $\ora{ReceiveMessage}(\mathcal{I}, P_1, P_2) \mapsto m$:
+ Read message $m$ in the channel from party $P_1$ to party $P_2$ in the execution
+ of interactive protocol $\mathcal{I}$. If no message is queued in the channel,
+ return $m = \bot$.
+
+ \item $\ora{WithdrawRequest}(\V{pkCustomer}, \V{pkD}) \mapsto \mathcal{I}$:
+ Triggers the execution of the \algo{WithdrawRequest} protocol. the
+ adversary full control of the communication channels between customer and
+ exchange.
+
+ \item $\ora{WithdrawPickup}(\V{pkCustomer}, \V{pkD}, \mathcal{T}) \mapsto \mathcal{I}$:
+ Triggers the execution of the \algo{WithdrawPickup} protocol, additionally giving
+ the adversary full control of the communication channels between customer and exchange.
+
+ The customer and withdraw identifier $\V{wid}$ are obtained from the \algo{WithdrawRequest} transcript $\mathcal{T}$.
+
+ \item $\ora{RefreshRequest}(\mathcal{H}, \V{pkD}) \mapsto \mathcal{I}$: Triggers the execution of the
+ \algo{RefreshRequest} protocol with the coin identified by coin handle
+ $\mathcal{H}$, additionally giving the adversary full control over the communication channels
+ between customer and exchange.
+
+ \item $\ora{RefreshPickup}(\mathcal{T}) \mapsto \mathcal{I}$:
+ Triggers the execution of the \algo{RefreshPickup} protocol, where the customer and refresh identifier $\V{rid}$
+ are obtained from the $\algo{RefreshRequest}$ protocol transcript $\mathcal{T}$.
+
+ Additionally gives the adversary full control over the communication channels
+ between customer and exchange.
+
+ \item $\ora{Link}(\mathcal{H}) \mapsto \mathcal{I}$: Triggers the execution of the
+ \algo{Link} protocol for the coin referenced by handle $\mathcal{H}$,
+ additionally giving the adversary full control over the communication channels
+ between customer and exchange.
+
+ \item $\ora{Spend}(\V{transactionId}, \V{pkCustomer}, \mathcal{H}, \V{pkMerchant}) \mapsto \V{depositPermission}$:
+ Makes a customer sign a deposit permission over a coin identified by handle
+ $\mathcal{H}$. Returns the deposit permission on success, or $\bot$ if $\mathcal{H}$
+ is not a coin handle that identifies a coin.
+
+ Note that $\ora{Spend}$ can be used to generate deposit permissions that,
+ when deposited, would result in an error due to overspending
+
+ Adds $(\V{transactionId}, \V{depositPermission})$ to $\V{acceptedContracts}[\V{pkCustomer}]$.
+
+ \item $\ora{Share}(\mathcal{H}, \V{pkCustomer}) \mapsto ()$:
+ Shares a coin (identified by handle $\mathcal{H}$) with the customer
+ identified by $\V{pkCustomer}$, i.e., puts the coin identified by $\mathcal{H}$
+ into $\V{wallet}[\V{pkCustomer}]$. Intended to be used by the adversary in attempts to
+ violate income transparency. Does not have a return value.
+
+ Note that this trivially violates anonymity (by sharing with a corrupted customer), thus the usage must
+ be restricted in some games.
+
+ % the share oracle is the reason why we don't need a second withdraw oracle
+
+ \item $\ora{CorruptCustomer}(\V{pkCustomer})\mapsto
+ \newline{}\qquad (\V{skCustomer}, \V{wallet}[\V{pkCustomer}],\V{acceptedContracts}[\V{pkCustomer}],
+ \newline{}\qquad \phantom{(}\V{refreshIds}[\V{pkCustomer}], \V{withdrawIds}[\V{pkCustomer}])$:
+
+ Used by the adversary to corrupt a customer, giving the adversary access to
+ the customer's secret key, wallet, withdraw/refresh identifiers and accepted contracts.
+
+ Permanently marks the customer as corrupted. There is nothing ``special''
+ about corrupted customers, other than that the adversary has used
+ \ora{CorruptCustomer} on them in the past. The adversary cannot modify
+ corrupted customer's wallets directly, and must use the oracle again to
+ obtain an updated view on the corrupted customer's private data.
+
+ \item $\ora{Deposit}(\V{depositPermission}) \mapsto \mathcal{I}$:
+ Triggers the execution of the \algo{Deposit} protocol, additionally giving
+ the adversary full control over the communication channels between merchant and exchange.
+
+ Returns an error if the deposit permission is addressed to a merchant that was not registered
+ with $\ora{AddMerchant}$.
+
+ This oracle does not give the adversary new information, but is used to
+ model the situation where there might be multiple conflicting deposit
+ permissions generated via $\algo{Spend}$, but only a limited number can be
+ deposited.
+\end{itemize}
+
+We write \oraSet{Taler} for the set of all the oracles we just defined,
+and $\oraSet{NoShare} := \oraSet{Taler} - \ora{Share}$ for all oracles except
+the share oracle.
+
+The exchange does not need to be corrupted with an oracle. A corrupted exchange
+is modeled by giving the adversary the appropriate oracles and the exchange
+secret key from the exchange key generation.
+
+If the adversary determines the exchange's secret key during the setup,
+invoking \ora{WithdrawRequest}, \ora{WithdrawPickup}, \ora{RefreshRequest},
+\ora{RefreshPickup} or \ora{Link} can be seen as the adversary playing the
+exchange. Since the adversary is an active man-in-the-middle in these oracles,
+it can drop messages to the simulated exchange and make up its own response.
+If the adversary calls these oracles with a corrupted customer, the adversary
+plays as the customer.
+
+%\begin{mdframed}
+%The difference between algorithms and interactive protocols
+%is that the ``pure'' algorithms only deal with data, while the interactive protocols
+%take ``handles'' to parties that are communicating in the protocol. The adversary can
+%always execute algorithms that don't depend on handles to communication partners.
+%However the adversary can't run the interactive protocols directly, instead it must
+%rely on the interaction oracles for it. Different interaction oracles might allow the
+%adversary to play different roles in the same interactive protocol.
+%
+%While most algorithms in Taler are not probabilistic, we still say that they are, since
+%somebody else might come up with an instantiation of Taler that uses probabilistic algorithms,
+%and then the games should still apply.
+%
+%
+%While we do have a \algo{Deposit} protocol that's used in some of the games, having a deposit oracle is not necessary
+%since it does not give the adversary any additional power.
+%\end{mdframed}
+
+\section{Games}
+
+We now define four security games (anonymity, conservation, unforgeability and
+income transparency) that are later used to define the security properties for
+Taler. Similar to \cite{bellare2006code} we assume that the game and adversary
+terminate in finite time, and thus random choices made by the challenger and
+adversary can be taken from a finite sample space.
+
+All games except income transparency return $1$ to indicate that the adversary
+has won and $0$ to indicate that the adversary has lost. The income
+transparency game returns $0$ if the adversary has lost, and a positive
+``laundering ratio'' if the adversary won.
+
+\subsection{Anonymity}
+Intuitively, an adversary~$\prt{A}$ (controlling the exchange and merchants) wins the
+anonymity game if they have a non-negligible advantage in correlating spending operations
+with the withdrawal or refresh operations that created a coin used in the
+spending operation.
+
+Let $b$ be the bit that will determine the mapping between customers and spend
+operations, which the adversary must guess.
+
+We define a helper procedure
+\begin{equation*}
+ \algo{Refresh}(\prt{E}(\V{sksE}), \prt{C}(\V{pkCustomer}, \V{pksE}, \V{coin}_0)) \mapsto \mathfrak{R}
+\end{equation*}
+that refreshes the whole remaining amount on $\V{coin}_0$ with repeated application of $\algo{RefreshRequest}$
+and $\algo{RefreshPickup}$ using the smallest possible set of target denominations, and returns all protocol transcripts
+in $\mathfrak{R}$.
+
+\begin{mdframed}
+\small
+\noindent $\mathit{Exp}_{\prt{A}}^{anon}(1^\lambda, 1^\kappa, b)$:
+\vspace{-0.5\topsep}
+\begin{enumerate}
+ \setlength\itemsep{0em}
+ \item $(\V{sksE}, \V{pksE}, \V{skM}, \V{pkM}) \leftarrow {\prt{A}}()$
+ \item $(\V{pkCustomer}_0, \V{pkCustomer}_1, \V{transactionId}_0, \V{transactionId}_1, f) \leftarrow {\prt{A}}^{\oraSet{NoShare}}()$
+ \item Select distinct fresh coins
+ \begin{align*}
+ \V{coin}_0 &\in \V{wallet}[\V{pkCustomer}_0]\\
+ \V{coin}_1 &\in \V{wallet}[\V{pkCustomer}_1]
+ \end{align*}
+ Return $0$ if either $\V{pkCustomer}_0$ or $\V{pkCustomer}_1$ are not registered customers with sufficient fresh coins.
+ \item For $i \in \{0,1\}$ run
+ \begin{align*}
+ &\V{dp_i} \leftarrow \algo{Spend}(\V{transactionId}_i, f, \V{coin}_{i-b}, \V{pkM}) \\
+ &\algo{Deposit}(\prt{A}(), \prt{M}(\V{skM}, \V{pksE}, \V{dp}_i)) \\
+ &\mathfrak{R}_i \leftarrow \algo{Refresh}(\prt{A}(), \prt{C}(\V{pkCustomer}_i, \V{pksE}, \V{coin}_{i-b}))
+ \end{align*}
+ \item $b' \leftarrow {\cal A}^{\oraSet{NoShare}}(\mathfrak{R}_0, \mathfrak{R}_1)$ \\
+ \item Return $0$ if $\ora{Spend}$ was used by the adversary on the coin handles
+ for $\V{coin}_0$ or $\V{coin}_1$ or $\ora{CorruptCustomer}$ was used on $\V{pkCustomer}_0$ or $\V{pkCustomer}_1$.
+ \item If $b = b'$ return $1$, otherwise return $0$.
+\end{enumerate}
+\end{mdframed}
+
+Note that unlike some other anonymity games defined in the literature (such as
+\cite{pointcheval2017cut}), our anonymity game always lets both customers spend
+in order to avoid having to hide the missing coin in one customer's wallet
+from the adversary.
+
+\subsection{Conservation}
+The adversary wins the conservation game if it can bring an honest customer in a
+situation where the spendable financial value left in the user's wallet plus
+the value spent for transactions known to the customer is less than the value
+withdrawn by the same customer through by the exchange.
+
+In practice, this property is necessary to guarantee that aborted or partially
+completed withdrawals, payments or refreshes, as well as other (transient)
+misbehavior from the exchange or merchant do not result in the customer losing
+money.
+
+\begin{mdframed}
+\small
+\noindent $\mathit{Exp}_{\cal A}^{conserv}(1^\lambda, 1^\kappa)$:
+\vspace{-0.5\topsep}
+\begin{enumerate}
+ \setlength\itemsep{0em}
+ \item $(\V{sksE}, \V{pksE}) \leftarrow \mathrm{ExchangeKeygen}(1^\lambda, 1^\kappa, M)$
+ \item $\V{pkCustomer} \leftarrow {\cal A}^{\oraSet{NoShare}}(\V{pksE})$
+ \item Return $0$ if $\V{pkCustomer}$ is a corrupted user.
+ \item \label{game:conserv:run} Run $\algo{WithdrawPickup}$ for each withdraw identifier $\V{wid}$
+ and $\algo{RefreshPickup}$ for each refresh identifier $\V{rid}$ that the user
+ has recorded in $\V{withdrawIds}$ and $\V{refreshIds}$. Run $\algo{Deposit}$
+ for all deposit permissions in $\V{acceptedContracts}$.
+ \item Let $v_{C}$ be the total financial value left on valid coins in $\V{wallet}[\V{pkCustomer}]$,
+ i.e., the denominated values minus the spend/refresh operations recorded in the exchange's database.
+ Let $v_{S}$ be the total financial value of contracts in $\V{acceptedContracts}[\V{pkCustomer}]$.
+ \item Return $1$ if $\V{withdrawn}[\V{pkCustomer}] > v_{C} + v_{S}$.
+\end{enumerate}
+\end{mdframed}
+
+
+Hence we ensure that:
+\begin{itemize}
+ \item if a coin was spent, it was spent for a contract that the customer
+ knows about, i.e., in practice the customer could prove that they ``own'' what they
+ paid for,
+ \item if a coin was refreshed, the customer ``owns'' the resulting coins,
+ even if the operation was aborted, and
+ \item if the customer withdraws, they can always obtain a coin whenever the
+ exchange accounted for a withdrawal, even when protocol executions are
+ intermittently aborted.
+\end{itemize}
+
+Note that we do not give the adversary access to the \ora{Share} oracle, since
+that would trivially allow the adversary to win the conservation game. In
+practice, conservation only holds for customers that do not share coins with
+parties that they do not fully trust.
+
+\subsection{Unforgeability}
+
+Intuitively, adversarial customers win if they can obtain more valid coins than
+they legitimately withdraw.
+
+\begin{mdframed}
+\small
+\noindent $\mathit{Exp}_{\cal A}^{forge}(1^\lambda, 1^\kappa)$:
+\vspace{-0.5\topsep}
+\begin{enumerate}
+ \setlength\itemsep{0em}
+ \item $(skE, pkE) \leftarrow \mathrm{ExchangeKeygen}()$
+ \item $(C_0, \dots, C_\ell) \leftarrow \mathcal{A}^{\oraSet{All}}(pkExchange)$
+ \item Return $0$ if any $C_i$ is not of the form $(\V{skCoin}, \V{pkCoin}, \V{pkD}, \V{coinCert})$
+ or any $\V{coinCert}$ is not a valid signature by $\V{pkD}$ on the respective $\V{pkCoin}$.
+ \item Return $1$ if the sum of the unspent value of valid coins in $C_0
+ \dots, C_\ell$ exceeds the amount withdrawn by corrupted
+ customers, return $0$ otherwise.
+\end{enumerate}
+\end{mdframed}
+
+
+\subsection{Income Transparency}
+
+Intuitively, the adversary wins if coins are in exclusive control of corrupted
+customers, but the exchange has no record of withdrawal or spending for them.
+This presumes that the adversary cannot delete from non-corrupted customer's
+wallets, even though it can use oracles to force protocol interactions of
+non-corrupted customers.
+
+For practical e-cash systems, income transparency disincentivizes the emergence
+of ``black markets'' among mutually distrusting customers, where currency
+circulates without the transactions being visible. This is in contrast to some
+other proposed e-cash systems and cryptocurrencies, where disintermediation is
+an explicit goal. The Link protocol introduces the threat of losing exclusive
+control of coins (despite having the option to refresh them) that were received
+without being visible as income to the exchange.
+
+\begin{mdframed}
+\small
+\noindent $\mathit{Exp}_{\cal A}^{income}(1^\lambda, 1^\kappa)$:
+\vspace{-0.5\topsep}
+\begin{enumerate}
+ \setlength\itemsep{0em}
+ \item $(skE, pkE) \leftarrow \mathrm{ExchangeKeygen}()$
+ \item $(\V{coin}_1, \dots, \V{coin}_\ell) \leftarrow \mathcal{A}^{\oraSet{All}}(pkExchange)$
+
+ (The $\V{coin}_i$ must be coins, including secret key and signature by the
+ denomination, for the adversary to win. However these coins need not be
+ present in any honest or corrupted customer's wallet.)
+ \item\label{game:income:spend} Augment the wallets of all non-corrupted customers with their
+ transitive closure using the \algo{Link} protocol.
+ Mark all remaining value on coins in wallets of non-corrupted customers as
+ spent in the exchange's database.
+ \item Let $L$ denote the sum of unspent value on valid coins in $(\V{coin}_1, \dots\, \V{coin}_\ell)$,
+ after accounting for the previous update of the exchange's database.
+ Also let $w'$ be the sum of coins withdrawn by corrupted customers.
+ Then $p := L - w'$ gives the adversary's untaxed income.
+ \item Let $w$ be the sum of coins withdrawn by non-corrupted customers, and
+ $s$ be the value marked as spent by non-corrupted customers, so that
+ $b := w - s$ gives the coins lost during refresh, that is the losses incurred attempting to hide income.
+ \item If $b+p \ne 0$, return $\frac{p}{b + p}$, i.e., the laundering ratio for attempting to obtain untaxed income. Otherwise return $0$.
+\end{enumerate}
+\end{mdframed}
+
+\section{Security Definitions}\label{sec:security-properties}
+We now give security definitions based upon the games defined in the previous
+section. Recall that $\lambda$ is the general security parameter, and $\kappa$ is the
+security parameter for income transparency. A polynomial-time adversary is implied to
+be polynimial in $\lambda+\kappa$.
+
+\begin{definition}[Anonymity]
+ We say that an e-cash scheme satisfies \emph{anonymity} if the success
+ probability $\Prb{b \randsel \{0,1\}: \mathit{Exp}_{\cal A}^{anon}(1^\lambda,
+ 1^\kappa, b) = 1}$ of the anonymity game is negligibly close to $1/2$ for any
+ polynomial-time adversary~$\mathcal{A}$.
+\end{definition}
+
+\begin{definition}[Conservation]
+ We say that an e-cash scheme satisfies \emph{conservation} if
+ the success probability $\Prb{\mathit{Exp}_{\cal A}^{conserv}(1^\lambda, 1^\kappa) = 1}$
+ of the conservation game is negligible for any polynomial-time adversary~$\mathcal{A}$.
+\end{definition}
+
+\begin{definition}[Unforgeability]
+ We say that an e-cash scheme satisfies \emph{unforgeability} if the success
+ probability $\Prb{\mathit{Exp}_{\cal A}^{forge}(1^\lambda, 1^\kappa) = 1}$ of
+ the unforgeability game is negligible for any polynomial-time adversary
+ $\mathcal{A}$.
+\end{definition}
+
+\begin{definition}[Strong Income Transparency]
+ We say that an e-cash scheme satisfies \emph{strong income transparency} if
+ the success probability $\Prb{\mathit{Exp}_{\cal A}^{income}(1^\lambda, 1^\kappa) \ne 0}$
+ for the income transparency game is negligible for any polynomial-time adversary~$\mathcal{A}$.
+\end{definition}
+The adversary is said to win one execution of the strong income transparency
+game if the game's return value is non-zero, i.e., there was at least one
+successful attempt to obtain untaxed income.
+
+
+\begin{definition}[Weak Income Transparency]
+ We say that an e-cash scheme satisfies \emph{weak income transparency}
+ if, for any polynomial-time adversary~$\mathcal{A}$,
+ the return value of the income transparency game satisfies
+ \begin{equation}\label{eq:income-transparency-expectation}
+ E\left[\mathit{Exp}_{\cal A}^{income}(1^\lambda, 1^\kappa)\right] \le {\frac{1}{\kappa}} \mathperiod
+ \end{equation}
+ In (\ref{eq:income-transparency-expectation}), the expectation runs over
+ any probability space used by the adversary and challenger.
+\end{definition}
+
+For some instantiations, e.g., ones based on zero-knowledge proofs, $\kappa$
+might be a security parameter in the traditional sense. However for an e-cash
+scheme to be useful in practice, the adversary does not need to have only
+negligible success probability to win the income transparency game. It
+suffices that the financial losses of the adversary in the game are a
+deterrent, after all our purpose of the game is to characterize tax evasion.
+
+Taler does not fulfill strong income transparency, since for Taler $\kappa$ must
+be a small cut-and-choose parameter, as the complexity of our cut-and-choose
+protocol grows linearly with $\kappa$. Instead we show that Taler satisfies
+weak income transparency, which is a statement about the adversary's financial
+loss when winning the game instead of the winning probability. The
+return-on-investment (represented by the game's return value) is bounded by
+$1/\kappa$.
+
+We still characterize strong income transparency, since it might be useful
+for other instantiations that provide more absolute guarantees.
+
+\section{Instantiation}
+We give an instantiation of our protocol syntax that is generic over
+a blind signature scheme, a signature scheme, a combined signature scheme / key
+exchange, a collision-resistant hash function and a pseudo-random function family (PRF).
+
+\subsection{Generic Instantiation}\label{sec:crypto:instantiation}
+Let $\textsc{BlindSign}$ be a blind signature scheme with the following syntax, where the party $\mathcal{S}$
+is the signer and $\mathcal{R}$ is the signature requester:
+\begin{itemize}
+ \item $\algo{KeyGen}_{BS}(1^\lambda) \mapsto (\V{sk}, \V{pk})$ is the key generation algorithm
+ for the signer of the blind signature protocol.
+ \item $\algo{Blind}_{BS}(\mathcal{S}(\V{sk}), \mathcal{R}(\V{pk}, m)) \mapsto (\overline{m}, r)$ is a possibly interactive protocol
+ to blind a message $m$ that is to be signed later. The result is a blinded message $\overline{m}$ and
+ a residual $r$ that allows to unblind a blinded signature on $m$ made by $\V{sk}$.
+ \item $\algo{Sign}_{BS}(\mathcal{S}(\V{sk}), \mathcal{R}(\overline{m})) \mapsto
+ \overline{\sigma}$ is an algorithm to sign a blinded message $\overline{m}$.
+ The result $\overline{\sigma}$ is a blinded signature that must be unblinded
+ using the $r$ returned from the corresponding blinding operation before
+ verification.
+ \item $\algo{UnblindSig}_{BS}(r, m, \overline{\sigma}) \mapsto \sigma$
+ is an algorithm to unblind a blinded signature.
+ \item $\algo{Verify}_{BS}(\V{pk}, m, \sigma) \mapsto b$ is an algorithm to
+ check the validity of an unblinded blind signature. Returns $1$ if the
+ signature $\sigma$ was valid for $m$ and $0$ otherwise.
+\end{itemize}
+
+Note that this syntax excludes some blind signature protocols, such as those
+with interactive/probabilistic verification or those without a ``blinding
+factor'', where the $\algo{Blind}_{BS}$ and $\algo{Sign}_{BS}$ and
+$\algo{UnblindSig}_{BS}$ would be merged into one interactive signing protocol.
+Such blind signature protocols have already been used to construct e-cash
+\cite{camenisch2005compact}.
+
+We require the following two security properties for $\textsc{BlindSign}$:
+\begin{itemize}
+ \item \emph{blindness}: It should be computationally infeasible for a
+ malicious signer to decide which of two messages has been signed first
+ in two executions with an honest user. The corresponding game can be defined as
+ in Abe and Okamoto \cite{abe2000provably}, with the additional enhancement
+ that the adversary generates the signing key \cite{schroder2017security}.
+ \item \emph{unforgeability}: An adversary that requests $k$ signatures with $\algo{Sign}_{BS}$
+ is unable to produce $k+1$ valid signatures with non-negligible probability.
+\end{itemize}
+For more generalized notions of the security of blind signatures see, e.g.,
+\cite{fischlin2009security,schroder2017security}.
+
+Let $\textsc{CoinSignKx}$ be combination of a signature scheme and key exchange protocol:
+
+\begin{itemize}
+ \item $\algo{KeyGenSec}_{CSK}(1^\lambda) \mapsto \V{sk}$ is a secret key generation algorithm.
+ \item $\algo{KeyGenPub}_{CSK}(\V{sk}) \mapsto \V{pk}$ produces the corresponding public key.
+ \item $\algo{Sign}_{CSK}(\V{sk}, m) \mapsto \sigma$ produces a signature $\sigma$ over message $m$.
+ \item $\algo{Verify}_{CSK}(\V{pk}, m, \sigma) \mapsto b$ is a signature verification algorithm.
+ Returns $1$ if the signature $\sigma$ is a valid signature on $m$ by $\V{pk}$, and $0$ otherwise.
+ \item $\algo{Kx}_{CSK}(\V{sk}_1, \V{pk}_2) \mapsto x$ is a key exchange algorithm that computes
+ the shared secret $x$ from secret key $\V{sk}_1$ and public key $\V{pk}_2$.
+\end{itemize}
+
+We occasionally need these key generation algorithms separately, but
+we usually combine them into $\algo{KeyGen}_{CSK}(1^\lambda) \mapsto (\V{sk}, \V{pk})$.
+
+We require the following security properties to hold for $\textsc{CoinSignKx}$:
+\begin{itemize}
+ \item \emph{unforgeability}: The signature scheme $(\algo{KeyGen}_{CSK}, \algo{Sign}_{CSK}, \algo{Verify}_{CSK})$
+ must satisfy existential unforgeability under chosen message attacks (EUF-CMA).
+
+ \item \emph{key exchange completeness}:
+ Any probabilistic polynomial-time adversary has only negligible chance to find
+ a degenerate key pair $(\V{sk}_A, \V{pk}_A)$ such that for some
+ honestly generated key pair
+ $(\V{sk}_B, \V{pk}_B) \leftarrow \algo{KeyGen}_{CSK}(1^\lambda)$
+ the key exchange fails, that is
+ $\algo{Kex}_{CSK}(\V{sk}_A, \V{pk}_B) \neq \algo{Kex}_{CSK}(\V{sk}_B, \V{pk}_A)$,
+ while the adversary can still produce a pair $(m, \sigma)$ such that $\algo{Verify}_{BS}(\V{pk}_A, m, \sigma) = 1$.
+
+ \item \emph{key exchange security}: The output of $\algo{Kx}_{CSK}$ must be computationally
+ indistinguishable from a random shared secret of the same length, for inputs that have been
+ generated with $\algo{KeyGen}_{CSK}$.
+\end{itemize}
+
+Let $\textsc{Sign} = (\algo{KeyGen}_{S}, \algo{Sign}_{S}, \algo{Verify}_{S})$ be a signature
+scheme that satisfies selective unforgeability under chosen message attacks (SUF-CMA).
+
+Let $\V{PRF}$ be a pseudo-random function family and $H : \{0,1\}^* \rightarrow \{0,1\}^\lambda$
+a collision-resistant hash function.
+
+Using these primitives, we now instantiate the syntax of our income-transparent e-cash scheme:
+
+\begin{itemize}
+ \item $\algo{ExchangeKeygen}(1^{\lambda}, 1^{\kappa}, \mathfrak{D})$:
+
+ Generate the exchange's signing key pair $\V{skESig} \leftarrow \algo{KeyGen}_{S}(1^\lambda)$.
+
+ For each element in the sequence $\mathfrak{D} = d_1,\dots,d_n$, generate
+ denomination key pair $(\V{skD}_i, \V{pkD}_i) \leftarrow \algo{KeyGen}_{BS}(1^\lambda)$.
+ \item $\algo{CustomerKeygen}(1^\lambda,1^\kappa)$:
+ Return key pair $\algo{KeyGen}_S(1^\lambda)$.
+ \item $\algo{MerchantKeygen}(1^\lambda,1^\kappa)$:
+ Return key pair $\algo{KeyGen}_S(1^\lambda)$.
+
+ \item $\algo{WithdrawRequest}(\prt{E}(\V{sksE}, \V{pkCustomer}), \prt{C}(\V{skCustomer}, \V{pksE}, \V{pkD}))$:
+
+ Let $\V{skD}$ be the exchange's denomination secret key corresponding to $\V{pkD}$.
+
+ \begin{enumerate}
+ \item \prt{C} generates coin key pair $(\V{skCoin}, \V{pkCoin}) \leftarrow \algo{KeyGen}_{CSK}(1^\lambda)$
+ \item \prt{C} runs $(\overline{m}, r) \leftarrow \algo{Blind}_{CSK}(\mathcal{E}(\V{skCoin}), \mathcal{C}(m))$ with the exchange
+ \end{enumerate}
+
+ The withdraw identifier is then
+ \begin{equation*}
+ \V{wid} := (\V{skCoin}, \V{pkCoin}, \overline{m}, r)
+ \end{equation*}
+
+
+ \item $\algo{WithdrawPickup}(\prt{E}(\V{sksE}, \V{pkCustomer}), \prt{C}(\V{skCustomer}, \V{pksE}, \V{wid}))$:
+
+ The customer looks up $\V{skCoin}$, $\V{pkCoin}$, \V{pkD} $\overline{m}$
+ and $r$ via the withdraw identifier $\V{wid}$.
+
+ \begin{enumerate}
+ \item \prt{C} runs $\overline{\sigma} \leftarrow \algo{Sign}_{BS}(\mathcal{E}(\V{skD}), \mathcal{C}(\overline{m}))$ with the exchange
+ \item \prt{C} unblinds the signature $\sigma \leftarrow \algo{UnblindSig}_{BS}(\overline{\sigma}, r, \overline{m})$
+ and stores the coin $(\V{skCoin}, \V{pkCoin}, \V{pkD}, \sigma)$ in their wallet.
+ \end{enumerate}
+
+ \item $\algo{Spend}(\V{transactionId}, f, \V{coin}, \V{pkMerchant})$:
+ Let $(\V{skCoin}, \V{pkCoin}, \V{pkD}, \sigma_C) := \V{coin}$.
+ The deposit permission is computed as
+ \begin{equation*}
+ \V{depositPermission} := (\V{pkCoin}, \sigma_D, m),
+ \end{equation*}
+ where
+ \begin{align*}
+ m &:= (\V{pkCoin}, \V{pkD}, \V{sigma}_C, \V{transactionId}, f, \V{pkMerchant}) \\
+ \sigma_D &\leftarrow \algo{Sign}_{CSK}(\V{skCoin}, m).
+ \end{align*}
+
+ \item $\algo{Deposit}(\prt{E}(\V{sksE}, \V{pkMerchant}), \prt{M}(\V{skMerchant}, \V{pksE}, \V{depositPermission}))$:
+ The merchant sends \V{depositPermission} to the exchange.
+
+ The exchange checks that the deposit permission is well-formed and sets
+ \begin{align*}
+ (\V{pkCoin}, \V{pkD}, \sigma_C, \sigma_D, \V{transactionId}, f, \V{pkMerchant})) &:= \V{depositPermission}
+ \end{align*}
+
+ The exchange checks the signature on the deposit permission and the validity of the coin with
+ \begin{align*}
+ b_1 := \algo{Verify}_{CSK}(\V{pkCoin}, \sigma_D, m) \\
+ b_2 := \algo{Verify}_{BS}(\V{pkD}, \sigma_C, \V{pkCoin})
+ \end{align*}
+ and aborts of $b_1 = 0$ or $b_2=0$.
+
+ The exchange aborts if spending $f$ would result in overspending
+ $\V{pkCoin}$ based on existing deposit/refresh records, and otherwise marks
+ $\V{pkCoin}$ as spent for $D(\V{pkD})$.
+
+ \item $\algo{RefreshRequest}(\prt{E}(\V{sksE}, \V{pkCustomer}), \prt{C}(\V{skCustomer}, \V{pksE}, \V{coin}_0, \V{pkD}_u))$:
+
+ Let $\V{skD}_u$ be the secret key corresponding to $\V{pkD}_u$.
+
+ We write
+ \[ \algo{Blind}^*_{BS}(\mathcal{S}(\V{sk}, \V{skESig}), \mathcal{R}(R, \V{skR}, \V{pk}, m)) \mapsto (\overline{m}, r, \mathcal{T}_{B*}) \]
+ for a modified version of $\algo{Blind}_{BS}$ where the signature requester
+ $\mathcal{R}$ takes all randomness from the sequence
+ $\left(\V{PRF}(R,\texttt{"blind"}\Vert n)\right)_{n>0}$, the messages from
+ the exchange are recorded in transcript $\mathcal{T}_{B*}$, all
+ messages sent by $\mathcal{R}$ are signed with $\V{skR}$ and all messages sent by $\mathcal{S}$
+ are signed with $\V{skESig}$.
+
+ Furthermore, we write \[ \algo{KeyGen}^*_{CSK}(R, 1^\lambda) \mapsto
+ (\V{sk}, \V{pk}) \] for a modified version of the key generation algorithm
+ that takes randomness from the sequence $\left(\V{PRF}(R,\texttt{"key"}\Vert
+ n)\right)_{n>0}$.
+
+ For each $i\in \{1,\dots,\kappa \}$, the customer
+ \begin{enumerate}
+ \item generates seed $s_i \randsel \{1, \dots, 1^\lambda\}$
+ \item generates transfer key pair $(t_i, T_i) \leftarrow \algo{KeyGen}^*_{CSK}(s_i, 1^\lambda)$
+ \item computes transfer secret $x_i \leftarrow \algo{Kx}(t_i, \V{pkCoin}_0)$
+ \item computes coin key pair $(\V{skCoin}_i, \V{pkCoin}_i) \leftarrow
+ \algo{KeyGen}^*_{CSK}(x_i, 1^\lambda)$
+ \item and executes the modified blinding protocol
+ \[
+ (\overline{m}_i, r_i, \mathcal{T}_{(B*,i)}) \leftarrow
+ \algo{Blind}^*_{BS}(\mathcal{E}(\V{skD_u}), \mathcal{C}(x_i, \V{skCoin}_0, \V{pkD}_u, \V{pkCoin}_i))
+ \]
+ with the exchange.
+ \end{enumerate}
+
+ The customer stores the refresh identifier
+ \begin{equation}
+ \V{rid} := (\V{coin}_0, \V{pkD}_u, \{ s_i \}, \{ \overline{m}_i \}, \{r_i\}, \{\mathcal{T}_{(B*,i)}\} ).
+ \end{equation}
+
+ \item $\algo{RefreshPickup}(\prt{E}(\V{sksE}, \V{pkCustomer}), \prt{C}(\V{skCustomer}, \V{pksE}, \V{rid})) \rightarrow \mathcal{T}$:
+ The customer looks up the refresh identifier $\V{rid}$ and recomputes the transfer key pairs,
+ transfer secrets and new coin key pairs.
+
+ Then customer sends the commitment $\pi_1 = (\V{pkCoin}_0, \V{pkD}_u, h_C)$ together with signature $\V{sig}_1
+ \leftarrow \algo{Sign}_{CSK}(\V{skCoin}_0, \pi_1)$ to the exchange, where
+ \begin{align*}
+ h_T &:= H(T_1, \dots, T_\kappa)\\
+ h_{\overline{m}} &:= H(\overline{m}_1, \dots, \overline{m}_\kappa)\\
+ h_C &:= H(h_T \Vert h_{\overline{m}})
+ \end{align*}
+
+ The exchange checks the signature $\V{sig}_1$, and aborts if invalid. Otherwise,
+ depending on the commitment:
+ \begin{enumerate}
+ \item If the exchange did not see $\pi_1$ before, it marks $\V{pkCoin}_0$
+ as spent for $D(\V{pkD}_u)$, chooses a uniform random $0 \le \gamma < \kappa$, stores it,
+ and sends this choice in a signed message $(\gamma, \V{sig}_2)$ to the customer,
+ where $\V{sig}_2 \leftarrow \algo{Sign}_{S}(\V{skESig}, \gamma)$.
+ \item Otherwise, the exchange sends back the same $\pi_2$ as it sent for the last
+ equivalent $\pi_1$.
+ \end{enumerate}
+
+ The customer checks if $\pi_2$ differs from a previously received $\pi_2'$ for the same
+ request $\pi_1$, and aborts if such a conflicting response was found.
+ Otherwise, the customer in response to $\pi_2$ sends the reveal message
+ \begin{equation*}
+ \pi_3 = T_\gamma, \overline{m}_\gamma,
+ (s_1, \dots, s_{\gamma-1}, s_{\gamma+1}, \dots, s_\kappa)
+ \end{equation*}
+ and signature
+ \begin{equation*}
+ \V{sig}_{3'} \leftarrow \algo{Sign}_{CSK}(\V{skCoin}_0, (\V{pkCoin}_0,
+ \V{pkD}_u, \mathcal{T}_{(B*,\gamma)}, T_\gamma, \overline{m}_\gamma))
+ \end{equation*} to the exchange. Note that $\V{sig}_{3'}$ is not a signature
+ over the full reveal message, but is primarily used in the linking protocol for
+ checks by the customer.
+
+ The exchange checks the signature $\V{sig}_{3'}$ and then computes for $i \ne \gamma$:
+ \begin{align*}
+ (t_i', T_i') &\leftarrow \algo{KeyGen}^*_{CSK}(s_i, 1^\lambda)\\
+ x_i' &\leftarrow \algo{Kx}(t_i, \V{pkCoin}_0)\\
+ (\V{skCoin}_i', \V{pkCoin}_i') &\leftarrow
+ \algo{KeyGen}^*_{CSK}(x_i', 1^\lambda) \\
+ h_T' &:= H(T'_1, \dots, T_{\gamma-1}', T_\gamma, T_{\gamma+1}', \dots, T_\kappa')
+ \end{align*}
+ and simulates the blinding protocol with recorded transcripts (without signing each message,
+ as indicated by the dot ($\cdot$) instead of a signing secret key), obtaining
+ \begin{align*}
+ (\overline{m}_i', r_i', \mathcal{T}_i) &\leftarrow
+ \algo{Blind}^*_{BS}(\mathcal{S}(\V{skD}_u), \mathcal{R}(x_i', \cdot, \V{pkD}_u, \V{skCoin}'_i))\\
+ \end{align*}
+ and finally
+ \begin{align*}
+ h_{\overline{m}}' &:= H(\overline{m}_1', \dots, \overline{m}_{\gamma-1}', \overline{m}_\gamma, \overline{m}_{\gamma+1}',\dots, \overline{m}_\kappa')\\
+ h_C' &:= H(h_T' \Vert h_{\overline{m}}').
+ \end{align*}
+
+ Now the exchange checks if $h_C = h_C'$, and aborts the protocol if the check fails.
+ Otherwise, the exchange sends a message back to $\prt{C}$ that the commitment verification succeeded and includes
+ the signature
+ \begin{equation*}
+ \overline{\sigma}_\gamma := \algo{Sign}_{BS}(\mathcal{E}(\V{skD}_u), \mathcal{C}(\overline{m}_\gamma)).
+ \end{equation*}
+
+ As a last step, the customer obtains the signature $\sigma_\gamma$ on the new coin's public key $\V{pkCoin}_u$ with
+ \begin{equation*}
+ \sigma_\gamma := \algo{UnblindSig}(r_\gamma, \V{pkCoin}_\gamma, \overline{\sigma}_\gamma).
+ \end{equation*}
+
+ Thus, the new, unlinkable coin is $\V{coin}_u := (\V{skCoin}_\gamma, \V{pkCoin}_\gamma, \V{pkD}_u, \sigma_\gamma)$.
+
+ \item $\algo{Link}(\prt{E}(\V{sksE}), \prt{C}(\V{skCustomer}, \V{pksE}, \V{coin}_0))$:
+ The customer sends the public key $\V{pkCoin}_0$ of $\V{coin}_0$ to the exchange.
+
+ For each completed refresh on $\V{pkCoin}_0$ recorded in the exchange's
+ database, the exchange sends the following data back to the customer: the
+ signed commit message $(\V{sig}_1, \pi_1)$, the transfer public key
+ $T_\gamma$, the signature $\V{sig}_{3'}$, the blinded signature $\overline{\sigma}_\gamma$, and the
+ transcript $\mathcal{T}_{(B*,\gamma)}$ of the customer's and exchange's messages
+ during the $\algo{Blind}^*_{BS}$ protocol execution.
+
+ The following logic is repeated by the customer for each response:
+ \begin{enumerate}
+ \item Verify the signatures (both from $\V{pkESig}$ and $\V{pkCoin}_0$) on the transcript $\mathcal{T}_{(B*,\gamma)}$,
+ abort otherwise.
+ \item Verify that $\V{sig}_1$ is a valid signature on $\pi_1$ by $\V{pkCoin}_0$, abort otherwise.
+ \item Re-compute the transfer secret and the new coin's key pair as
+ \begin{align*}
+ x_\gamma &\leftarrow \algo{Kx}_{CSK}(\V{skCoin}_0, T_\gamma)\\
+ (\V{skCoin}_\gamma, \V{pkCoin}_\gamma) &\leftarrow \algo{KeyGen}_{CSK}^*(x_\gamma, 1^\lambda).
+ \end{align*}
+ \item Simulate the blinding protocol with the message transcript received from the exchange to obtain
+ $(\overline{m}_\gamma, r_\gamma)$.
+ \item Check that $\algo{Verify}_{CSK}(\V{pkCoin}_0,
+ \V{pkD}_u, \V{skCoin}_0,(\mathcal{T}_{(B*,\gamma)}, \overline{m}_\gamma), \V{sig}_{3'})$
+ indicates a valid signature, abort otherwise.
+ \item Unblind the signature to obtain $\sigma_\gamma \leftarrow \algo{UnblindSig}(r_\gamma, \V{pkCoin}_\gamma, \overline{\sigma}_\gamma)$
+ \item (Re-)add the coin $(\V{skCoin}_\gamma, \V{pkCoin}_\gamma, \V{pkD}_u, \sigma_\gamma)$ to the customer's wallet.
+ \end{enumerate}
+
+\end{itemize}
+
+\subsection{Concrete Instantiation}
+We now give a concrete instantiation that is used in the implementation
+of GNU Taler for the schemes \textsc{BlindSign}, \textsc{CoinSignKx} and \textsc{Sign}.
+
+For \textsc{BlindSign}, we use RSA-FDH blind signatures
+\cite{chaum1983blind,bellare1996exact}. From the information-theoretic
+security of blinding, the computational blindness property follows directly. For
+the unforgeability property, we additionally rely on the RSA-KTI assumption as
+discussed in \cite{bellare2003onemore}. Note that since the blinding step in
+RSA blind signatures is non-interactive, storage and verification of the
+transcript is omitted in refresh and link.
+
+We instantiate \textsc{CoinSignKx} with signatures and key exchange operations
+on elliptic curves in Edwards form, where the same key is used for signatures
+and the Diffie--Hellman key exchange operations. In practice, we use Ed25519
+\cite{bernstein2012high} / Curve25519 \cite{bernstein2006curve25519} for
+$\lambda=256$. We caution that some other elliptic curve key exchange
+implementation might not satisfy the completeness property that we require, due
+to the lack of complete addition laws.
+
+For \textsc{Sign}, we use elliptic-curve signatures, concretely Ed25519. For
+the collision-resistant hash function $H$ we use SHA-512 \cite{rfc4634} and
+HKDF \cite{rfc5869} as a PRF.
+
+%In Taler's refresh, we avoid key exchange failures entirely because the
+%Edwards addition law is complete abelian group operation on the curve,
+%and the signature scheme verifies that the point lies on the curve.
+%% https://safecurves.cr.yp.to/refs.html#2007/bernstein-newelliptic
+%% https://safecurves.cr.yp.to/complete.html
+%We warn however that Weierstrass curves have incomplete addition formulas
+%that permit an adversarial merchant to pick transfer keys that yields failures.
+%There are further implementation mistakes that might enable collaborative
+%key exchange failures, like if the exchange does not enforce the transfer
+%private key being a multiple of the cofactor.
+%
+%In this vein, almost all post-quantum key exchanges suffer from key exchange
+%failures that permit invalid key attacks against non-ephemeral keys.
+%All these schemes support only one ephemeral party by revealing the
+%ephemeral party's private key to the non-ephemeral party,
+% ala the Fujisaki-Okamoto transform~\cite{fujisaki-okamoto} or similar.
+%We cannot reveal the old coin's private key to the exchange when
+%verifying the transfer private keys though, which
+% complicates verifying honest key generation of the old coin's key.
+
+
+\section{Proofs}
+%\begin{mdframed}
+% Currently the proofs don't have any explicit tightess bounds.
+% Because we don't know where to ``inject'' the value that we get from the challenger when carrying out
+% a reduction, we need to randomly guess in which coin/signature we should ``hijack'' our challenge value.
+% Thus for the proofs to work fully formally, we need to bound the total number of oracle invocations,
+% and our exact bound for the tightness of the reduction depends on this limit.
+%\end{mdframed}
+
+We now give proofs for the security properties defined in Section \ref{sec:security-properties}
+with the generic instantiation of Taler.
+
+\subsection{Anonymity}
+
+\begin{theorem}
+ Assuming
+ \begin{itemize}
+ \item the blindness of \textsc{BlindSign},
+ \item the unforgeability and key exchange security of \textsc{CoinSignKx}, and
+ \item the collision resistance of $H$,
+ \end{itemize}
+ our instantiation satisfies anonymity.
+\end{theorem}
+
+\begin{proof}
+ We give a proof via a sequence of games $\mathbb{G}_0(b), \mathbb{G}_1(b),
+ \mathbb{G}_2(b)$, where $\mathbb{G}_0(b)$ is the original anonymity game
+ $\mathit{Exp}_{\cal A}^{anon}(1^\lambda, 1^\kappa, b)$. We show that the
+ adversary can distinguish between subsequent games with only negligible
+ probability. Let $\epsilon_{HC}$ and $\epsilon_{KX}$ be the advantage of an
+ adversary for finding hash collisions and for breaking the security of the
+ key exchange, respectively.
+
+ We define $\mathbb{G}_1$ by replacing the link oracle \ora{Link} with a
+ modified version that behaves the same as \ora{Link}, unless the adversary
+ responds with link data that did not occur in the transcript of a successful
+ refresh operation, but despite of that still passes the customer's
+ verification. In that case, the game is aborted instead.
+
+ Observe that in case this failure event happens, the adversary must have forged a
+ signature on $\V{sig}_{3}$ on values not signed by the customer, yielding
+ an existential forgery. Thus, $\left| \Prb{\mathbb{G}_0 = 1} - \Prb{\mathbb{G}_1 = 1}
+ \right|$ is negligible.
+
+ In $\mathbb{G}_2$, the refresh oracle is modified so that the customer
+ responds with value drawn from a uniform random distribution $D_1$ for the
+ $\gamma$-th commitment instead of using the key exchange function. We must
+ handle the fact that $\gamma$ is chosen by the adversary after seeing the
+ commitments, so the challenger first makes a guess $\gamma^*$ and replaces
+ only the $\gamma^*$-th commitment with a uniform random value. If the
+ $\gamma$ chosen by the adversary does not match $\gamma^*$, then the
+ challenger rewinds \prt{A} to the point where the refresh oracle was called.
+ Note that we only replace the one commitment that
+ will not be opened to the adversary later.
+
+ Since $\kappa \ll \lambda$ and the security property of $\algo{Kx}$
+ guarantees that the adversary cannot distinguish the result of a key exchange
+ from randomness, the runtime complexity of the challenger still stays
+ polynomial in $\lambda$. An adversary that could with high probability
+ choose a $\gamma$ that would cause a rewind, could also distinguish
+ randomness from the output of $\algo{Kx}$.
+
+ %\mycomment{Tighness bound also missing}
+
+ We now show that $\left| \Prb{\mathbb{G}_1 = 1} - \Prb{\mathbb{G}_2 = 1}
+ \right| \le \epsilon_{KX}$ by defining a distinguishing game $\mathbb{G}_{1
+ \sim 2}$ for the key exchange as follows:
+
+ \bigskip
+ \noindent $\mathbb{G}_{1 \sim 2}(b)$:
+ \vspace{-0.5\topsep}
+ \begin{enumerate}
+ \setlength\itemsep{0em}
+ \item If $b=0$, set
+ \[
+ D_0 := \{ (A, B, \V{Kex}(a, B)) \mid (a, A) \leftarrow \V{KeyGen}(1^\lambda),(b, B) \leftarrow \V{KeyGen}(1^\lambda) \}.
+ \]
+ Otherwise, set
+ \[
+ D_1 := \{ (A, B, C) \mid (a, A) \leftarrow \V{KeyGen}(1^\lambda),
+ (b, B) \leftarrow \V{KeyGen}(1^\lambda),
+ C \randsel \{1,\dots,2^\lambda\} \}.
+ \]
+
+ \item Return $\mathit{Exp'}_{\cal A}^{anon}(b, D_b)$
+
+ (Modified anonymity game where the $\gamma$-th commitment in the
+ refresh oracle is drawn uniformly from $D_b$ (using rewinding). Technically, we need to
+ draw from $D_b$ on withdraw for the coin secret key, write it to a table, look it up on refresh and
+ use the matching tuple, so that with $b=0$ we perfectly simulate $\mathbb{G}_1$.)
+ \end{enumerate}
+
+ Depending on the coin flip $b$, we either simulate
+ $\mathbb{G}_1$ or $\mathbb{G}_2$ perfectly for our adversary~$\mathcal{A}$
+ against $\mathbb{G}_1$. At the same time $b$ determines whether \prt{A}
+ receives the result of the key exchange or real randomness. Thus, $\left|
+ \Prb{\mathbb{G}_1 = 1} - \Prb{\mathbb{G}_2 = 1} \right| = \epsilon_{KX}$ is
+ exactly the advantage of $\mathbb{G}_{1 \sim 2}$.
+
+ We observe in $\mathbb{G}_2$ that as $x_\gamma$ is uniform random and not
+ learned by the adversary, the generation of $(\V{skCoin}_\gamma,
+ \V{pkCoin}_\gamma)$ and the execution of the blinding protocol is equivalent (under the PRF assumption)
+ to using the randomized algorithms
+ $\algo{KeyGen}_{CSK}$ and $\algo{Blind}_{BS}$.
+
+ By the blindness of the $\textsc{BlindSign}$ scheme, the adversary is not
+ able to distinguish blinded values from randomness. Thus, the adversary is
+ unable to correlate a $\algo{Sign}_{BS}$ operation in refresh or withdraw
+ with the unblinded value observed during $\algo{Deposit}$.
+
+ We conclude the success probability for $\mathbb{G}_2$ must be $1/2$ and
+ hence the success probability for $\mathit{Exp}_{\cal A}^{anon}(1^\lambda,
+ \kappa, b)$ is at most $1/2 + \epsilon(\lambda)$, where $\epsilon$ is a
+ negligible function.
+\end{proof}
+% RSA ratios vs CDH in BLS below
+
+\subsection{Conservation}
+
+\begin{theorem}
+ Assuming existential unforgeability (EUF-CMA) of \textsc{CoinSignKx}, our instantiation satisfies conservation.
+\end{theorem}
+
+\begin{proof}
+
+% FIXME: argue that reduction is tight when you have malleability
+ In honest executions, we have $\V{withdrawn}[\V{pkCustomer}] = v_C + v_S$, i.e.,
+ the coins withdrawn add up to the coins still available and the coins spent
+ for known transactions.
+
+ In order to win the conservation game, the adversary must increase
+ $\V{withdrawn}[\V{pkCustomer}]$ or decrease $v_C$ or $v_S$. An adversary can
+ abort withdraw operations, thus causing $\V{withdrawn}[\V{pkCustomer}]$ to increase,
+ while the customer does not obtain any coins. However, in step
+ \ref{game:conserv:run}, the customer obtains coins from interrupted withdraw
+ operations. Similarly, for the refresh protocol, aborted \algo{RefreshRequest} / \algo{RefreshPickup}
+ operations that result in a coin's remaining value being reduced are completed
+ in step \ref{game:conserv:run}.
+
+ Thus, the only remaining option for the adversary is to decrease $v_C$ or $v_S$
+ with the $\ora{RefreshPickup}$ and $\ora{Deposit}$ oracles, respectively.
+
+ Since the exchange verifies signatures made by the secret key of the coin
+ that is being spent/refreshed, the adversary must forge this signature or have
+ access to the coin's secret key. As we do not give the adversary access to
+ the sharing oracle, it does not have direct access to any of the honest
+ customer's coin secret keys.
+
+ Thus, the adversary must either compute the coin's secret key from observing
+ the coin's public key (e.g., during a partial deposit operation), or forge
+ signatures directly. Both possibilities allow us to carry out a reduction
+ against the unforgeability property of the $\textsc{CoinSignKx}$ scheme, by
+ injecting the challenger's public key into one of the coins.
+
+\end{proof}
+
+\subsection{Unforgeability}
+
+\begin{theorem}
+Assuming the unforgeability of \textsc{BlindSign}, our instantiation satisfies {unforgeability}.
+\end{theorem}
+
+\begin{proof}
+The adversary must have produced at least one coin that was not blindly
+signed by the exchange.
+In order to carry out a reduction from this adversary to a blind signature
+forgery, we inject the challenger's public key into one randomly chosen
+denomination. Since we do not have access to the corresponding secret key
+of the challenger, signing operations for this denomination are replaced
+with calls to the challenger's signing oracle in \ora{WithdrawPickup} and
+\ora{RefreshPickup}. For $n$ denominations, an adversary against the
+unforgeability game would produce a blind signature forgery with probability $1/n$.
+\end{proof}
+
+%TODO: RSA-KTI
+
+\subsection{Income Transparency}
+\begin{theorem}
+ Assuming
+ \begin{itemize}
+ \item the unforgeability of \textsc{BlindSign},
+ \item the key exchange completeness of \textsc{CoinSignKx},
+ \item the pseudo-random function property of \V{PRF}, and
+ \item the collision resistance of $H$,
+ \end{itemize}
+ our instantiation satisfies {weak income transparency}.
+\end{theorem}
+
+\begin{proof}
+ We consider the directed forest on coins induced by the refresh protocol.
+ It follows from unforgeability that any coin must originate from some
+ customer's withdraw in this graph.
+ We may assume that all $\V{coin}_1, \dots, \V{coin}_l$ originate from
+ non-corrupted users, for some $l \leq \ell$. % So $\ell \leq w + |X|$.
+
+ For any $i \leq l$, there is a final refresh operation $R_i$ in which
+ a non-corrupted user could obtain the coin $C'$ consumed in the refresh
+ via the linking protocol, but no non-corrupted user could obtain the
+ coin provided by the refresh, as otherwise $\V{coin}_i$ gets marked as
+ spent in step step \ref{game:income:spend}.
+ Set $F := \{ R_i \mid i \leq l \}$.
+
+ During each $R_i \in F$, our adversary must have submitted a blinded
+ coin and transfer public key for which the linking protocol fails to
+ produce the resulting coin correctly, otherwise the coin would have
+ been spent in step \ref{game:income:spend}. In this case, we consider
+ several non-exclusive cases
+ \begin{enumerate}
+ \item the execution of the refresh protocol is incomplete,
+ \item the commitment for the $\gamma$-th blinded coin and transfer
+ public key is dishonest,
+ \item a commitment for a blinded coin and transfer public key other
+ than the $\gamma$-th is dishonest,
+ \end{enumerate}
+
+ We show these to be exhaustive by assuming their converses all hold: As the
+ commitment is signed by $\V{skCoin}_0$, our key exchange completeness
+ assumption of $\textsc{CoinSignKx}$ applies to the coin public key.
+ Any revealed values must match our honestly computed commitments,
+ as otherwise a collision in $H$ would have been found.
+ We assumed
+ the revealed $\gamma$-th transfer public key is honest. Hence our key
+ exchange completeness assumption of $\textsc{CoinSignKx}$ yields
+ $\algo{Kex}_{CSK}(t,C') = \algo{Kex}_{CSK}(c',T)$ where $T =
+ \algo{KeyGenPub}_{CSK}(t)$ is the transfer key, thus the customer obtains the
+ correct transfer secret. We assumed the refresh concluded and all
+ submissions besides the $\gamma$-th were honest, so the exchange correctly
+ reveals the signed blinded coin. We assumed the $\gamma$-th blinded coin is
+ correct too, so customer now re-compute the new coin correctly, violating
+ $R_i \in F$.
+
+ We shall prove
+ \begin{equation}\label{eq:income-transparency-proof}
+ \Exp{{\frac{p}{b + p}} \middle| F \neq \emptyset} = {\frac{1}{\kappa}}
+ \end{equation}
+ where the expectation runs over
+ any probability space used by the adversary and challenger.
+
+ We shall now consider executions of the income transparency game with an
+ optimal adversary with respect to maximizing $\frac{p}{b + p}$. Note that this
+ is permissible since we are not carring out a reduction, but are interested
+ in the expectation of the game's return value.
+
+ As a reminder, if a refresh operation is initiated using a false commitment
+ that is detected by the exchange, then the new coin cannot be obtained, and
+ contributes to the lost coins $b := w - s$ instead of the winnings $p := L -
+ w'$. We also note $b + p$ gives the value of
+ refreshes attempted with false commitments. As these are non-negative,
+ $\frac{p}{b + p}$ is undefined if and only if $p = 0$ and $b = 0$, which happens if and
+ only if the adversary does not use false commitments, i.e., $F = \emptyset$.
+
+ We may now assume for optimality that $\mathcal{A}$ submits a false
+ commitment for at most one choice of $\gamma$ in any $R_i \in F$, as
+ otherwise the refresh always fails. Furthermore, for an optimal adversary we
+ can exclude refreshes in $F$ that are incomplete, but that would be possible
+ to complete successfully, as completing such a refresh would only increase the
+ adversaries winnings.
+
+ We emphasize that an adversary that loses an $R_i$ loses the coin that would
+ have resulted from it completely, while an optimal adversary who wins an
+ $R_i$ should not gamble again. Indeed, an adversary has no reason to touch
+ its winnings from an $R_i$.
+
+% There is no way to influence $p$ or $b$ through withdrawals or spends
+% by corrupted users of course. In principle, one could decrease $b$ by
+% sharing from a corrupted user to a non-corrupted users,
+% but we may assume this does not occur either, again by optimality.
+
+ For any $R_i$, there are $\kappa$ game runs identical up through the
+ commitment phase of $R_i$ and exhibiting different outcomes based on the
+ challenger's random choice of $\gamma$.
+ If $v_i$ is the financial value of the coin resulting from refresh operation
+ $R_i$ then one of the possible runs adds $v_i$ to $p$, while the remaining
+ $\kappa-1$ runs add $v_i$ to $b$.
+
+ We define $p_i$ and $b_i$ to be these contributions summed over the $\kappa$ possible
+ runs, i.e.,
+ \begin{align*}
+ p_i &:= v_i\\
+ b_i &= (\kappa - 1)v_i
+ \end{align*}
+ The adversary will succeed in $1/\kappa$ runs ($p_i=v$) and loses in
+ $(\kappa-1)/\kappa$ runs ($p_i=0$). Hence:
+ \begin{align*}
+ \Exp{{\frac{p}{b + p}} \middle| F \neq \emptyset}
+ &= \frac{1}{|F|} \sum_{R_i\in F} {p_i \over b_i + p_i} \\
+ &= \frac{1}{\kappa |F|} \sum_{R_i\in F} {\frac{v_i}{0 + v_i}} + \frac{\kappa-1}{\kappa |F|} \sum_{R_i \in F} {\frac{0}{v_i + 0}} \\
+ &= {\frac{1}{\kappa}},
+ \end{align*}
+ which yields the equality (\ref{eq:income-transparency-proof}).
+
+As for $F = \emptyset$, the return value of the game must be $0$, we conclude
+\begin{equation*}
+ E\left[\mathit{Exp}_{\cal A}^{income}(1^\lambda, 1^\kappa)\right] \le {\frac{1}{\kappa}}.
+\end{equation*}
+
+\end{proof}
+
+\section{Discussion}
+
+\subsection{Limitations}
+Not all features of our implementation are part of the security model and proofs.
+In particular, the following features are left out of the formal discussion:
+
+\begin{itemize}
+ \item Reserves. In our formal model, we effectively assume that every customer has access
+ to exactly one unlimited reserve.
+ \item Offline and online keys. In our implementation, the exchange
+ has one offline master signing key, and online signing keys with
+ a shorter live span.
+ \item Refunds allow merchants to effectively ``undo'' a deposit operation
+ before the exchange settles the transaction with the merchant. This simple
+ extension preserves unlinkability of payments through refresh.
+ %\item Indian merchant scenario. In some markets (such as India), it is more
+ % likely for the customer to have Internet access (via smart phones) than for
+ % merchants, who in the case of street vendors often have simple phones
+ % without Internet access or support for apps. To use Taler in this case,
+ % it must be possible
+ \item Timeouts. In practice, a merchant gives the customer a deadline until
+ which the payment for a contract must have been completed, potentially by
+ using multiple coins.
+
+ If a customer is unable to complete a payment (e.g., because they notice
+ that their coins are already spent after a restore from backup), a refund
+ for this partial payment can be requested from the merchant.
+
+ Should the merchant become unavailable after a partially completed payment,
+ there are two possibilities: Either the customer can deposit the coins on
+ behalf of the merchant to obtain proof of their on-time payment, which can
+ be used in a later arbitration if necessary. Alternatively, the customer
+ can ask the exchange to undo the partial payments, though this requires the
+ exchange to know (or learn from the customer) the exact amount to be paid
+ for the contract.
+
+ %A complication in practice is that merchants may not want to reveal their
+ %full bank account information to the customer, but this information is
+ %necessary for the exchange to process the deposit, since we do not require
+ %merchants to register beforehand the exchange (as the merchant might
+ %support all exchanges audited by a specific auditor). We discuss a protocol
+ %extension that allows customers to deposit coins on behalf of merchants
+ %in~\ref{XXX}.
+
+ \item The fees incurred for operations, the protocols for backup and
+ synchronization as well as other possible extensions like tick payments are
+ not formally modeled.
+
+ %\item FIXME: auditor
+\end{itemize}
+
+We note that customer tipping (see \ref{taler:design:tipping}) basically amounts to an execution
+of the \algo{Withdraw} protocol where the party that generates the coin keys
+and blinding factors (in that case the merchant's customer) is different from
+the party that signs the withdraw request (the merchant with a ``customer'' key
+pair tied to the merchant's bank account). While this is desirable in some
+cases, we discussed in \ref{taler:design:fixing-withdraw-loophole} how this ``loophole'' for a one-hop untaxed
+payment could be avoided.
+
+\subsection{Other Properties}
+
+\subsubsection{Exculpability}
+Exculpability is a property of offline e-cash which guarantees that honest users
+cannot be falsely blamed for misbehavior such as double spending. For online
+e-cash it is not necessary, since coins are spent online with the exchange. In
+practice, even offline e-cash systems that provide exculpability are often
+undesirable, since hardware failures can result in unintentional overspending
+by honest users. If a device crashes after an offline coin has been sent to
+the merchant but before the write operation has been permanently recorded on
+the user's device (e.g., because it was not yet flushed from the cache to a
+hard drive), the next payment will cause a double spend, resulting in anonymity
+loss and a penalty for the customer.
+
+% FIXME: move this to design or implementation
+\subsubsection{Fair Exchange}\label{sec:security:atomic-swaps}
+
+% FIXME: we should mention "atomic swap" here
+
+The Endorsed E-Cash system by Camenisch et al. \cite{camenisch2007endorsed}
+allows for fair exchange---sometimes called atomic swap in the context of
+cryptocurrencies---of online or offline e-cash against digital goods. The
+online version of Camenisch's protocol does not protect the customer against
+loss of anonymity from linkability of aborted fair exchanges.
+
+Taler's refresh protocol can be used for fair exchange of online e-cash against
+digital goods, without any loss of anonymity due to linkability of aborted
+transactions, with the following small extension: The customer asks the
+exchange to \emph{lock coins to a merchant} until a timeout. Until the timeout
+occurs, the exchange provides the merchant with a guarantee that these coins can
+only be spent with this specific merchant, or not at all. The
+fair exchange exchanges the merchant's digital goods against the customer's
+deposit permissions for the locked coins. On aborted fair exchanges,
+the customer refreshes to obtain unlinkable coins.
+
diff --git a/doc/system/taler/snippet-keys.txt b/doc/system/taler/snippet-keys.txt
new file mode 100644
index 000000000..b05c79524
--- /dev/null
+++ b/doc/system/taler/snippet-keys.txt
@@ -0,0 +1,62 @@
+{
+ "version": "2:0:0",
+ "master_public_key": "CQQZ...",
+ "reserve_closing_delay": "/Delay(2419200)/",
+ "signkeys": [
+ {
+ "stamp_start": "/Date(1522223035)/",
+ "stamp_expire": "/Date(1533109435)/",
+ "stamp_end": "/Date(1585295035)/",
+ "master_sig": "842D...",
+ "key": "05XW..."
+ }
+ ],
+ "payback": [],
+ "denoms": [
+ {
+ "master_sig": "BHG5...",
+ "stamp_start": "/Date(1500450235)/",
+ "stamp_expire_withdraw": "/Date(1595058235)/",
+ "stamp_expire_deposit": "/Date(1658130235)/",
+ "stamp_expire_legal": "/Date(1815810235)/",
+ "denom_pub": "51RD...",
+ "value": "TESTKUDOS:10",
+ "fee_withdraw": "TESTKUDOS:0.01",
+ "fee_deposit": "TESTKUDOS:0.01",
+ "fee_refresh": "TESTKUDOS:0.01",
+ "fee_refund": "TESTKUDOS:0.01"
+ },
+ {
+ "master_sig": "QT0T...",
+ "stamp_start": "/Date(1500450235)/",
+ "stamp_expire_withdraw": "/Date(1595058235)/",
+ "stamp_expire_deposit": "/Date(1658130235)/",
+ "stamp_expire_legal": "/Date(1815810235)/",
+ "denom_pub": "51R7",
+ "value": "TESTKUDOS:0.1",
+ "fee_withdraw": "TESTKUDOS:0.01",
+ "fee_deposit": "TESTKUDOS:0.01",
+ "fee_refresh": "TESTKUDOS:0.01",
+ "fee_refund": "TESTKUDOS:0.01"
+ },
+ ],
+ "auditors": [
+ {
+ "denomination_keys": [
+ {
+ "denom_pub_h": "RNTQ...",
+ "auditor_sig": "6SC2..."
+ },
+ {
+ "denom_pub_h": "CP6B...",
+ "auditor_sig": "0GSE..."
+ }
+ ],
+ "auditor_url": "https://auditor.test.taler.net/",
+ "auditor_pub": "BW9DC..."
+ }
+ ],
+ "list_issue_date": "/Date(1530196508)/",
+ "eddsa_pub": "05XW...",
+ "eddsa_sig": "RXCD..."
+}
diff --git a/m4/.gitignore b/m4/.gitignore
new file mode 100644
index 000000000..1f12f43df
--- /dev/null
+++ b/m4/.gitignore
@@ -0,0 +1,6 @@
+# These are added by "autoreconf -if"
+libtool.m4
+ltoptions.m4
+ltsugar.m4
+ltversion.m4
+lt~obsolete.m4
diff --git a/m4/ax_compare_version.m4 b/m4/ax_compare_version.m4
new file mode 100644
index 000000000..ffb4997e8
--- /dev/null
+++ b/m4/ax_compare_version.m4
@@ -0,0 +1,177 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_compare_version.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE])
+#
+# DESCRIPTION
+#
+# This macro compares two version strings. Due to the various number of
+# minor-version numbers that can exist, and the fact that string
+# comparisons are not compatible with numeric comparisons, this is not
+# necessarily trivial to do in a autoconf script. This macro makes doing
+# these comparisons easy.
+#
+# The six basic comparisons are available, as well as checking equality
+# limited to a certain number of minor-version levels.
+#
+# The operator OP determines what type of comparison to do, and can be one
+# of:
+#
+# eq - equal (test A == B)
+# ne - not equal (test A != B)
+# le - less than or equal (test A <= B)
+# ge - greater than or equal (test A >= B)
+# lt - less than (test A < B)
+# gt - greater than (test A > B)
+#
+# Additionally, the eq and ne operator can have a number after it to limit
+# the test to that number of minor versions.
+#
+# eq0 - equal up to the length of the shorter version
+# ne0 - not equal up to the length of the shorter version
+# eqN - equal up to N sub-version levels
+# neN - not equal up to N sub-version levels
+#
+# When the condition is true, shell commands ACTION-IF-TRUE are run,
+# otherwise shell commands ACTION-IF-FALSE are run. The environment
+# variable 'ax_compare_version' is always set to either 'true' or 'false'
+# as well.
+#
+# Examples:
+#
+# AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8])
+# AX_COMPARE_VERSION([3.15],[lt],[3.15.8])
+#
+# would both be true.
+#
+# AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8])
+# AX_COMPARE_VERSION([3.15],[gt],[3.15.8])
+#
+# would both be false.
+#
+# AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8])
+#
+# would be true because it is only comparing two minor versions.
+#
+# AX_COMPARE_VERSION([3.15.7],[eq0],[3.15])
+#
+# would be true because it is only comparing the lesser number of minor
+# versions of the two values.
+#
+# Note: The characters that separate the version numbers do not matter. An
+# empty string is the same as version 0. OP is evaluated by autoconf, not
+# configure, so must be a string, not a variable.
+#
+# The author would like to acknowledge Guido Draheim whose advice about
+# the m4_case and m4_ifvaln functions make this macro only include the
+# portions necessary to perform the specific comparison specified by the
+# OP argument in the final configure script.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Tim Toolan <toolan@ele.uri.edu>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 13
+
+dnl #########################################################################
+AC_DEFUN([AX_COMPARE_VERSION], [
+ AC_REQUIRE([AC_PROG_AWK])
+
+ # Used to indicate true or false condition
+ ax_compare_version=false
+
+ # Convert the two version strings to be compared into a format that
+ # allows a simple string comparison. The end result is that a version
+ # string of the form 1.12.5-r617 will be converted to the form
+ # 0001001200050617. In other words, each number is zero padded to four
+ # digits, and non digits are removed.
+ AS_VAR_PUSHDEF([A],[ax_compare_version_A])
+ A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \
+ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \
+ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+ -e 's/[[^0-9]]//g'`
+
+ AS_VAR_PUSHDEF([B],[ax_compare_version_B])
+ B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \
+ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \
+ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+ -e 's/[[^0-9]]//g'`
+
+ dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary
+ dnl # then the first line is used to determine if the condition is true.
+ dnl # The sed right after the echo is to remove any indented white space.
+ m4_case(m4_tolower($2),
+ [lt],[
+ ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"`
+ ],
+ [gt],[
+ ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"`
+ ],
+ [le],[
+ ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"`
+ ],
+ [ge],[
+ ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"`
+ ],[
+ dnl Split the operator from the subversion count if present.
+ m4_bmatch(m4_substr($2,2),
+ [0],[
+ # A count of zero means use the length of the shorter version.
+ # Determine the number of characters in A and B.
+ ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'`
+ ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'`
+
+ # Set A to no more than B's length and B to no more than A's length.
+ A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"`
+ B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"`
+ ],
+ [[0-9]+],[
+ # A count greater than zero means use only that many subversions
+ A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"`
+ B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"`
+ ],
+ [.+],[
+ AC_WARNING(
+ [invalid OP numeric parameter: $2])
+ ],[])
+
+ # Pad zeros at end of numbers to make same length.
+ ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`"
+ B="$B`echo $A | sed 's/./0/g'`"
+ A="$ax_compare_version_tmp_A"
+
+ # Check for equality or inequality as necessary.
+ m4_case(m4_tolower(m4_substr($2,0,2)),
+ [eq],[
+ test "x$A" = "x$B" && ax_compare_version=true
+ ],
+ [ne],[
+ test "x$A" != "x$B" && ax_compare_version=true
+ ],[
+ AC_WARNING([invalid OP parameter: $2])
+ ])
+ ])
+
+ AS_VAR_POPDEF([A])dnl
+ AS_VAR_POPDEF([B])dnl
+
+ dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE.
+ if test "$ax_compare_version" = "true" ; then
+ m4_ifvaln([$4],[$4],[:])dnl
+ m4_ifvaln([$5],[else $5])dnl
+ fi
+]) dnl AX_COMPARE_VERSION
diff --git a/m4/ax_lib_postgresql.m4 b/m4/ax_lib_postgresql.m4
index 11b6991f0..cc8e75086 100644
--- a/m4/ax_lib_postgresql.m4
+++ b/m4/ax_lib_postgresql.m4
@@ -1,10 +1,10 @@
# ===========================================================================
-# http://www.gnu.org/software/autoconf-archive/ax_lib_postgresql.html
+# https://www.gnu.org/software/autoconf-archive/ax_lib_postgresql.html
# ===========================================================================
#
# SYNOPSIS
#
-# AX_LIB_POSTGRESQL([MINIMUM-VERSION])
+# AX_LIB_POSTGRESQL([MINIMUM-VERSION],[ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND])
#
# DESCRIPTION
#
@@ -23,133 +23,225 @@
# should be in the PATH)
#
# path - complete path to pg_config utility, use this option if pg_config
-# can't be found in the PATH
+# can't be found in the PATH (You could set also PG_CONFIG variable)
#
# This macro calls:
#
# AC_SUBST(POSTGRESQL_CPPFLAGS)
# AC_SUBST(POSTGRESQL_LDFLAGS)
+# AC_SUBST(POSTGRESQL_LIBS)
# AC_SUBST(POSTGRESQL_VERSION)
#
# And sets:
#
# HAVE_POSTGRESQL
#
+# It execute if found ACTION-IF-FOUND (empty by default) and
+# ACTION-IF-NOT-FOUND (AC_MSG_FAILURE by default) if not found.
+#
# LICENSE
#
# Copyright (c) 2008 Mateusz Loskot <mateusz@loskot.net>
+# Copyright (c) 2014 Sree Harsha Totakura <sreeharsha@totakura.in>
+# Copyright (c) 2018 Bastien Roucaries <rouca@debian.org>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
-#serial 9
+#serial 22
+
+AC_DEFUN([_AX_LIB_POSTGRESQL_OLD],[
+ found_postgresql="no"
+ _AX_LIB_POSTGRESQL_OLD_fail="no"
+ while true; do
+ AC_CACHE_CHECK([for the pg_config program], [ac_cv_path_PG_CONFIG],
+ [AC_PATH_PROGS_FEATURE_CHECK([PG_CONFIG], [pg_config],
+ [[ac_cv_path_PG_CONFIG="";$ac_path_PG_CONFIG --includedir > /dev/null \
+ && ac_cv_path_PG_CONFIG=$ac_path_PG_CONFIG ac_path_PG_CONFIG_found=:]],
+ [ac_cv_path_PG_CONFIG=""])])
+ PG_CONFIG=$ac_cv_path_PG_CONFIG
+ AS_IF([test "X$PG_CONFIG" = "X"],[break])
+
+ AC_CACHE_CHECK([for the PostgreSQL libraries CPPFLAGS],[ac_cv_POSTGRESQL_CPPFLAGS],
+ [ac_cv_POSTGRESQL_CPPFLAGS="-I`$PG_CONFIG --includedir`" || _AX_LIB_POSTGRESQL_OLD_fail=yes])
+ AS_IF([test "X$_AX_LIB_POSTGRESQL_OLD_fail" = "Xyes"],[break])
+ POSTGRESQL_CPPFLAGS="$ac_cv_POSTGRESQL_CPPFLAGS"
+
+ AC_CACHE_CHECK([for the PostgreSQL libraries LDFLAGS],[ac_cv_POSTGRESQL_LDFLAGS],
+ [ac_cv_POSTGRESQL_LDFLAGS="-L`$PG_CONFIG --libdir`" || _AX_LIB_POSTGRESQL_OLD_fail=yes])
+ AS_IF([test "X$_AX_LIB_POSTGRESQL_OLD_fail" = "Xyes"],[break])
+ POSTGRESQL_LDFLAGS="$ac_cv_POSTGRESQL_LDFLAGS"
+
+ AC_CACHE_CHECK([for the PostgreSQL libraries LIBS],[ac_cv_POSTGRESQL_LIBS],
+ [ac_cv_POSTGRESQL_LIBS="-lpq"])
+ POSTGRESQL_LIBS="$ac_cv_POSTGRESQL_LIBS"
+
+ AC_CACHE_CHECK([for the PostgreSQL version],[ac_cv_POSTGRESQL_VERSION],
+ [
+ ac_cv_POSTGRESQL_VERSION=`$PG_CONFIG --version | sed "s/^PostgreSQL[[[:space:]]][[[:space:]]]*\([[0-9.]][[0-9.]]*\).*/\1/"` \
+ || _AX_LIB_POSTGRESQL_OLD_fail=yes
+ ])
+ AS_IF([test "X$_AX_LIB_POSTGRESQL_OLD_fail" = "Xyes"],[break])
+ POSTGRESQL_VERSION="$ac_cv_POSTGRESQL_VERSION"
+
+
+ dnl
+ dnl Check if required version of PostgreSQL is available
+ dnl
+ AS_IF([test X"$postgresql_version_req" != "X"],[
+ AC_MSG_CHECKING([if PostgreSQL version $POSTGRESQL_VERSION is >= $postgresql_version_req])
+ AX_COMPARE_VERSION([$POSTGRESQL_VERSION],[ge],[$postgresql_version_req],
+ [found_postgresql_req_version=yes],[found_postgresql_req_version=no])
+ AC_MSG_RESULT([$found_postgresql_req_version])
+ ])
+ AS_IF([test "Xfound_postgresql_req_version" = "Xno"],[break])
+
+ found_postgresql="yes"
+ break
+ done
+])
-AC_DEFUN([AX_LIB_POSTGRESQL],
+AC_DEFUN([_AX_LIB_POSTGRESQL_PKG_CONFIG],
[
- AC_ARG_WITH([postgresql],
- AS_HELP_STRING([--with-postgresql=@<:@ARG@:>@],
- [use PostgreSQL library @<:@default=yes@:>@, optionally specify path to pg_config]
- ),
- [
- if test "$withval" = "no"; then
- want_postgresql="no"
- elif test "$withval" = "yes"; then
- want_postgresql="yes"
- else
- want_postgresql="yes"
- PG_CONFIG="$withval"
- fi
- ],
- [want_postgresql="yes"]
- )
+ AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+ found_postgresql=no
- POSTGRESQL_CPPFLAGS=""
- POSTGRESQL_LDFLAGS=""
- POSTGRESQL_VERSION=""
+ while true; do
+ PKG_PROG_PKG_CONFIG
+ AS_IF([test X$PKG_CONFIG = X],[break])
- dnl
- dnl Check PostgreSQL libraries (libpq)
- dnl
+ _AX_LIB_POSTGRESQL_PKG_CONFIG_fail=no;
+ AS_IF([test "X$postgresql_version_req" = "X"],
+ [PKG_CHECK_EXISTS([libpq],[found_postgresql_pkg_config=yes],[found_postgresql=no])],
+ [PKG_CHECK_EXISTS([libpq >= "$postgresql_version_req"],
+ [found_postgresql=yes],[found_postgresql=no])])
+ AS_IF([test "X$found_postgresql" = "no"],[break])
+
+ AC_CACHE_CHECK([for the PostgreSQL libraries CPPFLAGS],[ac_cv_POSTGRESQL_CPPFLAGS],
+ [ac_cv_POSTGRESQL_CPPFLAGS="`$PKG_CONFIG libpq --cflags-only-I`" || _AX_LIB_POSTGRESQL_PKG_CONFIG_fail=yes])
+ AS_IF([test "X$_AX_LIB_POSTGRESQL_PKG_CONFIG_fail" = "Xyes"],[break])
+ POSTGRESQL_CPPFLAGS="$ac_cv_POSTGRESQL_CPPFLAGS"
- if test "$want_postgresql" = "yes"; then
- if test -z "$PG_CONFIG" -o test; then
- AC_PATH_PROG([PG_CONFIG], [pg_config], [])
- fi
+ AC_CACHE_CHECK([for the PostgreSQL libraries LDFLAGS],[ac_cv_POSTGRESQL_LDFLAGS],
+ [ac_cv_POSTGRESQL_LDFLAGS="`$PKG_CONFIG libpq --libs-only-L --libs-only-other`" || _AX_LIB_POSTGRESQL_PKG_CONFIG_fail=yes])
+ AS_IF([test "X$_AX_LIB_POSTGRESQL_PKG_CONFIG_fail" = "Xyes"],[break])
+ POSTGRESQL_LDFLAGS="$ac_cv_POSTGRESQL_LDFLAGS"
- if test ! -x "$PG_CONFIG"; then
- dnl AC_MSG_ERROR([$PG_CONFIG does not exist or it is not an exectuable file])
- PG_CONFIG="no"
- found_postgresql="no"
- fi
- if test "$PG_CONFIG" != "no"; then
- AC_MSG_CHECKING([for PostgreSQL libraries])
+ AC_CACHE_CHECK([for the PostgreSQL libraries LIBS],[ac_cv_POSTGRESQL_LIBS],
+ [ac_cv_POSTGRESQL_LIBS="`$PKG_CONFIG libpq --libs-only-l`" || _AX_LIB_POSTGRESQL_PKG_CONFIG_fail=ye])
+ AS_IF([test "X$_AX_LIB_POSTGRESQL_PKG_CONFIG_fail" = "Xyes"],[break])
+ POSTGRESQL_LIBS="$ac_cv_POSTGRESQL_LIBS"
- POSTGRESQL_CPPFLAGS="-I`$PG_CONFIG --includedir`"
- POSTGRESQL_LDFLAGS="-L`$PG_CONFIG --libdir`"
+ dnl already checked by exist but need to be recovered
+ AC_CACHE_CHECK([for the PostgreSQL version],[ac_cv_POSTGRESQL_VERSION],
+ [ac_cv_POSTGRESQL_VERSION="`$PKG_CONFIG libpq --modversion`" || _AX_LIB_POSTGRESQL_PKG_CONFIG_fail=yes])
+ AS_IF([test "X$_AX_LIB_POSTGRESQL_PKG_CONFIG_fail" = "Xyes"],[break])
+ POSTGRESQL_VERSION="$ac_cv_POSTGRESQL_VERSION"
- POSTGRESQL_VERSION=`$PG_CONFIG --version | sed -e 's#PostgreSQL ##' | awk '{print $1}'`
+ found_postgresql=yes
+ break;
+ done
- AC_DEFINE([HAVE_POSTGRESQL], [1],
- [Define to 1 if PostgreSQL libraries are available])
+])
- found_postgresql="yes"
- AC_MSG_RESULT([yes])
- else
- found_postgresql="no"
- AC_MSG_RESULT([no])
- fi
- fi
- dnl
- dnl Check if required version of PostgreSQL is available
- dnl
+AC_DEFUN([AX_LIB_POSTGRESQL],
+[
+ AC_ARG_WITH([postgresql],
+ AS_HELP_STRING([--with-postgresql=@<:@ARG@:>@],
+ [use PostgreSQL library @<:@default=yes@:>@, optionally specify path to pg_config]
+ ),
+ [
+ AS_CASE([$withval],
+ [[[nN]][[oO]]],[want_postgresql="no"],
+ [[[yY]][[eE]][[sS]]],[want_postgresql="yes"],
+ [
+ want_postgresql="yes"
+ PG_CONFIG="$withval"
+ ])
+ ],
+ [want_postgresql="yes"]
+ )
+
+ AC_ARG_VAR([POSTGRESQL_CPPFLAGS],[cpp flags for PostgreSQL overriding detected flags])
+ AC_ARG_VAR([POSTGRESQL_LIBFLAGS],[libs for PostgreSQL overriding detected flags])
+ AC_ARG_VAR([POSTGRESQL_LDFLAGS],[linker flags for PostgreSQL overriding detected flags])
+
+ # populate cache
+ AS_IF([test "X$POSTGRESQL_CPPFLAGS" != X],[ac_cv_POSTGRESQL_CPPFLAGS="$POSTGRESQL_CPPFLAGS"])
+ AS_IF([test "X$POSTGRESQL_LDFLAGS" != X],[ac_cv_POSTGRESQL_LDFLAGS="$POSTGRESQL_LDFLAGS"])
+ AS_IF([test "X$POSTGRESQL_LIBS" != X],[ac_cv_POSTGRESQL_LIBS="$POSTGRESQL_LIBS"])
postgresql_version_req=ifelse([$1], [], [], [$1])
+ found_postgresql="no"
- if test "$found_postgresql" = "yes" -a -n "$postgresql_version_req"; then
-
- AC_MSG_CHECKING([if PostgreSQL version $POSTGRESQL_VERSION is >= $postgresql_version_req])
-
- dnl Decompose required version string of PostgreSQL
- dnl and calculate its number representation
- postgresql_version_req_major=`expr $postgresql_version_req : '\([[0-9]]*\)'`
- postgresql_version_req_minor=`expr $postgresql_version_req : '[[0-9]]*\.\([[0-9]]*\)'`
- postgresql_version_req_micro=`expr $postgresql_version_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'`
- if test "x$postgresql_version_req_micro" = "x"; then
- postgresql_version_req_micro="0"
- fi
-
- postgresql_version_req_number=`expr $postgresql_version_req_major \* 1000000 \
- \+ $postgresql_version_req_minor \* 1000 \
- \+ $postgresql_version_req_micro`
-
- dnl Decompose version string of installed PostgreSQL
- dnl and calculate its number representation
- postgresql_version_major=`expr $POSTGRESQL_VERSION : '\([[0-9]]*\)'`
- postgresql_version_minor=`expr $POSTGRESQL_VERSION : '[[0-9]]*\.\([[0-9]]*\)'`
- postgresql_version_micro=`expr $POSTGRESQL_VERSION : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'`
- if test "x$postgresql_version_micro" = "x"; then
- postgresql_version_micro="0"
- fi
-
- postgresql_version_number=`expr $postgresql_version_major \* 1000000 \
- \+ $postgresql_version_minor \* 1000 \
- \+ $postgresql_version_micro`
-
- postgresql_version_check=`expr $postgresql_version_number \>\= $postgresql_version_req_number`
- if test "$postgresql_version_check" = "1"; then
- AC_MSG_RESULT([yes])
- else
- AC_MSG_RESULT([no])
- fi
- fi
+ POSTGRESQL_VERSION=""
+
+ dnl
+ dnl Check PostgreSQL libraries (libpq)
+ dnl
+ AS_IF([test X"$want_postgresql" = "Xyes"],[
+ _AX_LIB_POSTGRESQL_PKG_CONFIG
+
+
+ AS_IF([test X"$found_postgresql" = "Xno"],
+ [_AX_LIB_POSTGRESQL_OLD])
+
+ AS_IF([test X"$found_postgresql" = Xyes],[
+ _AX_LIB_POSTGRESQL_OLD_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $POSTGRESQL_CPPFLAGS"
+ _AX_LIB_POSTGRESQL_OLD_LDFLAGS="$LDFLAGS"
+ LDFLAGS="$LDFLAGS $POSTGRESQL_LDFLAGS"
+ _AX_LIB_POSTGRESQL_OLD_LIBS="$LIBS"
+ LIBS="$LIBS $POSTGRESQL_LIBS"
+ while true; do
+ dnl try to compile
+ AC_CHECK_HEADER([libpq-fe.h],[],[found_postgresql=no])
+ AS_IF([test "X$found_postgresql" = "Xno"],[break])
+ dnl try now to link
+ AC_CACHE_CHECK([for the PostgreSQL library linking is working],[ac_cv_postgresql_found],
+ [
+ AC_LINK_IFELSE([
+ AC_LANG_PROGRAM(
+ [
+ #include <libpq-fe.h>
+ ],
+ [[
+ char conninfo[]="dbname = postgres";
+ PGconn *conn;
+ conn = PQconnectdb(conninfo);
+ ]]
+ )
+ ],[ac_cv_postgresql_found=yes],
+ [ac_cv_postgresql_found=no])
+ ])
+ found_postgresql="$ac_cv_postgresql_found"
+ AS_IF([test "X$found_postgresql" = "Xno"],[break])
+ break
+ done
+ CPPFLAGS="$_AX_LIB_POSTGRESQL_OLD_CPPFLAGS"
+ LDFLAGS="$_AX_LIB_POSTGRESQL_OLD_LDFLAGS"
+ LIBS="$_AX_LIB_POSTGRESQL_OLD_LIBS"
+ ])
+
+
+ AS_IF([test "x$found_postgresql" = "xyes"],[
+ AC_DEFINE([HAVE_POSTGRESQL], [1],
+ [Define to 1 if PostgreSQL libraries are available])])
+ ])
AC_SUBST([POSTGRESQL_VERSION])
AC_SUBST([POSTGRESQL_CPPFLAGS])
AC_SUBST([POSTGRESQL_LDFLAGS])
+ AC_SUBST([POSTGRESQL_LIBS])
+
+ AS_IF([test "x$found_postgresql" = "xyes"],
+ [ifelse([$2], , :, [$2])],
+ [ifelse([$3], , AS_IF([test X"$want_postgresql" = "Xyes"],[AC_MSG_ERROR([Library requirements (PostgreSQL) not met.])],[:]), [$3])])
+
])
diff --git a/m4/gettext.m4 b/m4/gettext.m4
new file mode 100644
index 000000000..eef5073b3
--- /dev/null
+++ b/m4/gettext.m4
@@ -0,0 +1,420 @@
+# gettext.m4 serial 68 (gettext-0.19.8)
+dnl Copyright (C) 1995-2014, 2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+dnl Authors:
+dnl Ulrich Drepper <drepper@cygnus.com>, 1995-2000.
+dnl Bruno Haible <haible@clisp.cons.org>, 2000-2006, 2008-2010.
+
+dnl Macro to add for using GNU gettext.
+
+dnl Usage: AM_GNU_GETTEXT([INTLSYMBOL], [NEEDSYMBOL], [INTLDIR]).
+dnl INTLSYMBOL can be one of 'external', 'no-libtool', 'use-libtool'. The
+dnl default (if it is not specified or empty) is 'no-libtool'.
+dnl INTLSYMBOL should be 'external' for packages with no intl directory,
+dnl and 'no-libtool' or 'use-libtool' for packages with an intl directory.
+dnl If INTLSYMBOL is 'use-libtool', then a libtool library
+dnl $(top_builddir)/intl/libintl.la will be created (shared and/or static,
+dnl depending on --{enable,disable}-{shared,static} and on the presence of
+dnl AM-DISABLE-SHARED). If INTLSYMBOL is 'no-libtool', a static library
+dnl $(top_builddir)/intl/libintl.a will be created.
+dnl If NEEDSYMBOL is specified and is 'need-ngettext', then GNU gettext
+dnl implementations (in libc or libintl) without the ngettext() function
+dnl will be ignored. If NEEDSYMBOL is specified and is
+dnl 'need-formatstring-macros', then GNU gettext implementations that don't
+dnl support the ISO C 99 <inttypes.h> formatstring macros will be ignored.
+dnl INTLDIR is used to find the intl libraries. If empty,
+dnl the value '$(top_builddir)/intl/' is used.
+dnl
+dnl The result of the configuration is one of three cases:
+dnl 1) GNU gettext, as included in the intl subdirectory, will be compiled
+dnl and used.
+dnl Catalog format: GNU --> install in $(datadir)
+dnl Catalog extension: .mo after installation, .gmo in source tree
+dnl 2) GNU gettext has been found in the system's C library.
+dnl Catalog format: GNU --> install in $(datadir)
+dnl Catalog extension: .mo after installation, .gmo in source tree
+dnl 3) No internationalization, always use English msgid.
+dnl Catalog format: none
+dnl Catalog extension: none
+dnl If INTLSYMBOL is 'external', only cases 2 and 3 can occur.
+dnl The use of .gmo is historical (it was needed to avoid overwriting the
+dnl GNU format catalogs when building on a platform with an X/Open gettext),
+dnl but we keep it in order not to force irrelevant filename changes on the
+dnl maintainers.
+dnl
+AC_DEFUN([AM_GNU_GETTEXT],
+[
+ dnl Argument checking.
+ ifelse([$1], [], , [ifelse([$1], [external], , [ifelse([$1], [no-libtool], , [ifelse([$1], [use-libtool], ,
+ [errprint([ERROR: invalid first argument to AM_GNU_GETTEXT
+])])])])])
+ ifelse(ifelse([$1], [], [old])[]ifelse([$1], [no-libtool], [old]), [old],
+ [AC_DIAGNOSE([obsolete], [Use of AM_GNU_GETTEXT without [external] argument is deprecated.])])
+ ifelse([$2], [], , [ifelse([$2], [need-ngettext], , [ifelse([$2], [need-formatstring-macros], ,
+ [errprint([ERROR: invalid second argument to AM_GNU_GETTEXT
+])])])])
+ define([gt_included_intl],
+ ifelse([$1], [external],
+ ifdef([AM_GNU_GETTEXT_][INTL_SUBDIR], [yes], [no]),
+ [yes]))
+ define([gt_libtool_suffix_prefix], ifelse([$1], [use-libtool], [l], []))
+ gt_NEEDS_INIT
+ AM_GNU_GETTEXT_NEED([$2])
+
+ AC_REQUIRE([AM_PO_SUBDIRS])dnl
+ ifelse(gt_included_intl, yes, [
+ AC_REQUIRE([AM_INTL_SUBDIR])dnl
+ ])
+
+ dnl Prerequisites of AC_LIB_LINKFLAGS_BODY.
+ AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+ AC_REQUIRE([AC_LIB_RPATH])
+
+ dnl Sometimes libintl requires libiconv, so first search for libiconv.
+ dnl Ideally we would do this search only after the
+ dnl if test "$USE_NLS" = "yes"; then
+ dnl if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" != "yes"; }; then
+ dnl tests. But if configure.in invokes AM_ICONV after AM_GNU_GETTEXT
+ dnl the configure script would need to contain the same shell code
+ dnl again, outside any 'if'. There are two solutions:
+ dnl - Invoke AM_ICONV_LINKFLAGS_BODY here, outside any 'if'.
+ dnl - Control the expansions in more detail using AC_PROVIDE_IFELSE.
+ dnl Since AC_PROVIDE_IFELSE is only in autoconf >= 2.52 and not
+ dnl documented, we avoid it.
+ ifelse(gt_included_intl, yes, , [
+ AC_REQUIRE([AM_ICONV_LINKFLAGS_BODY])
+ ])
+
+ dnl Sometimes, on Mac OS X, libintl requires linking with CoreFoundation.
+ gt_INTL_MACOSX
+
+ dnl Set USE_NLS.
+ AC_REQUIRE([AM_NLS])
+
+ ifelse(gt_included_intl, yes, [
+ BUILD_INCLUDED_LIBINTL=no
+ USE_INCLUDED_LIBINTL=no
+ ])
+ LIBINTL=
+ LTLIBINTL=
+ POSUB=
+
+ dnl Add a version number to the cache macros.
+ case " $gt_needs " in
+ *" need-formatstring-macros "*) gt_api_version=3 ;;
+ *" need-ngettext "*) gt_api_version=2 ;;
+ *) gt_api_version=1 ;;
+ esac
+ gt_func_gnugettext_libc="gt_cv_func_gnugettext${gt_api_version}_libc"
+ gt_func_gnugettext_libintl="gt_cv_func_gnugettext${gt_api_version}_libintl"
+
+ dnl If we use NLS figure out what method
+ if test "$USE_NLS" = "yes"; then
+ gt_use_preinstalled_gnugettext=no
+ ifelse(gt_included_intl, yes, [
+ AC_MSG_CHECKING([whether included gettext is requested])
+ AC_ARG_WITH([included-gettext],
+ [ --with-included-gettext use the GNU gettext library included here],
+ nls_cv_force_use_gnu_gettext=$withval,
+ nls_cv_force_use_gnu_gettext=no)
+ AC_MSG_RESULT([$nls_cv_force_use_gnu_gettext])
+
+ nls_cv_use_gnu_gettext="$nls_cv_force_use_gnu_gettext"
+ if test "$nls_cv_force_use_gnu_gettext" != "yes"; then
+ ])
+ dnl User does not insist on using GNU NLS library. Figure out what
+ dnl to use. If GNU gettext is available we use this. Else we have
+ dnl to fall back to GNU NLS library.
+
+ if test $gt_api_version -ge 3; then
+ gt_revision_test_code='
+#ifndef __GNU_GETTEXT_SUPPORTED_REVISION
+#define __GNU_GETTEXT_SUPPORTED_REVISION(major) ((major) == 0 ? 0 : -1)
+#endif
+changequote(,)dnl
+typedef int array [2 * (__GNU_GETTEXT_SUPPORTED_REVISION(0) >= 1) - 1];
+changequote([,])dnl
+'
+ else
+ gt_revision_test_code=
+ fi
+ if test $gt_api_version -ge 2; then
+ gt_expression_test_code=' + * ngettext ("", "", 0)'
+ else
+ gt_expression_test_code=
+ fi
+
+ AC_CACHE_CHECK([for GNU gettext in libc], [$gt_func_gnugettext_libc],
+ [AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[
+#include <libintl.h>
+#ifndef __GNU_GETTEXT_SUPPORTED_REVISION
+extern int _nl_msg_cat_cntr;
+extern int *_nl_domain_bindings;
+#define __GNU_GETTEXT_SYMBOL_EXPRESSION (_nl_msg_cat_cntr + *_nl_domain_bindings)
+#else
+#define __GNU_GETTEXT_SYMBOL_EXPRESSION 0
+#endif
+$gt_revision_test_code
+ ]],
+ [[
+bindtextdomain ("", "");
+return * gettext ("")$gt_expression_test_code + __GNU_GETTEXT_SYMBOL_EXPRESSION
+ ]])],
+ [eval "$gt_func_gnugettext_libc=yes"],
+ [eval "$gt_func_gnugettext_libc=no"])])
+
+ if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" != "yes"; }; then
+ dnl Sometimes libintl requires libiconv, so first search for libiconv.
+ ifelse(gt_included_intl, yes, , [
+ AM_ICONV_LINK
+ ])
+ dnl Search for libintl and define LIBINTL, LTLIBINTL and INCINTL
+ dnl accordingly. Don't use AC_LIB_LINKFLAGS_BODY([intl],[iconv])
+ dnl because that would add "-liconv" to LIBINTL and LTLIBINTL
+ dnl even if libiconv doesn't exist.
+ AC_LIB_LINKFLAGS_BODY([intl])
+ AC_CACHE_CHECK([for GNU gettext in libintl],
+ [$gt_func_gnugettext_libintl],
+ [gt_save_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $INCINTL"
+ gt_save_LIBS="$LIBS"
+ LIBS="$LIBS $LIBINTL"
+ dnl Now see whether libintl exists and does not depend on libiconv.
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[
+#include <libintl.h>
+#ifndef __GNU_GETTEXT_SUPPORTED_REVISION
+extern int _nl_msg_cat_cntr;
+extern
+#ifdef __cplusplus
+"C"
+#endif
+const char *_nl_expand_alias (const char *);
+#define __GNU_GETTEXT_SYMBOL_EXPRESSION (_nl_msg_cat_cntr + *_nl_expand_alias (""))
+#else
+#define __GNU_GETTEXT_SYMBOL_EXPRESSION 0
+#endif
+$gt_revision_test_code
+ ]],
+ [[
+bindtextdomain ("", "");
+return * gettext ("")$gt_expression_test_code + __GNU_GETTEXT_SYMBOL_EXPRESSION
+ ]])],
+ [eval "$gt_func_gnugettext_libintl=yes"],
+ [eval "$gt_func_gnugettext_libintl=no"])
+ dnl Now see whether libintl exists and depends on libiconv.
+ if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" != yes; } && test -n "$LIBICONV"; then
+ LIBS="$LIBS $LIBICONV"
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[
+#include <libintl.h>
+#ifndef __GNU_GETTEXT_SUPPORTED_REVISION
+extern int _nl_msg_cat_cntr;
+extern
+#ifdef __cplusplus
+"C"
+#endif
+const char *_nl_expand_alias (const char *);
+#define __GNU_GETTEXT_SYMBOL_EXPRESSION (_nl_msg_cat_cntr + *_nl_expand_alias (""))
+#else
+#define __GNU_GETTEXT_SYMBOL_EXPRESSION 0
+#endif
+$gt_revision_test_code
+ ]],
+ [[
+bindtextdomain ("", "");
+return * gettext ("")$gt_expression_test_code + __GNU_GETTEXT_SYMBOL_EXPRESSION
+ ]])],
+ [LIBINTL="$LIBINTL $LIBICONV"
+ LTLIBINTL="$LTLIBINTL $LTLIBICONV"
+ eval "$gt_func_gnugettext_libintl=yes"
+ ])
+ fi
+ CPPFLAGS="$gt_save_CPPFLAGS"
+ LIBS="$gt_save_LIBS"])
+ fi
+
+ dnl If an already present or preinstalled GNU gettext() is found,
+ dnl use it. But if this macro is used in GNU gettext, and GNU
+ dnl gettext is already preinstalled in libintl, we update this
+ dnl libintl. (Cf. the install rule in intl/Makefile.in.)
+ if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" = "yes"; } \
+ || { { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = "yes"; } \
+ && test "$PACKAGE" != gettext-runtime \
+ && test "$PACKAGE" != gettext-tools; }; then
+ gt_use_preinstalled_gnugettext=yes
+ else
+ dnl Reset the values set by searching for libintl.
+ LIBINTL=
+ LTLIBINTL=
+ INCINTL=
+ fi
+
+ ifelse(gt_included_intl, yes, [
+ if test "$gt_use_preinstalled_gnugettext" != "yes"; then
+ dnl GNU gettext is not found in the C library.
+ dnl Fall back on included GNU gettext library.
+ nls_cv_use_gnu_gettext=yes
+ fi
+ fi
+
+ if test "$nls_cv_use_gnu_gettext" = "yes"; then
+ dnl Mark actions used to generate GNU NLS library.
+ BUILD_INCLUDED_LIBINTL=yes
+ USE_INCLUDED_LIBINTL=yes
+ LIBINTL="ifelse([$3],[],\${top_builddir}/intl,[$3])/libintl.[]gt_libtool_suffix_prefix[]a $LIBICONV $LIBTHREAD"
+ LTLIBINTL="ifelse([$3],[],\${top_builddir}/intl,[$3])/libintl.[]gt_libtool_suffix_prefix[]a $LTLIBICONV $LTLIBTHREAD"
+ LIBS=`echo " $LIBS " | sed -e 's/ -lintl / /' -e 's/^ //' -e 's/ $//'`
+ fi
+
+ CATOBJEXT=
+ if test "$gt_use_preinstalled_gnugettext" = "yes" \
+ || test "$nls_cv_use_gnu_gettext" = "yes"; then
+ dnl Mark actions to use GNU gettext tools.
+ CATOBJEXT=.gmo
+ fi
+ ])
+
+ if test -n "$INTL_MACOSX_LIBS"; then
+ if test "$gt_use_preinstalled_gnugettext" = "yes" \
+ || test "$nls_cv_use_gnu_gettext" = "yes"; then
+ dnl Some extra flags are needed during linking.
+ LIBINTL="$LIBINTL $INTL_MACOSX_LIBS"
+ LTLIBINTL="$LTLIBINTL $INTL_MACOSX_LIBS"
+ fi
+ fi
+
+ if test "$gt_use_preinstalled_gnugettext" = "yes" \
+ || test "$nls_cv_use_gnu_gettext" = "yes"; then
+ AC_DEFINE([ENABLE_NLS], [1],
+ [Define to 1 if translation of program messages to the user's native language
+ is requested.])
+ else
+ USE_NLS=no
+ fi
+ fi
+
+ AC_MSG_CHECKING([whether to use NLS])
+ AC_MSG_RESULT([$USE_NLS])
+ if test "$USE_NLS" = "yes"; then
+ AC_MSG_CHECKING([where the gettext function comes from])
+ if test "$gt_use_preinstalled_gnugettext" = "yes"; then
+ if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = "yes"; }; then
+ gt_source="external libintl"
+ else
+ gt_source="libc"
+ fi
+ else
+ gt_source="included intl directory"
+ fi
+ AC_MSG_RESULT([$gt_source])
+ fi
+
+ if test "$USE_NLS" = "yes"; then
+
+ if test "$gt_use_preinstalled_gnugettext" = "yes"; then
+ if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = "yes"; }; then
+ AC_MSG_CHECKING([how to link with libintl])
+ AC_MSG_RESULT([$LIBINTL])
+ AC_LIB_APPENDTOVAR([CPPFLAGS], [$INCINTL])
+ fi
+
+ dnl For backward compatibility. Some packages may be using this.
+ AC_DEFINE([HAVE_GETTEXT], [1],
+ [Define if the GNU gettext() function is already present or preinstalled.])
+ AC_DEFINE([HAVE_DCGETTEXT], [1],
+ [Define if the GNU dcgettext() function is already present or preinstalled.])
+ fi
+
+ dnl We need to process the po/ directory.
+ POSUB=po
+ fi
+
+ ifelse(gt_included_intl, yes, [
+ dnl If this is used in GNU gettext we have to set BUILD_INCLUDED_LIBINTL
+ dnl to 'yes' because some of the testsuite requires it.
+ if test "$PACKAGE" = gettext-runtime || test "$PACKAGE" = gettext-tools; then
+ BUILD_INCLUDED_LIBINTL=yes
+ fi
+
+ dnl Make all variables we use known to autoconf.
+ AC_SUBST([BUILD_INCLUDED_LIBINTL])
+ AC_SUBST([USE_INCLUDED_LIBINTL])
+ AC_SUBST([CATOBJEXT])
+
+ dnl For backward compatibility. Some configure.ins may be using this.
+ nls_cv_header_intl=
+ nls_cv_header_libgt=
+
+ dnl For backward compatibility. Some Makefiles may be using this.
+ DATADIRNAME=share
+ AC_SUBST([DATADIRNAME])
+
+ dnl For backward compatibility. Some Makefiles may be using this.
+ INSTOBJEXT=.mo
+ AC_SUBST([INSTOBJEXT])
+
+ dnl For backward compatibility. Some Makefiles may be using this.
+ GENCAT=gencat
+ AC_SUBST([GENCAT])
+
+ dnl For backward compatibility. Some Makefiles may be using this.
+ INTLOBJS=
+ if test "$USE_INCLUDED_LIBINTL" = yes; then
+ INTLOBJS="\$(GETTOBJS)"
+ fi
+ AC_SUBST([INTLOBJS])
+
+ dnl Enable libtool support if the surrounding package wishes it.
+ INTL_LIBTOOL_SUFFIX_PREFIX=gt_libtool_suffix_prefix
+ AC_SUBST([INTL_LIBTOOL_SUFFIX_PREFIX])
+ ])
+
+ dnl For backward compatibility. Some Makefiles may be using this.
+ INTLLIBS="$LIBINTL"
+ AC_SUBST([INTLLIBS])
+
+ dnl Make all documented variables known to autoconf.
+ AC_SUBST([LIBINTL])
+ AC_SUBST([LTLIBINTL])
+ AC_SUBST([POSUB])
+])
+
+
+dnl gt_NEEDS_INIT ensures that the gt_needs variable is initialized.
+m4_define([gt_NEEDS_INIT],
+[
+ m4_divert_text([DEFAULTS], [gt_needs=])
+ m4_define([gt_NEEDS_INIT], [])
+])
+
+
+dnl Usage: AM_GNU_GETTEXT_NEED([NEEDSYMBOL])
+AC_DEFUN([AM_GNU_GETTEXT_NEED],
+[
+ m4_divert_text([INIT_PREPARE], [gt_needs="$gt_needs $1"])
+])
+
+
+dnl Usage: AM_GNU_GETTEXT_VERSION([gettext-version])
+AC_DEFUN([AM_GNU_GETTEXT_VERSION], [])
+
+
+dnl Usage: AM_GNU_GETTEXT_REQUIRE_VERSION([gettext-version])
+AC_DEFUN([AM_GNU_GETTEXT_REQUIRE_VERSION], [])
diff --git a/m4/iconv.m4 b/m4/iconv.m4
new file mode 100644
index 000000000..aa159c539
--- /dev/null
+++ b/m4/iconv.m4
@@ -0,0 +1,271 @@
+# iconv.m4 serial 19 (gettext-0.18.2)
+dnl Copyright (C) 2000-2002, 2007-2014, 2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+AC_DEFUN([AM_ICONV_LINKFLAGS_BODY],
+[
+ dnl Prerequisites of AC_LIB_LINKFLAGS_BODY.
+ AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+ AC_REQUIRE([AC_LIB_RPATH])
+
+ dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV
+ dnl accordingly.
+ AC_LIB_LINKFLAGS_BODY([iconv])
+])
+
+AC_DEFUN([AM_ICONV_LINK],
+[
+ dnl Some systems have iconv in libc, some have it in libiconv (OSF/1 and
+ dnl those with the standalone portable GNU libiconv installed).
+ AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+
+ dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV
+ dnl accordingly.
+ AC_REQUIRE([AM_ICONV_LINKFLAGS_BODY])
+
+ dnl Add $INCICONV to CPPFLAGS before performing the following checks,
+ dnl because if the user has installed libiconv and not disabled its use
+ dnl via --without-libiconv-prefix, he wants to use it. The first
+ dnl AC_LINK_IFELSE will then fail, the second AC_LINK_IFELSE will succeed.
+ am_save_CPPFLAGS="$CPPFLAGS"
+ AC_LIB_APPENDTOVAR([CPPFLAGS], [$INCICONV])
+
+ AC_CACHE_CHECK([for iconv], [am_cv_func_iconv], [
+ am_cv_func_iconv="no, consider installing GNU libiconv"
+ am_cv_lib_iconv=no
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[
+#include <stdlib.h>
+#include <iconv.h>
+ ]],
+ [[iconv_t cd = iconv_open("","");
+ iconv(cd,NULL,NULL,NULL,NULL);
+ iconv_close(cd);]])],
+ [am_cv_func_iconv=yes])
+ if test "$am_cv_func_iconv" != yes; then
+ am_save_LIBS="$LIBS"
+ LIBS="$LIBS $LIBICONV"
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[
+#include <stdlib.h>
+#include <iconv.h>
+ ]],
+ [[iconv_t cd = iconv_open("","");
+ iconv(cd,NULL,NULL,NULL,NULL);
+ iconv_close(cd);]])],
+ [am_cv_lib_iconv=yes]
+ [am_cv_func_iconv=yes])
+ LIBS="$am_save_LIBS"
+ fi
+ ])
+ if test "$am_cv_func_iconv" = yes; then
+ AC_CACHE_CHECK([for working iconv], [am_cv_func_iconv_works], [
+ dnl This tests against bugs in AIX 5.1, AIX 6.1..7.1, HP-UX 11.11,
+ dnl Solaris 10.
+ am_save_LIBS="$LIBS"
+ if test $am_cv_lib_iconv = yes; then
+ LIBS="$LIBS $LIBICONV"
+ fi
+ am_cv_func_iconv_works=no
+ for ac_iconv_const in '' 'const'; do
+ AC_RUN_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[
+#include <iconv.h>
+#include <string.h>
+
+#ifndef ICONV_CONST
+# define ICONV_CONST $ac_iconv_const
+#endif
+ ]],
+ [[int result = 0;
+ /* Test against AIX 5.1 bug: Failures are not distinguishable from successful
+ returns. */
+ {
+ iconv_t cd_utf8_to_88591 = iconv_open ("ISO8859-1", "UTF-8");
+ if (cd_utf8_to_88591 != (iconv_t)(-1))
+ {
+ static ICONV_CONST char input[] = "\342\202\254"; /* EURO SIGN */
+ char buf[10];
+ ICONV_CONST char *inptr = input;
+ size_t inbytesleft = strlen (input);
+ char *outptr = buf;
+ size_t outbytesleft = sizeof (buf);
+ size_t res = iconv (cd_utf8_to_88591,
+ &inptr, &inbytesleft,
+ &outptr, &outbytesleft);
+ if (res == 0)
+ result |= 1;
+ iconv_close (cd_utf8_to_88591);
+ }
+ }
+ /* Test against Solaris 10 bug: Failures are not distinguishable from
+ successful returns. */
+ {
+ iconv_t cd_ascii_to_88591 = iconv_open ("ISO8859-1", "646");
+ if (cd_ascii_to_88591 != (iconv_t)(-1))
+ {
+ static ICONV_CONST char input[] = "\263";
+ char buf[10];
+ ICONV_CONST char *inptr = input;
+ size_t inbytesleft = strlen (input);
+ char *outptr = buf;
+ size_t outbytesleft = sizeof (buf);
+ size_t res = iconv (cd_ascii_to_88591,
+ &inptr, &inbytesleft,
+ &outptr, &outbytesleft);
+ if (res == 0)
+ result |= 2;
+ iconv_close (cd_ascii_to_88591);
+ }
+ }
+ /* Test against AIX 6.1..7.1 bug: Buffer overrun. */
+ {
+ iconv_t cd_88591_to_utf8 = iconv_open ("UTF-8", "ISO-8859-1");
+ if (cd_88591_to_utf8 != (iconv_t)(-1))
+ {
+ static ICONV_CONST char input[] = "\304";
+ static char buf[2] = { (char)0xDE, (char)0xAD };
+ ICONV_CONST char *inptr = input;
+ size_t inbytesleft = 1;
+ char *outptr = buf;
+ size_t outbytesleft = 1;
+ size_t res = iconv (cd_88591_to_utf8,
+ &inptr, &inbytesleft,
+ &outptr, &outbytesleft);
+ if (res != (size_t)(-1) || outptr - buf > 1 || buf[1] != (char)0xAD)
+ result |= 4;
+ iconv_close (cd_88591_to_utf8);
+ }
+ }
+#if 0 /* This bug could be worked around by the caller. */
+ /* Test against HP-UX 11.11 bug: Positive return value instead of 0. */
+ {
+ iconv_t cd_88591_to_utf8 = iconv_open ("utf8", "iso88591");
+ if (cd_88591_to_utf8 != (iconv_t)(-1))
+ {
+ static ICONV_CONST char input[] = "\304rger mit b\366sen B\374bchen ohne Augenma\337";
+ char buf[50];
+ ICONV_CONST char *inptr = input;
+ size_t inbytesleft = strlen (input);
+ char *outptr = buf;
+ size_t outbytesleft = sizeof (buf);
+ size_t res = iconv (cd_88591_to_utf8,
+ &inptr, &inbytesleft,
+ &outptr, &outbytesleft);
+ if ((int)res > 0)
+ result |= 8;
+ iconv_close (cd_88591_to_utf8);
+ }
+ }
+#endif
+ /* Test against HP-UX 11.11 bug: No converter from EUC-JP to UTF-8 is
+ provided. */
+ if (/* Try standardized names. */
+ iconv_open ("UTF-8", "EUC-JP") == (iconv_t)(-1)
+ /* Try IRIX, OSF/1 names. */
+ && iconv_open ("UTF-8", "eucJP") == (iconv_t)(-1)
+ /* Try AIX names. */
+ && iconv_open ("UTF-8", "IBM-eucJP") == (iconv_t)(-1)
+ /* Try HP-UX names. */
+ && iconv_open ("utf8", "eucJP") == (iconv_t)(-1))
+ result |= 16;
+ return result;
+]])],
+ [am_cv_func_iconv_works=yes], ,
+ [case "$host_os" in
+ aix* | hpux*) am_cv_func_iconv_works="guessing no" ;;
+ *) am_cv_func_iconv_works="guessing yes" ;;
+ esac])
+ test "$am_cv_func_iconv_works" = no || break
+ done
+ LIBS="$am_save_LIBS"
+ ])
+ case "$am_cv_func_iconv_works" in
+ *no) am_func_iconv=no am_cv_lib_iconv=no ;;
+ *) am_func_iconv=yes ;;
+ esac
+ else
+ am_func_iconv=no am_cv_lib_iconv=no
+ fi
+ if test "$am_func_iconv" = yes; then
+ AC_DEFINE([HAVE_ICONV], [1],
+ [Define if you have the iconv() function and it works.])
+ fi
+ if test "$am_cv_lib_iconv" = yes; then
+ AC_MSG_CHECKING([how to link with libiconv])
+ AC_MSG_RESULT([$LIBICONV])
+ else
+ dnl If $LIBICONV didn't lead to a usable library, we don't need $INCICONV
+ dnl either.
+ CPPFLAGS="$am_save_CPPFLAGS"
+ LIBICONV=
+ LTLIBICONV=
+ fi
+ AC_SUBST([LIBICONV])
+ AC_SUBST([LTLIBICONV])
+])
+
+dnl Define AM_ICONV using AC_DEFUN_ONCE for Autoconf >= 2.64, in order to
+dnl avoid warnings like
+dnl "warning: AC_REQUIRE: `AM_ICONV' was expanded before it was required".
+dnl This is tricky because of the way 'aclocal' is implemented:
+dnl - It requires defining an auxiliary macro whose name ends in AC_DEFUN.
+dnl Otherwise aclocal's initial scan pass would miss the macro definition.
+dnl - It requires a line break inside the AC_DEFUN_ONCE and AC_DEFUN expansions.
+dnl Otherwise aclocal would emit many "Use of uninitialized value $1"
+dnl warnings.
+m4_define([gl_iconv_AC_DEFUN],
+ m4_version_prereq([2.64],
+ [[AC_DEFUN_ONCE(
+ [$1], [$2])]],
+ [m4_ifdef([gl_00GNULIB],
+ [[AC_DEFUN_ONCE(
+ [$1], [$2])]],
+ [[AC_DEFUN(
+ [$1], [$2])]])]))
+gl_iconv_AC_DEFUN([AM_ICONV],
+[
+ AM_ICONV_LINK
+ if test "$am_cv_func_iconv" = yes; then
+ AC_MSG_CHECKING([for iconv declaration])
+ AC_CACHE_VAL([am_cv_proto_iconv], [
+ AC_COMPILE_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[
+#include <stdlib.h>
+#include <iconv.h>
+extern
+#ifdef __cplusplus
+"C"
+#endif
+#if defined(__STDC__) || defined(_MSC_VER) || defined(__cplusplus)
+size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);
+#else
+size_t iconv();
+#endif
+ ]],
+ [[]])],
+ [am_cv_proto_iconv_arg1=""],
+ [am_cv_proto_iconv_arg1="const"])
+ am_cv_proto_iconv="extern size_t iconv (iconv_t cd, $am_cv_proto_iconv_arg1 char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);"])
+ am_cv_proto_iconv=`echo "[$]am_cv_proto_iconv" | tr -s ' ' | sed -e 's/( /(/'`
+ AC_MSG_RESULT([
+ $am_cv_proto_iconv])
+ AC_DEFINE_UNQUOTED([ICONV_CONST], [$am_cv_proto_iconv_arg1],
+ [Define as const if the declaration of iconv() needs const.])
+ dnl Also substitute ICONV_CONST in the gnulib generated <iconv.h>.
+ m4_ifdef([gl_ICONV_H_DEFAULTS],
+ [AC_REQUIRE([gl_ICONV_H_DEFAULTS])
+ if test -n "$am_cv_proto_iconv_arg1"; then
+ ICONV_CONST="const"
+ fi
+ ])
+ fi
+])
diff --git a/m4/lib-ld.m4 b/m4/lib-ld.m4
new file mode 100644
index 000000000..6209de65d
--- /dev/null
+++ b/m4/lib-ld.m4
@@ -0,0 +1,119 @@
+# lib-ld.m4 serial 6
+dnl Copyright (C) 1996-2003, 2009-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl Subroutines of libtool.m4,
+dnl with replacements s/_*LT_PATH/AC_LIB_PROG/ and s/lt_/acl_/ to avoid
+dnl collision with libtool.m4.
+
+dnl From libtool-2.4. Sets the variable with_gnu_ld to yes or no.
+AC_DEFUN([AC_LIB_PROG_LD_GNU],
+[AC_CACHE_CHECK([if the linker ($LD) is GNU ld], [acl_cv_prog_gnu_ld],
+[# I'd rather use --version here, but apparently some GNU lds only accept -v.
+case `$LD -v 2>&1 </dev/null` in
+*GNU* | *'with BFD'*)
+ acl_cv_prog_gnu_ld=yes
+ ;;
+*)
+ acl_cv_prog_gnu_ld=no
+ ;;
+esac])
+with_gnu_ld=$acl_cv_prog_gnu_ld
+])
+
+dnl From libtool-2.4. Sets the variable LD.
+AC_DEFUN([AC_LIB_PROG_LD],
+[AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_CANONICAL_HOST])dnl
+
+AC_ARG_WITH([gnu-ld],
+ [AS_HELP_STRING([--with-gnu-ld],
+ [assume the C compiler uses GNU ld [default=no]])],
+ [test "$withval" = no || with_gnu_ld=yes],
+ [with_gnu_ld=no])dnl
+
+# Prepare PATH_SEPARATOR.
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ # Determine PATH_SEPARATOR by trying to find /bin/sh in a PATH which
+ # contains only /bin. Note that ksh looks also at the FPATH variable,
+ # so we have to set that as well for the test.
+ PATH_SEPARATOR=:
+ (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \
+ && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \
+ || PATH_SEPARATOR=';'
+ }
+fi
+
+ac_prog=ld
+if test "$GCC" = yes; then
+ # Check if gcc -print-prog-name=ld gives a path.
+ AC_MSG_CHECKING([for ld used by $CC])
+ case $host in
+ *-*-mingw*)
+ # gcc leaves a trailing carriage return which upsets mingw
+ ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+ *)
+ ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+ esac
+ case $ac_prog in
+ # Accept absolute paths.
+ [[\\/]]* | ?:[[\\/]]*)
+ re_direlt='/[[^/]][[^/]]*/\.\./'
+ # Canonicalize the pathname of ld
+ ac_prog=`echo "$ac_prog"| sed 's%\\\\%/%g'`
+ while echo "$ac_prog" | grep "$re_direlt" > /dev/null 2>&1; do
+ ac_prog=`echo $ac_prog| sed "s%$re_direlt%/%"`
+ done
+ test -z "$LD" && LD="$ac_prog"
+ ;;
+ "")
+ # If it fails, then pretend we aren't using GCC.
+ ac_prog=ld
+ ;;
+ *)
+ # If it is relative, then search for the first ld in PATH.
+ with_gnu_ld=unknown
+ ;;
+ esac
+elif test "$with_gnu_ld" = yes; then
+ AC_MSG_CHECKING([for GNU ld])
+else
+ AC_MSG_CHECKING([for non-GNU ld])
+fi
+AC_CACHE_VAL([acl_cv_path_LD],
+[if test -z "$LD"; then
+ acl_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+ for ac_dir in $PATH; do
+ IFS="$acl_save_ifs"
+ test -z "$ac_dir" && ac_dir=.
+ if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+ acl_cv_path_LD="$ac_dir/$ac_prog"
+ # Check to see if the program is GNU ld. I'd rather use --version,
+ # but apparently some variants of GNU ld only accept -v.
+ # Break only if it was the GNU/non-GNU ld that we prefer.
+ case `"$acl_cv_path_LD" -v 2>&1 </dev/null` in
+ *GNU* | *'with BFD'*)
+ test "$with_gnu_ld" != no && break
+ ;;
+ *)
+ test "$with_gnu_ld" != yes && break
+ ;;
+ esac
+ fi
+ done
+ IFS="$acl_save_ifs"
+else
+ acl_cv_path_LD="$LD" # Let the user override the test with a path.
+fi])
+LD="$acl_cv_path_LD"
+if test -n "$LD"; then
+ AC_MSG_RESULT([$LD])
+else
+ AC_MSG_RESULT([no])
+fi
+test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH])
+AC_LIB_PROG_LD_GNU
+])
diff --git a/m4/lib-link.m4 b/m4/lib-link.m4
new file mode 100644
index 000000000..2f518553b
--- /dev/null
+++ b/m4/lib-link.m4
@@ -0,0 +1,777 @@
+# lib-link.m4 serial 26 (gettext-0.18.2)
+dnl Copyright (C) 2001-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+AC_PREREQ([2.54])
+
+dnl AC_LIB_LINKFLAGS(name [, dependencies]) searches for libname and
+dnl the libraries corresponding to explicit and implicit dependencies.
+dnl Sets and AC_SUBSTs the LIB${NAME} and LTLIB${NAME} variables and
+dnl augments the CPPFLAGS variable.
+dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname
+dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem.
+AC_DEFUN([AC_LIB_LINKFLAGS],
+[
+ AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+ AC_REQUIRE([AC_LIB_RPATH])
+ pushdef([Name],[m4_translit([$1],[./+-], [____])])
+ pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-],
+ [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
+ AC_CACHE_CHECK([how to link with lib[]$1], [ac_cv_lib[]Name[]_libs], [
+ AC_LIB_LINKFLAGS_BODY([$1], [$2])
+ ac_cv_lib[]Name[]_libs="$LIB[]NAME"
+ ac_cv_lib[]Name[]_ltlibs="$LTLIB[]NAME"
+ ac_cv_lib[]Name[]_cppflags="$INC[]NAME"
+ ac_cv_lib[]Name[]_prefix="$LIB[]NAME[]_PREFIX"
+ ])
+ LIB[]NAME="$ac_cv_lib[]Name[]_libs"
+ LTLIB[]NAME="$ac_cv_lib[]Name[]_ltlibs"
+ INC[]NAME="$ac_cv_lib[]Name[]_cppflags"
+ LIB[]NAME[]_PREFIX="$ac_cv_lib[]Name[]_prefix"
+ AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME)
+ AC_SUBST([LIB]NAME)
+ AC_SUBST([LTLIB]NAME)
+ AC_SUBST([LIB]NAME[_PREFIX])
+ dnl Also set HAVE_LIB[]NAME so that AC_LIB_HAVE_LINKFLAGS can reuse the
+ dnl results of this search when this library appears as a dependency.
+ HAVE_LIB[]NAME=yes
+ popdef([NAME])
+ popdef([Name])
+])
+
+dnl AC_LIB_HAVE_LINKFLAGS(name, dependencies, includes, testcode, [missing-message])
+dnl searches for libname and the libraries corresponding to explicit and
+dnl implicit dependencies, together with the specified include files and
+dnl the ability to compile and link the specified testcode. The missing-message
+dnl defaults to 'no' and may contain additional hints for the user.
+dnl If found, it sets and AC_SUBSTs HAVE_LIB${NAME}=yes and the LIB${NAME}
+dnl and LTLIB${NAME} variables and augments the CPPFLAGS variable, and
+dnl #defines HAVE_LIB${NAME} to 1. Otherwise, it sets and AC_SUBSTs
+dnl HAVE_LIB${NAME}=no and LIB${NAME} and LTLIB${NAME} to empty.
+dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname
+dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem.
+AC_DEFUN([AC_LIB_HAVE_LINKFLAGS],
+[
+ AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+ AC_REQUIRE([AC_LIB_RPATH])
+ pushdef([Name],[m4_translit([$1],[./+-], [____])])
+ pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-],
+ [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
+
+ dnl Search for lib[]Name and define LIB[]NAME, LTLIB[]NAME and INC[]NAME
+ dnl accordingly.
+ AC_LIB_LINKFLAGS_BODY([$1], [$2])
+
+ dnl Add $INC[]NAME to CPPFLAGS before performing the following checks,
+ dnl because if the user has installed lib[]Name and not disabled its use
+ dnl via --without-lib[]Name-prefix, he wants to use it.
+ ac_save_CPPFLAGS="$CPPFLAGS"
+ AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME)
+
+ AC_CACHE_CHECK([for lib[]$1], [ac_cv_lib[]Name], [
+ ac_save_LIBS="$LIBS"
+ dnl If $LIB[]NAME contains some -l options, add it to the end of LIBS,
+ dnl because these -l options might require -L options that are present in
+ dnl LIBS. -l options benefit only from the -L options listed before it.
+ dnl Otherwise, add it to the front of LIBS, because it may be a static
+ dnl library that depends on another static library that is present in LIBS.
+ dnl Static libraries benefit only from the static libraries listed after
+ dnl it.
+ case " $LIB[]NAME" in
+ *" -l"*) LIBS="$LIBS $LIB[]NAME" ;;
+ *) LIBS="$LIB[]NAME $LIBS" ;;
+ esac
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM([[$3]], [[$4]])],
+ [ac_cv_lib[]Name=yes],
+ [ac_cv_lib[]Name='m4_if([$5], [], [no], [[$5]])'])
+ LIBS="$ac_save_LIBS"
+ ])
+ if test "$ac_cv_lib[]Name" = yes; then
+ HAVE_LIB[]NAME=yes
+ AC_DEFINE([HAVE_LIB]NAME, 1, [Define if you have the lib][$1 library.])
+ AC_MSG_CHECKING([how to link with lib[]$1])
+ AC_MSG_RESULT([$LIB[]NAME])
+ else
+ HAVE_LIB[]NAME=no
+ dnl If $LIB[]NAME didn't lead to a usable library, we don't need
+ dnl $INC[]NAME either.
+ CPPFLAGS="$ac_save_CPPFLAGS"
+ LIB[]NAME=
+ LTLIB[]NAME=
+ LIB[]NAME[]_PREFIX=
+ fi
+ AC_SUBST([HAVE_LIB]NAME)
+ AC_SUBST([LIB]NAME)
+ AC_SUBST([LTLIB]NAME)
+ AC_SUBST([LIB]NAME[_PREFIX])
+ popdef([NAME])
+ popdef([Name])
+])
+
+dnl Determine the platform dependent parameters needed to use rpath:
+dnl acl_libext,
+dnl acl_shlibext,
+dnl acl_libname_spec,
+dnl acl_library_names_spec,
+dnl acl_hardcode_libdir_flag_spec,
+dnl acl_hardcode_libdir_separator,
+dnl acl_hardcode_direct,
+dnl acl_hardcode_minus_L.
+AC_DEFUN([AC_LIB_RPATH],
+[
+ dnl Tell automake >= 1.10 to complain if config.rpath is missing.
+ m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([config.rpath])])
+ AC_REQUIRE([AC_PROG_CC]) dnl we use $CC, $GCC, $LDFLAGS
+ AC_REQUIRE([AC_LIB_PROG_LD]) dnl we use $LD, $with_gnu_ld
+ AC_REQUIRE([AC_CANONICAL_HOST]) dnl we use $host
+ AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT]) dnl we use $ac_aux_dir
+ AC_CACHE_CHECK([for shared library run path origin], [acl_cv_rpath], [
+ CC="$CC" GCC="$GCC" LDFLAGS="$LDFLAGS" LD="$LD" with_gnu_ld="$with_gnu_ld" \
+ ${CONFIG_SHELL-/bin/sh} "$ac_aux_dir/config.rpath" "$host" > conftest.sh
+ . ./conftest.sh
+ rm -f ./conftest.sh
+ acl_cv_rpath=done
+ ])
+ wl="$acl_cv_wl"
+ acl_libext="$acl_cv_libext"
+ acl_shlibext="$acl_cv_shlibext"
+ acl_libname_spec="$acl_cv_libname_spec"
+ acl_library_names_spec="$acl_cv_library_names_spec"
+ acl_hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec"
+ acl_hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator"
+ acl_hardcode_direct="$acl_cv_hardcode_direct"
+ acl_hardcode_minus_L="$acl_cv_hardcode_minus_L"
+ dnl Determine whether the user wants rpath handling at all.
+ AC_ARG_ENABLE([rpath],
+ [ --disable-rpath do not hardcode runtime library paths],
+ :, enable_rpath=yes)
+])
+
+dnl AC_LIB_FROMPACKAGE(name, package)
+dnl declares that libname comes from the given package. The configure file
+dnl will then not have a --with-libname-prefix option but a
+dnl --with-package-prefix option. Several libraries can come from the same
+dnl package. This declaration must occur before an AC_LIB_LINKFLAGS or similar
+dnl macro call that searches for libname.
+AC_DEFUN([AC_LIB_FROMPACKAGE],
+[
+ pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-],
+ [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
+ define([acl_frompackage_]NAME, [$2])
+ popdef([NAME])
+ pushdef([PACK],[$2])
+ pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-],
+ [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
+ define([acl_libsinpackage_]PACKUP,
+ m4_ifdef([acl_libsinpackage_]PACKUP, [m4_defn([acl_libsinpackage_]PACKUP)[, ]],)[lib$1])
+ popdef([PACKUP])
+ popdef([PACK])
+])
+
+dnl AC_LIB_LINKFLAGS_BODY(name [, dependencies]) searches for libname and
+dnl the libraries corresponding to explicit and implicit dependencies.
+dnl Sets the LIB${NAME}, LTLIB${NAME} and INC${NAME} variables.
+dnl Also, sets the LIB${NAME}_PREFIX variable to nonempty if libname was found
+dnl in ${LIB${NAME}_PREFIX}/$acl_libdirstem.
+AC_DEFUN([AC_LIB_LINKFLAGS_BODY],
+[
+ AC_REQUIRE([AC_LIB_PREPARE_MULTILIB])
+ pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-],
+ [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
+ pushdef([PACK],[m4_ifdef([acl_frompackage_]NAME, [acl_frompackage_]NAME, lib[$1])])
+ pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-],
+ [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
+ pushdef([PACKLIBS],[m4_ifdef([acl_frompackage_]NAME, [acl_libsinpackage_]PACKUP, lib[$1])])
+ dnl Autoconf >= 2.61 supports dots in --with options.
+ pushdef([P_A_C_K],[m4_if(m4_version_compare(m4_defn([m4_PACKAGE_VERSION]),[2.61]),[-1],[m4_translit(PACK,[.],[_])],PACK)])
+ dnl By default, look in $includedir and $libdir.
+ use_additional=yes
+ AC_LIB_WITH_FINAL_PREFIX([
+ eval additional_includedir=\"$includedir\"
+ eval additional_libdir=\"$libdir\"
+ ])
+ AC_ARG_WITH(P_A_C_K[-prefix],
+[[ --with-]]P_A_C_K[[-prefix[=DIR] search for ]PACKLIBS[ in DIR/include and DIR/lib
+ --without-]]P_A_C_K[[-prefix don't search for ]PACKLIBS[ in includedir and libdir]],
+[
+ if test "X$withval" = "Xno"; then
+ use_additional=no
+ else
+ if test "X$withval" = "X"; then
+ AC_LIB_WITH_FINAL_PREFIX([
+ eval additional_includedir=\"$includedir\"
+ eval additional_libdir=\"$libdir\"
+ ])
+ else
+ additional_includedir="$withval/include"
+ additional_libdir="$withval/$acl_libdirstem"
+ if test "$acl_libdirstem2" != "$acl_libdirstem" \
+ && ! test -d "$withval/$acl_libdirstem"; then
+ additional_libdir="$withval/$acl_libdirstem2"
+ fi
+ fi
+ fi
+])
+ dnl Search the library and its dependencies in $additional_libdir and
+ dnl $LDFLAGS. Using breadth-first-seach.
+ LIB[]NAME=
+ LTLIB[]NAME=
+ INC[]NAME=
+ LIB[]NAME[]_PREFIX=
+ dnl HAVE_LIB${NAME} is an indicator that LIB${NAME}, LTLIB${NAME} have been
+ dnl computed. So it has to be reset here.
+ HAVE_LIB[]NAME=
+ rpathdirs=
+ ltrpathdirs=
+ names_already_handled=
+ names_next_round='$1 $2'
+ while test -n "$names_next_round"; do
+ names_this_round="$names_next_round"
+ names_next_round=
+ for name in $names_this_round; do
+ already_handled=
+ for n in $names_already_handled; do
+ if test "$n" = "$name"; then
+ already_handled=yes
+ break
+ fi
+ done
+ if test -z "$already_handled"; then
+ names_already_handled="$names_already_handled $name"
+ dnl See if it was already located by an earlier AC_LIB_LINKFLAGS
+ dnl or AC_LIB_HAVE_LINKFLAGS call.
+ uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./+-|ABCDEFGHIJKLMNOPQRSTUVWXYZ____|'`
+ eval value=\"\$HAVE_LIB$uppername\"
+ if test -n "$value"; then
+ if test "$value" = yes; then
+ eval value=\"\$LIB$uppername\"
+ test -z "$value" || LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$value"
+ eval value=\"\$LTLIB$uppername\"
+ test -z "$value" || LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$value"
+ else
+ dnl An earlier call to AC_LIB_HAVE_LINKFLAGS has determined
+ dnl that this library doesn't exist. So just drop it.
+ :
+ fi
+ else
+ dnl Search the library lib$name in $additional_libdir and $LDFLAGS
+ dnl and the already constructed $LIBNAME/$LTLIBNAME.
+ found_dir=
+ found_la=
+ found_so=
+ found_a=
+ eval libname=\"$acl_libname_spec\" # typically: libname=lib$name
+ if test -n "$acl_shlibext"; then
+ shrext=".$acl_shlibext" # typically: shrext=.so
+ else
+ shrext=
+ fi
+ if test $use_additional = yes; then
+ dir="$additional_libdir"
+ dnl The same code as in the loop below:
+ dnl First look for a shared library.
+ if test -n "$acl_shlibext"; then
+ if test -f "$dir/$libname$shrext"; then
+ found_dir="$dir"
+ found_so="$dir/$libname$shrext"
+ else
+ if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then
+ ver=`(cd "$dir" && \
+ for f in "$libname$shrext".*; do echo "$f"; done \
+ | sed -e "s,^$libname$shrext\\\\.,," \
+ | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \
+ | sed 1q ) 2>/dev/null`
+ if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then
+ found_dir="$dir"
+ found_so="$dir/$libname$shrext.$ver"
+ fi
+ else
+ eval library_names=\"$acl_library_names_spec\"
+ for f in $library_names; do
+ if test -f "$dir/$f"; then
+ found_dir="$dir"
+ found_so="$dir/$f"
+ break
+ fi
+ done
+ fi
+ fi
+ fi
+ dnl Then look for a static library.
+ if test "X$found_dir" = "X"; then
+ if test -f "$dir/$libname.$acl_libext"; then
+ found_dir="$dir"
+ found_a="$dir/$libname.$acl_libext"
+ fi
+ fi
+ if test "X$found_dir" != "X"; then
+ if test -f "$dir/$libname.la"; then
+ found_la="$dir/$libname.la"
+ fi
+ fi
+ fi
+ if test "X$found_dir" = "X"; then
+ for x in $LDFLAGS $LTLIB[]NAME; do
+ AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+ case "$x" in
+ -L*)
+ dir=`echo "X$x" | sed -e 's/^X-L//'`
+ dnl First look for a shared library.
+ if test -n "$acl_shlibext"; then
+ if test -f "$dir/$libname$shrext"; then
+ found_dir="$dir"
+ found_so="$dir/$libname$shrext"
+ else
+ if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then
+ ver=`(cd "$dir" && \
+ for f in "$libname$shrext".*; do echo "$f"; done \
+ | sed -e "s,^$libname$shrext\\\\.,," \
+ | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \
+ | sed 1q ) 2>/dev/null`
+ if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then
+ found_dir="$dir"
+ found_so="$dir/$libname$shrext.$ver"
+ fi
+ else
+ eval library_names=\"$acl_library_names_spec\"
+ for f in $library_names; do
+ if test -f "$dir/$f"; then
+ found_dir="$dir"
+ found_so="$dir/$f"
+ break
+ fi
+ done
+ fi
+ fi
+ fi
+ dnl Then look for a static library.
+ if test "X$found_dir" = "X"; then
+ if test -f "$dir/$libname.$acl_libext"; then
+ found_dir="$dir"
+ found_a="$dir/$libname.$acl_libext"
+ fi
+ fi
+ if test "X$found_dir" != "X"; then
+ if test -f "$dir/$libname.la"; then
+ found_la="$dir/$libname.la"
+ fi
+ fi
+ ;;
+ esac
+ if test "X$found_dir" != "X"; then
+ break
+ fi
+ done
+ fi
+ if test "X$found_dir" != "X"; then
+ dnl Found the library.
+ LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$found_dir -l$name"
+ if test "X$found_so" != "X"; then
+ dnl Linking with a shared library. We attempt to hardcode its
+ dnl directory into the executable's runpath, unless it's the
+ dnl standard /usr/lib.
+ if test "$enable_rpath" = no \
+ || test "X$found_dir" = "X/usr/$acl_libdirstem" \
+ || test "X$found_dir" = "X/usr/$acl_libdirstem2"; then
+ dnl No hardcoding is needed.
+ LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
+ else
+ dnl Use an explicit option to hardcode DIR into the resulting
+ dnl binary.
+ dnl Potentially add DIR to ltrpathdirs.
+ dnl The ltrpathdirs will be appended to $LTLIBNAME at the end.
+ haveit=
+ for x in $ltrpathdirs; do
+ if test "X$x" = "X$found_dir"; then
+ haveit=yes
+ break
+ fi
+ done
+ if test -z "$haveit"; then
+ ltrpathdirs="$ltrpathdirs $found_dir"
+ fi
+ dnl The hardcoding into $LIBNAME is system dependent.
+ if test "$acl_hardcode_direct" = yes; then
+ dnl Using DIR/libNAME.so during linking hardcodes DIR into the
+ dnl resulting binary.
+ LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
+ else
+ if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then
+ dnl Use an explicit option to hardcode DIR into the resulting
+ dnl binary.
+ LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
+ dnl Potentially add DIR to rpathdirs.
+ dnl The rpathdirs will be appended to $LIBNAME at the end.
+ haveit=
+ for x in $rpathdirs; do
+ if test "X$x" = "X$found_dir"; then
+ haveit=yes
+ break
+ fi
+ done
+ if test -z "$haveit"; then
+ rpathdirs="$rpathdirs $found_dir"
+ fi
+ else
+ dnl Rely on "-L$found_dir".
+ dnl But don't add it if it's already contained in the LDFLAGS
+ dnl or the already constructed $LIBNAME
+ haveit=
+ for x in $LDFLAGS $LIB[]NAME; do
+ AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+ if test "X$x" = "X-L$found_dir"; then
+ haveit=yes
+ break
+ fi
+ done
+ if test -z "$haveit"; then
+ LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir"
+ fi
+ if test "$acl_hardcode_minus_L" != no; then
+ dnl FIXME: Not sure whether we should use
+ dnl "-L$found_dir -l$name" or "-L$found_dir $found_so"
+ dnl here.
+ LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
+ else
+ dnl We cannot use $acl_hardcode_runpath_var and LD_RUN_PATH
+ dnl here, because this doesn't fit in flags passed to the
+ dnl compiler. So give up. No hardcoding. This affects only
+ dnl very old systems.
+ dnl FIXME: Not sure whether we should use
+ dnl "-L$found_dir -l$name" or "-L$found_dir $found_so"
+ dnl here.
+ LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name"
+ fi
+ fi
+ fi
+ fi
+ else
+ if test "X$found_a" != "X"; then
+ dnl Linking with a static library.
+ LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_a"
+ else
+ dnl We shouldn't come here, but anyway it's good to have a
+ dnl fallback.
+ LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir -l$name"
+ fi
+ fi
+ dnl Assume the include files are nearby.
+ additional_includedir=
+ case "$found_dir" in
+ */$acl_libdirstem | */$acl_libdirstem/)
+ basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem/"'*$,,'`
+ if test "$name" = '$1'; then
+ LIB[]NAME[]_PREFIX="$basedir"
+ fi
+ additional_includedir="$basedir/include"
+ ;;
+ */$acl_libdirstem2 | */$acl_libdirstem2/)
+ basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem2/"'*$,,'`
+ if test "$name" = '$1'; then
+ LIB[]NAME[]_PREFIX="$basedir"
+ fi
+ additional_includedir="$basedir/include"
+ ;;
+ esac
+ if test "X$additional_includedir" != "X"; then
+ dnl Potentially add $additional_includedir to $INCNAME.
+ dnl But don't add it
+ dnl 1. if it's the standard /usr/include,
+ dnl 2. if it's /usr/local/include and we are using GCC on Linux,
+ dnl 3. if it's already present in $CPPFLAGS or the already
+ dnl constructed $INCNAME,
+ dnl 4. if it doesn't exist as a directory.
+ if test "X$additional_includedir" != "X/usr/include"; then
+ haveit=
+ if test "X$additional_includedir" = "X/usr/local/include"; then
+ if test -n "$GCC"; then
+ case $host_os in
+ linux* | gnu* | k*bsd*-gnu) haveit=yes;;
+ esac
+ fi
+ fi
+ if test -z "$haveit"; then
+ for x in $CPPFLAGS $INC[]NAME; do
+ AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+ if test "X$x" = "X-I$additional_includedir"; then
+ haveit=yes
+ break
+ fi
+ done
+ if test -z "$haveit"; then
+ if test -d "$additional_includedir"; then
+ dnl Really add $additional_includedir to $INCNAME.
+ INC[]NAME="${INC[]NAME}${INC[]NAME:+ }-I$additional_includedir"
+ fi
+ fi
+ fi
+ fi
+ fi
+ dnl Look for dependencies.
+ if test -n "$found_la"; then
+ dnl Read the .la file. It defines the variables
+ dnl dlname, library_names, old_library, dependency_libs, current,
+ dnl age, revision, installed, dlopen, dlpreopen, libdir.
+ save_libdir="$libdir"
+ case "$found_la" in
+ */* | *\\*) . "$found_la" ;;
+ *) . "./$found_la" ;;
+ esac
+ libdir="$save_libdir"
+ dnl We use only dependency_libs.
+ for dep in $dependency_libs; do
+ case "$dep" in
+ -L*)
+ additional_libdir=`echo "X$dep" | sed -e 's/^X-L//'`
+ dnl Potentially add $additional_libdir to $LIBNAME and $LTLIBNAME.
+ dnl But don't add it
+ dnl 1. if it's the standard /usr/lib,
+ dnl 2. if it's /usr/local/lib and we are using GCC on Linux,
+ dnl 3. if it's already present in $LDFLAGS or the already
+ dnl constructed $LIBNAME,
+ dnl 4. if it doesn't exist as a directory.
+ if test "X$additional_libdir" != "X/usr/$acl_libdirstem" \
+ && test "X$additional_libdir" != "X/usr/$acl_libdirstem2"; then
+ haveit=
+ if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem" \
+ || test "X$additional_libdir" = "X/usr/local/$acl_libdirstem2"; then
+ if test -n "$GCC"; then
+ case $host_os in
+ linux* | gnu* | k*bsd*-gnu) haveit=yes;;
+ esac
+ fi
+ fi
+ if test -z "$haveit"; then
+ haveit=
+ for x in $LDFLAGS $LIB[]NAME; do
+ AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+ if test "X$x" = "X-L$additional_libdir"; then
+ haveit=yes
+ break
+ fi
+ done
+ if test -z "$haveit"; then
+ if test -d "$additional_libdir"; then
+ dnl Really add $additional_libdir to $LIBNAME.
+ LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$additional_libdir"
+ fi
+ fi
+ haveit=
+ for x in $LDFLAGS $LTLIB[]NAME; do
+ AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+ if test "X$x" = "X-L$additional_libdir"; then
+ haveit=yes
+ break
+ fi
+ done
+ if test -z "$haveit"; then
+ if test -d "$additional_libdir"; then
+ dnl Really add $additional_libdir to $LTLIBNAME.
+ LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$additional_libdir"
+ fi
+ fi
+ fi
+ fi
+ ;;
+ -R*)
+ dir=`echo "X$dep" | sed -e 's/^X-R//'`
+ if test "$enable_rpath" != no; then
+ dnl Potentially add DIR to rpathdirs.
+ dnl The rpathdirs will be appended to $LIBNAME at the end.
+ haveit=
+ for x in $rpathdirs; do
+ if test "X$x" = "X$dir"; then
+ haveit=yes
+ break
+ fi
+ done
+ if test -z "$haveit"; then
+ rpathdirs="$rpathdirs $dir"
+ fi
+ dnl Potentially add DIR to ltrpathdirs.
+ dnl The ltrpathdirs will be appended to $LTLIBNAME at the end.
+ haveit=
+ for x in $ltrpathdirs; do
+ if test "X$x" = "X$dir"; then
+ haveit=yes
+ break
+ fi
+ done
+ if test -z "$haveit"; then
+ ltrpathdirs="$ltrpathdirs $dir"
+ fi
+ fi
+ ;;
+ -l*)
+ dnl Handle this in the next round.
+ names_next_round="$names_next_round "`echo "X$dep" | sed -e 's/^X-l//'`
+ ;;
+ *.la)
+ dnl Handle this in the next round. Throw away the .la's
+ dnl directory; it is already contained in a preceding -L
+ dnl option.
+ names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'`
+ ;;
+ *)
+ dnl Most likely an immediate library name.
+ LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$dep"
+ LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$dep"
+ ;;
+ esac
+ done
+ fi
+ else
+ dnl Didn't find the library; assume it is in the system directories
+ dnl known to the linker and runtime loader. (All the system
+ dnl directories known to the linker should also be known to the
+ dnl runtime loader, otherwise the system is severely misconfigured.)
+ LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name"
+ LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-l$name"
+ fi
+ fi
+ fi
+ done
+ done
+ if test "X$rpathdirs" != "X"; then
+ if test -n "$acl_hardcode_libdir_separator"; then
+ dnl Weird platform: only the last -rpath option counts, the user must
+ dnl pass all path elements in one option. We can arrange that for a
+ dnl single library, but not when more than one $LIBNAMEs are used.
+ alldirs=
+ for found_dir in $rpathdirs; do
+ alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$found_dir"
+ done
+ dnl Note: acl_hardcode_libdir_flag_spec uses $libdir and $wl.
+ acl_save_libdir="$libdir"
+ libdir="$alldirs"
+ eval flag=\"$acl_hardcode_libdir_flag_spec\"
+ libdir="$acl_save_libdir"
+ LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag"
+ else
+ dnl The -rpath options are cumulative.
+ for found_dir in $rpathdirs; do
+ acl_save_libdir="$libdir"
+ libdir="$found_dir"
+ eval flag=\"$acl_hardcode_libdir_flag_spec\"
+ libdir="$acl_save_libdir"
+ LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag"
+ done
+ fi
+ fi
+ if test "X$ltrpathdirs" != "X"; then
+ dnl When using libtool, the option that works for both libraries and
+ dnl executables is -R. The -R options are cumulative.
+ for found_dir in $ltrpathdirs; do
+ LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-R$found_dir"
+ done
+ fi
+ popdef([P_A_C_K])
+ popdef([PACKLIBS])
+ popdef([PACKUP])
+ popdef([PACK])
+ popdef([NAME])
+])
+
+dnl AC_LIB_APPENDTOVAR(VAR, CONTENTS) appends the elements of CONTENTS to VAR,
+dnl unless already present in VAR.
+dnl Works only for CPPFLAGS, not for LIB* variables because that sometimes
+dnl contains two or three consecutive elements that belong together.
+AC_DEFUN([AC_LIB_APPENDTOVAR],
+[
+ for element in [$2]; do
+ haveit=
+ for x in $[$1]; do
+ AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+ if test "X$x" = "X$element"; then
+ haveit=yes
+ break
+ fi
+ done
+ if test -z "$haveit"; then
+ [$1]="${[$1]}${[$1]:+ }$element"
+ fi
+ done
+])
+
+dnl For those cases where a variable contains several -L and -l options
+dnl referring to unknown libraries and directories, this macro determines the
+dnl necessary additional linker options for the runtime path.
+dnl AC_LIB_LINKFLAGS_FROM_LIBS([LDADDVAR], [LIBSVALUE], [USE-LIBTOOL])
+dnl sets LDADDVAR to linker options needed together with LIBSVALUE.
+dnl If USE-LIBTOOL evaluates to non-empty, linking with libtool is assumed,
+dnl otherwise linking without libtool is assumed.
+AC_DEFUN([AC_LIB_LINKFLAGS_FROM_LIBS],
+[
+ AC_REQUIRE([AC_LIB_RPATH])
+ AC_REQUIRE([AC_LIB_PREPARE_MULTILIB])
+ $1=
+ if test "$enable_rpath" != no; then
+ if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then
+ dnl Use an explicit option to hardcode directories into the resulting
+ dnl binary.
+ rpathdirs=
+ next=
+ for opt in $2; do
+ if test -n "$next"; then
+ dir="$next"
+ dnl No need to hardcode the standard /usr/lib.
+ if test "X$dir" != "X/usr/$acl_libdirstem" \
+ && test "X$dir" != "X/usr/$acl_libdirstem2"; then
+ rpathdirs="$rpathdirs $dir"
+ fi
+ next=
+ else
+ case $opt in
+ -L) next=yes ;;
+ -L*) dir=`echo "X$opt" | sed -e 's,^X-L,,'`
+ dnl No need to hardcode the standard /usr/lib.
+ if test "X$dir" != "X/usr/$acl_libdirstem" \
+ && test "X$dir" != "X/usr/$acl_libdirstem2"; then
+ rpathdirs="$rpathdirs $dir"
+ fi
+ next= ;;
+ *) next= ;;
+ esac
+ fi
+ done
+ if test "X$rpathdirs" != "X"; then
+ if test -n ""$3""; then
+ dnl libtool is used for linking. Use -R options.
+ for dir in $rpathdirs; do
+ $1="${$1}${$1:+ }-R$dir"
+ done
+ else
+ dnl The linker is used for linking directly.
+ if test -n "$acl_hardcode_libdir_separator"; then
+ dnl Weird platform: only the last -rpath option counts, the user
+ dnl must pass all path elements in one option.
+ alldirs=
+ for dir in $rpathdirs; do
+ alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$dir"
+ done
+ acl_save_libdir="$libdir"
+ libdir="$alldirs"
+ eval flag=\"$acl_hardcode_libdir_flag_spec\"
+ libdir="$acl_save_libdir"
+ $1="$flag"
+ else
+ dnl The -rpath options are cumulative.
+ for dir in $rpathdirs; do
+ acl_save_libdir="$libdir"
+ libdir="$dir"
+ eval flag=\"$acl_hardcode_libdir_flag_spec\"
+ libdir="$acl_save_libdir"
+ $1="${$1}${$1:+ }$flag"
+ done
+ fi
+ fi
+ fi
+ fi
+ fi
+ AC_SUBST([$1])
+])
diff --git a/m4/lib-prefix.m4 b/m4/lib-prefix.m4
new file mode 100644
index 000000000..6851031d3
--- /dev/null
+++ b/m4/lib-prefix.m4
@@ -0,0 +1,224 @@
+# lib-prefix.m4 serial 7 (gettext-0.18)
+dnl Copyright (C) 2001-2005, 2008-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+dnl AC_LIB_ARG_WITH is synonymous to AC_ARG_WITH in autoconf-2.13, and
+dnl similar to AC_ARG_WITH in autoconf 2.52...2.57 except that is doesn't
+dnl require excessive bracketing.
+ifdef([AC_HELP_STRING],
+[AC_DEFUN([AC_LIB_ARG_WITH], [AC_ARG_WITH([$1],[[$2]],[$3],[$4])])],
+[AC_DEFUN([AC_][LIB_ARG_WITH], [AC_ARG_WITH([$1],[$2],[$3],[$4])])])
+
+dnl AC_LIB_PREFIX adds to the CPPFLAGS and LDFLAGS the flags that are needed
+dnl to access previously installed libraries. The basic assumption is that
+dnl a user will want packages to use other packages he previously installed
+dnl with the same --prefix option.
+dnl This macro is not needed if only AC_LIB_LINKFLAGS is used to locate
+dnl libraries, but is otherwise very convenient.
+AC_DEFUN([AC_LIB_PREFIX],
+[
+ AC_BEFORE([$0], [AC_LIB_LINKFLAGS])
+ AC_REQUIRE([AC_PROG_CC])
+ AC_REQUIRE([AC_CANONICAL_HOST])
+ AC_REQUIRE([AC_LIB_PREPARE_MULTILIB])
+ AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+ dnl By default, look in $includedir and $libdir.
+ use_additional=yes
+ AC_LIB_WITH_FINAL_PREFIX([
+ eval additional_includedir=\"$includedir\"
+ eval additional_libdir=\"$libdir\"
+ ])
+ AC_LIB_ARG_WITH([lib-prefix],
+[ --with-lib-prefix[=DIR] search for libraries in DIR/include and DIR/lib
+ --without-lib-prefix don't search for libraries in includedir and libdir],
+[
+ if test "X$withval" = "Xno"; then
+ use_additional=no
+ else
+ if test "X$withval" = "X"; then
+ AC_LIB_WITH_FINAL_PREFIX([
+ eval additional_includedir=\"$includedir\"
+ eval additional_libdir=\"$libdir\"
+ ])
+ else
+ additional_includedir="$withval/include"
+ additional_libdir="$withval/$acl_libdirstem"
+ fi
+ fi
+])
+ if test $use_additional = yes; then
+ dnl Potentially add $additional_includedir to $CPPFLAGS.
+ dnl But don't add it
+ dnl 1. if it's the standard /usr/include,
+ dnl 2. if it's already present in $CPPFLAGS,
+ dnl 3. if it's /usr/local/include and we are using GCC on Linux,
+ dnl 4. if it doesn't exist as a directory.
+ if test "X$additional_includedir" != "X/usr/include"; then
+ haveit=
+ for x in $CPPFLAGS; do
+ AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+ if test "X$x" = "X-I$additional_includedir"; then
+ haveit=yes
+ break
+ fi
+ done
+ if test -z "$haveit"; then
+ if test "X$additional_includedir" = "X/usr/local/include"; then
+ if test -n "$GCC"; then
+ case $host_os in
+ linux* | gnu* | k*bsd*-gnu) haveit=yes;;
+ esac
+ fi
+ fi
+ if test -z "$haveit"; then
+ if test -d "$additional_includedir"; then
+ dnl Really add $additional_includedir to $CPPFLAGS.
+ CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }-I$additional_includedir"
+ fi
+ fi
+ fi
+ fi
+ dnl Potentially add $additional_libdir to $LDFLAGS.
+ dnl But don't add it
+ dnl 1. if it's the standard /usr/lib,
+ dnl 2. if it's already present in $LDFLAGS,
+ dnl 3. if it's /usr/local/lib and we are using GCC on Linux,
+ dnl 4. if it doesn't exist as a directory.
+ if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then
+ haveit=
+ for x in $LDFLAGS; do
+ AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+ if test "X$x" = "X-L$additional_libdir"; then
+ haveit=yes
+ break
+ fi
+ done
+ if test -z "$haveit"; then
+ if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then
+ if test -n "$GCC"; then
+ case $host_os in
+ linux*) haveit=yes;;
+ esac
+ fi
+ fi
+ if test -z "$haveit"; then
+ if test -d "$additional_libdir"; then
+ dnl Really add $additional_libdir to $LDFLAGS.
+ LDFLAGS="${LDFLAGS}${LDFLAGS:+ }-L$additional_libdir"
+ fi
+ fi
+ fi
+ fi
+ fi
+])
+
+dnl AC_LIB_PREPARE_PREFIX creates variables acl_final_prefix,
+dnl acl_final_exec_prefix, containing the values to which $prefix and
+dnl $exec_prefix will expand at the end of the configure script.
+AC_DEFUN([AC_LIB_PREPARE_PREFIX],
+[
+ dnl Unfortunately, prefix and exec_prefix get only finally determined
+ dnl at the end of configure.
+ if test "X$prefix" = "XNONE"; then
+ acl_final_prefix="$ac_default_prefix"
+ else
+ acl_final_prefix="$prefix"
+ fi
+ if test "X$exec_prefix" = "XNONE"; then
+ acl_final_exec_prefix='${prefix}'
+ else
+ acl_final_exec_prefix="$exec_prefix"
+ fi
+ acl_save_prefix="$prefix"
+ prefix="$acl_final_prefix"
+ eval acl_final_exec_prefix=\"$acl_final_exec_prefix\"
+ prefix="$acl_save_prefix"
+])
+
+dnl AC_LIB_WITH_FINAL_PREFIX([statement]) evaluates statement, with the
+dnl variables prefix and exec_prefix bound to the values they will have
+dnl at the end of the configure script.
+AC_DEFUN([AC_LIB_WITH_FINAL_PREFIX],
+[
+ acl_save_prefix="$prefix"
+ prefix="$acl_final_prefix"
+ acl_save_exec_prefix="$exec_prefix"
+ exec_prefix="$acl_final_exec_prefix"
+ $1
+ exec_prefix="$acl_save_exec_prefix"
+ prefix="$acl_save_prefix"
+])
+
+dnl AC_LIB_PREPARE_MULTILIB creates
+dnl - a variable acl_libdirstem, containing the basename of the libdir, either
+dnl "lib" or "lib64" or "lib/64",
+dnl - a variable acl_libdirstem2, as a secondary possible value for
+dnl acl_libdirstem, either the same as acl_libdirstem or "lib/sparcv9" or
+dnl "lib/amd64".
+AC_DEFUN([AC_LIB_PREPARE_MULTILIB],
+[
+ dnl There is no formal standard regarding lib and lib64.
+ dnl On glibc systems, the current practice is that on a system supporting
+ dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under
+ dnl $prefix/lib64 and 32-bit libraries go under $prefix/lib. We determine
+ dnl the compiler's default mode by looking at the compiler's library search
+ dnl path. If at least one of its elements ends in /lib64 or points to a
+ dnl directory whose absolute pathname ends in /lib64, we assume a 64-bit ABI.
+ dnl Otherwise we use the default, namely "lib".
+ dnl On Solaris systems, the current practice is that on a system supporting
+ dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under
+ dnl $prefix/lib/64 (which is a symlink to either $prefix/lib/sparcv9 or
+ dnl $prefix/lib/amd64) and 32-bit libraries go under $prefix/lib.
+ AC_REQUIRE([AC_CANONICAL_HOST])
+ acl_libdirstem=lib
+ acl_libdirstem2=
+ case "$host_os" in
+ solaris*)
+ dnl See Solaris 10 Software Developer Collection > Solaris 64-bit Developer's Guide > The Development Environment
+ dnl <http://docs.sun.com/app/docs/doc/816-5138/dev-env?l=en&a=view>.
+ dnl "Portable Makefiles should refer to any library directories using the 64 symbolic link."
+ dnl But we want to recognize the sparcv9 or amd64 subdirectory also if the
+ dnl symlink is missing, so we set acl_libdirstem2 too.
+ AC_CACHE_CHECK([for 64-bit host], [gl_cv_solaris_64bit],
+ [AC_EGREP_CPP([sixtyfour bits], [
+#ifdef _LP64
+sixtyfour bits
+#endif
+ ], [gl_cv_solaris_64bit=yes], [gl_cv_solaris_64bit=no])
+ ])
+ if test $gl_cv_solaris_64bit = yes; then
+ acl_libdirstem=lib/64
+ case "$host_cpu" in
+ sparc*) acl_libdirstem2=lib/sparcv9 ;;
+ i*86 | x86_64) acl_libdirstem2=lib/amd64 ;;
+ esac
+ fi
+ ;;
+ *)
+ searchpath=`(LC_ALL=C $CC -print-search-dirs) 2>/dev/null | sed -n -e 's,^libraries: ,,p' | sed -e 's,^=,,'`
+ if test -n "$searchpath"; then
+ acl_save_IFS="${IFS= }"; IFS=":"
+ for searchdir in $searchpath; do
+ if test -d "$searchdir"; then
+ case "$searchdir" in
+ */lib64/ | */lib64 ) acl_libdirstem=lib64 ;;
+ */../ | */.. )
+ # Better ignore directories of this form. They are misleading.
+ ;;
+ *) searchdir=`cd "$searchdir" && pwd`
+ case "$searchdir" in
+ */lib64 ) acl_libdirstem=lib64 ;;
+ esac ;;
+ esac
+ fi
+ done
+ IFS="$acl_save_IFS"
+ fi
+ ;;
+ esac
+ test -n "$acl_libdirstem2" || acl_libdirstem2="$acl_libdirstem"
+])
diff --git a/m4/libcurl.m4 b/m4/libcurl.m4
index a84077a5e..047260bae 100644
--- a/m4/libcurl.m4
+++ b/m4/libcurl.m4
@@ -61,7 +61,7 @@ AC_DEFUN([LIBCURL_CHECK_CONFIG],
AH_TEMPLATE([LIBCURL_PROTOCOL_SMTP],[Defined if libcurl supports SMTP])
AC_ARG_WITH(libcurl,
- AC_HELP_STRING([--with-libcurl=PREFIX],[look for the curl library in PREFIX/lib and headers in PREFIX/include]),
+ AS_HELP_STRING([--with-libcurl=PREFIX],[look for the curl library in PREFIX/lib and headers in PREFIX/include]),
[_libcurl_with=$withval],[_libcurl_with=ifelse([$1],,[yes],[$1])])
if test "$_libcurl_with" != "no" ; then
diff --git a/m4/libgcrypt.m4 b/m4/libgcrypt.m4
index 6cf482fcb..9a29eb5ba 100644
--- a/m4/libgcrypt.m4
+++ b/m4/libgcrypt.m4
@@ -23,7 +23,7 @@ dnl
AC_DEFUN([AM_PATH_LIBGCRYPT],
[ AC_REQUIRE([AC_CANONICAL_HOST])
AC_ARG_WITH(libgcrypt-prefix,
- AC_HELP_STRING([--with-libgcrypt-prefix=PFX],
+ AS_HELP_STRING([--with-libgcrypt-prefix=PFX],
[prefix where LIBGCRYPT is installed (optional)]),
libgcrypt_config_prefix="$withval", libgcrypt_config_prefix="")
if test x$libgcrypt_config_prefix != x ; then
diff --git a/m4/libgnurl.m4 b/m4/libgnurl.m4
deleted file mode 100644
index da72e5e87..000000000
--- a/m4/libgnurl.m4
+++ /dev/null
@@ -1,266 +0,0 @@
-# LIBGNURL_CHECK_CONFIG ([DEFAULT-ACTION], [MINIMUM-VERSION],
-# [ACTION-IF-YES], [ACTION-IF-NO])
-# ----------------------------------------------------------
-# David Shaw <dshaw@jabberwocky.com> May-09-2006
-#
-# Checks for libgnurl. DEFAULT-ACTION is the string yes or no to
-# specify whether to default to --with-libgnurl or --without-libgnurl.
-# If not supplied, DEFAULT-ACTION is yes. MINIMUM-VERSION is the
-# minimum version of libgnurl to accept. Pass the version as a regular
-# version number like 7.10.1. If not supplied, any version is
-# accepted. ACTION-IF-YES is a list of shell commands to run if
-# libgnurl was successfully found and passed the various tests.
-# ACTION-IF-NO is a list of shell commands that are run otherwise.
-# Note that using --without-libgnurl does run ACTION-IF-NO.
-#
-# This macro #defines HAVE_LIBGNURL if a working libgnurl setup is
-# found, and sets @LIBGNURL@ and @LIBGNURL_CPPFLAGS@ to the necessary
-# values. Other useful defines are LIBGNURL_FEATURE_xxx where xxx are
-# the various features supported by libgnurl, and LIBGNURL_PROTOCOL_yyy
-# where yyy are the various protocols supported by libgnurl. Both xxx
-# and yyy are capitalized. See the list of AH_TEMPLATEs at the top of
-# the macro for the complete list of possible defines. Shell
-# variables $libgnurl_feature_xxx and $libgnurl_protocol_yyy are also
-# defined to 'yes' for those features and protocols that were found.
-# Note that xxx and yyy keep the same capitalization as in the
-# gnurl-config list (e.g. it's "HTTP" and not "http").
-#
-# Users may override the detected values by doing something like:
-# LIBGNURL="-lgnurl" LIBGNURL_CPPFLAGS="-I/usr/myinclude" ./configure
-#
-# For the sake of sanity, this macro assumes that any libgnurl that is
-# found is after version 7.7.2, the first version that included the
-# gnurl-config script. Note that it is very important for people
-# packaging binary versions of libgnurl to include this script!
-# Without gnurl-config, we can only guess what protocols are available,
-# or use gnurl_version_info to figure it out at runtime.
-
-AC_DEFUN([LIBGNURL_CHECK_CONFIG],
-[
- AH_TEMPLATE([LIBGNURL_FEATURE_SSL],[Defined if libgnurl supports SSL])
- AH_TEMPLATE([LIBGNURL_FEATURE_KRB4],[Defined if libgnurl supports KRB4])
- AH_TEMPLATE([LIBGNURL_FEATURE_IPV6],[Defined if libgnurl supports IPv6])
- AH_TEMPLATE([LIBGNURL_FEATURE_LIBZ],[Defined if libgnurl supports libz])
- AH_TEMPLATE([LIBGNURL_FEATURE_ASYNCHDNS],[Defined if libgnurl supports AsynchDNS])
- AH_TEMPLATE([LIBGNURL_FEATURE_IDN],[Defined if libgnurl supports IDN])
- AH_TEMPLATE([LIBGNURL_FEATURE_SSPI],[Defined if libgnurl supports SSPI])
- AH_TEMPLATE([LIBGNURL_FEATURE_NTLM],[Defined if libgnurl supports NTLM])
-
- AH_TEMPLATE([LIBGNURL_PROTOCOL_HTTP],[Defined if libgnurl supports HTTP])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_HTTPS],[Defined if libgnurl supports HTTPS])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_FTP],[Defined if libgnurl supports FTP])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_FTPS],[Defined if libgnurl supports FTPS])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_FILE],[Defined if libgnurl supports FILE])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_TELNET],[Defined if libgnurl supports TELNET])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_LDAP],[Defined if libgnurl supports LDAP])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_DICT],[Defined if libgnurl supports DICT])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_TFTP],[Defined if libgnurl supports TFTP])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_RTSP],[Defined if libgnurl supports RTSP])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_POP3],[Defined if libgnurl supports POP3])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_IMAP],[Defined if libgnurl supports IMAP])
- AH_TEMPLATE([LIBGNURL_PROTOCOL_SMTP],[Defined if libgnurl supports SMTP])
-
- AC_ARG_WITH(libgnurl,
- AC_HELP_STRING([--with-libgnurl=PREFIX],[look for the gnurl library in PREFIX/lib and headers in PREFIX/include]),
- [_libgnurl_with=$withval],[_libgnurl_with=ifelse([$1],,[yes],[$1])])
-
- if test "$_libgnurl_with" != "no" ; then
-
- AC_PROG_AWK
-
- _libgnurl_version_parse="eval $AWK '{split(\$NF,A,\".\"); X=256*256*A[[1]]+256*A[[2]]+A[[3]]; print X;}'"
-
- _libgnurl_try_link=yes
-
- if test -d "$_libgnurl_with" ; then
- LIBGNURL_CPPFLAGS="-I$withval/include"
- _libgnurl_ldflags="-L$withval/lib"
- AC_PATH_PROG([_libgnurl_config],[gnurl-config],[],
- ["$withval/bin"])
- else
- AC_PATH_PROG([_libgnurl_config],[gnurl-config],[],[$PATH])
- fi
-
- if test x$_libgnurl_config != "x" ; then
- AC_CACHE_CHECK([for the version of libgnurl],
- [libgnurl_cv_lib_gnurl_version],
- [libgnurl_cv_lib_gnurl_version=`$_libgnurl_config --version | $AWK '{print $[]2}'`])
-
- _libgnurl_version=`echo $libgnurl_cv_lib_gnurl_version | $_libgnurl_version_parse`
- _libgnurl_wanted=`echo ifelse([$2],,[0],[$2]) | $_libgnurl_version_parse`
-
- if test $_libgnurl_wanted -gt 0 ; then
- AC_CACHE_CHECK([for libgnurl >= version $2],
- [libgnurl_cv_lib_version_ok],
- [
- if test $_libgnurl_version -ge $_libgnurl_wanted ; then
- libgnurl_cv_lib_version_ok=yes
- else
- libgnurl_cv_lib_version_ok=no
- fi
- ])
- fi
-
- if test $_libgnurl_wanted -eq 0 || test x$libgnurl_cv_lib_version_ok = xyes ; then
- if test x"$LIBGNURL_CPPFLAGS" = "x" ; then
- LIBGNURL_CPPFLAGS=`$_libgnurl_config --cflags`
- fi
- if test x"$LIBGNURL" = "x" ; then
- LIBGNURL=`$_libgnurl_config --libs`
-
- # This is so silly, but Apple actually has a bug in their
- # gnurl-config script. Fixed in Tiger, but there are still
- # lots of Panther installs around.
- case "${host}" in
- powerpc-apple-darwin7*)
- LIBGNURL=`echo $LIBGNURL | sed -e 's|-arch i386||g'`
- ;;
- esac
- fi
-
- # All gnurl-config scripts support --feature
- _libgnurl_features=`$_libgnurl_config --feature`
-
- # Is it modern enough to have --protocols? (7.12.4)
- if test $_libgnurl_version -ge 461828 ; then
- _libgnurl_protocols=`$_libgnurl_config --protocols`
- fi
- else
- _libgnurl_try_link=no
- fi
-
- unset _libgnurl_wanted
- fi
-
- if test $_libgnurl_try_link = yes ; then
-
- # we didn't find gnurl-config, so let's see if the user-supplied
- # link line (or failing that, "-lgnurl") is enough.
- LIBGNURL=${LIBGNURL-"$_libgnurl_ldflags -lgnurl"}
-
- AC_CACHE_CHECK([whether libgnurl is usable],
- [libgnurl_cv_lib_gnurl_usable],
- [
- _libgnurl_save_cppflags=$CPPFLAGS
- CPPFLAGS="$LIBGNURL_CPPFLAGS $CPPFLAGS"
- _libgnurl_save_libs=$LIBS
- LIBS="$LIBGNURL $LIBS"
-
- AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <curl/curl.h>],[
-/* Try and use a few common options to force a failure if we are
- missing symbols or can't link. */
-int x;
-curl_easy_setopt(NULL,CURLOPT_URL,NULL);
-x=CURL_ERROR_SIZE;
-x=CURLOPT_WRITEFUNCTION;
-x=CURLOPT_FILE;
-x=CURLOPT_ERRORBUFFER;
-x=CURLOPT_STDERR;
-x=CURLOPT_VERBOSE;
-])],libgnurl_cv_lib_gnurl_usable=yes,libgnurl_cv_lib_gnurl_usable=no)
-
-# BEGIN Changes from original libcurl.m4:
-# Give it a 2nd shot using 'gnurl/curl.h'
- AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <gnurl/curl.h>],[
-/* Try and use a few common options to force a failure if we are
- missing symbols or can't link. */
-int x;
-curl_easy_setopt(NULL,CURLOPT_URL,NULL);
-x=CURL_ERROR_SIZE;
-x=CURLOPT_WRITEFUNCTION;
-x=CURLOPT_FILE;
-x=CURLOPT_ERRORBUFFER;
-x=CURLOPT_STDERR;
-x=CURLOPT_VERBOSE;
-])],libgnurl_cv_lib_gnurl_usable=yes)
-# END Changes from original libcurl.m4:
-
- CPPFLAGS=$_libgnurl_save_cppflags
- LIBS=$_libgnurl_save_libs
- unset _libgnurl_save_cppflags
- unset _libgnurl_save_libs
- ])
-
- if test $libgnurl_cv_lib_gnurl_usable = yes ; then
-
- # Does gnurl_free() exist in this version of libgnurl?
- # If not, fake it with free()
-
- _libgnurl_save_cppflags=$CPPFLAGS
- CPPFLAGS="$CPPFLAGS $LIBGNURL_CPPFLAGS"
- _libgnurl_save_libs=$LIBS
- LIBS="$LIBS $LIBGNURL"
-
- AC_CHECK_FUNC(curl_free,,
- AC_DEFINE(curl_free,free,
- [Define curl_free() as free() if our version of gnurl lacks curl_free.]))
-
- CPPFLAGS=$_libgnurl_save_cppflags
- LIBS=$_libgnurl_save_libs
- unset _libgnurl_save_cppflags
- unset _libgnurl_save_libs
-
- AC_DEFINE(HAVE_LIBGNURL,1,
- [Define to 1 if you have a functional gnurl library.])
- AC_SUBST(LIBGNURL_CPPFLAGS)
- AC_SUBST(LIBGNURL)
-
- for _libgnurl_feature in $_libgnurl_features ; do
- AC_DEFINE_UNQUOTED(AS_TR_CPP(libgnurl_feature_$_libgnurl_feature),[1])
- eval AS_TR_SH(libgnurl_feature_$_libgnurl_feature)=yes
- done
-
- if test "x$_libgnurl_protocols" = "x" ; then
-
- # We don't have --protocols, so just assume that all
- # protocols are available
- _libgnurl_protocols="HTTP FTP FILE TELNET LDAP DICT TFTP"
-
- if test x$libgnurl_feature_SSL = xyes ; then
- _libgnurl_protocols="$_libgnurl_protocols HTTPS"
-
- # FTPS wasn't standards-compliant until version
- # 7.11.0 (0x070b00 == 461568)
- if test $_libgnurl_version -ge 461568; then
- _libgnurl_protocols="$_libgnurl_protocols FTPS"
- fi
- fi
-
- # RTSP, IMAP, POP3 and SMTP were added in
- # 7.20.0 (0x071400 == 463872)
- if test $_libgnurl_version -ge 463872; then
- _libgnurl_protocols="$_libgnurl_protocols RTSP IMAP POP3 SMTP"
- fi
- fi
-
- for _libgnurl_protocol in $_libgnurl_protocols ; do
- AC_DEFINE_UNQUOTED(AS_TR_CPP(libgnurl_protocol_$_libgnurl_protocol),[1])
- eval AS_TR_SH(libgnurl_protocol_$_libgnurl_protocol)=yes
- done
- else
- unset LIBGNURL
- unset LIBGNURL_CPPFLAGS
- fi
- fi
-
- unset _libgnurl_try_link
- unset _libgnurl_version_parse
- unset _libgnurl_config
- unset _libgnurl_feature
- unset _libgnurl_features
- unset _libgnurl_protocol
- unset _libgnurl_protocols
- unset _libgnurl_version
- unset _libgnurl_ldflags
- fi
-
- if test x$_libgnurl_with = xno || test x$libgnurl_cv_lib_gnurl_usable != xyes ; then
- # This is the IF-NO path
- ifelse([$4],,:,[$4])
- else
- # This is the IF-YES path
- ifelse([$3],,:,[$3])
- fi
-
- unset _libgnurl_with
-])dnl
diff --git a/m4/m4_ax_python_module.m4 b/m4/m4_ax_python_module.m4
new file mode 100644
index 000000000..f0f873d19
--- /dev/null
+++ b/m4/m4_ax_python_module.m4
@@ -0,0 +1,56 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_python_module.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PYTHON_MODULE(modname[, fatal, python])
+#
+# DESCRIPTION
+#
+# Checks for Python module.
+#
+# If fatal is non-empty then absence of a module will trigger an error.
+# The third parameter can either be "python" for Python 2 or "python3" for
+# Python 3; defaults to Python 3.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Andrew Collier
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 9
+
+AU_ALIAS([AC_PYTHON_MODULE], [AX_PYTHON_MODULE])
+AC_DEFUN([AX_PYTHON_MODULE],[
+ if test -z $PYTHON;
+ then
+ if test -z "$3";
+ then
+ PYTHON="python3"
+ else
+ PYTHON="$3"
+ fi
+ fi
+ PYTHON_NAME=`basename $PYTHON`
+ AC_MSG_CHECKING($PYTHON_NAME module: $1)
+ $PYTHON -c "import $1" 2>/dev/null
+ if test $? -eq 0;
+ then
+ AC_MSG_RESULT(yes)
+ eval AS_TR_CPP(HAVE_PYMOD_$1)=yes
+ else
+ AC_MSG_RESULT(no)
+ eval AS_TR_CPP(HAVE_PYMOD_$1)=no
+ #
+ if test -n "$2"
+ then
+ AC_MSG_ERROR(failed to find required module $1)
+ exit 1
+ fi
+ fi
+])
diff --git a/m4/mhd.m4 b/m4/mhd.m4
new file mode 100644
index 000000000..40e5b4684
--- /dev/null
+++ b/m4/mhd.m4
@@ -0,0 +1,49 @@
+# mhd.m4
+
+# This file is part of TALER
+# Copyright (C) 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, If not, see <http://www.gnu.org/license>
+
+# serial 1
+
+dnl MHD_VERSION_AT_LEAST([VERSION])
+dnl
+dnl Check that microhttpd.h can be used to build a program that prints out
+dnl the MHD_VERSION tuple in X.Y.Z format, and that X.Y.Z is greater or equal
+dnl to VERSION. If not, display message and cause the configure script to
+dnl exit failurefully.
+dnl
+dnl This uses AX_COMPARE_VERSION to do the job.
+dnl It sets shell var mhd_cv_version, as well.
+dnl
+AC_DEFUN([MHD_VERSION_AT_LEAST],
+[AC_CACHE_CHECK([libmicrohttpd version],[mhd_cv_version],
+ [AC_LINK_IFELSE([AC_LANG_PROGRAM([[
+ #include <stdio.h>
+ #include <microhttpd.h>
+]],[[
+ int v = MHD_VERSION;
+ printf ("%x.%x.%x\n",
+ (v >> 24) & 0xff,
+ (v >> 16) & 0xff,
+ (v >> 8) & 0xff);
+]])],
+ [mhd_cv_version=$(./conftest)],
+ [mhd_cv_version=0])])
+AX_COMPARE_VERSION([$mhd_cv_version],[ge],[$1],,
+ [AC_MSG_ERROR([[
+***
+*** You need libmicrohttpd >= $1 to build this program.
+*** ]])])])
+
+# mhd.m4 ends here
diff --git a/m4/nls.m4 b/m4/nls.m4
new file mode 100644
index 000000000..afdb9cacd
--- /dev/null
+++ b/m4/nls.m4
@@ -0,0 +1,32 @@
+# nls.m4 serial 5 (gettext-0.18)
+dnl Copyright (C) 1995-2003, 2005-2006, 2008-2014, 2016 Free Software
+dnl Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+dnl Authors:
+dnl Ulrich Drepper <drepper@cygnus.com>, 1995-2000.
+dnl Bruno Haible <haible@clisp.cons.org>, 2000-2003.
+
+AC_PREREQ([2.50])
+
+AC_DEFUN([AM_NLS],
+[
+ AC_MSG_CHECKING([whether NLS is requested])
+ dnl Default is enabled NLS
+ AC_ARG_ENABLE([nls],
+ [ --disable-nls do not use Native Language Support],
+ USE_NLS=$enableval, USE_NLS=yes)
+ AC_MSG_RESULT([$USE_NLS])
+ AC_SUBST([USE_NLS])
+])
diff --git a/m4/po.m4 b/m4/po.m4
new file mode 100644
index 000000000..c5a2f6bf2
--- /dev/null
+++ b/m4/po.m4
@@ -0,0 +1,453 @@
+# po.m4 serial 24 (gettext-0.19)
+dnl Copyright (C) 1995-2014, 2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+dnl Authors:
+dnl Ulrich Drepper <drepper@cygnus.com>, 1995-2000.
+dnl Bruno Haible <haible@clisp.cons.org>, 2000-2003.
+
+AC_PREREQ([2.60])
+
+dnl Checks for all prerequisites of the po subdirectory.
+AC_DEFUN([AM_PO_SUBDIRS],
+[
+ AC_REQUIRE([AC_PROG_MAKE_SET])dnl
+ AC_REQUIRE([AC_PROG_INSTALL])dnl
+ AC_REQUIRE([AC_PROG_MKDIR_P])dnl
+ AC_REQUIRE([AC_PROG_SED])dnl
+ AC_REQUIRE([AM_NLS])dnl
+
+ dnl Release version of the gettext macros. This is used to ensure that
+ dnl the gettext macros and po/Makefile.in.in are in sync.
+ AC_SUBST([GETTEXT_MACRO_VERSION], [0.19])
+
+ dnl Perform the following tests also if --disable-nls has been given,
+ dnl because they are needed for "make dist" to work.
+
+ dnl Search for GNU msgfmt in the PATH.
+ dnl The first test excludes Solaris msgfmt and early GNU msgfmt versions.
+ dnl The second test excludes FreeBSD msgfmt.
+ AM_PATH_PROG_WITH_TEST(MSGFMT, msgfmt,
+ [$ac_dir/$ac_word --statistics /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 &&
+ (if $ac_dir/$ac_word --statistics /dev/null 2>&1 >/dev/null | grep usage >/dev/null; then exit 1; else exit 0; fi)],
+ :)
+ AC_PATH_PROG([GMSGFMT], [gmsgfmt], [$MSGFMT])
+
+ dnl Test whether it is GNU msgfmt >= 0.15.
+changequote(,)dnl
+ case `$MSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in
+ '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) MSGFMT_015=: ;;
+ *) MSGFMT_015=$MSGFMT ;;
+ esac
+changequote([,])dnl
+ AC_SUBST([MSGFMT_015])
+changequote(,)dnl
+ case `$GMSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in
+ '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) GMSGFMT_015=: ;;
+ *) GMSGFMT_015=$GMSGFMT ;;
+ esac
+changequote([,])dnl
+ AC_SUBST([GMSGFMT_015])
+
+ dnl Search for GNU xgettext 0.12 or newer in the PATH.
+ dnl The first test excludes Solaris xgettext and early GNU xgettext versions.
+ dnl The second test excludes FreeBSD xgettext.
+ AM_PATH_PROG_WITH_TEST(XGETTEXT, xgettext,
+ [$ac_dir/$ac_word --omit-header --copyright-holder= --msgid-bugs-address= /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 &&
+ (if $ac_dir/$ac_word --omit-header --copyright-holder= --msgid-bugs-address= /dev/null 2>&1 >/dev/null | grep usage >/dev/null; then exit 1; else exit 0; fi)],
+ :)
+ dnl Remove leftover from FreeBSD xgettext call.
+ rm -f messages.po
+
+ dnl Test whether it is GNU xgettext >= 0.15.
+changequote(,)dnl
+ case `$XGETTEXT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in
+ '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) XGETTEXT_015=: ;;
+ *) XGETTEXT_015=$XGETTEXT ;;
+ esac
+changequote([,])dnl
+ AC_SUBST([XGETTEXT_015])
+
+ dnl Search for GNU msgmerge 0.11 or newer in the PATH.
+ AM_PATH_PROG_WITH_TEST(MSGMERGE, msgmerge,
+ [$ac_dir/$ac_word --update -q /dev/null /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1], :)
+
+ dnl Installation directories.
+ dnl Autoconf >= 2.60 defines localedir. For older versions of autoconf, we
+ dnl have to define it here, so that it can be used in po/Makefile.
+ test -n "$localedir" || localedir='${datadir}/locale'
+ AC_SUBST([localedir])
+
+ dnl Support for AM_XGETTEXT_OPTION.
+ test -n "${XGETTEXT_EXTRA_OPTIONS+set}" || XGETTEXT_EXTRA_OPTIONS=
+ AC_SUBST([XGETTEXT_EXTRA_OPTIONS])
+
+ AC_CONFIG_COMMANDS([po-directories], [[
+ for ac_file in $CONFIG_FILES; do
+ # Support "outfile[:infile[:infile...]]"
+ case "$ac_file" in
+ *:*) ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+ esac
+ # PO directories have a Makefile.in generated from Makefile.in.in.
+ case "$ac_file" in */Makefile.in)
+ # Adjust a relative srcdir.
+ ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'`
+ ac_dir_suffix=/`echo "$ac_dir"|sed 's%^\./%%'`
+ ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'`
+ # In autoconf-2.13 it is called $ac_given_srcdir.
+ # In autoconf-2.50 it is called $srcdir.
+ test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir"
+ case "$ac_given_srcdir" in
+ .) top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;;
+ /*) top_srcdir="$ac_given_srcdir" ;;
+ *) top_srcdir="$ac_dots$ac_given_srcdir" ;;
+ esac
+ # Treat a directory as a PO directory if and only if it has a
+ # POTFILES.in file. This allows packages to have multiple PO
+ # directories under different names or in different locations.
+ if test -f "$ac_given_srcdir/$ac_dir/POTFILES.in"; then
+ rm -f "$ac_dir/POTFILES"
+ test -n "$as_me" && echo "$as_me: creating $ac_dir/POTFILES" || echo "creating $ac_dir/POTFILES"
+ gt_tab=`printf '\t'`
+ cat "$ac_given_srcdir/$ac_dir/POTFILES.in" | sed -e "/^#/d" -e "/^[ ${gt_tab}]*\$/d" -e "s,.*, $top_srcdir/& \\\\," | sed -e "\$s/\(.*\) \\\\/\1/" > "$ac_dir/POTFILES"
+ POMAKEFILEDEPS="POTFILES.in"
+ # ALL_LINGUAS, POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES depend
+ # on $ac_dir but don't depend on user-specified configuration
+ # parameters.
+ if test -f "$ac_given_srcdir/$ac_dir/LINGUAS"; then
+ # The LINGUAS file contains the set of available languages.
+ if test -n "$OBSOLETE_ALL_LINGUAS"; then
+ test -n "$as_me" && echo "$as_me: setting ALL_LINGUAS in configure.in is obsolete" || echo "setting ALL_LINGUAS in configure.in is obsolete"
+ fi
+ ALL_LINGUAS_=`sed -e "/^#/d" -e "s/#.*//" "$ac_given_srcdir/$ac_dir/LINGUAS"`
+ # Hide the ALL_LINGUAS assignment from automake < 1.5.
+ eval 'ALL_LINGUAS''=$ALL_LINGUAS_'
+ POMAKEFILEDEPS="$POMAKEFILEDEPS LINGUAS"
+ else
+ # The set of available languages was given in configure.in.
+ # Hide the ALL_LINGUAS assignment from automake < 1.5.
+ eval 'ALL_LINGUAS''=$OBSOLETE_ALL_LINGUAS'
+ fi
+ # Compute POFILES
+ # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).po)
+ # Compute UPDATEPOFILES
+ # as $(foreach lang, $(ALL_LINGUAS), $(lang).po-update)
+ # Compute DUMMYPOFILES
+ # as $(foreach lang, $(ALL_LINGUAS), $(lang).nop)
+ # Compute GMOFILES
+ # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).gmo)
+ case "$ac_given_srcdir" in
+ .) srcdirpre= ;;
+ *) srcdirpre='$(srcdir)/' ;;
+ esac
+ POFILES=
+ UPDATEPOFILES=
+ DUMMYPOFILES=
+ GMOFILES=
+ for lang in $ALL_LINGUAS; do
+ POFILES="$POFILES $srcdirpre$lang.po"
+ UPDATEPOFILES="$UPDATEPOFILES $lang.po-update"
+ DUMMYPOFILES="$DUMMYPOFILES $lang.nop"
+ GMOFILES="$GMOFILES $srcdirpre$lang.gmo"
+ done
+ # CATALOGS depends on both $ac_dir and the user's LINGUAS
+ # environment variable.
+ INST_LINGUAS=
+ if test -n "$ALL_LINGUAS"; then
+ for presentlang in $ALL_LINGUAS; do
+ useit=no
+ if test "%UNSET%" != "$LINGUAS"; then
+ desiredlanguages="$LINGUAS"
+ else
+ desiredlanguages="$ALL_LINGUAS"
+ fi
+ for desiredlang in $desiredlanguages; do
+ # Use the presentlang catalog if desiredlang is
+ # a. equal to presentlang, or
+ # b. a variant of presentlang (because in this case,
+ # presentlang can be used as a fallback for messages
+ # which are not translated in the desiredlang catalog).
+ case "$desiredlang" in
+ "$presentlang"*) useit=yes;;
+ esac
+ done
+ if test $useit = yes; then
+ INST_LINGUAS="$INST_LINGUAS $presentlang"
+ fi
+ done
+ fi
+ CATALOGS=
+ if test -n "$INST_LINGUAS"; then
+ for lang in $INST_LINGUAS; do
+ CATALOGS="$CATALOGS $lang.gmo"
+ done
+ fi
+ test -n "$as_me" && echo "$as_me: creating $ac_dir/Makefile" || echo "creating $ac_dir/Makefile"
+ sed -e "/^POTFILES =/r $ac_dir/POTFILES" -e "/^# Makevars/r $ac_given_srcdir/$ac_dir/Makevars" -e "s|@POFILES@|$POFILES|g" -e "s|@UPDATEPOFILES@|$UPDATEPOFILES|g" -e "s|@DUMMYPOFILES@|$DUMMYPOFILES|g" -e "s|@GMOFILES@|$GMOFILES|g" -e "s|@CATALOGS@|$CATALOGS|g" -e "s|@POMAKEFILEDEPS@|$POMAKEFILEDEPS|g" "$ac_dir/Makefile.in" > "$ac_dir/Makefile"
+ for f in "$ac_given_srcdir/$ac_dir"/Rules-*; do
+ if test -f "$f"; then
+ case "$f" in
+ *.orig | *.bak | *~) ;;
+ *) cat "$f" >> "$ac_dir/Makefile" ;;
+ esac
+ fi
+ done
+ fi
+ ;;
+ esac
+ done]],
+ [# Capture the value of obsolete ALL_LINGUAS because we need it to compute
+ # POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES, CATALOGS. But hide it
+ # from automake < 1.5.
+ eval 'OBSOLETE_ALL_LINGUAS''="$ALL_LINGUAS"'
+ # Capture the value of LINGUAS because we need it to compute CATALOGS.
+ LINGUAS="${LINGUAS-%UNSET%}"
+ ])
+])
+
+dnl Postprocesses a Makefile in a directory containing PO files.
+AC_DEFUN([AM_POSTPROCESS_PO_MAKEFILE],
+[
+ # When this code is run, in config.status, two variables have already been
+ # set:
+ # - OBSOLETE_ALL_LINGUAS is the value of LINGUAS set in configure.in,
+ # - LINGUAS is the value of the environment variable LINGUAS at configure
+ # time.
+
+changequote(,)dnl
+ # Adjust a relative srcdir.
+ ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'`
+ ac_dir_suffix=/`echo "$ac_dir"|sed 's%^\./%%'`
+ ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'`
+ # In autoconf-2.13 it is called $ac_given_srcdir.
+ # In autoconf-2.50 it is called $srcdir.
+ test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir"
+ case "$ac_given_srcdir" in
+ .) top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;;
+ /*) top_srcdir="$ac_given_srcdir" ;;
+ *) top_srcdir="$ac_dots$ac_given_srcdir" ;;
+ esac
+
+ # Find a way to echo strings without interpreting backslash.
+ if test "X`(echo '\t') 2>/dev/null`" = 'X\t'; then
+ gt_echo='echo'
+ else
+ if test "X`(printf '%s\n' '\t') 2>/dev/null`" = 'X\t'; then
+ gt_echo='printf %s\n'
+ else
+ echo_func () {
+ cat <<EOT
+$*
+EOT
+ }
+ gt_echo='echo_func'
+ fi
+ fi
+
+ # A sed script that extracts the value of VARIABLE from a Makefile.
+ tab=`printf '\t'`
+ sed_x_variable='
+# Test if the hold space is empty.
+x
+s/P/P/
+x
+ta
+# Yes it was empty. Look if we have the expected variable definition.
+/^['"${tab}"' ]*VARIABLE['"${tab}"' ]*=/{
+ # Seen the first line of the variable definition.
+ s/^['"${tab}"' ]*VARIABLE['"${tab}"' ]*=//
+ ba
+}
+bd
+:a
+# Here we are processing a line from the variable definition.
+# Remove comment, more precisely replace it with a space.
+s/#.*$/ /
+# See if the line ends in a backslash.
+tb
+:b
+s/\\$//
+# Print the line, without the trailing backslash.
+p
+tc
+# There was no trailing backslash. The end of the variable definition is
+# reached. Clear the hold space.
+s/^.*$//
+x
+bd
+:c
+# A trailing backslash means that the variable definition continues in the
+# next line. Put a nonempty string into the hold space to indicate this.
+s/^.*$/P/
+x
+:d
+'
+changequote([,])dnl
+
+ # Set POTFILES to the value of the Makefile variable POTFILES.
+ sed_x_POTFILES=`$gt_echo "$sed_x_variable" | sed -e '/^ *#/d' -e 's/VARIABLE/POTFILES/g'`
+ POTFILES=`sed -n -e "$sed_x_POTFILES" < "$ac_file"`
+ # Compute POTFILES_DEPS as
+ # $(foreach file, $(POTFILES), $(top_srcdir)/$(file))
+ POTFILES_DEPS=
+ for file in $POTFILES; do
+ POTFILES_DEPS="$POTFILES_DEPS "'$(top_srcdir)/'"$file"
+ done
+ POMAKEFILEDEPS=""
+
+ if test -n "$OBSOLETE_ALL_LINGUAS"; then
+ test -n "$as_me" && echo "$as_me: setting ALL_LINGUAS in configure.in is obsolete" || echo "setting ALL_LINGUAS in configure.in is obsolete"
+ fi
+ if test -f "$ac_given_srcdir/$ac_dir/LINGUAS"; then
+ # The LINGUAS file contains the set of available languages.
+ ALL_LINGUAS_=`sed -e "/^#/d" -e "s/#.*//" "$ac_given_srcdir/$ac_dir/LINGUAS"`
+ POMAKEFILEDEPS="$POMAKEFILEDEPS LINGUAS"
+ else
+ # Set ALL_LINGUAS to the value of the Makefile variable LINGUAS.
+ sed_x_LINGUAS=`$gt_echo "$sed_x_variable" | sed -e '/^ *#/d' -e 's/VARIABLE/LINGUAS/g'`
+ ALL_LINGUAS_=`sed -n -e "$sed_x_LINGUAS" < "$ac_file"`
+ fi
+ # Hide the ALL_LINGUAS assignment from automake < 1.5.
+ eval 'ALL_LINGUAS''=$ALL_LINGUAS_'
+ # Compute POFILES
+ # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).po)
+ # Compute UPDATEPOFILES
+ # as $(foreach lang, $(ALL_LINGUAS), $(lang).po-update)
+ # Compute DUMMYPOFILES
+ # as $(foreach lang, $(ALL_LINGUAS), $(lang).nop)
+ # Compute GMOFILES
+ # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).gmo)
+ # Compute PROPERTIESFILES
+ # as $(foreach lang, $(ALL_LINGUAS), $(top_srcdir)/$(DOMAIN)_$(lang).properties)
+ # Compute CLASSFILES
+ # as $(foreach lang, $(ALL_LINGUAS), $(top_srcdir)/$(DOMAIN)_$(lang).class)
+ # Compute QMFILES
+ # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).qm)
+ # Compute MSGFILES
+ # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(frob $(lang)).msg)
+ # Compute RESOURCESDLLFILES
+ # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(frob $(lang))/$(DOMAIN).resources.dll)
+ case "$ac_given_srcdir" in
+ .) srcdirpre= ;;
+ *) srcdirpre='$(srcdir)/' ;;
+ esac
+ POFILES=
+ UPDATEPOFILES=
+ DUMMYPOFILES=
+ GMOFILES=
+ PROPERTIESFILES=
+ CLASSFILES=
+ QMFILES=
+ MSGFILES=
+ RESOURCESDLLFILES=
+ for lang in $ALL_LINGUAS; do
+ POFILES="$POFILES $srcdirpre$lang.po"
+ UPDATEPOFILES="$UPDATEPOFILES $lang.po-update"
+ DUMMYPOFILES="$DUMMYPOFILES $lang.nop"
+ GMOFILES="$GMOFILES $srcdirpre$lang.gmo"
+ PROPERTIESFILES="$PROPERTIESFILES \$(top_srcdir)/\$(DOMAIN)_$lang.properties"
+ CLASSFILES="$CLASSFILES \$(top_srcdir)/\$(DOMAIN)_$lang.class"
+ QMFILES="$QMFILES $srcdirpre$lang.qm"
+ frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
+ MSGFILES="$MSGFILES $srcdirpre$frobbedlang.msg"
+ frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'`
+ RESOURCESDLLFILES="$RESOURCESDLLFILES $srcdirpre$frobbedlang/\$(DOMAIN).resources.dll"
+ done
+ # CATALOGS depends on both $ac_dir and the user's LINGUAS
+ # environment variable.
+ INST_LINGUAS=
+ if test -n "$ALL_LINGUAS"; then
+ for presentlang in $ALL_LINGUAS; do
+ useit=no
+ if test "%UNSET%" != "$LINGUAS"; then
+ desiredlanguages="$LINGUAS"
+ else
+ desiredlanguages="$ALL_LINGUAS"
+ fi
+ for desiredlang in $desiredlanguages; do
+ # Use the presentlang catalog if desiredlang is
+ # a. equal to presentlang, or
+ # b. a variant of presentlang (because in this case,
+ # presentlang can be used as a fallback for messages
+ # which are not translated in the desiredlang catalog).
+ case "$desiredlang" in
+ "$presentlang"*) useit=yes;;
+ esac
+ done
+ if test $useit = yes; then
+ INST_LINGUAS="$INST_LINGUAS $presentlang"
+ fi
+ done
+ fi
+ CATALOGS=
+ JAVACATALOGS=
+ QTCATALOGS=
+ TCLCATALOGS=
+ CSHARPCATALOGS=
+ if test -n "$INST_LINGUAS"; then
+ for lang in $INST_LINGUAS; do
+ CATALOGS="$CATALOGS $lang.gmo"
+ JAVACATALOGS="$JAVACATALOGS \$(DOMAIN)_$lang.properties"
+ QTCATALOGS="$QTCATALOGS $lang.qm"
+ frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
+ TCLCATALOGS="$TCLCATALOGS $frobbedlang.msg"
+ frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'`
+ CSHARPCATALOGS="$CSHARPCATALOGS $frobbedlang/\$(DOMAIN).resources.dll"
+ done
+ fi
+
+ sed -e "s|@POTFILES_DEPS@|$POTFILES_DEPS|g" -e "s|@POFILES@|$POFILES|g" -e "s|@UPDATEPOFILES@|$UPDATEPOFILES|g" -e "s|@DUMMYPOFILES@|$DUMMYPOFILES|g" -e "s|@GMOFILES@|$GMOFILES|g" -e "s|@PROPERTIESFILES@|$PROPERTIESFILES|g" -e "s|@CLASSFILES@|$CLASSFILES|g" -e "s|@QMFILES@|$QMFILES|g" -e "s|@MSGFILES@|$MSGFILES|g" -e "s|@RESOURCESDLLFILES@|$RESOURCESDLLFILES|g" -e "s|@CATALOGS@|$CATALOGS|g" -e "s|@JAVACATALOGS@|$JAVACATALOGS|g" -e "s|@QTCATALOGS@|$QTCATALOGS|g" -e "s|@TCLCATALOGS@|$TCLCATALOGS|g" -e "s|@CSHARPCATALOGS@|$CSHARPCATALOGS|g" -e 's,^#distdir:,distdir:,' < "$ac_file" > "$ac_file.tmp"
+ tab=`printf '\t'`
+ if grep -l '@TCLCATALOGS@' "$ac_file" > /dev/null; then
+ # Add dependencies that cannot be formulated as a simple suffix rule.
+ for lang in $ALL_LINGUAS; do
+ frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
+ cat >> "$ac_file.tmp" <<EOF
+$frobbedlang.msg: $lang.po
+${tab}@echo "\$(MSGFMT) -c --tcl -d \$(srcdir) -l $lang $srcdirpre$lang.po"; \
+${tab}\$(MSGFMT) -c --tcl -d "\$(srcdir)" -l $lang $srcdirpre$lang.po || { rm -f "\$(srcdir)/$frobbedlang.msg"; exit 1; }
+EOF
+ done
+ fi
+ if grep -l '@CSHARPCATALOGS@' "$ac_file" > /dev/null; then
+ # Add dependencies that cannot be formulated as a simple suffix rule.
+ for lang in $ALL_LINGUAS; do
+ frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'`
+ cat >> "$ac_file.tmp" <<EOF
+$frobbedlang/\$(DOMAIN).resources.dll: $lang.po
+${tab}@echo "\$(MSGFMT) -c --csharp -d \$(srcdir) -l $lang $srcdirpre$lang.po -r \$(DOMAIN)"; \
+${tab}\$(MSGFMT) -c --csharp -d "\$(srcdir)" -l $lang $srcdirpre$lang.po -r "\$(DOMAIN)" || { rm -f "\$(srcdir)/$frobbedlang.msg"; exit 1; }
+EOF
+ done
+ fi
+ if test -n "$POMAKEFILEDEPS"; then
+ cat >> "$ac_file.tmp" <<EOF
+Makefile: $POMAKEFILEDEPS
+EOF
+ fi
+ mv "$ac_file.tmp" "$ac_file"
+])
+
+dnl Initializes the accumulator used by AM_XGETTEXT_OPTION.
+AC_DEFUN([AM_XGETTEXT_OPTION_INIT],
+[
+ XGETTEXT_EXTRA_OPTIONS=
+])
+
+dnl Registers an option to be passed to xgettext in the po subdirectory.
+AC_DEFUN([AM_XGETTEXT_OPTION],
+[
+ AC_REQUIRE([AM_XGETTEXT_OPTION_INIT])
+ XGETTEXT_EXTRA_OPTIONS="$XGETTEXT_EXTRA_OPTIONS $1"
+])
diff --git a/m4/progtest.m4 b/m4/progtest.m4
new file mode 100644
index 000000000..9ace7c341
--- /dev/null
+++ b/m4/progtest.m4
@@ -0,0 +1,91 @@
+# progtest.m4 serial 7 (gettext-0.18.2)
+dnl Copyright (C) 1996-2003, 2005, 2008-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+dnl Authors:
+dnl Ulrich Drepper <drepper@cygnus.com>, 1996.
+
+AC_PREREQ([2.50])
+
+# Search path for a program which passes the given test.
+
+dnl AM_PATH_PROG_WITH_TEST(VARIABLE, PROG-TO-CHECK-FOR,
+dnl TEST-PERFORMED-ON-FOUND_PROGRAM [, VALUE-IF-NOT-FOUND [, PATH]])
+AC_DEFUN([AM_PATH_PROG_WITH_TEST],
+[
+# Prepare PATH_SEPARATOR.
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ # Determine PATH_SEPARATOR by trying to find /bin/sh in a PATH which
+ # contains only /bin. Note that ksh looks also at the FPATH variable,
+ # so we have to set that as well for the test.
+ PATH_SEPARATOR=:
+ (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \
+ && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \
+ || PATH_SEPARATOR=';'
+ }
+fi
+
+# Find out how to test for executable files. Don't use a zero-byte file,
+# as systems may use methods other than mode bits to determine executability.
+cat >conf$$.file <<_ASEOF
+#! /bin/sh
+exit 0
+_ASEOF
+chmod +x conf$$.file
+if test -x conf$$.file >/dev/null 2>&1; then
+ ac_executable_p="test -x"
+else
+ ac_executable_p="test -f"
+fi
+rm -f conf$$.file
+
+# Extract the first word of "$2", so it can be a program name with args.
+set dummy $2; ac_word=[$]2
+AC_MSG_CHECKING([for $ac_word])
+AC_CACHE_VAL([ac_cv_path_$1],
+[case "[$]$1" in
+ [[\\/]]* | ?:[[\\/]]*)
+ ac_cv_path_$1="[$]$1" # Let the user override the test with a path.
+ ;;
+ *)
+ ac_save_IFS="$IFS"; IFS=$PATH_SEPARATOR
+ for ac_dir in ifelse([$5], , $PATH, [$5]); do
+ IFS="$ac_save_IFS"
+ test -z "$ac_dir" && ac_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $ac_executable_p "$ac_dir/$ac_word$ac_exec_ext"; then
+ echo "$as_me: trying $ac_dir/$ac_word..." >&AS_MESSAGE_LOG_FD
+ if [$3]; then
+ ac_cv_path_$1="$ac_dir/$ac_word$ac_exec_ext"
+ break 2
+ fi
+ fi
+ done
+ done
+ IFS="$ac_save_IFS"
+dnl If no 4th arg is given, leave the cache variable unset,
+dnl so AC_PATH_PROGS will keep looking.
+ifelse([$4], , , [ test -z "[$]ac_cv_path_$1" && ac_cv_path_$1="$4"
+])dnl
+ ;;
+esac])dnl
+$1="$ac_cv_path_$1"
+if test ifelse([$4], , [-n "[$]$1"], ["[$]$1" != "$4"]); then
+ AC_MSG_RESULT([$][$1])
+else
+ AC_MSG_RESULT([no])
+fi
+AC_SUBST([$1])dnl
+])
diff --git a/po/ChangeLog b/po/ChangeLog
new file mode 100644
index 000000000..9f9465294
--- /dev/null
+++ b/po/ChangeLog
@@ -0,0 +1,12 @@
+2021-04-05 gettextize <bug-gnu-gettext@gnu.org>
+
+ * Makefile.in.in: New file, from gettext-0.19.8.1.
+ * Rules-quot: New file, from gettext-0.19.8.1.
+ * boldquot.sed: New file, from gettext-0.19.8.1.
+ * en@boldquot.header: New file, from gettext-0.19.8.1.
+ * en@quot.header: New file, from gettext-0.19.8.1.
+ * insert-header.sin: New file, from gettext-0.19.8.1.
+ * quot.sed: New file, from gettext-0.19.8.1.
+ * remove-potcdate.sin: New file, from gettext-0.19.8.1.
+ * POTFILES.in: New file.
+
diff --git a/po/Makefile.in.in b/po/Makefile.in.in
new file mode 100644
index 000000000..38c293d2e
--- /dev/null
+++ b/po/Makefile.in.in
@@ -0,0 +1,483 @@
+# Makefile for PO directory in any package using GNU gettext.
+# Copyright (C) 1995-1997, 2000-2007, 2009-2010 by Ulrich Drepper <drepper@gnu.ai.mit.edu>
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved. This file is offered as-is,
+# without any warranty.
+#
+# Origin: gettext-0.19.8
+GETTEXT_MACRO_VERSION = 0.19
+
+PACKAGE = @PACKAGE@
+VERSION = @VERSION@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+
+SED = @SED@
+SHELL = /bin/sh
+@SET_MAKE@
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datarootdir = @datarootdir@
+datadir = @datadir@
+localedir = @localedir@
+gettextsrcdir = $(datadir)/gettext/po
+
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+
+# We use $(mkdir_p).
+# In automake <= 1.9.x, $(mkdir_p) is defined either as "mkdir -p --" or as
+# "$(mkinstalldirs)" or as "$(install_sh) -d". For these automake versions,
+# @install_sh@ does not start with $(SHELL), so we add it.
+# In automake >= 1.10, @mkdir_p@ is derived from ${MKDIR_P}, which is defined
+# either as "/path/to/mkdir -p" or ".../install-sh -c -d". For these automake
+# versions, $(mkinstalldirs) and $(install_sh) are unused.
+mkinstalldirs = $(SHELL) @install_sh@ -d
+install_sh = $(SHELL) @install_sh@
+MKDIR_P = @MKDIR_P@
+mkdir_p = @mkdir_p@
+
+# When building gettext-tools, we prefer to use the built programs
+# rather than installed programs. However, we can't do that when we
+# are cross compiling.
+CROSS_COMPILING = @CROSS_COMPILING@
+
+GMSGFMT_ = @GMSGFMT@
+GMSGFMT_no = @GMSGFMT@
+GMSGFMT_yes = @GMSGFMT_015@
+GMSGFMT = $(GMSGFMT_$(USE_MSGCTXT))
+MSGFMT_ = @MSGFMT@
+MSGFMT_no = @MSGFMT@
+MSGFMT_yes = @MSGFMT_015@
+MSGFMT = $(MSGFMT_$(USE_MSGCTXT))
+XGETTEXT_ = @XGETTEXT@
+XGETTEXT_no = @XGETTEXT@
+XGETTEXT_yes = @XGETTEXT_015@
+XGETTEXT = $(XGETTEXT_$(USE_MSGCTXT))
+MSGMERGE = msgmerge
+MSGMERGE_UPDATE = @MSGMERGE@ --update
+MSGINIT = msginit
+MSGCONV = msgconv
+MSGFILTER = msgfilter
+
+POFILES = @POFILES@
+GMOFILES = @GMOFILES@
+UPDATEPOFILES = @UPDATEPOFILES@
+DUMMYPOFILES = @DUMMYPOFILES@
+DISTFILES.common = Makefile.in.in remove-potcdate.sin \
+$(DISTFILES.common.extra1) $(DISTFILES.common.extra2) $(DISTFILES.common.extra3)
+DISTFILES = $(DISTFILES.common) Makevars POTFILES.in \
+$(POFILES) $(GMOFILES) \
+$(DISTFILES.extra1) $(DISTFILES.extra2) $(DISTFILES.extra3)
+
+POTFILES = \
+
+CATALOGS = @CATALOGS@
+
+POFILESDEPS_ = $(srcdir)/$(DOMAIN).pot
+POFILESDEPS_yes = $(POFILESDEPS_)
+POFILESDEPS_no =
+POFILESDEPS = $(POFILESDEPS_$(PO_DEPENDS_ON_POT))
+
+DISTFILESDEPS_ = update-po
+DISTFILESDEPS_yes = $(DISTFILESDEPS_)
+DISTFILESDEPS_no =
+DISTFILESDEPS = $(DISTFILESDEPS_$(DIST_DEPENDS_ON_UPDATE_PO))
+
+# Makevars gets inserted here. (Don't remove this line!)
+
+.SUFFIXES:
+.SUFFIXES: .po .gmo .mo .sed .sin .nop .po-create .po-update
+
+.po.mo:
+ @echo "$(MSGFMT) -c -o $@ $<"; \
+ $(MSGFMT) -c -o t-$@ $< && mv t-$@ $@
+
+.po.gmo:
+ @lang=`echo $* | sed -e 's,.*/,,'`; \
+ test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
+ echo "$${cdcmd}rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics --verbose -o $${lang}.gmo $${lang}.po"; \
+ cd $(srcdir) && rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics --verbose -o t-$${lang}.gmo $${lang}.po && mv t-$${lang}.gmo $${lang}.gmo
+
+.sin.sed:
+ sed -e '/^#/d' $< > t-$@
+ mv t-$@ $@
+
+
+all: all-@USE_NLS@
+
+all-yes: stamp-po
+all-no:
+
+# Ensure that the gettext macros and this Makefile.in.in are in sync.
+CHECK_MACRO_VERSION = \
+ test "$(GETTEXT_MACRO_VERSION)" = "@GETTEXT_MACRO_VERSION@" \
+ || { echo "*** error: gettext infrastructure mismatch: using a Makefile.in.in from gettext version $(GETTEXT_MACRO_VERSION) but the autoconf macros are from gettext version @GETTEXT_MACRO_VERSION@" 1>&2; \
+ exit 1; \
+ }
+
+# $(srcdir)/$(DOMAIN).pot is only created when needed. When xgettext finds no
+# internationalized messages, no $(srcdir)/$(DOMAIN).pot is created (because
+# we don't want to bother translators with empty POT files). We assume that
+# LINGUAS is empty in this case, i.e. $(POFILES) and $(GMOFILES) are empty.
+# In this case, stamp-po is a nop (i.e. a phony target).
+
+# stamp-po is a timestamp denoting the last time at which the CATALOGS have
+# been loosely updated. Its purpose is that when a developer or translator
+# checks out the package via CVS, and the $(DOMAIN).pot file is not in CVS,
+# "make" will update the $(DOMAIN).pot and the $(CATALOGS), but subsequent
+# invocations of "make" will do nothing. This timestamp would not be necessary
+# if updating the $(CATALOGS) would always touch them; however, the rule for
+# $(POFILES) has been designed to not touch files that don't need to be
+# changed.
+stamp-po: $(srcdir)/$(DOMAIN).pot
+ @$(CHECK_MACRO_VERSION)
+ test ! -f $(srcdir)/$(DOMAIN).pot || \
+ test -z "$(GMOFILES)" || $(MAKE) $(GMOFILES)
+ @test ! -f $(srcdir)/$(DOMAIN).pot || { \
+ echo "touch stamp-po" && \
+ echo timestamp > stamp-poT && \
+ mv stamp-poT stamp-po; \
+ }
+
+# Note: Target 'all' must not depend on target '$(DOMAIN).pot-update',
+# otherwise packages like GCC can not be built if only parts of the source
+# have been downloaded.
+
+# This target rebuilds $(DOMAIN).pot; it is an expensive operation.
+# Note that $(DOMAIN).pot is not touched if it doesn't need to be changed.
+# The determination of whether the package xyz is a GNU one is based on the
+# heuristic whether some file in the top level directory mentions "GNU xyz".
+# If GNU 'find' is available, we avoid grepping through monster files.
+$(DOMAIN).pot-update: $(POTFILES) $(srcdir)/POTFILES.in remove-potcdate.sed
+ package_gnu="$(PACKAGE_GNU)"; \
+ test -n "$$package_gnu" || { \
+ if { if (LC_ALL=C find --version) 2>/dev/null | grep GNU >/dev/null; then \
+ LC_ALL=C find -L $(top_srcdir) -maxdepth 1 -type f \
+ -size -10000000c -exec grep 'GNU @PACKAGE@' \
+ /dev/null '{}' ';' 2>/dev/null; \
+ else \
+ LC_ALL=C grep 'GNU @PACKAGE@' $(top_srcdir)/* 2>/dev/null; \
+ fi; \
+ } | grep -v 'libtool:' >/dev/null; then \
+ package_gnu=yes; \
+ else \
+ package_gnu=no; \
+ fi; \
+ }; \
+ if test "$$package_gnu" = "yes"; then \
+ package_prefix='GNU '; \
+ else \
+ package_prefix=''; \
+ fi; \
+ if test -n '$(MSGID_BUGS_ADDRESS)' || test '$(PACKAGE_BUGREPORT)' = '@'PACKAGE_BUGREPORT'@'; then \
+ msgid_bugs_address='$(MSGID_BUGS_ADDRESS)'; \
+ else \
+ msgid_bugs_address='$(PACKAGE_BUGREPORT)'; \
+ fi; \
+ case `$(XGETTEXT) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
+ '' | 0.[0-9] | 0.[0-9].* | 0.1[0-5] | 0.1[0-5].* | 0.16 | 0.16.[0-1]*) \
+ $(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \
+ --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) @XGETTEXT_EXTRA_OPTIONS@ \
+ --files-from=$(srcdir)/POTFILES.in \
+ --copyright-holder='$(COPYRIGHT_HOLDER)' \
+ --msgid-bugs-address="$$msgid_bugs_address" \
+ ;; \
+ *) \
+ $(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \
+ --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) @XGETTEXT_EXTRA_OPTIONS@ \
+ --files-from=$(srcdir)/POTFILES.in \
+ --copyright-holder='$(COPYRIGHT_HOLDER)' \
+ --package-name="$${package_prefix}@PACKAGE@" \
+ --package-version='@VERSION@' \
+ --msgid-bugs-address="$$msgid_bugs_address" \
+ ;; \
+ esac
+ test ! -f $(DOMAIN).po || { \
+ if test -f $(srcdir)/$(DOMAIN).pot-header; then \
+ sed -e '1,/^#$$/d' < $(DOMAIN).po > $(DOMAIN).1po && \
+ cat $(srcdir)/$(DOMAIN).pot-header $(DOMAIN).1po > $(DOMAIN).po; \
+ rm -f $(DOMAIN).1po; \
+ fi; \
+ if test -f $(srcdir)/$(DOMAIN).pot; then \
+ sed -f remove-potcdate.sed < $(srcdir)/$(DOMAIN).pot > $(DOMAIN).1po && \
+ sed -f remove-potcdate.sed < $(DOMAIN).po > $(DOMAIN).2po && \
+ if cmp $(DOMAIN).1po $(DOMAIN).2po >/dev/null 2>&1; then \
+ rm -f $(DOMAIN).1po $(DOMAIN).2po $(DOMAIN).po; \
+ else \
+ rm -f $(DOMAIN).1po $(DOMAIN).2po $(srcdir)/$(DOMAIN).pot && \
+ mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \
+ fi; \
+ else \
+ mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \
+ fi; \
+ }
+
+# This rule has no dependencies: we don't need to update $(DOMAIN).pot at
+# every "make" invocation, only create it when it is missing.
+# Only "make $(DOMAIN).pot-update" or "make dist" will force an update.
+$(srcdir)/$(DOMAIN).pot:
+ $(MAKE) $(DOMAIN).pot-update
+
+# This target rebuilds a PO file if $(DOMAIN).pot has changed.
+# Note that a PO file is not touched if it doesn't need to be changed.
+$(POFILES): $(POFILESDEPS)
+ @lang=`echo $@ | sed -e 's,.*/,,' -e 's/\.po$$//'`; \
+ if test -f "$(srcdir)/$${lang}.po"; then \
+ test -f $(srcdir)/$(DOMAIN).pot || $(MAKE) $(srcdir)/$(DOMAIN).pot; \
+ test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
+ echo "$${cdcmd}$(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $${lang}.po $(DOMAIN).pot"; \
+ cd $(srcdir) \
+ && { case `$(MSGMERGE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
+ '' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \
+ $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) $${lang}.po $(DOMAIN).pot;; \
+ *) \
+ $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $${lang}.po $(DOMAIN).pot;; \
+ esac; \
+ }; \
+ else \
+ $(MAKE) $${lang}.po-create; \
+ fi
+
+
+install: install-exec install-data
+install-exec:
+install-data: install-data-@USE_NLS@
+ if test "$(PACKAGE)" = "gettext-tools"; then \
+ $(mkdir_p) $(DESTDIR)$(gettextsrcdir); \
+ for file in $(DISTFILES.common) Makevars.template; do \
+ $(INSTALL_DATA) $(srcdir)/$$file \
+ $(DESTDIR)$(gettextsrcdir)/$$file; \
+ done; \
+ for file in Makevars; do \
+ rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \
+ done; \
+ else \
+ : ; \
+ fi
+install-data-no: all
+install-data-yes: all
+ @catalogs='$(CATALOGS)'; \
+ for cat in $$catalogs; do \
+ cat=`basename $$cat`; \
+ lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
+ dir=$(localedir)/$$lang/LC_MESSAGES; \
+ $(mkdir_p) $(DESTDIR)$$dir; \
+ if test -r $$cat; then realcat=$$cat; else realcat=$(srcdir)/$$cat; fi; \
+ $(INSTALL_DATA) $$realcat $(DESTDIR)$$dir/$(DOMAIN).mo; \
+ echo "installing $$realcat as $(DESTDIR)$$dir/$(DOMAIN).mo"; \
+ for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \
+ if test -n "$$lc"; then \
+ if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \
+ link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \
+ mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
+ mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
+ (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \
+ for file in *; do \
+ if test -f $$file; then \
+ ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \
+ fi; \
+ done); \
+ rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
+ else \
+ if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \
+ :; \
+ else \
+ rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \
+ mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
+ fi; \
+ fi; \
+ rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
+ ln -s ../LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \
+ ln $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \
+ cp -p $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
+ echo "installing $$realcat link as $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo"; \
+ fi; \
+ done; \
+ done
+
+install-strip: install
+
+installdirs: installdirs-exec installdirs-data
+installdirs-exec:
+installdirs-data: installdirs-data-@USE_NLS@
+ if test "$(PACKAGE)" = "gettext-tools"; then \
+ $(mkdir_p) $(DESTDIR)$(gettextsrcdir); \
+ else \
+ : ; \
+ fi
+installdirs-data-no:
+installdirs-data-yes:
+ @catalogs='$(CATALOGS)'; \
+ for cat in $$catalogs; do \
+ cat=`basename $$cat`; \
+ lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
+ dir=$(localedir)/$$lang/LC_MESSAGES; \
+ $(mkdir_p) $(DESTDIR)$$dir; \
+ for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \
+ if test -n "$$lc"; then \
+ if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \
+ link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \
+ mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
+ mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
+ (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \
+ for file in *; do \
+ if test -f $$file; then \
+ ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \
+ fi; \
+ done); \
+ rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
+ else \
+ if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \
+ :; \
+ else \
+ rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \
+ mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
+ fi; \
+ fi; \
+ fi; \
+ done; \
+ done
+
+# Define this as empty until I found a useful application.
+installcheck:
+
+uninstall: uninstall-exec uninstall-data
+uninstall-exec:
+uninstall-data: uninstall-data-@USE_NLS@
+ if test "$(PACKAGE)" = "gettext-tools"; then \
+ for file in $(DISTFILES.common) Makevars.template; do \
+ rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \
+ done; \
+ else \
+ : ; \
+ fi
+uninstall-data-no:
+uninstall-data-yes:
+ catalogs='$(CATALOGS)'; \
+ for cat in $$catalogs; do \
+ cat=`basename $$cat`; \
+ lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
+ for lc in LC_MESSAGES $(EXTRA_LOCALE_CATEGORIES); do \
+ rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
+ done; \
+ done
+
+check: all
+
+info dvi ps pdf html tags TAGS ctags CTAGS ID:
+
+mostlyclean:
+ rm -f remove-potcdate.sed
+ rm -f stamp-poT
+ rm -f core core.* $(DOMAIN).po $(DOMAIN).1po $(DOMAIN).2po *.new.po
+ rm -fr *.o
+
+clean: mostlyclean
+
+distclean: clean
+ rm -f Makefile Makefile.in POTFILES *.mo
+
+maintainer-clean: distclean
+ @echo "This command is intended for maintainers to use;"
+ @echo "it deletes files that may require special tools to rebuild."
+ rm -f stamp-po $(GMOFILES)
+
+distdir = $(top_builddir)/$(PACKAGE)-$(VERSION)/$(subdir)
+dist distdir:
+ test -z "$(DISTFILESDEPS)" || $(MAKE) $(DISTFILESDEPS)
+ @$(MAKE) dist2
+# This is a separate target because 'update-po' must be executed before.
+dist2: stamp-po $(DISTFILES)
+ dists="$(DISTFILES)"; \
+ if test "$(PACKAGE)" = "gettext-tools"; then \
+ dists="$$dists Makevars.template"; \
+ fi; \
+ if test -f $(srcdir)/$(DOMAIN).pot; then \
+ dists="$$dists $(DOMAIN).pot stamp-po"; \
+ fi; \
+ if test -f $(srcdir)/ChangeLog; then \
+ dists="$$dists ChangeLog"; \
+ fi; \
+ for i in 0 1 2 3 4 5 6 7 8 9; do \
+ if test -f $(srcdir)/ChangeLog.$$i; then \
+ dists="$$dists ChangeLog.$$i"; \
+ fi; \
+ done; \
+ if test -f $(srcdir)/LINGUAS; then dists="$$dists LINGUAS"; fi; \
+ for file in $$dists; do \
+ if test -f $$file; then \
+ cp -p $$file $(distdir) || exit 1; \
+ else \
+ cp -p $(srcdir)/$$file $(distdir) || exit 1; \
+ fi; \
+ done
+
+update-po: Makefile
+ $(MAKE) $(DOMAIN).pot-update
+ test -z "$(UPDATEPOFILES)" || $(MAKE) $(UPDATEPOFILES)
+ $(MAKE) update-gmo
+
+# General rule for creating PO files.
+
+.nop.po-create:
+ @lang=`echo $@ | sed -e 's/\.po-create$$//'`; \
+ echo "File $$lang.po does not exist. If you are a translator, you can create it through 'msginit'." 1>&2; \
+ exit 1
+
+# General rule for updating PO files.
+
+.nop.po-update:
+ @lang=`echo $@ | sed -e 's/\.po-update$$//'`; \
+ if test "$(PACKAGE)" = "gettext-tools" && test "$(CROSS_COMPILING)" != "yes"; then PATH=`pwd`/../src:$$PATH; fi; \
+ tmpdir=`pwd`; \
+ echo "$$lang:"; \
+ test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
+ echo "$${cdcmd}$(MSGMERGE) $(MSGMERGE_OPTIONS) --lang=$$lang $$lang.po $(DOMAIN).pot -o $$lang.new.po"; \
+ cd $(srcdir); \
+ if { case `$(MSGMERGE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
+ '' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \
+ $(MSGMERGE) $(MSGMERGE_OPTIONS) -o $$tmpdir/$$lang.new.po $$lang.po $(DOMAIN).pot;; \
+ *) \
+ $(MSGMERGE) $(MSGMERGE_OPTIONS) --lang=$$lang -o $$tmpdir/$$lang.new.po $$lang.po $(DOMAIN).pot;; \
+ esac; \
+ }; then \
+ if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \
+ rm -f $$tmpdir/$$lang.new.po; \
+ else \
+ if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \
+ :; \
+ else \
+ echo "msgmerge for $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \
+ exit 1; \
+ fi; \
+ fi; \
+ else \
+ echo "msgmerge for $$lang.po failed!" 1>&2; \
+ rm -f $$tmpdir/$$lang.new.po; \
+ fi
+
+$(DUMMYPOFILES):
+
+update-gmo: Makefile $(GMOFILES)
+ @:
+
+# Recreate Makefile by invoking config.status. Explicitly invoke the shell,
+# because execution permission bits may not work on the current file system.
+# Use @SHELL@, which is the shell determined by autoconf for the use by its
+# scripts, not $(SHELL) which is hardwired to /bin/sh and may be deficient.
+Makefile: Makefile.in.in Makevars $(top_builddir)/config.status @POMAKEFILEDEPS@
+ cd $(top_builddir) \
+ && @SHELL@ ./config.status $(subdir)/$@.in po-directories
+
+force:
+
+# Tell versions [3.59,3.63) of GNU make not to export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/po/Makevars b/po/Makevars
new file mode 100644
index 000000000..6c805d13a
--- /dev/null
+++ b/po/Makevars
@@ -0,0 +1,78 @@
+# Makefile variables for PO directory in any package using GNU gettext.
+
+# Usually the message domain is the same as the package name.
+DOMAIN = $(PACKAGE)
+
+# These two variables depend on the location of this directory.
+subdir = po
+top_builddir = ..
+
+# These options get passed to xgettext.
+XGETTEXT_OPTIONS = --keyword=_ --keyword=N_
+
+# This is the copyright holder that gets inserted into the header of the
+# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding
+# package. (Note that the msgstr strings, extracted from the package's
+# sources, belong to the copyright holder of the package.) Translators are
+# expected to transfer the copyright for their translations to this person
+# or entity, or to disclaim their copyright. The empty string stands for
+# the public domain; in this case the translators are expected to disclaim
+# their copyright.
+COPYRIGHT_HOLDER = Taler Systems SA
+
+# This tells whether or not to prepend "GNU " prefix to the package
+# name that gets inserted into the header of the $(DOMAIN).pot file.
+# Possible values are "yes", "no", or empty. If it is empty, try to
+# detect it automatically by scanning the files in $(top_srcdir) for
+# "GNU packagename" string.
+PACKAGE_GNU = yes
+
+# This is the email address or URL to which the translators shall report
+# bugs in the untranslated strings:
+# - Strings which are not entire sentences, see the maintainer guidelines
+# in the GNU gettext documentation, section 'Preparing Strings'.
+# - Strings which use unclear terms or require additional context to be
+# understood.
+# - Strings which make invalid assumptions about notation of date, time or
+# money.
+# - Pluralisation problems.
+# - Incorrect English spelling.
+# - Incorrect formatting.
+# It can be your email address, or a mailing list address where translators
+# can write to without being subscribed, or the URL of a web page through
+# which the translators can contact you.
+MSGID_BUGS_ADDRESS = taler@gnu.org
+
+# This is the list of locale categories, beyond LC_MESSAGES, for which the
+# message catalogs shall be used. It is usually empty.
+EXTRA_LOCALE_CATEGORIES =
+
+# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt'
+# context. Possible values are "yes" and "no". Set this to yes if the
+# package uses functions taking also a message context, like pgettext(), or
+# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument.
+USE_MSGCTXT = no
+
+# These options get passed to msgmerge.
+# Useful options are in particular:
+# --previous to keep previous msgids of translated messages,
+# --quiet to reduce the verbosity.
+MSGMERGE_OPTIONS = --quiet
+
+# These options get passed to msginit.
+# If you want to disable line wrapping when writing PO files, add
+# --no-wrap to MSGMERGE_OPTIONS, XGETTEXT_OPTIONS, and
+# MSGINIT_OPTIONS.
+MSGINIT_OPTIONS =
+
+# This tells whether or not to regenerate a PO file when $(DOMAIN).pot
+# has changed. Possible values are "yes" and "no". Set this to no if
+# the POT file is checked in the repository and the version control
+# program ignores timestamps.
+PO_DEPENDS_ON_POT = yes
+
+# This tells whether or not to forcibly update $(DOMAIN).pot and
+# regenerate PO files on "make dist". Possible values are "yes" and
+# "no". Set this to no if the POT file and PO files are maintained
+# externally.
+DIST_DEPENDS_ON_UPDATE_PO = yes
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644
index 000000000..a38c16c27
--- /dev/null
+++ b/po/POTFILES.in
@@ -0,0 +1,2 @@
+# List of source files which contain translatable strings.
+src/util/taler_error_codes.c
diff --git a/po/Rules-quot b/po/Rules-quot
new file mode 100644
index 000000000..baf652858
--- /dev/null
+++ b/po/Rules-quot
@@ -0,0 +1,58 @@
+# This file, Rules-quot, can be copied and used freely without restrictions.
+# Special Makefile rules for English message catalogs with quotation marks.
+
+DISTFILES.common.extra1 = quot.sed boldquot.sed en@quot.header en@boldquot.header insert-header.sin Rules-quot
+
+.SUFFIXES: .insert-header .po-update-en
+
+en@quot.po-create:
+ $(MAKE) en@quot.po-update
+en@boldquot.po-create:
+ $(MAKE) en@boldquot.po-update
+
+en@quot.po-update: en@quot.po-update-en
+en@boldquot.po-update: en@boldquot.po-update-en
+
+.insert-header.po-update-en:
+ @lang=`echo $@ | sed -e 's/\.po-update-en$$//'`; \
+ if test "$(PACKAGE)" = "gettext-tools" && test "$(CROSS_COMPILING)" != "yes"; then PATH=`pwd`/../src:$$PATH; GETTEXTLIBDIR=`cd $(top_srcdir)/src && pwd`; export GETTEXTLIBDIR; fi; \
+ tmpdir=`pwd`; \
+ echo "$$lang:"; \
+ ll=`echo $$lang | sed -e 's/@.*//'`; \
+ LC_ALL=C; export LC_ALL; \
+ cd $(srcdir); \
+ if $(MSGINIT) $(MSGINIT_OPTIONS) -i $(DOMAIN).pot --no-translator -l $$lang -o - 2>/dev/null \
+ | $(SED) -f $$tmpdir/$$lang.insert-header | $(MSGCONV) -t UTF-8 | \
+ { case `$(MSGFILTER) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
+ '' | 0.[0-9] | 0.[0-9].* | 0.1[0-8] | 0.1[0-8].*) \
+ $(MSGFILTER) $(SED) -f `echo $$lang | sed -e 's/.*@//'`.sed \
+ ;; \
+ *) \
+ $(MSGFILTER) `echo $$lang | sed -e 's/.*@//'` \
+ ;; \
+ esac } 2>/dev/null > $$tmpdir/$$lang.new.po \
+ ; then \
+ if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \
+ rm -f $$tmpdir/$$lang.new.po; \
+ else \
+ if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \
+ :; \
+ else \
+ echo "creation of $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \
+ exit 1; \
+ fi; \
+ fi; \
+ else \
+ echo "creation of $$lang.po failed!" 1>&2; \
+ rm -f $$tmpdir/$$lang.new.po; \
+ fi
+
+en@quot.insert-header: insert-header.sin
+ sed -e '/^#/d' -e 's/HEADER/en@quot.header/g' $(srcdir)/insert-header.sin > en@quot.insert-header
+
+en@boldquot.insert-header: insert-header.sin
+ sed -e '/^#/d' -e 's/HEADER/en@boldquot.header/g' $(srcdir)/insert-header.sin > en@boldquot.insert-header
+
+mostlyclean: mostlyclean-quot
+mostlyclean-quot:
+ rm -f *.insert-header
diff --git a/po/boldquot.sed b/po/boldquot.sed
new file mode 100644
index 000000000..4b937aa51
--- /dev/null
+++ b/po/boldquot.sed
@@ -0,0 +1,10 @@
+s/"\([^"]*\)"/“\1â€/g
+s/`\([^`']*\)'/‘\1’/g
+s/ '\([^`']*\)' / ‘\1’ /g
+s/ '\([^`']*\)'$/ ‘\1’/g
+s/^'\([^`']*\)' /‘\1’ /g
+s/“â€/""/g
+s/“/“/g
+s/â€/â€/g
+s/‘/‘/g
+s/’/’/g
diff --git a/po/en@boldquot.header b/po/en@boldquot.header
new file mode 100644
index 000000000..fedb6a06d
--- /dev/null
+++ b/po/en@boldquot.header
@@ -0,0 +1,25 @@
+# All this catalog "translates" are quotation characters.
+# The msgids must be ASCII and therefore cannot contain real quotation
+# characters, only substitutes like grave accent (0x60), apostrophe (0x27)
+# and double quote (0x22). These substitutes look strange; see
+# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html
+#
+# This catalog translates grave accent (0x60) and apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019).
+# It also translates pairs of apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019)
+# and pairs of quotation mark (0x22) to
+# left double quotation mark (U+201C) and right double quotation mark (U+201D).
+#
+# When output to an UTF-8 terminal, the quotation characters appear perfectly.
+# When output to an ISO-8859-1 terminal, the single quotation marks are
+# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to
+# grave/acute accent (by libiconv), and the double quotation marks are
+# transliterated to 0x22.
+# When output to an ASCII terminal, the single quotation marks are
+# transliterated to apostrophes, and the double quotation marks are
+# transliterated to 0x22.
+#
+# This catalog furthermore displays the text between the quotation marks in
+# bold face, assuming the VT100/XTerm escape sequences.
+#
diff --git a/po/en@quot.header b/po/en@quot.header
new file mode 100644
index 000000000..a9647fc35
--- /dev/null
+++ b/po/en@quot.header
@@ -0,0 +1,22 @@
+# All this catalog "translates" are quotation characters.
+# The msgids must be ASCII and therefore cannot contain real quotation
+# characters, only substitutes like grave accent (0x60), apostrophe (0x27)
+# and double quote (0x22). These substitutes look strange; see
+# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html
+#
+# This catalog translates grave accent (0x60) and apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019).
+# It also translates pairs of apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019)
+# and pairs of quotation mark (0x22) to
+# left double quotation mark (U+201C) and right double quotation mark (U+201D).
+#
+# When output to an UTF-8 terminal, the quotation characters appear perfectly.
+# When output to an ISO-8859-1 terminal, the single quotation marks are
+# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to
+# grave/acute accent (by libiconv), and the double quotation marks are
+# transliterated to 0x22.
+# When output to an ASCII terminal, the single quotation marks are
+# transliterated to apostrophes, and the double quotation marks are
+# transliterated to 0x22.
+#
diff --git a/po/insert-header.sin b/po/insert-header.sin
new file mode 100644
index 000000000..b26de01f6
--- /dev/null
+++ b/po/insert-header.sin
@@ -0,0 +1,23 @@
+# Sed script that inserts the file called HEADER before the header entry.
+#
+# At each occurrence of a line starting with "msgid ", we execute the following
+# commands. At the first occurrence, insert the file. At the following
+# occurrences, do nothing. The distinction between the first and the following
+# occurrences is achieved by looking at the hold space.
+/^msgid /{
+x
+# Test if the hold space is empty.
+s/m/m/
+ta
+# Yes it was empty. First occurrence. Read the file.
+r HEADER
+# Output the file's contents by reading the next line. But don't lose the
+# current line while doing this.
+g
+N
+bb
+:a
+# The hold space was nonempty. Following occurrences. Do nothing.
+x
+:b
+}
diff --git a/po/quot.sed b/po/quot.sed
new file mode 100644
index 000000000..0122c4631
--- /dev/null
+++ b/po/quot.sed
@@ -0,0 +1,6 @@
+s/"\([^"]*\)"/“\1â€/g
+s/`\([^`']*\)'/‘\1’/g
+s/ '\([^`']*\)' / ‘\1’ /g
+s/ '\([^`']*\)'$/ ‘\1’/g
+s/^'\([^`']*\)' /‘\1’ /g
+s/“â€/""/g
diff --git a/po/remove-potcdate.sin b/po/remove-potcdate.sin
new file mode 100644
index 000000000..2436c49e7
--- /dev/null
+++ b/po/remove-potcdate.sin
@@ -0,0 +1,19 @@
+# Sed script that remove the POT-Creation-Date line in the header entry
+# from a POT file.
+#
+# The distinction between the first and the following occurrences of the
+# pattern is achieved by looking at the hold space.
+/^"POT-Creation-Date: .*"$/{
+x
+# Test if the hold space is empty.
+s/P/P/
+ta
+# Yes it was empty. First occurrence. Remove the line.
+g
+d
+bb
+:a
+# The hold space was nonempty. Following occurrences. Do nothing.
+x
+:b
+}
diff --git a/src/Makefile.am b/src/Makefile.am
index 4b07a1161..e10ecf8d8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -3,6 +3,9 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/include
if HAVE_POSTGRESQL
PQ_DIR = pq
endif
+if HAVE_SQLITE
+ SQ_DIR = sq
+endif
pkgcfgdir = $(prefix)/share/taler/config.d/
pkgcfg_DATA = \
@@ -15,15 +18,20 @@ SUBDIRS = \
include \
util \
json \
+ extensions \
curl \
$(PQ_DIR) \
+ $(SQ_DIR) \
mhd \
+ templating \
bank-lib \
exchangedb \
+ kyclogic \
exchange \
- exchange-tools \
auditordb \
auditor \
lib \
+ exchange-tools \
+ extensions/age_restriction \
testing \
benchmark
diff --git a/src/auditor/.gitignore b/src/auditor/.gitignore
index 7ad6c976f..11c875dc6 100644
--- a/src/auditor/.gitignore
+++ b/src/auditor/.gitignore
@@ -16,3 +16,13 @@ taler-helper-auditor-reserves
taler-helper-auditor-wire
generate-auditor-basedb-prod.conf
generate-auditor-basedb-revocation.conf
+revocation-tmp-*
+auditor-basedb.wdb
+taler-auditor-sync
+auditor-basedb.sqlite3
+taler-auditor-test.sqlite3
+libeufin-nexus.pid
+libeufin-sandbox.pid
+taler-helper-auditor-purses
+generate-kyc-basedb.conf.edited
+generate-auditor-basedb.conf.edited
diff --git a/src/auditor/Makefile.am b/src/auditor/Makefile.am
index c0644cc2c..381c0b115 100644
--- a/src/auditor/Makefile.am
+++ b/src/auditor/Makefile.am
@@ -11,14 +11,17 @@ pkgcfgdir = $(prefix)/share/taler/config.d/
pkgcfg_DATA = \
auditor.conf
+clean-local:
+ rm -rf revocation-tmp-* taler-auditor
+
bin_PROGRAMS = \
taler-auditor-dbinit \
- taler-auditor-exchange \
taler-auditor-httpd \
- taler-auditor-sign \
+ taler-auditor-sync \
taler-helper-auditor-aggregation \
taler-helper-auditor-coins \
taler-helper-auditor-deposits \
+ taler-helper-auditor-purses \
taler-helper-auditor-reserves \
taler-helper-auditor-wire
@@ -26,7 +29,7 @@ bin_SCRIPTS = \
taler-auditor \
taler-helper-auditor-render.py
-edit_script = $(SED) -e 's,%pkgdatadir%,$(pkgdatadir),'g $(NULL)
+edit_script = $(SED) -e 's,%datadir%,$(datadir),'g $(NULL)
taler-auditor: taler-auditor.in
rm -f $@ $@.tmp && \
@@ -44,26 +47,27 @@ libauditorreport_la_LIBADD = \
$(top_builddir)/src/pq/libtalerpq.la \
$(top_builddir)/src/auditordb/libtalerauditordb.la \
$(top_builddir)/src/exchangedb/libtalerexchangedb.la \
- -lgnunetutil $(XLIB)
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
taler_auditor_dbinit_SOURCES = \
taler-auditor-dbinit.c
taler_auditor_dbinit_LDADD = \
- $(LIBGCRYPT_LIBS) \
- $(top_builddir)/src/util/libtalerutil.la \
- $(top_builddir)/src/pq/libtalerpq.la \
$(top_builddir)/src/auditordb/libtalerauditordb.la \
- -lgnunetutil $(XLIB)
-taler_auditor_dbinit_LDFLAGS = \
- $(POSTGRESQL_LDFLAGS)
+ $(top_builddir)/src/pq/libtalerpq.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetutil \
+ $(LIBGCRYPT_LIBS) \
+ $(XLIB)
taler_auditor_dbinit_CPPFLAGS = \
-I$(top_srcdir)/src/include \
-I$(top_srcdir)/src/pq/ \
$(POSTGRESQL_CPPFLAGS)
-taler_helper_auditor_reserves_SOURCES = \
- taler-helper-auditor-reserves.c
-taler_helper_auditor_reserves_LDADD = \
+taler_helper_auditor_coins_SOURCES = \
+ taler-helper-auditor-coins.c
+taler_helper_auditor_coins_LDADD = \
$(LIBGCRYPT_LIBS) \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/json/libtalerjson.la \
@@ -73,11 +77,12 @@ taler_helper_auditor_reserves_LDADD = \
libauditorreport.la \
-ljansson \
-lgnunetjson \
- -lgnunetutil
+ -lgnunetutil \
+ $(XLIB)
-taler_helper_auditor_coins_SOURCES = \
- taler-helper-auditor-coins.c
-taler_helper_auditor_coins_LDADD = \
+taler_helper_auditor_aggregation_SOURCES = \
+ taler-helper-auditor-aggregation.c
+taler_helper_auditor_aggregation_LDADD = \
$(LIBGCRYPT_LIBS) \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/json/libtalerjson.la \
@@ -87,11 +92,12 @@ taler_helper_auditor_coins_LDADD = \
libauditorreport.la \
-ljansson \
-lgnunetjson \
- -lgnunetutil
+ -lgnunetutil \
+ $(XLIB)
-taler_helper_auditor_aggregation_SOURCES = \
- taler-helper-auditor-aggregation.c
-taler_helper_auditor_aggregation_LDADD = \
+taler_helper_auditor_deposits_SOURCES = \
+ taler-helper-auditor-deposits.c
+taler_helper_auditor_deposits_LDADD = \
$(LIBGCRYPT_LIBS) \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/json/libtalerjson.la \
@@ -101,11 +107,12 @@ taler_helper_auditor_aggregation_LDADD = \
libauditorreport.la \
-ljansson \
-lgnunetjson \
- -lgnunetutil
+ -lgnunetutil \
+ $(XLIB)
-taler_helper_auditor_deposits_SOURCES = \
- taler-helper-auditor-deposits.c
-taler_helper_auditor_deposits_LDADD = \
+taler_helper_auditor_purses_SOURCES = \
+ taler-helper-auditor-purses.c
+taler_helper_auditor_purses_LDADD = \
$(LIBGCRYPT_LIBS) \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/json/libtalerjson.la \
@@ -115,14 +122,32 @@ taler_helper_auditor_deposits_LDADD = \
libauditorreport.la \
-ljansson \
-lgnunetjson \
- -lgnunetutil
+ -lgnunetutil \
+ $(XLIB)
+
+taler_helper_auditor_reserves_SOURCES = \
+ taler-helper-auditor-reserves.c
+taler_helper_auditor_reserves_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+ $(top_builddir)/src/auditordb/libtalerauditordb.la \
+ libauditorreport.la \
+ -ljansson \
+ -lgnunetjson \
+ -lgnunetutil \
+ $(XLIB)
+
+
taler_helper_auditor_wire_SOURCES = \
taler-helper-auditor-wire.c
taler_helper_auditor_wire_LDADD = \
$(LIBGCRYPT_LIBS) \
- $(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/bank-lib/libtalerbank.la \
$(top_builddir)/src/exchangedb/libtalerexchangedb.la \
$(top_builddir)/src/auditordb/libtalerauditordb.la \
@@ -130,13 +155,14 @@ taler_helper_auditor_wire_LDADD = \
-ljansson \
-lgnunetjson \
-lgnunetcurl \
- -lgnunetutil
+ -lgnunetutil \
+ $(XLIB)
taler_auditor_httpd_SOURCES = \
taler-auditor-httpd.c taler-auditor-httpd.h \
taler-auditor-httpd_deposit-confirmation.c taler-auditor-httpd_deposit-confirmation.h \
- taler-auditor-httpd_exchanges.c taler-auditor-httpd_exchanges.h \
+ taler-auditor-httpd_deposit-confirmation-get.c taler-auditor-httpd_deposit-confirmation-get.h \
taler-auditor-httpd_mhd.c taler-auditor-httpd_mhd.h
taler_auditor_httpd_LDADD = \
$(LIBGCRYPT_LIBS) \
@@ -144,52 +170,49 @@ taler_auditor_httpd_LDADD = \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/auditordb/libtalerauditordb.la \
+ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
-lmicrohttpd \
-ljansson \
-lgnunetjson \
-lgnunetutil \
- -lz
+ -lz \
+ $(XLIB)
-taler_auditor_sign_SOURCES = \
- taler-auditor-sign.c
-taler_auditor_sign_LDADD = \
- $(LIBGCRYPT_LIBS) \
- $(top_builddir)/src/util/libtalerutil.la \
- $(top_builddir)/src/auditordb/libtalerauditordb.la \
+taler_auditor_sync_SOURCES = \
+ taler-auditor-sync.c
+taler_auditor_sync_LDADD = \
$(top_builddir)/src/exchangedb/libtalerexchangedb.la \
- -lgnunetutil $(XLIB)
-
-
-taler_auditor_exchange_SOURCES = \
- taler-auditor-exchange.c
-taler_auditor_exchange_LDADD = \
- $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ $(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
- $(top_builddir)/src/auditordb/libtalerauditordb.la \
- -lgnunetutil $(XLIB)
+ -lgnunetjson \
+ -lgnunetutil \
+ $(LIBGCRYPT_LIBS) \
+ $(XLIB)
+taler_auditor_sync_CPPFLAGS = \
+ -I$(top_srcdir)/src/include \
+ -I$(top_srcdir)/src/pq/ \
+ $(POSTGRESQL_CPPFLAGS)
+
check_SCRIPTS = \
test-auditor.sh \
- test-revocation.sh
+ test-kyc.sh \
+ test-revocation.sh \
+ test-sync.sh
.NOTPARALLEL:
-TESTS = $(check_SCRIPTS)
+# TESTS = $(check_SCRIPTS)
EXTRA_DIST = \
taler-auditor.in \
taler-helper-auditor-render.py \
auditor.conf \
- test-auditor.conf \
+ setup.sh \
+ test-sync-in.conf \
+ test-sync-out.conf \
generate-auditor-basedb.sh \
- generate-revoke-basedb.sh \
generate-auditor-basedb.conf \
- generate-auditor-basedb-template.conf \
- $(check_SCRIPTS) \
- auditor-basedb.age \
- auditor-basedb.sql \
- auditor-basedb.fees \
- auditor-basedb.mpub \
- revoke-basedb.age \
- revoke-basedb.sql \
- revoke-basedb.fees \
- revoke-basedb.mpub
+ generate-kyc-basedb.conf \
+ generate-revoke-basedb.sh \
+ $(check_SCRIPTS)
diff --git a/src/auditor/auditor-basedb.age b/src/auditor/auditor-basedb.age
deleted file mode 100644
index 43f81861d..000000000
--- a/src/auditor/auditor-basedb.age
+++ /dev/null
@@ -1 +0,0 @@
-1584124548
diff --git a/src/auditor/auditor-basedb.fees b/src/auditor/auditor-basedb.fees
deleted file mode 100644
index c3fe16373..000000000
--- a/src/auditor/auditor-basedb.fees
+++ /dev/null
Binary files differ
diff --git a/src/auditor/auditor-basedb.mpub b/src/auditor/auditor-basedb.mpub
deleted file mode 100644
index 0ed9694fe..000000000
--- a/src/auditor/auditor-basedb.mpub
+++ /dev/null
@@ -1 +0,0 @@
-BX3MKH0E1YPF03P1T2G4NKMYNHBGE1TC2N5P6RWHT1JHNXC32EN0
diff --git a/src/auditor/auditor-basedb.sql b/src/auditor/auditor-basedb.sql
deleted file mode 100644
index 4e5925937..000000000
--- a/src/auditor/auditor-basedb.sql
+++ /dev/null
@@ -1,4881 +0,0 @@
---
--- PostgreSQL database dump
---
-
--- Dumped from database version 10.5 (Debian 10.5-1)
--- Dumped by pg_dump version 10.5 (Debian 10.5-1)
-
-SET statement_timeout = 0;
-SET lock_timeout = 0;
-SET idle_in_transaction_session_timeout = 0;
-SET client_encoding = 'UTF8';
-SET standard_conforming_strings = on;
-SELECT pg_catalog.set_config('search_path', '', false);
-SET check_function_bodies = false;
-SET client_min_messages = warning;
-SET row_security = off;
-
---
--- Name: _v; Type: SCHEMA; Schema: -; Owner: -
---
-
-CREATE SCHEMA _v;
-
-
---
--- Name: SCHEMA _v; Type: COMMENT; Schema: -; Owner: -
---
-
-COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.';
-
-
---
--- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: -
---
-
-CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
-
-
---
--- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: -
---
-
-COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
-
-
---
--- Name: assert_patch_is_applied(text); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.assert_patch_is_applied(in_patch_name text) RETURNS text
- LANGUAGE plpgsql
- AS $$
-DECLARE
- t_text TEXT;
-BEGIN
- SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name;
- IF NOT FOUND THEN
- RAISE EXCEPTION 'Patch % is not applied!', in_patch_name;
- END IF;
- RETURN format('Patch %s is applied.', in_patch_name);
-END;
-$$;
-
-
---
--- Name: FUNCTION assert_patch_is_applied(in_patch_name text); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.assert_patch_is_applied(in_patch_name text) IS 'Function that can be used to make sure that patch has been applied.';
-
-
---
--- Name: assert_user_is_not_superuser(); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.assert_user_is_not_superuser() RETURNS text
- LANGUAGE plpgsql
- AS $$
-DECLARE
- v_super bool;
-BEGIN
- SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user;
- IF v_super THEN
- RAISE EXCEPTION 'Current user is superuser - cannot continue.';
- END IF;
- RETURN 'assert_user_is_not_superuser: OK';
-END;
-$$;
-
-
---
--- Name: FUNCTION assert_user_is_not_superuser(); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.assert_user_is_not_superuser() IS 'Function that can be used to make sure that patch is being applied using normal (not superuser) account.';
-
-
---
--- Name: assert_user_is_one_of(text[]); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.assert_user_is_one_of(VARIADIC p_acceptable_users text[]) RETURNS text
- LANGUAGE plpgsql
- AS $$
-DECLARE
-BEGIN
- IF current_user = any( p_acceptable_users ) THEN
- RETURN 'assert_user_is_one_of: OK';
- END IF;
- RAISE EXCEPTION 'User is not one of: % - cannot continue.', p_acceptable_users;
-END;
-$$;
-
-
---
--- Name: FUNCTION assert_user_is_one_of(VARIADIC p_acceptable_users text[]); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.assert_user_is_one_of(VARIADIC p_acceptable_users text[]) IS 'Function that can be used to make sure that patch is being applied by one of defined users.';
-
-
---
--- Name: assert_user_is_superuser(); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.assert_user_is_superuser() RETURNS text
- LANGUAGE plpgsql
- AS $$
-DECLARE
- v_super bool;
-BEGIN
- SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user;
- IF v_super THEN
- RETURN 'assert_user_is_superuser: OK';
- END IF;
- RAISE EXCEPTION 'Current user is not superuser - cannot continue.';
-END;
-$$;
-
-
---
--- Name: FUNCTION assert_user_is_superuser(); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.assert_user_is_superuser() IS 'Function that can be used to make sure that patch is being applied using superuser account.';
-
-
---
--- Name: register_patch(text); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.register_patch(text) RETURNS SETOF integer
- LANGUAGE sql
- AS $_$
- SELECT _v.register_patch( $1, NULL, NULL );
-$_$;
-
-
---
--- Name: FUNCTION register_patch(text); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.register_patch(text) IS 'Wrapper to allow registration of patches without requirements and conflicts.';
-
-
---
--- Name: register_patch(text, text[]); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.register_patch(text, text[]) RETURNS SETOF integer
- LANGUAGE sql
- AS $_$
- SELECT _v.register_patch( $1, $2, NULL );
-$_$;
-
-
---
--- Name: FUNCTION register_patch(text, text[]); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.register_patch(text, text[]) IS 'Wrapper to allow registration of patches without conflicts.';
-
-
---
--- Name: register_patch(text, text[], text[]); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.register_patch(in_patch_name text, in_requirements text[], in_conflicts text[], OUT versioning integer) RETURNS SETOF integer
- LANGUAGE plpgsql
- AS $$
-DECLARE
- t_text TEXT;
- t_text_a TEXT[];
- i INT4;
-BEGIN
- -- Thanks to this we know only one patch will be applied at a time
- LOCK TABLE _v.patches IN EXCLUSIVE MODE;
-
- SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name;
- IF FOUND THEN
- RAISE EXCEPTION 'Patch % is already applied!', in_patch_name;
- END IF;
-
- t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE patch_name = any( in_conflicts ) );
- IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
- RAISE EXCEPTION 'Versioning patches conflict. Conflicting patche(s) installed: %.', array_to_string( t_text_a, ', ' );
- END IF;
-
- IF array_upper( in_requirements, 1 ) IS NOT NULL THEN
- t_text_a := '{}';
- FOR i IN array_lower( in_requirements, 1 ) .. array_upper( in_requirements, 1 ) LOOP
- SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_requirements[i];
- IF NOT FOUND THEN
- t_text_a := t_text_a || in_requirements[i];
- END IF;
- END LOOP;
- IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
- RAISE EXCEPTION 'Missing prerequisite(s): %.', array_to_string( t_text_a, ', ' );
- END IF;
- END IF;
-
- INSERT INTO _v.patches (patch_name, applied_tsz, applied_by, requires, conflicts ) VALUES ( in_patch_name, now(), current_user, coalesce( in_requirements, '{}' ), coalesce( in_conflicts, '{}' ) );
- RETURN;
-END;
-$$;
-
-
---
--- Name: FUNCTION register_patch(in_patch_name text, in_requirements text[], in_conflicts text[], OUT versioning integer); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.register_patch(in_patch_name text, in_requirements text[], in_conflicts text[], OUT versioning integer) IS 'Function to register patches in database. Raises exception if there are conflicts, prerequisites are not installed or the migration has already been installed.';
-
-
---
--- Name: unregister_patch(text); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.unregister_patch(in_patch_name text, OUT versioning integer) RETURNS SETOF integer
- LANGUAGE plpgsql
- AS $$
-DECLARE
- i INT4;
- t_text_a TEXT[];
-BEGIN
- -- Thanks to this we know only one patch will be applied at a time
- LOCK TABLE _v.patches IN EXCLUSIVE MODE;
-
- t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE in_patch_name = ANY( requires ) );
- IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
- RAISE EXCEPTION 'Cannot uninstall %, as it is required by: %.', in_patch_name, array_to_string( t_text_a, ', ' );
- END IF;
-
- DELETE FROM _v.patches WHERE patch_name = in_patch_name;
- GET DIAGNOSTICS i = ROW_COUNT;
- IF i < 1 THEN
- RAISE EXCEPTION 'Patch % is not installed, so it can''t be uninstalled!', in_patch_name;
- END IF;
-
- RETURN;
-END;
-$$;
-
-
---
--- Name: FUNCTION unregister_patch(in_patch_name text, OUT versioning integer); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.unregister_patch(in_patch_name text, OUT versioning integer) IS 'Function to unregister patches in database. Dies if the patch is not registered, or if unregistering it would break dependencies.';
-
-
-SET default_tablespace = '';
-
-SET default_with_oids = false;
-
---
--- Name: patches; Type: TABLE; Schema: _v; Owner: -
---
-
-CREATE TABLE _v.patches (
- patch_name text NOT NULL,
- applied_tsz timestamp with time zone DEFAULT now() NOT NULL,
- applied_by text NOT NULL,
- requires text[],
- conflicts text[]
-);
-
-
---
--- Name: TABLE patches; Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON TABLE _v.patches IS 'Contains information about what patches are currently applied on database.';
-
-
---
--- Name: COLUMN patches.patch_name; Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON COLUMN _v.patches.patch_name IS 'Name of patch, has to be unique for every patch.';
-
-
---
--- Name: COLUMN patches.applied_tsz; Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON COLUMN _v.patches.applied_tsz IS 'When the patch was applied.';
-
-
---
--- Name: COLUMN patches.applied_by; Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON COLUMN _v.patches.applied_by IS 'Who applied this patch (PostgreSQL username)';
-
-
---
--- Name: COLUMN patches.requires; Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON COLUMN _v.patches.requires IS 'List of patches that are required for given patch.';
-
-
---
--- Name: COLUMN patches.conflicts; Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON COLUMN _v.patches.conflicts IS 'List of patches that conflict with given patch.';
-
-
---
--- Name: aggregation_tracking; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.aggregation_tracking (
- aggregation_serial_id bigint NOT NULL,
- deposit_serial_id bigint NOT NULL,
- wtid_raw bytea
-);
-
-
---
--- Name: TABLE aggregation_tracking; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.aggregation_tracking IS 'mapping from wire transfer identifiers (WTID) to deposits (and back)';
-
-
---
--- Name: aggregation_tracking_aggregation_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.aggregation_tracking_aggregation_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: aggregation_tracking_aggregation_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.aggregation_tracking_aggregation_serial_id_seq OWNED BY public.aggregation_tracking.aggregation_serial_id;
-
-
---
--- Name: app_bankaccount; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.app_bankaccount (
- is_public boolean NOT NULL,
- account_no integer NOT NULL,
- balance character varying NOT NULL,
- user_id integer NOT NULL
-);
-
-
---
--- Name: app_bankaccount_account_no_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.app_bankaccount_account_no_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: app_bankaccount_account_no_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.app_bankaccount_account_no_seq OWNED BY public.app_bankaccount.account_no;
-
-
---
--- Name: app_banktransaction; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.app_banktransaction (
- id integer NOT NULL,
- amount character varying NOT NULL,
- subject character varying(200) NOT NULL,
- date timestamp with time zone NOT NULL,
- cancelled boolean NOT NULL,
- request_uid character varying(128) NOT NULL,
- credit_account_id integer NOT NULL,
- debit_account_id integer NOT NULL
-);
-
-
---
--- Name: app_banktransaction_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.app_banktransaction_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: app_banktransaction_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.app_banktransaction_id_seq OWNED BY public.app_banktransaction.id;
-
-
---
--- Name: app_talerwithdrawoperation; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.app_talerwithdrawoperation (
- withdraw_id uuid NOT NULL,
- amount character varying NOT NULL,
- selection_done boolean NOT NULL,
- confirmation_done boolean NOT NULL,
- aborted boolean NOT NULL,
- selected_reserve_pub text,
- selected_exchange_account_id integer,
- withdraw_account_id integer NOT NULL
-);
-
-
---
--- Name: auditor_balance_summary; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_balance_summary (
- master_pub bytea,
- denom_balance_val bigint NOT NULL,
- denom_balance_frac integer NOT NULL,
- deposit_fee_balance_val bigint NOT NULL,
- deposit_fee_balance_frac integer NOT NULL,
- melt_fee_balance_val bigint NOT NULL,
- melt_fee_balance_frac integer NOT NULL,
- refund_fee_balance_val bigint NOT NULL,
- refund_fee_balance_frac integer NOT NULL,
- risk_val bigint NOT NULL,
- risk_frac integer NOT NULL,
- loss_val bigint NOT NULL,
- loss_frac integer NOT NULL,
- irregular_recoup_val bigint NOT NULL,
- irregular_recoup_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE auditor_balance_summary; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_balance_summary IS 'the sum of the outstanding coins from auditor_denomination_pending (denom_pubs must belong to the respectives exchange master public key); it represents the auditor_balance_summary of the exchange at this point (modulo unexpected historic_loss-style events where denomination keys are compromised)';
-
-
---
--- Name: auditor_denomination_pending; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_denomination_pending (
- denom_pub_hash bytea NOT NULL,
- denom_balance_val bigint NOT NULL,
- denom_balance_frac integer NOT NULL,
- denom_loss_val bigint NOT NULL,
- denom_loss_frac integer NOT NULL,
- num_issued bigint NOT NULL,
- denom_risk_val bigint NOT NULL,
- denom_risk_frac integer NOT NULL,
- recoup_loss_val bigint NOT NULL,
- recoup_loss_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE auditor_denomination_pending; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_denomination_pending IS 'outstanding denomination coins that the exchange is aware of and what the respective balances are (outstanding as well as issued overall which implies the maximum value at risk).';
-
-
---
--- Name: COLUMN auditor_denomination_pending.num_issued; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_denomination_pending.num_issued IS 'counts the number of coins issued (withdraw, refresh) of this denomination';
-
-
---
--- Name: COLUMN auditor_denomination_pending.denom_risk_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_denomination_pending.denom_risk_val IS 'amount that could theoretically be lost in the future due to recoup operations';
-
-
---
--- Name: COLUMN auditor_denomination_pending.recoup_loss_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_denomination_pending.recoup_loss_val IS 'amount actually lost due to recoup operations past revocation';
-
-
---
--- Name: auditor_denominations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_denominations (
- denom_pub_hash bytea NOT NULL,
- master_pub bytea,
- valid_from bigint NOT NULL,
- expire_withdraw bigint NOT NULL,
- expire_deposit bigint NOT NULL,
- expire_legal bigint NOT NULL,
- coin_val bigint NOT NULL,
- coin_frac integer NOT NULL,
- fee_withdraw_val bigint NOT NULL,
- fee_withdraw_frac integer NOT NULL,
- fee_deposit_val bigint NOT NULL,
- fee_deposit_frac integer NOT NULL,
- fee_refresh_val bigint NOT NULL,
- fee_refresh_frac integer NOT NULL,
- fee_refund_val bigint NOT NULL,
- fee_refund_frac integer NOT NULL,
- CONSTRAINT auditor_denominations_denom_pub_hash_check CHECK ((length(denom_pub_hash) = 64))
-);
-
-
---
--- Name: TABLE auditor_denominations; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_denominations IS 'denomination keys the auditor is aware of';
-
-
---
--- Name: auditor_exchange_signkeys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_exchange_signkeys (
- master_pub bytea,
- ep_start bigint NOT NULL,
- ep_expire bigint NOT NULL,
- ep_end bigint NOT NULL,
- exchange_pub bytea NOT NULL,
- master_sig bytea NOT NULL,
- CONSTRAINT auditor_exchange_signkeys_exchange_pub_check CHECK ((length(exchange_pub) = 32)),
- CONSTRAINT auditor_exchange_signkeys_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE auditor_exchange_signkeys; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_exchange_signkeys IS 'list of the online signing keys of exchanges we are auditing';
-
-
---
--- Name: auditor_exchanges; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_exchanges (
- master_pub bytea NOT NULL,
- exchange_url character varying NOT NULL,
- CONSTRAINT auditor_exchanges_master_pub_check CHECK ((length(master_pub) = 32))
-);
-
-
---
--- Name: TABLE auditor_exchanges; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_exchanges IS 'list of the exchanges we are auditing';
-
-
---
--- Name: auditor_historic_denomination_revenue; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_historic_denomination_revenue (
- master_pub bytea,
- denom_pub_hash bytea NOT NULL,
- revenue_timestamp bigint NOT NULL,
- revenue_balance_val bigint NOT NULL,
- revenue_balance_frac integer NOT NULL,
- loss_balance_val bigint NOT NULL,
- loss_balance_frac integer NOT NULL,
- CONSTRAINT auditor_historic_denomination_revenue_denom_pub_hash_check CHECK ((length(denom_pub_hash) = 64))
-);
-
-
---
--- Name: TABLE auditor_historic_denomination_revenue; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_historic_denomination_revenue IS 'Table with historic profits; basically, when a denom_pub has expired and everything associated with it is garbage collected, the final profits end up in here; note that the denom_pub here is not a foreign key, we just keep it as a reference point.';
-
-
---
--- Name: COLUMN auditor_historic_denomination_revenue.revenue_balance_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_historic_denomination_revenue.revenue_balance_val IS 'the sum of all of the profits we made on the coin except for withdraw fees (which are in historic_reserve_revenue); so this includes the deposit, melt and refund fees';
-
-
---
--- Name: auditor_historic_reserve_summary; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_historic_reserve_summary (
- master_pub bytea,
- start_date bigint NOT NULL,
- end_date bigint NOT NULL,
- reserve_profits_val bigint NOT NULL,
- reserve_profits_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE auditor_historic_reserve_summary; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_historic_reserve_summary IS 'historic profits from reserves; we eventually GC auditor_historic_reserve_revenue, and then store the totals in here (by time intervals).';
-
-
---
--- Name: auditor_predicted_result; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_predicted_result (
- master_pub bytea,
- balance_val bigint NOT NULL,
- balance_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE auditor_predicted_result; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_predicted_result IS 'Table with the sum of the ledger, auditor_historic_revenue and the auditor_reserve_balance. This is the final amount that the exchange should have in its bank account right now.';
-
-
---
--- Name: auditor_progress_aggregation; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_progress_aggregation (
- master_pub bytea NOT NULL,
- last_wire_out_serial_id bigint DEFAULT 0 NOT NULL
-);
-
-
---
--- Name: TABLE auditor_progress_aggregation; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_progress_aggregation IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
---
--- Name: auditor_progress_coin; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_progress_coin (
- master_pub bytea NOT NULL,
- last_withdraw_serial_id bigint DEFAULT 0 NOT NULL,
- last_deposit_serial_id bigint DEFAULT 0 NOT NULL,
- last_melt_serial_id bigint DEFAULT 0 NOT NULL,
- last_refund_serial_id bigint DEFAULT 0 NOT NULL,
- last_recoup_serial_id bigint DEFAULT 0 NOT NULL,
- last_recoup_refresh_serial_id bigint DEFAULT 0 NOT NULL
-);
-
-
---
--- Name: TABLE auditor_progress_coin; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_progress_coin IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
---
--- Name: auditor_progress_deposit_confirmation; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_progress_deposit_confirmation (
- master_pub bytea NOT NULL,
- last_deposit_confirmation_serial_id bigint DEFAULT 0 NOT NULL
-);
-
-
---
--- Name: TABLE auditor_progress_deposit_confirmation; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_progress_deposit_confirmation IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
---
--- Name: auditor_progress_reserve; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_progress_reserve (
- master_pub bytea NOT NULL,
- last_reserve_in_serial_id bigint DEFAULT 0 NOT NULL,
- last_reserve_out_serial_id bigint DEFAULT 0 NOT NULL,
- last_reserve_recoup_serial_id bigint DEFAULT 0 NOT NULL,
- last_reserve_close_serial_id bigint DEFAULT 0 NOT NULL
-);
-
-
---
--- Name: TABLE auditor_progress_reserve; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_progress_reserve IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
---
--- Name: auditor_reserve_balance; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_reserve_balance (
- master_pub bytea,
- reserve_balance_val bigint NOT NULL,
- reserve_balance_frac integer NOT NULL,
- withdraw_fee_balance_val bigint NOT NULL,
- withdraw_fee_balance_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE auditor_reserve_balance; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_reserve_balance IS 'sum of the balances of all customer reserves (by exchange master public key)';
-
-
---
--- Name: auditor_reserves; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_reserves (
- reserve_pub bytea NOT NULL,
- master_pub bytea,
- reserve_balance_val bigint NOT NULL,
- reserve_balance_frac integer NOT NULL,
- withdraw_fee_balance_val bigint NOT NULL,
- withdraw_fee_balance_frac integer NOT NULL,
- expiration_date bigint NOT NULL,
- auditor_reserves_rowid bigint NOT NULL,
- origin_account text,
- CONSTRAINT auditor_reserves_reserve_pub_check CHECK ((length(reserve_pub) = 32))
-);
-
-
---
--- Name: TABLE auditor_reserves; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_reserves IS 'all of the customer reserves and their respective balances that the auditor is aware of';
-
-
---
--- Name: auditor_reserves_auditor_reserves_rowid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auditor_reserves_auditor_reserves_rowid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auditor_reserves_auditor_reserves_rowid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auditor_reserves_auditor_reserves_rowid_seq OWNED BY public.auditor_reserves.auditor_reserves_rowid;
-
-
---
--- Name: auditor_wire_fee_balance; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_wire_fee_balance (
- master_pub bytea,
- wire_fee_balance_val bigint NOT NULL,
- wire_fee_balance_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE auditor_wire_fee_balance; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_wire_fee_balance IS 'sum of the balances of all wire fees (by exchange master public key)';
-
-
---
--- Name: auth_group; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auth_group (
- id integer NOT NULL,
- name character varying(150) NOT NULL
-);
-
-
---
--- Name: auth_group_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auth_group_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auth_group_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auth_group_id_seq OWNED BY public.auth_group.id;
-
-
---
--- Name: auth_group_permissions; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auth_group_permissions (
- id integer NOT NULL,
- group_id integer NOT NULL,
- permission_id integer NOT NULL
-);
-
-
---
--- Name: auth_group_permissions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auth_group_permissions_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auth_group_permissions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auth_group_permissions_id_seq OWNED BY public.auth_group_permissions.id;
-
-
---
--- Name: auth_permission; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auth_permission (
- id integer NOT NULL,
- name character varying(255) NOT NULL,
- content_type_id integer NOT NULL,
- codename character varying(100) NOT NULL
-);
-
-
---
--- Name: auth_permission_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auth_permission_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auth_permission_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auth_permission_id_seq OWNED BY public.auth_permission.id;
-
-
---
--- Name: auth_user; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auth_user (
- id integer NOT NULL,
- password character varying(128) NOT NULL,
- last_login timestamp with time zone,
- is_superuser boolean NOT NULL,
- username character varying(150) NOT NULL,
- first_name character varying(30) NOT NULL,
- last_name character varying(150) NOT NULL,
- email character varying(254) NOT NULL,
- is_staff boolean NOT NULL,
- is_active boolean NOT NULL,
- date_joined timestamp with time zone NOT NULL
-);
-
-
---
--- Name: auth_user_groups; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auth_user_groups (
- id integer NOT NULL,
- user_id integer NOT NULL,
- group_id integer NOT NULL
-);
-
-
---
--- Name: auth_user_groups_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auth_user_groups_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auth_user_groups_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auth_user_groups_id_seq OWNED BY public.auth_user_groups.id;
-
-
---
--- Name: auth_user_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auth_user_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auth_user_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auth_user_id_seq OWNED BY public.auth_user.id;
-
-
---
--- Name: auth_user_user_permissions; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auth_user_user_permissions (
- id integer NOT NULL,
- user_id integer NOT NULL,
- permission_id integer NOT NULL
-);
-
-
---
--- Name: auth_user_user_permissions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auth_user_user_permissions_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auth_user_user_permissions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auth_user_user_permissions_id_seq OWNED BY public.auth_user_user_permissions.id;
-
-
---
--- Name: denomination_revocations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.denomination_revocations (
- denom_revocations_serial_id bigint NOT NULL,
- denom_pub_hash bytea NOT NULL,
- master_sig bytea NOT NULL,
- CONSTRAINT denomination_revocations_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE denomination_revocations; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.denomination_revocations IS 'remembering which denomination keys have been revoked';
-
-
---
--- Name: denomination_revocations_denom_revocations_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.denomination_revocations_denom_revocations_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: denomination_revocations_denom_revocations_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.denomination_revocations_denom_revocations_serial_id_seq OWNED BY public.denomination_revocations.denom_revocations_serial_id;
-
-
---
--- Name: denominations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.denominations (
- denom_pub_hash bytea NOT NULL,
- denom_pub bytea NOT NULL,
- master_pub bytea NOT NULL,
- master_sig bytea NOT NULL,
- valid_from bigint NOT NULL,
- expire_withdraw bigint NOT NULL,
- expire_deposit bigint NOT NULL,
- expire_legal bigint NOT NULL,
- coin_val bigint NOT NULL,
- coin_frac integer NOT NULL,
- fee_withdraw_val bigint NOT NULL,
- fee_withdraw_frac integer NOT NULL,
- fee_deposit_val bigint NOT NULL,
- fee_deposit_frac integer NOT NULL,
- fee_refresh_val bigint NOT NULL,
- fee_refresh_frac integer NOT NULL,
- fee_refund_val bigint NOT NULL,
- fee_refund_frac integer NOT NULL,
- CONSTRAINT denominations_denom_pub_hash_check CHECK ((length(denom_pub_hash) = 64)),
- CONSTRAINT denominations_master_pub_check CHECK ((length(master_pub) = 32)),
- CONSTRAINT denominations_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE denominations; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.denominations IS 'Main denominations table. All the coins the exchange knows about.';
-
-
---
--- Name: deposit_confirmations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.deposit_confirmations (
- master_pub bytea,
- serial_id bigint NOT NULL,
- h_contract_terms bytea NOT NULL,
- h_wire bytea NOT NULL,
- "timestamp" bigint NOT NULL,
- refund_deadline bigint NOT NULL,
- amount_without_fee_val bigint NOT NULL,
- amount_without_fee_frac integer NOT NULL,
- coin_pub bytea NOT NULL,
- merchant_pub bytea NOT NULL,
- exchange_sig bytea NOT NULL,
- exchange_pub bytea NOT NULL,
- master_sig bytea NOT NULL,
- CONSTRAINT deposit_confirmations_coin_pub_check CHECK ((length(coin_pub) = 32)),
- CONSTRAINT deposit_confirmations_exchange_pub_check CHECK ((length(exchange_pub) = 32)),
- CONSTRAINT deposit_confirmations_exchange_sig_check CHECK ((length(exchange_sig) = 64)),
- CONSTRAINT deposit_confirmations_h_contract_terms_check CHECK ((length(h_contract_terms) = 64)),
- CONSTRAINT deposit_confirmations_h_wire_check CHECK ((length(h_wire) = 64)),
- CONSTRAINT deposit_confirmations_master_sig_check CHECK ((length(master_sig) = 64)),
- CONSTRAINT deposit_confirmations_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: TABLE deposit_confirmations; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.deposit_confirmations IS 'deposit confirmation sent to us by merchants; we must check that the exchange reported these properly.';
-
-
---
--- Name: deposit_confirmations_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.deposit_confirmations_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: deposit_confirmations_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.deposit_confirmations_serial_id_seq OWNED BY public.deposit_confirmations.serial_id;
-
-
---
--- Name: deposits; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.deposits (
- deposit_serial_id bigint NOT NULL,
- coin_pub bytea NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- "timestamp" bigint NOT NULL,
- refund_deadline bigint NOT NULL,
- wire_deadline bigint NOT NULL,
- merchant_pub bytea NOT NULL,
- h_contract_terms bytea NOT NULL,
- h_wire bytea NOT NULL,
- coin_sig bytea NOT NULL,
- wire text NOT NULL,
- tiny boolean DEFAULT false NOT NULL,
- done boolean DEFAULT false NOT NULL,
- CONSTRAINT deposits_coin_sig_check CHECK ((length(coin_sig) = 64)),
- CONSTRAINT deposits_h_contract_terms_check CHECK ((length(h_contract_terms) = 64)),
- CONSTRAINT deposits_h_wire_check CHECK ((length(h_wire) = 64)),
- CONSTRAINT deposits_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: TABLE deposits; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.deposits IS 'Deposits we have received and for which we need to make (aggregate) wire transfers (and manage refunds).';
-
-
---
--- Name: COLUMN deposits.tiny; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.tiny IS 'Set to TRUE if we decided that the amount is too small to ever trigger a wire transfer by itself (requires real aggregation)';
-
-
---
--- Name: COLUMN deposits.done; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.done IS 'Set to TRUE once we have included this deposit in some aggregate wire transfer to the merchant';
-
-
---
--- Name: deposits_deposit_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.deposits_deposit_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: deposits_deposit_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.deposits_deposit_serial_id_seq OWNED BY public.deposits.deposit_serial_id;
-
-
---
--- Name: django_content_type; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.django_content_type (
- id integer NOT NULL,
- app_label character varying(100) NOT NULL,
- model character varying(100) NOT NULL
-);
-
-
---
--- Name: django_content_type_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.django_content_type_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: django_content_type_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.django_content_type_id_seq OWNED BY public.django_content_type.id;
-
-
---
--- Name: django_migrations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.django_migrations (
- id integer NOT NULL,
- app character varying(255) NOT NULL,
- name character varying(255) NOT NULL,
- applied timestamp with time zone NOT NULL
-);
-
-
---
--- Name: django_migrations_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.django_migrations_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: django_migrations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.django_migrations_id_seq OWNED BY public.django_migrations.id;
-
-
---
--- Name: django_session; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.django_session (
- session_key character varying(40) NOT NULL,
- session_data text NOT NULL,
- expire_date timestamp with time zone NOT NULL
-);
-
-
---
--- Name: exchange_wire_fees; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.exchange_wire_fees (
- exchange_pub bytea NOT NULL,
- h_wire_method bytea NOT NULL,
- wire_fee_val bigint NOT NULL,
- wire_fee_frac integer NOT NULL,
- closing_fee_val bigint NOT NULL,
- closing_fee_frac integer NOT NULL,
- start_date bigint NOT NULL,
- end_date bigint NOT NULL,
- exchange_sig bytea NOT NULL,
- CONSTRAINT exchange_wire_fees_exchange_pub_check CHECK ((length(exchange_pub) = 32)),
- CONSTRAINT exchange_wire_fees_exchange_sig_check CHECK ((length(exchange_sig) = 64)),
- CONSTRAINT exchange_wire_fees_h_wire_method_check CHECK ((length(h_wire_method) = 64))
-);
-
-
---
--- Name: known_coins; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.known_coins (
- coin_pub bytea NOT NULL,
- denom_pub_hash bytea NOT NULL,
- denom_sig bytea NOT NULL,
- CONSTRAINT known_coins_coin_pub_check CHECK ((length(coin_pub) = 32))
-);
-
-
---
--- Name: TABLE known_coins; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.known_coins IS 'information about coins and their signatures, so we do not have to store the signatures more than once if a coin is involved in multiple operations';
-
-
---
--- Name: merchant_contract_terms; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_contract_terms (
- order_id character varying NOT NULL,
- merchant_pub bytea NOT NULL,
- contract_terms bytea NOT NULL,
- h_contract_terms bytea NOT NULL,
- "timestamp" bigint NOT NULL,
- row_id bigint NOT NULL,
- paid boolean DEFAULT false NOT NULL,
- CONSTRAINT merchant_contract_terms_h_contract_terms_check CHECK ((length(h_contract_terms) = 64)),
- CONSTRAINT merchant_contract_terms_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: merchant_contract_terms_row_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_contract_terms_row_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_contract_terms_row_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_contract_terms_row_id_seq OWNED BY public.merchant_contract_terms.row_id;
-
-
---
--- Name: merchant_deposits; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_deposits (
- h_contract_terms bytea NOT NULL,
- merchant_pub bytea NOT NULL,
- coin_pub bytea NOT NULL,
- exchange_url character varying NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- deposit_fee_val bigint NOT NULL,
- deposit_fee_frac integer NOT NULL,
- refund_fee_val bigint NOT NULL,
- refund_fee_frac integer NOT NULL,
- wire_fee_val bigint NOT NULL,
- wire_fee_frac integer NOT NULL,
- signkey_pub bytea NOT NULL,
- exchange_proof bytea NOT NULL,
- CONSTRAINT merchant_deposits_coin_pub_check CHECK ((length(coin_pub) = 32)),
- CONSTRAINT merchant_deposits_merchant_pub_check CHECK ((length(merchant_pub) = 32)),
- CONSTRAINT merchant_deposits_signkey_pub_check CHECK ((length(signkey_pub) = 32))
-);
-
-
---
--- Name: merchant_orders; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_orders (
- order_id character varying NOT NULL,
- merchant_pub bytea NOT NULL,
- contract_terms bytea NOT NULL,
- "timestamp" bigint NOT NULL,
- CONSTRAINT merchant_orders_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: merchant_proofs; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_proofs (
- exchange_url character varying NOT NULL,
- wtid bytea NOT NULL,
- execution_time bigint NOT NULL,
- signkey_pub bytea NOT NULL,
- proof bytea NOT NULL,
- CONSTRAINT merchant_proofs_signkey_pub_check CHECK ((length(signkey_pub) = 32)),
- CONSTRAINT merchant_proofs_wtid_check CHECK ((length(wtid) = 32))
-);
-
-
---
--- Name: merchant_refunds; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_refunds (
- rtransaction_id bigint NOT NULL,
- merchant_pub bytea NOT NULL,
- h_contract_terms bytea NOT NULL,
- coin_pub bytea NOT NULL,
- reason character varying NOT NULL,
- refund_amount_val bigint NOT NULL,
- refund_amount_frac integer NOT NULL,
- refund_fee_val bigint NOT NULL,
- refund_fee_frac integer NOT NULL,
- CONSTRAINT merchant_refunds_coin_pub_check CHECK ((length(coin_pub) = 32)),
- CONSTRAINT merchant_refunds_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: merchant_refunds_rtransaction_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_refunds_rtransaction_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_refunds_rtransaction_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_refunds_rtransaction_id_seq OWNED BY public.merchant_refunds.rtransaction_id;
-
-
---
--- Name: merchant_session_info; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_session_info (
- session_id character varying NOT NULL,
- fulfillment_url character varying NOT NULL,
- order_id character varying NOT NULL,
- merchant_pub bytea NOT NULL,
- "timestamp" bigint NOT NULL,
- CONSTRAINT merchant_session_info_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: merchant_tip_pickups; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_pickups (
- tip_id bytea NOT NULL,
- pickup_id bytea NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- CONSTRAINT merchant_tip_pickups_pickup_id_check CHECK ((length(pickup_id) = 64))
-);
-
-
---
--- Name: merchant_tip_reserve_credits; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_reserve_credits (
- reserve_priv bytea NOT NULL,
- credit_uuid bytea NOT NULL,
- "timestamp" bigint NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- CONSTRAINT merchant_tip_reserve_credits_credit_uuid_check CHECK ((length(credit_uuid) = 64)),
- CONSTRAINT merchant_tip_reserve_credits_reserve_priv_check CHECK ((length(reserve_priv) = 32))
-);
-
-
---
--- Name: merchant_tip_reserves; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_reserves (
- reserve_priv bytea NOT NULL,
- expiration bigint NOT NULL,
- balance_val bigint NOT NULL,
- balance_frac integer NOT NULL,
- CONSTRAINT merchant_tip_reserves_reserve_priv_check CHECK ((length(reserve_priv) = 32))
-);
-
-
---
--- Name: merchant_tips; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tips (
- reserve_priv bytea NOT NULL,
- tip_id bytea NOT NULL,
- exchange_url character varying NOT NULL,
- justification character varying NOT NULL,
- extra bytea NOT NULL,
- "timestamp" bigint NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- left_val bigint NOT NULL,
- left_frac integer NOT NULL,
- CONSTRAINT merchant_tips_reserve_priv_check CHECK ((length(reserve_priv) = 32)),
- CONSTRAINT merchant_tips_tip_id_check CHECK ((length(tip_id) = 64))
-);
-
-
---
--- Name: merchant_transfers; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_transfers (
- h_contract_terms bytea NOT NULL,
- coin_pub bytea NOT NULL,
- wtid bytea NOT NULL,
- CONSTRAINT merchant_transfers_coin_pub_check CHECK ((length(coin_pub) = 32)),
- CONSTRAINT merchant_transfers_wtid_check CHECK ((length(wtid) = 32))
-);
-
-
---
--- Name: prewire; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.prewire (
- prewire_uuid bigint NOT NULL,
- type text NOT NULL,
- finished boolean DEFAULT false NOT NULL,
- buf bytea NOT NULL
-);
-
-
---
--- Name: TABLE prewire; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.prewire IS 'pre-commit data for wire transfers we are about to execute';
-
-
---
--- Name: prewire_prewire_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.prewire_prewire_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: prewire_prewire_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.prewire_prewire_uuid_seq OWNED BY public.prewire.prewire_uuid;
-
-
---
--- Name: recoup; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.recoup (
- recoup_uuid bigint NOT NULL,
- coin_pub bytea NOT NULL,
- coin_sig bytea NOT NULL,
- coin_blind bytea NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- "timestamp" bigint NOT NULL,
- h_blind_ev bytea NOT NULL,
- CONSTRAINT recoup_coin_blind_check CHECK ((length(coin_blind) = 32)),
- CONSTRAINT recoup_coin_sig_check CHECK ((length(coin_sig) = 64))
-);
-
-
---
--- Name: TABLE recoup; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.recoup IS 'Information about recoups that were executed';
-
-
---
--- Name: COLUMN recoup.coin_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.recoup.coin_pub IS 'Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!';
-
-
---
--- Name: recoup_recoup_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.recoup_recoup_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: recoup_recoup_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.recoup_recoup_uuid_seq OWNED BY public.recoup.recoup_uuid;
-
-
---
--- Name: recoup_refresh; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.recoup_refresh (
- recoup_refresh_uuid bigint NOT NULL,
- coin_pub bytea NOT NULL,
- coin_sig bytea NOT NULL,
- coin_blind bytea NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- "timestamp" bigint NOT NULL,
- h_blind_ev bytea NOT NULL,
- CONSTRAINT recoup_refresh_coin_blind_check CHECK ((length(coin_blind) = 32)),
- CONSTRAINT recoup_refresh_coin_sig_check CHECK ((length(coin_sig) = 64))
-);
-
-
---
--- Name: COLUMN recoup_refresh.coin_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.recoup_refresh.coin_pub IS 'Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!';
-
-
---
--- Name: recoup_refresh_recoup_refresh_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.recoup_refresh_recoup_refresh_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: recoup_refresh_recoup_refresh_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.recoup_refresh_recoup_refresh_uuid_seq OWNED BY public.recoup_refresh.recoup_refresh_uuid;
-
-
---
--- Name: refresh_commitments; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.refresh_commitments (
- melt_serial_id bigint NOT NULL,
- rc bytea NOT NULL,
- old_coin_pub bytea NOT NULL,
- old_coin_sig bytea NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- noreveal_index integer NOT NULL,
- CONSTRAINT refresh_commitments_old_coin_sig_check CHECK ((length(old_coin_sig) = 64)),
- CONSTRAINT refresh_commitments_rc_check CHECK ((length(rc) = 64))
-);
-
-
---
--- Name: TABLE refresh_commitments; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.refresh_commitments IS 'Commitments made when melting coins and the gamma value chosen by the exchange.';
-
-
---
--- Name: refresh_commitments_melt_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.refresh_commitments_melt_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: refresh_commitments_melt_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.refresh_commitments_melt_serial_id_seq OWNED BY public.refresh_commitments.melt_serial_id;
-
-
---
--- Name: refresh_revealed_coins; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.refresh_revealed_coins (
- rc bytea NOT NULL,
- freshcoin_index integer NOT NULL,
- link_sig bytea NOT NULL,
- denom_pub_hash bytea NOT NULL,
- coin_ev bytea NOT NULL,
- h_coin_ev bytea NOT NULL,
- ev_sig bytea NOT NULL,
- CONSTRAINT refresh_revealed_coins_h_coin_ev_check CHECK ((length(h_coin_ev) = 64)),
- CONSTRAINT refresh_revealed_coins_link_sig_check CHECK ((length(link_sig) = 64))
-);
-
-
---
--- Name: TABLE refresh_revealed_coins; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.refresh_revealed_coins IS 'Revelations about the new coins that are to be created during a melting session.';
-
-
---
--- Name: COLUMN refresh_revealed_coins.rc; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.rc IS 'refresh commitment identifying the melt operation';
-
-
---
--- Name: COLUMN refresh_revealed_coins.freshcoin_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.freshcoin_index IS 'index of the fresh coin being created (one melt operation may result in multiple fresh coins)';
-
-
---
--- Name: COLUMN refresh_revealed_coins.coin_ev; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.coin_ev IS 'envelope of the new coin to be signed';
-
-
---
--- Name: COLUMN refresh_revealed_coins.h_coin_ev; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.h_coin_ev IS 'hash of the envelope of the new coin to be signed (for lookups)';
-
-
---
--- Name: COLUMN refresh_revealed_coins.ev_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.ev_sig IS 'exchange signature over the envelope';
-
-
---
--- Name: refresh_transfer_keys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.refresh_transfer_keys (
- rc bytea NOT NULL,
- transfer_pub bytea NOT NULL,
- transfer_privs bytea NOT NULL,
- CONSTRAINT refresh_transfer_keys_transfer_pub_check CHECK ((length(transfer_pub) = 32))
-);
-
-
---
--- Name: TABLE refresh_transfer_keys; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.refresh_transfer_keys IS 'Transfer keys of a refresh operation (the data revealed to the exchange).';
-
-
---
--- Name: COLUMN refresh_transfer_keys.rc; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_transfer_keys.rc IS 'refresh commitment identifying the melt operation';
-
-
---
--- Name: COLUMN refresh_transfer_keys.transfer_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_transfer_keys.transfer_pub IS 'transfer public key for the gamma index';
-
-
---
--- Name: COLUMN refresh_transfer_keys.transfer_privs; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_transfer_keys.transfer_privs IS 'array of TALER_CNC_KAPPA - 1 transfer private keys that have been revealed, with the gamma entry being skipped';
-
-
---
--- Name: refunds; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.refunds (
- refund_serial_id bigint NOT NULL,
- coin_pub bytea NOT NULL,
- merchant_pub bytea NOT NULL,
- merchant_sig bytea NOT NULL,
- h_contract_terms bytea NOT NULL,
- rtransaction_id bigint NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- CONSTRAINT refunds_h_contract_terms_check CHECK ((length(h_contract_terms) = 64)),
- CONSTRAINT refunds_merchant_pub_check CHECK ((length(merchant_pub) = 32)),
- CONSTRAINT refunds_merchant_sig_check CHECK ((length(merchant_sig) = 64))
-);
-
-
---
--- Name: TABLE refunds; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.refunds IS 'Data on coins that were refunded. Technically, refunds always apply against specific deposit operations involving a coin. The combination of coin_pub, merchant_pub, h_contract_terms and rtransaction_id MUST be unique, and we usually select by coin_pub so that one goes first.';
-
-
---
--- Name: COLUMN refunds.rtransaction_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refunds.rtransaction_id IS 'used by the merchant to make refunds unique in case the same coin for the same deposit gets a subsequent (higher) refund';
-
-
---
--- Name: refunds_refund_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.refunds_refund_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: refunds_refund_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.refunds_refund_serial_id_seq OWNED BY public.refunds.refund_serial_id;
-
-
---
--- Name: reserves; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.reserves (
- reserve_pub bytea NOT NULL,
- account_details text NOT NULL,
- current_balance_val bigint NOT NULL,
- current_balance_frac integer NOT NULL,
- expiration_date bigint NOT NULL,
- gc_date bigint NOT NULL,
- CONSTRAINT reserves_reserve_pub_check CHECK ((length(reserve_pub) = 32))
-);
-
-
---
--- Name: TABLE reserves; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.reserves IS 'Summarizes the balance of a reserve. Updated when new funds are added or withdrawn.';
-
-
---
--- Name: COLUMN reserves.expiration_date; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves.expiration_date IS 'Used to trigger closing of reserves that have not been drained after some time';
-
-
---
--- Name: COLUMN reserves.gc_date; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves.gc_date IS 'Used to forget all information about a reserve during garbage collection';
-
-
---
--- Name: reserves_close; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.reserves_close (
- close_uuid bigint NOT NULL,
- reserve_pub bytea NOT NULL,
- execution_date bigint NOT NULL,
- wtid bytea NOT NULL,
- receiver_account text NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- closing_fee_val bigint NOT NULL,
- closing_fee_frac integer NOT NULL,
- CONSTRAINT reserves_close_wtid_check CHECK ((length(wtid) = 32))
-);
-
-
---
--- Name: TABLE reserves_close; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.reserves_close IS 'wire transfers executed by the reserve to close reserves';
-
-
---
--- Name: reserves_close_close_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.reserves_close_close_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: reserves_close_close_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.reserves_close_close_uuid_seq OWNED BY public.reserves_close.close_uuid;
-
-
---
--- Name: reserves_in; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.reserves_in (
- reserve_in_serial_id bigint NOT NULL,
- reserve_pub bytea NOT NULL,
- wire_reference bigint NOT NULL,
- credit_val bigint NOT NULL,
- credit_frac integer NOT NULL,
- sender_account_details text NOT NULL,
- exchange_account_section text NOT NULL,
- execution_date bigint NOT NULL
-);
-
-
---
--- Name: TABLE reserves_in; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.reserves_in IS 'list of transfers of funds into the reserves, one per incoming wire transfer';
-
-
---
--- Name: reserves_in_reserve_in_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.reserves_in_reserve_in_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: reserves_in_reserve_in_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.reserves_in_reserve_in_serial_id_seq OWNED BY public.reserves_in.reserve_in_serial_id;
-
-
---
--- Name: reserves_out; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.reserves_out (
- reserve_out_serial_id bigint NOT NULL,
- h_blind_ev bytea NOT NULL,
- denom_pub_hash bytea NOT NULL,
- denom_sig bytea NOT NULL,
- reserve_pub bytea NOT NULL,
- reserve_sig bytea NOT NULL,
- execution_date bigint NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- CONSTRAINT reserves_out_h_blind_ev_check CHECK ((length(h_blind_ev) = 64)),
- CONSTRAINT reserves_out_reserve_sig_check CHECK ((length(reserve_sig) = 64))
-);
-
-
---
--- Name: TABLE reserves_out; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.reserves_out IS 'Withdraw operations performed on reserves.';
-
-
---
--- Name: COLUMN reserves_out.h_blind_ev; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves_out.h_blind_ev IS 'Hash of the blinded coin, used as primary key here so that broken clients that use a non-random coin or blinding factor fail to withdraw (otherwise they would fail on deposit when the coin is not unique there).';
-
-
---
--- Name: COLUMN reserves_out.denom_pub_hash; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves_out.denom_pub_hash IS 'We do not CASCADE ON DELETE here, we may keep the denomination data alive';
-
-
---
--- Name: reserves_out_reserve_out_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.reserves_out_reserve_out_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: reserves_out_reserve_out_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.reserves_out_reserve_out_serial_id_seq OWNED BY public.reserves_out.reserve_out_serial_id;
-
-
---
--- Name: wire_auditor_account_progress; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.wire_auditor_account_progress (
- master_pub bytea NOT NULL,
- account_name text NOT NULL,
- last_wire_reserve_in_serial_id bigint DEFAULT 0 NOT NULL,
- last_wire_wire_out_serial_id bigint DEFAULT 0 NOT NULL,
- wire_in_off bigint,
- wire_out_off bigint
-);
-
-
---
--- Name: TABLE wire_auditor_account_progress; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.wire_auditor_account_progress IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
---
--- Name: wire_auditor_progress; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.wire_auditor_progress (
- master_pub bytea NOT NULL,
- last_timestamp bigint NOT NULL,
- last_reserve_close_uuid bigint NOT NULL
-);
-
-
---
--- Name: wire_fee; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.wire_fee (
- wire_method character varying NOT NULL,
- start_date bigint NOT NULL,
- end_date bigint NOT NULL,
- wire_fee_val bigint NOT NULL,
- wire_fee_frac integer NOT NULL,
- closing_fee_val bigint NOT NULL,
- closing_fee_frac integer NOT NULL,
- master_sig bytea NOT NULL,
- CONSTRAINT wire_fee_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE wire_fee; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.wire_fee IS 'list of the wire fees of this exchange, by date';
-
-
---
--- Name: wire_out; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.wire_out (
- wireout_uuid bigint NOT NULL,
- execution_date bigint NOT NULL,
- wtid_raw bytea NOT NULL,
- wire_target text NOT NULL,
- exchange_account_section text NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- CONSTRAINT wire_out_wtid_raw_check CHECK ((length(wtid_raw) = 32))
-);
-
-
---
--- Name: TABLE wire_out; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.wire_out IS 'wire transfers the exchange has executed';
-
-
---
--- Name: wire_out_wireout_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.wire_out_wireout_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: wire_out_wireout_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.wire_out_wireout_uuid_seq OWNED BY public.wire_out.wireout_uuid;
-
-
---
--- Name: aggregation_tracking aggregation_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.aggregation_tracking ALTER COLUMN aggregation_serial_id SET DEFAULT nextval('public.aggregation_tracking_aggregation_serial_id_seq'::regclass);
-
-
---
--- Name: app_bankaccount account_no; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_bankaccount ALTER COLUMN account_no SET DEFAULT nextval('public.app_bankaccount_account_no_seq'::regclass);
-
-
---
--- Name: app_banktransaction id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_banktransaction ALTER COLUMN id SET DEFAULT nextval('public.app_banktransaction_id_seq'::regclass);
-
-
---
--- Name: auditor_reserves auditor_reserves_rowid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_reserves ALTER COLUMN auditor_reserves_rowid SET DEFAULT nextval('public.auditor_reserves_auditor_reserves_rowid_seq'::regclass);
-
-
---
--- Name: auth_group id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group ALTER COLUMN id SET DEFAULT nextval('public.auth_group_id_seq'::regclass);
-
-
---
--- Name: auth_group_permissions id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group_permissions ALTER COLUMN id SET DEFAULT nextval('public.auth_group_permissions_id_seq'::regclass);
-
-
---
--- Name: auth_permission id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_permission ALTER COLUMN id SET DEFAULT nextval('public.auth_permission_id_seq'::regclass);
-
-
---
--- Name: auth_user id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user ALTER COLUMN id SET DEFAULT nextval('public.auth_user_id_seq'::regclass);
-
-
---
--- Name: auth_user_groups id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_groups ALTER COLUMN id SET DEFAULT nextval('public.auth_user_groups_id_seq'::regclass);
-
-
---
--- Name: auth_user_user_permissions id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_user_permissions ALTER COLUMN id SET DEFAULT nextval('public.auth_user_user_permissions_id_seq'::regclass);
-
-
---
--- Name: denomination_revocations denom_revocations_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denomination_revocations ALTER COLUMN denom_revocations_serial_id SET DEFAULT nextval('public.denomination_revocations_denom_revocations_serial_id_seq'::regclass);
-
-
---
--- Name: deposit_confirmations serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposit_confirmations ALTER COLUMN serial_id SET DEFAULT nextval('public.deposit_confirmations_serial_id_seq'::regclass);
-
-
---
--- Name: deposits deposit_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits ALTER COLUMN deposit_serial_id SET DEFAULT nextval('public.deposits_deposit_serial_id_seq'::regclass);
-
-
---
--- Name: django_content_type id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.django_content_type ALTER COLUMN id SET DEFAULT nextval('public.django_content_type_id_seq'::regclass);
-
-
---
--- Name: django_migrations id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.django_migrations ALTER COLUMN id SET DEFAULT nextval('public.django_migrations_id_seq'::regclass);
-
-
---
--- Name: merchant_contract_terms row_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms ALTER COLUMN row_id SET DEFAULT nextval('public.merchant_contract_terms_row_id_seq'::regclass);
-
-
---
--- Name: merchant_refunds rtransaction_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refunds ALTER COLUMN rtransaction_id SET DEFAULT nextval('public.merchant_refunds_rtransaction_id_seq'::regclass);
-
-
---
--- Name: prewire prewire_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.prewire ALTER COLUMN prewire_uuid SET DEFAULT nextval('public.prewire_prewire_uuid_seq'::regclass);
-
-
---
--- Name: recoup recoup_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup ALTER COLUMN recoup_uuid SET DEFAULT nextval('public.recoup_recoup_uuid_seq'::regclass);
-
-
---
--- Name: recoup_refresh recoup_refresh_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh ALTER COLUMN recoup_refresh_uuid SET DEFAULT nextval('public.recoup_refresh_recoup_refresh_uuid_seq'::regclass);
-
-
---
--- Name: refresh_commitments melt_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_commitments ALTER COLUMN melt_serial_id SET DEFAULT nextval('public.refresh_commitments_melt_serial_id_seq'::regclass);
-
-
---
--- Name: refunds refund_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refunds ALTER COLUMN refund_serial_id SET DEFAULT nextval('public.refunds_refund_serial_id_seq'::regclass);
-
-
---
--- Name: reserves_close close_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_close ALTER COLUMN close_uuid SET DEFAULT nextval('public.reserves_close_close_uuid_seq'::regclass);
-
-
---
--- Name: reserves_in reserve_in_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_in ALTER COLUMN reserve_in_serial_id SET DEFAULT nextval('public.reserves_in_reserve_in_serial_id_seq'::regclass);
-
-
---
--- Name: reserves_out reserve_out_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out ALTER COLUMN reserve_out_serial_id SET DEFAULT nextval('public.reserves_out_reserve_out_serial_id_seq'::regclass);
-
-
---
--- Name: wire_out wireout_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_out ALTER COLUMN wireout_uuid SET DEFAULT nextval('public.wire_out_wireout_uuid_seq'::regclass);
-
-
---
--- Data for Name: patches; Type: TABLE DATA; Schema: _v; Owner: -
---
-
-COPY _v.patches (patch_name, applied_tsz, applied_by, requires, conflicts) FROM stdin;
-exchange-0001 2020-03-13 19:35:27.122355+01 grothoff {} {}
-auditor-0001 2020-03-13 19:35:34.389994+01 grothoff {} {}
-merchant-0001 2020-03-13 19:35:37.767017+01 grothoff {} {}
-\.
-
-
---
--- Data for Name: aggregation_tracking; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.aggregation_tracking (aggregation_serial_id, deposit_serial_id, wtid_raw) FROM stdin;
-\.
-
-
---
--- Data for Name: app_bankaccount; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.app_bankaccount (is_public, account_no, balance, user_id) FROM stdin;
-t 3 +TESTKUDOS:0 3
-t 4 +TESTKUDOS:0 4
-t 5 +TESTKUDOS:0 5
-t 6 +TESTKUDOS:0 6
-t 7 +TESTKUDOS:0 7
-t 8 +TESTKUDOS:0 8
-f 9 +TESTKUDOS:0 9
-f 10 +TESTKUDOS:0 10
-f 11 +TESTKUDOS:90 11
-t 1 -TESTKUDOS:200 1
-f 12 +TESTKUDOS:82 12
-t 2 +TESTKUDOS:28 2
-\.
-
-
---
--- Data for Name: app_banktransaction; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.app_banktransaction (id, amount, subject, date, cancelled, request_uid, credit_account_id, debit_account_id) FROM stdin;
-1 TESTKUDOS:100 Joining bonus 2020-03-13 19:35:41.187705+01 f 5e24163f-5331-4629-87e8-40fb8715a0c2 11 1
-2 TESTKUDOS:10 HG3AD5KPDE27RB7A2Y5WZK9K6GTX998K05YZ6C4DEC2RRR1J3PT0 2020-03-13 19:35:41.278467+01 f 9f36a88e-3b60-4e3a-9eba-e990fc231631 2 11
-3 TESTKUDOS:100 Joining bonus 2020-03-13 19:35:43.794597+01 f ad97b48b-6d1c-4634-9b43-7a130f35dc82 12 1
-4 TESTKUDOS:18 RT4Y2C6W4X7SX0Z064TRQ4H2ZZH1GD5E7WWZHMJQ9WM5F5KGWQ40 2020-03-13 19:35:43.881571+01 f b0223db0-35f9-4a33-958c-321d1f96deb4 2 12
-\.
-
-
---
--- Data for Name: app_talerwithdrawoperation; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.app_talerwithdrawoperation (withdraw_id, amount, selection_done, confirmation_done, aborted, selected_reserve_pub, selected_exchange_account_id, withdraw_account_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_balance_summary; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_balance_summary (master_pub, denom_balance_val, denom_balance_frac, deposit_fee_balance_val, deposit_fee_balance_frac, melt_fee_balance_val, melt_fee_balance_frac, refund_fee_balance_val, refund_fee_balance_frac, risk_val, risk_frac, loss_val, loss_frac, irregular_recoup_val, irregular_recoup_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_denomination_pending; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_denomination_pending (denom_pub_hash, denom_balance_val, denom_balance_frac, denom_loss_val, denom_loss_frac, num_issued, denom_risk_val, denom_risk_frac, recoup_loss_val, recoup_loss_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_denominations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_denominations (denom_pub_hash, master_pub, valid_from, expire_withdraw, expire_deposit, expire_legal, coin_val, coin_frac, fee_withdraw_val, fee_withdraw_frac, fee_deposit_val, fee_deposit_frac, fee_refresh_val, fee_refresh_frac, fee_refund_val, fee_refund_frac) FROM stdin;
-\\xa96d1614b62d8a838b7ebee977db7f3d3b7c3630c8d6aee9c96e848c5b4f72200672fb3ffe229bb3c0ca63fda38757550f42170cf04fa8c647342d4d82abee67 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584124527000000 1584729327000000 1647196527000000 1678732527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xbb45dd1f40b446b36785e2178930240f1407da65a34dc2f4c39b527838e014ae2f5a5249a5db28ab0a64c1e37608fc7345508132dff6c135fd8c1bf651248dac \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584729027000000 1585333827000000 1647801027000000 1679337027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xff062da42a2041b151a9a116837e70a11be427656d0e79ee440386edaa31181931fcc88667cbcfd1a47dcc382e8f770d897807ec2b0be258ca061eed3bee85ee \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585333527000000 1585938327000000 1648405527000000 1679941527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x9cb0ae54f9c698ebd27e4de1c80894794ba23e6a96651809bd62aaf4b09fc051248a210d11f632c21a9bf60eb8a81bcee79dbdf8827a522774beabb2e17f26ba \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585938027000000 1586542827000000 1649010027000000 1680546027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x73cc45fec9836dec65d8c0d9b9e0bed91b00a855f640a7ba5710ccf9f83b592660711f7fd27ad73cbe9b36ac8c934b3cf8dedb73a816a2e99338a600d171a09a \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1586542527000000 1587147327000000 1649614527000000 1681150527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xc17a45b79c699f6706a467b28baf97c8634266ce019fefef7f949690a6630f75c34a1dcf8793ecdb44eeca1ec4b4a599ef40d8bc93bf98ec92dbde5a02b4208d \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587147027000000 1587751827000000 1650219027000000 1681755027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xec3d9d60b61b86944f9200d17e51f16f8a3da2f549078d93fc666ac749413eed8b8c81c933f4571cc6f5133125df4ca44d4f0b2c9df66b695f61c52ecae31b72 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587751527000000 1588356327000000 1650823527000000 1682359527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x8e7a9699d1a4641f86dc5ccdd6905dda424bc97d25763b4f84a96094b549c657287dfb95a9a3bb8f91dba3c8e60debdd67caa97bc16fbc7c649981a935d60834 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588356027000000 1588960827000000 1651428027000000 1682964027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xec63e694c0183b24220e53ecc1b0edad4b82c6603faefd6e22973ba7ef86630f14c53355c235a51c3a6ad91ce2d58e3b30a8dbde6a1608b432f4daf3722ab05c \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588960527000000 1589565327000000 1652032527000000 1683568527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x0722edfc399662ca7faffec24979257e075cd88a5a87f3fe146aaab3edd24553e8061fc50edb8976dcec14b7084b0fea412031c4ac384f42929c8751725c7730 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1589565027000000 1590169827000000 1652637027000000 1684173027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xfa028ba1a8afd9a5617714699a85418c8374ce17bb6772c38a34a1f2291c987fa27f9153d24d2dfdcfa88fbd8cee27fd2b6ab2695393faa6e0c306899fcf8779 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590169527000000 1590774327000000 1653241527000000 1684777527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xb09acb2b6c52f00344caa32cf7c22a4096f6f9f0e49c2e5185f81bca35cc3ff0342f3c592f4c3eb98f58425bf14c6a46b19f7c7e8e2d4c82bb05818f8cd72b0e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590774027000000 1591378827000000 1653846027000000 1685382027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x8f86f18e72a6fea2e535ef6ab4ab220354de7513b2178410bd107332d913f609908f7dce6525617cca313271245f74a52eef4910234425a276fa3640ed585361 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591378527000000 1591983327000000 1654450527000000 1685986527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x6fbada069741a8cf3c0a0b0af5e673757cd38cc2b09c79c40e2fde238d41c934b97ced29394675359b44ebb9fc97e6808c9c4538b510d5df6e304c2cdf4d7eaf \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591983027000000 1592587827000000 1655055027000000 1686591027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xe9e17b1050c939d2dfa1e1cf820114c83a9d368c9b9273d853e29525467fd8561edcda45870c0f8364fc4242b2ba94eaf5729e316f4e9c2f4bc45f4312c08f5c \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1592587527000000 1593192327000000 1655659527000000 1687195527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xe7683d3b5ba7e7ebc47c07fa128a5dbd2f165d74c8ce40db5cbdfc5c4c4c2f2929be11b643153d2359bb2422ed50e5c6341f03f770ffd83118f5a4711e15cf61 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593192027000000 1593796827000000 1656264027000000 1687800027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xcd129a8de5bcb9a417ee3384450490aadc6e96d4c23a011412abc9914135b15c8af62b3efe2fc1bffb38fe665bf006e8c818b795ef1c5c0d2f8e9a5544abfe7f \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593796527000000 1594401327000000 1656868527000000 1688404527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x9a52bfde8f794fc3ebe4c5041fa82a3a188dc7e80f30e3920e89728af6aa65e63b8832b14c346898a64ef0c265b39a13a296e8831774e60f4a9b955903d00955 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1594401027000000 1595005827000000 1657473027000000 1689009027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x53f73b67bdd0d71a3272f4f75837b1b78d8c9b7d862005b247977f6bc8d632112cf09f2755b805efaf06d1f09f10bba629a03c0f9520be15808b8b982d040b6e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595005527000000 1595610327000000 1658077527000000 1689613527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x05e4e536ac90d31d6d00eab4e4fe65d0ef4a104164d9d4df273161040c182cd71cdc9e1a589f0ea506259f02331e96d718cf9fdb32de1a0796e3fddf75f05f7e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595610027000000 1596214827000000 1658682027000000 1690218027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x43d57d44225a1fe965beec829a7cd3d8d7bcd486a8cee33710595f5e89b8ca8b6c71072c3c6e7a58d3a99e52da89f3d1c718b9b8107d8f297ca8959953b03b6d \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596214527000000 1596819327000000 1659286527000000 1690822527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x5dd555b83b7c8249fe58aee8a1aa26b6abec1bb646c08468bbb5dd8bad9631cbea25955cd74f260593e0d8677911bc956e7ffd291da0922caed2fe7f3ce900c7 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596819027000000 1597423827000000 1659891027000000 1691427027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xf5fdea21d65de4a8375054fa9aee4ffb76cd37732ae433536fc04e8e0605f68979f0aa816d8c1c6bd5ca9cc7eba9e86f2f6a76b56d2b246af7de357be35ca8b6 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1597423527000000 1598028327000000 1660495527000000 1692031527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x3700f86633a517f3d477814161e960d675e76fbdc106d38cc223593a757fdf7ddab5ece025c0ca61b8d5bb87fc8de6cce0806edaf64c0e7822b3eb589dfbd220 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598028027000000 1598632827000000 1661100027000000 1692636027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x77aba628bce3de6e7d1f56641a5b8f610d075503ca1825e6bccee78917b29b05ae675d54a5bd45a2e36aae4abc7eca6c2dcd09d885112d83d886bc3f58b0eee1 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598632527000000 1599237327000000 1661704527000000 1693240527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x2d6e598e2b72549beadc9fc123231f322a589282d7058bb2b5e7392e7c2bba6c326d964acdccde37f906d5e27a11f0336792d0782132d41613f6e39ab7ee3404 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599237027000000 1599841827000000 1662309027000000 1693845027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x98a9c9651e6a672fcbae9a1ebdeb1a98b6aaf91b2fbf22274cd02411e8b18af0b38d2883785bbfd7e8aad783142a1218122bf52354cb4f0c1e6cc7e3c9588697 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599841527000000 1600446327000000 1662913527000000 1694449527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x0691299e9a38d9baa96dfc372381c15795e578416f4661251ea0a48e93d51dd3e7e107290e867903defd1b631a287316e7ff02c0a8a06f56bb1a3ee16c45c1db \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1600446027000000 1601050827000000 1663518027000000 1695054027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x7b59a5bd355465d9bd76bcd0b3fba924d23e4991c8173bf4c2d659ea7456f92eeed8d9f98a6dac8b3e5f99fcb15f17a1b7f3a9a9251ce5bd4f3b15db2f67150c \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601050527000000 1601655327000000 1664122527000000 1695658527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x1c58c604e312745957a04c19efd9a1a01f6a02971586571b7149f9cc96aad9acd5768c15110e73004a0071a2e654d38e095e5f0c8e8e4672af83fb3fa9735934 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601655027000000 1602259827000000 1664727027000000 1696263027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x4d5ddafe2829d944ef1dce238b01206c4bed413cb867e45fd9daf153ab91a504c003d00a8f4fd209664bb7f62f031dc0e67d7645c3a703e093abff98175d723f \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602259527000000 1602864327000000 1665331527000000 1696867527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x65cb9f2c483b19fab8014736451988c5c33285933f8dc64ff3cf90835f2653cb7041712e518839354d5485e97ef4838c4e8e5d98b67dd6f4e77ea84a9f26f683 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602864027000000 1603468827000000 1665936027000000 1697472027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x209f39ddd8564e30ef4957b75ab3caceef0c1525f7ebdfcba8e241c2b43cee69a1aa4c9efa26c46a1c0f674e36711873ce2b7d8301cec150d8673432c3d81ad7 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1603468527000000 1604073327000000 1666540527000000 1698076527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xe2557e383c1c3c019681c2521d415a758f1becb629a3a75fd9dc212f0f2e802aee81e71db725f0f9913bea0f913978bd7d238558712aa957d80c4e1673292b13 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584124527000000 1584729327000000 1647196527000000 1678732527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x89d632dde6b67bcd5496434399229c75e8925653c5c952393a61131b03f941ba1ec3de4e210861a730b85fb87cdcd7ccabc26317cdcb823f83d027ba8777324a \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584729027000000 1585333827000000 1647801027000000 1679337027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xf5ec27a33faef3b0da60b8403beaebe30e385c3bbb0849372b164d26b8c73265914f804eabffb948646a8ee0ff18ac8681b5014adb983dbc692a4406791ec1f2 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585333527000000 1585938327000000 1648405527000000 1679941527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x736fae372040bd5d2d9c97e454efa630d0ca6a01a277602638c998af302418885e017148f4ea7cbb144a0c29a463c8c922f3c527f0ff63add38f64292c69f5a4 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585938027000000 1586542827000000 1649010027000000 1680546027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x4faae841605109e2e8f43768baf54551b8d2ac29b16f601799c2ee260b9bcad53c8b4e5ab2352bbc1b12bc28c53df0e3ea355d61227430edb4644cdc4546907d \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1586542527000000 1587147327000000 1649614527000000 1681150527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x6848b532bbfefccfbe4fcff80fe6688ce482d891673965e64c99c2b363ad9d30177f1ef45d2abb9c947577f8e23e3aec2bc2c2e38d113acc13eb6b808fa5bb1b \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587147027000000 1587751827000000 1650219027000000 1681755027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x198bd5a41044f28606f048ce59414508461044ba74ba6abd7555bb91c78b750d594519eac6ade5e9f6a25cee0c3c0a1a6c6d3e7c42d529e68478802ca2c0e3ef \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587751527000000 1588356327000000 1650823527000000 1682359527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xb2e527f7d2180fa33a36f84d4a20d7d1a7a8f3d816634ea9a568ef8a1ff446facf89ab5bd01a585c8bb40c6088dd255f5e6bc0581d87b1bbed0194fc3d1f3cfe \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588356027000000 1588960827000000 1651428027000000 1682964027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x294fdd3e4e9176cfa35ad3f56da2f9648cb1d0a682e6b72e195fe46d9c792238d9dc663e30bff65c08cbce3caabcb9a8211e3888a5e5347dd50c6f05fc3afbc1 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588960527000000 1589565327000000 1652032527000000 1683568527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x678dd52ec4a7d4b4c2106cd3d83e14e70155bdb22497ccf200b25451c65e62062f684bb0acd1494bc7281c4e73f7539976f757456452831d6ff1a4438f87aded \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1589565027000000 1590169827000000 1652637027000000 1684173027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x3a98d8ef0e0a1e94e4703d14db2fdf6af57e8ccb0aceb3bd1aaecf747a45ff42fbbc82cfd972e863d7276f35b9b0bf5b1045a2055a4447f1b2de890459122718 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590169527000000 1590774327000000 1653241527000000 1684777527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x0f4c04c2c2ac57229cca630b90f8e1cae021a5fe6887d0e641746cbb6afb7a4b4463feb403c307625a772cb9e144d941f9a2a5354418cf649ae23012cf9deeb7 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590774027000000 1591378827000000 1653846027000000 1685382027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x2c8e413cd938e04f542bc9244698a4846cae350be51a4b96c23dd62328131d9f52c55d59461946268e76de4b73f3343797905bbc79e43639e911c1dd776062e8 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591378527000000 1591983327000000 1654450527000000 1685986527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x8132c0e0e925daf9425ffc2a1f19b014d3e5eaaab3f0dee040a88a8fe49e796ff7086f55e451cc3f89f4db290f650b15541ab9433f6748bd61740d997a173e87 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591983027000000 1592587827000000 1655055027000000 1686591027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xad283ca32be3327f8be03fae2f76639bcc280f2148545efdbde8f26a4d78b67c5142d3ca173bf3ff8bcd8d0270002b4eb6510f0d8bc6aa047409dfdc2320cffe \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1592587527000000 1593192327000000 1655659527000000 1687195527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x7bdcd195530ada526322c2b2ef1146ec3ba338b9d4e24c00454d97f805e46535512248f8d80ff821673f46464f9a95867f03d8c63d269582609b9599d46bbe00 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593192027000000 1593796827000000 1656264027000000 1687800027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x08410df47dc40825ad229bb0cc18c2ff1c378db2f44d688f808b15156935c759c3786d00f587d53826bc5981eec8d0714bf6bae4ebd6d5f4e46c9bfdd37e086c \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593796527000000 1594401327000000 1656868527000000 1688404527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x3b422076450dd388ec63cd304e3efe3fd8e63711d3b53b78b9edeae4f75aaae1d06fcdbd807507ee6e47ac0bc5a71d61dc2b321e211077bd9f845e5974cac6a3 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1594401027000000 1595005827000000 1657473027000000 1689009027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x310e1d0c6ac2a8aec9f821935d6c3fa9cc881043931d1c151401f0ad2e7a9c210768bea8b5939a3236f99a62a140f6055d1b74967143eee4f072f6bc9c247016 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595005527000000 1595610327000000 1658077527000000 1689613527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xea393ee266fe343fef6461d76325f099fb0c365ce0518cf1dc8150bcbbd7f3b1cb2418d9e4855dbe9b35d0647a31141a181dec55c80b7b3ad2d93ef4703ed749 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595610027000000 1596214827000000 1658682027000000 1690218027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xa724adab3e64fd5d7710f9620815d6ef7d631382af62d43759103ef6a9b689c66cd38fd669fda2dbd796d8221e06f91868556bbdecff3b356d52c9df988d313b \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596214527000000 1596819327000000 1659286527000000 1690822527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x7c2400b641bc05f2d2be2bfd2a5d0af2a811a8b3ae6c66ef760d47c250e9020460d28de2872c17cfd6b1016ad6a6e28c62b5b672f46ab9355af0ec149f7e9aed \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596819027000000 1597423827000000 1659891027000000 1691427027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xddcbcd710d062c8bf0493e790bc001768d60d000cc064b33ddbfd4bc71417c7657953766c55f33247acb905ea1ab69656d8cf78e384e4f0510ab0d27fc69f0e9 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1597423527000000 1598028327000000 1660495527000000 1692031527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xffefe5a4137034b1f91b954e42777fbdd0a7464b37e83335e1a6d0ae19a05879f1fdae0d375befbcf381ab5378cd514945b27a5e83d269b8737000d302f61bdb \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598028027000000 1598632827000000 1661100027000000 1692636027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x63e58e0daf3f9a2676fb1413859d980b07ae3731edea601ef941ed68a24d941937785701009fdf58db7ea87344afae343070d34e54ed403e094fd3df691f3d47 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598632527000000 1599237327000000 1661704527000000 1693240527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xa0741392a26e9f5d7780e740ed710ad8407a71dff1c23fd0059a6b759e8bbee8eb6962b67cba3915178e5640d68177e8b8de599f249656b98d3e1caa3875dfcd \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599237027000000 1599841827000000 1662309027000000 1693845027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x5b797546d8bbf3313e548256097eca0000f18e136faa339a89c81f5e9bed6145caf876b61d9613a1d2f2aaadf8926576e491c6224df6e8b87b5e83272fbc70e6 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599841527000000 1600446327000000 1662913527000000 1694449527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x6a09bde9661b00281d3ce948af500471cd71903b50b11eeed970a6e00144909ce5513cddc4ddf7a5cdac6211aceaabca7b17595508ed5458eaa907943802600e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1600446027000000 1601050827000000 1663518027000000 1695054027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x9cecf3582ee4c71272074227936e90e689a8296bad1b2524e0410882454299f6ba39743b80bb714d1b5ac8e765ed80b0103bfcefdb884bad6866f7aa45d06b17 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601050527000000 1601655327000000 1664122527000000 1695658527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xd2e957c9d5b7a9815cd99e61761688441323293483c12903efa2329e5bfcdb8db2ae0dcedf0f773920525bcbec0660a47bf7cc5fe8321f7a0185f49c52c2ed49 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601655027000000 1602259827000000 1664727027000000 1696263027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x58de8f1a094ef83e0c2210ebbfaef4e4f6df9f6591d15b4d77266f264cc40c9d347ab3f462ea31528c995875cc0b3f8de66966128dde4d6107714a820644ba7e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602259527000000 1602864327000000 1665331527000000 1696867527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x63ddd1b71fac8f9dc59893a23d001670c1db9840a24dbbf1e6b2bb490864070fa60f37766e45b02f56f7d558f4e694ef57743ac6b2477f7ba5e1407a001ad465 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602864027000000 1603468827000000 1665936027000000 1697472027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xc1df649c58ee62c06ee0db1d4bc3aaf83c76b5fb180b52d2b57249478d5900b845050eca79bc7767d14993c58bc4730f9e5dfe8f793fa404a9bcba5ac79d40c1 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1603468527000000 1604073327000000 1666540527000000 1698076527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xa6576c13e5143d56edc29a8c4917121a1c3917f3436c26f3349de6c226bb96bce029469a4b627b504a3f17d70c4ad7e6c015b4873e98c87238c9b824018f461b \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584124527000000 1584729327000000 1647196527000000 1678732527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xddc88ee65b9d5ffc9787a9154065eae790812d277640692b9de92d2ccbe417d95f649de7208d63a5b8dfbe9df3215c1c61862c14863168507078619eb14921a2 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584729027000000 1585333827000000 1647801027000000 1679337027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xca5e89c144df5f82fac1329f3c86754b0e0a105f7cf27eb9412d20cb48db7eeed585252b6a25a99c85ffaea656af87336881999a8035b50d7917c12f6a9b90c6 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585333527000000 1585938327000000 1648405527000000 1679941527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xb13be79e272cf95452c46fdc192d07d16f24c0c0d9638bf2ba7814146c195272bd5f6a308e5c53460c4e5adc12c8116170bef14a542ceae0379dd0862008d868 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585938027000000 1586542827000000 1649010027000000 1680546027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x19d70de23bde1e436057b52e826f99bfa483b84ce088c89daa91a32eab5edd2edfa2aafa1714e1cddfc6faf8667574e76bf139865720b73ac9d6d4e6cc04c5e0 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1586542527000000 1587147327000000 1649614527000000 1681150527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x8b6569762ff8a11fe0faa5b32c40ac6c89dc20727a792995c3edb02d0291e5489c43b196f5201c04b5cc6ca3e5e165e796e1e6f3b7a4d1a0f523b5dafe9df2af \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587147027000000 1587751827000000 1650219027000000 1681755027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xe3c8b25ef11b8de1f79bd681cb42c8c77abe4b5c446173dff907bd71e0b869c456fcb6f502ec91599746ee4a8c2fc8b8118d8a09017b09cc12b1af700a296750 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587751527000000 1588356327000000 1650823527000000 1682359527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x7fa7641cf151fc60d8c54c9b5d5df0d654e2f23680d45b1b143b83c202eaa3a99f9b2e61bafded652492052f8748394efe5a0fc800be252a47b2ee9b2fbf947f \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588356027000000 1588960827000000 1651428027000000 1682964027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa58ee51ad47a34c26d50a4c911040fd2991f2bc728d24bde2543bf4d26bcdca6f48dbe2a98cf874fee4064fd3d47f0d89a6dda4bbd44af9d5ee6dbbf4cb17fba \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588960527000000 1589565327000000 1652032527000000 1683568527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x1add4a9cf6ac98d872cbea41c8decfd34cb0c21ac0c9c5b864332733396093486204eefac6fa25f5581fa2fbc973464324ebf00cf05ab5e74d768cc2ef74945e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1589565027000000 1590169827000000 1652637027000000 1684173027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xc1c70468e1436c7742a22cb3fc1ff0e69a5549987dec101c448283338d35ecaf15b09f447d092950500b47c501c15ba33dcfedbfc366c370728175c65c9df943 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590169527000000 1590774327000000 1653241527000000 1684777527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xcc651df9bb6f5ecc41fb359bb0c4adbdb269da8ae2538c6a5b899c8ee1061767ffce29ff897ef1384fcacbc51301a64eb2720c796cdb1c809e7ec2605589b885 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590774027000000 1591378827000000 1653846027000000 1685382027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xfa6e3c35c16c059871ed81afea3328e8c971332ce564e7c13baee8869242f512d8bcc9ffb3ea9fa2c3f87137ef729aae47e145f1518fc3658440a5dc910e5566 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591378527000000 1591983327000000 1654450527000000 1685986527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xce1f1c37056c63bb7f60accd955972c2193d48abe8db245a2b5698582c75f24c9c5ecade1877d2056c0ade564342626091500c990144a32fc20876ba1a9ca534 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591983027000000 1592587827000000 1655055027000000 1686591027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x47de80930d0b79ef1bba526d1eb8e0d9c97e06e21b4a55fc6416b550d8b243b725e5debb43921d222521f9918694573c4489dfb50c3710652e318cbef5cee304 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1592587527000000 1593192327000000 1655659527000000 1687195527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa14dfdb030aa65c85f65af028728f3ff1de427cdf8d44fcfd07e688080c38ae8cef65442f6a5aa861cc7fdc10e3cf5688eebd77433a1356f5564b90d9cb0d91e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593192027000000 1593796827000000 1656264027000000 1687800027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x2c30c000ea26545d5dd5df051cc7d9d45c430d5d63678a04e3d562ab68af69296f7c5dcc6a8caed61498a63e0a9182ee2192afde46115b52775d25577d7ca8c0 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593796527000000 1594401327000000 1656868527000000 1688404527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xcb9a25d8a80d60b3431403195f50f71b28bf1fdb608ba2ecad24b6e7a02dd3b8d12b26eb4cf840c98bbc1d629b3b12853d91837f410990701007b7901ab49900 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1594401027000000 1595005827000000 1657473027000000 1689009027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x831f49572c67918fa6d843a59a6112a90ac74f72ad70ab5abff60fdad26c2052d34a973f6f2529aa3e7d8d7c579dc77ea40b4c1c012250869290da3dd0323311 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595005527000000 1595610327000000 1658077527000000 1689613527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x9e1b7933018618a7fed22ce2f841214c4587a7fa2a7d2a6f6e27aab525f8ab25a53ba878dc62c2b9d7722594e2330621962467d268770361ff277fbaafc89060 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595610027000000 1596214827000000 1658682027000000 1690218027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x467df6abe3205708661cb892fd45bd4a024542d59e2d5a4488122f2dd685c475d9426e23285f9b868c163fe52be65d42f9f963f787014b60fe091ea8914fef30 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596214527000000 1596819327000000 1659286527000000 1690822527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x27f580f5add0226f18eb385d280be808a59a0872d1da41b054b1dbb822516c5f82925d607aeaecd6f10451dc6596884715c425c71be7f2f769d1bd394e308296 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596819027000000 1597423827000000 1659891027000000 1691427027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xf072c12ceeef24628f4543557914e0e6fd8d2386da229ca52793772ec7e00e8a0cf84f1bd96dd9b6e535ba0890558b64ee00ad0862e6e077be545b22dbd76ea5 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1597423527000000 1598028327000000 1660495527000000 1692031527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xdff34e92c071b4022a12c51446a58ce0de6facb1a5f86eaedf30e6965ad8db665ce1556286d1a554e36d67af5079ef22f90f21a21a09df2ae8f7c5d7f436fd42 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598028027000000 1598632827000000 1661100027000000 1692636027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x091712f282d74bb33f903c23a11315c414a2e661131632bf49a7cc841bc8ae65f9d10a5f6144557beb1fbbfe00785a00df098296360469f048a906667c289770 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598632527000000 1599237327000000 1661704527000000 1693240527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x4c0e2f8254fcf5ee3ee7cff5baa5dc782565af7615117a4247486f87f48529c407acbd078df27b266b28b157eabafb108aec4d15706c9b704dc8e1b5e0672c64 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599237027000000 1599841827000000 1662309027000000 1693845027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x17edfe6a00c0f73775d2b1cbcce3cf2758e45d9f12a889f0353d57ca71fdc50ef8b989354967d3e3a4a19d561097b233a0d172d3d479c34ab741b4fa15f8c4eb \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599841527000000 1600446327000000 1662913527000000 1694449527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xf1b4524f05edeb81b92c8a1d179ff9dd58a7ae91204e667f5e06258a962ae14f9cdc293689569db1cc38b882a0a2471b52164ef4a9267db30a928ff5b4a4340e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1600446027000000 1601050827000000 1663518027000000 1695054027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xf5540668fab2a27f8ad6bef45f727a180a01aabd28c042fd6189608ccb636188bf7b24b3e5dcf16bfcf49588dfc0131c028a2745ad31a988b14d95ea47dbd2de \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601050527000000 1601655327000000 1664122527000000 1695658527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xcf5c6d6dca58e3ab6a1304c9f088bcd8dca056bd33448029dfaaf28aac36bd7cef9e1f83a67d10fab0fcd9e81d7432ea8cc110dae534e38a95c4c1db30b87e2e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601655027000000 1602259827000000 1664727027000000 1696263027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xf8a7c34086b833ce093ccb1b91802fe2e9f79f23860e91ba3036d7d68c5a111789480b749f1100451f971556598adc53cd6bc5e22988cd0aaa4c001147f88322 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602259527000000 1602864327000000 1665331527000000 1696867527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x2c50cea43b2a9d85633181c8ef2782c2a796ab638122d9604738fa8478aaa5e0e1199a9c587a2d3d15de108dda8047f4e1b4155e09c8c0460cbeb22bbfdcec85 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602864027000000 1603468827000000 1665936027000000 1697472027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x7ff91d9ef771cf4c9089ef1a742292bd887eae63c50ec766d3c27571bf92618ee49267e72581f097a1740e8a2a67d63f59685be0a44d2a7bb0f91aeec4b9f4e9 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1603468527000000 1604073327000000 1666540527000000 1698076527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x597a8051c87010b154a8dfa6643279b0c166b51539c69b9cb19af986ab521ed59877b3868c0e56aac00ebbd6f1f3036a6b667403fc032e32b5589ddb9c257b8d \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584124527000000 1584729327000000 1647196527000000 1678732527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x7246fdafe0cfad50a1068db61dcf1717abc01a6197db5fb2fa5bcf482d90e5071b7f670f10b9309551a53978bfa64481656854e7484bf6f867f3e94b8383d33b \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584729027000000 1585333827000000 1647801027000000 1679337027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xeb874e306cba69263f4ee7a1cecb2b7649b54a1d1ccd427343a838dafd4f8f2d97b828bc65c9557f83934e041f8957c5e7e28c3bad7e79a984c7a24acf57f803 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585333527000000 1585938327000000 1648405527000000 1679941527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xcd70779a6f182bbfffdc0da52269411a478384f9844eba7897009ffc8ba92c52ebfeaaa274564177ba77baddb77a7ea16d5f2327d6e78cb871f52f7cb458765d \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585938027000000 1586542827000000 1649010027000000 1680546027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x3b6d21737bf0bab81896a28cce0df7b6fad9718ed09fbcc0617c6f1da04d7806a9e7ab60bb2ede861246390a649951c74d210b12f5cccbc7d1e2539911bf585b \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1586542527000000 1587147327000000 1649614527000000 1681150527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x54c2564a81f88da5548a49c35edca0ca631c5d801855159e92b2d7549f2269cc3b939f72b13c99943008afab327d0586b561584ee1cf0a099a08994a7453402b \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587147027000000 1587751827000000 1650219027000000 1681755027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xd70f44b6a74a3fa01744112c45f1908a1a448d19347cc8b972ad88c70be8e04deeebd4f7d2c266f6432b1b190b30cf73332e5005334877ea340247183e304bbb \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587751527000000 1588356327000000 1650823527000000 1682359527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xa0f8b3d8cc70612760d8fb5d88e7f5f5ef57d5501aafa3304269be86ee0896bcd44e46cd678b27ebcb95e0fe1c782c87b30f935321f686371dbc1cf1bbd3bd97 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588356027000000 1588960827000000 1651428027000000 1682964027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x82adf06d497236bc505e9e907efc5ae1481d749600b7979423f8eb9bc4c72a62f97b5adc9f0cc69c052e1eab3a73fe15dbc0ba02c7555afa749b2fa70db79a3b \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588960527000000 1589565327000000 1652032527000000 1683568527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xabd95251f9ae32a208eceb957fc394ce015ecb98cb20711f238689939f7ca9aac028fcee070dcf38c9dcd8ba631d1ad708a2a19eebf5cd88534c7e0e62996a67 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1589565027000000 1590169827000000 1652637027000000 1684173027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x71f8874cad42e50a4477f00de15c59e721c5528b3785528ca8ec84f3f4660efc6ed472202bf2eab37f2379440d64ade01b03cef107a4dfdbc398bc1674806c17 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590169527000000 1590774327000000 1653241527000000 1684777527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x8bdeba76d758bd1faface814b4fb6c895d0236fc04d073ff25ceb4f5be3613521865b25fda61f6109f8c994dfb1cb21aa93aeefbb235885f300a3a510b952090 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590774027000000 1591378827000000 1653846027000000 1685382027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xf0608e0448c58a93ec0be344191f06ce313513be1d317280126faeda910cbdcadaef9add0b88bd526fefdc8fc91351dc80e366f053b33fd4e69521a1412e4af3 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591378527000000 1591983327000000 1654450527000000 1685986527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xb9b3d31175f4c977c18e742e6b2d444af91db6087fa1b81fc693c99b7715e320ea602a84cbce759261772291e0165dc57f0af52dae54ef39a2291191900deb10 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591983027000000 1592587827000000 1655055027000000 1686591027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xeebc5aa02919f853f75f78fc702fa7a9484d4ddafd3ae91ebe91ff2bd6383a1fd883ab12de4bcba8169cc9ae7de167f32c7f2cc4dc3ec146726bc45820b98abe \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1592587527000000 1593192327000000 1655659527000000 1687195527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x7d8e9387284f9c8597f20827bc4bebcbff97c770892d7ca6bc46ceb77126a104e0924b97406f70bfada5950ba16de2f6bbc73a59b63727be6885d4acf8b0f425 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593192027000000 1593796827000000 1656264027000000 1687800027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xca057129f7ea605703e8194c49fb73b7728a0dac8e0cb1aa74c0399306b779cf2616a7bed50044058447a4e69303d294acc8a8163a5d02d7a3738eb80a5183dc \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593796527000000 1594401327000000 1656868527000000 1688404527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xeec6c2ff5c537ae5087dde8a8cc2466de3c571e589fe6ce26b5384b9dcb334b98f828e4e255e15387257fef093998cce104585849a5655fd26d9eaccfa0fd11d \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1594401027000000 1595005827000000 1657473027000000 1689009027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xe194585a6a21ea6ef6d6c4da87c950653aea1fad8755818f0c6ca8500802618f384a8b78efb0c487f27981cf2901457f5c18a05df30b2f83554e89c2a87a74c5 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595005527000000 1595610327000000 1658077527000000 1689613527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xea3be6389edbca88905977f3975ff68bc8fa7c99d09a8d7a9f86ff717fb49e88c410e1f8fecfaf5ae87166453ddf479be016686cb804e56488b0af99dd918ea1 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595610027000000 1596214827000000 1658682027000000 1690218027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x3527980fcac602204588e0f9aa5f8c608d3a023418ecec3439ebc5f0aea358802c6e96bf0b6a7e05cc926ad4f17739ac456ca7a46bf9cf4a64c334ec9e13363a \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596214527000000 1596819327000000 1659286527000000 1690822527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x17ccc94df1731183e8d3977169afbad2f54c9e3624d6369134cc96b5097d36d53864f676ebf1cc7ed94c9bbfca232a02bbcc447005b7f5fb6407ca24223d3f38 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596819027000000 1597423827000000 1659891027000000 1691427027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x8a7b6fa6074351b69a4fbb08c139225066f91ac3673bae447e31663c0f29174e8ba1284aa3b58078e19fa8cde0faf000db681ca36f9c1415b4bd0592c9cdf01e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1597423527000000 1598028327000000 1660495527000000 1692031527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xe81c80f69f6d546234e5c4af547a8db8287c00423856490f177bc2898c5eeae1228f4e54596a579f5415e69364319d6e629ca942d6c7605e90c5254d76989df2 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598028027000000 1598632827000000 1661100027000000 1692636027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x6ebf441db55cbed32bd1a9eace307fe8a354e39f879d7ee35e17d2b4d531503d5765b8db81e1d76039957f00e609cae35ae003ed9ac6253ceb9f8ba51bfe046e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598632527000000 1599237327000000 1661704527000000 1693240527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xb7312854a7c1160d7f3d2bcf627aa143b8789af24016aee01082c8c7095add2a726a69e3ee2bbf35fdca7c1ba0a4df857be16949dede5fe2de2e0176e5974206 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599237027000000 1599841827000000 1662309027000000 1693845027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xb54852bdc9304bcadb4af920c36d2651381ea2c36686293d05f130f5115993a7d4fce63620c722f13663c198d92a9d12707d90fcf779719c87712269c84374ca \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599841527000000 1600446327000000 1662913527000000 1694449527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x8be470be5403c1ae0c0278238d097ab0516233ad5620ebf5c8b0eb94190b2ed63d0b2529bb4cc6795c6486ff6b9127d7041942d8143e07c5a26cc10cb304268b \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1600446027000000 1601050827000000 1663518027000000 1695054027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x839c05ded3a73edd5631ec93d0f461d7adae76d360bc75e24a678d5120348230972d0e45df2f8a59fcfa24ca012f003c99d645b8084ddb7b81cd243f7c015c08 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601050527000000 1601655327000000 1664122527000000 1695658527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xfe7826d2f2bb375a29375d6ea026fcfca525827d667c94d74e9e8b0c4f7cb5a9b3c284aa6c31bf99fc00ab4e6fe625d12e197bb5b99dee6e6a1a03ae24ec76db \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601655027000000 1602259827000000 1664727027000000 1696263027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xb0ab62dc4cb846f0482e71066eec379ecf3657bd806b4d797e125457b4eba59aee76872724c048597818b4125f388561ab1bae031355f4e8d5df2ed565ac73b7 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602259527000000 1602864327000000 1665331527000000 1696867527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x62f289752f1c3eceea483fe407b8ddde976821c0e2ac910394d191b6761aa4e41fb995e8360930d6766d68013e5b340e1a9b06ac7108374b14fa9fb378098b91 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602864027000000 1603468827000000 1665936027000000 1697472027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x9bd261f42cb7be585884414ec175eeaa7c446a0d3f26059cbd121a45adca55d377aaba48798f5d3c5094882194eee811e40bea8d5e18de1ee4393a6316b2eeb8 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1603468527000000 1604073327000000 1666540527000000 1698076527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x1e5e384e59ac0bdd77b086344af2985d8a5b1d4e58804ec47824d3d37a5927e0cedf43c55ce4051192f089480a1c61e31393185862e4781e563659684ca9d61e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584124527000000 1584729327000000 1647196527000000 1678732527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x3a1e88834aae2945a1143238e367ee63ac473383553b81e78b903246fc2ad1e20a4ec6fd6c9400c30ec0883bc0c4d0bbc6824cc3e155450faec74144070d164e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584729027000000 1585333827000000 1647801027000000 1679337027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x440ecd32fd5ef557cd02a53e17d936a34f7ec084e5aaaad521cde1ac77d4a4ac094eb424e8ee25d7389cd27763b7a92ec1522d637b8f953b59c767b789f6ede1 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585333527000000 1585938327000000 1648405527000000 1679941527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x6dddf8715186a8eb2a1cb88c6b31becd0205db2956289622f32f055a4985070b230996e1ed5faf8db243ffa3fd9e784e34c0e014f232b4e29f8a7ab9e2f22caa \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585938027000000 1586542827000000 1649010027000000 1680546027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xc1710f64ac6a6b8fa3d25387cd064283e52e2f79ae6fc6ce48df855cf7045634af2f1162119c48925ecd8a483b36b6545886deda8757e3b18cb79aa77ae409ef \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1586542527000000 1587147327000000 1649614527000000 1681150527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x29db9458faff8b21df3e0b3f84c92d386501a1071dc4c3d8eef767cf0c4578d3aa929be60c01aec4575c79886ee1e174384b7265d1c679dfaeabdfac33b7acc8 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587147027000000 1587751827000000 1650219027000000 1681755027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xe95b5d5f7ef71a62977150668372acfa9bb3418ded88dfba4d3e4b392d3cb5e9c757dbf8702934f96e785b093373ee10e16988d6a894c43c50b4be34c0287f05 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587751527000000 1588356327000000 1650823527000000 1682359527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x3ba78adafbae2851f7e89e251f96446b3f07582968708786333b9fd72f50c008b29a474b6189140e71ee4aea2a7b2ee056e3684162096521afd8efe97d6449bb \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588356027000000 1588960827000000 1651428027000000 1682964027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xce3424320907bdb4b3192f3c384eac349b1c1262752839a341ab7202c47d4f78cf8b8ce590ede72cf1d755a2397ffc26e575e04d9de5cb024892e2954f4a72ae \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588960527000000 1589565327000000 1652032527000000 1683568527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x0ad8cdeffa1fa20ecc12fc359a7c9ee5dcbd86e3c9e45bea047b00a3a1d3d93b643a053c76f06c6e20f8fcd36af54c2055f377ccc98592de6bd1093009a1e296 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1589565027000000 1590169827000000 1652637027000000 1684173027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x6c7918da2db6cf0d50baa637a5d6334a9348cffff793bed6775c1834f9768f74d81386eeed55fbc9ebe3d97c52a797c6946d069fde4272cfc9c555394fd7965a \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590169527000000 1590774327000000 1653241527000000 1684777527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xe70d5534d31b0542c9c4e020d5bbf140652d0c6f36aae26fca18ec4accf1a7bdf82fdca08140c0719cc40d8153ef52d6c6cc53dcf55e6369c416ceee888d93de \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590774027000000 1591378827000000 1653846027000000 1685382027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x99ade2cd19f3699cd23d31ff44f55f69e40bc9fa0118304d17011871519e035bb0ff14f3fe045539f4230d2aa171ac6d0d38a717cdf60645a906c8e291ea8fd4 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591378527000000 1591983327000000 1654450527000000 1685986527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xd4fd8e4a9c5bb20e021e9333a1871f31b78e12cb92113f248bcf303ddd3285c184d5704969caba9be0226b4e9ce222dc033cc491ff16bbcd6b2b1039fad3f9a8 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591983027000000 1592587827000000 1655055027000000 1686591027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xccb767410225a0a5655d3e5852e2b7a3014676365fa1f8b2cdbd98dc9c39015a26da313c2cdfc8bb3c14fd6aff08142eff1d4c250a24c1989cf3fc1b1a89741b \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1592587527000000 1593192327000000 1655659527000000 1687195527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xc2e39ed6d084ee01e55815c586e056c98be19e0eb1bcdde16299b310bcde1cb34785cdc11ac318f2e0cabffe9b835bd3c174da2973f35a1badc423bee334d13a \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593192027000000 1593796827000000 1656264027000000 1687800027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x4b691ba37fec3d24255cf1ace52e36a9f288b36244c0c61d7007cffd45513d0527f5f3c05659b4f9fc8a3185b24c04b626cf0d751ef0b77f02d1506a3b7cce9b \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593796527000000 1594401327000000 1656868527000000 1688404527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xb5807d51b5e20742015336a4c2208a8928d91c1a1ff2aed45d73689a6030ea41acb940321a45e02c25b92de7665387de7bafaef2db8bb29977a711ff3e1e4cd1 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1594401027000000 1595005827000000 1657473027000000 1689009027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xf5a7447d8227068a31470b1b3c070b6ceab3bc63e0aee599ee753282ded24670414ac04be069d26f885f1fb29960e6e9af0846460fd220a3169869f83fe3d825 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595005527000000 1595610327000000 1658077527000000 1689613527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xf8dae00595714666ad8c7881ced4755c6145c730e89738e66eec4555cd64003ef9080496ffeaacb3af335ff988807c820de0865be9c36074a805aeb22879ad5f \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595610027000000 1596214827000000 1658682027000000 1690218027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xd3ddd0f005e026cb906eed33c0bc4dfa1ec62697788deb415584b68ed7aabc6268591f853aa36f8d691acbb9863a56ac594c358d060aeaab6e8d4cc5571fa19e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596214527000000 1596819327000000 1659286527000000 1690822527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xbd5e2204e03e457b19f62dc7ceabe1001a964248bf6840a19639ddffc9c3510b8ece27d12cd0f54c3bc58f27fc8bee6dc9b141280ff6bee9300716ec1e80c6a7 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596819027000000 1597423827000000 1659891027000000 1691427027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xa71dbf87f15110e37038bf5621c50a5bce5c4a1337474739bc4c42f0f40afbbc62c90445f30959480dd8d66a9c5852b0e93d8d90a5ac10023bfa3a7b65dd9291 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1597423527000000 1598028327000000 1660495527000000 1692031527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x29d6d00d949c4790c20d59ec73676e15534cb1c146b0916ed20615239fb4d712a0e0807fc9adf6ecb41615c28441c225ab32b3788fc8d4a653a0a7462cbecf19 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598028027000000 1598632827000000 1661100027000000 1692636027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x9a9e6a85a27507bb16676c418d12f3a8bac940b55e59109239fe063ea03c1cbfb3f1c1ead34d8a68437ef59e2c24fbbd72727349d928e368be80d6e3e23f28ee \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598632527000000 1599237327000000 1661704527000000 1693240527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x353a0d013b2aaae6639b723315e74a47f2549d602e3222007f254126a6db8fe8745c28adb772acbfbbdbc7548783fc3773d1f28f1a5a72d3c5bc629ae72dc23b \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599237027000000 1599841827000000 1662309027000000 1693845027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xb351b95acb03de7d85f73635aa79e54a3e72b9827f249c889db3a98b02129fd37d77552daa366c8c81c27df9a443bf846c9a1be996b3141da2111e83b8192856 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599841527000000 1600446327000000 1662913527000000 1694449527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xcc6425da4bc0b5708ec1bac6f43f6e3d96354716b6e969672b159d56a94bec8a7384b96bc11fc0ee08d463f42003816cf1b243315ac8047ddb2591e040891a87 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1600446027000000 1601050827000000 1663518027000000 1695054027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x1799d4b35aafe948b59f3d0eebf034d61c1d45732367a54d5811d8d694edc58a2034262a822a83e14e77e13d4d332bca71ffb284ba012cdeea1c9d321d5f2dab \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601050527000000 1601655327000000 1664122527000000 1695658527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xcbaa0317d27bd90dcc9c1fadb0bacd291128775a01a14e719f23bdff89ed764aa4783ddd0f3ada1568371f0318cbc172721de0f4bcfc4ec674b295fea79db61d \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601655027000000 1602259827000000 1664727027000000 1696263027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x689e5487355ee63b3fe1fc74f6e68b846cbb53c6698d6436d2cc602f2b689ac03b0d9f0b3d2e3b007db10b92b5624e179861df7daee64361c3a1757c1b94798a \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602259527000000 1602864327000000 1665331527000000 1696867527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x7978efb03620d4808a5eb9ebd4d2363e1d2a28b5464f3650832f5a8a5f96e568aeb26febb57cacb2e4e097000ddf987586ec6078d4d459777b9fe23d959172a7 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602864027000000 1603468827000000 1665936027000000 1697472027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x5a3ef191b4f27a6667f4c34ff8794136ea9b3e3a29e7dcdd6475be15f8b1ac09c3ad2a8007b46bdee3b3196ffc864a685373888e4bf35a3c8f9750d5488eb008 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1603468527000000 1604073327000000 1666540527000000 1698076527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xdaca7cdc05a7e781c04ab1e233ca2664bac59caed67e4d00fef43e31f191487a7cb91d8b862d6f9329da670ba79e6aa599de8896a6032249796f50870eae3933 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584124527000000 1584729327000000 1647196527000000 1678732527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x70e1710ad98a6a196e28d1fd5f47e3efcd1b8e7b2f2c3a827876d2ac95f522849d4d310447b76c798fa99b8c2d2f778f584ea1794f9a6bd67e233f9755d0fbdd \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584729027000000 1585333827000000 1647801027000000 1679337027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x026828646242d17763289929b2e79e08ed0af6ef5e94c2e96ba2ec22fe38c4df3557ec2b99d7e6c1309a4c63f830548a86789d73e7032b7524d9b686be8f7db7 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585333527000000 1585938327000000 1648405527000000 1679941527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x9c46be3b92d42255ef22a8d18d3bf65d2600fb7b034d2f8d1c09dabe5a345f2e05322f6db09e8c70c0a52fd091ca4376405e85e6393a0a3bb7ba02894bc251ad \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585938027000000 1586542827000000 1649010027000000 1680546027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xb1dc83372de79855773717f0bc277c05909631a21672115b41ec805552235854229edd90b3b8334e1bf0eefe781167440f68bdbb61a482457d7c526407527eb4 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1586542527000000 1587147327000000 1649614527000000 1681150527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xf0dac394e0c22dbb44052708ed32560413d530b0485b238c5a92e02d5020516aeffda52bfaa95a97038727f0e3679bb64ed63e26d46c3c14bd829add29c6e339 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587147027000000 1587751827000000 1650219027000000 1681755027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x9611496044ae44b36d15c142db917df55069befb21d6f7edb77727028f7342abeae8c7a842d0f43620b54d5845f1e3ccc886326c1277d7455e406e6a026cc5ed \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587751527000000 1588356327000000 1650823527000000 1682359527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x8254bc93c919dc2e169b97e6bcfdf7bb16b466cfa7a6c047248d37361ac31df661165e9a9f633b39922da22b6121d65e0f9f466f3ab48da861728defc26b0793 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588356027000000 1588960827000000 1651428027000000 1682964027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x22687bb7f5689e6cd945fa27c438e9efb41ae3984aa990f6b10940e780c410d380735d6b13b3f5c4294ea2124ea5c4e935336df23e84e0aaddec8325118a72a0 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588960527000000 1589565327000000 1652032527000000 1683568527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x701ca07967f0258f55a69ab005c59a8e830b6d0a6cc2b057754d60c05048ef5d391e708719cb2b5b7e3a331ea0c382b19fdafe20429f6f17dcff306c7bcb6519 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1589565027000000 1590169827000000 1652637027000000 1684173027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x94ad0c4fa48c6d19e1b1712c4b6330f5f156303a6eb3ce17e79320df4e5dc42641a09aa349271ed6341fbd3c70bedc14d1759136cd1dbaf34cd3c3883093af2d \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590169527000000 1590774327000000 1653241527000000 1684777527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xc46aa522338c44a61794946022c7aacdfc177cc056a5ba686d7304bac4c99309c8b053a3d47381d1550dcf31370485d393bad7cbbabd2fca5dc0826659e1be70 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590774027000000 1591378827000000 1653846027000000 1685382027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xf4646cafb8a856272b8a6896df96dda4f6f4fdcf3d799be76930af7307b88316f4cba350a06755ed97c6105a40964c29db27b60f85e9d883185ea90237eeef09 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591378527000000 1591983327000000 1654450527000000 1685986527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x20fe9cc3b1cb73bef401048d55f789dba305380277596dce68fe8bb983840934520ae78da7cf22a760bc5281b78eca6f88b9f56d47bacca513c4aec6c12daf44 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591983027000000 1592587827000000 1655055027000000 1686591027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xcdcf0e88cf82d3cfd0c3abffed069072a54887fcfb154826bc23d3749d748d2e17c9bf873af4b315efcd2c1297b77c4850b7e76f84455a1443217a0a358775a3 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1592587527000000 1593192327000000 1655659527000000 1687195527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xfc7567301d3779518ec847800b1028fcf15f715dd717bfe3c25a3cfbceb2c04450694f1864d07ec6b1c47d275d2fa486030342f6156a1f83c2b6c24859594a47 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593192027000000 1593796827000000 1656264027000000 1687800027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xd7ec29922a2ab2c75db8815c37639811b54dcaf790a7d647d88dfd9381118d0ccb0fc131d56639e07eda8160d43ef211ebafd16980d8aa7a71d140fb84072daf \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593796527000000 1594401327000000 1656868527000000 1688404527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x425b7bbad7b652b7ee98590299f06e4fef593b4a3c190c6f07e2def0fce849b95f131f8be12a7cdf6ae0f4e4786cf73d71682727b9c896416f73c41ba974e7ac \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1594401027000000 1595005827000000 1657473027000000 1689009027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xd001bd79bdf26801b8c94490190f35af5e66d7b55e5c1f69e92e71faaf590b05046231f1cd1ffd3d1a1f58d181f5e2aa9bfbce128b7c2dfeb8ca9dcba2bfe911 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595005527000000 1595610327000000 1658077527000000 1689613527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xc5fe89ff0980ec18e88168476f477efb0933742a44091aaaddcc97545e078e8c385600a70041857fa8083394b0e8ea6ac0000cf7a03cb94ea97b45186929d824 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595610027000000 1596214827000000 1658682027000000 1690218027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xdbbd22a161dc28ebeb39c8c19b62c24a477399c6fa69a4cdd3c405d9dc4fccbda2c244b66feff49aa7eb7b58583503f34a4e73e83e2f6329d54c59df811a2dba \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596214527000000 1596819327000000 1659286527000000 1690822527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x45ca11e9216ea067d43e637c6a1c915afbadf21429d7e704e5358239289024b48c9c955e3d010b9ad2bba0b2c9fb493c3248ea6bb5065bbd6238eec1f522e005 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596819027000000 1597423827000000 1659891027000000 1691427027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x1c3c8bc7b05cb996362e2b4f9d4d02942a55f168f82ecd4fd91226f4d00420f2fd1d74331490962cce588c2fa593584ef247a4bcffbea9fc6219cfad974cf7ba \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1597423527000000 1598028327000000 1660495527000000 1692031527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x2320b1ad58ba388b2c77dc68b3aff1c8a190e1956867d0e1a205466e830f2770b39b545fed68dbe24ae6ec2782958f263a225814f5fe0c2e09ecffaeeb617d2a \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598028027000000 1598632827000000 1661100027000000 1692636027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xdd15cc1ccbc9e88271efb73fed0e16d0f2f6e49fe81c105facabc5fbeb53bee1305c3eef89325a735dd0f9bb7d3a4bd9520594e6c9e5e41b5163c24c5127b09c \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598632527000000 1599237327000000 1661704527000000 1693240527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xd1b124342be72b23b3e0e68ffb9f8b538e30dc828ce4a582c32e8f78aa39ee17d7f939beea0078609f6e45ca5f78a596709c2f98ee0a714967ac1be79798a29b \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599237027000000 1599841827000000 1662309027000000 1693845027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x9c4af6f6354aca37595ef1deaa37e5e7cb8bd7530e635e3d395e6902615aab6ea9fff183823c20d97e76ccee0b82d43571e2b2683fdc4e913c71bb66ec162036 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599841527000000 1600446327000000 1662913527000000 1694449527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x89d7ac9dd5b2f6b576b754f28b1c52f4cd7ead3bb2186edfe6235528b7250331f2eb4aa460e66753addfff4b1189ffde244472e98c9b6caeb7980db71231699b \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1600446027000000 1601050827000000 1663518027000000 1695054027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x99a69e5e26e2f8e5f83104c79dee072ee6018669f8d7b0f0c666d2e2f775db3423c0bd186facb12f9eb200b78a84afa21e7f06f0c95f0d42a916973fd0f2208c \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601050527000000 1601655327000000 1664122527000000 1695658527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x582a8c78af9af9a6ea680ab0ae51582123676c98a2eb206d2f5e9b023e448ab6e1b07702740fce88e9cd9fe3b9db8a107ffdbbcbbcc785e8f79052a4cfdfc206 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601655027000000 1602259827000000 1664727027000000 1696263027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xc1c5873f0daa2d7876f25794f58f81e01f33de23842c7861a02daa8813ff1ba8b8657cbd5f97aa9ae80f1d6933897ce8f6bde911e2e80b06915d319b4af03528 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602259527000000 1602864327000000 1665331527000000 1696867527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x4a70b8ee6a64968de4911cd02ea3c24fac7c5c6d6cf1ca2b6f1ced96073258c5cd7e1fa5a6c731d9e6ec7a282f457eaa49027756fab56bab60c833ef9230575e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602864027000000 1603468827000000 1665936027000000 1697472027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xaba6767e5fd7e407e88a1611419cc2493fd0855029fec7b8ee354db11c25e5e8ba3619d469789167a1cc31c2bf989237bf84addbe22e2e21839e4287e29df2a2 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1603468527000000 1604073327000000 1666540527000000 1698076527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584124527000000 1584729327000000 1647196527000000 1678732527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x219e13f4cc746bd69a9e42d0064f18f01df10a300f1e7f0eb6b702ce609c1c298cf79cb314ee6e6355b34e9683d010cda9feb50322f30f47604475ecea87ae51 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584729027000000 1585333827000000 1647801027000000 1679337027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x38a6316d7733d9ab22dc1ee21ddeec54445ab802265704f2cb1886424d2c71c9e01282bf1c7154bf812fda1515eb61b0e71ee6d3594a099f5e2ad3b7543a4256 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585333527000000 1585938327000000 1648405527000000 1679941527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x7129214704224f28016e13a81c5580ec25d516481a672541353410afc1eb28da038fa5429f2e324e95f96022279f8099c79625d3136b4c574d485d4370130a95 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585938027000000 1586542827000000 1649010027000000 1680546027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xc7ca973d4625d38421b350ecc2681bee151fa3890ffc4bf01ca0dce134ec798249cd9eaff9fff59833bcc8acb740c26d0535989dab7c2dd1aef9a68641a7fd2c \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1586542527000000 1587147327000000 1649614527000000 1681150527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x0c8cbbbe4b792c026532ee2b04eead7061235e2cfaf6640e4ca3386b5b86d01ab079c357186c5072e4a1af19c1b7d7fc5424d5b302530513605e36d6086697cd \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587147027000000 1587751827000000 1650219027000000 1681755027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x932b578fac6c518e2b2d9ae8590e67b3a5cd16275f2413aa545435f0f243f1b917cb8b99543669c5b075ea339b5f65e6d3bf834c1f379ae491b3eaafed97fbb0 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587751527000000 1588356327000000 1650823527000000 1682359527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x599ca0bb385b935f35c4954295ebcaa36ca4266987a715bd8aa4f6840267853c82cdb4f3b8065ec2c75f77c72f01417acb92a427a7dd1eddabc8f587ead09e60 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588356027000000 1588960827000000 1651428027000000 1682964027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x1651117a6ecb0e06b992727774ca18b3c6b74af6e6f6a216403a1c50f979712c8936d34b4689746e7045bf6cabbc1376b78a10154810179071efec0002fdccda \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588960527000000 1589565327000000 1652032527000000 1683568527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xab36a85522f86017ed676eba3e5edd2cad851339a7c2e6cb25e7810782b05b4e444e98f60280faa29c71f41958455b75ee271a547b2b257b3a12d2ddb8f1a1cc \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1589565027000000 1590169827000000 1652637027000000 1684173027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x224f0386a1b2de9542a2a6fc46c670b9aff12adca237f220e485e3b6a3fe8daa048bc09feb8d5cb9c3aac9653a58bed33a3ad39395561c2c99c20cce471f2b56 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590169527000000 1590774327000000 1653241527000000 1684777527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x5b9e014d272f57e4bd324400118d99854f12bb448d4f7478ce986fe8bde7bd9feebed3bdfd9fe2b28b36cf398363d7b291066f21ee6e601dec05a56aeaec91ef \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590774027000000 1591378827000000 1653846027000000 1685382027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa380b56b294a436a4b80f24016b5d0eaf28c92d975b399fc29bc3aeacaa7400d0dd3cdad622c4f8a2c0a15dec3601d6f75c45df3306a7901ce50b95af5fc4fbe \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591378527000000 1591983327000000 1654450527000000 1685986527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x42b90336763f126f6144297aa83afce12f9c6690c7c1e2866afc3497cf526d997338399f92639c80e2362ccac4e4de71930aca47fa7718d0bd74f49824308a3f \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591983027000000 1592587827000000 1655055027000000 1686591027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x6576b9dfcd7d9e6fb5991cca26a3eea1584637a0dcdbcc892e5ae5580cd542723c313ef89571eb2269adc40633c938e840c2e32e38c816d3c4e8ce40a18e03a9 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1592587527000000 1593192327000000 1655659527000000 1687195527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa6b81af3384f1a3402a8cfe1bd9635cb5a45e1394cdb9008d01a7db888201456dc3331c344cd246c48e216570cbca93f1b16c5831d9d6c70be25659cac679486 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593192027000000 1593796827000000 1656264027000000 1687800027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x574f9fdc3ebafb73ef88030346728d582d62a8d942cea38a11812f97e578af30ed0f29b893b3e23685864380fba443a6f302e55660fb23f3ad424a8ff3c30bd7 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593796527000000 1594401327000000 1656868527000000 1688404527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xff224808ff0ad42106d317584aab3cac2124212c7b06e6bb51209a03259f5c9cdd7e062b959342e57dd4d3c6d7e41556ae484bda9b6883d29f565ba445b08356 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1594401027000000 1595005827000000 1657473027000000 1689009027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x5b928f1516a891672a807fdbdc9161e69d3e8ad738efd8984f546dfd7af15dac67481882daa25a1cccbcbe90a5f95b8aaeecee7a67946525991aea83c87138c4 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595005527000000 1595610327000000 1658077527000000 1689613527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x0ef1a5d5333fa440ee46dd5c6e1b8e124a5f1bada7afe420ad600ace26657de38696e805900892b7d34b623296bc8af8748f9339e9d3e4cb5651999927cf4bdb \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595610027000000 1596214827000000 1658682027000000 1690218027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x20d2d6aa2894eb8ed375f6e0b39532f9ea83fdd5ec7f6615c270534b27d6538f0289fe486843da523cd6d59783677447ea6fcb914945ad85b7133db93d0d52c4 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596214527000000 1596819327000000 1659286527000000 1690822527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xdff6d8d844938fd27d142a4dcc0a5e2b6ee848cd0657b8b9afab2e6d2265b6f66c0638df55d387d0a17b30cb9d7283a9b64476fbd0acb4b95cb86ade59b42cac \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596819027000000 1597423827000000 1659891027000000 1691427027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x62af1aad614c87e4c7211e2e1a6d404259666145600da2ca69d2e0a4f3f0ede9a7ab84c8b8a54e973eb59418df7a35d3429368f179c1e216704c1069028e9de8 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1597423527000000 1598028327000000 1660495527000000 1692031527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xcebe8de4b2897b5e3f52d99278dc0fa28b5f3722e93183a1755739d4a779c137d9399acfe7893966a45628b4ceaecbee88a6b4b9280b9f60d41d48d943da0677 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598028027000000 1598632827000000 1661100027000000 1692636027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x2ac1920af6289b24cb867bf6ec8b70c8b3f1df824aa936b2c383af03375493a49184525d4ca8702bb7dc4f60d414e03a0859053d0d36dcfb5889c543da59759c \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598632527000000 1599237327000000 1661704527000000 1693240527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xbf00094b35ee43894327f7502174a5ce0b3a6d04f7469bdb70b2f7793710a0f4e34c088d939b2b1c4569e39b3c06d28773c6f7a091e7419fb6d64d321f55bfc1 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599237027000000 1599841827000000 1662309027000000 1693845027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x930afff580a3d98056ef09d557c0efe2696c28626e926f163442084190781116a2619da31d57905e5c07b32171a11be861c06c0cd032db27f66337652f6b6dc9 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599841527000000 1600446327000000 1662913527000000 1694449527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xf427726927f6deb2496e0899c5126ae1ab5594e0a9b4cb9c2292d822b5e6bea9c1204c96817ecb6ff19283c2e08f77f174c9719f783db029d1008ddf014539e3 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1600446027000000 1601050827000000 1663518027000000 1695054027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x82bc94f87de76b23d6d351dd5df662852d2153f116c7fed549834dea6cb58ba7d1121bf71dafe295646ce6657e1b73a38e218a701bb542215922dd7a4b399caa \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601050527000000 1601655327000000 1664122527000000 1695658527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xe61c2d268d94f57c99b3d4b82cd2b9a83aca28e1f3d7597245787e6d3cd1bf9a0e33190cdf12a7b830c0a5e86fe0a7133b442e2f49472d1c3c2ea3a401d34c29 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601655027000000 1602259827000000 1664727027000000 1696263027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x8973c5a7102a6da989320dd6bacf6a4302c2b58dd2d4fcd41d6415eaac1258daf3eff8b5efd2c394acdd2dd2562f8c84efbdf7aa6c1597591579a672ffe51948 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602259527000000 1602864327000000 1665331527000000 1696867527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x8fdbdb6b90c53ca421f9bdbcfb7f0c14c5da59b2ac010d264aa76d4c0708f095794f57f07d1c1abba907a8f77ef9f8d41ef19661d993a1196a9771a93fea98d5 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602864027000000 1603468827000000 1665936027000000 1697472027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x7dab82a9348a3ef8616de6b8784ffcfdb5e4fdf47783a0c60017ec646b24e7f28034e1550a7c705f0e9ede1747f3fc1a8bfd77b3cde21187325094b3d274dbfb \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1603468527000000 1604073327000000 1666540527000000 1698076527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x7668572c927d369ce701c10f7cf48ca67148183b126bc793ff741c54ce60c366e53f166c57a8c2039c894b521ef69e967c724e6a9261e2a424d0c8fbae3736c3 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584124527000000 1584729327000000 1647196527000000 1678732527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x20c978f9a2db37186d61ff11d9e30dcda92b2d8e29077bac309f9db9be570a75f7543935dc75f976443cb953832215fb2520cdfb3f06abae1e9e74c45c0fa589 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1584729027000000 1585333827000000 1647801027000000 1679337027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x4883660d80ed5b9651c06a467a913c5504940fc360d607fc922f5aabf1ef0e7567d952eba4764dcb1a3ceca37e42a5a49e86ec724240789eb6955866255d0c7c \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585333527000000 1585938327000000 1648405527000000 1679941527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x22348d1ad408d8ca6fa6d65ade4567aa687645a75b7e697af05be4df8fd8e5726b859aac44b6d0f6b589b105eb645842e17646881c31e813ec54f98de11c1194 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1585938027000000 1586542827000000 1649010027000000 1680546027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x5ca639afc97ac81e65ed5a047ec4039c2f01224fd70e58863a382a7f9af580d27d0e70e217a37976ef3bbcb29f9b8aaa70dea6c55654a4674418e1565b7bdfe2 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1586542527000000 1587147327000000 1649614527000000 1681150527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x6c7ed8d5009908068dbc107ed5740591c2934c2b20a17db1a5b5c4cdacc23864187a7c24e3e02c5a51a197e3121ab4d18f27f1070b98869f4b8f1381328a7c96 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587147027000000 1587751827000000 1650219027000000 1681755027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xf68937d70a7a1b3c9eb5932cd8760fdd18efd455b6f8e881aa0be1188466573f5b3d295045d07bd65d197dadc310484bad2d8fdde6dbc7c429aba29b8a696d84 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1587751527000000 1588356327000000 1650823527000000 1682359527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x12f79d30c267ac4f4c96d9c4a7a3cc20b0344ca6fade34c4d2c101797a84fdc49552f9dee0c56a535f36fec2fe81fddb9efb2a78261b357229c0071fc7685b46 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588356027000000 1588960827000000 1651428027000000 1682964027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x0f1f0d0a5cce6e3e7351e308f12554eeb9636729232575e59b315577a72f88be32e0ca83c027b4a69b98e02aa52ac88816d06e40b7a98964ca16c13cf8037c8c \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1588960527000000 1589565327000000 1652032527000000 1683568527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x6f446b124d0ba3d24fe2b37597dc3771f2f3f8a30a416f799b113a062b61c1a02282d66b5dc3552739a8f4f72d2e0169b0ce4b241ecf6855b43cee59517e9b2a \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1589565027000000 1590169827000000 1652637027000000 1684173027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x48db11bcbdc2695aceeb3bf99b43975521af749f0c0798117a3499db593094423e2c585d2fc605ecdb1f540ea9b7c46e74d5f69c8eca106bcfac840f8bd136b1 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590169527000000 1590774327000000 1653241527000000 1684777527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x12c102dbf4cb63761dce1611e278a075b617920bf4ba837a584dd1d55ce02423e2a3dc5632cf7b71671dea5b723a499cefa86e8b75adea0631ecb7d4c283dd0d \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1590774027000000 1591378827000000 1653846027000000 1685382027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xd2e2e3cc32b8db5ef884246990dfd5fe7a739b021db8f764a1dcfe63ea72f01cdd61ef75e51eeb4804c7620bede23ab72ce7b70ed4f6a4c61e33580cd3825ae2 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591378527000000 1591983327000000 1654450527000000 1685986527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x981de637694002caa02c2d44d2f9ebd56a9c3bb4df9a41ecb00a0fe5d7e587a456c35ac5bbef24165ab38db6a1c5d7e5586a0df332f3a01272ff7e52813cf237 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1591983027000000 1592587827000000 1655055027000000 1686591027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x1ddf58992df18cc06f19ead407f8a67330ad63dc896b981e907010696dd30f3866b57eae146946582b609e4d64089c7a88bffba4f66b8719e1482495e4f91a7e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1592587527000000 1593192327000000 1655659527000000 1687195527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x76c4a304399a29a22602a7499dbb603a98f59f75c62c07a1ca8c62de960b2ccfb4f8558675ff5ca18ccbdaa1ab6ed55e1d66d7422ce790c250646cf03d9d957e \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593192027000000 1593796827000000 1656264027000000 1687800027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x4e496c18160552ad02b3074d1a944062573ee32f25f62c5f2b803be49116d87397bc9e5f0f131c08f7914e770e4ab685ec98eca9216fe1ef668afd20e2fc43aa \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1593796527000000 1594401327000000 1656868527000000 1688404527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x71dca7e2794fd0d21b785e82f27fb351ebf06d4391a74a6feb220ef03632fdd61bda97d28cc3b4a039affebbc02e1532c4546eb5a7c1c1990ac5394335a910eb \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1594401027000000 1595005827000000 1657473027000000 1689009027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x789c55f97b0f830327f8a85f80307b590aff4392ca3f6ae98ebf7838b54d293d74e8ef9163a32b07f0a7288ae515218276cd66cdbc9e95ace31cc34dcd93c012 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595005527000000 1595610327000000 1658077527000000 1689613527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x007831ac13fb2fd91a3c534f21cf7cdea6d9780aed594c42b15d7fa86c119636db27324fc66c90523ac3e34f711f7128cafa8842e504acf3bc26d5fe3082ac7f \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1595610027000000 1596214827000000 1658682027000000 1690218027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x854d619f4d4e057244a431e6e22819f1cfde23c714d2dabf7b090736afbcbce1ee7140ce7b56f71623ffa83d50d7681be685d8b2dd5d34e388ba2fbea0e85761 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596214527000000 1596819327000000 1659286527000000 1690822527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xd21902884a8475d70ca341e11cad968081041a221797074b1cd63327806802c8900efa115d3d851895d737c38d7c709b1807b3332c43ffe1723e25181b1d724c \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1596819027000000 1597423827000000 1659891027000000 1691427027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x1dbd8b1d3a1c032f0b833ec7bf3f4848febe6489449b8e8e9ce8e408f2932ca6b6951a7cf2f493a99751ce064b7b23029da1c56242a9a0639b0fd8ae93d1212f \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1597423527000000 1598028327000000 1660495527000000 1692031527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x8f7f6d914a70f57e8480f65b732478cf9eb808ec794d472d9159299c41fab8674e97378039e21cbd79ab67b1f60cc5fe7334c584e6fdd53255df8dd2e1234a4c \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598028027000000 1598632827000000 1661100027000000 1692636027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xb02bf813b2db03452be8bca6a75245131e6e815a34fc0b9c807f2d09e082ebf20e103b9fb0262808343bdd495d11dc1fca022a718fe596514852e46ebd30ce7a \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1598632527000000 1599237327000000 1661704527000000 1693240527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x8a0f9e4fde53bdc1493c68daa3868b3168ba7b65a8ca10733eaa57af364d14331b2004632e253f4b0d5f722a2240cacb3edd3e2c799d9f2a30d110af6dad4209 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599237027000000 1599841827000000 1662309027000000 1693845027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xc1f565b59b68e91567944d3920f18b0342f22cdc29c8caef2e565a62a7cd5e6c408ab30e4eee68a3717eea9856e51142510a1cfcb5f2ba7a71ae68cde062c3f8 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1599841527000000 1600446327000000 1662913527000000 1694449527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x355c36c58f56c0639b6f32cd28c7e6f55bffc12614e1fbe7521211cde17631826e2ad632c9eeab6fb74385aeb600d61c342d8ede9ed84613f024006a460c6a80 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1600446027000000 1601050827000000 1663518027000000 1695054027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xafa1af804d576aa34a733bedce5aeb778fd7afa5123668cc30aa6ea5740a3a6cb6a7bc834c471ea17e9a0b867926d0fa73cecd85a70a5d6dc6ef6aec92912010 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601050527000000 1601655327000000 1664122527000000 1695658527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x6a0249c2536390e37c24462960e36c4f1a503be881978bd2e634990537a92568f6fc6490aa100118673ff64a200e0dca758ac2c783115eeb8e7df36bf0db3b27 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1601655027000000 1602259827000000 1664727027000000 1696263027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x8cea8ac77b34e19673eb0371460636bdab438dcba72fcab2fa302384fb4e15d48b63a245949e52dfb4170566a14cc4456a73896b7496fcc24115a4d3a31a00c8 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602259527000000 1602864327000000 1665331527000000 1696867527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xcfd79051d789a6c52ad31cd300b0f89362dc008da611ce6b4a9fec830b47060a963ab5437c5154969a53b62ce4a5f9bd1e5e04e538b8195a3a8056cf661a12ac \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1602864027000000 1603468827000000 1665936027000000 1697472027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xcef49acfd01ea30db38bbe6b73d15c1a56ce590a58f77cbc6206373c247989835391be91ebd15e00c1ba9249b08bcb0578588ed46fb908c03e19d4fe34295366 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa 1603468527000000 1604073327000000 1666540527000000 1698076527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\.
-
-
---
--- Data for Name: auditor_exchange_signkeys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_exchange_signkeys (master_pub, ep_start, ep_expire, ep_end, exchange_pub, master_sig) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_exchanges; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_exchanges (master_pub, exchange_url) FROM stdin;
-\\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa http://localhost:8081/
-\.
-
-
---
--- Data for Name: auditor_historic_denomination_revenue; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_historic_denomination_revenue (master_pub, denom_pub_hash, revenue_timestamp, revenue_balance_val, revenue_balance_frac, loss_balance_val, loss_balance_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_historic_reserve_summary; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_historic_reserve_summary (master_pub, start_date, end_date, reserve_profits_val, reserve_profits_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_predicted_result; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_predicted_result (master_pub, balance_val, balance_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_progress_aggregation; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_progress_aggregation (master_pub, last_wire_out_serial_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_progress_coin; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_progress_coin (master_pub, last_withdraw_serial_id, last_deposit_serial_id, last_melt_serial_id, last_refund_serial_id, last_recoup_serial_id, last_recoup_refresh_serial_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_progress_deposit_confirmation; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_progress_deposit_confirmation (master_pub, last_deposit_confirmation_serial_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_progress_reserve; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_progress_reserve (master_pub, last_reserve_in_serial_id, last_reserve_out_serial_id, last_reserve_recoup_serial_id, last_reserve_close_serial_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_reserve_balance; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_reserve_balance (master_pub, reserve_balance_val, reserve_balance_frac, withdraw_fee_balance_val, withdraw_fee_balance_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_reserves; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_reserves (reserve_pub, master_pub, reserve_balance_val, reserve_balance_frac, withdraw_fee_balance_val, withdraw_fee_balance_frac, expiration_date, auditor_reserves_rowid, origin_account) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_wire_fee_balance; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_wire_fee_balance (master_pub, wire_fee_balance_val, wire_fee_balance_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auth_group; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auth_group (id, name) FROM stdin;
-\.
-
-
---
--- Data for Name: auth_group_permissions; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auth_group_permissions (id, group_id, permission_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auth_permission; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auth_permission (id, name, content_type_id, codename) FROM stdin;
-1 Can add permission 1 add_permission
-2 Can change permission 1 change_permission
-3 Can delete permission 1 delete_permission
-4 Can view permission 1 view_permission
-5 Can add group 2 add_group
-6 Can change group 2 change_group
-7 Can delete group 2 delete_group
-8 Can view group 2 view_group
-9 Can add user 3 add_user
-10 Can change user 3 change_user
-11 Can delete user 3 delete_user
-12 Can view user 3 view_user
-13 Can add content type 4 add_contenttype
-14 Can change content type 4 change_contenttype
-15 Can delete content type 4 delete_contenttype
-16 Can view content type 4 view_contenttype
-17 Can add session 5 add_session
-18 Can change session 5 change_session
-19 Can delete session 5 delete_session
-20 Can view session 5 view_session
-21 Can add bank account 6 add_bankaccount
-22 Can change bank account 6 change_bankaccount
-23 Can delete bank account 6 delete_bankaccount
-24 Can view bank account 6 view_bankaccount
-25 Can add taler withdraw operation 7 add_talerwithdrawoperation
-26 Can change taler withdraw operation 7 change_talerwithdrawoperation
-27 Can delete taler withdraw operation 7 delete_talerwithdrawoperation
-28 Can view taler withdraw operation 7 view_talerwithdrawoperation
-29 Can add bank transaction 8 add_banktransaction
-30 Can change bank transaction 8 change_banktransaction
-31 Can delete bank transaction 8 delete_banktransaction
-32 Can view bank transaction 8 view_banktransaction
-\.
-
-
---
--- Data for Name: auth_user; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auth_user (id, password, last_login, is_superuser, username, first_name, last_name, email, is_staff, is_active, date_joined) FROM stdin;
-1 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f Bank f t 2020-03-13 19:35:38.157863+01
-2 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f Exchange f t 2020-03-13 19:35:38.237327+01
-3 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f Tor f t 2020-03-13 19:35:38.302998+01
-4 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f GNUnet f t 2020-03-13 19:35:38.368415+01
-5 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f Taler f t 2020-03-13 19:35:38.434473+01
-6 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f FSF f t 2020-03-13 19:35:38.499854+01
-7 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f Tutorial f t 2020-03-13 19:35:38.567893+01
-8 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f Survey f t 2020-03-13 19:35:38.638836+01
-9 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f 42 f t 2020-03-13 19:35:39.051866+01
-10 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f 43 f t 2020-03-13 19:35:39.469868+01
-11 pbkdf2_sha256$180000$dcFtllevXo2d$0ihEIW62H0Cp7cO5hW8wZT0+Vgf+G61Ihvam9R0vUa0= \N f testuser-LX9PpmbM f t 2020-03-13 19:35:41.106028+01
-12 pbkdf2_sha256$180000$CAv2OAXaz8qe$CwXlK4qnwaN+Qr4T0BxPSaMlgxNztPkv/rpNI8i/4TE= \N f testuser-GlcUdYmc f t 2020-03-13 19:35:43.726736+01
-\.
-
-
---
--- Data for Name: auth_user_groups; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auth_user_groups (id, user_id, group_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auth_user_user_permissions; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auth_user_user_permissions (id, user_id, permission_id) FROM stdin;
-\.
-
-
---
--- Data for Name: denomination_revocations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.denomination_revocations (denom_revocations_serial_id, denom_pub_hash, master_sig) FROM stdin;
-\.
-
-
---
--- Data for Name: denominations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.denominations (denom_pub_hash, denom_pub, master_pub, master_sig, valid_from, expire_withdraw, expire_deposit, expire_legal, coin_val, coin_frac, fee_withdraw_val, fee_withdraw_frac, fee_deposit_val, fee_deposit_frac, fee_refresh_val, fee_refresh_frac, fee_refund_val, fee_refund_frac) FROM stdin;
-\\xca5e89c144df5f82fac1329f3c86754b0e0a105f7cf27eb9412d20cb48db7eeed585252b6a25a99c85ffaea656af87336881999a8035b50d7917c12f6a9b90c6 \\x00800003ce4886caec21e05ee1abe6f6f4c8d81dd0ac85ca353471a839da3750ad17f281673a1c29bf91454c595c6d8cc14aa335bba13c6ca6ec649f5df6a89335a6aa1c01f6e01fd45ebeef59d5c8d985c9bc92d524b760a3a256daf6f9a96465621469e31880129962b94fb7cc3c12571879f30e4ba3a27399d03913b12357c1fbaacf010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x87fd4d25c4af04e885408429b31019610af8eaf3378ea494ac0fd82389d2b93f8a1c27bb1903f7b0218d08ad85d0a3a1840f84dce9b3e659e4f6c8414e11c20a 1585333527000000 1585938327000000 1648405527000000 1679941527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xb13be79e272cf95452c46fdc192d07d16f24c0c0d9638bf2ba7814146c195272bd5f6a308e5c53460c4e5adc12c8116170bef14a542ceae0379dd0862008d868 \\x00800003e1a047301ab6f248378b1625136bb85c95b482305e46666861fde22919b117b8880145d62bd6e8f2475f2e01e0ca8c2cd31f385a9cd770b075fc75341d84d5b915f390db44ecb110538004c1595217fdf37015c474f24f55476f33a1df7007cff712b195ca54003b83d19680201631356e18a467eae4a5d3c8a0da68c39f7c65010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xcc734296a398733a66af965cee89886370b8ebf0a5194c551f371e1c97e62cb29e1b516b41752476b4e0dcad4bf3c8e079e0713256c01e67d1bc25f48d3f7703 1585938027000000 1586542827000000 1649010027000000 1680546027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa6576c13e5143d56edc29a8c4917121a1c3917f3436c26f3349de6c226bb96bce029469a4b627b504a3f17d70c4ad7e6c015b4873e98c87238c9b824018f461b \\x00800003ec4d515927a5c089ec4bbb1c3307282fcc3688aa855a3ab3d7b9575504aff6a6260784b77c32b8768d7cc5a01568a756397d4d2424bd54e29ba8e552e7abaf77fc232bdcbf9df532af931724ed3ac81f738e15513c827799115d19ca130fc81163722970abc9a964dd5534b03415d9102a899d0aeba8e1fa47d5b71a0eee75e5010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x9d4b5be1bf25df041cb98a504324930bf13328f1088e23a443ed2ba0fa2ceeff6fcaaeabcd1e8e9cf0e63b8938d88ef9f4cdcdfb33a22ed644ad30730185a10f 1584124527000000 1584729327000000 1647196527000000 1678732527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x19d70de23bde1e436057b52e826f99bfa483b84ce088c89daa91a32eab5edd2edfa2aafa1714e1cddfc6faf8667574e76bf139865720b73ac9d6d4e6cc04c5e0 \\x00800003d98f24063131deea0b9040bf7454597718f6f948fd144072bc6f13e7ba1dcd390b527dac570a543f4839dab9db73f57f8d0d00e7b3e03e3a1725268420feea15491262b8495e9c65fc16f5de4422efddcbeb6d1f3efd8ea2e9c851afff851e1bc5de62b1f2a7853edf8d2252d219c9ef6783a944266d40354d02a44e3b6c1b89010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xef6e3cd3c60c0cbac51510ae5e89421542ac21195a5694dbb446cdf3d82d49a25550c24a2e0ef1ada2bb1c3c34e04ffa7f111b11e766cfe95aabc724267bfe06 1586542527000000 1587147327000000 1649614527000000 1681150527000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xddc88ee65b9d5ffc9787a9154065eae790812d277640692b9de92d2ccbe417d95f649de7208d63a5b8dfbe9df3215c1c61862c14863168507078619eb14921a2 \\x00800003e1ee0243d9f85b5479929948af7e82c0f1b9c951cfbe56360ceaf4d71477bed24d39f12c3b702fb62340c87b7103e82ea4de7b865d7eb027ea35ac848d04386e973dbfdcb2dff9cac33584eb64790bfdd68b56be1c0c58b7fe7db494aac8fe547e9ec92e5dc1c67cd75b3c0ab1085e4b0098a1e1c15fc00b040204916bcbf94f010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x765eb874c74dcf41c9633a9a63ffbaf8e27a2738a02ac04db3f7c4b487b1b41b27f9b2cd6ddbf5120bdb8509ac06c61fd1442b5ab9f1e82af949da50b5d1300c 1584729027000000 1585333827000000 1647801027000000 1679337027000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xff062da42a2041b151a9a116837e70a11be427656d0e79ee440386edaa31181931fcc88667cbcfd1a47dcc382e8f770d897807ec2b0be258ca061eed3bee85ee \\x00800003c41864e17885d6a72358c7eedaeead1158d641ef6e21e406227bbf64371f897c657c658ec0f5380bf17ddd48900c081246962ac89705cf84fc0252552a70a3cff2b5e732f874f44018d7ac5b7fa19c6e661155ebb191798198e0cb365b61cde26c5cd795881d8099db156911752678c3bb308dd5d2d41ef6caf75b0802e634c7010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x4d07eb9010978ddc6299024f760ca338a13d458213bdfb32928455026dd9cd9650114776091369eca1d87a7935dd5363d9d9e7f7a849478ff7d850ef66c5a405 1585333527000000 1585938327000000 1648405527000000 1679941527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x9cb0ae54f9c698ebd27e4de1c80894794ba23e6a96651809bd62aaf4b09fc051248a210d11f632c21a9bf60eb8a81bcee79dbdf8827a522774beabb2e17f26ba \\x00800003c803d6a8bf9489f9545c3c199b18794e22313602b2e7f19b74df47a987c284c1887ec33d14ec34c1f25b22c8ab1a915b16a446018b7a15fcc4aefd453cb13b27e28af23fa21dc325f2608543b390d41b27dcfa2dbbe27b6aa9f230e13b9124225d1538b703059ab6a003435731ba7d2ff9137723f0f17f8d77d9980ab8c7a149010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x15534bb923b552015ec1be1ddbc523dd49e8f6f55d0a817e8e9e5c8e75cea9ded8d0cf62d973b58f12ac55e1b90953a2057ae58e5f518a07b08b2a8dabf52407 1585938027000000 1586542827000000 1649010027000000 1680546027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa96d1614b62d8a838b7ebee977db7f3d3b7c3630c8d6aee9c96e848c5b4f72200672fb3ffe229bb3c0ca63fda38757550f42170cf04fa8c647342d4d82abee67 \\x00800003cb92954f616154a0ebe5c193a06fd3018035f879bdc03381d2a83d43b6f287e3d4cf4806523973118374ba31f6a8a71a545c1fca5f1026bdc767db8f3eed12956599b1cc6555fe388eee70a61e8e795df3ed48e6c67ea7b88ded6f00f0b4b1550fb2493031bc553374b63bec73c348b0a8e761f1c1275deb5d82ca1c4e613c61010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xd86f56888547205c27f966130842c59f30dc2c78974d2f790e9db5a87c222ffc0dcfeb5c2ba337c148a1b0692835071233221e5a49a3feb98d7e41c1c20bcb0a 1584124527000000 1584729327000000 1647196527000000 1678732527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x73cc45fec9836dec65d8c0d9b9e0bed91b00a855f640a7ba5710ccf9f83b592660711f7fd27ad73cbe9b36ac8c934b3cf8dedb73a816a2e99338a600d171a09a \\x00800003d0556420e2c659389778fc16e0e3d5fb2b49a56cca48126bfd7930f4993dc9c09ba31b0bbe576ac6c4a5f5aad846022d9eeef2889123987cf273867a2ec2c36e2ecf7f0ca08d4a252a6a8432eb9dd399f7a135948caea9edfd0ce3f10817ccdd5999bc75126de2431f616518fc75e4889ecfd726cebd27e8e114c3976a87df3b010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x3f477a85d8436d200c6de9edf4bc47abedd3c669939cd31b855e0eb54551f91b141bd8abb8f0cb5fd0fa26a1672d906214d9c9af9948f5a1f864f1c75b0d9405 1586542527000000 1587147327000000 1649614527000000 1681150527000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xbb45dd1f40b446b36785e2178930240f1407da65a34dc2f4c39b527838e014ae2f5a5249a5db28ab0a64c1e37608fc7345508132dff6c135fd8c1bf651248dac \\x00800003c8517efeb52d4907be3df6b94345049dc6d8fad031a444feb8e90a320b1b1d91060b21fdd71ca7476a8cfb204102ed44413ca2a40e59a07864e2015207a392a44ddef8546fa47fab665976beff3674e9acf3e179c3c36f1246740a08f25bfcf88d775776911aebd57082730077f1fa594e265988388c2505a4baa4174265022d010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x171138cc0d662433e6f66bddefe985e61e68e0a1fa88d2a0d951199b14ba5d750a118d4228558dfdcdd03281c9149637c86c74ac68aa7f00afc384a2f793870f 1584729027000000 1585333827000000 1647801027000000 1679337027000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x440ecd32fd5ef557cd02a53e17d936a34f7ec084e5aaaad521cde1ac77d4a4ac094eb424e8ee25d7389cd27763b7a92ec1522d637b8f953b59c767b789f6ede1 \\x00800003cc1ffd73de7dfebe1c562ce6a9ce5cfd3146ff33ab0f30492a72f1a6a742f324577025a341379715c90f0c04dbd96d07a27391a3325a94c0f175cf6d6414f961bdc6b323cc1207143486a647f1c3be1be501101b73129a8e5c4156026fe37d8ab50658d3c29eb7af3e53dbf5b34fc2043e18f12d97b04914f8b2caa01f4f0473010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xa1c8c14005102e08c0eddab19f6b46ddf7d00d6e70efb0a56a86490d24606109f5f92a4f43fc70ac966d6ad4fa5096b5b913c41e5b25f8b1f4484223c4560609 1585333527000000 1585938327000000 1648405527000000 1679941527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x6dddf8715186a8eb2a1cb88c6b31becd0205db2956289622f32f055a4985070b230996e1ed5faf8db243ffa3fd9e784e34c0e014f232b4e29f8a7ab9e2f22caa \\x00800003d2172c1f2cac388f4a262c5c6e6f829e56fe426714aaf5d3554dd0b212645aad03b746f5fdfa51c63d19bb4da228c4df158463ee5fd115c515c204bec75697c3c6ebe4047f385d17b609aa8e4cc542d9bd77e076f451e9b7ab4168fd3a81b4e4c94d166f6569d9ec2459bb9f08f8501583c58abbfe1cb45807f5b94ba5263b83010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x595814f099c431e68f9f8682b751688d645d33f7918f76fb28108b67e0630945b42222d49aa9d446f0247750f6fb036618d07849482604757ba3bd50b2ead507 1585938027000000 1586542827000000 1649010027000000 1680546027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x1e5e384e59ac0bdd77b086344af2985d8a5b1d4e58804ec47824d3d37a5927e0cedf43c55ce4051192f089480a1c61e31393185862e4781e563659684ca9d61e \\x00800003b9d0e89e0464cc294a4b703cb0916ec8f101c199c0913ff6b89889b34921e5370b0ceff1a645e605e0df0b2e76e50b676672bf6dd30ef8c69c2f54e9ea7d4d8970399edf6c1825908a3173df18383cc0a167684553c523acb9aec5cc6352355e856d273fd73b5da3a01aab35e85df41477312a4c75b085e624f38eba384c9aeb010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x304650635cac382af2fc1f54a0fc2f55940f9b17b5ccc0f1013352fc9b9edcd80b77d238a2088db34557570db54cf8e5793928ce2d980d13a45570cc3c23c806 1584124527000000 1584729327000000 1647196527000000 1678732527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xc1710f64ac6a6b8fa3d25387cd064283e52e2f79ae6fc6ce48df855cf7045634af2f1162119c48925ecd8a483b36b6545886deda8757e3b18cb79aa77ae409ef \\x00800003ad5737091e9743fd02b05911ecdcf921459b8b5a15dd43fafccafead79069faa43d2aaf9a70b983a37bf75e0916e5024ca8bb73a9d2836027b6dbfd29ce3d5b20724b76ae6b86d8fec4f4920600613478f62bc9035cef81a43c78e4ee250f64c17b5857884e17b0d169715f78b30d94aa6e997e389c128e4ae4c3f49331ccf47010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xceca691d7178b57909ffa6ba8df774fb6436bc2bb9f165ea0bf1c1663f8a1df188a5e12a92a28837cf202f4ea36d2f8719d92679a97b96fab3aad1fcb07d5d06 1586542527000000 1587147327000000 1649614527000000 1681150527000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x3a1e88834aae2945a1143238e367ee63ac473383553b81e78b903246fc2ad1e20a4ec6fd6c9400c30ec0883bc0c4d0bbc6824cc3e155450faec74144070d164e \\x00800003c591404b33727f144ecb7c99cc91486002e1e7ff0117fcc13c717195c82ea114c0279ac87ab8a3e4b0a618c4b67630e31a0d4aa8de2b477156be1b3f396a51d13934c9f81b377c09e465f76a7effe7509a1fa44fa3f623d0d52c6ecc94dc2a5fa63b3c054f60a25937d7acabf9fecfa896b40bf9e8a50699cd1b00d4689adc8b010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xef4431c1d9ff48d083d38d7fb3c56b03179eee3264f3758b604ad3f39de619519fcd977f1e5520b338b43a11b2a13ab8db4596afdd26f03fc03d48ce73ed6907 1584729027000000 1585333827000000 1647801027000000 1679337027000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xeb874e306cba69263f4ee7a1cecb2b7649b54a1d1ccd427343a838dafd4f8f2d97b828bc65c9557f83934e041f8957c5e7e28c3bad7e79a984c7a24acf57f803 \\x008000039d401132681791f70513d712f0f4b19241230afb120695b8a250e1766e06fe82150635cd4da217d182c1e3624a08312970e70a533a06f05701a23417b243b6e42d9dea9b1d67cb1f019352d19dd8afb4ca1773451080080562600af5d49ade612754a6a827fa95095230f1344ffd931f480fb1303aa901dbe240acfec0451847010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x5a499be00f19391fe05bb8d37ce15894b1a5686ce53ee68c8824c022ad1b4186c305eab089533b5a77a4f33220b9e80192192bcfa08929ff08ae8c2f19511603 1585333527000000 1585938327000000 1648405527000000 1679941527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xcd70779a6f182bbfffdc0da52269411a478384f9844eba7897009ffc8ba92c52ebfeaaa274564177ba77baddb77a7ea16d5f2327d6e78cb871f52f7cb458765d \\x00800003c47b94ccfedae9656f4b5c8c6f9524418a356d254af754e22cf6520b58da9c537c3be39e81a410f9e5e04c58f2733928c7d76fb10cea441eb8c3ca4fe35f3fd7e977e08c649bfdd0bb480da9443db0b95bf2da14476aab5c4bb6a459f337b71cc9959b37c242f684d9788835bdf90bd54cc55460d6917077b5caca4043905e57010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x55285c088ad40c3ca6b2bdf8b55a371b63d0dae62da7ba72d8329a6b8f89364b60d237796091d01e79fa4ad45fe9d372c3832bfda5565efd2805137fc90a3a02 1585938027000000 1586542827000000 1649010027000000 1680546027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x597a8051c87010b154a8dfa6643279b0c166b51539c69b9cb19af986ab521ed59877b3868c0e56aac00ebbd6f1f3036a6b667403fc032e32b5589ddb9c257b8d \\x00800003d9d03d2b9b09d7277ca24c5231038d49a46d080c8bd7671b665c39328e6b1be137953fe40be4a3cc752cff25165b08ad41a9d49707bfd49601d0dde0bfccfcadd64f02320c253db5da792606a53f8da11d1e9c39b9f18dff9ddf07c40b11ff235506da19b9e5aac9b7f17b02ad6718269a95fa48b7a5927b9b58eadf229750f7010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xba8ae58e0b93543fddc3e09efd7f67a32ffac7684627ab54fe1f2ba0b3429fd64540871b8d5a2d2b54a9c316c589828d54def56ccfc90ba999e8ddea9f38690d 1584124527000000 1584729327000000 1647196527000000 1678732527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x3b6d21737bf0bab81896a28cce0df7b6fad9718ed09fbcc0617c6f1da04d7806a9e7ab60bb2ede861246390a649951c74d210b12f5cccbc7d1e2539911bf585b \\x00800003b41bdc5e3f58e742601e474858b2c7a6c55e078c74ee175e71a4804fe0d774f9dbc503bf7b00d491ceac3a43798c613777d6c2cd86169ea8bf41395b6695baa3b0fe4fb56e561cf7479c3c3fa725bc095e15fc4a3620734e1e26e26354401db4c53305044bca0003e578dd9b77428db0fa36c8bc0e9751b2323e341b6fa6d00d010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x52a44f26383e8b727c7a1d3b2e74a39da18ffbfdb2888715438e5640281046414c66a60bfaf6f22e41bb43c9d54f5873120de03b6d2398a467ca6e8b3a35b401 1586542527000000 1587147327000000 1649614527000000 1681150527000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x7246fdafe0cfad50a1068db61dcf1717abc01a6197db5fb2fa5bcf482d90e5071b7f670f10b9309551a53978bfa64481656854e7484bf6f867f3e94b8383d33b \\x00800003a188bc13744a4300c1b95817c396f9076e755a0a09f403f94380db24b4a16bacbf48852a2d8753aae1a71727daa3bf6561b7a940c05798134066b3a367aa3c7fc286ef31368cbecb1de36b2e0ceaac727dadba705a98024ce98e2b07ad7e7820798192769c8f15262a30cc0c98ec2d4af730bec1cd16fe7096eac035329c391b010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x98845def777f9c9309519c4e7e2f3a4acf7cd8f4c09f33a431b3a8d97af2711e967490703f7a630d687921dc5207a0f30cc596990edd86a23dec3c5bde969e00 1584729027000000 1585333827000000 1647801027000000 1679337027000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x026828646242d17763289929b2e79e08ed0af6ef5e94c2e96ba2ec22fe38c4df3557ec2b99d7e6c1309a4c63f830548a86789d73e7032b7524d9b686be8f7db7 \\x00800003be4a7bb6192c1aa5e29adb2e162eed80bb207610b94139364156e901ae3a902b5f3188230287e0612dcbf9cf91a35e03736abd6f209a7d29faca45a4d55efb0c4b74a52cf8be1f9c0db0a4bc5a6b7d06c2170789fdb5dd7960628e3ea50fd1b2bf3aeae6c0eca4c7c7116051ad5edee47850dd811fa569e54eee2d06dd5f0231010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x5b898f41af054034864c16433fac04d8939b372b715baf27e947b515934ea9c01f4c5956d65b36dbe9962d84bb54efc505f4c5aafa5a9aa9be9ba8cb325d0d0a 1585333527000000 1585938327000000 1648405527000000 1679941527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x9c46be3b92d42255ef22a8d18d3bf65d2600fb7b034d2f8d1c09dabe5a345f2e05322f6db09e8c70c0a52fd091ca4376405e85e6393a0a3bb7ba02894bc251ad \\x00800003d5e49296ba82cc605345d6467a826e8b5237de416c65607b4da81bfee63da4aca78b8513b26de0e87d841b252c98d8f2e489aebf9841c515d1eb0793dd32a11d14b6cec67741dd1b18ca422df9fab85f23fe61db1b5bfcef5d83e88bf975fe84eb03e153cd5045b77eade82bb71a5083301c89c778b62c199f386fe5a867a87b010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x968470a358909119f81c95118798aff002d554228f39ee6bb4c616b3d19d7847620f2cbfd57faea0278a62fcfa21a3172f8f0db4b2d82934e2fa96a1a0a5710e 1585938027000000 1586542827000000 1649010027000000 1680546027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xdaca7cdc05a7e781c04ab1e233ca2664bac59caed67e4d00fef43e31f191487a7cb91d8b862d6f9329da670ba79e6aa599de8896a6032249796f50870eae3933 \\x00800003c09b6a69e5216256944a4647ea9b6db44423b35597fc0c4c8918deef59a930facae82b65e54e9e5e76d3b2fb9608844a6ca8f9a28a8380b3f43be1ba3b523377475fe4a60e047cb78ebc09d979a48cc271be31bd31dc10d638d29e68189de3b3bec2e3e7093a48b2e11614093ceb8003f0724cee27330fbd995c193e3a0a463f010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x834a002ab74ff3a7efefdc0486733ba7765bb5419c41990e846ff6c267e8b9246d4fb32a85771522adbc49602cc916ea89acde35ad0cf6d103c7a213d8863f0c 1584124527000000 1584729327000000 1647196527000000 1678732527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xb1dc83372de79855773717f0bc277c05909631a21672115b41ec805552235854229edd90b3b8334e1bf0eefe781167440f68bdbb61a482457d7c526407527eb4 \\x00800003d902ed74e57fcfdf691130afce1819a1556d3eb5e71de2696c208fed60e8eb24da71e28c33d15b77299784e42ba181a84f35181863061fb16867ce56a1f08d2e936598ad89ac0162a5b0357b7d446563850b07fc2ad9a151a45edd93bd3371777f4eeb5ba0c265f8b51efcd5261eb61f8cb48ef3d83d2aa05330718190f4d89d010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x7d0c4621b3e8df3f408bfa853e0c3d0380fd560472e3d4bc5535d4abefd832f5ff48dfeb07798423e84d24593f50ca22453d1a0a051d9405cf0d18f056fb210b 1586542527000000 1587147327000000 1649614527000000 1681150527000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x70e1710ad98a6a196e28d1fd5f47e3efcd1b8e7b2f2c3a827876d2ac95f522849d4d310447b76c798fa99b8c2d2f778f584ea1794f9a6bd67e233f9755d0fbdd \\x00800003a9b87263fa85ab87cec1d6f4453dce53beaf85ee2260f6533b16738adb0edf2da3704af15ef730b7aaae3be3803fccea699c1dbab23a11bb2892e9d7c0b0434d3f8f05467361f818f4038c67d7dee0fbfec836a4993dc66e1890749d38da26b014b69ce9e1543f0e8ba81622304be9f90131f31a31fce0fb63850c60bf6a65b9010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x3af07bf48c1ee872f8fdb089700a39b8d4958598611979399c6d0123080b1ec10026ae2026717eb8294669d5b1b42050e8053f97e13f044971285c1b1f9e2c0a 1584729027000000 1585333827000000 1647801027000000 1679337027000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x4883660d80ed5b9651c06a467a913c5504940fc360d607fc922f5aabf1ef0e7567d952eba4764dcb1a3ceca37e42a5a49e86ec724240789eb6955866255d0c7c \\x008000039efb9e5e59ff102939f6ca0854e42d3ae1e988f5a9477bb7ff143245e5cdd40b1730c6c0b2c5d6b34839e84c6768d78928f290154d88f589afbe7d219fb7f63b2cd2cf0dd5930e6d0c01c3581d4a4cbe72c3f26095ffdd8ec494015a613c02240b89acb5381e06e72fb949c002ba1f887b39479cc6a2fbf864c2437c1c8d0b7b010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x89d3f7e88b73571a8d76d50000abe919eecc577a5b101c19c520ee98105748630c8e471a204acfdfa71ba317f87a4513b12fd414796baa70478e06e61e8a4301 1585333527000000 1585938327000000 1648405527000000 1679941527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x22348d1ad408d8ca6fa6d65ade4567aa687645a75b7e697af05be4df8fd8e5726b859aac44b6d0f6b589b105eb645842e17646881c31e813ec54f98de11c1194 \\x00800003d7272bcbf166e1aad6c5b95aec6043986528265d0416f1e610aa38b787a9ba80a13f314facf4b1357269a5f4a2176a4ac1477bddbb39fb9143c85cb11f53a289b75b448334c793bfb0f602d599977c3a5a252ec48b81807481cfa97c2df0b366111611f65bb1653d604da3403f188fd804f5dcf1f9dfe41977ade0bbeaff90df010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x640f14fe341fa70cf1fef1d53186dd7719d11e308a338d4cb7ef827891656e9ef57b344310fb604d96534fdfe350161cec3e0f52348d685dd6516b3950acbd0a 1585938027000000 1586542827000000 1649010027000000 1680546027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x7668572c927d369ce701c10f7cf48ca67148183b126bc793ff741c54ce60c366e53f166c57a8c2039c894b521ef69e967c724e6a9261e2a424d0c8fbae3736c3 \\x00800003f83ed03e7c128662173fa86474e21137a372fda811106ad733002b3e7079f6711d5e074ba962bf825f5753483681a6c5654705e5c01d390e9a4a8fe928e6ad0e4a34920df1e00b4d98d8c73e8d83b5caee590700a4379b8171a1b23e82fa9baa81b2949f57361037a4956c2141a24c0fc0add51c6769a90a6740f25907a0f2b7010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xb5a03c23f108414d33e97ee29c9f03e46e8562e418404ea368bcd0ece18e234ecdecbdd19bf2f3665486bed4596d4c245cc764489a4861bf229d33f0d0d1f708 1584124527000000 1584729327000000 1647196527000000 1678732527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x5ca639afc97ac81e65ed5a047ec4039c2f01224fd70e58863a382a7f9af580d27d0e70e217a37976ef3bbcb29f9b8aaa70dea6c55654a4674418e1565b7bdfe2 \\x00800003ce25fd45c8df7a0d851c43f314200887fc8ddbe39c98f93559235dd4d69497a4b66f7cd808e05543c53e454a6f5b88f57e60e64cc7424e315563ebaac7ffad0e20b38cedccb2606584fe1f9473500b9286a9081d283a42e9554cfd3426a6b3f511f2aa436d6139957a2e52cc521feea4ed9bf8ed26326cd9a038f6b9a5f15ac9010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x96ed3ed03dbf7257d0e8b7ed6a58c5cc5b0f64a3cf922aa4f9b20b46ddd2b9291f425ba5bdbce3727f07aab1dd192cb99cc30df39e1a0b12fbe835c04027d50c 1586542527000000 1587147327000000 1649614527000000 1681150527000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x20c978f9a2db37186d61ff11d9e30dcda92b2d8e29077bac309f9db9be570a75f7543935dc75f976443cb953832215fb2520cdfb3f06abae1e9e74c45c0fa589 \\x00800003bf890a83bc06473603be6aa8561026fc2cee10cd4a14869dcd8becd5bb21f3fddd3bf8bad62c4eb6d72c951889e636f599425d5cf2bac1b96e2f25f318acd8703f60a2adeb4d6991ce0dd8b3ce89b265327cda0c70554c56a97a8482dbd9e3aaa311872bdb1419571b6c9ee6aa1e7bd562568e3d849bda4f064cfb8fdf1c3a57010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xff0ea7bd5f77ca3212ffadccdb47a41bdc5a2a9f67cb5555552a40ee35878d20e209354ef9b0d84348b75453d8b06cfffe9f0d9d9114db1e02b74a1ebf62840d 1584729027000000 1585333827000000 1647801027000000 1679337027000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x38a6316d7733d9ab22dc1ee21ddeec54445ab802265704f2cb1886424d2c71c9e01282bf1c7154bf812fda1515eb61b0e71ee6d3594a099f5e2ad3b7543a4256 \\x00800003dec4ff80e64098b7ae00280b357ce5563a98441c172c7c385b46af5b1be3e08d6010ff046bb2f94ac108613ffb7c3ddc9d325e722d28df0c0f9e0ab9ea2ae8ea5db847ec640b22c15a0eee77226c15192851b34e3c4e9d7ebf99e08a424bf88649201b225d02729d3c3cb227f1a94a70c45b85c30c6ba1f148389ebbaee92965010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xd7658085054adea134a5cbefca1bb0b730a3ec605b28b523735586e8a9eeb0693b287e3ee40b7975243514438f02d05117da9598689ddf43536d8b37963ca406 1585333527000000 1585938327000000 1648405527000000 1679941527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x7129214704224f28016e13a81c5580ec25d516481a672541353410afc1eb28da038fa5429f2e324e95f96022279f8099c79625d3136b4c574d485d4370130a95 \\x00800003bf60e5046468a2871409663abe71406c795bf1eea7324d766dc7e371038b3104238d08e3c79d92cc8176a4361d78fb7616a2201bf98e70553a5a79fcefe47ae4c89286be08aeca35f5b8e64ac51c17ab019def36a72d0f5d3ef68ef3af454eee726c01d8ec342fef5e89419d2c42f1c822e2aaeeea314ffa6c924ea9c780f4db010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x32670ec806a514e69ea969d15a6a914f1c9af8472c1dd37dd353ff798182f05f12d0a2c428550ac7af433b165c60095a7beba870ce0be458e40dcb5bcdc77907 1585938027000000 1586542827000000 1649010027000000 1680546027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x00800003bc3dd9c22574bd250fb58c4628dd8ad4480ac88f76602c563234ddbc06c7610b9c32ce137acf9c458ed251108c4e098fcf4aac926c7eda1550a26739f6a51800895c660fd01066518101667a8aae345a9ac839c8c6ceccc55127d668ae1fffebe0792228b6ceef327a60ee45e2bf642d17e1b3f35fc41614478ef863727cac87010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x3b4f830a3f7c7fb749abc4e04373ab71cbca0034a447cd3aebd15de0b88d19f757fa75f0ba692d8dbe75b3c2edcd2a67f2288786b48375131dbf64c5d02b7e07 1584124527000000 1584729327000000 1647196527000000 1678732527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xc7ca973d4625d38421b350ecc2681bee151fa3890ffc4bf01ca0dce134ec798249cd9eaff9fff59833bcc8acb740c26d0535989dab7c2dd1aef9a68641a7fd2c \\x00800003b50ea8f56d6483194523f595ffd5cdea47d3c5cf662f35ebde6d914f060952211985ce21a33ec2df14e25a1684a6e54bebab5d150735a45080659ee32ff1b4b0ea31b22cb9ec7b1db49e15b2b4e9bd2317f78467a97a9b44646e8fd423f57a7dd28f28dc385ff58f636254c645bca3b5d9dadb0292056835a2cf9da897699745010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xc5084a8e8a01e4667e5bc865edf053adb3444bdf088203b69924ce9f0da5d78ed39cf9d677728bf1a2d494c8d26295211d9aad05f7dd1d26df37b681dc588803 1586542527000000 1587147327000000 1649614527000000 1681150527000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x219e13f4cc746bd69a9e42d0064f18f01df10a300f1e7f0eb6b702ce609c1c298cf79cb314ee6e6355b34e9683d010cda9feb50322f30f47604475ecea87ae51 \\x00800003cad11e9222a9db28e0b7cb83c3471e00b7d165c9c75fde2bf3157952ab20d569f7488082fb24dd48f342b8e46cb507fdab6b5d570f30bdbd25a84d6b7b58b2cfeb49ce3dc1f5b5611142cab5459a91a690ccb71229efeffde04aed806e7d0f79f82e9b3fe776b9e474212a5134f85c2740939807e1457c8150860c5ec4c5360d010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xfe63aa058348bb8ec0dd2ffcb0aa88b23c835eafee0ac4022db6465308443878027271a989fa176073ad1a5f60e799cf4e803263e76e6e4050601a7a12a2070d 1584729027000000 1585333827000000 1647801027000000 1679337027000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xf5ec27a33faef3b0da60b8403beaebe30e385c3bbb0849372b164d26b8c73265914f804eabffb948646a8ee0ff18ac8681b5014adb983dbc692a4406791ec1f2 \\x00800003a3aa7255ad8146f08166581ffb0485e0f7151ab33ca42bb49c5a5cb713acf170f4a41caaec47093963d7d96e8b70208b7f946e87752011146a3e2687ec502df89295ad24c9f0c6a0de3cebce5beff56f29a2874a2041867d968d93dd72200773fcfb97cfc0642d056c74a69f4b8de44bff6ca0a31066a77d8bae36f3172ef491010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xc4b621489acc39e1fb0babfd152c9cdeb1e730b69495d01ebaf3bb69a98aed4df80b7f01fd37aeec2b71661b5ac7ee0024e2714aeedae8225bf952543e95b804 1585333527000000 1585938327000000 1648405527000000 1679941527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x736fae372040bd5d2d9c97e454efa630d0ca6a01a277602638c998af302418885e017148f4ea7cbb144a0c29a463c8c922f3c527f0ff63add38f64292c69f5a4 \\x00800003e295a2b289c08db64929f3358e797b79bdd1d47a5fdf8fd2311ae251a49524c2e5ff73c2a3c8e3a9e1ad3a22bfc85ff50d52e8e0b4a71203fdc33812d1f0d284f11c964a3d9045050553bff210b055beaaead552c8fe254dead414ae74e8e1059336af45d9b103c82cff12c48251d2e376d75b459f55ee07b6519bb25d9b3455010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xff0ad2fcc355f96adfc980b0d101cf1767078fb86d025614c3da24f0b3f9f9621bb8f28b549c14de96688adcd836d85e3de80fbd0765903b5e7cf3e96a92f604 1585938027000000 1586542827000000 1649010027000000 1680546027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xe2557e383c1c3c019681c2521d415a758f1becb629a3a75fd9dc212f0f2e802aee81e71db725f0f9913bea0f913978bd7d238558712aa957d80c4e1673292b13 \\x00800003e507725bcf9546efceca340ab3e26584112e0ef76dd658530c8c49d533cdfa99eb4b15aab02a9688b090c3d41524c16017367e68dd16697ba7378d107bb2847028738ed00a6a9c770503b8dc738b16c780fa27d9e50b355fd977ade1ac8c810b454d6c5fc387a00da8b67ff4a501bf90e4cd055ae47f3c3a091a1fd4113d9565010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xffe91c3d5d330d8614a94e6d6a7534aab3bd6e3b23b7b0e9e00112771662289a437237fcd2bb442b7353e16fd73fb855247025a1de099f6053084c75fc41f001 1584124527000000 1584729327000000 1647196527000000 1678732527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x4faae841605109e2e8f43768baf54551b8d2ac29b16f601799c2ee260b9bcad53c8b4e5ab2352bbc1b12bc28c53df0e3ea355d61227430edb4644cdc4546907d \\x00800003bff0c91b4cb35659641e0899c62e521f645d152916158343a3317d53558dccca832301e3f870efdd7a9df5247a37583b13a4a2e5076d65e0b2adf890857dffd58666b1ad707cc32441d51b15aba2339a41d7e0c1e43b51c0ee088ac49e402e849e869dc6c49c1bd85d0faa341e5680e770b7a06f9f3f6122f50393560277ee63010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\x127896e38a662f166503a6c75946816e76dd587e1efe1dfc9276c2795b62df909bcb29358ec76b35a9037ac41a991f74e137649e5f5aa42cd8e21bb5a64ab809 1586542527000000 1587147327000000 1649614527000000 1681150527000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x89d632dde6b67bcd5496434399229c75e8925653c5c952393a61131b03f941ba1ec3de4e210861a730b85fb87cdcd7ccabc26317cdcb823f83d027ba8777324a \\x00800003a92dd9345bbf84d519201c8cbad3783c48f9e8c5039c4a827183a0d002b44c08a9651d1d12859f314caa2e91a28ffbf2abddd4d0ab74ac03db2407680de5cf19dc3955f6b8cd6b6ae1cd4846c8b4d3a8d6fe0b24196ea1ffecf157b9523f60227d776b3b351bc060bb0220136242761cf2a548c1c657a0687d4fdd53ee3d8327010001 \\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xe88df9aa6e98cfc83a6af6116e8134ec82d654d9611ecb3f458db053bd4391e96e88d752179517f61be798823b626a4ad0f028fa11d1050602df228ba6be3e0d 1584729027000000 1585333827000000 1647801027000000 1679337027000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\.
-
-
---
--- Data for Name: deposit_confirmations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.deposit_confirmations (master_pub, serial_id, h_contract_terms, h_wire, "timestamp", refund_deadline, amount_without_fee_val, amount_without_fee_frac, coin_pub, merchant_pub, exchange_sig, exchange_pub, master_sig) FROM stdin;
-\.
-
-
---
--- Data for Name: deposits; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.deposits (deposit_serial_id, coin_pub, amount_with_fee_val, amount_with_fee_frac, "timestamp", refund_deadline, wire_deadline, merchant_pub, h_contract_terms, h_wire, coin_sig, wire, tiny, done) FROM stdin;
-1 \\xda43e65bcadb947221036c387da292da2642c97961f450027820d0d9d5ab1a0f 4 0 1584124542000000 1584125442000000 1584125442000000 \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\x61043073e65af0221d1c95ca0a32917de5241b89f81678577db4df852e19b5545a11b0b1d5f786917fe0df200159864d78c575ce57421112e58bdbf0e1b90672 \\x137f048953e22f0e5d28fc42089dfefbf9af016870a369a489f99c2b7038951e3644106d64a0906a06ef332199c006201be7c37cde19d3103b41394b885e3f7f \\x642576f253e7849a632bf7a61c38a5f3a538bce3a7f56f6d65a324f083c16b0644bec34f1a4741471593013d2c8ad439e9bdab29f3dc905ba097095968a49900 {"payto_uri":"payto://x-taler-bank/localhost/42","salt":"NR2Q644SMHJ19QD4534JZH52PS466JGGGV885PX7BSPHXSCWCP6X75X090E5XFK04GSB85EKFJAWZDRGE6ZJAB6BXCWC3WZZ3WJAPW0"} f f
-2 \\x9c73b669f228b004180fdcf3a9f33afdd000fddbaf12688eed6e3f4c57cef523 7 0 1584124544000000 1584125444000000 1584125444000000 \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\x06c4c1f2ee5260b7fb8bb1542fd840107b046cfe46b05923d205a5c03f03a3d19c367ff4c642be00989a36d3dd48abb6b444a7566897902b5d62e6cb01e10edd \\x137f048953e22f0e5d28fc42089dfefbf9af016870a369a489f99c2b7038951e3644106d64a0906a06ef332199c006201be7c37cde19d3103b41394b885e3f7f \\xb47295d4d8bb784929ca37c74a3ab62ac3964ed241192c4dbeec9197d5ce41f1b8bdc468a7e126f8dfcefd1c475631178b1aa8f0c8778a6af8a1b8aae63ee007 {"payto_uri":"payto://x-taler-bank/localhost/42","salt":"NR2Q644SMHJ19QD4534JZH52PS466JGGGV885PX7BSPHXSCWCP6X75X090E5XFK04GSB85EKFJAWZDRGE6ZJAB6BXCWC3WZZ3WJAPW0"} f f
-3 \\x0b11cfd1ebfd067d2cbc26e663f2f814a8b5e9a387667adedfc15006b5531b0a 3 0 1584124545000000 1584125445000000 1584125445000000 \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\x2552601dc511378133958937eaf4e1c6cd934cefcdfb1645774ca60278e386acbfa6dbfa6062e0c55dd09af197ba578ac715d74f11015c95b51904fe4e5adfcd \\x137f048953e22f0e5d28fc42089dfefbf9af016870a369a489f99c2b7038951e3644106d64a0906a06ef332199c006201be7c37cde19d3103b41394b885e3f7f \\xdee39e3e40bd23cb375afd5c8f934f3e7d2faa52eb616fc75ceccc32341d15b2cd1497ff908d2d87177010f87f86f488f167ba6174456ea0b3da17bad64bbc0f {"payto_uri":"payto://x-taler-bank/localhost/42","salt":"NR2Q644SMHJ19QD4534JZH52PS466JGGGV885PX7BSPHXSCWCP6X75X090E5XFK04GSB85EKFJAWZDRGE6ZJAB6BXCWC3WZZ3WJAPW0"} f f
-\.
-
-
---
--- Data for Name: django_content_type; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.django_content_type (id, app_label, model) FROM stdin;
-1 auth permission
-2 auth group
-3 auth user
-4 contenttypes contenttype
-5 sessions session
-6 app bankaccount
-7 app talerwithdrawoperation
-8 app banktransaction
-\.
-
-
---
--- Data for Name: django_migrations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.django_migrations (id, app, name, applied) FROM stdin;
-1 contenttypes 0001_initial 2020-03-13 19:35:37.942029+01
-2 auth 0001_initial 2020-03-13 19:35:37.965921+01
-3 app 0001_initial 2020-03-13 19:35:38.006049+01
-4 contenttypes 0002_remove_content_type_name 2020-03-13 19:35:38.02578+01
-5 auth 0002_alter_permission_name_max_length 2020-03-13 19:35:38.029156+01
-6 auth 0003_alter_user_email_max_length 2020-03-13 19:35:38.034873+01
-7 auth 0004_alter_user_username_opts 2020-03-13 19:35:38.040331+01
-8 auth 0005_alter_user_last_login_null 2020-03-13 19:35:38.046473+01
-9 auth 0006_require_contenttypes_0002 2020-03-13 19:35:38.047897+01
-10 auth 0007_alter_validators_add_error_messages 2020-03-13 19:35:38.053184+01
-11 auth 0008_alter_user_username_max_length 2020-03-13 19:35:38.063052+01
-12 auth 0009_alter_user_last_name_max_length 2020-03-13 19:35:38.071748+01
-13 auth 0010_alter_group_name_max_length 2020-03-13 19:35:38.078775+01
-14 auth 0011_update_proxy_permissions 2020-03-13 19:35:38.085658+01
-15 sessions 0001_initial 2020-03-13 19:35:38.090129+01
-\.
-
-
---
--- Data for Name: django_session; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.django_session (session_key, session_data, expire_date) FROM stdin;
-\.
-
-
---
--- Data for Name: exchange_wire_fees; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.exchange_wire_fees (exchange_pub, h_wire_method, wire_fee_val, wire_fee_frac, closing_fee_val, closing_fee_frac, start_date, end_date, exchange_sig) FROM stdin;
-\\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xf9099467bd884e86871559a62a7f23b6e876bf084a30371891b5129ce4440d3cbe27afe387d39b2ce8d9625abd388517c81bfc8da9f2e0f8c9471bff65a802b2 0 1000000 0 1000000 1577833200000000 1609455600000000 \\xac23892c002bf8a985814ae72efcc892c969a76146a4e678b8baa39be9a77aa44b6bb38061f0c7e5b62af0dc80a90d90269a9dcccf1f20dc970a7f587e912602
-\\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xf9099467bd884e86871559a62a7f23b6e876bf084a30371891b5129ce4440d3cbe27afe387d39b2ce8d9625abd388517c81bfc8da9f2e0f8c9471bff65a802b2 0 1000000 0 1000000 1609455600000000 1640991600000000 \\xd6a6229b7c19ce8eff9032ba6c2a10814419d915130c18ff9b9f5ac2cfcc356df041b8490216051ae64aa70f135aef569fe80611b89e08f8fbe0e8119ce75e07
-\\x5f4749c40e0facf00ec1d0a04ace9eac5707074c154b636391d0651af58313aa \\xf9099467bd884e86871559a62a7f23b6e876bf084a30371891b5129ce4440d3cbe27afe387d39b2ce8d9625abd388517c81bfc8da9f2e0f8c9471bff65a802b2 0 1000000 0 1000000 1640991600000000 1672527600000000 \\x0f71224698bd93d8ae632f307f9693057a631930534fc3ca30d1b8b6bfa9d8ec4d684b9aeeb156f02ed1ed4d7c7960b2960f06351c7046341c0dc035ce70bb05
-\.
-
-
---
--- Data for Name: known_coins; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.known_coins (coin_pub, denom_pub_hash, denom_sig) FROM stdin;
-\\xda43e65bcadb947221036c387da292da2642c97961f450027820d0d9d5ab1a0f \\xe2557e383c1c3c019681c2521d415a758f1becb629a3a75fd9dc212f0f2e802aee81e71db725f0f9913bea0f913978bd7d238558712aa957d80c4e1673292b13 \\x08075ab603a3dcafc04dda6808d30c65ddee0027cd08cadf3d73d7ec9c6458d92473ed0532b389dc2f1877d86a9d4eefabd08db7e6d258d9de1eebb13a3974db1afcaee9d1c59f0d18134c0738e26e45ea06c61302e6494a4c76a80e0742ea68c063b2742250caef0931bb2d2dfb5a4c7addd70c0ec45afeadff9288c581934b
-\\x9c73b669f228b004180fdcf3a9f33afdd000fddbaf12688eed6e3f4c57cef523 \\xa96d1614b62d8a838b7ebee977db7f3d3b7c3630c8d6aee9c96e848c5b4f72200672fb3ffe229bb3c0ca63fda38757550f42170cf04fa8c647342d4d82abee67 \\x5bad81f95c0fe8de6a8e5994526ffd7d511bfbfd37f43cdce2aff542af08c9c5a08789da0d31d5404995db7296583b8a32c9f79cce2bbc60238e1874caa107c7ed1d9bba2f9d2896945f3db6d8ffe23af7fc1dda85984ae7c0d12d961e46d96ac0f1ae1835f757ba98231409b9efa95b369b30d6f4e9663b0b03917c69430b1b
-\\x0b11cfd1ebfd067d2cbc26e663f2f814a8b5e9a387667adedfc15006b5531b0a \\xe2557e383c1c3c019681c2521d415a758f1becb629a3a75fd9dc212f0f2e802aee81e71db725f0f9913bea0f913978bd7d238558712aa957d80c4e1673292b13 \\x8e645e50b8a904c8d36ee6fb9242a49ba189395226d4a2fd995e77662acc4627fa54bdba1cdca91d6dffb908e18c3eda66674cb7e4ae7bb1b3c63d57b337bb408691d90e96200b89d0c1edce84670794755203cc57971034a544910cfb43f31902b32a1261df553fd18a9e75ea0d5be0d7f0fc7501b00fc97f45502d62d54a87
-\.
-
-
---
--- Data for Name: merchant_contract_terms; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_contract_terms (order_id, merchant_pub, contract_terms, h_contract_terms, "timestamp", row_id, paid) FROM stdin;
-2020.073-03CD47SDDBMRY \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\x7b22616d6f756e74223a22544553544b55444f533a34222c2273756d6d617279223a2268656c6c6f20776f726c64222c2266756c66696c6c6d656e745f75726c223a2274616c65723a2f2f66756c66696c6c6d656e742d737563636573732f746878222c22726566756e645f646561646c696e65223a7b22745f6d73223a313538343132353434323030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313538343132353434323030307d2c226f726465725f6964223a22323032302e3037332d303343443437534444424d5259222c2274696d657374616d70223a7b22745f6d73223a313538343132343534323030307d2c227061795f646561646c696e65223a7b22745f6d73223a313538343231303934323030307d2c226d61785f776972655f666565223a22544553544b55444f533a302e31222c226d61785f666565223a22544553544b55444f533a302e31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f7075626c69632f222c2270726f6475637473223a5b5d2c226d65726368616e74223a7b226e616d65223a224d65726368616e7420496e632e222c22696e7374616e6365223a2264656661756c74227d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a224258334d4b4830453159504630335031543247344e4b4d594e48424745315443324e35503652574854314a484e58433332454e30227d5d2c2261756469746f7273223a5b5d2c22685f77697265223a2232445a473932414b57385147575139385a483130483746595a46575459304238453248504b3934395a364532505731524a4d463343483047444e4a41313433413056514b363843535230333230365a37524459445736454b3230584d3245414248314633595a52222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226d65726368616e745f707562223a22395a44304b4b5157304d39415453593435365845484b5352534250434e443751585a4b3944384e31373532324b59464448434547222c226e6f6e6365223a224a3152594e474e34545845344a333553474e443733534748334d304756454d484a4d475a324b59315a5157504a4d33434b4e4b47227d \\x61043073e65af0221d1c95ca0a32917de5241b89f81678577db4df852e19b5545a11b0b1d5f786917fe0df200159864d78c575ce57421112e58bdbf0e1b90672 1584124542000000 1 t
-2020.073-038FRAPFCXZ9A \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\x7b22616d6f756e74223a22544553544b55444f533a37222c2273756d6d617279223a226f7264657220746861742077696c6c20626520726566756e646564222c2266756c66696c6c6d656e745f75726c223a2274616c65723a2f2f66756c66696c6c6d656e742d737563636573732f746878222c22726566756e645f646561646c696e65223a7b22745f6d73223a313538343132353434343030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313538343132353434343030307d2c226f726465725f6964223a22323032302e3037332d303338465241504643585a3941222c2274696d657374616d70223a7b22745f6d73223a313538343132343534343030307d2c227061795f646561646c696e65223a7b22745f6d73223a313538343231303934343030307d2c226d61785f776972655f666565223a22544553544b55444f533a302e31222c226d61785f666565223a22544553544b55444f533a302e31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f7075626c69632f222c2270726f6475637473223a5b5d2c226d65726368616e74223a7b226e616d65223a224d65726368616e7420496e632e222c22696e7374616e6365223a2264656661756c74227d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a224258334d4b4830453159504630335031543247344e4b4d594e48424745315443324e35503652574854314a484e58433332454e30227d5d2c2261756469746f7273223a5b5d2c22685f77697265223a2232445a473932414b57385147575139385a483130483746595a46575459304238453248504b3934395a364532505731524a4d463343483047444e4a41313433413056514b363843535230333230365a37524459445736454b3230584d3245414248314633595a52222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226d65726368616e745f707562223a22395a44304b4b5157304d39415453593435365845484b5352534250434e443751585a4b3944384e31373532324b59464448434547222c226e6f6e6365223a22594d4b48523146334a50374d4232525839505757424b475258364e4d354b45524648425452414859304d324b3538535752455347227d \\x06c4c1f2ee5260b7fb8bb1542fd840107b046cfe46b05923d205a5c03f03a3d19c367ff4c642be00989a36d3dd48abb6b444a7566897902b5d62e6cb01e10edd 1584124544000000 2 t
-2020.073-00M1XHBR2J7P4 \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\x7b22616d6f756e74223a22544553544b55444f533a33222c2273756d6d617279223a227061796d656e7420616674657220726566756e64222c2266756c66696c6c6d656e745f75726c223a2274616c65723a2f2f66756c66696c6c6d656e742d737563636573732f746878222c22726566756e645f646561646c696e65223a7b22745f6d73223a313538343132353434353030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313538343132353434353030307d2c226f726465725f6964223a22323032302e3037332d30304d3158484252324a375034222c2274696d657374616d70223a7b22745f6d73223a313538343132343534353030307d2c227061795f646561646c696e65223a7b22745f6d73223a313538343231303934353030307d2c226d61785f776972655f666565223a22544553544b55444f533a302e31222c226d61785f666565223a22544553544b55444f533a302e31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f7075626c69632f222c2270726f6475637473223a5b5d2c226d65726368616e74223a7b226e616d65223a224d65726368616e7420496e632e222c22696e7374616e6365223a2264656661756c74227d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a224258334d4b4830453159504630335031543247344e4b4d594e48424745315443324e35503652574854314a484e58433332454e30227d5d2c2261756469746f7273223a5b5d2c22685f77697265223a2232445a473932414b57385147575139385a483130483746595a46575459304238453248504b3934395a364532505731524a4d463343483047444e4a41313433413056514b363843535230333230365a37524459445736454b3230584d3245414248314633595a52222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226d65726368616e745f707562223a22395a44304b4b5157304d39415453593435365845484b5352534250434e443751585a4b3944384e31373532324b59464448434547222c226e6f6e6365223a224737384d5741435750324d424a5a36594b5339444e5a514546333248323232365742585a5a5342593330324a48364150574a5a47227d \\x2552601dc511378133958937eaf4e1c6cd934cefcdfb1645774ca60278e386acbfa6dbfa6062e0c55dd09af197ba578ac715d74f11015c95b51904fe4e5adfcd 1584124545000000 3 t
-\.
-
-
---
--- Data for Name: merchant_deposits; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_deposits (h_contract_terms, merchant_pub, coin_pub, exchange_url, amount_with_fee_val, amount_with_fee_frac, deposit_fee_val, deposit_fee_frac, refund_fee_val, refund_fee_frac, wire_fee_val, wire_fee_frac, signkey_pub, exchange_proof) FROM stdin;
-\\x61043073e65af0221d1c95ca0a32917de5241b89f81678577db4df852e19b5545a11b0b1d5f786917fe0df200159864d78c575ce57421112e58bdbf0e1b90672 \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\xda43e65bcadb947221036c387da292da2642c97961f450027820d0d9d5ab1a0f http://localhost:8081/ 4 0 0 2000000 0 4000000 0 1000000 \\x211946babbc3b7f23a87e5c3edfaf2566e556437c95ca68965a69073edfc07be \\x7b22737461747573223a224445504f5349545f4f4b222c22736967223a2242564553504b415043395447594d584e504748424b575245394d47344e3542423643434b31323456363958565a385435394841584b44485352465734375930373734433833564a4a5a4b36374853363458304658573050595043433159434e5037353434363047222c22707562223a223434434d44454e565245565a34454d37575131595659514a415351354153315153354541443242354d5438373756465730595a30227d
-\\x06c4c1f2ee5260b7fb8bb1542fd840107b046cfe46b05923d205a5c03f03a3d19c367ff4c642be00989a36d3dd48abb6b444a7566897902b5d62e6cb01e10edd \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\x9c73b669f228b004180fdcf3a9f33afdd000fddbaf12688eed6e3f4c57cef523 http://localhost:8081/ 7 0 0 1000000 0 1000000 0 1000000 \\x211946babbc3b7f23a87e5c3edfaf2566e556437c95ca68965a69073edfc07be \\x7b22737461747573223a224445504f5349545f4f4b222c22736967223a22465450344e434e3431344d42333942564458413833564e4846375a3334303752364d364a38534e4d58563546435046583647425736354348325957473434513130474237585a4a4646413942594256384733565145514653565834593038535a35325457453338222c22707562223a223434434d44454e565245565a34454d37575131595659514a415351354153315153354541443242354d5438373756465730595a30227d
-\\x2552601dc511378133958937eaf4e1c6cd934cefcdfb1645774ca60278e386acbfa6dbfa6062e0c55dd09af197ba578ac715d74f11015c95b51904fe4e5adfcd \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\x0b11cfd1ebfd067d2cbc26e663f2f814a8b5e9a387667adedfc15006b5531b0a http://localhost:8081/ 3 0 0 2000000 0 4000000 0 1000000 \\x211946babbc3b7f23a87e5c3edfaf2566e556437c95ca68965a69073edfc07be \\x7b22737461747573223a224445504f5349545f4f4b222c22736967223a2234393546534e3248533635484d323352594830534b334a56575856415939505759314733545450385750465139534252484b5358303032414b5253384e574a4b3432464350414d3745334e593830313857314b47563151324550515347574156374e3659363152222c22707562223a223434434d44454e565245565a34454d37575131595659514a415351354153315153354541443242354d5438373756465730595a30227d
-\.
-
-
---
--- Data for Name: merchant_orders; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_orders (order_id, merchant_pub, contract_terms, "timestamp") FROM stdin;
-2020.073-03CD47SDDBMRY \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\x7b22616d6f756e74223a22544553544b55444f533a34222c2273756d6d617279223a2268656c6c6f20776f726c64222c2266756c66696c6c6d656e745f75726c223a2274616c65723a2f2f66756c66696c6c6d656e742d737563636573732f746878222c22726566756e645f646561646c696e65223a7b22745f6d73223a313538343132353434323030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313538343132353434323030307d2c226f726465725f6964223a22323032302e3037332d303343443437534444424d5259222c2274696d657374616d70223a7b22745f6d73223a313538343132343534323030307d2c227061795f646561646c696e65223a7b22745f6d73223a313538343231303934323030307d2c226d61785f776972655f666565223a22544553544b55444f533a302e31222c226d61785f666565223a22544553544b55444f533a302e31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f7075626c69632f222c2270726f6475637473223a5b5d2c226d65726368616e74223a7b226e616d65223a224d65726368616e7420496e632e222c22696e7374616e6365223a2264656661756c74227d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a224258334d4b4830453159504630335031543247344e4b4d594e48424745315443324e35503652574854314a484e58433332454e30227d5d2c2261756469746f7273223a5b5d2c22685f77697265223a2232445a473932414b57385147575139385a483130483746595a46575459304238453248504b3934395a364532505731524a4d463343483047444e4a41313433413056514b363843535230333230365a37524459445736454b3230584d3245414248314633595a52222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226d65726368616e745f707562223a22395a44304b4b5157304d39415453593435365845484b5352534250434e443751585a4b3944384e31373532324b59464448434547227d 1584124542000000
-2020.073-038FRAPFCXZ9A \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\x7b22616d6f756e74223a22544553544b55444f533a37222c2273756d6d617279223a226f7264657220746861742077696c6c20626520726566756e646564222c2266756c66696c6c6d656e745f75726c223a2274616c65723a2f2f66756c66696c6c6d656e742d737563636573732f746878222c22726566756e645f646561646c696e65223a7b22745f6d73223a313538343132353434343030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313538343132353434343030307d2c226f726465725f6964223a22323032302e3037332d303338465241504643585a3941222c2274696d657374616d70223a7b22745f6d73223a313538343132343534343030307d2c227061795f646561646c696e65223a7b22745f6d73223a313538343231303934343030307d2c226d61785f776972655f666565223a22544553544b55444f533a302e31222c226d61785f666565223a22544553544b55444f533a302e31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f7075626c69632f222c2270726f6475637473223a5b5d2c226d65726368616e74223a7b226e616d65223a224d65726368616e7420496e632e222c22696e7374616e6365223a2264656661756c74227d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a224258334d4b4830453159504630335031543247344e4b4d594e48424745315443324e35503652574854314a484e58433332454e30227d5d2c2261756469746f7273223a5b5d2c22685f77697265223a2232445a473932414b57385147575139385a483130483746595a46575459304238453248504b3934395a364532505731524a4d463343483047444e4a41313433413056514b363843535230333230365a37524459445736454b3230584d3245414248314633595a52222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226d65726368616e745f707562223a22395a44304b4b5157304d39415453593435365845484b5352534250434e443751585a4b3944384e31373532324b59464448434547227d 1584124544000000
-2020.073-00M1XHBR2J7P4 \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\x7b22616d6f756e74223a22544553544b55444f533a33222c2273756d6d617279223a227061796d656e7420616674657220726566756e64222c2266756c66696c6c6d656e745f75726c223a2274616c65723a2f2f66756c66696c6c6d656e742d737563636573732f746878222c22726566756e645f646561646c696e65223a7b22745f6d73223a313538343132353434353030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313538343132353434353030307d2c226f726465725f6964223a22323032302e3037332d30304d3158484252324a375034222c2274696d657374616d70223a7b22745f6d73223a313538343132343534353030307d2c227061795f646561646c696e65223a7b22745f6d73223a313538343231303934353030307d2c226d61785f776972655f666565223a22544553544b55444f533a302e31222c226d61785f666565223a22544553544b55444f533a302e31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f7075626c69632f222c2270726f6475637473223a5b5d2c226d65726368616e74223a7b226e616d65223a224d65726368616e7420496e632e222c22696e7374616e6365223a2264656661756c74227d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a224258334d4b4830453159504630335031543247344e4b4d594e48424745315443324e35503652574854314a484e58433332454e30227d5d2c2261756469746f7273223a5b5d2c22685f77697265223a2232445a473932414b57385147575139385a483130483746595a46575459304238453248504b3934395a364532505731524a4d463343483047444e4a41313433413056514b363843535230333230365a37524459445736454b3230584d3245414248314633595a52222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226d65726368616e745f707562223a22395a44304b4b5157304d39415453593435365845484b5352534250434e443751585a4b3944384e31373532324b59464448434547227d 1584124545000000
-\.
-
-
---
--- Data for Name: merchant_proofs; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_proofs (exchange_url, wtid, execution_time, signkey_pub, proof) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_refunds; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_refunds (rtransaction_id, merchant_pub, h_contract_terms, coin_pub, reason, refund_amount_val, refund_amount_frac, refund_fee_val, refund_fee_frac) FROM stdin;
-1 \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\x06c4c1f2ee5260b7fb8bb1542fd840107b046cfe46b05923d205a5c03f03a3d19c367ff4c642be00989a36d3dd48abb6b444a7566897902b5d62e6cb01e10edd \\x9c73b669f228b004180fdcf3a9f33afdd000fddbaf12688eed6e3f4c57cef523 test refund 6 0 0 1000000
-\.
-
-
---
--- Data for Name: merchant_session_info; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_session_info (session_id, fulfillment_url, order_id, merchant_pub, "timestamp") FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tip_pickups; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_pickups (tip_id, pickup_id, amount_val, amount_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tip_reserve_credits; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_reserve_credits (reserve_priv, credit_uuid, "timestamp", amount_val, amount_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tip_reserves; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_reserves (reserve_priv, expiration, balance_val, balance_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tips; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tips (reserve_priv, tip_id, exchange_url, justification, extra, "timestamp", amount_val, amount_frac, left_val, left_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_transfers; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_transfers (h_contract_terms, coin_pub, wtid) FROM stdin;
-\.
-
-
---
--- Data for Name: prewire; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.prewire (prewire_uuid, type, finished, buf) FROM stdin;
-\.
-
-
---
--- Data for Name: recoup; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.recoup (recoup_uuid, coin_pub, coin_sig, coin_blind, amount_val, amount_frac, "timestamp", h_blind_ev) FROM stdin;
-\.
-
-
---
--- Data for Name: recoup_refresh; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.recoup_refresh (recoup_refresh_uuid, coin_pub, coin_sig, coin_blind, amount_val, amount_frac, "timestamp", h_blind_ev) FROM stdin;
-\.
-
-
---
--- Data for Name: refresh_commitments; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refresh_commitments (melt_serial_id, rc, old_coin_pub, old_coin_sig, amount_with_fee_val, amount_with_fee_frac, noreveal_index) FROM stdin;
-1 \\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 \\xda43e65bcadb947221036c387da292da2642c97961f450027820d0d9d5ab1a0f \\xce68d5436fb8696ba93b63a5e41189bf086c7bbd5df1e408f78ff92da88b8d7d171e46f8bfafa4ea456fa063248d2bf13bbb4f8274fc9f08b6067b0cb2425f0e 4 0 0
-2 \\xa2539fb2b22358903ebe61138e60844461b396a3716609da9bb0e1e6e034f2469a70c53480769ed38e70f6b2f47525f5a7315e881883308264b5783bbc3da4d9 \\x9c73b669f228b004180fdcf3a9f33afdd000fddbaf12688eed6e3f4c57cef523 \\x86edaa88e64050d9c970a86199ae8224a5c36af4593c12f1e3a141484f10cf0641dfdb58fedaf60a548674b3eb1d458aa20d6ac0e7d1e6f50cb58d348b66420c 8 98000000 0
-3 \\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 \\x0b11cfd1ebfd067d2cbc26e663f2f814a8b5e9a387667adedfc15006b5531b0a \\x5288dfb612ff514caa7494a42f652cd3f0c829352f313d8b83c3f5bca01bf4aaf0377a81ddea348bf2e10b5bb74fb42afa8437cb1f26451645cf1169f2602306 5 0 0
-\.
-
-
---
--- Data for Name: refresh_revealed_coins; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refresh_revealed_coins (rc, freshcoin_index, link_sig, denom_pub_hash, coin_ev, h_coin_ev, ev_sig) FROM stdin;
-\\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 0 \\x5d757d8ded464cc535bbff5c7e06cd72001e40f68dd4643b4ce5e331f57de481e7de40e2d3820ca75c023d8eb3952991d2c3d096756d4ff461190dd1f401060c \\x1e5e384e59ac0bdd77b086344af2985d8a5b1d4e58804ec47824d3d37a5927e0cedf43c55ce4051192f089480a1c61e31393185862e4781e563659684ca9d61e \\x29bcd76b9b306ed48e2aa57d2a247b4ee6b655212eee8d51674523a880058369d03480bb7ae91c3ac1bd921ba3428c8bfc0941d6973180a3757591b8e488758a6524da8d73c3cd70b1cfd7a31109d57255f6d9ff55a59440c3705caff16d9c81e7228122201ed160fa78d540a9aef1a60e2f026a8796a0f1ad483cd839353ffb \\x6616e66ca7ed72a7130b14ec3ae6775edc4edd2f673cf4918aaf9b243fa96abf7581f081a1ce31b6b6d1d02448d8916300d11d17f957ce1d8897edc5d5387848 \\x813c329cd0cf659cc7f2138499735913c63060b37bdc9d43af40de29d5101b2ab7f138cca79a97c2acec1758a3086dc7ef876c514491f97b83da03cdd37ee54d802689cd917207200c498430e76fa1c6e7a6352526c4efdfed281382204a8addbcf12e8c01633c22bcaa1a2308944de05e7f2f24fdb52b18868ab4959cc65b07
-\\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 1 \\x5a56609f221a13e60c169eda62547c17010f77a5e28cf9db0a260d515ebbde3fac03012aa569dc2b2c18a450303af9439f16c636c6172bedd10b82e4a3e3c000 \\xdaca7cdc05a7e781c04ab1e233ca2664bac59caed67e4d00fef43e31f191487a7cb91d8b862d6f9329da670ba79e6aa599de8896a6032249796f50870eae3933 \\x67b94720ae891e31b0587e11758b73ef6a50b08088d791646eb814a0870d42157d7ee2b83f32f0ccd6d9ee48b55907d031f4771f0a3f5eb35e914c1e52914055f07d2c0ecf34bb1104313b039f815fa571f121b953ca9a6a35de2de48c5efea3b5f0d3f288f55b9fd133e23408f5f3e68a1e77b2a5c2450bc2b2785b8afd9123 \\xc113c1a21b902881c40078d022f793f7f40a3a39d85aefb8e93dc9b9283f151e93657ce69691f22df8941d5597ce6ead01535e48167a517c9614cd4dc7a03567 \\x937002743c47155cf5efa898ea9cdcf1745b4cdfe7c9264abe20ef21566c308a86221310ac6b7c90abb3ee8a7470ade95817c7bd092a7c41c4d74d2ab982ab4393803df9fd00b38d5a68bedd2255bdab69f5a16b62ef18ceadd3f7b787c4837e5ac10f94b5e0fde331eade3dbcdc180f2e382756688eca813b691f2f23c18762
-\\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 2 \\x85674d5a1d5d66204adac4f41b2587397c8ef91cbfc73c1926774250bf62a357274b87b34baec80fafcb1e6c5b5afba69b18086836a98bce5fbcbf025fb0860e \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\xb813ebadd403e28c505f9b36513d5a719c47930c86dd26728d2385b5aba6aac9c73a9598cf7f8c54dabe11fafa678c43735cba362c9d6a28cf6262babc87e27a44a0ef1ec5b6169ac27af1803e9d7af346afe48ea090b01c1a207ef810737d74ff8dbd653a614bf68eafe246f7e0f209b86d2ba5dfc0de51f1c0b10804e0d26d \\x0b633e3e3c57b1aa53a778331f622a144c9411faa4db92616a172bb237d27fb2601826713c7edf589e1b573eaae8d0df538cc5ae1231a4c2c82f2b544f11ed5e \\x9a3ba41a035d440a040278495296e4baa3bb3f40513a224e70ffad2a1b286c298b6aae912b73e92d59a874ec1269e3e30f4c62711b6ae3d213e263b9f5d1f6ab046681a53809ee5783c9fb1db30bdc156a5a858bbc413e9f095663ff8fa7917c6f5c3cbd64b3ef3558cf9467d1c217ce125e021abe951055a5ce75d716e7f3dd
-\\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 3 \\x762c3c9e4f32274b6b4a50fe2ca43f0b99f9e3331a9697021103bc7ca3c96a51c8c8c7c2b01115f75572c756a75298f2ac54541585945fc471116615ff39be03 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x49c34a8f419206f64259ac19130400bbb024dfc05c9c5fd2f1038d9cb6d11e2da3611fe1bb6201480f39dd85931ebbfdc43884c42a262f67418a33e1d6d807f5f137ec3b0ea1671dce701cf18b01e1a851261a306f24a94681f14755e93d7f0f43dceb94876c51db027c9b8083448afe98214c8cbd3f9bb5c41af96bd19a2e8f \\x8e81f8caa32e728c1fedd0d22595e13fd1c9f0faadbc544a59eb04b2d64e0a99b87b9fb9a157224eb32bb62ef19121267b35c88ef3f063ef869dc5dd8f3d9f28 \\x63d615bde1ff8d67b1d23cd41649329b5ee86dd5ccb12cd15e5c59f53f2565af6fa73591946f970c6b5ccfba8a3806582b99c944d7ebfe53494fa6f007d0bf1e5508803835c625e8cb6ebcca58827dcd72cd9fece97727688f4edf86ee3aa8ab0c3c324a78a69304e5bddbdfe06a00dc1e1e3e436a824569b001905c2e9ea33a
-\\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 4 \\xc3fab254b784fc6083d1b358dddc5cb55f9df8896d95c7111dc607042d0e31d598fa7d97c66c576fa4d7de6b380634d84487f7d15689f9113d5fe34635ce080a \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x7ccdf86b1ed6fac9ab765d37b20d5dcc607301b69f451af8627faec8755c40824cc65ea79ba58d9ea124a751543940b4d089019d63e621dbca6132f4e448dddddcf12d785d2a843045dd12881dc864a8b0e44e022f3ecbbdf050dcaf8ab695e26c17357e6c33e9f34f3840f5b44b410aa9ac2c26241ade7d4de28525ae877f44 \\xd13c9d50fe4cf8d5f085705ad6817c17b496546c1fee4bbe340bd0cafebd25d5850786829e6adcd4e6538d7cb5227f16bb73aee4105e3a7eebe4a26d94953271 \\x44e9a725f8d033946cdf68ec332bfbfc04a1c0aa48de078bc94a6455c8dc912f1788d1470eeffd3ac68e39ab414096b0691005d9d8c37500e33d452319f9ee23443e88af4542cd5daf997b0ca54d825ad0f31ca85b075e214c6efbe04700fca3830e791036ae7206b60ee30bc4374eb0cfda5f4c5b5e10fde13be6476b6af248
-\\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 5 \\x1e925b8f0f7086d88f720dd36f5d0992cd7c0032169e83bb35a0c4aa204a67990ddde826e3449eeb13f2ae9488aa0352d04dd72236c12db04a5f66f9dff1c007 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\xaca3449e8ef650707fea1fcb024ed31d0347bd9a1ee7e950b24b9d455da4e13a4a6b5fbbe0c9bea099c75bf59260c51c5dcbd81e136e71ee4399b81509ea679e2aac44ecaefe72bd7ab2d350d8beab76857d9652ec3e42edb846689153504c9b200ea30030163ef54b2bf42ab7de88e1cdc5ca205e8161b8ac85f9684f267dde \\xe74e6977f80f40004c0444cc367b2a477c41f741b3aeb7a51db1b0d430c3de2d363dee86280f89d0604abf7e7c1c8f06c81b62cd592d427d155f219547e657d6 \\x391d8fd2c23c32ae8517902229318cca11afa486bdd8d5ab45d1b6521726a0e5eb70116070a9a09827a1183be23cd58b7811383f1b68ea430b7667a8ae64f90189087bf485e648590c012eac2434fe024a512e535ac324a455d74eef0a5e97fd8446220a238670403dbd46e85933e083c128530cc5afb803b3271197d260e8f2
-\\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 6 \\x1a45b240f4826bc117cc1e6eb40b336b335ef0a8e9b39b0ac80a86a9f51f8a32da55e62f4eefb9da93d58d7b8f35741f15bfefd9cca040bafe9efdaba1b5a404 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x5a997181b752affc30a943efc6ee3796a7f84d32f0448d8095c7bd9bdc270f3171173feca5c8f2a864992db02d2f7e5050c3b612d490d6f07eb84f03d22780b15044f338f56234f371ee900f121e1fe5f56172b6a513d24c4a013da9d9525bcdfc3aca7d300854c996a941f2b53063cc35b8115066fb97ccedfc284ba8a823b1 \\xccd35d9555c4d3c62340f260ea2cc6aa1019526b2a188cfdf82dd4f4ca98b43d6837c7ccf043e6666fad78f61177ee6b060f74897edcd875e82343a4a000ed65 \\xb9dd00db9e450f9414bf0db6b8847b90652f5ee9b23f95b2f89fad6ed273aa0f6492baac411a1444e8482c99515231c98f877b23d67e19496a611bde932cf3d780fc1667977c2c0fdb44cd2ac4bea55fd5cf2a0b2416c5e8a19480bb8f5f1c80a4077280b980909f240e9eadabc733799a789fd9af5f3319dc1cc5e85bd1bc79
-\\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 7 \\xf6af907a7221099d88723bfe887b5029165ecb3175e7e7bf0592f2d017b368ac207f26a76bcfcaa6c5aa613e4c5413bc0f6111d85652113d3927b631a3cc6105 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x0795e25daf7c8630e893af5d3cb36de449fcc969bb617d356a5f6b6f26637ccb696238fa45c637c7598e088565f89deffb960532c7ecdbb7ca405539eac0f69df9840bc23f8ab4d65acb3157ca933633ab6159727e38f49e650ccbee7fb02387c6fbef5175374b045c6522f461beb67d1a844fd0c0dc402068b39b2b1bd8646a \\x593c1dd249d0c95de7fb89a8f82e3571a37a2ca12603563b572fced2e7d250cfe5cbea656e1fc44fc2fafeefb7d62f44c4d84ce86dff43cbde0341b4849d6e50 \\x78b8279654c30b9eaf590a7d61770ffeedf31022196e1b1b982432e294c745a9106148fd13a5f3720b16704c6444e346504707fc5502099bbc56d11219b5037039d5c71398ee2022488e45f1a36ca7bcb25a81daae100d6e01165ba3b6efddffc4f01ccc0116760128028884005041da25baabc99f061c0b2a8ea16a7e0a369e
-\\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 8 \\xf3b3ec15b3624c6cf4aa0b508210d5a63543c332e53d554773c67c29626e82cdf64b310c4dac0abfcc227f90c8d4b083350f674011dbe2323205cbb885482304 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x1f7bda798f2113f9e3c2f7df93509c54f1c9498313f2bc7406ae27bb4e08416eb0863360aaf2f0392d4fff868fea77d0954830eab1baa1335adb59b9e648922b17fd2571e237ed13f7d6e39c6723e234355776fc9f23a85731737f0e4ee3e443b04a02f2e5d9da9a0788f6c49d4f575959cba410d7d8d9fe17035a2f8755ee1f \\x523b80fea69dec271f1095be5c9cd09c0ade706bc4e99f16fb38fd9604e6b79339d984bd2f28d5a79465df66b84556e493da0e066ea2a6edd3e2fc065a8f228d \\x9d751214a04e11737042db7d5d346bf03d67c38bf1feb8190c17beca528d9c888c40909b4dd818312f51addf9559e59f62d4181418f56fde2dbfacb1912cb68b00ca154a1a096d3f4061df700150f658d240350c6f9e004f67bfa594f5805a51c13b0486670e1b7f31e5142e28d2466fd1e0ad0e6d233bdca22368dbff9da0ff
-\\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 9 \\xb3c397a5aeb2e185aeaf38d987c762c68dba30bc2d0bcb6a7c43742abcb14e520530a8f4bc38ccd69e63c0be75d40b63a50ee18c765b5d82e86b2a4ebaec0704 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x6c5d135d20d976b2171e6158996e4367b1b79b8fc1247844f28cf7b304092b432d207e1dbb89dcb8846aec027cc16dacb6ddfa4aecd06a9bde62b86daca93c6dea6302e8411952682adbdeb6440d03383703060912a6d8841aaa1b973090bd9d0d46fe6a0c126ce3e73e45d328769ecca36c570e9b5484d4515b50958157203a \\xdc3ba1114d4d37ee8859eae0b39197808c2f8724786efc79c3b80ae9e0b27f551b8334aeac0c5cb0cd2d3bf539bbf1f3d4d512efca724d0e6df8788cc42056b3 \\x831167b67506962bd9af02ef40824f13d66035473741b58c8d3c5173dc7866104395ae69e111e50b9bf330bb65f1a028ca4f0af60f90db17ce60b19ee2e83c6f376259809b7c32b3d26178f767fb7419a83558370ce53622314913fe5a6ad2abb40fb52a424b70fb85207dd9e3a4e8ec9dcc4d85584b19e8c85afc9a08fca339
-\\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 10 \\xd8c58be2dd15c706938af45b65705e2d94456d96fe1f60a47670e32beff58b45a8794aad5cff585ff4a5491c467627efcab483b06a303685b39aeaa1be263109 \\x7668572c927d369ce701c10f7cf48ca67148183b126bc793ff741c54ce60c366e53f166c57a8c2039c894b521ef69e967c724e6a9261e2a424d0c8fbae3736c3 \\x253df068a7d6ddbbd71bbc424427cf86cb5ac66dfbabecd85a45a3d7f55650e696774fb74f972089ca301ca8a21c123dd960cc3fc4866d74997b148c7d49f1867ed927d5c1899322558f832fa6bd85d13c880bc7b13e4a2df137f943db7195e64291e72933490574a6cb3f1b0bd51e5cedd9e81636b0d7f591ccce2c423dfbc2 \\x55bf93f053184ef6de32a6f183d00748db8e9e737252beea1060f865ae823a5a65b5344cfd537f8dc07ded828924de37d91b9a58a17c23de0cddd0c9eb95f7e0 \\x06a64fb64c50fc57ac5599d48da5712fb04b70b62b92ff45f9b75bea093b1398e99a1c682ca456ac14ecb8c9ce89e75a23d61a883a1f4d58793d611330bd8f4992fcdf023d3c36b6a0d36be28b879070bb60f43b36b3d237fae5c51862fed6142276db3e3dcc2655b64fc5b1088c6b2b5ebec364c81f070d0c042bdb1321d120
-\\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 11 \\x058e8f11a5662c2e7ae39919837587018cfd2d2c10f01d7814f9353367a44a3f2f161e35283d3186e6177a34e4a5a23045518cc7e6de2d68d7dae663858b530a \\x7668572c927d369ce701c10f7cf48ca67148183b126bc793ff741c54ce60c366e53f166c57a8c2039c894b521ef69e967c724e6a9261e2a424d0c8fbae3736c3 \\x5ae1ddab42247a51044a37095f826fef1f525dad474a8fc382698818da05da0bb066e03375c5348d062f77f16ae8337e50a67e7384ae1d70e5c88b3ddf9d7a903aa497ced46306fbc64141ddfc76a927277c3a595712e75853c892cccdb844ac03742b8d4b44d0fa75e3fb85970f1ab44dd441101b014c8602f823e1b82bd43d \\x98da81e2bf94b4e213e09fa1aed0a4e74b71f785f04e04cd1b6f70efeef6d1b19d1b70ef18c78540fa48b1e826aaf9a1cad5a5db129ba91c0c63185a4f6126cf \\xe3d4cc0241895a1953cfc5743a42eb7ea1fb8e183b2b84288604ca226e02d3645dbffdbd2126446ac93a91be93fe33d4ae3140830d7e21735c2ff81ea2af38dd214d41d866be6477ad2300123b183ccef70b8d97aa711f53ad9b64c74a8972b9ac9781f5fe86b4fca174afa644ae0a49b35bc01dd4434dc3adba491030f6c926
-\\xa2539fb2b22358903ebe61138e60844461b396a3716609da9bb0e1e6e034f2469a70c53480769ed38e70f6b2f47525f5a7315e881883308264b5783bbc3da4d9 0 \\xc9ff941f9cf7caa2ae2630dd1ac6bd69e6e9ab045b00294722e63988f787b9a8c2fa90cbe743cbed4fb29f56a1e08c9ccdec641599bb9842e0f1275347a2ba0b \\xe2557e383c1c3c019681c2521d415a758f1becb629a3a75fd9dc212f0f2e802aee81e71db725f0f9913bea0f913978bd7d238558712aa957d80c4e1673292b13 \\x67c6c2c7470fbdb3a65f1950d19738fa6357ad7cc86edb4498a75267ee149e143486cdacf57323aedf52d1740606c2b600a866f8c5390d2102e4fc840a93789eb97104d82d85fd184c5a58dfd096b7c06b18d1a276a33c4b42aa568cb81e4e359c5363c5537429048de27c76fabc8efccde75b2cd3826c595cb4704f567e003e \\x77d2a3315f90ab944aeadd12f955de87a3510f2164fd8087a2a6c530a32b5f817fd7134454bfc8a8fd858d104c7897da355f5a9bf545688f50d9543b6eaa4a0f \\xd251081b412026ba4e8015422435172c32289b6aa8db52f0c4b7deabb15ee2c6f153dd523c1eeb336cea66102b9bbd868cd8ecc0ca321f2b4e8c20a31f53f6eacddf8839ae7161ace6623800d55c41ada6130cb38ad3c9e44560d5193cbb4bb3c1f85cd94aae047282606a46e714964f8532d05c32f7a46959206e3cb7b477b7
-\\xa2539fb2b22358903ebe61138e60844461b396a3716609da9bb0e1e6e034f2469a70c53480769ed38e70f6b2f47525f5a7315e881883308264b5783bbc3da4d9 1 \\xc81d12035061e98cca2e6982e219d8a98b093e91f27557459fb8a031fce1d90c94d1fc92f62eb9a4c4ea349314be1094908422d8a170ad780c617c4f484f7f04 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x19998639e81429659fdf6c006399c0a0bc3272af6d34ff18b6ae5f416c30676fdbdd9f6042b43d61d69cf6eaa2d38c41d0d1322c9bc38e9ce0b757ddb48f0319b82a543c425f97faa4087793d6bb83d006ee443a413639a4176cf9fe87d2b7483c08256e09a56208e74e0dec0db436dc354dcfe89c9ecaf2624bf62533c1789f \\x488d132f2097ddbb675192cb4a0b96a6480f1daa81ebd7ce31cd2f83332ec65621b1221051980d8dc534c4794a27ea0062a2c02a26f633eaabb947e384176398 \\x43b74f01008bd3e324d40b04935c8efe421da4ab5f3a10d350705dd539c17e5150177e66282c35f0af207bcf61eccbb77883367f99800c1b716ca2edb2db5d5877269cd7a799365d821255ebb8707669720f7d571578902d899eb3830aa34242a6c4f8c684fab58df05632ded73b8acab269628972878c97574ef9127a06abe1
-\\xa2539fb2b22358903ebe61138e60844461b396a3716609da9bb0e1e6e034f2469a70c53480769ed38e70f6b2f47525f5a7315e881883308264b5783bbc3da4d9 2 \\xc48ec00804a5b6e7f974e85a497b68bbf961ba2db50d8435a16f8a38c3500853726e3d38e81df367e50b9d502147fe7a269cecc6dda08a72a3ade01b5a877004 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x01c316b96b8e85ef2d716d93f6f58d3a6b1c5e0935638bb58c3a2f10338c2db42227ff7ce32d83a71d7b3de756aa538b47208c43a677cafeee0cf7ed0778574b439dbfa70990bfabdccd1b019867fb5d7f9475cd27890b9e3f9d430e3dd194edea54ef86a83aa2e7e6c771f317ead7a9600b78e620068d4549bbb7cd76e6e5e4 \\x5bfec2432c2819b08aeb93fcd5f0652bc042f26a50c47816ca8000d4548dd4166615fd7679ccff250d5a5e8fe438f3c1f464ea25e95e217ced771f9e09cdbc66 \\x770924e6b4cfe15ba6a8f12e243034e0c34a7480e9a2c102f652b5ccfb54f6827eb754e058da8c8f263b2d323d5bfe357b06ab5414f8c1526e4c3fe1ebdbebe11a405ad6538c6d7a41ec2860b2c1a5e316a1a551733f61cd9ccb558948e31de8b692d2e006713aac924a0f6c4ab3a42ddf2b54cc3a37167c5c6daf65bdbd1731
-\\xa2539fb2b22358903ebe61138e60844461b396a3716609da9bb0e1e6e034f2469a70c53480769ed38e70f6b2f47525f5a7315e881883308264b5783bbc3da4d9 3 \\xb02923008152cea2db5d14b6657990f533808cb8e1dc67e345e3244d604502d17e5a7ae7aa3f2ade4d1de0a92a6de8f959f6e757a7bbe73115feb83ece591c07 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\xa3129304e20212998064a10be845deec89ac8854459aecf29d6f4aee90bfa9f5bfe3dcf47136fcd05034e61edb572f3296687e407a44fc1cd45082b3494e81ef6e98e5dac00a10293eac9071ac0244d63e6607db8769fd4ad328c852f11821d7145c690950ad1390d776b8f896aeafc55399039f22adea9de225371f28167c49 \\x9d18240f5cfa8d36f9dd81e63c425e55185f73557c9f04313b2824f0bdc047477f9f7f0f03ed2f76f2fdb80ed59a77be3ff8934cf2844a7cd8f413f4e8408ca9 \\xa2c75e553ac34431a2e10a29089e048ca8b2352d7aa4d592f82bf6a2c5ab0264ca3661d5f18c2c8c083fd4b573d33117d1c75dd5a130c3359b5b678d5666610259907f9cb4944db0c7e472c068402373cf1b20dced0e648578741d0f2093a6fc867d7d431beab2e039af4b0fb1a8a71c4213bdd635f9cfe4b4043656639496d6
-\\xa2539fb2b22358903ebe61138e60844461b396a3716609da9bb0e1e6e034f2469a70c53480769ed38e70f6b2f47525f5a7315e881883308264b5783bbc3da4d9 4 \\x1148ec703f3836d250f2a8f30d20d3fc3cada4cd62dc95ad4d51500f4173c7ed0ac60d2fcabceeaf61ca5dabe94e3aecb4f769676c102d2f3fe20ec2172a780e \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x0ee06d4694680c476c6e10f89e8a736d74e3013726ab729aee56881c8c438ef2bdb429a87c81f9ac6f84e6d8c53d9684306a5bc0bcabe02934056d32870403fe04d735e42bcfc72669532b9aa9e7cc4dccc9b7bd22062d170753177fc61109c4dfbd325931c4f63956833ad543370500145f3c4eddc1bf6770ce0a185605b56a \\xf485cc75a8b25d075aadb7e6d4f90310bf399214efc3e4a01a8e0b650b5338def8e232a46ebe227eb6171eb360113b3ff43a938c3f5d761d1cf2fad6aa56bf95 \\x8c3055cf0c8dd2033f83461dd48424a325f5392fb55206196dad27659fc4eb3ce2c99819bea4b084d475ccc2a51c869ba9a983c9e86e5eb3451171edb7fea7984dd50567f810a0e5669011f763e1cdd42971796e9170111fb465f5449820ac86cb44f6aa5695df07bd9c9508dfd0bd184c961c4de9809d8972f38d1bf8127077
-\\xa2539fb2b22358903ebe61138e60844461b396a3716609da9bb0e1e6e034f2469a70c53480769ed38e70f6b2f47525f5a7315e881883308264b5783bbc3da4d9 5 \\x5d0f0b223586c45ba70b8ffe37220e26b10253ce8b46fa0c3de51a15bb475fcabc29230f626dfdc06d81b62b1ac492a9ab40f411587a4a47600f3c3f098dd102 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x2a102c4fde797a6d31e30a8053cde824c71d7cf000e0cba76403098e7f7278b90df4435ac0b44c6549d16d9bfc40cc2d85683873bc3647a07195f62f5c8a7618ea14ab0ccf57a85e48f456a68738c1952327d26b47f08d610db602c033a54b11a14e8ca02405bee0f1a4da3db1da17f8d921c73344bb3ccc8803d06861e1a253 \\xbcafc6137c2d5cede85e09c4378b6f83e9db89ff8cfadee7d82dc5c81763bb9cf491fd79911209e8954e6af885572846b94f04c6b942e7f20febcf2bbd330e18 \\x2cd00ba24bda8d8cc8862de83f4b8d39de58f8ef929304eafaf2f710c8f32e4057de9a25a41597cf3c3b1da713dc125c4b55b02671d89a4791ac4b05bca092283e456b0e1cad4f73616568cc9fa0508a9fcd16d3be34b14b38c7a22322b636faa3c0c05abcfeb151623e45d81e43731d09dc025475a4b71a5638dce09e2c4e57
-\\xa2539fb2b22358903ebe61138e60844461b396a3716609da9bb0e1e6e034f2469a70c53480769ed38e70f6b2f47525f5a7315e881883308264b5783bbc3da4d9 6 \\x377e84c3960755fa37310a10545b32c6c7d14b7d321c36b269e45d3785bd3b153af776b034283ef87d522d3c2c79b49c6d46a1411a268f85c942fff9e0af9f0b \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x6c8f89a681a8e4e50d7db49c715837dc65d3405cf15d45e06043ab5376302a1f2bcf31f27ecc574c84ff1bd48d32388177314cb5a0fad5184396b5e57a3d4a02c280b00f0828f5d6525095ef82c65f18275fabaf4bd444c59baba42906e89d03a1294c7a6111ac9ca1bbd2a61a52c0188b4b7d5e37f5549489eed4d48e56e3b7 \\x4d0e88be5f86ffd76ac4f1e76bbad5a32a3df48bb331a2b21766b0190a4560a439278361e71a6865fe49051071940afd9224830b051be9af64493387f839ff48 \\x0949b533f71753a775ba4a5cc9565f594456c69fdfb50fa913dfb5eb05862afe0c17729d14bb88c6dddd4b1b8b95c0e946d041796caa28ebe9bf5efb335db20d19696deb4da7c603a8f212fc927fe48ddebe600bcb8615b5b2ebefc5392c1cb7554d71421b4355adc6cf27b6b2a070b316bd7d3dac5352ff86a5acbc0ee7e055
-\\xa2539fb2b22358903ebe61138e60844461b396a3716609da9bb0e1e6e034f2469a70c53480769ed38e70f6b2f47525f5a7315e881883308264b5783bbc3da4d9 7 \\xd3db6a16e4c76989683799a647e8ef5f9ef5636ff0d368dc9c0a002cec75d56b804f1ca0ae62cee558c588a99a81cca36d67247cb37cd299d30974f569465804 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x5c20d8aa919a3c79bfc6b1fc79898670ddad6e7dd6e1952ba3c15c45709764824b74b32b50ce3b52e1db8cd7c0f38f4c7844528ed1cc69a39916b418178eab68019948929f340a24c86d18dcf41a35c24c26eb354a1d5c317d0d24461c470c64fb63421db8066145cc0dcf7debd5a5a208f0ebc7b8f879817e2d2c9983a91384 \\x7ad2c7d0f0d89029119eb3728aad827ccbeb8c8738a9d0348a63fb27815e2dadbe74d76d2b6a9d652c3ca7ed79c6726c8b89f3f550398c42bfe53dcc4ae8fa2b \\x6061d3fa816c4d773a39d11b76bc6a7792598d3eb01d6420c7bfeb1d203cec297becfd8df4ba6f2a7fa2e2786776fce0905412a381c1a27ebb8055c148b2930fb0d1856885f1c3e5fd4381e5cd6800c86c811e4249484f5922c902bb367166dd719dfbe03f56861b8c801f011ddbed10277b01ac49416299ea56233482d97ded
-\\xa2539fb2b22358903ebe61138e60844461b396a3716609da9bb0e1e6e034f2469a70c53480769ed38e70f6b2f47525f5a7315e881883308264b5783bbc3da4d9 8 \\xc4a911677931b7be2587ce7253a2c3a87c7478a01714ef62f4e5977f84d5d676a0680d3eb7ca36cf3c34882108e9d553e9cdddfa37e08c1b6fde1ae8d150a70c \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x74a0ffe9db28559cc9a971e507221b13caa7e99f9ee7a0d935c27a4db017995a92f9a10fdc6eb84fb9d1dc80c2406ccbeeafaeedb4f0ce0a8acfc057e4f1d1e5838804f2c57e485a52d66121ed5d59efc9c030e978840a850a6f16e783e1b28b09a7dfc7e25f0da96d1bb18a18395419b458fbff83c85952a93618150a0d6cca \\xf93c5e0eb6091895f3d13a8f48f726edf3183263524a5d97188eee8fadd345aaf950c05882de3e3d8a146f5dafba3a2cb26059b820ed0290d7de567d90c27dc4 \\x7999670d8a92a93a70124505f0d1a1f159996555b52ba58cfc1ad830b729f339a21b7ed0da6ba8005c2ed889476d6ec098e9478fa0db449fda5dacf42d10bbde11e92561eed5425d4a2f7608856907f64a9fe9ed7bf431bd5a0504cc4230ec0a2131cbd91a749b42adbfd4f0c677dee03d02f03bcf1f1e00c56981b01b5d9728
-\\xa2539fb2b22358903ebe61138e60844461b396a3716609da9bb0e1e6e034f2469a70c53480769ed38e70f6b2f47525f5a7315e881883308264b5783bbc3da4d9 9 \\x9fe022928413f617bdb339824f2d7163b18c783d2e6aab345419c62bc0971ea590945493743cd191fd6f5f8319971ecc09c753233fa9c9be340a1365312f0509 \\x7668572c927d369ce701c10f7cf48ca67148183b126bc793ff741c54ce60c366e53f166c57a8c2039c894b521ef69e967c724e6a9261e2a424d0c8fbae3736c3 \\x56cae8204b13e2976d516b82a3d79099c4be2441bf4a6be89262c2db2dfbddd0f1d519c5a29495dbf822833e094d6211323c98b9d3ef9649af597eded1830d461a48b5fe44cf64bf8527885352ab0e262c6db3ce731bc331cce483869bd9701540e0f774bf35d9a2bbb7415e1c05ccd7cbea5ad50573b7bb30afc322eadfb532 \\x39bd91206804c8aba8c59f9593c86aa19b569afd2d62a27e3b91fef7736e95830493f7afd6471286a767fa1626069d7b6546a9faba69648e09da86c617f554e8 \\x193100c193a302acad57caca4e3598b22061449dcaba7c6c0f6ab6363fc8aa3001442a19b8da368aa59532d6f1039e24405df14deeb95f0005232b488ac70f15d901d0cb8d7019cfce969aaa42a9226a49699a5e0229a9829324022416e1b2647695429ab1c6f46fe19d0ea526235c91b24c051d912b82544c3e5764dd667197
-\\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 0 \\x8fa015f01274a235d1ae6cd2730aeb05af565c05b44516832868f09acb5c8f96e4cd8c75f16c616a9b0b94f3a440f5ef95e70ab23a423d21a149f2e64a0b9909 \\x597a8051c87010b154a8dfa6643279b0c166b51539c69b9cb19af986ab521ed59877b3868c0e56aac00ebbd6f1f3036a6b667403fc032e32b5589ddb9c257b8d \\x3c513ec4c410ddc6ff8b8de06e8ce94349416c4f274049bf6ca233acf24e4a7806bab05842c7f8937be0c6404a4813914990c14a78d247c2b70e6533bc90d986d8e9ee33b9b0c6e6f08651fa09866505d161ab26808cb0d0a95d517935523cb8b5db5fa1151e654cc68e21b0b8cd00b39c3069fd379c4b409b5f6d38d26f3282 \\x98450f9a486dceac8195c251510737fa6a7b7e0b69ec5b62715639794857104231751873ddc43dab8e906befb5885c77544272bfb358a223defb7bdc76d928ec \\x6ac58663cafbac9207066b555496370c01113fbb69707bc9272df6eb27bae5c965b54821f3a731688c9e7befb3a432e1050d9fcf45570f53002ee3e4b1a85f398785fc19405b2beccb76655011926ef93f3315d2aede7b32ddbf4a7420e5c034efa6827f42f7bc194ab5c66d049ba38d2ef688adba382a1907397d9ff294f795
-\\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 1 \\xe78b1b4c9ebbb700f6b93b07aa5a1113e9ac6c47d91155592a4349de03d5fb5c2f1bee591d7d57f9791795376d5dee6b4d1532fdbe41995a0e5c137108e4fd08 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x5e2d10765c803b815492fcbe61ce4c4f99a2cd3fb8b9eb669a0bf460897fab5eebdfb80b315a1e6ce87e3ce37dc8182183a2c1139d66dbf06879f164994a13df530d5759909ba8e2f76805788c705089476ea54373631432623e98f21e687550d6cdf9ae61cd65804c1f527eebbc35c818654efbdb60e954f6391506000f30c2 \\xec4c4821a3b34e733c472d4aba6c998ad41a3c29290008bee1c41cafb8227a4bdf8810b1f086fa45300f2bb5eb0db0440c122a0c3a0f353e2ad971c926fd49d1 \\x61c6639071a155fbf0a2da44c55099342082e7cfe633139fd92ce79c44f042507d1ea53083b756243c04c428c538500173759725b28adda3c96fd729921fa683f033931e08b30e07d4cab3dbd73c5a8fbaf2271bf97fbabbb7f2e630d44687b2febf2d08eb7bbb86bd065df4d3c5fd9649acf74f397a7163c3b4c2edbd0b264c
-\\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 2 \\x22d56c30bfb8a46e93b88ed3e619d8a9cefb25e81fc904350215bf12ed9e472e64278a9c081542f221b8d7b943d4ba701efcb1b28f5bf8e1344d9d94f8cf8e08 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x87e08b409c650f96fc5f44b709f89d495a114b2dfaef2cc080f80daffd31b10492d730c1ac53f4fcf0c6e38eb85b2b0c2aac33f901098c6f0272cb358d875d14e2bc11c60801ee2d5a2733c52d4ecf5427fb4a8e611ddfc20749f0f6c88ae48074fb7979967e9a34123bb5a2ee3fb37a1ceeb06d2fcdab2af55088806679c8e7 \\x894fa356c00c1b7d36add8922df3880a9fa6d709a4e533e0e29f6903ae37c0a6f1b616882b6e675b16cc1ec1208c630ee3b0920c59c2f854767374ea8f0a2a77 \\x888be3ed5f97e10aee99e9f73c2121372cdcd45cb261c398dc0689b0a050e711c165b83a8fef0d931908900e70841eb4a1dd29d360c6875efb3587b6eb6fe1e7ce71293b5a85b7e11f1a1ce98fe389b482efc185570d10fc45d8b7b9286dc14f703a24252cd2c4a2f99e9313c14f9783c724d58d673da4b433eca1028d4903cc
-\\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 3 \\x6eeb50565e1868f5f45eedc4e7c5c374dfdb0825a05a4107f3465aa04aea4f998685cf83c251040404cf119e0546f514253d633a7d412dc7814e93ff889cd606 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x5da5e4b8d3dbf9a0e213d9f0eba615b775163def1dd2c2ed86183bf2c680ea0445200bc1d5476dd15f94a281b3d84a642c52a17039cf4c78f9616e357872eed30969280721f230253c44bf8819c63628f7c2fdd146c268d9cecc976283b85d3e081970e1fd43463660e331bf96d64186a565943a3427d90511ceb565a6ff76e9 \\xd0830eb59217cc8158b8554fe2b397ef262a4dd95368f773debc5cbdf2714399a563ee47f2b10cd3f115aecc5501a6a0f2cb6fa4ca2c04d7a75a4fb5bb140da1 \\x09400e3eb9a45cbb85bddbf3d5617a45f19f96d74f722a74230f2092e2a35ab527aff2dd75b82f84a56985fccbaa34944de4722a295c94e3623e7b8a87e83c676e9c5001f853948f5a91f704017ecc55a6cf10957f177c163d45136d49d202e238ced69f565394045c0555c612d434a32697a7b4d6bd2a01c0652d96207940e7
-\\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 4 \\xe5cf825054ada349d92b314f08b9a30a1cb11946656ba9908ff384caab4832e8933e4e62431e49fc294a32df3eb8b41c5a18ad17ae12a67f0443f3b04b6bfd02 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\xbaa409153fca06a9ef78260005e4f0c3da914d3a2428ba497a63df4873036c9e939e70cef5b5c934e28680a9f6b61b8186cf39b52e8889375d8c61a6a146b1e068bb6f9be5777ffeecae1b52d9d04382ec9cd5d5e11af9e2bb1006242e3d9c2c548af21c7cec554908082ac4ec42015f06d7996829fe4bf561bbd1d81570d54f \\x3c76952dd67078593583cd21f2712f67fea0d9f6b7eaa5381ddd38f88d8cc048b87b7119acd1e7935354a05e6bd2de8e777d2e929b7e037a3d94f6000f96dd31 \\x68f553e5260beb10a71e40965cedcee5b3629237c5fd216ceab65c2c51ba2bbac68da670e9f99f094bc2f1ca34cadd4965f5c9e6a9e7d4ed35ce29ab00b7e6d4b59c1c5e24e18c9370f25c81504a54015c25f852d3d596f92a7c33c9e895ca01a8b7d888e68c7a712a784bf39155f35968b239dc2e9a73c1ad8f6397399691e9
-\\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 5 \\xb5dba95165bc6fe856a37e9dfffae9812aa24eb8980e1ab2303ae801f385a8246da19abf3cb36a355965a6dbd14b3c6daaa2dec660477b42af9aaeeb5cdb190f \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x8f7755289e434a002683adc94e685c1eec5c86f5b4e362861ce9ae3b584d866d512bd6172f66f0f40b16ca66c54d9807a0de2e82bae12ae8575a718cfdf463b782ff249a3f1d84eafa3a2bd1b8d2df9a99352a08df5a9920fed906c77cf1381f88d50c11f1479a2fa3e061a4b81cacc89bfce1927901dd7e4f987db4ccc70105 \\x89f61956325100f9ab2528d9f9aa25445c83cf7159904602a4060233e49929b249087415437f08f37f1d34dec6c4a44e08d86a0984d0c304ca35d35ff523f47a \\x241c8539b2469dd43abecd44e314fb1463283b863759de8f753776b4dcaed21b26f257d66a327f1382c375eea55aaef815f5ce7842a7e9bfdf7cf628568db2f2cf62dd1951e489463bc62b28470124ff2721535cab1041d40af7156485caba6044cfeb8dd5a8af6f6f67f3333525a256f1595be2a9bfcd28328a7cc8d19ad1a7
-\\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 6 \\xfa28b91161591dd94f3391f3e60e2703028183cd45466e8443e5bbeb906ad1b1da778b9db001073f6fe143fe953693b6e2c293727019c90629dfa5028f321d05 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x97b90952c9f6fdf1f2e510fce2275f50f9050050f1fbfc6abd19ef429b447cdc010618793c8ca869e7b5efc6214f65f287615fb5f0adfd2d9dc0c247c37648069af93c12d7e12b0e559d170c486a110839ac4da9a21bce9ecbc0964ad2c909677776db096bf13f852c977dbb884e0ef50cca9e90a3da92e240642ef7e61ced02 \\x817d206e490344e0a99a23b05fe4f6d1668f60e0e03875e2311aa864c16490196fdc05a33dbfa36eb570a2a1a616c47143cb093f22fa8c65e18c59ae460ca29e \\x748dd54e3b4ff3147fa2e99b9de2348e62cd724a54d8f43e13a3d49e063928e08acfbdbc8466de754cbe6d62f6fae6613aabcb1bad9ce023962818069d6634c32b2ef69223cb84feaa4ae51675b561c2573342f5177fe9957e1157c27eb5c9053f65afb64329c0d696cdb23b653465c7248e2314dee787d51b2d4a7d8771186a
-\\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 7 \\x40f3c6bc97286e741ea369872937aecae9c8517f4f18d4527fded7c0eb271b8a28c3d08e3d1ab662c4c9bb55160053698e5b2af1ac5149762835dd7921fa960b \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x1c7da343e189ed46a2a02b89e8e7eff4825e9cf17ff4c824cd6100752c01fd1330b90002e521aed4dc6d290b733872998a64cd9b2ba2a4dc4fdd9f4348004eb7fcc4ae8872d2f39dd185be2a5de24c53d8bb419ca2807d09da1f06509759cab4eac8c0dbad82cff448714e13952fa8d7566ce623963a40feba967206b4a8e94b \\xc6dc28835406de1daba4e35c3bd08e1e19e42a2fb46c02c9da1ee5167981f4cd9dcec457a1f050d005245ac685c609b275bd50ab36794bb7e88ed93ccf027a19 \\x66836e8ab3031ef27bab368f3eae8a132d0a7ab81853f502ea31750ba2e6427dcb37459f4f1c7c6fc489e561fa479c4ce969489b06957c6074162aa31ad736ba75829bef7d412ec4c985953ab7027bb7e507a1aef3c93498b83f8ce49d88d5159e083f90c3cbc4a8c36a0864b532d79314d97459c0984622e4d137c5aa1781f1
-\\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 8 \\x4134b5fcc2deb08c0f5778192d47dc68d9375453f4cb86cb3c439a8fd921a5611b9af1d4d3b9738cc4e22258bfbbedcbce4035b58bfdae72040b4c7033993104 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x93628a46344628353fbab35846bf6dc8cc7b4cb9ecee206050b94172d310146fc3a33634853e164d901f85065bb35c5896ecdf70836639ac383dec117bee96493223e2d90c0b8f77a280d37e567aaf6d86855e4b71d094340ae1a20b39b055286509d8403d93696cf03f0e34734534b19e90414333836f90edaec2f40a592b3e \\x9163deea375ef1879cb21df95088f266a643ce4080dffc7e674d3e43d8912feca7b36cdc6c20b31746ed7da784da79310a7091c5e0b533ef0e386df6c74be5a3 \\x758946da3129941f324533a9beb0843a5097839dba688ffac2646de9d72d9289aa3d19d74de8b8401e0de9edc66b032514f37a914a9d37d0d9a6d63767395a040ad017791d7fc66c596754ff8288dd544eaca221c72c9bbd8fba6cea1faa2e5caf940624b0384656c7c132ac038ccbc31843043043b62104fafeecd4fb74b131
-\\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 9 \\x96dcdc4c56449144f1540bc5785649a736ee6fa1f8a77faf55e2ddacc99710921e959dca1910893f20157327fd7acadce2ac4795a58a744cea7303fc47a52608 \\x7668572c927d369ce701c10f7cf48ca67148183b126bc793ff741c54ce60c366e53f166c57a8c2039c894b521ef69e967c724e6a9261e2a424d0c8fbae3736c3 \\x7631553a86c25f673faa46ba1a43567031115b9ef718f80776f7addc330e257879f88fb2bacbb6f6589e70f58702108b3bb0c0ef53fe7f39635f6ce5a8155c973bbf349d917c91d5aa5c9e9620591aef9a7a93fdc28d088680dcd40278b869ab79ad748e5b51b02dd66546eeb7a8fed301e6ce56f1ee9fa00d055fe1f051e86c \\xc7732719d3a305748df74ca5ea958ec46a56eb1aa03ab7f04193263238be3cd0e97587e7cfa7a33cb66028bba7a5c3071c9d4a6d11dc0f61a2bc39d85e24b214 \\x7b87afd1dd242a0405386fcacc91e296852893066c25360f1d4910f3c5f5196beb50c793fbfa1bc4e53bd8c35a4bc4cb5f1aaa83f3f5049a207244bc5d0a9b89ad84c49226d9664f9bba0c1b44f7b9ddd6edf975421b49ee61286167b3d06e07b9e92da4ebc56081aa03f0d68f31ec6a1659e3bbc0b74eca93cf68a39d5312bf
-\\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 10 \\x0321a8bef7f6aaf41a94b11eecf9fdbbdbb3984172c299e8a4fa3dcc097e6fcffdc0977d514b79d240e3a3248815270227393990a48f3d38a529501b40a14c0b \\x7668572c927d369ce701c10f7cf48ca67148183b126bc793ff741c54ce60c366e53f166c57a8c2039c894b521ef69e967c724e6a9261e2a424d0c8fbae3736c3 \\x3ee67f5cc03aec02327e8cbbdc823160971156c76c49faa92d6f283b42e6f2845059e4913ffaac4736cfcbaab1a538f781ef97e5d6f6a523e7b8db1e1963a01a66e63f278bdc47a76659e03d936efc0ac31f206c14108f0ed5bc7be6cda784c469940ae832a6df66d9197159bd0b77cb95f5a8175ea84e8536dd770910d22766 \\x6d724f2ed0479f5b4655a28ea8ff1c35ba0d5cbc92ff527cc8ddb55181ed19728b1f0c47a86fca2c50f79c387bdd7a6a53f74a447b89551048237e1ffc6bcd29 \\x04be76c8fbfaab27a49b5a2f38beb96007786dc12ffb1a275c10c56929ed9e2dff25acbde18705ecba719281447cb4d458c9b2cf7a3388389c3b7375ba83df1aeaf0310cee0d4cf09eb78056320c22679250346e1600f9d6f66cf1dbd4b6df0a56e87c57b0c5fc66e4323bc2660e439d2b4cc97e107733d92e1fbb70b554b670
-\\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 11 \\xfbf7b5b7a64fbe3d046d1fa1142dbb4b9f5a54c1bd3f7f6d84049db375ff57b7a949248fe236f20ad84727d8f41a9b19151e6ca64c59bcb145f9aa5a415c220e \\x7668572c927d369ce701c10f7cf48ca67148183b126bc793ff741c54ce60c366e53f166c57a8c2039c894b521ef69e967c724e6a9261e2a424d0c8fbae3736c3 \\xc1e1703786ef733bd2e1a7d034f328a7652c3189bece90e09cef65fe4f6029ebeb42f10b89dfef2eb09ade2ba6c43c653e1da051a62c6b008a4fac8f7a9845f943455e732e677c71532fb1f371e449704e90e94ff94c6aa7c975e2cc76fe9e755c8ef165732e8495de79e3da227e01134494be7403233b02de975c7db4c5bc69 \\x20193cb0e0a2f2f58f74c30032866287637e0b5b49b882522425cc83221cdb65c65c01687f8730da9eac2f1e2799badc6173b211fe2e90a3b8e237e4aa13b24a \\xa4db8f6c230121b237876082e117aad31ba4e92568639a3d658a6a88940ff0d0a6eb5ced74038297bb5afd8c92100d0996d98b875b0b28e63515ff0d460ed4628ee26ac5bf5b39a4433f882f2fe57e139e37783d22db4fcd810352395b8de6237249990587fc061b2f72be1b8c1898d6d88d63a2547df1049ff4da0762643aaa
-\.
-
-
---
--- Data for Name: refresh_transfer_keys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refresh_transfer_keys (rc, transfer_pub, transfer_privs) FROM stdin;
-\\x9549429543fc07348c349a52ff48729b79be84d2f0179b246badd57fe175ca0380623da9bc796586062a0d1a9e316db948b5229dde43fa382f1e485bebab9fa9 \\x8ffae84ace74029e161b0564f6d1b6e2356669016cdf5699b2688ee402f2e104 \\xaa122988f1a74af7bd81addbdc49d43cc0087da441f9deb5f98e4f2d77aaafd84d1b77a563f097800f2ee5a4389643ecbe49a79792905d007cddeaea35880d72
-\\xa2539fb2b22358903ebe61138e60844461b396a3716609da9bb0e1e6e034f2469a70c53480769ed38e70f6b2f47525f5a7315e881883308264b5783bbc3da4d9 \\x2ce2660435ab4541ccbefc7be12c07bb4f029808d5730c0b18341dc61c326c28 \\x61eab7022cf30cfd36cf91fc697651fe07907e67344cfe53c11d5d2516889e80bd1ffa2641bd388d9006756bb32ad40d7eab6561f6e18aeaa7dad342e3df0678
-\\x65488fb18fb61b07f9e57894cc6c25458f91fa6c94989273f5c0752d67982872110e2091c7e6275376878ed19c3eb18c4c2f95c1c0d714a42b410207be061313 \\xc9e688e1ce02b452c395921c47dfa754a63565e5adde18d8587cbe009b3e5538 \\xcfb6b57252f7c3a20eeb5bedcf36e6993d4bc70cac62c21552f648a61fe736d75eab6830314b1c01737c5e050751201c5d5f886f7eb36fed069c5cc23e674d8f
-\.
-
-
---
--- Data for Name: refunds; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refunds (refund_serial_id, coin_pub, merchant_pub, merchant_sig, h_contract_terms, rtransaction_id, amount_with_fee_val, amount_with_fee_frac) FROM stdin;
-1 \\x9c73b669f228b004180fdcf3a9f33afdd000fddbaf12688eed6e3f4c57cef523 \\x4fda09cefc0512ad67c429bae8cf38caeccab4f7efe696a2a1394429f9ed8b1d \\xd3d48cd8b734e55aa411c2f7e941b82ffa5758f0ee9fb41419e1548607cc64a30fd9c77e0e21067d9adaf4d8ddca8e377d374d719a8528f876a8f5ae60016703 \\x06c4c1f2ee5260b7fb8bb1542fd840107b046cfe46b05923d205a5c03f03a3d19c367ff4c642be00989a36d3dd48abb6b444a7566897902b5d62e6cb01e10edd 1 6 0
-\.
-
-
---
--- Data for Name: reserves; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.reserves (reserve_pub, account_details, current_balance_val, current_balance_frac, expiration_date, gc_date) FROM stdin;
-\\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 payto://x-taler-bank/localhost/testuser-LX9PpmbM 0 1000000 1586543741000000 1804876542000000
-\\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 payto://x-taler-bank/localhost/testuser-GlcUdYmc 0 1000000 1586543743000000 1804876544000000
-\.
-
-
---
--- Data for Name: reserves_close; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.reserves_close (close_uuid, reserve_pub, execution_date, wtid, receiver_account, amount_val, amount_frac, closing_fee_val, closing_fee_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: reserves_in; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.reserves_in (reserve_in_serial_id, reserve_pub, wire_reference, credit_val, credit_frac, sender_account_details, exchange_account_section, execution_date) FROM stdin;
-1 \\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 2 10 0 payto://x-taler-bank/localhost/testuser-LX9PpmbM exchange-account-1 1584124541000000
-2 \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 4 18 0 payto://x-taler-bank/localhost/testuser-GlcUdYmc exchange-account-1 1584124543000000
-\.
-
-
---
--- Data for Name: reserves_out; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.reserves_out (reserve_out_serial_id, h_blind_ev, denom_pub_hash, denom_sig, reserve_pub, reserve_sig, execution_date, amount_with_fee_val, amount_with_fee_frac) FROM stdin;
-1 \\xfa1b86c56993a05a45f37483320bae8b4b2348ffa0be05f91eabf4c5931f4bfb251c3a76d0edc013d9eafe465e0e5e37a9972afbd990f3c7d072b77e9e94ac00 \\xe2557e383c1c3c019681c2521d415a758f1becb629a3a75fd9dc212f0f2e802aee81e71db725f0f9913bea0f913978bd7d238558712aa957d80c4e1673292b13 \\x2f5305e1e1440e301c58ea6f8987e0969851c758340411ca003579edfacb49f6ff8b695b49809c5c9d974a42bc5613656feb0ad6fe7cac42eb40bd41cf95cd0238018de9c871d9708e18f5db0867cd787cfa7274c83dee612008e3300b960973c7aa50be873e543928ba70d33ea48d95a80da3e4dee2d899e945e20fc871f0ac \\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 \\x060f575c9e1c26c934101bd1cef93dda2131e7446874048d672497f54c296722c70297b85019faa97b200fa064bfa26b2e0d6dc9e064126310c6c1283986f70e 1584124542000000 8 5000000
-2 \\x1aa1a7cad3f2595aa0251458f6a0009a8ee37af3caa54f939bee61fce69c8d469c467b24e2c2141bcf08a4f7b9d0d91ea36b57315e8943db1422a822d0cf0440 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\xb166db2b2202d49345c7f7dae302e2c6668ecc578505ad53c7ddc27a9e3382c33dd7c02fdff0df4e8597b28039c58f7b7c66a76dfe0733d16974055284d66244bc35dbd6ff7e0e218fc8239686ef9be85711f5257b38eb6ed3e416870784cfcc9a455a797a2158f9c4653aa5e45745d229eaa9875e77ec07ccc1956df0682cd7 \\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 \\x4dc4a790937ac128c613b27c8abff0075f0d8f711a6071020fb254f4c10af3c8a4dcb03e0ef0e6a7bcd59ab18385291e8f5f1edadf8679ddd8ab5ed91aadcf06 1584124542000000 0 11000000
-3 \\xf8be32fcfa942f145d0550ecc999217eafef010d95b594951fe030210252199c3853747eecd60c3d19b7a51d3657c1018a752dc0a4667c17284dcbf3428d2e6d \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x479f052c5389067a5319e8756bfb8e3ae79b2f0dfb3af72bceafe3ef9ecf87a77c6e68214dbf740bfa570a092e332492d422f953cb5af7f9c503faa77106e167220714a4e920aa04cc4cdc8fe91c76022c63ae596b484a1c8d62d9c9d08005fbc1468526cc027ea1f5ebc3d55b8579d3e5eff6c3adfc9864225444507727f725 \\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 \\xc3be358a3ac8aea91517da18a65326cf0773aae0d477014be2a9897d9c68dda6f268d1eb061d074d69ca12a5727003961ed5ce705353aa6ee2bc77a3bf122805 1584124542000000 0 11000000
-4 \\x823d560ca5ae4a90a9a69e7bf60522ab787a264cbcadc5a82da9d4536f9cbf54e93fe9b612e2dd6b8e7ae8308807aa4946a77615f0ac08730c3d48766f7d8150 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\xa15ce91a4145dd0f32433bfd540bab400a9e65578d52e3ca0597b3217db130274c9826341df00a93cdc8aaafc66967ec35dddc7eba93e733824d0b62b0e8194cf19a45ef57751d92c9b9748c1e79751d7f774210be72ca80159b8e39616ee81e3f2ddaef2692721532b9c1c62784b81a46c69c3df55284898a6cd8c63de3d08c \\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 \\x16f4a8dfe82338ef4b23bab8fc253dd5e20dcb59cb82a3618e43d69a072a765c68bcf577d7492eda3789b21d69a365a8cf4141e837fd8dcfe2731162e1c90b03 1584124542000000 0 11000000
-5 \\xe3fdf5a6f1f973472c480241883541599c62872663e01943883c1fd52f55e763664be60574589435d7cf7e40262701cbcb5aa38d7d1a2df8b63d4d007d071ec9 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x0505ee5f1acc559b0bb41e551d1aefc894c97d200edd46ba92a7cc44af5374f2d7aadebf543dd3675eceb38d728c9b134e9e91302800a0af2b66c764002f420e9296da60fb7fe4d21b4b5c6aa27de90f4dfc101062c33c5109e4e3b7ddf0c6ba87e5fe89957deb96752320e9fdee1d769b7e82025e3b2634c94e0d4a0e1a16b1 \\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 \\x57669d08691ab53a7dde30158159a29c08dd127f574dc81a84b2074364822ea9ab39bf19c907cab6f6bd79cd5d8069219ca63800aa364638000b3869e61dea04 1584124542000000 0 11000000
-6 \\xc9b289aeb3eda68342b90b684c7b9e533d1639cb8bbf2535dc1ae62da20778ca72d13de51eca2c5235469d4d9c3a914626fd15befb75caf0598a165c1c68b953 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x5c9945994705719a944ef7113cea1ff498ff8b30345fa31016c744910b9ad624005691ed3357a5fd77503fcf3b352f6e8f82cedae1c08faab1d023b1bafe48ba193b84648daa31767321dad9ee9d66d7078c51317100827d8a54849e34fd6421939908b4c24507fde5917a9e693239de3e7a9bb3a75c880622a71d849eed026d \\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 \\xbee2ead3509c0805ac3fff14fb58c3dc41618aded23f3cfbc02e3382dfc9f545a3e6e5d4aabfe109f60ae791dd2a4552b213008b589863d4ce2fee1c349fd70f 1584124542000000 0 11000000
-7 \\xf14c1a7a3d4f707722fc36053d5151b61ae3909fd0f663d503f526a866fc6a9b94a2bc08415426ed81e1a485e5e8388c736b06d9624b9332ad76b87a0e1d2712 \\xdaca7cdc05a7e781c04ab1e233ca2664bac59caed67e4d00fef43e31f191487a7cb91d8b862d6f9329da670ba79e6aa599de8896a6032249796f50870eae3933 \\x57120d516229df94203ac957bd8a8c07b3a38d7d315d11fc138c3ba617826ff4d35ae37fc292b52a2a02083e843e9d81a5f8840f6f11e756ffafb3d7dd3ab488ba333988d2fb2d3d0b4d4b02c017c31890b022cd18fb37d72e1e23e5cc9b9833006af9c7343ecf7c6e9a8dcf6b0e888545547b3a11bdaae00a35928caaab0e58 \\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 \\x4a51ef0c9c51ebb80a6750af653d3076bf48617afd415562b42a4d0a37ce1fb3e4c58c481715f4f6856dc025c2843b6a02a66d6e97f2d51b991f9e2ca4d0070e 1584124542000000 1 2000000
-8 \\x350520ffbb831ff91710b66618669bff74c545bd9d53d917b7e0d8c226ac59d5f1543e2dcf985fd172be823956af675c46f1e5e01ea2e5c5bf5f42468f64bbfa \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x76b26ba25d3c1e7f33322144fbb4ee5694c5f11a4e7f6f0ed0d243571efc78032f8ad021d81c9844496a831ec757b3fc38a0c793af54972bb1d38a01d5f07b006d6c7ac45416b884cc76c59ecfed5ff1d3f9322ba490c979cb99e1e6be32040479e4e4965c2c1eed54fae00eda0e91871247dae3389d7839f0776ed2f12e453d \\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 \\x5e00f9439e06aa899d24380d97e1b5b97eb428dff378c8815efb956df429095677045c031cff90ad8f5777c3f6a765417c98f2b8fee2212ebdef76ed0e95920b 1584124542000000 0 11000000
-9 \\xceb2492209454f94008bee1b0f4d809e5f836396f79a50b26ceaf3212d0c43c063e8b83c4ff665594eb185552f037f5aac411a25ee7c0a4361dfcf82e90e51c5 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x539c1cc9e91372e8d373f6939178e2566f3970944259ebfc2ede1cb352ce468612cde7a4b99f1fb0286582e66b03a22d89f61c0170f7c00598437982a88b55f8997d4993501bc96b88decc787f9f0dc7238b2199f1df34b3be1a6e567bfacecd7a293fcaeab57c267d4182d494df3fd11f9c588c07ee9296b624c46871672ec6 \\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 \\x1a1aeb760e4037918c3838e714cf7d3fdbcef0770f7a6f8787c8ed7f5bd0a9a00ade2f8f21e5dd32e5f59364d07dd7d08f5be9b4888a40cc00a14d69a145ae09 1584124542000000 0 11000000
-10 \\x8351a4ef65bfe266ff642393c10a4cffb6748528cdc2fe607e160314665d6efd91089255ecc0867405aef3f7d97f6d864b22438124ba79d29494f2439df5aaaa \\x7668572c927d369ce701c10f7cf48ca67148183b126bc793ff741c54ce60c366e53f166c57a8c2039c894b521ef69e967c724e6a9261e2a424d0c8fbae3736c3 \\x733acc58f69234a58cc571a15430c447d39f6b1e9c6b2699cdf87caf8226f16d2800be3c9ed11ca8b9b9410227ff9730d3916381959e6b2c60ff425d4966cb52591f721f6c01c53084db6d57a67d0136ddd12bf010fb97062370061511b6f0a8fd16373b2473a61bff4f5f9c6d0fefff815521c8d89ddfb5756c22398495a8dd \\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 \\xea99fce23cec8899cb444d4d9142a7c08c018f7910160b01489f325b91d9d088029d989bd1a793676150095378215bee0e5a5b9887c95b685baa21b48a0e9a06 1584124542000000 0 2000000
-11 \\x632929a099c72fde8b3f8f86682ae2d5cdf8fd645068342b491d1aa521ee3bf35337225c6c61ff4312c4eab983507d74482415d66cee4c065d50d977b98676a1 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x685d1aebd2cab0af54a5da8a679c208f6d37d4126f9e1b9cc0581926887f5b8c5ccb844603495c8b5fecf7f5e30d8b9d3ad67fcf116d8b1d1cf593c892c737a51e29045e77db3a00fdaf2120259ef519b72b3c5861f3ff806c0ffea52f141b51edd762293a3f2abfb356161dce663f5fb5472dc9601f1560efa15196728e5c5d \\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 \\x153bf29f347462c72c2d2390726733a8a3a92735dfae0e789fb678ed014ce297187b205bb84901bf915bf8cd2c3e49be134d8fee216cd97cf250b4e3f2978401 1584124542000000 0 11000000
-12 \\xd992aa3ec6bd29c170d3fcc91e91d82577c7fe65fda3162558b1c10736683ea1c2f55efc8e79c9e684f5f3eb2550a4c141b472a5a0f41c6cb4db4a66bd6c88c5 \\x7668572c927d369ce701c10f7cf48ca67148183b126bc793ff741c54ce60c366e53f166c57a8c2039c894b521ef69e967c724e6a9261e2a424d0c8fbae3736c3 \\xc085f590f5c325652872f55c8e4653aa7c245d0ea4f3ab9cb8563fa86b0c3227b2fe22874dc0ce0f051d458b8fbe1661e3bea78f3a35ba22ff5e61b14781b66f007466de7f488e39c5f58176c387db320959274005b6127e963e677951a14e8462872a1d5775cbe34512434f9a360a2850e43ff1809d595f8c64c73539681cca \\x8c06a696766b847c2cea178bcfcd333435d4a513017df3308d73058c60321db4 \\xc8fa6c24c844e69980ceb672886745a8663d31dfcc37305202289c1bc0d769fcb604709a0164fd221096b7efb5b4e8b116222111f704b3ad8ad1a010f019360a 1584124542000000 0 2000000
-13 \\xecacdf2e69a8ee2e19c7a29bc943c8d011268a0a24ca9f3e21753c01bfd47fe7ee2b9fb4581d4c30e285aa0e7cfe12115b428281b53d912a068f65068130c567 \\xa96d1614b62d8a838b7ebee977db7f3d3b7c3630c8d6aee9c96e848c5b4f72200672fb3ffe229bb3c0ca63fda38757550f42170cf04fa8c647342d4d82abee67 \\x18066d277056e3dbb475c587cf35c39d54739b07c989a7d3cbe3b25f57b8e14999dca51493b19376670e7f7aa2a0afc76d7740eee7cb446793cbabbbf7d58da47ac85378d25ff2d8e5f0db025bc159c206dd334c8d2cb6b19e1712a4d090079f24777ea51a5a3f2cdabb722821aa7af562f5e5d82b5e9f1ece9916d95ccb10cc \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\x2e1a8c0fb61a319966b1f93d231e92680af015eb2d0168d0d09ef0fd1d30a0fc53ccd719f2aa276b735eee4de06b4d3c5bc5036bb0dba9fa47c580e2ba2ca20a 1584124544000000 10 1000000
-14 \\xde074157cd9d02c4cc4e11754ff867f96456ed0af5245db58f4bb16e9aa9686c0ad7ad484f051488a2fe6e0d069df8ca2e802d1328143e4d73de9aa93ffe9e47 \\x1e5e384e59ac0bdd77b086344af2985d8a5b1d4e58804ec47824d3d37a5927e0cedf43c55ce4051192f089480a1c61e31393185862e4781e563659684ca9d61e \\x1a50336560bbc7ba5b61e0243ea6ee42745bfa5bf7cda1fc82c2570cd0d4abd5a29f002476e9e89f4be3a83a79a02a68d11a8403fc0b1a81536fe1bb2c59d2ae29a00726ea7c586dd437874b2a2b5478af4bbefdea43e9a24accf2666ebfbda795d34589223944ca7d76fd2aa0bbdf87edd5602a9ed8fe65c913128807e1fc96 \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\x451e233ab58b820740bf355cb62b7213985ffee90f3313fd9667f3206785f5d762619fb140462a5bba573b9da77c7da510fc37fa92d40ad03908a590325ddc04 1584124544000000 2 3000000
-15 \\xea3efab045c5e9828d08dee7d4a0a871642fee94bb298af1748f1c529227f0655800f8d28055a0207c32172a58e28269b488d5333e2ee72c7cd53934be02b4d9 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x5afeaa9d75790cd0d9169a07b46011993687357ac9d09aa73c981cc1ad81b00f151250bf7e7dce712a6100968e5acbef806de583a7eb3a29560caa8a490639d32cef0ff965282a942c77c70f4c1445f45685042f691863ff2ec5f4a86adf8c1da78ed0e9ae7cddfd99187a92fa904ccb315d8767f95c03ffb5a9097e7c5fa004 \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\x73b6d2f5d737015813bdc5ceb1ee79ded762f5f205e21b95270341e6f59b95f6e51991d81bb6f9c86ba67da3c7f42f242837e783c2ca8c89232acd2078ec0105 1584124544000000 0 11000000
-16 \\xbf03d2169dd458c679e4d7f0d81de5caebbac9c8427c187c1fe9da48377581159cf221d79d7dd67c21156276a2773e0016a7311cb65be73b9dfdd80e140f4adb \\xa6576c13e5143d56edc29a8c4917121a1c3917f3436c26f3349de6c226bb96bce029469a4b627b504a3f17d70c4ad7e6c015b4873e98c87238c9b824018f461b \\x5cb4339ecfaf2d660d56f80a0765efa2bbcf63430b2afbc95eb7ef42a0f566975f27241c6efa36e5910b83244ef82cad2b5e3ff4a8d338a5be505cc00ec2355197f81cac375f37ca1bc7afe0cb546b461d1fc1f8b339d5c641c6799ebd867c7edc7002fa4afbedb7f095d858ab7469c9793ca2a707a3658cad2e7d9cd0b12e1e \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\xb28d3ab7e9a1d235b5eeaeeef3951f28edab259edcc78cbebd9a2a1fac4d3563d3766e7686721ef329bd98f7e3f25d8b2edfcb7cc5be20faa2f4ccceb87b0103 1584124544000000 5 1000000
-17 \\x94529840e7ce591646521cf00e0d709bf4542318307492afc8935d6991638ff0d1f145724ad1d4d1b83083314bc7e2fd25b6cad123efc85bc7c8f55cde62b94f \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x09a36c9277ec01a29ef40ea76bb60d0563131b7dd7237bf7b480d9d2acac7f89bca077ac0bce392a62283b6588227f472e27279971e98fe31d205831bfc3c31a43e370becde748d0f3117040ee490fd97bcc9400f6e5e849d0d4a36593ad292a8e65c2e69d7ab9ead7343d48c065d00c3947190d41d529928fdf2aa4ecf9ac8f \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\x6eba7292d04246a91abbbf42546aeeb7e800d6334946bf7ff7b5c1f0222832b662d5d73d099994c0092b68ecedaebda63b90d896c6429cc0e64626793d5a6905 1584124544000000 0 11000000
-18 \\xc655a88e8f2cadf3cc7ec42fee373206bff0fbef40b293eda1ab509ffb67355b9ff38bfad9aab124c11f90b7d2b40ff1457bf58dd71c19ad9118883cbe39aaa8 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x01035ae702e16e9c836378a0555c1714fc13526d43c081389c7bc7f08b83018d4554159d4e1dfd99c73f66f2adc219de3e4cccba16b35665bc59c794b6c01616c78f2ecafb4d95a282c6328212e6433fe79188cab57056a9c13d47107d31eb5625339924f490e01282b91e28934c05a22bef6fbcdc62ff3ab03d02f08b814dc7 \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\xd5b350736d5d47474043063d3f6b7dccd6094c8eeb80014bd3aa515c2ca353c1c5db4f001752cedc64f1595f5d60ef17272f73c45daf7e8c4d5c350a05e60902 1584124544000000 0 11000000
-19 \\xf17cb2a463cc112c56312b250c44a786b8f34d9804368e159abfc6d5dcf6c6facb5da20a029c923a392f74021aaec291e0e6414156a01cbb3fc1fdb074396898 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x19553d53c7dfeafa86e23eacd87e8ede57d7750fca5307dcc83b1aee49fc9dcba27a70a294c4e75d4ede7775f1e352d16fe0673f1eeecf3d652d045415a06fa58b20d5c3f4e3cc444aae3d27a21018ba45cb5b912297b3370e5c563e0ecc540d67885062e7ca3ae3fc78a652a47ad6c68f75cb5852267e6eebff562fcf6b330e \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\x14bbe5d3b1d4fe349e4ae2f72a453517115268841588ba68a9808b1c0cca0f2a23e63c6d27d0bd4059701d06e4db5b4e5762ff1034adb71612ba44a5999dfe03 1584124544000000 0 11000000
-20 \\xd2b69f953def6efc355fc9c90354cd01cd1e72afe7c358947d701157e2b8458fd414754a1fa67c08ab0f42a6cb87c45e4a8f0fa93afb5d8ed10428f7791690e2 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x8002dd9304a443b1eb33d11c7a63dae67949087a8bbbb0e381ba23c4985ff498cd3b6d53342589d7e7518422d92959316a93c13847b626ba9d3f85566838f53d0768865744db40c8bfd53260c765b54c80686374f43a5c661e837fc6a0e3b1477b69a143f67afafcede90a8b36a94cdc4b5bba7eb35b0d0d1ffe8084d4d55362 \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\xcf7af496f0446677a6a6a4c313e82852c547437faaece881dd3e7436c26120562a7fa1916ef23d7b24b9e5150e8f78418109676ab6c20b5891e5d8960721e504 1584124544000000 0 11000000
-21 \\x19ea00eac23aa835f64fae13c3e837572eeeea7b7a7b316b390451711eb947c312a75707853ad435e932325eb204f49ba3922a5ff776b81b8ea65df8b9ef7c40 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x5840096bf1ddb120f2b9797ca33d89dc430409d806c8eb44648dd61766789684fce09254aad26f64747ea6f071bd3ba1101f338036165e571a06d5b02d7e753d489094aa373d69e7d2bd2a25f279de94b58238e0b3eb968e6584f6fe6aea36517883933b41456241ca9a5909ebc187ee459119b6a4ffe6865903ed2248b7185b \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\x92f2d699e0c15c3505195aed01c0059fa482cdb03e68000ab631513ca1ac40c23ab791724e159a7283b4efb3a236a5f3299e7ff737b22ed71cd4209d55dc160b 1584124544000000 0 11000000
-22 \\x921ec4037d54de78d334d451d68d6c09bcf518ec8cd2bb93294df08a38962ad1990955b3775f4a8a85582334e3553ec3eadcfe0fa22c7e583f5156fd5ff730c9 \\x7668572c927d369ce701c10f7cf48ca67148183b126bc793ff741c54ce60c366e53f166c57a8c2039c894b521ef69e967c724e6a9261e2a424d0c8fbae3736c3 \\x39a60102e685b6a6633b04d5cacab13a76131d3268ca9362bc996e71e6120b3d6f72a012af9a4c4f1cf6b66a7481aa0a7338117ca1b2b4593621ea2652c593e13ec8b68aeeacd00f13609a13a074e925e3445e30b886c2bfcf13f0dd52989bcac8736fb45849e0c481dd05cc80e330870b99d95407ae640e692231ae1f5c6568 \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\xebd122ce53497507e22f77d1f7dc63d064aadeb29424770dbe41c8c5dcdc012dac1e86d2748422bb69ed8f14649bc5d69f1b861cb631135225a5862dca748403 1584124544000000 0 2000000
-23 \\x1efcee558d5bb3aba55d6b5cb7e62638aa0c17bbddeb99b3eb9c9997c976362fda0b0f00f5dca05473e6bfdc763b7348655931138386d45b4f9002a798176d16 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x31c964f058e9a7986cfeb513ddda0a5058eedbc4de2eebca4be32c2e1080f2323ebc3f7619fcd80f369ee3fb88460872599d4d6c3ee03af0dba777b657ef124f166dcb19146ad74f07804845097bc1447d4b0f9074ba9f7a16fa9a2345f53820c01fe6c7dbdc632243f6b9b380cbbd62ccab3573d4735bef3984c41bc2efbee2 \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\xf9af143a09eaa9290a1b9d9cdaba7df4f25796410aca20388df1aec0022d7ed5544ad0150e7d28a4011a08b5d619e3ff80704f28cccfe8723d73229ece824207 1584124544000000 0 11000000
-24 \\xcaa70a990eeba71f90a86379a11b4786176402f80422213a3f1110eafd112262b82e9a7953e4834f58b081783ba4a806b2886a7ce7131359018be96654c433d7 \\x47e5c7c13b7b267f28df1d734a99e45e139c5c22d4abe4219be0d1918eeace6c933a6720f678008c9877548c4aaa3029aff0bd82d7085fac0046405e6202cc62 \\x2d2f7243d46aae23ac5966f69c8c1bf0eb947ff418b4ad53b7f5e852d938f8fc80de8c2cd452cae4a890e4ca47fed4801a7aaf7143f4de5af748e1e4d8eaf3132d4e03e7cf51ae8f40267753b7279802a8503bae5809db5bcd02a701486f61351a9087d87840fd0f1ab2cf17e5cd3262decb04c91786d17717a62fe75e6051c9 \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\xc3efb35a1afe82fb5e0a62ee8c30788399cac06fb191aae94f579a00d70d4b8e2df5a2f78f7ed54936e1f159239f030e8cb640abcc4328ed3e3bcc7f7b461805 1584124544000000 0 11000000
-25 \\x60407d1b14561de74f9060b2704af8b44d722a300c19131445c4d8e41fca020392f555b071cf889c1ee61c1cfc4590c1925c7d0c55a12185d22610edbd7ae8a1 \\x7668572c927d369ce701c10f7cf48ca67148183b126bc793ff741c54ce60c366e53f166c57a8c2039c894b521ef69e967c724e6a9261e2a424d0c8fbae3736c3 \\x117d07b85f8e96a14a154c094d12c9eaa96fd00f542d1f6db611b339a65bc1ddf1e2b924d171190bb8424087cda7fd28aa09f932764d22b20f5d50a4111f5eb0b74ae511f5207c3c653170608add58c43685f0f50f397dbd66ec97d444935aced35d0a9be39080b89ea4d98470f9a06ec2b61cbbf1ddb465998abeb43cd3c146 \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\x62df9c5df67b2e82990dac569ad3a14475d7bed8d6b612182cf2071163a6feaaf37f80f330ce16607af7112f1a9921a0fb82383f11669182214fb341a351550d 1584124544000000 0 2000000
-26 \\xa7fead0b1f217abcdd865867ad345bc0842012b37a09bc878639bcb013d6fbd55707775221144a137929b7de252e10e4b58129f5e1c3395b926e9ad13fb57cdd \\x7668572c927d369ce701c10f7cf48ca67148183b126bc793ff741c54ce60c366e53f166c57a8c2039c894b521ef69e967c724e6a9261e2a424d0c8fbae3736c3 \\x552b17682757c46977ca00b8a1d87d618bcd543f741074f2b1caca3f3e2c7f387bc87d67e51ff2c5eed7b8668d706b606b85f3e16792b2ffdc28153564e52b52978f0525884948ff694171398a92e1c788f24a63e1bb1c2e64e728ac6e4950c150b97db2094f77d3371550ad6f1d30f7d08e36d6b9ae8f9c6ec84666846b0876 \\xc689e130dc274f9e83e031358b9222ffe21834ae3f39f8d2574f28579670e5c8 \\xc7cc65e6cf593e2c8fa0f3c30af1617362467196739628608f945102a2bb7eb73c567876e7bc8cba1cb73ccfd7cdf380a85ef6cb80b54ad067cf702aade07a02 1584124544000000 0 2000000
-\.
-
-
---
--- Data for Name: wire_auditor_account_progress; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_auditor_account_progress (master_pub, account_name, last_wire_reserve_in_serial_id, last_wire_wire_out_serial_id, wire_in_off, wire_out_off) FROM stdin;
-\.
-
-
---
--- Data for Name: wire_auditor_progress; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_auditor_progress (master_pub, last_timestamp, last_reserve_close_uuid) FROM stdin;
-\.
-
-
---
--- Data for Name: wire_fee; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_fee (wire_method, start_date, end_date, wire_fee_val, wire_fee_frac, closing_fee_val, closing_fee_frac, master_sig) FROM stdin;
-\.
-
-
---
--- Data for Name: wire_out; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_out (wireout_uuid, execution_date, wtid_raw, wire_target, exchange_account_section, amount_val, amount_frac) FROM stdin;
-\.
-
-
---
--- Name: aggregation_tracking_aggregation_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.aggregation_tracking_aggregation_serial_id_seq', 1, false);
-
-
---
--- Name: app_bankaccount_account_no_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.app_bankaccount_account_no_seq', 12, true);
-
-
---
--- Name: app_banktransaction_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.app_banktransaction_id_seq', 4, true);
-
-
---
--- Name: auditor_reserves_auditor_reserves_rowid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auditor_reserves_auditor_reserves_rowid_seq', 1, false);
-
-
---
--- Name: auth_group_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auth_group_id_seq', 1, false);
-
-
---
--- Name: auth_group_permissions_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auth_group_permissions_id_seq', 1, false);
-
-
---
--- Name: auth_permission_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auth_permission_id_seq', 32, true);
-
-
---
--- Name: auth_user_groups_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auth_user_groups_id_seq', 1, false);
-
-
---
--- Name: auth_user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auth_user_id_seq', 12, true);
-
-
---
--- Name: auth_user_user_permissions_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auth_user_user_permissions_id_seq', 1, false);
-
-
---
--- Name: denomination_revocations_denom_revocations_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.denomination_revocations_denom_revocations_serial_id_seq', 1, false);
-
-
---
--- Name: deposit_confirmations_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.deposit_confirmations_serial_id_seq', 1, false);
-
-
---
--- Name: deposits_deposit_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.deposits_deposit_serial_id_seq', 3, true);
-
-
---
--- Name: django_content_type_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.django_content_type_id_seq', 8, true);
-
-
---
--- Name: django_migrations_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.django_migrations_id_seq', 15, true);
-
-
---
--- Name: merchant_contract_terms_row_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_contract_terms_row_id_seq', 3, true);
-
-
---
--- Name: merchant_refunds_rtransaction_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_refunds_rtransaction_id_seq', 1, true);
-
-
---
--- Name: prewire_prewire_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.prewire_prewire_uuid_seq', 1, false);
-
-
---
--- Name: recoup_recoup_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.recoup_recoup_uuid_seq', 1, false);
-
-
---
--- Name: recoup_refresh_recoup_refresh_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.recoup_refresh_recoup_refresh_uuid_seq', 1, false);
-
-
---
--- Name: refresh_commitments_melt_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.refresh_commitments_melt_serial_id_seq', 3, true);
-
-
---
--- Name: refunds_refund_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.refunds_refund_serial_id_seq', 1, true);
-
-
---
--- Name: reserves_close_close_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.reserves_close_close_uuid_seq', 1, false);
-
-
---
--- Name: reserves_in_reserve_in_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.reserves_in_reserve_in_serial_id_seq', 2, true);
-
-
---
--- Name: reserves_out_reserve_out_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.reserves_out_reserve_out_serial_id_seq', 26, true);
-
-
---
--- Name: wire_out_wireout_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.wire_out_wireout_uuid_seq', 1, false);
-
-
---
--- Name: patches patches_pkey; Type: CONSTRAINT; Schema: _v; Owner: -
---
-
-ALTER TABLE ONLY _v.patches
- ADD CONSTRAINT patches_pkey PRIMARY KEY (patch_name);
-
-
---
--- Name: aggregation_tracking aggregation_tracking_aggregation_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.aggregation_tracking
- ADD CONSTRAINT aggregation_tracking_aggregation_serial_id_key UNIQUE (aggregation_serial_id);
-
-
---
--- Name: aggregation_tracking aggregation_tracking_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.aggregation_tracking
- ADD CONSTRAINT aggregation_tracking_pkey PRIMARY KEY (deposit_serial_id);
-
-
---
--- Name: app_bankaccount app_bankaccount_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_bankaccount
- ADD CONSTRAINT app_bankaccount_pkey PRIMARY KEY (account_no);
-
-
---
--- Name: app_bankaccount app_bankaccount_user_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_bankaccount
- ADD CONSTRAINT app_bankaccount_user_id_key UNIQUE (user_id);
-
-
---
--- Name: app_banktransaction app_banktransaction_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_banktransaction
- ADD CONSTRAINT app_banktransaction_pkey PRIMARY KEY (id);
-
-
---
--- Name: app_banktransaction app_banktransaction_request_uid_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_banktransaction
- ADD CONSTRAINT app_banktransaction_request_uid_key UNIQUE (request_uid);
-
-
---
--- Name: app_talerwithdrawoperation app_talerwithdrawoperation_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_talerwithdrawoperation
- ADD CONSTRAINT app_talerwithdrawoperation_pkey PRIMARY KEY (withdraw_id);
-
-
---
--- Name: auditor_denomination_pending auditor_denomination_pending_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denomination_pending
- ADD CONSTRAINT auditor_denomination_pending_pkey PRIMARY KEY (denom_pub_hash);
-
-
---
--- Name: auditor_denominations auditor_denominations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denominations
- ADD CONSTRAINT auditor_denominations_pkey PRIMARY KEY (denom_pub_hash);
-
-
---
--- Name: auditor_exchanges auditor_exchanges_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_exchanges
- ADD CONSTRAINT auditor_exchanges_pkey PRIMARY KEY (master_pub);
-
-
---
--- Name: auditor_historic_denomination_revenue auditor_historic_denomination_revenue_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_historic_denomination_revenue
- ADD CONSTRAINT auditor_historic_denomination_revenue_pkey PRIMARY KEY (denom_pub_hash);
-
-
---
--- Name: auditor_progress_aggregation auditor_progress_aggregation_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_aggregation
- ADD CONSTRAINT auditor_progress_aggregation_pkey PRIMARY KEY (master_pub);
-
-
---
--- Name: auditor_progress_coin auditor_progress_coin_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_coin
- ADD CONSTRAINT auditor_progress_coin_pkey PRIMARY KEY (master_pub);
-
-
---
--- Name: auditor_progress_deposit_confirmation auditor_progress_deposit_confirmation_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_deposit_confirmation
- ADD CONSTRAINT auditor_progress_deposit_confirmation_pkey PRIMARY KEY (master_pub);
-
-
---
--- Name: auditor_progress_reserve auditor_progress_reserve_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_reserve
- ADD CONSTRAINT auditor_progress_reserve_pkey PRIMARY KEY (master_pub);
-
-
---
--- Name: auditor_reserves auditor_reserves_auditor_reserves_rowid_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_reserves
- ADD CONSTRAINT auditor_reserves_auditor_reserves_rowid_key UNIQUE (auditor_reserves_rowid);
-
-
---
--- Name: auth_group auth_group_name_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group
- ADD CONSTRAINT auth_group_name_key UNIQUE (name);
-
-
---
--- Name: auth_group_permissions auth_group_permissions_group_id_permission_id_0cd325b0_uniq; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group_permissions
- ADD CONSTRAINT auth_group_permissions_group_id_permission_id_0cd325b0_uniq UNIQUE (group_id, permission_id);
-
-
---
--- Name: auth_group_permissions auth_group_permissions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group_permissions
- ADD CONSTRAINT auth_group_permissions_pkey PRIMARY KEY (id);
-
-
---
--- Name: auth_group auth_group_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group
- ADD CONSTRAINT auth_group_pkey PRIMARY KEY (id);
-
-
---
--- Name: auth_permission auth_permission_content_type_id_codename_01ab375a_uniq; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_permission
- ADD CONSTRAINT auth_permission_content_type_id_codename_01ab375a_uniq UNIQUE (content_type_id, codename);
-
-
---
--- Name: auth_permission auth_permission_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_permission
- ADD CONSTRAINT auth_permission_pkey PRIMARY KEY (id);
-
-
---
--- Name: auth_user_groups auth_user_groups_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_groups
- ADD CONSTRAINT auth_user_groups_pkey PRIMARY KEY (id);
-
-
---
--- Name: auth_user_groups auth_user_groups_user_id_group_id_94350c0c_uniq; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_groups
- ADD CONSTRAINT auth_user_groups_user_id_group_id_94350c0c_uniq UNIQUE (user_id, group_id);
-
-
---
--- Name: auth_user auth_user_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user
- ADD CONSTRAINT auth_user_pkey PRIMARY KEY (id);
-
-
---
--- Name: auth_user_user_permissions auth_user_user_permissions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_user_permissions
- ADD CONSTRAINT auth_user_user_permissions_pkey PRIMARY KEY (id);
-
-
---
--- Name: auth_user_user_permissions auth_user_user_permissions_user_id_permission_id_14a6b632_uniq; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_user_permissions
- ADD CONSTRAINT auth_user_user_permissions_user_id_permission_id_14a6b632_uniq UNIQUE (user_id, permission_id);
-
-
---
--- Name: auth_user auth_user_username_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user
- ADD CONSTRAINT auth_user_username_key UNIQUE (username);
-
-
---
--- Name: denomination_revocations denomination_revocations_denom_revocations_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denomination_revocations
- ADD CONSTRAINT denomination_revocations_denom_revocations_serial_id_key UNIQUE (denom_revocations_serial_id);
-
-
---
--- Name: denomination_revocations denomination_revocations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denomination_revocations
- ADD CONSTRAINT denomination_revocations_pkey PRIMARY KEY (denom_pub_hash);
-
-
---
--- Name: denominations denominations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denominations
- ADD CONSTRAINT denominations_pkey PRIMARY KEY (denom_pub_hash);
-
-
---
--- Name: deposit_confirmations deposit_confirmations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposit_confirmations
- ADD CONSTRAINT deposit_confirmations_pkey PRIMARY KEY (h_contract_terms, h_wire, coin_pub, merchant_pub, exchange_sig, exchange_pub, master_sig);
-
-
---
--- Name: deposit_confirmations deposit_confirmations_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposit_confirmations
- ADD CONSTRAINT deposit_confirmations_serial_id_key UNIQUE (serial_id);
-
-
---
--- Name: deposits deposits_coin_pub_merchant_pub_h_contract_terms_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_coin_pub_merchant_pub_h_contract_terms_key UNIQUE (coin_pub, merchant_pub, h_contract_terms);
-
-
---
--- Name: deposits deposits_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_pkey PRIMARY KEY (deposit_serial_id);
-
-
---
--- Name: django_content_type django_content_type_app_label_model_76bd3d3b_uniq; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.django_content_type
- ADD CONSTRAINT django_content_type_app_label_model_76bd3d3b_uniq UNIQUE (app_label, model);
-
-
---
--- Name: django_content_type django_content_type_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.django_content_type
- ADD CONSTRAINT django_content_type_pkey PRIMARY KEY (id);
-
-
---
--- Name: django_migrations django_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.django_migrations
- ADD CONSTRAINT django_migrations_pkey PRIMARY KEY (id);
-
-
---
--- Name: django_session django_session_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.django_session
- ADD CONSTRAINT django_session_pkey PRIMARY KEY (session_key);
-
-
---
--- Name: exchange_wire_fees exchange_wire_fees_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.exchange_wire_fees
- ADD CONSTRAINT exchange_wire_fees_pkey PRIMARY KEY (exchange_pub, h_wire_method, start_date, end_date);
-
-
---
--- Name: known_coins known_coins_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.known_coins
- ADD CONSTRAINT known_coins_pkey PRIMARY KEY (coin_pub);
-
-
---
--- Name: merchant_contract_terms merchant_contract_terms_h_contract_terms_merchant_pub_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms
- ADD CONSTRAINT merchant_contract_terms_h_contract_terms_merchant_pub_key UNIQUE (h_contract_terms, merchant_pub);
-
-
---
--- Name: merchant_contract_terms merchant_contract_terms_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms
- ADD CONSTRAINT merchant_contract_terms_pkey PRIMARY KEY (order_id, merchant_pub);
-
-
---
--- Name: merchant_contract_terms merchant_contract_terms_row_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms
- ADD CONSTRAINT merchant_contract_terms_row_id_key UNIQUE (row_id);
-
-
---
--- Name: merchant_deposits merchant_deposits_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_pkey PRIMARY KEY (h_contract_terms, coin_pub);
-
-
---
--- Name: merchant_orders merchant_orders_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_orders
- ADD CONSTRAINT merchant_orders_pkey PRIMARY KEY (order_id, merchant_pub);
-
-
---
--- Name: merchant_proofs merchant_proofs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_proofs
- ADD CONSTRAINT merchant_proofs_pkey PRIMARY KEY (wtid, exchange_url);
-
-
---
--- Name: merchant_refunds merchant_refunds_rtransaction_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refunds
- ADD CONSTRAINT merchant_refunds_rtransaction_id_key UNIQUE (rtransaction_id);
-
-
---
--- Name: merchant_session_info merchant_session_info_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_session_info
- ADD CONSTRAINT merchant_session_info_pkey PRIMARY KEY (session_id, fulfillment_url, merchant_pub);
-
-
---
--- Name: merchant_session_info merchant_session_info_session_id_fulfillment_url_order_id_m_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_session_info
- ADD CONSTRAINT merchant_session_info_session_id_fulfillment_url_order_id_m_key UNIQUE (session_id, fulfillment_url, order_id, merchant_pub);
-
-
---
--- Name: merchant_tip_pickups merchant_tip_pickups_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickups
- ADD CONSTRAINT merchant_tip_pickups_pkey PRIMARY KEY (pickup_id);
-
-
---
--- Name: merchant_tip_reserve_credits merchant_tip_reserve_credits_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserve_credits
- ADD CONSTRAINT merchant_tip_reserve_credits_pkey PRIMARY KEY (credit_uuid);
-
-
---
--- Name: merchant_tip_reserves merchant_tip_reserves_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserves
- ADD CONSTRAINT merchant_tip_reserves_pkey PRIMARY KEY (reserve_priv);
-
-
---
--- Name: merchant_tips merchant_tips_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tips
- ADD CONSTRAINT merchant_tips_pkey PRIMARY KEY (tip_id);
-
-
---
--- Name: merchant_transfers merchant_transfers_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfers
- ADD CONSTRAINT merchant_transfers_pkey PRIMARY KEY (h_contract_terms, coin_pub);
-
-
---
--- Name: prewire prewire_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.prewire
- ADD CONSTRAINT prewire_pkey PRIMARY KEY (prewire_uuid);
-
-
---
--- Name: recoup recoup_recoup_uuid_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup
- ADD CONSTRAINT recoup_recoup_uuid_key UNIQUE (recoup_uuid);
-
-
---
--- Name: recoup_refresh recoup_refresh_recoup_refresh_uuid_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh
- ADD CONSTRAINT recoup_refresh_recoup_refresh_uuid_key UNIQUE (recoup_refresh_uuid);
-
-
---
--- Name: refresh_commitments refresh_commitments_melt_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_commitments
- ADD CONSTRAINT refresh_commitments_melt_serial_id_key UNIQUE (melt_serial_id);
-
-
---
--- Name: refresh_commitments refresh_commitments_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_commitments
- ADD CONSTRAINT refresh_commitments_pkey PRIMARY KEY (rc);
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_coin_ev_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_coin_ev_key UNIQUE (coin_ev);
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_h_coin_ev_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_h_coin_ev_key UNIQUE (h_coin_ev);
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_pkey PRIMARY KEY (rc, freshcoin_index);
-
-
---
--- Name: refresh_transfer_keys refresh_transfer_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_transfer_keys
- ADD CONSTRAINT refresh_transfer_keys_pkey PRIMARY KEY (rc);
-
-
---
--- Name: refunds refunds_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refunds
- ADD CONSTRAINT refunds_pkey PRIMARY KEY (coin_pub, merchant_pub, h_contract_terms, rtransaction_id);
-
-
---
--- Name: refunds refunds_refund_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refunds
- ADD CONSTRAINT refunds_refund_serial_id_key UNIQUE (refund_serial_id);
-
-
---
--- Name: reserves_close reserves_close_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_close
- ADD CONSTRAINT reserves_close_pkey PRIMARY KEY (close_uuid);
-
-
---
--- Name: reserves_in reserves_in_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_in
- ADD CONSTRAINT reserves_in_pkey PRIMARY KEY (reserve_pub, wire_reference);
-
-
---
--- Name: reserves_in reserves_in_reserve_in_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_in
- ADD CONSTRAINT reserves_in_reserve_in_serial_id_key UNIQUE (reserve_in_serial_id);
-
-
---
--- Name: reserves_out reserves_out_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out
- ADD CONSTRAINT reserves_out_pkey PRIMARY KEY (h_blind_ev);
-
-
---
--- Name: reserves_out reserves_out_reserve_out_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out
- ADD CONSTRAINT reserves_out_reserve_out_serial_id_key UNIQUE (reserve_out_serial_id);
-
-
---
--- Name: reserves reserves_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves
- ADD CONSTRAINT reserves_pkey PRIMARY KEY (reserve_pub);
-
-
---
--- Name: wire_auditor_account_progress wire_auditor_account_progress_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_auditor_account_progress
- ADD CONSTRAINT wire_auditor_account_progress_pkey PRIMARY KEY (master_pub, account_name);
-
-
---
--- Name: wire_auditor_progress wire_auditor_progress_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_auditor_progress
- ADD CONSTRAINT wire_auditor_progress_pkey PRIMARY KEY (master_pub);
-
-
---
--- Name: wire_fee wire_fee_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_fee
- ADD CONSTRAINT wire_fee_pkey PRIMARY KEY (wire_method, start_date);
-
-
---
--- Name: wire_out wire_out_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_out
- ADD CONSTRAINT wire_out_pkey PRIMARY KEY (wireout_uuid);
-
-
---
--- Name: wire_out wire_out_wtid_raw_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_out
- ADD CONSTRAINT wire_out_wtid_raw_key UNIQUE (wtid_raw);
-
-
---
--- Name: aggregation_tracking_wtid_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX aggregation_tracking_wtid_index ON public.aggregation_tracking USING btree (wtid_raw);
-
-
---
--- Name: INDEX aggregation_tracking_wtid_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.aggregation_tracking_wtid_index IS 'for lookup_transactions';
-
-
---
--- Name: app_banktransaction_credit_account_id_a8ba05ac; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX app_banktransaction_credit_account_id_a8ba05ac ON public.app_banktransaction USING btree (credit_account_id);
-
-
---
--- Name: app_banktransaction_date_f72bcad6; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX app_banktransaction_date_f72bcad6 ON public.app_banktransaction USING btree (date);
-
-
---
--- Name: app_banktransaction_debit_account_id_5b1f7528; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX app_banktransaction_debit_account_id_5b1f7528 ON public.app_banktransaction USING btree (debit_account_id);
-
-
---
--- Name: app_banktransaction_request_uid_b7d06af5_like; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX app_banktransaction_request_uid_b7d06af5_like ON public.app_banktransaction USING btree (request_uid varchar_pattern_ops);
-
-
---
--- Name: app_talerwithdrawoperation_selected_exchange_account__6c8b96cf; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX app_talerwithdrawoperation_selected_exchange_account__6c8b96cf ON public.app_talerwithdrawoperation USING btree (selected_exchange_account_id);
-
-
---
--- Name: app_talerwithdrawoperation_withdraw_account_id_992dc5b3; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX app_talerwithdrawoperation_withdraw_account_id_992dc5b3 ON public.app_talerwithdrawoperation USING btree (withdraw_account_id);
-
-
---
--- Name: auditor_historic_reserve_summary_by_master_pub_start_date; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auditor_historic_reserve_summary_by_master_pub_start_date ON public.auditor_historic_reserve_summary USING btree (master_pub, start_date);
-
-
---
--- Name: auditor_reserves_by_reserve_pub; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auditor_reserves_by_reserve_pub ON public.auditor_reserves USING btree (reserve_pub);
-
-
---
--- Name: auth_group_name_a6ea08ec_like; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_group_name_a6ea08ec_like ON public.auth_group USING btree (name varchar_pattern_ops);
-
-
---
--- Name: auth_group_permissions_group_id_b120cbf9; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_group_permissions_group_id_b120cbf9 ON public.auth_group_permissions USING btree (group_id);
-
-
---
--- Name: auth_group_permissions_permission_id_84c5c92e; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_group_permissions_permission_id_84c5c92e ON public.auth_group_permissions USING btree (permission_id);
-
-
---
--- Name: auth_permission_content_type_id_2f476e4b; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_permission_content_type_id_2f476e4b ON public.auth_permission USING btree (content_type_id);
-
-
---
--- Name: auth_user_groups_group_id_97559544; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_user_groups_group_id_97559544 ON public.auth_user_groups USING btree (group_id);
-
-
---
--- Name: auth_user_groups_user_id_6a12ed8b; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_user_groups_user_id_6a12ed8b ON public.auth_user_groups USING btree (user_id);
-
-
---
--- Name: auth_user_user_permissions_permission_id_1fbb5f2c; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_user_user_permissions_permission_id_1fbb5f2c ON public.auth_user_user_permissions USING btree (permission_id);
-
-
---
--- Name: auth_user_user_permissions_user_id_a95ead1b; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_user_user_permissions_user_id_a95ead1b ON public.auth_user_user_permissions USING btree (user_id);
-
-
---
--- Name: auth_user_username_6821ab7c_like; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_user_username_6821ab7c_like ON public.auth_user USING btree (username varchar_pattern_ops);
-
-
---
--- Name: denominations_expire_legal_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX denominations_expire_legal_index ON public.denominations USING btree (expire_legal);
-
-
---
--- Name: deposits_coin_pub_merchant_contract_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX deposits_coin_pub_merchant_contract_index ON public.deposits USING btree (coin_pub, merchant_pub, h_contract_terms);
-
-
---
--- Name: INDEX deposits_coin_pub_merchant_contract_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.deposits_coin_pub_merchant_contract_index IS 'for deposits_get_ready';
-
-
---
--- Name: deposits_get_ready_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX deposits_get_ready_index ON public.deposits USING btree (tiny, done, wire_deadline, refund_deadline);
-
-
---
--- Name: deposits_iterate_matching_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX deposits_iterate_matching_index ON public.deposits USING btree (merchant_pub, h_wire, done, wire_deadline);
-
-
---
--- Name: INDEX deposits_iterate_matching_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.deposits_iterate_matching_index IS 'for deposits_iterate_matching';
-
-
---
--- Name: django_session_expire_date_a5c62663; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX django_session_expire_date_a5c62663 ON public.django_session USING btree (expire_date);
-
-
---
--- Name: django_session_session_key_c0390e0f_like; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX django_session_session_key_c0390e0f_like ON public.django_session USING btree (session_key varchar_pattern_ops);
-
-
---
--- Name: known_coins_by_denomination; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX known_coins_by_denomination ON public.known_coins USING btree (denom_pub_hash);
-
-
---
--- Name: merchant_transfers_by_coin; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_transfers_by_coin ON public.merchant_transfers USING btree (h_contract_terms, coin_pub);
-
-
---
--- Name: merchant_transfers_by_wtid; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_transfers_by_wtid ON public.merchant_transfers USING btree (wtid);
-
-
---
--- Name: prepare_iteration_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX prepare_iteration_index ON public.prewire USING btree (finished);
-
-
---
--- Name: INDEX prepare_iteration_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.prepare_iteration_index IS 'for wire_prepare_data_get and gc_prewire';
-
-
---
--- Name: recoup_by_coin_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_by_coin_index ON public.recoup USING btree (coin_pub);
-
-
---
--- Name: recoup_by_h_blind_ev; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_by_h_blind_ev ON public.recoup USING btree (h_blind_ev);
-
-
---
--- Name: recoup_for_by_reserve; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_for_by_reserve ON public.recoup USING btree (coin_pub, h_blind_ev);
-
-
---
--- Name: recoup_refresh_by_coin_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_refresh_by_coin_index ON public.recoup_refresh USING btree (coin_pub);
-
-
---
--- Name: recoup_refresh_by_h_blind_ev; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_refresh_by_h_blind_ev ON public.recoup_refresh USING btree (h_blind_ev);
-
-
---
--- Name: recoup_refresh_for_by_reserve; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_refresh_for_by_reserve ON public.recoup_refresh USING btree (coin_pub, h_blind_ev);
-
-
---
--- Name: refresh_commitments_old_coin_pub_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX refresh_commitments_old_coin_pub_index ON public.refresh_commitments USING btree (old_coin_pub);
-
-
---
--- Name: refresh_revealed_coins_coin_pub_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX refresh_revealed_coins_coin_pub_index ON public.refresh_revealed_coins USING btree (denom_pub_hash);
-
-
---
--- Name: refresh_transfer_keys_coin_tpub; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX refresh_transfer_keys_coin_tpub ON public.refresh_transfer_keys USING btree (rc, transfer_pub);
-
-
---
--- Name: INDEX refresh_transfer_keys_coin_tpub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.refresh_transfer_keys_coin_tpub IS 'for get_link (unsure if this helps or hurts for performance as there should be very few transfer public keys per rc, but at least in theory this helps the ORDER BY clause)';
-
-
---
--- Name: refunds_coin_pub_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX refunds_coin_pub_index ON public.refunds USING btree (coin_pub);
-
-
---
--- Name: reserves_close_by_reserve; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_close_by_reserve ON public.reserves_close USING btree (reserve_pub);
-
-
---
--- Name: reserves_expiration_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_expiration_index ON public.reserves USING btree (expiration_date, current_balance_val, current_balance_frac);
-
-
---
--- Name: INDEX reserves_expiration_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.reserves_expiration_index IS 'used in get_expired_reserves';
-
-
---
--- Name: reserves_gc_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_gc_index ON public.reserves USING btree (gc_date);
-
-
---
--- Name: INDEX reserves_gc_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.reserves_gc_index IS 'for reserve garbage collection';
-
-
---
--- Name: reserves_in_exchange_account_serial; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_in_exchange_account_serial ON public.reserves_in USING btree (exchange_account_section, reserve_in_serial_id DESC);
-
-
---
--- Name: reserves_in_execution_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_in_execution_index ON public.reserves_in USING btree (exchange_account_section, execution_date);
-
-
---
--- Name: reserves_out_execution_date; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_out_execution_date ON public.reserves_out USING btree (execution_date);
-
-
---
--- Name: reserves_out_for_get_withdraw_info; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_out_for_get_withdraw_info ON public.reserves_out USING btree (denom_pub_hash, h_blind_ev);
-
-
---
--- Name: reserves_out_reserve_pub_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_out_reserve_pub_index ON public.reserves_out USING btree (reserve_pub);
-
-
---
--- Name: INDEX reserves_out_reserve_pub_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.reserves_out_reserve_pub_index IS 'for get_reserves_out';
-
-
---
--- Name: wire_fee_gc_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX wire_fee_gc_index ON public.wire_fee USING btree (end_date);
-
-
---
--- Name: aggregation_tracking aggregation_tracking_deposit_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.aggregation_tracking
- ADD CONSTRAINT aggregation_tracking_deposit_serial_id_fkey FOREIGN KEY (deposit_serial_id) REFERENCES public.deposits(deposit_serial_id) ON DELETE CASCADE;
-
-
---
--- Name: app_bankaccount app_bankaccount_user_id_2722a34f_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_bankaccount
- ADD CONSTRAINT app_bankaccount_user_id_2722a34f_fk_auth_user_id FOREIGN KEY (user_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: app_banktransaction app_banktransaction_credit_account_id_a8ba05ac_fk_app_banka; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_banktransaction
- ADD CONSTRAINT app_banktransaction_credit_account_id_a8ba05ac_fk_app_banka FOREIGN KEY (credit_account_id) REFERENCES public.app_bankaccount(account_no) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: app_banktransaction app_banktransaction_debit_account_id_5b1f7528_fk_app_banka; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_banktransaction
- ADD CONSTRAINT app_banktransaction_debit_account_id_5b1f7528_fk_app_banka FOREIGN KEY (debit_account_id) REFERENCES public.app_bankaccount(account_no) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: app_talerwithdrawoperation app_talerwithdrawope_selected_exchange_ac_6c8b96cf_fk_app_banka; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_talerwithdrawoperation
- ADD CONSTRAINT app_talerwithdrawope_selected_exchange_ac_6c8b96cf_fk_app_banka FOREIGN KEY (selected_exchange_account_id) REFERENCES public.app_bankaccount(account_no) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: app_talerwithdrawoperation app_talerwithdrawope_withdraw_account_id_992dc5b3_fk_app_banka; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_talerwithdrawoperation
- ADD CONSTRAINT app_talerwithdrawope_withdraw_account_id_992dc5b3_fk_app_banka FOREIGN KEY (withdraw_account_id) REFERENCES public.app_bankaccount(account_no) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auditor_denomination_pending auditor_denomination_pending_denom_pub_hash_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denomination_pending
- ADD CONSTRAINT auditor_denomination_pending_denom_pub_hash_fkey FOREIGN KEY (denom_pub_hash) REFERENCES public.auditor_denominations(denom_pub_hash) ON DELETE CASCADE;
-
-
---
--- Name: auth_group_permissions auth_group_permissio_permission_id_84c5c92e_fk_auth_perm; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group_permissions
- ADD CONSTRAINT auth_group_permissio_permission_id_84c5c92e_fk_auth_perm FOREIGN KEY (permission_id) REFERENCES public.auth_permission(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auth_group_permissions auth_group_permissions_group_id_b120cbf9_fk_auth_group_id; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group_permissions
- ADD CONSTRAINT auth_group_permissions_group_id_b120cbf9_fk_auth_group_id FOREIGN KEY (group_id) REFERENCES public.auth_group(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auth_permission auth_permission_content_type_id_2f476e4b_fk_django_co; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_permission
- ADD CONSTRAINT auth_permission_content_type_id_2f476e4b_fk_django_co FOREIGN KEY (content_type_id) REFERENCES public.django_content_type(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auth_user_groups auth_user_groups_group_id_97559544_fk_auth_group_id; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_groups
- ADD CONSTRAINT auth_user_groups_group_id_97559544_fk_auth_group_id FOREIGN KEY (group_id) REFERENCES public.auth_group(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auth_user_groups auth_user_groups_user_id_6a12ed8b_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_groups
- ADD CONSTRAINT auth_user_groups_user_id_6a12ed8b_fk_auth_user_id FOREIGN KEY (user_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auth_user_user_permissions auth_user_user_permi_permission_id_1fbb5f2c_fk_auth_perm; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_user_permissions
- ADD CONSTRAINT auth_user_user_permi_permission_id_1fbb5f2c_fk_auth_perm FOREIGN KEY (permission_id) REFERENCES public.auth_permission(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auth_user_user_permissions auth_user_user_permissions_user_id_a95ead1b_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_user_permissions
- ADD CONSTRAINT auth_user_user_permissions_user_id_a95ead1b_fk_auth_user_id FOREIGN KEY (user_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: denomination_revocations denomination_revocations_denom_pub_hash_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denomination_revocations
- ADD CONSTRAINT denomination_revocations_denom_pub_hash_fkey FOREIGN KEY (denom_pub_hash) REFERENCES public.denominations(denom_pub_hash) ON DELETE CASCADE;
-
-
---
--- Name: deposits deposits_coin_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_coin_pub_fkey FOREIGN KEY (coin_pub) REFERENCES public.known_coins(coin_pub) ON DELETE CASCADE;
-
-
---
--- Name: known_coins known_coins_denom_pub_hash_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.known_coins
- ADD CONSTRAINT known_coins_denom_pub_hash_fkey FOREIGN KEY (denom_pub_hash) REFERENCES public.denominations(denom_pub_hash) ON DELETE CASCADE;
-
-
---
--- Name: auditor_exchange_signkeys master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_exchange_signkeys
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_denominations master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denominations
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_progress_reserve master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_reserve
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_progress_aggregation master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_aggregation
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_progress_deposit_confirmation master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_deposit_confirmation
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_progress_coin master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_coin
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: wire_auditor_account_progress master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_auditor_account_progress
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: wire_auditor_progress master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_auditor_progress
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_reserves master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_reserves
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_reserve_balance master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_reserve_balance
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_wire_fee_balance master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_wire_fee_balance
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_balance_summary master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_balance_summary
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_historic_denomination_revenue master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_historic_denomination_revenue
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_historic_reserve_summary master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_historic_reserve_summary
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: deposit_confirmations master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposit_confirmations
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_predicted_result master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_predicted_result
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: merchant_deposits merchant_deposits_h_contract_terms_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_h_contract_terms_fkey FOREIGN KEY (h_contract_terms, merchant_pub) REFERENCES public.merchant_contract_terms(h_contract_terms, merchant_pub);
-
-
---
--- Name: merchant_tip_pickups merchant_tip_pickups_tip_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickups
- ADD CONSTRAINT merchant_tip_pickups_tip_id_fkey FOREIGN KEY (tip_id) REFERENCES public.merchant_tips(tip_id) ON DELETE CASCADE;
-
-
---
--- Name: recoup recoup_coin_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup
- ADD CONSTRAINT recoup_coin_pub_fkey FOREIGN KEY (coin_pub) REFERENCES public.known_coins(coin_pub);
-
-
---
--- Name: recoup recoup_h_blind_ev_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup
- ADD CONSTRAINT recoup_h_blind_ev_fkey FOREIGN KEY (h_blind_ev) REFERENCES public.reserves_out(h_blind_ev) ON DELETE CASCADE;
-
-
---
--- Name: recoup_refresh recoup_refresh_coin_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh
- ADD CONSTRAINT recoup_refresh_coin_pub_fkey FOREIGN KEY (coin_pub) REFERENCES public.known_coins(coin_pub);
-
-
---
--- Name: recoup_refresh recoup_refresh_h_blind_ev_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh
- ADD CONSTRAINT recoup_refresh_h_blind_ev_fkey FOREIGN KEY (h_blind_ev) REFERENCES public.refresh_revealed_coins(h_coin_ev) ON DELETE CASCADE;
-
-
---
--- Name: refresh_commitments refresh_commitments_old_coin_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_commitments
- ADD CONSTRAINT refresh_commitments_old_coin_pub_fkey FOREIGN KEY (old_coin_pub) REFERENCES public.known_coins(coin_pub) ON DELETE CASCADE;
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_denom_pub_hash_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_denom_pub_hash_fkey FOREIGN KEY (denom_pub_hash) REFERENCES public.denominations(denom_pub_hash) ON DELETE CASCADE;
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_rc_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_rc_fkey FOREIGN KEY (rc) REFERENCES public.refresh_commitments(rc) ON DELETE CASCADE;
-
-
---
--- Name: refresh_transfer_keys refresh_transfer_keys_rc_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_transfer_keys
- ADD CONSTRAINT refresh_transfer_keys_rc_fkey FOREIGN KEY (rc) REFERENCES public.refresh_commitments(rc) ON DELETE CASCADE;
-
-
---
--- Name: refunds refunds_coin_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refunds
- ADD CONSTRAINT refunds_coin_pub_fkey FOREIGN KEY (coin_pub) REFERENCES public.known_coins(coin_pub) ON DELETE CASCADE;
-
-
---
--- Name: reserves_close reserves_close_reserve_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_close
- ADD CONSTRAINT reserves_close_reserve_pub_fkey FOREIGN KEY (reserve_pub) REFERENCES public.reserves(reserve_pub) ON DELETE CASCADE;
-
-
---
--- Name: reserves_in reserves_in_reserve_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_in
- ADD CONSTRAINT reserves_in_reserve_pub_fkey FOREIGN KEY (reserve_pub) REFERENCES public.reserves(reserve_pub) ON DELETE CASCADE;
-
-
---
--- Name: reserves_out reserves_out_denom_pub_hash_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out
- ADD CONSTRAINT reserves_out_denom_pub_hash_fkey FOREIGN KEY (denom_pub_hash) REFERENCES public.denominations(denom_pub_hash);
-
-
---
--- Name: reserves_out reserves_out_reserve_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out
- ADD CONSTRAINT reserves_out_reserve_pub_fkey FOREIGN KEY (reserve_pub) REFERENCES public.reserves(reserve_pub) ON DELETE CASCADE;
-
-
---
--- Name: aggregation_tracking wire_out_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.aggregation_tracking
- ADD CONSTRAINT wire_out_ref FOREIGN KEY (wtid_raw) REFERENCES public.wire_out(wtid_raw) ON DELETE CASCADE DEFERRABLE;
-
-
---
--- PostgreSQL database dump complete
---
-
diff --git a/src/auditor/auditor.conf b/src/auditor/auditor.conf
index f90d4dbb0..3c04d196f 100644
--- a/src/auditor/auditor.conf
+++ b/src/auditor/auditor.conf
@@ -9,11 +9,15 @@ DB = postgres
#TINY_AMOUNT = KUDOS:0.01
# Where do we store the auditor's private key?
-AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv
+AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}auditor/offline-keys/auditor.priv
+
+# What is the public key of this auditor? Used for processes that
+# verify auditor's signatures but have no access to the private key.
+# PUBLIC_KEY = VALUE
# What is the Web site of the auditor (i.e. to file complaints about
# a misbehaving exchange)?
-# AUDITOR_URL = https://auditor.taler.net/
+BASE_URL = http://localhost:8083/
# Network configuration for the normal API/service HTTP server
@@ -22,7 +26,7 @@ SERVE = tcp
# Unix domain socket to listen on,
# only effective with "SERVE = unix"
-UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http
+UNIXPATH = ${TALER_RUNTIME_DIR}exchange.http
UNIXPATH_MODE = 660
# HTTP port the auditor listens to
diff --git a/src/auditor/batch.conf b/src/auditor/batch.conf
new file mode 100644
index 000000000..cd8c64b8e
--- /dev/null
+++ b/src/auditor/batch.conf
@@ -0,0 +1,183 @@
+[benchmark]
+MERCHANT_DETAILS = merchant_details.json
+BANK_DETAILS = bank_details.json
+
+[coin_kudos_10]
+rsa_keysize = 1024
+CIPHER = RSA
+fee_refund = TESTKUDOS:0.01
+fee_refresh = TESTKUDOS:0.03
+fee_deposit = TESTKUDOS:0.01
+fee_withdraw = TESTKUDOS:0.01
+duration_legal = 3 years
+duration_spend = 2 years
+duration_withdraw = 7 days
+value = TESTKUDOS:10
+
+[coin_kudos_8]
+rsa_keysize = 1024
+CIPHER = RSA
+fee_refund = TESTKUDOS:0.04
+fee_refresh = TESTKUDOS:0.03
+fee_deposit = TESTKUDOS:0.02
+fee_withdraw = TESTKUDOS:0.05
+duration_legal = 3 years
+duration_spend = 2 years
+duration_withdraw = 7 days
+value = TESTKUDOS:8
+
+[coin_kudos_5]
+rsa_keysize = 1024
+CIPHER = RSA
+fee_refund = TESTKUDOS:0.01
+fee_refresh = TESTKUDOS:0.03
+fee_deposit = TESTKUDOS:0.01
+fee_withdraw = TESTKUDOS:0.01
+duration_legal = 3 years
+duration_spend = 2 years
+duration_withdraw = 7 days
+value = TESTKUDOS:5
+
+[coin_kudos_4]
+rsa_keysize = 1024
+CIPHER = RSA
+fee_refund = TESTKUDOS:0.02
+fee_refresh = TESTKUDOS:0.04
+fee_deposit = TESTKUDOS:0.03
+fee_withdraw = TESTKUDOS:0.03
+duration_legal = 3 years
+duration_spend = 2 years
+duration_withdraw = 7 days
+value = TESTKUDOS:4
+
+[coin_kudos_2]
+rsa_keysize = 1024
+CIPHER = RSA
+fee_refund = TESTKUDOS:0.02
+fee_refresh = TESTKUDOS:0.04
+fee_deposit = TESTKUDOS:0.03
+fee_withdraw = TESTKUDOS:0.03
+duration_legal = 3 years
+duration_spend = 2 years
+duration_withdraw = 7 days
+value = TESTKUDOS:2
+
+[coin_kudos_1]
+rsa_keysize = 1024
+CIPHER = RSA
+fee_refund = TESTKUDOS:0.01
+fee_refresh = TESTKUDOS:0.03
+fee_deposit = TESTKUDOS:0.02
+fee_withdraw = TESTKUDOS:0.02
+duration_legal = 3 years
+duration_spend = 2 years
+duration_withdraw = 7 days
+value = TESTKUDOS:1
+
+[coin_kudos_ct_10]
+rsa_keysize = 1024
+CIPHER = RSA
+fee_refund = TESTKUDOS:0.01
+fee_refresh = TESTKUDOS:0.03
+fee_deposit = TESTKUDOS:0.01
+fee_withdraw = TESTKUDOS:0.01
+duration_legal = 3 years
+duration_spend = 2 years
+duration_withdraw = 7 days
+value = TESTKUDOS:0.10
+
+[coin_kudos_ct_1]
+rsa_keysize = 1024
+CIPHER = RSA
+fee_refund = TESTKUDOS:0.01
+fee_refresh = TESTKUDOS:0.01
+fee_deposit = TESTKUDOS:0.01
+fee_withdraw = TESTKUDOS:0.01
+duration_legal = 3 years
+duration_spend = 2 years
+duration_withdraw = 7 days
+value = TESTKUDOS:0.01
+
+[payments-generator]
+exchange = http://localhost:8081/
+exchange-admin = http://localhost:18080/
+exchange_admin = http://localhost:18080/
+merchant = http://localhost:9966/
+bank = http://localhost:8082/
+instance = default
+currency = TESTKUDOS
+
+[merchant-exchange-default]
+CURRENCY = TESTKUDOS
+EXCHANGE_BASE_URL = http://localhost:8081/
+MASTER_KEY = 2XPQZ7B7EERWT7GR0MF30HPFG4TA1J0CWCQ3XBD48PA4K7GVDBK0
+
+[merchant-account-merchant]
+ACTIVE_default = YES
+HONOR_default = YES
+PAYTO_URI = payto://x-taler-bank/localhost/42
+
+[exchange-accountcredentials-1]
+PASSWORD = x
+USERNAME = Exchange
+WIRE_GATEWAY_AUTH_METHOD = basic
+WIRE_GATEWAY_URL = http://localhost:8082/accounts/Exchange/taler-wire-gateway/
+
+[exchange-account-1]
+enable_credit = yes
+enable_debit = yes
+PAYTO_URI = payto://x-taler-bank/localhost/Exchange
+
+[instance-default]
+NAME = Merchant Inc.
+KEYFILE = ${TALER_DATA_HOME}/merchant/default.priv
+
+[taler]
+CURRENCY_ROUND_UNIT = TESTKUDOS:0.01
+CURRENCY = TESTKUDOS
+
+[merchantdb-postgres]
+CONFIG = postgres:///batch
+
+[merchant]
+DEFAULT_MAX_WIRE_FEE = TESTKUDOS:0.10
+KEYFILE = ${TALER_DATA_HOME}/merchant/merchant.priv
+DEFAULT_MAX_DEPOSIT_FEE = TESTKUDOS:0.1
+WIREFORMAT = default
+WIRE_TRANSFER_DELAY = 1 minute
+FORCE_AUDIT = YES
+UNIXPATH = ${TALER_RUNTIME_DIR}/merchant.http
+
+[exchangedb-postgres]
+CONFIG = postgres:///batch
+
+[exchange]
+LOOKAHEAD_SIGN = 32 weeks 1 day
+SIGNKEY_DURATION = 4 weeks
+MASTER_PUBLIC_KEY = 2XPQZ7B7EERWT7GR0MF30HPFG4TA1J0CWCQ3XBD48PA4K7GVDBK0
+SIGNKEY_LEGAL_DURATION = 4 weeks
+UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http
+
+[bank]
+SERVE = http
+ALLOW_REGISTRATIONS = YES
+SUGGESTED_EXCHANGE_PAYTO = payto://x-taler-bank/localhost:8082/2
+SUGGESTED_EXCHANGE = http://localhost:8081/
+HTTP_PORT = 8082
+MAX_DEBT_BANK = TESTKUDOS:100000.0
+MAX_DEBT = TESTKUDOS:50.0
+DATABASE = postgres:///batch
+
+[auditordb-postgres]
+CONFIG = postgres:///batch
+
+[auditor]
+PUBLIC_KEY = JG9QFRG7R7BH9701420BD6M38NZW21MV9AR3QHYJEAHZ4S26B3HG
+TINY_AMOUNT = TESTKUDOS:0.01
+BASE_URL = http://localhost:8083/
+
+[PATHS]
+TALER_CACHE_HOME = $TALER_HOME/.cache/taler/
+TALER_CONFIG_HOME = $TALER_HOME/.config/taler/
+TALER_DATA_HOME = $TALER_HOME/.local/share/taler/
+TALER_HOME = ${PWD}/generate_auditordb_home/
diff --git a/src/auditor/batch.sh b/src/auditor/batch.sh
new file mode 100755
index 000000000..bbe1be0ba
--- /dev/null
+++ b/src/auditor/batch.sh
@@ -0,0 +1,235 @@
+#!/bin/bash
+set -eu
+
+# Cleanup to run whenever we exit
+function cleanup()
+{
+ for n in `jobs -p`
+ do
+ kill $n 2> /dev/null || true
+ done
+ wait
+}
+
+# Install cleanup handler (except for kill -9)
+trap cleanup EXIT
+
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo $1
+ exit 77
+}
+
+# Where do we write the result?
+BASEDB=${1:-"batch"}
+
+# Name of the Postgres database we will use for the script.
+# Will be dropped, do NOT use anything that might be used
+# elsewhere
+export TARGET_DB=`basename ${BASEDB}`
+
+export WALLET_DB=${BASEDB:-"wallet"}.wdb
+
+# delete existing wallet database
+rm -f $WALLET_DB
+
+
+# Configuration file will be edited, so we create one
+# from the template.
+CONF=${BASEDB}.conf
+cp generate-auditor-basedb.conf $CONF
+
+
+echo -n "Testing for taler-fakebank-run"
+taler-fakebank-run -h >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+echo -n "Testing for taler-wallet-cli"
+taler-wallet-cli -v >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+echo -n "Testing for curl"
+curl --help >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+
+
+pwd
+# Clean up
+
+DATA_DIR=`taler-config -f -c $CONF -s PATHS -o TALER_HOME`
+rm -rf $DATA_DIR || true
+
+# reset database
+dropdb $TARGET_DB >/dev/null 2>/dev/null || true
+createdb $TARGET_DB || exit_skip "Could not create database $TARGET_DB"
+
+
+# obtain key configuration data
+MASTER_PRIV_FILE=$(taler-config -f -c $CONF -s exchange-offline -o MASTER_PRIV_FILE)
+MASTER_PRIV_DIR=$(dirname $MASTER_PRIV_FILE)
+mkdir -p $MASTER_PRIV_DIR
+gnunet-ecc -g1 $MASTER_PRIV_FILE > /dev/null
+MASTER_PUB=$(gnunet-ecc -p $MASTER_PRIV_FILE)
+EXCHANGE_URL=$(taler-config -c $CONF -s EXCHANGE -o BASE_URL)
+MERCHANT_PORT=$(taler-config -c $CONF -s MERCHANT -o PORT)
+MERCHANT_URL=http://localhost:${MERCHANT_PORT}/
+BANK_PORT=$(taler-config -c $CONF -s BANK -o HTTP_PORT)
+BANK_URL=http://localhost:${BANK_PORT}/
+AUDITOR_URL=http://localhost:8083/
+AUDITOR_PRIV_FILE=$(taler-config -f -c $CONF -s AUDITOR -o AUDITOR_PRIV_FILE)
+AUDITOR_PRIV_DIR=$(dirname $AUDITOR_PRIV_FILE)
+mkdir -p $AUDITOR_PRIV_DIR
+gnunet-ecc -g1 $AUDITOR_PRIV_FILE > /dev/null
+AUDITOR_PUB=$(gnunet-ecc -p $AUDITOR_PRIV_FILE)
+
+echo "AUDITOR PUB is $AUDITOR_PUB using file $AUDITOR_PRIV_FILE"
+
+# patch configuration
+taler-config -c $CONF -s exchange -o MASTER_PUBLIC_KEY -V $MASTER_PUB
+taler-config -c $CONF -s auditor -o PUBLIC_KEY -V $AUDITOR_PUB
+taler-config -c $CONF -s merchant-exchange-default -o MASTER_KEY -V $MASTER_PUB
+taler-config -c $CONF -s exchangedb-postgres -o CONFIG -V postgres:///$TARGET_DB
+taler-config -c $CONF -s auditordb-postgres -o CONFIG -V postgres:///$TARGET_DB
+taler-config -c $CONF -s merchantdb-postgres -o CONFIG -V postgres:///$TARGET_DB
+taler-config -c $CONF -s bank -o database -V postgres:///$TARGET_DB
+
+# setup exchange
+echo "Setting up exchange"
+taler-exchange-dbinit -c $CONF
+
+echo "Setting up merchant"
+taler-merchant-dbinit -c $CONF
+
+# setup auditor
+echo "Setting up auditor"
+taler-auditor-dbinit -c $CONF || exit_skip "Failed to initialize auditor DB"
+taler-auditor-exchange -c $CONF -m $MASTER_PUB -u $EXCHANGE_URL || exit_skip "Failed to add exchange to auditor"
+
+# Launch services
+echo "Launching services"
+taler-fakebank-run -c $CONF &> taler-bank.log &
+TFN=`which taler-exchange-httpd`
+TBINPFX=`dirname $TFN`
+TLIBEXEC=${TBINPFX}/../lib/taler/libexec/
+taler-exchange-secmod-eddsa -c $CONF 2> taler-exchange-secmod-eddsa.log &
+taler-exchange-secmod-rsa -c $CONF 2> taler-exchange-secmod-rsa.log &
+taler-exchange-secmod-cs -c $CONF 2> taler-exchange-secmod-cs.log &
+taler-exchange-httpd -c $CONF 2> taler-exchange-httpd.log &
+taler-merchant-httpd -c $CONF -L INFO 2> taler-merchant-httpd.log &
+taler-exchange-wirewatch -c $CONF 2> taler-exchange-wirewatch.log &
+taler-auditor-httpd -L INFO -c $CONF 2> taler-auditor-httpd.log &
+
+# Wait for all bank to be available (usually the slowest)
+for n in `seq 1 50`
+do
+ echo -n "."
+ sleep 0.2
+ OK=0
+ # bank
+ wget http://localhost:8082/ -o /dev/null -O /dev/null >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ 1 != $OK ]
+then
+ exit_skip "Failed to launch services"
+fi
+
+# Wait for all services to be available
+for n in `seq 1 50`
+do
+ echo -n "."
+ sleep 0.1
+ OK=0
+ # exchange
+ wget http://localhost:8081/seed -o /dev/null -O /dev/null >/dev/null || continue
+ # merchant
+ wget http://localhost:9966/ -o /dev/null -O /dev/null >/dev/null || continue
+ # Auditor
+ wget http://localhost:8083/ -o /dev/null -O /dev/null >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ 1 != $OK ]
+then
+ exit_skip "Failed to launch services"
+fi
+echo " DONE"
+
+echo -n "Setting up keys"
+taler-exchange-offline -c $CONF \
+ download sign \
+ enable-account payto://x-taler-bank/localhost/Exchange \
+ enable-auditor $AUDITOR_PUB $AUDITOR_URL "TESTKUDOS Auditor" \
+ wire-fee now x-taler-bank TESTKUDOS:0.01 TESTKUDOS:0.01 TESTKUDOS:0.01 \
+ global-fee now TESTKUDOS:0.01 TESTKUDOS:0.01 TESTKUDOS:0.01 TESTKUDOS:0.01 1h 1h 1year 5 \
+ upload &> taler-exchange-offline.log
+
+echo -n "."
+
+for n in `seq 1 2`
+do
+ echo -n "."
+ OK=0
+ wget --timeout=1 http://localhost:8081/keys -o /dev/null -O /dev/null >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ 1 != $OK ]
+then
+ exit_skip "Failed to setup keys"
+fi
+
+echo " DONE"
+echo -n "Adding auditor signatures ..."
+
+taler-auditor-offline -c $CONF \
+ download sign upload &> taler-auditor-offline.log
+
+echo " DONE"
+# Setup merchant
+
+echo -n "Setting up merchant"
+
+curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"accounts":[{"payto_uri":"payto://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"
+
+# run wallet CLI
+echo "Ready to run wallet"
+export WALLET_DB
+export EXCHANGE_URL
+export MERCHANT_URL
+export BANK_URL
+unset TALER_WALLET_INSECURE_TRUST_EXCHANGE
+export TALER_WALLET_BATCH_WITHDRAWAL=1
+echo 'taler-wallet-cli --wallet-db=$WALLET_DB -L TRACE advanced bench1 --config-json "{ \"exchange\": \"$EXCHANGE_URL\", \"bank\": \"${BANK_URL}\", \"currency\": \"TESTKUDOS\", \"payto\": \"payto://x-taler-bank/localhost/foo\", \"iterations\": 100000, \"deposits\": 10, \"restartAfter\": 2 }"'
+bash
+
+#taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api 'runIntegrationTest' \
+# "$(jq -n '
+# {
+# amountToSpend: "TESTKUDOS:4",
+# amountToWithdraw: "TESTKUDOS:10",
+# corebankApiBaseUrl: $BANK_URL,
+# exchangeBaseUrl: $EXCHANGE_URL,
+# merchantBaseUrl: $MERCHANT_URL,
+# }' \
+# --arg MERCHANT_URL "$MERCHANT_URL" \
+# --arg EXCHANGE_URL "$EXCHANGE_URL" \
+# --arg BANK_URL "$BANK_URL"
+# )" &> taler-wallet-cli.log
+
+
+echo "Shutting down services"
+cleanup
+
+# clean up
+echo "Final clean up"
+dropdb $TARGET_DB
+
+rm -rf $DATA_DIR || true
+exit 0
diff --git a/src/auditor/generate-auditor-basedb-template.conf b/src/auditor/generate-auditor-basedb-template.conf
deleted file mode 100644
index 1d18740c7..000000000
--- a/src/auditor/generate-auditor-basedb-template.conf
+++ /dev/null
@@ -1 +0,0 @@
-@INLINE@ generate-auditor-basedb.conf
diff --git a/src/auditor/generate-auditor-basedb.conf b/src/auditor/generate-auditor-basedb.conf
index 99721d3d6..8cf63fbba 100644
--- a/src/auditor/generate-auditor-basedb.conf
+++ b/src/auditor/generate-auditor-basedb.conf
@@ -1,135 +1,123 @@
+[PATHS]
+TALER_CACHE_HOME = $TALER_HOME/.cache/taler/
+TALER_CONFIG_HOME = $TALER_HOME/.config/taler/
+TALER_DATA_HOME = $TALER_HOME/.local/share/taler/
+TALER_HOME = ${PWD}/generate_auditordb_home/
+[taler]
+CURRENCY = TESTKUDOS
+CURRENCY_ROUND_UNIT = TESTKUDOS:0.01
+
[exchange]
-KEYDIR = ${TALER_DATA_HOME}/exchange/live-keys/
-REVOCATION_DIR = ${TALER_DATA_HOME}/exchange/revocations/
-MAX_KEYS_CACHING = forever
-DB = postgres
-MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/master.priv
-SERVE = tcp
-UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http
-UNIXPATH_MODE = 660
-PORT = 8081
-BASE_URL = http://localhost:8081/
+MASTER_PUBLIC_KEY = M4FGP18EQFXFGGFQ1AWXHACN2JX0SMVK9CNF6459Z1WG18JSN0BG
SIGNKEY_DURATION = 4 weeks
-LEGAL_DURATION = 2 years
LOOKAHEAD_SIGN = 32 weeks 1 day
-LOOKAHEAD_PROVIDE = 4 weeks 1 day
-
-[merchant]
-SERVE = tcp
-PORT = 9966
-UNIXPATH = ${TALER_RUNTIME_DIR}/merchant.http
-UNIXPATH_MODE = 660
-DEFAULT_WIRE_FEE_AMORTIZATION = 1
-DB = postgres
-WIREFORMAT = default
-# Set very low, so we can be sure that the database generated
-# will contain wire transfers "ready" for the aggregator.
-WIRE_TRANSFER_DELAY = 1 minute
-DEFAULT_PAY_DEADLINE = 1 day
-DEFAULT_MAX_DEPOSIT_FEE = TESTKUDOS:0.1
-KEYFILE = ${TALER_DATA_HOME}/merchant/merchant.priv
-DEFAULT_MAX_WIRE_FEE = TESTKUDOS:0.10
-
-# Ensure that merchant reports EVERY deposit confirmation to auditor
-FORCE_AUDIT = YES
+SIGNKEY_LEGAL_DURATION = 4 weeks
+AML_THRESHOLD = TESTKUDOS:1000000
+db = postgres
+BASE_URL = http://localhost:8081/
+IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
+LEGAL_RESERVE_EXPIRATION_TIME = 4 weeks
-[instance-default]
-KEYFILE = ${TALER_DATA_HOME}/merchant/default.priv
-NAME = Merchant Inc.
+[exchangedb]
+IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
+LEGAL_RESERVE_EXPIRATION_TIME = 4 weeks
+AGGREGATOR_SHIFT = 1 s
+DEFAULT_PURSE_LIMIT = 1
-[auditor]
-DB = postgres
-AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv
+[libeufin-bank]
+CURRENCY = TESTKUDOS
+DEFAULT_CUSTOMER_DEBT_LIMIT = TESTKUDOS:200
+DEFAULT_ADMIN_DEBT_LIMIT = TESTKUDOS:2000
+ALLOW_REGISTRATION = yes
+REGISTRATION_BONUS_ENABLED = yes
+REGISTRATION_BONUS = TESTKUDOS:100
+SUGGESTED_WITHDRAWAL_EXCHANGE = http://localhost:8081/
+WIRE_TYPE = iban
+IBAN_PAYTO_BIC = SANDBOXX
SERVE = tcp
-UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http
-UNIXPATH_MODE = 660
-PORT = 8083
-AUDITOR_URL = http://localhost:8083/
-TINY_AMOUNT = TESTKUDOS:0.01
+PORT = 8082
-[PATHS]
-TALER_HOME = ${PWD}/generate_auditordb_home/
-TALER_DATA_HOME = $TALER_HOME/.local/share/taler/
-TALER_CONFIG_HOME = $TALER_HOME/.config/taler/
-TALER_CACHE_HOME = $TALER_HOME/.cache/taler/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/taler-system-runtime/
-
-[bank]
-DATABASE = postgres:///taler-auditor-basedb
-MAX_DEBT = TESTKUDOS:50.0
-MAX_DEBT_BANK = TESTKUDOS:100000.0
-HTTP_PORT = 8082
-SUGGESTED_EXCHANGE = http://localhost:8081/
-SUGGESTED_EXCHANGE_PAYTO = payto://x-taler-bank/localhost/2
-ALLOW_REGISTRATIONS = YES
+[libeufin-bankdb-postgres]
+CONFIG = postgresql:///auditor-basedb
-[exchangedb]
-AUDITOR_BASE_DIR = ${TALER_DATA_HOME}/auditors/
-WIREFEE_BASE_DIR = ${TALER_DATA_HOME}/exchange/wirefees/
+[exchangedb-postgres]
+CONFIG = postgres:///auditor-basedb
+SQL_DIR = $DATADIR/sql/exchange/
IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
-LEGAL_RESERVE_EXPIRATION_TIME = 7 years
+LEGAL_RESERVE_EXPIRATION_TIME = 4 weeks
-[exchange_keys]
-signkey_duration = 4 weeks
-legal_duration = 2 years
-lookahead_sign = 32 weeks 1 day
-lookahead_provide = 4 weeks 1 day
+[exchange-account-1]
+PAYTO_URI = payto://iban/SANDBOXX/DE989651?receiver-name=Exchange+Company
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
-[taler]
-CURRENCY = TESTKUDOS
-CURRENCY_ROUND_UNIT = TESTKUDOS:0.01
+[exchange-accountcredentials-1]
+WIRE_GATEWAY_URL = http://localhost:8082/accounts/exchange/taler-wire-gateway/
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = exchange
+PASSWORD = x
-[exchange-account-1]
-WIRE_RESPONSE = ${TALER_DATA_HOME}/exchange/account-1.json
-PAYTO_URI = payto://x-taler-bank/localhost/Exchange
-enable_debit = yes
-enable_credit = yes
-WIRE_GATEWAY_URL = "http://localhost:8082/taler-wire-gateway/Exchange/"
+[exchange-account-2]
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-2]
WIRE_GATEWAY_AUTH_METHOD = basic
USERNAME = Exchange
PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
-[merchant-account-merchant]
-PAYTO_URI = payto://x-taler-bank/localhost/42
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/merchant/account-3.json
-HONOR_default = YES
-ACTIVE_default = YES
-
-[fees-x-taler-bank]
-wire-fee-2020 = TESTKUDOS:0.01
-closing-fee-2020 = TESTKUDOS:0.01
-wire-fee-2021 = TESTKUDOS:0.01
-closing-fee-2021 = TESTKUDOS:0.01
-wire-fee-2022 = TESTKUDOS:0.01
-closing-fee-2022 = TESTKUDOS:0.01
-wire-fee-2023 = TESTKUDOS:0.01
-closing-fee-2023 = TESTKUDOS:0.01
-wire-fee-2024 = TESTKUDOS:0.01
-closing-fee-2024 = TESTKUDOS:0.01
-wire-fee-2025 = TESTKUDOS:0.01
-closing-fee-2025 = TESTKUDOS:0.01
-wire-fee-2026 = TESTKUDOS:0.01
-closing-fee-2026 = TESTKUDOS:0.01
-wire-fee-2027 = TESTKUDOS:0.01
-closing-fee-2027 = TESTKUDOS:0.01
-wire-fee-2028 = TESTKUDOS:0.01
-closing-fee-2028 = TESTKUDOS:0.01
-
-[merchant-instance-wireformat-default]
-TEST_RESPONSE_FILE = ${TALER_CONFIG_HOME}/merchant/wire/tutorial.json
+[admin-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = basic
+# For now, fakebank still checks against the Exchange account...
+USERNAME = Exchange
+PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+
+
+[merchant]
+FORCE_AUDIT = YES
+SERVE = TCP
+PORT = 8888
+
+[merchantdb-postgres]
+CONFIG = postgres:///auditor-basedb
+SQL_DIR = $DATADIR/sql/merchant/
[merchant-exchange-default]
+MASTER_KEY = M4FGP18EQFXFGGFQ1AWXHACN2JX0SMVK9CNF6459Z1WG18JSN0BG
EXCHANGE_BASE_URL = http://localhost:8081/
CURRENCY = TESTKUDOS
-[payments-generator]
-currency = TESTKUDOS
-instance = default
-bank = http://localhost:8082/
-merchant = http://localhost:9966/
-exchange_admin = http://localhost:18080/
-exchange-admin = http://localhost:18080/
-exchange = http://localhost:8081/
+[bank]
+HTTP_PORT = 8082
+
+[libeufin-nexus]
+DB_CONNECTION="postgresql:///auditor-basedb"
+
+[libeufin-sandbox]
+DB_CONNECTION="postgresql:///auditor-basedb"
+
+[libeufin-bank]
+CURRENCY = TESTKUDOS
+DEFAULT_CUSTOMER_DEBT_LIMIT = TESTKUDOS:200 # dead
+DEFAULT_ADMIN_DEBT_LIMIT = TESTKUDOS:2000
+REGISTRATION_BONUS_ENABLED = yes
+REGISTRATION_BONUS = TESTKUDOS:100
+SUGGESTED_WITHDRAWAL_EXCHANGE = http://localhost:8081/
+SERVE = tcp
+PORT = 8082
+
+[auditor]
+BASE_URL = http://localhost:8083/
+TINY_AMOUNT = TESTKUDOS:0.01
+PUBLIC_KEY = 0EHPW5WEKHXPPN4MPJNGA7Z6D29JP21GKVNV8ARFB1YW7WWJX20G
+db = postgres
+
+[auditordb-postgres]
+CONFIG = postgres:///auditor-basedb
+SQL_DIR = $DATADIR/sql/auditor/
[coin_kudos_ct_1]
value = TESTKUDOS:0.01
@@ -140,6 +128,7 @@ fee_withdraw = TESTKUDOS:0.01
fee_deposit = TESTKUDOS:0.01
fee_refresh = TESTKUDOS:0.01
fee_refund = TESTKUDOS:0.01
+CIPHER = RSA
rsa_keysize = 1024
[coin_kudos_ct_10]
@@ -151,6 +140,7 @@ fee_withdraw = TESTKUDOS:0.01
fee_deposit = TESTKUDOS:0.01
fee_refresh = TESTKUDOS:0.03
fee_refund = TESTKUDOS:0.01
+CIPHER = RSA
rsa_keysize = 1024
[coin_kudos_1]
@@ -162,6 +152,7 @@ fee_withdraw = TESTKUDOS:0.02
fee_deposit = TESTKUDOS:0.02
fee_refresh = TESTKUDOS:0.03
fee_refund = TESTKUDOS:0.01
+CIPHER = RSA
rsa_keysize = 1024
[coin_kudos_2]
@@ -173,6 +164,7 @@ fee_withdraw = TESTKUDOS:0.03
fee_deposit = TESTKUDOS:0.03
fee_refresh = TESTKUDOS:0.04
fee_refund = TESTKUDOS:0.02
+CIPHER = RSA
rsa_keysize = 1024
[coin_kudos_4]
@@ -184,6 +176,7 @@ fee_withdraw = TESTKUDOS:0.03
fee_deposit = TESTKUDOS:0.03
fee_refresh = TESTKUDOS:0.04
fee_refund = TESTKUDOS:0.02
+CIPHER = RSA
rsa_keysize = 1024
[coin_kudos_5]
@@ -195,6 +188,7 @@ fee_withdraw = TESTKUDOS:0.01
fee_deposit = TESTKUDOS:0.01
fee_refresh = TESTKUDOS:0.03
fee_refund = TESTKUDOS:0.01
+CIPHER = RSA
rsa_keysize = 1024
[coin_kudos_8]
@@ -206,6 +200,7 @@ fee_withdraw = TESTKUDOS:0.05
fee_deposit = TESTKUDOS:0.02
fee_refresh = TESTKUDOS:0.03
fee_refund = TESTKUDOS:0.04
+CIPHER = RSA
rsa_keysize = 1024
[coin_kudos_10]
@@ -217,8 +212,5 @@ fee_withdraw = TESTKUDOS:0.01
fee_deposit = TESTKUDOS:0.01
fee_refresh = TESTKUDOS:0.03
fee_refund = TESTKUDOS:0.01
+CIPHER = RSA
rsa_keysize = 1024
-
-[benchmark]
-BANK_DETAILS = bank_details.json
-MERCHANT_DETAILS = merchant_details.json
diff --git a/src/auditor/generate-auditor-basedb.sh b/src/auditor/generate-auditor-basedb.sh
index 3ecd43dd0..bbce37cdc 100755
--- a/src/auditor/generate-auditor-basedb.sh
+++ b/src/auditor/generate-auditor-basedb.sh
@@ -1,159 +1,143 @@
#!/bin/bash
-# Script to generate the basic database for auditor
-# testing from a 'correct' interaction between exchange,
-# wallet and merchant.
+# This file is in the public domain.
#
-# Creates $BASEDB.sql, $BASEDB.fees, $BASEDB.mpub and
-# $BASEDB.age.
-# Default $BASEDB is "auditor-basedb", override via $1.
+# Script to generate the basic database for auditor testing from a 'correct'
+# interaction between exchange, wallet and merchant.
#
-# Currently must be run online as it interacts with
-# bank.test.taler.net; also requires the wallet CLI
-# to be installed and in the path. Furthermore, the
-# user running this script must be Postgres superuser
-# and be allowed to create/drop databases.
+# Creates "$1.sql".
+#
+# Requires the wallet CLI to be installed and in the path. Furthermore, the
+# user running this script must be Postgres superuser and be allowed to
+# create/drop databases.
#
set -eu
-trap "kill `jobs -p` &> /dev/null || true" ERR
-
-# Exit, with status code "skip" (no 'real' failure)
-function exit_skip() {
- echo $1
- exit 77
-}
+. setup.sh
+
+CONF="generate-auditor-basedb.conf"
+# Parse command-line options
+while getopts ':c:d:h' OPTION; do
+ case "$OPTION" in
+ c)
+ CONF="$OPTARG"
+ ;;
+ d)
+ BASEDB="$OPTARG"
+ ;;
+ h)
+ echo 'Supported options:'
+# shellcheck disable=SC2016
+ echo ' -c $CONF -- set configuration'
+# shellcheck disable=SC2016
+ echo ' -d $DB -- set database name'
+ ;;
+ ?)
+ exit_fail "Unrecognized command line option"
+ ;;
+ esac
+done
# Where do we write the result?
-BASEDB=${1:-"auditor-basedb"}
-
-# Name of the Postgres database we will use for the script.
-# Will be dropped, do NOT use anything that might be used
-# elsewhere
-TARGET_DB=taler-auditor-basedb
-
-# Configuration file will be edited, so we create one
-# from the template.
-CONF=generate-auditor-basedb-prod.conf
-cp generate-auditor-basedb-template.conf $CONF
-
+if [ ! -v BASEDB ]
+then
+ exit_fail "-d option required"
+fi
-echo -n "Testing for taler-bank-manage"
-taler-bank-manage -h >/dev/null </dev/null || exit_skip " MISSING"
-echo " FOUND"
-echo -n "Testing for taler-wallet-cli"
-taler-wallet-cli -v >/dev/null </dev/null || exit_skip " MISSING"
+echo -n "Testing for curl ..."
+curl --help >/dev/null </dev/null || exit_skip " MISSING"
echo " FOUND"
-
-
-# Clean up
-DATA_DIR=`taler-config -f -c $CONF -s PATHS -o TALER_HOME`
-rm -rf $DATA_DIR || true
-
# reset database
-dropdb $TARGET_DB >/dev/null 2>/dev/null || true
-createdb $TARGET_DB || exit_skip "Could not create database $TARGET_DB"
+echo -n "Reset 'auditor-basedb' database at $PGHOST ..."
+dropdb "auditor-basedb" >/dev/null 2>/dev/null || true
+createdb "auditor-basedb" || exit_skip "Could not create database '$BASEDB' at $PGHOST"
+echo " DONE"
+
+# Launch exchange, merchant and bank.
+setup -c "$CONF" \
+ -abemw \
+ -d "iban"
# obtain key configuration data
-MASTER_PRIV_FILE=`taler-config -f -c $CONF -s EXCHANGE -o MASTER_PRIV_FILE`
-MASTER_PRIV_DIR=`dirname $MASTER_PRIV_FILE`
-mkdir -p $MASTER_PRIV_DIR
-gnunet-ecc -g1 $MASTER_PRIV_FILE > /dev/null
-MASTER_PUB=`gnunet-ecc -p $MASTER_PRIV_FILE`
-EXCHANGE_URL=`taler-config -c $CONF -s EXCHANGE -o BASE_URL`
-MERCHANT_PORT=`taler-config -c $CONF -s MERCHANT -o PORT`
-MERCHANT_URL=http://localhost:${MERCHANT_PORT}/
-BANK_PORT=`taler-config -c $CONF -s BANK -o HTTP_PORT`
-BANK_URL=http://localhost:${BANK_PORT}/
-AUDITOR_URL=http://localhost:8083/
-
-# patch configuration
-taler-config -c $CONF -s exchange -o MASTER_PUBLIC_KEY -V $MASTER_PUB
-taler-config -c $CONF -s merchant-exchange-default -o MASTER_KEY -V $MASTER_PUB
-taler-config -c $CONF -s exchangedb-postgres -o CONFIG -V postgres:///$TARGET_DB
-taler-config -c $CONF -s auditordb-postgres -o CONFIG -V postgres:///$TARGET_DB
-taler-config -c $CONF -s merchantdb-postgres -o CONFIG -V postgres:///$TARGET_DB
-taler-config -c $CONF -s bank -o database -V postgres:///$TARGET_DB
-
-# setup exchange
-echo "Setting up exchange"
-taler-exchange-dbinit -c $CONF
-taler-exchange-wire -c $CONF 2> taler-exchange-wire.log
-taler-exchange-keyup -L INFO -c $CONF -o e2a.dat 2> taler-exchange-keyup.log
-
-# setup auditor
-echo "Setting up auditor"
-taler-auditor-dbinit -c $CONF
-taler-auditor-exchange -c $CONF -m $MASTER_PUB -u $EXCHANGE_URL
-taler-auditor-sign -c $CONF -u $AUDITOR_URL -r e2a.dat -o a2e.dat -m $MASTER_PUB
-rm -f e2a.dat
-
-# provide auditor's signature to exchange
-ABD=`taler-config -c $CONF -s EXCHANGEDB -o AUDITOR_BASE_DIR -f`
-mkdir -p $ABD
-mv a2e.dat $ABD
-
-# Launch services
-echo "Launching services"
-taler-bank-manage-testing $CONF postgres:///$TARGET_DB serve-http &
-taler-exchange-httpd -c $CONF 2> taler-exchange-httpd.log &
-taler-merchant-httpd -c $CONF -L INFO 2> taler-merchant-httpd.log &
-taler-exchange-wirewatch -c $CONF 2> taler-exchange-wirewatch.log &
-taler-auditor-httpd -c $CONF 2> taler-auditor-httpd.log &
-
-# Wait for all services to be available
-for n in `seq 1 50`
-do
- echo -n "."
- sleep 0.1
- OK=0
- # exchange
- wget http://localhost:8081/ -o /dev/null -O /dev/null >/dev/null || continue
- # merchant
- wget http://localhost:9966/ -o /dev/null -O /dev/null >/dev/null || continue
- # bank
- wget http://localhost:8082/ -o /dev/null -O /dev/null >/dev/null || continue
- # Auditor
- wget http://localhost:8083/ -o /dev/null -O /dev/null >/dev/null || continue
- OK=1
- break
-done
+EXCHANGE_URL=$(taler-config -c "$CONF" -s EXCHANGE -o BASE_URL)
+MERCHANT_PORT=$(taler-config -c "$CONF" -s MERCHANT -o PORT)
+MERCHANT_URL="http://localhost:${MERCHANT_PORT}/"
+BANK_PORT=$(taler-config -c "$CONF" -s BANK -o HTTP_PORT)
+BANK_URL="http://localhost:${BANK_PORT}"
+
+echo -n "Checking setup worked ..."
+wget \
+ --tries=1 \
+ --timeout=1 \
+ "${EXCHANGE_URL}config" \
+ -o /dev/null \
+ -O /dev/null >/dev/null
+echo "DONE"
+
+export MERCHANT_URL
+echo -n "Setting up merchant ..."
+
+curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"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},"use_stefan":false}' "${MERCHANT_URL}management/instances"
+echo " DONE"
+
+echo -n "Setting up merchant account ..."
+FORTYTHREE="payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ "${MERCHANT_URL}private/accounts" \
+ -d '{"payto_uri":"'"$FORTYTHREE"'"}' \
+ -w "%{http_code}" -s -o /dev/null)
-if [ 1 != $OK ]
+if [ "$STATUS" != "200" ]
then
- kill `jobs -p`
- wait
- exit_skip "Failed to launch services"
+ exit_fail "Expected 200 OK. Got: $STATUS"
fi
echo " DONE"
-# run wallet CLI
-echo "Running wallet"
-taler-wallet-cli testing integrationtest -e $EXCHANGE_URL -m $MERCHANT_URL -b $BANK_URL
+# delete existing wallet database
+export WALLET_DB="wallet.wdb"
+rm -f "$WALLET_DB"
+
+echo -n "Running wallet ..."
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ api \
+ --expect-success \
+ 'runIntegrationTest' \
+ "$(jq -n '
+ {
+ amountToSpend: "TESTKUDOS:4",
+ amountToWithdraw: "TESTKUDOS:10",
+ corebankApiBaseUrl: $BANK_URL,
+ exchangeBaseUrl: $EXCHANGE_URL,
+ merchantBaseUrl: $MERCHANT_URL,
+ }' \
+ --arg MERCHANT_URL "$MERCHANT_URL" \
+ --arg EXCHANGE_URL "$EXCHANGE_URL" \
+ --arg BANK_URL "$BANK_URL"
+ )" &> taler-wallet-cli.log
+echo " DONE"
-echo "Shutting down services"
-kill `jobs -p`
-wait
+taler-wallet-cli --wallet-db="$WALLET_DB" run-until-done
# Dump database
-echo "Dumping database"
-pg_dump -O $TARGET_DB | sed -e '/AS integer/d' > ${BASEDB}.sql
-
-echo $MASTER_PUB > ${BASEDB}.mpub
+mkdir -p "$(dirname "$BASEDB")"
-WIRE_FEE_DIR=`taler-config -c $CONF -f -s exchangedb -o WIREFEE_BASE_DIR`
-cp $WIRE_FEE_DIR/x-taler-bank.fee ${BASEDB}.fees
-date +%s > ${BASEDB}.age
+echo "Dumping database ${BASEDB}.sql"
+pg_dump -O "auditor-basedb" | sed -e '/AS integer/d' > "${BASEDB}.sql"
+cp "${CONF}.edited" "${BASEDB}.conf"
+cp "$(taler-config -c "${CONF}.edited" -s exchange-offline -o MASTER_PRIV_FILE -f)" "${BASEDB}.mpriv"
# clean up
-echo "Final clean up"
-dropdb $TARGET_DB
-
-rm -rf $DATA_DIR || true
-rm $CONF
+echo -n "Final clean up ..."
+kill -TERM "$SETUP_PID"
+wait
+unset SETUP_PID
+dropdb "auditor-basedb"
+echo " DONE"
echo "====================================="
-echo " Finished generation of $BASEDB"
+echo "Finished generation of ${BASEDB}.sql"
echo "====================================="
exit 0
diff --git a/src/auditor/generate-kyc-basedb.conf b/src/auditor/generate-kyc-basedb.conf
new file mode 100644
index 000000000..7f4a55cee
--- /dev/null
+++ b/src/auditor/generate-kyc-basedb.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+@INLINE@ generate-auditor-basedb.conf
+
+# FIXME: add options for KYC here! \ No newline at end of file
diff --git a/src/auditor/generate-revoke-basedb.sh b/src/auditor/generate-revoke-basedb.sh
index e687b1ffd..29aa74b27 100755
--- a/src/auditor/generate-revoke-basedb.sh
+++ b/src/auditor/generate-revoke-basedb.sh
@@ -6,183 +6,138 @@
# create/drop databases.
#
set -eu
+# set -x
+. setup.sh
-trap "kill `jobs -p` &> /dev/null || true" ERR
-
-# Exit, with status code "skip" (no 'real' failure)
-function exit_skip() {
- echo $1
- exit 77
-}
-
-# Where do we write the result?
-export BASEDB=${1:-"revoke-basedb"}
-
-# Name of the Postgres database we will use for the script.
-# Will be dropped, do NOT use anything that might be used
-# elsewhere
-export TARGET_DB=taler-auditor-revokedb
-TMP_DIR=`mktemp -d revocation-tmp-XXXXXX`
-export WALLET_DB=wallet-revocation.json
-rm -f $WALLET_DB
-
-# Configuration file will be edited, so we create one
-# from the template.
-export CONF=generate-auditor-basedb-revocation.conf
-cp generate-auditor-basedb-template.conf $CONF
-
-
-echo -n "Testing for taler-bank-manage"
-taler-bank-manage -h >/dev/null </dev/null || exit_skip " MISSING"
-echo " FOUND"
-echo -n "Testing for taler-wallet-cli"
-taler-wallet-cli -v >/dev/null </dev/null || exit_skip " MISSING"
+echo -n "Testing for curl ..."
+curl --help >/dev/null </dev/null || exit_skip " MISSING"
echo " FOUND"
-
-
-# Clean up
-DATA_DIR=`taler-config -f -c $CONF -s PATHS -o TALER_HOME`
-rm -rf $DATA_DIR || true
+CONF="generate-auditor-basedb.conf"
# reset database
-dropdb $TARGET_DB >/dev/null 2>/dev/null || true
-createdb $TARGET_DB || exit_skip "Could not create database $TARGET_DB"
+echo -n "Reset 'auditor-basedb' database ..."
+dropdb "auditor-basedb" >/dev/null 2>/dev/null || true
+createdb "auditor-basedb" || exit_skip "Could not create database '$BASEDB'"
+echo " DONE"
-# obtain key configuration data
-MASTER_PRIV_FILE=`taler-config -f -c $CONF -s EXCHANGE -o MASTER_PRIV_FILE`
-MASTER_PRIV_DIR=`dirname $MASTER_PRIV_FILE`
-mkdir -p $MASTER_PRIV_DIR
-gnunet-ecc -g1 $MASTER_PRIV_FILE > /dev/null
-export MASTER_PUB=`gnunet-ecc -p $MASTER_PRIV_FILE`
-export EXCHANGE_URL=`taler-config -c $CONF -s EXCHANGE -o BASE_URL`
-MERCHANT_PORT=`taler-config -c $CONF -s MERCHANT -o PORT`
-export MERCHANT_URL=http://localhost:${MERCHANT_PORT}/
-BANK_PORT=`taler-config -c $CONF -s BANK -o HTTP_PORT`
-export BANK_URL=http://localhost:${BANK_PORT}/
-export AUDITOR_URL=http://localhost:8083/
-
-# patch configuration
-taler-config -c $CONF -s exchange -o MASTER_PUBLIC_KEY -V $MASTER_PUB
-taler-config -c $CONF -s merchant-exchange-default -o MASTER_KEY -V $MASTER_PUB
-taler-config -c $CONF -s exchangedb-postgres -o CONFIG -V postgres:///$TARGET_DB
-taler-config -c $CONF -s auditordb-postgres -o CONFIG -V postgres:///$TARGET_DB
-taler-config -c $CONF -s merchantdb-postgres -o CONFIG -V postgres:///$TARGET_DB
-taler-config -c $CONF -s bank -o database -V postgres:///$TARGET_DB
-taler-config -c $CONF -s exchange -o KEYDIR -V "${TMP_DIR}/keydir/"
-taler-config -c $CONF -s exchange -o REVOCATION_DIR -V "${TMP_DIR}/revdir/"
-
-# setup exchange
-echo "Setting up exchange"
-taler-exchange-dbinit -c $CONF
-taler-exchange-wire -c $CONF 2> taler-exchange-wire.log
-taler-exchange-keyup -L INFO -c $CONF -o e2a.dat 2> taler-exchange-keyup.log
-
-# setup auditor
-echo "Setting up auditor"
-taler-auditor-dbinit -c $CONF
-taler-auditor-exchange -c $CONF -m $MASTER_PUB -u $EXCHANGE_URL
-taler-auditor-sign -c $CONF -u $AUDITOR_URL -r e2a.dat -o a2e.dat -m $MASTER_PUB
-rm -f e2a.dat
-
-# provide auditor's signature to exchange
-ABD=`taler-config -c $CONF -s EXCHANGEDB -o AUDITOR_BASE_DIR -f`
-mkdir -p $ABD
-mv a2e.dat $ABD
-
-# Launch services
-echo "Launching services"
-taler-bank-manage-testing $CONF postgres:///$TARGET_DB serve-http &> revocation-bank.log &
-taler-exchange-httpd -c $CONF 2> taler-exchange-httpd.log &
-EXCHANGE_PID=$!
-taler-merchant-httpd -c $CONF -L INFO 2> taler-merchant-httpd.log &
-MERCHANT_PID=$!
-taler-exchange-wirewatch -c $CONF 2> taler-exchange-wirewatch.log &
-taler-auditor-httpd -c $CONF 2> taler-auditor-httpd.log &
+# Launch exchange, merchant and bank.
+setup -c "$CONF" \
+ -abemw \
+ -d "iban"
-# Wait for all bank to be available (usually the slowest)
-for n in `seq 1 50`
-do
- echo -n "."
- sleep 0.2
- OK=0
- # bank
- wget http://localhost:8082/ -o /dev/null -O /dev/null >/dev/null || continue
- OK=1
- break
-done
-# Wait for all other services to be available
-for n in `seq 1 50`
-do
- echo -n "."
- sleep 0.1
- OK=0
- # exchange
- wget http://localhost:8081/ -o /dev/null -O /dev/null >/dev/null || continue
- # merchant
- wget http://localhost:9966/ -o /dev/null -O /dev/null >/dev/null || continue
- # Auditor
- wget http://localhost:8083/ -o /dev/null -O /dev/null >/dev/null || continue
- OK=1
- break
-done
+# obtain key configuration data
+EXCHANGE_URL=$(taler-config -c "$CONF" -s EXCHANGE -o BASE_URL)
+MERCHANT_PORT=$(taler-config -c "$CONF" -s MERCHANT -o PORT)
+MERCHANT_URL="http://localhost:${MERCHANT_PORT}/"
+BANK_PORT=$(taler-config -c "$CONF" -s BANK -o HTTP_PORT)
+BANK_URL="http://localhost:${BANK_PORT}"
-if [ 1 != $OK ]
-then
- kill `jobs -p`
- wait
- exit_skip "Failed to launch services"
-fi
+# Setup merchant
+echo -n "Setting up merchant ..."
+curl -H "Content-Type: application/json" -X POST -d '{"auth": {"method": "external"}, "accounts":[{"payto_uri":"payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"}],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000},"use_stefan":true}' "${MERCHANT_URL}management/instances"
echo " DONE"
+
# run wallet CLI
echo "Running wallet"
-taler-wallet-cli --wallet-db=$WALLET_DB --no-throttle \
- testing withdraw \
- -e $EXCHANGE_URL \
- -b $BANK_URL \
- -a TESTKUDOS:8
-
-export coins=$(taler-wallet-cli --wallet-db=$WALLET_DB advanced dump-coins)
+export WALLET_DB="wallet.wdb"
+rm -f "$WALLET_DB"
+
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ api \
+ --expect-success 'withdrawTestBalance' \
+ "$(jq -n '
+ {
+ amount: "TESTKUDOS:8",
+ corebankApiBaseUrl: $BANK_URL,
+ exchangeBaseUrl: $EXCHANGE_URL,
+ }' \
+ --arg BANK_URL "$BANK_URL" \
+ --arg EXCHANGE_URL "$EXCHANGE_URL"
+ )" &> taler-wallet-cli-withdraw.log
+
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ run-until-done \
+ &> taler-wallet-cli-withdraw-finish.log
+
+export COINS=$(taler-wallet-cli --wallet-db="$WALLET_DB" advanced dump-coins)
+
+echo -n "COINS are:"
+echo "$COINS"
# Find coin we want to revoke
-export rc=$(echo "$coins" | jq -r '[.coins[] | select((.denom_value == "TESTKUDOS:2"))][0] | .coin_pub')
+export rc=$(echo "$COINS" | jq -r '[.coins[] | select((.denom_value == "TESTKUDOS:2"))][0] | .coin_pub')
# Find the denom
-export rd=$(echo "$coins" | jq -r '[.coins[] | select((.denom_value == "TESTKUDOS:2"))][0] | .denom_pub_hash')
-echo "Revoking denomination ${rd} (to affect coin ${rc})"
+export rd=$(echo "$COINS" | jq -r '[.coins[] | select((.denom_value == "TESTKUDOS:2"))][0] | .denom_pub_hash')
+echo -n "Revoking denomination ${rd} (to affect coin ${rc}) ..."
# Find all other coins, which will be suspended
-export susp=$(echo "$coins" | jq --arg rc "$rc" '[.coins[] | select(.coin_pub != $rc) | .coin_pub]')
+export susp=$(echo "$COINS" | jq --arg rc "$rc" '[.coins[] | select(.coin_pub != $rc) | .coin_pub]')
# Do the revocation
-taler-exchange-keyup -o e2a2.dat -c $CONF -r $rd
-taler-auditor-sign -c $CONF -u $AUDITOR_URL -r e2a2.dat -o a2e2.dat -m $MASTER_PUB
-rm e2a2.dat
-mv a2e2.dat $ABD
-
-# Restart the exchange...
-kill -SIGUSR1 $EXCHANGE_PID
-sleep 1 # Give exchange time to re-scan data
-echo "Restarted the exchange post revocation"
+taler-exchange-offline \
+ -c $CONF \
+ revoke-denomination "${rd}" \
+ upload \
+ &> taler-exchange-offline-revoke.log
+echo "DONE"
+
+echo -n "Signing replacement keys ..."
+sleep 1 # Give exchange time to create replacmenent key
+
+# Re-sign replacement keys
+taler-auditor-offline \
+ -c $CONF \
+ download \
+ sign \
+ upload \
+ &> taler-auditor-offline-reinit.log
+echo " DONE"
# Now we suspend the other coins, so later we will pay with the recouped coin
-taler-wallet-cli --wallet-db=$WALLET_DB advanced suspend-coins "$susp"
+taler-wallet-cli \
+ --wallet-db="$WALLET_DB" \
+ advanced \
+ suspend-coins "$susp"
# Update exchange /keys so recoup gets scheduled
-taler-wallet-cli --wallet-db=$WALLET_DB exchanges update \
- -f $EXCHANGE_URL
+taler-wallet-cli \
+ --wallet-db="$WALLET_DB" \
+ exchanges \
+ update \
+ -f "$EXCHANGE_URL"
# Block until scheduled operations are done
-taler-wallet-cli --wallet-db=$WALLET_DB run-until-done
+taler-wallet-cli \
+ --wallet-db="$WALLET_DB"\
+ run-until-done
-# Now we buy something, only the coins resulting from recouped will be
+# Now we buy something, only the coins resulting from recoup will be
# used, as other ones are suspended
-taler-wallet-cli --wallet-db=$WALLET_DB testing test-pay \
- -m $MERCHANT_URL -k sandbox \
- -a "TESTKUDOS:1" -s "foo"
-taler-wallet-cli --wallet-db=$WALLET_DB run-until-done
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ api \
+ 'testPay' \
+ "$(jq -n '
+ {
+ amount: "TESTKUDOS:1",
+ merchantBaseUrl: $MERCHANT_URL,
+ summary: "foo",
+ }' \
+ --arg MERCHANT_URL "$MERCHANT_URL"
+ )"
+
+taler-wallet-cli \
+ --wallet-db="$WALLET_DB" \
+ run-until-done
echo "Purchase with recoup'ed coin (via reserve) done"
@@ -195,15 +150,21 @@ echo "Will refresh coin ${rrc} of denomination ${zombie_denom}"
# Find all other coins, which will be suspended
export susp=$(echo "$coins" | jq --arg rrc "$rrc" '[.coins[] | select(.coin_pub != $rrc) | .coin_pub]')
-export rrc
-export zombie_denom
-
# Travel into the future! (must match DURATION_WITHDRAW option)
export TIMETRAVEL="--timetravel=604800000000"
echo "Launching exchange 1 week in the future"
kill -TERM $EXCHANGE_PID
-taler-exchange-httpd $TIMETRAVEL -c $CONF 2> taler-exchange-httpd.log &
+kill -TERM $RSA_DENOM_HELPER_PID
+kill -TERM $CS_DENOM_HELPER_PID
+kill -TERM $SIGNKEY_HELPER_PID
+taler-exchange-secmod-eddsa $TIMETRAVEL -c $CONF 2> ${MY_TMP_DIR}/taler-exchange-secmod-eddsa.log &
+SIGNKEY_HELPER_PID=$!
+taler-exchange-secmod-rsa $TIMETRAVEL -c $CONF 2> ${MY_TMP_DIR}/taler-exchange-secmod-rsa.log &
+RSA_DENOM_HELPER_PID=$!
+taler-exchange-secmod-cs $TIMETRAVEL -c $CONF 2> ${MY_TMP_DIR}/taler-exchange-secmod-cs.log &
+CS_DENOM_HELPER_PID=$!
+taler-exchange-httpd $TIMETRAVEL -c $CONF 2> ${MY_TMP_DIR}/taler-exchange-httpd.log &
export EXCHANGE_PID=$!
# Wait for exchange to be available
@@ -219,8 +180,15 @@ do
done
echo "Refreshing coin $rrc"
-taler-wallet-cli $TIMETRAVEL --wallet-db=$WALLET_DB advanced force-refresh "$rrc"
-taler-wallet-cli $TIMETRAVEL --wallet-db=$WALLET_DB run-until-done
+taler-wallet-cli \
+ "$TIMETRAVEL" \
+ --wallet-db="$WALLET_DB" \
+ advanced force-refresh \
+ "$rrc"
+taler-wallet-cli \
+ "$TIMETRAVEL" \
+ --wallet-db="$WALLET_DB" \
+ run-until-done
# Update our list of the coins
export coins=$(taler-wallet-cli $TIMETRAVEL --wallet-db=$WALLET_DB advanced dump-coins)
@@ -243,30 +211,49 @@ export susp=$(echo "$coins" | jq --arg freshc "$freshc" '[.coins[] | select(.coi
# Do the revocation of freshc
echo "Revoking ${fresh_denom} (to affect coin ${freshc})"
-taler-exchange-keyup -c $CONF -o e2a3.dat -r $fresh_denom
-taler-auditor-sign -c $CONF -u $AUDITOR_URL -r e2a3.dat -o a2e3.dat -m $MASTER_PUB
-rm e2a3.dat
-mv a2e3.dat $ABD
+taler-exchange-offline \
+ -c "$CONF" \
+ revoke-denomination \
+ "${fresh_denom}" \
+ upload &> taler-exchange-offline-revoke-2.log
-# Restart the exchange...
-kill -SIGUSR1 $EXCHANGE_PID
-sleep 1 # give exchange time to re-scan data
+sleep 1 # Give exchange time to create replacmenent key
+# Re-sign replacement keys
+taler-auditor-offline \
+ -c "$CONF" \
+ download \
+ sign \
+ upload &> taler-auditor-offline.log
# Now we suspend the other coins, so later we will pay with the recouped coin
-taler-wallet-cli $TIMETRAVEL --wallet-db=$WALLET_DB advanced suspend-coins "$susp"
+taler-wallet-cli \
+ "$TIMETRAVEL" \
+ --wallet-db="$WALLET_DB" \
+ advanced \
+ suspend-coins "$susp"
# Update exchange /keys so recoup gets scheduled
-taler-wallet-cli $TIMETRAVEL --wallet-db=$WALLET_DB exchanges update \
- -f $EXCHANGE_URL
+taler-wallet-cli \
+ "$TIMETRAVEL"\
+ --wallet-db="$WALLET_DB" \
+ exchanges update \
+ -f "$EXCHANGE_URL"
# Block until scheduled operations are done
-taler-wallet-cli $TIMETRAVEL --wallet-db=$WALLET_DB run-until-done
+taler-wallet-cli \
+ "$TIMETRAVEL" \
+ --wallet-db="$WALLET_DB" \
+ run-until-done
echo "Restarting merchant (so new keys are known)"
kill -TERM $MERCHANT_PID
-taler-merchant-httpd -c $CONF -L INFO 2> taler-merchant-httpd.log &
+taler-merchant-httpd \
+ -c "$CONF" \
+ -L INFO \
+ 2> ${MY_TMP_DIR}/taler-merchant-httpd.log &
MERCHANT_PID=$!
+
# Wait for merchant to be again available
for n in `seq 1 50`
do
@@ -281,38 +268,44 @@ done
# Now we buy something, only the coins resulting from recoup+refresh will be
# used, as other ones are suspended
-taler-wallet-cli $TIMETRAVEL --wallet-db=$WALLET_DB testing test-pay \
- -m $MERCHANT_URL -k sandbox \
- -a "TESTKUDOS:0.02" -s "bar"
-taler-wallet-cli $TIMETRAVEL --wallet-db=$WALLET_DB run-until-done
+taler-wallet-cli $TIMETRAVEL --no-throttle --wallet-db=$WALLET_DB api 'testPay' \
+ "$(jq -n '
+ {
+ amount: "TESTKUDOS:0.02",
+ merchantBaseUrl: $MERCHANT_URL,
+ summary: "bar",
+ }' \
+ --arg MERCHANT_URL $MERCHANT_URL
+ )"
+taler-wallet-cli \
+ "$TIMETRAVEL" \
+ --wallet-db="$WALLET_DB" \
+ run-until-done
echo "Bought something with refresh-recouped coin"
echo "Shutting down services"
-kill `jobs -p`
-wait
-
+exit_cleanup
-# Dump database
-echo "Dumping database"
-pg_dump -O $TARGET_DB | sed -e '/AS integer/d' > ${BASEDB}.sql
-echo $MASTER_PUB > ${BASEDB}.mpub
+# Where do we write the result?
+export BASEDB=${1:-"revoke-basedb"}
-WIRE_FEE_DIR=`taler-config -c $CONF -f -s exchangedb -o WIREFEE_BASE_DIR`
-cp $WIRE_FEE_DIR/x-taler-bank.fee ${BASEDB}.fees
-date +%s > ${BASEDB}.age
+# Dump database
+echo "Dumping database ${BASEDB}.sql"
+pg_dump -O "auditor-basedb" | sed -e '/AS integer/d' > "${BASEDB}.sql"
# clean up
-echo "Final clean up (disabled)"
-dropdb $TARGET_DB
-rm -r $DATA_DIR || true
-rm $CONF
-rm -r $TMP_DIR
+echo -n "Final clean up ..."
+kill -TERM "$SETUP_PID"
+wait
+unset SETUP_PID
+dropdb "auditor-basedb"
+echo " DONE"
echo "====================================="
-echo " Finished revocation DB generation "
+echo "Finished generation of ${BASEDB}.sql"
echo "====================================="
exit 0
diff --git a/src/auditor/generate_auditordb_home/.local/share/taler/exchange-offline/master.priv b/src/auditor/generate_auditordb_home/.local/share/taler/exchange-offline/master.priv
new file mode 100644
index 000000000..85195dd8f
--- /dev/null
+++ b/src/auditor/generate_auditordb_home/.local/share/taler/exchange-offline/master.priv
@@ -0,0 +1 @@
+%I7qYÿ®ÜX˜2@–šò%'1†”ÂOàÔæJ³Ô¦‘ \ No newline at end of file
diff --git a/src/auditor/report-lib.c b/src/auditor/report-lib.c
index 6baf6e8b5..d0e1325ea 100644
--- a/src/auditor/report-lib.c
+++ b/src/auditor/report-lib.c
@@ -42,24 +42,29 @@ struct TALER_Amount TALER_ARL_currency_round_unit;
const struct GNUNET_CONFIGURATION_Handle *TALER_ARL_cfg;
/**
- * Our session with the #TALER_ARL_edb.
+ * Handle to access the auditor's database.
*/
-struct TALER_EXCHANGEDB_Session *TALER_ARL_esession;
+struct TALER_AUDITORDB_Plugin *TALER_ARL_adb;
/**
- * Handle to access the auditor's database.
+ * Master public key of the exchange to audit.
*/
-struct TALER_AUDITORDB_Plugin *TALER_ARL_adb;
+struct TALER_MasterPublicKeyP TALER_ARL_master_pub;
/**
- * Our session with the #TALER_ARL_adb.
+ * Public key of the auditor.
*/
-struct TALER_AUDITORDB_Session *TALER_ARL_asession;
+struct TALER_AuditorPublicKeyP TALER_ARL_auditor_pub;
/**
- * Master public key of the exchange to audit.
+ * REST API endpoint of the auditor.
*/
-struct TALER_MasterPublicKeyP TALER_ARL_master_pub;
+char *TALER_ARL_auditor_url;
+
+/**
+ * REST API endpoint of the exchange.
+ */
+char *TALER_ARL_exchange_url;
/**
* At what time did the auditor process start?
@@ -68,46 +73,34 @@ struct GNUNET_TIME_Absolute start_time;
/**
* Results about denominations, cached per-transaction, maps denomination pub hashes
- * to `struct TALER_DenominationKeyValidityPS`.
+ * to `const struct TALER_EXCHANGEDB_DenominationKeyInformation`.
*/
static struct GNUNET_CONTAINER_MultiHashMap *denominations;
-
/**
- * Convert absolute time to human-readable JSON string.
- *
- * @param at time to convert
- * @return human-readable string representing the time
+ * Flag that is raised to 'true' if the user
+ * presses CTRL-C to abort the audit.
*/
-json_t *
-TALER_ARL_json_from_time_abs_nbo (struct GNUNET_TIME_AbsoluteNBO at)
-{
- return json_string
- (GNUNET_STRINGS_absolute_time_to_string
- (GNUNET_TIME_absolute_ntoh (at)));
-}
+static volatile bool abort_flag;
+/**
+ * Context for the SIG-INT (ctrl-C) handler.
+ */
+static struct GNUNET_SIGNAL_Context *sig_int;
/**
- * Convert absolute time to human-readable JSON string.
- *
- * @param at time to convert
- * @return human-readable string representing the time
+ * Context for the SIGTERM handler.
*/
-json_t *
-TALER_ARL_json_from_time_abs (struct GNUNET_TIME_Absolute at)
+static struct GNUNET_SIGNAL_Context *sig_term;
+
+
+bool
+TALER_ARL_do_abort (void)
{
- return json_string
- (GNUNET_STRINGS_absolute_time_to_string (at));
+ return abort_flag;
}
-/**
- * Add @a object to the report @a array. Fail hard if this fails.
- *
- * @param array report array to append @a object to
- * @param object object to append, should be check that it is not NULL
- */
void
TALER_ARL_report (json_t *array,
json_t *object)
@@ -120,73 +113,61 @@ TALER_ARL_report (json_t *array,
/**
- * Function called with the results of select_denomination_info()
+ * Function called with the results of iterate_denomination_info(),
+ * or directly (!). Used to check and add the respective denomination
+ * to our hash table.
*
* @param cls closure, NULL
+ * @param denom_pub public key, sometimes NULL (!)
* @param issue issuing information with value, fees and other info about the denomination.
- * @return #GNUNET_OK (to continue)
*/
-static int
-add_denomination (void *cls,
- const struct TALER_DenominationKeyValidityPS *issue)
+static void
+add_denomination (
+ void *cls,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
{
(void) cls;
+ (void) denom_pub;
if (NULL !=
GNUNET_CONTAINER_multihashmap_get (denominations,
- &issue->denom_hash))
- return GNUNET_OK; /* value already known */
+ &issue->denom_hash.hash))
+ return; /* value already known */
#if GNUNET_EXTRA_LOGGING >= 1
{
- struct TALER_Amount value;
-
- TALER_amount_ntoh (&value,
- &issue->value);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Tracking denomination `%s' (%s)\n",
- GNUNET_h2s (&issue->denom_hash),
- TALER_amount2s (&value));
- TALER_amount_ntoh (&value,
- &issue->fee_withdraw);
+ GNUNET_h2s (&issue->denom_hash.hash),
+ TALER_amount2s (&issue->value));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Withdraw fee is %s\n",
- TALER_amount2s (&value));
+ TALER_amount2s (&issue->fees.withdraw));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Start time is %s\n",
- GNUNET_STRINGS_absolute_time_to_string
- (GNUNET_TIME_absolute_ntoh (issue->start)));
+ GNUNET_TIME_timestamp2s (issue->start));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Expire deposit time is %s\n",
- GNUNET_STRINGS_absolute_time_to_string
- (GNUNET_TIME_absolute_ntoh (issue->expire_deposit)));
+ GNUNET_TIME_timestamp2s (issue->expire_deposit));
}
#endif
{
- struct TALER_DenominationKeyValidityPS *i;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation *i;
- i = GNUNET_new (struct TALER_DenominationKeyValidityPS);
+ i = GNUNET_new (struct TALER_EXCHANGEDB_DenominationKeyInformation);
*i = *issue;
GNUNET_assert (GNUNET_OK ==
GNUNET_CONTAINER_multihashmap_put (denominations,
- &issue->denom_hash,
+ &issue->denom_hash.hash,
i,
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
}
- return GNUNET_OK;
}
-/**
- * Obtain information about a @a denom_pub.
- *
- * @param dh hash of the denomination public key to look up
- * @param[out] issue set to detailed information about @a denom_pub, NULL if not found, must
- * NOT be freed by caller
- * @return transaction status code
- */
enum GNUNET_DB_QueryStatus
-TALER_ARL_get_denomination_info_by_hash (const struct GNUNET_HashCode *dh,
- const struct
- TALER_DenominationKeyValidityPS **issue)
+TALER_ARL_get_denomination_info_by_hash (
+ const struct TALER_DenominationHashP *dh,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation **issue)
{
enum GNUNET_DB_QueryStatus qs;
@@ -194,22 +175,21 @@ TALER_ARL_get_denomination_info_by_hash (const struct GNUNET_HashCode *dh,
{
denominations = GNUNET_CONTAINER_multihashmap_create (256,
GNUNET_NO);
- qs = TALER_ARL_adb->select_denomination_info (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &add_denomination,
- NULL);
+ qs = TALER_ARL_edb->iterate_denomination_info (TALER_ARL_edb->cls,
+ &add_denomination,
+ NULL);
if (0 > qs)
{
+ GNUNET_break (0);
*issue = NULL;
return qs;
}
}
{
- const struct TALER_DenominationKeyValidityPS *i;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *i;
i = GNUNET_CONTAINER_multihashmap_get (denominations,
- dh);
+ &dh->hash);
if (NULL != i)
{
/* cache hit */
@@ -218,24 +198,30 @@ TALER_ARL_get_denomination_info_by_hash (const struct GNUNET_HashCode *dh,
}
}
/* maybe database changed since we last iterated, give it one more shot */
- qs = TALER_ARL_adb->select_denomination_info (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &add_denomination,
- NULL);
- if (qs <= 0)
- {
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Denomination %s not found\n",
- TALER_B2S (dh));
- return qs;
+ {
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
+
+ qs = TALER_ARL_edb->get_denomination_info (TALER_ARL_edb->cls,
+ dh,
+ &issue);
+ if (qs <= 0)
+ {
+ GNUNET_break (qs >= 0);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Denomination %s not found\n",
+ TALER_B2S (dh));
+ return qs;
+ }
+ add_denomination (NULL,
+ NULL,
+ &issue);
}
{
- const struct TALER_DenominationKeyValidityPS *i;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *i;
i = GNUNET_CONTAINER_multihashmap_get (denominations,
- dh);
+ &dh->hash);
if (NULL != i)
{
/* cache hit */
@@ -251,27 +237,18 @@ TALER_ARL_get_denomination_info_by_hash (const struct GNUNET_HashCode *dh,
}
-/**
- * Obtain information about a @a denom_pub.
- *
- * @param denom_pub key to look up
- * @param[out] issue set to detailed information about @a denom_pub, NULL if not found, must
- * NOT be freed by caller
- * @param[out] dh set to the hash of @a denom_pub, may be NULL
- * @return transaction status code
- */
enum GNUNET_DB_QueryStatus
TALER_ARL_get_denomination_info (
const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_DenominationKeyValidityPS **issue,
- struct GNUNET_HashCode *dh)
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation **issue,
+ struct TALER_DenominationHashP *dh)
{
- struct GNUNET_HashCode hc;
+ struct TALER_DenominationHashP hc;
if (NULL == dh)
dh = &hc;
- GNUNET_CRYPTO_rsa_public_key_hash (denom_pub->rsa_public_key,
- dh);
+ TALER_denom_pub_hash (denom_pub,
+ dh);
return TALER_ARL_get_denomination_info_by_hash (dh,
issue);
}
@@ -283,53 +260,51 @@ TALER_ARL_get_denomination_info (
*
* @param analysis analysis to run
* @param analysis_cls closure for @a analysis
- * @return #GNUNET_OK if @a analysis succeessfully committed,
+ * @return #GNUNET_OK if @a analysis successfully committed,
* #GNUNET_NO if we had an error on commit (retry may help)
* #GNUNET_SYSERR on hard errors
*/
-static int
+static enum GNUNET_GenericReturnValue
transact (TALER_ARL_Analysis analysis,
void *analysis_cls)
{
int ret;
enum GNUNET_DB_QueryStatus qs;
- ret = TALER_ARL_adb->start (TALER_ARL_adb->cls,
- TALER_ARL_asession);
+ ret = TALER_ARL_adb->start (TALER_ARL_adb->cls);
if (GNUNET_OK != ret)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
- TALER_ARL_edb->preflight (TALER_ARL_edb->cls,
- TALER_ARL_esession);
+ if (GNUNET_OK !=
+ TALER_ARL_edb->preflight (TALER_ARL_edb->cls))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
ret = TALER_ARL_edb->start (TALER_ARL_edb->cls,
- TALER_ARL_esession,
"auditor");
if (GNUNET_OK != ret)
{
GNUNET_break (0);
- TALER_ARL_edb->rollback (TALER_ARL_edb->cls,
- TALER_ARL_esession);
+ TALER_ARL_edb->rollback (TALER_ARL_edb->cls);
return GNUNET_SYSERR;
}
qs = analysis (analysis_cls);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
- qs = TALER_ARL_edb->commit (TALER_ARL_edb->cls,
- TALER_ARL_esession);
+ qs = TALER_ARL_edb->commit (TALER_ARL_edb->cls);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Exchange DB commit failed, rolling back transaction\n");
- TALER_ARL_adb->rollback (TALER_ARL_adb->cls,
- TALER_ARL_asession);
+ TALER_ARL_adb->rollback (TALER_ARL_adb->cls);
}
else
{
- qs = TALER_ARL_adb->commit (TALER_ARL_adb->cls,
- TALER_ARL_asession);
+ qs = TALER_ARL_adb->commit (TALER_ARL_adb->cls);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
@@ -342,10 +317,8 @@ transact (TALER_ARL_Analysis analysis,
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Processing failed (or no changes), rolling back transaction\n");
- TALER_ARL_adb->rollback (TALER_ARL_adb->cls,
- TALER_ARL_asession);
- TALER_ARL_edb->rollback (TALER_ARL_edb->cls,
- TALER_ARL_esession);
+ TALER_ARL_adb->rollback (TALER_ARL_adb->cls);
+ TALER_ARL_edb->rollback (TALER_ARL_edb->cls);
}
switch (qs)
{
@@ -362,26 +335,19 @@ transact (TALER_ARL_Analysis analysis,
}
-/**
- * Initialize DB sessions and run the analysis.
- *
- * @param ana analysis to run
- * @param ana_cls closure for @a ana
- * @return #GNUNET_OK on success
- */
-int
+enum GNUNET_GenericReturnValue
TALER_ARL_setup_sessions_and_run (TALER_ARL_Analysis ana,
void *ana_cls)
{
- TALER_ARL_esession = TALER_ARL_edb->get_session (TALER_ARL_edb->cls);
- if (NULL == TALER_ARL_esession)
+ if (GNUNET_SYSERR ==
+ TALER_ARL_edb->preflight (TALER_ARL_edb->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to initialize exchange session.\n");
+ "Failed to initialize exchange connection.\n");
return GNUNET_SYSERR;
}
- TALER_ARL_asession = TALER_ARL_adb->get_session (TALER_ARL_adb->cls);
- if (NULL == TALER_ARL_asession)
+ if (GNUNET_SYSERR ==
+ TALER_ARL_adb->preflight (TALER_ARL_adb->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to initialize auditor session.\n");
@@ -395,49 +361,199 @@ TALER_ARL_setup_sessions_and_run (TALER_ARL_Analysis ana,
}
-/**
- * Test if the given @a mpub matches the #TALER_ARL_master_pub.
- * If so, set "found" to GNUNET_YES.
- *
- * @param cls a `int *` pointing to "found"
- * @param mpub exchange master public key to compare
- * @param exchange_url URL of the exchange (ignored)
- */
-static void
-test_master_present (void *cls,
- const struct TALER_MasterPublicKeyP *mpub,
- const char *exchange_url)
+void
+TALER_ARL_amount_add_ (struct TALER_Amount *sum,
+ const struct TALER_Amount *a1,
+ const struct TALER_Amount *a2,
+ const char *filename,
+ const char *functionname,
+ unsigned int line)
+{
+ enum TALER_AmountArithmeticResult aar;
+ const char *msg;
+ char *a2s;
+
+ aar = TALER_amount_add (sum,
+ a1,
+ a2);
+ if (aar >= 0)
+ return;
+ switch (aar)
+ {
+ case TALER_AAR_INVALID_RESULT_OVERFLOW:
+ msg =
+ "arithmetic overflow in amount addition (likely the database is corrupt, see manual)";
+ break;
+ case TALER_AAR_INVALID_NORMALIZATION_FAILED:
+ msg =
+ "normalization failed in amount addition (likely the database is corrupt, see manual)";
+ break;
+ case TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE:
+ msg =
+ "incompatible currencies in amount addition (likely bad configuration and auditor code missing a sanity check, see manual)";
+ break;
+ default:
+ GNUNET_assert (0); /* should be impossible */
+ }
+ a2s = TALER_amount_to_string (a2);
+ fprintf (stderr,
+ "Aborting audit due to fatal error in function %s at %s:%d trying to add %s to %s: %s\n",
+ functionname,
+ filename,
+ line,
+ TALER_amount2s (a1),
+ a2s,
+ msg);
+ GNUNET_free (a2s);
+ exit (42);
+}
+
+
+void
+TALER_ARL_amount_subtract_ (struct TALER_Amount *diff,
+ const struct TALER_Amount *a1,
+ const struct TALER_Amount *a2,
+ const char *filename,
+ const char *functionname,
+ unsigned int line)
{
- int *found = cls;
+ enum TALER_AmountArithmeticResult aar;
+ const char *msg;
+ char *a2s;
+
+ aar = TALER_amount_subtract (diff,
+ a1,
+ a2);
+ if (aar >= 0)
+ return;
+ switch (aar)
+ {
+ case TALER_AAR_INVALID_NEGATIVE_RESULT:
+ msg =
+ "negative result in amount subtraction (likely the database is corrupt, see manual)";
+ break;
+ case TALER_AAR_INVALID_NORMALIZATION_FAILED:
+ msg =
+ "normalization failed in amount subtraction (likely the database is corrupt, see manual)";
+ break;
+ case TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE:
+ msg =
+ "currencies incompatible in amount subtraction (likely bad configuration and auditor code missing a sanity check, see manual)";
+ break;
+ default:
+ GNUNET_assert (0); /* should be impossible */
+ }
+ a2s = TALER_amount_to_string (a2);
+ fprintf (stderr,
+ "Aborting audit due to fatal error in function %s at %s:%d trying to subtract %s from %s: %s\n",
+ functionname,
+ filename,
+ line,
+ a2s,
+ TALER_amount2s (a1),
+ msg);
+ GNUNET_free (a2s);
+ exit (42);
+}
+
- (void) exchange_url;
- if (0 == GNUNET_memcmp (mpub,
- &TALER_ARL_master_pub))
- *found = GNUNET_YES;
+enum TALER_ARL_SubtractionResult
+TALER_ARL_amount_subtract_neg_ (struct TALER_Amount *diff,
+ const struct TALER_Amount *a1,
+ const struct TALER_Amount *a2,
+ const char *filename,
+ const char *functionname,
+ unsigned int line)
+{
+ enum TALER_AmountArithmeticResult aar;
+ const char *msg;
+ char *a2s;
+
+ aar = TALER_amount_subtract (diff,
+ a1,
+ a2);
+ switch (aar)
+ {
+ case TALER_AAR_RESULT_POSITIVE:
+ return TALER_ARL_SR_POSITIVE;
+ case TALER_AAR_RESULT_ZERO:
+ return TALER_ARL_SR_ZERO;
+ case TALER_AAR_INVALID_NEGATIVE_RESULT:
+ return TALER_ARL_SR_INVALID_NEGATIVE;
+ case TALER_AAR_INVALID_NORMALIZATION_FAILED:
+ msg =
+ "normalization failed in amount subtraction (likely the database is corrupt, see manual)";
+ break;
+ case TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE:
+ msg =
+ "currencies incompatible in amount subtraction (likely bad configuration and auditor code missing a sanity check, see manual)";
+ break;
+ default:
+ GNUNET_assert (0); /* should be impossible */
+ }
+ a2s = TALER_amount_to_string (a2);
+ fprintf (stderr,
+ "Aborting audit due to fatal error in function %s at %s:%d trying to subtract %s from %s: %s\n",
+ functionname,
+ filename,
+ line,
+ a2s,
+ TALER_amount2s (a1),
+ msg);
+ GNUNET_free (a2s);
+ exit (42);
}
/**
- * Setup global variables based on configuration.
- *
- * @param c configuration to use
- * @return #GNUNET_OK on success
+ * Signal handler called for signals that should cause us to shutdown.
*/
-int
+static void
+handle_sigint (void)
+{
+ abort_flag = true;
+}
+
+
+enum GNUNET_GenericReturnValue
TALER_ARL_init (const struct GNUNET_CONFIGURATION_Handle *c)
{
TALER_ARL_cfg = c;
start_time = GNUNET_TIME_absolute_get ();
- if (0 == GNUNET_is_zero (&TALER_ARL_master_pub))
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (TALER_ARL_cfg,
+ "auditor",
+ "BASE_URL",
+ &TALER_ARL_auditor_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "auditor",
+ "BASE_URL");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (TALER_ARL_cfg,
+ "exchange",
+ "BASE_URL",
+ &TALER_ARL_exchange_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_is_zero (&TALER_ARL_master_pub))
{
/* -m option not given, try configuration */
- char *TALER_ARL_master_public_key_str;
+ char *master_public_key_str;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (TALER_ARL_cfg,
"exchange",
"MASTER_PUBLIC_KEY",
- &TALER_ARL_master_public_key_str))
+ &master_public_key_str))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Pass option -m or set MASTER_PUBLIC_KEY in the configuration!\n");
@@ -448,24 +564,91 @@ TALER_ARL_init (const struct GNUNET_CONFIGURATION_Handle *c)
}
if (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_public_key_from_string (
- TALER_ARL_master_public_key_str,
- strlen (
- TALER_ARL_master_public_key_str),
- &TALER_ARL_master_pub.
- eddsa_pub))
+ master_public_key_str,
+ strlen (master_public_key_str),
+ &TALER_ARL_master_pub.eddsa_pub))
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Malformed master public key given in configuration file.");
- GNUNET_free (TALER_ARL_master_public_key_str);
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "MASTER_PUBLIC_KEY",
+ "invalid key");
+ GNUNET_free (master_public_key_str);
return GNUNET_SYSERR;
}
- GNUNET_free (TALER_ARL_master_public_key_str);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running auditor against exchange master public key `%s'\n",
+ master_public_key_str);
+ GNUNET_free (master_public_key_str);
} /* end of -m not given */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Taler auditor running for exchange master public key %s\n",
TALER_B2S (&TALER_ARL_master_pub));
+ if (GNUNET_is_zero (&TALER_ARL_auditor_pub))
+ {
+ char *auditor_public_key_str;
+
+ if (GNUNET_OK ==
+ GNUNET_CONFIGURATION_get_value_string (c,
+ "auditor",
+ "PUBLIC_KEY",
+ &auditor_public_key_str))
+ {
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_public_key_from_string (
+ auditor_public_key_str,
+ strlen (auditor_public_key_str),
+ &TALER_ARL_auditor_pub.eddsa_pub))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "auditor",
+ "PUBLIC_KEY",
+ "invalid key");
+ GNUNET_free (auditor_public_key_str);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (auditor_public_key_str);
+ }
+ }
+
+ if (GNUNET_is_zero (&TALER_ARL_auditor_pub))
+ {
+ /* public key not configured */
+ /* try loading private key and deriving public key */
+ char *fn;
+
+ if (GNUNET_OK ==
+ GNUNET_CONFIGURATION_get_value_filename (c,
+ "auditor",
+ "AUDITOR_PRIV_FILE",
+ &fn))
+ {
+ struct TALER_AuditorPrivateKeyP auditor_priv;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Loading offline private key from `%s' to get auditor public key\n",
+ fn);
+ if (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_key_from_file (fn,
+ GNUNET_NO, /* do NOT create it! */
+ &auditor_priv.eddsa_priv))
+ {
+ GNUNET_CRYPTO_eddsa_key_get_public (&auditor_priv.eddsa_priv,
+ &TALER_ARL_auditor_pub.eddsa_pub);
+ }
+ GNUNET_free (fn);
+ }
+ }
+
+ if (GNUNET_is_zero (&TALER_ARL_auditor_pub))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
+ "auditor",
+ "PUBLIC_KEY/AUDITOR_PRIV_FILE");
+ return GNUNET_SYSERR;
+ }
+
if (GNUNET_OK !=
TALER_config_get_currency (TALER_ARL_cfg,
&TALER_ARL_currency))
@@ -473,22 +656,43 @@ TALER_ARL_init (const struct GNUNET_CONFIGURATION_Handle *c)
return GNUNET_SYSERR;
}
{
- if (GNUNET_OK !=
- TALER_config_get_amount (TALER_ARL_cfg,
- "taler",
- "CURRENCY_ROUND_UNIT",
- &TALER_ARL_currency_round_unit))
+ if ( (GNUNET_OK !=
+ TALER_config_get_amount (TALER_ARL_cfg,
+ "taler",
+ "CURRENCY_ROUND_UNIT",
+ &TALER_ARL_currency_round_unit)) ||
+ ( (0 != TALER_ARL_currency_round_unit.fraction) &&
+ (0 != TALER_ARL_currency_round_unit.value) ) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid or missing amount in `TALER' under `CURRENCY_ROUND_UNIT'\n");
+ "Need non-zero value in section `TALER' under `CURRENCY_ROUND_UNIT'\n");
return GNUNET_SYSERR;
}
}
+ sig_int = GNUNET_SIGNAL_handler_install (SIGINT,
+ &handle_sigint);
+ if (NULL == sig_int)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "signal");
+ TALER_ARL_done (NULL);
+ return GNUNET_SYSERR;
+ }
+ sig_term = GNUNET_SIGNAL_handler_install (SIGTERM,
+ &handle_sigint);
+ if (NULL == sig_term)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "signal");
+ TALER_ARL_done (NULL);
+ return GNUNET_SYSERR;
+ }
if (NULL ==
(TALER_ARL_edb = TALER_EXCHANGEDB_plugin_load (TALER_ARL_cfg)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to initialize exchange database plugin.\n");
+ TALER_ARL_done (NULL);
return GNUNET_SYSERR;
}
if (NULL ==
@@ -499,46 +703,33 @@ TALER_ARL_init (const struct GNUNET_CONFIGURATION_Handle *c)
TALER_ARL_done (NULL);
return GNUNET_SYSERR;
}
+ if (GNUNET_SYSERR ==
+ TALER_ARL_adb->preflight (TALER_ARL_adb->cls))
{
- struct TALER_AUDITORDB_Session *as;
- int found;
-
- as = TALER_ARL_adb->get_session (TALER_ARL_adb->cls);
- if (NULL == as)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to start session with auditor database.\n");
- TALER_ARL_done (NULL);
- return GNUNET_SYSERR;
- }
- found = GNUNET_NO;
- (void) TALER_ARL_adb->list_exchanges (TALER_ARL_adb->cls,
- as,
- &test_master_present,
- &found);
- if (GNUNET_NO == found)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Exchange's master public key `%s' not known to auditor DB. Did you forget to run `taler-auditor-exchange`?\n",
- GNUNET_p2s (&TALER_ARL_master_pub.eddsa_pub));
- TALER_ARL_done (NULL);
- return GNUNET_SYSERR;
- }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start session with auditor database.\n");
+ TALER_ARL_done (NULL);
+ return GNUNET_SYSERR;
}
return GNUNET_OK;
}
-/**
- * Generate the report and close connectios to the database.
- *
- * @param report the report to output, may be NULL for no report
- */
void
TALER_ARL_done (json_t *report)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Audit complete\n");
+ if (NULL != sig_int)
+ {
+ GNUNET_SIGNAL_handler_uninstall (sig_int);
+ sig_int = NULL;
+ }
+ if (NULL != sig_term)
+ {
+ GNUNET_SIGNAL_handler_uninstall (sig_term);
+ sig_term = NULL;
+ }
if (NULL != TALER_ARL_adb)
{
TALER_AUDITORDB_plugin_unload (TALER_ARL_adb);
@@ -556,6 +747,8 @@ TALER_ARL_done (json_t *report)
JSON_INDENT (2));
json_decref (report);
}
+ GNUNET_free (TALER_ARL_exchange_url);
+ GNUNET_free (TALER_ARL_auditor_url);
}
diff --git a/src/auditor/report-lib.h b/src/auditor/report-lib.h
index 8176e740b..db29abc3a 100644
--- a/src/auditor/report-lib.h
+++ b/src/auditor/report-lib.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2016-2020 Taler Systems SA
+ Copyright (C) 2016-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero Public License as published by the Free Software
@@ -28,6 +28,52 @@
#include "taler_bank_service.h"
#include "taler_signatures.h"
+/**
+ * Macro to use to access progress point value @a name.
+ */
+#define TALER_ARL_USE_PP(name) TAC_ ## name
+
+/**
+ * Macro to use to declare progress point value @a name.
+ */
+#define TALER_ARL_DEF_PP(name) \
+ uint64_t TALER_ARL_USE_PP (name) = 0
+
+/**
+ * Macro to use to GET progress point value @a name from DB.
+ */
+#define TALER_ARL_GET_PP(name) \
+ TALER_S (name), &TALER_ARL_USE_PP (name)
+
+/**
+ * Macro to use to SET progress point value @a name in DB.
+ */
+#define TALER_ARL_SET_PP(name) \
+ TALER_S (name), TALER_ARL_USE_PP (name)
+
+/**
+ * Macro to use to access amount balance @a name.
+ */
+#define TALER_ARL_USE_AB(name) TAC_ ## name
+
+/**
+ * Macro to use to declare amount balance @a name.
+ */
+#define TALER_ARL_DEF_AB(name) \
+ struct TALER_Amount TALER_ARL_USE_AB (name)
+
+/**
+ * Macro to use to GET amount balance @a name from DB.
+ */
+#define TALER_ARL_GET_AB(name) \
+ TALER_S (name), &TALER_ARL_USE_AB (name)
+
+/**
+ * Macro to use to SET amount balance @a name in DB.
+ */
+#define TALER_ARL_SET_AB(name) \
+ TALER_S (name), &TALER_ARL_USE_AB (name)
+
/**
* Command-line option "-r": restart audit from scratch
@@ -55,49 +101,34 @@ extern struct TALER_Amount TALER_ARL_currency_round_unit;
extern const struct GNUNET_CONFIGURATION_Handle *TALER_ARL_cfg;
/**
- * Our session with the #TALER_ARL_edb.
- */
-extern struct TALER_EXCHANGEDB_Session *TALER_ARL_esession;
-
-/**
* Handle to access the auditor's database.
*/
extern struct TALER_AUDITORDB_Plugin *TALER_ARL_adb;
/**
- * Our session with the #TALER_ARL_adb.
- */
-extern struct TALER_AUDITORDB_Session *TALER_ARL_asession;
-
-/**
* Master public key of the exchange to audit.
*/
extern struct TALER_MasterPublicKeyP TALER_ARL_master_pub;
/**
- * At what time did the auditor process start?
+ * Public key of the auditor.
*/
-extern struct GNUNET_TIME_Absolute start_time;
-
+extern struct TALER_AuditorPublicKeyP TALER_ARL_auditor_pub;
/**
- * Convert absolute time to human-readable JSON string.
- *
- * @param at time to convert
- * @return human-readable string representing the time
+ * REST API endpoint of the auditor.
*/
-json_t *
-TALER_ARL_json_from_time_abs_nbo (struct GNUNET_TIME_AbsoluteNBO at);
+extern char *TALER_ARL_auditor_url;
+/**
+ * REST API endpoint of the exchange.
+ */
+extern char *TALER_ARL_exchange_url;
/**
- * Convert absolute time to human-readable JSON string.
- *
- * @param at time to convert
- * @return human-readable string representing the time
+ * At what time did the auditor process start?
*/
-json_t *
-TALER_ARL_json_from_time_abs (struct GNUNET_TIME_Absolute at);
+extern struct GNUNET_TIME_Absolute start_time;
/**
@@ -121,8 +152,8 @@ TALER_ARL_report (json_t *array,
*/
enum GNUNET_DB_QueryStatus
TALER_ARL_get_denomination_info_by_hash (
- const struct GNUNET_HashCode *dh,
- const struct TALER_DenominationKeyValidityPS **issue);
+ const struct TALER_DenominationHashP *dh,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation **issue);
/**
@@ -137,8 +168,8 @@ TALER_ARL_get_denomination_info_by_hash (
enum GNUNET_DB_QueryStatus
TALER_ARL_get_denomination_info (
const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_DenominationKeyValidityPS **issue,
- struct GNUNET_HashCode *dh);
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation **issue,
+ struct TALER_DenominationHashP *dh);
/**
@@ -153,24 +184,172 @@ typedef enum GNUNET_DB_QueryStatus
/**
+ * Perform addition of amounts. If the addition fails, logs
+ * a detailed error and calls exit() to terminate the process (!).
+ *
+ * Do not call this function directly, use #TALER_ARL_amount_add().
+ *
+ * @param[out] sum where to store @a a1 + @a a2, set to "invalid" on overflow
+ * @param a1 first amount to add
+ * @param a2 second amount to add
+ * @param filename where is the addition called
+ * @param functionname name of the function where the addition is called
+ * @param line line number of the addition
+ */
+void
+TALER_ARL_amount_add_ (struct TALER_Amount *sum,
+ const struct TALER_Amount *a1,
+ const struct TALER_Amount *a2,
+ const char *filename,
+ const char *functionname,
+ unsigned int line);
+
+
+/**
+ * Perform addition of amounts. If the addition fails, logs
+ * a detailed error and calls exit() to terminate the process (!).
+ *
+ * @param[out] sum where to store @a a1 + @a a2, set to "invalid" on overflow
+ * @param a1 first amount to add
+ * @param a2 second amount to add
+ */
+#define TALER_ARL_amount_add(sum,a1,a2) \
+ TALER_ARL_amount_add_ (sum, a1, a2, __FILE__, __FUNCTION__, __LINE__)
+
+
+/**
+ * Perform subtraction of amounts where the result "cannot" be negative. If the
+ * subtraction fails, logs a detailed error and calls exit() to terminate the
+ * process (!).
+ *
+ * Do not call this function directly, use #TALER_ARL_amount_subtract().
+ *
+ * @param[out] diff where to store (@a a1 - @a a2)
+ * @param a1 amount to subtract from
+ * @param a2 amount to subtract
+ * @param filename where is the addition called
+ * @param functionname name of the function where the addition is called
+ * @param line line number of the addition
+ */
+void
+TALER_ARL_amount_subtract_ (struct TALER_Amount *diff,
+ const struct TALER_Amount *a1,
+ const struct TALER_Amount *a2,
+ const char *filename,
+ const char *functionname,
+ unsigned int line);
+
+
+/**
+ * Perform subtraction of amounts where the result "cannot" be negative. If
+ * the subtraction fails, logs a detailed error and calls exit() to terminate
+ * the process (!).
+ *
+ * @param[out] diff where to store (@a a1 - @a a2)
+ * @param a1 amount to subtract from
+ * @param a2 amount to subtract
+ */
+#define TALER_ARL_amount_subtract(diff,a1,a2) \
+ TALER_ARL_amount_subtract_ (diff, a1, a2, __FILE__, __FUNCTION__, __LINE__)
+
+
+/**
+ * Possible outcomes of #TALER_ARL_amount_subtract_neg().
+ */
+enum TALER_ARL_SubtractionResult
+{
+ /**
+ * Note that in this case no actual result was computed.
+ */
+ TALER_ARL_SR_INVALID_NEGATIVE = -1,
+
+ /**
+ * The result of the subtraction is exactly zero.
+ */
+ TALER_ARL_SR_ZERO = 0,
+
+ /**
+ * The result of the subtraction is a positive value.
+ */
+ TALER_ARL_SR_POSITIVE = 1
+};
+
+
+/**
+ * Perform subtraction of amounts. Negative results should be signalled by the
+ * return value (leaving @a diff set to 'invalid'). If the subtraction fails
+ * for other reasons (currency mismatch, normalization failure), logs a
+ * detailed error and calls exit() to terminate the process (!).
+ *
+ * Do not call this function directly, use #TALER_ARL_amount_subtract_neg().
+ *
+ * @param[out] diff where to store (@a a1 - @a a2)
+ * @param a1 amount to subtract from
+ * @param a2 amount to subtract
+ * @param filename where is the addition called
+ * @param functionname name of the function where the addition is called
+ * @param line line number of the addition
+ * @return #TALER_ARL_SR_INVALID_NEGATIVE if the result was negative (and @a diff is now invalid),
+ * #TALER_ARL_SR_ZERO if the result was zero,
+ * #TALER_ARL_SR_POSITIVE if the result is positive
+ */
+enum TALER_ARL_SubtractionResult
+TALER_ARL_amount_subtract_neg_ (struct TALER_Amount *diff,
+ const struct TALER_Amount *a1,
+ const struct TALER_Amount *a2,
+ const char *filename,
+ const char *functionname,
+ unsigned int line);
+
+
+/**
+ * Perform subtraction of amounts. Negative results should be signalled by
+ * the return value (leaving @a diff set to 'invalid'). If the subtraction
+ * fails for other reasons (currency mismatch, normalization failure), logs a
+ * detailed error and calls exit() to terminate the process (!).
+ *
+ * @param[out] diff where to store (@a a1 - @a a2)
+ * @param a1 amount to subtract from
+ * @param a2 amount to subtract
+ * @return #TALER_ARL_SR_INVALID_NEGATIVE if the result was negative (and @a diff is now invalid),
+ * #TALER_ARL_SR_ZERO if the result was zero,
+ * #TALER_ARL_SR_POSITIVE if the result is positive
+ */
+#define TALER_ARL_amount_subtract_neg(diff,a1,a2) \
+ TALER_ARL_amount_subtract_neg_ (diff, a1, a2, __FILE__, __FUNCTION__, \
+ __LINE__)
+
+
+/**
* Initialize DB sessions and run the analysis.
*
* @param ana analysis to run
* @param ana_cls closure for @a ana
* @return #GNUNET_OK on success
*/
-int
+enum GNUNET_GenericReturnValue
TALER_ARL_setup_sessions_and_run (TALER_ARL_Analysis ana,
void *ana_cls);
/**
+ * Test if the audit should be aborted because the user
+ * pressed CTRL-C.
+ *
+ * @return false to continue the audit, true to terminate
+ * cleanly as soon as possible
+ */
+bool
+TALER_ARL_do_abort (void);
+
+
+/**
* Setup global variables based on configuration.
*
* @param c configuration to use
* @return #GNUNET_OK on success
*/
-int
+enum GNUNET_GenericReturnValue
TALER_ARL_init (const struct GNUNET_CONFIGURATION_Handle *c);
diff --git a/src/auditor/revoke-basedb.age b/src/auditor/revoke-basedb.age
deleted file mode 100644
index 421485f54..000000000
--- a/src/auditor/revoke-basedb.age
+++ /dev/null
@@ -1 +0,0 @@
-1585247241
diff --git a/src/auditor/test-auditor.conf b/src/auditor/revoke-basedb.conf
index 829300b4d..706f97347 100644
--- a/src/auditor/test-auditor.conf
+++ b/src/auditor/revoke-basedb.conf
@@ -1,34 +1,37 @@
[auditor]
-DB = postgres
+PUBLIC_KEY = CK4P6P5VXR82B1A4C3PY5DCHG8HDZA1HSZR76Z8D6FD57MASFT70
TINY_AMOUNT = TESTKUDOS:0.01
+BASE_URL = http://localhost:8083/
[exchange-account-1]
-WIRE_RESPONSE = ${TALER_DATA_HOME}/exchange/account-1.json
-PAYTO_URI = payto://x-taler-bank/localhost/Exchange
+PAYTO_URI = payto://iban/SANDBOXX/DE717324?receiver-name=Exchange+Company
enable_debit = yes
enable_credit = yes
-WIRE_GATEWAY_URL = "http://localhost:8082/taler-wire-gateway/Exchange/"
+
+[exchange-accountcredentials-1]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/exchange/taler-wire-gateway/"
WIRE_GATEWAY_AUTH_METHOD = basic
-USERNAME = Exchange
+USERNAME = exchange
PASSWORD = x
[exchangedb]
WIREFEE_BASE_DIR = ${PWD}/wirefees/
[auditordb-postgres]
-CONFIG = postgres:///taler-auditor-test
+CONFIG = postgres:///revoke-basedb
[exchangedb-postgres]
-CONFIG = postgres:///taler-auditor-test
+CONFIG = postgres:///revoke-basedb
[taler]
CURRENCY = TESTKUDOS
CURRENCY_ROUND_UNIT = TESTKUDOS:0.01
[bank]
-DATABASE = postgres:///taler-auditor-test
+DATABASE = postgres:///revoke-basedb
MAX_DEBT = TESTKUDOS:50.0
MAX_DEBT_BANK = TESTKUDOS:100000.0
HTTP_PORT = 8082
SUGGESTED_EXCHANGE = http://localhost:8081/
SUGGESTED_EXCHANGE_PAYTO = payto://x-taler-bank/localhost/2
+SERVE = http
diff --git a/src/auditor/revoke-basedb.fees b/src/auditor/revoke-basedb.fees
deleted file mode 100644
index 500c95050..000000000
--- a/src/auditor/revoke-basedb.fees
+++ /dev/null
Binary files differ
diff --git a/src/auditor/revoke-basedb.mpub b/src/auditor/revoke-basedb.mpub
deleted file mode 100644
index 6482de616..000000000
--- a/src/auditor/revoke-basedb.mpub
+++ /dev/null
@@ -1 +0,0 @@
-NG08W20XYEN3M663JQ0THGSRH2QT7Y3390933FVSZE033Q9A9XE0
diff --git a/src/auditor/revoke-basedb.sql b/src/auditor/revoke-basedb.sql
deleted file mode 100644
index d4f9f1e65..000000000
--- a/src/auditor/revoke-basedb.sql
+++ /dev/null
@@ -1,4892 +0,0 @@
---
--- PostgreSQL database dump
---
-
--- Dumped from database version 10.5 (Debian 10.5-1)
--- Dumped by pg_dump version 10.5 (Debian 10.5-1)
-
-SET statement_timeout = 0;
-SET lock_timeout = 0;
-SET idle_in_transaction_session_timeout = 0;
-SET client_encoding = 'UTF8';
-SET standard_conforming_strings = on;
-SELECT pg_catalog.set_config('search_path', '', false);
-SET check_function_bodies = false;
-SET client_min_messages = warning;
-SET row_security = off;
-
---
--- Name: _v; Type: SCHEMA; Schema: -; Owner: -
---
-
-CREATE SCHEMA _v;
-
-
---
--- Name: SCHEMA _v; Type: COMMENT; Schema: -; Owner: -
---
-
-COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.';
-
-
---
--- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: -
---
-
-CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
-
-
---
--- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: -
---
-
-COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
-
-
---
--- Name: assert_patch_is_applied(text); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.assert_patch_is_applied(in_patch_name text) RETURNS text
- LANGUAGE plpgsql
- AS $$
-DECLARE
- t_text TEXT;
-BEGIN
- SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name;
- IF NOT FOUND THEN
- RAISE EXCEPTION 'Patch % is not applied!', in_patch_name;
- END IF;
- RETURN format('Patch %s is applied.', in_patch_name);
-END;
-$$;
-
-
---
--- Name: FUNCTION assert_patch_is_applied(in_patch_name text); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.assert_patch_is_applied(in_patch_name text) IS 'Function that can be used to make sure that patch has been applied.';
-
-
---
--- Name: assert_user_is_not_superuser(); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.assert_user_is_not_superuser() RETURNS text
- LANGUAGE plpgsql
- AS $$
-DECLARE
- v_super bool;
-BEGIN
- SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user;
- IF v_super THEN
- RAISE EXCEPTION 'Current user is superuser - cannot continue.';
- END IF;
- RETURN 'assert_user_is_not_superuser: OK';
-END;
-$$;
-
-
---
--- Name: FUNCTION assert_user_is_not_superuser(); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.assert_user_is_not_superuser() IS 'Function that can be used to make sure that patch is being applied using normal (not superuser) account.';
-
-
---
--- Name: assert_user_is_one_of(text[]); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.assert_user_is_one_of(VARIADIC p_acceptable_users text[]) RETURNS text
- LANGUAGE plpgsql
- AS $$
-DECLARE
-BEGIN
- IF current_user = any( p_acceptable_users ) THEN
- RETURN 'assert_user_is_one_of: OK';
- END IF;
- RAISE EXCEPTION 'User is not one of: % - cannot continue.', p_acceptable_users;
-END;
-$$;
-
-
---
--- Name: FUNCTION assert_user_is_one_of(VARIADIC p_acceptable_users text[]); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.assert_user_is_one_of(VARIADIC p_acceptable_users text[]) IS 'Function that can be used to make sure that patch is being applied by one of defined users.';
-
-
---
--- Name: assert_user_is_superuser(); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.assert_user_is_superuser() RETURNS text
- LANGUAGE plpgsql
- AS $$
-DECLARE
- v_super bool;
-BEGIN
- SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user;
- IF v_super THEN
- RETURN 'assert_user_is_superuser: OK';
- END IF;
- RAISE EXCEPTION 'Current user is not superuser - cannot continue.';
-END;
-$$;
-
-
---
--- Name: FUNCTION assert_user_is_superuser(); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.assert_user_is_superuser() IS 'Function that can be used to make sure that patch is being applied using superuser account.';
-
-
---
--- Name: register_patch(text); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.register_patch(text) RETURNS SETOF integer
- LANGUAGE sql
- AS $_$
- SELECT _v.register_patch( $1, NULL, NULL );
-$_$;
-
-
---
--- Name: FUNCTION register_patch(text); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.register_patch(text) IS 'Wrapper to allow registration of patches without requirements and conflicts.';
-
-
---
--- Name: register_patch(text, text[]); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.register_patch(text, text[]) RETURNS SETOF integer
- LANGUAGE sql
- AS $_$
- SELECT _v.register_patch( $1, $2, NULL );
-$_$;
-
-
---
--- Name: FUNCTION register_patch(text, text[]); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.register_patch(text, text[]) IS 'Wrapper to allow registration of patches without conflicts.';
-
-
---
--- Name: register_patch(text, text[], text[]); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.register_patch(in_patch_name text, in_requirements text[], in_conflicts text[], OUT versioning integer) RETURNS SETOF integer
- LANGUAGE plpgsql
- AS $$
-DECLARE
- t_text TEXT;
- t_text_a TEXT[];
- i INT4;
-BEGIN
- -- Thanks to this we know only one patch will be applied at a time
- LOCK TABLE _v.patches IN EXCLUSIVE MODE;
-
- SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name;
- IF FOUND THEN
- RAISE EXCEPTION 'Patch % is already applied!', in_patch_name;
- END IF;
-
- t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE patch_name = any( in_conflicts ) );
- IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
- RAISE EXCEPTION 'Versioning patches conflict. Conflicting patche(s) installed: %.', array_to_string( t_text_a, ', ' );
- END IF;
-
- IF array_upper( in_requirements, 1 ) IS NOT NULL THEN
- t_text_a := '{}';
- FOR i IN array_lower( in_requirements, 1 ) .. array_upper( in_requirements, 1 ) LOOP
- SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_requirements[i];
- IF NOT FOUND THEN
- t_text_a := t_text_a || in_requirements[i];
- END IF;
- END LOOP;
- IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
- RAISE EXCEPTION 'Missing prerequisite(s): %.', array_to_string( t_text_a, ', ' );
- END IF;
- END IF;
-
- INSERT INTO _v.patches (patch_name, applied_tsz, applied_by, requires, conflicts ) VALUES ( in_patch_name, now(), current_user, coalesce( in_requirements, '{}' ), coalesce( in_conflicts, '{}' ) );
- RETURN;
-END;
-$$;
-
-
---
--- Name: FUNCTION register_patch(in_patch_name text, in_requirements text[], in_conflicts text[], OUT versioning integer); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.register_patch(in_patch_name text, in_requirements text[], in_conflicts text[], OUT versioning integer) IS 'Function to register patches in database. Raises exception if there are conflicts, prerequisites are not installed or the migration has already been installed.';
-
-
---
--- Name: unregister_patch(text); Type: FUNCTION; Schema: _v; Owner: -
---
-
-CREATE FUNCTION _v.unregister_patch(in_patch_name text, OUT versioning integer) RETURNS SETOF integer
- LANGUAGE plpgsql
- AS $$
-DECLARE
- i INT4;
- t_text_a TEXT[];
-BEGIN
- -- Thanks to this we know only one patch will be applied at a time
- LOCK TABLE _v.patches IN EXCLUSIVE MODE;
-
- t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE in_patch_name = ANY( requires ) );
- IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
- RAISE EXCEPTION 'Cannot uninstall %, as it is required by: %.', in_patch_name, array_to_string( t_text_a, ', ' );
- END IF;
-
- DELETE FROM _v.patches WHERE patch_name = in_patch_name;
- GET DIAGNOSTICS i = ROW_COUNT;
- IF i < 1 THEN
- RAISE EXCEPTION 'Patch % is not installed, so it can''t be uninstalled!', in_patch_name;
- END IF;
-
- RETURN;
-END;
-$$;
-
-
---
--- Name: FUNCTION unregister_patch(in_patch_name text, OUT versioning integer); Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON FUNCTION _v.unregister_patch(in_patch_name text, OUT versioning integer) IS 'Function to unregister patches in database. Dies if the patch is not registered, or if unregistering it would break dependencies.';
-
-
-SET default_tablespace = '';
-
-SET default_with_oids = false;
-
---
--- Name: patches; Type: TABLE; Schema: _v; Owner: -
---
-
-CREATE TABLE _v.patches (
- patch_name text NOT NULL,
- applied_tsz timestamp with time zone DEFAULT now() NOT NULL,
- applied_by text NOT NULL,
- requires text[],
- conflicts text[]
-);
-
-
---
--- Name: TABLE patches; Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON TABLE _v.patches IS 'Contains information about what patches are currently applied on database.';
-
-
---
--- Name: COLUMN patches.patch_name; Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON COLUMN _v.patches.patch_name IS 'Name of patch, has to be unique for every patch.';
-
-
---
--- Name: COLUMN patches.applied_tsz; Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON COLUMN _v.patches.applied_tsz IS 'When the patch was applied.';
-
-
---
--- Name: COLUMN patches.applied_by; Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON COLUMN _v.patches.applied_by IS 'Who applied this patch (PostgreSQL username)';
-
-
---
--- Name: COLUMN patches.requires; Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON COLUMN _v.patches.requires IS 'List of patches that are required for given patch.';
-
-
---
--- Name: COLUMN patches.conflicts; Type: COMMENT; Schema: _v; Owner: -
---
-
-COMMENT ON COLUMN _v.patches.conflicts IS 'List of patches that conflict with given patch.';
-
-
---
--- Name: aggregation_tracking; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.aggregation_tracking (
- aggregation_serial_id bigint NOT NULL,
- deposit_serial_id bigint NOT NULL,
- wtid_raw bytea
-);
-
-
---
--- Name: TABLE aggregation_tracking; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.aggregation_tracking IS 'mapping from wire transfer identifiers (WTID) to deposits (and back)';
-
-
---
--- Name: aggregation_tracking_aggregation_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.aggregation_tracking_aggregation_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: aggregation_tracking_aggregation_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.aggregation_tracking_aggregation_serial_id_seq OWNED BY public.aggregation_tracking.aggregation_serial_id;
-
-
---
--- Name: app_bankaccount; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.app_bankaccount (
- is_public boolean NOT NULL,
- account_no integer NOT NULL,
- balance character varying NOT NULL,
- user_id integer NOT NULL
-);
-
-
---
--- Name: app_bankaccount_account_no_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.app_bankaccount_account_no_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: app_bankaccount_account_no_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.app_bankaccount_account_no_seq OWNED BY public.app_bankaccount.account_no;
-
-
---
--- Name: app_banktransaction; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.app_banktransaction (
- id integer NOT NULL,
- amount character varying NOT NULL,
- subject character varying(200) NOT NULL,
- date timestamp with time zone NOT NULL,
- cancelled boolean NOT NULL,
- request_uid character varying(128) NOT NULL,
- credit_account_id integer NOT NULL,
- debit_account_id integer NOT NULL
-);
-
-
---
--- Name: app_banktransaction_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.app_banktransaction_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: app_banktransaction_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.app_banktransaction_id_seq OWNED BY public.app_banktransaction.id;
-
-
---
--- Name: app_talerwithdrawoperation; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.app_talerwithdrawoperation (
- withdraw_id uuid NOT NULL,
- amount character varying NOT NULL,
- selection_done boolean NOT NULL,
- confirmation_done boolean NOT NULL,
- aborted boolean NOT NULL,
- selected_reserve_pub text,
- selected_exchange_account_id integer,
- withdraw_account_id integer NOT NULL
-);
-
-
---
--- Name: auditor_balance_summary; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_balance_summary (
- master_pub bytea,
- denom_balance_val bigint NOT NULL,
- denom_balance_frac integer NOT NULL,
- deposit_fee_balance_val bigint NOT NULL,
- deposit_fee_balance_frac integer NOT NULL,
- melt_fee_balance_val bigint NOT NULL,
- melt_fee_balance_frac integer NOT NULL,
- refund_fee_balance_val bigint NOT NULL,
- refund_fee_balance_frac integer NOT NULL,
- risk_val bigint NOT NULL,
- risk_frac integer NOT NULL,
- loss_val bigint NOT NULL,
- loss_frac integer NOT NULL,
- irregular_recoup_val bigint NOT NULL,
- irregular_recoup_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE auditor_balance_summary; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_balance_summary IS 'the sum of the outstanding coins from auditor_denomination_pending (denom_pubs must belong to the respectives exchange master public key); it represents the auditor_balance_summary of the exchange at this point (modulo unexpected historic_loss-style events where denomination keys are compromised)';
-
-
---
--- Name: auditor_denomination_pending; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_denomination_pending (
- denom_pub_hash bytea NOT NULL,
- denom_balance_val bigint NOT NULL,
- denom_balance_frac integer NOT NULL,
- denom_loss_val bigint NOT NULL,
- denom_loss_frac integer NOT NULL,
- num_issued bigint NOT NULL,
- denom_risk_val bigint NOT NULL,
- denom_risk_frac integer NOT NULL,
- recoup_loss_val bigint NOT NULL,
- recoup_loss_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE auditor_denomination_pending; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_denomination_pending IS 'outstanding denomination coins that the exchange is aware of and what the respective balances are (outstanding as well as issued overall which implies the maximum value at risk).';
-
-
---
--- Name: COLUMN auditor_denomination_pending.num_issued; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_denomination_pending.num_issued IS 'counts the number of coins issued (withdraw, refresh) of this denomination';
-
-
---
--- Name: COLUMN auditor_denomination_pending.denom_risk_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_denomination_pending.denom_risk_val IS 'amount that could theoretically be lost in the future due to recoup operations';
-
-
---
--- Name: COLUMN auditor_denomination_pending.recoup_loss_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_denomination_pending.recoup_loss_val IS 'amount actually lost due to recoup operations past revocation';
-
-
---
--- Name: auditor_denominations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_denominations (
- denom_pub_hash bytea NOT NULL,
- master_pub bytea,
- valid_from bigint NOT NULL,
- expire_withdraw bigint NOT NULL,
- expire_deposit bigint NOT NULL,
- expire_legal bigint NOT NULL,
- coin_val bigint NOT NULL,
- coin_frac integer NOT NULL,
- fee_withdraw_val bigint NOT NULL,
- fee_withdraw_frac integer NOT NULL,
- fee_deposit_val bigint NOT NULL,
- fee_deposit_frac integer NOT NULL,
- fee_refresh_val bigint NOT NULL,
- fee_refresh_frac integer NOT NULL,
- fee_refund_val bigint NOT NULL,
- fee_refund_frac integer NOT NULL,
- CONSTRAINT auditor_denominations_denom_pub_hash_check CHECK ((length(denom_pub_hash) = 64))
-);
-
-
---
--- Name: TABLE auditor_denominations; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_denominations IS 'denomination keys the auditor is aware of';
-
-
---
--- Name: auditor_exchange_signkeys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_exchange_signkeys (
- master_pub bytea,
- ep_start bigint NOT NULL,
- ep_expire bigint NOT NULL,
- ep_end bigint NOT NULL,
- exchange_pub bytea NOT NULL,
- master_sig bytea NOT NULL,
- CONSTRAINT auditor_exchange_signkeys_exchange_pub_check CHECK ((length(exchange_pub) = 32)),
- CONSTRAINT auditor_exchange_signkeys_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE auditor_exchange_signkeys; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_exchange_signkeys IS 'list of the online signing keys of exchanges we are auditing';
-
-
---
--- Name: auditor_exchanges; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_exchanges (
- master_pub bytea NOT NULL,
- exchange_url character varying NOT NULL,
- CONSTRAINT auditor_exchanges_master_pub_check CHECK ((length(master_pub) = 32))
-);
-
-
---
--- Name: TABLE auditor_exchanges; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_exchanges IS 'list of the exchanges we are auditing';
-
-
---
--- Name: auditor_historic_denomination_revenue; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_historic_denomination_revenue (
- master_pub bytea,
- denom_pub_hash bytea NOT NULL,
- revenue_timestamp bigint NOT NULL,
- revenue_balance_val bigint NOT NULL,
- revenue_balance_frac integer NOT NULL,
- loss_balance_val bigint NOT NULL,
- loss_balance_frac integer NOT NULL,
- CONSTRAINT auditor_historic_denomination_revenue_denom_pub_hash_check CHECK ((length(denom_pub_hash) = 64))
-);
-
-
---
--- Name: TABLE auditor_historic_denomination_revenue; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_historic_denomination_revenue IS 'Table with historic profits; basically, when a denom_pub has expired and everything associated with it is garbage collected, the final profits end up in here; note that the denom_pub here is not a foreign key, we just keep it as a reference point.';
-
-
---
--- Name: COLUMN auditor_historic_denomination_revenue.revenue_balance_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_historic_denomination_revenue.revenue_balance_val IS 'the sum of all of the profits we made on the coin except for withdraw fees (which are in historic_reserve_revenue); so this includes the deposit, melt and refund fees';
-
-
---
--- Name: auditor_historic_reserve_summary; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_historic_reserve_summary (
- master_pub bytea,
- start_date bigint NOT NULL,
- end_date bigint NOT NULL,
- reserve_profits_val bigint NOT NULL,
- reserve_profits_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE auditor_historic_reserve_summary; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_historic_reserve_summary IS 'historic profits from reserves; we eventually GC auditor_historic_reserve_revenue, and then store the totals in here (by time intervals).';
-
-
---
--- Name: auditor_predicted_result; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_predicted_result (
- master_pub bytea,
- balance_val bigint NOT NULL,
- balance_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE auditor_predicted_result; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_predicted_result IS 'Table with the sum of the ledger, auditor_historic_revenue and the auditor_reserve_balance. This is the final amount that the exchange should have in its bank account right now.';
-
-
---
--- Name: auditor_progress_aggregation; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_progress_aggregation (
- master_pub bytea NOT NULL,
- last_wire_out_serial_id bigint DEFAULT 0 NOT NULL
-);
-
-
---
--- Name: TABLE auditor_progress_aggregation; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_progress_aggregation IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
---
--- Name: auditor_progress_coin; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_progress_coin (
- master_pub bytea NOT NULL,
- last_withdraw_serial_id bigint DEFAULT 0 NOT NULL,
- last_deposit_serial_id bigint DEFAULT 0 NOT NULL,
- last_melt_serial_id bigint DEFAULT 0 NOT NULL,
- last_refund_serial_id bigint DEFAULT 0 NOT NULL,
- last_recoup_serial_id bigint DEFAULT 0 NOT NULL,
- last_recoup_refresh_serial_id bigint DEFAULT 0 NOT NULL
-);
-
-
---
--- Name: TABLE auditor_progress_coin; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_progress_coin IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
---
--- Name: auditor_progress_deposit_confirmation; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_progress_deposit_confirmation (
- master_pub bytea NOT NULL,
- last_deposit_confirmation_serial_id bigint DEFAULT 0 NOT NULL
-);
-
-
---
--- Name: TABLE auditor_progress_deposit_confirmation; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_progress_deposit_confirmation IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
---
--- Name: auditor_progress_reserve; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_progress_reserve (
- master_pub bytea NOT NULL,
- last_reserve_in_serial_id bigint DEFAULT 0 NOT NULL,
- last_reserve_out_serial_id bigint DEFAULT 0 NOT NULL,
- last_reserve_recoup_serial_id bigint DEFAULT 0 NOT NULL,
- last_reserve_close_serial_id bigint DEFAULT 0 NOT NULL
-);
-
-
---
--- Name: TABLE auditor_progress_reserve; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_progress_reserve IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
---
--- Name: auditor_reserve_balance; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_reserve_balance (
- master_pub bytea,
- reserve_balance_val bigint NOT NULL,
- reserve_balance_frac integer NOT NULL,
- withdraw_fee_balance_val bigint NOT NULL,
- withdraw_fee_balance_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE auditor_reserve_balance; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_reserve_balance IS 'sum of the balances of all customer reserves (by exchange master public key)';
-
-
---
--- Name: auditor_reserves; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_reserves (
- reserve_pub bytea NOT NULL,
- master_pub bytea,
- reserve_balance_val bigint NOT NULL,
- reserve_balance_frac integer NOT NULL,
- withdraw_fee_balance_val bigint NOT NULL,
- withdraw_fee_balance_frac integer NOT NULL,
- expiration_date bigint NOT NULL,
- auditor_reserves_rowid bigint NOT NULL,
- origin_account text,
- CONSTRAINT auditor_reserves_reserve_pub_check CHECK ((length(reserve_pub) = 32))
-);
-
-
---
--- Name: TABLE auditor_reserves; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_reserves IS 'all of the customer reserves and their respective balances that the auditor is aware of';
-
-
---
--- Name: auditor_reserves_auditor_reserves_rowid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auditor_reserves_auditor_reserves_rowid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auditor_reserves_auditor_reserves_rowid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auditor_reserves_auditor_reserves_rowid_seq OWNED BY public.auditor_reserves.auditor_reserves_rowid;
-
-
---
--- Name: auditor_wire_fee_balance; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_wire_fee_balance (
- master_pub bytea,
- wire_fee_balance_val bigint NOT NULL,
- wire_fee_balance_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE auditor_wire_fee_balance; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_wire_fee_balance IS 'sum of the balances of all wire fees (by exchange master public key)';
-
-
---
--- Name: auth_group; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auth_group (
- id integer NOT NULL,
- name character varying(150) NOT NULL
-);
-
-
---
--- Name: auth_group_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auth_group_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auth_group_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auth_group_id_seq OWNED BY public.auth_group.id;
-
-
---
--- Name: auth_group_permissions; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auth_group_permissions (
- id integer NOT NULL,
- group_id integer NOT NULL,
- permission_id integer NOT NULL
-);
-
-
---
--- Name: auth_group_permissions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auth_group_permissions_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auth_group_permissions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auth_group_permissions_id_seq OWNED BY public.auth_group_permissions.id;
-
-
---
--- Name: auth_permission; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auth_permission (
- id integer NOT NULL,
- name character varying(255) NOT NULL,
- content_type_id integer NOT NULL,
- codename character varying(100) NOT NULL
-);
-
-
---
--- Name: auth_permission_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auth_permission_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auth_permission_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auth_permission_id_seq OWNED BY public.auth_permission.id;
-
-
---
--- Name: auth_user; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auth_user (
- id integer NOT NULL,
- password character varying(128) NOT NULL,
- last_login timestamp with time zone,
- is_superuser boolean NOT NULL,
- username character varying(150) NOT NULL,
- first_name character varying(30) NOT NULL,
- last_name character varying(150) NOT NULL,
- email character varying(254) NOT NULL,
- is_staff boolean NOT NULL,
- is_active boolean NOT NULL,
- date_joined timestamp with time zone NOT NULL
-);
-
-
---
--- Name: auth_user_groups; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auth_user_groups (
- id integer NOT NULL,
- user_id integer NOT NULL,
- group_id integer NOT NULL
-);
-
-
---
--- Name: auth_user_groups_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auth_user_groups_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auth_user_groups_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auth_user_groups_id_seq OWNED BY public.auth_user_groups.id;
-
-
---
--- Name: auth_user_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auth_user_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auth_user_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auth_user_id_seq OWNED BY public.auth_user.id;
-
-
---
--- Name: auth_user_user_permissions; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auth_user_user_permissions (
- id integer NOT NULL,
- user_id integer NOT NULL,
- permission_id integer NOT NULL
-);
-
-
---
--- Name: auth_user_user_permissions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auth_user_user_permissions_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auth_user_user_permissions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auth_user_user_permissions_id_seq OWNED BY public.auth_user_user_permissions.id;
-
-
---
--- Name: denomination_revocations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.denomination_revocations (
- denom_revocations_serial_id bigint NOT NULL,
- denom_pub_hash bytea NOT NULL,
- master_sig bytea NOT NULL,
- CONSTRAINT denomination_revocations_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE denomination_revocations; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.denomination_revocations IS 'remembering which denomination keys have been revoked';
-
-
---
--- Name: denomination_revocations_denom_revocations_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.denomination_revocations_denom_revocations_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: denomination_revocations_denom_revocations_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.denomination_revocations_denom_revocations_serial_id_seq OWNED BY public.denomination_revocations.denom_revocations_serial_id;
-
-
---
--- Name: denominations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.denominations (
- denom_pub_hash bytea NOT NULL,
- denom_pub bytea NOT NULL,
- master_pub bytea NOT NULL,
- master_sig bytea NOT NULL,
- valid_from bigint NOT NULL,
- expire_withdraw bigint NOT NULL,
- expire_deposit bigint NOT NULL,
- expire_legal bigint NOT NULL,
- coin_val bigint NOT NULL,
- coin_frac integer NOT NULL,
- fee_withdraw_val bigint NOT NULL,
- fee_withdraw_frac integer NOT NULL,
- fee_deposit_val bigint NOT NULL,
- fee_deposit_frac integer NOT NULL,
- fee_refresh_val bigint NOT NULL,
- fee_refresh_frac integer NOT NULL,
- fee_refund_val bigint NOT NULL,
- fee_refund_frac integer NOT NULL,
- CONSTRAINT denominations_denom_pub_hash_check CHECK ((length(denom_pub_hash) = 64)),
- CONSTRAINT denominations_master_pub_check CHECK ((length(master_pub) = 32)),
- CONSTRAINT denominations_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE denominations; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.denominations IS 'Main denominations table. All the coins the exchange knows about.';
-
-
---
--- Name: deposit_confirmations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.deposit_confirmations (
- master_pub bytea,
- serial_id bigint NOT NULL,
- h_contract_terms bytea NOT NULL,
- h_wire bytea NOT NULL,
- "timestamp" bigint NOT NULL,
- refund_deadline bigint NOT NULL,
- amount_without_fee_val bigint NOT NULL,
- amount_without_fee_frac integer NOT NULL,
- coin_pub bytea NOT NULL,
- merchant_pub bytea NOT NULL,
- exchange_sig bytea NOT NULL,
- exchange_pub bytea NOT NULL,
- master_sig bytea NOT NULL,
- CONSTRAINT deposit_confirmations_coin_pub_check CHECK ((length(coin_pub) = 32)),
- CONSTRAINT deposit_confirmations_exchange_pub_check CHECK ((length(exchange_pub) = 32)),
- CONSTRAINT deposit_confirmations_exchange_sig_check CHECK ((length(exchange_sig) = 64)),
- CONSTRAINT deposit_confirmations_h_contract_terms_check CHECK ((length(h_contract_terms) = 64)),
- CONSTRAINT deposit_confirmations_h_wire_check CHECK ((length(h_wire) = 64)),
- CONSTRAINT deposit_confirmations_master_sig_check CHECK ((length(master_sig) = 64)),
- CONSTRAINT deposit_confirmations_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: TABLE deposit_confirmations; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.deposit_confirmations IS 'deposit confirmation sent to us by merchants; we must check that the exchange reported these properly.';
-
-
---
--- Name: deposit_confirmations_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.deposit_confirmations_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: deposit_confirmations_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.deposit_confirmations_serial_id_seq OWNED BY public.deposit_confirmations.serial_id;
-
-
---
--- Name: deposits; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.deposits (
- deposit_serial_id bigint NOT NULL,
- coin_pub bytea NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- "timestamp" bigint NOT NULL,
- refund_deadline bigint NOT NULL,
- wire_deadline bigint NOT NULL,
- merchant_pub bytea NOT NULL,
- h_contract_terms bytea NOT NULL,
- h_wire bytea NOT NULL,
- coin_sig bytea NOT NULL,
- wire text NOT NULL,
- tiny boolean DEFAULT false NOT NULL,
- done boolean DEFAULT false NOT NULL,
- CONSTRAINT deposits_coin_sig_check CHECK ((length(coin_sig) = 64)),
- CONSTRAINT deposits_h_contract_terms_check CHECK ((length(h_contract_terms) = 64)),
- CONSTRAINT deposits_h_wire_check CHECK ((length(h_wire) = 64)),
- CONSTRAINT deposits_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: TABLE deposits; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.deposits IS 'Deposits we have received and for which we need to make (aggregate) wire transfers (and manage refunds).';
-
-
---
--- Name: COLUMN deposits.tiny; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.tiny IS 'Set to TRUE if we decided that the amount is too small to ever trigger a wire transfer by itself (requires real aggregation)';
-
-
---
--- Name: COLUMN deposits.done; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.done IS 'Set to TRUE once we have included this deposit in some aggregate wire transfer to the merchant';
-
-
---
--- Name: deposits_deposit_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.deposits_deposit_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: deposits_deposit_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.deposits_deposit_serial_id_seq OWNED BY public.deposits.deposit_serial_id;
-
-
---
--- Name: django_content_type; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.django_content_type (
- id integer NOT NULL,
- app_label character varying(100) NOT NULL,
- model character varying(100) NOT NULL
-);
-
-
---
--- Name: django_content_type_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.django_content_type_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: django_content_type_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.django_content_type_id_seq OWNED BY public.django_content_type.id;
-
-
---
--- Name: django_migrations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.django_migrations (
- id integer NOT NULL,
- app character varying(255) NOT NULL,
- name character varying(255) NOT NULL,
- applied timestamp with time zone NOT NULL
-);
-
-
---
--- Name: django_migrations_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.django_migrations_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: django_migrations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.django_migrations_id_seq OWNED BY public.django_migrations.id;
-
-
---
--- Name: django_session; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.django_session (
- session_key character varying(40) NOT NULL,
- session_data text NOT NULL,
- expire_date timestamp with time zone NOT NULL
-);
-
-
---
--- Name: exchange_wire_fees; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.exchange_wire_fees (
- exchange_pub bytea NOT NULL,
- h_wire_method bytea NOT NULL,
- wire_fee_val bigint NOT NULL,
- wire_fee_frac integer NOT NULL,
- closing_fee_val bigint NOT NULL,
- closing_fee_frac integer NOT NULL,
- start_date bigint NOT NULL,
- end_date bigint NOT NULL,
- exchange_sig bytea NOT NULL,
- CONSTRAINT exchange_wire_fees_exchange_pub_check CHECK ((length(exchange_pub) = 32)),
- CONSTRAINT exchange_wire_fees_exchange_sig_check CHECK ((length(exchange_sig) = 64)),
- CONSTRAINT exchange_wire_fees_h_wire_method_check CHECK ((length(h_wire_method) = 64))
-);
-
-
---
--- Name: known_coins; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.known_coins (
- coin_pub bytea NOT NULL,
- denom_pub_hash bytea NOT NULL,
- denom_sig bytea NOT NULL,
- CONSTRAINT known_coins_coin_pub_check CHECK ((length(coin_pub) = 32))
-);
-
-
---
--- Name: TABLE known_coins; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.known_coins IS 'information about coins and their signatures, so we do not have to store the signatures more than once if a coin is involved in multiple operations';
-
-
---
--- Name: merchant_contract_terms; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_contract_terms (
- order_id character varying NOT NULL,
- merchant_pub bytea NOT NULL,
- contract_terms bytea NOT NULL,
- h_contract_terms bytea NOT NULL,
- "timestamp" bigint NOT NULL,
- row_id bigint NOT NULL,
- paid boolean DEFAULT false NOT NULL,
- CONSTRAINT merchant_contract_terms_h_contract_terms_check CHECK ((length(h_contract_terms) = 64)),
- CONSTRAINT merchant_contract_terms_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: merchant_contract_terms_row_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_contract_terms_row_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_contract_terms_row_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_contract_terms_row_id_seq OWNED BY public.merchant_contract_terms.row_id;
-
-
---
--- Name: merchant_deposits; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_deposits (
- h_contract_terms bytea NOT NULL,
- merchant_pub bytea NOT NULL,
- coin_pub bytea NOT NULL,
- exchange_url character varying NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- deposit_fee_val bigint NOT NULL,
- deposit_fee_frac integer NOT NULL,
- refund_fee_val bigint NOT NULL,
- refund_fee_frac integer NOT NULL,
- wire_fee_val bigint NOT NULL,
- wire_fee_frac integer NOT NULL,
- signkey_pub bytea NOT NULL,
- exchange_proof bytea NOT NULL,
- CONSTRAINT merchant_deposits_coin_pub_check CHECK ((length(coin_pub) = 32)),
- CONSTRAINT merchant_deposits_merchant_pub_check CHECK ((length(merchant_pub) = 32)),
- CONSTRAINT merchant_deposits_signkey_pub_check CHECK ((length(signkey_pub) = 32))
-);
-
-
---
--- Name: merchant_orders; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_orders (
- order_id character varying NOT NULL,
- merchant_pub bytea NOT NULL,
- contract_terms bytea NOT NULL,
- "timestamp" bigint NOT NULL,
- CONSTRAINT merchant_orders_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: merchant_proofs; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_proofs (
- exchange_url character varying NOT NULL,
- wtid bytea NOT NULL,
- execution_time bigint NOT NULL,
- signkey_pub bytea NOT NULL,
- proof bytea NOT NULL,
- CONSTRAINT merchant_proofs_signkey_pub_check CHECK ((length(signkey_pub) = 32)),
- CONSTRAINT merchant_proofs_wtid_check CHECK ((length(wtid) = 32))
-);
-
-
---
--- Name: merchant_refunds; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_refunds (
- rtransaction_id bigint NOT NULL,
- merchant_pub bytea NOT NULL,
- h_contract_terms bytea NOT NULL,
- coin_pub bytea NOT NULL,
- reason character varying NOT NULL,
- refund_amount_val bigint NOT NULL,
- refund_amount_frac integer NOT NULL,
- refund_fee_val bigint NOT NULL,
- refund_fee_frac integer NOT NULL,
- CONSTRAINT merchant_refunds_coin_pub_check CHECK ((length(coin_pub) = 32)),
- CONSTRAINT merchant_refunds_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: merchant_refunds_rtransaction_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_refunds_rtransaction_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_refunds_rtransaction_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_refunds_rtransaction_id_seq OWNED BY public.merchant_refunds.rtransaction_id;
-
-
---
--- Name: merchant_session_info; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_session_info (
- session_id character varying NOT NULL,
- fulfillment_url character varying NOT NULL,
- order_id character varying NOT NULL,
- merchant_pub bytea NOT NULL,
- "timestamp" bigint NOT NULL,
- CONSTRAINT merchant_session_info_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: merchant_tip_pickups; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_pickups (
- tip_id bytea NOT NULL,
- pickup_id bytea NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- CONSTRAINT merchant_tip_pickups_pickup_id_check CHECK ((length(pickup_id) = 64))
-);
-
-
---
--- Name: merchant_tip_reserve_credits; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_reserve_credits (
- reserve_priv bytea NOT NULL,
- credit_uuid bytea NOT NULL,
- "timestamp" bigint NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- CONSTRAINT merchant_tip_reserve_credits_credit_uuid_check CHECK ((length(credit_uuid) = 64)),
- CONSTRAINT merchant_tip_reserve_credits_reserve_priv_check CHECK ((length(reserve_priv) = 32))
-);
-
-
---
--- Name: merchant_tip_reserves; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_reserves (
- reserve_priv bytea NOT NULL,
- expiration bigint NOT NULL,
- balance_val bigint NOT NULL,
- balance_frac integer NOT NULL,
- CONSTRAINT merchant_tip_reserves_reserve_priv_check CHECK ((length(reserve_priv) = 32))
-);
-
-
---
--- Name: merchant_tips; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tips (
- reserve_priv bytea NOT NULL,
- tip_id bytea NOT NULL,
- exchange_url character varying NOT NULL,
- justification character varying NOT NULL,
- extra bytea NOT NULL,
- "timestamp" bigint NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- left_val bigint NOT NULL,
- left_frac integer NOT NULL,
- CONSTRAINT merchant_tips_reserve_priv_check CHECK ((length(reserve_priv) = 32)),
- CONSTRAINT merchant_tips_tip_id_check CHECK ((length(tip_id) = 64))
-);
-
-
---
--- Name: merchant_transfers; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_transfers (
- h_contract_terms bytea NOT NULL,
- coin_pub bytea NOT NULL,
- wtid bytea NOT NULL,
- CONSTRAINT merchant_transfers_coin_pub_check CHECK ((length(coin_pub) = 32)),
- CONSTRAINT merchant_transfers_wtid_check CHECK ((length(wtid) = 32))
-);
-
-
---
--- Name: prewire; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.prewire (
- prewire_uuid bigint NOT NULL,
- type text NOT NULL,
- finished boolean DEFAULT false NOT NULL,
- buf bytea NOT NULL
-);
-
-
---
--- Name: TABLE prewire; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.prewire IS 'pre-commit data for wire transfers we are about to execute';
-
-
---
--- Name: prewire_prewire_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.prewire_prewire_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: prewire_prewire_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.prewire_prewire_uuid_seq OWNED BY public.prewire.prewire_uuid;
-
-
---
--- Name: recoup; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.recoup (
- recoup_uuid bigint NOT NULL,
- coin_pub bytea NOT NULL,
- coin_sig bytea NOT NULL,
- coin_blind bytea NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- "timestamp" bigint NOT NULL,
- h_blind_ev bytea NOT NULL,
- CONSTRAINT recoup_coin_blind_check CHECK ((length(coin_blind) = 32)),
- CONSTRAINT recoup_coin_sig_check CHECK ((length(coin_sig) = 64))
-);
-
-
---
--- Name: TABLE recoup; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.recoup IS 'Information about recoups that were executed';
-
-
---
--- Name: COLUMN recoup.coin_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.recoup.coin_pub IS 'Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!';
-
-
---
--- Name: recoup_recoup_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.recoup_recoup_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: recoup_recoup_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.recoup_recoup_uuid_seq OWNED BY public.recoup.recoup_uuid;
-
-
---
--- Name: recoup_refresh; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.recoup_refresh (
- recoup_refresh_uuid bigint NOT NULL,
- coin_pub bytea NOT NULL,
- coin_sig bytea NOT NULL,
- coin_blind bytea NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- "timestamp" bigint NOT NULL,
- h_blind_ev bytea NOT NULL,
- CONSTRAINT recoup_refresh_coin_blind_check CHECK ((length(coin_blind) = 32)),
- CONSTRAINT recoup_refresh_coin_sig_check CHECK ((length(coin_sig) = 64))
-);
-
-
---
--- Name: COLUMN recoup_refresh.coin_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.recoup_refresh.coin_pub IS 'Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!';
-
-
---
--- Name: recoup_refresh_recoup_refresh_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.recoup_refresh_recoup_refresh_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: recoup_refresh_recoup_refresh_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.recoup_refresh_recoup_refresh_uuid_seq OWNED BY public.recoup_refresh.recoup_refresh_uuid;
-
-
---
--- Name: refresh_commitments; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.refresh_commitments (
- melt_serial_id bigint NOT NULL,
- rc bytea NOT NULL,
- old_coin_pub bytea NOT NULL,
- old_coin_sig bytea NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- noreveal_index integer NOT NULL,
- CONSTRAINT refresh_commitments_old_coin_sig_check CHECK ((length(old_coin_sig) = 64)),
- CONSTRAINT refresh_commitments_rc_check CHECK ((length(rc) = 64))
-);
-
-
---
--- Name: TABLE refresh_commitments; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.refresh_commitments IS 'Commitments made when melting coins and the gamma value chosen by the exchange.';
-
-
---
--- Name: refresh_commitments_melt_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.refresh_commitments_melt_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: refresh_commitments_melt_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.refresh_commitments_melt_serial_id_seq OWNED BY public.refresh_commitments.melt_serial_id;
-
-
---
--- Name: refresh_revealed_coins; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.refresh_revealed_coins (
- rc bytea NOT NULL,
- freshcoin_index integer NOT NULL,
- link_sig bytea NOT NULL,
- denom_pub_hash bytea NOT NULL,
- coin_ev bytea NOT NULL,
- h_coin_ev bytea NOT NULL,
- ev_sig bytea NOT NULL,
- CONSTRAINT refresh_revealed_coins_h_coin_ev_check CHECK ((length(h_coin_ev) = 64)),
- CONSTRAINT refresh_revealed_coins_link_sig_check CHECK ((length(link_sig) = 64))
-);
-
-
---
--- Name: TABLE refresh_revealed_coins; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.refresh_revealed_coins IS 'Revelations about the new coins that are to be created during a melting session.';
-
-
---
--- Name: COLUMN refresh_revealed_coins.rc; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.rc IS 'refresh commitment identifying the melt operation';
-
-
---
--- Name: COLUMN refresh_revealed_coins.freshcoin_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.freshcoin_index IS 'index of the fresh coin being created (one melt operation may result in multiple fresh coins)';
-
-
---
--- Name: COLUMN refresh_revealed_coins.coin_ev; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.coin_ev IS 'envelope of the new coin to be signed';
-
-
---
--- Name: COLUMN refresh_revealed_coins.h_coin_ev; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.h_coin_ev IS 'hash of the envelope of the new coin to be signed (for lookups)';
-
-
---
--- Name: COLUMN refresh_revealed_coins.ev_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.ev_sig IS 'exchange signature over the envelope';
-
-
---
--- Name: refresh_transfer_keys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.refresh_transfer_keys (
- rc bytea NOT NULL,
- transfer_pub bytea NOT NULL,
- transfer_privs bytea NOT NULL,
- CONSTRAINT refresh_transfer_keys_transfer_pub_check CHECK ((length(transfer_pub) = 32))
-);
-
-
---
--- Name: TABLE refresh_transfer_keys; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.refresh_transfer_keys IS 'Transfer keys of a refresh operation (the data revealed to the exchange).';
-
-
---
--- Name: COLUMN refresh_transfer_keys.rc; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_transfer_keys.rc IS 'refresh commitment identifying the melt operation';
-
-
---
--- Name: COLUMN refresh_transfer_keys.transfer_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_transfer_keys.transfer_pub IS 'transfer public key for the gamma index';
-
-
---
--- Name: COLUMN refresh_transfer_keys.transfer_privs; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_transfer_keys.transfer_privs IS 'array of TALER_CNC_KAPPA - 1 transfer private keys that have been revealed, with the gamma entry being skipped';
-
-
---
--- Name: refunds; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.refunds (
- refund_serial_id bigint NOT NULL,
- coin_pub bytea NOT NULL,
- merchant_pub bytea NOT NULL,
- merchant_sig bytea NOT NULL,
- h_contract_terms bytea NOT NULL,
- rtransaction_id bigint NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- CONSTRAINT refunds_h_contract_terms_check CHECK ((length(h_contract_terms) = 64)),
- CONSTRAINT refunds_merchant_pub_check CHECK ((length(merchant_pub) = 32)),
- CONSTRAINT refunds_merchant_sig_check CHECK ((length(merchant_sig) = 64))
-);
-
-
---
--- Name: TABLE refunds; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.refunds IS 'Data on coins that were refunded. Technically, refunds always apply against specific deposit operations involving a coin. The combination of coin_pub, merchant_pub, h_contract_terms and rtransaction_id MUST be unique, and we usually select by coin_pub so that one goes first.';
-
-
---
--- Name: COLUMN refunds.rtransaction_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refunds.rtransaction_id IS 'used by the merchant to make refunds unique in case the same coin for the same deposit gets a subsequent (higher) refund';
-
-
---
--- Name: refunds_refund_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.refunds_refund_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: refunds_refund_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.refunds_refund_serial_id_seq OWNED BY public.refunds.refund_serial_id;
-
-
---
--- Name: reserves; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.reserves (
- reserve_pub bytea NOT NULL,
- account_details text NOT NULL,
- current_balance_val bigint NOT NULL,
- current_balance_frac integer NOT NULL,
- expiration_date bigint NOT NULL,
- gc_date bigint NOT NULL,
- CONSTRAINT reserves_reserve_pub_check CHECK ((length(reserve_pub) = 32))
-);
-
-
---
--- Name: TABLE reserves; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.reserves IS 'Summarizes the balance of a reserve. Updated when new funds are added or withdrawn.';
-
-
---
--- Name: COLUMN reserves.expiration_date; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves.expiration_date IS 'Used to trigger closing of reserves that have not been drained after some time';
-
-
---
--- Name: COLUMN reserves.gc_date; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves.gc_date IS 'Used to forget all information about a reserve during garbage collection';
-
-
---
--- Name: reserves_close; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.reserves_close (
- close_uuid bigint NOT NULL,
- reserve_pub bytea NOT NULL,
- execution_date bigint NOT NULL,
- wtid bytea NOT NULL,
- receiver_account text NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- closing_fee_val bigint NOT NULL,
- closing_fee_frac integer NOT NULL,
- CONSTRAINT reserves_close_wtid_check CHECK ((length(wtid) = 32))
-);
-
-
---
--- Name: TABLE reserves_close; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.reserves_close IS 'wire transfers executed by the reserve to close reserves';
-
-
---
--- Name: reserves_close_close_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.reserves_close_close_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: reserves_close_close_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.reserves_close_close_uuid_seq OWNED BY public.reserves_close.close_uuid;
-
-
---
--- Name: reserves_in; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.reserves_in (
- reserve_in_serial_id bigint NOT NULL,
- reserve_pub bytea NOT NULL,
- wire_reference bigint NOT NULL,
- credit_val bigint NOT NULL,
- credit_frac integer NOT NULL,
- sender_account_details text NOT NULL,
- exchange_account_section text NOT NULL,
- execution_date bigint NOT NULL
-);
-
-
---
--- Name: TABLE reserves_in; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.reserves_in IS 'list of transfers of funds into the reserves, one per incoming wire transfer';
-
-
---
--- Name: reserves_in_reserve_in_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.reserves_in_reserve_in_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: reserves_in_reserve_in_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.reserves_in_reserve_in_serial_id_seq OWNED BY public.reserves_in.reserve_in_serial_id;
-
-
---
--- Name: reserves_out; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.reserves_out (
- reserve_out_serial_id bigint NOT NULL,
- h_blind_ev bytea NOT NULL,
- denom_pub_hash bytea NOT NULL,
- denom_sig bytea NOT NULL,
- reserve_pub bytea NOT NULL,
- reserve_sig bytea NOT NULL,
- execution_date bigint NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- CONSTRAINT reserves_out_h_blind_ev_check CHECK ((length(h_blind_ev) = 64)),
- CONSTRAINT reserves_out_reserve_sig_check CHECK ((length(reserve_sig) = 64))
-);
-
-
---
--- Name: TABLE reserves_out; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.reserves_out IS 'Withdraw operations performed on reserves.';
-
-
---
--- Name: COLUMN reserves_out.h_blind_ev; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves_out.h_blind_ev IS 'Hash of the blinded coin, used as primary key here so that broken clients that use a non-random coin or blinding factor fail to withdraw (otherwise they would fail on deposit when the coin is not unique there).';
-
-
---
--- Name: COLUMN reserves_out.denom_pub_hash; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves_out.denom_pub_hash IS 'We do not CASCADE ON DELETE here, we may keep the denomination data alive';
-
-
---
--- Name: reserves_out_reserve_out_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.reserves_out_reserve_out_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: reserves_out_reserve_out_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.reserves_out_reserve_out_serial_id_seq OWNED BY public.reserves_out.reserve_out_serial_id;
-
-
---
--- Name: wire_auditor_account_progress; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.wire_auditor_account_progress (
- master_pub bytea NOT NULL,
- account_name text NOT NULL,
- last_wire_reserve_in_serial_id bigint DEFAULT 0 NOT NULL,
- last_wire_wire_out_serial_id bigint DEFAULT 0 NOT NULL,
- wire_in_off bigint,
- wire_out_off bigint
-);
-
-
---
--- Name: TABLE wire_auditor_account_progress; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.wire_auditor_account_progress IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
---
--- Name: wire_auditor_progress; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.wire_auditor_progress (
- master_pub bytea NOT NULL,
- last_timestamp bigint NOT NULL,
- last_reserve_close_uuid bigint NOT NULL
-);
-
-
---
--- Name: wire_fee; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.wire_fee (
- wire_method character varying NOT NULL,
- start_date bigint NOT NULL,
- end_date bigint NOT NULL,
- wire_fee_val bigint NOT NULL,
- wire_fee_frac integer NOT NULL,
- closing_fee_val bigint NOT NULL,
- closing_fee_frac integer NOT NULL,
- master_sig bytea NOT NULL,
- CONSTRAINT wire_fee_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE wire_fee; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.wire_fee IS 'list of the wire fees of this exchange, by date';
-
-
---
--- Name: wire_out; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.wire_out (
- wireout_uuid bigint NOT NULL,
- execution_date bigint NOT NULL,
- wtid_raw bytea NOT NULL,
- wire_target text NOT NULL,
- exchange_account_section text NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- CONSTRAINT wire_out_wtid_raw_check CHECK ((length(wtid_raw) = 32))
-);
-
-
---
--- Name: TABLE wire_out; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.wire_out IS 'wire transfers the exchange has executed';
-
-
---
--- Name: wire_out_wireout_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.wire_out_wireout_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: wire_out_wireout_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.wire_out_wireout_uuid_seq OWNED BY public.wire_out.wireout_uuid;
-
-
---
--- Name: aggregation_tracking aggregation_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.aggregation_tracking ALTER COLUMN aggregation_serial_id SET DEFAULT nextval('public.aggregation_tracking_aggregation_serial_id_seq'::regclass);
-
-
---
--- Name: app_bankaccount account_no; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_bankaccount ALTER COLUMN account_no SET DEFAULT nextval('public.app_bankaccount_account_no_seq'::regclass);
-
-
---
--- Name: app_banktransaction id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_banktransaction ALTER COLUMN id SET DEFAULT nextval('public.app_banktransaction_id_seq'::regclass);
-
-
---
--- Name: auditor_reserves auditor_reserves_rowid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_reserves ALTER COLUMN auditor_reserves_rowid SET DEFAULT nextval('public.auditor_reserves_auditor_reserves_rowid_seq'::regclass);
-
-
---
--- Name: auth_group id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group ALTER COLUMN id SET DEFAULT nextval('public.auth_group_id_seq'::regclass);
-
-
---
--- Name: auth_group_permissions id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group_permissions ALTER COLUMN id SET DEFAULT nextval('public.auth_group_permissions_id_seq'::regclass);
-
-
---
--- Name: auth_permission id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_permission ALTER COLUMN id SET DEFAULT nextval('public.auth_permission_id_seq'::regclass);
-
-
---
--- Name: auth_user id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user ALTER COLUMN id SET DEFAULT nextval('public.auth_user_id_seq'::regclass);
-
-
---
--- Name: auth_user_groups id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_groups ALTER COLUMN id SET DEFAULT nextval('public.auth_user_groups_id_seq'::regclass);
-
-
---
--- Name: auth_user_user_permissions id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_user_permissions ALTER COLUMN id SET DEFAULT nextval('public.auth_user_user_permissions_id_seq'::regclass);
-
-
---
--- Name: denomination_revocations denom_revocations_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denomination_revocations ALTER COLUMN denom_revocations_serial_id SET DEFAULT nextval('public.denomination_revocations_denom_revocations_serial_id_seq'::regclass);
-
-
---
--- Name: deposit_confirmations serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposit_confirmations ALTER COLUMN serial_id SET DEFAULT nextval('public.deposit_confirmations_serial_id_seq'::regclass);
-
-
---
--- Name: deposits deposit_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits ALTER COLUMN deposit_serial_id SET DEFAULT nextval('public.deposits_deposit_serial_id_seq'::regclass);
-
-
---
--- Name: django_content_type id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.django_content_type ALTER COLUMN id SET DEFAULT nextval('public.django_content_type_id_seq'::regclass);
-
-
---
--- Name: django_migrations id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.django_migrations ALTER COLUMN id SET DEFAULT nextval('public.django_migrations_id_seq'::regclass);
-
-
---
--- Name: merchant_contract_terms row_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms ALTER COLUMN row_id SET DEFAULT nextval('public.merchant_contract_terms_row_id_seq'::regclass);
-
-
---
--- Name: merchant_refunds rtransaction_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refunds ALTER COLUMN rtransaction_id SET DEFAULT nextval('public.merchant_refunds_rtransaction_id_seq'::regclass);
-
-
---
--- Name: prewire prewire_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.prewire ALTER COLUMN prewire_uuid SET DEFAULT nextval('public.prewire_prewire_uuid_seq'::regclass);
-
-
---
--- Name: recoup recoup_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup ALTER COLUMN recoup_uuid SET DEFAULT nextval('public.recoup_recoup_uuid_seq'::regclass);
-
-
---
--- Name: recoup_refresh recoup_refresh_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh ALTER COLUMN recoup_refresh_uuid SET DEFAULT nextval('public.recoup_refresh_recoup_refresh_uuid_seq'::regclass);
-
-
---
--- Name: refresh_commitments melt_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_commitments ALTER COLUMN melt_serial_id SET DEFAULT nextval('public.refresh_commitments_melt_serial_id_seq'::regclass);
-
-
---
--- Name: refunds refund_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refunds ALTER COLUMN refund_serial_id SET DEFAULT nextval('public.refunds_refund_serial_id_seq'::regclass);
-
-
---
--- Name: reserves_close close_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_close ALTER COLUMN close_uuid SET DEFAULT nextval('public.reserves_close_close_uuid_seq'::regclass);
-
-
---
--- Name: reserves_in reserve_in_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_in ALTER COLUMN reserve_in_serial_id SET DEFAULT nextval('public.reserves_in_reserve_in_serial_id_seq'::regclass);
-
-
---
--- Name: reserves_out reserve_out_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out ALTER COLUMN reserve_out_serial_id SET DEFAULT nextval('public.reserves_out_reserve_out_serial_id_seq'::regclass);
-
-
---
--- Name: wire_out wireout_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_out ALTER COLUMN wireout_uuid SET DEFAULT nextval('public.wire_out_wireout_uuid_seq'::regclass);
-
-
---
--- Data for Name: patches; Type: TABLE DATA; Schema: _v; Owner: -
---
-
-COPY _v.patches (patch_name, applied_tsz, applied_by, requires, conflicts) FROM stdin;
-exchange-0001 2020-03-26 19:26:49.92415+01 grothoff {} {}
-auditor-0001 2020-03-26 19:26:58.388906+01 grothoff {} {}
-merchant-0001 2020-03-26 19:27:01.737972+01 grothoff {} {}
-\.
-
-
---
--- Data for Name: aggregation_tracking; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.aggregation_tracking (aggregation_serial_id, deposit_serial_id, wtid_raw) FROM stdin;
-\.
-
-
---
--- Data for Name: app_bankaccount; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.app_bankaccount (is_public, account_no, balance, user_id) FROM stdin;
-t 3 +TESTKUDOS:0 3
-t 4 +TESTKUDOS:0 4
-t 5 +TESTKUDOS:0 5
-t 6 +TESTKUDOS:0 6
-t 7 +TESTKUDOS:0 7
-t 8 +TESTKUDOS:0 8
-f 9 +TESTKUDOS:0 9
-f 10 +TESTKUDOS:0 10
-t 1 -TESTKUDOS:100 1
-f 11 +TESTKUDOS:92 11
-t 2 +TESTKUDOS:8 2
-\.
-
-
---
--- Data for Name: app_banktransaction; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.app_banktransaction (id, amount, subject, date, cancelled, request_uid, credit_account_id, debit_account_id) FROM stdin;
-1 TESTKUDOS:100 Joining bonus 2020-03-26 19:27:05.284133+01 f 245cba88-c96b-44f9-965f-636e193f978a 11 1
-2 TESTKUDOS:8 AVFJ2WDPP5DB6E37XP0R8MDA1C42W1Q94HNQZASW3061WR7C78V0 2020-03-26 19:27:05.384338+01 f d44d7057-e248-4204-bb8e-4d9b0f434ac2 2 11
-\.
-
-
---
--- Data for Name: app_talerwithdrawoperation; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.app_talerwithdrawoperation (withdraw_id, amount, selection_done, confirmation_done, aborted, selected_reserve_pub, selected_exchange_account_id, withdraw_account_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_balance_summary; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_balance_summary (master_pub, denom_balance_val, denom_balance_frac, deposit_fee_balance_val, deposit_fee_balance_frac, melt_fee_balance_val, melt_fee_balance_frac, refund_fee_balance_val, refund_fee_balance_frac, risk_val, risk_frac, loss_val, loss_frac, irregular_recoup_val, irregular_recoup_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_denomination_pending; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_denomination_pending (denom_pub_hash, denom_balance_val, denom_balance_frac, denom_loss_val, denom_loss_frac, num_issued, denom_risk_val, denom_risk_frac, recoup_loss_val, recoup_loss_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_denominations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_denominations (denom_pub_hash, master_pub, valid_from, expire_withdraw, expire_deposit, expire_legal, coin_val, coin_frac, fee_withdraw_val, fee_withdraw_frac, fee_deposit_val, fee_deposit_frac, fee_refresh_val, fee_refresh_frac, fee_refund_val, fee_refund_frac) FROM stdin;
-\\xb378eecaa2af3d60482c35dad80df06074d106d106c4a99c0bf17ff78a1c4932f6c61c244ccfc9bce512b86d2e1029b1d79d5f6fa165e29f200906e8a25a1e7b \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585247210000000 1585852010000000 1648319210000000 1679855210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x0075376d6ad4bce8456a069e7bde945b7f9dd36d73480a147b55c2b06d18274dd666d2fcc28d94fcf6e8c401c26d462e2985a6714a59967886979c77f8ff6418 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585851710000000 1586456510000000 1648923710000000 1680459710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x05a962dc843c0ff39ee8e5fc3762a8c73db6be6e8777ea3eb8b5cb4e3423f7eae716dd7826800bc9778e004eeff63d38e3e3496475c5574a6978660bff8d677e \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1586456210000000 1587061010000000 1649528210000000 1681064210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x6a947bbcb38b820a51840d9cc4f0ffae9a605d045f0661f391cb241120e2bf147dd7954aec09deb4e8b70ca0725a6fe0c6bfd486b8470773e0970d0293eb5b30 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587060710000000 1587665510000000 1650132710000000 1681668710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x5b5d76d5af2cb092c8ff4b46d0d8e1d470bfdf010a456e290b5daf27633401624716c8cad6e767da252c78cf798c9e5bd5b71d34cf9d34788ec2efc8d38db026 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587665210000000 1588270010000000 1650737210000000 1682273210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa901213e3003e1b9010fe14ecec385d940c1062eb55665cabd1b7ecb4bb221b5dc4a504edc0a98341564c526e65f765c0c0339a9c4cacc14047f921974c0bc3a \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588269710000000 1588874510000000 1651341710000000 1682877710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xd29aaeecf5fbf380c2e8fdacd14b7a23933face6bd10f28dcc749b777a399a7860a598f17359fe51a33aff7c4c832ce87e88c2c589933b029929782c2bccfbab \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588874210000000 1589479010000000 1651946210000000 1683482210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xc56922dcda6e711bac6a7c8f3bd1539b2b5a7bfe9332832401e604ac2dd73a44da2209ab79bfa3113f3b37fbf7f5a01523e894f4fe18c1570544b4c83491eb2e \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1589478710000000 1590083510000000 1652550710000000 1684086710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x3755790ee6224cfff6c5db1d30eb3104b44998e1c3af8fbde9e24e8a11fb6ea6b48defbcf9d1503f8f7f58562ea5839e7133a054a6ac27794edb1a82aa9549e1 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590083210000000 1590688010000000 1653155210000000 1684691210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x2a0033891df149843997e3a479c447991380cdc13b804333f3089f25d0025eb810b3488384acf97a43f0b56f234a799febb0933bf2fd4e8b89e4cb8927558cce \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590687710000000 1591292510000000 1653759710000000 1685295710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x71d2de23e29bafed8c56373de5e7b25855b9160a2539b3535d7a9643548f8040dfb496895221925236b764817108a9a4ebca9690de96b7b141b5bdd9e0b9c070 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591292210000000 1591897010000000 1654364210000000 1685900210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xf2b3041ac316e2fa5656ed3035d55de12c428409518854086a1f9167d660e76ffe76a1762d7561011380582dadbca08304b398b0e7a8001c1d9b6d283690b7a1 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591896710000000 1592501510000000 1654968710000000 1686504710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x7cce87953b40814584186cf6d8a027decdeaf0abc91e3a79d5881b9706924e689e652005414c1ae373ab06482106a09ea6ae0a490a0ed665fc0f78cd38241292 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1592501210000000 1593106010000000 1655573210000000 1687109210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa9558be38da8ff07c79fdc8a2d49e50ccc1ce8d2326e58fca362b8f28c46e140ddd0aa94d6db59a384f28ba935825949d565f0be8967fd2d052167d3efcf23df \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593105710000000 1593710510000000 1656177710000000 1687713710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x26e839b7a0dbf7a50786189671b5f91235fe012177dded0a65073b946cd2012e0887c8d659191c8eb3ecd7ec4a9881bd1af4d0ce834c20bceba82ba222429d75 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593710210000000 1594315010000000 1656782210000000 1688318210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x93521fafe0753b9ac944df2b5088764539c41564d4c8659b5b64b05d1673526fb51035aa5cf4fcf3a70797435e205ad15b8b801d4822579c209828f91c34950c \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594314710000000 1594919510000000 1657386710000000 1688922710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x611216fe1f0d351925360b2e5f504e6ff857d7d90ab0731d7e121223eafac584c35e1f587980dbb950a83ef93f46e85da0b35973607a3bb417c4e9ba105643d8 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594919210000000 1595524010000000 1657991210000000 1689527210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x1cd5380661be05eee2aa2b9a7885eacb8c70fbadcb3a0f9f5f5190dfd699f8f3067fc8c9453b7037fe6de9bc84b76d3018e4e9bc8e535b36b736aeb6e5e15ca7 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1595523710000000 1596128510000000 1658595710000000 1690131710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xd6a0f2c0e038bc72508e4024ebdf3b4ac6666a9494748149210a13e17cabd8b5c1386ea458ecd3a0af132791a7e4ef4919c86b2fed5791d03e12924c1089a5eb \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596128210000000 1596733010000000 1659200210000000 1690736210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa405466aa03b97274d8f093b0477c616a58532684a25de6153fd19affe58caa58d00768dc2c8b6ce514a80c4ed77e6d0385d9648039d39639d6631dec2766f4c \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596732710000000 1597337510000000 1659804710000000 1691340710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x064a5c17b5a90e46f58604436d34a2660cf06f383e4e3a449d10e98d726b231c399688bd1b216798091f0d1e2ce1325d84995eb04dc71ce5b1ea75c9f77847e4 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597337210000000 1597942010000000 1660409210000000 1691945210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x48ed78d94d51217716a001c9364ba73d6f418f1783455ae18815fe288b7f25494ad56b2cfebbee2005e57a6ecd2352ca0c8101628ef8072a5946b2cc0c93051e \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597941710000000 1598546510000000 1661013710000000 1692549710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x63dac59cab8c7778c124ce13ec041a319a198c583c42604bce00bfd294f8b65d8cd382dab81f1e19c0500da84c41847a8a3d2f6362662d748d83054e29ce83eb \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1598546210000000 1599151010000000 1661618210000000 1693154210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x151533270a1b5cc2bc2257bf80b55700a8229aaee5153a7cbbfae69a60221a6dc9a0b6580fa57389a3e542d5e16070a7b1b1106b4b534d0a7007f500619f51b4 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599150710000000 1599755510000000 1662222710000000 1693758710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x6ea4274729ad9eafc072e4ad474ef802fdaa1584341adfef43043e369187fd63796d6787a4025464ea07e428b2316c60b4116da2068e2af9a6749e63586b1809 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599755210000000 1600360010000000 1662827210000000 1694363210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x7b4b20b04b041348520fe2c5c9a449d3ef883ebecaeb5a60f03971a911f51cf1e281c328785b0fb1ee28c521ea195969c259ff54532e2ab83c3feea5f8a5c4d2 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600359710000000 1600964510000000 1663431710000000 1694967710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xd2e06593f7acfc46946e23929994d69ed4316a2ea9eae0acbba7584224358d444fc2c8dc74a0174253362d976b647eea3353c678c0188901731cd35c037a5172 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600964210000000 1601569010000000 1664036210000000 1695572210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x5325201602106788cec43e0b1ca568904591b32a528a563ef959a5e97fab8a51618667ba28e18821976bb697a3adb1d814e33bdabd37db0c21640e63bbea9d4c \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1601568710000000 1602173510000000 1664640710000000 1696176710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xe27dc2dc8aca4fc4c55ed955fedde2c35f1f1835eaa8b5e8142fac5287516038c24deae42e9ab8602905c6919ac8fb9727822bdb2962c9506ce4ab37864f9ca1 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602173210000000 1602778010000000 1665245210000000 1696781210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x1264a2f5b5c378ee5837971f9e5a9a4338a47eaf72961bacb241e907cb891ae4766aab8369bab91b84b39b31c239a484a2febcce7b551d1da5c1f2e1b9980f15 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602777710000000 1603382510000000 1665849710000000 1697385710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x454b79a65edad8b2091105870ac85fe522e9230e03a1f5b9049e5d03a2577146287ffa632df54ce33d361b920102af8203f2f2123133c6aa6809975e11226d2c \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603382210000000 1603987010000000 1666454210000000 1697990210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x9dc566a04f9a3a41870aaed2a04663f4fe8fd5d7b494a41d73d5045fcf180bf97facb61e98fcc6eaf9b688af96fc4abf4a58b6b3d8b42b4ca311b8d8c02650b0 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603986710000000 1604591510000000 1667058710000000 1698594710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa1e69176cc4061fb32a1385394e4ccee48d1549914129ccc18ed538f3a5c7c9fa2673b953c681cf3cb35b55740256937c3eaaa440cba6d7c8dd498acf50d9ef3 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1604591210000000 1605196010000000 1667663210000000 1699199210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x770c748e581e8ed7edea219a09ecb0018a2015d5a8017f746511950f5da1c4ad1ded37a5f054bb1b0b8902cbd3767f9b0cf4e0a8e5a8f29bf555eb89dfe78fad \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585247210000000 1585852010000000 1648319210000000 1679855210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x478fe9f5af3431c4d3eb0041181970ebfc4ef63e15f712f3f0882bf6ad45c14698ac62d1c58b687b3811de87e2a3f6bad2a04382e70b98e225680285fad80678 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585851710000000 1586456510000000 1648923710000000 1680459710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xd24ed264c3aed6dc5e0b038789f8b96c7a39e5afad1a6d1152e7522dce16cb07057c685ebb3ec13c4d813e7526934a0bedae3a3f69ff5ed67b17ce19c3900d07 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1586456210000000 1587061010000000 1649528210000000 1681064210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x423cb1aa741ab0a5237c1a1e96d8564a5cd1a4e1cc092690215c66d33c2ac3bc55d265997f49cb54405ba72c621465571b26dc781c6fc224ebdd418042540ffe \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587060710000000 1587665510000000 1650132710000000 1681668710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xd628b203825e5c1d6f06fc772be687e4b1236e8525660bebc08612c7d985f1f55c2d01e32f26a994ee775a5d425c1c02d8c75ecc00fc90ede6f83fea0a5e090d \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587665210000000 1588270010000000 1650737210000000 1682273210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xea07a69a2e207613ee0342ac42375a512a5de80c2de1fe45b8e02fc088afc54e57800714896492d7592f1760ce39a52b2e993f188ae22af2b581338541df9e6d \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588269710000000 1588874510000000 1651341710000000 1682877710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xb0e17ff4a5cbef557de82ff930b13011bd3f3ec7c9ae048146acacaccfabef30fd1dfe9c431391293ba9c6f2ef4fcc605d0c2f2b6037fe50d064e16ef46725fa \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588874210000000 1589479010000000 1651946210000000 1683482210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xaa603aecf389058bad8639cb59cc294c2e07810582bc742d9464b9b1ce1db3c34c6acd1d6ddc15b392e59ea35c1950134adc777d0d7f1f374783d4d671c250c9 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1589478710000000 1590083510000000 1652550710000000 1684086710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xea6820ab6c18df81c1e83382b22e376a1f0e32ca3cf44b698f2d045e0211d61b91ad2eb23223038fae7ead0ad8151569f7d4ab19d457342fb1092ddb8acb0448 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590083210000000 1590688010000000 1653155210000000 1684691210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x9031081bcbc575982c7ef830a3c295e8bf53ae135da5a0a36dd0a8798b9e80730f0dfafae43181eecdcd7f4b5f2e8d92461e9285d3344ede49e6b8f0abbdf990 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590687710000000 1591292510000000 1653759710000000 1685295710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xc9c8855f6bf389672a04ffa463cb17a978cd60de6fff056c4babe6e4714405789a117e83f7d16ac0994f12399a18821f02518ffc24b69dc54e1b449d727244af \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591292210000000 1591897010000000 1654364210000000 1685900210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x34f12718d044564fb64907fd6da488af13745f71fa519a2241f2ad66173e328c25ab029acb0614d9f455517f2a5ea151b503ac40d765d8739e32611f938357fa \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591896710000000 1592501510000000 1654968710000000 1686504710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xd0b0025e5f0fe1b3b63b82dd8d3622f9bf597ca75ee456db436fb9b70feab75b2addf53d418da957197cb831cb9f24815f9260b13c58a21598b17164e99a6db9 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1592501210000000 1593106010000000 1655573210000000 1687109210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xa201d1f554ed771eacff448c58b847a075cf2e26067dbfe4341ed0d7cac664ed923f67736459c56e2e35ea662eb9cf6b3cd72ef9de03b423513c1553a004eda6 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593105710000000 1593710510000000 1656177710000000 1687713710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x8418bf8f77a622bf35ada66becc880478e3d21636e7bc67eb16583f06e384f6d36c54a5b3308723fbaf976bbbee491b89864db5849e0143c3f2ec32b8a4e7904 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593710210000000 1594315010000000 1656782210000000 1688318210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xf203968c6583ca909214cdfcedee187f3eeb9439ec533326304146787f4b7de96c14eef9d0e76741901125b166be7476143886790e6affdf8d3ce03090da6ba8 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594314710000000 1594919510000000 1657386710000000 1688922710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xec3dcd118571b36d0d66a591fc847e3e631b48714c8aba050eba1320212c084b99aed3ca443a8bc0962fa0022da729eb122a26a4fd49b398d57c948346390d3c \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594919210000000 1595524010000000 1657991210000000 1689527210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xb2533b2ef6147c934a80bb385287ddd0a56db64abcb50e017c9cf292727ef48b9fd71e2ca5a655b1260c8d07bb89612c76faaba2a882c9fc32f06f813c688704 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1595523710000000 1596128510000000 1658595710000000 1690131710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x5c42d74048f0f95be8cbdd401f5c52404c1c841218797be83c623b592355b3dc5c8345e16c1057e02052555bf341fb79236a27a189f90b9f6fd01b0e5480ae2c \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596128210000000 1596733010000000 1659200210000000 1690736210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xfa4c794cd00de8256058a2f2fe0db828dce524248d2948659c9dd954baa2664d82a291a2ec9d29a0402822a6039723ae60b35d40ad23098ff35f1501eee05d2e \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596732710000000 1597337510000000 1659804710000000 1691340710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xc2c67403edabb6712ffd28584485f924ed8bfa9bbe8fc92e79c0fe06e29a0ea908894601bc67f483414ceab1c0e2014ad072fa9d50d039a4f15996b0cf7bd322 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597337210000000 1597942010000000 1660409210000000 1691945210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x1265c56f0ff9d983edf4116e74381bf08002d13b16450395bedf89b768c41509cbb84cea3c37383ede857bd2e7822db7201b0bb8009937fbee558087cc5606c0 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597941710000000 1598546510000000 1661013710000000 1692549710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x48e61492b0edb65f7f30404ed9bbd1df09e6d6b02946663480b56b99dd6501f0d8c3cf1f4836a67d8040af93029ad73ce05015235c07c26b3347a24c813f8994 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1598546210000000 1599151010000000 1661618210000000 1693154210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xe41e7a792cce6eb2fadc32efbfa1668b09e6835b4dd36573fddf1169b738adb48abb9f265bfa4442f58690d8b88cdcf553ee57356f1bde96c98aaadd0004b45b \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599150710000000 1599755510000000 1662222710000000 1693758710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x932ff95ddb379cb1b5b4d30c2306072cb558a845ff69e396de62d8ccb9deeda2ed96a7c1e624f9aab477289533cf07b4642a3bdc06f5410cc3aab2f8b8aa00fe \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599755210000000 1600360010000000 1662827210000000 1694363210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xa892380640102dbfdb06bf698d3985c890ef7879b885a999a5021a5c2631217592861d5cf27705f7035ec16e1b284dc5d1add6a5e5ea4192358ccf6951b9fbf6 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600359710000000 1600964510000000 1663431710000000 1694967710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xc4b2f765266e0c216ca5a0ee39d9f3268d4bf529c808b890a4a3355a1f7a9e962df89c835dd3ccddfb20879281aad895833d9292fd7b4394a3c8d0f0b1552d69 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600964210000000 1601569010000000 1664036210000000 1695572210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xae96c3dda1bc040dd9f30f16d9118e16237a56decc1badf5e918805f3959dbcdcc206190ce9a81aa06be999899c187f86a112a76c5331eb8635537a0d1597a37 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1601568710000000 1602173510000000 1664640710000000 1696176710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xe9652c2e421600735f4b580ff9d71ea40ebbe54606411e484decec3b61abf37d7a921d9320a69f1806652b4c7e9c0f5d7e5c4154015f7ca2fbee3214f8d0f875 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602173210000000 1602778010000000 1665245210000000 1696781210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x177a0deae7b9263287cd88fea9dff0667fefadced9558b7374215c0abd40b7f50bc2fb6c488f198b0efe8dfbf96ccc3273ccdae095e7909f4764717531cf1e04 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602777710000000 1603382510000000 1665849710000000 1697385710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xe9da8f472d16740cea65dab706ef151172a45ce8c48b92196b712277d12c9ad43089503c7bc2e2c98daa64d20969fc082f924c6b19eaec3e8e4aed1f9df273ff \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603382210000000 1603987010000000 1666454210000000 1697990210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x83bb4ce6e18f7da1119c219e29fa91ab9d22de9a1612154b7e1c5841f88264310025d0928586feb554229f902a105e3bb6636f20e6c42f7e41529a78594d5e00 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603986710000000 1604591510000000 1667058710000000 1698594710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xf2435f6d2b47158c77f3c7057fdc9bb41c30b2937f84c5c6b6cdd3ddfae22bf88b1e5f86dae495349f2bea4a43914e82dac390c23553d5d1b256d01136aece17 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1604591210000000 1605196010000000 1667663210000000 1699199210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xb2b398d560e595d6d879842cd209cc84c924a57538ffe918aa6f3107492495740d2dadd63f34109c3a19caa665eabb781359cac5c496e56a36bfc6926d3a45a8 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585247210000000 1585852010000000 1648319210000000 1679855210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x22104ae3f0d6f6e6579cb6c7e00f4ce253150e2fecb6610d64f5a0643b628c38f6bf26565b91221a9d53b2af71be535bbde0ab3ef0266302668b4683708dc28c \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585851710000000 1586456510000000 1648923710000000 1680459710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x76697f4126c154b222b4c8e3f87e1c4aebd70a13aa1b73817e97bac1a03af9f8f6eb6008e0fb5fd0a9c8d147f96e3a6d48abe0f322969a8435081ddfbc8c56d7 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1586456210000000 1587061010000000 1649528210000000 1681064210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x83a1afd9a1b99a4d7824bb878db71db8b8718daab4429c879f55edda6c7050ed8efd60ef95b84af213dbdbbbe283bb84fb32817a8bd2476de1d563a7ae3940f7 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587060710000000 1587665510000000 1650132710000000 1681668710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xb08a0f5ed1b5859b7fdf907c5cfa6439e79f21ef48dfbf5c93b7e7ba10262c0652e5b4ea64dca448eda615d06b851d85773a6a4d94e2fa210c7dc9e6730f222f \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587665210000000 1588270010000000 1650737210000000 1682273210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xabfb9c9fcaec888f4c41cc0caa0697e857c7438ffd5b44ad31c6a03706e9f507e47b84042a40b40bbfce9ac3086337f1f93a3dd6c73b81b5dbe6b8008d0b195d \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588269710000000 1588874510000000 1651341710000000 1682877710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xd7b3a005ecef0e48d0c0c27e41ca25f45bfbb4fb379544bc7294645a82afd617380b61409830ebfca9f4b88937ea01cfbac87ebd7dd4c00923d53a899782d5a0 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588874210000000 1589479010000000 1651946210000000 1683482210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x736e42a1f4bb9a49045220bf4024995bcd982acfc61e23f515ac426f922fa9b29d757095b891a0e25b8e225c227046d744a00bb26172d14e0f1a1fb0d00f9daf \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1589478710000000 1590083510000000 1652550710000000 1684086710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xc3765433d6e727f0b51d6b2e10c9bc10a8e6beca70e57904bd130ca6d72c72015f6e31ee9e66706fe978576b4e0e6d2f956f775cfada9d1a419cdfad6aec15d9 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590083210000000 1590688010000000 1653155210000000 1684691210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x77f3cda2f035ae31416334c127cc558f2fe9ac11a828693d2dd58369cc372bc9529bb59abaeaa9330f681aff4e954af7f71f4af57c61a2a1d0edccf64808d072 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590687710000000 1591292510000000 1653759710000000 1685295710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x68b9a7b30a6319213df3410e1ba3f51707435d5cbf0a6edcc56c8954a5756d61f1e600be8a4f39fe6424e184995c1dda8515fe26ba5e448a8b833e827bf7a831 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591292210000000 1591897010000000 1654364210000000 1685900210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x1df3e99efd724a6c0dc0a2823ac9166edbcf1bd7a6cb0413866babc471686c59097c77538a86207890150aedd4185f4510e6818e4c750746101540e32b715b05 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591896710000000 1592501510000000 1654968710000000 1686504710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x5f05d950f1890fccb8485c90bb3dd12593a54a28214db64eb38cb8717b68adc26156f8487ae3559c289047fd628688ac6f5631282c4e5c388842291bb46f3824 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1592501210000000 1593106010000000 1655573210000000 1687109210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xe2260b2f3dccab0bcb338cc2882486ec3d820dd90bce313c98fa1a585373c12258ec5e6b03cbb1a3ad3a9f05e37e93f84f101b917a0dcacc56b0556e35fd4370 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593105710000000 1593710510000000 1656177710000000 1687713710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x9940f65d34ead9955365a9a662346df1df231f59b35d11647f1dc539978c123984f76b40d72c82c8959eaa4a4c43718d1a9dde9cd1b355f4516373c1d71ca80d \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593710210000000 1594315010000000 1656782210000000 1688318210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xaafbd0f00ba4171f53f308074294c05c4a89d70c82bfb22e3bf301d5a612d70492a75d0d7f06a2bce5375d826bcebc537faae1c904f51e84c8fe02de79b9f1ea \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594314710000000 1594919510000000 1657386710000000 1688922710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x9f7e3c4dfad9eab80fdaadf26b544e5dd077a53bec8d16299774b0414b2968f699fd109f5890c286774d40adcc2c0058282dd91c1044e95a14fb0952a68f92cf \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594919210000000 1595524010000000 1657991210000000 1689527210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x657bd65e4f96a0f101b424398e3027c407b429c748304835b2785e8a102cc0dc7eb0b64a4b4cd334114ce8b8d15ca7a54144fd93438b842da690e2e16fc00a0f \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1595523710000000 1596128510000000 1658595710000000 1690131710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xabf5ac2d1d162a67e34eb3c18495ef0f10cf1fe91ac95034ae74573a4917a8e3fac9df84bf7627fedbf6635a3ee9b63c2dac558272f4f33692ba7553f2460b23 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596128210000000 1596733010000000 1659200210000000 1690736210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xb18c75b760c19d74eaf60640c27cdca32e617e79fa375af367253fd5d82de0ffcb2cac275e10fa34414c7e487c022e1cc8cc0a4ff1b6aea6699044db55a2d6e8 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596732710000000 1597337510000000 1659804710000000 1691340710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xc8f4067b6df97c0f776882147e3050ee7fe6515d36bb3543f64d86a421ffc829e6223d7574140a69dbcaf1d9c2adfac04c20eaee5f9fda7647e3ade21fc89d58 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597337210000000 1597942010000000 1660409210000000 1691945210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x59cdf5b4960217fb40fb89197ff524d7d07d85ea9e8a86ff2df1de83168834b6718e40d0311781d494418d60d4da3c2c74993e658f4810fe29c921c79e70abea \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597941710000000 1598546510000000 1661013710000000 1692549710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x0cd102d8a79598cadba8953351ca90d4e0fc6dfd400c28a8dc5a13a40d22baa877ebaa8b1f5783d37f7ce24a4da699a11299ce7d0243177969c7478e9ab0e9c3 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1598546210000000 1599151010000000 1661618210000000 1693154210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x55132d223a1cc81ed6d1520e1479781932eae0786a8d966204533d8ac2acf6bda3bfc29f4e2c638e58e1403dff7a755e90cc82fc87ea19b7230c7fe8d29f07f2 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599150710000000 1599755510000000 1662222710000000 1693758710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xd712b1d48df298318617323fcadd4932df3972f52d8a0f715e185ab1b35138265e752ea28a2391aa65717e0cf99f18167c397266b3daa7142675dc12920f26a0 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599755210000000 1600360010000000 1662827210000000 1694363210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x64b9bb4270e35f24aa1ff42fa24e37f091d6897743ef2a33b45de3a901cfa1358fe1c8b0b5c8976a77e2cbd70b3fcf1656658ceec5498c64c2f3e441232d6bf6 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600359710000000 1600964510000000 1663431710000000 1694967710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xea0a144ba2b13f631d017472caf6126fa74a406b15ef655d21a0afb990d73af47295af21864089594cddb95b929c3e7d573e1fca7b46cdaf0d5a947638b4e21e \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600964210000000 1601569010000000 1664036210000000 1695572210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x98a8e152432ccca7f72f9e1ce4161822df6b989dae89f1fb84f81ea01b97d8badd8d9cda5fbde90fa28f048eee5f72525e793edbd1172867f1d3b45d2d12b5eb \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1601568710000000 1602173510000000 1664640710000000 1696176710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xc56873a3aaa4f1c03b07956ba80407614f95cef4a964080dbff67850fde6e45b51ef838e1fd118d6a2220fc6fa7d66581a33663cb77417b240b3ec5e5dd379bd \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602173210000000 1602778010000000 1665245210000000 1696781210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x9600561209bfd7c43d2342a93f5f2a33b249d1edec0d8494e93731fef49c9111bb03a10ceb809566187fd3664ce6ee38eacb489e8537a74759c99d137082e57f \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602777710000000 1603382510000000 1665849710000000 1697385710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x546e71f08f7965f19aed709f2e1d446d175caa3665353a26587032dc1a20d09d7fba8244bfe5257c9701b0a5325f658945b4e45a3f8a396b40c8710fdd5141de \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603382210000000 1603987010000000 1666454210000000 1697990210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa59076edbc3104a91b98a27be3f166e733b3f412041c0594b27927f28a3e9ce8ccf495d341627a0372dcca6ab90d22743d7a9c5b47ce4c1240c49f0a870390fb \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603986710000000 1604591510000000 1667058710000000 1698594710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xdb58657fba4f2bd0434188927b298e64b3d6a850c8cee9d6361dbcf1551a4fee9476036aca0b125dae93b76b4f18c4a1714e122c994d0d894bb5399ee88c99fb \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1604591210000000 1605196010000000 1667663210000000 1699199210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x133ec98fa5d8efee9e3db8305dbf2710c92fead62b0e5498cd3b6327a08bcda6829d723b7030edfaefd254544ee98919cb79ae488d288f0b44e2398b47543cf1 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585247210000000 1585852010000000 1648319210000000 1679855210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x596dd70417dea3f96b72decf73ebbee411295403914758bcc5d562d1232ad6aa8b14857b58e1cf9ae9b1535ffe4fbdeb4e9b6d67f0739173b64d9bfab00c1f0c \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585851710000000 1586456510000000 1648923710000000 1680459710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x5557667cab72b0e107bcf82f6afca038342e4dc9a1c9ad16e03dc7f8b087ef861fdfaf39c755ca5f63323740dd13edbbb844a72769442659736031ab8dc9f825 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1586456210000000 1587061010000000 1649528210000000 1681064210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x117efb8a2696ee71b862baf5718e5e897aa7b454c0b71731a8c51e174d6f47f0fb5f2b3e863b62242ab49a94f4a8c3b6adcc4c33e1f4f8eba0d0f2ab121a136d \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587060710000000 1587665510000000 1650132710000000 1681668710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x8ba1aedd039e8a27c23c673551ec9036efeed66998fb3371591db8ffd4a3782ef69b6a62969744c9c2052c40cb4600a8a1adc5bf731d0a4f76bb882fb7f20882 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587665210000000 1588270010000000 1650737210000000 1682273210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xf67f29265d2ff217d3b10a405f10948095527a1130bd9940b449ef76bb911c4fccb4b2e3d16d7e032f91a5eb71ab1d4a67df4430f69707ac71b5d39b30659f3b \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588269710000000 1588874510000000 1651341710000000 1682877710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x4e8ce661c04af3c5b33513911ecbfa3d939740d19c73e1a988633398cb0ed4cdf038c42bfa3ee51b1161ac21004c42eabe557d045ee33453c9c9bfeebeaaf4ff \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588874210000000 1589479010000000 1651946210000000 1683482210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x86902375dc166eb59ad208aa55a5f6b1fd98e00820ae55c82dff32b9cb24691432b12e6fe3132e2175d8de54a95ac4057aa709eb31772a40bdb66aabb1b2d66f \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1589478710000000 1590083510000000 1652550710000000 1684086710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xc98410a63b5283e3fd3746f4f1d40429e185eb675a490aea0355d459d8148cdf07414dd52086902b048bb38c41d9f2e062d66c8a5215cb78c1638bfae90e2eb4 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590083210000000 1590688010000000 1653155210000000 1684691210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xa11b8c9834c5f253ac0d6e7887de20cbb41f0085b2f7e35b50ff8795c062865534f17b131199845cfb025335933aec53a25c93956bc52362bcdaa51d33a88d99 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590687710000000 1591292510000000 1653759710000000 1685295710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x50740bd39f5bccab5f3d5d5d0450a3af3d5100e061a2fa8101a843704511e51d9f3049b060e68a5fca43e954a553d64ac575418cfe28380793db72e7a85d5459 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591292210000000 1591897010000000 1654364210000000 1685900210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xd8f913b6171b665b34523910b161f296d7ad1f04a20291a81c295c2929b4cf8c08ffdbef97042e263d93b82ee12002caaab69d2692c15fed75a271075182a242 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591896710000000 1592501510000000 1654968710000000 1686504710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x143ca07a3017510fed894746142d6072e1d36266c75ebde7f9ced543ab224acb252e02ebd9900eddee6f804f9991c44eb6740605ede5aa81c7d19239301dc03a \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1592501210000000 1593106010000000 1655573210000000 1687109210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x2e0a60b017910b147f18237c9e69a9a88f541d22426c3260589c010cdb9213a264353c43d507d17a0f7574672ce0d4d3404172c3864702a9be351bbd46542803 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593105710000000 1593710510000000 1656177710000000 1687713710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x68cd82c929fec87a096fbfc4f6da596342ed86a6f8e90862f8e459a14d26849f811f28e9898c566c90748360190632a00539bb9b1dbc12dced2726034c60870f \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593710210000000 1594315010000000 1656782210000000 1688318210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x20d3f42ca922c4f58ed26eec2f86c77495d9b48d4f0c04f5f1ec99d1f4fb98839cec4b6edced97bc8fa7f12276e6b9736df2b41fe94d27e314fc2587acf393d7 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594314710000000 1594919510000000 1657386710000000 1688922710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x5985c9aa64ca1f29c6faf3bc713c948138f2e595572337241e16c4b324ce9e10e02742c1250e5ae1e4bcbe6bc00b765e40afeb698d3fa61350bb3e8841de11a5 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594919210000000 1595524010000000 1657991210000000 1689527210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x4b33d18ffbbe7ee1aa0ef7644d28c80e1e2359a47ccf8b158c6bd04bfc8dd29ce52f7426bb3a788d6e37d52a840425f4dd7bc5c865745078577202c64f52fc9e \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1595523710000000 1596128510000000 1658595710000000 1690131710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xf1adc9150816414eb4f52128618355d9d5bd91ca8912d63f8e7a64ca34b1d7da69d32601866906589eea7d188782eaf246d333dc6e2bd19bf69a9a7b7483fa78 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596128210000000 1596733010000000 1659200210000000 1690736210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x0fa5cfdb816c6138bc62a113cef662541dd8c523b05b31c54d5f81367d0a31377688115e683d966ad0210b0f48a8105dcb0bf5fd46ff219be378e551db102923 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596732710000000 1597337510000000 1659804710000000 1691340710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x703b416c294aac9b4090aa92c3a1f109334704fc40cbde0a34554e5bc9f4211eabd39733a6cf4cb0f262ae18df506cb9dcba4ecb4b4e0cc1a8ca561b3583e313 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597337210000000 1597942010000000 1660409210000000 1691945210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xa3d873e455e18af938d24cf662c6bf37a9af858ebe4bf47a11c96dbff473465c7ed15177b14b7838aac4903c6855adedbc8a66815f0544c86f371a89ee41c0ac \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597941710000000 1598546510000000 1661013710000000 1692549710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xb099cc5eef6d7321f18012f657a17df19416c8a618dea525df502a1ee6fae657a50256c78e6075e9377d3a0e02ab38fde36ed232b2af9ac77ca6930e81de17e0 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1598546210000000 1599151010000000 1661618210000000 1693154210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x61855979e9803991bb836485fd0888797888dd56e9e8f07ecbba3ce0669373256f44c6815c66c9bd71975571790a8dbb2be8a79b614c1a07c417f48ac0cb5bb9 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599150710000000 1599755510000000 1662222710000000 1693758710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x7ca5c33be6ea670eda1354da45efdc57b6c8399f6b1e610654d57237e9b294fa7c15f036ea43ce335307848dc0603b9b5c706f597649013d212bdb0313bea037 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599755210000000 1600360010000000 1662827210000000 1694363210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xc583157b04cac89a2bfe8a2320f9e01ce123d8604aa4f133bd9cca72a725947707c383ffd6662b88bb5d1267e4aab6b4cacf41a29fce49dec4f34d97a8ddcef0 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600359710000000 1600964510000000 1663431710000000 1694967710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x08e39ccae406332695a9cfc04d20d3f733d1eda5ffb1d1771477aa6e2bce440ea646f5f3829a43129ac991a0323c6dc6a2717fa72683c338cd330824ee9bc0e1 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600964210000000 1601569010000000 1664036210000000 1695572210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x4333acaa5662f939afd4a38290ac2a39bde7ebd680fee28a047815e6c94c9c54291587af56c9be0e8bf0b96edd09cfa3cc57425e88b333649755ee53b4403c6a \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1601568710000000 1602173510000000 1664640710000000 1696176710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x774aeed719dcbb168d5257834a6419777a8e339a6979d9a93dde3385d76a97c2a5a0478e8ad0ac57164e27c9b5c8bc7eae65f9e6bb9fcd3022dcc03d53f5c885 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602173210000000 1602778010000000 1665245210000000 1696781210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x05e02d74bbbf5f3b4192e9fb55c67f37a439da3ff07d6e849fa6d7b7d70a103338a7b61699e253a3a556388a7ec81fe48addb0ca7fba9230e79edae128c0e12e \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602777710000000 1603382510000000 1665849710000000 1697385710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x85fa8e533d7f8ccb17c8f7992ff8ac2d993eb781003655bbf38455c5ccee44768244f6d87020ab1ef14041d89610f318a7f2cf6a912d97ef97cd29c742d1acb1 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603382210000000 1603987010000000 1666454210000000 1697990210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xc85238fd83db5be800a69bcf2d6d4bddc01df075760a83ac9face592fd257ce738d91159bcd66825ce1d661e99d4bc5e2858ec01771c9c2d911d9afff9ff721b \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603986710000000 1604591510000000 1667058710000000 1698594710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xca0a05f0d9d31709f15a0c19f114dde9103e16907cf42d72232d7d528f4d86f8ce3d05d502ba062889b3807f7326df6ec5c75823bb10bdaaa49765ca59dadd7d \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1604591210000000 1605196010000000 1667663210000000 1699199210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xe0726684567b8f6e972ae98a1c743d83344292e41f48d2696272bbe7d7cf28e4fe4ca576cefee21dcf4d05dc1927dbed383bd6383af4f6919c6185cb86c29fc2 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585247210000000 1585852010000000 1648319210000000 1679855210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x5e3fff3c1f92845ba0dcf1651b7ec20c0da933572c2ce961c0b7944ee4ee3fa13562d8fe5d799f8eb8e528cb431033634fe73318ccec18db2246ad6bafa0c762 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585851710000000 1586456510000000 1648923710000000 1680459710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xf47266abe3cb33716f3b864e07b033f66af6c7be58a6364c28d6c507b7ff70acf649b9a7396cffbdf77bb5bd7af9196c5299191dedd5c9a6ff04073be2ddb945 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1586456210000000 1587061010000000 1649528210000000 1681064210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x85ac0b83d4e7d7d59d97c69057d729e8eff4493677e7ca61c55593b87f647fd7fda8d5b2712848a1d4df31ff2f27f60815078aa5f2e59e9db57ea625cbb65bd0 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587060710000000 1587665510000000 1650132710000000 1681668710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x1f9600cf0bdf811771933679fd1dde21429a0c73f961960af88cd8d1d53a5ffdfaaf5f31cc5f7b28846298ef0a84de0f3abf8e9389814bcb913e21c630ee3cc5 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587665210000000 1588270010000000 1650737210000000 1682273210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x6fb5795135c3e9dda4c1f9726330f5c2133c1f10d75784c9acf91e2f5c480a50bcb7c3af863a2ce41df9aa20ec99eb6f9ca328ba3ce70718d4bbde98467f9077 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588269710000000 1588874510000000 1651341710000000 1682877710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x0c85da610e0f8ff8ecf6aa169d4a5546125f86d16cf6540852ed508585e62f80521248f909a5d10202e4721be516f6443f0343b4cd77062193f0ecff1fa688c2 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588874210000000 1589479010000000 1651946210000000 1683482210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x362cfb8a2e90e503a2c6737a72410fea45baf4811c9326fb67b11210c79646b209c45c30b0d36c8858e10bb0ba574fecbe6cca19539b23abc5e2e5c32ad721f6 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1589478710000000 1590083510000000 1652550710000000 1684086710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xe5c9a96e3c86cbecd1dbb3115134af53d6eabae04febb9b16bbed023f88d6595b31c96d76614ea1d9fa6cd41d33b69e01a81c8e72189eaceeefea14fcc85ae9b \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590083210000000 1590688010000000 1653155210000000 1684691210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x9e387402e2c0ed7310ba9fc821fc8bfeb97c7cdc1c745f5bfb9b101c8aefff575aaceaddde7a4a73d913da0fd3cb3b713355d1e1d9b5591f0cee4960ee49f082 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590687710000000 1591292510000000 1653759710000000 1685295710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x506bcf4cd7e2d1d680bbefae1f3081a1fbe16d06e053cade3eaddeeeee4aca1cd2da1937e23ac1f02d4198ae01e0fc5262da41b38b30d5c1425e52c48f3d8670 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591292210000000 1591897010000000 1654364210000000 1685900210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x8ec5d99117b5d08d9877621063cb351bbf1a75981c0848e9ee3bfb5d2f33d2d657cf5dbf3829e504137e00dc7f10c5d47adb48262c1bcecb09af9e6e836a21e2 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591896710000000 1592501510000000 1654968710000000 1686504710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x866eed1ec1a323f1bfdcce39d5713711c6c875025dd58d4104aba4274ce9091a55b2c3e25076bd2ccf911a8cc25227c9d4070087d187701554167d7c37192da2 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1592501210000000 1593106010000000 1655573210000000 1687109210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xa3af6b7ba5e61e2bb741deb79693b575f4929b12acaeb6842134067c0640ec26da7ae11f88e37fcbd3bded0347147b7d1689f4deba2f9e60507fa9568b24c1d7 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593105710000000 1593710510000000 1656177710000000 1687713710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xd93143bed0c88b7620b4eaa41df90750dc6bd54e7401820e063bedf7061c89dbda2336011360f70888a4934f2c7c3fa03ded0e959eadc46ab90faa62e0faf562 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593710210000000 1594315010000000 1656782210000000 1688318210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x30d2aed6b932d5ecbbfd6af28463c1ecfe7c178d93c78c3a77fbb50ba939c3623fe83d61d8a980b7ef992f976d4700cca5b8a9dbfe69d097639bbdd1dc3e0bcd \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594314710000000 1594919510000000 1657386710000000 1688922710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xe3668654fb8f1a3f4f03716ef07f5c99c3ef78f881e2cf2eed90bc0b30375d7590b405dac4afe600f79f3e4ae46c99316aeb1bcc35b6690159bd2105e6c2c0c6 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594919210000000 1595524010000000 1657991210000000 1689527210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x7ad08082cfe946f44c267270fc18ae6ed8415e069a6b5a4d2f74e0aa916f3c444488d3576c1d2cf9b32b8cd2448ef0bf15b5c38bc09266c689a3142c87b2565e \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1595523710000000 1596128510000000 1658595710000000 1690131710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xdd2db79a4569f54a04a721054dddaee82002e56fae734096a1fbd7d0d5c45d42f0a48b0073caef24e484bae22e987e488bfdf6216175c11a0a6700b6ea484b1f \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596128210000000 1596733010000000 1659200210000000 1690736210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x06967749c6890b49eeea509129a0a5da20afcf1d521bfb132bec60505a35d0e3ef9f37db07be5fec157ed39c0358ef8611e76517b3af3ad5bd9a4b6c7186713c \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596732710000000 1597337510000000 1659804710000000 1691340710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xa30eade17313fb80b1a774cd5879408f565ba6681952314812dcb9401df2f7704d9e6318c1dd11d19cb766df6fd6c8d7a5737f84c4bd4013fa5166ad3d15f01e \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597337210000000 1597942010000000 1660409210000000 1691945210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x684f3ed9abd9e696f61267a93b9e3f990e29980838ae3a17fca5db3c1d8c81a1dfb3e35b16412824526161110d047c57e066b317715d2b1ca73a21d8cbcd2fa7 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597941710000000 1598546510000000 1661013710000000 1692549710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x6e5ec95f36ae8c7402634ac623b8f58ff26f998c35abbfaea1342dee90f34fe84e05df9219690e911cc50ce16a8aa11eb7f15cceb6f22dbb17c15783e0c5be5c \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1598546210000000 1599151010000000 1661618210000000 1693154210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x165dbaf0c1bdd9d42be3722c488e5079a24091fd7dad07a5beade4c9603cb0682dc3708da84b3b095ca6863ec51f16804b2b297b8dbf31041ce56155ddb2b2cc \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599150710000000 1599755510000000 1662222710000000 1693758710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x74deb3f1abe0d0f7faedfb22fd0fa7c7496742a1aa2ccfd64086373df84639f8c6f87a976cda46f35df449c8bce076355d62870b7bf17f2f6907d900b59e1dee \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599755210000000 1600360010000000 1662827210000000 1694363210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xdbd34e536724be18061fb45c8f2e479ee586d5ae2ad2145096f4d25fbcbf97168d5a94d5740351a76f107eb40db7337999b7bc0114ad2d00c8b0eb5d61f1a723 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600359710000000 1600964510000000 1663431710000000 1694967710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xed4ea51f74204cb8177ea6b80ff8801222f54153397cbc190f39d3c72a264a451fb4043bbce6ca5691f8407c5c17fbf9c96ba8f729bf139a0f0277e6fa89860a \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600964210000000 1601569010000000 1664036210000000 1695572210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x816f803af6a7e11f0aa487de940ae8a0a30e402721dae1e10d34213e1b0cf99d694d2840b2d2b832f77e1d2feca9df281847905e5301cfa180bd1461309fe4cc \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1601568710000000 1602173510000000 1664640710000000 1696176710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xa5e1ffe58f26df4a9dd211988634517c07d854be23f66e5e77986e65fac688abd0333036b05baa09a72f13b57b4b872e19c7258b3355357e389833f71ea071f0 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602173210000000 1602778010000000 1665245210000000 1696781210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x3f0cb2a60e00b839aa07b37fa79e6ff7a287c13fd0b958a5328c3719a1bc26b5edea0238c48bd8cee41bc04b2816e26ced58bf0014e1d59212a82d58a1fdda97 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602777710000000 1603382510000000 1665849710000000 1697385710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x92ab7592772b2b881f7e70ae12e5ee8398b76bc0ef94e4c737b7dfcf9854f4f18cf2115345c0d24a30bcd717c27be8d5b140dc015da8862894b241761e253922 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603382210000000 1603987010000000 1666454210000000 1697990210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xd1061fe76cbaae0d04ab65e940c76e6bc9b82f09010caf40556b9672aa71a021dc89670edf00f86c32729d330fb09092a0a75f039db49601364b9a5b2379510b \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603986710000000 1604591510000000 1667058710000000 1698594710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xd136ffb55575c4ff5be64442bb108b83cec060c9bab8a5432e9762f0e32121bf3696990a13291206f08deb193356fda4cde77ebca556adf77099ba42ad4b03d3 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1604591210000000 1605196010000000 1667663210000000 1699199210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xfac60747f6f5230990e394d61524d5e5c2d7fde9d64cb67971383afded993d28b8ccc24859da6595521f2131f8b29f7249a132b21bd783ddc17df6743baa690f \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585247210000000 1585852010000000 1648319210000000 1679855210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xaf1c014942d7a667381cc079933a31eee8c9ecba69f1d05957757ff2735a71c20ec47253af465621395e5c5c08b0677345119eaa1d49309afbec7fbb27c85185 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585851710000000 1586456510000000 1648923710000000 1680459710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x9de8cce48ed3e96948d3b3afaa80ce3b88300019c9bc6f2448a82a9902ab58fb624caad443502b31a5b346881d74d3a52548fafdebe59134015a4af878d25f1e \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1586456210000000 1587061010000000 1649528210000000 1681064210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x5829756a33e2512e0c52cde712e9c737c4bd5efec68356606c9fca2163cb40262163ea08a65ac6a76a145397423299aa076a8f1f8e9957b8963409f85fdfb230 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587060710000000 1587665510000000 1650132710000000 1681668710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xe282452214a9d34232b6c24f6eccf2a97893d2ad9741e9e0b4c874a5c5fc2544446e0b43c0bda9211bbdce61a4762bcf9db641b46ec67ed8f71f3b0ff398d5a9 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587665210000000 1588270010000000 1650737210000000 1682273210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x88337537317cc8b51018fb4c4548bbedcbbf28c0c2eec201d8352f9110d08e80d3e1449a0008f62e5f44a27efdaf5ffa99c34abfb0279e2cb7fb8e9e9c091ed3 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588269710000000 1588874510000000 1651341710000000 1682877710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x3a5a7bbe7f8eec2371c769e6143d6ee1d449ca7cafaf1851bb96f4c9d2084d3231e74ae18fda9616a031199cb712737f01af2880510224d1e4119c26edadf105 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588874210000000 1589479010000000 1651946210000000 1683482210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x2fd6f2fa3d765fa976d488bc81e267be30795510570e980048c76145fbfdf4bf79b6858d1745000c0cd72149a80720225d9483bdd81e2e5e4434e7eec5ba7b0e \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1589478710000000 1590083510000000 1652550710000000 1684086710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x6e873350425cf51f7564bc5fbef921e7c9bf830c9d35f00d3f762b55aa504608e1dfc017499a3ac8abaa201cf072e6e3c9d3bada76e4e9378844aa1bceb461cf \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590083210000000 1590688010000000 1653155210000000 1684691210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x7c9fbc4197c48b8a6bee1360043ca58353cb3e40890fd78a6f04ab69cce9c00a88d572841d49943c2d7fc525c7f9fa3a413ff3d8a26a73e6dd226d0bc3b3b4d0 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590687710000000 1591292510000000 1653759710000000 1685295710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x9068b60510081087c9d4acc4a906fdd20dfe5ea8ea54e817433fad3ee25ea35e086ca5a7983097b0243f41583020699d491123a721bc45daa490b176f7c62b2a \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591292210000000 1591897010000000 1654364210000000 1685900210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x17e4a9d8daa2ca64826813cde9733f5949d9a30c88260c7d5933c6ea811a27b9f5c4732774466ff8d1f9b0b5d9ec439827e2f795119baf9164b7c7d139fd5ea7 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591896710000000 1592501510000000 1654968710000000 1686504710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x8fd23473f33b66e7336bce01e3b938f95b31f63f874edb521d5c9ff57969d5d78596f84b5a0447bfeccfd07f6b98710cdcb9306c62ee34ff53be01d0c7e678e4 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1592501210000000 1593106010000000 1655573210000000 1687109210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x69a2095992f3cddfd5f273c5c8fa9cacb924c58e1b5132c79c8001eb47e3e04fc812189675eafbe68b76579bb21fd056c4974502cedb1a7216c1ee7c42fe1f76 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593105710000000 1593710510000000 1656177710000000 1687713710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x3cf8a9a58ac03112f46a64c298a8150dad26d383fb12702ed4e225a8525da5b6ae26e4439d2b09a3635654c8599916d35142ceaeb5b9ef7094c8e918dd2acce6 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593710210000000 1594315010000000 1656782210000000 1688318210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xf8d36ed24a9593576c0fbe1fca1c45e91bbd440adf76f1e7589793d5cdc1f797084cf42f28d1b949b63ea04f107edbc98ae27a35bb3e635181f6ff7b74946b34 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594314710000000 1594919510000000 1657386710000000 1688922710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x1cef4b14c92d23143dc22bc8bff1c7637ae220ad5af5ec7b8fc5b289b592dc536381653840495238ad17efd80e3d067806ec02c93ee7d353fefeb90386312dd0 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594919210000000 1595524010000000 1657991210000000 1689527210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x6f15242144bfcddaaa200d7e799d514c320cadd2f149143656ef18425774ab340f174dfd87dafc87e4d15fc78dacb1afebe374b21a10208da099a650bae5eed6 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1595523710000000 1596128510000000 1658595710000000 1690131710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x4cb3097f1c37488a5f59c8e175f6989c8a2f5d8dae61a92105c1e46c19da6d64d0d952aad31cdfa0f5e24c90ffca6bfa9402466d6718f6e46adb408f734058a3 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596128210000000 1596733010000000 1659200210000000 1690736210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x440183e206d698c7a2730d1e5f71b32707963b9d6f117764657f3a615727f29ff7f69b2ea777fa55cda06b11777ffb77c6c73aa20968f9ffaa2f0c82d8128635 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596732710000000 1597337510000000 1659804710000000 1691340710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x8d14780f3f5baf8bb404d9d55185b8d151d1c3c5fa4236f6e5f2d0a6ad41a024eea070561434393d5d75e466795b99304a6455a81f3f220ce70486306b9b6f2a \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597337210000000 1597942010000000 1660409210000000 1691945210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x4b4d8458b842b658affb89f20635a3cf34036637fac52e83774af8c363c5c99986e74ecdc301dfd17cd87bd5893fcb3a8fd83a27867b7f5f269df497d5cdc821 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597941710000000 1598546510000000 1661013710000000 1692549710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xdf88cefbbb63463e09ce6355c7295f42bd686c851d63965ba8d1a41bb30bbf8bb2471a4e72fa61262b5870221fd78d3771c10e13da1c7745e2a2871e15cec100 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1598546210000000 1599151010000000 1661618210000000 1693154210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x776aed60373a03618957c5a94ee0ffe060aac91f810030e1b21334fd489aba75d3ac5a29f3f64e8d9b6a767c095be327fa8dce9af14687196edd62348e59a620 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599150710000000 1599755510000000 1662222710000000 1693758710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x3defc51b1c9b98abb0983b2210d8af346725a940769536fe9d279b372486cf803b031f378cfd45aba481f283b6d30b5f73297d5dc1793ca877168db2b9645b6a \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599755210000000 1600360010000000 1662827210000000 1694363210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x017334ba5b590f808e52213a51501905e57e6c4dc50bb4c34d07e039d8a29de77ace1725d4c2403ccb86256d48759f5df7de1127b6f0ffd369252370f7eeb511 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600359710000000 1600964510000000 1663431710000000 1694967710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x681520926447893b8d3557ecc1ebdba90eb23336ab721e93b954a42bf12a5f6cef5b67dc3d5a345d2ef96a1ed838a13d0512686f6ceba52ddd7c064a3b5a14f0 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600964210000000 1601569010000000 1664036210000000 1695572210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x25dc433ebaa4d91f9a7180e25f7d119c831ad9c1dbb1b8bdc05d03d3084ed4122ff6aada834a9c5dd3b5b1cec41946a1088e29278aa382819fad83ab188d82d8 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1601568710000000 1602173510000000 1664640710000000 1696176710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xaf630ded5f31fe2d4fc0b4209710fa38388c7e4d285d43592db68cd78623d1ae4be3f5e159bf7e01fb6baddb92981b96779c72ce4bac828b68e8a46f4853785a \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602173210000000 1602778010000000 1665245210000000 1696781210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x4cb9299a278deb0617c68dffc002d8f5c7ea71b3601f456f8aa09b50835380e4a91ae1814b6ff460e490a077338039ec2adc15d93992c0668c05206375510f2c \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602777710000000 1603382510000000 1665849710000000 1697385710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x276a6185aae025ddbd40ddca7cdcb5e11d9aba1465db83997cbb198ee3aa65976d11bb4d40f8b5eddb305117b4f88b090a5d66918761aee9e87a10024d490edb \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603382210000000 1603987010000000 1666454210000000 1697990210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xb3be6818a39d860eac2ec503286ee89697f668dd4fceaac27ef9f372affe273e03a76a961f96c3cf03d8acede33afec84a8c899a557bada8b16dc5616295885d \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603986710000000 1604591510000000 1667058710000000 1698594710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xf80f1f2994576496bc3d150c08404c7dadee844f5b220c0eb780f5d369f0d2481e2852c7dddb112f29c64832a5559e42d6c5c171ad96a5733a98da12a2200b44 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1604591210000000 1605196010000000 1667663210000000 1699199210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585247210000000 1585852010000000 1648319210000000 1679855210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585851710000000 1586456510000000 1648923710000000 1680459710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x98e9f02ee744cc60bb24bdf368c4b1a01e70b6d327c38380cd6a306d5451b79802d7b11a2859ffa86bc9e86f80ce67a8fb8aae6adcd0274739822cf2167b961a \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1586456210000000 1587061010000000 1649528210000000 1681064210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x993df19db1d9858d347a101b12278024a0034c70b2ab7a2b40bc87ee5c15e933ce1d952bae328b5a428c8bf39924481466c9ddb42fcccc8592324c8760961366 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587060710000000 1587665510000000 1650132710000000 1681668710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xda270368811829cb9103c1bb50c9d144bf3b263a2ef7902558d4d806de58be3df7598b6efc7287a0ebf7e340eacb69992d32e5316ef63b144df2b866796cd934 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587665210000000 1588270010000000 1650737210000000 1682273210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x47c01b601fcf1fb694b873ffcbbf6f29a967a62a4d8bb117224dea0059d89a75336a58a71a14a0c8e08474116ede3af09278f75027f5017ec8678de2b3a9fd79 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588269710000000 1588874510000000 1651341710000000 1682877710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa457bc1d426fd999113e12ebd82b6acad05856052cf84127b956680b59059c63408d45d054a4fc141bbe75194eb377247180ec7ca46d9c85b9f3380ed5642fe9 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588874210000000 1589479010000000 1651946210000000 1683482210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x8ee49acb2433bce380f39d5d35079c17ad446c51a4515a91ff51fb8c15946eb4999a0bc6c08c44633ec509dd43f9b10eb7cd0435475e9bd0674593b1ae8882cb \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1589478710000000 1590083510000000 1652550710000000 1684086710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x8a4572e2e1307d787c2df87ae424f11c29a04a6bd56d170b92ad51f7d93c0b1558749b5c8503e6afcd7f26fc0bb76ac8691b96af396e549897f55f9f84f75668 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590083210000000 1590688010000000 1653155210000000 1684691210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x165970869cbac37765df1a5b6668996ff985a1dce898d2e6e3001c99dd4ef50c625178de459f8978eedb762159d74910ce1d406d51568f8c36b698645b99adb7 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590687710000000 1591292510000000 1653759710000000 1685295710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x6c2e6b6de36b5556a9dd88073a174a22f8c45d82dadf45e12a77080fe78271f59704bff1a27f81c9e4e534036d312d799b12b00f7417fe3e4b5b42fc394d9246 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591292210000000 1591897010000000 1654364210000000 1685900210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x4d093bbbbf8d09bdcdece049e41693c3313fe3d7e1c3fab2cbe7468117eb61bf170a805be83c55b5b00a5c39e454dc685916e899ca6d119c115178ac7fbbeda7 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591896710000000 1592501510000000 1654968710000000 1686504710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x937c18af83d1e5b4ae777dfeed5569f6425de3a3cd1934ab2b07e2ee9daac5728187fa5489e207d93cc315f70657392f6dc7b81908e1ac37a94d5fc550a877bf \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1592501210000000 1593106010000000 1655573210000000 1687109210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x22a6087b38521a417e96a8649be7ca5086fadad0d399a6128a3eed9f2ef076016a9ea677c9aeec7b6587372a787707dd934b505b7f9b50c13e6a9c004751d3ec \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593105710000000 1593710510000000 1656177710000000 1687713710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x5793eb71e2e610b279666cdc1ee3fdfbb9d4765573aba7d2d02edd2c9b973818e1a38e3ee42e3062cae853c36fe660f9e1378e3c003c142f7ff3d31304489bf4 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593710210000000 1594315010000000 1656782210000000 1688318210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x721093e6f159bc5d74dedc00e1a964d2fe6c98085b7c7afabe821346249072332aa642b7d33841ca47dcfeb1c292f1892026b6cf4fd150319eab171f97e944a4 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594314710000000 1594919510000000 1657386710000000 1688922710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x158276bfcc7388cd491511f8f8fdbabc4ef8c5e011341d8f3ffe90e8f37a3ead99551ab339ca8c650a783510c95cad58897e5e99e2f7ebca59fcb85a00908e50 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594919210000000 1595524010000000 1657991210000000 1689527210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x1f8f6e2464a0ab5b3dad30b55a8a0593decdf5359ef995d07ff4f1b6fd3551ea359f93ecdc95b83e1a31273bab6adde46fdf585495c6e9e06a4cc593287f8c9f \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1595523710000000 1596128510000000 1658595710000000 1690131710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x5c0ac65cf450c8c0ea758a0ff1637d435641bcf74c114a12c11524d4f0ad0da3faf3e0f4a886edb51706372e6e072e69177163313e32d3691f14a4c26c6f4641 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596128210000000 1596733010000000 1659200210000000 1690736210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x2252c33242e3d31c7d4a176255ead7a2f8573762f4b6d6e07d8784d9879a2655d4acab02ec6d6192861b09318c4fa7de31f00bf15cd460842d550237c3218118 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596732710000000 1597337510000000 1659804710000000 1691340710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x54df5ed833117a0da8ce07e7192f07c67dd9578ac400dab173c89520f82b004752f1a6527e55a0661e089755482670844b9eef747a40bc712938e786a2437885 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597337210000000 1597942010000000 1660409210000000 1691945210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xff6212eda23c185b56b845937dde409a7fb307679c44f72510e81fd91959cc13987838f9dd0cc76641935bb67622d41256de15f241ae9c4ce86e38d70da572ea \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597941710000000 1598546510000000 1661013710000000 1692549710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x88cf6a75f50811a39837a8e04d11b08325f7d8ec24d47942088ce54d66794196574c40ccaa2df8aca347e49f06523d9720583a8ce6f73d4f8ef8ab5fc57d3d3b \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1598546210000000 1599151010000000 1661618210000000 1693154210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x3fbf102070aa2a1ed19a0a779d2a23f4f094712bd5822a5d38ebb5cd341dd3931c30e3f4ea01d99092c833ba23e8c79b651aca0dd1b8c41d5f7670533cb78ca7 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599150710000000 1599755510000000 1662222710000000 1693758710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xe3c00b46ea754f23cd3b5d41377fe7142d4146e13594e70d67a541b14a5a1f55e3a4619113c1593cd82bd32dfa74f7e2e27bbc4be449181d31b606c67c8ff591 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599755210000000 1600360010000000 1662827210000000 1694363210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xc322cd92118aabd445aec705cfc7e90a52fa11c86163eaa97e22ccda4839717679d0b3348b778dfc4a98f5274a1adb343d4cec43df97243a89b812abf4a4940e \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600359710000000 1600964510000000 1663431710000000 1694967710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x4c3c6d57a0664d5da30c8394375fd8ae8d06e27455e41afc4198cbf509abfdfc86ee823d070715e58fbded3acf3830c5e323a01b28fb629e3e728ceb77e2f13b \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600964210000000 1601569010000000 1664036210000000 1695572210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xbdeb7ac98af9c80c621707ba229659bbc0c9849a35efe1a32ab0b88943b736280c3f92fcef449337260c5e71f6c5bee5098c2a53a71b62c5d4ebe659db962ba8 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1601568710000000 1602173510000000 1664640710000000 1696176710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xc0b1afac5368d72b7251a133e13e4c3f271e0570361837d614ebcc0110045056eb78c5569ec668cc07a99d641e9dee321be1b621c03ed276f4870d79b227629a \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602173210000000 1602778010000000 1665245210000000 1696781210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xfe0571c189ffa9598bd2b10daca0b41915b79d2af0d182686b4489578fa21be8fff2682722dd4843d3db53627a7ad82923e893a341c8f1d3a81606a42c0fbf03 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602777710000000 1603382510000000 1665849710000000 1697385710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa0d996a0157b9c4008fd1daf1d8fbd0749d520f31b913357f2bbbe1100ec7d2307226a46573037a9c03cd36f85d5ff9aa9cd52d1a95b3637205befdebcaf93ba \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603382210000000 1603987010000000 1666454210000000 1697990210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xed9964b3619fdd935bfc8df972bb2506f28f19cb821c1262bd62b3ff4338710b07e629bc0fda9b6e35233d770831386fc7b3ccc41316f4ae63164c7843e78c06 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603986710000000 1604591510000000 1667058710000000 1698594710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x9f4b88691c86a171acb6c468816bcd93d0ad44e027412758b6980d0225407db0fb4f2475c5266ba59be99d262248708c8ab1aabf5857c09d1c076ce6f84598a3 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1604591210000000 1605196010000000 1667663210000000 1699199210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x747f5c89194b7266b995701b18f8f5f9b31a1f18997b72aa306b12fe8c1be3db30507cf68c32408ad8ed9accdf31dfb56ac917b362dd21e4727b7e499540110f \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585247210000000 1585852010000000 1648319210000000 1679855210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xf3b15f353353c828067a48d0cb5cc98b82fd0fe9f0403e675cbd25a702f0601c7254a5c1f12aa67841d9b347bed3720680b3fe8f48b3a3219ec5df26a55f41c6 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585851710000000 1586456510000000 1648923710000000 1680459710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x0e15259d45b92cb3220d56402b7cecdde2e0c7b35ac8c8fb35bac63491ed0087c7d878333802f9f46f17e663cc5e6a75571527114651b854ca920dc09d9d456d \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1586456210000000 1587061010000000 1649528210000000 1681064210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xf449fc3fb518f2f296fee1686d745967d03c457efc5c843ec7d18603aa0975b7295b28afc80c03a27359abacd0b8707d9fc2017c4c4ab91a128481b82bcd4d42 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587060710000000 1587665510000000 1650132710000000 1681668710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x3bacefcce0349291c705d69b39cba778aa0701d0bb2747d3010479c031f6f407e3be7bffe675b6076e7cb84ddd42da866073233515581cc8beb594980927f883 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1587665210000000 1588270010000000 1650737210000000 1682273210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xfd2ad49aab62ebae12f33fe6de7ccf47884d467965b906937194b5fc4bff011da194010a26d10aa3fe51d376fa91f6b24e4a874b9641b755ec78ff6330f913b2 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588269710000000 1588874510000000 1651341710000000 1682877710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xb3e1d98bb8bad7430a19bb95c9a1f5b1d6b34fe2f5193ea95f069cfd78944aee3e2c0fca478cbd5583010d630d1273adfb563f615eab1187bac00bac7d26deb3 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1588874210000000 1589479010000000 1651946210000000 1683482210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x8e58bc9aa845a8dcb4c93507273ab06f388df9d187a25e6982e2f15a5dd2778c6ea2616a95c4fc8fd01dc76bc667441e9f3db3c58ffe569a9f9634214c379f22 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1589478710000000 1590083510000000 1652550710000000 1684086710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x3c635b2a8a0891f12f3279c0d9e6549c677d1b4616e5233a54fdc9bce4a6224a90f01058553f7744093a19478bd71590f74ba95211aa91ce0806e58175a0756e \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590083210000000 1590688010000000 1653155210000000 1684691210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xc4765bcd9146d2e30f25dad9b3586eccfde80776971bf4126de8abd2c0f48192809a30a41349904bce93bd8a25d2966549b451b732fe049c77e4da8d82e4b600 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1590687710000000 1591292510000000 1653759710000000 1685295710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xbe125ef28ac75b00c11aa8118ca94a84929c6a4216bd6c8ff349b607c83e543b770b0ee24b580141a50ec997a2b73860f43658e1eeaf273b42e7fa2771dab223 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591292210000000 1591897010000000 1654364210000000 1685900210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xa477048519c7f8e54e792da8e2616e4c744c71e897f479c73b13409e7f9130f7d9e3f58c7798cca753a1ca799951c5f713f6d389dd0973c2eacf758a0ed7690d \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1591896710000000 1592501510000000 1654968710000000 1686504710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xf65f27fdd6ec270b95757c171a4961b3234b6ddc7f42cdd259741ec405383b12500b5645fc1f74311478277624b3ba16362dadb6bafbd629f7c89c2db86d6b10 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1592501210000000 1593106010000000 1655573210000000 1687109210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x381a76a33cbee8583d1e275d77ff8b4a233cc47f0d87ce76e4ab34fb13cd61b64f0c589be2c9f24cf1988fce64cadf29ebd1d4755445f7d6bee70a0ac2ee3d2d \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593105710000000 1593710510000000 1656177710000000 1687713710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xab99a6befa6052eadfb7f61f9d4b3b701c2624cc3f384df41f1a31d51385f991137819b981721c0209a2fece0bb555e805f6ea2681d3d153e0aaaf66f461a092 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1593710210000000 1594315010000000 1656782210000000 1688318210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x352c018bbc511a492262cf5e101aff1d079b4ee563303e06a4f77581976509fad09f88a568c7a75eb521020b30bf1ad39ad07624dca02595bee20049073e6fb6 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594314710000000 1594919510000000 1657386710000000 1688922710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xfff1c04f3e24640dbc9b8041f758eaf9f7ce0dcdf3259974e7fc85e2fa8a6905b55a495e567c70289aae9cd9043f30a03ea06608fd34afadb0aa08ec26f4edee \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1594919210000000 1595524010000000 1657991210000000 1689527210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x104974b598dda349ecced03038b53eb9528dee10014385d4d33b02b9a2c953682a05a32a120fb7ce97a5e97af9df3f9102db5cd835e63d8c2a656d1046039a82 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1595523710000000 1596128510000000 1658595710000000 1690131710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xa010b38180f0b7a0c061f7c5bb68d1da3d520d80eff59180f622ffda9bc51f9d2de29a3d75b91932cb1bbd32cd333f4ee8d82255810c91e0ae11400ae84e0378 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596128210000000 1596733010000000 1659200210000000 1690736210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xee0eb3d3eb3906db27a82f41ddc494383f6b613ca5b56e4396b83d04e7c565237f71ce98b3c5581a39f9e47eeb731dc74828e70930528b0d0a09fb1f71869c30 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1596732710000000 1597337510000000 1659804710000000 1691340710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xb72e604db5657b8d528f16d2b4a7fccc12b0b97cc43dd749e9f5d1cb78f7a65776af046ee2646592353ac41b6517a0ec64b37ea7cb3456d763854a1b16eadd2d \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597337210000000 1597942010000000 1660409210000000 1691945210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x568c9a5170ffd7ce7ef2eb2f2c294cfd54df443730b6d7d100297e980e7958a13aece3da0a50809810964d26aa0a1c805ec71598888120f22e22b90cd8f962ee \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1597941710000000 1598546510000000 1661013710000000 1692549710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x456a9de9e94e34ed44892ecffdd98a2c13cf14ea322745456c8e3ddcbc42591474514933a8017d01018834096b3e1f09dd22b24f0c6fd554cea781ce2c849f8d \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1598546210000000 1599151010000000 1661618210000000 1693154210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xc236898146d3077793082f98e9a6f88c1897482bc4be742a09cad99b85f28f87eb17da75230273a31ab8be3719568c9a1e4c4aa3e78309873127e1db3adb00e0 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599150710000000 1599755510000000 1662222710000000 1693758710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x81c218ca0a206bd839b2ade8018b88f7c1983900f1a26bf4c89807b10def2e4a53c0245034a6c34e7335d853ec1a4344baf9324711ee52ba9d5925314891defb \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1599755210000000 1600360010000000 1662827210000000 1694363210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x44e130ec640e8492c999d1bb9522ff27606a2ca8f5264a22415c661d0f00ac70989193722ea7fb332e495ca48a36446ad22be424406ee78f60b9d20b089283e2 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600359710000000 1600964510000000 1663431710000000 1694967710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x214da53a649cf2b3ff4255ebb59fb7e7b0412a419b21df0989d0d6af0ef540aac8338e691b31d550ca59c2c8d4d60f12434c034b982d38fe77ead94665e9178f \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1600964210000000 1601569010000000 1664036210000000 1695572210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x3341ff9efe2047bb74b842827056a58d87d46e3e4cbd3fefe1086a1ac55c9fa63795536626654ee22cbd62181fa9fecc1055a91473d1a419705e03cae7174fde \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1601568710000000 1602173510000000 1664640710000000 1696176710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xbc17dc5cabe2dc94e9e7f4ebc48cd3b405debd95214d558c866f9b25e4a20bf01877a1e173d9a71a59e8d19424de6ea41cfe992adfde1995c6bc9850eb707d39 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602173210000000 1602778010000000 1665245210000000 1696781210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x7294a2c2aed5224625bf2261ae05ef0945c5c2e39e68382ce7f282d8906681545a74b4967ea0608143adb52f5e51bdbc928ca81aa42a20a25f4c7890152d4f3b \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1602777710000000 1603382510000000 1665849710000000 1697385710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xe6e9928e69600ff897cfc930a9025ed83ae7c222f210748982f78e530ab2ae21d620b5ef5baaf150e92981a95bce2b95d21936828a77a6b4bc0f693ad26294b6 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603382210000000 1603987010000000 1666454210000000 1697990210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xfa402d97d8baf09dbf27b6f466e24a272e55a0b525d2201fa61cd2f45c6bda0eac110c05ac97ef914e12a2a5ad5be6181474035982bb7e646c93318443f7524b \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1603986710000000 1604591510000000 1667058710000000 1698594710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x995df75df0a3cda873baea40ad4b18aed83d13f2685d6e94f125681c613430baa1565ec480ffa3a812c08569e93fb9b733fd84beac6ae7abe53819621052c0c6 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1604591210000000 1605196010000000 1667663210000000 1699199210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xf83190c4582e1152cd1d1cc323b2b0bce4603dac971c160babf3389cf5e585796e4c3973db82924be749ba9655471d73653774df3963cc86535f423fd628ce48 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585247210000000 1585852010000000 1648319210000000 1679855210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xb09b6217d72bdb8f393332f676b9c85c2e246a6685c6f4ee8e96384b23ca29e89e587f782d24625001c7cd6cea13e85fdcc9591452f43c6d717d24297dd2dec6 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585851710000000 1586456510000000 1648923710000000 1680459710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\.
-
-
---
--- Data for Name: auditor_exchange_signkeys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_exchange_signkeys (master_pub, ep_start, ep_expire, ep_end, exchange_pub, master_sig) FROM stdin;
-\\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1585247210000000 1587666410000000 1648319210000000 \\x946e8b5af562e6bdae3f9e9f1996162e81eb835d52f6b1455194d1c8a4afde69 \\x099f81f628dcb585af0d033f33aa03c6070e0d929b3aae82bf8d0496ae1134c9302bda7ba2325470039e1845b7a72b1adccdbe0a0703f89ebeeb2b5876dee503
-\.
-
-
---
--- Data for Name: auditor_exchanges; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_exchanges (master_pub, exchange_url) FROM stdin;
-\\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c http://localhost:8081/
-\.
-
-
---
--- Data for Name: auditor_historic_denomination_revenue; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_historic_denomination_revenue (master_pub, denom_pub_hash, revenue_timestamp, revenue_balance_val, revenue_balance_frac, loss_balance_val, loss_balance_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_historic_reserve_summary; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_historic_reserve_summary (master_pub, start_date, end_date, reserve_profits_val, reserve_profits_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_predicted_result; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_predicted_result (master_pub, balance_val, balance_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_progress_aggregation; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_progress_aggregation (master_pub, last_wire_out_serial_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_progress_coin; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_progress_coin (master_pub, last_withdraw_serial_id, last_deposit_serial_id, last_melt_serial_id, last_refund_serial_id, last_recoup_serial_id, last_recoup_refresh_serial_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_progress_deposit_confirmation; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_progress_deposit_confirmation (master_pub, last_deposit_confirmation_serial_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_progress_reserve; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_progress_reserve (master_pub, last_reserve_in_serial_id, last_reserve_out_serial_id, last_reserve_recoup_serial_id, last_reserve_close_serial_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_reserve_balance; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_reserve_balance (master_pub, reserve_balance_val, reserve_balance_frac, withdraw_fee_balance_val, withdraw_fee_balance_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_reserves; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_reserves (reserve_pub, master_pub, reserve_balance_val, reserve_balance_frac, withdraw_fee_balance_val, withdraw_fee_balance_frac, expiration_date, auditor_reserves_rowid, origin_account) FROM stdin;
-\.
-
-
---
--- Data for Name: auditor_wire_fee_balance; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_wire_fee_balance (master_pub, wire_fee_balance_val, wire_fee_balance_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: auth_group; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auth_group (id, name) FROM stdin;
-\.
-
-
---
--- Data for Name: auth_group_permissions; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auth_group_permissions (id, group_id, permission_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auth_permission; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auth_permission (id, name, content_type_id, codename) FROM stdin;
-1 Can add permission 1 add_permission
-2 Can change permission 1 change_permission
-3 Can delete permission 1 delete_permission
-4 Can view permission 1 view_permission
-5 Can add group 2 add_group
-6 Can change group 2 change_group
-7 Can delete group 2 delete_group
-8 Can view group 2 view_group
-9 Can add user 3 add_user
-10 Can change user 3 change_user
-11 Can delete user 3 delete_user
-12 Can view user 3 view_user
-13 Can add content type 4 add_contenttype
-14 Can change content type 4 change_contenttype
-15 Can delete content type 4 delete_contenttype
-16 Can view content type 4 view_contenttype
-17 Can add session 5 add_session
-18 Can change session 5 change_session
-19 Can delete session 5 delete_session
-20 Can view session 5 view_session
-21 Can add bank account 6 add_bankaccount
-22 Can change bank account 6 change_bankaccount
-23 Can delete bank account 6 delete_bankaccount
-24 Can view bank account 6 view_bankaccount
-25 Can add taler withdraw operation 7 add_talerwithdrawoperation
-26 Can change taler withdraw operation 7 change_talerwithdrawoperation
-27 Can delete taler withdraw operation 7 delete_talerwithdrawoperation
-28 Can view taler withdraw operation 7 view_talerwithdrawoperation
-29 Can add bank transaction 8 add_banktransaction
-30 Can change bank transaction 8 change_banktransaction
-31 Can delete bank transaction 8 delete_banktransaction
-32 Can view bank transaction 8 view_banktransaction
-\.
-
-
---
--- Data for Name: auth_user; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auth_user (id, password, last_login, is_superuser, username, first_name, last_name, email, is_staff, is_active, date_joined) FROM stdin;
-1 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f Bank f t 2020-03-26 19:27:02.111305+01
-2 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f Exchange f t 2020-03-26 19:27:02.185388+01
-3 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f Tor f t 2020-03-26 19:27:02.253714+01
-4 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f GNUnet f t 2020-03-26 19:27:02.319708+01
-5 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f Taler f t 2020-03-26 19:27:02.385619+01
-6 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f FSF f t 2020-03-26 19:27:02.452093+01
-7 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f Tutorial f t 2020-03-26 19:27:02.517685+01
-8 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f Survey f t 2020-03-26 19:27:02.583952+01
-9 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f 42 f t 2020-03-26 19:27:03.007416+01
-10 pbkdf2_sha256$180000$RBYjEO0WzE1z$x2Avt35TkOL2pMHvts3B1U1NIJalXZf95WnJhGFOAUs= \N f 43 f t 2020-03-26 19:27:03.433778+01
-11 pbkdf2_sha256$180000$8p8hsOkotA0i$JXXYtj800s5rl0nyU9HCVuMB6ergdsZIVhaN5Qnx2LI= \N f testuser-JjVD1AYx f t 2020-03-26 19:27:05.200825+01
-\.
-
-
---
--- Data for Name: auth_user_groups; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auth_user_groups (id, user_id, group_id) FROM stdin;
-\.
-
-
---
--- Data for Name: auth_user_user_permissions; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auth_user_user_permissions (id, user_id, permission_id) FROM stdin;
-\.
-
-
---
--- Data for Name: denomination_revocations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.denomination_revocations (denom_revocations_serial_id, denom_pub_hash, master_sig) FROM stdin;
-1 \\xe0726684567b8f6e972ae98a1c743d83344292e41f48d2696272bbe7d7cf28e4fe4ca576cefee21dcf4d05dc1927dbed383bd6383af4f6919c6185cb86c29fc2 \\x22960d2314f1582a329648f60b9cf4260a030e824b1250bc5579971282746a0aa8d148b988945bedf94a4f3ffb566926e399f7073d2355e578d7e71472140d0f
-2 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x21fcac8b4ab4a51300d5a1315655d0ef8cce440fbf4518bc4ef759e4da6fe8f3bd725739ac7d77455dc3b006f66cfdda86567063b187708b588489408c5eda03
-\.
-
-
---
--- Data for Name: denominations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.denominations (denom_pub_hash, denom_pub, master_pub, master_sig, valid_from, expire_withdraw, expire_deposit, expire_legal, coin_val, coin_frac, fee_withdraw_val, fee_withdraw_frac, fee_deposit_val, fee_deposit_frac, fee_refresh_val, fee_refresh_frac, fee_refund_val, fee_refund_frac) FROM stdin;
-\\xb08a0f5ed1b5859b7fdf907c5cfa6439e79f21ef48dfbf5c93b7e7ba10262c0652e5b4ea64dca448eda615d06b851d85773a6a4d94e2fa210c7dc9e6730f222f \\x00800003f634a8b280d60e393fc134a026a9e75c73da984ee8b37d6f97126316c21a35030145c82a573dc4c2c8a1aaaeb4190a9ead8c3b5e7324f1f2aa4a4128460c41c63aecfba3a2345ae4fa0ad575077937b99c2fc3db16f3ff57ba26b5509bce6861f7223e18de56e76069a0fef1af8035997118cea476eaad9b8617fb1454af3a21010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x6dec5a22a2f272fcb4578979501441eab3fb2f8e56a714f961006a6f202d84818b99a712220c5f0c0c3f903bf6d33061b30629e71671d0e919bde07fc6c38306 1587665210000000 1588270010000000 1650737210000000 1682273210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x22104ae3f0d6f6e6579cb6c7e00f4ce253150e2fecb6610d64f5a0643b628c38f6bf26565b91221a9d53b2af71be535bbde0ab3ef0266302668b4683708dc28c \\x00800003d55ade68ee8e9736f077580c578526fda98ceeb09b45fda0ea51caac39833bdb2a18b515ae5e651b2cae56f6d52036119b31fd0dee64cc9fdf41187628016eb46db60fee0297c81effe636f910e3c6475afb86990374be80a89ddc305974c7c72b8055b9cdc965b2efc0a2c8fab26d70aea32585fdb75670084011f1128d630f010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x09610beb2ef510eba77251f7f64a731d27911b45a2b34a81e1d82e9d9f0da9c2a4ce305b4ca3b9cb9e96e5b88e64d6aa136bcc45eb9a41b1db595cc86b4b5f02 1585851710000000 1586456510000000 1648923710000000 1680459710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x83a1afd9a1b99a4d7824bb878db71db8b8718daab4429c879f55edda6c7050ed8efd60ef95b84af213dbdbbbe283bb84fb32817a8bd2476de1d563a7ae3940f7 \\x00800003b28058f5eaa720fb59f2af6159888da4879499ee69315b44294a0f286991a9939757cb48209c78f920ebfe3087af83b6123e44249a16e8e82917f63097ea43306937e8ad94c577541d8e5da649bf5176418f130fef96baf66e093d367b80f04cf5d78c08dfc9c17789bb3515281466daed6f7af3093ae1fefba33c3aed5f7151010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xab3fe5dfd1db939ee85243c93249912b8648aacb0ed0908f962387e57b833574f501d6698cc6cdc5f449ac54b598c2dfc25becd2be85bfecb1c1ca33b12d2b0e 1587060710000000 1587665510000000 1650132710000000 1681668710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xb2b398d560e595d6d879842cd209cc84c924a57538ffe918aa6f3107492495740d2dadd63f34109c3a19caa665eabb781359cac5c496e56a36bfc6926d3a45a8 \\x00800003ccf8f6e955ec8dbd5e1fafdbd4c2a5c63d6357c58171c5ce23cec69db797dc7d5d1e634d5cea7484cc2be8ef4615db3f9d0d8ff2769f3f06ded82447c3f4eea765131b0af9d9acae660876f2ab540ad8c1fcebb713f75da2ea03ce49c0c74667771ec592a90d263aa67b3771ec78312614b9998b597c84c7f155d94dc17ba6fb010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xae5361bdb70271fe0370497ebfb224e8a7eccc9a8e6fa6624bd7d31bd1e41f68388b461a04af37d5029372b60b75dbb12a3048acad0e683aad76e725e982790c 1585247210000000 1585852010000000 1648319210000000 1679855210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x76697f4126c154b222b4c8e3f87e1c4aebd70a13aa1b73817e97bac1a03af9f8f6eb6008e0fb5fd0a9c8d147f96e3a6d48abe0f322969a8435081ddfbc8c56d7 \\x00800003c005baafd68b9a18061a96d08a1f93c22924052134f0a5494208fb1d411886db3e0ecb074ae90bfffc03cf2b2232dd97116220e176379a5a72e8f80e57083e336a15611a673cbeafc0bd938a765a46b41524f29b5c7e736a7dbc2ff01ed34dc3764435fe9ab7a64f839d3346e6930f36081444990c8c44805dadfdb19b79478f010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x9c201cd873b697ee78b1cb5d617dbac0cd8f5b670b74c0e7975648ad0c6c8ff5527024165220e9158fc88021192913ab4a699c6b7ec3436c57ea31a67c4f480e 1586456210000000 1587061010000000 1649528210000000 1681064210000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x5b5d76d5af2cb092c8ff4b46d0d8e1d470bfdf010a456e290b5daf27633401624716c8cad6e767da252c78cf798c9e5bd5b71d34cf9d34788ec2efc8d38db026 \\x00800003ad0b99c6fff794279491456c65ef1115fb9d8dfea6b45f82b4971926815cab2264de5dc5a200289c2ad0212f6d7b595833284a423add333c599f8d92ce818a6eedd2df2557883249446258d516af67f711aced066bd0a9afbc5899ac54b0ea901936b9b05243ec73e6cd4e1275563395f3706565590adc115d28ad0ec84ebff7010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x05bc742ac859e4d65f78b4d2757d79a3e3e1f60fd03b3cac8dd5833f28bc96e950d39a264a322ae3c5a86a9755f9bf7a0b809e1a228a6f8d37e5bea745f54e0a 1587665210000000 1588270010000000 1650737210000000 1682273210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x0075376d6ad4bce8456a069e7bde945b7f9dd36d73480a147b55c2b06d18274dd666d2fcc28d94fcf6e8c401c26d462e2985a6714a59967886979c77f8ff6418 \\x00800003a9e811c8ec80e58b61eb62f02d23473c1a778a7faeb417a8140fb988d39a04dd5c1bdc031a0013e68d9fe7fb734da2e51f25c950e069c16d8f743c314b7035bfd12cd1dbc0243749fa6ca6ffed65674d1124277e54f48dfddb22ce3556352192eeaa41336b225f6416cfdf30e9a234d74d509bfa6604fb37626691e733325bc3010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x911d59d6e060e1acd0b857a95b089939b99411bb8a73918cd52515ff8f6e511ab12b52b51d68fec5ccf96106695644c97d7bd131808e0ad2e3c33d2375c2e205 1585851710000000 1586456510000000 1648923710000000 1680459710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x6a947bbcb38b820a51840d9cc4f0ffae9a605d045f0661f391cb241120e2bf147dd7954aec09deb4e8b70ca0725a6fe0c6bfd486b8470773e0970d0293eb5b30 \\x00800003aceef5e810ff509df46bb7578317fe5bf1a03e15954addc71e0e89b8043882b7ffa46e4dcf8d841527e90bd55e3b53175d6f849a8c211b49bcbf839e3315a820d56d8bf914c091dafa986b03d45974825feb252f8b18cf00119964b535cfcd03983a8a106995455ea0f3747f04673b600b6a00a7417f21538a240bc589f5d053010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x9556a823266bd931524199b26db0e0fa5113646f71b8870e12692bcad8f041d869abba16fd585fdd24d935aef5e2395444367f5f1a0c6658da67227b505d5901 1587060710000000 1587665510000000 1650132710000000 1681668710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xb378eecaa2af3d60482c35dad80df06074d106d106c4a99c0bf17ff78a1c4932f6c61c244ccfc9bce512b86d2e1029b1d79d5f6fa165e29f200906e8a25a1e7b \\x00800003dde42ff25bd4087a637db2c939985b8c32e96ab4a2e56c23d1e0b9d882c7e15def40d30d84363ffe2bbdfbd91dfb174305e772e6aed1dae9955c2580033219d64cbbf69a209c6fe7a71923fa1b349a9fe0b484c76a790da03d8ede39900a120bd4f3a60a755c16ec216f7f0d4e5cead106c197ae01c2a93379c55fe8911b2347010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xa79b383231e346ab1ba67f613471eda9b8e5732f346b8a43dc2ab1fff6f1abdd85aac75cd96eed57fb39e7d22433d3e906979ba92ecfd2673558e0e854916402 1585247210000000 1585852010000000 1648319210000000 1679855210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x05a962dc843c0ff39ee8e5fc3762a8c73db6be6e8777ea3eb8b5cb4e3423f7eae716dd7826800bc9778e004eeff63d38e3e3496475c5574a6978660bff8d677e \\x00800003ea5907be696e6a24713e2a8960145d74e8e787ec45cdf9e425a2fb2723125176c6540d36a64663f8a8a49239043d6a8e4f0cb1d7bcc240661eaeb961f9c2c4d292b724702e0c1da78d602b89e962d9cf4a9f7bc0124957d22fc29a900fcd6679bc9d752191a65b9f81a3393c662dae0f3a5cde312bbbdac6b5aff830decdc883010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x2a507230a1e3189c341c27e63cb2605aa835b69559e3e7ef2ff75c57531b1cfe8e65477022f6fc76d37749d4a6a031970575df387ad909dc5ffd5d2ab58e6d01 1586456210000000 1587061010000000 1649528210000000 1681064210000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x1f9600cf0bdf811771933679fd1dde21429a0c73f961960af88cd8d1d53a5ffdfaaf5f31cc5f7b28846298ef0a84de0f3abf8e9389814bcb913e21c630ee3cc5 \\x00800003cd35fbfe22e73bcb6dae7d7cb25fc6afe7628b1a3a1c2508ceb19b90cc8a6eb7c60ceece972bddb680500a030c29e4578b2bcc3e7000c992ec02811bda634d311d840a72b4735ba94534464b162b94092c192d535391678854c3245594d39ec21ffd5ca9ac2afc864a834b86e5f494004064817fc172c459ae6548eb3c1ce26b010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x40730d9738e4d8a4db917f9aae1e47e8df9b1e3d86208e228f9228b7e2a6b4dcbc0c7c42ea245d36e9d353635f54ae340a8f18886b483de3453d17d92b76db02 1587665210000000 1588270010000000 1650737210000000 1682273210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x5e3fff3c1f92845ba0dcf1651b7ec20c0da933572c2ce961c0b7944ee4ee3fa13562d8fe5d799f8eb8e528cb431033634fe73318ccec18db2246ad6bafa0c762 \\x00800003d92a694f24b843c35b266d64e8255b7d42ba05b344cf172d1e5801009cc1307a6c11454e985e3d107de7c10e44fd5129ff91b6cf6183f09bea1098de73eafdc4640b4bd8f1ba96b01fa4ccc417446e9c05d13b046e6be8fe3b8e1a8f5a5e99ed97fd3aa54e169360b504cc34e670c248b3a2ed6c9ba3e9c30bdff95cf08f0a29010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xc70623f832c4c919187efce808a53ffb5911f70caee101391e572b17499e0d015767186d6683265df2b80b999c99d9e674a1edd472c0082d830d81061368fc02 1585851710000000 1586456510000000 1648923710000000 1680459710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x85ac0b83d4e7d7d59d97c69057d729e8eff4493677e7ca61c55593b87f647fd7fda8d5b2712848a1d4df31ff2f27f60815078aa5f2e59e9db57ea625cbb65bd0 \\x00800003ce500a6d6a3c11ee23d22c03268a0a13606922c59678dea760e235a48c590681fe7794c4d1e63777a1bef2fe7fdb78eeaaaa2396f704df79e6dc21fd3ae140465d7879a2288acbd6f337b964455cea44ae3aaf5955331a742996e772d0b77917cc22b13282729d3f90781587d31da76bf86cf9c8431e1f11ebbe69ebbedd1edd010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xb65706df00d2a8c2ed70f527d5fb29b18d74ec8daf13e32bcfd22404626ea03c6a8df0787d114abcbd95f21925a25137a30081fe2f02d2cf4e0cadfae97ec809 1587060710000000 1587665510000000 1650132710000000 1681668710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xe0726684567b8f6e972ae98a1c743d83344292e41f48d2696272bbe7d7cf28e4fe4ca576cefee21dcf4d05dc1927dbed383bd6383af4f6919c6185cb86c29fc2 \\x00800003b0551bf050ee616e0a65669e3aa367e66d0bd6fc8d0c188d31a5179bc6f3ef6a584cedefb23babef8688b0b07a4333203d440375ad0f02199a93b17cb42ca6c52def85dcf6b6a796ef98eb84a051fa05be4ff0646cc21f9cc8619576f3599afda20313b3adff21dfeaf689712d3764f83172c8b5b0291b18ffda239af0a9a163010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x6a38bc0801b022a2701425f901af2f7dcf9d880d155b8ef55f92f9d15e7d02c7558ebbd539e71ad69332f823643fd9d8275edaa3ef147b4cdfe570e861879c0f 1585247210000000 1585852010000000 1648319210000000 1679855210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xf47266abe3cb33716f3b864e07b033f66af6c7be58a6364c28d6c507b7ff70acf649b9a7396cffbdf77bb5bd7af9196c5299191dedd5c9a6ff04073be2ddb945 \\x00800003c9c044e7e249e21c0f484f4d08b192186bd0d2f9d0e4ef9ca8c5d61f324c053a095a27700d204435cdeddced1dd0f8644669b46d8e9b79e2af304f42351a0eb094b0f93f6ef102cd0807806d4d3d8f7cb2b69ec866a3f133199b2acd51aa6c219bab92a8a3a37d833499ff9a7eba5d8ba28928702410b9d6620c59a4611ed187010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x74ab80aba48699935bd3fa517ec2c42cbc65a4a0d364d4331919852717e48fefbf4c4b4eb6ced68a57b718274c5e7c172db5899bcc97ab6052a9899011c3b508 1586456210000000 1587061010000000 1649528210000000 1681064210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x8ba1aedd039e8a27c23c673551ec9036efeed66998fb3371591db8ffd4a3782ef69b6a62969744c9c2052c40cb4600a8a1adc5bf731d0a4f76bb882fb7f20882 \\x00800003b98754c02d0e499276a63d9173c48466d7a97f253b8c129dcbeb6b3b6f41362bcbf90448da2722a6fe8655016d434030d6cc7e67617bbff116ddbd4e4e412ab56333aed2f0e014a160c9eec0009f84ba6c09d606e25bf74413fa825e62c37878df606c306c0b41ddecdf854550cb5d5fb236768bb60f16f97f16aaf4b50cdf7d010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x24d467b321192e02030a75cd59439083bcf671e0fd687770d930f4089e1c0f7ba5fd049f7f210064aa96b4bc43b0307e36052a1f56e0fd61f06a5d9531ebf80b 1587665210000000 1588270010000000 1650737210000000 1682273210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x596dd70417dea3f96b72decf73ebbee411295403914758bcc5d562d1232ad6aa8b14857b58e1cf9ae9b1535ffe4fbdeb4e9b6d67f0739173b64d9bfab00c1f0c \\x00800003b0e1c04b9976d6eba81ea37b67007c594a9d7a843f4437e81e26e671e66234e9e7c044d3aafb293cdaeb4a62fa3c9286c4bacac8a5925a1fae8aa84cf911deb8de017893b2317210d7f8b9f13ee8441d922413e54f429dfb3f3c778fd27e6e497a3707bd763349aaab51df7ecfd7ad26d30456d5715958a0f3535e4d38d4a30b010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xe314ee9e945c658a1c06fdf23f42d2ca888890044d9567b4dbfe8071c03165a55025279518e85a9edc312e19ec705fbd0cfe20036fde957bd9d3339b90ca1403 1585851710000000 1586456510000000 1648923710000000 1680459710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x117efb8a2696ee71b862baf5718e5e897aa7b454c0b71731a8c51e174d6f47f0fb5f2b3e863b62242ab49a94f4a8c3b6adcc4c33e1f4f8eba0d0f2ab121a136d \\x00800003aa84ecfda79a2e2ee06163dc14c700f1ef089493f905b39c95b555acc265c68738b0a956f241e41b786fcf8e5a4e44a2a95bad2458ba257454e45274421cc58c88ce300d0619f05b845b6c9f8f3d691159469e5e0f44a21978582d2bc53e800b7b216ab4fea54fa8b3ec2a46c01a635725a763c4eac46dffc02a597699cf8ac5010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x2f5631d9c0da79467142728da1d62d7f70f80463670baf7ae6db47316933f2d0e70dc308c906c453eefe46a07a1b81aab2ef5b2a7a06c3340f350a16a639a003 1587060710000000 1587665510000000 1650132710000000 1681668710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x133ec98fa5d8efee9e3db8305dbf2710c92fead62b0e5498cd3b6327a08bcda6829d723b7030edfaefd254544ee98919cb79ae488d288f0b44e2398b47543cf1 \\x00800003d12b2e46adcb4b64c396b334010401f805bc1f5448d23a854aedbac29ee346cc069eecd774a7d66467f85133533e7ae7d284c091bc499d012867e7b9826991e1d9dd5c9faad98011e342f5192ec64775c1dc93b2b2b5b40b7cdaa450085574b5e682687c9b77866211b8aee01b4ee5bf5f287ada53c9ece7e32e5b1dd34afe15010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x553880a4746a6d8373cfa9ba68be5f9efc0025535abe290eb6741f6df2033f227e6c98858d625ea96b99a859019bd78721ede3066600a3d185bf0a576e85c806 1585247210000000 1585852010000000 1648319210000000 1679855210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x5557667cab72b0e107bcf82f6afca038342e4dc9a1c9ad16e03dc7f8b087ef861fdfaf39c755ca5f63323740dd13edbbb844a72769442659736031ab8dc9f825 \\x00800003edd5526b05915fc86cea4943435410a19f281c1d89a54df84792032e495147511eaf43a84f86a95570f922653a238f97b042d757f0cdc9e969cfc0a075099308880a6dde598eb3a8ed09e8ecf64edcffc0fbf3f0641ed1429a45624e77ceff7aa1d30ec6efd671a55059bcaff6f44a168ed13fd35a74d251975bd4dbb66547a3010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xf36a4245dee05faf5d753ea994002f71f4edceb7a926c600a9eada90b3f2cb132046400340dbde9b4109800a2d52b3d317dacff34fcd1236edad29117ae8aa05 1586456210000000 1587061010000000 1649528210000000 1681064210000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xe282452214a9d34232b6c24f6eccf2a97893d2ad9741e9e0b4c874a5c5fc2544446e0b43c0bda9211bbdce61a4762bcf9db641b46ec67ed8f71f3b0ff398d5a9 \\x00800003cb8272ab137e8957e6b27a69532b8e418d4bae8ff05d5ff728a426577b606d63afc82c2062498dff4957aafde398a7834e5f0e2368598ce52efa69fc9f5dd6d35390b476d803880f4908dd953f98d22949c41694d8c2d3e37e2c229f2246b50ce62b50fe32088f5885fef1028a5aef7dfb307f86625b6eb2dbd145d32bbb9947010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x7c5d50c01a587fa52d1e9ba4eab515b8c1ee794f48e7064d0aa40bddcb9aca9e2c7d05ca25dd2c43bf7873777bebe476e7bfeb01bb1628a8013a996449af1b00 1587665210000000 1588270010000000 1650737210000000 1682273210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xaf1c014942d7a667381cc079933a31eee8c9ecba69f1d05957757ff2735a71c20ec47253af465621395e5c5c08b0677345119eaa1d49309afbec7fbb27c85185 \\x00800003b1bf3b11f7ccc2ff27f974eac3d706d7acfb5449ab83b8d8e9041c6ceef4e255bb779d1221160f4f5cac7e386f8e76acf607dc6bc25171800ed692fd6ab86e4ca021ee55ce71d982970293ff74d5bda9c57914e77bee22bf2f29bcc0655467dd6c28faaf4b10986a20318fd46807b81250713eb65ed39c8d5e8ab3581efbe2cb010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x8b7616206d7866b7c8055bdee7adb397b1f536e4346d4c786221af738a727891ec1916096ffd6559fee2dfb7e96a5e1d97d682255a19ecad12ba0c29334bc709 1585851710000000 1586456510000000 1648923710000000 1680459710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x5829756a33e2512e0c52cde712e9c737c4bd5efec68356606c9fca2163cb40262163ea08a65ac6a76a145397423299aa076a8f1f8e9957b8963409f85fdfb230 \\x00800003990f6be03aa254d61bd3a440c5b36314cd6a094b146a52be5db6cc09c3be4c96377c37b3c63e16d2a680ff720969af10986d4ba1744b3006354202d10918c8268788457bc447b9bd82ee1e296acc0799167ace2b11138abfbec7bd492fb55bc9b768d64bb0b70fb63b19f287c29ba2c48b5ea25ce20fcb38948c17a6463265f1010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x761e40863d9488915d4589a676bcedd05cafb75b904e38f23d5004f3fdd3f672d803aa11b84533817cfeb70650a536a1f8ed747991d8e1cb1274a022b57f2e0b 1587060710000000 1587665510000000 1650132710000000 1681668710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xfac60747f6f5230990e394d61524d5e5c2d7fde9d64cb67971383afded993d28b8ccc24859da6595521f2131f8b29f7249a132b21bd783ddc17df6743baa690f \\x00800003cef2c01704641dc599c84d724e2a23281e41c0b3e169d762e75cbd349c57b21648fee483f3d4f3a835c844b1abe496d92c8b812ffeada1bd501efd5937e6c1e2fb062565d0941fd91d57aab14bafb9b3e472dcafa3008db23a90a6bc371e8314adc1e48ebfd9be4eb090381421bb596b2f9a590feae44abdc7e9d822ed71e791010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x741daa2061da8db0a94be2a9997ab33b9c9b0f0f3de363972987609df8b6a8be20d9303fe9d6cd8c2a77cba01acbcceb66233c386cfd767da624e757dfc9e701 1585247210000000 1585852010000000 1648319210000000 1679855210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x9de8cce48ed3e96948d3b3afaa80ce3b88300019c9bc6f2448a82a9902ab58fb624caad443502b31a5b346881d74d3a52548fafdebe59134015a4af878d25f1e \\x00800003b94088d823d110517028c323cf8eb01011e1242c875323bfca0e1894e19053b015bc801b61366bd90a52d60a36c0387a07cc5cc66e2bcad30756a89e6f416a5a628ade9e3d395e045eafbba08f546028e93e40605bcc08fc0973923b2d81641d9acba5cdf22271a8e0fea3d55a60fd007f4bed17623d85a81aae53e3f06ff4bf010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x9a5d96fb19223c52e8943644d114e0941cf67ddb44f9a467271e7ee81021a842b75c4cb3c449ca82fa87b2495760e7b55adaa2fb285bd0632a5a620c9c3a2102 1586456210000000 1587061010000000 1649528210000000 1681064210000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\x3bacefcce0349291c705d69b39cba778aa0701d0bb2747d3010479c031f6f407e3be7bffe675b6076e7cb84ddd42da866073233515581cc8beb594980927f883 \\x008000039efdb7a58afbbbdfc3af3aa76fd48254eff1f8597ed1b147ce649f423de47858b6b5f07f6e59d1137bc5a0131e9514468c3237ec10f9b67f481c7136a067f95c52c24620c888c2cf6b9d08a0b7ccdf0d9f70e646cfc2ff46d7724d28135600036ed9bb37e78a924f6db1cb6d0779fd56a352f8faf756502b484c1a2b1f27c1af010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xff2eb53c691bf844fc736777e5a81c4d9eee0d9d9c4d9bf6e1549c86365a42847fda7dbc0b810bc69d3b1b1d3b89945603e39a9d80a003852bfc23be2b2e2205 1587665210000000 1588270010000000 1650737210000000 1682273210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xf3b15f353353c828067a48d0cb5cc98b82fd0fe9f0403e675cbd25a702f0601c7254a5c1f12aa67841d9b347bed3720680b3fe8f48b3a3219ec5df26a55f41c6 \\x00800003cd5947b0a750cf42de888eeb4dddf5546075f3ad6e010b1802cfc7141018616b01417cd60c4bac659de6ad3e93227de6377109f4a62c8c6c2cf3cc158c6a6fa8664154f84221732e11a0b44ead391f4a062356f824713f0e57024bd9e93f602f3d7d96a25232dcfc993ffa4820264b49b21746ab6b9ce8d38bdeeb69d540a961010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x68ad49dae3dc521076bb9130713d88974ecab0ce7ca5e5eaa38c6c171396d2c03d28ae1d369ae0de7baf6b30ef622fc72078b99067d9638deeed131cf7e6bf03 1585851710000000 1586456510000000 1648923710000000 1680459710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xf449fc3fb518f2f296fee1686d745967d03c457efc5c843ec7d18603aa0975b7295b28afc80c03a27359abacd0b8707d9fc2017c4c4ab91a128481b82bcd4d42 \\x00800003c65e7547192fde53f1d3d862a15fe8290497f5cfc6a6bf695346c6a0959fbcf01df3afc8eedd2df6555a65e60f0e34e94cfbbda6c58ccc5928f9f35b230c38fee2ccf9dcd0977182197511ab21d7502703a9432fd3ae9089c583d0d503b2a6a071074980f69a922f6a52d38dd24374c98ce95861e753c49fa87de7a0c88dcca9010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xbcbb51a027659295deae26797987edaedb378a3d50f01299065d28d1172a1b781f13733d467458da47dc115d0c789e11e51f156c2167487a30cbfadfa97a530c 1587060710000000 1587665510000000 1650132710000000 1681668710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x747f5c89194b7266b995701b18f8f5f9b31a1f18997b72aa306b12fe8c1be3db30507cf68c32408ad8ed9accdf31dfb56ac917b362dd21e4727b7e499540110f \\x00800003ceb8ec579cd089e157fe5155f3d0ffb70db073940f8a163c1a51441a44cff4eecfed0ed60c886107592cbdaafab3ae817182766a6402dd7d6f95aafa3b65b8cbc2fe81ad57d6d2924fbe3c6581f70250ef4ecdab91f43b588e14ef9091a8bd72a61d95e76606b3840beb7794fb92462ef1cae154a2cae67b30525f7bb0a61231010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x7ebdcc5dc79759b6638910ea1e4e979fdf95f360b23b42d7619685eea3025c05144c30c126c6cf064584d844b55ce7ff7fb77b9f55f731277bd44e1d0c751005 1585247210000000 1585852010000000 1648319210000000 1679855210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x0e15259d45b92cb3220d56402b7cecdde2e0c7b35ac8c8fb35bac63491ed0087c7d878333802f9f46f17e663cc5e6a75571527114651b854ca920dc09d9d456d \\x00800003c1e2c9c05b49085c35ac878ec97e53ea09cbfbe8c06c1e069bd477389dffc4ff73f6ba8c3ddd279a161b598d8d599c7e93778d45f89405aa457d77fd912e4ec2793783bd28117fc3c89d9dad1520dd46d58a5ab3ccf73b1bd6cad83cdb08e5992071e356555465f4fccd29a0465f150e18562ab78da1a7e26d288870f12032d5010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xe7fbd1cb37ce89fac2be18b79fcdd893e9706151ee21ce310b95b519c87aa28d0dcf0f996db8004b18f839e0042e4b40d89ff4817ba3e5b7af1cea7465a9010c 1586456210000000 1587061010000000 1649528210000000 1681064210000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\xda270368811829cb9103c1bb50c9d144bf3b263a2ef7902558d4d806de58be3df7598b6efc7287a0ebf7e340eacb69992d32e5316ef63b144df2b866796cd934 \\x00800003a3e5d2201f8f6720e6206acf80cc2ce407adba174d79c4f72d30ec6c83e8d0f0ca7d29c489974824d6fc7aae5d3b203de67c6f5d0db2dc8f8719f09d30f9ded0c0698455dc4baedc6a6627648f5ca4943d1e0e22a3c49dd170c625dc2a6a06e1e9d6d187792a3748e9f73fb7c795bd9a6b59505a0e254e7305546209cc6f0fe9010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x7435cefb6723aa32554ff1fee5f99d41855d59c3c1b180a3b4e5ce7f21a3e31b4a10e71183dcca45404f94b2b8de7ff9373a963a45f3e4c85ad7835a58664005 1587665210000000 1588270010000000 1650737210000000 1682273210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x00800003cd57265e6acbc3fe78eac67ae683406c3ee72b118db97420012fb8ce9faa64dc62e4489a14b72b365f0bd697ba4a375e33029851ac7840207be92b0689736e62b913588a0a95aa72a382236704f885bc32cd8ac01e913640226f4e932155b2083ed05eb0899350ac59120c5e1e3d287c8f63eef98159d4c11b61d3ee84a03c43010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x04eb4aeda3bea01ce30e104dcc9e09a73f1289d382f55ce81d68f86178352bb2fc42ed1b6c500241e923288cccb9bf87d830cdfc984d0103580027dde20e4902 1585851710000000 1586456510000000 1648923710000000 1680459710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x993df19db1d9858d347a101b12278024a0034c70b2ab7a2b40bc87ee5c15e933ce1d952bae328b5a428c8bf39924481466c9ddb42fcccc8592324c8760961366 \\x00800003d9316d8066f9e582fea86b1e1a6d846184987ad96f73f88e66e30853b35c86dfa86953d3264749dec61577d52171e1abd66213ae7eb2999953943060a4530adf930e44bdfa052dc6d890d4315693870f11c4091de89005aba66647f90ddc615dc78f72c1374567950912f85c04a00716e2833e576d403ef8a20e2972e7a2758f010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xb9921d59e4272ae767a979d9e3ab18270fa73f689b2c8d7ba6176344f0cbbb13f4d8d664e7b04ac03f7da44a3849f481a8db0a664f1b366682fcf90ca8c7be02 1587060710000000 1587665510000000 1650132710000000 1681668710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x00800003b986211387b410e1acf0938086595550c7cd4f84115ac0c87fef183d63963a9eb50d58ff0100e33466a39f26da9cb5f9088d829c1e8243c1964b49e66e39320d06ae95b388148e13dce4693fb3309c68b374b521a868f41da9b0235388c592964602202f776f807da998d5caf858b36fe46e983c1b063c28ab7fbbedec3c697b010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x78bcf7e85711ced525576e5c1d52bf16893c4fdf13d5fd4d188c6de4d8222b0d8afe13719f19d5342a2705ad20a8d2180039d82f2de834de769cca8c08e6ae07 1585247210000000 1585852010000000 1648319210000000 1679855210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\x98e9f02ee744cc60bb24bdf368c4b1a01e70b6d327c38380cd6a306d5451b79802d7b11a2859ffa86bc9e86f80ce67a8fb8aae6adcd0274739822cf2167b961a \\x00800003c1f82acfb562d6bad63f16a1bdc8e302dfbbc1f9cf9136f0b5ebd0204abd1e47917635712b2eeb9ce861a50490474e4345ef60042047a1376717510059037edaa8c0df53da16ece15d91b7dacb694521ed903af2ac0af387992da3d326feefe574ec7813ebd7688d77a9170cf70a55e9f63588e1ed57d220102933e780a6f8d1010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x08c5c5d8ec00c2b76214661d2846b6ce2bab719e6fb90822afdd285da69db41a1edec34ca02f05750ee93e275161bb5a9922b1ccb686448ede583729ad4c290b 1586456210000000 1587061010000000 1649528210000000 1681064210000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xd628b203825e5c1d6f06fc772be687e4b1236e8525660bebc08612c7d985f1f55c2d01e32f26a994ee775a5d425c1c02d8c75ecc00fc90ede6f83fea0a5e090d \\x00800003b1ac68a79a3326822f02c389681aeade7733d91ab3a79427c16039783f6f6ca0de0bdcc63c5bbff73f8da7f532a569f5d2a563eed5a826b0485472e71659dff71e2dffe3ea2bffacca6ced7363f77b40a3e6f22c0bd3c6b3e9a27cea48f9aad0a4582f2b0a56182e771d87f08c30f83646fcb3394ebe7a40de8cb1167d4235f5010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x5a703fcd8e76dba0e481a9aaf68d43bdca9b7b9e277d654bcc88fa1b8e8864ea41ef5bc87762dc7127e95e88ec682accde1383d09938c5b261256d2d0204020d 1587665210000000 1588270010000000 1650737210000000 1682273210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x478fe9f5af3431c4d3eb0041181970ebfc4ef63e15f712f3f0882bf6ad45c14698ac62d1c58b687b3811de87e2a3f6bad2a04382e70b98e225680285fad80678 \\x00800003b7aee48ab5d2db9ca0c90bdd4e6248b88117b984a09d3d1fd369373ae92229daf97270d0898958e32507471ab8e0930b63d4cb30585e5dff7f5c11060991ec3bf00c0b6a492be39595674ef0cd1c139c9ad9e04598a97e01ae51d40869c9011b75b7c5cff426c314eb1deb36b2d306e7ab0f03c157b45171ed814523d66ed8d9010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xc981df81e9dbc3700b84e6051a1f6936c7619ea8ec93f01b215754ebeddaa57bc1bac97b902c06ba6a7dd4c91a78ac37bad69e0540214de3649472fdbefae301 1585851710000000 1586456510000000 1648923710000000 1680459710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x423cb1aa741ab0a5237c1a1e96d8564a5cd1a4e1cc092690215c66d33c2ac3bc55d265997f49cb54405ba72c621465571b26dc781c6fc224ebdd418042540ffe \\x00800003cf2c3266dda5e26deeed6f12cbd216497461ef98d732286109cc866bbc281bacbea593f534e77700c6caed6084311fd243f058c53d622b69d7ac525f2eea33ad8796dc6ac6eec5af846df48c413665614c4e92c3cb4f6f3f03dcef1de33e3700ff2d8ae413366d3c92d26d46267c0027d0dec324da79805d85710790afdc1a8b010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x8b37dccb31a6b8a8a860856db7c2695f122f6170750f6e34e11d0eab20eb0bb23ec00c63107078f8015dc29094bf65e58680672fdd43176dde36561acab8e303 1587060710000000 1587665510000000 1650132710000000 1681668710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\x770c748e581e8ed7edea219a09ecb0018a2015d5a8017f746511950f5da1c4ad1ded37a5f054bb1b0b8902cbd3767f9b0cf4e0a8e5a8f29bf555eb89dfe78fad \\x00800003c25aced68214c1987b142f6407c6a40b01c3cac34606abcc69f71e13c5e3064b85a6b0a4ebeab7350e785887cbe9b1bc89746f16488352003fb00b202c71d1dbef46651c18b0c25e080a2a17c9c05f00c76c07bdf01399026a51f9d5e4337e9e4585d7dff0b2ec3ed02db6458c297dc892e3542037180b15993904eefaf64013010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x53048fbf584f2c38ec2fce2d2271bf392eb59fd00579ccf248c0bdfdc1014b60393ca1e5abf0612209b8ef7d787ae009398091b162c4a0a7f4eaf33cacecae09 1585247210000000 1585852010000000 1648319210000000 1679855210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xd24ed264c3aed6dc5e0b038789f8b96c7a39e5afad1a6d1152e7522dce16cb07057c685ebb3ec13c4d813e7526934a0bedae3a3f69ff5ed67b17ce19c3900d07 \\x00800003b7063021c33fe1a2350cd57dd0460a9b87ac5f1e1de626400f6c07b7b930180a8183753725882ba4b484a3995ce50f69bfcaa55decbec1dee1474478ed1b09590672eabb0ec284c40adf1821da15855b79226ab705b915f1a1c7700b8534fb5d8bca6183ff89a975bd772dadb4f4facd3206f048ed71cbe61732557e5f09ca6d010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x9927a1597b6a4ebd9f1a220dcdef44af6121de38591ba5589a9fe360557967d20e0018190feffd4cb403b22bd736415e6de8f3c86b9ad208f6f086374b62fb0b 1586456210000000 1587061010000000 1649528210000000 1681064210000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xf83190c4582e1152cd1d1cc323b2b0bce4603dac971c160babf3389cf5e585796e4c3973db82924be749ba9655471d73653774df3963cc86535f423fd628ce48 \\x0100000398c7409ceac2a45d8107fbc805d73def578387ccc929c08f6ef999c1d31656b8aa88bb7ce82709cb61fc71aa1866d4c208e875e0da6edd6677fc3ee1f4dbf1096ece98873937a4a06c1446653db2aea6bd51c3a288b619420305035c785b9d78120fdf2900bdb51d388c130f66871250ae7fc84e0848f44e4edae8c61811cc9b35f5b8c280bad4103f47febb3476df92ec7feaa3133b3f128e7afe0095b37f7e149c44d2f92e107e544dab6e5926bd3943522d537f0f7ffd201869240e9cf8c6fa11ab840f0ba0f6c918ae451bae71b590ad8c540046e31346f9b0680782c1d5f7a5841c853d39a89da685d31dbe6437e5c1a15b699799297c64d999ed3b3f1b010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xcc08ef004e961fa31c20bae17bc8dd22840cbb448a944006abb024f6bdcaa788cce8a5821f3d11f329b3940480504d6464863e4ff5219ca168cb60ea9b17360c 1585247210000000 1585852010000000 1648319210000000 1679855210000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xabfb9c9fcaec888f4c41cc0caa0697e857c7438ffd5b44ad31c6a03706e9f507e47b84042a40b40bbfce9ac3086337f1f93a3dd6c73b81b5dbe6b8008d0b195d \\x00800003c3e1272d04100a4da90c1c1fa880a9ce307147aea9c3446fc8d8af0c79c8bc5c3d18245f1d0de8341b7604e55f037c699023b1617f4a680b5af11c7667bd179ebc624ada5e9a566f4def432059e4b6987a003f9e0d1642d78f881d35f70e88596c8fc9a04b3fbd58db223c3cf67bf5b918d49c42b8f278d5b9fdf4c22e9b0e8b010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x92b719ff98607bf673e7fd296fb90715bbed78a29c460596bfd6ad18b654e1c1c6710b0e9b2cccf306d33a9d23ba0b69e6c5aa50f172d8a0b4855903f8411603 1588269710000000 1588874510000000 1651341710000000 1682877710000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\xa901213e3003e1b9010fe14ecec385d940c1062eb55665cabd1b7ecb4bb221b5dc4a504edc0a98341564c526e65f765c0c0339a9c4cacc14047f921974c0bc3a \\x00800003c2fc18727b002faaf51a748d609df4f2decef150315870e32ac863700490a84ad74da172a383e7cac437c2d7c01952fcc8e20acaafc0a95f7bbd39d3ecc16f9176fe346c41124efa51ca753226bd8f63439fcb019f191bd06b28c88144325070d8c30c0647ba0649bb6353da90f52e7456b2d15a91dd83fc08623928e91d644f010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x078d5089fda1a6ba991a9c8258c1502fbaa08a61171598fd716f7733eac65c063bcd1de3f86078518e41803bfe873aa14371418562511455979425568713a804 1588269710000000 1588874510000000 1651341710000000 1682877710000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-\\x6fb5795135c3e9dda4c1f9726330f5c2133c1f10d75784c9acf91e2f5c480a50bcb7c3af863a2ce41df9aa20ec99eb6f9ca328ba3ce70718d4bbde98467f9077 \\x00800003ce0c661bdd3ecfce5c3c0a4b600dce4cfbae366e89a36980c73737a3c885b9c18e0bee6aab678ceefb5c7d1fe23625735c1528624e06eb7855e27e633f0b968ad0d28c883ffc38db707d9800abda97d68ae9269bf765f40f7155ae91a7d12d5aed6ba9884c6d2b5a99c4e3fac277e21462f227fc04f2be4a049e16d8aefa43eb010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x20088dda14c294c0d56d5afbd26981e3bc5b22cfc9a1f008f025030fe4811254bd72ed03207d392d88db631da56c4a718ebee6f898b042e75ba6dddfecbe5a0d 1588269710000000 1588874510000000 1651341710000000 1682877710000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\xf67f29265d2ff217d3b10a405f10948095527a1130bd9940b449ef76bb911c4fccb4b2e3d16d7e032f91a5eb71ab1d4a67df4430f69707ac71b5d39b30659f3b \\x00800003e006a9ea892bd6352b09367421a6434bb14481eef0b9815dbcaf34939bfbf1ab545b0795fd39b1fed1a56d52581e7ee5d0143c9855f0dae8a25046369998320fbba3e6a51cc772110aca3e9871421242b3b1ac5416f5adb1989cf65508a52f0a282bf3017210f8695540531d4ac25c91c4095b3759da44661f34f602d373fa85010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x649d8a73c8d858bb8c82f1d03748c165ae5e6f2c91ec389fefbd1c7fe9f6366c118cd980435d44b818f7051b8fdb8d0e861a77f90cae5c43d9de0f753c8ba70c 1588269710000000 1588874510000000 1651341710000000 1682877710000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\\x88337537317cc8b51018fb4c4548bbedcbbf28c0c2eec201d8352f9110d08e80d3e1449a0008f62e5f44a27efdaf5ffa99c34abfb0279e2cb7fb8e9e9c091ed3 \\x00800003a9fb3a0f9cd59f01975206a2466546bb1151c79e3fd721f46589966b81277b685ddd768f58c505b1a40de62e87073e623748ccde14fe11933c25c1d1d9d9989c501fec933b04921ef94e72e2f27b403b3935d4905a7df21d1943e372195315e62379ed7c040f4d1110eca8095ccc5dbdcf2bcf533ac49db010ca6bea0dbe664f010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x81f04ba3211ad8c24e7e82241511b8ada9087df9cf201620092db4eeb91a52b3bc499e1e9892802972eb3dc82b6b1ee72e445f56e131a7f14d6ae5514e318904 1588269710000000 1588874510000000 1651341710000000 1682877710000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-\\xfd2ad49aab62ebae12f33fe6de7ccf47884d467965b906937194b5fc4bff011da194010a26d10aa3fe51d376fa91f6b24e4a874b9641b755ec78ff6330f913b2 \\x00800003a13560cc33a4d3c46ed4936079d4a857a36313d950aa782e0b2e7caf438d76300c0a8f827bc691451755ccd1a12d1520af895de882128c92b21cd270c5d60815bac69ef00944c7389412b801b5ac04553cf4bf77c15463c3ee72f4a69b4df0c32f0af8a34e9d2acd478f60787be2769ec73b08e6ff4248fac33decc33727e75d010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x1938eecca25bf04c6edc549362d35d30f4b435d2b3179bb2faa786e70f0c2e23da53c4528ede3e781975823f597309e52f647189c81fb4458671d394a3cbbc02 1588269710000000 1588874510000000 1651341710000000 1682877710000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-\\x47c01b601fcf1fb694b873ffcbbf6f29a967a62a4d8bb117224dea0059d89a75336a58a71a14a0c8e08474116ede3af09278f75027f5017ec8678de2b3a9fd79 \\x00800003bf5abf26d345a5a8d9c4bbe7566c68c34c8e94f38078dc5c1e044071954d2aaa75d0c91346c324af56b6965e4c6a36068820f98ef77185043b95a05bbd4f0a4912a0bfd9cd939fc990ecd85d3ad2ead9d5dcce378ed35c08ebdfbc6206887ba2d84fde2b64506a66fd84ea04f8fad937565f2985f2df0dd2213ea4db6240cdad010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\x76672d0041d15cc3a3b29152a0136bb51bac0e056d1bf27398dc9ec8160134281c3e0b38e6ef6ff0d5cd781ad758519a7941b131a221eb7179d8c01c5d73910c 1588269710000000 1588874510000000 1651341710000000 1682877710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\\xea07a69a2e207613ee0342ac42375a512a5de80c2de1fe45b8e02fc088afc54e57800714896492d7592f1760ce39a52b2e993f188ae22af2b581338541df9e6d \\x00800003d584493d65b12ffb09660cdf99fe7a0e73cd2bdc171f3842ee4553b1adabd4fc6cc66898994cbe304f1556bfd146b10c0d99c5f760d0914617e50e5ff2bd3f87afef5be664878951b684de9c04c402845dce1686826657ac3f265a83f5a83ac164d9a565bd76b3e7d7ace886df11e36b43cc261e66cf72273a8569bd1dcaa323010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xcc6fcc2c5aafdb1143b18c713156d7804d6fca3da80a5eeea5819ddd1e0f528a0cf3bee9e6465d1a6e935f8cb83c42c422b30029360188c2cb47d8dacce94c05 1588269710000000 1588874510000000 1651341710000000 1682877710000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-\\xb09b6217d72bdb8f393332f676b9c85c2e246a6685c6f4ee8e96384b23ca29e89e587f782d24625001c7cd6cea13e85fdcc9591452f43c6d717d24297dd2dec6 \\x01000003f03df875cb648ec841dc9a2f1b022dbbcdda85a0a751e4af31f417d93ab82940a39cd6a012173fd2d23660794f4c6abfa808a096ac80693b1e37ddd90e01ceff63814c30b181cc571366f996044b43e8e8353b1aa05e9fb104d54a4cc808b1fa007a052531ae857cb0cf835ffbc6eb8995c7d6cc761dee167f113c315bfa15c54fc5c70a1afc157e9b424863b27aeb1f3f2418f5d8e4704bb950cf697e45af263e03330804c618a4cf74c2b8a291a5c1f5038b829a8f8dd0e0574c15ec31a04f423d7b47496429b1fa76512f90fd1dc1c305a49e8384b3459b2d9ff8b7dc2e156e606c102cb0e73d21c70cd97e602f07ba29d6e884f2ee8579f25525a6b0215b010001 \\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xe7655b0a2a0fe1c405b2dc7172852c07c33964ccae520ed175a6a6901090a3d9d9d1d079c57a8789ae1b2e9b49c6f86f8c3fcbab1395d004fba86b8354012202 1585851710000000 1586456510000000 1648923710000000 1680459710000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-\.
-
-
---
--- Data for Name: deposit_confirmations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.deposit_confirmations (master_pub, serial_id, h_contract_terms, h_wire, "timestamp", refund_deadline, amount_without_fee_val, amount_without_fee_frac, coin_pub, merchant_pub, exchange_sig, exchange_pub, master_sig) FROM stdin;
-\\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c 1 \\x33dd12045a32c69793ec8aff33d4ed3a18e96490ef0e6a95c4216eece585e79c4589523cd3de5c5435b8891935944c19f544c1901152fcdd224bb6daa90793e3 \\xb5a83c13d972e767ee6746d0537d32cffadc8a0af078212c858c4e74d835e965707f782edb666ae541402ec83b136213951e730ec01021fd0d5679583f67e8d8 1585247238000000 1585248138000000 0 1000000 \\x24209fe74c418f2babfdad3860e58645ea750c60e1d1ad1ed22c110ab2eb83a0 \\x4d3d827abb497a7696f0efeea4d0e9f1eda12fd5718a1f5bba57c27e612144fb \\x0417c7c22bfd7a0af863e0969e493b14e08a9cd6123990867bed9c82baa325518ad5f0400b22d1018754440ca079c758498edd2b806303aa206bc4a208e61e0c \\x946e8b5af562e6bdae3f9e9f1996162e81eb835d52f6b1455194d1c8a4afde69 \\x6de6e4b001000000e0a5ff512e7f0000e38dd6d74c560000c90d00382e7f00004a0d00382e7f0000300d00382e7f0000340d00382e7f0000709900382e7f0000
-\.
-
-
---
--- Data for Name: deposits; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.deposits (deposit_serial_id, coin_pub, amount_with_fee_val, amount_with_fee_frac, "timestamp", refund_deadline, wire_deadline, merchant_pub, h_contract_terms, h_wire, coin_sig, wire, tiny, done) FROM stdin;
-1 \\x07fa6375f8679153a23927ce068b019acc3d50fd696e74caf09f2fd776c21cb8 1 0 1585247230000000 1585248130000000 1585248130000000 \\x4d3d827abb497a7696f0efeea4d0e9f1eda12fd5718a1f5bba57c27e612144fb \\x8390d204c43d8418bdf4a2e5466fb50084a86829ab5b445cd38c199bfdf433a6b2dcb78d977ba238be29fed3640acf62954837308581bfc86bac0c48c0d08997 \\xb5a83c13d972e767ee6746d0537d32cffadc8a0af078212c858c4e74d835e965707f782edb666ae541402ec83b136213951e730ec01021fd0d5679583f67e8d8 \\x8a54ca1e621e70637f08def917a9e7bac14892799508641b2f9d4b82418f05e7c5eea6d732b6782c24b11be23e746161027aef3fc4ee41cf9d69aa775b88710b {"payto_uri":"payto://x-taler-bank/localhost/42","salt":"35XFPHEBXDG1F1XRNGC4299K14CHFQKKBPX2WTSD748Z2ARQR0ZWKM6TWMY3Z2HN1K1AB4HBMBBX21NHY6Z120RZNSASXDX163XZA2G"} f f
-2 \\x24209fe74c418f2babfdad3860e58645ea750c60e1d1ad1ed22c110ab2eb83a0 0 2000000 1585247238000000 1585248138000000 1585248138000000 \\x4d3d827abb497a7696f0efeea4d0e9f1eda12fd5718a1f5bba57c27e612144fb \\x33dd12045a32c69793ec8aff33d4ed3a18e96490ef0e6a95c4216eece585e79c4589523cd3de5c5435b8891935944c19f544c1901152fcdd224bb6daa90793e3 \\xb5a83c13d972e767ee6746d0537d32cffadc8a0af078212c858c4e74d835e965707f782edb666ae541402ec83b136213951e730ec01021fd0d5679583f67e8d8 \\xf4976cd0bb732303872537f5f82dcb90da48297da2475af6d279607bc7b77a7a973344b14795c1e7f0dc7992e43edd1975f71bdedefc348a032f815c07a6d10f {"payto_uri":"payto://x-taler-bank/localhost/42","salt":"35XFPHEBXDG1F1XRNGC4299K14CHFQKKBPX2WTSD748Z2ARQR0ZWKM6TWMY3Z2HN1K1AB4HBMBBX21NHY6Z120RZNSASXDX163XZA2G"} f f
-\.
-
-
---
--- Data for Name: django_content_type; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.django_content_type (id, app_label, model) FROM stdin;
-1 auth permission
-2 auth group
-3 auth user
-4 contenttypes contenttype
-5 sessions session
-6 app bankaccount
-7 app talerwithdrawoperation
-8 app banktransaction
-\.
-
-
---
--- Data for Name: django_migrations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.django_migrations (id, app, name, applied) FROM stdin;
-1 contenttypes 0001_initial 2020-03-26 19:27:01.883355+01
-2 auth 0001_initial 2020-03-26 19:27:01.911343+01
-3 app 0001_initial 2020-03-26 19:27:01.957748+01
-4 contenttypes 0002_remove_content_type_name 2020-03-26 19:27:01.98024+01
-5 auth 0002_alter_permission_name_max_length 2020-03-26 19:27:01.9839+01
-6 auth 0003_alter_user_email_max_length 2020-03-26 19:27:01.989985+01
-7 auth 0004_alter_user_username_opts 2020-03-26 19:27:01.996036+01
-8 auth 0005_alter_user_last_login_null 2020-03-26 19:27:02.003583+01
-9 auth 0006_require_contenttypes_0002 2020-03-26 19:27:02.004971+01
-10 auth 0007_alter_validators_add_error_messages 2020-03-26 19:27:02.010342+01
-11 auth 0008_alter_user_username_max_length 2020-03-26 19:27:02.018993+01
-12 auth 0009_alter_user_last_name_max_length 2020-03-26 19:27:02.02738+01
-13 auth 0010_alter_group_name_max_length 2020-03-26 19:27:02.033904+01
-14 auth 0011_update_proxy_permissions 2020-03-26 19:27:02.041875+01
-15 sessions 0001_initial 2020-03-26 19:27:02.046428+01
-\.
-
-
---
--- Data for Name: django_session; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.django_session (session_key, session_data, expire_date) FROM stdin;
-\.
-
-
---
--- Data for Name: exchange_wire_fees; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.exchange_wire_fees (exchange_pub, h_wire_method, wire_fee_val, wire_fee_frac, closing_fee_val, closing_fee_frac, start_date, end_date, exchange_sig) FROM stdin;
-\\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xf9099467bd884e86871559a62a7f23b6e876bf084a30371891b5129ce4440d3cbe27afe387d39b2ce8d9625abd388517c81bfc8da9f2e0f8c9471bff65a802b2 0 1000000 0 1000000 1577833200000000 1609455600000000 \\x464682af73ec741329585433751b206eb2520bb65470ef018faa1c485030057220b883b6b968b8fbff256cfeecf2f995830ba20dc6a356cdc0207314d5e2af0e
-\\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xf9099467bd884e86871559a62a7f23b6e876bf084a30371891b5129ce4440d3cbe27afe387d39b2ce8d9625abd388517c81bfc8da9f2e0f8c9471bff65a802b2 0 1000000 0 1000000 1609455600000000 1640991600000000 \\x472c8ea2c7e1e8cc0bc5c55063f4cbe3d67b2e94355c4fd702b14b64d2986090292f67493a9921a63aa67057c5d169aa2e7f96b4c5d729879675e6d07595dd08
-\\xac008e081df3aa3a18c395c1a8c33888afa3f863481231bf79fb8031dd2a4f5c \\xf9099467bd884e86871559a62a7f23b6e876bf084a30371891b5129ce4440d3cbe27afe387d39b2ce8d9625abd388517c81bfc8da9f2e0f8c9471bff65a802b2 0 1000000 0 1000000 1640991600000000 1672527600000000 \\xc5ced6e9f6914133e532678d4136ddd79180a7465a0fe8f11b9d47f74916b71c5edd6f5064da771d819386714d6bb8cbc252a14554cae356630008a81b7ac70d
-\.
-
-
---
--- Data for Name: known_coins; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.known_coins (coin_pub, denom_pub_hash, denom_sig) FROM stdin;
-\\x2750ad23f959b4616f9bb9913b2c05789ac60905a9ecccd2a499f99ec9d40d0a \\xe0726684567b8f6e972ae98a1c743d83344292e41f48d2696272bbe7d7cf28e4fe4ca576cefee21dcf4d05dc1927dbed383bd6383af4f6919c6185cb86c29fc2 \\x974ee8270e59824583948825272df1816ad14b0304ec9b37546fc91f8946d27416549486ca279f35b054390663c0475b1f29b9bffdec6bb2450029b742d7694ea034f18f439b17ae7aae6f8acd8f3939d9a4c3fdfb7bbc448993d8b4d082df47d340c5e62faae002d728696cbda03eb4362aff1d4292f6f9d34d6ab6f6685ad9
-\\x07fa6375f8679153a23927ce068b019acc3d50fd696e74caf09f2fd776c21cb8 \\xfac60747f6f5230990e394d61524d5e5c2d7fde9d64cb67971383afded993d28b8ccc24859da6595521f2131f8b29f7249a132b21bd783ddc17df6743baa690f \\x83c04be2454671a89ace301cbb411f274541dfd4f7507b8087902a9b291ed676aec6f727f4d6f1a3e0d476cbbc2168e506d35a832c3e0ccbca28ce88b25c32756368ab972d2fbe5e05f665941beed0282f0ddf81cb41324ea5550294cc3f182c61578a09aa341ed5413fcea49821ce673841694ef953374b07dbabfaa444cfc9
-\\x39ecdf0cece28769866927fdcbc0229e73e0e6458efd948eb07278b8ac0d738c \\xb2b398d560e595d6d879842cd209cc84c924a57538ffe918aa6f3107492495740d2dadd63f34109c3a19caa665eabb781359cac5c496e56a36bfc6926d3a45a8 \\x613eea075a730f76e14eeb41e1baa6eec22ff929880ca8f587f66f402f627d4d1bcfa3936042c55be7cfec44313d25c5a16632314a6d235b416de39be3ba132789106d61a1adaedc8c54a100fc9ac6aeadd2032a4e654029cc67e8a3db891f69915aff2e8519484ad76bd56b2c5be8f4837d6bde18cc5f4b09fd0a679b91cacb
-\\x5dda7191fa7b5e84562493ff81198c38d00b847b9c16c26d8b92b906781e7560 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x80074868b3e549225c6c87faa8737dc832aab8407f5d1fb65904eeb4045ae170b002443a63dbd769848f172d8b9e17d469d922fa4cffc6381f1f3dac00266df603d42a70eec700b69b9ede18a95bde9c7938e585a4a48d9c721fd947ae66686487b029cba41e527bdf8ebdc86c5dbfdad788f49b4dc2f58a6403995101e487ee
-\\x06a8b55926e31e43b7514c608cc267fe498bbb6ee62655ef792c71966af1fa97 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x9e2827f7ccd5b3444a171d2279740abedb5ee623092e02cd602c7454855475b92de52cfa3dfbe9f060ab8f29f645af7b4cc230575212ad52e9bbe3da8affe17bbc42699e97d81b2fa334c35977dd17716fcf2bf3aee3c1c24a2ac727f26d64554e171f7fac975e08c81ee61d7671ad3e8852fac6171757d1d7c7d39e2554420f
-\\x4c647b1aa4a9b73b44a797b104bedcb9dfb4d46ba36b9e7b3f2125b3ba761c4b \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x98337fbf9c56519ec1de2aea2ea0162e13691aaf954b8ac8a32067d7f1a45a5866e99ccb03932bbf1c445100ca70363b4cf28b76e3bc6443666c677ed02a74251d32beaf3a978fef137aa00d655079e32df3bb495d02998bbe41d0e552178bb36f15d10f25f6e609c25ba69a06da8a05e65ebf01ab4f22b7b18373248506956b
-\\x6582a1874903dfdb722dbfda9c54cf0715fc63801ed583deb18e180bc8332a22 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x3e9426a6e6f7190342e4c2145619544881470c114cd752cc6648ff3f96d4970ab6c809695dbfe879c60cb03014b8d3714f3a95e8f6c8627271f5ef5e1af9eed4db7257ca7367491b7e99d0342cb57274a4be51f873b1892cf5a1ec548e59e2e8e0919fa4c47b15fc5f973630971e5f7acb3519c372432562b97384f5daec9245
-\\x310b12c172bc4c7af4a0afd129a91fc88f59c07a7a9b78d528971be425169509 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x851da010c78e988cd85fb4ec0e7df7ff360af246eb7e11f2843490c535375aab07f615c7ce1f98e1f636083f44d8e4dd208936f7355b2f074a06ae2e2aa8d74a53e8330999df6667c65a2dbb452b58956928a47be6bbcdb2df9d4fbc802080a54c2cb7bf0a73cadc6c51bb7473bc15b190635c64527c05c0910a2515ed31cf72
-\\x66e756ee0c152112a5a31e2127d3a13df65fdcc6a5d3de9abe803ed293508f05 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x0b100148c18896ae2e7ca3b18f2e7b83497217c4a05d2a64b69e233264dc6f2b817871aa1dfd074d3b48e29fa20f782a45a66922a0d805e7f91b9431b19dcc1bf1a11fbe6165def68d54eb80f5bcb7b37beb38641168ef15d8812b9682dc7d1b3279e499632139d96b7996bb8d88b00b902199b7126d7a492517d673b57a1cb2
-\\x4a4693f51d818eb32db0972f19710b2450e3514033fa33e6588c90e265b56095 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x563182fed54bc151b33f935ade827faf970a393f756f674ac85e31ad6e02b599f9c2ec8498c7b0e9441acb8c68da3688b56c2ce7e26b4aeaa5421356c6149aed33a9832e8f5903f2a140ca499d171d156c04879318ba1f900bc0eae9afc6ac0094d68ad782ddbe49a712c5a59a6a9125217b82c53ee99b4a0a37739872098a4a
-\\xeefcdce7677b4b91ba60c28abc7f2f827d8868e09abfb439567e255cff085137 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x9a239c847a3235355ee0ecd526d486f213f3df2a4dcffc8e3e0d691ad59007995d01e6f4723eb9334588a14b993ab4f039bf85efcaa58a251e9b7e64525f0a0a8084215ae18b014405b73ecce7e6e840ca5ae0de3ea46bfad2b81f2e38ec5a5961e2fa3cbb7c61fb67146cf82528e1a9b987be9f83bb7e2927a557d70a00835e
-\\x24209fe74c418f2babfdad3860e58645ea750c60e1d1ad1ed22c110ab2eb83a0 \\xb09b6217d72bdb8f393332f676b9c85c2e246a6685c6f4ee8e96384b23ca29e89e587f782d24625001c7cd6cea13e85fdcc9591452f43c6d717d24297dd2dec6 \\x1aa15032c925171a476ee694c75fcbee781cf98fe40034ca13f8369a7e8d0c9a0c1afca19353db50f3c8e6a8ad2fdabfe69c9786182df0a064ca5217c5e0d91882cf7d45109f1fa0435eed069ba3ed872c986fed3c7c6130d2e918733d76e262aedb0ddd4ff03d035dddfd9b65f7de41e1259c0ae5bbf3599841844ff3f0f94b0330e01da7ebf001755604aed05b7372c197bb2812d34e377b104a0026cf7afc36aa09736c8344d7d232f88eda43bd2f540b9844d5334a2834f2c4ab61bc25d43377391b596a47efd666c56c58c524eda63618db277abdfa776230bca74abef035e1996fbdbe5bdbcb7f6a2584141ac43fc59965a05b793c8ccfc22e7faf4c26
-\.
-
-
---
--- Data for Name: merchant_contract_terms; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_contract_terms (order_id, merchant_pub, contract_terms, h_contract_terms, "timestamp", row_id, paid) FROM stdin;
-2020.086-0104C3T13YHRE \\x4d3d827abb497a7696f0efeea4d0e9f1eda12fd5718a1f5bba57c27e612144fb \\x7b22616d6f756e74223a22544553544b55444f533a31222c2273756d6d617279223a22666f6f222c2266756c66696c6c6d656e745f75726c223a22222c22726566756e645f646561646c696e65223a7b22745f6d73223a313538353234383133303030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313538353234383133303030307d2c226f726465725f6964223a22323032302e3038362d30313034433354313359485245222c2274696d657374616d70223a7b22745f6d73223a313538353234373233303030307d2c227061795f646561646c696e65223a7b22745f6d73223a313538353333333633303030307d2c226d61785f776972655f666565223a22544553544b55444f533a302e31222c226d61785f666565223a22544553544b55444f533a302e31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f7075626c69632f222c2270726f6475637473223a5b5d2c226d65726368616e74223a7b226e616d65223a224d65726368616e7420496e632e222c22696e7374616e6365223a2264656661756c74227d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a224e4730385732305859454e334d3636334a51305448475352483251543759333339303933334656535a4530333351394139584530227d5d2c2261756469746f7273223a5b5d2c22685f77697265223a2250504d335234595345424b5046564b3738563835365a394a535a5844533247415931573232423435484837373950314e58354a51305a5652355644504354513538353032584a3156324448313735385945433743303431315a4d364e4359415237584b59485030222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226d65726368616e745f707562223a22394d595234594e563935583744355147585a5141394d3739593750543242594e4536353159505854415a313757523931384b5847222c226e6f6e6365223a22573041334b33595850324d415150535131305752543031515a4d523831524a44413244445346583150473345373830504e594530227d \\x8390d204c43d8418bdf4a2e5466fb50084a86829ab5b445cd38c199bfdf433a6b2dcb78d977ba238be29fed3640acf62954837308581bfc86bac0c48c0d08997 1585247230000000 1 t
-2020.086-01WE89FA7FS14 \\x4d3d827abb497a7696f0efeea4d0e9f1eda12fd5718a1f5bba57c27e612144fb \\x7b22616d6f756e74223a22544553544b55444f533a302e3032222c2273756d6d617279223a22626172222c2266756c66696c6c6d656e745f75726c223a22222c22726566756e645f646561646c696e65223a7b22745f6d73223a313538353234383133383030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313538353234383133383030307d2c226f726465725f6964223a22323032302e3038362d30315745383946413746533134222c2274696d657374616d70223a7b22745f6d73223a313538353234373233383030307d2c227061795f646561646c696e65223a7b22745f6d73223a313538353333333633383030307d2c226d61785f776972655f666565223a22544553544b55444f533a302e31222c226d61785f666565223a22544553544b55444f533a302e31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f7075626c69632f222c2270726f6475637473223a5b5d2c226d65726368616e74223a7b226e616d65223a224d65726368616e7420496e632e222c22696e7374616e6365223a2264656661756c74227d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a224e4730385732305859454e334d3636334a51305448475352483251543759333339303933334656535a4530333351394139584530227d5d2c2261756469746f7273223a5b5d2c22685f77697265223a2250504d335234595345424b5046564b3738563835365a394a535a5844533247415931573232423435484837373950314e58354a51305a5652355644504354513538353032584a3156324448313735385945433743303431315a4d364e4359415237584b59485030222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226d65726368616e745f707562223a22394d595234594e563935583744355147585a5141394d3739593750543242594e4536353159505854415a313757523931384b5847222c226e6f6e6365223a223156324d4652374850505759313948573859585341594e3150534b54443252594556443048504551503030525347584134343647227d \\x33dd12045a32c69793ec8aff33d4ed3a18e96490ef0e6a95c4216eece585e79c4589523cd3de5c5435b8891935944c19f544c1901152fcdd224bb6daa90793e3 1585247238000000 2 t
-\.
-
-
---
--- Data for Name: merchant_deposits; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_deposits (h_contract_terms, merchant_pub, coin_pub, exchange_url, amount_with_fee_val, amount_with_fee_frac, deposit_fee_val, deposit_fee_frac, refund_fee_val, refund_fee_frac, wire_fee_val, wire_fee_frac, signkey_pub, exchange_proof) FROM stdin;
-\\x8390d204c43d8418bdf4a2e5466fb50084a86829ab5b445cd38c199bfdf433a6b2dcb78d977ba238be29fed3640acf62954837308581bfc86bac0c48c0d08997 \\x4d3d827abb497a7696f0efeea4d0e9f1eda12fd5718a1f5bba57c27e612144fb \\x07fa6375f8679153a23927ce068b019acc3d50fd696e74caf09f2fd776c21cb8 http://localhost:8081/ 1 0 0 2000000 0 1000000 0 1000000 \\x946e8b5af562e6bdae3f9e9f1996162e81eb835d52f6b1455194d1c8a4afde69 \\x7b22737461747573223a224445504f5349545f4f4b222c22736967223a2259413239344743533532314d4b46394e5831364b353630503942515341514842524356474e4d424443413748444b4d473850415744304d3835383936445330373433544a4d50425a564443454a41583654504a30424738595a44524b5a4d575354585a51383152222c22707562223a224a4851385050514e43424b425642485a4b5446484b354750355430595130545841425642324841484a4b38574839354656534d47227d
-\\x33dd12045a32c69793ec8aff33d4ed3a18e96490ef0e6a95c4216eece585e79c4589523cd3de5c5435b8891935944c19f544c1901152fcdd224bb6daa90793e3 \\x4d3d827abb497a7696f0efeea4d0e9f1eda12fd5718a1f5bba57c27e612144fb \\x24209fe74c418f2babfdad3860e58645ea750c60e1d1ad1ed22c110ab2eb83a0 http://localhost:8081/ 0 2000000 0 1000000 0 1000000 0 1000000 \\x946e8b5af562e6bdae3f9e9f1996162e81eb835d52f6b1455194d1c8a4afde69 \\x7b22737461747573223a224445504f5349545f4f4b222c22736967223a2230474257464748425a4e58304e59333357324239574a3956324b47384e3736503238575331314b565850453835454e33344e38524e4e46473830354a354d383147584134383335304637334e474a4345564d4e52305252334e3847365148353231334b31573330222c22707562223a224a4851385050514e43424b425642485a4b5446484b354750355430595130545841425642324841484a4b38574839354656534d47227d
-\.
-
-
---
--- Data for Name: merchant_orders; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_orders (order_id, merchant_pub, contract_terms, "timestamp") FROM stdin;
-2020.086-0104C3T13YHRE \\x4d3d827abb497a7696f0efeea4d0e9f1eda12fd5718a1f5bba57c27e612144fb \\x7b22616d6f756e74223a22544553544b55444f533a31222c2273756d6d617279223a22666f6f222c2266756c66696c6c6d656e745f75726c223a22222c22726566756e645f646561646c696e65223a7b22745f6d73223a313538353234383133303030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313538353234383133303030307d2c226f726465725f6964223a22323032302e3038362d30313034433354313359485245222c2274696d657374616d70223a7b22745f6d73223a313538353234373233303030307d2c227061795f646561646c696e65223a7b22745f6d73223a313538353333333633303030307d2c226d61785f776972655f666565223a22544553544b55444f533a302e31222c226d61785f666565223a22544553544b55444f533a302e31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f7075626c69632f222c2270726f6475637473223a5b5d2c226d65726368616e74223a7b226e616d65223a224d65726368616e7420496e632e222c22696e7374616e6365223a2264656661756c74227d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a224e4730385732305859454e334d3636334a51305448475352483251543759333339303933334656535a4530333351394139584530227d5d2c2261756469746f7273223a5b5d2c22685f77697265223a2250504d335234595345424b5046564b3738563835365a394a535a5844533247415931573232423435484837373950314e58354a51305a5652355644504354513538353032584a3156324448313735385945433743303431315a4d364e4359415237584b59485030222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226d65726368616e745f707562223a22394d595234594e563935583744355147585a5141394d3739593750543242594e4536353159505854415a313757523931384b5847227d 1585247230000000
-2020.086-01WE89FA7FS14 \\x4d3d827abb497a7696f0efeea4d0e9f1eda12fd5718a1f5bba57c27e612144fb \\x7b22616d6f756e74223a22544553544b55444f533a302e3032222c2273756d6d617279223a22626172222c2266756c66696c6c6d656e745f75726c223a22222c22726566756e645f646561646c696e65223a7b22745f6d73223a313538353234383133383030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313538353234383133383030307d2c226f726465725f6964223a22323032302e3038362d30315745383946413746533134222c2274696d657374616d70223a7b22745f6d73223a313538353234373233383030307d2c227061795f646561646c696e65223a7b22745f6d73223a313538353333333633383030307d2c226d61785f776972655f666565223a22544553544b55444f533a302e31222c226d61785f666565223a22544553544b55444f533a302e31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f7075626c69632f222c2270726f6475637473223a5b5d2c226d65726368616e74223a7b226e616d65223a224d65726368616e7420496e632e222c22696e7374616e6365223a2264656661756c74227d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a224e4730385732305859454e334d3636334a51305448475352483251543759333339303933334656535a4530333351394139584530227d5d2c2261756469746f7273223a5b5d2c22685f77697265223a2250504d335234595345424b5046564b3738563835365a394a535a5844533247415931573232423435484837373950314e58354a51305a5652355644504354513538353032584a3156324448313735385945433743303431315a4d364e4359415237584b59485030222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226d65726368616e745f707562223a22394d595234594e563935583744355147585a5141394d3739593750543242594e4536353159505854415a313757523931384b5847227d 1585247238000000
-\.
-
-
---
--- Data for Name: merchant_proofs; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_proofs (exchange_url, wtid, execution_time, signkey_pub, proof) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_refunds; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_refunds (rtransaction_id, merchant_pub, h_contract_terms, coin_pub, reason, refund_amount_val, refund_amount_frac, refund_fee_val, refund_fee_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_session_info; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_session_info (session_id, fulfillment_url, order_id, merchant_pub, "timestamp") FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tip_pickups; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_pickups (tip_id, pickup_id, amount_val, amount_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tip_reserve_credits; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_reserve_credits (reserve_priv, credit_uuid, "timestamp", amount_val, amount_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tip_reserves; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_reserves (reserve_priv, expiration, balance_val, balance_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tips; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tips (reserve_priv, tip_id, exchange_url, justification, extra, "timestamp", amount_val, amount_frac, left_val, left_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_transfers; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_transfers (h_contract_terms, coin_pub, wtid) FROM stdin;
-\.
-
-
---
--- Data for Name: prewire; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.prewire (prewire_uuid, type, finished, buf) FROM stdin;
-\.
-
-
---
--- Data for Name: recoup; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.recoup (recoup_uuid, coin_pub, coin_sig, coin_blind, amount_val, amount_frac, "timestamp", h_blind_ev) FROM stdin;
-1 \\x2750ad23f959b4616f9bb9913b2c05789ac60905a9ecccd2a499f99ec9d40d0a \\x458250decaaee758800ede6a5f0dffa49e8a2ee946bbcfa37156d6a9ab5032f25fc83874092e1868fa17f8c747547f1afc956cb57a9f39989f14fec6bd6ec007 \\xdb7023ef2d57c4f4500a37295021194aea177622a15f4d8c11fd0136b269265a 2 0 1585247229000000 \\xe4c580166cc482aeed98e877c5c4b6d288c458c78ffdea5eff38729a2412bf787c7a462e5705103920288717ef27a3dbabd1c1c07ad7ea425e25ed7763dcc1f0
-\.
-
-
---
--- Data for Name: recoup_refresh; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.recoup_refresh (recoup_refresh_uuid, coin_pub, coin_sig, coin_blind, amount_val, amount_frac, "timestamp", h_blind_ev) FROM stdin;
-1 \\x5dda7191fa7b5e84562493ff81198c38d00b847b9c16c26d8b92b906781e7560 \\xfd217b015e0ccf9054b607a5ed62f1eeccbf65ed85277424418d9992671c38272eaed29d337a8e3542df1b650cb07abe436156d0790a1fcf0f902aaff6e32a0d \\xa6257f6882d3cc5c3c9b882d692f7e5736a732bb3396bbb3975bba0b18e6321e 0 10000000 1585852036000000 \\xc88c70b05f6f8cff8c173bcf1b223478c51c4497ba93c2472fe4dc76d00689cb4951a8ceca4d00944d8bde9cf2aac94db5e129c6e5be44a1a7ff8a864b3bd0d9
-2 \\x06a8b55926e31e43b7514c608cc267fe498bbb6ee62655ef792c71966af1fa97 \\x8061b43e162908e52a6ad932109c20125708e1aac8c147a95d9fba80fd81f1c5e4d903f83656f6ca9ec3dfaa4bc1533adb3c5f972c19acace87028551b822502 \\xf6d96f6a32d51fc9eac7282ab677bf1639f62d8d78e1fa9d159d6507b492ca27 0 10000000 1585852036000000 \\x33b42711b523febdb144fd930a49b050ed9108da4f653c9d7ff79c6fa3a77942449f6abdaa9ec0f43f8b2ee892082dec6e8fd86d244be5cc7504ae7b625420e3
-3 \\x4c647b1aa4a9b73b44a797b104bedcb9dfb4d46ba36b9e7b3f2125b3ba761c4b \\x093df9c0402076959641e4063e3aed2278bf4d52eb6da260b72a1c7a38db4d9b8064d8bd76a8c03f262e5d805f77adc54dac03b776287b616d27fb42f7bae70f \\xe7c812758c46b02b551f8ec6c3b936db1c28a6ff9e2004668d530f9708aaae33 0 10000000 1585852036000000 \\xf98f4b7afe27fdd8a572159413cfff527f6249d8731feb2f81811572cd3719f4daf4e671fee2bf180d1a15a203a6c5fe4c8d6f3a9e00d2f074e44efe3b4c44b8
-4 \\x6582a1874903dfdb722dbfda9c54cf0715fc63801ed583deb18e180bc8332a22 \\x790190f22f2d60f54114ee168b6e9f58d2466763299c04012796678e98c1723bc99baec9c542c0d10a459b38a7e9fed7d17c470106a62098f77305955ea1ba08 \\xd0a6359ef0fb86433c3d34a89594107fac6aeb9d4b3cfed5dcb8a07087561ce7 0 10000000 1585852036000000 \\x9e88a7e41f31c1048782955bde254a663e55c6764ea9b9354918221c2c6053ec2b6832fb72a80435c0b2055d754dde03ee7dac4e3251037150180d02f363364f
-5 \\x310b12c172bc4c7af4a0afd129a91fc88f59c07a7a9b78d528971be425169509 \\xb11b6cf502182e7661c368187179872574d68836bbd7f4ddaf72ff14c04862030197cd760a129022fa70743f4adbfc0fd71c0fb36d5d9d1a299d27f7582e5706 \\xea2a6013507080345e8e68b0a5da32560da61e31f65da3c61ed0ec2da203d75c 0 10000000 1585852036000000 \\xe2d21bfbb014870b62172458af49b6b8e6a36af38c79afbfd9fad9d66420b3d8af2e98c5b6c1a16fd49aca7b74bbfdb4ccce32d879d79f2a579afec89b627317
-6 \\x66e756ee0c152112a5a31e2127d3a13df65fdcc6a5d3de9abe803ed293508f05 \\xfb67164fb92f9c194e9d11adad127973532b74e6b6d44e917cd445cd05931a3da0a0e64cee92272d1501af215f355cd015f96c6f2ab8a5a7cae63dd8b54ce90a \\x795951840313147957428fbc8f2eee4bea77042c2546331a46c9c6d0e79ef242 0 10000000 1585852036000000 \\x5def2ae83577b46954abe9fccec8930c9d9f0d825bc545b224f990545f9b6fb1c38d559677ea9252ca3daeeac5963dc79fc860a240b7b3a7b9af8ed47a679b14
-7 \\x4a4693f51d818eb32db0972f19710b2450e3514033fa33e6588c90e265b56095 \\x170c35d8d4e6fc0f470701f2d08f4f038c47af1a5a590294783d714606cbc58b2f517f6bc33b68567548d04079f0a0b0c58707ac132e0df116a9079a77871d07 \\xea00a088f3efc2497b7d93976134696330398f4b4a4bff48df050e9a0161dcc9 0 10000000 1585852036000000 \\xb697df52aad42496994e2737f2980617856867883392fa8dcf71eea3a69896ae3385d84bf5e32226ae51553fbb5cffdbf57cfa6764d6d6918ec978c9f4971a82
-8 \\xeefcdce7677b4b91ba60c28abc7f2f827d8868e09abfb439567e255cff085137 \\xcbeb077660ab284a9ec0bae485c65c1c8c8244689648611612934063e740e1f0e3f01e7a309e5befea045fbb81e20c90b2353dc503020eecc2f378c5565ebe0c \\x6d4a775680ef7de6a176cfc0d6e3ab00c5401f4a4e2bd53ef982a35cc97b1cdc 0 10000000 1585852036000000 \\xe5ca930828a38b367f72ec9e268125eda82664be5fecfa3841df1ca0e9a9ec6e6c719153eb6a44aeb2675e4f3d013d11e51edbd492bacbc8bf8656bd626d108a
-\.
-
-
---
--- Data for Name: refresh_commitments; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refresh_commitments (melt_serial_id, rc, old_coin_pub, old_coin_sig, amount_with_fee_val, amount_with_fee_frac, noreveal_index) FROM stdin;
-1 \\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 \\x39ecdf0cece28769866927fdcbc0229e73e0e6458efd948eb07278b8ac0d738c \\x62a900a609109f343f68b39c5e0508b8cdde3da11fe8d0e21145ab5575abc8a0629f7328baa62c206296686b0b6a63370657135bbba8fa88be3b24353cd01f0f 5 0 0
-2 \\x9299f109ba5bbfb69085914e2112d4857a48d4a1f87bac2dc3f161e1e160566f3b1908b1102c1f58619f812c7cccd8efe7cf76056a55ddcb9bb598f1f49cea3f \\x39ecdf0cece28769866927fdcbc0229e73e0e6458efd948eb07278b8ac0d738c \\x7cab0997915f3c39464afcde89e7e35df1a450bbad44128b78f7da52f3328a73f3b0890a2f330ad76ca6e5d30caabd71e3e4e7ece21f5576964da4594dec8904 0 80000000 0
-3 \\xfcc21691669f322732ca1b3229921c06ad7de9ac8bbeb67cf83424a2380ad26f70552b222c21be55287447acba12746af988ed5bbddff7331589c2a3376879ec \\x24209fe74c418f2babfdad3860e58645ea750c60e1d1ad1ed22c110ab2eb83a0 \\xc24c117ead15d951c26b1748c302d574ee01b05be54ce134d33994a200cd2fb4b1b2e03e85dd1786a36874dd9f0a51645ad5d8cf24eca34ad13df75418f4bc0b 0 7000000 0
-\.
-
-
---
--- Data for Name: refresh_revealed_coins; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refresh_revealed_coins (rc, freshcoin_index, link_sig, denom_pub_hash, coin_ev, h_coin_ev, ev_sig) FROM stdin;
-\\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 0 \\x12b4debb810bd37ece7834604c3ba148cf831542903204b1247ee57a9d9e45428d388c4846926bbe840c548f2b3b3508af5bda66d78445663d930dc55b32ee09 \\x596dd70417dea3f96b72decf73ebbee411295403914758bcc5d562d1232ad6aa8b14857b58e1cf9ae9b1535ffe4fbdeb4e9b6d67f0739173b64d9bfab00c1f0c \\x01707fabae5b3f80339b9e28271b6d3ee4e63741de61b4b274cf742032fc9a6f38c352b798fda4a8c490a5a8886a9367abf5f20282060e2590d0a8ceb127e5cf6bc353c08e1b20ec0f6803c10d0aea3fd5e20f2bf02bf0b783c6df3a1d464bbe973efeb98442a99dab83bdab0b8add7e78cc42ac4422628a67fe3e0bbad83d6a \\x9ed687c6aeaf69d91788dd87a1d2adb4c68c0849a5baf0e4e1397ebd16a4b1ecec28da59196593f5f25db205a1562117027639ff772a3dc0efef4f531936a83e \\x80a780980d0f24d0b9735b55cc2c83238c97866ad50ab9ee3490cdda8b024b6dc7b02fcf9cc34ecd59dc5f671af4d5325224e4a9bf14a6591a7a276cb5aa6a9f19a948c354df538ed5cdf7809758934b3ff5b96153cd7b317d6f17c32d1aec40aa11699cdb44ca6c3fa2dad9b9624fd1d161452c4acc4f0c37a8f1864c58302a
-\\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 1 \\x52b38f12698482134ba9474f05c7067fa0b7d0e88eb8b8ff940f639997e248275aacc8f14cfaf377d8d66dea69caf3741eb0946a6cd10de7f7ae657278a73e00 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\xc83267d087846d48caf06d8a713e7cc66bf62c580630bc455149216cddcee9cfecb4fcb53b930794e99762bbaab9d82a5b6c0e5f15046907f949be7725891b66757c2421f9af5c32df310049bb6454aafc31802710df4432a10c954a6726c3980ba0faff437233670f7253ef6f37235e566630d24697014798525db15434bad6 \\xe5ca930828a38b367f72ec9e268125eda82664be5fecfa3841df1ca0e9a9ec6e6c719153eb6a44aeb2675e4f3d013d11e51edbd492bacbc8bf8656bd626d108a \\x794ac3266e873423e8295af80eeb3f36c6d6df8f4e57ce5495875ef52b2ead63fdbe8e1810fddeff996118bb3f9d58ab8698c50cc4675b2763b44794a474f9a00e8cdece7f586627b1c905a04dc61e4165ce3268deb0dcdedb4d9b0e80414fa3ddac03f691161cdf7445c4beaf72394cfdd96789a519a6a998f7ce79dd381763
-\\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 2 \\xe5d363effc8515ebac851b8595b5f6524c6bd72d929eae91618c6afba69e12b26e8543c10ec55f1e8f7b80c259a171b10d98b7f9f2ba4d1221eff2cd9c28a50e \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\xa4ebd6b85fa5150c4f43edb97a706a389f6832f2da4ffe55d3bb52690aa669340f3772165a752a000a7bd010f5df257d849846407bb952a67c5d26a5fe8630c90d6accda7e99acaa513a7a1d7c75a9f519e9f14ee19afd98c98806eb48171df68d97862c7689f760b566eb0517d99bed4297657f796bf9472137f0c52b58ec54 \\xc88c70b05f6f8cff8c173bcf1b223478c51c4497ba93c2472fe4dc76d00689cb4951a8ceca4d00944d8bde9cf2aac94db5e129c6e5be44a1a7ff8a864b3bd0d9 \\x66102cd4f231135457f477d78f2a4237b8c2a1f1dbefa15185bd89fc7eedc878217512d2c927400fa405a84a5d3f5327b8f2f083a505ed15ed5cb50ac7b5a998f45a309e0439f0ea5da2899b22ae45b5078295cfadf9c41bd801282b72d74490e31828e5d82bba5a8ef7d37eba3bc46b47cd42c0d5cff9215792ed689756cac1
-\\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 3 \\x95bd7c78e505e0f035256c88a0cb82b57b2b0f5b1a910f5c89ee538eb25b24ab0814781321c29494fec061c6b069919a245ba868ddc900baf720de49875ac507 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\xc5c2680371529b827e0c36f3f7999be4ad4b722ca0c64bf008bbab9fdfbc8dda025aa6ffb64f40fea79d628e197c147f02a93d489061b765ad03df46a89729b41376bd9bbb1481b94edf2186dfb4d6a46d4e931833e659ae73c5a42b6aa38e3c1b150ec54fd4d382eb33faecdc105f596e15f9564e369300181a157b50738118 \\x9e88a7e41f31c1048782955bde254a663e55c6764ea9b9354918221c2c6053ec2b6832fb72a80435c0b2055d754dde03ee7dac4e3251037150180d02f363364f \\x0ea5f18220402f4c3b1f1ac361a210490b0b8c9093f24b7192a6c53d6cda08482f39a58f6984d3d8f9926effa1c077f1556943d10d4aeb32c92b8ab6eab009cdd2cc0bfd473b6c1e421337abc15faf531ac8ff82ce5b341830289c750ab67b3b1a2b9fad44c980b248a22920070c5ce2b0595cca031286a4d86d75192cf8b8da
-\\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 4 \\xc92ad8dccfd74b064cf8e0c878b07a64c9d569a59b14d020e44bf452686456cce2d278749387c79b8fd8d475bf7521c2f3026a54c3df116fae5873b70cb26601 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x6e5cac4b0e48806e94acab58742a2f32a297648c1a3a52bca43f78a155657e24814a1da3f9d8b77722c5bf5225a9bd8c9b035d8d2094b67b5f726640447c6652b19a6f0aa0616a068a5b0255980a099749867a0db1bc649807ea461209231db08f7fd5797f8adb9f29f23a072202be8370acb9f859d865622805654d1fc4a5ae \\x33b42711b523febdb144fd930a49b050ed9108da4f653c9d7ff79c6fa3a77942449f6abdaa9ec0f43f8b2ee892082dec6e8fd86d244be5cc7504ae7b625420e3 \\x59acac73f58980d9730dbcb7401e57a1ca22a10a46adc2c4b59cb7a4dc3e7bbd0e9761315a23df78ed3ec7ac1709e7d4330c785fcfa10df3ba0b374881eab0a6f7bb85cfeb7fdc5060472a931ffbe6646cb6c48e5c77e5bd0a950051e4914af78111247dc419be12a62aac4384433b8ed0f04d5877e3ddb29cb068c37adc0ed4
-\\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 5 \\xbb38496d1d1a79d3aacdbbf888ef6dc053531eff1bbd01bfbf1c3589578de6cc2d0e0da06032ee6967b969f975499acb111f62ea52df767dbc7f6069d65dd001 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x8245e8ebed90310ab7728d08e80bef8bb1937739b46aaa3212ac8147dc354edeac4c29f3022f74a4427cc692f5ad08eaf22052e969ad76c0f40eb2509aa01b25485522faac96766c774afbd4cfc9c579bc23939bab896f2ff35c0a42b3ef36f420b2866bcaa0aa8a7c395ad7bc36eec9c2ee68c41c9cb47c102a2c0c51f0bff8 \\xf98f4b7afe27fdd8a572159413cfff527f6249d8731feb2f81811572cd3719f4daf4e671fee2bf180d1a15a203a6c5fe4c8d6f3a9e00d2f074e44efe3b4c44b8 \\x583c50c1d8e486a47b3feac3c2a4977c263a6a4d5b515099c20e4747e819ea23faf0488a3a19efeb06cf0544e237a0c61b0ac2d4fde3f9df204a255b16171a2166b68b059fb43030ededfe7ae635bcfd5e77feee6d53bfb6e829da96bb9e750a15c14f3eba8c03b7f51809b1ccd80f249bf1aee2565e83063a0dc8430e1985fb
-\\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 6 \\xacf163dd694450addc765dfee90db4b3278923f396e9e659b52c0d28ea5b714b90782932a671581551b71392df2a9006135f392052e1bd4516dccc59ccfa1105 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x7ee1c4e887a71495ea8536079562dda9c0c6b408d6b66a602b1aedabe967ffd538a5e8b35d023aadaec3ee01c9b717314beaddc171b4ea719792f9f12bf5ca61a1006a1d28deefc550c45a2d4ef7215654c0edd7ca299ba2e43a0c0f92d669c7e578f69252d2c0177a4febfa31ca210d67a4d3919c90e3827ce4a36c8fa4bad9 \\xb697df52aad42496994e2737f2980617856867883392fa8dcf71eea3a69896ae3385d84bf5e32226ae51553fbb5cffdbf57cfa6764d6d6918ec978c9f4971a82 \\x14fb3bb757d2929f96c399b859ad0feed63f9506d5ce34fd8965916e5541a60577bae519b15292287355368d3ddb5d071764509881ada194365e2443d6bed62d6d4383749d83451343362fe7c81019fde94051e2d772e0d9160e4a1529b039a9faa5a107a0d9434ccb3d176666a1df0dfda60d208cf1ac518363dfbc09418fc7
-\\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 7 \\xcb38875258c9294ee196d9f5bdcb51a737fd2de505a46f8bcfecb181569f96a9c08bb39b1ee6f0233dc26a461ec017e1eb741a0a9d538a502bc0c4a3e2383108 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\xca95ab52a78d3a587584a3d9e04cbd4c89d2ea8ba4ed55147cdcb44770750cc62e832ef8446869652ab1ed38841ac0bc0c226accd2790af562a00905534b76c9040a5cc93cf971e2f3954429aa4a1d726885c0990c98e20a3e63aba7aa0a4925397614f59ad63d9b531a12858e39b2940c6469b4ba7f5b34d4ac77f2e1edf4 \\xe2d21bfbb014870b62172458af49b6b8e6a36af38c79afbfd9fad9d66420b3d8af2e98c5b6c1a16fd49aca7b74bbfdb4ccce32d879d79f2a579afec89b627317 \\xa1261c9ca930dfe1201760be1ca6cdf2cfccfc5199c3248771a123a3dc1b29e9df520b037ffbd8496840faba4f170b636a068d89639d3595ddd3ea1724fbea8c65de7b24a05936dcc871d22608afeae08bfa20b0745ec6e3939ae1e76e852faa9f1b2872fa4e49777ec2d5c108f15884f9de7fbd50cecc0a9ffcbf35e59f27ff
-\\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 8 \\x97f293befcafecd875f4c154785c02cc097a0b703cb61aa99408d4495091c5862be75327197dccdd56c48067e444c19da0a9a19c1377d632be95920f8ee19901 \\x7f1749df7f2033f92e5a5a3ff9117934b7ac5d5757da9424b0d46d4286359678d67fca8b3792ad086bf23bae26833ed5ca3715373ab56ada7aa69db79df5c3fe \\x7830fdaddbf1548106a2e99e8910352cc4f1fac6737bfa10b8b4b60281f99cdc4294fba9140e85f3d11754a111023a109583e9a6c08a1d3721595084c59352f0b4a9d91cfebc1a81f5565daf28f15d11f8961157683d5c7585531a096152ccaa60087b52ede2f4498c23d0930062927f82822d32479107e7b3ee8e40b26964c7 \\x5def2ae83577b46954abe9fccec8930c9d9f0d825bc545b224f990545f9b6fb1c38d559677ea9252ca3daeeac5963dc79fc860a240b7b3a7b9af8ed47a679b14 \\x4a462c82e05a6151a1d986f8d38edbb72af73a049c4c2c76c44850e7182e8e37ae3756dad822d78e07394f34d030b2e493ddf0b5df2736ccb3a0197be412299823372f13db485c047a756add7b8df946d75d9d7323a4b7a02eb15e60705f9bbade4b4372143b44ed5ec072d9520112df00c84079fe40f2013dbef62e31d01fec
-\\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 9 \\x28c07b28799f691cf5aa7121d9f8bdce5df35b00dfb678a1ad96f7da7a495b9f993dcf953d2cc8a458169f743254d671eb875c5cfe9f1af9f9a93ad487407008 \\xf3b15f353353c828067a48d0cb5cc98b82fd0fe9f0403e675cbd25a702f0601c7254a5c1f12aa67841d9b347bed3720680b3fe8f48b3a3219ec5df26a55f41c6 \\x8771d525e4929d7be8e026073dc4b9bb1b1bb1e05bdcfb072ae2c7e0d919f4ee37348042e12a1b050e95506bbbc08c67ee3f4af245acac8611e4785208ef07c2b288c11f30c5f5f17171d1e21cd081b62ebf329754058432af7b8f9981968193ddd295aa9eba357ca06cf3a33d52debe63abb8ffa39c4dd4820ce05d07a0961d \\xab7cf97af456270d04840d41e1420a3d79ac47efb841554badbb32094dbe8f2c059826449360f79195a8f6cdfb1b0c76b1dcc4a3b0284302861f4591d871332a \\xc9e95d0d1d54ee4acc6b46403c02b165e3be7331006ae440b2d2dfd7ef3a3d0dfb3bfeb1b9f55fd7d3fb1a89441bf445fc9826770f1ad9eda2e9b1e3cfa2e2e8a61372c5fe42cb4938bb60f63eb519edd1ac793326640292fa5373b19d20b3db8ff702665244a54c8c23e8a6ca07320515f8729863c9927d7a3774c17c63cf77
-\\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 10 \\x5ad43c0ccde2c664e79cfbf76553df07f2e52462f6f83c7fdb24c3f0926916a6b67e5b0f4183d7d15e8e05d670090500302c0b1c912f11c3e8fa83c38677fb05 \\xf3b15f353353c828067a48d0cb5cc98b82fd0fe9f0403e675cbd25a702f0601c7254a5c1f12aa67841d9b347bed3720680b3fe8f48b3a3219ec5df26a55f41c6 \\x579db5e78f4e053806b6d8ca301acf0ff90a049a7c57402dd501e63946276c93ab41b38ca042e0ae4646c241899d991e800416bf80665c00587ab09e900761f784a18b157ed41bb4638ff92880dd9e2a6cd041add28f4ca0451ce5f4718505035766c6948d2af25d000dc7db2fa90291b7e2c63fc10d79f5d531cb928794b010 \\x5723f688454576fab541a9216c6ce1292954b3da870bd8f1f1547c622bdb7913ec1d6580a4fc0a6f929ecddc744f82f367aea9976682ebe0342ddf9eb22106de \\x8640e9a747b7db3b62b7955e690624552aaa41a7c89f3e937d7ea6c649f3cffe37f83a74e6aeb277017c9e06c0eda2904e1e280043e96c2d9c8b504a50f44b2df7dba382956cc08b897b245fb31db371387f1610bd3c80b422e2508a3b234252d3de37b5a597e089102af07dc9392a5d3bb04c3d31e9f2efc29317e95997cc5a
-\\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 11 \\x965f589b30e912fbd1f6f2887cec7f6a37701a7bfcf84d9ba70350d126efa35b1b721dcfc353f5777125a0b28e256b9645a153ba7b2c542054ba5fc345baec0c \\xf3b15f353353c828067a48d0cb5cc98b82fd0fe9f0403e675cbd25a702f0601c7254a5c1f12aa67841d9b347bed3720680b3fe8f48b3a3219ec5df26a55f41c6 \\x2a67d2eda9b1b84392d22bc220e1c1be939d8ab64176bd88813e63e1f3cb9f2f7c9049628a985fbf89920241c52adcf2d6c94e1f0a77b138145b0acca646f979b9437f305ceca4044e09d30f22f677a62be9fdbac0791f68482b59a6ffab0326595a3ce6676b905d8eb23ad0caa78d67ef60d57eaf2f8a158fa78d2008d07ad5 \\x361c3090eb3b4ed5d74b8c7c939fc95b5b7c35e3630a1058e1a58a99c4e3120d29ffbb1d426bd83958aef562b457579e0578406a64621fa596487fd44e3e9eed \\x4ef120adad7e3212425a4f13fbceb610af2bf9ff373192c25e2d6ebd7402b1f9349d675c8691bac38d2391e0e4e8f42b9f001619fd86590084d52a822f197359384b62f18b34506588f9e1ebeaf0c7debc8d950655fc5fc0e4b0ed06125eae63a37e9caba20c2216fa9a3de8ba9235780950741d69b2cec5a27229c14f6e2022
-\\x9299f109ba5bbfb69085914e2112d4857a48d4a1f87bac2dc3f161e1e160566f3b1908b1102c1f58619f812c7cccd8efe7cf76056a55ddcb9bb598f1f49cea3f 0 \\x53a6f7525578adba5deb77351745a19c77953ee2495bfe1d1f34298ef3436e7f873be770f6b668d757b499e2153e248768e8a663cfd74395f49b608f423a9c0d \\xb09b6217d72bdb8f393332f676b9c85c2e246a6685c6f4ee8e96384b23ca29e89e587f782d24625001c7cd6cea13e85fdcc9591452f43c6d717d24297dd2dec6 \\x1f1807b89a9acd9dcfe852bae1ff6db0cfa5b0ec6c80c04896a5e018300fc6d469fe4ddce79f0b77159606b048f46fae222d64f742a28768fa6bef7242cb850fb4c3e7f81095e9ef11111f75e0fa864c520f2a22af5326aa9d079b8913ebd22518968e61257e1df2dfb6d8a6d9570ce5d97bfa8bfff2d9612905bf77b52bc9a84626b01f5f27160b3dedb2dc5dd3cefd2afb3a3b2897c20f15c4c13bae0b40c55a56c3b3e361253019db2f5c5c82491c59ef875eb62802d5a68202be6c0865e7b5317f624932f687fc9d7dc99f103ef7906d87cf114dad387e5def4cd12180f4f8159b857fdd8c27cb4c8c53ffd77a3983eca367f65b736027e1fd72c73fadd7 \\xab2c69696fb2382100cd3dc3f67a3fcbb65eb45df22cf50bab30f6cd3777f431aaa174ea9b0337c447dd67b23f5fe8f3216dd8636e5268efa47a7361444c88ea \\x3ab00ba7a3c4c84eeece5d7a14beb177a0589f830e9b2293256aedddf775a10bc1422931727c5940a2acfe8a596661d352d56a291c7636ea011e5119fd05789ba8595b2b608dbc8d53cb427bfa85a0eedac49c39db84d791da4d72012b5a0d19cbc6198b247a73adbf2a544783600395f9e0f25faf78b415280afe655e6285a3f82ae6472bc3c994b27e1561072500bf65fc9aa8b4ea90f66fc5990fe535b427aade933936b2e8dbfa38d6e0261f3f5198cd0afd22f4118b0a4cf77ab2577a6fd34c19430c7a147a801f60fd38b833d2ee9f071b5c6e4f7276d77f45daf0a1a66eca3927e693640965b1a79eb4f7731ae8fd3e5d36e8c631fc600bf317ff468c
-\\x9299f109ba5bbfb69085914e2112d4857a48d4a1f87bac2dc3f161e1e160566f3b1908b1102c1f58619f812c7cccd8efe7cf76056a55ddcb9bb598f1f49cea3f 1 \\x371335ef38d97b73042c2dc656d34e5d0d416154838a503d66a2daf1791c82b130889809ec3b0cbc40417690d07fd8b41ff754139ba9c9e8c84d172258b33004 \\xb09b6217d72bdb8f393332f676b9c85c2e246a6685c6f4ee8e96384b23ca29e89e587f782d24625001c7cd6cea13e85fdcc9591452f43c6d717d24297dd2dec6 \\xd3f5a10d829522cd19fc83f462b2999312122acb59fac2da00110645827be939ada4eb4a51af509f6b7e0cdeac01eefbeb24aca7aafd7ed6237c97994c04b82d0cc8991a531998e0fff8cd1310f9481842616c0be35702568585261f17c75014c4077e439a8c19a6fd256c43d27fb226e065e2cde55846136b8e13946b89666b876364adba49d4a22947d438c40cef662be3dd2f24b6cdf0f5b119c6ef8cabc450f95266eedfff3c83f6af96e6382622db5a660da3029f5b98c6830348f1a0664ac741fc711316e2d2d87a79427f8254320242e07e89161d81f2d4b5bd9e32e5dd23b6a8cb2ecab25a55b630f5bbc7004fad9be8d421cb54167014cbec8ec316 \\x8406d4566067f679aa9ef346855a8577b2690dd0342dffe8911b6419890b9936541d56dcdbdf308fc712db037e579d4244e3d04daed505c8f99d600d334e37e6 \\x268445af33e672f8fa4b1ecc72548f23a815882a5a93b2be7f9a118eab5ae5164145a3ae9b5fab216a8be4af408f12f051b7a8b2de2140e905cca384ef12a9d945952a195b3366805e26a7dea78ba561addde400841c74cafe3448db4511725f11670564c5053e8e85a10d7f852fa005a9d4f191f374956fac7d1c594dbf3c0f003937bcb7e6e4555c110a81d6e29c527a4b839b8a32a685a9badd0d6b40c62f2caa23386fe35c25f25e1c2e4993ccde80bfac90e6071fd0a7f303174f333e9e3604090f3754910efabda9ed786d186ecfb934993dd77c64f4d72b1750e09ca6439e1f520df6402813ec5b866aa83d46eb0356093136cb3f57753bf0b9289f62
-\\x9299f109ba5bbfb69085914e2112d4857a48d4a1f87bac2dc3f161e1e160566f3b1908b1102c1f58619f812c7cccd8efe7cf76056a55ddcb9bb598f1f49cea3f 2 \\x362a9a20844e454a0fc9062289bc478c3beff6538c000ed3adaa4127b7e878deec7ca9c77c1ade56a3deb5eaa97be1f3b9858612c1a2551f25a87e6a28459e0c \\xb09b6217d72bdb8f393332f676b9c85c2e246a6685c6f4ee8e96384b23ca29e89e587f782d24625001c7cd6cea13e85fdcc9591452f43c6d717d24297dd2dec6 \\x19f30be726726f1638c9b89529e453dcc6ee3107355789939b0beddb4456d6267700a6e819e8f490ef73e4807a921798e8d4a6942283a568edeb09d7853fb5f0c9c9bbbb4835df31d36a454da9824dcea80ab33c017150511e52073ced14ec49c8f7789102f2af8676ed443df189fb6f5b8e73a6d8efa7da9ec72fd84b9411b689e03fa647ee90cfdba377b81ed4e778cd46a13eff27a4b3509986174b80b5ef04abf5ead4d5220504dd7afc1a1c9120b472c1077f082a260fed9b5f67a0d7bee086defd30d745ee3934bd3fd3235723ca5e9520af0e65bc7d4a4dbca8e2680c625807afa6990b235d423f321ec64a05de77a5b9abd0b20e6eb485f403705792 \\xc3c0b807d039a1d4e344642d2a975e840b933487ea9b50d6494b5d74cca65e9c2d14f203ac2cd50282aed3b06e5b372cf7a7463d2267cfffc2e2b1e90c7118a6 \\xdb633de0bf4fc1e3f2778aff868919f269857d0c2f9feff4ba342a8c1004e5e9a0f29a5817c7755f3118fc394177ee0d7cf52a5c9bdf561f38c69a77ff62f23a3a4adf8035d05d41b72860aefd10ab14889f87ebc0a710d7eb60306a60d3ac31091b77c57b8a3b0ed9d8cba24f52660c90c151dc58ebe94025b0cb717d1565a52a88a6525babac36d608335192f747d87f6348eedd8676824f599d4a46bf70e92173294f5eea4ddc48591eb89deeaf38c629ab5aac77a725de28ee9829f227610b4cc3f0ca06fc61ff5737a4567ad556bf0745f2922a1c54dd823f2eb2d5b2fbd09b8e1e738440418ebcb0f1eb51a33e9f9d28833c53a0baad128ff2abc8fcc8
-\\x9299f109ba5bbfb69085914e2112d4857a48d4a1f87bac2dc3f161e1e160566f3b1908b1102c1f58619f812c7cccd8efe7cf76056a55ddcb9bb598f1f49cea3f 3 \\x4119ced37056b0de05ff5bdbcff621bd9a5acf9329a6aae9613bc84f8680767191c935cfbb568cb95f9c79e939b21a8cc509cf760462b96a0fad0ca5b7edbc04 \\xb09b6217d72bdb8f393332f676b9c85c2e246a6685c6f4ee8e96384b23ca29e89e587f782d24625001c7cd6cea13e85fdcc9591452f43c6d717d24297dd2dec6 \\x57e6b0c6bb6896570069f7090ffebf89d62a34096464481d00a1e59b98ca420458134471c93445fbc774bb71b59fd39dbe0c5d8deebf5689b6789bc377a2facc6fe24aa767d07c2d3e918f7d8c24dd41b79e5eeb876a7e81f3634cc7c38e4e3522a9911e1486f703325ec9a8da51cac3d10341312f05efe97785d76d147e3e071c0a7f587dc607980d62687598b322f7f1cd6ea39d32d91e70e48404789755143b3d7e7529dfe6622fadf5419022b48d53742875fc812c1d5261fc02e8ea69b7567d8445b8a932cd0d24ade68dd91bc957912213981f8e8e2cec3c28b5114ce8de2949f0e8273eb9dbd2010f79a728088a784affc29c50abefdad816955381c6 \\x953fc67b1b484fee9538b57932b9f14837262235ef202751de5987669df27c31ae6bfa9f7ab79f8a35074a975b2986f9d2649077af075e435a29cdbe7ad90f97 \\x436b9e94484b36eaf6b733f1f4f2947941da0b319271300586ec02a6c35550a820e2c9daba0b3649f74aa1ef080708f3238f0fb2ca15f4a762a12f61296886fe14d6e2e1a72cf3615822d5e4500cf9859baf0ebf1f0027bdbe2d893d9baed2cd28a3ac84a252348b7e52ee0a6f132cbbffe9bf0de4013773ad553cea139bf116c0fa0cc7610741120b6f18f288b23985e9faffd829fdbe739aa06bd6976639a5e085a22446eee7d98343ff9402c8dd72c52b10db1ef7a5c86b6b73afe71082edaed4d8575c4f50f1d8ed1f99551f5912cb3dd05ed91b2610dcaf65172fa091ea7b49039ee12e2a8c2ed62c2c097935a1c90e15d041e8d230bf39073b188abb65
-\\x9299f109ba5bbfb69085914e2112d4857a48d4a1f87bac2dc3f161e1e160566f3b1908b1102c1f58619f812c7cccd8efe7cf76056a55ddcb9bb598f1f49cea3f 4 \\x55ad17639332de18700a2b670e5dad0589f29404103cb5163cbc105528cfb82799d67b2ad941d80962eaa7856e3695edf6cab28fc9cd7b9300c778f6a961df04 \\xb09b6217d72bdb8f393332f676b9c85c2e246a6685c6f4ee8e96384b23ca29e89e587f782d24625001c7cd6cea13e85fdcc9591452f43c6d717d24297dd2dec6 \\x91da6326fa9c5efe992f8c7d807177fe464b7edc247f3ae78ee3629f61755af4e370535ad2b60dd66eedcf76e262f59f27cac267d64c4b7978674eaedec95ae637f9cd918e8c2237abd4ee29d3e0a4d5442f488599aa56489ceb4519f7232a91b49bd7c6f35d9c328d677ef45c194da3a34d2faffe61e07f3ef14d076c8c088c9d5777fb4698efd7d70e28ee1fd19c22f4aa078622aa4632182b3f556bcaab63ca740809e8f0c1205ed21897b870a49f1485ded8238d1b969d96385c0d48a243383107f829d4d40dd76a780e0ddcecb226e6a6660725cf822a1dde0c16f2ce07b58785820c3be10383b3ee014c80b81c63690f4ceba3b249c7214acc581d066c \\xc6417a65789b64630e1f9298b67698608d1be14019df4f06be73c342c96e2f91c6129b64f9887bfce3fca30f1d8b7c1c19ce302f5458b9fccb0b39753779f782 \\x3a2bb7356333a9903e1f1cd46936eabe6f25a11fbf56e2e153f8a1c41a18944c78ea5202cf9351651e82647ade3c926d5bee177124a34fc00b38323c2fc7a043b62528355810f5814f6b990ee4f16cf33965047e577fe9db77fd20d199ea0989da6f9cfcc5436219e71f5c4a6d8f60fa01b32159e24c3b49c71d6bf07f223e51e9e3a0035cfffff0a5928fbdc4179810893b17cf654a5b82b8edcc466588402a197a2959dfa40fd69f03cc6a3c0892d8bb75cce3aa05b0a906d04ec6be657439e750373f9f1080e174dc9b215df1945b19398ea8ca077a57e7bc10fc6286d214799b7a83e843a36346d340a0c47caf48cc61c35f88ad854912fced88eaaadbb7
-\\x9299f109ba5bbfb69085914e2112d4857a48d4a1f87bac2dc3f161e1e160566f3b1908b1102c1f58619f812c7cccd8efe7cf76056a55ddcb9bb598f1f49cea3f 5 \\xea792ede870a12621f594314e274f07150e6788559929de7de7d8169e75cf69f7204e6cbd8054e221b11eb2666ac63e6a3584a81feef99c28be70d570775250f \\xb09b6217d72bdb8f393332f676b9c85c2e246a6685c6f4ee8e96384b23ca29e89e587f782d24625001c7cd6cea13e85fdcc9591452f43c6d717d24297dd2dec6 \\x83ab4f307610060a1c81c4b74cc69a4073017cd0b599f89cee9218900be96a971f43b095d2a05c03975573ad52498767bca75f15c7b58a6e49d2fc9489d40c0ed2dffc51d0751a1b4f0ff2ae7675308963bf6570dfc3ff14e6a5a8bc1f5de113bb228bdc10d7d31bd78d32824fa82cc43b55aa89927595fd8d69bc4f0ba0d93627ec139be1d9e85b08ec426c9023e34d63e675c3848d19b86bc2593ae5c7819e7e727854b9f2b434a601a78b3c2e88161f526a9c1ab41d38dc31869a72b7d088f0723eba93d652567510962d0b4d14d63b2a59f1d148bf5ec3212d96c6c50d7d7c78487504b58c69ea3f9d5cb0ecc32a3a66184176624120446c7e87cd1d0b94 \\x2ee3482d4ce1c97a5621dff114c2e52746a13e5c72a85a5e48d2ffecc33c86ec6b0ce9788a579e12ff825007d2f56acf5e6e5aa5ce0e2ec8ff31d1d0f532eb33 \\x58d9e0f411b619d277f8b71196c9be30ea9da4166302b9d8869e4199122e9464852dde58bbc3021afb0caa86a359d05888ea4eb54b83f3a6ac83f9cb5e39144e1c83f768ac768314f6b7d5e836a0f214c019c0286674681c9faf5d2886c5ec6c533835f704b76115a9f7213fb838573cb24083f8b0447e5c7b45faf0d35ea4183eee899187e9c9ff8edc2d59a6736950a0e40581fe8c4ea22328f610229b431d17a8317c10f4125f5cf36c7dd4edb7e440e59a584d077db1a2faae5bc1a384ea22d7a585a424e54712799be149f703ac5cf5ae115337a8808a04d3902b25fbe7da5bb404beeb3da63d21e75191f88fa1cfe64c81b7cde7697836877082ba3e95
-\\x9299f109ba5bbfb69085914e2112d4857a48d4a1f87bac2dc3f161e1e160566f3b1908b1102c1f58619f812c7cccd8efe7cf76056a55ddcb9bb598f1f49cea3f 6 \\xa02bfeaa2d5a66c44e6f233365f760f53018cc1d96499a235a90e9c1437781dfcd6768cdfb502797ce4006666781933d687af7ff85d7226e7d8db6da73199a0b \\xb09b6217d72bdb8f393332f676b9c85c2e246a6685c6f4ee8e96384b23ca29e89e587f782d24625001c7cd6cea13e85fdcc9591452f43c6d717d24297dd2dec6 \\xd1014baba95c600cf77ac40559b28b03bbd579283989af84c6b3adeb7bef11887b19bab7c5eb174ffddce3fc0ab486c307d6ee6ed82d5954cf74ac1419cbe454d65a92302fdcfabab8937f021aab344ac6644d61fa099dd1a807a09600c8e3da34eb95add3135492f6345a50f4c298491add7b9fe0464ba7c439619423e619bd1dbfbbcf391b69db3cc4881e42b2cc3975e925dfbb33a118fe1ae46ffef5fe034e6adbaddad4b8ebc12235f1909275208afab5bcabe2c8af65622b3f74053c53b6aa67f2b6ea0a6b5eec1c70ddc6278d873338eecc44318160dfed19bba0df973db28b18f4ae55c0234b553d67b74231c6e289a1b8c92d242838e7e41512f90e \\x3e8065c139bf41a32fd187274c4af1bd718cb0c016f35d527a4716ddc2b3c3e88cea7924653c09dd6c62205e5e582cf96069e14bc6d2004dfda9d03d750a3547 \\xef6f6482550a54bcac032e78e3ff441bc10bd903d139f9db8c134c9267056c8b8a1ca54d6c5c42129ce2718789628503ecb0f3b7397f979601c445b72c6c040a39fa4aceccec75faac68ef74ef9788cc28c525507db6b51b36db066e183b28616fa31447e61f6c0be44b6ab44ad551496a7ee6c0b9c3768b2982abaf4a88200739dc1964c9b05dc007ca4cb698ab2ecd0d18d5237eece654e9081612fbd5c85056226f6fa036d3d2446e04f4dca68324be91d92476cbed46a73386972841e0ba2519e2ae27d93120b11f2a8e72c41cf250f12efe03c914034f92cdd2df0a0974b809ec3cd166c8baafe527c811cc72b1db912a3e436abe61aa387646b817eefa
-\\xfcc21691669f322732ca1b3229921c06ad7de9ac8bbeb67cf83424a2380ad26f70552b222c21be55287447acba12746af988ed5bbddff7331589c2a3376879ec 0 \\x6e35b295208cceeb9c6c199b1f1ae52fda3e4ccab3543b5bf8b506fcc8756b1bc0073e437ea2851f66e63894512ec5916eab0a158c0932dbb20a45678681720d \\xf3b15f353353c828067a48d0cb5cc98b82fd0fe9f0403e675cbd25a702f0601c7254a5c1f12aa67841d9b347bed3720680b3fe8f48b3a3219ec5df26a55f41c6 \\x6536da6ceb5cec792ae76b6acb5710a99ecaf1190c097bdd5a1e1af087c2442e7e9e962c572150a055bc2cb781d20fb1bf6f1e9ca216a1bf2ccca468bd39ee5c6416ca22101e47e4176e3409b714cc356663c19ae637780d676b6b308f1fe463719d803c85236d465e3994c16dbe680fcdbbb6a7755f7761db0f32ea1edfc66a \\x4107b1e138fb50ea7bdaac15afeee44167327b376467af5522796a454868a29ce6db869eb2cbeddaa3077f8d15d6caa58fe436a7f481813d734073863714c232 \\x51bf9d87d5bdda5de0751041ae9a2ddc14988105bd394e4180f0a86b52d4f8df1ac7e1f6225d9fb20c2445396ad6f0152ec5c5251e35e20714ff13e2c0df5ecaef3060c2da1adcb0c8255692a9e8190f69195b83a702fbeeec934521b3402bf7bbdd8c8b0ac30ce97301195ce6ae970aeceb686783a3ae8f1b33f43fbcfad956
-\\xfcc21691669f322732ca1b3229921c06ad7de9ac8bbeb67cf83424a2380ad26f70552b222c21be55287447acba12746af988ed5bbddff7331589c2a3376879ec 1 \\x3bc4b3e1be4d49b66bf2094752ccc521082565f02dd53c7469d452d22cd9dd934417cd35fa12efe3e75ad826862bef6a8da065d551d6a06258bfba8c39bef502 \\xf3b15f353353c828067a48d0cb5cc98b82fd0fe9f0403e675cbd25a702f0601c7254a5c1f12aa67841d9b347bed3720680b3fe8f48b3a3219ec5df26a55f41c6 \\xb2a698d8cf72b3d4b36ffe39fc03540e28a7ecaf645b59d69d4e16a7908c971eb408cb1789fce47071bb3ec8bd94df9b71ef97c79477a733df1e51bcb71f0aa2558622e8aa3ef3df62f530e30b1e308f6bffe691aeac1ba43b63bb46d8b4aea9aa1567184c9ed7fa25c95bef29379978577a73aa3ce6aac40402de46cb8f585f \\x13e40e2ba06c26a3ead37ec342533624f82ac4a6ac517a415c77d40a788dedf478ac1f3179c08e5f45107f2cf34b0a39169f203671d774065bdb51f21341fcd5 \\xba1ca7ba386e938215ab43af0a60b8ff2ea78d5d4e63c182441d99d370f200cf66388dc237258a8e6f2ce3b5384787d24e3609c84a83fa6af431edb673d6df2138179c5cd66e32f4246438b02998d1e1c63c108159e07eeaecc9e11871178bd72525ae901b66d5cdac22791bf8ab9403957733ae335a4b4db1865ed9dcad37a5
-\.
-
-
---
--- Data for Name: refresh_transfer_keys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refresh_transfer_keys (rc, transfer_pub, transfer_privs) FROM stdin;
-\\x31cced9afea62ba3578d70d72b0fb97669a4e3337ae4e20df97c4754dd7e9c279fa14b0eebe7041289b281d3d7604e7c53b6e7eba614d8a29cab3a559707b785 \\xd918c54605c7e14648b928b526ed8038148c15af15e293a1c5ba6d687ee91056 \\x9a86bcdbb3d50a280f8de9623bd82135a36799d7de417d8a7021f5cbb9e2ce71b2c8c52445efa89e81e1c8c7b440601719b6508e9db637d902a873fd4ab2bf1a
-\\x9299f109ba5bbfb69085914e2112d4857a48d4a1f87bac2dc3f161e1e160566f3b1908b1102c1f58619f812c7cccd8efe7cf76056a55ddcb9bb598f1f49cea3f \\x14df49a28a7c6c38f3e7d5d6185105b7382b2a8f996bf0038d1442ce331f834c \\x972486fadf7f64314b0b46238bb42920083c2b669c311ac887ff2e596b6096ff50b70e22dfc1b04412eb3422f03b81fe16816c1ee53c1585d355d5d4e9bb81d7
-\\xfcc21691669f322732ca1b3229921c06ad7de9ac8bbeb67cf83424a2380ad26f70552b222c21be55287447acba12746af988ed5bbddff7331589c2a3376879ec \\x83f68c7d166403c62d94e094e08cbb0a9aacc0862d230ccf972846a5b99a6a18 \\xf64291cb3a36b748230f9eb097439b0138e4854bd33d56da070b2321ed20bc8eb73263a72f5f5e269b9c6e8271e6e74a26cffc5e5c3dea208cf8367b796b39dd
-\.
-
-
---
--- Data for Name: refunds; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refunds (refund_serial_id, coin_pub, merchant_pub, merchant_sig, h_contract_terms, rtransaction_id, amount_with_fee_val, amount_with_fee_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: reserves; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.reserves (reserve_pub, account_details, current_balance_val, current_balance_frac, expiration_date, gc_date) FROM stdin;
-\\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 payto://x-taler-bank/localhost/testuser-JjVD1AYx 0 0 1587666429000000 1805999230000000
-\.
-
-
---
--- Data for Name: reserves_close; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.reserves_close (close_uuid, reserve_pub, execution_date, wtid, receiver_account, amount_val, amount_frac, closing_fee_val, closing_fee_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: reserves_in; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.reserves_in (reserve_in_serial_id, reserve_pub, wire_reference, credit_val, credit_frac, sender_account_details, exchange_account_section, execution_date) FROM stdin;
-1 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 2 8 0 payto://x-taler-bank/localhost/testuser-JjVD1AYx exchange-account-1 1585247225000000
-\.
-
-
---
--- Data for Name: reserves_out; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.reserves_out (reserve_out_serial_id, h_blind_ev, denom_pub_hash, denom_sig, reserve_pub, reserve_sig, execution_date, amount_with_fee_val, amount_with_fee_frac) FROM stdin;
-1 \\xe2a5976be0ff4d8fcbc4158806046c4d13e5451be8a85b16e0f733a349f12f021dbdd7fdab0d3c97cd80e2a0962321f44ff82d5cacf8bf7a2ef3d0e57839fef7 \\xb2b398d560e595d6d879842cd209cc84c924a57538ffe918aa6f3107492495740d2dadd63f34109c3a19caa665eabb781359cac5c496e56a36bfc6926d3a45a8 \\x88d94c7ef6b9fdbe123821244ccc8cc7d5bf4f4f3f88341a7d0bdd5bab04fb0f2e06e2fbebca41eb8d6d926f0da55fab4bbe32cb9bd39e05ba305106dd31002547e62637030349da5ab8f468de6e232856f946f79db511649d469c08b8f014e7c4503a9fcd74247c15e85ab5189b537060900e20f641c6390e32b9443d15f8cf \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xc6dcafdc44aa3c23118876a2f1a1cb240fa57a3510c2c650f48764f680f2273b342a73683b80bd6011b4d15688cda9866a17747109a20268092d1e853ce52d0d 1585247226000000 5 1000000
-2 \\x729dbcc6b62e8c06af71732625ab60fc87ff2b8c8f67eec84c2fc091ff96acab9d6e46d9daec54cdbdcfed0c971487d232c070707ba45dc842318f4d82f759d2 \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x5503753f883cb430a8f127898411600b0446a87d3f9b0f590d69530bb648e214130d2a02228fd0e380cfa4c7787948d54cbb64070ece94ff077065309e3f164168481ff29375ac141765db17149e12b269f8b9b8a3f6b527b87c8bc8537ae0d36ad7b7c085365d3c69a59f5fd0afddbedfc0beb226fe008534e108d391cf0f16 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\x2d6cf195d1f05247afb9849c499b6dec127d5a97fc1d91f1992309046d6cf4419230af94de47b49555f909d6cc65f23214cdb537512a8fabb5b6944e1c503f02 1585247226000000 0 11000000
-3 \\x2f445f26fdeed3207cff9fc26c0da8cb334ae9cffa6c504e6113e01ca870450f9427e43e7fef653f161562c68b49cc032425247db5ec0beab3c48f5ea2afed4a \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x5dfaa41d01999757ba3c2989dcd95d32e90a55573f12ffb1b7482edb149cc71a068cdd8ac8f124c932be077ccc1a1d5884a8a3696c32d0771205b4088c6b7f7aa972b6e03352587acb341e9d5cfa7a6a8dde24a0d19b8f4f3a256d623a7403c105c4f8726d65b5428f141ee49566100f5ada7d3853a3d8ab91ce1ae586106f1d \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xf8f3cf5095af2246a7465ea7f9b42f1cca5168dc5b071a5f72d9e0b7218101fde22ef841fba2021ead35833da8232dbb755a53e5e83c9547bbbc831d3589dd07 1585247226000000 0 11000000
-4 \\xb461405da8de1da0bbed12352af73b58860dd9222b0ee43d6bd410e10c830c140df671286b8818abc1664cc260de8957b40dc5085f44f3f65facce7c949f44fd \\x747f5c89194b7266b995701b18f8f5f9b31a1f18997b72aa306b12fe8c1be3db30507cf68c32408ad8ed9accdf31dfb56ac917b362dd21e4727b7e499540110f \\x83dbe0e184f2445941d57c8ead85ab4d22f86db1cee9c15c9958772b9ac870b32064e82ca7db7a8cfcb18f95d0656fffe8b2d2ec185cef483fd830868c11f1f52563b32f3cd50ff7a9401a41d13b5850ef79fee7f8031a45c692c0a843c1c4ed1dbd41cc0bb12efd29fa5755569fe359fcf25d8f134a9c5192d81114ab421bbe \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xa75434524d67b0316a1c51ebcd99cfaaabb91a37b3848ac47b063d6653afa13fe1aed35f15b17ec0a7c29b56144588440d234ec7e4650f93f3db57960fba4400 1585247226000000 0 2000000
-5 \\xe4c580166cc482aeed98e877c5c4b6d288c458c78ffdea5eff38729a2412bf787c7a462e5705103920288717ef27a3dbabd1c1c07ad7ea425e25ed7763dcc1f0 \\xe0726684567b8f6e972ae98a1c743d83344292e41f48d2696272bbe7d7cf28e4fe4ca576cefee21dcf4d05dc1927dbed383bd6383af4f6919c6185cb86c29fc2 \\x84869b61fd17f94c8a23e40424b1247e94a4cf5208962106a63db062be1ce1f54dd86e1f1a7dff56a03ad1a769abb4ca34844dd9593208d053cbe656fe64e5e790c9db67012988cafe65a3fcaa0a844720b9b13b26e0e977cb18cb3753bae29f43b5e2185116a40644dbc83b8408c32a1fc67e4f18ca2037fac27909c370ba6d \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\x4d675f9bd9890c61184e3676c30d16de23bf4c6c7e72b101608e22903b1a6768de0b2286f3af8e8ff31532fbed5959394e7bb88cba6cca884390818d13826604 1585247226000000 2 3000000
-6 \\xfbe66b1b4843b2790ccff3d22d3454aa433919d967a231523f0178503914c79ebed229e74519c0a201d03384c181b8e30aeaee8be86b7fd109febb06f6c8dcbf \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x3321faadd56c35f36d7398a8f77ee4a2969a499e3c90a41e1c91d6512f2c89156ea535c7bda92fa6c43edbce360d9d26bed704f0a4a363e6390f06c36d55b47c13d0f9f45d10d87ee2006068ca2d7ba8ca87cc003c1c95fc0d2947240e0d6d0ed041501842dd54eab84a7fa232dda60bdbaff7c31e1ed01ed8210d99e8d8b402 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xb91d7e405e3c4d8a87c57d6e7abc2b01906cb1bbe72ffd7b30beb3e3c34ba9a76adeaaf18cf15be12a47745ac9d9bf91b2c0cab180e857af7f3c0a4419a2f208 1585247226000000 0 11000000
-7 \\xb8406db7a5f0ef73155b7c696136e3867c0088b166d904cbb511f431be02ef0d7f9d5965928954582a15ddc734953341e2286df1a004a3f08b57bbbd1b9cafef \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x1ecc94b5f450b885c41f612509ca1babcab67e46d291e94d22ad28d24dfe258b896c25311cc9d51fc40e83c87c7071f539a27a8a6c3e02f61945c199a6d21e0c431c232802d23ba10bdf3516097da8a236471748115dd4ba468642ff750746b06890c1a3050276a53568b31b919659c268751b73a2ecec37489b451c34530cd4 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xefa63a164c772c419d203f6aa9137e571e922666d43634906a782c14423c039fbffe10251eb683a78c299d43c8571b9a6829978bfab1386125bbb688eed8d609 1585247226000000 0 11000000
-8 \\x80f9cd293cccb47979a32a7a48ca65db99e7931c98b48183fca8c116a907c703677270a1b2dc52f3ce32a4145e02a40041beaebfddc97c5e14d0c525d1cdba20 \\x747f5c89194b7266b995701b18f8f5f9b31a1f18997b72aa306b12fe8c1be3db30507cf68c32408ad8ed9accdf31dfb56ac917b362dd21e4727b7e499540110f \\x7f003d48d5382176d0d1721e464ae92715b294bd21b4512f69730c5e1bdda025e3c96c5fd49791fc9351283545c1e3cc07947972f8ab27bea324a6521b684b101b1ac35791f26d0af2526e3b06e5d90d401f78af82ad3f03f00b342c52361d331543e4b40be61fca3d205e443eeb4c57d8648f56c85d3ba8f6fe21e343a750ce \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\x690318e2a4dad9ae83ec0a684c5d4ffb3b1dde44d3b261ebac5c24377947a66be425d6d1467e3b4fcd25c2f566b383cf2c88e905953f56dfa1795cf03c89b803 1585247226000000 0 2000000
-9 \\x9ee5fcdd21814ca508e77fb1f12878fd8fde8de0dbf98c09c4eafbdb16d1a891a5cdf4fc6bb27b35f332cab91777340b925ba6cb09814f8151b5b877cb472050 \\x747f5c89194b7266b995701b18f8f5f9b31a1f18997b72aa306b12fe8c1be3db30507cf68c32408ad8ed9accdf31dfb56ac917b362dd21e4727b7e499540110f \\x9a71e4484e30c9b649e6ba6c64a4450f758e34226d369ca0837ca459776fb2f142b572b1e64d846bb4845c362d8ca07268fed5b31edbf65f413fb2fb1caa8abc83d92d0da49fdafff32042ef867989bb78957e59faac66291837a8c3fa230fbc992f404a1a31924f6f99e7a8d76949db7dbba57ac103df3c1d767c33623c4270 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xaf53eb48f261b549c9de5270cae9795bd7a8592391759de3b521bb79acadb53ea78a25d14674f863bb21c05d96ea43e25a6ec5e18faa544650896f0f8534360a 1585247226000000 0 2000000
-10 \\x60632d0439e49678b7f284dfab08a4a3e657f521465d440bb103dd6012da2514ef38b48f972765d56c27460428450c6618d5cbeef6a71adfb1b2abf64fc4bf68 \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x5a35b65a05ef66471acb62788150dc92f0d3bdb5af550eedd18f364e88bb30f53d95a1a740348d3c2a92d5df395655a87920724068fd877dfed267c172cc2d065769b83e220bb743f450ab6c609c32171e49333dfa13a696c393d0ea0c3a955262410f5e096963a606d7e7012b7c5bc1d5408f71fce7978df5a5723d7f69d3a1 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xe0d59b95446aef3ad0ee8128d7f6f59e20639def53ecca82072f799f2f93246d2bb5e8f9197ccddbe5c3cdba9299bd2346d558dc60c9102cc5986ec58eb82b06 1585247226000000 0 11000000
-11 \\xca41556b29565f0be8a1614df891946e35d4e20c1e613d983c2401102d56e89615780ce613a025470c2b03ca188314d510109a52adbd99c3fbc8aefca447dc8b \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\xa8b27505ef3eb313171476cf80897adc89921bcb3e97eb758b3b0b928909eca42249182e8595faa046dd8f545a021a8c3b25b0d3022e3a5e77a928000ad3402147a562ee6b78744976cf2db1a64fe03f308987db1f85ec1197a049bbc506d979c9666951d57cc9daa0893b0241de5a977e35f736dee165b619ba0aeb090edc59 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\x86b3e87a469ffbe0504a6289882427bf04f70446e8be4195572d48a9d5fe2225b939bab7a562b47bc3ad91892cc095231d107213760a17e8c27a405c1547a10c 1585247226000000 0 11000000
-12 \\x4b53c212fc6d01d19b6eea549ef51d69255630cbe2828ee728ac02457cb885f03dff8d89c6bd63e51f7fe0172b644b967a24061dd25ccb83a6d18626a31e37c3 \\x747f5c89194b7266b995701b18f8f5f9b31a1f18997b72aa306b12fe8c1be3db30507cf68c32408ad8ed9accdf31dfb56ac917b362dd21e4727b7e499540110f \\xa30d8e019e066ee05a44e8b7327c9fcf7d6e503e4815f5bc885b99656dd1acd1985b43d8ded53cf587196cf06a8e27890bb51d883c79c7da39795ba68cc3abcae887f682c38ab3d8988269e4a72550fbf1c92af8eb2376aa55359f899345bebff26c3ce9a7ea5f57ed122eae9f4c43c293b3eefb2d732379a7247b902aa87113 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\x574c05f5afae8c1191d0a8a577f430590c01f7f4b99f47ebfccb8f0f65504a5ef8a8dbbfac9227d696f3b886f97741625707ddcdc6aae474e688552041bafe0d 1585247226000000 0 2000000
-13 \\xde66e8165426c0a5506e3cda95fa32a5e5ad78fe18c8af2a6309416a86d69da1132131edca99b2684c0a0793246143d7b1bdadb8f6ee09e2201134fa0d0994ff \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x936ac8eb60f0dbba8c146170c1c46da9b6dcf604c7759be8de925b990425c6e2f129516ed0234a13d5782db59b49b1a2a56185a5674a749c0b89f4869e2997678a86d7effb16a4b8348aa1470d0dfa559beb5b3e240f331415449911a1d63a85247b4bade791b3093d36a993d2785ad17ed099da4e22867ac9f8491448acdeff \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\x00a69d0a669a3561d1a2718eab1af264b955e59eb0b0f977e956d2af4074fb018530ce72aba04ebfcaa9b16caa48f3845078c464156165c5ea7975ee0f096807 1585247226000000 0 11000000
-14 \\xf2cf47750bf61cd70088a8c23f5a2449aa915d839e23d20cd845166b2183e7788b25df00d0da40fee13af660a10e5e85b0bc878d807e1c5f13046e0ed151537d \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x6b1f1eba71b3e8fa153b47f0d5acb90903ec86e69adf63d40a78ecb75ec1025029295d7d354eaba636ab7f06f28250026ce8ace07b3ea294fbfab77339fc6cf7dbaf86cfbb8ac2d1c88655012dbc5211b84eb4af42d8fe5bb5b841568586cd749df3b20ede2d2b6cdd6d9ce9d0f207d4bd79843ea18f8de57f98ced3898d43e1 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\x8bbb9c97d8e2ae2298bca00cee1e52e86260d46f4205991d33315829a702634fa07c518934ece2e8598481c573cbf6e7c3918302ea877efddf3d4596d6eedd03 1585247226000000 0 11000000
-15 \\x0e53156fba4e75d71bb1dd03ec212bb660cff0c2d5bc3683d6677a4fda7ff6c9644bd039ae479cd315c2a9445cdfcedac1aa2e323ae501fd0d2c04781c37cc62 \\xfac60747f6f5230990e394d61524d5e5c2d7fde9d64cb67971383afded993d28b8ccc24859da6595521f2131f8b29f7249a132b21bd783ddc17df6743baa690f \\x1b4a73f61abd43fe3032e7bd7f2ae75b7d4b7aaf1d52a7ffa41cad1fcfaba3df9ad4f53f682164ff189a5cae3f5476d800d48769a444608b0f88960fdef16c3538c55281221fb882edc57f474e4deff47300e2a920124e54a8076258edd1c372e5944a9fe9f5df7875595efb55c6efd8d3cf3df45fc81e4a1f5dfca7f72a1200 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xf732e916f16f2b6362f54323dd483ef92745dd1d65933427ba12a223628595d3896831f95c69ac8afbfc7e211f60878983cb1df60cf1ffa8aee4c01d716f840c 1585247229000000 1 2000000
-16 \\x8462d40f51d83a81b975f8944404ac198173e851992a346e07ad09c859093a2a19ca7bb8c78d9c1b063a5ce248932ba32bc6a3ed07329f3c9609a4be9858bf8f \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x3d0bef9880eabf5d8e4db31ddc896f5cac12daf48f1cdd5a4a8dbfca48ca645c11148b32e15a441d6e17c7669ea10f51bf596d10b741ebd898d64bf7b498f9cce4fe7203f371ae03e5b2cfd261ef336a00817aecd243ec61ac5849a35536246eab312708d61cc8abbbd50b1cc9a6bae4ad5f40d564b607ddf9ef426c65264613 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\x44bbadffed4ee4587f92605e72d67103f92345399352d406f7d38811461b6ae3eca9ffd1d201d875dfc1b3bed85a5ae857dbbcf8fc551c3e86edd11c2157a80e 1585247230000000 0 11000000
-17 \\x4b35931e6ead2975c9099cfc47fe715087be04a7c3730a131cf16dcd6e8b5fbd1b58e660afa7d1022cba95f9a2e1bacf6187115329db523d6b9aa404345853bc \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x705c95c755ac42c1280f8ef6bfe442362d3aaa9f373fe5090338cbb39531f6c0ff83d3724a0e098ad1b6af8831ad26473c8990d2bff5a18c6133692f8dc9ac0ffa0a80b2906d022a3b35de9aab72e18032f2279e9d8f82cdd993f0a07f9e6d15ce9c8e54fe22ba64d992c7c9314854c4a8162814e0bcfbc53c92b3738c2b0fcf \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xb7c5f441cecda52fd4c0626d811518acd09904e87633a9935fa160e4ec43d553e472c6ab87dedf6c159416c312355d90801c8c4924e1a9add31cb89722164609 1585247230000000 0 11000000
-18 \\x05f3a74fe2fe247762d754fcdd5bcb83d563ecfe3b6afcf5bb7c84ff818896fa3080d0311117990e576e31d54d3656c121018fe343bd32faf81b6ad6a21e332f \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x9a46f96041f4a79916724a0761def85dd23f21dc710642e665793d7055d3f1764fdcd177a287aa609886016d3b42578d0cd9acde50f46473d6f772e2949ccf00db546f37a53697f8cf3e63024d742f752508927af9ff6950fc3e6ee7032c3d49a79f858ec12d01bee7f78ba5b5e6e5ca31a10920d634544e0b5dac4b61490d5d \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xebc8717aabfabd10282204f9ef88b55edfc5949ae3e18b98e14a7d3e07abe0e82c3501628c6673b6655c75d9f8c2c4662a3e3624dc8bc4e7bd11f726e2c0c408 1585247230000000 0 11000000
-19 \\x66ae656c02e2fffdec39453e05cbc4422de0bae93fc22f845a681e9c0a80c5b27b11b16813ef6a63a184884b0eb911ef09f7b92fa043d48a320c7dea5c2bac7f \\x747f5c89194b7266b995701b18f8f5f9b31a1f18997b72aa306b12fe8c1be3db30507cf68c32408ad8ed9accdf31dfb56ac917b362dd21e4727b7e499540110f \\x6edf87b1913134c93ffd14a9734c2e4b69bf841b7ba133606d26edddd69b3561ea733d3de73bc3a3780f0feef8817d5e5b0d4a7b13bcfd185deb5594a1a3c9d5b3d9929d8d566c1c9769de373a035c033ac02c98974c55229493225dcb880d54fdc56e95a8ff1c945a28ca7c63952cf138626c05549c0ce7664883818d00bf11 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\x96bd755d90921484fe6689a86486e106d45fc78dc6ab6a73f9ca9f584d3825d6cc245658e10f5875954f6ecfb0c700362605a5d2804db0b72851a98397cf510d 1585247230000000 0 2000000
-20 \\x8a31eeabe64ae80d2c0c99c68e7200f5089608071ff04cfa32fc40c0143ccbec2d190a2aedf72ad5926177665e55f3ef66a3e19a9401afba76ef539e7451870f \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x08c3e0f40be0aaabcb31d5f8045ee908192792e0980c12945bbe5dfdcd1e779386b9fdfbddae72bf84021918967496c69e9d0f9630a3b4975b2dbbed4e6a4a4df8ce88f1c538131b519a1b4cd795c01b8e204440a3590b9ce757409b349442ca72f12f4e4f0e080be5628eb75aea9d6174726b88e393535d1a4ca314f82fb284 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\x0884c026f4df818dc7723823ee1542f6bdc81738478e7491842630473d5c67fcc2c1660cf0be1f45fce434c81c38b6dd9d3c1db74d5c241c29d04c658a18390a 1585247230000000 0 11000000
-25 \\xf2a6c8490d5759e9cd44d8e474ee8aac282a95326b8af1b3e6cd097f65e055633f8a95f456add1ae539a161142b551001ea7d643630e561169a0b0c2f2054917 \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x65d094cd42bf265e17f74ec061b5f58f07b460c9942f9049346e620d98c1109b4b1ac4b4db065539c8159f198824c271c41be310b14f2f0ad4276cc5d18f8aae19a8eed68eacd558dc82f270232dae708d259ae0250c5166950fe88b6c63bf6d296883d449d9daf3cc30d1e32861da0b3ac9703118e2b0e9411141646b3843a7 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xd87631d6086f3e5d71e211aacf324a7ec853b15b77c4b17e88c6d5920949c86a9b60e74ff1953c16c8430017925e2868c0c1ce7f51b0cbe1c8ced9d27824530d 1585247230000000 0 11000000
-21 \\x8faff62e01a2e73d5510bc93d1dce5bcc032e616c0650201650ca170dc815ec175e6223c85b232553c08a00bdf5d0db1ceaff775c91f2d9afac0ba97e9b63081 \\x747f5c89194b7266b995701b18f8f5f9b31a1f18997b72aa306b12fe8c1be3db30507cf68c32408ad8ed9accdf31dfb56ac917b362dd21e4727b7e499540110f \\xc1cbe09f93ec79424d690499b928d23c2653cf52f5fcd624d40627382fd35756c985f91e4d971da86ede32db8b0ed286f55fb02794d723866e61e9cee99a6765c7e42ae8f926d8d73a7f95441219450df3386fa0ea5949f8a83ed298f33c7cf3cdd16a7eefababc065429ee72db1cd79d065671dc3af70a21d97fbb8a211a975 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xeaea23d476645ffe095d8d6f5969d9b88e74a95a18ffbec57c5207fddb6c52f0805f0aade8995b788e11b215489792af2c8409798a4d17e2979ee078cd750609 1585247230000000 0 2000000
-22 \\x58a82e4e4716bde678b3532cc06632b50d47afc1637391e2b6e95b3fea751077a746967495c3ea32994316e4bbe2bf8bf2e1637a882938802e5429b43e4f0c84 \\x747f5c89194b7266b995701b18f8f5f9b31a1f18997b72aa306b12fe8c1be3db30507cf68c32408ad8ed9accdf31dfb56ac917b362dd21e4727b7e499540110f \\x7681e9b708d5e987a6dc6b23f081273b2f5645d2b3a6bae978f11e2cbb93e31061d23b1865fc8dee54d5205480929ed5dfae1c166e00575dbaa53eeed8b8b53405bf89a8fe2ac5bb1fbf2bc11c340492e307797d7d0b1edd8a10d44b1c1b72b20a784b03fdccb6eeb8b1038f8c68a5524230d57677e01402510c8d1a2bc4803e \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\x221c138f646f4ca50f54b87b942db487c126e49fe35e67af9457864d26d4bd013928efaae98a9d18bd064cefc5bdf82dcfbcc949ca041e35fa20e83953950809 1585247230000000 0 2000000
-23 \\x9aaa401c7cbe71171a1cf1a75fbcad721f2071a2495622c3a2be00fe19addf942c29260bb2e427310938e85cbe0bc76adbb19bbfb27e40fb69c4156010c7802a \\x747f5c89194b7266b995701b18f8f5f9b31a1f18997b72aa306b12fe8c1be3db30507cf68c32408ad8ed9accdf31dfb56ac917b362dd21e4727b7e499540110f \\x24dedb75ebc6b9529c652c41b88dd408f6a5078845d464a27ae4473a1d3f6aa93c719303ba1ef5fdde5a83941ca804179491e0cc22007af400c61c9175e0776b9879c6cc422a464c752c6eb06766553ca3c241bb4bb68810d7c9e4f723deacb2daaa3741a3c5b25ccce0d92e92dabdb659ceb9c4c1c8c4eefaab8b500cd0a1ca \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xf2c6e943016d8b743bab51992d1418edc8c340f45afc66464cd4d18dd7fe208235f35f5f3515cd7bed5e097b5e75eb9f8ac8d5eca59ae9432275d62dbe5e1806 1585247230000000 0 2000000
-24 \\x6ecd5993d2ebb9c51f62e3fb88db2092e39c63fbdbbe87adc8b9f3c874a2c89b1b20b87ba0ab1e8c25c57e272cbf584ab94bd666b45c299fda51b6e46d7425a0 \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x2900c01b645d85e0e49a1649cd416b5367bd3401f64d3b5230b05aa047e0dc6ba9c555056e51cffec724b2c38bdf0ed14519f098d987fd4b629ac27d1173c46203285913837c82422373b37707f7d866ed454ea90609ffff635efe7f0d79fbc916c8058dd3b108f07f3353e6fd8ceecb38fd7d12278b9e001e22630c888b625b \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xa134ac4ed1d1b5f83410753def597fcc22933c4e437c1de44136990fc4a42bfad1b4bf427c73a58024ccbd0500180628da898c73e737d56377e63bb781ecb208 1585247230000000 0 11000000
-26 \\x16c88a50e2920c8a330585b2e490e22929c6d7ce9abb2d519c6381efc9e939248fdca90b509ec25c2a0de4b13046e1692e9bde9304a03825c0bbf31db4043b50 \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x9a027e2d37b4eaf80f54372d5097332208e4c9daa42f7615855e545c01c3424e703150911901cfd8e7c82748c324e3f7000f3ec6168bbaa64f40b8cec7db1be6ff581c47506581add4828a9ccf031d52816dd7912872d221eff1ba197ecc7ba2e5e442384326e3075382623775a9be991e3ad14aa023c12e51aede29d14dba30 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\xb1ab12d70329f3942c13b29a4419fbf0b2d3c7321f11068faceb35a7e9bd9470c4decfd72e4a5384c02eab97b567c71fe926b9e8a058317e9eaef0bc0e27830f 1585247230000000 0 11000000
-27 \\x4a773b09239e4eb500fc1f7901824a15d989f9d0fabf717bacd3a8a81c7ae9858fea32244fbd882f0f55d8763262a34508b0a9e1ce50b96c3e04212568a724c8 \\x747f5c89194b7266b995701b18f8f5f9b31a1f18997b72aa306b12fe8c1be3db30507cf68c32408ad8ed9accdf31dfb56ac917b362dd21e4727b7e499540110f \\x216c2135704cf4f5e1275784111b36a56bfdc9a5370a0197a8b610db27c170074e30f37079cfd0a64ffd919236c1bd8e98c4ad1939e51e43adc0e180cfcc08d0ae2ac0565d5350662ab50700ea87b17a08ee280896b7d98cdb3abfd459297b3da7ea536cd75d420e516b7f25a85e9380bdff17750bbdf2857c6fab1c9029902c \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\x8ff08d79c31ee50fecab4b49730ed7c04d8f5fcadf1fd3279e128c88a0e8d0044d2927e090f4a674a6bb4224461bdee8b6fae97c484c37e8a832bda5cdce0b0b 1585247230000000 0 2000000
-28 \\x77f76187f4587134ab7e00b26347225863ef176a0d9885c4206e01611cca519ea0c5fc5ac6cdcbaef3823b9cca6bab32487bd9c5e0ccaaa00614917c9355c168 \\x515baaf90e9c05724c56f5304095310345032d27286e6136a972ac6017b9f67fa608a11a806b632cc1f7d367ea63e1df40cd2d0c0756de82f829bc67f8b82ec5 \\x711ef153eafca8a60f7a7033d50f96ab6f614d8ebde72ef96742d5d351cc75f409a9c8a25f8151e39fcfc7de7299e3f0a725b58705f4822ffbe9ebca7d0ace87722e91848ea5fb94f637c65d145b803781b23d1a22dc2742aec014364dc54ed6ce4aab2eb13da550b4b08e62d09297af96ae27f4098a2fe276b3e90e7b791502 \\x56df2171b6b15ab33867ed818451aa0b082e06e9246b7fab3c180c1e60ec3a36 \\x91cdc2831948debbebfb4f1cdc9f68eedf602e0a7972891a92db3f486a4517422d8b8f53301415a572bdcb80b28d1b4ba9e7050d5070fc82bc7503dea2c8f506 1585247230000000 0 11000000
-\.
-
-
---
--- Data for Name: wire_auditor_account_progress; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_auditor_account_progress (master_pub, account_name, last_wire_reserve_in_serial_id, last_wire_wire_out_serial_id, wire_in_off, wire_out_off) FROM stdin;
-\.
-
-
---
--- Data for Name: wire_auditor_progress; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_auditor_progress (master_pub, last_timestamp, last_reserve_close_uuid) FROM stdin;
-\.
-
-
---
--- Data for Name: wire_fee; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_fee (wire_method, start_date, end_date, wire_fee_val, wire_fee_frac, closing_fee_val, closing_fee_frac, master_sig) FROM stdin;
-\.
-
-
---
--- Data for Name: wire_out; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_out (wireout_uuid, execution_date, wtid_raw, wire_target, exchange_account_section, amount_val, amount_frac) FROM stdin;
-\.
-
-
---
--- Name: aggregation_tracking_aggregation_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.aggregation_tracking_aggregation_serial_id_seq', 1, false);
-
-
---
--- Name: app_bankaccount_account_no_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.app_bankaccount_account_no_seq', 11, true);
-
-
---
--- Name: app_banktransaction_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.app_banktransaction_id_seq', 2, true);
-
-
---
--- Name: auditor_reserves_auditor_reserves_rowid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auditor_reserves_auditor_reserves_rowid_seq', 1, false);
-
-
---
--- Name: auth_group_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auth_group_id_seq', 1, false);
-
-
---
--- Name: auth_group_permissions_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auth_group_permissions_id_seq', 1, false);
-
-
---
--- Name: auth_permission_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auth_permission_id_seq', 32, true);
-
-
---
--- Name: auth_user_groups_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auth_user_groups_id_seq', 1, false);
-
-
---
--- Name: auth_user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auth_user_id_seq', 11, true);
-
-
---
--- Name: auth_user_user_permissions_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auth_user_user_permissions_id_seq', 1, false);
-
-
---
--- Name: denomination_revocations_denom_revocations_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.denomination_revocations_denom_revocations_serial_id_seq', 2, true);
-
-
---
--- Name: deposit_confirmations_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.deposit_confirmations_serial_id_seq', 1, true);
-
-
---
--- Name: deposits_deposit_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.deposits_deposit_serial_id_seq', 2, true);
-
-
---
--- Name: django_content_type_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.django_content_type_id_seq', 8, true);
-
-
---
--- Name: django_migrations_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.django_migrations_id_seq', 15, true);
-
-
---
--- Name: merchant_contract_terms_row_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_contract_terms_row_id_seq', 2, true);
-
-
---
--- Name: merchant_refunds_rtransaction_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_refunds_rtransaction_id_seq', 1, false);
-
-
---
--- Name: prewire_prewire_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.prewire_prewire_uuid_seq', 1, false);
-
-
---
--- Name: recoup_recoup_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.recoup_recoup_uuid_seq', 1, true);
-
-
---
--- Name: recoup_refresh_recoup_refresh_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.recoup_refresh_recoup_refresh_uuid_seq', 8, true);
-
-
---
--- Name: refresh_commitments_melt_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.refresh_commitments_melt_serial_id_seq', 3, true);
-
-
---
--- Name: refunds_refund_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.refunds_refund_serial_id_seq', 1, false);
-
-
---
--- Name: reserves_close_close_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.reserves_close_close_uuid_seq', 1, false);
-
-
---
--- Name: reserves_in_reserve_in_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.reserves_in_reserve_in_serial_id_seq', 1, true);
-
-
---
--- Name: reserves_out_reserve_out_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.reserves_out_reserve_out_serial_id_seq', 28, true);
-
-
---
--- Name: wire_out_wireout_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.wire_out_wireout_uuid_seq', 1, false);
-
-
---
--- Name: patches patches_pkey; Type: CONSTRAINT; Schema: _v; Owner: -
---
-
-ALTER TABLE ONLY _v.patches
- ADD CONSTRAINT patches_pkey PRIMARY KEY (patch_name);
-
-
---
--- Name: aggregation_tracking aggregation_tracking_aggregation_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.aggregation_tracking
- ADD CONSTRAINT aggregation_tracking_aggregation_serial_id_key UNIQUE (aggregation_serial_id);
-
-
---
--- Name: aggregation_tracking aggregation_tracking_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.aggregation_tracking
- ADD CONSTRAINT aggregation_tracking_pkey PRIMARY KEY (deposit_serial_id);
-
-
---
--- Name: app_bankaccount app_bankaccount_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_bankaccount
- ADD CONSTRAINT app_bankaccount_pkey PRIMARY KEY (account_no);
-
-
---
--- Name: app_bankaccount app_bankaccount_user_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_bankaccount
- ADD CONSTRAINT app_bankaccount_user_id_key UNIQUE (user_id);
-
-
---
--- Name: app_banktransaction app_banktransaction_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_banktransaction
- ADD CONSTRAINT app_banktransaction_pkey PRIMARY KEY (id);
-
-
---
--- Name: app_banktransaction app_banktransaction_request_uid_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_banktransaction
- ADD CONSTRAINT app_banktransaction_request_uid_key UNIQUE (request_uid);
-
-
---
--- Name: app_talerwithdrawoperation app_talerwithdrawoperation_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_talerwithdrawoperation
- ADD CONSTRAINT app_talerwithdrawoperation_pkey PRIMARY KEY (withdraw_id);
-
-
---
--- Name: auditor_denomination_pending auditor_denomination_pending_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denomination_pending
- ADD CONSTRAINT auditor_denomination_pending_pkey PRIMARY KEY (denom_pub_hash);
-
-
---
--- Name: auditor_denominations auditor_denominations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denominations
- ADD CONSTRAINT auditor_denominations_pkey PRIMARY KEY (denom_pub_hash);
-
-
---
--- Name: auditor_exchanges auditor_exchanges_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_exchanges
- ADD CONSTRAINT auditor_exchanges_pkey PRIMARY KEY (master_pub);
-
-
---
--- Name: auditor_historic_denomination_revenue auditor_historic_denomination_revenue_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_historic_denomination_revenue
- ADD CONSTRAINT auditor_historic_denomination_revenue_pkey PRIMARY KEY (denom_pub_hash);
-
-
---
--- Name: auditor_progress_aggregation auditor_progress_aggregation_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_aggregation
- ADD CONSTRAINT auditor_progress_aggregation_pkey PRIMARY KEY (master_pub);
-
-
---
--- Name: auditor_progress_coin auditor_progress_coin_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_coin
- ADD CONSTRAINT auditor_progress_coin_pkey PRIMARY KEY (master_pub);
-
-
---
--- Name: auditor_progress_deposit_confirmation auditor_progress_deposit_confirmation_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_deposit_confirmation
- ADD CONSTRAINT auditor_progress_deposit_confirmation_pkey PRIMARY KEY (master_pub);
-
-
---
--- Name: auditor_progress_reserve auditor_progress_reserve_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_reserve
- ADD CONSTRAINT auditor_progress_reserve_pkey PRIMARY KEY (master_pub);
-
-
---
--- Name: auditor_reserves auditor_reserves_auditor_reserves_rowid_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_reserves
- ADD CONSTRAINT auditor_reserves_auditor_reserves_rowid_key UNIQUE (auditor_reserves_rowid);
-
-
---
--- Name: auth_group auth_group_name_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group
- ADD CONSTRAINT auth_group_name_key UNIQUE (name);
-
-
---
--- Name: auth_group_permissions auth_group_permissions_group_id_permission_id_0cd325b0_uniq; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group_permissions
- ADD CONSTRAINT auth_group_permissions_group_id_permission_id_0cd325b0_uniq UNIQUE (group_id, permission_id);
-
-
---
--- Name: auth_group_permissions auth_group_permissions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group_permissions
- ADD CONSTRAINT auth_group_permissions_pkey PRIMARY KEY (id);
-
-
---
--- Name: auth_group auth_group_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group
- ADD CONSTRAINT auth_group_pkey PRIMARY KEY (id);
-
-
---
--- Name: auth_permission auth_permission_content_type_id_codename_01ab375a_uniq; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_permission
- ADD CONSTRAINT auth_permission_content_type_id_codename_01ab375a_uniq UNIQUE (content_type_id, codename);
-
-
---
--- Name: auth_permission auth_permission_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_permission
- ADD CONSTRAINT auth_permission_pkey PRIMARY KEY (id);
-
-
---
--- Name: auth_user_groups auth_user_groups_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_groups
- ADD CONSTRAINT auth_user_groups_pkey PRIMARY KEY (id);
-
-
---
--- Name: auth_user_groups auth_user_groups_user_id_group_id_94350c0c_uniq; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_groups
- ADD CONSTRAINT auth_user_groups_user_id_group_id_94350c0c_uniq UNIQUE (user_id, group_id);
-
-
---
--- Name: auth_user auth_user_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user
- ADD CONSTRAINT auth_user_pkey PRIMARY KEY (id);
-
-
---
--- Name: auth_user_user_permissions auth_user_user_permissions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_user_permissions
- ADD CONSTRAINT auth_user_user_permissions_pkey PRIMARY KEY (id);
-
-
---
--- Name: auth_user_user_permissions auth_user_user_permissions_user_id_permission_id_14a6b632_uniq; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_user_permissions
- ADD CONSTRAINT auth_user_user_permissions_user_id_permission_id_14a6b632_uniq UNIQUE (user_id, permission_id);
-
-
---
--- Name: auth_user auth_user_username_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user
- ADD CONSTRAINT auth_user_username_key UNIQUE (username);
-
-
---
--- Name: denomination_revocations denomination_revocations_denom_revocations_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denomination_revocations
- ADD CONSTRAINT denomination_revocations_denom_revocations_serial_id_key UNIQUE (denom_revocations_serial_id);
-
-
---
--- Name: denomination_revocations denomination_revocations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denomination_revocations
- ADD CONSTRAINT denomination_revocations_pkey PRIMARY KEY (denom_pub_hash);
-
-
---
--- Name: denominations denominations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denominations
- ADD CONSTRAINT denominations_pkey PRIMARY KEY (denom_pub_hash);
-
-
---
--- Name: deposit_confirmations deposit_confirmations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposit_confirmations
- ADD CONSTRAINT deposit_confirmations_pkey PRIMARY KEY (h_contract_terms, h_wire, coin_pub, merchant_pub, exchange_sig, exchange_pub, master_sig);
-
-
---
--- Name: deposit_confirmations deposit_confirmations_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposit_confirmations
- ADD CONSTRAINT deposit_confirmations_serial_id_key UNIQUE (serial_id);
-
-
---
--- Name: deposits deposits_coin_pub_merchant_pub_h_contract_terms_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_coin_pub_merchant_pub_h_contract_terms_key UNIQUE (coin_pub, merchant_pub, h_contract_terms);
-
-
---
--- Name: deposits deposits_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_pkey PRIMARY KEY (deposit_serial_id);
-
-
---
--- Name: django_content_type django_content_type_app_label_model_76bd3d3b_uniq; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.django_content_type
- ADD CONSTRAINT django_content_type_app_label_model_76bd3d3b_uniq UNIQUE (app_label, model);
-
-
---
--- Name: django_content_type django_content_type_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.django_content_type
- ADD CONSTRAINT django_content_type_pkey PRIMARY KEY (id);
-
-
---
--- Name: django_migrations django_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.django_migrations
- ADD CONSTRAINT django_migrations_pkey PRIMARY KEY (id);
-
-
---
--- Name: django_session django_session_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.django_session
- ADD CONSTRAINT django_session_pkey PRIMARY KEY (session_key);
-
-
---
--- Name: exchange_wire_fees exchange_wire_fees_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.exchange_wire_fees
- ADD CONSTRAINT exchange_wire_fees_pkey PRIMARY KEY (exchange_pub, h_wire_method, start_date, end_date);
-
-
---
--- Name: known_coins known_coins_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.known_coins
- ADD CONSTRAINT known_coins_pkey PRIMARY KEY (coin_pub);
-
-
---
--- Name: merchant_contract_terms merchant_contract_terms_h_contract_terms_merchant_pub_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms
- ADD CONSTRAINT merchant_contract_terms_h_contract_terms_merchant_pub_key UNIQUE (h_contract_terms, merchant_pub);
-
-
---
--- Name: merchant_contract_terms merchant_contract_terms_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms
- ADD CONSTRAINT merchant_contract_terms_pkey PRIMARY KEY (order_id, merchant_pub);
-
-
---
--- Name: merchant_contract_terms merchant_contract_terms_row_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms
- ADD CONSTRAINT merchant_contract_terms_row_id_key UNIQUE (row_id);
-
-
---
--- Name: merchant_deposits merchant_deposits_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_pkey PRIMARY KEY (h_contract_terms, coin_pub);
-
-
---
--- Name: merchant_orders merchant_orders_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_orders
- ADD CONSTRAINT merchant_orders_pkey PRIMARY KEY (order_id, merchant_pub);
-
-
---
--- Name: merchant_proofs merchant_proofs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_proofs
- ADD CONSTRAINT merchant_proofs_pkey PRIMARY KEY (wtid, exchange_url);
-
-
---
--- Name: merchant_refunds merchant_refunds_rtransaction_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refunds
- ADD CONSTRAINT merchant_refunds_rtransaction_id_key UNIQUE (rtransaction_id);
-
-
---
--- Name: merchant_session_info merchant_session_info_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_session_info
- ADD CONSTRAINT merchant_session_info_pkey PRIMARY KEY (session_id, fulfillment_url, merchant_pub);
-
-
---
--- Name: merchant_session_info merchant_session_info_session_id_fulfillment_url_order_id_m_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_session_info
- ADD CONSTRAINT merchant_session_info_session_id_fulfillment_url_order_id_m_key UNIQUE (session_id, fulfillment_url, order_id, merchant_pub);
-
-
---
--- Name: merchant_tip_pickups merchant_tip_pickups_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickups
- ADD CONSTRAINT merchant_tip_pickups_pkey PRIMARY KEY (pickup_id);
-
-
---
--- Name: merchant_tip_reserve_credits merchant_tip_reserve_credits_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserve_credits
- ADD CONSTRAINT merchant_tip_reserve_credits_pkey PRIMARY KEY (credit_uuid);
-
-
---
--- Name: merchant_tip_reserves merchant_tip_reserves_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserves
- ADD CONSTRAINT merchant_tip_reserves_pkey PRIMARY KEY (reserve_priv);
-
-
---
--- Name: merchant_tips merchant_tips_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tips
- ADD CONSTRAINT merchant_tips_pkey PRIMARY KEY (tip_id);
-
-
---
--- Name: merchant_transfers merchant_transfers_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfers
- ADD CONSTRAINT merchant_transfers_pkey PRIMARY KEY (h_contract_terms, coin_pub);
-
-
---
--- Name: prewire prewire_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.prewire
- ADD CONSTRAINT prewire_pkey PRIMARY KEY (prewire_uuid);
-
-
---
--- Name: recoup recoup_recoup_uuid_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup
- ADD CONSTRAINT recoup_recoup_uuid_key UNIQUE (recoup_uuid);
-
-
---
--- Name: recoup_refresh recoup_refresh_recoup_refresh_uuid_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh
- ADD CONSTRAINT recoup_refresh_recoup_refresh_uuid_key UNIQUE (recoup_refresh_uuid);
-
-
---
--- Name: refresh_commitments refresh_commitments_melt_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_commitments
- ADD CONSTRAINT refresh_commitments_melt_serial_id_key UNIQUE (melt_serial_id);
-
-
---
--- Name: refresh_commitments refresh_commitments_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_commitments
- ADD CONSTRAINT refresh_commitments_pkey PRIMARY KEY (rc);
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_coin_ev_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_coin_ev_key UNIQUE (coin_ev);
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_h_coin_ev_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_h_coin_ev_key UNIQUE (h_coin_ev);
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_pkey PRIMARY KEY (rc, freshcoin_index);
-
-
---
--- Name: refresh_transfer_keys refresh_transfer_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_transfer_keys
- ADD CONSTRAINT refresh_transfer_keys_pkey PRIMARY KEY (rc);
-
-
---
--- Name: refunds refunds_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refunds
- ADD CONSTRAINT refunds_pkey PRIMARY KEY (coin_pub, merchant_pub, h_contract_terms, rtransaction_id);
-
-
---
--- Name: refunds refunds_refund_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refunds
- ADD CONSTRAINT refunds_refund_serial_id_key UNIQUE (refund_serial_id);
-
-
---
--- Name: reserves_close reserves_close_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_close
- ADD CONSTRAINT reserves_close_pkey PRIMARY KEY (close_uuid);
-
-
---
--- Name: reserves_in reserves_in_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_in
- ADD CONSTRAINT reserves_in_pkey PRIMARY KEY (reserve_pub, wire_reference);
-
-
---
--- Name: reserves_in reserves_in_reserve_in_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_in
- ADD CONSTRAINT reserves_in_reserve_in_serial_id_key UNIQUE (reserve_in_serial_id);
-
-
---
--- Name: reserves_out reserves_out_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out
- ADD CONSTRAINT reserves_out_pkey PRIMARY KEY (h_blind_ev);
-
-
---
--- Name: reserves_out reserves_out_reserve_out_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out
- ADD CONSTRAINT reserves_out_reserve_out_serial_id_key UNIQUE (reserve_out_serial_id);
-
-
---
--- Name: reserves reserves_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves
- ADD CONSTRAINT reserves_pkey PRIMARY KEY (reserve_pub);
-
-
---
--- Name: wire_auditor_account_progress wire_auditor_account_progress_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_auditor_account_progress
- ADD CONSTRAINT wire_auditor_account_progress_pkey PRIMARY KEY (master_pub, account_name);
-
-
---
--- Name: wire_auditor_progress wire_auditor_progress_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_auditor_progress
- ADD CONSTRAINT wire_auditor_progress_pkey PRIMARY KEY (master_pub);
-
-
---
--- Name: wire_fee wire_fee_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_fee
- ADD CONSTRAINT wire_fee_pkey PRIMARY KEY (wire_method, start_date);
-
-
---
--- Name: wire_out wire_out_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_out
- ADD CONSTRAINT wire_out_pkey PRIMARY KEY (wireout_uuid);
-
-
---
--- Name: wire_out wire_out_wtid_raw_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_out
- ADD CONSTRAINT wire_out_wtid_raw_key UNIQUE (wtid_raw);
-
-
---
--- Name: aggregation_tracking_wtid_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX aggregation_tracking_wtid_index ON public.aggregation_tracking USING btree (wtid_raw);
-
-
---
--- Name: INDEX aggregation_tracking_wtid_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.aggregation_tracking_wtid_index IS 'for lookup_transactions';
-
-
---
--- Name: app_banktransaction_credit_account_id_a8ba05ac; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX app_banktransaction_credit_account_id_a8ba05ac ON public.app_banktransaction USING btree (credit_account_id);
-
-
---
--- Name: app_banktransaction_date_f72bcad6; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX app_banktransaction_date_f72bcad6 ON public.app_banktransaction USING btree (date);
-
-
---
--- Name: app_banktransaction_debit_account_id_5b1f7528; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX app_banktransaction_debit_account_id_5b1f7528 ON public.app_banktransaction USING btree (debit_account_id);
-
-
---
--- Name: app_banktransaction_request_uid_b7d06af5_like; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX app_banktransaction_request_uid_b7d06af5_like ON public.app_banktransaction USING btree (request_uid varchar_pattern_ops);
-
-
---
--- Name: app_talerwithdrawoperation_selected_exchange_account__6c8b96cf; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX app_talerwithdrawoperation_selected_exchange_account__6c8b96cf ON public.app_talerwithdrawoperation USING btree (selected_exchange_account_id);
-
-
---
--- Name: app_talerwithdrawoperation_withdraw_account_id_992dc5b3; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX app_talerwithdrawoperation_withdraw_account_id_992dc5b3 ON public.app_talerwithdrawoperation USING btree (withdraw_account_id);
-
-
---
--- Name: auditor_historic_reserve_summary_by_master_pub_start_date; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auditor_historic_reserve_summary_by_master_pub_start_date ON public.auditor_historic_reserve_summary USING btree (master_pub, start_date);
-
-
---
--- Name: auditor_reserves_by_reserve_pub; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auditor_reserves_by_reserve_pub ON public.auditor_reserves USING btree (reserve_pub);
-
-
---
--- Name: auth_group_name_a6ea08ec_like; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_group_name_a6ea08ec_like ON public.auth_group USING btree (name varchar_pattern_ops);
-
-
---
--- Name: auth_group_permissions_group_id_b120cbf9; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_group_permissions_group_id_b120cbf9 ON public.auth_group_permissions USING btree (group_id);
-
-
---
--- Name: auth_group_permissions_permission_id_84c5c92e; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_group_permissions_permission_id_84c5c92e ON public.auth_group_permissions USING btree (permission_id);
-
-
---
--- Name: auth_permission_content_type_id_2f476e4b; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_permission_content_type_id_2f476e4b ON public.auth_permission USING btree (content_type_id);
-
-
---
--- Name: auth_user_groups_group_id_97559544; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_user_groups_group_id_97559544 ON public.auth_user_groups USING btree (group_id);
-
-
---
--- Name: auth_user_groups_user_id_6a12ed8b; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_user_groups_user_id_6a12ed8b ON public.auth_user_groups USING btree (user_id);
-
-
---
--- Name: auth_user_user_permissions_permission_id_1fbb5f2c; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_user_user_permissions_permission_id_1fbb5f2c ON public.auth_user_user_permissions USING btree (permission_id);
-
-
---
--- Name: auth_user_user_permissions_user_id_a95ead1b; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_user_user_permissions_user_id_a95ead1b ON public.auth_user_user_permissions USING btree (user_id);
-
-
---
--- Name: auth_user_username_6821ab7c_like; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX auth_user_username_6821ab7c_like ON public.auth_user USING btree (username varchar_pattern_ops);
-
-
---
--- Name: denominations_expire_legal_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX denominations_expire_legal_index ON public.denominations USING btree (expire_legal);
-
-
---
--- Name: deposits_coin_pub_merchant_contract_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX deposits_coin_pub_merchant_contract_index ON public.deposits USING btree (coin_pub, merchant_pub, h_contract_terms);
-
-
---
--- Name: INDEX deposits_coin_pub_merchant_contract_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.deposits_coin_pub_merchant_contract_index IS 'for deposits_get_ready';
-
-
---
--- Name: deposits_get_ready_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX deposits_get_ready_index ON public.deposits USING btree (tiny, done, wire_deadline, refund_deadline);
-
-
---
--- Name: deposits_iterate_matching_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX deposits_iterate_matching_index ON public.deposits USING btree (merchant_pub, h_wire, done, wire_deadline);
-
-
---
--- Name: INDEX deposits_iterate_matching_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.deposits_iterate_matching_index IS 'for deposits_iterate_matching';
-
-
---
--- Name: django_session_expire_date_a5c62663; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX django_session_expire_date_a5c62663 ON public.django_session USING btree (expire_date);
-
-
---
--- Name: django_session_session_key_c0390e0f_like; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX django_session_session_key_c0390e0f_like ON public.django_session USING btree (session_key varchar_pattern_ops);
-
-
---
--- Name: known_coins_by_denomination; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX known_coins_by_denomination ON public.known_coins USING btree (denom_pub_hash);
-
-
---
--- Name: merchant_transfers_by_coin; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_transfers_by_coin ON public.merchant_transfers USING btree (h_contract_terms, coin_pub);
-
-
---
--- Name: merchant_transfers_by_wtid; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_transfers_by_wtid ON public.merchant_transfers USING btree (wtid);
-
-
---
--- Name: prepare_iteration_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX prepare_iteration_index ON public.prewire USING btree (finished);
-
-
---
--- Name: INDEX prepare_iteration_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.prepare_iteration_index IS 'for wire_prepare_data_get and gc_prewire';
-
-
---
--- Name: recoup_by_coin_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_by_coin_index ON public.recoup USING btree (coin_pub);
-
-
---
--- Name: recoup_by_h_blind_ev; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_by_h_blind_ev ON public.recoup USING btree (h_blind_ev);
-
-
---
--- Name: recoup_for_by_reserve; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_for_by_reserve ON public.recoup USING btree (coin_pub, h_blind_ev);
-
-
---
--- Name: recoup_refresh_by_coin_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_refresh_by_coin_index ON public.recoup_refresh USING btree (coin_pub);
-
-
---
--- Name: recoup_refresh_by_h_blind_ev; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_refresh_by_h_blind_ev ON public.recoup_refresh USING btree (h_blind_ev);
-
-
---
--- Name: recoup_refresh_for_by_reserve; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_refresh_for_by_reserve ON public.recoup_refresh USING btree (coin_pub, h_blind_ev);
-
-
---
--- Name: refresh_commitments_old_coin_pub_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX refresh_commitments_old_coin_pub_index ON public.refresh_commitments USING btree (old_coin_pub);
-
-
---
--- Name: refresh_revealed_coins_coin_pub_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX refresh_revealed_coins_coin_pub_index ON public.refresh_revealed_coins USING btree (denom_pub_hash);
-
-
---
--- Name: refresh_transfer_keys_coin_tpub; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX refresh_transfer_keys_coin_tpub ON public.refresh_transfer_keys USING btree (rc, transfer_pub);
-
-
---
--- Name: INDEX refresh_transfer_keys_coin_tpub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.refresh_transfer_keys_coin_tpub IS 'for get_link (unsure if this helps or hurts for performance as there should be very few transfer public keys per rc, but at least in theory this helps the ORDER BY clause)';
-
-
---
--- Name: refunds_coin_pub_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX refunds_coin_pub_index ON public.refunds USING btree (coin_pub);
-
-
---
--- Name: reserves_close_by_reserve; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_close_by_reserve ON public.reserves_close USING btree (reserve_pub);
-
-
---
--- Name: reserves_expiration_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_expiration_index ON public.reserves USING btree (expiration_date, current_balance_val, current_balance_frac);
-
-
---
--- Name: INDEX reserves_expiration_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.reserves_expiration_index IS 'used in get_expired_reserves';
-
-
---
--- Name: reserves_gc_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_gc_index ON public.reserves USING btree (gc_date);
-
-
---
--- Name: INDEX reserves_gc_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.reserves_gc_index IS 'for reserve garbage collection';
-
-
---
--- Name: reserves_in_exchange_account_serial; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_in_exchange_account_serial ON public.reserves_in USING btree (exchange_account_section, reserve_in_serial_id DESC);
-
-
---
--- Name: reserves_in_execution_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_in_execution_index ON public.reserves_in USING btree (exchange_account_section, execution_date);
-
-
---
--- Name: reserves_out_execution_date; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_out_execution_date ON public.reserves_out USING btree (execution_date);
-
-
---
--- Name: reserves_out_for_get_withdraw_info; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_out_for_get_withdraw_info ON public.reserves_out USING btree (denom_pub_hash, h_blind_ev);
-
-
---
--- Name: reserves_out_reserve_pub_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_out_reserve_pub_index ON public.reserves_out USING btree (reserve_pub);
-
-
---
--- Name: INDEX reserves_out_reserve_pub_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.reserves_out_reserve_pub_index IS 'for get_reserves_out';
-
-
---
--- Name: wire_fee_gc_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX wire_fee_gc_index ON public.wire_fee USING btree (end_date);
-
-
---
--- Name: aggregation_tracking aggregation_tracking_deposit_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.aggregation_tracking
- ADD CONSTRAINT aggregation_tracking_deposit_serial_id_fkey FOREIGN KEY (deposit_serial_id) REFERENCES public.deposits(deposit_serial_id) ON DELETE CASCADE;
-
-
---
--- Name: app_bankaccount app_bankaccount_user_id_2722a34f_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_bankaccount
- ADD CONSTRAINT app_bankaccount_user_id_2722a34f_fk_auth_user_id FOREIGN KEY (user_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: app_banktransaction app_banktransaction_credit_account_id_a8ba05ac_fk_app_banka; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_banktransaction
- ADD CONSTRAINT app_banktransaction_credit_account_id_a8ba05ac_fk_app_banka FOREIGN KEY (credit_account_id) REFERENCES public.app_bankaccount(account_no) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: app_banktransaction app_banktransaction_debit_account_id_5b1f7528_fk_app_banka; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_banktransaction
- ADD CONSTRAINT app_banktransaction_debit_account_id_5b1f7528_fk_app_banka FOREIGN KEY (debit_account_id) REFERENCES public.app_bankaccount(account_no) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: app_talerwithdrawoperation app_talerwithdrawope_selected_exchange_ac_6c8b96cf_fk_app_banka; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_talerwithdrawoperation
- ADD CONSTRAINT app_talerwithdrawope_selected_exchange_ac_6c8b96cf_fk_app_banka FOREIGN KEY (selected_exchange_account_id) REFERENCES public.app_bankaccount(account_no) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: app_talerwithdrawoperation app_talerwithdrawope_withdraw_account_id_992dc5b3_fk_app_banka; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.app_talerwithdrawoperation
- ADD CONSTRAINT app_talerwithdrawope_withdraw_account_id_992dc5b3_fk_app_banka FOREIGN KEY (withdraw_account_id) REFERENCES public.app_bankaccount(account_no) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auditor_denomination_pending auditor_denomination_pending_denom_pub_hash_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denomination_pending
- ADD CONSTRAINT auditor_denomination_pending_denom_pub_hash_fkey FOREIGN KEY (denom_pub_hash) REFERENCES public.auditor_denominations(denom_pub_hash) ON DELETE CASCADE;
-
-
---
--- Name: auth_group_permissions auth_group_permissio_permission_id_84c5c92e_fk_auth_perm; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group_permissions
- ADD CONSTRAINT auth_group_permissio_permission_id_84c5c92e_fk_auth_perm FOREIGN KEY (permission_id) REFERENCES public.auth_permission(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auth_group_permissions auth_group_permissions_group_id_b120cbf9_fk_auth_group_id; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_group_permissions
- ADD CONSTRAINT auth_group_permissions_group_id_b120cbf9_fk_auth_group_id FOREIGN KEY (group_id) REFERENCES public.auth_group(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auth_permission auth_permission_content_type_id_2f476e4b_fk_django_co; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_permission
- ADD CONSTRAINT auth_permission_content_type_id_2f476e4b_fk_django_co FOREIGN KEY (content_type_id) REFERENCES public.django_content_type(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auth_user_groups auth_user_groups_group_id_97559544_fk_auth_group_id; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_groups
- ADD CONSTRAINT auth_user_groups_group_id_97559544_fk_auth_group_id FOREIGN KEY (group_id) REFERENCES public.auth_group(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auth_user_groups auth_user_groups_user_id_6a12ed8b_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_groups
- ADD CONSTRAINT auth_user_groups_user_id_6a12ed8b_fk_auth_user_id FOREIGN KEY (user_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auth_user_user_permissions auth_user_user_permi_permission_id_1fbb5f2c_fk_auth_perm; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_user_permissions
- ADD CONSTRAINT auth_user_user_permi_permission_id_1fbb5f2c_fk_auth_perm FOREIGN KEY (permission_id) REFERENCES public.auth_permission(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: auth_user_user_permissions auth_user_user_permissions_user_id_a95ead1b_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auth_user_user_permissions
- ADD CONSTRAINT auth_user_user_permissions_user_id_a95ead1b_fk_auth_user_id FOREIGN KEY (user_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED;
-
-
---
--- Name: denomination_revocations denomination_revocations_denom_pub_hash_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denomination_revocations
- ADD CONSTRAINT denomination_revocations_denom_pub_hash_fkey FOREIGN KEY (denom_pub_hash) REFERENCES public.denominations(denom_pub_hash) ON DELETE CASCADE;
-
-
---
--- Name: deposits deposits_coin_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_coin_pub_fkey FOREIGN KEY (coin_pub) REFERENCES public.known_coins(coin_pub) ON DELETE CASCADE;
-
-
---
--- Name: known_coins known_coins_denom_pub_hash_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.known_coins
- ADD CONSTRAINT known_coins_denom_pub_hash_fkey FOREIGN KEY (denom_pub_hash) REFERENCES public.denominations(denom_pub_hash) ON DELETE CASCADE;
-
-
---
--- Name: auditor_exchange_signkeys master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_exchange_signkeys
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_denominations master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denominations
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_progress_reserve master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_reserve
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_progress_aggregation master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_aggregation
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_progress_deposit_confirmation master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_deposit_confirmation
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_progress_coin master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_progress_coin
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: wire_auditor_account_progress master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_auditor_account_progress
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: wire_auditor_progress master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_auditor_progress
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_reserves master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_reserves
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_reserve_balance master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_reserve_balance
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_wire_fee_balance master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_wire_fee_balance
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_balance_summary master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_balance_summary
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_historic_denomination_revenue master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_historic_denomination_revenue
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_historic_reserve_summary master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_historic_reserve_summary
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: deposit_confirmations master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposit_confirmations
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: auditor_predicted_result master_pub_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_predicted_result
- ADD CONSTRAINT master_pub_ref FOREIGN KEY (master_pub) REFERENCES public.auditor_exchanges(master_pub) ON DELETE CASCADE;
-
-
---
--- Name: merchant_deposits merchant_deposits_h_contract_terms_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_h_contract_terms_fkey FOREIGN KEY (h_contract_terms, merchant_pub) REFERENCES public.merchant_contract_terms(h_contract_terms, merchant_pub);
-
-
---
--- Name: merchant_tip_pickups merchant_tip_pickups_tip_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickups
- ADD CONSTRAINT merchant_tip_pickups_tip_id_fkey FOREIGN KEY (tip_id) REFERENCES public.merchant_tips(tip_id) ON DELETE CASCADE;
-
-
---
--- Name: recoup recoup_coin_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup
- ADD CONSTRAINT recoup_coin_pub_fkey FOREIGN KEY (coin_pub) REFERENCES public.known_coins(coin_pub);
-
-
---
--- Name: recoup recoup_h_blind_ev_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup
- ADD CONSTRAINT recoup_h_blind_ev_fkey FOREIGN KEY (h_blind_ev) REFERENCES public.reserves_out(h_blind_ev) ON DELETE CASCADE;
-
-
---
--- Name: recoup_refresh recoup_refresh_coin_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh
- ADD CONSTRAINT recoup_refresh_coin_pub_fkey FOREIGN KEY (coin_pub) REFERENCES public.known_coins(coin_pub);
-
-
---
--- Name: recoup_refresh recoup_refresh_h_blind_ev_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh
- ADD CONSTRAINT recoup_refresh_h_blind_ev_fkey FOREIGN KEY (h_blind_ev) REFERENCES public.refresh_revealed_coins(h_coin_ev) ON DELETE CASCADE;
-
-
---
--- Name: refresh_commitments refresh_commitments_old_coin_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_commitments
- ADD CONSTRAINT refresh_commitments_old_coin_pub_fkey FOREIGN KEY (old_coin_pub) REFERENCES public.known_coins(coin_pub) ON DELETE CASCADE;
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_denom_pub_hash_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_denom_pub_hash_fkey FOREIGN KEY (denom_pub_hash) REFERENCES public.denominations(denom_pub_hash) ON DELETE CASCADE;
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_rc_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_rc_fkey FOREIGN KEY (rc) REFERENCES public.refresh_commitments(rc) ON DELETE CASCADE;
-
-
---
--- Name: refresh_transfer_keys refresh_transfer_keys_rc_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_transfer_keys
- ADD CONSTRAINT refresh_transfer_keys_rc_fkey FOREIGN KEY (rc) REFERENCES public.refresh_commitments(rc) ON DELETE CASCADE;
-
-
---
--- Name: refunds refunds_coin_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refunds
- ADD CONSTRAINT refunds_coin_pub_fkey FOREIGN KEY (coin_pub) REFERENCES public.known_coins(coin_pub) ON DELETE CASCADE;
-
-
---
--- Name: reserves_close reserves_close_reserve_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_close
- ADD CONSTRAINT reserves_close_reserve_pub_fkey FOREIGN KEY (reserve_pub) REFERENCES public.reserves(reserve_pub) ON DELETE CASCADE;
-
-
---
--- Name: reserves_in reserves_in_reserve_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_in
- ADD CONSTRAINT reserves_in_reserve_pub_fkey FOREIGN KEY (reserve_pub) REFERENCES public.reserves(reserve_pub) ON DELETE CASCADE;
-
-
---
--- Name: reserves_out reserves_out_denom_pub_hash_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out
- ADD CONSTRAINT reserves_out_denom_pub_hash_fkey FOREIGN KEY (denom_pub_hash) REFERENCES public.denominations(denom_pub_hash);
-
-
---
--- Name: reserves_out reserves_out_reserve_pub_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out
- ADD CONSTRAINT reserves_out_reserve_pub_fkey FOREIGN KEY (reserve_pub) REFERENCES public.reserves(reserve_pub) ON DELETE CASCADE;
-
-
---
--- Name: aggregation_tracking wire_out_ref; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.aggregation_tracking
- ADD CONSTRAINT wire_out_ref FOREIGN KEY (wtid_raw) REFERENCES public.wire_out(wtid_raw) ON DELETE CASCADE DEFERRABLE;
-
-
---
--- PostgreSQL database dump complete
---
-
diff --git a/src/auditor/setup.sh b/src/auditor/setup.sh
new file mode 100755
index 000000000..bb17e92ae
--- /dev/null
+++ b/src/auditor/setup.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+# This file is in the public domain
+
+# Script to be inlined into the main test scripts. Defines function 'setup()'
+# which wraps around 'taler-unified-setup.sh' to launch GNU Taler services.
+# Call setup() with the arguments to pass to 'taler-unified-setup'. setup()
+# will then launch GNU Taler, wait for the process to be complete before
+# returning. The script will also install an exit handler to ensure the GNU
+# Taler processes are stopped when the shell exits.
+
+set -eu
+
+# Cleanup to run whenever we exit
+function exit_cleanup()
+{
+ if [ ! -z ${SETUP_PID+x} ]
+ then
+ echo "Killing taler-unified-setup ($SETUP_PID)" >&2
+ kill -TERM "$SETUP_PID" 2> /dev/null || true
+ wait "$SETUP_PID" 2> /dev/null || true
+ fi
+}
+
+# Install cleanup handler (except for kill -9)
+trap exit_cleanup EXIT
+
+function setup()
+{
+ echo "Starting test system ..." >&2
+ # Create a named pipe in a temp directory we own.
+ FIFO_DIR=$(mktemp -d fifo-XXXXXX)
+ FIFO_OUT=$(echo "$FIFO_DIR/out")
+ mkfifo "$FIFO_OUT"
+ # Open pipe as FD 3 (RW) and FD 4 (RO)
+ exec 3<> "$FIFO_OUT" 4< "$FIFO_OUT"
+ rm -rf "$FIFO_DIR"
+ # We require '-W' for our termination logic to work.
+ taler-unified-setup.sh -W "$@" \
+ > >(tee taler-unified-setup.log >&3) &
+ SETUP_PID=$!
+ # Close FD3
+ exec 3>&-
+ sed -u '/<<READY>>/ q' <&4
+ # Close FD4
+ exec 4>&-
+ echo "Test system ready" >&2
+}
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_fail() {
+ echo "$@" >&2
+ exit 1
+}
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo "SKIPPING: $1"
+ exit 77
+}
+
+function get_payto_uri() {
+ export LIBEUFIN_SANDBOX_USERNAME="$1"
+ export LIBEUFIN_SANDBOX_PASSWORD="$2"
+ export LIBEUFIN_SANDBOX_URL="http://localhost:18082"
+ echo "get_payto_uri currently not implemented"
+ exit 1
+# libeufin-cli sandbox demobank info --bank-account "$1" | jq --raw-output '.paytoUri'
+}
+
+# Stop libeufin-bank (if running)
+function stop_libeufin()
+{
+ echo -n "Stopping libeufin... "
+ if [ -f "${MY_TMP_DIR:-/}/libeufin-bank.pid" ]
+ then
+ PID=$(cat "${MY_TMP_DIR}/libeufin-bank.pid" 2> /dev/null)
+ echo "Killing libeufin-bank $PID"
+ rm "${MY_TMP_DIR}/libeufin-bank.pid"
+ kill "$PID" 2> /dev/null || true
+ wait "$PID" || true
+ fi
+ echo "DONE"
+}
+
+
+function launch_libeufin () {
+ libeufin-bank serve \
+ -c "$CONF" \
+ -L "INFO" \
+ > "${MY_TMP_DIR}/libeufin-bank-stdout.log" \
+ 2> "${MY_TMP_DIR}/libeufin-bank-stderr.log" &
+ echo $! > "${MY_TMP_DIR}/libeufin-bank.pid"
+}
diff --git a/src/auditor/taler-auditor-dbinit.c b/src/auditor/taler-auditor-dbinit.c
index 99ef96f8d..4cb46f470 100644
--- a/src/auditor/taler-auditor-dbinit.c
+++ b/src/auditor/taler-auditor-dbinit.c
@@ -21,6 +21,7 @@
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
#include "taler_auditordb_plugin.h"
@@ -69,7 +70,7 @@ run (void *cls,
{
fprintf (stderr,
"Failed to initialize database plugin.\n");
- global_ret = 1;
+ global_ret = EXIT_NOTINSTALLED;
return;
}
if (reset_db)
@@ -89,12 +90,14 @@ run (void *cls,
"Failed to restart audits\n");
}
if (GNUNET_OK !=
- plugin->create_tables (plugin->cls))
+ plugin->create_tables (plugin->cls,
+ false,
+ 0))
{
fprintf (stderr,
"Failed to initialize database.\n");
TALER_AUDITORDB_plugin_unload (plugin);
- global_ret = 1;
+ global_ret = EXIT_NOPERMISSION;
return;
}
if (gc_db)
@@ -134,22 +137,27 @@ main (int argc,
&gc_db),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue ret;
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
/* 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 */
- (void) TALER_project_data_default ();
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-auditor-dbinit",
- "INFO",
- NULL));
- if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc, argv,
- "taler-auditor-dbinit",
- "Initialize Taler auditor database",
- options,
- &run, NULL))
- return 1;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-auditor-dbinit",
+ gettext_noop ("Initialize Taler auditor database"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
return global_ret;
}
diff --git a/src/auditor/taler-auditor-exchange.c b/src/auditor/taler-auditor-exchange.c
deleted file mode 100644
index 41df335b3..000000000
--- a/src/auditor/taler-auditor-exchange.c
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014, 2015, 2018, 2019 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 taler-auditor-exchange.c
- * @brief Tool used by the auditor to add or remove the exchange's master key
- * to its database.
- * @author Christian Grothoff
- */
-#include <platform.h>
-#include "taler_exchangedb_lib.h"
-#include "taler_auditordb_lib.h"
-
-
-/**
- * URL of the exchange.
- */
-static char *exchange_url;
-
-/**
- * Master public key of the exchange.
- */
-static struct TALER_MasterPublicKeyP master_public_key;
-
-/**
- * Our configuration.
- */
-static struct GNUNET_CONFIGURATION_Handle *cfg;
-
-/**
- * Handle to access the auditor's database.
- */
-static struct TALER_AUDITORDB_Plugin *adb;
-
-/**
- * -r option given.
- */
-static int remove_flag;
-
-
-/**
- * The main function of the taler-auditor-exchange tool. This tool is used
- * to add (or remove) an exchange's master key and base URL to the auditor's
- * database.
- *
- * @param argc number of arguments from the command line
- * @param argv command line arguments
- * @return 0 ok, non-zero on error
- */
-int
-main (int argc,
- char *const *argv)
-{
- char *cfgfile = NULL;
- const struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_cfgfile (&cfgfile),
- GNUNET_GETOPT_option_help (
- "Add or remove exchange to list of audited exchanges"),
- GNUNET_GETOPT_option_mandatory
- (GNUNET_GETOPT_option_base32_auto ('m',
- "exchange-key",
- "KEY",
- "public key of the exchange (Crockford base32 encoded)",
- &master_public_key)),
- GNUNET_GETOPT_option_string ('u',
- "exchange-url",
- "URL",
- "base URL of the exchange",
- &exchange_url),
- GNUNET_GETOPT_option_flag ('r',
- "remove",
- "remove the exchange's key (default is to add)",
- &remove_flag),
- GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
- GNUNET_GETOPT_OPTION_END
- };
-
- TALER_gcrypt_init (); /* must trigger initialization manually at this point! */
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-auditor-exchange",
- "WARNING",
- NULL));
- if (GNUNET_GETOPT_run ("taler-auditor-exchange",
- options,
- argc, argv) <= 0)
- return 1;
- if (NULL == cfgfile)
- cfgfile = GNUNET_strdup (GNUNET_OS_project_data_get ()->user_config_file);
- cfg = GNUNET_CONFIGURATION_create ();
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Loading config file: %s\n",
- cfgfile);
-
- if (GNUNET_SYSERR ==
- GNUNET_CONFIGURATION_load (cfg,
- cfgfile))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Malformed configuration file `%s', exit ...\n",
- cfgfile);
- GNUNET_free_non_null (cfgfile);
- return 1;
- }
- GNUNET_free_non_null (cfgfile);
-
- if (! remove_flag)
- {
- if (NULL == exchange_url)
- {
- fprintf (stderr,
- _ ("Missing either `%s' or `%s'.\n"),
- "-u URL",
- "--remove");
- return 1;
- }
- if ( (0 == strlen (exchange_url)) ||
- ( (0 != strncasecmp ("http://",
- exchange_url,
- strlen ("http://"))) &&
- (0 != strncasecmp ("https://",
- exchange_url,
- strlen ("https://"))) ) ||
- ('/' != exchange_url[strlen (exchange_url) - 1]) )
- {
- fprintf (stderr,
- "Exchange URL must begin with `http://` or `https://` and end with `/'\n");
- return 1;
- }
- }
-
-
- if (NULL ==
- (adb = TALER_AUDITORDB_plugin_load (cfg)))
- {
- fprintf (stderr,
- "Failed to initialize auditor database plugin.\n");
- return 3;
- }
-
- /* Create required tables */
- if (GNUNET_OK !=
- adb->create_tables (adb->cls))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to create tables in auditor's database\n");
- TALER_AUDITORDB_plugin_unload (adb);
- return 3;
- }
-
- /* Update DB */
- {
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_AUDITORDB_Session *session;
-
- session = adb->get_session (adb->cls);
- if (NULL == session)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to initialize database session\n");
- TALER_AUDITORDB_plugin_unload (adb);
- return 3;
- }
-
- if (remove_flag)
- {
- qs = adb->delete_exchange (adb->cls,
- session,
- &master_public_key);
- }
- else
- {
- qs = adb->insert_exchange (adb->cls,
- session,
- &master_public_key,
- exchange_url);
- }
- if (0 > qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to update auditor database (status code: %d)\n",
- qs);
- TALER_AUDITORDB_plugin_unload (adb);
- return 3;
- }
- if (0 == qs)
- {
- GNUNET_log (
- GNUNET_ERROR_TYPE_WARNING,
- (remove_flag)
- ? "Could not remove exchange from database: entry already absent\n"
- : "Could not add exchange to database: entry already exists\n");
- TALER_AUDITORDB_plugin_unload (adb);
- return 4;
- }
- }
- TALER_AUDITORDB_plugin_unload (adb);
- return 0;
-}
-
-
-/* end of taler-auditor-exchange.c */
diff --git a/src/auditor/taler-auditor-httpd.c b/src/auditor/taler-auditor-httpd.c
index 4f7e11f84..59bd849bc 100644
--- a/src/auditor/taler-auditor-httpd.c
+++ b/src/auditor/taler-auditor-httpd.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014, 2015, 2016, 2018 Taler Systems SA
+ 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 Affero General Public License as published by the Free Software
@@ -29,8 +29,9 @@
#include <sys/resource.h>
#include "taler_mhd_lib.h"
#include "taler_auditordb_lib.h"
+#include "taler_exchangedb_lib.h"
#include "taler-auditor-httpd_deposit-confirmation.h"
-#include "taler-auditor-httpd_exchanges.h"
+#include "taler-auditor-httpd_deposit-confirmation-get.h"
#include "taler-auditor-httpd_mhd.h"
#include "taler-auditor-httpd.h"
@@ -47,7 +48,8 @@
* release version, and the format is NOT the same that semantic
* versioning uses either.
*/
-#define AUDITOR_PROTOCOL_VERSION "0:0:0"
+#define AUDITOR_PROTOCOL_VERSION "1:0:1"
+
/**
* Backlog for listen operation on unix domain sockets.
@@ -60,9 +62,9 @@
static int auditor_connection_close;
/**
- * The auditor's configuration (global)
+ * The auditor's configuration.
*/
-static struct GNUNET_CONFIGURATION_Handle *cfg;
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
/**
* Our DB plugin.
@@ -70,196 +72,40 @@ static struct GNUNET_CONFIGURATION_Handle *cfg;
struct TALER_AUDITORDB_Plugin *TAH_plugin;
/**
- * Public key of this auditor.
+ * Our DB plugin to talk to the *exchange* database.
*/
-static struct TALER_AuditorPublicKeyP auditor_pub;
+struct TALER_EXCHANGEDB_Plugin *TAH_eplugin;
/**
- * Default timeout in seconds for HTTP requests.
+ * Public key of this auditor.
*/
-static unsigned int connection_timeout = 30;
+static struct TALER_AuditorPublicKeyP auditor_pub;
/**
- * The HTTP Daemon.
+ * Exchange master public key (according to the
+ * configuration). (global)
*/
-static struct MHD_Daemon *mhd;
+struct TALER_MasterPublicKeyP TAH_master_public_key;
/**
- * Port to run the daemon on.
+ * Default timeout in seconds for HTTP requests.
*/
-static uint16_t serve_port;
+static unsigned int connection_timeout = 30;
/**
- * Path for the unix domain-socket to run the daemon on.
+ * Return value from main()
*/
-static char *serve_unixpath;
+static int global_ret;
/**
- * File mode for unix-domain socket.
+ * Port to run the daemon on.
*/
-static mode_t unixpath_mode;
+static uint16_t serve_port;
/**
* Our currency.
*/
-static char *currency;
-
-/**
- * Pipe used for signaling reloading of our key state.
- */
-static int reload_pipe[2] = { -1, -1 };
-
-
-/**
- * Handle a signal, writing relevant signal numbers to the pipe.
- *
- * @param signal_number the signal number
- */
-static void
-handle_signal (int signal_number)
-{
- char c = signal_number;
-
- (void) ! write (reload_pipe[1],
- &c,
- 1);
- /* While one might like to "handle errors" here, even logging via fprintf()
- isn't safe inside of a signal handler. So there is nothing we safely CAN
- do. OTOH, also very little that can go wrong in practice. Calling _exit()
- on errors might be a possibility, but that might do more harm than good. *///
-}
-
-
-/**
- * Call #handle_signal() to pass the received signal via
- * the control pipe.
- */
-static void
-handle_sigint (void)
-{
- handle_signal (SIGINT);
-}
-
-
-/**
- * Call #handle_signal() to pass the received signal via
- * the control pipe.
- */
-static void
-handle_sigterm (void)
-{
- handle_signal (SIGTERM);
-}
-
-
-/**
- * Call #handle_signal() to pass the received signal via
- * the control pipe.
- */
-static void
-handle_sighup (void)
-{
- handle_signal (SIGHUP);
-}
-
-
-/**
- * Call #handle_signal() to pass the received signal via
- * the control pipe.
- */
-static void
-handle_sigchld (void)
-{
- handle_signal (SIGCHLD);
-}
-
-
-/**
- * Read signals from a pipe in a loop, and reload keys from disk if
- * SIGUSR1 is received, terminate if SIGTERM/SIGINT is received, and
- * restart if SIGHUP is received.
- *
- * @return #GNUNET_SYSERR on errors,
- * #GNUNET_OK to terminate normally
- * #GNUNET_NO to restart an update version of the binary
- */
-static int
-signal_loop (void)
-{
- struct GNUNET_SIGNAL_Context *sigterm;
- struct GNUNET_SIGNAL_Context *sigint;
- struct GNUNET_SIGNAL_Context *sighup;
- struct GNUNET_SIGNAL_Context *sigchld;
- int ret;
-
- if (0 != pipe (reload_pipe))
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "pipe");
- return GNUNET_SYSERR;
- }
- sigterm = GNUNET_SIGNAL_handler_install (SIGTERM,
- &handle_sigterm);
- sigint = GNUNET_SIGNAL_handler_install (SIGINT,
- &handle_sigint);
- sighup = GNUNET_SIGNAL_handler_install (SIGHUP,
- &handle_sighup);
- sigchld = GNUNET_SIGNAL_handler_install (SIGCHLD,
- &handle_sigchld);
-
- ret = 2;
- while (2 == ret)
- {
- char c;
- ssize_t res;
-
- errno = 0;
- res = read (reload_pipe[0],
- &c,
- 1);
- if ( (res < 0) &&
- (EINTR != errno))
- {
- GNUNET_break (0);
- ret = GNUNET_SYSERR;
- break;
- }
- if (EINTR == errno)
- {
- /* ignore, do the loop again */
- continue;
- }
- switch (c)
- {
- case SIGTERM:
- case SIGINT:
- /* terminate */
- ret = GNUNET_OK;
- break;
- case SIGHUP:
- /* restart updated binary */
- ret = GNUNET_NO;
- break;
-#if HAVE_DEVELOPER
- case SIGCHLD:
- /* running in test-mode, test finished, terminate */
- ret = GNUNET_OK;
- break;
-#endif
- default:
- /* unexpected character */
- GNUNET_break (0);
- break;
- }
- }
- GNUNET_SIGNAL_handler_uninstall (sigterm);
- GNUNET_SIGNAL_handler_uninstall (sigint);
- GNUNET_SIGNAL_handler_uninstall (sighup);
- GNUNET_SIGNAL_handler_uninstall (sigchld);
- GNUNET_break (0 == close (reload_pipe[0]));
- GNUNET_break (0 == close (reload_pipe[1]));
- return ret;
-}
+char *TAH_currency;
/**
@@ -293,7 +139,7 @@ handle_mhd_completion_callback (void *cls,
/**
- * Handle a "/version" request.
+ * Handle a "/config" request.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
@@ -302,12 +148,12 @@ handle_mhd_completion_callback (void *cls,
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
-static int
-handle_version (struct TAH_RequestHandler *rh,
- struct MHD_Connection *connection,
- void **connection_cls,
- const char *upload_data,
- size_t *upload_data_size)
+static MHD_RESULT
+handle_config (struct TAH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
{
static json_t *ver; /* we build the response only once, keep around for next query! */
@@ -317,11 +163,19 @@ handle_version (struct TAH_RequestHandler *rh,
(void) connection_cls;
if (NULL == ver)
{
- ver = json_pack ("{s:s, s:s, s:o}",
- "version", AUDITOR_PROTOCOL_VERSION,
- "currency", currency,
- "auditor_public_key", GNUNET_JSON_from_data_auto (
- &auditor_pub));
+ ver = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("name",
+ "taler-auditor"),
+ GNUNET_JSON_pack_string ("version",
+ AUDITOR_PROTOCOL_VERSION),
+ GNUNET_JSON_pack_string ("implementation",
+ "urn:net:taler:specs:taler-auditor:c-reference"),
+ GNUNET_JSON_pack_string ("currency",
+ TAH_currency),
+ GNUNET_JSON_pack_data_auto ("auditor_public_key",
+ &auditor_pub),
+ GNUNET_JSON_pack_data_auto ("exchange_master_public_key",
+ &TAH_master_public_key));
}
if (NULL == ver)
{
@@ -347,7 +201,7 @@ handle_version (struct TAH_RequestHandler *rh,
* @param con_cls closure for request (a `struct Buffer *`)
* @return MHD result code
*/
-static int
+static MHD_RESULT
handle_mhd_request (void *cls,
struct MHD_Connection *connection,
const char *url,
@@ -363,12 +217,15 @@ handle_mhd_request (void *cls,
{ "/deposit-confirmation", MHD_HTTP_METHOD_PUT, "application/json",
NULL, 0,
&TAH_DEPOSIT_CONFIRMATION_handler, MHD_HTTP_OK },
- { "/exchanges", MHD_HTTP_METHOD_GET, "application/json",
+ { "/deposit-confirmation", MHD_HTTP_METHOD_GET, "application/json",
NULL, 0,
- &TAH_EXCHANGES_handler, MHD_HTTP_OK },
- { "/version", MHD_HTTP_METHOD_GET, "application/json",
+ &TAH_DEPOSIT_CONFIRMATION_handler_get, MHD_HTTP_OK },
+// { "/deposit-confirmation", MHD_HTTP_METHOD_DELETE, "application/json",
+// NULL, 0,
+// &TAH_DEPOSIT_CONFIRMATION_delete, MHD_HTTP_OK },
+ { "/config", MHD_HTTP_METHOD_GET, "application/json",
NULL, 0,
- &handle_version, MHD_HTTP_OK },
+ &handle_config, MHD_HTTP_OK },
/* Landing page, for now tells humans to go away
* (NOTE: ideally, the reverse proxy will respond with a nicer page) */
{ "/", MHD_HTTP_METHOD_GET, "text/plain",
@@ -427,31 +284,70 @@ handle_mhd_request (void *cls,
*
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
auditor_serve_process_config (void)
{
if (NULL ==
(TAH_plugin = TALER_AUDITORDB_plugin_load (cfg)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to initialize DB subsystem\n");
+ "Failed to initialize DB subsystem to interact with auditor database\n");
return GNUNET_SYSERR;
}
- if (GNUNET_OK !=
- TALER_MHD_parse_config (cfg,
- "auditor",
- &serve_port,
- &serve_unixpath,
- &unixpath_mode))
+ if (NULL ==
+ (TAH_eplugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem to query exchange database\n");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_SYSERR ==
+ TAH_eplugin->preflight (TAH_eplugin->cls))
{
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem to query exchange database\n");
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
TALER_config_get_currency (cfg,
- &currency))
+ &TAH_currency))
{
return GNUNET_SYSERR;
}
+
+ {
+ char *master_public_key_str;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "MASTER_PUBLIC_KEY",
+ &master_public_key_str))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "MASTER_PUBLIC_KEY");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_public_key_from_string (
+ master_public_key_str,
+ strlen (master_public_key_str),
+ &TAH_master_public_key.eddsa_pub))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "MASTER_PUBLIC_KEY",
+ "invalid base32 encoding for a master public key");
+ GNUNET_free (master_public_key_str);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Launching auditor for exchange `%s'...\n",
+ master_public_key_str);
+ GNUNET_free (master_public_key_str);
+ }
+
{
char *pub;
@@ -479,7 +375,7 @@ auditor_serve_process_config (void)
{
/* Fall back to trying to read private key */
char *auditor_key_file;
- struct GNUNET_CRYPTO_EddsaPrivateKey *eddsa_priv;
+ struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (cfg,
@@ -495,8 +391,10 @@ auditor_serve_process_config (void)
"AUDITOR_PRIV_FILE");
return GNUNET_SYSERR;
}
- eddsa_priv = GNUNET_CRYPTO_eddsa_key_create_from_file (auditor_key_file);
- if (NULL == eddsa_priv)
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_key_from_file (auditor_key_file,
+ GNUNET_NO,
+ &eddsa_priv))
{
/* Both failed, complain! */
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
@@ -509,15 +407,124 @@ auditor_serve_process_config (void)
return 1;
}
GNUNET_free (auditor_key_file);
- GNUNET_CRYPTO_eddsa_key_get_public (eddsa_priv,
+ GNUNET_CRYPTO_eddsa_key_get_public (&eddsa_priv,
&auditor_pub.eddsa_pub);
- GNUNET_free (eddsa_priv);
}
return GNUNET_OK;
}
/**
+ * Function run on shutdown.
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ struct MHD_Daemon *mhd;
+ (void) cls;
+
+ mhd = TALER_MHD_daemon_stop ();
+ TEAH_DEPOSIT_CONFIRMATION_done ();
+ if (NULL != mhd)
+ MHD_stop_daemon (mhd);
+ if (NULL != TAH_plugin)
+ {
+ TALER_AUDITORDB_plugin_unload (TAH_plugin);
+ TAH_plugin = NULL;
+ }
+ if (NULL != TAH_eplugin)
+ {
+ TALER_EXCHANGEDB_plugin_unload (TAH_eplugin);
+ TAH_eplugin = NULL;
+ }
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be
+ * NULL!)
+ * @param config configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *config)
+{
+ enum TALER_MHD_GlobalOptions go;
+ int fh;
+
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+ go = TALER_MHD_GO_NONE;
+ if (auditor_connection_close)
+ go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
+ TALER_MHD_setup (go);
+ cfg = config;
+
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ if (GNUNET_OK !=
+ auditor_serve_process_config ())
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ TEAH_DEPOSIT_CONFIRMATION_init ();
+ fh = TALER_MHD_bind (cfg,
+ "auditor",
+ &serve_port);
+ if ( (0 == serve_port) &&
+ (-1 == fh) )
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ {
+ struct MHD_Daemon *mhd;
+
+ mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME
+ | MHD_USE_PIPE_FOR_SHUTDOWN
+ | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
+ | MHD_USE_TCP_FASTOPEN,
+ (-1 == fh) ? serve_port : 0,
+ NULL, NULL,
+ &handle_mhd_request, NULL,
+ MHD_OPTION_LISTEN_BACKLOG_SIZE,
+ (unsigned int) 1024,
+ MHD_OPTION_LISTEN_SOCKET,
+ fh,
+ MHD_OPTION_EXTERNAL_LOGGER,
+ &TALER_MHD_handle_logs,
+ NULL,
+ MHD_OPTION_NOTIFY_COMPLETED,
+ &handle_mhd_completion_callback,
+ NULL,
+ MHD_OPTION_CONNECTION_TIMEOUT,
+ connection_timeout,
+ MHD_OPTION_END);
+ if (NULL == mhd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to launch HTTP service. Is the port in use?\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ global_ret = EXIT_SUCCESS;
+ TALER_MHD_daemon_start (mhd);
+ }
+}
+
+
+/**
* The main function of the taler-auditor-httpd server ("the auditor").
*
* @param argc number of arguments from the command line
@@ -528,15 +535,11 @@ int
main (int argc,
char *const *argv)
{
- char *cfgfile = NULL;
- char *loglev = NULL;
- char *logfile = NULL;
const struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_option_flag ('C',
"connection-close",
"force HTTP connections to be closed after each request",
&auditor_connection_close),
- GNUNET_GETOPT_option_cfgfile (&cfgfile),
GNUNET_GETOPT_option_uint ('t',
"timeout",
"SECONDS",
@@ -544,192 +547,22 @@ main (int argc,
&connection_timeout),
GNUNET_GETOPT_option_help (
"HTTP server providing a RESTful API to access a Taler auditor"),
- GNUNET_GETOPT_option_loglevel (&loglev),
- GNUNET_GETOPT_option_logfile (&logfile),
GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
GNUNET_GETOPT_OPTION_END
};
int ret;
- const char *listen_pid;
- const char *listen_fds;
- int fh = -1;
- enum TALER_MHD_GlobalOptions go;
-
- if (0 >=
- GNUNET_GETOPT_run ("taler-auditor-httpd",
- options,
- argc, argv))
- return 1;
- go = TALER_MHD_GO_NONE;
- if (auditor_connection_close)
- go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
- TALER_MHD_setup (go);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-auditor-httpd",
- (NULL == loglev) ? "INFO" : loglev,
- logfile));
- if (NULL == cfgfile)
- cfgfile = GNUNET_strdup (GNUNET_OS_project_data_get ()->user_config_file);
- cfg = GNUNET_CONFIGURATION_create ();
- if (GNUNET_SYSERR ==
- GNUNET_CONFIGURATION_load (cfg,
- cfgfile))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Malformed configuration file `%s', exiting ...\n",
- cfgfile);
- GNUNET_free_non_null (cfgfile);
- return 1;
- }
- GNUNET_free_non_null (cfgfile);
-
- if (GNUNET_OK !=
- auditor_serve_process_config ())
- return 1;
- TEAH_DEPOSIT_CONFIRMATION_init ();
- /* check for systemd-style FD passing */
- listen_pid = getenv ("LISTEN_PID");
- listen_fds = getenv ("LISTEN_FDS");
- if ( (NULL != listen_pid) &&
- (NULL != listen_fds) &&
- (getpid () == strtol (listen_pid,
- NULL,
- 10)) &&
- (1 == strtoul (listen_fds,
- NULL,
- 10)) )
- {
- int flags;
-
- fh = 3;
- flags = fcntl (fh,
- F_GETFD);
- if ( (-1 == flags) &&
- (EBADF == errno) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Bad listen socket passed, ignored\n");
- fh = -1;
- }
- flags |= FD_CLOEXEC;
- if ( (-1 != fh) &&
- (0 != fcntl (fh,
- F_SETFD,
- flags)) )
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "fcntl");
- }
-
- /* consider unix path */
- if ( (-1 == fh) &&
- (NULL != serve_unixpath) )
- {
- fh = TALER_MHD_open_unix_path (serve_unixpath,
- unixpath_mode);
- if (-1 == fh)
- {
- TEAH_DEPOSIT_CONFIRMATION_done ();
- return 1;
- }
- }
-
- mhd = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_PIPE_FOR_SHUTDOWN
- | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
- | MHD_USE_INTERNAL_POLLING_THREAD
- | MHD_USE_TCP_FASTOPEN,
- (-1 == fh) ? serve_port : 0,
- NULL, NULL,
- &handle_mhd_request, NULL,
- MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) 32,
- MHD_OPTION_LISTEN_BACKLOG_SIZE, (unsigned int) 1024,
- MHD_OPTION_LISTEN_SOCKET, fh,
- MHD_OPTION_EXTERNAL_LOGGER, &TALER_MHD_handle_logs,
- NULL,
- MHD_OPTION_NOTIFY_COMPLETED,
- &handle_mhd_completion_callback, NULL,
- MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout,
- MHD_OPTION_END);
- if (NULL == mhd)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to start HTTP server.\n");
- TEAH_DEPOSIT_CONFIRMATION_done ();
- return 1;
- }
- /* normal behavior */
- ret = signal_loop ();
- switch (ret)
- {
- case GNUNET_OK:
- case GNUNET_SYSERR:
- MHD_stop_daemon (mhd);
- break;
- case GNUNET_NO:
- {
- MHD_socket sock = MHD_quiesce_daemon (mhd);
- pid_t chld;
- int flags;
-
- /* Set flags to make 'sock' inherited by child */
- flags = fcntl (sock, F_GETFD);
- GNUNET_assert (-1 != flags);
- flags &= ~FD_CLOEXEC;
- GNUNET_assert (-1 != fcntl (sock, F_SETFD, flags));
- chld = fork ();
- if (-1 == chld)
- {
- /* fork() failed, continue clean up, unhappily */
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "fork");
- }
- if (0 == chld)
- {
- char pids[12];
-
- /* exec another taler-auditor-httpd, passing on the listen socket;
- as in systemd it is expected to be on FD #3 */
- if (3 != dup2 (sock, 3))
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "dup2");
- _exit (1);
- }
- /* Tell the child that it is the desired recipient for FD #3 */
- GNUNET_snprintf (pids,
- sizeof (pids),
- "%u",
- getpid ());
- setenv ("LISTEN_PID", pids, 1);
- setenv ("LISTEN_FDS", "1", 1);
- /* Finally, exec the (presumably) more recent auditor binary */
- execvp ("taler-auditor-httpd",
- argv);
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "execvp");
- _exit (1);
- }
- /* we're the original process, handle remaining contextions
- before exiting; as the listen socket is no longer used,
- close it here */
- GNUNET_break (0 == close (sock));
- while (0 != MHD_get_daemon_info (mhd,
- MHD_DAEMON_INFO_CURRENT_CONNECTIONS)->
- num_connections)
- sleep (1);
- /* Now we're really done, practice clean shutdown */
- MHD_stop_daemon (mhd);
- }
- break;
- default:
- GNUNET_break (0);
- MHD_stop_daemon (mhd);
- break;
- }
- TALER_AUDITORDB_plugin_unload (TAH_plugin);
- TAH_plugin = NULL;
- TEAH_DEPOSIT_CONFIRMATION_done ();
- return (GNUNET_SYSERR == ret) ? 1 : 0;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (argc, argv,
+ "taler-auditor-httpd",
+ "Taler auditor HTTP service",
+ options,
+ &run, NULL);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
}
diff --git a/src/auditor/taler-auditor-httpd.h b/src/auditor/taler-auditor-httpd.h
index 3e7e79a10..853722f09 100644
--- a/src/auditor/taler-auditor-httpd.h
+++ b/src/auditor/taler-auditor-httpd.h
@@ -25,6 +25,7 @@
#include <microhttpd.h>
#include "taler_auditordb_plugin.h"
+#include "taler_exchangedb_plugin.h"
/**
@@ -32,6 +33,21 @@
*/
extern struct TALER_AUDITORDB_Plugin *TAH_plugin;
+/**
+ * Our DB plugin to talk to the *exchange* database.
+ */
+extern struct TALER_EXCHANGEDB_Plugin *TAH_eplugin;
+
+/**
+ * Exchange master public key (according to the
+ * configuration). (global)
+ */
+extern struct TALER_MasterPublicKeyP TAH_master_public_key;
+
+/**
+ * Our currency.
+ */
+extern char *TAH_currency;
/**
* @brief Struct describing an URL and the handler for it.
@@ -75,16 +91,16 @@ struct TAH_RequestHandler
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
- int (*handler)(struct TAH_RequestHandler *rh,
- struct MHD_Connection *connection,
- void **connection_cls,
- const char *upload_data,
- size_t *upload_data_size);
+ MHD_RESULT (*handler)(struct TAH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
/**
* Default response code.
*/
- int response_code;
+ unsigned int response_code;
};
diff --git a/src/auditor/taler-auditor-httpd_deposit-confirmation-get.c b/src/auditor/taler-auditor-httpd_deposit-confirmation-get.c
new file mode 100644
index 000000000..265d625c4
--- /dev/null
+++ b/src/auditor/taler-auditor-httpd_deposit-confirmation-get.c
@@ -0,0 +1,166 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2024 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-auditor-httpd_deposit-confirmation-get.c
+ * @brief Handle /deposit-confirmation requests; return list of deposit confirmations from merchant
+ * that were not received from the exchange, by auditor.
+ * @author Nic Eigel
+ */
+
+#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-auditor-httpd.h"
+#include "taler-auditor-httpd_deposit-confirmation-get.h"
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Information about a signing key of the exchange. Signing keys are used
+ * to sign exchange messages other than coins, i.e. to confirm that a
+ * deposit was successful or that a refresh was accepted.
+ */
+struct ExchangeSigningKeyDataP
+{
+
+ /**
+ * When does this signing key begin to be valid?
+ */
+ struct GNUNET_TIME_TimestampNBO start;
+
+ /**
+ * When does this signing key expire? Note: This is currently when
+ * the Exchange will definitively stop using it. Signatures made with
+ * the key remain valid until @e end. When checking validity periods,
+ * clients should allow for some overlap between keys and tolerate
+ * the use of either key during the overlap time (due to the
+ * possibility of clock skew).
+ */
+ struct GNUNET_TIME_TimestampNBO expire;
+
+ /**
+ * When do signatures with this signing key become invalid? After
+ * this point, these signatures cannot be used in (legal) disputes
+ * anymore, as the Exchange is then allowed to destroy its side of the
+ * evidence. @e end is expected to be significantly larger than @e
+ * expire (by a year or more).
+ */
+ struct GNUNET_TIME_TimestampNBO end;
+
+ /**
+ * The public online signing key that the exchange will use
+ * between @e start and @e expire.
+ */
+ struct TALER_ExchangePublicKeyP signkey_pub;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+/**
+ * Add deposit confirmation to the list.
+ *
+ * @param[in,out] cls a `json_t *` array to extend
+ * @param serial_id location of the @a dc in the database
+ * @param dc struct of deposit confirmation
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop iterating
+ */
+static enum GNUNET_GenericReturnValue
+add_deposit_confirmation (void *cls,
+ uint64_t serial_id,
+ const struct TALER_AUDITORDB_DepositConfirmation *dc)
+{
+ json_t *list = cls;
+ json_t *obj;
+
+ obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("dc",
+ dc));
+ GNUNET_break (0 ==
+ json_array_append_new (list,
+ obj));
+ return GNUNET_OK;
+}
+
+
+/**
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+MHD_RESULT
+TAH_DEPOSIT_CONFIRMATION_handler_get (struct TAH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ json_t *ja;
+ enum GNUNET_DB_QueryStatus qs;
+
+ (void) rh;
+ (void) connection_cls;
+ (void) upload_data;
+ (void) upload_data_size;
+ if (GNUNET_SYSERR ==
+ TAH_plugin->preflight (TAH_plugin->cls))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SETUP_FAILED,
+ NULL);
+ }
+ ja = json_array ();
+ GNUNET_break (NULL != ja);
+ // TODO correct below
+ qs = TAH_plugin->get_deposit_confirmations (
+ TAH_plugin->cls,
+ 0, /* FIXME: get from query parameters! */
+ false, /* FIXME: get from query parameters! */
+ &add_deposit_confirmation,
+ ja);
+
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ json_decref (ja);
+ TALER_LOG_WARNING (
+ "Failed to handle GET /deposit-confirmation in database\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "deposit-confirmation");
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("deposit-confirmation",
+ ja));
+}
+
+
+/* end of taler-auditor-httpd_deposit-confirmation-get.c */
diff --git a/src/auditor/taler-auditor-httpd_deposit-confirmation-get.h b/src/auditor/taler-auditor-httpd_deposit-confirmation-get.h
new file mode 100644
index 000000000..f1f522787
--- /dev/null
+++ b/src/auditor/taler-auditor-httpd_deposit-confirmation-get.h
@@ -0,0 +1,70 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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-auditor-httpd_deposit-confirmation-get.h
+ * @brief Handle GET /deposit-confirmation requests
+ * @author Nic Eigel
+ */
+#ifndef TALER_AUDITOR_HTTPD_DEPOSIT_CONFIRMATION_GET_H
+#define TALER_AUDITOR_HTTPD_DEPOSIT_CONFIRMATION_GET_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-auditor-httpd.h"
+
+/**
+ * Initialize subsystem.
+ */
+void
+TEAH_DEPOSIT_CONFIRMATION_GET_init (void);
+
+/**
+ * Shut down subsystem.
+ */
+void
+TEAH_DEPOSIT_CONFIRMATION_GET_done (void);
+
+/**
+ * Handle a "/deposit-confirmation" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+MHD_RESULT
+TAH_DEPOSIT_CONFIRMATION_handler_get (struct TAH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+/**
+ * Handle a DELETE "/deposit-confirmation/$dc" request.
+ *
+ * @param rc request details about the request to handle
+ * @param args argument with the dc primary key
+ * @return MHD result code
+ */
+/*MHD_RESULT
+TAH_DEPOSIT_CONFIRMATION_delete (
+ struct TEH_RequestContext *rc,
+ const char *const args[1]);*/
+
+
+#endif
diff --git a/src/auditor/taler-auditor-httpd_deposit-confirmation.c b/src/auditor/taler-auditor-httpd_deposit-confirmation.c
index 45be222af..8b449bf47 100644
--- a/src/auditor/taler-auditor-httpd_deposit-confirmation.c
+++ b/src/auditor/taler-auditor-httpd_deposit-confirmation.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 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
@@ -31,11 +31,54 @@
#include "taler-auditor-httpd.h"
#include "taler-auditor-httpd_deposit-confirmation.h"
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Information about a signing key of the exchange. Signing keys are used
+ * to sign exchange messages other than coins, i.e. to confirm that a
+ * deposit was successful or that a refresh was accepted.
+ */
+struct ExchangeSigningKeyDataP
+{
+
+ /**
+ * When does this signing key begin to be valid?
+ */
+ struct GNUNET_TIME_TimestampNBO start;
+
+ /**
+ * When does this signing key expire? Note: This is currently when
+ * the Exchange will definitively stop using it. Signatures made with
+ * the key remain valid until @e end. When checking validity periods,
+ * clients should allow for some overlap between keys and tolerate
+ * the use of either key during the overlap time (due to the
+ * possibility of clock skew).
+ */
+ struct GNUNET_TIME_TimestampNBO expire;
+
+ /**
+ * When do signatures with this signing key become invalid? After
+ * this point, these signatures cannot be used in (legal) disputes
+ * anymore, as the Exchange is then allowed to destroy its side of the
+ * evidence. @e end is expected to be significantly larger than @e
+ * expire (by a year or more).
+ */
+ struct GNUNET_TIME_TimestampNBO end;
+
+ /**
+ * The public online signing key that the exchange will use
+ * between @e start and @e expire.
+ */
+ struct TALER_ExchangePublicKeyP signkey_pub;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
/**
* Cache of already verified exchange signing keys. Maps the hash of the
* `struct TALER_ExchangeSigningKeyValidityPS` to the (static) string
- * "verified". Access to this map is guarded by the #lock.
+ * "verified" or "revoked". Access to this map is guarded by the #lock.
*/
static struct GNUNET_CONTAINER_MultiHashMap *cache;
@@ -56,76 +99,76 @@ static pthread_mutex_t lock;
* @param es information about the exchange's signing key
* @return MHD result code
*/
-static int
+static MHD_RESULT
verify_and_execute_deposit_confirmation (
struct MHD_Connection *connection,
const struct TALER_AUDITORDB_DepositConfirmation *dc,
const struct TALER_AUDITORDB_ExchangeSigningKey *es)
{
- struct TALER_AUDITORDB_Session *session;
enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute now;
struct GNUNET_HashCode h;
- int cached;
- struct TALER_ExchangeSigningKeyValidityPS skv = {
- .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY),
- .purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS)),
- .master_public_key = es->master_public_key,
- .start = GNUNET_TIME_absolute_hton (es->ep_start),
- .expire = GNUNET_TIME_absolute_hton (es->ep_expire),
- .end = GNUNET_TIME_absolute_hton (es->ep_end),
+ const char *cached;
+ struct ExchangeSigningKeyDataP skv = {
+ .start = GNUNET_TIME_timestamp_hton (es->ep_start),
+ .expire = GNUNET_TIME_timestamp_hton (es->ep_expire),
+ .end = GNUNET_TIME_timestamp_hton (es->ep_end),
.signkey_pub = es->exchange_pub
};
+ const struct TALER_CoinSpendSignatureP *coin_sigps[
+ GNUNET_NZL (dc->num_coins)];
- now = GNUNET_TIME_absolute_get ();
- if ( (es->ep_start.abs_value_us > now.abs_value_us) ||
- (es->ep_expire.abs_value_us < now.abs_value_us) )
+ for (unsigned int i = 0; i < dc->num_coins; i++)
+ coin_sigps[i] = &dc->coin_sigs[i];
+
+ if (GNUNET_TIME_absolute_is_future (es->ep_start.abs_time) ||
+ GNUNET_TIME_absolute_is_past (es->ep_expire.abs_time))
{
/* Signing key expired */
TALER_LOG_WARNING ("Expired exchange signing key\n");
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_FORBIDDEN,
- TALER_EC_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID,
- "master_sig (expired)");
+ TALER_EC_AUDITOR_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID,
+ "master signature expired");
}
/* check our cache */
GNUNET_CRYPTO_hash (&skv,
- sizeof (skv),
+ sizeof(skv),
&h);
GNUNET_assert (0 == pthread_mutex_lock (&lock));
- cached = GNUNET_CONTAINER_multihashmap_contains (cache,
- &h);
+ cached = GNUNET_CONTAINER_multihashmap_get (cache,
+ &h);
GNUNET_assert (0 == pthread_mutex_unlock (&lock));
-
- session = TAH_plugin->get_session (TAH_plugin->cls);
- if (NULL == session)
+ if (GNUNET_SYSERR ==
+ TAH_plugin->preflight (TAH_plugin->cls))
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_DB_SETUP_FAILED,
- "failed to establish session with database");
+ TALER_EC_GENERIC_DB_SETUP_FAILED,
+ NULL);
}
- if (! cached)
+ if (NULL == cached)
{
/* Not in cache, need to verify the signature, persist it, and possibly cache it */
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY,
- &skv.purpose,
- &es->master_sig.eddsa_signature,
- &es->master_public_key.eddsa_pub))
+ TALER_exchange_offline_signkey_validity_verify (
+ &es->exchange_pub,
+ es->ep_start,
+ es->ep_expire,
+ es->ep_end,
+ &TAH_master_public_key,
+ &es->master_sig))
{
TALER_LOG_WARNING ("Invalid signature on exchange signing key\n");
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_FORBIDDEN,
- TALER_EC_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID,
- "master_sig");
+ TALER_EC_AUDITOR_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID,
+ "master signature invalid");
}
/* execute transaction */
qs = TAH_plugin->insert_exchange_signkey (TAH_plugin->cls,
- session,
es);
if (0 > qs)
{
@@ -133,54 +176,82 @@ verify_and_execute_deposit_confirmation (
TALER_LOG_WARNING ("Failed to store exchange signing key in database\n");
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_AUDITOR_EXCHANGE_STORE_DB_ERROR,
- "failed to persist exchange signing key");
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "exchange signing key");
}
-
- /* Cache it, due to concurreny it might already be in the cache,
- so we do not cache it twice but also don't insist on the 'put' to
- succeed. */
- GNUNET_assert (0 == pthread_mutex_lock (&lock));
- (void) GNUNET_CONTAINER_multihashmap_put (cache,
- &h,
- "verified",
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
- GNUNET_assert (0 == pthread_mutex_unlock (&lock));
+ cached = "verified";
}
- /* check deposit confirmation signature */
+ if (0 == strcmp (cached,
+ "verified"))
{
- struct TALER_DepositConfirmationPS dcs = {
- .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT),
- .purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)),
- .h_contract_terms = dc->h_contract_terms,
- .h_wire = dc->h_wire,
- .timestamp = GNUNET_TIME_absolute_hton (dc->timestamp),
- .refund_deadline = GNUNET_TIME_absolute_hton (dc->refund_deadline),
- .coin_pub = dc->coin_pub,
- .merchant = dc->merchant
- };
-
- TALER_amount_hton (&dcs.amount_without_fee,
- &dc->amount_without_fee);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT,
- &dcs.purpose,
- &dc->exchange_sig.eddsa_signature,
- &dc->exchange_pub.eddsa_pub))
+ struct TALER_MasterSignatureP master_sig;
+
+ /* check for revocation */
+ qs = TAH_eplugin->lookup_signkey_revocation (TAH_eplugin->cls,
+ &es->exchange_pub,
+ &master_sig);
+ if (0 > qs)
{
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
TALER_LOG_WARNING (
- "Invalid signature on /deposit-confirmation request\n");
+ "Failed to check for signing key revocation in database\n");
return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID,
- "exchange_sig");
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "exchange signing key revocation");
}
+ if (0 < qs)
+ cached = "revoked";
+ }
+
+ /* Cache it, due to concurreny it might already be in the cache,
+ so we do not cache it twice but also don't insist on the 'put' to
+ succeed. */
+ GNUNET_assert (0 == pthread_mutex_lock (&lock));
+ (void) GNUNET_CONTAINER_multihashmap_put (cache,
+ &h,
+ (void *) cached,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
+ GNUNET_assert (0 == pthread_mutex_unlock (&lock));
+
+ if (0 == strcmp (cached,
+ "revoked"))
+ {
+ TALER_LOG_WARNING (
+ "Invalid signature on /deposit-confirmation request: key was revoked\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_GONE,
+ TALER_EC_AUDITOR_EXCHANGE_SIGNING_KEY_REVOKED,
+ "exchange signing key was revoked");
+ }
+
+ /* check deposit confirmation signature */
+ if (GNUNET_OK !=
+ TALER_exchange_online_deposit_confirmation_verify (
+ &dc->h_contract_terms,
+ &dc->h_wire,
+ &dc->h_policy,
+ dc->exchange_timestamp,
+ dc->wire_deadline,
+ dc->refund_deadline,
+ &dc->total_without_fee,
+ dc->num_coins,
+ coin_sigps,
+ &dc->merchant,
+ &dc->exchange_pub,
+ &dc->exchange_sig))
+ {
+ TALER_LOG_WARNING (
+ "Invalid signature on /deposit-confirmation request\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_AUDITOR_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID,
+ "exchange signature invalid");
}
/* execute transaction */
qs = TAH_plugin->insert_deposit_confirmation (TAH_plugin->cls,
- session,
dc);
if (0 > qs)
{
@@ -188,64 +259,77 @@ verify_and_execute_deposit_confirmation (
TALER_LOG_WARNING ("Failed to store /deposit-confirmation in database\n");
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_DEPOSIT_CONFIRMATION_STORE_DB_ERROR,
- "failed to persist deposit-confirmation data");
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "deposit confirmation");
}
- return TALER_MHD_reply_json_pack (connection,
+ return TALER_MHD_REPLY_JSON_PACK (connection,
MHD_HTTP_OK,
- "{s:s}",
- "status", "DEPOSIT_CONFIRMATION_OK");
+ GNUNET_JSON_pack_string ("status",
+ "DEPOSIT_CONFIRMATION_OK"));
}
-/**
- * Handle a "/deposit-confirmation" request. Parses the JSON, and, if
- * successful, passes the JSON data to #verify_and_execute_deposit_confirmation()
- * to further check the details of the operation specified. If
- * everything checks out, this will ultimately lead to the "/deposit-confirmation"
- * being stored in the database.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
- * @return MHD result code
- */
-int
-TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh,
- struct MHD_Connection *connection,
- void **connection_cls,
- const char *upload_data,
- size_t *upload_data_size)
+MHD_RESULT
+TAH_DEPOSIT_CONFIRMATION_handler (
+ struct TAH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ 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;
+ const json_t *jcoin_sigs;
+ const json_t *jcoin_pubs;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &dc.h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("h_wire", &dc.h_wire),
- GNUNET_JSON_spec_absolute_time ("timestamp", &dc.timestamp),
- GNUNET_JSON_spec_absolute_time ("refund_deadline", &dc.refund_deadline),
- TALER_JSON_spec_amount ("amount_without_fee", &dc.amount_without_fee),
- GNUNET_JSON_spec_fixed_auto ("coin_pub", &dc.coin_pub),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub", &dc.merchant),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig", &dc.exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub", &dc.exchange_pub),
- GNUNET_JSON_spec_fixed_auto ("master_pub", &es.master_public_key),
- GNUNET_JSON_spec_absolute_time ("ep_start", &es.ep_start),
- GNUNET_JSON_spec_absolute_time ("ep_expire", &es.ep_expire),
- GNUNET_JSON_spec_absolute_time ("ep_end", &es.ep_end),
- GNUNET_JSON_spec_fixed_auto ("master_sig", &es.master_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &dc.h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("h_policy",
+ &dc.h_policy),
+ GNUNET_JSON_spec_fixed_auto ("h_wire",
+ &dc.h_wire),
+ GNUNET_JSON_spec_timestamp ("exchange_timestamp",
+ &dc.exchange_timestamp),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("refund_deadline",
+ &dc.refund_deadline),
+ NULL),
+ GNUNET_JSON_spec_timestamp ("wire_deadline",
+ &dc.wire_deadline),
+ TALER_JSON_spec_amount ("total_without_fee",
+ TAH_currency,
+ &dc.total_without_fee),
+ GNUNET_JSON_spec_array_const ("coin_pubs",
+ &jcoin_pubs),
+ GNUNET_JSON_spec_array_const ("coin_sigs",
+ &jcoin_sigs),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &dc.merchant),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &dc.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &dc.exchange_pub),
+ GNUNET_JSON_spec_timestamp ("ep_start",
+ &es.ep_start),
+ GNUNET_JSON_spec_timestamp ("ep_expire",
+ &es.ep_expire),
+ GNUNET_JSON_spec_timestamp ("ep_end",
+ &es.ep_end),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &es.master_sig),
GNUNET_JSON_spec_end ()
};
+ unsigned int num_coins;
+ json_t *json;
(void) rh;
(void) connection_cls;
(void) upload_data;
(void) upload_data_size;
{
- json_t *json;
- int res;
+ enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_post_json (connection,
connection_cls,
@@ -254,36 +338,99 @@ TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh,
&json);
if (GNUNET_SYSERR == res)
return MHD_NO;
- if ( (GNUNET_NO == res) ||
- (NULL == json) )
+ if ((GNUNET_NO == res) ||
+ (NULL == json))
return MHD_YES;
res = TALER_MHD_parse_json_data (connection,
json,
spec);
- json_decref (json);
if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
+ {
+ json_decref (json);
+ return MHD_NO; /* hard failure */
+ }
if (GNUNET_NO == res)
- return MHD_YES; /* failure */
+ {
+ json_decref (json);
+ return MHD_YES; /* failure */
+ }
}
-
- es.exchange_pub = dc.exchange_pub; /* used twice! */
- dc.master_public_key = es.master_public_key;
+ num_coins = json_array_size (jcoin_sigs);
+ if (num_coins != json_array_size (jcoin_pubs))
+ {
+ GNUNET_break_op (0);
+ json_decref (json);
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "coin_pubs.length != coin_sigs.length");
+ }
+ if (0 == num_coins)
{
- int res;
+ GNUNET_break_op (0);
+ json_decref (json);
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "coin_pubs array is empty");
+ }
+ {
+ struct TALER_CoinSpendPublicKeyP coin_pubs[num_coins];
+ struct TALER_CoinSpendSignatureP coin_sigs[num_coins];
+ MHD_RESULT res;
+
+ for (unsigned int i = 0; i < num_coins; i++)
+ {
+ json_t *jpub = json_array_get (jcoin_pubs,
+ i);
+ json_t *jsig = json_array_get (jcoin_sigs,
+ i);
+ const char *ps = json_string_value (jpub);
+ const char *ss = json_string_value (jsig);
+ if ((NULL == ps) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (ps,
+ strlen (ps),
+ &coin_pubs[i],
+ sizeof(coin_pubs[i]))))
+ {
+ GNUNET_break_op (0);
+ json_decref (json);
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "coin_pub[] malformed");
+ }
+ if ((NULL == ss) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (ss,
+ strlen (ss),
+ &coin_sigs[i],
+ sizeof(coin_sigs[i]))))
+ {
+ GNUNET_break_op (0);
+ json_decref (json);
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "coin_sig[] malformed");
+ }
+ }
+ dc.num_coins = num_coins;
+ dc.coin_pubs = coin_pubs;
+ dc.coin_sigs = coin_sigs;
+ es.exchange_pub = dc.exchange_pub; /* used twice! */
res = verify_and_execute_deposit_confirmation (connection,
&dc,
&es);
GNUNET_JSON_parse_free (spec);
+ json_decref (json);
return res;
}
}
-/**
- * Initialize subsystem.
- */
void
TEAH_DEPOSIT_CONFIRMATION_init (void)
{
@@ -293,16 +440,13 @@ TEAH_DEPOSIT_CONFIRMATION_init (void)
}
-/**
- * Shut down subsystem.
- */
void
TEAH_DEPOSIT_CONFIRMATION_done (void)
{
- GNUNET_CONTAINER_multihashmap_destroy (cache);
- cache = NULL;
- GNUNET_assert (0 == pthread_mutex_destroy (&lock));
+ if (NULL != cache)
+ {
+ GNUNET_CONTAINER_multihashmap_destroy (cache);
+ cache = NULL;
+ GNUNET_assert (0 == pthread_mutex_destroy (&lock));
+ }
}
-
-
-/* end of taler-auditor-httpd_deposit-confirmation.c */
diff --git a/src/auditor/taler-auditor-httpd_deposit-confirmation.h b/src/auditor/taler-auditor-httpd_deposit-confirmation.h
index 531f3c93c..1226dda69 100644
--- a/src/auditor/taler-auditor-httpd_deposit-confirmation.h
+++ b/src/auditor/taler-auditor-httpd_deposit-confirmation.h
@@ -49,11 +49,12 @@ TEAH_DEPOSIT_CONFIRMATION_done (void);
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
-int
+MHD_RESULT
TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size);
+
#endif
diff --git a/src/auditor/taler-auditor-httpd_exchanges.c b/src/auditor/taler-auditor-httpd_exchanges.c
deleted file mode 100644
index 237b973f8..000000000
--- a/src/auditor/taler-auditor-httpd_exchanges.c
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as 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-auditor-httpd_exchanges.c
- * @brief Handle /exchanges requests; returns list of exchanges we audit
- * @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-auditor-httpd.h"
-#include "taler-auditor-httpd_exchanges.h"
-
-
-/**
- * Add exchange information to the list.
- *
- * @param[in,out] cls a `json_t *` array to extend
- * @param master_pub master public key of an exchange
- * @param exchange_url base URL of an exchange
- */
-static void
-add_exchange (void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *exchange_url)
-{
- json_t *list = cls;
- json_t *obj;
-
- obj = json_pack ("{s:o, s:s}",
- "master_pub",
- GNUNET_JSON_from_data_auto (master_pub),
- "exchange_url",
- exchange_url);
- GNUNET_break (NULL != obj);
- GNUNET_break (0 ==
- json_array_append_new (list,
- obj));
-
-}
-
-
-/**
- * Handle a "/exchanges" request.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
- * @return MHD result code
- */
-int
-TAH_EXCHANGES_handler (struct TAH_RequestHandler *rh,
- struct MHD_Connection *connection,
- void **connection_cls,
- const char *upload_data,
- size_t *upload_data_size)
-{
- json_t *ja;
- struct TALER_AUDITORDB_Session *session;
- enum GNUNET_DB_QueryStatus qs;
-
- (void) rh;
- (void) connection_cls;
- (void) upload_data;
- (void) upload_data_size;
- session = TAH_plugin->get_session (TAH_plugin->cls);
- if (NULL == session)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_DB_SETUP_FAILED,
- "failed to establish session with database");
- }
- ja = json_array ();
- GNUNET_break (NULL != ja);
- qs = TAH_plugin->list_exchanges (TAH_plugin->cls,
- session,
- &add_exchange,
- ja);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- json_decref (ja);
- TALER_LOG_WARNING ("Failed to handle /exchanges in database\n");
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_LIST_EXCHANGES_DB_ERROR,
- "Could not fetch exchange list from database");
- }
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:o}",
- "exchanges", ja);
-}
-
-
-/* end of taler-auditor-httpd_exchanges.c */
diff --git a/src/auditor/taler-auditor-httpd_mhd.c b/src/auditor/taler-auditor-httpd_mhd.c
index a8322a830..e5d2f71ee 100644
--- a/src/auditor/taler-auditor-httpd_mhd.c
+++ b/src/auditor/taler-auditor-httpd_mhd.c
@@ -43,7 +43,7 @@
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
-int
+MHD_RESULT
TAH_MHD_handler_static_response (struct TAH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
@@ -77,7 +77,7 @@ TAH_MHD_handler_static_response (struct TAH_RequestHandler *rh,
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
-int
+MHD_RESULT
TAH_MHD_handler_agpl_redirect (struct TAH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
diff --git a/src/auditor/taler-auditor-httpd_mhd.h b/src/auditor/taler-auditor-httpd_mhd.h
index 1096ee346..1804a1861 100644
--- a/src/auditor/taler-auditor-httpd_mhd.h
+++ b/src/auditor/taler-auditor-httpd_mhd.h
@@ -39,7 +39,7 @@
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
-int
+MHD_RESULT
TAH_MHD_handler_static_response (struct TAH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
@@ -58,7 +58,7 @@ TAH_MHD_handler_static_response (struct TAH_RequestHandler *rh,
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
-int
+MHD_RESULT
TAH_MHD_handler_agpl_redirect (struct TAH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
diff --git a/src/auditor/taler-auditor-sign.c b/src/auditor/taler-auditor-sign.c
deleted file mode 100644
index 62f565907..000000000
--- a/src/auditor/taler-auditor-sign.c
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014, 2015, 2018 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 taler-auditor-sign.c
- * @brief Tool used by the auditor to sign the exchange's master key and the
- * denomination key(s).
- * @author Christian Grothoff
- */
-#include <platform.h>
-#include "taler_exchangedb_lib.h"
-#include "taler_auditordb_lib.h"
-
-
-/**
- * Are we running in verbose mode?
- */
-static unsigned int verbose;
-
-/**
- * Filename of the auditor's private key.
- */
-static char *auditor_key_file;
-
-/**
- * File with the Exchange's denomination keys to sign, itself
- * signed by the Exchange's public key.
- */
-static char *exchange_request_file;
-
-/**
- * Where should we write the auditor's signature?
- */
-static char *output_file;
-
-/**
- * URL of the auditor (informative for the user).
- */
-static char *auditor_url;
-
-/**
- * Master public key of the exchange.
- */
-static struct TALER_MasterPublicKeyP master_public_key;
-
-/**
- * Our configuration.
- */
-static struct GNUNET_CONFIGURATION_Handle *cfg;
-
-/**
- * Handle to access the auditor's database.
- */
-static struct TALER_AUDITORDB_Plugin *adb;
-
-
-/**
- * Print denomination key details for diagnostics.
- *
- * @param dk denomination key to print
- */
-static void
-print_dk (const struct TALER_DenominationKeyValidityPS *dk)
-{
- struct TALER_Amount a;
- char *s;
-
- fprintf (stdout,
- "Denomination key hash: %s\n",
- GNUNET_h2s_full (&dk->denom_hash));
- TALER_amount_ntoh (&a,
- &dk->value);
- fprintf (stdout,
- "Value: %s\n",
- s = TALER_amount_to_string (&a));
- GNUNET_free (s);
- TALER_amount_ntoh (&a,
- &dk->fee_withdraw);
- fprintf (stdout,
- "Withdraw fee: %s\n",
- s = TALER_amount_to_string (&a));
- GNUNET_free (s);
- TALER_amount_ntoh (&a,
- &dk->fee_deposit);
- fprintf (stdout,
- "Deposit fee: %s\n",
- s = TALER_amount_to_string (&a));
- GNUNET_free (s);
- TALER_amount_ntoh (&a,
- &dk->fee_refresh);
- fprintf (stdout,
- "Refresh fee: %s\n",
- s = TALER_amount_to_string (&a));
- GNUNET_free (s);
- TALER_amount_ntoh (&a,
- &dk->fee_refund);
- fprintf (stdout,
- "Refund fee: %s\n",
- s = TALER_amount_to_string (&a));
- GNUNET_free (s);
-
- fprintf (stdout,
- "Validity start time: %s\n",
- GNUNET_STRINGS_absolute_time_to_string (GNUNET_TIME_absolute_ntoh (
- dk->start)));
- fprintf (stdout,
- "Withdraw end time: %s\n",
- GNUNET_STRINGS_absolute_time_to_string (GNUNET_TIME_absolute_ntoh (
- dk->expire_withdraw)));
- fprintf (stdout,
- "Deposit end time: %s\n",
- GNUNET_STRINGS_absolute_time_to_string (GNUNET_TIME_absolute_ntoh (
- dk->expire_deposit)));
- fprintf (stdout,
- "Legal dispute end time: %s\n",
- GNUNET_STRINGS_absolute_time_to_string (GNUNET_TIME_absolute_ntoh (
- dk->expire_legal)));
-
- fprintf (stdout,
- "\n");
-}
-
-
-/**
- * The main function of the taler-auditor-sign tool. This tool is used
- * to sign a exchange's master and denomination keys, affirming that the
- * auditor is aware of them and will validate the exchange's database with
- * respect to these keys.
- *
- * @param argc number of arguments from the command line
- * @param argv command line arguments
- * @return 0 ok, 1 on error
- */
-int
-main (int argc,
- char *const *argv)
-{
- char *cfgfile = NULL;
- char *loglev = NULL;
- char *logfile = NULL;
- const struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_filename ('a',
- "auditor-key",
- "FILENAME",
- "file containing the private key of the auditor",
- &auditor_key_file),
- GNUNET_GETOPT_option_cfgfile (&cfgfile),
- GNUNET_GETOPT_option_help ("Sign denomination keys of an exchange"),
- GNUNET_GETOPT_option_loglevel (&loglev),
- GNUNET_GETOPT_option_logfile (&logfile),
- GNUNET_GETOPT_option_mandatory
- (GNUNET_GETOPT_option_base32_auto ('m',
- "exchange-key",
- "KEY",
- "public key of the exchange (Crockford base32 encoded)",
- &master_public_key)),
- GNUNET_GETOPT_option_string ('u',
- "auditor-url",
- "URL",
- "URL of the auditor (informative link for the user)",
- &auditor_url),
- GNUNET_GETOPT_option_mandatory
- (GNUNET_GETOPT_option_filename ('r',
- "exchange-request",
- "FILENAME",
- "set of keys the exchange requested the auditor to sign",
- &exchange_request_file)),
- GNUNET_GETOPT_option_filename ('o',
- "output",
- "FILENAME",
- "where to write our signature",
- &output_file),
- GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
- GNUNET_GETOPT_option_verbose (&verbose),
- GNUNET_GETOPT_OPTION_END
- };
- struct GNUNET_CRYPTO_EddsaPrivateKey *eddsa_priv;
- struct TALER_AuditorSignatureP *sigs;
- struct TALER_AuditorPublicKeyP apub;
- struct GNUNET_DISK_FileHandle *fh;
- struct TALER_DenominationKeyValidityPS *dki;
- unsigned int dki_len;
- struct TALER_ExchangeKeyValidityPS kv;
- off_t in_size;
-
- if (GNUNET_GETOPT_run ("taler-auditor-sign",
- options,
- argc, argv) <= 0)
- return 1;
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-auditor-sign",
- loglev,
- logfile));
- if (NULL == cfgfile)
- cfgfile = GNUNET_strdup (GNUNET_OS_project_data_get ()->user_config_file);
- cfg = GNUNET_CONFIGURATION_create ();
- if (GNUNET_SYSERR ==
- GNUNET_CONFIGURATION_load (cfg,
- cfgfile))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Malformed configuration file `%s', exiting ...\n",
- cfgfile);
- GNUNET_free_non_null (cfgfile);
- return 1;
- }
- GNUNET_free_non_null (cfgfile);
- if ( (NULL == auditor_key_file) &&
- (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (cfg,
- "auditor",
- "AUDITOR_PRIV_FILE",
- &auditor_key_file)) )
- {
- fprintf (stderr,
- "Auditor key file not given in neither configuration nor command-line\n");
- return 1;
- }
- if ( (NULL == auditor_url) &&
- (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "auditor",
- "AUDITOR_URL",
- &auditor_url)) )
- {
- fprintf (stderr,
- "Auditor URL not given in neither configuration nor command-line\n");
- return 1;
- }
- if (GNUNET_YES !=
- GNUNET_DISK_file_test (auditor_key_file))
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Auditor private key `%s' does not exist yet, creating it!\n",
- auditor_key_file);
- eddsa_priv = GNUNET_CRYPTO_eddsa_key_create_from_file (auditor_key_file);
- if (NULL == eddsa_priv)
- {
- fprintf (stderr,
- "Failed to initialize auditor key from file `%s'\n",
- auditor_key_file);
- return 1;
- }
- GNUNET_CRYPTO_eddsa_key_get_public (eddsa_priv,
- &apub.eddsa_pub);
- fh = GNUNET_DISK_file_open (exchange_request_file,
- GNUNET_DISK_OPEN_READ,
- GNUNET_DISK_PERM_NONE);
- if (NULL == fh)
- {
- fprintf (stderr,
- "Failed to open file `%s': %s\n",
- exchange_request_file,
- strerror (errno));
- GNUNET_free (eddsa_priv);
- return 1;
- }
- if (GNUNET_OK !=
- GNUNET_DISK_file_handle_size (fh,
- &in_size))
- {
- fprintf (stderr,
- "Failed to obtain input file size `%s': %s\n",
- exchange_request_file,
- strerror (errno));
- GNUNET_DISK_file_close (fh);
- GNUNET_free (eddsa_priv);
- return 1;
- }
- if (0 != (in_size % sizeof (struct TALER_DenominationKeyValidityPS)))
- {
- fprintf (stderr,
- "Input file size of file `%s' is invalid\n",
- exchange_request_file);
- GNUNET_DISK_file_close (fh);
- GNUNET_free (eddsa_priv);
- return 1;
- }
- dki_len = in_size / sizeof (struct TALER_DenominationKeyValidityPS);
- if (0 == dki_len)
- {
- fprintf (stderr,
- "Failed to produce auditor signature, denomination list is empty.\n");
- GNUNET_DISK_file_close (fh);
- GNUNET_free (eddsa_priv);
- return 2;
- }
- if (NULL ==
- (adb = TALER_AUDITORDB_plugin_load (cfg)))
- {
- fprintf (stderr,
- "Failed to initialize auditor database plugin.\n");
- GNUNET_DISK_file_close (fh);
- GNUNET_free (eddsa_priv);
- return 3;
- }
-
- kv.purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS);
- kv.purpose.size = htonl (sizeof (struct TALER_ExchangeKeyValidityPS));
- GNUNET_CRYPTO_hash (auditor_url,
- strlen (auditor_url) + 1,
- &kv.auditor_url_hash);
- kv.master = master_public_key;
- dki = GNUNET_new_array (dki_len,
- struct TALER_DenominationKeyValidityPS);
- if (in_size !=
- GNUNET_DISK_file_read (fh,
- dki,
- in_size))
- {
- fprintf (stderr,
- "Failed to read input file `%s': %s\n",
- exchange_request_file,
- strerror (errno));
- TALER_AUDITORDB_plugin_unload (adb);
- GNUNET_DISK_file_close (fh);
- GNUNET_free (dki);
- GNUNET_free (eddsa_priv);
- return 1;
- }
- GNUNET_DISK_file_close (fh);
- sigs = GNUNET_new_array (dki_len,
- struct TALER_AuditorSignatureP);
- for (unsigned int i = 0; i<dki_len; i++)
- {
- struct TALER_DenominationKeyValidityPS *dk = &dki[i];
-
- if (verbose)
- print_dk (dk);
- kv.start = dk->start;
- kv.expire_withdraw = dk->expire_withdraw;
- kv.expire_deposit = dk->expire_deposit;
- kv.expire_legal = dk->expire_legal;
- kv.value = dk->value;
- kv.fee_withdraw = dk->fee_withdraw;
- kv.fee_deposit = dk->fee_deposit;
- kv.fee_refresh = dk->fee_refresh;
- kv.fee_refund = dk->fee_refund;
- kv.denom_hash = dk->denom_hash;
-
- /* Finally sign ... */
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (eddsa_priv,
- &kv.purpose,
- &sigs[i].eddsa_sig));
- }
-
- if (NULL == output_file)
- {
- fprintf (stderr,
- "Output file not given\n");
- TALER_AUDITORDB_plugin_unload (adb);
- GNUNET_free (dki);
- GNUNET_free (sigs);
- GNUNET_free (eddsa_priv);
- return 1;
- }
-
- /* Create required tables */
- if (GNUNET_OK !=
- adb->create_tables (adb->cls))
- {
- fprintf (stderr,
- "Failed to create tables in auditor's database\n");
- TALER_AUDITORDB_plugin_unload (adb);
- GNUNET_free (dki);
- GNUNET_free (sigs);
- GNUNET_free (eddsa_priv);
- return 3;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Taler auditor importing keys for exchange master public key %s\n",
- TALER_B2S (&master_public_key));
-
- /* Update DB */
- {
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_AUDITORDB_Session *session;
-
- session = adb->get_session (adb->cls);
- if (NULL == session)
- {
- fprintf (stderr,
- "Failed to initialize database session\n");
- TALER_AUDITORDB_plugin_unload (adb);
- GNUNET_free (dki);
- GNUNET_free (sigs);
- GNUNET_free (eddsa_priv);
- return 3;
- }
- for (unsigned int i = 0; i<dki_len; i++)
- {
- const struct TALER_DenominationKeyValidityPS *dk = &dki[i];
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Adding denomination key %s to auditor database\n",
- TALER_B2S (&dk->denom_hash));
- qs = adb->insert_denomination_info (adb->cls,
- session,
- dk);
- if (0 > qs)
- {
- fprintf (stderr,
- "Failed to store key in auditor DB (did you add the exchange using taler-auditor-exchange first?)\n");
- TALER_AUDITORDB_plugin_unload (adb);
- GNUNET_free (dki);
- GNUNET_free (sigs);
- GNUNET_free (eddsa_priv);
- return 3;
- }
- }
- }
- TALER_AUDITORDB_plugin_unload (adb);
-
- /* write result to disk */
- if (GNUNET_OK !=
- TALER_EXCHANGEDB_auditor_write (output_file,
- &apub,
- auditor_url,
- sigs,
- &master_public_key,
- dki_len,
- dki))
- {
- fprintf (stderr,
- "Failed to write to file `%s': %s\n",
- output_file,
- strerror (errno));
- GNUNET_free (sigs);
- GNUNET_free (dki);
- return 1;
- }
- GNUNET_free (sigs);
- GNUNET_free (dki);
- GNUNET_free (eddsa_priv);
- return 0;
-}
-
-
-/* end of taler-auditor-sign.c */
diff --git a/src/auditor/taler-auditor-sync.c b/src/auditor/taler-auditor-sync.c
new file mode 100644
index 000000000..e4022d325
--- /dev/null
+++ b/src/auditor/taler-auditor-sync.c
@@ -0,0 +1,645 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-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/>
+*/
+/**
+ * @file taler-auditor-sync.c
+ * @brief Tool used by the auditor to make a 'safe' copy of the exchanges' database.
+ * @author Christian Grothoff
+ */
+#include <platform.h>
+#include "taler_exchangedb_lib.h"
+
+
+/**
+ * Handle to access the exchange's source database.
+ */
+static struct TALER_EXCHANGEDB_Plugin *src;
+
+/**
+ * Handle to access the exchange's destination database.
+ */
+static struct TALER_EXCHANGEDB_Plugin *dst;
+
+/**
+ * Return value from #main().
+ */
+static int global_ret;
+
+/**
+ * Main task to do synchronization.
+ */
+static struct GNUNET_SCHEDULER_Task *sync_task;
+
+/**
+ * What is our target transaction size (number of records)?
+ */
+static unsigned int transaction_size = 512;
+
+/**
+ * Number of records copied in this transaction.
+ */
+static unsigned long long actual_size;
+
+/**
+ * Terminate once synchronization is achieved.
+ */
+static int exit_if_synced;
+
+
+/**
+ * Information we track per replicated table.
+ */
+struct Table
+{
+ /**
+ * Which table is this record about?
+ */
+ enum TALER_EXCHANGEDB_ReplicatedTable rt;
+
+ /**
+ * Up to which record is the destination table synchronized.
+ */
+ uint64_t start_serial;
+
+ /**
+ * Highest serial in the source table.
+ */
+ uint64_t end_serial;
+
+ /**
+ * Marker for the end of the list of #tables.
+ */
+ bool end;
+};
+
+
+/**
+ * Information about replicated tables.
+ */
+static struct Table tables[] = {
+ { .rt = TALER_EXCHANGEDB_RT_DENOMINATIONS},
+ { .rt = TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS},
+ { .rt = TALER_EXCHANGEDB_RT_WIRE_TARGETS},
+ { .rt = TALER_EXCHANGEDB_RT_LEGITIMIZATION_PROCESSES},
+ { .rt = TALER_EXCHANGEDB_RT_LEGITIMIZATION_REQUIREMENTS},
+ { .rt = TALER_EXCHANGEDB_RT_RESERVES},
+ { .rt = TALER_EXCHANGEDB_RT_RESERVES_IN},
+ { .rt = TALER_EXCHANGEDB_RT_RESERVES_CLOSE},
+ { .rt = TALER_EXCHANGEDB_RT_RESERVES_OPEN_REQUESTS},
+ { .rt = TALER_EXCHANGEDB_RT_RESERVES_OPEN_DEPOSITS},
+ { .rt = TALER_EXCHANGEDB_RT_RESERVES_OUT},
+ { .rt = TALER_EXCHANGEDB_RT_AUDITORS},
+ { .rt = TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS},
+ { .rt = TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS},
+ { .rt = TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS},
+ { .rt = TALER_EXCHANGEDB_RT_KNOWN_COINS},
+ { .rt = TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS},
+ { .rt = TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS},
+ { .rt = TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS},
+ { .rt = TALER_EXCHANGEDB_RT_BATCH_DEPOSITS},
+ { .rt = TALER_EXCHANGEDB_RT_COIN_DEPOSITS},
+ { .rt = TALER_EXCHANGEDB_RT_REFUNDS},
+ { .rt = TALER_EXCHANGEDB_RT_WIRE_OUT},
+ { .rt = TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING},
+ { .rt = TALER_EXCHANGEDB_RT_WIRE_FEE},
+ { .rt = TALER_EXCHANGEDB_RT_GLOBAL_FEE},
+ { .rt = TALER_EXCHANGEDB_RT_RECOUP},
+ { .rt = TALER_EXCHANGEDB_RT_RECOUP_REFRESH },
+ { .rt = TALER_EXCHANGEDB_RT_EXTENSIONS},
+ { .rt = TALER_EXCHANGEDB_RT_POLICY_DETAILS },
+ { .rt = TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS },
+ { .rt = TALER_EXCHANGEDB_RT_PURSE_REQUESTS},
+ { .rt = TALER_EXCHANGEDB_RT_PURSE_DECISION},
+ { .rt = TALER_EXCHANGEDB_RT_PURSE_MERGES},
+ { .rt = TALER_EXCHANGEDB_RT_PURSE_DEPOSITS},
+ { .rt = TALER_EXCHANGEDB_RT_ACCOUNT_MERGES},
+ { .rt = TALER_EXCHANGEDB_RT_HISTORY_REQUESTS},
+ { .rt = TALER_EXCHANGEDB_RT_CLOSE_REQUESTS},
+ { .rt = TALER_EXCHANGEDB_RT_WADS_OUT},
+ { .rt = TALER_EXCHANGEDB_RT_WADS_OUT_ENTRIES},
+ { .rt = TALER_EXCHANGEDB_RT_WADS_IN},
+ { .rt = TALER_EXCHANGEDB_RT_WADS_IN_ENTRIES},
+ { .rt = TALER_EXCHANGEDB_RT_PROFIT_DRAINS},
+ { .end = true }
+};
+
+
+/**
+ * Closure for #do_insert.
+ */
+struct InsertContext
+{
+ /**
+ * Table we are replicating.
+ */
+ struct Table *table;
+
+ /**
+ * Set to error if insertion created an error.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function called on data to replicate in the auditor's database.
+ *
+ * @param cls closure, a `struct InsertContext`
+ * @param td record from an exchange table
+ * @return #GNUNET_OK to continue to iterate,
+ * #GNUNET_SYSERR to fail with an error
+ */
+static enum GNUNET_GenericReturnValue
+do_insert (void *cls,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct InsertContext *ctx = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (0 >= ctx->qs)
+ return GNUNET_SYSERR;
+ qs = dst->insert_records_by_table (dst->cls,
+ td);
+ if (0 >= qs)
+ {
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_assert (0);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to insert record into table %d: no change\n",
+ td->table);
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Serialization error inserting record into table %d (will retry)\n",
+ td->table);
+ break;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to insert record into table %d: hard error\n",
+ td->table);
+ break;
+ }
+ ctx->qs = qs;
+ return GNUNET_SYSERR;
+ }
+ actual_size++;
+ ctx->table->start_serial = td->serial;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Run one replication transaction.
+ *
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR to rollback
+ */
+static enum GNUNET_GenericReturnValue
+transact (void)
+{
+ struct InsertContext ctx = {
+ .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+ };
+
+ if (0 >
+ src->start (src->cls,
+ "lookup src serials"))
+ return GNUNET_SYSERR;
+ for (unsigned int i = 0; ! tables[i].end; i++)
+ src->lookup_serial_by_table (src->cls,
+ tables[i].rt,
+ &tables[i].end_serial);
+ src->rollback (src->cls);
+ if (GNUNET_OK !=
+ dst->start (dst->cls,
+ "lookup dst serials"))
+ return GNUNET_SYSERR;
+ for (unsigned int i = 0; ! tables[i].end; i++)
+ dst->lookup_serial_by_table (dst->cls,
+ tables[i].rt,
+ &tables[i].start_serial);
+ dst->rollback (dst->cls);
+ for (unsigned int i = 0; ! tables[i].end; i++)
+ {
+ struct Table *table = &tables[i];
+
+ if (table->start_serial == table->end_serial)
+ continue;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Replicating table %d from %llu to %llu\n",
+ i,
+ (unsigned long long) table->start_serial,
+ (unsigned long long) table->end_serial);
+ ctx.table = table;
+ while (table->start_serial < table->end_serial)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (GNUNET_OK !=
+ src->start (src->cls,
+ "copy table (src)"))
+ return GNUNET_SYSERR;
+ if (GNUNET_OK !=
+ dst->start (dst->cls,
+ "copy table (dst)"))
+ return GNUNET_SYSERR;
+ qs = src->lookup_records_by_table (src->cls,
+ table->rt,
+ table->start_serial,
+ &do_insert,
+ &ctx);
+ if (ctx.qs < 0)
+ qs = ctx.qs;
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to lookup records from table %d: hard error\n",
+ i);
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Serialization error looking up records from table %d (will retry)\n",
+ i);
+ return GNUNET_SYSERR; /* will retry */
+ }
+ if (0 == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to lookup records from table %d: no results\n",
+ i);
+ GNUNET_break (0); /* should be impossible */
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ if (0 == ctx.qs)
+ return GNUNET_SYSERR; /* insertion failed, maybe record existed? try again */
+ src->rollback (src->cls);
+ qs = dst->commit (dst->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Serialization error committing transaction on table %d (will retry)\n",
+ i);
+ continue;
+ }
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Hard error committing transaction on table %d\n",
+ i);
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+ /* we do not care about conflicting UPDATEs to src table, so safe to just rollback */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sync pass completed successfully with %llu updates\n",
+ actual_size);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Task to do the actual synchronization work.
+ *
+ * @param cls NULL, unused
+ */
+static void
+do_sync (void *cls)
+{
+ static struct GNUNET_TIME_Relative delay;
+
+ (void) cls;
+ sync_task = NULL;
+ actual_size = 0;
+ if (GNUNET_SYSERR ==
+ src->preflight (src->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to begin transaction with data source. Exiting\n");
+ return;
+ }
+ if (GNUNET_SYSERR ==
+ dst->preflight (dst->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to begin transaction with data destination. Exiting\n");
+ return;
+ }
+ if (GNUNET_OK != transact ())
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Transaction failed, rolling back\n");
+ src->rollback (src->cls);
+ dst->rollback (dst->cls);
+ }
+ if (0 != global_ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Transaction failed permanently, exiting\n");
+ return;
+ }
+ if ( (0 == actual_size) &&
+ (exit_if_synced) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Databases are synchronized. Exiting\n");
+ return;
+ }
+ if (actual_size < transaction_size / 2)
+ {
+ delay = GNUNET_TIME_STD_BACKOFF (delay);
+ }
+ else if (actual_size >= transaction_size)
+ {
+ delay = GNUNET_TIME_UNIT_ZERO;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Next sync pass in %s\n",
+ GNUNET_STRINGS_relative_time_to_string (delay,
+ GNUNET_YES));
+ sync_task = GNUNET_SCHEDULER_add_delayed (delay,
+ &do_sync,
+ NULL);
+}
+
+
+/**
+ * Set an option of type 'char *' from the command line with
+ * filename expansion a la #GNUNET_STRINGS_filename_expand().
+ *
+ * @param ctx command line processing context
+ * @param scls additional closure (will point to the `char *`,
+ * which will be allocated)
+ * @param option name of the option
+ * @param value actual value of the option (a string)
+ * @return #GNUNET_OK
+ */
+static enum GNUNET_GenericReturnValue
+set_filename (struct GNUNET_GETOPT_CommandLineProcessorContext *ctx,
+ void *scls,
+ const char *option,
+ const char *value)
+{
+ char **val = scls;
+
+ (void) ctx;
+ (void) option;
+ GNUNET_assert (NULL != value);
+ GNUNET_free (*val);
+ *val = GNUNET_STRINGS_filename_expand (value);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Allow user to specify configuration file name (-s option)
+ *
+ * @param[out] fn set to the name of the configuration file
+ */
+static struct GNUNET_GETOPT_CommandLineOption
+option_cfgfile_src (char **fn)
+{
+ struct GNUNET_GETOPT_CommandLineOption clo = {
+ .shortName = 's',
+ .name = "source-configuration",
+ .argumentHelp = "FILENAME",
+ .description = gettext_noop (
+ "use configuration file FILENAME for the SOURCE database"),
+ .require_argument = 1,
+ .processor = &set_filename,
+ .scls = (void *) fn
+ };
+
+ return clo;
+}
+
+
+/**
+ * Allow user to specify configuration file name (-d option)
+ *
+ * @param[out] fn set to the name of the configuration file
+ */
+static struct GNUNET_GETOPT_CommandLineOption
+option_cfgfile_dst (char **fn)
+{
+ struct GNUNET_GETOPT_CommandLineOption clo = {
+ .shortName = 'd',
+ .name = "destination-configuration",
+ .argumentHelp = "FILENAME",
+ .description = gettext_noop (
+ "use configuration file FILENAME for the DESTINATION database"),
+ .require_argument = 1,
+ .processor = &set_filename,
+ .scls = (void *) fn
+ };
+
+ return clo;
+}
+
+
+static struct GNUNET_CONFIGURATION_Handle *
+load_config (const char *cfgfile)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ cfg = GNUNET_CONFIGURATION_create ();
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Loading config file: %s\n",
+ cfgfile);
+ if (GNUNET_SYSERR ==
+ GNUNET_CONFIGURATION_load (cfg,
+ cfgfile))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Malformed configuration file `%s', exit ...\n",
+ cfgfile);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return NULL;
+ }
+ return cfg;
+}
+
+
+/**
+ * Shutdown task.
+ *
+ * @param cls NULL, unused
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+ if (NULL != sync_task)
+ {
+ GNUNET_SCHEDULER_cancel (sync_task);
+ sync_task = NULL;
+ }
+}
+
+
+/**
+ * Initial task.
+ *
+ * @param cls NULL, unused
+ */
+static void
+run (void *cls)
+{
+ (void) cls;
+
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ sync_task = GNUNET_SCHEDULER_add_now (&do_sync,
+ NULL);
+}
+
+
+/**
+ * Setup plugins in #src and #dst and #run() the main
+ * logic with those plugins.
+ */
+static void
+setup (struct GNUNET_CONFIGURATION_Handle *src_cfg,
+ struct GNUNET_CONFIGURATION_Handle *dst_cfg)
+{
+ src = TALER_EXCHANGEDB_plugin_load (src_cfg);
+ if (NULL == src)
+ {
+ global_ret = EXIT_NOTINSTALLED;
+ return;
+ }
+ dst = TALER_EXCHANGEDB_plugin_load (dst_cfg);
+ if (NULL == dst)
+ {
+ global_ret = EXIT_NOTINSTALLED;
+ TALER_EXCHANGEDB_plugin_unload (src);
+ src = NULL;
+ return;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ NULL);
+ TALER_EXCHANGEDB_plugin_unload (src);
+ src = NULL;
+ TALER_EXCHANGEDB_plugin_unload (dst);
+ dst = NULL;
+}
+
+
+/**
+ * The main function of the taler-auditor-exchange tool. This tool is used
+ * to add (or remove) an exchange's master key and base URL to the auditor's
+ * database.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, non-zero on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ char *src_cfgfile = NULL;
+ char *dst_cfgfile = NULL;
+ char *level = GNUNET_strdup ("WARNING");
+ struct GNUNET_CONFIGURATION_Handle *src_cfg;
+ struct GNUNET_CONFIGURATION_Handle *dst_cfg;
+ const struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_mandatory (
+ option_cfgfile_src (&src_cfgfile)),
+ GNUNET_GETOPT_option_mandatory (
+ option_cfgfile_dst (&dst_cfgfile)),
+ GNUNET_GETOPT_option_help (
+ gettext_noop ("Make a safe copy of an exchange database")),
+ GNUNET_GETOPT_option_uint (
+ 'b',
+ "batch",
+ "SIZE",
+ gettext_noop (
+ "target SIZE for a the number of records to copy in one transaction"),
+ &transaction_size),
+ GNUNET_GETOPT_option_flag (
+ 't',
+ "terminate-when-synchronized",
+ gettext_noop (
+ "terminate as soon as the databases are synchronized"),
+ &exit_if_synced),
+ GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
+ GNUNET_GETOPT_option_loglevel (&level),
+ GNUNET_GETOPT_OPTION_END
+ };
+
+ TALER_OS_init ();
+ TALER_gcrypt_init (); /* must trigger initialization manually at this point! */
+ {
+ int ret;
+
+ ret = GNUNET_GETOPT_run ("taler-auditor-sync",
+ options,
+ argc, argv);
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_log_setup ("taler-auditor-sync",
+ level,
+ NULL));
+ GNUNET_free (level);
+ /* suppress compiler warnings... */
+ GNUNET_assert (NULL != src_cfgfile);
+ GNUNET_assert (NULL != dst_cfgfile);
+ if (0 == strcmp (src_cfgfile,
+ dst_cfgfile))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Source and destination configuration files must differ!\n");
+ return EXIT_INVALIDARGUMENT;
+ }
+ src_cfg = load_config (src_cfgfile);
+ if (NULL == src_cfg)
+ {
+ GNUNET_free (src_cfgfile);
+ GNUNET_free (dst_cfgfile);
+ return EXIT_NOTCONFIGURED;
+ }
+ dst_cfg = load_config (dst_cfgfile);
+ if (NULL == dst_cfg)
+ {
+ GNUNET_CONFIGURATION_destroy (src_cfg);
+ GNUNET_free (src_cfgfile);
+ GNUNET_free (dst_cfgfile);
+ return EXIT_NOTCONFIGURED;
+ }
+ setup (src_cfg,
+ dst_cfg);
+ GNUNET_CONFIGURATION_destroy (src_cfg);
+ GNUNET_CONFIGURATION_destroy (dst_cfg);
+ GNUNET_free (src_cfgfile);
+ GNUNET_free (dst_cfgfile);
+
+ return global_ret;
+}
+
+
+/* end of taler-auditor-sync.c */
diff --git a/src/auditor/taler-auditor.in b/src/auditor/taler-auditor.in
index 11443a5da..ab3d8d202 100755..100644
--- a/src/auditor/taler-auditor.in
+++ b/src/auditor/taler-auditor.in
@@ -1,19 +1,105 @@
-#!/bin/sh
+#!/bin/bash
set -eu
-DIR=`mktemp -d reportXXXXXX`
-for n in aggregation coins deposits reserves wire
+function usage {
+ cat - <<EOF
+taler-auditor
+Audit Taler exchange database for consistency.
+Arguments mandatory for long options are also mandatory for short options.
+ -c, --config=FILENAME use configuration file FILENAME
+ -h, --help print this help
+ -i, --internal perform checks only applicable for
+ exchange-internal audits
+ -I, --ignore-not-found ignore problems with the exchange bank account not existing
+ -L, --log=LOGLEVEL configure logging to use LOGLEVEL
+ -l, --logfile=FILENAME configure logging to write logs to FILENAME
+ -m, --exchange-key=KEY public key of the exchange (Crockford base32
+ encoded)
+ -T, --timetravel=[+/-]MICROSECONDS
+ modify system time by given offset (for
+ debugging/testing only)
+ -v, --version print the version number
+Report bugs to taler@gnu.org.
+Home page: http://www.gnu.org/s/taler/
+General help using GNU software: http://www.gnu.org/gethelp/
+EOF
+}
+
+
+function optcheck {
+
+TEMP=`getopt -o c:hiIL:l:m:T:v --long config:,help,internal,ignore-not-found,log:,logfile:exchange-key:,timetravel:,version -n 'taler-auditor' -- "$@"`
+
+if [ $? != 0 ] ;
+then
+ exit 1 ;
+fi
+
+# Note the quotes around `$TEMP': they are essential!
+eval set -- "$TEMP"
+
+VERBOSE=false
+DEBUG=false
+MEMORY=
+DEBUGFILE=
+JAVA_MISC_OPT=
+INF=
+while true; do
+ case "$1" in
+ -c | --config ) shift 2 ;;
+ -h | --help )
+ usage
+ exit 0
+ ;;
+ -i | --internal ) shift ;;
+ -I | --ignore-not-found ) INF="-I"; shift ;;
+ -L | --log ) shift 2;;
+ -l | --logfile ) shift ;;
+ -m | --exchange-key ) shift 2 ;;
+ -t | --timetravel ) shift 2 ;;
+ -m | --memory ) MEMORY="$2"; shift 2 ;;
+ -v | --version )
+ taler-helper-auditor-deposits -v | sed -e 's/taler-helper-auditor-deposits/taler-auditor/'
+ exit 0
+ ;;
+ -- )
+ shift;
+ break
+ ;;
+ * )
+ usage
+ exit 1
+ ;;
+ esac
+done
+
+}
+# End of function 'optcheck'
+
+optcheck "$@"
+
+# Remove "-I" from $@ if present, store result in $ARGS.
+ARGS=("$@")
+ARGS=(${ARGS[@]/$INF})
+
+DATE=`date +%F_%H:%M:%S`
+DIR="report_$DATE"
+mkdir $DIR
+for n in aggregation coins deposits purses reserves
do
- taler-helper-auditor-$n "$@" > ${DIR}/$n.json
+ taler-helper-auditor-$n ${ARGS[*]} > ${DIR}/$n.json
done
+taler-helper-auditor-wire $INF ${ARGS[*]} > ${DIR}/wire.json
+
+echo "Generating auditor report in ${DIR}."
taler-helper-auditor-render.py \
${DIR}/aggregation.json \
${DIR}/coins.json \
${DIR}/deposits.json \
${DIR}/reserves.json \
- ${DIR}/wire.json < %pkgdatadir%/auditor-report.tex.j2 > ${DIR}/auditor-report.tex
+ ${DIR}/wire.json < %datadir%/taler/exchange/auditor-report.tex.j2 > ${DIR}/auditor-report.tex
cd ${DIR}
pdflatex auditor-report.tex < /dev/null &> /dev/null || true
pdflatex auditor-report.tex < /dev/null &> /dev/null || true
diff --git a/src/auditor/taler-helper-auditor-aggregation.c b/src/auditor/taler-helper-auditor-aggregation.c
index 190b7260e..a0f2a190f 100644
--- a/src/auditor/taler-helper-auditor-aggregation.c
+++ b/src/auditor/taler-helper-auditor-aggregation.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2016-2020 Taler Systems SA
+ Copyright (C) 2016-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero Public License as published by the Free Software
@@ -34,17 +34,26 @@
static int global_ret;
/**
- * Checkpointing our progress for aggregations.
+ * Run in test mode. Exit when idle instead of
+ * going to sleep and waiting for more work.
+ *
+ * FIXME: not yet implemented!
*/
-static struct TALER_AUDITORDB_ProgressPointAggregation ppa;
+static int test_mode;
/**
* Checkpointing our progress for aggregations.
*/
-static struct TALER_AUDITORDB_ProgressPointAggregation ppa_start;
+static TALER_ARL_DEF_PP (aggregation_last_wire_out_serial_id);
+
+/**
+ * Total aggregation fees (wire fees) earned.
+ */
+static TALER_ARL_DEF_AB (aggregation_total_wire_fee_revenue);
+
/**
- * Array of reports about row inconsitencies.
+ * Array of reports about row inconsistencies.
*/
static json_t *report_row_inconsistencies;
@@ -102,11 +111,6 @@ static struct TALER_Amount total_arithmetic_delta_plus;
static struct TALER_Amount total_arithmetic_delta_minus;
/**
- * Total aggregation fees earned.
- */
-static struct TALER_Amount total_aggregation_fee_income;
-
-/**
* Array of reports about coin operations with bad signatures.
*/
static json_t *report_bad_sig_losses;
@@ -116,13 +120,18 @@ static json_t *report_bad_sig_losses;
*/
static struct TALER_Amount total_bad_sig_loss;
+/**
+ * Should we run checks that only work for exchange-internal audits?
+ */
+static int internal_checks;
+
/**
* Report a (serious) inconsistency in the exchange's database with
* respect to calculations involving amounts.
*
* @param operation what operation had the inconsistency
- * @param rowid affected row, UINT64_MAX if row is missing
+ * @param rowid affected row, 0 if row is missing
* @param exchange amount calculated by exchange
* @param auditor amount calculated by auditor
* @param profitable 1 if @a exchange being larger than @a auditor is
@@ -145,36 +154,38 @@ report_amount_arithmetic_inconsistency (
auditor))
{
/* exchange > auditor */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- exchange,
- auditor));
+ TALER_ARL_amount_subtract (&delta,
+ exchange,
+ auditor);
}
else
{
/* auditor < exchange */
profitable = -profitable;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- auditor,
- exchange));
+ TALER_ARL_amount_subtract (&delta,
+ auditor,
+ exchange);
}
TALER_ARL_report (report_amount_arithmetic_inconsistencies,
- json_pack ("{s:s, s:I, s:o, s:o, s:I}",
- "operation", operation,
- "rowid", (json_int_t) rowid,
- "exchange", TALER_JSON_from_amount (exchange),
- "auditor", TALER_JSON_from_amount (auditor),
- "profitable", (json_int_t) profitable));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ operation),
+ GNUNET_JSON_pack_uint64 ("rowid",
+ rowid),
+ TALER_JSON_pack_amount ("exchange",
+ exchange),
+ TALER_JSON_pack_amount ("auditor",
+ auditor),
+ GNUNET_JSON_pack_int64 ("profitable",
+ profitable)));
if (0 != profitable)
{
target = (1 == profitable)
? &total_arithmetic_delta_plus
: &total_arithmetic_delta_minus;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (target,
- target,
- &delta));
+ TALER_ARL_amount_add (target,
+ target,
+ &delta);
}
}
@@ -207,37 +218,38 @@ report_coin_arithmetic_inconsistency (
auditor))
{
/* exchange > auditor */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- exchange,
- auditor));
+ TALER_ARL_amount_subtract (&delta,
+ exchange,
+ auditor);
}
else
{
/* auditor < exchange */
profitable = -profitable;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- auditor,
- exchange));
+ TALER_ARL_amount_subtract (&delta,
+ auditor,
+ exchange);
}
TALER_ARL_report (report_coin_inconsistencies,
- json_pack ("{s:s, s:o, s:o, s:o, s:I}",
- "operation", operation,
- "coin_pub", GNUNET_JSON_from_data_auto (
- coin_pub),
- "exchange", TALER_JSON_from_amount (exchange),
- "auditor", TALER_JSON_from_amount (auditor),
- "profitable", (json_int_t) profitable));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ operation),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ coin_pub),
+ TALER_JSON_pack_amount ("exchange",
+ exchange),
+ TALER_JSON_pack_amount ("auditor",
+ auditor),
+ GNUNET_JSON_pack_int64 ("profitable",
+ profitable)));
if (0 != profitable)
{
target = (1 == profitable)
? &total_coin_delta_plus
: &total_coin_delta_minus;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (target,
- target,
- &delta));
+ TALER_ARL_amount_add (target,
+ target,
+ &delta);
}
}
@@ -246,7 +258,7 @@ report_coin_arithmetic_inconsistency (
* Report a (serious) inconsistency in the exchange's database.
*
* @param table affected table
- * @param rowid affected row, UINT64_MAX if row is missing
+ * @param rowid affected row, 0 if row is missing
* @param diagnostic message explaining the problem
*/
static void
@@ -255,10 +267,13 @@ report_row_inconsistency (const char *table,
const char *diagnostic)
{
TALER_ARL_report (report_row_inconsistencies,
- json_pack ("{s:s, s:I, s:s}",
- "table", table,
- "row", (json_int_t) rowid,
- "diagnostic", diagnostic));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ table),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ GNUNET_JSON_pack_string ("diagnostic",
+ diagnostic)));
}
@@ -286,22 +301,17 @@ struct WireFeeInfo
/**
* When does the fee go into effect (inclusive).
*/
- struct GNUNET_TIME_Absolute start_date;
+ struct GNUNET_TIME_Timestamp start_date;
/**
* When does the fee stop being in effect (exclusive).
*/
- struct GNUNET_TIME_Absolute end_date;
+ struct GNUNET_TIME_Timestamp end_date;
/**
- * How high is the wire fee.
+ * How high are the wire fees.
*/
- struct TALER_Amount wire_fee;
-
- /**
- * How high is the closing fee.
- */
- struct TALER_Amount closing_fee;
+ struct TALER_WireFeeSet fees;
};
@@ -347,14 +357,14 @@ struct WireCheckContext
struct TALER_Amount total_deposits;
/**
- * Hash of the wire transfer details of the receiver.
+ * Target account details of the receiver.
*/
- struct GNUNET_HashCode h_wire;
+ const char *payto_uri;
/**
* Execution time of the wire transfer.
*/
- struct GNUNET_TIME_Absolute date;
+ struct GNUNET_TIME_Timestamp date;
/**
* Database transaction status.
@@ -379,12 +389,12 @@ struct WireCheckContext
* @param[out] deposit_gain amount the coin contributes excluding refunds
* @return #GNUNET_OK on success, #GNUNET_SYSERR if the transaction must fail (hard error)
*/
-static int
+static enum GNUNET_GenericReturnValue
check_transaction_history_for_deposit (
const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_DenominationKeyValidityPS *issue,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue,
const struct TALER_EXCHANGEDB_TransactionList *tl_head,
struct TALER_Amount *merchant_gain,
struct TALER_Amount *deposit_gain)
@@ -392,74 +402,57 @@ check_transaction_history_for_deposit (
struct TALER_Amount expenditures;
struct TALER_Amount refunds;
struct TALER_Amount spent;
+ struct TALER_Amount *deposited = NULL;
struct TALER_Amount merchant_loss;
const struct TALER_Amount *deposit_fee;
- int refund_deposit_fee;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Checking transaction history of coin %s\n",
TALER_B2S (coin_pub));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&expenditures));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&refunds));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
merchant_gain));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&merchant_loss));
/* Go over transaction history to compute totals; note that we do not bother
to reconstruct the order of the events, so instead of subtracting we
compute positive (deposit, melt) and negative (refund) values separately
here, and then subtract the negative from the positive at the end (after
- the loops). *///
- refund_deposit_fee = GNUNET_NO;
+ the loops). */
deposit_fee = NULL;
for (const struct TALER_EXCHANGEDB_TransactionList *tl = tl_head;
NULL != tl;
tl = tl->next)
{
- const struct TALER_Amount *amount_with_fee;
const struct TALER_Amount *fee_claimed;
switch (tl->type)
{
case TALER_EXCHANGEDB_TT_DEPOSIT:
/* check wire and h_wire are consistent */
+ if (NULL != deposited)
{
- struct GNUNET_HashCode hw;
-
- if (GNUNET_OK !=
- TALER_JSON_merchant_wire_signature_hash (
- tl->details.deposit->receiver_wire_account,
- &hw))
- {
- report_row_inconsistency ("deposits",
- tl->serial_id,
- "wire account given is malformed");
- }
- else if (0 !=
- GNUNET_memcmp (&hw,
- &tl->details.deposit->h_wire))
- {
- report_row_inconsistency ("deposits",
- tl->serial_id,
- "h(wire) does not match wire");
- }
+ TALER_ARL_report (report_row_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ "deposits"),
+ GNUNET_JSON_pack_uint64 ("row",
+ tl->serial_id),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "multiple deposits of the same coin into the same contract detected")));
}
- amount_with_fee = &tl->details.deposit->amount_with_fee; /* according to exchange*/
+ deposited = &tl->details.deposit->amount_with_fee; /* according to exchange*/
fee_claimed = &tl->details.deposit->deposit_fee; /* Fee according to exchange DB */
- if (GNUNET_OK !=
- TALER_amount_add (&expenditures,
+ TALER_ARL_amount_add (&expenditures,
&expenditures,
- amount_with_fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
+ deposited);
/* Check if this deposit is within the remit of the aggregation
we are investigating, if so, include it in the totals. */
if ( (0 == GNUNET_memcmp (merchant_pub,
@@ -469,177 +462,180 @@ check_transaction_history_for_deposit (
{
struct TALER_Amount amount_without_fee;
- if (GNUNET_OK !=
- TALER_amount_subtract (&amount_without_fee,
- amount_with_fee,
- fee_claimed))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (merchant_gain,
+ TALER_ARL_amount_subtract (&amount_without_fee,
+ deposited,
+ fee_claimed);
+ TALER_ARL_amount_add (merchant_gain,
merchant_gain,
- &amount_without_fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
+ &amount_without_fee);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Detected applicable deposit of %s\n",
TALER_amount2s (&amount_without_fee));
deposit_fee = fee_claimed; /* We had a deposit, remember the fee, we may need it */
}
/* Check that the fees given in the transaction list and in dki match */
+ if (0 !=
+ TALER_amount_cmp (&issue->fees.deposit,
+ fee_claimed))
{
- struct TALER_Amount fee_expected;
-
- /* Fee according to denomination data of auditor */
- TALER_amount_ntoh (&fee_expected,
- &issue->fee_deposit);
- if (0 !=
- TALER_amount_cmp (&fee_expected,
- fee_claimed))
- {
- /* Disagreement in fee structure between auditor and exchange DB! */
- report_amount_arithmetic_inconsistency ("deposit fee",
- UINT64_MAX,
- fee_claimed,
- &fee_expected,
- 1);
- }
+ /* Disagreement in fee structure between auditor and exchange DB! */
+ report_amount_arithmetic_inconsistency ("deposit fee",
+ 0,
+ fee_claimed,
+ &issue->fees.deposit,
+ 1);
}
break;
case TALER_EXCHANGEDB_TT_MELT:
- amount_with_fee = &tl->details.melt->amount_with_fee;
- fee_claimed = &tl->details.melt->melt_fee;
- if (GNUNET_OK !=
- TALER_amount_add (&expenditures,
- &expenditures,
- amount_with_fee))
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- /* Check that the fees given in the transaction list and in dki match */
- {
- struct TALER_Amount fee_expected;
-
- TALER_amount_ntoh (&fee_expected,
- &issue->fee_refresh);
+ const struct TALER_Amount *amount_with_fee;
+
+ amount_with_fee = &tl->details.melt->amount_with_fee;
+ fee_claimed = &tl->details.melt->melt_fee;
+ TALER_ARL_amount_add (&expenditures,
+ &expenditures,
+ amount_with_fee);
+ /* Check that the fees given in the transaction list and in dki match */
if (0 !=
- TALER_amount_cmp (&fee_expected,
+ TALER_amount_cmp (&issue->fees.refresh,
fee_claimed))
{
/* Disagreement in fee structure between exchange and auditor */
report_amount_arithmetic_inconsistency ("melt fee",
- UINT64_MAX,
+ 0,
fee_claimed,
- &fee_expected,
+ &issue->fees.refresh,
1);
}
+ break;
}
- break;
case TALER_EXCHANGEDB_TT_REFUND:
- amount_with_fee = &tl->details.refund->refund_amount;
- fee_claimed = &tl->details.refund->refund_fee;
- if (GNUNET_OK !=
- TALER_amount_add (&refunds,
- &refunds,
- amount_with_fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (&expenditures,
- &expenditures,
- fee_claimed))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- /* Check if this refund is within the remit of the aggregation
- we are investigating, if so, include it in the totals. */
- if ( (0 == GNUNET_memcmp (merchant_pub,
- &tl->details.refund->merchant_pub)) &&
- (0 == GNUNET_memcmp (h_contract_terms,
- &tl->details.refund->h_contract_terms)) )
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Detected applicable refund of %s\n",
- TALER_amount2s (amount_with_fee));
- if (GNUNET_OK !=
- TALER_amount_add (&merchant_loss,
- &merchant_loss,
- amount_with_fee))
+ const struct TALER_Amount *amount_with_fee;
+
+ amount_with_fee = &tl->details.refund->refund_amount;
+ fee_claimed = &tl->details.refund->refund_fee;
+ TALER_ARL_amount_add (&refunds,
+ &refunds,
+ amount_with_fee);
+ TALER_ARL_amount_add (&expenditures,
+ &expenditures,
+ fee_claimed);
+ /* Check if this refund is within the remit of the aggregation
+ we are investigating, if so, include it in the totals. */
+ if ( (0 == GNUNET_memcmp (merchant_pub,
+ &tl->details.refund->merchant_pub)) &&
+ (0 == GNUNET_memcmp (h_contract_terms,
+ &tl->details.refund->h_contract_terms)) )
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Detected applicable refund of %s\n",
+ TALER_amount2s (amount_with_fee));
+ TALER_ARL_amount_add (&merchant_loss,
+ &merchant_loss,
+ amount_with_fee);
}
- /* If there is a refund, we give back the deposit fee */
- refund_deposit_fee = GNUNET_YES;
- }
- /* Check that the fees given in the transaction list and in dki match */
- {
- struct TALER_Amount fee_expected;
-
- TALER_amount_ntoh (&fee_expected,
- &issue->fee_refund);
+ /* Check that the fees given in the transaction list and in dki match */
if (0 !=
- TALER_amount_cmp (&fee_expected,
+ TALER_amount_cmp (&issue->fees.refund,
fee_claimed))
{
/* Disagreement in fee structure between exchange and auditor! */
report_amount_arithmetic_inconsistency ("refund fee",
- UINT64_MAX,
+ 0,
fee_claimed,
- &fee_expected,
+ &issue->fees.refund,
1);
}
+ break;
}
- break;
case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
- amount_with_fee = &tl->details.old_coin_recoup->value;
- /* We count recoups of refreshed coins like refunds for the dirty old
- coin, as they equivalently _increase_ the remaining value on the
- _old_ coin */
- if (GNUNET_OK !=
- TALER_amount_add (&refunds,
- &refunds,
- amount_with_fee))
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ const struct TALER_Amount *amount_with_fee;
+
+ amount_with_fee = &tl->details.old_coin_recoup->value;
+ /* We count recoups of refreshed coins like refunds for the dirty old
+ coin, as they equivalently _increase_ the remaining value on the
+ _old_ coin */
+ TALER_ARL_amount_add (&refunds,
+ &refunds,
+ amount_with_fee);
+ break;
}
- break;
case TALER_EXCHANGEDB_TT_RECOUP:
- /* We count recoups of the coin as expenditures, as it
- equivalently decreases the remaining value of the recouped coin. */
- amount_with_fee = &tl->details.recoup->value;
- if (GNUNET_OK !=
- TALER_amount_add (&expenditures,
- &expenditures,
- amount_with_fee))
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ const struct TALER_Amount *amount_with_fee;
+
+ /* We count recoups of the coin as expenditures, as it
+ equivalently decreases the remaining value of the recouped coin. */
+ amount_with_fee = &tl->details.recoup->value;
+ TALER_ARL_amount_add (&expenditures,
+ &expenditures,
+ amount_with_fee);
+ break;
}
- break;
case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
- /* We count recoups of the coin as expenditures, as it
- equivalently decreases the remaining value of the recouped coin. */
- amount_with_fee = &tl->details.recoup_refresh->value;
- if (GNUNET_OK !=
- TALER_amount_add (&expenditures,
- &expenditures,
- amount_with_fee))
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ const struct TALER_Amount *amount_with_fee;
+
+ /* We count recoups of the coin as expenditures, as it
+ equivalently decreases the remaining value of the recouped coin. */
+ amount_with_fee = &tl->details.recoup_refresh->value;
+ TALER_ARL_amount_add (&expenditures,
+ &expenditures,
+ amount_with_fee);
+ break;
}
- break;
- }
+ case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
+ {
+ const struct TALER_Amount *amount_with_fee;
+
+ amount_with_fee = &tl->details.purse_deposit->amount;
+ if (! tl->details.purse_deposit->refunded)
+ TALER_ARL_amount_add (&expenditures,
+ &expenditures,
+ amount_with_fee);
+ break;
+ }
+
+ case TALER_EXCHANGEDB_TT_PURSE_REFUND:
+ {
+ const struct TALER_Amount *amount_with_fee;
+
+ amount_with_fee = &tl->details.purse_refund->refund_amount;
+ fee_claimed = &tl->details.purse_refund->refund_fee;
+ TALER_ARL_amount_add (&refunds,
+ &refunds,
+ amount_with_fee);
+ TALER_ARL_amount_add (&expenditures,
+ &expenditures,
+ fee_claimed);
+ /* Check that the fees given in the transaction list and in dki match */
+ if (0 !=
+ TALER_amount_cmp (&issue->fees.refund,
+ fee_claimed))
+ {
+ /* Disagreement in fee structure between exchange and auditor! */
+ report_amount_arithmetic_inconsistency ("refund fee",
+ 0,
+ fee_claimed,
+ &issue->fees.refund,
+ 1);
+ }
+ break;
+ }
+
+ case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
+ {
+ const struct TALER_Amount *amount_with_fee;
+
+ amount_with_fee = &tl->details.reserve_open->coin_contribution;
+ TALER_ARL_amount_add (&expenditures,
+ &expenditures,
+ amount_with_fee);
+ break;
+ }
+ } /* switch (tl->type) */
} /* for 'tl' */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -649,27 +645,26 @@ check_transaction_history_for_deposit (
"Aggregation loss due to refunds is %s\n",
TALER_amount2s (&merchant_loss));
*deposit_gain = *merchant_gain;
- if ( (GNUNET_YES == refund_deposit_fee) &&
- (NULL != deposit_fee) )
+ if ( (NULL != deposited) &&
+ (NULL != deposit_fee) &&
+ (0 == TALER_amount_cmp (&refunds,
+ deposited)) )
{
- /* We had a /deposit operation AND a /refund operation,
- and should thus not charge the merchant the /deposit fee */
- if (GNUNET_OK !=
- TALER_amount_add (merchant_gain,
+ /* We had a /deposit operation AND /refund operations adding up to the
+ total deposited value including deposit fee. Thus, we should not
+ subtract the /deposit fee from the merchant gain (as it was also
+ refunded). */
+ TALER_ARL_amount_add (merchant_gain,
merchant_gain,
- deposit_fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
+ deposit_fee);
}
{
struct TALER_Amount final_gain;
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&final_gain,
- merchant_gain,
- &merchant_loss))
+ if (TALER_ARL_SR_INVALID_NEGATIVE ==
+ TALER_ARL_amount_subtract_neg (&final_gain,
+ merchant_gain,
+ &merchant_loss))
{
/* refunds above deposits? Bad! */
report_coin_arithmetic_inconsistency ("refund (merchant)",
@@ -681,7 +676,7 @@ check_transaction_history_for_deposit (
as a NEGATIVE contribution as that is not allowed; so
let's count it as zero as that's the best we can do. */
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
merchant_gain));
}
else
@@ -696,10 +691,10 @@ check_transaction_history_for_deposit (
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Subtracting refunds of %s from coin value loss\n",
TALER_amount2s (&refunds));
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&spent,
- &expenditures,
- &refunds))
+ if (TALER_ARL_SR_INVALID_NEGATIVE ==
+ TALER_ARL_amount_subtract_neg (&spent,
+ &expenditures,
+ &refunds))
{
/* refunds above expenditures? Bad! */
report_coin_arithmetic_inconsistency ("refund (balance)",
@@ -711,18 +706,14 @@ check_transaction_history_for_deposit (
else
{
/* Now check that 'spent' is less or equal than the total coin value */
- struct TALER_Amount value;
-
- TALER_amount_ntoh (&value,
- &issue->value);
if (1 == TALER_amount_cmp (&spent,
- &value))
+ &issue->value))
{
/* spent > value */
report_coin_arithmetic_inconsistency ("spend",
coin_pub,
&spent,
- &value,
+ &issue->value,
-1);
}
}
@@ -735,7 +726,7 @@ check_transaction_history_for_deposit (
"Coin %s contributes %s to contract %s\n",
TALER_B2S (coin_pub),
TALER_amount2s (merchant_gain),
- GNUNET_h2s (h_contract_terms));
+ GNUNET_h2s (&h_contract_terms->hash));
return GNUNET_OK;
}
@@ -747,8 +738,8 @@ check_transaction_history_for_deposit (
* @param[in,out] cls a `struct WireCheckContext`
* @param rowid which row in the table is the information from (for diagnostics)
* @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls)
- * @param h_wire hash of wire transfer details of the merchant (should be same for all callbacks with the same @e cls)
- * @param account_details where did we transfer the funds?
+ * @param account_pay_uri where did we transfer the funds?
+ * @param h_payto hash over @a account_payto_uri as it is in the DB
* @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls)
* @param h_contract_terms which proposal was this payment about
* @param denom_pub denomination of @a coin_pub
@@ -763,47 +754,53 @@ wire_transfer_information_cb (
void *cls,
uint64_t rowid,
const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_wire,
- const json_t *account_details,
- struct GNUNET_TIME_Absolute exec_time,
- const struct GNUNET_HashCode *h_contract_terms,
+ const char *account_pay_uri,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp exec_time,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *coin_value,
const struct TALER_Amount *deposit_fee)
{
struct WireCheckContext *wcc = cls;
- const struct TALER_DenominationKeyValidityPS *issue;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
struct TALER_Amount computed_value;
struct TALER_Amount total_deposit_without_refunds;
struct TALER_EXCHANGEDB_TransactionList *tl;
struct TALER_CoinPublicInfo coin;
enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_HashCode hw;
-
- if (GNUNET_OK !=
- TALER_JSON_merchant_wire_signature_hash (account_details,
- &hw))
+ struct TALER_PaytoHashP hpt;
+ uint64_t etag_out;
+
+ TALER_payto_hash (account_pay_uri,
+ &hpt);
+ if (0 !=
+ GNUNET_memcmp (&hpt,
+ h_payto))
{
- report_row_inconsistency ("aggregation",
+ report_row_inconsistency ("wire_targets",
rowid,
- "failed to compute hash of given wire data");
+ "h-payto does not match payto URI");
}
- else if (0 !=
- GNUNET_memcmp (&hw,
- h_wire))
+ /* Obtain coin's transaction history */
+ /* TODO: could use 'start' mechanism to only fetch transactions
+ we did not yet process, instead of going over them
+ again and again.*/
+
{
- report_row_inconsistency ("aggregation",
- rowid,
- "database contains wrong hash code for wire details");
+ struct TALER_Amount balance;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ qs = TALER_ARL_edb->get_coin_transactions (TALER_ARL_edb->cls,
+ coin_pub,
+ 0,
+ 0,
+ &etag_out,
+ &balance,
+ &h_denom_pub,
+ &tl);
}
-
- /* Obtain coin's transaction history */
- qs = TALER_ARL_edb->get_coin_transactions (TALER_ARL_edb->cls,
- TALER_ARL_esession,
- coin_pub,
- GNUNET_YES,
- &tl);
if ( (qs < 0) ||
(NULL == tl) )
{
@@ -814,7 +811,6 @@ wire_transfer_information_cb (
return;
}
qs = TALER_ARL_edb->get_known_coin (TALER_ARL_edb->cls,
- TALER_ARL_esession,
coin_pub,
&coin);
if (qs <= 0)
@@ -834,7 +830,7 @@ wire_transfer_information_cb (
&issue);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
- GNUNET_CRYPTO_rsa_signature_free (coin.denom_sig.rsa_signature);
+ TALER_denom_sig_free (&coin.denom_sig);
TALER_ARL_edb->free_coin_transaction_list (TALER_ARL_edb->cls,
tl);
if (0 == qs)
@@ -853,17 +849,19 @@ wire_transfer_information_cb (
denom_pub))
{
TALER_ARL_report (report_bad_sig_losses,
- json_pack ("{s:s, s:I, s:o, s:o}",
- "operation", "wire",
- "row", (json_int_t) rowid,
- "loss", TALER_JSON_from_amount (coin_value),
- "coin_pub", GNUNET_JSON_from_data_auto (
- &coin.coin_pub)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
- coin_value));
- GNUNET_CRYPTO_rsa_signature_free (coin.denom_sig.rsa_signature);
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "wire"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ coin_value),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &coin.coin_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ coin_value);
+ TALER_denom_sig_free (&coin.denom_sig);
TALER_ARL_edb->free_coin_transaction_list (TALER_ARL_edb->cls,
tl);
report_row_inconsistency ("deposit",
@@ -871,8 +869,7 @@ wire_transfer_information_cb (
"coin denomination signature invalid");
return;
}
- GNUNET_CRYPTO_rsa_signature_free (coin.denom_sig.rsa_signature);
- coin.denom_sig.rsa_signature = NULL; /* just to be sure */
+ TALER_denom_sig_free (&coin.denom_sig);
GNUNET_assert (NULL != issue); /* mostly to help static analysis */
/* Check transaction history to see if it supports aggregate
valuation */
@@ -898,10 +895,10 @@ wire_transfer_information_cb (
{
struct TALER_Amount coin_value_without_fee;
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&coin_value_without_fee,
- coin_value,
- deposit_fee))
+ if (TALER_ARL_SR_INVALID_NEGATIVE ==
+ TALER_ARL_amount_subtract_neg (&coin_value_without_fee,
+ coin_value,
+ deposit_fee))
{
wcc->qs = GNUNET_DB_STATUS_HARD_ERROR;
report_amount_arithmetic_inconsistency (
@@ -923,20 +920,21 @@ wire_transfer_information_cb (
"aggregation (contribution)",
rowid,
&coin_value_without_fee,
- &
- total_deposit_without_refunds,
+ &total_deposit_without_refunds,
-1);
}
}
/* Check other details of wire transfer match */
- if (0 != GNUNET_memcmp (h_wire,
- &wcc->h_wire))
+ if (0 != strcmp (account_pay_uri,
+ wcc->payto_uri))
{
report_row_inconsistency ("aggregation",
rowid,
"target of outgoing wire transfer do not match hash of wire from deposit");
}
- if (exec_time.abs_value_us != wcc->date.abs_value_us)
+ if (GNUNET_TIME_timestamp_cmp (exec_time,
+ !=,
+ wcc->date) )
{
/* This should be impossible from database constraints */
GNUNET_break (0);
@@ -949,15 +947,9 @@ wire_transfer_information_cb (
{
struct TALER_Amount res;
- if (GNUNET_OK !=
- TALER_amount_add (&res,
+ TALER_ARL_amount_add (&res,
&wcc->total_deposits,
- &computed_value))
- {
- GNUNET_break (0);
- wcc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
+ &computed_value);
wcc->total_deposits = res;
}
}
@@ -974,7 +966,7 @@ wire_transfer_information_cb (
static const struct TALER_Amount *
get_wire_fee (struct AggregationContext *ac,
const char *method,
- struct GNUNET_TIME_Absolute timestamp)
+ struct GNUNET_TIME_Timestamp timestamp)
{
struct WireFeeInfo *wfi;
struct WireFeeInfo *pos;
@@ -983,10 +975,16 @@ get_wire_fee (struct AggregationContext *ac,
/* Check if fee is already loaded in cache */
for (pos = ac->fee_head; NULL != pos; pos = pos->next)
{
- if ( (pos->start_date.abs_value_us <= timestamp.abs_value_us) &&
- (pos->end_date.abs_value_us > timestamp.abs_value_us) )
- return &pos->wire_fee;
- if (pos->start_date.abs_value_us > timestamp.abs_value_us)
+ if (GNUNET_TIME_timestamp_cmp (pos->start_date,
+ <=,
+ timestamp) &&
+ GNUNET_TIME_timestamp_cmp (pos->end_date,
+ >,
+ timestamp) )
+ return &pos->fees.wire;
+ if (GNUNET_TIME_timestamp_cmp (pos->start_date,
+ >,
+ timestamp))
break;
}
@@ -994,13 +992,11 @@ get_wire_fee (struct AggregationContext *ac,
wfi = GNUNET_new (struct WireFeeInfo);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
TALER_ARL_edb->get_wire_fee (TALER_ARL_edb->cls,
- TALER_ARL_esession,
method,
timestamp,
&wfi->start_date,
&wfi->end_date,
- &wfi->wire_fee,
- &wfi->closing_fee,
+ &wfi->fees,
&master_sig))
{
GNUNET_break (0);
@@ -1012,28 +1008,17 @@ get_wire_fee (struct AggregationContext *ac,
easily make this one up, but it means that we have proof that the master
key was used for inconsistent wire fees if a merchant complains.) */
{
- struct TALER_MasterWireFeePS wf = {
- .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_FEES),
- .purpose.size = htonl (sizeof (wf)),
- .start_date = GNUNET_TIME_absolute_hton (wfi->start_date),
- .end_date = GNUNET_TIME_absolute_hton (wfi->end_date)
- };
-
- GNUNET_CRYPTO_hash (method,
- strlen (method) + 1,
- &wf.h_wire_method);
- TALER_amount_hton (&wf.wire_fee,
- &wfi->wire_fee);
- TALER_amount_hton (&wf.closing_fee,
- &wfi->closing_fee);
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_FEES,
- &wf.purpose,
- &master_sig.eddsa_signature,
- &TALER_ARL_master_pub.eddsa_pub))
+ TALER_exchange_offline_wire_fee_verify (
+ method,
+ wfi->start_date,
+ wfi->end_date,
+ &wfi->fees,
+ &TALER_ARL_master_pub,
+ &master_sig))
{
report_row_inconsistency ("wire-fee",
- timestamp.abs_value_us,
+ timestamp.abs_time.abs_value_us,
"wire fee signature invalid at given time");
}
}
@@ -1041,8 +1026,8 @@ get_wire_fee (struct AggregationContext *ac,
/* Established fee, keep in sorted list */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Wire fee is %s starting at %s\n",
- TALER_amount2s (&wfi->wire_fee),
- GNUNET_STRINGS_absolute_time_to_string (wfi->start_date));
+ TALER_amount2s (&wfi->fees.wire),
+ GNUNET_TIME_timestamp2s (wfi->start_date));
if ( (NULL == pos) ||
(NULL == pos->prev) )
GNUNET_CONTAINER_DLL_insert (ac->fee_head,
@@ -1055,28 +1040,34 @@ get_wire_fee (struct AggregationContext *ac,
wfi);
/* Check non-overlaping fee invariant */
if ( (NULL != wfi->prev) &&
- (wfi->prev->end_date.abs_value_us > wfi->start_date.abs_value_us) )
+ GNUNET_TIME_timestamp_cmp (wfi->prev->end_date,
+ >,
+ wfi->start_date) )
{
TALER_ARL_report (report_fee_time_inconsistencies,
- json_pack ("{s:s, s:s, s:o}",
- "type", method,
- "diagnostic",
- "start date before previous end date",
- "time", TALER_ARL_json_from_time_abs (
- wfi->start_date)));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ method),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "start date before previous end date"),
+ TALER_JSON_pack_time_abs_human ("time",
+ wfi->start_date.abs_time)));
}
if ( (NULL != wfi->next) &&
- (wfi->next->start_date.abs_value_us >= wfi->end_date.abs_value_us) )
+ GNUNET_TIME_timestamp_cmp (wfi->next->start_date,
+ >=,
+ wfi->end_date) )
{
TALER_ARL_report (report_fee_time_inconsistencies,
- json_pack ("{s:s, s:s, s:o}",
- "type", method,
- "diagnostic",
- "end date date after next start date",
- "time", TALER_ARL_json_from_time_abs (
- wfi->end_date)));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ method),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "end date date after next start date"),
+ TALER_JSON_pack_time_abs_human ("time",
+ wfi->end_date.abs_time)));
}
- return &wfi->wire_fee;
+ return &wfi->fees.wire;
}
@@ -1088,16 +1079,16 @@ get_wire_fee (struct AggregationContext *ac,
* @param rowid identifier of the respective row in the database
* @param date timestamp of the wire transfer (roughly)
* @param wtid wire transfer subject
- * @param wire wire transfer details of the receiver
+ * @param payto_uri bank account details of the receiver
* @param amount amount that was wired
* @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration
*/
-static int
+static enum GNUNET_GenericReturnValue
check_wire_out_cb (void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute date,
+ struct GNUNET_TIME_Timestamp date,
const struct TALER_WireTransferIdentifierRawP *wtid,
- const json_t *wire,
+ const char *payto_uri,
const struct TALER_Amount *amount)
{
struct AggregationContext *ac = cls;
@@ -1108,15 +1099,16 @@ check_wire_out_cb (void *cls,
char *method;
/* should be monotonically increasing */
- GNUNET_assert (rowid >= ppa.last_wire_out_serial_id);
- ppa.last_wire_out_serial_id = rowid + 1;
+ GNUNET_assert (rowid >=
+ TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id));
+ TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id) = rowid + 1;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Checking wire transfer %s over %s performed on %s\n",
TALER_B2S (wtid),
TALER_amount2s (amount),
- GNUNET_STRINGS_absolute_time_to_string (date));
- if (NULL == (method = TALER_JSON_wire_to_method (wire)))
+ GNUNET_TIME_timestamp2s (date));
+ if (NULL == (method = TALER_payto_get_method (payto_uri)))
{
report_row_inconsistency ("wire_out",
rowid,
@@ -1128,18 +1120,10 @@ check_wire_out_cb (void *cls,
wcc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
wcc.date = date;
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (amount->currency,
+ TALER_amount_set_zero (amount->currency,
&wcc.total_deposits));
- if (GNUNET_OK !=
- TALER_JSON_merchant_wire_signature_hash (wire,
- &wcc.h_wire))
- {
- GNUNET_break (0);
- GNUNET_free (method);
- return GNUNET_SYSERR;
- }
+ wcc.payto_uri = payto_uri;
qs = TALER_ARL_edb->lookup_wire_transfer (TALER_ARL_edb->cls,
- TALER_ARL_esession,
wtid,
&wire_transfer_information_cb,
&wcc);
@@ -1156,7 +1140,7 @@ check_wire_out_cb (void *cls,
in #wire_transfer_information_cb, so here we
only log for debugging */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Inconsitency for wire_out %llu (WTID %s) detected\n",
+ "Inconsistency for wire_out %llu (WTID %s) detected\n",
(unsigned long long) rowid,
TALER_B2S (wtid));
}
@@ -1172,15 +1156,15 @@ check_wire_out_cb (void *cls,
if (NULL == wire_fee)
{
report_row_inconsistency ("wire-fee",
- date.abs_value_us,
+ date.abs_time.abs_value_us,
"wire fee unavailable for given time");
/* If fee is unknown, we just assume the fee is zero */
final_amount = wcc.total_deposits;
}
- else if (GNUNET_SYSERR ==
- TALER_amount_subtract (&final_amount,
- &wcc.total_deposits,
- wire_fee))
+ else if (TALER_ARL_SR_INVALID_NEGATIVE ==
+ TALER_ARL_amount_subtract_neg (&final_amount,
+ &wcc.total_deposits,
+ wire_fee))
{
report_amount_arithmetic_inconsistency (
"wire out (fee structure)",
@@ -1200,26 +1184,13 @@ check_wire_out_cb (void *cls,
&TALER_ARL_currency_round_unit));
/* Calculate the exchange's gain as the fees plus rounding differences! */
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&exchange_gain,
+ TALER_ARL_amount_subtract (&exchange_gain,
&wcc.total_deposits,
- &final_amount))
- {
- GNUNET_break (0);
- ac->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return GNUNET_SYSERR;
- }
-
+ &final_amount);
/* Sum up aggregation fees (we simply include the rounding gains) */
- if (GNUNET_OK !=
- TALER_amount_add (&total_aggregation_fee_income,
- &total_aggregation_fee_income,
- &exchange_gain))
- {
- GNUNET_break (0);
- ac->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return GNUNET_SYSERR;
- }
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue),
+ &TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue),
+ &exchange_gain);
/* Check that calculated amount matches actual amount */
if (0 != TALER_amount_cmp (amount,
@@ -1231,41 +1202,43 @@ check_wire_out_cb (void *cls,
&final_amount))
{
/* amount > final_amount */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- amount,
- &final_amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_wire_out_delta_plus,
- &total_wire_out_delta_plus,
- &delta));
+ TALER_ARL_amount_subtract (&delta,
+ amount,
+ &final_amount);
+ TALER_ARL_amount_add (&total_wire_out_delta_plus,
+ &total_wire_out_delta_plus,
+ &delta);
}
else
{
/* amount < final_amount */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- &final_amount,
- amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_wire_out_delta_minus,
- &total_wire_out_delta_minus,
- &delta));
+ TALER_ARL_amount_subtract (&delta,
+ &final_amount,
+ amount);
+ TALER_ARL_amount_add (&total_wire_out_delta_minus,
+ &total_wire_out_delta_minus,
+ &delta);
}
TALER_ARL_report (report_wire_out_inconsistencies,
- json_pack ("{s:O, s:I, s:o, s:o}",
- "destination_account", wire,
- "rowid", (json_int_t) rowid,
- "expected",
- TALER_JSON_from_amount (&final_amount),
- "claimed",
- TALER_JSON_from_amount (amount)));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("destination_account",
+ payto_uri),
+ GNUNET_JSON_pack_uint64 ("rowid",
+ rowid),
+ TALER_JSON_pack_amount ("expected",
+ &final_amount),
+ TALER_JSON_pack_amount ("claimed",
+ amount)));
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Aggregation unit %s is OK\n",
TALER_B2S (wtid));
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
@@ -1288,10 +1261,10 @@ analyze_aggregations (void *cls)
(void) cls;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Analyzing aggregations\n");
- qsp = TALER_ARL_adb->get_auditor_progress_aggregation (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &ppa);
+ qsp = TALER_ARL_adb->get_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_PP (aggregation_last_wire_out_serial_id),
+ NULL);
if (0 > qsp)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsp);
@@ -1304,19 +1277,19 @@ analyze_aggregations (void *cls)
}
else
{
- ppa_start = ppa;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Resuming aggregation audit at %llu\n",
- (unsigned long long) ppa.last_wire_out_serial_id);
+ (unsigned long long) TALER_ARL_USE_PP (
+ aggregation_last_wire_out_serial_id));
}
memset (&ac,
0,
sizeof (ac));
- qsx = TALER_ARL_adb->get_wire_fee_summary (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &total_aggregation_fee_income);
+ qsx = TALER_ARL_adb->get_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_AB (aggregation_total_wire_fee_revenue),
+ NULL);
if (0 > qsx)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx);
@@ -1325,8 +1298,7 @@ analyze_aggregations (void *cls)
ac.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
qs = TALER_ARL_edb->select_wire_out_above_serial_id (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- ppa.last_wire_out_serial_id,
+ TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id),
&check_wire_out_cb,
&ac);
if (0 > qs)
@@ -1352,34 +1324,30 @@ analyze_aggregations (void *cls)
return ac.qs;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsx)
- ac.qs = TALER_ARL_adb->insert_wire_fee_summary (
+ ac.qs = TALER_ARL_adb->insert_balance (
TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &total_aggregation_fee_income);
+ TALER_ARL_SET_AB (aggregation_total_wire_fee_revenue),
+ NULL);
else
- ac.qs = TALER_ARL_adb->update_wire_fee_summary (
+ ac.qs = TALER_ARL_adb->update_balance (
TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &total_aggregation_fee_income);
+ TALER_ARL_SET_AB (aggregation_total_wire_fee_revenue),
+ NULL);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ac.qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == ac.qs);
return ac.qs;
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp)
- qs = TALER_ARL_adb->update_auditor_progress_aggregation (
+ qs = TALER_ARL_adb->update_auditor_progress (
TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &ppa);
+ TALER_ARL_SET_PP (aggregation_last_wire_out_serial_id),
+ NULL);
else
- qs = TALER_ARL_adb->insert_auditor_progress_aggregation (
+ qs = TALER_ARL_adb->insert_auditor_progress (
TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &ppa);
+ TALER_ARL_SET_PP (aggregation_last_wire_out_serial_id),
+ NULL);
if (0 >= qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -1389,7 +1357,8 @@ analyze_aggregations (void *cls)
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Concluded aggregation audit step at %llu\n",
- (unsigned long long) ppa.last_wire_out_serial_id);
+ (unsigned long long) TALER_ARL_USE_PP (
+ aggregation_last_wire_out_serial_id));
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
@@ -1409,8 +1378,6 @@ run (void *cls,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *c)
{
- json_t *report;
-
(void) cls;
(void) args;
(void) cfgfile;
@@ -1419,34 +1386,35 @@ run (void *cls,
if (GNUNET_OK !=
TALER_ARL_init (c))
{
- global_ret = 1;
+ global_ret = EXIT_FAILURE;
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Starting audit\n");
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_aggregation_fee_income));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ aggregation_total_wire_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_wire_out_delta_plus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_wire_out_delta_minus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_arithmetic_delta_plus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_arithmetic_delta_minus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_coin_delta_plus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_coin_delta_minus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_bad_sig_loss));
GNUNET_assert (NULL !=
(report_row_inconsistencies
@@ -1473,71 +1441,71 @@ run (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Audit failed\n");
TALER_ARL_done (NULL);
- global_ret = 1;
+ global_ret = EXIT_FAILURE;
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Audit complete\n");
- report = json_pack ("{s:o, s:o, s:o, s:o, s:o,"
- " s:o, s:o, s:o, s:o, s:o,"
- " s:o, s:o, s:o, s:I, s:I,"
- " s:o, s:o, s:o }",
- /* blocks #1 */
+ TALER_ARL_done (GNUNET_JSON_PACK (
+ /* blocks #1 */
+ GNUNET_JSON_pack_array_steal (
"wire_out_inconsistencies",
- report_wire_out_inconsistencies,
- /* Tested in test-auditor.sh #23 */
+ report_wire_out_inconsistencies),
+ /* Tested in test-auditor.sh #23 */
+ TALER_JSON_pack_amount (
"total_wire_out_delta_plus",
- TALER_JSON_from_amount (
- &total_wire_out_delta_plus),
- /* Tested in test-auditor.sh #23 */
+ &total_wire_out_delta_plus),
+ /* Tested in test-auditor.sh #23 */
+ TALER_JSON_pack_amount (
"total_wire_out_delta_minus",
- TALER_JSON_from_amount (
- &total_wire_out_delta_minus),
- /* Tested in test-auditor.sh #28/32 */
- "bad_sig_losses",
- report_bad_sig_losses,
- /* Tested in test-auditor.sh #28/32 */
- "total_bad_sig_loss",
- TALER_JSON_from_amount (&total_bad_sig_loss),
- /* block #2 */
- /* Tested in test-auditor.sh #15 */
+ &total_wire_out_delta_minus),
+ /* Tested in test-auditor.sh #28/32 */
+ GNUNET_JSON_pack_array_steal ("bad_sig_losses",
+ report_bad_sig_losses),
+ /* Tested in test-auditor.sh #28/32 */
+ TALER_JSON_pack_amount ("total_bad_sig_loss",
+ &total_bad_sig_loss),
+ /* block #2 */
+ /* Tested in test-auditor.sh #15 */
+ GNUNET_JSON_pack_array_steal (
"row_inconsistencies",
- report_row_inconsistencies,
+ report_row_inconsistencies),
+ GNUNET_JSON_pack_array_steal (
"coin_inconsistencies",
- report_coin_inconsistencies,
- "total_coin_delta_plus",
- TALER_JSON_from_amount (&total_coin_delta_plus),
- "total_coin_delta_minus",
- TALER_JSON_from_amount (
- &total_coin_delta_minus),
+ report_coin_inconsistencies),
+ TALER_JSON_pack_amount ("total_coin_delta_plus",
+ &total_coin_delta_plus),
+ TALER_JSON_pack_amount ("total_coin_delta_minus",
+ &total_coin_delta_minus),
+ GNUNET_JSON_pack_array_steal (
"amount_arithmetic_inconsistencies",
- report_amount_arithmetic_inconsistencies,
- /* block #3 */
+ report_amount_arithmetic_inconsistencies),
+ /* block #3 */
+ TALER_JSON_pack_amount (
"total_arithmetic_delta_plus",
- TALER_JSON_from_amount (
- &total_arithmetic_delta_plus),
+ &total_arithmetic_delta_plus),
+ TALER_JSON_pack_amount (
"total_arithmetic_delta_minus",
- TALER_JSON_from_amount (
- &total_arithmetic_delta_minus),
- "total_aggregation_fee_income",
- TALER_JSON_from_amount (
- &total_aggregation_fee_income),
+ &total_arithmetic_delta_minus),
+ TALER_JSON_pack_amount (
+ "aggregation_total_wire_fee_revenue",
+ &TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue)),
+ GNUNET_JSON_pack_uint64 (
"start_ppa_wire_out_serial_id",
- (json_int_t) ppa_start.last_wire_out_serial_id,
+ 0 /* defunct */),
+ GNUNET_JSON_pack_uint64 (
"end_ppa_wire_out_serial_id",
- (json_int_t) ppa.last_wire_out_serial_id,
- /* block #4 */
+ TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id)),
+ /* block #4 */
+ TALER_JSON_pack_time_abs_human (
"auditor_start_time",
- TALER_ARL_json_from_time_abs (
- start_time),
+ start_time),
+ TALER_JSON_pack_time_abs_human (
"auditor_end_time",
- TALER_ARL_json_from_time_abs (
- GNUNET_TIME_absolute_get ()),
+ GNUNET_TIME_absolute_get ()),
+ GNUNET_JSON_pack_array_steal (
"wire_fee_time_inconsistencies",
- report_fee_time_inconsistencies
- );
- GNUNET_break (NULL != report);
- TALER_ARL_done (report);
+ report_fee_time_inconsistencies)));
}
@@ -1553,33 +1521,41 @@ main (int argc,
char *const *argv)
{
const struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_base32_auto ('m',
- "exchange-key",
- "KEY",
- "public key of the exchange (Crockford base32 encoded)",
- &TALER_ARL_master_pub),
+ GNUNET_GETOPT_option_flag ('i',
+ "internal",
+ "perform checks only applicable for exchange-internal audits",
+ &internal_checks),
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue 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 */
(void) TALER_project_data_default ();
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-helper-auditor-aggregation",
- "MESSAGE",
- NULL));
if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc,
- argv,
- "taler-helper-auditor-aggregation",
- "Audit Taler exchange aggregation activity",
- options,
- &run,
- NULL))
- return 1;
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ ret = GNUNET_PROGRAM_run (
+ argc,
+ argv,
+ "taler-helper-auditor-aggregation",
+ gettext_noop ("Audit Taler exchange aggregation activity"),
+ options,
+ &run,
+ NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
return global_ret;
}
diff --git a/src/auditor/taler-helper-auditor-coins.c b/src/auditor/taler-helper-auditor-coins.c
index fb50c8dbf..f88f39eaf 100644
--- a/src/auditor/taler-helper-auditor-coins.c
+++ b/src/auditor/taler-helper-auditor-coins.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2016-2020 Taler Systems SA
+ Copyright (C) 2016-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero Public License as published by the Free Software
@@ -17,9 +17,6 @@
* @file auditor/taler-helper-auditor-coins.c
* @brief audits coins in an exchange database.
* @author Christian Grothoff
- *
- * UNDECIDED:
- * - do we care about checking the 'done' flag in deposit_cb?
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
@@ -48,14 +45,37 @@
static int global_ret;
/**
- * Checkpointing our progress for coins.
+ * Run in test mode. Exit when idle instead of
+ * going to sleep and waiting for more work.
+ *
+ * FIXME: not yet implemented!
*/
-static struct TALER_AUDITORDB_ProgressPointCoin ppc;
+static int test_mode;
/**
* Checkpointing our progress for coins.
*/
-static struct TALER_AUDITORDB_ProgressPointCoin ppc_start;
+static TALER_ARL_DEF_PP (coins_withdraw_serial_id);
+static TALER_ARL_DEF_PP (coins_deposit_serial_id);
+static TALER_ARL_DEF_PP (coins_melt_serial_id);
+static TALER_ARL_DEF_PP (coins_refund_serial_id);
+static TALER_ARL_DEF_PP (coins_recoup_serial_id);
+static TALER_ARL_DEF_PP (coins_recoup_refresh_serial_id);
+static TALER_ARL_DEF_PP (coins_purse_deposits_serial_id);
+static TALER_ARL_DEF_PP (coins_purse_refunds_serial_id);
+
+
+/**
+ * Global coin balance sheet (for coins).
+ */
+static TALER_ARL_DEF_AB (coin_balance_risk);
+static TALER_ARL_DEF_AB (total_escrowed);
+static TALER_ARL_DEF_AB (coin_irregular_loss);
+static TALER_ARL_DEF_AB (coin_melt_fee_revenue);
+static TALER_ARL_DEF_AB (coin_deposit_fee_revenue);
+static TALER_ARL_DEF_AB (coin_refund_fee_revenue);
+static TALER_ARL_DEF_AB (total_recoup_loss);
+
/**
* Array of reports about denomination keys with an
@@ -70,11 +90,16 @@ static json_t *report_emergencies;
static json_t *report_emergencies_by_count;
/**
- * Array of reports about row inconsitencies.
+ * Array of reports about row inconsistencies.
*/
static json_t *report_row_inconsistencies;
/**
+ * Array of reports about denominations not counter-signed by the auditor.
+ */
+static json_t *report_denominations_without_sigs;
+
+/**
* Report about amount calculation differences (causing profit
* or loss at the exchange).
*/
@@ -110,40 +135,6 @@ static struct TALER_Amount reported_emergency_loss;
*/
static struct TALER_Amount reported_emergency_loss_by_count;
-/**
- * Expected balance in the escrow account.
- */
-static struct TALER_Amount total_escrow_balance;
-
-/**
- * Active risk exposure.
- */
-static struct TALER_Amount total_risk;
-
-/**
- * Actualized risk (= loss) from recoups.
- */
-static struct TALER_Amount total_recoup_loss;
-
-/**
- * Recoups we made on denominations that were not revoked (!?).
- */
-static struct TALER_Amount total_irregular_recoups;
-
-/**
- * Total deposit fees earned.
- */
-static struct TALER_Amount total_deposit_fee_income;
-
-/**
- * Total melt fees earned.
- */
-static struct TALER_Amount total_melt_fee_income;
-
-/**
- * Total refund fees earned.
- */
-static struct TALER_Amount total_refund_fee_income;
/**
* Array of reports about coin operations with bad signatures.
@@ -151,15 +142,10 @@ static struct TALER_Amount total_refund_fee_income;
static json_t *report_bad_sig_losses;
/**
- * Total amount lost by operations for which signatures were invalid.
- */
-static struct TALER_Amount total_bad_sig_loss;
-
-/**
* Array of refresh transactions where the /refresh/reveal has not yet
* happened (and may of course never happen).
*/
-static json_t *report_refreshs_hanging;
+static json_t *report_refreshes_hanging;
/**
* Total amount lost by operations for which signatures were invalid.
@@ -188,6 +174,11 @@ struct CoinHistory
*/
static struct CoinHistory coin_histories[MAX_COIN_HISTORIES];
+/**
+ * Should we run checks that only work for exchange-internal audits?
+ */
+static int internal_checks;
+
/**
* Return the index we should use for @a coin_pub in #coin_histories.
@@ -200,9 +191,9 @@ coin_history_index (const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
uint32_t i;
- memcpy (&i,
- coin_pub,
- sizeof (i));
+ GNUNET_memcpy (&i,
+ coin_pub,
+ sizeof (i));
return i % MAX_COIN_HISTORIES;
}
@@ -238,8 +229,9 @@ get_cached_history (const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
unsigned int i = coin_history_index (coin_pub);
- if (0 == GNUNET_memcmp (coin_pub,
- &coin_histories[i].coin_pub))
+ if (0 ==
+ GNUNET_memcmp (coin_pub,
+ &coin_histories[i].coin_pub))
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Found verification of %s in cache\n",
@@ -266,38 +258,35 @@ get_cached_history (const struct TALER_CoinSpendPublicKeyP *coin_pub)
*/
static void
report_emergency_by_amount (
- const struct TALER_DenominationKeyValidityPS *issue,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue,
const struct TALER_Amount *risk,
const struct TALER_Amount *loss)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Reporting emergency on denomination `%s' over loss of %s\n",
- GNUNET_h2s (&issue->denom_hash),
+ GNUNET_h2s (&issue->denom_hash.hash),
TALER_amount2s (loss));
- TALER_ARL_report (report_emergencies,
- json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}",
- "denompub_hash",
- GNUNET_JSON_from_data_auto (&issue->denom_hash),
- "denom_risk",
- TALER_JSON_from_amount (risk),
- "denom_loss",
- TALER_JSON_from_amount (loss),
- "start",
- TALER_ARL_json_from_time_abs_nbo (
- issue->start),
- "deposit_end",
- TALER_ARL_json_from_time_abs_nbo (
- issue->expire_deposit),
- "value",
- TALER_JSON_from_amount_nbo (&issue->value)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&reported_emergency_risk_by_amount,
- &reported_emergency_risk_by_amount,
- risk));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&reported_emergency_loss,
- &reported_emergency_loss,
- loss));
+ TALER_ARL_report (
+ report_emergencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denompub_hash",
+ &issue->denom_hash),
+ TALER_JSON_pack_amount ("denom_risk",
+ risk),
+ TALER_JSON_pack_amount ("denom_loss",
+ loss),
+ TALER_JSON_pack_time_abs_human ("start",
+ issue->start.abs_time),
+ TALER_JSON_pack_time_abs_human ("deposit_end",
+ issue->expire_deposit.abs_time),
+ TALER_JSON_pack_amount ("value",
+ &issue->value)));
+ TALER_ARL_amount_add (&reported_emergency_risk_by_amount,
+ &reported_emergency_risk_by_amount,
+ risk);
+ TALER_ARL_amount_add (&reported_emergency_loss,
+ &reported_emergency_loss,
+ loss);
}
@@ -317,43 +306,35 @@ report_emergency_by_amount (
*/
static void
report_emergency_by_count (
- const struct TALER_DenominationKeyValidityPS *issue,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue,
uint64_t num_issued,
uint64_t num_known,
const struct TALER_Amount *risk)
{
- struct TALER_Amount denom_value;
-
- TALER_ARL_report (report_emergencies_by_count,
- json_pack ("{s:o, s:I, s:I, s:o, s:o, s:o, s:o}",
- "denompub_hash",
- GNUNET_JSON_from_data_auto (&issue->denom_hash),
- "num_issued",
- (json_int_t) num_issued,
- "num_known",
- (json_int_t) num_known,
- "denom_risk",
- TALER_JSON_from_amount (risk),
- "start",
- TALER_ARL_json_from_time_abs_nbo (
- issue->start),
- "deposit_end",
- TALER_ARL_json_from_time_abs_nbo (
- issue->expire_deposit),
- "value",
- TALER_JSON_from_amount_nbo (&issue->value)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&reported_emergency_risk_by_count,
- &reported_emergency_risk_by_count,
- risk));
- TALER_amount_ntoh (&denom_value,
- &issue->value);
+ TALER_ARL_report (
+ report_emergencies_by_count,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denompub_hash",
+ &issue->denom_hash),
+ GNUNET_JSON_pack_uint64 ("num_issued",
+ num_issued),
+ GNUNET_JSON_pack_uint64 ("num_known",
+ num_known),
+ TALER_JSON_pack_amount ("denom_risk",
+ risk),
+ TALER_JSON_pack_time_abs_human ("start",
+ issue->start.abs_time),
+ TALER_JSON_pack_time_abs_human ("deposit_end",
+ issue->expire_deposit.abs_time),
+ TALER_JSON_pack_amount ("value",
+ &issue->value)));
+ TALER_ARL_amount_add (&reported_emergency_risk_by_count,
+ &reported_emergency_risk_by_count,
+ risk);
for (uint64_t i = num_issued; i<num_known; i++)
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (
- &reported_emergency_loss_by_count,
- &reported_emergency_loss_by_count,
- &denom_value));
+ TALER_ARL_amount_add (&reported_emergency_loss_by_count,
+ &reported_emergency_loss_by_count,
+ &issue->value);
}
@@ -363,7 +344,7 @@ report_emergency_by_count (
* respect to calculations involving amounts.
*
* @param operation what operation had the inconsistency
- * @param rowid affected row, UINT64_MAX if row is missing
+ * @param rowid affected row, 0 if row is missing
* @param exchange amount calculated by exchange
* @param auditor amount calculated by auditor
* @param profitable 1 if @a exchange being larger than @a auditor is
@@ -388,36 +369,38 @@ report_amount_arithmetic_inconsistency (
auditor))
{
/* exchange > auditor */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- exchange,
- auditor));
+ TALER_ARL_amount_subtract (&delta,
+ exchange,
+ auditor);
}
else
{
/* auditor < exchange */
profitable = -profitable;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- auditor,
- exchange));
+ TALER_ARL_amount_subtract (&delta,
+ auditor,
+ exchange);
}
TALER_ARL_report (report_amount_arithmetic_inconsistencies,
- json_pack ("{s:s, s:I, s:o, s:o, s:I}",
- "operation", operation,
- "rowid", (json_int_t) rowid,
- "exchange", TALER_JSON_from_amount (exchange),
- "auditor", TALER_JSON_from_amount (auditor),
- "profitable", (json_int_t) profitable));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ operation),
+ GNUNET_JSON_pack_uint64 ("rowid",
+ rowid),
+ TALER_JSON_pack_amount ("exchange",
+ exchange),
+ TALER_JSON_pack_amount ("auditor",
+ auditor),
+ GNUNET_JSON_pack_int64 ("profitable",
+ profitable)));
if (0 != profitable)
{
target = (1 == profitable)
? &total_arithmetic_delta_plus
: &total_arithmetic_delta_minus;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (target,
- target,
- &delta));
+ TALER_ARL_amount_add (target,
+ target,
+ &delta);
}
}
@@ -426,7 +409,7 @@ report_amount_arithmetic_inconsistency (
* Report a (serious) inconsistency in the exchange's database.
*
* @param table affected table
- * @param rowid affected row, UINT64_MAX if row is missing
+ * @param rowid affected row, 0 if row is missing
* @param diagnostic message explaining the problem
*/
static void
@@ -435,10 +418,13 @@ report_row_inconsistency (const char *table,
const char *diagnostic)
{
TALER_ARL_report (report_row_inconsistencies,
- json_pack ("{s:s, s:I, s:s}",
- "table", table,
- "row", (json_int_t) rowid,
- "diagnostic", diagnostic));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ table),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ GNUNET_JSON_pack_string ("diagnostic",
+ diagnostic)));
}
@@ -467,26 +453,37 @@ check_coin_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
struct TALER_Amount spent;
struct TALER_Amount refunded;
struct TALER_Amount deposit_fee;
- int have_refund;
+ bool have_refund;
+ uint64_t etag_out;
+
+ /* TODO: could use 'etag' mechanism to only fetch transactions
+ we did not yet process, instead of going over them
+ again and again. */
+ {
+ struct TALER_Amount balance;
+ struct TALER_DenominationHashP h_denom_pub;
- qs = TALER_ARL_edb->get_coin_transactions (TALER_ARL_edb->cls,
- TALER_ARL_esession,
- coin_pub,
- GNUNET_YES,
- &tl);
+ qs = TALER_ARL_edb->get_coin_transactions (TALER_ARL_edb->cls,
+ coin_pub,
+ 0,
+ 0,
+ &etag_out,
+ &balance,
+ &h_denom_pub,
+ &tl);
+ }
if (0 >= qs)
return qs;
-
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (value->currency,
+ TALER_amount_set_zero (value->currency,
&refunded));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (value->currency,
+ TALER_amount_set_zero (value->currency,
&spent));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (value->currency,
+ TALER_amount_set_zero (value->currency,
&deposit_fee));
- have_refund = GNUNET_NO;
+ have_refund = false;
for (struct TALER_EXCHANGEDB_TransactionList *pos = tl;
NULL != pos;
pos = pos->next)
@@ -495,68 +492,79 @@ check_coin_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
{
case TALER_EXCHANGEDB_TT_DEPOSIT:
/* spent += pos->amount_with_fee */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&spent,
- &spent,
- &pos->details.deposit->amount_with_fee));
+ TALER_ARL_amount_add (&spent,
+ &spent,
+ &pos->details.deposit->amount_with_fee);
deposit_fee = pos->details.deposit->deposit_fee;
break;
case TALER_EXCHANGEDB_TT_MELT:
/* spent += pos->amount_with_fee */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&spent,
- &spent,
- &pos->details.melt->amount_with_fee));
+ TALER_ARL_amount_add (&spent,
+ &spent,
+ &pos->details.melt->amount_with_fee);
break;
case TALER_EXCHANGEDB_TT_REFUND:
/* refunded += pos->refund_amount - pos->refund_fee */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&refunded,
- &refunded,
- &pos->details.refund->refund_amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&spent,
- &spent,
- &pos->details.refund->refund_fee));
- have_refund = GNUNET_YES;
+ TALER_ARL_amount_add (&refunded,
+ &refunded,
+ &pos->details.refund->refund_amount);
+ TALER_ARL_amount_add (&spent,
+ &spent,
+ &pos->details.refund->refund_fee);
+ have_refund = true;
break;
case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
/* refunded += pos->value */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&refunded,
- &refunded,
- &pos->details.old_coin_recoup->value));
+ TALER_ARL_amount_add (&refunded,
+ &refunded,
+ &pos->details.old_coin_recoup->value);
break;
case TALER_EXCHANGEDB_TT_RECOUP:
/* spent += pos->value */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&spent,
- &spent,
- &pos->details.recoup->value));
+ TALER_ARL_amount_add (&spent,
+ &spent,
+ &pos->details.recoup->value);
break;
case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
/* spent += pos->value */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&spent,
- &spent,
- &pos->details.recoup_refresh->value));
+ TALER_ARL_amount_add (&spent,
+ &spent,
+ &pos->details.recoup_refresh->value);
break;
- }
- }
+ case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
+ /* spent += pos->value */
+ TALER_ARL_amount_add (&spent,
+ &spent,
+ &pos->details.purse_deposit->amount);
+ break;
+ case TALER_EXCHANGEDB_TT_PURSE_REFUND:
+ TALER_ARL_amount_add (&refunded,
+ &refunded,
+ &tl->details.purse_refund->refund_amount);
+ TALER_ARL_amount_add (&spent,
+ &spent,
+ &pos->details.purse_refund->refund_fee);
+ have_refund = true;
+ break;
+ case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
+ TALER_ARL_amount_add (&spent,
+ &spent,
+ &tl->details.reserve_open->coin_contribution);
+ break;
+ } /* switch (pos->type) */
+ } /* for (...) */
if (have_refund)
{
/* If we gave any refund, also discount ONE deposit fee */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&refunded,
- &refunded,
- &deposit_fee));
+ TALER_ARL_amount_add (&refunded,
+ &refunded,
+ &deposit_fee);
}
/* total coin value = original value plus refunds */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total,
- &refunded,
- value));
+ TALER_ARL_amount_add (&total,
+ &refunded,
+ value);
if (1 ==
TALER_amount_cmp (&spent,
&total))
@@ -564,10 +572,9 @@ check_coin_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
/* spent > total: bad */
struct TALER_Amount loss;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&loss,
- &spent,
- &total));
+ TALER_ARL_amount_subtract (&loss,
+ &spent,
+ &total);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Loss detected for coin %s - %s\n",
TALER_B2S (coin_pub),
@@ -596,56 +603,32 @@ check_coin_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
struct DenominationSummary
{
/**
- * Total value of outstanding (not deposited) coins issued with this
- * denomination key.
+ * Information about the circulation.
*/
- struct TALER_Amount denom_balance;
-
- /**
- * Total losses made (once coins deposited exceed
- * coins withdrawn and thus the @e denom_balance is
- * effectively negative).
- */
- struct TALER_Amount denom_loss;
-
- /**
- * Total value of coins issued with this denomination key.
- */
- struct TALER_Amount denom_risk;
-
- /**
- * Total value of coins subjected to recoup with this denomination key.
- */
- struct TALER_Amount denom_recoup;
-
- /**
- * How many coins (not their amount!) of this denomination
- * did the exchange issue overall?
- */
- uint64_t num_issued;
+ struct TALER_AUDITORDB_DenominationCirculationData dcd;
/**
* Denomination key information for this denomination.
*/
- const struct TALER_DenominationKeyValidityPS *issue;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
/**
- * #GNUNET_YES if this record already existed in the DB.
+ * True if this record already existed in the DB.
* Used to decide between insert/update in
* #sync_denomination().
*/
- int in_db;
+ bool in_db;
/**
* Should we report an emergency for this denomination, causing it to be
* revoked (because more coins were deposited than issued)?
*/
- int report_emergency;
+ bool report_emergency;
/**
- * #GNUNET_YES if this denomination was revoked.
+ * True if this denomination was revoked.
*/
- int was_revoked;
+ bool was_revoked;
};
@@ -676,7 +659,7 @@ struct CoinContext
* @return transaction status code
*/
static enum GNUNET_DB_QueryStatus
-init_denomination (const struct GNUNET_HashCode *denom_hash,
+init_denomination (const struct TALER_DenominationHashP *denom_hash,
struct DenominationSummary *ds)
{
enum GNUNET_DB_QueryStatus qs;
@@ -684,13 +667,8 @@ init_denomination (const struct GNUNET_HashCode *denom_hash,
uint64_t rowid;
qs = TALER_ARL_adb->get_denomination_balance (TALER_ARL_adb->cls,
- TALER_ARL_asession,
denom_hash,
- &ds->denom_balance,
- &ds->denom_loss,
- &ds->denom_risk,
- &ds->denom_recoup,
- &ds->num_issued);
+ &ds->dcd);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
@@ -698,30 +676,29 @@ init_denomination (const struct GNUNET_HashCode *denom_hash,
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
- ds->in_db = GNUNET_YES;
+ ds->in_db = true;
}
else
{
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &ds->denom_balance));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &ds->dcd.denom_balance));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &ds->denom_loss));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &ds->dcd.denom_loss));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &ds->denom_risk));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &ds->dcd.denom_risk));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &ds->denom_recoup));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &ds->dcd.recoup_loss));
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Starting balance for denomination `%s' is %s (%llu)\n",
- GNUNET_h2s (denom_hash),
- TALER_amount2s (&ds->denom_balance),
- (unsigned long long) ds->num_issued);
+ GNUNET_h2s (&denom_hash->hash),
+ TALER_amount2s (&ds->dcd.denom_balance),
+ (unsigned long long) ds->dcd.num_issued);
qs = TALER_ARL_edb->get_denomination_revocation (TALER_ARL_edb->cls,
- TALER_ARL_esession,
denom_hash,
&msig,
&rowid);
@@ -733,19 +710,11 @@ init_denomination (const struct GNUNET_HashCode *denom_hash,
if (0 < qs)
{
/* check revocation signature */
- struct TALER_MasterDenominationKeyRevocationPS rm = {
- .purpose.purpose = htonl (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED),
- .purpose.size = htonl (sizeof (rm)),
- .h_denom_pub = *denom_hash
- };
-
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED,
- &rm.purpose,
- &msig.eddsa_signature,
- &TALER_ARL_master_pub.eddsa_pub))
+ TALER_exchange_offline_denomination_revoke_verify (
+ denom_hash,
+ &TALER_ARL_master_pub,
+ &msig))
{
report_row_inconsistency ("denomination revocations",
rowid,
@@ -753,10 +722,10 @@ init_denomination (const struct GNUNET_HashCode *denom_hash,
}
else
{
- ds->was_revoked = GNUNET_YES;
+ ds->was_revoked = true;
}
}
- return (GNUNET_YES == ds->in_db)
+ return ds->in_db
? GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
: GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
}
@@ -771,14 +740,15 @@ init_denomination (const struct GNUNET_HashCode *denom_hash,
* @return NULL on error
*/
static struct DenominationSummary *
-get_denomination_summary (struct CoinContext *cc,
- const struct TALER_DenominationKeyValidityPS *issue,
- const struct GNUNET_HashCode *dh)
+get_denomination_summary (
+ struct CoinContext *cc,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue,
+ const struct TALER_DenominationHashP *dh)
{
struct DenominationSummary *ds;
ds = GNUNET_CONTAINER_multihashmap_get (cc->denom_summaries,
- dh);
+ &dh->hash);
if (NULL != ds)
return ds;
ds = GNUNET_new (struct DenominationSummary);
@@ -792,7 +762,7 @@ get_denomination_summary (struct CoinContext *cc,
}
GNUNET_assert (GNUNET_OK ==
GNUNET_CONTAINER_multihashmap_put (cc->denom_summaries,
- dh,
+ &dh->hash,
ds,
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
return ds;
@@ -809,69 +779,68 @@ get_denomination_summary (struct CoinContext *cc,
* @param value a `struct DenominationSummary`
* @return #GNUNET_OK (continue to iterate)
*/
-static int
+static enum GNUNET_GenericReturnValue
sync_denomination (void *cls,
const struct GNUNET_HashCode *denom_hash,
void *value)
{
struct CoinContext *cc = cls;
+ struct TALER_DenominationHashP denom_h = {
+ .hash = *denom_hash
+ };
struct DenominationSummary *ds = value;
- const struct TALER_DenominationKeyValidityPS *issue = ds->issue;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue = ds->issue;
struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Absolute expire_deposit;
+ struct GNUNET_TIME_Timestamp expire_deposit;
struct GNUNET_TIME_Absolute expire_deposit_grace;
enum GNUNET_DB_QueryStatus qs;
now = GNUNET_TIME_absolute_get ();
- expire_deposit = GNUNET_TIME_absolute_ntoh (issue->expire_deposit);
+ expire_deposit = issue->expire_deposit;
/* add day grace period to deal with clocks not being perfectly synchronized */
- expire_deposit_grace = GNUNET_TIME_absolute_add (expire_deposit,
+ expire_deposit_grace = GNUNET_TIME_absolute_add (expire_deposit.abs_time,
DEPOSIT_GRACE_PERIOD);
- if (now.abs_value_us > expire_deposit_grace.abs_value_us)
+ if (GNUNET_TIME_absolute_cmp (now,
+ >,
+ expire_deposit_grace) )
{
/* Denomination key has expired, book remaining balance of
outstanding coins as revenue; and reduce cc->risk exposure. */
if (ds->in_db)
qs = TALER_ARL_adb->del_denomination_balance (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- denom_hash);
+ &denom_h);
else
qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
- ( (0 != ds->denom_risk.value) ||
- (0 != ds->denom_risk.fraction) ) )
+ (! TALER_amount_is_zero (&ds->dcd.denom_risk)) )
{
/* The denomination expired and carried a balance; we can now
book the remaining balance as profit, and reduce our risk
exposure by the accumulated risk of the denomination. */
- GNUNET_assert (GNUNET_SYSERR !=
- TALER_amount_subtract (&total_risk,
- &total_risk,
- &ds->denom_risk));
+ TALER_ARL_amount_subtract (&TALER_ARL_USE_AB (coin_balance_risk),
+ &TALER_ARL_USE_AB (coin_balance_risk),
+ &ds->dcd.denom_risk);
/* If the above fails, our risk assessment is inconsistent!
This is really, really bad (auditor-internal invariant
would be violated). Hence we can "safely" assert. If
this assertion fails, well, good luck: there is a bug
- in the auditor _or_ the auditor's database is corrupt. *///
+ in the auditor _or_ the auditor's database is corrupt. */
}
if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
- ( (0 != ds->denom_balance.value) ||
- (0 != ds->denom_balance.fraction) ) )
+ (! TALER_amount_is_zero (&ds->dcd.denom_balance)) )
{
/* book denom_balance coin expiration profits! */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Denomination `%s' expired, booking %s in expiration profits\n",
GNUNET_h2s (denom_hash),
- TALER_amount2s (&ds->denom_balance));
+ TALER_amount2s (&ds->dcd.denom_balance));
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
(qs = TALER_ARL_adb->insert_historic_denom_revenue (
TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- denom_hash,
+ &denom_h,
expire_deposit,
- &ds->denom_balance,
- &ds->denom_recoup)))
+ &ds->dcd.denom_balance,
+ &ds->dcd.recoup_loss)))
{
/* Failed to store profits? Bad database */
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
@@ -888,11 +857,10 @@ sync_denomination (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Final balance for denomination `%s' is %s (%llu)\n",
GNUNET_h2s (denom_hash),
- TALER_amount2s (&ds->denom_balance),
- (unsigned long long) ds->num_issued);
+ TALER_amount2s (&ds->dcd.denom_balance),
+ (unsigned long long) ds->dcd.num_issued);
cnt = TALER_ARL_edb->count_known_coins (TALER_ARL_edb->cls,
- TALER_ARL_esession,
- denom_hash);
+ &denom_h);
if (0 > cnt)
{
/* Failed to obtain count? Bad database */
@@ -902,41 +870,31 @@ sync_denomination (void *cls,
}
else
{
- if (ds->num_issued < (uint64_t) cnt)
+ if (ds->dcd.num_issued < (uint64_t) cnt)
{
/* more coins deposited than issued! very bad */
report_emergency_by_count (issue,
- ds->num_issued,
+ ds->dcd.num_issued,
cnt,
- &ds->denom_risk);
+ &ds->dcd.denom_risk);
}
- if (GNUNET_YES == ds->report_emergency)
+ if (ds->report_emergency)
{
/* Value of coins deposited exceed value of coins
issued! Also very bad! */
report_emergency_by_amount (issue,
- &ds->denom_risk,
- &ds->denom_loss);
+ &ds->dcd.denom_risk,
+ &ds->dcd.denom_loss);
}
if (ds->in_db)
qs = TALER_ARL_adb->update_denomination_balance (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- denom_hash,
- &ds->denom_balance,
- &ds->denom_loss,
- &ds->denom_risk,
- &ds->denom_recoup,
- ds->num_issued);
+ &denom_h,
+ &ds->dcd);
else
qs = TALER_ARL_adb->insert_denomination_balance (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- denom_hash,
- &ds->denom_balance,
- &ds->denom_loss,
- &ds->denom_risk,
- &ds->denom_recoup,
- ds->num_issued);
+ &denom_h,
+ &ds->dcd);
}
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@@ -974,21 +932,20 @@ sync_denomination (void *cls,
* @param amount_with_fee amount that was withdrawn
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
withdraw_cb (void *cls,
uint64_t rowid,
- const struct GNUNET_HashCode *h_blind_ev,
+ const struct TALER_BlindedCoinHashP *h_blind_ev,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_ReserveSignatureP *reserve_sig,
- struct GNUNET_TIME_Absolute execution_date,
+ struct GNUNET_TIME_Timestamp execution_date,
const struct TALER_Amount *amount_with_fee)
{
struct CoinContext *cc = cls;
struct DenominationSummary *ds;
- struct GNUNET_HashCode dh;
- const struct TALER_DenominationKeyValidityPS *issue;
- struct TALER_Amount value;
+ struct TALER_DenominationHashP dh;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
enum GNUNET_DB_QueryStatus qs;
/* Note: some optimization potential here: lots of fields we
@@ -999,8 +956,9 @@ withdraw_cb (void *cls,
(void) execution_date;
(void) amount_with_fee;
- GNUNET_assert (rowid >= ppc.last_withdraw_serial_id); /* should be monotonically increasing */
- ppc.last_withdraw_serial_id = rowid + 1;
+ GNUNET_assert (rowid >=
+ TALER_ARL_USE_PP (coins_withdraw_serial_id)); /* should be monotonically increasing */
+ TALER_ARL_USE_PP (coins_withdraw_serial_id) = rowid + 1;
qs = TALER_ARL_get_denomination_info (denom_pub,
&issue,
@@ -1010,6 +968,8 @@ withdraw_cb (void *cls,
report_row_inconsistency ("withdraw",
rowid,
"denomination key not found");
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@@ -1028,33 +988,29 @@ withdraw_cb (void *cls,
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == cc->qs);
return GNUNET_SYSERR;
}
- TALER_amount_ntoh (&value,
- &issue->value);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Issued coin in denomination `%s' of total value %s\n",
- GNUNET_h2s (&dh),
- TALER_amount2s (&value));
- ds->num_issued++;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&ds->denom_balance,
- &ds->denom_balance,
- &value));
+ GNUNET_h2s (&dh.hash),
+ TALER_amount2s (&issue->value));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"New balance of denomination `%s' is %s\n",
- GNUNET_h2s (&dh),
- TALER_amount2s (&ds->denom_balance));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_escrow_balance,
- &total_escrow_balance,
- &value));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_risk,
- &total_risk,
- &value));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&ds->denom_risk,
- &ds->denom_risk,
- &value));
+ GNUNET_h2s (&dh.hash),
+ TALER_amount2s (&ds->dcd.denom_balance));
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_escrowed),
+ &TALER_ARL_USE_AB (total_escrowed),
+ &issue->value);
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_balance_risk),
+ &TALER_ARL_USE_AB (coin_balance_risk),
+ &issue->value);
+ ds->dcd.num_issued++;
+ TALER_ARL_amount_add (&ds->dcd.denom_balance,
+ &ds->dcd.denom_balance,
+ &issue->value);
+ TALER_ARL_amount_add (&ds->dcd.denom_risk,
+ &ds->dcd.denom_risk,
+ &issue->value);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
@@ -1068,7 +1024,7 @@ struct RevealContext
/**
* Denomination public data of the new coins.
*/
- const struct TALER_DenominationKeyValidityPS **new_issues;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation **new_issues;
/**
* Set to the size of the @a new_issues array.
@@ -1085,7 +1041,7 @@ struct RevealContext
* #GNUNET_NO if a denomination key was not found
* #GNUNET_SYSERR if we had a database error.
*/
- int err;
+ enum GNUNET_GenericReturnValue err;
/**
* Database error, if @e err is #GNUNET_SYSERR.
@@ -1100,30 +1056,18 @@ struct RevealContext
* @param cls closure with a `struct RevealContext *` in it
* @param num_freshcoins size of the @a rrcs array
* @param rrcs array of @a num_freshcoins information about coins to be created
- * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
- * @param tprivs array of @e num_tprivs transfer private keys
- * @param tp transfer public key information
*/
static void
reveal_data_cb (void *cls,
uint32_t num_freshcoins,
- const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
- unsigned int num_tprivs,
- const struct TALER_TransferPrivateKeyP *tprivs,
- const struct TALER_TransferPublicKeyP *tp)
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs)
{
struct RevealContext *rctx = cls;
- /* Note: optimization using custom database accessor API could avoid
- fetching these fields -- and we */
- (void) num_tprivs;
- (void) tprivs;
- (void) tp;
-
rctx->num_freshcoins = num_freshcoins;
rctx->new_issues = GNUNET_new_array (
num_freshcoins,
- const struct TALER_DenominationKeyValidityPS *);
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *);
/* Update outstanding amounts for all new coin's denominations */
for (unsigned int i = 0; i<num_freshcoins; i++)
@@ -1131,9 +1075,8 @@ reveal_data_cb (void *cls,
enum GNUNET_DB_QueryStatus qs;
/* lookup new coin denomination key */
- qs = TALER_ARL_get_denomination_info (&rrcs[i].denom_pub,
- &rctx->new_issues[i],
- NULL);
+ qs = TALER_ARL_get_denomination_info_by_hash (&rrcs[i].h_denom_pub,
+ &rctx->new_issues[i]);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
report_row_inconsistency ("refresh_reveal",
@@ -1167,26 +1110,23 @@ reveal_data_cb (void *cls,
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
*/
static enum GNUNET_DB_QueryStatus
-check_known_coin (const char *operation,
- const struct TALER_DenominationKeyValidityPS *issue,
- uint64_t rowid,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_Amount *loss_potential)
+check_known_coin (
+ const char *operation,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue,
+ uint64_t rowid,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_Amount *loss_potential)
{
struct TALER_CoinPublicInfo ci;
enum GNUNET_DB_QueryStatus qs;
if (NULL == get_cached_history (coin_pub))
{
- struct TALER_Amount value;
-
- TALER_amount_ntoh (&value,
- &issue->value);
qs = check_coin_history (coin_pub,
rowid,
operation,
- &value);
+ &issue->value);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
@@ -1200,7 +1140,6 @@ check_known_coin (const char *operation,
"Checking denomination signature on %s\n",
TALER_B2S (coin_pub));
qs = TALER_ARL_edb->get_known_coin (TALER_ARL_edb->cls,
- TALER_ARL_esession,
coin_pub,
&ci);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@@ -1213,21 +1152,80 @@ check_known_coin (const char *operation,
denom_pub))
{
TALER_ARL_report (report_bad_sig_losses,
- json_pack ("{s:s, s:I, s:o, s:o}",
- "operation", operation,
- "row", (json_int_t) rowid,
- "loss", TALER_JSON_from_amount (
- loss_potential),
- "coin_pub", GNUNET_JSON_from_data_auto (
- coin_pub)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
- loss_potential));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ operation),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ loss_potential),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ coin_pub)));
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_irregular_loss),
+ &TALER_ARL_USE_AB (coin_irregular_loss),
+ loss_potential);
+ }
+ TALER_denom_sig_free (&ci.denom_sig);
+ return qs;
+}
+
+/**
+ * Update the denom balance in @a dso reducing it by
+ * @a amount_with_fee. If this is not possible, report
+ * an emergency. Also updates the balance.
+ *
+ * @param dso denomination summary to update
+ * @param rowid responsible row (for logging)
+ * @param amount_with_fee amount to subtract
+ */
+static void
+reduce_denom_balance (struct DenominationSummary *dso,
+ uint64_t rowid,
+ const struct TALER_Amount *amount_with_fee)
+{
+ struct TALER_Amount tmp;
+
+ if (TALER_ARL_SR_INVALID_NEGATIVE ==
+ TALER_ARL_amount_subtract_neg (&tmp,
+ &dso->dcd.denom_balance,
+ amount_with_fee))
+ {
+ TALER_ARL_amount_add (&dso->dcd.denom_loss,
+ &dso->dcd.denom_loss,
+ amount_with_fee);
+ dso->report_emergency = true;
}
- GNUNET_CRYPTO_rsa_signature_free (ci.denom_sig.rsa_signature);
- return qs;
+ else
+ {
+ dso->dcd.denom_balance = tmp;
+ }
+ if (-1 == TALER_amount_cmp (&TALER_ARL_USE_AB (total_escrowed),
+ amount_with_fee))
+ {
+ /* This can theoretically happen if for example the exchange
+ never issued any coins (i.e. escrow balance is zero), but
+ accepted a forged coin (i.e. emergency situation after
+ private key compromise). In that case, we cannot even
+ subtract the profit we make from the fee from the escrow
+ balance. Tested as part of test-auditor.sh, case #18 */
+ report_amount_arithmetic_inconsistency (
+ "subtracting amount from escrow balance",
+ rowid,
+ &TALER_ARL_USE_AB (total_escrowed),
+ amount_with_fee,
+ 0);
+ }
+ else
+ {
+ TALER_ARL_amount_subtract (&TALER_ARL_USE_AB (total_escrowed),
+ &TALER_ARL_USE_AB (total_escrowed),
+ amount_with_fee);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "New balance of denomination `%s' is %s\n",
+ GNUNET_h2s (&dso->issue->denom_hash.hash),
+ TALER_amount2s (&dso->dcd.denom_balance));
}
@@ -1241,6 +1239,7 @@ check_known_coin (const char *operation,
* @param cls closure
* @param rowid unique serial ID for the refresh session in our DB
* @param denom_pub denomination public key of @a coin_pub
+ * @param h_age_commitment hash of the age commitment for the coin
* @param coin_pub public key of the coin
* @param coin_sig signature from the coin
* @param amount_with_fee amount that was deposited including fee
@@ -1248,10 +1247,11 @@ check_known_coin (const char *operation,
* @param rc what is the refresh commitment
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
refresh_session_cb (void *cls,
uint64_t rowid,
const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
const struct TALER_Amount *amount_with_fee,
@@ -1259,15 +1259,15 @@ refresh_session_cb (void *cls,
const struct TALER_RefreshCommitmentP *rc)
{
struct CoinContext *cc = cls;
- const struct TALER_DenominationKeyValidityPS *issue;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
struct DenominationSummary *dso;
struct TALER_Amount amount_without_fee;
- struct TALER_Amount tmp;
enum GNUNET_DB_QueryStatus qs;
(void) noreveal_index;
- GNUNET_assert (rowid >= ppc.last_melt_serial_id); /* should be monotonically increasing */
- ppc.last_melt_serial_id = rowid + 1;
+ GNUNET_assert (rowid >=
+ TALER_ARL_USE_PP (coins_melt_serial_id)); /* should be monotonically increasing */
+ TALER_ARL_USE_PP (coins_melt_serial_id) = rowid + 1;
qs = TALER_ARL_get_denomination_info (denom_pub,
&issue,
@@ -1277,6 +1277,8 @@ refresh_session_cb (void *cls,
report_row_inconsistency ("melt",
rowid,
"denomination key not found");
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@@ -1300,40 +1302,39 @@ refresh_session_cb (void *cls,
/* verify melt signature */
{
- struct TALER_RefreshMeltCoinAffirmationPS rmc = {
- .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
- .purpose.size = htonl (sizeof (rmc)),
- .rc = *rc,
- .melt_fee = issue->fee_refresh,
- .coin_pub = *coin_pub
- };
+ struct TALER_DenominationHashP h_denom_pub;
- TALER_amount_hton (&rmc.amount_with_fee,
- amount_with_fee);
+ TALER_denom_pub_hash (denom_pub,
+ &h_denom_pub);
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT,
- &rmc.purpose,
- &coin_sig->eddsa_signature,
- &coin_pub->eddsa_pub))
+ TALER_wallet_melt_verify (amount_with_fee,
+ &issue->fees.refresh,
+ rc,
+ &h_denom_pub,
+ h_age_commitment,
+ coin_pub,
+ coin_sig))
{
+ GNUNET_break_op (0);
TALER_ARL_report (report_bad_sig_losses,
- json_pack ("{s:s, s:I, s:o, s:o}",
- "operation", "melt",
- "row", (json_int_t) rowid,
- "loss", TALER_JSON_from_amount (
- amount_with_fee),
- "coin_pub", GNUNET_JSON_from_data_auto (
- coin_pub)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
- amount_with_fee));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "melt"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ amount_with_fee),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ coin_pub)));
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_irregular_loss),
+ &TALER_ARL_USE_AB (coin_irregular_loss),
+ amount_with_fee);
}
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Melting coin %s in denomination `%s' of value %s\n",
TALER_B2S (coin_pub),
- GNUNET_h2s (&issue->denom_hash),
+ GNUNET_h2s (&issue->denom_hash.hash),
TALER_amount2s (amount_with_fee));
{
@@ -1344,7 +1345,6 @@ refresh_session_cb (void *cls,
};
qs = TALER_ARL_edb->get_refresh_reveal (TALER_ARL_edb->cls,
- TALER_ARL_esession,
rc,
&reveal_data_cb,
&reveal_ctx);
@@ -1360,17 +1360,19 @@ refresh_session_cb (void *cls,
/* This can legitimately happen if reveal was not yet called or only
with invalid data, even if the exchange is correctly operating. We
still report it. */
- TALER_ARL_report (report_refreshs_hanging,
- json_pack ("{s:I, s:o, s:o}",
- "row", (json_int_t) rowid,
- "amount", TALER_JSON_from_amount (
- amount_with_fee),
- "coin_pub", GNUNET_JSON_from_data_auto (
- coin_pub)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_refresh_hanging,
- &total_refresh_hanging,
- amount_with_fee));
+ TALER_ARL_report (report_refreshes_hanging,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("amount",
+ amount_with_fee),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ coin_pub)));
+ TALER_ARL_amount_add (&total_refresh_hanging,
+ &total_refresh_hanging,
+ amount_with_fee);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
if (GNUNET_SYSERR == reveal_ctx.err)
@@ -1378,66 +1380,59 @@ refresh_session_cb (void *cls,
if (GNUNET_OK != reveal_ctx.err)
{
- GNUNET_free_non_null (reveal_ctx.new_issues);
- return (GNUNET_SYSERR == reveal_ctx.err) ? GNUNET_SYSERR : GNUNET_OK;
+ GNUNET_free (reveal_ctx.new_issues);
+ if (GNUNET_SYSERR == reveal_ctx.err)
+ return GNUNET_SYSERR;
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
}
/* Check that the resulting amounts are consistent with the value being
refreshed by calculating the total refresh cost */
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (amount_with_fee->currency,
+ TALER_amount_set_zero (amount_with_fee->currency,
&refresh_cost));
for (unsigned int i = 0; i<reveal_ctx.num_freshcoins; i++)
{
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *ni
+ = reveal_ctx.new_issues[i];
/* update cost of refresh */
- struct TALER_Amount fee;
- struct TALER_Amount value;
- TALER_amount_ntoh (&fee,
- &reveal_ctx.new_issues[i]->fee_withdraw);
- TALER_amount_ntoh (&value,
- &reveal_ctx.new_issues[i]->value);
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&refresh_cost,
- &refresh_cost,
- &fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&refresh_cost,
- &refresh_cost,
- &value));
+ TALER_ARL_amount_add (&refresh_cost,
+ &refresh_cost,
+ &ni->fees.withdraw);
+ TALER_ARL_amount_add (&refresh_cost,
+ &refresh_cost,
+ &ni->value);
}
/* compute contribution of old coin */
+ if (TALER_ARL_SR_POSITIVE !=
+ TALER_ARL_amount_subtract_neg (&amount_without_fee,
+ amount_with_fee,
+ &issue->fees.refresh))
{
- struct TALER_Amount melt_fee;
-
- TALER_amount_ntoh (&melt_fee,
- &issue->fee_refresh);
- if (GNUNET_OK !=
- TALER_amount_subtract (&amount_without_fee,
- amount_with_fee,
- &melt_fee))
- {
- /* Melt fee higher than contribution of melted coin; this makes
- no sense (exchange should never have accepted the operation) */
- report_amount_arithmetic_inconsistency ("melt contribution vs. fee",
- rowid,
- amount_with_fee,
- &melt_fee,
- -1);
- /* To continue, best assumption is the melted coin contributed
- nothing (=> all withdrawal amounts will be counted as losses) */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &amount_without_fee));
- }
+ /* Melt fee higher than contribution of melted coin; this makes
+ no sense (exchange should never have accepted the operation) */
+ report_amount_arithmetic_inconsistency ("melt contribution vs. fee",
+ rowid,
+ amount_with_fee,
+ &issue->fees.refresh,
+ -1);
+ /* To continue, best assumption is the melted coin contributed
+ nothing (=> all withdrawal amounts will be counted as losses) */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &amount_without_fee));
}
- /* check old coin covers complete expenses (of withdraw operations) */
+ /* check old coin covers complete expenses (of refresh operation) */
if (1 == TALER_amount_cmp (&refresh_cost,
&amount_without_fee))
{
/* refresh_cost > amount_without_fee, which is bad (exchange lost) */
+ GNUNET_break_op (0);
report_amount_arithmetic_inconsistency ("melt (cost)",
rowid,
&amount_without_fee, /* 'exchange' */
@@ -1448,12 +1443,13 @@ refresh_session_cb (void *cls,
/* update outstanding denomination amounts for fresh coins withdrawn */
for (unsigned int i = 0; i<reveal_ctx.num_freshcoins; i++)
{
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *ni
+ = reveal_ctx.new_issues[i];
struct DenominationSummary *dsi;
- struct TALER_Amount value;
dsi = get_denomination_summary (cc,
- reveal_ctx.new_issues[i],
- &reveal_ctx.new_issues[i]->denom_hash);
+ ni,
+ &ni->denom_hash);
if (NULL == dsi)
{
report_row_inconsistency ("refresh_reveal",
@@ -1462,36 +1458,30 @@ refresh_session_cb (void *cls,
}
else
{
- TALER_amount_ntoh (&value,
- &reveal_ctx.new_issues[i]->value);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Created fresh coin in denomination `%s' of value %s\n",
- GNUNET_h2s (&reveal_ctx.new_issues[i]->denom_hash),
- TALER_amount2s (&value));
- dsi->num_issued++;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&dsi->denom_balance,
- &dsi->denom_balance,
- &value));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&dsi->denom_risk,
- &dsi->denom_risk,
- &value));
+ GNUNET_h2s (&ni->denom_hash.hash),
+ TALER_amount2s (&ni->value));
+ dsi->dcd.num_issued++;
+ TALER_ARL_amount_add (&dsi->dcd.denom_balance,
+ &dsi->dcd.denom_balance,
+ &ni->value);
+ TALER_ARL_amount_add (&dsi->dcd.denom_risk,
+ &dsi->dcd.denom_risk,
+ &ni->value);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"New balance of denomination `%s' is %s\n",
- GNUNET_h2s (&reveal_ctx.new_issues[i]->denom_hash),
- TALER_amount2s (&dsi->denom_balance));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_escrow_balance,
- &total_escrow_balance,
- &value));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_risk,
- &total_risk,
- &value));
+ GNUNET_h2s (&ni->denom_hash.hash),
+ TALER_amount2s (&dsi->dcd.denom_balance));
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_escrowed),
+ &TALER_ARL_USE_AB (total_escrowed),
+ &ni->value);
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_balance_risk),
+ &TALER_ARL_USE_AB (coin_balance_risk),
+ &ni->value);
}
}
- GNUNET_free_non_null (reveal_ctx.new_issues);
+ GNUNET_free (reveal_ctx.new_issues);
}
/* update old coin's denomination balance */
@@ -1506,61 +1496,17 @@ refresh_session_cb (void *cls,
}
else
{
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&tmp,
- &dso->denom_balance,
- amount_with_fee))
- {
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&dso->denom_loss,
- &dso->denom_loss,
- amount_with_fee));
- dso->report_emergency = GNUNET_YES;
- }
- else
- {
- dso->denom_balance = tmp;
- }
- if (-1 == TALER_amount_cmp (&total_escrow_balance,
- amount_with_fee))
- {
- /* This can theoretically happen if for example the exchange
- never issued any coins (i.e. escrow balance is zero), but
- accepted a forged coin (i.e. emergency situation after
- private key compromise). In that case, we cannot even
- subtract the profit we make from the fee from the escrow
- balance. Tested as part of test-auditor.sh, case #18 *///
- report_amount_arithmetic_inconsistency (
- "subtracting refresh fee from escrow balance",
- rowid,
- &total_escrow_balance,
- amount_with_fee,
- 0);
- }
- else
- {
- GNUNET_assert (GNUNET_SYSERR !=
- TALER_amount_subtract (&total_escrow_balance,
- &total_escrow_balance,
- amount_with_fee));
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "New balance of denomination `%s' after melt is %s\n",
- GNUNET_h2s (&issue->denom_hash),
- TALER_amount2s (&dso->denom_balance));
+ reduce_denom_balance (dso,
+ rowid,
+ amount_with_fee);
}
/* update global melt fees */
- {
- struct TALER_Amount rfee;
-
- TALER_amount_ntoh (&rfee,
- &issue->fee_refresh);
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_melt_fee_income,
- &total_melt_fee_income,
- &rfee));
- }
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_melt_fee_revenue),
+ &TALER_ARL_USE_AB (coin_melt_fee_revenue),
+ &issue->fees.refresh);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
@@ -1571,45 +1517,30 @@ refresh_session_cb (void *cls,
*
* @param cls closure
* @param rowid unique serial ID for the deposit in our DB
- * @param timestamp when did the deposit happen
- * @param merchant_pub public key of the merchant
+ * @param exchange_timestamp when did the exchange get the deposit
+ * @param deposit deposit details
* @param denom_pub denomination public key of @a coin_pub
- * @param coin_pub public key of the coin
- * @param coin_sig signature from the coin
- * @param amount_with_fee amount that was deposited including fee
- * @param h_contract_terms hash of the proposal data known to merchant and customer
- * @param refund_deadline by which the merchant advised that he might want
- * to get a refund
- * @param wire_deadline by which the merchant advised that he would like the
- * wire transfer to be executed
- * @param receiver_wire_account wire details for the merchant, NULL from iterate_matching_deposits()
* @param done flag set if the deposit was already executed (or not)
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
deposit_cb (void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute timestamp,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ const struct TALER_EXCHANGEDB_Deposit *deposit,
const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_Amount *amount_with_fee,
- const struct GNUNET_HashCode *h_contract_terms,
- struct GNUNET_TIME_Absolute refund_deadline,
- struct GNUNET_TIME_Absolute wire_deadline,
- const json_t *receiver_wire_account,
- int done)
+ bool done)
{
struct CoinContext *cc = cls;
- const struct TALER_DenominationKeyValidityPS *issue;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
struct DenominationSummary *ds;
enum GNUNET_DB_QueryStatus qs;
- (void) wire_deadline;
(void) done;
- GNUNET_assert (rowid >= ppc.last_deposit_serial_id); /* should be monotonically increasing */
- ppc.last_deposit_serial_id = rowid + 1;
+ (void) exchange_timestamp;
+ GNUNET_assert (rowid >=
+ TALER_ARL_USE_PP (coins_deposit_serial_id)); /* should be monotonically increasing */
+ TALER_ARL_USE_PP (coins_deposit_serial_id) = rowid + 1;
qs = TALER_ARL_get_denomination_info (denom_pub,
&issue,
@@ -1619,8 +1550,18 @@ deposit_cb (void *cls,
report_row_inconsistency ("deposits",
rowid,
"denomination key not found");
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
+ if (GNUNET_TIME_timestamp_cmp (deposit->refund_deadline,
+ >,
+ deposit->wire_deadline))
+ {
+ report_row_inconsistency ("deposits",
+ rowid,
+ "refund deadline past wire deadline");
+ }
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
@@ -1631,9 +1572,9 @@ deposit_cb (void *cls,
qs = check_known_coin ("deposit",
issue,
rowid,
- coin_pub,
+ &deposit->coin.coin_pub,
denom_pub,
- amount_with_fee);
+ &deposit->amount_with_fee);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
@@ -1643,66 +1584,57 @@ deposit_cb (void *cls,
/* Verify deposit signature */
{
- struct TALER_DepositRequestPS dr = {
- .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT),
- .purpose.size = htonl (sizeof (dr)),
- .h_contract_terms = *h_contract_terms,
- .timestamp = GNUNET_TIME_absolute_hton (timestamp),
- .refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline),
- .deposit_fee = issue->fee_deposit,
- .merchant = *merchant_pub,
- .coin_pub = *coin_pub
- };
+ struct TALER_MerchantWireHashP h_wire;
+ struct TALER_DenominationHashP h_denom_pub;
- if (GNUNET_OK !=
- TALER_JSON_merchant_wire_signature_hash (receiver_wire_account,
- &dr.h_wire))
- {
- TALER_ARL_report (report_bad_sig_losses,
- json_pack ("{s:s, s:I, s:o, s:o}",
- "operation", "deposit",
- "row", (json_int_t) rowid,
- "loss", TALER_JSON_from_amount (
- amount_with_fee),
- "coin_pub", GNUNET_JSON_from_data_auto (
- coin_pub)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
- amount_with_fee));
- return GNUNET_OK;
- }
- TALER_amount_hton (&dr.amount_with_fee,
- amount_with_fee);
+ TALER_denom_pub_hash (denom_pub,
+ &h_denom_pub);
+ TALER_merchant_wire_signature_hash (deposit->receiver_wire_account,
+ &deposit->wire_salt,
+ &h_wire);
/* NOTE: This is one of the operations we might eventually
want to do in parallel in the background to improve
auditor performance! */
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT,
- &dr.purpose,
- &coin_sig->eddsa_signature,
- &coin_pub->eddsa_pub))
+ TALER_wallet_deposit_verify (&deposit->amount_with_fee,
+ &issue->fees.deposit,
+ &h_wire,
+ &deposit->h_contract_terms,
+ deposit->no_wallet_data_hash
+ ? NULL
+ : &deposit->wallet_data_hash,
+ &deposit->coin.h_age_commitment,
+ &deposit->h_policy,
+ &h_denom_pub,
+ deposit->timestamp,
+ &deposit->merchant_pub,
+ deposit->refund_deadline,
+ &deposit->coin.coin_pub,
+ &deposit->csig))
{
TALER_ARL_report (report_bad_sig_losses,
- json_pack ("{s:s, s:I, s:o, s:o}",
- "operation", "deposit",
- "row", (json_int_t) rowid,
- "loss", TALER_JSON_from_amount (
- amount_with_fee),
- "coin_pub", GNUNET_JSON_from_data_auto (
- coin_pub)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
- amount_with_fee));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "deposit"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ &deposit->amount_with_fee),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &deposit->coin.coin_pub)));
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_irregular_loss),
+ &TALER_ARL_USE_AB (coin_irregular_loss),
+ &deposit->amount_with_fee);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Deposited coin %s in denomination `%s' of value %s\n",
- TALER_B2S (coin_pub),
- GNUNET_h2s (&issue->denom_hash),
- TALER_amount2s (amount_with_fee));
+ TALER_B2S (&deposit->coin.coin_pub),
+ GNUNET_h2s (&issue->denom_hash.hash),
+ TALER_amount2s (&deposit->amount_with_fee));
/* update old coin's denomination balance */
ds = get_denomination_summary (cc,
@@ -1716,66 +1648,17 @@ deposit_cb (void *cls,
}
else
{
- struct TALER_Amount tmp;
-
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&tmp,
- &ds->denom_balance,
- amount_with_fee))
- {
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&ds->denom_loss,
- &ds->denom_loss,
- amount_with_fee));
- ds->report_emergency = GNUNET_YES;
- }
- else
- {
- ds->denom_balance = tmp;
- }
-
- if (-1 == TALER_amount_cmp (&total_escrow_balance,
- amount_with_fee))
- {
- /* This can theoretically happen if for example the exchange
- never issued any coins (i.e. escrow balance is zero), but
- accepted a forged coin (i.e. emergency situation after
- private key compromise). In that case, we cannot even
- subtract the profit we make from the fee from the escrow
- balance. Tested as part of test-auditor.sh, case #18 *///
- report_amount_arithmetic_inconsistency (
- "subtracting deposit fee from escrow balance",
- rowid,
- &total_escrow_balance,
- amount_with_fee,
- 0);
- }
- else
- {
- GNUNET_assert (GNUNET_SYSERR !=
- TALER_amount_subtract (&total_escrow_balance,
- &total_escrow_balance,
- amount_with_fee));
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "New balance of denomination `%s' after deposit is %s\n",
- GNUNET_h2s (&issue->denom_hash),
- TALER_amount2s (&ds->denom_balance));
+ reduce_denom_balance (ds,
+ rowid,
+ &deposit->amount_with_fee);
}
/* update global deposit fees */
- {
- struct TALER_Amount dfee;
-
- TALER_amount_ntoh (&dfee,
- &issue->fee_deposit);
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_deposit_fee_income,
- &total_deposit_fee_income,
- &dfee));
- }
-
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_deposit_fee_revenue),
+ &TALER_ARL_USE_AB (coin_deposit_fee_revenue),
+ &issue->fees.deposit);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
@@ -1794,29 +1677,30 @@ deposit_cb (void *cls,
* @param merchant_sig signature of the merchant
* @param h_contract_terms hash of the proposal data known to merchant and customer
* @param rtransaction_id refund transaction ID chosen by the merchant
+ * @param full_refund true if the refunds total up to the entire deposited value
* @param amount_with_fee amount that was deposited including fee
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
refund_cb (void *cls,
uint64_t rowid,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantSignatureP *merchant_sig,
- const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
uint64_t rtransaction_id,
+ bool full_refund,
const struct TALER_Amount *amount_with_fee)
{
struct CoinContext *cc = cls;
- const struct TALER_DenominationKeyValidityPS *issue;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
struct DenominationSummary *ds;
struct TALER_Amount amount_without_fee;
- struct TALER_Amount refund_fee;
enum GNUNET_DB_QueryStatus qs;
- GNUNET_assert (rowid >= ppc.last_refund_serial_id); /* should be monotonically increasing */
- ppc.last_refund_serial_id = rowid + 1;
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (coins_refund_serial_id)); /* should be monotonically increasing */
+ TALER_ARL_USE_PP (coins_refund_serial_id) = rowid + 1;
qs = TALER_ARL_get_denomination_info (denom_pub,
&issue,
@@ -1826,7 +1710,9 @@ refund_cb (void *cls,
report_row_inconsistency ("refunds",
rowid,
"denomination key not found");
- return GNUNET_SYSERR;
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
@@ -1835,60 +1721,51 @@ refund_cb (void *cls,
}
/* verify refund signature */
+ if (GNUNET_OK !=
+ TALER_merchant_refund_verify (coin_pub,
+ h_contract_terms,
+ rtransaction_id,
+ amount_with_fee,
+ merchant_pub,
+ merchant_sig))
{
- struct TALER_RefundRequestPS rr = {
- .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
- .purpose.size = htonl (sizeof (rr)),
- .h_contract_terms = *h_contract_terms,
- .coin_pub = *coin_pub,
- .merchant = *merchant_pub,
- .rtransaction_id = GNUNET_htonll (rtransaction_id),
- .refund_fee = issue->fee_refund
- };
-
- TALER_amount_hton (&rr.refund_amount,
- amount_with_fee);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
- &rr.purpose,
- &merchant_sig->eddsa_sig,
- &merchant_pub->eddsa_pub))
- {
- TALER_ARL_report (report_bad_sig_losses,
- json_pack ("{s:s, s:I, s:o, s:o}",
- "operation", "refund",
- "row", (json_int_t) rowid,
- "loss", TALER_JSON_from_amount (
- amount_with_fee),
- "coin_pub", GNUNET_JSON_from_data_auto (
- coin_pub)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
- amount_with_fee));
- return GNUNET_OK;
- }
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "refund"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ amount_with_fee),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ coin_pub)));
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_irregular_loss),
+ &TALER_ARL_USE_AB (coin_irregular_loss),
+ amount_with_fee);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
}
- TALER_amount_ntoh (&refund_fee,
- &issue->fee_refund);
- if (GNUNET_OK !=
- TALER_amount_subtract (&amount_without_fee,
- amount_with_fee,
- &refund_fee))
+ if (TALER_ARL_SR_INVALID_NEGATIVE ==
+ TALER_ARL_amount_subtract_neg (&amount_without_fee,
+ amount_with_fee,
+ &issue->fees.refund))
{
report_amount_arithmetic_inconsistency ("refund (fee)",
rowid,
&amount_without_fee,
- &refund_fee,
+ &issue->fees.refund,
-1);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Refunding coin %s in denomination `%s' value %s\n",
TALER_B2S (coin_pub),
- GNUNET_h2s (&issue->denom_hash),
+ GNUNET_h2s (&issue->denom_hash.hash),
TALER_amount2s (amount_with_fee));
/* update coin's denomination balance */
@@ -1903,32 +1780,162 @@ refund_cb (void *cls,
}
else
{
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&ds->denom_balance,
- &ds->denom_balance,
- &amount_without_fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&ds->denom_risk,
- &ds->denom_risk,
- &amount_without_fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_escrow_balance,
- &total_escrow_balance,
- &amount_without_fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_risk,
- &total_risk,
- &amount_without_fee));
+ TALER_ARL_amount_add (&ds->dcd.denom_balance,
+ &ds->dcd.denom_balance,
+ &amount_without_fee);
+ TALER_ARL_amount_add (&ds->dcd.denom_risk,
+ &ds->dcd.denom_risk,
+ &amount_without_fee);
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_escrowed),
+ &TALER_ARL_USE_AB (total_escrowed),
+ &amount_without_fee);
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_balance_risk),
+ &TALER_ARL_USE_AB (coin_balance_risk),
+ &amount_without_fee);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"New balance of denomination `%s' after refund is %s\n",
- GNUNET_h2s (&issue->denom_hash),
- TALER_amount2s (&ds->denom_balance));
+ GNUNET_h2s (&issue->denom_hash.hash),
+ TALER_amount2s (&ds->dcd.denom_balance));
}
/* update total refund fee balance */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_refund_fee_income,
- &total_refund_fee_income,
- &refund_fee));
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_refund_fee_revenue),
+ &TALER_ARL_USE_AB (coin_refund_fee_revenue),
+ &issue->fees.refund);
+ if (full_refund)
+ {
+ TALER_ARL_amount_subtract (&TALER_ARL_USE_AB (coin_deposit_fee_revenue),
+ &TALER_ARL_USE_AB (coin_deposit_fee_revenue),
+ &issue->fees.deposit);
+ }
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about purse refunds that have been made, with
+ * the goal of auditing the purse refund's execution.
+ *
+ * @param cls closure
+ * @param rowid row of the purse-refund
+ * @param amount_with_fee amount of the deposit into the purse
+ * @param coin_pub coin that is to be refunded the @a given amount_with_fee
+ * @param denom_pub denomination of @a coin_pub
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+purse_refund_coin_cb (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_DenominationPublicKey *denom_pub)
+{
+ struct CoinContext *cc = cls;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
+ struct DenominationSummary *ds;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TALER_ARL_get_denomination_info (denom_pub,
+ &issue,
+ NULL);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ report_row_inconsistency ("purse-refunds",
+ rowid,
+ "denomination key not found");
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Aborted purse-deposit of coin %s in denomination `%s' value %s\n",
+ TALER_B2S (coin_pub),
+ GNUNET_h2s (&issue->denom_hash.hash),
+ TALER_amount2s (amount_with_fee));
+
+ /* update coin's denomination balance */
+ ds = get_denomination_summary (cc,
+ issue,
+ &issue->denom_hash);
+ if (NULL == ds)
+ {
+ report_row_inconsistency ("purse-refund",
+ rowid,
+ "denomination key for purse-refunded coin unknown to auditor");
+ }
+ else
+ {
+ TALER_ARL_amount_add (&ds->dcd.denom_balance,
+ &ds->dcd.denom_balance,
+ amount_with_fee);
+ TALER_ARL_amount_add (&ds->dcd.denom_risk,
+ &ds->dcd.denom_risk,
+ amount_with_fee);
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_escrowed),
+ &TALER_ARL_USE_AB (total_escrowed),
+ amount_with_fee);
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_balance_risk),
+ &TALER_ARL_USE_AB (coin_balance_risk),
+ amount_with_fee);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "New balance of denomination `%s' after purse-refund is %s\n",
+ GNUNET_h2s (&issue->denom_hash.hash),
+ TALER_amount2s (&ds->dcd.denom_balance));
+ }
+ /* update total deposit fee balance */
+ TALER_ARL_amount_subtract (&TALER_ARL_USE_AB (coin_deposit_fee_revenue),
+ &TALER_ARL_USE_AB (coin_deposit_fee_revenue),
+ &issue->fees.deposit);
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about a purse that was refunded. Adds the
+ * refunded amounts back to the outstanding balance of the respective
+ * denominations.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the refund in our DB
+ * @param purse_pub public key of the purse
+ * @param reserve_pub public key of the targeted reserve (ignored)
+ * @param val targeted amount to be in the reserve (ignored)
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+purse_refund_cb (void *cls,
+ uint64_t rowid,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *val)
+{
+ struct CoinContext *cc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ (void) val; /* irrelevant on refund */
+ (void) reserve_pub; /* irrelevant, may even be NULL */
+ GNUNET_assert (rowid >=
+ TALER_ARL_USE_PP (coins_purse_refunds_serial_id)); /* should be monotonically increasing */
+ TALER_ARL_USE_PP (coins_purse_refunds_serial_id) = rowid + 1;
+ qs = TALER_ARL_edb->select_purse_deposits_by_purse (TALER_ARL_edb->cls,
+ purse_pub,
+ &purse_refund_coin_cb,
+ cc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return GNUNET_SYSERR;
+ }
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
@@ -1947,7 +1954,7 @@ refund_cb (void *cls,
* @param coin_blind blinding factor used to blind the coin
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
check_recoup (struct CoinContext *cc,
const char *operation,
uint64_t rowid,
@@ -1955,27 +1962,39 @@ check_recoup (struct CoinContext *cc,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind)
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_blind)
{
struct DenominationSummary *ds;
enum GNUNET_DB_QueryStatus qs;
- const struct TALER_DenominationKeyValidityPS *issue;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
if (GNUNET_OK !=
+ TALER_wallet_recoup_verify (&coin->denom_pub_hash,
+ coin_blind,
+ &coin->coin_pub,
+ coin_sig))
+ {
+ report_row_inconsistency (operation,
+ rowid,
+ "recoup signature invalid");
+ }
+ if (GNUNET_OK !=
TALER_test_coin_valid (coin,
denom_pub))
{
TALER_ARL_report (report_bad_sig_losses,
- json_pack ("{s:s, s:I, s:o, s:o}",
- "operation", operation,
- "row", (json_int_t) rowid,
- "loss", TALER_JSON_from_amount (amount),
- "coin_pub", GNUNET_JSON_from_data_auto (
- &coin->denom_pub_hash)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
- amount));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ operation),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ amount),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &coin->denom_pub_hash)));
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_irregular_loss),
+ &TALER_ARL_USE_AB (coin_irregular_loss),
+ amount);
}
qs = TALER_ARL_get_denomination_info_by_hash (&coin->denom_pub_hash,
&issue);
@@ -1984,6 +2003,8 @@ check_recoup (struct CoinContext *cc,
report_row_inconsistency (operation,
rowid,
"denomination key not found");
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@@ -2006,35 +2027,6 @@ check_recoup (struct CoinContext *cc,
cc->qs = qs;
return GNUNET_SYSERR;
}
- {
- struct TALER_RecoupRequestPS pr = {
- .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP),
- .purpose.size = htonl (sizeof (pr)),
- .coin_pub = coin->coin_pub,
- .coin_blind = *coin_blind,
- .h_denom_pub = coin->denom_pub_hash
- };
-
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP,
- &pr.purpose,
- &coin_sig->eddsa_signature,
- &coin->coin_pub.eddsa_pub))
- {
- TALER_ARL_report (report_bad_sig_losses,
- json_pack ("{s:s, s:I, s:o, s:o}",
- "operation", operation,
- "row", (json_int_t) rowid,
- "loss", TALER_JSON_from_amount (amount),
- "coin_pub", GNUNET_JSON_from_data_auto (
- &coin->coin_pub)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
- amount));
- return GNUNET_OK;
- }
- }
ds = get_denomination_summary (cc,
issue,
&issue->denom_hash);
@@ -2046,33 +2038,34 @@ check_recoup (struct CoinContext *cc,
}
else
{
- if (GNUNET_NO == ds->was_revoked)
+ if (! ds->was_revoked)
{
/* Woopsie, we allowed recoup on non-revoked denomination!? */
TALER_ARL_report (report_bad_sig_losses,
- json_pack ("{s:s, s:s, s:I, s:o, s:o}",
- "operation",
- operation,
- "hint",
- "denomination not revoked",
- "row", (json_int_t) rowid,
- "loss", TALER_JSON_from_amount (amount),
- "coin_pub", GNUNET_JSON_from_data_auto (
- &coin->coin_pub)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
- amount));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ operation),
+ GNUNET_JSON_pack_string ("hint",
+ "denomination not revoked"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ amount),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &coin->coin_pub)));
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_irregular_loss),
+ &TALER_ARL_USE_AB (coin_irregular_loss),
+ amount);
}
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&ds->denom_recoup,
- &ds->denom_recoup,
- amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_recoup_loss,
- &total_recoup_loss,
- amount));
- }
+ TALER_ARL_amount_add (&ds->dcd.recoup_loss,
+ &ds->dcd.recoup_loss,
+ amount);
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_recoup_loss),
+ &TALER_ARL_USE_AB (total_recoup_loss),
+ amount);
+ }
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
@@ -2091,23 +2084,46 @@ check_recoup (struct CoinContext *cc,
* @param coin_blind blinding factor used to blind the coin
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
recoup_cb (void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute timestamp,
+ struct GNUNET_TIME_Timestamp timestamp,
const struct TALER_Amount *amount,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind)
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_blind)
{
struct CoinContext *cc = cls;
- GNUNET_assert (rowid >= ppc.last_recoup_serial_id); /* should be monotonically increasing */
- ppc.last_recoup_serial_id = rowid + 1;
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (coins_recoup_serial_id)); /* should be monotonically increasing */
+ TALER_ARL_USE_PP (coins_recoup_serial_id) = rowid + 1;
(void) timestamp;
(void) reserve_pub;
+ if (GNUNET_OK !=
+ TALER_wallet_recoup_verify (&coin->denom_pub_hash,
+ coin_blind,
+ &coin->coin_pub,
+ coin_sig))
+ {
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "recoup"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ amount),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &coin->coin_pub)));
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_irregular_loss),
+ &TALER_ARL_USE_AB (coin_irregular_loss),
+ amount);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+ }
return check_recoup (cc,
"recoup",
rowid,
@@ -2135,26 +2151,26 @@ recoup_cb (void *cls,
* @param coin_blind blinding factor used to blind the coin
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
recoup_refresh_cb (void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute timestamp,
+ struct GNUNET_TIME_Timestamp timestamp,
const struct TALER_Amount *amount,
const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
- const struct GNUNET_HashCode *old_denom_pub_hash,
+ const struct TALER_DenominationHashP *old_denom_pub_hash,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind)
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_blind)
{
struct CoinContext *cc = cls;
- const struct TALER_DenominationKeyValidityPS *issue;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
enum GNUNET_DB_QueryStatus qs;
(void) timestamp;
(void) old_coin_pub;
- GNUNET_assert (rowid >= ppc.last_recoup_refresh_serial_id); /* should be monotonically increasing */
- ppc.last_recoup_refresh_serial_id = rowid + 1;
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (coins_recoup_refresh_serial_id)); /* should be monotonically increasing */
+ TALER_ARL_USE_PP (coins_recoup_refresh_serial_id) = rowid + 1;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Recoup-refresh amount is %s\n",
TALER_amount2s (amount));
@@ -2189,17 +2205,39 @@ recoup_refresh_cb (void *cls,
}
else
{
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&dso->denom_balance,
- &dso->denom_balance,
- amount));
+ TALER_ARL_amount_add (&dso->dcd.denom_balance,
+ &dso->dcd.denom_balance,
+ amount);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"New balance of denomination `%s' after refresh-recoup is %s\n",
- GNUNET_h2s (&issue->denom_hash),
- TALER_amount2s (&dso->denom_balance));
+ GNUNET_h2s (&issue->denom_hash.hash),
+ TALER_amount2s (&dso->dcd.denom_balance));
}
}
+ if (GNUNET_OK !=
+ TALER_wallet_recoup_refresh_verify (&coin->denom_pub_hash,
+ coin_blind,
+ &coin->coin_pub,
+ coin_sig))
+ {
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "recoup-refresh"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ amount),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &coin->coin_pub)));
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_irregular_loss),
+ &TALER_ARL_USE_AB (coin_irregular_loss),
+ amount);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+ }
return check_recoup (cc,
"recoup-refresh",
rowid,
@@ -2212,6 +2250,197 @@ recoup_refresh_cb (void *cls,
/**
+ * Function called with the results of iterate_denomination_info(),
+ * or directly (!). Used to check that we correctly signed the
+ * denomination and to warn if there are denominations not approved
+ * by this auditor.
+ *
+ * @param cls closure, NULL
+ * @param denom_pub public key, sometimes NULL (!)
+ * @param issue issuing information with value, fees and other info about the denomination.
+ */
+static void
+check_denomination (
+ void *cls,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_AuditorSignatureP auditor_sig;
+
+ (void) cls;
+ (void) denom_pub;
+ qs = TALER_ARL_edb->select_auditor_denom_sig (TALER_ARL_edb->cls,
+ &issue->denom_hash,
+ &TALER_ARL_auditor_pub,
+ &auditor_sig);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ return; /* skip! */
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Encountered denomination `%s' (%s) valid from %s (%llu-%llu) that this auditor is not auditing!\n",
+ GNUNET_h2s (&issue->denom_hash.hash),
+ TALER_amount2s (&issue->value),
+ GNUNET_TIME_timestamp2s (issue->start),
+ (unsigned long long) issue->start.abs_time.abs_value_us,
+ (unsigned long long) issue->expire_legal.abs_time.abs_value_us);
+ return; /* skip! */
+ }
+ if (GNUNET_OK !=
+ TALER_auditor_denom_validity_verify (
+ TALER_ARL_auditor_url,
+ &issue->denom_hash,
+ &TALER_ARL_master_pub,
+ issue->start,
+ issue->expire_withdraw,
+ issue->expire_deposit,
+ issue->expire_legal,
+ &issue->value,
+ &issue->fees,
+ &TALER_ARL_auditor_pub,
+ &auditor_sig))
+ {
+ TALER_ARL_report (report_denominations_without_sigs,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denomination",
+ &issue->denom_hash),
+ TALER_JSON_pack_amount ("value",
+ &issue->value),
+ TALER_JSON_pack_time_abs_human ("start_time",
+ issue->start.abs_time),
+ TALER_JSON_pack_time_abs_human ("end_time",
+ issue->expire_legal.
+ abs_time)));
+ }
+}
+
+
+/**
+ * Function called with details about purse deposits that have been made, with
+ * the goal of auditing the deposit's execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param deposit deposit details
+ * @param reserve_pub which reserve is the purse merged into, NULL if unknown
+ * @param flags purse flags
+ * @param auditor_balance purse balance (according to the
+ * auditor during auditing)
+ * @param purse_total target amount the purse should reach
+ * @param denom_pub denomination public key of @a coin_pub
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+purse_deposit_cb (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_EXCHANGEDB_PurseDeposit *deposit,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_Amount *auditor_balance,
+ const struct TALER_Amount *purse_total,
+ const struct TALER_DenominationPublicKey *denom_pub)
+{
+ struct CoinContext *cc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_DenominationHashP dh;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
+ struct DenominationSummary *ds;
+
+ (void) flags;
+ (void) auditor_balance;
+ (void) purse_total;
+ (void) reserve_pub;
+ GNUNET_assert (rowid >=
+ TALER_ARL_USE_PP (coins_purse_deposits_serial_id));
+ TALER_ARL_USE_PP (coins_purse_deposits_serial_id) = rowid + 1;
+ qs = TALER_ARL_get_denomination_info (denom_pub,
+ &issue,
+ &dh);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ report_row_inconsistency ("purse-deposits",
+ rowid,
+ "denomination key not found");
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+ }
+ qs = check_known_coin ("purse-deposit",
+ issue,
+ rowid,
+ &deposit->coin_pub,
+ denom_pub,
+ &deposit->amount);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ cc->qs = qs;
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_wallet_purse_deposit_verify (
+ NULL != deposit->exchange_base_url
+ ? deposit->exchange_base_url
+ : TALER_ARL_exchange_url,
+ &deposit->purse_pub,
+ &deposit->amount,
+ &dh,
+ &deposit->h_age_commitment,
+ &deposit->coin_pub,
+ &deposit->coin_sig))
+ {
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "purse-deposit"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ &deposit->amount),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &deposit->coin_pub)));
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_irregular_loss),
+ &TALER_ARL_USE_AB (coin_irregular_loss),
+ &deposit->amount);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+ }
+
+ /* update coin's denomination balance */
+ ds = get_denomination_summary (cc,
+ issue,
+ &issue->denom_hash);
+ if (NULL == ds)
+ {
+ report_row_inconsistency ("purse-deposit",
+ rowid,
+ "denomination key for purse-deposited coin unknown to auditor");
+ }
+ else
+ {
+ reduce_denom_balance (ds,
+ rowid,
+ &deposit->amount);
+ }
+
+ /* update global deposit fees */
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_deposit_fee_revenue),
+ &TALER_ARL_USE_AB (coin_deposit_fee_revenue),
+ &issue->fees.deposit);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+/**
* Analyze the exchange's processing of coins.
*
* @param cls closure
@@ -2227,11 +2456,28 @@ analyze_coins (void *cls)
(void) cls;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Checking denominations...\n");
+ qs = TALER_ARL_edb->iterate_denomination_info (TALER_ARL_edb->cls,
+ &check_denomination,
+ NULL);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Analyzing coins\n");
- qsp = TALER_ARL_adb->get_auditor_progress_coin (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &ppc);
+ qsp = TALER_ARL_adb->get_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_PP (coins_withdraw_serial_id),
+ TALER_ARL_GET_PP (coins_deposit_serial_id),
+ TALER_ARL_GET_PP (coins_melt_serial_id),
+ TALER_ARL_GET_PP (coins_refund_serial_id),
+ TALER_ARL_GET_PP (coins_recoup_serial_id),
+ TALER_ARL_GET_PP (coins_recoup_refresh_serial_id),
+ TALER_ARL_GET_PP (coins_purse_deposits_serial_id),
+ TALER_ARL_GET_PP (coins_purse_refunds_serial_id),
+ NULL);
if (0 > qsp)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsp);
@@ -2244,30 +2490,39 @@ analyze_coins (void *cls)
}
else
{
- ppc_start = ppc;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming coin audit at %llu/%llu/%llu/%llu/%llu\n",
- (unsigned long long) ppc.last_deposit_serial_id,
- (unsigned long long) ppc.last_melt_serial_id,
- (unsigned long long) ppc.last_refund_serial_id,
- (unsigned long long) ppc.last_withdraw_serial_id,
- (unsigned long long) ppc.last_recoup_refresh_serial_id);
+ GNUNET_log (
+ GNUNET_ERROR_TYPE_INFO,
+ "Resuming coin audit at %llu/%llu/%llu/%llu/%llu/%llu/%llu\n",
+ (unsigned long long) TALER_ARL_USE_PP (
+ coins_deposit_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ coins_melt_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ coins_refund_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ coins_withdraw_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ coins_recoup_refresh_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ coins_purse_deposits_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ coins_purse_refunds_serial_id));
}
/* setup 'cc' */
cc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
cc.denom_summaries = GNUNET_CONTAINER_multihashmap_create (256,
GNUNET_NO);
- qsx = TALER_ARL_adb->get_balance_summary (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &total_escrow_balance,
- &total_deposit_fee_income,
- &total_melt_fee_income,
- &total_refund_fee_income,
- &total_risk,
- &total_recoup_loss,
- &total_irregular_recoups);
+ qsx = TALER_ARL_adb->get_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_AB (coin_balance_risk),
+ TALER_ARL_GET_AB (total_escrowed),
+ TALER_ARL_GET_AB (coin_irregular_loss),
+ TALER_ARL_GET_AB (coin_melt_fee_revenue),
+ TALER_ARL_GET_AB (coin_deposit_fee_revenue),
+ TALER_ARL_GET_AB (coin_refund_fee_revenue),
+ TALER_ARL_GET_AB (total_recoup_loss),
+ NULL);
if (0 > qsx)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx);
@@ -2278,8 +2533,7 @@ analyze_coins (void *cls)
if (0 >
(qs = TALER_ARL_edb->select_withdrawals_above_serial_id (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- ppc.last_withdraw_serial_id,
+ TALER_ARL_USE_PP (coins_withdraw_serial_id),
&withdraw_cb,
&cc)) )
{
@@ -2293,8 +2547,7 @@ analyze_coins (void *cls)
if (0 >
(qs = TALER_ARL_edb->select_refunds_above_serial_id (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- ppc.last_refund_serial_id,
+ TALER_ARL_USE_PP (coins_refund_serial_id),
&refund_cb,
&cc)))
{
@@ -2304,12 +2557,26 @@ analyze_coins (void *cls)
if (0 > cc.qs)
return cc.qs;
+ /* process purse_refunds */
+ if (0 >
+ (qs = TALER_ARL_edb->select_purse_decisions_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (coins_purse_refunds_serial_id),
+ true, /* only go for refunds! */
+ &purse_refund_cb,
+ &cc)))
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ if (0 > cc.qs)
+ return cc.qs;
+
/* process recoups */
if (0 >
(qs = TALER_ARL_edb->select_recoup_refresh_above_serial_id (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- ppc.last_recoup_refresh_serial_id,
+ TALER_ARL_USE_PP (coins_recoup_refresh_serial_id),
&recoup_refresh_cb,
&cc)))
{
@@ -2321,8 +2588,7 @@ analyze_coins (void *cls)
if (0 >
(qs = TALER_ARL_edb->select_recoup_above_serial_id (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- ppc.last_recoup_serial_id,
+ TALER_ARL_USE_PP (coins_recoup_serial_id),
&recoup_cb,
&cc)))
{
@@ -2332,12 +2598,11 @@ analyze_coins (void *cls)
if (0 > cc.qs)
return cc.qs;
- /* process refreshs */
+ /* process refreshes */
if (0 >
(qs = TALER_ARL_edb->select_refreshes_above_serial_id (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- ppc.last_melt_serial_id,
+ TALER_ARL_USE_PP (coins_melt_serial_id),
&refresh_session_cb,
&cc)))
{
@@ -2349,10 +2614,9 @@ analyze_coins (void *cls)
/* process deposits */
if (0 >
- (qs = TALER_ARL_edb->select_deposits_above_serial_id (
+ (qs = TALER_ARL_edb->select_coin_deposits_above_serial_id (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- ppc.last_deposit_serial_id,
+ TALER_ARL_USE_PP (coins_deposit_serial_id),
&deposit_cb,
&cc)))
{
@@ -2362,6 +2626,20 @@ analyze_coins (void *cls)
if (0 > cc.qs)
return cc.qs;
+ /* process purse_deposits */
+ if (0 >
+ (qs = TALER_ARL_edb->select_purse_deposits_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (coins_purse_deposits_serial_id),
+ &purse_deposit_cb,
+ &cc)))
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ if (0 > cc.qs)
+ return cc.qs;
+
/* sync 'cc' back to disk */
cc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
GNUNET_CONTAINER_multihashmap_iterate (cc.denom_summaries,
@@ -2374,27 +2652,27 @@ analyze_coins (void *cls)
return cc.qs;
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsx)
- qs = TALER_ARL_adb->update_balance_summary (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &total_escrow_balance,
- &total_deposit_fee_income,
- &total_melt_fee_income,
- &total_refund_fee_income,
- &total_risk,
- &total_recoup_loss,
- &total_irregular_recoups);
+ qs = TALER_ARL_adb->update_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_AB (coin_balance_risk),
+ TALER_ARL_SET_AB (total_escrowed),
+ TALER_ARL_SET_AB (coin_irregular_loss),
+ TALER_ARL_SET_AB (coin_melt_fee_revenue),
+ TALER_ARL_SET_AB (coin_deposit_fee_revenue),
+ TALER_ARL_SET_AB (coin_refund_fee_revenue),
+ TALER_ARL_SET_AB (total_recoup_loss),
+ NULL);
else
- qs = TALER_ARL_adb->insert_balance_summary (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &total_escrow_balance,
- &total_deposit_fee_income,
- &total_melt_fee_income,
- &total_refund_fee_income,
- &total_risk,
- &total_recoup_loss,
- &total_irregular_recoups);
+ qs = TALER_ARL_adb->insert_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_AB (coin_balance_risk),
+ TALER_ARL_SET_AB (total_escrowed),
+ TALER_ARL_SET_AB (coin_irregular_loss),
+ TALER_ARL_SET_AB (coin_melt_fee_revenue),
+ TALER_ARL_SET_AB (coin_deposit_fee_revenue),
+ TALER_ARL_SET_AB (coin_refund_fee_revenue),
+ TALER_ARL_SET_AB (total_recoup_loss),
+ NULL);
if (0 >= qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
@@ -2402,15 +2680,29 @@ analyze_coins (void *cls)
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp)
- qs = TALER_ARL_adb->update_auditor_progress_coin (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &ppc);
+ qs = TALER_ARL_adb->update_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_PP (coins_withdraw_serial_id),
+ TALER_ARL_SET_PP (coins_deposit_serial_id),
+ TALER_ARL_SET_PP (coins_melt_serial_id),
+ TALER_ARL_SET_PP (coins_refund_serial_id),
+ TALER_ARL_SET_PP (coins_recoup_serial_id),
+ TALER_ARL_SET_PP (coins_recoup_refresh_serial_id),
+ TALER_ARL_SET_PP (coins_purse_deposits_serial_id),
+ TALER_ARL_SET_PP (coins_purse_refunds_serial_id),
+ NULL);
else
- qs = TALER_ARL_adb->insert_auditor_progress_coin (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &ppc);
+ qs = TALER_ARL_adb->insert_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_PP (coins_withdraw_serial_id),
+ TALER_ARL_SET_PP (coins_deposit_serial_id),
+ TALER_ARL_SET_PP (coins_melt_serial_id),
+ TALER_ARL_SET_PP (coins_refund_serial_id),
+ TALER_ARL_SET_PP (coins_recoup_serial_id),
+ TALER_ARL_SET_PP (coins_recoup_refresh_serial_id),
+ TALER_ARL_SET_PP (coins_purse_deposits_serial_id),
+ TALER_ARL_SET_PP (coins_purse_refunds_serial_id),
+ NULL);
if (0 >= qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -2419,12 +2711,17 @@ analyze_coins (void *cls)
return qs;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Concluded coin audit step at %llu/%llu/%llu/%llu/%llu\n",
- (unsigned long long) ppc.last_deposit_serial_id,
- (unsigned long long) ppc.last_melt_serial_id,
- (unsigned long long) ppc.last_refund_serial_id,
- (unsigned long long) ppc.last_withdraw_serial_id,
- (unsigned long long) ppc.last_recoup_refresh_serial_id);
+ "Concluded coin audit step at %llu/%llu/%llu/%llu/%llu/%llu/%llu\n",
+ (unsigned long long) TALER_ARL_USE_PP (coins_deposit_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (coins_melt_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (coins_refund_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (coins_withdraw_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ coins_recoup_refresh_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ coins_purse_deposits_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ coins_purse_refunds_serial_id));
return qs;
}
@@ -2443,8 +2740,6 @@ run (void *cls,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *c)
{
- json_t *report;
-
(void) cls;
(void) args;
(void) cfgfile;
@@ -2453,55 +2748,56 @@ run (void *cls,
if (GNUNET_OK !=
TALER_ARL_init (c))
{
- global_ret = 1;
+ global_ret = EXIT_FAILURE;
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Starting audit\n");
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&reported_emergency_loss));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&reported_emergency_risk_by_amount));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&reported_emergency_risk_by_count));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&reported_emergency_loss_by_count));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_escrow_balance));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (total_escrowed)));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_risk));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ coin_deposit_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_recoup_loss));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ coin_melt_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_irregular_recoups));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ coin_refund_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_deposit_fee_income));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (coin_balance_risk)));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_melt_fee_income));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (total_recoup_loss)));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_refund_fee_income));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ coin_irregular_loss)));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_arithmetic_delta_plus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_arithmetic_delta_minus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_bad_sig_loss));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_refresh_hanging));
GNUNET_assert (NULL !=
(report_emergencies = json_array ()));
@@ -2510,12 +2806,14 @@ run (void *cls,
GNUNET_assert (NULL !=
(report_row_inconsistencies = json_array ()));
GNUNET_assert (NULL !=
+ (report_denominations_without_sigs = json_array ()));
+ GNUNET_assert (NULL !=
(report_amount_arithmetic_inconsistencies =
json_array ()));
GNUNET_assert (NULL !=
(report_bad_sig_losses = json_array ()));
GNUNET_assert (NULL !=
- (report_refreshs_hanging = json_array ()));
+ (report_refreshes_hanging = json_array ()));
if (GNUNET_OK !=
TALER_ARL_setup_sessions_and_run (&analyze_coins,
NULL))
@@ -2525,117 +2823,101 @@ run (void *cls,
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Audit complete\n");
- report = json_pack ("{s:o, s:o, s:o, s:o, s:o,"
- " s:o, s:o, s:o, s:o, s:o,"
- " s:o, s:o, s:o, s:o, s:o,"
- " s:o, s:o, s:o, s:o, s:o,"
- " s:I, s:I, s:I, s:I, s:I,"
- " s:I, s:I, s:I, s:I, s:I,"
- " s:I, s:I, s:o, s:o, s:o}",
- /* Block #1 */
- "total_escrow_balance",
- TALER_JSON_from_amount (&total_escrow_balance),
- "total_active_risk",
- TALER_JSON_from_amount (&total_risk),
- "total_deposit_fee_income",
- TALER_JSON_from_amount (
- &total_deposit_fee_income),
- "total_melt_fee_income",
- TALER_JSON_from_amount (&total_melt_fee_income),
- "total_refund_fee_income",
- TALER_JSON_from_amount (
- &total_refund_fee_income),
- /* Block #2 */
- /* Tested in test-auditor.sh #18 */
- "emergencies",
- report_emergencies,
- /* Tested in test-auditor.sh #18 */
- "emergencies_risk_by_amount",
- TALER_JSON_from_amount (
- &reported_emergency_risk_by_amount),
- /* Tested in test-auditor.sh #4/#5/#6/#13/#26 */
- "bad_sig_losses",
- report_bad_sig_losses,
- /* Tested in test-auditor.sh #4/#5/#6/#13/#26 */
- "total_bad_sig_loss",
- TALER_JSON_from_amount (&total_bad_sig_loss),
- /* Tested in test-auditor.sh #31 */
- "row_inconsistencies",
- report_row_inconsistencies,
- /* Block #3 */
- /* Tested in test-auditor.sh #18 */
- "amount_arithmetic_inconsistencies",
- report_amount_arithmetic_inconsistencies,
- "total_arithmetic_delta_plus",
- TALER_JSON_from_amount (
- &total_arithmetic_delta_plus),
- "total_arithmetic_delta_minus",
- TALER_JSON_from_amount (
- &total_arithmetic_delta_minus),
- /* Tested in test-auditor.sh #12 */
- "total_refresh_hanging",
- TALER_JSON_from_amount (&total_refresh_hanging),
- /* Tested in test-auditor.sh #12 */
- "refresh_hanging",
- report_refreshs_hanging,
- /* Block #4 */
- "total_recoup_loss",
- TALER_JSON_from_amount (&total_recoup_loss),
- /* Tested in test-auditor.sh #18 */
- "emergencies_by_count",
- report_emergencies_by_count,
- /* Tested in test-auditor.sh #18 */
- "emergencies_risk_by_count",
- TALER_JSON_from_amount (
- &reported_emergency_risk_by_count),
- /* Tested in test-auditor.sh #18 */
- "emergencies_loss",
- TALER_JSON_from_amount (
- &reported_emergency_loss),
- /* Tested in test-auditor.sh #18 */
- "emergencies_loss_by_count",
- TALER_JSON_from_amount (
- &reported_emergency_loss_by_count),
- /* Block #5 */
- "start_ppc_withdraw_serial_id",
- (json_int_t) ppc_start.last_withdraw_serial_id,
- "start_ppc_deposit_serial_id",
- (json_int_t) ppc_start.last_deposit_serial_id,
- "start_ppc_melt_serial_id",
- (json_int_t) ppc_start.last_melt_serial_id,
- "start_ppc_refund_serial_id",
- (json_int_t) ppc_start.last_refund_serial_id,
- "start_ppc_recoup_serial_id",
- (json_int_t) ppc_start.last_recoup_serial_id,
- /* Block #6 */
- "start_ppc_recoup_refresh_serial_id",
- (json_int_t) ppc_start.
- last_recoup_refresh_serial_id,
- "end_ppc_withdraw_serial_id",
- (json_int_t) ppc.last_withdraw_serial_id,
- "end_ppc_deposit_serial_id",
- (json_int_t) ppc.last_deposit_serial_id,
- "end_ppc_melt_serial_id",
- (json_int_t) ppc.last_melt_serial_id,
- "end_ppc_refund_serial_id",
- (json_int_t) ppc.last_refund_serial_id,
- /* Block #7 */
- "end_ppc_recoup_serial_id",
- (json_int_t) ppc.last_recoup_serial_id,
- "end_ppc_recoup_refresh_serial_id",
- (json_int_t) ppc.last_recoup_refresh_serial_id,
- "auditor_start_time",
- TALER_ARL_json_from_time_abs (
- start_time),
- "auditor_end_time",
- TALER_ARL_json_from_time_abs (
- GNUNET_TIME_absolute_get ()),
- "total_irregular_recoups",
- TALER_JSON_from_amount (
- &total_irregular_recoups)
- );
- GNUNET_break (NULL != report);
- TALER_ARL_done (report);
+ TALER_ARL_done (
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("total_escrow_balance",
+ &TALER_ARL_USE_AB (total_escrowed)),
+ TALER_JSON_pack_amount ("total_deposit_fee_income",
+ &TALER_ARL_USE_AB (coin_deposit_fee_revenue)),
+ TALER_JSON_pack_amount ("total_melt_fee_income",
+ &TALER_ARL_USE_AB (coin_melt_fee_revenue)),
+ TALER_JSON_pack_amount ("total_refund_fee_income",
+ &TALER_ARL_USE_AB (coin_refund_fee_revenue)),
+ TALER_JSON_pack_amount ("total_active_risk",
+ &TALER_ARL_USE_AB (coin_balance_risk)),
+ TALER_JSON_pack_amount ("total_recoup_loss",
+ &TALER_ARL_USE_AB (total_recoup_loss)),
+ /* Tested in test-auditor.sh #4/#5/#6/#13/#26 */
+ TALER_JSON_pack_amount ("irregular_loss",
+ &TALER_ARL_USE_AB (coin_irregular_loss)),
+ /* Tested in test-auditor.sh #18 */
+ GNUNET_JSON_pack_array_steal ("emergencies",
+ report_emergencies),
+ /* Tested in test-auditor.sh #18 */
+ TALER_JSON_pack_amount ("emergencies_risk_by_amount",
+ &reported_emergency_risk_by_amount),
+ /* Tested in test-auditor.sh #31 */
+ GNUNET_JSON_pack_array_steal ("row_inconsistencies",
+ report_row_inconsistencies),
+ /* Tested in test-auditor.sh #18 */
+ GNUNET_JSON_pack_array_steal ("amount_arithmetic_inconsistencies",
+ report_amount_arithmetic_inconsistencies),
+ TALER_JSON_pack_amount ("total_arithmetic_delta_plus",
+ &total_arithmetic_delta_plus),
+ TALER_JSON_pack_amount ("total_arithmetic_delta_minus",
+ &total_arithmetic_delta_minus),
+ TALER_JSON_pack_amount ("total_refresh_hanging",
+ &total_refresh_hanging),
+ GNUNET_JSON_pack_array_steal ("bad_sig_losses",
+ report_bad_sig_losses),
+ /* Tested in test-auditor.sh #12 */
+ GNUNET_JSON_pack_array_steal ("refresh_hanging",
+ report_refreshes_hanging),
+ /* Tested in test-auditor.sh #18 */
+ GNUNET_JSON_pack_array_steal ("emergencies_by_count",
+ report_emergencies_by_count),
+ /* Tested in test-auditor.sh #18 */
+ TALER_JSON_pack_amount ("emergencies_risk_by_count",
+ &reported_emergency_risk_by_count),
+ /* Tested in test-auditor.sh #18 */
+ TALER_JSON_pack_amount ("emergencies_loss",
+ &reported_emergency_loss),
+ /* Tested in test-auditor.sh #18 */
+ TALER_JSON_pack_amount ("emergencies_loss_by_count",
+ &reported_emergency_loss_by_count),
+ GNUNET_JSON_pack_uint64 ("start_ppc_withdraw_serial_id",
+ 0 /* not implemented */),
+ GNUNET_JSON_pack_uint64 ("start_ppc_deposit_serial_id",
+ 0 /* not implemented */),
+ GNUNET_JSON_pack_uint64 ("start_ppc_melt_serial_id",
+ 0 /* not implemented */),
+ GNUNET_JSON_pack_uint64 ("start_ppc_refund_serial_id",
+ 0 /* not implemented */),
+ GNUNET_JSON_pack_uint64 ("start_ppc_recoup_serial_id",
+ 0 /* not implemented */),
+ GNUNET_JSON_pack_uint64 ("start_ppc_recoup_refresh_serial_id",
+ 0 /* not implemented */),
+ GNUNET_JSON_pack_uint64 ("start_ppc_purse_deposits_serial_id",
+ 0 /* not implemented */),
+ GNUNET_JSON_pack_uint64 ("start_ppc_purse_refunds_serial_id",
+ 0 /* not implemented */),
+ GNUNET_JSON_pack_uint64 ("end_ppc_withdraw_serial_id",
+ TALER_ARL_USE_PP (coins_withdraw_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppc_deposit_serial_id",
+ TALER_ARL_USE_PP (coins_deposit_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppc_melt_serial_id",
+ TALER_ARL_USE_PP (coins_melt_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppc_refund_serial_id",
+ TALER_ARL_USE_PP (coins_refund_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppc_recoup_serial_id",
+ TALER_ARL_USE_PP (coins_recoup_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppc_recoup_refresh_serial_id",
+ TALER_ARL_USE_PP (
+ coins_recoup_refresh_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppc_purse_deposits_serial_id",
+ TALER_ARL_USE_PP (
+ coins_purse_deposits_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppc_purse_refunds_serial_id",
+ TALER_ARL_USE_PP (
+ coins_purse_refunds_serial_id)),
+ TALER_JSON_pack_time_abs_human (
+ "auditor_start_time",
+ start_time),
+ TALER_JSON_pack_time_abs_human ("auditor_end_time",
+ GNUNET_TIME_absolute_get ()),
+ GNUNET_JSON_pack_array_steal (
+ "unsigned_denominations",
+ report_denominations_without_sigs)));
}
@@ -2651,33 +2933,41 @@ main (int argc,
char *const *argv)
{
const struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_base32_auto ('m',
- "exchange-key",
- "KEY",
- "public key of the exchange (Crockford base32 encoded)",
- &TALER_ARL_master_pub),
+ GNUNET_GETOPT_option_flag ('i',
+ "internal",
+ "perform checks only applicable for exchange-internal audits",
+ &internal_checks),
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue 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 */
(void) TALER_project_data_default ();
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-helper-auditor-coins",
- "MESSAGE",
- NULL));
if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc,
- argv,
- "taler-helper-auditor-coins",
- "Audit Taler coin processing",
- options,
- &run,
- NULL))
- return 1;
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ ret = GNUNET_PROGRAM_run (
+ argc,
+ argv,
+ "taler-helper-auditor-coins",
+ gettext_noop ("Audit Taler coin processing"),
+ options,
+ &run,
+ NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
return global_ret;
}
diff --git a/src/auditor/taler-helper-auditor-deposits.c b/src/auditor/taler-helper-auditor-deposits.c
index 1b6a46f1d..3dbce0183 100644
--- a/src/auditor/taler-helper-auditor-deposits.c
+++ b/src/auditor/taler-helper-auditor-deposits.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2016-2020 Taler Systems SA
+ Copyright (C) 2016-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero Public License as published by the Free Software
@@ -17,6 +17,7 @@
* @file auditor/taler-helper-auditor-deposits.c
* @brief audits an exchange database for deposit confirmation consistency
* @author Christian Grothoff
+ * @author Nic Eigel
*
* We simply check that all of the deposit confirmations reported to us
* by merchants were also reported to us by the exchange.
@@ -29,7 +30,29 @@
#include "taler_bank_service.h"
#include "taler_signatures.h"
#include "report-lib.h"
+#include "taler_dbevents.h"
+#include <jansson.h>
+/*
+--
+-- SELECT serial_id,h_contract_terms,h_wire,merchant_pub ...
+-- FROM auditor.auditor_deposit_confirmations
+-- WHERE NOT ancient
+-- ORDER BY exchange_timestamp ASC;
+-- SELECT 1
+- FROM exchange.deposits dep
+ WHERE ($RESULT.contract_terms = dep.h_contract_terms) AND ($RESULT.h_wire = dep.h_wire) AND ...);
+-- IF FOUND
+-- DELETE FROM auditor.auditor_deposit_confirmations
+-- WHERE serial_id = $RESULT.serial_id;
+-- SELECT exchange_timestamp AS latest
+-- FROM exchange.deposits ORDER BY exchange_timestamp DESC;
+-- latest -= 1 hour; // time is not exactly monotonic...
+-- UPDATE auditor.deposit_confirmations
+-- SET ancient=TRUE
+-- WHERE exchange_timestamp < latest
+-- AND NOT ancient;
+*/
/**
* Return value from main().
@@ -37,6 +60,14 @@
static int global_ret;
/**
+ * Run in test mode. Exit when idle instead of
+ * going to sleep and waiting for more work.
+ *
+ * FIXME: not yet implemented!
+ */
+static int test_mode;
+
+/**
* Array of reports about missing deposit confirmations.
*/
static json_t *report_deposit_confirmation_inconsistencies;
@@ -51,6 +82,22 @@ static json_int_t number_missed_deposit_confirmations;
*/
static struct TALER_Amount total_missed_deposit_confirmations;
+/**
+ * Should we run checks that only work for exchange-internal audits?
+ */
+static int internal_checks;
+
+static struct GNUNET_DB_EventHandler *eh;
+
+/**
+ * Our database plugin.
+ */
+static struct TALER_AUDITORDB_Plugin *db_plugin;
+
+/**
+ * The auditors's configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
/**
* Closure for #test_dc.
@@ -88,7 +135,6 @@ struct DepositConfirmationContext
};
-
/**
* Given a deposit confirmation from #TALER_ARL_adb, check that it is also
* in #TALER_ARL_edb. Update the deposit confirmation context accordingly.
@@ -96,61 +142,69 @@ struct DepositConfirmationContext
* @param cls our `struct DepositConfirmationContext`
* @param serial_id row of the @a dc in the database
* @param dc the deposit confirmation we know
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop iterating
*/
-static void
+static enum GNUNET_GenericReturnValue
test_dc (void *cls,
uint64_t serial_id,
const struct TALER_AUDITORDB_DepositConfirmation *dc)
{
struct DepositConfirmationContext *dcc = cls;
+ bool missing = false;
dcc->last_seen_coin_serial = serial_id;
+ for (unsigned int i = 0; i < dc->num_coins; i++)
{
enum GNUNET_DB_QueryStatus qs;
- struct TALER_EXCHANGEDB_Deposit dep = {
- .coin.coin_pub = dc->coin_pub,
- .h_contract_terms = dc->h_contract_terms,
- .merchant_pub = dc->merchant,
- .h_wire = dc->h_wire,
- .refund_deadline = dc->refund_deadline
- };
-
- qs = TALER_ARL_edb->have_deposit (TALER_ARL_edb->cls,
- TALER_ARL_esession,
- &dep,
- GNUNET_NO /* do not check refund deadline */);
- if (qs > 0)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Found deposit %s in exchange database\n",
- GNUNET_h2s (&dc->h_contract_terms));
- return; /* found, all good */
- }
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+ struct TALER_Amount deposit_fee;
+
+ qs = TALER_ARL_edb->have_deposit2 (TALER_ARL_edb->cls,
+ &dc->h_contract_terms,
+ &dc->h_wire,
+ &dc->coin_pubs[i],
+ &dc->merchant,
+ dc->refund_deadline,
+ &deposit_fee,
+ &exchange_timestamp);
+ missing |= (0 == qs);
if (qs < 0)
{
GNUNET_break (0); /* DB error, complain */
dcc->qs = qs;
- return;
+ return GNUNET_SYSERR;
}
}
+ if (! missing)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Found deposit %s in exchange database\n",
+ GNUNET_h2s (&dc->h_contract_terms.hash));
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK; /* all coins found, all good */
+ }
/* deposit confirmation missing! report! */
- TALER_ARL_report (report_deposit_confirmation_inconsistencies,
- json_pack ("{s:o, s:o, s:I, s:o}",
- "timestamp",
- TALER_ARL_json_from_time_abs (dc->timestamp),
- "amount",
- TALER_JSON_from_amount (&dc->amount_without_fee),
- "rowid",
- (json_int_t) serial_id,
- "account",
- GNUNET_JSON_from_data_auto (&dc->h_wire)));
+ TALER_ARL_report (
+ report_deposit_confirmation_inconsistencies,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ dc->exchange_timestamp.abs_time),
+ TALER_JSON_pack_amount ("amount",
+ &dc->total_without_fee),
+ GNUNET_JSON_pack_uint64 ("rowid",
+ serial_id),
+ GNUNET_JSON_pack_data_auto ("account",
+ &dc->h_wire)));
dcc->first_missed_coin_serial = GNUNET_MIN (dcc->first_missed_coin_serial,
serial_id);
dcc->missed_count++;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&dcc->missed_amount,
- &dcc->missed_amount,
- &dc->amount_without_fee));
+ TALER_ARL_amount_add (&dcc->missed_amount,
+ &dcc->missed_amount,
+ &dc->total_without_fee);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
}
@@ -164,21 +218,18 @@ test_dc (void *cls,
static enum GNUNET_DB_QueryStatus
analyze_deposit_confirmations (void *cls)
{
- struct TALER_AUDITORDB_ProgressPointDepositConfirmation ppdc;
+ TALER_ARL_DEF_PP (deposit_confirmation_serial_id);
struct DepositConfirmationContext dcc;
enum GNUNET_DB_QueryStatus qs;
enum GNUNET_DB_QueryStatus qsx;
enum GNUNET_DB_QueryStatus qsp;
-
(void) cls;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Analyzing deposit confirmations\n");
- ppdc.last_deposit_confirmation_serial_id = 0;
- qsp = TALER_ARL_adb->get_auditor_progress_deposit_confirmation (
+
+ qsp = TALER_ARL_adb->get_auditor_progress (
TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &ppdc);
+ TALER_ARL_GET_PP (deposit_confirmation_serial_id),
+ NULL);
+
if (0 > qsp)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsp);
@@ -193,23 +244,26 @@ analyze_deposit_confirmations (void *cls)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Resuming deposit confirmation audit at %llu\n",
- (unsigned long long) ppdc.last_deposit_confirmation_serial_id);
+ (unsigned long long) TALER_ARL_USE_PP (
+ deposit_confirmation_serial_id));
}
/* setup 'cc' */
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&dcc.missed_amount));
dcc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
dcc.missed_count = 0LLU;
dcc.first_missed_coin_serial = UINT64_MAX;
- qsx = TALER_ARL_adb->get_deposit_confirmations (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- ppdc.
- last_deposit_confirmation_serial_id,
- &test_dc,
- &dcc);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "lastdepconfserialid %lu\n",
+ TALER_ARL_USE_PP (deposit_confirmation_serial_id));
+ qsx = TALER_ARL_adb->get_deposit_confirmations (
+ TALER_ARL_adb->cls,
+ TALER_ARL_USE_PP (deposit_confirmation_serial_id),
+ true, /* return suppressed */
+ &test_dc,
+ &dcc);
if (0 > qsx)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx);
@@ -218,30 +272,30 @@ analyze_deposit_confirmations (void *cls)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Analyzed %d deposit confirmations (above serial ID %llu)\n",
(int) qsx,
- (unsigned long long) ppdc.last_deposit_confirmation_serial_id);
+ (unsigned long long) TALER_ARL_USE_PP (
+ deposit_confirmation_serial_id));
if (0 > dcc.qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == dcc.qs);
return dcc.qs;
}
- if (UINT64_MAX == dcc.first_missed_coin_serial)
- ppdc.last_deposit_confirmation_serial_id = dcc.last_seen_coin_serial;
- else
- ppdc.last_deposit_confirmation_serial_id = dcc.first_missed_coin_serial - 1;
+ /* if (UINT64_MAX == dcc.first_missed_coin_serial)
+ ppdc.last_deposit_confirmation_serial_id = dcc.last_seen_coin_serial;
+ else
+ ppdc.last_deposit_confirmation_serial_id = dcc.first_missed_coin_serial - 1;
+ */
/* sync 'cc' back to disk */
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp)
- qs = TALER_ARL_adb->update_auditor_progress_deposit_confirmation (
+ qs = TALER_ARL_adb->update_auditor_progress (
TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &ppdc);
+ TALER_ARL_SET_PP (deposit_confirmation_serial_id),
+ NULL);
else
- qs = TALER_ARL_adb->insert_auditor_progress_deposit_confirmation (
+ qs = TALER_ARL_adb->insert_auditor_progress (
TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &ppdc);
+ TALER_ARL_SET_PP (deposit_confirmation_serial_id),
+ NULL);
if (0 >= qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -249,17 +303,90 @@ analyze_deposit_confirmations (void *cls)
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
return qs;
}
+
number_missed_deposit_confirmations = (json_int_t) dcc.missed_count;
total_missed_deposit_confirmations = dcc.missed_amount;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Concluded deposit confirmation audit step at %llu\n",
- (unsigned long long) ppdc.last_deposit_confirmation_serial_id);
+ (unsigned long long) TALER_ARL_USE_PP (
+ deposit_confirmation_serial_id));
return qs;
}
/**
+ * Function called on events received from Postgres.
+ *
+ * @param cls closure, NULL
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_notify (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received notification for new deposit_confirmation\n");
+
+ (void) cls;
+ (void) extra;
+ (void) extra_size;
+
+ if (NULL ==
+ (db_plugin = TALER_AUDITORDB_plugin_load (cfg)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_assert (NULL !=
+ (report_deposit_confirmation_inconsistencies = json_array ()));
+
+ if (GNUNET_OK !=
+ TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations,
+ NULL))
+ {
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Deposit audit complete\n");
+ TALER_ARL_done (
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("deposit_confirmation_inconsistencies",
+ report_deposit_confirmation_inconsistencies),
+ GNUNET_JSON_pack_uint64 ("missing_deposit_confirmation_count",
+ number_missed_deposit_confirmations),
+ TALER_JSON_pack_amount ("missing_deposit_confirmation_total",
+ &total_missed_deposit_confirmations),
+ TALER_JSON_pack_time_abs_human ("auditor_start_time",
+ start_time),
+ TALER_JSON_pack_time_abs_human ("auditor_end_time",
+ GNUNET_TIME_absolute_get ())));
+}
+
+
+/**
+ * Function called on shutdown.
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+
+ db_plugin->event_listen_cancel (eh);
+ eh = NULL;
+ TALER_AUDITORDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ TALER_ARL_done (NULL);
+}
+
+
+/**
* Main function that will be run.
*
* @param cls closure
@@ -276,14 +403,46 @@ run (void *cls,
(void) cls;
(void) args;
(void) cfgfile;
+ cfg = c;
+
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Launching deposit auditor\n");
if (GNUNET_OK !=
TALER_ARL_init (c))
{
- global_ret = 1;
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+
+ if (NULL ==
+ (db_plugin = TALER_AUDITORDB_plugin_load (cfg)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem\n");
+ GNUNET_SCHEDULER_shutdown ();
return;
}
+ if (GNUNET_OK !=
+ db_plugin->preflight (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to connect to database\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_NEW_DEPOSIT_CONFIRMATION)
+ };
+ eh = db_plugin->event_listen (db_plugin->cls,
+ &es,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &db_notify,
+ NULL);
+
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Starting deposit audit\n");
GNUNET_assert (NULL !=
@@ -292,32 +451,23 @@ run (void *cls,
TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations,
NULL))
{
- global_ret = 1;
+ global_ret = EXIT_FAILURE;
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Deposit audit complete\n");
- {
- json_t *report;
-
- report = json_pack ("{s:o, s:I, s:o, s:o, s:o}",
- "deposit_confirmation_inconsistencies",
- report_deposit_confirmation_inconsistencies,
- "missing_deposit_confirmation_count",
- (json_int_t) number_missed_deposit_confirmations,
- "missing_deposit_confirmation_total",
- TALER_JSON_from_amount (
- &total_missed_deposit_confirmations),
- "auditor_start_time",
- TALER_ARL_json_from_time_abs (
- start_time),
- "auditor_end_time",
- TALER_ARL_json_from_time_abs (
- GNUNET_TIME_absolute_get ())
- );
- GNUNET_break (NULL != report);
- TALER_ARL_done (report);
- }
+ TALER_ARL_done (
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("deposit_confirmation_inconsistencies",
+ report_deposit_confirmation_inconsistencies),
+ GNUNET_JSON_pack_uint64 ("missing_deposit_confirmation_count",
+ number_missed_deposit_confirmations),
+ TALER_JSON_pack_amount ("missing_deposit_confirmation_total",
+ &total_missed_deposit_confirmations),
+ TALER_JSON_pack_time_abs_human ("auditor_start_time",
+ start_time),
+ TALER_JSON_pack_time_abs_human ("auditor_end_time",
+ GNUNET_TIME_absolute_get ())));
}
@@ -333,33 +483,42 @@ main (int argc,
char *const *argv)
{
const struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_base32_auto ('m',
- "exchange-key",
- "KEY",
- "public key of the exchange (Crockford base32 encoded)",
- &TALER_ARL_master_pub),
+ GNUNET_GETOPT_option_flag ('i',
+ "internal",
+ "perform checks only applicable for exchange-internal audits",
+ &internal_checks),
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue 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 */
(void) TALER_project_data_default ();
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-helper-auditor-deposits",
- "MESSAGE",
- NULL));
if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc,
- argv,
- "taler-helper-auditor-deposits",
- "Audit Taler exchange database for deposit confirmation consistency",
- options,
- &run,
- NULL))
- return 1;
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ ret = GNUNET_PROGRAM_run (
+ argc,
+ argv,
+ "taler-helper-auditor-deposits",
+ gettext_noop (
+ "Audit Taler exchange database for deposit confirmation consistency"),
+ options,
+ &run,
+ NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
return global_ret;
}
diff --git a/src/auditor/taler-helper-auditor-purses.c b/src/auditor/taler-helper-auditor-purses.c
new file mode 100644
index 000000000..967ac13a7
--- /dev/null
+++ b/src/auditor/taler-helper-auditor-purses.c
@@ -0,0 +1,1451 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2016-2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero 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 Public License for more details.
+
+ You should have received a copy of the GNU Affero Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file auditor/taler-helper-auditor-purses.c
+ * @brief audits the purses of an exchange database
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_auditordb_plugin.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_bank_service.h"
+#include "taler_signatures.h"
+#include "report-lib.h"
+
+
+/**
+ * Use a 1 day grace period to deal with clocks not being perfectly synchronized.
+ */
+#define EXPIRATION_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * Run in test mode. Exit when idle instead of
+ * going to sleep and waiting for more work.
+ *
+ * FIXME: not yet implemented!
+ */
+static int test_mode;
+
+/**
+ * Checkpointing our progress for purses.
+ */
+static TALER_ARL_DEF_PP (purse_account_merge_serial_id);
+static TALER_ARL_DEF_PP (purse_decision_serial_id);
+static TALER_ARL_DEF_PP (purse_deposits_serial_id);
+static TALER_ARL_DEF_PP (purse_merges_serial_id);
+static TALER_ARL_DEF_PP (purse_request_serial_id);
+static TALER_ARL_DEF_PP (purse_open_counter);
+static TALER_ARL_DEF_AB (purse_global_balance);
+
+/**
+ * Array of reports about row inconsistencies.
+ */
+static json_t *report_row_inconsistencies;
+
+/**
+ * Array of reports about purse balance insufficient inconsistencies.
+ */
+static json_t *report_purse_balance_insufficient_inconsistencies;
+
+/**
+ * Total amount purses were merged with insufficient balance.
+ */
+static struct TALER_Amount total_balance_insufficient_loss;
+
+/**
+ * Total amount purse decisions are delayed past deadline.
+ */
+static struct TALER_Amount total_delayed_decisions;
+
+/**
+ * Array of reports about purses's not being closed inconsistencies.
+ */
+static json_t *report_purse_not_closed_inconsistencies;
+
+/**
+ * Total amount affected by purses not having been closed on time.
+ */
+static struct TALER_Amount total_balance_purse_not_closed;
+
+/**
+ * Report about amount calculation differences (causing profit
+ * or loss at the exchange).
+ */
+static json_t *report_amount_arithmetic_inconsistencies;
+
+/**
+ * Profits the exchange made by bad amount calculations.
+ */
+static struct TALER_Amount total_arithmetic_delta_plus;
+
+/**
+ * Losses the exchange made by bad amount calculations.
+ */
+static struct TALER_Amount total_arithmetic_delta_minus;
+
+/**
+ * Array of reports about coin operations with bad signatures.
+ */
+static json_t *report_bad_sig_losses;
+
+/**
+ * Total amount lost by operations for which signatures were invalid.
+ */
+static struct TALER_Amount total_bad_sig_loss;
+
+/**
+ * Should we run checks that only work for exchange-internal audits?
+ */
+static int internal_checks;
+
+/* ***************************** Report logic **************************** */
+
+
+/**
+ * Report a (serious) inconsistency in the exchange's database with
+ * respect to calculations involving amounts.
+ *
+ * @param operation what operation had the inconsistency
+ * @param rowid affected row, 0 if row is missing
+ * @param exchange amount calculated by exchange
+ * @param auditor amount calculated by auditor
+ * @param profitable 1 if @a exchange being larger than @a auditor is
+ * profitable for the exchange for this operation,
+ * -1 if @a exchange being smaller than @a auditor is
+ * profitable for the exchange, and 0 if it is unclear
+ */
+static void
+report_amount_arithmetic_inconsistency (
+ const char *operation,
+ uint64_t rowid,
+ const struct TALER_Amount *exchange,
+ const struct TALER_Amount *auditor,
+ int profitable)
+{
+ struct TALER_Amount delta;
+ struct TALER_Amount *target;
+
+ if (0 < TALER_amount_cmp (exchange,
+ auditor))
+ {
+ /* exchange > auditor */
+ TALER_ARL_amount_subtract (&delta,
+ exchange,
+ auditor);
+ }
+ else
+ {
+ /* auditor < exchange */
+ profitable = -profitable;
+ TALER_ARL_amount_subtract (&delta,
+ auditor,
+ exchange);
+ }
+ TALER_ARL_report (report_amount_arithmetic_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ operation),
+ GNUNET_JSON_pack_uint64 ("rowid",
+ rowid),
+ TALER_JSON_pack_amount ("exchange",
+ exchange),
+ TALER_JSON_pack_amount ("auditor",
+ auditor),
+ GNUNET_JSON_pack_int64 ("profitable",
+ profitable)));
+ if (0 != profitable)
+ {
+ target = (1 == profitable)
+ ? &total_arithmetic_delta_plus
+ : &total_arithmetic_delta_minus;
+ TALER_ARL_amount_add (target,
+ target,
+ &delta);
+ }
+}
+
+
+/**
+ * Report a (serious) inconsistency in the exchange's database.
+ *
+ * @param table affected table
+ * @param rowid affected row, 0 if row is missing
+ * @param diagnostic message explaining the problem
+ */
+static void
+report_row_inconsistency (const char *table,
+ uint64_t rowid,
+ const char *diagnostic)
+{
+ TALER_ARL_report (report_row_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ table),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ GNUNET_JSON_pack_string ("diagnostic",
+ diagnostic)));
+}
+
+
+/**
+ * Obtain the purse fee for a purse created at @a time.
+ *
+ * @param atime when was the purse created
+ * @param[out] fee set to the purse fee
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+get_purse_fee (struct GNUNET_TIME_Timestamp atime,
+ struct TALER_Amount *fee)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct TALER_GlobalFeeSet fees;
+ struct GNUNET_TIME_Relative ptimeout;
+ struct GNUNET_TIME_Relative hexp;
+ uint32_t pacl;
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ TALER_ARL_edb->get_global_fee (TALER_ARL_edb->cls,
+ atime,
+ &start_date,
+ &end_date,
+ &fees,
+ &ptimeout,
+ &hexp,
+ &pacl,
+ &master_sig))
+ {
+ char *diag;
+
+ GNUNET_asprintf (&diag,
+ "purse fee unavailable at %s\n",
+ GNUNET_TIME_timestamp2s (atime));
+ report_row_inconsistency ("purse-fee",
+ atime.abs_time.abs_value_us,
+ diag);
+ GNUNET_free (diag);
+ return GNUNET_SYSERR;
+ }
+ *fee = fees.purse;
+ return GNUNET_OK;
+}
+
+
+/* ***************************** Analyze purses ************************ */
+/* This logic checks the purses_requests, purse_deposits,
+ purse_refunds, purse_merges and account_merges */
+
+/**
+ * Summary data we keep per purse.
+ */
+struct PurseSummary
+{
+ /**
+ * Public key of the purse.
+ * Always set when the struct is first initialized.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Balance of the purse from deposits (includes purse fee, excludes deposit
+ * fees), as calculated by auditor.
+ */
+ struct TALER_Amount balance;
+
+ /**
+ * Expected value of the purse, excludes purse fee.
+ */
+ struct TALER_Amount total_value;
+
+ /**
+ * Purse balance according to exchange DB.
+ */
+ struct TALER_Amount exchange_balance;
+
+ /**
+ * Contract terms of the purse.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Merge timestamp (as per exchange DB).
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+
+ /**
+ * Purse creation date. This is when the merge
+ * fee is applied.
+ */
+ struct GNUNET_TIME_Timestamp creation_date;
+
+ /**
+ * Purse expiration date.
+ */
+ struct GNUNET_TIME_Timestamp expiration_date;
+
+ /**
+ * Did we have a previous purse info? Used to decide between UPDATE and
+ * INSERT later. Initialized in #load_auditor_purse_summary().
+ */
+ bool had_pi;
+
+ /**
+ * Was the purse deleted? FIXME: Not yet handled (do we need to? purse
+ * might just appear as expired eventually; but in the meantime, exchange
+ * may seem to have refunded the coins for no good reason...), also we do
+ * not yet check the deletion signature.
+ */
+ bool purse_deleted;
+
+ /**
+ * Was the purse refunded? FIXME: Not yet handled (do we need to?)
+ */
+ bool purse_refunded;
+
+};
+
+
+/**
+ * Load the auditor's remembered state about the purse into @a ps.
+ *
+ * @param[in,out] ps purse summary to (fully) initialize
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+load_auditor_purse_summary (struct PurseSummary *ps)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t rowid;
+
+ qs = TALER_ARL_adb->get_purse_info (TALER_ARL_adb->cls,
+ &ps->purse_pub,
+ &rowid,
+ &ps->balance,
+ &ps->expiration_date);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ ps->had_pi = false;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &ps->balance));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Creating fresh purse `%s'\n",
+ TALER_B2S (&ps->purse_pub));
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ ps->had_pi = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Auditor remembers purse `%s' has balance %s\n",
+ TALER_B2S (&ps->purse_pub),
+ TALER_amount2s (&ps->balance));
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+/**
+ * Closure to the various callbacks we make while checking a purse.
+ */
+struct PurseContext
+{
+ /**
+ * Map from hash of purse's public key to a `struct PurseSummary`.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *purses;
+
+ /**
+ * Transaction status code, set to error codes if applicable.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+
+};
+
+
+/**
+ * Create a new reserve for @a reserve_pub in @a rc.
+ *
+ * @param[in,out] pc context to update
+ * @param purse_pub key for which to create a purse
+ * @return NULL on error
+ */
+static struct PurseSummary *
+setup_purse (struct PurseContext *pc,
+ const struct TALER_PurseContractPublicKeyP *purse_pub)
+{
+ struct PurseSummary *ps;
+ struct GNUNET_HashCode key;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_CRYPTO_hash (purse_pub,
+ sizeof (*purse_pub),
+ &key);
+ ps = GNUNET_CONTAINER_multihashmap_get (pc->purses,
+ &key);
+ if (NULL != ps)
+ return ps;
+ ps = GNUNET_new (struct PurseSummary);
+ ps->purse_pub = *purse_pub;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &ps->balance));
+ /* get purse meta-data from exchange DB */
+ qs = TALER_ARL_edb->select_purse (TALER_ARL_edb->cls,
+ purse_pub,
+ &ps->creation_date,
+ &ps->expiration_date,
+ &ps->total_value,
+ &ps->exchange_balance,
+ &ps->h_contract_terms,
+ &ps->merge_timestamp,
+ &ps->purse_deleted,
+ &ps->purse_refunded);
+ if (0 >= qs)
+ {
+ GNUNET_free (ps);
+ pc->qs = qs;
+ return NULL;
+ }
+ if (0 > (qs = load_auditor_purse_summary (ps)))
+ {
+ GNUNET_free (ps);
+ pc->qs = qs;
+ return NULL;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (pc->purses,
+ &key,
+ ps,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ return ps;
+}
+
+
+/**
+ * Function called on purse requests.
+ *
+ * @param cls closure
+ * @param rowid which row in the database was the request stored in
+ * @param purse_pub public key of the purse
+ * @param merge_pub public key representing the merge capability
+ * @param purse_creation when was the purse created
+ * @param purse_expiration when would an unmerged purse expire
+ * @param h_contract_terms contract associated with the purse
+ * @param age_limit the age limit for deposits into the purse
+ * @param target_amount amount to be put into the purse
+ * @param purse_sig signature of the purse over the initialization data
+ * @return #GNUNET_OK to continue to iterate
+ */
+static enum GNUNET_GenericReturnValue
+handle_purse_requested (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct GNUNET_TIME_Timestamp purse_creation,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t age_limit,
+ const struct TALER_Amount *target_amount,
+ const struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct PurseContext *pc = cls;
+ struct PurseSummary *ps;
+ struct GNUNET_HashCode key;
+
+ TALER_ARL_USE_PP (purse_request_serial_id) = rowid;
+ if (GNUNET_OK !=
+ TALER_wallet_purse_create_verify (purse_expiration,
+ h_contract_terms,
+ merge_pub,
+ age_limit,
+ target_amount,
+ purse_pub,
+ purse_sig))
+ {
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "purse-reqeust"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ target_amount),
+ GNUNET_JSON_pack_data_auto ("key_pub",
+ purse_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ target_amount);
+ }
+ GNUNET_CRYPTO_hash (purse_pub,
+ sizeof (*purse_pub),
+ &key);
+ ps = GNUNET_new (struct PurseSummary);
+ ps->purse_pub = *purse_pub;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &ps->balance));
+ ps->creation_date = purse_creation;
+ ps->expiration_date = purse_expiration;
+ ps->total_value = *target_amount;
+ ps->h_contract_terms = *h_contract_terms;
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (pc->purses,
+ &key,
+ ps,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about purse deposits that have been made, with
+ * the goal of auditing the deposit's execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param deposit deposit details
+ * @param reserve_pub which reserve is the purse merged into, NULL if unknown
+ * @param flags purse flags
+ * @param auditor_balance purse balance (according to the
+ * auditor during auditing)
+ * @param purse_total target amount the purse should reach
+ * @param denom_pub denomination public key of @a coin_pub
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+handle_purse_deposits (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_EXCHANGEDB_PurseDeposit *deposit,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_Amount *auditor_balance,
+ const struct TALER_Amount *purse_total,
+ const struct TALER_DenominationPublicKey *denom_pub)
+{
+ struct PurseContext *pc = cls;
+ struct TALER_Amount amount_minus_fee;
+ struct PurseSummary *ps;
+ const char *base_url
+ = (NULL == deposit->exchange_base_url)
+ ? TALER_ARL_exchange_url
+ : deposit->exchange_base_url;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /* should be monotonically increasing */
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_deposits_serial_id));
+ TALER_ARL_USE_PP (purse_deposits_serial_id) = rowid + 1;
+
+ {
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TALER_ARL_get_denomination_info (denom_pub,
+ &issue,
+ &h_denom_pub);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Hard database error trying to get denomination %s from database!\n",
+ TALER_B2S (denom_pub));
+ pc->qs = qs;
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ report_row_inconsistency ("purse-deposit",
+ rowid,
+ "denomination key not found");
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+ }
+ TALER_ARL_amount_subtract (&amount_minus_fee,
+ &deposit->amount,
+ &issue->fees.deposit);
+ }
+
+ if (GNUNET_OK !=
+ TALER_wallet_purse_deposit_verify (base_url,
+ &deposit->purse_pub,
+ &deposit->amount,
+ &h_denom_pub,
+ &deposit->h_age_commitment,
+ &deposit->coin_pub,
+ &deposit->coin_sig))
+ {
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "purse-deposit"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ &deposit->amount),
+ GNUNET_JSON_pack_data_auto ("key_pub",
+ &deposit->coin_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ &deposit->amount);
+ return GNUNET_OK;
+ }
+
+ ps = setup_purse (pc,
+ &deposit->purse_pub);
+ if (NULL == ps)
+ {
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs)
+ {
+ report_row_inconsistency ("purse-deposit",
+ rowid,
+ "purse not found");
+ }
+ else
+ {
+ /* Database trouble!? */
+ GNUNET_break (0);
+ }
+ return GNUNET_SYSERR;
+ }
+ TALER_ARL_amount_add (&ps->balance,
+ &ps->balance,
+ &amount_minus_fee);
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
+ &TALER_ARL_USE_AB (purse_global_balance),
+ &amount_minus_fee);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about purse merges that have been made, with
+ * the goal of auditing the purse merge execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param partner_base_url where is the reserve, NULL for this exchange
+ * @param amount total amount expected in the purse
+ * @param balance current balance in the purse
+ * @param flags purse flags
+ * @param merge_pub merge capability key
+ * @param reserve_pub reserve the merge affects
+ * @param merge_sig signature affirming the merge
+ * @param purse_pub purse key
+ * @param merge_timestamp when did the merge happen
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+handle_purse_merged (
+ void *cls,
+ uint64_t rowid,
+ const char *partner_base_url,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *balance,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp merge_timestamp)
+{
+ struct PurseContext *pc = cls;
+ struct PurseSummary *ps;
+
+ /* should be monotonically increasing */
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_merges_serial_id));
+ TALER_ARL_USE_PP (purse_merges_serial_id) = rowid + 1;
+
+ {
+ char *reserve_url;
+
+ reserve_url
+ = TALER_reserve_make_payto (NULL == partner_base_url
+ ? TALER_ARL_exchange_url
+ : partner_base_url,
+ reserve_pub);
+ if (GNUNET_OK !=
+ TALER_wallet_purse_merge_verify (reserve_url,
+ merge_timestamp,
+ purse_pub,
+ merge_pub,
+ merge_sig))
+ {
+ GNUNET_free (reserve_url);
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "merge-purse"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ amount),
+ GNUNET_JSON_pack_data_auto ("key_pub",
+ merge_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ amount);
+ return GNUNET_OK;
+ }
+ GNUNET_free (reserve_url);
+ }
+
+ ps = setup_purse (pc,
+ purse_pub);
+ if (NULL == ps)
+ {
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs)
+ {
+ report_row_inconsistency ("purse-merge",
+ rowid,
+ "purse not found");
+ }
+ else
+ {
+ /* Database trouble!? */
+ GNUNET_break (0);
+ }
+ return GNUNET_SYSERR;
+ }
+ GNUNET_break (0 ==
+ GNUNET_TIME_timestamp_cmp (merge_timestamp,
+ ==,
+ ps->merge_timestamp));
+ TALER_ARL_amount_add (&ps->balance,
+ &ps->balance,
+ amount);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about account merge requests that have been
+ * made, with the goal of auditing the account merge execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param reserve_pub reserve affected by the merge
+ * @param purse_pub purse being merged
+ * @param h_contract_terms hash over contract of the purse
+ * @param purse_expiration when would the purse expire
+ * @param amount total amount in the purse
+ * @param min_age minimum age of all coins deposited into the purse
+ * @param flags how was the purse created
+ * @param purse_fee if a purse fee was paid, how high is it
+ * @param merge_timestamp when was the merge approved
+ * @param reserve_sig signature by reserve approving the merge
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+handle_account_merged (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_Amount *amount,
+ uint32_t min_age,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_Amount *purse_fee,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct PurseContext *pc = cls;
+ struct PurseSummary *ps;
+
+ /* should be monotonically increasing */
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_account_merge_serial_id));
+ TALER_ARL_USE_PP (purse_account_merge_serial_id) = rowid + 1;
+ if (GNUNET_OK !=
+ TALER_wallet_account_merge_verify (merge_timestamp,
+ purse_pub,
+ purse_expiration,
+ h_contract_terms,
+ amount,
+ purse_fee,
+ min_age,
+ flags,
+ reserve_pub,
+ reserve_sig))
+ {
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "account-merge"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ purse_fee),
+ GNUNET_JSON_pack_data_auto ("key_pub",
+ reserve_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ purse_fee);
+ return GNUNET_OK;
+ }
+ ps = setup_purse (pc,
+ purse_pub);
+ if (NULL == ps)
+ {
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs)
+ {
+ report_row_inconsistency ("account-merge",
+ rowid,
+ "purse not found");
+ }
+ else
+ {
+ /* Database trouble!? */
+ GNUNET_break (0);
+ }
+ return GNUNET_SYSERR;
+ }
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
+ &TALER_ARL_USE_AB (purse_global_balance),
+ purse_fee);
+ TALER_ARL_amount_add (&ps->balance,
+ &ps->balance,
+ purse_fee);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about purse decisions that have been made.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param purse_pub which purse was the decision made on
+ * @param refunded true if decision was to refund
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+handle_purse_decision (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ bool refunded)
+{
+ struct PurseContext *pc = cls;
+ struct PurseSummary *ps;
+ struct GNUNET_HashCode key;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_Amount purse_fee;
+ struct TALER_Amount balance_without_purse_fee;
+
+ TALER_ARL_USE_PP (purse_decision_serial_id) = rowid;
+ ps = setup_purse (pc,
+ purse_pub);
+ if (NULL == ps)
+ {
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs)
+ {
+ report_row_inconsistency ("purse-decision",
+ rowid,
+ "purse not found");
+ }
+ else
+ {
+ /* Database trouble!? */
+ GNUNET_break (0);
+ }
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ get_purse_fee (ps->creation_date,
+ &purse_fee))
+ {
+ report_row_inconsistency ("purse-request",
+ rowid,
+ "purse fee unavailable");
+ }
+ if (0 >
+ TALER_amount_subtract (&balance_without_purse_fee,
+ &ps->balance,
+ &purse_fee))
+ {
+ report_row_inconsistency ("purse-request",
+ rowid,
+ "purse fee higher than balance");
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &balance_without_purse_fee));
+ }
+
+ if (refunded)
+ {
+ if (-1 != TALER_amount_cmp (&balance_without_purse_fee,
+ &ps->total_value))
+ {
+ report_amount_arithmetic_inconsistency ("purse-decision: refund",
+ rowid,
+ &balance_without_purse_fee,
+ &ps->total_value,
+ 0);
+ }
+ }
+ else
+ {
+ if (-1 == TALER_amount_cmp (&balance_without_purse_fee,
+ &ps->total_value))
+ {
+ report_amount_arithmetic_inconsistency ("purse-decision: merge",
+ rowid,
+ &ps->total_value,
+ &balance_without_purse_fee,
+ 0);
+ TALER_ARL_amount_add (&total_balance_insufficient_loss,
+ &total_balance_insufficient_loss,
+ &ps->total_value);
+ }
+ }
+
+ qs = TALER_ARL_adb->delete_purse_info (TALER_ARL_adb->cls,
+ purse_pub);
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ pc->qs = qs;
+ return GNUNET_SYSERR;
+ }
+ GNUNET_CRYPTO_hash (purse_pub,
+ sizeof (*purse_pub),
+ &key);
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CONTAINER_multihashmap_remove (pc->purses,
+ &key,
+ ps));
+ GNUNET_free (ps);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called on expired purses.
+ *
+ * @param cls closure
+ * @param purse_pub public key of the purse
+ * @param balance amount of money in the purse
+ * @param expiration_date when did the purse expire?
+ * @return #GNUNET_OK to continue to iterate
+ */
+static enum GNUNET_GenericReturnValue
+handle_purse_expired (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance,
+ struct GNUNET_TIME_Timestamp expiration_date)
+{
+ struct PurseContext *pc = cls;
+
+ (void) pc;
+ TALER_ARL_report (report_purse_not_closed_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ purse_pub),
+ TALER_JSON_pack_amount ("balance",
+ balance),
+ TALER_JSON_pack_time_abs_human ("expired",
+ expiration_date.abs_time)));
+ TALER_ARL_amount_add (&total_delayed_decisions,
+ &total_delayed_decisions,
+ balance);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check that the purse summary matches what the exchange database
+ * thinks about the purse, and update our own state of the purse.
+ *
+ * Remove all purses that we are happy with from the DB.
+ *
+ * @param cls our `struct PurseContext`
+ * @param key hash of the purse public key
+ * @param value a `struct PurseSummary`
+ * @return #GNUNET_OK to process more entries
+ */
+static enum GNUNET_GenericReturnValue
+verify_purse_balance (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ struct PurseContext *pc = cls;
+ struct PurseSummary *ps = value;
+ enum GNUNET_GenericReturnValue ret;
+ enum GNUNET_DB_QueryStatus qs;
+
+ ret = GNUNET_OK;
+ if (internal_checks)
+ {
+ struct TALER_Amount pf;
+ struct TALER_Amount balance_without_purse_fee;
+
+ /* subtract purse fee from ps->balance to get actual balance we expect, as
+ we track the balance including purse fee, while the exchange subtracts
+ the purse fee early on. */
+ if (GNUNET_OK !=
+ get_purse_fee (ps->creation_date,
+ &pf))
+ {
+ GNUNET_break (0);
+ report_row_inconsistency ("purse",
+ 0,
+ "purse fee unavailable");
+ }
+ if (0 >
+ TALER_amount_subtract (&balance_without_purse_fee,
+ &ps->balance,
+ &pf))
+ {
+ report_row_inconsistency ("purse",
+ 0,
+ "purse fee higher than balance");
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &balance_without_purse_fee));
+ }
+
+ if (0 != TALER_amount_cmp (&ps->exchange_balance,
+ &balance_without_purse_fee))
+ {
+ report_amount_arithmetic_inconsistency ("purse-balance",
+ 0,
+ &ps->exchange_balance,
+ &balance_without_purse_fee,
+ 0);
+ }
+ }
+
+ if (ps->had_pi)
+ qs = TALER_ARL_adb->update_purse_info (TALER_ARL_adb->cls,
+ &ps->purse_pub,
+ &ps->balance);
+ else
+ qs = TALER_ARL_adb->insert_purse_info (TALER_ARL_adb->cls,
+ &ps->purse_pub,
+ &ps->balance,
+ ps->expiration_date);
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ pc->qs = qs;
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CONTAINER_multihashmap_remove (pc->purses,
+ key,
+ ps));
+ GNUNET_free (ps);
+ return ret;
+}
+
+
+/**
+ * Analyze purses for being well-formed.
+ *
+ * @param cls NULL
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+analyze_purses (void *cls)
+{
+ struct PurseContext pc;
+ enum GNUNET_DB_QueryStatus qsx;
+ enum GNUNET_DB_QueryStatus qs;
+ enum GNUNET_DB_QueryStatus qsp;
+
+ (void) cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Analyzing purses\n");
+ qsp = TALER_ARL_adb->get_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_PP (purse_account_merge_serial_id),
+ TALER_ARL_GET_PP (purse_decision_serial_id),
+ TALER_ARL_GET_PP (purse_deposits_serial_id),
+ TALER_ARL_GET_PP (purse_merges_serial_id),
+ TALER_ARL_GET_PP (purse_request_serial_id),
+ TALER_ARL_GET_PP (purse_open_counter),
+ NULL);
+ if (0 > qsp)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsp);
+ return qsp;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsp)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "First analysis using this auditor, starting audit from scratch\n");
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming purse audit at %llu/%llu/%llu/%llu/%llu\n",
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_request_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_decision_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_merges_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_deposits_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_account_merge_serial_id));
+ }
+ pc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ qsx = TALER_ARL_adb->get_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_AB (purse_global_balance),
+ NULL);
+ if (qsx < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx);
+ return qsx;
+ }
+ pc.purses = GNUNET_CONTAINER_multihashmap_create (512,
+ GNUNET_NO);
+
+ qs = TALER_ARL_edb->select_purse_requests_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (purse_request_serial_id),
+ &handle_purse_requested,
+ &pc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+
+ qs = TALER_ARL_edb->select_purse_merges_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (purse_merges_serial_id),
+ &handle_purse_merged,
+ &pc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ qs = TALER_ARL_edb->select_purse_deposits_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (purse_deposits_serial_id),
+ &handle_purse_deposits,
+ &pc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ /* Charge purse fee! */
+ qs = TALER_ARL_edb->select_account_merges_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (purse_account_merge_serial_id),
+ &handle_account_merged,
+ &pc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+
+ qs = TALER_ARL_edb->select_all_purse_decisions_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (purse_decision_serial_id),
+ &handle_purse_decision,
+ &pc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+
+ qs = TALER_ARL_adb->select_purse_expired (
+ TALER_ARL_adb->cls,
+ &handle_purse_expired,
+ &pc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+
+ GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
+ &verify_purse_balance,
+ &pc);
+ GNUNET_break (0 ==
+ GNUNET_CONTAINER_multihashmap_size (pc.purses));
+ GNUNET_CONTAINER_multihashmap_destroy (pc.purses);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != pc.qs)
+ return qs;
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsx)
+ {
+ qs = TALER_ARL_adb->insert_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_AB (purse_global_balance),
+ NULL);
+ }
+ else
+ {
+ qs = TALER_ARL_adb->update_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_AB (purse_global_balance),
+ NULL);
+ }
+ if (0 >= qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp)
+ qs = TALER_ARL_adb->update_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_PP (purse_account_merge_serial_id),
+ TALER_ARL_SET_PP (purse_decision_serial_id),
+ TALER_ARL_SET_PP (purse_deposits_serial_id),
+ TALER_ARL_SET_PP (purse_merges_serial_id),
+ TALER_ARL_SET_PP (purse_request_serial_id),
+ TALER_ARL_SET_PP (purse_open_counter),
+ NULL);
+ else
+ qs = TALER_ARL_adb->insert_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_PP (purse_account_merge_serial_id),
+ TALER_ARL_SET_PP (purse_decision_serial_id),
+ TALER_ARL_SET_PP (purse_deposits_serial_id),
+ TALER_ARL_SET_PP (purse_merges_serial_id),
+ TALER_ARL_SET_PP (purse_request_serial_id),
+ TALER_ARL_SET_PP (purse_open_counter),
+ NULL);
+ if (0 >= qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Failed to update auditor DB, not recording progress\n");
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Concluded purse audit step at %llu/%llu/%llu/%llu/%llu\n",
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_request_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_decision_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_merges_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_deposits_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_account_merge_serial_id));
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+/**
+ * Main function that will be run.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param c configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+{
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Launching auditor\n");
+ if (GNUNET_OK !=
+ TALER_ARL_init (c))
+ {
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ purse_global_balance)));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_balance_insufficient_loss));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_delayed_decisions));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_arithmetic_delta_plus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_arithmetic_delta_minus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_balance_purse_not_closed));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_bad_sig_loss));
+
+ GNUNET_assert (NULL !=
+ (report_row_inconsistencies = json_array ()));
+ GNUNET_assert (NULL !=
+ (report_purse_balance_insufficient_inconsistencies
+ = json_array ()));
+ GNUNET_assert (NULL !=
+ (report_purse_not_closed_inconsistencies
+ = json_array ()));
+ GNUNET_assert (NULL !=
+ (report_amount_arithmetic_inconsistencies
+ = json_array ()));
+ GNUNET_assert (NULL !=
+ (report_bad_sig_losses = json_array ()));
+ if (GNUNET_OK !=
+ TALER_ARL_setup_sessions_and_run (&analyze_purses,
+ NULL))
+ {
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ TALER_ARL_done (
+ GNUNET_JSON_PACK (
+ /* Globals (REVIEW!) */
+ TALER_JSON_pack_amount ("total_balance_insufficient",
+ &total_balance_insufficient_loss),
+ TALER_JSON_pack_amount ("total_delayed_purse_decisions",
+ &total_delayed_decisions),
+ GNUNET_JSON_pack_array_steal (
+ "purse_balance_insufficient_inconsistencies",
+ report_purse_balance_insufficient_inconsistencies),
+ TALER_JSON_pack_amount ("total_balance_purse_not_closed",
+ &total_balance_purse_not_closed),
+ TALER_JSON_pack_amount ("total_bad_sig_loss",
+ &total_bad_sig_loss),
+ TALER_JSON_pack_amount ("total_arithmetic_delta_plus",
+ &total_arithmetic_delta_plus),
+ TALER_JSON_pack_amount ("total_arithmetic_delta_minus",
+ &total_arithmetic_delta_minus),
+
+ /* Global 'balances' */
+ TALER_JSON_pack_amount ("total_purse_balance",
+ &TALER_ARL_USE_AB (purse_global_balance)),
+ GNUNET_JSON_pack_uint64 ("total_purse_count",
+ TALER_ARL_USE_PP (purse_open_counter)),
+
+ GNUNET_JSON_pack_array_steal ("purse_not_closed_inconsistencies",
+ report_purse_not_closed_inconsistencies),
+ GNUNET_JSON_pack_array_steal ("bad_sig_losses",
+ report_bad_sig_losses),
+ GNUNET_JSON_pack_array_steal ("row_inconsistencies",
+ report_row_inconsistencies),
+ GNUNET_JSON_pack_array_steal ("amount_arithmetic_inconsistencies",
+ report_amount_arithmetic_inconsistencies),
+ /* Information about audited range ... */
+ TALER_JSON_pack_time_abs_human ("auditor_start_time",
+ start_time),
+ TALER_JSON_pack_time_abs_human ("auditor_end_time",
+ GNUNET_TIME_absolute_get ()),
+ GNUNET_JSON_pack_uint64 ("start_ppp_purse_merges_serial_id",
+ 0 /* not supported anymore */),
+ GNUNET_JSON_pack_uint64 ("start_ppp_purse_deposits_serial_id",
+ 0 /* not supported anymore */),
+ GNUNET_JSON_pack_uint64 ("start_ppp_account_merge_serial_id",
+ 0 /* not supported anymore */),
+ GNUNET_JSON_pack_uint64 ("end_ppp_purse_merges_serial_id",
+ TALER_ARL_USE_PP (
+ purse_merges_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppp_purse_deposits_serial_id",
+ TALER_ARL_USE_PP (
+ purse_deposits_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppp_account_merge_serial_id",
+ TALER_ARL_USE_PP (
+ purse_account_merge_serial_id))));
+}
+
+
+/**
+ * The main function to check the database's handling of purses.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ const struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_flag ('i',
+ "internal",
+ "perform checks only applicable for exchange-internal audits",
+ &internal_checks),
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
+ GNUNET_GETOPT_option_timetravel ('T',
+ "timetravel"),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue 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 */
+ (void) TALER_project_data_default ();
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ ret = GNUNET_PROGRAM_run (
+ argc,
+ argv,
+ "taler-helper-auditor-purses",
+ gettext_noop ("Audit Taler exchange purse handling"),
+ options,
+ &run,
+ NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+
+/* end of taler-helper-auditor-purses.c */
diff --git a/src/auditor/taler-helper-auditor-render.py b/src/auditor/taler-helper-auditor-render.py
index 4b086cb62..b9c92b29c 100644
--- a/src/auditor/taler-helper-auditor-render.py
+++ b/src/auditor/taler-helper-auditor-render.py
@@ -53,4 +53,14 @@ jinjaEnv = jinja2.Environment(loader=StdinLoader(),
autoescape=False)
tmpl = jinjaEnv.get_template('stdin');
-print(tmpl.render(aggregation = jsonData1, coins = jsonData2, deposits = jsonData3, reserves = jsonData4, wire = jsonData5))
+try:
+ print(tmpl.render(aggregation = jsonData1, coins = jsonData2, deposits = jsonData3, reserves = jsonData4, wire = jsonData5))
+except jinja2.TemplateSyntaxError as error:
+ print("Template syntax error: {error.message} on line {error.lineno}.".format(error=error))
+ exit(1)
+except jinja2.UndefinedError as error:
+ print("Template undefined error: {error.message}.".format(error=error))
+ exit(1)
+except TypeError as error:
+ print("Template type error: {0}.".format(error.args[0]))
+ exit(1)
diff --git a/src/auditor/taler-helper-auditor-reserves.c b/src/auditor/taler-helper-auditor-reserves.c
index 4917ef0a1..aa35c6a75 100644
--- a/src/auditor/taler-helper-auditor-reserves.c
+++ b/src/auditor/taler-helper-auditor-reserves.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2016-2020 Taler Systems SA
+ Copyright (C) 2016-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero Public License as published by the Free Software
@@ -39,6 +39,14 @@
static int global_ret;
/**
+ * Run in test mode. Exit when idle instead of
+ * going to sleep and waiting for more work.
+ *
+ * FIXME: not yet implemented!
+ */
+static int test_mode;
+
+/**
* After how long should idle reserves be closed?
*/
static struct GNUNET_TIME_Relative idle_reserve_expiration_time;
@@ -46,15 +54,29 @@ static struct GNUNET_TIME_Relative idle_reserve_expiration_time;
/**
* Checkpointing our progress for reserves.
*/
-static struct TALER_AUDITORDB_ProgressPointReserve ppr;
+static TALER_ARL_DEF_PP (reserves_reserve_in_serial_id);
+static TALER_ARL_DEF_PP (reserves_reserve_out_serial_id);
+static TALER_ARL_DEF_PP (reserves_reserve_recoup_serial_id);
+static TALER_ARL_DEF_PP (reserves_reserve_open_serial_id);
+static TALER_ARL_DEF_PP (reserves_reserve_close_serial_id);
+static TALER_ARL_DEF_PP (reserves_purse_decisions_serial_id);
+static TALER_ARL_DEF_PP (reserves_account_merges_serial_id);
+static TALER_ARL_DEF_PP (reserves_history_requests_serial_id);
/**
- * Checkpointing our progress for reserves.
+ * Tracked global reserve balances.
*/
-static struct TALER_AUDITORDB_ProgressPointReserve ppr_start;
+static TALER_ARL_DEF_AB (reserves_reserve_total_balance);
+static TALER_ARL_DEF_AB (reserves_reserve_loss);
+static TALER_ARL_DEF_AB (reserves_withdraw_fee_revenue);
+static TALER_ARL_DEF_AB (reserves_close_fee_revenue);
+static TALER_ARL_DEF_AB (reserves_purse_fee_revenue);
+static TALER_ARL_DEF_AB (reserves_open_fee_revenue);
+static TALER_ARL_DEF_AB (reserves_history_fee_revenue);
+
/**
- * Array of reports about row inconsitencies.
+ * Array of reports about row inconsistencies.
*/
static json_t *report_row_inconsistencies;
@@ -65,14 +87,14 @@ static json_t *report_row_inconsistencies;
static json_t *denomination_key_validity_withdraw_inconsistencies;
/**
- * Array of reports about reserve balance insufficient inconsitencies.
+ * Array of reports about reserve balance insufficient inconsistencies.
*/
static json_t *report_reserve_balance_insufficient_inconsistencies;
/**
- * Total amount reserves were charged beyond their balance.
+ * Array of reports about purse balance insufficient inconsistencies.
*/
-static struct TALER_Amount total_balance_insufficient_loss;
+static json_t *report_purse_balance_insufficient_inconsistencies;
/**
* Array of reports about reserve balance summary wrong in database.
@@ -81,18 +103,20 @@ static json_t *report_reserve_balance_summary_wrong_inconsistencies;
/**
* Total delta between expected and stored reserve balance summaries,
- * for positive deltas.
+ * for positive deltas. Used only when internal checks are
+ * enabled.
*/
static struct TALER_Amount total_balance_summary_delta_plus;
/**
* Total delta between expected and stored reserve balance summaries,
- * for negative deltas.
+ * for negative deltas. Used only when internal checks are
+ * enabled.
*/
static struct TALER_Amount total_balance_summary_delta_minus;
/**
- * Array of reports about reserve's not being closed inconsitencies.
+ * Array of reports about reserve's not being closed inconsistencies.
*/
static json_t *report_reserve_not_closed_inconsistencies;
@@ -118,21 +142,6 @@ static struct TALER_Amount total_arithmetic_delta_plus;
static struct TALER_Amount total_arithmetic_delta_minus;
/**
- * Expected balance in the escrow account.
- */
-static struct TALER_Amount total_escrow_balance;
-
-/**
- * Recoups we made on denominations that were not revoked (!?).
- */
-static struct TALER_Amount total_irregular_recoups;
-
-/**
- * Total withdraw fees earned.
- */
-static struct TALER_Amount total_withdraw_fee_income;
-
-/**
* Array of reports about coin operations with bad signatures.
*/
static json_t *report_bad_sig_losses;
@@ -142,6 +151,10 @@ static json_t *report_bad_sig_losses;
*/
static struct TALER_Amount total_bad_sig_loss;
+/**
+ * Should we run checks that only work for exchange-internal audits?
+ */
+static int internal_checks;
/* ***************************** Report logic **************************** */
@@ -151,7 +164,7 @@ static struct TALER_Amount total_bad_sig_loss;
* respect to calculations involving amounts.
*
* @param operation what operation had the inconsistency
- * @param rowid affected row, UINT64_MAX if row is missing
+ * @param rowid affected row, 0 if row is missing
* @param exchange amount calculated by exchange
* @param auditor amount calculated by auditor
* @param profitable 1 if @a exchange being larger than @a auditor is
@@ -174,36 +187,38 @@ report_amount_arithmetic_inconsistency (
auditor))
{
/* exchange > auditor */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- exchange,
- auditor));
+ TALER_ARL_amount_subtract (&delta,
+ exchange,
+ auditor);
}
else
{
/* auditor < exchange */
profitable = -profitable;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- auditor,
- exchange));
+ TALER_ARL_amount_subtract (&delta,
+ auditor,
+ exchange);
}
TALER_ARL_report (report_amount_arithmetic_inconsistencies,
- json_pack ("{s:s, s:I, s:o, s:o, s:I}",
- "operation", operation,
- "rowid", (json_int_t) rowid,
- "exchange", TALER_JSON_from_amount (exchange),
- "auditor", TALER_JSON_from_amount (auditor),
- "profitable", (json_int_t) profitable));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ operation),
+ GNUNET_JSON_pack_uint64 ("rowid",
+ rowid),
+ TALER_JSON_pack_amount ("exchange",
+ exchange),
+ TALER_JSON_pack_amount ("auditor",
+ auditor),
+ GNUNET_JSON_pack_int64 ("profitable",
+ profitable)));
if (0 != profitable)
{
target = (1 == profitable)
- ? &total_arithmetic_delta_plus
- : &total_arithmetic_delta_minus;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (target,
- target,
- &delta));
+ ? &total_arithmetic_delta_plus
+ : &total_arithmetic_delta_minus;
+ TALER_ARL_amount_add (target,
+ target,
+ &delta);
}
}
@@ -212,7 +227,7 @@ report_amount_arithmetic_inconsistency (
* Report a (serious) inconsistency in the exchange's database.
*
* @param table affected table
- * @param rowid affected row, UINT64_MAX if row is missing
+ * @param rowid affected row, 0 if row is missing
* @param diagnostic message explaining the problem
*/
static void
@@ -221,10 +236,13 @@ report_row_inconsistency (const char *table,
const char *diagnostic)
{
TALER_ARL_report (report_row_inconsistencies,
- json_pack ("{s:s, s:I, s:s}",
- "table", table,
- "row", (json_int_t) rowid,
- "diagnostic", diagnostic));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ table),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ GNUNET_JSON_pack_string ("diagnostic",
+ diagnostic)));
}
@@ -255,27 +273,21 @@ struct ReserveSummary
struct TALER_Amount total_out;
/**
- * Sum of withdraw fees encountered during this transaction.
+ * Sum of balance and fees encountered during this transaction.
*/
- struct TALER_Amount total_fee;
+ struct TALER_AUDITORDB_ReserveFeeBalance curr_balance;
/**
- * Previous balance of the reserve as remembered by the auditor.
+ * Previous balances of the reserve as remembered by the auditor.
* (updated based on @e total_in and @e total_out at the end).
*/
- struct TALER_Amount a_balance;
-
- /**
- * Previous withdraw fee balance of the reserve, as remembered by the auditor.
- * (updated based on @e total_fee at the end).
- */
- struct TALER_Amount a_withdraw_fee_balance;
+ struct TALER_AUDITORDB_ReserveFeeBalance prev_balance;
/**
* Previous reserve expiration data, as remembered by the auditor.
* (updated on-the-fly in #handle_reserve_in()).
*/
- struct GNUNET_TIME_Absolute a_expiration_date;
+ struct GNUNET_TIME_Timestamp a_expiration_date;
/**
* Which account did originally put money into the reserve?
@@ -288,7 +300,7 @@ struct ReserveSummary
* #load_auditor_reserve_summary() together with the a-* values
* (if available).
*/
- int had_ri;
+ bool had_ri;
};
@@ -308,12 +320,9 @@ load_auditor_reserve_summary (struct ReserveSummary *rs)
uint64_t rowid;
qs = TALER_ARL_adb->get_reserve_info (TALER_ARL_adb->cls,
- TALER_ARL_asession,
&rs->reserve_pub,
- &TALER_ARL_master_pub,
&rowid,
- &rs->a_balance,
- &rs->a_withdraw_fee_balance,
+ &rs->prev_balance,
&rs->a_expiration_date,
&rs->sender_account);
if (0 > qs)
@@ -323,34 +332,38 @@ load_auditor_reserve_summary (struct ReserveSummary *rs)
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
- rs->had_ri = GNUNET_NO;
+ rs->had_ri = false;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (rs->total_in.currency,
+ &rs->prev_balance.reserve_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (rs->total_in.currency,
+ &rs->prev_balance.reserve_loss));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (rs->total_in.currency,
+ &rs->prev_balance.withdraw_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (rs->total_in.currency,
+ &rs->prev_balance.close_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (rs->total_in.currency,
+ &rs->prev_balance.purse_fee_balance));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (rs->total_in.currency,
- &rs->a_balance));
+ TALER_amount_set_zero (rs->total_in.currency,
+ &rs->prev_balance.open_fee_balance));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (rs->total_in.currency,
- &rs->a_withdraw_fee_balance));
+ TALER_amount_set_zero (rs->total_in.currency,
+ &rs->prev_balance.history_fee_balance));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Creating fresh reserve `%s' with starting balance %s\n",
- TALER_B2S (&rs->reserve_pub),
- TALER_amount2s (&rs->a_balance));
+ "Creating fresh reserve `%s'\n",
+ TALER_B2S (&rs->reserve_pub));
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
}
- rs->had_ri = GNUNET_YES;
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&rs->a_balance,
- &rs->a_withdraw_fee_balance)) ||
- (GNUNET_YES !=
- TALER_amount_cmp_currency (&rs->total_in,
- &rs->a_balance)) )
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
+ rs->had_ri = true;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Auditor remembers reserve `%s' has balance %s\n",
TALER_B2S (&rs->reserve_pub),
- TALER_amount2s (&rs->a_balance));
+ TALER_amount2s (&rs->prev_balance.reserve_balance));
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
@@ -382,6 +395,72 @@ struct ReserveContext
/**
+ * Create a new reserve for @a reserve_pub in @a rc.
+ *
+ * @param[in,out] rc context to update
+ * @param reserve_pub key for which to create a reserve
+ * @return NULL on error
+ */
+static struct ReserveSummary *
+setup_reserve (struct ReserveContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+ struct ReserveSummary *rs;
+ struct GNUNET_HashCode key;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_CRYPTO_hash (reserve_pub,
+ sizeof (*reserve_pub),
+ &key);
+ rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
+ &key);
+ if (NULL != rs)
+ return rs;
+ rs = GNUNET_new (struct ReserveSummary);
+ rs->reserve_pub = *reserve_pub;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &rs->total_in));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &rs->total_out));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &rs->curr_balance.reserve_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &rs->curr_balance.reserve_loss));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &rs->curr_balance.withdraw_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &rs->curr_balance.close_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &rs->curr_balance.purse_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &rs->curr_balance.open_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &rs->curr_balance.history_fee_balance));
+ if (0 > (qs = load_auditor_reserve_summary (rs)))
+ {
+ GNUNET_free (rs);
+ rc->qs = qs;
+ return NULL;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (rc->reserves,
+ &key,
+ rs,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ return rs;
+}
+
+
+/**
* Function called with details about incoming wire transfers.
*
* @param cls our `struct ReserveContext`
@@ -393,73 +472,46 @@ struct ReserveContext
* @param execution_date when did we receive the funds
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
handle_reserve_in (void *cls,
uint64_t rowid,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_Amount *credit,
const char *sender_account_details,
uint64_t wire_reference,
- struct GNUNET_TIME_Absolute execution_date)
+ struct GNUNET_TIME_Timestamp execution_date)
{
struct ReserveContext *rc = cls;
- struct GNUNET_HashCode key;
struct ReserveSummary *rs;
- struct GNUNET_TIME_Absolute expiry;
- enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp expiry;
(void) wire_reference;
/* should be monotonically increasing */
- GNUNET_assert (rowid >= ppr.last_reserve_in_serial_id);
- ppr.last_reserve_in_serial_id = rowid + 1;
-
- GNUNET_CRYPTO_hash (reserve_pub,
- sizeof (*reserve_pub),
- &key);
- rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
- &key);
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_in_serial_id));
+ TALER_ARL_USE_PP (reserves_reserve_in_serial_id) = rowid + 1;
+ rs = setup_reserve (rc,
+ reserve_pub);
if (NULL == rs)
{
- rs = GNUNET_new (struct ReserveSummary);
- rs->sender_account = GNUNET_strdup (sender_account_details);
- rs->reserve_pub = *reserve_pub;
- rs->total_in = *credit;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (credit->currency,
- &rs->total_out));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (credit->currency,
- &rs->total_fee));
- if (0 > (qs = load_auditor_reserve_summary (rs)))
- {
- GNUNET_break (0);
- GNUNET_free (rs);
- rc->qs = qs;
- return GNUNET_SYSERR;
- }
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (rc->reserves,
- &key,
- rs,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
- }
- else
- {
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&rs->total_in,
- &rs->total_in,
- credit));
- if (NULL == rs->sender_account)
- rs->sender_account = GNUNET_strdup (sender_account_details);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
+ if (NULL == rs->sender_account)
+ rs->sender_account = GNUNET_strdup (sender_account_details);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Additional incoming wire transfer for reserve `%s' of %s\n",
TALER_B2S (reserve_pub),
TALER_amount2s (credit));
- expiry = GNUNET_TIME_absolute_add (execution_date,
- idle_reserve_expiration_time);
- rs->a_expiration_date = GNUNET_TIME_absolute_max (rs->a_expiration_date,
- expiry);
+ expiry = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (execution_date.abs_time,
+ idle_reserve_expiration_time));
+ rs->a_expiration_date = GNUNET_TIME_timestamp_max (rs->a_expiration_date,
+ expiry);
+ TALER_ARL_amount_add (&rs->total_in,
+ &rs->total_in,
+ credit);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
@@ -478,40 +530,32 @@ handle_reserve_in (void *cls,
* @param amount_with_fee amount that was withdrawn
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
handle_reserve_out (void *cls,
uint64_t rowid,
- const struct GNUNET_HashCode *h_blind_ev,
+ const struct TALER_BlindedCoinHashP *h_blind_ev,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_ReserveSignatureP *reserve_sig,
- struct GNUNET_TIME_Absolute execution_date,
+ struct GNUNET_TIME_Timestamp execution_date,
const struct TALER_Amount *amount_with_fee)
{
struct ReserveContext *rc = cls;
- struct GNUNET_HashCode key;
struct ReserveSummary *rs;
- const struct TALER_DenominationKeyValidityPS *issue;
- struct TALER_Amount withdraw_fee;
- struct GNUNET_TIME_Absolute valid_start;
- struct GNUNET_TIME_Absolute expire_withdraw;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
+ struct TALER_Amount auditor_amount_with_fee;
enum GNUNET_DB_QueryStatus qs;
- struct TALER_WithdrawRequestPS wsrd = {
- .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW),
- .purpose.size = htonl (sizeof (wsrd)),
- .reserve_pub = *reserve_pub,
- .h_coin_envelope = *h_blind_ev
- };
+ struct TALER_DenominationHashP h_denom_pub;
/* should be monotonically increasing */
- GNUNET_assert (rowid >= ppr.last_reserve_out_serial_id);
- ppr.last_reserve_out_serial_id = rowid + 1;
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_out_serial_id));
+ TALER_ARL_USE_PP (reserves_reserve_out_serial_id) = rowid + 1;
/* lookup denomination pub data (make sure denom_pub is valid, establish fees);
initializes wsrd.h_denomination_pub! */
qs = TALER_ARL_get_denomination_info (denom_pub,
&issue,
- &wsrd.h_denomination_pub);
+ &h_denom_pub);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
@@ -528,106 +572,98 @@ handle_reserve_out (void *cls,
report_row_inconsistency ("withdraw",
rowid,
"denomination key not found");
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
/* check that execution date is within withdraw range for denom_pub */
- valid_start = GNUNET_TIME_absolute_ntoh (issue->start);
- expire_withdraw = GNUNET_TIME_absolute_ntoh (issue->expire_withdraw);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Checking withdraw timing: %llu, expire: %llu, timing: %llu\n",
- (unsigned long long) valid_start.abs_value_us,
- (unsigned long long) expire_withdraw.abs_value_us,
- (unsigned long long) execution_date.abs_value_us);
- if ( (valid_start.abs_value_us > execution_date.abs_value_us) ||
- (expire_withdraw.abs_value_us < execution_date.abs_value_us) )
+ (unsigned long long) issue->start.abs_time.abs_value_us,
+ (unsigned long long) issue->expire_withdraw.abs_time.abs_value_us,
+ (unsigned long long) execution_date.abs_time.abs_value_us);
+ if (GNUNET_TIME_timestamp_cmp (issue->start,
+ >,
+ execution_date) ||
+ GNUNET_TIME_timestamp_cmp (issue->expire_withdraw,
+ <,
+ execution_date))
{
TALER_ARL_report (denomination_key_validity_withdraw_inconsistencies,
- json_pack ("{s:I, s:o, s:o, s:o}",
- "row", (json_int_t) rowid,
- "execution_date",
- TALER_ARL_json_from_time_abs (execution_date),
- "reserve_pub", GNUNET_JSON_from_data_auto (
- reserve_pub),
- "denompub_h", GNUNET_JSON_from_data_auto (
- &wsrd.h_denomination_pub)));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_time_abs_human ("execution_date",
+ execution_date.abs_time),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ reserve_pub),
+ GNUNET_JSON_pack_data_auto ("denompub_h",
+ &h_denom_pub)));
}
/* check reserve_sig (first: setup remaining members of wsrd) */
- wsrd.withdraw_fee = issue->fee_withdraw;
- TALER_amount_hton (&wsrd.amount_with_fee,
- amount_with_fee);
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
- &wsrd.purpose,
- &reserve_sig->eddsa_signature,
- &reserve_pub->eddsa_pub))
+ TALER_wallet_withdraw_verify (&h_denom_pub,
+ amount_with_fee,
+ h_blind_ev,
+ reserve_pub,
+ reserve_sig))
{
TALER_ARL_report (report_bad_sig_losses,
- json_pack ("{s:s, s:I, s:o, s:o}",
- "operation", "withdraw",
- "row", (json_int_t) rowid,
- "loss", TALER_JSON_from_amount (
- amount_with_fee),
- "key_pub", GNUNET_JSON_from_data_auto (
- reserve_pub)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
- amount_with_fee));
- return GNUNET_OK; /* exit here, we cannot add this to the legitimate withdrawals */
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "withdraw"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ amount_with_fee),
+ GNUNET_JSON_pack_data_auto ("key_pub",
+ reserve_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ amount_with_fee);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK; /* exit function here, we cannot add this to the legitimate withdrawals */
}
- GNUNET_CRYPTO_hash (reserve_pub,
- sizeof (*reserve_pub),
- &key);
- rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
- &key);
- if (NULL == rs)
+ TALER_ARL_amount_add (&auditor_amount_with_fee,
+ &issue->value,
+ &issue->fees.withdraw);
+ if (0 !=
+ TALER_amount_cmp (&auditor_amount_with_fee,
+ amount_with_fee))
{
- rs = GNUNET_new (struct ReserveSummary);
- rs->reserve_pub = *reserve_pub;
- rs->total_out = *amount_with_fee;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (amount_with_fee->currency,
- &rs->total_in));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (amount_with_fee->currency,
- &rs->total_fee));
- qs = load_auditor_reserve_summary (rs);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- GNUNET_free (rs);
- rc->qs = qs;
- return GNUNET_SYSERR;
- }
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (rc->reserves,
- &key,
- rs,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ report_row_inconsistency ("withdraw",
+ rowid,
+ "amount with fee from exchange does not match denomination value plus fee");
}
- else
+ rs = setup_reserve (rc,
+ reserve_pub);
+ if (NULL == rs)
{
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&rs->total_out,
- &rs->total_out,
- amount_with_fee));
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Reserve `%s' reduced by %s from withdraw\n",
TALER_B2S (reserve_pub),
- TALER_amount2s (amount_with_fee));
- TALER_amount_ntoh (&withdraw_fee,
- &issue->fee_withdraw);
+ TALER_amount2s (&auditor_amount_with_fee));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Increasing withdraw profits by fee %s\n",
- TALER_amount2s (&withdraw_fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&rs->total_fee,
- &rs->total_fee,
- &withdraw_fee));
+ TALER_amount2s (&issue->fees.withdraw));
+ TALER_ARL_amount_add (&rs->curr_balance.withdraw_fee_balance,
+ &rs->curr_balance.withdraw_fee_balance,
+ &issue->fees.withdraw);
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_withdraw_fee_revenue),
+ &TALER_ARL_USE_AB (reserves_withdraw_fee_revenue),
+ &issue->fees.withdraw);
+ TALER_ARL_amount_add (&rs->total_out,
+ &rs->total_out,
+ &auditor_amount_with_fee);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
@@ -648,22 +684,21 @@ handle_reserve_out (void *cls,
* @param coin_blind blinding factor used to blind the coin
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
handle_recoup_by_reserve (
void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute timestamp,
+ struct GNUNET_TIME_Timestamp timestamp,
const struct TALER_Amount *amount,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind)
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_blind)
{
struct ReserveContext *rc = cls;
- struct GNUNET_HashCode key;
struct ReserveSummary *rs;
- struct GNUNET_TIME_Absolute expiry;
+ struct GNUNET_TIME_Timestamp expiry;
struct TALER_MasterSignatureP msig;
uint64_t rev_rowid;
enum GNUNET_DB_QueryStatus qs;
@@ -671,46 +706,37 @@ handle_recoup_by_reserve (
(void) denom_pub;
/* should be monotonically increasing */
- GNUNET_assert (rowid >= ppr.last_reserve_recoup_serial_id);
- ppr.last_reserve_recoup_serial_id = rowid + 1;
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_recoup_serial_id));
+ TALER_ARL_USE_PP (reserves_reserve_recoup_serial_id) = rowid + 1;
/* We know that denom_pub matches denom_pub_hash because this
is how the SQL statement joined the tables. */
+ if (GNUNET_OK !=
+ TALER_wallet_recoup_verify (&coin->denom_pub_hash,
+ coin_blind,
+ &coin->coin_pub,
+ coin_sig))
{
- struct TALER_RecoupRequestPS pr = {
- .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP),
- .purpose.size = htonl (sizeof (pr)),
- .h_denom_pub = coin->denom_pub_hash,
- .coin_pub = coin->coin_pub,
- .coin_blind = *coin_blind
- };
-
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP,
- &pr.purpose,
- &coin_sig->eddsa_signature,
- &coin->coin_pub.eddsa_pub))
- {
- TALER_ARL_report (report_bad_sig_losses,
- json_pack ("{s:s, s:I, s:o, s:o}",
- "operation", "recoup",
- "row", (json_int_t) rowid,
- "loss", TALER_JSON_from_amount (amount),
- "key_pub", GNUNET_JSON_from_data_auto (
- &coin->coin_pub)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
- amount));
- }
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "recoup"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ amount),
+ GNUNET_JSON_pack_data_auto ("key_pub",
+ &coin->coin_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ amount);
}
/* check that the coin was eligible for recoup!*/
rev = GNUNET_CONTAINER_multihashmap_get (rc->revoked,
- &coin->denom_pub_hash);
+ &coin->denom_pub_hash.hash);
if (NULL == rev)
{
qs = TALER_ARL_edb->get_denomination_revocation (TALER_ARL_edb->cls,
- TALER_ARL_esession,
&coin->denom_pub_hash,
&msig,
&rev_rowid);
@@ -725,27 +751,17 @@ handle_recoup_by_reserve (
report_row_inconsistency ("recoup",
rowid,
"denomination key not in revocation set");
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_irregular_recoups,
- &total_irregular_recoups,
- amount));
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_reserve_loss),
+ &TALER_ARL_USE_AB (reserves_reserve_loss),
+ amount);
}
else
{
- /* verify msig */
- struct TALER_MasterDenominationKeyRevocationPS kr = {
- .purpose.purpose = htonl (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED),
- .purpose.size = htonl (sizeof (kr)),
- .h_denom_pub = coin->denom_pub_hash
- };
-
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED,
- &kr.purpose,
- &msig.eddsa_signature,
- &TALER_ARL_master_pub.eddsa_pub))
+ TALER_exchange_offline_denomination_revoke_verify (
+ &coin->denom_pub_hash,
+ &TALER_ARL_master_pub,
+ &msig))
{
rev = "master signature invalid";
}
@@ -753,78 +769,59 @@ handle_recoup_by_reserve (
{
rev = "revoked";
}
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (rc->revoked,
- &coin->denom_pub_hash,
- (void *) rev,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (
+ rc->revoked,
+ &coin->denom_pub_hash.hash,
+ (void *) rev,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
}
}
else
{
- rev_rowid = 0; /* reported elsewhere */
+ rev_rowid = 0; /* reported elsewhere */
}
if ( (NULL != rev) &&
- (0 == strcmp (rev, "master signature invalid")) )
+ (0 == strcmp (rev,
+ "master signature invalid")) )
{
TALER_ARL_report (report_bad_sig_losses,
- json_pack ("{s:s, s:I, s:o, s:o}",
- "operation", "recoup-master",
- "row", (json_int_t) rev_rowid,
- "loss", TALER_JSON_from_amount (amount),
- "key_pub", GNUNET_JSON_from_data_auto (
- &TALER_ARL_master_pub)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
- amount));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "recoup-master"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rev_rowid),
+ TALER_JSON_pack_amount ("loss",
+ amount),
+ GNUNET_JSON_pack_data_auto ("key_pub",
+ &TALER_ARL_master_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ amount);
}
- GNUNET_CRYPTO_hash (reserve_pub,
- sizeof (*reserve_pub),
- &key);
- rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
- &key);
+ rs = setup_reserve (rc,
+ reserve_pub);
if (NULL == rs)
{
- rs = GNUNET_new (struct ReserveSummary);
- rs->reserve_pub = *reserve_pub;
- rs->total_in = *amount;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (amount->currency,
- &rs->total_out));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (amount->currency,
- &rs->total_fee));
- qs = load_auditor_reserve_summary (rs);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- GNUNET_free (rs);
- rc->qs = qs;
- return GNUNET_SYSERR;
- }
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (rc->reserves,
- &key,
- rs,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
- }
- else
- {
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&rs->total_in,
- &rs->total_in,
- amount));
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
+ TALER_ARL_amount_add (&rs->total_in,
+ &rs->total_in,
+ amount);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Additional /recoup value to for reserve `%s' of %s\n",
TALER_B2S (reserve_pub),
TALER_amount2s (amount));
- expiry = GNUNET_TIME_absolute_add (timestamp,
- idle_reserve_expiration_time);
- rs->a_expiration_date = GNUNET_TIME_absolute_max (rs->a_expiration_date,
- expiry);
+ expiry = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (timestamp.abs_time,
+ idle_reserve_expiration_time));
+ rs->a_expiration_date = GNUNET_TIME_timestamp_max (rs->a_expiration_date,
+ expiry);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
@@ -838,15 +835,15 @@ handle_recoup_by_reserve (
* @param[out] fee set to the closing fee
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
get_closing_fee (const char *receiver_account,
- struct GNUNET_TIME_Absolute atime,
+ struct GNUNET_TIME_Timestamp atime,
struct TALER_Amount *fee)
{
struct TALER_MasterSignatureP master_sig;
- struct GNUNET_TIME_Absolute start_date;
- struct GNUNET_TIME_Absolute end_date;
- struct TALER_Amount wire_fee;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct TALER_WireFeeSet fees;
char *method;
method = TALER_payto_get_method (receiver_account);
@@ -855,13 +852,11 @@ get_closing_fee (const char *receiver_account,
method);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
TALER_ARL_edb->get_wire_fee (TALER_ARL_edb->cls,
- TALER_ARL_esession,
method,
atime,
&start_date,
&end_date,
- &wire_fee,
- fee,
+ &fees,
&master_sig))
{
char *diag;
@@ -869,20 +864,103 @@ get_closing_fee (const char *receiver_account,
GNUNET_asprintf (&diag,
"closing fee for `%s' unavailable at %s\n",
method,
- GNUNET_STRINGS_absolute_time_to_string (atime));
+ GNUNET_TIME_timestamp2s (atime));
report_row_inconsistency ("closing-fee",
- atime.abs_value_us,
+ atime.abs_time.abs_value_us,
diag);
GNUNET_free (diag);
GNUNET_free (method);
return GNUNET_SYSERR;
}
+ *fee = fees.closing;
GNUNET_free (method);
return GNUNET_OK;
}
/**
+ * Function called about reserve opening operations.
+ *
+ * @param cls closure
+ * @param rowid row identifier used to uniquely identify the reserve closing operation
+ * @param reserve_payment how much to pay from the
+ * reserve's own balance for opening the reserve
+ * @param request_timestamp when was the request created
+ * @param reserve_expiration desired expiration time for the reserve
+ * @param purse_limit minimum number of purses the client
+ * wants to have concurrently open for this reserve
+ * @param reserve_pub public key of the reserve
+ * @param reserve_sig signature affirming the operation
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserve_open (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_Amount *reserve_payment,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ struct GNUNET_TIME_Timestamp reserve_expiration,
+ uint32_t purse_limit,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct ReserveContext *rc = cls;
+ struct ReserveSummary *rs;
+
+ /* should be monotonically increasing */
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_open_serial_id));
+ TALER_ARL_USE_PP (reserves_reserve_open_serial_id) = rowid + 1;
+
+ rs = setup_reserve (rc,
+ reserve_pub);
+ if (NULL == rs)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_open_verify (reserve_payment,
+ request_timestamp,
+ reserve_expiration,
+ purse_limit,
+ reserve_pub,
+ reserve_sig))
+ {
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "reserve-open"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ reserve_payment),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ reserve_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ reserve_payment);
+ return GNUNET_OK;
+ }
+ TALER_ARL_amount_add (&rs->curr_balance.open_fee_balance,
+ &rs->curr_balance.open_fee_balance,
+ reserve_payment);
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_open_fee_revenue),
+ &TALER_ARL_USE_AB (reserves_open_fee_revenue),
+ reserve_payment);
+ TALER_ARL_amount_add (&rs->total_out,
+ &rs->total_out,
+ reserve_payment);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Additional open operation for reserve `%s' of %s\n",
+ TALER_B2S (reserve_pub),
+ TALER_amount2s (reserve_payment));
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+/**
* Function called about reserve closing operations
* the aggregator triggered.
*
@@ -894,69 +972,40 @@ get_closing_fee (const char *receiver_account,
* @param reserve_pub public key of the reserve
* @param receiver_account where did we send the funds
* @param transfer_details details about the wire transfer
+ * @param close_request_row which close request triggered the operation?
+ * 0 if it was a timeout
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
handle_reserve_closed (
void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute execution_date,
+ struct GNUNET_TIME_Timestamp execution_date,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *closing_fee,
const struct TALER_ReservePublicKeyP *reserve_pub,
const char *receiver_account,
- const struct TALER_WireTransferIdentifierRawP *transfer_details)
+ const struct TALER_WireTransferIdentifierRawP *transfer_details,
+ uint64_t close_request_row)
{
struct ReserveContext *rc = cls;
- struct GNUNET_HashCode key;
struct ReserveSummary *rs;
- enum GNUNET_DB_QueryStatus qs;
(void) transfer_details;
/* should be monotonically increasing */
- GNUNET_assert (rowid >= ppr.last_reserve_close_serial_id);
- ppr.last_reserve_close_serial_id = rowid + 1;
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_close_serial_id));
+ TALER_ARL_USE_PP (reserves_reserve_close_serial_id) = rowid + 1;
- GNUNET_CRYPTO_hash (reserve_pub,
- sizeof (*reserve_pub),
- &key);
- rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
- &key);
+ rs = setup_reserve (rc,
+ reserve_pub);
if (NULL == rs)
{
- rs = GNUNET_new (struct ReserveSummary);
- rs->reserve_pub = *reserve_pub;
- rs->total_out = *amount_with_fee;
- rs->total_fee = *closing_fee;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (amount_with_fee->currency,
- &rs->total_in));
- qs = load_auditor_reserve_summary (rs);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- GNUNET_free (rs);
- rc->qs = qs;
- return GNUNET_SYSERR;
- }
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (rc->reserves,
- &key,
- rs,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
- else
{
struct TALER_Amount expected_fee;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&rs->total_out,
- &rs->total_out,
- amount_with_fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&rs->total_fee,
- &rs->total_fee,
- closing_fee));
/* verify closing_fee is correct! */
if (GNUNET_OK !=
get_closing_fee (receiver_account,
@@ -976,24 +1025,254 @@ handle_reserve_closed (
1);
}
}
- if (NULL == rs->sender_account)
+
+ TALER_ARL_amount_add (&rs->curr_balance.close_fee_balance,
+ &rs->curr_balance.close_fee_balance,
+ closing_fee);
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_close_fee_revenue),
+ &TALER_ARL_USE_AB (reserves_close_fee_revenue),
+ closing_fee);
+ TALER_ARL_amount_add (&rs->total_out,
+ &rs->total_out,
+ amount_with_fee);
+ if (0 != close_request_row)
{
- GNUNET_break (GNUNET_NO == rs->had_ri);
- report_row_inconsistency ("reserves_close",
- rowid,
- "target account not verified, auditor does not know reserve");
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct GNUNET_TIME_Timestamp request_timestamp;
+ struct TALER_Amount close_balance;
+ struct TALER_Amount close_fee;
+ char *payto_uri;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TALER_ARL_edb->select_reserve_close_request_info (
+ TALER_ARL_edb->cls,
+ reserve_pub,
+ close_request_row,
+ &reserve_sig,
+ &request_timestamp,
+ &close_balance,
+ &close_fee,
+ &payto_uri);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ report_row_inconsistency ("reserves_close",
+ rowid,
+ "reserve close request unknown");
+ }
+ else
+ {
+ struct TALER_PaytoHashP h_payto;
+
+ TALER_payto_hash (payto_uri,
+ &h_payto);
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_close_verify (
+ request_timestamp,
+ &h_payto,
+ reserve_pub,
+ &reserve_sig))
+ {
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "close-request"),
+ GNUNET_JSON_pack_uint64 ("row",
+ close_request_row),
+ TALER_JSON_pack_amount ("loss",
+ amount_with_fee),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ reserve_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ amount_with_fee);
+ }
+ }
+ if ( (NULL == payto_uri) &&
+ (NULL == rs->sender_account) )
+ {
+ GNUNET_break (! rs->had_ri);
+ report_row_inconsistency ("reserves_close",
+ rowid,
+ "target account not verified, auditor does not know reserve");
+ }
+ if (NULL == payto_uri)
+ {
+ if ( (NULL == rs->sender_account) ||
+ (0 != strcmp (rs->sender_account,
+ receiver_account)) )
+ {
+ report_row_inconsistency ("reserves_close",
+ rowid,
+ "target account does not match origin account");
+ }
+ }
+ else
+ {
+ if (0 != strcmp (payto_uri,
+ receiver_account))
+ {
+ report_row_inconsistency ("reserves_close",
+ rowid,
+ "target account does not match origin account");
+ }
+ }
+ GNUNET_free (payto_uri);
}
- else if (0 != strcmp (rs->sender_account,
- receiver_account))
+ else
{
- report_row_inconsistency ("reserves_close",
- rowid,
- "target account does not match origin account");
+ if (NULL == rs->sender_account)
+ {
+ GNUNET_break (! rs->had_ri);
+ report_row_inconsistency ("reserves_close",
+ rowid,
+ "target account not verified, auditor does not know reserve");
+ }
+ else if (0 != strcmp (rs->sender_account,
+ receiver_account))
+ {
+ report_row_inconsistency ("reserves_close",
+ rowid,
+ "target account does not match origin account");
+ }
}
+
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Additional closing operation for reserve `%s' of %s\n",
TALER_B2S (reserve_pub),
TALER_amount2s (amount_with_fee));
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about account merge requests that have been
+ * made, with the goal of accounting for the merge fee paid by the reserve (if
+ * applicable).
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param reserve_pub reserve affected by the merge
+ * @param purse_pub purse being merged
+ * @param h_contract_terms hash over contract of the purse
+ * @param purse_expiration when would the purse expire
+ * @param amount total amount in the purse
+ * @param min_age minimum age of all coins deposited into the purse
+ * @param flags how was the purse created
+ * @param purse_fee if a purse fee was paid, how high is it
+ * @param merge_timestamp when was the merge approved
+ * @param reserve_sig signature by reserve approving the merge
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+handle_account_merged (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_Amount *amount,
+ uint32_t min_age,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_Amount *purse_fee,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct ReserveContext *rc = cls;
+ struct ReserveSummary *rs;
+
+ /* should be monotonically increasing */
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_account_merges_serial_id));
+ TALER_ARL_USE_PP (reserves_account_merges_serial_id) = rowid + 1;
+ if (GNUNET_OK !=
+ TALER_wallet_account_merge_verify (merge_timestamp,
+ purse_pub,
+ purse_expiration,
+ h_contract_terms,
+ amount,
+ purse_fee,
+ min_age,
+ flags,
+ reserve_pub,
+ reserve_sig))
+ {
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "account-merge"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ purse_fee),
+ GNUNET_JSON_pack_data_auto ("key_pub",
+ reserve_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ purse_fee);
+ return GNUNET_OK;
+ }
+ if ( (flags & TALER_WAMF_MERGE_MODE_MASK) !=
+ TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE)
+ return GNUNET_OK; /* no impact on reserve balance */
+ rs = setup_reserve (rc,
+ reserve_pub);
+ if (NULL == rs)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_purse_fee_revenue),
+ &TALER_ARL_USE_AB (reserves_purse_fee_revenue),
+ purse_fee);
+ TALER_ARL_amount_add (&rs->curr_balance.purse_fee_balance,
+ &rs->curr_balance.purse_fee_balance,
+ purse_fee);
+ TALER_ARL_amount_add (&rs->total_out,
+ &rs->total_out,
+ purse_fee);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about a purse that was merged into an account.
+ * Only updates the reserve balance, the actual verifications are done in the
+ * purse helper.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the refund in our DB
+ * @param purse_pub public key of the purse
+ * @param reserve_pub which reserve is the purse credited to
+ * @param purse_value what is the target value of the purse
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+purse_decision_cb (void *cls,
+ uint64_t rowid,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *purse_value)
+{
+ struct ReserveContext *rc = cls;
+ struct ReserveSummary *rs;
+
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (
+ reserves_purse_decisions_serial_id)); /* should be monotonically increasing */
+ TALER_ARL_USE_PP (reserves_purse_decisions_serial_id) = rowid + 1;
+ rs = setup_reserve (rc,
+ reserve_pub);
+ if (NULL == rs)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ TALER_ARL_amount_add (&rs->total_in,
+ &rs->total_in,
+ purse_value);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
@@ -1009,129 +1288,137 @@ handle_reserve_closed (
* @param value a `struct ReserveSummary`
* @return #GNUNET_OK to process more entries
*/
-static int
+static enum GNUNET_GenericReturnValue
verify_reserve_balance (void *cls,
const struct GNUNET_HashCode *key,
void *value)
{
struct ReserveContext *rc = cls;
struct ReserveSummary *rs = value;
- struct TALER_EXCHANGEDB_Reserve reserve;
- struct TALER_Amount balance;
+ struct TALER_Amount mbalance;
struct TALER_Amount nbalance;
enum GNUNET_DB_QueryStatus qs;
- int ret;
+ enum GNUNET_GenericReturnValue ret;
ret = GNUNET_OK;
/* Check our reserve summary balance calculation shows that
the reserve balance is acceptable (i.e. non-negative) */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&balance,
- &rs->total_in,
- &rs->a_balance));
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&nbalance,
- &balance,
- &rs->total_out))
+ TALER_ARL_amount_add (&mbalance,
+ &rs->total_in,
+ &rs->prev_balance.reserve_balance);
+ if (TALER_ARL_SR_INVALID_NEGATIVE ==
+ TALER_ARL_amount_subtract_neg (&nbalance,
+ &mbalance,
+ &rs->total_out))
{
struct TALER_Amount loss;
- GNUNET_assert (GNUNET_SYSERR !=
- TALER_amount_subtract (&loss,
- &rs->total_out,
- &balance));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_balance_insufficient_loss,
- &total_balance_insufficient_loss,
- &loss));
+ TALER_ARL_amount_subtract (&loss,
+ &rs->total_out,
+ &mbalance);
+ TALER_ARL_amount_add (&rs->curr_balance.reserve_loss,
+ &rs->prev_balance.reserve_loss,
+ &loss);
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_reserve_loss),
+ &TALER_ARL_USE_AB (reserves_reserve_loss),
+ &loss);
TALER_ARL_report (report_reserve_balance_insufficient_inconsistencies,
- json_pack ("{s:o, s:o}",
- "reserve_pub",
- GNUNET_JSON_from_data_auto (&rs->reserve_pub),
- "loss",
- TALER_JSON_from_amount (&loss)));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &rs->reserve_pub),
+ TALER_JSON_pack_amount ("loss",
+ &loss)));
/* Continue with a reserve balance of zero */
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (balance.currency,
- &nbalance));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &rs->curr_balance.reserve_balance));
}
-
- /* Now check OUR balance calculation vs. the one the exchange has
- in its database */
- reserve.pub = rs->reserve_pub;
- qs = TALER_ARL_edb->reserves_get (TALER_ARL_edb->cls,
- TALER_ARL_esession,
- &reserve);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ else
{
- /* If the exchange doesn't have this reserve in the summary, it
- is like the exchange 'lost' that amount from its records,
- making an illegitimate gain over the amount it dropped.
- We don't add the amount to some total simply because it is
- not an actualized gain and could be trivially corrected by
- restoring the summary. *///
- TALER_ARL_report (report_reserve_balance_insufficient_inconsistencies,
- json_pack ("{s:o, s:o}",
- "reserve_pub",
- GNUNET_JSON_from_data_auto (&rs->reserve_pub),
- "gain",
- TALER_JSON_from_amount (&nbalance)));
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_break (0);
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- }
- rc->qs = qs;
+ /* Update remaining reserve balance! */
+ rs->curr_balance.reserve_balance = nbalance;
}
- else
+
+ if (internal_checks)
{
- /* Check that exchange's balance matches our expected balance for the reserve */
- if (0 != TALER_amount_cmp (&nbalance,
- &reserve.balance))
+ /* Now check OUR balance calculation vs. the one the exchange has
+ in its database. This can only be done when we are doing an
+ internal audit, as otherwise the balance of the 'reserves' table
+ is not replicated at the auditor. */
+ struct TALER_EXCHANGEDB_Reserve reserve;
+
+ reserve.pub = rs->reserve_pub;
+ qs = TALER_ARL_edb->reserves_get (TALER_ARL_edb->cls,
+ &reserve);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
- struct TALER_Amount delta;
-
- if (0 < TALER_amount_cmp (&nbalance,
- &reserve.balance))
+ /* If the exchange doesn't have this reserve in the summary, it
+ is like the exchange 'lost' that amount from its records,
+ making an illegitimate gain over the amount it dropped.
+ We don't add the amount to some total simply because it is
+ not an actualized gain and could be trivially corrected by
+ restoring the summary. */
+ TALER_ARL_report (report_reserve_balance_insufficient_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &rs->reserve_pub),
+ TALER_JSON_pack_amount ("gain",
+ &nbalance)));
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
- /* balance > reserve.balance */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- &nbalance,
- &reserve.balance));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_balance_summary_delta_plus,
- &total_balance_summary_delta_plus,
- &delta));
+ GNUNET_break (0);
+ qs = GNUNET_DB_STATUS_HARD_ERROR;
}
- else
+ rc->qs = qs;
+ }
+ else
+ {
+ /* Check that exchange's balance matches our expected balance for the reserve */
+ if (0 != TALER_amount_cmp (&rs->curr_balance.reserve_balance,
+ &reserve.balance))
{
- /* balance < reserve.balance */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- &reserve.balance,
- &nbalance));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_balance_summary_delta_minus,
- &total_balance_summary_delta_minus,
- &delta));
+ struct TALER_Amount delta;
+
+ if (0 < TALER_amount_cmp (&rs->curr_balance.reserve_balance,
+ &reserve.balance))
+ {
+ /* balance > reserve.balance */
+ TALER_ARL_amount_subtract (&delta,
+ &rs->curr_balance.reserve_balance,
+ &reserve.balance);
+ TALER_ARL_amount_add (&total_balance_summary_delta_plus,
+ &total_balance_summary_delta_plus,
+ &delta);
+ }
+ else
+ {
+ /* balance < reserve.balance */
+ TALER_ARL_amount_subtract (&delta,
+ &reserve.balance,
+ &rs->curr_balance.reserve_balance);
+ TALER_ARL_amount_add (&total_balance_summary_delta_minus,
+ &total_balance_summary_delta_minus,
+ &delta);
+ }
+ TALER_ARL_report (report_reserve_balance_summary_wrong_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &rs->reserve_pub),
+ TALER_JSON_pack_amount ("exchange",
+ &reserve.balance),
+ TALER_JSON_pack_amount ("auditor",
+ &rs->curr_balance.
+ reserve_balance)));
}
- TALER_ARL_report (report_reserve_balance_summary_wrong_inconsistencies,
- json_pack ("{s:o, s:o, s:o}",
- "reserve_pub",
- GNUNET_JSON_from_data_auto (
- &rs->reserve_pub),
- "exchange",
- TALER_JSON_from_amount (&reserve.balance),
- "auditor",
- TALER_JSON_from_amount (&nbalance)));
}
- }
+ } /* end of 'if (internal_checks)' */
/* Check that reserve is being closed if it is past its expiration date
(and the closing fee would not exceed the remaining balance) */
- if (CLOSING_GRACE_PERIOD.rel_value_us <
- GNUNET_TIME_absolute_get_duration (rs->a_expiration_date).rel_value_us)
+ if (GNUNET_TIME_relative_cmp (CLOSING_GRACE_PERIOD,
+ <,
+ GNUNET_TIME_absolute_get_duration (
+ rs->a_expiration_date.abs_time)))
{
/* Reserve is expired */
struct TALER_Amount cfee;
@@ -1147,103 +1434,111 @@ verify_reserve_balance (void *cls,
&cfee))
{
/* remaining balance (according to us) exceeds closing fee */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_balance_reserve_not_closed,
- &total_balance_reserve_not_closed,
- &nbalance));
- TALER_ARL_report (report_reserve_not_closed_inconsistencies,
- json_pack ("{s:o, s:o, s:o}",
- "reserve_pub",
- GNUNET_JSON_from_data_auto (
- &rs->reserve_pub),
- "balance",
- TALER_JSON_from_amount (&nbalance),
- "expiration_time",
- TALER_ARL_json_from_time_abs (
- rs->a_expiration_date)));
+ TALER_ARL_amount_add (&total_balance_reserve_not_closed,
+ &total_balance_reserve_not_closed,
+ &nbalance);
+ TALER_ARL_report (
+ report_reserve_not_closed_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &rs->reserve_pub),
+ TALER_JSON_pack_amount ("balance",
+ &nbalance),
+ TALER_JSON_pack_time_abs_human ("expiration_time",
+ rs->a_expiration_date.abs_time)));
}
}
else
{
/* We failed to determine the closing fee, complain! */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_balance_reserve_not_closed,
- &total_balance_reserve_not_closed,
- &nbalance));
- TALER_ARL_report (report_reserve_not_closed_inconsistencies,
- json_pack ("{s:o, s:o, s:o, s:s}",
- "reserve_pub",
- GNUNET_JSON_from_data_auto (
- &rs->reserve_pub),
- "balance",
- TALER_JSON_from_amount (&nbalance),
- "expiration_time",
- TALER_ARL_json_from_time_abs (
- rs->a_expiration_date),
- "diagnostic",
- "could not determine closing fee"));
+ TALER_ARL_amount_add (&total_balance_reserve_not_closed,
+ &total_balance_reserve_not_closed,
+ &nbalance);
+ TALER_ARL_report (
+ report_reserve_not_closed_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &rs->reserve_pub),
+ TALER_JSON_pack_amount ("balance",
+ &nbalance),
+ TALER_JSON_pack_time_abs_human ("expiration_time",
+ rs->a_expiration_date.abs_time),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "could not determine closing fee")));
}
}
- /* Add withdraw fees we encountered to totals */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Reserve reserve `%s' made %s in withdraw fees\n",
- TALER_B2S (&rs->reserve_pub),
- TALER_amount2s (&rs->total_fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&rs->a_withdraw_fee_balance,
- &rs->a_withdraw_fee_balance,
- &rs->total_fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_escrow_balance,
- &total_escrow_balance,
- &rs->total_in));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_add (&total_withdraw_fee_income,
- &total_withdraw_fee_income,
- &rs->total_fee));
+ /* We already computed the 'new' balance in 'curr_balance'
+ to include the previous balance, so this one is just
+ an assignment, not adding up! */
+ rs->prev_balance.reserve_balance = rs->curr_balance.reserve_balance;
+
+ /* Add up new totals to previous totals */
+ TALER_ARL_amount_add (&rs->prev_balance.reserve_loss,
+ &rs->prev_balance.reserve_loss,
+ &rs->curr_balance.reserve_loss);
+ TALER_ARL_amount_add (&rs->prev_balance.withdraw_fee_balance,
+ &rs->prev_balance.withdraw_fee_balance,
+ &rs->curr_balance.withdraw_fee_balance);
+ TALER_ARL_amount_add (&rs->prev_balance.close_fee_balance,
+ &rs->prev_balance.close_fee_balance,
+ &rs->curr_balance.close_fee_balance);
+ TALER_ARL_amount_add (&rs->prev_balance.purse_fee_balance,
+ &rs->prev_balance.purse_fee_balance,
+ &rs->curr_balance.purse_fee_balance);
+ TALER_ARL_amount_add (&rs->prev_balance.open_fee_balance,
+ &rs->prev_balance.open_fee_balance,
+ &rs->curr_balance.open_fee_balance);
+ TALER_ARL_amount_add (&rs->prev_balance.history_fee_balance,
+ &rs->prev_balance.history_fee_balance,
+ &rs->curr_balance.history_fee_balance);
+
+ /* Update global balance: add incoming first, then try
+ to subtract outgoing... */
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_reserve_total_balance),
+ &TALER_ARL_USE_AB (reserves_reserve_total_balance),
+ &rs->total_in);
{
struct TALER_Amount r;
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&r,
- &total_escrow_balance,
- &rs->total_out))
+ if (TALER_ARL_SR_INVALID_NEGATIVE ==
+ TALER_ARL_amount_subtract_neg (&r,
+ &TALER_ARL_USE_AB (
+ reserves_reserve_total_balance),
+ &rs->total_out))
{
/* We could not reduce our total balance, i.e. exchange allowed IN TOTAL (!)
to be withdrawn more than it was IN TOTAL ever given (exchange balance
went negative!). Woopsie. Calculate how badly it went and log. */
report_amount_arithmetic_inconsistency ("global escrow balance",
- UINT64_MAX,
- &total_escrow_balance, /* what we had */
- &rs->total_out, /* what we needed */
+ 0,
+ &TALER_ARL_USE_AB (
+ reserves_reserve_total_balance), /* what we had */
+ &rs->total_out, /* what we needed */
0 /* specific profit/loss does not apply to the total summary */);
/* We unexpectedly went negative, so a sane value to continue from
would be zero. */
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_escrow_balance));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ reserves_reserve_total_balance)));
}
else
{
- total_escrow_balance = r;
+ TALER_ARL_USE_AB (reserves_reserve_total_balance) = r;
}
}
- if ( (0ULL == balance.value) &&
- (0U == balance.fraction) )
+ if (TALER_amount_is_zero (&rs->prev_balance.reserve_balance))
{
/* balance is zero, drop reserve details (and then do not update/insert) */
if (rs->had_ri)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Final balance of reserve `%s' is %s, dropping it\n",
- TALER_B2S (&rs->reserve_pub),
- TALER_amount2s (&nbalance));
+ "Final balance of reserve `%s' is zero, dropping it\n",
+ TALER_B2S (&rs->reserve_pub));
qs = TALER_ARL_adb->del_reserve_info (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &rs->reserve_pub,
- &TALER_ARL_master_pub);
+ &rs->reserve_pub);
if (0 >= qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
@@ -1254,9 +1549,8 @@ verify_reserve_balance (void *cls,
else
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Final balance of reserve `%s' is %s, no need to remember it\n",
- TALER_B2S (&rs->reserve_pub),
- TALER_amount2s (&nbalance));
+ "Final balance of reserve `%s' is zero, no need to remember it\n",
+ TALER_B2S (&rs->reserve_pub));
}
}
else
@@ -1265,22 +1559,16 @@ verify_reserve_balance (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Remembering final balance of reserve `%s' as %s\n",
TALER_B2S (&rs->reserve_pub),
- TALER_amount2s (&nbalance));
+ TALER_amount2s (&rs->prev_balance.reserve_balance));
if (rs->had_ri)
qs = TALER_ARL_adb->update_reserve_info (TALER_ARL_adb->cls,
- TALER_ARL_asession,
&rs->reserve_pub,
- &TALER_ARL_master_pub,
- &nbalance,
- &rs->a_withdraw_fee_balance,
+ &rs->prev_balance,
rs->a_expiration_date);
else
qs = TALER_ARL_adb->insert_reserve_info (TALER_ARL_adb->cls,
- TALER_ARL_asession,
&rs->reserve_pub,
- &TALER_ARL_master_pub,
- &nbalance,
- &rs->a_withdraw_fee_balance,
+ &rs->prev_balance,
rs->a_expiration_date,
rs->sender_account);
if (0 >= qs)
@@ -1290,12 +1578,12 @@ verify_reserve_balance (void *cls,
rc->qs = qs;
}
}
-
+ /* now we can discard the cached entry */
GNUNET_assert (GNUNET_YES ==
GNUNET_CONTAINER_multihashmap_remove (rc->reserves,
key,
rs));
- GNUNET_free_non_null (rs->sender_account);
+ GNUNET_free (rs->sender_account);
GNUNET_free (rs);
return ret;
}
@@ -1318,10 +1606,17 @@ analyze_reserves (void *cls)
(void) cls;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Analyzing reserves\n");
- qsp = TALER_ARL_adb->get_auditor_progress_reserve (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &ppr);
+ qsp = TALER_ARL_adb->get_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_PP (reserves_reserve_in_serial_id),
+ TALER_ARL_GET_PP (reserves_reserve_out_serial_id),
+ TALER_ARL_GET_PP (reserves_reserve_recoup_serial_id),
+ TALER_ARL_GET_PP (reserves_reserve_open_serial_id),
+ TALER_ARL_GET_PP (reserves_reserve_close_serial_id),
+ TALER_ARL_GET_PP (reserves_purse_decisions_serial_id),
+ TALER_ARL_GET_PP (reserves_account_merges_serial_id),
+ TALER_ARL_GET_PP (reserves_history_requests_serial_id),
+ NULL);
if (0 > qsp)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsp);
@@ -1334,20 +1629,36 @@ analyze_reserves (void *cls)
}
else
{
- ppr_start = ppr;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming reserve audit at %llu/%llu/%llu/%llu\n",
- (unsigned long long) ppr.last_reserve_in_serial_id,
- (unsigned long long) ppr.last_reserve_out_serial_id,
- (unsigned long long) ppr.last_reserve_recoup_serial_id,
- (unsigned long long) ppr.last_reserve_close_serial_id);
+ "Resuming reserve audit at %llu/%llu/%llu/%llu/%llu/%llu/%llu/%llu\n",
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_reserve_in_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_reserve_out_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_reserve_recoup_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_reserve_open_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_reserve_close_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_purse_decisions_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_account_merges_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_history_requests_serial_id));
}
rc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
- qsx = TALER_ARL_adb->get_reserve_summary (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &total_escrow_balance,
- &total_withdraw_fee_income);
+ qsx = TALER_ARL_adb->get_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_AB (reserves_reserve_total_balance),
+ TALER_ARL_GET_AB (reserves_reserve_loss),
+ TALER_ARL_GET_AB (reserves_withdraw_fee_revenue),
+ TALER_ARL_GET_AB (reserves_close_fee_revenue),
+ TALER_ARL_GET_AB (reserves_purse_fee_revenue),
+ TALER_ARL_GET_AB (reserves_open_fee_revenue),
+ TALER_ARL_GET_AB (reserves_history_fee_revenue),
+ NULL);
if (qsx < 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx);
@@ -1357,11 +1668,9 @@ analyze_reserves (void *cls)
GNUNET_NO);
rc.revoked = GNUNET_CONTAINER_multihashmap_create (4,
GNUNET_NO);
-
qs = TALER_ARL_edb->select_reserves_in_above_serial_id (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- ppr.last_reserve_in_serial_id,
+ TALER_ARL_USE_PP (reserves_reserve_in_serial_id),
&handle_reserve_in,
&rc);
if (qs < 0)
@@ -1371,8 +1680,7 @@ analyze_reserves (void *cls)
}
qs = TALER_ARL_edb->select_withdrawals_above_serial_id (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- ppr.last_reserve_out_serial_id,
+ TALER_ARL_USE_PP (reserves_reserve_out_serial_id),
&handle_reserve_out,
&rc);
if (qs < 0)
@@ -1382,8 +1690,7 @@ analyze_reserves (void *cls)
}
qs = TALER_ARL_edb->select_recoup_above_serial_id (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- ppr.last_reserve_recoup_serial_id,
+ TALER_ARL_USE_PP (reserves_reserve_recoup_serial_id),
&handle_recoup_by_reserve,
&rc);
if (qs < 0)
@@ -1391,10 +1698,19 @@ analyze_reserves (void *cls)
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
return qs;
}
+ qs = TALER_ARL_edb->select_reserve_open_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (reserves_reserve_open_serial_id),
+ &handle_reserve_open,
+ &rc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
qs = TALER_ARL_edb->select_reserve_closed_above_serial_id (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- ppr.last_reserve_close_serial_id,
+ TALER_ARL_USE_PP (reserves_reserve_close_serial_id),
&handle_reserve_closed,
&rc);
if (qs < 0)
@@ -1402,7 +1718,31 @@ analyze_reserves (void *cls)
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
return qs;
}
-
+ /* process purse_decisions (to credit reserve) */
+ if (0 >
+ (qs = TALER_ARL_edb->select_purse_decisions_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (reserves_purse_decisions_serial_id),
+ false, /* only go for merged purses! */
+ &purse_decision_cb,
+ &rc)))
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ if (0 > rc.qs)
+ return rc.qs;
+ /* Charge purse fee! */
+ qs = TALER_ARL_edb->select_account_merges_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (reserves_account_merges_serial_id),
+ &handle_account_merged,
+ &rc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
GNUNET_CONTAINER_multihashmap_iterate (rc.reserves,
&verify_reserve_balance,
&rc);
@@ -1410,25 +1750,33 @@ analyze_reserves (void *cls)
GNUNET_CONTAINER_multihashmap_size (rc.reserves));
GNUNET_CONTAINER_multihashmap_destroy (rc.reserves);
GNUNET_CONTAINER_multihashmap_destroy (rc.revoked);
-
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != rc.qs)
return qs;
-
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsx)
{
- qs = TALER_ARL_adb->insert_reserve_summary (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &total_escrow_balance,
- &total_withdraw_fee_income);
+ qs = TALER_ARL_adb->insert_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_AB (reserves_reserve_total_balance),
+ TALER_ARL_SET_AB (reserves_reserve_loss),
+ TALER_ARL_SET_AB (reserves_withdraw_fee_revenue),
+ TALER_ARL_SET_AB (reserves_close_fee_revenue),
+ TALER_ARL_SET_AB (reserves_purse_fee_revenue),
+ TALER_ARL_SET_AB (reserves_open_fee_revenue),
+ TALER_ARL_SET_AB (reserves_history_fee_revenue),
+ NULL);
}
else
{
- qs = TALER_ARL_adb->update_reserve_summary (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &total_escrow_balance,
- &total_withdraw_fee_income);
+ qs = TALER_ARL_adb->update_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_AB (reserves_reserve_total_balance),
+ TALER_ARL_SET_AB (reserves_reserve_loss),
+ TALER_ARL_SET_AB (reserves_withdraw_fee_revenue),
+ TALER_ARL_SET_AB (reserves_close_fee_revenue),
+ TALER_ARL_SET_AB (reserves_purse_fee_revenue),
+ TALER_ARL_SET_AB (reserves_open_fee_revenue),
+ TALER_ARL_SET_AB (reserves_history_fee_revenue),
+ NULL);
}
if (0 >= qs)
{
@@ -1436,15 +1784,29 @@ analyze_reserves (void *cls)
return qs;
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp)
- qs = TALER_ARL_adb->update_auditor_progress_reserve (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &ppr);
+ qs = TALER_ARL_adb->update_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_PP (reserves_reserve_in_serial_id),
+ TALER_ARL_SET_PP (reserves_reserve_out_serial_id),
+ TALER_ARL_SET_PP (reserves_reserve_recoup_serial_id),
+ TALER_ARL_SET_PP (reserves_reserve_open_serial_id),
+ TALER_ARL_SET_PP (reserves_reserve_close_serial_id),
+ TALER_ARL_SET_PP (reserves_purse_decisions_serial_id),
+ TALER_ARL_SET_PP (reserves_account_merges_serial_id),
+ TALER_ARL_SET_PP (reserves_history_requests_serial_id),
+ NULL);
else
- qs = TALER_ARL_adb->insert_auditor_progress_reserve (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &ppr);
+ qs = TALER_ARL_adb->insert_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_PP (reserves_reserve_in_serial_id),
+ TALER_ARL_SET_PP (reserves_reserve_out_serial_id),
+ TALER_ARL_SET_PP (reserves_reserve_recoup_serial_id),
+ TALER_ARL_SET_PP (reserves_reserve_open_serial_id),
+ TALER_ARL_SET_PP (reserves_reserve_close_serial_id),
+ TALER_ARL_SET_PP (reserves_purse_decisions_serial_id),
+ TALER_ARL_SET_PP (reserves_account_merges_serial_id),
+ TALER_ARL_SET_PP (reserves_history_requests_serial_id),
+ NULL);
if (0 >= qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -1453,11 +1815,23 @@ analyze_reserves (void *cls)
return qs;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Concluded reserve audit step at %llu/%llu/%llu/%llu\n",
- (unsigned long long) ppr.last_reserve_in_serial_id,
- (unsigned long long) ppr.last_reserve_out_serial_id,
- (unsigned long long) ppr.last_reserve_recoup_serial_id,
- (unsigned long long) ppr.last_reserve_close_serial_id);
+ "Concluded reserve audit step at %llu/%llu/%llu/%llu/%llu/%llu/%llu/%llu\n",
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_reserve_in_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_reserve_out_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_reserve_recoup_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_reserve_open_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_reserve_close_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_purse_decisions_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_account_merges_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ reserves_history_requests_serial_id));
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
@@ -1484,7 +1858,7 @@ run (void *cls,
if (GNUNET_OK !=
TALER_ARL_init (c))
{
- global_ret = 1;
+ global_ret = EXIT_FAILURE;
return;
}
if (GNUNET_OK !=
@@ -1496,41 +1870,59 @@ run (void *cls,
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchangedb",
"IDLE_RESERVE_EXPIRATION_TIME");
- global_ret = 1;
+ global_ret = EXIT_FAILURE;
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Starting audit\n");
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_escrow_balance));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ reserves_reserve_total_balance)));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ reserves_reserve_loss)));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ reserves_withdraw_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_irregular_recoups));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ reserves_close_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_withdraw_fee_income));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ reserves_purse_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_balance_insufficient_loss));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ reserves_open_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ reserves_history_fee_revenue)));
+ // REVIEW:
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_balance_summary_delta_plus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_balance_summary_delta_minus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_arithmetic_delta_plus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_arithmetic_delta_minus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_balance_reserve_not_closed));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_bad_sig_loss));
+
GNUNET_assert (NULL !=
(report_row_inconsistencies = json_array ()));
GNUNET_assert (NULL !=
@@ -1543,6 +1935,9 @@ run (void *cls,
(report_reserve_balance_insufficient_inconsistencies
= json_array ()));
GNUNET_assert (NULL !=
+ (report_purse_balance_insufficient_inconsistencies
+ = json_array ()));
+ GNUNET_assert (NULL !=
(report_reserve_not_closed_inconsistencies
= json_array ()));
GNUNET_assert (NULL !=
@@ -1554,103 +1949,117 @@ run (void *cls,
TALER_ARL_setup_sessions_and_run (&analyze_reserves,
NULL))
{
- global_ret = 1;
+ global_ret = EXIT_FAILURE;
return;
}
- {
- json_t *report;
-
- report = json_pack ("{s:o, s:o, s:o, s:o, s:o,"
- " s:o, s:o, s:o, s:o, s:o,"
- " s:o, s:o, s:o, s:o, s:o,"
- " s:o, s:o, s:o, s:o, s:I,"
- " s:I, s:I, s:I, s:I, s:I,"
- " s:I, s:I }",
- /* blocks #1 */
- "reserve_balance_insufficient_inconsistencies",
- report_reserve_balance_insufficient_inconsistencies,
- /* Tested in test-auditor.sh #3 */
- "total_loss_balance_insufficient",
- TALER_JSON_from_amount (
- &total_balance_insufficient_loss),
- /* Tested in test-auditor.sh #3 */
- "reserve_balance_summary_wrong_inconsistencies",
- report_reserve_balance_summary_wrong_inconsistencies,
- "total_balance_summary_delta_plus",
- TALER_JSON_from_amount (
- &total_balance_summary_delta_plus),
- "total_balance_summary_delta_minus",
- TALER_JSON_from_amount (
- &total_balance_summary_delta_minus),
- /* blocks #2 */
- "total_escrow_balance",
- TALER_JSON_from_amount (&total_escrow_balance),
- "total_withdraw_fee_income",
- TALER_JSON_from_amount (
- &total_withdraw_fee_income),
- /* Tested in test-auditor.sh #21 */
- "reserve_not_closed_inconsistencies",
- report_reserve_not_closed_inconsistencies,
- /* Tested in test-auditor.sh #21 */
- "total_balance_reserve_not_closed",
- TALER_JSON_from_amount (
- &total_balance_reserve_not_closed),
- /* Tested in test-auditor.sh #7 */
- "bad_sig_losses",
- report_bad_sig_losses,
- /* blocks #3 */
- /* Tested in test-auditor.sh #7 */
- "total_bad_sig_loss",
- TALER_JSON_from_amount (&total_bad_sig_loss),
- /* Tested in test-revocation.sh #4 */
- "row_inconsistencies",
- report_row_inconsistencies,
- /* Tested in test-auditor.sh #23 */
- "denomination_key_validity_withdraw_inconsistencies",
- denomination_key_validity_withdraw_inconsistencies,
- "amount_arithmetic_inconsistencies",
- report_amount_arithmetic_inconsistencies,
- "total_arithmetic_delta_plus",
- TALER_JSON_from_amount (
- &total_arithmetic_delta_plus),
- /* blocks #4 */
- "total_arithmetic_delta_minus",
- TALER_JSON_from_amount (
- &total_arithmetic_delta_minus),
- "auditor_start_time",
- TALER_ARL_json_from_time_abs (
- start_time),
- "auditor_end_time",
- TALER_ARL_json_from_time_abs (
- GNUNET_TIME_absolute_get ()),
- "total_irregular_recoups",
- TALER_JSON_from_amount (
- &total_irregular_recoups),
- "start_ppr_reserve_in_serial_id",
- (json_int_t) ppr_start.last_reserve_in_serial_id,
- /* blocks #5 */
- "start_ppr_reserve_out_serial_id",
- (json_int_t) ppr_start.
- last_reserve_out_serial_id,
- "start_ppr_reserve_recoup_serial_id",
- (json_int_t) ppr_start.
- last_reserve_recoup_serial_id,
- "start_ppr_reserve_close_serial_id",
- (json_int_t) ppr_start.
- last_reserve_close_serial_id,
- "end_ppr_reserve_in_serial_id",
- (json_int_t) ppr.last_reserve_in_serial_id,
- "end_ppr_reserve_out_serial_id",
- (json_int_t) ppr.last_reserve_out_serial_id,
- /* blocks #6 */
- "end_ppr_reserve_recoup_serial_id",
- (json_int_t) ppr.last_reserve_recoup_serial_id,
- "end_ppr_reserve_close_serial_id",
- (json_int_t) ppr.last_reserve_close_serial_id
- );
- GNUNET_break (NULL != report);
- TALER_ARL_done (report);
- }
+ TALER_ARL_done (
+ GNUNET_JSON_PACK (
+ /* Tested in test-auditor.sh #3 */
+ GNUNET_JSON_pack_array_steal (
+ "reserve_balance_summary_wrong_inconsistencies",
+ report_reserve_balance_summary_wrong_inconsistencies),
+ TALER_JSON_pack_amount ("total_balance_summary_delta_plus",
+ &total_balance_summary_delta_plus),
+ TALER_JSON_pack_amount ("total_balance_summary_delta_minus",
+ &total_balance_summary_delta_minus),
+ /* Tested in test-auditor.sh #21 */
+ TALER_JSON_pack_amount ("total_balance_reserve_not_closed",
+ &total_balance_reserve_not_closed),
+ /* Tested in test-auditor.sh #7 */
+ TALER_JSON_pack_amount ("total_bad_sig_loss",
+ &total_bad_sig_loss),
+ TALER_JSON_pack_amount ("total_arithmetic_delta_plus",
+ &total_arithmetic_delta_plus),
+ TALER_JSON_pack_amount ("total_arithmetic_delta_minus",
+ &total_arithmetic_delta_minus),
+
+ /* Global 'balances' */
+ TALER_JSON_pack_amount ("total_escrow_balance",
+ &TALER_ARL_USE_AB (
+ reserves_reserve_total_balance)),
+ /* Tested in test-auditor.sh #3 */
+ TALER_JSON_pack_amount ("total_irregular_loss",
+ &TALER_ARL_USE_AB (reserves_reserve_loss)),
+ TALER_JSON_pack_amount ("total_withdraw_fee_income",
+ &TALER_ARL_USE_AB (
+ reserves_withdraw_fee_revenue)),
+ TALER_JSON_pack_amount ("total_close_fee_income",
+ &TALER_ARL_USE_AB (reserves_close_fee_revenue)),
+ TALER_JSON_pack_amount ("total_purse_fee_income",
+ &TALER_ARL_USE_AB (reserves_purse_fee_revenue)),
+ TALER_JSON_pack_amount ("total_open_fee_income",
+ &TALER_ARL_USE_AB (reserves_open_fee_revenue)),
+ TALER_JSON_pack_amount ("total_history_fee_income",
+ &TALER_ARL_USE_AB (reserves_history_fee_revenue)),
+
+ /* Detailed report tables */
+ GNUNET_JSON_pack_array_steal (
+ "reserve_balance_insufficient_inconsistencies",
+ report_reserve_balance_insufficient_inconsistencies),
+ GNUNET_JSON_pack_array_steal (
+ "purse_balance_insufficient_inconsistencies",
+ report_purse_balance_insufficient_inconsistencies),
+ /* Tested in test-auditor.sh #21 */
+ GNUNET_JSON_pack_array_steal ("reserve_not_closed_inconsistencies",
+ report_reserve_not_closed_inconsistencies),
+ /* Tested in test-auditor.sh #7 */
+ GNUNET_JSON_pack_array_steal ("bad_sig_losses",
+ report_bad_sig_losses),
+ /* Tested in test-revocation.sh #4 */
+ GNUNET_JSON_pack_array_steal ("row_inconsistencies",
+ report_row_inconsistencies),
+ /* Tested in test-auditor.sh #23 */
+ GNUNET_JSON_pack_array_steal (
+ "denomination_key_validity_withdraw_inconsistencies",
+ denomination_key_validity_withdraw_inconsistencies),
+ GNUNET_JSON_pack_array_steal ("amount_arithmetic_inconsistencies",
+ report_amount_arithmetic_inconsistencies),
+
+ /* Information about audited range ... */
+ TALER_JSON_pack_time_abs_human ("auditor_start_time",
+ start_time),
+ TALER_JSON_pack_time_abs_human ("auditor_end_time",
+ GNUNET_TIME_absolute_get ()),
+ GNUNET_JSON_pack_uint64 ("start_ppr_reserve_in_serial_id",
+ 0 /* no longer supported */),
+ GNUNET_JSON_pack_uint64 ("start_ppr_reserve_out_serial_id",
+ 0 /* no longer supported */),
+ GNUNET_JSON_pack_uint64 ("start_ppr_reserve_recoup_serial_id",
+ 0 /* no longer supported */),
+ GNUNET_JSON_pack_uint64 ("start_ppr_reserve_open_serial_id",
+ 0 /* no longer supported */),
+ GNUNET_JSON_pack_uint64 ("start_ppr_reserve_close_serial_id",
+ 0 /* no longer supported */),
+ GNUNET_JSON_pack_uint64 ("start_ppr_purse_decisions_serial_id",
+ 0 /* no longer supported */),
+ GNUNET_JSON_pack_uint64 ("start_ppr_account_merges_serial_id",
+ 0 /* no longer supported */),
+ GNUNET_JSON_pack_uint64 ("start_ppr_history_requests_serial_id",
+ 0 /* no longer supported */),
+ GNUNET_JSON_pack_uint64 ("end_ppr_reserve_in_serial_id",
+ TALER_ARL_USE_PP (
+ reserves_reserve_in_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppr_reserve_out_serial_id",
+ TALER_ARL_USE_PP (
+ reserves_reserve_out_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppr_reserve_recoup_serial_id",
+ TALER_ARL_USE_PP (
+ reserves_reserve_recoup_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppr_reserve_open_serial_id",
+ TALER_ARL_USE_PP (
+ reserves_reserve_open_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppr_reserve_close_serial_id",
+ TALER_ARL_USE_PP (
+ reserves_reserve_close_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppr_purse_decisions_serial_id",
+ TALER_ARL_USE_PP (
+ reserves_purse_decisions_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppr_account_merges_serial_id",
+ TALER_ARL_USE_PP (
+ reserves_account_merges_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppr_history_requests_serial_id",
+ TALER_ARL_USE_PP (
+ reserves_history_requests_serial_id))));
}
@@ -1666,33 +2075,41 @@ main (int argc,
char *const *argv)
{
const struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_base32_auto ('m',
- "exchange-key",
- "KEY",
- "public key of the exchange (Crockford base32 encoded)",
- &TALER_ARL_master_pub),
+ GNUNET_GETOPT_option_flag ('i',
+ "internal",
+ "perform checks only applicable for exchange-internal audits",
+ &internal_checks),
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue 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 */
(void) TALER_project_data_default ();
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-helper-auditor-reserves",
- "MESSAGE",
- NULL));
if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc,
- argv,
- "taler-helper-auditor-reserves",
- "Audit Taler exchange reserve handling",
- options,
- &run,
- NULL))
- return 1;
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ ret = GNUNET_PROGRAM_run (
+ argc,
+ argv,
+ "taler-helper-auditor-reserves",
+ gettext_noop ("Audit Taler exchange reserve handling"),
+ options,
+ &run,
+ NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
return global_ret;
}
diff --git a/src/auditor/taler-helper-auditor-wire.c b/src/auditor/taler-helper-auditor-wire.c
index 59cb544a4..d48ac1f18 100644
--- a/src/auditor/taler-helper-auditor-wire.c
+++ b/src/auditor/taler-helper-auditor-wire.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2017-2020 Taler Systems SA
+ Copyright (C) 2017-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
@@ -53,6 +53,20 @@
/**
+ * Run in test mode. Exit when idle instead of
+ * going to sleep and waiting for more work.
+ *
+ * FIXME: not yet implemented!
+ */
+static int test_mode;
+
+struct TALER_AUDITORDB_WireAccountProgressPoint
+{
+ uint64_t last_reserve_in_serial_id;
+ uint64_t last_wire_out_serial_id;
+};
+
+/**
* Information we keep for each supported account.
*/
struct WireAccount
@@ -68,14 +82,9 @@ struct WireAccount
struct WireAccount *prev;
/**
- * Authentication data for the account.
- */
- struct TALER_BANK_AuthenticationData auth;
-
- /**
- * Name of the section that configures this account.
+ * Account details.
*/
- char *section_name;
+ const struct TALER_EXCHANGEDB_AccountInfo *ai;
/**
* Active wire request for the transaction history.
@@ -98,24 +107,34 @@ struct WireAccount
struct TALER_AUDITORDB_WireAccountProgressPoint start_pp;
/**
- * Where we are in the inbound (CREDIT) transaction history.
+ * Where we are in the inbound transaction history.
*/
- uint64_t in_wire_off;
+ uint64_t wire_off_in;
/**
- * Where we are in the inbound (DEBIT) transaction history.
+ * Where we are in the outbound transaction history.
*/
- uint64_t out_wire_off;
+ uint64_t wire_off_out;
/**
- * We should check for inbound transactions to this account.
+ * Label under which we store our pp's reserve_in_serial_id.
*/
- int watch_credit;
+ char *label_reserve_in_serial_id;
/**
- * We should check for outbound transactions from this account.
+ * Label under which we store our pp's reserve_in_serial_id.
*/
- int watch_debit;
+ char *label_wire_out_serial_id;
+
+ /**
+ * Label under which we store our wire_off_in.
+ */
+ char *label_wire_off_in;
+
+ /**
+ * Label under which we store our wire_off_out.
+ */
+ char *label_wire_off_out;
/**
* Return value when we got this account's progress point.
@@ -137,7 +156,7 @@ struct ReserveClosure
/**
* When was the reserve closed?
*/
- struct GNUNET_TIME_Absolute execution_date;
+ struct GNUNET_TIME_Timestamp execution_date;
/**
* Amount transferred (amount remaining minus fee).
@@ -198,20 +217,17 @@ static enum GNUNET_DB_QueryStatus qsx_gwap;
/**
* Last reserve_in / wire_out serial IDs seen.
*/
-static struct TALER_AUDITORDB_WireProgressPoint pp;
+static TALER_ARL_DEF_PP (wire_reserve_close_id);
+static TALER_ARL_DEF_PP (wire_batch_deposit_id);
+static TALER_ARL_DEF_PP (wire_aggregation_id);
/**
- * Last reserve_in / wire_out serial IDs seen.
- */
-static struct TALER_AUDITORDB_WireProgressPoint start_pp;
-
-/**
- * Array of reports about row inconsitencies in wire_out table.
+ * Array of reports about row inconsistencies in wire_out table.
*/
static json_t *report_wire_out_inconsistencies;
/**
- * Array of reports about row inconsitencies in reserves_in table.
+ * Array of reports about row inconsistencies in reserves_in table.
*/
static json_t *report_reserve_in_inconsistencies;
@@ -219,7 +235,7 @@ static json_t *report_reserve_in_inconsistencies;
* Array of reports about wrong bank account being recorded for
* incoming wire transfers.
*/
-static json_t *report_missattribution_in_inconsistencies;
+static json_t *report_misattribution_in_inconsistencies;
/**
* Array of reports about row inconsistencies.
@@ -243,6 +259,16 @@ static json_t *report_row_minor_inconsistencies;
static json_t *report_lags;
/**
+ * Array of reports about lagging transactions from deposits due to missing KYC.
+ */
+static json_t *report_kyc_lags;
+
+/**
+ * Array of reports about lagging transactions from deposits due to pending or frozen AML decisions.
+ */
+static json_t *report_aml_lags;
+
+/**
* Array of reports about lagging transactions from reserve closures.
*/
static json_t *report_closure_lags;
@@ -282,7 +308,7 @@ static struct TALER_Amount total_bad_amount_in_minus;
* for incoming funds and may thus wire funds to the wrong
* destination when closing the reserve.
*/
-static struct TALER_Amount total_missattribution_in;
+static struct TALER_Amount total_misattribution_in;
/**
* Total amount which the exchange did not transfer in time.
@@ -300,6 +326,36 @@ static struct TALER_Amount total_closure_amount_lag;
static struct TALER_Amount total_wire_format_amount;
/**
+ * Total amount credited to exchange accounts.
+ */
+static struct TALER_Amount total_wire_in;
+
+/**
+ * Total amount debited to exchange accounts.
+ */
+static struct TALER_Amount total_wire_out;
+
+/**
+ * Total amount of profits drained.
+ */
+static TALER_ARL_DEF_AB (total_drained);
+
+/**
+ * Final balance at the end of this iteration.
+ */
+static TALER_ARL_DEF_AB (final_balance);
+
+/**
+ * Starting balance at the beginning of this iteration.
+ */
+static struct TALER_Amount start_balance;
+
+/**
+ * True if #start_balance was initialized.
+ */
+static bool had_start_balance;
+
+/**
* Amount of zero in our currency.
*/
static struct TALER_Amount zero;
@@ -314,6 +370,16 @@ static struct GNUNET_CURL_Context *ctx;
*/
static struct GNUNET_CURL_RescheduleContext *rc;
+/**
+ * Should we run checks that only work for exchange-internal audits?
+ */
+static int internal_checks;
+
+/**
+ * Should we ignore if the bank does not know our bank
+ * account?
+ */
+static int ignore_account_404;
/* ***************************** Shutdown **************************** */
@@ -372,7 +438,7 @@ struct ReserveOutInfo
* @param value the `struct ReserveInInfo` to free
* @return #GNUNET_OK
*/
-static int
+static enum GNUNET_GenericReturnValue
free_rii (void *cls,
const struct GNUNET_HashCode *key,
void *value)
@@ -397,7 +463,7 @@ free_rii (void *cls,
* @param value the `struct ReserveOutInfo` to free
* @return #GNUNET_OK
*/
-static int
+static enum GNUNET_GenericReturnValue
free_roi (void *cls,
const struct GNUNET_HashCode *key,
void *value)
@@ -422,7 +488,7 @@ free_roi (void *cls,
* @param value the `struct ReserveClosure` to free
* @return #GNUNET_OK
*/
-static int
+static enum GNUNET_GenericReturnValue
free_rc (void *cls,
const struct GNUNET_HashCode *key,
void *value)
@@ -453,99 +519,93 @@ do_shutdown (void *cls)
(void) cls;
if (NULL != report_row_inconsistencies)
{
- json_t *report;
-
GNUNET_assert (NULL != report_row_minor_inconsistencies);
- report = json_pack ("{s:o, s:o, s:o, s:o, s:o,"
- " s:o, s:o, s:o, s:o, s:o,"
- " s:o, s:o, s:o, s:o, s:o,"
- " s:o, s:o, s:o, s:I, s:I,"
- " s:o, s:o, s:o }",
- /* blocks of 5 */
- /* Tested in test-auditor.sh #11, #15, #20 */
- "wire_out_amount_inconsistencies",
- report_wire_out_inconsistencies,
- "total_wire_out_delta_plus",
- TALER_JSON_from_amount (
- &total_bad_amount_out_plus),
- /* Tested in test-auditor.sh #11, #15, #19 */
- "total_wire_out_delta_minus",
- TALER_JSON_from_amount (
- &total_bad_amount_out_minus),
- /* Tested in test-auditor.sh #2 */
- "reserve_in_amount_inconsistencies",
- report_reserve_in_inconsistencies,
- /* Tested in test-auditor.sh #2 */
- "total_wire_in_delta_plus",
- TALER_JSON_from_amount (
- &total_bad_amount_in_plus),
- /* block */
- /* Tested in test-auditor.sh #3 */
- "total_wire_in_delta_minus",
- TALER_JSON_from_amount (
- &total_bad_amount_in_minus),
- /* Tested in test-auditor.sh #9 */
- "missattribution_in_inconsistencies",
- report_missattribution_in_inconsistencies,
- /* Tested in test-auditor.sh #9 */
- "total_missattribution_in",
- TALER_JSON_from_amount (
- &total_missattribution_in),
- "row_inconsistencies",
- report_row_inconsistencies,
- /* Tested in test-auditor.sh #10/#17 */
- "row_minor_inconsistencies",
- report_row_minor_inconsistencies,
- /* block */
- /* Tested in test-auditor.sh #19 */
- "total_wire_format_amount",
- TALER_JSON_from_amount (
- &total_wire_format_amount),
- /* Tested in test-auditor.sh #19 */
- "wire_format_inconsistencies",
- report_wire_format_inconsistencies,
- /* Tested in test-auditor.sh #1 */
- "total_amount_lag",
- TALER_JSON_from_amount (&total_amount_lag),
- /* Tested in test-auditor.sh #1 */
- "lag_details",
- report_lags,
- /* Tested in test-auditor.sh #22 */
- "total_closure_amount_lag",
- TALER_JSON_from_amount (
- &total_closure_amount_lag),
- /* blocks of 5 */
- /* Tested in test-auditor.sh #22 */
- "reserve_lag_details",
- report_closure_lags,
- "wire_auditor_start_time",
- TALER_ARL_json_from_time_abs (
- start_time),
- "wire_auditor_end_time",
- TALER_ARL_json_from_time_abs (
- GNUNET_TIME_absolute_get ()),
- "start_pp_reserve_close_uuid",
- (json_int_t) start_pp.last_reserve_close_uuid,
- "end_pp_reserve_close_uuid",
- (json_int_t) pp.last_reserve_close_uuid,
- /* blocks of 5 */
- "start_pp_last_timestamp",
- TALER_ARL_json_from_time_abs (
- start_pp.last_timestamp),
- "end_pp_last_timestamp",
- TALER_ARL_json_from_time_abs (
- pp.last_timestamp),
- "account_progress",
- report_account_progress
- );
- GNUNET_break (NULL != report);
- TALER_ARL_done (report);
+ TALER_ARL_done (
+ GNUNET_JSON_PACK (
+ /* Tested in test-auditor.sh #11, #15, #20 */
+ GNUNET_JSON_pack_array_steal ("wire_out_amount_inconsistencies",
+ report_wire_out_inconsistencies),
+ TALER_JSON_pack_amount ("total_wire_out_delta_plus",
+ &total_bad_amount_out_plus),
+ /* Tested in test-auditor.sh #11, #15, #19 */
+ TALER_JSON_pack_amount ("total_wire_out_delta_minus",
+ &total_bad_amount_out_minus),
+ /* Tested in test-auditor.sh #2 */
+ GNUNET_JSON_pack_array_steal ("reserve_in_amount_inconsistencies",
+ report_reserve_in_inconsistencies),
+ /* Tested in test-auditor.sh #2 */
+ TALER_JSON_pack_amount ("total_wire_in_delta_plus",
+ &total_bad_amount_in_plus),
+ /* Tested in test-auditor.sh #3 */
+ TALER_JSON_pack_amount ("total_wire_in_delta_minus",
+ &total_bad_amount_in_minus),
+ /* Tested in test-auditor.sh #9 */
+ GNUNET_JSON_pack_array_steal ("misattribution_in_inconsistencies",
+ report_misattribution_in_inconsistencies),
+ /* Tested in test-auditor.sh #9 */
+ TALER_JSON_pack_amount ("total_misattribution_in",
+ &total_misattribution_in),
+ GNUNET_JSON_pack_array_steal ("row_inconsistencies",
+ report_row_inconsistencies),
+ /* Tested in test-auditor.sh #10/#17 */
+ GNUNET_JSON_pack_array_steal ("row_minor_inconsistencies",
+ report_row_minor_inconsistencies),
+ /* Tested in test-auditor.sh #19 */
+ TALER_JSON_pack_amount ("total_wire_format_amount",
+ &total_wire_format_amount),
+ /* Tested in test-auditor.sh #19 */
+ GNUNET_JSON_pack_array_steal ("wire_format_inconsistencies",
+ report_wire_format_inconsistencies),
+ TALER_JSON_pack_amount ("total_wire_in",
+ &total_wire_in),
+ TALER_JSON_pack_amount ("total_wire_out",
+ &total_wire_out),
+ TALER_JSON_pack_amount ("total_drained",
+ &TALER_ARL_USE_AB (total_drained)),
+ TALER_JSON_pack_amount ("final_balance",
+ &TALER_ARL_USE_AB (final_balance)),
+ /* Tested in test-auditor.sh #1 */
+ TALER_JSON_pack_amount ("total_amount_lag",
+ &total_amount_lag),
+ /* Tested in test-auditor.sh #1 */
+ GNUNET_JSON_pack_array_steal ("lag_details",
+ report_lags),
+ GNUNET_JSON_pack_array_steal ("lag_aml_details",
+ report_aml_lags),
+ GNUNET_JSON_pack_array_steal ("lag_kyc_details",
+ report_kyc_lags),
+ /* Tested in test-auditor.sh #22 */
+ TALER_JSON_pack_amount ("total_closure_amount_lag",
+ &total_closure_amount_lag),
+ /* Tested in test-auditor.sh #22 */
+ GNUNET_JSON_pack_array_steal ("reserve_lag_details",
+ report_closure_lags),
+ TALER_JSON_pack_time_abs_human ("wire_auditor_start_time",
+ start_time),
+ TALER_JSON_pack_time_abs_human ("wire_auditor_end_time",
+ GNUNET_TIME_absolute_get ()),
+ GNUNET_JSON_pack_uint64 ("start_pp_reserve_close_id",
+ 0 /* no longer supported */),
+ GNUNET_JSON_pack_uint64 ("end_pp_reserve_close_id",
+ TALER_ARL_USE_PP (wire_reserve_close_id)),
+ GNUNET_JSON_pack_uint64 ("start_pp_last_batch_deposit_id",
+ 0 /* no longer supported */),
+ GNUNET_JSON_pack_uint64 ("end_pp_last_batch_deposit_id",
+ TALER_ARL_USE_PP (wire_batch_deposit_id)),
+ GNUNET_JSON_pack_uint64 ("start_pp_last_aggregation_serial_id",
+ 0 /* no longer supported */),
+ GNUNET_JSON_pack_uint64 ("end_pp_last_aggregation_serial_id",
+ TALER_ARL_USE_PP (wire_aggregation_id)),
+ GNUNET_JSON_pack_array_steal ("account_progress",
+ report_account_progress)));
report_wire_out_inconsistencies = NULL;
report_reserve_in_inconsistencies = NULL;
report_row_inconsistencies = NULL;
report_row_minor_inconsistencies = NULL;
- report_missattribution_in_inconsistencies = NULL;
+ report_misattribution_in_inconsistencies = NULL;
report_lags = NULL;
+ report_kyc_lags = NULL;
+ report_aml_lags = NULL;
report_closure_lags = NULL;
report_account_progress = NULL;
report_wire_format_inconsistencies = NULL;
@@ -593,8 +653,10 @@ do_shutdown (void *cls)
GNUNET_CONTAINER_DLL_remove (wa_head,
wa_tail,
wa);
- TALER_BANK_auth_free (&wa->auth);
- GNUNET_free (wa->section_name);
+ GNUNET_free (wa->label_reserve_in_serial_id);
+ GNUNET_free (wa->label_wire_out_serial_id);
+ GNUNET_free (wa->label_wire_off_in);
+ GNUNET_free (wa->label_wire_off_out);
GNUNET_free (wa);
}
if (NULL != ctx)
@@ -607,6 +669,8 @@ do_shutdown (void *cls)
GNUNET_CURL_gnunet_rc_destroy (rc);
rc = NULL;
}
+ TALER_EXCHANGEDB_unload_accounts ();
+ TALER_ARL_cfg = NULL;
}
@@ -620,7 +684,7 @@ do_shutdown (void *cls)
* @param value the `struct ReserveClosure` to free
* @return #GNUNET_OK
*/
-static int
+static enum GNUNET_GenericReturnValue
check_pending_rc (void *cls,
const struct GNUNET_HashCode *key,
void *value)
@@ -629,22 +693,26 @@ check_pending_rc (void *cls,
(void) cls;
(void) key;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_closure_amount_lag,
- &total_closure_amount_lag,
- &rc->amount));
+ TALER_ARL_amount_add (&total_closure_amount_lag,
+ &total_closure_amount_lag,
+ &rc->amount);
if ( (0 != rc->amount.value) ||
(0 != rc->amount.fraction) )
- TALER_ARL_report (report_closure_lags,
- json_pack ("{s:I, s:o, s:o, s:o, s:s}",
- "row", (json_int_t) rc->rowid,
- "amount", TALER_JSON_from_amount (&rc->amount),
- "deadline", TALER_ARL_json_from_time_abs (
- rc->execution_date),
- "wtid", GNUNET_JSON_from_data_auto (&rc->wtid),
- "account", rc->receiver_account));
- pp.last_reserve_close_uuid
- = GNUNET_MIN (pp.last_reserve_close_uuid,
+ TALER_ARL_report (
+ report_closure_lags,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rc->rowid),
+ TALER_JSON_pack_amount ("amount",
+ &rc->amount),
+ TALER_JSON_pack_time_abs_human ("deadline",
+ rc->execution_date.abs_time),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &rc->wtid),
+ GNUNET_JSON_pack_string ("account",
+ rc->receiver_account)));
+ TALER_ARL_USE_PP (wire_reserve_close_id)
+ = GNUNET_MIN (TALER_ARL_USE_PP (wire_reserve_close_id),
rc->rowid);
return GNUNET_OK;
}
@@ -666,12 +734,12 @@ hash_rc (const char *receiver_account,
size_t slen = strlen (receiver_account);
char buf[sizeof (struct TALER_WireTransferIdentifierRawP) + slen];
- memcpy (buf,
- wtid,
- sizeof (*wtid));
- memcpy (&buf[sizeof (*wtid)],
- receiver_account,
- slen);
+ GNUNET_memcpy (buf,
+ wtid,
+ sizeof (*wtid));
+ GNUNET_memcpy (&buf[sizeof (*wtid)],
+ receiver_account,
+ slen);
GNUNET_CRYPTO_hash (buf,
sizeof (buf),
key);
@@ -687,6 +755,42 @@ hash_rc (const char *receiver_account,
static enum GNUNET_DB_QueryStatus
commit (enum GNUNET_DB_QueryStatus qs)
{
+ if (qs >= 0)
+ {
+ if (had_start_balance)
+ {
+ struct TALER_Amount sum;
+
+ TALER_ARL_amount_add (&sum,
+ &total_wire_in,
+ &start_balance);
+ TALER_ARL_amount_subtract (&TALER_ARL_USE_AB (final_balance),
+ &sum,
+ &total_wire_out);
+ qs = TALER_ARL_adb->update_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_AB (total_drained),
+ TALER_ARL_SET_AB (final_balance),
+ NULL);
+ }
+ else
+ {
+ TALER_ARL_amount_subtract (&TALER_ARL_USE_AB (final_balance),
+ &total_wire_in,
+ &total_wire_out);
+ qs = TALER_ARL_adb->insert_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_AB (total_drained),
+ TALER_ARL_SET_AB (final_balance),
+ NULL);
+ }
+ }
+ else
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (final_balance)));
+ }
if (0 > qs)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@@ -695,51 +799,53 @@ commit (enum GNUNET_DB_QueryStatus qs)
else
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Hard error, not recording progress\n");
- TALER_ARL_adb->rollback (TALER_ARL_adb->cls,
- TALER_ARL_asession);
- TALER_ARL_edb->rollback (TALER_ARL_edb->cls,
- TALER_ARL_esession);
+ TALER_ARL_adb->rollback (TALER_ARL_adb->cls);
+ TALER_ARL_edb->rollback (TALER_ARL_edb->cls);
return qs;
}
for (struct WireAccount *wa = wa_head;
NULL != wa;
wa = wa->next)
{
- GNUNET_assert (0 ==
- json_array_append_new (
- report_account_progress,
- json_pack (
- "{s:s, s:I, s:I, s:I, s:I}",
- "account",
- wa->section_name,
- "start_reserve_in",
- (json_int_t) wa->start_pp.last_reserve_in_serial_id,
- "end_reserve_in",
- (json_int_t) wa->pp.last_reserve_in_serial_id,
- "start_wire_out",
- (json_int_t) wa->start_pp.last_wire_out_serial_id,
- "end_wire_out",
- (json_int_t) wa->pp.last_wire_out_serial_id
- ))
- );
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ report_account_progress,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("account",
+ wa->ai->section_name),
+ GNUNET_JSON_pack_uint64 ("start_reserve_in",
+ wa->start_pp.last_reserve_in_serial_id),
+ GNUNET_JSON_pack_uint64 ("end_reserve_in",
+ wa->pp.last_reserve_in_serial_id),
+ GNUNET_JSON_pack_uint64 ("start_wire_out",
+ wa->start_pp.last_wire_out_serial_id),
+ GNUNET_JSON_pack_uint64 ("end_wire_out",
+ wa->pp.last_wire_out_serial_id))));
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == wa->qsx)
- qs = TALER_ARL_adb->update_wire_auditor_account_progress (
+ qs = TALER_ARL_adb->update_auditor_progress (
TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- wa->section_name,
- &wa->pp,
- wa->in_wire_off,
- wa->out_wire_off);
+ wa->label_reserve_in_serial_id,
+ wa->pp.last_reserve_in_serial_id,
+ wa->label_wire_out_serial_id,
+ wa->pp.last_wire_out_serial_id,
+ wa->label_wire_off_in,
+ wa->wire_off_in,
+ wa->label_wire_off_out,
+ wa->wire_off_out,
+ NULL);
else
- qs = TALER_ARL_adb->insert_wire_auditor_account_progress (
+ qs = TALER_ARL_adb->insert_auditor_progress (
TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- wa->section_name,
- &wa->pp,
- wa->in_wire_off,
- wa->out_wire_off);
+ wa->label_reserve_in_serial_id,
+ wa->pp.last_reserve_in_serial_id,
+ wa->label_wire_out_serial_id,
+ wa->pp.last_wire_out_serial_id,
+ wa->label_wire_off_in,
+ wa->wire_off_in,
+ wa->label_wire_off_out,
+ wa->wire_off_out,
+ NULL);
if (0 >= qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -752,15 +858,19 @@ commit (enum GNUNET_DB_QueryStatus qs)
&check_pending_rc,
NULL);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsx_gwap)
- qs = TALER_ARL_adb->update_wire_auditor_progress (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &pp);
+ qs = TALER_ARL_adb->update_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_PP (wire_reserve_close_id),
+ TALER_ARL_SET_PP (wire_batch_deposit_id),
+ TALER_ARL_SET_PP (wire_aggregation_id),
+ NULL);
else
- qs = TALER_ARL_adb->insert_wire_auditor_progress (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &pp);
+ qs = TALER_ARL_adb->insert_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_PP (wire_reserve_close_id),
+ TALER_ARL_SET_PP (wire_batch_deposit_id),
+ TALER_ARL_SET_PP (wire_aggregation_id),
+ NULL);
if (0 >= qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -769,25 +879,23 @@ commit (enum GNUNET_DB_QueryStatus qs)
return qs;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Concluded audit step at %s\n",
- GNUNET_STRINGS_absolute_time_to_string (pp.last_timestamp));
+ "Concluded audit step at %llu/%llu\n",
+ (unsigned long long) TALER_ARL_USE_PP (wire_aggregation_id),
+ (unsigned long long) TALER_ARL_USE_PP (wire_batch_deposit_id));
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
- qs = TALER_ARL_edb->commit (TALER_ARL_edb->cls,
- TALER_ARL_esession);
+ qs = TALER_ARL_edb->commit (TALER_ARL_edb->cls);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Exchange DB commit failed, rolling back transaction\n");
- TALER_ARL_adb->rollback (TALER_ARL_adb->cls,
- TALER_ARL_asession);
+ TALER_ARL_adb->rollback (TALER_ARL_adb->cls);
}
else
{
- qs = TALER_ARL_adb->commit (TALER_ARL_adb->cls,
- TALER_ARL_asession);
+ qs = TALER_ARL_adb->commit (TALER_ARL_adb->cls);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
@@ -800,10 +908,8 @@ commit (enum GNUNET_DB_QueryStatus qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Processing failed, rolling back transaction\n");
- TALER_ARL_adb->rollback (TALER_ARL_adb->cls,
- TALER_ARL_asession);
- TALER_ARL_edb->rollback (TALER_ARL_edb->cls,
- TALER_ARL_esession);
+ TALER_ARL_adb->rollback (TALER_ARL_adb->cls);
+ TALER_ARL_edb->rollback (TALER_ARL_edb->cls);
}
return qs;
}
@@ -812,48 +918,341 @@ commit (enum GNUNET_DB_QueryStatus qs)
/* ***************************** Analyze required transfers ************************ */
/**
+ * Closure for import_wire_missing_cb().
+ */
+struct ImportMissingWireContext
+{
+ /**
+ * Set to maximum row ID encountered.
+ */
+ uint64_t max_batch_deposit_uuid;
+
+ /**
+ * Set to database errors in callback.
+ */
+ enum GNUNET_DB_QueryStatus err;
+};
+
+
+/**
+ * Function called on deposits that need to be checked for their
+ * wire transfer.
+ *
+ * @param cls closure, points to a `struct ImportMissingWireContext`
+ * @param batch_deposit_serial_id serial of the entry in the batch deposits table
+ * @param total_amount value of the missing deposits, including fee
+ * @param wire_target_h_payto where should the funds be wired
+ * @param deadline what was the earliest requested wire transfer deadline
+ */
+static void
+import_wire_missing_cb (void *cls,
+ uint64_t batch_deposit_serial_id,
+ const struct TALER_Amount *total_amount,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ struct GNUNET_TIME_Timestamp deadline)
+{
+ struct ImportMissingWireContext *wc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (wc->err < 0)
+ return; /* already failed */
+ GNUNET_assert (batch_deposit_serial_id > wc->max_batch_deposit_uuid);
+ wc->max_batch_deposit_uuid = batch_deposit_serial_id;
+ qs = TALER_ARL_adb->insert_pending_deposit (
+ TALER_ARL_adb->cls,
+ batch_deposit_serial_id,
+ wire_target_h_payto,
+ total_amount,
+ deadline);
+ if (qs < 0)
+ wc->err = qs;
+}
+
+
+/**
+ * Information about a delayed wire transfer and the possible
+ * reasons for the delay.
+ */
+struct ReasonDetail
+{
+ /**
+ * Total amount that should have been transferred.
+ */
+ struct TALER_Amount total_amount;
+
+ /**
+ * Earliest deadline for an expected transfer to the account.
+ */
+ struct GNUNET_TIME_Timestamp deadline;
+
+ /**
+ * Target account, NULL if even that is not known (due to
+ * exchange lacking required entry in wire_targets table).
+ */
+ char *payto_uri;
+
+ /**
+ * Reasons due to pending KYC requests.
+ */
+ char *kyc_pending;
+
+ /**
+ * AML decision state for the target account.
+ */
+ enum TALER_AmlDecisionState status;
+
+ /**
+ * Current AML threshold for the account, may be an invalid account if the
+ * default threshold applies.
+ */
+ struct TALER_Amount aml_limit;
+};
+
+/**
+ * Closure for report_wire_missing_cb().
+ */
+struct ReportMissingWireContext
+{
+ /**
+ * Map from wire_target_h_payto to `struct ReasonDetail`.
+ */
+ struct GNUNET_CONTAINER_MultiShortmap *map;
+
+ /**
+ * Set to database errors in callback.
+ */
+ enum GNUNET_DB_QueryStatus err;
+};
+
+
+/**
+ * Closure for #clear_finished_transfer_cb().
+ */
+struct AggregationContext
+{
+ /**
+ * Set to maximum row ID encountered.
+ */
+ uint64_t max_aggregation_serial;
+
+ /**
+ * Set to database errors in callback.
+ */
+ enum GNUNET_DB_QueryStatus err;
+};
+
+
+/**
+ * Free memory allocated in @a value.
+ *
+ * @param cls unused
+ * @param key unused
+ * @param value must be a `struct ReasonDetail`
+ * @return #GNUNET_YES if we should continue to
+ * iterate,
+ * #GNUNET_NO if not.
+ */
+static enum GNUNET_GenericReturnValue
+free_report_entry (void *cls,
+ const struct GNUNET_ShortHashCode *key,
+ void *value)
+{
+ struct ReasonDetail *rd = value;
+
+ GNUNET_free (rd->kyc_pending);
+ GNUNET_free (rd->payto_uri);
+ GNUNET_free (rd);
+ return GNUNET_YES;
+}
+
+
+/**
+ * We had an entry in our map of wire transfers that
+ * should have been performed. Generate report.
+ *
+ * @param cls unused
+ * @param key unused
+ * @param value must be a `struct ReasonDetail`
+ * @return #GNUNET_YES if we should continue to
+ * iterate,
+ * #GNUNET_NO if not.
+ */
+static enum GNUNET_GenericReturnValue
+generate_report (void *cls,
+ const struct GNUNET_ShortHashCode *key,
+ void *value)
+{
+ struct ReasonDetail *rd = value;
+
+ /* For now, we simplify and only check that the
+ amount was tiny */
+ if (0 > TALER_amount_cmp (&rd->total_amount,
+ &tiny_amount))
+ return free_report_entry (cls,
+ key,
+ value); /* acceptable, amount was tiny */
+ // TODO: maybe split total_amount_lag up by category below?
+ TALER_ARL_amount_add (&total_amount_lag,
+ &total_amount_lag,
+ &rd->total_amount);
+ if (NULL != rd->kyc_pending)
+ {
+ json_t *rep;
+
+ rep = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("total_amount",
+ &rd->total_amount),
+ TALER_JSON_pack_time_abs_human ("deadline",
+ rd->deadline.abs_time),
+ GNUNET_JSON_pack_string ("kyc_pending",
+ rd->kyc_pending),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("account",
+ rd->payto_uri)));
+ TALER_ARL_report (report_kyc_lags,
+ rep);
+ }
+ else if (TALER_AML_NORMAL != rd->status)
+ {
+ const char *sstatus = "<undefined>";
+ json_t *rep;
+
+ switch (rd->status)
+ {
+ case TALER_AML_NORMAL:
+ GNUNET_assert (0);
+ break;
+ case TALER_AML_PENDING:
+ sstatus = "pending";
+ break;
+ case TALER_AML_FROZEN:
+ sstatus = "frozen";
+ break;
+ }
+ rep = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("total_amount",
+ &rd->total_amount),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("aml_limit",
+ TALER_amount_is_valid (&rd->aml_limit)
+ ? &rd->aml_limit
+ : NULL)),
+ TALER_JSON_pack_time_abs_human ("deadline",
+ rd->deadline.abs_time),
+ GNUNET_JSON_pack_string ("aml_status",
+ sstatus),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("account",
+ rd->payto_uri)));
+ TALER_ARL_report (report_aml_lags,
+ rep);
+ }
+ else
+ {
+ json_t *rep;
+
+ rep = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("total_amount",
+ &rd->total_amount),
+ TALER_JSON_pack_time_abs_human ("deadline",
+ rd->deadline.abs_time),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("account",
+ rd->payto_uri)));
+ TALER_ARL_report (report_lags,
+ rep);
+ }
+
+ return free_report_entry (cls,
+ key,
+ value);
+}
+
+
+/**
* Function called on deposits that are past their due date
* and have not yet seen a wire transfer.
*
+ * @param cls closure, points to a `struct ReportMissingWireContext`
+ * @param batch_deposit_serial_id row in the database for which the wire transfer is missing
+ * @param total_amount value of the missing deposits, including fee
+ * @param wire_target_h_payto hash of payto-URI where the funds should have been wired
+ * @param deadline what was the earliest requested wire transfer deadline
+ */
+static void
+report_wire_missing_cb (void *cls,
+ uint64_t batch_deposit_serial_id,
+ const struct TALER_Amount *total_amount,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ struct GNUNET_TIME_Timestamp deadline)
+{
+ struct ReportMissingWireContext *rc = cls;
+ struct ReasonDetail *rd;
+
+ rd = GNUNET_CONTAINER_multishortmap_get (rc->map,
+ &wire_target_h_payto->hash);
+ if (NULL == rd)
+ {
+ rd = GNUNET_new (struct ReasonDetail);
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CONTAINER_multishortmap_put (
+ rc->map,
+ &wire_target_h_payto->hash,
+ rd,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ rc->err = TALER_ARL_edb->select_justification_for_missing_wire (
+ TALER_ARL_edb->cls,
+ wire_target_h_payto,
+ &rd->payto_uri,
+ &rd->kyc_pending,
+ &rd->status,
+ &rd->aml_limit);
+ rd->total_amount = *total_amount;
+ rd->deadline = deadline;
+ }
+ else
+ {
+ TALER_ARL_amount_add (&rd->total_amount,
+ &rd->total_amount,
+ total_amount);
+ rd->deadline = GNUNET_TIME_timestamp_min (rd->deadline,
+ deadline);
+ }
+}
+
+
+/**
+ * Function called on aggregations that were done for
+ * a (batch) deposit.
+ *
* @param cls closure
- * @param rowid deposit table row of the coin's deposit
- * @param coin_pub public key of the coin
- * @param amount value of the deposit, including fee
- * @param wire where should the funds be wired
- * @param deadline what was the requested wire transfer deadline
- * @param tiny did the exchange defer this transfer because it is too small?
- * @param done did the exchange claim that it made a transfer?
+ * @param tracking_serial_id where in the table are we
+ * @param batch_deposit_serial_id which batch deposit was aggregated
*/
static void
-wire_missing_cb (void *cls,
- uint64_t rowid,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount,
- const json_t *wire,
- struct GNUNET_TIME_Absolute deadline,
- /* bool? */ int tiny,
- /* bool? */ int done)
+clear_finished_transfer_cb (
+ void *cls,
+ uint64_t tracking_serial_id,
+ uint64_t batch_deposit_serial_id)
{
- (void) cls;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_amount_lag,
- &total_amount_lag,
- amount));
- if ( (GNUNET_YES == tiny) &&
- (0 > TALER_amount_cmp (amount,
- &tiny_amount)) )
- return; /* acceptable, amount was tiny */
- TALER_ARL_report (report_lags,
- json_pack ("{s:I, s:o, s:o, s:s, s:o, s:O}",
- "row", (json_int_t) rowid,
- "amount", TALER_JSON_from_amount (amount),
- "deadline", TALER_ARL_json_from_time_abs (
- deadline),
- "claimed_done", (done) ? "yes" : "no",
- "coin_pub", GNUNET_JSON_from_data_auto (
- coin_pub),
- "account", wire));
+ struct AggregationContext *ac = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ if (0 > ac->err)
+ return; /* already failed */
+ GNUNET_assert (ac->max_aggregation_serial < tracking_serial_id);
+ ac->max_aggregation_serial = tracking_serial_id;
+ qs = TALER_ARL_adb->delete_pending_deposit (
+ TALER_ARL_adb->cls,
+ batch_deposit_serial_id);
+ if (0 == qs)
+ {
+ /* Aggregated something twice or other error, report! */
+ GNUNET_break (0);
+ // FIXME: report more nicely!
+ }
+ if (0 > qs)
+ ac->err = qs;
}
@@ -864,32 +1263,78 @@ wire_missing_cb (void *cls,
static void
check_for_required_transfers (void)
{
- struct GNUNET_TIME_Absolute next_timestamp;
+ struct ImportMissingWireContext wc = {
+ .max_batch_deposit_uuid = TALER_ARL_USE_PP (wire_batch_deposit_id),
+ .err = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+ };
+ struct GNUNET_TIME_Absolute deadline;
enum GNUNET_DB_QueryStatus qs;
+ struct ReportMissingWireContext rc = {
+ .err = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+ };
+ struct AggregationContext ac = {
+ .max_aggregation_serial = TALER_ARL_USE_PP (wire_aggregation_id),
+ .err = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+ };
- next_timestamp = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&next_timestamp);
+ qs = TALER_ARL_edb->select_batch_deposits_missing_wire (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (wire_batch_deposit_id),
+ &import_wire_missing_cb,
+ &wc);
+ if ( (0 > qs) || (0 > wc.err) )
+ {
+ GNUNET_break (0);
+ GNUNET_break ( (GNUNET_DB_STATUS_SOFT_ERROR == qs) ||
+ (GNUNET_DB_STATUS_SOFT_ERROR == wc.err) );
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ TALER_ARL_USE_PP (wire_batch_deposit_id) = wc.max_batch_deposit_uuid;
+ qs = TALER_ARL_edb->select_aggregations_above_serial (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (wire_aggregation_id),
+ &clear_finished_transfer_cb,
+ &ac);
+ if ( (0 > qs) || (0 > ac.err) )
+ {
+ GNUNET_break (0);
+ GNUNET_break ( (GNUNET_DB_STATUS_SOFT_ERROR == qs) ||
+ (GNUNET_DB_STATUS_SOFT_ERROR == ac.err) );
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ TALER_ARL_USE_PP (wire_aggregation_id) = ac.max_aggregation_serial;
/* Subtract #GRACE_PERIOD, so we can be a bit behind in processing
without immediately raising undue concern */
- next_timestamp = GNUNET_TIME_absolute_subtract (next_timestamp,
- GRACE_PERIOD);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Analyzing exchange's unfinished deposits (deadline: %s)\n",
- GNUNET_STRINGS_absolute_time_to_string (next_timestamp));
- qs = TALER_ARL_edb->select_deposits_missing_wire (TALER_ARL_edb->cls,
- TALER_ARL_esession,
- pp.last_timestamp,
- next_timestamp,
- &wire_missing_cb,
- &next_timestamp);
- if (0 > qs)
+ deadline = GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (),
+ GRACE_PERIOD);
+ rc.map = GNUNET_CONTAINER_multishortmap_create (1024,
+ GNUNET_NO);
+ qs = TALER_ARL_adb->select_pending_deposits (
+ TALER_ARL_adb->cls,
+ deadline,
+ &report_wire_missing_cb,
+ &rc);
+ if ( (0 > qs) || (0 > rc.err) )
{
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- global_ret = 1;
+ GNUNET_break (0);
+ GNUNET_break ( (GNUNET_DB_STATUS_SOFT_ERROR == qs) ||
+ (GNUNET_DB_STATUS_SOFT_ERROR == rc.err) );
+ GNUNET_CONTAINER_multishortmap_iterate (rc.map,
+ &free_report_entry,
+ NULL);
+ GNUNET_CONTAINER_multishortmap_destroy (rc.map);
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
- pp.last_timestamp = next_timestamp;
+ GNUNET_CONTAINER_multishortmap_iterate (rc.map,
+ &generate_report,
+ NULL);
+ GNUNET_CONTAINER_multishortmap_destroy (rc.map);
/* conclude with success */
commit (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
GNUNET_SCHEDULER_shutdown ();
@@ -922,30 +1367,35 @@ conclude_wire_out (void)
static void
check_time_difference (const char *table,
uint64_t rowid,
- struct GNUNET_TIME_Absolute want,
- struct GNUNET_TIME_Absolute have)
+ struct GNUNET_TIME_Timestamp want,
+ struct GNUNET_TIME_Timestamp have)
{
struct GNUNET_TIME_Relative delta;
char *details;
- if (have.abs_value_us > want.abs_value_us)
- delta = GNUNET_TIME_absolute_get_difference (want,
- have);
+ if (GNUNET_TIME_timestamp_cmp (have, >, want))
+ delta = GNUNET_TIME_absolute_get_difference (want.abs_time,
+ have.abs_time);
else
- delta = GNUNET_TIME_absolute_get_difference (have,
- want);
- if (delta.rel_value_us <= TIME_TOLERANCE.rel_value_us)
+ delta = GNUNET_TIME_absolute_get_difference (have.abs_time,
+ want.abs_time);
+ if (GNUNET_TIME_relative_cmp (delta,
+ <=,
+ TIME_TOLERANCE))
return;
GNUNET_asprintf (&details,
"execution date mismatch (%s)",
- GNUNET_STRINGS_relative_time_to_string (delta,
- GNUNET_YES));
+ GNUNET_TIME_relative2s (delta,
+ true));
TALER_ARL_report (report_row_minor_inconsistencies,
- json_pack ("{s:s, s:I, s:s}",
- "table", table,
- "row", (json_int_t) rowid,
- "diagnostic", details));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ table),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ GNUNET_JSON_pack_string ("diagnostic",
+ details)));
GNUNET_free (details);
}
@@ -958,16 +1408,16 @@ check_time_difference (const char *table,
* @param rowid unique serial ID for the refresh session in our DB
* @param date timestamp of the transfer (roughly)
* @param wtid wire transfer subject
- * @param wire wire transfer details of the receiver
+ * @param payto_uri wire transfer details of the receiver
* @param amount amount that was wired
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
wire_out_cb (void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute date,
+ struct GNUNET_TIME_Timestamp date,
const struct TALER_WireTransferIdentifierRawP *wtid,
- const json_t *wire,
+ const char *payto_uri,
const struct TALER_Amount *amount)
{
struct WireAccount *wa = cls;
@@ -976,9 +1426,12 @@ wire_out_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Exchange wire OUT at %s of %s with WTID %s\n",
- GNUNET_STRINGS_absolute_time_to_string (date),
+ GNUNET_TIME_timestamp2s (date),
TALER_amount2s (amount),
TALER_B2S (wtid));
+ TALER_ARL_amount_add (&total_wire_out,
+ &total_wire_out,
+ amount);
GNUNET_CRYPTO_hash (wtid,
sizeof (struct TALER_WireTransferIdentifierRawP),
&key);
@@ -988,115 +1441,129 @@ wire_out_cb (void *cls,
{
/* Wire transfer was not made (yet) at all (but would have been
justified), so the entire amount is missing / still to be done.
- This is moderately harmless, it might just be that the aggreator
+ This is moderately harmless, it might just be that the aggregator
has not yet fully caught up with the transfers it should do. */
- TALER_ARL_report (report_wire_out_inconsistencies,
- json_pack ("{s:I, s:o, s:o, s:o, s:o, s:s, s:s}",
- "row", (json_int_t) rowid,
- "amount_wired", TALER_JSON_from_amount (&zero),
- "amount_justified", TALER_JSON_from_amount (
- amount),
- "wtid", GNUNET_JSON_from_data_auto (wtid),
- "timestamp", TALER_ARL_json_from_time_abs (
- date),
- "diagnostic", "wire transfer not made (yet?)",
- "account_section", wa->section_name));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_amount_out_minus,
- &total_bad_amount_out_minus,
- amount));
+ TALER_ARL_report (
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("amount_wired",
+ &zero),
+ TALER_JSON_pack_amount ("amount_justified",
+ amount),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ date.abs_time),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "wire transfer not made (yet?)"),
+ GNUNET_JSON_pack_string ("account_section",
+ wa->ai->section_name)));
+ TALER_ARL_amount_add (&total_bad_amount_out_minus,
+ &total_bad_amount_out_minus,
+ amount);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
+ if (0 != strcasecmp (payto_uri,
+ roi->details.credit_account_uri))
{
- char *payto_uri;
-
- payto_uri = TALER_JSON_wire_to_payto (wire);
- if (0 != strcasecmp (payto_uri,
- roi->details.credit_account_url))
- {
- /* Destination bank account is wrong in actual wire transfer, so
- we should count the wire transfer as entirely spurious, and
- additionally consider the justified wire transfer as missing. */
- TALER_ARL_report (report_wire_out_inconsistencies,
- json_pack ("{s:I, s:o, s:o, s:o, s:o, s:s, s:s}",
- "row", (json_int_t) rowid,
- "amount_wired", TALER_JSON_from_amount (
- &roi->details.amount),
- "amount_justified", TALER_JSON_from_amount (
- &zero),
- "wtid", GNUNET_JSON_from_data_auto (wtid),
- "timestamp", TALER_ARL_json_from_time_abs (
- date),
- "diagnostic", "recevier account mismatch",
- "account_section", wa->section_name));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_amount_out_plus,
- &total_bad_amount_out_plus,
- &roi->details.amount));
- TALER_ARL_report (report_wire_out_inconsistencies,
- json_pack ("{s:I, s:o, s:o, s:o, s:o, s:s, s:s}",
- "row", (json_int_t) rowid,
- "amount_wired", TALER_JSON_from_amount (
- &zero),
- "amount_justified", TALER_JSON_from_amount (
- amount),
- "wtid", GNUNET_JSON_from_data_auto (wtid),
- "timestamp", TALER_ARL_json_from_time_abs (
- date),
- "diagnostic", "receiver account mismatch",
- "account_section", wa->section_name));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_amount_out_minus,
- &total_bad_amount_out_minus,
- amount));
- GNUNET_free (payto_uri);
- goto cleanup;
- }
- GNUNET_free (payto_uri);
+ /* Destination bank account is wrong in actual wire transfer, so
+ we should count the wire transfer as entirely spurious, and
+ additionally consider the justified wire transfer as missing. */
+ TALER_ARL_report (
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("amount_wired",
+ &roi->details.amount),
+ TALER_JSON_pack_amount ("amount_justified",
+ &zero),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ date.abs_time),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "receiver account mismatch"),
+ GNUNET_JSON_pack_string ("target",
+ payto_uri),
+ GNUNET_JSON_pack_string ("account_section",
+ wa->ai->section_name)));
+ TALER_ARL_amount_add (&total_bad_amount_out_plus,
+ &total_bad_amount_out_plus,
+ &roi->details.amount);
+ TALER_ARL_report (
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("amount_wired",
+ &zero),
+ TALER_JSON_pack_amount ("amount_justified",
+ amount),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ date.abs_time),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "receiver account mismatch"),
+ GNUNET_JSON_pack_string ("target",
+ roi->details.
+ credit_account_uri),
+ GNUNET_JSON_pack_string ("account_section",
+ wa->ai->section_name)));
+ TALER_ARL_amount_add (&total_bad_amount_out_minus,
+ &total_bad_amount_out_minus,
+ amount);
+ goto cleanup;
}
if (0 != TALER_amount_cmp (&roi->details.amount,
amount))
{
- TALER_ARL_report (report_wire_out_inconsistencies,
- json_pack ("{s:I, s:o, s:o, s:o, s:o, s:s, s:s}",
- "row", (json_int_t) rowid,
- "amount_justified", TALER_JSON_from_amount (
- amount),
- "amount_wired", TALER_JSON_from_amount (
- &roi->details.amount),
- "wtid", GNUNET_JSON_from_data_auto (wtid),
- "timestamp", TALER_ARL_json_from_time_abs (
- date),
- "diagnostic", "wire amount does not match",
- "account_section", wa->section_name));
+ TALER_ARL_report (
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("amount_justified",
+ amount),
+ TALER_JSON_pack_amount ("amount_wired",
+ &roi->details.amount),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ date.abs_time),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "wire amount does not match"),
+ GNUNET_JSON_pack_string ("account_section",
+ wa->ai->section_name)));
if (0 < TALER_amount_cmp (amount,
&roi->details.amount))
{
/* amount > roi->details.amount: wire transfer was smaller than it should have been */
struct TALER_Amount delta;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- amount,
- &roi->details.amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_amount_out_minus,
- &total_bad_amount_out_minus,
- &delta));
+ TALER_ARL_amount_subtract (&delta,
+ amount,
+ &roi->details.amount);
+ TALER_ARL_amount_add (&total_bad_amount_out_minus,
+ &total_bad_amount_out_minus,
+ &delta);
}
else
{
/* roi->details.amount < amount: wire transfer was larger than it should have been */
struct TALER_Amount delta;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- &roi->details.amount,
- amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_amount_out_plus,
- &total_bad_amount_out_plus,
- &delta));
+ TALER_ARL_amount_subtract (&delta,
+ &roi->details.amount,
+ amount);
+ TALER_ARL_amount_add (&total_bad_amount_out_plus,
+ &total_bad_amount_out_plus,
+ &delta);
}
goto cleanup;
}
@@ -1111,6 +1578,8 @@ cleanup:
&key,
roi));
wa->pp.last_wire_out_serial_id = rowid + 1;
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
@@ -1127,9 +1596,9 @@ struct CheckMatchContext
const struct ReserveOutInfo *roi;
/**
- * Set to #GNUNET_YES if we found a match.
+ * Set to true if we found a match.
*/
- int found;
+ bool found;
};
@@ -1140,7 +1609,7 @@ struct CheckMatchContext
* @param key key of @a value in #reserve_closures
* @param value a `struct ReserveClosure`
*/
-static int
+static enum GNUNET_GenericReturnValue
check_rc_matches (void *cls,
const struct GNUNET_HashCode *key,
void *value)
@@ -1151,7 +1620,7 @@ check_rc_matches (void *cls,
if ( (0 == GNUNET_memcmp (&ctx->roi->details.wtid,
&rc->wtid)) &&
(0 == strcasecmp (rc->receiver_account,
- ctx->roi->details.credit_account_url)) &&
+ ctx->roi->details.credit_account_uri)) &&
(0 == TALER_amount_cmp (&rc->amount,
&ctx->roi->details.amount)) )
{
@@ -1159,7 +1628,7 @@ check_rc_matches (void *cls,
rc->rowid,
rc->execution_date,
ctx->roi->details.execution_date);
- ctx->found = GNUNET_YES;
+ ctx->found = true;
free_rc (NULL,
key,
rc);
@@ -1170,16 +1639,17 @@ check_rc_matches (void *cls,
/**
- * Check whether the given transfer was justified by a reserve closure. If
- * not, complain that we failed to match an entry from #out_map. This means a
- * wire transfer was made without proper justification.
+ * Check whether the given transfer was justified by a reserve closure or
+ * profit drain. If not, complain that we failed to match an entry from
+ * #out_map. This means a wire transfer was made without proper
+ * justification.
*
* @param cls a `struct WireAccount`
* @param key unused key
* @param value the `struct ReserveOutInfo` to report
- * @return #GNUNET_OK
+ * @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
complain_out_not_found (void *cls,
const struct GNUNET_HashCode *key,
void *value)
@@ -1189,38 +1659,164 @@ complain_out_not_found (void *cls,
struct GNUNET_HashCode rkey;
struct CheckMatchContext ctx = {
.roi = roi,
- .found = GNUNET_NO
+ .found = false
};
(void) key;
- hash_rc (roi->details.credit_account_url,
+ hash_rc (roi->details.credit_account_uri,
&roi->details.wtid,
&rkey);
GNUNET_CONTAINER_multihashmap_get_multiple (reserve_closures,
&rkey,
&check_rc_matches,
&ctx);
- if (GNUNET_YES == ctx.found)
+ if (ctx.found)
return GNUNET_OK;
- TALER_ARL_report (report_wire_out_inconsistencies,
- json_pack ("{s:I, s:o, s:o, s:o, s:o, s:s, s:s}",
- "row", (json_int_t) 0,
- "amount_wired", TALER_JSON_from_amount (
- &roi->details.amount),
- "amount_justified", TALER_JSON_from_amount (
- &zero),
- "wtid", GNUNET_JSON_from_data_auto (
- &roi->details.wtid),
- "timestamp", TALER_ARL_json_from_time_abs (
- roi->details.execution_date),
- "account_section",
- wa->section_name,
- "diagnostic",
- "justification for wire transfer not found"));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_amount_out_plus,
- &total_bad_amount_out_plus,
- &roi->details.amount));
+ /* check for profit drain */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t serial;
+ char *account_section;
+ char *payto_uri;
+ struct GNUNET_TIME_Timestamp request_timestamp;
+ struct TALER_Amount amount;
+ struct TALER_MasterSignatureP master_sig;
+
+ qs = TALER_ARL_edb->get_drain_profit (TALER_ARL_edb->cls,
+ &roi->details.wtid,
+ &serial,
+ &account_section,
+ &payto_uri,
+ &request_timestamp,
+ &amount,
+ &master_sig);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* should fail on commit later ... */
+ GNUNET_break (0);
+ return GNUNET_NO;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* not a profit drain */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Profit drain of %s to %s found!\n",
+ TALER_amount2s (&amount),
+ payto_uri);
+ if (GNUNET_OK !=
+ TALER_exchange_offline_profit_drain_verify (
+ &roi->details.wtid,
+ request_timestamp,
+ &amount,
+ account_section,
+ payto_uri,
+ &TALER_ARL_master_pub,
+ &master_sig))
+ {
+ GNUNET_break (0);
+ TALER_ARL_report (report_row_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ "profit_drains"),
+ GNUNET_JSON_pack_uint64 ("row",
+ serial),
+ GNUNET_JSON_pack_data_auto ("id",
+ &roi->details.wtid),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "invalid signature")));
+ TALER_ARL_amount_add (&total_bad_amount_out_plus,
+ &total_bad_amount_out_plus,
+ &amount);
+ }
+ else if (0 !=
+ strcasecmp (payto_uri,
+ roi->details.credit_account_uri))
+ {
+ TALER_ARL_report (
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ serial),
+ TALER_JSON_pack_amount ("amount_wired",
+ &roi->details.amount),
+ TALER_JSON_pack_amount ("amount_wired",
+ &amount),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &roi->details.wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ roi->details.execution_date.abs_time),
+ GNUNET_JSON_pack_string ("account",
+ wa->ai->section_name),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "wrong target account")));
+ TALER_ARL_amount_add (&total_bad_amount_out_plus,
+ &total_bad_amount_out_plus,
+ &amount);
+ }
+ else if (0 !=
+ TALER_amount_cmp (&amount,
+ &roi->details.amount))
+ {
+ TALER_ARL_report (
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ serial),
+ TALER_JSON_pack_amount ("amount_justified",
+ &roi->details.amount),
+ TALER_JSON_pack_amount ("amount_wired",
+ &amount),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &roi->details.wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ roi->details.execution_date.abs_time),
+ GNUNET_JSON_pack_string ("account",
+ wa->ai->section_name),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "profit drain amount incorrect")));
+ TALER_ARL_amount_add (&total_bad_amount_out_minus,
+ &total_bad_amount_out_minus,
+ &roi->details.amount);
+ TALER_ARL_amount_add (&total_bad_amount_out_plus,
+ &total_bad_amount_out_plus,
+ &amount);
+ }
+ GNUNET_free (account_section);
+ GNUNET_free (payto_uri);
+ /* profit drain was correct */
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_drained),
+ &TALER_ARL_USE_AB (total_drained),
+ &amount);
+ return GNUNET_OK;
+ }
+ }
+
+ TALER_ARL_report (
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ 0),
+ TALER_JSON_pack_amount ("amount_wired",
+ &roi->details.amount),
+ TALER_JSON_pack_amount ("amount_justified",
+ &zero),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &roi->details.wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ roi->details.execution_date.abs_time),
+ GNUNET_JSON_pack_string ("account_section",
+ wa->ai->section_name),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "justification for wire transfer not found")));
+ TALER_ARL_amount_add (&total_bad_amount_out_plus,
+ &total_bad_amount_out_plus,
+ &roi->details.amount);
return GNUNET_OK;
}
@@ -1247,20 +1843,20 @@ check_exchange_wire_out (struct WireAccount *wa)
{
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL == wa->dhh);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Analyzing exchange's wire OUT table for account `%s'\n",
- wa->section_name);
+ wa->ai->section_name);
qs = TALER_ARL_edb->select_wire_out_above_serial_id_by_account (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- wa->section_name,
+ wa->ai->section_name,
wa->pp.last_wire_out_serial_id,
&wire_out_cb,
wa);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- global_ret = 1;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -1281,89 +1877,92 @@ check_exchange_wire_out (struct WireAccount *wa)
* transactions).
*
* @param cls `struct WireAccount` with current wire account to process
- * @param http_status_code http status of the request
- * @param ec error code in case something went wrong
- * @param row_off identification of the position at which we are querying
- * @param details details about the wire transfer
- * @param json original response in JSON format
- * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
- */
-static int
+ * @param dhr HTTP response details
+ */
+static void
history_debit_cb (void *cls,
- unsigned int http_status_code,
- enum TALER_ErrorCode ec,
- uint64_t row_off,
- const struct TALER_BANK_DebitDetails *details,
- const json_t *json)
+ const struct TALER_BANK_DebitHistoryResponse *dhr)
{
struct WireAccount *wa = cls;
struct ReserveOutInfo *roi;
size_t slen;
- (void) json;
- if (NULL == details)
+ wa->dhh = NULL;
+ switch (dhr->http_status)
{
- wa->dhh = NULL;
- if (TALER_EC_NONE != ec)
+ case MHD_HTTP_OK:
+ for (unsigned int i = 0; i<dhr->details.ok.details_length; i++)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Error fetching debit history of account %s: %u/%u!\n",
- wa->section_name,
- http_status_code,
- (unsigned int) ec);
- commit (GNUNET_DB_STATUS_HARD_ERROR);
- global_ret = 1;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_SYSERR;
+ const struct TALER_BANK_DebitDetails *dd
+ = &dhr->details.ok.details[i];
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Analyzing bank DEBIT at %s of %s with WTID %s\n",
+ GNUNET_TIME_timestamp2s (dd->execution_date),
+ TALER_amount2s (&dd->amount),
+ TALER_B2S (&dd->wtid));
+ /* Update offset */
+ wa->wire_off_out = dd->serial_id;
+ slen = strlen (dd->credit_account_uri) + 1;
+ roi = GNUNET_malloc (sizeof (struct ReserveOutInfo)
+ + slen);
+ GNUNET_CRYPTO_hash (&dd->wtid,
+ sizeof (dd->wtid),
+ &roi->subject_hash);
+ roi->details.amount = dd->amount;
+ roi->details.execution_date = dd->execution_date;
+ roi->details.wtid = dd->wtid;
+ roi->details.credit_account_uri = (const char *) &roi[1];
+ GNUNET_memcpy (&roi[1],
+ dd->credit_account_uri,
+ slen);
+ if (GNUNET_OK !=
+ GNUNET_CONTAINER_multihashmap_put (out_map,
+ &roi->subject_hash,
+ roi,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+ {
+ char *diagnostic;
+
+ GNUNET_asprintf (&diagnostic,
+ "duplicate subject hash `%s'",
+ TALER_B2S (&roi->subject_hash));
+ TALER_ARL_amount_add (&total_wire_format_amount,
+ &total_wire_format_amount,
+ &dd->amount);
+ TALER_ARL_report (report_wire_format_inconsistencies,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("amount",
+ &dd->amount),
+ GNUNET_JSON_pack_uint64 ("wire_offset",
+ dd->serial_id),
+ GNUNET_JSON_pack_string ("diagnostic",
+ diagnostic)));
+ GNUNET_free (diagnostic);
+ }
}
check_exchange_wire_out (wa);
- return GNUNET_OK;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Analyzing bank DEBIT at %s of %s with WTID %s\n",
- GNUNET_STRINGS_absolute_time_to_string (details->execution_date),
- TALER_amount2s (&details->amount),
- TALER_B2S (&details->wtid));
- /* Update offset */
- wa->out_wire_off = row_off;
- slen = strlen (details->credit_account_url) + 1;
- roi = GNUNET_malloc (sizeof (struct ReserveOutInfo)
- + slen);
- GNUNET_CRYPTO_hash (&details->wtid,
- sizeof (details->wtid),
- &roi->subject_hash);
- roi->details.amount = details->amount;
- roi->details.execution_date = details->execution_date;
- roi->details.wtid = details->wtid;
- roi->details.credit_account_url = (const char *) &roi[1];
- memcpy (&roi[1],
- details->credit_account_url,
- slen);
- if (GNUNET_OK !=
- GNUNET_CONTAINER_multihashmap_put (out_map,
- &roi->subject_hash,
- roi,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
- {
- char *diagnostic;
-
- GNUNET_asprintf (&diagnostic,
- "duplicate subject hash `%s'",
- TALER_B2S (&roi->subject_hash));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_wire_format_amount,
- &total_wire_format_amount,
- &details->amount));
- TALER_ARL_report (report_wire_format_inconsistencies,
- json_pack ("{s:o, s:I, s:s}",
- "amount", TALER_JSON_from_amount (
- &details->amount),
- "wire_offset", (json_int_t) row_off,
- "diagnostic", diagnostic));
- GNUNET_free (diagnostic);
- return GNUNET_OK;
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ check_exchange_wire_out (wa);
+ return;
+ case MHD_HTTP_NOT_FOUND:
+ if (ignore_account_404)
+ {
+ check_exchange_wire_out (wa);
+ return;
+ }
+ break;
+ default:
+ break;
}
- return GNUNET_OK;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error fetching debit history of account %s: %u/%u!\n",
+ wa->ai->section_name,
+ dhr->http_status,
+ (unsigned int) dhr->ec);
+ commit (GNUNET_DB_STATUS_HARD_ERROR);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
}
@@ -1381,7 +1980,7 @@ process_debits (void *cls)
/* skip accounts where DEBIT is not enabled */
while ( (NULL != wa) &&
- (GNUNET_NO == wa->watch_debit) )
+ (GNUNET_NO == wa->ai->debit_enabled) )
wa = wa->next;
if (NULL == wa)
{
@@ -1392,21 +1991,24 @@ process_debits (void *cls)
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Checking bank DEBIT records of account `%s'\n",
- wa->section_name);
+ wa->ai->section_name);
GNUNET_assert (NULL == wa->dhh);
+ // FIXME: handle the case where more than INT32_MAX transactions exist.
+ // (CG: used to be INT64_MAX, changed by MS to INT32_MAX, why? To be discussed with him!)
wa->dhh = TALER_BANK_debit_history (ctx,
- &wa->auth,
- wa->out_wire_off,
- INT64_MAX,
+ wa->ai->auth,
+ wa->wire_off_out,
+ INT32_MAX,
+ GNUNET_TIME_UNIT_ZERO,
&history_debit_cb,
wa);
if (NULL == wa->dhh)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to obtain bank transaction history for `%s'\n",
- wa->section_name);
+ wa->ai->section_name);
commit (GNUNET_DB_STATUS_HARD_ERROR);
- global_ret = 1;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -1419,8 +2021,9 @@ process_debits (void *cls)
static void
begin_debit_audit (void)
{
+ GNUNET_assert (NULL == out_map);
out_map = GNUNET_CONTAINER_multihashmap_create (1024,
- GNUNET_YES);
+ true);
process_debits (wa_head);
}
@@ -1435,8 +2038,11 @@ begin_debit_audit (void)
static void
conclude_credit_history (void)
{
- GNUNET_CONTAINER_multihashmap_destroy (in_map);
- in_map = NULL;
+ if (NULL != in_map)
+ {
+ GNUNET_CONTAINER_multihashmap_destroy (in_map);
+ in_map = NULL;
+ }
/* credit done, now check debits */
begin_debit_audit ();
}
@@ -1455,14 +2061,14 @@ conclude_credit_history (void)
* @param execution_date when did we receive the funds
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
reserve_in_cb (void *cls,
uint64_t rowid,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_Amount *credit,
const char *sender_account_details,
uint64_t wire_reference,
- struct GNUNET_TIME_Absolute execution_date)
+ struct GNUNET_TIME_Timestamp execution_date)
{
struct WireAccount *wa = cls;
struct ReserveInInfo *rii;
@@ -1471,19 +2077,22 @@ reserve_in_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Analyzing exchange wire IN (%llu) at %s of %s with reserve_pub %s\n",
(unsigned long long) rowid,
- GNUNET_STRINGS_absolute_time_to_string (execution_date),
+ GNUNET_TIME_timestamp2s (execution_date),
TALER_amount2s (credit),
TALER_B2S (reserve_pub));
+ TALER_ARL_amount_add (&total_wire_in,
+ &total_wire_in,
+ credit);
slen = strlen (sender_account_details) + 1;
rii = GNUNET_malloc (sizeof (struct ReserveInInfo) + slen);
rii->rowid = rowid;
rii->details.amount = *credit;
rii->details.execution_date = execution_date;
rii->details.reserve_pub = *reserve_pub;
- rii->details.debit_account_url = (const char *) &rii[1];
- memcpy (&rii[1],
- sender_account_details,
- slen);
+ rii->details.debit_account_uri = (const char *) &rii[1];
+ GNUNET_memcpy (&rii[1],
+ sender_account_details,
+ slen);
GNUNET_CRYPTO_hash (&wire_reference,
sizeof (uint64_t),
&rii->row_off_hash);
@@ -1494,17 +2103,23 @@ reserve_in_cb (void *cls,
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
{
TALER_ARL_report (report_row_inconsistencies,
- json_pack ("{s:s, s:I, s:o, s:s}",
- "table", "reserves_in",
- "row", (json_int_t) rowid,
- "wire_offset_hash",
- GNUNET_JSON_from_data_auto (
- &rii->row_off_hash),
- "diagnostic", "duplicate wire offset"));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ "reserves_in"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ GNUNET_JSON_pack_data_auto ("id",
+ &rii->row_off_hash),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "duplicate wire offset")));
GNUNET_free (rii);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
wa->pp.last_reserve_in_serial_id = rowid + 1;
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
@@ -1517,7 +2132,7 @@ reserve_in_cb (void *cls,
* @param value the `struct ReserveInInfo` to free
* @return #GNUNET_OK
*/
-static int
+static enum GNUNET_GenericReturnValue
complain_in_not_found (void *cls,
const struct GNUNET_HashCode *key,
void *value)
@@ -1526,24 +2141,26 @@ complain_in_not_found (void *cls,
struct ReserveInInfo *rii = value;
(void) key;
- TALER_ARL_report (report_reserve_in_inconsistencies,
- json_pack ("{s:I, s:o, s:o, s:o, s:o, s:s, s:s}",
- "row", (json_int_t) rii->rowid,
- "amount_exchange_expected",
- TALER_JSON_from_amount (
- &rii->details.amount),
- "amount_wired", TALER_JSON_from_amount (&zero),
- "reserve_pub", GNUNET_JSON_from_data_auto (
- &rii->details.reserve_pub),
- "timestamp", TALER_ARL_json_from_time_abs (
- rii->details.execution_date),
- "account", wa->section_name,
- "diagnostic",
- "incoming wire transfer claimed by exchange not found"));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_amount_in_minus,
- &total_bad_amount_in_minus,
- &rii->details.amount));
+ TALER_ARL_report (
+ report_reserve_in_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rii->rowid),
+ TALER_JSON_pack_amount ("amount_exchange_expected",
+ &rii->details.amount),
+ TALER_JSON_pack_amount ("amount_wired",
+ &zero),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &rii->details.reserve_pub),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ rii->details.execution_date.abs_time),
+ GNUNET_JSON_pack_string ("account",
+ wa->ai->section_name),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "incoming wire transfer claimed by exchange not found")));
+ TALER_ARL_amount_add (&total_bad_amount_in_minus,
+ &total_bad_amount_in_minus,
+ &rii->details.amount);
return GNUNET_OK;
}
@@ -1559,50 +2176,19 @@ process_credits (void *cls);
/**
- * This function is called for all transactions that
- * are credited to the exchange's account (incoming
- * transactions).
+ * We got all of the incoming transactions for @a wa,
+ * finish processing the account.
*
- * @param cls `struct WireAccount` we are processing
- * @param http_status HTTP status returned by the bank
- * @param ec error code in case something went wrong
- * @param row_off identification of the position at which we are querying
- * @param details details about the wire transfer
- * @param json raw response
- * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
- */
-static int
-history_credit_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint64_t row_off,
- const struct TALER_BANK_CreditDetails *details,
- const json_t *json)
+ * @param[in,out] wa wire account to process
+ */
+static void
+conclude_account (struct WireAccount *wa)
{
- struct WireAccount *wa = cls;
- struct ReserveInInfo *rii;
- struct GNUNET_HashCode key;
-
- (void) json;
- if (NULL == details)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Reconciling CREDIT processing of account `%s'\n",
+ wa->ai->section_name);
+ if (NULL != in_map)
{
- wa->chh = NULL;
- if (TALER_EC_NONE != ec)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Error fetching credit history of account %s: %u/%u!\n",
- wa->section_name,
- http_status,
- (unsigned int) ec);
- commit (GNUNET_DB_STATUS_HARD_ERROR);
- global_ret = 1;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_SYSERR;
- }
- /* end of operation */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Reconciling CREDIT processing of account `%s'\n",
- wa->section_name);
GNUNET_CONTAINER_multihashmap_iterate (in_map,
&complain_in_not_found,
wa);
@@ -1610,16 +2196,32 @@ history_credit_cb (void *cls,
GNUNET_CONTAINER_multihashmap_iterate (in_map,
&free_rii,
NULL);
- process_credits (wa->next);
- return GNUNET_OK;
}
+ process_credits (wa->next);
+}
+
+
+/**
+ * Analyze credit transaction @a details into @a wa.
+ *
+ * @param[in,out] wa account that received the transfer
+ * @param details transfer details
+ * @return true on success, false to stop loop at this point
+ */
+static bool
+analyze_credit (struct WireAccount *wa,
+ const struct TALER_BANK_CreditDetails *details)
+{
+ struct ReserveInInfo *rii;
+ struct GNUNET_HashCode key;
+
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Analyzing bank CREDIT at %s of %s with Reserve-pub %s\n",
- GNUNET_STRINGS_absolute_time_to_string (details->execution_date),
+ GNUNET_TIME_timestamp2s (details->execution_date),
TALER_amount2s (&details->amount),
TALER_B2S (&details->reserve_pub));
- GNUNET_CRYPTO_hash (&row_off,
- sizeof (row_off),
+ GNUNET_CRYPTO_hash (&details->serial_id,
+ sizeof (details->serial_id),
&key);
rii = GNUNET_CONTAINER_multihashmap_get (in_map,
&key);
@@ -1627,137 +2229,199 @@ history_credit_cb (void *cls,
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Failed to find wire transfer at `%s' in exchange database. Audit ends at this point in time.\n",
- GNUNET_STRINGS_absolute_time_to_string (
- details->execution_date));
- wa->chh = NULL;
+ GNUNET_TIME_timestamp2s (details->execution_date));
process_credits (wa->next);
- return GNUNET_SYSERR; /* not an error, just end of processing */
+ return false; /* not an error, just end of processing */
}
/* Update offset */
- wa->in_wire_off = row_off;
+ wa->wire_off_in = details->serial_id;
/* compare records with expected data */
if (0 != GNUNET_memcmp (&details->reserve_pub,
&rii->details.reserve_pub))
{
- TALER_ARL_report (report_reserve_in_inconsistencies,
- json_pack ("{s:I, s:I, s:o, s:o, s:o, s:o, s:s}",
- "row", (json_int_t) rii->rowid,
- "bank_row", (json_int_t) row_off,
- "amount_exchange_expected",
- TALER_JSON_from_amount (
- &rii->details.amount),
- "amount_wired", TALER_JSON_from_amount (&zero),
- "reserve_pub", GNUNET_JSON_from_data_auto (
- &rii->details.reserve_pub),
- "timestamp", TALER_ARL_json_from_time_abs (
- rii->details.execution_date),
- "diagnostic", "wire subject does not match"));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_amount_in_minus,
- &total_bad_amount_in_minus,
- &rii->details.amount));
- TALER_ARL_report (report_reserve_in_inconsistencies,
- json_pack ("{s:I, s:I, s:o, s:o, s:o, s:o, s:s}",
- "row", (json_int_t) rii->rowid,
- "bank_row", (json_int_t) row_off,
- "amount_exchange_expected",
- TALER_JSON_from_amount (
- &zero),
- "amount_wired", TALER_JSON_from_amount (
- &details->amount),
- "reserve_pub", GNUNET_JSON_from_data_auto (
- &details->reserve_pub),
- "timestamp", TALER_ARL_json_from_time_abs (
- details->execution_date),
- "diagnostic", "wire subject does not match"));
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_amount_in_plus,
- &total_bad_amount_in_plus,
- &details->amount));
+ TALER_ARL_report (
+ report_reserve_in_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rii->rowid),
+ GNUNET_JSON_pack_uint64 ("bank_row",
+ details->serial_id),
+ TALER_JSON_pack_amount ("amount_exchange_expected",
+ &rii->details.amount),
+ TALER_JSON_pack_amount ("amount_wired",
+ &zero),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &rii->details.reserve_pub),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ rii->details.execution_date.abs_time),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "wire subject does not match")));
+ TALER_ARL_amount_add (&total_bad_amount_in_minus,
+ &total_bad_amount_in_minus,
+ &rii->details.amount);
+ TALER_ARL_report (
+ report_reserve_in_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rii->rowid),
+ GNUNET_JSON_pack_uint64 ("bank_row",
+ details->serial_id),
+ TALER_JSON_pack_amount ("amount_exchange_expected",
+ &zero),
+ TALER_JSON_pack_amount ("amount_wired",
+ &details->amount),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &details->reserve_pub),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ details->execution_date.abs_time),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "wire subject does not match")));
+
+ TALER_ARL_amount_add (&total_bad_amount_in_plus,
+ &total_bad_amount_in_plus,
+ &details->amount);
goto cleanup;
}
if (0 != TALER_amount_cmp (&rii->details.amount,
&details->amount))
{
- TALER_ARL_report (report_reserve_in_inconsistencies,
- json_pack ("{s:I, s:I, s:o, s:o, s:o, s:o, s:s}",
- "row", (json_int_t) rii->rowid,
- "bank_row", (json_int_t) row_off,
- "amount_exchange_expected",
- TALER_JSON_from_amount (
- &rii->details.amount),
- "amount_wired", TALER_JSON_from_amount (
- &details->amount),
- "reserve_pub", GNUNET_JSON_from_data_auto (
- &details->reserve_pub),
- "timestamp", TALER_ARL_json_from_time_abs (
- details->execution_date),
- "diagnostic", "wire amount does not match"));
+ TALER_ARL_report (
+ report_reserve_in_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rii->rowid),
+ GNUNET_JSON_pack_uint64 ("bank_row",
+ details->serial_id),
+ TALER_JSON_pack_amount ("amount_exchange_expected",
+ &rii->details.amount),
+ TALER_JSON_pack_amount ("amount_wired",
+ &details->amount),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &details->reserve_pub),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ details->execution_date.abs_time),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "wire amount does not match")));
if (0 < TALER_amount_cmp (&details->amount,
&rii->details.amount))
{
/* details->amount > rii->details.amount: wire transfer was larger than it should have been */
struct TALER_Amount delta;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- &details->amount,
- &rii->details.amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_amount_in_plus,
- &total_bad_amount_in_plus,
- &delta));
+ TALER_ARL_amount_subtract (&delta,
+ &details->amount,
+ &rii->details.amount);
+ TALER_ARL_amount_add (&total_bad_amount_in_plus,
+ &total_bad_amount_in_plus,
+ &delta);
}
else
{
/* rii->details.amount < details->amount: wire transfer was smaller than it should have been */
struct TALER_Amount delta;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&delta,
- &rii->details.amount,
- &details->amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_bad_amount_in_minus,
- &total_bad_amount_in_minus,
- &delta));
+ TALER_ARL_amount_subtract (&delta,
+ &rii->details.amount,
+ &details->amount);
+ TALER_ARL_amount_add (&total_bad_amount_in_minus,
+ &total_bad_amount_in_minus,
+ &delta);
}
goto cleanup;
}
- if (0 != strcasecmp (details->debit_account_url,
- rii->details.debit_account_url))
+ if (0 != strcasecmp (details->debit_account_uri,
+ rii->details.debit_account_uri))
{
- TALER_ARL_report (report_missattribution_in_inconsistencies,
- json_pack ("{s:o, s:I, s:I, s:o}",
- "amount", TALER_JSON_from_amount (
- &rii->details.amount),
- "row", (json_int_t) rii->rowid,
- "bank_row", (json_int_t) row_off,
- "reserve_pub", GNUNET_JSON_from_data_auto (
- &rii->details.reserve_pub)));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&total_missattribution_in,
- &total_missattribution_in,
- &rii->details.amount));
+ TALER_ARL_report (report_misattribution_in_inconsistencies,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("amount",
+ &rii->details.amount),
+ GNUNET_JSON_pack_uint64 ("row",
+ rii->rowid),
+ GNUNET_JSON_pack_uint64 ("bank_row",
+ details->serial_id),
+ GNUNET_JSON_pack_data_auto (
+ "reserve_pub",
+ &rii->details.reserve_pub)));
+ TALER_ARL_amount_add (&total_misattribution_in,
+ &total_misattribution_in,
+ &rii->details.amount);
}
- if (details->execution_date.abs_value_us !=
- rii->details.execution_date.abs_value_us)
+ if (GNUNET_TIME_timestamp_cmp (details->execution_date,
+ !=,
+ rii->details.execution_date))
{
TALER_ARL_report (report_row_minor_inconsistencies,
- json_pack ("{s:s, s:I, s:I, s:s}",
- "table", "reserves_in",
- "row", (json_int_t) rii->rowid,
- "bank_row", (json_int_t) row_off,
- "diagnostic", "execution date mismatch"));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ "reserves_in"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rii->rowid),
+ GNUNET_JSON_pack_uint64 ("bank_row",
+ details->serial_id),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "execution date mismatch")));
}
cleanup:
GNUNET_assert (GNUNET_OK ==
free_rii (NULL,
&key,
rii));
- return GNUNET_OK;
+ return true;
+}
+
+
+/**
+ * This function is called for all transactions that
+ * are credited to the exchange's account (incoming
+ * transactions).
+ *
+ * @param cls `struct WireAccount` we are processing
+ * @param chr HTTP response returned by the bank
+ */
+static void
+history_credit_cb (void *cls,
+ const struct TALER_BANK_CreditHistoryResponse *chr)
+{
+ struct WireAccount *wa = cls;
+
+ wa->chh = NULL;
+ switch (chr->http_status)
+ {
+ case MHD_HTTP_OK:
+ for (unsigned int i = 0; i<chr->details.ok.details_length; i++)
+ {
+ const struct TALER_BANK_CreditDetails *cd
+ = &chr->details.ok.details[i];
+
+ if (! analyze_credit (wa,
+ cd))
+ return;
+ }
+ conclude_account (wa);
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ conclude_account (wa);
+ return;
+ case MHD_HTTP_NOT_FOUND:
+ if (ignore_account_404)
+ {
+ conclude_account (wa);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error fetching credit history of account %s: %u/%s!\n",
+ wa->ai->section_name,
+ chr->http_status,
+ TALER_ErrorCode_get_hint (chr->ec));
+ commit (GNUNET_DB_STATUS_HARD_ERROR);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
}
@@ -1778,7 +2442,7 @@ process_credits (void *cls)
/* skip accounts where CREDIT is not enabled */
while ( (NULL != wa) &&
- (GNUNET_NO == wa->watch_credit) )
+ (GNUNET_NO == wa->ai->credit_enabled) )
wa = wa->next;
if (NULL == wa)
{
@@ -1788,29 +2452,31 @@ process_credits (void *cls)
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Analyzing exchange's wire IN table for account `%s'\n",
- wa->section_name);
+ wa->ai->section_name);
qs = TALER_ARL_edb->select_reserves_in_above_serial_id_by_account (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- wa->section_name,
+ wa->ai->section_name,
wa->pp.last_reserve_in_serial_id,
&reserve_in_cb,
wa);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- global_ret = 1;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Starting bank CREDIT history of account `%s'\n",
- wa->section_name);
+ wa->ai->section_name);
+ // NOTE: handle the case where more than INT32_MAX transactions exist.
+ // (CG: used to be INT64_MAX, changed by MS to INT32_MAX, why? To be discussed with him!)
wa->chh = TALER_BANK_credit_history (ctx,
- &wa->auth,
- wa->in_wire_off,
- INT64_MAX,
+ wa->ai->auth,
+ wa->wire_off_in,
+ INT32_MAX,
+ GNUNET_TIME_UNIT_ZERO,
&history_credit_cb,
wa);
if (NULL == wa->chh)
@@ -1818,7 +2484,7 @@ process_credits (void *cls)
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to obtain bank transaction history\n");
commit (GNUNET_DB_STATUS_HARD_ERROR);
- global_ret = 1;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -1831,6 +2497,7 @@ process_credits (void *cls)
static void
begin_credit_audit (void)
{
+ GNUNET_assert (NULL == in_map);
in_map = GNUNET_CONTAINER_multihashmap_create (1024,
GNUNET_YES);
/* now go over all bank accounts and check delta with in_map */
@@ -1839,8 +2506,7 @@ begin_credit_audit (void)
/**
- * Function called about reserve closing operations
- * the aggregator triggered.
+ * Function called about reserve closing operations the aggregator triggered.
*
* @param cls closure
* @param rowid row identifier used to uniquely identify the reserve closing operation
@@ -1850,45 +2516,53 @@ begin_credit_audit (void)
* @param reserve_pub public key of the reserve
* @param receiver_account where did we send the funds, in payto://-format
* @param wtid identifier used for the wire transfer
+ * @param close_request_row which close request triggered the operation?
+ * 0 if it was a timeout (not used)
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
reserve_closed_cb (void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute execution_date,
+ struct GNUNET_TIME_Timestamp execution_date,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *closing_fee,
const struct TALER_ReservePublicKeyP *reserve_pub,
const char *receiver_account,
- const struct TALER_WireTransferIdentifierRawP *wtid)
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t close_request_row)
{
struct ReserveClosure *rc;
struct GNUNET_HashCode key;
(void) cls;
+ (void) close_request_row;
rc = GNUNET_new (struct ReserveClosure);
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&rc->amount,
- amount_with_fee,
- closing_fee))
+ if (TALER_ARL_SR_INVALID_NEGATIVE ==
+ TALER_ARL_amount_subtract_neg (&rc->amount,
+ amount_with_fee,
+ closing_fee))
{
TALER_ARL_report (report_row_inconsistencies,
- json_pack ("{s:s, s:I, s:o, s:o, s:o, s:s}",
- "table", "reserves_closures",
- "row", (json_int_t) rowid,
- "reserve_pub", GNUNET_JSON_from_data_auto (
- reserve_pub),
- "amount_with_fee", TALER_JSON_from_amount (
- amount_with_fee),
- "closing_fee", TALER_JSON_from_amount (
- closing_fee),
- "diagnostic",
- "closing fee above total amount"));
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ "reserves_closures"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ GNUNET_JSON_pack_data_auto ("id",
+ reserve_pub),
+ TALER_JSON_pack_amount ("amount_with_fee",
+ amount_with_fee),
+ TALER_JSON_pack_amount ("closing_fee",
+ closing_fee),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "closing fee above total amount")));
GNUNET_free (rc);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
- pp.last_reserve_close_uuid
- = GNUNET_MAX (pp.last_reserve_close_uuid,
+ TALER_ARL_USE_PP (wire_reserve_close_id)
+ = GNUNET_MAX (TALER_ARL_USE_PP (wire_reserve_close_id),
rowid + 1);
rc->receiver_account = GNUNET_strdup (receiver_account);
rc->wtid = *wtid;
@@ -1901,6 +2575,8 @@ reserve_closed_cb (void *cls,
&key,
rc,
GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
@@ -1913,49 +2589,96 @@ reserve_closed_cb (void *cls,
static enum GNUNET_DB_QueryStatus
begin_transaction (void)
{
- TALER_ARL_esession = TALER_ARL_edb->get_session (TALER_ARL_edb->cls);
- if (NULL == TALER_ARL_esession)
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (GNUNET_SYSERR ==
+ TALER_ARL_edb->preflight (TALER_ARL_edb->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to initialize exchange database session.\n");
- return GNUNET_SYSERR;
+ "Failed to initialize exchange database connection.\n");
+ return GNUNET_DB_STATUS_HARD_ERROR;
}
- TALER_ARL_asession = TALER_ARL_adb->get_session (TALER_ARL_adb->cls);
- if (NULL == TALER_ARL_asession)
+ if (GNUNET_SYSERR ==
+ TALER_ARL_adb->preflight (TALER_ARL_adb->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to initialize auditor database session.\n");
- return GNUNET_SYSERR;
+ return GNUNET_DB_STATUS_HARD_ERROR;
}
if (GNUNET_OK !=
- TALER_ARL_adb->start (TALER_ARL_adb->cls,
- TALER_ARL_asession))
+ TALER_ARL_adb->start (TALER_ARL_adb->cls))
{
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- TALER_ARL_edb->preflight (TALER_ARL_edb->cls,
- TALER_ARL_esession);
+ TALER_ARL_edb->preflight (TALER_ARL_edb->cls);
if (GNUNET_OK !=
TALER_ARL_edb->start (TALER_ARL_edb->cls,
- TALER_ARL_esession,
"wire auditor"))
{
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (total_drained)));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_wire_in));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_wire_out));
+ qs = TALER_ARL_adb->get_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_AB (total_drained),
+ TALER_ARL_GET_AB (final_balance),
+ NULL);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ had_start_balance = false;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ had_start_balance = true;
+ break;
+ }
for (struct WireAccount *wa = wa_head;
NULL != wa;
wa = wa->next)
{
- wa->qsx = TALER_ARL_adb->get_wire_auditor_account_progress (
+ GNUNET_asprintf (&wa->label_reserve_in_serial_id,
+ "wire-%s-%s",
+ wa->ai->section_name,
+ "reserve_in_serial_id");
+ GNUNET_asprintf (&wa->label_wire_out_serial_id,
+ "wire-%s-%s",
+ wa->ai->section_name,
+ "wire_out_serial_id");
+ GNUNET_asprintf (&wa->label_wire_off_in,
+ "wire-%s-%s",
+ wa->ai->section_name,
+ "wire_off_in");
+ GNUNET_asprintf (&wa->label_wire_off_out,
+ "wire-%s-%s",
+ wa->ai->section_name,
+ "wire_off_out");
+ wa->qsx = TALER_ARL_adb->get_auditor_progress (
TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- wa->section_name,
- &wa->pp,
- &wa->in_wire_off,
- &wa->out_wire_off);
+ wa->label_reserve_in_serial_id,
+ &wa->pp.last_reserve_in_serial_id,
+ wa->label_wire_out_serial_id,
+ &wa->pp.last_wire_out_serial_id,
+ wa->label_wire_off_in,
+ &wa->wire_off_in,
+ wa->label_wire_off_out,
+ &wa->wire_off_out,
+ NULL);
if (0 > wa->qsx)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == wa->qsx);
@@ -1963,10 +2686,12 @@ begin_transaction (void)
}
wa->start_pp = wa->pp;
}
- qsx_gwap = TALER_ARL_adb->get_wire_auditor_progress (TALER_ARL_adb->cls,
- TALER_ARL_asession,
- &TALER_ARL_master_pub,
- &pp);
+ qsx_gwap = TALER_ARL_adb->get_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_PP (wire_reserve_close_id),
+ TALER_ARL_GET_PP (wire_batch_deposit_id),
+ TALER_ARL_GET_PP (wire_aggregation_id),
+ NULL);
if (0 > qsx_gwap)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx_gwap);
@@ -1979,11 +2704,11 @@ begin_transaction (void)
}
else
{
- start_pp = pp;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming wire audit at %s / %llu\n",
- GNUNET_STRINGS_absolute_time_to_string (pp.last_timestamp),
- (unsigned long long) pp.last_reserve_close_uuid);
+ "Resuming wire audit at %llu / %llu / %llu\n",
+ (unsigned long long) TALER_ARL_USE_PP (wire_reserve_close_id),
+ (unsigned long long) TALER_ARL_USE_PP (wire_batch_deposit_id),
+ (unsigned long long) TALER_ARL_USE_PP (wire_aggregation_id));
}
{
@@ -1991,8 +2716,7 @@ begin_transaction (void)
qs = TALER_ARL_edb->select_reserve_closed_above_serial_id (
TALER_ARL_edb->cls,
- TALER_ARL_esession,
- pp.last_reserve_close_uuid,
+ TALER_ARL_USE_PP (wire_reserve_close_id),
&reserve_closed_cb,
NULL);
if (0 > qs)
@@ -2021,31 +2745,14 @@ process_account_cb (void *cls,
struct WireAccount *wa;
(void) cls;
- if ( (GNUNET_NO == ai->debit_enabled) &&
- (GNUNET_NO == ai->credit_enabled) )
+ if ( (! ai->debit_enabled) &&
+ (! ai->credit_enabled) )
return; /* not an active exchange account */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Found exchange account `%s'\n",
ai->section_name);
wa = GNUNET_new (struct WireAccount);
- wa->section_name = GNUNET_strdup (ai->section_name);
- wa->watch_debit = ai->debit_enabled;
- wa->watch_credit = ai->credit_enabled;
- if (GNUNET_OK !=
- TALER_BANK_auth_parse_cfg (TALER_ARL_cfg,
- ai->section_name,
- &wa->auth))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to access bank account `%s'\n",
- wa->section_name);
- GNUNET_break (0);
- GNUNET_free (wa->section_name);
- GNUNET_free (wa);
- global_ret = 1;
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
+ wa->ai = ai;
GNUNET_CONTAINER_DLL_insert (wa_head,
wa_tail,
wa);
@@ -2074,7 +2781,7 @@ run (void *cls,
if (GNUNET_OK !=
TALER_ARL_init (c))
{
- global_ret = 1;
+ global_ret = EXIT_FAILURE;
return;
}
if (GNUNET_OK !=
@@ -2083,11 +2790,7 @@ run (void *cls,
"TINY_AMOUNT",
&tiny_amount))
{
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "auditor",
- "TINY_AMOUNT",
- "invalid amount");
- global_ret = 1;
+ global_ret = EXIT_NOTCONFIGURED;
return;
}
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
@@ -2098,6 +2801,7 @@ run (void *cls,
if (NULL == ctx)
{
GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
return;
}
reserve_closures = GNUNET_CONTAINER_multihashmap_create (1024,
@@ -2114,48 +2818,63 @@ run (void *cls,
GNUNET_assert (NULL !=
(report_row_inconsistencies = json_array ()));
GNUNET_assert (NULL !=
- (report_missattribution_in_inconsistencies
+ (report_misattribution_in_inconsistencies
= json_array ()));
GNUNET_assert (NULL !=
(report_lags = json_array ()));
GNUNET_assert (NULL !=
+ (report_aml_lags = json_array ()));
+ GNUNET_assert (NULL !=
+ (report_kyc_lags = json_array ()));
+ GNUNET_assert (NULL !=
(report_closure_lags = json_array ()));
GNUNET_assert (NULL !=
(report_account_progress = json_array ()));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_bad_amount_out_plus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_bad_amount_out_minus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_bad_amount_in_plus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_bad_amount_in_minus));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
- &total_missattribution_in));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_misattribution_in));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_amount_lag));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_closure_amount_lag));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&total_wire_format_amount));
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (TALER_ARL_currency,
+ TALER_amount_set_zero (TALER_ARL_currency,
&zero));
- TALER_EXCHANGEDB_find_accounts (TALER_ARL_cfg,
- &process_account_cb,
+ if (GNUNET_OK !=
+ TALER_EXCHANGEDB_load_accounts (TALER_ARL_cfg,
+ TALER_EXCHANGEDB_ALO_DEBIT
+ | TALER_EXCHANGEDB_ALO_CREDIT
+ | TALER_EXCHANGEDB_ALO_AUTHDATA))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No bank accounts configured\n");
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ }
+ TALER_EXCHANGEDB_find_accounts (&process_account_cb,
NULL);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
begin_transaction ())
{
- global_ret = 1;
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
}
}
@@ -2175,33 +2894,46 @@ main (int argc,
char *const *argv)
{
const struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_base32_auto ('m',
- "exchange-key",
- "KEY",
- "public key of the exchange (Crockford base32 encoded)",
- &TALER_ARL_master_pub),
+ GNUNET_GETOPT_option_flag ('i',
+ "internal",
+ "perform checks only applicable for exchange-internal audits",
+ &internal_checks),
+ GNUNET_GETOPT_option_flag ('I',
+ "ignore-not-found",
+ "continue, even if the bank account of the exchange was not found",
+ &ignore_account_404),
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue 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 */
(void) TALER_project_data_default ();
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-helper-auditor-wire",
- "MESSAGE",
- NULL));
if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc,
- argv,
- "taler-helper-auditor-wire",
- "Audit exchange database for consistency with the bank's wire transfers",
- options,
- &run,
- NULL))
- return 1;
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ ret = GNUNET_PROGRAM_run (
+ argc,
+ argv,
+ "taler-helper-auditor-wire",
+ gettext_noop (
+ "Audit exchange database for consistency with the bank's wire transfers"),
+ options,
+ &run,
+ NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
return global_ret;
}
diff --git a/src/auditor/test-auditor.sh b/src/auditor/test-auditor.sh
index 57b017d28..2cfea0532 100755
--- a/src/auditor/test-auditor.sh
+++ b/src/auditor/test-auditor.sh
@@ -1,4 +1,24 @@
#!/bin/bash
+#
+# This file is part of TALER
+# 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
+# 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, If not, see <http://www.gnu.org/license>
+#
+#
+# shellcheck disable=SC2317
+# shellcheck disable=SC1091
+#
+#
# Setup database which was generated from a perfectly normal
# exchange-wallet interaction and run the auditor against it.
#
@@ -6,10 +26,11 @@
#
# Requires 'jq' tool and Postgres superuser rights!
set -eu
+#set -x
# Set of numbers for all the testcases.
# When adding new tests, increase the last number:
-ALL_TESTS=`seq 0 32`
+ALL_TESTS=$(seq 0 33)
# $TESTS determines which tests we should run.
# This construction is used to make it easy to
@@ -27,49 +48,105 @@ TESTS=${1:-$ALL_TESTS}
# VALGRIND=valgrind
VALGRIND=""
-# Exit, with status code "skip" (no 'real' failure)
-function exit_skip() {
- echo $1
- exit 77
+# Number of seconds to let libeuifn background
+# tasks apply a cycle of payment submission and
+# history request.
+LIBEUFIN_SETTLE_TIME=1
+
+. setup.sh
+
+
+# Cleanup exchange and libeufin between runs.
+function cleanup()
+{
+ if [ ! -z "${EPID:-}" ]
+ then
+ echo -n "Stopping exchange $EPID..."
+ kill -TERM "$EPID"
+ wait "$EPID" || true
+ echo "DONE"
+ unset EPID
+ fi
+ stop_libeufin
}
-# Exit, with error message (hard failure)
-function exit_fail() {
- echo $1
- kill `jobs -p` >/dev/null 2>/dev/null || true
- wait
- exit 1
+# Cleanup to run whenever we exit
+function exit_cleanup()
+{
+ echo "Running exit-cleanup"
+ if [ ! -z "${POSTGRES_PATH:-}" ]
+ then
+ echo "Stopping Postgres at ${POSTGRES_PATH}"
+ "${POSTGRES_PATH}/pg_ctl" \
+ -D "$TMPDIR" \
+ -l /dev/null \
+ stop \
+ &> /dev/null \
+ || true
+ fi
+ cleanup
+ for n in $(jobs -p)
+ do
+ kill "$n" 2> /dev/null || true
+ done
+ wait || true
+ echo "DONE"
}
+# Install cleanup handler (except for kill -9)
+trap exit_cleanup EXIT
+
# Operations to run before the actual audit
function pre_audit () {
# Launch bank
- echo -n "Launching bank "
- taler-bank-manage-testing $CONF postgres:///$DB serve-http 2>bank.err >bank.log &
- for n in `seq 1 20`
+ echo -n "Launching libeufin-bank"
+ export CONF
+ export MY_TMP_DIR
+ launch_libeufin
+ for n in $(seq 1 80)
do
echo -n "."
sleep 0.1
OK=1
- wget http://localhost:8082/ -o /dev/null -O /dev/null >/dev/null && break
+ wget http://localhost:8082/ \
+ -o /dev/null \
+ -O /dev/null \
+ >/dev/null \
+ && break
OK=0
done
- if [ 1 != $OK ]
+ if [ 1 != "$OK" ]
then
- exit_skip "Failed to launch bank"
+ exit_skip "Failed to launch libeufin-bank"
fi
echo " DONE"
- if test ${1:-no} = "aggregator"
+ if [ "${1:-no}" = "aggregator" ]
then
echo -n "Running exchange aggregator ..."
- taler-exchange-aggregator -L INFO -t -c $CONF 2> aggregator.log || exit_fail "FAIL"
+ taler-exchange-aggregator \
+ -y \
+ -L "INFO" \
+ -t \
+ -c "$CONF" \
+ 2> "${MY_TMP_DIR}/aggregator.log" \
+ || exit_fail "FAIL"
echo " DONE"
echo -n "Running exchange closer ..."
- taler-exchange-closer -L INFO -t -c $CONF 2> closer.log || exit_fail "FAIL"
+ taler-exchange-closer \
+ -L "INFO" \
+ -t \
+ -c "$CONF" \
+ 2> "${MY_TMP_DIR}/closer.log" \
+ || exit_fail "FAIL"
echo " DONE"
echo -n "Running exchange transfer ..."
- taler-exchange-transfer -L INFO -t -c $CONF 2> transfer.log || exit_fail "FAIL"
+ taler-exchange-transfer \
+ -L "INFO" \
+ -t \
+ -c "$CONF" \
+ 2> "${MY_TMP_DIR}/transfer.log" \
+ || exit_fail "FAIL"
echo " DONE"
fi
}
@@ -80,26 +157,92 @@ function audit_only () {
echo -n "Running audit(s) ..."
# Restart so that first run is always fresh, and second one is incremental
- taler-auditor-dbinit -r -c $CONF
- $VALGRIND taler-helper-auditor-aggregation -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-aggregation.json 2> test-audit-aggregation.log || exit_fail "aggregation audit failed"
+ taler-auditor-dbinit \
+ -r \
+ -c "$CONF"
+ $VALGRIND taler-helper-auditor-aggregation \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-aggregation.out" \
+ 2> "${MY_TMP_DIR}/test-audit-aggregation.err" \
+ || exit_fail "aggregation audit failed (see ${MY_TMP_DIR}/test-audit-aggregation.*)"
echo -n "."
- $VALGRIND taler-helper-auditor-aggregation -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-aggregation-inc.json 2> test-audit-aggregation-inc.log || exit_fail "incremental aggregation audit failed"
+ $VALGRIND taler-helper-auditor-aggregation \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-aggregation-inc.out" \
+ 2> "${MY_TMP_DIR}/test-audit-aggregation-inc.err" \
+ || exit_fail "incremental aggregation audit failed (see ${MY_TMP_DIR}/test-audit-aggregation-inc.*)"
echo -n "."
- $VALGRIND taler-helper-auditor-coins -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-coins.json 2> test-audit-coins.log || exit_fail "coin audit failed"
+ $VALGRIND taler-helper-auditor-coins \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-coins.out" \
+ 2> "${MY_TMP_DIR}/test-audit-coins.err" \
+ || exit_fail "coin audit failed (see ${MY_TMP_DIR}/test-audit-coins.*)"
echo -n "."
- $VALGRIND taler-helper-auditor-coins -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-coins-inc.json 2> test-audit-coins-inc.log || exit_fail "incremental coin audit failed"
+ $VALGRIND taler-helper-auditor-coins \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-coins-inc.out" \
+ 2> "${MY_TMP_DIR}/test-audit-coins-inc.err" \
+ || exit_fail "incremental coin audit failed (see ${MY_TMP_DIR}/test-audit-coins-inc.*)"
echo -n "."
- $VALGRIND taler-helper-auditor-deposits -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-deposits.json 2> test-audit-deposits.log || exit_fail "deposits audit failed"
+ $VALGRIND taler-helper-auditor-deposits \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-deposits.out" \
+ 2> "${MY_TMP_DIR}/test-audit-deposits.err" \
+ || exit_fail "deposits audit failed (see ${MY_TMP_DIR}/test-audit-deposits.*)"
echo -n "."
- $VALGRIND taler-helper-auditor-deposits -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-deposits-inc.json 2> test-audit-deposits-inc.log || exit_fail "incremental deposits audit failed"
+ $VALGRIND taler-helper-auditor-deposits \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-deposits-inc.out" \
+ 2> "${MY_TMP_DIR}/test-audit-deposits-inc.err" \
+ || exit_fail "incremental deposits audit failed (see ${MY_TMP_DIR}/test-audit-deposits-inc.*)"
echo -n "."
- $VALGRIND taler-helper-auditor-reserves -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-reserves.json 2> test-audit-reserves.log || exit_fail "reserves audit failed"
+ $VALGRIND taler-helper-auditor-reserves \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-reserves.out" \
+ 2> "${MY_TMP_DIR}/test-audit-reserves.err" \
+ || exit_fail "reserves audit failed (see ${MY_TMP_DIR}/test-audit-reserves.*)"
echo -n "."
- $VALGRIND taler-helper-auditor-reserves -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-reserves-inc.json 2> test-audit-reserves-inc.log || exit_fail "incremental reserves audit failed"
+ $VALGRIND taler-helper-auditor-reserves \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-reserves-inc.out" \
+ 2> "${MY_TMP_DIR}/test-audit-reserves-inc.err" \
+ || exit_fail "incremental reserves audit failed (see ${MY_TMP_DIR}/test-audit-reserves-inc.*)"
echo -n "."
- $VALGRIND taler-helper-auditor-wire -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-wire.json 2> test-wire-audit.log || exit_fail "wire audit failed"
+ $VALGRIND taler-helper-auditor-wire \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-wire.out" \
+ 2> "${MY_TMP_DIR}/test-audit-wire.err" \
+ || exit_fail "wire audit failed (see ${MY_TMP_DIR}/test-audit-wire.*)"
echo -n "."
- $VALGRIND taler-helper-auditor-wire -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-wire-inc.json 2> test-wire-audit-inc.log || exit_fail "wire audit failed"
+ $VALGRIND taler-helper-auditor-wire \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-wire-inc.out" \
+ 2> "${MY_TMP_DIR}/test-audit-wire-inc.err" \
+ || exit_fail "wire audit inc failed (see ${MY_TMP_DIR}/test-audit-wire-inc.*)"
echo -n "."
echo " DONE"
@@ -108,19 +251,11 @@ function audit_only () {
# Cleanup to run after the auditor
function post_audit () {
- taler-exchange-dbinit -g || exit_fail "exchange DB GC failed"
-
- kill -TERM `jobs -p` >/dev/null 2>/dev/null || true
- echo -n "Waiting for servers to die ..."
- wait
- echo "DONE"
- echo -n "TeXing ."
- taler-helper-auditor-render.py test-audit-aggregation.json test-audit-coins.json test-audit-deposits.json test-audit-reserves.json test-audit-wire.json < ../../contrib/auditor-report.tex.j2 > test-report.tex || exit_fail "Renderer failed"
-
- echo -n "."
- timeout 10 pdflatex test-report.tex >/dev/null || exit_fail "pdflatex failed"
- echo -n "."
- timeout 10 pdflatex test-report.tex >/dev/null
+ taler-exchange-dbinit \
+ -c "$CONF" \
+ -g \
+ || exit_fail "exchange DB GC failed"
+ cleanup
echo " DONE"
}
@@ -129,143 +264,207 @@ function post_audit () {
# generation. Pass "aggregator" as $1 to run
# $ taler-exchange-aggregator
# before auditor (to trigger pending wire transfers).
+# Pass "drain" as $2 to run a drain operation as well.
function run_audit () {
- pre_audit ${1:-no}
+ pre_audit "${1:-no}"
+ if [ "${2:-no}" = "drain" ]
+ then
+ echo -n "Starting exchange..."
+ taler-exchange-httpd \
+ -c "${CONF}" \
+ -L INFO \
+ 2> "${MY_TMP_DIR}/exchange-httpd-drain.err" &
+ EPID=$!
+
+ # Wait for all services to be available
+ for n in $(seq 1 50)
+ do
+ echo -n "."
+ sleep 0.1
+ OK=0
+ # exchange
+ wget "http://localhost:8081/seed" \
+ -o /dev/null \
+ -O /dev/null \
+ >/dev/null \
+ || continue
+ OK=1
+ break
+ done
+ echo "... DONE."
+ export CONF
+
+ echo -n "Running taler-exchange-offline drain "
+
+ taler-exchange-offline \
+ -L DEBUG \
+ -c "${CONF}" \
+ drain TESTKUDOS:0.1 \
+ exchange-account-1 payto://iban/SANDBOXX/DE360679?receiver-name=Exchange+Drain \
+ upload \
+ 2> "${MY_TMP_DIR}/taler-exchange-offline-drain.log" \
+ || exit_fail "offline draining failed"
+ kill -TERM "$EPID"
+ wait "$EPID" || true
+ unset EPID
+ echo -n "Running taler-exchange-drain ..."
+ printf "\n" | taler-exchange-drain \
+ -L DEBUG \
+ -c "$CONF" \
+ 2> "${MY_TMP_DIR}/taler-exchange-drain.log" \
+ || exit_fail "FAIL"
+ echo " DONE"
+
+ echo -n "Running taler-exchange-transfer ..."
+ taler-exchange-transfer \
+ -L INFO \
+ -t \
+ -c "$CONF" \
+ 2> "${MY_TMP_DIR}/drain-transfer.log" \
+ || exit_fail "FAIL"
+ echo " DONE"
+ fi
audit_only
post_audit
-
}
# Do a full reload of the (original) database
-full_reload()
+function full_reload()
{
- echo -n "Doing full reload of the database... "
- dropdb $DB 2> /dev/null || true
- createdb -T template0 $DB || exit_skip "could not create database"
+ echo -n "Doing full reload of the database (loading ${BASEDB}.sql into $DB at $PGHOST)... "
+ dropdb "$DB" 2> /dev/null || true
+ createdb -T template0 "$DB" \
+ || exit_skip "could not create database $DB (at $PGHOST)"
# Import pre-generated database, -q(ietly) using single (-1) transaction
- psql -Aqt $DB -q -1 -f ${BASEDB}.sql > /dev/null || exit_skip "Failed to load database"
+ psql -Aqt "$DB" \
+ -q \
+ -1 \
+ -f "${BASEDB}.sql" \
+ > /dev/null \
+ || exit_skip "Failed to load database $DB from ${BASEDB}.sql"
echo "DONE"
+ # Technically, this call shouldn't be needed as libeufin should already be stopped here...
+ stop_libeufin
}
function test_0() {
-echo "===========0: normal run with aggregator==========="
-run_audit aggregator
-
-echo "Checking output"
-# if an emergency was detected, that is a bug and we should fail
-echo -n "Test for emergencies... "
-jq -e .emergencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency detected in ordinary run" || echo PASS
-echo -n "Test for deposit confirmation emergencies... "
-jq -e .deposit_confirmation_inconsistencies[0] < test-audit-deposits.json > /dev/null && exit_fail "Unexpected deposit confirmation inconsistency detected" || echo PASS
-echo -n "Test for emergencies by count... "
-jq -e .emergencies_by_count[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency by count detected in ordinary run" || echo PASS
-
-echo -n "Test for wire inconsistencies... "
-jq -e .wire_out_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire out inconsistency detected in ordinary run"
-jq -e .reserve_in_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected reserve in inconsistency detected in ordinary run"
-jq -e .missattribution_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected missattribution inconsistency detected in ordinary run"
-jq -e .row_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected row inconsistency detected in ordinary run"
-jq -e .denomination_key_validity_withdraw_inconsistencies[0] < test-audit-reserves.json > /dev/null && exit_fail "Unexpected denomination key withdraw inconsistency detected in ordinary run"
-jq -e .row_minor_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected minor row inconsistency detected in ordinary run"
-jq -e .lag_details[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected lag detected in ordinary run"
-jq -e .wire_format_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire format inconsistencies detected in ordinary run"
-
-
-# TODO: check operation balances are correct (once we have all transaction types and wallet is deterministic)
-# TODO: check revenue summaries are correct (once we have all transaction types and wallet is deterministic)
+ echo "===========0: normal run with aggregator==========="
+ run_audit aggregator
+ echo "Checking output"
+ # if an emergency was detected, that is a bug and we should fail
+ echo -n "Test for emergencies... "
+ jq -e .emergencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency detected in ordinary run" || echo PASS
+ echo -n "Test for deposit confirmation emergencies... "
+ jq -e .deposit_confirmation_inconsistencies[0] < test-audit-deposits.json > /dev/null && exit_fail "Unexpected deposit confirmation inconsistency detected" || echo PASS
+ echo -n "Test for emergencies by count... "
+ jq -e .emergencies_by_count[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency by count detected in ordinary run" || echo PASS
+
+ echo -n "Test for wire inconsistencies... "
+ jq -e .wire_out_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire out inconsistency detected in ordinary run"
+ jq -e .reserve_in_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected reserve in inconsistency detected in ordinary run"
+ jq -e .misattribution_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected misattribution inconsistency detected in ordinary run"
+ jq -e .row_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected row inconsistency detected in ordinary run"
+ jq -e .denomination_key_validity_withdraw_inconsistencies[0] < test-audit-reserves.json > /dev/null && exit_fail "Unexpected denomination key withdraw inconsistency detected in ordinary run"
+ jq -e .row_minor_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected minor row inconsistency detected in ordinary run"
+ jq -e .lag_details[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected lag detected in ordinary run"
+ jq -e .wire_format_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire format inconsistencies detected in ordinary run"
+
+
+ # TODO: check operation balances are correct (once we have all transaction types and wallet is deterministic)
+ # TODO: check revenue summaries are correct (once we have all transaction types and wallet is deterministic)
-echo PASS
+ echo PASS
-LOSS=`jq -r .total_bad_sig_loss < test-audit-aggregation.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong total bad sig loss from aggregation, got unexpected loss of $LOSS"
-fi
-LOSS=`jq -r .total_bad_sig_loss < test-audit-coins.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong total bad sig loss from coins, got unexpected loss of $LOSS"
-fi
-LOSS=`jq -r .total_bad_sig_loss < test-audit-reserves.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong total bad sig loss from reserves, got unexpected loss of $LOSS"
-fi
+ LOSS=$(jq -r .total_bad_sig_loss < test-audit-aggregation.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total bad sig loss from aggregation, got unexpected loss of $LOSS"
+ fi
+ LOSS=$(jq -r .irregular_loss < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total bad sig loss from coins, got unexpected loss of $LOSS"
+ fi
+ LOSS=$(jq -r .total_bad_sig_loss < test-audit-reserves.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total bad sig loss from reserves, got unexpected loss of $LOSS"
+ fi
-echo -n "Test for wire amounts... "
-WIRED=`jq -r .total_wire_in_delta_plus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta plus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_wire_in_delta_minus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta minus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_wire_out_delta_plus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta plus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_wire_out_delta_minus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta minus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_missattribution_in < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total missattribution in wrong, got $WIRED"
-fi
-echo PASS
+ echo -n "Test for wire amounts... "
+ WIRED=$(jq -r .total_wire_in_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_in_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_misattribution_in < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total misattribution in wrong, got $WIRED"
+ fi
+ echo "PASS"
-echo -n "Checking for unexpected arithmetic differences "
-LOSS=`jq -r .total_arithmetic_delta_plus < test-audit-aggregation.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong arithmetic delta from aggregations, got unexpected plus of $LOSS"
-fi
-LOSS=`jq -r .total_arithmetic_delta_minus < test-audit-aggregation.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong arithmetic delta from aggregation, got unexpected minus of $LOSS"
-fi
-LOSS=`jq -r .total_arithmetic_delta_plus < test-audit-coins.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong arithmetic delta from coins, got unexpected plus of $LOSS"
-fi
-LOSS=`jq -r .total_arithmetic_delta_minus < test-audit-coins.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong arithmetic delta from coins, got unexpected minus of $LOSS"
-fi
-LOSS=`jq -r .total_arithmetic_delta_plus < test-audit-reserves.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong arithmetic delta from reserves, got unexpected plus of $LOSS"
-fi
-LOSS=`jq -r .total_arithmetic_delta_minus < test-audit-reserves.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong arithmetic delta from reserves, got unexpected minus of $LOSS"
-fi
+ echo -n "Checking for unexpected arithmetic differences "
+ LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-aggregation.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from aggregations, got unexpected plus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-aggregation.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from aggregation, got unexpected minus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from coins, got unexpected plus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from coins, got unexpected minus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-reserves.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from reserves, got unexpected plus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-reserves.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from reserves, got unexpected minus of $LOSS"
+ fi
-jq -e .amount_arithmetic_inconsistencies[0] < test-audit-aggregation.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from aggregations detected in ordinary run"
-jq -e .amount_arithmetic_inconsistencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from coins detected in ordinary run"
-jq -e .amount_arithmetic_inconsistencies[0] < test-audit-reserves.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from reserves detected in ordinary run"
-echo PASS
+ jq -e .amount_arithmetic_inconsistencies[0] < test-audit-aggregation.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from aggregations detected in ordinary run"
+ jq -e .amount_arithmetic_inconsistencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from coins detected in ordinary run"
+ jq -e .amount_arithmetic_inconsistencies[0] < test-audit-reserves.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from reserves detected in ordinary run"
+ echo "PASS"
-echo -n "Checking for unexpected wire out differences "
-jq -e .wire_out_inconsistencies[0] < test-audit-aggregation.json > /dev/null && exit_fail "Unexpected wire out inconsistencies detected in ordinary run"
-echo PASS
+ echo -n "Checking for unexpected wire out differences "
+ jq -e .wire_out_inconsistencies[0] < test-audit-aggregation.json > /dev/null && exit_fail "Unexpected wire out inconsistencies detected in ordinary run"
+ echo "PASS"
-# cannot easily undo aggregator, hence full reload
-full_reload
+ # cannot easily undo aggregator, hence full reload
+ full_reload
}
@@ -274,121 +473,147 @@ full_reload
# transfer lag!
function test_1() {
-echo "===========1: normal run==========="
-run_audit
-
-echo "Checking output"
-# if an emergency was detected, that is a bug and we should fail
-echo -n "Test for emergencies... "
-jq -e .emergencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency detected in ordinary run" || echo PASS
-echo -n "Test for emergencies by count... "
-jq -e .emergencies_by_count[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency by count detected in ordinary run" || echo PASS
-
-echo -n "Test for wire inconsistencies... "
-jq -e .wire_out_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire out inconsistency detected in ordinary run"
-jq -e .reserve_in_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected reserve in inconsistency detected in ordinary run"
-jq -e .missattribution_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected missattribution inconsistency detected in ordinary run"
-jq -e .row_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected row inconsistency detected in ordinary run"
-jq -e .row_minor_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected minor row inconsistency detected in ordinary run"
-jq -e .wire_format_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire format inconsistencies detected in ordinary run"
-
-# TODO: check operation balances are correct (once we have all transaction types and wallet is deterministic)
-# TODO: check revenue summaries are correct (once we have all transaction types and wallet is deterministic)
-
-echo PASS
-
-echo -n "Check for lag detection... "
-
-# Check wire transfer lag reported (no aggregator!)
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-helper-auditor-wire.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
- jq -e .lag_details[0] < test-audit-wire.json > /dev/null || exit_fail "Lag not detected in run without aggregator at age $DELTA"
+ echo "===========1: normal run==========="
+ run_audit
+
+ echo "Checking output"
+ # if an emergency was detected, that is a bug and we should fail
+ echo -n "Test for emergencies... "
+ jq -e .emergencies[0] \
+ < test-audit-coins.json \
+ > /dev/null \
+ && exit_fail "Unexpected emergency detected in ordinary run";
+ echo "PASS"
+ echo -n "Test for emergencies by count... "
+ jq -e .emergencies_by_count[0] \
+ < test-audit-coins.json \
+ > /dev/null \
+ && exit_fail "Unexpected emergency by count detected in ordinary run"
+ echo "PASS"
+
+ echo -n "Test for wire inconsistencies... "
+ jq -e .wire_out_amount_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected wire out inconsistency detected in ordinary run"
+ jq -e .reserve_in_amount_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected reserve in inconsistency detected in ordinary run"
+ jq -e .misattribution_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected misattribution inconsistency detected in ordinary run"
+ jq -e .row_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected row inconsistency detected in ordinary run"
+ jq -e .row_minor_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected minor row inconsistency detected in ordinary run"
+ jq -e .wire_format_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected wire format inconsistencies detected in ordinary run"
+
+ # TODO: check operation balances are correct (once we have all transaction types and wallet is deterministic)
+ # TODO: check revenue summaries are correct (once we have all transaction types and wallet is deterministic)
+
+ echo "PASS"
- LAG=`jq -r .total_amount_lag < test-audit-wire.json`
- if test $LAG = "TESTKUDOS:0"
+ echo -n "Check for lag detection... "
+
+ # Check wire transfer lag reported (no aggregator!)
+ # NOTE: This test is EXPECTED to fail for ~1h after
+ # re-generating the test database as we do not
+ # report lag of less than 1h (see GRACE_PERIOD in
+ # taler-helper-auditor-wire.c)
+ jq -e .lag_details[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ || exit_fail "Lag not detected in run without aggregator"
+
+ LAG=$(jq -r .total_amount_lag < test-audit-wire.json)
+ if [ "$LAG" = "TESTKUDOS:0" ]
then
exit_fail "Expected total lag to be non-zero"
fi
echo "PASS"
-else
- echo "SKIP (database too new)"
-fi
-echo -n "Test for wire amounts... "
-WIRED=`jq -r .total_wire_in_delta_plus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta plus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_wire_in_delta_minus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta minus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_wire_out_delta_plus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta plus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_wire_out_delta_minus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta minus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_missattribution_in < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total missattribution in wrong, got $WIRED"
-fi
-# Database was unmodified, no need to undo
-echo "OK"
+ echo -n "Test for wire amounts... "
+ WIRED=$(jq -r .total_wire_in_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_in_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_misattribution_in < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total misattribution in wrong, got $WIRED"
+ fi
+ # Database was unmodified, no need to undo
+ echo "OK"
}
# Change amount of wire transfer reported by exchange
function test_2() {
-echo "===========2: reserves_in inconsistency==========="
-echo "UPDATE reserves_in SET credit_val=5 WHERE reserve_in_serial_id=1" | psql -Aqt $DB
+ echo "===========2: reserves_in inconsistency ==========="
+ echo "UPDATE exchange.reserves_in SET credit_val=5 WHERE reserve_in_serial_id=1" \
+ | psql -At "$DB"
-run_audit
+ run_audit
-echo -n "Testing inconsistency detection... "
-ROW=`jq .reserve_in_amount_inconsistencies[0].row < test-audit-wire.json`
-if test $ROW != 1
-then
- exit_fail "Row wrong"
-fi
-WIRED=`jq -r .reserve_in_amount_inconsistencies[0].amount_wired < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:10"
-then
- exit_fail "Amount wrong"
-fi
-EXPECTED=`jq -r .reserve_in_amount_inconsistencies[0].amount_exchange_expected < test-audit-wire.json`
-if test $EXPECTED != "TESTKUDOS:5"
-then
- exit_fail "Expected amount wrong"
-fi
+ echo -n "Testing inconsistency detection... "
+ ROW=$(jq .reserve_in_amount_inconsistencies[0].row < test-audit-wire.json)
+ if [ "$ROW" != 1 ]
+ then
+ exit_fail "Row $ROW is wrong"
+ fi
+ WIRED=$(jq -r .reserve_in_amount_inconsistencies[0].amount_wired < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:10" ]
+ then
+ exit_fail "Amount wrong"
+ fi
+ EXPECTED=$(jq -r .reserve_in_amount_inconsistencies[0].amount_exchange_expected < test-audit-wire.json)
+ if [ "$EXPECTED" != "TESTKUDOS:5" ]
+ then
+ exit_fail "Expected amount wrong"
+ fi
-WIRED=`jq -r .total_wire_in_delta_minus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Wrong total wire_in_delta_minus, got $WIRED"
-fi
-DELTA=`jq -r .total_wire_in_delta_plus < test-audit-wire.json`
-if test $DELTA != "TESTKUDOS:5"
-then
- exit_fail "Expected total wire delta plus wrong, got $DELTA"
-fi
-echo PASS
+ WIRED=$(jq -r .total_wire_in_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total wire_in_delta_minus, got $WIRED"
+ fi
+ DELTA=$(jq -r .total_wire_in_delta_plus < test-audit-wire.json)
+ if [ "$DELTA" != "TESTKUDOS:5" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $DELTA"
+ fi
+ echo "PASS"
-# Undo database modification
-echo "UPDATE reserves_in SET credit_val=10 WHERE reserve_in_serial_id=1" | psql -Aqt $DB
+ # Undo database modification
+ echo "UPDATE exchange.reserves_in SET credit_val=10 WHERE reserve_in_serial_id=1" \
+ | psql -Aqt "$DB"
}
@@ -397,61 +622,62 @@ echo "UPDATE reserves_in SET credit_val=10 WHERE reserve_in_serial_id=1" | psql
# lower than what exchange claims to have received.
function test_3() {
-echo "===========3: reserves_in inconsistency==========="
-echo "UPDATE reserves_in SET credit_val=15 WHERE reserve_in_serial_id=1" | psql -Aqt $DB
+ echo "===========3: reserves_in inconsistency==========="
+ echo "UPDATE exchange.reserves_in SET credit_val=15 WHERE reserve_in_serial_id=1" \
+ | psql -Aqt "$DB"
-run_audit
+ run_audit
-EXPECTED=`jq -r .reserve_balance_summary_wrong_inconsistencies[0].auditor < test-audit-reserves.json`
-if test $EXPECTED != "TESTKUDOS:5.01"
-then
- exit_fail "Expected reserve balance summary amount wrong, got $EXPECTED (auditor)"
-fi
+ EXPECTED=$(jq -r .reserve_balance_summary_wrong_inconsistencies[0].auditor < test-audit-reserves.json)
+ if [ "$EXPECTED" != "TESTKUDOS:5.01" ]
+ then
+ exit_fail "Expected reserve balance summary amount wrong, got $EXPECTED (auditor)"
+ fi
-EXPECTED=`jq -r .reserve_balance_summary_wrong_inconsistencies[0].exchange < test-audit-reserves.json`
-if test $EXPECTED != "TESTKUDOS:0.01"
-then
- exit_fail "Expected reserve balance summary amount wrong, got $EXPECTED (exchange)"
-fi
+ EXPECTED=$(jq -r .reserve_balance_summary_wrong_inconsistencies[0].exchange < test-audit-reserves.json)
+ if [ "$EXPECTED" != "TESTKUDOS:0.01" ]
+ then
+ exit_fail "Expected reserve balance summary amount wrong, got $EXPECTED (exchange)"
+ fi
-WIRED=`jq -r .total_loss_balance_insufficient < test-audit-reserves.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Wrong total loss from insufficient balance, got $WIRED"
-fi
+ WIRED=$(jq -r .total_irregular_loss < test-audit-reserves.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total loss from insufficient balance, got $WIRED"
+ fi
-ROW=`jq -e .reserve_in_amount_inconsistencies[0].row < test-audit-wire.json`
-if test $ROW != 1
-then
- exit_fail "Row wrong, got $ROW"
-fi
+ ROW=$(jq -e .reserve_in_amount_inconsistencies[0].row < test-audit-wire.json)
+ if [ "$ROW" != 1 ]
+ then
+ exit_fail "Row wrong, got $ROW"
+ fi
-WIRED=`jq -r .reserve_in_amount_inconsistencies[0].amount_exchange_expected < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:15"
-then
- exit_fail "Wrong amount_exchange_expected, got $WIRED"
-fi
+ WIRED=$(jq -r .reserve_in_amount_inconsistencies[0].amount_exchange_expected < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:15" ]
+ then
+ exit_fail "Wrong amount_exchange_expected, got $WIRED"
+ fi
-WIRED=`jq -r .reserve_in_amount_inconsistencies[0].amount_wired < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:10"
-then
- exit_fail "Wrong amount_wired, got $WIRED"
-fi
+ WIRED=$(jq -r .reserve_in_amount_inconsistencies[0].amount_wired < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:10" ]
+ then
+ exit_fail "Wrong amount_wired, got $WIRED"
+ fi
-WIRED=`jq -r .total_wire_in_delta_minus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:5"
-then
- exit_fail "Wrong total wire_in_delta_minus, got $WIRED"
-fi
+ WIRED=$(jq -r .total_wire_in_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:5" ]
+ then
+ exit_fail "Wrong total wire_in_delta_minus, got $WIRED"
+ fi
-WIRED=`jq -r .total_wire_in_delta_plus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Wrong total wire_in_delta_plus, got $WIRED"
-fi
+ WIRED=$(jq -r .total_wire_in_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total wire_in_delta_plus, got $WIRED"
+ fi
-# Undo database modification
-echo "UPDATE reserves_in SET credit_val=10 WHERE reserve_in_serial_id=1" | psql -Aqt $DB
+ # Undo database modification
+ echo "UPDATE exchange.reserves_in SET credit_val=10 WHERE reserve_in_serial_id=1" | psql -Aqt "$DB"
}
@@ -460,45 +686,52 @@ echo "UPDATE reserves_in SET credit_val=10 WHERE reserve_in_serial_id=1" | psql
# lower than what exchange claims to have received.
function test_4() {
-echo "===========4: deposit wire target wrong================="
-# Original target bank account was 43, changing to 44
-SERIAL=`echo "SELECT deposit_serial_id FROM deposits WHERE amount_with_fee_val=3 AND amount_with_fee_frac=0 ORDER BY deposit_serial_id LIMIT 1" | psql $DB -Aqt`
-OLD_WIRE=`echo "SELECT wire FROM deposits WHERE deposit_serial_id=${SERIAL};" | psql $DB -Aqt`
-echo "UPDATE deposits SET wire='{\"payto_uri\":\"payto://x-taler-bank/localhost:8082/44\",\"salt\":\"test-salt\"}' WHERE deposit_serial_id=${SERIAL}" | psql -Aqt $DB
+ echo "===========4: deposit wire target wrong================="
+ # Original target bank account was 43, changing to 44
+ SERIAL=$(echo "SELECT deposit_serial_id FROM exchange.deposits WHERE amount_with_fee_val=3 AND amount_with_fee_frac=0 ORDER BY deposit_serial_id LIMIT 1" | psql "$DB" -Aqt)
+ OLD_WIRE_ID=$(echo "SELECT wire_target_h_payto FROM exchange.deposits WHERE deposit_serial_id=${SERIAL};" | psql "$DB" -Aqt)
+# shellcheck disable=SC2028
+ echo "INSERT INTO exchange.wire_targets (payto_uri, wire_target_h_payto) VALUES ('payto://x-taler-bank/localhost/testuser-xxlargtp', '\x1e8f31936b3cee8f8afd3aac9e38b5db42d45b721ffc4eb1e5b9ddaf1565660b');" \
+ | psql "$DB" \
+ -Aqt \
+ > /dev/null
+# shellcheck disable=SC2028
+ echo "UPDATE exchange.deposits SET wire_target_h_payto='\x1e8f31936b3cee8f8afd3aac9e38b5db42d45b721ffc4eb1e5b9ddaf1565660b' WHERE deposit_serial_id=${SERIAL}" \
+ | psql -Aqt "$DB"
-run_audit
+ run_audit
-echo -n "Testing inconsistency detection... "
+ echo -n "Testing inconsistency detection... "
-jq -e .bad_sig_losses[0] < test-audit-coins.json > /dev/null || exit_fail "Bad signature not detected"
+ jq -e .bad_sig_losses[0] < test-audit-coins.json > /dev/null || exit_fail "Bad signature not detected"
-ROW=`jq -e .bad_sig_losses[0].row < test-audit-coins.json`
-if test $ROW != ${SERIAL}
-then
- exit_fail "Row wrong, got $ROW"
-fi
+ ROW=$(jq -e .bad_sig_losses[0].row < test-audit-coins.json)
+ if [ "$ROW" != "${SERIAL}" ]
+ then
+ exit_fail "Row wrong, got $ROW"
+ fi
-LOSS=`jq -r .bad_sig_losses[0].loss < test-audit-coins.json`
-if test $LOSS != "TESTKUDOS:3"
-then
- exit_fail "Wrong deposit bad signature loss, got $LOSS"
-fi
+ LOSS=$(jq -r .bad_sig_losses[0].loss < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:3" ]
+ then
+ exit_fail "Wrong deposit bad signature loss, got $LOSS"
+ fi
-OP=`jq -r .bad_sig_losses[0].operation < test-audit-coins.json`
-if test $OP != "deposit"
-then
- exit_fail "Wrong operation, got $OP"
-fi
+ OP=$(jq -r .bad_sig_losses[0].operation < test-audit-coins.json)
+ if [ "$OP" != "deposit" ]
+ then
+ exit_fail "Wrong operation, got $OP"
+ fi
-LOSS=`jq -r .total_bad_sig_loss < test-audit-coins.json`
-if test $LOSS != "TESTKUDOS:3"
-then
- exit_fail "Wrong total bad sig loss, got $LOSS"
-fi
+ LOSS=$(jq -r .irregular_loss < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:3" ]
+ then
+ exit_fail "Wrong total bad sig loss, got $LOSS"
+ fi
-echo PASS
-# Undo:
-echo "UPDATE deposits SET wire='$OLD_WIRE' WHERE deposit_serial_id=${SERIAL}" | psql -Aqt $DB
+ echo PASS
+ # Undo:
+ echo "UPDATE exchange.deposits SET wire_target_h_payto='$OLD_WIRE_ID' WHERE deposit_serial_id=${SERIAL}" | psql -Aqt "$DB"
}
@@ -507,42 +740,44 @@ echo "UPDATE deposits SET wire='$OLD_WIRE' WHERE deposit_serial_id=${SERIAL}" |
# Test where h_contract_terms in the deposit table is wrong
# (=> bad signature)
function test_5() {
-echo "===========5: deposit contract hash wrong================="
-# Modify h_wire hash, so it is inconsistent with 'wire'
-SERIAL=`echo "SELECT deposit_serial_id FROM deposits WHERE amount_with_fee_val=3 AND amount_with_fee_frac=0 ORDER BY deposit_serial_id LIMIT 1" | psql $DB -Aqt`
-OLD_H=`echo "SELECT h_contract_terms FROM deposits WHERE deposit_serial_id=$SERIAL;" | psql $DB -Aqt`
-echo "UPDATE deposits SET h_contract_terms='\x12bb676444955c98789f219148aa31899d8c354a63330624d3d143222cf3bb8b8e16f69accd5a8773127059b804c1955696bf551dd7be62719870613332aa8d5' WHERE deposit_serial_id=$SERIAL" | psql -Aqt $DB
+ echo "===========5: deposit contract hash wrong================="
+ # Modify h_wire hash, so it is inconsistent with 'wire'
+ SERIAL=$(echo "SELECT deposit_serial_id FROM exchange.deposits WHERE amount_with_fee_val=3 AND amount_with_fee_frac=0 ORDER BY deposit_serial_id LIMIT 1" | psql "$DB" -Aqt)
+ OLD_H=$(echo "SELECT h_contract_terms FROM exchange.deposits WHERE deposit_serial_id=$SERIAL;" | psql "$DB" -Aqt)
+# shellcheck disable=SC2028
+ echo "UPDATE exchange.deposits SET h_contract_terms='\x12bb676444955c98789f219148aa31899d8c354a63330624d3d143222cf3bb8b8e16f69accd5a8773127059b804c1955696bf551dd7be62719870613332aa8d5' WHERE deposit_serial_id=$SERIAL" \
+ | psql -Aqt "$DB"
-run_audit
+ run_audit
-echo -n "Checking bad signature detection... "
-ROW=`jq -e .bad_sig_losses[0].row < test-audit-coins.json`
-if test $ROW != $SERIAL
-then
- exit_fail "Row wrong, got $ROW"
-fi
+ echo -n "Checking bad signature detection... "
+ ROW=$(jq -e .bad_sig_losses[0].row < test-audit-coins.json)
+ if [ "$ROW" != "$SERIAL" ]
+ then
+ exit_fail "Row wrong, got $ROW"
+ fi
-LOSS=`jq -r .bad_sig_losses[0].loss < test-audit-coins.json`
-if test $LOSS != "TESTKUDOS:3"
-then
- exit_fail "Wrong deposit bad signature loss, got $LOSS"
-fi
+ LOSS=$(jq -r .bad_sig_losses[0].loss < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:3" ]
+ then
+ exit_fail "Wrong deposit bad signature loss, got $LOSS"
+ fi
-OP=`jq -r .bad_sig_losses[0].operation < test-audit-coins.json`
-if test $OP != "deposit"
-then
- exit_fail "Wrong operation, got $OP"
-fi
+ OP=$(jq -r .bad_sig_losses[0].operation < test-audit-coins.json)
+ if [ "$OP" != "deposit" ]
+ then
+ exit_fail "Wrong operation, got $OP"
+ fi
-LOSS=`jq -r .total_bad_sig_loss < test-audit-coins.json`
-if test $LOSS != "TESTKUDOS:3"
-then
- exit_fail "Wrong total bad sig loss, got $LOSS"
-fi
-echo PASS
+ LOSS=$(jq -r .irregular_loss < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:3" ]
+ then
+ exit_fail "Wrong total bad sig loss, got $LOSS"
+ fi
+ echo PASS
-# Undo:
-echo "UPDATE deposits SET h_contract_terms='${OLD_H}' WHERE deposit_serial_id=$SERIAL" | psql -Aqt $DB
+ # Undo:
+ echo "UPDATE exchange.deposits SET h_contract_terms='${OLD_H}' WHERE deposit_serial_id=$SERIAL" | psql -Aqt "$DB"
}
@@ -550,40 +785,42 @@ echo "UPDATE deposits SET h_contract_terms='${OLD_H}' WHERE deposit_serial_id=$S
# Test where denom_sig in known_coins table is wrong
# (=> bad signature)
function test_6() {
-echo "===========6: known_coins signature wrong================="
-# Modify denom_sig, so it is wrong
-OLD_SIG=`echo 'SELECT denom_sig FROM known_coins LIMIT 1;' | psql $DB -Aqt`
-COIN_PUB=`echo "SELECT coin_pub FROM known_coins WHERE denom_sig='$OLD_SIG';" | psql $DB -Aqt`
-echo "UPDATE known_coins SET denom_sig='\x287369672d76616c200a2028727361200a2020287320233542383731423743393036444643303442424430453039353246413642464132463537303139374131313437353746324632323332394644443146324643333445393939413336363430334233413133324444464239413833353833464536354442374335434445304441453035374438363336434541423834463843323843344446304144363030343430413038353435363039373833434431333239393736423642433437313041324632414132414435413833303432434346314139464635394244434346374436323238344143354544364131373739463430353032323241373838423837363535453434423145443831364244353638303232413123290a2020290a20290b' WHERE coin_pub='$COIN_PUB'" | psql -Aqt $DB
+ echo "===========6: known_coins signature wrong================="
+ # Modify denom_sig, so it is wrong
+ OLD_SIG=$(echo 'SELECT denom_sig FROM exchange.known_coins LIMIT 1;' | psql "$DB" -Aqt)
+ COIN_PUB=$(echo "SELECT coin_pub FROM exchange.known_coins WHERE denom_sig='$OLD_SIG';" | psql "$DB" -Aqt)
+# shellcheck disable=SC2028
+ echo "UPDATE exchange.known_coins SET denom_sig='\x0000000100000000287369672d76616c200a2028727361200a2020287320233542383731423743393036444643303442424430453039353246413642464132463537303139374131313437353746324632323332394644443146324643333445393939413336363430334233413133324444464239413833353833464536354442374335434445304441453035374438363336434541423834463843323843344446304144363030343430413038353435363039373833434431333239393736423642433437313041324632414132414435413833303432434346314139464635394244434346374436323238344143354544364131373739463430353032323241373838423837363535453434423145443831364244353638303232413123290a2020290a20290b' WHERE coin_pub='$COIN_PUB'" \
+ | psql -Aqt "$DB"
-run_audit
+ run_audit
-ROW=`jq -e .bad_sig_losses[0].row < test-audit-coins.json`
-if test $ROW != "1"
-then
- exit_fail "Row wrong, got $ROW"
-fi
+ ROW=$(jq -e .bad_sig_losses[0].row < test-audit-coins.json)
+ if [ "$ROW" != "1" ]
+ then
+ exit_fail "Row wrong, got $ROW"
+ fi
-LOSS=`jq -r .bad_sig_losses[0].loss < test-audit-coins.json`
-if test $LOSS == "TESTKUDOS:0"
-then
- exit_fail "Wrong deposit bad signature loss, got $LOSS"
-fi
+ LOSS=$(jq -r .bad_sig_losses[0].loss < test-audit-coins.json)
+ if [ "$LOSS" == "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong deposit bad signature loss, got $LOSS"
+ fi
-OP=`jq -r .bad_sig_losses[0].operation < test-audit-coins.json`
-if test $OP != "melt"
-then
- exit_fail "Wrong operation, got $OP"
-fi
+ OP=$(jq -r .bad_sig_losses[0].operation < test-audit-coins.json)
+ if [ "$OP" != "melt" ]
+ then
+ exit_fail "Wrong operation, got $OP"
+ fi
-LOSS=`jq -r .total_bad_sig_loss < test-audit-coins.json`
-if test $LOSS == "TESTKUDOS:0"
-then
- exit_fail "Wrong total bad sig loss, got $LOSS"
-fi
+ LOSS=$(jq -r .irregular_loss < test-audit-coins.json)
+ if [ "$LOSS" == "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total bad sig loss, got $LOSS"
+ fi
-# Undo
-echo "UPDATE known_coins SET denom_sig='$OLD_SIG' WHERE coin_pub='$COIN_PUB'" | psql -Aqt $DB
+ # Undo
+ echo "UPDATE exchange.known_coins SET denom_sig='$OLD_SIG' WHERE coin_pub='$COIN_PUB'" | psql -Aqt "$DB"
}
@@ -591,51 +828,53 @@ echo "UPDATE known_coins SET denom_sig='$OLD_SIG' WHERE coin_pub='$COIN_PUB'" |
# Test where h_wire in the deposit table is wrong
function test_7() {
-echo "===========7: reserves_out signature wrong================="
-# Modify reserve_sig, so it is bogus
-HBE=`echo 'SELECT h_blind_ev FROM reserves_out LIMIT 1;' | psql $DB -Aqt`
-OLD_SIG=`echo "SELECT reserve_sig FROM reserves_out WHERE h_blind_ev='$HBE';" | psql $DB -Aqt`
-A_VAL=`echo "SELECT amount_with_fee_val FROM reserves_out WHERE h_blind_ev='$HBE';" | psql $DB -Aqt`
-A_FRAC=`echo "SELECT amount_with_fee_frac FROM reserves_out WHERE h_blind_ev='$HBE';" | psql $DB -Aqt`
-# Normalize, we only deal with cents in this test-case
-A_FRAC=`expr $A_FRAC / 1000000 || true`
-echo "UPDATE reserves_out SET reserve_sig='\x9ef381a84aff252646a157d88eded50f708b2c52b7120d5a232a5b628f9ced6d497e6652d986b581188fb014ca857fd5e765a8ccc4eb7e2ce9edcde39accaa4b' WHERE h_blind_ev='$HBE'" | psql -Aqt $DB
-
-run_audit
-
-OP=`jq -r .bad_sig_losses[0].operation < test-audit-reserves.json`
-if test $OP != "withdraw"
-then
- exit_fail "Wrong operation, got $OP"
-fi
+ echo "===========7: reserves_out signature wrong================="
+ # Modify reserve_sig, so it is bogus
+ HBE=$(echo 'SELECT h_blind_ev FROM exchange.reserves_out LIMIT 1;' | psql "$DB" -Aqt)
+ OLD_SIG=$(echo "SELECT reserve_sig FROM exchange.reserves_out WHERE h_blind_ev='$HBE';" | psql "$DB" -Aqt)
+ A_VAL=$(echo "SELECT amount_with_fee_val FROM exchange.reserves_out WHERE h_blind_ev='$HBE';" | psql "$DB" -Aqt)
+ A_FRAC=$(echo "SELECT amount_with_fee_frac FROM exchange.reserves_out WHERE h_blind_ev='$HBE';" | psql "$DB" -Aqt)
+ # Normalize, we only deal with cents in this test-case
+ A_FRAC=$(( A_FRAC / 1000000))
+# shellcheck disable=SC2028
+ echo "UPDATE exchange.reserves_out SET reserve_sig='\x9ef381a84aff252646a157d88eded50f708b2c52b7120d5a232a5b628f9ced6d497e6652d986b581188fb014ca857fd5e765a8ccc4eb7e2ce9edcde39accaa4b' WHERE h_blind_ev='$HBE'" \
+ | psql -Aqt "$DB"
-LOSS=`jq -r .bad_sig_losses[0].loss < test-audit-reserves.json`
-LOSS_TOTAL=`jq -r .total_bad_sig_loss < test-audit-reserves.json`
-if test $LOSS != $LOSS_TOTAL
-then
- exit_fail "Expected loss $LOSS and total loss $LOSS_TOTAL do not match"
-fi
-if test $A_FRAC != 0
-then
- if [ $A_FRAC -lt 10 ]
+ run_audit
+
+ OP=$(jq -r .bad_sig_losses[0].operation < test-audit-reserves.json)
+ if [ "$OP" != "withdraw" ]
then
- A_PREV="0"
- else
- A_PREV=""
+ exit_fail "Wrong operation, got $OP"
fi
- if test $LOSS != "TESTKUDOS:$A_VAL.$A_PREV$A_FRAC"
+
+ LOSS=$(jq -r .bad_sig_losses[0].loss < test-audit-reserves.json)
+ LOSS_TOTAL=$(jq -r .total_bad_sig_loss < test-audit-reserves.json)
+ if [ "$LOSS" != "$LOSS_TOTAL" ]
then
- exit_fail "Expected loss TESTKUDOS:$A_VAL.$A_PREV$A_FRAC but got $LOSS"
+ exit_fail "Expected loss $LOSS and total loss $LOSS_TOTAL do not match"
fi
-else
- if test $LOSS != "TESTKUDOS:$A_VAL"
+ if [ "$A_FRAC" != 0 ]
then
- exit_fail "Expected loss TESTKUDOS:$A_VAL but got $LOSS"
+ if [ "$A_FRAC" -lt 10 ]
+ then
+ A_PREV="0"
+ else
+ A_PREV=""
+ fi
+ if [ "$LOSS" != "TESTKUDOS:$A_VAL.$A_PREV$A_FRAC" ]
+ then
+ exit_fail "Expected loss TESTKUDOS:$A_VAL.$A_PREV$A_FRAC but got $LOSS"
+ fi
+ else
+ if [ "$LOSS" != "TESTKUDOS:$A_VAL" ]
+ then
+ exit_fail "Expected loss TESTKUDOS:$A_VAL but got $LOSS"
+ fi
fi
-fi
-# Undo:
-echo "UPDATE reserves_out SET reserve_sig='$OLD_SIG' WHERE h_blind_ev='$HBE'" | psql -Aqt $DB
+ # Undo:
+ echo "UPDATE exchange.reserves_out SET reserve_sig='$OLD_SIG' WHERE h_blind_ev='$HBE'" | psql -Aqt "$DB"
}
@@ -643,475 +882,459 @@ echo "UPDATE reserves_out SET reserve_sig='$OLD_SIG' WHERE h_blind_ev='$HBE'" |
# Test wire transfer subject disagreement!
function test_8() {
-echo "===========8: wire-transfer-subject disagreement==========="
-OLD_ID=`echo "SELECT id FROM app_banktransaction WHERE amount='TESTKUDOS:10' ORDER BY id LIMIT 1;" | psql $DB -Aqt`
-OLD_WTID=`echo "SELECT subject FROM app_banktransaction WHERE id='$OLD_ID';" | psql $DB -Aqt`
-NEW_WTID="CK9QBFY972KR32FVA1MW958JWACEB6XCMHHKVFMCH1A780Q12SVG"
-echo "UPDATE app_banktransaction SET subject='$NEW_WTID' WHERE id='$OLD_ID';" | psql -Aqt $DB
+ echo "===========8: wire-transfer-subject disagreement==========="
+ # Technically, this call shouldn't be needed, as libeufin should already be stopped here.
+ stop_libeufin
+ echo "FIXME: test needs update to new libeufin-bank schema"
+ exit 0
+ OLD_ID=$(echo "SELECT id FROM NexusBankTransactions WHERE amount='10' AND currency='TESTKUDOS' ORDER BY id LIMIT 1;" | psql "${DB}" -Aqt) \
+ || exit_fail "Failed to SELECT FROM NexusBankTransactions nexus DB!"
+ OLD_WTID=$(echo "SELECT \"reservePublicKey\" FROM TalerIncomingPayments WHERE payment='$OLD_ID';" \
+ | psql "${DB}" -Aqt)
+ NEW_WTID="CK9QBFY972KR32FVA1MW958JWACEB6XCMHHKVFMCH1A780Q12SVG"
+ echo "UPDATE TalerIncomingPayments SET \"reservePublicKey\"='$NEW_WTID' WHERE payment='$OLD_ID';" \
+ | psql "${DB}" -q \
+ || exit_fail "Failed to update TalerIncomingPayments"
-run_audit
+ run_audit
-echo -n "Testing inconsistency detection... "
-DIAG=`jq -r .reserve_in_amount_inconsistencies[0].diagnostic < test-audit-wire.json`
-if test "x$DIAG" != "xwire subject does not match"
-then
- exit_fail "Diagnostic wrong: $DIAG (0)"
-fi
-WTID=`jq -r .reserve_in_amount_inconsistencies[0].reserve_pub < test-audit-wire.json`
-if test x$WTID != x"$OLD_WTID" -a x$WTID != x"$NEW_WTID"
-then
- exit_fail "WTID reported wrong: $WTID"
-fi
-EX_A=`jq -r .reserve_in_amount_inconsistencies[0].amount_exchange_expected < test-audit-wire.json`
-if test x$WTID = x$OLD_WTID -a x$EX_A != x"TESTKUDOS:10"
-then
- exit_fail "Amount reported wrong: $EX_A"
-fi
-if test x$WTID = x$NEW_WTID -a x$EX_A != x"TESTKUDOS:0"
-then
- exit_fail "Amount reported wrong: $EX_A"
-fi
-DIAG=`jq -r .reserve_in_amount_inconsistencies[1].diagnostic < test-audit-wire.json`
-if test "x$DIAG" != "xwire subject does not match"
-then
- exit_fail "Diagnostic wrong: $DIAG (1)"
-fi
-WTID=`jq -r .reserve_in_amount_inconsistencies[1].reserve_pub < test-audit-wire.json`
-if test $WTID != "$OLD_WTID" -a $WTID != "$NEW_WTID"
-then
- exit_fail "WTID reported wrong: $WTID (wanted: $NEW_WTID or $OLD_WTID)"
-fi
-EX_A=`jq -r .reserve_in_amount_inconsistencies[1].amount_exchange_expected < test-audit-wire.json`
-if test $WTID = "$OLD_WTID" -a $EX_A != "TESTKUDOS:10"
-then
- exit_fail "Amount reported wrong: $EX_A"
-fi
-if test $WTID = "$NEW_WTID" -a $EX_A != "TESTKUDOS:0"
-then
- exit_fail "Amount reported wrong: $EX_A"
-fi
+ echo -n "Testing inconsistency detection... "
+ DIAG=$(jq -r .reserve_in_amount_inconsistencies[0].diagnostic < test-audit-wire.json)
+ if [ "x$DIAG" != "xwire subject does not match" ]
+ then
+ exit_fail "Diagnostic wrong: $DIAG (0)"
+ fi
+ WTID=$(jq -r .reserve_in_amount_inconsistencies[0].reserve_pub < test-audit-wire.json)
+ if [ "$WTID" != "$OLD_WTID" ] && [ "$WTID" != "$NEW_WTID" ]
+ then
+ exit_fail "WTID reported wrong: $WTID (wanted $OLD_WTID or $NEW_WTID)"
+ fi
+ EX_A=$(jq -r .reserve_in_amount_inconsistencies[0].amount_exchange_expected < test-audit-wire.json)
+ if [ "$WTID" = "$OLD_WTID" ] && [ "$EX_A" != "TESTKUDOS:10" ]
+ then
+ exit_fail "Amount reported wrong: $EX_A"
+ fi
+ if [ "$WTID" = "$NEW_WTID" ] && [ "$EX_A" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Amount reported wrong: $EX_A"
+ fi
+ DIAG=$(jq -r .reserve_in_amount_inconsistencies[1].diagnostic < test-audit-wire.json)
+ if [ "$DIAG" != "wire subject does not match" ]
+ then
+ exit_fail "Diagnostic wrong: $DIAG (1)"
+ fi
+ WTID=$(jq -r .reserve_in_amount_inconsistencies[1].reserve_pub < test-audit-wire.json)
+ if [ "$WTID" != "$OLD_WTID" ] && [ "$WTID" != "$NEW_WTID" ]
+ then
+ exit_fail "WTID reported wrong: $WTID (wanted: $NEW_WTID or $OLD_WTID)"
+ fi
+ EX_A=$(jq -r .reserve_in_amount_inconsistencies[1].amount_exchange_expected < test-audit-wire.json)
+ if [ "$WTID" = "$OLD_WTID" ] && [ "$EX_A" != "TESTKUDOS:10" ]
+ then
+ exit_fail "Amount reported wrong: $EX_A"
+ fi
+ if [ "$WTID" = "$NEW_WTID" ] && [ "$EX_A" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Amount reported wrong: $EX_A"
+ fi
-WIRED=`jq -r .total_wire_in_delta_minus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:10"
-then
- exit_fail "Wrong total wire_in_delta_minus, got $WIRED"
-fi
-DELTA=`jq -r .total_wire_in_delta_plus < test-audit-wire.json`
-if test $DELTA != "TESTKUDOS:10"
-then
- exit_fail "Expected total wire delta plus wrong, got $DELTA"
-fi
-echo PASS
+ WIRED=$(jq -r .total_wire_in_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:10" ]
+ then
+ exit_fail "Wrong total wire_in_delta_minus, got $WIRED"
+ fi
+ DELTA=$(jq -r .total_wire_in_delta_plus < test-audit-wire.json)
+ if [ "$DELTA" != "TESTKUDOS:10" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $DELTA"
+ fi
+ echo "PASS"
-# Undo database modification
-echo "UPDATE app_banktransaction SET subject='$OLD_WTID' WHERE id='$OLD_ID';" | psql -Aqt $DB
+ # Undo database modification
+ echo "UPDATE TalerIncomingPayments SET \"reservePublicKey\"='$OLD_WTID' WHERE payment='$OLD_ID';" \
+ | psql "${DB}" -q
}
-
# Test wire origin disagreement!
function test_9() {
-echo "===========9: wire-origin disagreement==========="
-OLD_ID=`echo "SELECT id FROM app_banktransaction WHERE amount='TESTKUDOS:10' ORDER BY id LIMIT 1;" | psql $DB -Aqt`
-OLD_ACC=`echo "SELECT debit_account_id FROM app_banktransaction WHERE id='$OLD_ID';" | psql $DB -Aqt`
-echo "UPDATE app_banktransaction SET debit_account_id=1 WHERE id='$OLD_ID';" | psql -Aqt $DB
+ echo "===========9: wire-origin disagreement==========="
+ # Technically, this call shouldn't be needed, as libeufin should already be stopped here.
+ stop_libeufin
+ echo "FIXME: test needs update to new libeufin-bank schema"
+ exit 0
+ OLD_ID=$(echo "SELECT id FROM NexusBankTransactions WHERE amount='10' AND currency='TESTKUDOS' ORDER BY id LIMIT 1;" | psql "${DB}" -Aqt)
+ OLD_ACC=$(echo 'SELECT "incomingPaytoUri" FROM TalerIncomingPayments WHERE payment='"'$OLD_ID';" | psql "${DB}" -Aqt)
+ echo "UPDATE TalerIncomingPayments SET \"incomingPaytoUri\"='payto://iban/SANDBOXX/DE144373?receiver-name=New+Exchange+Company' WHERE payment='$OLD_ID';" \
+ | psql "${DB}" -q
-run_audit
+ run_audit
-echo -n "Testing inconsistency detection... "
-AMOUNT=`jq -r .missattribution_in_inconsistencies[0].amount < test-audit-wire.json`
-if test "x$AMOUNT" != "xTESTKUDOS:10"
-then
- exit_fail "Reported amount wrong: $AMOUNT"
-fi
-AMOUNT=`jq -r .total_missattribution_in < test-audit-wire.json`
-if test "x$AMOUNT" != "xTESTKUDOS:10"
-then
- exit_fail "Reported total amount wrong: $AMOUNT"
-fi
-echo PASS
+ echo -n "Testing inconsistency detection... "
+ AMOUNT=$(jq -r .misattribution_in_inconsistencies[0].amount < test-audit-wire.json)
+ if test "x$AMOUNT" != "xTESTKUDOS:10"
+ then
+ exit_fail "Reported amount wrong: $AMOUNT"
+ fi
+ AMOUNT=$(jq -r .total_misattribution_in < test-audit-wire.json)
+ if test "x$AMOUNT" != "xTESTKUDOS:10"
+ then
+ exit_fail "Reported total amount wrong: $AMOUNT"
+ fi
+ echo PASS
-# Undo database modification
-echo "UPDATE app_banktransaction SET debit_account_id=$OLD_ACC WHERE id='$OLD_ID';" | psql -Aqt $DB
+ # Undo database modification
+ echo "UPDATE TalerIncomingPayments SET \"incomingPaytoUri\"='$OLD_ACC' WHERE payment='$OLD_ID';" \
+ | psql "${DB}" -q
}
# Test wire_in timestamp disagreement!
function test_10() {
+ NOW_MS=$(date +%s)000
+ echo "===========10: wire-timestamp disagreement==========="
+ # Technically, this call shouldn't be needed, as libeufin should already be stopped here.
+ stop_libeufin
+ echo "FIXME: test needs update to new libeufin-bank schema"
+ exit 0
+ OLD_ID=$(echo "SELECT id FROM NexusBankTransactions WHERE amount='10' AND currency='TESTKUDOS' ORDER BY id LIMIT 1;" | psql "${DB}" -Aqt)
+ OLD_DATE=$(echo "SELECT \"timestampMs\" FROM TalerIncomingPayments WHERE payment='$OLD_ID';" | psql "${DB}" -Aqt)
+ echo "UPDATE TalerIncomingPayments SET \"timestampMs\"=$NOW_MS WHERE payment=$OLD_ID;" | psql "${DB}" -q
-echo "===========10: wire-timestamp disagreement==========="
-OLD_ID=`echo "SELECT id FROM app_banktransaction WHERE amount='TESTKUDOS:10' ORDER BY id LIMIT 1;" | psql $DB -Aqt`
-OLD_DATE=`echo "SELECT date FROM app_banktransaction WHERE id='$OLD_ID';" | psql $DB -Aqt`
-echo "UPDATE app_banktransaction SET date=NOW() WHERE id=$OLD_ID;" | psql -Aqt $DB
-
-run_audit
+ run_audit
-echo -n "Testing inconsistency detection... "
-DIAG=`jq -r .row_minor_inconsistencies[0].diagnostic < test-audit-wire.json`
-if test "x$DIAG" != "xexecution date mismatch"
-then
- exit_fail "Reported diagnostic wrong: $DIAG"
-fi
-TABLE=`jq -r .row_minor_inconsistencies[0].table < test-audit-wire.json`
-if test "x$TABLE" != "xreserves_in"
-then
- exit_fail "Reported table wrong: $TABLE"
-fi
-echo PASS
+ echo -n "Testing inconsistency detection... "
+ DIAG=$(jq -r .row_minor_inconsistencies[0].diagnostic < test-audit-wire.json)
+ if test "x$DIAG" != "xexecution date mismatch"
+ then
+ exit_fail "Reported diagnostic wrong: $DIAG"
+ fi
+ TABLE=$(jq -r .row_minor_inconsistencies[0].table < test-audit-wire.json)
+ if test "x$TABLE" != "xreserves_in"
+ then
+ exit_fail "Reported table wrong: $TABLE"
+ fi
+ echo "PASS"
-# Undo database modification
-echo "UPDATE app_banktransaction SET date='$OLD_DATE' WHERE id=$OLD_ID;" | psql -Aqt $DB
+ # Undo database modification
+ echo "UPDATE TalerIncomingPayments SET \"timestampMs\"='$OLD_DATE' WHERE payment=$OLD_ID;" | psql "${DB}" -q
}
# Test for extra outgoing wire transfer.
+# In case of changing the subject in the Nexus
+# ingested table: '.batches[0].batchTransactions[0].details.unstructuredRemittanceInformation'
function test_11() {
+ echo "===========11: spurious outgoing transfer ==========="
+ # Technically, this call shouldn't be needed, as libeufin should already be stopped here.
+ stop_libeufin
+ echo "FIXME: test needs update to new libeufin-bank schema"
+ exit 0
+ OLD_ID=$(echo "SELECT id FROM NexusBankTransactions WHERE amount='10' AND currency='TESTKUDOS' ORDER BY id LIMIT 1;" | psql "${DB}" -Aqt)
+ OLD_TX=$(echo "SELECT \"transactionJson\" FROM NexusBankTransactions WHERE id='$OLD_ID';" | psql "${DB}" -Aqt)
+ # Change wire transfer to be FROM the exchange (#2) to elsewhere!
+ # (Note: this change also causes a missing incoming wire transfer, but
+ # this test is only concerned about the outgoing wire transfer
+ # being detected as such, and we simply ignore the other
+ # errors being reported.)
+ OTHER_IBAN=$(echo -e "SELECT iban FROM BankAccounts WHERE label='fortytwo'" | psql "${DB}" -Aqt)
+ NEW_TX=$(echo "$OLD_TX" | jq .batches[0].batchTransactions[0].details.creditDebitIndicator='"DBIT"' | jq 'del(.batches[0].batchTransactions[0].details.debtor)' | jq 'del(.batches[0].batchTransactions[0].details.debtorAccount)' | jq 'del(.batches[0].batchTransactions[0].details.debtorAgent)' | jq '.batches[0].batchTransactions[0].details.creditor'='{"name": "Forty Two"}' | jq .batches[0].batchTransactions[0].details.creditorAccount='{"iban": "'"$OTHER_IBAN"'"}' | jq .batches[0].batchTransactions[0].details.creditorAgent='{"bic": "SANDBOXX"}' | jq .batches[0].batchTransactions[0].details.unstructuredRemittanceInformation='"CK9QBFY972KR32FVA1MW958JWACEB6XCMHHKVFMCH1A780Q12SVG http://exchange.example.com/"')
+ echo -e "UPDATE NexusBankTransactions SET \"transactionJson\"='""$NEW_TX""' WHERE id=$OLD_ID" \
+ | psql "${DB}" -q
+ # Now fake that the exchange prepared this payment (= it POSTed to /transfer)
+ # This step is necessary, because the TWG table that accounts for outgoing
+ # payments needs it. Worth noting here is the column 'rawConfirmation' that
+ # points to the transaction from the main Nexus ledger; without that column set,
+ # a prepared payment won't appear as actually outgoing.
+ echo -e "INSERT INTO PaymentInitiations (\"bankAccount\",\"preparationDate\",\"submissionDate\",sum,currency,\"endToEndId\",\"paymentInformationId\",\"instructionId\",subject,\"creditorIban\",\"creditorBic\",\"creditorName\",submitted,\"messageId\",\"rawConfirmation\") VALUES (1,1,1,10,'TESTKUDOS','NOTGIVEN','unused','unused','CK9QBFY972KR32FVA1MW958JWACEB6XCMHHKVFMCH1A780Q12SVG http://exchange.example.com/','""$OTHER_IBAN""','SANDBOXX','Forty Two',false,1,$OLD_ID)" \
+ | psql "${DB}" -q
+ # Now populate the TWG table that accounts for outgoing payments, in
+ # order to let /history/outgoing return one result.
+ echo -e "INSERT INTO TalerRequestedPayments (facade,payment,\"requestUid\",amount,\"exchangeBaseUrl\",wtid,\"creditAccount\") VALUES (1,1,'unused','TESTKUDOS:10','http://exchange.example.com/','CK9QBFY972KR32FVA1MW958JWACEB6XCMHHKVFMCH1A780Q12SVG','payto://iban/SANDBOXX/""$OTHER_IBAN""?receiver-name=Forty+Two')" \
+ | psql "${DB}" -q
-echo "===========11: spurious outgoing transfer ==========="
-OLD_ID=`echo "SELECT id FROM app_banktransaction WHERE amount='TESTKUDOS:10' ORDER BY id LIMIT 1;" | psql $DB -Aqt`
-OLD_ACC=`echo "SELECT debit_account_id FROM app_banktransaction WHERE id=$OLD_ID;" | psql $DB -Aqt`
-OLD_SUBJECT=`echo "SELECT subject FROM app_banktransaction WHERE id=$OLD_ID;" | psql $DB -Aqt`
-# Change wire transfer to be FROM the exchange (#2) to elsewhere!
-# (Note: this change also causes a missing incoming wire transfer, but
-# this test is only concerned about the outgoing wire transfer
-# being detected as such, and we simply ignore the other
-# errors being reported.)
-echo -e "UPDATE app_banktransaction SET debit_account_id=2,credit_account_id=1,subject='CK9QBFY972KR32FVA1MW958JWACEB6XCMHHKVFMCH1A780Q12SVG http://exchange.example.com/' WHERE id=$OLD_ID;" | psql -Aqt $DB
-
-run_audit
-
-echo -n "Testing inconsistency detection... "
-AMOUNT=`jq -r .wire_out_amount_inconsistencies[0].amount_wired < test-audit-wire.json`
-if test "x$AMOUNT" != "xTESTKUDOS:10"
-then
- exit_fail "Reported wired amount wrong: $AMOUNT"
-fi
-AMOUNT=`jq -r .total_wire_out_delta_plus < test-audit-wire.json`
-if test "x$AMOUNT" != "xTESTKUDOS:10"
-then
- exit_fail "Reported total plus amount wrong: $AMOUNT"
-fi
-AMOUNT=`jq -r .total_wire_out_delta_minus < test-audit-wire.json`
-if test "x$AMOUNT" != "xTESTKUDOS:0"
-then
- exit_fail "Reported total minus amount wrong: $AMOUNT"
-fi
-AMOUNT=`jq -r .wire_out_amount_inconsistencies[0].amount_justified < test-audit-wire.json`
-if test "x$AMOUNT" != "xTESTKUDOS:0"
-then
- exit_fail "Reported justified amount wrong: $AMOUNT"
-fi
-DIAG=`jq -r .wire_out_amount_inconsistencies[0].diagnostic < test-audit-wire.json`
-if test "x$DIAG" != "xjustification for wire transfer not found"
-then
- exit_fail "Reported diagnostic wrong: $DIAG"
-fi
-echo PASS
+ run_audit
-# Undo database modification (exchange always has account #2)
-echo "UPDATE app_banktransaction SET debit_account_id=$OLD_ACC,credit_account_id=2,subject='$OLD_SUBJECT' WHERE id=$OLD_ID;" | psql -Aqt $DB
+ echo -n "Testing inconsistency detection... "
+ AMOUNT=$(jq -r .wire_out_amount_inconsistencies[0].amount_wired < test-audit-wire.json)
+ if [ "x$AMOUNT" != "xTESTKUDOS:10" ]
+ then
+ exit_fail "Reported wired amount wrong: $AMOUNT"
+ fi
+ AMOUNT=$(jq -r .total_wire_out_delta_plus < test-audit-wire.json)
+ if [ "x$AMOUNT" != "xTESTKUDOS:10" ]
+ then
+ exit_fail "Reported total plus amount wrong: $AMOUNT"
+ fi
+ AMOUNT=$(jq -r .total_wire_out_delta_minus < test-audit-wire.json)
+ if [ "x$AMOUNT" != "xTESTKUDOS:0" ]
+ then
+ exit_fail "Reported total minus amount wrong: $AMOUNT"
+ fi
+ AMOUNT=$(jq -r .wire_out_amount_inconsistencies[0].amount_justified < test-audit-wire.json)
+ if [ "x$AMOUNT" != "xTESTKUDOS:0" ]
+ then
+ exit_fail "Reported justified amount wrong: $AMOUNT"
+ fi
+ DIAG=$(jq -r .wire_out_amount_inconsistencies[0].diagnostic < test-audit-wire.json)
+ if [ "x$DIAG" != "xjustification for wire transfer not found" ]
+ then
+ exit_fail "Reported diagnostic wrong: $DIAG"
+ fi
+ echo "PASS"
+ full_reload
}
-
# Test for hanging/pending refresh.
function test_12() {
-echo "===========12: incomplete refresh ==========="
-OLD_ACC=`echo "DELETE FROM refresh_revealed_coins;" | psql $DB -Aqt`
-
-run_audit
-
-echo -n "Testing hung refresh detection... "
-
-HANG=`jq -er .refresh_hanging[0].amount < test-audit-coins.json`
-TOTAL_HANG=`jq -er .total_refresh_hanging < test-audit-coins.json`
-if test x$HANG = TESTKUDOS:0
-then
- exit_fail "Hanging amount zero"
-fi
-if test x$TOTAL_HANG = TESTKUDOS:0
-then
- exit_fail "Total hanging amount zero"
-fi
+ echo "===========12: incomplete refresh ==========="
+ OLD_ACC=$(echo "DELETE FROM exchange.refresh_revealed_coins;" | psql "$DB" -Aqt)
-echo PASS
+ run_audit
+ echo -n "Testing hung refresh detection... "
-# cannot easily undo DELETE, hence full reload
-full_reload
+ HANG=$(jq -er .refresh_hanging[0].amount < test-audit-coins.json)
+ TOTAL_HANG=$(jq -er .total_refresh_hanging < test-audit-coins.json)
+ if [ "$HANG" = "TESTKUDOS:0" ]
+ then
+ exit_fail "Hanging amount zero"
+ fi
+ if [ "$TOTAL_HANG" = "TESTKUDOS:0" ]
+ then
+ exit_fail "Total hanging amount zero"
+ fi
+ echo "PASS"
+ # cannot easily undo DELETE, hence full reload
+ full_reload
}
# Test for wrong signature on refresh.
function test_13() {
-echo "===========13: wrong melt signature ==========="
-# Modify denom_sig, so it is wrong
-COIN_PUB=`echo "SELECT old_coin_pub FROM refresh_commitments LIMIT 1;" | psql $DB -Aqt`
-OLD_SIG=`echo "SELECT old_coin_sig FROM refresh_commitments WHERE old_coin_pub='$COIN_PUB';" | psql $DB -Aqt`
-NEW_SIG="\xba588af7c13c477dca1ac458f65cc484db8fba53b969b873f4353ecbd815e6b4c03f42c0cb63a2b609c2d726e612fd8e0c084906a41f409b6a23a08a83c89a02"
-echo "UPDATE refresh_commitments SET old_coin_sig='$NEW_SIG' WHERE old_coin_pub='$COIN_PUB'" | psql -Aqt $DB
+ echo "===========13: wrong melt signature ==========="
+ # Modify denom_sig, so it is wrong
+ COIN_PUB=$(echo "SELECT old_coin_pub FROM exchange.refresh_commitments LIMIT 1;" | psql "$DB" -Aqt)
+ OLD_SIG=$(echo "SELECT old_coin_sig FROM exchange.refresh_commitments WHERE old_coin_pub='$COIN_PUB';" | psql "$DB" -Aqt)
+ NEW_SIG="\xba588af7c13c477dca1ac458f65cc484db8fba53b969b873f4353ecbd815e6b4c03f42c0cb63a2b609c2d726e612fd8e0c084906a41f409b6a23a08a83c89a02"
+ echo "UPDATE exchange.refresh_commitments SET old_coin_sig='$NEW_SIG' WHERE old_coin_pub='$COIN_PUB'" \
+ | psql -Aqt "$DB"
-run_audit
+ run_audit
-echo -n "Testing inconsistency detection... "
+ echo -n "Testing inconsistency detection... "
-OP=`jq -er .bad_sig_losses[0].operation < test-audit-coins.json`
-if test x$OP != xmelt
-then
- exit_fail "Operation wrong, got $OP"
-fi
+ OP=$(jq -er .bad_sig_losses[0].operation < test-audit-coins.json)
+ if [ "$OP" != "melt" ]
+ then
+ exit_fail "Operation wrong, got $OP"
+ fi
-LOSS=`jq -er .bad_sig_losses[0].loss < test-audit-coins.json`
-TOTAL_LOSS=`jq -er .total_bad_sig_loss < test-audit-coins.json`
-if test x$LOSS != x$TOTAL_LOSS
-then
- exit_fail "Loss inconsistent, got $LOSS and $TOTAL_LOSS"
-fi
-if test x$TOTAL_LOSS = TESTKUDOS:0
-then
- exit_fail "Loss zero"
-fi
+ LOSS=$(jq -er .bad_sig_losses[0].loss < test-audit-coins.json)
+ TOTAL_LOSS=$(jq -er .irregular_loss < test-audit-coins.json)
+ if [ "$LOSS" != "$TOTAL_LOSS" ]
+ then
+ exit_fail "Loss inconsistent, got $LOSS and $TOTAL_LOSS"
+ fi
+ if [ "$TOTAL_LOSS" = "TESTKUDOS:0" ]
+ then
+ exit_fail "Loss zero"
+ fi
-echo PASS
+ echo "PASS"
-# cannot easily undo DELETE, hence full reload
-full_reload
+ # cannot easily undo DELETE, hence full reload
+ full_reload
}
# Test for wire fee disagreement
function test_14() {
-echo "===========14: wire-fee disagreement==========="
-
-# Check wire transfer lag reported (no aggregator!)
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-wire-auditor.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
+ echo "===========14: wire-fee disagreement==========="
# Wire fees are only checked/generated once there are
# actual outgoing wire transfers, so we need to run the
# aggregator here.
pre_audit aggregator
- echo "UPDATE wire_fee SET wire_fee_frac=100;" | psql -Aqt $DB
+ echo "UPDATE exchange.wire_fee SET wire_fee_frac=100;" \
+ | psql -Aqt "$DB"
audit_only
post_audit
echo -n "Testing inconsistency detection... "
- TABLE=`jq -r .row_inconsistencies[0].table < test-audit-aggregation.json`
- if test "x$TABLE" != "xwire-fee"
+ TABLE=$(jq -r .row_inconsistencies[0].table < test-audit-aggregation.json)
+ if [ "$TABLE" != "wire-fee" ]
then
exit_fail "Reported table wrong: $TABLE"
fi
- DIAG=`jq -r .row_inconsistencies[0].diagnostic < test-audit-aggregation.json`
- if test "x$DIAG" != "xwire fee signature invalid at given time"
+ DIAG=$(jq -r .row_inconsistencies[0].diagnostic < test-audit-aggregation.json)
+ if [ "$DIAG" != "wire fee signature invalid at given time" ]
then
exit_fail "Reported diagnostic wrong: $DIAG"
fi
- echo PASS
+ echo "PASS"
# cannot easily undo aggregator, hence full reload
full_reload
-
-else
- echo "Test skipped (database too new)"
-fi
-
}
-
-# Test where h_wire in the deposit table is wrong
+# Test where salt in the deposit table is wrong
function test_15() {
-echo "===========15: deposit wire hash wrong================="
+ echo "===========15: deposit wire salt wrong================="
-# Check wire transfer lag reported (no aggregator!)
+ # Modify wire_salt hash, so it is inconsistent
+ SALT=$(echo "SELECT wire_salt FROM exchange.deposits WHERE deposit_serial_id=1;" | psql -Aqt "$DB")
+# shellcheck disable=SC2028
+ echo "UPDATE exchange.deposits SET wire_salt='\x1197cd7f7b0e13ab1905fedb36c536a2' WHERE deposit_serial_id=1;" \
+ | psql -Aqt "$DB"
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-helper-auditor-wire.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
-
- # Modify h_wire hash, so it is inconsistent with 'wire'
- echo "UPDATE deposits SET h_wire='\x973e52d193a357940be9ef2939c19b0575ee1101f52188c3c01d9005b7d755c397e92624f09cfa709104b3b65605fe5130c90d7e1b7ee30f8fc570f39c16b853' WHERE deposit_serial_id=1" | psql -Aqt $DB
-
- # The auditor checks h_wire consistency only for
- # coins where the wire transfer has happened, hence
- # run aggregator first to get this test to work.
- run_audit aggregator
+ run_audit
echo -n "Testing inconsistency detection... "
- TABLE=`jq -r .row_inconsistencies[0].table < test-audit-aggregation.json`
- if test "x$TABLE" != "xaggregation" -a "x$TABLE" != "xdeposits"
+ OP=$(jq -r .bad_sig_losses[0].operation < test-audit-coins.json)
+ if [ "$OP" != "deposit" ]
then
- exit_fail "Reported table wrong: $TABLE"
+ exit_fail "Reported operation wrong: $OP"
fi
- echo PASS
+ echo "PASS"
- # cannot easily undo aggregator, hence full reload
- full_reload
+ # Restore DB
+ echo "UPDATE exchange.deposits SET wire_salt='$SALT' WHERE deposit_serial_id=1;" \
+ | psql -Aqt "$DB"
-else
- echo "Test skipped (database too new)"
-fi
}
# Test where wired amount (wire out) is wrong
function test_16() {
-echo "===========16: incorrect wire_out amount================="
-
-# Check wire transfer lag reported (no aggregator!)
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-helper-auditor-wire.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
+ echo "===========16: incorrect wire_out amount================="
+
+ # Check wire transfer lag reported (no aggregator!)
# First, we need to run the aggregator so we even
# have a wire_out to modify.
pre_audit aggregator
- # Modify wire amount, such that it is inconsistent with 'aggregation'
- # (exchange account is #2, so the logic below should select the outgoing
- # wire transfer):
- OLD_ID=`echo "SELECT id FROM app_banktransaction WHERE debit_account_id=2 ORDER BY id LIMIT 1;" | psql $DB -Aqt`
- OLD_AMOUNT=`echo "SELECT amount FROM app_banktransaction WHERE id='${OLD_ID}';" | psql $DB -Aqt`
+ stop_libeufin
+ OLD_AMOUNT=$(echo "SELECT amount FROM TalerRequestedPayments WHERE id='1';" | psql "${DB}" -Aqt)
NEW_AMOUNT="TESTKUDOS:50"
- echo "UPDATE app_banktransaction SET amount='${NEW_AMOUNT}' WHERE id='${OLD_ID}';" | psql -Aqt $DB
-
+ echo "UPDATE TalerRequestedPayments SET amount='${NEW_AMOUNT}' WHERE id='1';" \
+ | psql "${DB}" -q
+ launch_libeufin
audit_only
echo -n "Testing inconsistency detection... "
- AMOUNT=`jq -r .wire_out_amount_inconsistencies[0].amount_justified < test-audit-wire.json`
- if test "x$AMOUNT" != "x$OLD_AMOUNT"
+ AMOUNT=$(jq -r .wire_out_amount_inconsistencies[0].amount_justified < test-audit-wire.json)
+ if [ "$AMOUNT" != "$OLD_AMOUNT" ]
then
exit_fail "Reported justified amount wrong: $AMOUNT"
fi
- AMOUNT=`jq -r .wire_out_amount_inconsistencies[0].amount_wired < test-audit-wire.json`
- if test "x$AMOUNT" != "x$NEW_AMOUNT"
+ AMOUNT=$(jq -r .wire_out_amount_inconsistencies[0].amount_wired < test-audit-wire.json)
+ if [ "$AMOUNT" != "$NEW_AMOUNT" ]
then
exit_fail "Reported wired amount wrong: $AMOUNT"
fi
- TOTAL_AMOUNT=`jq -r .total_wire_out_delta_minus < test-audit-wire.json`
- if test "x$TOTAL_AMOUNT" != "xTESTKUDOS:0"
+ TOTAL_AMOUNT=$(jq -r .total_wire_out_delta_minus < test-audit-wire.json)
+ if [ "$TOTAL_AMOUNT" != "TESTKUDOS:0" ]
then
exit_fail "Reported total wired amount minus wrong: $TOTAL_AMOUNT"
fi
- TOTAL_AMOUNT=`jq -r .total_wire_out_delta_plus < test-audit-wire.json`
- if test "x$TOTAL_AMOUNT" = "xTESTKUDOS:0"
+ TOTAL_AMOUNT=$(jq -r .total_wire_out_delta_plus < test-audit-wire.json)
+ if [ "$TOTAL_AMOUNT" = "TESTKUDOS:0" ]
then
exit_fail "Reported total wired amount plus wrong: $TOTAL_AMOUNT"
fi
- echo PASS
+ echo "PASS"
+ stop_libeufin
echo "Second modification: wire nothing"
NEW_AMOUNT="TESTKUDOS:0"
- echo "UPDATE app_banktransaction SET amount='${NEW_AMOUNT}' WHERE id='${OLD_ID}';" | psql -Aqt $DB
-
+ echo "UPDATE TalerRequestedPayments SET amount='${NEW_AMOUNT}' WHERE id='1';" \
+ | psql "${DB}" -q
+ launch_libeufin
audit_only
-
+ stop_libeufin
echo -n "Testing inconsistency detection... "
- AMOUNT=`jq -r .wire_out_amount_inconsistencies[0].amount_justified < test-audit-wire.json`
- if test "x$AMOUNT" != "x$OLD_AMOUNT"
+ AMOUNT=$(jq -r .wire_out_amount_inconsistencies[0].amount_justified < test-audit-wire.json)
+ if [ "$AMOUNT" != "$OLD_AMOUNT" ]
then
exit_fail "Reported justified amount wrong: $AMOUNT"
fi
- AMOUNT=`jq -r .wire_out_amount_inconsistencies[0].amount_wired < test-audit-wire.json`
- if test "x$AMOUNT" != "x$NEW_AMOUNT"
+ AMOUNT=$(jq -r .wire_out_amount_inconsistencies[0].amount_wired < test-audit-wire.json)
+ if [ "$AMOUNT" != "$NEW_AMOUNT" ]
then
exit_fail "Reported wired amount wrong: $AMOUNT"
fi
- TOTAL_AMOUNT=`jq -r .total_wire_out_delta_minus < test-audit-wire.json`
- if test "x$TOTAL_AMOUNT" != "x$OLD_AMOUNT"
+ TOTAL_AMOUNT=$(jq -r .total_wire_out_delta_minus < test-audit-wire.json)
+ if [ "$TOTAL_AMOUNT" != "$OLD_AMOUNT" ]
then
exit_fail "Reported total wired amount minus wrong: $TOTAL_AMOUNT (wanted $OLD_AMOUNT)"
fi
- TOTAL_AMOUNT=`jq -r .total_wire_out_delta_plus < test-audit-wire.json`
- if test "x$TOTAL_AMOUNT" != "xTESTKUDOS:0"
+ TOTAL_AMOUNT=$(jq -r .total_wire_out_delta_plus < test-audit-wire.json)
+ if [ "$TOTAL_AMOUNT" != "TESTKUDOS:0" ]
then
exit_fail "Reported total wired amount plus wrong: $TOTAL_AMOUNT"
fi
- echo PASS
+ echo "PASS"
post_audit
# cannot easily undo aggregator, hence full reload
full_reload
-else
- echo "Test skipped (database too new)"
-fi
-
}
-
# Test where wire-out timestamp is wrong
function test_17() {
-echo "===========17: incorrect wire_out timestamp================="
-
-# Check wire transfer lag reported (no aggregator!)
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-helper-auditor-wire.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
+ echo "===========17: incorrect wire_out timestamp================="
# First, we need to run the aggregator so we even
# have a wire_out to modify.
pre_audit aggregator
-
- # Modify wire amount, such that it is inconsistent with 'aggregation'
- # (exchange account is #2, so the logic below should select the outgoing
- # wire transfer):
- OLD_ID=`echo "SELECT id FROM app_banktransaction WHERE debit_account_id=2 ORDER BY id LIMIT 1;" | psql $DB -Aqt`
- OLD_DATE=`echo "SELECT date FROM app_banktransaction WHERE id='${OLD_ID}';" | psql $DB -Aqt`
+ stop_libeufin
+ OLD_ID=1
+ OLD_PREP=$(echo "SELECT payment FROM TalerRequestedPayments WHERE id='${OLD_ID}';" | psql "${DB}" -Aqt)
+ OLD_DATE=$(echo "SELECT \"preparationDate\" FROM PaymentInitiations WHERE id='${OLD_ID}';" | psql "${DB}" -Aqt)
# Note: need - interval '1h' as "NOW()" may otherwise be exactly what is already in the DB
# (due to rounding, if this machine is fast...)
- echo "UPDATE app_banktransaction SET date=NOW()- interval '1 hour' WHERE id='${OLD_ID}';" | psql -Aqt $DB
-
+ NOW_1HR=$(( $(date +%s) - 3600))
+ echo "UPDATE PaymentInitiations SET \"preparationDate\"='$NOW_1HR' WHERE id='${OLD_PREP}';" \
+ | psql "${DB}" -q
+ launch_libeufin
+ echo "DONE"
audit_only
post_audit
echo -n "Testing inconsistency detection... "
- TABLE=`jq -r .row_minor_inconsistencies[0].table < test-audit-wire.json`
- if test "x$TABLE" != "xwire_out"
+ TABLE=$(jq -r .row_minor_inconsistencies[0].table < test-audit-wire.json)
+ if [ "$TABLE" != "wire_out" ]
then
exit_fail "Reported table wrong: $TABLE"
fi
- DIAG=`jq -r .row_minor_inconsistencies[0].diagnostic < test-audit-wire.json`
- DIAG=`echo "$DIAG" | awk '{print $1 " " $2 " " $3}'`
- if test "x$DIAG" != "xexecution date mismatch"
+ DIAG=$(jq -r .row_minor_inconsistencies[0].diagnostic < test-audit-wire.json)
+ DIAG=$(echo "$DIAG" | awk '{print $1 " " $2 " " $3}')
+ if [ "$DIAG" != "execution date mismatch" ]
then
exit_fail "Reported diagnostic wrong: $DIAG"
fi
- echo PASS
+ echo "PASS"
# cannot easily undo aggregator, hence full reload
full_reload
-
-else
- echo "Test skipped (database too new)"
-fi
-
}
@@ -1119,64 +1342,67 @@ fi
# Test where we trigger an emergency.
function test_18() {
-echo "===========18: emergency================="
-
-echo "DELETE FROM reserves_out;" | psql -Aqt $DB
+ echo "===========18: emergency================="
-run_audit
+ echo "DELETE FROM exchange.reserves_out;" \
+ | psql -Aqt "$DB" -q
-echo -n "Testing emergency detection... "
-
-jq -e .reserve_balance_summary_wrong_inconsistencies[0] < test-audit-reserves.json > /dev/null || exit_fail "Reserve balance inconsistency not detected"
-
-jq -e .emergencies[0] < test-audit-coins.json > /dev/null || exit_fail "Emergency not detected"
-jq -e .emergencies_by_count[0] < test-audit-coins.json > /dev/null || exit_fail "Emergency by count not detected"
-jq -e .amount_arithmetic_inconsistencies[0] < test-audit-coins.json > /dev/null || exit_fail "Escrow balance calculation impossibility not detected"
-
-echo PASS
-
-echo -n "Testing loss calculation... "
+ run_audit
-AMOUNT=`jq -r .emergencies_loss < test-audit-coins.json`
-if test "x$AMOUNT" == "xTESTKUDOS:0"
-then
- exit_fail "Reported amount wrong: $AMOUNT"
-fi
-AMOUNT=`jq -r .emergencies_loss_by_count < test-audit-coins.json`
-if test "x$AMOUNT" == "xTESTKUDOS:0"
-then
- exit_fail "Reported amount wrong: $AMOUNT"
-fi
+ echo -n "Testing emergency detection... "
+ jq -e .reserve_balance_summary_wrong_inconsistencies[0] \
+ < test-audit-reserves.json \
+ > /dev/null \
+ || exit_fail "Reserve balance inconsistency not detected"
+ jq -e .emergencies[0] \
+ < test-audit-coins.json \
+ > /dev/null \
+ || exit_fail "Emergency not detected"
+ jq -e .emergencies_by_count[0] \
+ < test-audit-coins.json \
+ > /dev/null \
+ || exit_fail "Emergency by count not detected"
+ jq -e .amount_arithmetic_inconsistencies[0] \
+ < test-audit-coins.json \
+ > /dev/null \
+ || exit_fail "Escrow balance calculation impossibility not detected"
+ echo "PASS"
-echo PASS
+ echo -n "Testing loss calculation... "
+ AMOUNT=$(jq -r .emergencies_loss < test-audit-coins.json)
+ if [ "$AMOUNT" == "TESTKUDOS:0" ]
+ then
+ exit_fail "Reported amount wrong: $AMOUNT"
+ fi
+ AMOUNT=$(jq -r .emergencies_loss_by_count < test-audit-coins.json)
+ if [ "$AMOUNT" == "TESTKUDOS:0" ]
+ then
+ exit_fail "Reported amount wrong: $AMOUNT"
+ fi
+ echo "PASS"
-# cannot easily undo broad DELETE operation, hence full reload
-full_reload
+ # cannot easily undo broad DELETE operation, hence full reload
+ full_reload
}
# Test where reserve closure was done properly
function test_19() {
-echo "===========19: reserve closure done properly ================="
+ echo "===========19: reserve closure done properly ================="
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-helper-auditor-wire.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
-
- OLD_TIME=`echo "SELECT execution_date FROM reserves_in WHERE reserve_in_serial_id=1;" | psql $DB -Aqt`
- OLD_VAL=`echo "SELECT credit_val FROM reserves_in WHERE reserve_in_serial_id=1;" | psql $DB -Aqt`
- RES_PUB=`echo "SELECT reserve_pub FROM reserves_in WHERE reserve_in_serial_id=1;" | psql $DB -Aqt`
- OLD_EXP=`echo "SELECT expiration_date FROM reserves WHERE reserve_pub='${RES_PUB}';" | psql $DB -Aqt`
+ OLD_TIME=$(echo "SELECT execution_date FROM exchange.reserves_in WHERE reserve_in_serial_id=1;" | psql "$DB" -Aqt)
+ OLD_VAL=$(echo "SELECT credit_val FROM exchange.reserves_in WHERE reserve_in_serial_id=1;" | psql "$DB" -Aqt)
+ RES_PUB=$(echo "SELECT reserve_pub FROM exchange.reserves_in WHERE reserve_in_serial_id=1;" | psql "$DB" -Aqt)
+ OLD_EXP=$(echo "SELECT expiration_date FROM exchange.reserves WHERE reserve_pub='${RES_PUB}';" | psql "$DB" -Aqt)
VAL_DELTA=1
- NEW_TIME=`expr $OLD_TIME - 3024000000000 || true` # 5 weeks
- NEW_EXP=`expr $OLD_EXP - 3024000000000 || true` # 5 weeks
- NEW_CREDIT=`expr $OLD_VAL + $VAL_DELTA || true`
- echo "UPDATE reserves_in SET execution_date='${NEW_TIME}',credit_val=${NEW_CREDIT} WHERE reserve_in_serial_id=1;" | psql -Aqt $DB
- echo "UPDATE reserves SET current_balance_val=${VAL_DELTA}+current_balance_val,expiration_date='${NEW_EXP}' WHERE reserve_pub='${RES_PUB}';" | psql -Aqt $DB
+ NEW_TIME=$(( OLD_TIME - 3024000000000)) # 5 weeks
+ NEW_EXP=$(( OLD_EXP - 3024000000000)) # 5 weeks
+ NEW_CREDIT=$(( OLD_VAL + VAL_DELTA))
+ echo "UPDATE exchange.reserves_in SET execution_date='${NEW_TIME}',credit_val=${NEW_CREDIT} WHERE reserve_in_serial_id=1;" \
+ | psql -Aqt "$DB"
+ echo "UPDATE exchange.reserves SET current_balance_val=${VAL_DELTA}+current_balance_val,expiration_date='${NEW_EXP}' WHERE reserve_pub='${RES_PUB}';" \
+ | psql -Aqt "$DB"
# Need to run with the aggregator so the reserve closure happens
run_audit aggregator
@@ -1194,89 +1420,90 @@ then
# cannot easily undo aggregator, hence full reload
full_reload
-
-else
- echo "Test skipped (database too new)"
-fi
}
# Test where reserve closure was not done properly
function test_20() {
-echo "===========20: reserve closure missing ================="
-
-OLD_TIME=`echo "SELECT execution_date FROM reserves_in WHERE reserve_in_serial_id=1;" | psql $DB -Aqt`
-OLD_VAL=`echo "SELECT credit_val FROM reserves_in WHERE reserve_in_serial_id=1;" | psql $DB -Aqt`
-RES_PUB=`echo "SELECT reserve_pub FROM reserves_in WHERE reserve_in_serial_id=1;" | psql $DB -Aqt`
-NEW_TIME=`expr $OLD_TIME - 3024000000000 || true` # 5 weeks
-NEW_CREDIT=`expr $OLD_VAL + 100 || true`
-echo "UPDATE reserves_in SET execution_date='${NEW_TIME}',credit_val=${NEW_CREDIT} WHERE reserve_in_serial_id=1;" | psql -Aqt $DB
-echo "UPDATE reserves SET current_balance_val=100+current_balance_val WHERE reserve_pub='${RES_PUB}';" | psql -Aqt $DB
-
-# This time, run without the aggregator so the reserve closure is skipped!
-run_audit
+ echo "===========20: reserve closure missing ================="
+
+ OLD_TIME=$(echo "SELECT execution_date FROM exchange.reserves_in WHERE reserve_in_serial_id=1;" | psql "$DB" -Aqt)
+ OLD_VAL=$(echo "SELECT credit_val FROM exchange.reserves_in WHERE reserve_in_serial_id=1;" | psql "$DB" -Aqt)
+ RES_PUB=$(echo "SELECT reserve_pub FROM exchange.reserves_in WHERE reserve_in_serial_id=1;" | psql "$DB" -Aqt)
+ NEW_TIME=$(( OLD_TIME - 3024000000000 )) # 5 weeks
+ NEW_CREDIT=$(( OLD_VAL + 100 ))
+ echo "UPDATE exchange.reserves_in SET execution_date='${NEW_TIME}',credit_val=${NEW_CREDIT} WHERE reserve_in_serial_id=1;" \
+ | psql -Aqt "$DB"
+ echo "UPDATE exchange.reserves SET current_balance_val=100+current_balance_val WHERE reserve_pub='${RES_PUB}';" \
+ | psql -Aqt "$DB"
+
+ # This time, run without the aggregator so the reserve closure is skipped!
+ run_audit
-echo -n "Testing reserve closure missing detected... "
-jq -e .reserve_not_closed_inconsistencies[0] < test-audit-reserves.json > /dev/null || exit_fail "Reserve not closed inconsistency not detected"
-echo "PASS"
+ echo -n "Testing reserve closure missing detected... "
+ jq -e .reserve_not_closed_inconsistencies[0] \
+ < test-audit-reserves.json \
+ > /dev/null \
+ || exit_fail "Reserve not closed inconsistency not detected"
+ echo "PASS"
-AMOUNT=`jq -r .total_balance_reserve_not_closed < test-audit-reserves.json`
-if test "x$AMOUNT" == "xTESTKUDOS:0"
-then
- exit_fail "Reported total amount wrong: $AMOUNT"
-fi
+ AMOUNT=$(jq -r .total_balance_reserve_not_closed < test-audit-reserves.json)
+ if [ "$AMOUNT" == "TESTKUDOS:0" ]
+ then
+ exit_fail "Reported total amount wrong: $AMOUNT"
+ fi
-# Undo
-echo "UPDATE reserves_in SET execution_date='${OLD_TIME}',credit_val=${OLD_VAL} WHERE reserve_in_serial_id=1;" | psql -Aqt $DB
-echo "UPDATE reserves SET current_balance_val=current_balance_val-100 WHERE reserve_pub='${RES_PUB}';" | psql -Aqt $DB
+ # Undo
+ echo "UPDATE exchange.reserves_in SET execution_date='${OLD_TIME}',credit_val=${OLD_VAL} WHERE reserve_in_serial_id=1;" \
+ | psql -Aqt "$DB"
+ echo "UPDATE exchange.reserves SET current_balance_val=current_balance_val-100 WHERE reserve_pub='${RES_PUB}';" \
+ | psql -Aqt "$DB"
}
# Test reserve closure reported but wire transfer missing detection
function test_21() {
-echo "===========21: reserve closure missreported ================="
-
-# Check wire transfer lag reported (no aggregator!)
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-helper-auditor-wire.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
+ echo "===========21: reserve closure missreported ================="
- OLD_TIME=`echo "SELECT execution_date FROM reserves_in WHERE reserve_in_serial_id=1;" | psql $DB -Aqt`
- OLD_VAL=`echo "SELECT credit_val FROM reserves_in WHERE reserve_in_serial_id=1;" | psql $DB -Aqt`
- RES_PUB=`echo "SELECT reserve_pub FROM reserves_in WHERE reserve_in_serial_id=1;" | psql $DB -Aqt`
- OLD_EXP=`echo "SELECT expiration_date FROM reserves WHERE reserve_pub='${RES_PUB}';" | psql $DB -Aqt`
+ OLD_TIME=$(echo "SELECT execution_date FROM exchange.reserves_in WHERE reserve_in_serial_id=1;" | psql "$DB" -Aqt)
+ OLD_VAL=$(echo "SELECT credit_val FROM exchange.reserves_in WHERE reserve_in_serial_id=1;" | psql "$DB" -Aqt)
+ RES_PUB=$(echo "SELECT reserve_pub FROM exchange.reserves_in WHERE reserve_in_serial_id=1;" | psql "$DB" -Aqt)
+ OLD_EXP=$(echo "SELECT expiration_date FROM exchange.reserves WHERE reserve_pub='${RES_PUB}';" | psql "$DB" -Aqt)
VAL_DELTA=1
- NEW_TIME=`expr $OLD_TIME - 3024000000000 || true` # 5 weeks
- NEW_EXP=`expr $OLD_EXP - 3024000000000 || true` # 5 weeks
- NEW_CREDIT=`expr $OLD_VAL + $VAL_DELTA || true`
- echo "UPDATE reserves_in SET execution_date='${NEW_TIME}',credit_val=${NEW_CREDIT} WHERE reserve_in_serial_id=1;" | psql -Aqt $DB
- echo "UPDATE reserves SET current_balance_val=${VAL_DELTA}+current_balance_val,expiration_date='${NEW_EXP}' WHERE reserve_pub='${RES_PUB}';" | psql -Aqt $DB
+ NEW_TIME=$(( OLD_TIME - 3024000000000 )) # 5 weeks
+ NEW_EXP=$(( OLD_EXP - 3024000000000 )) # 5 weeks
+ NEW_CREDIT=$(( OLD_VAL + VAL_DELTA ))
+ echo "UPDATE exchange.reserves_in SET execution_date='${NEW_TIME}',credit_val=${NEW_CREDIT} WHERE reserve_in_serial_id=1;" \
+ | psql -Aqt "$DB"
+ echo "UPDATE exchange.reserves SET current_balance_val=${VAL_DELTA}+current_balance_val,expiration_date='${NEW_EXP}' WHERE reserve_pub='${RES_PUB}';" \
+ | psql -Aqt "$DB"
# Need to first run the aggregator so the transfer is marked as done exists
pre_audit aggregator
-
-
+ stop_libeufin
# remove transaction from bank DB
- echo "DELETE FROM app_banktransaction WHERE debit_account_id=2 AND amount='TESTKUDOS:${VAL_DELTA}';" | psql -Aqt $DB
-
+ # Currently emulating this (to be deleted):
+ echo "DELETE FROM TalerRequestedPayments WHERE amount='TESTKUDOS:${VAL_DELTA}'" \
+ | psql "${DB}" -q
+ launch_libeufin
audit_only
post_audit
echo -n "Testing lack of reserve closure transaction detected... "
- jq -e .reserve_lag_details[0] < test-audit-wire.json > /dev/null || exit_fail "Reserve closure lag not detected"
+ jq -e .reserve_lag_details[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ || exit_fail "Reserve closure lag not detected"
- AMOUNT=`jq -r .reserve_lag_details[0].amount < test-audit-wire.json`
- if test "x$AMOUNT" != "xTESTKUDOS:${VAL_DELTA}"
+ AMOUNT=$(jq -r .reserve_lag_details[0].amount < test-audit-wire.json)
+ if [ "$AMOUNT" != "TESTKUDOS:${VAL_DELTA}" ]
then
exit_fail "Reported total amount wrong: $AMOUNT"
fi
- AMOUNT=`jq -r .total_closure_amount_lag < test-audit-wire.json`
- if test "x$AMOUNT" != "xTESTKUDOS:${VAL_DELTA}"
+ AMOUNT=$(jq -r .total_closure_amount_lag < test-audit-wire.json)
+ if [ "$AMOUNT" != "TESTKUDOS:${VAL_DELTA}" ]
then
exit_fail "Reported total amount wrong: $AMOUNT"
fi
@@ -1285,35 +1512,32 @@ then
# cannot easily undo aggregator, hence full reload
full_reload
-else
- echo "Test skipped (database too new)"
-fi
}
# Test use of withdraw-expired denomination key
function test_22() {
-echo "===========22: denomination key expired ================="
+ echo "===========22: denomination key expired ================="
-H_DENOM=`echo 'SELECT denom_pub_hash FROM reserves_out LIMIT 1;' | psql $DB -Aqt`
+ S_DENOM=$(echo 'SELECT denominations_serial FROM exchange.reserves_out LIMIT 1;' | psql "$DB" -Aqt)
-OLD_START=`echo "SELECT valid_from FROM auditor_denominations WHERE denom_pub_hash='${H_DENOM}';" | psql $DB -Aqt`
-OLD_WEXP=`echo "SELECT expire_withdraw FROM auditor_denominations WHERE denom_pub_hash='${H_DENOM}';" | psql $DB -Aqt`
-# Basically expires 'immediately', so that the withdraw must have been 'invalid'
-NEW_WEXP=`expr $OLD_START + 1 || true`
+ OLD_START=$(echo "SELECT valid_from FROM exchange.denominations WHERE denominations_serial='${S_DENOM}';" | psql "$DB" -Aqt)
+ OLD_WEXP=$(echo "SELECT expire_withdraw FROM exchange.denominations WHERE denominations_serial='${S_DENOM}';" | psql "$DB" -Aqt)
+ # Basically expires 'immediately', so that the withdraw must have been 'invalid'
+ NEW_WEXP=$OLD_START
-echo "UPDATE auditor_denominations SET expire_withdraw=${NEW_WEXP} WHERE denom_pub_hash='${H_DENOM}';" | psql -Aqt $DB
+ echo "UPDATE exchange.denominations SET expire_withdraw=${NEW_WEXP} WHERE denominations_serial='${S_DENOM}';" | psql -Aqt "$DB"
-run_audit
+ run_audit
-echo -n "Testing inconsistency detection... "
-jq -e .denomination_key_validity_withdraw_inconsistencies[0] < test-audit-reserves.json > /dev/null || exit_fail "Denomination key withdraw inconsistency not detected"
+ echo -n "Testing inconsistency detection... "
+ jq -e .denomination_key_validity_withdraw_inconsistencies[0] < test-audit-reserves.json > /dev/null || exit_fail "Denomination key withdraw inconsistency for $S_DENOM not detected"
-echo PASS
+ echo "PASS"
-# Undo modification
-echo "UPDATE auditor_denominations SET expire_withdraw=${OLD_WEXP} WHERE denom_pub_hash='${H_DENOM}';" | psql -Aqt $DB
+ # Undo modification
+ echo "UPDATE exchange.denominations SET expire_withdraw=${OLD_WEXP} WHERE denominations_serial='${S_DENOM}';" | psql -Aqt "$DB"
}
@@ -1321,50 +1545,46 @@ echo "UPDATE auditor_denominations SET expire_withdraw=${OLD_WEXP} WHERE denom_p
# Test calculation of wire-out amounts
function test_23() {
-echo "===========23: wire out calculations ================="
-
-# Check wire transfer lag reported (no aggregator!)
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-helper-auditor-wire.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
+ echo "===========23: wire out calculations ================="
# Need to first run the aggregator so the transfer is marked as done exists
pre_audit aggregator
- OLD_AMOUNT=`echo "SELECT amount_frac FROM wire_out WHERE wireout_uuid=1;" | psql $DB -Aqt`
- NEW_AMOUNT=`expr $OLD_AMOUNT - 1000000 || true`
- echo "UPDATE wire_out SET amount_frac=${NEW_AMOUNT} WHERE wireout_uuid=1;" | psql -Aqt $DB
+ OLD_AMOUNT=$(echo "SELECT amount_frac FROM exchange.wire_out WHERE wireout_uuid=1;" | psql "$DB" -Aqt)
+ NEW_AMOUNT=$(( OLD_AMOUNT - 1000000 ))
+ echo "UPDATE exchange.wire_out SET amount_frac=${NEW_AMOUNT} WHERE wireout_uuid=1;" \
+ | psql -Aqt "$DB"
audit_only
post_audit
echo -n "Testing inconsistency detection... "
- jq -e .wire_out_inconsistencies[0] < test-audit-aggregation.json > /dev/null || exit_fail "Wire out inconsistency not detected"
+ jq -e .wire_out_inconsistencies[0] \
+ < test-audit-aggregation.json \
+ > /dev/null \
+ || exit_fail "Wire out inconsistency not detected"
- ROW=`jq .wire_out_inconsistencies[0].rowid < test-audit-aggregation.json`
- if test $ROW != 1
+ ROW=$(jq .wire_out_inconsistencies[0].rowid < test-audit-aggregation.json)
+ if [ "$ROW" != 1 ]
then
exit_fail "Row wrong"
fi
- AMOUNT=`jq -r .total_wire_out_delta_plus < test-audit-aggregation.json`
- if test "x$AMOUNT" != "xTESTKUDOS:0"
+ AMOUNT=$(jq -r .total_wire_out_delta_plus < test-audit-aggregation.json)
+ if [ "$AMOUNT" != "TESTKUDOS:0" ]
then
exit_fail "Reported amount wrong: $AMOUNT"
fi
- AMOUNT=`jq -r .total_wire_out_delta_minus < test-audit-aggregation.json`
- if test "x$AMOUNT" != "xTESTKUDOS:0.01"
+ AMOUNT=$(jq -r .total_wire_out_delta_minus < test-audit-aggregation.json)
+ if [ "$AMOUNT" != "TESTKUDOS:0.01" ]
then
exit_fail "Reported total amount wrong: $AMOUNT"
fi
- echo PASS
+ echo "PASS"
echo "Second pass: changing how amount is wrong to other direction"
- NEW_AMOUNT=`expr $OLD_AMOUNT + 1000000 || true`
- echo "UPDATE wire_out SET amount_frac=${NEW_AMOUNT} WHERE wireout_uuid=1;" | psql -Aqt $DB
+ NEW_AMOUNT=$(( OLD_AMOUNT + 1000000 ))
+ echo "UPDATE exchange.wire_out SET amount_frac=${NEW_AMOUNT} WHERE wireout_uuid=1;" | psql -Aqt "$DB"
pre_audit
audit_only
@@ -1374,29 +1594,25 @@ then
jq -e .wire_out_inconsistencies[0] < test-audit-aggregation.json > /dev/null || exit_fail "Wire out inconsistency not detected"
- ROW=`jq .wire_out_inconsistencies[0].rowid < test-audit-aggregation.json`
- if test $ROW != 1
+ ROW=$(jq .wire_out_inconsistencies[0].rowid < test-audit-aggregation.json)
+ if [ "$ROW" != 1 ]
then
exit_fail "Row wrong"
fi
- AMOUNT=`jq -r .total_wire_out_delta_minus < test-audit-aggregation.json`
- if test "x$AMOUNT" != "xTESTKUDOS:0"
+ AMOUNT=$(jq -r .total_wire_out_delta_minus < test-audit-aggregation.json)
+ if [ "$AMOUNT" != "TESTKUDOS:0" ]
then
exit_fail "Reported amount wrong: $AMOUNT"
fi
- AMOUNT=`jq -r .total_wire_out_delta_plus < test-audit-aggregation.json`
- if test "x$AMOUNT" != "xTESTKUDOS:0.01"
+ AMOUNT=$(jq -r .total_wire_out_delta_plus < test-audit-aggregation.json)
+ if [ "$AMOUNT" != "TESTKUDOS:0.01" ]
then
exit_fail "Reported total amount wrong: $AMOUNT"
fi
- echo PASS
-
+ echo "PASS"
# cannot easily undo aggregator, hence full reload
full_reload
-else
- echo "Test skipped (database too new)"
-fi
}
@@ -1404,175 +1620,173 @@ fi
# Test for missing deposits in exchange database.
function test_24() {
-echo "===========24: deposits missing ==========="
-# Modify denom_sig, so it is wrong
-CNT=`echo "SELECT COUNT(*) FROM deposit_confirmations;" | psql -Aqt $DB`
-if test x$CNT = x0
-then
- echo "Skipping deposits missing test: no deposit confirmations in database!"
-else
- echo "DELETE FROM deposits;" | psql -Aqt $DB
- echo "DELETE FROM deposits WHERE deposit_serial_id=1;" | psql -Aqt $DB
+ echo "===========24: deposits missing ==========="
+ # Modify denom_sig, so it is wrong
+ CNT=$(echo "SELECT COUNT(*) FROM auditor.deposit_confirmations;" | psql -Aqt "$DB")
+ if [ "$CNT" = "0" ]
+ then
+ echo "Skipping deposits missing test: no deposit confirmations in database!"
+ else
+ echo "DELETE FROM exchange.deposits;" | psql -Aqt "$DB"
+ echo "DELETE FROM exchange.deposits WHERE deposit_serial_id=1;" \
+ | psql -Aqt "$DB"
- run_audit
+ run_audit
- echo -n "Testing inconsistency detection... "
+ echo -n "Testing inconsistency detection... "
- jq -e .deposit_confirmation_inconsistencies[0] < test-audit-deposits.json > /dev/null || exit_fail "Deposit confirmation inconsistency NOT detected"
+ jq -e .deposit_confirmation_inconsistencies[0] \
+ < test-audit-deposits.json \
+ > /dev/null \
+ || exit_fail "Deposit confirmation inconsistency NOT detected"
- AMOUNT=`jq -er .missing_deposit_confirmation_total < test-audit-deposits.json`
- if test x$AMOUNT = xTESTKUDOS:0
- then
- exit_fail "Expected non-zero total missing deposit confirmation amount"
- fi
- COUNT=`jq -er .missing_deposit_confirmation_count < test-audit-deposits.json`
- if test x$AMOUNT = x0
- then
- exit_fail "Expected non-zero total missing deposit confirmation count"
- fi
+ AMOUNT=$(jq -er .missing_deposit_confirmation_total < test-audit-deposits.json)
+ if [ "$AMOUNT" = "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected non-zero total missing deposit confirmation amount"
+ fi
+ COUNT=$(jq -er .missing_deposit_confirmation_count < test-audit-deposits.json)
+ if [ "$AMOUNT" = "0" ]
+ then
+ exit_fail "Expected non-zero total missing deposit confirmation count"
+ fi
- echo PASS
+ echo "PASS"
- # cannot easily undo DELETE, hence full reload
- full_reload
-fi
+ # cannot easily undo DELETE, hence full reload
+ full_reload
+ fi
}
# Test for inconsistent coin history.
function test_25() {
-echo "=========25: inconsistent coin history========="
-
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-helper-auditor-wire.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
+ echo "=========25: inconsistent coin history========="
# Drop refund, so coin history is bogus.
- echo "DELETE FROM refunds WHERE refund_serial_id=1;" | psql -Aqt $DB
+ echo "DELETE FROM exchange.refunds WHERE refund_serial_id=1;" \
+ | psql -At "$DB"
run_audit aggregator
echo -n "Testing inconsistency detection... "
- jq -e .coin_inconsistencies[0] < test-audit-aggregation.json > /dev/null || exit_fail "Coin inconsistency NOT detected"
+ jq -e .coin_inconsistencies[0] \
+ < test-audit-aggregation.json \
+ > /dev/null \
+ || exit_fail "Coin inconsistency NOT detected"
# Note: if the wallet withdrew much more than it spent, this might indeed
# go legitimately unnoticed.
- jq -e .emergencies[0] < test-audit-coins.json > /dev/null || exit_fail "Denomination value emergency NOT reported"
+ jq -e .emergencies[0] \
+ < test-audit-coins.json \
+ > /dev/null \
+ || exit_fail "Denomination value emergency NOT reported"
- AMOUNT=`jq -er .total_coin_delta_minus < test-audit-aggregation.json`
- if test x$AMOUNT = xTESTKUDOS:0
+ AMOUNT=$(jq -er .total_coin_delta_minus < test-audit-aggregation.json)
+ if [ "$AMOUNT" = "TESTKUDOS:0" ]
then
exit_fail "Expected non-zero total inconsistency amount from coins"
fi
# Note: if the wallet withdrew much more than it spent, this might indeed
# go legitimately unnoticed.
- COUNT=`jq -er .emergencies_risk_by_amount < test-audit-coins.json`
- if test x$AMOUNT = xTESTKUDOS:0
+ COUNT=$(jq -er .emergencies_risk_by_amount < test-audit-coins.json)
+ if [ "$COUNT" = "TESTKUDOS:0" ]
then
exit_fail "Expected non-zero emergency-by-amount"
fi
- echo PASS
+ echo "PASS"
# cannot easily undo DELETE, hence full reload
full_reload
-else
- echo "Test skipped (database too new)"
-fi
}
# Test for deposit wire target malformed
function test_26() {
-echo "===========26: deposit wire target malformed ================="
-# Expects 'payto_uri', not 'url' (also breaks signature, but we cannot even check that).
-SERIAL=`echo "SELECT deposit_serial_id FROM deposits WHERE amount_with_fee_val=3 AND amount_with_fee_frac=0 ORDER BY deposit_serial_id LIMIT 1" | psql $DB -Aqt`
-OLD_WIRE=`echo "SELECT wire FROM deposits WHERE deposit_serial_id=${SERIAL};" | psql $DB -Aqt`
-echo "UPDATE deposits SET wire='{\"url\":\"payto://x-taler-bank/localhost:8082/44\",\"salt\":\"test-salt\"}' WHERE deposit_serial_id=${SERIAL}" | psql -Aqt $DB
+ echo "===========26: deposit wire target malformed ================="
+ # Expects 'payto_uri', not 'url' (also breaks signature, but we cannot even check that).
+ SERIAL=$(echo "SELECT deposit_serial_id FROM exchange.deposits WHERE amount_with_fee_val=3 AND amount_with_fee_frac=0 ORDER BY deposit_serial_id LIMIT 1" | psql "$DB" -Aqt)
+ OLD_WIRE_ID=$(echo "SELECT wire_target_h_payto FROM exchange.deposits WHERE deposit_serial_id=${SERIAL};" | psql "$DB" -Aqt)
+# shellcheck disable=SC2028
+ echo "INSERT INTO exchange.wire_targets (payto_uri, wire_target_h_payto) VALUES ('payto://x-taler-bank/localhost/testuser-xxlargtp', '\x1e8f31936b3cee8f8afd3aac9e38b5db42d45b721ffc4eb1e5b9ddaf1565660b');" \
+ | psql "$DB" -Aqt
+# shellcheck disable=SC2028
+ echo "UPDATE exchange.deposits SET wire_target_h_payto='\x1e8f31936b3cee8f8afd3aac9e38b5db42d45b721ffc4eb1e5b9ddaf1565660b' WHERE deposit_serial_id=${SERIAL}" \
+ | psql -Aqt "$DB"
-run_audit
+ run_audit
-echo -n "Testing inconsistency detection... "
+ echo -n "Testing inconsistency detection... "
-jq -e .bad_sig_losses[0] < test-audit-coins.json > /dev/null || exit_fail "Bad signature not detected"
+ jq -e .bad_sig_losses[0] < test-audit-coins.json > /dev/null || exit_fail "Bad signature not detected"
-ROW=`jq -e .bad_sig_losses[0].row < test-audit-coins.json`
-if test $ROW != ${SERIAL}
-then
- exit_fail "Row wrong, got $ROW"
-fi
-
-LOSS=`jq -r .bad_sig_losses[0].loss < test-audit-coins.json`
-if test $LOSS != "TESTKUDOS:3"
-then
- exit_fail "Wrong deposit bad signature loss, got $LOSS"
-fi
+ ROW=$(jq -e .bad_sig_losses[0].row < test-audit-coins.json)
+ if [ "$ROW" != "${SERIAL}" ]
+ then
+ exit_fail "Row wrong, got $ROW"
+ fi
-OP=`jq -r .bad_sig_losses[0].operation < test-audit-coins.json`
-if test $OP != "deposit"
-then
- exit_fail "Wrong operation, got $OP"
-fi
+ LOSS=$(jq -r .bad_sig_losses[0].loss < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:3" ]
+ then
+ exit_fail "Wrong deposit bad signature loss, got $LOSS"
+ fi
-LOSS=`jq -r .total_bad_sig_loss < test-audit-coins.json`
-if test $LOSS != "TESTKUDOS:3"
-then
- exit_fail "Wrong total bad sig loss, got $LOSS"
-fi
+ OP=$(jq -r .bad_sig_losses[0].operation < test-audit-coins.json)
+ if [ "$OP" != "deposit" ]
+ then
+ exit_fail "Wrong operation, got $OP"
+ fi
-echo PASS
-# Undo:
-echo "UPDATE deposits SET wire='$OLD_WIRE' WHERE deposit_serial_id=${SERIAL}" | psql -Aqt $DB
+ LOSS=$(jq -r .irregular_loss < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:3" ]
+ then
+ exit_fail "Wrong total bad sig loss, got $LOSS"
+ fi
+ echo "PASS"
+ # Undo:
+ echo "UPDATE exchange.deposits SET wire_target_h_payto='$OLD_WIRE_ID' WHERE deposit_serial_id=${SERIAL}" \
+ | psql -Aqt "$DB"
}
# Test for duplicate wire transfer subject
function test_27() {
-echo "===========27: duplicate WTID detection ================="
-
-# Check wire transfer lag reported (no aggregator!)
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-helper-auditor-wire.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
+ echo "===========27: duplicate WTID detection ================="
pre_audit aggregator
-
+ stop_libeufin
# Obtain data to duplicate.
- ID=`echo "SELECT id FROM app_banktransaction WHERE debit_account_id=2 LIMIT 1" | psql $DB -Aqt`
- WTID=`echo "SELECT subject FROM app_banktransaction WHERE debit_account_id=2 LIMIT 1" | psql $DB -Aqt`
- UUID="992e8936-a64d-4845-87d7-021440330f8a"
- echo "INSERT INTO app_banktransaction (amount,subject,date,credit_account_id,debit_account_id,cancelled,request_uid) VALUES ('TESTKUDOS:1','$WTID',NOW(),12,2,'f','$UUID')" | psql -Aqt $DB
-
+ WTID=$(echo SELECT wtid FROM TalerRequestedPayments WHERE id=1 | psql "${DB}" -Aqt)
+ OTHER_IBAN=$(echo -e "SELECT iban FROM BankAccounts WHERE label='fortytwo'" | psql "${DB}" -Aqt)
+ # 'rawConfirmation' is set to 2 here, that doesn't
+ # point to any record. That's only needed to set a non null value.
+ echo -e "INSERT INTO PaymentInitiations (\"bankAccount\",\"preparationDate\",\"submissionDate\",sum,currency,\"endToEndId\",\"paymentInformationId\",\"instructionId\",subject,\"creditorIban\",\"creditorBic\",\"creditorName\",submitted,\"messageId\",\"rawConfirmation\") VALUES (1,$(date +%s),$(( $(date +%s) + 2)),10,'TESTKUDOS','NOTGIVEN','unused','unused','$WTID http://exchange.example.com/','$OTHER_IBAN','SANDBOXX','Forty Two',false,1,2)" \
+ | psql "${DB}" -q
+ echo -e "INSERT INTO TalerRequestedPayments (facade,payment,\"requestUid\",amount,\"exchangeBaseUrl\",wtid,\"creditAccount\") VALUES (1,2,'unused','TESTKUDOS:1','http://exchange.example.com/','$WTID','payto://iban/SANDBOXX/$OTHER_IBAN?receiver-name=Forty+Two')" \
+ | psql "${DB}" -q
+ launch_libeufin
audit_only
post_audit
echo -n "Testing inconsistency detection... "
- AMOUNT=`jq -r .wire_format_inconsistencies[0].amount < test-audit-wire.json`
- if test "${AMOUNT}" != "TESTKUDOS:1"
+ AMOUNT=$(jq -r .wire_format_inconsistencies[0].amount < test-audit-wire.json)
+ if [ "${AMOUNT}" != "TESTKUDOS:1" ]
then
exit_fail "Amount wrong, got ${AMOUNT}"
fi
- AMOUNT=`jq -r .total_wire_format_amount < test-audit-wire.json`
- if test "${AMOUNT}" != "TESTKUDOS:1"
+ AMOUNT=$(jq -r .total_wire_format_amount < test-audit-wire.json)
+ if [ "${AMOUNT}" != "TESTKUDOS:1" ]
then
exit_fail "Wrong total wire format amount, got $AMOUNT"
fi
# cannot easily undo aggregator, hence full reload
full_reload
-else
- echo "Test skipped (database too new)"
-fi
-
}
@@ -1581,47 +1795,37 @@ fi
# Test where denom_sig in known_coins table is wrong
# (=> bad signature) AND the coin is used in aggregation
function test_28() {
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-helper-auditor-wire.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
echo "===========28: known_coins signature wrong================="
# Modify denom_sig, so it is wrong
- OLD_SIG=`echo 'SELECT denom_sig FROM known_coins LIMIT 1;' | psql $DB -Aqt`
- COIN_PUB=`echo "SELECT coin_pub FROM known_coins WHERE denom_sig='$OLD_SIG';" | psql $DB -Aqt`
- echo "UPDATE known_coins SET denom_sig='\x287369672d76616c200a2028727361200a2020287320233542383731423743393036444643303442424430453039353246413642464132463537303139374131313437353746324632323332394644443146324643333445393939413336363430334233413133324444464239413833353833464536354442374335434445304441453035374438363336434541423834463843323843344446304144363030343430413038353435363039373833434431333239393736423642433437313041324632414132414435413833303432434346314139464635394244434346374436323238344143354544364131373739463430353032323241373838423837363535453434423145443831364244353638303232413123290a2020290a20290b' WHERE coin_pub='$COIN_PUB'" | psql -Aqt $DB
+ OLD_SIG=$(echo 'SELECT denom_sig FROM exchange.known_coins LIMIT 1;' | psql "$DB" -Aqt)
+ COIN_PUB=$(echo "SELECT coin_pub FROM exchange.known_coins WHERE denom_sig='$OLD_SIG';" | psql "$DB" -Aqt)
+# shellcheck disable=SC2028
+ echo "UPDATE exchange.known_coins SET denom_sig='\x0000000100000000287369672d76616c200a2028727361200a2020287320233542383731423743393036444643303442424430453039353246413642464132463537303139374131313437353746324632323332394644443146324643333445393939413336363430334233413133324444464239413833353833464536354442374335434445304441453035374438363336434541423834463843323843344446304144363030343430413038353435363039373833434431333239393736423642433437313041324632414132414435413833303432434346314139464635394244434346374436323238344143354544364131373739463430353032323241373838423837363535453434423145443831364244353638303232413123290a2020290a20290b' WHERE coin_pub='$COIN_PUB'" \
+ | psql -Aqt "$DB"
run_audit aggregator
echo -n "Testing inconsistency detection... "
- ROW=`jq -e .bad_sig_losses[0].row < test-audit-aggregation.json`
- if test $ROW != "1"
- then
- exit_fail "Row wrong, got $ROW"
- fi
-
- LOSS=`jq -r .bad_sig_losses[0].loss < test-audit-aggregation.json`
- if test $LOSS == "TESTKUDOS:0"
+ LOSS=$(jq -r .bad_sig_losses[0].loss < test-audit-aggregation.json)
+ if [ "$LOSS" == "TESTKUDOS:0" ]
then
exit_fail "Wrong deposit bad signature loss, got $LOSS"
fi
- OP=`jq -r .bad_sig_losses[0].operation < test-audit-aggregation.json`
- if test $OP != "wire"
+ OP=$(jq -r .bad_sig_losses[0].operation < test-audit-aggregation.json)
+ if [ "$OP" != "wire" ]
then
exit_fail "Wrong operation, got $OP"
fi
- TAB=`jq -r .row_inconsistencies[0].table < test-audit-aggregation.json`
- if test $TAB != "deposit"
+ TAB=$(jq -r .row_inconsistencies[0].table < test-audit-aggregation.json)
+ if [ "$TAB" != "deposit" ]
then
exit_fail "Wrong table for row inconsistency, got $TAB"
fi
- LOSS=`jq -r .total_bad_sig_loss < test-audit-aggregation.json`
- if test $LOSS == "TESTKUDOS:0"
+ LOSS=$(jq -r .total_bad_sig_loss < test-audit-aggregation.json)
+ if [ "$LOSS" == "TESTKUDOS:0" ]
then
exit_fail "Wrong total bad sig loss, got $LOSS"
fi
@@ -1629,10 +1833,6 @@ then
echo "OK"
# cannot easily undo aggregator, hence full reload
full_reload
-
-else
- echo "Test skipped (database too new)"
-fi
}
@@ -1640,27 +1840,27 @@ fi
# Test where fees known to the auditor differ from those
# accounted for by the exchange
function test_29() {
-echo "===========29: withdraw fee inconsistency ================="
+ echo "===========29: withdraw fee inconsistency ================="
-echo "UPDATE auditor_denominations SET fee_withdraw_frac=5000000 WHERE coin_val=1;" | psql -Aqt $DB
+ echo "UPDATE exchange.denominations SET fee_withdraw_frac=5000000 WHERE coin_val=1;" | psql -Aqt "$DB"
-run_audit
+ run_audit
-echo -n "Testing inconsistency detection... "
-AMOUNT=`jq -r .total_balance_summary_delta_plus < test-audit-reserves.json`
-if test "x$AMOUNT" == "xTESTKUDOS:0"
-then
- exit_fail "Reported total amount wrong: $AMOUNT"
-fi
+ echo -n "Testing inconsistency detection... "
+ AMOUNT=$(jq -r .total_balance_summary_delta_minus < test-audit-reserves.json)
+ if [ "$AMOUNT" == "TESTKUDOS:0" ]
+ then
+ exit_fail "Reported total amount wrong: $AMOUNT"
+ fi
-PROFIT=`jq -r .amount_arithmetic_inconsistencies[0].profitable < test-audit-coins.json`
-if test "x$PROFIT" != "x-1"
-then
- exit_fail "Reported wrong profitability: $PROFIT"
-fi
-echo "OK"
-# Undo
-echo "UPDATE auditor_denominations SET fee_withdraw_frac=2000000 WHERE coin_val=1;" | psql -Aqt $DB
+ PROFIT=$(jq -r .amount_arithmetic_inconsistencies[0].profitable < test-audit-coins.json)
+ if [ "$PROFIT" != "-1" ]
+ then
+ exit_fail "Reported wrong profitability: $PROFIT"
+ fi
+ echo "OK"
+ # Undo
+ echo "UPDATE exchange.denominations SET fee_withdraw_frac=2000000 WHERE coin_val=1;" | psql -Aqt "$DB"
}
@@ -1668,28 +1868,28 @@ echo "UPDATE auditor_denominations SET fee_withdraw_frac=2000000 WHERE coin_val=
# Test where fees known to the auditor differ from those
# accounted for by the exchange
function test_30() {
-echo "===========30: melt fee inconsistency ================="
+ echo "===========30: melt fee inconsistency ================="
-echo "UPDATE auditor_denominations SET fee_refresh_frac=5000000 WHERE coin_val=10;" | psql -Aqt $DB
+ echo "UPDATE exchange.denominations SET fee_refresh_frac=5000000 WHERE coin_val=10;" | psql -Aqt "$DB"
-run_audit
-echo -n "Testing inconsistency detection... "
-AMOUNT=`jq -r .bad_sig_losses[0].loss < test-audit-coins.json`
-if test "x$AMOUNT" == "xTESTKUDOS:0"
-then
- exit_fail "Reported total amount wrong: $AMOUNT"
-fi
+ run_audit
+ echo -n "Testing inconsistency detection... "
+ AMOUNT=$(jq -r .bad_sig_losses[0].loss < test-audit-coins.json)
+ if [ "$AMOUNT" == "TESTKUDOS:0" ]
+ then
+ exit_fail "Reported total amount wrong: $AMOUNT"
+ fi
-PROFIT=`jq -r .amount_arithmetic_inconsistencies[0].profitable < test-audit-coins.json`
-if test "x$PROFIT" != "x-1"
-then
- exit_fail "Reported profitability wrong: $PROFIT"
-fi
+ PROFIT=$(jq -r .amount_arithmetic_inconsistencies[0].profitable < test-audit-coins.json)
+ if [ "$PROFIT" != "-1" ]
+ then
+ exit_fail "Reported profitability wrong: $PROFIT"
+ fi
-jq -e .emergencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency detected in ordinary run"
-echo "OK"
-# Undo
-echo "UPDATE auditor_denominations SET fee_refresh_frac=3000000 WHERE coin_val=1;" | psql -Aqt $DB
+ jq -e .emergencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency detected in ordinary run"
+ echo "OK"
+ # Undo
+ echo "UPDATE exchange.denominations SET fee_refresh_frac=3000000 WHERE coin_val=10;" | psql -Aqt "$DB"
}
@@ -1698,39 +1898,27 @@ echo "UPDATE auditor_denominations SET fee_refresh_frac=3000000 WHERE coin_val=1
# accounted for by the exchange
function test_31() {
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-helper-auditor-wire.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
-
echo "===========31: deposit fee inconsistency ================="
- echo "UPDATE auditor_denominations SET fee_deposit_frac=5000000 WHERE coin_val=8;" | psql -Aqt $DB
+ echo "UPDATE exchange.denominations SET fee_deposit_frac=5000000 WHERE coin_val=8;" | psql -Aqt "$DB"
run_audit aggregator
echo -n "Testing inconsistency detection... "
- AMOUNT=`jq -r .total_bad_sig_loss < test-audit-coins.json`
- if test "x$AMOUNT" == "xTESTKUDOS:0"
+ AMOUNT=$(jq -r .irregular_loss < test-audit-coins.json)
+ if [ "$AMOUNT" == "TESTKUDOS:0" ]
then
exit_fail "Reported total amount wrong: $AMOUNT"
fi
- OP=`jq -r --arg dep "deposit" '.bad_sig_losses[] | select(.operation == $dep) | .operation'< test-audit-coins.json | head -n1`
- if test "x$OP" != "xdeposit"
+ OP=$(jq -r --arg dep "deposit" '.bad_sig_losses[] | select(.operation == $dep) | .operation'< test-audit-coins.json | head -n1)
+ if [ "$OP" != "deposit" ]
then
exit_fail "Reported wrong operation: $OP"
fi
echo "OK"
# Undo
- echo "UPDATE auditor_denominations SET fee_deposit_frac=2000000 WHERE coin_val=8;" | psql -Aqt $DB
-
-else
- echo "Test skipped (database too new)"
-fi
-
+ echo "UPDATE exchange.denominations SET fee_deposit_frac=2000000 WHERE coin_val=8;" | psql -Aqt "$DB"
}
@@ -1740,30 +1928,25 @@ fi
# (=> bad signature)
function test_32() {
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-helper-auditor-wire.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
-
echo "===========32: known_coins signature wrong w. aggregation================="
# Modify denom_sig, so it is wrong
- OLD_SIG=`echo 'SELECT denom_sig FROM known_coins LIMIT 1;' | psql $DB -At`
- COIN_PUB=`echo "SELECT coin_pub FROM known_coins WHERE denom_sig='$OLD_SIG';" | psql $DB -At`
- echo "UPDATE known_coins SET denom_sig='\x287369672d76616c200a2028727361200a2020287320233542383731423743393036444643303442424430453039353246413642464132463537303139374131313437353746324632323332394644443146324643333445393939413336363430334233413133324444464239413833353833464536354442374335434445304441453035374438363336434541423834463843323843344446304144363030343430413038353435363039373833434431333239393736423642433437313041324632414132414435413833303432434346314139464635394244434346374436323238344143354544364131373739463430353032323241373838423837363535453434423145443831364244353638303232413123290a2020290a20290b' WHERE coin_pub='$COIN_PUB'" | psql -Aqt $DB
+ OLD_SIG=$(echo 'SELECT denom_sig FROM exchange.known_coins LIMIT 1;' | psql "$DB" -Aqt)
+ COIN_PUB=$(echo "SELECT coin_pub FROM exchange.known_coins WHERE denom_sig='$OLD_SIG';" | psql "$DB" -Aqt)
+# shellcheck disable=SC2028
+ echo "UPDATE exchange.known_coins SET denom_sig='\x0000000100000000287369672d76616c200a2028727361200a2020287320233542383731423743393036444643303442424430453039353246413642464132463537303139374131313437353746324632323332394644443146324643333445393939413336363430334233413133324444464239413833353833464536354442374335434445304441453035374438363336434541423834463843323843344446304144363030343430413038353435363039373833434431333239393736423642433437313041324632414132414435413833303432434346314139464635394244434346374436323238344143354544364131373739463430353032323241373838423837363535453434423145443831364244353638303232413123290a2020290a20290b' WHERE coin_pub='$COIN_PUB'" \
+ | psql -Aqt "$DB"
run_audit aggregator
echo -n "Testing inconsistency detection... "
- AMOUNT=`jq -r .total_bad_sig_loss < test-audit-aggregation.json`
- if test "x$AMOUNT" == "xTESTKUDOS:0"
+ AMOUNT=$(jq -r .total_bad_sig_loss < test-audit-aggregation.json)
+ if [ "$AMOUNT" == "TESTKUDOS:0" ]
then
exit_fail "Reported total amount wrong: $AMOUNT"
fi
- OP=`jq -r .bad_sig_losses[0].operation < test-audit-aggregation.json`
- if test "x$OP" != "xwire"
+ OP=$(jq -r .bad_sig_losses[0].operation < test-audit-aggregation.json)
+ if [ "$OP" != "wire" ]
then
exit_fail "Reported wrong operation: $OP"
fi
@@ -1771,38 +1954,166 @@ then
echo "OK"
# Cannot undo aggregation, do full reload
full_reload
-
-fi
}
+function test_33() {
+
+ echo "===========33: normal run with aggregator and profit drain==========="
+ run_audit aggregator drain
+
+ echo "Checking output"
+ # if an emergency was detected, that is a bug and we should fail
+ echo -n "Test for emergencies... "
+ jq -e .emergencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency detected in ordinary run" || echo PASS
+ echo -n "Test for deposit confirmation emergencies... "
+ jq -e .deposit_confirmation_inconsistencies[0] < test-audit-deposits.json > /dev/null && exit_fail "Unexpected deposit confirmation inconsistency detected" || echo PASS
+ echo -n "Test for emergencies by count... "
+ jq -e .emergencies_by_count[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency by count detected in ordinary run" || echo PASS
+
+ echo -n "Test for wire inconsistencies... "
+ jq -e .wire_out_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire out inconsistency detected in ordinary run"
+ jq -e .reserve_in_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected reserve in inconsistency detected in ordinary run"
+ jq -e .misattribution_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected misattribution inconsistency detected in ordinary run"
+ jq -e .row_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected row inconsistency detected in ordinary run"
+ jq -e .denomination_key_validity_withdraw_inconsistencies[0] < test-audit-reserves.json > /dev/null && exit_fail "Unexpected denomination key withdraw inconsistency detected in ordinary run"
+ jq -e .row_minor_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected minor row inconsistency detected in ordinary run"
+ jq -e .lag_details[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected lag detected in ordinary run"
+ jq -e .wire_format_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire format inconsistencies detected in ordinary run"
+
+
+ # TODO: check operation balances are correct (once we have all transaction types and wallet is deterministic)
+ # TODO: check revenue summaries are correct (once we have all transaction types and wallet is deterministic)
+
+ echo PASS
+
+ LOSS=$(jq -r .total_bad_sig_loss < test-audit-aggregation.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total bad sig loss from aggregation, got unexpected loss of $LOSS"
+ fi
+ LOSS=$(jq -r .irregular_loss < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total bad sig loss from coins, got unexpected loss of $LOSS"
+ fi
+ LOSS=$(jq -r .total_bad_sig_loss < test-audit-reserves.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total bad sig loss from reserves, got unexpected loss of $LOSS"
+ fi
+
+ echo -n "Test for wire amounts... "
+ WIRED=$(jq -r .total_wire_in_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_in_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_misattribution_in < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total misattribution in wrong, got $WIRED"
+ fi
+ echo PASS
+
+ echo -n "Checking for unexpected arithmetic differences "
+ LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-aggregation.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from aggregations, got unexpected plus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-aggregation.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from aggregation, got unexpected minus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from coins, got unexpected plus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from coins, got unexpected minus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-reserves.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from reserves, got unexpected plus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-reserves.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from reserves, got unexpected minus of $LOSS"
+ fi
+
+ DRAINED=$(jq -r .total_drained < test-audit-wire.json)
+ if [ "$DRAINED" != "TESTKUDOS:0.1" ]
+ then
+ exit_fail "Wrong amount drained, got unexpected drain of $DRAINED"
+ fi
+
+ jq -e .amount_arithmetic_inconsistencies[0] \
+ < test-audit-aggregation.json \
+ > /dev/null \
+ && exit_fail "Unexpected arithmetic inconsistencies from aggregations detected in ordinary run"
+ jq -e .amount_arithmetic_inconsistencies[0] \
+ < test-audit-coins.json \
+ > /dev/null \
+ && exit_fail "Unexpected arithmetic inconsistencies from coins detected in ordinary run"
+ jq -e .amount_arithmetic_inconsistencies[0] \
+ < test-audit-reserves.json \
+ > /dev/null \
+ && exit_fail "Unexpected arithmetic inconsistencies from reserves detected in ordinary run"
+ echo "PASS"
+
+ echo -n "Checking for unexpected wire out differences "
+ jq -e .wire_out_inconsistencies[0] \
+ < test-audit-aggregation.json \
+ > /dev/null \
+ && exit_fail "Unexpected wire out inconsistencies detected in ordinary run"
+ echo "PASS"
+
+ # cannot easily undo aggregator, hence full reload
+ full_reload
+
+}
+
+
# *************** Main test loop starts here **************
# Run all the tests against the database given in $1.
# Sets $fail to 0 on success, non-zero on failure.
-check_with_database()
+function check_with_database()
{
- BASEDB=$1
+ BASEDB="$1"
+ CONF="$1.conf"
echo "Running test suite with database $BASEDB using configuration $CONF"
-
- # Setup database-specific globals
- MASTER_PUB=`cat ${BASEDB}.mpub`
-
- # Where to store wire fee details for aggregator
- echo "Storing wire fees"
- WIRE_FEE_DIR=`taler-config -c $CONF -f -s exchangedb -o WIREFEE_BASE_DIR`
- mkdir -p $WIRE_FEE_DIR
- cp ${BASEDB}.fees $WIRE_FEE_DIR/x-taler-bank.fee
-
- # Determine database age
- echo "Calculating database age based on ${BASEDB}.age"
- AGE=`cat ${BASEDB}.age`
- NOW=`date +%s`
- # NOTE: expr "fails" if the result is zero.
- DATABASE_AGE=`expr ${NOW} - ${AGE} || true`
- echo "Database age is ${DATABASE_AGE} seconds"
+ MASTER_PRIV_FILE="${BASEDB}.mpriv"
+ taler-config \
+ -f \
+ -c "${CONF}" \
+ -s exchange-offline \
+ -o MASTER_PRIV_FILE \
+ -V "${MASTER_PRIV_FILE}"
# Load database
full_reload
@@ -1811,67 +2122,134 @@ check_with_database()
fail=0
for i in $TESTS
do
- test_$i
+ "test_$i"
if test 0 != $fail
then
break
fi
done
echo "Cleanup (disabled, leaving database $DB behind)"
- dropdb $DB
- rm -r $WIRE_FEE_DIR
+ # dropdb $DB
}
-
-
# *************** Main logic starts here **************
# ####### Setup globals ######
-# Postgres database to use
-DB=taler-auditor-test
-
-# Configuration file to use
-CONF=test-auditor.conf
+# Postgres database to use (must match configuration file)
+export DB="auditor-basedb"
# test required commands exist
echo "Testing for jq"
jq -h > /dev/null || exit_skip "jq required"
-echo "Testing for taler-bank-manage"
-taler-bank-manage -h >/dev/null </dev/null || exit_skip "taler-bank-manage required"
+echo "Testing for faketime"
+faketime -h > /dev/null || exit_skip "faketime required"
+# NOTE: really check for all three libeufin commands?
+echo "Testing for libeufin"
+libeufin-bank --help >/dev/null 2> /dev/null </dev/null || exit_skip "libeufin required"
echo "Testing for pdflatex"
which pdflatex > /dev/null </dev/null || exit_skip "pdflatex required"
+echo "Testing for taler-wallet-cli"
+taler-wallet-cli -h >/dev/null </dev/null 2>/dev/null || exit_skip "taler-wallet-cli required"
-# check if we should regenerate the database
-if test -n "${1:-}"
-then
- echo "Custom run, will only run on existing DB."
+
+echo -n "Testing for Postgres"
+# Available directly in path?
+INITDB_BIN=$(command -v initdb) || true
+if [[ -n "$INITDB_BIN" ]]; then
+ echo " FOUND (in path) at $INITDB_BIN"
else
- echo -n "Testing for taler-wallet-cli"
- if taler-wallet-cli -h >/dev/null </dev/null 2>/dev/null
+ HAVE_INITDB=$(find /usr -name "initdb" 2> /dev/null \
+ | head -1 2> /dev/null \
+ | grep postgres) \
+ || exit_skip " MISSING"
+ echo " FOUND at $(dirname "$HAVE_INITDB")"
+ INITDB_BIN=$(echo "$HAVE_INITDB" | grep bin/initdb | grep postgres | sort -n | tail -n1)
+fi
+POSTGRES_PATH=$(dirname "$INITDB_BIN")
+
+MY_TMP_DIR=$(mktemp -d /tmp/taler-auditor-basedbXXXXXX)
+echo "Using $MY_TMP_DIR for logging and temporary data"
+TMPDIR="$MY_TMP_DIR/postgres"
+mkdir -p "$TMPDIR"
+echo -n "Setting up Postgres DB at $TMPDIR ..."
+$INITDB_BIN \
+ --no-sync \
+ --auth=trust \
+ -D "${TMPDIR}" \
+ > "${MY_TMP_DIR}/postgres-dbinit.log" \
+ 2> "${MY_TMP_DIR}/postgres-dbinit.err"
+echo "DONE"
+
+# Once we move to PG16, we can use:
+# --set listen_addresses='' \
+# --set fsync=off \
+# --set max_wal_senders=0 \
+# --set synchronous_commit=off \
+# --set wal_level=minimal \
+# --set unix_socket_directories="${TMPDIR}/sockets" \
+
+
+SOCKETDIR="${TMPDIR}/sockets"
+mkdir "${SOCKETDIR}"
+
+echo -n "Launching Postgres service"
+
+cat - >> "$TMPDIR/postgresql.conf" <<EOF
+unix_socket_directories='${TMPDIR}/sockets'
+fsync=off
+max_wal_senders=0
+synchronous_commit=off
+wal_level=minimal
+listen_addresses=''
+EOF
+
+grep -v host \
+ < "$TMPDIR/pg_hba.conf" \
+ > "$TMPDIR/pg_hba.conf.new"
+mv "$TMPDIR/pg_hba.conf.new" "$TMPDIR/pg_hba.conf"
+"${POSTGRES_PATH}/pg_ctl" \
+ -D "$TMPDIR" \
+ -l "${MY_TMP_DIR}/postgres.log" \
+ start \
+ > "${MY_TMP_DIR}/postgres-start.log" \
+ 2> "${MY_TMP_DIR}/postgres-start.err"
+echo " DONE"
+PGHOST="$TMPDIR/sockets"
+export PGHOST
+MYDIR="${MY_TMP_DIR}/basedb"
+mkdir -p "${MYDIR}"
+
+if [ -z $REUSE_BASEDB_DIR ]
+then
+ echo "Generating fresh database at $MYDIR"
+
+ if faketime -f '-1 d' ./generate-auditor-basedb.sh -d "$MYDIR/$DB"
then
- MYDIR=`mktemp -d /tmp/taler-auditor-basedbXXXXXX`
- echo " FOUND. Generating fresh database at $MYDIR"
- if ./generate-auditor-basedb.sh $MYDIR/basedb
- then
- check_with_database $MYDIR/basedb
- if test x$fail != x0
- then
- exit $fail
- else
- echo "Cleaning up $MYDIR..."
- rm -rf $MYDIR || echo "Removing $MYDIR failed"
- fi
- else
- echo "Generation failed, running only on existing DB"
- fi
+ echo -n "Reset 'auditor-basedb' database at $PGHOST ..."
+ dropdb "auditor-basedb" >/dev/null 2>/dev/null || true
+ createdb "auditor-basedb" || exit_skip "Could not create database '$BASEDB' at $PGHOST"
+ echo " DONE"
else
- echo " NOT FOUND, running only on existing DB"
+ echo "Generation failed"
+ exit 1
fi
+else
+ echo "Reusing existing database from ${REUSE_BASEDB_DIR}"
+ cp -r "${REUSE_BASEDB_DIR}/basedb"/* "${MYDIR}/"
fi
-check_with_database "auditor-basedb"
+check_with_database "$MYDIR/$DB"
+if [ "$fail" != "0" ]
+then
+ exit "$fail"
+fi
+
+if [ -z $REUSE_BASEDB_DIR ]
+then
+ echo "Run 'export REUSE_BASEDB_DIR=${MY_TMP_DIR}' to re-run tests against the same database"
+fi
-exit $fail
+exit 0
diff --git a/src/auditor/test-kyc.sh b/src/auditor/test-kyc.sh
new file mode 100755
index 000000000..2c4fa6594
--- /dev/null
+++ b/src/auditor/test-kyc.sh
@@ -0,0 +1,751 @@
+#!/bin/bash
+#
+# This file is part of TALER
+# 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
+# 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, If not, see <http://www.gnu.org/license>
+#
+#
+# shellcheck disable=SC2317
+# shellcheck disable=SC1091
+#
+#
+# Setup database which was generated from a perfectly normal
+# exchange-wallet interaction with KYC enabled and transactions
+# blocked due to KYC and run the auditor against it.
+#
+# Check that the auditor report is as expected.
+#
+# Requires 'jq' tool and Postgres superuser rights!
+#
+set -eu
+#set -x
+
+# Set of numbers for all the testcases.
+# When adding new tests, increase the last number:
+ALL_TESTS=$(seq 0 1)
+
+# $TESTS determines which tests we should run.
+# This construction is used to make it easy to
+# only run a subset of the tests. To only run a subset,
+# pass the numbers of the tests to run as the FIRST
+# argument to test-kyc.sh, i.e.:
+#
+# $ test-kyc.sh "1 3"
+#
+# to run tests 1 and 3 only. By default, all tests are run.
+#
+TESTS=${1:-$ALL_TESTS}
+
+# Global variable to run the auditor processes under valgrind
+# VALGRIND=valgrind
+VALGRIND=""
+
+# Number of seconds to let libeuifn background
+# tasks apply a cycle of payment submission and
+# history request.
+LIBEUFIN_SETTLE_TIME=1
+
+. setup.sh
+
+
+# Cleanup exchange and libeufin between runs.
+function cleanup()
+{
+ if test ! -z "${EPID:-}"
+ then
+ echo -n "Stopping exchange $EPID..."
+ kill -TERM "$EPID"
+ wait "$EPID" || true
+ echo "DONE"
+ unset EPID
+ fi
+ stop_libeufin
+}
+
+# Cleanup to run whenever we exit
+function exit_cleanup()
+{
+ echo "Running exit-cleanup"
+ 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
+ fi
+ cleanup
+ for n in $(jobs -p)
+ do
+ kill "$n" 2> /dev/null || true
+ done
+ wait || true
+ echo "DONE"
+}
+
+# Install cleanup handler (except for kill -9)
+trap exit_cleanup EXIT
+
+
+
+# Operations to run before the actual audit
+function pre_audit () {
+ # Launch bank
+ echo -n "Launching bank"
+ launch_libeufin
+ for n in $(seq 1 80)
+ do
+ echo -n "."
+ sleep 0.1
+ OK=1
+ wget http://localhost:18082/ \
+ -o /dev/null \
+ -O /dev/null \
+ >/dev/null \
+ && break
+ OK=0
+ done
+ if [ 1 != "$OK" ]
+ then
+ exit_skip "Failed to launch Sandbox"
+ fi
+ sleep "$LIBEUFIN_SETTLE_TIME"
+ for n in $(seq 1 80)
+ do
+ echo -n "."
+ sleep 0.1
+ OK=1
+ wget http://localhost:8082/ \
+ -o /dev/null \
+ -O /dev/null \
+ >/dev/null \
+ && break
+ OK=0
+ done
+ if [ 1 != "$OK" ]
+ then
+ exit_skip "Failed to launch Nexus"
+ fi
+ echo " DONE"
+ if test "${1:-no}" = "aggregator"
+ then
+ echo -n "Running exchange aggregator ..."
+ taler-exchange-aggregator \
+ -y \
+ -L "INFO" \
+ -t \
+ -c "$CONF" \
+ 2> "${MY_TMP_DIR}/aggregator.log" \
+ || exit_fail "FAIL"
+ echo " DONE"
+ echo -n "Running exchange closer ..."
+ taler-exchange-closer \
+ -L "INFO" \
+ -t \
+ -c "$CONF" \
+ 2> "${MY_TMP_DIR}/closer.log" \
+ || exit_fail "FAIL"
+ echo " DONE"
+ echo -n "Running exchange transfer ..."
+ taler-exchange-transfer \
+ -L "INFO" \
+ -t \
+ -c "$CONF" \
+ 2> "${MY_TMP_DIR}/transfer.log" \
+ || exit_fail "FAIL"
+ echo " DONE"
+ fi
+}
+
+# actual audit run
+function audit_only () {
+ # Run the auditor!
+ echo -n "Running audit(s) ..."
+
+ # Restart so that first run is always fresh, and second one is incremental
+ taler-auditor-dbinit \
+ -r \
+ -c "$CONF"
+ $VALGRIND taler-helper-auditor-aggregation \
+ -L DEBUG \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-aggregation.json \
+ 2> "${MY_TMP_DIR}/test-audit-aggregation.log" \
+ || exit_fail "aggregation audit failed"
+ echo -n "."
+ $VALGRIND taler-helper-auditor-aggregation \
+ -L DEBUG \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-aggregation-inc.json \
+ 2> "${MY_TMP_DIR}/test-audit-aggregation-inc.log" \
+ || exit_fail "incremental aggregation audit failed"
+ echo -n "."
+ $VALGRIND taler-helper-auditor-coins \
+ -L DEBUG \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-coins.json \
+ 2> "${MY_TMP_DIR}/test-audit-coins.log" \
+ || exit_fail "coin audit failed"
+ echo -n "."
+ $VALGRIND taler-helper-auditor-coins \
+ -L DEBUG \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-coins-inc.json \
+ 2> "${MY_TMP_DIR}/test-audit-coins-inc.log" \
+ || exit_fail "incremental coin audit failed"
+ echo -n "."
+ $VALGRIND taler-helper-auditor-deposits \
+ -L DEBUG \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-deposits.json \
+ 2> "${MY_TMP_DIR}/test-audit-deposits.log" \
+ || exit_fail "deposits audit failed"
+ echo -n "."
+ $VALGRIND taler-helper-auditor-deposits \
+ -L DEBUG \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-deposits-inc.json \
+ 2> "${MY_TMP_DIR}/test-audit-deposits-inc.log" \
+ || exit_fail "incremental deposits audit failed"
+ echo -n "."
+ $VALGRIND taler-helper-auditor-reserves \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-reserves.json \
+ 2> "${MY_TMP_DIR}/test-audit-reserves.log" \
+ || exit_fail "reserves audit failed"
+ echo -n "."
+ $VALGRIND taler-helper-auditor-reserves \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-reserves-inc.json \
+ 2> "${MY_TMP_DIR}/test-audit-reserves-inc.log" \
+ || exit_fail "incremental reserves audit failed"
+ echo -n "."
+ rm -f "${MY_TMP_DIR}/test-wire-audit.log"
+ thaw() {
+ $VALGRIND taler-helper-auditor-wire \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-wire.json \
+ 2>> "${MY_TMP_DIR}/test-wire-audit.log"
+ }
+ thaw || ( echo -e " FIRST CALL TO taler-helper-auditor-wire FAILED,\nRETRY AFTER TWO SECONDS..." | tee -a "${MY_TMP_DIR}/test-wire-audit.log"
+ sleep 2
+ thaw || exit_fail "wire audit failed" )
+ echo -n "."
+ $VALGRIND taler-helper-auditor-wire \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-wire-inc.json \
+ 2> "${MY_TMP_DIR}/test-wire-audit-inc.log" \
+ || exit_fail "wire audit inc failed"
+ echo -n "."
+
+ echo " DONE"
+}
+
+
+# Cleanup to run after the auditor
+function post_audit () {
+ taler-exchange-dbinit \
+ -c "$CONF" \
+ -g \
+ || exit_fail "exchange DB GC failed"
+
+ cleanup
+ echo -n "TeXing ."
+ taler-helper-auditor-render.py \
+ test-audit-aggregation.json \
+ test-audit-coins.json \
+ test-audit-deposits.json \
+ test-audit-reserves.json \
+ test-audit-wire.json \
+ < ../../contrib/auditor-report.tex.j2 \
+ > test-report.tex \
+ || exit_fail "Renderer failed"
+
+ echo -n "."
+ timeout 10 pdflatex test-report.tex \
+ >/dev/null \
+ || exit_fail "pdflatex failed"
+ echo -n "."
+ timeout 10 pdflatex test-report.tex \
+ >/dev/null
+ echo " DONE"
+}
+
+
+# Run audit process on current database, including report
+# generation. Pass "aggregator" as $1 to run
+# $ taler-exchange-aggregator
+# before auditor (to trigger pending wire transfers).
+# Pass "drain" as $2 to run a drain operation as well.
+function run_audit () {
+ pre_audit "${1:-no}"
+ if test "${2:-no}" = "drain"
+ then
+ echo -n "Starting exchange..."
+ taler-exchange-httpd \
+ -c "${CONF}" \
+ -L INFO \
+ 2> "${MY_TMP_DIR}/exchange-httpd-drain.err" &
+ EPID=$!
+
+ # Wait for all services to be available
+ for n in $(seq 1 50)
+ do
+ echo -n "."
+ sleep 0.1
+ OK=0
+ # exchange
+ wget "http://localhost:8081/seed" \
+ -o /dev/null \
+ -O /dev/null \
+ >/dev/null \
+ || continue
+ OK=1
+ break
+ done
+ echo "... DONE."
+ export CONF
+
+ echo -n "Running taler-exchange-offline drain "
+
+ taler-exchange-offline \
+ -L DEBUG \
+ -c "${CONF}" \
+ drain TESTKUDOS:0.1 \
+ exchange-account-1 payto://iban/SANDBOXX/DE360679?receiver-name=Exchange+Drain \
+ upload \
+ 2> "${MY_TMP_DIR}/taler-exchange-offline-drain.log" \
+ || exit_fail "offline draining failed"
+ kill -TERM "$EPID"
+ wait "$EPID" || true
+ unset EPID
+ echo -n "Running taler-exchange-drain ..."
+ printf "\n" | taler-exchange-drain \
+ -L DEBUG \
+ -c "$CONF" \
+ 2> "${MY_TMP_DIR}/taler-exchange-drain.log" \
+ || exit_fail "FAIL"
+ echo " DONE"
+
+ echo -n "Running taler-exchange-transfer ..."
+ taler-exchange-transfer \
+ -L INFO \
+ -t \
+ -c "$CONF" \
+ 2> "${MY_TMP_DIR}/drain-transfer.log" \
+ || exit_fail "FAIL"
+ echo " DONE"
+
+ audit_only
+ post_audit
+}
+
+
+# Do a full reload of the (original) database
+function full_reload()
+{
+ echo -n "Doing full reload of the database (loading ${BASEDB}.sql into $DB at $PGHOST)... "
+ dropdb "$DB" 2> /dev/null || true
+ createdb -T template0 "$DB" \
+ || exit_skip "could not create database $DB (at $PGHOST)"
+ # Import pre-generated database, -q(ietly) using single (-1) transaction
+ psql -Aqt "$DB" \
+ -q \
+ -1 \
+ -f "${BASEDB}.sql" \
+ > /dev/null \
+ || exit_skip "Failed to load database $DB from ${BASEDB}.sql"
+ echo "DONE"
+ # Technically, this call shouldn't be needed as libeufin should already be stopped here...
+ stop_libeufin
+}
+
+
+function test_0() {
+
+ echo "===========0: normal run with aggregator==========="
+ run_audit aggregator
+ echo "Checking output"
+ # if an emergency was detected, that is a bug and we should fail
+ echo -n "Test for emergencies... "
+ jq -e .emergencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency detected in ordinary run" || echo PASS
+ echo -n "Test for deposit confirmation emergencies... "
+ jq -e .deposit_confirmation_inconsistencies[0] < test-audit-deposits.json > /dev/null && exit_fail "Unexpected deposit confirmation inconsistency detected" || echo PASS
+ echo -n "Test for emergencies by count... "
+ jq -e .emergencies_by_count[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency by count detected in ordinary run" || echo PASS
+
+ echo -n "Test for wire inconsistencies... "
+ jq -e .wire_out_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire out inconsistency detected in ordinary run"
+ jq -e .reserve_in_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected reserve in inconsistency detected in ordinary run"
+ jq -e .misattribution_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected misattribution inconsistency detected in ordinary run"
+ jq -e .row_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected row inconsistency detected in ordinary run"
+ jq -e .denomination_key_validity_withdraw_inconsistencies[0] < test-audit-reserves.json > /dev/null && exit_fail "Unexpected denomination key withdraw inconsistency detected in ordinary run"
+ jq -e .row_minor_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected minor row inconsistency detected in ordinary run"
+ jq -e .lag_details[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected lag detected in ordinary run"
+ jq -e .wire_format_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire format inconsistencies detected in ordinary run"
+
+
+ # TODO: check operation balances are correct (once we have all transaction types and wallet is deterministic)
+ # TODO: check revenue summaries are correct (once we have all transaction types and wallet is deterministic)
+
+ echo PASS
+
+ LOSS=$(jq -r .total_bad_sig_loss < test-audit-aggregation.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total bad sig loss from aggregation, got unexpected loss of $LOSS"
+ fi
+ LOSS=$(jq -r .irregular_loss < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total bad sig loss from coins, got unexpected loss of $LOSS"
+ fi
+ LOSS=$(jq -r .total_bad_sig_loss < test-audit-reserves.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total bad sig loss from reserves, got unexpected loss of $LOSS"
+ fi
+
+ echo -n "Test for wire amounts... "
+ WIRED=$(jq -r .total_wire_in_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_in_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_misattribution_in < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total misattribution in wrong, got $WIRED"
+ fi
+ echo "PASS"
+
+ echo -n "Checking for unexpected arithmetic differences "
+ LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-aggregation.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from aggregations, got unexpected plus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-aggregation.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from aggregation, got unexpected minus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from coins, got unexpected plus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from coins, got unexpected minus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-reserves.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from reserves, got unexpected plus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-reserves.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from reserves, got unexpected minus of $LOSS"
+ fi
+
+ jq -e .amount_arithmetic_inconsistencies[0] < test-audit-aggregation.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from aggregations detected in ordinary run"
+ jq -e .amount_arithmetic_inconsistencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from coins detected in ordinary run"
+ jq -e .amount_arithmetic_inconsistencies[0] < test-audit-reserves.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from reserves detected in ordinary run"
+ echo "PASS"
+
+ echo -n "Checking for unexpected wire out differences "
+ jq -e .wire_out_inconsistencies[0] < test-audit-aggregation.json > /dev/null && exit_fail "Unexpected wire out inconsistencies detected in ordinary run"
+ echo "PASS"
+
+ # cannot easily undo aggregator, hence full reload
+ full_reload
+
+}
+
+
+# Run without aggregator, hence auditor should detect wire
+# transfer lag!
+function test_1() {
+
+ echo "===========1: normal run==========="
+ run_audit
+
+ echo "Checking output"
+ # if an emergency was detected, that is a bug and we should fail
+ echo -n "Test for emergencies... "
+ jq -e .emergencies[0] \
+ < test-audit-coins.json \
+ > /dev/null \
+ && exit_fail "Unexpected emergency detected in ordinary run";
+ echo "PASS"
+ echo -n "Test for emergencies by count... "
+ jq -e .emergencies_by_count[0] \
+ < test-audit-coins.json \
+ > /dev/null \
+ && exit_fail "Unexpected emergency by count detected in ordinary run"
+ echo "PASS"
+
+ echo -n "Test for wire inconsistencies... "
+ jq -e .wire_out_amount_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected wire out inconsistency detected in ordinary run"
+ jq -e .reserve_in_amount_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected reserve in inconsistency detected in ordinary run"
+ jq -e .misattribution_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected misattribution inconsistency detected in ordinary run"
+ jq -e .row_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected row inconsistency detected in ordinary run"
+ jq -e .row_minor_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected minor row inconsistency detected in ordinary run"
+ jq -e .wire_format_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected wire format inconsistencies detected in ordinary run"
+
+ # TODO: check operation balances are correct (once we have all transaction types and wallet is deterministic)
+ # TODO: check revenue summaries are correct (once we have all transaction types and wallet is deterministic)
+
+ echo "PASS"
+
+ echo -n "Check for lag detection... "
+
+ # Check wire transfer lag reported (no aggregator!)
+ # NOTE: This test is EXPECTED to fail for ~1h after
+ # re-generating the test database as we do not
+ # report lag of less than 1h (see GRACE_PERIOD in
+ # taler-helper-auditor-wire.c)
+ jq -e .lag_details[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ || exit_fail "Lag not detected in run without aggregator"
+
+ LAG=$(jq -r .total_amount_lag < test-audit-wire.json)
+ if [ "$LAG" = "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total lag to be non-zero"
+ fi
+ echo "PASS"
+
+
+ echo -n "Test for wire amounts... "
+ WIRED=$(jq -r .total_wire_in_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_in_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_misattribution_in < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total misattribution in wrong, got $WIRED"
+ fi
+ # Database was unmodified, no need to undo
+ echo "OK"
+}
+
+
+
+# *************** Main test loop starts here **************
+
+
+# Run all the tests against the database given in $1.
+# Sets $fail to 0 on success, non-zero on failure.
+function check_with_database()
+{
+ BASEDB="$1"
+ CONF="$1.conf"
+ echo "Running test suite with database $BASEDB using configuration $CONF"
+ MASTER_PRIV_FILE="${BASEDB}.mpriv"
+ taler-config \
+ -f \
+ -c "${CONF}" \
+ -s exchange-offline \
+ -o MASTER_PRIV_FILE \
+ -V "${MASTER_PRIV_FILE}"
+ MASTER_PUB=$(gnunet-ecc -p "$MASTER_PRIV_FILE")
+
+ echo "MASTER PUB is ${MASTER_PUB} using file ${MASTER_PRIV_FILE}"
+
+ # Load database
+ full_reload
+
+ # Run test suite
+ fail=0
+ for i in $TESTS
+ do
+ "test_$i"
+ if test 0 != $fail
+ then
+ break
+ fi
+ done
+ echo "Cleanup (disabled, leaving database $DB behind)"
+ # dropdb $DB
+}
+
+
+
+
+# *************** Main logic starts here **************
+
+# ####### Setup globals ######
+# Postgres database to use (must match configuration file)
+export DB="auditor-basedb"
+
+# test required commands exist
+echo "Testing for jq"
+jq -h > /dev/null || exit_skip "jq required"
+echo "Testing for faketime"
+faketime -h > /dev/null || exit_skip "faketime required"
+# NOTE: really check for all three libeufin commands?
+echo "Testing for libeufin-bank"
+libeufin-bank --help >/dev/null 2> /dev/null </dev/null || exit_skip "libeufin-bank required"
+echo "Testing for pdflatex"
+which pdflatex > /dev/null </dev/null || exit_skip "pdflatex required"
+echo "Testing for taler-wallet-cli"
+taler-wallet-cli -h >/dev/null </dev/null 2>/dev/null || exit_skip "taler-wallet-cli required"
+
+
+echo -n "Testing for Postgres"
+# Available directly in path?
+INITDB_BIN=$(command -v initdb) || true
+if [[ -n "$INITDB_BIN" ]]; then
+ echo " FOUND (in path) at $INITDB_BIN"
+else
+ HAVE_INITDB=$(find /usr -name "initdb" | head -1 2> /dev/null | grep postgres) \
+ || exit_skip " MISSING"
+ echo " FOUND at $(dirname "$HAVE_INITDB")"
+ INITDB_BIN=$(echo "$HAVE_INITDB" | grep bin/initdb | grep postgres | sort -n | tail -n1)
+fi
+POSTGRES_PATH=$(dirname "$INITDB_BIN")
+
+MY_TMP_DIR=$(mktemp -d /tmp/taler-auditor-basedbXXXXXX)
+echo "Using $MY_TMP_DIR for logging and temporary data"
+TMPDIR="$MY_TMP_DIR/postgres"
+mkdir -p "$TMPDIR"
+echo -n "Setting up Postgres DB at $TMPDIR ..."
+$INITDB_BIN \
+ --no-sync \
+ --auth=trust \
+ -D "${TMPDIR}" \
+ > "${MY_TMP_DIR}/postgres-dbinit.log" \
+ 2> "${MY_TMP_DIR}/postgres-dbinit.err"
+echo "DONE"
+SOCKETDIR="${TMPDIR}/sockets"
+mkdir "${SOCKETDIR}"
+echo -n "Launching Postgres service"
+cat - >> "$TMPDIR/postgresql.conf" <<EOF
+unix_socket_directories='${TMPDIR}/sockets'
+fsync=off
+max_wal_senders=0
+synchronous_commit=off
+wal_level=minimal
+listen_addresses=''
+EOF
+grep -v host \
+ < "$TMPDIR/pg_hba.conf" \
+ > "$TMPDIR/pg_hba.conf.new"
+mv "$TMPDIR/pg_hba.conf.new" "$TMPDIR/pg_hba.conf"
+"${POSTGRES_PATH}/pg_ctl" \
+ -D "$TMPDIR" \
+ -l /dev/null \
+ start \
+ > "${MY_TMP_DIR}/postgres-start.log" \
+ 2> "${MY_TMP_DIR}/postgres-start.err"
+echo " DONE"
+PGHOST="$TMPDIR/sockets"
+export PGHOST
+
+MYDIR="${MY_TMP_DIR}/basedb"
+mkdir -p "${MYDIR}"
+echo "Generating fresh database at $MYDIR"
+if faketime -f '-1 d' ./generate-auditor-basedb.sh \
+ -c generate-kyc-basedb.conf \
+ -d "$MYDIR/$DB"
+then
+ echo -n "Reset 'auditor-basedb' database at $PGHOST ..."
+ dropdb "auditor-basedb" >/dev/null 2>/dev/null || true
+ createdb "auditor-basedb" || exit_skip "Could not create database '$BASEDB' at $PGHOST"
+ echo " DONE"
+ check_with_database "$MYDIR/$DB"
+ if [ "$fail" != "0" ]
+ then
+ exit "$fail"
+ fi
+else
+ echo "Generation failed"
+ exit 1
+fi
+
+exit 0
diff --git a/src/auditor/test-revocation.sh b/src/auditor/test-revocation.sh
index 714fcc8a1..277b102fb 100755
--- a/src/auditor/test-revocation.sh
+++ b/src/auditor/test-revocation.sh
@@ -1,15 +1,33 @@
#!/bin/bash
+#
+# 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, If not, see <http://www.gnu.org/license>
+#
# Setup database which was generated from a exchange-wallet interaction
# with revocations and run the auditor against it.
#
# Check that the auditor report is as expected.
#
+# shellcheck disable=SC2317
+#
# Requires 'jq' tool and Postgres superuser rights!
set -eu
+# set -x
# Set of numbers for all the testcases.
# When adding new tests, increase the last number:
-ALL_TESTS=`seq 0 4`
+ALL_TESTS=$(seq 0 4)
# $TESTS determines which tests we should run.
# This construction is used to make it easy to
@@ -26,51 +44,114 @@ TESTS=${1:-$ALL_TESTS}
# Global variable to run the auditor processes under valgrind
# VALGRIND=valgrind
VALGRIND=""
+LOGLEVEL="INFO"
-# Exit, with status code "skip" (no 'real' failure)
-function exit_skip() {
- echo $1
- exit 77
+. setup.sh
+
+# Cleanup to run whenever we exit
+function cleanup()
+{
+ if [ ! -z "${EPID:-}" ]
+ then
+ echo -n "Stopping exchange $EPID..."
+ kill -TERM "$EPID"
+ wait "$EPID"
+ echo " DONE"
+ unset EPID
+ fi
+ stop_libeufin
}
-# Exit, with error message (hard failure)
-function exit_fail() {
- echo $1
- kill `jobs -p` >/dev/null 2>/dev/null || true
+# Cleanup to run whenever we exit
+function exit_cleanup()
+{
+ echo "Running exit-cleanup"
+ if [ ! -z "${POSTGRES_PATH:-}" ]
+ then
+ echo "Stopping Postgres at ${POSTGRES_PATH}"
+ "${POSTGRES_PATH}/pg_ctl" \
+ -D "$TMPDIR" \
+ -l /dev/null \
+ stop \
+ &> /dev/null \
+ || true
+ fi
+ cleanup
+ for n in $(jobs -p)
+ do
+ kill "$n" 2> /dev/null || true
+ done
wait
- exit 1
+ echo "DONE"
}
+# Install cleanup handler (except for kill -9)
+trap exit_cleanup EXIT
+
# Operations to run before the actual audit
function pre_audit () {
# Launch bank
echo -n "Launching bank "
- taler-bank-manage-testing $CONF postgres:///$DB serve-http 2>bank.err >bank.log &
- for n in `seq 1 20`
+ launch_libeufin
+ for n in $(seq 1 80)
do
echo -n "."
sleep 0.1
OK=1
- wget http://localhost:8082/ -o /dev/null -O /dev/null >/dev/null && break
+ wget http://localhost:18082/ \
+ -o /dev/null \
+ -O /dev/null \
+ >/dev/null && break
OK=0
done
- if [ 1 != $OK ]
+ if [ 1 != "$OK" ]
then
- exit_skip "Failed to launch bank"
+ exit_skip "Failed to launch Sandbox"
+ fi
+ for n in $(seq 1 80)
+ do
+ echo -n "."
+ sleep 0.1
+ OK=1
+ wget http://localhost:8082/ \
+ -o /dev/null \
+ -O /dev/null \
+ >/dev/null && break
+ OK=0
+ done
+ if [ 1 != "$OK" ]
+ then
+ exit_skip "Failed to launch Nexus"
fi
echo " DONE"
-
- if test ${1:-no} = "aggregator"
+ if [ "${1:-no}" = "aggregator" ]
then
- echo -n "Running exchange aggregator ..."
- taler-exchange-aggregator -L INFO -t -c $CONF 2> aggregator.log || exit_fail "FAIL"
+ export CONF
+ echo -n "Running exchange aggregator ... (config: $CONF)"
+ taler-exchange-aggregator \
+ -L "$LOGLEVEL" \
+ -t \
+ -c "$CONF" \
+ -y \
+ 2> "${MY_TMP_DIR}/aggregator.log" \
+ || exit_fail "FAIL"
echo " DONE"
echo -n "Running exchange closer ..."
- taler-exchange-closer -L INFO -t -c $CONF 2> closer.log || exit_fail "FAIL"
+ taler-exchange-closer \
+ -L "$LOGLEVEL" \
+ -t \
+ -c "$CONF" \
+ 2> "${MY_TMP_DIR}/closer.log" \
+ || exit_fail "FAIL"
echo " DONE"
echo -n "Running exchange transfer ..."
- taler-exchange-transfer -L INFO -t -c $CONF 2> transfer.log || exit_fail "FAIL"
+ taler-exchange-transfer \
+ -L "$LOGLEVEL" \
+ -t \
+ -c "$CONF" \
+ 2> "${MY_TMP_DIR}/transfer.log" \
+ || exit_fail "FAIL"
echo " DONE"
fi
}
@@ -78,48 +159,120 @@ function pre_audit () {
# actual audit run
function audit_only () {
# Run the auditor!
- echo -n "Running audit(s) ..."
+ echo -n "Running audit(s) ... (conf is $CONF)"
# Restart so that first run is always fresh, and second one is incremental
- taler-auditor-dbinit -r -c $CONF
- $VALGRIND taler-helper-auditor-aggregation -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-aggregation.json 2> test-audit-aggregation.log || exit_fail "aggregation audit failed"
+ taler-auditor-dbinit \
+ -r \
+ -c "$CONF"
+ $VALGRIND taler-helper-auditor-aggregation \
+ -L "$LOGLEVEL" \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-aggregation.json \
+ 2> test-audit-aggregation.log \
+ || exit_fail "aggregation audit failed"
echo -n "."
- $VALGRIND taler-helper-auditor-aggregation -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-aggregation-inc.json 2> test-audit-aggregation-inc.log || exit_fail "incremental aggregation audit failed"
+ $VALGRIND taler-helper-auditor-aggregation \
+ -L "$LOGLEVEL" \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-aggregation-inc.json \
+ 2> test-audit-aggregation-inc.log \
+ || exit_fail "incremental aggregation audit failed"
echo -n "."
- $VALGRIND taler-helper-auditor-coins -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-coins.json 2> test-audit-coins.log || exit_fail "coin audit failed"
+ $VALGRIND taler-helper-auditor-coins \
+ -L "$LOGLEVEL" \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-coins.json \
+ 2> test-audit-coins.log \
+ || exit_fail "coin audit failed"
echo -n "."
- $VALGRIND taler-helper-auditor-coins -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-coins-inc.json 2> test-audit-coins-inc.log || exit_fail "incremental coin audit failed"
+ $VALGRIND taler-helper-auditor-coins \
+ -L "$LOGLEVEL" \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-coins-inc.json \
+ 2> test-audit-coins-inc.log \
+ || exit_fail "incremental coin audit failed"
echo -n "."
- $VALGRIND taler-helper-auditor-deposits -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-deposits.json 2> test-audit-deposits.log || exit_fail "deposits audit failed"
+ $VALGRIND taler-helper-auditor-deposits \
+ -L "$LOGLEVEL" \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-deposits.json \
+ 2> test-audit-deposits.log \
+ || exit_fail "deposits audit failed"
echo -n "."
- $VALGRIND taler-helper-auditor-deposits -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-deposits-inc.json 2> test-audit-deposits-inc.log || exit_fail "incremental deposits audit failed"
+ $VALGRIND taler-helper-auditor-deposits \
+ -L "$LOGLEVEL" \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-deposits-inc.json \
+ 2> test-audit-deposits-inc.log \
+ || exit_fail "incremental deposits audit failed"
echo -n "."
- $VALGRIND taler-helper-auditor-reserves -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-reserves.json 2> test-audit-reserves.log || exit_fail "reserves audit failed"
+ $VALGRIND taler-helper-auditor-reserves \
+ -i \
+ -L "$LOGLEVEL" \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-reserves.json \
+ 2> test-audit-reserves.log \
+ || exit_fail "reserves audit failed"
echo -n "."
- $VALGRIND taler-helper-auditor-reserves -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-reserves-inc.json 2> test-audit-reserves-inc.log || exit_fail "incremental reserves audit failed"
+ $VALGRIND taler-helper-auditor-reserves \
+ -i \
+ -L "$LOGLEVEL" \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-reserves-inc.json \
+ 2> test-audit-reserves-inc.log \
+ || exit_fail "incremental reserves audit failed"
echo -n "."
- $VALGRIND taler-helper-auditor-wire -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-wire.json 2> test-wire-audit.log || exit_fail "wire audit failed"
+ $VALGRIND taler-helper-auditor-wire \
+ -i \
+ -L "$LOGLEVEL" \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-wire.json \
+ 2> test-wire-audit.log \
+ || exit_fail "wire audit failed"
echo -n "."
- $VALGRIND taler-helper-auditor-wire -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-wire-inc.json 2> test-wire-audit-inc.log || exit_fail "wire audit failed"
+ $VALGRIND taler-helper-auditor-wire \
+ -i \
+ -L "$LOGLEVEL" \
+ -c "$CONF" \
+ -m "$MASTER_PUB" \
+ > test-audit-wire-inc.json \
+ 2> test-wire-audit-inc.log \
+ || exit_fail "wire audit failed"
echo -n "."
-
echo " DONE"
}
# Cleanup to run after the auditor
function post_audit () {
- kill -TERM `jobs -p` >/dev/null 2>/dev/null || true
- echo -n "Waiting for servers to die ..."
- wait
- echo "DONE"
+ cleanup
echo -n "TeXing ."
- taler-helper-auditor-render.py test-audit-aggregation.json test-audit-coins.json test-audit-deposits.json test-audit-reserves.json test-audit-wire.json < ../../contrib/auditor-report.tex.j2 > test-report.tex || exit_fail "Renderer failed"
-
+ taler-helper-auditor-render.py \
+ test-audit-aggregation.json \
+ test-audit-coins.json \
+ test-audit-deposits.json \
+ test-audit-reserves.json \
+ test-audit-wire.json \
+ < ../../contrib/auditor-report.tex.j2 \
+ > test-report.tex \
+ || exit_fail "Renderer failed"
echo -n "."
- timeout 10 pdflatex test-report.tex >/dev/null || exit_fail "pdflatex failed"
+ timeout 10 pdflatex test-report.tex \
+ >/dev/null \
+ || exit_fail "pdflatex failed"
echo -n "."
- timeout 10 pdflatex test-report.tex >/dev/null
+ timeout 10 pdflatex test-report.tex \
+ >/dev/null
echo " DONE"
}
@@ -129,143 +282,158 @@ function post_audit () {
# $ taler-exchange-aggregator
# before auditor (to trigger pending wire transfers).
function run_audit () {
- pre_audit ${1:-no}
+ pre_audit "${1:-no}"
audit_only
post_audit
-
}
# Do a full reload of the (original) database
-full_reload()
+function full_reload()
{
echo -n "Doing full reload of the database... "
- dropdb $DB 2> /dev/null || true
- createdb -T template0 $DB || exit_skip "could not create database"
+ dropdb "$DB" 2> /dev/null || true
+ createdb -T template0 "$DB" \
+ || exit_skip "could not create database $DB (at $PGHOST)"
# Import pre-generated database, -q(ietly) using single (-1) transaction
- psql -Aqt $DB -q -1 -f ${BASEDB}.sql > /dev/null || exit_skip "Failed to load database"
+ psql -Aqt "$DB" \
+ -q \
+ -1 \
+ -f "${BASEDB}.sql" \
+ > /dev/null \
+ || exit_skip "Failed to load database $DB from ${BASEDB}.sql"
echo "DONE"
}
function test_0() {
+ echo "===========0: normal run with aggregator==========="
+ run_audit aggregator
+
+ echo "Checking output"
+ # if an emergency was detected, that is a bug and we should fail
+ echo -n "Test for emergencies... "
+ jq -e .emergencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency detected in ordinary run" || echo PASS
+ echo -n "Test for deposit confirmation emergencies... "
+ jq -e .deposit_confirmation_inconsistencies[0] < test-audit-deposits.json > /dev/null && exit_fail "Unexpected deposit confirmation inconsistency detected" || echo PASS
+ echo -n "Test for emergencies by count... "
+ jq -e .emergencies_by_count[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency by count detected in ordinary run" || echo PASS
+
+ echo -n "Test for wire inconsistencies... "
+ jq -e .wire_out_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire out inconsistency detected in ordinary run"
+ jq -e .reserve_in_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected reserve in inconsistency detected in ordinary run"
+ jq -e .misattribution_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected misattribution inconsistency detected in ordinary run"
+ jq -e .row_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected row inconsistency detected in ordinary run"
+ jq -e .denomination_key_validity_withdraw_inconsistencies[0] < test-audit-reserves.json > /dev/null && exit_fail "Unexpected denomination key withdraw inconsistency detected in ordinary run"
+ jq -e .row_minor_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected minor row inconsistency detected in ordinary run"
+ jq -e .lag_details[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected lag detected in ordinary run"
+ jq -e .wire_format_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire format inconsistencies detected in ordinary run"
+
+
+ # TODO: check operation balances are correct (once we have all transaction types and wallet is deterministic)
+ # TODO: check revenue summaries are correct (once we have all transaction types and wallet is deterministic)
+
+ echo PASS
+
+ LOSS=$(jq -r .total_bad_sig_loss < test-audit-aggregation.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total bad sig loss from aggregation, got unexpected loss of $LOSS"
+ fi
+ LOSS=$(jq -r .irregular_loss < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total bad sig loss from coins, got unexpected loss of $LOSS"
+ fi
+ LOSS=$(jq -r .total_bad_sig_loss < test-audit-reserves.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong total bad sig loss from reserves, got unexpected loss of $LOSS"
+ fi
-echo "===========0: normal run with aggregator==========="
-run_audit aggregator
-
-echo "Checking output"
-# if an emergency was detected, that is a bug and we should fail
-echo -n "Test for emergencies... "
-jq -e .emergencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency detected in ordinary run" || echo PASS
-echo -n "Test for deposit confirmation emergencies... "
-jq -e .deposit_confirmation_inconsistencies[0] < test-audit-deposits.json > /dev/null && exit_fail "Unexpected deposit confirmation inconsistency detected" || echo PASS
-echo -n "Test for emergencies by count... "
-jq -e .emergencies_by_count[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency by count detected in ordinary run" || echo PASS
-
-echo -n "Test for wire inconsistencies... "
-jq -e .wire_out_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire out inconsistency detected in ordinary run"
-jq -e .reserve_in_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected reserve in inconsistency detected in ordinary run"
-jq -e .missattribution_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected missattribution inconsistency detected in ordinary run"
-jq -e .row_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected row inconsistency detected in ordinary run"
-jq -e .denomination_key_validity_withdraw_inconsistencies[0] < test-audit-reserves.json > /dev/null && exit_fail "Unexpected denomination key withdraw inconsistency detected in ordinary run"
-jq -e .row_minor_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected minor row inconsistency detected in ordinary run"
-jq -e .lag_details[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected lag detected in ordinary run"
-jq -e .wire_format_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire format inconsistencies detected in ordinary run"
-
-
-# TODO: check operation balances are correct (once we have all transaction types and wallet is deterministic)
-# TODO: check revenue summaries are correct (once we have all transaction types and wallet is deterministic)
-
-echo PASS
-
-LOSS=`jq -r .total_bad_sig_loss < test-audit-aggregation.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong total bad sig loss from aggregation, got unexpected loss of $LOSS"
-fi
-LOSS=`jq -r .total_bad_sig_loss < test-audit-coins.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong total bad sig loss from coins, got unexpected loss of $LOSS"
-fi
-LOSS=`jq -r .total_bad_sig_loss < test-audit-reserves.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong total bad sig loss from reserves, got unexpected loss of $LOSS"
-fi
-
-echo -n "Test for wire amounts... "
-WIRED=`jq -r .total_wire_in_delta_plus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta plus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_wire_in_delta_minus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta minus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_wire_out_delta_plus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta plus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_wire_out_delta_minus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta minus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_missattribution_in < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total missattribution in wrong, got $WIRED"
-fi
-echo PASS
-
-echo -n "Checking for unexpected arithmetic differences "
-LOSS=`jq -r .total_arithmetic_delta_plus < test-audit-aggregation.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong arithmetic delta from aggregations, got unexpected plus of $LOSS"
-fi
-LOSS=`jq -r .total_arithmetic_delta_minus < test-audit-aggregation.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong arithmetic delta from aggregation, got unexpected minus of $LOSS"
-fi
-LOSS=`jq -r .total_arithmetic_delta_plus < test-audit-coins.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong arithmetic delta from coins, got unexpected plus of $LOSS"
-fi
-LOSS=`jq -r .total_arithmetic_delta_minus < test-audit-coins.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong arithmetic delta from coins, got unexpected minus of $LOSS"
-fi
-LOSS=`jq -r .total_arithmetic_delta_plus < test-audit-reserves.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong arithmetic delta from reserves, got unexpected plus of $LOSS"
-fi
-LOSS=`jq -r .total_arithmetic_delta_minus < test-audit-reserves.json`
-if test $LOSS != "TESTKUDOS:0"
-then
- exit_fail "Wrong arithmetic delta from reserves, got unexpected minus of $LOSS"
-fi
+ echo -n "Test for wire amounts... "
+ WIRED=$(jq -r .total_wire_in_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_in_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_misattribution_in < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total misattribution in wrong, got $WIRED"
+ fi
+ echo "PASS"
-jq -e .amount_arithmetic_inconsistencies[0] < test-audit-aggregation.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from aggregations detected in ordinary run"
-jq -e .amount_arithmetic_inconsistencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from coins detected in ordinary run"
-jq -e .amount_arithmetic_inconsistencies[0] < test-audit-reserves.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from reserves detected in ordinary run"
-echo PASS
+ echo -n "Checking for unexpected arithmetic differences "
+ LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-aggregation.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from aggregations, got unexpected plus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-aggregation.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from aggregation, got unexpected minus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from coins, got unexpected plus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-coins.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from coins, got unexpected minus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-reserves.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from reserves, got unexpected plus of $LOSS"
+ fi
+ LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-reserves.json)
+ if [ "$LOSS" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Wrong arithmetic delta from reserves, got unexpected minus of $LOSS"
+ fi
-echo -n "Checking for unexpected wire out differences "
-jq -e .wire_out_inconsistencies[0] < test-audit-aggregation.json > /dev/null && exit_fail "Unexpected wire out inconsistencies detected in ordinary run"
-echo PASS
+ jq -e .amount_arithmetic_inconsistencies[0] \
+ < test-audit-aggregation.json \
+ > /dev/null \
+ && exit_fail "Unexpected arithmetic inconsistencies from aggregations detected in ordinary run"
+ jq -e .amount_arithmetic_inconsistencies[0] \
+ < test-audit-coins.json \
+ > /dev/null \
+ && exit_fail "Unexpected arithmetic inconsistencies from coins detected in ordinary run"
+ jq -e .amount_arithmetic_inconsistencies[0] \
+ < test-audit-reserves.json \
+ > /dev/null \
+ && exit_fail "Unexpected arithmetic inconsistencies from reserves detected in ordinary run"
+ echo "PASS"
-# cannot easily undo aggregator, hence full reload
-full_reload
+ echo -n "Checking for unexpected wire out differences "
+ jq -e .wire_out_inconsistencies[0] \
+ < test-audit-aggregation.json \
+ > /dev/null \
+ && exit_fail "Unexpected wire out inconsistencies detected in ordinary run"
+ echo "PASS"
+ # cannot easily undo aggregator, hence full reload
+ full_reload
}
@@ -273,80 +441,84 @@ full_reload
# transfer lag!
function test_1() {
-echo "===========1: normal run==========="
-run_audit
-
-echo "Checking output"
-# if an emergency was detected, that is a bug and we should fail
-echo -n "Test for emergencies... "
-jq -e .emergencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency detected in ordinary run" || echo PASS
-echo -n "Test for emergencies by count... "
-jq -e .emergencies_by_count[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency by count detected in ordinary run" || echo PASS
-
-echo -n "Test for wire inconsistencies... "
-jq -e .wire_out_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire out inconsistency detected in ordinary run"
-jq -e .reserve_in_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected reserve in inconsistency detected in ordinary run"
-jq -e .missattribution_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected missattribution inconsistency detected in ordinary run"
-jq -e .row_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected row inconsistency detected in ordinary run"
-jq -e .row_minor_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected minor row inconsistency detected in ordinary run"
-jq -e .wire_format_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire format inconsistencies detected in ordinary run"
-
-# TODO: check operation balances are correct (once we have all transaction types and wallet is deterministic)
-# TODO: check revenue summaries are correct (once we have all transaction types and wallet is deterministic)
-
-echo PASS
-
-echo -n "Check for lag detection... "
-
-# Check wire transfer lag reported (no aggregator!)
-# NOTE: This test is EXPECTED to fail for ~1h after
-# re-generating the test database as we do not
-# report lag of less than 1h (see GRACE_PERIOD in
-# taler-helper-auditor-wire.c)
-if [ $DATABASE_AGE -gt 3600 ]
-then
- jq -e .lag_details[0] < test-audit-wire.json > /dev/null || exit_fail "Lag not detected in run without aggregator at age $DELTA"
+ echo "===========1: normal run==========="
+ run_audit
+
+ echo "Checking output"
+ # if an emergency was detected, that is a bug and we should fail
+ echo -n "Test for emergencies... "
+ jq -e .emergencies[0] \
+ < test-audit-coins.json \
+ > /dev/null \
+ && exit_fail "Unexpected emergency detected in ordinary run" \
+ || echo "PASS"
+ echo -n "Test for emergencies by count... "
+ jq -e .emergencies_by_count[0] \
+ < test-audit-coins.json \
+ > /dev/null \
+ && exit_fail "Unexpected emergency by count detected in ordinary run" \
+ || echo "PASS"
+
+ echo -n "Test for wire inconsistencies... "
+ jq -e .wire_out_amount_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected wire out inconsistency detected in ordinary run"
+ jq -e .reserve_in_amount_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected reserve in inconsistency detected in ordinary run"
+ jq -e .misattribution_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected misattribution inconsistency detected in ordinary run"
+ jq -e .row_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected row inconsistency detected in ordinary run"
+ jq -e .row_minor_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected minor row inconsistency detected in ordinary run"
+ jq -e .wire_format_inconsistencies[0] \
+ < test-audit-wire.json \
+ > /dev/null \
+ && exit_fail "Unexpected wire format inconsistencies detected in ordinary run"
+
+ # TODO: check operation balances are correct (once we have all transaction types and wallet is deterministic)
+ # TODO: check revenue summaries are correct (once we have all transaction types and wallet is deterministic)
- LAG=`jq -r .total_amount_lag < test-audit-wire.json`
- if test $LAG = "TESTKUDOS:0"
- then
- exit_fail "Expected total lag to be non-zero"
- fi
echo "PASS"
-else
- echo "SKIP (database too new)"
-fi
-
-echo -n "Test for wire amounts... "
-WIRED=`jq -r .total_wire_in_delta_plus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta plus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_wire_in_delta_minus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta minus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_wire_out_delta_plus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta plus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_wire_out_delta_minus < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total wire delta minus wrong, got $WIRED"
-fi
-WIRED=`jq -r .total_missattribution_in < test-audit-wire.json`
-if test $WIRED != "TESTKUDOS:0"
-then
- exit_fail "Expected total missattribution in wrong, got $WIRED"
-fi
+ echo -n "Test for wire amounts... "
+ WIRED=$(jq -r .total_wire_in_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_in_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_plus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_wire_out_delta_minus < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ fi
+ WIRED=$(jq -r .total_misattribution_in < test-audit-wire.json)
+ if [ "$WIRED" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Expected total misattribution in wrong, got $WIRED"
+ fi
-# Database was unmodified, no need to undo
-echo "OK"
+ # Database was unmodified, no need to undo
+ echo "OK"
}
@@ -354,38 +526,38 @@ echo "OK"
# Change recoup amount
function test_2() {
-echo "===========2: recoup amount inconsistency==========="
-echo "UPDATE recoup SET amount_val=5 WHERE recoup_uuid=1" | psql -Aqt $DB
+ echo "===========2: recoup amount inconsistency==========="
+ echo "UPDATE exchange.recoup SET amount_val=5 WHERE recoup_uuid=1" | psql -Aqt "$DB"
-run_audit
+ run_audit
-# Reserve balance is now wrong
-echo -n "Testing inconsistency detection... "
-AMOUNT=`jq -r .reserve_balance_summary_wrong_inconsistencies[0].auditor < test-audit-reserves.json`
-if test $AMOUNT != "TESTKUDOS:3"
-then
- exit_fail "Reserve auditor amount $AMOUNT is wrong"
-fi
-AMOUNT=`jq -r .reserve_balance_summary_wrong_inconsistencies[0].exchange < test-audit-reserves.json`
-if test $AMOUNT != "TESTKUDOS:0"
-then
- exit_fail "Reserve exchange amount $AMOUNT is wrong"
-fi
-# Coin spent exceeded coin's value
-AMOUNT=`jq -r .amount_arithmetic_inconsistencies[0].auditor < test-audit-coins.json`
-if test $AMOUNT != "TESTKUDOS:2"
-then
- exit_fail "Coin auditor amount $AMOUNT is wrong"
-fi
-AMOUNT=`jq -r .amount_arithmetic_inconsistencies[0].exchange < test-audit-coins.json`
-if test $AMOUNT != "TESTKUDOS:5"
-then
- exit_fail "Coin exchange amount $AMOUNT is wrong"
-fi
-echo OK
+ # Reserve balance is now wrong
+ echo -n "Testing inconsistency detection... "
+ AMOUNT=$(jq -r .reserve_balance_summary_wrong_inconsistencies[0].auditor < test-audit-reserves.json)
+ if [ "$AMOUNT" != "TESTKUDOS:3" ]
+ then
+ exit_fail "Reserve auditor amount $AMOUNT is wrong"
+ fi
+ AMOUNT=$(jq -r .reserve_balance_summary_wrong_inconsistencies[0].exchange < test-audit-reserves.json)
+ if [ "$AMOUNT" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Reserve exchange amount $AMOUNT is wrong"
+ fi
+ # Coin spent exceeded coin's value
+ AMOUNT=$(jq -r .amount_arithmetic_inconsistencies[0].auditor < test-audit-coins.json)
+ if [ "$AMOUNT" != "TESTKUDOS:2" ]
+ then
+ exit_fail "Coin auditor amount $AMOUNT is wrong"
+ fi
+ AMOUNT=$(jq -r .amount_arithmetic_inconsistencies[0].exchange < test-audit-coins.json)
+ if [ "$AMOUNT" != "TESTKUDOS:5" ]
+ then
+ exit_fail "Coin exchange amount $AMOUNT is wrong"
+ fi
+ echo "OK"
-# Undo database modification
-echo "UPDATE recoup SET amount_val=2 WHERE recoup_uuid=1" | psql -Aqt $DB
+ # Undo database modification
+ echo "UPDATE exchange.recoup SET amount_val=2 WHERE recoup_uuid=1" | psql -Aqt "$DB"
}
@@ -393,27 +565,27 @@ echo "UPDATE recoup SET amount_val=2 WHERE recoup_uuid=1" | psql -Aqt $DB
# Change recoup-refresh amount
function test_3() {
-echo "===========3: recoup-refresh amount inconsistency==========="
-echo "UPDATE recoup_refresh SET amount_val=5 WHERE recoup_refresh_uuid=1" | psql -Aqt $DB
+ echo "===========3: recoup-refresh amount inconsistency==========="
+ echo "UPDATE exchange.recoup_refresh SET amount_val=5 WHERE recoup_refresh_uuid=1" | psql -Aqt "$DB"
-run_audit
+ run_audit
-echo -n "Testing inconsistency detection... "
-# Coin spent exceeded coin's value
-AMOUNT=`jq -r .total_arithmetic_delta_minus < test-audit-coins.json`
-if test $AMOUNT != "TESTKUDOS:5"
-then
- exit_fail "Arithmetic delta minus amount $AMOUNT is wrong"
-fi
-AMOUNT=`jq -r .total_arithmetic_delta_plus < test-audit-coins.json`
-if test $AMOUNT != "TESTKUDOS:0"
-then
- exit_fail "Arithmetic delta plus amount $AMOUNT is wrong"
-fi
-echo OK
+ echo -n "Testing inconsistency detection... "
+ # Coin spent exceeded coin's value
+ AMOUNT=$(jq -r .total_arithmetic_delta_minus < test-audit-coins.json)
+ if [ "$AMOUNT" != "TESTKUDOS:5" ]
+ then
+ exit_fail "Arithmetic delta minus amount $AMOUNT is wrong"
+ fi
+ AMOUNT=$(jq -r .total_arithmetic_delta_plus < test-audit-coins.json)
+ if [ "$AMOUNT" != "TESTKUDOS:0" ]
+ then
+ exit_fail "Arithmetic delta plus amount $AMOUNT is wrong"
+ fi
+ echo "OK"
-# Undo database modification
-echo "UPDATE recoup_refresh SET amount_val=0 WHERE recoup_refresh_uuid=1" | psql -Aqt $DB
+ # Undo database modification
+ echo "UPDATE exchange.recoup_refresh SET amount_val=0 WHERE recoup_refresh_uuid=1" | psql -Aqt "$DB"
}
@@ -421,79 +593,68 @@ echo "UPDATE recoup_refresh SET amount_val=0 WHERE recoup_refresh_uuid=1" | psql
# Void recoup-refresh entry by 'unrevoking' denomination
function test_4() {
-echo "===========4: invalid recoup==========="
-echo "DELETE FROM denomination_revocations;" | psql -Aqt $DB
+ echo "===========4: invalid recoup==========="
+ echo "DELETE FROM exchange.denomination_revocations;" | psql -Aqt "$DB"
-run_audit
+ run_audit
-echo -n "Testing inconsistency detection... "
-# Coin spent exceeded coin's value
-jq -e .bad_sig_losses[0] < test-audit-coins.json > /dev/null || exit_fail "Bad recoup not detected"
-AMOUNT=`jq -r .total_bad_sig_losses < test-audit-coins.json`
-if test $AMOUNT == "TESTKUDOS:0"
-then
- exit_fail "Total bad sig losses are wrong"
-fi
-TAB=`jq -r .row_inconsistencies[0].table < test-audit-reserves.json`
-if test $TAB != "recoup"
-then
- exit_fail "Wrong table for row inconsistency, got $TAB"
-fi
-echo OK
-
-# Undo database modification (can't easily undo DELETE, so full reload)
-full_reload
+ echo -n "Testing inconsistency detection... "
+ # Coin spent exceeded coin's value
+ jq -e .bad_sig_losses[0] \
+ < test-audit-coins.json \
+ > /dev/null \
+ || exit_fail "Bad recoup not detected"
+ AMOUNT=$(jq -r .irregular_loss < test-audit-coins.json)
+ if [ "$AMOUNT" == "TESTKUDOS:0" ]
+ then
+ exit_fail "Total bad sig losses are wrong"
+ fi
+ TAB=$(jq -r .row_inconsistencies[0].table < test-audit-reserves.json)
+ if [ "$TAB" != "recoup" ]
+ then
+ exit_fail "Wrong table for row inconsistency, got $TAB"
+ fi
+ echo "OK"
+ # Undo database modification (can't easily undo DELETE, so full reload)
+ full_reload
}
-
# *************** Main test loop starts here **************
# Run all the tests against the database given in $1.
# Sets $fail to 0 on success, non-zero on failure.
-check_with_database()
+function check_with_database()
{
- BASEDB=$1
+ BASEDB="$1"
+ # Configuration file to use
+ CONF="$1.conf"
echo "Running test suite with database $BASEDB using configuration $CONF"
- # Setup database-specific globals
- MASTER_PUB=`cat ${BASEDB}.mpub`
+ MASTER_PRIV_FILE="${BASEDB}.mpriv"
+ taler-config -f -c "${CONF}" -s exchange-offline -o MASTER_PRIV_FILE -V "${MASTER_PRIV_FILE}"
+ MASTER_PUB=$(gnunet-ecc -p "$MASTER_PRIV_FILE")
- # Where to store wire fee details for aggregator
- echo "Storing wire fees"
- WIRE_FEE_DIR=`taler-config -c $CONF -f -s exchangedb -o WIREFEE_BASE_DIR`
- mkdir -p $WIRE_FEE_DIR
- cp ${BASEDB}.fees $WIRE_FEE_DIR/x-taler-bank.fee
-
- # Determine database age
- echo "Calculating database age based on ${BASEDB}.age"
- AGE=`cat ${BASEDB}.age`
- NOW=`date +%s`
- # NOTE: expr "fails" if the result is zero.
- DATABASE_AGE=`expr ${NOW} - ${AGE} || true`
- echo "Database age is ${DATABASE_AGE} seconds"
+ echo "MASTER PUB is ${MASTER_PUB} using file ${MASTER_PRIV_FILE}"
# Load database
full_reload
-
# Run test suite
fail=0
for i in $TESTS
do
- test_$i
- if test 0 != $fail
+ "test_$i"
+ if [ 0 != "$fail" ]
then
break
fi
done
# echo "Cleanup (disabled, leaving database $DB behind)"
- dropdb $DB
- rm -r $WIRE_FEE_DIR
- rm -f test-audit.log test-wire-audit.log
+ dropdb "$DB"
}
@@ -501,48 +662,91 @@ check_with_database()
# *************** Main logic starts here **************
# ####### Setup globals ######
-# Postgres database to use (must match test-auditor.conf)
-DB=taler-auditor-test
+# Postgres database to use
+DB=revoke-basedb
-# Configuration file to use
-CONF=test-auditor.conf
# test required commands exist
echo "Testing for jq"
jq -h > /dev/null || exit_skip "jq required"
-echo "Testing for taler-bank-manage"
-taler-bank-manage -h >/dev/null </dev/null || exit_skip "taler-bank-manage required"
+echo "Testing for faketime"
+faketime -h > /dev/null \
+ || exit_skip "faketime required"
+echo "Testing for libeufin-bank"
+libeufin-bank --help \
+ >/dev/null \
+ 2> /dev/null \
+ </dev/null \
+ || exit_skip "libeufin-bank required"
echo "Testing for pdflatex"
which pdflatex > /dev/null </dev/null || exit_skip "pdflatex required"
-
-# check if we should regenerate the database
-if test -n "${1:-}"
-then
- echo "Custom run, will only run on existing DB."
+echo "Testing for taler-wallet-cli"
+taler-wallet-cli -h \
+ >/dev/null \
+ </dev/null \
+ 2>/dev/null \
+ || exit_skip "taler-wallet-cli required"
+
+echo -n "Testing for Postgres "
+# Available directly in path?
+INITDB_BIN=$(command -v initdb) || true
+if [[ -n "$INITDB_BIN" ]]; then
+ echo "FOUND (in path) at $INITDB_BIN"
else
- echo -n "Testing for taler-wallet-cli"
- if taler-wallet-cli -h >/dev/null </dev/null 2>/dev/null
+ HAVE_INITDB=$(find /usr -name "initdb" | head -1 2> /dev/null | grep postgres) || exit_skip " MISSING"
+ echo "FOUND at " "$(dirname "$HAVE_INITDB")"
+ INITDB_BIN=$(echo "$HAVE_INITDB" | grep bin/initdb | grep postgres | sort -n | tail -n1)
+fi
+echo -n "Setting up Postgres DB"
+POSTGRES_PATH=$(dirname "$INITDB_BIN")
+MY_TMP_DIR=$(mktemp -d /tmp/taler-auditor-basedbXXXXXX)
+TMPDIR="${MY_TMP_DIR}/postgres/"
+mkdir -p "$TMPDIR"
+echo -n "Setting up Postgres DB at $TMPDIR ..."
+"$INITDB_BIN" \
+ --no-sync \
+ --auth=trust \
+ -D "${TMPDIR}" \
+ > "${MY_TMP_DIR}/postgres-dbinit.log" \
+ 2> "${MY_TMP_DIR}/postgres-dbinit.err"
+echo " DONE"
+mkdir "${TMPDIR}/sockets"
+echo -n "Launching Postgres service at $POSTGRES_PATH"
+cat - >> "$TMPDIR/postgresql.conf" <<EOF
+unix_socket_directories='${TMPDIR}/sockets'
+fsync=off
+max_wal_senders=0
+synchronous_commit=off
+wal_level=minimal
+listen_addresses=''
+EOF
+grep -v host \
+ < "$TMPDIR/pg_hba.conf" \
+ > "$TMPDIR/pg_hba.conf.new"
+mv "$TMPDIR/pg_hba.conf.new" "$TMPDIR/pg_hba.conf"
+"${POSTGRES_PATH}/pg_ctl" \
+ -D "$TMPDIR" \
+ -l /dev/null \
+ start \
+ > "${MY_TMP_DIR}/postgres-start.log" \
+ 2> "${MY_TMP_DIR}/postgres-start.err"
+echo " DONE"
+PGHOST="$TMPDIR/sockets"
+export PGHOST
+
+echo "Generating fresh database at $MY_TMP_DIR"
+if faketime -f '-1 d' ./generate-revoke-basedb.sh "$MY_TMP_DIR/$DB"
+then
+ check_with_database "$MY_TMP_DIR/$DB"
+ if [ "x$fail" != "x0" ]
then
- MYDIR=`mktemp -d /tmp/taler-auditor-basedbXXXXXX`
- echo " FOUND. Generating fresh database at $MYDIR"
- if ./generate-revoke-basedb.sh $MYDIR/basedb
- then
- check_with_database $MYDIR/basedb
- if test x$fail != x0
- then
- exit $fail
- else
- echo "Cleaning up $MYDIR..."
- rm -rf $MYDIR || echo "Removing $MYDIR failed"
- fi
- else
- echo "Generation failed, running only on existing DB"
- fi
+ exit "$fail"
else
- echo " NOT FOUND, running only on existing DB"
+ echo "Cleaning up $MY_TMP_DIR..."
+ rm -rf "$MY_TMP_DIR" || echo "Removing $MY_TMP_DIR failed"
fi
+else
+ echo "Generation failed"
fi
-check_with_database "revoke-basedb"
-
-exit $fail
+exit 0
diff --git a/src/auditor/test-sync-in.conf b/src/auditor/test-sync-in.conf
new file mode 100644
index 000000000..ef79cf90d
--- /dev/null
+++ b/src/auditor/test-sync-in.conf
@@ -0,0 +1,29 @@
+[exchange]
+#The DB plugin to use
+DB = postgres
+
+[exchangedb-postgres]
+
+#The connection string the plugin has to use for connecting to the database
+CONFIG = postgres:///talercheck-in
+
+# Where are the SQL files to setup our tables?
+SQL_DIR = $DATADIR/sql/exchange/
+
+
+[taler]
+CURRENCY = EUR
+
+[exchangedb]
+
+# After how long do we close idle reserves? The exchange
+# and the auditor must agree on this value. We currently
+# expect it to be globally defined for the whole system,
+# as there is no way for wallets to query this value. Thus,
+# it is only configurable for testing, and should be treated
+# as constant in production.
+IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
+
+# After how long do we forget about reserves? Should be above
+# the legal expiration timeframe of withdrawn coins.
+LEGAL_RESERVE_EXPIRATION_TIME = 7 years
diff --git a/src/auditor/test-sync-out.conf b/src/auditor/test-sync-out.conf
new file mode 100644
index 000000000..32fb46b37
--- /dev/null
+++ b/src/auditor/test-sync-out.conf
@@ -0,0 +1,29 @@
+[exchange]
+#The DB plugin to use
+DB = postgres
+
+[exchangedb-postgres]
+
+#The connection string the plugin has to use for connecting to the database
+CONFIG = postgres:///talercheck-out
+
+# Where are the SQL files to setup our tables?
+SQL_DIR = $DATADIR/sql/exchange/
+
+[taler]
+CURRENCY = EUR
+
+
+[exchangedb]
+
+# After how long do we close idle reserves? The exchange
+# and the auditor must agree on this value. We currently
+# expect it to be globally defined for the whole system,
+# as there is no way for wallets to query this value. Thus,
+# it is only configurable for testing, and should be treated
+# as constant in production.
+IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
+
+# After how long do we forget about reserves? Should be above
+# the legal expiration timeframe of withdrawn coins.
+LEGAL_RESERVE_EXPIRATION_TIME = 7 years
diff --git a/src/auditor/test-sync.sh b/src/auditor/test-sync.sh
new file mode 100755
index 000000000..bcef908aa
--- /dev/null
+++ b/src/auditor/test-sync.sh
@@ -0,0 +1,167 @@
+#!/bin/bash
+#
+# This file is part of TALER
+# 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
+# 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, If not, see <http://www.gnu.org/license>
+#
+# shellcheck disable=SC2317
+
+set -eu
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo "SKIPPING test: $1"
+ exit 77
+}
+
+# Exit, with error message (hard failure)
+function exit_fail() {
+ echo "FAILING test: $1"
+ exit 1
+}
+
+# Cleanup to run whenever we exit
+function cleanup() {
+ if [ -n "${POSTGRES_PATH:-}" ]
+ then
+ "${POSTGRES_PATH}/pg_ctl" -D "$TMPDIR" stop &> /dev/null || true
+ fi
+ for n in $(jobs -p)
+ do
+ kill "$n" 2> /dev/null || true
+ done
+ wait
+}
+
+# Install cleanup handler (except for kill -9)
+trap cleanup EXIT
+
+function check_with_database()
+{
+ echo -n "Testing synchronization logic ..."
+
+ dropdb talercheck-in 2> /dev/null || true
+ dropdb talercheck-out 2> /dev/null || true
+
+ createdb talercheck-in || exit 77
+ createdb talercheck-out || exit 77
+ echo -n "."
+
+ taler-exchange-dbinit -c test-sync-out.conf
+ echo -n "."
+ psql -Aqt talercheck-in \
+ -q -1 \
+ -f "$1.sql" \
+ >/dev/null \
+ || exit_skip "Failed to load database"
+
+ echo -n "."
+ taler-auditor-sync \
+ -s test-sync-in.conf \
+ -d test-sync-out.conf -t
+
+ # cs_nonce_locks excluded: no point
+ for table in denominations denomination_revocations wire_targets reserves reserves_in reserves_close reserves_out auditors auditor_denom_sigs exchange_sign_keys signkey_revocations extensions policy_details policy_fulfillments known_coins refresh_commitments refresh_revealed_coins refresh_transfer_keys deposits refunds wire_out aggregation_tracking wire_fee recoup recoup_refresh
+ do
+ echo -n "."
+ CIN=$(echo "SELECT COUNT(*) FROM exchange.$table" | psql talercheck-in -Aqt)
+ COUT=$(echo "SELECT COUNT(*) FROM exchange.$table" | psql talercheck-out -Aqt)
+
+ if [ "${CIN}" != "${COUT}" ]
+ then
+ dropdb talercheck-in
+ dropdb talercheck-out
+ echo "FAIL"
+ exit_fail "Record count mismatch: $CIN / $COUT in table $table"
+ fi
+ done
+
+ echo -n ". "
+ dropdb talercheck-in
+ dropdb talercheck-out
+
+ echo "PASS"
+ fail=0
+}
+
+# test required commands exist
+echo "Testing for jq"
+jq -h > /dev/null || exit_skip "jq required"
+echo "Testing for faketime"
+faketime -h > /dev/null || exit_skip "faketime required"
+echo "Testing for libeufin-bank"
+libeufin-bank --help >/dev/null </dev/null 2> /dev/null || exit_skip "libeufin-bank required"
+echo "Testing for pdflatex"
+which pdflatex > /dev/null </dev/null || exit_skip "pdflatex required"
+echo "Testing for taler-wallet-cli"
+taler-wallet-cli -h >/dev/null </dev/null 2>/dev/null || exit_skip "taler-wallet-cli required"
+
+echo -n "Testing for Postgres"
+# Available directly in path?
+INITDB_BIN=$(command -v initdb) || true
+if [[ -n "$INITDB_BIN" ]]; then
+ echo " FOUND (in path) at $INITDB_BIN"
+else
+ HAVE_INITDB=$(find /usr -name "initdb" | head -1 2> /dev/null | grep postgres) || exit_skip " MISSING"
+ echo " FOUND at " "$(dirname "$HAVE_INITDB")"
+ INITDB_BIN=$(echo "$HAVE_INITDB" | grep bin/initdb | grep postgres | sort -n | tail -n1)
+fi
+echo -n "Setting up Postgres DB"
+POSTGRES_PATH=$(dirname "$INITDB_BIN")
+MYDIR=$(mktemp -d /tmp/taler-auditor-basedbXXXXXX)
+TMPDIR="$MYDIR/postgres/"
+mkdir -p "$TMPDIR"
+"$INITDB_BIN" --no-sync --auth=trust -D "${TMPDIR}" \
+ > "${MYDIR}/postgres-dbinit.log" \
+ 2> "${MYDIR}/postgres-dbinit.err"
+echo " DONE"
+mkdir "${TMPDIR}/sockets"
+echo -n "Launching Postgres service"
+cat - >> "$TMPDIR/postgresql.conf" <<EOF
+unix_socket_directories='${TMPDIR}/sockets'
+fsync=off
+max_wal_senders=0
+synchronous_commit=off
+wal_level=minimal
+listen_addresses=''
+EOF
+grep -v host \
+ < "$TMPDIR/pg_hba.conf" \
+ > "$TMPDIR/pg_hba.conf.new"
+mv "$TMPDIR/pg_hba.conf.new" "$TMPDIR/pg_hba.conf"
+"${POSTGRES_PATH}/pg_ctl" \
+ -D "$TMPDIR" \
+ -l /dev/null \
+ start \
+ > "${MYDIR}/postgres-start.log" \
+ 2> "${MYDIR}/postgres-start.err"
+echo " DONE"
+PGHOST="$TMPDIR/sockets"
+export PGHOST
+
+echo "Generating fresh database at $MYDIR"
+if faketime -f '-1 d' ./generate-auditor-basedb.sh -d "$MYDIR/auditor-basedb"
+then
+ check_with_database "$MYDIR/auditor-basedb"
+ if [ x$fail != x0 ]
+ then
+ exit "$fail"
+ else
+ echo "Cleaning up $MYDIR..."
+ rm -rf "$MYDIR" || echo "Removing $MYDIR failed"
+ fi
+else
+ echo "Generation failed"
+ exit 77
+fi
+exit 0
diff --git a/src/auditordb/.gitignore b/src/auditordb/.gitignore
index 56c08312b..e1c7a648b 100644
--- a/src/auditordb/.gitignore
+++ b/src/auditordb/.gitignore
@@ -1 +1,5 @@
test-auditordb-postgres
+auditor-0002.sql
+procedures.sql
+test_auditordb_checkpoints_postgres
+test_auditordb-postgres
diff --git a/src/auditordb/0002-auditor_amount_arithmetic_inconsistency.sql b/src/auditordb/0002-auditor_amount_arithmetic_inconsistency.sql
new file mode 100644
index 000000000..19dfa682c
--- /dev/null
+++ b/src/auditordb/0002-auditor_amount_arithmetic_inconsistency.sql
@@ -0,0 +1,26 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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 TABLE auditor_amount_arithmetic_inconsistency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ operation BYTEA,
+ exchange_amount taler_amount,
+ auditor_amount taler_amount,
+ profitable BOOLEAN
+);
+COMMENT ON TABLE auditor_amount_arithmetic_inconsistency
+ IS 'Report a (serious) inconsistency in the exchange''s database with respect to calculations involving amounts';
diff --git a/src/auditordb/0002-auditor_bad_sig_losses.sql b/src/auditordb/0002-auditor_bad_sig_losses.sql
new file mode 100644
index 000000000..ac17a5120
--- /dev/null
+++ b/src/auditordb/0002-auditor_bad_sig_losses.sql
@@ -0,0 +1,26 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_bad_sig_losses
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ operation BYTEA,
+ loss taler_amount,
+ operation_specific_pub BYTEA
+);
+COMMENT ON TABLE auditor_bad_sig_losses
+ IS 'Report a (serious) inconsistency with losses due to bad signatures'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_balances.sql b/src/auditordb/0002-auditor_balances.sql
new file mode 100644
index 000000000..8014b9c41
--- /dev/null
+++ b/src/auditordb/0002-auditor_balances.sql
@@ -0,0 +1,30 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 TABLE IF NOT EXISTS auditor_balances
+(
+ balance_key TEXT PRIMARY KEY NOT NULL
+ ,balance_value taler_amount
+);
+COMMENT
+ON TABLE auditor_balances
+ IS 'table storing various global balances of the auditor';
+COMMENT
+ON COLUMN auditor_balances.balance_key
+ IS 'unique name for the balance value';
+COMMENT
+ON COLUMN auditor_balances.balance_value
+ IS 'balance amount';
diff --git a/src/auditordb/0002-auditor_closure_lags.sql b/src/auditordb/0002-auditor_closure_lags.sql
new file mode 100644
index 000000000..8473b25f9
--- /dev/null
+++ b/src/auditordb/0002-auditor_closure_lags.sql
@@ -0,0 +1,27 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_closure_lags
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ amount taler_amount,
+ deadline BIGINT,
+ wtid integer,
+ account BYTEA
+);
+COMMENT ON TABLE auditor_closure_lags
+ IS 'Report closure lags.'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_coin_inconsistency.sql b/src/auditordb/0002-auditor_coin_inconsistency.sql
new file mode 100644
index 000000000..91f954a68
--- /dev/null
+++ b/src/auditordb/0002-auditor_coin_inconsistency.sql
@@ -0,0 +1,28 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_coin_inconsistency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ operation BYTEA,
+ exchange_amount taler_amount,
+ auditor_amount taler_amount,
+ coin_pub BYTEA,
+ profitable BOOLEAN
+);
+COMMENT ON TABLE auditor_coin_inconsistency
+ IS 'Report a (serious) inconsistency in the exchange''s database with respect to calculations involving amounts'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_denomination_key_validity_withdraw_inconsistency.sql b/src/auditordb/0002-auditor_denomination_key_validity_withdraw_inconsistency.sql
new file mode 100644
index 000000000..fd18f35fb
--- /dev/null
+++ b/src/auditordb/0002-auditor_denomination_key_validity_withdraw_inconsistency.sql
@@ -0,0 +1,26 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_denomination_key_validity_withdraw_inconsistency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ execution_date BIGINT,
+ reserve_pub BYTEA,
+ denompub_h BYTEA
+);
+COMMENT ON TABLE auditor_denomination_key_validity_withdraw_inconsistency
+ IS 'Report a (serious) denomination key validity withdraw inconsistency in the exchange''s database'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_denomination_pending.sql b/src/auditordb/0002-auditor_denomination_pending.sql
new file mode 100644
index 000000000..f9ba535b4
--- /dev/null
+++ b/src/auditordb/0002-auditor_denomination_pending.sql
@@ -0,0 +1,34 @@
+--
+-- 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 TABLE auditor_denomination_pending
+ (denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)
+ ,denom_balance taler_amount NOT NULL
+ ,denom_loss taler_amount NOT NULL
+ ,num_issued INT8 NOT NULL
+ ,denom_risk taler_amount NOT NULL
+ ,recoup_loss taler_amount NOT NULL
+);
+COMMENT ON TABLE auditor_denomination_pending
+ IS 'outstanding denomination coins that the exchange is aware of and what the respective balances are (outstanding as well as issued overall which implies the maximum value at risk).';
+COMMENT ON COLUMN auditor_denomination_pending.num_issued
+ IS 'counts the number of coins issued (withdraw, refresh) of this denomination';
+COMMENT ON COLUMN auditor_denomination_pending.denom_risk
+ IS 'amount that could theoretically be lost in the future due to recoup operations';
+COMMENT ON COLUMN auditor_denomination_pending.denom_loss
+ IS 'amount that was lost due to failures by the exchange';
+COMMENT ON COLUMN auditor_denomination_pending.recoup_loss
+ IS 'amount actually lost due to recoup operations after a revocation';
diff --git a/src/auditordb/0002-auditor_denominations_without_sigs.sql b/src/auditordb/0002-auditor_denominations_without_sigs.sql
new file mode 100644
index 000000000..86c83e94f
--- /dev/null
+++ b/src/auditordb/0002-auditor_denominations_without_sigs.sql
@@ -0,0 +1,27 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_denominations_without_sigs
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ denompub_h BYTEA,
+ value taler_amount,
+ start_time BIGINT,
+ end_time BIGINT
+);
+COMMENT ON TABLE auditor_denominations_without_sigs
+ IS 'Report encountered denomination that auditor is not auditing.'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_deposit_confirmations.sql b/src/auditordb/0002-auditor_deposit_confirmations.sql
new file mode 100644
index 000000000..1b7fec185
--- /dev/null
+++ b/src/auditordb/0002-auditor_deposit_confirmations.sql
@@ -0,0 +1,58 @@
+--
+-- 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 TABLE auditor_deposit_confirmations
+(deposit_confirmation_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
+ ,h_policy BYTEA NOT NULL CHECK (LENGTH(h_policy)=64)
+ ,h_wire BYTEA NOT NULL CHECK (LENGTH(h_wire)=64)
+ ,exchange_timestamp INT8 NOT NULL
+ ,refund_deadline INT8 NOT NULL
+ ,wire_deadline INT8 NOT NULL
+ ,total_without_fee taler_amount NOT NULL
+ ,coin_pubs BYTEA[] NOT NULL CHECK (CARDINALITY(coin_pubs)>0)
+ ,coin_sigs BYTEA[] NOT NULL CHECK (CARDINALITY(coin_sigs)=CARDINALITY(coin_pubs))
+ ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
+ ,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64)
+ ,exchange_pub BYTEA NOT NULL CHECK (LENGTH(exchange_pub)=32)
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ ,suppressed BOOLEAN NOT NULL DEFAULT FALSE
+ ,ancient BOOLEAN NOT NULL DEFAULT FALSE
+ ,PRIMARY KEY (h_contract_terms,h_wire,merchant_pub,exchange_sig,exchange_pub,master_sig)
+ );
+COMMENT ON TABLE auditor_deposit_confirmations
+ IS 'deposit confirmation sent to us by merchants; we must check that the exchange reported these properly.';
+
+CREATE INDEX IF NOT EXISTS auditor_deposit_confirmations_not_ancient
+ ON auditor_deposit_confirmations
+ (exchange_timestamp ASC)
+ WHERE NOT ancient;
+
+CREATE OR REPLACE FUNCTION auditor_new_transactions_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+AS $$
+BEGIN
+ NOTIFY XX81AFHF88YGN6ESNH39KR5VQE9MHD7GSSNMTCXB82SZ6T99ARHE0;
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION auditor_new_transactions_trigger()
+ IS 'Call auditor_call_db_notify on new entry';
+
+CREATE TRIGGER auditor_notify_helper_deposits
+ AFTER INSERT
+ ON auditor.auditor_deposit_confirmations
+EXECUTE PROCEDURE auditor_new_transactions_trigger();
diff --git a/src/auditordb/0002-auditor_emergency.sql b/src/auditordb/0002-auditor_emergency.sql
new file mode 100644
index 000000000..2bb13d7e5
--- /dev/null
+++ b/src/auditordb/0002-auditor_emergency.sql
@@ -0,0 +1,29 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_emergency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ denompub_h BYTEA,
+ denom_risk taler_amount,
+ denom_loss taler_amount,
+ deposit_start BIGINT,
+ deposit_end BIGINT,
+ value taler_amount
+);
+COMMENT ON TABLE auditor_emergency
+ IS 'Report an emergency denomination.'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_emergency_by_count.sql b/src/auditordb/0002-auditor_emergency_by_count.sql
new file mode 100644
index 000000000..4daa994a7
--- /dev/null
+++ b/src/auditordb/0002-auditor_emergency_by_count.sql
@@ -0,0 +1,30 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_emergency_by_count
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ denompub_h BYTEA,
+ num_issued integer,
+ num_known integer,
+ risk taler_amount,
+ start BIGINT,
+ deposit_end BIGINT,
+ value taler_amount
+);
+COMMENT ON TABLE auditor_emergency_by_count
+ IS 'Report an emergency denomination.'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_exchange_signkeys.sql b/src/auditordb/0002-auditor_exchange_signkeys.sql
new file mode 100644
index 000000000..64349a2ff
--- /dev/null
+++ b/src/auditordb/0002-auditor_exchange_signkeys.sql
@@ -0,0 +1,35 @@
+ --
+-- 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 TABLE auditor_exchange_signkeys
+ (exchange_pub BYTEA PRIMARY KEY CHECK (LENGTH(exchange_pub)=32)
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ ,ep_valid_from INT8 NOT NULL
+ ,ep_expire_sign INT8 NOT NULL
+ ,ep_expire_legal INT8 NOT NULL
+ );
+COMMENT ON TABLE auditor_exchange_signkeys
+ IS 'list of the online signing keys of exchanges we are auditing';
+COMMENT ON COLUMN auditor_exchange_signkeys.exchange_pub
+ IS 'Public online signing key of the exchange.';
+COMMENT ON COLUMN auditor_exchange_signkeys.master_sig
+ IS 'Signature affirming the validity of the signing key of purpose TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY.';
+COMMENT ON COLUMN auditor_exchange_signkeys.ep_valid_from
+ IS 'Time when this online signing key will first be used to sign messages.';
+COMMENT ON COLUMN auditor_exchange_signkeys.ep_expire_sign
+ IS 'Time when this online signing key will no longer be used to sign.';
+COMMENT ON COLUMN auditor_exchange_signkeys.ep_expire_legal
+ IS 'Time when this online signing key legally expires.';
diff --git a/src/auditordb/0002-auditor_fee_time_inconsistency.sql b/src/auditordb/0002-auditor_fee_time_inconsistency.sql
new file mode 100644
index 000000000..b89cc59c7
--- /dev/null
+++ b/src/auditordb/0002-auditor_fee_time_inconsistency.sql
@@ -0,0 +1,26 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_fee_time_inconsistency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ type BYTEA,
+ time BIGINT,
+ diagnostic BYTEA
+);
+COMMENT ON TABLE auditor_fee_time_inconsistency
+ IS 'Report a (serious) fee time inconsistency in the exchange''s database'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_historic_denomination_revenue.sql b/src/auditordb/0002-auditor_historic_denomination_revenue.sql
new file mode 100644
index 000000000..bf7e4c07e
--- /dev/null
+++ b/src/auditordb/0002-auditor_historic_denomination_revenue.sql
@@ -0,0 +1,32 @@
+--
+-- 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 TABLE auditor_historic_denomination_revenue
+ (denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)
+ ,revenue_timestamp INT8 NOT NULL
+ ,revenue_balance taler_amount NOT NULL
+ ,loss_balance taler_amount NOT NULL
+ );
+COMMENT ON TABLE auditor_historic_denomination_revenue
+ IS 'Table with historic profits; basically, when a denom_pub has expired and everything associated with it is garbage collected, the final profits end up in here; note that the denom_pub here is not a foreign key, we just keep it as a reference point.';
+COMMENT ON COLUMN auditor_historic_denomination_revenue.denom_pub_hash
+ IS 'hash of the denomination public key that created this revenue';
+COMMENT ON COLUMN auditor_historic_denomination_revenue.revenue_timestamp
+ IS 'when was this revenue realized (by the denomination public key expiring)';
+COMMENT ON COLUMN auditor_historic_denomination_revenue.revenue_balance
+ IS 'the sum of all of the profits we made on the denomination except for withdraw fees (which are in historic_reserve_revenue); so this includes the deposit, melt and refund fees';
+COMMENT ON COLUMN auditor_historic_denomination_revenue.loss_balance
+ IS 'the sum of all of the losses we made on the denomination (for example, because the signing key was compromised and thus we redeemed coins we never issued); of course should be zero in practice in most cases';
diff --git a/src/auditordb/0002-auditor_historic_reserve_summary.sql b/src/auditordb/0002-auditor_historic_reserve_summary.sql
new file mode 100644
index 000000000..819c4e160
--- /dev/null
+++ b/src/auditordb/0002-auditor_historic_reserve_summary.sql
@@ -0,0 +1,30 @@
+--
+-- 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_historic_reserve_summary
+ (start_date INT8 PRIMARY KEY
+ ,end_date INT8 NOT NULL
+ ,reserve_profits taler_amount NOT NULL
+ );
+COMMENT ON TABLE auditor_historic_reserve_summary
+ IS 'historic profits from reserves; we eventually GC auditor_historic_reserve_revenue, and then store the totals in here (by time intervals).';
+COMMENT ON COLUMN auditor_historic_reserve_summary.start_date
+ IS 'start date of the time interval over which we made these profits from reserves';
+COMMENT ON COLUMN auditor_historic_reserve_summary.end_date
+ IS 'end date (exclusive) of the time interval over which we made these profits from reserves';
+COMMENT ON COLUMN auditor_historic_reserve_summary.reserve_profits
+ IS 'total amount in profits made';
diff --git a/src/auditordb/0002-auditor_misattribution_in_inconsistency.sql b/src/auditordb/0002-auditor_misattribution_in_inconsistency.sql
new file mode 100644
index 000000000..f786d0fdf
--- /dev/null
+++ b/src/auditordb/0002-auditor_misattribution_in_inconsistency.sql
@@ -0,0 +1,26 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_misattribution_in_inconsistency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ amount taler_amount,
+ bank_row BIGINT,
+ reserve_pub BYTEA
+);
+COMMENT ON TABLE auditor_misattribution_in_inconsistency
+ IS 'Report wire transfer that was smaller than it should have been.'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_progress.sql b/src/auditordb/0002-auditor_progress.sql
new file mode 100644
index 000000000..288a08ae9
--- /dev/null
+++ b/src/auditordb/0002-auditor_progress.sql
@@ -0,0 +1,26 @@
+--
+-- 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 TABLE IF NOT EXISTS auditor_progress
+ (progress_key TEXT PRIMARY KEY NOT NULL
+ ,progress_offset INT8 NOT NULL
+ );
+COMMENT ON TABLE auditor_progress
+ IS 'Information about to the point until which the audit has progressed. Used for SELECTing the statements to process.';
+COMMENT ON COLUMN auditor_progress.progress_key
+ IS 'Name of the progress indicator';
+COMMENT ON COLUMN auditor_progress.progress_offset
+ IS 'Table offset or timestamp or counter until which the audit has progressed';
diff --git a/src/auditordb/0002-auditor_purse_not_closed_inconsistencies.sql b/src/auditordb/0002-auditor_purse_not_closed_inconsistencies.sql
new file mode 100644
index 000000000..5ffb6e85a
--- /dev/null
+++ b/src/auditordb/0002-auditor_purse_not_closed_inconsistencies.sql
@@ -0,0 +1,26 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_purse_not_closed_inconsistencies
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ purse_pub BYTEA,
+ amount taler_amount,
+ expiration_date BIGINT
+);
+COMMENT ON TABLE auditor_purse_not_closed_inconsistencies
+ IS 'Report expired purses'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_purses.sql b/src/auditordb/0002-auditor_purses.sql
new file mode 100644
index 000000000..86b6494d1
--- /dev/null
+++ b/src/auditordb/0002-auditor_purses.sql
@@ -0,0 +1,25 @@
+--
+-- 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 TABLE auditor_purses
+ (auditor_purses_rowid BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,purse_pub BYTEA PRIMARY KEY CHECK(LENGTH(purse_pub)=32)
+ ,balance taler_amount NOT NULL DEFAULT(0,0)
+ ,target taler_amount NOT NULL
+ ,expiration_date INT8 NOT NULL
+ );
+COMMENT ON TABLE auditor_purses
+ IS 'all of the purses and their respective balances that the auditor is aware of';
diff --git a/src/auditordb/0002-auditor_refreshes_hanging.sql b/src/auditordb/0002-auditor_refreshes_hanging.sql
new file mode 100644
index 000000000..5544bc0d8
--- /dev/null
+++ b/src/auditordb/0002-auditor_refreshes_hanging.sql
@@ -0,0 +1,25 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_refreshes_hanging
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ amount taler_amount,
+ coin_pub BYTEA
+);
+COMMENT ON TABLE auditor_refreshes_hanging
+ IS 'Report a hanging refresh.'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_reserve_balance_insufficient_inconsistency.sql b/src/auditordb/0002-auditor_reserve_balance_insufficient_inconsistency.sql
new file mode 100644
index 000000000..bbc0c8118
--- /dev/null
+++ b/src/auditordb/0002-auditor_reserve_balance_insufficient_inconsistency.sql
@@ -0,0 +1,26 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_reserve_balance_insufficient_inconsistency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ reserve_pub BYTEA,
+ inconsistency_gain BOOLEAN,
+ inconsistency_amount taler_amount
+);
+COMMENT ON TABLE auditor_reserve_balance_insufficient_inconsistency
+ IS 'Report a (serious) balance insufficiency in the exchange''s database'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_reserve_balance_summary_wrong_inconsistency.sql b/src/auditordb/0002-auditor_reserve_balance_summary_wrong_inconsistency.sql
new file mode 100644
index 000000000..26d872132
--- /dev/null
+++ b/src/auditordb/0002-auditor_reserve_balance_summary_wrong_inconsistency.sql
@@ -0,0 +1,26 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_reserve_balance_summary_wrong_inconsistency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ reserve_pub BYTEA,
+ exchange_amount taler_amount,
+ auditor_amount taler_amount
+);
+COMMENT ON TABLE auditor_reserve_balance_summary_wrong_inconsistency
+ IS 'Report a (serious) reserve balance insufficiency.'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_reserve_in_inconsistency.sql b/src/auditordb/0002-auditor_reserve_in_inconsistency.sql
new file mode 100644
index 000000000..bb90c4018
--- /dev/null
+++ b/src/auditordb/0002-auditor_reserve_in_inconsistency.sql
@@ -0,0 +1,29 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_reserve_in_inconsistency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ amount_exchange_expected taler_amount,
+ amount_wired taler_amount,
+ reserve_pub BYTEA,
+ timestamp BIGINT,
+ account BYTEA,
+ diagnostic BYTEA
+);
+COMMENT ON TABLE auditor_reserve_in_inconsistency
+ IS 'Report an incoming wire transfer claimed by exchange not found.'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_reserve_not_closed_inconsistency.sql b/src/auditordb/0002-auditor_reserve_not_closed_inconsistency.sql
new file mode 100644
index 000000000..1147b4ae8
--- /dev/null
+++ b/src/auditordb/0002-auditor_reserve_not_closed_inconsistency.sql
@@ -0,0 +1,27 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_reserve_not_closed_inconsistency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ reserve_pub BYTEA,
+ balance taler_amount,
+ expiration_time BIGINT,
+ diagnostic BYTEA
+);
+COMMENT ON TABLE auditor_reserve_not_closed_inconsistency
+ IS 'Report a (serious) reserve balance insufficiency.'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_reserves.sql b/src/auditordb/0002-auditor_reserves.sql
new file mode 100644
index 000000000..808524b28
--- /dev/null
+++ b/src/auditordb/0002-auditor_reserves.sql
@@ -0,0 +1,31 @@
+--
+-- 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 TABLE auditor_reserves
+ (auditor_reserves_rowid BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,reserve_pub BYTEA PRIMARY KEY CHECK(LENGTH(reserve_pub)=32)
+ ,reserve_balance taler_amount NOT NULL
+ ,reserve_loss taler_amount NOT NULL
+ ,withdraw_fee_balance taler_amount NOT NULL
+ ,close_fee_balance taler_amount NOT NULL
+ ,purse_fee_balance taler_amount NOT NULL
+ ,open_fee_balance taler_amount NOT NULL
+ ,history_fee_balance taler_amount NOT NULL
+ ,expiration_date INT8 NOT NULL
+ ,origin_account TEXT
+ );
+COMMENT ON TABLE auditor_reserves
+ IS 'all of the customer reserves and their respective balances that the auditor is aware of';
diff --git a/src/auditordb/0002-auditor_row_inconsistency.sql b/src/auditordb/0002-auditor_row_inconsistency.sql
new file mode 100644
index 000000000..ece2e5661
--- /dev/null
+++ b/src/auditordb/0002-auditor_row_inconsistency.sql
@@ -0,0 +1,25 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_row_inconsistency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ table BYTEA,
+ diagnostic BYTEA
+);
+COMMENT ON TABLE auditor_row_inconsistency
+ IS 'Report a (serious) row inconsistency in the exchange''s database'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_row_minor_inconsistencies.sql b/src/auditordb/0002-auditor_row_minor_inconsistencies.sql
new file mode 100644
index 000000000..7836037c7
--- /dev/null
+++ b/src/auditordb/0002-auditor_row_minor_inconsistencies.sql
@@ -0,0 +1,25 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_row_minor_inconsistency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ table BYTEA,
+ diagnostic BYTEA
+);
+COMMENT ON TABLE auditor_row_minor_inconsistency
+ IS 'Report a (serious) row inconsistency in the exchange''s database.'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_wire_format_inconsistency.sql b/src/auditordb/0002-auditor_wire_format_inconsistency.sql
new file mode 100644
index 000000000..1bc9af89d
--- /dev/null
+++ b/src/auditordb/0002-auditor_wire_format_inconsistency.sql
@@ -0,0 +1,26 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_wire_format_inconsistency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ amount taler_amount,
+ wire_offset BIGINT,
+ diagnostic BYTEA
+);
+COMMENT ON TABLE auditor_wire_format_inconsistency
+ IS 'Report a (serious) format inconsistency.'; \ No newline at end of file
diff --git a/src/auditordb/0002-auditor_wire_out_inconsistency.sql b/src/auditordb/0002-auditor_wire_out_inconsistency.sql
new file mode 100644
index 000000000..6a49c24a0
--- /dev/null
+++ b/src/auditordb/0002-auditor_wire_out_inconsistency.sql
@@ -0,0 +1,26 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2024 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/>
+--
+
+SET search_path TO auditor;
+CREATE TABLE IF NOT EXISTS auditor_wire_out_inconsistency
+(
+ row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY,
+ destination_account BYTEA,
+ expected taler_amount,
+ claimed taler_amount
+);
+COMMENT ON TABLE auditor_wire_out_inconsistency
+ IS 'Report a (serious) wire inconsistency in the exchange''s database'; \ No newline at end of file
diff --git a/src/auditordb/9999.sql b/src/auditordb/9999.sql
deleted file mode 100644
index d6add4b20..000000000
--- a/src/auditordb/9999.sql
+++ /dev/null
@@ -1,53 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2014--2020 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/>
---
-
--- Everything in one big transaction
-BEGIN;
-
-NOTE: This code is not yet ready / in use. It was archived here
-as we might want this kind of table in the future. It is NOT
-to be installed in a production system (hence in EXTRA_DIST and
-not in the SQL target!)
-
--- Check patch versioning is in place.
-SELECT _v.register_patch('auditor-9999', NULL, NULL);
-
-
--- Table with historic business ledger; basically, when the exchange
--- operator decides to use operating costs for anything but wire
--- transfers to merchants, it goes in here. This happens when the
--- operator users transaction fees for business expenses. purpose
--- is free-form but should be a human-readable wire transfer
--- identifier. This is NOT yet used and outside of the scope of
--- the core auditing logic. However, once we do take fees to use
--- operating costs, and if we still want auditor_predicted_result to match
--- the tables overall, we'll need a command-line tool to insert rows
--- into this table and update auditor_predicted_result accordingly.
--- (So this table for now just exists as a reminder of what we'll
--- need in the long term.)
-CREATE TABLE IF NOT EXISTS auditor_historic_ledger
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,purpose VARCHAR NOT NULL
- ,timestamp INT8 NOT NULL
- ,balance_val INT8 NOT NULL
- ,balance_frac INT4 NOT NULL
- );
-CREATE INDEX history_ledger_by_master_pub_and_time
- ON auditor_historic_ledger
- (master_pub
- ,timestamp);
-
-COMMIT;
diff --git a/src/auditordb/Makefile.am b/src/auditordb/Makefile.am
index c7a2e0241..c0282e9c9 100644
--- a/src/auditordb/Makefile.am
+++ b/src/auditordb/Makefile.am
@@ -13,17 +13,41 @@ pkgcfg_DATA = \
sqldir = $(prefix)/share/taler/sql/auditor/
+sqlinputs = \
+ 0002-*.sql \
+ auditor-0002.sql.in \
+ auditor_do_*.sql \
+ procedures.sql.in
+
sql_DATA = \
- auditor-0000.sql \
+ versioning.sql \
auditor-0001.sql \
- drop0001.sql \
- restart0001.sql
+ auditor-0002.sql \
+ drop.sql \
+ restart.sql \
+ procedures.sql
+
+CLEANFILES = \
+ auditor-0002.sql
+
+procedures.sql: procedures.sql.in auditor_do_*.sql
+ chmod +w $@ 2> /dev/null || true
+ gcc -E -P -undef - < procedures.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
+ chmod ugo-w $@
+
+auditor-0002.sql: auditor-0002.sql.in 0002-*.sql
+ chmod +w $@ 2> /dev/null || true
+ gcc -E -P -undef - < auditor-0002.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
+ chmod ugo-w $@
+
EXTRA_DIST = \
auditordb-postgres.conf \
test-auditor-db-postgres.conf \
+ $(sqlinputs) \
$(sql_DATA) \
- 9999.sql
+ pg_template.h pg_template.c \
+ pg_template.sh
plugindir = $(libdir)/taler
@@ -33,27 +57,58 @@ plugin_LTLIBRARIES = \
endif
libtaler_plugin_auditordb_postgres_la_SOURCES = \
- plugin_auditordb_postgres.c
-libtaler_plugin_auditordb_postgres_la_LIBADD = \
- $(LTLIBINTL)
+ plugin_auditordb_postgres.c pg_helper.h \
+ pg_delete_deposit_confirmations.c pg_delete_deposit_confirmations.h \
+ pg_delete_pending_deposit.c pg_delete_pending_deposit.h \
+ pg_delete_purse_info.c pg_delete_purse_info.h \
+ pg_del_denomination_balance.h pg_del_denomination_balance.c \
+ pg_del_reserve_info.c pg_del_reserve_info.h \
+ pg_get_auditor_progress.c pg_get_auditor_progress.h \
+ pg_get_balance.c pg_get_balance.h \
+ pg_get_denomination_balance.c pg_get_denomination_balance.h \
+ pg_get_deposit_confirmations.c pg_get_deposit_confirmations.h \
+ pg_get_purse_info.c pg_get_purse_info.h \
+ pg_get_reserve_info.c pg_get_reserve_info.h \
+ pg_get_wire_fee_summary.c pg_get_wire_fee_summary.h \
+ pg_insert_auditor_progress.c pg_insert_auditor_progress.h \
+ pg_insert_balance.c pg_insert_balance.h \
+ pg_insert_denomination_balance.c pg_insert_denomination_balance.h \
+ pg_insert_deposit_confirmation.c pg_insert_deposit_confirmation.h \
+ pg_insert_exchange_signkey.c pg_insert_exchange_signkey.h \
+ pg_insert_historic_denom_revenue.c pg_insert_historic_denom_revenue.h \
+ pg_insert_historic_reserve_revenue.c pg_insert_historic_reserve_revenue.h \
+ pg_insert_pending_deposit.c pg_insert_pending_deposit.h \
+ pg_insert_purse_info.c pg_insert_purse_info.h \
+ pg_insert_reserve_info.c pg_insert_reserve_info.h \
+ pg_select_historic_denom_revenue.c pg_select_historic_denom_revenue.h \
+ pg_select_historic_reserve_revenue.c pg_select_historic_reserve_revenue.h \
+ pg_select_pending_deposits.c pg_select_pending_deposits.h \
+ pg_select_purse_expired.c pg_select_purse_expired.h \
+ pg_update_auditor_progress.c pg_update_auditor_progress.h \
+ pg_update_balance.c pg_update_balance.h \
+ pg_update_denomination_balance.c pg_update_denomination_balance.h \
+ pg_update_purse_info.c pg_update_purse_info.h \
+ pg_update_reserve_info.c pg_update_reserve_info.h \
+ pg_update_wire_fee_summary.c pg_update_wire_fee_summary.h
libtaler_plugin_auditordb_postgres_la_LDFLAGS = \
- $(TALER_PLUGIN_LDFLAGS) \
+ $(TALER_PLUGIN_LDFLAGS)
+libtaler_plugin_auditordb_postgres_la_LIBADD = \
+ $(LTLIBINTL) \
$(top_builddir)/src/pq/libtalerpq.la \
$(top_builddir)/src/util/libtalerutil.la \
- -lpq \
-lgnunetpq \
- -lgnunetutil $(XLIB)
+ -lgnunetutil \
+ -lpq \
+ $(XLIB)
lib_LTLIBRARIES = \
libtalerauditordb.la
libtalerauditordb_la_SOURCES = \
auditordb_plugin.c
-
libtalerauditordb_la_LIBADD = \
$(top_builddir)/src/util/libtalerutil.la \
-lgnunetutil $(XLIB)
-
libtalerauditordb_la_LDFLAGS = \
$(POSTGRESQL_LDFLAGS) \
-version-info 0:0:0 \
@@ -65,11 +120,22 @@ libtalerauditordb_la_LDFLAGS = \
check_PROGRAMS = \
- test-auditordb-postgres
+ test_auditordb_checkpoints-postgres \
+ test_auditordb-postgres
AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
TESTS = \
- test-auditordb-postgres
+ test_auditordb_checkpoints-postgres \
+ test_auditordb-postgres
+
+test_auditordb_checkpoints_postgres_SOURCES = \
+ test_auditordb_checkpoints.c
+test_auditordb_checkpoints_postgres_LDADD = \
+ libtalerauditordb.la \
+ $(top_srcdir)/src/pq/libtalerpq.la \
+ $(top_srcdir)/src/util/libtalerutil.la \
+ -lgnunetutil \
+ $(XLIB)
test_auditordb_postgres_SOURCES = \
test_auditordb.c
@@ -77,4 +143,5 @@ test_auditordb_postgres_LDADD = \
libtalerauditordb.la \
$(top_srcdir)/src/pq/libtalerpq.la \
$(top_srcdir)/src/util/libtalerutil.la \
- -lgnunetutil
+ -lgnunetutil \
+ $(XLIB)
diff --git a/src/auditordb/auditor-0001.sql b/src/auditordb/auditor-0001.sql
index ff8867bee..0b7823cec 100644
--- a/src/auditordb/auditor-0001.sql
+++ b/src/auditordb/auditor-0001.sql
@@ -1,6 +1,6 @@
--
-- This file is part of TALER
--- Copyright (C) 2014--2020 Taler Systems SA
+-- Copyright (C) 2014--2024 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
@@ -14,266 +14,283 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
--- Everything in one big transaction
BEGIN;
--- Check patch versioning is in place.
SELECT _v.register_patch('auditor-0001', NULL, NULL);
-
-CREATE TABLE IF NOT EXISTS auditor_exchanges
- (master_pub BYTEA PRIMARY KEY CHECK (LENGTH(master_pub)=32)
- ,exchange_url VARCHAR NOT NULL
- );
-COMMENT ON TABLE auditor_exchanges
- IS 'list of the exchanges we are auditing';
-
-
-CREATE TABLE IF NOT EXISTS auditor_exchange_signkeys
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,ep_start INT8 NOT NULL
- ,ep_expire INT8 NOT NULL
- ,ep_end INT8 NOT NULL
- ,exchange_pub BYTEA NOT NULL CHECK (LENGTH(exchange_pub)=32)
- ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
- );
-COMMENT ON TABLE auditor_exchange_signkeys
- IS 'list of the online signing keys of exchanges we are auditing';
-
-
-CREATE TABLE IF NOT EXISTS auditor_denominations
- (denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)
- ,master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,valid_from INT8 NOT NULL
- ,expire_withdraw INT8 NOT NULL
- ,expire_deposit INT8 NOT NULL
- ,expire_legal INT8 NOT NULL
- ,coin_val INT8 NOT NULL
- ,coin_frac INT4 NOT NULL
- ,fee_withdraw_val INT8 NOT NULL
- ,fee_withdraw_frac INT4 NOT NULL
- ,fee_deposit_val INT8 NOT NULL
- ,fee_deposit_frac INT4 NOT NULL
- ,fee_refresh_val INT8 NOT NULL
- ,fee_refresh_frac INT4 NOT NULL
- ,fee_refund_val INT8 NOT NULL
- ,fee_refund_frac INT4 NOT NULL
- );
-COMMENT ON TABLE auditor_denominations
- IS 'denomination keys the auditor is aware of';
-
-
-CREATE TABLE IF NOT EXISTS auditor_progress_reserve
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,last_reserve_in_serial_id INT8 NOT NULL DEFAULT 0
- ,last_reserve_out_serial_id INT8 NOT NULL DEFAULT 0
- ,last_reserve_recoup_serial_id INT8 NOT NULL DEFAULT 0
- ,last_reserve_close_serial_id INT8 NOT NULL DEFAULT 0
- ,PRIMARY KEY (master_pub)
- );
-COMMENT ON TABLE auditor_progress_reserve
- IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
-CREATE TABLE IF NOT EXISTS auditor_progress_aggregation
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,last_wire_out_serial_id INT8 NOT NULL DEFAULT 0
- ,PRIMARY KEY (master_pub)
- );
-COMMENT ON TABLE auditor_progress_aggregation
- IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
-CREATE TABLE IF NOT EXISTS auditor_progress_deposit_confirmation
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,last_deposit_confirmation_serial_id INT8 NOT NULL DEFAULT 0
- ,PRIMARY KEY (master_pub)
- );
-COMMENT ON TABLE auditor_progress_deposit_confirmation
- IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
-CREATE TABLE IF NOT EXISTS auditor_progress_coin
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,last_withdraw_serial_id INT8 NOT NULL DEFAULT 0
- ,last_deposit_serial_id INT8 NOT NULL DEFAULT 0
- ,last_melt_serial_id INT8 NOT NULL DEFAULT 0
- ,last_refund_serial_id INT8 NOT NULL DEFAULT 0
- ,last_recoup_serial_id INT8 NOT NULL DEFAULT 0
- ,last_recoup_refresh_serial_id INT8 NOT NULL DEFAULT 0
- ,PRIMARY KEY (master_pub)
- );
-COMMENT ON TABLE auditor_progress_coin
- IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
-CREATE TABLE IF NOT EXISTS wire_auditor_account_progress
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,account_name TEXT NOT NULL
- ,last_wire_reserve_in_serial_id INT8 NOT NULL DEFAULT 0
- ,last_wire_wire_out_serial_id INT8 NOT NULL DEFAULT 0
- ,wire_in_off INT8
- ,wire_out_off INT8
- ,PRIMARY KEY (master_pub,account_name)
- );
-COMMENT ON TABLE wire_auditor_account_progress
- IS 'information as to which transactions the auditor has processed in the exchange database. Used for SELECTing the
- statements to process. The indices include the last serial ID from the respective tables that we have processed. Thus, we need to select those table entries that are strictly larger (and process in monotonically increasing order).';
-
-
-CREATE TABLE IF NOT EXISTS wire_auditor_progress
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,last_timestamp INT8 NOT NULL
- ,last_reserve_close_uuid INT8 NOT NULL
- ,PRIMARY KEY (master_pub)
- );
-
-
-CREATE TABLE IF NOT EXISTS auditor_reserves
- (reserve_pub BYTEA NOT NULL CHECK(LENGTH(reserve_pub)=32)
- ,master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,reserve_balance_val INT8 NOT NULL
- ,reserve_balance_frac INT4 NOT NULL
- ,withdraw_fee_balance_val INT8 NOT NULL
- ,withdraw_fee_balance_frac INT4 NOT NULL
- ,expiration_date INT8 NOT NULL
- ,auditor_reserves_rowid BIGSERIAL UNIQUE
- ,origin_account TEXT
- );
-COMMENT ON TABLE auditor_reserves
- IS 'all of the customer reserves and their respective balances that the auditor is aware of';
-
-CREATE INDEX IF NOT EXISTS auditor_reserves_by_reserve_pub
- ON auditor_reserves
- (reserve_pub);
-
-
-CREATE TABLE IF NOT EXISTS auditor_reserve_balance
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,reserve_balance_val INT8 NOT NULL
- ,reserve_balance_frac INT4 NOT NULL
- ,withdraw_fee_balance_val INT8 NOT NULL
- ,withdraw_fee_balance_frac INT4 NOT NULL
- );
-COMMENT ON TABLE auditor_reserve_balance
- IS 'sum of the balances of all customer reserves (by exchange master public key)';
-
-
-CREATE TABLE IF NOT EXISTS auditor_wire_fee_balance
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,wire_fee_balance_val INT8 NOT NULL
- ,wire_fee_balance_frac INT4 NOT NULL
- );
-COMMENT ON TABLE auditor_wire_fee_balance
- IS 'sum of the balances of all wire fees (by exchange master public key)';
-
-
-CREATE TABLE IF NOT EXISTS auditor_denomination_pending
- (denom_pub_hash BYTEA PRIMARY KEY REFERENCES auditor_denominations (denom_pub_hash) ON DELETE CASCADE
- ,denom_balance_val INT8 NOT NULL
- ,denom_balance_frac INT4 NOT NULL
- ,denom_loss_val INT8 NOT NULL
- ,denom_loss_frac INT4 NOT NULL
- ,num_issued INT8 NOT NULL
- ,denom_risk_val INT8 NOT NULL
- ,denom_risk_frac INT4 NOT NULL
- ,recoup_loss_val INT8 NOT NULL
- ,recoup_loss_frac INT4 NOT NULL
- );
-COMMENT ON TABLE auditor_denomination_pending
- IS 'outstanding denomination coins that the exchange is aware of and what the respective balances are (outstanding as well as issued overall which implies the maximum value at risk).';
-COMMENT ON COLUMN auditor_denomination_pending.num_issued
- IS 'counts the number of coins issued (withdraw, refresh) of this denomination';
-COMMENT ON COLUMN auditor_denomination_pending.denom_risk_val
- IS 'amount that could theoretically be lost in the future due to recoup operations';
-COMMENT ON COLUMN auditor_denomination_pending.recoup_loss_val
- IS 'amount actually lost due to recoup operations past revocation';
-
-
-CREATE TABLE IF NOT EXISTS auditor_balance_summary
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,denom_balance_val INT8 NOT NULL
- ,denom_balance_frac INT4 NOT NULL
- ,deposit_fee_balance_val INT8 NOT NULL
- ,deposit_fee_balance_frac INT4 NOT NULL
- ,melt_fee_balance_val INT8 NOT NULL
- ,melt_fee_balance_frac INT4 NOT NULL
- ,refund_fee_balance_val INT8 NOT NULL
- ,refund_fee_balance_frac INT4 NOT NULL
- ,risk_val INT8 NOT NULL
- ,risk_frac INT4 NOT NULL
- ,loss_val INT8 NOT NULL
- ,loss_frac INT4 NOT NULL
- ,irregular_recoup_val INT8 NOT NULL
- ,irregular_recoup_frac INT4 NOT NULL
+CREATE SCHEMA auditor;
+COMMENT ON SCHEMA auditor IS 'taler-auditor data';
+
+SET search_path TO auditor;
+
+---------------------------------------------------------------------------
+-- General procedures for DB setup
+---------------------------------------------------------------------------
+
+CREATE TABLE auditor_tables
+ (table_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY
+ ,name TEXT NOT NULL
+ ,version TEXT NOT NULL
+ ,action TEXT NOT NULL
+ ,partitioned BOOL NOT NULL
+ ,by_range BOOL NOT NULL
+ ,finished BOOL NOT NULL DEFAULT(FALSE));
+COMMENT ON TABLE auditor_tables
+ IS 'Tables of the auditor and their status';
+COMMENT ON COLUMN auditor_tables.name
+ IS 'Base name of the table (without partition/shard)';
+COMMENT ON COLUMN auditor_tables.version
+ IS 'Version of the DB in which the given action happened';
+COMMENT ON COLUMN auditor_tables.action
+ IS 'Action to take on the table (e.g. create, constrain, or foreign). Create is done for the master table and each partition; constrain is only for partitions or for master if there are no partitions; master only on master (takes no argument); foreign only on master if there are no partitions.';
+COMMENT ON COLUMN auditor_tables.partitioned
+ IS 'TRUE if the table is partitioned';
+COMMENT ON COLUMN auditor_tables.by_range
+ IS 'TRUE if the table is partitioned by range';
+COMMENT ON COLUMN auditor_tables.finished
+ IS 'TRUE if the respective migration has been run';
+
+
+CREATE FUNCTION create_partitioned_table(
+ IN table_definition TEXT -- SQL template for table creation
+ ,IN table_name TEXT -- base name of the table
+ ,IN main_table_partition_str TEXT -- declaration for how to partition the table
+ ,IN partition_suffix TEXT DEFAULT NULL -- NULL: no partitioning, 0: yes partitioning, no sharding, >0: sharding
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ IF (partition_suffix IS NULL)
+ THEN
+ -- no partitioning, disable option
+ main_table_partition_str = '';
+ ELSE
+ IF (partition_suffix::int > 0)
+ THEN
+ -- sharding, add shard name
+ table_name=table_name || '_' || partition_suffix;
+ END IF;
+ END IF;
+ EXECUTE FORMAT(
+ table_definition,
+ table_name,
+ main_table_partition_str
);
-COMMENT ON TABLE auditor_balance_summary
- IS 'the sum of the outstanding coins from auditor_denomination_pending (denom_pubs must belong to the respectives exchange master public key); it represents the auditor_balance_summary of the exchange at this point (modulo unexpected historic_loss-style events where denomination keys are compromised)';
-
-
-CREATE TABLE IF NOT EXISTS auditor_historic_denomination_revenue
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)
- ,revenue_timestamp INT8 NOT NULL
- ,revenue_balance_val INT8 NOT NULL
- ,revenue_balance_frac INT4 NOT NULL
- ,loss_balance_val INT8 NOT NULL
- ,loss_balance_frac INT4 NOT NULL
- );
-COMMENT ON TABLE auditor_historic_denomination_revenue
- IS 'Table with historic profits; basically, when a denom_pub has expired and everything associated with it is garbage collected, the final profits end up in here; note that the denom_pub here is not a foreign key, we just keep it as a reference point.';
-COMMENT ON COLUMN auditor_historic_denomination_revenue.revenue_balance_val
- IS 'the sum of all of the profits we made on the coin except for withdraw fees (which are in historic_reserve_revenue); so this includes the deposit, melt and refund fees';
-
-
-CREATE TABLE IF NOT EXISTS auditor_historic_reserve_summary
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,start_date INT8 NOT NULL
- ,end_date INT8 NOT NULL
- ,reserve_profits_val INT8 NOT NULL
- ,reserve_profits_frac INT4 NOT NULL
+END $$;
+
+COMMENT ON FUNCTION create_partitioned_table
+ IS 'Generic function to create a table that is partitioned or sharded.';
+
+
+CREATE FUNCTION comment_partitioned_table(
+ IN table_comment TEXT
+ ,IN table_name TEXT
+ ,IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ IF ( (partition_suffix IS NOT NULL) AND
+ (partition_suffix::int > 0) )
+ THEN
+ -- sharding, add shard name
+ table_name=table_name || '_' || partition_suffix;
+ END IF;
+ EXECUTE FORMAT(
+ 'COMMENT ON TABLE %s IS %s'
+ ,table_name
+ ,quote_literal(table_comment)
);
-COMMENT ON TABLE auditor_historic_reserve_summary
- IS 'historic profits from reserves; we eventually GC auditor_historic_reserve_revenue, and then store the totals in here (by time intervals).';
-
-CREATE INDEX IF NOT EXISTS auditor_historic_reserve_summary_by_master_pub_start_date
- ON auditor_historic_reserve_summary
- (master_pub
- ,start_date);
-
-
-CREATE TABLE IF NOT EXISTS deposit_confirmations
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,serial_id BIGSERIAL UNIQUE
- ,h_contract_terms BYTEA CHECK (LENGTH(h_contract_terms)=64)
- ,h_wire BYTEA CHECK (LENGTH(h_wire)=64)
- ,timestamp INT8 NOT NULL
- ,refund_deadline INT8 NOT NULL
- ,amount_without_fee_val INT8 NOT NULL
- ,amount_without_fee_frac INT4 NOT NULL
- ,coin_pub BYTEA CHECK (LENGTH(coin_pub)=32)
- ,merchant_pub BYTEA CHECK (LENGTH(merchant_pub)=32)
- ,exchange_sig BYTEA CHECK (LENGTH(exchange_sig)=64)
- ,exchange_pub BYTEA CHECK (LENGTH(exchange_pub)=32)
- ,master_sig BYTEA CHECK (LENGTH(master_sig)=64)
- ,PRIMARY KEY (h_contract_terms,h_wire,coin_pub,merchant_pub,exchange_sig,exchange_pub,master_sig)
- );
-COMMENT ON TABLE deposit_confirmations
- IS 'deposit confirmation sent to us by merchants; we must check that the exchange reported these properly.';
-
-
-CREATE TABLE IF NOT EXISTS auditor_predicted_result
- (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,balance_val INT8 NOT NULL
- ,balance_frac INT4 NOT NULL
+END $$;
+
+COMMENT ON FUNCTION comment_partitioned_table
+ IS 'Generic function to create a comment on table that is partitioned.';
+
+
+CREATE FUNCTION comment_partitioned_column(
+ IN table_comment TEXT
+ ,IN column_name TEXT
+ ,IN table_name TEXT
+ ,IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ IF ( (partition_suffix IS NOT NULL) AND
+ (partition_suffix::int > 0) )
+ THEN
+ -- sharding, add shard name
+ table_name=table_name || '_' || partition_suffix;
+ END IF;
+ EXECUTE FORMAT(
+ 'COMMENT ON COLUMN %s.%s IS %s'
+ ,table_name
+ ,column_name
+ ,quote_literal(table_comment)
);
-COMMENT ON TABLE auditor_predicted_result
- IS 'Table with the sum of the ledger, auditor_historic_revenue and the auditor_reserve_balance. This is the final amount that the exchange should have in its bank account right now.';
+END $$;
+
+COMMENT ON FUNCTION comment_partitioned_column
+ IS 'Generic function to create a comment on column of a table that is partitioned.';
+
+
+---------------------------------------------------------------------------
+-- Main DB setup loop
+---------------------------------------------------------------------------
+
+CREATE FUNCTION do_create_tables(
+ num_partitions INTEGER
+-- NULL: no partitions, add foreign constraints
+-- 0: no partitions, no foreign constraints
+-- 1: only 1 default partition
+-- > 1: normal partitions
+)
+ RETURNS VOID
+ LANGUAGE plpgsql
+AS $$
+DECLARE
+ tc CURSOR FOR
+ SELECT table_serial_id
+ ,name
+ ,action
+ ,partitioned
+ ,by_range
+ FROM auditor.auditor_tables
+ WHERE NOT finished
+ ORDER BY table_serial_id ASC;
+BEGIN
+ FOR rec IN tc
+ LOOP
+ CASE rec.action
+ -- "create" actions apply to master and partitions
+ WHEN 'create'
+ THEN
+ IF (rec.partitioned AND
+ (num_partitions IS NOT NULL))
+ THEN
+ -- Create master table with partitioning.
+ EXECUTE FORMAT(
+ 'SELECT auditor.%s_table_%s (%s)'::text
+ ,rec.action
+ ,rec.name
+ ,quote_literal('0')
+ );
+ IF (rec.by_range OR
+ (num_partitions = 0))
+ THEN
+ -- Create default partition.
+ IF (rec.by_range)
+ THEN
+ -- Range partition
+ EXECUTE FORMAT(
+ 'CREATE TABLE auditor.%s_default'
+ ' PARTITION OF %s'
+ ' DEFAULT'
+ ,rec.name
+ ,rec.name
+ );
+ ELSE
+ -- Hash partition
+ EXECUTE FORMAT(
+ 'CREATE TABLE auditor.%s_default'
+ ' PARTITION OF %s'
+ ' FOR VALUES WITH (MODULUS 1, REMAINDER 0)'
+ ,rec.name
+ ,rec.name
+ );
+ END IF;
+ ELSE
+ FOR i IN 1..num_partitions LOOP
+ -- Create num_partitions
+ EXECUTE FORMAT(
+ 'CREATE TABLE auditor.%I'
+ ' PARTITION OF %I'
+ ' FOR VALUES WITH (MODULUS %s, REMAINDER %s)'
+ ,rec.name || '_' || i
+ ,rec.name
+ ,num_partitions
+ ,i-1
+ );
+ END LOOP;
+ END IF;
+ ELSE
+ -- Only create master table. No partitions.
+ EXECUTE FORMAT(
+ 'SELECT auditor.%s_table_%s ()'::text
+ ,rec.action
+ ,rec.name
+ );
+ END IF;
+ -- Constrain action apply to master OR each partition
+ WHEN 'constrain'
+ THEN
+ ASSERT rec.partitioned, 'constrain action only applies to partitioned tables';
+ IF (num_partitions IS NULL)
+ THEN
+ -- Constrain master table
+ EXECUTE FORMAT(
+ 'SELECT auditor.%s_table_%s (NULL)'::text
+ ,rec.action
+ ,rec.name
+ );
+ ELSE
+ IF ( (num_partitions = 0) OR
+ (rec.by_range) )
+ THEN
+ -- Constrain default table
+ EXECUTE FORMAT(
+ 'SELECT auditor.%s_table_%s (%s)'::text
+ ,rec.action
+ ,rec.name
+ ,quote_literal('default')
+ );
+ ELSE
+ -- Constrain each partition
+ FOR i IN 1..num_partitions LOOP
+ EXECUTE FORMAT(
+ 'SELECT auditor.%s_table_%s (%s)'::text
+ ,rec.action
+ ,rec.name
+ ,quote_literal(i)
+ );
+ END LOOP;
+ END IF;
+ END IF;
+ -- Foreign actions only apply if partitioning is off
+ WHEN 'foreign'
+ THEN
+ IF (num_partitions IS NULL)
+ THEN
+ -- Add foreign constraints
+ EXECUTE FORMAT(
+ 'SELECT auditor.%s_table_%s (%s)'::text
+ ,rec.action
+ ,rec.name
+ ,NULL
+ );
+ END IF;
+ WHEN 'master'
+ THEN
+ EXECUTE FORMAT(
+ 'SELECT auditor.%s_table_%s ()'::text
+ ,rec.action
+ ,rec.name
+ );
+ ELSE
+ ASSERT FALSE, 'unsupported action type: ' || rec.action;
+ END CASE; -- END CASE (rec.action)
+ -- Mark as finished
+ UPDATE auditor.auditor_tables
+ SET finished=TRUE
+ WHERE table_serial_id=rec.table_serial_id;
+ END LOOP; -- create/alter/drop actions
+END $$;
+
+COMMENT ON FUNCTION do_create_tables
+ IS 'Creates all tables for the given number of partitions that need creating. Does NOT support sharding.';
--- Finally, commit everything
COMMIT;
diff --git a/src/auditordb/auditor-0002.sql.in b/src/auditordb/auditor-0002.sql.in
new file mode 100644
index 000000000..ad459b472
--- /dev/null
+++ b/src/auditordb/auditor-0002.sql.in
@@ -0,0 +1,46 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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/>
+--
+
+BEGIN;
+
+SELECT _v.register_patch('auditor-0002', NULL, NULL);
+
+SET search_path TO auditor;
+
+DO $$ BEGIN
+ CREATE TYPE taler_amount
+ AS
+ (val INT8
+ ,frac INT4
+ );
+ COMMENT ON TYPE taler_amount
+ IS 'Stores an amount, fraction is in units of 1/100000000 of the base value';
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+
+#include "0002-auditor_amount_arithmetic_inconsistency.sql"
+#include "0002-auditor_balances.sql"
+#include "0002-auditor_denomination_pending.sql"
+#include "0002-auditor_exchange_signkeys.sql"
+#include "0002-auditor_historic_denomination_revenue.sql"
+#include "0002-auditor_historic_reserve_summary.sql"
+#include "0002-auditor_progress.sql"
+#include "0002-auditor_purses.sql"
+#include "0002-auditor_reserves.sql"
+#include "0002-auditor_deposit_confirmations.sql"
+
+COMMIT;
diff --git a/src/auditordb/auditor_do_get_auditor_progress.sql b/src/auditordb/auditor_do_get_auditor_progress.sql
new file mode 100644
index 000000000..9371cf67b
--- /dev/null
+++ b/src/auditordb/auditor_do_get_auditor_progress.sql
@@ -0,0 +1,38 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 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/>
+--
+-- @author Christian Grothoff
+
+CREATE OR REPLACE FUNCTION auditor_do_get_auditor_progress(
+ IN in_keys TEXT[])
+RETURNS INT8
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_key TEXT;
+ my_off INT8;
+BEGIN
+ FOREACH my_key IN ARRAY in_keys
+ LOOP
+ SELECT progress_offset
+ INTO my_off
+ FROM auditor_progress
+ WHERE progress_key=my_key;
+ RETURN my_off;
+ END LOOP;
+END $$;
+
+COMMENT ON FUNCTION auditor_do_get_auditor_progress(TEXT[])
+ IS 'Finds all progress offsets associated with the array of keys given as the argument and returns them in order';
diff --git a/src/auditordb/auditor_do_get_balance.sql b/src/auditordb/auditor_do_get_balance.sql
new file mode 100644
index 000000000..782a31f89
--- /dev/null
+++ b/src/auditordb/auditor_do_get_balance.sql
@@ -0,0 +1,47 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 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/>
+--
+-- @author Christian Grothoff
+
+CREATE OR REPLACE FUNCTION auditor_do_get_balance(
+ IN in_keys TEXT[])
+RETURNS taler_amount
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_key TEXT;
+ my_rec RECORD;
+ my_val taler_amount;
+BEGIN
+ FOREACH my_key IN ARRAY in_keys
+ LOOP
+ SELECT (ab.balance_value).val
+ ,(ab.balance_value).frac
+ INTO my_rec
+ FROM auditor_balances ab
+ WHERE balance_key=my_key;
+ IF FOUND
+ THEN
+ my_val.val = my_rec.val;
+ my_val.frac = my_rec.frac;
+ RETURN my_val;
+ ELSE
+ RETURN NULL;
+ END IF;
+ END LOOP;
+END $$;
+
+COMMENT ON FUNCTION auditor_do_get_balance(TEXT[])
+ IS 'Finds all balances associated with the array of keys given as the argument and returns them in order';
diff --git a/src/auditordb/auditordb_plugin.c b/src/auditordb/auditordb_plugin.c
index d04e8815a..635247aa3 100644
--- a/src/auditordb/auditordb_plugin.c
+++ b/src/auditordb/auditordb_plugin.c
@@ -24,12 +24,6 @@
#include <ltdl.h>
-/**
- * Initialize the plugin.
- *
- * @param cfg configuration to use
- * @return #GNUNET_OK on success
- */
struct TALER_AUDITORDB_Plugin *
TALER_AUDITORDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
@@ -62,11 +56,6 @@ TALER_AUDITORDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg)
}
-/**
- * Shutdown the plugin.
- *
- * @param plugin the plugin to unload
- */
void
TALER_AUDITORDB_plugin_unload (struct TALER_AUDITORDB_Plugin *plugin)
{
diff --git a/src/auditordb/drop.sql b/src/auditordb/drop.sql
new file mode 100644
index 000000000..4bae66103
--- /dev/null
+++ b/src/auditordb/drop.sql
@@ -0,0 +1,31 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2020 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/>
+--
+
+-- Everything in one big transaction
+BEGIN;
+
+WITH xpatches AS (
+ SELECT patch_name
+ FROM _v.patches
+ WHERE starts_with(patch_name,'auditor-')
+)
+ SELECT _v.unregister_patch(xpatches.patch_name)
+ FROM xpatches;
+
+DROP SCHEMA auditor CASCADE;
+
+-- And we're out of here...
+COMMIT;
diff --git a/src/auditordb/drop0001.sql b/src/auditordb/drop0001.sql
deleted file mode 100644
index 80f953e32..000000000
--- a/src/auditordb/drop0001.sql
+++ /dev/null
@@ -1,50 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2014--2020 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/>
---
-
--- Everything in one big transaction
-BEGIN;
-
--- This script DROPs all of the tables we create, including the
--- versioning schema!
---
--- Unlike the other SQL files, it SHOULD be updated to reflect the
--- latest requirements for dropping tables.
-
--- Drops for 0001.sql
-DROP TABLE IF EXISTS auditor_predicted_result;
-DROP TABLE IF EXISTS auditor_historic_denomination_revenue;
-DROP TABLE IF EXISTS auditor_balance_summary;
-DROP TABLE IF EXISTS auditor_denomination_pending;
-DROP TABLE IF EXISTS auditor_reserve_balance;
-DROP TABLE IF EXISTS auditor_wire_fee_balance;
-DROP TABLE IF EXISTS auditor_reserves;
-DROP TABLE IF EXISTS auditor_progress_reserve;
-DROP TABLE IF EXISTS auditor_progress_aggregation;
-DROP TABLE IF EXISTS auditor_progress_deposit_confirmation;
-DROP TABLE IF EXISTS auditor_progress_coin;
-DROP TABLE IF EXISTS auditor_exchange_signkeys;
-DROP TABLE IF EXISTS wire_auditor_progress;
-DROP TABLE IF EXISTS wire_auditor_account_progress;
-DROP TABLE IF EXISTS auditor_historic_reserve_summary CASCADE;
-DROP TABLE IF EXISTS auditor_denominations CASCADE;
-DROP TABLE IF EXISTS deposit_confirmations CASCADE;
-DROP TABLE IF EXISTS auditor_exchanges CASCADE;
-
--- Drop versioning (0000.sql)
-DROP SCHEMA IF EXISTS _v CASCADE;
-
--- And we're out of here...
-COMMIT;
diff --git a/src/auditordb/hdr.h b/src/auditordb/hdr.h
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/auditordb/hdr.h
diff --git a/src/auditordb/pg_del_denomination_balance.c b/src/auditordb/pg_del_denomination_balance.c
new file mode 100644
index 000000000..154dc50bb
--- /dev/null
+++ b/src/auditordb/pg_del_denomination_balance.c
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 auditordb/pg_del_denomination_balance.c
+ * @brief Implementation of the del_denomination_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_del_denomination_balance.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_del_denomination_balance (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_del_denomination_balance",
+ "DELETE"
+ " FROM auditor_denomination_pending"
+ " WHERE denom_pub_hash=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_del_denomination_balance",
+ params);
+}
diff --git a/src/auditordb/pg_del_denomination_balance.h b/src/auditordb/pg_del_denomination_balance.h
new file mode 100644
index 000000000..56e9232b0
--- /dev/null
+++ b/src/auditordb/pg_del_denomination_balance.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file auditordb/pg_del_denomination_balance.h
+ * @brief implementation of the del_denomination_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DEL_DENOMINATION_BALANCE_H
+#define PG_DEL_DENOMINATION_BALANCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+/**
+ * Delete information about a denomination key's balances.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param denom_pub_hash hash of the denomination public key
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_del_denomination_balance (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash);
+
+#endif
diff --git a/src/auditordb/pg_del_reserve_info.c b/src/auditordb/pg_del_reserve_info.c
new file mode 100644
index 000000000..619bd0afa
--- /dev/null
+++ b/src/auditordb/pg_del_reserve_info.c
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_del_reserve_info.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_del_reserve_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_del_reserve_info (void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_del_reserve_info",
+ "DELETE"
+ " FROM auditor_reserves"
+ " WHERE reserve_pub=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_del_reserve_info",
+ params);
+}
diff --git a/src/auditordb/pg_del_reserve_info.h b/src/auditordb/pg_del_reserve_info.h
new file mode 100644
index 000000000..88a10bcfd
--- /dev/null
+++ b/src/auditordb/pg_del_reserve_info.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_del_reserve_info.h
+ * @brief implementation of the del_reserve_info function
+ * @author Christian Grothoff
+ */
+#ifndef PG_DEL_RESERVE_INFO_H
+#define PG_DEL_RESERVE_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Delete information about a reserve.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_del_reserve_info (void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub);
+
+
+#endif
diff --git a/src/auditordb/pg_delete_deposit_confirmations.c b/src/auditordb/pg_delete_deposit_confirmations.c
new file mode 100644
index 000000000..6cb76d4e9
--- /dev/null
+++ b/src/auditordb/pg_delete_deposit_confirmations.c
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 auditordb/pg_delete_deposit_confirmations.c
+ * @brief Implementation of the delete_deposit_confirmations function for Postgres
+ * @author Nicola Eigel
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_delete_deposit_confirmations.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_delete_deposit_confirmation (
+ void *cls,
+ uint64_t row_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&row_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_delete_deposit_confirmation",
+ "DELETE"
+ " FROM deposit_confirmations"
+ " WHERE deposit_confirmation_serial_id=$1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_delete_deposit_confirmation",
+ params);
+}
diff --git a/src/auditordb/pg_delete_deposit_confirmations.h b/src/auditordb/pg_delete_deposit_confirmations.h
new file mode 100644
index 000000000..5f7700ba1
--- /dev/null
+++ b/src/auditordb/pg_delete_deposit_confirmations.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 auditordb/pg_delete_deposit_confirmations.h
+ * @brief implementation of the delete_deposit_confirmation function for Postgres
+ * @author Nicola Eigel
+ */
+#ifndef PG_DELETE_DEPOSIT_CONFIRMATIONS_H
+#define PG_DELETE_DEPOSIT_CONFIRMATIONS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+/**
+ * Delete a row from the deposit confirmations table.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param row_id row to delete
+ * @return query transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_delete_deposit_confirmation (
+ void *cls,
+ uint64_t row_id);
+
+
+#endif
diff --git a/src/auditordb/pg_delete_pending_deposit.c b/src/auditordb/pg_delete_pending_deposit.c
new file mode 100644
index 000000000..29814e84b
--- /dev/null
+++ b/src/auditordb/pg_delete_pending_deposit.c
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file auditordb/pg_delete_pending_deposit.c
+ * @brief Implementation of the delete_pending_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_delete_pending_deposit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_delete_pending_deposit (
+ void *cls,
+ uint64_t batch_deposit_serial_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&batch_deposit_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_delete_pending_deposit",
+ "DELETE"
+ " FROM auditor_pending_deposits"
+ " WHERE batch_deposit_serial_id=$1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_delete_pending_deposit",
+ params);
+}
diff --git a/src/auditordb/pg_delete_pending_deposit.h b/src/auditordb/pg_delete_pending_deposit.h
new file mode 100644
index 000000000..8e7159eac
--- /dev/null
+++ b/src/auditordb/pg_delete_pending_deposit.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file auditordb/pg_delete_pending_deposit.h
+ * @brief implementation of the delete_pending_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DELETE_PENDING_DEPOSIT_H
+#define PG_DELETE_PENDING_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Delete a row from the pending deposit table.
+ * Usually done when the respective wire transfer
+ * was finally detected.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param batch_deposit_serial_id which entry to delete
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_delete_pending_deposit (
+ void *cls,
+ uint64_t batch_deposit_serial_id);
+
+
+#endif
diff --git a/src/auditordb/pg_delete_purse_info.c b/src/auditordb/pg_delete_purse_info.c
new file mode 100644
index 000000000..8fa77ba46
--- /dev/null
+++ b/src/auditordb/pg_delete_purse_info.c
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file auditordb/pg_delete_purse_info.c
+ * @brief Implementation of the delete_purse_info function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_delete_purse_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_delete_purse_info (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_purses_delete",
+ "DELETE FROM auditor_purses "
+ " WHERE purse_pub=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_purses_delete",
+ params);
+}
diff --git a/src/auditordb/pg_delete_purse_info.h b/src/auditordb/pg_delete_purse_info.h
new file mode 100644
index 000000000..88393f9b8
--- /dev/null
+++ b/src/auditordb/pg_delete_purse_info.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file auditordb/pg_delete_purse_info.h
+ * @brief implementation of the delete_purse_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DELETE_PURSE_INFO_H
+#define PG_DELETE_PURSE_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Delete information about a purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the reserve
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_delete_purse_info (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub);
+
+
+#endif
diff --git a/src/auditordb/pg_get_auditor_progress.c b/src/auditordb/pg_get_auditor_progress.c
new file mode 100644
index 000000000..3742d2eb6
--- /dev/null
+++ b/src/auditordb/pg_get_auditor_progress.c
@@ -0,0 +1,178 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 pg_get_auditor_progress.c
+ * @brief Implementation of get_auditor_progress function
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_auditor_progress.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #auditor_progress_cb().
+ */
+struct AuditorProgressContext
+{
+
+ /**
+ * Where to store results.
+ */
+ uint64_t **dst;
+
+ /**
+ * Offset in @e dst.
+ */
+ unsigned int off;
+
+ /**
+ * Length of array at @e dst.
+ */
+ unsigned int len;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to true on failure.
+ */
+ bool failure;
+};
+
+
+/**
+ * Helper function for #TAH_PG_get_auditor_progress().
+ * To be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct AuditorProgressContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+auditor_progress_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AuditorProgressContext *ctx = cls;
+
+ GNUNET_assert (num_results <= ctx->len);
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ bool is_missing = false;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("progress_offset",
+ ctx->dst[i]),
+ &is_missing),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->failure = true;
+ return;
+ }
+ if (is_missing)
+ *ctx->dst[i] = 0;
+ ctx->off++;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_auditor_progress (void *cls,
+ const char *progress_key,
+ uint64_t *progress_offset,
+ ...)
+{
+ struct PostgresClosure *pg = cls;
+ unsigned int cnt = 1;
+ va_list ap;
+
+ va_start (ap,
+ progress_offset);
+ while (NULL != va_arg (ap,
+ const char *))
+ {
+ cnt++;
+ (void) va_arg (ap,
+ uint64_t *);
+ }
+ va_end (ap);
+ {
+ const char *keys[cnt];
+ uint64_t *dsts[cnt];
+ unsigned int off = 1;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_array_ptrs_string (cnt,
+ keys,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ struct AuditorProgressContext ctx = {
+ .dst = dsts,
+ .len = cnt,
+ .pg = pg
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ keys[0] = progress_key;
+ dsts[0] = progress_offset;
+
+ va_start (ap,
+ progress_offset);
+ while (off < cnt)
+ {
+ keys[off] = va_arg (ap,
+ const char *);
+ dsts[off] = va_arg (ap,
+ uint64_t *);
+ off++;
+ }
+ GNUNET_assert (NULL == va_arg (ap,
+ const char *));
+ va_end (ap);
+
+ PREPARE (pg,
+ "get_auditor_progress",
+ "SELECT"
+ " auditor_do_get_auditor_progress AS progress_offset"
+ " FROM auditor_do_get_auditor_progress "
+ "($1);");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_auditor_progress",
+ params,
+ &auditor_progress_cb,
+ &ctx);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ if (ctx.failure)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ if (qs < 0)
+ return qs;
+ return qs;
+ }
+}
diff --git a/src/auditordb/pg_get_auditor_progress.h b/src/auditordb/pg_get_auditor_progress.h
new file mode 100644
index 000000000..fee7a4424
--- /dev/null
+++ b/src/auditordb/pg_get_auditor_progress.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 pg_get_auditor_progress.h
+ * @brief implementation of the get_auditor_progress function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_AUDITOR_PROGRESS_H
+#define PG_GET_AUDITOR_PROGRESS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Get information about the progress of the auditor.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param progress_key name of the progress indicator
+ * @param[out] progress_offset set to offset until which we have made progress
+ * @param ... NULL terminated list of additional key-value pairs to fetch
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_auditor_progress (void *cls,
+ const char *progress_key,
+ uint64_t *progress_offset,
+ ...);
+
+#endif
diff --git a/src/auditordb/pg_get_balance.c b/src/auditordb/pg_get_balance.c
new file mode 100644
index 000000000..4edc1c89f
--- /dev/null
+++ b/src/auditordb/pg_get_balance.c
@@ -0,0 +1,183 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 auditordb/pg_get_balance.c
+ * @brief Implementation of the get_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_balance.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #balance_cb().
+ */
+struct BalanceContext
+{
+
+ /**
+ * Where to store results.
+ */
+ struct TALER_Amount **dst;
+
+ /**
+ * Offset in @e dst.
+ */
+ unsigned int off;
+
+ /**
+ * Length of array at @e dst.
+ */
+ unsigned int len;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to true on failure.
+ */
+ bool failure;
+};
+
+
+/**
+ * Helper function for #TAH_PG_get_balance().
+ * To be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct BalanceContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+balance_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct BalanceContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ GNUNET_assert (num_results <= ctx->len);
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ bool is_missing = false;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_amount ("balance",
+ pg->currency,
+ ctx->dst[i]),
+ &is_missing),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->failure = true;
+ return;
+ }
+ if (is_missing)
+ memset (ctx->dst[i],
+ 0,
+ sizeof (struct TALER_Amount));
+ ctx->off++;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_balance (void *cls,
+ const char *balance_key,
+ struct TALER_Amount *balance_value,
+ ...)
+{
+ struct PostgresClosure *pg = cls;
+ unsigned int cnt = 1;
+ va_list ap;
+
+ va_start (ap,
+ balance_value);
+ while (NULL != va_arg (ap,
+ const char *))
+ {
+ cnt++;
+ (void) va_arg (ap,
+ struct TALER_Amount *);
+ }
+ va_end (ap);
+ {
+ const char *keys[cnt];
+ struct TALER_Amount *dsts[cnt];
+ unsigned int off = 1;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_array_ptrs_string (cnt,
+ keys,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ struct BalanceContext ctx = {
+ .dst = dsts,
+ .len = cnt,
+ .pg = pg
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ keys[0] = balance_key;
+ dsts[0] = balance_value;
+
+ va_start (ap,
+ balance_value);
+ while (off < cnt)
+ {
+ keys[off] = va_arg (ap,
+ const char *);
+ dsts[off] = va_arg (ap,
+ struct TALER_Amount *);
+ off++;
+ }
+ GNUNET_assert (NULL == va_arg (ap,
+ const char *));
+ va_end (ap);
+
+ PREPARE (pg,
+ "get_balance",
+ "SELECT "
+ " auditor_do_get_balance AS balance"
+ " FROM auditor_do_get_balance "
+ "($1);");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_balance",
+ params,
+ &balance_cb,
+ &ctx);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ if (ctx.failure)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ if (qs < 0)
+ return qs;
+ GNUNET_assert (qs == ctx.off);
+ return qs;
+ }
+}
diff --git a/src/auditordb/pg_get_balance.h b/src/auditordb/pg_get_balance.h
new file mode 100644
index 000000000..59d2af0ae
--- /dev/null
+++ b/src/auditordb/pg_get_balance.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 auditordb/pg_get_balance.h
+ * @brief implementation of the get_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_BALANCE_H
+#define PG_GET_BALANCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Get summary information about balance tracked by the auditor.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param balance_key key of the balance to store
+ * @param[out] balance_value set to amount stored under @a balance_key
+ * @param ... NULL terminated list of additional key-value pairs to fetch
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_balance (void *cls,
+ const char *balance_key,
+ struct TALER_Amount *balance_value,
+ ...);
+
+#endif
diff --git a/src/auditordb/pg_get_denomination_balance.c b/src/auditordb/pg_get_denomination_balance.c
new file mode 100644
index 000000000..40af766cd
--- /dev/null
+++ b/src/auditordb/pg_get_denomination_balance.c
@@ -0,0 +1,68 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_get_denomination_balance.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_denomination_balance.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_denomination_balance (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_AUDITORDB_DenominationCirculationData *dcd)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("denom_balance",
+ &dcd->denom_balance),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("denom_loss",
+ &dcd->denom_loss),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("denom_risk",
+ &dcd->denom_risk),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("recoup_loss",
+ &dcd->recoup_loss),
+ GNUNET_PQ_result_spec_uint64 ("num_issued",
+ &dcd->num_issued),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "auditor_denomination_pending_select",
+ "SELECT"
+ " denom_balance"
+ ",denom_loss"
+ ",num_issued"
+ ",denom_risk"
+ ",recoup_loss"
+ " FROM auditor_denomination_pending"
+ " WHERE denom_pub_hash=$1");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "auditor_denomination_pending_select",
+ params,
+ rs);
+}
diff --git a/src/auditordb/pg_get_denomination_balance.h b/src/auditordb/pg_get_denomination_balance.h
new file mode 100644
index 000000000..31f377066
--- /dev/null
+++ b/src/auditordb/pg_get_denomination_balance.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_get_denomination_balance.h
+ * @brief implementation of the get_denomination_balance function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_DENOMINATION_BALANCE_H
+#define PG_GET_DENOMINATION_BALANCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Get information about a denomination key's balances.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param denom_pub_hash hash of the denomination public key
+ * @param[out] dcd circulation data to initialize
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_denomination_balance (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_AUDITORDB_DenominationCirculationData *dcd);
+
+#endif
diff --git a/src/auditordb/pg_get_deposit_confirmations.c b/src/auditordb/pg_get_deposit_confirmations.c
new file mode 100644
index 000000000..b8055a296
--- /dev/null
+++ b/src/auditordb/pg_get_deposit_confirmations.c
@@ -0,0 +1,199 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 pg_get_deposit_confirmations.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_deposit_confirmations.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #deposit_confirmation_cb().
+ */
+struct DepositConfirmationContext
+{
+
+ /**
+ * Function to call for each deposit confirmation.
+ */
+ TALER_AUDITORDB_DepositConfirmationCallback cb;
+
+ /**
+ * Closure for @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Query status to return.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Helper function for #TAH_PG_get_deposit_confirmations().
+ * To be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct DepositConfirmationContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+deposit_confirmation_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct DepositConfirmationContext *dcc = cls;
+ struct PostgresClosure *pg = dcc->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t serial_id;
+ struct TALER_AUDITORDB_DepositConfirmation dc = { 0};
+ struct TALER_CoinSpendPublicKeyP *coin_pubs = NULL;
+ struct TALER_CoinSpendSignatureP *coin_sigs = NULL;
+ size_t num_pubs = 0;
+ size_t num_sigs = 0;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &dc.h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("h_policy",
+ &dc.h_policy),
+ GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+ &dc.h_wire),
+ GNUNET_PQ_result_spec_timestamp ("exchange_timestamp",
+ &dc.exchange_timestamp),
+ GNUNET_PQ_result_spec_timestamp ("refund_deadline",
+ &dc.refund_deadline),
+ GNUNET_PQ_result_spec_timestamp ("wire_deadline",
+ &dc.wire_deadline),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("total_without_fee",
+ &dc.total_without_fee),
+ GNUNET_PQ_result_spec_auto_array_from_type (pg->conn,
+ "coin_pubs",
+ &num_pubs,
+ coin_pubs),
+ GNUNET_PQ_result_spec_auto_array_from_type (pg->conn,
+ "coin_sigs",
+ &num_sigs,
+ coin_sigs),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &dc.merchant),
+ GNUNET_PQ_result_spec_auto_from_type ("exchange_sig",
+ &dc.exchange_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
+ &dc.exchange_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &dc.master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue rval;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dcc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ if (num_sigs != num_pubs)
+ {
+ GNUNET_break (0);
+ dcc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ GNUNET_PQ_cleanup_result (rs);
+ return;
+ }
+ dcc->qs = i + 1;
+ dc.coin_pubs = coin_pubs;
+ dc.coin_sigs = coin_sigs;
+ dc.num_coins = num_sigs;
+ rval = dcc->cb (dcc->cb_cls,
+ serial_id,
+ &dc);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != rval)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_deposit_confirmations (
+ void *cls,
+ uint64_t start_id,
+ bool return_suppressed,
+ TALER_AUDITORDB_DepositConfirmationCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&start_id),
+ GNUNET_PQ_query_param_bool (return_suppressed),
+ GNUNET_PQ_query_param_end
+ };
+ struct DepositConfirmationContext dcc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "auditor_deposit_confirmation_select",
+ "SELECT"
+ " deposit_confirmation_serial_id"
+ ",h_contract_terms"
+ ",h_policy"
+ ",h_wire"
+ ",exchange_timestamp"
+ ",wire_deadline"
+ ",refund_deadline"
+ ",total_without_fee"
+ ",coin_pubs"
+ ",coin_sigs"
+ ",merchant_pub"
+ ",exchange_sig"
+ ",exchange_pub"
+ ",master_sig"
+ " FROM auditor_deposit_confirmations"
+ " WHERE deposit_confirmation_serial_id>$1"
+ " AND ($2 OR NOT suppressed);");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "auditor_deposit_confirmation_select",
+ params,
+ &deposit_confirmation_cb,
+ &dcc);
+ if (qs > 0)
+ return dcc.qs;
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+ return qs;
+}
diff --git a/src/auditordb/pg_get_deposit_confirmations.h b/src/auditordb/pg_get_deposit_confirmations.h
new file mode 100644
index 000000000..6b33e9e6f
--- /dev/null
+++ b/src/auditordb/pg_get_deposit_confirmations.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_get_deposit_confirmations.h
+ * @brief implementation of the get_deposit_confirmations function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_DEPOSIT_CONFIRMATIONS_H
+#define PG_GET_DEPOSIT_CONFIRMATIONS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Get information about deposit confirmations from the database.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param start_id row/serial ID where to start the iteration (0 from
+ * the start, exclusive, i.e. serial_ids must start from 1)
+ * @param return_suppressed should suppressed rows be returned anyway?
+ * @param cb function to call with results
+ * @param cb_cls closure for @a cb
+ * @return query result status
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_deposit_confirmations (
+ void *cls,
+ uint64_t start_id,
+ bool return_suppressed,
+ TALER_AUDITORDB_DepositConfirmationCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/auditordb/pg_get_purse_info.c b/src/auditordb/pg_get_purse_info.c
new file mode 100644
index 000000000..6a0faf616
--- /dev/null
+++ b/src/auditordb/pg_get_purse_info.c
@@ -0,0 +1,62 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file auditordb/pg_get_purse_info.c
+ * @brief Implementation of the get_purse_info function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_purse_info.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_purse_info (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ uint64_t *rowid,
+ struct TALER_Amount *balance,
+ struct GNUNET_TIME_Timestamp *expiration_date)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ balance),
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ expiration_date),
+ GNUNET_PQ_result_spec_uint64 ("auditor_purses_rowid",
+ rowid),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "auditor_get_purse_info",
+ "SELECT"
+ " expiration_date"
+ ",balance"
+ " FROM auditor_purses"
+ " WHERE purse_pub=$1");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "auditor_get_purse_info",
+ params,
+ rs);
+}
diff --git a/src/auditordb/pg_get_purse_info.h b/src/auditordb/pg_get_purse_info.h
new file mode 100644
index 000000000..e8268282e
--- /dev/null
+++ b/src/auditordb/pg_get_purse_info.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file auditordb/pg_get_purse_info.h
+ * @brief implementation of the get_purse_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_PURSE_INFO_H
+#define PG_GET_PURSE_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Get information about a purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the purse
+ * @param[out] rowid which row did we get the information from
+ * @param[out] balance set to balance of the purse
+ * @param[out] expiration_date expiration date of the purse
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_purse_info (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ uint64_t *rowid,
+ struct TALER_Amount *balance,
+ struct GNUNET_TIME_Timestamp *expiration_date);
+
+#endif
diff --git a/src/auditordb/pg_get_reserve_info.c b/src/auditordb/pg_get_reserve_info.c
new file mode 100644
index 000000000..f16c6d995
--- /dev/null
+++ b/src/auditordb/pg_get_reserve_info.c
@@ -0,0 +1,88 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_get_reserve_info.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_reserve_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_reserve_info (void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *rowid,
+ struct TALER_AUDITORDB_ReserveFeeBalance *rfb,
+ struct GNUNET_TIME_Timestamp *expiration_date,
+ char **sender_account)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_balance",
+ &rfb->reserve_balance),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_loss",
+ &rfb->reserve_loss),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("withdraw_fee_balance",
+ &rfb->withdraw_fee_balance),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("close_fee_balance",
+ &rfb->close_fee_balance),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee_balance",
+ &rfb->purse_fee_balance),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("open_fee_balance",
+ &rfb->open_fee_balance),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("history_fee_balance",
+ &rfb->history_fee_balance),
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ expiration_date),
+ GNUNET_PQ_result_spec_uint64 ("auditor_reserves_rowid",
+ rowid),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("origin_account",
+ sender_account),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ *sender_account = NULL;
+ PREPARE (pg,
+ "auditor_get_reserve_info",
+ "SELECT"
+ " reserve_balance"
+ ",reserve_loss"
+ ",withdraw_fee_balance"
+ ",close_fee_balance"
+ ",purse_fee_balance"
+ ",open_fee_balance"
+ ",history_fee_balance"
+ ",expiration_date"
+ ",auditor_reserves_rowid"
+ ",origin_account"
+ " FROM auditor_reserves"
+ " WHERE reserve_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "auditor_get_reserve_info",
+ params,
+ rs);
+}
diff --git a/src/auditordb/pg_get_reserve_info.h b/src/auditordb/pg_get_reserve_info.h
new file mode 100644
index 000000000..3eba035fc
--- /dev/null
+++ b/src/auditordb/pg_get_reserve_info.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_get_reserve_info.h
+ * @brief implementation of the get_reserve_info function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_RESERVE_INFO_H
+#define PG_GET_RESERVE_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Get information about a reserve.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @param[out] rowid which row did we get the information from
+ * @param[out] rfb where to store the reserve balance summary
+ * @param[out] expiration_date expiration date of the reserve
+ * @param[out] sender_account from where did the money in the reserve originally come from
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_reserve_info (void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *rowid,
+ struct TALER_AUDITORDB_ReserveFeeBalance *rfb,
+ struct GNUNET_TIME_Timestamp *expiration_date,
+ char **sender_account);
+
+
+#endif
diff --git a/src/auditordb/pg_get_wire_fee_summary.c b/src/auditordb/pg_get_wire_fee_summary.c
new file mode 100644
index 000000000..b0eb9ba50
--- /dev/null
+++ b/src/auditordb/pg_get_wire_fee_summary.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_get_wire_fee_summary.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_wire_fee_summary.h"
+#include "pg_helper.h"
+
+
+/**
+ * Get summary information about an exchanges wire fee balance.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param[out] wire_fee_balance set amount the exchange gained in wire fees
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_wire_fee_summary (void *cls,
+ struct TALER_Amount *wire_fee_balance)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee_balance",
+ wire_fee_balance),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "auditor_wire_fee_balance_select",
+ "SELECT"
+ " wire_fee_balance"
+ " FROM auditor_wire_fee_balance");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "auditor_wire_fee_balance_select",
+ params,
+ rs);
+}
diff --git a/src/auditordb/pg_get_wire_fee_summary.h b/src/auditordb/pg_get_wire_fee_summary.h
new file mode 100644
index 000000000..4c7f1aebd
--- /dev/null
+++ b/src/auditordb/pg_get_wire_fee_summary.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_get_wire_fee_summary.h
+ * @brief implementation of the get_wire_fee_summary function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_WIRE_FEE_SUMMARY_H
+#define PG_GET_WIRE_FEE_SUMMARY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Get summary information about an exchanges wire fee balance.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param[out] wire_fee_balance set amount the exchange gained in wire fees
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_get_wire_fee_summary (void *cls,
+ struct TALER_Amount *wire_fee_balance);
+
+
+#endif
diff --git a/src/auditordb/pg_helper.h b/src/auditordb/pg_helper.h
new file mode 100644
index 000000000..54ccd5978
--- /dev/null
+++ b/src/auditordb/pg_helper.h
@@ -0,0 +1,119 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_helper.h
+ * @brief shared internal definitions for postgres DB plugin
+ * @author Christian Grothoff
+ */
+#ifndef PG_HELPER_H
+#define PG_HELPER_H
+
+
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Type of the "cls" argument given to each of the functions in
+ * our API.
+ */
+struct PostgresClosure
+{
+
+ /**
+ * Postgres connection handle.
+ */
+ struct GNUNET_PQ_Context *conn;
+
+ /**
+ * Name of the ongoing transaction, used to debug cases where
+ * a transaction is not properly terminated via COMMIT or
+ * ROLLBACK.
+ */
+ const char *transaction_name;
+
+ /**
+ * Our configuration.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * How often have we connected to the DB so far?
+ */
+ unsigned long long prep_gen;
+
+ /**
+ * Which currency should we assume all amounts to be in?
+ */
+ char *currency;
+};
+
+
+/**
+ * Prepares SQL statement @a sql under @a name for
+ * connection @a pg once.
+ * Returns with #GNUNET_DB_STATUS_HARD_ERROR on failure.
+ *
+ * @param pg a `struct PostgresClosure`
+ * @param name name to prepare the statement under
+ * @param sql actual SQL text
+ */
+#define PREPARE(pg,name,sql) \
+ do { \
+ static struct { \
+ unsigned long long cnt; \
+ struct PostgresClosure *pg; \
+ } preps[2]; /* 2 ctrs for taler-auditor-sync*/ \
+ unsigned int off = 0; \
+ \
+ while ( (NULL != preps[off].pg) && \
+ (pg != preps[off].pg) && \
+ (off < sizeof(preps) / sizeof(*preps)) ) \
+ off++; \
+ GNUNET_assert (off < \
+ sizeof(preps) / sizeof(*preps)); \
+ if (preps[off].cnt < pg->prep_gen) \
+ { \
+ struct GNUNET_PQ_PreparedStatement ps[] = { \
+ GNUNET_PQ_make_prepare (name, sql), \
+ GNUNET_PQ_PREPARED_STATEMENT_END \
+ }; \
+ \
+ if (GNUNET_OK != \
+ GNUNET_PQ_prepare_statements (pg->conn, \
+ ps)) \
+ { \
+ GNUNET_break (0); \
+ return GNUNET_DB_STATUS_HARD_ERROR; \
+ } \
+ preps[off].pg = pg; \
+ preps[off].cnt = pg->prep_gen; \
+ } \
+ } while (0)
+
+
+/**
+ * Wrapper macro to add the currency from the plugin's state
+ * when fetching amounts from the database.
+ *
+ * @param field name of the database field to fetch amount from
+ * @param[out] amountp pointer to amount to set
+ */
+#define TALER_PQ_RESULT_SPEC_AMOUNT(field, \
+ amountp) TALER_PQ_result_spec_amount ( \
+ field,pg->currency,amountp)
+
+
+#endif
diff --git a/src/auditordb/pg_insert_auditor_progress.c b/src/auditordb/pg_insert_auditor_progress.c
new file mode 100644
index 000000000..3c5d25eef
--- /dev/null
+++ b/src/auditordb/pg_insert_auditor_progress.c
@@ -0,0 +1,97 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 pg_insert_auditor_progress.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_auditor_progress.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_auditor_progress (
+ void *cls,
+ const char *progress_key,
+ uint64_t progress_offset,
+ ...)
+{
+ struct PostgresClosure *pg = cls;
+ unsigned int cnt = 1;
+ va_list ap;
+
+ va_start (ap,
+ progress_offset);
+ while (NULL != va_arg (ap,
+ const char *))
+ {
+ cnt++;
+ (void) va_arg (ap,
+ uint64_t);
+ }
+ va_end (ap);
+ {
+ const char *keys[cnt];
+ uint64_t offsets[cnt];
+ unsigned int off = 1;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_array_ptrs_string (cnt,
+ keys,
+ pg->conn),
+ GNUNET_PQ_query_param_array_uint64 (cnt,
+ offsets,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ keys[0] = progress_key;
+ offsets[0] = progress_offset;
+
+ va_start (ap,
+ progress_offset);
+ while (off < cnt)
+ {
+ keys[off] = va_arg (ap,
+ const char *);
+ offsets[off] = va_arg (ap,
+ uint64_t);
+ off++;
+ }
+ GNUNET_assert (NULL == va_arg (ap,
+ const char *));
+ va_end (ap);
+
+ PREPARE (pg,
+ "auditor_progress_insert",
+ "INSERT INTO auditor_progress "
+ "(progress_key"
+ ",progress_offset"
+ ") SELECT *"
+ " FROM UNNEST (CAST($1 AS TEXT[]),"
+ " CAST($2 AS INT8[]))"
+ " ON CONFLICT DO NOTHING;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_progress_insert",
+ params);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
+ }
+}
diff --git a/src/auditordb/pg_insert_auditor_progress.h b/src/auditordb/pg_insert_auditor_progress.h
new file mode 100644
index 000000000..a20e376c8
--- /dev/null
+++ b/src/auditordb/pg_insert_auditor_progress.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_auditor_progress.h
+ * @brief implementation of the insert_auditor_progress function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_AUDITOR_PROGRESS_H
+#define PG_INSERT_AUDITOR_PROGRESS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Insert information about the auditor's progress with an exchange's
+ * data.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param progress_key name of the progress indicator
+ * @param progress_offset offset until which we have made progress
+ * @param ... NULL terminated list of additional key-value pairs to insert
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_auditor_progress (
+ void *cls,
+ const char *progress_key,
+ uint64_t progress_offset,
+ ...);
+
+#endif
diff --git a/src/auditordb/pg_insert_balance.c b/src/auditordb/pg_insert_balance.c
new file mode 100644
index 000000000..0d1762acc
--- /dev/null
+++ b/src/auditordb/pg_insert_balance.c
@@ -0,0 +1,97 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 pg_insert_balance.c
+ * @brief Implementation of the insert_balance function
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_balance.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_balance (void *cls,
+ const char *balance_key,
+ const struct TALER_Amount *balance_value,
+ ...)
+{
+ struct PostgresClosure *pg = cls;
+ unsigned int cnt = 1;
+ va_list ap;
+
+ va_start (ap,
+ balance_value);
+ while (NULL != va_arg (ap,
+ const char *))
+ {
+ cnt++;
+ (void) va_arg (ap,
+ const struct TALER_Amount *);
+ }
+ va_end (ap);
+ {
+ const char *keys[cnt];
+ struct TALER_Amount amounts[cnt];
+ unsigned int off = 1;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_array_ptrs_string (cnt,
+ keys,
+ pg->conn),
+ TALER_PQ_query_param_array_amount (cnt,
+ amounts,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ keys[0] = balance_key;
+ amounts[0] = *balance_value;
+
+ va_start (ap,
+ balance_value);
+ while (off < cnt)
+ {
+ keys[off] = va_arg (ap,
+ const char *);
+ amounts[off] = *va_arg (ap,
+ const struct TALER_Amount *);
+ off++;
+ }
+ GNUNET_assert (NULL == va_arg (ap,
+ const char *));
+ va_end (ap);
+
+ PREPARE (pg,
+ "auditor_balance_insert",
+ "INSERT INTO auditor_balances "
+ "(balance_key"
+ ",balance_value.val"
+ ",balance_value.frac"
+ ") SELECT *"
+ " FROM UNNEST (CAST($1 AS TEXT[]),"
+ " CAST($2 AS taler_amount[]))"
+ " ON CONFLICT DO NOTHING;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_balance_insert",
+ params);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
+ }
+}
diff --git a/src/auditordb/pg_insert_balance.h b/src/auditordb/pg_insert_balance.h
new file mode 100644
index 000000000..dff69eec9
--- /dev/null
+++ b/src/auditordb/pg_insert_balance.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 pg_insert_balance.h
+ * @brief implementation of the insert_balance function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_BALANCE_H
+#define PG_INSERT_BALANCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+/**
+ * Insert information about a balance tracked by the auditor. There must not be an
+ * existing record.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param balance_key key of the balance to store
+ * @param balance_value value to store
+ * @param ... NULL terminated list of additional key-value pairs to insert
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_balance (void *cls,
+ const char *balance_key,
+ const struct TALER_Amount *balance_value,
+ ...);
+
+
+#endif
diff --git a/src/auditordb/pg_insert_denomination_balance.c b/src/auditordb/pg_insert_denomination_balance.c
new file mode 100644
index 000000000..bbf4127f4
--- /dev/null
+++ b/src/auditordb/pg_insert_denomination_balance.c
@@ -0,0 +1,65 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_denomination_balance.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_denomination_balance.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_denomination_balance (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ const struct TALER_AUDITORDB_DenominationCirculationData *dcd)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+ TALER_PQ_query_param_amount (pg->conn,
+ &dcd->denom_balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ &dcd->denom_loss),
+ GNUNET_PQ_query_param_uint64 (&dcd->num_issued),
+ TALER_PQ_query_param_amount (pg->conn,
+ &dcd->denom_risk),
+ TALER_PQ_query_param_amount (pg->conn,
+ &dcd->recoup_loss),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_denomination_pending_insert",
+ "INSERT INTO auditor_denomination_pending "
+ "(denom_pub_hash"
+ ",denom_balance"
+ ",denom_loss"
+ ",num_issued"
+ ",denom_risk"
+ ",recoup_loss"
+ ") VALUES ("
+ "$1,$2,$3,$4,$5,$6"
+ ");");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_denomination_pending_insert",
+ params);
+}
diff --git a/src/auditordb/pg_insert_denomination_balance.h b/src/auditordb/pg_insert_denomination_balance.h
new file mode 100644
index 000000000..90776367c
--- /dev/null
+++ b/src/auditordb/pg_insert_denomination_balance.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_denomination_balance.h
+ * @brief implementation of the insert_denomination_balance function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_DENOMINATION_BALANCE_H
+#define PG_INSERT_DENOMINATION_BALANCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Insert information about a denomination key's balances. There
+ * must not be an existing record for the denomination key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param denom_pub_hash hash of the denomination public key
+ * @param dcd circulation data to store
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_denomination_balance (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ const struct TALER_AUDITORDB_DenominationCirculationData *dcd);
+
+
+#endif
diff --git a/src/auditordb/pg_insert_deposit_confirmation.c b/src/auditordb/pg_insert_deposit_confirmation.c
new file mode 100644
index 000000000..1b5205782
--- /dev/null
+++ b/src/auditordb/pg_insert_deposit_confirmation.c
@@ -0,0 +1,77 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 pg_insert_deposit_confirmation.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_deposit_confirmation.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_deposit_confirmation (
+ void *cls,
+ const struct TALER_AUDITORDB_DepositConfirmation *dc)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&dc->h_contract_terms),
+ GNUNET_PQ_query_param_auto_from_type (&dc->h_policy),
+ GNUNET_PQ_query_param_auto_from_type (&dc->h_wire),
+ GNUNET_PQ_query_param_timestamp (&dc->exchange_timestamp),
+ GNUNET_PQ_query_param_timestamp (&dc->wire_deadline),
+ GNUNET_PQ_query_param_timestamp (&dc->refund_deadline),
+ TALER_PQ_query_param_amount (pg->conn,
+ &dc->total_without_fee),
+ GNUNET_PQ_query_param_array_auto_from_type (dc->num_coins,
+ dc->coin_pubs,
+ pg->conn),
+ GNUNET_PQ_query_param_array_auto_from_type (dc->num_coins,
+ dc->coin_sigs,
+ pg->conn),
+ GNUNET_PQ_query_param_auto_from_type (&dc->merchant),
+ GNUNET_PQ_query_param_auto_from_type (&dc->exchange_sig),
+ GNUNET_PQ_query_param_auto_from_type (&dc->exchange_pub),
+ GNUNET_PQ_query_param_auto_from_type (&dc->master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_deposit_confirmation_insert",
+ "INSERT INTO deposit_confirmations "
+ "(h_contract_terms"
+ ",h_policy"
+ ",h_wire"
+ ",exchange_timestamp"
+ ",wire_deadline"
+ ",refund_deadline"
+ ",total_without_fee"
+ ",coin_pubs"
+ ",coin_sigs"
+ ",merchant_pub"
+ ",exchange_sig"
+ ",exchange_pub"
+ ",master_sig" /* master_sig could be normalized... */
+ ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_deposit_confirmation_insert",
+ params);
+}
diff --git a/src/auditordb/pg_insert_deposit_confirmation.h b/src/auditordb/pg_insert_deposit_confirmation.h
new file mode 100644
index 000000000..f3d11e3cb
--- /dev/null
+++ b/src/auditordb/pg_insert_deposit_confirmation.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_deposit_confirmation.h
+ * @brief implementation of the insert_deposit_confirmation function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_DEPOSIT_CONFIRMATION_H
+#define PG_INSERT_DEPOSIT_CONFIRMATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Insert information about a deposit confirmation into the database.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param dc deposit confirmation information to store
+ * @return query result status
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_deposit_confirmation (
+ void *cls,
+ const struct TALER_AUDITORDB_DepositConfirmation *dc);
+
+
+#endif
diff --git a/src/auditordb/pg_insert_exchange_signkey.c b/src/auditordb/pg_insert_exchange_signkey.c
new file mode 100644
index 000000000..8bf439da0
--- /dev/null
+++ b/src/auditordb/pg_insert_exchange_signkey.c
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_exchange_signkey.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_exchange_signkey.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_exchange_signkey (
+ void *cls,
+ const struct TALER_AUDITORDB_ExchangeSigningKey *sk)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&sk->ep_start),
+ GNUNET_PQ_query_param_timestamp (&sk->ep_expire),
+ GNUNET_PQ_query_param_timestamp (&sk->ep_end),
+ GNUNET_PQ_query_param_auto_from_type (&sk->exchange_pub),
+ GNUNET_PQ_query_param_auto_from_type (&sk->master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_insert_exchange_signkey",
+ "INSERT INTO auditor_exchange_signkeys "
+ "(ep_start"
+ ",ep_expire"
+ ",ep_end"
+ ",exchange_pub"
+ ",master_sig"
+ ") VALUES ($1,$2,$3,$4,$5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_insert_exchange_signkey",
+ params);
+}
diff --git a/src/auditordb/pg_insert_exchange_signkey.h b/src/auditordb/pg_insert_exchange_signkey.h
new file mode 100644
index 000000000..1c1eefe35
--- /dev/null
+++ b/src/auditordb/pg_insert_exchange_signkey.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_exchange_signkey.h
+ * @brief implementation of the insert_exchange_signkey function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_EXCHANGE_SIGNKEY_H
+#define PG_INSERT_EXCHANGE_SIGNKEY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+/**
+ * Insert information about a signing key of the exchange.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param sk signing key information to store
+ * @return query result status
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_exchange_signkey (
+ void *cls,
+ const struct TALER_AUDITORDB_ExchangeSigningKey *sk);
+
+
+#endif
diff --git a/src/auditordb/pg_insert_historic_denom_revenue.c b/src/auditordb/pg_insert_historic_denom_revenue.c
new file mode 100644
index 000000000..2c3dd44cc
--- /dev/null
+++ b/src/auditordb/pg_insert_historic_denom_revenue.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_historic_denom_revenue.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_historic_denom_revenue.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_historic_denom_revenue (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct GNUNET_TIME_Timestamp revenue_timestamp,
+ const struct TALER_Amount *revenue_balance,
+ const struct TALER_Amount *loss_balance)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+ GNUNET_PQ_query_param_timestamp (&revenue_timestamp),
+ TALER_PQ_query_param_amount (pg->conn,
+ revenue_balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ loss_balance),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_historic_denomination_revenue_insert",
+ "INSERT INTO auditor_historic_denomination_revenue"
+ "(denom_pub_hash"
+ ",revenue_timestamp"
+ ",revenue_balance"
+ ",loss_balance"
+ ") VALUES ($1,$2,$3,$4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_historic_denomination_revenue_insert",
+ params);
+}
diff --git a/src/auditordb/pg_insert_historic_denom_revenue.h b/src/auditordb/pg_insert_historic_denom_revenue.h
new file mode 100644
index 000000000..02567119b
--- /dev/null
+++ b/src/auditordb/pg_insert_historic_denom_revenue.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_historic_denom_revenue.h
+ * @brief implementation of the insert_historic_denom_revenue function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_HISTORIC_DENOM_REVENUE_H
+#define PG_INSERT_HISTORIC_DENOM_REVENUE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Insert information about an exchange's historic
+ * revenue about a denomination key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param denom_pub_hash hash of the denomination key
+ * @param revenue_timestamp when did this profit get realized
+ * @param revenue_balance what was the total profit made from
+ * deposit fees, melting fees, refresh fees
+ * and coins that were never returned?
+ * @param loss_balance total losses suffered by the exchange at the time
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_historic_denom_revenue (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct GNUNET_TIME_Timestamp revenue_timestamp,
+ const struct TALER_Amount *revenue_balance,
+ const struct TALER_Amount *loss_balance);
+
+#endif
diff --git a/src/auditordb/pg_insert_historic_reserve_revenue.c b/src/auditordb/pg_insert_historic_reserve_revenue.c
new file mode 100644
index 000000000..953fba34a
--- /dev/null
+++ b/src/auditordb/pg_insert_historic_reserve_revenue.c
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_historic_reserve_revenue.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_historic_reserve_revenue.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_historic_reserve_revenue (
+ void *cls,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ const struct TALER_Amount *reserve_profits)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&start_time),
+ GNUNET_PQ_query_param_timestamp (&end_time),
+ TALER_PQ_query_param_amount (pg->conn,
+ reserve_profits),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_historic_reserve_summary_insert",
+ "INSERT INTO auditor_historic_reserve_summary"
+ "(start_date"
+ ",end_date"
+ ",reserve_profits"
+ ") VALUES ($1,$2,$3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_historic_reserve_summary_insert",
+ params);
+}
diff --git a/src/auditordb/pg_insert_historic_reserve_revenue.h b/src/auditordb/pg_insert_historic_reserve_revenue.h
new file mode 100644
index 000000000..a76a50b39
--- /dev/null
+++ b/src/auditordb/pg_insert_historic_reserve_revenue.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_historic_reserve_revenue.h
+ * @brief implementation of the insert_historic_reserve_revenue function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_HISTORIC_RESERVE_REVENUE_H
+#define PG_INSERT_HISTORIC_RESERVE_REVENUE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Insert information about an exchange's historic revenue from reserves.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param start_time beginning of aggregated time interval
+ * @param end_time end of aggregated time interval
+ * @param reserve_profits total profits made
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_historic_reserve_revenue (
+ void *cls,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ const struct TALER_Amount *reserve_profits);
+
+#endif
diff --git a/src/auditordb/pg_insert_pending_deposit.c b/src/auditordb/pg_insert_pending_deposit.c
new file mode 100644
index 000000000..50b655ee7
--- /dev/null
+++ b/src/auditordb/pg_insert_pending_deposit.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file auditordb/pg_insert_pending_deposit.c
+ * @brief Implementation of the insert_pending_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_pending_deposit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_pending_deposit (
+ void *cls,
+ uint64_t batch_deposit_serial_id,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ const struct TALER_Amount *total_amount,
+ struct GNUNET_TIME_Timestamp deadline)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ total_amount),
+ GNUNET_PQ_query_param_auto_from_type (wire_target_h_payto),
+ GNUNET_PQ_query_param_uint64 (&batch_deposit_serial_id),
+ GNUNET_PQ_query_param_timestamp (&deadline),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_insert_pending_deposit",
+ "INSERT INTO auditor_pending_deposits "
+ "(total_amount"
+ ",wire_target_h_payto"
+ ",batch_deposit_serial_id"
+ ",deadline"
+ ") VALUES ($1,$2,$3,$4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_insert_pending_deposit",
+ params);
+}
diff --git a/src/auditordb/pg_insert_pending_deposit.h b/src/auditordb/pg_insert_pending_deposit.h
new file mode 100644
index 000000000..7c2b59809
--- /dev/null
+++ b/src/auditordb/pg_insert_pending_deposit.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file auditordb/pg_insert_pending_deposit.h
+ * @brief implementation of the insert_pending_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_PENDING_DEPOSIT_H
+#define PG_INSERT_PENDING_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Insert new row into the pending deposits table.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param batch_deposit_serial_id where in the table are we
+ * @param total_amount value of all missing deposits, including fees
+ * @param wire_target_h_payto hash of the recipient account's payto URI
+ * @param deadline what was the requested wire transfer deadline
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_pending_deposit (
+ void *cls,
+ uint64_t batch_deposit_serial_id,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ const struct TALER_Amount *total_amount,
+ struct GNUNET_TIME_Timestamp deadline);
+
+
+#endif
diff --git a/src/auditordb/pg_insert_purse_info.c b/src/auditordb/pg_insert_purse_info.c
new file mode 100644
index 000000000..7eaad8d3c
--- /dev/null
+++ b/src/auditordb/pg_insert_purse_info.c
@@ -0,0 +1,55 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file auditordb/pg_insert_purse_info.c
+ * @brief Implementation of the insert_purse_info function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_purse_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_purse_info (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance,
+ struct GNUNET_TIME_Timestamp expiration_date)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ TALER_PQ_query_param_amount (pg->conn,
+ balance),
+ GNUNET_PQ_query_param_timestamp (&expiration_date),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_purses_insert",
+ "INSERT INTO auditor_purses "
+ "(purse_pub"
+ ",target"
+ ",expiration_date"
+ ") VALUES ($1,$2,$3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_purses_insert",
+ params);
+}
diff --git a/src/auditordb/pg_insert_purse_info.h b/src/auditordb/pg_insert_purse_info.h
new file mode 100644
index 000000000..ee1b3eebd
--- /dev/null
+++ b/src/auditordb/pg_insert_purse_info.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file auditordb/pg_insert_purse_info.h
+ * @brief implementation of the insert_purse_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_PURSE_INFO_H
+#define PG_INSERT_PURSE_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+/**
+ * Insert information about a purse. There must not be an
+ * existing record for the purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the purse
+ * @param balance balance of the purse
+ * @param expiration_date expiration date of the purse
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_purse_info (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance,
+ struct GNUNET_TIME_Timestamp expiration_date);
+
+#endif
diff --git a/src/auditordb/pg_insert_reserve_info.c b/src/auditordb/pg_insert_reserve_info.c
new file mode 100644
index 000000000..4c99394fe
--- /dev/null
+++ b/src/auditordb/pg_insert_reserve_info.c
@@ -0,0 +1,79 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_reserve_info.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_reserve_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_reserve_info (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_AUDITORDB_ReserveFeeBalance *rfb,
+ struct GNUNET_TIME_Timestamp expiration_date,
+ const char *origin_account)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ TALER_PQ_query_param_amount (pg->conn,
+ &rfb->reserve_balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ &rfb->reserve_loss),
+ TALER_PQ_query_param_amount (pg->conn,
+ &rfb->withdraw_fee_balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ &rfb->close_fee_balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ &rfb->purse_fee_balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ &rfb->open_fee_balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ &rfb->history_fee_balance),
+ GNUNET_PQ_query_param_timestamp (&expiration_date),
+ NULL == origin_account
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (origin_account),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_insert_reserve_info",
+ "INSERT INTO auditor_reserves "
+ "(reserve_pub"
+ ",reserve_balance"
+ ",reserve_loss"
+ ",withdraw_fee_balance"
+ ",close_fee_balance"
+ ",purse_fee_balance"
+ ",open_fee_balance"
+ ",history_fee_balance"
+ ",expiration_date"
+ ",origin_account"
+ ") VALUES "
+ "($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_insert_reserve_info",
+ params);
+}
diff --git a/src/auditordb/pg_insert_reserve_info.h b/src/auditordb/pg_insert_reserve_info.h
new file mode 100644
index 000000000..b416aa556
--- /dev/null
+++ b/src/auditordb/pg_insert_reserve_info.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_reserve_info.h
+ * @brief implementation of the insert_reserve_info function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_RESERVE_INFO_H
+#define PG_INSERT_RESERVE_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Insert information about a reserve. There must not be an
+ * existing record for the reserve.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @param rfb balance amounts for the reserve
+ * @param expiration_date when will the reserve expire
+ * @param origin_account where did the money in the reserve originally come from
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_insert_reserve_info (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_AUDITORDB_ReserveFeeBalance *rfb,
+ struct GNUNET_TIME_Timestamp expiration_date,
+ const char *origin_account);
+
+#endif
diff --git a/src/auditordb/pg_select_historic_denom_revenue.c b/src/auditordb/pg_select_historic_denom_revenue.c
new file mode 100644
index 000000000..aa44625e7
--- /dev/null
+++ b/src/auditordb/pg_select_historic_denom_revenue.c
@@ -0,0 +1,146 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_historic_denom_revenue.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_historic_denom_revenue.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #historic_denom_revenue_cb().
+ */
+struct HistoricDenomRevenueContext
+{
+ /**
+ * Function to call for each result.
+ */
+ TALER_AUDITORDB_HistoricDenominationRevenueDataCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Number of results processed.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Helper function for #TAH_PG_select_historic_denom_revenue().
+ * To be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct HistoricRevenueContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+historic_denom_revenue_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct HistoricDenomRevenueContext *hrc = cls;
+ struct PostgresClosure *pg = hrc->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct TALER_DenominationHashP denom_pub_hash;
+ struct GNUNET_TIME_Timestamp revenue_timestamp;
+ struct TALER_Amount revenue_balance;
+ struct TALER_Amount loss;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &denom_pub_hash),
+ GNUNET_PQ_result_spec_timestamp ("revenue_timestamp",
+ &revenue_timestamp),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("revenue_balance",
+ &revenue_balance),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("loss_balance",
+ &loss),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ hrc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+
+ hrc->qs = i + 1;
+ if (GNUNET_OK !=
+ hrc->cb (hrc->cb_cls,
+ &denom_pub_hash,
+ revenue_timestamp,
+ &revenue_balance,
+ &loss))
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_select_historic_denom_revenue (
+ void *cls,
+ TALER_AUDITORDB_HistoricDenominationRevenueDataCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct HistoricDenomRevenueContext hrc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "auditor_historic_denomination_revenue_select",
+ "SELECT"
+ " denom_pub_hash"
+ ",revenue_timestamp"
+ ",revenue_balance"
+ ",loss_balance"
+ " FROM auditor_historic_denomination_revenue;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "auditor_historic_denomination_revenue_select",
+ params,
+ &historic_denom_revenue_cb,
+ &hrc);
+ if (qs <= 0)
+ return qs;
+ return hrc.qs;
+}
diff --git a/src/auditordb/pg_select_historic_denom_revenue.h b/src/auditordb/pg_select_historic_denom_revenue.h
new file mode 100644
index 000000000..25a68dacb
--- /dev/null
+++ b/src/auditordb/pg_select_historic_denom_revenue.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_historic_denom_revenue.h
+ * @brief implementation of the select_historic_denom_revenue function
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_HISTORIC_DENOM_REVENUE_H
+#define PG_SELECT_HISTORIC_DENOM_REVENUE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Obtain all of the historic denomination key revenue
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call with the results
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_select_historic_denom_revenue (
+ void *cls,
+ TALER_AUDITORDB_HistoricDenominationRevenueDataCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/auditordb/pg_select_historic_reserve_revenue.c b/src/auditordb/pg_select_historic_reserve_revenue.c
new file mode 100644
index 000000000..877c3e2d7
--- /dev/null
+++ b/src/auditordb/pg_select_historic_reserve_revenue.c
@@ -0,0 +1,140 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_historic_reserve_revenue.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_historic_reserve_revenue.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #historic_reserve_revenue_cb().
+ */
+struct HistoricReserveRevenueContext
+{
+ /**
+ * Function to call for each result.
+ */
+ TALER_AUDITORDB_HistoricReserveRevenueDataCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Number of results processed.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Helper function for #TAH_PG_select_historic_reserve_revenue().
+ * To be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct HistoricRevenueContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+historic_reserve_revenue_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct HistoricReserveRevenueContext *hrc = cls;
+ struct PostgresClosure *pg = hrc->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct TALER_Amount reserve_profits;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("start_date",
+ &start_date),
+ GNUNET_PQ_result_spec_timestamp ("end_date",
+ &end_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_profits",
+ &reserve_profits),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ hrc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ hrc->qs = i + 1;
+ if (GNUNET_OK !=
+ hrc->cb (hrc->cb_cls,
+ start_date,
+ end_date,
+ &reserve_profits))
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_select_historic_reserve_revenue (
+ void *cls,
+ TALER_AUDITORDB_HistoricReserveRevenueDataCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ struct HistoricReserveRevenueContext hrc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+
+ PREPARE (pg,
+ "auditor_historic_reserve_summary_select",
+ "SELECT"
+ " start_date"
+ ",end_date"
+ ",reserve_profits"
+ " FROM auditor_historic_reserve_summary");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "auditor_historic_reserve_summary_select",
+ params,
+ &historic_reserve_revenue_cb,
+ &hrc);
+ if (0 >= qs)
+ return qs;
+ return hrc.qs;
+}
diff --git a/src/auditordb/pg_select_historic_reserve_revenue.h b/src/auditordb/pg_select_historic_reserve_revenue.h
new file mode 100644
index 000000000..b067c8917
--- /dev/null
+++ b/src/auditordb/pg_select_historic_reserve_revenue.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_historic_reserve_revenue.h
+ * @brief implementation of the select_historic_reserve_revenue function
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_HISTORIC_RESERVE_REVENUE_H
+#define PG_SELECT_HISTORIC_RESERVE_REVENUE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+/**
+ * Return information about an exchange's historic revenue from reserves.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call with results
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_select_historic_reserve_revenue (
+ void *cls,
+ TALER_AUDITORDB_HistoricReserveRevenueDataCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/auditordb/pg_select_pending_deposits.c b/src/auditordb/pg_select_pending_deposits.c
new file mode 100644
index 000000000..1190fb132
--- /dev/null
+++ b/src/auditordb/pg_select_pending_deposits.c
@@ -0,0 +1,149 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file auditordb/pg_select_pending_deposits.c
+ * @brief Implementation of the select_pending_deposits function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_pending_deposits.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #wire_missing_cb().
+ */
+struct WireMissingContext
+{
+
+ /**
+ * Function to call for each pending deposit.
+ */
+ TALER_AUDITORDB_WireMissingCallback cb;
+
+ /**
+ * Closure for @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Query status to return.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Helper function for #TAH_PG_select_purse_expired().
+ * To be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct WireMissingContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+wire_missing_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct WireMissingContext *eic = cls;
+ struct PostgresClosure *pg = eic->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t batch_deposit_serial_id;
+ struct TALER_Amount total_amount;
+ struct TALER_PaytoHashP wire_target_h_payto;
+ struct GNUNET_TIME_Timestamp deadline;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("batch_deposit_serial_id",
+ &batch_deposit_serial_id),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("total_amount",
+ &total_amount),
+ GNUNET_PQ_result_spec_auto_from_type ("wire_target_h_payto",
+ &wire_target_h_payto),
+ GNUNET_PQ_result_spec_timestamp ("deadline",
+ &deadline),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ eic->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ eic->cb (eic->cb_cls,
+ batch_deposit_serial_id,
+ &total_amount,
+ &wire_target_h_payto,
+ deadline);
+ }
+ eic->qs = num_results;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_select_pending_deposits (
+ void *cls,
+ struct GNUNET_TIME_Absolute deadline,
+ TALER_AUDITORDB_WireMissingCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&deadline),
+ GNUNET_PQ_query_param_end
+ };
+ struct WireMissingContext eic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "auditor_select_pending_deposits",
+ "SELECT"
+ " batch_deposit_serial_id"
+ ",total_amount"
+ ",wire_target_h_payto"
+ ",deadline"
+ " FROM auditor_pending_deposits"
+ " WHERE deadline<$1;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "auditor_select_pending_deposits",
+ params,
+ &wire_missing_cb,
+ &eic);
+ if (0 > qs)
+ return qs;
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != eic.qs);
+ return eic.qs;
+}
diff --git a/src/auditordb/pg_select_pending_deposits.h b/src/auditordb/pg_select_pending_deposits.h
new file mode 100644
index 000000000..e165098c5
--- /dev/null
+++ b/src/auditordb/pg_select_pending_deposits.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file auditordb/pg_select_pending_deposits.h
+ * @brief implementation of the select_pending_deposits function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PENDING_DEPOSITS_H
+#define PG_SELECT_PENDING_DEPOSITS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Return (batch) deposits for which we have not yet
+ * seen the required wire transfer.
+ *
+ * @param cls closure
+ * @param deadline only return up to this deadline
+ * @param cb function to call on each entry
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_select_pending_deposits (
+ void *cls,
+ struct GNUNET_TIME_Absolute deadline,
+ TALER_AUDITORDB_WireMissingCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/auditordb/pg_select_purse_expired.c b/src/auditordb/pg_select_purse_expired.c
new file mode 100644
index 000000000..77c6d3b26
--- /dev/null
+++ b/src/auditordb/pg_select_purse_expired.c
@@ -0,0 +1,146 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file auditordb/pg_select_purse_expired.c
+ * @brief Implementation of the select_purse_expired function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_expired.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #purse_expired_cb().
+ */
+struct PurseExpiredContext
+{
+
+ /**
+ * Function to call for each expired purse.
+ */
+ TALER_AUDITORDB_ExpiredPurseCallback cb;
+
+ /**
+ * Closure for @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Query status to return.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Helper function for #TAH_PG_select_purse_expired().
+ * To be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct PurseExpiredContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+purse_expired_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct PurseExpiredContext *eic = cls;
+ struct PostgresClosure *pg = eic->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct GNUNET_TIME_Timestamp expiration_date;
+ struct TALER_Amount balance;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &purse_pub),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ &balance),
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ &expiration_date),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ eic->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ eic->qs = i + 1;
+ if (GNUNET_OK !=
+ eic->cb (eic->cb_cls,
+ &purse_pub,
+ &balance,
+ expiration_date))
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_select_purse_expired (
+ void *cls,
+ TALER_AUDITORDB_ExpiredPurseCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp now
+ = GNUNET_TIME_timestamp_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct PurseExpiredContext eic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "auditor_select_expired_purses",
+ "SELECT"
+ " purse_pub"
+ ",expiration_date"
+ ",balance"
+ " FROM auditor_purses"
+ " AND expiration_date<$1;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "auditor_select_expired_purses",
+ params,
+ &purse_expired_cb,
+ &eic);
+ if (qs > 0)
+ return eic.qs;
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+ return qs;
+}
diff --git a/src/auditordb/pg_select_purse_expired.h b/src/auditordb/pg_select_purse_expired.h
new file mode 100644
index 000000000..36d81285c
--- /dev/null
+++ b/src/auditordb/pg_select_purse_expired.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file auditordb/pg_select_purse_expired.h
+ * @brief implementation of the select_purse_expired function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_EXPIRED_H
+#define PG_SELECT_PURSE_EXPIRED_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Get information about expired purses.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on expired purses
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_select_purse_expired (
+ void *cls,
+ TALER_AUDITORDB_ExpiredPurseCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/auditordb/pg_template.c b/src/auditordb/pg_template.c
new file mode 100644
index 000000000..e44fdab60
--- /dev/null
+++ b/src/auditordb/pg_template.c
@@ -0,0 +1,26 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 auditordb/pg_template.c
+ * @brief Implementation of the template function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_template.h"
+#include "pg_helper.h"
diff --git a/src/auditordb/pg_template.h b/src/auditordb/pg_template.h
new file mode 100644
index 000000000..911faf107
--- /dev/null
+++ b/src/auditordb/pg_template.h
@@ -0,0 +1,29 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 auditordb/pg_template.h
+ * @brief implementation of the template function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_TEMPLATE_H
+#define PG_TEMPLATE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+#endif
diff --git a/src/auditordb/pg_template.sh b/src/auditordb/pg_template.sh
new file mode 100755
index 000000000..c0937dcd3
--- /dev/null
+++ b/src/auditordb/pg_template.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# This file is in the public domain.
+#
+# Instantiates pg_template for a particular function.
+
+for n in $*
+do
+ NCAPS=`echo $n | tr a-z A-Z`
+ if test ! -e pg_$n.c
+ then
+ cat pg_template.c | sed -e s/template/$n/g -e s/TEMPLATE/$NCAPS/g > pg_$n.c
+ cat pg_template.h | sed -e s/template/$n/g -e s/TEMPLATE/$NCAPS/g > pg_$n.h
+ echo " plugin->$n\n = &TAH_PG_$n;" >> tmpl.c
+ echo "#include \"pg_$n.h\"" >> tmpl.inc
+ echo " pg_$n.h pg_$n.c \\" >> tmpl.am
+ fi
+done
+
+echo "Add lines from tmpl.am to Makefile.am"
+echo "Add lines from tmpl.inc to plugin_auditordb_postgres.c at the beginning"
+echo "Add lines from tmpl.c to plugin_auditordb_postgres.c at the end"
diff --git a/src/auditordb/pg_update_auditor_progress.c b/src/auditordb/pg_update_auditor_progress.c
new file mode 100644
index 000000000..b30be18bd
--- /dev/null
+++ b/src/auditordb/pg_update_auditor_progress.c
@@ -0,0 +1,99 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 pg_update_auditor_progress.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_auditor_progress.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_update_auditor_progress (
+ void *cls,
+ const char *progress_key,
+ uint64_t progress_offset,
+ ...)
+{
+ struct PostgresClosure *pg = cls;
+ unsigned int cnt = 1;
+ va_list ap;
+
+ va_start (ap,
+ progress_offset);
+ while (NULL != va_arg (ap,
+ const char *))
+ {
+ cnt++;
+ (void) va_arg (ap,
+ uint64_t);
+ }
+ va_end (ap);
+ {
+ const char *keys[cnt];
+ uint64_t offsets[cnt];
+ unsigned int off = 1;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_array_ptrs_string (cnt,
+ keys,
+ pg->conn),
+ GNUNET_PQ_query_param_array_uint64 (cnt,
+ offsets,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ keys[0] = progress_key;
+ offsets[0] = progress_offset;
+
+ va_start (ap,
+ progress_offset);
+ while (off < cnt)
+ {
+ keys[off] = va_arg (ap,
+ const char *);
+ offsets[off] = va_arg (ap,
+ uint64_t);
+ off++;
+ }
+ GNUNET_assert (NULL == va_arg (ap,
+ const char *));
+ va_end (ap);
+
+ PREPARE (pg,
+ "auditor_progress_update",
+ "UPDATE auditor_progress"
+ " SET progress_offset=data.off"
+ " FROM ("
+ " SELECT *"
+ " FROM UNNEST (CAST($1 AS TEXT[]),"
+ " CAST($2 AS INT8[]))"
+ " AS t(key,off)"
+ " ) AS data"
+ " WHERE auditor_progress.progress_key=data.key;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_progress_update",
+ params);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
+ }
+}
diff --git a/src/auditordb/pg_update_auditor_progress.h b/src/auditordb/pg_update_auditor_progress.h
new file mode 100644
index 000000000..e8de58161
--- /dev/null
+++ b/src/auditordb/pg_update_auditor_progress.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_update_auditor_progress.h
+ * @brief implementation of the update_auditor_progress function
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_AUDITOR_PROGRESS_H
+#define PG_UPDATE_AUDITOR_PROGRESS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Update information about the progress of the auditor. There
+ * must be an existing record for the exchange.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param progress_key name of the progress indicator
+ * @param progress_offset offset until which we have made progress
+ * @param ... NULL terminated list of additional key-value pairs to update
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_update_auditor_progress (
+ void *cls,
+ const char *progress_key,
+ uint64_t progress_offset,
+ ...);
+
+#endif
diff --git a/src/auditordb/pg_update_balance.c b/src/auditordb/pg_update_balance.c
new file mode 100644
index 000000000..eff03056b
--- /dev/null
+++ b/src/auditordb/pg_update_balance.c
@@ -0,0 +1,100 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file auditordb/pg_update_balance.c
+ * @brief Implementation of the update_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_balance.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_update_balance (
+ void *cls,
+ const char *balance_key,
+ const struct TALER_Amount *balance_amount,
+ ...)
+{
+ struct PostgresClosure *pg = cls;
+ unsigned int cnt = 1;
+ va_list ap;
+
+ va_start (ap,
+ balance_amount);
+ while (NULL != va_arg (ap,
+ const char *))
+ {
+ cnt++;
+ (void) va_arg (ap,
+ const struct TALER_Amount *);
+ }
+ va_end (ap);
+ {
+ const char *keys[cnt];
+ struct TALER_Amount amounts[cnt];
+ unsigned int off = 1;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_array_ptrs_string (cnt,
+ keys,
+ pg->conn),
+ TALER_PQ_query_param_array_amount (cnt,
+ amounts,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ keys[0] = balance_key;
+ amounts[0] = *balance_amount;
+
+ va_start (ap,
+ balance_amount);
+ while (off < cnt)
+ {
+ keys[off] = va_arg (ap,
+ const char *);
+ amounts[off] = *va_arg (ap,
+ const struct TALER_Amount *);
+ off++;
+ }
+ GNUNET_assert (NULL == va_arg (ap,
+ const char *));
+ va_end (ap);
+
+ PREPARE (pg,
+ "auditor_balance_update",
+ "UPDATE auditor_balances"
+ " SET balance_value.val=data.val"
+ " ,balance_value.frac=data.frac"
+ " FROM ("
+ " SELECT *"
+ " FROM UNNEST (CAST($1 AS TEXT[]),"
+ " CAST($2 AS taler_amount[]))"
+ " AS t(key,val,frac)"
+ " ) AS data"
+ " WHERE auditor_balances.balance_key=data.key;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_balance_update",
+ params);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
+ }
+}
diff --git a/src/auditordb/pg_update_balance.h b/src/auditordb/pg_update_balance.h
new file mode 100644
index 000000000..8f83726f5
--- /dev/null
+++ b/src/auditordb/pg_update_balance.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 auditordb/pg_update_balance.h
+ * @brief implementation of the update_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_BALANCE_H
+#define PG_UPDATE_BALANCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Insert information about a balance tracked by the auditor. Destructively updates an
+ * existing record, which must already exist.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param balance_key key of the balance to store
+ * @param balance_amount value to store
+ * @param ... NULL terminated list of additional key-value pairs to update
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_update_balance (
+ void *cls,
+ const char *balance_key,
+ const struct TALER_Amount *balance_amount,
+ ...);
+
+
+#endif
diff --git a/src/auditordb/pg_update_denomination_balance.c b/src/auditordb/pg_update_denomination_balance.c
new file mode 100644
index 000000000..0c738779e
--- /dev/null
+++ b/src/auditordb/pg_update_denomination_balance.c
@@ -0,0 +1,62 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_update_denomination_balance.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_denomination_balance.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_update_denomination_balance (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ const struct TALER_AUDITORDB_DenominationCirculationData *dcd)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ &dcd->denom_balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ &dcd->denom_loss),
+ GNUNET_PQ_query_param_uint64 (&dcd->num_issued),
+ TALER_PQ_query_param_amount (pg->conn,
+ &dcd->denom_risk),
+ TALER_PQ_query_param_amount (pg->conn,
+ &dcd->recoup_loss),
+ GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_denomination_pending_update",
+ "UPDATE auditor_denomination_pending SET"
+ " denom_balance=$1"
+ ",denom_loss=$2"
+ ",num_issued=$3"
+ ",denom_risk=$4"
+ ",recoup_loss=$5"
+ " WHERE denom_pub_hash=$6");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_denomination_pending_update",
+ params);
+}
diff --git a/src/auditordb/pg_update_denomination_balance.h b/src/auditordb/pg_update_denomination_balance.h
new file mode 100644
index 000000000..474fcaafd
--- /dev/null
+++ b/src/auditordb/pg_update_denomination_balance.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_update_denomination_balance.h
+ * @brief implementation of the update_denomination_balance function
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_DENOMINATION_BALANCE_H
+#define PG_UPDATE_DENOMINATION_BALANCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Update information about a denomination key's balances. There
+ * must be an existing record for the denomination key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param denom_pub_hash hash of the denomination public key
+ * @param dcd circulation data to store
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_update_denomination_balance (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ const struct TALER_AUDITORDB_DenominationCirculationData *dcd);
+
+
+#endif
diff --git a/src/auditordb/pg_update_purse_info.c b/src/auditordb/pg_update_purse_info.c
new file mode 100644
index 000000000..66dfd490e
--- /dev/null
+++ b/src/auditordb/pg_update_purse_info.c
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file auditordb/pg_update_purse_info.c
+ * @brief Implementation of the update_purse_info function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_purse_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_update_purse_info (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance)
+{
+
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ TALER_PQ_query_param_amount (pg->conn,
+ balance),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_purses_update",
+ "UPDATE auditor_purses"
+ " SET balance=$2"
+ " WHERE purse_pub=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_purses_update",
+ params);
+}
diff --git a/src/auditordb/pg_update_purse_info.h b/src/auditordb/pg_update_purse_info.h
new file mode 100644
index 000000000..ac37be7b4
--- /dev/null
+++ b/src/auditordb/pg_update_purse_info.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file auditordb/pg_update_purse_info.h
+ * @brief implementation of the update_purse_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_PURSE_INFO_H
+#define PG_UPDATE_PURSE_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Update information about a purse. Destructively updates an
+ * existing record, which must already exist.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the purse
+ * @param balance new balance for the purse
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_update_purse_info (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance);
+
+
+#endif
diff --git a/src/auditordb/pg_update_reserve_info.c b/src/auditordb/pg_update_reserve_info.c
new file mode 100644
index 000000000..2d38a2b0e
--- /dev/null
+++ b/src/auditordb/pg_update_reserve_info.c
@@ -0,0 +1,69 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_update_reserve_info.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_reserve_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_update_reserve_info (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_AUDITORDB_ReserveFeeBalance *rfb,
+ struct GNUNET_TIME_Timestamp expiration_date)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ &rfb->reserve_balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ &rfb->reserve_loss),
+ TALER_PQ_query_param_amount (pg->conn,
+ &rfb->withdraw_fee_balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ &rfb->purse_fee_balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ &rfb->open_fee_balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ &rfb->history_fee_balance),
+ GNUNET_PQ_query_param_timestamp (&expiration_date),
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_update_reserve_info",
+ "UPDATE auditor_reserves SET"
+ " reserve_balance=$1"
+ ",reserve_loss=$2"
+ ",withdraw_fee_balance=$3"
+ ",purse_fee_balance=$4"
+ ",open_fee_balance=$5"
+ ",history_fee_balance=$6"
+ ",expiration_date=$7"
+ " WHERE reserve_pub=$8");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_update_reserve_info",
+ params);
+}
diff --git a/src/auditordb/pg_update_reserve_info.h b/src/auditordb/pg_update_reserve_info.h
new file mode 100644
index 000000000..25f43476a
--- /dev/null
+++ b/src/auditordb/pg_update_reserve_info.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_update_reserve_info.h
+ * @brief implementation of the update_reserve_info function
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_RESERVE_INFO_H
+#define PG_UPDATE_RESERVE_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Update information about a reserve. Destructively updates an
+ * existing record, which must already exist.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @param rfb amounts for the reserve
+ * @param expiration_date expiration date of the reserve
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_update_reserve_info (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_AUDITORDB_ReserveFeeBalance *rfb,
+ struct GNUNET_TIME_Timestamp expiration_date);
+
+#endif
diff --git a/src/auditordb/pg_update_wire_fee_summary.c b/src/auditordb/pg_update_wire_fee_summary.c
new file mode 100644
index 000000000..192612f17
--- /dev/null
+++ b/src/auditordb/pg_update_wire_fee_summary.c
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_update_wire_fee_summary.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_wire_fee_summary.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TAH_PG_update_wire_fee_summary (
+ void *cls,
+ const struct TALER_Amount *wire_fee_balance)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ wire_fee_balance),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "auditor_wire_fee_balance_update",
+ "UPDATE auditor_wire_fee_balance SET"
+ " wire_fee_balance=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "auditor_wire_fee_balance_update",
+ params);
+}
diff --git a/src/auditordb/pg_update_wire_fee_summary.h b/src/auditordb/pg_update_wire_fee_summary.h
new file mode 100644
index 000000000..a004a2db4
--- /dev/null
+++ b/src/auditordb/pg_update_wire_fee_summary.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_update_wire_fee_summary.h
+ * @brief implementation of the update_wire_fee_summary function
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_WIRE_FEE_SUMMARY_H
+#define PG_UPDATE_WIRE_FEE_SUMMARY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_auditordb_plugin.h"
+
+
+/**
+ * Insert information about exchange's wire fee balance. Destructively updates an
+ * existing record, which must already exist.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param wire_fee_balance amount the exchange gained in wire fees
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TAH_PG_update_wire_fee_summary (
+ void *cls,
+ const struct TALER_Amount *wire_fee_balance);
+
+#endif
diff --git a/src/auditordb/plugin_auditordb_postgres.c b/src/auditordb/plugin_auditordb_postgres.c
index 467c4c6db..f5c405d8d 100644
--- a/src/auditordb/plugin_auditordb_postgres.c
+++ b/src/auditordb/plugin_auditordb_postgres.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
+ Copyright (C) 2014-2024 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
@@ -21,9 +21,41 @@
*/
#include "platform.h"
#include "taler_pq_lib.h"
-#include "taler_auditordb_plugin.h"
#include <pthread.h>
#include <libpq-fe.h>
+#include "pg_delete_deposit_confirmations.h"
+#include "pg_delete_pending_deposit.h"
+#include "pg_delete_purse_info.h"
+#include "pg_del_denomination_balance.h"
+#include "pg_del_reserve_info.h"
+#include "pg_get_auditor_progress.h"
+#include "pg_get_balance.h"
+#include "pg_get_denomination_balance.h"
+#include "pg_get_deposit_confirmations.h"
+#include "pg_get_purse_info.h"
+#include "pg_get_reserve_info.h"
+#include "pg_get_wire_fee_summary.h"
+#include "pg_helper.h"
+#include "pg_insert_auditor_progress.h"
+#include "pg_insert_balance.h"
+#include "pg_insert_denomination_balance.h"
+#include "pg_insert_deposit_confirmation.h"
+#include "pg_insert_exchange_signkey.h"
+#include "pg_insert_historic_denom_revenue.h"
+#include "pg_insert_historic_reserve_revenue.h"
+#include "pg_insert_pending_deposit.h"
+#include "pg_insert_purse_info.h"
+#include "pg_insert_reserve_info.h"
+#include "pg_select_historic_denom_revenue.h"
+#include "pg_select_historic_reserve_revenue.h"
+#include "pg_select_pending_deposits.h"
+#include "pg_select_purse_expired.h"
+#include "pg_update_auditor_progress.h"
+#include "pg_update_balance.h"
+#include "pg_update_denomination_balance.h"
+#include "pg_update_purse_info.h"
+#include "pg_update_reserve_info.h"
+#include "pg_update_wire_fee_summary.h"
#define LOG(kind,...) GNUNET_log_from (kind, "taler-auditordb-postgres", \
@@ -31,73 +63,6 @@
/**
- * Wrapper macro to add the currency from the plugin's state
- * when fetching amounts from the database.
- *
- * @param field name of the database field to fetch amount from
- * @param[out] amountp pointer to amount to set
- */
-#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) \
- TALER_PQ_result_spec_amount ( \
- field,pg->currency,amountp)
-
-/**
- * Wrapper macro to add the currency from the plugin's state
- * when fetching amounts from the database. NBO variant.
- *
- * @param field name of the database field to fetch amount from
- * @param[out] amountp pointer to amount to set
- */
-#define TALER_PQ_RESULT_SPEC_AMOUNT_NBO(field, \
- amountp) TALER_PQ_result_spec_amount_nbo ( \
- field,pg->currency,amountp)
-
-
-/**
- * Handle for a database session (per-thread, for transactions).
- */
-struct TALER_AUDITORDB_Session
-{
- /**
- * Postgres connection handle.
- */
- struct GNUNET_PQ_Context *conn;
-
- /**
- * Name of the ongoing transaction, used to debug cases where
- * a transaction is not properly terminated via COMMIT or
- * ROLLBACK.
- */
- const char *transaction_name;
-};
-
-
-/**
- * Type of the "cls" argument given to each of the functions in
- * our API.
- */
-struct PostgresClosure
-{
-
- /**
- * Thread-local database connection.
- * Contains a pointer to `PGconn` or NULL.
- */
- pthread_key_t db_conn_threadlocal;
-
- /**
- * Our configuration.
- */
- const struct GNUNET_CONFIGURATION_Handle *cfg;
-
- /**
- * Which currency should we assume all amounts to be in?
- */
- char *currency;
-};
-
-
-/**
* Drop all auditor tables OR deletes recoverable auditor state.
* This should only be used by testcases or when restarting the
* auditor from scratch.
@@ -108,22 +73,25 @@ struct PostgresClosure
* used when restarting the auditor
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
-static int
+static enum GNUNET_GenericReturnValue
postgres_drop_tables (void *cls,
- int drop_exchangelist)
+ bool drop_exchangelist)
{
struct PostgresClosure *pc = cls;
struct GNUNET_PQ_Context *conn;
+ enum GNUNET_GenericReturnValue ret;
conn = GNUNET_PQ_connect_with_cfg (pc->cfg,
"auditordb-postgres",
- (drop_exchangelist) ? "drop" : "restart",
+ NULL,
NULL,
NULL);
if (NULL == conn)
return GNUNET_SYSERR;
+ ret = GNUNET_PQ_exec_sql (conn,
+ (drop_exchangelist) ? "drop" : "restart");
GNUNET_PQ_disconnect (conn);
- return GNUNET_OK;
+ return ret;
}
@@ -131,662 +99,210 @@ postgres_drop_tables (void *cls,
* Create the necessary tables if they are not present
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param support_partitions true to support partitioning
+ * @param num_partitions number of partitions to use
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
-static int
-postgres_create_tables (void *cls)
+static enum GNUNET_GenericReturnValue
+postgres_create_tables (void *cls,
+ bool support_partitions,
+ uint32_t num_partitions)
{
struct PostgresClosure *pc = cls;
+ enum GNUNET_GenericReturnValue ret = GNUNET_OK;
struct GNUNET_PQ_Context *conn;
+ struct GNUNET_PQ_QueryParam params[] = {
+ support_partitions
+ ? GNUNET_PQ_query_param_uint32 (&num_partitions)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_PreparedStatement ps[] = {
+ GNUNET_PQ_make_prepare ("create_tables",
+ "SELECT"
+ " auditor.do_create_tables"
+ " ($1);"),
+ GNUNET_PQ_PREPARED_STATEMENT_END
+ };
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_try_execute ("SET search_path TO auditor;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
conn = GNUNET_PQ_connect_with_cfg (pc->cfg,
"auditordb-postgres",
"auditor-",
- NULL,
- NULL);
+ es,
+ ps);
if (NULL == conn)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to connect to database\n");
return GNUNET_SYSERR;
+ }
+ if (0 >
+ GNUNET_PQ_eval_prepared_non_select (conn,
+ "create_tables",
+ params))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to run 'create_tables' prepared statement\n");
+ ret = GNUNET_SYSERR;
+ }
+ if (GNUNET_OK == ret)
+ {
+ ret = GNUNET_PQ_exec_sql (conn,
+ "procedures");
+ if (GNUNET_OK != ret)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to load stored procedures\n");
+ }
GNUNET_PQ_disconnect (conn);
- return GNUNET_OK;
+ return ret;
+}
+
+
+/**
+ * Register callback to be invoked on events of type @a es.
+ *
+ * @param cls database context to use
+ * @param es specification of the event to listen for
+ * @param timeout how long to wait for the event
+ * @param cb function to call when the event happens, possibly
+ * mulrewardle times (until cancel is invoked)
+ * @param cb_cls closure for @a cb
+ * @return handle useful to cancel the listener
+ */
+static struct GNUNET_DB_EventHandler *
+postgres_event_listen (void *cls,
+ const struct GNUNET_DB_EventHeaderP *es,
+ struct GNUNET_TIME_Relative timeout,
+ GNUNET_DB_EventCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+
+ return GNUNET_PQ_event_listen (pg->conn,
+ es,
+ timeout,
+ cb,
+ cb_cls);
}
/**
- * Close thread-local database connection when a thread is destroyed.
+ * Stop notifications.
*
- * @param cls closure we get from pthreads (the db handle)
+ * @param eh handle to unregister.
*/
static void
-db_conn_destroy (void *cls)
+postgres_event_listen_cancel (struct GNUNET_DB_EventHandler *eh)
{
- struct TALER_AUDITORDB_Session *session = cls;
- struct GNUNET_PQ_Context *db_conn;
+ GNUNET_PQ_event_listen_cancel (eh);
+}
- if (NULL == session)
- return;
- db_conn = session->conn;
- session->conn = NULL;
- if (NULL != db_conn)
- GNUNET_PQ_disconnect (db_conn);
- GNUNET_free (session);
+
+/**
+ * Notify all that listen on @a es of an event.
+ *
+ * @param cls database context to use
+ * @param es specification of the event to generate
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+postgres_event_notify (void *cls,
+ const struct GNUNET_DB_EventHeaderP *es,
+ const void *extra,
+ size_t extra_size)
+{
+ struct PostgresClosure *pg = cls;
+
+ return GNUNET_PQ_event_notify (pg->conn,
+ es,
+ extra,
+ extra_size);
}
/**
- * Get the thread-local database-handle.
* Connect to the db if the connection does not exist yet.
*
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @return the database connection, or NULL on error
+ * @param[in,out] pg the plugin-specific state
+ * @return #GNUNET_OK on success
*/
-static struct TALER_AUDITORDB_Session *
-postgres_get_session (void *cls)
+static enum GNUNET_GenericReturnValue
+setup_connection (struct PostgresClosure *pg)
{
- struct PostgresClosure *pc = cls;
- struct GNUNET_PQ_Context *db_conn;
- struct TALER_AUDITORDB_Session *session;
- struct GNUNET_PQ_PreparedStatement ps[] = {
- /* used in #postgres_commit */
- GNUNET_PQ_make_prepare ("do_commit",
- "COMMIT",
- 0),
- /* used in #postgres_insert_exchange */
- GNUNET_PQ_make_prepare ("auditor_insert_exchange",
- "INSERT INTO auditor_exchanges "
- "(master_pub"
- ",exchange_url"
- ") VALUES ($1,$2);",
- 2),
- /* used in #postgres_delete_exchange */
- GNUNET_PQ_make_prepare ("auditor_delete_exchange",
- "DELETE"
- " FROM auditor_exchanges"
- " WHERE master_pub=$1;",
- 1),
- /* used in #postgres_list_exchanges */
- GNUNET_PQ_make_prepare ("auditor_list_exchanges",
- "SELECT"
- " master_pub"
- ",exchange_url"
- " FROM auditor_exchanges",
- 0),
- /* used in #postgres_insert_exchange_signkey */
- GNUNET_PQ_make_prepare ("auditor_insert_exchange_signkey",
- "INSERT INTO auditor_exchange_signkeys "
- "(master_pub"
- ",ep_start"
- ",ep_expire"
- ",ep_end"
- ",exchange_pub"
- ",master_sig"
- ") VALUES ($1,$2,$3,$4,$5,$6);",
- 6),
- /* Used in #postgres_insert_denomination_info() */
- GNUNET_PQ_make_prepare ("auditor_denominations_insert",
- "INSERT INTO auditor_denominations "
- "(denom_pub_hash"
- ",master_pub"
- ",valid_from"
- ",expire_withdraw"
- ",expire_deposit"
- ",expire_legal"
- ",coin_val"
- ",coin_frac"
- ",fee_withdraw_val"
- ",fee_withdraw_frac"
- ",fee_deposit_val"
- ",fee_deposit_frac"
- ",fee_refresh_val"
- ",fee_refresh_frac"
- ",fee_refund_val"
- ",fee_refund_frac"
- ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16);",
- 16),
- /* Used in #postgres_insert_denomination_info() */
- GNUNET_PQ_make_prepare ("auditor_denominations_select",
- "SELECT"
- " denom_pub_hash"
- ",valid_from"
- ",expire_withdraw"
- ",expire_deposit"
- ",expire_legal"
- ",coin_val"
- ",coin_frac"
- ",fee_withdraw_val"
- ",fee_withdraw_frac"
- ",fee_deposit_val"
- ",fee_deposit_frac"
- ",fee_refresh_val"
- ",fee_refresh_frac"
- ",fee_refund_val"
- ",fee_refund_frac"
- " FROM auditor_denominations"
- " WHERE master_pub=$1;",
- 1),
- /* Used in #postgres_insert_deposit_confirmation() */
- GNUNET_PQ_make_prepare ("auditor_deposit_confirmation_insert",
- "INSERT INTO deposit_confirmations "
- "(master_pub"
- ",h_contract_terms"
- ",h_wire"
- ",timestamp"
- ",refund_deadline"
- ",amount_without_fee_val"
- ",amount_without_fee_frac"
- ",coin_pub"
- ",merchant_pub"
- ",exchange_sig"
- ",exchange_pub"
- ",master_sig" /* master_sig could be normalized... */
- ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12);",
- 12),
- /* Used in #postgres_get_deposit_confirmations() */
- GNUNET_PQ_make_prepare ("auditor_deposit_confirmation_select",
- "SELECT"
- " serial_id"
- ",h_contract_terms"
- ",h_wire"
- ",timestamp"
- ",refund_deadline"
- ",amount_without_fee_val"
- ",amount_without_fee_frac"
- ",coin_pub"
- ",merchant_pub"
- ",exchange_sig"
- ",exchange_pub"
- ",master_sig" /* master_sig could be normalized... */
- " FROM deposit_confirmations"
- " WHERE master_pub=$1"
- " AND serial_id>$2",
- 2),
- /* Used in #postgres_update_auditor_progress_reserve() */
- GNUNET_PQ_make_prepare ("auditor_progress_update_reserve",
- "UPDATE auditor_progress_reserve SET "
- " last_reserve_in_serial_id=$1"
- ",last_reserve_out_serial_id=$2"
- ",last_reserve_recoup_serial_id=$3"
- ",last_reserve_close_serial_id=$4"
- " WHERE master_pub=$5",
- 5),
- /* Used in #postgres_get_auditor_progress_reserve() */
- GNUNET_PQ_make_prepare ("auditor_progress_select_reserve",
- "SELECT"
- " last_reserve_in_serial_id"
- ",last_reserve_out_serial_id"
- ",last_reserve_recoup_serial_id"
- ",last_reserve_close_serial_id"
- " FROM auditor_progress_reserve"
- " WHERE master_pub=$1;",
- 1),
- /* Used in #postgres_insert_auditor_progress_reserve() */
- GNUNET_PQ_make_prepare ("auditor_progress_insert_reserve",
- "INSERT INTO auditor_progress_reserve "
- "(master_pub"
- ",last_reserve_in_serial_id"
- ",last_reserve_out_serial_id"
- ",last_reserve_recoup_serial_id"
- ",last_reserve_close_serial_id"
- ") VALUES ($1,$2,$3,$4,$5);",
- 5),
- /* Used in #postgres_update_auditor_progress_aggregation() */
- GNUNET_PQ_make_prepare ("auditor_progress_update_aggregation",
- "UPDATE auditor_progress_aggregation SET "
- " last_wire_out_serial_id=$1"
- " WHERE master_pub=$2",
- 2),
- /* Used in #postgres_get_auditor_progress_aggregation() */
- GNUNET_PQ_make_prepare ("auditor_progress_select_aggregation",
- "SELECT"
- " last_wire_out_serial_id"
- " FROM auditor_progress_aggregation"
- " WHERE master_pub=$1;",
- 1),
- /* Used in #postgres_insert_auditor_progress_aggregation() */
- GNUNET_PQ_make_prepare ("auditor_progress_insert_aggregation",
- "INSERT INTO auditor_progress_aggregation "
- "(master_pub"
- ",last_wire_out_serial_id"
- ") VALUES ($1,$2);",
- 2),
- /* Used in #postgres_update_auditor_progress_deposit_confirmation() */
- GNUNET_PQ_make_prepare ("auditor_progress_update_deposit_confirmation",
- "UPDATE auditor_progress_deposit_confirmation SET "
- " last_deposit_confirmation_serial_id=$1"
- " WHERE master_pub=$2",
- 2),
- /* Used in #postgres_get_auditor_progress_deposit_confirmation() */
- GNUNET_PQ_make_prepare ("auditor_progress_select_deposit_confirmation",
- "SELECT"
- " last_deposit_confirmation_serial_id"
- " FROM auditor_progress_deposit_confirmation"
- " WHERE master_pub=$1;",
- 1),
- /* Used in #postgres_insert_auditor_progress_deposit_confirmation() */
- GNUNET_PQ_make_prepare ("auditor_progress_insert_deposit_confirmation",
- "INSERT INTO auditor_progress_deposit_confirmation "
- "(master_pub"
- ",last_deposit_confirmation_serial_id"
- ") VALUES ($1,$2);",
- 2),
- /* Used in #postgres_update_auditor_progress_coin() */
- GNUNET_PQ_make_prepare ("auditor_progress_update_coin",
- "UPDATE auditor_progress_coin SET "
- " last_withdraw_serial_id=$1"
- ",last_deposit_serial_id=$2"
- ",last_melt_serial_id=$3"
- ",last_refund_serial_id=$4"
- ",last_recoup_serial_id=$5"
- ",last_recoup_refresh_serial_id=$6"
- " WHERE master_pub=$7",
- 7),
- /* Used in #postgres_get_auditor_progress_coin() */
- GNUNET_PQ_make_prepare ("auditor_progress_select_coin",
- "SELECT"
- " last_withdraw_serial_id"
- ",last_deposit_serial_id"
- ",last_melt_serial_id"
- ",last_refund_serial_id"
- ",last_recoup_serial_id"
- ",last_recoup_refresh_serial_id"
- " FROM auditor_progress_coin"
- " WHERE master_pub=$1;",
- 1),
- /* Used in #postgres_insert_auditor_progress() */
- GNUNET_PQ_make_prepare ("auditor_progress_insert_coin",
- "INSERT INTO auditor_progress_coin "
- "(master_pub"
- ",last_withdraw_serial_id"
- ",last_deposit_serial_id"
- ",last_melt_serial_id"
- ",last_refund_serial_id"
- ",last_recoup_serial_id"
- ",last_recoup_refresh_serial_id"
- ") VALUES ($1,$2,$3,$4,$5,$6,$7);",
- 7),
- /* Used in #postgres_insert_wire_auditor_account_progress() */
- GNUNET_PQ_make_prepare ("wire_auditor_account_progress_insert",
- "INSERT INTO wire_auditor_account_progress "
- "(master_pub"
- ",account_name"
- ",last_wire_reserve_in_serial_id"
- ",last_wire_wire_out_serial_id"
- ",wire_in_off"
- ",wire_out_off"
- ") VALUES ($1,$2,$3,$4,$5,$6);",
- 6),
- /* Used in #postgres_update_wire_auditor_account_progress() */
- GNUNET_PQ_make_prepare ("wire_auditor_account_progress_update",
- "UPDATE wire_auditor_account_progress SET "
- " last_wire_reserve_in_serial_id=$1"
- ",last_wire_wire_out_serial_id=$2"
- ",wire_in_off=$3"
- ",wire_out_off=$4"
- " WHERE master_pub=$5 AND account_name=$6",
- 6),
- /* Used in #postgres_get_wire_auditor_account_progress() */
- GNUNET_PQ_make_prepare ("wire_auditor_account_progress_select",
- "SELECT"
- " last_wire_reserve_in_serial_id"
- ",last_wire_wire_out_serial_id"
- ",wire_in_off"
- ",wire_out_off"
- " FROM wire_auditor_account_progress"
- " WHERE master_pub=$1 AND account_name=$2;",
- 2),
- /* Used in #postgres_insert_wire_auditor_progress() */
- GNUNET_PQ_make_prepare ("wire_auditor_progress_insert",
- "INSERT INTO wire_auditor_progress "
- "(master_pub"
- ",last_timestamp"
- ",last_reserve_close_uuid"
- ") VALUES ($1,$2,$3);",
- 3),
- /* Used in #postgres_update_wire_auditor_progress() */
- GNUNET_PQ_make_prepare ("wire_auditor_progress_update",
- "UPDATE wire_auditor_progress SET "
- " last_timestamp=$1"
- ",last_reserve_close_uuid=$2"
- " WHERE master_pub=$3",
- 3),
- /* Used in #postgres_get_wire_auditor_progress() */
- GNUNET_PQ_make_prepare ("wire_auditor_progress_select",
- "SELECT"
- " last_timestamp"
- ",last_reserve_close_uuid"
- " FROM wire_auditor_progress"
- " WHERE master_pub=$1;",
- 1),
- /* Used in #postgres_insert_reserve_info() */
- GNUNET_PQ_make_prepare ("auditor_reserves_insert",
- "INSERT INTO auditor_reserves "
- "(reserve_pub"
- ",master_pub"
- ",reserve_balance_val"
- ",reserve_balance_frac"
- ",withdraw_fee_balance_val"
- ",withdraw_fee_balance_frac"
- ",expiration_date"
- ",origin_account"
- ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8);",
- 8),
- /* Used in #postgres_update_reserve_info() */
- GNUNET_PQ_make_prepare ("auditor_reserves_update",
- "UPDATE auditor_reserves SET"
- " reserve_balance_val=$1"
- ",reserve_balance_frac=$2"
- ",withdraw_fee_balance_val=$3"
- ",withdraw_fee_balance_frac=$4"
- ",expiration_date=$5"
- " WHERE reserve_pub=$6 AND master_pub=$7;",
- 7),
- /* Used in #postgres_get_reserve_info() */
- GNUNET_PQ_make_prepare ("auditor_reserves_select",
- "SELECT"
- " reserve_balance_val"
- ",reserve_balance_frac"
- ",withdraw_fee_balance_val"
- ",withdraw_fee_balance_frac"
- ",expiration_date"
- ",auditor_reserves_rowid"
- ",origin_account"
- " FROM auditor_reserves"
- " WHERE reserve_pub=$1 AND master_pub=$2;",
- 2),
- /* Used in #postgres_del_reserve_info() */
- GNUNET_PQ_make_prepare ("auditor_reserves_delete",
- "DELETE"
- " FROM auditor_reserves"
- " WHERE reserve_pub=$1 AND master_pub=$2;",
- 2),
- /* Used in #postgres_insert_reserve_summary() */
- GNUNET_PQ_make_prepare ("auditor_reserve_balance_insert",
- "INSERT INTO auditor_reserve_balance"
- "(master_pub"
- ",reserve_balance_val"
- ",reserve_balance_frac"
- ",withdraw_fee_balance_val"
- ",withdraw_fee_balance_frac"
- ") VALUES ($1,$2,$3,$4,$5)",
- 5),
- /* Used in #postgres_update_reserve_summary() */
- GNUNET_PQ_make_prepare ("auditor_reserve_balance_update",
- "UPDATE auditor_reserve_balance SET"
- " reserve_balance_val=$1"
- ",reserve_balance_frac=$2"
- ",withdraw_fee_balance_val=$3"
- ",withdraw_fee_balance_frac=$4"
- " WHERE master_pub=$5;",
- 5),
- /* Used in #postgres_get_reserve_summary() */
- GNUNET_PQ_make_prepare ("auditor_reserve_balance_select",
- "SELECT"
- " reserve_balance_val"
- ",reserve_balance_frac"
- ",withdraw_fee_balance_val"
- ",withdraw_fee_balance_frac"
- " FROM auditor_reserve_balance"
- " WHERE master_pub=$1;",
- 1),
- /* Used in #postgres_insert_wire_fee_summary() */
- GNUNET_PQ_make_prepare ("auditor_wire_fee_balance_insert",
- "INSERT INTO auditor_wire_fee_balance"
- "(master_pub"
- ",wire_fee_balance_val"
- ",wire_fee_balance_frac"
- ") VALUES ($1,$2,$3)",
- 3),
- /* Used in #postgres_update_wire_fee_summary() */
- GNUNET_PQ_make_prepare ("auditor_wire_fee_balance_update",
- "UPDATE auditor_wire_fee_balance SET"
- " wire_fee_balance_val=$1"
- ",wire_fee_balance_frac=$2"
- " WHERE master_pub=$3;",
- 3),
- /* Used in #postgres_get_wire_fee_summary() */
- GNUNET_PQ_make_prepare ("auditor_wire_fee_balance_select",
- "SELECT"
- " wire_fee_balance_val"
- ",wire_fee_balance_frac"
- " FROM auditor_wire_fee_balance"
- " WHERE master_pub=$1;",
- 1),
- /* Used in #postgres_insert_denomination_balance() */
- GNUNET_PQ_make_prepare ("auditor_denomination_pending_insert",
- "INSERT INTO auditor_denomination_pending "
- "(denom_pub_hash"
- ",denom_balance_val"
- ",denom_balance_frac"
- ",denom_loss_val"
- ",denom_loss_frac"
- ",num_issued"
- ",denom_risk_val"
- ",denom_risk_frac"
- ",recoup_loss_val"
- ",recoup_loss_frac"
- ") VALUES ("
- "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10"
- ");",
- 10),
- /* Used in #postgres_update_denomination_balance() */
- GNUNET_PQ_make_prepare ("auditor_denomination_pending_update",
- "UPDATE auditor_denomination_pending SET"
- " denom_balance_val=$1"
- ",denom_balance_frac=$2"
- ",denom_loss_val=$3"
- ",denom_loss_frac=$4"
- ",num_issued=$5"
- ",denom_risk_val=$6"
- ",denom_risk_frac=$7"
- ",recoup_loss_val=$8"
- ",recoup_loss_frac=$9"
- " WHERE denom_pub_hash=$10",
- 10),
- /* Used in #postgres_get_denomination_balance() */
- GNUNET_PQ_make_prepare ("auditor_denomination_pending_select",
- "SELECT"
- " denom_balance_val"
- ",denom_balance_frac"
- ",denom_loss_val"
- ",denom_loss_frac"
- ",num_issued"
- ",denom_risk_val"
- ",denom_risk_frac"
- ",recoup_loss_val"
- ",recoup_loss_frac"
- " FROM auditor_denomination_pending"
- " WHERE denom_pub_hash=$1",
- 1),
- /* Used in #postgres_insert_balance_summary() */
- GNUNET_PQ_make_prepare ("auditor_balance_summary_insert",
- "INSERT INTO auditor_balance_summary "
- "(master_pub"
- ",denom_balance_val"
- ",denom_balance_frac"
- ",deposit_fee_balance_val"
- ",deposit_fee_balance_frac"
- ",melt_fee_balance_val"
- ",melt_fee_balance_frac"
- ",refund_fee_balance_val"
- ",refund_fee_balance_frac"
- ",risk_val"
- ",risk_frac"
- ",loss_val"
- ",loss_frac"
- ",irregular_recoup_val"
- ",irregular_recoup_frac"
- ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,"
- " $11,$12,$13,$14,$15);",
- 15),
- /* Used in #postgres_update_balance_summary() */
- GNUNET_PQ_make_prepare ("auditor_balance_summary_update",
- "UPDATE auditor_balance_summary SET"
- " denom_balance_val=$1"
- ",denom_balance_frac=$2"
- ",deposit_fee_balance_val=$3"
- ",deposit_fee_balance_frac=$4"
- ",melt_fee_balance_val=$5"
- ",melt_fee_balance_frac=$6"
- ",refund_fee_balance_val=$7"
- ",refund_fee_balance_frac=$8"
- ",risk_val=$9"
- ",risk_frac=$10"
- ",loss_val=$11"
- ",loss_frac=$12"
- ",irregular_recoup_val=$13"
- ",irregular_recoup_frac=$14"
- " WHERE master_pub=$15;",
- 15),
- /* Used in #postgres_get_balance_summary() */
- GNUNET_PQ_make_prepare ("auditor_balance_summary_select",
- "SELECT"
- " denom_balance_val"
- ",denom_balance_frac"
- ",deposit_fee_balance_val"
- ",deposit_fee_balance_frac"
- ",melt_fee_balance_val"
- ",melt_fee_balance_frac"
- ",refund_fee_balance_val"
- ",refund_fee_balance_frac"
- ",risk_val"
- ",risk_frac"
- ",loss_val"
- ",loss_frac"
- ",irregular_recoup_val"
- ",irregular_recoup_frac"
- " FROM auditor_balance_summary"
- " WHERE master_pub=$1;",
- 1),
- /* Used in #postgres_insert_historic_denom_revenue() */
- GNUNET_PQ_make_prepare ("auditor_historic_denomination_revenue_insert",
- "INSERT INTO auditor_historic_denomination_revenue"
- "(master_pub"
- ",denom_pub_hash"
- ",revenue_timestamp"
- ",revenue_balance_val"
- ",revenue_balance_frac"
- ",loss_balance_val"
- ",loss_balance_frac"
- ") VALUES ($1,$2,$3,$4,$5,$6,$7);",
- 7),
- /* Used in #postgres_select_historic_denom_revenue() */
- GNUNET_PQ_make_prepare ("auditor_historic_denomination_revenue_select",
- "SELECT"
- " denom_pub_hash"
- ",revenue_timestamp"
- ",revenue_balance_val"
- ",revenue_balance_frac"
- ",loss_balance_val"
- ",loss_balance_frac"
- " FROM auditor_historic_denomination_revenue"
- " WHERE master_pub=$1;",
- 1),
- /* Used in #postgres_insert_historic_reserve_revenue() */
- GNUNET_PQ_make_prepare ("auditor_historic_reserve_summary_insert",
- "INSERT INTO auditor_historic_reserve_summary"
- "(master_pub"
- ",start_date"
- ",end_date"
- ",reserve_profits_val"
- ",reserve_profits_frac"
- ") VALUES ($1,$2,$3,$4,$5);",
- 5),
- /* Used in #postgres_select_historic_reserve_revenue() */
- GNUNET_PQ_make_prepare ("auditor_historic_reserve_summary_select",
- "SELECT"
- " start_date"
- ",end_date"
- ",reserve_profits_val"
- ",reserve_profits_frac"
- " FROM auditor_historic_reserve_summary"
- " WHERE master_pub=$1;",
- 1),
- /* Used in #postgres_insert_predicted_result() */
- GNUNET_PQ_make_prepare ("auditor_predicted_result_insert",
- "INSERT INTO auditor_predicted_result"
- "(master_pub"
- ",balance_val"
- ",balance_frac"
- ") VALUES ($1,$2,$3);",
- 3),
- /* Used in #postgres_update_predicted_result() */
- GNUNET_PQ_make_prepare ("auditor_predicted_result_update",
- "UPDATE auditor_predicted_result SET"
- " balance_val=$1"
- ",balance_frac=$2"
- " WHERE master_pub=$3;",
- 3),
- /* Used in #postgres_get_predicted_balance() */
- GNUNET_PQ_make_prepare ("auditor_predicted_result_select",
- "SELECT"
- " balance_val"
- ",balance_frac"
- " FROM auditor_predicted_result"
- " WHERE master_pub=$1;",
- 1),
- GNUNET_PQ_PREPARED_STATEMENT_END
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_try_execute ("SET search_path TO auditor;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
};
+ struct GNUNET_PQ_Context *db_conn;
- if (NULL != (session = pthread_getspecific (pc->db_conn_threadlocal)))
+ if (NULL != pg->conn)
{
- GNUNET_PQ_reconnect_if_down (session->conn);
- return session;
+ GNUNET_PQ_reconnect_if_down (pg->conn);
+ return GNUNET_OK;
}
- db_conn = GNUNET_PQ_connect_with_cfg (pc->cfg,
+ db_conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
"auditordb-postgres",
NULL,
- NULL,
- ps);
+ es,
+ NULL);
if (NULL == db_conn)
- return NULL;
- session = GNUNET_new (struct TALER_AUDITORDB_Session);
- session->conn = db_conn;
- if (0 != pthread_setspecific (pc->db_conn_threadlocal,
- session))
- {
- GNUNET_break (0);
- GNUNET_PQ_disconnect (db_conn);
- GNUNET_free (session);
- return NULL;
- }
- return session;
+ return GNUNET_SYSERR;
+ pg->conn = db_conn;
+ pg->prep_gen++;
+ return GNUNET_OK;
}
/**
* Do a pre-flight check that we are not in an uncommitted transaction.
- * If we are, try to commit the previous transaction and output a warning.
- * Does not return anything, as we will continue regardless of the outcome.
+ * If we are, rollback the previous transaction and output a warning.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session the database connection
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if we rolled back an earlier transaction
+ * #GNUNET_SYSERR if we have no DB connection
*/
-static void
-postgres_preflight (void *cls,
- struct TALER_AUDITORDB_Session *session)
+static enum GNUNET_GenericReturnValue
+postgres_preflight (void *cls)
{
+ struct PostgresClosure *pg = cls;
struct GNUNET_PQ_ExecuteStatement es[] = {
GNUNET_PQ_make_execute ("ROLLBACK"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
- (void) cls;
- if (NULL == session->transaction_name)
- return; /* all good */
+ if (NULL == pg->conn)
+ {
+ if (GNUNET_OK !=
+ setup_connection (pg))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ if (NULL == pg->transaction_name)
+ return GNUNET_OK; /* all good */
if (GNUNET_OK ==
- GNUNET_PQ_exec_statements (session->conn,
+ GNUNET_PQ_exec_statements (pg->conn,
es))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"BUG: Preflight check rolled back transaction `%s'!\n",
- session->transaction_name);
+ pg->transaction_name);
}
else
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"BUG: Preflight check failed to rollback transaction `%s'!\n",
- session->transaction_name);
+ pg->transaction_name);
}
- session->transaction_name = NULL;
+ pg->transaction_name = NULL;
+ return GNUNET_NO;
}
@@ -794,23 +310,20 @@ postgres_preflight (void *cls,
* Start a transaction.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session the database connection
* @return #GNUNET_OK on success
*/
-static int
-postgres_start (void *cls,
- struct TALER_AUDITORDB_Session *session)
+static enum GNUNET_GenericReturnValue
+postgres_start (void *cls)
{
+ struct PostgresClosure *pg = cls;
struct GNUNET_PQ_ExecuteStatement es[] = {
GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
- postgres_preflight (cls,
- session);
- (void) cls;
+ postgres_preflight (cls);
if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (session->conn,
+ GNUNET_PQ_exec_statements (pg->conn,
es))
{
TALER_LOG_ERROR ("Failed to start transaction\n");
@@ -825,21 +338,18 @@ postgres_start (void *cls,
* Roll back the current transaction of a database connection.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session the database connection
- * @return #GNUNET_OK on success
*/
static void
-postgres_rollback (void *cls,
- struct TALER_AUDITORDB_Session *session)
+postgres_rollback (void *cls)
{
+ struct PostgresClosure *pg = cls;
struct GNUNET_PQ_ExecuteStatement es[] = {
GNUNET_PQ_make_execute ("ROLLBACK"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
- (void) cls;
GNUNET_break (GNUNET_OK ==
- GNUNET_PQ_exec_statements (session->conn,
+ GNUNET_PQ_exec_statements (pg->conn,
es));
}
@@ -848,19 +358,20 @@ postgres_rollback (void *cls,
* Commit the current transaction of a database connection.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session the database connection
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
-postgres_commit (void *cls,
- struct TALER_AUDITORDB_Session *session)
+postgres_commit (void *cls)
{
+ struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_end
};
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
+ PREPARE (pg,
+ "do_commit",
+ "COMMIT");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"do_commit",
params);
}
@@ -874,31 +385,30 @@ postgres_commit (void *cls,
* @return #GNUNET_OK on success,
* #GNUNET_SYSERR on DB errors
*/
-static int
+static enum GNUNET_GenericReturnValue
postgres_gc (void *cls)
{
- struct PostgresClosure *pc = cls;
- struct GNUNET_TIME_Absolute now;
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = {0};
struct GNUNET_PQ_QueryParam params_time[] = {
- TALER_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_Context *conn;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_PreparedStatement ps[] = {
-#if 0
- GNUNET_PQ_make_prepare ("gc_auditor",
- "TODO: #4960",
- 0),
-#endif
GNUNET_PQ_PREPARED_STATEMENT_END
};
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_try_execute ("SET search_path TO auditor;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
now = GNUNET_TIME_absolute_get ();
- conn = GNUNET_PQ_connect_with_cfg (pc->cfg,
+ conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
"auditordb-postgres",
NULL,
- NULL,
+ es,
ps);
if (NULL == conn)
return GNUNET_SYSERR;
@@ -918,2263 +428,6 @@ postgres_gc (void *cls)
/**
- * Insert information about an exchange this auditor will be auditing.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param master_pub master public key of the exchange
- * @param exchange_url public (base) URL of the API of the exchange
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_exchange (void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *exchange_url)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_string (exchange_url),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_insert_exchange",
- params);
-}
-
-
-/**
- * Delete an exchange from the list of exchanges this auditor is auditing.
- * Warning: this will cascade and delete all knowledge of this auditor related
- * to this exchange!
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param master_pub master public key of the exchange
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_delete_exchange (void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_delete_exchange",
- params);
-}
-
-
-/**
- * Closure for #exchange_info_cb().
- */
-struct ExchangeInfoContext
-{
-
- /**
- * Function to call for each exchange.
- */
- TALER_AUDITORDB_ExchangeCallback cb;
-
- /**
- * Closure for @e cb
- */
- void *cb_cls;
-
- /**
- * Query status to return.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Helper function for #postgres_list_exchanges().
- * To be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct ExchangeInfoContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-exchange_info_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ExchangeInfoContext *eic = cls;
-
- (void) cls;
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_MasterPublicKeyP master_pub;
- char *exchange_url;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("master_pub", &master_pub),
- GNUNET_PQ_result_spec_string ("exchange_url", &exchange_url),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- eic->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- eic->qs = i + 1;
- eic->cb (eic->cb_cls,
- &master_pub,
- exchange_url);
- GNUNET_free (exchange_url);
- }
-}
-
-
-/**
- * Obtain information about exchanges this auditor is auditing.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param cb function to call with the results
- * @param cb_cls closure for @a cb
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_list_exchanges (void *cls,
- struct TALER_AUDITORDB_Session *session,
- TALER_AUDITORDB_ExchangeCallback cb,
- void *cb_cls)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- struct ExchangeInfoContext eic = {
- .cb = cb,
- .cb_cls = cb_cls
- };
- enum GNUNET_DB_QueryStatus qs;
-
- (void) cls;
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "auditor_list_exchanges",
- params,
- &exchange_info_cb,
- &eic);
- if (qs > 0)
- return eic.qs;
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
- return qs;
-}
-
-
-/**
- * Insert information about a signing key of the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param sk signing key information to store
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_exchange_signkey (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_AUDITORDB_ExchangeSigningKey *sk)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&sk->master_public_key),
- TALER_PQ_query_param_absolute_time (&sk->ep_start),
- TALER_PQ_query_param_absolute_time (&sk->ep_expire),
- TALER_PQ_query_param_absolute_time (&sk->ep_end),
- GNUNET_PQ_query_param_auto_from_type (&sk->exchange_pub),
- GNUNET_PQ_query_param_auto_from_type (&sk->master_sig),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_insert_exchange_signkey",
- params);
-}
-
-
-/**
- * Insert information about a deposit confirmation into the database.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param dc deposit confirmation information to store
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_deposit_confirmation (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_AUDITORDB_DepositConfirmation *dc)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&dc->master_public_key),
- GNUNET_PQ_query_param_auto_from_type (&dc->h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (&dc->h_wire),
- TALER_PQ_query_param_absolute_time (&dc->timestamp),
- TALER_PQ_query_param_absolute_time (&dc->refund_deadline),
- TALER_PQ_query_param_amount (&dc->amount_without_fee),
- GNUNET_PQ_query_param_auto_from_type (&dc->coin_pub),
- GNUNET_PQ_query_param_auto_from_type (&dc->merchant),
- GNUNET_PQ_query_param_auto_from_type (&dc->exchange_sig),
- GNUNET_PQ_query_param_auto_from_type (&dc->exchange_pub),
- GNUNET_PQ_query_param_auto_from_type (&dc->master_sig),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_deposit_confirmation_insert",
- params);
-}
-
-
-/**
- * Closure for #deposit_confirmation_cb().
- */
-struct DepositConfirmationContext
-{
-
- /**
- * Master public key that is being used.
- */
- const struct TALER_MasterPublicKeyP *master_pub;
-
- /**
- * Function to call for each deposit confirmation.
- */
- TALER_AUDITORDB_DepositConfirmationCallback cb;
-
- /**
- * Closure for @e cb
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Query status to return.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Helper function for #postgres_get_deposit_confirmations().
- * To be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct DepositConfirmationContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-deposit_confirmation_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct DepositConfirmationContext *dcc = cls;
- struct PostgresClosure *pg = dcc->pg;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- uint64_t serial_id;
- struct TALER_AUDITORDB_DepositConfirmation dc = {
- .master_public_key = *dcc->master_pub
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial_id",
- &serial_id),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &dc.h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type ("h_wire",
- &dc.h_wire),
- GNUNET_PQ_result_spec_absolute_time ("timestamp",
- &dc.timestamp),
- GNUNET_PQ_result_spec_absolute_time ("refund_deadline",
- &dc.refund_deadline),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_without_fee",
- &dc.amount_without_fee),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &dc.coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
- &dc.merchant),
- GNUNET_PQ_result_spec_auto_from_type ("exchange_sig",
- &dc.exchange_sig),
- GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
- &dc.exchange_pub),
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- &dc.master_sig),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- dcc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- dcc->qs = i + 1;
- dcc->cb (dcc->cb_cls,
- serial_id,
- &dc);
- }
-}
-
-
-/**
- * Get information about deposit confirmations from the database.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param master_public_key for which exchange do we want to get deposit confirmations
- * @param start_id row/serial ID where to start the iteration (0 from
- * the start, exclusive, i.e. serial_ids must start from 1)
- * @param cb function to call with results
- * @param cb_cls closure for @a cb
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_deposit_confirmations (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_public_key,
- uint64_t start_id,
- TALER_AUDITORDB_DepositConfirmationCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_public_key),
- GNUNET_PQ_query_param_uint64 (&start_id),
- GNUNET_PQ_query_param_end
- };
- struct DepositConfirmationContext dcc = {
- .master_pub = master_public_key,
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "auditor_deposit_confirmation_select",
- params,
- &deposit_confirmation_cb,
- &dcc);
- if (qs > 0)
- return dcc.qs;
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
- return qs;
-}
-
-
-/**
- * Insert information about a denomination key and in particular
- * the properties (value, fees, expiration times) the coins signed
- * with this key have.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param issue issuing information with value, fees and other info about the denomination
- * @return operation status result
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_denomination_info (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_DenominationKeyValidityPS *issue)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&issue->denom_hash),
- GNUNET_PQ_query_param_auto_from_type (&issue->master),
- TALER_PQ_query_param_absolute_time_nbo (&issue->start),
- TALER_PQ_query_param_absolute_time_nbo (&issue->expire_withdraw),
- TALER_PQ_query_param_absolute_time_nbo (&issue->expire_deposit),
- TALER_PQ_query_param_absolute_time_nbo (&issue->expire_legal),
- TALER_PQ_query_param_amount_nbo (&issue->value),
- TALER_PQ_query_param_amount_nbo (&issue->fee_withdraw),
- TALER_PQ_query_param_amount_nbo (&issue->fee_deposit),
- TALER_PQ_query_param_amount_nbo (&issue->fee_refresh),
- TALER_PQ_query_param_amount_nbo (&issue->fee_refund),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- /* check fees match coin currency */
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency_nbo (&issue->value,
- &issue->fee_withdraw));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency_nbo (&issue->value,
- &issue->fee_deposit));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency_nbo (&issue->value,
- &issue->fee_refresh));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency_nbo (&issue->value,
- &issue->fee_refund));
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_denominations_insert",
- params);
-}
-
-
-/**
- * Closure for #denomination_info_cb().
- */
-struct DenominationInfoContext
-{
-
- /**
- * Master public key that is being used.
- */
- const struct TALER_MasterPublicKeyP *master_pub;
-
- /**
- * Function to call for each denomination.
- */
- TALER_AUDITORDB_DenominationInfoDataCallback cb;
-
- /**
- * Closure for @e cb
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Query status to return.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Helper function for #postgres_select_denomination_info().
- * To be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct DenominationInfoContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-denomination_info_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct DenominationInfoContext *dic = cls;
- struct PostgresClosure *pg = dic->pg;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_DenominationKeyValidityPS issue = {
- .master = *dic->master_pub
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &issue.denom_hash),
- TALER_PQ_result_spec_absolute_time_nbo ("valid_from", &issue.start),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_withdraw",
- &issue.expire_withdraw),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_deposit",
- &issue.expire_deposit),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_legal",
- &issue.expire_legal),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("coin", &issue.value),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_withdraw", &issue.fee_withdraw),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_deposit", &issue.fee_deposit),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refresh", &issue.fee_refresh),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refund", &issue.fee_refund),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- dic->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- dic->qs = i + 1;
- if (GNUNET_OK !=
- dic->cb (dic->cb_cls,
- &issue))
- return;
- }
-}
-
-
-/**
- * Get information about denomination keys of a particular exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param cb function to call with the results
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_denomination_info (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- TALER_AUDITORDB_DenominationInfoDataCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
- struct DenominationInfoContext dic = {
- .master_pub = master_pub,
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg
- };
- enum GNUNET_DB_QueryStatus qs;
-
- (void) cls;
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "auditor_denominations_select",
- params,
- &denomination_info_cb,
- &dic);
- if (qs > 0)
- return dic.qs;
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
- return qs;
-}
-
-
-/**
- * Insert information about the auditor's progress with an exchange's
- * data.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppr where is the auditor in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_auditor_progress_reserve (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointReserve *ppr)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_uint64 (&ppr->last_reserve_in_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppr->last_reserve_out_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppr->last_reserve_recoup_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppr->last_reserve_close_serial_id),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_progress_insert_reserve",
- params);
-}
-
-
-/**
- * Update information about the progress of the auditor. There
- * must be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppr where is the auditor in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_auditor_progress_reserve (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointReserve *ppr)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&ppr->last_reserve_in_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppr->last_reserve_out_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppr->last_reserve_recoup_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppr->last_reserve_close_serial_id),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_progress_update_reserve",
- params);
-}
-
-
-/**
- * Get information about the progress of the auditor.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param[out] ppr set to where the auditor is in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_auditor_progress_reserve (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_ProgressPointReserve *ppr)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("last_reserve_in_serial_id",
- &ppr->last_reserve_in_serial_id),
- GNUNET_PQ_result_spec_uint64 ("last_reserve_out_serial_id",
- &ppr->last_reserve_out_serial_id),
- GNUNET_PQ_result_spec_uint64 ("last_reserve_recoup_serial_id",
- &ppr->last_reserve_recoup_serial_id),
- GNUNET_PQ_result_spec_uint64 ("last_reserve_close_serial_id",
- &ppr->last_reserve_close_serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "auditor_progress_select_reserve",
- params,
- rs);
-}
-
-
-/**
- * Insert information about the auditor's progress with an exchange's
- * data.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppa where is the auditor in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_auditor_progress_aggregation (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointAggregation *ppa)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_uint64 (&ppa->last_wire_out_serial_id),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_progress_insert_aggregation",
- params);
-}
-
-
-/**
- * Update information about the progress of the auditor. There
- * must be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppa where is the auditor in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_auditor_progress_aggregation (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointAggregation *ppa)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&ppa->last_wire_out_serial_id),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_progress_update_aggregation",
- params);
-}
-
-
-/**
- * Get information about the progress of the auditor.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param[out] ppa set to where the auditor is in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_auditor_progress_aggregation (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_ProgressPointAggregation *ppa)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("last_wire_out_serial_id",
- &ppa->last_wire_out_serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "auditor_progress_select_aggregation",
- params,
- rs);
-}
-
-
-/**
- * Insert information about the auditor's progress with an exchange's
- * data.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppdc where is the auditor in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_auditor_progress_deposit_confirmation (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointDepositConfirmation *ppdc)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_uint64 (&ppdc->last_deposit_confirmation_serial_id),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_progress_insert_deposit_confirmation",
- params);
-}
-
-
-/**
- * Update information about the progress of the auditor. There
- * must be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppdc where is the auditor in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_auditor_progress_deposit_confirmation (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointDepositConfirmation *ppdc)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&ppdc->last_deposit_confirmation_serial_id),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_progress_update_deposit_confirmation",
- params);
-}
-
-
-/**
- * Get information about the progress of the auditor.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param[out] ppdc set to where the auditor is in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_auditor_progress_deposit_confirmation (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_ProgressPointDepositConfirmation *ppdc)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("last_deposit_confirmation_serial_id",
- &ppdc->last_deposit_confirmation_serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "auditor_progress_select_deposit_confirmation",
- params,
- rs);
-}
-
-
-/**
- * Insert information about the auditor's progress with an exchange's
- * data.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppc where is the auditor in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_auditor_progress_coin (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointCoin *ppc)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_uint64 (&ppc->last_withdraw_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppc->last_deposit_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppc->last_melt_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppc->last_refund_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppc->last_recoup_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppc->last_recoup_refresh_serial_id),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_progress_insert_coin",
- params);
-}
-
-
-/**
- * Update information about the progress of the auditor. There
- * must be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppc where is the auditor in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_auditor_progress_coin (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointCoin *ppc)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&ppc->last_withdraw_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppc->last_deposit_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppc->last_melt_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppc->last_refund_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppc->last_recoup_serial_id),
- GNUNET_PQ_query_param_uint64 (&ppc->last_recoup_refresh_serial_id),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_progress_update_coin",
- params);
-}
-
-
-/**
- * Get information about the progress of the auditor.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param[out] ppc set to where the auditor is in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_auditor_progress_coin (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_ProgressPointCoin *ppc)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("last_withdraw_serial_id",
- &ppc->last_withdraw_serial_id),
- GNUNET_PQ_result_spec_uint64 ("last_deposit_serial_id",
- &ppc->last_deposit_serial_id),
- GNUNET_PQ_result_spec_uint64 ("last_melt_serial_id",
- &ppc->last_melt_serial_id),
- GNUNET_PQ_result_spec_uint64 ("last_refund_serial_id",
- &ppc->last_refund_serial_id),
- GNUNET_PQ_result_spec_uint64 ("last_recoup_serial_id",
- &ppc->last_recoup_serial_id),
- GNUNET_PQ_result_spec_uint64 ("last_recoup_refresh_serial_id",
- &ppc->last_recoup_refresh_serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "auditor_progress_select_coin",
- params,
- rs);
-}
-
-
-/**
- * Insert information about the auditor's progress with an exchange's
- * data.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param account_name name of the wire account we are auditing
- * @param pp how far are we in the auditor's tables
- * @param in_wire_off how far are we in the incoming wire transfers
- * @param out_wire_off how far are we in the outgoing wire transfers
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_wire_auditor_account_progress (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *account_name,
- const struct TALER_AUDITORDB_WireAccountProgressPoint *pp,
- uint64_t in_wire_off,
- uint64_t out_wire_off)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_string (account_name),
- GNUNET_PQ_query_param_uint64 (&pp->last_reserve_in_serial_id),
- GNUNET_PQ_query_param_uint64 (&pp->last_wire_out_serial_id),
- GNUNET_PQ_query_param_uint64 (&in_wire_off),
- GNUNET_PQ_query_param_uint64 (&out_wire_off),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "wire_auditor_account_progress_insert",
- params);
-}
-
-
-/**
- * Update information about the progress of the auditor. There
- * must be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param account_name name of the wire account we are auditing
- * @param pp where is the auditor in processing
- * @param in_wire_off how far are we in the incoming wire transaction history
- * @param out_wire_off how far are we in the outgoing wire transaction history
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_wire_auditor_account_progress (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *account_name,
- const struct TALER_AUDITORDB_WireAccountProgressPoint *pp,
- uint64_t in_wire_off,
- uint64_t out_wire_off)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&pp->last_reserve_in_serial_id),
- GNUNET_PQ_query_param_uint64 (&pp->last_wire_out_serial_id),
- GNUNET_PQ_query_param_uint64 (&in_wire_off),
- GNUNET_PQ_query_param_uint64 (&out_wire_off),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_string (account_name),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "wire_auditor_account_progress_update",
- params);
-}
-
-
-/**
- * Get information about the progress of the auditor.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param account_name name of the wire account we are auditing
- * @param[out] pp where is the auditor in processing
- * @param[out] in_wire_off how far are we in the incoming wire transaction history
- * @param[out] out_wire_off how far are we in the outgoing wire transaction history
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_wire_auditor_account_progress (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *account_name,
- struct TALER_AUDITORDB_WireAccountProgressPoint *pp,
- uint64_t *in_wire_off,
- uint64_t *out_wire_off)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_string (account_name),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("last_wire_reserve_in_serial_id",
- &pp->last_reserve_in_serial_id),
- GNUNET_PQ_result_spec_uint64 ("last_wire_wire_out_serial_id",
- &pp->last_wire_out_serial_id),
- GNUNET_PQ_result_spec_uint64 ("wire_in_off",
- in_wire_off),
- GNUNET_PQ_result_spec_uint64 ("wire_out_off",
- out_wire_off),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "wire_auditor_account_progress_select",
- params,
- rs);
-}
-
-
-/**
- * Insert information about the auditor's progress with an exchange's
- * data.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param pp where is the auditor in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_wire_auditor_progress (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_WireProgressPoint *pp)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- TALER_PQ_query_param_absolute_time (&pp->last_timestamp),
- GNUNET_PQ_query_param_uint64 (&pp->last_reserve_close_uuid),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "wire_auditor_progress_insert",
- params);
-}
-
-
-/**
- * Update information about the progress of the auditor. There
- * must be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param pp where is the auditor in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_wire_auditor_progress (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_WireProgressPoint *pp)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_absolute_time (&pp->last_timestamp),
- GNUNET_PQ_query_param_uint64 (&pp->last_reserve_close_uuid),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "wire_auditor_progress_update",
- params);
-}
-
-
-/**
- * Get information about the progress of the auditor.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param[out] pp set to where the auditor is in processing
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_wire_auditor_progress (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_WireProgressPoint *pp)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_result_spec_absolute_time ("last_timestamp",
- &pp->last_timestamp),
- GNUNET_PQ_result_spec_uint64 ("last_reserve_close_uuid",
- &pp->last_reserve_close_uuid),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "wire_auditor_progress_select",
- params,
- rs);
-}
-
-
-/**
- * Insert information about a reserve. There must not be an
- * existing record for the reserve.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param reserve_pub public key of the reserve
- * @param master_pub master public key of the exchange
- * @param reserve_balance amount stored in the reserve
- * @param withdraw_fee_balance amount the exchange gained in withdraw fees
- * due to withdrawals from this reserve
- * @param expiration_date expiration date of the reserve
- * @param origin_account where did the money in the reserve originally come from
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_reserve_info (void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *reserve_balance,
- const struct TALER_Amount *withdraw_fee_balance,
- struct GNUNET_TIME_Absolute expiration_date,
- const char *origin_account)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- TALER_PQ_query_param_amount (reserve_balance),
- TALER_PQ_query_param_amount (withdraw_fee_balance),
- TALER_PQ_query_param_absolute_time (&expiration_date),
- GNUNET_PQ_query_param_string (origin_account),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (reserve_balance,
- withdraw_fee_balance));
-
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_reserves_insert",
- params);
-}
-
-
-/**
- * Update information about a reserve. Destructively updates an
- * existing record, which must already exist.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param reserve_pub public key of the reserve
- * @param master_pub master public key of the exchange
- * @param reserve_balance amount stored in the reserve
- * @param withdraw_fee_balance amount the exchange gained in withdraw fees
- * due to withdrawals from this reserve
- * @param expiration_date expiration date of the reserve
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_reserve_info (void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *reserve_balance,
- const struct TALER_Amount *withdraw_fee_balance,
- struct GNUNET_TIME_Absolute expiration_date)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_amount (reserve_balance),
- TALER_PQ_query_param_amount (withdraw_fee_balance),
- TALER_PQ_query_param_absolute_time (&expiration_date),
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (reserve_balance,
- withdraw_fee_balance));
-
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_reserves_update",
- params);
-}
-
-
-/**
- * Delete information about a reserve.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param reserve_pub public key of the reserve
- * @param master_pub master public key of the exchange
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_del_reserve_info (void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_MasterPublicKeyP *master_pub)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_reserves_delete",
- params);
-}
-
-
-/**
- * Get information about a reserve.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param reserve_pub public key of the reserve
- * @param master_pub master public key of the exchange
- * @param[out] rowid which row did we get the information from
- * @param[out] reserve_balance amount stored in the reserve
- * @param[out] withdraw_fee_balance amount the exchange gained in withdraw fees
- * due to withdrawals from this reserve
- * @param[out] expiration_date expiration date of the reserve
- * @param[out] sender_account from where did the money in the reserve originally come from
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_reserve_info (void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_MasterPublicKeyP *master_pub,
- uint64_t *rowid,
- struct TALER_Amount *reserve_balance,
- struct TALER_Amount *withdraw_fee_balance,
- struct GNUNET_TIME_Absolute *expiration_date,
- char **sender_account)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_balance", reserve_balance),
- TALER_PQ_RESULT_SPEC_AMOUNT ("withdraw_fee_balance", withdraw_fee_balance),
- TALER_PQ_result_spec_absolute_time ("expiration_date", expiration_date),
- GNUNET_PQ_result_spec_uint64 ("auditor_reserves_rowid", rowid),
- GNUNET_PQ_result_spec_string ("origin_account", sender_account),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "auditor_reserves_select",
- params,
- rs);
-}
-
-
-/**
- * Insert information about all reserves. There must not be an
- * existing record for the @a master_pub.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param reserve_balance amount stored in the reserve
- * @param withdraw_fee_balance amount the exchange gained in withdraw fees
- * due to withdrawals from this reserve
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_reserve_summary (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *reserve_balance,
- const struct TALER_Amount *withdraw_fee_balance)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- TALER_PQ_query_param_amount (reserve_balance),
- TALER_PQ_query_param_amount (withdraw_fee_balance),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (reserve_balance,
- withdraw_fee_balance));
-
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_reserve_balance_insert",
- params);
-}
-
-
-/**
- * Update information about all reserves. Destructively updates an
- * existing record, which must already exist.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param reserve_balance amount stored in the reserve
- * @param withdraw_fee_balance amount the exchange gained in withdraw fees
- * due to withdrawals from this reserve
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_reserve_summary (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *reserve_balance,
- const struct TALER_Amount *withdraw_fee_balance)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_amount (reserve_balance),
- TALER_PQ_query_param_amount (withdraw_fee_balance),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_reserve_balance_update",
- params);
-}
-
-
-/**
- * Get summary information about all reserves.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param[out] reserve_balance amount stored in the reserve
- * @param[out] withdraw_fee_balance amount the exchange gained in withdraw fees
- * due to withdrawals from this reserve
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_reserve_summary (void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_Amount *reserve_balance,
- struct TALER_Amount *withdraw_fee_balance)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_balance", reserve_balance),
- TALER_PQ_RESULT_SPEC_AMOUNT ("withdraw_fee_balance", withdraw_fee_balance),
-
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "auditor_reserve_balance_select",
- params,
- rs);
-}
-
-
-/**
- * Insert information about exchange's wire fee balance. There must not be an
- * existing record for the same @a master_pub.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param wire_fee_balance amount the exchange gained in wire fees
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_wire_fee_summary (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *wire_fee_balance)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- TALER_PQ_query_param_amount (wire_fee_balance),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_wire_fee_balance_insert",
- params);
-}
-
-
-/**
- * Insert information about exchange's wire fee balance. Destructively updates an
- * existing record, which must already exist.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param wire_fee_balance amount the exchange gained in wire fees
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_wire_fee_summary (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *wire_fee_balance)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_amount (wire_fee_balance),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_wire_fee_balance_update",
- params);
-}
-
-
-/**
- * Get summary information about an exchanges wire fee balance.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param[out] wire_fee_balance set amount the exchange gained in wire fees
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_wire_fee_summary (void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_Amount *wire_fee_balance)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee_balance",
- wire_fee_balance),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "auditor_wire_fee_balance_select",
- params,
- rs);
-}
-
-
-/**
- * Insert information about a denomination key's balances. There
- * must not be an existing record for the denomination key.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param denom_pub_hash hash of the denomination public key
- * @param denom_balance value of coins outstanding with this denomination key
- * @param denom_loss value of coins redeemed that were not outstanding (effectively, negative @a denom_balance)
- * @param denom_risk value of coins issued with this denomination key
- * @param recoup_loss losses from recoup (if this denomination was revoked)
- * @param num_issued how many coins of this denomination did the exchange blind-sign
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_denomination_balance (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash,
- const struct TALER_Amount *denom_balance,
- const struct TALER_Amount *denom_loss,
- const struct TALER_Amount *denom_risk,
- const struct TALER_Amount *recoup_loss,
- uint64_t num_issued)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
- TALER_PQ_query_param_amount (denom_balance),
- TALER_PQ_query_param_amount (denom_loss),
- GNUNET_PQ_query_param_uint64 (&num_issued),
- TALER_PQ_query_param_amount (denom_risk),
- TALER_PQ_query_param_amount (recoup_loss),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_denomination_pending_insert",
- params);
-}
-
-
-/**
- * Update information about a denomination key's balances. There
- * must be an existing record for the denomination key.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param denom_pub_hash hash of the denomination public key
- * @param denom_balance value of coins outstanding with this denomination key
- * @param denom_loss value of coins redeemed that were not outstanding (effectively, negative @a denom_balance)
-* @param denom_risk value of coins issued with this denomination key
- * @param recoup_loss losses from recoup (if this denomination was revoked)
- * @param num_issued how many coins of this denomination did the exchange blind-sign
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_denomination_balance (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash,
- const struct TALER_Amount *denom_balance,
- const struct TALER_Amount *denom_loss,
- const struct TALER_Amount *denom_risk,
- const struct TALER_Amount *recoup_loss,
- uint64_t num_issued)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_amount (denom_balance),
- TALER_PQ_query_param_amount (denom_loss),
- GNUNET_PQ_query_param_uint64 (&num_issued),
- TALER_PQ_query_param_amount (denom_risk),
- TALER_PQ_query_param_amount (recoup_loss),
- GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_denomination_pending_update",
- params);
-}
-
-
-/**
- * Get information about a denomination key's balances.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param denom_pub_hash hash of the denomination public key
- * @param[out] denom_balance value of coins outstanding with this denomination key
- * @param[out] denom_risk value of coins issued with this denomination key
- * @param[out] denom_loss value of coins redeemed that were not outstanding (effectively, negative @a denom_balance)
- * @param[out] recoup_loss losses from recoup (if this denomination was revoked)
- * @param[out] num_issued how many coins of this denomination did the exchange blind-sign
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_denomination_balance (void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash,
- struct TALER_Amount *denom_balance,
- struct TALER_Amount *denom_loss,
- struct TALER_Amount *denom_risk,
- struct TALER_Amount *recoup_loss,
- uint64_t *num_issued)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("denom_balance", denom_balance),
- TALER_PQ_RESULT_SPEC_AMOUNT ("denom_loss", denom_loss),
- TALER_PQ_RESULT_SPEC_AMOUNT ("denom_risk", denom_risk),
- TALER_PQ_RESULT_SPEC_AMOUNT ("recoup_loss", recoup_loss),
- GNUNET_PQ_result_spec_uint64 ("num_issued", num_issued),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "auditor_denomination_pending_select",
- params,
- rs);
-}
-
-
-/**
- * Insert information about an exchange's denomination balances. There
- * must not be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param denom_balance value of coins outstanding with this denomination key
- * @param deposit_fee_balance total deposit fees collected for this DK
- * @param melt_fee_balance total melt fees collected for this DK
- * @param refund_fee_balance total refund fees collected for this DK
- * @param risk maximum risk exposure of the exchange
- * @param loss materialized @a risk from recoup
- * @param irregular_recoup recoups on non-revoked coins
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_balance_summary (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *denom_balance,
- const struct TALER_Amount *deposit_fee_balance,
- const struct TALER_Amount *melt_fee_balance,
- const struct TALER_Amount *refund_fee_balance,
- const struct TALER_Amount *risk,
- const struct TALER_Amount *loss,
- const struct TALER_Amount *irregular_recoup)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- TALER_PQ_query_param_amount (denom_balance),
- TALER_PQ_query_param_amount (deposit_fee_balance),
- TALER_PQ_query_param_amount (melt_fee_balance),
- TALER_PQ_query_param_amount (refund_fee_balance),
- TALER_PQ_query_param_amount (risk),
- TALER_PQ_query_param_amount (loss),
- TALER_PQ_query_param_amount (irregular_recoup),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (denom_balance,
- deposit_fee_balance));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (denom_balance,
- melt_fee_balance));
-
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (denom_balance,
- refund_fee_balance));
-
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_balance_summary_insert",
- params);
-}
-
-
-/**
- * Update information about an exchange's denomination balances. There
- * must be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param denom_balance value of coins outstanding with this denomination key
- * @param deposit_fee_balance total deposit fees collected for this DK
- * @param melt_fee_balance total melt fees collected for this DK
- * @param refund_fee_balance total refund fees collected for this DK
- * @param risk maximum risk exposure of the exchange
- * @param loss materialized @a risk from recoup
- * @param irregular_recoup recoups made on non-revoked coins
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_balance_summary (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *denom_balance,
- const struct TALER_Amount *deposit_fee_balance,
- const struct TALER_Amount *melt_fee_balance,
- const struct TALER_Amount *refund_fee_balance,
- const struct TALER_Amount *risk,
- const struct TALER_Amount *loss,
- const struct TALER_Amount *irregular_recoup)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_amount (denom_balance),
- TALER_PQ_query_param_amount (deposit_fee_balance),
- TALER_PQ_query_param_amount (melt_fee_balance),
- TALER_PQ_query_param_amount (refund_fee_balance),
- TALER_PQ_query_param_amount (risk),
- TALER_PQ_query_param_amount (loss),
- TALER_PQ_query_param_amount (irregular_recoup),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_balance_summary_update",
- params);
-}
-
-
-/**
- * Get information about an exchange's denomination balances.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param[out] denom_balance value of coins outstanding with this denomination key
- * @param[out] deposit_fee_balance total deposit fees collected for this DK
- * @param[out] melt_fee_balance total melt fees collected for this DK
- * @param[out] refund_fee_balance total refund fees collected for this DK
- * @param[out] risk maximum risk exposure of the exchange
- * @param[out] loss losses from recoup (on revoked denominations)
- * @param[out] irregular_recoup recoups on NOT revoked denominations
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_balance_summary (void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_Amount *denom_balance,
- struct TALER_Amount *deposit_fee_balance,
- struct TALER_Amount *melt_fee_balance,
- struct TALER_Amount *refund_fee_balance,
- struct TALER_Amount *risk,
- struct TALER_Amount *loss,
- struct TALER_Amount *irregular_recoup)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("denom_balance", denom_balance),
- TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee_balance", deposit_fee_balance),
- TALER_PQ_RESULT_SPEC_AMOUNT ("melt_fee_balance", melt_fee_balance),
- TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee_balance", refund_fee_balance),
- TALER_PQ_RESULT_SPEC_AMOUNT ("risk", risk),
- TALER_PQ_RESULT_SPEC_AMOUNT ("loss", loss),
- TALER_PQ_RESULT_SPEC_AMOUNT ("irregular_recoup", irregular_recoup),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "auditor_balance_summary_select",
- params,
- rs);
-}
-
-
-/**
- * Insert information about an exchange's historic
- * revenue about a denomination key.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param denom_pub_hash hash of the denomination key
- * @param revenue_timestamp when did this profit get realized
- * @param revenue_balance what was the total profit made from
- * deposit fees, melting fees, refresh fees
- * and coins that were never returned?
- * @param loss_balance total losses suffered by the exchange at the time
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_historic_denom_revenue (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct GNUNET_HashCode *denom_pub_hash,
- struct GNUNET_TIME_Absolute revenue_timestamp,
- const struct TALER_Amount *revenue_balance,
- const struct TALER_Amount *loss_balance)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
- TALER_PQ_query_param_absolute_time (&revenue_timestamp),
- TALER_PQ_query_param_amount (revenue_balance),
- TALER_PQ_query_param_amount (loss_balance),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_historic_denomination_revenue_insert",
- params);
-}
-
-
-/**
- * Closure for #historic_denom_revenue_cb().
- */
-struct HistoricDenomRevenueContext
-{
- /**
- * Function to call for each result.
- */
- TALER_AUDITORDB_HistoricDenominationRevenueDataCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Number of results processed.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Helper function for #postgres_select_historic_denom_revenue().
- * To be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct HistoricRevenueContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-historic_denom_revenue_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct HistoricDenomRevenueContext *hrc = cls;
- struct PostgresClosure *pg = hrc->pg;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct GNUNET_HashCode denom_pub_hash;
- struct GNUNET_TIME_Absolute revenue_timestamp;
- struct TALER_Amount revenue_balance;
- struct TALER_Amount loss;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", &denom_pub_hash),
- TALER_PQ_result_spec_absolute_time ("revenue_timestamp",
- &revenue_timestamp),
- TALER_PQ_RESULT_SPEC_AMOUNT ("revenue_balance", &revenue_balance),
- TALER_PQ_RESULT_SPEC_AMOUNT ("loss_balance", &loss),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- hrc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
-
- hrc->qs = i + 1;
- if (GNUNET_OK !=
- hrc->cb (hrc->cb_cls,
- &denom_pub_hash,
- revenue_timestamp,
- &revenue_balance,
- &loss))
- break;
- }
-}
-
-
-/**
- * Obtain all of the historic denomination key revenue
- * of the given @a master_pub.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param cb function to call with the results
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_historic_denom_revenue (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- TALER_AUDITORDB_HistoricDenominationRevenueDataCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
- struct HistoricDenomRevenueContext hrc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "auditor_historic_denomination_revenue_select",
- params,
- &historic_denom_revenue_cb,
- &hrc);
- if (qs <= 0)
- return qs;
- return hrc.qs;
-}
-
-
-/**
- * Insert information about an exchange's historic revenue from reserves.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param start_time beginning of aggregated time interval
- * @param end_time end of aggregated time interval
- * @param reserve_profits total profits made
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_historic_reserve_revenue (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct GNUNET_TIME_Absolute start_time,
- struct GNUNET_TIME_Absolute end_time,
- const struct TALER_Amount *reserve_profits)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- TALER_PQ_query_param_absolute_time (&start_time),
- TALER_PQ_query_param_absolute_time (&end_time),
- TALER_PQ_query_param_amount (reserve_profits),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_historic_reserve_summary_insert",
- params);
-}
-
-
-/**
- * Closure for #historic_reserve_revenue_cb().
- */
-struct HistoricReserveRevenueContext
-{
- /**
- * Function to call for each result.
- */
- TALER_AUDITORDB_HistoricReserveRevenueDataCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Number of results processed.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Helper function for #postgres_select_historic_reserve_revenue().
- * To be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct HistoricRevenueContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-historic_reserve_revenue_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct HistoricReserveRevenueContext *hrc = cls;
- struct PostgresClosure *pg = hrc->pg;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct GNUNET_TIME_Absolute start_date;
- struct GNUNET_TIME_Absolute end_date;
- struct TALER_Amount reserve_profits;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_result_spec_absolute_time ("start_date", &start_date),
- TALER_PQ_result_spec_absolute_time ("end_date", &end_date),
- TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_profits", &reserve_profits),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- hrc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- hrc->qs = i + 1;
- if (GNUNET_OK !=
- hrc->cb (hrc->cb_cls,
- start_date,
- end_date,
- &reserve_profits))
- break;
- }
-}
-
-
-/**
- * Return information about an exchange's historic revenue from reserves.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param cb function to call with results
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_historic_reserve_revenue (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- TALER_AUDITORDB_HistoricReserveRevenueDataCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
- struct HistoricReserveRevenueContext hrc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg
- };
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "auditor_historic_reserve_summary_select",
- params,
- &historic_reserve_revenue_cb,
- &hrc);
- if (0 >= qs)
- return qs;
- return hrc.qs;
-}
-
-
-/**
- * Insert information about the predicted exchange's bank
- * account balance.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param balance what the bank account balance of the exchange should show
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_predicted_result (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *balance)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- TALER_PQ_query_param_amount (balance),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_predicted_result_insert",
- params);
-}
-
-
-/**
- * Update information about an exchange's predicted balance. There
- * must be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param balance what the bank account balance of the exchange should show
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_predicted_result (
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *balance)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_amount (balance),
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "auditor_predicted_result_update",
- params);
-}
-
-
-/**
- * Get an exchange's predicted balance.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param[out] balance expected bank account balance of the exchange
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_predicted_balance (void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_Amount *balance)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
- balance),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "auditor_predicted_result_select",
- params,
- rs);
-}
-
-
-/**
* Initialize Postgres database subsystem.
*
* @param cls a configuration instance
@@ -3189,13 +442,6 @@ libtaler_plugin_auditordb_postgres_init (void *cls)
pg = GNUNET_new (struct PostgresClosure);
pg->cfg = cfg;
- if (0 != pthread_key_create (&pg->db_conn_threadlocal,
- &db_conn_destroy))
- {
- TALER_LOG_ERROR ("Cannot create pthread key.\n");
- GNUNET_free (pg);
- return NULL;
- }
if (GNUNET_OK !=
TALER_config_get_currency (cfg,
&pg->currency))
@@ -3205,89 +451,84 @@ libtaler_plugin_auditordb_postgres_init (void *cls)
}
plugin = GNUNET_new (struct TALER_AUDITORDB_Plugin);
plugin->cls = pg;
- plugin->get_session = &postgres_get_session;
+ plugin->preflight = &postgres_preflight;
plugin->drop_tables = &postgres_drop_tables;
plugin->create_tables = &postgres_create_tables;
+ plugin->event_listen = &postgres_event_listen;
+ plugin->event_listen_cancel = &postgres_event_listen_cancel;
+ plugin->event_notify = &postgres_event_notify;
plugin->start = &postgres_start;
plugin->commit = &postgres_commit;
plugin->rollback = &postgres_rollback;
plugin->gc = &postgres_gc;
- plugin->insert_exchange = &postgres_insert_exchange;
- plugin->delete_exchange = &postgres_delete_exchange;
- plugin->list_exchanges = &postgres_list_exchanges;
- plugin->insert_exchange_signkey = &postgres_insert_exchange_signkey;
- plugin->insert_deposit_confirmation = &postgres_insert_deposit_confirmation;
- plugin->get_deposit_confirmations = &postgres_get_deposit_confirmations;
-
- plugin->select_denomination_info = &postgres_select_denomination_info;
- plugin->insert_denomination_info = &postgres_insert_denomination_info;
-
- plugin->get_auditor_progress_reserve = &postgres_get_auditor_progress_reserve;
- plugin->update_auditor_progress_reserve =
- &postgres_update_auditor_progress_reserve;
- plugin->insert_auditor_progress_reserve =
- &postgres_insert_auditor_progress_reserve;
- plugin->get_auditor_progress_aggregation =
- &postgres_get_auditor_progress_aggregation;
- plugin->update_auditor_progress_aggregation =
- &postgres_update_auditor_progress_aggregation;
- plugin->insert_auditor_progress_aggregation =
- &postgres_insert_auditor_progress_aggregation;
- plugin->get_auditor_progress_deposit_confirmation =
- &postgres_get_auditor_progress_deposit_confirmation;
- plugin->update_auditor_progress_deposit_confirmation =
- &postgres_update_auditor_progress_deposit_confirmation;
- plugin->insert_auditor_progress_deposit_confirmation =
- &postgres_insert_auditor_progress_deposit_confirmation;
- plugin->get_auditor_progress_coin = &postgres_get_auditor_progress_coin;
- plugin->update_auditor_progress_coin = &postgres_update_auditor_progress_coin;
- plugin->insert_auditor_progress_coin = &postgres_insert_auditor_progress_coin;
-
- plugin->get_wire_auditor_account_progress =
- &postgres_get_wire_auditor_account_progress;
- plugin->update_wire_auditor_account_progress =
- &postgres_update_wire_auditor_account_progress;
- plugin->insert_wire_auditor_account_progress =
- &postgres_insert_wire_auditor_account_progress;
- plugin->get_wire_auditor_progress = &postgres_get_wire_auditor_progress;
- plugin->update_wire_auditor_progress = &postgres_update_wire_auditor_progress;
- plugin->insert_wire_auditor_progress = &postgres_insert_wire_auditor_progress;
-
- plugin->del_reserve_info = &postgres_del_reserve_info;
- plugin->get_reserve_info = &postgres_get_reserve_info;
- plugin->update_reserve_info = &postgres_update_reserve_info;
- plugin->insert_reserve_info = &postgres_insert_reserve_info;
-
- plugin->get_reserve_summary = &postgres_get_reserve_summary;
- plugin->update_reserve_summary = &postgres_update_reserve_summary;
- plugin->insert_reserve_summary = &postgres_insert_reserve_summary;
-
- plugin->get_wire_fee_summary = &postgres_get_wire_fee_summary;
- plugin->update_wire_fee_summary = &postgres_update_wire_fee_summary;
- plugin->insert_wire_fee_summary = &postgres_insert_wire_fee_summary;
-
- plugin->get_denomination_balance = &postgres_get_denomination_balance;
- plugin->update_denomination_balance = &postgres_update_denomination_balance;
- plugin->insert_denomination_balance = &postgres_insert_denomination_balance;
-
- plugin->get_balance_summary = &postgres_get_balance_summary;
- plugin->update_balance_summary = &postgres_update_balance_summary;
- plugin->insert_balance_summary = &postgres_insert_balance_summary;
-
- plugin->select_historic_denom_revenue =
- &postgres_select_historic_denom_revenue;
- plugin->insert_historic_denom_revenue =
- &postgres_insert_historic_denom_revenue;
-
- plugin->select_historic_reserve_revenue =
- &postgres_select_historic_reserve_revenue;
- plugin->insert_historic_reserve_revenue =
- &postgres_insert_historic_reserve_revenue;
-
- plugin->get_predicted_balance = &postgres_get_predicted_balance;
- plugin->update_predicted_result = &postgres_update_predicted_result;
- plugin->insert_predicted_result = &postgres_insert_predicted_result;
+ plugin->get_auditor_progress
+ = &TAH_PG_get_auditor_progress;
+ plugin->get_balance
+ = &TAH_PG_get_balance;
+ plugin->insert_auditor_progress
+ = &TAH_PG_insert_auditor_progress;
+ plugin->insert_balance
+ = &TAH_PG_insert_balance;
+ plugin->update_auditor_progress
+ = &TAH_PG_update_auditor_progress;
+ plugin->update_balance
+ = &TAH_PG_update_balance;
+
+ plugin->insert_exchange_signkey
+ = &TAH_PG_insert_exchange_signkey;
+ plugin->insert_deposit_confirmation
+ = &TAH_PG_insert_deposit_confirmation;
+ plugin->get_deposit_confirmations
+ = &TAH_PG_get_deposit_confirmations;
+ plugin->delete_deposit_confirmation
+ = &TAH_PG_delete_deposit_confirmation;
+
+ plugin->insert_reserve_info
+ = &TAH_PG_insert_reserve_info;
+ plugin->update_reserve_info
+ = &TAH_PG_update_reserve_info;
+ plugin->get_reserve_info
+ = &TAH_PG_get_reserve_info;
+ plugin->del_reserve_info
+ = &TAH_PG_del_reserve_info;
+
+ plugin->insert_pending_deposit
+ = &TAH_PG_insert_pending_deposit;
+ plugin->select_pending_deposits
+ = &TAH_PG_select_pending_deposits;
+ plugin->delete_pending_deposit
+ = &TAH_PG_delete_pending_deposit;
+
+ plugin->insert_purse_info
+ = &TAH_PG_insert_purse_info;
+ plugin->update_purse_info
+ = &TAH_PG_update_purse_info;
+ plugin->get_purse_info
+ = &TAH_PG_get_purse_info;
+ plugin->delete_purse_info
+ = &TAH_PG_delete_purse_info;
+ plugin->select_purse_expired
+ = &TAH_PG_select_purse_expired;
+
+ plugin->insert_denomination_balance
+ = &TAH_PG_insert_denomination_balance;
+ plugin->update_denomination_balance
+ = &TAH_PG_update_denomination_balance;
+ plugin->del_denomination_balance
+ = &TAH_PG_del_denomination_balance;
+ plugin->get_denomination_balance
+ = &TAH_PG_get_denomination_balance;
+
+ plugin->insert_historic_denom_revenue
+ = &TAH_PG_insert_historic_denom_revenue;
+ plugin->select_historic_denom_revenue
+ = &TAH_PG_select_historic_denom_revenue;
+
+ plugin->insert_historic_reserve_revenue
+ = &TAH_PG_insert_historic_reserve_revenue;
+ plugin->select_historic_reserve_revenue
+ = &TAH_PG_select_historic_reserve_revenue;
return plugin;
}
@@ -3305,6 +546,8 @@ libtaler_plugin_auditordb_postgres_done (void *cls)
struct TALER_AUDITORDB_Plugin *plugin = cls;
struct PostgresClosure *pg = plugin->cls;
+ if (NULL != pg->conn)
+ GNUNET_PQ_disconnect (pg->conn);
GNUNET_free (pg->currency);
GNUNET_free (pg);
GNUNET_free (plugin);
diff --git a/src/auditordb/procedures.sql.in b/src/auditordb/procedures.sql.in
new file mode 100644
index 000000000..0c83bdb5e
--- /dev/null
+++ b/src/auditordb/procedures.sql.in
@@ -0,0 +1,24 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 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/>
+--
+
+BEGIN;
+
+SET search_path TO auditor;
+
+#include "auditor_do_get_auditor_progress.sql"
+#include "auditor_do_get_balance.sql"
+
+COMMIT;
diff --git a/src/auditordb/restart0001.sql b/src/auditordb/restart.sql
index 90bb59551..2dc6864ff 100644
--- a/src/auditordb/restart0001.sql
+++ b/src/auditordb/restart.sql
@@ -17,6 +17,8 @@
-- Everything in one big transaction
BEGIN;
+SET search_path TO auditor;
+
-- This script restart the auditor state as done to RESTART
-- an audit from scratch. It does NOT drop tables and also
-- PRESERVES data that running the auditor would not recover,
@@ -29,20 +31,14 @@ BEGIN;
-- Unlike the other SQL files, it SHOULD be updated to reflect the
-- latest requirements for dropping tables.
-DELETE FROM auditor_predicted_result;
-DELETE FROM auditor_historic_denomination_revenue;
-DELETE FROM auditor_balance_summary;
+DELETE FROM auditor_amount_arithmetic_inconsistency;
+DELETE FROM auditor_balances;
DELETE FROM auditor_denomination_pending;
-DELETE FROM auditor_reserve_balance;
-DELETE FROM auditor_wire_fee_balance;
-DELETE FROM auditor_reserves;
-DELETE FROM auditor_progress_reserve;
-DELETE FROM auditor_progress_aggregation;
-DELETE FROM auditor_progress_deposit_confirmation;
-DELETE FROM auditor_progress_coin;
-DELETE FROM wire_auditor_progress;
-DELETE FROM wire_auditor_account_progress;
+DELETE FROM auditor_historic_denomination_revenue;
DELETE FROM auditor_historic_reserve_summary;
+DELETE FROM auditor_progress;
+DELETE FROM auditor_purses;
+DELETE FROM auditor_reserves;
-- And we're out of here...
COMMIT;
diff --git a/src/auditordb/test_auditordb.c b/src/auditordb/test_auditordb.c
index 201d61b80..5184722f0 100644
--- a/src/auditordb/test_auditordb.c
+++ b/src/auditordb/test_auditordb.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2016 Taler Systems SA
+ Copyright (C) 2016--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
@@ -17,17 +17,17 @@
* @file auditordb/test_auditordb.c
* @brief test cases for DB interaction functions
* @author Gabor X Toth
+ * @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_db_lib.h>
#include "taler_auditordb_lib.h"
#include "taler_auditordb_plugin.h"
-
/**
- * Global result from the testcase.
+ * Currency we use, must match CURRENCY in "test-auditor-db-postgres.conf".
*/
-static int result = -1;
+#define CURRENCY "EUR"
/**
* Report line of error if @a cond is true, and jump to label "drop".
@@ -39,7 +39,6 @@ static int result = -1;
goto drop; \
} while (0)
-
/**
* Initializes @a ptr with random data.
*/
@@ -54,28 +53,118 @@ static int result = -1;
/**
- * Currency we use, must match CURRENCY in "test-auditor-db-postgres.conf".
+ * Global result from the testcase.
*/
-#define CURRENCY "EUR"
+static int result = -1;
+
+/**
+ * Hash of denomination public key.
+ */
+static struct TALER_DenominationHashP denom_pub_hash;
+
+/**
+ * Another hash of a denomination public key.
+ */
+static struct TALER_DenominationHashP rnd_hash;
+
+/**
+ * Current time.
+ */
+static struct GNUNET_TIME_Timestamp now;
+
+/**
+ * Timestamp in the past.
+ */
+static struct GNUNET_TIME_Timestamp past;
+
+/**
+ * Timestamp in the future.
+ */
+static struct GNUNET_TIME_Timestamp future;
/**
* Database plugin under test.
*/
static struct TALER_AUDITORDB_Plugin *plugin;
+/**
+ * Historic denomination revenue value.
+ */
+static struct TALER_Amount rbalance;
-static int
-select_denomination_info_result (void *cls,
- const struct
- TALER_DenominationKeyValidityPS *issue2)
+/**
+ * Historic denomination loss value.
+ */
+static struct TALER_Amount rloss;
+
+/**
+ * Reserve profit value we are using.
+ */
+static struct TALER_Amount reserve_profits;
+
+
+static enum GNUNET_GenericReturnValue
+select_historic_denom_revenue_result (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash2,
+ struct GNUNET_TIME_Timestamp revenue_timestamp2,
+ const struct TALER_Amount *revenue_balance2,
+ const struct TALER_Amount *loss2)
{
- const struct TALER_DenominationKeyValidityPS *issue1 = cls;
+ static int n = 0;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "select_historic_denom_revenue_result: row %u\n", n);
+
+ if ( (2 <= n++)
+ || (cls != NULL)
+ || ((0 != GNUNET_memcmp (&revenue_timestamp2,
+ &past))
+ && (0 != GNUNET_memcmp (&revenue_timestamp2,
+ &now)))
+ || ((0 != GNUNET_memcmp (denom_pub_hash2,
+ &denom_pub_hash))
+ && (0 != GNUNET_memcmp (denom_pub_hash2,
+ &rnd_hash)))
+ || (0 != TALER_amount_cmp (revenue_balance2,
+ &rbalance))
+ || (0 != TALER_amount_cmp (loss2,
+ &rloss)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "select_historic_denom_revenue_result: result does not match\n");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
- if (0 != GNUNET_memcmp (issue1,
- issue2))
+static enum GNUNET_GenericReturnValue
+select_historic_reserve_revenue_result (
+ void *cls,
+ struct GNUNET_TIME_Timestamp start_time2,
+ struct GNUNET_TIME_Timestamp end_time2,
+ const struct TALER_Amount *reserve_profits2)
+{
+ static int n = 0;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "select_historic_reserve_revenue_result: row %u\n", n);
+
+ if ((2 <= n++)
+ || (cls != NULL)
+ || ((0 != GNUNET_memcmp (&start_time2,
+ &past))
+ && (0 != GNUNET_memcmp (&start_time2,
+ &now)))
+ || (0 != GNUNET_memcmp (&end_time2,
+ &future))
+ || (0 != TALER_amount_cmp (reserve_profits2,
+ &reserve_profits)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "select_denomination_info_result: issue does not match\n");
+ "select_historic_reserve_revenue_result: result does not match\n");
GNUNET_break (0);
return GNUNET_SYSERR;
}
@@ -92,7 +181,6 @@ static void
run (void *cls)
{
struct GNUNET_CONFIGURATION_Handle *cfg = cls;
- struct TALER_AUDITORDB_Session *session;
uint64_t rowid;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -108,21 +196,22 @@ run (void *cls)
(void) plugin->drop_tables (plugin->cls,
GNUNET_YES);
if (GNUNET_OK !=
- plugin->create_tables (plugin->cls))
+ plugin->create_tables (plugin->cls,
+ false,
+ 0))
{
result = 77;
goto unload;
}
- if (NULL ==
- (session = plugin->get_session (plugin->cls)))
+ if (GNUNET_SYSERR ==
+ plugin->preflight (plugin->cls))
{
result = 77;
goto drop;
}
FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls,
- session));
+ plugin->start (plugin->cls));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"initializing\n");
@@ -149,594 +238,211 @@ run (void *cls)
TALER_string_to_amount (CURRENCY ":0.000014",
&fee_refund));
- struct TALER_MasterPublicKeyP master_pub;
struct TALER_ReservePublicKeyP reserve_pub;
- struct GNUNET_HashCode rnd_hash;
- RND_BLK (&master_pub);
- RND_BLK (&reserve_pub);
- RND_BLK (&rnd_hash);
-
struct TALER_DenominationPrivateKey denom_priv;
struct TALER_DenominationPublicKey denom_pub;
- struct GNUNET_HashCode denom_pub_hash;
-
- denom_priv.rsa_private_key = GNUNET_CRYPTO_rsa_private_key_create (1024);
- denom_pub.rsa_public_key = GNUNET_CRYPTO_rsa_private_key_get_public (
- denom_priv.rsa_private_key);
- GNUNET_CRYPTO_rsa_public_key_hash (denom_pub.rsa_public_key, &denom_pub_hash);
- GNUNET_CRYPTO_rsa_private_key_free (denom_priv.rsa_private_key);
- GNUNET_CRYPTO_rsa_public_key_free (denom_pub.rsa_public_key);
-
- struct GNUNET_TIME_Absolute now, past, future, date;
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
- past = GNUNET_TIME_absolute_subtract (now,
- GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_HOURS,
- 4));
- future = GNUNET_TIME_absolute_add (now,
- GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_HOURS,
- 4));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: auditor_insert_exchange\n");
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_exchange (plugin->cls,
- session,
- &master_pub,
- "https://exchange/"));
-
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: insert_denomination_info\n");
-
- struct TALER_DenominationKeyValidityPS issue = { 0 };
- issue.master = master_pub;
- issue.denom_hash = denom_pub_hash;
-
- issue.start = GNUNET_TIME_absolute_hton (now);
- issue.expire_withdraw = GNUNET_TIME_absolute_hton
- (GNUNET_TIME_absolute_add (now,
- GNUNET_TIME_UNIT_HOURS));
- issue.expire_deposit = GNUNET_TIME_absolute_hton
- (GNUNET_TIME_absolute_add
- (now,
- GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_HOURS, 2)));
- issue.expire_legal = GNUNET_TIME_absolute_hton
- (GNUNET_TIME_absolute_add
- (now,
- GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_HOURS, 3)));
- TALER_amount_hton (&issue.value, &value);
- TALER_amount_hton (&issue.fee_withdraw, &fee_withdraw);
- TALER_amount_hton (&issue.fee_deposit, &fee_deposit);
- TALER_amount_hton (&issue.fee_refresh, &fee_refresh);
- TALER_amount_hton (&issue.fee_refund, &fee_refund);
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_denomination_info (plugin->cls,
- session,
- &issue));
+ struct GNUNET_TIME_Timestamp date;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: select_denomination_info\n");
-
- FAILIF (0 >=
- plugin->select_denomination_info (plugin->cls,
- session,
- &master_pub,
- &select_denomination_info_result,
- &issue));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: insert_auditor_progress\n");
-
- struct TALER_AUDITORDB_ProgressPointCoin ppc = {
- .last_deposit_serial_id = 123,
- .last_melt_serial_id = 456,
- .last_refund_serial_id = 789,
- .last_withdraw_serial_id = 555
- };
- struct TALER_AUDITORDB_ProgressPointCoin ppc2 = {
- .last_deposit_serial_id = 0,
- .last_melt_serial_id = 0,
- .last_refund_serial_id = 0,
- .last_withdraw_serial_id = 0
- };
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_auditor_progress_coin (plugin->cls,
- session,
- &master_pub,
- &ppc));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: update_auditor_progress\n");
-
- ppc.last_deposit_serial_id++;
- ppc.last_melt_serial_id++;
- ppc.last_refund_serial_id++;
- ppc.last_withdraw_serial_id++;
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->update_auditor_progress_coin (plugin->cls,
- session,
- &master_pub,
- &ppc));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: get_auditor_progress\n");
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_auditor_progress_coin (plugin->cls,
- session,
- &master_pub,
- &ppc2));
- FAILIF ( (ppc.last_deposit_serial_id != ppc2.last_deposit_serial_id) ||
- (ppc.last_melt_serial_id != ppc2.last_melt_serial_id) ||
- (ppc.last_refund_serial_id != ppc2.last_refund_serial_id) ||
- (ppc.last_withdraw_serial_id != ppc2.last_withdraw_serial_id) );
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: insert_reserve_info\n");
-
- struct TALER_Amount reserve_balance, withdraw_fee_balance;
- struct TALER_Amount reserve_balance2 = {}, withdraw_fee_balance2 = {};
-
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (CURRENCY ":12.345678",
- &reserve_balance));
+ RND_BLK (&reserve_pub);
+ RND_BLK (&rnd_hash);
GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (CURRENCY ":23.456789",
- &withdraw_fee_balance));
-
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_reserve_info (plugin->cls,
- session,
- &reserve_pub,
- &master_pub,
- &reserve_balance,
- &withdraw_fee_balance,
- past,
- "payto://bla/blub"));
+ TALER_denom_priv_create (&denom_priv,
+ &denom_pub,
+ GNUNET_CRYPTO_BSA_RSA,
+ 1024));
+ TALER_denom_pub_hash (&denom_pub,
+ &denom_pub_hash);
+ TALER_denom_priv_free (&denom_priv);
+ TALER_denom_pub_free (&denom_pub);
+
+ now = GNUNET_TIME_timestamp_get ();
+ past = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_subtract (now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS,
+ 4)));
+ future = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS,
+ 4)));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: update_reserve_info\n");
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->update_reserve_info (plugin->cls,
- session,
- &reserve_pub,
- &master_pub,
- &reserve_balance,
- &withdraw_fee_balance,
- future));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: get_reserve_info\n");
-
- char *payto;
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_reserve_info (plugin->cls,
- session,
- &reserve_pub,
- &master_pub,
- &rowid,
- &reserve_balance2,
- &withdraw_fee_balance2,
- &date,
- &payto));
- FAILIF (0 != strcmp (payto,
- "payto://bla/blub"));
- GNUNET_free (payto);
- FAILIF (0 != GNUNET_memcmp (&date, &future)
- || 0 != GNUNET_memcmp (&reserve_balance2, &reserve_balance)
- || 0 != GNUNET_memcmp (&withdraw_fee_balance2,
- &withdraw_fee_balance));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: insert_reserve_summary\n");
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_reserve_summary (plugin->cls,
- session,
- &master_pub,
- &withdraw_fee_balance,
- &reserve_balance));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: update_reserve_summary\n");
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->update_reserve_summary (plugin->cls,
- session,
- &master_pub,
- &reserve_balance,
- &withdraw_fee_balance));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: get_reserve_summary\n");
+ {
+ struct TALER_AUDITORDB_ReserveFeeBalance rfb;
+ struct TALER_AUDITORDB_ReserveFeeBalance rfb2;
- ZR_BLK (&reserve_balance2);
- ZR_BLK (&withdraw_fee_balance2);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test: insert_reserve_info\n");
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":12.345678",
+ &rfb.reserve_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":11.245678",
+ &rfb.reserve_loss));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":23.456789",
+ &rfb.withdraw_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":23.456719",
+ &rfb.close_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":33.456789",
+ &rfb.purse_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":43.456789",
+ &rfb.open_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":53.456789",
+ &rfb.history_fee_balance));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_reserve_info (plugin->cls,
+ &reserve_pub,
+ &rfb,
+ past,
+ "payto://bla/blub"));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test: update_reserve_info\n");
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->update_reserve_info (plugin->cls,
+ &reserve_pub,
+ &rfb,
+ future));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test: get_reserve_info\n");
+ {
+ char *payto;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_reserve_info (plugin->cls,
+ &reserve_pub,
+ &rowid,
+ &rfb2,
+ &date,
+ &payto));
+ FAILIF (0 != strcmp (payto,
+ "payto://bla/blub"));
+ GNUNET_free (payto);
+ }
+ FAILIF ( (0 != GNUNET_memcmp (&date,
+ &future))
+ || (0 != TALER_amount_cmp (&rfb2.reserve_balance,
+ &rfb.reserve_balance))
+ || (0 != TALER_amount_cmp (&rfb2.withdraw_fee_balance,
+ &rfb.withdraw_fee_balance))
+ || (0 != TALER_amount_cmp (&rfb2.close_fee_balance,
+ &rfb.close_fee_balance))
+ || (0 != TALER_amount_cmp (&rfb2.purse_fee_balance,
+ &rfb.purse_fee_balance))
+ || (0 != TALER_amount_cmp (&rfb2.open_fee_balance,
+ &rfb.open_fee_balance))
+ || (0 != TALER_amount_cmp (&rfb2.history_fee_balance,
+ &rfb.history_fee_balance))
+ );
+ }
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_reserve_summary (plugin->cls,
- session,
- &master_pub,
- &reserve_balance2,
- &withdraw_fee_balance2));
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test: insert_denomination_balance\n");
+
+ struct TALER_AUDITORDB_DenominationCirculationData dcd;
+ struct TALER_AUDITORDB_DenominationCirculationData dcd2;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":12.345678",
+ &dcd.denom_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.1",
+ &dcd.denom_loss));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":13.57986",
+ &dcd.denom_risk));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":12.57986",
+ &dcd.recoup_loss));
+ dcd.num_issued = 62;
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_denomination_balance (plugin->cls,
+ &denom_pub_hash,
+ &dcd));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test: update_denomination_balance\n");
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->update_denomination_balance (plugin->cls,
+ &denom_pub_hash,
+ &dcd));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test: get_denomination_balance\n");
- FAILIF ( (0 != GNUNET_memcmp (&reserve_balance2,
- &reserve_balance) ||
- (0 != GNUNET_memcmp (&withdraw_fee_balance2,
- &withdraw_fee_balance)) ) );
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_denomination_balance (plugin->cls,
+ &denom_pub_hash,
+ &dcd2));
+ FAILIF (0 != TALER_amount_cmp (&dcd2.denom_balance,
+ &dcd.denom_balance));
+ FAILIF (0 != TALER_amount_cmp (&dcd2.denom_loss,
+ &dcd.denom_loss));
+ FAILIF (0 != TALER_amount_cmp (&dcd2.denom_risk,
+ &dcd.denom_risk));
+ FAILIF (0 != TALER_amount_cmp (&dcd2.recoup_loss,
+ &dcd.recoup_loss));
+ FAILIF (dcd2.num_issued != dcd.num_issued);
+ }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: insert_denomination_balance\n");
-
- struct TALER_Amount denom_balance;
- struct TALER_Amount denom_loss;
- struct TALER_Amount denom_loss2;
- struct TALER_Amount deposit_fee_balance;
- struct TALER_Amount melt_fee_balance;
- struct TALER_Amount refund_fee_balance;
- struct TALER_Amount denom_balance2;
- struct TALER_Amount deposit_fee_balance2;
- struct TALER_Amount melt_fee_balance2;
- struct TALER_Amount refund_fee_balance2;
- struct TALER_Amount rbalance;
- struct TALER_Amount rbalance2;
- struct TALER_Amount loss;
- struct TALER_Amount loss2;
- struct TALER_Amount iirp;
- struct TALER_Amount iirp2;
- uint64_t nissued;
-
+ "Test: insert_historic_denom_revenue\n");
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":12.345678",
- &denom_balance));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (CURRENCY ":0.1",
- &denom_loss));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (CURRENCY ":23.456789",
- &deposit_fee_balance));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (CURRENCY ":34.567890",
- &melt_fee_balance));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (CURRENCY ":45.678901",
- &refund_fee_balance));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (CURRENCY ":13.57986",
&rbalance));
GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (CURRENCY ":1.6",
- &loss));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (CURRENCY ":1.1",
- &iirp));
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_denomination_balance (plugin->cls,
- session,
- &denom_pub_hash,
- &denom_balance,
- &denom_loss,
- &rbalance,
- &loss,
- 42));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: update_denomination_balance\n");
-
- ppc.last_withdraw_serial_id++;
- ppc.last_deposit_serial_id++;
- ppc.last_melt_serial_id++;
- ppc.last_refund_serial_id++;
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->update_denomination_balance (plugin->cls,
- session,
- &denom_pub_hash,
- &denom_balance,
- &denom_loss,
- &rbalance,
- &loss,
- 62));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: get_denomination_balance\n");
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_denomination_balance (plugin->cls,
- session,
- &denom_pub_hash,
- &denom_balance2,
- &denom_loss2,
- &rbalance2,
- &loss2,
- &nissued));
-
- FAILIF (0 != GNUNET_memcmp (&denom_balance2, &denom_balance));
- FAILIF (0 != GNUNET_memcmp (&denom_loss2, &denom_loss));
- FAILIF (0 != GNUNET_memcmp (&rbalance2, &rbalance));
- FAILIF (62 != nissued);
-
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: insert_balance_summary\n");
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_balance_summary (plugin->cls,
- session,
- &master_pub,
- &refund_fee_balance,
- &melt_fee_balance,
- &deposit_fee_balance,
- &denom_balance,
- &rbalance,
- &loss,
- &iirp));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: update_balance_summary\n");
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->update_balance_summary (plugin->cls,
- session,
- &master_pub,
- &denom_balance,
- &deposit_fee_balance,
- &melt_fee_balance,
- &refund_fee_balance,
- &rbalance,
- &loss,
- &iirp));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: get_balance_summary\n");
-
- ZR_BLK (&denom_balance2);
- ZR_BLK (&deposit_fee_balance2);
- ZR_BLK (&melt_fee_balance2);
- ZR_BLK (&refund_fee_balance2);
- ZR_BLK (&rbalance2);
- ZR_BLK (&loss2);
- ZR_BLK (&iirp2);
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_balance_summary (plugin->cls,
- session,
- &master_pub,
- &denom_balance2,
- &deposit_fee_balance2,
- &melt_fee_balance2,
- &refund_fee_balance2,
- &rbalance2,
- &loss2,
- &iirp2));
-
- FAILIF ( (0 != GNUNET_memcmp (&denom_balance2,
- &denom_balance) ) ||
- (0 != GNUNET_memcmp (&deposit_fee_balance2,
- &deposit_fee_balance) ) ||
- (0 != GNUNET_memcmp (&melt_fee_balance2,
- &melt_fee_balance) ) ||
- (0 != GNUNET_memcmp (&refund_fee_balance2,
- &refund_fee_balance)) );
- FAILIF (0 != GNUNET_memcmp (&rbalance2,
- &rbalance));
- FAILIF (0 != GNUNET_memcmp (&loss2,
- &loss));
- FAILIF (0 != GNUNET_memcmp (&iirp2,
- &iirp));
-
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: insert_historic_denom_revenue\n");
-
+ TALER_string_to_amount (CURRENCY ":23.456789",
+ &rloss));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_historic_denom_revenue (plugin->cls,
- session,
- &master_pub,
&denom_pub_hash,
past,
&rbalance,
- &loss));
-
+ &rloss));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_historic_denom_revenue (plugin->cls,
- session,
- &master_pub,
&rnd_hash,
now,
&rbalance,
- &loss));
-
+ &rloss));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Test: select_historic_denom_revenue\n");
-
- int
- select_historic_denom_revenue_result (void *cls,
- const struct
- GNUNET_HashCode *denom_pub_hash2,
- struct GNUNET_TIME_Absolute
- revenue_timestamp2,
- const struct
- TALER_Amount *revenue_balance2,
- const struct TALER_Amount *loss2)
- {
- static int n = 0;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "select_historic_denom_revenue_result: row %u\n", n);
-
- if ((2 <= n++)
- || (cls != NULL)
- || ((0 != GNUNET_memcmp (&revenue_timestamp2, &past))
- && (0 != GNUNET_memcmp (&revenue_timestamp2, &now)))
- || ((0 != GNUNET_memcmp (denom_pub_hash2, &denom_pub_hash))
- && (0 != GNUNET_memcmp (denom_pub_hash2, &rnd_hash)))
- || (0 != GNUNET_memcmp (revenue_balance2, &rbalance))
- || (0 != GNUNET_memcmp (loss2, &loss)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "select_historic_denom_revenue_result: result does not match\n");
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
- }
-
-
FAILIF (0 >=
- plugin->select_historic_denom_revenue (plugin->cls,
- session,
- &master_pub,
- &
- select_historic_denom_revenue_result,
- NULL));
-
+ plugin->select_historic_denom_revenue (
+ plugin->cls,
+ &select_historic_denom_revenue_result,
+ NULL));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Test: insert_historic_reserve_revenue\n");
-
- struct TALER_Amount reserve_profits;
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":56.789012",
&reserve_profits));
-
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_historic_reserve_revenue (plugin->cls,
- session,
- &master_pub,
past,
future,
&reserve_profits));
-
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_historic_reserve_revenue (plugin->cls,
- session,
- &master_pub,
now,
future,
&reserve_profits));
-
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Test: select_historic_reserve_revenue\n");
-
- int
- select_historic_reserve_revenue_result (void *cls,
- struct GNUNET_TIME_Absolute
- start_time2,
- struct GNUNET_TIME_Absolute end_time2,
- const struct
- TALER_Amount *reserve_profits2)
- {
- static int n = 0;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "select_historic_reserve_revenue_result: row %u\n", n);
-
- if ((2 <= n++)
- || (cls != NULL)
- || ((0 != GNUNET_memcmp (&start_time2, &past))
- && (0 != GNUNET_memcmp (&start_time2, &now)))
- || (0 != GNUNET_memcmp (&end_time2, &future))
- || (0 != GNUNET_memcmp (reserve_profits2, &reserve_profits)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "select_historic_reserve_revenue_result: result does not match\n");
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
- }
-
-
FAILIF (0 >=
plugin->select_historic_reserve_revenue (plugin->cls,
- session,
- &master_pub,
select_historic_reserve_revenue_result,
NULL));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: insert_predicted_result\n");
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_predicted_result (plugin->cls,
- session,
- &master_pub,
- &rbalance));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: update_predicted_result\n");
-
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (CURRENCY ":78.901234",
- &rbalance));
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->update_predicted_result (plugin->cls,
- session,
- &master_pub,
- &rbalance));
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_wire_fee_summary (plugin->cls,
- session,
- &master_pub,
- &rbalance));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->update_wire_fee_summary (plugin->cls,
- session,
- &master_pub,
- &reserve_profits));
- {
- struct TALER_Amount rprof;
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_wire_fee_summary (plugin->cls,
- session,
- &master_pub,
- &rprof));
- FAILIF (0 !=
- TALER_amount_cmp (&rprof,
- &reserve_profits));
- }
FAILIF (0 >
- plugin->commit (plugin->cls,
- session));
-
-
- FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls,
- session));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: get_predicted_balance\n");
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_predicted_balance (plugin->cls,
- session,
- &master_pub,
- &rbalance2));
+ plugin->commit (plugin->cls));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->del_reserve_info (plugin->cls,
- session,
- &reserve_pub,
- &master_pub));
-
- FAILIF (0 != TALER_amount_cmp (&rbalance2,
- &rbalance));
-
- plugin->rollback (plugin->cls,
- session);
+ &reserve_pub));
#if GC_IMPLEMENTED
FAILIF (GNUNET_OK !=
@@ -746,23 +452,7 @@ run (void *cls)
result = 0;
drop:
- if (NULL != session)
- {
- plugin->rollback (plugin->cls,
- session);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: auditor_delete_exchange\n");
- GNUNET_break (GNUNET_OK ==
- plugin->start (plugin->cls,
- session));
- GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
- plugin->delete_exchange (plugin->cls,
- session,
- &master_pub));
- GNUNET_break (0 <=
- plugin->commit (plugin->cls,
- session));
- }
+ plugin->rollback (plugin->cls);
GNUNET_break (GNUNET_OK ==
plugin->drop_tables (plugin->cls,
GNUNET_YES));
@@ -783,17 +473,20 @@ main (int argc,
(void) argc;
result = -1;
- if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ GNUNET_log_setup (argv[0],
+ "WARNING",
+ NULL);
+ TALER_OS_init ();
+ if (NULL == (plugin_name = strrchr (argv[0],
+ (int) '-')))
{
GNUNET_break (0);
return -1;
}
- GNUNET_log_setup (argv[0],
- "WARNING",
- NULL);
plugin_name++;
(void) GNUNET_asprintf (&testname,
- "test-auditor-db-%s", plugin_name);
+ "test-auditor-db-%s",
+ plugin_name);
(void) GNUNET_asprintf (&config_filename,
"%s.conf", testname);
cfg = GNUNET_CONFIGURATION_create ();
@@ -806,7 +499,8 @@ main (int argc,
GNUNET_free (testname);
return 2;
}
- GNUNET_SCHEDULER_run (&run, cfg);
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
GNUNET_CONFIGURATION_destroy (cfg);
GNUNET_free (config_filename);
GNUNET_free (testname);
diff --git a/src/auditordb/test_auditordb_checkpoints.c b/src/auditordb/test_auditordb_checkpoints.c
new file mode 100644
index 000000000..f9bb83142
--- /dev/null
+++ b/src/auditordb/test_auditordb_checkpoints.c
@@ -0,0 +1,386 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2016--2024 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 auditordb/test_auditordb_checkpoints.c
+ * @brief test cases for DB interaction functions
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_db_lib.h>
+#include "taler_auditordb_lib.h"
+#include "taler_auditordb_plugin.h"
+
+/**
+ * Currency we use, must match CURRENCY in "test-auditor-db-postgres.conf".
+ */
+#define CURRENCY "EUR"
+
+/**
+ * Report line of error if @a cond is true, and jump to label "drop".
+ */
+#define FAILIF(cond) \
+ do { \
+ if (! (cond)) { break;} \
+ GNUNET_break (0); \
+ goto drop; \
+ } while (0)
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, \
+ sizeof (*ptr))
+
+/**
+ * Initializes @a ptr with zeros.
+ */
+#define ZR_BLK(ptr) \
+ memset (ptr, 0, sizeof (*ptr))
+
+
+/**
+ * Global result from the testcase.
+ */
+static int result = -1;
+
+/**
+ * Database plugin under test.
+ */
+static struct TALER_AUDITORDB_Plugin *plugin;
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+static void
+run (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct TALER_Amount a1;
+ struct TALER_Amount a2;
+ struct TALER_Amount a3;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":11.245678",
+ &a1));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":2",
+ &a2));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":3",
+ &a3));
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "loading database plugin\n");
+ if (NULL ==
+ (plugin = TALER_AUDITORDB_plugin_load (cfg)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to connect to database\n");
+ result = 77;
+ return;
+ }
+
+ (void) plugin->drop_tables (plugin->cls,
+ GNUNET_YES);
+ if (GNUNET_OK !=
+ plugin->create_tables (plugin->cls,
+ false,
+ 0))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to 'create_tables'\n");
+ result = 77;
+ goto unload;
+ }
+ if (GNUNET_SYSERR ==
+ plugin->preflight (plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed preflight check\n");
+ result = 77;
+ goto drop;
+ }
+
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls));
+
+ /* Test inserting a blank value, should tell us one result */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ plugin->insert_auditor_progress (plugin->cls,
+ "Test",
+ 69,
+ NULL)
+ );
+ /* Test re-inserting the same value; should yield no results */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+ plugin->insert_auditor_progress (plugin->cls,
+ "Test",
+ 69,
+ NULL)
+ );
+ /* Test inserting multiple values, with one already existing */
+ GNUNET_assert (
+ 2 == plugin->insert_auditor_progress (plugin->cls,
+ "Test",
+ 69,
+ "Test2",
+ 123,
+ "Test3",
+ 245,
+ NULL)
+ );
+ /* Test re-re-inserting the same key with a different value; should also yield no results */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+ plugin->insert_auditor_progress (plugin->cls,
+ "Test",
+ 42,
+ NULL)
+ );
+ /* Test updating the same key (again) with a different value; should yield a result */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ plugin->update_auditor_progress (plugin->cls,
+ "Test",
+ 42,
+ NULL)
+ );
+ /* Test updating a key that doesn't exist; should yield 0 */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+ plugin->update_auditor_progress (plugin->cls,
+ "NonexistentTest",
+ 1,
+ NULL)
+ );
+
+ /* Right now, the state should look like this:
+ * Test = 42
+ * Test2 = 123
+ * Test3 = 245
+ * Let's make sure that's the case! */
+ uint64_t value;
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ plugin->get_auditor_progress (
+ plugin->cls,
+ "Test",
+ &value,
+ NULL)
+ );
+ GNUNET_assert (value == 42);
+
+ /* Ensure the rest are also at their expected values */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ plugin->get_auditor_progress (
+ plugin->cls,
+ "Test2",
+ &value,
+ NULL)
+ );
+ GNUNET_assert (value == 123);
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ plugin->get_auditor_progress (
+ plugin->cls,
+ "Test3",
+ &value,
+ NULL)
+ );
+ GNUNET_assert (value == 245);
+
+ /* Try fetching value that does not exist */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ plugin->get_auditor_progress (
+ plugin->cls,
+ "TestNX",
+ &value,
+ NULL)
+ );
+ GNUNET_assert (0 == value);
+
+
+ /* Test inserting a blank value, should tell us one result */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ plugin->insert_balance (plugin->cls,
+ "Test",
+ &a1,
+ NULL)
+ );
+ /* Test re-inserting the same value; should yield no results */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+ plugin->insert_balance (plugin->cls,
+ "Test",
+ &a1,
+ NULL)
+ );
+ /* Test inserting multiple values, with one already existing */
+ GNUNET_assert (
+ 2 == plugin->insert_balance (plugin->cls,
+ "Test",
+ &a1,
+ "Test2",
+ &a2,
+ "Test3",
+ &a3,
+ NULL)
+ );
+ /* Test re-re-inserting the same key with a different value; should also yield no results */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+ plugin->insert_balance (plugin->cls,
+ "Test",
+ &a2,
+ NULL)
+ );
+ /* Test updating the same key (again) with a different value; should yield a result */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ plugin->update_balance (plugin->cls,
+ "Test",
+ &a2,
+ NULL)
+ );
+ /* Test updating a key that doesn't exist; should yield 0 */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+ plugin->update_balance (plugin->cls,
+ "NonexistentTest",
+ &a2,
+ NULL)
+ );
+
+ /* Right now, the state should look like this:
+ * Test = a2
+ * Test2 = a2
+ * Test3 = a3
+ * Let's make sure that's the case! */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ plugin->get_balance (
+ plugin->cls,
+ "Test",
+ &a1,
+ NULL)
+ );
+ GNUNET_assert (0 ==
+ TALER_amount_cmp (&a1,
+ &a2));
+
+ /* Ensure the rest are also at their expected values */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ plugin->get_balance (
+ plugin->cls,
+ "Test2",
+ &a1,
+ NULL)
+ );
+ GNUNET_assert (0 ==
+ TALER_amount_cmp (&a1,
+ &a2));
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ plugin->get_balance (
+ plugin->cls,
+ "Test3",
+ &a1,
+ NULL)
+ );
+ GNUNET_assert (0 ==
+ TALER_amount_cmp (&a1,
+ &a3));
+
+ /* Try fetching value that does not exist */
+ GNUNET_assert (
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ plugin->get_balance (
+ plugin->cls,
+ "TestNX",
+ &a1,
+ NULL)
+ );
+ GNUNET_assert (GNUNET_OK !=
+ TALER_amount_is_valid (&a1));
+
+ result = 0;
+ GNUNET_break (0 <=
+ plugin->commit (plugin->cls));
+drop:
+ GNUNET_break (GNUNET_OK ==
+ plugin->drop_tables (plugin->cls,
+ GNUNET_YES));
+unload:
+ TALER_AUDITORDB_plugin_unload (plugin);
+ plugin = NULL;
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ char *testname;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ (void) argc;
+ result = -1;
+ TALER_OS_init ();
+ GNUNET_log_setup (argv[0],
+ "INFO",
+ NULL);
+ if (NULL == (plugin_name = strrchr (argv[0],
+ (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ plugin_name++;
+ (void) GNUNET_asprintf (&testname,
+ "test-auditor-db-%s",
+ plugin_name);
+ (void) GNUNET_asprintf (&config_filename,
+ "%s.conf",
+ testname);
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run, cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return result;
+}
diff --git a/src/exchangedb/exchange-0000.sql b/src/auditordb/versioning.sql
index 116f409b7..444cf95ed 100644
--- a/src/exchangedb/exchange-0000.sql
+++ b/src/auditordb/versioning.sql
@@ -146,12 +146,13 @@
BEGIN;
+
-- This file adds versioning support to database it will be loaded to.
-- It requires that PL/pgSQL is already loaded - will raise exception otherwise.
-- All versioning "stuff" (tables, functions) is in "_v" schema.
-- All functions are defined as 'RETURNS SETOF INT4' to be able to make them to RETURN literally nothing (0 rows).
--- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling.
+-- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling
CREATE SCHEMA IF NOT EXISTS _v;
COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.';
diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am
index 282c9db7e..a292dcece 100644
--- a/src/bank-lib/Makefile.am
+++ b/src/bank-lib/Makefile.am
@@ -7,23 +7,23 @@ if USE_COVERAGE
endif
bin_PROGRAMS = \
- taler-bank-transfer
-
-noinst_PROGRAMS = \
+ taler-exchange-wire-gateway-client \
taler-fakebank-run
taler_fakebank_run_SOURCES = \
taler-fakebank-run.c
taler_fakebank_run_LDADD = \
libtalerfakebank.la \
+ $(top_builddir)/src/mhd/libtalermhd.la \
$(top_builddir)/src/util/libtalerutil.la \
-lgnunetutil
-taler_bank_transfer_SOURCES = \
- taler-bank-transfer.c
-taler_bank_transfer_LDADD = \
+taler_exchange_wire_gateway_client_SOURCES = \
+ taler-exchange-wire-gateway-client.c
+taler_exchange_wire_gateway_client_LDADD = \
$(LIBGCRYPT_LIBS) \
$(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/json/libtalerjson.la \
libtalerbank.la \
-lgnunetcurl \
-lgnunetutil \
@@ -46,23 +46,65 @@ libtalerbank_la_SOURCES = \
libtalerbank_la_LIBADD = \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/curl/libtalercurl.la \
+ $(top_builddir)/src/util/libtalerutil.la \
-lgnunetcurl \
-lgnunetjson \
-lgnunetutil \
-ljansson \
- $(LIBGNURLCURL_LIBS) \
+ -lcurl \
$(XLIB)
libtalerfakebank_la_LDFLAGS = \
-version-info 0:0:0 \
-no-undefined
libtalerfakebank_la_SOURCES = \
- fakebank.c
+ fakebank.c fakebank.h \
+ fakebank_api_check.c \
+ fakebank_common_lookup.c fakebank_common_lookup.h \
+ fakebank_common_lp.c fakebank_common_lp.h \
+ fakebank_common_make_admin_transfer.c fakebank_common_make_admin_transfer.h \
+ fakebank_common_parser.c fakebank_common_parser.h \
+ fakebank_common_transact.c fakebank_common_transact.h \
+ fakebank_stop.c \
+ fakebank_bank.c fakebank_bank.h \
+ fakebank_bank_accounts_withdrawals.c fakebank_bank_accounts_withdrawals.h \
+ fakebank_bank_get_accounts.c fakebank_bank_get_accounts.h \
+ fakebank_bank_get_withdrawals.c fakebank_bank_get_withdrawals.h \
+ fakebank_bank_get_root.c fakebank_bank_get_root.h \
+ fakebank_bank_post_accounts_withdrawals.c fakebank_bank_post_accounts_withdrawals.h \
+ fakebank_bank_post_withdrawals_abort.c fakebank_bank_post_withdrawals_abort.h \
+ fakebank_bank_post_withdrawals_confirm.c fakebank_bank_post_withdrawals_confirm.h \
+ fakebank_bank_post_withdrawals_id_op.c fakebank_bank_post_withdrawals_id_op.h \
+ fakebank_bank_testing_register.c fakebank_bank_testing_register.h \
+ fakebank_tbr.c fakebank_tbr.h \
+ fakebank_tbr_get_history.c fakebank_tbr_get_history.h \
+ fakebank_tbr_get_root.c fakebank_tbr_get_root.h \
+ fakebank_tbi.c fakebank_tbi.h \
+ fakebank_tbi_get_withdrawal_operation.c fakebank_tbi_get_withdrawal_operation.h \
+ fakebank_tbi_post_withdrawal_operation.c fakebank_tbi_post_withdrawal_operation.h \
+ fakebank_twg.c fakebank_twg.h \
+ fakebank_twg_admin_add_incoming.c fakebank_twg_admin_add_incoming.h \
+ fakebank_twg_get_root.c fakebank_twg_get_root.h \
+ fakebank_twg_history.c fakebank_twg_history.h \
+ fakebank_twg_transfer.c fakebank_twg_transfer.h
libtalerfakebank_la_LIBADD = \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/mhd/libtalermhd.la \
+ $(top_builddir)/src/util/libtalerutil.la \
-lgnunetjson \
-lgnunetutil \
-ljansson \
+ -lcurl \
-lmicrohttpd \
+ -lpthread \
$(XLIB)
+
+check_SCRIPTS = \
+ test_bank.sh
+
+TESTS = \
+ $(check_SCRIPTS)
+
+EXTRA_DIST = \
+ $(check_SCRIPTS) \
+ test_bank.conf
diff --git a/src/bank-lib/bank_api_admin.c b/src/bank-lib/bank_api_admin.c
index 6c92241dd..f12ab6ee2 100644
--- a/src/bank-lib/bank_api_admin.c
+++ b/src/bank-lib/bank_api_admin.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015--2020 Taler Systems SA
+ Copyright (C) 2015--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
@@ -16,7 +16,7 @@
*/
/**
* @file bank-lib/bank_api_admin.c
- * @brief Implementation of the /admin/ requests of the bank's HTTP API
+ * @brief Implementation of the /admin/add-incoming requests of the bank's HTTP API
* @author Christian Grothoff
*/
#include "platform.h"
@@ -27,7 +27,7 @@
/**
- * @brief An admin/add-incoming Handle
+ * @brief An /admin/add-incoming Handle
*/
struct TALER_BANK_AdminAddIncomingHandle
{
@@ -74,25 +74,25 @@ handle_admin_add_incoming_finished (void *cls,
const void *response)
{
struct TALER_BANK_AdminAddIncomingHandle *aai = cls;
- uint64_t row_id = UINT64_MAX;
- struct GNUNET_TIME_Absolute timestamp;
- enum TALER_ErrorCode ec;
const json_t *j = response;
+ struct TALER_BANK_AdminAddIncomingResponse ir = {
+ .http_status = response_code,
+ .response = response
+ };
aai->job = NULL;
- timestamp = GNUNET_TIME_UNIT_FOREVER_ABS;
switch (response_code)
{
case 0:
- ec = TALER_EC_INVALID_RESPONSE;
+ ir.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_uint64 ("row_id",
- &row_id),
- GNUNET_JSON_spec_absolute_time ("timestamp",
- &timestamp),
+ &ir.details.ok.serial_id),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &ir.details.ok.timestamp),
GNUNET_JSON_spec_end ()
};
@@ -102,37 +102,41 @@ handle_admin_add_incoming_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_INVALID_RESPONSE;
+ ir.http_status = 0;
+ ir.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
- ec = TALER_EC_NONE;
}
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the bank is buggy
(or API version conflict); just pass JSON reply to the application */
GNUNET_break_op (0);
- ec = TALER_JSON_get_error_code (j);
+ ir.ec = TALER_JSON_get_error_code (j);
break;
case MHD_HTTP_FORBIDDEN:
/* Access denied */
- ec = TALER_JSON_get_error_code (j);
+ ir.ec = TALER_JSON_get_error_code (j);
break;
case MHD_HTTP_UNAUTHORIZED:
/* Nothing really to verify, bank says the password is invalid; we should
pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ ir.ec = TALER_JSON_get_error_code (j);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, maybe account really does not exist.
We should pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ ir.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Nothing to verify, we used the same wire subject
+ twice? */
+ ir.ec = TALER_JSON_get_error_code (j);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
- ec = TALER_JSON_get_error_code (j);
+ ir.ec = TALER_JSON_get_error_code (j);
break;
default:
/* unexpected response code */
@@ -140,37 +144,15 @@ handle_admin_add_incoming_finished (void *cls,
"Unexpected response code %u\n",
(unsigned int) response_code);
GNUNET_break (0);
- ec = TALER_JSON_get_error_code (j);
- response_code = 0;
+ ir.ec = TALER_JSON_get_error_code (j);
break;
}
aai->cb (aai->cb_cls,
- response_code,
- ec,
- row_id,
- timestamp,
- j);
+ &ir);
TALER_BANK_admin_add_incoming_cancel (aai);
}
-/**
- * Perform a wire transfer from some account to the exchange to fill a
- * reserve. Note that this API is usually only used for testing (with
- * fakebank and our Python bank) and thus may not be accessible in a
- * production setting.
- *
- * @param ctx curl context for the event loop
- * @param auth authentication data to send to the bank
- * @param reserve_pub wire transfer subject for the transfer
- * @param amount amount that was is to be deposited
- * @param debit_account account to deposit from (payto URI, but used as 'payfrom')
- * @param res_cb the callback to call when the final result for this request is available
- * @param res_cb_cls closure for the above callback
- * @return NULL
- * if the inputs are invalid (i.e. invalid amount) or internal errors.
- * In this case, the callback is not called.
- */
struct TALER_BANK_AdminAddIncomingHandle *
TALER_BANK_admin_add_incoming (
struct GNUNET_CURL_Context *ctx,
@@ -185,13 +167,28 @@ TALER_BANK_admin_add_incoming (
json_t *admin_obj;
CURL *eh;
- admin_obj = json_pack ("{s:o, s:o, s:s}",
- "reserve_pub",
- GNUNET_JSON_from_data_auto (reserve_pub),
- "amount",
- TALER_JSON_from_amount (amount),
- "debit_account",
- debit_account);
+ if (NULL == debit_account)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (NULL == reserve_pub)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (NULL == amount)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ admin_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ reserve_pub),
+ TALER_JSON_pack_amount ("amount",
+ amount),
+ GNUNET_JSON_pack_string ("debit_account",
+ debit_account));
if (NULL == admin_obj)
{
GNUNET_break (0);
@@ -207,16 +204,16 @@ TALER_BANK_admin_add_incoming (
{
GNUNET_free (aai);
json_decref (admin_obj);
- GNUNET_break (0);
return NULL;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Requesting administrative transaction at `%s' for reserve %s\n",
aai->request_url,
TALER_B2S (reserve_pub));
- aai->post_ctx.headers = curl_slist_append
- (aai->post_ctx.headers,
- "Content-Type: application/json");
+ aai->post_ctx.headers
+ = curl_slist_append (
+ aai->post_ctx.headers,
+ "Content-Type: application/json");
eh = curl_easy_init ();
if ( (NULL == eh) ||
@@ -250,12 +247,6 @@ TALER_BANK_admin_add_incoming (
}
-/**
- * Cancel an add incoming. This function cannot be used on a request
- * handle if a response is already served for it.
- *
- * @param aai the admin add incoming request handle
- */
void
TALER_BANK_admin_add_incoming_cancel (
struct TALER_BANK_AdminAddIncomingHandle *aai)
diff --git a/src/bank-lib/bank_api_common.c b/src/bank-lib/bank_api_common.c
index abdeee31a..2c47429ad 100644
--- a/src/bank-lib/bank_api_common.c
+++ b/src/bank-lib/bank_api_common.c
@@ -23,20 +23,11 @@
#include "bank_api_common.h"
-/**
- * Set authentication data in @a easy from @a auth.
- * The API currently specifies the use of HTTP basic
- * authentication.
- *
- * @param easy curl handle to setup for authentication
- * @param auth authentication data to use
- * @return #GNUNET_OK in success
- */
-int
+enum GNUNET_GenericReturnValue
TALER_BANK_setup_auth_ (CURL *easy,
const struct TALER_BANK_AuthenticationData *auth)
{
- int ret;
+ enum GNUNET_GenericReturnValue ret;
ret = GNUNET_OK;
switch (auth->method)
diff --git a/src/bank-lib/bank_api_common.h b/src/bank-lib/bank_api_common.h
index ac059e9ed..e288a7e6f 100644
--- a/src/bank-lib/bank_api_common.h
+++ b/src/bank-lib/bank_api_common.h
@@ -36,7 +36,7 @@
* @param auth authentication data to use
* @return #GNUNET_OK in success
*/
-int
+enum GNUNET_GenericReturnValue
TALER_BANK_setup_auth_ (CURL *easy,
const struct TALER_BANK_AuthenticationData *auth);
diff --git a/src/bank-lib/bank_api_credit.c b/src/bank-lib/bank_api_credit.c
index 953d3d83b..124415b80 100644
--- a/src/bank-lib/bank_api_credit.c
+++ b/src/bank-lib/bank_api_credit.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2017--2020 Taler Systems SA
+ Copyright (C) 2017--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
@@ -30,6 +30,13 @@
/**
+ * How much longer than the application-specified timeout
+ * do we wait (giving the server a chance to respond)?
+ */
+#define GRACE_PERIOD_MS 1000
+
+
+/**
* @brief A /history/incoming Handle
*/
struct TALER_BANK_CreditHistoryHandle
@@ -66,66 +73,71 @@ struct TALER_BANK_CreditHistoryHandle
* were set,
* #GNUNET_SYSERR if there was a protocol violation in @a history
*/
-static int
+static enum GNUNET_GenericReturnValue
parse_account_history (struct TALER_BANK_CreditHistoryHandle *hh,
const json_t *history)
{
- json_t *history_array;
+ struct TALER_BANK_CreditHistoryResponse chr = {
+ .http_status = MHD_HTTP_OK,
+ .ec = TALER_EC_NONE,
+ .response = history
+ };
+ const json_t *history_array;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("incoming_transactions",
+ &history_array),
+ GNUNET_JSON_spec_string ("credit_account",
+ &chr.details.ok.credit_account_uri),
+ GNUNET_JSON_spec_end ()
+ };
- if (NULL == (history_array = json_object_get (history,
- "incoming_transactions")))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (! json_is_array (history_array))
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (history,
+ spec,
+ NULL,
+ NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- for (unsigned int i = 0; i<json_array_size (history_array); i++)
{
- struct TALER_BANK_CreditDetails td;
- uint64_t row_id;
- struct GNUNET_JSON_Specification hist_spec[] = {
- TALER_JSON_spec_amount ("amount",
- &td.amount),
- GNUNET_JSON_spec_absolute_time ("date",
- &td.execution_date),
- GNUNET_JSON_spec_uint64 ("row_id",
- &row_id),
- GNUNET_JSON_spec_fixed_auto ("reserve_pub",
- &td.reserve_pub),
- GNUNET_JSON_spec_string ("debit_account",
- &td.debit_account_url),
- GNUNET_JSON_spec_string ("credit_account",
- &td.credit_account_url),
- GNUNET_JSON_spec_end ()
- };
- json_t *transaction = json_array_get (history_array,
- i);
+ size_t len = json_array_size (history_array);
+ struct TALER_BANK_CreditDetails cd[GNUNET_NZL (len)];
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- hist_spec,
- NULL, NULL))
+ GNUNET_break_op (0 != len);
+ for (size_t i = 0; i<len; i++)
{
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- hh->hcb (hh->hcb_cls,
- MHD_HTTP_OK,
- TALER_EC_NONE,
- row_id,
- &td,
- transaction))
- {
- hh->hcb = NULL;
- GNUNET_JSON_parse_free (hist_spec);
- return GNUNET_OK;
+ struct TALER_BANK_CreditDetails *td = &cd[i];
+ struct GNUNET_JSON_Specification hist_spec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &td->amount),
+ GNUNET_JSON_spec_timestamp ("date",
+ &td->execution_date),
+ GNUNET_JSON_spec_uint64 ("row_id",
+ &td->serial_id),
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &td->reserve_pub),
+ GNUNET_JSON_spec_string ("debit_account",
+ &td->debit_account_uri),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *transaction = json_array_get (history_array,
+ i);
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ hist_spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
}
- GNUNET_JSON_parse_free (hist_spec);
+ chr.details.ok.details_length = len;
+ chr.details.ok.details = cd;
+ hh->hcb (hh->hcb_cls,
+ &chr);
}
return GNUNET_OK;
}
@@ -145,101 +157,84 @@ handle_credit_history_finished (void *cls,
const void *response)
{
struct TALER_BANK_CreditHistoryHandle *hh = cls;
- const json_t *j = response;
- enum TALER_ErrorCode ec;
+ struct TALER_BANK_CreditHistoryResponse chr = {
+ .http_status = response_code,
+ .response = response
+ };
hh->job = NULL;
switch (response_code)
{
case 0:
- ec = TALER_EC_INVALID_RESPONSE;
+ chr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
parse_account_history (hh,
- j))
+ chr.response))
{
GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_INVALID_RESPONSE;
+ json_dumpf (chr.response,
+ stderr,
+ JSON_INDENT (2));
+ chr.http_status = 0;
+ chr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
- response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */
- ec = TALER_EC_NONE;
+ TALER_BANK_credit_history_cancel (hh);
+ return;
+ case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the bank is buggy
(or API version conflict); just pass JSON reply to the application */
GNUNET_break_op (0);
- ec = TALER_JSON_get_error_code (j);
+ chr.ec = TALER_JSON_get_error_code (chr.response);
break;
case MHD_HTTP_UNAUTHORIZED:
/* Nothing really to verify, bank says the HTTP Authentication
failed. May happen if HTTP authentication is used and the
user supplied a wrong username/password combination. */
- ec = TALER_JSON_get_error_code (j);
+ chr.ec = TALER_JSON_get_error_code (chr.response);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify: the bank is either unaware
of the endpoint (not a bank), or of the account.
We should pass the JSON (?) reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ chr.ec = TALER_JSON_get_error_code (chr.response);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
- ec = TALER_JSON_get_error_code (j);
+ chr.ec = TALER_JSON_get_error_code (chr.response);
break;
default:
/* unexpected response code */
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u\n",
(unsigned int) response_code);
- GNUNET_break_op (0);
- ec = TALER_JSON_get_error_code (j);
- response_code = 0;
+ chr.ec = TALER_JSON_get_error_code (chr.response);
break;
}
- if (NULL != hh->hcb)
- hh->hcb (hh->hcb_cls,
- response_code,
- ec,
- 0LLU,
- NULL,
- j);
+ hh->hcb (hh->hcb_cls,
+ &chr);
TALER_BANK_credit_history_cancel (hh);
}
-/**
- * Request the credit history of the exchange's bank account.
- *
- * @param ctx curl context for the event loop
- * @param auth authentication data to use
- * @param start_row from which row on do we want to get results,
- * use UINT64_MAX for the latest; exclusive
- * @param num_results how many results do we want;
- * negative numbers to go into the past, positive numbers
- * to go into the future starting at @a start_row;
- * must not be zero.
- * @param hres_cb the callback to call with the transaction
- * history
- * @param hres_cb_cls closure for the above callback
- * @return NULL if the inputs are invalid (i.e. zero value for
- * @e num_results). In this case, the callback is not
- * called.
- */
struct TALER_BANK_CreditHistoryHandle *
TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
const struct TALER_BANK_AuthenticationData *auth,
uint64_t start_row,
int64_t num_results,
+ struct GNUNET_TIME_Relative timeout,
TALER_BANK_CreditHistoryCallback hres_cb,
void *hres_cb_cls)
{
char url[128];
struct TALER_BANK_CreditHistoryHandle *hh;
CURL *eh;
+ unsigned long long tms;
if (0 == num_results)
{
@@ -247,20 +242,49 @@ TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
return NULL;
}
+ tms = (unsigned long long) (timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
if ( ( (UINT64_MAX == start_row) &&
(0 > num_results) ) ||
( (0 == start_row) &&
(0 < num_results) ) )
- GNUNET_snprintf (url,
- sizeof (url),
- "history/incoming?delta=%lld",
- (long long) num_results);
+ {
+ 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",
+ (long long) num_results);
+ }
else
- GNUNET_snprintf (url,
- sizeof (url),
- "history/incoming?delta=%lld&start=%llu",
- (long long) num_results,
- (unsigned long long) start_row);
+ {
+ 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",
+ (long long) num_results,
+ (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",
+ (long long) num_results,
+ (unsigned long long) start_row);
+ }
hh = GNUNET_new (struct TALER_BANK_CreditHistoryHandle);
hh->hcb = hres_cb;
hh->hcb_cls = hres_cb_cls;
@@ -292,6 +316,13 @@ TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
curl_easy_cleanup (eh);
return NULL;
}
+ if (0 != tms)
+ {
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) tms + GRACE_PERIOD_MS));
+ }
hh->job = GNUNET_CURL_job_add2 (ctx,
eh,
NULL,
@@ -301,13 +332,6 @@ TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
}
-/**
- * Cancel a history request. This function cannot be
- * used on a request handle if a response is already
- * served for it.
- *
- * @param hh the history request handle
- */
void
TALER_BANK_credit_history_cancel (struct TALER_BANK_CreditHistoryHandle *hh)
{
diff --git a/src/bank-lib/bank_api_debit.c b/src/bank-lib/bank_api_debit.c
index 33ff20ef8..58dc0a736 100644
--- a/src/bank-lib/bank_api_debit.c
+++ b/src/bank-lib/bank_api_debit.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2017--2020 Taler Systems SA
+ Copyright (C) 2017--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
@@ -30,6 +30,13 @@
/**
+ * How much longer than the application-specified timeout
+ * do we wait (giving the server a chance to respond)?
+ */
+#define GRACE_PERIOD_MS 1000
+
+
+/**
* @brief A /history/outgoing Handle
*/
struct TALER_BANK_DebitHistoryHandle
@@ -66,68 +73,73 @@ struct TALER_BANK_DebitHistoryHandle
* were set,
* #GNUNET_SYSERR if there was a protocol violation in @a history
*/
-static int
+static enum GNUNET_GenericReturnValue
parse_account_history (struct TALER_BANK_DebitHistoryHandle *hh,
const json_t *history)
{
- json_t *history_array;
+ struct TALER_BANK_DebitHistoryResponse dhr = {
+ .http_status = MHD_HTTP_OK,
+ .ec = TALER_EC_NONE,
+ .response = history
+ };
+ const json_t *history_array;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("outgoing_transactions",
+ &history_array),
+ GNUNET_JSON_spec_string ("debit_account",
+ &dhr.details.ok.debit_account_uri),
+ GNUNET_JSON_spec_end ()
+ };
- if (NULL == (history_array = json_object_get (history,
- "outgoing_transactions")))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (! json_is_array (history_array))
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (history,
+ spec,
+ NULL,
+ NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- for (unsigned int i = 0; i<json_array_size (history_array); i++)
{
- struct TALER_BANK_DebitDetails td;
- uint64_t row_id;
- struct GNUNET_JSON_Specification hist_spec[] = {
- TALER_JSON_spec_amount ("amount",
- &td.amount),
- GNUNET_JSON_spec_absolute_time ("date",
- &td.execution_date),
- GNUNET_JSON_spec_uint64 ("row_id",
- &row_id),
- GNUNET_JSON_spec_fixed_auto ("wtid",
- &td.wtid),
- GNUNET_JSON_spec_string ("credit_account",
- &td.credit_account_url),
- GNUNET_JSON_spec_string ("debit_account",
- &td.debit_account_url),
- GNUNET_JSON_spec_string ("exchange_base_url",
- &td.exchange_base_url),
- GNUNET_JSON_spec_end ()
- };
- json_t *transaction = json_array_get (history_array,
- i);
+ size_t len = json_array_size (history_array);
+ struct TALER_BANK_DebitDetails dd[GNUNET_NZL (len)];
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- hist_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- hh->hcb (hh->hcb_cls,
- MHD_HTTP_OK,
- TALER_EC_NONE,
- row_id,
- &td,
- transaction))
+ GNUNET_break_op (0 != len);
+ for (unsigned int i = 0; i<len; i++)
{
- hh->hcb = NULL;
- GNUNET_JSON_parse_free (hist_spec);
- return GNUNET_OK;
+ struct TALER_BANK_DebitDetails *td = &dd[i];
+ struct GNUNET_JSON_Specification hist_spec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &td->amount),
+ GNUNET_JSON_spec_timestamp ("date",
+ &td->execution_date),
+ GNUNET_JSON_spec_uint64 ("row_id",
+ &td->serial_id),
+ GNUNET_JSON_spec_fixed_auto ("wtid",
+ &td->wtid),
+ GNUNET_JSON_spec_string ("credit_account",
+ &td->credit_account_uri),
+ GNUNET_JSON_spec_string ("exchange_base_url",
+ &td->exchange_base_url),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *transaction = json_array_get (history_array,
+ i);
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ hist_spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
}
- GNUNET_JSON_parse_free (hist_spec);
+ dhr.details.ok.details_length = len;
+ dhr.details.ok.details = dd;
+ hh->hcb (hh->hcb_cls,
+ &dhr);
}
return GNUNET_OK;
}
@@ -147,104 +159,81 @@ handle_debit_history_finished (void *cls,
const void *response)
{
struct TALER_BANK_DebitHistoryHandle *hh = cls;
- const json_t *j = response;
- enum TALER_ErrorCode ec;
+ struct TALER_BANK_DebitHistoryResponse dhr = {
+ .http_status = response_code,
+ .response = response
+ };
hh->job = NULL;
switch (response_code)
{
case 0:
- ec = TALER_EC_INVALID_RESPONSE;
+ dhr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
parse_account_history (hh,
- j))
+ dhr.response))
{
GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_INVALID_RESPONSE;
+ dhr.http_status = 0;
+ dhr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
- response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */
- ec = TALER_EC_NONE;
- break;
+ TALER_BANK_debit_history_cancel (hh);
+ return;
case MHD_HTTP_NO_CONTENT:
- ec = TALER_EC_NONE;
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the bank is buggy
(or API version conflict); just pass JSON reply to the application */
GNUNET_break_op (0);
- ec = TALER_JSON_get_error_code (j);
+ dhr.ec = TALER_JSON_get_error_code (dhr.response);
break;
case MHD_HTTP_UNAUTHORIZED:
/* Nothing really to verify, bank says the HTTP Authentication
failed. May happen if HTTP authentication is used and the
user supplied a wrong username/password combination. */
- ec = TALER_JSON_get_error_code (j);
+ dhr.ec = TALER_JSON_get_error_code (dhr.response);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify: the bank is either unaware
of the endpoint (not a bank), or of the account.
We should pass the JSON (?) reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ dhr.ec = TALER_JSON_get_error_code (dhr.response);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
- ec = TALER_JSON_get_error_code (j);
+ dhr.ec = TALER_JSON_get_error_code (dhr.response);
break;
default:
/* unexpected response code */
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u\n",
(unsigned int) response_code);
- GNUNET_break_op (0);
- ec = TALER_JSON_get_error_code (j);
- response_code = 0;
+ dhr.ec = TALER_JSON_get_error_code (dhr.response);
break;
}
- if (NULL != hh->hcb)
- hh->hcb (hh->hcb_cls,
- response_code,
- ec,
- 0LLU,
- NULL,
- j);
+ hh->hcb (hh->hcb_cls,
+ &dhr);
TALER_BANK_debit_history_cancel (hh);
}
-/**
- * Request the debit history of the exchange's bank account.
- *
- * @param ctx curl context for the event loop
- * @param auth authentication data to use
- * @param start_row from which row on do we want to get results,
- * use UINT64_MAX for the latest; exclusive
- * @param num_results how many results do we want;
- * negative numbers to go into the past, positive numbers
- * to go into the future starting at @a start_row;
- * must not be zero.
- * @param hres_cb the callback to call with the transaction
- * history
- * @param hres_cb_cls closure for the above callback
- * @return NULL if the inputs are invalid (i.e. zero value for
- * @e num_results). In this case, the callback is not
- * called.
- */
struct TALER_BANK_DebitHistoryHandle *
TALER_BANK_debit_history (struct GNUNET_CURL_Context *ctx,
const struct TALER_BANK_AuthenticationData *auth,
uint64_t start_row,
int64_t num_results,
+ struct GNUNET_TIME_Relative timeout,
TALER_BANK_DebitHistoryCallback hres_cb,
void *hres_cb_cls)
{
char url[128];
struct TALER_BANK_DebitHistoryHandle *hh;
CURL *eh;
+ unsigned long long tms;
if (0 == num_results)
{
@@ -252,20 +241,43 @@ TALER_BANK_debit_history (struct GNUNET_CURL_Context *ctx,
return NULL;
}
+ tms = (unsigned long long) (timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
if ( ( (UINT64_MAX == start_row) &&
(0 > num_results) ) ||
( (0 == start_row) &&
(0 < num_results) ) )
- GNUNET_snprintf (url,
- sizeof (url),
- "history/outgoing?delta=%lld",
- (long long) num_results);
+ {
+ if ( (0 < num_results) &&
+ (! GNUNET_TIME_relative_is_zero (timeout)) )
+ GNUNET_snprintf (url,
+ sizeof (url),
+ "history/outgoing?delta=%lld&long_poll_ms=%llu",
+ (long long) num_results,
+ tms);
+ else
+ GNUNET_snprintf (url,
+ sizeof (url),
+ "history/outgoing?delta=%lld",
+ (long long) num_results);
+ }
else
- GNUNET_snprintf (url,
- sizeof (url),
- "history/outgoing?delta=%lld&start=%llu",
- (long long) num_results,
- (unsigned long long) start_row);
+ {
+ if ( (0 < num_results) &&
+ (! GNUNET_TIME_relative_is_zero (timeout)) )
+ GNUNET_snprintf (url,
+ sizeof (url),
+ "history/outgoing?delta=%lld&start=%llu&long_poll_ms=%llu",
+ (long long) num_results,
+ (unsigned long long) start_row,
+ tms);
+ else
+ GNUNET_snprintf (url,
+ sizeof (url),
+ "history/outgoing?delta=%lld&start=%llu",
+ (long long) num_results,
+ (unsigned long long) start_row);
+ }
hh = GNUNET_new (struct TALER_BANK_DebitHistoryHandle);
hh->hcb = hres_cb;
hh->hcb_cls = hres_cb_cls;
@@ -297,6 +309,13 @@ TALER_BANK_debit_history (struct GNUNET_CURL_Context *ctx,
curl_easy_cleanup (eh);
return NULL;
}
+ if (0 != tms)
+ {
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) tms + GRACE_PERIOD_MS));
+ }
hh->job = GNUNET_CURL_job_add2 (ctx,
eh,
NULL,
@@ -306,13 +325,6 @@ TALER_BANK_debit_history (struct GNUNET_CURL_Context *ctx,
}
-/**
- * Cancel a history request. This function cannot be
- * used on a request handle if a response is already
- * served for it.
- *
- * @param hh the history request handle
- */
void
TALER_BANK_debit_history_cancel (struct TALER_BANK_DebitHistoryHandle *hh)
{
diff --git a/src/bank-lib/bank_api_parse.c b/src/bank-lib/bank_api_parse.c
index b09e5af35..0d30e9d08 100644
--- a/src/bank-lib/bank_api_parse.c
+++ b/src/bank-lib/bank_api_parse.c
@@ -23,15 +23,7 @@
#include "taler_bank_service.h"
-/**
- * Parse configuration section with bank authentication data.
- *
- * @param cfg configuration to parse
- * @param section the section with the configuration data
- * @param[out] auth set to the configuration data found
- * @return #GNUNET_OK on success
- */
-int
+enum GNUNET_GenericReturnValue
TALER_BANK_auth_parse_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
struct TALER_BANK_AuthenticationData *auth)
@@ -122,12 +114,6 @@ TALER_BANK_auth_parse_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
}
-/**
- * Free memory inside of @a auth (but not @a auth itself).
- * Dual to #TALER_BANK_auth_parse_cfg().
- *
- * @param[in] auth authentication data to free
- */
void
TALER_BANK_auth_free (struct TALER_BANK_AuthenticationData *auth)
{
diff --git a/src/bank-lib/bank_api_transfer.c b/src/bank-lib/bank_api_transfer.c
index 0cf59602e..0748a0d7e 100644
--- a/src/bank-lib/bank_api_transfer.c
+++ b/src/bank-lib/bank_api_transfer.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015--2020 Taler Systems SA
+ Copyright (C) 2015--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
@@ -65,18 +65,7 @@ struct WirePackP
GNUNET_NETWORK_STRUCT_END
-/**
- * Prepare for execution of a wire transfer from the exchange to some
- * merchant.
- *
- * @param destination_account_payto_uri payto:// URL identifying where to send the money
- * @param amount amount to transfer, already rounded
- * @param exchange_base_url base URL of this exchange (included in subject
- * to facilitate use of tracking API by merchant backend)
- * @param wtid wire transfer identifier to use
- * @param[out] buf set to transfer data to persist, NULL on error
- * @param[out] buf_size set to number of bytes in @a buf, 0 on error
- */
+
void
TALER_BANK_prepare_transfer (
const char *destination_account_payto_uri,
@@ -91,8 +80,9 @@ TALER_BANK_prepare_transfer (
size_t u_len = strlen (exchange_base_url) + 1;
char *end;
- if ( (d_len > (size_t) UINT32_MAX) ||
- (u_len > (size_t) UINT32_MAX) )
+ if ( (d_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) ||
+ (u_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) ||
+ (d_len + u_len + sizeof (*wp) >= GNUNET_MAX_MALLOC_CHECKED) )
{
GNUNET_break (0); /* that's some long URL... */
*buf = NULL;
@@ -109,12 +99,12 @@ TALER_BANK_prepare_transfer (
wp->account_len = htonl ((uint32_t) d_len);
wp->exchange_url_len = htonl ((uint32_t) u_len);
end = (char *) &wp[1];
- memcpy (end,
- destination_account_payto_uri,
- d_len);
- memcpy (end + d_len,
- exchange_base_url,
- u_len);
+ GNUNET_memcpy (end,
+ destination_account_payto_uri,
+ d_len);
+ GNUNET_memcpy (end + d_len,
+ exchange_base_url,
+ u_len);
*buf = (char *) wp;
}
@@ -168,23 +158,24 @@ handle_transfer_finished (void *cls,
{
struct TALER_BANK_TransferHandle *th = cls;
const json_t *j = response;
- uint64_t row_id = UINT64_MAX;
- struct GNUNET_TIME_Absolute timestamp = GNUNET_TIME_UNIT_FOREVER_ABS;
- enum TALER_ErrorCode ec;
+ struct TALER_BANK_TransferResponse tr = {
+ .http_status = response_code,
+ .response = j
+ };
th->job = NULL;
switch (response_code)
{
case 0:
- ec = TALER_EC_INVALID_RESPONSE;
+ tr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_uint64 ("row_id",
- &row_id),
- GNUNET_JSON_spec_absolute_time ("timestamp",
- &timestamp),
+ &tr.details.ok.row_id),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &tr.details.ok.timestamp),
GNUNET_JSON_spec_end ()
};
@@ -194,39 +185,38 @@ handle_transfer_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_INVALID_RESPONSE;
+ tr.http_status = 0;
+ tr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
- ec = TALER_EC_NONE;
}
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the bank is buggy
(or API version conflict); just pass JSON reply to the application */
GNUNET_break_op (0);
- ec = TALER_JSON_get_error_code (j);
+ tr.ec = TALER_JSON_get_error_code (j);
break;
case MHD_HTTP_UNAUTHORIZED:
/* Nothing really to verify, bank says our credentials are
invalid. We should pass the JSON reply to the application. */
- ec = TALER_JSON_get_error_code (j);
+ tr.ec = TALER_JSON_get_error_code (j);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, endpoint wrong -- could be user unknown */
- ec = TALER_JSON_get_error_code (j);
+ tr.ec = TALER_JSON_get_error_code (j);
break;
case MHD_HTTP_CONFLICT:
/* Nothing really to verify. Server says we used the same transfer request
UID before, but with different details. Should not happen if the user
properly used #TALER_BANK_prepare_transfer() and our PRNG is not
broken... */
- ec = TALER_JSON_get_error_code (j);
+ tr.ec = TALER_JSON_get_error_code (j);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
- ec = TALER_JSON_get_error_code (j);
+ tr.ec = TALER_JSON_get_error_code (j);
break;
default:
/* unexpected response code */
@@ -234,30 +224,15 @@ handle_transfer_finished (void *cls,
"Unexpected response code %u\n",
(unsigned int) response_code);
GNUNET_break (0);
- ec = TALER_JSON_get_error_code (j);
- response_code = 0;
+ tr.ec = TALER_JSON_get_error_code (j);
break;
}
th->cb (th->cb_cls,
- response_code,
- ec,
- row_id,
- timestamp);
+ &tr);
TALER_BANK_transfer_cancel (th);
}
-/**
- * Execute a wire transfer.
- *
- * @param ctx curl context for our event loop
- * @param auth authentication data to authenticate with the bank
- * @param buf buffer with the prepared execution details
- * @param buf_size number of bytes in @a buf
- * @param cc function to call upon success
- * @param cc_cls closure for @a cc
- * @return NULL on error
- */
struct TALER_BANK_TransferHandle *
TALER_BANK_transfer (
struct GNUNET_CURL_Context *ctx,
@@ -319,13 +294,17 @@ TALER_BANK_transfer (
GNUNET_break (0);
return NULL;
}
- transfer_obj = json_pack ("{s:o, s:o, s:s, s:o, s:s}",
- "request_uid", GNUNET_JSON_from_data_auto (
- &wp->request_uid),
- "amount", TALER_JSON_from_amount (&amount),
- "exchange_base_url", exchange_base_url,
- "wtid", GNUNET_JSON_from_data_auto (&wp->wtid),
- "credit_account", destination_account_uri);
+ transfer_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("request_uid",
+ &wp->request_uid),
+ TALER_JSON_pack_amount ("amount",
+ &amount),
+ GNUNET_JSON_pack_string ("exchange_base_url",
+ exchange_base_url),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &wp->wtid),
+ GNUNET_JSON_pack_string ("credit_account",
+ destination_account_uri));
if (NULL == transfer_obj)
{
GNUNET_break (0);
@@ -353,7 +332,6 @@ TALER_BANK_transfer (
return NULL;
}
json_decref (transfer_obj);
-
th->job = GNUNET_CURL_job_add2 (ctx,
eh,
th->post_ctx.headers,
@@ -363,21 +341,6 @@ TALER_BANK_transfer (
}
-/**
- * Abort execution of a wire transfer. For example, because we are shutting
- * down. Note that if an execution is aborted, it may or may not still
- * succeed.
- *
- * The caller MUST run #TALER_BANK_transfer() again for the same request as
- * soon as possible, to ensure that the request either ultimately succeeds or
- * ultimately fails. Until this has been done, the transaction is in limbo
- * (i.e. may or may not have been committed).
- *
- * This function cannot be used on a request handle if a response is already
- * served for it.
- *
- * @param th the wire transfer request handle
- */
void
TALER_BANK_transfer_cancel (struct TALER_BANK_TransferHandle *th)
{
diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c
index 9c4aeb6e9..3a004dc80 100644
--- a/src/bank-lib/fakebank.c
+++ b/src/bank-lib/fakebank.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2016-2020 Taler Systems SA
+ (C) 2016-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
@@ -22,527 +22,19 @@
* @author Christian Grothoff <christian@grothoff.org>
*/
#include "platform.h"
+#include <pthread.h>
+#include <poll.h>
+#ifdef __linux__
+#include <sys/eventfd.h>
+#endif
#include "taler_fakebank_lib.h"
#include "taler_bank_service.h"
#include "taler_mhd_lib.h"
-
-/**
- * Maximum POST request size (for /admin/add-incoming)
- */
-#define REQUEST_BUFFER_MAX (4 * 1024)
-
-
-/**
- * Details about a transcation we (as the simulated bank) received.
- */
-struct Transaction
-{
- /**
- * We store transactions in a DLL.
- */
- struct Transaction *next;
-
- /**
- * We store transactions in a DLL.
- */
- struct Transaction *prev;
-
- /**
- * Amount to be transferred.
- */
- struct TALER_Amount amount;
-
- /**
- * Account to debit (string, not payto!)
- */
- char *debit_account;
-
- /**
- * Account to credit (string, not payto!)
- */
- char *credit_account;
-
- /**
- * Random unique identifier for the request.
- */
- struct GNUNET_HashCode request_uid;
-
- /**
- * What does the @e subject contain?
- */
- enum
- {
- /**
- * Transfer TO the exchange.
- */
- T_CREDIT,
-
- /**
- * Transfer FROM the exchange.
- */
- T_DEBIT
- } type;
-
- /**
- * Wire transfer subject.
- */
- union
- {
-
- /**
- * Used if @e type is T_DEBIT.
- */
- struct
- {
-
- /**
- * Subject of the transfer.
- */
- struct TALER_WireTransferIdentifierRawP wtid;
-
- /**
- * Base URL of the exchange.
- */
- char *exchange_base_url;
-
- } debit;
-
- /**
- * Used if @e type is T_CREDIT.
- */
- struct
- {
-
- /**
- * Reserve public key of the credit operation.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- } credit;
-
- } subject;
-
- /**
- * When did the transaction happen?
- */
- struct GNUNET_TIME_Absolute date;
-
- /**
- * Number of this transaction.
- */
- uint64_t row_id;
-
- /**
- * Has this transaction been subjected to #TALER_FAKEBANK_check_credit()
- * or #TALER_FAKEBANK_check_debit()
- * and should thus no longer be counted in
- * #TALER_FAKEBANK_check_empty()?
- */
- int checked;
-};
-
-
-/**
- * Handle for the fake bank.
- */
-struct TALER_FAKEBANK_Handle
-{
- /**
- * We store transactions in a DLL.
- */
- struct Transaction *transactions_head;
-
- /**
- * We store transactions in a DLL.
- */
- struct Transaction *transactions_tail;
-
- /**
- * HTTP server we run to pretend to be the "test" bank.
- */
- struct MHD_Daemon *mhd_bank;
-
- /**
- * Task running HTTP server for the "test" bank.
- */
- struct GNUNET_SCHEDULER_Task *mhd_task;
-
- /**
- * Number of transactions.
- */
- uint64_t serial_counter;
-
- /**
- * Currency used by the fakebank.
- */
- char *currency;
-
- /**
- * BaseURL of the fakebank.
- */
- char *my_baseurl;
-
- /**
- * Our port number.
- */
- uint16_t port;
-
-#if EPOLL_SUPPORT
- /**
- * Boxed @e mhd_fd.
- */
- struct GNUNET_NETWORK_Handle *mhd_rfd;
-
- /**
- * File descriptor to use to wait for MHD.
- */
- int mhd_fd;
-#endif
-};
-
-
-/**
- * Generate log messages for failed check operation.
- *
- * @param h handle to output transaction log for
- */
-static void
-check_log (struct TALER_FAKEBANK_Handle *h)
-{
- for (struct Transaction *t = h->transactions_head; NULL != t; t = t->next)
- {
- if (GNUNET_YES == t->checked)
- continue;
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "%s -> %s (%s) %s (%s)\n",
- t->debit_account,
- t->credit_account,
- TALER_amount2s (&t->amount),
- (T_DEBIT == t->type)
- ? t->subject.debit.exchange_base_url
- : TALER_B2S (&t->subject.credit.reserve_pub),
- (T_DEBIT == t->type) ? "DEBIT" : "CREDIT");
- }
-}
-
-
-/**
- * Check that the @a want_amount was transferred from the @a
- * want_debit to the @a want_credit account. If so, set the @a subject
- * to the transfer identifier and remove the transaction from the
- * list. If the transaction was not recorded, return #GNUNET_SYSERR.
- *
- * @param h bank instance
- * @param want_amount transfer amount desired
- * @param want_debit account that should have been debited
- * @param want_credit account that should have been credited
- * @param exchange_base_url expected base URL of the exchange,
- * i.e. "https://example.com/"; may include a port
- * @param[out] wtid set to the wire transfer identifier
- * @return #GNUNET_OK on success
- */
-int
-TALER_FAKEBANK_check_debit (struct TALER_FAKEBANK_Handle *h,
- const struct TALER_Amount *want_amount,
- const char *want_debit,
- const char *want_credit,
- const char *exchange_base_url,
- struct TALER_WireTransferIdentifierRawP *wtid)
-{
- GNUNET_assert (0 == strcasecmp (want_amount->currency,
- h->currency));
- for (struct Transaction *t = h->transactions_head; NULL != t; t = t->next)
- {
- if ( (0 == strcasecmp (want_debit,
- t->debit_account)) &&
- (0 == strcasecmp (want_credit,
- t->credit_account)) &&
- (0 == TALER_amount_cmp (want_amount,
- &t->amount)) &&
- (GNUNET_NO == t->checked) &&
- (T_DEBIT == t->type) &&
- (0 == strcasecmp (exchange_base_url,
- t->subject.debit.exchange_base_url)) )
- {
- *wtid = t->subject.debit.wtid;
- t->checked = GNUNET_YES;
- return GNUNET_OK;
- }
- }
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Did not find matching transaction! I have:\n");
- check_log (h);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "I wanted: %s->%s (%s) from exchange %s (DEBIT)\n",
- want_debit,
- want_credit,
- TALER_amount2s (want_amount),
- exchange_base_url);
- return GNUNET_SYSERR;
-}
-
-
-/**
- * Check that the @a want_amount was transferred from the @a want_debit to the
- * @a want_credit account with the @a subject. If so, remove the transaction
- * from the list. If the transaction was not recorded, return #GNUNET_SYSERR.
- *
- * @param h bank instance
- * @param want_amount transfer amount desired
- * @param want_debit account that should have been debited
- * @param want_credit account that should have been credited
- * @param reserve_pub reserve public key expected in wire subject
- * @return #GNUNET_OK on success
- */
-int
-TALER_FAKEBANK_check_credit (struct TALER_FAKEBANK_Handle *h,
- const struct TALER_Amount *want_amount,
- const char *want_debit,
- const char *want_credit,
- const struct TALER_ReservePublicKeyP *reserve_pub)
-{
- GNUNET_assert (0 == strcasecmp (want_amount->currency,
- h->currency));
- for (struct Transaction *t = h->transactions_head; NULL != t; t = t->next)
- {
- if ( (0 == strcasecmp (want_debit,
- t->debit_account)) &&
- (0 == strcasecmp (want_credit,
- t->credit_account)) &&
- (0 == TALER_amount_cmp (want_amount,
- &t->amount)) &&
- (GNUNET_NO == t->checked) &&
- (T_CREDIT == t->type) &&
- (0 == GNUNET_memcmp (reserve_pub,
- &t->subject.credit.reserve_pub)) )
- {
- t->checked = GNUNET_YES;
- return GNUNET_OK;
- }
- }
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Did not find matching transaction!\nI have:\n");
- check_log (h);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "I wanted:\n%s -> %s (%s) with subject %s (CREDIT)\n",
- want_debit,
- want_credit,
- TALER_amount2s (want_amount),
- TALER_B2S (reserve_pub));
- return GNUNET_SYSERR;
-}
-
-
-/**
- * Tell the fakebank to create another wire transfer *from* an exchange.
- *
- * @param h fake bank handle
- * @param debit_account account to debit
- * @param credit_account account to credit
- * @param amount amount to transfer
- * @param subject wire transfer subject to use
- * @param exchange_base_url exchange URL
- * @param request_uid unique number to make the request unique, or NULL to create one
- * @param[out] ret_row_id pointer to store the row ID of this transaction
- * @return #GNUNET_YES if the transfer was successful,
- * #GNUNET_SYSERR if the request_uid was reused for a different transfer
- */
-int
-TALER_FAKEBANK_make_transfer (
- struct TALER_FAKEBANK_Handle *h,
- const char *debit_account,
- const char *credit_account,
- const struct TALER_Amount *amount,
- const struct TALER_WireTransferIdentifierRawP *subject,
- const char *exchange_base_url,
- const struct GNUNET_HashCode *request_uid,
- uint64_t *ret_row_id)
-{
- struct Transaction *t;
-
- GNUNET_assert (0 == strcasecmp (amount->currency,
- h->currency));
- GNUNET_break (0 != strncasecmp ("payto://",
- debit_account,
- strlen ("payto://")));
- GNUNET_break (0 != strncasecmp ("payto://",
- credit_account,
- strlen ("payto://")));
- if (NULL != request_uid)
- {
- for (struct Transaction *t = h->transactions_head; NULL != t; t = t->next)
- {
- if (0 != GNUNET_memcmp (request_uid, &t->request_uid))
- continue;
- if ( (0 != strcasecmp (debit_account,
- t->debit_account)) ||
- (0 != strcasecmp (credit_account,
- t->credit_account)) ||
- (0 != TALER_amount_cmp (amount,
- &t->amount)) ||
- (T_DEBIT != t->type) ||
- (0 != GNUNET_memcmp (subject,
- &t->subject.debit.wtid)) )
- {
- /* Transaction exists, but with different details. */
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- *ret_row_id = t->row_id;
- return GNUNET_OK;
- }
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Making transfer from %s to %s over %s and subject %s; for exchange: %s\n",
- debit_account,
- credit_account,
- TALER_amount2s (amount),
- TALER_B2S (subject),
- exchange_base_url);
- t = GNUNET_new (struct Transaction);
- t->debit_account = GNUNET_strdup (debit_account);
- t->credit_account = GNUNET_strdup (credit_account);
- t->amount = *amount;
- t->row_id = ++h->serial_counter;
- t->date = GNUNET_TIME_absolute_get ();
- t->type = T_DEBIT;
- t->subject.debit.exchange_base_url = GNUNET_strdup (exchange_base_url);
- t->subject.debit.wtid = *subject;
- if (NULL == request_uid)
- GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE,
- &t->request_uid);
- else
- t->request_uid = *request_uid;
- GNUNET_TIME_round_abs (&t->date);
- GNUNET_CONTAINER_DLL_insert_tail (h->transactions_head,
- h->transactions_tail,
- t);
- *ret_row_id = t->row_id;
- return GNUNET_OK;
-}
-
-
-/**
- * Tell the fakebank to create another wire transfer *to* an exchange.
- *
- * @param h fake bank handle
- * @param debit_account account to debit
- * @param credit_account account to credit
- * @param amount amount to transfer
- * @param reserve_pub reserve public key to use in subject
- * @return serial_id of the transfer
- */
-uint64_t
-TALER_FAKEBANK_make_admin_transfer (
- struct TALER_FAKEBANK_Handle *h,
- const char *debit_account,
- const char *credit_account,
- const struct TALER_Amount *amount,
- const struct TALER_ReservePublicKeyP *reserve_pub)
-{
- struct Transaction *t;
-
- GNUNET_assert (0 == strcasecmp (amount->currency,
- h->currency));
- GNUNET_assert (NULL != debit_account);
- GNUNET_assert (NULL != credit_account);
- GNUNET_break (0 != strncasecmp ("payto://",
- debit_account,
- strlen ("payto://")));
- GNUNET_break (0 != strncasecmp ("payto://",
- credit_account,
- strlen ("payto://")));
- t = GNUNET_new (struct Transaction);
- t->debit_account = GNUNET_strdup (debit_account);
- t->credit_account = GNUNET_strdup (credit_account);
- t->amount = *amount;
- t->row_id = ++h->serial_counter;
- t->date = GNUNET_TIME_absolute_get ();
- t->type = T_CREDIT;
- t->subject.credit.reserve_pub = *reserve_pub;
- GNUNET_TIME_round_abs (&t->date);
- GNUNET_CONTAINER_DLL_insert_tail (h->transactions_head,
- h->transactions_tail,
- t);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Making transfer from %s to %s over %s and subject %s at row %llu\n",
- debit_account,
- credit_account,
- TALER_amount2s (amount),
- TALER_B2S (reserve_pub),
- (unsigned long long) t->row_id);
- return t->row_id;
-}
-
-
-/**
- * Check that no wire transfers were ordered (or at least none
- * that have not been taken care of via #TALER_FAKEBANK_check_credit()
- * or #TALER_FAKEBANK_check_debit()).
- * If any transactions are onrecord, return #GNUNET_SYSERR.
- *
- * @param h bank instance
- * @return #GNUNET_OK on success
- */
-int
-TALER_FAKEBANK_check_empty (struct TALER_FAKEBANK_Handle *h)
-{
- struct Transaction *t;
-
- t = h->transactions_head;
- while (NULL != t)
- {
- if (GNUNET_YES != t->checked)
- break;
- t = t->next;
- }
- if (NULL == t)
- return GNUNET_OK;
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Expected empty transaction set, but I have:\n");
- check_log (h);
- return GNUNET_SYSERR;
-}
-
-
-/**
- * Stop running the fake bank.
- *
- * @param h bank to stop
- */
-void
-TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h)
-{
- struct Transaction *t;
-
- while (NULL != (t = h->transactions_head))
- {
- GNUNET_CONTAINER_DLL_remove (h->transactions_head,
- h->transactions_tail,
- t);
- GNUNET_free (t->debit_account);
- GNUNET_free (t->credit_account);
- if (T_DEBIT == t->type)
- GNUNET_free (t->subject.debit.exchange_base_url);
- GNUNET_free (t);
- }
- if (NULL != h->mhd_task)
- {
- GNUNET_SCHEDULER_cancel (h->mhd_task);
- h->mhd_task = NULL;
- }
-#if EPOLL_SUPPORT
- GNUNET_NETWORK_socket_free_memory_only_ (h->mhd_rfd);
-#endif
- if (NULL != h->mhd_bank)
- {
- MHD_stop_daemon (h->mhd_bank);
- h->mhd_bank = NULL;
- }
- GNUNET_free (h->my_baseurl);
- GNUNET_free (h->currency);
- GNUNET_free (h);
-}
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_tbi.h"
/**
@@ -551,9 +43,9 @@ TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h)
* @a con_cls that might still need to be cleaned up. Call the
* respective function to free the memory.
*
- * @param cls client-defined closure
+ * @param cls a `struct TALER_FAKEBANK_Handle *`
* @param connection connection handle
- * @param con_cls value as set by the last call to
+ * @param con_cls a `struct ConnectionContext *`
* the #MHD_AccessHandlerCallback
* @param toe reason for request termination
* @see #MHD_OPTION_NOTIFY_COMPLETED
@@ -565,704 +57,16 @@ handle_mhd_completion_callback (void *cls,
void **con_cls,
enum MHD_RequestTerminationCode toe)
{
- /* struct TALER_FAKEBANK_Handle *h = cls; */
- (void) cls;
- (void) connection;
- (void) toe;
- GNUNET_JSON_post_parser_cleanup (*con_cls);
- *con_cls = NULL;
-}
-
-
-/**
- * Handle incoming HTTP request for /admin/add/incoming.
- *
- * @param h the fakebank handle
- * @param connection the connection
- * @param account account into which to deposit the funds (credit)
- * @param upload_data request data
- * @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure for request (a `struct Buffer *`)
- * @return MHD result code
- */
-static int
-handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h,
- struct MHD_Connection *connection,
- const char *account,
- const char *upload_data,
- size_t *upload_data_size,
- void **con_cls)
-{
- enum GNUNET_JSON_PostResult pr;
- json_t *json;
- uint64_t row_id;
-
- pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
- connection,
- con_cls,
- upload_data,
- upload_data_size,
- &json);
- switch (pr)
- {
- case GNUNET_JSON_PR_OUT_OF_MEMORY:
- GNUNET_break (0);
- return MHD_NO;
- case GNUNET_JSON_PR_CONTINUE:
- return MHD_YES;
- case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
- GNUNET_break (0);
- return MHD_NO;
- case GNUNET_JSON_PR_JSON_INVALID:
- GNUNET_break (0);
- return MHD_NO;
- case GNUNET_JSON_PR_SUCCESS:
- break;
- }
- {
- const char *debit_account;
- struct TALER_Amount amount;
- struct TALER_ReservePublicKeyP reserve_pub;
- char *debit;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_pub", &reserve_pub),
- GNUNET_JSON_spec_string ("debit_account", &debit_account),
- TALER_JSON_spec_amount ("amount", &amount),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break (0);
- json_decref (json);
- /* We're fakebank, no need for nice error handling */
- return MHD_NO;
- }
- debit = TALER_xtalerbank_account_from_payto (debit_account);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Receiving incoming wire transfer: %s->%s, subject: %s, amount: %s\n",
- debit,
- account,
- TALER_B2S (&reserve_pub),
- TALER_amount2s (&amount));
- row_id = TALER_FAKEBANK_make_admin_transfer (h,
- debit,
- account,
- &amount,
- &reserve_pub);
- GNUNET_free (debit);
- }
- json_decref (json);
-
- /* Finally build response object */
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:I, s:o}",
- "row_id",
- (json_int_t) row_id,
- "timestamp",
- GNUNET_JSON_from_time_abs (
- h->transactions_tail->date));
-}
-
-
-/**
- * Handle incoming HTTP request for /transfer.
- *
- * @param h the fakebank handle
- * @param connection the connection
- * @param account account making the transfer
- * @param upload_data request data
- * @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure for request (a `struct Buffer *`)
- * @return MHD result code
- */
-static int
-handle_transfer (struct TALER_FAKEBANK_Handle *h,
- struct MHD_Connection *connection,
- const char *account,
- const char *upload_data,
- size_t *upload_data_size,
- void **con_cls)
-{
- enum GNUNET_JSON_PostResult pr;
- json_t *json;
- uint64_t row_id;
-
- pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
- connection,
- con_cls,
- upload_data,
- upload_data_size,
- &json);
- switch (pr)
- {
- case GNUNET_JSON_PR_OUT_OF_MEMORY:
- GNUNET_break (0);
- return MHD_NO;
- case GNUNET_JSON_PR_CONTINUE:
- return MHD_YES;
- case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
- GNUNET_break (0);
- return MHD_NO;
- case GNUNET_JSON_PR_JSON_INVALID:
- GNUNET_break (0);
- return MHD_NO;
- case GNUNET_JSON_PR_SUCCESS:
- break;
- }
- {
- struct GNUNET_HashCode uuid;
- struct TALER_WireTransferIdentifierRawP wtid;
- const char *credit_account;
- char *credit;
- const char *base_url;
- struct TALER_Amount amount;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("request_uid",
- &uuid),
- TALER_JSON_spec_amount ("amount",
- &amount),
- GNUNET_JSON_spec_string ("exchange_base_url",
- &base_url),
- GNUNET_JSON_spec_fixed_auto ("wtid",
- &wtid),
- GNUNET_JSON_spec_string ("credit_account",
- &credit_account),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break (0);
- json_decref (json);
- /* We are fakebank, no need for nice error handling */
- return MHD_NO;
- }
- {
- int ret;
-
- credit = TALER_xtalerbank_account_from_payto (credit_account);
- ret = TALER_FAKEBANK_make_transfer (h,
- account,
- credit,
- &amount,
- &wtid,
- base_url,
- &uuid,
- &row_id);
- if (GNUNET_OK != ret)
- {
- GNUNET_break (0);
- json_decref (json);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED,
- "transfer request UID was reused");
-
-
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Receiving incoming wire transfer: %s->%s, subject: %s, amount: %s, from %s\n",
- account,
- credit,
- TALER_B2S (&wtid),
- TALER_amount2s (&amount),
- base_url);
- GNUNET_free (credit);
- }
- }
- json_decref (json);
-
- /* Finally build response object */
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:I, s:o}",
- "row_id",
- (json_int_t) row_id,
- /* dummy timestamp */
- "timestamp", GNUNET_JSON_from_time_abs (
- GNUNET_TIME_UNIT_ZERO_ABS));
-}
-
-
-/**
- * Handle incoming HTTP request for / (home page).
- *
- * @param h the fakebank handle
- * @param connection the connection
- * @param con_cls place to store state, not used
- * @return MHD result code
- */
-static int
-handle_home_page (struct TALER_FAKEBANK_Handle *h,
- struct MHD_Connection *connection,
- void **con_cls)
-{
- int ret;
- struct MHD_Response *resp;
-#define HELLOMSG "Hello, Fakebank!"
+ struct TALER_FAKEBANK_Handle *h = cls;
+ struct ConnectionContext *cc = *con_cls;
(void) h;
- (void) con_cls;
- resp = MHD_create_response_from_buffer
- (strlen (HELLOMSG),
- HELLOMSG,
- MHD_RESPMEM_MUST_COPY);
-
- ret = MHD_queue_response (connection,
- MHD_HTTP_OK,
- resp);
-
- MHD_destroy_response (resp);
- return ret;
-}
-
-
-/**
- * This is the "base" structure for both the /history and the
- * /history-range API calls.
- */
-struct HistoryArgs
-{
-
- /**
- * Bank account number of the requesting client.
- */
- uint64_t account_number;
-
- /**
- * Index of the starting transaction.
- */
- uint64_t start_idx;
-
- /**
- * Requested number of results and order
- * (positive: ascending, negative: descending)
- */
- int64_t delta;
-
- /**
- * Timeout for long polling.
- */
- struct GNUNET_TIME_Relative lp_timeout;
-
- /**
- * #GNUNET_YES if starting point was given.
- */
- int have_start;
-
-};
-
-
-/**
- * Parse URL history arguments, of _both_ APIs:
- * /history/incoming and /history/outgoing.
- *
- * @param connection MHD connection.
- * @param[out] ha will contain the parsed values.
- * @return #GNUNET_OK only if the parsing succeeds.
- */
-static int
-parse_history_common_args (struct MHD_Connection *connection,
- struct HistoryArgs *ha)
-{
- const char *start;
- const char *delta;
- const char *long_poll_ms;
- unsigned long long lp_timeout;
- unsigned long long sval;
- long long d;
-
- start = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "start");
- ha->have_start = (NULL != start);
- delta = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "delta");
- long_poll_ms = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "long_poll_ms");
- lp_timeout = 0;
- if ( (NULL == delta) ||
- (1 != sscanf (delta,
- "%lld",
- &d)) ||
- ( (NULL != long_poll_ms) &&
- (1 != sscanf (long_poll_ms,
- "%llu",
- &lp_timeout)) ) ||
- ( (NULL != start) &&
- (1 != sscanf (start,
- "%llu",
- &sval)) ) )
- {
- /* Fail if one of the above failed. */
- /* Invalid request, given that this is fakebank we impolitely
- * just kill the connection instead of returning a nice error.
- */
- GNUNET_break (0);
- return GNUNET_NO;
- }
- if (NULL == start)
- ha->start_idx = (d > 0) ? 0 : UINT64_MAX;
- else
- ha->start_idx = (uint64_t) sval;
- ha->delta = (int64_t) d;
- if (0 == ha->delta)
- {
- GNUNET_break (0);
- return GNUNET_NO;
- }
- ha->lp_timeout
- = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
- lp_timeout);
- return GNUNET_OK;
-}
-
-
-/**
- * Handle incoming HTTP request for /history/outgoing
- *
- * @param h the fakebank handle
- * @param connection the connection
- * @param account which account the request is about
- * @return MHD result code
- */
-static int
-handle_debit_history (struct TALER_FAKEBANK_Handle *h,
- struct MHD_Connection *connection,
- const char *account)
-{
- struct HistoryArgs ha;
- const struct Transaction *pos;
- json_t *history;
-
- if (GNUNET_OK !=
- parse_history_common_args (connection,
- &ha))
- {
- GNUNET_break (0);
- return MHD_NO;
- }
-
- if (! ha.have_start)
- {
- pos = (0 > ha.delta)
- ? h->transactions_tail
- : h->transactions_head;
- }
- else if (NULL != h->transactions_head)
- {
- for (pos = h->transactions_head;
- NULL != pos;
- pos = pos->next)
- if (pos->row_id == ha.start_idx)
- break;
- if (NULL == pos)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid start specified, transaction %llu not known!\n",
- (unsigned long long) ha.start_idx);
- return MHD_NO;
- }
- /* range is exclusive, skip the matching entry */
- if (0 > ha.delta)
- pos = pos->prev;
- else
- pos = pos->next;
- }
- else
- {
- /* list is empty */
- pos = NULL;
- }
- history = json_array ();
- while ( (0 != ha.delta) &&
- (NULL != pos) )
- {
- if ( (0 == strcasecmp (pos->debit_account,
- account)) &&
- (T_DEBIT == pos->type) )
- {
- json_t *trans;
- char *credit_payto;
- char *debit_payto;
-
- GNUNET_asprintf (&credit_payto,
- "payto://x-taler-bank/localhost/%s",
- pos->credit_account);
-
- GNUNET_asprintf (&debit_payto,
- "payto://x-taler-bank/localhost/%s",
- pos->debit_account);
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "made credit_payto (%s) from credit_account (%s) within fakebank\n",
- credit_payto,
- pos->credit_account);
-
- trans = json_pack
- ("{s:I, s:o, s:o, s:s, s:s, s:s, s:o}",
- "row_id", (json_int_t) pos->row_id,
- "date", GNUNET_JSON_from_time_abs (pos->date),
- "amount", TALER_JSON_from_amount (&pos->amount),
- "credit_account", credit_payto,
- "debit_account", debit_payto,
- "exchange_base_url",
- pos->subject.debit.exchange_base_url,
- "wtid", GNUNET_JSON_from_data_auto (
- &pos->subject.debit.wtid));
- GNUNET_free (credit_payto);
- GNUNET_free (debit_payto);
- GNUNET_assert (0 ==
- json_array_append_new (history,
- trans));
- if (ha.delta > 0)
- ha.delta--;
- else
- ha.delta++;
- }
- if (0 > ha.delta)
- pos = pos->prev;
- if (0 < ha.delta)
- pos = pos->next;
- }
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:o}",
- "outgoing_transactions",
- history);
-}
-
-
-/**
- * Handle incoming HTTP request for /history/incoming
- *
- * @param h the fakebank handle
- * @param connection the connection
- * @param account which account the request is about
- * @return MHD result code
- */
-static int
-handle_credit_history (struct TALER_FAKEBANK_Handle *h,
- struct MHD_Connection *connection,
- const char *account)
-{
- struct HistoryArgs ha;
- const struct Transaction *pos;
- json_t *history;
-
- if (GNUNET_OK !=
- parse_history_common_args (connection,
- &ha))
- {
- GNUNET_break (0);
- return MHD_NO;
- }
- if (! ha.have_start)
- {
- pos = (0 > ha.delta)
- ? h->transactions_tail
- : h->transactions_head;
- }
- else if (NULL != h->transactions_head)
- {
- for (pos = h->transactions_head;
- NULL != pos;
- pos = pos->next)
- {
- if (pos->row_id == ha.start_idx)
- break;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Skipping transaction %s->%s (%s) at %llu (looking for start index %llu)\n",
- pos->debit_account,
- pos->credit_account,
- TALER_B2S (&pos->subject.credit.reserve_pub),
- (unsigned long long) pos->row_id,
- (unsigned long long) ha.start_idx);
- }
- if (NULL == pos)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid start specified, transaction %llu not known!\n",
- (unsigned long long) ha.start_idx);
- return MHD_NO;
- }
- /* range is exclusive, skip the matching entry */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Skipping transaction %s->%s (%s) (start index %llu is exclusive)\n",
- pos->debit_account,
- pos->credit_account,
- TALER_B2S (&pos->subject.credit.reserve_pub),
- (unsigned long long) ha.start_idx);
- if (0 > ha.delta)
- pos = pos->prev;
- else
- pos = pos->next;
- }
- else
- {
- /* list is empty */
- pos = NULL;
- }
- history = json_array ();
- while ( (0 != ha.delta) &&
- (NULL != pos) )
- {
- if ( (0 == strcasecmp (pos->credit_account,
- account)) &&
- (T_CREDIT == pos->type) )
- {
- json_t *trans;
- char *credit_payto;
- char *debit_payto;
-
- GNUNET_asprintf (&credit_payto,
- "payto://x-taler-bank/localhost/%s",
- pos->credit_account);
-
- GNUNET_asprintf (&debit_payto,
- "payto://x-taler-bank/localhost/%s",
- pos->debit_account);
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "made credit_payto (%s) from credit_account (%s) within fakebank\n",
- credit_payto,
- pos->credit_account);
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Returning transaction %s->%s (%s) at %llu\n",
- pos->debit_account,
- pos->credit_account,
- TALER_B2S (&pos->subject.credit.reserve_pub),
- (unsigned long long) pos->row_id);
- trans = json_pack
- ("{s:I, s:o, s:o, s:s, s:s, s:o}",
- "row_id", (json_int_t) pos->row_id,
- "date", GNUNET_JSON_from_time_abs (pos->date),
- "amount", TALER_JSON_from_amount (&pos->amount),
- "credit_account", credit_payto,
- "debit_account", debit_payto,
- "reserve_pub", GNUNET_JSON_from_data_auto (
- &pos->subject.credit.reserve_pub));
- GNUNET_free (credit_payto);
- GNUNET_free (debit_payto);
- GNUNET_assert (0 ==
- json_array_append_new (history,
- trans));
- if (ha.delta > 0)
- ha.delta--;
- else
- ha.delta++;
- }
- else if (T_CREDIT == pos->type)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Skipping transaction %s->%s (%s) at row %llu\n",
- pos->debit_account,
- pos->credit_account,
- TALER_B2S (&pos->subject.credit.reserve_pub),
- (unsigned long long) pos->row_id);
- }
- if (0 > ha.delta)
- pos = pos->prev;
- if (0 < ha.delta)
- pos = pos->next;
- }
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:o}",
- "incoming_transactions",
- history);
-}
-
-
-/**
- * Handle incoming HTTP request.
- *
- * @param h our handle
- * @param connection the connection
- * @param url the requested url
- * @param method the method (POST, GET, ...)
- * @param account which account should process the request
- * @param upload_data request data
- * @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure for request (a `struct Buffer *`)
- * @return MHD result code
- */
-static int
-serve (struct TALER_FAKEBANK_Handle *h,
- struct MHD_Connection *connection,
- const char *account,
- const char *url,
- const char *method,
- const char *upload_data,
- size_t *upload_data_size,
- void **con_cls)
-{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Fakebank, serving URL `%s' for account `%s'\n",
- url,
- account);
- if ( (0 == strcmp (url,
- "/")) &&
- (0 == strcasecmp (method,
- MHD_HTTP_METHOD_GET)) )
- return handle_home_page (h,
- connection,
- con_cls);
- if ( (0 == strcmp (url,
- "/admin/add-incoming")) &&
- (0 == strcasecmp (method,
- MHD_HTTP_METHOD_POST)) )
- return handle_admin_add_incoming (h,
- connection,
- account,
- upload_data,
- upload_data_size,
- con_cls);
- if ( (0 == strcmp (url,
- "/transfer")) &&
- (NULL != account) &&
- (0 == strcasecmp (method,
- MHD_HTTP_METHOD_POST)) )
- return handle_transfer (h,
- connection,
- account,
- upload_data,
- upload_data_size,
- con_cls);
- if ( (0 == strcmp (url,
- "/history/incoming")) &&
- (NULL != account) &&
- (0 == strcasecmp (method,
- MHD_HTTP_METHOD_GET)) )
- return handle_credit_history (h,
- connection,
- account);
- if ( (0 == strcmp (url,
- "/history/outgoing")) &&
- (NULL != account) &&
- (0 == strcasecmp (method,
- MHD_HTTP_METHOD_GET)) )
- return handle_debit_history (h,
- connection,
- account);
-
- /* Unexpected URL path, just close the connection. */
- /* we're rather impolite here, but it's a testcase. */
- TALER_LOG_ERROR ("Breaking URL: %s\n",
- url);
- GNUNET_break_op (0);
- return MHD_NO;
+ (void) connection;
+ (void) toe;
+ if (NULL == cc)
+ return;
+ cc->ctx_cleaner (cc->ctx);
+ GNUNET_free (cc);
}
@@ -1276,10 +80,10 @@ serve (struct TALER_FAKEBANK_Handle *h,
* @param version HTTP version (ignored)
* @param upload_data request data
* @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure for request (a `struct Buffer *`)
+ * @param con_cls closure for request
* @return MHD result code
*/
-static int
+static MHD_RESULT
handle_mhd_request (void *cls,
struct MHD_Connection *connection,
const char *url,
@@ -1290,43 +94,31 @@ handle_mhd_request (void *cls,
void **con_cls)
{
struct TALER_FAKEBANK_Handle *h = cls;
- char *account = NULL;
- char *end;
- int ret;
(void) version;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Handling request for `%s'\n",
- url);
- if ( (strlen (url) > 1) &&
- (NULL != (end = strchr (url + 1, '/'))) )
+ if (0 == strncmp (url,
+ "/taler-integration/",
+ strlen ("/taler-integration/")))
{
- account = GNUNET_strndup (url + 1,
- end - url - 1);
- url = end;
+ url += strlen ("/taler-integration");
+ return TALER_FAKEBANK_tbi_main_ (h,
+ connection,
+ url,
+ method,
+ upload_data,
+ upload_data_size,
+ con_cls);
}
- ret = serve (h,
- connection,
- account,
- url,
- method,
- upload_data,
- upload_data_size,
- con_cls);
- GNUNET_free_non_null (account);
- return ret;
+ return TALER_FAKEBANK_bank_main_ (h,
+ connection,
+ url,
+ method,
+ upload_data,
+ upload_data_size,
+ con_cls);
}
-/**
- * Task run whenever HTTP server operations are pending.
- *
- * @param cls the `struct TALER_FAKEBANK_Handle`
- */
-static void
-run_mhd (void *cls);
-
-
#if EPOLL_SUPPORT
/**
* Schedule MHD. This function should be called initially when an
@@ -1342,6 +134,7 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h)
MHD_UNSIGNED_LONG_LONG timeout;
struct GNUNET_TIME_Relative tv;
+ GNUNET_assert (-1 != h->mhd_fd);
haveto = MHD_get_timeout (h->mhd_bank,
&timeout);
if (MHD_YES == haveto)
@@ -1353,7 +146,7 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h)
h->mhd_task =
GNUNET_SCHEDULER_add_read_net (tv,
h->mhd_rfd,
- &run_mhd,
+ &TALER_FAKEBANK_run_mhd_,
h);
}
@@ -1379,16 +172,27 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h)
MHD_UNSIGNED_LONG_LONG timeout;
struct GNUNET_TIME_Relative tv;
+#ifdef __linux__
+ GNUNET_assert (-1 == h->lp_event);
+#else
+ GNUNET_assert (-1 == h->lp_event_in);
+ GNUNET_assert (-1 == h->lp_event_out);
+#endif
FD_ZERO (&rs);
FD_ZERO (&ws);
FD_ZERO (&es);
max = -1;
- if (MHD_YES != MHD_get_fdset (h->mhd_bank, &rs, &ws, &es, &max))
+ if (MHD_YES != MHD_get_fdset (h->mhd_bank,
+ &rs,
+ &ws,
+ &es,
+ &max))
{
GNUNET_assert (0);
return;
}
- haveto = MHD_get_timeout (h->mhd_bank, &timeout);
+ haveto = MHD_get_timeout (h->mhd_bank,
+ &timeout);
if (MHD_YES == haveto)
tv.rel_value_us = (uint64_t) timeout * 1000LL;
else
@@ -1397,8 +201,12 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h)
{
wrs = GNUNET_NETWORK_fdset_create ();
wws = GNUNET_NETWORK_fdset_create ();
- GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1);
- GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1);
+ GNUNET_NETWORK_fdset_copy_native (wrs,
+ &rs,
+ max + 1);
+ GNUNET_NETWORK_fdset_copy_native (wws,
+ &ws,
+ max + 1);
}
else
{
@@ -1412,7 +220,8 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h)
tv,
wrs,
wws,
- &run_mhd, h);
+ &TALER_FAKEBANK_run_mhd_,
+ h);
if (NULL != wrs)
GNUNET_NETWORK_fdset_destroy (wrs);
if (NULL != wws)
@@ -1428,71 +237,258 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h)
*
* @param cls the `struct TALER_FAKEBANK_Handle`
*/
-static void
-run_mhd (void *cls)
+void
+TALER_FAKEBANK_run_mhd_ (void *cls)
{
struct TALER_FAKEBANK_Handle *h = cls;
h->mhd_task = NULL;
- MHD_run (h->mhd_bank);
+ h->mhd_again = true;
+ while (h->mhd_again)
+ {
+ h->mhd_again = false;
+ MHD_run (h->mhd_bank);
+ }
+#ifdef __linux__
+ GNUNET_assert (-1 == h->lp_event);
+#else
+ GNUNET_assert (-1 == h->lp_event_in);
+ GNUNET_assert (-1 == h->lp_event_out);
+#endif
schedule_httpd (h);
}
-/**
- * Start the fake bank. The fake bank will, like the normal bank, listen for
- * requests for /admin/add/incoming and /transfer. However, instead of
- * executing or storing those requests, it will simply allow querying whether
- * such a request has been made via #TALER_FAKEBANK_check_debit() and
- * #TALER_FAKEBANK_check_credit() as well as the history API.
- *
- * This is useful for writing testcases to check whether the exchange
- * would have issued the correct wire transfer orders.
- *
- * @param port port to listen to
- * @param currency currency the bank uses
- * @return NULL on error
- */
struct TALER_FAKEBANK_Handle *
TALER_FAKEBANK_start (uint16_t port,
const char *currency)
{
+ return TALER_FAKEBANK_start2 (port,
+ currency,
+ 65536, /* RAM limit */
+ 1);
+}
+
+
+struct TALER_FAKEBANK_Handle *
+TALER_FAKEBANK_start2 (uint16_t port,
+ const char *currency,
+ uint64_t ram_limit,
+ unsigned int num_threads)
+{
+ struct TALER_Amount zero;
+
+ if (GNUNET_OK !=
+ TALER_amount_set_zero (currency,
+ &zero))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ return TALER_FAKEBANK_start3 ("localhost",
+ port,
+ NULL,
+ currency,
+ ram_limit,
+ num_threads,
+ &zero);
+}
+
+
+struct TALER_FAKEBANK_Handle *
+TALER_FAKEBANK_start3 (const char *hostname,
+ uint16_t port,
+ const char *exchange_url,
+ const char *currency,
+ uint64_t ram_limit,
+ unsigned int num_threads,
+ const struct TALER_Amount *signup_bonus)
+{
struct TALER_FAKEBANK_Handle *h;
+ if (SIZE_MAX / sizeof (struct Transaction *) < ram_limit)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "This CPU architecture does not support keeping %llu transactions in RAM\n",
+ (unsigned long long) ram_limit);
+ return NULL;
+ }
GNUNET_assert (strlen (currency) < TALER_CURRENCY_LEN);
+ if (0 != strcmp (signup_bonus->currency,
+ currency))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
h = GNUNET_new (struct TALER_FAKEBANK_Handle);
+ h->signup_bonus = *signup_bonus;
+ if (NULL != exchange_url)
+ h->exchange_url = GNUNET_strdup (exchange_url);
+#ifdef __linux__
+ h->lp_event = -1;
+#else
+ h->lp_event_in = -1;
+ h->lp_event_out = -1;
+#endif
+#if EPOLL_SUPPORT
+ h->mhd_fd = -1;
+#endif
h->port = port;
+ h->ram_limit = ram_limit;
+ h->serial_counter = 0;
+ GNUNET_assert (0 ==
+ pthread_mutex_init (&h->accounts_lock,
+ NULL));
+ GNUNET_assert (0 ==
+ pthread_mutex_init (&h->rpubs_lock,
+ NULL));
+ GNUNET_assert (0 ==
+ pthread_mutex_init (&h->uuid_map_lock,
+ NULL));
+ GNUNET_assert (0 ==
+ pthread_mutex_init (&h->big_lock,
+ NULL));
+ h->transactions
+ = GNUNET_malloc_large (sizeof (struct Transaction *)
+ * ram_limit);
+ if (NULL == h->transactions)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "malloc");
+ TALER_FAKEBANK_stop (h);
+ return NULL;
+ }
+ h->accounts = GNUNET_CONTAINER_multihashmap_create (128,
+ GNUNET_NO);
+ h->uuid_map = GNUNET_CONTAINER_multihashmap_create (ram_limit * 4 / 3,
+ GNUNET_YES);
+ if (NULL == h->uuid_map)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "malloc");
+ TALER_FAKEBANK_stop (h);
+ return NULL;
+ }
+ h->rpubs = GNUNET_CONTAINER_multipeermap_create (ram_limit * 4 / 3,
+ GNUNET_NO);
+ if (NULL == h->rpubs)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "malloc");
+ TALER_FAKEBANK_stop (h);
+ return NULL;
+ }
+ h->lp_heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
h->currency = GNUNET_strdup (currency);
+ h->hostname = GNUNET_strdup (hostname);
GNUNET_asprintf (&h->my_baseurl,
- "http://localhost:%u/",
+ "http://%s:%u/",
+ h->hostname,
(unsigned int) port);
- h->mhd_bank = MHD_start_daemon (MHD_USE_DEBUG
+ if (0 == num_threads)
+ {
+ h->mhd_bank = MHD_start_daemon (
+ MHD_USE_DEBUG
#if EPOLL_SUPPORT
- | MHD_USE_EPOLL_INTERNAL_THREAD
-#else
- | MHD_USE_INTERNAL_POLLING_THREAD
+ | MHD_USE_EPOLL
#endif
- | MHD_USE_DUAL_STACK,
- port,
- NULL, NULL,
- &handle_mhd_request, h,
- MHD_OPTION_NOTIFY_COMPLETED,
- &handle_mhd_completion_callback, h,
- MHD_OPTION_LISTEN_BACKLOG_SIZE,
- (unsigned int) 1024,
- MHD_OPTION_END);
- if (NULL == h->mhd_bank)
- {
- GNUNET_free (h->currency);
- GNUNET_free (h);
- return NULL;
- }
+ | MHD_USE_DUAL_STACK
+ | MHD_ALLOW_SUSPEND_RESUME,
+ port,
+ NULL, NULL,
+ &handle_mhd_request, h,
+ MHD_OPTION_NOTIFY_COMPLETED,
+ &handle_mhd_completion_callback, h,
+ MHD_OPTION_LISTEN_BACKLOG_SIZE,
+ (unsigned int) 1024,
+ MHD_OPTION_CONNECTION_LIMIT,
+ (unsigned int) 65536,
+ MHD_OPTION_END);
+ if (NULL == h->mhd_bank)
+ {
+ TALER_FAKEBANK_stop (h);
+ return NULL;
+ }
#if EPOLL_SUPPORT
- h->mhd_fd = MHD_get_daemon_info (h->mhd_bank,
- MHD_DAEMON_INFO_EPOLL_FD)->epoll_fd;
- h->mhd_rfd = GNUNET_NETWORK_socket_box_native (h->mhd_fd);
+ h->mhd_fd = MHD_get_daemon_info (h->mhd_bank,
+ MHD_DAEMON_INFO_EPOLL_FD)->epoll_fd;
+ h->mhd_rfd = GNUNET_NETWORK_socket_box_native (h->mhd_fd);
#endif
- schedule_httpd (h);
+ schedule_httpd (h);
+ }
+ else
+ {
+#ifdef __linux__
+ h->lp_event = eventfd (0,
+ EFD_CLOEXEC);
+ if (-1 == h->lp_event)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "eventfd");
+ TALER_FAKEBANK_stop (h);
+ return NULL;
+ }
+#else
+ {
+ int pipefd[2];
+
+ if (0 != pipe (pipefd))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "pipe");
+ TALER_FAKEBANK_stop (h);
+ return NULL;
+ }
+ h->lp_event_out = pipefd[0];
+ h->lp_event_in = pipefd[1];
+ }
+#endif
+ if (0 !=
+ pthread_create (&h->lp_thread,
+ NULL,
+ &TALER_FAKEBANK_lp_expiration_thread_,
+ h))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "pthread_create");
+#ifdef __linux__
+ GNUNET_break (0 == close (h->lp_event));
+ h->lp_event = -1;
+#else
+ GNUNET_break (0 == close (h->lp_event_in));
+ GNUNET_break (0 == close (h->lp_event_out));
+ h->lp_event_in = -1;
+ h->lp_event_out = -1;
+#endif
+ TALER_FAKEBANK_stop (h);
+ return NULL;
+ }
+ h->mhd_bank = MHD_start_daemon (
+ MHD_USE_DEBUG
+ | MHD_USE_AUTO_INTERNAL_THREAD
+ | MHD_ALLOW_SUSPEND_RESUME
+ | MHD_USE_TURBO
+ | MHD_USE_TCP_FASTOPEN
+ | MHD_USE_DUAL_STACK,
+ port,
+ NULL, NULL,
+ &handle_mhd_request, h,
+ MHD_OPTION_NOTIFY_COMPLETED,
+ &handle_mhd_completion_callback, h,
+ MHD_OPTION_LISTEN_BACKLOG_SIZE,
+ (unsigned int) 1024,
+ MHD_OPTION_CONNECTION_LIMIT,
+ (unsigned int) 65536,
+ MHD_OPTION_THREAD_POOL_SIZE,
+ num_threads,
+ MHD_OPTION_END);
+ if (NULL == h->mhd_bank)
+ {
+ GNUNET_break (0);
+ TALER_FAKEBANK_stop (h);
+ return NULL;
+ }
+ }
return h;
}
diff --git a/src/bank-lib/fakebank.h b/src/bank-lib/fakebank.h
new file mode 100644
index 000000000..a9d61d8b1
--- /dev/null
+++ b/src/bank-lib/fakebank.h
@@ -0,0 +1,691 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank.h
+ * @brief general state of the fakebank
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_H
+#define FAKEBANK_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * How long are exchange base URLs allowed to be at most?
+ * Set to a relatively low number as this does contribute
+ * significantly to our RAM consumption.
+ */
+#define MAX_URL_LEN 64
+
+
+/**
+ * Maximum POST request size.
+ */
+#define REQUEST_BUFFER_MAX (4 * 1024)
+
+
+/**
+ * Per account information.
+ */
+struct Account;
+
+
+/**
+ * Types of long polling activities.
+ */
+enum LongPollType
+{
+ /**
+ * Transfer TO the exchange.
+ */
+ LP_CREDIT,
+
+ /**
+ * Transfer FROM the exchange.
+ */
+ LP_DEBIT,
+
+ /**
+ * Withdraw operation completion/abort.
+ */
+ LP_WITHDRAW
+
+};
+
+/**
+ * Client waiting for activity on this account.
+ */
+struct LongPoller
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct LongPoller *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct LongPoller *prev;
+
+ /**
+ * Fakebank this long poller belongs with.
+ */
+ struct TALER_FAKEBANK_Handle *h;
+
+ /**
+ * Account this long poller is waiting on.
+ */
+ struct Account *account;
+
+ /**
+ * Withdraw operation we are waiting on,
+ * only if @e type is #LP_WITHDRAW, otherwise NULL.
+ */
+ const struct WithdrawalOperation *wo;
+
+ /**
+ * Entry in the heap for this long poller.
+ */
+ struct GNUNET_CONTAINER_HeapNode *hn;
+
+ /**
+ * Client that is waiting for transactions.
+ */
+ struct MHD_Connection *conn;
+
+ /**
+ * When will this long poller time out?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * What does the @e connection wait for?
+ */
+ enum LongPollType type;
+
+};
+
+
+/**
+ * Details about a transcation we (as the simulated bank) received.
+ */
+struct Transaction;
+
+
+/**
+ * Information we keep per withdraw operation.
+ */
+struct WithdrawalOperation
+{
+ /**
+ * Unique (random) operation ID.
+ */
+ struct GNUNET_ShortHashCode wopid;
+
+ /**
+ * Debited account.
+ */
+ struct Account *debit_account;
+
+ /**
+ * Target exchange account, or NULL if unknown.
+ */
+ const struct Account *exchange_account;
+
+ /**
+ * RowID of the resulting transaction, if any. Otherwise 0.
+ */
+ uint64_t row_id;
+
+ /**
+ * Amount transferred.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Public key of the reserve, wire transfer subject.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * When was the transaction made? 0 if not yet.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Was the withdrawal aborted?
+ */
+ bool aborted;
+
+ /**
+ * Did the bank confirm the withdrawal?
+ */
+ bool confirmation_done;
+
+ /**
+ * Is @e reserve_pub initialized?
+ */
+ bool selection_done;
+
+};
+
+
+/**
+ * Per account information.
+ */
+struct Account
+{
+
+ /**
+ * Inbound transactions for this account in a MDLL.
+ */
+ struct Transaction *in_head;
+
+ /**
+ * Inbound transactions for this account in a MDLL.
+ */
+ struct Transaction *in_tail;
+
+ /**
+ * Outbound transactions for this account in a MDLL.
+ */
+ struct Transaction *out_head;
+
+ /**
+ * Outbound transactions for this account in a MDLL.
+ */
+ struct Transaction *out_tail;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct LongPoller *lp_head;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct LongPoller *lp_tail;
+
+ /**
+ * Account name (string, not payto!)
+ */
+ char *account_name;
+
+ /**
+ * Receiver name for payto:// URIs.
+ */
+ char *receiver_name;
+
+ /**
+ * Payto URI for this account.
+ */
+ char *payto_uri;
+
+ /**
+ * Password set for the account (if any).
+ */
+ char *password;
+
+ /**
+ * Current account balance.
+ */
+ struct TALER_Amount balance;
+
+ /**
+ * true if the balance is negative.
+ */
+ bool is_negative;
+
+};
+
+
+/**
+ * Details about a transcation we (as the simulated bank) received.
+ */
+struct Transaction
+{
+ /**
+ * We store inbound transactions in a MDLL.
+ */
+ struct Transaction *next_in;
+
+ /**
+ * We store inbound transactions in a MDLL.
+ */
+ struct Transaction *prev_in;
+
+ /**
+ * We store outbound transactions in a MDLL.
+ */
+ struct Transaction *next_out;
+
+ /**
+ * We store outbound transactions in a MDLL.
+ */
+ struct Transaction *prev_out;
+
+ /**
+ * Amount to be transferred.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Account to debit.
+ */
+ struct Account *debit_account;
+
+ /**
+ * Account to credit.
+ */
+ struct Account *credit_account;
+
+ /**
+ * Random unique identifier for the request.
+ * Used to detect idempotent requests.
+ */
+ struct GNUNET_HashCode request_uid;
+
+ /**
+ * When did the transaction happen?
+ */
+ struct GNUNET_TIME_Timestamp date;
+
+ /**
+ * Number of this transaction.
+ */
+ uint64_t row_id;
+
+ /**
+ * What does the @e subject contain?
+ */
+ enum
+ {
+ /**
+ * Transfer TO the exchange.
+ */
+ T_CREDIT,
+
+ /**
+ * Transfer FROM the exchange.
+ */
+ T_DEBIT,
+
+ /**
+ * Exchange-to-exchange WAD transfer.
+ */
+ T_WAD,
+ } type;
+
+ /**
+ * Wire transfer subject.
+ */
+ union
+ {
+
+ /**
+ * Used if @e type is T_DEBIT.
+ */
+ struct
+ {
+
+ /**
+ * Subject of the transfer.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ /**
+ * Base URL of the exchange.
+ */
+ char exchange_base_url[MAX_URL_LEN];
+
+ } debit;
+
+ /**
+ * Used if @e type is T_CREDIT.
+ */
+ struct
+ {
+
+ /**
+ * Reserve public key of the credit operation.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ } credit;
+
+ /**
+ * Used if @e type is T_WAD.
+ */
+ struct
+ {
+
+ /**
+ * Subject of the transfer.
+ */
+ struct TALER_WadIdentifierP wad;
+
+ /**
+ * Base URL of the originating exchange.
+ */
+ char origin_base_url[MAX_URL_LEN];
+
+ } wad;
+
+ } subject;
+
+ /**
+ * Has this transaction not yet been subjected to
+ * #TALER_FAKEBANK_check_credit() or #TALER_FAKEBANK_check_debit() and
+ * should thus be counted in #TALER_FAKEBANK_check_empty()?
+ */
+ bool unchecked;
+};
+
+
+/**
+ * Function called to clean up context of a connection.
+ *
+ * @param ctx context to clean up
+ */
+typedef void
+(*ConnectionCleaner)(void *ctx);
+
+/**
+ * Universal context we keep per connection.
+ */
+struct ConnectionContext
+{
+ /**
+ * Function we call upon completion to clean up.
+ */
+ ConnectionCleaner ctx_cleaner;
+
+ /**
+ * Request-handler specific context.
+ */
+ void *ctx;
+};
+
+
+/**
+ * This is the "base" structure for both the /history and the
+ * /history-range API calls.
+ */
+struct HistoryArgs
+{
+
+ /**
+ * Bank account number of the requesting client.
+ */
+ uint64_t account_number;
+
+ /**
+ * Index of the starting transaction, exclusive (!).
+ */
+ uint64_t start_idx;
+
+ /**
+ * Requested number of results and order
+ * (positive: ascending, negative: descending)
+ */
+ int64_t delta;
+
+ /**
+ * Timeout for long polling.
+ */
+ struct GNUNET_TIME_Relative lp_timeout;
+
+ /**
+ * true if starting point was given.
+ */
+ bool have_start;
+
+};
+
+
+/**
+ * Context we keep per history request.
+ */
+struct HistoryContext
+{
+ /**
+ * When does this request time out.
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * Client arguments for this request.
+ */
+ struct HistoryArgs ha;
+
+ /**
+ * Account the request is about.
+ */
+ struct Account *acc;
+
+ /**
+ * JSON object we are building to return.
+ */
+ json_t *history;
+
+};
+
+
+/**
+ * Context we keep per get withdrawal operation request.
+ */
+struct WithdrawContext
+{
+ /**
+ * When does this request time out.
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * The withdrawal operation this is about.
+ */
+ struct WithdrawalOperation *wo;
+
+};
+
+
+/**
+ * Handle for the fake bank.
+ */
+struct TALER_FAKEBANK_Handle
+{
+ /**
+ * We store transactions in a revolving array.
+ */
+ struct Transaction **transactions;
+
+ /**
+ * HTTP server we run to pretend to be the "test" bank.
+ */
+ struct MHD_Daemon *mhd_bank;
+
+ /**
+ * Task running HTTP server for the "test" bank,
+ * unless we are using a thread pool (then NULL).
+ */
+ struct GNUNET_SCHEDULER_Task *mhd_task;
+
+ /**
+ * Task for expiring long-polling connections,
+ * unless we are using a thread pool (then NULL).
+ */
+ struct GNUNET_SCHEDULER_Task *lp_task;
+
+ /**
+ * Task for expiring long-polling connections, unless we are using the
+ * GNUnet scheduler (then NULL).
+ */
+ pthread_t lp_thread;
+
+ /**
+ * MIN-heap of long pollers, sorted by timeout.
+ */
+ struct GNUNET_CONTAINER_Heap *lp_heap;
+
+ /**
+ * Hashmap of reserve public keys to
+ * `struct Transaction` with that reserve public
+ * key. Used to prevent public-key reuse.
+ */
+ struct GNUNET_CONTAINER_MultiPeerMap *rpubs;
+
+ /**
+ * Hashmap of short hashes (wopids) to
+ * `struct WithdrawalOperation`.
+ * Used to lookup withdrawal operations.
+ */
+ struct GNUNET_CONTAINER_MultiShortmap *wops;
+
+ /**
+ * (Base) URL to suggest for the exchange. Can
+ * be NULL if there is no suggestion to be made.
+ */
+ char *exchange_url;
+
+ /**
+ * Lock for accessing @a rpubs map.
+ */
+ pthread_mutex_t rpubs_lock;
+
+ /**
+ * Hashmap of hashes of account names to `struct Account`.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *accounts;
+
+ /**
+ * Lock for accessing @a accounts hash map.
+ */
+ pthread_mutex_t accounts_lock;
+
+ /**
+ * Hashmap of hashes of transaction request_uids to `struct Transaction`.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *uuid_map;
+
+ /**
+ * Lock for accessing @a uuid_map.
+ */
+ pthread_mutex_t uuid_map_lock;
+
+ /**
+ * Lock for accessing the internals of
+ * accounts and transaction array entries.
+ */
+ pthread_mutex_t big_lock;
+
+ /**
+ * How much money should be put into new accounts
+ * on /register.
+ */
+ struct TALER_Amount signup_bonus;
+
+ /**
+ * Current transaction counter.
+ */
+ uint64_t serial_counter;
+
+ /**
+ * Number of transactions we keep in memory (at most).
+ */
+ uint64_t ram_limit;
+
+ /**
+ * Currency used by the fakebank.
+ */
+ char *currency;
+
+ /**
+ * Hostname of the fakebank.
+ */
+ char *hostname;
+
+ /**
+ * BaseURL of the fakebank.
+ */
+ char *my_baseurl;
+
+ /**
+ * Our port number.
+ */
+ uint16_t port;
+
+#ifdef __linux__
+ /**
+ * Event FD to signal @a lp_thread a change in
+ * @a lp_heap.
+ */
+ int lp_event;
+#else
+ /**
+ * Pipe input to signal @a lp_thread a change in
+ * @a lp_heap.
+ */
+ int lp_event_in;
+
+ /**
+ * Pipe output to signal @a lp_thread a change in
+ * @a lp_heap.
+ */
+ int lp_event_out;
+#endif
+
+ /**
+ * Set to true once we are shutting down.
+ */
+ bool in_shutdown;
+
+ /**
+ * Should we run MHD immediately again?
+ */
+ bool mhd_again;
+
+#if EPOLL_SUPPORT
+ /**
+ * Boxed @e mhd_fd.
+ */
+ struct GNUNET_NETWORK_Handle *mhd_rfd;
+
+ /**
+ * File descriptor to use to wait for MHD.
+ */
+ int mhd_fd;
+#endif
+};
+
+
+/**
+ * Task run whenever HTTP server operations are pending.
+ *
+ * @param cls the `struct TALER_FAKEBANK_Handle`
+ */
+void
+TALER_FAKEBANK_run_mhd_ (void *cls);
+
+
+#endif
diff --git a/src/bank-lib/fakebank_api_check.c b/src/bank-lib/fakebank_api_check.c
new file mode 100644
index 000000000..04656ebab
--- /dev/null
+++ b/src/bank-lib/fakebank_api_check.c
@@ -0,0 +1,238 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_api_check.c
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+
+
+/**
+ * Generate log messages for failed check operation.
+ *
+ * @param h handle to output transaction log for
+ */
+static void
+check_log (struct TALER_FAKEBANK_Handle *h)
+{
+ for (uint64_t i = 0; i<h->ram_limit; i++)
+ {
+ struct Transaction *t = h->transactions[i];
+
+ if (NULL == t)
+ continue;
+ if (! t->unchecked)
+ continue;
+ switch (t->type)
+ {
+ case T_DEBIT:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%s -> %s (%s) %s (%s)\n",
+ t->debit_account->account_name,
+ t->credit_account->account_name,
+ TALER_amount2s (&t->amount),
+ t->subject.debit.exchange_base_url,
+ "DEBIT");
+ break;
+ case T_CREDIT:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%s -> %s (%s) %s (%s)\n",
+ t->debit_account->account_name,
+ t->credit_account->account_name,
+ TALER_amount2s (&t->amount),
+ TALER_B2S (&t->subject.credit.reserve_pub),
+ "CREDIT");
+ break;
+ case T_WAD:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%s -> %s (%s) %s[%s] (%s)\n",
+ t->debit_account->account_name,
+ t->credit_account->account_name,
+ TALER_amount2s (&t->amount),
+ t->subject.wad.origin_base_url,
+ TALER_B2S (&t->subject.wad),
+ "WAD");
+ break;
+ }
+ }
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_check_debit (struct TALER_FAKEBANK_Handle *h,
+ const struct TALER_Amount *want_amount,
+ const char *want_debit,
+ const char *want_credit,
+ const char *exchange_base_url,
+ struct TALER_WireTransferIdentifierRawP *wtid)
+{
+ struct Account *debit_account;
+ struct Account *credit_account;
+
+ GNUNET_assert (0 ==
+ strcasecmp (want_amount->currency,
+ h->currency));
+ debit_account = TALER_FAKEBANK_lookup_account_ (h,
+ want_debit,
+ NULL);
+ credit_account = TALER_FAKEBANK_lookup_account_ (h,
+ want_credit,
+ NULL);
+ if (NULL == debit_account)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "I wanted: %s->%s (%s) from exchange %s (DEBIT), but debit account does not even exist!\n",
+ want_debit,
+ want_credit,
+ TALER_amount2s (want_amount),
+ exchange_base_url);
+ return GNUNET_SYSERR;
+ }
+ if (NULL == credit_account)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "I wanted: %s->%s (%s) from exchange %s (DEBIT), but credit account does not even exist!\n",
+ want_debit,
+ want_credit,
+ TALER_amount2s (want_amount),
+ exchange_base_url);
+ return GNUNET_SYSERR;
+ }
+ for (struct Transaction *t = debit_account->out_tail;
+ NULL != t;
+ t = t->prev_out)
+ {
+ if ( (t->unchecked) &&
+ (credit_account == t->credit_account) &&
+ (T_DEBIT == t->type) &&
+ (0 == TALER_amount_cmp (want_amount,
+ &t->amount)) &&
+ (0 == strcasecmp (exchange_base_url,
+ t->subject.debit.exchange_base_url)) )
+ {
+ *wtid = t->subject.debit.wtid;
+ t->unchecked = false;
+ return GNUNET_OK;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Did not find matching transaction! I have:\n");
+ check_log (h);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "I wanted: %s->%s (%s) from exchange %s (DEBIT)\n",
+ want_debit,
+ want_credit,
+ TALER_amount2s (want_amount),
+ exchange_base_url);
+ return GNUNET_SYSERR;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_check_credit (struct TALER_FAKEBANK_Handle *h,
+ const struct TALER_Amount *want_amount,
+ const char *want_debit,
+ const char *want_credit,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+ struct Account *debit_account;
+ struct Account *credit_account;
+
+ GNUNET_assert (0 == strcasecmp (want_amount->currency,
+ h->currency));
+ debit_account = TALER_FAKEBANK_lookup_account_ (h,
+ want_debit,
+ NULL);
+ credit_account = TALER_FAKEBANK_lookup_account_ (h,
+ want_credit,
+ NULL);
+ if (NULL == debit_account)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "I wanted:\n%s -> %s (%s) with subject %s (CREDIT) but debit account is unknown.\n",
+ want_debit,
+ want_credit,
+ TALER_amount2s (want_amount),
+ TALER_B2S (reserve_pub));
+ return GNUNET_SYSERR;
+ }
+ if (NULL == credit_account)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "I wanted:\n%s -> %s (%s) with subject %s (CREDIT) but credit account is unknown.\n",
+ want_debit,
+ want_credit,
+ TALER_amount2s (want_amount),
+ TALER_B2S (reserve_pub));
+ return GNUNET_SYSERR;
+ }
+ for (struct Transaction *t = credit_account->in_tail;
+ NULL != t;
+ t = t->prev_in)
+ {
+ if ( (t->unchecked) &&
+ (debit_account == t->debit_account) &&
+ (T_CREDIT == t->type) &&
+ (0 == TALER_amount_cmp (want_amount,
+ &t->amount)) &&
+ (0 == GNUNET_memcmp (reserve_pub,
+ &t->subject.credit.reserve_pub)) )
+ {
+ t->unchecked = false;
+ return GNUNET_OK;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Did not find matching transaction!\nI have:\n");
+ check_log (h);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "I wanted:\n%s -> %s (%s) with subject %s (CREDIT)\n",
+ want_debit,
+ want_credit,
+ TALER_amount2s (want_amount),
+ TALER_B2S (reserve_pub));
+ return GNUNET_SYSERR;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_check_empty (struct TALER_FAKEBANK_Handle *h)
+{
+ for (uint64_t i = 0; i<h->ram_limit; i++)
+ {
+ struct Transaction *t = h->transactions[i];
+
+ if ( (NULL != t) &&
+ (t->unchecked) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected empty transaction set, but I have:\n");
+ check_log (h);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
diff --git a/src/bank-lib/fakebank_bank.c b/src/bank-lib/fakebank_bank.c
new file mode 100644
index 000000000..7c2d39ab4
--- /dev/null
+++ b/src/bank-lib/fakebank_bank.c
@@ -0,0 +1,524 @@
+/*
+ This file is part of TALER
+ (C) 2016-2024 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 bank-lib/fakebank_bank.c
+ * @brief Main dispatcher for the Taler Bank API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank.h"
+#include "fakebank_tbr.h"
+#include "fakebank_twg.h"
+#include "fakebank_bank_get_accounts.h"
+#include "fakebank_bank_get_withdrawals.h"
+#include "fakebank_bank_get_root.h"
+#include "fakebank_bank_post_accounts_withdrawals.h"
+#include "fakebank_bank_post_withdrawals_abort.h"
+#include "fakebank_bank_post_withdrawals_confirm.h"
+#include "fakebank_bank_post_withdrawals_id_op.h"
+#include "fakebank_bank_testing_register.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_bank_main_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_HEAD))
+ method = MHD_HTTP_METHOD_GET;
+
+ if ( (0 == strcmp (url,
+ "/")) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET)) )
+ {
+ /* GET / */
+ return TALER_FAKEBANK_bank_get_root_ (h,
+ connection);
+ }
+
+ if ( (0 == strcmp (url,
+ "/config")) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET)) )
+ {
+ /* GET /config */
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("version",
+ "4:0:4"), /* not sure, API versions are not properly marked up! */
+ GNUNET_JSON_pack_string ("currency",
+ h->currency),
+ GNUNET_JSON_pack_string ("implementation",
+ "urn:net:taler:specs:bank:fakebank"),
+ GNUNET_JSON_pack_string ("name",
+ "taler-corebank"));
+ }
+
+ if ( (0 == strcmp (url,
+ "/public-accounts")) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET)) )
+ {
+ /* GET /public-accounts */
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("public_accounts",
+ json_array ()));
+ }
+
+ /* account registration API */
+ if ( (0 == strcmp (url,
+ "/accounts")) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_POST)) )
+ {
+ /* POST /accounts */
+ return TALER_FAKEBANK_bank_testing_register_ (h,
+ connection,
+ upload_data,
+ upload_data_size,
+ con_cls);
+ }
+
+ if ( (0 == strcmp (url,
+ "/accounts")) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET)) )
+ {
+ /* GET /accounts */
+ GNUNET_break (0); /* not implemented */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+
+ if ( (0 == strcmp (url,
+ "/cashout-rate")) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET)) )
+ {
+ /* GET /cashout-rate */
+ GNUNET_break (0); /* not implemented */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+
+ if ( (0 == strcmp (url,
+ "/cashouts")) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET)) )
+ {
+ /* GET /cashouts */
+ GNUNET_break (0); /* not implemented */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+
+ if ( (0 == strncmp (url,
+ "/withdrawals/",
+ strlen ("/withdrawals/"))) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET)) )
+ {
+ /* GET /withdrawals/$WID */
+ const char *wid;
+
+ wid = &url[strlen ("/withdrawals/")];
+ return TALER_FAKEBANK_bank_get_withdrawals_ (h,
+ connection,
+ wid);
+ }
+
+ if ( (0 == strncmp (url,
+ "/withdrawals/",
+ strlen ("/withdrawals/"))) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_POST)) )
+ {
+ /* POST /withdrawals/$WID* */
+ const char *wid = url + strlen ("/withdrawals/");
+ const char *opid = strchr (wid,
+ '/');
+ char *wi;
+
+ if (NULL == opid)
+ {
+ /* POST /withdrawals/$WID (not defined) */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ url);
+ }
+ wi = GNUNET_strndup (wid,
+ opid - wid);
+ if (0 == strcmp (opid,
+ "/abort"))
+ {
+ /* POST /withdrawals/$WID/abort */
+ MHD_RESULT ret;
+
+ ret = TALER_FAKEBANK_bank_withdrawals_abort_ (h,
+ connection,
+ wi);
+ GNUNET_free (wi);
+ return ret;
+ }
+ if (0 == strcmp (opid,
+ "/confirm"))
+ {
+ /* POST /withdrawals/$WID/confirm */
+ MHD_RESULT ret;
+
+ ret = TALER_FAKEBANK_bank_withdrawals_confirm_ (h,
+ connection,
+ wi);
+ GNUNET_free (wi);
+ return ret;
+ }
+ }
+
+ if (0 == strncmp (url,
+ "/accounts/",
+ strlen ("/accounts/")))
+ {
+ const char *acc_name = &url[strlen ("/accounts/")];
+ const char *end_acc = strchr (acc_name,
+ '/');
+
+ if ( (NULL != end_acc) &&
+ (0 == strncmp (end_acc,
+ "/taler-wire-gateway/",
+ strlen ("/taler-wire-gateway/"))) )
+ {
+ /* $METHOD /accounts/$ACCOUNT/taler-wire-gateway/ */
+ char *acc;
+ MHD_RESULT ret;
+
+ acc = GNUNET_strndup (acc_name,
+ end_acc - acc_name);
+ end_acc += strlen ("/taler-wire-gateway");
+ ret = TALER_FAKEBANK_twg_main_ (h,
+ connection,
+ acc,
+ end_acc,
+ method,
+ upload_data,
+ upload_data_size,
+ con_cls);
+ GNUNET_free (acc);
+ return ret;
+ }
+
+ if ( (NULL != end_acc) &&
+ (0 == strncmp (end_acc,
+ "/taler-revenue/",
+ strlen ("/taler-revenue/"))) )
+ {
+ /* $METHOD /accounts/$ACCOUNT/taler-revenue/ */
+ char *acc;
+ MHD_RESULT ret;
+
+ acc = GNUNET_strndup (acc_name,
+ end_acc - acc_name);
+ end_acc += strlen ("/taler-revenue");
+ ret = TALER_FAKEBANK_tbr_main_ (h,
+ connection,
+ acc,
+ end_acc,
+ method,
+ upload_data,
+ upload_data_size,
+ con_cls);
+ GNUNET_free (acc);
+ return ret;
+ }
+
+ if ( (NULL == end_acc) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET)) )
+ {
+ /* GET /accounts/$ACCOUNT */
+ return TALER_FAKEBANK_bank_get_accounts_ (h,
+ connection,
+ acc_name);
+ }
+
+ if ( (NULL == end_acc) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_PATCH)) )
+ {
+ /* PATCH /accounts/$USERNAME */
+ GNUNET_break (0); /* not implemented */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+
+ if ( (NULL == end_acc) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_DELETE)) )
+ {
+ /* DELETE /accounts/$USERNAME */
+ GNUNET_break (0); /* not implemented */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+
+ if ( (NULL != end_acc) &&
+ (0 == strcmp ("/auth",
+ end_acc)) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_PATCH)) )
+ {
+ /* PATCH /accounts/$USERNAME/auth */
+ GNUNET_break (0); /* not implemented */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+
+ if ( (NULL != end_acc) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET)) )
+ {
+ /* GET /accounts/$ACCOUNT/+ */
+
+ if (0 == strcmp (end_acc,
+ "/transactions"))
+ {
+ /* GET /accounts/$USERNAME/transactions */
+ GNUNET_break (0); /* not implemented */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+ if (0 == strncmp (end_acc,
+ "/transactions/",
+ strlen ("/transactions/")))
+ {
+ /* GET /accounts/$USERNAME/transactions/$TID */
+ GNUNET_break (0); /* not implemented */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+ if (0 == strcmp (end_acc,
+ "/withdrawals"))
+ {
+ /* GET /accounts/$USERNAME/withdrawals */
+ GNUNET_break (0); /* not implemented */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+ if (0 == strcmp (end_acc,
+ "/cashouts"))
+ {
+ /* GET /accounts/$USERNAME/cashouts */
+ GNUNET_break (0); /* not implemented */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+ if (0 == strncmp (end_acc,
+ "/cashouts/",
+ strlen ("/cashouts/")))
+ {
+ /* GET /accounts/$USERNAME/cashouts/$CID */
+ GNUNET_break (0); /* not implemented */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+
+
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ acc_name);
+ }
+
+ if ( (NULL != end_acc) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_POST)) )
+ {
+ /* POST /accounts/$ACCOUNT/+ */
+ char *acc;
+
+ acc = GNUNET_strndup (acc_name,
+ end_acc - acc_name);
+ if (0 == strcmp (end_acc,
+ "/cashouts"))
+ {
+ /* POST /accounts/$USERNAME/cashouts */
+ GNUNET_break (0); /* not implemented */
+ GNUNET_free (acc);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+ if (0 == strncmp (end_acc,
+ "/cashouts/",
+ strlen ("/cashouts/")))
+ {
+ /* POST /accounts/$USERNAME/cashouts/+ */
+ const char *cid = end_acc + strlen ("/cashouts/");
+ const char *opid = strchr (cid,
+ '/');
+ char *ci;
+
+ if (NULL == opid)
+ {
+ /* POST /accounts/$ACCOUNT/cashouts/$CID (not defined) */
+ GNUNET_break_op (0);
+ GNUNET_free (acc);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ acc_name);
+ }
+ ci = GNUNET_strndup (cid,
+ opid - cid);
+ if (0 == strcmp (opid,
+ "/abort"))
+ {
+ GNUNET_break (0); /* not implemented */
+ GNUNET_free (ci);
+ GNUNET_free (acc);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+ if (0 == strcmp (opid,
+ "/confirm"))
+ {
+ GNUNET_break (0); /* not implemented */
+ GNUNET_free (ci);
+ GNUNET_free (acc);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ url);
+ }
+ }
+
+ if (0 == strcmp (end_acc,
+ "/withdrawals"))
+ {
+ /* POST /accounts/$ACCOUNT/withdrawals */
+ MHD_RESULT ret;
+
+ ret = TALER_FAKEBANK_bank_post_account_withdrawals_ (
+ h,
+ connection,
+ acc,
+ upload_data,
+ upload_data_size,
+ con_cls);
+ GNUNET_free (acc);
+ return ret;
+ }
+
+ if (0 == strncmp (end_acc,
+ "/withdrawals/",
+ strlen ("/withdrawals/")))
+ {
+ /* POST /accounts/$ACCOUNT/withdrawals/$WID/$OP */
+ MHD_RESULT ret;
+ const char *pos = &end_acc[strlen ("/withdrawals/")];
+ const char *op = strchr (pos, '/');
+
+ if (NULL != op)
+ {
+ char *wid = GNUNET_strndup (pos,
+ op - pos);
+
+ ret = TALER_FAKEBANK_bank_withdrawals_id_op_ (
+ h,
+ connection,
+ acc,
+ wid,
+ op,
+ upload_data,
+ upload_data_size,
+ con_cls);
+ GNUNET_free (wid);
+ GNUNET_free (acc);
+ return ret;
+ }
+ GNUNET_free (acc);
+ }
+ }
+ }
+
+ GNUNET_break_op (0);
+ TALER_LOG_ERROR ("Breaking URL: %s %s\n",
+ method,
+ url);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ url);
+}
diff --git a/src/bank-lib/fakebank_bank.h b/src/bank-lib/fakebank_bank.h
new file mode 100644
index 000000000..1c51f88f8
--- /dev/null
+++ b/src/bank-lib/fakebank_bank.h
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank.h
+ * @brief Main dispatcher for the Taler Bank API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_H
+#define FAKEBANK_BANK_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Handle incoming HTTP request to the Taler bank API.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param url the requested url
+ * @param method the method (POST, GET, ...)
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_main_ (struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_accounts_withdrawals.c b/src/bank-lib/fakebank_bank_accounts_withdrawals.c
new file mode 100644
index 000000000..bb435d975
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_accounts_withdrawals.c
@@ -0,0 +1,101 @@
+/*
+ This file is part of TALER
+ (C) 2016-2024 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 bank-lib/fakebank_bank_accounts_withdrawals.c
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_accounts_withdrawals.h"
+#include "fakebank_common_lookup.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_bank_account_withdrawals_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account_name,
+ const char *withdrawal_id)
+{
+ struct WithdrawalOperation *wo;
+ struct Account *acc;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+ withdrawal_id);
+ if (NULL == wo)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+ withdrawal_id);
+ }
+ acc = TALER_FAKEBANK_lookup_account_ (h,
+ account_name,
+ NULL);
+ if (NULL == acc)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_UNKNOWN_ACCOUNT,
+ account_name);
+ }
+ if (wo->debit_account != acc)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+ account_name);
+ }
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_bool ("aborted",
+ wo->aborted),
+ GNUNET_JSON_pack_bool ("selection_done",
+ wo->selection_done),
+ GNUNET_JSON_pack_bool ("transfer_done",
+ wo->confirmation_done),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("selected_exchange_account",
+ wo->exchange_account->payto_uri)),
+ GNUNET_JSON_pack_allow_null (
+ wo->selection_done
+ ? GNUNET_JSON_pack_data_auto ("selected_reserve_pub",
+ &wo->reserve_pub)
+ : GNUNET_JSON_pack_string ("selected_reserve_pub",
+ NULL)),
+ TALER_JSON_pack_amount ("amount",
+ &wo->amount));
+}
diff --git a/src/bank-lib/fakebank_bank_accounts_withdrawals.h b/src/bank-lib/fakebank_bank_accounts_withdrawals.h
new file mode 100644
index 000000000..2a598dee9
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_accounts_withdrawals.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_accounts_withdrawals.h
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_ACCOUNTS_WITHDRAWALS_H
+#define FAKEBANK_BANK_ACCOUNTS_WITHDRAWALS_H
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Handle GET /accounts/${account_name}/withdrawals/{withdrawal_id} request
+ * to the Taler bank access API.
+ *
+ * @param h the handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @param withdrawal_id withdrawal ID to return status of
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_account_withdrawals_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account_name,
+ const char *withdrawal_id);
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_get_accounts.c b/src/bank-lib/fakebank_bank_get_accounts.c
new file mode 100644
index 000000000..e85387d2a
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_get_accounts.c
@@ -0,0 +1,80 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_get_accounts.c
+ * @brief implements the Taler Bank API "GET /accounts/" handler
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_get_accounts.h"
+#include "fakebank_common_lookup.h"
+
+/**
+ * Handle GET /accounts/${account_name} request of the Taler bank API.
+ *
+ * @param h the handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_get_accounts_ (struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account_name)
+{
+ struct Account *acc;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ acc = TALER_FAKEBANK_lookup_account_ (h,
+ account_name,
+ NULL);
+ if (NULL == acc)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_UNKNOWN_ACCOUNT,
+ account_name);
+ }
+
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("payto_uri",
+ acc->payto_uri),
+ GNUNET_JSON_pack_object_steal (
+ "balance",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("credit_debit_indicator",
+ acc->is_negative
+ ? "debit"
+ : "credit"),
+ TALER_JSON_pack_amount ("amount",
+ &acc->balance))));
+}
diff --git a/src/bank-lib/fakebank_bank_get_accounts.h b/src/bank-lib/fakebank_bank_get_accounts.h
new file mode 100644
index 000000000..7d3872133
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_get_accounts.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_get_accounts.h
+ * @brief implements the Taler Bank API "GET /accounts/" handler
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_GET_ACCOUNTS_H
+#define FAKEBANK_BANK_GET_ACCOUNTS_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle GET /accounts/${account_name} request of the Taler bank API.
+ *
+ * @param h the handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_get_accounts_ (struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account_name);
+
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_get_root.c b/src/bank-lib/fakebank_bank_get_root.c
new file mode 100644
index 000000000..8c34697b4
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_get_root.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_get_root.c
+ * @brief handle a GET "/" request for the bank API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_get_root.h"
+
+
+/**
+ * Handle incoming HTTP request for "/" (home page).
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_get_root_ (struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection)
+{
+ MHD_RESULT ret;
+ struct MHD_Response *resp;
+#define HELLOMSG "Hello, Fakebank!"
+
+ (void) h;
+ resp = MHD_create_response_from_buffer (
+ strlen (HELLOMSG),
+ HELLOMSG,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ resp);
+ MHD_destroy_response (resp);
+ return ret;
+}
diff --git a/src/bank-lib/fakebank_bank_get_root.h b/src/bank-lib/fakebank_bank_get_root.h
new file mode 100644
index 000000000..2eedb94a1
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_get_root.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_get_root.c
+ * @brief handle a GET "/" request for the bank API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_GET_ROOT_H
+#define FAKEBANK_BANK_GET_ROOT_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Handle incoming HTTP request for "/" (home page).
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_get_root_ (struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection);
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_get_withdrawals.c b/src/bank-lib/fakebank_bank_get_withdrawals.c
new file mode 100644
index 000000000..7f65e8660
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_get_withdrawals.c
@@ -0,0 +1,87 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_get_withdrawals.c
+ * @brief implements the Taler Bank API "GET /withdrawals/$WID" handler
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_get_withdrawals.h"
+#include "fakebank_common_lookup.h"
+
+
+/**
+ * Handle GET /withdrawals/{withdrawal_id} request
+ * to the Taler bank access API.
+ *
+ * @param h the handle
+ * @param connection the connection
+ * @param withdrawal_id withdrawal ID to return status of
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_get_withdrawals_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *withdrawal_id)
+{
+ struct WithdrawalOperation *wo;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+ withdrawal_id);
+ if (NULL == wo)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+ withdrawal_id);
+ }
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_bool ("aborted",
+ wo->aborted),
+ GNUNET_JSON_pack_bool ("selection_done",
+ wo->selection_done),
+ GNUNET_JSON_pack_bool ("transfer_done",
+ wo->confirmation_done),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("selected_exchange_account",
+ wo->exchange_account->payto_uri)),
+ GNUNET_JSON_pack_allow_null (
+ wo->selection_done
+ ? GNUNET_JSON_pack_data_auto ("selected_reserve_pub",
+ &wo->reserve_pub)
+ : GNUNET_JSON_pack_string ("selected_reserve_pub",
+ NULL)),
+ TALER_JSON_pack_amount ("amount",
+ &wo->amount));
+}
diff --git a/src/bank-lib/fakebank_bank_get_withdrawals.h b/src/bank-lib/fakebank_bank_get_withdrawals.h
new file mode 100644
index 000000000..62a96866c
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_get_withdrawals.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_get_withdrawals.h
+ * @brief implements the Taler Bank API "GET /accounts/ACC/withdrawals/WID" handler
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_GET_WITHDRAWALS_H
+#define FAKEBANK_BANK_GET_WITHDRAWALS_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_get_withdrawals.h"
+
+
+/**
+ * Handle GET /withdrawals/{withdrawal_id} request
+ * to the Taler bank access API.
+ *
+ * @param h the handle
+ * @param connection the connection
+ * @param withdrawal_id withdrawal ID to return status of
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_get_withdrawals_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *withdrawal_id);
+
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals.c b/src/bank-lib/fakebank_bank_post_accounts_withdrawals.c
new file mode 100644
index 000000000..7fbb93352
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals.c
@@ -0,0 +1,196 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_post_accounts_withdrawals.c
+ * @brief implementation of the bank API's POST /accounts/AID/withdrawals endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_post_accounts_withdrawals.h"
+#include "fakebank_common_lookup.h"
+
+
+/**
+ * Execute POST /accounts/$account_name/withdrawals request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @param amount amount to withdraw
+ * @return MHD result code
+ */
+static MHD_RESULT
+do_post_account_withdrawals (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account_name,
+ const struct TALER_Amount *amount)
+{
+ struct Account *acc;
+ struct WithdrawalOperation *wo;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ acc = TALER_FAKEBANK_lookup_account_ (h,
+ account_name,
+ NULL);
+ if (NULL == acc)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_UNKNOWN_ACCOUNT,
+ account_name);
+ }
+ wo = GNUNET_new (struct WithdrawalOperation);
+ wo->debit_account = acc;
+ wo->amount = *amount;
+ if (NULL == h->wops)
+ {
+ h->wops = GNUNET_CONTAINER_multishortmap_create (32,
+ GNUNET_YES);
+ }
+ while (1)
+ {
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &wo->wopid,
+ sizeof (wo->wopid));
+ if (GNUNET_OK ==
+ GNUNET_CONTAINER_multishortmap_put (h->wops,
+ &wo->wopid,
+ wo,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+ break;
+ }
+ {
+ char *wopids;
+ char *uri;
+ MHD_RESULT res;
+
+ wopids = GNUNET_STRINGS_data_to_string_alloc (&wo->wopid,
+ sizeof (wo->wopid));
+ GNUNET_asprintf (&uri,
+ "taler+http://withdraw/%s:%u/taler-integration/%s",
+ h->hostname,
+ (unsigned int) h->port,
+ wopids);
+ GNUNET_free (wopids);
+ res = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("taler_withdraw_uri",
+ uri),
+ GNUNET_JSON_pack_data_auto ("withdrawal_id",
+ &wo->wopid));
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ GNUNET_free (uri);
+ return res;
+ }
+}
+
+
+/**
+ * Handle POST /accounts/$account_name/withdrawals request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_post_account_withdrawals_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account_name,
+ const void *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ struct ConnectionContext *cc = *con_cls;
+ enum GNUNET_JSON_PostResult pr;
+ json_t *json;
+ MHD_RESULT res;
+
+ if (NULL == cc)
+ {
+ cc = GNUNET_new (struct ConnectionContext);
+ cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
+ *con_cls = cc;
+ }
+ pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
+ connection,
+ &cc->ctx,
+ upload_data,
+ upload_data_size,
+ &json);
+ switch (pr)
+ {
+ case GNUNET_JSON_PR_OUT_OF_MEMORY:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_CONTINUE:
+ return MHD_YES;
+ case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_JSON_INVALID:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_SUCCESS:
+ break;
+ }
+
+ {
+ struct TALER_Amount amount;
+ enum GNUNET_GenericReturnValue ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("amount",
+ h->currency,
+ &amount),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ (ret = TALER_MHD_parse_json_data (connection,
+ json,
+ spec)))
+ {
+ GNUNET_break_op (0);
+ json_decref (json);
+ return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+ }
+ res = do_post_account_withdrawals (h,
+ connection,
+ account_name,
+ &amount);
+ }
+ json_decref (json);
+ return res;
+}
diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals.h b/src/bank-lib/fakebank_bank_post_accounts_withdrawals.h
new file mode 100644
index 000000000..1becf1efc
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals.h
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_post_accounts_withdrawals.c
+ * @brief implementation of the bank API's POST /accounts/AID/withdrawals endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_H
+#define FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle POST /accounts/$account_name/withdrawals request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_post_account_withdrawals_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account_name,
+ const void *upload_data,
+ size_t *upload_data_size,
+ void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_post_withdrawals_abort.c b/src/bank-lib/fakebank_bank_post_withdrawals_abort.c
new file mode 100644
index 000000000..f8ebf1b93
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_withdrawals_abort.c
@@ -0,0 +1,74 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_post_withdrawals_abort.c
+ * @brief implement bank API withdrawals /abort endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_post_withdrawals_abort.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_bank_withdrawals_abort_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *withdrawal_id)
+{
+ struct WithdrawalOperation *wo;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+ withdrawal_id);
+ if (NULL == wo)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+ withdrawal_id);
+ }
+ if (wo->confirmation_done)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_BANK_ABORT_CONFIRM_CONFLICT,
+ withdrawal_id);
+ }
+ wo->aborted = true;
+ TALER_FAKEBANK_notify_withdrawal_ (h,
+ wo);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_json (connection,
+ json_object (), /* FIXME: #7301 */
+ MHD_HTTP_OK);
+}
diff --git a/src/bank-lib/fakebank_bank_post_withdrawals_abort.h b/src/bank-lib/fakebank_bank_post_withdrawals_abort.h
new file mode 100644
index 000000000..920b0b802
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_withdrawals_abort.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_post_withdrawals_abort.h
+ * @brief implement bank API withdrawals /abort endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_POST_WITHDRAWALS_ABORT_H
+#define FAKEBANK_BANK_POST_WITHDRAWALS_ABORT_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle POST /withdrawals/{withdrawal_id}/abort request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param withdrawal_id the withdrawal operation identifier
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_withdrawals_abort_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *withdrawal_id);
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_post_withdrawals_confirm.c b/src/bank-lib/fakebank_bank_post_withdrawals_confirm.c
new file mode 100644
index 000000000..2fa67c970
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_withdrawals_confirm.c
@@ -0,0 +1,107 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_post_withdrawals_confirm.c
+ * @brief implement bank API withdrawals /confirm endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_post_withdrawals_confirm.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_common_make_admin_transfer.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_bank_withdrawals_confirm_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *withdrawal_id)
+{
+ struct WithdrawalOperation *wo;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+ withdrawal_id);
+ if (NULL == wo)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+ withdrawal_id);
+ }
+ if (NULL == wo->exchange_account)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_BANK_POST_WITHDRAWAL_OPERATION_REQUIRED,
+ NULL);
+ }
+ if (wo->aborted)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_BANK_CONFIRM_ABORT_CONFLICT,
+ withdrawal_id);
+ }
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ if (GNUNET_OK !=
+ TALER_FAKEBANK_make_admin_transfer_ (
+ h,
+ wo->debit_account->account_name,
+ wo->exchange_account->account_name,
+ &wo->amount,
+ &wo->reserve_pub,
+ &wo->row_id,
+ &wo->timestamp))
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
+ NULL);
+ }
+ /* Re-acquiring the lock and continuing to operate on 'wo'
+ is currently (!) acceptable because we NEVER free 'wo'
+ until shutdown. We may want to revise this if keeping
+ all withdraw operations in RAM becomes an issue... */
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ wo->confirmation_done = true;
+ TALER_FAKEBANK_notify_withdrawal_ (h,
+ wo);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_json (connection,
+ json_object (),
+ MHD_HTTP_OK);
+}
diff --git a/src/bank-lib/fakebank_bank_post_withdrawals_confirm.h b/src/bank-lib/fakebank_bank_post_withdrawals_confirm.h
new file mode 100644
index 000000000..56cd2deda
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_withdrawals_confirm.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_post_withdrawals_confirm.h
+ * @brief implement bank API withdrawals /confirm endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_POST_WITHDRAWALS_CONFIRM_H
+#define FAKEBANK_BANK_POST_WITHDRAWALS_CONFIRM_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle POST /accounts/{account_name}/withdrawals/{withdrawal_id}/confirm request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param withdrawal_id the withdrawal operation identifier
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_withdrawals_confirm_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *withdrawal_id);
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_post_withdrawals_id_op.c b/src/bank-lib/fakebank_bank_post_withdrawals_id_op.c
new file mode 100644
index 000000000..fe5cc982d
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_withdrawals_id_op.c
@@ -0,0 +1,241 @@
+/*
+ This file is part of TALER
+ (C) 2016-2024 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 bank-lib/fakebank_bank_post_withdrawals_id_op.c
+ * @brief implement bank API POST /accounts/$ACCOUNT/withdrawals/$WID/$OP endpoint(s)
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_post_withdrawals_id_op.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_common_make_admin_transfer.h"
+
+
+/**
+ * Handle POST /accounts/$ACC/withdrawals/{withdrawal_id}/confirm request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param account name of the account
+ * @param withdrawal_id the withdrawal operation identifier
+ * @return MHD result code
+ */
+static MHD_RESULT
+bank_withdrawals_confirm (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ const char *withdrawal_id)
+{
+ const struct Account *acc;
+ struct WithdrawalOperation *wo;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ acc = TALER_FAKEBANK_lookup_account_ (h,
+ account,
+ NULL);
+ if (NULL == acc)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Account %s is unknown\n",
+ account);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_UNKNOWN_ACCOUNT,
+ account);
+ }
+ wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+ withdrawal_id);
+ if ( (NULL == wo) ||
+ (acc != wo->debit_account) )
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+ withdrawal_id);
+ }
+ if (NULL == wo->exchange_account)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_BANK_POST_WITHDRAWAL_OPERATION_REQUIRED,
+ NULL);
+ }
+ if (wo->aborted)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_BANK_CONFIRM_ABORT_CONFLICT,
+ withdrawal_id);
+ }
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ if (GNUNET_OK !=
+ TALER_FAKEBANK_make_admin_transfer_ (
+ h,
+ wo->debit_account->account_name,
+ wo->exchange_account->account_name,
+ &wo->amount,
+ &wo->reserve_pub,
+ &wo->row_id,
+ &wo->timestamp))
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
+ NULL);
+ }
+ /* Re-acquiring the lock and continuing to operate on 'wo'
+ is currently (!) acceptable because we NEVER free 'wo'
+ until shutdown. We may want to revise this if keeping
+ all withdraw operations in RAM becomes an issue... */
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ wo->confirmation_done = true;
+ TALER_FAKEBANK_notify_withdrawal_ (h,
+ wo);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/**
+ * Handle POST /accounts/$ACC/withdrawals/{withdrawal_id}/abort request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param account name of the account
+ * @param withdrawal_id the withdrawal operation identifier
+ * @return MHD result code
+ */
+static MHD_RESULT
+bank_withdrawals_abort (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ const char *withdrawal_id)
+{
+ struct WithdrawalOperation *wo;
+ const struct Account *acc;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ acc = TALER_FAKEBANK_lookup_account_ (h,
+ account,
+ NULL);
+ if (NULL == acc)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Account %s is unknown\n",
+ account);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_UNKNOWN_ACCOUNT,
+ account);
+ }
+ wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+ withdrawal_id);
+ if ( (NULL == wo) ||
+ (acc != wo->debit_account) )
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+ withdrawal_id);
+ }
+ if (wo->confirmation_done)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_BANK_ABORT_CONFIRM_CONFLICT,
+ withdrawal_id);
+ }
+ wo->aborted = true;
+ TALER_FAKEBANK_notify_withdrawal_ (h,
+ wo);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+MHD_RESULT
+TALER_FAKEBANK_bank_withdrawals_id_op_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ const char *withdrawal_id,
+ const char *op,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ if (0 == strcmp (op,
+ "/confirm"))
+ {
+ return bank_withdrawals_confirm (h,
+ connection,
+ account,
+ withdrawal_id);
+ }
+ if (0 == strcmp (op,
+ "/abort"))
+ {
+ return bank_withdrawals_abort (h,
+ connection,
+ account,
+ withdrawal_id);
+ }
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ op);
+}
diff --git a/src/bank-lib/fakebank_bank_post_withdrawals_id_op.h b/src/bank-lib/fakebank_bank_post_withdrawals_id_op.h
new file mode 100644
index 000000000..a2d40e66f
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_withdrawals_id_op.h
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ (C) 2016-2024 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 bank-lib/fakebank_bank_post_withdrawals_id_op.h
+ * @brief implement bank API POST /accounts/$ACCOUNT/withdrawals/$WID/$OP endpoint(s)
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_POST_WITHDRAWALS_ID_OP_H
+#define FAKEBANK_BANK_POST_WITHDRAWALS_ID_OP_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle POST /accounts/{account_name}/withdrawals/{withdrawal_id}/${OP} request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param account name of the account
+ * @param withdrawal_id the withdrawal operation identifier
+ * @param op operation to be performed, includes leading "/"
+ * @param upload_data data uploaded
+ * @param[in,out] upload_data_size number of bytes in @a upload_data
+ * @param[in,out] con_cls application context that can be used
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_withdrawals_id_op_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ const char *withdrawal_id,
+ const char *op,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_testing_register.c b/src/bank-lib/fakebank_bank_testing_register.c
new file mode 100644
index 000000000..e5720f1a6
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_testing_register.c
@@ -0,0 +1,128 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_testing_register.c
+ * @brief implementation of /testing/register endpoint for the bank API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_testing_register.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_bank_testing_register_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const void *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ struct ConnectionContext *cc = *con_cls;
+ enum GNUNET_JSON_PostResult pr;
+ json_t *json;
+ MHD_RESULT res;
+
+ if (NULL == cc)
+ {
+ cc = GNUNET_new (struct ConnectionContext);
+ cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
+ *con_cls = cc;
+ }
+ pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
+ connection,
+ &cc->ctx,
+ upload_data,
+ upload_data_size,
+ &json);
+ switch (pr)
+ {
+ case GNUNET_JSON_PR_OUT_OF_MEMORY:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_CONTINUE:
+ return MHD_YES;
+ case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_JSON_INVALID:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_SUCCESS:
+ break;
+ }
+
+ {
+ const char *username;
+ const char *password;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("username",
+ &username),
+ GNUNET_JSON_spec_string ("password",
+ &password),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue ret;
+ struct Account *acc;
+
+ if (GNUNET_OK !=
+ (ret = TALER_MHD_parse_json_data (connection,
+ json,
+ spec)))
+ {
+ GNUNET_break_op (0);
+ json_decref (json);
+ return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+ }
+ acc = TALER_FAKEBANK_lookup_account_ (h,
+ username,
+ NULL);
+ if (NULL != acc)
+ {
+ if (0 != strcmp (password,
+ acc->password))
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_BANK_REGISTER_CONFLICT,
+ "password");
+ }
+ }
+ else
+ {
+ acc = TALER_FAKEBANK_lookup_account_ (h,
+ username,
+ username);
+ GNUNET_assert (NULL != acc);
+ acc->password = GNUNET_strdup (password);
+ acc->balance = h->signup_bonus; /* magic money creation! */
+ }
+ res = TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ }
+ json_decref (json);
+ return res;
+}
diff --git a/src/bank-lib/fakebank_bank_testing_register.h b/src/bank-lib/fakebank_bank_testing_register.h
new file mode 100644
index 000000000..d8744ecc2
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_testing_register.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_bank_testing_register.h
+ * @brief implementation of /testing/register endpoint for the bank API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_TESTING_REGISTER_H
+#define FAKEBANK_BANK_TESTING_REGISTER_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+
+
+/**
+ * Handle POST /testing/register request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_testing_register_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const void *upload_data,
+ size_t *upload_data_size,
+ void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_common_lookup.c b/src/bank-lib/fakebank_common_lookup.c
new file mode 100644
index 000000000..b4f853871
--- /dev/null
+++ b/src/bank-lib/fakebank_common_lookup.c
@@ -0,0 +1,103 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_common_lookup.c
+ * @brief common helper functions related to lookups
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+
+struct WithdrawalOperation *
+TALER_FAKEBANK_lookup_withdrawal_operation_ (struct TALER_FAKEBANK_Handle *h,
+ const char *wopid)
+{
+ struct GNUNET_ShortHashCode sh;
+
+ if (NULL == h->wops)
+ return NULL;
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (wopid,
+ strlen (wopid),
+ &sh,
+ sizeof (sh)))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ return GNUNET_CONTAINER_multishortmap_get (h->wops,
+ &sh);
+}
+
+
+struct Account *
+TALER_FAKEBANK_lookup_account_ (struct TALER_FAKEBANK_Handle *h,
+ const char *name,
+ const char *receiver_name)
+{
+ struct GNUNET_HashCode hc;
+ size_t slen;
+ struct Account *account;
+
+ memset (&hc,
+ 0,
+ sizeof (hc));
+ slen = strlen (name);
+ GNUNET_CRYPTO_hash (name,
+ slen,
+ &hc);
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->accounts_lock));
+ account = GNUNET_CONTAINER_multihashmap_get (h->accounts,
+ &hc);
+ if (NULL == account)
+ {
+ if (NULL == receiver_name)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->accounts_lock));
+ return NULL;
+ }
+ account = GNUNET_new (struct Account);
+ account->account_name = GNUNET_strdup (name);
+ account->receiver_name = GNUNET_strdup (receiver_name);
+ GNUNET_asprintf (&account->payto_uri,
+ "payto://x-taler-bank/%s/%s?receiver-name=%s",
+ h->hostname,
+ account->account_name,
+ account->receiver_name);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (h->currency,
+ &account->balance));
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (h->accounts,
+ &hc,
+ account,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ }
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->accounts_lock));
+ return account;
+}
diff --git a/src/bank-lib/fakebank_common_lookup.h b/src/bank-lib/fakebank_common_lookup.h
new file mode 100644
index 000000000..b93447743
--- /dev/null
+++ b/src/bank-lib/fakebank_common_lookup.h
@@ -0,0 +1,62 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_common_lookup.h
+ * @brief common helper functions related to lookups
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+
+#ifndef FAKEBANK_COMMON_LOOKUP_H
+#define FAKEBANK_COMMON_LOOKUP_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Lookup account with @a name, and if it does not exist, create it.
+ *
+ * @param[in,out] h bank to lookup account at
+ * @param name account name to resolve
+ * @param receiver_name receiver name in payto:// URI,
+ * NULL if the account must already exist
+ * @return account handle, NULL if account does not yet exist
+ */
+struct Account *
+TALER_FAKEBANK_lookup_account_ (
+ struct TALER_FAKEBANK_Handle *h,
+ const char *name,
+ const char *receiver_name);
+
+
+/**
+ * Find withdrawal operation @a wopid in @a h.
+ *
+ * @param h fakebank handle
+ * @param wopid withdrawal operation ID as a string
+ * @return NULL if operation was not found
+ */
+struct WithdrawalOperation *
+TALER_FAKEBANK_lookup_withdrawal_operation_ (struct TALER_FAKEBANK_Handle *h,
+ const char *wopid);
+
+#endif
diff --git a/src/bank-lib/fakebank_common_lp.c b/src/bank-lib/fakebank_common_lp.c
new file mode 100644
index 000000000..22a9e3ab4
--- /dev/null
+++ b/src/bank-lib/fakebank_common_lp.c
@@ -0,0 +1,344 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_common_lp.c
+ * @brief long-polling support for fakebank
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include <poll.h>
+#ifdef __linux__
+#include <sys/eventfd.h>
+#endif
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+void
+TALER_FAKEBANK_lp_trigger_ (struct LongPoller *lp)
+{
+ struct TALER_FAKEBANK_Handle *h = lp->h;
+ struct Account *acc = lp->account;
+
+ GNUNET_CONTAINER_DLL_remove (acc->lp_head,
+ acc->lp_tail,
+ lp);
+ MHD_resume_connection (lp->conn);
+ GNUNET_free (lp);
+ h->mhd_again = true;
+#ifdef __linux__
+ if (-1 == h->lp_event)
+#else
+ if ( (-1 == h->lp_event_in) &&
+ (-1 == h->lp_event_out) )
+#endif
+ {
+ if (NULL != h->mhd_task)
+ GNUNET_SCHEDULER_cancel (h->mhd_task);
+ h->mhd_task =
+ GNUNET_SCHEDULER_add_now (&TALER_FAKEBANK_run_mhd_,
+ h);
+ }
+}
+
+
+void *
+TALER_FAKEBANK_lp_expiration_thread_ (void *cls)
+{
+ struct TALER_FAKEBANK_Handle *h = cls;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ while (! h->in_shutdown)
+ {
+ struct LongPoller *lp;
+ int timeout_ms;
+
+ lp = GNUNET_CONTAINER_heap_peek (h->lp_heap);
+ while ( (NULL != lp) &&
+ GNUNET_TIME_absolute_is_past (lp->timeout))
+ {
+ GNUNET_assert (lp ==
+ GNUNET_CONTAINER_heap_remove_root (h->lp_heap));
+ TALER_FAKEBANK_lp_trigger_ (lp);
+ lp = GNUNET_CONTAINER_heap_peek (h->lp_heap);
+ }
+ if (NULL != lp)
+ {
+ struct GNUNET_TIME_Relative rem;
+ unsigned long long left_ms;
+
+ rem = GNUNET_TIME_absolute_get_remaining (lp->timeout);
+ left_ms = rem.rel_value_us / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
+ if (left_ms > INT_MAX)
+ timeout_ms = INT_MAX;
+ else
+ timeout_ms = (int) left_ms;
+ }
+ else
+ {
+ timeout_ms = -1; /* infinity */
+ }
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ {
+ struct pollfd p = {
+#ifdef __linux__
+ .fd = h->lp_event,
+#else
+ .fd = h->lp_event_out,
+#endif
+ .events = POLLIN
+ };
+ int ret;
+
+ ret = poll (&p,
+ 1,
+ timeout_ms);
+ if (-1 == ret)
+ {
+ if (EINTR != errno)
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "poll");
+ }
+ else if (1 == ret)
+ {
+ /* clear event */
+ uint64_t ev;
+ ssize_t iret;
+
+#ifdef __linux__
+ iret = read (h->lp_event,
+ &ev,
+ sizeof (ev));
+#else
+ iret = read (h->lp_event_out,
+ &ev,
+ sizeof (ev));
+#endif
+ if (-1 == iret)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "read");
+ }
+ else
+ {
+ GNUNET_break (sizeof (uint64_t) == iret);
+ }
+ }
+ }
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ }
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return NULL;
+}
+
+
+/**
+ * Trigger long pollers that might have been waiting
+ * for @a t.
+ *
+ * @param h fakebank handle
+ * @param t transaction to notify on
+ */
+void
+TALER_FAKEBANK_notify_transaction_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct Transaction *t)
+{
+ struct Account *debit_acc = t->debit_account;
+ struct Account *credit_acc = t->credit_account;
+ struct LongPoller *nxt;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ for (struct LongPoller *lp = debit_acc->lp_head;
+ NULL != lp;
+ lp = nxt)
+ {
+ nxt = lp->next;
+ if (LP_DEBIT == lp->type)
+ {
+ GNUNET_assert (lp ==
+ GNUNET_CONTAINER_heap_remove_node (lp->hn));
+ TALER_FAKEBANK_lp_trigger_ (lp);
+ }
+ }
+ for (struct LongPoller *lp = credit_acc->lp_head;
+ NULL != lp;
+ lp = nxt)
+ {
+ nxt = lp->next;
+ if (LP_CREDIT == lp->type)
+ {
+ GNUNET_assert (lp ==
+ GNUNET_CONTAINER_heap_remove_node (lp->hn));
+ TALER_FAKEBANK_lp_trigger_ (lp);
+ }
+ }
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+}
+
+
+/**
+ * Notify long pollers that a @a wo was updated.
+ * Must be called with the "big_lock" still held.
+ *
+ * @param h fakebank handle
+ * @param wo withdraw operation that finished
+ */
+void
+TALER_FAKEBANK_notify_withdrawal_ (
+ struct TALER_FAKEBANK_Handle *h,
+ const struct WithdrawalOperation *wo)
+{
+ struct Account *debit_acc = wo->debit_account;
+ struct LongPoller *nxt;
+
+ for (struct LongPoller *lp = debit_acc->lp_head;
+ NULL != lp;
+ lp = nxt)
+ {
+ nxt = lp->next;
+ if ( (LP_WITHDRAW == lp->type) &&
+ (wo == lp->wo) )
+ {
+ GNUNET_assert (lp ==
+ GNUNET_CONTAINER_heap_remove_node (lp->hn));
+ TALER_FAKEBANK_lp_trigger_ (lp);
+ }
+ }
+}
+
+
+/**
+ * Task run when a long poller is about to time out.
+ * Only used in single-threaded mode.
+ *
+ * @param cls a `struct TALER_FAKEBANK_Handle *`
+ */
+static void
+lp_timeout (void *cls)
+{
+ struct TALER_FAKEBANK_Handle *h = cls;
+ struct LongPoller *lp;
+
+ h->lp_task = NULL;
+ while (NULL != (lp = GNUNET_CONTAINER_heap_peek (h->lp_heap)))
+ {
+ if (GNUNET_TIME_absolute_is_future (lp->timeout))
+ break;
+ GNUNET_assert (lp ==
+ GNUNET_CONTAINER_heap_remove_root (h->lp_heap));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Timeout reached for long poller %p\n",
+ lp->conn);
+ TALER_FAKEBANK_lp_trigger_ (lp);
+ }
+ if (NULL == lp)
+ return;
+ h->lp_task = GNUNET_SCHEDULER_add_at (lp->timeout,
+ &lp_timeout,
+ h);
+}
+
+
+/**
+ * Reschedule the timeout task of @a h for time @a t.
+ *
+ * @param h fakebank handle
+ * @param t when will the next connection timeout expire
+ */
+static void
+reschedule_lp_timeout (struct TALER_FAKEBANK_Handle *h,
+ struct GNUNET_TIME_Absolute t)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Scheduling timeout task for %s\n",
+ GNUNET_STRINGS_absolute_time_to_string (t));
+#ifdef __linux__
+ if (-1 != h->lp_event)
+#else
+ if (-1 != h->lp_event_in && -1 != h->lp_event_out)
+#endif
+ {
+ uint64_t num = 1;
+
+ GNUNET_break (sizeof (num) ==
+#ifdef __linux__
+ write (h->lp_event,
+ &num,
+ sizeof (num)));
+#else
+ write (h->lp_event_in,
+ &num,
+ sizeof (num)));
+#endif
+ }
+ else
+ {
+ if (NULL != h->lp_task)
+ GNUNET_SCHEDULER_cancel (h->lp_task);
+ h->lp_task = GNUNET_SCHEDULER_add_at (t,
+ &lp_timeout,
+ h);
+ }
+}
+
+
+void
+TALER_FAKEBANK_start_lp_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ struct Account *acc,
+ struct GNUNET_TIME_Relative lp_timeout,
+ enum LongPollType dir,
+ const struct WithdrawalOperation *wo)
+{
+ struct LongPoller *lp;
+ bool toc;
+
+ lp = GNUNET_new (struct LongPoller);
+ lp->account = acc;
+ lp->h = h;
+ lp->wo = wo;
+ lp->conn = connection;
+ lp->timeout = GNUNET_TIME_relative_to_absolute (lp_timeout);
+ lp->type = dir;
+ lp->hn = GNUNET_CONTAINER_heap_insert (h->lp_heap,
+ lp,
+ lp->timeout.abs_value_us);
+ toc = (lp ==
+ GNUNET_CONTAINER_heap_peek (h->lp_heap));
+ GNUNET_CONTAINER_DLL_insert (acc->lp_head,
+ acc->lp_tail,
+ lp);
+ MHD_suspend_connection (connection);
+ if (toc)
+ reschedule_lp_timeout (h,
+ lp->timeout);
+
+}
diff --git a/src/bank-lib/fakebank_common_lp.h b/src/bank-lib/fakebank_common_lp.h
new file mode 100644
index 000000000..37094e12b
--- /dev/null
+++ b/src/bank-lib/fakebank_common_lp.h
@@ -0,0 +1,100 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_common_lp.h
+ * @brief long-polling support for fakebank
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_COMMON_LP_H
+#define FAKEBANK_COMMON_LP_H
+#include "taler_fakebank_lib.h"
+
+
+/**
+ * Trigger the @a lp. Frees associated resources, except the entry of @a lp in
+ * the timeout heap. Must be called while the ``big lock`` is held.
+ *
+ * @param[in] lp long poller to trigger
+ */
+void
+TALER_FAKEBANK_lp_trigger_ (struct LongPoller *lp);
+
+
+/**
+ * Trigger long pollers that might have been waiting
+ * for @a t.
+ *
+ * @param h fakebank handle
+ * @param t transaction to notify on
+ */
+void
+TALER_FAKEBANK_notify_transaction_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct Transaction *t);
+
+
+/**
+ * Notify long pollers that a @a wo was updated.
+ * Must be called with the "big_lock" still held.
+ *
+ * @param h fakebank handle
+ * @param wo withdraw operation that finished
+ */
+void
+TALER_FAKEBANK_notify_withdrawal_ (
+ struct TALER_FAKEBANK_Handle *h,
+ const struct WithdrawalOperation *wo);
+
+
+/**
+ * Start long-polling for @a connection and @a acc
+ * for transfers in @a dir. Must be called with the
+ * "big lock" held.
+ *
+ * @param[in,out] h fakebank handle
+ * @param[in,out] connection to suspend
+ * @param[in,out] acc account affected
+ * @param lp_timeout how long to suspend
+ * @param dir direction of transfers to watch for
+ * @param wo withdraw operation to watch, only
+ * if @a dir is #LP_WITHDRAW
+ */
+void
+TALER_FAKEBANK_start_lp_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ struct Account *acc,
+ struct GNUNET_TIME_Relative lp_timeout,
+ enum LongPollType dir,
+ const struct WithdrawalOperation *wo);
+
+
+/**
+ * Main routine of a thread that is run to wake up connections that have hit
+ * their timeout. Runs until in_shutdown is set to true. Must be send signals
+ * via lp_event on shutdown and/or whenever the heap changes to an earlier
+ * timeout.
+ *
+ * @param cls a `struct TALER_FAKEBANK_Handle *`
+ * @return NULL
+ */
+void *
+TALER_FAKEBANK_lp_expiration_thread_ (void *cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_common_make_admin_transfer.c b/src/bank-lib/fakebank_common_make_admin_transfer.c
new file mode 100644
index 000000000..4a11d412c
--- /dev/null
+++ b/src/bank-lib/fakebank_common_make_admin_transfer.c
@@ -0,0 +1,117 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_common_make_admin_transfer.c
+ * @brief routine to create transfers to the exchange
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_common_transact.h"
+
+
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_make_admin_transfer_ (
+ struct TALER_FAKEBANK_Handle *h,
+ const char *debit_account,
+ const char *credit_account,
+ const struct TALER_Amount *amount,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *row_id,
+ struct GNUNET_TIME_Timestamp *timestamp)
+{
+ struct Transaction *t;
+ const struct GNUNET_PeerIdentity *pid;
+ struct Account *debit_acc;
+ struct Account *credit_acc;
+
+ GNUNET_static_assert (sizeof (*pid) ==
+ sizeof (*reserve_pub));
+ pid = (const struct GNUNET_PeerIdentity *) reserve_pub;
+ GNUNET_assert (NULL != debit_account);
+ GNUNET_assert (NULL != credit_account);
+ GNUNET_assert (0 == strcasecmp (amount->currency,
+ h->currency));
+ GNUNET_break (0 != strncasecmp ("payto://",
+ debit_account,
+ strlen ("payto://")));
+ GNUNET_break (0 != strncasecmp ("payto://",
+ credit_account,
+ strlen ("payto://")));
+ debit_acc = TALER_FAKEBANK_lookup_account_ (h,
+ debit_account,
+ debit_account);
+ credit_acc = TALER_FAKEBANK_lookup_account_ (h,
+ credit_account,
+ credit_account);
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->rpubs_lock));
+ t = GNUNET_CONTAINER_multipeermap_get (h->rpubs,
+ pid);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->rpubs_lock));
+ if (NULL != t)
+ {
+ /* duplicate reserve public key not allowed */
+ GNUNET_break_op (0);
+ return GNUNET_NO;
+ }
+
+ t = GNUNET_new (struct Transaction);
+ t->unchecked = true;
+ t->debit_account = debit_acc;
+ t->credit_account = credit_acc;
+ t->amount = *amount;
+ t->date = GNUNET_TIME_timestamp_get ();
+ if (NULL != timestamp)
+ *timestamp = t->date;
+ t->type = T_CREDIT;
+ t->subject.credit.reserve_pub = *reserve_pub;
+ TALER_FAKEBANK_transact_ (h,
+ t);
+ if (NULL != row_id)
+ *row_id = t->row_id;
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->rpubs_lock));
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multipeermap_put (
+ h->rpubs,
+ pid,
+ t,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->rpubs_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Making transfer from %s to %s over %s and subject %s at row %llu\n",
+ debit_account,
+ credit_account,
+ TALER_amount2s (amount),
+ TALER_B2S (reserve_pub),
+ (unsigned long long) t->row_id);
+ TALER_FAKEBANK_notify_transaction_ (h,
+ t);
+ return GNUNET_OK;
+}
diff --git a/src/bank-lib/fakebank_common_make_admin_transfer.h b/src/bank-lib/fakebank_common_make_admin_transfer.h
new file mode 100644
index 000000000..841cfb481
--- /dev/null
+++ b/src/bank-lib/fakebank_common_make_admin_transfer.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_common_make_admin_transfer.h
+ * @brief routine to create transfers to the exchange
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_COMMON_MAKE_ADMIN_TRANSFER_H
+#define FAKEBANK_COMMON_MAKE_ADMIN_TRANSFER_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Tell the fakebank to create another wire transfer *to* an exchange.
+ *
+ * @param h fake bank handle
+ * @param debit_account account to debit
+ * @param credit_account account to credit
+ * @param amount amount to transfer
+ * @param reserve_pub reserve public key to use in subject
+ * @param[out] row_id serial_id of the transfer
+ * @param[out] timestamp when was the transfer made
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_make_admin_transfer_ (
+ struct TALER_FAKEBANK_Handle *h,
+ const char *debit_account,
+ const char *credit_account,
+ const struct TALER_Amount *amount,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *row_id,
+ struct GNUNET_TIME_Timestamp *timestamp);
+
+#endif
diff --git a/src/bank-lib/fakebank_common_parser.c b/src/bank-lib/fakebank_common_parser.c
new file mode 100644
index 000000000..cf2dc5a74
--- /dev/null
+++ b/src/bank-lib/fakebank_common_parser.c
@@ -0,0 +1,138 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_common_parser.c
+ * @brief functions to help parse REST requests
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_common_parse_history_args (
+ const struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ struct HistoryArgs *ha)
+{
+ const char *start;
+ const char *delta;
+ const char *long_poll_ms;
+ unsigned long long lp_timeout;
+ unsigned long long sval;
+ long long d;
+ char dummy;
+
+ start = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "start");
+ ha->have_start = (NULL != start);
+ delta = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "delta");
+ long_poll_ms = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "long_poll_ms");
+ lp_timeout = 0;
+ if ( (NULL == delta) ||
+ (1 != sscanf (delta,
+ "%lld%c",
+ &d,
+ &dummy)) )
+ {
+ /* Fail if one of the above failed. */
+ /* Invalid request, given that this is fakebank we impolitely
+ * just kill the connection instead of returning a nice error.
+ */
+ GNUNET_break_op (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "delta"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if ( (NULL != long_poll_ms) &&
+ (1 != sscanf (long_poll_ms,
+ "%llu%c",
+ &lp_timeout,
+ &dummy)) )
+ {
+ /* Fail if one of the above failed. */
+ /* Invalid request, given that this is fakebank we impolitely
+ * just kill the connection instead of returning a nice error.
+ */
+ GNUNET_break_op (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "long_poll_ms"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if ( (NULL != start) &&
+ (1 != sscanf (start,
+ "%llu%c",
+ &sval,
+ &dummy)) )
+ {
+ /* Fail if one of the above failed. */
+ /* Invalid request, given that this is fakebank we impolitely
+ * just kill the connection instead of returning a nice error.
+ */
+ GNUNET_break_op (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "start"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (NULL == start)
+ ha->start_idx = (d > 0) ? 0 : UINT64_MAX;
+ else
+ ha->start_idx = (uint64_t) sval;
+ ha->delta = (int64_t) d;
+ if (0 == ha->delta)
+ {
+ GNUNET_break_op (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "delta"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ ha->lp_timeout
+ = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+ lp_timeout);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Request for %lld records from %llu\n",
+ (long long) ha->delta,
+ (unsigned long long) ha->start_idx);
+ return GNUNET_OK;
+}
diff --git a/src/bank-lib/fakebank_common_parser.h b/src/bank-lib/fakebank_common_parser.h
new file mode 100644
index 000000000..8e0d14649
--- /dev/null
+++ b/src/bank-lib/fakebank_common_parser.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_common_parser.h
+ * @brief functions to help parse REST requests
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_COMMON_PARSER_H
+#define FAKEBANK_COMMON_PARSER_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Parse URL history arguments, of _both_ APIs:
+ * /history/incoming and /history/outgoing.
+ *
+ * @param h bank handle to work on
+ * @param connection MHD connection.
+ * @param[out] ha will contain the parsed values.
+ * @return #GNUNET_OK only if the parsing succeeds,
+ * #GNUNET_SYSERR if it failed,
+ * #GNUNET_NO if it failed and an error was returned
+ */
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_common_parse_history_args (
+ const struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ struct HistoryArgs *ha);
+
+#endif
diff --git a/src/bank-lib/fakebank_common_transact.c b/src/bank-lib/fakebank_common_transact.c
new file mode 100644
index 000000000..a099ef966
--- /dev/null
+++ b/src/bank-lib/fakebank_common_transact.c
@@ -0,0 +1,261 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_common_transact.c
+ * @brief actual transaction logic for FAKEBANK
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_common_transact.h"
+
+
+/**
+ * Update @a account balance by @a amount.
+ *
+ * The @a big_lock must already be locked when calling
+ * this function.
+ *
+ * @param[in,out] account account to update
+ * @param amount balance change
+ * @param debit true to subtract, false to add @a amount
+ */
+static void
+update_balance (struct Account *account,
+ const struct TALER_Amount *amount,
+ bool debit)
+{
+ if (debit == account->is_negative)
+ {
+ GNUNET_assert (0 <=
+ TALER_amount_add (&account->balance,
+ &account->balance,
+ amount));
+ return;
+ }
+ if (0 <= TALER_amount_cmp (&account->balance,
+ amount))
+ {
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&account->balance,
+ &account->balance,
+ amount));
+ }
+ else
+ {
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&account->balance,
+ amount,
+ &account->balance));
+ account->is_negative = ! account->is_negative;
+ }
+}
+
+
+/**
+ * Add transaction to the debit and credit accounts,
+ * updating the balances as needed.
+ *
+ * The transaction @a t must already be locked
+ * when calling this function!
+ *
+ * @param[in,out] h bank handle
+ * @param[in,out] t transaction to add to account lists
+ */
+void
+TALER_FAKEBANK_transact_ (struct TALER_FAKEBANK_Handle *h,
+ struct Transaction *t)
+{
+ struct Account *debit_acc = t->debit_account;
+ struct Account *credit_acc = t->credit_account;
+ uint64_t row_id;
+ struct Transaction *old;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ row_id = ++h->serial_counter;
+ old = h->transactions[row_id % h->ram_limit];
+ h->transactions[row_id % h->ram_limit] = t;
+ t->row_id = row_id;
+ GNUNET_CONTAINER_MDLL_insert_tail (out,
+ debit_acc->out_head,
+ debit_acc->out_tail,
+ t);
+ update_balance (debit_acc,
+ &t->amount,
+ true);
+ GNUNET_CONTAINER_MDLL_insert_tail (in,
+ credit_acc->in_head,
+ credit_acc->in_tail,
+ t);
+ update_balance (credit_acc,
+ &t->amount,
+ false);
+ if (NULL != old)
+ {
+ struct Account *da;
+ struct Account *ca;
+
+ da = old->debit_account;
+ ca = old->credit_account;
+ /* slot was already in use, must clean out old
+ entry first! */
+ GNUNET_CONTAINER_MDLL_remove (out,
+ da->out_head,
+ da->out_tail,
+ old);
+ GNUNET_CONTAINER_MDLL_remove (in,
+ ca->in_head,
+ ca->in_tail,
+ old);
+ }
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ if ( (NULL != old) &&
+ (T_DEBIT == old->type) )
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->uuid_map_lock));
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_remove (h->uuid_map,
+ &old->request_uid,
+ old));
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->uuid_map_lock));
+ }
+ GNUNET_free (old);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_make_transfer_ (
+ struct TALER_FAKEBANK_Handle *h,
+ const char *debit_account,
+ const char *credit_account,
+ const struct TALER_Amount *amount,
+ const struct TALER_WireTransferIdentifierRawP *subject,
+ const char *exchange_base_url,
+ const struct GNUNET_HashCode *request_uid,
+ uint64_t *ret_row_id,
+ struct GNUNET_TIME_Timestamp *timestamp)
+{
+ struct Transaction *t;
+ struct Account *debit_acc;
+ struct Account *credit_acc;
+ size_t url_len;
+
+ GNUNET_assert (0 == strcasecmp (amount->currency,
+ h->currency));
+ GNUNET_assert (NULL != debit_account);
+ GNUNET_assert (NULL != credit_account);
+ GNUNET_break (0 != strncasecmp ("payto://",
+ debit_account,
+ strlen ("payto://")));
+ GNUNET_break (0 != strncasecmp ("payto://",
+ credit_account,
+ strlen ("payto://")));
+ url_len = strlen (exchange_base_url);
+ GNUNET_assert (url_len < MAX_URL_LEN);
+ debit_acc = TALER_FAKEBANK_lookup_account_ (h,
+ debit_account,
+ debit_account);
+ credit_acc = TALER_FAKEBANK_lookup_account_ (h,
+ credit_account,
+ credit_account);
+ if (NULL != request_uid)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->uuid_map_lock));
+ t = GNUNET_CONTAINER_multihashmap_get (h->uuid_map,
+ request_uid);
+ if (NULL != t)
+ {
+ if ( (debit_acc != t->debit_account) ||
+ (credit_acc != t->credit_account) ||
+ (0 != TALER_amount_cmp (amount,
+ &t->amount)) ||
+ (T_DEBIT != t->type) ||
+ (0 != GNUNET_memcmp (subject,
+ &t->subject.debit.wtid)) )
+ {
+ /* Transaction exists, but with different details. */
+ GNUNET_break (0);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->uuid_map_lock));
+ return GNUNET_SYSERR;
+ }
+ *ret_row_id = t->row_id;
+ *timestamp = t->date;
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->uuid_map_lock));
+ return GNUNET_OK;
+ }
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->uuid_map_lock));
+ }
+ t = GNUNET_new (struct Transaction);
+ t->unchecked = true;
+ t->debit_account = debit_acc;
+ t->credit_account = credit_acc;
+ t->amount = *amount;
+ t->date = GNUNET_TIME_timestamp_get ();
+ if (NULL != timestamp)
+ *timestamp = t->date;
+ t->type = T_DEBIT;
+ GNUNET_memcpy (t->subject.debit.exchange_base_url,
+ exchange_base_url,
+ url_len);
+ t->subject.debit.wtid = *subject;
+ if (NULL == request_uid)
+ GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE,
+ &t->request_uid);
+ else
+ t->request_uid = *request_uid;
+ TALER_FAKEBANK_transact_ (h,
+ t);
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->uuid_map_lock));
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (
+ h->uuid_map,
+ &t->request_uid,
+ t,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->uuid_map_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Making transfer %llu from %s to %s over %s and subject %s; for exchange: %s\n",
+ (unsigned long long) t->row_id,
+ debit_account,
+ credit_account,
+ TALER_amount2s (amount),
+ TALER_B2S (subject),
+ exchange_base_url);
+ *ret_row_id = t->row_id;
+ TALER_FAKEBANK_notify_transaction_ (h,
+ t);
+ return GNUNET_OK;
+}
diff --git a/src/bank-lib/fakebank_common_transact.h b/src/bank-lib/fakebank_common_transact.h
new file mode 100644
index 000000000..0914785e9
--- /dev/null
+++ b/src/bank-lib/fakebank_common_transact.h
@@ -0,0 +1,76 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_common_transact.h
+ * @brief actual transaction logic for FAKEBANK
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_COMMON_TRANSACT_H
+#define FAKEBANK_COMMON_TRANSACT_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Add transaction to the debit and credit accounts,
+ * updating the balances as needed.
+ *
+ * The transaction @a t must already be locked
+ * when calling this function!
+ *
+ * @param[in,out] h bank handle
+ * @param[in,out] t transaction to add to account lists
+ */
+void
+TALER_FAKEBANK_transact_ (struct TALER_FAKEBANK_Handle *h,
+ struct Transaction *t);
+
+
+/**
+ * Tell the fakebank to create another wire transfer *from* an exchange.
+ *
+ * @param h fake bank handle
+ * @param debit_account account to debit
+ * @param credit_account account to credit
+ * @param amount amount to transfer
+ * @param subject wire transfer subject to use
+ * @param exchange_base_url exchange URL
+ * @param request_uid unique number to make the request unique, or NULL to create one
+ * @param[out] ret_row_id pointer to store the row ID of this transaction
+ * @param[out] timestamp set to the time of the transfer
+ * @return #GNUNET_YES if the transfer was successful,
+ * #GNUNET_SYSERR if the request_uid was reused for a different transfer
+ */
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_make_transfer_ (
+ struct TALER_FAKEBANK_Handle *h,
+ const char *debit_account,
+ const char *credit_account,
+ const struct TALER_Amount *amount,
+ const struct TALER_WireTransferIdentifierRawP *subject,
+ const char *exchange_base_url,
+ const struct GNUNET_HashCode *request_uid,
+ uint64_t *ret_row_id,
+ struct GNUNET_TIME_Timestamp *timestamp);
+
+#endif
diff --git a/src/bank-lib/fakebank_stop.c b/src/bank-lib/fakebank_stop.c
new file mode 100644
index 000000000..e31d47523
--- /dev/null
+++ b/src/bank-lib/fakebank_stop.c
@@ -0,0 +1,192 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_stop.c
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include <poll.h>
+#ifdef __linux__
+#include <sys/eventfd.h>
+#endif
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lp.h"
+
+
+/**
+ * Helper function to free memory when finished.
+ *
+ * @param cls NULL
+ * @param key key of the account to free (ignored)
+ * @param val a `struct Account` to free.
+ */
+static enum GNUNET_GenericReturnValue
+free_account (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *val)
+{
+ struct Account *account = val;
+
+ (void) cls;
+ (void) key;
+ GNUNET_assert (NULL == account->lp_head);
+ GNUNET_free (account->account_name);
+ GNUNET_free (account->receiver_name);
+ GNUNET_free (account->payto_uri);
+ GNUNET_free (account->password);
+ GNUNET_free (account);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Helper function to free memory when finished.
+ *
+ * @param cls NULL
+ * @param key key of the operation to free (ignored)
+ * @param val a `struct WithdrawalOperation *` to free.
+ */
+static enum GNUNET_GenericReturnValue
+free_withdraw_op (void *cls,
+ const struct GNUNET_ShortHashCode *key,
+ void *val)
+{
+ struct WithdrawalOperation *wo = val;
+
+ (void) cls;
+ (void) key;
+ GNUNET_free (wo);
+ return GNUNET_OK;
+}
+
+
+void
+TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h)
+{
+ if (NULL != h->lp_task)
+ {
+ GNUNET_SCHEDULER_cancel (h->lp_task);
+ h->lp_task = NULL;
+ }
+#if EPOLL_SUPPORT
+ if (NULL != h->mhd_rfd)
+ {
+ GNUNET_NETWORK_socket_free_memory_only_ (h->mhd_rfd);
+ h->mhd_rfd = NULL;
+ }
+#endif
+#ifdef __linux__
+ if (-1 != h->lp_event)
+#else
+ if (-1 != h->lp_event_in && -1 != h->lp_event_out)
+#endif
+ {
+ uint64_t val = 1;
+ void *ret;
+ struct LongPoller *lp;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ h->in_shutdown = true;
+ while (NULL != (lp = GNUNET_CONTAINER_heap_remove_root (h->lp_heap)))
+ TALER_FAKEBANK_lp_trigger_ (lp);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+#ifdef __linux__
+ GNUNET_break (sizeof (val) ==
+ write (h->lp_event,
+ &val,
+ sizeof (val)));
+#else
+ GNUNET_break (sizeof (val) ==
+ write (h->lp_event_in,
+ &val,
+ sizeof (val)));
+#endif
+ GNUNET_break (0 ==
+ pthread_join (h->lp_thread,
+ &ret));
+ GNUNET_break (NULL == ret);
+#ifdef __linux__
+ GNUNET_break (0 == close (h->lp_event));
+ h->lp_event = -1;
+#else
+ GNUNET_break (0 == close (h->lp_event_in));
+ GNUNET_break (0 == close (h->lp_event_out));
+ h->lp_event_in = -1;
+ h->lp_event_out = -1;
+#endif
+ }
+ else
+ {
+ struct LongPoller *lp;
+
+ while (NULL != (lp = GNUNET_CONTAINER_heap_remove_root (h->lp_heap)))
+ TALER_FAKEBANK_lp_trigger_ (lp);
+ }
+ if (NULL != h->mhd_bank)
+ {
+ MHD_stop_daemon (h->mhd_bank);
+ h->mhd_bank = NULL;
+ }
+ if (NULL != h->mhd_task)
+ {
+ GNUNET_SCHEDULER_cancel (h->mhd_task);
+ h->mhd_task = NULL;
+ }
+ if (NULL != h->accounts)
+ {
+ GNUNET_CONTAINER_multihashmap_iterate (h->accounts,
+ &free_account,
+ NULL);
+ GNUNET_CONTAINER_multihashmap_destroy (h->accounts);
+ }
+ if (NULL != h->wops)
+ {
+ GNUNET_CONTAINER_multishortmap_iterate (h->wops,
+ &free_withdraw_op,
+ NULL);
+ GNUNET_CONTAINER_multishortmap_destroy (h->wops);
+ }
+ GNUNET_CONTAINER_multihashmap_destroy (h->uuid_map);
+ GNUNET_CONTAINER_multipeermap_destroy (h->rpubs);
+ GNUNET_CONTAINER_heap_destroy (h->lp_heap);
+ GNUNET_assert (0 ==
+ pthread_mutex_destroy (&h->big_lock));
+ GNUNET_assert (0 ==
+ pthread_mutex_destroy (&h->uuid_map_lock));
+ GNUNET_assert (0 ==
+ pthread_mutex_destroy (&h->accounts_lock));
+ GNUNET_assert (0 ==
+ pthread_mutex_destroy (&h->rpubs_lock));
+ for (uint64_t i = 0; i<h->ram_limit; i++)
+ GNUNET_free (h->transactions[i]);
+ GNUNET_free (h->transactions);
+ GNUNET_free (h->my_baseurl);
+ GNUNET_free (h->currency);
+ GNUNET_free (h->exchange_url);
+ GNUNET_free (h->hostname);
+ GNUNET_free (h);
+}
diff --git a/src/bank-lib/fakebank_tbi.c b/src/bank-lib/fakebank_tbi.c
new file mode 100644
index 000000000..463ec7d31
--- /dev/null
+++ b/src/bank-lib/fakebank_tbi.c
@@ -0,0 +1,167 @@
+/*
+ This file is part of TALER
+ (C) 2016-2024 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 bank-lib/fakebank_tbi.c
+ * @brief main entry point to the Taler Bank Integration (TBI) API implementation
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_tbi.h"
+#include "fakebank_tbi_get_withdrawal_operation.h"
+#include "fakebank_tbi_post_withdrawal_operation.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_tbi_main_ (struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_HEAD))
+ method = MHD_HTTP_METHOD_GET;
+ if ( (0 == strcmp (url,
+ "/config")) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET)) )
+ {
+ struct TALER_Amount zero;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (h->currency,
+ &zero));
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("version",
+ "1:0:1"),
+ GNUNET_JSON_pack_string ("currency",
+ h->currency),
+ GNUNET_JSON_pack_string ("implementation",
+ "urn:net:taler:specs:bank:fakebank"),
+ GNUNET_JSON_pack_bool ("allow_conversion",
+ false),
+ GNUNET_JSON_pack_bool ("allow_registrations",
+ true),
+ GNUNET_JSON_pack_bool ("allow_deletions",
+ false),
+ GNUNET_JSON_pack_bool ("allow_edit_name",
+ false),
+ GNUNET_JSON_pack_bool ("allow_edit_cashout_payto_uri",
+ false),
+ TALER_JSON_pack_amount ("default_debit_threshold",
+ &zero),
+ GNUNET_JSON_pack_array_steal ("supported_tan_channels",
+ json_array ()),
+ GNUNET_JSON_pack_object_steal (
+ "currency_specification",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("name",
+ h->currency),
+ GNUNET_JSON_pack_string ("currency",
+ h->currency),
+ GNUNET_JSON_pack_uint64 ("num_fractional_input_digits",
+ 2),
+ GNUNET_JSON_pack_uint64 ("num_fractional_normal_digits",
+ 2),
+ GNUNET_JSON_pack_uint64 ("num_fractional_trailing_zero_digits",
+ 2),
+ GNUNET_JSON_pack_object_steal (
+ "alt_unit_names",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("0",
+ h->currency))),
+ GNUNET_JSON_pack_string ("name",
+ h->currency))),
+ GNUNET_JSON_pack_string ("name",
+ "taler-bank-integration"));
+ }
+ if ( (0 == strncmp (url,
+ "/withdrawal-operation/",
+ strlen ("/withdrawal-operation/"))) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET)) )
+ {
+ const char *wopid = &url[strlen ("/withdrawal-operation/")];
+ const char *lp_s
+ = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "long_poll_ms");
+ struct GNUNET_TIME_Relative lp = GNUNET_TIME_UNIT_ZERO;
+
+ if (NULL != lp_s)
+ {
+ unsigned long long d;
+ char dummy;
+
+ if (1 != sscanf (lp_s,
+ "%llu%c",
+ &d,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "long_poll_ms");
+ }
+ lp = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+ d);
+ }
+ return TALER_FAKEBANK_tbi_get_withdrawal_operation_ (h,
+ connection,
+ wopid,
+ lp,
+ con_cls);
+
+ }
+ if ( (0 == strncmp (url,
+ "/withdrawal-operation/",
+ strlen ("/withdrawal-operation/"))) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_POST)) )
+ {
+ const char *wopid = &url[strlen ("/withdrawal-operation/")];
+
+ return TALER_FAKEBANK_tbi_post_withdrawal (h,
+ connection,
+ wopid,
+ upload_data,
+ upload_data_size,
+ con_cls);
+ }
+
+ TALER_LOG_ERROR ("Breaking URL: %s %s\n",
+ method,
+ url);
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ url);
+}
diff --git a/src/bank-lib/fakebank_tbi.h b/src/bank-lib/fakebank_tbi.h
new file mode 100644
index 000000000..ef9f35fa2
--- /dev/null
+++ b/src/bank-lib/fakebank_tbi.h
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_tbi.c
+ * @brief main entry point to the Taler Bank Integration (TBI) API implementation
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+#ifndef FAKEBANK_TBI_H
+#define FAKEBANK_TBI_H
+
+/**
+ * Handle incoming HTTP request to the bank integration API.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param url the requested url
+ * @param method the method (POST, GET, ...)
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_tbi_main_ (struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_tbi_get_withdrawal_operation.c b/src/bank-lib/fakebank_tbi_get_withdrawal_operation.c
new file mode 100644
index 000000000..4749bda77
--- /dev/null
+++ b/src/bank-lib/fakebank_tbi_get_withdrawal_operation.c
@@ -0,0 +1,141 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_tbi_get_withdrawal_operation.c
+ * @brief Implementation of the GET /withdrawal-operation/ request of the Taler Bank Integration API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_tbi_get_withdrawal_operation.h"
+
+/**
+ * Function called to clean up a withdraw context.
+ *
+ * @param cls a `struct WithdrawContext *`
+ */
+static void
+withdraw_cleanup (void *cls)
+{
+ struct WithdrawContext *wc = cls;
+
+ GNUNET_free (wc);
+}
+
+
+MHD_RESULT
+TALER_FAKEBANK_tbi_get_withdrawal_operation_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *wopid,
+ struct GNUNET_TIME_Relative lp,
+ void **con_cls)
+{
+ struct ConnectionContext *cc = *con_cls;
+ struct WithdrawContext *wc;
+ const char *status_string;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ if (NULL == cc)
+ {
+ cc = GNUNET_new (struct ConnectionContext);
+ cc->ctx_cleaner = &withdraw_cleanup;
+ *con_cls = cc;
+ wc = GNUNET_new (struct WithdrawContext);
+ cc->ctx = wc;
+ wc->wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+ wopid);
+ if (NULL == wc->wo)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+ wopid);
+ }
+ wc->timeout = GNUNET_TIME_relative_to_absolute (lp);
+ }
+ else
+ {
+ wc = cc->ctx;
+ }
+ if (GNUNET_TIME_absolute_is_past (wc->timeout) ||
+ h->in_shutdown ||
+ wc->wo->confirmation_done ||
+ wc->wo->aborted)
+ {
+ json_t *wt;
+
+ wt = json_array ();
+ GNUNET_assert (NULL != wt);
+ GNUNET_assert (0 ==
+ json_array_append_new (wt,
+ json_string ("x-taler-bank")));
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ if (wc->wo->aborted)
+ status_string = "aborted";
+ else if (wc->wo->confirmation_done)
+ status_string = "confirmed";
+ else if (wc->wo->selection_done)
+ status_string = "selected";
+ else
+ status_string = "pending";
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ // FIXME: deprecated field, should be removed in the future.
+ GNUNET_JSON_pack_bool ("aborted",
+ wc->wo->aborted),
+ // FIXME: deprecated field, should be removed in the future.
+ GNUNET_JSON_pack_bool ("selection_done",
+ wc->wo->selection_done),
+ // FIXME: deprecated field, should be removed in the future.
+ GNUNET_JSON_pack_bool ("transfer_done",
+ wc->wo->confirmation_done),
+ GNUNET_JSON_pack_string ("status",
+ status_string),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("suggested_exchange",
+ h->exchange_url)),
+ TALER_JSON_pack_amount ("amount",
+ &wc->wo->amount),
+ GNUNET_JSON_pack_array_steal ("wire_types",
+ wt));
+ }
+
+ TALER_FAKEBANK_start_lp_ (h,
+ connection,
+ wc->wo->debit_account,
+ GNUNET_TIME_absolute_get_remaining (wc->timeout),
+ LP_WITHDRAW,
+ wc->wo);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return MHD_YES;
+}
diff --git a/src/bank-lib/fakebank_tbi_get_withdrawal_operation.h b/src/bank-lib/fakebank_tbi_get_withdrawal_operation.h
new file mode 100644
index 000000000..b42e5a768
--- /dev/null
+++ b/src/bank-lib/fakebank_tbi_get_withdrawal_operation.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_tbi_get_withdrawal_operation.h
+ * @brief Implementation of the GET /withdrawal-operation/ request of the Taler Bank Integration API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TBI_GET_WITHDRAWAL_OPERATION_H
+#define FAKEBANK_TBI_GET_WITHDRAWAL_OPERATION_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Handle GET /withdrawal-operation/{wopid} request.
+ *
+ * @param h the handle
+ * @param connection the connection
+ * @param wopid the withdrawal operation identifier
+ * @param lp how long is the long-polling timeout
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_tbi_get_withdrawal_operation_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *wopid,
+ struct GNUNET_TIME_Relative lp,
+ void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_tbi_post_withdrawal_operation.c b/src/bank-lib/fakebank_tbi_post_withdrawal_operation.c
new file mode 100644
index 000000000..38b92e494
--- /dev/null
+++ b/src/bank-lib/fakebank_tbi_post_withdrawal_operation.c
@@ -0,0 +1,231 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_tbi_post_withdrawal_operation.c
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_tbi_post_withdrawal_operation.h"
+
+
+/**
+ * Execute POST /withdrawal-operation/ request.
+ *
+ * @param h our handle
+ * @param connection the connection
+ * @param wopid the withdrawal operation identifier
+ * @param reserve_pub public key of the reserve
+ * @param exchange_payto_uri payto://-URI of the exchange
+ * @return MHD result code
+ */
+static MHD_RESULT
+do_post_withdrawal (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *wopid,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *exchange_payto_uri)
+{
+ struct WithdrawalOperation *wo;
+ char *credit_name;
+ struct Account *credit_account;
+ const char *status_string;
+
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+ wopid);
+ if (NULL == wo)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+ wopid);
+ }
+ if ( (wo->selection_done) &&
+ (0 != GNUNET_memcmp (&wo->reserve_pub,
+ reserve_pub)) )
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT,
+ "reserve public key changed");
+ }
+ {
+ /* check if reserve_pub is already in use */
+ const struct GNUNET_PeerIdentity *pid;
+
+ pid = (const struct GNUNET_PeerIdentity *) &wo->reserve_pub;
+ if (GNUNET_CONTAINER_multipeermap_contains (h->rpubs,
+ pid))
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
+ NULL);
+ }
+ }
+ credit_name = TALER_xtalerbank_account_from_payto (exchange_payto_uri);
+ if (NULL == credit_name)
+ {
+ GNUNET_break_op (0);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
+ NULL);
+ }
+ credit_account = TALER_FAKEBANK_lookup_account_ (h,
+ credit_name,
+ NULL);
+ if (NULL == credit_account)
+ {
+ MHD_RESULT res;
+
+ GNUNET_break_op (0);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ res = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_UNKNOWN_ACCOUNT,
+ credit_name);
+ GNUNET_free (credit_name);
+ return res;
+ }
+ GNUNET_free (credit_name);
+ if ( (NULL != wo->exchange_account) &&
+ (credit_account != wo->exchange_account) )
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT,
+ "exchange account changed");
+ }
+ wo->exchange_account = credit_account;
+ wo->reserve_pub = *reserve_pub;
+ wo->selection_done = true;
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ if (wo->aborted)
+ status_string = "aborted";
+ else if (wo->confirmation_done)
+ status_string = "confirmed";
+ else
+ status_string = "selected";
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ // FIXME: Deprecated field, should be deleted in the future.
+ GNUNET_JSON_pack_bool ("transfer_done",
+ wo->confirmation_done),
+ GNUNET_JSON_pack_string ("status",
+ status_string));
+}
+
+
+MHD_RESULT
+TALER_FAKEBANK_tbi_post_withdrawal (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *wopid,
+ const void *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ struct ConnectionContext *cc = *con_cls;
+ enum GNUNET_JSON_PostResult pr;
+ json_t *json;
+ MHD_RESULT res;
+
+ if (NULL == cc)
+ {
+ cc = GNUNET_new (struct ConnectionContext);
+ cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
+ *con_cls = cc;
+ }
+ pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
+ connection,
+ &cc->ctx,
+ upload_data,
+ upload_data_size,
+ &json);
+ switch (pr)
+ {
+ case GNUNET_JSON_PR_OUT_OF_MEMORY:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_CONTINUE:
+ return MHD_YES;
+ case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_JSON_INVALID:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_SUCCESS:
+ break;
+ }
+
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ const char *exchange_payto_url;
+ enum GNUNET_GenericReturnValue ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &reserve_pub),
+ GNUNET_JSON_spec_string ("selected_exchange",
+ &exchange_payto_url),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ (ret = TALER_MHD_parse_json_data (connection,
+ json,
+ spec)))
+ {
+ GNUNET_break_op (0);
+ json_decref (json);
+ return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+ }
+ res = do_post_withdrawal (h,
+ connection,
+ wopid,
+ &reserve_pub,
+ exchange_payto_url);
+ }
+ json_decref (json);
+ return res;
+}
diff --git a/src/bank-lib/fakebank_tbi_post_withdrawal_operation.h b/src/bank-lib/fakebank_tbi_post_withdrawal_operation.h
new file mode 100644
index 000000000..8873bb5f6
--- /dev/null
+++ b/src/bank-lib/fakebank_tbi_post_withdrawal_operation.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_tbi_post_withdrawal_operation.c
+ * @brief Implementation of the Taler Bank Integration API for POAT /withdrawal-operation/ requests
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TBI_POST_WITHDRAWAL_OPERATION_H
+#define FAKEBANK_TBI_POST_WITHDRAWAL_OPERATION_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Handle POST /withdrawal-operation/ request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param wopid the withdrawal operation identifier
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_tbi_post_withdrawal (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *wopid,
+ const void *upload_data,
+ size_t *upload_data_size,
+ void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_tbr.c b/src/bank-lib/fakebank_tbr.c
new file mode 100644
index 000000000..0f0e5bdc1
--- /dev/null
+++ b/src/bank-lib/fakebank_tbr.c
@@ -0,0 +1,94 @@
+/*
+ This file is part of TALER
+ (C) 2016-2024 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 bank-lib/fakebank_tbr.c
+ * @brief main entry point for the Taler Bank Revenue API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_tbr_get_history.h"
+#include "fakebank_tbr_get_root.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_tbr_main_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ const char *url,
+ const char *method,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Fakebank - Anastasis API: serving URL `%s' for account `%s'\n",
+ url,
+ account);
+
+ if ( (0 == strcmp (url,
+ "/config")) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET)) )
+ {
+ /* GET /config */
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("version",
+ "0:0:0"),
+ GNUNET_JSON_pack_string ("currency",
+ h->currency),
+ GNUNET_JSON_pack_string ("implementation",
+ "urn:net:taler:specs:bank:fakebank"),
+ GNUNET_JSON_pack_string ("name",
+ "taler-revenue"));
+ }
+
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET))
+ {
+ if ( (0 == strcmp (url,
+ "/history")) &&
+ (NULL != account) )
+ return TALER_FAKEBANK_tbr_get_history (h,
+ connection,
+ account,
+ con_cls);
+ if (0 == strcmp (url,
+ "/"))
+ return TALER_FAKEBANK_tbr_get_root (h,
+ connection);
+ }
+ /* Unexpected URL path, just close the connection. */
+ TALER_LOG_ERROR ("Breaking URL: %s %s\n",
+ method,
+ url);
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ url);
+}
diff --git a/src/bank-lib/fakebank_tbr.h b/src/bank-lib/fakebank_tbr.h
new file mode 100644
index 000000000..fb9bdfefc
--- /dev/null
+++ b/src/bank-lib/fakebank_tbr.h
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_tbr.h
+ * @brief main entry point for the Taler Bank Revenue API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+#ifndef FAKEBANK_TBR_H
+#define FAKEBANK_TBR_H
+
+/**
+ * Handle incoming HTTP request to the Taler Bank Revenue API.
+ *
+ * @param h our handle
+ * @param connection the connection
+ * @param url the requested url
+ * @param method the method (POST, GET, ...)
+ * @param account which account should process the request
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_tbr_main_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ const char *url,
+ const char *method,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_tbr_get_history.c b/src/bank-lib/fakebank_tbr_get_history.c
new file mode 100644
index 000000000..a6cfaad8d
--- /dev/null
+++ b/src/bank-lib/fakebank_tbr_get_history.c
@@ -0,0 +1,312 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_tbr_get_history.c
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_common_parser.h"
+#include "fakebank_tbr_get_history.h"
+
+
+/**
+ * Function called to clean up a history context.
+ *
+ * @param cls a `struct HistoryContext *`
+ */
+static void
+history_cleanup (void *cls)
+{
+ struct HistoryContext *hc = cls;
+
+ json_decref (hc->history);
+ GNUNET_free (hc);
+}
+
+
+MHD_RESULT
+TALER_FAKEBANK_tbr_get_history (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ void **con_cls)
+{
+ struct ConnectionContext *cc = *con_cls;
+ struct HistoryContext *hc;
+ const struct Transaction *pos;
+ enum GNUNET_GenericReturnValue ret;
+ bool in_shutdown;
+ const char *acc_payto_uri;
+
+ if (NULL == cc)
+ {
+ cc = GNUNET_new (struct ConnectionContext);
+ cc->ctx_cleaner = &history_cleanup;
+ *con_cls = cc;
+ hc = GNUNET_new (struct HistoryContext);
+ cc->ctx = hc;
+ hc->history = json_array ();
+ if (NULL == hc->history)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling /accounts/%s/taler-revenue/history request\n",
+ account);
+ if (GNUNET_OK !=
+ (ret = TALER_FAKEBANK_common_parse_history_args (h,
+ connection,
+ &hc->ha)))
+ {
+ GNUNET_break_op (0);
+ return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
+ }
+ hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout);
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ if (UINT64_MAX == hc->ha.start_idx)
+ hc->ha.start_idx = h->serial_counter;
+ hc->acc = TALER_FAKEBANK_lookup_account_ (h,
+ account,
+ NULL);
+ if (NULL == hc->acc)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Account %s is unknown\n",
+ account);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_UNKNOWN_ACCOUNT,
+ account);
+ }
+ }
+ else
+ {
+ hc = cc->ctx;
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ }
+
+ if (! hc->ha.have_start)
+ {
+ pos = (0 > hc->ha.delta)
+ ? hc->acc->in_tail
+ : hc->acc->in_head;
+ }
+ else
+ {
+ struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit];
+ bool overflow;
+ uint64_t dir;
+ bool skip = true;
+
+ overflow = ( (NULL != t) && (t->row_id != hc->ha.start_idx) );
+ dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1;
+ /* If account does not match, linear scan for
+ first matching account. */
+ while ( (! overflow) &&
+ (NULL != t) &&
+ (t->credit_account != hc->acc) )
+ {
+ skip = false;
+ t = h->transactions[(t->row_id + dir) % h->ram_limit];
+ if ( (NULL != t) &&
+ (t->row_id == hc->ha.start_idx) )
+ overflow = true; /* full circle, give up! */
+ }
+ if ( (NULL == t) ||
+ overflow)
+ {
+ in_shutdown = h->in_shutdown;
+ /* FIXME: these conditions are unclear to me. */
+ if (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout) &&
+ (0 < hc->ha.delta))
+ {
+ acc_payto_uri = hc->acc->payto_uri;
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ if (overflow)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Transactions lost due to RAM limits\n");
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_BANK_ANCIENT_TRANSACTION_GONE,
+ NULL);
+ }
+ goto finish;
+ }
+ if (in_shutdown)
+ {
+ acc_payto_uri = hc->acc->payto_uri;
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ goto finish;
+ }
+ TALER_FAKEBANK_start_lp_ (h,
+ connection,
+ hc->acc,
+ GNUNET_TIME_absolute_get_remaining (
+ hc->timeout),
+ LP_CREDIT,
+ NULL);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return MHD_YES;
+ }
+ if (skip)
+ {
+ /* range from application is exclusive, skip the
+ matching entry */
+ if (0 > hc->ha.delta)
+ pos = t->prev_in;
+ else
+ pos = t->next_in;
+ }
+ else
+ {
+ pos = t;
+ }
+ }
+ if (NULL != pos)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning %lld credit transactions starting (inclusive) from %llu\n",
+ (long long) hc->ha.delta,
+ (unsigned long long) pos->row_id);
+ while ( (0 != hc->ha.delta) &&
+ (NULL != pos) )
+ {
+ json_t *trans;
+ char *subject;
+
+ if (T_DEBIT != pos->type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unexpected CREDIT transaction #%llu for account `%s'\n",
+ (unsigned long long) pos->row_id,
+ account);
+ if (0 > hc->ha.delta)
+ pos = pos->prev_in;
+ if (0 < hc->ha.delta)
+ pos = pos->next_in;
+ continue;
+ }
+
+ {
+ char *wtids;
+
+ wtids = GNUNET_STRINGS_data_to_string_alloc (
+ &pos->subject.debit.wtid,
+ sizeof (pos->subject.debit.wtid));
+ GNUNET_asprintf (&subject,
+ "%s %s",
+ wtids,
+ pos->subject.debit.exchange_base_url);
+ GNUNET_free (wtids);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found transaction over %s with subject %s\n",
+ TALER_amount2s (&pos->amount),
+ subject);
+ trans = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RESERVE"),
+ GNUNET_JSON_pack_uint64 ("row_id",
+ pos->row_id),
+ GNUNET_JSON_pack_timestamp ("date",
+ pos->date),
+ TALER_JSON_pack_amount ("amount",
+ &pos->amount),
+ GNUNET_JSON_pack_string ("debit_account",
+ pos->debit_account->payto_uri),
+ GNUNET_JSON_pack_string ("subject",
+ subject));
+ GNUNET_free (subject);
+ GNUNET_assert (NULL != trans);
+ GNUNET_assert (0 ==
+ json_array_append_new (hc->history,
+ trans));
+ if (hc->ha.delta > 0)
+ hc->ha.delta--;
+ else
+ hc->ha.delta++;
+ if (0 > hc->ha.delta)
+ pos = pos->prev_in;
+ if (0 < hc->ha.delta)
+ pos = pos->next_in;
+ }
+ if ( (0 == json_array_size (hc->history)) &&
+ (! h->in_shutdown) &&
+ (GNUNET_TIME_absolute_is_future (hc->timeout)) &&
+ (0 < hc->ha.delta))
+ {
+ TALER_FAKEBANK_start_lp_ (h,
+ connection,
+ hc->acc,
+ GNUNET_TIME_absolute_get_remaining (hc->timeout),
+ LP_CREDIT,
+ NULL);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return MHD_YES;
+ }
+ in_shutdown = h->in_shutdown;
+ acc_payto_uri = hc->acc->payto_uri;
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+finish:
+ if (0 == json_array_size (hc->history))
+ {
+ GNUNET_break (in_shutdown ||
+ (! GNUNET_TIME_absolute_is_future (hc->timeout)));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Zero transactions found\n");
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ }
+ {
+ json_t *h = hc->history;
+
+ hc->history = NULL;
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string (
+ "credit_account",
+ acc_payto_uri),
+ GNUNET_JSON_pack_array_steal (
+ "incoming_transactions",
+ h));
+ }
+}
diff --git a/src/bank-lib/fakebank_tbr_get_history.h b/src/bank-lib/fakebank_tbr_get_history.h
new file mode 100644
index 000000000..997c9a86b
--- /dev/null
+++ b/src/bank-lib/fakebank_tbr_get_history.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_tbr_get_history.h
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TBR_GET_HISTORY_H
+#define FAKEBANK_TBR_GET_HISTORY_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle incoming HTTP request for /history
+ * of the taler-revenue API. This one can return transactions
+ * created by debits from the exchange!
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @param account which account the request is about
+ * @param con_cls closure for request (NULL or &special_ptr)
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_tbr_get_history (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_tbr_get_root.c b/src/bank-lib/fakebank_tbr_get_root.c
new file mode 100644
index 000000000..6e518d661
--- /dev/null
+++ b/src/bank-lib/fakebank_tbr_get_root.c
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_tbr_get_root.c
+ * @brief return the main "/" page for the Taler Bank Revenue API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_tbr_get_root (struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection)
+{
+ MHD_RESULT ret;
+ struct MHD_Response *resp;
+#define HELLOMSG "Hello, Fakebank (Bank Revenue API here)!"
+
+ (void) h;
+ resp = MHD_create_response_from_buffer (
+ strlen (HELLOMSG),
+ HELLOMSG,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ resp);
+ MHD_destroy_response (resp);
+ return ret;
+}
diff --git a/src/bank-lib/fakebank_tbr_get_root.h b/src/bank-lib/fakebank_tbr_get_root.h
new file mode 100644
index 000000000..eda8060bf
--- /dev/null
+++ b/src/bank-lib/fakebank_tbr_get_root.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_tbr_get_root.h
+ * @brief return the main "/" page for the Taler Bank Revenue API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TBR_GET_ROOT_H
+#define FAKEBANK_TBR_GET_ROOT_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle incoming HTTP request for "/" (home page).
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_tbr_get_root (struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection);
+
+#endif
diff --git a/src/bank-lib/fakebank_twg.c b/src/bank-lib/fakebank_twg.c
new file mode 100644
index 000000000..9356e69ba
--- /dev/null
+++ b/src/bank-lib/fakebank_twg.c
@@ -0,0 +1,124 @@
+/*
+ This file is part of TALER
+ (C) 2016-2024 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 bank-lib/fakebank_twg.c
+ * @brief main entry point for the Taler Wire Gateway API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_twg.h"
+#include "fakebank_twg_admin_add_incoming.h"
+#include "fakebank_twg_get_root.h"
+#include "fakebank_twg_history.h"
+#include "fakebank_twg_transfer.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_twg_main_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ const char *url,
+ const char *method,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Fakebank TWG, serving URL `%s' for account `%s'\n",
+ url,
+ account);
+ if ( (0 == strcmp (url,
+ "/config")) &&
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET)) )
+ {
+ /* GET /config */
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("version",
+ "0:0:0"),
+ GNUNET_JSON_pack_string ("currency",
+ h->currency),
+ GNUNET_JSON_pack_string ("implementation",
+ "urn:net:taler:specs:bank:fakebank"),
+ GNUNET_JSON_pack_string ("name",
+ "taler-wire-gateway"));
+ }
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET))
+ {
+ if ( (0 == strcmp (url,
+ "/history/incoming")) &&
+ (NULL != account) )
+ return TALER_FAKEBANK_twg_get_credit_history_ (h,
+ connection,
+ account,
+ con_cls);
+ if ( (0 == strcmp (url,
+ "/history/outgoing")) &&
+ (NULL != account) )
+ return TALER_FAKEBANK_twg_get_debit_history_ (h,
+ connection,
+ account,
+ con_cls);
+ if (0 == strcmp (url,
+ "/"))
+ return TALER_FAKEBANK_twg_get_root_ (h,
+ connection);
+ }
+ else if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_POST))
+ {
+ if ( (0 == strcmp (url,
+ "/admin/add-incoming")) &&
+ (NULL != account) )
+ return TALER_FAKEBANK_twg_admin_add_incoming_ (h,
+ connection,
+ account,
+ upload_data,
+ upload_data_size,
+ con_cls);
+ if ( (0 == strcmp (url,
+ "/transfer")) &&
+ (NULL != account) )
+ return TALER_FAKEBANK_handle_transfer_ (h,
+ connection,
+ account,
+ upload_data,
+ upload_data_size,
+ con_cls);
+ }
+ /* Unexpected URL path, just close the connection. */
+ TALER_LOG_ERROR ("Breaking URL: %s %s\n",
+ method,
+ url);
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ url);
+}
diff --git a/src/bank-lib/fakebank_twg.h b/src/bank-lib/fakebank_twg.h
new file mode 100644
index 000000000..de808a21f
--- /dev/null
+++ b/src/bank-lib/fakebank_twg.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_twg.h
+ * @brief main entry point for the Taler Wire Gateway API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TWG_H
+#define FAKEBANK_TWG_H
+
+#include "taler_fakebank_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * Handle incoming HTTP request to the Taler Wire Gateway
+ * API.
+ *
+ * @param h our handle
+ * @param connection the connection
+ * @param url the requested url
+ * @param method the method (POST, GET, ...)
+ * @param account which account should process the request
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_twg_main_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ const char *url,
+ const char *method,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_twg_admin_add_incoming.c b/src/bank-lib/fakebank_twg_admin_add_incoming.c
new file mode 100644
index 000000000..2db4f1fe5
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_admin_add_incoming.c
@@ -0,0 +1,160 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_twg_admin_add_incoming.c
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_make_admin_transfer.h"
+#include "fakebank_twg_admin_add_incoming.h"
+
+MHD_RESULT
+TALER_FAKEBANK_twg_admin_add_incoming_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ struct ConnectionContext *cc = *con_cls;
+ enum GNUNET_JSON_PostResult pr;
+ json_t *json;
+ uint64_t row_id;
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ if (NULL == cc)
+ {
+ cc = GNUNET_new (struct ConnectionContext);
+ cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
+ *con_cls = cc;
+ }
+ pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
+ connection,
+ &cc->ctx,
+ upload_data,
+ upload_data_size,
+ &json);
+ switch (pr)
+ {
+ case GNUNET_JSON_PR_OUT_OF_MEMORY:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_CONTINUE:
+ return MHD_YES;
+ case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_JSON_INVALID:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_SUCCESS:
+ break;
+ }
+ {
+ const char *debit_account;
+ struct TALER_Amount amount;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ char *debit;
+ enum GNUNET_GenericReturnValue ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &reserve_pub),
+ GNUNET_JSON_spec_string ("debit_account",
+ &debit_account),
+ TALER_JSON_spec_amount ("amount",
+ h->currency,
+ &amount),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ (ret = TALER_MHD_parse_json_data (connection,
+ json,
+ spec)))
+ {
+ GNUNET_break_op (0);
+ json_decref (json);
+ return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+ }
+ if (0 != strcasecmp (amount.currency,
+ h->currency))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Currency `%s' does not match our configuration\n",
+ amount.currency);
+ json_decref (json);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ NULL);
+ }
+ debit = TALER_xtalerbank_account_from_payto (debit_account);
+ if (NULL == debit)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
+ debit_account);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Receiving incoming wire transfer: %s->%s, subject: %s, amount: %s\n",
+ debit,
+ account,
+ TALER_B2S (&reserve_pub),
+ TALER_amount2s (&amount));
+ ret = TALER_FAKEBANK_make_admin_transfer_ (h,
+ debit,
+ account,
+ &amount,
+ &reserve_pub,
+ &row_id,
+ &timestamp);
+ GNUNET_free (debit);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Reserve public key not unique\n");
+ json_decref (json);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
+ NULL);
+ }
+ }
+ json_decref (json);
+
+ /* Finally build response object */
+ return TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_uint64 ("row_id",
+ row_id),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ timestamp));
+}
diff --git a/src/bank-lib/fakebank_twg_admin_add_incoming.h b/src/bank-lib/fakebank_twg_admin_add_incoming.h
new file mode 100644
index 000000000..220a10fc8
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_admin_add_incoming.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_twg_admin_add_incoming.h
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TWG_ADMIN_ADD_INCOMING_H
+#define FAKEBANK_TWG_ADMIN_ADD_INCOMING_H
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Handle incoming HTTP request for /admin/add/incoming.
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @param account account into which to deposit the funds (credit)
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request (a `struct ConnectionContext *`)
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_twg_admin_add_incoming_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_twg_get_root.c b/src/bank-lib/fakebank_twg_get_root.c
new file mode 100644
index 000000000..09589890e
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_get_root.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_twg_get_root.c
+ * @brief return the "/" page for the taler wire gateway
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle incoming HTTP request for "/" (home page).
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_twg_get_root_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection)
+{
+ MHD_RESULT ret;
+ struct MHD_Response *resp;
+#define HELLOMSG "Hello, Fakebank (Taler Wire Gateway)!"
+
+ (void) h;
+ resp = MHD_create_response_from_buffer (
+ strlen (HELLOMSG),
+ HELLOMSG,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ resp);
+ MHD_destroy_response (resp);
+ return ret;
+}
diff --git a/src/bank-lib/fakebank_twg_get_root.h b/src/bank-lib/fakebank_twg_get_root.h
new file mode 100644
index 000000000..8bbcf4192
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_get_root.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_twg_get_root.h
+ * @brief return the "/" page for the taler wire gateway
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TWG_GET_ROOT_H
+#define FAKEBANK_TWG_GET_ROOT_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle incoming HTTP request for "/" (home page).
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_twg_get_root_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection);
+
+#endif
diff --git a/src/bank-lib/fakebank_twg_history.c b/src/bank-lib/fakebank_twg_history.c
new file mode 100644
index 000000000..5dfc160b0
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_history.c
@@ -0,0 +1,537 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_twg_history.c
+ * @brief routines to return account histories for the Taler Wire Gateway API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_common_parser.h"
+
+/**
+ * Function called to clean up a history context.
+ *
+ * @param cls a `struct HistoryContext *`
+ */
+static void
+history_cleanup (void *cls)
+{
+ struct HistoryContext *hc = cls;
+
+ json_decref (hc->history);
+ GNUNET_free (hc);
+}
+
+
+MHD_RESULT
+TALER_FAKEBANK_twg_get_debit_history_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ void **con_cls)
+{
+ struct ConnectionContext *cc = *con_cls;
+ struct HistoryContext *hc;
+ struct Transaction *pos;
+ enum GNUNET_GenericReturnValue ret;
+ bool in_shutdown;
+ const char *acc_payto_uri;
+
+ if (NULL == cc)
+ {
+ cc = GNUNET_new (struct ConnectionContext);
+ cc->ctx_cleaner = &history_cleanup;
+ *con_cls = cc;
+ hc = GNUNET_new (struct HistoryContext);
+ cc->ctx = hc;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling /history/outgoing connection %p\n",
+ connection);
+ if (GNUNET_OK !=
+ (ret = TALER_FAKEBANK_common_parse_history_args (h,
+ connection,
+ &hc->ha)))
+ {
+ GNUNET_break_op (0);
+ return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
+ }
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ if (UINT64_MAX == hc->ha.start_idx)
+ hc->ha.start_idx = h->serial_counter;
+ hc->acc = TALER_FAKEBANK_lookup_account_ (h,
+ account,
+ NULL);
+ if (NULL == hc->acc)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_UNKNOWN_ACCOUNT,
+ account);
+ }
+ hc->history = json_array ();
+ if (NULL == hc->history)
+ {
+ GNUNET_break (0);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return MHD_NO;
+ }
+ hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout);
+ }
+ else
+ {
+ hc = cc->ctx;
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ }
+
+ if (! hc->ha.have_start)
+ {
+ pos = (0 > hc->ha.delta)
+ ? hc->acc->out_tail
+ : hc->acc->out_head;
+ }
+ else
+ {
+ struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit];
+ bool overflow;
+ uint64_t dir;
+ bool skip = true;
+
+ dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1;
+ overflow = (t->row_id != hc->ha.start_idx);
+ /* If account does not match, linear scan for
+ first matching account. */
+ while ( (! overflow) &&
+ (NULL != t) &&
+ (t->debit_account != hc->acc) )
+ {
+ skip = false;
+ t = h->transactions[(t->row_id + dir) % h->ram_limit];
+ if ( (NULL != t) &&
+ (t->row_id == hc->ha.start_idx) )
+ overflow = true; /* full circle, give up! */
+ }
+ if ( (NULL == t) ||
+ overflow)
+ {
+ /* FIXME: these conditions are unclear to me. */
+ if ( (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout)) &&
+ (0 < hc->ha.delta))
+ {
+ acc_payto_uri = hc->acc->payto_uri;
+ in_shutdown = h->in_shutdown;
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ if (overflow)
+ {
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_BANK_ANCIENT_TRANSACTION_GONE,
+ NULL);
+ }
+ goto finish;
+ }
+ if (h->in_shutdown)
+ {
+ acc_payto_uri = hc->acc->payto_uri;
+ in_shutdown = h->in_shutdown;
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ goto finish;
+ }
+ TALER_FAKEBANK_start_lp_ (h,
+ connection,
+ hc->acc,
+ GNUNET_TIME_absolute_get_remaining (
+ hc->timeout),
+ LP_DEBIT,
+ NULL);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return MHD_YES;
+ }
+ if (t->debit_account != hc->acc)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid start specified, transaction %llu not with account %s!\n",
+ (unsigned long long) hc->ha.start_idx,
+ account);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return MHD_NO;
+ }
+ if (skip)
+ {
+ /* range is exclusive, skip the matching entry */
+ if (0 > hc->ha.delta)
+ pos = t->prev_out;
+ else
+ pos = t->next_out;
+ }
+ else
+ {
+ pos = t;
+ }
+ }
+ if (NULL != pos)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning %lld debit transactions starting (inclusive) from %llu\n",
+ (long long) hc->ha.delta,
+ (unsigned long long) pos->row_id);
+ while ( (0 != hc->ha.delta) &&
+ (NULL != pos) )
+ {
+ json_t *trans;
+ char *credit_payto;
+
+ if (T_DEBIT != pos->type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unexpected CREDIT transaction #%llu for account `%s'\n",
+ (unsigned long long) pos->row_id,
+ account);
+ if (0 > hc->ha.delta)
+ pos = pos->prev_in;
+ if (0 < hc->ha.delta)
+ pos = pos->next_in;
+ continue;
+ }
+ GNUNET_asprintf (&credit_payto,
+ "payto://x-taler-bank/localhost/%s?receiver-name=%s",
+ pos->credit_account->account_name,
+ pos->credit_account->receiver_name);
+
+ trans = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row_id",
+ pos->row_id),
+ GNUNET_JSON_pack_timestamp ("date",
+ pos->date),
+ TALER_JSON_pack_amount ("amount",
+ &pos->amount),
+ GNUNET_JSON_pack_string ("credit_account",
+ credit_payto),
+ GNUNET_JSON_pack_string ("exchange_base_url",
+ pos->subject.debit.exchange_base_url),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &pos->subject.debit.wtid));
+ GNUNET_assert (NULL != trans);
+ GNUNET_free (credit_payto);
+ GNUNET_assert (0 ==
+ json_array_append_new (hc->history,
+ trans));
+ if (hc->ha.delta > 0)
+ hc->ha.delta--;
+ else
+ hc->ha.delta++;
+ if (0 > hc->ha.delta)
+ pos = pos->prev_out;
+ if (0 < hc->ha.delta)
+ pos = pos->next_out;
+ }
+ if ( (0 == json_array_size (hc->history)) &&
+ (! h->in_shutdown) &&
+ (GNUNET_TIME_absolute_is_future (hc->timeout)) &&
+ (0 < hc->ha.delta))
+ {
+ TALER_FAKEBANK_start_lp_ (h,
+ connection,
+ hc->acc,
+ GNUNET_TIME_absolute_get_remaining (hc->timeout),
+ LP_DEBIT,
+ NULL);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return MHD_YES;
+ }
+ in_shutdown = h->in_shutdown;
+ acc_payto_uri = hc->acc->payto_uri;
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+finish:
+ if (0 == json_array_size (hc->history))
+ {
+ GNUNET_break (in_shutdown ||
+ (! GNUNET_TIME_absolute_is_future (hc->timeout)));
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ }
+ {
+ json_t *h = hc->history;
+
+ hc->history = NULL;
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string (
+ "debit_account",
+ acc_payto_uri),
+ GNUNET_JSON_pack_array_steal (
+ "outgoing_transactions",
+ h));
+ }
+}
+
+
+MHD_RESULT
+TALER_FAKEBANK_twg_get_credit_history_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ void **con_cls)
+{
+ struct ConnectionContext *cc = *con_cls;
+ struct HistoryContext *hc;
+ const struct Transaction *pos;
+ enum GNUNET_GenericReturnValue ret;
+ bool in_shutdown;
+ const char *acc_payto_uri;
+
+ if (NULL == cc)
+ {
+ cc = GNUNET_new (struct ConnectionContext);
+ cc->ctx_cleaner = &history_cleanup;
+ *con_cls = cc;
+ hc = GNUNET_new (struct HistoryContext);
+ cc->ctx = hc;
+ hc->history = json_array ();
+ if (NULL == hc->history)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling /history/incoming connection %p\n",
+ connection);
+ if (GNUNET_OK !=
+ (ret = TALER_FAKEBANK_common_parse_history_args (h,
+ connection,
+ &hc->ha)))
+ {
+ GNUNET_break_op (0);
+ return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
+ }
+ hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout);
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ if (UINT64_MAX == hc->ha.start_idx)
+ hc->ha.start_idx = h->serial_counter;
+ hc->acc = TALER_FAKEBANK_lookup_account_ (h,
+ account,
+ NULL);
+ if (NULL == hc->acc)
+ {
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_BANK_UNKNOWN_ACCOUNT,
+ account);
+ }
+ }
+ else
+ {
+ hc = cc->ctx;
+ GNUNET_assert (0 ==
+ pthread_mutex_lock (&h->big_lock));
+ }
+
+ if (! hc->ha.have_start)
+ {
+ pos = (0 > hc->ha.delta)
+ ? hc->acc->in_tail
+ : hc->acc->in_head;
+ }
+ else
+ {
+ struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit];
+ bool overflow;
+ uint64_t dir;
+ bool skip = true;
+
+ overflow = ( (NULL != t) && (t->row_id != hc->ha.start_idx) );
+ dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1;
+ /* If account does not match, linear scan for
+ first matching account. */
+ while ( (! overflow) &&
+ (NULL != t) &&
+ (t->credit_account != hc->acc) )
+ {
+ skip = false;
+ t = h->transactions[(t->row_id + dir) % h->ram_limit];
+ if ( (NULL != t) &&
+ (t->row_id == hc->ha.start_idx) )
+ overflow = true; /* full circle, give up! */
+ }
+ if ( (NULL == t) ||
+ overflow)
+ {
+ in_shutdown = h->in_shutdown;
+ /* FIXME: these conditions are unclear to me. */
+ if (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout) &&
+ (0 < hc->ha.delta))
+ {
+ acc_payto_uri = hc->acc->payto_uri;
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ if (overflow)
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_BANK_ANCIENT_TRANSACTION_GONE,
+ NULL);
+ goto finish;
+ }
+ if (in_shutdown)
+ {
+ acc_payto_uri = hc->acc->payto_uri;
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ goto finish;
+ }
+ TALER_FAKEBANK_start_lp_ (h,
+ connection,
+ hc->acc,
+ GNUNET_TIME_absolute_get_remaining (
+ hc->timeout),
+ LP_CREDIT,
+ NULL);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return MHD_YES;
+ }
+ if (skip)
+ {
+ /* range from application is exclusive, skip the
+ matching entry */
+ if (0 > hc->ha.delta)
+ pos = t->prev_in;
+ else
+ pos = t->next_in;
+ }
+ else
+ {
+ pos = t;
+ }
+ }
+ if (NULL != pos)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning %lld credit transactions starting (inclusive) from %llu\n",
+ (long long) hc->ha.delta,
+ (unsigned long long) pos->row_id);
+ while ( (0 != hc->ha.delta) &&
+ (NULL != pos) )
+ {
+ json_t *trans;
+
+ if (T_CREDIT != pos->type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unexpected DEBIT transaction #%llu for account `%s'\n",
+ (unsigned long long) pos->row_id,
+ account);
+ if (0 > hc->ha.delta)
+ pos = pos->prev_in;
+ if (0 < hc->ha.delta)
+ pos = pos->next_in;
+ continue;
+ }
+ trans = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row_id",
+ pos->row_id),
+ GNUNET_JSON_pack_timestamp ("date",
+ pos->date),
+ TALER_JSON_pack_amount ("amount",
+ &pos->amount),
+ GNUNET_JSON_pack_string ("debit_account",
+ pos->debit_account->payto_uri),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &pos->subject.credit.reserve_pub));
+ GNUNET_assert (NULL != trans);
+ GNUNET_assert (0 ==
+ json_array_append_new (hc->history,
+ trans));
+ if (hc->ha.delta > 0)
+ hc->ha.delta--;
+ else
+ hc->ha.delta++;
+ if (0 > hc->ha.delta)
+ pos = pos->prev_in;
+ if (0 < hc->ha.delta)
+ pos = pos->next_in;
+ }
+ if ( (0 == json_array_size (hc->history)) &&
+ (! h->in_shutdown) &&
+ (GNUNET_TIME_absolute_is_future (hc->timeout)) &&
+ (0 < hc->ha.delta))
+ {
+ TALER_FAKEBANK_start_lp_ (h,
+ connection,
+ hc->acc,
+ GNUNET_TIME_absolute_get_remaining (hc->timeout),
+ LP_CREDIT,
+ NULL);
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+ return MHD_YES;
+ }
+ in_shutdown = h->in_shutdown;
+ acc_payto_uri = hc->acc->payto_uri;
+ GNUNET_assert (0 ==
+ pthread_mutex_unlock (&h->big_lock));
+finish:
+ if (0 == json_array_size (hc->history))
+ {
+ GNUNET_break (in_shutdown ||
+ (! GNUNET_TIME_absolute_is_future (hc->timeout)));
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ }
+ {
+ json_t *h = hc->history;
+
+ hc->history = NULL;
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string (
+ "credit_account",
+ acc_payto_uri),
+ GNUNET_JSON_pack_array_steal (
+ "incoming_transactions",
+ h));
+ }
+}
diff --git a/src/bank-lib/fakebank_twg_history.h b/src/bank-lib/fakebank_twg_history.h
new file mode 100644
index 000000000..c49678aef
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_history.h
@@ -0,0 +1,67 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_twg_history.c
+ * @brief routines to return account histories for the Taler Wire Gateway API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TWG_HISTORY_H
+#define FAKEBANK_TWG_HISTORY_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle incoming HTTP request for /history/outgoing
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @param account which account the request is about
+ * @param con_cls closure for request
+ */
+MHD_RESULT
+TALER_FAKEBANK_twg_get_debit_history_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ void **con_cls);
+
+
+/**
+ * Handle incoming HTTP request for /history/incoming
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @param account which account the request is about
+ * @param con_cls closure for request (NULL or &special_ptr)
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_twg_get_credit_history_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ void **con_cls);
+
+
+#endif
diff --git a/src/bank-lib/fakebank_twg_transfer.c b/src/bank-lib/fakebank_twg_transfer.c
new file mode 100644
index 000000000..fef314a52
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_transfer.c
@@ -0,0 +1,178 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_twg_transfer.c
+ * @brief implementation of the Taler Wire Gateway "/transfer" endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_transact.h"
+#include "fakebank_twg_transfer.h"
+
+
+/**
+ * Handle incoming HTTP request for /transfer.
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @param account account making the transfer
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request (a `struct ConnectionContext *`)
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_handle_transfer_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ struct ConnectionContext *cc = *con_cls;
+ enum GNUNET_JSON_PostResult pr;
+ json_t *json;
+ uint64_t row_id;
+ struct GNUNET_TIME_Timestamp ts;
+
+ if (NULL == cc)
+ {
+ cc = GNUNET_new (struct ConnectionContext);
+ cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
+ *con_cls = cc;
+ }
+ pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
+ connection,
+ &cc->ctx,
+ upload_data,
+ upload_data_size,
+ &json);
+ switch (pr)
+ {
+ case GNUNET_JSON_PR_OUT_OF_MEMORY:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_CONTINUE:
+ return MHD_YES;
+ case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_JSON_INVALID:
+ GNUNET_break (0);
+ return MHD_NO;
+ case GNUNET_JSON_PR_SUCCESS:
+ break;
+ }
+ {
+ struct GNUNET_HashCode uuid;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ const char *credit_account;
+ char *credit;
+ const char *base_url;
+ struct TALER_Amount amount;
+ enum GNUNET_GenericReturnValue ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("request_uid",
+ &uuid),
+ TALER_JSON_spec_amount ("amount",
+ h->currency,
+ &amount),
+ GNUNET_JSON_spec_string ("exchange_base_url",
+ &base_url),
+ GNUNET_JSON_spec_fixed_auto ("wtid",
+ &wtid),
+ GNUNET_JSON_spec_string ("credit_account",
+ &credit_account),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ (ret = TALER_MHD_parse_json_data (connection,
+ json,
+ spec)))
+ {
+ GNUNET_break_op (0);
+ json_decref (json);
+ return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+ }
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ credit = TALER_xtalerbank_account_from_payto (credit_account);
+ if (NULL == credit)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
+ credit_account);
+ }
+ ret = TALER_FAKEBANK_make_transfer_ (h,
+ account,
+ credit,
+ &amount,
+ &wtid,
+ base_url,
+ &uuid,
+ &row_id,
+ &ts);
+ if (GNUNET_OK != ret)
+ {
+ MHD_RESULT res;
+ char *uids;
+
+ GNUNET_break (0);
+ uids = GNUNET_STRINGS_data_to_string_alloc (&uuid,
+ sizeof (uuid));
+ json_decref (json);
+ res = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED,
+ uids);
+ GNUNET_free (uids);
+ return res;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Receiving incoming wire transfer: %s->%s, subject: %s, amount: %s, from %s\n",
+ account,
+ credit,
+ TALER_B2S (&wtid),
+ TALER_amount2s (&amount),
+ base_url);
+ GNUNET_free (credit);
+ }
+ }
+ json_decref (json);
+
+ /* Finally build response object */
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_uint64 ("row_id",
+ row_id),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ ts));
+}
diff --git a/src/bank-lib/fakebank_twg_transfer.h b/src/bank-lib/fakebank_twg_transfer.h
new file mode 100644
index 000000000..2019565bf
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_transfer.h
@@ -0,0 +1,55 @@
+/*
+ This file is part of TALER
+ (C) 2016-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file bank-lib/fakebank_twg_transfer.h
+ * @brief implementation of the Taler Wire Gateway "/transfer" endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TWG_TRANSFER_H
+#define FAKEBANK_TWG_TRANSFER_H
+
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle incoming HTTP request for /transfer.
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @param account account making the transfer
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request (a `struct ConnectionContext *`)
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_handle_transfer_ (
+ struct TALER_FAKEBANK_Handle *h,
+ struct MHD_Connection *connection,
+ const char *account,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls);
+
+#endif
diff --git a/src/bank-lib/taler-bank-transfer.c b/src/bank-lib/taler-exchange-wire-gateway-client.c
index bc4ebcfbc..b0d387b71 100644
--- a/src/bank-lib/taler-bank-transfer.c
+++ b/src/bank-lib/taler-exchange-wire-gateway-client.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2017-2020 Taler Systems SA
+ Copyright (C) 2017-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
@@ -14,7 +14,7 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file taler-bank-transfer.c
+ * @file taler-exchange-wire-gateway-client.c
* @brief Execute wire transfer.
* @author Christian Grothoff
*/
@@ -64,7 +64,7 @@ static char *account_section;
/**
* Starting row.
*/
-static unsigned long long start_row;
+static unsigned long long start_row = UINT64_MAX;
/**
* Authentication data.
@@ -152,73 +152,72 @@ do_shutdown (void *cls)
/**
- * Callback used to process ONE entry in the transaction
+ * Callback used to process the transaction
* history returned by the bank.
*
* @param cls closure
- * @param http_status HTTP status code from server
- * @param ec taler error code
- * @param serial_id identification of the position at
- * which we are returning data
- * @param details details about the wire transfer
- * @param json original full response from server
- * @return #GNUNET_OK to continue, #GNUNET_SYSERR to
- * abort iteration
+ * @param reply response we got from the bank
*/
-static int
+static void
credit_history_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint64_t serial_id,
- const struct TALER_BANK_CreditDetails *details,
- const json_t *json)
+ const struct TALER_BANK_CreditHistoryResponse *reply)
{
(void) cls;
- if (MHD_HTTP_OK != http_status)
+ chh = NULL;
+ switch (reply->http_status)
{
- if ( (MHD_HTTP_NO_CONTENT != http_status) ||
- (TALER_EC_NONE != ec) ||
- (NULL == details) )
+ case 0:
+ fprintf (stderr,
+ "Failed to obtain HTTP reply from `%s'\n",
+ auth.wire_gateway_url);
+ global_ret = 2;
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ fprintf (stdout,
+ "No transactions.\n");
+ global_ret = 0;
+ break;
+ case MHD_HTTP_OK:
+ for (unsigned int i = 0; i<reply->details.ok.details_length; i++)
{
- fprintf (stderr,
- "Failed to obtain credit history: %u/%d\n",
- http_status,
- ec);
- if (NULL != json)
- json_dumpf (json,
- stderr,
- JSON_INDENT (2));
- global_ret = 2;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_NO;
+ const struct TALER_BANK_CreditDetails *cd =
+ &reply->details.ok.details[i];
+
+ /* If credit/debit accounts were specified, use as a filter */
+ if ( (NULL != credit_account) &&
+ (0 != strcasecmp (credit_account,
+ reply->details.ok.credit_account_uri) ) )
+ continue;
+ if ( (NULL != debit_account) &&
+ (0 != strcasecmp (debit_account,
+ cd->debit_account_uri) ) )
+ continue;
+ fprintf (stdout,
+ "%llu: %s->%s (%s) over %s at %s\n",
+ (unsigned long long) cd->serial_id,
+ cd->debit_account_uri,
+ reply->details.ok.credit_account_uri,
+ TALER_B2S (&cd->reserve_pub),
+ TALER_amount2s (&cd->amount),
+ GNUNET_TIME_timestamp2s (cd->execution_date));
}
- fprintf (stdout,
- "End of transactions list.\n");
global_ret = 0;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_NO;
+ break;
+ default:
+ fprintf (stderr,
+ "Failed to obtain credit history from `%s': HTTP status %u (%s)\n",
+ auth.wire_gateway_url,
+ reply->http_status,
+ TALER_ErrorCode_get_hint (reply->ec));
+ if (NULL != reply->response)
+ json_dumpf (reply->response,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = 2;
+ break;
}
-
- /* If credit/debit accounts were specified, use as a filter */
- if ( (NULL != credit_account) &&
- (0 != strcasecmp (credit_account,
- details->credit_account_url) ) )
- return GNUNET_OK;
- if ( (NULL != debit_account) &&
- (0 != strcasecmp (debit_account,
- details->debit_account_url) ) )
- return GNUNET_OK;
-
- fprintf (stdout,
- "%llu: %s->%s (%s) over %s at %s\n",
- (unsigned long long) serial_id,
- details->debit_account_url,
- details->credit_account_url,
- TALER_B2S (&details->reserve_pub),
- TALER_amount2s (&details->amount),
- GNUNET_STRINGS_absolute_time_to_string (details->execution_date));
- return GNUNET_OK;
+ GNUNET_SCHEDULER_shutdown ();
}
@@ -227,7 +226,7 @@ credit_history_cb (void *cls,
* mentioned in the config section given by the user.
*/
static void
-execute_credit_history ()
+execute_credit_history (void)
{
if (NULL != subject)
{
@@ -240,6 +239,7 @@ execute_credit_history ()
&auth,
start_row,
-10,
+ GNUNET_TIME_UNIT_ZERO,
&credit_history_cb,
NULL);
if (NULL == chh)
@@ -253,74 +253,71 @@ execute_credit_history ()
/**
- * Function with the debit debit transaction history.
+ * Function with the debit transaction history.
*
* @param cls closure
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
- * 0 if the bank's reply is bogus (fails to follow the protocol),
- * #MHD_HTTP_NO_CONTENT if there are no more results; on success the
- * last callback is always of this status (even if `abs(num_results)` were
- * already returned).
- * @param ec detailed error code
- * @param serial_id monotonically increasing counter corresponding to the transaction
- * @param details details about the wire transfer
- * @param json detailed response from the HTTPD, or NULL if reply was not in JSON
- * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
+ * @param reply response details
*/
-static int
+static void
debit_history_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint64_t serial_id,
- const struct TALER_BANK_DebitDetails *details,
- const json_t *json)
+ const struct TALER_BANK_DebitHistoryResponse *reply)
{
(void) cls;
- if (MHD_HTTP_OK != http_status)
+ dhh = NULL;
+ switch (reply->http_status)
{
- if ( (MHD_HTTP_NO_CONTENT != http_status) ||
- (TALER_EC_NONE != ec) ||
- (NULL == details) )
+ case 0:
+ fprintf (stderr,
+ "Failed to obtain HTTP reply from `%s'\n",
+ auth.wire_gateway_url);
+ global_ret = 2;
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ fprintf (stdout,
+ "No transactions.\n");
+ global_ret = 0;
+ break;
+ case MHD_HTTP_OK:
+ for (unsigned int i = 0; i<reply->details.ok.details_length; i++)
{
- fprintf (stderr,
- "Failed to obtain debit history: %u/%d\n",
- http_status,
- ec);
- if (NULL != json)
- json_dumpf (json,
- stderr,
- JSON_INDENT (2));
- global_ret = 2;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_NO;
+ const struct TALER_BANK_DebitDetails *dd =
+ &reply->details.ok.details[i];
+
+ /* If credit/debit accounts were specified, use as a filter */
+ if ( (NULL != credit_account) &&
+ (0 != strcasecmp (credit_account,
+ dd->credit_account_uri) ) )
+ continue;
+ if ( (NULL != debit_account) &&
+ (0 != strcasecmp (debit_account,
+ reply->details.ok.debit_account_uri) ) )
+ continue;
+ fprintf (stdout,
+ "%llu: %s->%s (%s) over %s at %s\n",
+ (unsigned long long) dd->serial_id,
+ reply->details.ok.debit_account_uri,
+ dd->credit_account_uri,
+ TALER_B2S (&dd->wtid),
+ TALER_amount2s (&dd->amount),
+ GNUNET_TIME_timestamp2s (dd->execution_date));
}
- fprintf (stdout,
- "End of transactions list.\n");
global_ret = 0;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_NO;
+ break;
+ default:
+ fprintf (stderr,
+ "Failed to obtain debit history from `%s': HTTP status %u (%s)\n",
+ auth.wire_gateway_url,
+ reply->http_status,
+ TALER_ErrorCode_get_hint (reply->ec));
+ if (NULL != reply->response)
+ json_dumpf (reply->response,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = 2;
+ break;
}
-
- /* If credit/debit accounts were specified, use as a filter */
- if ( (NULL != credit_account) &&
- (0 != strcasecmp (credit_account,
- details->credit_account_url) ) )
- return GNUNET_OK;
- if ( (NULL != debit_account) &&
- (0 != strcasecmp (debit_account,
- details->debit_account_url) ) )
- return GNUNET_OK;
-
- fprintf (stdout,
- "%llu: %s->%s (%s) over %s at %s\n",
- (unsigned long long) serial_id,
- details->debit_account_url,
- details->credit_account_url,
- TALER_B2S (&details->wtid),
- TALER_amount2s (&details->amount),
- GNUNET_STRINGS_absolute_time_to_string (details->execution_date));
- return GNUNET_OK;
+ GNUNET_SCHEDULER_shutdown ();
}
@@ -329,7 +326,7 @@ debit_history_cb (void *cls,
* mentioned in the config section given by the user.
*/
static void
-execute_debit_history ()
+execute_debit_history (void)
{
if (NULL != subject)
{
@@ -342,6 +339,7 @@ execute_debit_history ()
&auth,
start_row,
-10,
+ GNUNET_TIME_UNIT_ZERO,
&debit_history_cb,
NULL);
if (NULL == dhh)
@@ -359,33 +357,28 @@ execute_debit_history ()
* execution.
*
* @param cls closure
- * @param response_code HTTP status code
- * @param ec taler error code
- * @param row_id unique ID of the wire transfer in the bank's records
- * @param timestamp when did the transaction go into effect
+ * @param tr response details
*/
static void
confirmation_cb (void *cls,
- unsigned int response_code,
- enum TALER_ErrorCode ec,
- uint64_t row_id,
- struct GNUNET_TIME_Absolute timestamp)
+ const struct TALER_BANK_TransferResponse *tr)
{
(void) cls;
- if (MHD_HTTP_OK != response_code)
+ eh = NULL;
+ if (MHD_HTTP_OK != tr->http_status)
{
fprintf (stderr,
"The wire transfer didn't execute correctly (%u/%d).\n",
- response_code,
- ec);
+ tr->http_status,
+ tr->ec);
GNUNET_SCHEDULER_shutdown ();
return;
}
fprintf (stdout,
"Wire transfer #%llu executed successfully at %s.\n",
- (unsigned long long) row_id,
- GNUNET_STRINGS_absolute_time_to_string (timestamp));
+ (unsigned long long) tr->details.ok.row_id,
+ GNUNET_TIME_timestamp2s (tr->details.ok.timestamp));
global_ret = 0;
GNUNET_SCHEDULER_shutdown ();
}
@@ -395,11 +388,12 @@ confirmation_cb (void *cls,
* Ask the bank to execute a wire transfer.
*/
static void
-execute_wire_transfer ()
+execute_wire_transfer (void)
{
struct TALER_WireTransferIdentifierRawP wtid;
void *buf;
size_t buf_size;
+ char *params;
if (NULL != debit_account)
{
@@ -408,6 +402,10 @@ execute_wire_transfer ()
GNUNET_SCHEDULER_shutdown ();
return;
}
+
+ /* See if subject was given as a payto-parameter. */
+ if (NULL == subject)
+ subject = TALER_payto_get_subject (credit_account);
if (NULL != subject)
{
if (GNUNET_OK !=
@@ -418,10 +416,9 @@ execute_wire_transfer ()
{
fprintf (stderr,
"Error: wire transfer subject must be a WTID\n");
+ GNUNET_SCHEDULER_shutdown ();
return;
}
- GNUNET_SCHEDULER_shutdown ();
- return;
}
else
{
@@ -430,6 +427,10 @@ execute_wire_transfer ()
&wtid,
sizeof (wtid));
}
+ params = strchr (credit_account,
+ (unsigned char) '&');
+ if (NULL != params)
+ *params = '\0';
TALER_BANK_prepare_transfer (credit_account,
&amount,
"http://exchange.example.com/",
@@ -442,6 +443,7 @@ execute_wire_transfer ()
buf_size,
&confirmation_cb,
NULL);
+ GNUNET_free (buf);
if (NULL == eh)
{
fprintf (stderr,
@@ -456,39 +458,29 @@ execute_wire_transfer ()
* Function called with the result of the operation.
*
* @param cls closure
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
- * 0 if the bank's reply is bogus (fails to follow the protocol)
- * @param ec detailed error code
- * @param serial_id unique ID of the wire transfer in the bank's records; UINT64_MAX on error
- * @param timestamp timestamp when the transaction got settled at the bank.
- * @param json detailed response from the HTTPD, or NULL if reply was not in JSON
+ * @param air response details
*/
static void
res_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint64_t serial_id,
- struct GNUNET_TIME_Absolute timestamp,
- const json_t *json)
+ const struct TALER_BANK_AdminAddIncomingResponse *air)
{
(void) cls;
- (void) timestamp;
op = NULL;
- switch (ec)
+ switch (air->http_status)
{
- case TALER_EC_NONE:
+ case MHD_HTTP_OK:
global_ret = 0;
fprintf (stdout,
"%llu\n",
- (unsigned long long) serial_id);
+ (unsigned long long) air->details.ok.serial_id);
break;
default:
fprintf (stderr,
"Operation failed with status code %u/%u\n",
- (unsigned int) ec,
- http_status);
- if (NULL != json)
- json_dumpf (json,
+ (unsigned int) air->ec,
+ air->http_status);
+ if (NULL != air->response)
+ json_dumpf (air->response,
stderr,
JSON_INDENT (2));
break;
@@ -501,7 +493,7 @@ res_cb (void *cls,
* Ask the bank to execute a wire transfer to the exchange.
*/
static void
-execute_admin_transfer ()
+execute_admin_transfer (void)
{
struct TALER_ReservePublicKeyP reserve_pub;
@@ -529,7 +521,7 @@ execute_admin_transfer ()
&auth,
&reserve_pub,
&amount,
- credit_account,
+ debit_account,
&res_cb,
NULL);
if (NULL == op)
@@ -569,12 +561,22 @@ run (void *cls,
rc = GNUNET_CURL_gnunet_rc_create (ctx);
if (NULL != account_section)
{
+ if (0 != strncasecmp ("exchange-accountcredentials-",
+ account_section,
+ strlen ("exchange-accountcredentials-")))
+ {
+ fprintf (stderr,
+ "Error: invalid section specified, must begin with `%s`\n",
+ "exchange-accountcredentials-");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
if ( (NULL != auth.wire_gateway_url) ||
(NULL != auth.details.basic.username) ||
(NULL != auth.details.basic.password) )
{
fprintf (stderr,
- "Conflicting authentication options provided. Please only use one method.\n");
+ "Error: Conflicting authentication options provided. Please only use one method.\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -584,7 +586,7 @@ run (void *cls,
&auth))
{
fprintf (stderr,
- "Authentication information not found in configuration section `%s'\n",
+ "Error: Authentication information not found in configuration section `%s'\n",
account_section);
GNUNET_SCHEDULER_shutdown ();
return;
@@ -601,16 +603,28 @@ run (void *cls,
else if (NULL == auth.wire_gateway_url)
{
fprintf (stderr,
- "No account specified (use -b or -s options).\n");
+ "Error: No account specified (use -b or -s options).\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
}
+ if ( (NULL == auth.wire_gateway_url) ||
+ (0 == strlen (auth.wire_gateway_url)) ||
+ (0 != strncasecmp ("http",
+ auth.wire_gateway_url,
+ strlen ("http"))) )
+ {
+ fprintf (stderr,
+ "Error: Invalid wire gateway URL `%s' configured.\n",
+ auth.wire_gateway_url);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
if ( (GNUNET_YES == incoming_history) &&
- (GNUNET_YES == incoming_history) )
+ (GNUNET_YES == outgoing_history) )
{
fprintf (stderr,
- "Please specify only -i or -o, but not both.\n");
+ "Error: Please specify only -i or -o, but not both.\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -643,7 +657,7 @@ run (void *cls,
/**
- * The main function of the taler-bank-transfer tool
+ * The main function of the taler-exchange-wire-gateway-client
*
* @param argc number of arguments from the command line
* @param argv command line arguments
@@ -664,7 +678,6 @@ main (int argc,
"URL",
"Wire gateway URL to use to talk to the bank",
&auth.wire_gateway_url),
- GNUNET_GETOPT_option_help ("Deposit funds into a Taler reserve"),
GNUNET_GETOPT_option_string ('C',
"credit",
"ACCOUNT",
@@ -710,21 +723,30 @@ main (int argc,
&start_row),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue ret;
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-bank-transfer",
- "WARNING",
- NULL));
- global_ret = 1;
+ /* 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 */
+ (void) TALER_project_data_default ();
if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc, argv,
- "taler-bank-transfer",
- "Execute bank transfer to the exchange",
- options,
- &run, NULL))
- return 1;
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return 4;
+ global_ret = 1;
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-wire-gateway-client",
+ gettext_noop ("Client tool of the Taler Wire Gateway"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return 3;
+ if (GNUNET_NO == ret)
+ return 0;
return global_ret;
}
-/* end taler-bank-transfer.c */
+/* end taler-wire-gateway-client.c */
diff --git a/src/bank-lib/taler-fakebank-run.c b/src/bank-lib/taler-fakebank-run.c
index 4b94a4bd9..c15145ecb 100644
--- a/src/bank-lib/taler-fakebank-run.c
+++ b/src/bank-lib/taler-fakebank-run.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2016, 2017 Taler Systems SA
+ Copyright (C) 2016-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
@@ -21,12 +21,73 @@
* @file bank-lib/taler-fakebank-run.c
* @brief Launch the fakebank, for testing the fakebank itself.
* @author Marcello Stanisci
+ * @author Christian Grothoff
*/
-
#include "platform.h"
#include "taler_fakebank_lib.h"
+#include "taler_mhd_lib.h"
+
+/**
+ * Number of threads to use (-n)
+ */
+static unsigned int num_threads;
+
+/**
+ * Force connection close after each request (-C)
+ */
+static int connection_close;
+
+/**
+ * Global return value.
+ */
+static int ret;
+
+/**
+ * Handle for the service.
+ */
+static struct TALER_FAKEBANK_Handle *fb;
+
+/**
+ * Keepalive task in multi-threaded mode.
+ */
+static struct GNUNET_SCHEDULER_Task *keepalive;
+
+/**
+ * Amount to credit an account with on /register.
+ */
+static struct TALER_Amount signup_bonus;
+
+/**
+ * Stop the process.
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+ TALER_FAKEBANK_stop (fb);
+ fb = NULL;
+ if (NULL != keepalive)
+ {
+ GNUNET_SCHEDULER_cancel (keepalive);
+ keepalive = NULL;
+ }
+}
+
+
+/**
+ * Task that should never be run.
+ *
+ * @param cls NULL
+ */
+static void
+keepalive_task (void *cls)
+{
+ (void) cls;
+ GNUNET_assert (0);
+}
-int ret;
/**
* Main function that will be run.
@@ -43,7 +104,11 @@ run (void *cls,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *cfg)
{
+ unsigned long long port = 8082;
+ unsigned long long ram = 1024 * 128; /* 128 k entries */
char *currency_string;
+ char *hostname;
+ char *exchange_url;
(void) cls;
(void) args;
@@ -52,14 +117,91 @@ run (void *cls,
TALER_config_get_currency (cfg,
&currency_string))
{
- ret = 1;
+ ret = EXIT_NOTCONFIGURED;
return;
}
- if (NULL == TALER_FAKEBANK_start (8082,
- currency_string))
- ret = 1;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ "bank",
+ "HTTP_PORT",
+ &port))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Listening on default port %llu\n",
+ port);
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "bank",
+ "SUGGESTED_EXCHANGE",
+ &exchange_url))
+ {
+ /* no suggested exchange */
+ exchange_url = NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "bank",
+ "HOSTNAME",
+ &hostname))
+ {
+ hostname = GNUNET_strdup ("localhost");
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ "bank",
+ "RAM_LIMIT",
+ &ram))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Maximum transaction history in RAM set to default of %llu\n",
+ ram);
+ }
+ {
+ enum TALER_MHD_GlobalOptions go;
+
+ go = TALER_MHD_GO_NONE;
+ if (0 != connection_close)
+ go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
+ TALER_MHD_setup (go);
+ }
+ if (GNUNET_OK !=
+ TALER_amount_is_valid (&signup_bonus))
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (currency_string,
+ &signup_bonus));
+ }
+ if (0 != strcmp (currency_string,
+ signup_bonus.currency))
+ {
+ fprintf (stderr,
+ "Signup bonus and main currency do not match\n");
+ ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ fb = TALER_FAKEBANK_start3 (hostname,
+ (uint16_t) port,
+ exchange_url,
+ currency_string,
+ ram,
+ num_threads,
+ &signup_bonus);
+ GNUNET_free (hostname);
+ GNUNET_free (exchange_url);
GNUNET_free (currency_string);
- ret = 0;
+ if (NULL == fb)
+ {
+ GNUNET_break (0);
+ ret = EXIT_FAILURE;
+ return;
+ }
+ keepalive = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
+ &keepalive_task,
+ NULL);
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ ret = EXIT_SUCCESS;
}
@@ -75,16 +217,33 @@ main (int argc,
char *const *argv)
{
const struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_flag ('C',
+ "connection-close",
+ "force HTTP connections to be closed after each request",
+ &connection_close),
+ GNUNET_GETOPT_option_uint ('n',
+ "num-threads",
+ "NUM_THREADS",
+ "size of the thread pool",
+ &num_threads),
+ TALER_getopt_get_amount ('s',
+ "signup-bonus",
+ "AMOUNT",
+ "amount to credit newly registered account (created out of thin air)",
+ &signup_bonus),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue iret;
- if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc, argv,
- "taler-fakebank-run",
- "Runs the fakebank",
- options,
- &run,
- NULL))
- return 1;
+ iret = GNUNET_PROGRAM_run (argc, argv,
+ "taler-fakebank-run",
+ "Runs the fakebank",
+ options,
+ &run,
+ NULL);
+ if (GNUNET_SYSERR == iret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == iret)
+ return EXIT_SUCCESS;
return ret;
}
diff --git a/src/bank-lib/test_bank.conf b/src/bank-lib/test_bank.conf
new file mode 100644
index 000000000..317bc05ad
--- /dev/null
+++ b/src/bank-lib/test_bank.conf
@@ -0,0 +1,10 @@
+[taler]
+CURRENCY = TESTKUDOS
+
+[bank]
+serve = http
+HTTP_PORT = 8899
+MAX_DEBT_BANK = TESTKUDOS:0.0
+MAX_DEBT = TESTKUDOS:50.0
+
+RAM_LIMIT = 32
diff --git a/src/bank-lib/test_bank.sh b/src/bank-lib/test_bank.sh
new file mode 100755
index 000000000..5ee2bd836
--- /dev/null
+++ b/src/bank-lib/test_bank.sh
@@ -0,0 +1,99 @@
+#!/bin/bash
+# This file is in the public domain.
+# shellcheck disable=SC2317
+set -eu
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo "$1"
+ exit 77
+}
+
+# Cleanup to run whenever we exit
+function cleanup()
+{
+ for n in $(jobs -p)
+ do
+ kill "$n" 2> /dev/null || true
+ done
+ wait
+}
+
+# Install cleanup handler (except for kill -9)
+trap cleanup EXIT
+
+echo -n "Launching bank..."
+
+taler-fakebank-run \
+ -c test_bank.conf \
+ -L DEBUG &> bank.log &
+
+# Wait for bank to be available (usually the slowest)
+for n in $(seq 1 50)
+do
+ echo -n "."
+ sleep 0.2
+ OK=0
+ # bank
+ wget \
+ --tries=1 \
+ --timeout=1 \
+ http://localhost:8899/ \
+ -o /dev/null \
+ -O /dev/null \
+ >/dev/null \
+ || continue
+ OK=1
+ break
+done
+
+if [ 1 != "$OK" ]
+then
+ exit_skip "Failed to launch services (bank)"
+fi
+
+echo "OK"
+
+echo -n "Making wire transfer to exchange ..."
+
+taler-exchange-wire-gateway-client \
+ -b http://localhost:8899/accounts/exchange/taler-wire-gateway/ \
+ -S 0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00 \
+ -D payto://x-taler-bank/localhost:8899/user?receiver-name=user \
+ -a TESTKUDOS:4 > /dev/null
+echo " OK"
+
+echo -n "Requesting exchange incoming transaction list ..."
+
+./taler-exchange-wire-gateway-client \
+ -b http://localhost:8899/accounts/exchange/taler-wire-gateway/ \
+ -i \
+ | grep TESTKUDOS:4 \
+ > /dev/null
+
+echo " OK"
+
+echo -n "Making wire transfer from exchange..."
+
+./taler-exchange-wire-gateway-client \
+ -b http://localhost:8899/accounts/exchange/taler-wire-gateway/ \
+ -S 0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00 \
+ -C payto://x-taler-bank/localhost:8899/merchant?receiver-name=merchant \
+ -a TESTKUDOS:2 \
+ -L DEBUG > /dev/null
+echo " OK"
+
+
+echo -n "Requesting exchange's outgoing transaction list..."
+
+./taler-exchange-wire-gateway-client \
+ -b http://localhost:8899/accounts/exchange/taler-wire-gateway/ \
+ -o \
+ | grep TESTKUDOS:2 \
+ > /dev/null
+
+echo " OK"
+
+echo "All tests passed"
+
+exit 0
diff --git a/src/benchmark/.gitignore b/src/benchmark/.gitignore
new file mode 100644
index 000000000..a1b4711e7
--- /dev/null
+++ b/src/benchmark/.gitignore
@@ -0,0 +1,3 @@
+taler-bank-benchmark
+taler-aggregator-benchmark
+*.edited
diff --git a/src/benchmark/Makefile.am b/src/benchmark/Makefile.am
index af1f5b94c..c82584626 100644
--- a/src/benchmark/Makefile.am
+++ b/src/benchmark/Makefile.am
@@ -11,18 +11,53 @@ if USE_COVERAGE
endif
bin_PROGRAMS = \
+ taler-aggregator-benchmark \
+ taler-bank-benchmark \
taler-exchange-benchmark
+
+taler_aggregator_benchmark_SOURCES = \
+ taler-aggregator-benchmark.c
+taler_aggregator_benchmark_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetjson \
+ -ljansson \
+ -lgnunetutil \
+ $(XLIB)
+
+taler_bank_benchmark_SOURCES = \
+ taler-bank-benchmark.c
+taler_bank_benchmark_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/testing/libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
+ -lgnunetjson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
taler_exchange_benchmark_SOURCES = \
taler-exchange-benchmark.c
taler_exchange_benchmark_LDADD = \
$(LIBGCRYPT_LIBS) \
- $(top_builddir)/src/json/libtalerjson.la \
- $(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/lib/libtalerexchange.la \
+ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
$(top_builddir)/src/testing/libtalertesting.la \
$(top_builddir)/src/bank-lib/libtalerfakebank.la \
$(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
@@ -30,5 +65,10 @@ taler_exchange_benchmark_LDADD = \
$(XLIB)
EXTRA_DIST = \
- benchmark.conf \
- exchange_benchmark_home/.local/share/taler/exchange/offline-keys/master.priv
+ benchmark-common.conf \
+ benchmark-cs.conf \
+ benchmark-rsa.conf \
+ bank-benchmark-cs.conf \
+ bank-benchmark-rsa.conf \
+ coins-cs.conf \
+ coins-rsa.conf
diff --git a/src/benchmark/bank-benchmark-cs.conf b/src/benchmark/bank-benchmark-cs.conf
new file mode 100644
index 000000000..39c82a3fe
--- /dev/null
+++ b/src/benchmark/bank-benchmark-cs.conf
@@ -0,0 +1,5 @@
+# This file is in the public domain.
+@INLINE@ benchmark-common.conf
+@INLINE@ coins-cs.conf
+
+
diff --git a/src/benchmark/bank-benchmark-rsa.conf b/src/benchmark/bank-benchmark-rsa.conf
new file mode 100644
index 000000000..ca5d6b0da
--- /dev/null
+++ b/src/benchmark/bank-benchmark-rsa.conf
@@ -0,0 +1,5 @@
+# This file is in the public domain.
+@INLINE@ benchmark-common.conf
+@INLINE@ coins-rsa.conf
+
+
diff --git a/src/benchmark/benchmark-common.conf b/src/benchmark/benchmark-common.conf
new file mode 100644
index 000000000..e47115a2b
--- /dev/null
+++ b/src/benchmark/benchmark-common.conf
@@ -0,0 +1,106 @@
+# This file is in the public domain.
+[paths]
+TALER_TEST_HOME=exchange_benchmark_home/
+
+[taler]
+CURRENCY=EUR
+CURRENCY_ROUND_UNIT=EUR:0.01
+
+[exchange]
+AML_THRESHOLD=EUR:99999999
+SIGNKEY_LEGAL_DURATION=2 years
+PORT=8081
+MASTER_PUBLIC_KEY=98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+DB=postgres
+BASE_URL="http://localhost:8081/"
+# Only set this option if you are actually running
+# multiple aggregators!
+# AGGREGATOR_SHARD_SIZE=67108864
+WIREWATCH_IDLE_SLEEP_INTERVAL=5 ms
+
+[exchangedb-postgres]
+CONFIG="postgres:///talercheck"
+
+[exchange-offline]
+MASTER_PRIV_FILE=${TALER_TEST_HOME}/.local/share/taler/exchange/offline-keys/master.priv
+
+[taler-exchange-secmod-rsa]
+LOOKAHEAD_SIGN="1 d"
+
+[taler-exchange-secmod-cs]
+LOOKAHEAD_SIGN="1 d"
+
+[taler-exchange-secmod-eddsa]
+DURATION="2 d"
+LOOKAHEAD_SIGN="1 d"
+
+# account-2 is suitable for fakebank
+[exchange-account-1]
+PAYTO_URI = "payto://x-taler-bank/localhost/exchange?receiver-name=exchange"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-1]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8080/accounts/exchange/taler-wire-gateway/"
+
+[admin-accountcredentials-1]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8080/accounts/exchange/taler-wire-gateway/"
+
+# account-2 is suitable for libeufin
+[exchange-account-2]
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+PAYTO_URI = payto://iban/SANDBOXX/DE033310?receiver-name=Exchange+Company
+
+[exchange-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = exchange
+PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8080/accounts/exchange/taler-wire-gateway/"
+
+[admin-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = admin
+PASSWORD = secret
+WIRE_GATEWAY_URL = "http://localhost:8080/accounts/exchange/taler-wire-gateway/"
+
+
+# Trust local exchange for "EUR" currency
+[merchant-exchange-benchmark]
+EXCHANGE_BASE_URL = http://localhost:8081/
+MASTER_KEY=98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+# If currency does not match [TALER] section, the exchange
+# will be ignored!
+CURRENCY = EUR
+
+
+[merchantdb-postgres]
+CONFIG="postgres:///talercheck"
+
+[auditordb-postgres]
+CONFIG="postgres:///talercheck"
+
+[syncdb-postgres]
+CONFIG="postgres:///talercheck"
+
+[exchange]
+WIREWATCH_IDLE_SLEEP_INTERVAL = 5000 ms
+
+[bank]
+HTTP_PORT=8080
+SERVE=http
+RAM_LIMIT=10000000
+
+[libeufin-bank]
+CURRENCY = EUR
+
+[libeufin-nexus]
+DB_CONNECTION="postgresql:///talercheck"
+
+[libeufin-sandbox]
+DB_CONNECTION="postgresql:///talercheck"
+
+[auditor]
+BASE_URL="http://localhost:8083/"
diff --git a/src/benchmark/benchmark-cs.conf b/src/benchmark/benchmark-cs.conf
new file mode 100644
index 000000000..7f660ad31
--- /dev/null
+++ b/src/benchmark/benchmark-cs.conf
@@ -0,0 +1,16 @@
+# This file is in the public domain.
+@INLINE@ benchmark-common.conf
+@INLINE@ coins-cs.conf
+
+[exchange-account-test]
+# What is the bank account (with the "Taler Bank" demo system)? Must end with "/".
+PAYTO_URI = "payto://x-taler-bank/localhost/Exchange"
+# Authentication information for basic authentication
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-test]
+WIRE_GATEWAY_URL = http://localhost:8082/accounts/Exchange/taler-wire-gateway/
+WIRE_GATEWAY_AUTH_METHOD = "basic"
+USERNAME = Exchange
+PASSWORD = x
diff --git a/src/benchmark/benchmark-rsa.conf b/src/benchmark/benchmark-rsa.conf
new file mode 100644
index 000000000..a6c1512ee
--- /dev/null
+++ b/src/benchmark/benchmark-rsa.conf
@@ -0,0 +1,16 @@
+# This file is in the public domain.
+@INLINE@ benchmark-common.conf
+@INLINE@ coins-rsa.conf
+
+[exchange-account-test]
+# What is the bank account (with the "Taler Bank" demo system)? Must end with "/".
+PAYTO_URI = "payto://x-taler-bank/localhost/Exchange"
+# Authentication information for basic authentication
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-test]
+WIRE_GATEWAY_URL = http://localhost:8082/accounts/Exchange/taler-wire-gateway/
+WIRE_GATEWAY_AUTH_METHOD = "basic"
+USERNAME = Exchange
+PASSWORD = x
diff --git a/src/benchmark/benchmark.conf b/src/benchmark/benchmark.conf
deleted file mode 100644
index b9d1d141d..000000000
--- a/src/benchmark/benchmark.conf
+++ /dev/null
@@ -1,151 +0,0 @@
-# This file is in the public domain.
-#
-[paths]
-# Persistent data storage for the testcase
-# This value is a default for `taler_config_home'
-taler_test_home = exchange_benchmark_home/
-
-[taler]
-# Currency supported by the exchange (can only be one)
-currency = EUR
-CURRENCY_ROUND_UNIT = EUR:0.01
-
-[exchange]
-# how long is one signkey valid?
-signkey_duration = 4 weeks
-# how long are the signatures with the signkey valid?
-legal_duration = 2 years
-# how long do we provide to clients denomination and signing keys
-# ahead of time?
-lookahead_provide = 4 weeks 1 day
-# HTTP port the exchange listens to
-port = 8081
-# Master public key used to sign the exchange's various keys
-master_public_key = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
-# How to access our database
-DB = postgres
-# Base URL of the exchange. Must be set to a URL where the
-# exchange (or the twister) is actually listening.
-base_url = "http://localhost:8081/"
-# Keep it short so the test runs fast.
-lookahead_sign = 12 h
-
-[auditor]
-BASE_URL = "http://localhost:8083/"
-
-[exchangedb-postgres]
-config = "postgres:///talercheck"
-
-[benchmark-remote-exchange]
-host = localhost
-# Adjust $HOME to match remote target!
-dir = $HOME/repos/taler/exchange/src/benchmark
-
-[bank]
-HTTP_PORT = 8082
-SERVE = http
-MAX_DEBT = EUR:100000000000.0
-MAX_DEBT_BANK = EUR:1000000000000000.0
-
-[benchmark]
-USER_PAYTO_URI = payto://x-taler-bank/localhost:8082/42
-
-[exchange-account-2]
-# What is the payto://-URL of the exchange (to generate wire response)
-PAYTO_URI = "payto://x-taler-bank/localhost:8082/Exchange"
-# What is the bank account (with the "Taler Bank" demo system)? Must end with "/".
-WIRE_GATEWAY_URL = http://localhost:8082/taler-wire-gateway/Exchange/
-# This is the response we give out for the /wire request. It provides
-# wallets with the bank information for transfers to the exchange.
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-exchange.json
-# Authentication information for basic authentication
-WIRE_GATEWAY_AUTH_METHOD = "basic"
-username = Exchange
-password = x
-
-enable_debit = YES
-enable_credit = YES
-
-
-
-[fees-x-taler-bank]
-# Fees for the foreseeable future...
-# If you see this after 2017, update to match the next 10 years...
-wire-fee-2018 = EUR:0.01
-wire-fee-2019 = EUR:0.01
-wire-fee-2020 = EUR:0.01
-wire-fee-2021 = EUR:0.01
-wire-fee-2022 = EUR:0.01
-wire-fee-2023 = EUR:0.01
-wire-fee-2024 = EUR:0.01
-wire-fee-2025 = EUR:0.01
-wire-fee-2026 = EUR:0.01
-wire-fee-2027 = EUR:0.01
-
-closing-fee-2018 = EUR:0.01
-closing-fee-2019 = EUR:0.01
-closing-fee-2020 = EUR:0.01
-closing-fee-2021 = EUR:0.01
-closing-fee-2022 = EUR:0.01
-closing-fee-2023 = EUR:0.01
-closing-fee-2024 = EUR:0.01
-closing-fee-2025 = EUR:0.01
-closing-fee-2026 = EUR:0.01
-closing-fee-2027 = EUR:0.01
-
-# Sections starting with "coin_" specify which denominations
-# the exchange should support (and their respective fee structure)
-[coin_eur_ct_1]
-value = EUR:0.01
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.00
-fee_deposit = EUR:0.00
-fee_refresh = EUR:0.01
-fee_refund = EUR:0.01
-rsa_keysize = 2048
-
-[coin_eur_ct_10]
-value = EUR:0.10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 2048
-
-[coin_eur_1]
-value = EUR:1
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 2048
-
-[coin_eur_5]
-value = EUR:5
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 2048
-
-[coin_eur_10]
-value = EUR:10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 2048
diff --git a/src/benchmark/coins-cs.conf b/src/benchmark/coins-cs.conf
new file mode 100644
index 000000000..c4b5a45c1
--- /dev/null
+++ b/src/benchmark/coins-cs.conf
@@ -0,0 +1,58 @@
+# This file is in the public domain.
+#
+# Sections starting with "coin_" specify which denominations
+# the exchange should support (and their respective fee structure)
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_5]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_10]
+value = EUR:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
diff --git a/src/benchmark/coins-rsa.conf b/src/benchmark/coins-rsa.conf
new file mode 100644
index 000000000..42eb8acfc
--- /dev/null
+++ b/src/benchmark/coins-rsa.conf
@@ -0,0 +1,63 @@
+# This file is in the public domain.
+#
+# Sections starting with "coin_" specify which denominations
+# the exchange should support (and their respective fee structure)
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 2048
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 2048
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 2048
+
+[coin_eur_5]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 2048
+
+[coin_eur_10]
+value = EUR:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 2048
diff --git a/src/benchmark/exchange_benchmark_home/.local/share/taler/exchange/offline-keys/master.priv b/src/benchmark/exchange_benchmark_home/.local/share/taler/exchange/offline-keys/master.priv
deleted file mode 100644
index 394926938..000000000
--- a/src/benchmark/exchange_benchmark_home/.local/share/taler/exchange/offline-keys/master.priv
+++ /dev/null
@@ -1 +0,0 @@
-pÚ^ó-Ú33ˆ€XXÁ!ˆ\0qúýµmUþ_‰ˆ \ No newline at end of file
diff --git a/src/benchmark/taler-aggregator-benchmark.c b/src/benchmark/taler-aggregator-benchmark.c
new file mode 100644
index 000000000..228d050e4
--- /dev/null
+++ b/src/benchmark/taler-aggregator-benchmark.c
@@ -0,0 +1,658 @@
+/*
+ This file is part of TALER
+ (C) 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
+ 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 benchmark/taler-aggregator-benchmark.c
+ * @brief Setup exchange database suitable for aggregator benchmarking
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_error_codes.h"
+
+
+/**
+ * Exit code.
+ */
+static int global_ret;
+
+/**
+ * How many deposits we want to create per merchant.
+ */
+static unsigned int howmany_deposits = 1;
+
+/**
+ * How many merchants do we want to setup.
+ */
+static unsigned int howmany_merchants = 1;
+
+/**
+ * Probability of a refund, as in $NUMBER:100.
+ * Use 0 for no refunds.
+ */
+static unsigned int refund_rate = 0;
+
+/**
+ * Currency used.
+ */
+static char *currency;
+
+/**
+ * Configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Database plugin.
+ */
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+
+/**
+ * Main task doing the work().
+ */
+static struct GNUNET_SCHEDULER_Task *task;
+
+/**
+ * Hash of the denomination.
+ */
+static struct TALER_DenominationHashP h_denom_pub;
+
+/**
+ * "signature" to use for the coin(s).
+ */
+static struct TALER_DenominationSignature denom_sig;
+
+/**
+ * Time range when deposits start.
+ */
+static struct GNUNET_TIME_Timestamp start;
+
+/**
+ * Time range when deposits end.
+ */
+static struct GNUNET_TIME_Timestamp end;
+
+
+/**
+ * Throw a weighted coin with @a probability.
+ *
+ * @return #GNUNET_OK with @a probability,
+ * #GNUNET_NO with 1 - @a probability
+ */
+static unsigned int
+eval_probability (float probability)
+{
+ uint64_t random;
+ float random_01;
+
+ random = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
+ UINT64_MAX);
+ random_01 = (double) random / (double) UINT64_MAX;
+ return (random_01 <= probability) ? GNUNET_OK : GNUNET_NO;
+}
+
+
+/**
+ * Randomize data at pointer @a x
+ *
+ * @param x pointer to data to randomize
+ */
+#define RANDOMIZE(x) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, x, sizeof (*x))
+
+
+/**
+ * Initialize @a out with an amount given by @a val and
+ * @a frac using the main "currency".
+ *
+ * @param val value to set
+ * @param frac fraction to set
+ * @param[out] out where to write the amount
+ */
+static void
+make_amount (unsigned int val,
+ unsigned int frac,
+ struct TALER_Amount *out)
+{
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (currency,
+ out));
+ out->value = val;
+ out->fraction = frac;
+}
+
+
+/**
+ * Create random-ish timestamp.
+ *
+ * @return time stamp between start and end
+ */
+static struct GNUNET_TIME_Timestamp
+random_time (void)
+{
+ uint64_t delta;
+ struct GNUNET_TIME_Absolute ret;
+
+ delta = end.abs_time.abs_value_us - start.abs_time.abs_value_us;
+ delta = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
+ delta);
+ ret.abs_value_us = start.abs_time.abs_value_us + delta;
+ return GNUNET_TIME_absolute_to_timestamp (ret);
+}
+
+
+/**
+ * Function run on shutdown.
+ *
+ * @param cls unused
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+ if (NULL != plugin)
+ {
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+ }
+ if (NULL != task)
+ {
+ GNUNET_SCHEDULER_cancel (task);
+ task = NULL;
+ }
+ TALER_denom_sig_free (&denom_sig);
+}
+
+
+struct Merchant
+{
+
+ /**
+ * Public key of the merchant. Enables later identification
+ * of the merchant in case of a need to rollback transactions.
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+
+ /**
+ * Hash of the (canonical) representation of @e wire, used
+ * to check the signature on the request. Generated by
+ * the exchange from the detailed wire data provided by the
+ * merchant.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * Salt used when computing @e h_wire.
+ */
+ struct TALER_WireSaltP wire_salt;
+
+ /**
+ * Account information for the merchant.
+ */
+ char *payto_uri;
+
+};
+
+struct Deposit
+{
+
+ /**
+ * Information about the coin that is being deposited.
+ */
+ struct TALER_CoinPublicInfo coin;
+
+ /**
+ * Hash over the proposal data between merchant and customer
+ * (remains unknown to the Exchange).
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+};
+
+
+/**
+ * Add a refund from @a m for @a d.
+ *
+ * @param m merchant granting the refund
+ * @param d deposit being refunded
+ * @return true on success
+ */
+static bool
+add_refund (const struct Merchant *m,
+ const struct Deposit *d)
+{
+ struct TALER_EXCHANGEDB_Refund r;
+
+ r.coin = d->coin;
+ r.details.merchant_pub = m->merchant_pub;
+ RANDOMIZE (&r.details.merchant_sig);
+ r.details.h_contract_terms = d->h_contract_terms;
+ r.details.rtransaction_id = 42;
+ make_amount (0, 5000000, &r.details.refund_amount);
+ make_amount (0, 5, &r.details.refund_fee);
+ if (0 >=
+ plugin->insert_refund (plugin->cls,
+ &r))
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Add a (random-ish) deposit for merchant @a m.
+ *
+ * @param m merchant to receive the deposit
+ * @return true on success
+ */
+static bool
+add_deposit (const struct Merchant *m)
+{
+ struct Deposit d;
+ struct TALER_EXCHANGEDB_CoinDepositInformation deposit;
+ struct TALER_EXCHANGEDB_BatchDeposit bd = {
+ .cdis = &deposit,
+ .num_cdis = 1
+ };
+ uint64_t known_coin_id;
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+
+ RANDOMIZE (&d.coin.coin_pub);
+ d.coin.denom_pub_hash = h_denom_pub;
+ d.coin.denom_sig = denom_sig;
+ RANDOMIZE (&d.h_contract_terms);
+ d.coin.no_age_commitment = true;
+ memset (&d.coin.h_age_commitment,
+ 0,
+ sizeof (d.coin.h_age_commitment));
+
+ if (0 >=
+ plugin->ensure_coin_known (plugin->cls,
+ &d.coin,
+ &known_coin_id,
+ &dph,
+ &agh))
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return false;
+ }
+ deposit.coin = d.coin;
+ RANDOMIZE (&deposit.csig);
+ bd.merchant_pub = m->merchant_pub;
+ bd.h_contract_terms = d.h_contract_terms;
+ bd.wire_salt = m->wire_salt;
+ bd.receiver_wire_account = m->payto_uri;
+ bd.wallet_timestamp = random_time ();
+ do {
+ bd.refund_deadline = random_time ();
+ bd.wire_deadline = random_time ();
+ } while (GNUNET_TIME_timestamp_cmp (bd.wire_deadline,
+ <,
+ bd.refund_deadline));
+
+ make_amount (1, 0, &deposit.amount_with_fee);
+
+ {
+ struct GNUNET_TIME_Timestamp now;
+ bool balance_ok;
+ uint32_t bad_idx;
+ bool conflict;
+
+ now = random_time ();
+ if (0 >=
+ plugin->do_deposit (plugin->cls,
+ &bd,
+ &now,
+ &balance_ok,
+ &bad_idx,
+ &conflict))
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return false;
+ }
+ }
+ if (GNUNET_YES ==
+ eval_probability (((float) refund_rate) / 100.0))
+ return add_refund (m,
+ &d);
+ return true;
+}
+
+
+/**
+ * Function to do the work.
+ *
+ * @param cls unused
+ */
+static void
+work (void *cls)
+{
+ struct Merchant m;
+ uint64_t rnd1;
+ uint64_t rnd2;
+
+ (void) cls;
+ task = NULL;
+ rnd1 = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
+ UINT64_MAX);
+ rnd2 = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
+ UINT64_MAX);
+ GNUNET_asprintf (&m.payto_uri,
+ "payto://x-taler-bank/localhost:8082/account-%llX-%llX",
+ (unsigned long long) rnd1,
+ (unsigned long long) rnd2);
+ RANDOMIZE (&m.merchant_pub);
+ RANDOMIZE (&m.wire_salt);
+ TALER_merchant_wire_signature_hash (m.payto_uri,
+ &m.wire_salt,
+ &m.h_wire);
+ if (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "aggregator-benchmark-fill"))
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_free (m.payto_uri);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ for (unsigned int i = 0; i<howmany_deposits; i++)
+ {
+ if (! add_deposit (&m))
+ {
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ GNUNET_free (m.payto_uri);
+ return;
+ }
+ }
+ if (0 <=
+ plugin->commit (plugin->cls))
+ {
+ if (0 == --howmany_merchants)
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ GNUNET_free (m.payto_uri);
+ return;
+ }
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to commit, will try again\n");
+ }
+ GNUNET_free (m.payto_uri);
+ task = GNUNET_SCHEDULER_add_now (&work,
+ NULL);
+}
+
+
+/**
+ * Actual execution.
+ *
+ * @param cls unused
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param c configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+{
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
+
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+ /* make sure everything 'ends' before the current time,
+ so that the aggregator will process everything without
+ need for time-travel */
+ end = GNUNET_TIME_timestamp_get ();
+ start = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_subtract (end.abs_time,
+ GNUNET_TIME_UNIT_MONTHS));
+ cfg = c;
+ if (GNUNET_OK !=
+ TALER_config_get_currency (cfg,
+ &currency))
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ plugin = TALER_EXCHANGEDB_plugin_load (cfg);
+ if (NULL == plugin)
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_SYSERR ==
+ plugin->preflight (plugin->cls))
+ {
+ global_ret = EXIT_FAILURE;
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ return;
+ }
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ memset (&issue,
+ 0,
+ sizeof (issue));
+ RANDOMIZE (&issue.signature);
+ issue.start
+ = start;
+ issue.expire_withdraw
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (start.abs_time,
+ GNUNET_TIME_UNIT_DAYS));
+ issue.expire_deposit
+ = end;
+ issue.expire_legal
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (end.abs_time,
+ GNUNET_TIME_UNIT_YEARS));
+ {
+ struct TALER_DenominationPrivateKey pk;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_PlanchetDetail pd;
+ struct TALER_BlindedDenominationSignature bds;
+ struct TALER_PlanchetMasterSecretP ps;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_AgeCommitmentHash hac;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ const struct TALER_ExchangeWithdrawValues *alg_values;
+
+ RANDOMIZE (&coin_pub);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_priv_create (&pk,
+ &denom_pub,
+ GNUNET_CRYPTO_BSA_RSA,
+ 1024));
+ alg_values = TALER_denom_ewv_rsa_singleton ();
+ denom_pub.age_mask = issue.age_mask;
+ TALER_denom_pub_hash (&denom_pub,
+ &h_denom_pub);
+ make_amount (2, 0, &issue.value);
+ make_amount (0, 5, &issue.fees.withdraw);
+ make_amount (0, 5, &issue.fees.deposit);
+ make_amount (0, 5, &issue.fees.refresh);
+ make_amount (0, 5, &issue.fees.refund);
+ issue.denom_hash = h_denom_pub;
+ if (0 >=
+ plugin->insert_denomination_info (plugin->cls,
+ &denom_pub,
+ &issue))
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+
+ TALER_planchet_blinding_secret_create (&ps,
+ TALER_denom_ewv_rsa_singleton (),
+ &bks);
+
+ {
+ struct GNUNET_HashCode seed;
+ struct TALER_AgeMask mask = {
+ .bits = 1 | (1 << 8) | (1 << 12) | (1 << 16) | (1 << 18)
+ };
+ struct TALER_AgeCommitmentProof acp = {0};
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &seed,
+ sizeof(seed));
+ TALER_age_restriction_commit (&mask,
+ 13,
+ &seed,
+ &acp);
+ TALER_age_commitment_hash (&acp.commitment,
+ &hac);
+ }
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_blind (&denom_pub,
+ &bks,
+ NULL,
+ &hac,
+ &coin_pub,
+ alg_values,
+ &c_hash,
+ &pd.blinded_planchet));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sign_blinded (&bds,
+ &pk,
+ false,
+ &pd.blinded_planchet));
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&denom_sig,
+ &bds,
+ &bks,
+ &c_hash,
+ alg_values,
+ &denom_pub));
+ TALER_blinded_denom_sig_free (&bds);
+ TALER_denom_pub_free (&denom_pub);
+ TALER_denom_priv_free (&pk);
+ }
+
+ {
+ struct TALER_WireFeeSet fees;
+ struct TALER_MasterSignatureP master_sig;
+ unsigned int year;
+ struct GNUNET_TIME_Timestamp ws;
+ struct GNUNET_TIME_Timestamp we;
+
+ year = GNUNET_TIME_get_current_year ();
+ for (unsigned int y = year - 1; y<year + 2; y++)
+ {
+ ws = GNUNET_TIME_absolute_to_timestamp (GNUNET_TIME_year_to_time (y - 1));
+ we = GNUNET_TIME_absolute_to_timestamp (GNUNET_TIME_year_to_time (y));
+ make_amount (0, 5, &fees.wire);
+ make_amount (0, 5, &fees.closing);
+ memset (&master_sig,
+ 0,
+ sizeof (master_sig));
+ if (0 >
+ plugin->insert_wire_fee (plugin->cls,
+ "x-taler-bank",
+ ws,
+ we,
+ &fees,
+ &master_sig))
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ }
+ }
+
+ task = GNUNET_SCHEDULER_add_now (&work,
+ NULL);
+}
+
+
+/**
+ * The main function of the taler-aggregator-benchmark tool.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, non-zero on failure
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_uint ('d',
+ "deposits",
+ "DN",
+ "How many deposits we should instantiate per merchant",
+ &howmany_deposits),
+ GNUNET_GETOPT_option_uint ('m',
+ "merchants",
+ "DM",
+ "How many merchants should we create",
+ &howmany_merchants),
+ GNUNET_GETOPT_option_uint ('r',
+ "refunds",
+ "RATE",
+ "Probability of refund per deposit (0-100)",
+ &refund_rate),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue result;
+
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ if (0 >=
+ (result = GNUNET_PROGRAM_run (argc,
+ argv,
+ "taler-aggregator-benchmark",
+ "generate database to benchmark the aggregator",
+ options,
+ &run,
+ NULL)))
+ {
+ if (GNUNET_NO == result)
+ return EXIT_SUCCESS;
+ return EXIT_INVALIDARGUMENT;
+ }
+ return global_ret;
+}
diff --git a/src/benchmark/taler-bank-benchmark.c b/src/benchmark/taler-bank-benchmark.c
new file mode 100644
index 000000000..528798424
--- /dev/null
+++ b/src/benchmark/taler-bank-benchmark.c
@@ -0,0 +1,576 @@
+/*
+ This file is part of TALER
+ (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 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 benchmark/taler-bank-benchmark.c
+ * @brief code to benchmark only the 'bank' and the 'taler-exchange-wirewatch' tool
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+// TODO:
+// - use more than one 'client' bank account
+// - also add taler-exchange-transfer to simulate outgoing payments
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include <sys/resource.h>
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_json_lib.h"
+#include "taler_bank_service.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_fakebank_lib.h"
+#include "taler_testing_lib.h"
+#include "taler_error_codes.h"
+
+#define SHARD_SIZE "1024"
+
+/**
+ * Credentials to use for the benchmark.
+ */
+static struct TALER_TESTING_Credentials cred;
+
+/**
+ * Array of all the commands the benchmark is running.
+ */
+static struct TALER_TESTING_Command *all_commands;
+
+/**
+ * Name of our configuration file.
+ */
+static char *cfg_filename;
+
+/**
+ * Use the fakebank instead of LibEuFin.
+ */
+static int use_fakebank;
+
+/**
+ * Verbosity level.
+ */
+static unsigned int verbose;
+
+/**
+ * How many reserves we want to create per client.
+ */
+static unsigned int howmany_reserves = 1;
+
+/**
+ * How many clients we want to create.
+ */
+static unsigned int howmany_clients = 1;
+
+/**
+ * How many wirewatch processes do we want to create.
+ */
+static unsigned int start_wirewatch;
+
+/**
+ * Log level used during the run.
+ */
+static char *loglev;
+
+/**
+ * Log file.
+ */
+static char *logfile;
+
+/**
+ * Configuration.
+ */
+static struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Section with the configuration data for the exchange
+ * bank account.
+ */
+static char *exchange_bank_section;
+
+/**
+ * Currency used.
+ */
+static char *currency;
+
+/**
+ * Array of command labels.
+ */
+static char **labels;
+
+/**
+ * Length of #labels.
+ */
+static unsigned int label_len;
+
+/**
+ * Offset in #labels.
+ */
+static unsigned int label_off;
+
+/**
+ * Performance counters.
+ */
+static struct TALER_TESTING_Timer timings[] = {
+ { .prefix = "createreserve" },
+ { .prefix = NULL }
+};
+
+
+/**
+ * Add label to the #labels table and return it.
+ *
+ * @param label string to add to the table
+ * @return same string, now stored in the table
+ */
+const char *
+add_label (char *label)
+{
+ if (label_off == label_len)
+ GNUNET_array_grow (labels,
+ label_len,
+ label_len * 2 + 4);
+ labels[label_off++] = label;
+ return label;
+}
+
+
+/**
+ * Print performance statistics for this process.
+ */
+static void
+print_stats (void)
+{
+ for (unsigned int i = 0; NULL != timings[i].prefix; i++)
+ {
+ char *total;
+ char *latency;
+
+ total = GNUNET_strdup (
+ GNUNET_STRINGS_relative_time_to_string (timings[i].total_duration,
+ true));
+ latency = GNUNET_strdup (
+ GNUNET_STRINGS_relative_time_to_string (timings[i].success_latency,
+ true));
+ fprintf (stderr,
+ "%s-%d took %s in total with %s for latency for %u executions (%u repeats)\n",
+ timings[i].prefix,
+ (int) getpid (),
+ total,
+ latency,
+ timings[i].num_commands,
+ timings[i].num_retries);
+ GNUNET_free (total);
+ GNUNET_free (latency);
+ }
+}
+
+
+/**
+ * Actual commands construction and execution.
+ *
+ * @param cls unused
+ * @param is interpreter to run commands with
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+ char *total_reserve_amount;
+ size_t len;
+
+ (void) cls;
+ len = howmany_reserves + 2;
+ all_commands = GNUNET_malloc_large ((1 + len)
+ * sizeof (struct TALER_TESTING_Command));
+ GNUNET_assert (NULL != all_commands);
+ all_commands[0]
+ = TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true);
+
+ GNUNET_asprintf (&total_reserve_amount,
+ "%s:5",
+ currency);
+ for (unsigned int j = 0; j < howmany_reserves; j++)
+ {
+ char *create_reserve_label;
+
+ GNUNET_asprintf (&create_reserve_label,
+ "createreserve-%u",
+ j);
+ // TODO: vary user accounts more...
+ all_commands[1 + j]
+ = TALER_TESTING_cmd_admin_add_incoming_retry (
+ TALER_TESTING_cmd_admin_add_incoming (add_label (
+ create_reserve_label),
+ total_reserve_amount,
+ &cred.ba_admin,
+ cred.user42_payto));
+ }
+ GNUNET_free (total_reserve_amount);
+ all_commands[1 + howmany_reserves]
+ = TALER_TESTING_cmd_stat (timings);
+ all_commands[1 + howmany_reserves + 1]
+ = TALER_TESTING_cmd_end ();
+ TALER_TESTING_run2 (is,
+ all_commands,
+ GNUNET_TIME_UNIT_FOREVER_REL); /* no timeout */
+}
+
+
+/**
+ * Starts #howmany_clients workers to run the client logic from #run().
+ */
+static enum GNUNET_GenericReturnValue
+launch_clients (void)
+{
+ enum GNUNET_GenericReturnValue result = GNUNET_OK;
+ pid_t cpids[howmany_clients];
+
+ if (1 == howmany_clients)
+ {
+ /* do everything in this process */
+ result = TALER_TESTING_loop (&run,
+ NULL);
+ if (verbose)
+ print_stats ();
+ return result;
+ }
+ /* start work processes */
+ for (unsigned int i = 0; i<howmany_clients; i++)
+ {
+ if (0 == (cpids[i] = fork ()))
+ {
+ /* I am the child, do the work! */
+ GNUNET_log_setup ("benchmark-worker",
+ NULL == loglev ? "INFO" : loglev,
+ logfile);
+ result = TALER_TESTING_loop (&run,
+ NULL);
+ if (verbose)
+ print_stats ();
+ if (GNUNET_OK != result)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failure in child process test suite!\n");
+ if (GNUNET_OK == result)
+ exit (0);
+ else
+ exit (1);
+ }
+ if (-1 == cpids[i])
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "fork");
+ howmany_clients = i;
+ result = GNUNET_SYSERR;
+ break;
+ }
+ /* fork() success, continue starting more processes! */
+ }
+ /* collect all children */
+ for (unsigned int i = 0; i<howmany_clients; i++)
+ {
+ int wstatus;
+
+again:
+ if (cpids[i] !=
+ waitpid (cpids[i],
+ &wstatus,
+ 0))
+ {
+ if (EINTR == errno)
+ goto again;
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "waitpid");
+ return GNUNET_SYSERR;
+ }
+ if ( (! WIFEXITED (wstatus)) ||
+ (0 != WEXITSTATUS (wstatus)) )
+ {
+ GNUNET_break (0);
+ result = GNUNET_SYSERR;
+ }
+ }
+ return result;
+}
+
+
+/**
+ * Run the benchmark in parallel in many (client) processes
+ * and summarize result.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parallel_benchmark (void)
+{
+ enum GNUNET_GenericReturnValue result = GNUNET_OK;
+ struct GNUNET_OS_Process *wirewatch[GNUNET_NZL (start_wirewatch)];
+
+ memset (wirewatch,
+ 0,
+ sizeof (wirewatch));
+ /* start exchange wirewatch */
+ for (unsigned int w = 0; w<start_wirewatch; w++)
+ {
+ wirewatch[w] = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-exchange-wirewatch",
+ "taler-exchange-wirewatch",
+ "-c", cfg_filename,
+ "-a", exchange_bank_section,
+ "-S", SHARD_SIZE,
+ (NULL != loglev) ? "-L" : NULL,
+ loglev,
+ NULL);
+ if (NULL == wirewatch[w])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to launch wirewatch, aborting benchmark\n");
+ for (unsigned int x = 0; x<w; x++)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (wirewatch[x],
+ SIGTERM));
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait (wirewatch[x]));
+ GNUNET_OS_process_destroy (wirewatch[x]);
+ wirewatch[x] = NULL;
+ }
+ return GNUNET_SYSERR;
+ }
+ }
+ result = launch_clients ();
+ /* Ensure wirewatch runs to completion! */
+ if (0 != start_wirewatch)
+ {
+ /* replace ONE of the wirewatchers with one that is in test-mode */
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (wirewatch[0],
+ SIGTERM));
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait (wirewatch[0]));
+ GNUNET_OS_process_destroy (wirewatch[0]);
+ wirewatch[0] = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-exchange-wirewatch",
+ "taler-exchange-wirewatch",
+ "-c", cfg_filename,
+ "-a", exchange_bank_section,
+ "-S", SHARD_SIZE,
+ "-t",
+ (NULL != loglev) ? "-L" : NULL,
+ loglev,
+ NULL);
+ /* wait for it to finish! */
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait (wirewatch[0]));
+ GNUNET_OS_process_destroy (wirewatch[0]);
+ wirewatch[0] = NULL;
+ /* Then stop the rest, which should basically also be finished */
+ for (unsigned int w = 1; w<start_wirewatch; w++)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (wirewatch[w],
+ SIGTERM));
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait (wirewatch[w]));
+ GNUNET_OS_process_destroy (wirewatch[w]);
+ }
+
+ /* 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",
+ "taler-exchange-wirewatch",
+ "-c", cfg_filename,
+ "-a", exchange_bank_section,
+ "-S", SHARD_SIZE,
+ "-t",
+ (NULL != loglev) ? "-L" : NULL,
+ loglev,
+ NULL);
+ /* wait for it to finish! */
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait (wirewatch[0]));
+ GNUNET_OS_process_destroy (wirewatch[0]);
+ wirewatch[0] = NULL;
+ }
+
+ return result;
+}
+
+
+/**
+ * The main function of the serve tool
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, or `enum PaymentGeneratorError` on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ enum GNUNET_GenericReturnValue result;
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_mandatory (
+ GNUNET_GETOPT_option_cfgfile (&cfg_filename)),
+ GNUNET_GETOPT_option_flag ('f',
+ "fakebank",
+ "we are using fakebank",
+ &use_fakebank),
+ GNUNET_GETOPT_option_help ("taler-bank benchmark"),
+ GNUNET_GETOPT_option_string ('l',
+ "logfile",
+ "LF",
+ "will log to file LF",
+ &logfile),
+ GNUNET_GETOPT_option_loglevel (&loglev),
+ GNUNET_GETOPT_option_uint ('p',
+ "worker-parallelism",
+ "NPROCS",
+ "How many client processes we should run",
+ &howmany_clients),
+ GNUNET_GETOPT_option_uint ('r',
+ "reserves",
+ "NRESERVES",
+ "How many reserves per client we should create",
+ &howmany_reserves),
+ GNUNET_GETOPT_option_mandatory (
+ GNUNET_GETOPT_option_string (
+ 'u',
+ "exchange-account-section",
+ "SECTION",
+ "use exchange bank account configuration from the given SECTION",
+ &exchange_bank_section)),
+ GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION),
+ GNUNET_GETOPT_option_verbose (&verbose),
+ GNUNET_GETOPT_option_uint ('w',
+ "wirewatch",
+ "NPROC",
+ "run NPROC taler-exchange-wirewatch processes",
+ &start_wirewatch),
+ GNUNET_GETOPT_OPTION_END
+ };
+ struct GNUNET_TIME_Relative duration;
+
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ if (0 >=
+ (result = GNUNET_GETOPT_run ("taler-bank-benchmark",
+ options,
+ argc,
+ argv)))
+ {
+ GNUNET_free (cfg_filename);
+ if (GNUNET_NO == result)
+ return 0;
+ return EXIT_INVALIDARGUMENT;
+ }
+ if (NULL == exchange_bank_section)
+ exchange_bank_section = "exchange-account-1";
+ if (NULL == loglev)
+ loglev = "INFO";
+ GNUNET_log_setup ("taler-bank-benchmark",
+ loglev,
+ logfile);
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_load (cfg,
+ cfg_filename))
+ {
+ TALER_LOG_ERROR ("Could not parse configuration\n");
+ GNUNET_free (cfg_filename);
+ return EXIT_NOTCONFIGURED;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_currency (cfg,
+ &currency))
+ {
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (cfg_filename);
+ return EXIT_NOTCONFIGURED;
+ }
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_credentials (
+ cfg_filename,
+ exchange_bank_section,
+ use_fakebank
+ ? TALER_TESTING_BS_FAKEBANK
+ : TALER_TESTING_BS_IBAN,
+ &cred))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Required bank credentials not given in configuration\n");
+ GNUNET_free (cfg_filename);
+ return EXIT_NOTCONFIGURED;
+ }
+
+ {
+ struct GNUNET_TIME_Absolute start_time;
+
+ start_time = GNUNET_TIME_absolute_get ();
+ result = parallel_benchmark ();
+ duration = GNUNET_TIME_absolute_get_duration (start_time);
+ }
+
+ if (GNUNET_OK == result)
+ {
+ struct rusage usage;
+ unsigned long long tps;
+
+ GNUNET_assert (0 == getrusage (RUSAGE_CHILDREN,
+ &usage));
+ fprintf (stdout,
+ "Executed Reserve=%u * Parallel=%u, operations in %s\n",
+ howmany_reserves,
+ howmany_clients,
+ GNUNET_STRINGS_relative_time_to_string (duration,
+ GNUNET_YES));
+ if (! GNUNET_TIME_relative_is_zero (duration))
+ {
+ tps = ((unsigned long long) howmany_reserves) * howmany_clients * 1000LLU
+ / (duration.rel_value_us / 1000LL);
+ fprintf (stdout,
+ "RAW: %04u %04u %16llu (%llu TPS)\n",
+ howmany_reserves,
+ howmany_clients,
+ (unsigned long long) duration.rel_value_us,
+ tps);
+ }
+ fprintf (stdout,
+ "CPU time: sys %llu user %llu\n",
+ (unsigned long long) (usage.ru_stime.tv_sec * 1000 * 1000
+ + usage.ru_stime.tv_usec),
+ (unsigned long long) (usage.ru_utime.tv_sec * 1000 * 1000
+ + usage.ru_utime.tv_usec));
+ }
+ for (unsigned int i = 0; i<label_off; i++)
+ GNUNET_free (labels[i]);
+ GNUNET_array_grow (labels,
+ label_len,
+ 0);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (cfg_filename);
+ return (GNUNET_OK == result) ? 0 : result;
+}
diff --git a/src/benchmark/taler-exchange-benchmark.c b/src/benchmark/taler-exchange-benchmark.c
index 307b0f378..75424a358 100644
--- a/src/benchmark/taler-exchange-benchmark.c
+++ b/src/benchmark/taler-exchange-benchmark.c
@@ -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
@@ -28,24 +28,7 @@
#include <microhttpd.h>
#include <sys/resource.h>
#include "taler_util.h"
-#include "taler_signatures.h"
-#include "taler_exchange_service.h"
-#include "taler_json_lib.h"
-#include "taler_bank_service.h"
-#include "taler_fakebank_lib.h"
#include "taler_testing_lib.h"
-#include "taler_error_codes.h"
-
-/* Error codes. */
-enum BenchmarkError
-{
- MISSING_BANK_URL,
- FAILED_TO_LAUNCH_BANK,
- BAD_CLI_ARG,
- BAD_CONFIG_FILE,
- NO_CONFIG_FILE_GIVEN
-};
-
/**
* The whole benchmark is a repetition of a "unit". Each
@@ -54,56 +37,11 @@ enum BenchmarkError
*/
#define UNITY_SIZE 6
-#define FIRST_INSTRUCTION -1
-
-
-/**
- * What mode should the benchmark run in?
- */
-enum BenchmarkMode
-{
- /**
- * Run as client (with fakebank), also starts a remote exchange.
- */
- MODE_CLIENT = 1,
-
- /**
- * Run the the exchange.
- */
- MODE_EXCHANGE = 2,
-
- /**
- * Run both, for a local benchmark.
- */
- MODE_BOTH = 3,
-};
-
/**
- * Hold information regarding which bank has the exchange account.
+ * Credentials to use for the benchmark.
*/
-static struct TALER_BANK_AuthenticationData exchange_bank_account;
-
-/**
- * Configuration of our exchange.
- */
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-/**
- * Hold information about a user at the bank.
- */
-static char *user_payto_uri;
-
-/**
- * Time snapshot taken right before executing the CMDs.
- */
-static struct GNUNET_TIME_Absolute start_time;
-
-/**
- * Benchmark duration time taken right after the CMD interpreter
- * returns.
- */
-static struct GNUNET_TIME_Relative duration;
+static struct TALER_TESTING_Credentials cred;
/**
* Array of all the commands the benchmark is running.
@@ -116,16 +54,6 @@ static struct TALER_TESTING_Command *all_commands;
static char *cfg_filename;
/**
- * Exit code.
- */
-static int result;
-
-/**
- * Use the fakebank instead of the Python bank.
- */
-static int use_fakebank;
-
-/**
* How many coins we want to create per client and reserve.
*/
static unsigned int howmany_coins = 1;
@@ -146,11 +74,6 @@ static unsigned int refresh_rate = 10;
static unsigned int howmany_clients = 1;
/**
- * Bank configuration to use.
- */
-static struct TALER_TESTING_BankConfiguration bc;
-
-/**
* Log level used during the run.
*/
static char *loglev;
@@ -161,16 +84,6 @@ static char *loglev;
static char *logfile;
/**
- * Benchmarking mode (run as client, exchange, both) as string.
- */
-static char *mode_str;
-
-/**
- * Benchmarking mode (run as client, exchange, both).
- */
-static enum BenchmarkMode mode;
-
-/**
* Configuration.
*/
static struct GNUNET_CONFIGURATION_Handle *cfg;
@@ -181,19 +94,20 @@ static struct GNUNET_CONFIGURATION_Handle *cfg;
static int reserves_first;
/**
- * Currency used.
+ * Are we running against 'fakebank'?
*/
-static char *currency;
+static int use_fakebank;
/**
- * Remote host that runs the exchange.
+ * Section with the configuration data for the exchange
+ * bank account.
*/
-static char *remote_host;
+static char *exchange_bank_section;
/**
- * Remote benchmarking directory.
+ * Currency used.
*/
-static char *remote_dir;
+static char *currency;
/**
* Array of command labels.
@@ -211,12 +125,6 @@ static unsigned int label_len;
static unsigned int label_off;
/**
- * Don't kill exchange/fakebank/wirewatch until
- * requested by the user explicitly.
- */
-static int linger;
-
-/**
* Performance counters.
*/
static struct TALER_TESTING_Timer timings[] = {
@@ -252,40 +160,18 @@ static struct TALER_TESTING_Command
cmd_transfer_to_exchange (const char *label,
const char *amount)
{
- return TALER_TESTING_cmd_admin_add_incoming_retry
- (TALER_TESTING_cmd_admin_add_incoming (label,
- amount,
- &exchange_bank_account,
- user_payto_uri));
-}
-
-
-/**
- * Decide which exchange account is going to be
- * used to address a wire transfer to. Used at
- * withdrawal time.
- *
- * @param cls closure
- * @param section section name.
- */
-static void
-pick_exchange_account_cb (void *cls,
- const char *section)
-{
- if (0 == strncasecmp ("exchange-account-",
- section,
- strlen ("exchange-account-")))
- {
- const char **s = cls;
-
- *s = section;
- }
+ return TALER_TESTING_cmd_admin_add_incoming_retry (
+ TALER_TESTING_cmd_admin_add_incoming (label,
+ amount,
+ &cred.ba_admin,
+ cred.user42_payto));
}
/**
* Throw a weighted coin with @a probability.
*
+ * @param probability weight of the coin flip
* @return #GNUNET_OK with @a probability,
* #GNUNET_NO with 1 - @a probability
*/
@@ -297,7 +183,7 @@ eval_probability (float probability)
random = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
UINT64_MAX);
- random_01 = (double) random / UINT64_MAX;
+ random_01 = (double) random / (double) UINT64_MAX;
return (random_01 <= probability) ? GNUNET_OK : GNUNET_NO;
}
@@ -320,16 +206,26 @@ run (void *cls,
char *amount_1;
(void) cls;
- all_commands = GNUNET_new_array (howmany_reserves * (1 /* Withdraw block */
- + howmany_coins) /* All units */
- + 1 /* stat CMD */
- + 1 /* End CMD */,
- struct TALER_TESTING_Command);
+ all_commands = GNUNET_malloc_large (
+ (1 /* exchange CMD */
+ + howmany_reserves
+ * (1 /* Withdraw block */
+ + howmany_coins) /* All units */
+ + 1 /* stat CMD */
+ + 1 /* End CMD */) * sizeof (struct TALER_TESTING_Command));
+ GNUNET_assert (NULL != all_commands);
+ all_commands[0]
+ = TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true);
GNUNET_asprintf (&amount_5, "%s:5", currency);
GNUNET_asprintf (&amount_4, "%s:4", currency);
GNUNET_asprintf (&amount_1, "%s:1", currency);
- GNUNET_assert (GNUNET_OK == TALER_amount_get_zero (currency,
- &total_reserve_amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (currency,
+ &total_reserve_amount));
total_reserve_amount.value = 5 * howmany_coins;
GNUNET_asprintf (&withdraw_fee_str,
"%s:0.1",
@@ -338,7 +234,7 @@ run (void *cls,
TALER_string_to_amount (withdraw_fee_str,
&withdraw_fee));
for (unsigned int i = 0; i < howmany_coins; i++)
- GNUNET_assert (GNUNET_OK ==
+ GNUNET_assert (0 <=
TALER_amount_add (&total_reserve_amount,
&total_reserve_amount,
&withdraw_fee));
@@ -360,9 +256,9 @@ run (void *cls,
GNUNET_asprintf (&batch_label,
"batch-start-%u",
j);
- all_commands[reserves_first
- ? j
- : j * (howmany_coins + 1)]
+ all_commands[1 + (reserves_first
+ ? j
+ : j * (howmany_coins + 1))]
= TALER_TESTING_cmd_batch (add_label (batch_label),
make_reserve);
}
@@ -381,23 +277,25 @@ run (void *cls,
wl = add_label (withdraw_label);
GNUNET_asprintf (&order_enc,
"{\"nonce\": %llu}",
- i + (howmany_coins * j));
+ ((unsigned long long) i)
+ + (howmany_coins * (unsigned long long) j));
unit[0] =
- TALER_TESTING_cmd_withdraw_with_retry
- (TALER_TESTING_cmd_withdraw_amount (wl,
- create_reserve_label,
- amount_5,
- MHD_HTTP_OK));
+ TALER_TESTING_cmd_withdraw_with_retry (
+ TALER_TESTING_cmd_withdraw_amount (wl,
+ create_reserve_label,
+ amount_5,
+ 0, /* age restriction off */
+ MHD_HTTP_OK));
unit[1] =
- TALER_TESTING_cmd_deposit_with_retry
- (TALER_TESTING_cmd_deposit ("deposit",
- wl,
- 0, /* Index of the one withdrawn coin in the traits. */
- user_payto_uri,
- add_label (order_enc),
- GNUNET_TIME_UNIT_ZERO,
- amount_1,
- MHD_HTTP_OK));
+ TALER_TESTING_cmd_deposit_with_retry (
+ TALER_TESTING_cmd_deposit ("deposit",
+ wl,
+ 0, /* Index of the one withdrawn coin in the traits. */
+ cred.user43_payto,
+ add_label (order_enc),
+ GNUNET_TIME_UNIT_ZERO,
+ amount_1,
+ MHD_HTTP_OK));
if (eval_probability (refresh_rate / 100.0))
{
char *melt_label;
@@ -416,21 +314,21 @@ run (void *cls,
j);
rl = add_label (reveal_label);
unit[2] =
- TALER_TESTING_cmd_melt_with_retry
- (TALER_TESTING_cmd_melt (ml,
- wl,
- MHD_HTTP_OK,
- NULL));
+ TALER_TESTING_cmd_melt_with_retry (
+ TALER_TESTING_cmd_melt (ml,
+ wl,
+ MHD_HTTP_OK,
+ NULL));
unit[3] =
- TALER_TESTING_cmd_refresh_reveal_with_retry
- (TALER_TESTING_cmd_refresh_reveal (rl,
- ml,
- MHD_HTTP_OK));
+ TALER_TESTING_cmd_refresh_reveal_with_retry (
+ TALER_TESTING_cmd_refresh_reveal (rl,
+ ml,
+ MHD_HTTP_OK));
unit[4] =
- TALER_TESTING_cmd_refresh_link_with_retry
- (TALER_TESTING_cmd_refresh_link ("link",
- rl,
- MHD_HTTP_OK));
+ TALER_TESTING_cmd_refresh_link_with_retry (
+ TALER_TESTING_cmd_refresh_link ("link",
+ rl,
+ MHD_HTTP_OK));
unit[5] = TALER_TESTING_cmd_end ();
}
else
@@ -440,16 +338,16 @@ run (void *cls,
"unit-%u-%u",
i,
j);
- all_commands[reserves_first
- ? howmany_reserves + j * howmany_coins + i
- : j * (howmany_coins + 1) + (1 + i)]
+ all_commands[1 + (reserves_first
+ ? howmany_reserves + j * howmany_coins + i
+ : j * (howmany_coins + 1) + (1 + i))]
= TALER_TESTING_cmd_batch (add_label (unit_label),
unit);
}
}
- all_commands[howmany_reserves * (1 + howmany_coins)]
+ all_commands[1 + howmany_reserves * (1 + howmany_coins)]
= TALER_TESTING_cmd_stat (timings);
- all_commands[howmany_reserves * (1 + howmany_coins) + 1]
+ all_commands[1 + howmany_reserves * (1 + howmany_coins) + 1]
= TALER_TESTING_cmd_end ();
TALER_TESTING_run2 (is,
all_commands,
@@ -458,7 +356,6 @@ run (void *cls,
GNUNET_free (amount_4);
GNUNET_free (amount_5);
GNUNET_free (withdraw_fee_str);
- result = 1;
}
@@ -475,10 +372,10 @@ print_stats (void)
total = GNUNET_strdup (
GNUNET_STRINGS_relative_time_to_string (timings[i].total_duration,
- GNUNET_YES));
+ true));
latency = GNUNET_strdup (
GNUNET_STRINGS_relative_time_to_string (timings[i].success_latency,
- GNUNET_YES));
+ true));
fprintf (stderr,
"%s-%d took %s in total with %s for latency for %u executions (%u repeats)\n",
timings[i].prefix,
@@ -494,44 +391,6 @@ print_stats (void)
/**
- * Stop the fakebank.
- *
- * @param cls fakebank handle
- */
-static void
-stop_fakebank (void *cls)
-{
- struct TALER_FAKEBANK_Handle *fakebank = cls;
-
- TALER_FAKEBANK_stop (fakebank);
-}
-
-
-/**
- * Start the fakebank.
- *
- * @param cls NULL
- */
-static void
-launch_fakebank (void *cls)
-{
- struct TALER_FAKEBANK_Handle *fakebank;
-
- (void) cls;
- fakebank
- = TALER_TESTING_run_fakebank (exchange_bank_account.wire_gateway_url,
- currency);
- if (NULL == fakebank)
- {
- GNUNET_break (0);
- return;
- }
- GNUNET_SCHEDULER_add_shutdown (&stop_fakebank,
- fakebank);
-}
-
-
-/**
* Run the benchmark in parallel in many (client) processes
* and summarize result.
*
@@ -540,369 +399,58 @@ launch_fakebank (void *cls)
* @param config_file configuration file to use
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
parallel_benchmark (TALER_TESTING_Main main_cb,
void *main_cb_cls,
const char *config_file)
{
- int result = GNUNET_OK;
+ enum GNUNET_GenericReturnValue result = GNUNET_OK;
pid_t cpids[howmany_clients];
- pid_t fakebank = -1;
- int wstatus;
- struct GNUNET_OS_Process *bankd = NULL;
- struct GNUNET_OS_Process *auditord = NULL;
- struct GNUNET_OS_Process *exchanged = NULL;
- struct GNUNET_OS_Process *wirewatch = NULL;
- struct GNUNET_OS_Process *exchange_slave = NULL;
- struct GNUNET_DISK_PipeHandle *exchange_slave_pipe;
- if ( (MODE_CLIENT == mode) || (MODE_BOTH == mode) )
+ if (1 == howmany_clients)
{
- if (use_fakebank)
+ result = TALER_TESTING_loop (main_cb,
+ main_cb_cls);
+ print_stats ();
+ }
+ else
+ {
+ for (unsigned int i = 0; i<howmany_clients; i++)
{
- /* start fakebank */
- fakebank = fork ();
- if (0 == fakebank)
+ if (0 == (cpids[i] = fork ()))
{
- GNUNET_log_setup ("benchmark-fakebank",
- NULL == loglev ? "INFO" : loglev,
+ /* I am the child, do the work! */
+ GNUNET_log_setup ("benchmark-worker",
+ loglev,
logfile);
- GNUNET_SCHEDULER_run (&launch_fakebank,
- NULL);
- exit (0);
+ result = TALER_TESTING_loop (main_cb,
+ main_cb_cls);
+ print_stats ();
+ if (GNUNET_OK != result)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failure in child process %u test suite!\n",
+ i);
+ if (GNUNET_OK == result)
+ exit (0);
+ else
+ exit (1);
}
- if (-1 == fakebank)
+ if (-1 == cpids[i])
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"fork");
- return GNUNET_SYSERR;
- }
- }
- else
- {
- /* start bank */
- if (GNUNET_OK !=
- TALER_TESTING_prepare_bank (cfg_filename,
- GNUNET_NO,
- "exchange-account-2",
- &bc))
- {
- return 1;
- }
- bankd = TALER_TESTING_run_bank (cfg_filename,
- "http://localhost:8082/");
- if (NULL == bankd)
- return 77;
- }
- }
-
- if ( (MODE_EXCHANGE == mode) || (MODE_BOTH == mode) )
- {
- /* start exchange */
- exchanged = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-exchange-httpd",
- "taler-exchange-httpd",
- "-c", config_file,
- "-C",
- NULL);
- if ( (NULL == exchanged) && (MODE_BOTH == mode) )
- {
- if (-1 != fakebank)
- {
- kill (fakebank,
- SIGTERM);
- waitpid (fakebank,
- &wstatus,
- 0);
- }
- if (NULL != bankd)
- {
- GNUNET_OS_process_kill (bankd,
- SIGTERM);
- GNUNET_OS_process_destroy (bankd);
- }
- return 77;
- }
- /* start auditor */
- auditord = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-auditor-httpd",
- "taler-auditor-httpd",
- "-c", config_file,
- NULL);
- if (NULL == auditord)
- {
- GNUNET_OS_process_kill (exchanged,
- SIGTERM);
- if (MODE_BOTH == mode)
- {
- if (-1 != fakebank)
- {
- kill (fakebank,
- SIGTERM);
- waitpid (fakebank,
- &wstatus,
- 0);
- }
- if (NULL != bankd)
- {
- GNUNET_OS_process_kill (bankd,
- SIGTERM);
- GNUNET_OS_process_destroy (bankd);
- }
- }
- GNUNET_OS_process_destroy (exchanged);
- return 77;
- }
- /* start exchange wirewatch */
- wirewatch = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-exchange-wirewatch",
- "taler-exchange-wirewatch",
- "-c", config_file,
- NULL);
- if (NULL == wirewatch)
- {
- GNUNET_OS_process_kill (auditord,
- SIGTERM);
- GNUNET_OS_process_kill (exchanged,
- SIGTERM);
- if (MODE_BOTH == mode)
- {
- if (-1 != fakebank)
- {
- kill (fakebank,
- SIGTERM);
- waitpid (fakebank,
- &wstatus,
- 0);
- }
- if (NULL != bankd)
- {
- GNUNET_OS_process_kill (bankd,
- SIGTERM);
- GNUNET_OS_process_destroy (bankd);
- }
- }
- GNUNET_OS_process_destroy (exchanged);
- return 77;
- }
- }
-
- if (MODE_CLIENT == mode)
- {
- char *remote_cmd;
-
- GNUNET_asprintf (&remote_cmd,
- ("cd '%s'; "
- "taler-exchange-benchmark --mode=exchange -c '%s'"),
- remote_dir,
- config_file);
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "remote command: %s\n",
- remote_cmd);
-
- GNUNET_assert (NULL != (exchange_slave_pipe =
- GNUNET_DISK_pipe (GNUNET_YES,
- GNUNET_YES,
- 0, 0)));
-
- exchange_slave = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_OUT_AND_ERR,
- exchange_slave_pipe, NULL, NULL,
- "ssh",
- "ssh",
- /* Don't ask for pw/passphrase, rather fail */
- "-oBatchMode=yes",
- remote_host,
- remote_cmd,
- NULL);
- GNUNET_free (remote_cmd);
- }
-
- /* We always wait for the exchange, no matter if it's running locally or
- remotely */
- if (0 != TALER_TESTING_wait_exchange_ready (ec.exchange_url))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to detect running exchange at `%s'\n",
- ec.exchange_url);
- GNUNET_OS_process_kill (exchanged,
- SIGTERM);
- if ( (MODE_BOTH == mode) || (MODE_CLIENT == mode))
- {
- if (-1 != fakebank)
- {
- kill (fakebank,
- SIGTERM);
- waitpid (fakebank,
- &wstatus,
- 0);
- }
- if (NULL != bankd)
- {
- GNUNET_OS_process_kill (bankd,
- SIGTERM);
- GNUNET_OS_process_destroy (bankd);
+ howmany_clients = i;
+ result = GNUNET_SYSERR;
+ break;
}
+ /* fork() success, continue starting more processes! */
}
- GNUNET_OS_process_wait (exchanged);
- GNUNET_OS_process_destroy (exchanged);
- if (NULL != wirewatch)
- {
- GNUNET_OS_process_kill (wirewatch,
- SIGTERM);
- GNUNET_OS_process_wait (wirewatch);
- GNUNET_OS_process_destroy (wirewatch);
- }
- if (NULL != auditord)
- {
- GNUNET_OS_process_kill (auditord,
- SIGTERM);
- GNUNET_OS_process_wait (auditord);
- GNUNET_OS_process_destroy (auditord);
- }
- return 77;
- }
- if ( (MODE_CLIENT == mode) || (MODE_BOTH == mode) )
- {
- if (-1 != fakebank)
- sleep (1); /* make sure fakebank process is ready before continuing */
-
- start_time = GNUNET_TIME_absolute_get ();
- result = GNUNET_OK;
-
- if (1 == howmany_clients)
- {
- result = TALER_TESTING_setup (main_cb,
- main_cb_cls,
- cfg,
- exchanged,
- GNUNET_YES);
- print_stats ();
- }
- else
+ /* collect all children */
+ for (unsigned int i = 0; i<howmany_clients; i++)
{
- for (unsigned int i = 0; i<howmany_clients; i++)
- {
- if (0 == (cpids[i] = fork ()))
- {
- /* I am the child, do the work! */
- GNUNET_log_setup ("benchmark-worker",
- NULL == loglev ? "INFO" : loglev,
- logfile);
-
- result = TALER_TESTING_setup (main_cb,
- main_cb_cls,
- cfg,
- exchanged,
- GNUNET_YES);
- print_stats ();
- if (GNUNET_OK != result)
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failure in child process test suite!\n");
- if (GNUNET_OK == result)
- exit (0);
- else
- exit (1);
- }
- if (-1 == cpids[i])
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "fork");
- howmany_clients = i;
- result = GNUNET_SYSERR;
- break;
- }
- /* fork() success, continue starting more processes! */
- }
- /* collect all children */
- for (unsigned int i = 0; i<howmany_clients; i++)
- {
- waitpid (cpids[i],
- &wstatus,
- 0);
- if ( (! WIFEXITED (wstatus)) ||
- (0 != WEXITSTATUS (wstatus)) )
- {
- GNUNET_break (0);
- result = GNUNET_SYSERR;
- }
- }
- }
- }
-
- /* Wait for our master to die or to tell us to die */
- if (MODE_EXCHANGE == mode)
- (void) getchar ();
-
- if ( (GNUNET_YES == linger) &&
- ( ((mode == MODE_BOTH) ||
- (mode == MODE_CLIENT) ) ) )
- {
- printf ("press ENTER to stop\n");
- (void) getchar ();
- }
-
- if (MODE_CLIENT == mode)
- {
- char c = 'q';
-
- GNUNET_assert (NULL != exchange_slave);
+ int wstatus;
- /* Write a character to the pipe to end the exchange slave.
- * We can't send a signal here, as it would just kill SSH and
- * not necessarily the process on the other machine. */
- GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle
- (exchange_slave_pipe, GNUNET_DISK_PIPE_END_WRITE),
- &c, sizeof (c));
-
- GNUNET_break (GNUNET_OK ==
- GNUNET_OS_process_wait (exchange_slave));
- GNUNET_OS_process_destroy (exchange_slave);
- }
-
- if ( (MODE_EXCHANGE == mode) || (MODE_BOTH == mode) )
- {
- GNUNET_assert (NULL != wirewatch);
- GNUNET_assert (NULL != exchanged);
- GNUNET_assert (NULL != auditord);
- /* stop wirewatch */
- GNUNET_break (0 ==
- GNUNET_OS_process_kill (wirewatch,
- SIGTERM));
- GNUNET_break (GNUNET_OK ==
- GNUNET_OS_process_wait (wirewatch));
- GNUNET_OS_process_destroy (wirewatch);
- /* stop auditor */
- GNUNET_break (0 ==
- GNUNET_OS_process_kill (auditord,
- SIGTERM));
- GNUNET_break (GNUNET_OK ==
- GNUNET_OS_process_wait (auditord));
- GNUNET_OS_process_destroy (auditord);
- /* stop exchange */
- GNUNET_break (0 ==
- GNUNET_OS_process_kill (exchanged,
- SIGTERM));
- GNUNET_break (GNUNET_OK ==
- GNUNET_OS_process_wait (exchanged));
- GNUNET_OS_process_destroy (exchanged);
- }
-
- if ( (MODE_CLIENT == mode) || (MODE_BOTH == mode) )
- {
- /* stop fakebank */
- if (-1 != fakebank)
- {
- kill (fakebank,
- SIGTERM);
- waitpid (fakebank,
+ waitpid (cpids[i],
&wstatus,
0);
if ( (! WIFEXITED (wstatus)) ||
@@ -912,12 +460,6 @@ parallel_benchmark (TALER_TESTING_Main main_cb,
result = GNUNET_SYSERR;
}
}
- if (NULL != bankd)
- {
- GNUNET_OS_process_kill (bankd,
- SIGTERM);
- GNUNET_OS_process_destroy (bankd);
- }
}
return result;
}
@@ -935,55 +477,65 @@ main (int argc,
char *const *argv)
{
struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_mandatory
- (GNUNET_GETOPT_option_cfgfile (&cfg_filename)),
- GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION),
- GNUNET_GETOPT_option_help ("Exchange benchmark"),
- GNUNET_GETOPT_option_loglevel (&loglev),
- GNUNET_GETOPT_option_uint ('n',
- "coins-number",
- "CN",
- "How many coins we should instantiate per reserve",
- &howmany_coins),
- GNUNET_GETOPT_option_uint ('p',
- "parallelism",
- "NPROCS",
- "How many client processes we should run",
- &howmany_clients),
- GNUNET_GETOPT_option_uint ('r',
- "reserves",
- "NRESERVES",
- "How many reserves per client we should create",
- &howmany_reserves),
- GNUNET_GETOPT_option_uint ('R',
- "refresh-rate",
- "RATE",
- "Probability of refresh per coin (0-100)",
- &refresh_rate),
- GNUNET_GETOPT_option_string ('m',
- "mode",
- "MODE",
- "run as exchange, clients or both",
- &mode_str),
- GNUNET_GETOPT_option_string ('l',
- "logfile",
- "LF",
- "will log to file LF",
- &logfile),
- GNUNET_GETOPT_option_flag ('f',
- "fakebank",
- "start a fakebank instead of the Python bank",
- &use_fakebank),
- GNUNET_GETOPT_option_flag ('F',
- "reserves-first",
- "should all reserves be created first, before starting normal operations",
- &reserves_first),
- GNUNET_GETOPT_option_flag ('K',
- "linger",
- "linger around until key press",
- &linger),
+ GNUNET_GETOPT_option_mandatory (
+ GNUNET_GETOPT_option_cfgfile (
+ &cfg_filename)),
+ GNUNET_GETOPT_option_version (
+ PACKAGE_VERSION " " VCS_VERSION),
+ GNUNET_GETOPT_option_flag (
+ 'f',
+ "fakebank",
+ "use fakebank for the banking system",
+ &use_fakebank),
+ GNUNET_GETOPT_option_flag (
+ 'F',
+ "reserves-first",
+ "should all reserves be created first, before starting normal operations",
+ &reserves_first),
+ GNUNET_GETOPT_option_help (
+ "Exchange benchmark"),
+ GNUNET_GETOPT_option_string (
+ 'l',
+ "logfile",
+ "LF",
+ "will log to file LF",
+ &logfile),
+ GNUNET_GETOPT_option_loglevel (
+ &loglev),
+ GNUNET_GETOPT_option_uint (
+ 'n',
+ "coins-number",
+ "CN",
+ "How many coins we should instantiate per reserve",
+ &howmany_coins),
+ GNUNET_GETOPT_option_uint (
+ 'p',
+ "parallelism",
+ "NPROCS",
+ "How many client processes we should run",
+ &howmany_clients),
+ GNUNET_GETOPT_option_uint (
+ 'r',
+ "reserves",
+ "NRESERVES",
+ "How many reserves per client we should create",
+ &howmany_reserves),
+ GNUNET_GETOPT_option_uint (
+ 'R',
+ "refresh-rate",
+ "RATE",
+ "Probability of refresh per coin (0-100)",
+ &refresh_rate),
+ GNUNET_GETOPT_option_string (
+ 'u',
+ "exchange-account-section",
+ "SECTION",
+ "use exchange bank account configuration from the given SECTION",
+ &exchange_bank_section),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue result;
+ struct GNUNET_TIME_Relative duration;
unsetenv ("XDG_DATA_HOME");
unsetenv ("XDG_CONFIG_HOME");
@@ -993,29 +545,26 @@ main (int argc,
argc,
argv)))
{
- GNUNET_free_non_null (cfg_filename);
- return BAD_CLI_ARG;
+ GNUNET_free (cfg_filename);
+ if (GNUNET_NO == result)
+ return 0;
+ return EXIT_INVALIDARGUMENT;
}
+ if (NULL == exchange_bank_section)
+ exchange_bank_section = "exchange-account-1";
+ if (NULL == loglev)
+ loglev = "INFO";
GNUNET_log_setup ("taler-exchange-benchmark",
- NULL == loglev ? "INFO" : loglev,
+ loglev,
logfile);
- if (NULL == mode_str)
- mode = MODE_BOTH;
- else if (0 == strcmp (mode_str, "exchange"))
- mode = MODE_EXCHANGE;
- else if (0 == strcmp (mode_str, "client"))
- mode = MODE_CLIENT;
- else if (0 == strcmp (mode_str, "both"))
- mode = MODE_BOTH;
- else
+ if (NULL == cfg_filename)
+ cfg_filename = GNUNET_CONFIGURATION_default_filename ();
+ if (NULL == cfg_filename)
{
- TALER_LOG_ERROR ("Unknown mode given: '%s'\n", mode_str);
- GNUNET_free_non_null (cfg_filename);
- return BAD_CONFIG_FILE;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Can't find default configuration file.\n");
+ return EXIT_NOTCONFIGURED;
}
- if (NULL == cfg_filename)
- cfg_filename = GNUNET_strdup (
- GNUNET_OS_project_data_get ()->user_config_file);
cfg = GNUNET_CONFIGURATION_create ();
if (GNUNET_OK !=
GNUNET_CONFIGURATION_load (cfg,
@@ -1023,7 +572,7 @@ main (int argc,
{
TALER_LOG_ERROR ("Could not parse configuration\n");
GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
+ return EXIT_NOTCONFIGURED;
}
if (GNUNET_OK !=
TALER_config_get_currency (cfg,
@@ -1031,182 +580,91 @@ main (int argc,
{
GNUNET_CONFIGURATION_destroy (cfg);
GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
+ return EXIT_NOTCONFIGURED;
}
if (howmany_clients > 10240)
{
TALER_LOG_ERROR ("-p option value given is too large\n");
- return BAD_CLI_ARG;
+ return EXIT_INVALIDARGUMENT;
}
if (0 == howmany_clients)
{
TALER_LOG_ERROR ("-p option value must not be zero\n");
GNUNET_free (cfg_filename);
- return BAD_CLI_ARG;
+ return EXIT_INVALIDARGUMENT;
}
+
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "benchmark",
- "USER_PAYTO_URI",
- &user_payto_uri))
+ TALER_TESTING_get_credentials (
+ cfg_filename,
+ exchange_bank_section,
+ use_fakebank
+ ? TALER_TESTING_BS_FAKEBANK
+ : TALER_TESTING_BS_IBAN,
+ &cred))
{
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "benchmark",
- "USER_PAYTO_URI");
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Required bank credentials not given in configuration\n");
GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
+ return EXIT_NOTCONFIGURED;
}
{
- const char *bank_details_section;
+ struct GNUNET_TIME_Absolute start_time;
- GNUNET_CONFIGURATION_iterate_sections (cfg,
- &pick_exchange_account_cb,
- &bank_details_section);
- if (NULL == bank_details_section)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Missing specification of bank account in configuration\n");
- GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
- }
- if (GNUNET_OK !=
- TALER_BANK_auth_parse_cfg (cfg,
- bank_details_section,
- &exchange_bank_account))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Configuration fails to provide exchange bank details in section `%s'\n",
- bank_details_section);
- GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
- }
- }
- if ( (MODE_EXCHANGE == mode) || (MODE_BOTH == mode) )
- {
- struct GNUNET_OS_Process *compute_wire_response;
-
- compute_wire_response
- = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-exchange-wire",
- "taler-exchange-wire",
- "-c", cfg_filename,
- NULL);
- if (NULL == compute_wire_response)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to run `taler-exchange-wire`, is your PATH correct?\n");
- GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
- }
- GNUNET_OS_process_wait (compute_wire_response);
- GNUNET_OS_process_destroy (compute_wire_response);
- /* If we use the fakebank, we MUST reset the database as the fakebank
- will have forgotten everything... */
- GNUNET_assert (GNUNET_OK ==
- TALER_TESTING_prepare_exchange (cfg_filename,
- (GNUNET_YES == use_fakebank)
- ? GNUNET_YES
- : GNUNET_NO,
- &ec));
- }
- else
- {
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "exchange",
- "BASE_URL",
- &ec.exchange_url))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "base_url");
- GNUNET_CONFIGURATION_destroy (cfg);
- GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
- }
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "benchmark-remote-exchange",
- "host",
- &remote_host))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "benchmark-remote-exchange",
- "host");
- GNUNET_CONFIGURATION_destroy (cfg);
- GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
- }
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "benchmark-remote-exchange",
- "dir",
- &remote_dir))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "benchmark-remote-exchange",
- "dir");
- GNUNET_CONFIGURATION_destroy (cfg);
- GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
- }
+ start_time = GNUNET_TIME_absolute_get ();
+ result = parallel_benchmark (&run,
+ NULL,
+ cfg_filename);
+ duration = GNUNET_TIME_absolute_get_duration (start_time);
}
- result = parallel_benchmark (&run,
- NULL,
- cfg_filename);
- GNUNET_CONFIGURATION_destroy (cfg);
- GNUNET_free (cfg_filename);
-
- /* If we're the exchange worker, we're done now. No need to print results */
- if (MODE_EXCHANGE == mode)
- {
- return (GNUNET_OK == result) ? 0 : result;
- }
- duration = GNUNET_TIME_absolute_get_duration (start_time);
if (GNUNET_OK == result)
{
struct rusage usage;
- GNUNET_assert (0 == getrusage (RUSAGE_CHILDREN, &usage));
+
+ GNUNET_assert (0 == getrusage (RUSAGE_CHILDREN,
+ &usage));
fprintf (stdout,
- "Executed (Withdraw=%u, Deposit=%u, Refresh~=%5.2f) * Reserve=%u * Parallel=%u, operations in %s\n",
+ "Executed (Withdraw=%u, Deposit=%u, Refresh~=%5.2f)"
+ " * Reserve=%u * Parallel=%u, operations in %s\n",
howmany_coins,
howmany_coins,
(float) howmany_coins * (refresh_rate / 100.0),
howmany_reserves,
howmany_clients,
- GNUNET_STRINGS_relative_time_to_string
- (duration,
- GNUNET_NO));
+ GNUNET_STRINGS_relative_time_to_string (
+ duration,
+ false));
fprintf (stdout,
"(approximately %s/coin)\n",
- GNUNET_STRINGS_relative_time_to_string
- (GNUNET_TIME_relative_divide (duration,
- (unsigned long long) howmany_coins
- * howmany_reserves
- * howmany_clients),
- GNUNET_YES));
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_relative_divide (
+ duration,
+ (unsigned long long) howmany_coins
+ * howmany_reserves
+ * howmany_clients),
+ true));
fprintf (stdout,
"RAW: %04u %04u %04u %16llu\n",
howmany_coins,
howmany_reserves,
howmany_clients,
(unsigned long long) duration.rel_value_us);
- fprintf (stdout, "cpu time: sys %llu user %llu\n", \
+ fprintf (stdout,
+ "cpu time: sys %llu user %llu\n",
(unsigned long long) (usage.ru_stime.tv_sec * 1000 * 1000
+ usage.ru_stime.tv_usec),
(unsigned long long) (usage.ru_utime.tv_sec * 1000 * 1000
+ usage.ru_utime.tv_usec));
}
+
for (unsigned int i = 0; i<label_off; i++)
GNUNET_free (labels[i]);
GNUNET_array_grow (labels,
label_len,
0);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (cfg_filename);
return (GNUNET_OK == result) ? 0 : result;
}
diff --git a/src/curl/Makefile.am b/src/curl/Makefile.am
index 7ddfe4d66..c8f8761aa 100644
--- a/src/curl/Makefile.am
+++ b/src/curl/Makefile.am
@@ -17,4 +17,8 @@ libtalercurl_la_SOURCES = \
libtalercurl_la_LIBADD = \
-lgnunetcurl \
-lgnunetutil \
+ -lcurl \
+ -ljansson \
+ -lz \
+ -lm \
$(XLIB)
diff --git a/src/curl/curl.c b/src/curl/curl.c
index dd1194906..483c9b671 100644
--- a/src/curl/curl.c
+++ b/src/curl/curl.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2019-2020 Taler Systems SA
+ Copyright (C) 2019-2024 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
@@ -24,20 +24,65 @@
#include "platform.h"
#include "taler_curl_lib.h"
-#if COMPRESS_BODIES
+
+#if TALER_CURL_COMPRESS_BODIES
#include <zlib.h>
#endif
-/**
- * Add the @a body as POST data to the easy handle in @a ctx.
- *
- * @param[in,out] ctx a request context (updated)
- * @param eh easy handle to use
- * @param body JSON body to add to @e ctx
- * @return #GNUNET_OK on success #GNUNET_SYSERR on failure
- */
-int
+void
+TALER_curl_set_secure_redirect_policy (CURL *eh,
+ const char *url)
+{
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_FOLLOWLOCATION,
+ 1L));
+ GNUNET_assert ( (0 == strncasecmp (url,
+ "https://",
+ strlen ("https://"))) ||
+ (0 == strncasecmp (url,
+ "http://",
+ strlen ("http://"))) );
+#ifdef CURLOPT_REDIR_PROTOCOLS_STR
+ if (0 == strncasecmp (url,
+ "https://",
+ strlen ("https://")))
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_REDIR_PROTOCOLS_STR,
+ "https"));
+ else
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_REDIR_PROTOCOLS_STR,
+ "http,https"));
+#else
+#ifdef CURLOPT_REDIR_PROTOCOLS
+ if (0 == strncasecmp (url,
+ "https://",
+ strlen ("https://")))
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_REDIR_PROTOCOLS,
+ CURLPROTO_HTTPS));
+ else
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_REDIR_PROTOCOLS,
+ CURLPROTO_HTTP | CURLPROTO_HTTPS));
+#endif
+#endif
+ /* limit MAXREDIRS to 5 as a simple security measure against
+ a potential infinite loop caused by a malicious target */
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_MAXREDIRS,
+ 5L));
+}
+
+
+enum GNUNET_GenericReturnValue
TALER_curl_easy_post (struct TALER_CURL_PostContext *ctx,
CURL *eh,
const json_t *body)
@@ -53,7 +98,8 @@ TALER_curl_easy_post (struct TALER_CURL_PostContext *ctx,
return GNUNET_SYSERR;
}
slen = strlen (str);
-#if COMPRESS_BODIES
+ if (TALER_CURL_COMPRESS_BODIES &&
+ (! ctx->disable_compression) )
{
Bytef *cbuf;
uLongf cbuf_size;
@@ -75,19 +121,21 @@ TALER_curl_easy_post (struct TALER_CURL_PostContext *ctx,
free (str);
slen = (size_t) cbuf_size;
ctx->json_enc = (char *) cbuf;
+ GNUNET_assert (
+ NULL !=
+ (ctx->headers = curl_slist_append (
+ ctx->headers,
+ "Content-Encoding: deflate")));
}
- GNUNET_assert
- (NULL != (ctx->headers = curl_slist_append
- (ctx->headers,
- "Content-Encoding: deflate")));
-#else
- ctx->json_enc = str;
-#endif
-
- GNUNET_assert
- (NULL != (ctx->headers = curl_slist_append
- (ctx->headers,
- "Content-Type: application/json")));
+ else
+ {
+ ctx->json_enc = str;
+ }
+ GNUNET_assert (
+ NULL !=
+ (ctx->headers = curl_slist_append (
+ ctx->headers,
+ "Content-Type: application/json")));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
@@ -101,16 +149,11 @@ TALER_curl_easy_post (struct TALER_CURL_PostContext *ctx,
}
-/**
- * Free the data in @a ctx.
- *
- * @param[in] ctx a request context (updated)
- */
void
TALER_curl_easy_post_finished (struct TALER_CURL_PostContext *ctx)
{
curl_slist_free_all (ctx->headers);
ctx->headers = NULL;
- GNUNET_free_non_null (ctx->json_enc);
+ GNUNET_free (ctx->json_enc);
ctx->json_enc = NULL;
}
diff --git a/src/exchange-tools/.gitignore b/src/exchange-tools/.gitignore
index 6e9e12faf..69279d792 100644
--- a/src/exchange-tools/.gitignore
+++ b/src/exchange-tools/.gitignore
@@ -1,3 +1,3 @@
-test_taler_exchange_httpd_home/.local/share/taler/exchange/live-keys/
-test_taler_exchange_httpd_home/.local/share/taler/exchange/wirefees/
-test_taler_exchange_httpd_home/.config/taler/account-1.json
+taler-exchange-offline
+taler-auditor-offline
+taler-crypto-worker
diff --git a/src/exchange-tools/Makefile.am b/src/exchange-tools/Makefile.am
index 2327d8111..955544564 100644
--- a/src/exchange-tools/Makefile.am
+++ b/src/exchange-tools/Makefile.am
@@ -4,7 +4,8 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/include
pkgcfgdir = $(prefix)/share/taler/config.d/
pkgcfg_DATA = \
- coins.conf
+ coins.conf \
+ exchange-offline.conf
if USE_COVERAGE
AM_CFLAGS = --coverage -O0
@@ -12,44 +13,37 @@ if USE_COVERAGE
endif
bin_PROGRAMS = \
- taler-exchange-keyup \
- taler-exchange-keycheck \
- taler-exchange-wire \
+ taler-auditor-offline \
+ taler-exchange-offline \
taler-exchange-dbinit
-taler_exchange_keyup_SOURCES = \
- taler-exchange-keyup.c
-taler_exchange_keyup_LDADD = \
- $(LIBGCRYPT_LIBS) \
- $(top_builddir)/src/util/libtalerutil.la \
- $(top_builddir)/src/pq/libtalerpq.la \
- $(top_builddir)/src/bank-lib/libtalerbank.la \
- $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
- -lgnunetutil $(XLIB)
-taler_exchange_keyup_LDFLAGS = $(POSTGRESQL_LDFLAGS)
-
-
-taler_exchange_wire_SOURCES = \
- taler-exchange-wire.c
-taler_exchange_wire_LDADD = \
+taler_exchange_offline_SOURCES = \
+ taler-exchange-offline.c
+taler_exchange_offline_LDADD = \
$(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/lib/libtalerexchange.la \
$(top_builddir)/src/json/libtalerjson.la \
- $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
- $(top_builddir)/src/bank-lib/libtalerbank.la \
$(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
-lgnunetjson \
+ -lgnunetcurl \
+ -ljansson \
-lgnunetutil \
- -ljansson $(XLIB)
-taler_exchange_wire_LDFLAGS = $(POSTGRESQL_LDFLAGS)
+ $(XLIB)
-taler_exchange_keycheck_SOURCES = \
- taler-exchange-keycheck.c
-taler_exchange_keycheck_LDADD = \
+taler_auditor_offline_SOURCES = \
+ taler-auditor-offline.c
+taler_auditor_offline_LDADD = \
$(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
- $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
- -lgnunetutil $(XLIB)
-taler_exchange_keycheck_LDFLAGS = $(POSTGRESQL_LDFLAGS)
+ $(top_builddir)/src/extensions/libtalerextensions.la \
+ -lgnunetjson \
+ -lgnunetcurl \
+ -ljansson \
+ -lgnunetutil \
+ $(XLIB)
taler_exchange_dbinit_SOURCES = \
taler-exchange-dbinit.c
@@ -58,9 +52,8 @@ taler_exchange_dbinit_LDADD = \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/pq/libtalerpq.la \
$(top_builddir)/src/exchangedb/libtalerexchangedb.la \
- -lgnunetutil $(XLIB)
-taler_exchange_dbinit_LDFLAGS = \
- $(POSTGRESQL_LDFLAGS)
+ -lgnunetutil \
+ $(XLIB)
taler_exchange_dbinit_CPPFLAGS = \
-I$(top_srcdir)/src/include \
-I$(top_srcdir)/src/pq/ \
@@ -71,17 +64,7 @@ taler_exchange_dbinit_CPPFLAGS = \
AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
-check_SCRIPTS = \
- test_taler_exchange_keyup.sh
-
-TESTS = \
- $(check_SCRIPTS)
-
# Distribution
EXTRA_DIST = \
- test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv \
- test_taler_exchange_httpd.conf \
- key-helper.c \
- $(check_SCRIPTS) \
$(pkgcfg_DATA)
diff --git a/src/exchange-tools/exchange-offline.conf b/src/exchange-tools/exchange-offline.conf
new file mode 100644
index 000000000..020eb34ba
--- /dev/null
+++ b/src/exchange-tools/exchange-offline.conf
@@ -0,0 +1,15 @@
+# This file is in the public domain.
+#
+[exchange-offline]
+
+# Where do we store the offline master private key of the exchange?
+MASTER_PRIV_FILE = ${TALER_DATA_HOME}exchange-offline/master.priv
+
+# Where do we store the TOFU key material?
+SECM_TOFU_FILE = ${TALER_DATA_HOME}exchange-offline/secm_tofus.pub
+
+# Base32-encoded public key of the RSA helper.
+# SECM_DENOM_PUBKEY =
+
+# Base32-encoded public key of the EdDSA helper.
+# SECM_ESIGN_PUBKEY =
diff --git a/src/exchange-tools/key-helper.c b/src/exchange-tools/key-helper.c
deleted file mode 100644
index 281202bd7..000000000
--- a/src/exchange-tools/key-helper.c
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2015-2020 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 key-helper.c
- * @brief shared logic between tools that deal with the master private key
- * @author Christian Grothoff
- */
-
-/**
- * Extract the @a master_priv from the @a cfg or @a masterkeyfile and
- * verify that it matches the master public key given in @a cfg.
- *
- * @param cfg configuration to use
- * @param masterkeyfile master private key filename, can be NULL to use from @a cfg
- * @param[out] master_priv where to store the master private key on success
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on failures
- */
-static int
-get_and_check_master_key (const struct GNUNET_CONFIGURATION_Handle *cfg,
- const char *masterkeyfile,
- struct TALER_MasterPrivateKeyP *master_priv)
-{
- struct GNUNET_CRYPTO_EddsaPublicKey mpub;
- struct GNUNET_CRYPTO_EddsaPublicKey mpub_cfg;
- char *fn;
-
- if (NULL != masterkeyfile)
- {
- fn = GNUNET_strdup (masterkeyfile);
- }
- else
- {
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (cfg,
- "exchange",
- "MASTER_PRIV_FILE",
- &fn))
- {
- fprintf (stderr,
- "Master private key file given neither in configuration nor on command-line\n");
- return GNUNET_SYSERR;
- }
- }
- if (GNUNET_YES !=
- GNUNET_DISK_file_test (fn))
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Exchange master private key `%s' does not exist yet, creating it!\n",
- fn);
- {
- struct GNUNET_CRYPTO_EddsaPrivateKey *eddsa_priv;
-
- eddsa_priv = GNUNET_CRYPTO_eddsa_key_create_from_file (fn);
- if (NULL == eddsa_priv)
- {
- fprintf (stderr,
- "Failed to initialize master key from file `%s'\n",
- fn);
- GNUNET_free (fn);
- return GNUNET_SYSERR;
- }
- master_priv->eddsa_priv = *eddsa_priv;
- GNUNET_CRYPTO_eddsa_key_get_public (eddsa_priv,
- &mpub);
- GNUNET_free (eddsa_priv);
- }
-
- /* Check our key matches that in the configuration */
- {
- char *masters;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "exchange",
- "MASTER_PUBLIC_KEY",
- &masters))
- {
- /* Help user by telling them precisely what to fix */
- masters = GNUNET_STRINGS_data_to_string_alloc (&mpub,
- sizeof (mpub));
- fprintf (stderr,
- "You must set MASTER_PUBLIC_KEY to `%s' in the [exchange] section of the configuration before proceeding.\n",
- masters);
- GNUNET_free (masters);
- GNUNET_free (fn);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (masters,
- strlen (masters),
- &mpub_cfg,
- sizeof (mpub_cfg)))
- {
- fprintf (stderr,
- "MASTER_PUBLIC_KEY value `%s' specified in section [exchange] of the configuration is a valid public key\n",
- masters);
- GNUNET_free (masters);
- GNUNET_free (fn);
- return GNUNET_SYSERR;
- }
- if (0 != GNUNET_memcmp (&mpub,
- &mpub_cfg))
- {
- fprintf (stderr,
- "MASTER_PUBLIC_KEY value `%s' specified in section [exchange] of the configuration does not match our master private key. You can use `gnunet-ecc -p \"%s\"' to determine the correct value.\n",
- masters,
- fn);
- GNUNET_free (masters);
- GNUNET_free (fn);
- return GNUNET_SYSERR;
- }
- GNUNET_free (masters);
- }
- GNUNET_free (fn);
-
- return GNUNET_OK;
-}
diff --git a/src/exchange-tools/taler-auditor-offline.c b/src/exchange-tools/taler-auditor-offline.c
new file mode 100644
index 000000000..8c280d46b
--- /dev/null
+++ b/src/exchange-tools/taler-auditor-offline.c
@@ -0,0 +1,1496 @@
+/*
+ This file is part of TALER
+ 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
+ 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 taler-auditor-offline.c
+ * @brief Support for operations involving the auditor's (offline) key.
+ * @author Christian Grothoff
+ */
+#include <platform.h>
+#include <gnunet/gnunet_json_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+
+/**
+ * Name of the input of a denomination key signature for the 'upload' operation.
+ * The "auditor-" prefix ensures that there is no ambiguity between
+ * taler-exchange-offline and taler-auditor-offline JSON formats.
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_SIGN_DENOMINATION "auditor-sign-denomination-0"
+
+/**
+ * Name of the input for the 'sign' and 'show' operations.
+ * The "auditor-" prefix ensures that there is no ambiguity between
+ * taler-exchange-offline and taler-auditor-offline JSON formats.
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_INPUT_KEYS "auditor-keys-0"
+
+/**
+ * Show the offline signing key.
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_SETUP "auditor-setup-0"
+
+/**
+ * Our private key, initialized in #load_offline_key().
+ */
+static struct TALER_AuditorPrivateKeyP auditor_priv;
+
+/**
+ * Our private key, initialized in #load_offline_key().
+ */
+static struct TALER_AuditorPublicKeyP auditor_pub;
+
+/**
+ * Base URL of this auditor's REST endpoint.
+ */
+static char *auditor_url;
+
+/**
+ * Exchange's master public key.
+ */
+static struct TALER_MasterPublicKeyP master_pub;
+
+/**
+ * Our context for making HTTP requests.
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * Reschedule context for #ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * Handle to the exchange's configuration
+ */
+static const struct GNUNET_CONFIGURATION_Handle *kcfg;
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * Input to consume.
+ */
+static json_t *in;
+
+/**
+ * Array of actions to perform.
+ */
+static json_t *out;
+
+/**
+ * Currency supported by this auditor.
+ */
+static char *currency;
+
+
+/**
+ * A subcommand supported by this program.
+ */
+struct SubCommand
+{
+ /**
+ * Name of the command.
+ */
+ const char *name;
+
+ /**
+ * Help text for the command.
+ */
+ const char *help;
+
+ /**
+ * Function implementing the command.
+ *
+ * @param args subsequent command line arguments (char **)
+ */
+ void (*cb)(char *const *args);
+};
+
+
+/**
+ * Data structure for wire add requests.
+ */
+struct DenominationAddRequest
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct DenominationAddRequest *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct DenominationAddRequest *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_EXCHANGE_AuditorAddDenominationHandle *h;
+
+ /**
+ * Array index of the associated command.
+ */
+ size_t idx;
+};
+
+
+/**
+ * Next work item to perform.
+ */
+static struct GNUNET_SCHEDULER_Task *nxt;
+
+/**
+ * Active denomination add requests.
+ */
+static struct DenominationAddRequest *dar_head;
+
+/**
+ * Active denomination add requests.
+ */
+static struct DenominationAddRequest *dar_tail;
+
+/**
+ * Handle to the exchange, used to request /keys.
+ */
+static struct TALER_EXCHANGE_GetKeysHandle *exchange;
+
+
+/**
+ * Shutdown task. Invoked when the application is being terminated.
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+
+ {
+ struct DenominationAddRequest *dar;
+
+ while (NULL != (dar = dar_head))
+ {
+ fprintf (stderr,
+ "Aborting incomplete wire add #%u\n",
+ (unsigned int) dar->idx);
+ TALER_EXCHANGE_add_auditor_denomination_cancel (dar->h);
+ GNUNET_CONTAINER_DLL_remove (dar_head,
+ dar_tail,
+ dar);
+ GNUNET_free (dar);
+ }
+ }
+ if (NULL != out)
+ {
+ json_dumpf (out,
+ stdout,
+ JSON_INDENT (2));
+ json_decref (out);
+ out = NULL;
+ }
+ if (NULL != in)
+ {
+ fprintf (stderr,
+ "Darning: input not consumed!\n");
+ json_decref (in);
+ in = NULL;
+ }
+ if (NULL != exchange)
+ {
+ TALER_EXCHANGE_get_keys_cancel (exchange);
+ exchange = NULL;
+ }
+ if (NULL != nxt)
+ {
+ GNUNET_SCHEDULER_cancel (nxt);
+ nxt = NULL;
+ }
+ if (NULL != ctx)
+ {
+ GNUNET_CURL_fini (ctx);
+ ctx = NULL;
+ }
+ if (NULL != rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (rc);
+ rc = NULL;
+ }
+}
+
+
+/**
+ * Test if we should shut down because all tasks are done.
+ */
+static void
+test_shutdown (void)
+{
+ if ( (NULL == dar_head) &&
+ (NULL == exchange) &&
+ (NULL == nxt) )
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * Function to continue processing the next command.
+ *
+ * @param cls must be a `char *const*` with the array of
+ * command-line arguments to process next
+ */
+static void
+work (void *cls);
+
+
+/**
+ * Function to schedule job to process the next command.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+next (char *const *args)
+{
+ GNUNET_assert (NULL == nxt);
+ if (NULL == args[0])
+ {
+ test_shutdown ();
+ return;
+ }
+ nxt = GNUNET_SCHEDULER_add_now (&work,
+ (void *) args);
+}
+
+
+/**
+ * Add an operation to the #out JSON array for processing later.
+ *
+ * @param op_name name of the operation
+ * @param op_value values for the operation (consumed)
+ */
+static void
+output_operation (const char *op_name,
+ json_t *op_value)
+{
+ json_t *action;
+
+ GNUNET_assert (NULL != out);
+ action = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ op_name),
+ GNUNET_JSON_pack_object_steal ("arguments",
+ op_value));
+ GNUNET_break (0 ==
+ json_array_append_new (out,
+ action));
+}
+
+
+/**
+ * Information about a subroutine for an upload.
+ */
+struct UploadHandler
+{
+ /**
+ * Key to trigger this subroutine.
+ */
+ const char *key;
+
+ /**
+ * Function implementing an upload.
+ *
+ * @param exchange_url URL of the exchange
+ * @param idx index of the operation we are performing
+ * @param value arguments to drive the upload.
+ */
+ void (*cb)(const char *exchange_url,
+ size_t idx,
+ const json_t *value);
+
+};
+
+
+/**
+ * Load the offline key (if not yet done). Triggers shutdown on failure.
+ *
+ * @param do_create #GNUNET_YES if the key may be created
+ * @return #GNUNET_OK on success
+ */
+static int
+load_offline_key (int do_create)
+{
+ static bool done;
+ int ret;
+ char *fn;
+
+ if (done)
+ return GNUNET_OK;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (kcfg,
+ "auditor",
+ "AUDITOR_PRIV_FILE",
+ &fn))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "auditor",
+ "AUDITOR_PRIV_FILE");
+ test_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_YES !=
+ GNUNET_DISK_file_test (fn))
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Auditor private key `%s' does not exist yet, creating it!\n",
+ fn);
+ ret = GNUNET_CRYPTO_eddsa_key_from_file (fn,
+ do_create,
+ &auditor_priv.eddsa_priv);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize auditor key from file `%s': %s\n",
+ fn,
+ "could not create file");
+ GNUNET_free (fn);
+ test_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (fn);
+ GNUNET_CRYPTO_eddsa_key_get_public (&auditor_priv.eddsa_priv,
+ &auditor_pub.eddsa_pub);
+ done = true;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with information about the post denomination (signature)
+ * add operation result.
+ *
+ * @param cls closure with a `struct DenominationAddRequest`
+ * @param adr response data
+ */
+static void
+denomination_add_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_AuditorAddDenominationResponse *adr)
+{
+ struct DenominationAddRequest *dar = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &adr->hr;
+
+ if (MHD_HTTP_NO_CONTENT != hr->http_status)
+ {
+ fprintf (stderr,
+ "Upload failed for command #%u with status %u: %s (%s)\n",
+ (unsigned int) dar->idx,
+ hr->http_status,
+ TALER_ErrorCode_get_hint (hr->ec),
+ NULL != hr->hint
+ ? hr->hint
+ : "no hint provided");
+ global_ret = EXIT_FAILURE;
+ }
+ GNUNET_CONTAINER_DLL_remove (dar_head,
+ dar_tail,
+ dar);
+ GNUNET_free (dar);
+ test_shutdown ();
+}
+
+
+/**
+ * Upload denomination add data.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_denomination_add (const char *exchange_url,
+ size_t idx,
+ const json_t *value)
+{
+ struct TALER_AuditorSignatureP auditor_sig;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct DenominationAddRequest *dar;
+ const char *err_name;
+ unsigned int err_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_spec_fixed_auto ("auditor_sig",
+ &auditor_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ fprintf (stderr,
+ "Invalid input for adding denomination: %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) idx);
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ return;
+ }
+ dar = GNUNET_new (struct DenominationAddRequest);
+ dar->idx = idx;
+ dar->h =
+ TALER_EXCHANGE_add_auditor_denomination (ctx,
+ exchange_url,
+ &h_denom_pub,
+ &auditor_pub,
+ &auditor_sig,
+ &denomination_add_cb,
+ dar);
+ GNUNET_CONTAINER_DLL_insert (dar_head,
+ dar_tail,
+ dar);
+}
+
+
+/**
+ * Perform uploads based on the JSON in #out.
+ *
+ * @param exchange_url base URL of the exchange to use
+ */
+static void
+trigger_upload (const char *exchange_url)
+{
+ struct UploadHandler uhs[] = {
+ {
+ .key = OP_SIGN_DENOMINATION,
+ .cb = &upload_denomination_add
+ },
+ /* array termination */
+ {
+ .key = NULL
+ }
+ };
+ size_t index;
+ json_t *obj;
+
+ json_array_foreach (out, index, obj) {
+ bool found = false;
+ const char *key;
+ const json_t *value;
+
+ key = json_string_value (json_object_get (obj, "operation"));
+ value = json_object_get (obj, "arguments");
+ if (NULL == key)
+ {
+ fprintf (stderr,
+ "Malformed JSON input\n");
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ return;
+ }
+ /* block of code that uses key and value */
+ for (unsigned int i = 0; NULL != uhs[i].key; i++)
+ {
+ if (0 == strcasecmp (key,
+ uhs[i].key))
+ {
+ found = true;
+ uhs[i].cb (exchange_url,
+ index,
+ value);
+ break;
+ }
+ }
+ if (! found)
+ {
+ fprintf (stderr,
+ "Upload does not know how to handle `%s'\n",
+ key);
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ return;
+ }
+ }
+ /* test here, in case no upload was triggered (i.e. empty input) */
+ test_shutdown ();
+}
+
+
+/**
+ * Upload operation result (signatures) to exchange.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_upload (char *const *args)
+{
+ char *exchange_url;
+
+ (void) args;
+ if (GNUNET_YES == GNUNET_is_zero (&auditor_pub))
+ {
+ /* private key not available, try configuration for public key */
+ char *auditor_public_key_str;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (kcfg,
+ "auditor",
+ "PUBLIC_KEY",
+ &auditor_public_key_str))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "auditor",
+ "PUBLIC_KEY");
+ global_ret = EXIT_NOTCONFIGURED;
+ test_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_public_key_from_string (
+ auditor_public_key_str,
+ strlen (auditor_public_key_str),
+ &auditor_pub.eddsa_pub))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "auditor",
+ "PUBLIC_KEY",
+ "invalid key");
+ GNUNET_free (auditor_public_key_str);
+ global_ret = EXIT_NOTCONFIGURED;
+ test_shutdown ();
+ return;
+ }
+ GNUNET_free (auditor_public_key_str);
+ }
+ if (NULL != in)
+ {
+ fprintf (stderr,
+ "Downloaded data was not consumed, refusing upload\n");
+ test_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if (NULL == out)
+ {
+ json_error_t err;
+
+ out = json_loadf (stdin,
+ JSON_REJECT_DUPLICATES,
+ &err);
+ if (NULL == out)
+ {
+ fprintf (stderr,
+ "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
+ err.text,
+ err.line,
+ err.source,
+ err.position);
+ test_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ }
+ if (! json_is_array (out))
+ {
+ fprintf (stderr,
+ "Error: expected JSON array for `upload` command\n");
+ test_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (kcfg,
+ "exchange",
+ "BASE_URL",
+ &exchange_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ global_ret = EXIT_NOTCONFIGURED;
+ test_shutdown ();
+ return;
+ }
+ trigger_upload (exchange_url);
+ json_decref (out);
+ out = NULL;
+ GNUNET_free (exchange_url);
+}
+
+
+/**
+ * Function called with information about who is auditing
+ * a particular exchange and what keys the exchange is using.
+ *
+ * @param cls closure with the `char **` remaining args
+ * @param kr response data
+ * @param keys key data from the exchange
+ */
+static void
+keys_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_KeysResponse *kr,
+ struct TALER_EXCHANGE_Keys *keys)
+{
+ char *const *args = cls;
+
+ exchange = NULL;
+ switch (kr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ if (NULL == kr->hr.reply)
+ {
+ GNUNET_break (0);
+ test_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ break;
+ default:
+ fprintf (stderr,
+ "Failed to download keys: %s (HTTP status: %u/%u)\n",
+ kr->hr.hint,
+ kr->hr.http_status,
+ (unsigned int) kr->hr.ec);
+ test_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ in = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ OP_INPUT_KEYS),
+ GNUNET_JSON_pack_object_incref ("arguments",
+ (json_t *) kr->hr.reply));
+ if (NULL == args[0])
+ {
+ json_dumpf (in,
+ stdout,
+ JSON_INDENT (2));
+ json_decref (in);
+ in = NULL;
+ }
+ next (args);
+ TALER_EXCHANGE_keys_decref (keys);
+}
+
+
+/**
+ * Download future keys.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_download (char *const *args)
+{
+ char *exchange_url;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (kcfg,
+ "exchange",
+ "BASE_URL",
+ &exchange_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ test_shutdown ();
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ exchange = TALER_EXCHANGE_get_keys (ctx,
+ exchange_url,
+ NULL,
+ &keys_cb,
+ (void *) args);
+ GNUNET_free (exchange_url);
+}
+
+
+/**
+ * Output @a denomkeys for human consumption.
+ *
+ * @param denomkeys keys to output
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+show_denomkeys (const json_t *denomkeys)
+{
+ size_t index;
+ json_t *value;
+
+ json_array_foreach (denomkeys, index, value) {
+ struct TALER_DenominationGroup group;
+ const json_t *denoms;
+ const char *err_name;
+ unsigned int err_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_denomination_group (NULL,
+ currency,
+ &group),
+ GNUNET_JSON_spec_array_const ("denoms",
+ &denoms),
+ GNUNET_JSON_spec_end ()
+ };
+ size_t index2;
+ json_t *value2;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ fprintf (stderr,
+ "Invalid input for denomination key to 'show': %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) index);
+ GNUNET_JSON_parse_free (spec);
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ json_array_foreach (denoms, index2, value2) {
+ struct GNUNET_TIME_Timestamp stamp_start;
+ struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
+ struct GNUNET_TIME_Timestamp stamp_expire_deposit;
+ struct GNUNET_TIME_Timestamp stamp_expire_legal;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_denom_pub_cipher (NULL,
+ group.cipher,
+ &denom_pub),
+ GNUNET_JSON_spec_timestamp ("stamp_start",
+ &stamp_start),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
+ &stamp_expire_withdraw),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
+ &stamp_expire_deposit),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
+ &stamp_expire_legal),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_TIME_Relative duration;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value2,
+ ispec,
+ &err_name,
+ &err_line))
+ {
+ fprintf (stderr,
+ "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) index,
+ (unsigned int) index2);
+ GNUNET_JSON_parse_free (spec);
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ duration = GNUNET_TIME_absolute_get_difference (
+ stamp_start.abs_time,
+ stamp_expire_withdraw.abs_time);
+ TALER_denom_pub_hash (&denom_pub,
+ &h_denom_pub);
+ if (GNUNET_OK !=
+ TALER_exchange_offline_denom_validity_verify (
+ &h_denom_pub,
+ stamp_start,
+ stamp_expire_withdraw,
+ stamp_expire_deposit,
+ stamp_expire_legal,
+ &group.value,
+ &group.fees,
+ &master_pub,
+ &master_sig))
+ {
+ fprintf (stderr,
+ "Invalid master signature for key %s (aborting)\n",
+ TALER_B2S (&h_denom_pub));
+ global_ret = EXIT_FAILURE;
+ GNUNET_JSON_parse_free (ispec);
+ GNUNET_JSON_parse_free (spec);
+ test_shutdown ();
+ return GNUNET_SYSERR;
+ }
+
+ {
+ char *withdraw_fee_s;
+ char *deposit_fee_s;
+ char *refresh_fee_s;
+ char *refund_fee_s;
+ char *deposit_s;
+ char *legal_s;
+
+ withdraw_fee_s = TALER_amount_to_string (&group.fees.withdraw);
+ deposit_fee_s = TALER_amount_to_string (&group.fees.deposit);
+ refresh_fee_s = TALER_amount_to_string (&group.fees.refresh);
+ refund_fee_s = TALER_amount_to_string (&group.fees.refund);
+ deposit_s = GNUNET_strdup (
+ GNUNET_TIME_timestamp2s (stamp_expire_deposit));
+ legal_s = GNUNET_strdup (
+ GNUNET_TIME_timestamp2s (stamp_expire_legal));
+
+ printf (
+ "DENOMINATION-KEY %s of value %s starting at %s "
+ "(used for: %s, deposit until: %s legal end: %s) with fees %s/%s/%s/%s\n",
+ TALER_B2S (&h_denom_pub),
+ TALER_amount2s (&group.value),
+ GNUNET_TIME_timestamp2s (stamp_start),
+ GNUNET_TIME_relative2s (duration,
+ false),
+ deposit_s,
+ legal_s,
+ withdraw_fee_s,
+ deposit_fee_s,
+ refresh_fee_s,
+ refund_fee_s);
+ GNUNET_free (withdraw_fee_s);
+ GNUNET_free (deposit_fee_s);
+ GNUNET_free (refresh_fee_s);
+ GNUNET_free (refund_fee_s);
+ GNUNET_free (deposit_s);
+ GNUNET_free (legal_s);
+ }
+ GNUNET_JSON_parse_free (ispec);
+ }
+ GNUNET_JSON_parse_free (spec);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse the '/keys' input for operation called @a command_name.
+ *
+ * @param command_name name of the command, for logging errors
+ * @return NULL if the input is malformed
+ */
+static json_t *
+parse_keys (const char *command_name)
+{
+ json_t *keys;
+ const char *op_str;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("arguments",
+ &keys),
+ GNUNET_JSON_spec_string ("operation",
+ &op_str),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *err_name;
+ unsigned int err_line;
+
+ if (NULL == in)
+ {
+ json_error_t err;
+
+ in = json_loadf (stdin,
+ JSON_REJECT_DUPLICATES,
+ &err);
+ if (NULL == in)
+ {
+ fprintf (stderr,
+ "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
+ err.text,
+ err.line,
+ err.source,
+ err.position);
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ return NULL;
+ }
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (in,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input to '%s': %s#%u (skipping)\n",
+ command_name,
+ err_name,
+ err_line);
+ json_dumpf (in,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ return NULL;
+ }
+ if (0 != strcmp (op_str,
+ OP_INPUT_KEYS))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input to '%s' : operation is `%s', expected `%s'\n",
+ command_name,
+ op_str,
+ OP_INPUT_KEYS);
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+ }
+ json_decref (in);
+ in = NULL;
+ return keys;
+}
+
+
+/**
+ * Show exchange denomination keys.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_show (char *const *args)
+{
+ json_t *keys;
+ const char *err_name;
+ unsigned int err_line;
+ const json_t *denomkeys;
+ struct TALER_MasterPublicKeyP mpub;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("denominations",
+ &denomkeys),
+ GNUNET_JSON_spec_fixed_auto ("master_public_key",
+ &mpub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ keys = parse_keys ("show");
+ if (NULL == keys)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Showing failed: no valid input\n");
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (keys,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ fprintf (stderr,
+ "Invalid input to 'show': %s#%u (skipping)\n",
+ err_name,
+ err_line);
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ json_decref (keys);
+ return;
+ }
+ if (0 !=
+ GNUNET_memcmp (&mpub,
+ &master_pub))
+ {
+ fprintf (stderr,
+ "Exchange master public key does not match key we have configured (aborting)\n");
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ json_decref (keys);
+ return;
+ }
+ if (GNUNET_OK !=
+ show_denomkeys (denomkeys))
+ {
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ json_decref (keys);
+ return;
+ }
+ json_decref (keys);
+ /* do NOT consume input if next argument is '-' */
+ if ( (NULL != args[0]) &&
+ (0 == strcmp ("-",
+ args[0])) )
+ {
+ next (args + 1);
+ return;
+ }
+ next (args);
+}
+
+
+/**
+ * Sign @a denomkeys with offline key.
+ *
+ * @param denomkeys keys to output
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+sign_denomkeys (const json_t *denomkeys)
+{
+ size_t group_idx;
+ json_t *value;
+
+ json_array_foreach (denomkeys, group_idx, value) {
+ struct TALER_DenominationGroup group = { 0 };
+ const json_t *denom_keys_array;
+ const char *err_name;
+ unsigned int err_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_denomination_group (NULL,
+ currency,
+ &group),
+ GNUNET_JSON_spec_array_const ("denoms",
+ &denom_keys_array),
+ GNUNET_JSON_spec_end ()
+ };
+ size_t index;
+ json_t *denom_key_obj;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ fprintf (stderr,
+ "Invalid input for denomination key to 'sign': %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) group_idx);
+ GNUNET_JSON_parse_free (spec);
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ json_array_foreach (denom_keys_array, index, denom_key_obj) {
+ struct GNUNET_TIME_Timestamp stamp_start;
+ struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
+ struct GNUNET_TIME_Timestamp stamp_expire_deposit;
+ struct GNUNET_TIME_Timestamp stamp_expire_legal;
+ struct TALER_DenominationPublicKey denom_pub = {
+ .age_mask = group.age_mask
+ };
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_denom_pub_cipher (NULL,
+ group.cipher,
+ &denom_pub),
+ GNUNET_JSON_spec_timestamp ("stamp_start",
+ &stamp_start),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
+ &stamp_expire_withdraw),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
+ &stamp_expire_deposit),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
+ &stamp_expire_legal),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TALER_DenominationHashP h_denom_pub;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (denom_key_obj,
+ ispec,
+ &err_name,
+ &err_line))
+ {
+ fprintf (stderr,
+ "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) group_idx,
+ (unsigned int) index);
+ GNUNET_JSON_parse_free (spec);
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ TALER_denom_pub_hash (&denom_pub,
+ &h_denom_pub);
+ if (GNUNET_OK !=
+ TALER_exchange_offline_denom_validity_verify (
+ &h_denom_pub,
+ stamp_start,
+ stamp_expire_withdraw,
+ stamp_expire_deposit,
+ stamp_expire_legal,
+ &group.value,
+ &group.fees,
+ &master_pub,
+ &master_sig))
+ {
+ fprintf (stderr,
+ "Invalid master signature for key %s (aborting)\n",
+ TALER_B2S (&h_denom_pub));
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct TALER_AuditorSignatureP auditor_sig;
+
+ TALER_auditor_denom_validity_sign (auditor_url,
+ &h_denom_pub,
+ &master_pub,
+ stamp_start,
+ stamp_expire_withdraw,
+ stamp_expire_deposit,
+ stamp_expire_legal,
+ &group.value,
+ &group.fees,
+ &auditor_priv,
+ &auditor_sig);
+ output_operation (OP_SIGN_DENOMINATION,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_pack_data_auto ("auditor_sig",
+ &auditor_sig)));
+ }
+ GNUNET_JSON_parse_free (ispec);
+ }
+ GNUNET_JSON_parse_free (spec);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Sign denomination keys.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_sign (char *const *args)
+{
+ json_t *keys;
+ const char *err_name;
+ unsigned int err_line;
+ struct TALER_MasterPublicKeyP mpub;
+ const json_t *denomkeys;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("denominations",
+ &denomkeys),
+ GNUNET_JSON_spec_fixed_auto ("master_public_key",
+ &mpub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ keys = parse_keys ("sign");
+ if (NULL == keys)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Signing failed: no valid input\n");
+ return;
+ }
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ {
+ json_decref (keys);
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (keys,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ fprintf (stderr,
+ "Invalid input to 'sign': %s#%u (skipping)\n",
+ err_name,
+ err_line);
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ json_decref (keys);
+ return;
+ }
+ if (0 !=
+ GNUNET_memcmp (&mpub,
+ &master_pub))
+ {
+ fprintf (stderr,
+ "Exchange master public key does not match key we have configured (aborting)\n");
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ json_decref (keys);
+ return;
+ }
+ if (NULL == out)
+ {
+ out = json_array ();
+ GNUNET_assert (NULL != out);
+ }
+ if (GNUNET_OK !=
+ sign_denomkeys (denomkeys))
+ {
+ global_ret = EXIT_FAILURE;
+ test_shutdown ();
+ json_decref (keys);
+ return;
+ }
+ json_decref (keys);
+ next (args);
+}
+
+
+/**
+ * Setup and output offline signing key.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_setup (char *const *args)
+{
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_YES))
+ {
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if (NULL != *args)
+ {
+ if (NULL == out)
+ {
+ out = json_array ();
+ GNUNET_assert (NULL != out);
+ }
+ output_operation (OP_SETUP,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("auditor_pub",
+ &auditor_pub)));
+ }
+
+ else
+ {
+ char *pub_s;
+
+ pub_s = GNUNET_STRINGS_data_to_string_alloc (&auditor_pub,
+ sizeof (auditor_pub));
+ fprintf (stdout,
+ "%s\n",
+ pub_s);
+ GNUNET_free (pub_s);
+ }
+ if ( (NULL != *args) &&
+ (0 == strcmp (*args,
+ "-")) )
+ args++;
+ next (args);
+}
+
+
+static void
+work (void *cls)
+{
+ char *const *args = cls;
+ struct SubCommand cmds[] = {
+ {
+ .name = "setup",
+ .help =
+ "setup auditor offline private key and show the public key",
+ .cb = &do_setup
+ },
+ {
+ .name = "download",
+ .help =
+ "obtain keys from exchange (to be performed online!)",
+ .cb = &do_download
+ },
+ {
+ .name = "show",
+ .help =
+ "display keys from exchange for human review (pass '-' as argument to disable consuming input)",
+ .cb = &do_show
+ },
+ {
+ .name = "sign",
+ .help =
+ "sing all denomination keys from the input",
+ .cb = &do_sign
+ },
+ {
+ .name = "upload",
+ .help =
+ "upload operation result to exchange (to be performed online!)",
+ .cb = &do_upload
+ },
+ /* list terminator */
+ {
+ .name = NULL,
+ }
+ };
+ (void) cls;
+
+ nxt = NULL;
+ for (unsigned int i = 0; NULL != cmds[i].name; i++)
+ {
+ if (0 == strcasecmp (cmds[i].name,
+ args[0]))
+ {
+ cmds[i].cb (&args[1]);
+ return;
+ }
+ }
+
+ if (0 != strcasecmp ("help",
+ args[0]))
+ {
+ fprintf (stderr,
+ "Unexpected command `%s'\n",
+ args[0]);
+ global_ret = EXIT_INVALIDARGUMENT;
+ }
+ fprintf (stderr,
+ "Supported subcommands:\n");
+ for (unsigned int i = 0; NULL != cmds[i].name; i++)
+ {
+ fprintf (stderr,
+ "\t%s - %s\n",
+ cmds[i].name,
+ cmds[i].help);
+ }
+}
+
+
+/**
+ * Main function that will be run.
+ *
+ * @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) cfgfile;
+ kcfg = cfg;
+ if (GNUNET_OK !=
+ TALER_config_get_currency (kcfg,
+ &currency))
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (kcfg,
+ "auditor",
+ "BASE_URL",
+ &auditor_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "auditor",
+ "BASE_URL");
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ {
+ char *master_public_key_str;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "MASTER_PUBLIC_KEY",
+ &master_public_key_str))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "MASTER_PUBLIC_KEY");
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_public_key_from_string (
+ master_public_key_str,
+ strlen (master_public_key_str),
+ &master_pub.eddsa_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid master public key given in exchange configuration.");
+ GNUNET_free (master_public_key_str);
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ GNUNET_free (master_public_key_str);
+ }
+ ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &rc);
+ rc = GNUNET_CURL_gnunet_rc_create (ctx);
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ next (args);
+}
+
+
+/**
+ * The main function of the taler-auditor-offline tool. This tool is used to
+ * sign denomination keys with the auditor's key. It uses the long-term
+ * offline private key of the auditor and generates signatures with it. It
+ * also supports online operations with the exchange to download its input
+ * data and to upload its results. Those online operations should be performed
+ * on another machine in production!
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ /* 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-auditor-offline",
+ gettext_noop ("Operations for offline signing for a Taler exchange"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+
+/* end of taler-auditor-offline.c */
diff --git a/src/exchange-tools/taler-exchange-dbinit.c b/src/exchange-tools/taler-exchange-dbinit.c
index 67632faca..41ff2ab24 100644
--- a/src/exchange-tools/taler-exchange-dbinit.c
+++ b/src/exchange-tools/taler-exchange-dbinit.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014, 2015 Taler Systems SA
+ Copyright (C) 2014-2024 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
@@ -17,6 +17,7 @@
* @file exchange-tools/taler-exchange-dbinit.c
* @brief Create tables for the exchange database.
* @author Florian Dold
+ * @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
@@ -29,16 +30,36 @@
static int global_ret;
/**
+ * -a option: inject auditor triggers
+ */
+static int inject_auditor;
+
+/**
* -r option: do full DB reset
*/
static int reset_db;
/**
+ * -s option: clear revolving shard locks
+ */
+static int clear_shards;
+
+/**
* -g option: garbage collect DB reset
*/
static int gc_db;
/**
+ * -P option: setup a partitioned database
+ */
+static uint32_t num_partitions;
+
+/**
+ * -f option: force partitions to be created when there is only one
+ */
+static int force_create_partitions;
+
+/**
* Main function that will be run.
*
* @param cls closure
@@ -57,40 +78,77 @@ run (void *cls,
(void) cls;
(void) args;
(void) cfgfile;
+
if (NULL ==
(plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
{
fprintf (stderr,
"Failed to initialize database plugin.\n");
- global_ret = 1;
+ global_ret = EXIT_NOTINSTALLED;
return;
}
if (reset_db)
{
- if (GNUNET_OK != plugin->drop_tables (plugin->cls))
+ if (GNUNET_OK !=
+ plugin->drop_tables (plugin->cls))
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Could not drop tables as requested. Either database was not yet initialized, or permission denied. Consult the logs. Will still try to create new tables.\n");
}
}
if (GNUNET_OK !=
- plugin->create_tables (plugin->cls))
+ plugin->create_tables (plugin->cls,
+ force_create_partitions || num_partitions > 0,
+ num_partitions))
{
fprintf (stderr,
"Failed to initialize database.\n");
TALER_EXCHANGEDB_plugin_unload (plugin);
- global_ret = 1;
+ plugin = NULL;
+ global_ret = EXIT_NOPERMISSION;
return;
}
- if (gc_db)
+ if (gc_db || clear_shards)
+ {
+ if (GNUNET_OK !=
+ plugin->preflight (plugin->cls))
+ {
+ fprintf (stderr,
+ "Failed to prepare database.\n");
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+ global_ret = EXIT_NOPERMISSION;
+ return;
+ }
+ if (clear_shards)
+ {
+ if (GNUNET_OK !=
+ plugin->delete_shard_locks (plugin->cls))
+ {
+ fprintf (stderr,
+ "Clearing revolving shards failed!\n");
+ }
+ }
+ if (gc_db)
+ {
+ if (GNUNET_SYSERR == plugin->gc (plugin->cls))
+ {
+ fprintf (stderr,
+ "Garbage collection failed!\n");
+ }
+ }
+ }
+ if (inject_auditor)
{
- if (GNUNET_SYSERR == plugin->gc (plugin->cls))
+ if (GNUNET_SYSERR == plugin->inject_auditor_triggers (plugin->cls))
{
fprintf (stderr,
- "Garbage collection failed!\n");
+ "Injecting auditor triggers failed!\n");
+ global_ret = EXIT_FAILURE;
}
}
TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
}
@@ -100,39 +158,61 @@ run (void *cls,
*
* @param argc number of arguments from the command line
* @param argv command line arguments
- * @return 0 ok, 1 on error
+ * @return 0 ok, non-zero on error
*/
int
main (int argc,
char *const *argv)
{
const struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_flag ('r',
- "reset",
- "reset database (DANGEROUS: all existing data is lost!)",
- &reset_db),
+ GNUNET_GETOPT_option_flag ('a',
+ "inject-auditor",
+ "inject auditor triggers",
+ &inject_auditor),
GNUNET_GETOPT_option_flag ('g',
"gc",
"garbage collect database",
&gc_db),
+ GNUNET_GETOPT_option_flag ('r',
+ "reset",
+ "reset database (DANGEROUS: all existing data is lost!)",
+ &reset_db),
+ GNUNET_GETOPT_option_flag ('s',
+ "shardunlock",
+ "unlock all revolving shard locks (use after system crash or shard size change while services are not running)",
+ &clear_shards),
+ GNUNET_GETOPT_option_uint ('P',
+ "partition",
+ "NUMBER",
+ "Setup a partitioned database where each table which can be partitioned holds NUMBER partitions on a single DB node",
+ &num_partitions),
+ GNUNET_GETOPT_option_flag ('f',
+ "force",
+ "Force partitions to be created if there is only one partition",
+ &force_create_partitions),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue ret;
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
/* 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 */
- (void) TALER_project_data_default ();
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-exchange-dbinit",
- "INFO",
- NULL));
- if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc, argv,
- "taler-exchange-dbinit",
- "Initialize Taler exchange database",
- options,
- &run, NULL))
- return 1;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-exchange-dbinit",
+ gettext_noop ("Initialize Taler exchange database"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
return global_ret;
}
diff --git a/src/exchange-tools/taler-exchange-keycheck.c b/src/exchange-tools/taler-exchange-keycheck.c
deleted file mode 100644
index 6b6ce9eb9..000000000
--- a/src/exchange-tools/taler-exchange-keycheck.c
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014, 2015, 2016 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 taler-exchange-keycheck.c
- * @brief Check exchange keys for validity. Reads the signing and denomination
- * keys from the exchange directory and checks to make sure they are
- * well-formed. This is purely a diagnostic tool.
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#include <platform.h>
-#include <gnunet/gnunet_util_lib.h>
-#include "taler_exchangedb_lib.h"
-
-/**
- * Exchange directory with the keys.
- */
-static char *exchange_directory;
-
-/**
- * Our configuration.
- */
-static const struct GNUNET_CONFIGURATION_Handle *kcfg;
-
-/**
- * Return value from main().
- */
-static int global_ret;
-
-/**
- * Option -i used to print full denomination key hashes for
- * denominations of certain amounts.
- */
-static struct TALER_Amount print_dk_amount;
-
-
-/**
- * Function called on each signing key.
- *
- * @param cls closure (NULL)
- * @param filename name of the file the key came from
- * @param ski the sign key
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-static int
-signkeys_iter (void *cls,
- const char *filename,
- const struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP *ski)
-{
- (void) cls;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Iterating over key `%s' for start time %s\n",
- filename,
- GNUNET_STRINGS_absolute_time_to_string
- (GNUNET_TIME_absolute_ntoh (ski->issue.start)));
-
- if (ntohl (ski->issue.purpose.size) !=
- (sizeof (struct TALER_ExchangeSigningKeyValidityPS)))
- {
- fprintf (stderr,
- "Signing key `%s' has invalid purpose size\n",
- filename);
- return GNUNET_SYSERR;
- }
- if ( (0 != GNUNET_TIME_absolute_ntoh (ski->issue.start).abs_value_us
- % 1000000) ||
- (0 != GNUNET_TIME_absolute_ntoh (ski->issue.expire).abs_value_us
- % 1000000) ||
- (0 != GNUNET_TIME_absolute_ntoh (ski->issue.end).abs_value_us
- % 1000000) )
- {
- fprintf (stderr,
- "Timestamps are not multiples of a round second\n");
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY,
- &ski->issue.purpose,
- &ski->master_sig.eddsa_signature,
- &ski->issue.master_public_key.eddsa_pub))
- {
- fprintf (stderr,
- "Signing key `%s' has invalid signature\n",
- filename);
- return GNUNET_SYSERR;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Signing key `%s' valid\n",
- filename);
- return GNUNET_OK;
-}
-
-
-/**
- * Check signing keys.
- *
- * @return #GNUNET_OK if the keys are OK
- * #GNUNET_NO if not
- */
-static int
-exchange_signkeys_check ()
-{
- if (0 > TALER_EXCHANGEDB_signing_keys_iterate (exchange_directory,
- &signkeys_iter,
- NULL))
- return GNUNET_NO;
- return GNUNET_OK;
-}
-
-
-/**
- * Function called on each denomination key.
- *
- * @param cls closure (NULL)
- * @param dki the denomination key
- * @param alias coin alias
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-static int
-denomkeys_iter (void *cls,
- const char *alias,
- const struct
- TALER_EXCHANGEDB_DenominationKey *dki)
-{
- struct GNUNET_HashCode hc;
- struct TALER_Amount value;
-
- (void) cls;
- if (ntohl (dki->issue.properties.purpose.size) !=
- sizeof (struct TALER_DenominationKeyValidityPS))
- {
- fprintf (stderr,
- "Denomination key for `%s' has invalid purpose size\n",
- alias);
- return GNUNET_SYSERR;
- }
-
- if ( (0 != GNUNET_TIME_absolute_ntoh (
- dki->issue.properties.start).abs_value_us % 1000000) ||
- (0 != GNUNET_TIME_absolute_ntoh (
- dki->issue.properties.expire_withdraw).abs_value_us % 1000000) ||
- (0 != GNUNET_TIME_absolute_ntoh (
- dki->issue.properties.expire_legal).abs_value_us % 1000000) ||
- (0 != GNUNET_TIME_absolute_ntoh (
- dki->issue.properties.expire_deposit).abs_value_us % 1000000) )
- {
- fprintf (stderr,
- "Timestamps are not multiples of a round second\n");
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY,
- &dki->issue.properties.purpose,
- &dki->issue.signature.eddsa_signature,
- &dki->issue.properties.master.eddsa_pub))
- {
- fprintf (stderr,
- "Denomination key for `%s' has invalid signature\n",
- alias);
- return GNUNET_SYSERR;
- }
- GNUNET_CRYPTO_rsa_public_key_hash (dki->denom_pub.rsa_public_key,
- &hc);
- if (0 != GNUNET_memcmp (&hc,
- &dki->issue.properties.denom_hash))
- {
- fprintf (stderr,
- "Public key for `%s' does not match signature\n",
- alias);
- return GNUNET_SYSERR;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Denomination key `%s' (%s) is valid\n",
- alias,
- GNUNET_h2s (&hc));
- TALER_amount_ntoh (&value,
- &dki->issue.properties.value);
- if ( (GNUNET_OK ==
- TALER_amount_cmp_currency (&print_dk_amount,
- &value)) &&
- (0 ==
- TALER_amount_cmp (&print_dk_amount,
- &value)) )
- {
- char *dh;
- struct GNUNET_TIME_Absolute start;
-
- start = GNUNET_TIME_absolute_ntoh (dki->issue.properties.start);
- dh = GNUNET_STRINGS_data_to_string_alloc (&dki->issue.properties.denom_hash,
- sizeof (struct GNUNET_HashCode));
- /* output start time first for easy numeric sorting, then
- the denomination hash, and finally the human-readable start time */
- printf ("%020llu %s %s\n",
- (unsigned long long) start.abs_value_us,
- dh,
- GNUNET_STRINGS_absolute_time_to_string (start));
- GNUNET_free (dh);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Check denomination keys.
- *
- * @return #GNUNET_OK if the keys are OK
- * #GNUNET_NO if not
- */
-static int
-exchange_denomkeys_check ()
-{
- struct TALER_MasterPublicKeyP master_public_key_from_cfg;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_data (kcfg,
- "exchange",
- "master_public_key",
- &master_public_key_from_cfg,
- sizeof (struct
- GNUNET_CRYPTO_EddsaPublicKey)))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "master_public_key");
- return GNUNET_NO;
- }
- if (0 > TALER_EXCHANGEDB_denomination_keys_iterate (exchange_directory,
- &denomkeys_iter,
- NULL))
- return GNUNET_NO;
- return GNUNET_OK;
-}
-
-
-/**
- * Main function that will be run.
- *
- * @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;
- kcfg = cfg;
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (kcfg,
- "exchange",
- "KEYDIR",
- &exchange_directory))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "KEYDIR");
- global_ret = 1;
- return;
- }
-
- if ( (GNUNET_OK != exchange_signkeys_check ()) ||
- (GNUNET_OK != exchange_denomkeys_check ()) )
- {
- global_ret = 1;
- return;
- }
-}
-
-
-/**
- * The main function of the keyup tool
- *
- * @param argc number of arguments from the command line
- * @param argv command line arguments
- * @return 0 ok, 1 on error
- */
-int
-main (int argc,
- char *const *argv)
-{
- const struct GNUNET_GETOPT_CommandLineOption options[] = {
- TALER_getopt_get_amount ('i',
- "denomination-info-hash",
- "AMOUNT",
- "print full denomination hashes of all denominations with the given AMOUNT value",
- &print_dk_amount),
- GNUNET_GETOPT_OPTION_END
- };
-
- /* 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 */
- (void) TALER_project_data_default ();
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-exchange-keycheck",
- "WARNING",
- NULL));
- if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc, argv,
- "taler-exchange-keycheck",
- "Check keys of the exchange for validity",
- options,
- &run, NULL))
- return 1;
- return global_ret;
-
-}
-
-
-/* end of taler-exchange-keycheck.c */
diff --git a/src/exchange-tools/taler-exchange-keyup.c b/src/exchange-tools/taler-exchange-keyup.c
deleted file mode 100644
index f4d389752..000000000
--- a/src/exchange-tools/taler-exchange-keyup.c
+++ /dev/null
@@ -1,1519 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2020 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 taler-exchange-keyup.c
- * @brief Update the exchange's keys for coins and online signing keys,
- * using the exchange's offline master key.
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#include <platform.h>
-#include "taler_exchangedb_lib.h"
-
-
-/**
- * When generating filenames from a cryptographic hash, we do not use all 512
- * bits but cut off after this number of characters (in base32-encoding).
- * Base32 is 5 bit per character, and given that we have very few coin types,
- * at 100 bits the chance of collision (by accident over such a tiny set) is
- * negligible. (Also, some file-systems do not support very long file names.)
- */
-#define HASH_CUTOFF 20
-
-
-GNUNET_NETWORK_STRUCT_BEGIN
-
-/**
- * Struct with all of the meta data about a denomination. Hashed
- * to generate a unique directory name per coin type.
- */
-struct DenominationNBOP
-{
- /**
- * How long are the signatures legally valid?
- */
- struct GNUNET_TIME_RelativeNBO duration_legal;
-
- /**
- * How long can the coins be spend?
- */
- struct GNUNET_TIME_RelativeNBO duration_spend;
-
- /**
- * How long can coins be withdrawn (generated)?
- */
- struct GNUNET_TIME_RelativeNBO duration_withdraw;
-
- /**
- * What is the value of each coin?
- */
- struct TALER_AmountNBO value;
-
- /**
- * What is the fee charged for withdrawal?
- */
- struct TALER_AmountNBO fee_withdraw;
-
- /**
- * What is the fee charged for deposits?
- */
- struct TALER_AmountNBO fee_deposit;
-
- /**
- * What is the fee charged for melting?
- */
- struct TALER_AmountNBO fee_refresh;
-
- /**
- * What is the fee charged for refunds?
- */
- struct TALER_AmountNBO fee_refund;
-
- /**
- * Key size (in NBO).
- */
- uint32_t rsa_keysize;
-};
-
-GNUNET_NETWORK_STRUCT_END
-
-/**
- * Set of all of the parameters that characterize a denomination.
- */
-struct DenominationParameters
-{
-
- /**
- * How long are the signatures legally valid? Should be
- * significantly larger than @e duration_spend (i.e. years).
- */
- struct GNUNET_TIME_Relative duration_legal;
-
- /**
- * How long can the coins be spend? Should be significantly
- * larger than @e duration_withdraw (i.e. years).
- */
- struct GNUNET_TIME_Relative duration_spend;
-
- /**
- * How long can coins be withdrawn (generated)? Should be small
- * enough to limit how many coins will be signed into existence with
- * the same key, but large enough to still provide a reasonable
- * anonymity set.
- */
- struct GNUNET_TIME_Relative duration_withdraw;
-
- /**
- * What is the value of each coin?
- */
- struct TALER_Amount value;
-
- /**
- * What is the fee charged for withdrawal?
- */
- struct TALER_Amount fee_withdraw;
-
- /**
- * What is the fee charged for deposits?
- */
- struct TALER_Amount fee_deposit;
-
- /**
- * What is the fee charged for melting?
- */
- struct TALER_Amount fee_refresh;
-
- /**
- * What is the fee charged for refunds?
- */
- struct TALER_Amount fee_refund;
-
- /**
- * Time at which this coin is supposed to become valid.
- */
- struct GNUNET_TIME_Absolute anchor;
-
- /**
- * Length of the RSA key (in bits).
- */
- uint32_t rsa_keysize;
-};
-
-
-/**
- * How much should coin creation (@e duration_withdraw) duration overlap
- * with the next denomination? Basically, the starting time of two
- * denominations is always @e duration_withdraw - #duration_overlap apart.
- */
-static struct GNUNET_TIME_Relative duration_overlap;
-
-/**
- * The configured currency.
- */
-static char *currency;
-
-/**
- * Filename of the master private key.
- */
-static char *masterkeyfile;
-
-/**
- * Filename where to write denomination key signing
- * requests for the auditor (optional, can be NULL).
- */
-static char *auditorrequestfile;
-
-/**
- * Handle for writing the output for the auditor.
- */
-static FILE *auditor_output_file;
-
-/**
- * Director of the exchange, containing the keys.
- */
-static char *exchange_directory;
-
-/**
- * Directory where we should write the wire transfer fee structure.
- */
-static char *feedir;
-
-/**
- * Handle to the exchange's configuration
- */
-static const struct GNUNET_CONFIGURATION_Handle *kcfg;
-
-/**
- * Time when the key update is executed.
- * Either the actual current time, or a pretended time.
- */
-static struct GNUNET_TIME_Absolute now;
-
-/**
- * The time for the key update, as passed by the user
- * on the command line.
- */
-static struct GNUNET_TIME_Absolute now_tmp;
-
-/**
- * Master private key of the exchange.
- */
-static struct TALER_MasterPrivateKeyP master_priv;
-
-/**
- * Master public key of the exchange.
- */
-static struct TALER_MasterPublicKeyP master_public_key;
-
-/**
- * Until what time do we provide keys?
- */
-static struct GNUNET_TIME_Absolute lookahead_sign_stamp;
-
-/**
- * Largest duration for spending of any key.
- */
-static struct GNUNET_TIME_Relative max_duration_spend;
-
-/**
- * Revoke denomination key identified by this hash (if non-zero).
- */
-static struct GNUNET_HashCode revoke_dkh;
-
-/**
- * Which RSA key size should we use for replacement keys after revocation?
- * (Useful because maybe that's the one option one might usefully want to
- * change when replacing a key.)
- */
-static unsigned int replacement_key_size = 2048;
-
-/**
- * Return value from main().
- */
-static int global_ret;
-
-
-#include "key-helper.c"
-
-/**
- * Hash the data defining a denomination type. Exclude information that may
- * not be the same for all instances of the denomination's type (i.e. the
- * anchor, overlap).
- *
- * @param p denomination parameters to convert to a hash
- * @param[out] hash set to the hash matching @a p
- */
-static void
-hash_denomination_parameters (const struct DenominationParameters *p,
- struct GNUNET_HashCode *hash)
-{
- struct DenominationNBOP p_nbo;
-
- memset (&p_nbo,
- 0,
- sizeof (struct DenominationNBOP));
- p_nbo.duration_spend = GNUNET_TIME_relative_hton (p->duration_spend);
- p_nbo.duration_legal = GNUNET_TIME_relative_hton (p->duration_legal);
- p_nbo.duration_withdraw = GNUNET_TIME_relative_hton (p->duration_withdraw);
- TALER_amount_hton (&p_nbo.value,
- &p->value);
- TALER_amount_hton (&p_nbo.fee_withdraw,
- &p->fee_withdraw);
- TALER_amount_hton (&p_nbo.fee_deposit,
- &p->fee_deposit);
- TALER_amount_hton (&p_nbo.fee_refresh,
- &p->fee_refresh);
- TALER_amount_hton (&p_nbo.fee_refund,
- &p->fee_refund);
- p_nbo.rsa_keysize = htonl (p->rsa_keysize);
- GNUNET_CRYPTO_hash (&p_nbo,
- sizeof (struct DenominationNBOP),
- hash);
-}
-
-
-/**
- * Obtain the name of the directory we should use to store denominations of
- * the given type. The directory name has the format
- * "$EXCHANGEDIR/$VALUE/$HASH/" where "$VALUE" represents the value of the
- * coins and "$HASH" encodes all of the denomination's parameters, generating
- * a unique string for each type of denomination. Note that the "$HASH"
- * includes neither the absolute creation time nor the key of the
- * denomination, thus the files in the subdirectory really just refer to the
- * same type of denominations, not the same denomination.
- *
- * @param p denomination parameters to convert to a directory name
- * @return directory name (valid until next call to this function)
- */
-static const char *
-get_denomination_dir (const struct DenominationParameters *p)
-{
- static char dir[4096];
- struct GNUNET_HashCode hash;
- char *hash_str;
- char *val_str;
-
- hash_denomination_parameters (p,
- &hash);
- hash_str = GNUNET_STRINGS_data_to_string_alloc (&hash,
- sizeof (struct
- GNUNET_HashCode));
- GNUNET_assert (NULL != hash_str);
- GNUNET_assert (HASH_CUTOFF <= strlen (hash_str) + 1);
- hash_str[HASH_CUTOFF] = 0;
-
- val_str = TALER_amount_to_string (&p->value);
- GNUNET_assert (NULL != val_str);
- for (size_t i = 0; i < strlen (val_str); i++)
- if ( (':' == val_str[i]) ||
- ('.' == val_str[i]) )
- val_str[i] = '_';
-
- GNUNET_snprintf (dir,
- sizeof (dir),
- "%s" DIR_SEPARATOR_STR TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS
- DIR_SEPARATOR_STR "%s-%s",
- exchange_directory,
- val_str,
- hash_str);
- GNUNET_free (hash_str);
- GNUNET_free (val_str);
- return dir;
-}
-
-
-/**
- * Obtain the name of the file we would use to store the key
- * information for a denomination of the given type @a p and validity
- * start time @a start
- *
- * @param p parameters for the denomination
- * @param start when would the denomination begin to be issued
- * @return name of the file to use for this denomination
- * (valid until next call to this function)
- */
-static const char *
-get_denomination_type_file (const struct DenominationParameters *p,
- struct GNUNET_TIME_Absolute start)
-{
- static char filename[4096];
- const char *dir;
-
- dir = get_denomination_dir (p);
- GNUNET_snprintf (filename,
- sizeof (filename),
- "%s" DIR_SEPARATOR_STR "%llu",
- dir,
- (unsigned long long) start.abs_value_us);
- return filename;
-}
-
-
-/**
- * Get the latest key file from a past run of the key generation
- * tool. Used to calculate the starting time for the keys we
- * generate during this invocation. This function is used to
- * handle both signing keys and denomination keys, as in both cases
- * the filenames correspond to the timestamps we need.
- *
- * @param cls closure, a `struct GNUNET_TIME_Absolute *`, updated
- * to contain the highest timestamp (below #now)
- * that was found
- * @param filename complete filename (absolute path)
- * @return #GNUNET_OK (to continue to iterate)
- */
-static int
-get_anchor_iter (void *cls,
- const char *filename)
-{
- struct GNUNET_TIME_Absolute *anchor = cls;
- struct GNUNET_TIME_Absolute stamp;
- const char *base;
- char *end = NULL;
- long long int bval;
-
- base = GNUNET_STRINGS_get_short_name (filename);
- bval = strtoll (base,
- &end,
- 10);
- if ( (NULL == end) ||
- (0 != *end) ||
- (0 > bval) )
- {
- fprintf (stderr,
- "Ignoring unexpected file `%s'.\n",
- filename);
- return GNUNET_OK;
- }
- stamp.abs_value_us = (uint64_t) bval;
- *anchor = GNUNET_TIME_absolute_max (stamp,
- *anchor);
- return GNUNET_OK;
-}
-
-
-/**
- * Get the timestamp where the first new key should be generated.
- * Relies on correctly named key files (as we do not parse them,
- * but just look at the filenames to "guess" at their contents).
- *
- * @param dir directory that should contain the existing keys
- * @param duration how long is one key valid (for signing)?
- * @param overlap what's the overlap between the keys validity period?
- * @param[out] anchor the timestamp where the first new key should be generated
- */
-static void
-get_anchor (const char *dir,
- struct GNUNET_TIME_Relative duration,
- struct GNUNET_TIME_Relative overlap,
- struct GNUNET_TIME_Absolute *anchor)
-{
- GNUNET_assert (0 == duration.rel_value_us % 1000000);
- GNUNET_assert (0 == overlap.rel_value_us % 1000000);
- if (GNUNET_YES !=
- GNUNET_DISK_directory_test (dir,
- GNUNET_YES))
- {
- *anchor = now;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No existing keys found, starting with fresh key set.\n");
- return;
- }
- *anchor = GNUNET_TIME_UNIT_ZERO_ABS;
- if (-1 ==
- GNUNET_DISK_directory_scan (dir,
- &get_anchor_iter,
- anchor))
- {
- *anchor = now;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No existing keys found, starting with fresh key set.\n");
- return;
- }
-
- if ((GNUNET_TIME_absolute_add (*anchor,
- duration)).abs_value_us < now.abs_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Existing keys are way too old, starting with fresh key set.\n");
- *anchor = now;
- }
- else if (anchor->abs_value_us != now.abs_value_us)
- {
- *anchor = GNUNET_TIME_absolute_add (*anchor,
- duration);
- *anchor = GNUNET_TIME_absolute_subtract (*anchor,
- overlap);
- }
-
- /* anchor is now the stamp where we need to create a new key */
-}
-
-
-/**
- * Create a exchange signing key (for signing exchange messages, not for
- * signing coins) and assert its correctness by signing it with the master
- * key.
- *
- * @param start start time of the validity period for the key
- * @param duration how long should the key be valid
- * @param end when do all signatures by this key expire
- * @param[out] pi set to the signing key information
- */
-static void
-create_signkey_issue_priv (
- struct GNUNET_TIME_Absolute start,
- struct GNUNET_TIME_Relative duration,
- struct GNUNET_TIME_Absolute end,
- struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP *pi)
-{
- struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
- struct TALER_ExchangeSigningKeyValidityPS *issue = &pi->issue;
-
- priv = GNUNET_CRYPTO_eddsa_key_create ();
- pi->signkey_priv.eddsa_priv = *priv;
- GNUNET_free (priv);
- issue->master_public_key = master_public_key;
- issue->start = GNUNET_TIME_absolute_hton (start);
- issue->expire = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (start,
- duration));
- issue->end = GNUNET_TIME_absolute_hton (end);
- GNUNET_CRYPTO_eddsa_key_get_public (&pi->signkey_priv.eddsa_priv,
- &issue->signkey_pub.eddsa_pub);
- issue->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY);
- issue->purpose.size = htonl (sizeof (struct
- TALER_ExchangeSigningKeyValidityPS));
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (&master_priv.eddsa_priv,
- &issue->purpose,
- &pi->master_sig.eddsa_signature));
-}
-
-
-/**
- * Generate signing keys starting from the last key found to
- * the lookahead time.
- *
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
- */
-static int
-exchange_keys_update_signkeys (void)
-{
- struct GNUNET_TIME_Relative signkey_duration;
- struct GNUNET_TIME_Relative legal_duration;
- struct GNUNET_TIME_Absolute anchor;
- char *signkey_dir;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (kcfg,
- "exchange",
- "SIGNKEY_DURATION",
- &signkey_duration))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "SIGNKEY_DURATION");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (kcfg,
- "exchange",
- "LEGAL_DURATION",
- &legal_duration))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "LEGAL_DURATION",
- "fails to specify valid timeframe");
- return GNUNET_SYSERR;
- }
- if (signkey_duration.rel_value_us > legal_duration.rel_value_us)
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "LEGAL_DURATION",
- "Value given for LEGAL_DURATION must be longer than value for SIGNKEY_DURATION");
- return GNUNET_SYSERR;
- }
- GNUNET_TIME_round_rel (&signkey_duration);
- GNUNET_asprintf (&signkey_dir,
- "%s" DIR_SEPARATOR_STR TALER_EXCHANGEDB_DIR_SIGNING_KEYS,
- exchange_directory);
- /* make sure the directory exists */
- if (GNUNET_OK !=
- GNUNET_DISK_directory_create (signkey_dir))
- {
- fprintf (stderr,
- "Failed to create signing key directory\n");
- GNUNET_free (signkey_dir);
- return GNUNET_SYSERR;
- }
-
- get_anchor (signkey_dir,
- signkey_duration,
- GNUNET_TIME_UNIT_ZERO /* no overlap for signing keys */,
- &anchor);
- GNUNET_free (signkey_dir);
-
- while (anchor.abs_value_us < lookahead_sign_stamp.abs_value_us)
- {
- struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP signkey_issue;
- struct GNUNET_TIME_Absolute end;
-
- end = GNUNET_TIME_absolute_add (anchor,
- legal_duration);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Generating signing key for %s.\n",
- GNUNET_STRINGS_absolute_time_to_string (anchor));
- create_signkey_issue_priv (anchor,
- signkey_duration,
- end,
- &signkey_issue);
- if (GNUNET_OK !=
- TALER_EXCHANGEDB_signing_key_write (exchange_directory,
- anchor,
- &signkey_issue))
- return GNUNET_SYSERR;
- anchor = GNUNET_TIME_absolute_add (anchor,
- signkey_duration);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Parse configuration for denomination type parameters. Also determines
- * our anchor by looking at the existing denominations of the same type.
- *
- * @param ct section in the configuration file giving the denomination type parameters
- * @param[out] params set to the denomination parameters from the configuration
- * @return #GNUNET_OK on success, #GNUNET_SYSERR if the configuration is invalid
- */
-static int
-get_denomination_type_params (const char *ct,
- struct DenominationParameters *params)
-{
- const char *dir;
- unsigned long long rsa_keysize;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (kcfg,
- ct,
- "DURATION_WITHDRAW",
- &params->duration_withdraw))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ct,
- "DURATION_WITHDRAW");
- return GNUNET_SYSERR;
- }
- GNUNET_TIME_round_rel (&params->duration_withdraw);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (kcfg,
- ct,
- "DURATION_SPEND",
- &params->duration_spend))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ct,
- "DURATION_SPEND");
- return GNUNET_SYSERR;
- }
- GNUNET_TIME_round_rel (&params->duration_spend);
- max_duration_spend = GNUNET_TIME_relative_max (max_duration_spend,
- params->duration_spend);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (kcfg,
- ct,
- "DURATION_LEGAL",
- &params->duration_legal))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ct,
- "DURATION_LEGAL");
- return GNUNET_SYSERR;
- }
- GNUNET_TIME_round_rel (&params->duration_legal);
- if (duration_overlap.rel_value_us >=
- params->duration_withdraw.rel_value_us)
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchangedb",
- "DURATION_OVERLAP",
- "Value given for DURATION_OVERLAP must be smaller than value for DURATION_WITHDRAW!");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_number (kcfg,
- ct,
- "rsa_keysize",
- &rsa_keysize))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ct,
- "rsa_keysize");
- return GNUNET_SYSERR;
- }
- if ( (rsa_keysize > 4 * 2048) ||
- (rsa_keysize < 1024) )
- {
- fprintf (stderr,
- "Given RSA keysize %llu outside of permitted range\n",
- rsa_keysize);
- return GNUNET_SYSERR;
- }
- params->rsa_keysize = (unsigned int) rsa_keysize;
- if (GNUNET_OK !=
- TALER_config_get_amount (kcfg,
- ct,
- "VALUE",
- &params->value))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ct,
- "VALUE");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_config_get_amount (kcfg,
- ct,
- "FEE_WITHDRAW",
- &params->fee_withdraw))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ct,
- "FEE_WITHDRAW");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_config_get_amount (kcfg,
- ct,
- "FEE_DEPOSIT",
- &params->fee_deposit))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ct,
- "FEE_DEPOSIT");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_config_get_amount (kcfg,
- ct,
- "FEE_REFRESH",
- &params->fee_refresh))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ct,
- "FEE_REFRESH");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_config_get_amount (kcfg,
- ct,
- "fee_refund",
- &params->fee_refund))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ct,
- "FEE_REFUND");
- return GNUNET_SYSERR;
- }
-
- dir = get_denomination_dir (params);
- get_anchor (dir,
- params->duration_withdraw,
- duration_overlap,
- &params->anchor);
-
- /**
- * The "anchor" is merely the latest denom key filename
- * converted to a GNUnet absolute time.
- */
-
- return GNUNET_OK;
-}
-
-
-/**
- * Initialize the private and public key information structure for
- * signing coins into existence. Generates the private signing key
- * and signes it together with the denomination's meta data using the master
- * signing key.
- *
- * @param params parameters used to initialize the @a dki
- * @param[out] dki initialized according to @a params
- */
-static void
-create_denomkey_issue (
- const struct DenominationParameters *params,
- struct TALER_EXCHANGEDB_DenominationKey *dki)
-{
- dki->denom_priv.rsa_private_key
- = GNUNET_CRYPTO_rsa_private_key_create (params->rsa_keysize);
- GNUNET_assert (NULL != dki->denom_priv.rsa_private_key);
- dki->denom_pub.rsa_public_key
- = GNUNET_CRYPTO_rsa_private_key_get_public (
- dki->denom_priv.rsa_private_key);
- GNUNET_CRYPTO_rsa_public_key_hash (dki->denom_pub.rsa_public_key,
- &dki->issue.properties.denom_hash);
- dki->issue.properties.master = master_public_key;
- dki->issue.properties.start = GNUNET_TIME_absolute_hton (params->anchor);
- dki->issue.properties.expire_withdraw =
- GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor,
- params->
- duration_withdraw));
- dki->issue.properties.expire_deposit =
- GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor,
- params->duration_spend));
- dki->issue.properties.expire_legal =
- GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor,
- params->duration_legal));
- TALER_amount_hton (&dki->issue.properties.value,
- &params->value);
- TALER_amount_hton (&dki->issue.properties.fee_withdraw,
- &params->fee_withdraw);
- TALER_amount_hton (&dki->issue.properties.fee_deposit,
- &params->fee_deposit);
- TALER_amount_hton (&dki->issue.properties.fee_refresh,
- &params->fee_refresh);
- TALER_amount_hton (&dki->issue.properties.fee_refund,
- &params->fee_refund);
- dki->issue.properties.purpose.purpose
- = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY);
- dki->issue.properties.purpose.size
- = htonl (sizeof (struct TALER_DenominationKeyValidityPS));
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (&master_priv.eddsa_priv,
- &dki->issue.properties.purpose,
- &dki->issue.signature.eddsa_signature));
-}
-
-
-/**
- * Write the @a denomkey_issue to file @a dkf and also (if applicable)
- * dump the properties to the #auditor_output_file.
- *
- * @param dkf where to write the @a denomkey_issue
- * @param denomkey_issue data to write
- * @return #GNUNET_OK on success
- */
-static int
-write_denomkey_issue (
- const char *dkf,
- const struct TALER_EXCHANGEDB_DenominationKey *denomkey_issue)
-{
- if (GNUNET_OK !=
- TALER_EXCHANGEDB_denomination_key_write (dkf,
- denomkey_issue))
- {
- fprintf (stderr,
- "Failed to write denomination key information to file `%s'.\n",
- dkf);
- return GNUNET_SYSERR;
- }
- if ( (NULL != auditor_output_file) &&
- (1 !=
- fwrite (&denomkey_issue->issue.properties,
- sizeof (struct TALER_DenominationKeyValidityPS),
- 1,
- auditor_output_file)) )
- {
- fprintf (stderr,
- "Failed to write denomination key information to %s: %s\n",
- auditorrequestfile,
- strerror (errno));
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Generate new denomination signing keys for the denomination type of the given @a
- * denomination_alias.
- *
- * @param cls a `int *`, to be set to #GNUNET_SYSERR on failure
- * @param denomination_alias name of the denomination's section in the configuration
- */
-static void
-exchange_keys_update_denominationtype (void *cls,
- const char *denomination_alias)
-{
- int *ret = cls;
- struct DenominationParameters p;
- const char *dkf;
- struct TALER_EXCHANGEDB_DenominationKey denomkey_issue;
-
- if (0 != strncasecmp (denomination_alias,
- "coin_",
- strlen ("coin_")))
- return; /* not a denomination type definition */
- if (GNUNET_OK !=
- get_denomination_type_params (denomination_alias,
- &p))
- {
- *ret = GNUNET_SYSERR;
- return;
- }
- /* p has the right anchor now = latest denom filename converted to time. */
- if (GNUNET_OK !=
- GNUNET_DISK_directory_create (get_denomination_dir (&p)))
- {
- *ret = GNUNET_SYSERR;
- return;
- }
-
- while (p.anchor.abs_value_us < lookahead_sign_stamp.abs_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Future time not covered yet for type `%s': %s\n",
- denomination_alias,
- GNUNET_STRINGS_relative_time_to_string
- (GNUNET_TIME_absolute_get_difference (p.anchor,
- lookahead_sign_stamp),
- GNUNET_NO));
- dkf = get_denomination_type_file (&p,
- p.anchor);
- GNUNET_break (GNUNET_YES !=
- GNUNET_DISK_file_test (dkf));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Generating denomination key for type `%s', start %s at %s\n",
- denomination_alias,
- GNUNET_STRINGS_absolute_time_to_string (p.anchor),
- dkf);
- create_denomkey_issue (&p,
- &denomkey_issue);
- *ret = write_denomkey_issue (dkf,
- &denomkey_issue);
- GNUNET_CRYPTO_rsa_private_key_free (
- denomkey_issue.denom_priv.rsa_private_key);
- GNUNET_CRYPTO_rsa_public_key_free (denomkey_issue.denom_pub.rsa_public_key);
- if (GNUNET_OK != *ret)
- return; /* stop loop, hard error */
- p.anchor = GNUNET_TIME_absolute_add (p.anchor,
- p.duration_withdraw);
- p.anchor = GNUNET_TIME_absolute_subtract (p.anchor,
- duration_overlap);
- }
-}
-
-
-/**
- * Update all of the denomination keys of the exchange.
- *
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
- */
-static int
-exchange_keys_update_denomkeys (void)
-{
- int ok;
-
- ok = GNUNET_OK;
- GNUNET_CONFIGURATION_iterate_sections (kcfg,
- &exchange_keys_update_denominationtype,
- &ok);
- return ok;
-}
-
-
-/**
- * Sign @a af with @a priv
- *
- * @param[in,out] af fee structure to sign
- * @param method name of the wire method for which we sign
- * @param priv private key to use for signing
- */
-static void
-sign_af (struct TALER_EXCHANGEDB_AggregateFees *af,
- const char *method,
- const struct GNUNET_CRYPTO_EddsaPrivateKey *priv)
-{
- struct TALER_MasterWireFeePS wf;
-
- TALER_EXCHANGEDB_fees_2_wf (method,
- af,
- &wf);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (priv,
- &wf.purpose,
- &af->master_sig.eddsa_signature));
-}
-
-
-/**
- * Output the wire fee structure. Must be run after #max_duration_spend
- * was initialized.
- *
- * @param cls pointer to `int`, set to #GNUNET_SYSERR on error
- * @param wiremethod method to write fees for
- */
-static void
-create_wire_fee_for_method (void *cls,
- const char *wiremethod)
-{
- int *ret = cls;
- struct TALER_EXCHANGEDB_AggregateFees *af_head;
- struct TALER_EXCHANGEDB_AggregateFees *af_tail;
- unsigned int year;
- struct GNUNET_TIME_Absolute last_date;
- struct GNUNET_TIME_Absolute start_date;
- struct GNUNET_TIME_Absolute end_date;
- char yearstr[12];
- char *fn;
- char *section;
-
- if (GNUNET_OK != *ret)
- return;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Setting up wire fees for `%s'\n",
- wiremethod);
- last_date = GNUNET_TIME_absolute_add (lookahead_sign_stamp,
- max_duration_spend);
- GNUNET_asprintf (&section,
- "fees-%s",
- wiremethod);
- GNUNET_asprintf (&fn,
- "%s/%s.fee",
- feedir,
- wiremethod);
- af_head = NULL;
- af_tail = NULL;
- year = GNUNET_TIME_get_current_year ();
- start_date = GNUNET_TIME_year_to_time (year);
- while (start_date.abs_value_us < last_date.abs_value_us)
- {
- struct TALER_EXCHANGEDB_AggregateFees *af;
- char *opt;
-
- GNUNET_snprintf (yearstr,
- sizeof (yearstr),
- "%u",
- year);
- end_date = GNUNET_TIME_year_to_time (year + 1);
- af = GNUNET_new (struct TALER_EXCHANGEDB_AggregateFees);
- af->start_date = start_date;
- af->end_date = end_date;
-
- /* handle wire fee */
- GNUNET_asprintf (&opt,
- "wire-fee-%u",
- year);
- if ( (GNUNET_OK !=
- TALER_config_get_amount (kcfg,
- section,
- opt,
- &af->wire_fee)) ||
- (0 != strcasecmp (currency,
- af->wire_fee.currency)) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid or missing amount in `%s' under `%s'\n",
- wiremethod,
- opt);
- *ret = GNUNET_SYSERR;
- GNUNET_free (opt);
- break;
- }
- GNUNET_free (opt);
-
- /* handle closing fee */
- GNUNET_asprintf (&opt,
- "closing-fee-%u",
- year);
- if ( (GNUNET_OK !=
- TALER_config_get_amount (kcfg,
- section,
- opt,
- &af->closing_fee)) ||
- (0 != strcasecmp (currency,
- af->closing_fee.currency)) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid or missing amount in `%s' under `%s'\n",
- wiremethod,
- opt);
- *ret = GNUNET_SYSERR;
- GNUNET_free (opt);
- break;
- }
-
- GNUNET_free (opt);
- sign_af (af,
- wiremethod,
- &master_priv.eddsa_priv);
- if (NULL == af_tail)
- af_head = af;
- else
- af_tail->next = af;
- af_tail = af;
- start_date = end_date;
- year++;
- }
- if ( (GNUNET_OK == *ret) &&
- (GNUNET_OK !=
- TALER_EXCHANGEDB_fees_write (fn,
- wiremethod,
- af_head)) )
- *ret = GNUNET_SYSERR;
- GNUNET_free (section);
- GNUNET_free (fn);
- TALER_EXCHANGEDB_fees_free (af_head);
-}
-
-
-/**
- * Output the wire fee structure. Must be run after #max_duration_spend
- * was initialized.
- *
- * @param cls pointer to `int`, set to #GNUNET_SYSERR on error
- * @param ai information about enabled accounts
- */
-static void
-create_wire_fee_by_account (void *cls,
- const struct TALER_EXCHANGEDB_AccountInfo *ai)
-{
- int *ret = cls;
-
- if (GNUNET_NO == ai->credit_enabled)
- return;
- /* We may call this function repeatedly for the same method
- if there are multiple accounts with plugins using the
- same method, but except for some minor performance loss,
- this is harmless. */
- create_wire_fee_for_method (ret,
- ai->method);
-}
-
-
-/**
- * Output the wire fee structure. Must be run after #max_duration_spend
- * was initialized.
- *
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
- */
-static int
-create_wire_fees (void)
-{
- int ret;
-
- ret = GNUNET_OK;
- TALER_EXCHANGEDB_find_accounts (kcfg,
- &create_wire_fee_by_account,
- &ret);
- return ret;
-}
-
-
-/**
- * Check if the denomination that we just revoked is currently active,
- * and if so, generate a replacement key.
- *
- * @param cls closure with the revoked denomination key hash, a `struct GNUNET_HashCode *`
- * @param alias coin alias
- * @param dki the denomination key
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-static int
-check_revocation_regeneration (
- void *cls,
- const char *alias,
- const struct TALER_EXCHANGEDB_DenominationKey *dki)
-{
- const struct GNUNET_HashCode *denom_hash = cls;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Absolute withdraw_end;
-
- (void) alias;
- if (0 !=
- GNUNET_memcmp (denom_hash,
- &dki->issue.properties.denom_hash))
- return GNUNET_OK; /* does not match */
- now = GNUNET_TIME_absolute_get ();
- withdraw_end = GNUNET_TIME_absolute_ntoh (
- dki->issue.properties.expire_withdraw);
- if (now.abs_value_us >= withdraw_end.abs_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Revoked denomination key has expired, no need to create a replacement\n");
- return GNUNET_NO;
- }
-
- {
- struct GNUNET_TIME_Absolute anchor
- = GNUNET_TIME_absolute_ntoh (dki->issue.properties.start);
- struct TALER_EXCHANGEDB_DenominationKey dki_new;
- const char *dkf;
- int ret;
- struct DenominationParameters dp = {
- .duration_legal
- = GNUNET_TIME_absolute_get_difference
- (anchor,
- GNUNET_TIME_absolute_ntoh (dki->issue.properties.expire_legal)),
- .duration_spend
- = GNUNET_TIME_absolute_get_difference
- (anchor,
- GNUNET_TIME_absolute_ntoh (dki->issue.properties.expire_deposit)),
- .duration_withdraw
- = GNUNET_TIME_absolute_get_difference
- (anchor,
- GNUNET_TIME_absolute_ntoh (dki->issue.properties.expire_withdraw)),
- .anchor = anchor,
- .rsa_keysize = replacement_key_size
- };
- char *dkfi;
-
- TALER_amount_ntoh (&dp.value,
- &dki->issue.properties.value);
- TALER_amount_ntoh (&dp.fee_withdraw,
- &dki->issue.properties.fee_withdraw);
- TALER_amount_ntoh (&dp.fee_deposit,
- &dki->issue.properties.fee_deposit);
- TALER_amount_ntoh (&dp.fee_refresh,
- &dki->issue.properties.fee_refresh);
- TALER_amount_ntoh (&dp.fee_refund,
- &dki->issue.properties.fee_refund);
-
- /* find unused file name for revocation file by appending -%u */
- dkf = get_denomination_type_file (&dp,
- dp.anchor);
- for (unsigned int i = 1;; i++)
- {
- GNUNET_asprintf (&dkfi,
- "%s-%u",
- dkf,
- i);
- if (GNUNET_YES != GNUNET_DISK_file_test (dkfi))
- break;
- GNUNET_free (dkfi);
- }
-
- create_denomkey_issue (&dp,
- &dki_new);
- ret = write_denomkey_issue (dkfi,
- &dki_new);
- GNUNET_free (dkfi);
- GNUNET_CRYPTO_rsa_private_key_free (dki_new.denom_priv.rsa_private_key);
- GNUNET_CRYPTO_rsa_public_key_free (dki_new.denom_pub.rsa_public_key);
- if (GNUNET_OK != ret)
- return GNUNET_SYSERR;
- }
-
- return GNUNET_NO;
-}
-
-
-/**
- * Revoke the denomination key matching @a hc and request /recoup to be
- * initiated.
- *
- * @param hc denomination key hash to revoke
- * @return #GNUNET_OK on success,
- * #GNUNET_NO if @a hc was not found
- * #GNUNET_SYSERR on error
- */
-static int
-revoke_denomination (const struct GNUNET_HashCode *hc)
-{
- {
- char *basedir;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (kcfg,
- "exchange",
- "REVOCATION_DIR",
- &basedir))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "REVOCATION_DIR");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGEDB_denomination_key_revoke (basedir,
- hc,
- &master_priv))
- {
- GNUNET_free (basedir);
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- GNUNET_free (basedir);
- }
-
- if (GNUNET_SYSERR ==
- TALER_EXCHANGEDB_denomination_keys_iterate (exchange_directory,
- &check_revocation_regeneration,
- (void *) hc))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Might have failed to generate replacement for revoked denomination key!\n");
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Main function that will be run.
- *
- * @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)
-{
- struct GNUNET_TIME_Relative lookahead_sign;
-
- (void) cls;
- (void) args;
- (void) cfgfile;
- kcfg = cfg;
- if (GNUNET_OK !=
- TALER_config_get_currency (cfg,
- &currency))
- {
- global_ret = 1;
- return;
- }
- if (now.abs_value_us != now_tmp.abs_value_us)
- {
- /* The user gave "--now", use it! */
- now = now_tmp;
- }
- GNUNET_TIME_round_abs (&now);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (kcfg,
- "exchangedb",
- "DURATION_OVERLAP",
- &duration_overlap))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchangedb",
- "DURATION_OVERLAP");
- global_ret = 1;
- return;
- }
- GNUNET_TIME_round_rel (&duration_overlap);
-
- if (NULL == feedir)
- {
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (kcfg,
- "exchangedb",
- "WIREFEE_BASE_DIR",
- &feedir))
- {
- fprintf (stderr,
- "Wire fee directory given neither in configuration nor on command-line\n");
- global_ret = 1;
- return;
- }
- }
- if (GNUNET_OK !=
- GNUNET_DISK_directory_create (feedir))
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "mkdir",
- feedir);
- global_ret = 1;
- return;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (kcfg,
- "exchange",
- "KEYDIR",
- &exchange_directory))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "KEYDIR");
- global_ret = 1;
- return;
- }
-
- if (GNUNET_OK !=
- get_and_check_master_key (kcfg,
- masterkeyfile,
- &master_priv))
- {
- global_ret = 1;
- return;
- }
- GNUNET_CRYPTO_eddsa_key_get_public (&master_priv.eddsa_priv,
- &master_public_key.eddsa_pub);
-
- if (NULL != auditorrequestfile)
- {
- auditor_output_file = fopen (auditorrequestfile,
- "w");
- if (NULL == auditor_output_file)
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "open (w)",
- auditorrequestfile);
- global_ret = 1;
- return;
- }
- }
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (kcfg,
- "exchange",
- "LOOKAHEAD_SIGN",
- &lookahead_sign))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "LOOKAHEAD_SIGN");
- global_ret = 1;
- return;
- }
- if (0 == lookahead_sign.rel_value_us)
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "LOOKAHEAD_SIGN",
- "must not be zero");
- global_ret = 1;
- return;
- }
- GNUNET_TIME_round_rel (&lookahead_sign);
- lookahead_sign_stamp = GNUNET_TIME_absolute_add (now,
- lookahead_sign);
-
-
- /* finally, do actual work */
- if (0 != GNUNET_is_zero (&revoke_dkh))
- {
- if (GNUNET_OK != revoke_denomination (&revoke_dkh))
- {
- global_ret = 1;
- return;
- }
- /* if we were invoked to revoke a key, let's not also generate
- new keys, as that might not be desired. */
- return;
- }
-
- if (NULL == auditor_output_file)
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Option `-o' missing. Hence, you will NOT be able to use an auditor with the generated keys!\n");
-
- if (GNUNET_OK != exchange_keys_update_signkeys ())
- {
- global_ret = 1;
- return;
- }
- if (GNUNET_OK != exchange_keys_update_denomkeys ())
- {
- global_ret = 1;
- return;
- }
- if (GNUNET_OK != create_wire_fees ())
- {
- global_ret = 1;
- return;
- }
-}
-
-
-/**
- * The main function of the taler-exchange-keyup tool. This tool is used to
- * create the signing and denomination keys for the exchange. It uses the
- * long-term offline private key and writes the (additional) key files to the
- * respective exchange directory (from where they can then be copied to the
- * online server). Note that we need (at least) the most recent generated
- * previous keys to align the validity periods.
- *
- * @param argc number of arguments from the command line
- * @param argv command line arguments
- * @return 0 ok, 1 on error
- */
-int
-main (int argc,
- char *const *argv)
-{
- struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_filename ('m',
- "master-key",
- "FILENAME",
- "master key file (private key)",
- &masterkeyfile),
- GNUNET_GETOPT_option_filename ('f',
- "feedir",
- "DIRNAME",
- "directory where to write wire transfer fee structure",
- &feedir),
- GNUNET_GETOPT_option_uint ('k',
- "replacement-keysize",
- "BITS",
- "when creating a replacement key in a revocation operation, which key size should be used for the new denomination key",
- &replacement_key_size),
- GNUNET_GETOPT_option_filename ('o',
- "output",
- "FILENAME",
- "auditor denomination key signing request file to create",
- &auditorrequestfile),
- GNUNET_GETOPT_option_base32_auto ('r',
- "revoke",
- "DKH",
- "revoke denomination key hash (DKH) and request wallets to initiate recoup",
- &revoke_dkh),
- GNUNET_GETOPT_option_timetravel ('T',
- "timetravel"),
- GNUNET_GETOPT_option_absolute_time ('t',
- "time",
- "TIMESTAMP",
- "pretend it is a different time for the update",
- &now_tmp),
- GNUNET_GETOPT_OPTION_END
- };
-
- /* 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 */
- (void) TALER_project_data_default ();
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-exchange-keyup",
- "WARNING",
- NULL));
- now = now_tmp = GNUNET_TIME_absolute_get ();
- if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc, argv,
- "taler-exchange-keyup",
- "Setup signing and denomination keys for a Taler exchange",
- options,
- &run, NULL))
- return 1;
- if (NULL != auditor_output_file)
- {
- GNUNET_assert (0 == fclose (auditor_output_file));
- auditor_output_file = NULL;
- }
- return global_ret;
-}
-
-
-/* end of taler-exchange-keyup.c */
diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c
new file mode 100644
index 000000000..1f10c55e3
--- /dev/null
+++ b/src/exchange-tools/taler-exchange-offline.c
@@ -0,0 +1,5497 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-2024 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 taler-exchange-offline.c
+ * @brief Support for operations involving the exchange's offline master key.
+ * @author Christian Grothoff
+ */
+#include <platform.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "taler_extensions.h"
+#include <regex.h>
+
+
+/**
+ * Name of the input for the 'sign' and 'show' operation.
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_INPUT_KEYS "exchange-input-keys-0"
+
+/**
+ * Name of the operation to 'disable auditor'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_DISABLE_AUDITOR "exchange-disable-auditor-0"
+
+/**
+ * Name of the operation to 'enable auditor'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_ENABLE_AUDITOR "exchange-enable-auditor-0"
+
+/**
+ * Name of the operation to 'enable wire'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_ENABLE_WIRE "exchange-enable-wire-0"
+
+/**
+ * Name of the operation to 'disable wire'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_DISABLE_WIRE "exchange-disable-wire-0"
+
+/**
+ * Name of the operation to set a 'wire-fee'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_SET_WIRE_FEE "exchange-set-wire-fee-0"
+
+/**
+ * Name of the operation to set a 'global-fee'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_SET_GLOBAL_FEE "exchange-set-global-fee-0"
+
+/**
+ * Name of the operation to 'upload' key signatures
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_UPLOAD_SIGS "exchange-upload-sigs-0"
+
+/**
+ * Name of the operation to 'revoke-denomination' key
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_REVOKE_DENOMINATION "exchange-revoke-denomination-0"
+
+/**
+ * Name of the operation to 'revoke-signkey'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_REVOKE_SIGNKEY "exchange-revoke-signkey-0"
+
+/**
+ * Show the offline signing key.
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' changes.
+ */
+#define OP_SETUP "exchange-setup-0"
+
+/**
+ * sign the enabled and configured extensions.
+ */
+#define OP_EXTENSIONS "exchange-extensions-0"
+
+/**
+ * Generate message to drain profits.
+ */
+#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().
+ */
+static struct TALER_MasterPrivateKeyP master_priv;
+
+/**
+ * Our private key, initialized in #load_offline_key().
+ */
+static struct TALER_MasterPublicKeyP master_pub;
+
+/**
+ * Our context for making HTTP requests.
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * Reschedule context for #ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * Handle to the exchange's configuration
+ */
+static const struct GNUNET_CONFIGURATION_Handle *kcfg;
+
+/**
+ * Age restriction configuration
+ */
+static bool ar_enabled = false;
+static struct TALER_AgeRestrictionConfig ar_config = {0};
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * Input to consume.
+ */
+static json_t *in;
+
+/**
+ * Array of actions to perform.
+ */
+static json_t *out;
+
+/**
+ * Currency we have configured.
+ */
+static char *currency;
+
+/**
+ * URL of the exchange we are interacting with
+ * as per our configuration.
+ */
+static char *CFG_exchange_url;
+
+/**
+ * A subcommand supported by this program.
+ */
+struct SubCommand
+{
+ /**
+ * Name of the command.
+ */
+ const char *name;
+
+ /**
+ * Help text for the command.
+ */
+ const char *help;
+
+ /**
+ * Function implementing the command.
+ *
+ * @param args subsequent command line arguments (char **)
+ */
+ void (*cb)(char *const *args);
+};
+
+
+/**
+ * Data structure for denomination revocation requests.
+ */
+struct DenomRevocationRequest
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct DenomRevocationRequest *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct DenomRevocationRequest *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *h;
+
+ /**
+ * Array index of the associated command.
+ */
+ size_t idx;
+};
+
+
+/**
+ * Data structure for signkey revocation requests.
+ */
+struct SignkeyRevocationRequest
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct SignkeyRevocationRequest *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct SignkeyRevocationRequest *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *h;
+
+ /**
+ * Array index of the associated command.
+ */
+ size_t idx;
+};
+
+
+/**
+ * Data structure for auditor add requests.
+ */
+struct AuditorAddRequest
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct AuditorAddRequest *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct AuditorAddRequest *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_EXCHANGE_ManagementAuditorEnableHandle *h;
+
+ /**
+ * Array index of the associated command.
+ */
+ size_t idx;
+};
+
+
+/**
+ * Data structure for auditor del requests.
+ */
+struct AuditorDelRequest
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct AuditorDelRequest *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct AuditorDelRequest *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_EXCHANGE_ManagementAuditorDisableHandle *h;
+
+ /**
+ * Array index of the associated command.
+ */
+ size_t idx;
+};
+
+
+/**
+ * Data structure for wire add requests.
+ */
+struct WireAddRequest
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WireAddRequest *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WireAddRequest *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_EXCHANGE_ManagementWireEnableHandle *h;
+
+ /**
+ * Array index of the associated command.
+ */
+ size_t idx;
+};
+
+
+/**
+ * Data structure for wire del requests.
+ */
+struct WireDelRequest
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WireDelRequest *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WireDelRequest *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_EXCHANGE_ManagementWireDisableHandle *h;
+
+ /**
+ * Array index of the associated command.
+ */
+ size_t idx;
+};
+
+
+/**
+ * Data structure for announcing wire fees.
+ */
+struct WireFeeRequest
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WireFeeRequest *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WireFeeRequest *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_EXCHANGE_ManagementSetWireFeeHandle *h;
+
+ /**
+ * Array index of the associated command.
+ */
+ size_t idx;
+};
+
+
+/**
+ * Data structure for draining profits.
+ */
+struct DrainProfitsRequest
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct DrainProfitsRequest *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct DrainProfitsRequest *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_EXCHANGE_ManagementDrainProfitsHandle *h;
+
+ /**
+ * Array index of the associated command.
+ */
+ size_t idx;
+};
+
+
+/**
+ * Data structure for announcing global fees.
+ */
+struct GlobalFeeRequest
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct GlobalFeeRequest *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct GlobalFeeRequest *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *h;
+
+ /**
+ * Array index of the associated command.
+ */
+ size_t idx;
+};
+
+
+/**
+ * Ongoing /keys request.
+ */
+struct UploadKeysRequest
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct UploadKeysRequest *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct UploadKeysRequest *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_EXCHANGE_ManagementPostKeysHandle *h;
+
+ /**
+ * Operation index.
+ */
+ size_t idx;
+};
+
+/**
+ * Ongoing /management/extensions request.
+ */
+struct UploadExtensionsRequest
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct UploadExtensionsRequest *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct UploadExtensionsRequest *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_EXCHANGE_ManagementPostExtensionsHandle *h;
+
+ /**
+ * Operation index.
+ */
+ size_t idx;
+};
+
+
+/**
+ * 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.
+ */
+static struct GNUNET_SCHEDULER_Task *nxt;
+
+/**
+ * Handle for #do_download.
+ */
+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.
+ */
+static struct DenomRevocationRequest *drr_head;
+
+/**
+ * Active denomiantion revocation requests.
+ */
+static struct DenomRevocationRequest *drr_tail;
+
+/**
+ * Active signkey revocation requests.
+ */
+static struct SignkeyRevocationRequest *srr_head;
+
+/**
+ * Active signkey revocation requests.
+ */
+static struct SignkeyRevocationRequest *srr_tail;
+
+/**
+ * Active auditor add requests.
+ */
+static struct AuditorAddRequest *aar_head;
+
+/**
+ * Active auditor add requests.
+ */
+static struct AuditorAddRequest *aar_tail;
+
+/**
+ * Active auditor del requests.
+ */
+static struct AuditorDelRequest *adr_head;
+
+/**
+ * Active auditor del requests.
+ */
+static struct AuditorDelRequest *adr_tail;
+
+/**
+ * Active wire add requests.
+ */
+static struct WireAddRequest *war_head;
+
+/**
+ * Active wire add requests.
+ */
+static struct WireAddRequest *war_tail;
+
+/**
+ * Active wire del requests.
+ */
+static struct WireDelRequest *wdr_head;
+
+/**
+ * Active wire del requests.
+ */
+static struct WireDelRequest *wdr_tail;
+
+/**
+ * Active wire fee requests.
+ */
+static struct WireFeeRequest *wfr_head;
+
+/**
+ * Active wire fee requests.
+ */
+static struct WireFeeRequest *wfr_tail;
+
+/**
+ * Active global fee requests.
+ */
+static struct GlobalFeeRequest *gfr_head;
+
+/**
+ * Active global fee requests.
+ */
+static struct GlobalFeeRequest *gfr_tail;
+
+/**
+ * Active keys upload requests.
+ */
+static struct UploadKeysRequest *ukr_head;
+
+/**
+ * Active keys upload requests.
+ */
+static struct UploadKeysRequest *ukr_tail;
+
+/**
+ * Active extensions upload requests.
+ */
+static struct UploadExtensionsRequest *uer_head;
+
+/**
+ * Active extensions upload requests.
+ */
+static struct UploadExtensionsRequest *uer_tail;
+
+/**
+ * Active drain profits requests.
+ */
+struct DrainProfitsRequest *dpr_head;
+
+/**
+ * Active drain profits requests.
+ */
+static struct DrainProfitsRequest *dpr_tail;
+
+
+/**
+ * Shutdown task. Invoked when the application is being terminated.
+ *
+ * @param cls NULL
+ */
+static void
+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;
+
+ while (NULL != (drr = drr_head))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Aborting incomplete denomination revocation #%u\n",
+ (unsigned int) drr->idx);
+ TALER_EXCHANGE_management_revoke_denomination_key_cancel (drr->h);
+ GNUNET_CONTAINER_DLL_remove (drr_head,
+ drr_tail,
+ drr);
+ GNUNET_free (drr);
+ }
+ }
+ {
+ struct SignkeyRevocationRequest *srr;
+
+ while (NULL != (srr = srr_head))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Aborting incomplete signkey revocation #%u\n",
+ (unsigned int) srr->idx);
+ TALER_EXCHANGE_management_revoke_signing_key_cancel (srr->h);
+ GNUNET_CONTAINER_DLL_remove (srr_head,
+ srr_tail,
+ srr);
+ GNUNET_free (srr);
+ }
+ }
+
+ {
+ struct AuditorAddRequest *aar;
+
+ while (NULL != (aar = aar_head))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Aborting incomplete auditor add #%u\n",
+ (unsigned int) aar->idx);
+ TALER_EXCHANGE_management_enable_auditor_cancel (aar->h);
+ GNUNET_CONTAINER_DLL_remove (aar_head,
+ aar_tail,
+ aar);
+ GNUNET_free (aar);
+ }
+ }
+ {
+ struct AuditorDelRequest *adr;
+
+ while (NULL != (adr = adr_head))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Aborting incomplete auditor del #%u\n",
+ (unsigned int) adr->idx);
+ TALER_EXCHANGE_management_disable_auditor_cancel (adr->h);
+ GNUNET_CONTAINER_DLL_remove (adr_head,
+ adr_tail,
+ adr);
+ GNUNET_free (adr);
+ }
+ }
+ {
+ struct WireAddRequest *war;
+
+ while (NULL != (war = war_head))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Aborting incomplete wire add #%u\n",
+ (unsigned int) war->idx);
+ TALER_EXCHANGE_management_enable_wire_cancel (war->h);
+ GNUNET_CONTAINER_DLL_remove (war_head,
+ war_tail,
+ war);
+ GNUNET_free (war);
+ }
+ }
+ {
+ struct WireDelRequest *wdr;
+
+ while (NULL != (wdr = wdr_head))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Aborting incomplete wire del #%u\n",
+ (unsigned int) wdr->idx);
+ TALER_EXCHANGE_management_disable_wire_cancel (wdr->h);
+ GNUNET_CONTAINER_DLL_remove (wdr_head,
+ wdr_tail,
+ wdr);
+ GNUNET_free (wdr);
+ }
+ }
+ {
+ struct WireFeeRequest *wfr;
+
+ while (NULL != (wfr = wfr_head))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Aborting incomplete wire fee #%u\n",
+ (unsigned int) wfr->idx);
+ TALER_EXCHANGE_management_set_wire_fees_cancel (wfr->h);
+ GNUNET_CONTAINER_DLL_remove (wfr_head,
+ wfr_tail,
+ wfr);
+ GNUNET_free (wfr);
+ }
+ }
+ {
+ struct GlobalFeeRequest *gfr;
+
+ while (NULL != (gfr = gfr_head))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Aborting incomplete global fee #%u\n",
+ (unsigned int) gfr->idx);
+ TALER_EXCHANGE_management_set_global_fees_cancel (gfr->h);
+ GNUNET_CONTAINER_DLL_remove (gfr_head,
+ gfr_tail,
+ gfr);
+ GNUNET_free (gfr);
+ }
+ }
+ {
+ struct UploadKeysRequest *ukr;
+
+ while (NULL != (ukr = ukr_head))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Aborting incomplete key signature upload #%u\n",
+ (unsigned int) ukr->idx);
+ TALER_EXCHANGE_post_management_keys_cancel (ukr->h);
+ GNUNET_CONTAINER_DLL_remove (ukr_head,
+ ukr_tail,
+ ukr);
+ GNUNET_free (ukr);
+ }
+ }
+ {
+ struct UploadExtensionsRequest *uer;
+
+ while (NULL != (uer = uer_head))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Aborting incomplete extensions signature upload #%u\n",
+ (unsigned int) uer->idx);
+ TALER_EXCHANGE_management_post_extensions_cancel (uer->h);
+ GNUNET_CONTAINER_DLL_remove (uer_head,
+ uer_tail,
+ uer);
+ GNUNET_free (uer);
+ }
+ }
+
+ {
+ struct DrainProfitsRequest *dpr;
+
+ while (NULL != (dpr = dpr_head))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Aborting incomplete drain profits request #%u\n",
+ (unsigned int) dpr->idx);
+ TALER_EXCHANGE_management_drain_profits_cancel (dpr->h);
+ GNUNET_CONTAINER_DLL_remove (dpr_head,
+ dpr_tail,
+ dpr);
+ GNUNET_free (dpr);
+ }
+ }
+
+ if (NULL != out)
+ {
+ if (EXIT_SUCCESS == global_ret)
+ json_dumpf (out,
+ stdout,
+ JSON_INDENT (2));
+ json_decref (out);
+ out = NULL;
+ }
+ if (NULL != in)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Input not consumed!\n");
+ json_decref (in);
+ in = NULL;
+ }
+ if (NULL != nxt)
+ {
+ GNUNET_SCHEDULER_cancel (nxt);
+ nxt = NULL;
+ }
+ if (NULL != mgkh)
+ {
+ TALER_EXCHANGE_get_management_keys_cancel (mgkh);
+ mgkh = NULL;
+ }
+ if (NULL != ctx)
+ {
+ GNUNET_CURL_fini (ctx);
+ ctx = NULL;
+ }
+ if (NULL != rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (rc);
+ rc = NULL;
+ }
+}
+
+
+/**
+ * Test if we should shut down because all tasks are done.
+ */
+static void
+test_shutdown (void)
+{
+ if ( (NULL == drr_head) &&
+ (NULL == par_head) &&
+ (NULL == asr_head) &&
+ (NULL == srr_head) &&
+ (NULL == aar_head) &&
+ (NULL == adr_head) &&
+ (NULL == war_head) &&
+ (NULL == wdr_head) &&
+ (NULL == wfr_head) &&
+ (NULL == gfr_head) &&
+ (NULL == ukr_head) &&
+ (NULL == uer_head) &&
+ (NULL == dpr_head) &&
+ (NULL == mgkh) &&
+ (NULL == nxt) )
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * Function to continue processing the next command.
+ *
+ * @param cls must be a `char *const*` with the array of
+ * command-line arguments to process next
+ */
+static void
+work (void *cls);
+
+
+/**
+ * Function to schedule job to process the next command.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+next (char *const *args)
+{
+ GNUNET_assert (NULL == nxt);
+ if (NULL == args[0])
+ {
+ test_shutdown ();
+ return;
+ }
+ nxt = GNUNET_SCHEDULER_add_now (&work,
+ (void *) args);
+}
+
+
+/**
+ * Add an operation to the #out JSON array for processing later.
+ *
+ * @param op_name name of the operation
+ * @param op_value values for the operation (consumed)
+ */
+static void
+output_operation (const char *op_name,
+ json_t *op_value)
+{
+ json_t *action;
+
+ GNUNET_break (NULL != op_value);
+ if (NULL == out)
+ {
+ out = json_array ();
+ GNUNET_assert (NULL != out);
+ }
+ action = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ op_name),
+ GNUNET_JSON_pack_object_steal ("arguments",
+ op_value));
+ GNUNET_assert (0 ==
+ json_array_append_new (out,
+ action));
+}
+
+
+/**
+ * Information about a subroutine for an upload.
+ */
+struct UploadHandler
+{
+ /**
+ * Key to trigger this subroutine.
+ */
+ const char *key;
+
+ /**
+ * Function implementing an upload.
+ *
+ * @param exchange_url URL of the exchange
+ * @param idx index of the operation we are performing
+ * @param value arguments to drive the upload.
+ */
+ void (*cb)(const char *exchange_url,
+ size_t idx,
+ const json_t *value);
+
+};
+
+
+/**
+ * Load the offline key (if not yet done). Triggers shutdown on failure.
+ *
+ * @param do_create #GNUNET_YES if the key may be created
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+load_offline_key (int do_create)
+{
+ static bool done;
+ int ret;
+ char *fn;
+
+ if (done)
+ return GNUNET_OK;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (kcfg,
+ "exchange-offline",
+ "MASTER_PRIV_FILE",
+ &fn))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange-offline",
+ "MASTER_PRIV_FILE");
+ test_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_YES !=
+ GNUNET_DISK_file_test (fn))
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange master private key `%s' does not exist yet, creating it!\n",
+ fn);
+ ret = GNUNET_CRYPTO_eddsa_key_from_file (fn,
+ do_create,
+ &master_priv.eddsa_priv);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize master key from file `%s': %s\n",
+ fn,
+ "could not create file");
+ GNUNET_free (fn);
+ test_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (fn);
+ GNUNET_CRYPTO_eddsa_key_get_public (&master_priv.eddsa_priv,
+ &master_pub.eddsa_pub);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Using master public key %s\n",
+ TALER_B2S (&master_pub));
+ done = true;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with information about the post revocation operation result.
+ *
+ * @param cls closure with a `struct DenomRevocationRequest`
+ * @param dr response data
+ */
+static void
+denom_revocation_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementRevokeDenominationResponse *dr)
+{
+ struct DenomRevocationRequest *drr = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &dr->hr;
+
+ 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) drr->idx,
+ hr->http_status,
+ hr->hint,
+ TALER_JSON_get_error_hint (hr->reply));
+ global_ret = EXIT_FAILURE;
+ }
+ GNUNET_CONTAINER_DLL_remove (drr_head,
+ drr_tail,
+ drr);
+ GNUNET_free (drr);
+ test_shutdown ();
+}
+
+
+/**
+ * Upload denomination revocation request data.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_denom_revocation (const char *exchange_url,
+ size_t idx,
+ const json_t *value)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct DenomRevocationRequest *drr;
+ const char *err_name;
+ unsigned int err_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input for denomination revocation: %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) idx);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ drr = GNUNET_new (struct DenomRevocationRequest);
+ drr->idx = idx;
+ drr->h =
+ TALER_EXCHANGE_management_revoke_denomination_key (ctx,
+ exchange_url,
+ &h_denom_pub,
+ &master_sig,
+ &denom_revocation_cb,
+ drr);
+ GNUNET_CONTAINER_DLL_insert (drr_head,
+ drr_tail,
+ drr);
+}
+
+
+/**
+ * Function called with information about the post revocation operation result.
+ *
+ * @param cls closure with a `struct SignkeyRevocationRequest`
+ * @param sr response data
+ */
+static void
+signkey_revocation_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse *sr)
+{
+ struct SignkeyRevocationRequest *srr = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &sr->hr;
+
+ 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) srr->idx,
+ hr->http_status,
+ hr->hint,
+ TALER_JSON_get_error_hint (hr->reply));
+ global_ret = EXIT_FAILURE;
+ }
+ GNUNET_CONTAINER_DLL_remove (srr_head,
+ srr_tail,
+ srr);
+ GNUNET_free (srr);
+ test_shutdown ();
+}
+
+
+/**
+ * Upload signkey revocation request data.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_signkey_revocation (const char *exchange_url,
+ size_t idx,
+ const json_t *value)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct SignkeyRevocationRequest *srr;
+ const char *err_name;
+ unsigned int err_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input for signkey revocation: %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) idx);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ srr = GNUNET_new (struct SignkeyRevocationRequest);
+ srr->idx = idx;
+ srr->h =
+ TALER_EXCHANGE_management_revoke_signing_key (ctx,
+ exchange_url,
+ &exchange_pub,
+ &master_sig,
+ &signkey_revocation_cb,
+ srr);
+ GNUNET_CONTAINER_DLL_insert (srr_head,
+ srr_tail,
+ srr);
+}
+
+
+/**
+ * Function called with information about the post auditor add operation result.
+ *
+ * @param cls closure with a `struct AuditorAddRequest`
+ * @param mer response data
+ */
+static void
+auditor_add_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementAuditorEnableResponse *mer)
+{
+ struct AuditorAddRequest *aar = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &mer->hr;
+
+ 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) aar->idx,
+ hr->http_status,
+ TALER_ErrorCode_get_hint (hr->ec),
+ hr->hint);
+ global_ret = EXIT_FAILURE;
+ }
+ GNUNET_CONTAINER_DLL_remove (aar_head,
+ aar_tail,
+ aar);
+ GNUNET_free (aar);
+ test_shutdown ();
+}
+
+
+/**
+ * Upload auditor add data.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_auditor_add (const char *exchange_url,
+ size_t idx,
+ const json_t *value)
+{
+ struct TALER_MasterSignatureP master_sig;
+ const char *auditor_url;
+ const char *auditor_name;
+ struct GNUNET_TIME_Timestamp start_time;
+ struct TALER_AuditorPublicKeyP auditor_pub;
+ struct AuditorAddRequest *aar;
+ const char *err_name;
+ unsigned int err_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_web_url ("auditor_url",
+ &auditor_url),
+ GNUNET_JSON_spec_string ("auditor_name",
+ &auditor_name),
+ GNUNET_JSON_spec_timestamp ("validity_start",
+ &start_time),
+ GNUNET_JSON_spec_fixed_auto ("auditor_pub",
+ &auditor_pub),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input for adding auditor: %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) idx);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ aar = GNUNET_new (struct AuditorAddRequest);
+ aar->idx = idx;
+ aar->h =
+ TALER_EXCHANGE_management_enable_auditor (ctx,
+ exchange_url,
+ &auditor_pub,
+ auditor_url,
+ auditor_name,
+ start_time,
+ &master_sig,
+ &auditor_add_cb,
+ aar);
+ GNUNET_CONTAINER_DLL_insert (aar_head,
+ aar_tail,
+ aar);
+}
+
+
+/**
+ * Function called with information about the post auditor del operation result.
+ *
+ * @param cls closure with a `struct AuditorDelRequest`
+ * @param mdr response data
+ */
+static void
+auditor_del_cb (void *cls,
+ const struct
+ TALER_EXCHANGE_ManagementAuditorDisableResponse *mdr)
+{
+ struct AuditorDelRequest *adr = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &mdr->hr;
+
+ 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) adr->idx,
+ hr->http_status,
+ TALER_ErrorCode_get_hint (hr->ec),
+ hr->hint);
+ global_ret = EXIT_FAILURE;
+ }
+ GNUNET_CONTAINER_DLL_remove (adr_head,
+ adr_tail,
+ adr);
+ GNUNET_free (adr);
+ test_shutdown ();
+}
+
+
+/**
+ * Upload auditor del data.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_auditor_del (const char *exchange_url,
+ size_t idx,
+ const json_t *value)
+{
+ struct TALER_AuditorPublicKeyP auditor_pub;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_TIME_Timestamp end_time;
+ struct AuditorDelRequest *adr;
+ const char *err_name;
+ unsigned int err_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("auditor_pub",
+ &auditor_pub),
+ GNUNET_JSON_spec_timestamp ("validity_end",
+ &end_time),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input to disable auditor: %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) idx);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ adr = GNUNET_new (struct AuditorDelRequest);
+ adr->idx = idx;
+ adr->h =
+ TALER_EXCHANGE_management_disable_auditor (ctx,
+ exchange_url,
+ &auditor_pub,
+ end_time,
+ &master_sig,
+ &auditor_del_cb,
+ adr);
+ GNUNET_CONTAINER_DLL_insert (adr_head,
+ adr_tail,
+ adr);
+}
+
+
+/**
+ * Function called with information about the post wire add operation result.
+ *
+ * @param cls closure with a `struct WireAddRequest`
+ * @param wer response data
+ */
+static void
+wire_add_cb (void *cls,
+ const struct TALER_EXCHANGE_ManagementWireEnableResponse *wer)
+{
+ struct WireAddRequest *war = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &wer->hr;
+
+ 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) war->idx,
+ hr->http_status,
+ TALER_ErrorCode_get_hint (hr->ec),
+ hr->hint);
+ global_ret = EXIT_FAILURE;
+ }
+ GNUNET_CONTAINER_DLL_remove (war_head,
+ war_tail,
+ war);
+ GNUNET_free (war);
+ test_shutdown ();
+}
+
+
+/**
+ * Upload wire add data.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_wire_add (const char *exchange_url,
+ size_t idx,
+ const json_t *value)
+{
+ struct TALER_MasterSignatureP master_sig_add;
+ struct TALER_MasterSignatureP master_sig_wire;
+ const char *payto_uri;
+ struct GNUNET_TIME_Timestamp start_time;
+ struct WireAddRequest *war;
+ const char *err_name;
+ const char *conversion_url = NULL;
+ const char *bank_label = NULL;
+ int64_t priority = 0;
+ const json_t *debit_restrictions;
+ const json_t *credit_restrictions;
+ unsigned int err_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &payto_uri),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("conversion_url",
+ &conversion_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("bank_label",
+ &bank_label),
+ NULL),
+ GNUNET_JSON_spec_int64 ("priority",
+ &priority),
+ GNUNET_JSON_spec_array_const ("debit_restrictions",
+ &debit_restrictions),
+ GNUNET_JSON_spec_array_const ("credit_restrictions",
+ &credit_restrictions),
+ GNUNET_JSON_spec_timestamp ("validity_start",
+ &start_time),
+ GNUNET_JSON_spec_fixed_auto ("master_sig_add",
+ &master_sig_add),
+ GNUNET_JSON_spec_fixed_auto ("master_sig_wire",
+ &master_sig_wire),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input for adding wire account: %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) idx);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ {
+ char *wire_method;
+
+ wire_method = TALER_payto_get_method (payto_uri);
+ if (NULL == wire_method)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "payto:// URI `%s' is malformed\n",
+ payto_uri);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_free (wire_method);
+ }
+ {
+ char *msg = TALER_payto_validate (payto_uri);
+
+ if (NULL != msg)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "payto URI is malformed: %s\n",
+ msg);
+ GNUNET_free (msg);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ }
+ war = GNUNET_new (struct WireAddRequest);
+ war->idx = idx;
+ war->h =
+ TALER_EXCHANGE_management_enable_wire (ctx,
+ exchange_url,
+ payto_uri,
+ conversion_url,
+ debit_restrictions,
+ credit_restrictions,
+ start_time,
+ &master_sig_add,
+ &master_sig_wire,
+ bank_label,
+ priority,
+ &wire_add_cb,
+ war);
+ GNUNET_CONTAINER_DLL_insert (war_head,
+ war_tail,
+ war);
+}
+
+
+/**
+ * Function called with information about the post wire del operation result.
+ *
+ * @param cls closure with a `struct WireDelRequest`
+ * @param wdres response data
+ */
+static void
+wire_del_cb (void *cls,
+ const struct TALER_EXCHANGE_ManagementWireDisableResponse *wdres)
+{
+ struct WireDelRequest *wdr = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &wdres->hr;
+
+ 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) wdr->idx,
+ hr->http_status,
+ TALER_ErrorCode_get_hint (hr->ec),
+ hr->hint);
+ global_ret = EXIT_FAILURE;
+ }
+ GNUNET_CONTAINER_DLL_remove (wdr_head,
+ wdr_tail,
+ wdr);
+ GNUNET_free (wdr);
+ test_shutdown ();
+}
+
+
+/**
+ * Upload wire del data.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_wire_del (const char *exchange_url,
+ size_t idx,
+ const json_t *value)
+{
+ struct TALER_MasterSignatureP master_sig;
+ const char *payto_uri;
+ struct GNUNET_TIME_Timestamp end_time;
+ struct WireDelRequest *wdr;
+ const char *err_name;
+ unsigned int err_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &payto_uri),
+ GNUNET_JSON_spec_timestamp ("validity_end",
+ &end_time),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input to disable wire account: %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) idx);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ wdr = GNUNET_new (struct WireDelRequest);
+ wdr->idx = idx;
+ wdr->h =
+ TALER_EXCHANGE_management_disable_wire (ctx,
+ exchange_url,
+ payto_uri,
+ end_time,
+ &master_sig,
+ &wire_del_cb,
+ wdr);
+ GNUNET_CONTAINER_DLL_insert (wdr_head,
+ wdr_tail,
+ wdr);
+}
+
+
+/**
+ * Function called with information about the post wire fee operation result.
+ *
+ * @param cls closure with a `struct WireFeeRequest`
+ * @param swr response data
+ */
+static void
+wire_fee_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementSetWireFeeResponse *swr)
+{
+ struct WireFeeRequest *wfr = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &swr->hr;
+
+ 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) wfr->idx,
+ hr->http_status,
+ TALER_ErrorCode_get_hint (hr->ec),
+ hr->hint);
+ global_ret = EXIT_FAILURE;
+ }
+ GNUNET_CONTAINER_DLL_remove (wfr_head,
+ wfr_tail,
+ wfr);
+ GNUNET_free (wfr);
+ test_shutdown ();
+}
+
+
+/**
+ * Upload wire fee.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_wire_fee (const char *exchange_url,
+ size_t idx,
+ const json_t *value)
+{
+ struct TALER_MasterSignatureP master_sig;
+ const char *wire_method;
+ struct WireFeeRequest *wfr;
+ const char *err_name;
+ unsigned int err_line;
+ struct TALER_WireFeeSet fees;
+ struct GNUNET_TIME_Timestamp start_time;
+ struct GNUNET_TIME_Timestamp end_time;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("wire_method",
+ &wire_method),
+ TALER_JSON_spec_amount ("wire_fee",
+ currency,
+ &fees.wire),
+ TALER_JSON_spec_amount ("closing_fee",
+ currency,
+ &fees.closing),
+ GNUNET_JSON_spec_timestamp ("start_time",
+ &start_time),
+ GNUNET_JSON_spec_timestamp ("end_time",
+ &end_time),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input to set wire fee: %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) idx);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ wfr = GNUNET_new (struct WireFeeRequest);
+ wfr->idx = idx;
+ wfr->h =
+ TALER_EXCHANGE_management_set_wire_fees (ctx,
+ exchange_url,
+ wire_method,
+ start_time,
+ end_time,
+ &fees,
+ &master_sig,
+ &wire_fee_cb,
+ wfr);
+ GNUNET_CONTAINER_DLL_insert (wfr_head,
+ wfr_tail,
+ wfr);
+}
+
+
+/**
+ * Function called with information about the post global fee operation result.
+ *
+ * @param cls closure with a `struct WireFeeRequest`
+ * @param gr response data
+ */
+static void
+global_fee_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementSetGlobalFeeResponse *gr)
+{
+ struct GlobalFeeRequest *gfr = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &gr->hr;
+
+ 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) gfr->idx,
+ hr->http_status,
+ TALER_ErrorCode_get_hint (hr->ec),
+ hr->hint);
+ global_ret = EXIT_FAILURE;
+ }
+ GNUNET_CONTAINER_DLL_remove (gfr_head,
+ gfr_tail,
+ gfr);
+ GNUNET_free (gfr);
+ test_shutdown ();
+}
+
+
+/**
+ * Upload global fee.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_global_fee (const char *exchange_url,
+ size_t idx,
+ const json_t *value)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct GlobalFeeRequest *gfr;
+ const char *err_name;
+ unsigned int err_line;
+ struct TALER_GlobalFeeSet fees;
+ struct GNUNET_TIME_Timestamp start_time;
+ struct GNUNET_TIME_Timestamp end_time;
+ struct GNUNET_TIME_Relative purse_timeout;
+ struct GNUNET_TIME_Relative history_expiration;
+ uint32_t purse_account_limit;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("history_fee",
+ currency,
+ &fees.history),
+ TALER_JSON_spec_amount ("account_fee",
+ currency,
+ &fees.account),
+ TALER_JSON_spec_amount ("purse_fee",
+ currency,
+ &fees.purse),
+ GNUNET_JSON_spec_relative_time ("purse_timeout",
+ &purse_timeout),
+ GNUNET_JSON_spec_relative_time ("history_expiration",
+ &history_expiration),
+ GNUNET_JSON_spec_uint32 ("purse_account_limit",
+ &purse_account_limit),
+ GNUNET_JSON_spec_timestamp ("start_time",
+ &start_time),
+ GNUNET_JSON_spec_timestamp ("end_time",
+ &end_time),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input to set wire fee: %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) idx);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ gfr = GNUNET_new (struct GlobalFeeRequest);
+ gfr->idx = idx;
+ gfr->h =
+ TALER_EXCHANGE_management_set_global_fees (ctx,
+ exchange_url,
+ start_time,
+ end_time,
+ &fees,
+ purse_timeout,
+ history_expiration,
+ purse_account_limit,
+ &master_sig,
+ &global_fee_cb,
+ gfr);
+ GNUNET_CONTAINER_DLL_insert (gfr_head,
+ gfr_tail,
+ gfr);
+}
+
+
+/**
+ * Function called with information about the drain profits operation.
+ *
+ * @param cls closure with a `struct DrainProfitsRequest`
+ * @param mdr response data
+ */
+static void
+drain_profits_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementDrainResponse *mdr)
+{
+ struct DrainProfitsRequest *dpr = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &mdr->hr;
+
+ 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) dpr->idx,
+ hr->http_status,
+ TALER_ErrorCode_get_hint (hr->ec),
+ hr->hint);
+ global_ret = EXIT_FAILURE;
+ }
+ GNUNET_CONTAINER_DLL_remove (dpr_head,
+ dpr_tail,
+ dpr);
+ GNUNET_free (dpr);
+ test_shutdown ();
+}
+
+
+/**
+ * Upload drain profit action.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for drain profits
+ */
+static void
+upload_drain (const char *exchange_url,
+ size_t idx,
+ const json_t *value)
+{
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct TALER_MasterSignatureP master_sig;
+ const char *err_name;
+ unsigned int err_line;
+ struct TALER_Amount amount;
+ struct GNUNET_TIME_Timestamp date;
+ const char *payto_uri;
+ const char *account_section;
+ struct DrainProfitsRequest *dpr;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("wtid",
+ &wtid),
+ TALER_JSON_spec_amount ("amount",
+ currency,
+ &amount),
+ GNUNET_JSON_spec_timestamp ("date",
+ &date),
+ GNUNET_JSON_spec_string ("account_section",
+ &account_section),
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &payto_uri),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input to drain profits: %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) idx);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ dpr = GNUNET_new (struct DrainProfitsRequest);
+ dpr->idx = idx;
+ dpr->h =
+ TALER_EXCHANGE_management_drain_profits (ctx,
+ exchange_url,
+ &wtid,
+ &amount,
+ date,
+ account_section,
+ payto_uri,
+ &master_sig,
+ &drain_profits_cb,
+ dpr);
+ GNUNET_CONTAINER_DLL_insert (dpr_head,
+ dpr_tail,
+ dpr);
+}
+
+
+/**
+ * Function called with information about the post upload keys operation result.
+ *
+ * @param cls closure with a `struct UploadKeysRequest`
+ * @param mr response data
+ */
+static void
+keys_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementPostKeysResponse *mr)
+{
+ struct UploadKeysRequest *ukr = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &mr->hr;
+
+ 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) ukr->idx,
+ hr->http_status,
+ TALER_ErrorCode_get_hint (hr->ec),
+ hr->hint);
+ global_ret = EXIT_FAILURE;
+ }
+ GNUNET_CONTAINER_DLL_remove (ukr_head,
+ ukr_tail,
+ ukr);
+ GNUNET_free (ukr);
+ test_shutdown ();
+}
+
+
+/**
+ * Upload (denomination and signing) key master signatures.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for POSTing keys
+ */
+static void
+upload_keys (const char *exchange_url,
+ size_t idx,
+ const json_t *value)
+{
+ struct TALER_EXCHANGE_ManagementPostKeysData pkd;
+ struct UploadKeysRequest *ukr;
+ const char *err_name;
+ unsigned int err_line;
+ const json_t *denom_sigs;
+ const json_t *signkey_sigs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("denom_sigs",
+ &denom_sigs),
+ GNUNET_JSON_spec_array_const ("signkey_sigs",
+ &signkey_sigs),
+ GNUNET_JSON_spec_end ()
+ };
+ bool ok = true;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input to 'upload': %s#%u (skipping)\n",
+ err_name,
+ err_line);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ pkd.num_sign_sigs = json_array_size (signkey_sigs);
+ pkd.num_denom_sigs = json_array_size (denom_sigs);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Uploading %u denomination and %u signing key signatures\n",
+ pkd.num_denom_sigs,
+ pkd.num_sign_sigs);
+ pkd.sign_sigs = GNUNET_new_array (
+ pkd.num_sign_sigs,
+ struct TALER_EXCHANGE_SigningKeySignature);
+ pkd.denom_sigs = GNUNET_new_array (
+ pkd.num_denom_sigs,
+ struct TALER_EXCHANGE_DenominationKeySignature);
+ for (unsigned int i = 0; i<pkd.num_sign_sigs; i++)
+ {
+ struct TALER_EXCHANGE_SigningKeySignature *ss = &pkd.sign_sigs[i];
+ json_t *val = json_array_get (signkey_sigs,
+ i);
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &ss->exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &ss->master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (val,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input for signkey validity: %s#%u at %u (aborting)\n",
+ err_name,
+ err_line,
+ i);
+ json_dumpf (val,
+ stderr,
+ JSON_INDENT (2));
+ ok = false;
+ }
+ }
+ for (unsigned int i = 0; i<pkd.num_denom_sigs; i++)
+ {
+ struct TALER_EXCHANGE_DenominationKeySignature *ds = &pkd.denom_sigs[i];
+ json_t *val = json_array_get (denom_sigs,
+ i);
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &ds->h_denom_pub),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &ds->master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (val,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input for denomination validity: %s#%u at %u (aborting)\n",
+ err_name,
+ err_line,
+ i);
+ json_dumpf (val,
+ stderr,
+ JSON_INDENT (2));
+ ok = false;
+ }
+ }
+
+ if (ok)
+ {
+ ukr = GNUNET_new (struct UploadKeysRequest);
+ ukr->idx = idx;
+ ukr->h =
+ TALER_EXCHANGE_post_management_keys (ctx,
+ exchange_url,
+ &pkd,
+ &keys_cb,
+ ukr);
+ GNUNET_CONTAINER_DLL_insert (ukr_head,
+ ukr_tail,
+ ukr);
+ }
+ else
+ {
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ }
+ GNUNET_free (pkd.sign_sigs);
+ GNUNET_free (pkd.denom_sigs);
+}
+
+
+/**
+ * Function called with information about the post upload extensions operation result.
+ *
+ * @param cls closure with a `struct UploadExtensionsRequest`
+ * @param er response data
+ */
+static void
+extensions_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementPostExtensionsResponse *er)
+{
+ struct UploadExtensionsRequest *uer = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &er->hr;
+
+ 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) uer->idx,
+ hr->http_status,
+ TALER_ErrorCode_get_hint (hr->ec),
+ hr->hint);
+ global_ret = EXIT_FAILURE;
+ }
+ GNUNET_CONTAINER_DLL_remove (uer_head,
+ uer_tail,
+ uer);
+ GNUNET_free (uer);
+ test_shutdown ();
+}
+
+
+/**
+ * Upload extension configuration
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for POSTing configurations of extensions
+ */
+static void
+upload_extensions (const char *exchange_url,
+ size_t idx,
+ const json_t *value)
+{
+ const json_t *extensions;
+ struct TALER_MasterSignatureP sig;
+ const char *err_name;
+ unsigned int err_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_object_const ("extensions",
+ &extensions),
+ GNUNET_JSON_spec_fixed_auto ("extensions_sig",
+ &sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ /* 1. Parse the signed extensions */
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input to set extensions: %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) idx);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ /* 2. Verify the signature */
+ {
+ struct TALER_ExtensionManifestsHashP h_manifests;
+
+ if (GNUNET_OK !=
+ TALER_JSON_extensions_manifests_hash (extensions,
+ &h_manifests))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "couldn't hash extensions' manifests\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ return;
+
+ if (GNUNET_OK != TALER_exchange_offline_extension_manifests_hash_verify (
+ &h_manifests,
+ &master_pub,
+ &sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "invalid signature for extensions\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+
+ /* 3. Upload the extensions */
+ {
+ struct TALER_EXCHANGE_ManagementPostExtensionsData ped = {
+ .extensions = extensions,
+ .extensions_sig = sig,
+ };
+ struct UploadExtensionsRequest *uer
+ = GNUNET_new (struct UploadExtensionsRequest);
+
+ uer->idx = idx;
+ uer->h = TALER_EXCHANGE_management_post_extensions (
+ ctx,
+ exchange_url,
+ &ped,
+ &extensions_cb,
+ uer);
+ GNUNET_CONTAINER_DLL_insert (uer_head,
+ uer_tail,
+ uer);
+ }
+}
+
+
+/**
+ * Function called with information about the add partner operation.
+ *
+ * @param cls closure with a `struct PartnerAddRequest`
+ * @param apr response data
+ */
+static void
+add_partner_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementAddPartnerResponse *apr)
+{
+ struct PartnerAddRequest *par = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &apr->hr;
+
+ 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),
+ TALER_JSON_spec_web_url ("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;
+ GNUNET_SCHEDULER_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 ar response data
+ */
+static void
+update_aml_officer_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse *ar)
+{
+ struct AmlStaffRequest *asr = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &ar->hr;
+
+ 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;
+ GNUNET_SCHEDULER_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.
+ *
+ * @param exchange_url base URL of the exchange to use
+ */
+static void
+trigger_upload (const char *exchange_url)
+{
+ struct UploadHandler uhs[] = {
+ {
+ .key = OP_REVOKE_DENOMINATION,
+ .cb = &upload_denom_revocation
+ },
+ {
+ .key = OP_REVOKE_SIGNKEY,
+ .cb = &upload_signkey_revocation
+ },
+ {
+ .key = OP_ENABLE_AUDITOR,
+ .cb = &upload_auditor_add
+ },
+ {
+ .key = OP_DISABLE_AUDITOR,
+ .cb = &upload_auditor_del
+ },
+ {
+ .key = OP_ENABLE_WIRE,
+ .cb = &upload_wire_add
+ },
+ {
+ .key = OP_DISABLE_WIRE,
+ .cb = &upload_wire_del
+ },
+ {
+ .key = OP_SET_WIRE_FEE,
+ .cb = &upload_wire_fee
+ },
+ {
+ .key = OP_SET_GLOBAL_FEE,
+ .cb = &upload_global_fee
+ },
+ {
+ .key = OP_UPLOAD_SIGS,
+ .cb = &upload_keys
+ },
+ {
+ .key = OP_DRAIN_PROFITS,
+ .cb = &upload_drain
+ },
+ {
+ .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
+ }
+ };
+ size_t index;
+ json_t *obj;
+
+ json_array_foreach (out, index, obj) {
+ bool found = false;
+ const char *key;
+ const json_t *value;
+
+ key = json_string_value (json_object_get (obj, "operation"));
+ value = json_object_get (obj, "arguments");
+ if (NULL == key)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Malformed JSON input\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ /* block of code that uses key and value */
+ for (unsigned int i = 0; NULL != uhs[i].key; i++)
+ {
+ if (0 == strcasecmp (key,
+ uhs[i].key))
+ {
+
+ found = true;
+ uhs[i].cb (exchange_url,
+ index,
+ value);
+ break;
+ }
+ }
+ if (! found)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Upload does not know how to handle `%s'\n",
+ key);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+}
+
+
+/**
+ * Upload operation result (signatures) to exchange.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_upload (char *const *args)
+{
+ (void) args;
+ if (NULL != in)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Downloaded data was not consumed, refusing upload\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if (NULL == out)
+ {
+ json_error_t err;
+
+ out = json_loadf (stdin,
+ JSON_REJECT_DUPLICATES,
+ &err);
+ if (NULL == out)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
+ err.text,
+ err.line,
+ err.source,
+ err.position);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ }
+ if (! json_is_array (out))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error: expected JSON array for `upload` command\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if ( (NULL == CFG_exchange_url) &&
+ (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (kcfg,
+ "exchange",
+ "BASE_URL",
+ &CFG_exchange_url)) )
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ trigger_upload (CFG_exchange_url);
+ json_decref (out);
+ out = NULL;
+}
+
+
+/**
+ * Revoke denomination key.
+ *
+ * @param args the array of command-line arguments to process next;
+ * args[0] must be the hash of the denomination key to revoke
+ */
+static void
+do_revoke_denomination_key (char *const *args)
+{
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_MasterSignatureP master_sig;
+
+ if (NULL != in)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Downloaded data was not consumed, refusing revocation\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if ( (NULL == args[0]) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &h_denom_pub,
+ sizeof (h_denom_pub))) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "You must specify a denomination key with this subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ return;
+ TALER_exchange_offline_denomination_revoke_sign (&h_denom_pub,
+ &master_priv,
+ &master_sig);
+ output_operation (OP_REVOKE_DENOMINATION,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &master_sig)));
+ next (args + 1);
+}
+
+
+/**
+ * Revoke signkey.
+ *
+ * @param args the array of command-line arguments to process next;
+ * args[0] must be the hash of the denomination key to revoke
+ */
+static void
+do_revoke_signkey (char *const *args)
+{
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_MasterSignatureP master_sig;
+
+ if (NULL != in)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Downloaded data was not consumed, refusing revocation\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if ( (NULL == args[0]) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &exchange_pub,
+ sizeof (exchange_pub))) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "You must specify an exchange signing key with this subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ return;
+ TALER_exchange_offline_signkey_revoke_sign (&exchange_pub,
+ &master_priv,
+ &master_sig);
+ output_operation (OP_REVOKE_SIGNKEY,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &master_sig)));
+ next (args + 1);
+}
+
+
+/**
+ * Add auditor.
+ *
+ * @param args the array of command-line arguments to process next;
+ * args[0] must be the auditor's public key, args[1] the auditor's
+ * API base URL, and args[2] the auditor's name.
+ */
+static void
+do_add_auditor (char *const *args)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct TALER_AuditorPublicKeyP auditor_pub;
+ struct GNUNET_TIME_Timestamp now;
+
+ if (NULL != in)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Downloaded data was not consumed, not adding auditor\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if ( (NULL == args[0]) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &auditor_pub,
+ sizeof (auditor_pub))) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "You must specify an auditor public key as first argument for this subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+
+ if ( (NULL == args[1]) ||
+ (0 != strncmp ("http",
+ args[1],
+ strlen ("http"))) ||
+ (NULL == args[2]) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "You must specify an auditor URI and auditor name as 2nd and 3rd arguments to this subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ return;
+ now = GNUNET_TIME_timestamp_get ();
+ TALER_exchange_offline_auditor_add_sign (&auditor_pub,
+ args[1],
+ now,
+ &master_priv,
+ &master_sig);
+ output_operation (OP_ENABLE_AUDITOR,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("auditor_url",
+ args[1]),
+ GNUNET_JSON_pack_string ("auditor_name",
+ args[2]),
+ GNUNET_JSON_pack_timestamp ("validity_start",
+ now),
+ GNUNET_JSON_pack_data_auto ("auditor_pub",
+ &auditor_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &master_sig)));
+ next (args + 3);
+}
+
+
+/**
+ * Disable auditor account.
+ *
+ * @param args the array of command-line arguments to process next;
+ * args[0] must be the hash of the denomination key to revoke
+ */
+static void
+do_del_auditor (char *const *args)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct TALER_AuditorPublicKeyP auditor_pub;
+ struct GNUNET_TIME_Timestamp now;
+
+ if (NULL != in)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Downloaded data was not consumed, not deleting auditor account\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if ( (NULL == args[0]) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &auditor_pub,
+ sizeof (auditor_pub))) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "You must specify an auditor public key as first argument for this subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ return;
+ now = GNUNET_TIME_timestamp_get ();
+ TALER_exchange_offline_auditor_del_sign (&auditor_pub,
+ now,
+ &master_priv,
+ &master_sig);
+ output_operation (OP_DISABLE_AUDITOR,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("auditor_pub",
+ &auditor_pub),
+ GNUNET_JSON_pack_timestamp ("validity_end",
+ now),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &master_sig)));
+ next (args + 1);
+}
+
+
+/**
+ * Parse account restriction.
+ *
+ * @param args the array of command-line arguments to process next
+ * @param[in,out] restrictions JSON array to update
+ * @return -1 on error, otherwise number of arguments from @a args that were used
+ */
+static int
+parse_restriction (char *const *args,
+ json_t *restrictions)
+{
+ if (NULL == args[0])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Restriction TYPE argument missing\n");
+ return -1;
+ }
+ if (0 == strcmp (args[0],
+ "deny"))
+ {
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ restrictions,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "deny"))));
+ return 1;
+ }
+ if (0 == strcmp (args[0],
+ "regex"))
+ {
+ json_t *i18n;
+ json_error_t err;
+
+ if ( (NULL == args[1]) ||
+ (NULL == args[2]) ||
+ (NULL == args[3]) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Mandatory arguments for restriction of type `regex' missing (REGEX, HINT, HINT-I18 required)\n");
+ return -1;
+ }
+ {
+ regex_t ex;
+
+ if (0 != regcomp (&ex,
+ args[1],
+ REG_NOSUB | REG_EXTENDED))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid regular expression `%s'\n",
+ args[1]);
+ return -1;
+ }
+ regfree (&ex);
+ }
+
+ i18n = json_loads (args[3],
+ JSON_REJECT_DUPLICATES,
+ &err);
+ if (NULL == i18n)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid JSON for restriction of type `regex': `%s` at %d\n",
+ args[3],
+ err.position);
+ return -1;
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ restrictions,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "regex"),
+ GNUNET_JSON_pack_string ("payto_regex",
+ args[1]),
+ GNUNET_JSON_pack_string ("human_hint",
+ args[2]),
+ GNUNET_JSON_pack_object_steal ("human_hint_i18n",
+ i18n)
+ )));
+ return 4;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Restriction TYPE `%s' unsupported\n",
+ args[0]);
+ return -1;
+}
+
+
+/**
+ * Add wire account.
+ *
+ * @param args the array of command-line arguments to process next;
+ * args[0] must be the hash of the denomination key to revoke
+ */
+static void
+do_add_wire (char *const *args)
+{
+ struct TALER_MasterSignatureP master_sig_add;
+ struct TALER_MasterSignatureP master_sig_wire;
+ struct GNUNET_TIME_Timestamp now;
+ const char *conversion_url = NULL;
+ const char *bank_label = NULL;
+ int64_t priority = 0;
+ json_t *debit_restrictions;
+ json_t *credit_restrictions;
+ unsigned int num_args = 1;
+
+ if (NULL != in)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Downloaded data was not consumed, not adding wire account\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if (NULL == args[0])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "You must specify a payto://-URI with this subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ return;
+ {
+ char *msg = TALER_payto_validate (args[0]);
+
+ if (NULL != msg)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "payto URI is malformed: %s\n",
+ msg);
+ GNUNET_free (msg);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ }
+ now = GNUNET_TIME_timestamp_get ();
+ {
+ char *wire_method;
+
+ wire_method = TALER_payto_get_method (args[0]);
+ if (NULL == wire_method)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "payto:// URI `%s' is malformed\n",
+ args[0]);
+ global_ret = EXIT_INVALIDARGUMENT;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_free (wire_method);
+ }
+ debit_restrictions = json_array ();
+ GNUNET_assert (NULL != debit_restrictions);
+ credit_restrictions = json_array ();
+ GNUNET_assert (NULL != credit_restrictions);
+ while (NULL != args[num_args])
+ {
+ if (0 == strcmp (args[num_args],
+ "conversion-url"))
+ {
+ num_args++;
+ conversion_url = args[num_args];
+ if (NULL == conversion_url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "'conversion-url' requires an argument\n");
+ global_ret = EXIT_INVALIDARGUMENT;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (debit_restrictions);
+ json_decref (credit_restrictions);
+ return;
+ }
+ if (! TALER_is_web_url (conversion_url))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "'conversion-url' must refer to HTTP(S) endpoint, `%s' is invalid\n",
+ conversion_url);
+ global_ret = EXIT_INVALIDARGUMENT;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (debit_restrictions);
+ json_decref (credit_restrictions);
+ return;
+ }
+ num_args++;
+ continue;
+ }
+ if (0 == strcmp (args[num_args],
+ "credit-restriction"))
+ {
+ int iret;
+
+ num_args++;
+ iret = parse_restriction (&args[num_args],
+ credit_restrictions);
+ if (iret <= 0)
+ {
+ global_ret = EXIT_INVALIDARGUMENT;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (debit_restrictions);
+ json_decref (credit_restrictions);
+ return;
+ }
+ num_args += iret;
+ continue;
+ }
+ if (0 == strcmp (args[num_args],
+ "debit-restriction"))
+ {
+ int iret;
+
+ num_args++;
+ iret = parse_restriction (&args[num_args],
+ debit_restrictions);
+ if (iret <= 0)
+ {
+ global_ret = EXIT_INVALIDARGUMENT;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (debit_restrictions);
+ json_decref (credit_restrictions);
+ return;
+ }
+ num_args += iret;
+ continue;
+ }
+ if (0 == strcmp (args[num_args],
+ "display-hint"))
+ {
+ long long p;
+ char dummy;
+
+ num_args++;
+ if ( (NULL == args[num_args]) ||
+ (NULL == args[num_args + 1]) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "'display-hint' requires at least two arguments (priority and label)\n");
+ global_ret = EXIT_INVALIDARGUMENT;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (debit_restrictions);
+ json_decref (credit_restrictions);
+ return;
+ }
+ if (1 != sscanf (args[num_args],
+ "%lld%c",
+ &p,
+ &dummy))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Priority argument `%s' is not a number\n",
+ args[num_args]);
+ global_ret = EXIT_INVALIDARGUMENT;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (debit_restrictions);
+ json_decref (credit_restrictions);
+ return;
+ }
+ priority = (int64_t) p;
+ num_args++;
+ bank_label = args[num_args];
+ num_args++;
+ continue;
+ }
+ break;
+ }
+ TALER_exchange_offline_wire_add_sign (args[0],
+ conversion_url,
+ debit_restrictions,
+ credit_restrictions,
+ now,
+ &master_priv,
+ &master_sig_add);
+ TALER_exchange_wire_signature_make (args[0],
+ conversion_url,
+ debit_restrictions,
+ credit_restrictions,
+ &master_priv,
+ &master_sig_wire);
+ output_operation (OP_ENABLE_WIRE,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ args[0]),
+ GNUNET_JSON_pack_array_steal ("debit_restrictions",
+ debit_restrictions),
+ GNUNET_JSON_pack_array_steal ("credit_restrictions",
+ credit_restrictions),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("conversion_url",
+ conversion_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("bank_label",
+ bank_label)),
+ GNUNET_JSON_pack_int64 ("priority",
+ priority),
+ GNUNET_JSON_pack_timestamp ("validity_start",
+ now),
+ GNUNET_JSON_pack_data_auto ("master_sig_add",
+ &master_sig_add),
+ GNUNET_JSON_pack_data_auto ("master_sig_wire",
+ &master_sig_wire)));
+ next (args + num_args);
+}
+
+
+/**
+ * Disable wire account.
+ *
+ * @param args the array of command-line arguments to process next;
+ * args[0] must be the hash of the denomination key to revoke
+ */
+static void
+do_del_wire (char *const *args)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_TIME_Timestamp now;
+
+ if (NULL != in)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Downloaded data was not consumed, not deleting wire account\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if (NULL == args[0])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "You must specify a payto://-URI with this subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ return;
+ now = GNUNET_TIME_timestamp_get ();
+ TALER_exchange_offline_wire_del_sign (args[0],
+ now,
+ &master_priv,
+ &master_sig);
+ output_operation (OP_DISABLE_WIRE,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ args[0]),
+ GNUNET_JSON_pack_timestamp ("validity_end",
+ now),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &master_sig)));
+ next (args + 1);
+}
+
+
+/**
+ * Set wire fees for the given year.
+ *
+ * @param args the array of command-line arguments to process next;
+ * args[0] must be the year, args[1] the wire method, args[2] the wire fee and args[3]
+ * the closing fee.
+ */
+static void
+do_set_wire_fee (char *const *args)
+{
+ struct TALER_MasterSignatureP master_sig;
+ char dummy;
+ unsigned int year;
+ struct TALER_WireFeeSet fees;
+ struct GNUNET_TIME_Timestamp start_time;
+ struct GNUNET_TIME_Timestamp end_time;
+
+ if (NULL != in)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Downloaded data was not consumed, not setting wire fee\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if ( (NULL == args[0]) ||
+ (NULL == args[1]) ||
+ (NULL == args[2]) ||
+ (NULL == args[3]) ||
+ ( (1 != sscanf (args[0],
+ "%u%c",
+ &year,
+ &dummy)) &&
+ (0 != strcasecmp ("now",
+ args[0])) ) ||
+ (GNUNET_OK !=
+ TALER_string_to_amount (args[2],
+ &fees.wire)) ||
+ (GNUNET_OK !=
+ TALER_string_to_amount (args[3],
+ &fees.closing)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "You must use YEAR, METHOD, WIRE-FEE, and CLOSING-FEE as arguments for this subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if (0 == strcasecmp ("now",
+ args[0]))
+ year = GNUNET_TIME_get_current_year ();
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ return;
+ start_time = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_year_to_time (year));
+ end_time = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_year_to_time (year + 1));
+
+ TALER_exchange_offline_wire_fee_sign (args[1],
+ start_time,
+ end_time,
+ &fees,
+ &master_priv,
+ &master_sig);
+ output_operation (OP_SET_WIRE_FEE,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("wire_method",
+ args[1]),
+ GNUNET_JSON_pack_timestamp ("start_time",
+ start_time),
+ GNUNET_JSON_pack_timestamp ("end_time",
+ end_time),
+ TALER_JSON_pack_amount ("wire_fee",
+ &fees.wire),
+ TALER_JSON_pack_amount ("closing_fee",
+ &fees.closing),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &master_sig)));
+ next (args + 4);
+}
+
+
+/**
+ * Set global fees for the given year.
+ *
+ * @param args the array of command-line arguments to process next;
+ * args[0] must be the year, args[1] the history fee, args[2]
+ * the account fee and args[3] the purse fee. These are followed by args[4] purse timeout,
+ * args[5] history expiration. Last is args[6] the (free) purse account limit.
+ */
+static void
+do_set_global_fee (char *const *args)
+{
+ struct TALER_MasterSignatureP master_sig;
+ char dummy;
+ unsigned int year;
+ struct TALER_GlobalFeeSet fees;
+ struct GNUNET_TIME_Relative purse_timeout;
+ struct GNUNET_TIME_Relative history_expiration;
+ unsigned int purse_account_limit;
+ struct GNUNET_TIME_Timestamp start_time;
+ struct GNUNET_TIME_Timestamp end_time;
+
+ if (NULL != in)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Downloaded data was not consumed, not setting global fee\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if ( (NULL == args[0]) ||
+ (NULL == args[1]) ||
+ (NULL == args[2]) ||
+ (NULL == args[3]) ||
+ (NULL == args[4]) ||
+ (NULL == args[5]) ||
+ (NULL == args[6]) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "You must use YEAR, HISTORY-FEE, ACCOUNT-FEE, PURSE-FEE, PURSE-TIMEOUT, HISTORY-EXPIRATION and PURSE-ACCOUNT-LIMIT as arguments for this subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if ( (1 != sscanf (args[0],
+ "%u%c",
+ &year,
+ &dummy)) &&
+ (0 != strcasecmp ("now",
+ args[0])) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid YEAR given for 'global-fee' subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if ( (GNUNET_OK !=
+ TALER_string_to_amount (args[1],
+ &fees.history)) ||
+ (GNUNET_OK !=
+ TALER_string_to_amount (args[2],
+ &fees.account)) ||
+ (GNUNET_OK !=
+ TALER_string_to_amount (args[3],
+ &fees.purse)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid amount given for 'global-fee' subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if ( (GNUNET_OK !=
+ GNUNET_STRINGS_fancy_time_to_relative (args[4],
+ &purse_timeout)) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_fancy_time_to_relative (args[5],
+ &history_expiration)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid delay given for 'global-fee' subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if (1 != sscanf (args[6],
+ "%u%c",
+ &purse_account_limit,
+ &dummy))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid purse account limit given for 'global-fee' subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if (0 == strcasecmp ("now",
+ args[0]))
+ year = GNUNET_TIME_get_current_year ();
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ return;
+ start_time = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_year_to_time (year));
+ end_time = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_year_to_time (year + 1));
+
+ TALER_exchange_offline_global_fee_sign (start_time,
+ end_time,
+ &fees,
+ purse_timeout,
+ history_expiration,
+ (uint32_t) purse_account_limit,
+ &master_priv,
+ &master_sig);
+ output_operation (OP_SET_GLOBAL_FEE,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_timestamp ("start_time",
+ start_time),
+ GNUNET_JSON_pack_timestamp ("end_time",
+ end_time),
+ TALER_JSON_pack_amount ("history_fee",
+ &fees.history),
+ TALER_JSON_pack_amount ("account_fee",
+ &fees.account),
+ TALER_JSON_pack_amount ("purse_fee",
+ &fees.purse),
+ GNUNET_JSON_pack_time_rel ("purse_timeout",
+ purse_timeout),
+ GNUNET_JSON_pack_time_rel ("history_expiration",
+ history_expiration),
+ GNUNET_JSON_pack_uint64 ("purse_account_limit",
+ (uint32_t) purse_account_limit),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &master_sig)));
+ next (args + 7);
+}
+
+
+/**
+ * Drain profits from exchange's escrow account to
+ * regular exchange account.
+ *
+ * @param args the array of command-line arguments to process next;
+ * args[0] must be the amount,
+ * args[1] must be the section of the escrow account to drain
+ * args[2] must be the payto://-URI of the target account
+ */
+static void
+do_drain (char *const *args)
+{
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct GNUNET_TIME_Timestamp date;
+ struct TALER_Amount amount;
+ const char *account_section;
+ const char *payto_uri;
+ struct TALER_MasterSignatureP master_sig;
+ char *err;
+
+ if (NULL != in)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Downloaded data was not consumed, refusing drain\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if ( (NULL == args[0]) ||
+ (NULL == args[1]) ||
+ (NULL == args[2]) ||
+ (GNUNET_OK !=
+ TALER_string_to_amount (args[0],
+ &amount)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Drain requires an amount, section name and target payto://-URI as arguments\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if ( (NULL == args[0]) ||
+ (NULL == args[1]) ||
+ (NULL == args[2]) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Drain requires an amount, section name and target payto://-URI as arguments\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_string_to_amount (args[0],
+ &amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid amount `%s' specified for drain\n",
+ args[0]);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ account_section = args[1];
+ payto_uri = args[2];
+ err = TALER_payto_validate (payto_uri);
+ if (NULL != err)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid payto://-URI `%s' specified for drain: %s\n",
+ payto_uri,
+ err);
+ GNUNET_free (err);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ return;
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &wtid,
+ sizeof (wtid));
+ date = GNUNET_TIME_timestamp_get ();
+ TALER_exchange_offline_profit_drain_sign (&wtid,
+ date,
+ &amount,
+ account_section,
+ payto_uri,
+ &master_priv,
+ &master_sig);
+ output_operation (OP_DRAIN_PROFITS,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &wtid),
+ GNUNET_JSON_pack_string ("account_section",
+ account_section),
+ GNUNET_JSON_pack_string ("payto_uri",
+ payto_uri),
+ TALER_JSON_pack_amount ("amount",
+ &amount),
+ GNUNET_JSON_pack_timestamp ("date",
+ date),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &master_sig)));
+ next (args + 3);
+}
+
+
+/**
+ * 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");
+ GNUNET_SCHEDULER_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");
+ GNUNET_SCHEDULER_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");
+ GNUNET_SCHEDULER_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]);
+ GNUNET_SCHEDULER_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]);
+ GNUNET_SCHEDULER_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]);
+ GNUNET_SCHEDULER_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");
+ GNUNET_SCHEDULER_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");
+ GNUNET_SCHEDULER_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");
+ GNUNET_SCHEDULER_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]);
+ GNUNET_SCHEDULER_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
+ * whether there are subsequent commands).
+ *
+ * @param cls closure with the `char **` remaining args
+ * @param mgr response data
+ */
+static void
+download_cb (void *cls,
+ const struct TALER_EXCHANGE_ManagementGetKeysResponse *mgr)
+{
+ char *const *args = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &mgr->hr;
+
+ mgkh = NULL;
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ break;
+ default:
+ if (0 != hr->http_status)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to download keys from `%s': %s (HTTP status: %u/%u)\n",
+ CFG_exchange_url,
+ hr->hint,
+ hr->http_status,
+ (unsigned int) hr->ec);
+ else
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to download keys from `%s' (no HTTP response)\n",
+ CFG_exchange_url);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ in = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ OP_INPUT_KEYS),
+ GNUNET_JSON_pack_object_incref ("arguments",
+ (json_t *) hr->reply));
+ if (NULL == args[0])
+ {
+ json_dumpf (in,
+ stdout,
+ JSON_INDENT (2));
+ json_decref (in);
+ in = NULL;
+ }
+ next (args);
+}
+
+
+/**
+ * Download future keys.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_download (char *const *args)
+{
+ if ( (NULL == CFG_exchange_url) &&
+ (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (kcfg,
+ "exchange",
+ "BASE_URL",
+ &CFG_exchange_url)) )
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ mgkh = TALER_EXCHANGE_get_management_keys (ctx,
+ CFG_exchange_url,
+ &download_cb,
+ (void *) args);
+}
+
+
+/**
+ * Check that the security module keys are the same as before. If we had no
+ * keys in store before, remember them (Trust On First Use).
+ *
+ * @param secmset security module keys
+ * @return #GNUNET_OK if keys match with what we have in store
+ * #GNUNET_NO if we had nothing in store but now do
+ * #GNUNET_SYSERR if keys changed from what we remember or other error
+ */
+static enum GNUNET_GenericReturnValue
+tofu_check (const struct TALER_SecurityModulePublicKeySetP *secmset)
+{
+ char *fn;
+ struct TALER_SecurityModulePublicKeySetP oldset;
+ ssize_t ret;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (kcfg,
+ "exchange-offline",
+ "SECM_TOFU_FILE",
+ &fn))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange-offline",
+ "SECM_TOFU_FILE");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK ==
+ GNUNET_DISK_file_test (fn))
+ {
+ ret = GNUNET_DISK_fn_read (fn,
+ &oldset,
+ sizeof (oldset));
+ if (GNUNET_SYSERR != ret)
+ {
+ if (ret != sizeof (oldset))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "File `%s' corrupt\n",
+ fn);
+ GNUNET_free (fn);
+ return GNUNET_SYSERR;
+ }
+ /* TOFU check */
+ if (0 != memcmp (&oldset,
+ secmset,
+ sizeof (*secmset)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Fatal: security module keys changed (file `%s')!\n",
+ fn);
+ GNUNET_free (fn);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (fn);
+ return GNUNET_OK;
+ }
+ }
+
+ {
+ char *key;
+
+ /* check against SECMOD-keys pinned in configuration */
+ if (GNUNET_OK ==
+ GNUNET_CONFIGURATION_get_value_string (kcfg,
+ "exchange-offline",
+ "SECM_ESIGN_PUBKEY",
+ &key))
+ {
+ struct TALER_SecurityModulePublicKeyP k;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (key,
+ strlen (key),
+ &k,
+ sizeof (k)))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "exchange-offline",
+ "SECM_ESIGN_PUBKEY",
+ "key malformed");
+ GNUNET_free (key);
+ GNUNET_free (fn);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (key);
+ if (0 !=
+ GNUNET_memcmp (&k,
+ &secmset->eddsa))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "ESIGN security module key does not match SECM_ESIGN_PUBKEY in configuration\n");
+ GNUNET_free (fn);
+ return GNUNET_SYSERR;
+ }
+ }
+ if (GNUNET_OK ==
+ GNUNET_CONFIGURATION_get_value_string (kcfg,
+ "exchange-offline",
+ "SECM_DENOM_PUBKEY",
+ &key))
+ {
+ struct TALER_SecurityModulePublicKeyP k;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (key,
+ strlen (key),
+ &k,
+ sizeof (k)))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "exchange-offline",
+ "SECM_DENOM_PUBKEY",
+ "key malformed");
+ GNUNET_free (key);
+ GNUNET_free (fn);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (key);
+ if (0 !=
+ GNUNET_memcmp (&k,
+ &secmset->rsa))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "DENOM security module key does not match SECM_DENOM_PUBKEY in configuration\n");
+ GNUNET_free (fn);
+ return GNUNET_SYSERR;
+ }
+ }
+ if (GNUNET_OK ==
+ GNUNET_CONFIGURATION_get_value_string (kcfg,
+ "exchange-offline",
+ "SECM_DENOM_CS_PUBKEY",
+ &key))
+ {
+ struct TALER_SecurityModulePublicKeyP k;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (key,
+ strlen (key),
+ &k,
+ sizeof (k)))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "exchange-offline",
+ "SECM_DENOM_CS_PUBKEY",
+ "key malformed");
+ GNUNET_free (key);
+ GNUNET_free (fn);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (key);
+ if (0 !=
+ GNUNET_memcmp (&k,
+ &secmset->cs))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "DENOM security module key does not match SECM_DENOM_CS_PUBKEY in configuration\n");
+ GNUNET_free (fn);
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+ if (GNUNET_OK !=
+ GNUNET_DISK_directory_create_for_file (fn))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed create directory to store key material in file `%s'\n",
+ fn);
+ GNUNET_free (fn);
+ return GNUNET_SYSERR;
+ }
+ /* persist keys for future runs */
+ if (GNUNET_OK !=
+ GNUNET_DISK_fn_write (fn,
+ secmset,
+ sizeof (oldset),
+ GNUNET_DISK_PERM_USER_READ))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to store key material in file `%s'\n",
+ fn);
+ GNUNET_free (fn);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (fn);
+ return GNUNET_NO;
+}
+
+
+/**
+ * Output @a signkeys for human consumption.
+ *
+ * @param secm_pub security module public key used to sign the denominations
+ * @param signkeys keys to output
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+show_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
+ const json_t *signkeys)
+{
+ size_t index;
+ json_t *value;
+
+ json_array_foreach (signkeys, index, value) {
+ const char *err_name;
+ unsigned int err_line;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_SecurityModuleSignatureP secm_sig;
+ struct GNUNET_TIME_Timestamp start_time;
+ struct GNUNET_TIME_Timestamp sign_end;
+ struct GNUNET_TIME_Timestamp legal_end;
+ struct GNUNET_TIME_Relative duration;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("stamp_start",
+ &start_time),
+ GNUNET_JSON_spec_timestamp ("stamp_expire",
+ &sign_end),
+ GNUNET_JSON_spec_timestamp ("stamp_end",
+ &legal_end),
+ GNUNET_JSON_spec_fixed_auto ("key",
+ &exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("signkey_secmod_sig",
+ &secm_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input for signing key to 'show': %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) index);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ duration = GNUNET_TIME_absolute_get_difference (start_time.abs_time,
+ sign_end.abs_time);
+ if (GNUNET_OK !=
+ TALER_exchange_secmod_eddsa_verify (&exchange_pub,
+ start_time,
+ duration,
+ secm_pub,
+ &secm_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid security module signature for signing key %s (aborting)\n",
+ TALER_B2S (&exchange_pub));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ {
+ char *legal_end_s;
+
+ legal_end_s = GNUNET_strdup (
+ GNUNET_TIME_timestamp2s (legal_end));
+ printf ("EXCHANGE-KEY %s starting at %s (used for: %s, legal end: %s)\n",
+ TALER_B2S (&exchange_pub),
+ GNUNET_TIME_timestamp2s (start_time),
+ GNUNET_TIME_relative2s (duration,
+ false),
+ legal_end_s);
+ GNUNET_free (legal_end_s);
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Output @a denomkeys for human consumption.
+ *
+ * @param secm_pub_rsa security module public key used to sign the RSA denominations
+ * @param secm_pub_cs security module public key used to sign the CS denominations
+ * @param denomkeys keys to output
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+show_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub_rsa,
+ const struct TALER_SecurityModulePublicKeyP *secm_pub_cs,
+ const json_t *denomkeys)
+{
+ size_t index;
+ json_t *value;
+
+ json_array_foreach (denomkeys, index, value) {
+ const char *err_name;
+ unsigned int err_line;
+ const char *section_name;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct GNUNET_TIME_Timestamp stamp_start;
+ struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
+ struct GNUNET_TIME_Timestamp stamp_expire_deposit;
+ struct GNUNET_TIME_Timestamp stamp_expire_legal;
+ struct TALER_Amount coin_value;
+ struct TALER_DenomFeeSet fees;
+ struct TALER_SecurityModuleSignatureP secm_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("section_name",
+ &section_name),
+ TALER_JSON_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ TALER_JSON_spec_amount ("value",
+ currency,
+ &coin_value),
+ TALER_JSON_SPEC_DENOM_FEES ("fee",
+ currency,
+ &fees),
+ GNUNET_JSON_spec_timestamp ("stamp_start",
+ &stamp_start),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
+ &stamp_expire_withdraw),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
+ &stamp_expire_deposit),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
+ &stamp_expire_legal),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_sig",
+ &secm_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_TIME_Relative duration;
+ struct TALER_DenominationHashP h_denom_pub;
+ enum GNUNET_GenericReturnValue ok;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input for denomination key to 'show': %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) index);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_JSON_parse_free (spec);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ duration = GNUNET_TIME_absolute_get_difference (
+ stamp_start.abs_time,
+ stamp_expire_withdraw.abs_time);
+ TALER_denom_pub_hash (&denom_pub,
+ &h_denom_pub);
+ switch (denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ {
+ struct TALER_RsaPubHashP h_rsa;
+
+ TALER_rsa_pub_hash (denom_pub.bsign_pub_key->details.rsa_public_key,
+ &h_rsa);
+ ok = TALER_exchange_secmod_rsa_verify (&h_rsa,
+ section_name,
+ stamp_start,
+ duration,
+ secm_pub_rsa,
+ &secm_sig);
+ }
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ struct TALER_CsPubHashP h_cs;
+
+ TALER_cs_pub_hash (&denom_pub.bsign_pub_key->details.cs_public_key,
+ &h_cs);
+ ok = TALER_exchange_secmod_cs_verify (&h_cs,
+ section_name,
+ stamp_start,
+ duration,
+ secm_pub_cs,
+ &secm_sig);
+ }
+ break;
+ default:
+ GNUNET_break (0);
+ ok = GNUNET_SYSERR;
+ break;
+ }
+ if (GNUNET_OK != ok)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid security module signature for denomination key %s (aborting)\n",
+ GNUNET_h2s (&h_denom_pub.hash));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+
+ {
+ char *withdraw_fee_s;
+ char *deposit_fee_s;
+ char *refresh_fee_s;
+ char *refund_fee_s;
+ char *deposit_s;
+ char *legal_s;
+
+ withdraw_fee_s = TALER_amount_to_string (&fees.withdraw);
+ deposit_fee_s = TALER_amount_to_string (&fees.deposit);
+ refresh_fee_s = TALER_amount_to_string (&fees.refresh);
+ refund_fee_s = TALER_amount_to_string (&fees.refund);
+ deposit_s = GNUNET_strdup (
+ GNUNET_TIME_timestamp2s (stamp_expire_deposit));
+ legal_s = GNUNET_strdup (
+ GNUNET_TIME_timestamp2s (stamp_expire_legal));
+
+ printf (
+ "DENOMINATION-KEY(%s) %s of value %s starting at %s "
+ "(used for: %s, deposit until: %s legal end: %s) with fees %s/%s/%s/%s\n",
+ section_name,
+ TALER_B2S (&h_denom_pub),
+ TALER_amount2s (&coin_value),
+ GNUNET_TIME_timestamp2s (stamp_start),
+ GNUNET_TIME_relative2s (duration,
+ false),
+ deposit_s,
+ legal_s,
+ withdraw_fee_s,
+ deposit_fee_s,
+ refresh_fee_s,
+ refund_fee_s);
+ GNUNET_free (withdraw_fee_s);
+ GNUNET_free (deposit_fee_s);
+ GNUNET_free (refresh_fee_s);
+ GNUNET_free (refund_fee_s);
+ GNUNET_free (deposit_s);
+ GNUNET_free (legal_s);
+ }
+
+ GNUNET_JSON_parse_free (spec);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse the input of exchange keys for the 'show' and 'sign' commands.
+ *
+ * @param command_name name of the command, for logging
+ * @return NULL on error, otherwise the keys details to be free'd by caller
+ */
+static json_t *
+parse_keys_input (const char *command_name)
+{
+ const char *op_str;
+ json_t *keys;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("arguments",
+ &keys),
+ GNUNET_JSON_spec_string ("operation",
+ &op_str),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *err_name;
+ unsigned int err_line;
+
+ if (NULL == in)
+ {
+ json_error_t err;
+
+ in = json_loadf (stdin,
+ JSON_REJECT_DUPLICATES,
+ &err);
+ if (NULL == in)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
+ err.text,
+ err.line,
+ err.source,
+ err.position);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return NULL;
+ }
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (in,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input to '%s': %s#%u (skipping)\n",
+ command_name,
+ err_name,
+ err_line);
+ json_dumpf (in,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return NULL;
+ }
+ if (0 != strcmp (op_str,
+ OP_INPUT_KEYS))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input to '%s' : operation is `%s', expected `%s'\n",
+ command_name,
+ op_str,
+ OP_INPUT_KEYS);
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+ }
+ json_decref (in);
+ in = NULL;
+ return keys;
+}
+
+
+/**
+ * Show future keys.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_show (char *const *args)
+{
+ json_t *keys;
+ const char *err_name;
+ unsigned int err_line;
+ const json_t *denomkeys;
+ const json_t *signkeys;
+ struct TALER_MasterPublicKeyP mpub;
+ struct TALER_SecurityModulePublicKeySetP secmset;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("future_denoms",
+ &denomkeys),
+ GNUNET_JSON_spec_array_const ("future_signkeys",
+ &signkeys),
+ GNUNET_JSON_spec_fixed_auto ("master_pub",
+ &mpub),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key",
+ &secmset.rsa),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key",
+ &secmset.cs),
+ GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key",
+ &secmset.eddsa),
+ GNUNET_JSON_spec_end ()
+ };
+
+ keys = parse_keys_input ("show");
+ if (NULL == keys)
+ return;
+
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ return;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (keys,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input to 'show': %s #%u (skipping)\n",
+ err_name,
+ err_line);
+ json_dumpf (in,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (keys);
+ return;
+ }
+ if (0 !=
+ GNUNET_memcmp (&master_pub,
+ &mpub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Fatal: exchange uses different master key!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (keys);
+ return;
+ }
+ if (GNUNET_SYSERR ==
+ tofu_check (&secmset))
+ {
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (keys);
+ return;
+ }
+ if ( (GNUNET_OK !=
+ show_signkeys (&secmset.eddsa,
+ signkeys)) ||
+ (GNUNET_OK !=
+ show_denomkeys (&secmset.rsa,
+ &secmset.cs,
+ denomkeys)) )
+ {
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (keys);
+ return;
+ }
+ json_decref (keys);
+ next (args);
+}
+
+
+/**
+ * Sign @a signkeys with offline key.
+ *
+ * @param secm_pub security module public key used to sign the denominations
+ * @param signkeys keys to output
+ * @param[in,out] result array where to output the signatures
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+sign_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
+ const json_t *signkeys,
+ json_t *result)
+{
+ size_t index;
+ json_t *value;
+
+ json_array_foreach (signkeys, index, value) {
+ const char *err_name;
+ unsigned int err_line;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_SecurityModuleSignatureP secm_sig;
+ struct GNUNET_TIME_Timestamp start_time;
+ struct GNUNET_TIME_Timestamp sign_end;
+ struct GNUNET_TIME_Timestamp legal_end;
+ struct GNUNET_TIME_Relative duration;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("stamp_start",
+ &start_time),
+ GNUNET_JSON_spec_timestamp ("stamp_expire",
+ &sign_end),
+ GNUNET_JSON_spec_timestamp ("stamp_end",
+ &legal_end),
+ GNUNET_JSON_spec_fixed_auto ("key",
+ &exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("signkey_secmod_sig",
+ &secm_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input for signing key to 'show': %s #%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) index);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+
+ duration = GNUNET_TIME_absolute_get_difference (start_time.abs_time,
+ sign_end.abs_time);
+ if (GNUNET_OK !=
+ TALER_exchange_secmod_eddsa_verify (&exchange_pub,
+ start_time,
+ duration,
+ secm_pub,
+ &secm_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid security module signature for signing key %s (aborting)\n",
+ TALER_B2S (&exchange_pub));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ {
+ struct TALER_MasterSignatureP master_sig;
+
+ TALER_exchange_offline_signkey_validity_sign (&exchange_pub,
+ start_time,
+ sign_end,
+ legal_end,
+ &master_priv,
+ &master_sig);
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ result,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &master_sig))));
+ }
+ GNUNET_JSON_parse_free (spec);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Looks up the AGE_RESTRICTED setting for a denomination in the config and
+ * returns the age restriction (mask) accordingly.
+ *
+ * @param section_name Section in the configuration for the particular
+ * denomination.
+ */
+static struct TALER_AgeMask
+load_age_mask (const char*section_name)
+{
+ static const struct TALER_AgeMask null_mask = {0};
+ enum GNUNET_GenericReturnValue ret;
+
+ if (! ar_enabled)
+ return null_mask;
+
+ if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value (
+ kcfg,
+ section_name,
+ "AGE_RESTRICTED")))
+ return null_mask;
+
+ ret = GNUNET_CONFIGURATION_get_value_yesno (kcfg,
+ section_name,
+ "AGE_RESTRICTED");
+ if (GNUNET_SYSERR == ret)
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section_name,
+ "AGE_RESTRICTED",
+ "Value must be YES or NO\n");
+ if (GNUNET_YES == ret)
+ return ar_config.mask;
+
+ return null_mask;
+}
+
+
+/**
+ * Sign @a denomkeys with offline key.
+ *
+ * @param secm_pub_rsa security module public key used to sign the RSA denominations
+ * @param secm_pub_cs security module public key used to sign the CS denominations
+ * @param denomkeys keys to output
+ * @param[in,out] result array where to output the signatures
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub_rsa,
+ const struct TALER_SecurityModulePublicKeyP *secm_pub_cs,
+ const json_t *denomkeys,
+ json_t *result)
+{
+ size_t index;
+ json_t *value;
+
+ json_array_foreach (denomkeys, index, value) {
+ const char *err_name;
+ unsigned int err_line;
+ const char *section_name;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct GNUNET_TIME_Timestamp stamp_start;
+ struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
+ struct GNUNET_TIME_Timestamp stamp_expire_deposit;
+ struct GNUNET_TIME_Timestamp stamp_expire_legal;
+ struct TALER_Amount coin_value;
+ struct TALER_DenomFeeSet fees;
+ struct TALER_SecurityModuleSignatureP secm_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("section_name",
+ &section_name),
+ TALER_JSON_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ TALER_JSON_spec_amount ("value",
+ currency,
+ &coin_value),
+ TALER_JSON_SPEC_DENOM_FEES ("fee",
+ currency,
+ &fees),
+ GNUNET_JSON_spec_timestamp ("stamp_start",
+ &stamp_start),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
+ &stamp_expire_withdraw),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
+ &stamp_expire_deposit),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
+ &stamp_expire_legal),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_sig",
+ &secm_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_TIME_Relative duration;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input for denomination key to 'sign': %s #%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) index);
+ json_dumpf (value,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_JSON_parse_free (spec);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ duration = GNUNET_TIME_absolute_get_difference (
+ stamp_start.abs_time,
+ stamp_expire_withdraw.abs_time);
+
+ /* Load the age mask, if applicable to this denomination */
+ denom_pub.age_mask = load_age_mask (section_name);
+
+ TALER_denom_pub_hash (&denom_pub,
+ &h_denom_pub);
+
+ switch (denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ {
+ struct TALER_RsaPubHashP h_rsa;
+
+ TALER_rsa_pub_hash (denom_pub.bsign_pub_key->details.rsa_public_key,
+ &h_rsa);
+ if (GNUNET_OK !=
+ TALER_exchange_secmod_rsa_verify (&h_rsa,
+ section_name,
+ stamp_start,
+ duration,
+ secm_pub_rsa,
+ &secm_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid security module signature for denomination key %s (aborting)\n",
+ GNUNET_h2s (&h_denom_pub.hash));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ }
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ struct TALER_CsPubHashP h_cs;
+
+ TALER_cs_pub_hash (&denom_pub.bsign_pub_key->details.cs_public_key,
+ &h_cs);
+ if (GNUNET_OK !=
+ TALER_exchange_secmod_cs_verify (&h_cs,
+ section_name,
+ stamp_start,
+ duration,
+ secm_pub_cs,
+ &secm_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid security module signature for denomination key %s (aborting)\n",
+ GNUNET_h2s (&h_denom_pub.hash));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ }
+ break;
+ default:
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct TALER_MasterSignatureP master_sig;
+
+ TALER_exchange_offline_denom_validity_sign (&h_denom_pub,
+ stamp_start,
+ stamp_expire_withdraw,
+ stamp_expire_deposit,
+ stamp_expire_legal,
+ &coin_value,
+ &fees,
+ &master_priv,
+ &master_sig);
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ result,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &master_sig))));
+ }
+ GNUNET_JSON_parse_free (spec);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Sign future keys.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_sign (char *const *args)
+{
+ json_t *keys;
+ const char *err_name;
+ unsigned int err_line;
+ const json_t *denomkeys;
+ const json_t *signkeys;
+ struct TALER_MasterPublicKeyP mpub;
+ struct TALER_SecurityModulePublicKeySetP secmset;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("future_denoms",
+ &denomkeys),
+ GNUNET_JSON_spec_array_const ("future_signkeys",
+ &signkeys),
+ GNUNET_JSON_spec_fixed_auto ("master_pub",
+ &mpub),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key",
+ &secmset.rsa),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key",
+ &secmset.cs),
+ GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key",
+ &secmset.eddsa),
+ GNUNET_JSON_spec_end ()
+ };
+
+ keys = parse_keys_input ("sign");
+ if (NULL == keys)
+ return;
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ {
+ json_decref (keys);
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (keys,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid input to 'sign' : %s #%u (skipping)\n",
+ err_name,
+ err_line);
+ json_dumpf (in,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (keys);
+ return;
+ }
+ if (0 !=
+ GNUNET_memcmp (&master_pub,
+ &mpub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Fatal: exchange uses different master key!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (keys);
+ return;
+ }
+ if (GNUNET_SYSERR ==
+ tofu_check (&secmset))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Fatal: security module keys changed!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (keys);
+ return;
+ }
+ {
+ json_t *signkey_sig_array = json_array ();
+ json_t *denomkey_sig_array = json_array ();
+
+ GNUNET_assert (NULL != signkey_sig_array);
+ GNUNET_assert (NULL != denomkey_sig_array);
+ if ( (GNUNET_OK !=
+ sign_signkeys (&secmset.eddsa,
+ signkeys,
+ signkey_sig_array)) ||
+ (GNUNET_OK !=
+ sign_denomkeys (&secmset.rsa,
+ &secmset.cs,
+ denomkeys,
+ denomkey_sig_array)) )
+ {
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ json_decref (signkey_sig_array);
+ json_decref (denomkey_sig_array);
+ json_decref (keys);
+ return;
+ }
+
+ output_operation (OP_UPLOAD_SIGS,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("denom_sigs",
+ denomkey_sig_array),
+ GNUNET_JSON_pack_array_steal ("signkey_sigs",
+ signkey_sig_array)));
+ }
+ json_decref (keys);
+ next (args);
+}
+
+
+/**
+ * Setup and output offline signing key.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_setup (char *const *args)
+{
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_YES))
+ {
+ global_ret = EXIT_NOPERMISSION;
+ return;
+ }
+ if (NULL != *args)
+ {
+ output_operation (OP_SETUP,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("exchange_offline_pub",
+ &master_pub)));
+ }
+
+ else
+ {
+ char *pub_s;
+
+ pub_s = GNUNET_STRINGS_data_to_string_alloc (&master_pub,
+ sizeof (master_pub));
+ fprintf (stdout,
+ "%s\n",
+ pub_s);
+ GNUNET_free (pub_s);
+ }
+ if ( (NULL != *args) &&
+ (0 == strcmp (*args,
+ "-")) )
+ args++;
+ next (args);
+}
+
+
+/**
+ * Print the current extensions as configured
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_extensions_show (char *const *args)
+{
+ const struct TALER_Extensions *it;
+ json_t *exts = json_object ();
+ json_t *obj;
+
+ GNUNET_assert (NULL != exts);
+ for (it = TALER_extensions_get_head ();
+ NULL != it && NULL != it->extension;
+ it = it->next)
+ {
+ const struct TALER_Extension *extension = it->extension;
+ int ret;
+
+ ret = json_object_set_new (exts,
+ extension->name,
+ extension->manifest (extension));
+ GNUNET_assert (-1 != ret);
+ }
+
+ obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_steal ("extensions",
+ exts));
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "%s\n",
+ json_dumps (obj,
+ JSON_INDENT (2)));
+ json_decref (obj);
+ next (args);
+}
+
+
+/**
+ * Sign the configurations of the enabled extensions
+ */
+static void
+do_extensions_sign (char *const *args)
+{
+ json_t *extensions = json_object ();
+ struct TALER_ExtensionManifestsHashP h_manifests;
+ struct TALER_MasterSignatureP sig;
+ const struct TALER_Extensions *it;
+ bool found = false;
+ json_t *obj;
+
+ GNUNET_assert (NULL != extensions);
+ for (it = TALER_extensions_get_head ();
+ NULL != it && NULL != it->extension;
+ it = it->next)
+ {
+ const struct TALER_Extension *ext = it->extension;
+ GNUNET_assert (ext);
+
+ found = true;
+
+ GNUNET_assert (0 ==
+ json_object_set_new (extensions,
+ ext->name,
+ ext->manifest (ext)));
+ }
+
+ if (! found)
+ return;
+
+ if (GNUNET_OK !=
+ TALER_JSON_extensions_manifests_hash (extensions,
+ &h_manifests))
+ {
+ json_decref (extensions);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "error while hashing manifest for extensions\n");
+ return;
+ }
+
+ if (GNUNET_OK !=
+ load_offline_key (GNUNET_NO))
+ {
+ json_decref (extensions);
+ return;
+ }
+
+ TALER_exchange_offline_extension_manifests_hash_sign (&h_manifests,
+ &master_priv,
+ &sig);
+ obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_steal ("extensions",
+ extensions),
+ GNUNET_JSON_pack_data_auto (
+ "extensions_sig",
+ &sig));
+
+ output_operation (OP_EXTENSIONS,
+ obj);
+ next (args);
+}
+
+
+/**
+ * Dispatch @a args in the @a cmds array.
+ *
+ * @param args arguments with subcommand to dispatch
+ * @param cmds array of possible subcommands to call
+ */
+static void
+cmd_handler (char *const *args,
+ const struct SubCommand *cmds)
+{
+ nxt = NULL;
+ for (unsigned int i = 0; NULL != cmds[i].name; i++)
+ {
+ if (0 == strcasecmp (cmds[i].name,
+ args[0]))
+ {
+ cmds[i].cb (&args[1]);
+ return;
+ }
+ }
+
+ if (0 != strcasecmp ("help",
+ args[0]))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Unexpected command `%s'\n",
+ args[0]);
+ global_ret = EXIT_INVALIDARGUMENT;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Supported subcommands:\n");
+ for (unsigned int i = 0; NULL != cmds[i].name; i++)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "- %s: %s\n",
+ cmds[i].name,
+ cmds[i].help);
+ }
+ json_decref (out);
+ out = NULL;
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+static void
+do_work_extensions (char *const *args)
+{
+ struct SubCommand cmds[] = {
+ {
+ .name = "show",
+ .help =
+ "show the extensions in the Taler-config and their configured parameters",
+ .cb = &do_extensions_show
+ },
+ {
+ .name = "sign",
+ .help =
+ "sign the configuration of the extensions and publish it with the exchange",
+ .cb = &do_extensions_sign
+ },
+ {
+ .name = NULL,
+ }
+ };
+
+ if (NULL == args[0])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "You must provide a subcommand: `show` or `sign`.\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
+ return;
+ }
+
+ cmd_handler (args, cmds);
+}
+
+
+static void
+work (void *cls)
+{
+ char *const *args = cls;
+ struct SubCommand cmds[] = {
+ {
+ .name = "setup",
+ .help =
+ "initialize offline key signing material and display public offline key",
+ .cb = &do_setup
+ },
+ {
+ .name = "download",
+ .help =
+ "obtain future public keys from exchange (to be performed online!)",
+ .cb = &do_download
+ },
+ {
+ .name = "show",
+ .help =
+ "display future public keys from exchange for human review (pass '-' as argument to disable consuming input)",
+ .cb = &do_show
+ },
+ {
+ .name = "sign",
+ .help = "sign all future public keys from the input",
+ .cb = &do_sign
+ },
+ {
+ .name = "revoke-denomination",
+ .help =
+ "revoke denomination key (hash of public key must be given as argument)",
+ .cb = &do_revoke_denomination_key
+ },
+ {
+ .name = "revoke-signkey",
+ .help =
+ "revoke exchange online signing key (public key must be given as argument)",
+ .cb = &do_revoke_signkey
+ },
+ {
+ .name = "enable-auditor",
+ .help =
+ "enable auditor for the exchange (auditor-public key, auditor-URI and auditor-name must be given as arguments)",
+ .cb = &do_add_auditor
+ },
+ {
+ .name = "disable-auditor",
+ .help =
+ "disable auditor at the exchange (auditor-public key must be given as argument)",
+ .cb = &do_del_auditor
+ },
+ {
+ .name = "enable-account",
+ .help =
+ "enable wire account of the exchange (payto-URI must be given as argument; for optional arguments see man page)",
+ .cb = &do_add_wire
+ },
+ {
+ .name = "disable-account",
+ .help =
+ "disable wire account of the exchange (payto-URI must be given as argument)",
+ .cb = &do_del_wire
+ },
+ {
+ .name = "wire-fee",
+ .help =
+ "sign wire fees for the given year (year, wire method, wire fee, and closing fee must be given as arguments)",
+ .cb = &do_set_wire_fee
+ },
+ {
+ .name = "global-fee",
+ .help =
+ "sign global fees for the given year (year, history fee, account fee, purse fee, purse timeout, history expiration and the maximum number of free purses per account must be given as arguments)",
+ .cb = &do_set_global_fee
+ },
+ {
+ .name = "drain",
+ .help =
+ "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 =
+ "upload operation result to exchange (to be performed online!)",
+ .cb = &do_upload
+ },
+ {
+ .name = "extensions",
+ .help = "subcommands for extension handling",
+ .cb = &do_work_extensions
+ },
+ /* list terminator */
+ {
+ .name = NULL,
+ }
+ };
+ (void) cls;
+
+ cmd_handler (args, cmds);
+}
+
+
+/**
+ * Main function that will be run.
+ *
+ * @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) cfgfile;
+ kcfg = cfg;
+
+ /* load extensions */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_extensions_init (kcfg));
+
+ /* setup age restriction, if applicable */
+ {
+ const struct TALER_AgeRestrictionConfig *arc;
+
+ if (NULL !=
+ (arc = TALER_extensions_get_age_restriction_config ()))
+ {
+ ar_config = *arc;
+ ar_enabled = true;
+ }
+ }
+
+
+ if (GNUNET_OK !=
+ TALER_config_get_currency (kcfg,
+ &currency))
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+
+ ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &rc);
+ rc = GNUNET_CURL_gnunet_rc_create (ctx);
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ next (args);
+}
+
+
+/**
+ * The main function of the taler-exchange-offline tool. This tool is used to
+ * create the signing and denomination keys for the exchange. It uses the
+ * long-term offline private key and generates signatures with it. It also
+ * supports online operations with the exchange to download its input data and
+ * to upload its results. Those online operations should be performed on
+ * another machine in production!
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue 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 */
+ (void) TALER_project_data_default ();
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-exchange-offline",
+ gettext_noop ("Operations for offline signing for a Taler exchange"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+
+/* end of taler-exchange-offline.c */
diff --git a/src/exchange-tools/taler-exchange-wire.c b/src/exchange-tools/taler-exchange-wire.c
deleted file mode 100644
index 2f6b4ad73..000000000
--- a/src/exchange-tools/taler-exchange-wire.c
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2015-2018 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 taler-exchange-wire.c
- * @brief Create signed response for /wire requests.
- * @author Christian Grothoff
- */
-#include <platform.h>
-#include <jansson.h>
-#include <gnunet/gnunet_json_lib.h>
-#include "taler_crypto_lib.h"
-#include "taler_util.h"
-#include "taler_json_lib.h"
-#include "taler_exchangedb_lib.h"
-#include "taler_signatures.h"
-
-
-/**
- * Filename of the master private key.
- */
-static char *masterkeyfile;
-
-/**
- * Private key for signing.
- */
-static struct TALER_MasterPrivateKeyP master_priv;
-
-/**
- * Return value from main().
- */
-static int global_ret;
-
-
-#include "key-helper.c"
-
-
-/**
- * Function called with information about a wire account. Signs
- * the account's wire details and writes out the JSON file to disk.
- *
- * @param cls closure
- * @param ai account information
- */
-static void
-sign_account_data (void *cls,
- const struct TALER_EXCHANGEDB_AccountInfo *ai)
-{
- char *json_out;
- FILE *out;
- int ret;
-
- (void) cls;
- if (GNUNET_NO == ai->credit_enabled)
- return;
- if (NULL == ai->wire_response_filename)
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ai->section_name,
- "WIRE_RESPONSE");
- global_ret = 1;
- return;
- }
-
- {
- json_t *wire;
-
- wire = TALER_JSON_exchange_wire_signature_make (ai->payto_uri,
- &master_priv);
- if (NULL == wire)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not sign wire account `%s'. Is the URI well-formed?\n",
- ai->payto_uri);
- global_ret = 1;
- return;
- }
- GNUNET_assert (NULL != wire);
- json_out = json_dumps (wire,
- JSON_INDENT (2));
- json_decref (wire);
- }
- GNUNET_assert (NULL != json_out);
- if (GNUNET_OK !=
- GNUNET_DISK_directory_create_for_file (ai->wire_response_filename))
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "mkdir",
- ai->wire_response_filename);
- global_ret = 1;
- free (json_out);
- return;
- }
-
- out = fopen (ai->wire_response_filename,
- "w+"); /* create, if exists, truncate */
- if (NULL == out)
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "fopen(w+)",
- ai->wire_response_filename);
- global_ret = 1;
- free (json_out);
- return;
- }
- ret = fprintf (out,
- "%s",
- json_out);
- if ( (0 != fclose (out)) ||
- (-1 == ret) )
- {
- fprintf (stderr,
- "Failure creating wire account file `%s': %s\n",
- ai->wire_response_filename,
- strerror (errno));
- /* attempt to remove malformed file */
- if (0 != unlink (ai->wire_response_filename))
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
- "unlink",
- ai->wire_response_filename);
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Created wire account file `%s'\n",
- ai->wire_response_filename);
- }
- free (json_out);
-}
-
-
-/**
- * Main function that will be run.
- *
- * @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;
-
- if (GNUNET_OK !=
- get_and_check_master_key (cfg,
- masterkeyfile,
- &master_priv))
- {
- global_ret = 1;
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Signing /wire responses\n");
- TALER_EXCHANGEDB_find_accounts (cfg,
- &sign_account_data,
- NULL);
-}
-
-
-/**
- * The main function of the taler-exchange-wire tool. This tool is
- * used to sign the bank account details using the master key.
- *
- * @param argc number of arguments from the command line
- * @param argv command line arguments
- * @return 0 ok, 1 on error
- */
-int
-main (int argc,
- char *const *argv)
-{
- const struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_timetravel ('T',
- "timetravel"),
- GNUNET_GETOPT_option_filename ('m',
- "master-key",
- "FILENAME",
- "master key file (private key)",
- &masterkeyfile),
- GNUNET_GETOPT_OPTION_END
- };
-
- /* 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 */
- (void) TALER_project_data_default ();
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-exchange-wire",
- "WARNING",
- NULL));
- if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc, argv,
- "taler-exchange-wire",
- "Setup /wire response",
- options,
- &run, NULL))
- return 1;
- return global_ret;
-}
-
-
-/* end of taler-exchange-wire.c */
diff --git a/src/exchange-tools/test_taler_exchange_httpd.conf b/src/exchange-tools/test_taler_exchange_httpd.conf
deleted file mode 100644
index 27fc067a9..000000000
--- a/src/exchange-tools/test_taler_exchange_httpd.conf
+++ /dev/null
@@ -1,130 +0,0 @@
-[PATHS]
-# Persistent data storage for the testcase
-TALER_TEST_HOME = test_taler_exchange_httpd_home/
-
-[taler]
-# Currency supported by the exchange (can only be one)
-CURRENCY = EUR
-CURRENCY_ROUND_UNIT = EUR:0.01
-
-[exchange]
-
-# Directory with our terms of service.
-TERMS_DIR = ../../contrib/tos
-
-# Etag / filename for the terms of service.
-TERMS_ETAG = 0
-
-
-# Directory with our privacy policy.
-PRIVACY_DIR = ../../contrib/pp
-
-# Etag / filename for the privacy policy.
-PRIVACY_ETAG = 0
-
-# MAX_REQUESTS = 2
-# how long is one signkey valid?
-SIGNKEY_DURATION = 4 weeks
-
-# how long are the signatures with the signkey valid?
-LEGAL_DURATION = 2 years
-
-# how long do we generate denomination and signing keys
-# ahead of time?
-LOOKAHEAD_SIGN = 2 weeks 1 day
-
-# how long do we provide to clients denomination and signing keys
-# ahead of time?
-LOOKAHEAD_PROVIDE = 1 weeks 1 day
-
-# HTTP port the exchange listens to
-PORT = 8081
-
-# Master public key used to sign the exchange's various keys
-MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
-
-# How to access our database
-DB = postgres
-
-
-[exchangedb]
-# After how long do we close idle reserves? The exchange
-# and the auditor must agree on this value. We currently
-# expect it to be globally defined for the whole system,
-# as there is no way for wallets to query this value. Thus,
-# it is only configurable for testing, and should be treated
-# as constant in production.
-IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
-
-
-[exchangedb-postgres]
-CONFIG = "postgres:///talercheck"
-
-[exchange-account-1]
-PAYTO_URI = "payto://x-taler-bank/localhost:8082/3"
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json
-ENABLE_DEBIT = YES
-ENABLE_CREDIT = YES
-TALER_BANK_AUTH_METHOD = NONE
-
-
-# Wire fees are specified by wire method
-[fees-x-taler-bank]
-# Fees for the foreseeable future...
-# If you see this after 2018, update to match the next 10 years...
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
-WIRE-FEE-2027 = EUR:0.01
-
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-CLOSING-FEE-2027 = EUR:0.01
-
-
-# Coins for the tests.
-[coin_eur_ct_1]
-value = EUR:0.01
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.00
-fee_deposit = EUR:0.00
-fee_refresh = EUR:0.01
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_ct_10]
-value = EUR:0.10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_1]
-value = EUR:1
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
diff --git a/src/exchange-tools/test_taler_exchange_httpd_home/.config/taler/test.json b/src/exchange-tools/test_taler_exchange_httpd_home/.config/taler/test.json
deleted file mode 100644
index eca394241..000000000
--- a/src/exchange-tools/test_taler_exchange_httpd_home/.config/taler/test.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "salt": "AZPRFVJ58NM6M7J5CZQPJAH3EW5DYM52AEZ9Y1C1ER3W94QV8D8TQKF6CK8MYQRA9QMSKDQTGZ306ZS9GQ0M6R01CJ20KPP49WFDZK8",
- "name": "The exchange",
- "account_number": 3,
- "bank_url": "http://localhost:8082/",
- "type": "test",
- "sig": "RPQXP9S4P8PQP7HEZQNRSZCT0ATNEP8GW0P5TPM34V5RX86FCD670V44R9NETSYDDKB8SZV7TKY9PAJYTY51D3VDWY9XXQ5BPFRXR28"
-}
diff --git a/src/exchange-tools/test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv b/src/exchange-tools/test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv
deleted file mode 100644
index 394926938..000000000
--- a/src/exchange-tools/test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv
+++ /dev/null
@@ -1 +0,0 @@
-pÚ^ó-Ú33ˆ€XXÁ!ˆ\0qúýµmUþ_‰ˆ \ No newline at end of file
diff --git a/src/exchange-tools/test_taler_exchange_keyup.sh b/src/exchange-tools/test_taler_exchange_keyup.sh
deleted file mode 100755
index 26f7fe131..000000000
--- a/src/exchange-tools/test_taler_exchange_keyup.sh
+++ /dev/null
@@ -1,142 +0,0 @@
-#!/bin/bash
-#
-# This file is part of TALER
-# Copyright (C) 2015-2020 Taler Systems SA
-#
-# TALER is free software; you can redistribute it and/or modify it under the
-# terms of the GNU Affero General Public License as 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, If not, see <http://www.gnu.org/licenses/>
-#
-#
-# This script uses 'curl' to POST various ill-formed requests to the
-# taler-exchange-httpd. Basically, the goal is to make sure that the
-# HTTP server survives (and produces the 'correct' error code).
-#
-#
-# Clear environment from variables that override config.
-unset XDG_DATA_HOME
-unset XDG_CONFIG_HOME
-#
-
-
-# Exit, with status code "skip" (no 'real' failure)
-function exit_skip() {
- echo $1
- exit 77
-}
-
-# test required commands exist
-echo -n "Testing for jq ..."
-jq -h > /dev/null || exit_skip "jq required"
-echo " OK"
-
-CONF="-c test_taler_exchange_httpd.conf"
-
-echo -n "Launching exchange ..."
-PREFIX=
-# Uncomment this line to run with valgrind...
-# PREFIX="valgrind --leak-check=yes --track-fds=yes --error-exitcode=1 --log-file=valgrind.%p"
-
-# Setup database
-taler-exchange-dbinit $CONF &> /dev/null
-# Setup keys.
-taler-exchange-keyup $CONF &> /dev/null || exit 1
-# Setup wire accounts.
-taler-exchange-wire $CONF > /dev/null || exit 1
-# Run Exchange HTTPD (in background)
-$PREFIX taler-exchange-httpd $CONF 2> test-exchange.log &
-
-# Give HTTP time to start
-
-for n in `seq 1 100`
-do
- echo -n "."
- sleep 0.1
- OK=1
- wget http://localhost:8081/ -o /dev/null -O /dev/null >/dev/null && break
- OK=0
-done
-if [ 1 != $OK ]
-then
- echo "Failed to launch exchange"
- kill -TERM $!
- wait $!
- echo Process status: $?
- exit 77
-fi
-echo " DONE"
-
-# Finally run test...
-echo -n "Running tests ... "
-
-# Revoke active denomination key
-REVOKE_DENOM_HASH=`taler-exchange-keycheck $CONF -i EUR:1 | sort | head -n1 | awk '{print $2}'`
-REVOKE_DENOM_TIME=`taler-exchange-keycheck $CONF -i EUR:1 | sort | head -n1 | awk '{print $1}'`
-
-taler-exchange-keyup $CONF -r "$REVOKE_DENOM_HASH" -k 1024
-
-# check revocation file exists
-RDIR=`taler-config $CONF -f -s exchange -o REVOCATION_DIR`
-if [ -f "$RDIR"/$REVOKE_DENOM_HASH.rev ]
-then
- echo -n "REV-OK "
-else
- echo -n "REV-FAIL ($RDIR) "
- RET=1
-fi
-
-# Check we now have two keys for that timestamp
-CNT=`taler-exchange-keycheck $CONF -i EUR:1 | awk '{print $1}' | grep -- "$REVOKE_DENOM_TIME" | wc -l`
-
-if [ x2 != x${CNT} ]
-then
- echo -n "CNT-FAIL (${CNT}) "
- RET=1
-else
- echo -n "CNT-OK "
-fi
-
-# Reload keys (and revocation data) at the exchange
-kill -SIGUSR1 $!
-
-# Give exchange chance to parse and reload keys
-sleep 5
-
-# Download (updated) keys
-wget http://localhost:8081/keys -O keys.json -o /dev/null >/dev/null
-
-RK=`jq -er .recoup[0].h_denom_pub < keys.json`
-if [ x$RK != x$REVOKE_DENOM_HASH ]
-then
- echo -n "KEYS-FAIL ($RK vs $REVOKE_DENOM_HASH)"
- RET=1
-else
- echo -n "KEYS-OK"
-fi
-
-echo " DONE"
-# $! is the last backgrounded process, hence the exchange
-kill -TERM $!
-wait $!
-if [ 0 != $? ]
-then
- RET=4
-fi
-
-echo "Final cleanup"
-# Can't leave revocations around, would mess up next test run
-rm -r "$RDIR"
-# Also cleaning up live keys, as otherwise we have two for the revoked denomination type next time
-KDIR=`taler-config $CONF -f -s exchange -o KEYDIR`
-rm -r "$KDIR"
-# Clean up our temporary file
-rm keys.json
-
-exit $RET
diff --git a/src/exchange/.gitignore b/src/exchange/.gitignore
index 5818f1717..bcfdb7e82 100644
--- a/src/exchange/.gitignore
+++ b/src/exchange/.gitignore
@@ -9,3 +9,5 @@ test_taler_exchange_wirewatch-postgres
test_taler_exchange_httpd_home/.config/taler/account-1.json
taler-exchange-closer
taler-exchange-transfer
+taler-exchange-router
+taler-exchange-expire
diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am
index 4876b3074..1c0c2c684 100644
--- a/src/exchange/Makefile.am
+++ b/src/exchange/Makefile.am
@@ -15,11 +15,16 @@ pkgcfg_DATA = \
exchange.conf
# Programs
+bin_SCRIPTS = \
+ taler-exchange-kyc-aml-pep-trigger.sh
bin_PROGRAMS = \
taler-exchange-aggregator \
taler-exchange-closer \
+ taler-exchange-drain \
+ taler-exchange-expire \
taler-exchange-httpd \
+ taler-exchange-router \
taler-exchange-transfer \
taler-exchange-wirewatch
@@ -27,13 +32,15 @@ taler_exchange_aggregator_SOURCES = \
taler-exchange-aggregator.c
taler_exchange_aggregator_LDADD = \
$(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/kyclogic/libtalerkyclogic.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/bank-lib/libtalerbank.la \
$(top_builddir)/src/exchangedb/libtalerexchangedb.la \
-ljansson \
-lgnunetcurl \
- -lgnunetutil
+ -lgnunetutil \
+ $(XLIB)
taler_exchange_closer_SOURCES = \
@@ -46,11 +53,12 @@ taler_exchange_closer_LDADD = \
$(top_builddir)/src/exchangedb/libtalerexchangedb.la \
-ljansson \
-lgnunetcurl \
- -lgnunetutil
+ -lgnunetutil \
+ $(XLIB)
-taler_exchange_wirewatch_SOURCES = \
- taler-exchange-wirewatch.c
-taler_exchange_wirewatch_LDADD = \
+taler_exchange_drain_SOURCES = \
+ taler-exchange-drain.c
+taler_exchange_drain_LDADD = \
$(LIBGCRYPT_LIBS) \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
@@ -58,7 +66,34 @@ taler_exchange_wirewatch_LDADD = \
$(top_builddir)/src/exchangedb/libtalerexchangedb.la \
-ljansson \
-lgnunetcurl \
- -lgnunetutil
+ -lgnunetutil \
+ $(XLIB)
+
+taler_exchange_expire_SOURCES = \
+ taler-exchange-expire.c
+taler_exchange_expire_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+ -ljansson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ $(XLIB)
+
+taler_exchange_router_SOURCES = \
+ taler-exchange-router.c
+taler_exchange_router_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+ -ljansson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ $(XLIB)
taler_exchange_transfer_SOURCES = \
taler-exchange-transfer.c
@@ -70,54 +105,116 @@ taler_exchange_transfer_LDADD = \
$(top_builddir)/src/exchangedb/libtalerexchangedb.la \
-ljansson \
-lgnunetcurl \
- -lgnunetutil
+ -lgnunetutil \
+ $(XLIB)
+
+taler_exchange_wirewatch_SOURCES = \
+ taler-exchange-wirewatch.c
+taler_exchange_wirewatch_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+ -ljansson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ $(XLIB)
+
taler_exchange_httpd_SOURCES = \
taler-exchange-httpd.c taler-exchange-httpd.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_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_coins_get.c taler-exchange-httpd_coins_get.h \
+ taler-exchange-httpd_common_deposit.c taler-exchange-httpd_common_deposit.h \
+ taler-exchange-httpd_common_kyc.c taler-exchange-httpd_common_kyc.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 \
- taler-exchange-httpd_deposit.c taler-exchange-httpd_deposit.h \
taler-exchange-httpd_deposits_get.c taler-exchange-httpd_deposits_get.h \
- taler-exchange-httpd_keystate.c taler-exchange-httpd_keystate.h \
+ taler-exchange-httpd_extensions.c taler-exchange-httpd_extensions.h \
+ taler-exchange-httpd_keys.c taler-exchange-httpd_keys.h \
+ taler-exchange-httpd_kyc-check.c taler-exchange-httpd_kyc-check.h \
+ taler-exchange-httpd_kyc-proof.c taler-exchange-httpd_kyc-proof.h \
+ taler-exchange-httpd_kyc-wallet.c taler-exchange-httpd_kyc-wallet.h \
+ taler-exchange-httpd_kyc-webhook.c taler-exchange-httpd_kyc-webhook.h \
taler-exchange-httpd_link.c taler-exchange-httpd_link.h \
+ taler-exchange-httpd_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 \
+ taler-exchange-httpd_management_wire_disable.c \
+ taler-exchange-httpd_management_wire_fees.c \
+ taler-exchange-httpd_melt.c taler-exchange-httpd_melt.h \
+ taler-exchange-httpd_metrics.c taler-exchange-httpd_metrics.h \
taler-exchange-httpd_mhd.c taler-exchange-httpd_mhd.h \
+ taler-exchange-httpd_purses_create.c taler-exchange-httpd_purses_create.h \
+ taler-exchange-httpd_purses_deposit.c taler-exchange-httpd_purses_deposit.h \
+ taler-exchange-httpd_purses_delete.c taler-exchange-httpd_purses_delete.h \
+ taler-exchange-httpd_purses_get.c taler-exchange-httpd_purses_get.h \
+ taler-exchange-httpd_purses_merge.c taler-exchange-httpd_purses_merge.h \
taler-exchange-httpd_recoup.c taler-exchange-httpd_recoup.h \
- taler-exchange-httpd_melt.c taler-exchange-httpd_melt.h \
+ taler-exchange-httpd_recoup-refresh.c taler-exchange-httpd_recoup-refresh.h \
taler-exchange-httpd_refreshes_reveal.c taler-exchange-httpd_refreshes_reveal.h \
taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \
+ taler-exchange-httpd_reserves_attest.c taler-exchange-httpd_reserves_attest.h \
+ taler-exchange-httpd_reserves_close.c taler-exchange-httpd_reserves_close.h \
taler-exchange-httpd_reserves_get.c taler-exchange-httpd_reserves_get.h \
+ taler-exchange-httpd_reserves_get_attest.c taler-exchange-httpd_reserves_get_attest.h \
+ taler-exchange-httpd_reserves_history.c taler-exchange-httpd_reserves_history.h \
+ taler-exchange-httpd_reserves_open.c taler-exchange-httpd_reserves_open.h \
+ taler-exchange-httpd_reserves_purse.c taler-exchange-httpd_reserves_purse.h \
taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \
+ taler-exchange-httpd_spa.c taler-exchange-httpd_spa.h \
taler-exchange-httpd_terms.c taler-exchange-httpd_terms.h \
- taler-exchange-httpd_transfers_get.c taler-exchange-httpd_transfers_get.h \
- taler-exchange-httpd_wire.c taler-exchange-httpd_wire.h \
- taler-exchange-httpd_withdraw.c taler-exchange-httpd_withdraw.h
+ taler-exchange-httpd_transfers_get.c taler-exchange-httpd_transfers_get.h
+
taler_exchange_httpd_LDADD = \
$(LIBGCRYPT_LIBS) \
$(top_builddir)/src/bank-lib/libtalerbank.la \
$(top_builddir)/src/mhd/libtalermhd.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+ $(top_builddir)/src/templating/libtalertemplating.la \
+ $(top_builddir)/src/kyclogic/libtalerkyclogic.la \
$(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
-lmicrohttpd \
-lgnunetcurl \
-lgnunetutil \
-lgnunetjson \
-ljansson \
+ -lcurl \
-lz \
- -lpthread
+ $(XLIB)
# Testcases
AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
check_SCRIPTS = \
- test_taler_exchange_httpd.sh \
- test_taler_exchange_httpd_restart.sh
+ test_taler_exchange_httpd.sh
if HAVE_EXPENSIVE_TESTS
check_SCRIPTS += \
test_taler_exchange_httpd_afl.sh
endif
-.NOTPARALLEL:
TESTS = \
$(check_SCRIPTS)
@@ -130,4 +227,5 @@ EXTRA_DIST = \
test_taler_exchange_httpd.get \
test_taler_exchange_httpd.post \
exchange.conf \
+ $(bin_SCRIPTS) \
$(check_SCRIPTS)
diff --git a/src/exchange/exchange.conf b/src/exchange/exchange.conf
index 3abd8efd9..ce471a292 100644
--- a/src/exchange/exchange.conf
+++ b/src/exchange/exchange.conf
@@ -2,19 +2,37 @@
#
[exchange]
-# Where do we store the private keys the exchange needs at
-# runtime? (Denomination and signing keys are then stored
-# in respective subdirectories.)
-KEYDIR = ${TALER_DATA_HOME}/exchange/live-keys/
-
-# Directory where the exchange expects to find revocation
-# certificates (and where taler-exchange-keyup will write them).
-REVOCATION_DIR = ${TALER_DATA_HOME}/exchange/revocations/
-
# Master public key used to sign the exchange's various keys
-# This must be adjusted to your actually installation.
+# This must be adjusted to your actual installation.
# MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+# Must be set to the threshold above which transactions
+# are flagged for AML review.
+# AML_THRESHOLD =
+
+# How many digits does the currency use by default on displays.
+# Hint provided to wallets. Should be 2 for EUR/USD/CHF,
+# and 0 for JPY. Default is 2 as that is most common.
+# Maximum value is 8. Note that this is the number of
+# fractions shown in the wallet by default, it is still
+# possible to configure denominations with more digits
+# and those will then be rendered using 'tiny' fraction
+# capitals (like at gas stations) when present.
+CURRENCY_FRACTION_DIGITS = 2
+
+# Specifies a program (binary) to run on KYC attribute data to decide
+# whether we should immediately flag an account for AML review.
+# The KYC attribute data will be passed on standard-input.
+# Return non-zero to trigger AML review of the new user.
+KYC_AML_TRIGGER = true
+
+# Attribute encryption key for storing attributes encrypted
+# in the database. Should be a high-entropy nonce.
+ATTRIBUTE_ENCRYPTION_KEY = SET_ME_PLEASE
+
+# Set to NO to disable rewards.
+ENABLE_REWARDS = YES
+
# 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.
@@ -24,22 +42,18 @@ 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
-# Where do we store the offline master private key of the exchange?
-MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/master.priv
-
-
# Network configuration for the normal API/service HTTP server
# serve via tcp socket (on PORT)
SERVE = tcp
# Unix domain socket to listen on,
# only effective with "SERVE = unix"
-UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http
+UNIXPATH = ${TALER_RUNTIME_DIR}/exchange-httpd/exchange-http.sock
UNIXPATH_MODE = 660
# HTTP port the exchange listens to
@@ -53,40 +67,72 @@ PORT = 8081
# transfers to enable tracking.
BASE_URL = http://localhost:8081/
+# How long should the aggregator sleep if it has nothing to do?
+AGGREGATOR_IDLE_SLEEP_INTERVAL = 60 s
-# How long should the aggregator (and closer, and transfer)
+# What type of asset is the exchange managing? Used to adjust
+# the user-interface of the wallet.
+# Possibilities include: "fiat", "regional" and "crypto".
+# In the future (and already permitted but not yet supported by wallets)
+# we also expect to have "stock" and "future" (and more).
+# Default is "fiat".
+ASSET_TYPE = "fiat"
+
+# FIXME: document!
+ROUTER_IDLE_SLEEP_INTERVAL = 60 s
+
+# How big is an individual shard to be processed
+# by taler-exchange-expire (in time). It may take
+# this much time for an expired purse to be really
+# cleaned up and the coins refunded.
+EXPIRE_SHARD_SIZE = 60 s
+
+# How long should the transfer tool
# sleep if it has nothing to do?
-AGGREGATOR_IDLE_SLEEP_INTERVAL = 60 s
+TRANSFER_IDLE_SLEEP_INTERVAL = 60 s
+
+# How long should the closer tool
+# sleep if it has nothing to do?
+CLOSER_IDLE_SLEEP_INTERVAL = 60 s
+
+# Values of 0 or above 2^31 disable sharding, which
+# is a sane default for most use-cases.
+# When changing this value, you MUST stop all
+# aggregators and manually run
+#
+# $ taler-exchange-dbinit -s
+#
+# against the exchange's database. Otherwise, the
+# aggregation logic will break badly!
+AGGREGATOR_SHARD_SIZE = 2147483648
+
+# Values of 0 or above 2^31 disable sharding, which
+# is a sane default for most use-cases.
+# When changing this value, you MUST stop all
+# aggregators and manually run
+#
+# $ taler-exchange-dbinit -s
+#
+# against the exchange's database. Otherwise, the
+# aggregation logic will break badly!
+ROUTER_SHARD_SIZE = 2147483648
# How long should wirewatch sleep if it has nothing to do?
# (Set very aggressively here for the demonstrators to be
# super fast.)
WIREWATCH_IDLE_SLEEP_INTERVAL = 1 s
-# how long is one signkey valid?
-SIGNKEY_DURATION = 4 weeks
-
# how long are the signatures with the signkey valid?
-LEGAL_DURATION = 2 years
-
-# how long do we generate denomination and signing keys
-# ahead of time?
-LOOKAHEAD_SIGN = 32 weeks 1 day
-
-# how long do we provide to clients denomination and signing keys
-# ahead of time?
-LOOKAHEAD_PROVIDE = 4 weeks 1 day
-
+SIGNKEY_LEGAL_DURATION = 2 years
# Directory with our terms of service.
-# TERMS_DIR =
+TERMS_DIR = $TALER_DATA_HOME/terms/
# Etag / filename for the terms of service.
-# TERMS_ETAG =
-
+TERMS_ETAG = exchange-tos-v0
# Directory with our privacy policy.
-# PRIVACY_DIR =
+PRIVACY_DIR = $TALER_DATA_HOME/terms/
# Etag / filename for the privacy policy.
-# PRIVACY_ETAG =
+PRIVACY_ETAG = exchange-pp-v0
diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c
index da24a125d..691d65ae3 100644
--- a/src/exchange/taler-exchange-aggregator.c
+++ b/src/exchange/taler-exchange-aggregator.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2016-2020 Taler Systems SA
+ Copyright (C) 2016-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -26,7 +26,9 @@
#include "taler_exchangedb_lib.h"
#include "taler_exchangedb_plugin.h"
#include "taler_json_lib.h"
+#include "taler_kyclogic_lib.h"
#include "taler_bank_service.h"
+#include "taler_dbevents.h"
/**
@@ -43,7 +45,13 @@ struct AggregationUnit
struct TALER_MerchantPublicKeyP merchant_pub;
/**
- * Total amount to be transferred, before subtraction of @e wire_fee and rounding down.
+ * Transient amount already found aggregated,
+ * set only if @e have_transient is true.
+ */
+ struct TALER_Amount trans;
+
+ /**
+ * Total amount to be transferred, before subtraction of @e fees.wire and rounding down.
*/
struct TALER_Amount total_amount;
@@ -55,12 +63,7 @@ struct AggregationUnit
/**
* Wire fee we charge for @e wp at @e execution_time.
*/
- struct TALER_Amount wire_fee;
-
- /**
- * Hash of @e wire.
- */
- struct GNUNET_HashCode h_wire;
+ struct TALER_WireFeeSet fees;
/**
* Wire transfer identifier we use.
@@ -68,57 +71,75 @@ struct AggregationUnit
struct TALER_WireTransferIdentifierRawP wtid;
/**
- * Row ID of the transaction that started it all.
- */
- uint64_t row_id;
-
- /**
* The current time (which triggered the aggregation and
* defines the wire fee).
*/
- struct GNUNET_TIME_Absolute execution_time;
+ struct GNUNET_TIME_Timestamp execution_time;
/**
* Wire details of the merchant.
*/
- json_t *wire;
+ char *payto_uri;
+
+ /**
+ * Selected wire target for the aggregation.
+ */
+ struct TALER_PaytoHashP h_payto;
/**
* Exchange wire account to be used for the preparation and
* eventual execution of the aggregate wire transfer.
*/
- struct TALER_EXCHANGEDB_WireAccount *wa;
+ const struct TALER_EXCHANGEDB_AccountInfo *wa;
/**
- * Database session for all of our transactions.
+ * Row in KYC table for legitimization requirements
+ * that are pending for this aggregation, or 0 if none.
*/
- struct TALER_EXCHANGEDB_Session *session;
+ uint64_t requirement_row;
/**
- * Wire preparation handle.
+ * Set to #GNUNET_OK during transient checking
+ * while everything is OK. Otherwise see return
+ * value of #do_aggregate().
*/
- struct TALER_BANK_PrepareHandle *ph;
+ enum GNUNET_GenericReturnValue ret;
/**
- * Array of row_ids from the aggregation.
+ * Do we have an entry in the transient table for
+ * this aggregation?
*/
- uint64_t additional_rows[TALER_EXCHANGEDB_MATCHING_DEPOSITS_LIMIT];
+ bool have_transient;
+
+};
+
+
+/**
+ * Work shard we are processing.
+ */
+struct Shard
+{
+
+ /**
+ * When did we start processing the shard?
+ */
+ struct GNUNET_TIME_Timestamp start_time;
/**
- * Offset specifying how many @e additional_rows are in use.
+ * Starting row of the shard.
*/
- unsigned int rows_offset;
+ uint32_t shard_start;
/**
- * Set to #GNUNET_YES if we have to abort due to failure.
+ * Inclusive end row of the shard.
*/
- int failed;
+ uint32_t shard_end;
/**
- * Set to #GNUNET_YES if we encountered a refund during #refund_by_coin_cb.
- * Used to wave the deposit fee.
+ * Number of starting points found in the shard.
*/
- int have_refund;
+ uint64_t work_counter;
+
};
@@ -129,13 +150,26 @@ struct AggregationUnit
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 to that merchants and governments
+ * wire transfer subjects so that merchants and governments
* can ask for the list of aggregated deposits.
*/
static char *exchange_base_url;
/**
+ * Set to #GNUNET_YES if this exchange does not support KYC checks
+ * and thus deposits are to be aggregated regardless of the
+ * KYC status of the target account.
+ */
+static int kyc_off;
+
+/**
* The exchange's configuration.
*/
static const struct GNUNET_CONFIGURATION_Handle *cfg;
@@ -156,23 +190,16 @@ static struct GNUNET_SCHEDULER_Task *task;
static struct GNUNET_TIME_Relative aggregator_idle_sleep_interval;
/**
+ * How big are the shards we are processing? Is an inclusive offset, so every
+ * shard ranges from [X,X+shard_size) exclusive. So a shard covers
+ * shard_size slots. The maximum value for shard_size is INT32_MAX+1.
+ */
+static uint32_t shard_size;
+
+/**
* Value to return from main(). 0 on success, non-zero on errors.
*/
-static enum
-{
- GR_SUCCESS = 0,
- GR_DATABASE_SESSION_FAIL = 1,
- GR_DATABASE_TRANSACTION_BEGIN_FAIL = 2,
- GR_DATABASE_READY_DEPOSIT_HARD_FAIL = 3,
- GR_DATABASE_ITERATE_DEPOSIT_HARD_FAIL = 4,
- GR_DATABASE_TINY_MARK_HARD_FAIL = 5,
- GR_DATABASE_PREPARE_HARD_FAIL = 6,
- GR_DATABASE_PREPARE_COMMIT_HARD_FAIL = 7,
- GR_INVARIANT_FAILURE = 8,
- GR_CONFIGURATION_INVALID = 9,
- GR_CMD_LINE_UTF8_ERROR = 9,
- GR_CMD_LINE_OPTIONS_WRONG = 10,
-} global_ret;
+static int global_ret;
/**
* #GNUNET_YES if we are in test mode and should exit when idle.
@@ -184,13 +211,22 @@ static int test_mode;
* Main work function that queries the DB and aggregates transactions
* into larger wire transfers.
*
- * @param cls NULL
+ * @param cls a `struct Shard *`
*/
static void
run_aggregation (void *cls);
/**
+ * Work on transactions unlocked by KYC.
+ *
+ * @param cls NULL
+ */
+static void
+drain_kyc_alerts (void *cls);
+
+
+/**
* Free data stored in @a au, but not @a au itself (stack allocated).
*
* @param au aggregation unit to clean up
@@ -199,8 +235,7 @@ static void
cleanup_au (struct AggregationUnit *au)
{
GNUNET_assert (NULL != au);
- if (NULL != au->wire)
- json_decref (au->wire);
+ GNUNET_free (au->payto_uri);
memset (au,
0,
sizeof (*au));
@@ -223,6 +258,7 @@ shutdown_task (void *cls)
GNUNET_SCHEDULER_cancel (task);
task = NULL;
}
+ TALER_KYCLOGIC_kyc_done ();
TALER_EXCHANGEDB_plugin_unload (db_plugin);
db_plugin = NULL;
TALER_EXCHANGEDB_unload_accounts ();
@@ -231,12 +267,12 @@ shutdown_task (void *cls)
/**
- * Parse the configuration for wirewatch.
+ * Parse the configuration for aggregator.
*
* @return #GNUNET_OK on success
*/
-static int
-parse_wirewatch_config (void)
+static enum GNUNET_GenericReturnValue
+parse_aggregator_config (void)
{
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
@@ -265,11 +301,20 @@ parse_wirewatch_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,
- "Invalid value specified 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,
+ "exchange",
+ "AML_THRESHOLD",
+ &aml_threshold))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Need amount in section `exchange' under `AML_THRESHOLD'\n");
return GNUNET_SYSERR;
}
@@ -281,7 +326,8 @@ parse_wirewatch_config (void)
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- TALER_EXCHANGEDB_load_accounts (cfg))
+ TALER_EXCHANGEDB_load_accounts (cfg,
+ TALER_EXCHANGEDB_ALO_DEBIT))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No wire accounts configured for debit!\n");
@@ -294,679 +340,937 @@ parse_wirewatch_config (void)
/**
- * Callback invoked with information about refunds applicable
- * to a particular coin. Subtract refunded amount(s) from
- * the aggregation unit's total amount.
+ * Perform a database commit. If it fails, print a warning.
*
- * @param cls closure with a `struct AggregationUnit *`
- * @param amount_with_fee what was the refunded amount with the fee
- * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ * @return status of commit
*/
-static int
-refund_by_coin_cb (void *cls,
- const struct TALER_Amount *amount_with_fee)
+static enum GNUNET_DB_QueryStatus
+commit_or_warn (void)
{
- struct AggregationUnit *aux = cls;
+ enum GNUNET_DB_QueryStatus qs;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Aggregator subtracts applicable refund of amount %s\n",
- TALER_amount2s (amount_with_fee));
- aux->have_refund = GNUNET_YES;
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&aux->total_amount,
- &aux->total_amount,
- amount_with_fee))
+ qs = db_plugin->commit (db_plugin->cls);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return qs;
+ GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ ? GNUNET_ERROR_TYPE_INFO
+ : GNUNET_ERROR_TYPE_ERROR,
+ "Failed to commit database transaction!\n");
+ return qs;
+}
+
+
+/**
+ * Release lock on shard @a s in the database.
+ * On error, terminates this process.
+ *
+ * @param[in] s shard to free (and memory to release)
+ */
+static void
+release_shard (struct Shard *s)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->release_revolving_shard (
+ db_plugin->cls,
+ "aggregator",
+ s->shard_start,
+ s->shard_end);
+ GNUNET_free (s);
+ switch (qs)
{
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
GNUNET_break (0);
- return GNUNET_SYSERR;
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* Strange, but let's just continue */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* normal case */
+ break;
}
- return GNUNET_OK;
}
/**
- * Function called with details about deposits that have been made,
- * with the goal of executing the corresponding wire transaction.
+ * Trigger the wire transfer for the @a au_active
+ * and delete the record of the aggregation.
*
- * @param cls a `struct AggregationUnit`
- * @param row_id identifies database entry
- * @param merchant_pub public key of the merchant
- * @param coin_pub public key of the coin
- * @param amount_with_fee amount that was deposited including fee
- * @param deposit_fee amount the exchange gets to keep as transaction fees
- * @param h_contract_terms hash of the proposal data known to merchant and customer
- * @param wire_deadline by which the merchant advised that he would like the
- * wire transfer to be executed
- * @param wire wire details for the merchant
- * @return transaction status code, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT to continue to iterate
+ * @param au_active information about the aggregation
*/
static enum GNUNET_DB_QueryStatus
-deposit_cb (void *cls,
- uint64_t row_id,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct GNUNET_HashCode *h_contract_terms,
- struct GNUNET_TIME_Absolute wire_deadline,
- const json_t *wire)
+trigger_wire_transfer (const struct AggregationUnit *au_active)
{
- struct AggregationUnit *au = cls;
enum GNUNET_DB_QueryStatus qs;
- (void) cls;
- /* NOTE: potential optimization: use custom SQL API to not
- fetch this one: */
- (void) wire_deadline; /* already checked by SQL query */
- au->merchant_pub = *merchant_pub;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Aggregator processing payment %s with amount %s\n",
- TALER_B2S (coin_pub),
- TALER_amount2s (amount_with_fee));
- au->row_id = row_id;
- au->total_amount = *amount_with_fee;
- au->have_refund = GNUNET_NO;
- qs = db_plugin->select_refunds_by_coin (db_plugin->cls,
- au->session,
- coin_pub,
- &au->merchant_pub,
- h_contract_terms,
- &refund_by_coin_cb,
- au);
- if (0 > qs)
+ "Preparing wire transfer of %s to %s\n",
+ TALER_amount2s (&au_active->final_amount),
+ TALER_B2S (&au_active->merchant_pub));
{
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- if (GNUNET_NO == au->have_refund)
- {
- struct TALER_Amount ntotal;
+ void *buf;
+ size_t buf_size;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Non-refunded transaction, subtracting deposit fee %s\n",
- TALER_amount2s (deposit_fee));
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&ntotal,
- amount_with_fee,
- deposit_fee))
- {
- /* This should never happen, issue a warning, but continue processing
- with an amount of zero, least we hang here for good. */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Fatally malformed record at row %llu over %s (deposit fee exceeds deposited value)\n",
- (unsigned long long) row_id,
- TALER_amount2s (amount_with_fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (au->total_amount.currency,
- &au->total_amount));
- }
- else
- {
- au->total_amount = ntotal;
- }
+ TALER_BANK_prepare_transfer (au_active->payto_uri,
+ &au_active->final_amount,
+ exchange_base_url,
+ &au_active->wtid,
+ &buf,
+ &buf_size);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Storing %u bytes of wire prepare data\n",
+ (unsigned int) buf_size);
+ /* Commit our intention to execute the wire transfer! */
+ qs = db_plugin->wire_prepare_data_insert (db_plugin->cls,
+ au_active->wa->method,
+ buf,
+ buf_size);
+ GNUNET_free (buf);
}
+ /* Commit the WTID data to 'wire_out' */
+ if (qs >= 0)
+ qs = db_plugin->store_wire_transfer_out (db_plugin->cls,
+ au_active->execution_time,
+ &au_active->wtid,
+ &au_active->h_payto,
+ au_active->wa->section_name,
+ &au_active->final_amount);
+
+ if ( (qs >= 0) &&
+ au_active->have_transient)
+ qs = db_plugin->delete_aggregation_transient (db_plugin->cls,
+ &au_active->h_payto,
+ &au_active->wtid);
+ return qs;
+}
+
- GNUNET_assert (NULL == au->wire);
- if (NULL == (au->wire = json_incref ((json_t *) wire)))
+/**
+ * Callback to return all applicable amounts for the KYC
+ * decision to @ a cb.
+ *
+ * @param cls a `struct AggregationUnit *`
+ * @param limit time limit for the iteration
+ * @param cb function to call with the amounts
+ * @param cb_cls closure for @a cb
+ */
+static void
+return_relevant_amounts (void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ const struct AggregationUnit *au_active = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning amount %s in KYC check\n",
+ TALER_amount2s (&au_active->total_amount));
+ if (GNUNET_OK !=
+ cb (cb_cls,
+ &au_active->total_amount,
+ GNUNET_TIME_absolute_get ()))
+ return;
+ qs = db_plugin->select_aggregation_amounts_for_kyc_check (
+ db_plugin->cls,
+ &au_active->h_payto,
+ limit,
+ cb,
+ cb_cls);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to select aggregation amounts for KYC limit check!\n");
}
- if (GNUNET_OK !=
- TALER_JSON_merchant_wire_signature_hash (wire,
- &au->h_wire))
+}
+
+
+/**
+ * Test if KYC is required for a transfer to @a h_payto.
+ *
+ * @param[in,out] au_active aggregation unit to check for
+ * @return true if KYC checks are satisfied
+ */
+static bool
+kyc_satisfied (struct AggregationUnit *au_active)
+{
+ char *requirement;
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (kyc_off)
+ return true;
+ 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,
+ &requirement);
+ if (qs < 0)
{
- GNUNET_break (0);
- json_decref (au->wire);
- au->wire = NULL;
- return GNUNET_DB_STATUS_HARD_ERROR;
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return false;
}
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
- &au->wtid,
- sizeof (au->wtid));
+ if (NULL == requirement)
+ return true;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Starting aggregation under H(WTID)=%s, starting amount %s at %llu\n",
- TALER_B2S (&au->wtid),
- TALER_amount2s (amount_with_fee),
- (unsigned long long) row_id);
+ "KYC requirement for %s is %s\n",
+ TALER_amount2s (&au_active->total_amount),
+ requirement);
+ qs = db_plugin->insert_kyc_requirement_for_account (
+ db_plugin->cls,
+ requirement,
+ &au_active->h_payto,
+ NULL, /* not a reserve */
+ &au_active->requirement_row);
+ if (qs < 0)
{
- char *url;
-
- url = TALER_JSON_wire_to_payto (au->wire);
- au->wa = TALER_EXCHANGEDB_find_account_by_payto_uri (url);
- if (NULL == au->wa)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "No exchange account configured for `%s', please fix your setup to continue!\n",
- url);
- GNUNET_free (url);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- GNUNET_free (url);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to persist KYC requirement `%s' in DB!\n",
+ requirement);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Legitimization process %llu started\n",
+ (unsigned long long) au_active->requirement_row);
}
+ GNUNET_free (requirement);
+ return false;
+}
- /* make sure we have current fees */
- au->execution_time = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&au->execution_time);
+
+/**
+ * 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))
{
- struct TALER_EXCHANGEDB_AggregateFees *af;
-
- af = TALER_EXCHANGEDB_update_fees (cfg,
- db_plugin,
- au->wa,
- au->execution_time,
- au->session);
- if (NULL == af)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not get or persist wire fees for %s. Aborting run.\n",
- GNUNET_STRINGS_absolute_time_to_string (au->execution_time));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- au->wire_fee = af->wire_fee;
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
+ return GNUNET_OK;
+}
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Aggregator starts aggregation for deposit %llu to %s with wire fee %s\n",
- (unsigned long long) row_id,
- TALER_B2S (&au->wtid),
- TALER_amount2s (&au->wire_fee));
- qs = db_plugin->insert_aggregation_tracking (db_plugin->cls,
- au->session,
- &au->wtid,
- row_id);
- if (qs <= 0)
+
+/**
+ * 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 qs;
+ return false;
}
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Aggregator marks deposit %llu as done\n",
- (unsigned long long) row_id);
- qs = db_plugin->mark_deposit_done (db_plugin->cls,
- au->session,
- row_id);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ 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 qs;
+ return false;
}
- return qs;
+ 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;
}
/**
- * Function called with details about another deposit we
- * can aggregate into an existing aggregation unit.
+ * Perform the main aggregation work for @a au. Expects to be in
+ * a working transaction, which the caller must also ultimately commit
+ * (or rollback) depending on our return value.
*
- * @param cls a `struct AggregationUnit`
- * @param row_id identifies database entry
- * @param merchant_pub public key of the merchant
- * @param coin_pub public key of the coin
- * @param amount_with_fee amount that was deposited including fee
- * @param deposit_fee amount the exchange gets to keep as transaction fees
- * @param h_contract_terms hash of the proposal data known to merchant and customer
- * @param wire_deadline by which the merchant advised that he would like the
- * wire transfer to be executed
- * @param wire wire details for the merchant
- * @return transaction status code
+ * @param[in,out] au aggregation unit to work on
+ * @return #GNUNET_OK if aggregation succeeded,
+ * #GNUNET_NO to rollback and try again (serialization issue)
+ * #GNUNET_SYSERR hard error, terminate aggregator process
*/
-static enum GNUNET_DB_QueryStatus
-aggregate_cb (void *cls,
- uint64_t row_id,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct GNUNET_HashCode *h_contract_terms,
- struct GNUNET_TIME_Absolute wire_deadline,
- const json_t *wire)
+static enum GNUNET_GenericReturnValue
+do_aggregate (struct AggregationUnit *au)
{
- struct AggregationUnit *au = cls;
- struct TALER_Amount old;
enum GNUNET_DB_QueryStatus qs;
- /* NOTE: potential optimization: use custom SQL API to not
- fetch these: */
- (void) wire_deadline; /* checked by SQL */
- (void) wire; /* must match */
- GNUNET_break (0 == GNUNET_memcmp (&au->merchant_pub,
- merchant_pub));
+ au->wa = TALER_EXCHANGEDB_find_account_by_payto_uri (
+ au->payto_uri);
+ if (NULL == au->wa)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No exchange account configured for `%s', please fix your setup to continue!\n",
+ au->payto_uri);
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
- if (au->rows_offset >= TALER_EXCHANGEDB_MATCHING_DEPOSITS_LIMIT)
{
- /* Bug: we asked for at most #TALER_EXCHANGEDB_MATCHING_DEPOSITS_LIMIT results! */
- GNUNET_break (0);
- /* Skip this one, but keep going with the overall transaction */
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct TALER_MasterSignatureP master_sig;
+
+ qs = db_plugin->get_wire_fee (db_plugin->cls,
+ au->wa->method,
+ au->execution_time,
+ &start_date,
+ &end_date,
+ &au->fees,
+ &master_sig);
+ if (0 >= qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not get wire fees for %s at %s. Aborting run.\n",
+ au->wa->method,
+ GNUNET_TIME_timestamp2s (au->execution_time));
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
}
- /* add to total */
+ /* Now try to find other deposits to aggregate */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Adding transaction amount %s from row %llu to aggregation\n",
- TALER_amount2s (amount_with_fee),
- (unsigned long long) row_id);
- /* save the existing total aggregate in 'old', for later */
- old = au->total_amount;
- /* we begin with the total contribution of the current coin */
- au->total_amount = *amount_with_fee;
- /* compute contribution of this coin (after fees) */
- au->have_refund = GNUNET_NO;
- qs = db_plugin->select_refunds_by_coin (db_plugin->cls,
- au->session,
- coin_pub,
- &au->merchant_pub,
- h_contract_terms,
- &refund_by_coin_cb,
- au);
- if (0 > qs)
+ "Found ready deposit for %s, aggregating by target %s\n",
+ TALER_B2S (&au->merchant_pub),
+ au->payto_uri);
+ qs = db_plugin->select_aggregation_transient (db_plugin->cls,
+ &au->h_payto,
+ &au->merchant_pub,
+ au->wa->section_name,
+ &au->wtid,
+ &au->trans);
+ switch (qs)
{
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to lookup transient aggregates!\n");
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* serializiability issue, try again */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Serialization issue, trying again later!\n");
+ return GNUNET_NO;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &au->wtid,
+ sizeof (au->wtid));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No transient aggregation found, starting %s\n",
+ TALER_B2S (&au->wtid));
+ au->have_transient = false;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ au->have_transient = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Transient aggregation found, resuming %s\n",
+ TALER_B2S (&au->wtid));
+ break;
}
- if (GNUNET_NO == au->have_refund)
+ qs = db_plugin->aggregate (db_plugin->cls,
+ &au->h_payto,
+ &au->merchant_pub,
+ &au->wtid,
+ &au->total_amount);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
- struct TALER_Amount tmp;
-
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to execute aggregation!\n");
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ /* serializiability issue, try again */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Subtracting deposit fee %s for non-refunded coin\n",
- TALER_amount2s (deposit_fee));
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&tmp,
+ "Serialization issue, trying again later!\n");
+ return GNUNET_NO;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Aggregation total is %s.\n",
+ TALER_amount2s (&au->total_amount));
+ /* Subtract wire transfer fee and round to the unit supported by the
+ wire transfer method; Check if after rounding down, we still have
+ an amount to transfer, and if not mark as 'tiny'. */
+ if (au->have_transient)
+ GNUNET_assert (0 <=
+ TALER_amount_add (&au->total_amount,
+ &au->total_amount,
+ &au->trans));
+
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Rounding aggregate of %s\n",
+ TALER_amount2s (&au->total_amount));
+ if ( (0 >=
+ TALER_amount_subtract (&au->final_amount,
&au->total_amount,
- deposit_fee))
+ &au->fees.wire)) ||
+ (GNUNET_SYSERR ==
+ TALER_amount_round_down (&au->final_amount,
+ &currency_round_unit)) ||
+ (TALER_amount_is_zero (&au->final_amount)) ||
+ (! kyc_satisfied (au)) ||
+ (! aml_satisfied (au)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Not ready for wire transfer (%d/%s)\n",
+ qs,
+ TALER_amount2s (&au->final_amount));
+ if (au->have_transient)
+ qs = db_plugin->update_aggregation_transient (db_plugin->cls,
+ &au->h_payto,
+ &au->wtid,
+ au->requirement_row,
+ &au->total_amount);
+ else
+ qs = db_plugin->create_aggregation_transient (db_plugin->cls,
+ &au->h_payto,
+ au->wa->section_name,
+ &au->merchant_pub,
+ &au->wtid,
+ au->requirement_row,
+ &au->total_amount);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Fatally malformed record at %llu over amount %s (deposit fee exceeds deposited value)\n",
- (unsigned long long) row_id,
- TALER_amount2s (&au->total_amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (old.currency,
- &au->total_amount));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization issue, trying again later!\n");
+ return GNUNET_NO;
}
- else
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
- au->total_amount = tmp;
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
}
+ /* commit */
+ return GNUNET_OK;
}
- /* now add the au->total_amount with the (remaining) contribution of
- the current coin to the 'old' value with the current aggregate value */
+ qs = trigger_wire_transfer (au);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization issue during aggregation; trying again later!\n")
+ ;
+ return GNUNET_NO;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ default:
+ break;
+ }
{
- struct TALER_Amount tmp;
+ struct TALER_CoinDepositEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED),
+ .merchant_pub = au->merchant_pub
+ };
+
+ db_plugin->event_notify (db_plugin->cls,
+ &rep.header,
+ NULL,
+ 0);
+ }
+ return GNUNET_OK;
+
+}
+
+
+static void
+run_aggregation (void *cls)
+{
+ struct Shard *s = cls;
+ struct AggregationUnit au_active;
+ enum GNUNET_DB_QueryStatus qs;
+ enum GNUNET_GenericReturnValue ret;
- if (GNUNET_OK !=
- TALER_amount_add (&tmp,
- &au->total_amount,
- &old))
+ task = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking for ready deposits to aggregate\n");
+ /* make sure we have current fees */
+ memset (&au_active,
+ 0,
+ sizeof (au_active));
+ au_active.execution_time = GNUNET_TIME_timestamp_get ();
+ if (GNUNET_OK !=
+ db_plugin->start_deferred_wire_out (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start database transaction!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ release_shard (s);
+ return;
+ }
+ qs = db_plugin->get_ready_deposit (
+ db_plugin->cls,
+ s->shard_start,
+ s->shard_end,
+ &au_active.merchant_pub,
+ &au_active.payto_uri);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ cleanup_au (&au_active);
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to begin deposit iteration!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ release_shard (s);
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ cleanup_au (&au_active);
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_aggregation,
+ s);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Overflow or currency incompatibility during aggregation at %llu\n",
- (unsigned long long) row_id);
- /* Skip this one, but keep going! */
- au->total_amount = old;
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ uint64_t counter = s->work_counter;
+ struct GNUNET_TIME_Relative duration
+ = GNUNET_TIME_absolute_get_duration (s->start_time.abs_time);
+
+ cleanup_au (&au_active);
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Completed shard [%u,%u] after %s with %llu deposits\n",
+ (unsigned int) s->shard_start,
+ (unsigned int) s->shard_end,
+ GNUNET_TIME_relative2s (duration,
+ true),
+ (unsigned long long) counter);
+ release_shard (s);
+ if ( (GNUNET_YES == test_mode) &&
+ (0 == counter) )
+ {
+ /* in test mode, shutdown after a shard is done with 0 work */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No work done and in test mode, shutting down\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_assert (NULL == task);
+ /* If we ended up doing zero work, sleep a bit */
+ if (0 == counter)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Going to sleep for %s before trying again\n",
+ GNUNET_TIME_relative2s (aggregator_idle_sleep_interval,
+ true));
+ task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
+ &drain_kyc_alerts,
+ NULL);
+ }
+ else
+ {
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
+ NULL);
+ }
+ return;
}
- au->total_amount = tmp;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ s->work_counter++;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found ready deposit!\n");
+ /* continued below */
+ break;
}
- /* "append" to our list of rows */
- au->additional_rows[au->rows_offset++] = row_id;
- /* insert into aggregation tracking table */
- qs = db_plugin->insert_aggregation_tracking (db_plugin->cls,
- au->session,
- &au->wtid,
- row_id);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ TALER_payto_hash (au_active.payto_uri,
+ &au_active.h_payto);
+ ret = do_aggregate (&au_active);
+ cleanup_au (&au_active);
+ switch (ret)
{
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
+ case GNUNET_SYSERR:
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ db_plugin->rollback (db_plugin->cls);
+ release_shard (s);
+ return;
+ case GNUNET_NO:
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_aggregation,
+ s);
+ return;
+ case GNUNET_OK:
+ /* continued below */
+ break;
}
- qs = db_plugin->mark_deposit_done (db_plugin->cls,
- au->session,
- row_id);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Committing aggregation result\n");
+
+ /* Now we can finally commit the overall transaction, as we are
+ again consistent if all of this passes. */
+ switch (commit_or_warn ())
{
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* try again */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization issue on commit; trying again later!\n");
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_aggregation,
+ s);
+ return;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ db_plugin->rollback (db_plugin->cls); /* just in case */
+ release_shard (s);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Commit complete, going again\n");
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_aggregation,
+ s);
+ return;
+ default:
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ db_plugin->rollback (db_plugin->cls); /* just in case */
+ release_shard (s);
+ return;
}
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Aggregator marked deposit %llu as DONE\n",
- (unsigned long long) row_id);
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
/**
- * Perform a database commit. If it fails, print a warning.
+ * Select a shard to work on.
*
- * @param session session to perform the commit for.
- * @return status of commit
+ * @param cls NULL
*/
-static enum GNUNET_DB_QueryStatus
-commit_or_warn (struct TALER_EXCHANGEDB_Session *session)
+static void
+run_shard (void *cls)
{
+ struct Shard *s;
enum GNUNET_DB_QueryStatus qs;
- qs = db_plugin->commit (db_plugin->cls,
- session);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- return qs;
- GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs)
- ? GNUNET_ERROR_TYPE_INFO
- : GNUNET_ERROR_TYPE_ERROR,
- "Failed to commit database transaction!\n");
- return qs;
+ (void) cls;
+ task = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running aggregation shard\n");
+ if (GNUNET_SYSERR ==
+ db_plugin->preflight (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain database connection!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ s = GNUNET_new (struct Shard);
+ s->start_time = GNUNET_TIME_timestamp_get ();
+ qs = db_plugin->begin_revolving_shard (db_plugin->cls,
+ "aggregator",
+ shard_size,
+ 1U + INT32_MAX,
+ &s->shard_start,
+ &s->shard_end);
+ if (0 >= qs)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ static struct GNUNET_TIME_Relative delay;
+
+ GNUNET_free (s);
+ delay = GNUNET_TIME_randomized_backoff (delay,
+ GNUNET_TIME_UNIT_SECONDS);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_delayed (delay,
+ &run_shard,
+ NULL);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to begin shard (%d)!\n",
+ qs);
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting shard [%u:%u]!\n",
+ (unsigned int) s->shard_start,
+ (unsigned int) s->shard_end);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_aggregation,
+ s);
}
/**
- * Main work function that queries the DB and aggregates transactions
- * into larger wire transfers.
+ * Function called on transient aggregations matching
+ * a particular hash of a payto URI.
*
- * @param cls NULL
+ * @param cls
+ * @param payto_uri corresponding payto URI
+ * @param wtid wire transfer identifier of transient aggregation
+ * @param merchant_pub public key of the merchant
+ * @param total amount aggregated so far
+ * @return true to continue to iterate
*/
+static bool
+handle_transient_cb (
+ void *cls,
+ const char *payto_uri,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_Amount *total)
+{
+ struct AggregationUnit *au = cls;
+
+ if (GNUNET_OK != au->ret)
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ au->payto_uri = GNUNET_strdup (payto_uri);
+ au->wtid = *wtid;
+ au->merchant_pub = *merchant_pub;
+ au->trans = *total;
+ au->have_transient = true;
+ au->ret = do_aggregate (au);
+ GNUNET_free (au->payto_uri);
+ return (GNUNET_OK == au->ret);
+}
+
+
static void
-run_aggregation (void *cls)
+drain_kyc_alerts (void *cls)
{
- struct AggregationUnit au_active;
- struct TALER_EXCHANGEDB_Session *session;
enum GNUNET_DB_QueryStatus qs;
+ struct AggregationUnit au;
(void) cls;
task = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Checking for ready deposits to aggregate\n");
- if (NULL == (session = db_plugin->get_session (db_plugin->cls)))
+ "Draining KYC alerts\n");
+ memset (&au,
+ 0,
+ sizeof (au));
+ au.execution_time = GNUNET_TIME_timestamp_get ();
+ if (GNUNET_SYSERR ==
+ db_plugin->preflight (db_plugin->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to obtain database session!\n");
- global_ret = GR_DATABASE_SESSION_FAIL;
+ "Failed to obtain database connection!\n");
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
- db_plugin->start_deferred_wire_out (db_plugin->cls,
- session))
+ db_plugin->start (db_plugin->cls,
+ "handle kyc alerts"))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to start database transaction!\n");
- global_ret = GR_DATABASE_TRANSACTION_BEGIN_FAIL;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
- memset (&au_active,
- 0,
- sizeof (au_active));
- au_active.session = session;
- qs = db_plugin->get_ready_deposit (db_plugin->cls,
- session,
- &deposit_cb,
- &au_active);
- if (0 >= qs)
+ while (1)
{
- cleanup_au (&au_active);
- db_plugin->rollback (db_plugin->cls,
- session);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ qs = db_plugin->drain_kyc_alert (db_plugin->cls,
+ 1,
+ &au.h_payto);
+ switch (qs)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to execute deposit iteration!\n");
- global_ret = GR_DATABASE_READY_DEPOSIT_HARD_FAIL;
- GNUNET_SCHEDULER_shutdown ();
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
+ NULL);
return;
- }
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- /* should re-try immediately */
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ db_plugin->rollback (db_plugin->cls);
GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No more ready deposits, going to sleep\n");
- if (GNUNET_YES == test_mode)
- {
- /* in test mode, shutdown if we end up being idle */
- GNUNET_SCHEDULER_shutdown ();
- }
- else
- {
- /* nothing to do, sleep for a minute and try again */
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ qs = db_plugin->commit (db_plugin->cls);
+ if (qs < 0)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to commit KYC drain\n");
GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
- &run_aggregation,
- NULL);
+ task = GNUNET_SCHEDULER_add_now (&run_shard,
+ NULL);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* handled below */
+ break;
}
- return;
- }
- /* Now try to find other deposits to aggregate */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Found ready deposit for %s, aggregating\n",
- TALER_B2S (&au_active.merchant_pub));
- qs = db_plugin->iterate_matching_deposits (db_plugin->cls,
- session,
- &au_active.h_wire,
- &au_active.merchant_pub,
- &aggregate_cb,
- &au_active,
- TALER_EXCHANGEDB_MATCHING_DEPOSITS_LIMIT);
- if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) ||
- (GNUNET_YES == au_active.failed) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to execute deposit iteration!\n");
- cleanup_au (&au_active);
- db_plugin->rollback (db_plugin->cls,
- session);
- global_ret = GR_DATABASE_ITERATE_DEPOSIT_HARD_FAIL;
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- /* serializiability issue, try again */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Serialization issue, trying again later!\n");
- db_plugin->rollback (db_plugin->cls,
- session);
- cleanup_au (&au_active);
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
- NULL);
- return;
- }
-
- /* Subtract wire transfer fee and round to the unit supported by the
- wire transfer method; Check if after rounding down, we still have
- an amount to transfer, and if not mark as 'tiny'. */
- if ( (GNUNET_OK !=
- TALER_amount_subtract (&au_active.final_amount,
- &au_active.total_amount,
- &au_active.wire_fee)) ||
- (GNUNET_SYSERR ==
- TALER_amount_round_down (&au_active.final_amount,
- &currency_round_unit)) ||
- ( (0 == au_active.final_amount.value) &&
- (0 == au_active.final_amount.fraction) ) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Aggregate value too low for transfer (%d/%s)\n",
- qs,
- TALER_amount2s (&au_active.final_amount));
- /* Rollback ongoing transaction, as we will not use the respective
- WTID and thus need to remove the tracking data */
- db_plugin->rollback (db_plugin->cls,
- session);
-
- /* There were results, just the value was too low. Start another
- transaction to mark all* of the selected deposits as minor! */
- if (GNUNET_OK !=
- db_plugin->start (db_plugin->cls,
- session,
- "aggregator mark tiny transactions"))
+ au.ret = GNUNET_OK;
+ qs = db_plugin->find_aggregation_transient (db_plugin->cls,
+ &au.h_payto,
+ &handle_transient_cb,
+ &au);
+ switch (qs)
{
+ case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to start database transaction!\n");
- global_ret = GR_DATABASE_TRANSACTION_BEGIN_FAIL;
- cleanup_au (&au_active);
- GNUNET_SCHEDULER_shutdown ();
+ "Failed to lookup transient aggregates!\n");
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
+ NULL);
return;
- }
- /* Mark transactions by row_id as minor */
- qs = db_plugin->mark_deposit_tiny (db_plugin->cls,
- session,
- au_active.row_id);
- if (0 <= qs)
- {
- for (unsigned int i = 0; i<au_active.rows_offset; i++)
- {
- qs = db_plugin->mark_deposit_tiny (db_plugin->cls,
- session,
- au_active.additional_rows[i]);
- if (0 > qs)
- break;
- }
- }
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* serializiability issue, try again */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Serialization issue, trying again later!\n");
- db_plugin->rollback (db_plugin->cls,
- session);
- cleanup_au (&au_active);
- /* start again */
+ db_plugin->rollback (db_plugin->cls);
GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ continue; /* while (1) */
+ default:
+ break;
}
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- db_plugin->rollback (db_plugin->cls,
- session);
- cleanup_au (&au_active);
- global_ret = GR_DATABASE_TINY_MARK_HARD_FAIL;
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- /* commit */
- (void) commit_or_warn (session);
- cleanup_au (&au_active);
-
- /* start again */
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
- NULL);
- return;
- }
- {
- char *amount_s;
-
- amount_s = TALER_amount_to_string (&au_active.final_amount);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Preparing wire transfer of %s to %s\n",
- amount_s,
- TALER_B2S (&au_active.merchant_pub));
- GNUNET_free (amount_s);
- }
+ break;
+ } /* while(1) */
{
- void *buf;
- size_t buf_size;
+ enum GNUNET_GenericReturnValue ret;
+ ret = au.ret;
+ cleanup_au (&au);
+ switch (ret)
{
- char *url;
-
- url = TALER_JSON_wire_to_payto (au_active.wire);
- TALER_BANK_prepare_transfer (url,
- &au_active.final_amount,
- exchange_base_url,
- &au_active.wtid,
- &buf,
- &buf_size);
- GNUNET_free (url);
+ case GNUNET_SYSERR:
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ db_plugin->rollback (db_plugin->cls); /* just in case */
+ return;
+ case GNUNET_NO:
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
+ NULL);
+ return;
+ case GNUNET_OK:
+ /* continued below */
+ break;
}
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Storing %u bytes of wire prepare data\n",
- (unsigned int) buf_size);
- /* Commit our intention to execute the wire transfer! */
- qs = db_plugin->wire_prepare_data_insert (db_plugin->cls,
- session,
- au_active.wa->method,
- buf,
- buf_size);
- GNUNET_free (buf);
}
- /* Commit the WTID data to 'wire_out' to finally satisfy aggregation
- table constraints */
- if (qs >= 0)
- qs = db_plugin->store_wire_transfer_out (db_plugin->cls,
- session,
- au_active.execution_time,
- &au_active.wtid,
- au_active.wire,
- au_active.wa->section_name,
- &au_active.final_amount);
- cleanup_au (&au_active);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Serialization issue for prepared wire data; trying again later!\n");
- db_plugin->rollback (db_plugin->cls,
- session);
- /* start again */
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
- NULL);
- return;
- }
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- GNUNET_break (0);
- db_plugin->rollback (db_plugin->cls,
- session);
- /* die hard */
- global_ret = GR_DATABASE_PREPARE_HARD_FAIL;
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Stored wire transfer out instructions\n");
-
- /* Now we can finally commit the overall transaction, as we are
- again consistent if all of this passes. */
- switch (commit_or_warn (session))
+ switch (commit_or_warn ())
{
case GNUNET_DB_STATUS_SOFT_ERROR:
/* try again */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Commit issue for prepared wire data; trying again later!\n");
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization issue on commit; trying again later!\n");
GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
return;
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
- global_ret = GR_DATABASE_PREPARE_COMMIT_HARD_FAIL;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
+ db_plugin->rollback (db_plugin->cls); /* just in case */
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Preparation complete, going again\n");
+ "Commit complete, going again\n");
GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
return;
default:
GNUNET_break (0);
- global_ret = GR_INVARIANT_FAILURE;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
+ db_plugin->rollback (db_plugin->cls); /* just in case */
return;
}
}
@@ -986,22 +1290,46 @@ run (void *cls,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *c)
{
+ unsigned long long ass;
(void) cls;
(void) args;
(void) cfgfile;
cfg = c;
- if (GNUNET_OK != parse_wirewatch_config ())
+ if (GNUNET_OK !=
+ parse_aggregator_config ())
+ {
+ cfg = NULL;
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ "exchange",
+ "AGGREGATOR_SHARD_SIZE",
+ &ass))
+ {
+ cfg = NULL;
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if ( (0 == ass) ||
+ (ass > INT32_MAX) )
+ shard_size = 1U + INT32_MAX;
+ else
+ shard_size = (uint32_t) ass;
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_init (cfg))
{
cfg = NULL;
- global_ret = GR_CONFIGURATION_INVALID;
+ global_ret = EXIT_NOTCONFIGURED;
return;
}
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ NULL);
GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
- GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
- cls);
}
@@ -1023,26 +1351,31 @@ main (int argc,
"test",
"run in test mode and exit when idle",
&test_mode),
- GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
+ GNUNET_GETOPT_option_flag ('y',
+ "kyc-off",
+ "perform wire transfers without KYC checks",
+ &kyc_off),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue ret;
if (GNUNET_OK !=
GNUNET_STRINGS_get_utf8_args (argc, argv,
&argc, &argv))
- return GR_CMD_LINE_UTF8_ERROR;
- if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc, argv,
- "taler-exchange-aggregator",
- gettext_noop (
- "background process that aggregates and executes wire transfers"),
- options,
- &run, NULL))
- {
- GNUNET_free ((void *) argv);
- return GR_CMD_LINE_OPTIONS_WRONG;
- }
- GNUNET_free ((void *) argv);
+ return EXIT_INVALIDARGUMENT;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-exchange-aggregator",
+ gettext_noop (
+ "background process that aggregates and executes wire transfers"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
return global_ret;
}
diff --git a/src/exchange/taler-exchange-closer.c b/src/exchange/taler-exchange-closer.c
index b4faffb48..779525c4e 100644
--- a/src/exchange/taler-exchange-closer.c
+++ b/src/exchange/taler-exchange-closer.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2016-2020 Taler Systems SA
+ Copyright (C) 2016-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -37,7 +37,7 @@ static struct TALER_Amount currency_round_unit;
/**
* What is the base URL of this exchange? Used in the
- * wire transfer subjects to that merchants and governments
+ * wire transfer subjects so that merchants and governments
* can ask for the list of aggregated deposits.
*/
static char *exchange_base_url;
@@ -60,28 +60,13 @@ static struct GNUNET_SCHEDULER_Task *task;
/**
* How long should we sleep when idle before trying to find more work?
*/
-static struct GNUNET_TIME_Relative aggregator_idle_sleep_interval;
+static struct GNUNET_TIME_Relative closer_idle_sleep_interval;
/**
* Value to return from main(). 0 on success, non-zero
* on serious errors.
*/
-static enum
-{
- GR_SUCCESS = 0,
- GR_WIRE_ACCOUNT_NOT_CONFIGURED = 1,
- GR_WIRE_TRANSFER_FEES_NOT_CONFIGURED = 2,
- GR_FAILURE_TO_ROUND_AMOUNT = 3,
- GR_DATABASE_INSERT_HARD_FAIL = 4,
- GR_DATABASE_SELECT_HARD_FAIL = 5,
- GR_DATABASE_COMMIT_HARD_FAIL = 6,
- GR_DATABASE_SESSION_START_FAIL = 7,
- GR_DATABASE_TRANSACTION_BEGIN_FAIL = 8,
- GR_CONFIGURATION_INVALID = 9,
- GR_CMD_LINE_UTF8_ERROR = 10,
- GR_CMD_LINE_OPTIONS_WRONG = 11,
- GR_INVALID_PAYTO_ENCOUNTERED = 12,
-} global_ret;
+static int global_ret;
/**
* #GNUNET_YES if we are in test mode and should exit when idle.
@@ -127,8 +112,8 @@ shutdown_task (void *cls)
*
* @return #GNUNET_OK on success
*/
-static int
-parse_wirewatch_config (void)
+static enum GNUNET_GenericReturnValue
+parse_closer_config (void)
{
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
@@ -144,12 +129,12 @@ parse_wirewatch_config (void)
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
"exchange",
- "AGGREGATOR_IDLE_SLEEP_INTERVAL",
- &aggregator_idle_sleep_interval))
+ "CLOSER_IDLE_SLEEP_INTERVAL",
+ &closer_idle_sleep_interval))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchange",
- "AGGREGATOR_IDLE_SLEEP_INTERVAL");
+ "CLOSER_IDLE_SLEEP_INTERVAL");
return GNUNET_SYSERR;
}
if ( (GNUNET_OK !=
@@ -161,7 +146,7 @@ parse_wirewatch_config (void)
(0 != currency_round_unit.value) ) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid value specified in section `TALER' under `CURRENCY_ROUND_UNIT'\n");
+ "Need non-zero amount in section `TALER' under `CURRENCY_ROUND_UNIT'\n");
return GNUNET_SYSERR;
}
@@ -173,7 +158,8 @@ parse_wirewatch_config (void)
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- TALER_EXCHANGEDB_load_accounts (cfg))
+ TALER_EXCHANGEDB_load_accounts (cfg,
+ TALER_EXCHANGEDB_ALO_DEBIT))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No wire accounts configured for debit!\n");
@@ -188,16 +174,14 @@ parse_wirewatch_config (void)
/**
* Perform a database commit. If it fails, print a warning.
*
- * @param session session to perform the commit for.
* @return status of commit
*/
static enum GNUNET_DB_QueryStatus
-commit_or_warn (struct TALER_EXCHANGEDB_Session *session)
+commit_or_warn (void)
{
enum GNUNET_DB_QueryStatus qs;
- qs = db_plugin->commit (db_plugin->cls,
- session);
+ qs = db_plugin->commit (db_plugin->cls);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
return qs;
GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs)
@@ -209,57 +193,47 @@ commit_or_warn (struct TALER_EXCHANGEDB_Session *session)
/**
- * Closure for #expired_reserve_cb().
- */
-struct ExpiredReserveContext
-{
-
- /**
- * Database session we are using.
- */
- struct TALER_EXCHANGEDB_Session *session;
-
-};
-
-
-/**
* Function called with details about expired reserves.
* We trigger the reserve closure by inserting the respective
* closing record and prewire instructions into the respective
* tables.
*
- * @param cls a `struct ExpiredReserveContext *`
+ * @param cls NULL
* @param reserve_pub public key of the reserve
* @param left amount left in the reserve
* @param account_payto_uri information about the bank account that initially
* caused the reserve to be created
* @param expiration_date when did the reserve expire
- * @return transaction status code
+ * @param close_request_row row of request asking for
+ * closure, 0 for expired reserves
+ * @return #GNUNET_OK on success (continue)
+ * #GNUNET_NO on non-fatal errors (try again)
+ * #GNUNET_SYSERR on fatal errors (abort)
*/
-static enum GNUNET_DB_QueryStatus
+static enum GNUNET_GenericReturnValue
expired_reserve_cb (void *cls,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_Amount *left,
const char *account_payto_uri,
- struct GNUNET_TIME_Absolute expiration_date)
+ struct GNUNET_TIME_Timestamp expiration_date,
+ uint64_t close_request_row)
{
- struct ExpiredReserveContext *erc = cls;
- struct TALER_EXCHANGEDB_Session *session = erc->session;
- struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Timestamp now;
struct TALER_WireTransferIdentifierRawP wtid;
struct TALER_Amount amount_without_fee;
- const struct TALER_Amount *closing_fee;
- int ret;
+ struct TALER_Amount closing_fee;
+ struct TALER_WireFeeSet fees;
+ enum TALER_AmountArithmeticResult ret;
enum GNUNET_DB_QueryStatus qs;
- struct TALER_EXCHANGEDB_WireAccount *wa;
+ const struct TALER_EXCHANGEDB_AccountInfo *wa;
+ (void) cls;
/* NOTE: potential optimization: use custom SQL API to not
fetch this: */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Processing reserve closure at %s\n",
- GNUNET_STRINGS_absolute_time_to_string (expiration_date));
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
+ GNUNET_TIME_timestamp2s (expiration_date));
+ now = GNUNET_TIME_timestamp_get ();
/* lookup account we should use */
wa = TALER_EXCHANGEDB_find_account_by_payto_uri (account_payto_uri);
@@ -268,43 +242,60 @@ expired_reserve_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No wire account configured to deal with target URI `%s'\n",
account_payto_uri);
- global_ret = GR_WIRE_ACCOUNT_NOT_CONFIGURED;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
- return GNUNET_DB_STATUS_HARD_ERROR;
+ return GNUNET_SYSERR;
}
- /* lookup `closing_fee` from time of actual reserve expiration
+ /* lookup `fees` from time of actual reserve expiration
(we may be lagging behind!) */
{
- struct TALER_EXCHANGEDB_AggregateFees *af;
-
- af = TALER_EXCHANGEDB_update_fees (cfg,
- db_plugin,
- wa,
- expiration_date,
- session);
- if (NULL == af)
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct TALER_MasterSignatureP master_sig;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->get_wire_fee (db_plugin->cls,
+ wa->method,
+ expiration_date,
+ &start_date,
+ &end_date,
+ &fees,
+ &master_sig);
+ switch (qs)
{
- global_ret = GR_WIRE_TRANSFER_FEES_NOT_CONFIGURED;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not get wire fees for %s at %s. Aborting run.\n",
+ wa->method,
+ GNUNET_TIME_timestamp2s (expiration_date));
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return GNUNET_NO;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* continued below */
+ break;
}
- closing_fee = &af->closing_fee;
}
/* calculate transfer amount */
+ closing_fee = fees.closing;
ret = TALER_amount_subtract (&amount_without_fee,
left,
- closing_fee);
- if ( (GNUNET_SYSERR == ret) ||
- (GNUNET_NO == ret) )
+ &closing_fee);
+ if ( (TALER_AAR_INVALID_NEGATIVE_RESULT == ret) ||
+ (TALER_AAR_RESULT_ZERO == ret) )
{
/* Closing fee higher than or equal to remaining balance, close
without wire transfer. */
- closing_fee = left;
+ closing_fee = *left;
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (left->currency,
+ TALER_amount_set_zero (left->currency,
&amount_without_fee));
+ ret = TALER_AAR_RESULT_ZERO;
}
/* round down to enable transfer */
if (GNUNET_SYSERR ==
@@ -312,57 +303,58 @@ expired_reserve_cb (void *cls,
&currency_round_unit))
{
GNUNET_break (0);
- global_ret = GR_FAILURE_TO_ROUND_AMOUNT;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
- return GNUNET_DB_STATUS_HARD_ERROR;
+ return GNUNET_SYSERR;
}
- if ( (0 == amount_without_fee.value) &&
- (0 == amount_without_fee.fraction) )
- ret = GNUNET_NO;
-
/* NOTE: sizeof (*reserve_pub) == sizeof (wtid) right now, but to
be future-compatible, we use the memset + min construction */
memset (&wtid,
0,
sizeof (wtid));
- memcpy (&wtid,
- reserve_pub,
- GNUNET_MIN (sizeof (wtid),
- sizeof (*reserve_pub)));
- if (GNUNET_SYSERR != ret)
- qs = db_plugin->insert_reserve_closed (db_plugin->cls,
- session,
- reserve_pub,
- now,
- account_payto_uri,
- &wtid,
- left,
- closing_fee);
- else
- qs = GNUNET_DB_STATUS_HARD_ERROR;
+ GNUNET_memcpy (&wtid,
+ reserve_pub,
+ GNUNET_MIN (sizeof (wtid),
+ sizeof (*reserve_pub)));
+ qs = db_plugin->insert_reserve_closed (db_plugin->cls,
+ reserve_pub,
+ now,
+ account_payto_uri,
+ &wtid,
+ left,
+ &closing_fee,
+ close_request_row);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Closing reserve %s over %s (%d, %d)\n",
TALER_B2S (reserve_pub),
TALER_amount2s (left),
- ret,
+ (int) ret,
qs);
/* Check for hard failure */
- if ( (GNUNET_SYSERR == ret) ||
- (GNUNET_DB_STATUS_HARD_ERROR == qs) )
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
- global_ret = GR_DATABASE_INSERT_HARD_FAIL;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
- return GNUNET_DB_STATUS_HARD_ERROR;
+ return GNUNET_SYSERR;
}
- if ( (GNUNET_OK != ret) ||
- (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) )
+ if (TALER_amount_is_zero (&amount_without_fee))
{
- /* Reserve balance was almost zero OR soft error */
+ /* Reserve balance was zero OR soft error */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Reserve was virtually empty, moving on\n");
- (void) commit_or_warn (session);
- return qs;
+ qs = commit_or_warn ();
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return GNUNET_NO;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return GNUNET_OK;
+ }
}
/* success, perform wire transfer */
@@ -378,25 +370,30 @@ expired_reserve_cb (void *cls,
&buf_size);
/* Commit our intention to execute the wire transfer! */
qs = db_plugin->wire_prepare_data_insert (db_plugin->cls,
- session,
wa->method,
buf,
buf_size);
GNUNET_free (buf);
}
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ switch (qs)
{
+ case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
- global_ret = GR_DATABASE_INSERT_HARD_FAIL;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
/* start again */
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ return GNUNET_NO;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
}
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ return GNUNET_OK;
}
@@ -409,80 +406,77 @@ expired_reserve_cb (void *cls,
static void
run_reserve_closures (void *cls)
{
- struct TALER_EXCHANGEDB_Session *session;
enum GNUNET_DB_QueryStatus qs;
- struct ExpiredReserveContext erc;
- struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Timestamp now;
(void) cls;
task = NULL;
- if (NULL == (session = db_plugin->get_session (db_plugin->cls)))
+ if (GNUNET_SYSERR ==
+ db_plugin->preflight (db_plugin->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to obtain database session!\n");
- global_ret = GR_DATABASE_SESSION_START_FAIL;
+ "Failed to obtain database connection!\n");
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
db_plugin->start (db_plugin->cls,
- session,
"aggregator reserve closures"))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to start database transaction!\n");
- global_ret = GR_DATABASE_TRANSACTION_BEGIN_FAIL;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
- erc.session = session;
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
+ now = GNUNET_TIME_timestamp_get ();
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Checking for reserves to close by date %s\n",
- GNUNET_STRINGS_absolute_time_to_string (now));
- qs = db_plugin->get_expired_reserves (db_plugin->cls,
- session,
- now,
- &expired_reserve_cb,
- &erc);
- GNUNET_assert (1 >= qs);
+ GNUNET_TIME_timestamp2s (now));
+ qs = db_plugin->get_unfinished_close_requests (db_plugin->cls,
+ &expired_reserve_cb,
+ NULL);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* Try expired reserves as well */
+ qs = db_plugin->get_expired_reserves (
+ db_plugin->cls,
+ now,
+ &expired_reserve_cb,
+ NULL);
+ }
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
- db_plugin->rollback (db_plugin->cls,
- session);
- global_ret = GR_DATABASE_SELECT_HARD_FAIL;
+ db_plugin->rollback (db_plugin->cls);
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
- db_plugin->rollback (db_plugin->cls,
- session);
+ db_plugin->rollback (db_plugin->cls);
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
NULL);
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No more idle reserves, going back to aggregation\n");
- db_plugin->rollback (db_plugin->cls,
- session);
+ "No more idle reserves to close, going to sleep.\n");
+ db_plugin->rollback (db_plugin->cls);
GNUNET_assert (NULL == task);
if (GNUNET_YES == test_mode)
{
GNUNET_SCHEDULER_shutdown ();
+ return;
}
- else
- {
- task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
- &run_reserve_closures,
- NULL);
- }
+ task = GNUNET_SCHEDULER_add_delayed (closer_idle_sleep_interval,
+ &run_reserve_closures,
+ NULL);
return;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- (void) commit_or_warn (session);
+ (void) commit_or_warn ();
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
NULL);
@@ -512,10 +506,10 @@ run (void *cls,
(void) cfgfile;
cfg = c;
- if (GNUNET_OK != parse_wirewatch_config ())
+ if (GNUNET_OK != parse_closer_config ())
{
cfg = NULL;
- global_ret = GR_CONFIGURATION_INVALID;
+ global_ret = EXIT_NOTCONFIGURED;
return;
}
GNUNET_assert (NULL == task);
@@ -544,26 +538,26 @@ main (int argc,
"test",
"run in test mode and exit when idle",
&test_mode),
- GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue ret;
if (GNUNET_OK !=
GNUNET_STRINGS_get_utf8_args (argc, argv,
&argc, &argv))
- return GR_CMD_LINE_UTF8_ERROR;
- if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc, argv,
- "taler-exchange-closer",
- gettext_noop (
- "background process that closes expired reserves"),
- options,
- &run, NULL))
- {
- GNUNET_free ((void *) argv);
- return GR_CMD_LINE_OPTIONS_WRONG;
- }
- GNUNET_free ((void *) argv);
+ return EXIT_INVALIDARGUMENT;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-exchange-closer",
+ gettext_noop ("background process that closes expired reserves"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
return global_ret;
}
diff --git a/src/exchange/taler-exchange-drain.c b/src/exchange/taler-exchange-drain.c
new file mode 100644
index 000000000..d409487c1
--- /dev/null
+++ b/src/exchange/taler-exchange-drain.c
@@ -0,0 +1,431 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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-drain.c
+ * @brief Process that drains exchange profits from the escrow account
+ * and puts them into some regular account of the exchange.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <pthread.h>
+#include "taler_exchangedb_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_json_lib.h"
+#include "taler_bank_service.h"
+
+
+/**
+ * The exchange's configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Our database plugin.
+ */
+static struct TALER_EXCHANGEDB_Plugin *db_plugin;
+
+/**
+ * Our master public key.
+ */
+static struct TALER_MasterPublicKeyP master_pub;
+
+/**
+ * Next task to run, if any.
+ */
+static struct GNUNET_SCHEDULER_Task *task;
+
+/**
+ * Base URL of this exchange.
+ */
+static char *exchange_base_url;
+
+/**
+ * Value to return from main(). 0 on success, non-zero on errors.
+ */
+static int global_ret;
+
+
+/**
+ * We're being aborted with CTRL-C (or SIGTERM). Shut down.
+ *
+ * @param cls closure
+ */
+static void
+shutdown_task (void *cls)
+{
+ (void) cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running shutdown\n");
+ if (NULL != task)
+ {
+ GNUNET_SCHEDULER_cancel (task);
+ task = NULL;
+ }
+ db_plugin->rollback (db_plugin->cls); /* just in case */
+ TALER_EXCHANGEDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ TALER_EXCHANGEDB_unload_accounts ();
+ cfg = NULL;
+}
+
+
+/**
+ * Parse the configuration for drain.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_drain_config (void)
+{
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "BASE_URL",
+ &exchange_base_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ return GNUNET_SYSERR;
+ }
+
+ {
+ char *master_public_key_str;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "MASTER_PUBLIC_KEY",
+ &master_public_key_str))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "MASTER_PUBLIC_KEY");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_public_key_from_string (master_public_key_str,
+ strlen (
+ master_public_key_str),
+ &master_pub.eddsa_pub))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "MASTER_PUBLIC_KEY",
+ "invalid base32 encoding for a master public key");
+ GNUNET_free (master_public_key_str);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (master_public_key_str);
+ }
+ if (NULL ==
+ (db_plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem\n");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGEDB_load_accounts (cfg,
+ TALER_EXCHANGEDB_ALO_DEBIT
+ | TALER_EXCHANGEDB_ALO_AUTHDATA))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No wire accounts configured for debit!\n");
+ TALER_EXCHANGEDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Perform a database commit. If it fails, print a warning.
+ *
+ * @return status of commit
+ */
+static enum GNUNET_DB_QueryStatus
+commit_or_warn (void)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->commit (db_plugin->cls);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return qs;
+ GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ ? GNUNET_ERROR_TYPE_INFO
+ : GNUNET_ERROR_TYPE_ERROR,
+ "Failed to commit database transaction!\n");
+ return qs;
+}
+
+
+/**
+ * Execute a wire drain.
+ *
+ * @param cls NULL
+ */
+static void
+run_drain (void *cls)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t serial;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ char *account_section;
+ char *payto_uri;
+ struct GNUNET_TIME_Timestamp request_timestamp;
+ struct TALER_Amount amount;
+ struct TALER_MasterSignatureP master_sig;
+
+ (void) cls;
+ task = NULL;
+ if (GNUNET_OK !=
+ db_plugin->start (db_plugin->cls,
+ "run drain"))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start database transaction!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ qs = db_plugin->profit_drains_get_pending (db_plugin->cls,
+ &serial,
+ &wtid,
+ &account_section,
+ &payto_uri,
+ &request_timestamp,
+ &amount,
+ &master_sig);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Serialization failure on simple SELECT!?\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* no profit drains, finished */
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_assert (NULL == task);
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "No profit drains pending. Exiting.\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ default:
+ /* continued below */
+ break;
+ }
+ /* Check signature (again, this is a critical operation!) */
+ if (GNUNET_OK !=
+ TALER_exchange_offline_profit_drain_verify (
+ &wtid,
+ request_timestamp,
+ &amount,
+ account_section,
+ payto_uri,
+ &master_pub,
+ &master_sig))
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_assert (NULL == task);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ /* Display data for manual human check */
+ fprintf (stdout,
+ "Critical operation. MANUAL CHECK REQUIRED.\n");
+ fprintf (stdout,
+ "We will wire %s to `%s'\n based on instructions from %s.\n",
+ TALER_amount2s (&amount),
+ payto_uri,
+ GNUNET_TIME_timestamp2s (request_timestamp));
+ fprintf (stdout,
+ "Press ENTER to confirm, CTRL-D to abort.\n");
+ while (1)
+ {
+ int key;
+
+ key = getchar ();
+ if (EOF == key)
+ {
+ fprintf (stdout,
+ "Transfer aborted.\n"
+ "Re-run 'taler-exchange-drain' to try it again.\n"
+ "Contact Taler Systems SA to cancel it for good.\n"
+ "Exiting.\n");
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_assert (NULL == task);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if ('\n' == key)
+ break;
+ }
+
+ /* Note: account_section ignored for now, we
+ might want to use it here in the future... */
+ (void) account_section;
+ {
+ char *method;
+ void *buf;
+ size_t buf_size;
+
+ TALER_BANK_prepare_transfer (payto_uri,
+ &amount,
+ exchange_base_url,
+ &wtid,
+ &buf,
+ &buf_size);
+ method = TALER_payto_get_method (payto_uri);
+ qs = db_plugin->wire_prepare_data_insert (db_plugin->cls,
+ method,
+ buf,
+ buf_size);
+ GNUNET_free (method);
+ GNUNET_free (buf);
+ }
+ qs = db_plugin->profit_drains_set_finished (db_plugin->cls,
+ serial);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed: database serialization issue\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_assert (NULL == task);
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ default:
+ /* continued below */
+ break;
+ }
+ /* commit transaction + report success + exit */
+ if (0 >= commit_or_warn ())
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Profit drain triggered. Exiting.\n");
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * First task.
+ *
+ * @param cls closure, NULL
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param c configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+{
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+
+ cfg = c;
+ if (GNUNET_OK != parse_drain_config ())
+ {
+ cfg = NULL;
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_SYSERR ==
+ db_plugin->preflight (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain database connection!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_drain,
+ NULL);
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ cls);
+}
+
+
+/**
+ * The main function of the taler-exchange-drain.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-exchange-drain",
+ gettext_noop (
+ "process that executes a single profit drain"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+
+/* end of taler-exchange-drain.c */
diff --git a/src/exchange/taler-exchange-expire.c b/src/exchange/taler-exchange-expire.c
new file mode 100644
index 000000000..b2d34ee1c
--- /dev/null
+++ b/src/exchange/taler-exchange-expire.c
@@ -0,0 +1,500 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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-expire.c
+ * @brief Process that cleans up expired purses
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <pthread.h>
+#include "taler_exchangedb_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_json_lib.h"
+#include "taler_bank_service.h"
+
+
+/**
+ * Work shard we are processing.
+ */
+struct Shard
+{
+
+ /**
+ * When did we start processing the shard?
+ */
+ struct GNUNET_TIME_Timestamp start_time;
+
+ /**
+ * Starting row of the shard.
+ */
+ struct GNUNET_TIME_Absolute shard_start;
+
+ /**
+ * Inclusive end row of the shard.
+ */
+ struct GNUNET_TIME_Absolute shard_end;
+
+ /**
+ * Number of starting points found in the shard.
+ */
+ uint64_t work_counter;
+
+};
+
+
+/**
+ * The exchange's configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Our database plugin.
+ */
+static struct TALER_EXCHANGEDB_Plugin *db_plugin;
+
+/**
+ * Next task to run, if any.
+ */
+static struct GNUNET_SCHEDULER_Task *task;
+
+/**
+ * How big are the shards we are processing? Is an inclusive offset, so every
+ * shard ranges from [X,X+shard_size) exclusive. So a shard covers
+ * shard_size slots.
+ */
+static struct GNUNET_TIME_Relative shard_size;
+
+/**
+ * Value to return from main(). 0 on success, non-zero on errors.
+ */
+static int global_ret;
+
+/**
+ * #GNUNET_YES if we are in test mode and should exit when idle.
+ */
+static int test_mode;
+
+/**
+ * If this is a first-time run, we immediately
+ * try to catch up with the present.
+ */
+static bool jump_mode;
+
+
+/**
+ * Select a shard to work on.
+ *
+ * @param cls NULL
+ */
+static void
+run_shard (void *cls);
+
+
+/**
+ * We're being aborted with CTRL-C (or SIGTERM). Shut down.
+ *
+ * @param cls closure
+ */
+static void
+shutdown_task (void *cls)
+{
+ (void) cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running shutdown\n");
+ if (NULL != task)
+ {
+ GNUNET_SCHEDULER_cancel (task);
+ task = NULL;
+ }
+ TALER_EXCHANGEDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ cfg = NULL;
+}
+
+
+/**
+ * Parse the configuration for expire.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_expire_config (void)
+{
+ if (NULL ==
+ (db_plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem\n");
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Perform a database commit. If it fails, print a warning.
+ *
+ * @return status of commit
+ */
+static enum GNUNET_DB_QueryStatus
+commit_or_warn (void)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->commit (db_plugin->cls);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return qs;
+ GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ ? GNUNET_ERROR_TYPE_INFO
+ : GNUNET_ERROR_TYPE_ERROR,
+ "Failed to commit database transaction!\n");
+ return qs;
+}
+
+
+/**
+ * Release lock on shard @a s in the database.
+ * On error, terminates this process.
+ *
+ * @param[in] s shard to free (and memory to release)
+ */
+static void
+release_shard (struct Shard *s)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ unsigned long long wc = (unsigned long long) s->work_counter;
+
+ qs = db_plugin->complete_shard (
+ db_plugin->cls,
+ "expire",
+ s->shard_start.abs_value_us,
+ s->shard_end.abs_value_us);
+ GNUNET_free (s);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* Strange, but let's just continue */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purse expiration shard completed with %llu purses\n",
+ wc);
+ /* normal case */
+ break;
+ }
+ if ( (0 == wc) &&
+ (test_mode) &&
+ (! jump_mode) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "In test-mode without work. Terminating.\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
+/**
+ * Release lock on shard @a s in the database due to an abort of the
+ * operation. On error, terminates this process.
+ *
+ * @param[in] s shard to free (and memory to release)
+ */
+static void
+abort_shard (struct Shard *s)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->abort_shard (db_plugin->cls,
+ "expire",
+ s->shard_start.abs_value_us,
+ s->shard_end.abs_value_us);
+ if (0 >= qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to abort shard (%d)!\n",
+ qs);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
+/**
+ * Main function that processes the work in one shard.
+ *
+ * @param[in] cls a `struct Shard` to process
+ */
+static void
+run_expire (void *cls)
+{
+ struct Shard *s = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ task = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking for expired purses\n");
+ if (GNUNET_SYSERR ==
+ db_plugin->preflight (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain database connection!\n");
+ abort_shard (s);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ db_plugin->start (db_plugin->cls,
+ "expire-purse"))
+ {
+ GNUNET_break (0);
+ db_plugin->rollback (db_plugin->cls);
+ abort_shard (s);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ qs = db_plugin->expire_purse (db_plugin->cls,
+ s->shard_start,
+ s->shard_end);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ db_plugin->rollback (db_plugin->cls);
+ abort_shard (s);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ db_plugin->rollback (db_plugin->cls);
+ abort_shard (s);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_shard,
+ NULL);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ if (0 > commit_or_warn ())
+ {
+ db_plugin->rollback (db_plugin->cls);
+ abort_shard (s);
+ }
+ else
+ {
+ release_shard (s);
+ }
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_shard,
+ NULL);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* commit, and go again immediately */
+ s->work_counter++;
+ (void) commit_or_warn ();
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_expire,
+ s);
+ }
+}
+
+
+/**
+ * Select a shard to work on.
+ *
+ * @param cls NULL
+ */
+static void
+run_shard (void *cls)
+{
+ struct Shard *s;
+ enum GNUNET_DB_QueryStatus qs;
+
+ (void) cls;
+ task = NULL;
+ if (GNUNET_SYSERR ==
+ db_plugin->preflight (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain database connection!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ s = GNUNET_new (struct Shard);
+ s->start_time = GNUNET_TIME_timestamp_get ();
+ qs = db_plugin->begin_shard (db_plugin->cls,
+ "expire",
+ shard_size,
+ jump_mode
+ ? GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_get (),
+ shard_size).
+ abs_value_us
+ : shard_size.rel_value_us,
+ &s->shard_start.abs_value_us,
+ &s->shard_end.abs_value_us);
+ jump_mode = false;
+ if (0 >= qs)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ static struct GNUNET_TIME_Relative delay;
+
+ GNUNET_free (s);
+ delay = GNUNET_TIME_randomized_backoff (delay,
+ GNUNET_TIME_UNIT_SECONDS);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_delayed (delay,
+ &run_shard,
+ NULL);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to begin shard (%d)!\n",
+ qs);
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_TIME_absolute_is_future (s->shard_end))
+ {
+ abort_shard (s);
+ if (test_mode)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "In test-mode without work. Terminating.\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_at (s->shard_end,
+ &run_shard,
+ NULL);
+ return;
+ }
+ /* If this is a first-time run, we immediately
+ try to catch up with the present */
+ if (GNUNET_TIME_absolute_is_zero (s->shard_start))
+ jump_mode = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting shard [%llu:%llu)!\n",
+ (unsigned long long) s->shard_start.abs_value_us,
+ (unsigned long long) s->shard_end.abs_value_us);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_expire,
+ s);
+}
+
+
+/**
+ * First task.
+ *
+ * @param cls closure, NULL
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param c configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+{
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+
+ cfg = c;
+ if (GNUNET_OK != parse_expire_config ())
+ {
+ cfg = NULL;
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ "exchange",
+ "EXPIRE_SHARD_SIZE",
+ &shard_size))
+ {
+ cfg = NULL;
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_shard,
+ NULL);
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ cls);
+}
+
+
+/**
+ * The main function of the taler-exchange-expire.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, non-zero on error, see #global_ret
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_timetravel ('T',
+ "timetravel"),
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-exchange-expire",
+ gettext_noop (
+ "background process that expires purses"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+
+/* end of taler-exchange-expire.c */
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
index de251acbb..36459fbd7 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -1,18 +1,18 @@
/*
- This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
+ This file is part of TALER
+ 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
- Foundation; either version 3, or (at your option) any later version.
+ 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.
+ 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/>
-*/
+ 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.c
* @brief Serve the HTTP interface of the exchange
@@ -24,63 +24,73 @@
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
#include <microhttpd.h>
-#include <pthread.h>
+#include <sched.h>
#include <sys/resource.h>
+#include <limits.h>
+#include "taler_kyclogic_lib.h"
+#include "taler_templating_lib.h"
#include "taler_mhd_lib.h"
-#include "taler-exchange-httpd_mhd.h"
-#include "taler-exchange-httpd_deposit.h"
-#include "taler-exchange-httpd_refund.h"
-#include "taler-exchange-httpd_reserves_get.h"
-#include "taler-exchange-httpd_withdraw.h"
-#include "taler-exchange-httpd_recoup.h"
+#include "taler-exchange-httpd_age-withdraw.h"
+#include "taler-exchange-httpd_age-withdraw_reveal.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_coins_get.h"
+#include "taler-exchange-httpd_config.h"
+#include "taler-exchange-httpd_contract.h"
+#include "taler-exchange-httpd_csr.h"
+#include "taler-exchange-httpd_deposits_get.h"
+#include "taler-exchange-httpd_extensions.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_kyc-check.h"
+#include "taler-exchange-httpd_kyc-proof.h"
+#include "taler-exchange-httpd_kyc-wallet.h"
+#include "taler-exchange-httpd_kyc-webhook.h"
#include "taler-exchange-httpd_link.h"
+#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_melt.h"
+#include "taler-exchange-httpd_metrics.h"
+#include "taler-exchange-httpd_mhd.h"
+#include "taler-exchange-httpd_purses_create.h"
+#include "taler-exchange-httpd_purses_deposit.h"
+#include "taler-exchange-httpd_purses_get.h"
+#include "taler-exchange-httpd_purses_delete.h"
+#include "taler-exchange-httpd_purses_merge.h"
+#include "taler-exchange-httpd_recoup.h"
+#include "taler-exchange-httpd_recoup-refresh.h"
#include "taler-exchange-httpd_refreshes_reveal.h"
+#include "taler-exchange-httpd_refund.h"
+#include "taler-exchange-httpd_reserves_attest.h"
+#include "taler-exchange-httpd_reserves_close.h"
+#include "taler-exchange-httpd_reserves_get.h"
+#include "taler-exchange-httpd_reserves_get_attest.h"
+#include "taler-exchange-httpd_reserves_history.h"
+#include "taler-exchange-httpd_reserves_open.h"
+#include "taler-exchange-httpd_reserves_purse.h"
+#include "taler-exchange-httpd_spa.h"
#include "taler-exchange-httpd_terms.h"
#include "taler-exchange-httpd_transfers_get.h"
-#include "taler-exchange-httpd_deposits_get.h"
-#include "taler-exchange-httpd_keystate.h"
-#include "taler-exchange-httpd_wire.h"
+#include "taler_exchangedb_lib.h"
#include "taler_exchangedb_plugin.h"
-
+#include "taler_extensions.h"
+#include <gnunet/gnunet_mhd_compat.h>
/**
* Backlog for listen operation on unix domain sockets.
*/
-#define UNIX_BACKLOG 500
-
-
-/**
- * Type of the closure associated with each HTTP request to the exchange.
- */
-struct ExchangeHttpRequestClosure
-{
- /**
- * Async Scope ID associated with this request.
- */
- struct GNUNET_AsyncScopeId async_scope_id;
-
- /**
- * Opaque parsing context.
- */
- void *opaque_post_parsing_context;
-
- /**
- * Cached request handler for this request (once we have found one).
- */
- struct TEH_RequestHandler *rh;
-};
-
+#define UNIX_BACKLOG 50
/**
- * Base directory of the exchange (global)
+ * How often will we try to connect to the database before giving up?
*/
-char *TEH_exchange_directory;
+#define MAX_DB_RETRIES 5
/**
- * Directory where revocations are stored (global)
+ * Above what request latency do we start to log?
*/
-char *TEH_revocation_directory;
+#define WARN_LATENCY GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MILLISECONDS, 500)
/**
* Are clients allowed to request /keys for times other than the
@@ -91,9 +101,27 @@ char *TEH_revocation_directory;
int TEH_allow_keys_timetravel;
/**
+ * Should we allow two HTTPDs to bind to the same port?
+ */
+static int allow_address_reuse;
+
+/**
* The exchange's configuration (global)
*/
-struct GNUNET_CONFIGURATION_Handle *TEH_cfg;
+const struct GNUNET_CONFIGURATION_Handle *TEH_cfg;
+
+/**
+ * Configuration of age restriction
+ *
+ * Set after loading the library, enabled in database event handler.
+ */
+bool TEH_age_restriction_enabled = false;
+struct TALER_AgeRestrictionConfig TEH_age_restriction_config = {0};
+
+/**
+ * Handle to the HTTP server.
+ */
+static struct MHD_Daemon *mhd;
/**
* How long is caching /keys allowed at most? (global)
@@ -101,41 +129,111 @@ struct GNUNET_CONFIGURATION_Handle *TEH_cfg;
struct GNUNET_TIME_Relative TEH_max_keys_caching;
/**
+ * How long is the delay before we close reserves?
+ */
+struct GNUNET_TIME_Relative TEH_reserve_closing_delay;
+
+/**
* Master public key (according to the
* configuration in the exchange directory). (global)
*/
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)
*/
struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
/**
+ * Absolute STEFAN parameter.
+ */
+struct TALER_Amount TEH_stefan_abs;
+
+/**
+ * Logarithmic STEFAN parameter.
+ */
+struct TALER_Amount TEH_stefan_log;
+
+/**
+ * Linear STEFAN parameter.
+ */
+float TEH_stefan_lin;
+
+/**
+ * Where to redirect users from "/"?
+ */
+static char *toplevel_redirect_url;
+
+/**
+ * Our currency.
+ */
+char *TEH_currency;
+
+/**
+ * Name of the KYC-AML-trigger evaluation binary.
+ */
+char *TEH_kyc_aml_trigger;
+
+/**
+ * Option set to #GNUNET_YES if rewards are enabled.
+ */
+int TEH_enable_rewards;
+
+/**
+ * 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.
+ */
+char *TEH_base_url;
+
+/**
* Default timeout in seconds for HTTP requests.
*/
static unsigned int connection_timeout = 30;
/**
- * The HTTP Daemon.
+ * -C command-line flag given?
*/
-static struct MHD_Daemon *mhd;
+static int connection_close;
/**
- * Port to run the daemon on.
+ * -I command-line flag given?
*/
-static uint16_t serve_port;
+int TEH_check_invariants_flag;
/**
- * Path for the unix domain-socket
- * to run the daemon on.
+ * True if we should commit suicide once all active
+ * connections are finished.
*/
-static char *serve_unixpath;
+bool TEH_suicide;
/**
- * File mode for unix-domain socket.
+ * Signature of the configuration of all enabled extensions,
+ * signed by the exchange's offline master key with purpose
+ * TALER_SIGNATURE_MASTER_EXTENSION.
*/
-static mode_t unixpath_mode;
+struct TALER_MasterSignatureP TEH_extensions_sig;
+bool TEH_extensions_signed = false;
+
+/**
+ * Value to return from main()
+ */
+static int global_ret;
+
+/**
+ * Port to run the daemon on.
+ */
+static uint16_t serve_port;
/**
* Counter for the number of requests this HTTP has processed so far.
@@ -143,12 +241,43 @@ static mode_t unixpath_mode;
static unsigned long long req_count;
/**
+ * Counter for the number of open connections.
+ */
+static unsigned long long active_connections;
+
+/**
* Limit for the number of requests this HTTP may process before restarting.
* (This was added as one way of dealing with unavoidable memory fragmentation
* happening slowly over time.)
*/
static unsigned long long req_max;
+/**
+ * Length of the cspecs array.
+ */
+static unsigned int num_cspecs;
+
+/**
+ * Rendering specs for currencies.
+ */
+static struct TALER_CurrencySpecification *cspecs;
+
+/**
+ * Rendering spec for our currency.
+ */
+const struct TALER_CurrencySpecification *TEH_cspec;
+
+
+/**
+ * Context for all CURL operations (useful to the event loop)
+ */
+struct GNUNET_CURL_Context *TEH_curl_ctx;
+
+/**
+ * Context for integrating #TEH_curl_ctx with the
+ * GNUnet event loop.
+ */
+static struct GNUNET_CURL_RescheduleContext *exchange_curl_rc;
/**
* Signature of functions that handle operations on coins.
@@ -158,26 +287,41 @@ static unsigned long long req_max;
* @param root uploaded JSON data
* @return MHD result code
*/
-typedef int
+typedef MHD_RESULT
(*CoinOpHandler)(struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const json_t *root);
/**
+ * Generate a 404 "not found" reply on @a connection with
+ * the hint @a details.
+ *
+ * @param connection where to send the reply on
+ * @param details details for the error message, can be NULL
+ */
+static MHD_RESULT
+r404 (struct MHD_Connection *connection,
+ const char *details)
+{
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
+ details);
+}
+
+
+/**
* Handle a "/coins/$COIN_PUB/$OP" POST request. Parses the "coin_pub"
* EdDSA key of the coin and demultiplexes based on $OP.
*
- * @param rh context of the handler
- * @param connection the MHD connection to handle
+ * @param rc request context
* @param root uploaded JSON data
- * @param args array of additional options (first must be the
- * reserve public key, the second one should be "withdraw")
+ * @param args array of additional options
* @return MHD result code
*/
-static int
-handle_post_coins (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+static MHD_RESULT
+handle_post_coins (struct TEH_RequestContext *rc,
const json_t *root,
const char *const args[2])
{
@@ -196,10 +340,6 @@ handle_post_coins (const struct TEH_RequestHandler *rh,
} h[] = {
{
- .op = "deposit",
- .handler = &TEH_handler_deposit
- },
- {
.op = "melt",
.handler = &TEH_handler_melt
},
@@ -208,6 +348,10 @@ handle_post_coins (const struct TEH_RequestHandler *rh,
.handler = &TEH_handler_recoup
},
{
+ .op = "recoup-refresh",
+ .handler = &TEH_handler_recoup_refresh
+ },
+ {
.op = "refund",
.handler = &TEH_handler_refund
},
@@ -217,7 +361,6 @@ handle_post_coins (const struct TEH_RequestHandler *rh,
},
};
- (void) rh;
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[0],
strlen (args[0]),
@@ -225,21 +368,624 @@ handle_post_coins (const struct TEH_RequestHandler *rh,
sizeof (coin_pub)))
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
+ return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_COINS_INVALID_COIN_PUB,
- "coin public key malformed");
+ TALER_EC_EXCHANGE_GENERIC_COINS_INVALID_COIN_PUB,
+ args[0]);
}
for (unsigned int i = 0; NULL != h[i].op; i++)
if (0 == strcmp (h[i].op,
args[1]))
- return h[i].handler (connection,
+ return h[i].handler (rc->connection,
&coin_pub,
root);
- return TALER_MHD_reply_with_error (connection,
+ return r404 (rc->connection,
+ args[1]);
+}
+
+
+/**
+ * Handle a GET "/coins/$COIN_PUB[/$OP]" request. Parses the "coin_pub"
+ * EdDSA key of the coin and demultiplexes based on $OP.
+ *
+ * @param rc request context
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_get_coins (struct TEH_RequestContext *rc,
+ const char *const args[2])
+{
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ if (NULL == args[0])
+ {
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &coin_pub,
+ sizeof (coin_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_COINS_INVALID_COIN_PUB,
+ args[0]);
+ }
+ if (NULL != args[1])
+ {
+ if (0 == strcmp (args[1],
+ "history"))
+ return TEH_handler_coins_get (rc,
+ &coin_pub);
+ if (0 == strcmp (args[1],
+ "link"))
+ return TEH_handler_link (rc,
+ &coin_pub);
+ }
+ return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND,
- TALER_EC_OPERATION_INVALID,
- "requested operation on coin unknown");
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+}
+
+
+/**
+ * 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:
+ 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]);
+}
+
+
+/**
+ * Handle a "/age-withdraw/$ACH/reveal" POST request. Parses the "ACH"
+ * hash of the commitment from a previous call to
+ * /reserves/$reserve_pub/age-withdraw
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_post_age_withdraw (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[2])
+{
+ struct TALER_AgeWithdrawCommitmentHashP ach;
+
+ if (0 != strcmp ("reveal", args[1]))
+ return r404 (rc->connection,
+ args[1]);
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &ach,
+ sizeof (ach)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
+ args[0]);
+ }
+
+ return TEH_handler_age_withdraw_reveal (rc,
+ &ach,
+ root);
+}
+
+
+/**
+ * Signature of functions that handle operations on reserves.
+ *
+ * @param rc request context
+ * @param reserve_pub the public key of the reserve
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*ReserveOpHandler)(struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root);
+
+
+/**
+ * Handle a "/reserves/$RESERVE_PUB/$OP" POST request. Parses the "reserve_pub"
+ * EdDSA key of the reserve 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_reserves (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[2])
+{
+ struct TALER_ReservePublicKeyP reserve_pub;
+ static const struct
+ {
+ /**
+ * Name of the operation (args[1])
+ */
+ const char *op;
+
+ /**
+ * Function to call to perform the operation.
+ */
+ ReserveOpHandler handler;
+
+ } h[] = {
+ {
+ .op = "batch-withdraw",
+ .handler = &TEH_handler_batch_withdraw
+ },
+ {
+ .op = "age-withdraw",
+ .handler = &TEH_handler_age_withdraw
+ },
+ {
+ .op = "purse",
+ .handler = &TEH_handler_reserves_purse
+ },
+ {
+ .op = "open",
+ .handler = &TEH_handler_reserves_open
+ },
+ {
+ .op = "close",
+ .handler = &TEH_handler_reserves_close
+ },
+ {
+ .op = NULL,
+ .handler = NULL
+ },
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &reserve_pub,
+ sizeof (reserve_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
+ args[0]);
+ }
+ for (unsigned int i = 0; NULL != h[i].op; i++)
+ if (0 == strcmp (h[i].op,
+ args[1]))
+ return h[i].handler (rc,
+ &reserve_pub,
+ root);
+ return r404 (rc->connection,
+ args[1]);
+}
+
+
+/**
+ * Signature of functions that handle GET operations on reserves.
+ *
+ * @param rc request context
+ * @param reserve_pub the public key of the reserve
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*ReserveGetOpHandler)(struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub);
+
+
+/**
+ * Handle a "GET /reserves/$RESERVE_PUB[/$OP]" request. Parses the "reserve_pub"
+ * EdDSA key of the reserve and demultiplexes based on $OP.
+ *
+ * @param rc request context
+ * @param args NULL-terminated array of additional options, zero, one or two
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_get_reserves (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ struct TALER_ReservePublicKeyP reserve_pub;
+ static const struct
+ {
+ /**
+ * Name of the operation (args[1]), optional
+ */
+ const char *op;
+
+ /**
+ * Function to call to perform the operation.
+ */
+ ReserveGetOpHandler handler;
+
+ } h[] = {
+ {
+ .op = NULL,
+ .handler = &TEH_handler_reserves_get
+ },
+ {
+ .op = "history",
+ .handler = &TEH_handler_reserves_history
+ },
+ {
+ .op = NULL,
+ .handler = NULL
+ },
+ };
+
+ if ( (NULL == args[0]) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &reserve_pub,
+ sizeof (reserve_pub))) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
+ args[0]);
+ }
+ for (unsigned int i = 0; NULL != h[i].handler; i++)
+ {
+ if ( ( (NULL == args[1]) &&
+ (NULL == h[i].op) ) ||
+ ( (NULL != args[1]) &&
+ (NULL != h[i].op) &&
+ (0 == strcmp (h[i].op,
+ args[1])) ) )
+ return h[i].handler (rc,
+ &reserve_pub);
+ }
+ return r404 (rc->connection,
+ args[1]);
+}
+
+
+/**
+ * Signature of functions that handle operations on purses.
+ *
+ * @param connection HTTP request handle
+ * @param purse_pub the public key of the purse
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*PurseOpHandler)(struct MHD_Connection *connection,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *root);
+
+
+/**
+ * Handle a "/purses/$RESERVE_PUB/$OP" POST request. Parses the "purse_pub"
+ * EdDSA key of the purse 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_purses (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[2])
+{
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ static const struct
+ {
+ /**
+ * Name of the operation (args[1])
+ */
+ const char *op;
+
+ /**
+ * Function to call to perform the operation.
+ */
+ PurseOpHandler handler;
+
+ } h[] = {
+ {
+ .op = "create",
+ .handler = &TEH_handler_purses_create
+ },
+ {
+ .op = "deposit",
+ .handler = &TEH_handler_purses_deposit
+ },
+ {
+ .op = "merge",
+ .handler = &TEH_handler_purses_merge
+ },
+ {
+ .op = NULL,
+ .handler = NULL
+ },
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &purse_pub,
+ sizeof (purse_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_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->connection,
+ &purse_pub,
+ root);
+ return r404 (rc->connection,
+ args[1]);
+}
+
+
+/**
+ * Increments our request counter and checks if this
+ * process should commit suicide.
+ */
+static void
+check_suicide (void)
+{
+ int fd;
+ pid_t chld;
+ unsigned long long cnt;
+
+ cnt = req_count++;
+ if (req_max != cnt)
+ return;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Restarting exchange service after %llu requests\n",
+ cnt);
+ /* Stop accepting new connections */
+ fd = MHD_quiesce_daemon (mhd);
+ GNUNET_break (0 == close (fd));
+ /* Continue handling existing connections in child,
+ so that this process can die and be replaced by
+ systemd with a fresh one */
+ chld = fork ();
+ if (-1 == chld)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "fork");
+ _exit (1);
+ }
+ if (0 != chld)
+ {
+ /* We are the parent, instant-suicide! */
+ _exit (0);
+ }
+ TEH_suicide = true;
}
@@ -263,52 +1009,90 @@ handle_mhd_completion_callback (void *cls,
void **con_cls,
enum MHD_RequestTerminationCode toe)
{
- struct ExchangeHttpRequestClosure *ecls = *con_cls;
+ struct TEH_RequestContext *rc = *con_cls;
+ struct GNUNET_AsyncScopeSave old_scope;
(void) cls;
- (void) connection;
- (void) toe;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Request completed\n");
- if (NULL == ecls)
+ if (NULL == rc)
return;
- TALER_MHD_parse_post_cleanup_callback (ecls->opaque_post_parsing_context);
- GNUNET_free (ecls);
- *con_cls = NULL;
+ GNUNET_async_scope_enter (&rc->async_scope_id,
+ &old_scope);
+ check_suicide ();
+ TEH_check_invariants ();
+ if (NULL != rc->rh_cleaner)
+ rc->rh_cleaner (rc);
+ if (NULL != rc->root)
+ {
+ json_decref (rc->root);
+ rc->root = NULL;
+ }
+ TEH_check_invariants ();
+ {
+#if MHD_VERSION >= 0x00097304
+ const union MHD_ConnectionInfo *ci;
+ unsigned int http_status = 0;
+
+ ci = MHD_get_connection_info (connection,
+ MHD_CONNECTION_INFO_HTTP_STATUS);
+ if (NULL != ci)
+ http_status = ci->http_status;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Request for `%s' completed with HTTP status %u (%d)\n",
+ rc->url,
+ http_status,
+ toe);
+#else
+ (void) connection;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Request for `%s' completed (%d)\n",
+ rc->url,
+ toe);
+#endif
+ }
+
+ TALER_MHD_parse_post_cleanup_callback (rc->opaque_post_parsing_context);
/* Sanity-check that we didn't leave any transactions hanging */
- /* NOTE: In high-performance production, we could consider
- removing this as it should not be needed and might be costly
- (to be benchmarked). */
- TEH_plugin->preflight (TEH_plugin->cls,
- TEH_plugin->get_session (TEH_plugin->cls));
+ GNUNET_break (GNUNET_OK ==
+ TEH_plugin->preflight (TEH_plugin->cls));
+ {
+ struct GNUNET_TIME_Relative latency;
+
+ latency = GNUNET_TIME_absolute_get_duration (rc->start_time);
+ if (latency.rel_value_us >
+ WARN_LATENCY.rel_value_us)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Request for `%s' took %s\n",
+ rc->url,
+ GNUNET_STRINGS_relative_time_to_string (latency,
+ GNUNET_YES));
+ }
+ GNUNET_free (rc);
+ *con_cls = NULL;
+ GNUNET_async_scope_restore (&old_scope);
}
/**
- * We found @a rh responsible for handling a request. Parse the
+ * We found a request handler responsible for handling a request. Parse the
* @a upload_data (if applicable) and the @a url and call the
* handler.
*
- * @param rh request handler to call
- * @param connection connection being handled
+ * @param rc request context
* @param url rest of the URL to parse
- * @param inner_cls closure for the handler, if needed
* @param upload_data upload data to parse (if available)
* @param[in,out] upload_data_size number of bytes in @a upload_data
* @return MHD result code
*/
-static int
-proceed_with_handler (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+static MHD_RESULT
+proceed_with_handler (struct TEH_RequestContext *rc,
const char *url,
- void **inner_cls,
const char *upload_data,
size_t *upload_data_size)
{
- const char *args[rh->nargs + 1];
+ const struct TEH_RequestHandler *rh = rc->rh;
+ const char *args[rh->nargs + 2];
size_t ulen = strlen (url) + 1;
- json_t *root = NULL;
- int ret;
+ MHD_RESULT ret;
/* We do check for "ulen" here, because we'll later stack-allocate a buffer
of that size and don't want to enable malicious clients to cause us
@@ -320,107 +1104,459 @@ proceed_with_handler (const struct TEH_RequestHandler *rh,
/deposits/). The value should be adjusted if we ever define protocol
endpoints with plausibly longer inputs. */
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
+ return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_URI_TOO_LONG,
- TALER_EC_URI_TOO_LONG,
- "The URI given is too long");
+ TALER_EC_GENERIC_URI_TOO_LONG,
+ url);
}
/* All POST endpoints come with a body in JSON format. So we parse
the JSON here. */
- if (0 == strcasecmp (rh->method,
- MHD_HTTP_METHOD_POST))
+ if ( (0 == strcasecmp (rh->method,
+ MHD_HTTP_METHOD_POST)) &&
+ (NULL == rc->root) )
{
- int res;
+ enum GNUNET_GenericReturnValue res;
- res = TALER_MHD_parse_post_json (connection,
- inner_cls,
+ res = TALER_MHD_parse_post_json (rc->connection,
+ &rc->opaque_post_parsing_context,
upload_data,
upload_data_size,
- &root);
+ &rc->root);
if (GNUNET_SYSERR == res)
{
- GNUNET_assert (NULL == root);
- return MHD_NO; /* bad upload, could not even generate error */
+ GNUNET_assert (NULL == rc->root);
+ return MHD_NO; /* bad upload, could not even generate error */
}
- if ( (GNUNET_NO == res) || (NULL == root) )
+ if ( (GNUNET_NO == res) ||
+ (NULL == rc->root) )
{
- GNUNET_assert (NULL == root);
+ GNUNET_assert (NULL == rc->root);
return MHD_YES; /* so far incomplete upload or parser error */
}
}
{
char d[ulen];
-
- /* Parse command-line arguments, if applicable */
- if (rh->nargs > 0)
- {
- unsigned int i;
- const char *fin;
- char *sp;
-
- /* make a copy of 'url' because 'strtok_r()' will modify */
- memcpy (d,
- url,
- ulen);
- i = 0;
- args[i++] = strtok_r (d, "/", &sp);
- while ( (NULL != args[i - 1]) &&
- (i < rh->nargs) )
- args[i++] = strtok_r (NULL, "/", &sp);
- /* make sure above loop ran nicely until completion, and also
- that there is no excess data in 'd' afterwards */
- if ( (i != rh->nargs) ||
- (NULL == args[i - 1]) ||
- (NULL != (fin = strtok_r (NULL, "/", &sp))) )
- {
- char emsg[128 + 512];
-
- GNUNET_snprintf (emsg,
- sizeof (emsg),
- "Got %u/%u segments for %s request ('%s')",
- (NULL == args[i - 1])
- ? i - 1
- : i + ((NULL != fin) ? 1 : 0),
- rh->nargs,
- rh->url,
- url);
- GNUNET_break_op (0);
- if (NULL != root)
- json_decref (root);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_WRONG_NUMBER_OF_SEGMENTS,
- emsg);
- }
+ unsigned int i;
+ char *sp;
+
+ /* Parse command-line arguments */
+ /* make a copy of 'url' because 'strtok_r()' will modify */
+ GNUNET_memcpy (d,
+ url,
+ ulen);
+ i = 0;
+ args[i++] = strtok_r (d, "/", &sp);
+ while ( (NULL != args[i - 1]) &&
+ (i <= rh->nargs + 1) )
+ args[i++] = strtok_r (NULL, "/", &sp);
+ /* make sure above loop ran nicely until completion, and also
+ that there is no excess data in 'd' afterwards */
+ if ( ( (rh->nargs_is_upper_bound) &&
+ (i - 1 > rh->nargs) ) ||
+ ( (! rh->nargs_is_upper_bound) &&
+ (i - 1 != rh->nargs) ) )
+ {
+ char emsg[128 + 512];
+
+ GNUNET_snprintf (emsg,
+ sizeof (emsg),
+ "Got %u+/%u segments for `%s' request (`%s')",
+ i - 1,
+ rh->nargs,
+ rh->url,
+ url);
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS,
+ emsg);
}
-
- /* just to be safe(r), we always terminate the array with a NULL
- (which handlers should not read, but at least if they do, they'll
- crash pretty reliably...) */
- args[rh->nargs] = NULL;
+ GNUNET_assert (NULL == args[i - 1]);
/* Above logic ensures that 'root' is exactly non-NULL for POST operations,
so we test for 'root' to decide which handler to invoke. */
- if (NULL != root)
- ret = rh->handler.post (rh,
- connection,
- root,
+ if (0 == strcasecmp (rh->method,
+ MHD_HTTP_METHOD_POST))
+ ret = rh->handler.post (rc,
+ rc->root,
args);
- else /* We also only have "POST" or "GET" in the API for at this point
- (OPTIONS/HEAD are taken care of earlier) */
- ret = rh->handler.get (rh,
- connection,
+ else if (0 == strcasecmp (rh->method,
+ MHD_HTTP_METHOD_DELETE))
+ ret = rh->handler.delete (rc,
+ args);
+ else /* Only GET left */
+ ret = rh->handler.get (rc,
args);
}
- if (NULL != root)
- json_decref (root);
return ret;
}
/**
+ * Handle a "/seed" request.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be empty for this function)
+ * @return MHD result code
+ */
+static MHD_RESULT
+handler_seed (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+#define SEED_SIZE 32
+ char *body;
+ MHD_RESULT ret;
+ struct MHD_Response *resp;
+
+ (void) args;
+ body = malloc (SEED_SIZE); /* must use malloc(), because MHD will use free() */
+ if (NULL == body)
+ return MHD_NO;
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ body,
+ SEED_SIZE);
+ resp = MHD_create_response_from_buffer (SEED_SIZE,
+ body,
+ MHD_RESPMEM_MUST_FREE);
+ TALER_MHD_add_global_headers (resp);
+ ret = MHD_queue_response (rc->connection,
+ MHD_HTTP_OK,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
+#undef SEED_SIZE
+}
+
+
+/**
+ * Signature of functions that handle simple
+ * POST operations for the management API.
+ *
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*ManagementPostHandler)(
+ struct MHD_Connection *connection,
+ const json_t *root);
+
+
+/**
+ * Handle POST "/management/..." requests.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_post_management (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ static const struct
+ {
+ const char *arg0;
+ const char *arg1;
+ ManagementPostHandler handler;
+ } plain_posts[] = {
+ {
+ .arg0 = "keys",
+ .handler = &TEH_handler_management_post_keys
+ },
+ {
+ .arg0 = "wire",
+ .handler = &TEH_handler_management_post_wire
+ },
+ {
+ .arg0 = "wire",
+ .arg1 = "disable",
+ .handler = &TEH_handler_management_post_wire_disable
+ },
+ {
+ .arg0 = "wire-fee",
+ .handler = &TEH_handler_management_post_wire_fees
+ },
+ {
+ .arg0 = "global-fee",
+ .handler = &TEH_handler_management_post_global_fees
+ },
+ {
+ .arg0 = "extensions",
+ .handler = &TEH_handler_management_post_extensions
+ },
+ {
+ .arg0 = "drain",
+ .handler = &TEH_handler_management_post_drain
+ },
+ {
+ .arg0 = "aml-officers",
+ .handler = &TEH_handler_management_aml_officers
+ },
+ {
+ .arg0 = "partners",
+ .handler = &TEH_handler_management_partners
+ },
+ {
+ NULL,
+ NULL,
+ NULL
+ }
+ };
+ if (NULL == args[0])
+ {
+ GNUNET_break_op (0);
+ return r404 (rc->connection,
+ "/management");
+ }
+ if (0 == strcmp (args[0],
+ "auditors"))
+ {
+ struct TALER_AuditorPublicKeyP auditor_pub;
+
+ if (NULL == args[1])
+ return TEH_handler_management_auditors (rc->connection,
+ root);
+ if ( (NULL == args[1]) ||
+ (NULL == args[2]) ||
+ (0 != strcmp (args[2],
+ "disable")) ||
+ (NULL != args[3]) )
+ return r404 (rc->connection,
+ "/management/auditors/$AUDITOR_PUB/disable");
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[1],
+ strlen (args[1]),
+ &auditor_pub,
+ sizeof (auditor_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ args[1]);
+ }
+ return TEH_handler_management_auditors_AP_disable (rc->connection,
+ &auditor_pub,
+ root);
+ }
+ if (0 == strcmp (args[0],
+ "denominations"))
+ {
+ struct TALER_DenominationHashP h_denom_pub;
+
+ if ( (NULL == args[0]) ||
+ (NULL == args[1]) ||
+ (NULL == args[2]) ||
+ (0 != strcmp (args[2],
+ "revoke")) ||
+ (NULL != args[3]) )
+ return r404 (rc->connection,
+ "/management/denominations/$HDP/revoke");
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[1],
+ strlen (args[1]),
+ &h_denom_pub,
+ sizeof (h_denom_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ args[1]);
+ }
+ return TEH_handler_management_denominations_HDP_revoke (rc->connection,
+ &h_denom_pub,
+ root);
+ }
+ if (0 == strcmp (args[0],
+ "signkeys"))
+ {
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ if ( (NULL == args[0]) ||
+ (NULL == args[1]) ||
+ (NULL == args[2]) ||
+ (0 != strcmp (args[2],
+ "revoke")) ||
+ (NULL != args[3]) )
+ return r404 (rc->connection,
+ "/management/signkeys/$HDP/revoke");
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[1],
+ strlen (args[1]),
+ &exchange_pub,
+ sizeof (exchange_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ args[1]);
+ }
+ return TEH_handler_management_signkeys_EP_revoke (rc->connection,
+ &exchange_pub,
+ root);
+ }
+ for (unsigned int i = 0;
+ NULL != plain_posts[i].handler;
+ i++)
+ {
+ if (0 == strcmp (args[0],
+ plain_posts[i].arg0))
+ {
+ if ( ( (NULL == args[1]) &&
+ (NULL == plain_posts[i].arg1) ) ||
+ ( (NULL != args[1]) &&
+ (NULL != plain_posts[i].arg1) &&
+ (0 == strcmp (args[1],
+ plain_posts[i].arg1)) ) )
+ return plain_posts[i].handler (rc->connection,
+ root);
+ }
+ }
+ GNUNET_break_op (0);
+ return r404 (rc->connection,
+ "/management/*");
+}
+
+
+/**
+ * Handle a GET "/management" request.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be [0] == "keys")
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_get_management (struct TEH_RequestContext *rc,
+ const char *const args[2])
+{
+ if ( (NULL != args[0]) &&
+ (0 == strcmp (args[0],
+ "keys")) &&
+ (NULL == args[1]) )
+ {
+ return TEH_keys_management_get_keys_handler (rc->rh,
+ rc->connection);
+ }
+ GNUNET_break_op (0);
+ return r404 (rc->connection,
+ "/management/*");
+}
+
+
+/**
+ * Handle POST "/auditors/..." requests.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_post_auditors (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ struct TALER_AuditorPublicKeyP auditor_pub;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ if ( (NULL == args[0]) ||
+ (NULL == args[1]) ||
+ (NULL != args[2]) )
+ {
+ GNUNET_break_op (0);
+ return r404 (rc->connection,
+ "/auditors/$AUDITOR_PUB/$H_DENOM_PUB");
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &auditor_pub,
+ sizeof (auditor_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ args[0]);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[1],
+ strlen (args[1]),
+ &h_denom_pub,
+ sizeof (h_denom_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ args[1]);
+ }
+ return TEH_handler_auditors (rc->connection,
+ &auditor_pub,
+ &h_denom_pub,
+ root);
+}
+
+
+/**
+ * Generates the response for "/", redirecting the
+ * client to the ``toplevel_redirect_url``.
+ *
+ * @param rc request context
+ * @param args remaining arguments (should be empty)
+ * @return MHD result code
+ */
+static MHD_RESULT
+toplevel_redirect (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ const char *text = "Redirecting to /webui/";
+ struct MHD_Response *response;
+
+ response = MHD_create_response_from_buffer (strlen (text),
+ (void *) text,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == response)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ TALER_MHD_add_global_headers (response);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/plain"));
+ if (MHD_NO ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_LOCATION,
+ toplevel_redirect_url))
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (response);
+ return MHD_NO;
+ }
+
+ {
+ MHD_RESULT ret;
+
+ ret = MHD_queue_response (rc->connection,
+ MHD_HTTP_FOUND,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+ }
+}
+
+
+/**
* Handle incoming HTTP request.
*
* @param cls closure for MHD daemon (unused)
@@ -430,10 +1566,10 @@ proceed_with_handler (const struct TEH_RequestHandler *rh,
* @param version HTTP version (ignored)
* @param upload_data request data
* @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure for request (a `struct Buffer *`)
+ * @param con_cls closure for request (a `struct TEH_RequestContext *`)
* @return MHD result code
*/
-static int
+static MHD_RESULT
handle_mhd_request (void *cls,
struct MHD_Connection *connection,
const char *url,
@@ -453,15 +1589,11 @@ handle_mhd_request (void *cls,
.data = "User-agent: *\nDisallow: /\n",
.response_code = MHD_HTTP_OK
},
- /* Landing page, tell humans to go away. */
+ /* Landing page, redirect to toplevel_redirect_url */
{
.url = "",
.method = MHD_HTTP_METHOD_GET,
- .handler.get = TEH_handler_static_response,
- .mime_type = "text/plain",
- .data =
- "Hello, I'm the Taler exchange. This HTTP server is not for humans.\n",
- .response_code = MHD_HTTP_OK
+ .handler.get = &toplevel_redirect
},
/* AGPL licensing page, redirect to source. As per the AGPL-license, every
deployment is required to offer the user a download of the source of
@@ -472,6 +1604,23 @@ handle_mhd_request (void *cls,
.method = MHD_HTTP_METHOD_GET,
.handler.get = &TEH_handler_agpl_redirect
},
+ {
+ .url = "seed",
+ .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",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &TEH_handler_metrics
+ },
/* Terms of service */
{
.url = "terms",
@@ -488,27 +1637,59 @@ handle_mhd_request (void *cls,
{
.url = "keys",
.method = MHD_HTTP_METHOD_GET,
- .handler.get = &TEH_handler_keys,
+ .handler.get = &TEH_keys_get_handler,
},
- /* Requests for wiring information */
{
- .url = "wire",
- .method = MHD_HTTP_METHOD_GET,
- .handler.get = &TEH_handler_wire
+ .url = "batch-deposit",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &TEH_handler_batch_deposit,
+ .nargs = 0
+ },
+ /* request R, used in clause schnorr withdraw and refresh */
+ {
+ .url = "csr-melt",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &TEH_handler_csr_melt,
+ .nargs = 0
+ },
+ {
+ .url = "csr-withdraw",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &TEH_handler_csr_withdraw,
+ .nargs = 0
},
/* Withdrawing coins / interaction with reserves */
{
.url = "reserves",
.method = MHD_HTTP_METHOD_GET,
- .handler.get = &TEH_handler_reserves_get,
- .nargs = 1
+ .handler.get = &handle_get_reserves,
+ .nargs = 2,
+ .nargs_is_upper_bound = true
},
{
.url = "reserves",
.method = MHD_HTTP_METHOD_POST,
- .handler.post = &TEH_handler_withdraw,
+ .handler.post = &handle_post_reserves,
+ .nargs = 2
+ },
+ {
+ .url = "age-withdraw",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &handle_post_age_withdraw,
.nargs = 2
},
+ {
+ .url = "reserves-attest",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &TEH_handler_reserves_get_attest,
+ .nargs = 1
+ },
+ {
+ .url = "reserves-attest",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &TEH_handler_reserves_attest,
+ .nargs = 1
+ },
/* coins */
{
.url = "coins",
@@ -519,8 +1700,9 @@ handle_mhd_request (void *cls,
{
.url = "coins",
.method = MHD_HTTP_METHOD_GET,
- .handler.get = TEH_handler_link,
+ .handler.get = &handle_get_coins,
.nargs = 2,
+ .nargs_is_upper_bound = true
},
/* refreshes/$RCH/reveal */
{
@@ -543,55 +1725,160 @@ handle_mhd_request (void *cls,
.handler.get = &TEH_handler_deposits_get,
.nargs = 4
},
+ /* Operating on purses */
+ {
+ .url = "purses",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &handle_post_purses,
+ .nargs = 2
+ },
+ /* Getting purse status */
+ {
+ .url = "purses",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &TEH_handler_purses_get,
+ .nargs = 2
+ },
+ /* Deleting purse */
+ {
+ .url = "purses",
+ .method = MHD_HTTP_METHOD_DELETE,
+ .handler.delete = &TEH_handler_purses_delete,
+ .nargs = 1
+ },
+ /* Getting contracts */
+ {
+ .url = "contracts",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &TEH_handler_contracts_get,
+ .nargs = 1
+ },
+ /* KYC endpoints */
+ {
+ .url = "kyc-check",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &TEH_handler_kyc_check,
+ .nargs = 3
+ },
+ {
+ .url = "kyc-proof",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &TEH_handler_kyc_proof,
+ .nargs = 1
+ },
+ {
+ .url = "kyc-wallet",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &TEH_handler_kyc_wallet,
+ .nargs = 0
+ },
+ {
+ .url = "kyc-webhook",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &TEH_handler_kyc_webhook_get,
+ .nargs = 16, /* more is not plausible */
+ .nargs_is_upper_bound = true
+ },
+ {
+ .url = "kyc-webhook",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &TEH_handler_kyc_webhook_post,
+ .nargs = 16, /* more is not plausible */
+ .nargs_is_upper_bound = true
+ },
+ /* POST management endpoints */
+ {
+ .url = "management",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &handle_post_management,
+ .nargs = 4,
+ .nargs_is_upper_bound = true
+ },
+ /* GET management endpoints (we only really have "/management/keys") */
+ {
+ .url = "management",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &handle_get_management,
+ .nargs = 1
+ },
+ /* auditor endpoints */
+ {
+ .url = "auditors",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &handle_post_auditors,
+ .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
+ },
+ {
+ .url = "webui",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &TEH_handler_spa,
+ .nargs = 1,
+ .nargs_is_upper_bound = true
+ },
+
/* mark end of list */
{
.url = NULL
}
};
- struct ExchangeHttpRequestClosure *ecls = *con_cls;
- void **inner_cls;
+ struct TEH_RequestContext *rc = *con_cls;
struct GNUNET_AsyncScopeSave old_scope;
const char *correlation_id = NULL;
(void) cls;
(void) version;
- if (NULL == ecls)
+ if (NULL == rc)
{
- unsigned long long cnt;
-
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Handling new request\n");
- /* Atomic operation, no need for a lock ;-) */
- cnt = __sync_add_and_fetch (&req_count,
- 1LLU);
- if (req_max == cnt)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Restarting exchange service after %llu requests\n",
- cnt);
- (void) kill (getpid (),
- SIGHUP);
- }
/* We're in a new async scope! */
- ecls = *con_cls = GNUNET_new (struct ExchangeHttpRequestClosure);
- GNUNET_async_scope_fresh (&ecls->async_scope_id);
+ rc = *con_cls = GNUNET_new (struct TEH_RequestContext);
+ rc->start_time = GNUNET_TIME_absolute_get ();
+ GNUNET_async_scope_fresh (&rc->async_scope_id);
+ TEH_check_invariants ();
+ rc->url = url;
+ rc->connection = connection;
/* We only read the correlation ID on the first callback for every client */
correlation_id = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
"Taler-Correlation-Id");
- if ((NULL != correlation_id) &&
- (GNUNET_YES != GNUNET_CURL_is_valid_scope_id (correlation_id)))
+ if ( (NULL != correlation_id) &&
+ (GNUNET_YES !=
+ GNUNET_CURL_is_valid_scope_id (correlation_id)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"illegal incoming correlation ID\n");
correlation_id = NULL;
}
+
+ /* Check if upload is in bounds */
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_POST))
+ {
+ TALER_MHD_check_content_length (connection,
+ TALER_MHD_REQUEST_BUFFER_MAX);
+ }
}
- inner_cls = &ecls->opaque_post_parsing_context;
- GNUNET_async_scope_enter (&ecls->async_scope_id,
+ GNUNET_async_scope_enter (&rc->async_scope_id,
&old_scope);
+ TEH_check_invariants ();
if (NULL != correlation_id)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Handling request (%s) for URL '%s', correlation_id=%s\n",
@@ -604,9 +1891,9 @@ handle_mhd_request (void *cls,
method,
url);
/* on repeated requests, check our cache first */
- if (NULL != ecls->rh)
+ if (NULL != rc->rh)
{
- int ret;
+ MHD_RESULT ret;
const char *start;
if ('\0' == url[0])
@@ -615,23 +1902,27 @@ handle_mhd_request (void *cls,
start = strchr (url + 1, '/');
if (NULL == start)
start = "";
- ret = proceed_with_handler (ecls->rh,
- connection,
+ ret = proceed_with_handler (rc,
start,
- inner_cls,
upload_data,
upload_data_size);
GNUNET_async_scope_restore (&old_scope);
return ret;
}
+ if ( (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_OPTIONS)) &&
+ (0 == strcmp ("*",
+ url)) )
+ return TALER_MHD_reply_cors_preflight (connection);
+
if (0 == strcasecmp (method,
MHD_HTTP_METHOD_HEAD))
- method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */
+ method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */
/* parse first part of URL */
{
- int found = GNUNET_NO;
+ bool found = false;
size_t tok_size;
const char *tok;
const char *rest;
@@ -654,13 +1945,15 @@ handle_mhd_request (void *cls,
{
struct TEH_RequestHandler *rh = &handlers[i];
- if (0 != strncmp (tok,
- rh->url,
- tok_size))
+ if ( (0 != strncmp (tok,
+ rh->url,
+ tok_size)) ||
+ (tok_size != strlen (rh->url) ) )
continue;
- found = GNUNET_YES;
+ found = true;
/* The URL is a match! What we now do depends on the method. */
- if (0 == strcasecmp (method, MHD_HTTP_METHOD_OPTIONS))
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_OPTIONS))
{
GNUNET_async_scope_restore (&old_scope);
return TALER_MHD_reply_cors_preflight (connection);
@@ -669,15 +1962,13 @@ handle_mhd_request (void *cls,
if (0 == strcasecmp (method,
rh->method))
{
- int ret;
+ MHD_RESULT ret;
/* cache to avoid the loop next time */
- ecls->rh = rh;
+ rc->rh = rh;
/* run handler */
- ret = proceed_with_handler (rh,
- connection,
+ ret = proceed_with_handler (rc,
url + tok_size + 1,
- inner_cls,
upload_data,
upload_data_size);
GNUNET_async_scope_restore (&old_scope);
@@ -685,25 +1976,74 @@ handle_mhd_request (void *cls,
}
}
- if (GNUNET_YES == found)
+ if (found)
{
/* we found a matching address, but the method is wrong */
+ struct MHD_Response *reply;
+ MHD_RESULT ret;
+ char *allowed = NULL;
+
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_METHOD_NOT_ALLOWED,
- TALER_EC_METHOD_INVALID,
- "The HTTP method used is invalid for this URL");
+ for (unsigned int i = 0; NULL != handlers[i].url; i++)
+ {
+ struct TEH_RequestHandler *rh = &handlers[i];
+
+ if ( (0 != strncmp (tok,
+ rh->url,
+ tok_size)) ||
+ (tok_size != strlen (rh->url) ) )
+ continue;
+ if (NULL == allowed)
+ {
+ allowed = GNUNET_strdup (rh->method);
+ }
+ else
+ {
+ char *tmp;
+
+ GNUNET_asprintf (&tmp,
+ "%s, %s",
+ allowed,
+ rh->method);
+ GNUNET_free (allowed);
+ allowed = tmp;
+ }
+ if (0 == strcasecmp (rh->method,
+ MHD_HTTP_METHOD_GET))
+ {
+ char *tmp;
+
+ GNUNET_asprintf (&tmp,
+ "%s, %s",
+ allowed,
+ MHD_HTTP_METHOD_HEAD);
+ GNUNET_free (allowed);
+ allowed = tmp;
+ }
+ }
+ reply = TALER_MHD_make_error (TALER_EC_GENERIC_METHOD_INVALID,
+ method);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (reply,
+ MHD_HTTP_HEADER_ALLOW,
+ allowed));
+ GNUNET_free (allowed);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_METHOD_NOT_ALLOWED,
+ reply);
+ MHD_destroy_response (reply);
+ return ret;
}
}
/* No handler matches, generate not found */
{
- int ret;
+ MHD_RESULT ret;
ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
- TALER_EC_ENDPOINT_UNKNOWN,
- "No handler found for the given URL");
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ url);
GNUNET_async_scope_restore (&old_scope);
return ret;
}
@@ -716,19 +2056,44 @@ handle_mhd_request (void *cls,
*
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
exchange_serve_process_config (void)
{
+ static struct TALER_CurrencySpecification defspec = {
+ .num_fractional_input_digits = 2,
+ .num_fractional_normal_digits = 2,
+ .num_fractional_trailing_zero_digits = 2
+ };
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_init (TEH_cfg))
+ {
+ return GNUNET_SYSERR;
+ }
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_number (TEH_cfg,
"exchange",
"MAX_REQUESTS",
&req_max))
{
- req_max = ULONG_LONG_MAX;
+ req_max = ULLONG_MAX;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+ "exchangedb",
+ "IDLE_RESERVE_EXPIRATION_TIME",
+ &TEH_reserve_closing_delay))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchangedb",
+ "IDLE_RESERVE_EXPIRATION_TIME");
+ /* use default */
+ TEH_reserve_closing_delay
+ = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_WEEKS,
+ 4);
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
"exchange",
"MAX_KEYS_CACHING",
&TEH_max_keys_caching))
@@ -740,27 +2105,146 @@ exchange_serve_process_config (void)
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (TEH_cfg,
- "exchange",
- "KEYDIR",
- &TEH_exchange_directory))
+ GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
+ "exchange",
+ "KYC_AML_TRIGGER",
+ &TEH_kyc_aml_trigger))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchange",
- "KEYDIR");
+ "KYC_AML_TRIGGER");
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (TEH_cfg,
- "exchange",
- "REVOCATION_DIR",
- &TEH_revocation_directory))
+ GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
+ "exchange",
+ "TOPLEVEL_REDIRECT_URL",
+ &toplevel_redirect_url))
+ {
+ toplevel_redirect_url = GNUNET_strdup ("/terms");
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_currency (TEH_cfg,
+ &TEH_currency))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "taler",
+ "CURRENCY");
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_CONFIG_parse_currencies (TEH_cfg,
+ &num_cspecs,
+ &cspecs))
+ return GNUNET_SYSERR;
+ for (unsigned int i = 0; i<num_cspecs; i++)
+ {
+ struct TALER_CurrencySpecification *cspec;
+
+ cspec = &cspecs[i];
+ if (0 == strcmp (TEH_currency,
+ cspec->currency))
+ {
+ TEH_cspec = cspec;
+ break;
+ }
+ }
+ if (NULL == TEH_cspec)
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING,
+ "taler",
+ "CURRENCY",
+ "Lacking enabled currency specification for the given currency, using default");
+ defspec.map_alt_unit_names
+ = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("0",
+ TEH_currency)
+ );
+ defspec.name = TEH_currency;
+ GNUNET_assert (strlen (TEH_currency) <
+ sizeof (defspec.currency));
+ strcpy (defspec.currency,
+ TEH_currency);
+ TEH_cspec = &defspec;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (TEH_cfg,
+ "exchange",
+ "AML_THRESHOLD",
+ &TEH_aml_threshold))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Need amount in section `exchange' under `AML_THRESHOLD'\n");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (TEH_cfg,
+ "exchange",
+ "STEFAN_ABS",
+ &TEH_stefan_abs))
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &TEH_stefan_abs));
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (TEH_cfg,
+ "exchange",
+ "STEFAN_LOG",
+ &TEH_stefan_log))
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &TEH_stefan_log));
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_float (TEH_cfg,
+ "exchange",
+ "STEFAN_LIN",
+ &TEH_stefan_lin))
+ {
+ TEH_stefan_lin = 0.0f;
+ }
+
+ if (0 != strcmp (TEH_currency,
+ TEH_aml_threshold.currency))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Amount in section `exchange' under `AML_THRESHOLD' uses the wrong currency!\n");
+ return GNUNET_SYSERR;
+ }
+ TEH_enable_rewards
+ = GNUNET_CONFIGURATION_get_value_yesno (
+ TEH_cfg,
+ "exchange",
+ "ENABLE_REWARDS");
+ if (GNUNET_SYSERR == TEH_enable_rewards)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Need YES or NO in section `exchange' under `ENABLE_REWARDS'\n");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
+ "exchange",
+ "BASE_URL",
+ &TEH_base_url))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchange",
- "REVOCATION_DIR");
+ "BASE_URL");
return GNUNET_SYSERR;
}
+ if (! TALER_url_valid_charset (TEH_base_url))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL",
+ "invalid URL");
+ return GNUNET_SYSERR;
+ }
+
{
char *master_public_key_str;
@@ -772,49 +2256,62 @@ exchange_serve_process_config (void)
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchange",
- "master_public_key");
+ "MASTER_PUBLIC_KEY");
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_public_key_from_string (master_public_key_str,
- strlen (
- master_public_key_str),
- &TEH_master_public_key.
- eddsa_pub))
+ GNUNET_CRYPTO_eddsa_public_key_from_string (
+ master_public_key_str,
+ strlen (master_public_key_str),
+ &TEH_master_public_key.eddsa_pub))
{
- fprintf (stderr,
- "Invalid master public key given in exchange configuration.");
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "MASTER_PUBLIC_KEY",
+ "invalid base32 encoding for a master public key");
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 (GNUNET_OK !=
- TEH_WIRE_init (TEH_cfg))
- return GNUNET_SYSERR;
+ {
+ 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);
+ }
- if (NULL ==
- (TEH_plugin = TALER_EXCHANGEDB_plugin_load (TEH_cfg)))
+ for (unsigned int i = 0; i<MAX_DB_RETRIES; i++)
{
- fprintf (stderr,
- "Failed to initialize DB subsystem\n");
- TEH_WIRE_done ();
- return GNUNET_SYSERR;
+ 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 (GNUNET_OK !=
- TALER_MHD_parse_config (TEH_cfg,
- "exchange",
- &serve_port,
- &serve_unixpath,
- &unixpath_mode))
+ if (NULL == TEH_plugin)
{
- TEH_WIRE_done ();
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem. Giving up.\n");
return GNUNET_SYSERR;
}
return GNUNET_OK;
@@ -838,7 +2335,7 @@ write_stats (void)
if (NULL == benchmark_dir)
return;
GNUNET_asprintf (&s,
- "%s/taler-exchange-%llu-%llu.txt",
+ "%s/taler-exchange-%llu.txt",
benchmark_dir,
(unsigned long long) pid);
fh = GNUNET_DISK_file_open (s,
@@ -874,16 +2371,11 @@ write_stats (void)
/* Developer logic for supporting the `-f' option. */
#if HAVE_DEVELOPER
-
/**
* Option `-f' (specifies an input file to give to the HTTP server).
*/
static char *input_filename;
-/**
- * We finished handling the request and should now terminate.
- */
-static int do_terminate;
/**
* Run 'nc' or 'ncat' as a fake HTTP client using #input_filename
@@ -903,7 +2395,8 @@ run_fake_client (void)
"-"))
fd = STDIN_FILENO;
else
- fd = open (input_filename, O_RDONLY);
+ fd = open (input_filename,
+ O_RDONLY);
if (-1 == fd)
{
fprintf (stderr,
@@ -951,9 +2444,55 @@ run_fake_client (void)
/**
+ * Run the exchange to serve a single request only, without threads.
+ *
+ * @return #GNUNET_OK on success
+ */
+static void
+run_single_request (void)
+{
+ pid_t xfork;
+
+ xfork = fork ();
+ if (-1 == xfork)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "fork");
+ global_ret = EXIT_NO_RESTART;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (0 == xfork)
+ {
+ pid_t cld;
+
+ cld = run_fake_client ();
+ if (-1 == cld)
+ _exit (EXIT_FAILURE);
+ _exit (EXIT_SUCCESS);
+ }
+
+ {
+ int status;
+
+ if (xfork != waitpid (xfork,
+ &status,
+ 0))
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Waiting for `nc' child failed: %s\n",
+ strerror (errno));
+ }
+}
+
+
+/* end of HAVE_DEVELOPER */
+#endif
+
+
+/**
* Signature of the callback used by MHD to notify the application
* about completed connections. If we are running in test-mode with
- * an #input_filename, this function is used to terminate the HTTPD
+ * an input_filename, this function is used to terminate the HTTPD
* after the first request has been processed.
*
* @param cls client-defined closure, NULL
@@ -970,217 +2509,224 @@ connection_done (void *cls,
(void) cls;
(void) connection;
(void) socket_context;
+
+ switch (toe)
+ {
+ case MHD_CONNECTION_NOTIFY_STARTED:
+ active_connections++;
+ break;
+ case MHD_CONNECTION_NOTIFY_CLOSED:
+ active_connections--;
+ if (TEH_suicide &&
+ (0 == active_connections) )
+ GNUNET_SCHEDULER_shutdown ();
+ break;
+ }
+#if HAVE_DEVELOPER
/* We only act if the connection is closed. */
if (MHD_CONNECTION_NOTIFY_CLOSED != toe)
return;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Connection done!\n");
- do_terminate = GNUNET_YES;
+ if (NULL != input_filename)
+ GNUNET_SCHEDULER_shutdown ();
+#endif
}
/**
- * Run the exchange to serve a single request only, without threads.
+ * Function run on shutdown.
*
- * @return #GNUNET_OK on success
+ * @param cls NULL
*/
-static int
-run_single_request (void)
+static void
+do_shutdown (void *cls)
{
- pid_t cld;
- int status;
-
- /* run only the testfile input, then terminate */
- mhd
- = MHD_start_daemon (MHD_USE_PIPE_FOR_SHUTDOWN
- | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
- | MHD_USE_TCP_FASTOPEN,
- 0, /* pick free port */
- NULL, NULL,
- &handle_mhd_request, NULL,
- MHD_OPTION_LISTEN_BACKLOG_SIZE, (unsigned int) 10,
- MHD_OPTION_EXTERNAL_LOGGER, &TALER_MHD_handle_logs,
- NULL,
- MHD_OPTION_NOTIFY_COMPLETED,
- &handle_mhd_completion_callback, NULL,
- MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout,
- MHD_OPTION_NOTIFY_CONNECTION, &connection_done, NULL,
- MHD_OPTION_END);
- if (NULL == mhd)
+ struct MHD_Daemon *mhd;
+ (void) cls;
+
+ mhd = TALER_MHD_daemon_stop ();
+ TEH_resume_keys_requests (true);
+ TEH_deposits_get_cleanup ();
+ TEH_reserves_get_cleanup ();
+ TEH_purses_get_cleanup ();
+ TEH_kyc_check_cleanup ();
+ TEH_kyc_proof_cleanup ();
+ TALER_KYCLOGIC_kyc_done ();
+ if (NULL != mhd)
{
- fprintf (stderr,
- "Failed to start HTTP server.\n");
- return GNUNET_SYSERR;
+ MHD_stop_daemon (mhd);
+ mhd = NULL;
}
- serve_port = MHD_get_daemon_info (mhd,
- MHD_DAEMON_INFO_BIND_PORT)->port;
- cld = run_fake_client ();
- if (-1 == cld)
- return GNUNET_SYSERR;
- /* run the event loop until #connection_done() was called */
- while (GNUNET_NO == do_terminate)
- {
- fd_set rs;
- fd_set ws;
- fd_set es;
- struct timeval tv;
- MHD_UNSIGNED_LONG_LONG timeout;
- int maxsock = -1;
- int have_tv;
-
- FD_ZERO (&rs);
- FD_ZERO (&ws);
- FD_ZERO (&es);
- if (MHD_YES !=
- MHD_get_fdset (mhd,
- &rs,
- &ws,
- &es,
- &maxsock))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- have_tv = MHD_get_timeout (mhd,
- &timeout);
- tv.tv_sec = timeout / 1000;
- tv.tv_usec = 1000 * (timeout % 1000);
- if (-1 == select (maxsock + 1,
- &rs,
- &ws,
- &es,
- have_tv ? &tv : NULL))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- MHD_run (mhd);
+ TEH_wire_done ();
+ TEH_extensions_done ();
+ TEH_keys_finished ();
+ if (NULL != TEH_plugin)
+ {
+ TALER_EXCHANGEDB_plugin_unload (TEH_plugin);
+ TEH_plugin = NULL;
}
- MHD_stop_daemon (mhd);
- mhd = NULL;
- if (cld != waitpid (cld,
- &status,
- 0))
- fprintf (stderr,
- "Waiting for `nc' child failed: %s\n",
- strerror (errno));
- return GNUNET_OK;
+ if (NULL != TEH_curl_ctx)
+ {
+ GNUNET_CURL_fini (TEH_curl_ctx);
+ TEH_curl_ctx = NULL;
+ }
+ if (NULL != exchange_curl_rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (exchange_curl_rc);
+ exchange_curl_rc = NULL;
+ }
+ TALER_TEMPLATING_done ();
+ TEH_cspec = NULL;
+ TALER_CONFIG_free_currencies (num_cspecs,
+ cspecs);
+ num_cspecs = 0;
+ cspecs = NULL;
}
-/* end of HAVE_DEVELOPER */
-#endif
-
-
/**
- * Run the ordinary multi-threaded main loop and the logic to
- * wait for CTRL-C.
+ * Main function that will be run by the scheduler.
*
- * @param fh listen socket
- * @param argv command line arguments
- * @return #GNUNET_OK on success
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be
+ * NULL!)
+ * @param config configuration
*/
-static int
-run_main_loop (int fh,
- char *const *argv)
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *config)
{
- int ret;
-
- mhd
- = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_PIPE_FOR_SHUTDOWN
- | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
- | MHD_USE_INTERNAL_POLLING_THREAD
- | MHD_USE_TCP_FASTOPEN,
- (-1 == fh) ? serve_port : 0,
- NULL, NULL,
- &handle_mhd_request, NULL,
- MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) 32,
- MHD_OPTION_LISTEN_BACKLOG_SIZE, (unsigned int) 1024,
- MHD_OPTION_LISTEN_SOCKET, fh,
- MHD_OPTION_EXTERNAL_LOGGER, &TALER_MHD_handle_logs,
- NULL,
- MHD_OPTION_NOTIFY_COMPLETED,
- &handle_mhd_completion_callback, NULL,
- MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout,
- MHD_OPTION_END);
- if (NULL == mhd)
+ enum TALER_MHD_GlobalOptions go;
+ int fh;
+
+ (void) cls;
+ (void) args;
+ (void ) cfgfile;
+ go = TALER_MHD_GO_NONE;
+ if (connection_close)
+ go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
+ TALER_MHD_setup (go);
+ TEH_cfg = config;
+
+ if (GNUNET_OK !=
+ exchange_serve_process_config ())
{
- fprintf (stderr,
- "Failed to start HTTP server.\n");
- return GNUNET_SYSERR;
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
}
-
- atexit (&write_stats);
- ret = TEH_KS_loop ();
- switch (ret)
+ if (GNUNET_OK !=
+ TEH_spa_init ())
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TEMPLATING_init ("exchange"))
+ {
+ global_ret = EXIT_NOTINSTALLED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
{
- case GNUNET_OK:
- case GNUNET_SYSERR:
- MHD_stop_daemon (mhd);
- break;
- case GNUNET_NO:
- {
- MHD_socket sock = MHD_quiesce_daemon (mhd);
- pid_t chld;
- int flags;
-
- /* Set flags to make 'sock' inherited by child */
- flags = fcntl (sock, F_GETFD);
- GNUNET_assert (-1 != flags);
- flags &= ~FD_CLOEXEC;
- GNUNET_assert (-1 != fcntl (sock, F_SETFD, flags));
- chld = fork ();
- if (-1 == chld)
- {
- /* fork() failed, continue clean up, unhappily */
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "fork");
- }
- if (0 == chld)
- {
- char pids[12];
-
- /* exec another taler-exchange-httpd, passing on the listen socket;
- as in systemd it is expected to be on FD #3 */
- if (3 != dup2 (sock, 3))
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "dup2");
- _exit (1);
- }
- /* Tell the child that it is the desired recipient for FD #3 */
- GNUNET_snprintf (pids,
- sizeof (pids),
- "%u",
- getpid ());
- setenv ("LISTEN_PID", pids, 1);
- setenv ("LISTEN_FDS", "1", 1);
- /* Finally, exec the (presumably) more recent exchange binary */
- execvp ("taler-exchange-httpd",
- argv);
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "execvp");
- _exit (1);
- }
- /* we're the original process, handle remaining contextions
- before exiting; as the listen socket is no longer used,
- close it here */
- GNUNET_break (0 == close (sock));
- while (0 != MHD_get_daemon_info (mhd,
- MHD_DAEMON_INFO_CURRENT_CONNECTIONS)->
- num_connections)
- sleep (1);
- /* Now we're really done, practice clean shutdown */
- MHD_stop_daemon (mhd);
- }
- break;
- default:
GNUNET_break (0);
- MHD_stop_daemon (mhd);
- break;
+ global_ret = EXIT_NO_RESTART;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TEH_extensions_init ())
+ {
+ global_ret = EXIT_NOTINSTALLED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TEH_keys_init ())
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_NO_RESTART;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TEH_wire_init ())
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_NO_RESTART;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
}
- return ret;
+ TEH_load_terms (TEH_cfg);
+ TEH_curl_ctx
+ = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &exchange_curl_rc);
+ if (NULL == TEH_curl_ctx)
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_NO_RESTART;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ exchange_curl_rc = GNUNET_CURL_gnunet_rc_create (TEH_curl_ctx);
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ fh = TALER_MHD_bind (TEH_cfg,
+ "exchange",
+ &serve_port);
+ if ( (0 == serve_port) &&
+ (-1 == fh) )
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME
+ | MHD_USE_PIPE_FOR_SHUTDOWN
+ | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
+ | MHD_USE_TCP_FASTOPEN,
+ (-1 == fh) ? serve_port : 0,
+ NULL, NULL,
+ &handle_mhd_request, NULL,
+ MHD_OPTION_LISTEN_BACKLOG_SIZE,
+ (unsigned int) 1024,
+ MHD_OPTION_LISTEN_SOCKET,
+ fh,
+ MHD_OPTION_EXTERNAL_LOGGER,
+ &TALER_MHD_handle_logs,
+ NULL,
+ MHD_OPTION_NOTIFY_COMPLETED,
+ &handle_mhd_completion_callback,
+ NULL,
+ MHD_OPTION_NOTIFY_CONNECTION,
+ &connection_done,
+ NULL,
+ MHD_OPTION_CONNECTION_TIMEOUT,
+ connection_timeout,
+ (0 == allow_address_reuse)
+ ? MHD_OPTION_END
+ : MHD_OPTION_LISTENING_ADDRESS_REUSE,
+ (unsigned int) allow_address_reuse,
+ MHD_OPTION_END);
+ if (NULL == mhd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to launch HTTP service. Is the port in use?\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ global_ret = EXIT_SUCCESS;
+ TALER_MHD_daemon_start (mhd);
+ atexit (&write_stats);
+#if HAVE_DEVELOPER
+ if (NULL != input_filename)
+ run_single_request ();
+#endif
}
@@ -1195,10 +2741,6 @@ int
main (int argc,
char *const *argv)
{
- char *cfgfile = NULL;
- char *loglev = NULL;
- char *logfile = NULL;
- int connection_close = GNUNET_NO;
const struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_option_flag ('a',
"allow-timetravel",
@@ -1208,7 +2750,14 @@ main (int argc,
"connection-close",
"force HTTP connections to be closed after each request",
&connection_close),
- GNUNET_GETOPT_option_cfgfile (&cfgfile),
+ GNUNET_GETOPT_option_flag ('I',
+ "check-invariants",
+ "enable expensive invariant checks",
+ &TEH_check_invariants_flag),
+ GNUNET_GETOPT_option_flag ('r',
+ "allow-reuse-address",
+ "allow multiple HTTPDs to listen to the same port",
+ &allow_address_reuse),
GNUNET_GETOPT_option_uint ('t',
"timeout",
"SECONDS",
@@ -1225,114 +2774,21 @@ main (int argc,
#endif
GNUNET_GETOPT_option_help (
"HTTP server providing a RESTful API to access a Taler exchange"),
- GNUNET_GETOPT_option_loglevel (&loglev),
- GNUNET_GETOPT_option_logfile (&logfile),
- GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
GNUNET_GETOPT_OPTION_END
};
- int ret;
- const char *listen_pid;
- const char *listen_fds;
- int fh = -1;
- enum TALER_MHD_GlobalOptions go;
-
- if (0 >=
- GNUNET_GETOPT_run ("taler-exchange-httpd",
- options,
- argc, argv))
- return 1;
- go = TALER_MHD_GO_NONE;
- if (connection_close)
- go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
- TALER_MHD_setup (go);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_log_setup ("taler-exchange-httpd",
- (NULL == loglev) ? "INFO" : loglev,
- logfile));
- GNUNET_free_non_null (loglev);
- GNUNET_free_non_null (logfile);
- if (NULL == cfgfile)
- cfgfile = GNUNET_strdup (GNUNET_OS_project_data_get ()->user_config_file);
- TEH_cfg = GNUNET_CONFIGURATION_create ();
- if (GNUNET_SYSERR ==
- GNUNET_CONFIGURATION_load (TEH_cfg,
- cfgfile))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Malformed configuration file `%s', exit ...\n",
- cfgfile);
- GNUNET_free_non_null (cfgfile);
- return 1;
- }
- GNUNET_free_non_null (cfgfile);
- if (GNUNET_OK !=
- exchange_serve_process_config ())
- return 1;
- TEH_load_terms (TEH_cfg);
-
- /* check for systemd-style FD passing */
- listen_pid = getenv ("LISTEN_PID");
- listen_fds = getenv ("LISTEN_FDS");
- if ( (NULL != listen_pid) &&
- (NULL != listen_fds) &&
- (getpid () == strtol (listen_pid,
- NULL,
- 10)) &&
- (1 == strtoul (listen_fds,
- NULL,
- 10)) )
- {
- int flags;
-
- fh = 3;
- flags = fcntl (fh,
- F_GETFD);
- if ( (-1 == flags) &&
- (EBADF == errno) )
- {
- fprintf (stderr,
- "Bad listen socket passed, ignored\n");
- fh = -1;
- }
- flags |= FD_CLOEXEC;
- if ( (-1 != fh) &&
- (0 != fcntl (fh,
- F_SETFD,
- flags)) )
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "fcntl");
- }
-
- /* initialize #internal_key_state with an RC of 1 */
- ret = TEH_KS_init ();
- if (GNUNET_OK == ret)
- {
-#if HAVE_DEVELOPER
- if (NULL != input_filename)
- {
- ret = run_single_request ();
- }
- else
-#endif
- {
- /* consider unix path */
- if ( (-1 == fh) &&
- (NULL != serve_unixpath) )
- {
- fh = TALER_MHD_open_unix_path (serve_unixpath,
- unixpath_mode);
- if (-1 == fh)
- return 1;
- }
- ret = run_main_loop (fh,
- argv);
- }
- /* release #internal_key_state */
- TEH_KS_free ();
- }
- TALER_EXCHANGEDB_plugin_unload (TEH_plugin);
- TEH_WIRE_done ();
- return (GNUNET_SYSERR == ret) ? 1 : 0;
+ enum GNUNET_GenericReturnValue ret;
+
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (argc, argv,
+ "taler-exchange-httpd",
+ "Taler exchange HTTP service",
+ options,
+ &run, NULL);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
}
diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h
index 512fae8f0..25e9e1105 100644
--- a/src/exchange/taler-exchange-httpd.h
+++ b/src/exchange/taler-exchange-httpd.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014, 2015, 2020 Taler Systems SA
+ Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -25,7 +25,10 @@
#include <microhttpd.h>
#include "taler_json_lib.h"
-#include "taler_crypto_lib.h"
+#include "taler_util.h"
+#include "taler_kyclogic_plugin.h"
+#include "taler_extensions.h"
+#include <gnunet/gnunet_mhd_compat.h>
/**
@@ -34,9 +37,14 @@
extern struct GNUNET_TIME_Relative TEH_max_keys_caching;
/**
+ * How long is the delay before we close reserves?
+ */
+extern struct GNUNET_TIME_Relative TEH_reserve_closing_delay;
+
+/**
* The exchange's configuration.
*/
-extern struct GNUNET_CONFIGURATION_Handle *TEH_cfg;
+extern const struct GNUNET_CONFIGURATION_Handle *TEH_cfg;
/**
* Main directory with exchange data.
@@ -44,6 +52,11 @@ extern struct GNUNET_CONFIGURATION_Handle *TEH_cfg;
extern char *TEH_exchange_directory;
/**
+ * -I command-line flag given?
+ */
+extern int TEH_check_invariants_flag;
+
+/**
* Are clients allowed to request /keys for times other than the
* current time? Allowing this could be abused in a DoS-attack
* as building new /keys responses is expensive. Should only be
@@ -52,21 +65,160 @@ extern char *TEH_exchange_directory;
extern int TEH_allow_keys_timetravel;
/**
+ * Option set to #GNUNET_YES if rewards are allowed.
+ */
+extern int TEH_enable_rewards;
+
+/**
* Main directory with revocation data.
*/
extern char *TEH_revocation_directory;
/**
+ * True if we should commit suicide once all active
+ * connections are finished. Also forces /keys requests
+ * to terminate if they are long-polling.
+ */
+extern bool TEH_suicide;
+
+/**
* Master public key (according to the
* configuration in the exchange directory).
*/
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.
*/
extern struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
+/**
+ * Absolute STEFAN parameter.
+ */
+extern struct TALER_Amount TEH_stefan_abs;
+
+/**
+ * Logarithmic STEFAN parameter.
+ */
+extern struct TALER_Amount TEH_stefan_log;
+
+/**
+ * Linear STEFAN parameter.
+ */
+extern float TEH_stefan_lin;
+
+/**
+ * Default ways how to render #TEH_currency amounts.
+ */
+extern const struct TALER_CurrencySpecification *TEH_cspec;
+
+/**
+ * Our currency.
+ */
+extern char *TEH_currency;
+
+/**
+ * Name of the KYC-AML-trigger evaluation binary.
+ */
+extern char *TEH_kyc_aml_trigger;
+
+/**
+ * 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.
+ */
+extern char *TEH_base_url;
+
+/**
+ * Are we shutting down?
+ */
+extern volatile bool MHD_terminating;
+
+/**
+ * Context for all CURL operations (useful to the event loop)
+ */
+extern struct GNUNET_CURL_Context *TEH_curl_ctx;
+
+/*
+ * Signature of the offline master key of all enabled extensions' configuration
+ */
+extern struct TALER_MasterSignatureP TEH_extensions_sig;
+extern bool TEH_extensions_signed;
+
+/**
+ * @brief Struct describing an URL and the handler for it.
+ */
+struct TEH_RequestHandler;
+
+
+/**
+ * @brief Context in which the exchange is processing
+ * all requests
+ */
+struct TEH_RequestContext
+{
+
+ /**
+ * Async Scope ID associated with this request.
+ */
+ struct GNUNET_AsyncScopeId async_scope_id;
+
+ /**
+ * When was this request started?
+ */
+ struct GNUNET_TIME_Absolute start_time;
+
+ /**
+ * Opaque parsing context.
+ */
+ void *opaque_post_parsing_context;
+
+ /**
+ * Request handler responsible for this request.
+ */
+ const struct TEH_RequestHandler *rh;
+
+ /**
+ * Request URL (for logging).
+ */
+ const char *url;
+
+ /**
+ * Connection we are processing.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * JSON root of uploaded data (or NULL, if none).
+ */
+ json_t *root;
+
+ /**
+ * @e rh-specific cleanup routine. Function called
+ * upon completion of the request that should
+ * clean up @a rh_ctx. Can be NULL.
+ */
+ void
+ (*rh_cleaner)(struct TEH_RequestContext *rc);
+
+ /**
+ * @e rh-specific context. Place where the request
+ * handler can associate state with this request.
+ * Can be NULL.
+ */
+ void *rh_ctx;
+};
+
/**
* @brief Struct describing an URL and the handler for it.
@@ -91,43 +243,45 @@ struct TEH_RequestHandler
union
{
/**
- * Function to call to handle a GET requests (and those
+ * Function to call to handle GET requests (and those
* with @e method NULL).
*
- * @param rh this struct
- * @param mime_type the @e mime_type for the reply (hint, can be NULL)
- * @param connection the MHD connection to handle
+ * @param rc context for the request
* @param args array of arguments, needs to be of length @e args_expected
* @return MHD result code
*/
- int (*get)(const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
- const char *const args[]);
+ MHD_RESULT
+ (*get)(struct TEH_RequestContext *rc,
+ const char *const args[]);
/**
- * Function to call to handle a POST request.
+ * Function to call to handle POST requests.
*
- * @param rh this struct
- * @param mime_type the @e mime_type for the reply (hint, can be NULL)
- * @param connection the MHD connection to handle
+ * @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
*/
- int (*post)(const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
- const json_t *root,
+ MHD_RESULT
+ (*post)(struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[]);
+
+ /**
+ * Function to call to handle DELETE requests.
+ *
+ * @param rc context for the request
+ * @param args array of arguments, needs to be of length @e nargs
+ * @return MHD result code
+ */
+ MHD_RESULT
+ (*delete)(struct TEH_RequestContext *rc,
const char *const args[]);
} handler;
/**
- * Number of arguments this handler expects in the @a args array.
- */
- unsigned int nargs;
-
- /**
* Mime type to use in reply (hint, can be NULL).
*/
const char *mime_type;
@@ -146,7 +300,22 @@ 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;
};
+/* Age restriction configuration */
+extern bool TEH_age_restriction_enabled;
+extern struct TALER_AgeRestrictionConfig TEH_age_restriction_config;
+
#endif
diff --git a/src/exchange/taler-exchange-httpd_age-withdraw.c b/src/exchange/taler-exchange-httpd_age-withdraw.c
new file mode 100644
index 000000000..9276fb191
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_age-withdraw.c
@@ -0,0 +1,1019 @@
+/*
+ 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_common.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+#include "taler_error_codes.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"
+#include "taler_util.h"
+
+
+/**
+ * Context for #age_withdraw_transaction.
+ */
+struct AgeWithdrawContext
+{
+ /**
+ * KYC status for the operation.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Timestamp
+ */
+ struct GNUNET_TIME_Timestamp now;
+
+ /**
+ * Hash of the wire source URL, needed when kyc is needed.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * The data from the age-withdraw request, as we persist it
+ */
+ struct TALER_EXCHANGEDB_AgeWithdraw commitment;
+
+ /**
+ * Number of coins/denonations in the reveal
+ */
+ uint32_t num_coins;
+
+ /**
+ * #num_coins * #kappa hashes of blinded coin planchets.
+ */
+ struct TALER_BlindedPlanchet (*coin_evs) [ TALER_CNC_KAPPA];
+
+ /**
+ * #num_coins hashes of the denominations from which the coins are withdrawn.
+ * Those must support age restriction.
+ */
+ struct TALER_DenominationHashP *denom_hs;
+
+};
+
+/*
+ * @brief Free the resources within a AgeWithdrawContext
+ *
+ * @param awc the context to free
+ */
+static void
+free_age_withdraw_context_resources (struct AgeWithdrawContext *awc)
+{
+ GNUNET_free (awc->denom_hs);
+ GNUNET_free (awc->coin_evs);
+ GNUNET_free (awc->commitment.denom_serials);
+ /*
+ * Note:
+ * awc->commitment.denom_sigs and .h_coin_evs were stack allocated and
+ * .denom_pub_hashes is NULL for this context.
+ */
+}
+
+
+/**
+ * Parse the denominations and blinded coin data of an '/age-withdraw' request.
+ *
+ * @param connection The MHD connection to handle
+ * @param j_denom_hs Array of n hashes of the denominations for the withdrawal, in JSON format
+ * @param j_blinded_coin_evs Array of n arrays of kappa blinded envelopes of in JSON format for the coins.
+ * @param[out] awc The context of the operation, only partially built at call time
+ * @param[out] mhd_ret The result if a reply is queued for MHD
+ * @return true on success, false on failure, with a reply already queued for MHD
+ */
+static enum GNUNET_GenericReturnValue
+parse_age_withdraw_json (
+ struct MHD_Connection *connection,
+ const json_t *j_denom_hs,
+ const json_t *j_blinded_coin_evs,
+ struct AgeWithdrawContext *awc,
+ MHD_RESULT *mhd_ret)
+{
+ char buf[256] = {0};
+ const char *error = NULL;
+ unsigned int idx = 0;
+ json_t *value = NULL;
+ struct GNUNET_HashContext *hash_context;
+
+
+ /* The age value MUST be on the beginning of an age group */
+ if (awc->commitment.max_age !=
+ TALER_get_lowest_age (&TEH_age_restriction_config.mask,
+ awc->commitment.max_age))
+ {
+ error = "max_age must be the lower edge of an age group";
+ goto EXIT;
+ }
+
+ /* Verify JSON-structure consistency */
+ {
+ uint32_t num_coins = json_array_size (j_denom_hs);
+
+ if (! json_is_array (j_denom_hs))
+ error = "denoms_h must be an array";
+ else if (! json_is_array (j_blinded_coin_evs))
+ error = "coin_evs must be an array";
+ else if (num_coins == 0)
+ error = "denoms_h must not be empty";
+ else if (num_coins != json_array_size (j_blinded_coin_evs))
+ error = "denoms_h and coins_evs must be arrays of the same size";
+ else if (num_coins > TALER_MAX_FRESH_COINS)
+ /**
+ * The wallet had committed to more than the maximum coins allowed, the
+ * reserve has been charged, but now the user can not withdraw any money
+ * from it. Note that the user can't get their money back in this case!
+ **/
+ error = "maximum number of coins that can be withdrawn has been exceeded";
+
+ _Static_assert ((TALER_MAX_FRESH_COINS < INT_MAX / TALER_CNC_KAPPA),
+ "TALER_MAX_FRESH_COINS too large");
+
+ if (NULL != error)
+ goto EXIT;
+
+ awc->num_coins = num_coins;
+ awc->commitment.num_coins = num_coins;
+ }
+
+ /* Continue parsing the parts */
+
+ /* Parse denomination keys */
+ awc->denom_hs = GNUNET_new_array (awc->num_coins,
+ struct TALER_DenominationHashP);
+
+ json_array_foreach (j_denom_hs, idx, value) {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL, &awc->denom_hs[idx]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value, spec, NULL, NULL))
+ {
+ GNUNET_snprintf (buf,
+ sizeof(buf),
+ "couldn't parse entry no. %d in array denoms_h",
+ idx + 1);
+ error = buf;
+ goto EXIT;
+ }
+ };
+
+ {
+ typedef struct TALER_BlindedPlanchet
+ _array_of_kappa_planchets[TALER_CNC_KAPPA];
+
+ awc->coin_evs = GNUNET_new_array (awc->num_coins,
+ _array_of_kappa_planchets);
+ }
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+ GNUNET_assert (NULL != hash_context);
+
+ /* Parse blinded envelopes. */
+ json_array_foreach (j_blinded_coin_evs, idx, value) {
+ const json_t *j_kappa_coin_evs = value;
+ if (! json_is_array (j_kappa_coin_evs))
+ {
+ GNUNET_snprintf (buf,
+ sizeof(buf),
+ "enxtry %d in array blinded_coin_evs is not an array",
+ idx + 1);
+ error = buf;
+ goto EXIT;
+ }
+ else if (TALER_CNC_KAPPA != json_array_size (j_kappa_coin_evs))
+ {
+ GNUNET_snprintf (buf,
+ sizeof(buf),
+ "array no. %d in coin_evs not of correct size",
+ idx + 1);
+ error = buf;
+ goto EXIT;
+ }
+
+ /* Now parse the individual kappa envelopes and calculate the hash of
+ * the commitment along the way. */
+ {
+ unsigned int kappa = 0;
+
+ json_array_foreach (j_kappa_coin_evs, kappa, value) {
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_blinded_planchet (NULL,
+ &awc->coin_evs[idx][kappa]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_snprintf (buf,
+ sizeof(buf),
+ "couldn't parse array no. %d in blinded_coin_evs[%d]",
+ kappa + 1,
+ idx + 1);
+ error = buf;
+ goto EXIT;
+ }
+
+ /* Continue to hash of the coin candidates */
+ {
+ struct TALER_BlindedCoinHashP bch;
+
+ TALER_coin_ev_hash (&awc->coin_evs[idx][kappa],
+ &awc->denom_hs[idx],
+ &bch);
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &bch,
+ sizeof(bch));
+ }
+
+ /* Check for duplicate planchets. Technically a bug on
+ * the client side that is harmless for us, but still
+ * not allowed per protocol */
+ for (unsigned int i = 0; i < idx; i++)
+ {
+ if (0 == TALER_blinded_planchet_cmp (&awc->coin_evs[idx][kappa],
+ &awc->coin_evs[i][kappa]))
+ {
+ GNUNET_JSON_parse_free (spec);
+ error = "duplicate planchet";
+ goto EXIT;
+ }
+ }
+ }
+ }
+ }; /* json_array_foreach over j_blinded_coin_evs */
+
+ /* Finally, calculate the h_commitment from all blinded envelopes */
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &awc->commitment.h_commitment.hash);
+
+ GNUNET_assert (NULL == error);
+
+
+EXIT:
+ if (NULL != error)
+ {
+ /* Note: resources are freed in caller */
+
+ *mhd_ret = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ error);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check if the given denomination is still or already valid, has not been
+ * revoked and supports age restriction.
+ *
+ * @param connection HTTP-connection to the client
+ * @param ksh The handle to the current state of (denomination) keys in the exchange
+ * @param denom_h Hash of the denomination key to check
+ * @param[out] pdk On success, will contain the denomination key details
+ * @param[out] result On failure, an MHD-response will be queued and result will be set to accordingly
+ * @return true on success (denomination valid), false otherwise
+ */
+static bool
+denomination_is_valid (
+ struct MHD_Connection *connection,
+ struct TEH_KeyStateHandle *ksh,
+ const struct TALER_DenominationHashP *denom_h,
+ struct TEH_DenominationKey **pdk,
+ MHD_RESULT *result)
+{
+ struct TEH_DenominationKey *dk;
+ dk = TEH_keys_denomination_by_hash_from_state (ksh,
+ denom_h,
+ connection,
+ result);
+ if (NULL == dk)
+ {
+ /* The denomination doesn't exist */
+ /* Note: a HTTP-response has been queued and result has been set by
+ * TEH_keys_denominations_by_hash_from_state */
+ return false;
+ }
+
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
+ {
+ /* This denomination is past the expiration time for withdraws */
+ /* FIXME[oec]: add idempotency check */
+ *result = TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ denom_h,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "age-withdraw_reveal");
+ return false;
+ }
+
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ /* This denomination is not yet valid */
+ *result = TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ denom_h,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "age-withdraw_reveal");
+ return false;
+ }
+
+ if (dk->recoup_possible)
+ {
+ /* This denomination has been revoked */
+ *result = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+ NULL);
+ return false;
+ }
+
+ if (0 == dk->denom_pub.age_mask.bits)
+ {
+ /* This denomation does not support age restriction */
+ char msg[256] = {0};
+ GNUNET_snprintf (msg,
+ sizeof(msg),
+ "denomination %s does not support age restriction",
+ GNUNET_h2s (&denom_h->hash));
+
+ *result = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
+ msg);
+ return false;
+ }
+
+ *pdk = dk;
+ return true;
+}
+
+
+/**
+ * Check if the given array of hashes of denomination_keys a) belong
+ * to valid denominations and b) those are marked as age restricted.
+ * Also, calculate the total amount of the denominations including fees
+ * for withdraw.
+ *
+ * @param connection The HTTP connection to the client
+ * @param len The lengths of the array @a denoms_h
+ * @param denom_hs array of hashes of denomination public keys
+ * @param coin_evs array of blinded coin planchet candidates
+ * @param[out] denom_serials On success, will be filled with the serial-id's of the denomination keys. Caller must deallocate.
+ * @param[out] amount_with_fee On success, will contain the committed amount including fees
+ * @param[out] result In the error cases, a response will be queued with MHD and this will be the result.
+ * @return #GNUNET_OK if the denominations are valid and support age-restriction
+ * #GNUNET_SYSERR otherwise
+ */
+static enum GNUNET_GenericReturnValue
+are_denominations_valid (
+ struct MHD_Connection *connection,
+ uint32_t len,
+ const struct TALER_DenominationHashP *denom_hs,
+ const struct TALER_BlindedPlanchet (*coin_evs) [ TALER_CNC_KAPPA],
+ uint64_t **denom_serials,
+ struct TALER_Amount *amount_with_fee,
+ MHD_RESULT *result)
+{
+ struct TALER_Amount total_amount;
+ struct TALER_Amount total_fee;
+ struct TEH_KeyStateHandle *ksh;
+ uint64_t *serials;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ *denom_serials =
+ serials = GNUNET_new_array (len, uint64_t);
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &total_amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &total_fee));
+
+ for (uint32_t i = 0; i < len; i++)
+ {
+ struct TEH_DenominationKey *dk;
+ if (! denomination_is_valid (connection,
+ ksh,
+ &denom_hs[i],
+ &dk,
+ result))
+ /* FIXME[oec]: add idempotency check */
+ return GNUNET_SYSERR;
+
+ /* Ensure the ciphers from the planchets match the denominations' */
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ if (dk->denom_pub.bsign_pub_key->cipher !=
+ coin_evs[i][k].blinded_message->cipher)
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ /* Accumulate the values */
+ if (0 > TALER_amount_add (&total_amount,
+ &total_amount,
+ &dk->meta.value))
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW,
+ "amount");
+ return GNUNET_SYSERR;
+ }
+
+ /* Accumulate the withdraw fees */
+ if (0 > TALER_amount_add (&total_fee,
+ &total_fee,
+ &dk->meta.fees.withdraw))
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW,
+ "fee");
+ return GNUNET_SYSERR;
+ }
+
+ serials[i] = dk->meta.serial;
+ }
+
+ /* Save the total amount including fees */
+ GNUNET_assert (0 < TALER_amount_add (amount_with_fee,
+ &total_amount,
+ &total_fee));
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * @brief Verify the signature of the request body with the reserve key
+ *
+ * @param connection the connection to the client
+ * @param commitment the age withdraw commitment
+ * @param mhd_ret the response to fill in the error case
+ * @return GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+verify_reserve_signature (
+ struct MHD_Connection *connection,
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
+ enum MHD_Result *mhd_ret)
+{
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_age_withdraw_verify (&commitment->h_commitment,
+ &commitment->amount_with_fee,
+ &TEH_age_restriction_config.mask,
+ commitment->max_age,
+ &commitment->reserve_pub,
+ &commitment->reserve_sig))
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * 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));
+}
+
+
+/**
+ * Check if the request is replayed and we already have an
+ * answer. If so, replay the existing answer and return the
+ * HTTP response.
+ *
+ * @param con connection to the client
+ * @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 MHD_Connection *con,
+ struct AgeWithdrawContext *awc,
+ MHD_RESULT *mret)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_EXCHANGEDB_AgeWithdraw commitment;
+
+ qs = TEH_plugin->get_age_withdraw (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_ec (con,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_age_withdraw");
+ return true; /* Well, kind-of. At least we have set mret. */
+ }
+
+ 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 (con,
+ &commitment.h_commitment,
+ commitment.noreveal_index);
+ return true;
+}
+
+
+/**
+ * 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, of type struct AgeWithdrawContext
+ */
+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.
+ *
+ * @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;
+
+ 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_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "kyc_test_required");
+ }
+ return qs;
+ }
+
+ if (NULL != kyc_required)
+ {
+ /* Mark result and return by inserting 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->commitment.reserve_pub,
+ &awc->kyc.requirement_row);
+ }
+ }
+
+ awc->kyc.ok = true;
+
+ /* KYC requirement fulfilled, do the age-withdraw transaction */
+ {
+ bool found = false;
+ bool balance_ok = false;
+ bool age_ok = false;
+ bool conflict = false;
+ uint16_t allowed_maximum_age = 0;
+ uint32_t reserve_birthday = 0;
+ struct TALER_Amount reserve_balance;
+
+ qs = TEH_plugin->do_age_withdraw (TEH_plugin->cls,
+ &awc->commitment,
+ awc->now,
+ &found,
+ &balance_ok,
+ &reserve_balance,
+ &age_ok,
+ &allowed_maximum_age,
+ &reserve_birthday,
+ &conflict);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "do_age_withdraw");
+ return qs;
+ }
+ if (! found)
+ {
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! age_ok)
+ {
+ enum TALER_ErrorCode ec =
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE;
+
+ *mhd_ret =
+ TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_MHD_PACK_EC (ec),
+ GNUNET_JSON_pack_uint64 ("allowed_maximum_age",
+ allowed_maximum_age),
+ GNUNET_JSON_pack_uint64 ("reserve_birthday",
+ reserve_birthday));
+
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ 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,
+ &reserve_balance,
+ &awc->commitment.amount_with_fee,
+ &awc->commitment.reserve_pub);
+
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (conflict)
+ {
+ /* do_age_withdraw signaled a conflict, so there MUST be an entry
+ * in the DB. Put that into the response */
+ bool ok = request_is_idempotent (connection,
+ awc,
+ mhd_ret);
+ GNUNET_assert (ok);
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ *mhd_ret = -1;
+ }
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_AGE_WITHDRAW]++;
+ return qs;
+}
+
+
+/**
+ * @brief Sign the chosen blinded coins, debit the reserve and persist
+ * the commitment.
+ *
+ * On conflict, the noreveal_index from the previous, existing
+ * commitment is returned to the client, returning success.
+ *
+ * On error (like, insufficient funds), the client is notified.
+ *
+ * Note that on success, there are two possible states:
+ * 1.) KYC is required (awc.kyc.ok == false) or
+ * 2.) age withdraw was successful.
+ *
+ * @param connection HTTP-connection to the client
+ * @param awc The context for the current age withdraw request
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
+ */
+static enum GNUNET_GenericReturnValue
+sign_and_do_age_withdraw (
+ struct MHD_Connection *connection,
+ struct AgeWithdrawContext *awc,
+ MHD_RESULT *result)
+{
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+ struct TALER_BlindedCoinHashP h_coin_evs[awc->num_coins];
+ struct TALER_BlindedDenominationSignature denom_sigs[awc->num_coins];
+ uint8_t noreveal_index;
+
+ awc->now = GNUNET_TIME_timestamp_get ();
+
+ /* Pick the challenge */
+ noreveal_index =
+ GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
+ TALER_CNC_KAPPA);
+
+ awc->commitment.noreveal_index = noreveal_index;
+
+ /* Choose and sign the coins */
+ {
+ struct TEH_CoinSignData csds[awc->num_coins];
+ enum TALER_ErrorCode ec;
+
+ /* Pick the chosen blinded coins */
+ for (uint32_t i = 0; i<awc->num_coins; i++)
+ {
+ csds[i].bp = &awc->coin_evs[i][noreveal_index];
+ csds[i].h_denom_pub = &awc->denom_hs[i];
+ }
+
+ ec = TEH_keys_denomination_batch_sign (awc->num_coins,
+ csds,
+ false,
+ denom_sigs);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Signatures ready, starting DB interaction\n");
+
+ /* Prepare the hashes of the coins for insertion */
+ for (uint32_t i = 0; i<awc->num_coins; i++)
+ {
+ TALER_coin_ev_hash (&awc->coin_evs[i][noreveal_index],
+ &awc->denom_hs[i],
+ &h_coin_evs[i]);
+ }
+
+ /* Run the transaction */
+ awc->commitment.h_coin_evs = h_coin_evs;
+ awc->commitment.denom_sigs = denom_sigs;
+ ret = TEH_DB_run_transaction (connection,
+ "run age withdraw",
+ TEH_MT_REQUEST_AGE_WITHDRAW,
+ result,
+ &age_withdraw_transaction,
+ awc);
+ /* Free resources */
+ for (unsigned int i = 0; i<awc->num_coins; i++)
+ TALER_blinded_denom_sig_free (&denom_sigs[i]);
+ awc->commitment.h_coin_evs = NULL;
+ awc->commitment.denom_sigs = NULL;
+ return ret;
+}
+
+
+MHD_RESULT
+TEH_handler_age_withdraw (struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root)
+{
+ MHD_RESULT mhd_ret;
+ const json_t *j_denom_hs;
+ const json_t *j_blinded_coin_evs;
+ struct AgeWithdrawContext awc = {0};
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("denom_hs",
+ &j_denom_hs),
+ GNUNET_JSON_spec_array_const ("blinded_coin_evs",
+ &j_blinded_coin_evs),
+ GNUNET_JSON_spec_uint16 ("max_age",
+ &awc.commitment.max_age),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &awc.commitment.reserve_sig),
+ 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 {
+ /* Note: If we break the statement here at any point,
+ * a response to the client MUST have been populated
+ * with an appropriate answer and mhd_ret MUST have
+ * been set accordingly.
+ */
+
+ /* Parse denoms_h and blinded_coins_evs, partially fill awc */
+ if (GNUNET_OK !=
+ parse_age_withdraw_json (rc->connection,
+ j_denom_hs,
+ j_blinded_coin_evs,
+ &awc,
+ &mhd_ret))
+ break;
+
+ /* Ensure validity of denoms and calculate amounts and fees */
+ if (GNUNET_OK !=
+ are_denominations_valid (rc->connection,
+ awc.num_coins,
+ awc.denom_hs,
+ awc.coin_evs,
+ &awc.commitment.denom_serials,
+ &awc.commitment.amount_with_fee,
+ &mhd_ret))
+ break;
+
+ /* Now that amount_with_fee is calculated, verify the signature of
+ * the request body with the reserve key.
+ */
+ if (GNUNET_OK !=
+ verify_reserve_signature (rc->connection,
+ &awc.commitment,
+ &mhd_ret))
+ break;
+
+ /* Sign the chosen blinded coins, persist the commitment and
+ * charge the reserve.
+ * On error (like, insufficient funds), the client is notified.
+ * On conflict, the noreveal_index from the previous, existing
+ * commitment is returned to the client, returning success.
+ * Note that on success, there are two possible states:
+ * KYC is required (awc.kyc.ok == false) or
+ * age withdraw was successful.
+ */
+ if (GNUNET_OK !=
+ sign_and_do_age_withdraw (rc->connection,
+ &awc,
+ &mhd_ret))
+ break;
+
+ /* Send back final response, depending on the outcome of
+ * the DB-transaction */
+ if (! awc.kyc.ok)
+ mhd_ret = TEH_RESPONSE_reply_kyc_required (rc->connection,
+ &awc.h_payto,
+ &awc.kyc);
+ else
+ mhd_ret = reply_age_withdraw_success (rc->connection,
+ &awc.commitment.h_commitment,
+ awc.commitment.noreveal_index);
+
+ } while (0);
+
+ GNUNET_JSON_parse_free (spec);
+ free_age_withdraw_context_resources (&awc);
+ return mhd_ret;
+
+}
+
+
+/* end of taler-exchange-httpd_age-withdraw.c */
diff --git a/src/exchange/taler-exchange-httpd_age-withdraw.h b/src/exchange/taler-exchange-httpd_age-withdraw.h
new file mode 100644
index 000000000..a76779190
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_age-withdraw.h
@@ -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
diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c
new file mode 100644
index 000000000..c9aca8e99
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c
@@ -0,0 +1,610 @@
+/*
+ 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.c
+ * @brief Handle /age-withdraw/$ACH/reveal requests
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd_metrics.h"
+#include "taler_error_codes.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_mhd.h"
+#include "taler-exchange-httpd_age-withdraw_reveal.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+
+/**
+ * State for an /age-withdraw/$ACH/reveal operation.
+ */
+struct AgeRevealContext
+{
+
+ /**
+ * Commitment for the age-withdraw operation, previously called by the
+ * client.
+ */
+ struct TALER_AgeWithdrawCommitmentHashP ach;
+
+ /**
+ * Public key of the reserve for with the age-withdraw commitment was
+ * originally made. This parameter is provided by the client again
+ * during the call to reveal in order to save a database-lookup.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Number of coins to reveal. MUST be equal to
+ * @e num_secrets/(kappa -1).
+ */
+ uint32_t num_coins;
+
+ /**
+ * Number of secrets in the reveal. MUST be a multiple of (kappa-1).
+ */
+ uint32_t num_secrets;
+
+ /**
+ * @e num_secrets secrets for disclosed coins.
+ */
+ struct TALER_PlanchetMasterSecretP *disclosed_coin_secrets;
+
+ /**
+ * The data from the original age-withdraw. Will be retrieved from
+ * the DB via @a ach and @a reserve_pub.
+ */
+ struct TALER_EXCHANGEDB_AgeWithdraw commitment;
+};
+
+
+/**
+ * Parse the json body of an '/age-withdraw/$ACH/reveal' request. It extracts
+ * the denomination hashes, blinded coins and disclosed coins and allocates
+ * memory for those.
+ *
+ * @param connection The MHD connection to handle
+ * @param j_disclosed_coin_secrets The n*(kappa-1) disclosed coins' private keys in JSON format, from which all other attributes (age restriction, blinding, nonce) will be derived from
+ * @param[out] actx The context of the operation, only partially built at call time
+ * @param[out] mhd_ret The result if a reply is queued for MHD
+ * @return true on success, false on failure, with a reply already queued for MHD.
+ */
+static enum GNUNET_GenericReturnValue
+parse_age_withdraw_reveal_json (
+ struct MHD_Connection *connection,
+ const json_t *j_disclosed_coin_secrets,
+ struct AgeRevealContext *actx,
+ MHD_RESULT *mhd_ret)
+{
+ enum GNUNET_GenericReturnValue result = GNUNET_SYSERR;
+ size_t num_entries;
+
+ /* Verify JSON-structure consistency */
+ {
+ const char *error = NULL;
+
+ num_entries = json_array_size (j_disclosed_coin_secrets); /* 0, if not an array */
+
+ if (! json_is_array (j_disclosed_coin_secrets))
+ error = "disclosed_coin_secrets must be an array";
+ else if (num_entries == 0)
+ error = "disclosed_coin_secrets must not be empty";
+ else if (num_entries > TALER_MAX_FRESH_COINS)
+ error = "maximum number of coins that can be withdrawn has been exceeded";
+
+ if (NULL != error)
+ {
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ error);
+ return GNUNET_SYSERR;
+ }
+
+ actx->num_secrets = num_entries * (TALER_CNC_KAPPA - 1);
+ actx->num_coins = num_entries;
+
+ }
+
+ /* Continue parsing the parts */
+ {
+ unsigned int idx = 0;
+ unsigned int k = 0;
+ json_t *array = NULL;
+ json_t *value = NULL;
+
+ /* Parse diclosed keys */
+ actx->disclosed_coin_secrets =
+ GNUNET_new_array (actx->num_secrets,
+ struct TALER_PlanchetMasterSecretP);
+
+ json_array_foreach (j_disclosed_coin_secrets, idx, array) {
+ if (! json_is_array (array) ||
+ (TALER_CNC_KAPPA - 1 != json_array_size (array)))
+ {
+ char msg[256] = {0};
+ GNUNET_snprintf (msg,
+ sizeof(msg),
+ "couldn't parse entry no. %d in array disclosed_coin_secrets",
+ idx + 1);
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ msg);
+ goto EXIT;
+
+ }
+
+ json_array_foreach (array, k, value)
+ {
+ struct TALER_PlanchetMasterSecretP *secret =
+ &actx->disclosed_coin_secrets[2 * idx + k];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL, secret),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value, spec, NULL, NULL))
+ {
+ char msg[256] = {0};
+ GNUNET_snprintf (msg,
+ sizeof(msg),
+ "couldn't parse entry no. %d in array disclosed_coin_secrets[%d]",
+ k + 1,
+ idx + 1);
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ msg);
+ goto EXIT;
+ }
+ }
+ };
+ }
+
+ result = GNUNET_OK;
+
+EXIT:
+ return result;
+}
+
+
+/**
+ * Check if the request belongs to an existing age-withdraw request.
+ * If so, sets the commitment object with the request data.
+ * Otherwise, it queues an appropriate MHD response.
+ *
+ * @param connection The HTTP connection to the client
+ * @param h_commitment Original commitment value sent with the age-withdraw request
+ * @param reserve_pub Reserve public key used in the original age-withdraw request
+ * @param[out] commitment Data from the original age-withdraw request
+ * @param[out] result In the error cases, a response will be queued with MHD and this will be the result.
+ * @return #GNUNET_OK if the withdraw request has been found,
+ * #GNUNET_SYSERR if we did not find the request in the DB
+ */
+static enum GNUNET_GenericReturnValue
+find_original_commitment (
+ struct MHD_Connection *connection,
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
+ MHD_RESULT *result)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ for (unsigned int try = 0; try < 3; try++)
+ {
+ qs = TEH_plugin->get_age_withdraw (TEH_plugin->cls,
+ reserve_pub,
+ h_commitment,
+ commitment);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return GNUNET_OK; /* Only happy case */
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_COMMITMENT_UNKNOWN,
+ NULL);
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_age_withdraw_info");
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ break; /* try again */
+ default:
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+ /* after unsuccessful retries*/
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_age_withdraw_info");
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * @brief Derives a age-restricted planchet from a given secret and calculates the hash
+ *
+ * @param connection Connection to the client
+ * @param keys The denomination keys in memory
+ * @param secret The secret to a planchet
+ * @param denom_pub_h The hash of the denomination for the planchet
+ * @param max_age The maximum age allowed
+ * @param[out] bch Hashcode to write
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise, with an error message
+ * written to the client and @e result set.
+ */
+static enum GNUNET_GenericReturnValue
+calculate_blinded_hash (
+ struct MHD_Connection *connection,
+ const struct TEH_KeyStateHandle *keys,
+ const struct TALER_PlanchetMasterSecretP *secret,
+ const struct TALER_DenominationHashP *denom_pub_h,
+ uint8_t max_age,
+ struct TALER_BlindedCoinHashP *bch,
+ MHD_RESULT *result)
+{
+ enum GNUNET_GenericReturnValue ret;
+ struct TEH_DenominationKey *denom_key;
+ struct TALER_AgeCommitmentHash ach;
+
+ /* First, retrieve denomination details */
+ denom_key = TEH_keys_denomination_by_hash_from_state (keys,
+ denom_pub_h,
+ connection,
+ result);
+ if (NULL == denom_key)
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ /* calculate age commitment hash */
+ {
+ struct TALER_AgeCommitmentProof acp;
+
+ TALER_age_restriction_from_secret (secret,
+ &denom_key->denom_pub.age_mask,
+ max_age,
+ &acp);
+ TALER_age_commitment_hash (&acp.commitment,
+ &ach);
+ TALER_age_commitment_proof_free (&acp);
+ }
+
+ /* Next: calculate planchet */
+ {
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_PlanchetDetail detail = {0};
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = denom_key->denom_pub.bsign_pub_key->cipher
+ };
+ struct TALER_ExchangeWithdrawValues alg_values = {
+ .blinding_inputs = &bi
+ };
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+ union GNUNET_CRYPTO_BlindSessionNonce *noncep = NULL;
+
+ // FIXME: add logic to denom.c to do this!
+ if (GNUNET_CRYPTO_BSA_CS == bi.cipher)
+ {
+ struct TEH_CsDeriveData cdd = {
+ .h_denom_pub = &denom_key->h_denom_pub,
+ .nonce = &nonce.cs_nonce,
+ };
+
+ TALER_cs_withdraw_nonce_derive (secret,
+ &nonce.cs_nonce);
+ noncep = &nonce;
+ GNUNET_assert (TALER_EC_NONE ==
+ TEH_keys_denomination_cs_r_pub (
+ &cdd,
+ false,
+ &bi.details.cs_values));
+ }
+ TALER_planchet_blinding_secret_create (secret,
+ &alg_values,
+ &bks);
+ TALER_planchet_setup_coin_priv (secret,
+ &alg_values,
+ &coin_priv);
+ ret = TALER_planchet_prepare (&denom_key->denom_pub,
+ &alg_values,
+ &bks,
+ noncep,
+ &coin_priv,
+ &ach,
+ &c_hash,
+ &detail);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_json_pack (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "{ss}",
+ "details",
+ "failed to prepare planchet from base key");
+ return ret;
+ }
+
+ TALER_coin_ev_hash (&detail.blinded_planchet,
+ &denom_key->h_denom_pub,
+ bch);
+ TALER_blinded_planchet_free (&detail.blinded_planchet);
+ }
+
+ return ret;
+}
+
+
+/**
+ * @brief Checks the validity of the disclosed coins as follows:
+ * - Derives and calculates the disclosed coins'
+ * - public keys,
+ * - nonces (if applicable),
+ * - age commitments,
+ * - blindings
+ * - blinded hashes
+ * - Computes h_commitment with those calculated and the undisclosed hashes
+ * - Compares h_commitment with the value from the original commitment
+ * - Verifies that all public keys in indices larger than the age group
+ * corresponding to max_age are derived from the constant public key.
+ *
+ * The derivation of the blindings, (potential) nonces and age-commitment from
+ * a coin's private keys is defined in
+ * https://docs.taler.net/design-documents/024-age-restriction.html#withdraw
+ *
+ * @param connection HTTP-connection to the client
+ * @param commitment Original commitment
+ * @param disclosed_coin_secrets The secrets of the disclosed coins, (TALER_CNC_KAPPA - 1)*num_coins many
+ * @param num_coins number of coins to reveal via @a disclosed_coin_secrets
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
+ */
+static enum GNUNET_GenericReturnValue
+verify_commitment_and_max_age (
+ struct MHD_Connection *connection,
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
+ const struct TALER_PlanchetMasterSecretP *disclosed_coin_secrets,
+ uint32_t num_coins,
+ MHD_RESULT *result)
+{
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+ struct GNUNET_HashContext *hash_context;
+ struct TEH_KeyStateHandle *keys;
+
+ if (num_coins != commitment->num_coins)
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "#coins");
+ return GNUNET_SYSERR;
+ }
+
+ /* We need the current keys in memory for the meta-data of the denominations */
+ keys = TEH_keys_get_state ();
+ if (NULL == keys)
+ {
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+
+ for (size_t coin_idx = 0; coin_idx < num_coins; coin_idx++)
+ {
+ size_t i = 0; /* either 0 or 1, to index into coin_evs */
+
+ for (size_t k = 0; k<TALER_CNC_KAPPA; k++)
+ {
+ if (k == (size_t) commitment->noreveal_index)
+ {
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &commitment->h_coin_evs[coin_idx],
+ sizeof(commitment->h_coin_evs[coin_idx]));
+ }
+ else
+ {
+ /* j is the index into disclosed_coin_secrets[] */
+ size_t j = (TALER_CNC_KAPPA - 1) * coin_idx + i;
+ const struct TALER_PlanchetMasterSecretP *secret;
+ struct TALER_BlindedCoinHashP bch;
+
+ GNUNET_assert (2>i);
+ GNUNET_assert ((TALER_CNC_KAPPA - 1) * num_coins > j);
+
+ secret = &disclosed_coin_secrets[j];
+ i++;
+
+ ret = calculate_blinded_hash (connection,
+ keys,
+ secret,
+ &commitment->denom_pub_hashes[coin_idx],
+ commitment->max_age,
+ &bch,
+ result);
+
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_CRYPTO_hash_context_abort (hash_context);
+ return GNUNET_SYSERR;
+ }
+
+ /* Continue the running hash of all coin hashes with the calculated
+ * hash-value of the current, disclosed coin */
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &bch,
+ sizeof(bch));
+ }
+ }
+ }
+
+ /* Finally, compare the calculated hash with the original commitment */
+ {
+ struct GNUNET_HashCode calc_hash;
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &calc_hash);
+
+ if (0 != GNUNET_CRYPTO_hash_cmp (&commitment->h_commitment.hash,
+ &calc_hash))
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * @brief Send a response for "/age-withdraw/$RCH/reveal"
+ *
+ * @param connection The http connection to the client to send the response to
+ * @param commitment The data from the commitment with signatures
+ * @return a MHD result code
+ */
+static MHD_RESULT
+reply_age_withdraw_reveal_success (
+ struct MHD_Connection *connection,
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment)
+{
+ json_t *list = json_array ();
+ GNUNET_assert (NULL != list);
+
+ for (unsigned int i = 0; i < commitment->num_coins; i++)
+ {
+ json_t *obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_denom_sig (NULL,
+ &commitment->denom_sigs[i]));
+ GNUNET_assert (0 ==
+ json_array_append_new (list,
+ obj));
+ }
+
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("ev_sigs",
+ list));
+}
+
+
+MHD_RESULT
+TEH_handler_age_withdraw_reveal (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AgeWithdrawCommitmentHashP *ach,
+ const json_t *root)
+{
+ MHD_RESULT result = MHD_NO;
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+ struct AgeRevealContext actx = {0};
+ const json_t *j_disclosed_coin_secrets;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &actx.reserve_pub),
+ GNUNET_JSON_spec_array_const ("disclosed_coin_secrets",
+ &j_disclosed_coin_secrets),
+ GNUNET_JSON_spec_end ()
+ };
+
+ actx.ach = *ach;
+
+ /* Parse JSON body*/
+ ret = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break_op (0);
+ return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
+ }
+
+
+ do {
+ /* Extract denominations, blinded and disclosed coins */
+ if (GNUNET_OK !=
+ parse_age_withdraw_reveal_json (
+ rc->connection,
+ j_disclosed_coin_secrets,
+ &actx,
+ &result))
+ break;
+
+ /* Find original commitment */
+ if (GNUNET_OK !=
+ find_original_commitment (
+ rc->connection,
+ &actx.ach,
+ &actx.reserve_pub,
+ &actx.commitment,
+ &result))
+ break;
+
+ /* Verify the computed h_commitment equals the committed one and that coins
+ * have a maximum age group corresponding max_age (age-mask dependent) */
+ if (GNUNET_OK !=
+ verify_commitment_and_max_age (
+ rc->connection,
+ &actx.commitment,
+ actx.disclosed_coin_secrets,
+ actx.num_coins,
+ &result))
+ break;
+
+ /* Finally, return the signatures */
+ result = reply_age_withdraw_reveal_success (rc->connection,
+ &actx.commitment);
+
+ } while (0);
+
+ GNUNET_JSON_parse_free (spec);
+ if (NULL != actx.commitment.denom_sigs)
+ for (unsigned int i = 0; i<actx.num_coins; i++)
+ TALER_blinded_denom_sig_free (&actx.commitment.denom_sigs[i]);
+ GNUNET_free (actx.commitment.denom_sigs);
+ GNUNET_free (actx.commitment.denom_pub_hashes);
+ GNUNET_free (actx.commitment.denom_serials);
+ GNUNET_free (actx.disclosed_coin_secrets);
+ return result;
+}
+
+
+/* end of taler-exchange-httpd_age-withdraw_reveal.c */
diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.h b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.h
new file mode 100644
index 000000000..f7b813fe7
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.h
@@ -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_REVEAL_H
+#define TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_REVEAL_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
diff --git a/src/exchange/taler-exchange-httpd_aml-decision-get.c b/src/exchange/taler-exchange-httpd_aml-decision-get.c
new file mode 100644
index 000000000..b4f337db1
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_aml-decision-get.c
@@ -0,0 +1,233 @@
+/*
+ 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 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,
+ 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 */
diff --git a/src/exchange/taler-exchange-httpd_aml-decision.c b/src/exchange/taler-exchange-httpd_aml-decision.c
new file mode 100644
index 000000000..bf43fdbf2
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_aml-decision.c
@@ -0,0 +1,358 @@
+/*
+ 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.
+ */
+ const 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,
+ NULL, /* not a reserve */
+ &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
+ };
+ 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),
+ TALER_JSON_spec_aml_decision ("new_state",
+ &dc.new_state),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("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 */
+ }
+ }
+ 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;
+
+ json_array_foreach (dc.kyc_requirements, index, elem)
+ {
+ const char *val;
+
+ if (! json_is_string (elem))
+ {
+ GNUNET_break_op (0);
+ 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);
+ 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))
+ {
+ return mhd_ret;
+ }
+ }
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_aml-decision.c */
diff --git a/src/exchange/taler-exchange-httpd_aml-decision.h b/src/exchange/taler-exchange-httpd_aml-decision.h
new file mode 100644
index 000000000..8af742c0a
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_aml-decision.h
@@ -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
diff --git a/src/exchange/taler-exchange-httpd_aml-decisions-get.c b/src/exchange/taler-exchange-httpd_aml-decisions-get.c
new file mode 100644
index 000000000..763817cf6
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_aml-decisions-get.c
@@ -0,0 +1,215 @@
+/*
+ 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;
+ 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,
+ "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");
+ }
+ }
+ if (delta > 0)
+ start = 0;
+ else
+ start = INT64_MAX;
+ 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");
+ }
+ }
+ }
+
+ {
+ 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 */
diff --git a/src/exchange/taler-exchange-httpd_auditors.c b/src/exchange/taler-exchange-httpd_auditors.c
new file mode 100644
index 000000000..9d3239e86
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_auditors.c
@@ -0,0 +1,232 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as 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_auditors.c
+ * @brief Handle request to add auditor signature on a denomination.
+ * @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_auditors.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Closure for the #add_auditor_denom_sig transaction.
+ */
+struct AddAuditorDenomContext
+{
+ /**
+ * Auditor's signature affirming the AUDITORS XXX operation
+ * (includes timestamp).
+ */
+ struct TALER_AuditorSignatureP auditor_sig;
+
+ /**
+ * Denomination this is about.
+ */
+ const struct TALER_DenominationHashP *h_denom_pub;
+
+ /**
+ * Auditor this is about.
+ */
+ const struct TALER_AuditorPublicKeyP *auditor_pub;
+
+};
+
+
+/**
+ * Function implementing database transaction to add an auditors. 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 AddAuditorDenomContext`
+ * @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
+add_auditor_denom_sig (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct AddAuditorDenomContext *awc = cls;
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
+ enum GNUNET_DB_QueryStatus qs;
+ char *auditor_url;
+ bool enabled;
+
+ qs = TEH_plugin->lookup_denomination_key (
+ TEH_plugin->cls,
+ awc->h_denom_pub,
+ &meta);
+ 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,
+ "lookup denomination key");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
+ GNUNET_h2s (&awc->h_denom_pub->hash));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ qs = TEH_plugin->lookup_auditor_status (
+ TEH_plugin->cls,
+ awc->auditor_pub,
+ &auditor_url,
+ &enabled);
+ 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,
+ "lookup auditor");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_PRECONDITION_FAILED,
+ TALER_EC_EXCHANGE_AUDITORS_AUDITOR_UNKNOWN,
+ TALER_B2S (awc->auditor_pub));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! enabled)
+ {
+ GNUNET_free (auditor_url);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_GONE,
+ TALER_EC_EXCHANGE_AUDITORS_AUDITOR_INACTIVE,
+ TALER_B2S (awc->auditor_pub));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_auditor_denom_validity_verify (
+ auditor_url,
+ awc->h_denom_pub,
+ &TEH_master_public_key,
+ meta.start,
+ meta.expire_withdraw,
+ meta.expire_deposit,
+ meta.expire_legal,
+ &meta.value,
+ &meta.fees,
+ awc->auditor_pub,
+ &awc->auditor_sig))
+ {
+ GNUNET_free (auditor_url);
+ /* signature invalid */
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_AUDITORS_AUDITOR_SIGNATURE_INVALID,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_free (auditor_url);
+
+ qs = TEH_plugin->insert_auditor_denom_sig (TEH_plugin->cls,
+ awc->h_denom_pub,
+ awc->auditor_pub,
+ &awc->auditor_sig);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "add auditor signature");
+ return qs;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_auditors (
+ struct MHD_Connection *connection,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const json_t *root)
+{
+ struct AddAuditorDenomContext awc = {
+ .auditor_pub = auditor_pub,
+ .h_denom_pub = h_denom_pub
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("auditor_sig",
+ &awc.auditor_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ MHD_RESULT res;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == ret)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == ret)
+ return MHD_YES; /* failure */
+ ret = TEH_DB_run_transaction (connection,
+ "add auditor denom sig",
+ TEH_MT_REQUEST_OTHER,
+ &res,
+ &add_auditor_denom_sig,
+ &awc);
+ if (GNUNET_SYSERR == ret)
+ return res;
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_auditors.c */
diff --git a/src/auditor/taler-auditor-httpd_exchanges.h b/src/exchange/taler-exchange-httpd_auditors.h
index c356d7e99..5d5c3a49c 100644
--- a/src/auditor/taler-auditor-httpd_exchanges.h
+++ b/src/exchange/taler-exchange-httpd_auditors.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018 Taler Systems SA
+ Copyright (C) 2020 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -14,33 +14,33 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file taler-auditor-httpd_exchanges.h
- * @brief Handle /exchanges requests
+ * @file taler-exchange-httpd_auditors.h
+ * @brief Handlers for the /auditors/ endpoints
* @author Christian Grothoff
*/
-#ifndef TALER_AUDITOR_HTTPD_EXCHANGES_H
-#define TALER_AUDITOR_HTTPD_EXCHANGES_H
+#ifndef TALER_EXCHANGE_HTTPD_AUDITORS_H
+#define TALER_EXCHANGE_HTTPD_AUDITORS_H
#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
-#include "taler-auditor-httpd.h"
+#include "taler-exchange-httpd.h"
/**
- * Handle a "/exchanges" request.
+ * Handle a "/auditors/$AUDITOR_PUB/$H_DENOM_PUB" request.
*
- * @param rh context of the handler
* @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param root uploaded JSON data
+ * @param auditor_pub public key of the auditor
+ * @param h_denom_pub hash of the denomination public key
* @return MHD result code
- */
-int
-TAH_EXCHANGES_handler (struct TAH_RequestHandler *rh,
- struct MHD_Connection *connection,
- void **connection_cls,
- const char *upload_data,
- size_t *upload_data_size);
+ */
+MHD_RESULT
+TEH_handler_auditors (
+ struct MHD_Connection *connection,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const json_t *root);
+
#endif
diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c b/src/exchange/taler-exchange-httpd_batch-deposit.c
new file mode 100644
index 000000000..84f27dd94
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_batch-deposit.c
@@ -0,0 +1,738 @@
+/*
+ This file is part of TALER
+ 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
+ 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_batch-deposit.c
+ * @brief Handle /batch-deposit requests; parses the POST and JSON and
+ * verifies the coin signatures before handing things off
+ * to the database.
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @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_extensions_policy.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_batch-deposit.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+/**
+ * Closure for #batch_deposit_transaction.
+ */
+struct BatchDepositContext
+{
+
+ /**
+ * Array with the individual coin deposit fees.
+ */
+ struct TALER_Amount *deposit_fees;
+
+ /**
+ * Our timestamp (when we received the request).
+ * Possibly updated by the transaction if the
+ * request is idempotent (was repeated).
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
+ * Details about the batch deposit operation.
+ */
+ struct TALER_EXCHANGEDB_BatchDeposit bd;
+
+
+ /**
+ * Total amount that is accumulated with this deposit,
+ * without fee.
+ */
+ struct TALER_Amount accumulated_total_without_fee;
+
+ /**
+ * True, if no policy was present in the request. Then
+ * @e policy_json is NULL and @e h_policy will be all zero.
+ */
+ bool has_no_policy;
+
+ /**
+ * Additional details for policy extension relevant for this
+ * deposit operation, possibly NULL!
+ */
+ json_t *policy_json;
+
+ /**
+ * If @e policy_json was present, the corresponding policy extension
+ * calculates these details. These will be persisted in the policy_details
+ * table.
+ */
+ struct TALER_PolicyDetails policy_details;
+
+ /**
+ * Hash over @e policy_details, might be all zero
+ */
+ struct TALER_ExtensionPolicyHashP h_policy;
+
+ /**
+ * Hash over the merchant's payto://-URI with the wire salt.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * When @e policy_details are persisted, this contains the id of the record
+ * in the policy_details table.
+ */
+ uint64_t policy_details_serial_id;
+
+};
+
+
+/**
+ * Send confirmation of batch deposit success to client. This function will
+ * create a signed message affirming the given information and return it to
+ * the client. By this, the exchange affirms that the coins had sufficient
+ * (residual) value for the specified transaction and that it will execute the
+ * requested batch deposit operation with the given wiring details.
+ *
+ * @param connection connection to the client
+ * @param dc information about the batch deposit
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_batch_deposit_success (
+ struct MHD_Connection *connection,
+ const struct BatchDepositContext *dc)
+{
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
+ const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (bd->num_cdis)];
+ enum TALER_ErrorCode ec;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+
+ for (unsigned int i = 0; i<bd->num_cdis; i++)
+ csigs[i] = &bd->cdis[i].csig;
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_deposit_confirmation_sign (
+ &TEH_keys_exchange_sign_,
+ &bd->h_contract_terms,
+ &dc->h_wire,
+ dc->has_no_policy ? NULL : &dc->h_policy,
+ dc->exchange_timestamp,
+ bd->wire_deadline,
+ bd->refund_deadline,
+ &dc->accumulated_total_without_fee,
+ bd->num_cdis,
+ csigs,
+ &dc->bd.merchant_pub,
+ &pub,
+ &sig)))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+ dc->exchange_timestamp),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig));
+}
+
+
+/**
+ * Execute database transaction for /batch-deposit. 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 BatchDepositContext`
+ * @param connection MHD request context
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+batch_deposit_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct BatchDepositContext *dc = cls;
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
+ enum GNUNET_DB_QueryStatus qs = GNUNET_DB_STATUS_HARD_ERROR;
+ uint32_t bad_balance_coin_index = UINT32_MAX;
+ bool balance_ok;
+ bool in_conflict;
+
+ /* If the deposit has a policy associated to it, persist it. This will
+ * insert or update the record. */
+ if (! dc->has_no_policy)
+ {
+ qs = TEH_plugin->persist_policy_details (
+ TEH_plugin->cls,
+ &dc->policy_details,
+ &dc->bd.policy_details_serial_id,
+ &dc->accumulated_total_without_fee,
+ &dc->policy_details.fulfillment_state);
+ if (qs < 0)
+ return qs;
+
+ dc->bd.policy_blocked =
+ dc->policy_details.fulfillment_state != TALER_PolicyFulfillmentSuccess;
+ }
+
+ /* FIXME: replace by batch insert! */
+ for (unsigned int i = 0; i<bd->num_cdis; i++)
+ {
+ const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+ = &bd->cdis[i];
+ uint64_t known_coin_id;
+
+ qs = TEH_make_coin_known (&cdi->coin,
+ connection,
+ &known_coin_id,
+ mhd_ret);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "make coin known (%s) returned %d\n",
+ TALER_B2S (&cdi->coin.coin_pub),
+ qs);
+ if (qs < 0)
+ return qs;
+ }
+
+ qs = TEH_plugin->do_deposit (
+ TEH_plugin->cls,
+ bd,
+ &dc->exchange_timestamp,
+ &balance_ok,
+ &bad_balance_coin_index,
+ &in_conflict);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ TALER_LOG_WARNING (
+ "Failed to store /batch-deposit information in database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "batch-deposit");
+ return qs;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "do_deposit returned: %d / %s[%u] / %s\n",
+ qs,
+ balance_ok ? "balance ok" : "balance insufficient",
+ (unsigned int) bad_balance_coin_index,
+ in_conflict ? "in conflict" : "no conflict");
+ if (in_conflict)
+ {
+ struct TALER_MerchantWireHashP h_wire;
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ TEH_plugin->get_wire_hash_for_contract (
+ TEH_plugin->cls,
+ &bd->merchant_pub,
+ &bd->h_contract_terms,
+ &h_wire))
+ {
+ TALER_LOG_WARNING (
+ "Failed to retrieve conflicting contract details from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "batch-deposit");
+ return qs;
+ }
+
+ *mhd_ret
+ = TEH_RESPONSE_reply_coin_conflicting_contract (
+ connection,
+ TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
+ &h_wire);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! balance_ok)
+ {
+ GNUNET_assert (bad_balance_coin_index < bd->num_cdis);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "returning history of conflicting coin (%s)\n",
+ TALER_B2S (&bd->cdis[bad_balance_coin_index].coin.coin_pub));
+ *mhd_ret
+ = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &bd->cdis[bad_balance_coin_index].coin.denom_pub_hash,
+ &bd->cdis[bad_balance_coin_index].coin.coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT]++;
+ return qs;
+}
+
+
+/**
+ * Parse per-coin deposit information from @a jcoin
+ * into @a deposit. Fill in generic information from
+ * @a ctx.
+ *
+ * @param connection connection we are handling
+ * @param dc information about the overall batch
+ * @param jcoin coin data to parse
+ * @param[out] cdi where to store the result
+ * @param[out] deposit_fee where to write the deposit fee
+ * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
+ * #GNUNET_SYSERR on failure and no error could be returned
+ */
+static enum GNUNET_GenericReturnValue
+parse_coin (struct MHD_Connection *connection,
+ const struct BatchDepositContext *dc,
+ json_t *jcoin,
+ struct TALER_EXCHANGEDB_CoinDepositInformation *cdi,
+ struct TALER_Amount *deposit_fee)
+{
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("contribution",
+ TEH_currency,
+ &cdi->amount_with_fee),
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+ &cdi->coin.denom_pub_hash),
+ TALER_JSON_spec_denom_sig ("ub_sig",
+ &cdi->coin.denom_sig),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &cdi->coin.coin_pub),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &cdi->coin.h_age_commitment),
+ &cdi->coin.no_age_commitment),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &cdi->csig),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ if (GNUNET_OK !=
+ (res = TALER_MHD_parse_json_data (connection,
+ jcoin,
+ spec)))
+ return res;
+ /* check denomination exists and is valid */
+ {
+ struct TEH_DenominationKey *dk;
+ MHD_RESULT mret;
+
+ dk = TEH_keys_denomination_by_hash (&cdi->coin.denom_pub_hash,
+ connection,
+ &mret);
+ if (NULL == dk)
+ {
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES == mret)
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (0 > TALER_amount_cmp (&dk->meta.value,
+ &cdi->amount_with_fee))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
+ NULL))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
+ {
+ /* This denomination is past the expiration time for deposits */
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &cdi->coin.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "DEPOSIT"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ /* This denomination is not yet valid */
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &cdi->coin.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "DEPOSIT"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (dk->recoup_possible)
+ {
+ /* This denomination has been revoked */
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &cdi->coin.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+ "DEPOSIT"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (dk->denom_pub.bsign_pub_key->cipher !=
+ cdi->coin.denom_sig.unblinded_sig->cipher)
+ {
+ /* denomination cipher and denomination signature cipher not the same */
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+ NULL))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+
+ *deposit_fee = dk->meta.fees.deposit;
+ /* check coin signature */
+ switch (dk->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
+ break;
+ default:
+ break;
+ }
+ if (GNUNET_YES !=
+ TALER_test_coin_valid (&cdi->coin,
+ &dk->denom_pub))
+ {
+ TALER_LOG_WARNING ("Invalid coin passed for /batch-deposit\n");
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+ NULL))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ }
+ if (0 < TALER_amount_cmp (deposit_fee,
+ &cdi->amount_with_fee))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
+ NULL))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_deposit_verify (
+ &cdi->amount_with_fee,
+ deposit_fee,
+ &dc->h_wire,
+ &bd->h_contract_terms,
+ &bd->wallet_data_hash,
+ cdi->coin.no_age_commitment
+ ? NULL
+ : &cdi->coin.h_age_commitment,
+ NULL != dc->policy_json ? &dc->h_policy : NULL,
+ &cdi->coin.denom_pub_hash,
+ bd->wallet_timestamp,
+ &bd->merchant_pub,
+ bd->refund_deadline,
+ &cdi->coin.coin_pub,
+ &cdi->csig))
+ {
+ TALER_LOG_WARNING ("Invalid signature on /batch-deposit request\n");
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
+ TALER_B2S (&cdi->coin.coin_pub)))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+MHD_RESULT
+TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ struct MHD_Connection *connection = rc->connection;
+ struct BatchDepositContext dc = { 0 };
+ struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc.bd;
+ const json_t *coins;
+ bool no_refund_deadline = true;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_payto_uri ("merchant_payto_uri",
+ &bd->receiver_wire_account),
+ GNUNET_JSON_spec_fixed_auto ("wire_salt",
+ &bd->wire_salt),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &bd->merchant_pub),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &bd->h_contract_terms),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
+ &bd->wallet_data_hash),
+ &bd->no_wallet_data_hash),
+ GNUNET_JSON_spec_array_const ("coins",
+ &coins),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("policy",
+ &dc.policy_json),
+ &dc.has_no_policy),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &bd->wallet_timestamp),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("refund_deadline",
+ &bd->refund_deadline),
+ &no_refund_deadline),
+ GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
+ &bd->wire_deadline),
+ GNUNET_JSON_spec_end ()
+ };
+
+ (void) args;
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+
+ /* validate merchant's wire details (as far as we can) */
+ {
+ char *emsg;
+
+ emsg = TALER_payto_validate (bd->receiver_wire_account);
+ if (NULL != emsg)
+ {
+ MHD_RESULT ret;
+
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ emsg);
+ GNUNET_free (emsg);
+ return ret;
+ }
+ }
+ if (GNUNET_TIME_timestamp_cmp (bd->refund_deadline,
+ >,
+ bd->wire_deadline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
+ NULL);
+ }
+ if (GNUNET_TIME_absolute_is_never (bd->wire_deadline.abs_time))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER,
+ NULL);
+ }
+ TALER_payto_hash (bd->receiver_wire_account,
+ &bd->wire_target_h_payto);
+ TALER_merchant_wire_signature_hash (bd->receiver_wire_account,
+ &bd->wire_salt,
+ &dc.h_wire);
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &dc.accumulated_total_without_fee));
+
+ /* handle policy, if present */
+ if (! dc.has_no_policy)
+ {
+ const char *error_hint = NULL;
+
+ if (GNUNET_OK !=
+ TALER_extensions_create_policy_details (
+ TEH_currency,
+ dc.policy_json,
+ &dc.policy_details,
+ &error_hint))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED,
+ error_hint);
+ }
+
+ TALER_deposit_policy_hash (dc.policy_json,
+ &dc.h_policy);
+ }
+
+ bd->num_cdis = json_array_size (coins);
+ if (0 == bd->num_cdis)
+ {
+ 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,
+ "coins");
+ }
+ if (TALER_MAX_FRESH_COINS < bd->num_cdis)
+ {
+ 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,
+ "coins");
+ }
+
+ {
+ struct TALER_EXCHANGEDB_CoinDepositInformation cdis[
+ GNUNET_NZL (bd->num_cdis)];
+ struct TALER_Amount deposit_fees[GNUNET_NZL (bd->num_cdis)];
+ enum GNUNET_GenericReturnValue res;
+ unsigned int i;
+
+ bd->cdis = cdis;
+ dc.deposit_fees = deposit_fees;
+ for (i = 0; i<bd->num_cdis; i++)
+ {
+ struct TALER_Amount amount_without_fee;
+
+ res = parse_coin (connection,
+ &dc,
+ json_array_get (coins,
+ i),
+ &cdis[i],
+ &deposit_fees[i]);
+ if (GNUNET_OK != res)
+ break;
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (
+ &amount_without_fee,
+ &cdis[i].amount_with_fee,
+ &deposit_fees[i]));
+
+ GNUNET_assert (0 <=
+ TALER_amount_add (
+ &dc.accumulated_total_without_fee,
+ &dc.accumulated_total_without_fee,
+ &amount_without_fee));
+ }
+ if (GNUNET_OK != res)
+ {
+ for (unsigned int j = 0; j<i; j++)
+ TALER_denom_sig_free (&cdis[j].coin.denom_sig);
+ GNUNET_JSON_parse_free (spec);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+
+ dc.exchange_timestamp = GNUNET_TIME_timestamp_get ();
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure");
+ }
+
+ /* execute transaction */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "execute batch deposit",
+ TEH_MT_REQUEST_BATCH_DEPOSIT,
+ &mhd_ret,
+ &batch_deposit_transaction,
+ &dc))
+ {
+ for (unsigned int j = 0; j<bd->num_cdis; j++)
+ TALER_denom_sig_free (&cdis[j].coin.denom_sig);
+ GNUNET_JSON_parse_free (spec);
+ return mhd_ret;
+ }
+ }
+
+ /* generate regular response */
+ {
+ MHD_RESULT mhd_ret;
+
+ mhd_ret = reply_batch_deposit_success (connection,
+ &dc);
+ for (unsigned int j = 0; j<bd->num_cdis; j++)
+ TALER_denom_sig_free (&cdis[j].coin.denom_sig);
+ GNUNET_JSON_parse_free (spec);
+ return mhd_ret;
+ }
+ }
+}
+
+
+/* end of taler-exchange-httpd_batch-deposit.c */
diff --git a/src/exchange/taler-exchange-httpd_deposit.h b/src/exchange/taler-exchange-httpd_batch-deposit.h
index 642e29dd5..187fb9f20 100644
--- a/src/exchange/taler-exchange-httpd_deposit.h
+++ b/src/exchange/taler-exchange-httpd_batch-deposit.h
@@ -14,14 +14,14 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file taler-exchange-httpd_deposit.h
- * @brief Handle /deposit requests
+ * @file taler-exchange-httpd_batch-deposit.h
+ * @brief Handle /batch-deposit requests
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*/
-#ifndef TALER_EXCHANGE_HTTPD_DEPOSIT_H
-#define TALER_EXCHANGE_HTTPD_DEPOSIT_H
+#ifndef TALER_EXCHANGE_HTTPD_BATCH_DEPOSIT_H
+#define TALER_EXCHANGE_HTTPD_BATCH_DEPOSIT_H
#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
@@ -29,21 +29,21 @@
/**
- * Handle a "/coins/$COIN_PUB/deposit" request. Parses the JSON, and, if
+ * Handle a "/batch-deposit" request. Parses the JSON, and, if
* successful, passes the JSON data to #deposit_transaction() to
* further check the details of the operation specified. If everything checks
- * out, this will ultimately lead to the "/deposit" being executed, or
+ * out, this will ultimately lead to the "/batch-deposit" being executed, or
* rejected.
*
- * @param connection the MHD connection to handle
- * @param coin_pub public key of the coin
+ * @param rc request context
* @param root uploaded JSON data
+ * @param args arguments, empty in this case
* @return MHD result code
*/
-int
-TEH_handler_deposit (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const json_t *root);
+MHD_RESULT
+TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[]);
#endif
diff --git a/src/exchange/taler-exchange-httpd_batch-withdraw.c b/src/exchange/taler-exchange-httpd_batch-withdraw.c
new file mode 100644
index 000000000..2b80c2fc4
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_batch-withdraw.c
@@ -0,0 +1,935 @@
+/*
+ This file is part of TALER
+ 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 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_batch-withdraw.c
+ * @brief Handle /reserves/$RESERVE_PUB/batch-withdraw requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler-exchange-httpd.h"
+#include "taler_json_lib.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_batch-withdraw.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler_util.h"
+
+
+/**
+ * Information per planchet in the batch.
+ */
+struct PlanchetContext
+{
+
+ /**
+ * Hash of the (blinded) message to be signed by the Exchange.
+ */
+ struct TALER_BlindedCoinHashP h_coin_envelope;
+
+ /**
+ * Value of the coin being exchanged (matching the denomination key)
+ * plus the transaction fee. We include this in what is being
+ * signed so that we can verify a reserve's remaining total balance
+ * without needing to access the respective denomination key
+ * information each time.
+ */
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Blinded planchet.
+ */
+ struct TALER_BlindedPlanchet blinded_planchet;
+
+ /**
+ * Set to the resulting signed coin data to be returned to the client.
+ */
+ struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
+
+};
+
+/**
+ * Context for #batch_withdraw_transaction.
+ */
+struct BatchWithdrawContext
+{
+
+ /**
+ * Public key of the reserve.
+ */
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+
+ /**
+ * request context
+ */
+ const struct TEH_RequestContext *rc;
+
+ /**
+ * KYC status of the reserve used for the operation.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Array of @e planchets_length planchets we are processing.
+ */
+ struct PlanchetContext *planchets;
+
+ /**
+ * Hash of the payto-URI representing the reserve
+ * from which we are withdrawing.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Current time for the DB transaction.
+ */
+ struct GNUNET_TIME_Timestamp now;
+
+ /**
+ * Total amount from all coins with fees.
+ */
+ struct TALER_Amount batch_total;
+
+ /**
+ * Length of the @e planchets array.
+ */
+ unsigned int planchets_length;
+
+ /**
+ * AML decision, #TALER_AML_NORMAL if we may proceed.
+ */
+ enum TALER_AmlDecisionState aml_decision;
+
+};
+
+
+/**
+ * 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
+batch_withdraw_amount_cb (void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ struct BatchWithdrawContext *wc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (GNUNET_OK !=
+ cb (cb_cls,
+ &wc->batch_total,
+ wc->now.abs_time))
+ return;
+ qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (
+ TEH_plugin->cls,
+ &wc->h_payto,
+ limit,
+ cb,
+ cb_cls);
+ GNUNET_break (qs >= 0);
+}
+
+
+/**
+ * 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
+ * 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 "wc->collectable.sig" is set before entering this function as we
+ * signed before entering the transaction.
+ *
+ * @param cls a `struct BatchWithdrawContext *`
+ * @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
+batch_withdraw_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct BatchWithdrawContext *wc = cls;
+ uint64_t ruuid;
+ enum GNUNET_DB_QueryStatus qs;
+ bool found = false;
+ bool balance_ok = false;
+ bool age_ok = false;
+ uint16_t allowed_maximum_age = 0;
+ struct TALER_Amount reserve_balance;
+ 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 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)
+ {
+ 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,
+ &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;
+ qs = TEH_plugin->insert_kyc_requirement_for_account (
+ TEH_plugin->cls,
+ kyc_required,
+ &wc->h_payto,
+ wc->reserve_pub,
+ &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,
+ wc->now,
+ wc->reserve_pub,
+ &wc->batch_total,
+ TEH_age_restriction_enabled,
+ &found,
+ &balance_ok,
+ &reserve_balance,
+ &age_ok,
+ &allowed_maximum_age,
+ &ruuid);
+ 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)
+ {
+ *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;
+ }
+
+ if (! age_ok)
+ {
+ /* We respond with the lowest age in the corresponding age group
+ * of the required age */
+ uint16_t lowest_age = TALER_get_lowest_age (
+ &TEH_age_restriction_config.mask,
+ allowed_maximum_age);
+
+ TEH_plugin->rollback (TEH_plugin->cls);
+ *mhd_ret = TEH_RESPONSE_reply_reserve_age_restriction_required (
+ connection,
+ lowest_age);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ if (! balance_ok)
+ {
+ TEH_plugin->rollback (TEH_plugin->cls);
+ *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance (
+ connection,
+ TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS,
+ &reserve_balance,
+ &wc->batch_total,
+ wc->reserve_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ /* Add information about each planchet in the batch */
+ for (unsigned int i = 0; i<wc->planchets_length; i++)
+ {
+ struct PlanchetContext *pc = &wc->planchets[i];
+ const struct TALER_BlindedPlanchet *bp = &pc->blinded_planchet;
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce = NULL;
+ bool denom_unknown = true;
+ bool conflict = true;
+ bool nonce_reuse = true;
+
+ switch (bp->blinded_message->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ nonce = (const union GNUNET_CRYPTO_BlindSessionNonce *)
+ &bp->blinded_message->details.cs_blinded_message.nonce;
+ break;
+ }
+ qs = TEH_plugin->do_batch_withdraw_insert (TEH_plugin->cls,
+ nonce,
+ &pc->collectable,
+ wc->now,
+ ruuid,
+ &denom_unknown,
+ &conflict,
+ &nonce_reuse);
+ 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_batch_withdraw_insert");
+ return qs;
+ }
+ if (denom_unknown)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
+ (conflict) )
+ {
+ if (! check_request_idempotent (wc,
+ mhd_ret))
+ {
+ /* We do not support *some* of the coins of the request being
+ idempotent while others being fresh. */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Idempotent coin in batch, not allowed. Aborting.\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_WITHDRAW_BATCH_IDEMPOTENT_PLANCHET,
+ NULL);
+ }
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (nonce_reuse)
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_BATCH_WITHDRAW]++;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+/**
+ * The request was parsed successfully. Prepare
+ * our side for the main DB transaction.
+ *
+ * @param rc request details
+ * @param wc storage for request processing
+ * @return MHD result for the @a rc
+ */
+static MHD_RESULT
+prepare_transaction (const struct TEH_RequestContext *rc,
+ struct BatchWithdrawContext *wc)
+{
+ struct TEH_CoinSignData csds[wc->planchets_length];
+ struct TALER_BlindedDenominationSignature bss[wc->planchets_length];
+
+ for (unsigned int i = 0; i<wc->planchets_length; i++)
+ {
+ struct PlanchetContext *pc = &wc->planchets[i];
+
+ csds[i].h_denom_pub = &pc->collectable.denom_pub_hash;
+ csds[i].bp = &pc->blinded_planchet;
+ }
+ {
+ enum TALER_ErrorCode ec;
+
+ ec = TEH_keys_denomination_batch_sign (
+ wc->planchets_length,
+ csds,
+ false,
+ bss);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (rc->connection,
+ ec,
+ NULL);
+ }
+ }
+ for (unsigned int i = 0; i<wc->planchets_length; i++)
+ {
+ struct PlanchetContext *pc = &wc->planchets[i];
+
+ pc->collectable.sig = bss[i];
+ }
+
+ /* run transaction */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (rc->connection,
+ "run batch withdraw",
+ TEH_MT_REQUEST_WITHDRAW,
+ &mhd_ret,
+ &batch_withdraw_transaction,
+ wc))
+ {
+ return mhd_ret;
+ }
+ }
+ /* return final positive response */
+ return generate_reply_success (rc,
+ wc);
+}
+
+
+/**
+ * Continue processing the request @a rc by parsing the
+ * @a planchets and then running the transaction.
+ *
+ * @param rc request details
+ * @param wc storage for request processing
+ * @param planchets array of planchets to parse
+ * @return MHD result for the @a rc
+ */
+static MHD_RESULT
+parse_planchets (const struct TEH_RequestContext *rc,
+ struct BatchWithdrawContext *wc,
+ const json_t *planchets)
+{
+ struct TEH_KeyStateHandle *ksh;
+ MHD_RESULT mret;
+
+ for (unsigned int i = 0; i<wc->planchets_length; i++)
+ {
+ struct PlanchetContext *pc = &wc->planchets[i];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &pc->collectable.reserve_sig),
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+ &pc->collectable.denom_pub_hash),
+ TALER_JSON_spec_blinded_planchet ("coin_ev",
+ &pc->blinded_planchet),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (rc->connection,
+ json_array_get (planchets,
+ i),
+ ispec);
+ if (GNUNET_OK != res)
+ return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+ }
+ pc->collectable.reserve_pub = *wc->reserve_pub;
+ for (unsigned int k = 0; k<i; k++)
+ {
+ const struct PlanchetContext *kpc = &wc->planchets[k];
+
+ if (0 ==
+ TALER_blinded_planchet_cmp (&kpc->blinded_planchet,
+ &pc->blinded_planchet))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "duplicate planchet");
+ }
+ }
+ }
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ if (! check_request_idempotent (wc,
+ &mret))
+ {
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ }
+ return mret;
+ }
+ for (unsigned int i = 0; i<wc->planchets_length; i++)
+ {
+ struct PlanchetContext *pc = &wc->planchets[i];
+ struct TEH_DenominationKey *dk;
+
+ dk = TEH_keys_denomination_by_hash_from_state (
+ ksh,
+ &pc->collectable.denom_pub_hash,
+ NULL,
+ NULL);
+
+ if (NULL == dk)
+ {
+ if (! check_request_idempotent (wc,
+ &mret))
+ {
+ return TEH_RESPONSE_reply_unknown_denom_pub_hash (
+ rc->connection,
+ &pc->collectable.denom_pub_hash);
+ }
+ return mret;
+ }
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
+ {
+ /* This denomination is past the expiration time for withdraws */
+ if (! check_request_idempotent (wc,
+ &mret))
+ {
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ rc->connection,
+ &pc->collectable.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "WITHDRAW");
+ }
+ return mret;
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ /* This denomination is not yet valid, no need to check
+ for idempotency! */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ rc->connection,
+ &pc->collectable.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "WITHDRAW");
+ }
+ if (dk->recoup_possible)
+ {
+ /* This denomination has been revoked */
+ if (! check_request_idempotent (wc,
+ &mret))
+ {
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ rc->connection,
+ &pc->collectable.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+ "WITHDRAW");
+ }
+ return mret;
+ }
+ if (dk->denom_pub.bsign_pub_key->cipher !=
+ pc->blinded_planchet.blinded_message->cipher)
+ {
+ /* denomination cipher and blinded planchet cipher not the same */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+ NULL);
+ }
+ if (0 >
+ TALER_amount_add (&pc->collectable.amount_with_fee,
+ &dk->meta.value,
+ &dk->meta.fees.withdraw))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
+ NULL);
+ }
+ if (0 >
+ TALER_amount_add (&wc->batch_total,
+ &wc->batch_total,
+ &pc->collectable.amount_with_fee))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
+ NULL);
+ }
+
+ TALER_coin_ev_hash (&pc->blinded_planchet,
+ &pc->collectable.denom_pub_hash,
+ &pc->collectable.h_coin_envelope);
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_withdraw_verify (&pc->collectable.denom_pub_hash,
+ &pc->collectable.amount_with_fee,
+ &pc->collectable.h_coin_envelope,
+ &pc->collectable.reserve_pub,
+ &pc->collectable.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
+ NULL);
+ }
+ }
+ /* everything parsed */
+ return prepare_transaction (rc,
+ wc);
+}
+
+
+MHD_RESULT
+TEH_handler_batch_withdraw (struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root)
+{
+ struct BatchWithdrawContext wc = {
+ .reserve_pub = reserve_pub,
+ .rc = rc
+ };
+ const json_t *planchets;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("planchets",
+ &planchets),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &wc.batch_total));
+ {
+ 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;
+ }
+ wc.planchets_length = json_array_size (planchets);
+ if (0 == wc.planchets_length)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "planchets");
+ }
+ if (wc.planchets_length > TALER_MAX_FRESH_COINS)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "too many planchets");
+ }
+ {
+ struct PlanchetContext splanchets[wc.planchets_length];
+ MHD_RESULT ret;
+
+ memset (splanchets,
+ 0,
+ sizeof (splanchets));
+ wc.planchets = splanchets;
+ ret = parse_planchets (rc,
+ &wc,
+ planchets);
+ /* Clean up */
+ for (unsigned int i = 0; i<wc.planchets_length; i++)
+ {
+ struct PlanchetContext *pc = &wc.planchets[i];
+
+ TALER_blinded_planchet_free (&pc->blinded_planchet);
+ TALER_blinded_denom_sig_free (&pc->collectable.sig);
+ }
+ return ret;
+ }
+}
+
+
+/* end of taler-exchange-httpd_batch-withdraw.c */
diff --git a/src/exchange/taler-exchange-httpd_batch-withdraw.h b/src/exchange/taler-exchange-httpd_batch-withdraw.h
new file mode 100644
index 000000000..dfc6e5ad8
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_batch-withdraw.h
@@ -0,0 +1,48 @@
+/*
+ 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 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_batch-withdraw.h
+ * @brief Handle /reserve/batch-withdraw requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_BATCH_WITHDRAW_H
+#define TALER_EXCHANGE_HTTPD_BATCH_WITHDRAW_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/reserves/$RESERVE_PUB/batch-withdraw" request. Parses the batch of
+ * requested "denom_pub" which specifies the key/value of the coin to be
+ * withdrawn, and checks that the signature "reserve_sig" makes this a valid
+ * withdrawal request from the specified reserve. If so, the envelope with
+ * the blinded coin "coin_ev" is passed down to execute the withdrawal
+ * operation.
+ *
+ * @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_batch_withdraw (struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_coins_get.c b/src/exchange/taler-exchange-httpd_coins_get.c
new file mode 100644
index 000000000..cd453275e
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_coins_get.c
@@ -0,0 +1,709 @@
+/*
+ This file is part of TALER
+ 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
+ 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_coins_get.c
+ * @brief Handle GET /coins/$COIN_PUB/history requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_mhd_lib.h"
+#include "taler_json_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_coins_get.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Add the headers we want to set for every response.
+ *
+ * @param cls the key state to use
+ * @param[in,out] response the response to modify
+ */
+static void
+add_response_headers (void *cls,
+ struct MHD_Response *response)
+{
+ (void) cls;
+ TALER_MHD_add_global_headers (response);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "no-cache"));
+}
+
+
+/**
+ * Compile the transaction history of a coin into a JSON object.
+ *
+ * @param coin_pub public key of the coin
+ * @param tl transaction history to JSON-ify
+ * @return json representation of the @a rh, NULL on error
+ */
+static json_t *
+compile_transaction_history (
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_EXCHANGEDB_TransactionList *tl)
+{
+ json_t *history;
+
+ history = json_array ();
+ if (NULL == history)
+ {
+ GNUNET_break (0); /* out of memory!? */
+ return NULL;
+ }
+ for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
+ NULL != pos;
+ pos = pos->next)
+ {
+ switch (pos->type)
+ {
+ case TALER_EXCHANGEDB_TT_DEPOSIT:
+ {
+ const struct TALER_EXCHANGEDB_DepositListEntry *deposit =
+ pos->details.deposit;
+ struct TALER_MerchantWireHashP h_wire;
+
+ TALER_merchant_wire_signature_hash (deposit->receiver_wire_account,
+ &deposit->wire_salt,
+ &h_wire);
+#if ENABLE_SANITY_CHECKS
+ /* internal sanity check before we hand out a bogus sig... */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_deposit_verify (
+ &deposit->amount_with_fee,
+ &deposit->deposit_fee,
+ &h_wire,
+ &deposit->h_contract_terms,
+ deposit->no_wallet_data_hash
+ ? NULL
+ : &deposit->wallet_data_hash,
+ deposit->no_age_commitment
+ ? NULL
+ : &deposit->h_age_commitment,
+ &deposit->h_policy,
+ &deposit->h_denom_pub,
+ deposit->timestamp,
+ &deposit->merchant_pub,
+ deposit->refund_deadline,
+ coin_pub,
+ &deposit->csig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+#endif
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "DEPOSIT"),
+ TALER_JSON_pack_amount ("amount",
+ &deposit->amount_with_fee),
+ TALER_JSON_pack_amount ("deposit_fee",
+ &deposit->deposit_fee),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ deposit->timestamp),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("refund_deadline",
+ deposit->refund_deadline)),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ &deposit->merchant_pub),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &deposit->h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("h_wire",
+ &h_wire),
+ GNUNET_JSON_pack_allow_null (
+ deposit->no_age_commitment ?
+ GNUNET_JSON_pack_string (
+ "h_age_commitment", NULL) :
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ &deposit->h_age_commitment)),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &deposit->csig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+ case TALER_EXCHANGEDB_TT_MELT:
+ {
+ const struct TALER_EXCHANGEDB_MeltListEntry *melt =
+ pos->details.melt;
+ const struct TALER_AgeCommitmentHash *phac = NULL;
+
+#if ENABLE_SANITY_CHECKS
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_melt_verify (
+ &melt->amount_with_fee,
+ &melt->melt_fee,
+ &melt->rc,
+ &melt->h_denom_pub,
+ &melt->h_age_commitment,
+ coin_pub,
+ &melt->coin_sig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+#endif
+
+ /* Age restriction is optional. We communicate a NULL value to
+ * JSON_PACK below */
+ if (! melt->no_age_commitment)
+ phac = &melt->h_age_commitment;
+
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "MELT"),
+ TALER_JSON_pack_amount ("amount",
+ &melt->amount_with_fee),
+ TALER_JSON_pack_amount ("melt_fee",
+ &melt->melt_fee),
+ GNUNET_JSON_pack_data_auto ("rc",
+ &melt->rc),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ phac)),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &melt->coin_sig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_TT_REFUND:
+ {
+ const struct TALER_EXCHANGEDB_RefundListEntry *refund =
+ pos->details.refund;
+ struct TALER_Amount value;
+
+#if ENABLE_SANITY_CHECKS
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_merchant_refund_verify (
+ coin_pub,
+ &refund->h_contract_terms,
+ refund->rtransaction_id,
+ &refund->refund_amount,
+ &refund->merchant_pub,
+ &refund->merchant_sig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+#endif
+ if (0 >
+ TALER_amount_subtract (&value,
+ &refund->refund_amount,
+ &refund->refund_fee))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "REFUND"),
+ TALER_JSON_pack_amount ("amount",
+ &value),
+ TALER_JSON_pack_amount ("refund_fee",
+ &refund->refund_fee),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &refund->h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ &refund->merchant_pub),
+ GNUNET_JSON_pack_uint64 ("rtransaction_id",
+ refund->rtransaction_id),
+ GNUNET_JSON_pack_data_auto ("merchant_sig",
+ &refund->merchant_sig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
+ {
+ struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
+ pos->details.old_coin_recoup;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_confirm_recoup_refresh_sign (
+ &TEH_keys_exchange_sign_,
+ pr->timestamp,
+ &pr->value,
+ &pr->coin.coin_pub,
+ &pr->old_coin_pub,
+ &epub,
+ &esig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ /* NOTE: we could also provide coin_pub's coin_sig, denomination key hash and
+ the denomination key's RSA signature over coin_pub, but as the
+ wallet should really already have this information (and cannot
+ check or do anything with it anyway if it doesn't), it seems
+ strictly unnecessary. */
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "OLD-COIN-RECOUP"),
+ TALER_JSON_pack_amount ("amount",
+ &pr->value),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &pr->coin.coin_pub),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ pr->timestamp))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+ case TALER_EXCHANGEDB_TT_RECOUP:
+ {
+ const struct TALER_EXCHANGEDB_RecoupListEntry *recoup =
+ pos->details.recoup;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_confirm_recoup_sign (
+ &TEH_keys_exchange_sign_,
+ recoup->timestamp,
+ &recoup->value,
+ coin_pub,
+ &recoup->reserve_pub,
+ &epub,
+ &esig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RECOUP"),
+ TALER_JSON_pack_amount ("amount",
+ &recoup->value),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &recoup->reserve_pub),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &recoup->coin_sig),
+ GNUNET_JSON_pack_data_auto ("coin_blind",
+ &recoup->coin_blind),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &recoup->reserve_pub),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ recoup->timestamp))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
+ {
+ struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
+ pos->details.recoup_refresh;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_confirm_recoup_refresh_sign (
+ &TEH_keys_exchange_sign_,
+ pr->timestamp,
+ &pr->value,
+ coin_pub,
+ &pr->old_coin_pub,
+ &epub,
+ &esig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ /* NOTE: we could also provide coin_pub's coin_sig, denomination key
+ hash and the denomination key's RSA signature over coin_pub, but as
+ the wallet should really already have this information (and cannot
+ check or do anything with it anyway if it doesn't), it seems
+ strictly unnecessary. */
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RECOUP-REFRESH"),
+ TALER_JSON_pack_amount ("amount",
+ &pr->value),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("old_coin_pub",
+ &pr->old_coin_pub),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &pr->coin_sig),
+ GNUNET_JSON_pack_data_auto ("coin_blind",
+ &pr->coin_blind),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ pr->timestamp))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+
+ case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
+ {
+ struct TALER_EXCHANGEDB_PurseDepositListEntry *pd
+ = pos->details.purse_deposit;
+ const struct TALER_AgeCommitmentHash *phac = NULL;
+
+ if (! pd->no_age_commitment)
+ phac = &pd->h_age_commitment;
+
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "PURSE-DEPOSIT"),
+ TALER_JSON_pack_amount ("amount",
+ &pd->amount),
+ GNUNET_JSON_pack_string ("exchange_base_url",
+ NULL == pd->exchange_base_url
+ ? TEH_base_url
+ : pd->exchange_base_url),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ phac)),
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ &pd->purse_pub),
+ GNUNET_JSON_pack_bool ("refunded",
+ pd->refunded),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &pd->coin_sig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+
+ case TALER_EXCHANGEDB_TT_PURSE_REFUND:
+ {
+ const struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund =
+ pos->details.purse_refund;
+ struct TALER_Amount value;
+ enum TALER_ErrorCode ec;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+
+ if (0 >
+ TALER_amount_subtract (&value,
+ &prefund->refund_amount,
+ &prefund->refund_fee))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ ec = TALER_exchange_online_purse_refund_sign (
+ &TEH_keys_exchange_sign_,
+ &value,
+ &prefund->refund_fee,
+ coin_pub,
+ &prefund->purse_pub,
+ &epub,
+ &esig);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "PURSE-REFUND"),
+ TALER_JSON_pack_amount ("amount",
+ &value),
+ TALER_JSON_pack_amount ("refund_fee",
+ &prefund->refund_fee),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ &prefund->purse_pub))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ }
+ break;
+
+ case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
+ {
+ struct TALER_EXCHANGEDB_ReserveOpenListEntry *role
+ = pos->details.reserve_open;
+
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RESERVE-OPEN-DEPOSIT"),
+ TALER_JSON_pack_amount ("coin_contribution",
+ &role->coin_contribution),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &role->reserve_sig),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &role->coin_sig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+ }
+ }
+ return history;
+}
+
+
+MHD_RESULT
+TEH_handler_coins_get (struct TEH_RequestContext *rc,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+ struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
+ uint64_t start_off = 0;
+ uint64_t etag_in;
+ uint64_t etag_out;
+ char etagp[24];
+ struct MHD_Response *resp;
+ unsigned int http_status;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_Amount balance;
+
+ TALER_MHD_parse_request_number (rc->connection,
+ "start",
+ &start_off);
+ /* Check signature */
+ {
+ struct TALER_CoinSpendSignatureP coin_sig;
+ bool required = true;
+
+ TALER_MHD_parse_request_header_auto (rc->connection,
+ TALER_COIN_HISTORY_SIGNATURE_HEADER,
+ &coin_sig,
+ required);
+ if (GNUNET_OK !=
+ TALER_wallet_coin_history_verify (start_off,
+ coin_pub,
+ &coin_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_COIN_HISTORY_BAD_SIGNATURE,
+ NULL);
+ }
+ }
+
+ /* Get etag */
+ {
+ const char *etags;
+
+ etags = MHD_lookup_connection_value (rc->connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ if (NULL != etags)
+ {
+ char dummy;
+ unsigned long long ev;
+
+ if (1 != sscanf (etags,
+ "\"%llu\"%c",
+ &ev,
+ &dummy))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client send malformed `If-None-Match' header `%s'\n",
+ etags);
+ etag_in = start_off;
+ }
+ else
+ {
+ etag_in = (uint64_t) ev;
+ }
+ }
+ else
+ {
+ etag_in = start_off;
+ }
+ }
+
+ /* Get history from DB between etag and now */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
+ coin_pub,
+ start_off,
+ etag_in,
+ &etag_out,
+ &balance,
+ &h_denom_pub,
+ &tl);
+ switch (qs)
+ {
+ 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_coin_history");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "get_coin_history");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_COIN_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* Handled below */
+ break;
+ }
+ }
+
+ GNUNET_snprintf (etagp,
+ sizeof (etagp),
+ "\"%llu\"",
+ (unsigned long long) etag_out);
+ if (etag_in == etag_out)
+ {
+ return TEH_RESPONSE_reply_not_modified (rc->connection,
+ etagp,
+ &add_response_headers,
+ NULL);
+ }
+ if (NULL == tl)
+ {
+ /* 204: empty history */
+ resp = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ http_status = MHD_HTTP_NO_CONTENT;
+ }
+ else
+ {
+ /* 200: regular history */
+ json_t *history;
+
+ history = compile_transaction_history (coin_pub,
+ tl);
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ tl = NULL;
+ if (NULL == history)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+ "Failed to compile coin history");
+ }
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &h_denom_pub),
+ TALER_JSON_pack_amount ("balance",
+ &balance),
+ GNUNET_JSON_pack_array_steal ("history",
+ history));
+ http_status = MHD_HTTP_OK;
+ }
+ add_response_headers (NULL,
+ resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_ETAG,
+ etagp));
+ {
+ MHD_RESULT ret;
+
+ ret = MHD_queue_response (rc->connection,
+ http_status,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+}
+
+
+/* end of taler-exchange-httpd_coins_get.c */
diff --git a/src/exchange/taler-exchange-httpd_coins_get.h b/src/exchange/taler-exchange-httpd_coins_get.h
new file mode 100644
index 000000000..90405b55d
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_coins_get.h
@@ -0,0 +1,53 @@
+/*
+ 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_coins_get.h
+ * @brief Handle GET /coins/$COIN_PUB requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_COINS_GET_H
+#define TALER_EXCHANGE_HTTPD_COINS_GET_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Shutdown reserves-get subsystem. Resumes all
+ * suspended long-polling clients and cleans up
+ * data structures.
+ */
+void
+TEH_reserves_get_cleanup (void);
+
+
+/**
+ * Handle a GET "/coins/$COIN_PUB/history" request. Parses the
+ * given "coins_pub" in @a args (which should contain the
+ * EdDSA public key of a reserve) and then respond with the
+ * transaction history of the coin.
+ *
+ * @param rc request context
+ * @param coin_pub public key of the coin
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_coins_get (struct TEH_RequestContext *rc,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_common_deposit.c b/src/exchange/taler-exchange-httpd_common_deposit.c
new file mode 100644
index 000000000..898e23dd9
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_common_deposit.c
@@ -0,0 +1,268 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_common_deposit.c
+ * @brief shared logic for handling deposited coins
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-exchange-httpd_common_deposit.h"
+#include "taler-exchange-httpd.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+enum GNUNET_GenericReturnValue
+TEH_common_purse_deposit_parse_coin (
+ struct MHD_Connection *connection,
+ struct TEH_PurseDepositedCoin *coin,
+ const json_t *jcoin)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("amount",
+ TEH_currency,
+ &coin->amount),
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+ &coin->cpi.denom_pub_hash),
+ TALER_JSON_spec_denom_sig ("ub_sig",
+ &coin->cpi.denom_sig),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("attest",
+ &coin->attest),
+ &coin->no_attest),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_age_commitment ("age_commitment",
+ &coin->age_commitment),
+ &coin->cpi.no_age_commitment),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &coin->coin_sig),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &coin->cpi.coin_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ memset (coin,
+ 0,
+ sizeof (*coin));
+ coin->cpi.no_age_commitment = true;
+ coin->no_attest = true;
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ jcoin,
+ spec);
+ if (GNUNET_OK != res)
+ return res;
+ }
+
+ /* check denomination exists and is valid */
+ {
+ struct TEH_DenominationKey *dk;
+ MHD_RESULT mret;
+
+ dk = TEH_keys_denomination_by_hash (&coin->cpi.denom_pub_hash,
+ connection,
+ &mret);
+ if (NULL == dk)
+ {
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES == mret) ? GNUNET_NO : GNUNET_SYSERR;
+ }
+ if (! coin->cpi.no_age_commitment)
+ {
+ coin->age_commitment.mask = dk->meta.age_mask;
+ TALER_age_commitment_hash (&coin->age_commitment,
+ &coin->cpi.h_age_commitment);
+ }
+ if (0 > TALER_amount_cmp (&dk->meta.value,
+ &coin->amount))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
+ NULL))
+ ? GNUNET_NO : GNUNET_SYSERR;
+ }
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
+ {
+ /* This denomination is past the expiration time for deposits */
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->cpi.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "PURSE CREATE"))
+ ? GNUNET_NO : GNUNET_SYSERR;
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ /* This denomination is not yet valid */
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->cpi.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "PURSE CREATE"))
+ ? GNUNET_NO : GNUNET_SYSERR;
+ }
+ if (dk->recoup_possible)
+ {
+ /* This denomination has been revoked */
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->cpi.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+ "PURSE CREATE"))
+ ? GNUNET_NO : GNUNET_SYSERR;
+ }
+ if (dk->denom_pub.bsign_pub_key->cipher !=
+ coin->cpi.denom_sig.unblinded_sig->cipher)
+ {
+ /* denomination cipher and denomination signature cipher not the same */
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+ NULL))
+ ? GNUNET_NO : GNUNET_SYSERR;
+ }
+
+ coin->deposit_fee = dk->meta.fees.deposit;
+ if (0 < TALER_amount_cmp (&coin->deposit_fee,
+ &coin->amount))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
+ NULL);
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&coin->amount_minus_fee,
+ &coin->amount,
+ &coin->deposit_fee));
+
+ /* check coin signature */
+ switch (dk->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
+ break;
+ default:
+ break;
+ }
+ if (GNUNET_YES !=
+ TALER_test_coin_valid (&coin->cpi,
+ &dk->denom_pub))
+ {
+ TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+ NULL))
+ ? GNUNET_NO : GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_common_deposit_check_purse_deposit (
+ struct MHD_Connection *connection,
+ const struct TEH_PurseDepositedCoin *coin,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ uint32_t min_age)
+{
+ if (GNUNET_OK !=
+ TALER_wallet_purse_deposit_verify (TEH_base_url,
+ purse_pub,
+ &coin->amount,
+ &coin->cpi.denom_pub_hash,
+ &coin->cpi.h_age_commitment,
+ &coin->cpi.coin_pub,
+ &coin->coin_sig))
+ {
+ TALER_LOG_WARNING (
+ "Invalid coin signature to deposit into purse\n");
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_SIGNATURE_INVALID,
+ TEH_base_url))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+
+ if (0 == min_age)
+ return GNUNET_OK; /* no need to apply age checks */
+
+ /* Check and verify the age restriction. */
+ if (coin->no_attest != coin->cpi.no_age_commitment)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_CONFLICTING_ATTEST_VS_AGE_COMMITMENT,
+ "mismatch of attest and age_commitment");
+ }
+
+ if (coin->cpi.no_age_commitment)
+ return GNUNET_OK; /* unrestricted coin */
+
+ /* age attestation must be valid */
+ if (GNUNET_OK !=
+ TALER_age_commitment_verify (&coin->age_commitment,
+ min_age,
+ &coin->attest))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_AGE_ATTESTATION_FAILURE,
+ "invalid attest for minimum age");
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Release data structures of @a coin. Note that
+ * @a coin itself is NOT freed.
+ *
+ * @param[in] coin information to release
+ */
+void
+TEH_common_purse_deposit_free_coin (struct TEH_PurseDepositedCoin *coin)
+{
+ TALER_denom_sig_free (&coin->cpi.denom_sig);
+ if (! coin->cpi.no_age_commitment)
+ GNUNET_free (coin->age_commitment.keys); /* Only the keys have been allocated */
+}
diff --git a/src/exchange/taler-exchange-httpd_common_deposit.h b/src/exchange/taler-exchange-httpd_common_deposit.h
new file mode 100644
index 000000000..10fd7e8bf
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_common_deposit.h
@@ -0,0 +1,130 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_common_deposit.h
+ * @brief shared logic for handling deposited coins
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_COMMON_DEPOSIT_H
+#define TALER_EXCHANGE_HTTPD_COMMON_DEPOSIT_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+
+
+/**
+ * Information about an individual coin being deposited.
+ */
+struct TEH_PurseDepositedCoin
+{
+ /**
+ * Public information about the coin.
+ */
+ struct TALER_CoinPublicInfo cpi;
+
+ /**
+ * Signature affirming spending the coin.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * Amount to be put into the purse from this coin.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Deposit fee applicable for this coin.
+ */
+ struct TALER_Amount deposit_fee;
+
+ /**
+ * Amount to be put into the purse from this coin.
+ */
+ struct TALER_Amount amount_minus_fee;
+
+ /**
+ * Age attestation provided, set if @e no_attest is false.
+ */
+ struct TALER_AgeAttestation attest;
+
+ /**
+ * Age commitment provided, set if @e cpi.no_age_commitment is false.
+ */
+ struct TALER_AgeCommitment age_commitment;
+
+ /**
+ * ID of the coin in known_coins.
+ */
+ uint64_t known_coin_id;
+
+ /**
+ * True if @e attest was not provided.
+ */
+ bool no_attest;
+
+};
+
+
+/**
+ * Parse a coin and check signature of the coin and the denomination
+ * signature over the coin.
+ *
+ * @param[in,out] connection our HTTP connection
+ * @param[out] coin coin to initialize
+ * @param jcoin coin to parse
+ * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
+ * #GNUNET_SYSERR on failure and no error could be returned
+ */
+enum GNUNET_GenericReturnValue
+TEH_common_purse_deposit_parse_coin (
+ struct MHD_Connection *connection,
+ struct TEH_PurseDepositedCoin *coin,
+ const json_t *jcoin);
+
+
+/**
+ * Check that the deposited @a coin is valid for @a purse_pub
+ * and has a valid age commitment for @a min_age.
+ *
+ * @param[in,out] connection our HTTP connection
+ * @param coin the coin to evaluate
+ * @param purse_pub public key of the purse the coin was deposited into
+ * @param min_age minimum age restriction expected for this purse
+ * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
+ * #GNUNET_SYSERR on failure and no error could be returned
+ */
+enum GNUNET_GenericReturnValue
+TEH_common_deposit_check_purse_deposit (
+ struct MHD_Connection *connection,
+ const struct TEH_PurseDepositedCoin *coin,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ uint32_t min_age);
+
+
+/**
+ * Release data structures of @a coin. Note that
+ * @a coin itself is NOT freed.
+ *
+ * @param[in] coin information to release
+ */
+void
+TEH_common_purse_deposit_free_coin (struct TEH_PurseDepositedCoin *coin);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_common_kyc.c b/src/exchange/taler-exchange-httpd_common_kyc.c
new file mode 100644
index 000000000..bcee5a0d2
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_common_kyc.c
@@ -0,0 +1,302 @@
+/*
+ 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_common_kyc.c
+ * @brief shared logic for finishing a KYC process
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-exchange-httpd.h"
+#include "taler-exchange-httpd_common_kyc.h"
+#include "taler_attributes.h"
+#include "taler_error_codes.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include <gnunet/gnunet_common.h>
+
+struct TEH_KycAmlTrigger
+{
+
+ /**
+ * Our logging scope.
+ */
+ struct GNUNET_AsyncScopeId scope;
+
+ /**
+ * account the operation is about
+ */
+ struct TALER_PaytoHashP account_id;
+
+ /**
+ * until when is the KYC data valid
+ */
+ struct GNUNET_TIME_Absolute expiration;
+
+ /**
+ * legitimization process the KYC data is about
+ */
+ uint64_t process_row;
+
+ /**
+ * name of the configuration section of the logic that was run
+ */
+ char *provider_section;
+
+ /**
+ * set to user ID at the provider, or NULL if not supported or unknown
+ */
+ char *provider_user_id;
+
+ /**
+ * provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
+ */
+ char *provider_legitimization_id;
+
+ /**
+ * function to call with the result
+ */
+ TEH_KycAmlTriggerCallback cb;
+
+ /**
+ * closure for @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * user attributes returned by the provider
+ */
+ json_t *attributes;
+
+ /**
+ * response to return to the HTTP client
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Handle to an external process that evaluates the
+ * need to run AML on the account.
+ */
+ struct TALER_JSON_ExternalConversion *kyc_aml;
+
+ /**
+ * HTTP status code of @e response
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Type of a callback that receives a JSON @a result.
+ *
+ * @param cls closure of type `struct TEH_KycAmlTrigger *`
+ * @param status_type how did the process die
+ * @param code termination status code from the process,
+ * non-zero if AML checks are required next
+ * @param result some JSON result, NULL if we failed to get an JSON output
+ */
+static void
+kyc_aml_finished (void *cls,
+ enum GNUNET_OS_ProcessStatusType status_type,
+ unsigned long code,
+ const json_t *result)
+{
+ struct TEH_KycAmlTrigger *kat = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ size_t eas;
+ void *ea;
+ const char *birthdate;
+ unsigned int birthday = 0;
+ struct GNUNET_ShortHashCode kyc_prox;
+ struct GNUNET_AsyncScopeSave old_scope;
+ unsigned int num_checks;
+ char **provided_checks;
+
+ kat->kyc_aml = NULL;
+ GNUNET_async_scope_enter (&kat->scope,
+ &old_scope);
+ TALER_CRYPTO_attributes_to_kyc_prox (kat->attributes,
+ &kyc_prox);
+ birthdate = json_string_value (json_object_get (kat->attributes,
+ TALER_ATTRIBUTE_BIRTHDATE));
+ if ( (TEH_age_restriction_enabled) &&
+ (NULL != birthdate) )
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = TALER_parse_coarse_date (birthdate,
+ &TEH_age_restriction_config.mask,
+ &birthday);
+
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse birthdate `%s' from KYC attributes\n",
+ birthdate);
+ if (NULL != kat->response)
+ MHD_destroy_response (kat->response);
+ kat->http_status = MHD_HTTP_BAD_REQUEST;
+ kat->response = TALER_MHD_make_error (
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ TALER_ATTRIBUTE_BIRTHDATE);
+ goto RETURN_RESULT;
+ }
+ }
+
+ TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key,
+ kat->attributes,
+ &ea,
+ &eas);
+ TALER_KYCLOGIC_lookup_checks (kat->provider_section,
+ &num_checks,
+ &provided_checks);
+ qs = TEH_plugin->insert_kyc_attributes (
+ TEH_plugin->cls,
+ kat->process_row,
+ &kat->account_id,
+ &kyc_prox,
+ kat->provider_section,
+ num_checks,
+ (const char **) provided_checks,
+ birthday,
+ GNUNET_TIME_timestamp_get (),
+ kat->provider_user_id,
+ kat->provider_legitimization_id,
+ kat->expiration,
+ eas,
+ ea,
+ 0 != code);
+ for (unsigned int i = 0; i<num_checks; i++)
+ GNUNET_free (provided_checks[i]);
+ GNUNET_free (provided_checks);
+ GNUNET_free (ea);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Stored encrypted KYC process #%llu attributes: %d\n",
+ (unsigned long long) kat->process_row,
+ qs);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ GNUNET_break (0);
+ if (NULL != kat->response)
+ MHD_destroy_response (kat->response);
+ kat->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ kat->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
+ "do_insert_kyc_attributes");
+ /* Continued below to return the response */
+ }
+RETURN_RESULT:
+ /* Finally, return result to main handler */
+ kat->cb (kat->cb_cls,
+ kat->http_status,
+ kat->response);
+ kat->response = NULL;
+ TEH_kyc_finished_cancel (kat);
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
+struct TEH_KycAmlTrigger *
+TEH_kyc_finished (const struct GNUNET_AsyncScopeId *scope,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *account_id,
+ const char *provider_section,
+ 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,
+ TEH_KycAmlTriggerCallback cb,
+ void *cb_cls)
+{
+ struct TEH_KycAmlTrigger *kat;
+
+ kat = GNUNET_new (struct TEH_KycAmlTrigger);
+ kat->scope = *scope;
+ kat->process_row = process_row;
+ kat->account_id = *account_id;
+ kat->provider_section
+ = GNUNET_strdup (provider_section);
+ if (NULL != provider_user_id)
+ kat->provider_user_id
+ = GNUNET_strdup (provider_user_id);
+ if (NULL != provider_legitimization_id)
+ kat->provider_legitimization_id
+ = GNUNET_strdup (provider_legitimization_id);
+ kat->expiration = expiration;
+ kat->attributes = json_incref ((json_t*) attributes);
+ kat->http_status = http_status;
+ kat->response = response;
+ kat->cb = cb;
+ kat->cb_cls = cb_cls;
+ kat->kyc_aml
+ = TALER_JSON_external_conversion_start (
+ attributes,
+ &kyc_aml_finished,
+ kat,
+ TEH_kyc_aml_trigger,
+ TEH_kyc_aml_trigger,
+ NULL);
+ if (NULL == kat->kyc_aml)
+ {
+ GNUNET_break (0);
+ TEH_kyc_finished_cancel (kat);
+ return NULL;
+ }
+ return kat;
+}
+
+
+void
+TEH_kyc_finished_cancel (struct TEH_KycAmlTrigger *kat)
+{
+ if (NULL != kat->kyc_aml)
+ {
+ TALER_JSON_external_conversion_stop (kat->kyc_aml);
+ kat->kyc_aml = NULL;
+ }
+ GNUNET_free (kat->provider_section);
+ GNUNET_free (kat->provider_user_id);
+ GNUNET_free (kat->provider_legitimization_id);
+ json_decref (kat->attributes);
+ if (NULL != kat->response)
+ {
+ MHD_destroy_response (kat->response);
+ kat->response = NULL;
+ }
+ GNUNET_free (kat);
+}
+
+
+bool
+TEH_kyc_failed (uint64_t process_row,
+ const struct TALER_PaytoHashP *account_id,
+ const char *provider_section,
+ const char *provider_user_id,
+ const char *provider_legitimization_id)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->insert_kyc_failure (
+ TEH_plugin->cls,
+ process_row,
+ account_id,
+ provider_section,
+ provider_user_id,
+ provider_legitimization_id);
+ GNUNET_break (qs >= 0);
+ return qs >= 0;
+}
diff --git a/src/exchange/taler-exchange-httpd_common_kyc.h b/src/exchange/taler-exchange-httpd_common_kyc.h
new file mode 100644
index 000000000..8198679c9
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_common_kyc.h
@@ -0,0 +1,117 @@
+/*
+ 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_common_kyc.h
+ * @brief shared logic for finishing a KYC process
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_COMMON_KYC_H
+#define TALER_EXCHANGE_HTTPD_COMMON_KYC_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Function called after the KYC-AML trigger is done.
+ *
+ * @param cls closure
+ * @param http_status final HTTP status to return
+ * @param[in] response final HTTP ro return
+ */
+typedef void
+(*TEH_KycAmlTriggerCallback) (
+ void *cls,
+ unsigned int http_status,
+ struct MHD_Response *response);
+
+
+/**
+ * Handle for an asynchronous operation to finish
+ * a KYC process after running the AML trigger.
+ */
+struct TEH_KycAmlTrigger;
+
+// FIXME: also pass async log context and set it!
+/**
+ * We have finished a KYC process and obtained new
+ * @a attributes for a given @a account_id.
+ * Check with the KYC-AML trigger to see if we need
+ * to initiate an AML process, and store the attributes
+ * in the database. Then call @a cb.
+ *
+ * @param scope the HTTP request logging scope
+ * @param process_row legitimization process the webhook was about
+ * @param account_id account the webhook was about
+ * @param provider_section name of the configuration section of the logic that was run
+ * @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
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel the operation
+ */
+struct TEH_KycAmlTrigger *
+TEH_kyc_finished (const struct GNUNET_AsyncScopeId *scope,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *account_id,
+ const char *provider_section,
+ 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,
+ TEH_KycAmlTriggerCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel KYC finish operation.
+ *
+ * @param[in] kat operation to abort
+ */
+void
+TEH_kyc_finished_cancel (struct TEH_KycAmlTrigger *kat);
+
+
+/**
+ * Update state of a legitmization process to 'finished'
+ * (and failed, no attributes were obtained).
+ *
+ * @param process_row legitimization process the webhook was about
+ * @param account_id account the webhook was about
+ * @param provider_section name of the configuration section of the logic that was run
+ * @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
+ * @return true on success, false if updating the database failed
+ */
+bool
+TEH_kyc_failed (uint64_t process_row,
+ const struct TALER_PaytoHashP *account_id,
+ const char *provider_section,
+ const char *provider_user_id,
+ const char *provider_legitimization_id);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_config.c b/src/exchange/taler-exchange-httpd_config.c
new file mode 100644
index 000000000..257dfa6ba
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_config.c
@@ -0,0 +1,92 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2024 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;
+ static struct GNUNET_TIME_Absolute a;
+
+ (void) args;
+ if ( (GNUNET_TIME_absolute_is_past (a)) &&
+ (NULL != resp) )
+ {
+ MHD_destroy_response (resp);
+ resp = NULL;
+ }
+ if (NULL == resp)
+ {
+ struct GNUNET_TIME_Timestamp km;
+ char dat[128];
+
+ a = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS);
+ /* Round up to next full day to ensure the expiration
+ time does not become a fingerprint! */
+ a = GNUNET_TIME_absolute_round_down (a,
+ GNUNET_TIME_UNIT_DAYS);
+ a = GNUNET_TIME_absolute_add (a,
+ GNUNET_TIME_UNIT_DAYS);
+ /* => /config response stays at most 48h in caches! */
+ km = GNUNET_TIME_absolute_to_timestamp (a);
+ TALER_MHD_get_date_string (km.abs_time,
+ dat);
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("supported_kyc_requirements",
+ TALER_KYCLOGIC_get_satisfiable ()),
+ GNUNET_JSON_pack_object_steal (
+ "currency_specification",
+ TALER_CONFIG_currency_specs_to_json (TEH_cspec)),
+ GNUNET_JSON_pack_string ("currency",
+ TEH_currency),
+ GNUNET_JSON_pack_string ("name",
+ "taler-exchange"),
+ GNUNET_JSON_pack_string ("implementation",
+ "urn:net:taler:specs:taler-exchange:c-reference")
+ ,
+ GNUNET_JSON_pack_string ("version",
+ EXCHANGE_PROTOCOL_VERSION));
+
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_EXPIRES,
+ dat));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "public,max-age=21600")); /* 6h */
+ }
+ return MHD_queue_response (rc->connection,
+ MHD_HTTP_OK,
+ resp);
+}
+
+
+/* end of taler-exchange-httpd_config.c */
diff --git a/src/exchange/taler-exchange-httpd_config.h b/src/exchange/taler-exchange-httpd_config.h
new file mode 100644
index 000000000..068f51d41
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_config.h
@@ -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 "19:2: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
diff --git a/src/exchange/taler-exchange-httpd_contract.c b/src/exchange/taler-exchange-httpd_contract.c
new file mode 100644
index 000000000..defb7816d
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_contract.c
@@ -0,0 +1,99 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_contract.c
+ * @brief Handle GET /contracts/$C_PUB requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_contract.h"
+#include "taler-exchange-httpd_mhd.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+MHD_RESULT
+TEH_handler_contracts_get (struct TEH_RequestContext *rc,
+ const char *const args[1])
+{
+ struct TALER_ContractDiffiePublicP contract_pub;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ void *econtract;
+ size_t econtract_size;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_PurseContractSignatureP econtract_sig;
+ MHD_RESULT res;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &contract_pub,
+ sizeof (contract_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_CONTRACTS_INVALID_CONTRACT_PUB,
+ args[0]);
+ }
+
+ qs = TEH_plugin->select_contract (TEH_plugin->cls,
+ &contract_pub,
+ &purse_pub,
+ &econtract_sig,
+ &econtract_size,
+ &econtract);
+ switch (qs)
+ {
+ 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,
+ "select_contract");
+ 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,
+ "select_contract");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_CONTRACTS_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* handled below */
+ }
+ res = TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ &purse_pub),
+ GNUNET_JSON_pack_data_auto ("econtract_sig",
+ &econtract_sig),
+ GNUNET_JSON_pack_data_varsize ("econtract",
+ econtract,
+ econtract_size));
+ GNUNET_free (econtract);
+ return res;
+}
+
+
+/* end of taler-exchange-httpd_contract.c */
diff --git a/src/exchange/taler-exchange-httpd_contract.h b/src/exchange/taler-exchange-httpd_contract.h
new file mode 100644
index 000000000..dac6b81e3
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_contract.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_contract.h
+ * @brief Handle /coins/$COIN_PUB/contract requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_CONTRACT_H
+#define TALER_EXCHANGE_HTTPD_CONTRACT_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a GET "/contracts/$C_PUB" request. Returns the
+ * encrypted contract.
+ *
+ * @param rc request context
+ * @param args array of additional options (length: 1, first is the contract_pub)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_contracts_get (struct TEH_RequestContext *rc,
+ const char *const args[1]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_csr.c b/src/exchange/taler-exchange-httpd_csr.c
new file mode 100644
index 000000000..e4fa4f5e4
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_csr.c
@@ -0,0 +1,351 @@
+/*
+ This file is part of TALER
+ 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 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_csr.c
+ * @brief Handle /csr requests
+ * @author Lucien Heuzeveldt
+ * @author Gian Demarmles
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_csr.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+MHD_RESULT
+TEH_handler_csr_melt (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ struct TALER_RefreshMasterSecretP rms;
+ unsigned int csr_requests_num;
+ const json_t *csr_requests;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("rms",
+ &rms),
+ GNUNET_JSON_spec_array_const ("nks",
+ &csr_requests),
+ GNUNET_JSON_spec_end ()
+ };
+ enum TALER_ErrorCode ec;
+ struct TEH_DenominationKey *dk;
+
+ (void) args;
+ /* parse input */
+ {
+ 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;
+ }
+ csr_requests_num = json_array_size (csr_requests);
+ if ( (TALER_MAX_FRESH_COINS <= csr_requests_num) ||
+ (0 == csr_requests_num) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE,
+ NULL);
+ }
+
+ {
+ struct GNUNET_CRYPTO_BlindingInputValues ewvs[csr_requests_num];
+ {
+ struct GNUNET_CRYPTO_CsSessionNonce nonces[csr_requests_num];
+ struct TALER_DenominationHashP denom_pub_hashes[csr_requests_num];
+ struct TEH_CsDeriveData cdds[csr_requests_num];
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[csr_requests_num];
+
+ for (unsigned int i = 0; i < csr_requests_num; i++)
+ {
+ uint32_t coin_off;
+ struct TALER_DenominationHashP *denom_pub_hash = &denom_pub_hashes[i];
+ struct GNUNET_JSON_Specification csr_spec[] = {
+ GNUNET_JSON_spec_uint32 ("coin_offset",
+ &coin_off),
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+ denom_pub_hash),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_array (rc->connection,
+ csr_requests,
+ csr_spec,
+ i,
+ -1);
+ if (GNUNET_OK != res)
+ {
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+ TALER_cs_refresh_nonce_derive (&rms,
+ coin_off,
+ &nonces[i]);
+ }
+
+ for (unsigned int i = 0; i < csr_requests_num; i++)
+ {
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce = &nonces[i];
+ const struct TALER_DenominationHashP *denom_pub_hash =
+ &denom_pub_hashes[i];
+
+ ewvs[i].cipher = GNUNET_CRYPTO_BSA_CS;
+ /* check denomination referenced by denom_pub_hash */
+ {
+ struct TEH_KeyStateHandle *ksh;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ }
+ dk = TEH_keys_denomination_by_hash_from_state (ksh,
+ denom_pub_hash,
+ NULL,
+ NULL);
+ if (NULL == dk)
+ {
+ return TEH_RESPONSE_reply_unknown_denom_pub_hash (
+ rc->connection,
+ &denom_pub_hash[i]);
+ }
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
+ {
+ /* This denomination is past the expiration time for withdraws/refreshes*/
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ rc->connection,
+ denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "csr-melt");
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ /* This denomination is not yet valid, no need to check
+ for idempotency! */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ rc->connection,
+ denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "csr-melt");
+ }
+ if (dk->recoup_possible)
+ {
+ /* This denomination has been revoked */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ rc->connection,
+ denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+ "csr-melt");
+ }
+ if (GNUNET_CRYPTO_BSA_CS !=
+ dk->denom_pub.bsign_pub_key->cipher)
+ {
+ /* denomination is valid but not for CS */
+ return TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
+ rc->connection,
+ denom_pub_hash);
+ }
+ }
+ cdds[i].h_denom_pub = denom_pub_hash;
+ cdds[i].nonce = nonce;
+ } /* for (i) */
+ ec = TEH_keys_denomination_cs_batch_r_pub (csr_requests_num,
+ cdds,
+ true,
+ r_pubs);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (rc->connection,
+ ec,
+ NULL);
+ }
+ for (unsigned int i = 0; i < csr_requests_num; i++)
+ ewvs[i].details.cs_values = r_pubs[i];
+ } /* end scope */
+
+ /* send response */
+ {
+ json_t *csr_response_ewvs;
+ json_t *csr_response;
+
+ csr_response_ewvs = json_array ();
+ for (unsigned int i = 0; i < csr_requests_num; i++)
+ {
+ json_t *csr_obj;
+ struct TALER_ExchangeWithdrawValues exw = {
+ .blinding_inputs = &ewvs[i]
+ };
+
+ csr_obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_exchange_withdraw_values ("ewv",
+ &exw));
+ GNUNET_assert (NULL != csr_obj);
+ GNUNET_assert (0 ==
+ json_array_append_new (csr_response_ewvs,
+ csr_obj));
+ }
+ csr_response = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("ewvs",
+ csr_response_ewvs));
+ GNUNET_assert (NULL != csr_response);
+ return TALER_MHD_reply_json_steal (rc->connection,
+ csr_response,
+ MHD_HTTP_OK);
+ }
+ }
+}
+
+
+MHD_RESULT
+TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ struct GNUNET_CRYPTO_CsSessionNonce nonce;
+ struct TALER_DenominationHashP denom_pub_hash;
+ struct GNUNET_CRYPTO_BlindingInputValues ewv = {
+ .cipher = GNUNET_CRYPTO_BSA_CS
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("nonce",
+ &nonce),
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+ &denom_pub_hash),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TEH_DenominationKey *dk;
+
+ (void) args;
+ {
+ 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;
+ }
+
+ {
+ struct TEH_KeyStateHandle *ksh;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ }
+ dk = TEH_keys_denomination_by_hash_from_state (ksh,
+ &denom_pub_hash,
+ NULL,
+ NULL);
+ if (NULL == dk)
+ {
+ return TEH_RESPONSE_reply_unknown_denom_pub_hash (
+ rc->connection,
+ &denom_pub_hash);
+ }
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
+ {
+ /* This denomination is past the expiration time for withdraws/refreshes*/
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ rc->connection,
+ &denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "csr-withdraw");
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ /* This denomination is not yet valid, no need to check
+ for idempotency! */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ rc->connection,
+ &denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "csr-withdraw");
+ }
+ if (dk->recoup_possible)
+ {
+ /* This denomination has been revoked */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ rc->connection,
+ &denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+ "csr-withdraw");
+ }
+ if (GNUNET_CRYPTO_BSA_CS !=
+ dk->denom_pub.bsign_pub_key->cipher)
+ {
+ /* denomination is valid but not for CS */
+ return TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
+ rc->connection,
+ &denom_pub_hash);
+ }
+ }
+
+ /* derive r_pub */
+ {
+ enum TALER_ErrorCode ec;
+ const struct TEH_CsDeriveData cdd = {
+ .h_denom_pub = &denom_pub_hash,
+ .nonce = &nonce
+ };
+
+ ec = TEH_keys_denomination_cs_r_pub (&cdd,
+ false,
+ &ewv.details.cs_values);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (rc->connection,
+ ec,
+ NULL);
+ }
+ }
+ {
+ struct TALER_ExchangeWithdrawValues exw = {
+ .blinding_inputs = &ewv
+ };
+
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_exchange_withdraw_values ("ewv",
+ &exw));
+ }
+}
+
+
+/* end of taler-exchange-httpd_csr.c */
diff --git a/src/exchange/taler-exchange-httpd_csr.h b/src/exchange/taler-exchange-httpd_csr.h
new file mode 100644
index 000000000..615255f94
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_csr.h
@@ -0,0 +1,56 @@
+/*
+ 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 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_csr.h
+ * @brief Handle /csr-* requests
+ * @author Lucien Heuzeveldt
+ * @author Gian Demarmles
+ */
+#ifndef TALER_EXCHANGE_HTTPD_CSR_H
+#define TALER_EXCHANGE_HTTPD_CSR_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/csr-melt" request.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args empty array
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_csr_melt (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[]);
+
+
+/**
+ * Handle a "/csr-withdraw" request.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args empty array
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c
index f98116226..6fec3fee4 100644
--- a/src/exchange/taler-exchange-httpd_db.c
+++ b/src/exchange/taler-exchange-httpd_db.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2017 Taler Systems SA
+ Copyright (C) 2014-2017, 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
@@ -19,104 +19,125 @@
* @author Christian Grothoff
*/
#include "platform.h"
+#include <gnunet/gnunet_db_lib.h>
#include <pthread.h>
#include <jansson.h>
#include <gnunet/gnunet_json_lib.h>
+#include "taler_error_codes.h"
+#include "taler_exchangedb_plugin.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
+#include "taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_db.h"
#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keystate.h"
-
-/**
- * How often should we retry a transaction before giving up
- * (for transactions resulting in serialization/dead locks only).
- *
- * The current value is likely too high for production. We might want to
- * benchmark good values once we have a good database setup. The code is
- * expected to work correctly with any positive value, albeit inefficiently if
- * we too aggressively force clients to retry the HTTP request merely because
- * we have database serialization issues.
- */
-#define MAX_TRANSACTION_COMMIT_RETRIES 100
-/**
- * Execute database transaction to ensure coin is known. Run 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 TEH_DB_KnowCoinContext`
- * @param connection MHD request context, must not be NULL
- * @param session database session and transaction to use
- * @param[out] mhd_ret set to MHD status on error, must not be NULL
- * @return transaction status
- */
enum GNUNET_DB_QueryStatus
-TEH_DB_know_coin_transaction (void *cls,
- struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
+TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
+ struct MHD_Connection *connection,
+ uint64_t *known_coin_id,
+ MHD_RESULT *mhd_ret)
{
- struct TEH_DB_KnowCoinContext *kcc = cls;
- enum GNUNET_DB_QueryStatus qs;
+ enum TALER_EXCHANGEDB_CoinKnownStatus cks;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_AgeCommitmentHash h_age_commitment = {{{0}}};
- GNUNET_assert (NULL != mhd_ret);
- qs = TEH_plugin->ensure_coin_known (TEH_plugin->cls,
- session,
- kcc->coin);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ /* make sure coin is 'known' in database */
+ cks = TEH_plugin->ensure_coin_known (TEH_plugin->cls,
+ coin,
+ known_coin_id,
+ &h_denom_pub,
+ &h_age_commitment);
+ switch (cks)
{
+ case TALER_EXCHANGEDB_CKS_ADDED:
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ case TALER_EXCHANGEDB_CKS_PRESENT:
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ case TALER_EXCHANGEDB_CKS_SOFT_FAIL:
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+ case TALER_EXCHANGEDB_CKS_HARD_FAIL:
*mhd_ret
= TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_DB_COIN_HISTORY_STORE_ERROR,
- "could not persist coin data");
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case TALER_EXCHANGEDB_CKS_DENOM_CONFLICT:
+ /* The exchange has a seen this coin before, but with a different denomination.
+ * Get the corresponding signature and sent it to the client as proof */
+ {
+ struct
+ {
+ struct TALER_DenominationPublicKey pub;
+ struct TALER_DenominationSignature sig;
+ } prev_denom = {0};
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ TEH_plugin->get_signature_for_known_coin (TEH_plugin->cls,
+ &coin->coin_pub,
+ &prev_denom.pub,
+ &prev_denom.sig))
+ {
+ /* There _should_ have been a result, because
+ * we ended here due to a conflict! */
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ *mhd_ret = TEH_RESPONSE_reply_coin_denomination_conflict (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
+ &coin->coin_pub,
+ &prev_denom.pub,
+ &prev_denom.sig);
+
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL:
+ case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL:
+ case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS:
+ *mhd_ret = TEH_RESPONSE_reply_coin_age_commitment_conflict (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH,
+ cks,
+ &h_denom_pub,
+ &coin->coin_pub,
+ &h_age_commitment);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- return qs;
+ GNUNET_assert (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
}
-/**
- * Run a database transaction for @a connection.
- * Starts a transaction and calls @a cb. Upon success,
- * attempts to commit the transaction. Upon soft failures,
- * retries @a cb a few times. Upon hard or persistent soft
- * errors, generates an error message for @a connection.
- *
- * @param connection MHD connection to run @a cb for, can be NULL
- * @param name name of the transaction (for debugging)
- * @param[out] mhd_ret set to MHD response code, if transaction failed;
- * NULL if we are not running with a @a connection and thus
- * must not queue MHD replies
- * @param cb callback implementing transaction logic
- * @param cb_cls closure for @a cb, must be read-only!
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
- */
-int
+enum GNUNET_GenericReturnValue
TEH_DB_run_transaction (struct MHD_Connection *connection,
const char *name,
- int *mhd_ret,
+ enum TEH_MetricTypeRequest mt,
+ MHD_RESULT *mhd_ret,
TEH_DB_TransactionCallback cb,
void *cb_cls)
{
- struct TALER_EXCHANGEDB_Session *session;
-
if (NULL != mhd_ret)
*mhd_ret = -1; /* set to invalid value, to help detect bugs */
- if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls)))
+ if (GNUNET_OK !=
+ TEH_plugin->preflight (TEH_plugin->cls))
{
GNUNET_break (0);
if (NULL != mhd_ret)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_DB_SETUP_FAILED,
- "could not establish database session");
+ TALER_EC_GENERIC_DB_SETUP_FAILED,
+ NULL);
return GNUNET_SYSERR;
}
+ GNUNET_assert (mt < TEH_MT_REQUEST_COUNT);
+ TEH_METRICS_num_requests[mt]++;
for (unsigned int retries = 0;
retries < MAX_TRANSACTION_COMMIT_RETRIES;
retries++)
@@ -125,52 +146,57 @@ TEH_DB_run_transaction (struct MHD_Connection *connection,
if (GNUNET_OK !=
TEH_plugin->start (TEH_plugin->cls,
- session,
name))
{
GNUNET_break (0);
if (NULL != mhd_ret)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_DB_START_FAILED,
- "could not begin transaction");
+ TALER_EC_GENERIC_DB_START_FAILED,
+ NULL);
return GNUNET_SYSERR;
}
qs = cb (cb_cls,
connection,
- session,
mhd_ret);
if (0 > qs)
- TEH_plugin->rollback (TEH_plugin->cls,
- session);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- return GNUNET_SYSERR;
- if (0 <= qs)
- qs = TEH_plugin->commit (TEH_plugin->cls,
- session);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
- if (NULL != mhd_ret)
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_DB_COMMIT_FAILED_HARD,
- "could not commit database transaction");
- return GNUNET_SYSERR;
+ TEH_plugin->rollback (TEH_plugin->cls);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ return GNUNET_SYSERR;
+ }
+ else
+ {
+ qs = TEH_plugin->commit (TEH_plugin->cls);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ TEH_plugin->rollback (TEH_plugin->cls);
+ if (NULL != mhd_ret)
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ if (0 > qs)
+ TEH_plugin->rollback (TEH_plugin->cls);
}
/* make sure callback did not violate invariants! */
GNUNET_assert ( (NULL == mhd_ret) ||
- (-1 == *mhd_ret) );
+ (-1 == (int) *mhd_ret) );
if (0 <= qs)
return GNUNET_OK;
+ TEH_METRICS_num_conflict[mt]++;
}
+ TEH_plugin->rollback (TEH_plugin->cls);
TALER_LOG_ERROR ("Transaction `%s' commit failed %u times\n",
name,
MAX_TRANSACTION_COMMIT_RETRIES);
if (NULL != mhd_ret)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_DB_COMMIT_FAILED_ON_RETRY,
- "repatedly failed to serialize database transaction");
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ NULL);
return GNUNET_SYSERR;
}
diff --git a/src/exchange/taler-exchange-httpd_db.h b/src/exchange/taler-exchange-httpd_db.h
index 380f00d3e..482bc5923 100644
--- a/src/exchange/taler-exchange-httpd_db.h
+++ b/src/exchange/taler-exchange-httpd_db.h
@@ -23,44 +23,37 @@
#include <microhttpd.h>
#include "taler_exchangedb_plugin.h"
+#include "taler-exchange-httpd_metrics.h"
+#include <gnunet/gnunet_mhd_compat.h>
/**
- * Type of closure for #TEH_DB_know_coin_transaction.
+ * How often should we retry a transaction before giving up
+ * (for transactions resulting in serialization/dead locks only).
+ *
+ * The current value is likely too high for production. We might want to
+ * benchmark good values once we have a good database setup. The code is
+ * expected to work correctly with any positive value, albeit inefficiently if
+ * we too aggressively force clients to retry the HTTP request merely because
+ * we have database serialization issues.
*/
-struct TEH_DB_KnowCoinContext
-{
- /**
- * The coin to make sure it is known.
- */
- const struct TALER_CoinPublicInfo *coin;
-
- /**
- * MHD connection to queue errors with.
- */
- struct MHD_Connection *connection;
-};
+#define MAX_TRANSACTION_COMMIT_RETRIES 100
/**
- * Execute database transaction to ensure coin is known. Run 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.
+ * Ensure coin is known in the database, and handle conflicts and errors.
*
- * @param cls a `struct TEH_DB_KnowCoinContext`
- * @param connection MHD request context, must not be NULL
- * @param session database session and transaction to use
- * @param[out] mhd_ret set to MHD status on error, must not be NULL
- * @return transaction status
+ * @param coin the coin to make known
+ * @param connection MHD request context
+ * @param[out] known_coin_id set to the unique ID for the coin in the DB
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status, negative on error (@a mhd_ret will be set in this case)
*/
enum GNUNET_DB_QueryStatus
-TEH_DB_know_coin_transaction (void *cls,
- struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret);
+TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
+ struct MHD_Connection *connection,
+ uint64_t *known_coin_id,
+ MHD_RESULT *mhd_ret);
/**
@@ -73,7 +66,6 @@ TEH_DB_know_coin_transaction (void *cls,
*
* @param cls closure
* @param connection MHD request which triggered the transaction
- * @param session database session to use
* @param[out] mhd_ret set to MHD response status for @a connection,
* if transaction failed (!)
* @return transaction status
@@ -81,8 +73,7 @@ TEH_DB_know_coin_transaction (void *cls,
typedef enum GNUNET_DB_QueryStatus
(*TEH_DB_TransactionCallback)(void *cls,
struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret);
+ MHD_RESULT *mhd_ret);
/**
@@ -94,17 +85,19 @@ typedef enum GNUNET_DB_QueryStatus
*
* @param connection MHD connection to run @a cb for, can be NULL
* @param name name of the transaction (for debugging)
- * @param[out] mhd_ret set to MHD response code, if transaction failed;
+ * @param mt type of the requests, for metric generation
+ * @param[out] mhd_ret set to MHD response code, if transaction failed (returned #GNUNET_SYSERR);
* NULL if we are not running with a @a connection and thus
* must not queue MHD replies
* @param cb callback implementing transaction logic
* @param cb_cls closure for @a cb, must be read-only!
* @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
*/
-int
+enum GNUNET_GenericReturnValue
TEH_DB_run_transaction (struct MHD_Connection *connection,
const char *name,
- int *mhd_ret,
+ enum TEH_MetricTypeRequest mt,
+ MHD_RESULT *mhd_ret,
TEH_DB_TransactionCallback cb,
void *cb_cls);
diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c
deleted file mode 100644
index 193101c08..000000000
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ /dev/null
@@ -1,564 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as 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_deposit.c
- * @brief Handle /deposit requests; parses the POST and JSON and
- * verifies the coin signature before handing things off
- * to the database.
- * @author Florian Dold
- * @author Benedikt Mueller
- * @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-exchange-httpd_deposit.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keystate.h"
-
-
-/**
- * Send confirmation of deposit success to client. This function
- * will create a signed message affirming the given information
- * and return it to the client. By this, the exchange affirms that
- * the coin had sufficient (residual) value for the specified
- * transaction and that it will execute the requested deposit
- * operation with the given wiring details.
- *
- * @param connection connection to the client
- * @param coin_pub public key of the coin
- * @param h_wire hash of wire details
- * @param h_contract_terms hash of contract details
- * @param timestamp client's timestamp
- * @param refund_deadline until when this deposit be refunded
- * @param merchant merchant public key
- * @param amount_without_fee fraction of coin value to deposit, without the fee
- * @return MHD result code
- */
-static int
-reply_deposit_success (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct GNUNET_HashCode *h_wire,
- const struct GNUNET_HashCode *h_contract_terms,
- struct GNUNET_TIME_Absolute timestamp,
- struct GNUNET_TIME_Absolute refund_deadline,
- const struct TALER_MerchantPublicKeyP *merchant,
- const struct TALER_Amount *amount_without_fee)
-{
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
- struct TALER_DepositConfirmationPS dc = {
- .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT),
- .purpose.size = htonl (sizeof (dc)),
- .h_contract_terms = *h_contract_terms,
- .h_wire = *h_wire,
- .timestamp = GNUNET_TIME_absolute_hton (timestamp),
- .refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline),
- .coin_pub = *coin_pub,
- .merchant = *merchant
- };
-
- TALER_amount_hton (&dc.amount_without_fee,
- amount_without_fee);
- if (GNUNET_OK !=
- TEH_KS_sign (&dc.purpose,
- &pub,
- &sig))
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_BAD_CONFIGURATION,
- "no keys");
- }
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:s, s:o, s:o}",
- "status", "DEPOSIT_OK",
- "sig", GNUNET_JSON_from_data_auto (&sig),
- "pub", GNUNET_JSON_from_data_auto (&pub));
-}
-
-
-/**
- * Closure for #deposit_transaction.
- */
-struct DepositContext
-{
- /**
- * Information about the deposit request.
- */
- const struct TALER_EXCHANGEDB_Deposit *deposit;
-
- /**
- * Value of the coin.
- */
- struct TALER_Amount value;
-
-};
-
-
-/**
- * Execute database transaction for /deposit. 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 DepositContext`
- * @param connection MHD request context
- * @param session database session and transaction to use
- * @param[out] mhd_ret set to MHD status on error
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-deposit_transaction (void *cls,
- struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
-{
- struct DepositContext *dc = cls;
- const struct TALER_EXCHANGEDB_Deposit *deposit = dc->deposit;
- struct TALER_Amount spent;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->have_deposit (TEH_plugin->cls,
- session,
- deposit,
- GNUNET_YES /* check refund deadline */);
- 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_DEPOSIT_HISTORY_DB_ERROR,
- "Could not check for existing identical deposit");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- return qs;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- struct TALER_Amount amount_without_fee;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "/deposit replay, accepting again!\n");
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&amount_without_fee,
- &deposit->amount_with_fee,
- &deposit->deposit_fee));
- *mhd_ret = reply_deposit_success (connection,
- &deposit->coin.coin_pub,
- &deposit->h_wire,
- &deposit->h_contract_terms,
- deposit->timestamp,
- deposit->refund_deadline,
- &deposit->merchant_pub,
- &amount_without_fee);
- /* Treat as 'hard' DB error as we want to rollback and
- never try again. */
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- /* Start with fee for THIS transaction */
- spent = deposit->amount_with_fee;
- /* add cost of all previous transactions; skip RECOUP as revoked
- denominations are not eligible for deposit, and if we are the old coin
- pub of a revoked coin (aka a zombie), then ONLY refresh is allowed. */
- {
- struct TALER_EXCHANGEDB_TransactionList *tl;
-
- qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
- session,
- &deposit->coin.coin_pub,
- GNUNET_NO,
- &tl);
- 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_DEPOSIT_HISTORY_DB_ERROR,
- "could not fetch coin transaction history");
- return qs;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGEDB_calculate_transaction_list_totals (tl,
- &spent, /* starting offset */
- &spent /* result */))
- {
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_DEPOSIT_HISTORY_DB_ERROR,
- "could not calculate historic coin amount total");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- /* Check that cost of all transactions (including the current one) is
- smaller (or equal) than the value of the coin. */
- if (0 < TALER_amount_cmp (&spent,
- &dc->value))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Deposited coin has insufficient funds left!\n");
- *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (connection,
- TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS,
- &deposit->coin.
- coin_pub,
- tl);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- }
- qs = TEH_plugin->insert_deposit (TEH_plugin->cls,
- session,
- deposit);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- TALER_LOG_WARNING ("Failed to store /deposit information in database\n");
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_DEPOSIT_STORE_DB_ERROR,
- "Could not persist /deposit data");
- }
- return qs;
-}
-
-
-/**
- * Check that @a ts is reasonably close to our own RTC.
- *
- * @param ts timestamp to check
- * @return #GNUNET_OK if @a ts is reasonable
- */
-static int
-check_timestamp_current (struct GNUNET_TIME_Absolute ts)
-{
- struct GNUNET_TIME_Relative r;
- struct GNUNET_TIME_Relative tolerance;
-
- /* Let's be VERY generous (after all, this is basically about
- which year the deposit counts for in terms of tax purposes) */
- tolerance = GNUNET_TIME_UNIT_MONTHS;
- r = GNUNET_TIME_absolute_get_duration (ts);
- if (r.rel_value_us > tolerance.rel_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Deposit timestamp too old: %llu vs %llu > %llu\n",
- (unsigned long long) ts.abs_value_us,
- (unsigned long long) GNUNET_TIME_absolute_get ().abs_value_us,
- (unsigned long long) tolerance.rel_value_us);
- return GNUNET_SYSERR;
- }
- r = GNUNET_TIME_absolute_get_remaining (ts);
- if (r.rel_value_us > tolerance.rel_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Deposit timestamp too new: %llu vs %llu < - %llu\n",
- (unsigned long long) ts.abs_value_us,
- (unsigned long long) GNUNET_TIME_absolute_get ().abs_value_us,
- (unsigned long long) tolerance.rel_value_us);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Handle a "/coins/$COIN_PUB/deposit" request. Parses the JSON, and, if
- * successful, passes the JSON data to #deposit_transaction() to
- * further check the details of the operation specified. If everything checks
- * out, this will ultimately lead to the "/deposit" being executed, or
- * rejected.
- *
- * @param connection the MHD connection to handle
- * @param coin_pub public key of the coin
- * @param root uploaded JSON data
- * @return MHD result code
- */
-int
-TEH_handler_deposit (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const json_t *root)
-{
- json_t *wire;
- struct DepositContext dc;
- struct TALER_EXCHANGEDB_Deposit deposit;
- struct GNUNET_HashCode my_h_wire;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("wire", &wire),
- TALER_JSON_spec_amount ("contribution", &deposit.amount_with_fee),
- GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
- &deposit.coin.denom_pub_hash),
- TALER_JSON_spec_denomination_signature ("ub_sig", &deposit.coin.denom_sig),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub", &deposit.merchant_pub),
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &deposit.h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("h_wire", &deposit.h_wire),
- GNUNET_JSON_spec_fixed_auto ("coin_sig", &deposit.csig),
- GNUNET_JSON_spec_absolute_time ("timestamp", &deposit.timestamp),
- GNUNET_JSON_spec_absolute_time ("refund_deadline",
- &deposit.refund_deadline),
- GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline",
- &deposit.wire_deadline),
- GNUNET_JSON_spec_end ()
- };
-
- memset (&deposit,
- 0,
- sizeof (deposit));
- deposit.coin.coin_pub = *coin_pub;
- {
- int res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
- return MHD_NO; /* hard failure */
- }
- if (GNUNET_NO == res)
- {
- GNUNET_break_op (0);
- return MHD_YES; /* failure */
- }
- }
- deposit.receiver_wire_account = wire;
- if (deposit.refund_deadline.abs_value_us > deposit.wire_deadline.abs_value_us)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
- "refund_deadline");
- }
-
- if (GNUNET_OK !=
- check_timestamp_current (deposit.timestamp))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_DEPOSIT_INVALID_TIMESTAMP,
- "timestamp");
- }
- if (GNUNET_OK !=
- TALER_JSON_merchant_wire_signature_hash (wire,
- &my_h_wire))
- {
- TALER_LOG_WARNING (
- "Failed to parse JSON wire format specification for /deposit request\n");
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_DEPOSIT_INVALID_WIRE_FORMAT_JSON,
- "wire");
- }
- if (0 != GNUNET_memcmp (&deposit.h_wire,
- &my_h_wire))
- {
- /* Client hashed wire details differently than we did, reject */
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_DEPOSIT_INVALID_WIRE_FORMAT_CONTRACT_HASH_CONFLICT,
- "h_wire");
- }
-
- /* check denomination exists and is valid */
- {
- struct TEH_KS_StateHandle *key_state;
- struct TALER_EXCHANGEDB_DenominationKey *dki;
- enum TALER_ErrorCode ec;
- unsigned int hc;
-
- key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
- if (NULL == key_state)
- {
- TALER_LOG_ERROR ("Lacking keys to operate\n");
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_BAD_CONFIGURATION,
- "no keys");
- }
- dki = TEH_KS_denomination_key_lookup_by_hash (key_state,
- &deposit.coin.denom_pub_hash,
- TEH_KS_DKU_DEPOSIT,
- &ec,
- &hc);
- if (NULL == dki)
- {
- TALER_LOG_DEBUG ("Unknown denomination key in /deposit request\n");
- TEH_KS_release (key_state);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- hc,
- ec,
- "Could not find denomination key used in deposit");
- }
- TALER_amount_ntoh (&deposit.deposit_fee,
- &dki->issue.properties.fee_deposit);
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&deposit.amount_with_fee,
- &deposit.deposit_fee) )
- {
- GNUNET_break_op (0);
- TEH_KS_release (key_state);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_DEPOSIT_CURRENCY_MISMATCH,
- "contribution");
- }
- /* check coin signature */
- if (GNUNET_YES !=
- TALER_test_coin_valid (&deposit.coin,
- &dki->denom_pub))
- {
- TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
- TEH_KS_release (key_state);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_UNAUTHORIZED,
- TALER_EC_DEPOSIT_DENOMINATION_SIGNATURE_INVALID,
- "ub_sig");
- }
- TALER_amount_ntoh (&deposit.deposit_fee,
- &dki->issue.properties.fee_deposit);
- TALER_amount_ntoh (&dc.value,
- &dki->issue.properties.value);
- TEH_KS_release (key_state);
- }
- if (0 < TALER_amount_cmp (&deposit.deposit_fee,
- &deposit.amount_with_fee))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
- "deposited amount smaller than depositing fee");
- }
-
- /* make sure coin is 'known' in database */
- {
- int mhd_ret;
- struct TEH_DB_KnowCoinContext kcc = {
- .coin = &deposit.coin,
- .connection = connection
- };
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "know coin for deposit",
- &mhd_ret,
- &TEH_DB_know_coin_transaction,
- &kcc))
- {
- GNUNET_JSON_parse_free (spec);
- return mhd_ret;
- }
- }
-
- /* check deposit signature */
- {
- struct TALER_DepositRequestPS dr = {
- .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT),
- .purpose.size = htonl (sizeof (dr)),
- .h_contract_terms = deposit.h_contract_terms,
- .h_wire = deposit.h_wire,
- .timestamp = GNUNET_TIME_absolute_hton (deposit.timestamp),
- .refund_deadline = GNUNET_TIME_absolute_hton (deposit.refund_deadline),
- .merchant = deposit.merchant_pub,
- .coin_pub = deposit.coin.coin_pub
- };
-
- TALER_amount_hton (&dr.amount_with_fee,
- &deposit.amount_with_fee);
- TALER_amount_hton (&dr.deposit_fee,
- &deposit.deposit_fee);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT,
- &dr.purpose,
- &deposit.csig.eddsa_signature,
- &deposit.coin.coin_pub.eddsa_pub))
- {
- TALER_LOG_WARNING ("Invalid signature on /deposit request\n");
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_UNAUTHORIZED,
- TALER_EC_DEPOSIT_COIN_SIGNATURE_INVALID,
- "coin_sig");
- }
- }
-
- /* execute transaction */
- dc.deposit = &deposit;
- {
- int mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "execute deposit",
- &mhd_ret,
- &deposit_transaction,
- &dc))
- {
- GNUNET_JSON_parse_free (spec);
- return mhd_ret;
- }
- }
-
- /* generate regular response */
- {
- struct TALER_Amount amount_without_fee;
- int res;
-
- GNUNET_assert (GNUNET_SYSERR !=
- TALER_amount_subtract (&amount_without_fee,
- &deposit.amount_with_fee,
- &deposit.deposit_fee));
- res = reply_deposit_success (connection,
- &deposit.coin.coin_pub,
- &deposit.h_wire,
- &deposit.h_contract_terms,
- deposit.timestamp,
- deposit.refund_deadline,
- &deposit.merchant_pub,
- &amount_without_fee);
- GNUNET_JSON_parse_free (spec);
- return res;
- }
-}
-
-
-/* end of taler-exchange-httpd_deposit.c */
diff --git a/src/exchange/taler-exchange-httpd_deposits_get.c b/src/exchange/taler-exchange-httpd_deposits_get.c
index 1c9c58d11..0850d19eb 100644
--- a/src/exchange/taler-exchange-httpd_deposits_get.c
+++ b/src/exchange/taler-exchange-httpd_deposits_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2017 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
@@ -23,93 +23,62 @@
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
+#include "taler_dbevents.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_signatures.h"
-#include "taler-exchange-httpd_keystate.h"
+#include "taler-exchange-httpd_keys.h"
#include "taler-exchange-httpd_deposits_get.h"
#include "taler-exchange-httpd_responses.h"
/**
- * A merchant asked for details about a deposit. Provide
- * them. Generates the 200 reply.
- *
- * @param connection connection to the client
- * @param h_contract_terms hash of the contract
- * @param h_wire hash of wire account details
- * @param coin_pub public key of the coin
- * @param coin_contribution how much did the coin we asked about
- * contribute to the total transfer value? (deposit value minus fee)
- * @param wtid raw wire transfer identifier
- * @param exec_time execution time of the wire transfer
- * @return MHD result code
- */
-static int
-reply_deposit_details (struct MHD_Connection *connection,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct GNUNET_HashCode *h_wire,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *coin_contribution,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- struct GNUNET_TIME_Absolute exec_time)
-{
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
- struct TALER_ConfirmWirePS cw = {
- .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE),
- .purpose.size = htonl (sizeof (cw)),
- .h_wire = *h_wire,
- .h_contract_terms = *h_contract_terms,
- .wtid = *wtid,
- .coin_pub = *coin_pub,
- .execution_time = GNUNET_TIME_absolute_hton (exec_time)
- };
-
- TALER_amount_hton (&cw.coin_contribution,
- coin_contribution);
- if (GNUNET_OK !=
- TEH_KS_sign (&cw.purpose,
- &pub,
- &sig))
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_BAD_CONFIGURATION,
- "no keys");
- }
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:o, s:o, s:o, s:o, s:o}",
- "wtid", GNUNET_JSON_from_data_auto (
- wtid),
- "execution_time",
- GNUNET_JSON_from_time_abs (exec_time),
- "coin_contribution",
- TALER_JSON_from_amount (
- coin_contribution),
- "exchange_sig",
- GNUNET_JSON_from_data_auto (&sig),
- "exchange_pub",
- GNUNET_JSON_from_data_auto (&pub));
-}
-
-
-/**
* Closure for #handle_wtid_data.
*/
struct DepositWtidContext
{
/**
- * Deposit details.
+ * Kept in a DLL.
+ */
+ struct DepositWtidContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct DepositWtidContext *prev;
+
+ /**
+ * Context for the request we are processing.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Subscription for the database event we are waiting for.
+ */
+ struct GNUNET_DB_EventHandler *eh;
+
+ /**
+ * Hash over the proposal data of the contract for which this deposit is made.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Hash over the wiring information of the merchant.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * The Merchant's public key. The deposit inquiry request is to be
+ * signed by the corresponding private key (using EdDSA).
*/
- const struct TALER_DepositTrackPS *tps;
+ struct TALER_MerchantPublicKeyP merchant;
/**
- * Public key of the merchant.
+ * The coin's public key. This is the value that must have been
+ * signed (blindly) by the Exchange.
*/
- const struct TALER_MerchantPublicKeyP *merchant_pub;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
/**
* Set by #handle_wtid data to the wire transfer ID.
@@ -117,6 +86,12 @@ struct DepositWtidContext
struct TALER_WireTransferIdentifierRawP wtid;
/**
+ * Signature by the merchant.
+ */
+ struct TALER_MerchantSignatureP merchant_sig;
+
+
+ /**
* Set by #handle_wtid data to the coin's contribution to the wire transfer.
*/
struct TALER_Amount coin_contribution;
@@ -129,7 +104,12 @@ struct DepositWtidContext
/**
* Set by #handle_wtid data to the wire transfer execution time.
*/
- struct GNUNET_TIME_Absolute execution_time;
+ struct GNUNET_TIME_Timestamp execution_time;
+
+ /**
+ * Timeout of the request, for long-polling.
+ */
+ struct GNUNET_TIME_Absolute timeout;
/**
* Set by #handle_wtid to the coin contribution to the transaction
@@ -138,57 +118,108 @@ struct DepositWtidContext
struct TALER_Amount coin_delta;
/**
+ * KYC status information for the receiving account.
+ */
+ 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).
* Set to #GNUNET_SYSERR if there was a serious error.
*/
- int pending;
+ enum GNUNET_GenericReturnValue pending;
+
+ /**
+ * #GNUNET_YES if we were suspended, #GNUNET_SYSERR
+ * if we were woken up due to shutdown.
+ */
+ enum GNUNET_GenericReturnValue suspended;
};
/**
- * Function called with the results of the lookup of the
- * wire transfer identifier information.
- *
- * @param cls our context for transmission, a `struct DepositWtidContext *`
- * @param wtid raw wire transfer identifier, NULL
- * if the transaction was not yet done
- * @param coin_contribution how much did the coin we asked about
- * contribute to the total transfer value? (deposit value including fee)
- * @param coin_fee how much did the exchange charge for the deposit fee
- * @param execution_time when was the transaction done, or
- * when we expect it to be done (if @a wtid was NULL);
- * #GNUNET_TIME_UNIT_FOREVER_ABS if the /deposit is unknown
- * to the exchange
+ * Head of DLL of suspended requests.
*/
-static void
-handle_wtid_data (void *cls,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *coin_contribution,
- const struct TALER_Amount *coin_fee,
- struct GNUNET_TIME_Absolute execution_time)
+static struct DepositWtidContext *dwc_head;
+
+/**
+ * Tail of DLL of suspended requests.
+ */
+static struct DepositWtidContext *dwc_tail;
+
+
+void
+TEH_deposits_get_cleanup ()
{
- struct DepositWtidContext *ctx = cls;
+ struct DepositWtidContext *n;
- if (NULL == wtid)
+ for (struct DepositWtidContext *ctx = dwc_head;
+ NULL != ctx;
+ ctx = n)
{
- ctx->pending = GNUNET_YES;
- ctx->execution_time = execution_time;
- return;
+ n = ctx->next;
+ GNUNET_assert (GNUNET_YES == ctx->suspended);
+ ctx->suspended = GNUNET_SYSERR;
+ MHD_resume_connection (ctx->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (dwc_head,
+ dwc_tail,
+ ctx);
}
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&ctx->coin_delta,
- coin_contribution,
- coin_fee))
+}
+
+
+/**
+ * A merchant asked for details about a deposit. Provide
+ * them. Generates the 200 reply.
+ *
+ * @param connection connection to the client
+ * @param ctx details to respond with
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_deposit_details (
+ struct MHD_Connection *connection,
+ const struct DepositWtidContext *ctx)
+{
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ enum TALER_ErrorCode ec;
+
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_confirm_wire_sign (
+ &TEH_keys_exchange_sign_,
+ &ctx->h_wire,
+ &ctx->h_contract_terms,
+ &ctx->wtid,
+ &ctx->coin_pub,
+ ctx->execution_time,
+ &ctx->coin_delta,
+ &pub,
+ &sig)))
{
GNUNET_break (0);
- ctx->pending = GNUNET_SYSERR;
- return;
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
}
- ctx->wtid = *wtid;
- ctx->execution_time = execution_time;
- ctx->coin_contribution = *coin_contribution;
- ctx->coin_fee = *coin_fee;
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &ctx->wtid),
+ GNUNET_JSON_pack_timestamp ("execution_time",
+ ctx->execution_time),
+ TALER_JSON_pack_amount ("coin_contribution",
+ &ctx->coin_delta),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub));
}
@@ -204,7 +235,6 @@ handle_wtid_data (void *cls,
*
* @param cls closure of type `struct DepositWtidContext *`
* @param connection MHD request which triggered the transaction
- * @param session database session to use
* @param[out] mhd_ret set to MHD response status for @a connection,
* if transaction failed (!)
* @return transaction status
@@ -212,20 +242,25 @@ handle_wtid_data (void *cls,
static enum GNUNET_DB_QueryStatus
deposits_get_transaction (void *cls,
struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
+ MHD_RESULT *mhd_ret)
{
struct DepositWtidContext *ctx = cls;
enum GNUNET_DB_QueryStatus qs;
+ bool pending;
+ struct TALER_Amount fee;
qs = TEH_plugin->lookup_transfer_by_deposit (TEH_plugin->cls,
- session,
- &ctx->tps->h_contract_terms,
- &ctx->tps->h_wire,
- &ctx->tps->coin_pub,
- ctx->merchant_pub,
- &handle_wtid_data,
- ctx);
+ &ctx->h_contract_terms,
+ &ctx->h_wire,
+ &ctx->coin_pub,
+ &ctx->merchant,
+ &pending,
+ &ctx->wtid,
+ &ctx->execution_time,
+ &ctx->coin_contribution,
+ &fee,
+ &ctx->kyc,
+ &ctx->aml_decision);
if (0 > qs)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
@@ -233,8 +268,8 @@ deposits_get_transaction (void *cls,
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_DEPOSITS_GET_DB_FETCH_FAILED,
- "failed to fetch transaction data");
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
}
return qs;
}
@@ -242,159 +277,248 @@ deposits_get_transaction (void *cls,
{
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
- TALER_EC_DEPOSITS_GET_NOT_FOUND,
- "transaction unknown");
+ TALER_EC_EXCHANGE_DEPOSITS_GET_NOT_FOUND,
+ NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
+
+ if (0 >
+ TALER_amount_subtract (&ctx->coin_delta,
+ &ctx->coin_contribution,
+ &fee))
+ {
+ GNUNET_break (0);
+ ctx->pending = GNUNET_SYSERR;
+ return qs;
+ }
+ ctx->pending = (pending) ? GNUNET_YES : GNUNET_NO;
return qs;
}
/**
- * Lookup and return the wire transfer identifier.
+ * Function called on events received from Postgres.
+ * Wakes up long pollers.
*
- * @param connection the MHD connection to handle
- * @param tps signed request to execute
- * @param merchant_pub public key from the merchant
- * @return MHD result code
+ * @param cls the `struct DepositWtidContext *`
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
*/
-static int
-handle_track_transaction_request (
- struct MHD_Connection *connection,
- const struct TALER_DepositTrackPS *tps,
- const struct TALER_MerchantPublicKeyP *merchant_pub)
+static void
+db_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
{
- int mhd_ret;
- struct DepositWtidContext ctx = {
- .pending = GNUNET_NO,
- .tps = tps,
- .merchant_pub = merchant_pub
- };
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "handle deposits GET",
- &mhd_ret,
- &deposits_get_transaction,
- &ctx))
- return mhd_ret;
- if (GNUNET_YES == ctx.pending)
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_ACCEPTED,
- "{s:o}",
- "execution_time",
- GNUNET_JSON_from_time_abs (
- ctx.execution_time));
- if (GNUNET_SYSERR == ctx.pending)
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_DEPOSITS_GET_DB_FEE_INCONSISTENT,
- "fees are inconsistent");
- return reply_deposit_details (connection,
- &tps->h_contract_terms,
- &tps->h_wire,
- &tps->coin_pub,
- &ctx.coin_delta,
- &ctx.wtid,
- ctx.execution_time);
+ struct DepositWtidContext *ctx = cls;
+ struct GNUNET_AsyncScopeSave old_scope;
+
+ (void) extra;
+ (void) extra_size;
+ if (GNUNET_YES != ctx->suspended)
+ return; /* might get multiple wake-up events */
+ GNUNET_CONTAINER_DLL_remove (dwc_head,
+ dwc_tail,
+ ctx);
+ GNUNET_async_scope_enter (&ctx->rc->async_scope_id,
+ &old_scope);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming request handling\n");
+ TEH_check_invariants ();
+ ctx->suspended = GNUNET_NO;
+ MHD_resume_connection (ctx->rc->connection);
+ TALER_MHD_daemon_trigger ();
+ TEH_check_invariants ();
+ GNUNET_async_scope_restore (&old_scope);
}
/**
- * Handle a "/deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB"
- * request.
+ * Lookup and return the wire transfer identifier.
*
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param args array of additional options (length: 4, contains:
- * h_wire, merchant_pub, h_contract_terms and coin_pub)
+ * @param ctx context of the signed request to execute
* @return MHD result code
- */
-int
-TEH_handler_deposits_get (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
- const char *const args[4])
+ */
+static MHD_RESULT
+handle_track_transaction_request (
+ struct DepositWtidContext *ctx)
{
- int res;
- struct TALER_MerchantSignatureP merchant_sig;
- struct TALER_DepositTrackPS tps = {
- .purpose.size = htonl (sizeof (tps)),
- .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION)
- };
-
- (void) rh;
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &tps.h_wire,
- sizeof (tps.h_wire)))
+ struct MHD_Connection *connection = ctx->rc->connection;
+
+ if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
+ (NULL == ctx->eh) )
{
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_DEPOSITS_INVALID_H_WIRE,
- "wire hash malformed");
+ struct TALER_CoinDepositEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED),
+ .merchant_pub = ctx->merchant
+ };
+
+ ctx->eh = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (ctx->timeout),
+ &rep.header,
+ &db_event_cb,
+ ctx);
+ GNUNET_break (NULL != ctx->eh);
}
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[1],
- strlen (args[1]),
- &tps.merchant,
- sizeof (tps.merchant)))
{
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_DEPOSITS_INVALID_MERCHANT_PUB,
- "merchant public key malformed");
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "handle deposits GET",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &deposits_get_transaction,
+ ctx))
+ return mhd_ret;
}
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[2],
- strlen (args[2]),
- &tps.h_contract_terms,
- sizeof (tps.h_contract_terms)))
- {
- GNUNET_break_op (0);
+ if (GNUNET_SYSERR == ctx->pending)
return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_DEPOSITS_INVALID_H_CONTRACT_TERMS,
- "contract terms hash malformed");
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "wire fees exceed aggregate in database");
+ if (GNUNET_YES == ctx->pending)
+ {
+ if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
+ (GNUNET_NO == ctx->suspended) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending request handling\n");
+ GNUNET_CONTAINER_DLL_insert (dwc_head,
+ dwc_tail,
+ ctx);
+ ctx->suspended = GNUNET_YES;
+ MHD_suspend_connection (connection);
+ return MHD_YES;
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_ACCEPTED,
+ GNUNET_JSON_pack_allow_null (
+ (0 == ctx->kyc.requirement_row)
+ ? GNUNET_JSON_pack_string ("requirement_row",
+ 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",
+ ctx->execution_time));
}
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[3],
- strlen (args[3]),
- &tps.coin_pub,
- sizeof (tps.coin_pub)))
+ return reply_deposit_details (connection,
+ ctx);
+}
+
+
+/**
+ * Function called to clean up a context.
+ *
+ * @param rc request context with data to clean up
+ */
+static void
+dwc_cleaner (struct TEH_RequestContext *rc)
+{
+ struct DepositWtidContext *ctx = rc->rh_ctx;
+
+ GNUNET_assert (GNUNET_NO == ctx->suspended);
+ if (NULL != ctx->eh)
{
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_DEPOSITS_INVALID_COIN_PUB,
- "coin public key malformed");
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ ctx->eh);
+ ctx->eh = NULL;
}
- res = TALER_MHD_parse_request_arg_data (connection,
- "merchant_sig",
- &merchant_sig,
- sizeof (merchant_sig));
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* internal error */
- if (GNUNET_NO == res)
- return MHD_YES; /* parse error */
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION,
- &tps.purpose,
- &merchant_sig.eddsa_sig,
- &tps.merchant.eddsa_pub))
+ GNUNET_free (ctx);
+}
+
+
+MHD_RESULT
+TEH_handler_deposits_get (struct TEH_RequestContext *rc,
+ const char *const args[4])
+{
+ struct DepositWtidContext *ctx = rc->rh_ctx;
+
+ if (NULL == ctx)
{
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID,
- "merchant_sig");
+ ctx = GNUNET_new (struct DepositWtidContext);
+ ctx->rc = rc;
+ rc->rh_ctx = ctx;
+ rc->rh_cleaner = &dwc_cleaner;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &ctx->h_wire,
+ sizeof (ctx->h_wire)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE,
+ args[0]);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[1],
+ strlen (args[1]),
+ &ctx->merchant,
+ sizeof (ctx->merchant)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB,
+ args[1]);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[2],
+ strlen (args[2]),
+ &ctx->h_contract_terms,
+ sizeof (ctx->h_contract_terms)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS,
+ args[2]);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[3],
+ strlen (args[3]),
+ &ctx->coin_pub,
+ sizeof (ctx->coin_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB,
+ args[3]);
+ }
+ TALER_MHD_parse_request_arg_auto_t (rc->connection,
+ "merchant_sig",
+ &ctx->merchant_sig);
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &ctx->timeout);
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ {
+ if (GNUNET_OK !=
+ TALER_merchant_deposit_verify (&ctx->merchant,
+ &ctx->coin_pub,
+ &ctx->h_contract_terms,
+ &ctx->h_wire,
+ &ctx->merchant_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID,
+ NULL);
+ }
+ }
}
- return handle_track_transaction_request (connection,
- &tps,
- &tps.merchant);
+ return handle_track_transaction_request (ctx);
}
diff --git a/src/exchange/taler-exchange-httpd_deposits_get.h b/src/exchange/taler-exchange-httpd_deposits_get.h
index 36d072aa8..c7b1698bb 100644
--- a/src/exchange/taler-exchange-httpd_deposits_get.h
+++ b/src/exchange/taler-exchange-httpd_deposits_get.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2017 Taler Systems SA
+ Copyright (C) 2014-2017, 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
@@ -27,18 +27,23 @@
/**
+ * Resume long pollers on GET /deposits.
+ */
+void
+TEH_deposits_get_cleanup (void);
+
+
+/**
* Handle a "/deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB"
* request.
*
- * @param rh context of the handler
- * @param connection the MHD connection to handle
+ * @param rc request context
* @param args array of additional options (length: 4, contains:
* h_wire, merchant_pub, h_contract_terms and coin_pub)
* @return MHD result code
*/
-int
-TEH_handler_deposits_get (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+MHD_RESULT
+TEH_handler_deposits_get (struct TEH_RequestContext *rc,
const char *const args[4]);
diff --git a/src/exchange/taler-exchange-httpd_extensions.c b/src/exchange/taler-exchange-httpd_extensions.c
new file mode 100644
index 000000000..d62a618ae
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_extensions.c
@@ -0,0 +1,442 @@
+/*
+ This file is part of TALER
+ 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
+ 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_extensions.c
+ * @brief Handle extensions (age-restriction, policy extensions)
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include <gnunet/gnunet_json_lib.h>
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_extensions.h"
+#include "taler_extensions_policy.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_extensions.h"
+#include <jansson.h>
+
+/**
+ * Handler listening for extensions updates by other exchange
+ * services.
+ */
+static struct GNUNET_DB_EventHandler *extensions_eh;
+
+/**
+ * Function called whenever another exchange process has updated
+ * the extensions data in the database.
+ *
+ * @param cls NULL
+ * @param extra type of the extension
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+extension_update_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ (void) cls;
+ uint32_t nbo_type;
+ enum TALER_Extension_Type type;
+ const struct TALER_Extension *extension;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received extensions update event\n");
+
+ if (sizeof(nbo_type) != extra_size)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Oops, incorrect size of extra for TALER_Extension_type\n");
+ return;
+ }
+
+ GNUNET_assert (NULL != extra);
+
+ nbo_type = *(uint32_t *) extra;
+ type = (enum TALER_Extension_Type) ntohl (nbo_type);
+
+ /* Get the corresponding extension */
+ extension = TALER_extensions_get_by_type (type);
+ if (NULL == extension)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Oops, unknown extension type: %d\n", type);
+ return;
+ }
+
+ // Get the manifest from the database as string
+ {
+ char *manifest_str = NULL;
+ enum GNUNET_DB_QueryStatus qs;
+ json_error_t err;
+ json_t *manifest_js;
+ enum GNUNET_GenericReturnValue ret;
+
+ qs = TEH_plugin->get_extension_manifest (TEH_plugin->cls,
+ extension->name,
+ &manifest_str);
+
+ if (qs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Couldn't get extension manifest\n");
+ GNUNET_break (0);
+ return;
+ }
+
+ // No config found -> disable extension
+ if (NULL == manifest_str)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No manifest found for extension %s, disabling it\n",
+ extension->name);
+ extension->disable ((struct TALER_Extension *) extension);
+ return;
+ }
+
+ // Parse the string as JSON
+ manifest_js = json_loads (manifest_str, JSON_DECODE_ANY, &err);
+ if (NULL == manifest_js)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse manifest for extension `%s' as JSON: %s (%s)\n",
+ extension->name,
+ err.text,
+ err.source);
+ GNUNET_break (0);
+ free (manifest_str);
+ return;
+ }
+
+ // Call the parser for the extension
+ ret = extension->load_config (
+ json_object_get (manifest_js, "config"),
+ (struct TALER_Extension *) extension);
+
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Couldn't parse configuration for extension %s from the manifest in the database: %s\n",
+ extension->name,
+ manifest_str);
+ GNUNET_break (0);
+ }
+
+ free (manifest_str);
+ json_decref (manifest_js);
+ }
+
+ /* Special case age restriction: Update global flag and mask */
+ if (TALER_Extension_AgeRestriction == type)
+ {
+ const struct TALER_AgeRestrictionConfig *conf =
+ TALER_extensions_get_age_restriction_config ();
+ TEH_age_restriction_enabled = false;
+ if (NULL != conf)
+ {
+ TEH_age_restriction_enabled = extension->enabled;
+ 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));
+ }
+ }
+
+ // Finally, call TEH_keys_update_states in order to refresh the cached
+ // values.
+ TEH_keys_update_states ();
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_extensions_init ()
+{
+ /* Set the event handler for updates */
+ struct GNUNET_DB_EventHeaderP ev = {
+ .size = htons (sizeof (ev)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED),
+ };
+
+ /* Load the shared libraries first */
+ if (GNUNET_OK !=
+ TALER_extensions_init (TEH_cfg))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "failed to load extensions");
+ return GNUNET_SYSERR;
+ }
+
+ /* Check for age restriction */
+ {
+ const struct TALER_AgeRestrictionConfig *arc;
+
+ if (NULL !=
+ (arc = TALER_extensions_get_age_restriction_config ()))
+ TEH_age_restriction_config = *arc;
+ }
+
+ extensions_eh = TEH_plugin->event_listen (TEH_plugin->cls,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &ev,
+ &extension_update_event_cb,
+ NULL);
+ if (NULL == extensions_eh)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* Trigger the initial load of configuration from the db */
+ for (const struct TALER_Extensions *it = TALER_extensions_get_head ();
+ NULL != it && NULL != it->extension;
+ it = it->next)
+ {
+ const struct TALER_Extension *ext = it->extension;
+ uint32_t typ = htonl (ext->type);
+ json_t *jmani;
+ char *manifest;
+
+ jmani = ext->manifest (ext);
+ manifest = json_dumps (jmani,
+ JSON_COMPACT);
+ json_decref (jmani);
+ TEH_plugin->set_extension_manifest (TEH_plugin->cls,
+ ext->name,
+ manifest);
+ free (manifest);
+ extension_update_event_cb (NULL,
+ &typ,
+ sizeof(typ));
+ }
+
+ return GNUNET_OK;
+}
+
+
+void
+TEH_extensions_done ()
+{
+ if (NULL != extensions_eh)
+ {
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ extensions_eh);
+ extensions_eh = NULL;
+ }
+}
+
+
+/*
+ * @brief Execute database transactions for /extensions/policy_* POST requests.
+ *
+ * @param cls a `struct TALER_PolicyFulfillmentOutcome`
+ * @param connection MHD request context
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+policy_fulfillment_transaction (
+ void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct TALER_PolicyFulfillmentTransactionData *fulfillment = cls;
+
+ /* FIXME[oec]: use connection and mhd_ret? */
+ (void) connection;
+ (void) mhd_ret;
+
+ return TEH_plugin->add_policy_fulfillment_proof (TEH_plugin->cls,
+ fulfillment);
+}
+
+
+/* FIXME[oec]-#7999: In this handler: do we transition correctly between states? */
+MHD_RESULT
+TEH_extensions_post_handler (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ const struct TALER_Extension *ext = NULL;
+ json_t *output;
+ struct TALER_PolicyDetails *policy_details = NULL;
+ size_t policy_details_count = 0;
+
+
+ if (NULL == args[0])
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
+ "/extensions/$EXTENSION");
+ }
+
+ ext = TALER_extensions_get_by_name (args[0]);
+ if (NULL == ext)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
+ "/extensions/$EXTENSION unknown");
+ }
+
+ if (NULL == ext->policy_post_handler)
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
+ "POST /extensions/$EXTENSION not supported");
+
+ /* Extract hash_codes and retrieve related policy_details from the DB */
+ {
+ enum GNUNET_GenericReturnValue ret;
+ enum GNUNET_DB_QueryStatus qs;
+ const char *error_msg;
+ struct GNUNET_HashCode *hcs;
+ size_t len;
+ json_t*val;
+ size_t idx;
+ json_t *jhash_codes = json_object_get (root,
+ "policy_hash_codes");
+ if (! json_is_array (jhash_codes))
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
+ "policy_hash_codes are missing");
+
+ len = json_array_size (jhash_codes);
+ hcs = GNUNET_new_array (len,
+ struct GNUNET_HashCode);
+ policy_details = GNUNET_new_array (len,
+ struct TALER_PolicyDetails);
+
+ json_array_foreach (jhash_codes, idx, val)
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL, &hcs[idx]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ ret = GNUNET_JSON_parse (val,
+ spec,
+ &error_msg,
+ NULL);
+ if (GNUNET_OK != ret)
+ break;
+
+ qs = TEH_plugin->get_policy_details (TEH_plugin->cls,
+ &hcs[idx],
+ &policy_details[idx]);
+ if (0 > qs)
+ {
+ GNUNET_free (hcs);
+ GNUNET_free (policy_details);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
+ "a policy_hash_code couldn't be found");
+ }
+
+ /* We proceed according to the state of fulfillment */
+ switch (policy_details[idx].fulfillment_state)
+ {
+ case TALER_PolicyFulfillmentReady:
+ break;
+ case TALER_PolicyFulfillmentInsufficient:
+ error_msg = "a policy is not yet fully funded";
+ ret = GNUNET_SYSERR;
+ break;
+ case TALER_PolicyFulfillmentTimeout:
+ error_msg = "a policy is has already timed out";
+ ret = GNUNET_SYSERR;
+ break;
+ case TALER_PolicyFulfillmentSuccess:
+ /* FIXME[oec]-#8001: Idempotency handling. */
+ GNUNET_break (0);
+ break;
+ case TALER_PolicyFulfillmentFailure:
+ /* FIXME[oec]-#7999: What to do in the failure case? */
+ GNUNET_break (0);
+ break;
+ default:
+ /* Unknown state */
+ GNUNET_assert (0);
+ }
+
+ if (GNUNET_OK != ret)
+ break;
+ }
+
+ GNUNET_free (hcs);
+
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_free (policy_details);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
+ error_msg);
+ }
+ }
+
+
+ if (GNUNET_OK !=
+ ext->policy_post_handler (root,
+ &args[1],
+ policy_details,
+ policy_details_count,
+ &output))
+ {
+ return TALER_MHD_reply_json_steal (
+ rc->connection,
+ output,
+ MHD_HTTP_BAD_REQUEST);
+ }
+
+ /* execute fulfillment transaction */
+ {
+ MHD_RESULT mhd_ret;
+ struct TALER_PolicyFulfillmentTransactionData fulfillment = {
+ .proof = root,
+ .timestamp = GNUNET_TIME_timestamp_get (),
+ .details = policy_details,
+ .details_count = policy_details_count
+ };
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (rc->connection,
+ "execute policy fulfillment",
+ TEH_MT_REQUEST_POLICY_FULFILLMENT,
+ &mhd_ret,
+ &policy_fulfillment_transaction,
+ &fulfillment))
+ {
+ json_decref (output);
+ return mhd_ret;
+ }
+ }
+
+ return TALER_MHD_reply_json_steal (rc->connection,
+ output,
+ MHD_HTTP_OK);
+}
+
+
+/* end of taler-exchange-httpd_extensions.c */
diff --git a/src/exchange/taler-exchange-httpd_extensions.h b/src/exchange/taler-exchange-httpd_extensions.h
new file mode 100644
index 000000000..e435f8f03
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_extensions.h
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_extensions.h
+ * @brief Manage extensions
+ * @author Özgür Kesim
+ */
+#ifndef TALER_EXCHANGE_HTTPD_EXTENSIONS_H
+#define TALER_EXCHANGE_HTTPD_EXTENSIONS_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Initialize extensions
+ *
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_extensions_init (void);
+
+/**
+ * Terminate the extension subsystem
+ */
+void
+TEH_extensions_done (void);
+
+
+/**
+ * Handle POST "/extensions/..." requests.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_extensions_post_handler (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c
new file mode 100644
index 000000000..0ec28e950
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_keys.c
@@ -0,0 +1,4354 @@
+/*
+ This file is part of TALER
+ 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 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_keys.c
+ * @brief management of our various keys
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#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"
+#include "taler_extensions.h"
+
+
+/**
+ * How many /keys request do we hold in suspension at
+ * most at any time?
+ */
+#define SKR_LIMIT 32
+
+
+/**
+ * When do we forcefully timeout a /keys request?
+ */
+#define KEYS_TIMEOUT GNUNET_TIME_UNIT_MINUTES
+
+
+/**
+ * Information about a denomination on offer by the denomination helper.
+ */
+struct HelperDenomination
+{
+
+ /**
+ * When will the helper start to use this key for signing?
+ */
+ struct GNUNET_TIME_Timestamp start_time;
+
+ /**
+ * For how long will the helper allow signing? 0 if
+ * the key was revoked or purged.
+ */
+ struct GNUNET_TIME_Relative validity_duration;
+
+ /**
+ * Hash of the full denomination key.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Signature over this key from the security module's key.
+ */
+ struct TALER_SecurityModuleSignatureP sm_sig;
+
+ /**
+ * The (full) public key.
+ */
+ struct TALER_DenominationPublicKey denom_pub;
+
+ /**
+ * Details depend on the @e denom_pub.cipher type.
+ */
+ union
+ {
+
+ /**
+ * Hash of the RSA key.
+ */
+ struct TALER_RsaPubHashP h_rsa;
+
+ /**
+ * Hash of the CS key.
+ */
+ struct TALER_CsPubHashP h_cs;
+
+ } h_details;
+
+ /**
+ * Name in configuration section for this denomination type.
+ */
+ char *section_name;
+
+
+};
+
+
+/**
+ * Signatures of an auditor over a denomination key of this exchange.
+ */
+struct TEH_AuditorSignature
+{
+ /**
+ * We store the signatures in a DLL.
+ */
+ struct TEH_AuditorSignature *prev;
+
+ /**
+ * We store the signatures in a DLL.
+ */
+ struct TEH_AuditorSignature *next;
+
+ /**
+ * A signature from the auditor.
+ */
+ struct TALER_AuditorSignatureP asig;
+
+ /**
+ * Public key of the auditor.
+ */
+ struct TALER_AuditorPublicKeyP apub;
+
+};
+
+
+/**
+ * Information about a signing key on offer by the esign helper.
+ */
+struct HelperSignkey
+{
+ /**
+ * When will the helper start to use this key for signing?
+ */
+ struct GNUNET_TIME_Timestamp start_time;
+
+ /**
+ * For how long will the helper allow signing? 0 if
+ * the key was revoked or purged.
+ */
+ struct GNUNET_TIME_Relative validity_duration;
+
+ /**
+ * The public key.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Signature over this key from the security module's key.
+ */
+ struct TALER_SecurityModuleSignatureP sm_sig;
+
+};
+
+
+/**
+ * State associated with the crypto helpers / security modules. NOT updated
+ * when the #key_generation is updated (instead constantly kept in sync
+ * whenever #TEH_keys_get_state() is called).
+ */
+struct HelperState
+{
+
+ /**
+ * Handle for the esign/EdDSA helper.
+ */
+ struct TALER_CRYPTO_ExchangeSignHelper *esh;
+
+ /**
+ * Handle for the denom/RSA helper.
+ */
+ struct TALER_CRYPTO_RsaDenominationHelper *rsadh;
+
+ /**
+ * Handle for the denom/CS helper.
+ */
+ struct TALER_CRYPTO_CsDenominationHelper *csdh;
+
+ /**
+ * Map from H(denom_pub) to `struct HelperDenomination` entries.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *denom_keys;
+
+ /**
+ * Map from H(rsa_pub) to `struct HelperDenomination` entries.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *rsa_keys;
+
+ /**
+ * Map from H(cs_pub) to `struct HelperDenomination` entries.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *cs_keys;
+
+ /**
+ * Map from `struct TALER_ExchangePublicKey` to `struct HelperSignkey`
+ * entries. Based on the fact that a `struct GNUNET_PeerIdentity` is also
+ * an EdDSA public key.
+ */
+ struct GNUNET_CONTAINER_MultiPeerMap *esign_keys;
+
+};
+
+
+/**
+ * Entry in (sorted) array with possible pre-build responses for /keys.
+ * We keep pre-build responses for the various (valid) cherry-picking
+ * values around.
+ */
+struct KeysResponseData
+{
+
+ /**
+ * Response to return if the client supports (deflate) compression.
+ */
+ struct MHD_Response *response_compressed;
+
+ /**
+ * Response to return if the client does not support compression.
+ */
+ struct MHD_Response *response_uncompressed;
+
+ /**
+ * ETag for these responses.
+ */
+ char *etag;
+
+ /**
+ * Cherry-picking timestamp the client must have set for this
+ * response to be valid. 0 if this is the "full" response.
+ * The client's request must include this date or a higher one
+ * for this response to be applicable.
+ */
+ struct GNUNET_TIME_Timestamp cherry_pick_date;
+
+};
+
+
+/**
+ * @brief All information about an exchange online signing key (which is used to
+ * sign messages from the exchange).
+ */
+struct SigningKey
+{
+
+ /**
+ * The exchange's (online signing) public key.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Meta data about the signing key, such as validity periods.
+ */
+ struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+
+ /**
+ * The long-term offline master key's signature for this signing key.
+ * Signs over @e exchange_pub and @e meta.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+};
+
+struct TEH_KeyStateHandle
+{
+
+ /**
+ * Mapping from denomination keys to denomination key issue struct.
+ * Used to lookup the key by hash.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *denomkey_map;
+
+ /**
+ * Map from `struct TALER_ExchangePublicKey` to `struct SigningKey`
+ * entries. Based on the fact that a `struct GNUNET_PeerIdentity` is also
+ * an EdDSA public key.
+ */
+ struct GNUNET_CONTAINER_MultiPeerMap *signkey_map;
+
+ /**
+ * Head of DLL of our global fees.
+ */
+ struct TEH_GlobalFee *gf_head;
+
+ /**
+ * Tail of DLL of our global fees.
+ */
+ struct TEH_GlobalFee *gf_tail;
+
+ /**
+ * json array with the auditors of this exchange. Contains exactly
+ * the information needed for the "auditors" field of the /keys response.
+ */
+ json_t *auditors;
+
+ /**
+ * json array with the global fees of this exchange. Contains exactly
+ * the information needed for the "global_fees" field of the /keys response.
+ */
+ json_t *global_fees;
+
+ /**
+ * Sorted array of responses to /keys (MUST be sorted by cherry-picking date) of
+ * length @e krd_array_length;
+ */
+ struct KeysResponseData *krd_array;
+
+ /**
+ * Length of the @e krd_array.
+ */
+ unsigned int krd_array_length;
+
+ /**
+ * Information we track for thecrypto helpers. Preserved
+ * when the @e key_generation changes, thus kept separate.
+ */
+ struct HelperState *helpers;
+
+ /**
+ * Cached reply for a GET /management/keys request. Used so we do not
+ * re-create the reply every time.
+ */
+ json_t *management_keys_reply;
+
+ /**
+ * For which (global) key_generation was this data structure created?
+ * Used to check when we are outdated and need to be re-generated.
+ */
+ uint64_t key_generation;
+
+ /**
+ * When did we initiate the key reloading?
+ */
+ struct GNUNET_TIME_Timestamp reload_time;
+
+ /**
+ * What is the period at which we rotate keys
+ * (signing or denomination keys)?
+ */
+ struct GNUNET_TIME_Relative rekey_frequency;
+
+ /**
+ * When does our online signing key expire and we
+ * thus need to re-generate this response?
+ */
+ struct GNUNET_TIME_Timestamp signature_expires;
+
+ /**
+ * True if #finish_keys_response() was not yet run and this key state
+ * is only suitable for the /management/keys API.
+ */
+ bool management_only;
+
+};
+
+
+/**
+ * Entry of /keys requests that are currently suspended because we are
+ * waiting for /keys to become ready.
+ */
+struct SuspendedKeysRequests
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct SuspendedKeysRequests *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct SuspendedKeysRequests *prev;
+
+ /**
+ * The suspended connection.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * When does this request timeout?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+};
+
+
+/**
+ * Information we track about wire fees.
+ */
+struct WireFeeSet
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WireFeeSet *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WireFeeSet *prev;
+
+ /**
+ * Actual fees.
+ */
+ struct TALER_WireFeeSet fees;
+
+ /**
+ * Start date of fee validity (inclusive).
+ */
+ struct GNUNET_TIME_Timestamp start_date;
+
+ /**
+ * End date of fee validity (exclusive).
+ */
+ struct GNUNET_TIME_Timestamp end_date;
+
+ /**
+ * Wire method the fees apply to.
+ */
+ char *method;
+};
+
+
+/**
+ * State we keep per thread to cache the /wire response.
+ */
+struct WireStateHandle
+{
+
+ /**
+ * JSON reply for /wire response.
+ */
+ json_t *json_reply;
+
+ /**
+ * ETag for this response (if any).
+ */
+ char *etag;
+
+ /**
+ * head of DLL of wire fees.
+ */
+ struct WireFeeSet *wfs_head;
+
+ /**
+ * Tail of DLL of wire fees.
+ */
+ struct WireFeeSet *wfs_tail;
+
+ /**
+ * Earliest timestamp of all the wire methods when we have no more fees.
+ */
+ struct GNUNET_TIME_Absolute cache_expiration;
+
+ /**
+ * @e cache_expiration time, formatted.
+ */
+ char dat[128];
+
+ /**
+ * For which (global) wire_generation was this data structure created?
+ * Used to check when we are outdated and need to be re-generated.
+ */
+ uint64_t wire_generation;
+
+ /**
+ * Is the wire data ready?
+ */
+ bool ready;
+
+};
+
+
+/**
+ * Stores the latest generation of our wire response.
+ */
+static struct WireStateHandle *wire_state;
+
+/**
+ * Handler listening for wire updates by other exchange
+ * services.
+ */
+static struct GNUNET_DB_EventHandler *wire_eh;
+
+/**
+ * Counter incremented whenever we have a reason to re-build the #wire_state
+ * because something external changed.
+ */
+static uint64_t wire_generation;
+
+
+/**
+ * Stores the latest generation of our key state.
+ */
+static struct TEH_KeyStateHandle *key_state;
+
+/**
+ * Counter incremented whenever we have a reason to re-build the keys because
+ * something external changed. See #TEH_keys_get_state() and
+ * #TEH_keys_update_states() for uses of this variable.
+ */
+static uint64_t key_generation;
+
+/**
+ * Handler listening for wire updates by other exchange
+ * services.
+ */
+static struct GNUNET_DB_EventHandler *keys_eh;
+
+/**
+ * Head of DLL of suspended /keys requests.
+ */
+static struct SuspendedKeysRequests *skr_head;
+
+/**
+ * Tail of DLL of suspended /keys requests.
+ */
+static struct SuspendedKeysRequests *skr_tail;
+
+/**
+ * Number of entries in the @e skr_head DLL.
+ */
+static unsigned int skr_size;
+
+/**
+ * Handle to a connection that should be force-resumed
+ * with a hard error due to @a skr_size hitting
+ * #SKR_LIMIT.
+ */
+static struct MHD_Connection *skr_connection;
+
+/**
+ * Task to force timeouts on /keys requests.
+ */
+static struct GNUNET_SCHEDULER_Task *keys_tt;
+
+/**
+ * For how long should a signing key be legally retained?
+ * Configuration value.
+ */
+static struct GNUNET_TIME_Relative signkey_legal_duration;
+
+/**
+ * What type of asset are we dealing with here?
+ */
+static char *asset_type;
+
+/**
+ * RSA security module public key, all zero if not known.
+ */
+static struct TALER_SecurityModulePublicKeyP denom_rsa_sm_pub;
+
+/**
+ * CS security module public key, all zero if not known.
+ */
+static struct TALER_SecurityModulePublicKeyP denom_cs_sm_pub;
+
+/**
+ * EdDSA security module public key, all zero if not known.
+ */
+static struct TALER_SecurityModulePublicKeyP esign_sm_pub;
+
+/**
+ * Are we shutting down?
+ */
+static bool terminating;
+
+
+/**
+ * Free memory associated with @a wsh
+ *
+ * @param[in] wsh wire state to destroy
+ */
+static void
+destroy_wire_state (struct WireStateHandle *wsh)
+{
+ struct WireFeeSet *wfs;
+
+ while (NULL != (wfs = wsh->wfs_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (wsh->wfs_head,
+ wsh->wfs_tail,
+ wfs);
+ GNUNET_free (wfs->method);
+ GNUNET_free (wfs);
+ }
+ json_decref (wsh->json_reply);
+ GNUNET_free (wsh->etag);
+ GNUNET_free (wsh);
+}
+
+
+/**
+ * Function called whenever another exchange process has updated
+ * the wire data in the database.
+ *
+ * @param cls NULL
+ * @param extra unused
+ * @param extra_size number of bytes in @a extra unused
+ */
+static void
+wire_update_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ (void) cls;
+ (void) extra;
+ (void) extra_size;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received /wire update event\n");
+ TEH_check_invariants ();
+ wire_generation++;
+ key_generation++;
+ TEH_resume_keys_requests (false);
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_wire_init ()
+{
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
+ };
+
+ wire_eh = TEH_plugin->event_listen (TEH_plugin->cls,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &es,
+ &wire_update_event_cb,
+ NULL);
+ if (NULL == wire_eh)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+void
+TEH_wire_done ()
+{
+ if (NULL != wire_state)
+ {
+ destroy_wire_state (wire_state);
+ wire_state = NULL;
+ }
+ if (NULL != wire_eh)
+ {
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ wire_eh);
+ wire_eh = NULL;
+ }
+}
+
+
+/**
+ * Add information about a wire account to @a cls.
+ *
+ * @param cls a `json_t *` object to expand with wire account details
+ * @param payto_uri the exchange bank account URI to add
+ * @param conversion_url URL of a conversion service, NULL if there is no conversion
+ * @param debit_restrictions JSON array with debit restrictions on the account
+ * @param credit_restrictions JSON array with credit restrictions on the account
+ * @param master_sig master key signature affirming that this is a bank
+ * account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS)
+ * @param bank_label label the wallet should use to display the account, can be NULL
+ * @param priority priority for ordering bank account labels
+ */
+static void
+add_wire_account (void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority)
+{
+ json_t *a = cls;
+
+ if (GNUNET_OK !=
+ TALER_exchange_wire_signature_check (
+ payto_uri,
+ conversion_url,
+ debit_restrictions,
+ credit_restrictions,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has wire account with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
+ if (0 !=
+ json_array_append_new (
+ a,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ payto_uri),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("conversion_url",
+ conversion_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("bank_label",
+ bank_label)),
+ GNUNET_JSON_pack_int64 ("priority",
+ priority),
+ GNUNET_JSON_pack_array_incref ("debit_restrictions",
+ (json_t *) debit_restrictions),
+ GNUNET_JSON_pack_array_incref ("credit_restrictions",
+ (json_t *) credit_restrictions),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig))))
+ {
+ GNUNET_break (0); /* out of memory!? */
+ return;
+ }
+}
+
+
+/**
+ * Closure for #add_wire_fee().
+ */
+struct AddContext
+{
+ /**
+ * Wire method the fees are for.
+ */
+ char *wire_method;
+
+ /**
+ * Wire state we are building.
+ */
+ struct WireStateHandle *wsh;
+
+ /**
+ * Array to append the fee to.
+ */
+ json_t *a;
+
+ /**
+ * Set to the maximum end-date seen.
+ */
+ struct GNUNET_TIME_Absolute max_seen;
+};
+
+
+/**
+ * Add information about a wire account to @a cls.
+ *
+ * @param cls a `struct AddContext`
+ * @param fees the wire fees we charge
+ * @param start_date from when are these fees valid (start date)
+ * @param end_date until when are these fees valid (end date, exclusive)
+ * @param master_sig master key signature affirming that this is the correct
+ * fee (of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES)
+ */
+static void
+add_wire_fee (void *cls,
+ const struct TALER_WireFeeSet *fees,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct AddContext *ac = cls;
+ struct WireFeeSet *wfs;
+
+ if (GNUNET_OK !=
+ TALER_exchange_offline_wire_fee_verify (
+ ac->wire_method,
+ start_date,
+ end_date,
+ fees,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has wire fee with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
+ ac->max_seen = GNUNET_TIME_absolute_max (ac->max_seen,
+ end_date.abs_time);
+ wfs = GNUNET_new (struct WireFeeSet);
+ wfs->start_date = start_date;
+ wfs->end_date = end_date;
+ wfs->fees = *fees;
+ wfs->method = GNUNET_strdup (ac->wire_method);
+ GNUNET_CONTAINER_DLL_insert (ac->wsh->wfs_head,
+ ac->wsh->wfs_tail,
+ wfs);
+ if (0 !=
+ json_array_append_new (
+ ac->a,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("wire_fee",
+ &fees->wire),
+ TALER_JSON_pack_amount ("closing_fee",
+ &fees->closing),
+ GNUNET_JSON_pack_timestamp ("start_date",
+ start_date),
+ GNUNET_JSON_pack_timestamp ("end_date",
+ end_date),
+ GNUNET_JSON_pack_data_auto ("sig",
+ master_sig))))
+ {
+ GNUNET_break (0); /* out of memory!? */
+ return;
+ }
+}
+
+
+/**
+ * Create the /wire response from our database state.
+ *
+ * @return NULL on error
+ */
+static struct WireStateHandle *
+build_wire_state (void)
+{
+ json_t *wire_accounts_array;
+ json_t *wire_fee_object;
+ uint64_t wg = wire_generation; /* must be obtained FIRST */
+ enum GNUNET_DB_QueryStatus qs;
+ struct WireStateHandle *wsh;
+ json_t *wads;
+
+ wsh = GNUNET_new (struct WireStateHandle);
+ wsh->wire_generation = wg;
+ wire_accounts_array = json_array ();
+ GNUNET_assert (NULL != wire_accounts_array);
+ qs = TEH_plugin->get_wire_accounts (TEH_plugin->cls,
+ &add_wire_account,
+ wire_accounts_array);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ json_decref (wire_accounts_array);
+ wsh->ready = false;
+ return wsh;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Build /wire data with %u accounts\n",
+ (unsigned int) json_array_size (wire_accounts_array));
+ wire_fee_object = json_object ();
+ GNUNET_assert (NULL != wire_fee_object);
+ wsh->cache_expiration = GNUNET_TIME_UNIT_FOREVER_ABS;
+ {
+ json_t *account;
+ size_t index;
+
+ json_array_foreach (wire_accounts_array,
+ index,
+ account)
+ {
+ char *wire_method;
+ const char *payto_uri = json_string_value (json_object_get (account,
+ "payto_uri"));
+
+ GNUNET_assert (NULL != payto_uri);
+ wire_method = TALER_payto_get_method (payto_uri);
+ if (NULL == wire_method)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No wire method in `%s'\n",
+ payto_uri);
+ wsh->ready = false;
+ json_decref (wire_accounts_array);
+ json_decref (wire_fee_object);
+ return wsh;
+ }
+ if (NULL == json_object_get (wire_fee_object,
+ wire_method))
+ {
+ struct AddContext ac = {
+ .wire_method = wire_method,
+ .wsh = wsh,
+ .a = json_array ()
+ };
+
+ GNUNET_assert (NULL != ac.a);
+ qs = TEH_plugin->get_wire_fees (TEH_plugin->cls,
+ wire_method,
+ &add_wire_fee,
+ &ac);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ json_decref (ac.a);
+ json_decref (wire_fee_object);
+ json_decref (wire_accounts_array);
+ GNUNET_free (wire_method);
+ wsh->ready = false;
+ return wsh;
+ }
+ if (0 != json_array_size (ac.a))
+ {
+ wsh->cache_expiration
+ = GNUNET_TIME_absolute_min (ac.max_seen,
+ wsh->cache_expiration);
+ GNUNET_assert (0 ==
+ json_object_set_new (wire_fee_object,
+ wire_method,
+ ac.a));
+ }
+ else
+ {
+ json_decref (ac.a);
+ }
+ }
+ GNUNET_free (wire_method);
+ }
+ }
+
+ wads = json_array (); /* #7271 */
+ GNUNET_assert (NULL != wads);
+ wsh->json_reply = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("accounts",
+ wire_accounts_array),
+ GNUNET_JSON_pack_array_steal ("wads",
+ wads),
+ GNUNET_JSON_pack_object_steal ("fees",
+ wire_fee_object));
+ wsh->ready = true;
+ return wsh;
+}
+
+
+void
+TEH_wire_update_state (void)
+{
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED),
+ };
+
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &es,
+ NULL,
+ 0);
+ wire_generation++;
+ key_generation++;
+}
+
+
+/**
+ * Return the current key state for this thread. Possibly
+ * re-builds the key state if we have reason to believe
+ * that something changed.
+ *
+ * @return NULL on error
+ */
+struct WireStateHandle *
+get_wire_state (void)
+{
+ struct WireStateHandle *old_wsh;
+
+ old_wsh = wire_state;
+ if ( (NULL == old_wsh) ||
+ (old_wsh->wire_generation < wire_generation) )
+ {
+ struct WireStateHandle *wsh;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Rebuilding /wire, generation upgrade from %llu to %llu\n",
+ (unsigned long long) (NULL == old_wsh) ? 0LL :
+ old_wsh->wire_generation,
+ (unsigned long long) wire_generation);
+ TEH_check_invariants ();
+ wsh = build_wire_state ();
+ wire_state = wsh;
+ if (NULL != old_wsh)
+ destroy_wire_state (old_wsh);
+ TEH_check_invariants ();
+ return wsh;
+ }
+ return old_wsh;
+}
+
+
+const struct TALER_WireFeeSet *
+TEH_wire_fees_by_time (
+ struct GNUNET_TIME_Timestamp ts,
+ const char *method)
+{
+ struct WireStateHandle *wsh = get_wire_state ();
+
+ for (struct WireFeeSet *wfs = wsh->wfs_head;
+ NULL != wfs;
+ wfs = wfs->next)
+ {
+ if (0 != strcmp (method,
+ wfs->method))
+ continue;
+ if ( (GNUNET_TIME_timestamp_cmp (wfs->start_date,
+ >,
+ ts)) ||
+ (GNUNET_TIME_timestamp_cmp (ts,
+ >=,
+ wfs->end_date)) )
+ continue;
+ return &wfs->fees;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No wire fees for method `%s' at %s configured\n",
+ method,
+ GNUNET_TIME_timestamp2s (ts));
+ return NULL;
+}
+
+
+/**
+ * Function called to forcefully resume suspended keys requests.
+ *
+ * @param cls unused, NULL
+ */
+static void
+keys_timeout_cb (void *cls)
+{
+ struct SuspendedKeysRequests *skr;
+
+ (void) cls;
+ keys_tt = NULL;
+ while (NULL != (skr = skr_head))
+ {
+ if (GNUNET_TIME_absolute_is_future (skr->timeout))
+ break;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming /keys request due to timeout\n");
+ GNUNET_CONTAINER_DLL_remove (skr_head,
+ skr_tail,
+ skr);
+ MHD_resume_connection (skr->connection);
+ TALER_MHD_daemon_trigger ();
+ GNUNET_free (skr);
+ }
+ if (NULL == skr)
+ return;
+ keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout,
+ &keys_timeout_cb,
+ NULL);
+}
+
+
+/**
+ * Suspend /keys request while we (hopefully) are waiting to be
+ * provisioned with key material.
+ *
+ * @param[in] connection to suspend
+ */
+static MHD_RESULT
+suspend_request (struct MHD_Connection *connection)
+{
+ struct SuspendedKeysRequests *skr;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending /keys request until key material changes\n");
+ if (terminating)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ "Exchange terminating");
+ }
+ skr = GNUNET_new (struct SuspendedKeysRequests);
+ skr->connection = connection;
+ MHD_suspend_connection (connection);
+ GNUNET_CONTAINER_DLL_insert (skr_head,
+ skr_tail,
+ skr);
+ skr->timeout = GNUNET_TIME_relative_to_absolute (KEYS_TIMEOUT);
+ if (NULL == keys_tt)
+ {
+ keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout,
+ &keys_timeout_cb,
+ NULL);
+ }
+ skr_size++;
+ if (skr_size > SKR_LIMIT)
+ {
+ skr = skr_tail;
+ GNUNET_CONTAINER_DLL_remove (skr_head,
+ skr_tail,
+ skr);
+ skr_size--;
+ skr_connection = skr->connection;
+ MHD_resume_connection (skr->connection);
+ TALER_MHD_daemon_trigger ();
+ GNUNET_free (skr);
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Called on each denomination key. Checks that the key still works.
+ *
+ * @param cls NULL
+ * @param hc denomination hash (unused)
+ * @param value a `struct TEH_DenominationKey`
+ * @return #GNUNET_OK
+ */
+static enum GNUNET_GenericReturnValue
+check_dk (void *cls,
+ const struct GNUNET_HashCode *hc,
+ void *value)
+{
+ struct TEH_DenominationKey *dk = value;
+
+ (void) cls;
+ (void) hc;
+ switch (dk->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ GNUNET_assert (GNUNET_CRYPTO_rsa_public_key_check (
+ dk->denom_pub.bsign_pub_key->details.rsa_public_key));
+ return GNUNET_OK;
+ case GNUNET_CRYPTO_BSA_CS:
+ /* nothing to do for GNUNET_CRYPTO_BSA_CS */
+ return GNUNET_OK;
+ }
+ GNUNET_assert (0);
+ return GNUNET_SYSERR;
+}
+
+
+void
+TEH_check_invariants ()
+{
+ struct TEH_KeyStateHandle *ksh;
+
+ if (0 == TEH_check_invariants_flag)
+ return;
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ return;
+ GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+ &check_dk,
+ NULL);
+}
+
+
+void
+TEH_resume_keys_requests (bool do_shutdown)
+{
+ struct SuspendedKeysRequests *skr;
+
+ if (do_shutdown)
+ terminating = true;
+ while (NULL != (skr = skr_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (skr_head,
+ skr_tail,
+ skr);
+ skr_size--;
+ MHD_resume_connection (skr->connection);
+ TALER_MHD_daemon_trigger ();
+ GNUNET_free (skr);
+ }
+}
+
+
+/**
+ * Clear memory for responses to "/keys" in @a ksh.
+ *
+ * @param[in,out] ksh key state to update
+ */
+static void
+clear_response_cache (struct TEH_KeyStateHandle *ksh)
+{
+ for (unsigned int i = 0; i<ksh->krd_array_length; i++)
+ {
+ struct KeysResponseData *krd = &ksh->krd_array[i];
+
+ MHD_destroy_response (krd->response_compressed);
+ MHD_destroy_response (krd->response_uncompressed);
+ GNUNET_free (krd->etag);
+ }
+ GNUNET_array_grow (ksh->krd_array,
+ ksh->krd_array_length,
+ 0);
+}
+
+
+/**
+ * Check that the given RSA security module's public key is the one
+ * we have pinned. If it does not match, we die hard.
+ *
+ * @param sm_pub RSA security module public key to check
+ */
+static void
+check_denom_rsa_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
+{
+ if (0 !=
+ GNUNET_memcmp (sm_pub,
+ &denom_rsa_sm_pub))
+ {
+ if (! GNUNET_is_zero (&denom_rsa_sm_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Our RSA security module changed its key. This must not happen.\n");
+ GNUNET_assert (0);
+ }
+ denom_rsa_sm_pub = *sm_pub; /* TOFU ;-) */
+ }
+}
+
+
+/**
+ * Check that the given CS security module's public key is the one
+ * we have pinned. If it does not match, we die hard.
+ *
+ * @param sm_pub RSA security module public key to check
+ */
+static void
+check_denom_cs_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
+{
+ if (0 !=
+ GNUNET_memcmp (sm_pub,
+ &denom_cs_sm_pub))
+ {
+ if (! GNUNET_is_zero (&denom_cs_sm_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Our CS security module changed its key. This must not happen.\n");
+ GNUNET_assert (0);
+ }
+ denom_cs_sm_pub = *sm_pub; /* TOFU ;-) */
+ }
+}
+
+
+/**
+ * Check that the given EdDSA security module's public key is the one
+ * we have pinned. If it does not match, we die hard.
+ *
+ * @param sm_pub EdDSA security module public key to check
+ */
+static void
+check_esign_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
+{
+ if (0 !=
+ GNUNET_memcmp (sm_pub,
+ &esign_sm_pub))
+ {
+ if (! GNUNET_is_zero (&esign_sm_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Our EdDSA security module changed its key. This must not happen.\n");
+ GNUNET_assert (0);
+ }
+ esign_sm_pub = *sm_pub; /* TOFU ;-) */
+ }
+}
+
+
+/**
+ * Helper function for #destroy_key_helpers to free all entries
+ * in the `denom_keys` map.
+ *
+ * @param cls the `struct HelperDenomination`
+ * @param h_denom_pub hash of the denomination public key
+ * @param value the `struct HelperDenomination` to release
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+free_denom_cb (void *cls,
+ const struct GNUNET_HashCode *h_denom_pub,
+ void *value)
+{
+ struct HelperDenomination *hd = value;
+
+ (void) cls;
+ (void) h_denom_pub;
+ TALER_denom_pub_free (&hd->denom_pub);
+ GNUNET_free (hd->section_name);
+ GNUNET_free (hd);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Helper function for #destroy_key_helpers to free all entries
+ * in the `esign_keys` map.
+ *
+ * @param cls the `struct HelperSignkey`
+ * @param pid unused, matches the exchange public key
+ * @param value the `struct HelperSignkey` to release
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+free_esign_cb (void *cls,
+ const struct GNUNET_PeerIdentity *pid,
+ void *value)
+{
+ struct HelperSignkey *hsk = value;
+
+ (void) cls;
+ (void) pid;
+ GNUNET_free (hsk);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Destroy helper state. Does NOT call free() on @a hs, as that
+ * state is not separately allocated! Dual to #setup_key_helpers().
+ *
+ * @param[in] hs helper state to free, but NOT the @a hs pointer itself!
+ */
+static void
+destroy_key_helpers (struct HelperState *hs)
+{
+ GNUNET_CONTAINER_multihashmap_iterate (hs->denom_keys,
+ &free_denom_cb,
+ hs);
+ GNUNET_CONTAINER_multihashmap_destroy (hs->rsa_keys);
+ hs->rsa_keys = NULL;
+ GNUNET_CONTAINER_multihashmap_destroy (hs->cs_keys);
+ hs->cs_keys = NULL;
+ GNUNET_CONTAINER_multihashmap_destroy (hs->denom_keys);
+ hs->denom_keys = NULL;
+ GNUNET_CONTAINER_multipeermap_iterate (hs->esign_keys,
+ &free_esign_cb,
+ hs);
+ GNUNET_CONTAINER_multipeermap_destroy (hs->esign_keys);
+ hs->esign_keys = NULL;
+ if (NULL != hs->rsadh)
+ {
+ TALER_CRYPTO_helper_rsa_disconnect (hs->rsadh);
+ hs->rsadh = NULL;
+ }
+ if (NULL != hs->csdh)
+ {
+ TALER_CRYPTO_helper_cs_disconnect (hs->csdh);
+ hs->csdh = NULL;
+ }
+ if (NULL != hs->esh)
+ {
+ TALER_CRYPTO_helper_esign_disconnect (hs->esh);
+ hs->esh = NULL;
+ }
+}
+
+
+/**
+ * Looks up the AGE_RESTRICTED setting for a denomination in the config and
+ * returns the age restriction (mask) accordingly.
+ *
+ * @param section_name Section in the configuration for the particular
+ * denomination.
+ */
+static struct TALER_AgeMask
+load_age_mask (const char *section_name)
+{
+ static const struct TALER_AgeMask null_mask = {0};
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value (
+ TEH_cfg,
+ section_name,
+ "AGE_RESTRICTED")))
+ return null_mask;
+
+ if (GNUNET_SYSERR ==
+ (ret = GNUNET_CONFIGURATION_get_value_yesno (TEH_cfg,
+ section_name,
+ "AGE_RESTRICTED")))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section_name,
+ "AGE_RESTRICTED",
+ "Value must be YES or NO\n");
+ return null_mask;
+ }
+
+ if (GNUNET_OK == ret)
+ {
+ if (! TEH_age_restriction_enabled)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "age restriction set in section %s, yet, age restriction is not enabled\n",
+ section_name);
+ return TEH_age_restriction_config.mask;
+ }
+
+
+ return null_mask;
+}
+
+
+/**
+ * Function called with information about available keys for signing. Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure with the `struct HelperState *`
+ * @param section_name name of the denomination type in the configuration;
+ * NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ * zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ * zero if the key has been revoked or purged
+ * @param h_rsa hash of the @a denom_pub that is available (or was purged)
+ * @param bs_pub the public key itself, NULL if the key was revoked or purged
+ * @param sm_pub public key of the security module, NULL if the key was revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was revoked or purged
+ * The signature was already verified against @a sm_pub.
+ */
+static void
+helper_rsa_cb (
+ void *cls,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Relative validity_duration,
+ const struct TALER_RsaPubHashP *h_rsa,
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
+ const struct TALER_SecurityModulePublicKeyP *sm_pub,
+ const struct TALER_SecurityModuleSignatureP *sm_sig)
+{
+ struct HelperState *hs = cls;
+ struct HelperDenomination *hd;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "RSA helper announces key %s for denomination type %s with validity %s\n",
+ GNUNET_h2s (&h_rsa->hash),
+ section_name,
+ GNUNET_STRINGS_relative_time_to_string (validity_duration,
+ GNUNET_NO));
+ key_generation++;
+ TEH_resume_keys_requests (false);
+ hd = GNUNET_CONTAINER_multihashmap_get (hs->rsa_keys,
+ &h_rsa->hash);
+ if (NULL != hd)
+ {
+ /* should be just an update (revocation!), so update existing entry */
+ hd->validity_duration = validity_duration;
+ return;
+ }
+ GNUNET_assert (NULL != sm_pub);
+ check_denom_rsa_sm_pub (sm_pub);
+ hd = GNUNET_new (struct HelperDenomination);
+ hd->start_time = start_time;
+ hd->validity_duration = validity_duration;
+ hd->h_details.h_rsa = *h_rsa;
+ hd->sm_sig = *sm_sig;
+ GNUNET_assert (GNUNET_CRYPTO_BSA_RSA == bs_pub->cipher);
+ hd->denom_pub.bsign_pub_key =
+ GNUNET_CRYPTO_bsign_pub_incref (bs_pub);
+ /* load the age mask for the denomination, if applicable */
+ hd->denom_pub.age_mask = load_age_mask (section_name);
+ TALER_denom_pub_hash (&hd->denom_pub,
+ &hd->h_denom_pub);
+ hd->section_name = GNUNET_strdup (section_name);
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (
+ hs->denom_keys,
+ &hd->h_denom_pub.hash,
+ hd,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (
+ hs->rsa_keys,
+ &hd->h_details.h_rsa.hash,
+ hd,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Function called with information about available CS keys for signing. Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure with the `struct HelperState *`
+ * @param section_name name of the denomination type in the configuration;
+ * NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ * zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ * zero if the key has been revoked or purged
+ * @param h_cs hash of the @a denom_pub that is available (or was purged)
+ * @param bs_pub the public key itself, NULL if the key was revoked or purged
+ * @param sm_pub public key of the security module, NULL if the key was revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was revoked or purged
+ * The signature was already verified against @a sm_pub.
+ */
+static void
+helper_cs_cb (
+ void *cls,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Relative validity_duration,
+ const struct TALER_CsPubHashP *h_cs,
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
+ const struct TALER_SecurityModulePublicKeyP *sm_pub,
+ const struct TALER_SecurityModuleSignatureP *sm_sig)
+{
+ struct HelperState *hs = cls;
+ struct HelperDenomination *hd;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "CS helper announces key %s for denomination type %s with validity %s\n",
+ GNUNET_h2s (&h_cs->hash),
+ section_name,
+ GNUNET_STRINGS_relative_time_to_string (validity_duration,
+ GNUNET_NO));
+ key_generation++;
+ TEH_resume_keys_requests (false);
+ hd = GNUNET_CONTAINER_multihashmap_get (hs->cs_keys,
+ &h_cs->hash);
+ if (NULL != hd)
+ {
+ /* should be just an update (revocation!), so update existing entry */
+ hd->validity_duration = validity_duration;
+ return;
+ }
+ GNUNET_assert (NULL != sm_pub);
+ check_denom_cs_sm_pub (sm_pub);
+ hd = GNUNET_new (struct HelperDenomination);
+ hd->start_time = start_time;
+ hd->validity_duration = validity_duration;
+ hd->h_details.h_cs = *h_cs;
+ hd->sm_sig = *sm_sig;
+ GNUNET_assert (GNUNET_CRYPTO_BSA_CS == bs_pub->cipher);
+ hd->denom_pub.bsign_pub_key
+ = GNUNET_CRYPTO_bsign_pub_incref (bs_pub);
+ /* load the age mask for the denomination, if applicable */
+ hd->denom_pub.age_mask = load_age_mask (section_name);
+ TALER_denom_pub_hash (&hd->denom_pub,
+ &hd->h_denom_pub);
+ hd->section_name = GNUNET_strdup (section_name);
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (
+ hs->denom_keys,
+ &hd->h_denom_pub.hash,
+ hd,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (
+ hs->cs_keys,
+ &hd->h_details.h_cs.hash,
+ hd,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Function called with information about available keys for signing. Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure with the `struct HelperState *`
+ * @param start_time when does the key become available for signing;
+ * zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ * zero if the key has been revoked or purged
+ * @param exchange_pub the public key itself, NULL if the key was revoked or purged
+ * @param sm_pub public key of the security module, NULL if the key was revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was revoked or purged
+ * The signature was already verified against @a sm_pub.
+ */
+static void
+helper_esign_cb (
+ void *cls,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Relative validity_duration,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_SecurityModulePublicKeyP *sm_pub,
+ const struct TALER_SecurityModuleSignatureP *sm_sig)
+{
+ struct HelperState *hs = cls;
+ struct HelperSignkey *hsk;
+ struct GNUNET_PeerIdentity pid;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "EdDSA helper announces signing key %s with validity %s\n",
+ TALER_B2S (exchange_pub),
+ GNUNET_STRINGS_relative_time_to_string (validity_duration,
+ GNUNET_NO));
+ key_generation++;
+ TEH_resume_keys_requests (false);
+ pid.public_key = exchange_pub->eddsa_pub;
+ hsk = GNUNET_CONTAINER_multipeermap_get (hs->esign_keys,
+ &pid);
+ if (NULL != hsk)
+ {
+ /* should be just an update (revocation!), so update existing entry */
+ hsk->validity_duration = validity_duration;
+ return;
+ }
+ GNUNET_assert (NULL != sm_pub);
+ check_esign_sm_pub (sm_pub);
+ hsk = GNUNET_new (struct HelperSignkey);
+ hsk->start_time = start_time;
+ hsk->validity_duration = validity_duration;
+ hsk->exchange_pub = *exchange_pub;
+ hsk->sm_sig = *sm_sig;
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multipeermap_put (
+ hs->esign_keys,
+ &pid,
+ hsk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Setup helper state.
+ *
+ * @param[out] hs helper state to initialize
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+setup_key_helpers (struct HelperState *hs)
+{
+ hs->denom_keys
+ = GNUNET_CONTAINER_multihashmap_create (1024,
+ GNUNET_YES);
+ hs->rsa_keys
+ = GNUNET_CONTAINER_multihashmap_create (1024,
+ GNUNET_YES);
+ hs->cs_keys
+ = GNUNET_CONTAINER_multihashmap_create (1024,
+ GNUNET_YES);
+ hs->esign_keys
+ = GNUNET_CONTAINER_multipeermap_create (32,
+ GNUNET_NO /* MUST BE NO! */);
+ hs->rsadh = TALER_CRYPTO_helper_rsa_connect (TEH_cfg,
+ "taler-exchange",
+ &helper_rsa_cb,
+ hs);
+ if (NULL == hs->rsadh)
+ {
+ destroy_key_helpers (hs);
+ return GNUNET_SYSERR;
+ }
+ hs->csdh = TALER_CRYPTO_helper_cs_connect (TEH_cfg,
+ "taler-exchange",
+ &helper_cs_cb,
+ hs);
+ if (NULL == hs->csdh)
+ {
+ destroy_key_helpers (hs);
+ return GNUNET_SYSERR;
+ }
+ hs->esh = TALER_CRYPTO_helper_esign_connect (TEH_cfg,
+ "taler-exchange",
+ &helper_esign_cb,
+ hs);
+ if (NULL == hs->esh)
+ {
+ destroy_key_helpers (hs);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Synchronize helper state. Polls the key helper for updates.
+ *
+ * @param[in,out] hs helper state to synchronize
+ */
+static void
+sync_key_helpers (struct HelperState *hs)
+{
+ TALER_CRYPTO_helper_rsa_poll (hs->rsadh);
+ TALER_CRYPTO_helper_cs_poll (hs->csdh);
+ TALER_CRYPTO_helper_esign_poll (hs->esh);
+}
+
+
+/**
+ * Free denomination key data.
+ *
+ * @param cls a `struct TEH_KeyStateHandle`, unused
+ * @param h_denom_pub hash of the denomination public key, unused
+ * @param value a `struct TEH_DenominationKey` to free
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+clear_denomination_cb (void *cls,
+ const struct GNUNET_HashCode *h_denom_pub,
+ void *value)
+{
+ struct TEH_DenominationKey *dk = value;
+ struct TEH_AuditorSignature *as;
+
+ (void) cls;
+ (void) h_denom_pub;
+ TALER_denom_pub_free (&dk->denom_pub);
+ while (NULL != (as = dk->as_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (dk->as_head,
+ dk->as_tail,
+ as);
+ GNUNET_free (as);
+ }
+ GNUNET_free (dk);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Free denomination key data.
+ *
+ * @param cls a `struct TEH_KeyStateHandle`, unused
+ * @param pid the online signing key (type-disguised), unused
+ * @param value a `struct SigningKey` to free
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+clear_signkey_cb (void *cls,
+ const struct GNUNET_PeerIdentity *pid,
+ void *value)
+{
+ struct SigningKey *sk = value;
+
+ (void) cls;
+ (void) pid;
+ GNUNET_free (sk);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Free resources associated with @a cls, possibly excluding
+ * the helper data.
+ *
+ * @param[in] ksh key state to release
+ * @param free_helper true to also release the helper state
+ */
+static void
+destroy_key_state (struct TEH_KeyStateHandle *ksh,
+ bool free_helper)
+{
+ struct TEH_GlobalFee *gf;
+
+ clear_response_cache (ksh);
+ while (NULL != (gf = ksh->gf_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (ksh->gf_head,
+ ksh->gf_tail,
+ gf);
+ GNUNET_free (gf);
+ }
+ GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+ &clear_denomination_cb,
+ ksh);
+ GNUNET_CONTAINER_multihashmap_destroy (ksh->denomkey_map);
+ GNUNET_CONTAINER_multipeermap_iterate (ksh->signkey_map,
+ &clear_signkey_cb,
+ ksh);
+ GNUNET_CONTAINER_multipeermap_destroy (ksh->signkey_map);
+ json_decref (ksh->auditors);
+ ksh->auditors = NULL;
+ json_decref (ksh->global_fees);
+ ksh->global_fees = NULL;
+ if (free_helper)
+ {
+ destroy_key_helpers (ksh->helpers);
+ GNUNET_free (ksh->helpers);
+ }
+ if (NULL != ksh->management_keys_reply)
+ {
+ json_decref (ksh->management_keys_reply);
+ ksh->management_keys_reply = NULL;
+ }
+ GNUNET_free (ksh);
+}
+
+
+/**
+ * Function called whenever another exchange process has updated
+ * the keys data in the database.
+ *
+ * @param cls NULL
+ * @param extra unused
+ * @param extra_size number of bytes in @a extra unused
+ */
+static void
+keys_update_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ (void) cls;
+ (void) extra;
+ (void) extra_size;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received /keys update event\n");
+ TEH_check_invariants ();
+ key_generation++;
+ TEH_resume_keys_requests (false);
+ TEH_check_invariants ();
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_keys_init ()
+{
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+ "exchange",
+ "SIGNKEY_LEGAL_DURATION",
+ &signkey_legal_duration))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "SIGNKEY_LEGAL_DURATION");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
+ "exchange",
+ "ASSET_TYPE",
+ &asset_type))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ "exchange",
+ "ASSET_TYPE");
+ asset_type = GNUNET_strdup ("fiat");
+ }
+ keys_eh = TEH_plugin->event_listen (TEH_plugin->cls,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &es,
+ &keys_update_event_cb,
+ NULL);
+ if (NULL == keys_eh)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Fully clean up our state.
+ */
+void
+TEH_keys_finished ()
+{
+ if (NULL != keys_tt)
+ {
+ GNUNET_SCHEDULER_cancel (keys_tt);
+ keys_tt = NULL;
+ }
+ if (NULL != key_state)
+ destroy_key_state (key_state,
+ true);
+ if (NULL != keys_eh)
+ {
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ keys_eh);
+ keys_eh = NULL;
+ }
+}
+
+
+/**
+ * Function called with information about the exchange's denomination keys.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param denom_pub public key of the denomination
+ * @param h_denom_pub hash of @a denom_pub
+ * @param meta meta data information about the denomination type (value, expirations, fees)
+ * @param master_sig master signature affirming the validity of this denomination
+ * @param recoup_possible true if the key was revoked and clients can currently recoup
+ * coins of this denomination
+ */
+static void
+denomination_info_cb (
+ void *cls,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig,
+ bool recoup_possible)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ struct TEH_DenominationKey *dk;
+
+ if (GNUNET_OK !=
+ TALER_exchange_offline_denom_validity_verify (
+ h_denom_pub,
+ meta->start,
+ meta->expire_withdraw,
+ meta->expire_deposit,
+ meta->expire_legal,
+ &meta->value,
+ &meta->fees,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has denomination with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
+
+ GNUNET_assert (GNUNET_CRYPTO_BSA_INVALID !=
+ denom_pub->bsign_pub_key->cipher);
+ if (GNUNET_TIME_absolute_is_zero (meta->start.abs_time) ||
+ GNUNET_TIME_absolute_is_zero (meta->expire_withdraw.abs_time) ||
+ GNUNET_TIME_absolute_is_zero (meta->expire_deposit.abs_time) ||
+ GNUNET_TIME_absolute_is_zero (meta->expire_legal.abs_time) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database contains invalid denomination key %s\n",
+ GNUNET_h2s (&h_denom_pub->hash));
+ return;
+ }
+ dk = GNUNET_new (struct TEH_DenominationKey);
+ TALER_denom_pub_copy (&dk->denom_pub,
+ denom_pub);
+ dk->h_denom_pub = *h_denom_pub;
+ dk->meta = *meta;
+ dk->master_sig = *master_sig;
+ dk->recoup_possible = recoup_possible;
+ dk->denom_pub.age_mask = meta->age_mask;
+
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (ksh->denomkey_map,
+ &dk->h_denom_pub.hash,
+ dk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Function called with information about the exchange's online signing keys.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param exchange_pub the public key
+ * @param meta meta data information about the denomination type (expirations)
+ * @param master_sig master signature affirming the validity of this denomination
+ */
+static void
+signkey_info_cb (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ struct SigningKey *sk;
+ struct GNUNET_PeerIdentity pid;
+
+ if (GNUNET_OK !=
+ TALER_exchange_offline_signkey_validity_verify (
+ exchange_pub,
+ meta->start,
+ meta->expire_sign,
+ meta->expire_legal,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has signing key with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
+ sk = GNUNET_new (struct SigningKey);
+ sk->exchange_pub = *exchange_pub;
+ sk->meta = *meta;
+ sk->master_sig = *master_sig;
+ pid.public_key = exchange_pub->eddsa_pub;
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multipeermap_put (ksh->signkey_map,
+ &pid,
+ sk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Closure for #get_auditor_sigs.
+ */
+struct GetAuditorSigsContext
+{
+ /**
+ * Where to store the matching signatures.
+ */
+ json_t *denom_keys;
+
+ /**
+ * Public key of the auditor to match against.
+ */
+ const struct TALER_AuditorPublicKeyP *auditor_pub;
+};
+
+
+/**
+ * Extract the auditor signatures matching the auditor's public
+ * key from the @a value and generate the respective JSON.
+ *
+ * @param cls a `struct GetAuditorSigsContext`
+ * @param h_denom_pub hash of the denomination public key
+ * @param value a `struct TEH_DenominationKey`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+get_auditor_sigs (void *cls,
+ const struct GNUNET_HashCode *h_denom_pub,
+ void *value)
+{
+ struct GetAuditorSigsContext *ctx = cls;
+ struct TEH_DenominationKey *dk = value;
+
+ for (struct TEH_AuditorSignature *as = dk->as_head;
+ NULL != as;
+ as = as->next)
+ {
+ if (0 !=
+ GNUNET_memcmp (ctx->auditor_pub,
+ &as->apub))
+ continue;
+ GNUNET_break (0 ==
+ json_array_append_new (
+ ctx->denom_keys,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denom_pub_h",
+ h_denom_pub),
+ GNUNET_JSON_pack_data_auto ("auditor_sig",
+ &as->asig))));
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with information about the exchange's auditors.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param auditor_pub the public key of the auditor
+ * @param auditor_url URL of the REST API of the auditor
+ * @param auditor_name human readable official name of the auditor
+ */
+static void
+auditor_info_cb (
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ struct GetAuditorSigsContext ctx;
+
+ ctx.denom_keys = json_array ();
+ GNUNET_assert (NULL != ctx.denom_keys);
+ ctx.auditor_pub = auditor_pub;
+ GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+ &get_auditor_sigs,
+ &ctx);
+ GNUNET_break (0 ==
+ json_array_append_new (
+ ksh->auditors,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("auditor_name",
+ auditor_name),
+ GNUNET_JSON_pack_data_auto ("auditor_pub",
+ auditor_pub),
+ GNUNET_JSON_pack_string ("auditor_url",
+ auditor_url),
+ GNUNET_JSON_pack_array_steal ("denomination_keys",
+ ctx.denom_keys))));
+}
+
+
+/**
+ * Function called with information about the denominations
+ * audited by the exchange's auditors.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param auditor_pub the public key of an auditor
+ * @param h_denom_pub hash of a denomination key audited by this auditor
+ * @param auditor_sig signature from the auditor affirming this
+ */
+static void
+auditor_denom_cb (
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorSignatureP *auditor_sig)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ struct TEH_DenominationKey *dk;
+ struct TEH_AuditorSignature *as;
+
+ dk = GNUNET_CONTAINER_multihashmap_get (ksh->denomkey_map,
+ &h_denom_pub->hash);
+ if (NULL == dk)
+ {
+ /* Odd, this should be impossible as per foreign key
+ constraint on 'auditor_denom_sigs'! Well, we can
+ safely continue anyway, so let's just log it. */
+ GNUNET_break (0);
+ return;
+ }
+ as = GNUNET_new (struct TEH_AuditorSignature);
+ as->asig = *auditor_sig;
+ as->apub = *auditor_pub;
+ GNUNET_CONTAINER_DLL_insert (dk->as_head,
+ dk->as_tail,
+ as);
+}
+
+
+/**
+ * Closure for #add_sign_key_cb.
+ */
+struct SignKeyCtx
+{
+ /**
+ * What is the current rotation frequency for signing keys. Updated.
+ */
+ struct GNUNET_TIME_Relative min_sk_frequency;
+
+ /**
+ * JSON array of signing keys (being created).
+ */
+ json_t *signkeys;
+};
+
+
+/**
+ * Function called for all signing keys, used to build up the
+ * respective JSON response.
+ *
+ * @param cls a `struct SignKeyCtx *` with the array to append keys to
+ * @param pid the exchange public key (in type disguise)
+ * @param value a `struct SigningKey`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_sign_key_cb (void *cls,
+ const struct GNUNET_PeerIdentity *pid,
+ void *value)
+{
+ struct SignKeyCtx *ctx = cls;
+ struct SigningKey *sk = value;
+
+ (void) pid;
+ if (GNUNET_TIME_absolute_is_future (sk->meta.expire_sign.abs_time))
+ {
+ ctx->min_sk_frequency =
+ GNUNET_TIME_relative_min (ctx->min_sk_frequency,
+ GNUNET_TIME_absolute_get_difference (
+ sk->meta.start.abs_time,
+ sk->meta.expire_sign.abs_time));
+ }
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ ctx->signkeys,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_timestamp ("stamp_start",
+ sk->meta.start),
+ GNUNET_JSON_pack_timestamp ("stamp_expire",
+ sk->meta.expire_sign),
+ GNUNET_JSON_pack_timestamp ("stamp_end",
+ sk->meta.expire_legal),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &sk->master_sig),
+ GNUNET_JSON_pack_data_auto ("key",
+ &sk->exchange_pub))));
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #add_denom_key_cb.
+ */
+struct DenomKeyCtx
+{
+ /**
+ * Heap for sorting active denomination keys by start time.
+ */
+ struct GNUNET_CONTAINER_Heap *heap;
+
+ /**
+ * JSON array of revoked denomination keys.
+ */
+ json_t *recoup;
+
+ /**
+ * What is the minimum key rotation frequency of
+ * valid denomination keys?
+ */
+ struct GNUNET_TIME_Relative min_dk_frequency;
+};
+
+
+/**
+ * Function called for all denomination keys, used to build up the
+ * JSON list of *revoked* denomination keys and the
+ * heap of non-revoked denomination keys by timeout.
+ *
+ * @param cls a `struct DenomKeyCtx`
+ * @param h_denom_pub hash of the denomination key
+ * @param value a `struct TEH_DenominationKey`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_denom_key_cb (void *cls,
+ const struct GNUNET_HashCode *h_denom_pub,
+ void *value)
+{
+ struct DenomKeyCtx *dkc = cls;
+ struct TEH_DenominationKey *dk = value;
+
+ if (dk->recoup_possible)
+ {
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ dkc->recoup,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ h_denom_pub))));
+ }
+ else
+ {
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ dkc->min_dk_frequency =
+ GNUNET_TIME_relative_min (dkc->min_dk_frequency,
+ GNUNET_TIME_absolute_get_difference (
+ dk->meta.start.abs_time,
+ dk->meta.expire_withdraw.abs_time));
+ }
+ (void) GNUNET_CONTAINER_heap_insert (dkc->heap,
+ dk,
+ dk->meta.start.abs_time.abs_value_us);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Add the headers we want to set for every /keys response.
+ *
+ * @param cls the key state to use
+ * @param[in,out] response the response to modify
+ */
+static void
+setup_general_response_headers (void *cls,
+ struct MHD_Response *response)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ char dat[128];
+
+ TALER_MHD_add_global_headers (response);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "application/json"));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "public,must-revalidate,max-age=86400"));
+ if (! GNUNET_TIME_relative_is_zero (ksh->rekey_frequency))
+ {
+ struct GNUNET_TIME_Relative r;
+ struct GNUNET_TIME_Absolute a;
+ struct GNUNET_TIME_Timestamp km;
+ struct GNUNET_TIME_Timestamp m;
+ struct GNUNET_TIME_Timestamp we;
+
+ r = GNUNET_TIME_relative_min (TEH_max_keys_caching,
+ ksh->rekey_frequency);
+ a = GNUNET_TIME_relative_to_absolute (r);
+ /* Round up to next full day to ensure the expiration
+ time does not become a fingerprint! */
+ a = GNUNET_TIME_absolute_round_down (a,
+ GNUNET_TIME_UNIT_DAYS);
+ a = GNUNET_TIME_absolute_add (a,
+ GNUNET_TIME_UNIT_DAYS);
+ km = GNUNET_TIME_absolute_to_timestamp (a);
+ we = GNUNET_TIME_absolute_to_timestamp (wire_state->cache_expiration);
+ m = GNUNET_TIME_timestamp_min (we,
+ km);
+ TALER_MHD_get_date_string (m.abs_time,
+ dat);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Setting /keys 'Expires' header to '%s' (rekey frequency is %s)\n",
+ dat,
+ GNUNET_TIME_relative2s (ksh->rekey_frequency,
+ false));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_EXPIRES,
+ dat));
+ ksh->signature_expires
+ = GNUNET_TIME_timestamp_min (m,
+ ksh->signature_expires);
+ }
+ /* Set cache control headers: our response varies depending on these headers */
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_VARY,
+ MHD_HTTP_HEADER_ACCEPT_ENCODING));
+}
+
+
+/**
+ * Function called with wallet balance thresholds.
+ *
+ * @param[in,out] cls a `json **` where to put the array of json amounts discovered
+ * @param threshold another threshold amount to add
+ */
+static void
+wallet_threshold_cb (void *cls,
+ const struct TALER_Amount *threshold)
+{
+ json_t **ret = cls;
+
+ if (NULL == *ret)
+ *ret = json_array ();
+ GNUNET_assert (0 ==
+ json_array_append_new (*ret,
+ TALER_JSON_from_amount (
+ threshold)));
+}
+
+
+/**
+ * Initialize @a krd using the given values for @a signkeys,
+ * @a recoup and @a denoms.
+ *
+ * @param[in,out] ksh key state handle we build @a krd for
+ * @param[in] denom_keys_hash hash over all the denomination keys in @a denoms
+ * @param last_cherry_pick_date timestamp to use
+ * @param[in,out] signkeys list of sign keys to return
+ * @param[in,out] recoup list of revoked keys to return
+ * @param[in,out] grouped_denominations list of grouped denominations to return
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+create_krd (struct TEH_KeyStateHandle *ksh,
+ const struct GNUNET_HashCode *denom_keys_hash,
+ struct GNUNET_TIME_Timestamp last_cherry_pick_date,
+ json_t *signkeys,
+ json_t *recoup,
+ json_t *grouped_denominations)
+{
+ struct KeysResponseData krd;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct WireStateHandle *wsh;
+ json_t *keys;
+
+ wsh = get_wire_state ();
+ if (! wsh->ready)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ last_cherry_pick_date.abs_time));
+ GNUNET_assert (NULL != signkeys);
+ GNUNET_assert (NULL != recoup);
+ GNUNET_assert (NULL != grouped_denominations);
+ GNUNET_assert (NULL != ksh->auditors);
+ GNUNET_assert (NULL != TEH_currency);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Creating /keys at cherry pick date %s\n",
+ GNUNET_TIME_timestamp2s (last_cherry_pick_date));
+
+ /* Sign hash over master signatures of all denomination keys until this time
+ (in reverse order). */
+ {
+ enum TALER_ErrorCode ec;
+
+ if (TALER_EC_NONE !=
+ (ec =
+ TALER_exchange_online_key_set_sign (
+ &TEH_keys_exchange_sign2_,
+ ksh,
+ last_cherry_pick_date,
+ denom_keys_hash,
+ &exchange_pub,
+ &exchange_sig)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Could not create key response data: cannot sign (%s)\n",
+ TALER_ErrorCode_get_hint (ec));
+ return GNUNET_SYSERR;
+ }
+ }
+
+ {
+ const struct SigningKey *sk;
+
+ sk = GNUNET_CONTAINER_multipeermap_get (
+ ksh->signkey_map,
+ (const struct GNUNET_PeerIdentity *) &exchange_pub);
+ ksh->signature_expires = GNUNET_TIME_timestamp_min (sk->meta.expire_sign,
+ ksh->signature_expires);
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Build /keys data with %u wire accounts\n",
+ (unsigned int) json_array_size (
+ json_object_get (wsh->json_reply,
+ "accounts")));
+
+ keys = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("version",
+ EXCHANGE_PROTOCOL_VERSION),
+ GNUNET_JSON_pack_string ("base_url",
+ TEH_base_url),
+ GNUNET_JSON_pack_string ("currency",
+ TEH_currency),
+ GNUNET_JSON_pack_object_steal (
+ "currency_specification",
+ TALER_CONFIG_currency_specs_to_json (TEH_cspec)),
+ TALER_JSON_pack_amount ("stefan_abs",
+ &TEH_stefan_abs),
+ TALER_JSON_pack_amount ("stefan_log",
+ &TEH_stefan_log),
+ GNUNET_JSON_pack_double ("stefan_lin",
+ (double) TEH_stefan_lin),
+ GNUNET_JSON_pack_string ("asset_type",
+ asset_type),
+ GNUNET_JSON_pack_bool ("rewards_allowed",
+ GNUNET_YES ==
+ TEH_enable_rewards),
+ GNUNET_JSON_pack_data_auto ("master_public_key",
+ &TEH_master_public_key),
+ GNUNET_JSON_pack_time_rel ("reserve_closing_delay",
+ TEH_reserve_closing_delay),
+ GNUNET_JSON_pack_array_incref ("signkeys",
+ signkeys),
+ GNUNET_JSON_pack_array_incref ("recoup",
+ recoup),
+ GNUNET_JSON_pack_array_incref ("wads",
+ json_object_get (wsh->json_reply,
+ "wads")),
+ GNUNET_JSON_pack_array_incref ("accounts",
+ json_object_get (wsh->json_reply,
+ "accounts")),
+ GNUNET_JSON_pack_object_incref ("wire_fees",
+ json_object_get (wsh->json_reply,
+ "fees")),
+ GNUNET_JSON_pack_array_incref ("denominations",
+ grouped_denominations),
+ GNUNET_JSON_pack_array_incref ("auditors",
+ ksh->auditors),
+ GNUNET_JSON_pack_array_incref ("global_fees",
+ ksh->global_fees),
+ GNUNET_JSON_pack_timestamp ("list_issue_date",
+ last_cherry_pick_date),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &exchange_sig));
+ GNUNET_assert (NULL != keys);
+
+ /* Set wallet limit if KYC is configured */
+ {
+ json_t *wblwk = NULL;
+
+ TALER_KYCLOGIC_kyc_iterate_thresholds (
+ TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE,
+ &wallet_threshold_cb,
+ &wblwk);
+ if (NULL != wblwk)
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (
+ keys,
+ "wallet_balance_limit_without_kyc",
+ wblwk));
+ }
+
+ /* Signal support for the configured, enabled extensions. */
+ {
+ 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;
+ iter = iter->next)
+ {
+ const struct TALER_Extension *extension = iter->extension;
+ json_t *manifest;
+ int r;
+
+ /* skip if not enabled */
+ if (! extension->enabled)
+ continue;
+
+ /* flag our findings so far */
+ has_extensions = true;
+
+
+ manifest = extension->manifest (extension);
+ GNUNET_assert (manifest);
+
+ r = json_object_set_new (
+ extensions,
+ extension->name,
+ manifest);
+ GNUNET_assert (0 == r);
+ }
+
+ /* Update the keys object with the extensions and its signature */
+ if (has_extensions)
+ {
+ json_t *sig;
+ int r;
+
+ r = json_object_set_new (
+ keys,
+ "extensions",
+ extensions);
+ GNUNET_assert (0 == r);
+
+ /* Add the signature of the extensions, if it is not zero */
+ if (TEH_extensions_signed)
+ {
+ sig = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("extensions_sig",
+ &TEH_extensions_sig));
+
+ r = json_object_update (keys, sig);
+ GNUNET_assert (0 == r);
+ }
+ }
+ else
+ {
+ json_decref (extensions);
+ }
+ }
+
+
+ {
+ char *keys_json;
+ void *keys_jsonz;
+ size_t keys_jsonz_size;
+ int comp;
+ char etag[sizeof (struct GNUNET_HashCode) * 2];
+
+ /* Convert /keys response to UTF8-String */
+ keys_json = json_dumps (keys,
+ JSON_INDENT (2));
+ json_decref (keys);
+ GNUNET_assert (NULL != keys_json);
+
+ /* Keep copy for later compression... */
+ keys_jsonz = GNUNET_strdup (keys_json);
+ keys_jsonz_size = strlen (keys_json);
+
+ /* hash to compute etag */
+ {
+ struct GNUNET_HashCode ehash;
+ char *end;
+
+ GNUNET_CRYPTO_hash (keys_jsonz,
+ keys_jsonz_size,
+ &ehash);
+ end = GNUNET_STRINGS_data_to_string (&ehash,
+ sizeof (ehash),
+ etag,
+ sizeof (etag));
+ *end = '\0';
+ }
+
+ /* Create uncompressed response */
+ krd.response_uncompressed
+ = MHD_create_response_from_buffer (keys_jsonz_size,
+ keys_json,
+ MHD_RESPMEM_MUST_FREE);
+ GNUNET_assert (NULL != krd.response_uncompressed);
+ setup_general_response_headers (ksh,
+ krd.response_uncompressed);
+ /* Information is always public, revalidate after 1 day */
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (krd.response_uncompressed,
+ MHD_HTTP_HEADER_ETAG,
+ etag));
+ /* Also compute compressed version of /keys response */
+ comp = TALER_MHD_body_compress (&keys_jsonz,
+ &keys_jsonz_size);
+ krd.response_compressed
+ = MHD_create_response_from_buffer (keys_jsonz_size,
+ keys_jsonz,
+ MHD_RESPMEM_MUST_FREE);
+ GNUNET_assert (NULL != krd.response_compressed);
+ /* If the response is actually compressed, set the
+ respective header. */
+ GNUNET_assert ( (MHD_YES != comp) ||
+ (MHD_YES ==
+ MHD_add_response_header (krd.response_compressed,
+ MHD_HTTP_HEADER_CONTENT_ENCODING,
+ "deflate")) );
+ setup_general_response_headers (ksh,
+ krd.response_compressed);
+ /* Information is always public, revalidate after 1 day */
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (krd.response_compressed,
+ MHD_HTTP_HEADER_ETAG,
+ etag));
+ krd.etag = GNUNET_strdup (etag);
+ }
+ krd.cherry_pick_date = last_cherry_pick_date;
+ GNUNET_array_append (ksh->krd_array,
+ ksh->krd_array_length,
+ krd);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Element in the `struct SignatureContext` array.
+ */
+struct SignatureElement
+{
+
+ /**
+ * Offset of the denomination in the group array,
+ * for sorting (2nd rank, ascending).
+ */
+ unsigned int offset;
+
+ /**
+ * Offset of the group in the denominations array,
+ * for sorting (2nd rank, ascending).
+ */
+ unsigned int group_offset;
+
+ /**
+ * Pointer to actual master signature to hash over.
+ */
+ struct TALER_MasterSignatureP master_sig;
+};
+
+/**
+ * Context for collecting the array of master signatures
+ * needed to verify the exchange_sig online signature.
+ */
+struct SignatureContext
+{
+ /**
+ * Array of signatures to hash over.
+ */
+ struct SignatureElement *elements;
+
+ /**
+ * Write offset in the @e elements array.
+ */
+ unsigned int elements_pos;
+
+ /**
+ * Allocated space for @e elements.
+ */
+ unsigned int elements_size;
+};
+
+
+/**
+ * Determine order to sort two elements by before
+ * we hash the master signatures. Used for
+ * sorting with qsort().
+ *
+ * @param a pointer to a `struct SignatureElement`
+ * @param b pointer to a `struct SignatureElement`
+ * @return 0 if equal, -1 if a < b, 1 if a > b.
+ */
+static int
+signature_context_sort_cb (const void *a,
+ const void *b)
+{
+ const struct SignatureElement *sa = a;
+ const struct SignatureElement *sb = b;
+
+ if (sa->group_offset < sb->group_offset)
+ return -1;
+ if (sa->group_offset > sb->group_offset)
+ return 1;
+ if (sa->offset < sb->offset)
+ return -1;
+ if (sa->offset > sb->offset)
+ return 1;
+ /* We should never have two disjoint elements
+ with same time and offset */
+ GNUNET_assert (sa == sb);
+ return 0;
+}
+
+
+/**
+ * Append a @a master_sig to the @a sig_ctx using the
+ * given attributes for (later) sorting.
+ *
+ * @param[in,out] sig_ctx signature context to update
+ * @param group_offset offset for the group
+ * @param offset offset for the entry
+ * @param master_sig master signature for the entry
+ */
+static void
+append_signature (struct SignatureContext *sig_ctx,
+ unsigned int group_offset,
+ unsigned int offset,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct SignatureElement *element;
+ unsigned int new_size;
+
+ if (sig_ctx->elements_pos == sig_ctx->elements_size)
+ {
+ if (0 == sig_ctx->elements_size)
+ new_size = 1024;
+ else
+ new_size = sig_ctx->elements_size * 2;
+ GNUNET_array_grow (sig_ctx->elements,
+ sig_ctx->elements_size,
+ new_size);
+ }
+ element = &sig_ctx->elements[sig_ctx->elements_pos++];
+ element->offset = offset;
+ element->group_offset = group_offset;
+ element->master_sig = *master_sig;
+}
+
+
+/**
+ *GroupData is the value we store for each group meta-data */
+struct GroupData
+{
+ /**
+ * The json blob with the group meta-data and list of denominations
+ */
+ json_t *json;
+
+ /**
+ * List of denominations for the group,
+ * included in @e json, do not free separately!
+ */
+ json_t *list;
+
+ /**
+ * Offset of the group in the final array.
+ */
+ unsigned int group_off;
+
+};
+
+
+/**
+ * Helper function called to clean up the group data
+ * in the denominations_by_group below.
+ *
+ * @param cls unused
+ * @param key unused
+ * @param value a `struct GroupData` to free
+ * @return #GNUNET_OK
+ */
+static int
+free_group (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ struct GroupData *gd = value;
+
+ (void) cls;
+ (void) key;
+ GNUNET_free (gd);
+ return GNUNET_OK;
+}
+
+
+static void
+compute_msig_hash (struct SignatureContext *sig_ctx,
+ struct GNUNET_HashCode *hc)
+{
+ struct GNUNET_HashContext *hash_context;
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+ qsort (sig_ctx->elements,
+ sig_ctx->elements_pos,
+ sizeof (struct SignatureElement),
+ &signature_context_sort_cb);
+ for (unsigned int i = 0; i<sig_ctx->elements_pos; i++)
+ {
+ struct SignatureElement *element = &sig_ctx->elements[i];
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Adding %u,%u,%s\n",
+ element->group_offset,
+ element->offset,
+ TALER_B2S (&element->master_sig));
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &element->master_sig,
+ sizeof (element->master_sig));
+ }
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ hc);
+}
+
+
+/**
+ * Update the "/keys" responses in @a ksh, computing the detailed replies.
+ *
+ * This function is to recompute all (including cherry-picked) responses we
+ * might want to return, based on the state already in @a ksh.
+ *
+ * @param[in,out] ksh state handle to update
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+finish_keys_response (struct TEH_KeyStateHandle *ksh)
+{
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+ json_t *recoup;
+ struct SignKeyCtx sctx = {
+ .min_sk_frequency = GNUNET_TIME_UNIT_FOREVER_REL
+ };
+ json_t *grouped_denominations = NULL;
+ struct GNUNET_TIME_Timestamp last_cherry_pick_date;
+ struct GNUNET_CONTAINER_Heap *heap;
+ struct SignatureContext sig_ctx = { 0 };
+ /* Remember if we have any denomination with age restriction */
+ bool has_age_restricted_denomination = false;
+ struct WireStateHandle *wsh;
+
+ wsh = get_wire_state ();
+ if (! wsh->ready)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 ==
+ json_array_size (json_object_get (wsh->json_reply,
+ "accounts")) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No wire accounts available. Refusing to generate /keys response.\n");
+ return GNUNET_NO;
+ }
+ sctx.signkeys = json_array ();
+ GNUNET_assert (NULL != sctx.signkeys);
+ recoup = json_array ();
+ GNUNET_assert (NULL != recoup);
+ grouped_denominations = json_array ();
+ GNUNET_assert (NULL != grouped_denominations);
+
+ GNUNET_CONTAINER_multipeermap_iterate (ksh->signkey_map,
+ &add_sign_key_cb,
+ &sctx);
+ if (0 == json_array_size (sctx.signkeys))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No online signing keys available. Refusing to generate /keys response.\n");
+ ret = GNUNET_NO;
+ goto CLEANUP;
+ }
+ heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MAX);
+ {
+ struct DenomKeyCtx dkc = {
+ .recoup = recoup,
+ .heap = heap,
+ .min_dk_frequency = GNUNET_TIME_UNIT_FOREVER_REL,
+ };
+
+ GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+ &add_denom_key_cb,
+ &dkc);
+ ksh->rekey_frequency
+ = GNUNET_TIME_relative_min (dkc.min_dk_frequency,
+ sctx.min_sk_frequency);
+ }
+
+ last_cherry_pick_date = GNUNET_TIME_UNIT_ZERO_TS;
+
+ {
+ struct TEH_DenominationKey *dk;
+ struct GNUNET_CONTAINER_MultiHashMap *denominations_by_group;
+
+ denominations_by_group =
+ GNUNET_CONTAINER_multihashmap_create (1024,
+ GNUNET_NO /* NO, because keys are only on the stack */);
+ /* heap = max heap, sorted by start time */
+ while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap)))
+ {
+ if (GNUNET_TIME_timestamp_cmp (last_cherry_pick_date,
+ !=,
+ dk->meta.start) &&
+ (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time)) )
+ {
+ /*
+ * This is not the first entry in the heap (because last_cherry_pick_date !=
+ * GNUNET_TIME_UNIT_ZERO_TS) and the previous entry had a different
+ * start time. Therefore, we create a new entry in ksh.
+ */
+ struct GNUNET_HashCode hc;
+
+ compute_msig_hash (&sig_ctx,
+ &hc);
+ if (GNUNET_OK !=
+ create_krd (ksh,
+ &hc,
+ last_cherry_pick_date,
+ sctx.signkeys,
+ recoup,
+ grouped_denominations))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to generate key response data for %s\n",
+ GNUNET_TIME_timestamp2s (last_cherry_pick_date));
+ /* drain heap before destroying it */
+ while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap)))
+ /* intentionally empty */;
+ GNUNET_CONTAINER_heap_destroy (heap);
+ goto CLEANUP;
+ }
+ }
+
+ last_cherry_pick_date = dk->meta.start;
+ /*
+ * Group the denominations by {cipher, value, fees, age_mask}.
+ *
+ * For each group we save the group meta-data and the list of
+ * denominations in this group as a json-blob in the multihashmap
+ * denominations_by_group.
+ */
+ {
+ struct GroupData *group;
+ json_t *entry;
+ struct GNUNET_HashCode key;
+ struct TALER_DenominationGroup meta = {
+ .cipher = dk->denom_pub.bsign_pub_key->cipher,
+ .value = dk->meta.value,
+ .fees = dk->meta.fees,
+ .age_mask = dk->meta.age_mask,
+ };
+
+ /* Search the group/JSON-blob for the key */
+ TALER_denomination_group_get_key (&meta,
+ &key);
+ group = GNUNET_CONTAINER_multihashmap_get (
+ denominations_by_group,
+ &key);
+ if (NULL == group)
+ {
+ /* There is no group for this meta-data yet, so we create a new group */
+ bool age_restricted = meta.age_mask.bits != 0;
+ const char *cipher;
+
+ group = GNUNET_new (struct GroupData);
+ switch (meta.cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ cipher = age_restricted ? "RSA+age_restricted" : "RSA";
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ cipher = age_restricted ? "CS+age_restricted" : "CS";
+ break;
+ default:
+ GNUNET_assert (false);
+ }
+ /* Create a new array for the denominations in this group */
+ group->list = json_array ();
+ GNUNET_assert (NULL != group->list);
+ group->json = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ cipher),
+ GNUNET_JSON_pack_array_steal ("denoms",
+ group->list),
+ TALER_JSON_PACK_DENOM_FEES ("fee",
+ &meta.fees),
+ TALER_JSON_pack_amount ("value",
+ &meta.value));
+ GNUNET_assert (NULL != group->json);
+ if (age_restricted)
+ {
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (group->json,
+ "age_mask",
+ json_integer (
+ meta.age_mask.bits)));
+ /* Remember that we have found at least _one_ age restricted denomination */
+ has_age_restricted_denomination = true;
+ }
+ group->group_off
+ = json_array_size (grouped_denominations);
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ grouped_denominations,
+ group->json));
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (denominations_by_group,
+ &key,
+ group,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ }
+
+ /* Now that we have found/created the right group, add the
+ denomination to the list */
+ {
+ struct HelperDenomination *hd;
+ struct GNUNET_JSON_PackSpec key_spec;
+ bool private_key_lost;
+
+ hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+ &dk->h_denom_pub.hash);
+ private_key_lost
+ = (NULL == hd) ||
+ GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_add (
+ hd->start_time.abs_time,
+ hd->validity_duration));
+ switch (meta.cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ key_spec =
+ GNUNET_JSON_pack_rsa_public_key (
+ "rsa_pub",
+ dk->denom_pub.bsign_pub_key->details.rsa_public_key);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ key_spec =
+ GNUNET_JSON_pack_data_varsize (
+ "cs_pub",
+ &dk->denom_pub.bsign_pub_key->details.cs_public_key,
+ sizeof (dk->denom_pub.bsign_pub_key->details.cs_public_key));
+ break;
+ default:
+ GNUNET_assert (false);
+ }
+
+ entry = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &dk->master_sig),
+ GNUNET_JSON_pack_allow_null (
+ private_key_lost
+ ? GNUNET_JSON_pack_bool ("lost",
+ true)
+ : GNUNET_JSON_pack_string ("dummy",
+ NULL)),
+ GNUNET_JSON_pack_timestamp ("stamp_start",
+ dk->meta.start),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
+ dk->meta.expire_withdraw),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
+ dk->meta.expire_deposit),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
+ dk->meta.expire_legal),
+ key_spec
+ );
+ GNUNET_assert (NULL != entry);
+ }
+
+ /* Build up the running hash of all master signatures of the
+ denominations */
+ append_signature (&sig_ctx,
+ group->group_off,
+ (unsigned int) json_array_size (group->list),
+ &dk->master_sig);
+ /* Finally, add the denomination to the list of denominations in this
+ group */
+ GNUNET_assert (json_is_array (group->list));
+ GNUNET_assert (0 ==
+ json_array_append_new (group->list,
+ entry));
+ }
+ } /* loop over heap ends */
+
+ GNUNET_CONTAINER_multihashmap_iterate (denominations_by_group,
+ &free_group,
+ NULL);
+ GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group);
+ }
+ GNUNET_CONTAINER_heap_destroy (heap);
+
+ if (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time))
+ {
+ struct GNUNET_HashCode hc;
+
+ compute_msig_hash (&sig_ctx,
+ &hc);
+ if (GNUNET_OK !=
+ create_krd (ksh,
+ &hc,
+ last_cherry_pick_date,
+ sctx.signkeys,
+ recoup,
+ grouped_denominations))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to generate key response data for %s\n",
+ GNUNET_TIME_timestamp2s (last_cherry_pick_date));
+ goto CLEANUP;
+ }
+ ksh->management_only = false;
+
+ /* Sanity check: Make sure that age restriction is enabled IFF at least
+ * one age restricted denomination exist */
+ if (! has_age_restricted_denomination && TEH_age_restriction_enabled)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Age restriction is enabled, but NO denominations with age restriction found!\n");
+ goto CLEANUP;
+ }
+ else if (has_age_restricted_denomination && ! TEH_age_restriction_enabled)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Age restriction is NOT enabled, but denominations with age restriction found!\n");
+ goto CLEANUP;
+ }
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No denomination keys available. Refusing to generate /keys response.\n");
+ }
+ ret = GNUNET_OK;
+
+CLEANUP:
+ GNUNET_array_grow (sig_ctx.elements,
+ sig_ctx.elements_size,
+ 0);
+ json_decref (grouped_denominations);
+ if (NULL != sctx.signkeys)
+ json_decref (sctx.signkeys);
+ json_decref (recoup);
+ return ret;
+}
+
+
+/**
+ * Called with information about global fees.
+ *
+ * @param cls `struct TEH_KeyStateHandle *` we are building
+ * @param fees the global fees we charge
+ * @param purse_timeout when do purses time out
+ * @param history_expiration how long are account histories preserved
+ * @param purse_account_limit how many purses are free per account
+ * @param start_date from when are these fees valid (start date)
+ * @param end_date until when are these fees valid (end date, exclusive)
+ * @param master_sig master key signature affirming that this is the correct
+ * fee (of purpose #TALER_SIGNATURE_MASTER_GLOBAL_FEES)
+ */
+static void
+global_fee_info_cb (
+ void *cls,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ struct TEH_GlobalFee *gf;
+
+ if (GNUNET_OK !=
+ TALER_exchange_offline_global_fee_verify (
+ start_date,
+ end_date,
+ fees,
+ purse_timeout,
+ history_expiration,
+ purse_account_limit,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has global fee with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found global fees with %u purses\n",
+ purse_account_limit);
+ gf = GNUNET_new (struct TEH_GlobalFee);
+ gf->start_date = start_date;
+ gf->end_date = end_date;
+ gf->fees = *fees;
+ gf->purse_timeout = purse_timeout;
+ gf->history_expiration = history_expiration;
+ gf->purse_account_limit = purse_account_limit;
+ gf->master_sig = *master_sig;
+ GNUNET_CONTAINER_DLL_insert (ksh->gf_head,
+ ksh->gf_tail,
+ gf);
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ ksh->global_fees,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_timestamp ("start_date",
+ start_date),
+ GNUNET_JSON_pack_timestamp ("end_date",
+ end_date),
+ TALER_JSON_PACK_GLOBAL_FEES (fees),
+ GNUNET_JSON_pack_time_rel ("history_expiration",
+ history_expiration),
+ GNUNET_JSON_pack_time_rel ("purse_timeout",
+ purse_timeout),
+ GNUNET_JSON_pack_uint64 ("purse_account_limit",
+ purse_account_limit),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig))));
+}
+
+
+/**
+ * Create a key state.
+ *
+ * @param[in] hs helper state to (re)use, NULL if not available
+ * @param management_only if we should NOT run 'finish_keys_response()'
+ * because we only need the state for the /management/keys API
+ * @return NULL on error (i.e. failed to access database)
+ */
+static struct TEH_KeyStateHandle *
+build_key_state (struct HelperState *hs,
+ bool management_only)
+{
+ struct TEH_KeyStateHandle *ksh;
+ enum GNUNET_DB_QueryStatus qs;
+
+ ksh = GNUNET_new (struct TEH_KeyStateHandle);
+ ksh->signature_expires = GNUNET_TIME_UNIT_FOREVER_TS;
+ ksh->reload_time = GNUNET_TIME_timestamp_get ();
+ /* We must use the key_generation from when we STARTED the process! */
+ ksh->key_generation = key_generation;
+ if (NULL == hs)
+ {
+ ksh->helpers = GNUNET_new (struct HelperState);
+ if (GNUNET_OK !=
+ setup_key_helpers (ksh->helpers))
+ {
+ GNUNET_free (ksh->helpers);
+ GNUNET_assert (NULL == ksh->management_keys_reply);
+ GNUNET_free (ksh);
+ return NULL;
+ }
+ }
+ else
+ {
+ ksh->helpers = hs;
+ }
+ ksh->denomkey_map = GNUNET_CONTAINER_multihashmap_create (1024,
+ true);
+ ksh->signkey_map = GNUNET_CONTAINER_multipeermap_create (32,
+ false /* MUST be false! */);
+ ksh->auditors = json_array ();
+ GNUNET_assert (NULL != ksh->auditors);
+ /* NOTE: fetches master-signed signkeys, but ALSO those that were revoked! */
+ GNUNET_break (GNUNET_OK ==
+ TEH_plugin->preflight (TEH_plugin->cls));
+ if (NULL != ksh->global_fees)
+ json_decref (ksh->global_fees);
+ ksh->global_fees = json_array ();
+ qs = TEH_plugin->get_global_fees (TEH_plugin->cls,
+ &global_fee_info_cb,
+ ksh);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Loading global fees from DB: %d\n",
+ qs);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+ destroy_key_state (ksh,
+ true);
+ return NULL;
+ }
+ qs = TEH_plugin->iterate_denominations (TEH_plugin->cls,
+ &denomination_info_cb,
+ ksh);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+ destroy_key_state (ksh,
+ true);
+ return NULL;
+ }
+ /* NOTE: ONLY fetches non-revoked AND master-signed signkeys! */
+ qs = TEH_plugin->iterate_active_signkeys (TEH_plugin->cls,
+ &signkey_info_cb,
+ ksh);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ destroy_key_state (ksh,
+ true);
+ return NULL;
+ }
+ qs = TEH_plugin->iterate_auditor_denominations (TEH_plugin->cls,
+ &auditor_denom_cb,
+ ksh);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ destroy_key_state (ksh,
+ true);
+ return NULL;
+ }
+ qs = TEH_plugin->iterate_active_auditors (TEH_plugin->cls,
+ &auditor_info_cb,
+ ksh);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ destroy_key_state (ksh,
+ true);
+ return NULL;
+ }
+
+ if (management_only)
+ {
+ ksh->management_only = true;
+ return ksh;
+ }
+
+ if (GNUNET_OK !=
+ finish_keys_response (ksh))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Could not finish /keys response (required data not configured yet)\n");
+ destroy_key_state (ksh,
+ true);
+ return NULL;
+ }
+
+ return ksh;
+}
+
+
+void
+TEH_keys_update_states ()
+{
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
+ };
+
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &es,
+ NULL,
+ 0);
+ key_generation++;
+ TEH_resume_keys_requests (false);
+}
+
+
+static struct TEH_KeyStateHandle *
+keys_get_state (bool management_only)
+{
+ struct TEH_KeyStateHandle *old_ksh;
+ struct TEH_KeyStateHandle *ksh;
+
+ old_ksh = key_state;
+ if (NULL == old_ksh)
+ {
+ ksh = build_key_state (NULL,
+ management_only);
+ if (NULL == ksh)
+ return NULL;
+ key_state = ksh;
+ return ksh;
+ }
+ if ( (old_ksh->key_generation < key_generation) ||
+ (GNUNET_TIME_absolute_is_past (old_ksh->signature_expires.abs_time)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Rebuilding /keys, generation upgrade from %llu to %llu\n",
+ (unsigned long long) old_ksh->key_generation,
+ (unsigned long long) key_generation);
+ ksh = build_key_state (old_ksh->helpers,
+ management_only);
+ key_state = ksh;
+ old_ksh->helpers = NULL;
+ destroy_key_state (old_ksh,
+ false);
+ return ksh;
+ }
+ sync_key_helpers (old_ksh->helpers);
+ return old_ksh;
+}
+
+
+struct TEH_KeyStateHandle *
+TEH_keys_get_state_for_management_only (void)
+{
+ return keys_get_state (true);
+}
+
+
+struct TEH_KeyStateHandle *
+TEH_keys_get_state (void)
+{
+ struct TEH_KeyStateHandle *ksh;
+
+ ksh = keys_get_state (false);
+ if (NULL == ksh)
+ return NULL;
+
+ if (ksh->management_only)
+ {
+ if (GNUNET_OK !=
+ finish_keys_response (ksh))
+ return NULL;
+ }
+
+ return ksh;
+}
+
+
+const struct TEH_GlobalFee *
+TEH_keys_global_fee_by_time (
+ struct TEH_KeyStateHandle *ksh,
+ struct GNUNET_TIME_Timestamp ts)
+{
+ for (const struct TEH_GlobalFee *gf = ksh->gf_head;
+ NULL != gf;
+ gf = gf->next)
+ {
+ if (GNUNET_TIME_timestamp_cmp (ts,
+ >=,
+ gf->start_date) &&
+ GNUNET_TIME_timestamp_cmp (ts,
+ <,
+ gf->end_date))
+ return gf;
+ }
+ return NULL;
+}
+
+
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_hash (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct MHD_Connection *conn,
+ MHD_RESULT *mret)
+{
+ struct TEH_KeyStateHandle *ksh;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ *mret = TALER_MHD_reply_with_error (conn,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return NULL;
+ }
+
+ return TEH_keys_denomination_by_hash_from_state (ksh,
+ h_denom_pub,
+ conn,
+ mret);
+}
+
+
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_hash_from_state (
+ const struct TEH_KeyStateHandle *ksh,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct MHD_Connection *conn,
+ MHD_RESULT *mret)
+{
+ struct TEH_DenominationKey *dk;
+
+ dk = GNUNET_CONTAINER_multihashmap_get (ksh->denomkey_map,
+ &h_denom_pub->hash);
+ if (NULL == dk)
+ {
+ if (NULL == conn)
+ return NULL;
+ *mret = TEH_RESPONSE_reply_unknown_denom_pub_hash (conn,
+ h_denom_pub);
+ return NULL;
+ }
+ return dk;
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_denomination_batch_sign (
+ unsigned int csds_length,
+ const struct TEH_CoinSignData csds[static csds_length],
+ bool for_melt,
+ struct TALER_BlindedDenominationSignature bss[static csds_length])
+{
+ struct TEH_KeyStateHandle *ksh;
+ struct HelperDenomination *hd;
+ struct TALER_CRYPTO_RsaSignRequest rsrs[csds_length];
+ struct TALER_CRYPTO_CsSignRequest csrs[csds_length];
+ struct TALER_BlindedDenominationSignature rs[csds_length];
+ struct TALER_BlindedDenominationSignature cs[csds_length];
+ unsigned int rsrs_pos = 0;
+ unsigned int csrs_pos = 0;
+ enum TALER_ErrorCode ec;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+ for (unsigned int i = 0; i<csds_length; i++)
+ {
+ const struct TALER_DenominationHashP *h_denom_pub = csds[i].h_denom_pub;
+ const struct TALER_BlindedPlanchet *bp = csds[i].bp;
+
+ hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+ &h_denom_pub->hash);
+ if (NULL == hd)
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ if (bp->blinded_message->cipher !=
+ hd->denom_pub.bsign_pub_key->cipher)
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ switch (hd->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ rsrs[rsrs_pos].h_rsa = &hd->h_details.h_rsa;
+ rsrs[rsrs_pos].msg
+ = bp->blinded_message->details.rsa_blinded_message.blinded_msg;
+ rsrs[rsrs_pos].msg_size
+ = bp->blinded_message->details.rsa_blinded_message.blinded_msg_size;
+ rsrs_pos++;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ csrs[csrs_pos].h_cs = &hd->h_details.h_cs;
+ csrs[csrs_pos].blinded_planchet
+ = &bp->blinded_message->details.cs_blinded_message;
+ csrs_pos++;
+ break;
+ default:
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ }
+
+ if ( (0 != csrs_pos) &&
+ (0 != rsrs_pos) )
+ {
+ memset (rs,
+ 0,
+ sizeof (rs));
+ memset (cs,
+ 0,
+ sizeof (cs));
+ }
+ ec = TALER_EC_NONE;
+ if (0 != csrs_pos)
+ {
+ ec = TALER_CRYPTO_helper_cs_batch_sign (
+ ksh->helpers->csdh,
+ csrs_pos,
+ csrs,
+ for_melt,
+ (0 == rsrs_pos) ? bss : cs);
+ if (TALER_EC_NONE != ec)
+ {
+ for (unsigned int i = 0; i<csrs_pos; i++)
+ TALER_blinded_denom_sig_free (&cs[i]);
+ return ec;
+ }
+ TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS] += csrs_pos;
+ }
+ if (0 != rsrs_pos)
+ {
+ ec = TALER_CRYPTO_helper_rsa_batch_sign (
+ ksh->helpers->rsadh,
+ rsrs_pos,
+ rsrs,
+ (0 == csrs_pos) ? bss : rs);
+ if (TALER_EC_NONE != ec)
+ {
+ for (unsigned int i = 0; i<csrs_pos; i++)
+ TALER_blinded_denom_sig_free (&cs[i]);
+ for (unsigned int i = 0; i<rsrs_pos; i++)
+ TALER_blinded_denom_sig_free (&rs[i]);
+ return ec;
+ }
+ TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA] += rsrs_pos;
+ }
+
+ if ( (0 != csrs_pos) &&
+ (0 != rsrs_pos) )
+ {
+ rsrs_pos = 0;
+ csrs_pos = 0;
+ for (unsigned int i = 0; i<csds_length; i++)
+ {
+ const struct TALER_BlindedPlanchet *bp = csds[i].bp;
+
+ switch (bp->blinded_message->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ bss[i] = rs[rsrs_pos++];
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ bss[i] = cs[csrs_pos++];
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ }
+ }
+ return TALER_EC_NONE;
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_r_pub (
+ const struct TEH_CsDeriveData *cdd,
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP *r_pub)
+{
+ const struct TALER_DenominationHashP *h_denom_pub = cdd->h_denom_pub;
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce = cdd->nonce;
+ struct TEH_KeyStateHandle *ksh;
+ struct HelperDenomination *hd;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+ }
+ hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+ &h_denom_pub->hash);
+ if (NULL == hd)
+ {
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ }
+ if (GNUNET_CRYPTO_BSA_CS !=
+ hd->denom_pub.bsign_pub_key->cipher)
+ {
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+
+ {
+ struct TALER_CRYPTO_CsDeriveRequest cdr = {
+ .h_cs = &hd->h_details.h_cs,
+ .nonce = nonce
+ };
+ return TALER_CRYPTO_helper_cs_r_derive (ksh->helpers->csdh,
+ &cdr,
+ for_melt,
+ r_pub);
+ }
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_batch_r_pub (
+ unsigned int cdds_length,
+ const struct TEH_CsDeriveData cdds[static cdds_length],
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static cdds_length])
+{
+ struct TEH_KeyStateHandle *ksh;
+ struct HelperDenomination *hd;
+ struct TALER_CRYPTO_CsDeriveRequest cdrs[cdds_length];
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+ }
+ for (unsigned int i = 0; i<cdds_length; i++)
+ {
+ const struct TALER_DenominationHashP *h_denom_pub = cdds[i].h_denom_pub;
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce = cdds[i].nonce;
+
+ hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+ &h_denom_pub->hash);
+ if (NULL == hd)
+ {
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ }
+ if (GNUNET_CRYPTO_BSA_CS !=
+ hd->denom_pub.bsign_pub_key->cipher)
+ {
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ cdrs[i].h_cs = &hd->h_details.h_cs;
+ cdrs[i].nonce = nonce;
+ }
+
+ return TALER_CRYPTO_helper_cs_r_batch_derive (ksh->helpers->csdh,
+ cdds_length,
+ cdrs,
+ for_melt,
+ r_pubs);
+}
+
+
+void
+TEH_keys_denomination_revoke (const struct TALER_DenominationHashP *h_denom_pub)
+{
+ struct TEH_KeyStateHandle *ksh;
+ struct HelperDenomination *hd;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+ &h_denom_pub->hash);
+ if (NULL == hd)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ switch (hd->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ TALER_CRYPTO_helper_rsa_revoke (ksh->helpers->rsadh,
+ &hd->h_details.h_rsa);
+ TEH_keys_update_states ();
+ return;
+ case GNUNET_CRYPTO_BSA_CS:
+ TALER_CRYPTO_helper_cs_revoke (ksh->helpers->csdh,
+ &hd->h_details.h_cs);
+ TEH_keys_update_states ();
+ return;
+ }
+ GNUNET_break (0);
+ return;
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_exchange_sign_ (
+ const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TEH_KeyStateHandle *ksh;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ /* This *can* happen if the exchange's crypto helper is not running
+ or had some bad error. */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Cannot sign request, no valid signing keys available.\n");
+ return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+ }
+ return TEH_keys_exchange_sign2_ (ksh,
+ purpose,
+ pub,
+ sig);
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_exchange_sign2_ (
+ void *cls,
+ const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ enum TALER_ErrorCode ec;
+
+ TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_EDDSA]++;
+ ec = TALER_CRYPTO_helper_esign_sign_ (ksh->helpers->esh,
+ purpose,
+ pub,
+ sig);
+ if (TALER_EC_NONE != ec)
+ return ec;
+ {
+ /* Here we check here that 'pub' is set to an exchange public key that is
+ actually signed by the master key! Otherwise, we happily continue to
+ use key material even if the offline signatures have not been made
+ yet! */
+ struct GNUNET_PeerIdentity pid;
+ struct SigningKey *sk;
+
+ pid.public_key = pub->eddsa_pub;
+ sk = GNUNET_CONTAINER_multipeermap_get (ksh->signkey_map,
+ &pid);
+ if (NULL == sk)
+ {
+ /* just to be safe, zero out the (valid) signature, as the key
+ should not or no longer be used */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Cannot sign, offline key signatures are missing!\n");
+ memset (sig,
+ 0,
+ sizeof (*sig));
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ }
+ }
+ return ec;
+}
+
+
+void
+TEH_keys_exchange_revoke (const struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+ struct TEH_KeyStateHandle *ksh;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ TALER_CRYPTO_helper_esign_revoke (ksh->helpers->esh,
+ exchange_pub);
+ TEH_keys_update_states ();
+}
+
+
+/**
+ * Comparator used for a binary search by cherry_pick_date for @a key in the
+ * `struct KeysResponseData` array. See libc's qsort() and bsearch() functions.
+ *
+ * @param key pointer to a `struct GNUNET_TIME_Timestamp`
+ * @param value pointer to a `struct KeysResponseData` array entry
+ * @return 0 if time matches, -1 if key is smaller, 1 if key is larger
+ */
+static int
+krd_search_comparator (const void *key,
+ const void *value)
+{
+ const struct GNUNET_TIME_Timestamp *kd = key;
+ const struct KeysResponseData *krd = value;
+
+ if (GNUNET_TIME_timestamp_cmp (*kd,
+ >,
+ krd->cherry_pick_date))
+ return -1;
+ if (GNUNET_TIME_timestamp_cmp (*kd,
+ <,
+ krd->cherry_pick_date))
+ return 1;
+ return 0;
+}
+
+
+MHD_RESULT
+TEH_keys_get_handler (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ struct GNUNET_TIME_Timestamp last_issue_date;
+ const char *etag;
+
+ etag = MHD_lookup_connection_value (rc->connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ (void) args;
+ {
+ const char *have_cherrypick;
+
+ have_cherrypick = MHD_lookup_connection_value (rc->connection,
+ MHD_GET_ARGUMENT_KIND,
+ "last_issue_date");
+ if (NULL != have_cherrypick)
+ {
+ unsigned long long cherrypickn;
+
+ if (1 !=
+ sscanf (have_cherrypick,
+ "%llu",
+ &cherrypickn))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ have_cherrypick);
+ }
+ /* The following multiplication may overflow; but this should not really
+ be a problem, as giving back 'older' data than what the client asks for
+ (given that the client asks for data in the distant future) is not
+ problematic */
+ last_issue_date = GNUNET_TIME_timestamp_from_s (cherrypickn);
+ }
+ else
+ {
+ last_issue_date = GNUNET_TIME_UNIT_ZERO_TS;
+ }
+ }
+
+ {
+ struct TEH_KeyStateHandle *ksh;
+ const struct KeysResponseData *krd;
+
+ ksh = TEH_keys_get_state ();
+ if ( (NULL == ksh) ||
+ (0 == ksh->krd_array_length) )
+ {
+ if ( ( (SKR_LIMIT == skr_size) &&
+ (rc->connection == skr_connection) ) ||
+ TEH_suicide)
+ {
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ TEH_suicide
+ ? "server terminating"
+ : "too many connections suspended waiting on /keys");
+ }
+ return suspend_request (rc->connection);
+ }
+ krd = bsearch (&last_issue_date,
+ ksh->krd_array,
+ ksh->krd_array_length,
+ sizeof (struct KeysResponseData),
+ &krd_search_comparator);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Filtering /keys by cherry pick date %s found entry %u/%u\n",
+ GNUNET_TIME_timestamp2s (last_issue_date),
+ (unsigned int) (krd - ksh->krd_array),
+ ksh->krd_array_length);
+ if ( (NULL == krd) &&
+ (ksh->krd_array_length > 0) )
+ {
+ if (! GNUNET_TIME_absolute_is_zero (last_issue_date.abs_time))
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client provided invalid cherry picking timestamp %s, returning full response\n",
+ GNUNET_TIME_timestamp2s (last_issue_date));
+ krd = &ksh->krd_array[ksh->krd_array_length - 1];
+ }
+ if (NULL == krd)
+ {
+ /* Likely keys not ready *yet*.
+ Wait until they are. */
+ return suspend_request (rc->connection);
+ }
+ if ( (NULL != etag) &&
+ (0 == strcmp (etag,
+ krd->etag)) )
+ return TEH_RESPONSE_reply_not_modified (rc->connection,
+ krd->etag,
+ &setup_general_response_headers,
+ ksh);
+
+ return MHD_queue_response (rc->connection,
+ MHD_HTTP_OK,
+ (MHD_YES ==
+ TALER_MHD_can_compress (rc->connection))
+ ? krd->response_compressed
+ : krd->response_uncompressed);
+ }
+}
+
+
+/**
+ * Load extension data, like fees, expiration times (!) and age restriction
+ * flags for the denomination type configured in section @a section_name.
+ * Before calling this function, the `start` and `validity_duration` times must
+ * already be initialized in @a meta.
+ *
+ * @param section_name section in the configuration to use
+ * @param[in,out] meta denomination type data to complete
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+load_extension_data (const char *section_name,
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta)
+{
+ struct GNUNET_TIME_Relative deposit_duration;
+ struct GNUNET_TIME_Relative legal_duration;
+
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (meta->start.abs_time)); /* caller bug */
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+ section_name,
+ "DURATION_SPEND",
+ &deposit_duration))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section_name,
+ "DURATION_SPEND");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+ section_name,
+ "DURATION_LEGAL",
+ &legal_duration))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section_name,
+ "DURATION_LEGAL");
+ return GNUNET_SYSERR;
+ }
+ meta->expire_deposit
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (meta->expire_withdraw.abs_time,
+ deposit_duration));
+ meta->expire_legal = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (meta->expire_deposit.abs_time,
+ legal_duration));
+ if (GNUNET_OK !=
+ TALER_config_get_amount (TEH_cfg,
+ section_name,
+ "VALUE",
+ &meta->value))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "Need amount for option `%s' in section `%s'\n",
+ "VALUE",
+ section_name);
+ return GNUNET_SYSERR;
+ }
+ if (0 != strcasecmp (TEH_currency,
+ meta->value.currency))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Need denomination value in section `%s' to use currency `%s'\n",
+ section_name,
+ TEH_currency);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_denom_fees (TEH_cfg,
+ TEH_currency,
+ section_name,
+ &meta->fees))
+ return GNUNET_SYSERR;
+ meta->age_mask = load_age_mask (section_name);
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_keys_load_fees (struct TEH_KeyStateHandle *ksh,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_DenominationPublicKey *denom_pub,
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta)
+{
+ struct HelperDenomination *hd;
+ enum GNUNET_GenericReturnValue ok;
+
+ hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+ &h_denom_pub->hash);
+ if (NULL == hd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Denomination %s not known\n",
+ GNUNET_h2s (&h_denom_pub->hash));
+ return GNUNET_NO;
+ }
+ meta->start = hd->start_time;
+ meta->expire_withdraw = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (meta->start.abs_time,
+ hd->validity_duration));
+ ok = load_extension_data (hd->section_name,
+ meta);
+ if (GNUNET_OK == ok)
+ {
+ GNUNET_assert (GNUNET_CRYPTO_BSA_INVALID !=
+ hd->denom_pub.bsign_pub_key->cipher);
+ TALER_denom_pub_copy (denom_pub,
+ &hd->denom_pub);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No fees for `%s', voiding key\n",
+ hd->section_name);
+ memset (denom_pub,
+ 0,
+ sizeof (*denom_pub));
+ }
+ return ok;
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_EXCHANGEDB_SignkeyMetaData *meta)
+{
+ struct TEH_KeyStateHandle *ksh;
+ struct HelperSignkey *hsk;
+ struct GNUNET_PeerIdentity pid;
+
+ ksh = TEH_keys_get_state_for_management_only ();
+ if (NULL == ksh)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ pid.public_key = exchange_pub->eddsa_pub;
+ hsk = GNUNET_CONTAINER_multipeermap_get (ksh->helpers->esign_keys,
+ &pid);
+ if (NULL == hsk)
+ {
+ GNUNET_break (0);
+ return GNUNET_NO;
+ }
+ meta->start = hsk->start_time;
+
+ meta->expire_sign = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (meta->start.abs_time,
+ hsk->validity_duration));
+ meta->expire_legal = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (meta->expire_sign.abs_time,
+ signkey_legal_duration));
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #add_future_denomkey_cb and #add_future_signkey_cb.
+ */
+struct FutureBuilderContext
+{
+ /**
+ * Our key state.
+ */
+ struct TEH_KeyStateHandle *ksh;
+
+ /**
+ * Array of denomination keys.
+ */
+ json_t *denoms;
+
+ /**
+ * Array of signing keys.
+ */
+ json_t *signkeys;
+
+};
+
+
+/**
+ * Function called on all of our current and future denomination keys
+ * known to the helper process. Filters out those that are current
+ * and adds the remaining denomination keys (with their configuration
+ * data) to the JSON array.
+ *
+ * @param cls the `struct FutureBuilderContext *`
+ * @param h_denom_pub hash of the denomination public key
+ * @param value a `struct HelperDenomination`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_future_denomkey_cb (void *cls,
+ const struct GNUNET_HashCode *h_denom_pub,
+ void *value)
+{
+ struct FutureBuilderContext *fbc = cls;
+ struct HelperDenomination *hd = value;
+ struct TEH_DenominationKey *dk;
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0};
+
+ dk = GNUNET_CONTAINER_multihashmap_get (fbc->ksh->denomkey_map,
+ h_denom_pub);
+ if (NULL != dk)
+ return GNUNET_OK; /* skip: this key is already active! */
+ if (GNUNET_TIME_relative_is_zero (hd->validity_duration))
+ return GNUNET_OK; /* this key already expired! */
+ meta.start = hd->start_time;
+ meta.expire_withdraw = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (meta.start.abs_time,
+ hd->validity_duration));
+ if (GNUNET_OK !=
+ load_extension_data (hd->section_name,
+ &meta))
+ {
+ /* Woops, couldn't determine fee structure!? */
+ return GNUNET_OK;
+ }
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ fbc->denoms,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("value",
+ &meta.value),
+ GNUNET_JSON_pack_timestamp ("stamp_start",
+ meta.start),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
+ meta.expire_withdraw),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
+ meta.expire_deposit),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
+ meta.expire_legal),
+ TALER_JSON_pack_denom_pub ("denom_pub",
+ &hd->denom_pub),
+ TALER_JSON_PACK_DENOM_FEES ("fee",
+ &meta.fees),
+ GNUNET_JSON_pack_data_auto ("denom_secmod_sig",
+ &hd->sm_sig),
+ GNUNET_JSON_pack_string ("section_name",
+ hd->section_name))));
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called on all of our current and future exchange signing keys
+ * known to the helper process. Filters out those that are current
+ * and adds the remaining signing keys (with their configuration
+ * data) to the JSON array.
+ *
+ * @param cls the `struct FutureBuilderContext *`
+ * @param pid actually the exchange public key (type disguised)
+ * @param value a `struct HelperDenomination`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_future_signkey_cb (void *cls,
+ const struct GNUNET_PeerIdentity *pid,
+ void *value)
+{
+ struct FutureBuilderContext *fbc = cls;
+ struct HelperSignkey *hsk = value;
+ struct SigningKey *sk;
+ struct GNUNET_TIME_Timestamp stamp_expire;
+ struct GNUNET_TIME_Timestamp legal_end;
+
+ sk = GNUNET_CONTAINER_multipeermap_get (fbc->ksh->signkey_map,
+ pid);
+ if (NULL != sk)
+ return GNUNET_OK; /* skip: this key is already active */
+ if (GNUNET_TIME_relative_is_zero (hsk->validity_duration))
+ return GNUNET_OK; /* this key already expired! */
+ stamp_expire = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (hsk->start_time.abs_time,
+ hsk->validity_duration));
+ legal_end = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (stamp_expire.abs_time,
+ signkey_legal_duration));
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ fbc->signkeys,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("key",
+ &hsk->exchange_pub),
+ GNUNET_JSON_pack_timestamp ("stamp_start",
+ hsk->start_time),
+ GNUNET_JSON_pack_timestamp ("stamp_expire",
+ stamp_expire),
+ GNUNET_JSON_pack_timestamp ("stamp_end",
+ legal_end),
+ GNUNET_JSON_pack_data_auto ("signkey_secmod_sig",
+ &hsk->sm_sig))));
+ return GNUNET_OK;
+}
+
+
+MHD_RESULT
+TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
+ struct MHD_Connection *connection)
+{
+ struct TEH_KeyStateHandle *ksh;
+ json_t *reply;
+
+ (void) rh;
+ ksh = TEH_keys_get_state_for_management_only ();
+ if (NULL == ksh)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ "no key state");
+ }
+ sync_key_helpers (ksh->helpers);
+ if (NULL == ksh->management_keys_reply)
+ {
+ struct FutureBuilderContext fbc = {
+ .ksh = ksh,
+ .denoms = json_array (),
+ .signkeys = json_array ()
+ };
+
+ 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,
+ NULL);
+ }
+ if (GNUNET_is_zero (&esign_sm_pub))
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_GATEWAY,
+ TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE,
+ NULL);
+ }
+ GNUNET_assert (NULL != fbc.denoms);
+ GNUNET_assert (NULL != fbc.signkeys);
+ GNUNET_CONTAINER_multihashmap_iterate (ksh->helpers->denom_keys,
+ &add_future_denomkey_cb,
+ &fbc);
+ GNUNET_CONTAINER_multipeermap_iterate (ksh->helpers->esign_keys,
+ &add_future_signkey_cb,
+ &fbc);
+ reply = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("future_denoms",
+ fbc.denoms),
+ GNUNET_JSON_pack_array_steal ("future_signkeys",
+ fbc.signkeys),
+ GNUNET_JSON_pack_data_auto ("master_pub",
+ &TEH_master_public_key),
+ GNUNET_JSON_pack_data_auto ("denom_secmod_public_key",
+ &denom_rsa_sm_pub),
+ GNUNET_JSON_pack_data_auto ("denom_secmod_cs_public_key",
+ &denom_cs_sm_pub),
+ GNUNET_JSON_pack_data_auto ("signkey_secmod_public_key",
+ &esign_sm_pub));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning GET /management/keys response:\n");
+ if (NULL == reply)
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+ NULL);
+ GNUNET_assert (NULL == ksh->management_keys_reply);
+ ksh->management_keys_reply = reply;
+ }
+ else
+ {
+ reply = ksh->management_keys_reply;
+ }
+ return TALER_MHD_reply_json (connection,
+ reply,
+ MHD_HTTP_OK);
+}
+
+
+/* end of taler-exchange-httpd_keys.c */
diff --git a/src/exchange/taler-exchange-httpd_keys.h b/src/exchange/taler-exchange-httpd_keys.h
new file mode 100644
index 000000000..e526385ff
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_keys.h
@@ -0,0 +1,587 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_keys.h
+ * @brief management of our various keys
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+#ifndef TALER_EXCHANGE_HTTPD_KEYS_H
+#define TALER_EXCHANGE_HTTPD_KEYS_H
+
+/**
+ * Signatures of an auditor over a denomination key of this exchange.
+ */
+struct TEH_AuditorSignature;
+
+
+/**
+ * @brief All information about a denomination key (which is used to
+ * sign coins into existence).
+ */
+struct TEH_DenominationKey
+{
+
+ /**
+ * Decoded denomination public key (the hash of it is in
+ * @e issue, but we sometimes need the full public key as well).
+ */
+ struct TALER_DenominationPublicKey denom_pub;
+
+ /**
+ * Hash code of the denomination public key.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Meta data about the type of the denomination, such as fees and validity
+ * periods.
+ */
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
+
+ /**
+ * The long-term offline master key's signature for this denomination.
+ * Signs over @e h_denom_pub and @e meta.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * We store the auditor signatures for this denomination in a DLL.
+ */
+ struct TEH_AuditorSignature *as_head;
+
+ /**
+ * We store the auditor signatures for this denomination in a DLL.
+ */
+ struct TEH_AuditorSignature *as_tail;
+
+ /**
+ * Set to 'true' if this denomination has been revoked and recoup is
+ * thus supported right now.
+ */
+ bool recoup_possible;
+
+};
+
+
+/**
+ * Set of global fees (and options) for a time range.
+ */
+struct TEH_GlobalFee
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct TEH_GlobalFee *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TEH_GlobalFee *prev;
+
+ /**
+ * Beginning of the validity period (inclusive).
+ */
+ struct GNUNET_TIME_Timestamp start_date;
+
+ /**
+ * End of the validity period (exclusive).
+ */
+ struct GNUNET_TIME_Timestamp end_date;
+
+ /**
+ * How long do unmerged purses stay around at most?
+ */
+ struct GNUNET_TIME_Relative purse_timeout;
+
+ /**
+ * What is the longest history we return?
+ */
+ struct GNUNET_TIME_Relative history_expiration;
+
+ /**
+ * Signature affirming these details.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Fee structure for operations that do not depend
+ * on a denomination or wire method.
+ */
+ struct TALER_GlobalFeeSet fees;
+
+ /**
+ * Number of free purses per account.
+ */
+ uint32_t purse_account_limit;
+};
+
+
+/**
+ * Snapshot of the (coin and signing) keys (including private keys) of
+ * the exchange. There can be multiple instances of this struct, as it is
+ * reference counted and only destroyed once the last user is done
+ * with it. The current instance is acquired using
+ * #TEH_KS_acquire(). Using this function increases the
+ * reference count. The contents of this structure (except for the
+ * reference counter) should be considered READ-ONLY until it is
+ * ultimately destroyed (as there can be many concurrent users).
+ */
+struct TEH_KeyStateHandle;
+
+
+/**
+ * Run internal invariant checks. For debugging.
+ */
+void
+TEH_check_invariants (void);
+
+/**
+ * Clean up wire subsystem.
+ */
+void
+TEH_wire_done (void);
+
+
+/**
+ * Look up wire fee structure by @a ts.
+ *
+ * @param ts timestamp to lookup wire fees at
+ * @param method wire method to lookup fees for
+ * @return the wire fee details, or
+ * NULL if none are configured for @a ts and @a method
+ */
+const struct TALER_WireFeeSet *
+TEH_wire_fees_by_time (
+ struct GNUNET_TIME_Timestamp ts,
+ const char *method);
+
+
+/**
+ * Initialize wire subsystem.
+ *
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_wire_init (void);
+
+
+/**
+ * Something changed in the database. Rebuild the wire replies. This function
+ * should be called if the exchange learns about a new signature from our
+ * master key.
+ *
+ * (We do not do so immediately, but merely signal to all threads that they
+ * need to rebuild their wire state upon the next call to
+ * #TEH_keys_get_state()).
+ */
+void
+TEH_wire_update_state (void);
+
+
+/**
+ * Return the current key state for this thread. Possibly re-builds the key
+ * state if we have reason to believe that something changed.
+ *
+ * The result is ONLY valid until the next call to
+ * #TEH_keys_denomination_by_hash() or #TEH_keys_get_state()
+ * or #TEH_keys_exchange_sign().
+ *
+ * @return NULL on error
+ */
+struct TEH_KeyStateHandle *
+TEH_keys_get_state (void);
+
+/**
+ * Obtain the key state if we should NOT run finish_keys_response() because we
+ * only need the state for the /management/keys API
+ */
+struct TEH_KeyStateHandle *
+TEH_keys_get_state_for_management_only (void);
+
+/**
+ * Something changed in the database. Rebuild all key states. This function
+ * should be called if the exchange learns about a new signature from an
+ * auditor or our master key.
+ *
+ * (We do not do so immediately, but merely signal to all threads that they
+ * need to rebuild their key state upon the next call to
+ * #TEH_keys_get_state()).
+ */
+void
+TEH_keys_update_states (void);
+
+
+/**
+ * Look up global fee structure by @a ts.
+ *
+ * @param ksh key state state to look in
+ * @param ts timestamp to lookup global fees at
+ * @return the global fee details, or
+ * NULL if none are configured for @a ts
+ */
+const struct TEH_GlobalFee *
+TEH_keys_global_fee_by_time (
+ struct TEH_KeyStateHandle *ksh,
+ struct GNUNET_TIME_Timestamp ts);
+
+
+/**
+ * Look up the issue for a denom public key. Note that the result
+ * must only be used in this thread and only until another key or
+ * key state is resolved.
+ *
+ * @param h_denom_pub hash of denomination public key
+ * @param[in,out] conn used to return status message if NULL is returned
+ * @param[out] mret set to the MHD status if NULL is returned
+ * @return the denomination key issue,
+ * or NULL if @a h_denom_pub could not be found
+ */
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_hash (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct MHD_Connection *conn,
+ MHD_RESULT *mret);
+
+
+/**
+ * Look up the issue for a denom public key using a given @a ksh. This allows
+ * requesting multiple denominations with the same @a ksh which thus will
+ * remain valid until the next call to #TEH_keys_denomination_by_hash() or
+ * #TEH_keys_get_state() or #TEH_keys_exchange_sign().
+ *
+ * @param ksh key state state to look in
+ * @param h_denom_pub hash of denomination public key
+ * @param[in,out] conn connection used to return status message if NULL is returned
+ * @param[out] mret set to the MHD status if NULL is returned
+ * @return the denomination key issue,
+ * or NULL if @a h_denom_pub could not be found
+ */
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_hash_from_state (
+ const struct TEH_KeyStateHandle *ksh,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct MHD_Connection *conn,
+ MHD_RESULT *mret);
+
+/**
+ * Information needed to create a blind signature.
+ */
+struct TEH_CoinSignData
+{
+ /**
+ * Hash of key to sign with.
+ */
+ const struct TALER_DenominationHashP *h_denom_pub;
+
+ /**
+ * Blinded planchet to sign over.
+ */
+ const struct TALER_BlindedPlanchet *bp;
+};
+
+
+/**
+ * Request to sign @a csds.
+ *
+ * @param csds array with data to blindly sign (and keys to sign with)
+ * @param csds_length length of @a csds array
+ * @param for_melt true if this is for a melt operation
+ * @param[out] bss array set to the blind signature on success; must be of length @a csds_length
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_denomination_batch_sign (
+ unsigned int csds_length,
+ const struct TEH_CoinSignData csds[static csds_length],
+ bool for_melt,
+ struct TALER_BlindedDenominationSignature bss[static csds_length]);
+
+
+/**
+ * Information needed to derive the CS r_pub.
+ */
+struct TEH_CsDeriveData
+{
+ /**
+ * Hash of key to sign with.
+ */
+ const struct TALER_DenominationHashP *h_denom_pub;
+
+ /**
+ * Nonce to use.
+ */
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce;
+};
+
+
+/**
+ * Request to derive CS @a r_pub using the denomination and nonce from @a cdd.
+ *
+ * @param cdd data to compute @a r_pub from
+ * @param for_melt true if this is for a melt operation
+ * @param[out] r_pub where to write the result
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_r_pub (
+ const struct TEH_CsDeriveData *cdd,
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP *r_pub);
+
+
+/**
+ * Request to derive a bunch of CS @a r_pubs using the
+ * denominations and nonces from @a cdds.
+ *
+ * @param cdds array to compute @a r_pubs from
+ * @param cdds_length length of the @a cdds array
+ * @param for_melt true if this is for a melt operation
+ * @param[out] r_pubs array where to write the result; must be of length @a cdds_length
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_batch_r_pub (
+ unsigned int cdds_length,
+ const struct TEH_CsDeriveData cdds[static cdds_length],
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static cdds_length]);
+
+
+/**
+ * Revoke the public key associated with @a h_denom_pub.
+ * This function should be called AFTER the database was
+ * updated, as it also triggers #TEH_keys_update_states().
+ *
+ * Note that the actual revocation happens asynchronously and
+ * may thus fail silently. To verify that the revocation succeeded,
+ * clients must watch for the associated change to the key state.
+ *
+ * @param h_denom_pub hash of the public key to revoke
+ */
+void
+TEH_keys_denomination_revoke (
+ const struct TALER_DenominationHashP *h_denom_pub);
+
+
+/**
+ * Fully clean up keys subsystem.
+ */
+void
+TEH_keys_finished (void);
+
+
+/**
+ * Resumes all suspended /keys requests, we may now have key material
+ * (or are shutting down).
+ *
+ * @param do_shutdown are we shutting down?
+ */
+void
+TEH_resume_keys_requests (bool do_shutdown);
+
+
+/**
+ * Sign the message in @a purpose with the exchange's signing key.
+ *
+ * The @a purpose data is the beginning of the data of which the signature is
+ * to be created. The `size` field in @a purpose must correctly indicate the
+ * number of bytes of the data structure, including its header. Use
+ * #TEH_keys_exchange_sign() instead of calling this function directly!
+ *
+ * @param purpose the message to sign
+ * @param[out] pub set to the current public signing key of the exchange
+ * @param[out] sig signature over purpose using current signing key
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_exchange_sign_ (
+ const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Sign the message in @a purpose with the exchange's signing key.
+ *
+ * The @a purpose data is the beginning of the data of which the signature is
+ * to be created. The `size` field in @a purpose must correctly indicate the
+ * number of bytes of the data structure, including its header. Use
+ * #TEH_keys_exchange_sign() instead of calling this function directly!
+ *
+ * @param cls key state state to look in
+ * @param purpose the message to sign
+ * @param[out] pub set to the current public signing key of the exchange
+ * @param[out] sig signature over purpose using current signing key
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_exchange_sign2_ (
+ void *cls,
+ const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * @ingroup crypto
+ * @brief EdDSA sign a given block.
+ *
+ * The @a ps data must be a fixed-size struct for which the signature is to be
+ * created. The `size` field in @a ps->purpose must correctly indicate the
+ * number of bytes of the data structure, including its header.
+ *
+ * @param ps packed struct with what to sign, MUST begin with a purpose
+ * @param[out] pub where to store the public key to use for the signing
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+#define TEH_keys_exchange_sign(ps,pub,sig) \
+ ({ \
+ /* check size is set correctly */ \
+ GNUNET_assert (htonl ((ps)->purpose.size) == \
+ sizeof (*ps)); \
+ /* check 'ps' begins with the purpose */ \
+ GNUNET_static_assert (((void*) (ps)) == \
+ ((void*) &(ps)->purpose)); \
+ TEH_keys_exchange_sign_ (&(ps)->purpose, \
+ pub, \
+ sig); \
+ })
+
+
+/**
+ * @ingroup crypto
+ * @brief EdDSA sign a given block.
+ *
+ * The @a ps data must be a fixed-size struct for which the signature is to be
+ * created. The `size` field in @a ps->purpose must correctly indicate the
+ * number of bytes of the data structure, including its header.
+ *
+ * This allows requesting multiple denominations with the same @a ksh which
+ * thus will remain valid until the next call to
+ * #TEH_keys_denomination_by_hash() or #TEH_keys_get_state() or
+ * #TEH_keys_exchange_sign().
+ *
+ * @param ksh key state to use
+ * @param ps packed struct with what to sign, MUST begin with a purpose
+ * @param[out] pub where to store the public key to use for the signing
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+#define TEH_keys_exchange_sign2(ksh,ps,pub,sig) \
+ ({ \
+ /* check size is set correctly */ \
+ GNUNET_assert (htonl ((ps)->purpose.size) == \
+ sizeof (*ps)); \
+ /* check 'ps' begins with the purpose */ \
+ GNUNET_static_assert (((void*) (ps)) == \
+ ((void*) &(ps)->purpose)); \
+ TEH_keys_exchange_sign2_ (ksh, \
+ &(ps)->purpose, \
+ pub, \
+ sig); \
+ })
+
+
+/**
+ * Revoke the given exchange's signing key.
+ * This function should be called AFTER the database was
+ * updated, as it also triggers #TEH_keys_update_states().
+ *
+ * Note that the actual revocation happens asynchronously and
+ * may thus fail silently. To verify that the revocation succeeded,
+ * clients must watch for the associated change to the key state.
+ *
+ * @param exchange_pub key to revoke
+ */
+void
+TEH_keys_exchange_revoke (const struct TALER_ExchangePublicKeyP *exchange_pub);
+
+
+/**
+ * Function to call to handle requests to "/keys" by sending
+ * back our current key material.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be empty for this function)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_keys_get_handler (struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+
+/**
+ * Function to call to handle requests to "/management/keys" by sending
+ * back our future key material.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
+ struct MHD_Connection *connection);
+
+
+/**
+ * Load fees and expiration times (!) for the denomination type configured for
+ * the denomination matching @a h_denom_pub.
+ *
+ * @param ksh key state to load fees from
+ * @param h_denom_pub hash of the denomination public key
+ * to use to derive the section name of the configuration to use
+ * @param[out] denom_pub set to the denomination public key (to be freed by caller!)
+ * @param[out] meta denomination type data to complete
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if @a h_denom_pub is not known
+ * #GNUNET_SYSERR on hard errors
+ */
+enum GNUNET_GenericReturnValue
+TEH_keys_load_fees (struct TEH_KeyStateHandle *ksh,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_DenominationPublicKey *denom_pub,
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta);
+
+
+/**
+ * Load expiration times for the given onling signing key.
+ *
+ * @param exchange_pub the online signing key
+ * @param[out] meta set to meta data about the key
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_EXCHANGEDB_SignkeyMetaData *meta);
+
+
+/**
+ * Initialize keys subsystem.
+ *
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_keys_init (void);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_keystate.c b/src/exchange/taler-exchange-httpd_keystate.c
deleted file mode 100644
index 1c56b7a0f..000000000
--- a/src/exchange/taler-exchange-httpd_keystate.c
+++ /dev/null
@@ -1,2608 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014--2019 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_keystate.c
- * @brief management of our coin signing keys
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <pthread.h>
-#include "taler_json_lib.h"
-#include "taler_mhd_lib.h"
-#include "taler-exchange-httpd_keystate.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler_exchangedb_plugin.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!
- */
-#define EXCHANGE_PROTOCOL_VERSION "7:0:0"
-
-
-/**
- * Signatures of an auditor over a denomination key of this exchange.
- */
-struct AuditorSignature
-{
- /**
- * We store the signatures in a DLL.
- */
- struct AuditorSignature *prev;
-
- /**
- * We store the signatures in a DLL.
- */
- struct AuditorSignature *next;
-
- /**
- * A signature from the auditor.
- */
- struct TALER_AuditorSignatureP asig;
-
- /**
- * Public key of the auditor.
- */
- struct TALER_AuditorPublicKeyP apub;
-
- /**
- * URL of the auditor. Allocated at the end of this struct.
- */
- const char *auditor_url;
-
-};
-
-
-/**
- * Entry in sorted array of denomination keys. Sorted by starting
- * "start" time (validity period) of the `struct
- * TALER_DenominationKeyValidityPS`.
- */
-struct DenominationKeyEntry
-{
-
- /**
- * Reference to the public key.
- * (Must also be in the `denomkey_map`).
- */
- const struct TALER_EXCHANGEDB_DenominationKey *dki;
-
- /**
- * Head of DLL of signatures for this @e dki.
- */
- struct AuditorSignature *as_head;
-
- /**
- * Tail of DLL of signatures for this @e dki.
- */
- struct AuditorSignature *as_tail;
-
- /**
- * Hash of the public denomination key.
- */
- struct GNUNET_HashCode denom_key_hash;
-
-};
-
-
-/**
- * Entry in (sorted) array with possible pre-build responses for /keys.
- * We keep pre-build responses for the various (valid) cherry-picking
- * values around.
- */
-struct KeysResponseData
-{
-
- /**
- * Response to return if the client supports (deflate) compression.
- */
- struct MHD_Response *response_compressed;
-
- /**
- * Response to return if the client does not support compression.
- */
- struct MHD_Response *response_uncompressed;
-
- /**
- * Cherry-picking timestamp the client must have set for this
- * response to be valid. 0 if this is the "full" response.
- * The client's request must include this date or a higher one
- * for this response to be applicable.
- */
- struct GNUNET_TIME_Absolute cherry_pick_date;
-
-};
-
-
-/**
- * State we keep around while building an individual entry in the
- * `struct KeysResponseData` array, i.e. the global state for ONE of
- * the responses.
- */
-struct ResponseBuilderContext
-{
-
- /**
- * Hash context we used to combine the hashes of all denomination
- * keys into one big hash for signing.
- */
- struct GNUNET_HashContext *hash_context;
-
- /**
- * JSON array with denomination key information.
- */
- json_t *denom_keys_array;
-
- /**
- * JSON array with auditor information.
- */
- json_t *auditors_array;
-
- /**
- * Keys after what issue date do we care about?
- */
- struct GNUNET_TIME_Absolute last_issue_date;
-
- /**
- * Flag set to #GNUNET_SYSERR on internal errors
- */
- int error;
-
-};
-
-
-/**
- * State we keep around while building the `struct KeysResponseData`
- * array, i.e. the global state for all of the responses.
- */
-struct ResponseFactoryContext
-{
-
- /**
- * JSON array with revoked denomination keys. Every response
- * always returns the full list (cherry picking does not apply
- * for key revocations, as we cannot sort those by issue date).
- */
- json_t *recoup_array;
-
- /**
- * JSON array with signing keys. Every response includes the full
- * list, as it should be quite short anyway, and for simplicity the
- * client only communicates the one time stamp of the last
- * denomination key it knows when cherry picking.
- */
- json_t *sign_keys_array;
-
- /**
- * Sorted array of denomination keys. Length is @e denomkey_array_length.
- * Entries are sorted by the validity period's starting time.
- */
- struct DenominationKeyEntry *denomkey_array;
-
- /**
- * The main key state we are building everything for.
- */
- struct TEH_KS_StateHandle *key_state;
-
- /**
- * Time stamp used as "now".
- */
- struct GNUNET_TIME_Absolute now;
-
- /**
- * Length of the @e denomkey_array.
- */
- unsigned int denomkey_array_length;
-};
-
-
-/**
- * Snapshot of the (coin and signing) keys (including private keys) of
- * the exchange. There can be multiple instances of this struct, as it is
- * reference counted and only destroyed once the last user is done
- * with it. The current instance is acquired using
- * #TEH_KS_acquire(). Using this function increases the
- * reference count. The contents of this structure (except for the
- * reference counter) should be considered READ-ONLY until it is
- * ultimately destroyed (as there can be many concurrent users).
- */
-struct TEH_KS_StateHandle
-{
-
- /**
- * Mapping from denomination keys to denomination key issue struct.
- * Used to lookup the key by hash.
- */
- struct GNUNET_CONTAINER_MultiHashMap *denomkey_map;
-
- /**
- * Mapping from revoked denomination keys to denomination key issue struct.
- * Used to lookup the key by hash.
- */
- struct GNUNET_CONTAINER_MultiHashMap *revoked_map;
-
- /**
- * Sorted array of responses to /keys (MUST be sorted by cherry-picking date) of
- * length @e krd_array_length;
- */
- struct KeysResponseData *krd_array;
-
- /**
- * When did we initiate the key reloading?
- */
- struct GNUNET_TIME_Absolute reload_time;
-
- /**
- * When is the next key invalid and we have to reload? (We also
- * reload on SIGUSR1.)
- */
- struct GNUNET_TIME_Absolute next_reload;
-
- /**
- * When does the first active denomination key expire (for deposit)?
- */
- struct GNUNET_TIME_Absolute min_dk_expire;
-
- /**
- * Exchange signing key that should be used currently.
- */
- struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP current_sign_key_issue;
-
- /**
- * Reference count. The struct is released when the RC hits zero. Once
- * this object is aliased, the reference counter must only be changed while
- * holding the #internal_key_state_mutex.
- */
- unsigned int refcnt;
-
- /**
- * Length of the @e krd_array.
- */
- unsigned int krd_array_length;
-};
-
-
-/**
- * Exchange key state. This is the long-term, read-only internal global state,
- * which the various threads "lock" to use in read-only ways. We eventually
- * create a completely new object "on the side" and then start to return
- * the new read-only object to threads that ask. Once none of the threads
- * use the previous object (RC drops to zero), we discard it.
- *
- * Thus, this instance should never be used directly, instead reserve
- * access via #TEH_KS_acquire() and release it via #TEH_KS_release().
- *
- * As long as MHD threads are running, access to this field requires
- * locking the #internal_key_state_mutex.
- */
-static struct TEH_KS_StateHandle *internal_key_state;
-
-/**
- * Mutex protecting access to #internal_key_state.
- */
-static pthread_mutex_t internal_key_state_mutex = PTHREAD_MUTEX_INITIALIZER;
-
-/**
- * Configuration value LOOKAHEAD_PROVIDE that says for how far in the
- * future we want to provide exchange key information to clients.
- */
-static struct GNUNET_TIME_Relative conf_duration_provide;
-
-
-/* ************************** Clean up logic *********************** */
-
-
-/**
- * Release memory used by @a rfc.
- *
- * @param rfc factory to release (but do not #GNUNET_free() rfc itself!)
- */
-static void
-destroy_response_factory (struct ResponseFactoryContext *rfc)
-{
- if (NULL != rfc->recoup_array)
- {
- json_decref (rfc->recoup_array);
- rfc->recoup_array = NULL;
- }
- if (NULL != rfc->sign_keys_array)
- {
- json_decref (rfc->sign_keys_array);
- rfc->sign_keys_array = NULL;
- }
- for (unsigned int i = 0; i<rfc->denomkey_array_length; i++)
- {
- struct DenominationKeyEntry *dke = &rfc->denomkey_array[i];
- struct AuditorSignature *as;
-
- while (NULL != (as = dke->as_head))
- {
- GNUNET_CONTAINER_DLL_remove (dke->as_head,
- dke->as_tail,
- as);
- GNUNET_free (as);
- }
- }
- GNUNET_array_grow (rfc->denomkey_array,
- rfc->denomkey_array_length,
- 0);
-}
-
-
-/**
- * Release memory used by @a rbc.
- *
- * @param rbc memory to release, excluding @a rbc itself
- */
-static void
-destroy_response_builder (struct ResponseBuilderContext *rbc)
-{
- if (NULL != rbc->denom_keys_array)
- {
- json_decref (rbc->denom_keys_array);
- rbc->denom_keys_array = NULL;
- }
- if (NULL != rbc->auditors_array)
- {
- json_decref (rbc->auditors_array);
- rbc->auditors_array = NULL;
- }
- if (NULL != rbc->hash_context)
- {
- GNUNET_CRYPTO_hash_context_abort (rbc->hash_context);
- rbc->hash_context = NULL;
- }
-}
-
-
-/**
- * Iterator for freeing denomination keys.
- *
- * @param cls closure with the `struct TEH_KS_StateHandle` (unused)
- * @param key hash of the denomination key (unused)
- * @param value coin details, a `struct TALER_EXCHANGEDB_DenominationKey`
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-static int
-free_denom_key (void *cls,
- const struct GNUNET_HashCode *key,
- void *value)
-{
- struct TALER_EXCHANGEDB_DenominationKey *dki = value;
-
- (void) cls;
- (void) key;
- if (NULL != dki->denom_priv.rsa_private_key)
- GNUNET_CRYPTO_rsa_private_key_free (dki->denom_priv.rsa_private_key);
- GNUNET_CRYPTO_rsa_public_key_free (dki->denom_pub.rsa_public_key);
- GNUNET_free (dki);
- return GNUNET_OK;
-}
-
-
-/**
- * Internal function to free key state. Reference count must be at zero.
- *
- * @param key_state the key state to free
- */
-static void
-ks_free (struct TEH_KS_StateHandle *key_state)
-{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "KS release called (%p)\n",
- key_state);
- GNUNET_assert (0 == key_state->refcnt);
- if (NULL != key_state->denomkey_map)
- {
- GNUNET_CONTAINER_multihashmap_iterate (key_state->denomkey_map,
- &free_denom_key,
- key_state);
- GNUNET_CONTAINER_multihashmap_destroy (key_state->denomkey_map);
- key_state->denomkey_map = NULL;
- }
- if (NULL != key_state->revoked_map)
- {
- GNUNET_CONTAINER_multihashmap_iterate (key_state->revoked_map,
- &free_denom_key,
- key_state);
- GNUNET_CONTAINER_multihashmap_destroy (key_state->revoked_map);
- key_state->revoked_map = NULL;
- }
- for (unsigned int i = 0; i<key_state->krd_array_length; i++)
- {
- struct KeysResponseData *krd = &key_state->krd_array[i];
-
- if (NULL != krd->response_compressed)
- MHD_destroy_response (krd->response_compressed);
- if (NULL != krd->response_uncompressed)
- MHD_destroy_response (krd->response_uncompressed);
- }
- GNUNET_array_grow (key_state->krd_array,
- key_state->krd_array_length,
- 0);
- GNUNET_free (key_state);
-}
-
-
-/* ************************* Signal logic ************************** */
-
-/**
- * Pipe used for signaling reloading of our key state.
- */
-static int reload_pipe[2] = { -1, -1 };
-
-
-/**
- * Handle a signal, writing relevant signal numbers to the pipe.
- *
- * @param signal_number the signal number
- */
-static void
-handle_signal (int signal_number)
-{
- char c = (char) signal_number; /* never seen a signal_number > 127 on any platform */
-
- (void) ! write (reload_pipe[1],
- &c,
- 1);
- /* While one might like to "handle errors" here, even logging via fprintf()
- isn't safe inside of a signal handler. So there is nothing we safely CAN
- do. OTOH, also very little that can go wrong in practice. Calling _exit()
- on errors might be a possibility, but that might do more harm than good. *///
-}
-
-
-/* ************************** State builder ************************ */
-
-
-/**
- * Convert the public part of denomination key data to a JSON object.
- *
- * @param pk public key of the denomination
- * @param dki the denomination key issue information
- * @return a JSON object describing the denomination key issue (public part)
- */
-static json_t *
-denom_key_issue_to_json (
- const struct TALER_DenominationPublicKey *pk,
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki)
-{
- struct TALER_Amount value;
- struct TALER_Amount fee_withdraw;
- struct TALER_Amount fee_deposit;
- struct TALER_Amount fee_refresh;
- struct TALER_Amount fee_refund;
-
- TALER_amount_ntoh (&value,
- &dki->properties.value);
- TALER_amount_ntoh (&fee_withdraw,
- &dki->properties.fee_withdraw);
- TALER_amount_ntoh (&fee_deposit,
- &dki->properties.fee_deposit);
- TALER_amount_ntoh (&fee_refresh,
- &dki->properties.fee_refresh);
- TALER_amount_ntoh (&fee_refund,
- &dki->properties.fee_refund);
- return
- json_pack ("{s:o, s:o, s:o, s:o, s:o,"
- " s:o, s:o, s:o, s:o, s:o,"
- " s:o}",
- "master_sig",
- GNUNET_JSON_from_data_auto (&dki->signature),
- "stamp_start",
- GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh (
- dki->properties.start)),
- "stamp_expire_withdraw",
- GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh (
- dki->properties.expire_withdraw)),
- "stamp_expire_deposit",
- GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh (
- dki->properties.expire_deposit)),
- "stamp_expire_legal",
- GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh (
- dki->properties.expire_legal)),
- /* 5 entries until here */
- "denom_pub",
- GNUNET_JSON_from_rsa_public_key (pk->rsa_public_key),
- "value",
- TALER_JSON_from_amount (&value),
- "fee_withdraw",
- TALER_JSON_from_amount (&fee_withdraw),
- "fee_deposit",
- TALER_JSON_from_amount (&fee_deposit),
- "fee_refresh",
- TALER_JSON_from_amount (&fee_refresh),
- /* 10 entries until here */
- "fee_refund",
- TALER_JSON_from_amount (&fee_refund));
-}
-
-
-/**
- * Store a copy of @a dki in @a map.
- *
- * @param map hash map to store @a dki in
- * @param dki information to store in @a map
- * @return #GNUNET_OK on success,
- * #GNUNET_NO if such an entry already exists
- */
-static int
-store_in_map (struct GNUNET_CONTAINER_MultiHashMap *map,
- const struct TALER_EXCHANGEDB_DenominationKey *dki)
-{
- /* First, we verify that the @a dki is actually well-formed. While it comes
- from our own hard disk, there is the possibility of misconfiguration
- (i.e. bogus file in the directory), or that the administrator used the
- wrong master public key, and we should not accept entries that are not
- well-formed. *///
- {
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dkip;
- struct TALER_DenominationKeyValidityPS denom_key_issue;
-
- dkip = &dki->issue;
- denom_key_issue = dkip->properties;
- /* The above is straight from our disk. We should not trust
- that it is well-formed, so we setup crucial fields ourselves. */
- denom_key_issue.purpose.purpose
- = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY);
- denom_key_issue.purpose.size
- = htonl (sizeof (struct TALER_DenominationKeyValidityPS));
- denom_key_issue.master = TEH_master_public_key;
-
- /* Check that the data we read matches our expectations */
- if (0 !=
- GNUNET_memcmp (&denom_key_issue,
- &dkip->properties))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid fields in denomination key `%s'\n",
- GNUNET_h2s (&dkip->properties.denom_hash));
- return GNUNET_SYSERR;
- }
-
- /* Also check the signature by the master public key */
- if (GNUNET_SYSERR ==
- GNUNET_CRYPTO_eddsa_verify (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY,
- &denom_key_issue.purpose,
- &dkip->signature.eddsa_signature,
- &TEH_master_public_key.eddsa_pub))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid signature on denomination key `%s'\n",
- GNUNET_h2s (&dkip->properties.denom_hash));
- return GNUNET_SYSERR;
- }
- }
-
- /* We need to make a deep copy of the @a dki, as the original was allocated
- elsewhere and will be freed by the caller. */
- {
- struct TALER_EXCHANGEDB_DenominationKey *d2;
-
- d2 = GNUNET_new (struct TALER_EXCHANGEDB_DenominationKey);
- d2->issue = dki->issue;
- if (GNUNET_OK !=
- GNUNET_CONTAINER_multihashmap_put (map,
- &d2->issue.properties.denom_hash,
- d2,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Duplicate denomination key `%s'\n",
- GNUNET_h2s (&d2->issue.properties.denom_hash));
- GNUNET_free (d2);
- return GNUNET_NO;
- }
-
- /* finish *deep* part of deep copy */
- if (NULL != dki->denom_priv.rsa_private_key)
- {
- /* we might be past the withdraw period, so private key could have been deleted,
- so only try to (deep) copy if non-NULL. */
- d2->denom_priv.rsa_private_key
- = GNUNET_CRYPTO_rsa_private_key_dup (dki->denom_priv.rsa_private_key);
- }
- d2->denom_pub.rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_dup (dki->denom_pub.rsa_public_key);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Closure for #add_revocations_transaction().
- */
-struct AddRevocationContext
-{
- /**
- * Denomination key that is revoked.
- */
- const struct TALER_EXCHANGEDB_DenominationKey *dki;
-
- /**
- * Signature affirming the revocation.
- */
- const struct TALER_MasterSignatureP *revocation_master_sig;
-};
-
-
-/**
- * Execute transaction to add revocations.
- *
- * @param cls closure with the `struct AddRevocationContext *`
- * @param connection NULL
- * @param session database session to use
- * @param[out] mhd_ret not used
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-add_revocations_transaction (void *cls,
- struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
-{
- struct AddRevocationContext *arc = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_MasterSignatureP master_sig;
- uint64_t rowid;
-
- (void) connection;
- (void) mhd_ret;
- qs = TEH_plugin->get_denomination_revocation (TEH_plugin->cls,
- session,
- &arc->dki->issue.properties.
- denom_hash,
- &master_sig,
- &rowid);
- if (0 > qs)
- return qs; /* failure */
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- return qs; /* already exists == success */
- return TEH_plugin->insert_denomination_revocation (TEH_plugin->cls,
- session,
- &arc->dki->issue.properties
- .denom_hash,
- arc->revocation_master_sig);
-}
-
-
-/**
- * Execute transaction to add a denomination to the DB.
- *
- * @param cls closure with the `const struct TALER_EXCHANGEDB_DenominationKey *`
- * @param connection NULL
- * @param session database session to use
- * @param[out] mhd_ret not used
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-add_denomination_transaction (void *cls,
- struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
-{
- const struct TALER_EXCHANGEDB_DenominationKey *dki = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_EXCHANGEDB_DenominationKeyInformationP issue_exists;
-
- (void) connection;
- (void) mhd_ret;
- qs = TEH_plugin->get_denomination_info (TEH_plugin->cls,
- session,
- &dki->issue.properties.denom_hash,
- &issue_exists);
- if (0 > qs)
- return qs;
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- return qs;
- return TEH_plugin->insert_denomination_info (TEH_plugin->cls,
- session,
- &dki->denom_pub,
- &dki->issue);
-}
-
-
-/**
- * Iterator for (re)loading/initializing denomination keys.
- *
- * @param cls closure with a `struct ResponseFactoryContext *`
- * @param dki the denomination key issue
- * @param alias coin alias
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-static int
-reload_keys_denom_iter (void *cls,
- const char *alias,
- const struct TALER_EXCHANGEDB_DenominationKey *dki)
-{
- struct ResponseFactoryContext *rfc = cls;
- struct TEH_KS_StateHandle *key_state = rfc->key_state;
- struct GNUNET_TIME_Absolute start;
- struct GNUNET_TIME_Absolute horizon;
- struct GNUNET_TIME_Absolute expire_deposit;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Loading denomination key `%s' (%s)\n",
- alias,
- GNUNET_h2s (&dki->issue.properties.denom_hash));
- expire_deposit = GNUNET_TIME_absolute_ntoh (
- dki->issue.properties.expire_deposit);
- if (expire_deposit.abs_value_us < rfc->now.abs_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Skipping expired denomination key `%s'\n",
- alias);
- return GNUNET_OK;
- }
- if (0 != GNUNET_memcmp (&dki->issue.properties.master,
- &TEH_master_public_key))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Master key in denomination key file `%s' does not match! Skipping it.\n",
- alias);
- return GNUNET_OK;
- }
-
- horizon = GNUNET_TIME_absolute_add (rfc->now,
- conf_duration_provide);
- start = GNUNET_TIME_absolute_ntoh (dki->issue.properties.start);
- if (start.abs_value_us > horizon.abs_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Skipping future denomination key `%s' (%s), validity starts at %s\n",
- alias,
- GNUNET_h2s (&dki->issue.properties.denom_hash),
- GNUNET_STRINGS_absolute_time_to_string (start));
- return GNUNET_OK;
- }
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (NULL,
- "add denomination key",
- NULL,
- &add_denomination_transaction,
- (void *) dki))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not persist denomination key %s in DB. Committing suicide via SIGTERM.\n",
- GNUNET_h2s (&dki->issue.properties.denom_hash));
- handle_signal (SIGTERM);
- return GNUNET_SYSERR;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Adding denomination key `%s' (%s) to active set\n",
- alias,
- GNUNET_h2s (&dki->issue.properties.denom_hash));
- if (GNUNET_NO /* entry already exists */ ==
- store_in_map (key_state->denomkey_map,
- dki))
- return GNUNET_OK; /* do not update expiration if entry exists */
- key_state->min_dk_expire = GNUNET_TIME_absolute_min (key_state->min_dk_expire,
- expire_deposit);
- return GNUNET_OK;
-}
-
-
-/**
- * Iterator for revocation of denomination keys.
- *
- * @param cls closure with a `struct ResponseFactoryContext *`
- * @param denom_hash hash of revoked denomination public key
- * @param revocation_master_sig signature showing @a denom_hash was revoked
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-static int
-revocations_iter (void *cls,
- const struct GNUNET_HashCode *denom_hash,
- const struct TALER_MasterSignatureP *revocation_master_sig)
-{
- struct ResponseFactoryContext *rfc = cls;
- struct TEH_KS_StateHandle *key_state = rfc->key_state;
- struct AddRevocationContext arc;
- struct TALER_EXCHANGEDB_DenominationKey *dki;
-
- dki = GNUNET_CONTAINER_multihashmap_get (key_state->denomkey_map,
- denom_hash);
- if (NULL == dki)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Revoked denomination `%s' unknown (or duplicate file), ignoring revocation\n",
- GNUNET_h2s (denom_hash));
- return GNUNET_OK;
-
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Adding denomination key `%s' to revocation set\n",
- GNUNET_h2s (denom_hash));
- GNUNET_assert (GNUNET_YES ==
- GNUNET_CONTAINER_multihashmap_remove (key_state->denomkey_map,
- denom_hash,
- dki));
- GNUNET_assert (GNUNET_YES ==
- GNUNET_CONTAINER_multihashmap_put (key_state->revoked_map,
- &dki->issue.properties.
- denom_hash,
- dki,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
- /* Try to insert revocation into DB */
- arc.dki = dki;
- arc.revocation_master_sig = revocation_master_sig;
- if (GNUNET_OK !=
- TEH_DB_run_transaction (NULL,
- "add denomination key revocation",
- NULL,
- &add_revocations_transaction,
- &arc))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to add revocation to database. This is fatal. Committing suicide via SIGTERM.\n");
- handle_signal (SIGTERM);
- return GNUNET_SYSERR;
- }
-
- {
- json_t *obj;
-
- obj = json_pack ("{s:o}",
- "h_denom_pub",
- GNUNET_JSON_from_data_auto (denom_hash));
- if (NULL == obj)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (0 !=
- json_array_append_new (rfc->recoup_array,
- obj))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Convert the public part of a sign key issue to a JSON object.
- *
- * @param ski the sign key issue
- * @param ski_sig signature over @a ski
- * @return a JSON object describing the sign key issue (public part)
- */
-static json_t *
-sign_key_issue_to_json (const struct TALER_ExchangeSigningKeyValidityPS *ski,
- const struct TALER_MasterSignatureP *ski_sig)
-{
- return
- json_pack ("{s:o, s:o, s:o, s:o, s:o}",
- "stamp_start",
- GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh (
- ski->start)),
- "stamp_expire",
- GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh (
- ski->expire)),
- "stamp_end",
- GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh (ski->end)),
- "master_sig",
- GNUNET_JSON_from_data_auto (ski_sig),
- "key",
- GNUNET_JSON_from_data_auto (&ski->signkey_pub));
-}
-
-
-/**
- * Iterator for sign keys. Adds current and near-future signing keys
- * to the `sign_keys_array` and stores the current one in the
- * `key_state`.
- *
- * @param cls closure with the `struct ResponseFactoryContext *`
- * @param filename name of the file the key came from
- * @param ski the sign key issue
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-static int
-reload_keys_sign_iter (
- void *cls,
- const char *filename,
- const struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP *ski)
-{
- struct ResponseFactoryContext *rfc = cls;
- struct TEH_KS_StateHandle *key_state = rfc->key_state;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Absolute horizon;
-
- horizon = GNUNET_TIME_relative_to_absolute (conf_duration_provide);
- if (GNUNET_TIME_absolute_ntoh (ski->issue.start).abs_value_us >
- horizon.abs_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Skipping future signing key `%s'\n",
- filename);
- return GNUNET_OK;
- }
- now = GNUNET_TIME_absolute_get ();
- if (GNUNET_TIME_absolute_ntoh (ski->issue.expire).abs_value_us <
- now.abs_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Skipping expired signing key `%s'\n",
- filename);
- return GNUNET_OK;
- }
-
- if (0 != GNUNET_memcmp (&ski->issue.master_public_key,
- &TEH_master_public_key))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Master key in signing key file `%s' does not match! Skipping it.\n",
- filename);
- return GNUNET_OK;
- }
-
- /* The signkey is valid at this time, check if it's more recent than
- what we have so far! */
- if ( (GNUNET_TIME_absolute_ntoh (
- key_state->current_sign_key_issue.issue.start).abs_value_us <
- GNUNET_TIME_absolute_ntoh (ski->issue.start).abs_value_us) &&
- (GNUNET_TIME_absolute_ntoh (ski->issue.start).abs_value_us <
- now.abs_value_us) )
- {
- /* We use the most recent one, if it is valid now (not just in the near future) */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Found signing key valid until `%s'\n",
- GNUNET_STRINGS_absolute_time_to_string (
- GNUNET_TIME_absolute_ntoh (
- key_state->current_sign_key_issue.issue.end)));
- key_state->current_sign_key_issue = *ski;
- }
- if (0 !=
- json_array_append_new (rfc->sign_keys_array,
- sign_key_issue_to_json (&ski->issue,
- &ski->master_sig)))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * @brief Iterator called with auditor information.
- * Check that the @a mpub actually matches this exchange, and then
- * add the auditor information to our /keys response (if it is
- * (still) applicable).
- *
- * @param cls closure with the `struct ResponseFactoryContext *`
- * @param apub the auditor's public key
- * @param auditor_url URL of the auditor
- * @param mpub the exchange's public key (as expected by the auditor)
- * @param dki_len length of @a dki and @a asigs
- * @param asigs array with the auditor's signatures, of length @a dki_len
- * @param dki array of denomination coin data signed by the auditor
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-static int
-reload_auditor_iter (void *cls,
- const struct TALER_AuditorPublicKeyP *apub,
- const char *auditor_url,
- const struct TALER_MasterPublicKeyP *mpub,
- unsigned int dki_len,
- const struct TALER_AuditorSignatureP *asigs,
- const struct TALER_DenominationKeyValidityPS *dki)
-{
- struct ResponseFactoryContext *rfc = cls;
- struct TEH_KS_StateHandle *key_state = rfc->key_state;
-
- /* Check if the signature is at least for this exchange. */
- if (0 != memcmp (&mpub->eddsa_pub,
- &TEH_master_public_key,
- sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Auditing information provided for a different exchange, ignored\n");
- return GNUNET_OK;
- }
- /* Filter the auditor information for those for which the
- keys actually match the denomination keys that are active right now */
- for (unsigned int i = 0; i<dki_len; i++)
- {
- int matched;
-
- if (GNUNET_YES !=
- GNUNET_CONTAINER_multihashmap_contains (key_state->denomkey_map,
- &dki[i].denom_hash))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Found auditor signature for DK `%s', but key is not in active map\n",
- GNUNET_h2s (&dki[i].denom_hash));
- continue;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Found auditor signature for DK `%s'\n",
- GNUNET_h2s (&dki[i].denom_hash));
- /* Note: the array is sorted, we could theoretically
- speed this up using a binary search. */
- matched = GNUNET_NO;
- for (unsigned int j = 0; j<rfc->denomkey_array_length; j++)
- {
- struct DenominationKeyEntry *dke = &rfc->denomkey_array[j];
- struct AuditorSignature *as;
-
- if (0 !=
- memcmp (&dki[i].denom_hash,
- &dke->dki->issue.properties.denom_hash,
- sizeof (struct GNUNET_HashCode)))
- continue;
- if (0 !=
- memcmp (&dki[i],
- &dke->dki->issue.properties,
- sizeof (struct TALER_DenominationKeyValidityPS)))
- {
- /* if the hash is the same, the properties should also match! */
- GNUNET_break (0);
- continue;
- }
- as = GNUNET_malloc (sizeof (struct AuditorSignature)
- + strlen (auditor_url) + 1);
- as->asig = asigs[i];
- as->apub = *apub;
- as->auditor_url = (const char *) &as[1];
- memcpy (&as[1],
- auditor_url,
- strlen (auditor_url) + 1);
- GNUNET_CONTAINER_DLL_insert (dke->as_head,
- dke->as_tail,
- as);
- matched = GNUNET_YES;
- break;
- }
- if (GNUNET_NO == matched)
- {
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "DK `%s' is in active map, but not in array!?\n",
- GNUNET_h2s (&dki[i].denom_hash));
- }
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Initialize the `denomkey_array`. We are called once per
- * array index, which is tracked in `denomkey_array_length` (the
- * array will be of sufficient size). Set the pointer to the
- * denomination key and increment the `denomkey_array_length`.
- *
- * @param cls a `struct ResponseFactoryContext`
- * @param denom_hash hash of a denomination key
- * @param value a `struct TALER_EXCHANGEDB_DenominationKey *`
- * @return #GNUNET_OK
- */
-static int
-initialize_denomkey_array (void *cls,
- const struct GNUNET_HashCode *denom_hash,
- void *value)
-{
- struct ResponseFactoryContext *rfc = cls;
- struct TALER_EXCHANGEDB_DenominationKey *dki = value;
-
- rfc->denomkey_array[rfc->denomkey_array_length].denom_key_hash = *denom_hash;
- rfc->denomkey_array[rfc->denomkey_array_length++].dki = dki;
- return GNUNET_OK;
-}
-
-
-/**
- * Comparator used to sort the `struct DenominationKeyEntry` array
- * by the validity period's starting time of the keys. Must match
- * the expected sorting by cherry_pick_date (which is based on the
- * issue.properties.start) used in #krd_search_comparator.
- *
- * @param k1 a `struct DenominationKeyEntry *`
- * @param k2 a `struct DenominationKeyEntry *`
- * @return -1 if k1 starts before k2,
- * 1 if k2 starts before k1,
- * 0 if they start at the same time
- */
-static int
-denomkey_array_sort_comparator (const void *k1,
- const void *k2)
-{
- const struct DenominationKeyEntry *dke1 = k1;
- const struct DenominationKeyEntry *dke2 = k2;
- struct GNUNET_TIME_Absolute d1
- = GNUNET_TIME_absolute_ntoh (dke1->dki->issue.properties.start);
- struct GNUNET_TIME_Absolute d2
- = GNUNET_TIME_absolute_ntoh (dke2->dki->issue.properties.start);
-
- if (d1.abs_value_us < d2.abs_value_us)
- return -1;
- if (d1.abs_value_us > d2.abs_value_us)
- return 1;
- return 0;
-}
-
-
-/**
- * Produce HTTP "Date:" header.
- *
- * @param at time to write to @a date
- * @param[out] date where to write the header, with
- * at least 128 bytes available space.
- */
-static void
-get_date_string (struct GNUNET_TIME_Absolute at,
- char date[128])
-{
- static const char *const days[] =
- { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
- static const char *const mons[] =
- { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
- "Nov", "Dec"};
- struct tm now;
- time_t t;
-#if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \
- ! defined(HAVE_GMTIME_R)
- struct tm*pNow;
-#endif
-
- date[0] = 0;
- t = (time_t) (at.abs_value_us / 1000LL / 1000LL);
-#if defined(HAVE_C11_GMTIME_S)
- if (NULL == gmtime_s (&t, &now))
- return;
-#elif defined(HAVE_W32_GMTIME_S)
- if (0 != gmtime_s (&now, &t))
- return;
-#elif defined(HAVE_GMTIME_R)
- if (NULL == gmtime_r (&t, &now))
- return;
-#else
- pNow = gmtime (&t);
- if (NULL == pNow)
- return;
- now = *pNow;
-#endif
- sprintf (date,
- "%3s, %02u %3s %04u %02u:%02u:%02u GMT",
- days[now.tm_wday % 7],
- (unsigned int) now.tm_mday,
- mons[now.tm_mon % 12],
- (unsigned int) (1900 + now.tm_year),
- (unsigned int) now.tm_hour,
- (unsigned int) now.tm_min,
- (unsigned int) now.tm_sec);
-}
-
-
-/**
- * Add the headers we want to set for every /keys response.
- *
- * @param key_state the key state to use
- * @param[in,out] response the response to modify
- * @return #GNUNET_OK on success
- */
-static int
-setup_general_response_headers (const struct TEH_KS_StateHandle *key_state,
- struct MHD_Response *response)
-{
- char dat[128];
-
- TALER_MHD_add_global_headers (response);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- "application/json"));
- get_date_string (key_state->reload_time,
- dat);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_LAST_MODIFIED,
- dat));
- if (0 != key_state->next_reload.abs_value_us)
- {
- struct GNUNET_TIME_Absolute m;
-
- m = GNUNET_TIME_relative_to_absolute (TEH_max_keys_caching);
- m = GNUNET_TIME_absolute_min (m,
- key_state->next_reload);
- get_date_string (m,
- dat);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Setting /keys 'Expires' header to '%s'\n",
- dat);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_EXPIRES,
- dat));
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Information about an auditor to be added.
- */
-struct AuditorEntry
-{
- /**
- * URL of the auditor (allocated still as part of a
- * `struct AuditorSignature`, do not free!).
- */
- const char *auditor_url;
-
- /**
- * Public key of the auditor (allocated still as part of a
- * `struct AuditorSignature`, do not free!).
- */
- const struct TALER_AuditorPublicKeyP *apub;
-
- /**
- * Array of denomination keys and auditor signatures.
- */
- json_t *ar;
-
-};
-
-
-/**
- * Free auditor entry from the hash map.
- *
- * @param cls NULL
- * @param key unused
- * @param value a `struct AuditorEntry` to free
- * @return #GNUNET_OK (to continue to iterate)
- */
-static int
-free_auditor_entry (void *cls,
- const struct GNUNET_HashCode *key,
- void *value)
-{
- struct AuditorEntry *ae = value;
-
- (void) cls;
- (void) key;
- json_decref (ae->ar);
- GNUNET_free (ae);
- return GNUNET_OK;
-}
-
-
-/**
- * Convert auditor entries from the hash map to entries
- * in the auditor array, free the auditor entry as well.
- *
- * @param cls a `struct ResponseBuilderContext *`
- * @param key unused
- * @param value a `struct AuditorEntry` to add to the `auditors_array`
- * @return #GNUNET_OK (to continue to iterate), #GNUNET_SYSERR to terminate with error
- */
-static int
-add_auditor_entry (void *cls,
- const struct GNUNET_HashCode *key,
- void *value)
-{
- struct ResponseBuilderContext *rbc = cls;
- struct AuditorEntry *ae = value;
- json_t *ao;
-
- (void) key;
- ao = json_pack ("{s:o, s:s, s:o}",
- "denomination_keys", ae->ar,
- "auditor_url", ae->auditor_url,
- "auditor_pub", GNUNET_JSON_from_data_auto (ae->apub));
- if (NULL == ao)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (0 !=
- json_array_append_new (rbc->auditors_array,
- ao))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- GNUNET_free (ae);
- return GNUNET_OK;
-}
-
-
-/**
- * Initialize @a krd for the given @a cherry_pick_date using
- * the key data in @a rfc. This function actually builds the
- * respective JSON replies (compressed and uncompressed).
- *
- * @param rfc factory with key material
- * @param[out] krd response object to initialize
- * @param denom_off offset in the @a rfc's `denomkey_array` at which
- * keys beyond the @a cherry_pick_date are stored
- * @param cherry_pick_date cut-off date to use
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
- */
-static int
-build_keys_response (const struct ResponseFactoryContext *rfc,
- struct KeysResponseData *krd,
- unsigned int denom_off,
- struct GNUNET_TIME_Absolute cherry_pick_date)
-{
- struct ResponseBuilderContext rbc;
- json_t *keys;
- struct TALER_ExchangeKeySetPS ks;
- struct TALER_ExchangeSignatureP sig;
- char *keys_json;
- struct GNUNET_TIME_Relative reserve_closing_delay;
- void *keys_jsonz;
- size_t keys_jsonz_size;
- int comp;
-
- krd->cherry_pick_date = cherry_pick_date;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Creating /keys for cherry pick date %s\n",
- GNUNET_STRINGS_absolute_time_to_string (cherry_pick_date));
-
- /* Initialize `rbc` */
- memset (&rbc,
- 0,
- sizeof (rbc));
- rbc.denom_keys_array = json_array ();
- if (NULL == rbc.denom_keys_array)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- rbc.auditors_array = json_array ();
- if (NULL == rbc.auditors_array)
- {
- destroy_response_builder (&rbc);
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- rbc.hash_context = GNUNET_CRYPTO_hash_context_start ();
-
- /* Go over relevant denomination keys. */
- {
- struct GNUNET_CONTAINER_MultiHashMap *auditors;
-
- auditors = GNUNET_CONTAINER_multihashmap_create (4,
- GNUNET_NO);
- for (unsigned int i = denom_off; i<rfc->denomkey_array_length; i++)
- {
- /* Add denomination key to the response */
- const struct DenominationKeyEntry *dke
- = &rfc->denomkey_array[i];
- const struct GNUNET_HashCode *denom_key_hash
- = &dke->denom_key_hash;
-
- GNUNET_CRYPTO_hash_context_read (rbc.hash_context,
- denom_key_hash,
- sizeof (struct GNUNET_HashCode));
- if (0 !=
- json_array_append_new (rbc.denom_keys_array,
- denom_key_issue_to_json (&dke->dki->denom_pub,
- &dke->dki->issue)))
- {
- GNUNET_break (0);
- destroy_response_builder (&rbc);
- return GNUNET_SYSERR;
- }
-
- /* Add auditor data */
- for (const struct AuditorSignature *as = dke->as_head;
- NULL != as;
- as = as->next)
- {
- struct GNUNET_HashCode ahash;
- struct AuditorEntry *ae;
-
- GNUNET_CRYPTO_hash (&as->apub,
- sizeof (as->apub),
- &ahash);
- ae = GNUNET_CONTAINER_multihashmap_get (auditors,
- &ahash);
- if (NULL == ae)
- {
- ae = GNUNET_new (struct AuditorEntry);
- ae->auditor_url = as->auditor_url;
- ae->ar = json_array ();
- ae->apub = &as->apub;
- GNUNET_assert (GNUNET_YES ==
- GNUNET_CONTAINER_multihashmap_put (auditors,
- &ahash,
- ae,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
- }
- if (0 !=
- json_array_append_new (ae->ar,
- json_pack ("{s:o, s:o}",
- "denom_pub_h",
- GNUNET_JSON_from_data_auto (
- denom_key_hash),
- "auditor_sig",
- GNUNET_JSON_from_data_auto (
- &as->asig))))
- {
- destroy_response_builder (&rbc);
- GNUNET_break (0);
- GNUNET_CONTAINER_multihashmap_iterate (auditors,
- &free_auditor_entry,
- NULL);
- GNUNET_CONTAINER_multihashmap_destroy (auditors);
- return GNUNET_SYSERR;
- }
- }
- }
-
- GNUNET_CONTAINER_multihashmap_iterate (auditors,
- &add_auditor_entry,
- &rbc);
- GNUNET_CONTAINER_multihashmap_destroy (auditors);
- }
-
- /* Sign hash over denomination keys */
- ks.purpose.size = htonl (sizeof (ks));
- ks.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET);
- ks.list_issue_date = GNUNET_TIME_absolute_hton (rfc->key_state->reload_time);
- GNUNET_CRYPTO_hash_context_finish (rbc.hash_context,
- &ks.hc);
- rbc.hash_context = NULL;
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (
- &rfc->key_state->current_sign_key_issue.signkey_priv.
- eddsa_priv,
- &ks.purpose,
- &sig.eddsa_signature));
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
- "exchangedb",
- "IDLE_RESERVE_EXPIRATION_TIME",
- &reserve_closing_delay))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchangedb",
- "IDLE_RESERVE_EXPIRATION_TIME");
- /* use default */
- reserve_closing_delay = GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_WEEKS,
- 4);
- }
- /* Build /keys response */
- keys = json_pack ("{s:s, s:o, s:o, s:O, s:O,"
- " s:o, s:o, s:o, s:o, s:o}",
- /* 1-5 */
- "version", EXCHANGE_PROTOCOL_VERSION,
- "master_public_key", GNUNET_JSON_from_data_auto (
- &TEH_master_public_key),
- "reserve_closing_delay", GNUNET_JSON_from_time_rel (
- reserve_closing_delay),
- "signkeys", rfc->sign_keys_array,
- "recoup", rfc->recoup_array,
- /* 6-10 */
- "denoms", rbc.denom_keys_array,
- "auditors", rbc.auditors_array,
- "list_issue_date", GNUNET_JSON_from_time_abs (
- rfc->key_state->reload_time),
- "eddsa_pub", GNUNET_JSON_from_data_auto (
- &rfc->key_state->current_sign_key_issue.issue.signkey_pub),
- "eddsa_sig", GNUNET_JSON_from_data_auto (&sig));
- if (NULL == keys)
- {
- destroy_response_builder (&rbc);
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- rbc.denom_keys_array = NULL;
- rbc.auditors_array = NULL;
- destroy_response_builder (&rbc);
-
- /* Convert /keys response to UTF8-String */
- keys_json = json_dumps (keys,
- JSON_INDENT (2));
- json_decref (keys);
- if (NULL == keys_json)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- /* Keep copy for later compression... */
- keys_jsonz = GNUNET_strdup (keys_json);
- keys_jsonz_size = strlen (keys_json);
-
- /* Create uncompressed response */
- krd->response_uncompressed
- = MHD_create_response_from_buffer (keys_jsonz_size,
- keys_json,
- MHD_RESPMEM_MUST_FREE);
- if (NULL == krd->response_uncompressed)
- {
- GNUNET_break (0);
- GNUNET_free (keys_json);
- GNUNET_free (keys_jsonz);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- setup_general_response_headers (rfc->key_state,
- krd->response_uncompressed))
- {
- GNUNET_break (0);
- GNUNET_free (keys_jsonz);
- return GNUNET_SYSERR;
- }
-
- /* Also compute compressed version of /keys response */
- comp = TALER_MHD_body_compress (&keys_jsonz,
- &keys_jsonz_size);
- krd->response_compressed
- = MHD_create_response_from_buffer (keys_jsonz_size,
- keys_jsonz,
- MHD_RESPMEM_MUST_FREE);
- if (NULL == krd->response_compressed)
- {
- GNUNET_break (0);
- GNUNET_free (keys_jsonz);
- return GNUNET_SYSERR;
- }
- /* If the response is actually compressed, set the
- respective header. */
- if ( (MHD_YES == comp) &&
- (MHD_YES !=
- MHD_add_response_header (krd->response_compressed,
- MHD_HTTP_HEADER_CONTENT_ENCODING,
- "deflate")) )
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- setup_general_response_headers (rfc->key_state,
- krd->response_compressed))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called with information about the exchange's denomination
- * keys based on what is known in the database. Used to learn our
- * public keys (after the private keys are deleted, we still need to
- * have the public keys around for a while to verify signatures).
- *
- * This function checks if the @a denom_pub is already known to us,
- * and if not adds it to our set.
- *
- * @param cls closure, a `struct ResponseFactoryContext *`
- * @param denom_pub public key of the denomination
- * @param issue detailed information about the denomination (value, expiration times, fees)
- */
-static void
-reload_public_denoms_cb (
- void *cls,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue)
-{
- struct ResponseFactoryContext *rfc = cls;
- struct TALER_EXCHANGEDB_DenominationKey dki;
- int ret;
-
- if (rfc->now.abs_value_us > GNUNET_TIME_absolute_ntoh
- (issue->properties.expire_legal).abs_value_us)
- {
- /* Expired key, discard. */
- return;
- }
-
- if (NULL !=
- GNUNET_CONTAINER_multihashmap_get (rfc->key_state->denomkey_map,
- &issue->properties.denom_hash))
- return; /* exists / known */
- if (NULL !=
- GNUNET_CONTAINER_multihashmap_get (rfc->key_state->revoked_map,
- &issue->properties.denom_hash))
- return; /* exists / known */
- /* zero-out, just for future-proofing */
- memset (&dki,
- 0,
- sizeof (dki));
- dki.denom_priv.rsa_private_key = NULL; /* not available! */
- dki.denom_pub.rsa_public_key = denom_pub->rsa_public_key;
- dki.issue = *issue;
- ret = store_in_map (rfc->key_state->denomkey_map,
- &dki /* makes a deep copy of dki */);
- if (GNUNET_SYSERR == ret)
- {
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Signature wrong on denomination key `%s' (skipping)!\n",
- GNUNET_h2s (&issue->properties.denom_hash));
- return;
- }
- /* we can assert here as we checked for duplicates just above */
- GNUNET_assert (GNUNET_OK == ret);
-}
-
-
-/**
- * Actual "main" logic that builds the state which this module
- * evolves around. This function will import the key data from
- * the exchangedb module and convert it into (1) internally used
- * lookup tables, and (2) HTTP responses to be returned from
- * /keys.
- *
- * State returned is to be freed with #ks_free() -- but only
- * once the reference counter has again hit zero.
- *
- * @return NULL on error (usually pretty fatal...)
- */
-static struct TEH_KS_StateHandle *
-make_fresh_key_state (struct GNUNET_TIME_Absolute now)
-{
- struct TEH_KS_StateHandle *key_state;
- struct ResponseFactoryContext rfc;
- struct GNUNET_TIME_Absolute last;
- unsigned int off;
- enum GNUNET_DB_QueryStatus qs;
-
- memset (&rfc,
- 0,
- sizeof (rfc));
- rfc.recoup_array = json_array ();
- if (NULL == rfc.recoup_array)
- {
- GNUNET_break (0);
- return NULL;
- }
- rfc.sign_keys_array = json_array ();
- if (NULL == rfc.sign_keys_array)
- {
- GNUNET_break (0);
- json_decref (rfc.recoup_array);
- return NULL;
- }
-
- key_state = GNUNET_new (struct TEH_KS_StateHandle);
- rfc.key_state = key_state;
- rfc.now = now;
- key_state->min_dk_expire = GNUNET_TIME_UNIT_FOREVER_ABS;
- key_state->denomkey_map = GNUNET_CONTAINER_multihashmap_create (32,
- GNUNET_NO);
- key_state->revoked_map = GNUNET_CONTAINER_multihashmap_create (4,
- GNUNET_NO);
- key_state->reload_time = GNUNET_TIME_absolute_get ();
- GNUNET_TIME_round_abs (&key_state->reload_time);
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Loading keys from `%s'\n",
- TEH_exchange_directory);
- /* Initialize the 'denomkey_map' and the 'revoked_map' and
- 'rfc.recoup_array' */
- if (-1 ==
- TALER_EXCHANGEDB_denomination_keys_iterate (TEH_exchange_directory,
- &reload_keys_denom_iter,
- &rfc))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to load denomination keys from `%s'.\n",
- TEH_exchange_directory);
- ks_free (key_state);
- json_decref (rfc.recoup_array);
- json_decref (rfc.sign_keys_array);
- return NULL;
- }
-
- /* We do not get expired DKIs from
- TALER_EXCHANGEDB_denomination_keys_iterate(), so we must fetch
- the old keys (where we only have the public keys) from the
- database! */
- qs = TEH_plugin->iterate_denomination_info (TEH_plugin->cls,
- &reload_public_denoms_cb,
- &rfc);
- GNUNET_break (0 <= qs); /* warn, but continue, fingers crossed */
-
- /* process revocations */
- if (-1 ==
- TALER_EXCHANGEDB_revocations_iterate (TEH_revocation_directory,
- &TEH_master_public_key,
- &revocations_iter,
- &rfc))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to load denomination keys from `%s'.\n",
- TEH_exchange_directory);
- ks_free (key_state);
- json_decref (rfc.recoup_array);
- json_decref (rfc.sign_keys_array);
- return NULL;
- }
-
- /* Initialize `current_sign_key_issue` and `rfc.sign_keys_array` */
- TALER_EXCHANGEDB_signing_keys_iterate (TEH_exchange_directory,
- &reload_keys_sign_iter,
- &rfc);
- if (0 !=
- memcmp (&key_state->current_sign_key_issue.issue.master_public_key,
- &TEH_master_public_key,
- sizeof (struct TALER_MasterPublicKeyP)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Have no signing key. Bad configuration.\n");
- ks_free (key_state);
- destroy_response_factory (&rfc);
- return NULL;
- }
-
- /* sanity check */
- if (0 == GNUNET_CONTAINER_multihashmap_size (key_state->denomkey_map))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Have no denomination keys. Bad configuration.\n");
- ks_free (key_state);
- destroy_response_factory (&rfc);
- return NULL;
- }
-
- /* Initialize and sort the `denomkey_array` */
- rfc.denomkey_array
- = GNUNET_new_array (GNUNET_CONTAINER_multihashmap_size (
- key_state->denomkey_map),
- struct DenominationKeyEntry);
- GNUNET_CONTAINER_multihashmap_iterate (key_state->denomkey_map,
- &initialize_denomkey_array,
- &rfc);
- GNUNET_assert (rfc.denomkey_array_length ==
- GNUNET_CONTAINER_multihashmap_size (key_state->denomkey_map));
- qsort (rfc.denomkey_array,
- rfc.denomkey_array_length,
- sizeof (struct DenominationKeyEntry),
- &denomkey_array_sort_comparator);
-
- /* Complete `denomkey_array` by adding auditor signature data */
- TALER_EXCHANGEDB_auditor_iterate (TEH_cfg,
- &reload_auditor_iter,
- &rfc);
- /* Sanity check: do we have auditors for all denomination keys? */
- for (unsigned int i = 0; i<rfc.denomkey_array_length; i++)
- {
- const struct DenominationKeyEntry *dke
- = &rfc.denomkey_array[i];
-
- if (NULL == dke->as_head)
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Denomination key `%s' at %p not signed by any auditor!\n",
- GNUNET_h2s (&dke->denom_key_hash),
- dke);
- }
-
- /* Determine size of `krd_array` by counting number of discrete
- denomination key starting times. */
- last = GNUNET_TIME_UNIT_ZERO_ABS;
- key_state->krd_array_length = 0;
- off = 1; /* reserve one slot for the "no keys" response */
- for (unsigned int i = 0; i<rfc.denomkey_array_length; i++)
- {
- const struct DenominationKeyEntry *dke
- = &rfc.denomkey_array[i];
- struct GNUNET_TIME_Absolute d
- = GNUNET_TIME_absolute_ntoh (dke->dki->issue.properties.start);
-
- if (last.abs_value_us == d.abs_value_us)
- continue;
- /* must be monotonically increasing as per qsort() call above: */
- GNUNET_assert (last.abs_value_us < d.abs_value_us);
- last = d;
- off++;
- }
-
- /* Compute next automatic reload time */
- key_state->next_reload =
- GNUNET_TIME_absolute_min (GNUNET_TIME_absolute_ntoh (
- key_state->current_sign_key_issue.issue.expire),
- key_state->min_dk_expire);
- GNUNET_assert (0 != key_state->next_reload.abs_value_us);
-
-
- /* Initialize `krd_array` */
- key_state->krd_array_length = off;
- key_state->krd_array
- = GNUNET_new_array (key_state->krd_array_length,
- struct KeysResponseData);
- off = 0;
- last = GNUNET_TIME_UNIT_ZERO_ABS;
- for (unsigned int i = 0; i<rfc.denomkey_array_length; i++)
- {
- const struct DenominationKeyEntry *dke
- = &rfc.denomkey_array[i];
- struct GNUNET_TIME_Absolute d
- = GNUNET_TIME_absolute_ntoh (dke->dki->issue.properties.start);
-
- if (last.abs_value_us == d.abs_value_us)
- continue;
- if (GNUNET_OK !=
- build_keys_response (&rfc,
- &key_state->krd_array[off++],
- i,
- last))
- {
- /* Fail hard, will be caught via test on `off` below */
- GNUNET_break (0);
- off = key_state->krd_array_length; /* flag as 'invalid' */
- break;
- }
- last = d;
- }
-
- /* Finally, build an `empty` response without denomination keys
- for requests past the last known denomination key start date */
- if ( (off + 1 < key_state->krd_array_length) ||
- (GNUNET_OK !=
- build_keys_response (&rfc,
- &key_state->krd_array[off++],
- rfc.denomkey_array_length,
- last)) )
- {
- GNUNET_break (0);
- ks_free (key_state);
- destroy_response_factory (&rfc);
- return NULL;
- }
-
- /* Clean up intermediary state we don't need anymore and return
- new key_state! */
- destroy_response_factory (&rfc);
- return key_state;
-}
-
-
-/* ************************** Persistent part ********************** */
-
-/**
- * Release key state, free if necessary (if reference count gets to zero).
- *
- * @param location name of the function in which the lock is acquired
- * @param key_state the key state to release
- */
-void
-TEH_KS_release_ (const char *location,
- struct TEH_KS_StateHandle *key_state)
-{
- int do_free;
-
- GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "KS released at %s (%p/%d)\n",
- location,
- key_state,
- key_state->refcnt);
- GNUNET_assert (0 < key_state->refcnt);
- key_state->refcnt--;
- do_free = (0 == key_state->refcnt);
- GNUNET_assert ( (! do_free) ||
- (key_state != internal_key_state) );
- GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex));
- if (do_free)
- ks_free (key_state);
-}
-
-
-/**
- * Acquire the key state of the exchange. Updates keys if necessary.
- * For every call to #TEH_KS_acquire(), a matching call
- * to #TEH_KS_release() must be made.
- *
- * @param now for what timestamp should we acquire the key state
- * @param location name of the function in which the lock is acquired
- * @return the key state, NULL on error (usually pretty fatal)
- */
-struct TEH_KS_StateHandle *
-TEH_KS_acquire_ (struct GNUNET_TIME_Absolute now,
- const char *location)
-{
- struct TEH_KS_StateHandle *key_state;
- struct TEH_KS_StateHandle *os;
-
- os = NULL;
- GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex));
- /* If the current internal key state is missing (failed to load one on
- startup?) or expired, we try to setup a fresh one even without having
- gotten SIGUSR1 */
- if ( ( (NULL != internal_key_state) &&
- (internal_key_state->next_reload.abs_value_us <= now.abs_value_us) ) ||
- (NULL == internal_key_state) )
- {
- os = internal_key_state;
- internal_key_state = make_fresh_key_state (now);
- if (NULL != internal_key_state)
- internal_key_state->refcnt = 1; /* alias from #internal_key_state */
- if (NULL != os)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "KS released in acquire due to expiration\n");
- GNUNET_assert (0 < os->refcnt);
- os->refcnt--; /* #internal_key_state alias dropped */
- if (0 != os->refcnt)
- os = NULL; /* do NOT release yet, otherwise release after unlocking */
- }
- }
- if (NULL == internal_key_state)
- {
- /* We tried and failed to setup #internal_key_state */
- GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex));
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to initialize key state\n");
- if (NULL != os)
- ks_free (os);
- return NULL;
- }
- key_state = internal_key_state;
- key_state->refcnt++; /* returning an alias, increment RC */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "KS acquired at %s (%p/%d)\n",
- location,
- key_state,
- key_state->refcnt);
- GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex));
- if (NULL != os)
- ks_free (os);
- return key_state;
-}
-
-
-/**
- * Look up the issue for a denom public key. Note that the result
- * is only valid while the @a key_state is not released!
- *
- * @param key_state state to look in
- * @param denom_pub_hash hash of denomination public key
- * @param use purpose for which the key is being located
- * @param[out] ec set to the error code, in case the operation failed
- * @param[out] hc set to the HTTP status code to use
- * @return the denomination key issue,
- * or NULL if denom_pub could not be found (or is not valid at this time for the given @a use)
- */
-struct TALER_EXCHANGEDB_DenominationKey *
-TEH_KS_denomination_key_lookup_by_hash (
- const struct TEH_KS_StateHandle *key_state,
- const struct GNUNET_HashCode *denom_pub_hash,
- enum TEH_KS_DenominationKeyUse use,
- enum TALER_ErrorCode *ec,
- unsigned int *hc)
-{
- struct TALER_EXCHANGEDB_DenominationKey *dki;
- struct GNUNET_TIME_Absolute now;
- const struct GNUNET_CONTAINER_MultiHashMap *map;
-
- map = (TEH_KS_DKU_RECOUP == use) ? key_state->revoked_map :
- key_state->denomkey_map;
- dki = GNUNET_CONTAINER_multihashmap_get (map,
- denom_pub_hash);
- if ( (NULL == dki) && (TEH_KS_DKU_ZOMBIE == use))
- dki = GNUNET_CONTAINER_multihashmap_get (key_state->revoked_map,
- denom_pub_hash);
- if (NULL == dki)
- {
- *hc = MHD_HTTP_NOT_FOUND;
- switch (use)
- {
- case TEH_KS_DKU_RECOUP:
- *ec = TALER_EC_RECOUP_DENOMINATION_KEY_UNKNOWN;
- break;
- case TEH_KS_DKU_ZOMBIE:
- *ec = TALER_EC_REFRESH_RECOUP_DENOMINATION_KEY_NOT_FOUND;
- break;
- case TEH_KS_DKU_WITHDRAW:
- *ec = TALER_EC_WITHDRAW_DENOMINATION_KEY_NOT_FOUND;
- break;
- case TEH_KS_DKU_DEPOSIT:
- *ec = TALER_EC_DEPOSIT_DENOMINATION_KEY_UNKNOWN;
- break;
- }
- return NULL;
- }
- now = GNUNET_TIME_absolute_get ();
- if (now.abs_value_us <
- GNUNET_TIME_absolute_ntoh (dki->issue.properties.start).abs_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not returning DKI for %s, as start time is in the future\n",
- GNUNET_h2s (denom_pub_hash));
- *hc = MHD_HTTP_PRECONDITION_FAILED;
- switch (use)
- {
- case TEH_KS_DKU_RECOUP:
- *ec = TALER_EC_RECOUP_DENOMINATION_VALIDITY_IN_FUTURE;
- break;
- case TEH_KS_DKU_ZOMBIE:
- *ec = TALER_EC_REFRESH_RECOUP_DENOMINATION_VALIDITY_IN_FUTURE;
- break;
- case TEH_KS_DKU_WITHDRAW:
- *ec = TALER_EC_WITHDRAW_VALIDITY_IN_FUTURE;
- break;
- case TEH_KS_DKU_DEPOSIT:
- *ec = TALER_EC_DEPOSIT_DENOMINATION_VALIDITY_IN_FUTURE;
- break;
- }
- return NULL;
- }
- now = GNUNET_TIME_absolute_get ();
- switch (use)
- {
- case TEH_KS_DKU_WITHDRAW:
- if (now.abs_value_us >
- GNUNET_TIME_absolute_ntoh (
- dki->issue.properties.expire_withdraw).abs_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not returning DKI for %s, as time to create coins has passed\n",
- GNUNET_h2s (denom_pub_hash));
- *ec = TALER_EC_WITHDRAW_VALIDITY_IN_PAST;
- *hc = MHD_HTTP_GONE;
- return NULL;
- }
- if (NULL == dki->denom_priv.rsa_private_key)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Not returning DKI of %s for WITHDRAW operation as we lack the private key, even though the withdraw period did not yet expire!\n",
- GNUNET_h2s (denom_pub_hash));
- *ec = TALER_EC_DENOMINATION_KEY_LOST;
- *hc = MHD_HTTP_SERVICE_UNAVAILABLE;
- return NULL;
- }
- break;
- case TEH_KS_DKU_DEPOSIT:
- if (now.abs_value_us >
- GNUNET_TIME_absolute_ntoh (
- dki->issue.properties.expire_deposit).abs_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not returning DKI for %s, as time to spend coin has passed\n",
- GNUNET_h2s (denom_pub_hash));
- *ec = TALER_EC_DEPOSIT_DENOMINATION_EXPIRED;
- *hc = MHD_HTTP_GONE;
- return NULL;
- }
- break;
- case TEH_KS_DKU_RECOUP:
- if (now.abs_value_us >
- GNUNET_TIME_absolute_ntoh (
- dki->issue.properties.expire_deposit).abs_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not returning DKI for %s, as time to recoup coin has passed\n",
- GNUNET_h2s (denom_pub_hash));
- *ec = TALER_EC_REFRESH_RECOUP_DENOMINATION_EXPIRED;
- *hc = MHD_HTTP_GONE;
- return NULL;
- }
- break;
- case TEH_KS_DKU_ZOMBIE:
- if (now.abs_value_us >
- GNUNET_TIME_absolute_ntoh (
- dki->issue.properties.expire_legal).abs_value_us)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not returning DKI for %s, as legal expiration of coin has passed\n",
- GNUNET_h2s (denom_pub_hash));
- *ec = TALER_EC_REFRESH_ZOMBIE_DENOMINATION_EXPIRED;
- *hc = MHD_HTTP_GONE;
- return NULL;
- }
- break;
- }
- return dki;
-}
-
-
-/**
- * Call #handle_signal() to pass the received signal via
- * the control pipe.
- */
-static void
-handle_sigusr1 (void)
-{
- handle_signal (SIGUSR1);
-}
-
-
-/**
- * Call #handle_signal() to pass the received signal via
- * the control pipe.
- */
-static void
-handle_sigint (void)
-{
- handle_signal (SIGINT);
-}
-
-
-/**
- * Call #handle_signal() to pass the received signal via
- * the control pipe.
- */
-static void
-handle_sigterm (void)
-{
- handle_signal (SIGTERM);
-}
-
-
-/**
- * Call #handle_signal() to pass the received signal via
- * the control pipe.
- */
-static void
-handle_sighup (void)
-{
- handle_signal (SIGHUP);
-}
-
-
-/**
- * Call #handle_signal() to pass the received signal via
- * the control pipe.
- */
-static void
-handle_sigchld (void)
-{
- handle_signal (SIGCHLD);
-}
-
-
-/**
- * Read signals from a pipe in a loop, and reload keys from disk if
- * SIGUSR1 is received, terminate if SIGTERM/SIGINT is received, and
- * restart if SIGHUP is received.
- *
- * @return #GNUNET_SYSERR on errors,
- * #GNUNET_OK to terminate normally
- * #GNUNET_NO to restart an update version of the binary
- */
-int
-TEH_KS_loop (void)
-{
- int ret;
-
- ret = 2;
- while (2 == ret)
- {
- char c;
- ssize_t res;
-
- errno = 0;
- res = read (reload_pipe[0],
- &c,
- 1);
- if ((res < 0) && (EINTR != errno))
- {
- GNUNET_break (0);
- ret = GNUNET_SYSERR;
- break;
- }
- if (EINTR == errno)
- continue;
- switch (c)
- {
- case SIGUSR1:
- {
- struct TEH_KS_StateHandle *fs;
- struct TEH_KS_StateHandle *os;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "(re-)loading keys\n");
- /* Create fresh key state before critical region */
- fs = make_fresh_key_state (GNUNET_TIME_absolute_get ());
- if (NULL == fs)
- {
- /* Ok, that went badly, terminate process */
- ret = GNUNET_SYSERR;
- break;
- }
- fs->refcnt = 1; /* we'll alias from #internal_key_state soon */
- /* swap active key state in critical region */
- GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex));
- os = internal_key_state;
- internal_key_state = fs;
- if (NULL != os)
- {
- GNUNET_assert (0 < os->refcnt);
- os->refcnt--; /* removed #internal_key_state reference */
- if (0 != os->refcnt)
- os = NULL; /* other aliases are still active, do not yet free */
- }
- GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex));
- if (NULL != os)
- ks_free (os); /* RC did hit zero, free */
- }
- break;
- case SIGTERM:
- case SIGINT:
- /* terminate */
- ret = GNUNET_OK;
- break;
- case SIGHUP:
- /* restart updated binary */
- ret = GNUNET_NO;
- break;
-#if HAVE_DEVELOPER
- case SIGCHLD:
- /* running in test-mode, test finished, terminate */
- ret = GNUNET_OK;
- break;
-#endif
- default:
- /* unexpected character */
- GNUNET_break (0);
- break;
- }
- }
- return ret;
-}
-
-
-static struct GNUNET_SIGNAL_Context *sigusr1;
-static struct GNUNET_SIGNAL_Context *sigterm;
-static struct GNUNET_SIGNAL_Context *sigint;
-static struct GNUNET_SIGNAL_Context *sighup;
-static struct GNUNET_SIGNAL_Context *sigchld;
-
-
-/**
- * Setup initial #internal_key_state and our signal handlers.
- */
-int
-TEH_KS_init (void)
-{
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
- "exchange",
- "LOOKAHEAD_PROVIDE",
- &conf_duration_provide))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "LOOKAHEAD_PROVIDE",
- "time value required");
- return GNUNET_SYSERR;
- }
- if (0 == conf_duration_provide.rel_value_us)
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "LOOKAHEAD_PROVIDE",
- "value cannot be zero");
- return GNUNET_SYSERR;
- }
- if (0 != pipe (reload_pipe))
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "pipe");
- return GNUNET_SYSERR;
- }
- sigusr1 = GNUNET_SIGNAL_handler_install (SIGUSR1,
- &handle_sigusr1);
- sigterm = GNUNET_SIGNAL_handler_install (SIGTERM,
- &handle_sigterm);
- sigint = GNUNET_SIGNAL_handler_install (SIGINT,
- &handle_sigint);
- sighup = GNUNET_SIGNAL_handler_install (SIGHUP,
- &handle_sighup);
- sigchld = GNUNET_SIGNAL_handler_install (SIGCHLD,
- &handle_sigchld);
- /* no need to lock here, as we are still single-threaded */
- internal_key_state = make_fresh_key_state (GNUNET_TIME_absolute_get ());
- if (NULL == internal_key_state)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to setup initial key state. This exchange cannot work.\n");
- return GNUNET_SYSERR;
- }
- internal_key_state->refcnt = 1;
- return GNUNET_OK;
-}
-
-
-/**
- * Finally release #internal_key_state and our signal handlers.
- */
-void
-TEH_KS_free (void)
-{
- struct TEH_KS_StateHandle *ks;
-
- /* Note: locking is no longer be required, as we are again
- single-threaded. */
- ks = internal_key_state;
- if (NULL != ks)
- {
- GNUNET_assert (1 == ks->refcnt);
- ks->refcnt--;
- ks_free (ks);
- }
- if (NULL != sigusr1)
- {
- GNUNET_SIGNAL_handler_uninstall (sigusr1);
- sigusr1 = NULL;
- }
- if (NULL != sigterm)
- {
- GNUNET_SIGNAL_handler_uninstall (sigterm);
- sigterm = NULL;
- }
- if (NULL != sigint)
- {
- GNUNET_SIGNAL_handler_uninstall (sigint);
- sigint = NULL;
- }
- if (NULL != sighup)
- {
- GNUNET_SIGNAL_handler_uninstall (sighup);
- sighup = NULL;
- }
- if (NULL != sigchld)
- {
- GNUNET_SIGNAL_handler_uninstall (sigchld);
- sigchld = NULL;
- }
- if (-1 != reload_pipe[0])
- {
- GNUNET_break (0 == close (reload_pipe[0]));
- GNUNET_break (0 == close (reload_pipe[1]));
- reload_pipe[0] = reload_pipe[1] = -1;
- }
-}
-
-
-/**
- * Sign the message in @a purpose with the exchange's signing key.
- *
- * @param purpose the message to sign
- * @param[out] pub set to the current public signing key of the exchange
- * @param[out] sig signature over purpose using current signing key
- * @return #GNUNET_OK on success, #GNUNET_SYSERR if we lack key material
- */
-int
-TEH_KS_sign (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
- struct TALER_ExchangePublicKeyP *pub,
- struct TALER_ExchangeSignatureP *sig)
-
-{
- struct TEH_KS_StateHandle *key_state;
-
- key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
- if (NULL == key_state)
- {
- /* This *can* happen if the exchange's keys are not properly maintained
- (i.e. administrator forgets to provision us with non-expired signing
- keys or to send signal to reload keys after provisioning). */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Cannot sign request, no valid signing keys available. Were they properly provisioned and did you signal the exchange to reload the keys?\n");
- return GNUNET_SYSERR;
- }
- *pub = key_state->current_sign_key_issue.issue.signkey_pub;
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (
- &key_state->current_sign_key_issue.signkey_priv.eddsa_priv,
- purpose,
- &sig->eddsa_signature));
- TEH_KS_release (key_state);
- return GNUNET_OK;
-}
-
-
-/**
- * Comparator used for a binary search by cherry_pick_date for @a key in the
- * `struct KeysResponseData` array. See libc's qsort() and bsearch() functions.
- *
- * @param key pointer to a `struct GNUNET_TIME_Absolute`
- * @param value pointer to a `struct KeysResponseData` array entry
- * @return 0 if time matches, -1 if key is smaller, 1 if key is larger
- */
-static int
-krd_search_comparator (const void *key,
- const void *value)
-{
- const struct GNUNET_TIME_Absolute *kd = key;
- const struct KeysResponseData *krd = value;
-
- if (kd->abs_value_us > krd->cherry_pick_date.abs_value_us)
- return 1;
- if (kd->abs_value_us < krd->cherry_pick_date.abs_value_us)
- return -1;
- return 0;
-}
-
-
-/**
- * Function to call to handle the request by sending
- * back static data from the @a rh.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param args array of additional options (must be empty for this function)
- * @return MHD result code
- */
-int
-TEH_handler_keys (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
- const char *const args[])
-{
- int ret;
- const char *have_cherrypick;
- const char *have_fakenow;
- struct GNUNET_TIME_Absolute last_issue_date;
- struct GNUNET_TIME_Absolute now;
- const struct KeysResponseData *krd;
-
- (void) rh;
- (void) args;
- have_cherrypick = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "last_issue_date");
- if (NULL != have_cherrypick)
- {
- unsigned long long cherrypickn;
-
- if (1 !=
- sscanf (have_cherrypick,
- "%llu",
- &cherrypickn))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_KEYS_HAVE_NOT_NUMERIC,
- "last_issue_date");
- }
- /* The following multiplication may overflow; but this should not really
- be a problem, as giving back 'older' data than what the client asks for
- (given that the client asks for data in the distant future) is not
- problematic */
- last_issue_date.abs_value_us = (uint64_t) cherrypickn * 1000000LLU;
- }
- else
- {
- last_issue_date.abs_value_us = 0LLU;
- }
- now = GNUNET_TIME_absolute_get ();
- have_fakenow = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "now");
- if (NULL != have_fakenow)
- {
- unsigned long long fakenown;
-
- if (1 !=
- sscanf (have_fakenow,
- "%llu",
- &fakenown))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_KEYS_HAVE_NOT_NUMERIC,
- "now");
- }
- if (TEH_allow_keys_timetravel)
- {
- /* The following multiplication may overflow; but this should not really
- be a problem, as giving back 'older' data than what the client asks for
- (given that the client asks for data in the distant future) is not
- problematic */
- now.abs_value_us = (uint64_t) fakenown * 1000000LLU;
- }
- else
- {
- /* Option not allowed by configuration */
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_KEYS_TIMETRAVEL_FORBIDDEN,
- "timetravel not allowed by this exchange");
- }
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Handling request for /keys (%s/%s)\n",
- have_cherrypick,
- have_fakenow);
- {
- struct TEH_KS_StateHandle *key_state;
-
- key_state = TEH_KS_acquire (now);
- if (NULL == key_state)
- {
- /* Maybe client picked time stamp too far in the future? In that case,
- #MHD_HTTP_INTERNAL_SERVER_ERROR might be misleading, could be more like a
- NOT_FOUND situation. But, OTOH, for 'sane' clients it is more likely
- to be our fault, so let's speculatively assume we are to blame ;-) */
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_BAD_CONFIGURATION,
- "no keys for requested time");
- }
- krd = bsearch (&last_issue_date,
- key_state->krd_array,
- key_state->krd_array_length,
- sizeof (struct KeysResponseData),
- &krd_search_comparator);
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Filtering /keys by cherry pick date %s found entry %u/%u\n",
- GNUNET_STRINGS_absolute_time_to_string (last_issue_date),
- (unsigned int) (krd - key_state->krd_array),
- key_state->krd_array_length);
- if ( (NULL == krd) &&
- (key_state->krd_array_length > 0) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Client provided invalid cherry picking timestamp %s, returning full response\n",
- GNUNET_STRINGS_absolute_time_to_string (last_issue_date));
- krd = &key_state->krd_array[0];
- }
- if (NULL == krd)
- {
- /* Maybe client picked time stamp too far in the future? In that case,
- "INTERNAL_SERVER_ERROR" might be misleading, could be more like a
- NOT_FOUND situation. But, OTOH, for 'sane' clients it is more likely
- to be our fault, so let's speculatively assume we are to blame ;-) *///
- GNUNET_break (0);
- TEH_KS_release (key_state);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_KEYS_MISSING,
- "no key response found");
- }
- ret = MHD_queue_response (connection,
- MHD_HTTP_OK,
- (MHD_YES == TALER_MHD_can_compress (connection))
- ? krd->response_compressed
- : krd->response_uncompressed);
- TEH_KS_release (key_state);
- }
- return ret;
-}
-
-
-/* end of taler-exchange-httpd_keystate.c */
diff --git a/src/exchange/taler-exchange-httpd_keystate.h b/src/exchange/taler-exchange-httpd_keystate.h
deleted file mode 100644
index 671652387..000000000
--- a/src/exchange/taler-exchange-httpd_keystate.h
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014, 2015 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 exchange/taler-exchange-httpd_keystate.h
- * @brief management of our private signing keys (denomination keys)
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_KEYSTATE_H
-#define TALER_EXCHANGE_HTTPD_KEYSTATE_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-#include "taler_error_codes.h"
-#include "taler_exchangedb_lib.h"
-
-
-/**
- * Snapshot of the (coin and signing)
- * keys (including private keys) of the exchange.
- */
-struct TEH_KS_StateHandle;
-
-
-/**
- * Acquire the key state of the exchange. Updates keys if necessary.
- * For every call to #TEH_KS_acquire(), a matching call
- * to #TEH_KS_release() must be made.
- *
- * @param now for what timestamp should we acquire the key state
- * @param location name of the function in which the lock is acquired
- * @return the key state, NULL on error (usually pretty fatal)
- */
-struct TEH_KS_StateHandle *
-TEH_KS_acquire_ (struct GNUNET_TIME_Absolute now,
- const char *location);
-
-
-/**
- * Release key state, free if necessary (if reference count gets to zero).
- *
- * @param location name of the function in which the lock is acquired
- * @param key_state the key state to release
- */
-void
-TEH_KS_release_ (const char *location,
- struct TEH_KS_StateHandle *key_state);
-
-
-/**
- * Acquire the key state of the exchange. Updates keys if necessary.
- * For every call to #TEH_KS_acquire(), a matching call
- * to #TEH_KS_release() must be made.
- *
- * @param now current time snapshot; either true, or given by the
- * client via the "now" URL parameter of "/keys".
- * @return the key state
- */
-#define TEH_KS_acquire(now) TEH_KS_acquire_ (now, __FUNCTION__)
-
-
-/**
- * Release key state, free if necessary (if reference count gets to zero).
- *
- * @param key_state the key state to release
- */
-#define TEH_KS_release(key_state) TEH_KS_release_ (__FUNCTION__, key_state)
-
-
-/**
- * Setup initial #internal_key_state and our signal handlers.
- *
- * @return #GNUNET_OK on success
- */
-int
-TEH_KS_init (void);
-
-
-/**
- * Finally, release #internal_key_state and our signal handlers.
- */
-void
-TEH_KS_free (void);
-
-
-/**
- * Denomination key lookups can be for signing of fresh coins
- * or to validate signatures on existing coins. As the validity
- * periods for a key differ, the caller must specify which
- * use is relevant for the current operation.
- */
-enum TEH_KS_DenominationKeyUse
-{
-
- /**
- * The denomination key is to be used for a withdraw or reveal operation.
- */
- TEH_KS_DKU_WITHDRAW,
-
- /**
- * The denomination key is to be used for a deposit or melt operation.
- */
- TEH_KS_DKU_DEPOSIT,
-
- /**
- * The denomination key is to be used for a recoup operation, or to
- * melt a coin that was deposited (or melted) before the revocation.
- */
- TEH_KS_DKU_RECOUP,
-
- /**
- * The key is to be used for a refresh + recoup operation,
- * i.e. it is an old coin that regained value from a
- * recoup on a new coin derived from the old coin.
- */
- TEH_KS_DKU_ZOMBIE
-
-};
-
-
-/**
- * Look up the issue for a denom public key. Note that the result
- * is only valid while the @a key_state is not released!
- *
- * @param key_state state to look in
- * @param denom_pub_hash hash of denomination public key
- * @param use purpose for which the key is being located
- * @param[out] ec set to the error code, in case the operation failed
- * @param[out] hc set to the HTTP status code to use
- * @return the denomination key issue,
- * or NULL if denom_pub could not be found (or is not valid at this time for the given @a use)
- */
-struct TALER_EXCHANGEDB_DenominationKey *
-TEH_KS_denomination_key_lookup_by_hash (
- const struct TEH_KS_StateHandle *key_state,
- const struct GNUNET_HashCode *denom_pub_hash,
- enum TEH_KS_DenominationKeyUse use,
- enum TALER_ErrorCode *ec,
- unsigned int *hc);
-
-
-/**
- * Read signals from a pipe in a loop, and reload keys from disk if
- * SIGUSR1 is received, terminate if SIGTERM/SIGINT is received, and
- * restart if SIGHUP is received.
- *
- * @return #GNUNET_SYSERR on errors,
- * #GNUNET_OK to terminate normally
- * #GNUNET_NO to restart an update version of the binary
- */
-int
-TEH_KS_loop (void);
-
-
-/**
- * Sign the message in @a purpose with the exchange's signing
- * key.
- *
- * @param purpose the message to sign
- * @param[out] pub set to the current public signing key of the exchange
- * @param[out] sig signature over purpose using current signing key
- * @return #GNUNET_OK on success, #GNUNET_SYSERR if we lack key material
- */
-int
-TEH_KS_sign (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
- struct TALER_ExchangePublicKeyP *pub,
- struct TALER_ExchangeSignatureP *sig);
-
-
-/**
- * Handle a "/keys" request
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param args array of additional options (must be empty for this function)
- * @return MHD result code
- */
-int
-TEH_handler_keys (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
- const char *const args[]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_kyc-check.c b/src/exchange/taler-exchange-httpd_kyc-check.c
new file mode 100644
index 000000000..362c20a2e
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_kyc-check.c
@@ -0,0 +1,710 @@
+/*
+ This file is part of TALER
+ 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
+ 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_kyc-check.c
+ * @brief Handle request for generic KYC check.
+ * @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_kyclogic_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_signatures.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_kyc-wallet.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Reserve GET request that is long-polling.
+ */
+struct KycPoller
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct KycPoller *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct KycPoller *prev;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Logic for @e ih
+ */
+ struct TALER_KYCLOGIC_Plugin *ih_logic;
+
+ /**
+ * Handle to asynchronously running KYC initiation
+ * request.
+ */
+ struct TALER_KYCLOGIC_InitiateHandle *ih;
+
+ /**
+ * Subscription for the database event we are
+ * waiting for.
+ */
+ struct GNUNET_DB_EventHandler *eh;
+
+ /**
+ * Row of the requirement being checked.
+ */
+ uint64_t requirement_row;
+
+ /**
+ * Row of KYC process being initiated.
+ */
+ uint64_t process_row;
+
+ /**
+ * Hash of the payto:// URI we are confirming to
+ * have finished the KYC for.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * When will this request time out?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * If the KYC complete, what kind of data was collected?
+ */
+ json_t *kyc_details;
+
+ /**
+ * Set to starting URL of KYC process if KYC is required.
+ */
+ char *kyc_url;
+
+ /**
+ * Set to error details, on error (@ec not TALER_EC_NONE).
+ */
+ char *hint;
+
+ /**
+ * Name of the section of the provider in the configuration.
+ */
+ 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.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * What kind of entity is doing the KYC check?
+ */
+ enum TALER_KYCLOGIC_KycUserType ut;
+
+ /**
+ * True if we are still suspended.
+ */
+ bool suspended;
+
+ /**
+ * False if KYC is not required.
+ */
+ bool kyc_required;
+
+ /**
+ * True if we once tried the KYC initiation.
+ */
+ bool ih_done;
+
+};
+
+
+/**
+ * Head of list of requests in long polling.
+ */
+static struct KycPoller *kyp_head;
+
+/**
+ * Tail of list of requests in long polling.
+ */
+static struct KycPoller *kyp_tail;
+
+
+void
+TEH_kyc_check_cleanup ()
+{
+ struct KycPoller *kyp;
+
+ while (NULL != (kyp = kyp_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (kyp_head,
+ kyp_tail,
+ kyp);
+ if (NULL != kyp->ih)
+ {
+ kyp->ih_logic->initiate_cancel (kyp->ih);
+ kyp->ih = NULL;
+ }
+ if (kyp->suspended)
+ {
+ kyp->suspended = false;
+ MHD_resume_connection (kyp->connection);
+ }
+ }
+}
+
+
+/**
+ * Function called once a connection is done to
+ * clean up the `struct ReservePoller` state.
+ *
+ * @param rc context to clean up for
+ */
+static void
+kyp_cleanup (struct TEH_RequestContext *rc)
+{
+ struct KycPoller *kyp = rc->rh_ctx;
+
+ GNUNET_assert (! kyp->suspended);
+ if (NULL != kyp->eh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Cancelling DB event listening\n");
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ kyp->eh);
+ kyp->eh = NULL;
+ }
+ if (NULL != kyp->ih)
+ {
+ kyp->ih_logic->initiate_cancel (kyp->ih);
+ kyp->ih = NULL;
+ }
+ json_decref (kyp->kyc_details);
+ GNUNET_free (kyp->kyc_url);
+ GNUNET_free (kyp->hint);
+ GNUNET_free (kyp);
+}
+
+
+/**
+ * Function called with the result of a KYC initiation
+ * operation.
+ *
+ * @param cls closure with our `struct KycPoller *`
+ * @param ec #TALER_EC_NONE on success
+ * @param redirect_url set to where to redirect the user on success, NULL on failure
+ * @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 error_msg_hint set to additional details to return to user, NULL on success
+ */
+static void
+initiate_cb (
+ void *cls,
+ enum TALER_ErrorCode ec,
+ const char *redirect_url,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ const char *error_msg_hint)
+{
+ struct KycPoller *kyp = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ kyp->ih = NULL;
+ kyp->ih_done = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC initiation `%s' completed with ec=%d (%s)\n",
+ provider_legitimization_id,
+ ec,
+ (TALER_EC_NONE == ec)
+ ? redirect_url
+ : error_msg_hint);
+ kyp->ec = ec;
+ if (TALER_EC_NONE == ec)
+ {
+ kyp->kyc_url = GNUNET_strdup (redirect_url);
+ }
+ else
+ {
+ kyp->hint = GNUNET_strdup (error_msg_hint);
+ }
+ qs = TEH_plugin->update_kyc_process_by_row (
+ TEH_plugin->cls,
+ kyp->process_row,
+ kyp->section_name,
+ &kyp->h_payto,
+ provider_user_id,
+ provider_legitimization_id,
+ redirect_url,
+ GNUNET_TIME_UNIT_ZERO_ABS);
+ if (qs <= 0)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYC requirement update failed for %s with status %d at %s:%u\n",
+ TALER_B2S (&kyp->h_payto),
+ qs,
+ __FILE__,
+ __LINE__);
+ GNUNET_assert (kyp->suspended);
+ kyp->suspended = false;
+ GNUNET_CONTAINER_DLL_remove (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_resume_connection (kyp->connection);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+/**
+ * Function implementing database transaction to check wallet's KYC status.
+ * 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 KycPoller *`
+ * @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
+kyc_check (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct KycPoller *kyp = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+ enum GNUNET_GenericReturnValue ret;
+ struct TALER_PaytoHashP h_payto;
+ char *requirements;
+ char *redirect_url;
+ 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)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No KYC requirements open for %llu\n",
+ (unsigned long long) kyp->requirement_row);
+ return qs;
+ }
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+ return qs;
+ }
+ if (0 !=
+ GNUNET_memcmp (&kyp->h_payto,
+ &h_payto))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Requirement %llu provided, but h_payto does not match\n",
+ (unsigned long long) kyp->requirement_row);
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED,
+ "h_payto");
+ GNUNET_free (requirements);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ qs = TALER_KYCLOGIC_check_satisfied (
+ &requirements,
+ &h_payto,
+ &kyp->kyc_details,
+ TEH_plugin->select_satisfied_kyc_processes,
+ TEH_plugin->cls,
+ &satisfied);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "kyc_test_required");
+ GNUNET_free (requirements);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (satisfied)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC requirements `%s' already satisfied\n",
+ requirements);
+ GNUNET_free (requirements);
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+
+ kyp->kyc_required = true;
+ ret = TALER_KYCLOGIC_requirements_to_logic (requirements,
+ kyp->ut,
+ &kyp->ih_logic,
+ &pd,
+ &kyp->section_name);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYC requirements `%s' cannot be checked, but are set as required in database!\n",
+ requirements);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_GONE,
+ requirements);
+ GNUNET_free (requirements);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_free (requirements);
+
+ if (kyp->ih_done)
+ return qs;
+ qs = TEH_plugin->get_pending_kyc_requirement_process (
+ TEH_plugin->cls,
+ &h_payto,
+ kyp->section_name,
+ &redirect_url);
+ 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;
+ }
+ if ( (qs > 0) &&
+ (NULL != redirect_url) )
+ {
+ kyp->kyc_url = redirect_url;
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* set up new requirement process */
+ qs = TEH_plugin->insert_kyc_requirement_process (
+ TEH_plugin->cls,
+ &h_payto,
+ kyp->section_name,
+ 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);
+ kyp->ih = kyp->ih_logic->initiate (kyp->ih_logic->cls,
+ pd,
+ &h_payto,
+ kyp->process_row,
+ &initiate_cb,
+ kyp);
+ GNUNET_break (NULL != kyp->ih);
+ return qs;
+}
+
+
+/**
+ * Function called on events received from Postgres.
+ * Wakes up long pollers.
+ *
+ * @param cls the `struct TEH_RequestContext *`
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ struct TEH_RequestContext *rc = cls;
+ struct KycPoller *kyp = rc->rh_ctx;
+ struct GNUNET_AsyncScopeSave old_scope;
+
+ (void) extra;
+ (void) extra_size;
+ if (! kyp->suspended)
+ return; /* event triggered while main transaction
+ was still running, or got multiple wake-up events */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received KYC update event\n");
+ kyp->suspended = false;
+ GNUNET_async_scope_enter (&rc->async_scope_id,
+ &old_scope);
+ TEH_check_invariants ();
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming from long-polling on KYC status\n");
+ GNUNET_CONTAINER_DLL_remove (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_resume_connection (kyp->connection);
+ TALER_MHD_daemon_trigger ();
+ TEH_check_invariants ();
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
+MHD_RESULT
+TEH_handler_kyc_check (
+ struct TEH_RequestContext *rc,
+ const char *const args[3])
+{
+ struct KycPoller *kyp = rc->rh_ctx;
+ MHD_RESULT res;
+ enum GNUNET_GenericReturnValue ret;
+ struct GNUNET_TIME_Timestamp now;
+
+ if (NULL == kyp)
+ {
+ kyp = GNUNET_new (struct KycPoller);
+ kyp->connection = rc->connection;
+ rc->rh_ctx = kyp;
+ rc->rh_cleaner = &kyp_cleanup;
+
+ {
+ unsigned long long requirement_row;
+ char dummy;
+
+ if (1 !=
+ sscanf (args[0],
+ "%llu%c",
+ &requirement_row,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "requirement_row");
+ }
+ kyp->requirement_row = (uint64_t) requirement_row;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[1],
+ strlen (args[1]),
+ &kyp->h_payto,
+ sizeof (kyp->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 (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_user_type_from_string (args[2],
+ &kyp->ut))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "usertype");
+ }
+
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &kyp->timeout);
+ }
+ /* KYC plugin generated reply? */
+ if (NULL != kyp->kyc_url)
+ {
+ 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));
+ }
+
+ if ( (NULL == kyp->eh) &&
+ GNUNET_TIME_absolute_is_future (kyp->timeout) )
+ {
+ struct TALER_KycCompletedEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
+ .h_payto = kyp->h_payto
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting DB event listening\n");
+ kyp->eh = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (kyp->timeout),
+ &rep.header,
+ &db_event_cb,
+ rc);
+ }
+
+ now = GNUNET_TIME_timestamp_get ();
+ ret = TEH_DB_run_transaction (rc->connection,
+ "kyc check",
+ TEH_MT_REQUEST_OTHER,
+ &res,
+ &kyc_check,
+ kyp);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Transaction failed.\n");
+ return res;
+ }
+ /* KYC plugin generated reply? */
+ if (NULL != kyp->kyc_url)
+ {
+ 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));
+ }
+
+ 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",
+ (unsigned long long) kyp->requirement_row);
+ return TALER_MHD_reply_static (
+ rc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ }
+
+ if (NULL != kyp->ih)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending HTTP request on KYC logic...\n");
+ kyp->suspended = true;
+ GNUNET_CONTAINER_DLL_insert (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_suspend_connection (kyp->connection);
+ return MHD_YES;
+ }
+
+ /* long polling? */
+ if ( (NULL != kyp->section_name) &&
+ GNUNET_TIME_absolute_is_future (kyp->timeout))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending HTTP request on timeout (%s) now...\n",
+ GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_remaining (
+ kyp->timeout),
+ true));
+ GNUNET_assert (NULL != kyp->eh);
+ kyp->suspended = true;
+ kyp->section_name = NULL;
+ GNUNET_CONTAINER_DLL_insert (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_suspend_connection (kyp->connection);
+ return MHD_YES;
+ }
+
+ if (TALER_EC_NONE != kyp->ec)
+ {
+ return TALER_MHD_reply_with_ec (rc->connection,
+ kyp->ec,
+ kyp->hint);
+ }
+
+ /* KYC must have succeeded! */
+ {
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ enum TALER_ErrorCode ec;
+
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_account_setup_success_sign (
+ &TEH_keys_exchange_sign_,
+ &kyp->h_payto,
+ kyp->kyc_details,
+ now,
+ &pub,
+ &sig)))
+ {
+ return TALER_MHD_reply_with_ec (rc->connection,
+ ec,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &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",
+ now));
+ }
+}
+
+
+/* end of taler-exchange-httpd_kyc-check.c */
diff --git a/src/exchange/taler-exchange-httpd_kyc-check.h b/src/exchange/taler-exchange-httpd_kyc-check.h
new file mode 100644
index 000000000..f1f2c9e7d
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_kyc-check.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_kyc-check.h
+ * @brief Handle /kyc-check requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_KYC_CHECK_H
+#define TALER_EXCHANGE_HTTPD_KYC_CHECK_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/kyc-check" request. Checks the KYC
+ * status of the given account and returns it.
+ *
+ * @param rc details about the request to handle
+ * @param args one argument with the legitimization_uuid
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_kyc_check (
+ struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+
+/**
+ * Clean up long-polling KYC requests during shutdown.
+ */
+void
+TEH_kyc_check_cleanup (void);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.c b/src/exchange/taler-exchange-httpd_kyc-proof.c
new file mode 100644
index 000000000..bad377a2a
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_kyc-proof.c
@@ -0,0 +1,566 @@
+/*
+ This file is part of TALER
+ 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
+ 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_kyc-proof.c
+ * @brief Handle request for proof for KYC check.
+ * @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 "taler_attributes.h"
+#include "taler_json_lib.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_templating_lib.h"
+#include "taler-exchange-httpd_common_kyc.h"
+#include "taler-exchange-httpd_kyc-proof.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Context for the proof.
+ */
+struct KycProofContext
+{
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct KycProofContext *next;
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct KycProofContext *prev;
+
+ /**
+ * Details about the connection we are processing.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Proof logic to run.
+ */
+ struct TALER_KYCLOGIC_Plugin *logic;
+
+ /**
+ * Configuration for @a logic.
+ */
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Asynchronous operation with the proof system.
+ */
+ struct TALER_KYCLOGIC_ProofHandle *ph;
+
+ /**
+ * KYC AML trigger operation.
+ */
+ struct TEH_KycAmlTrigger *kat;
+
+ /**
+ * Process information about the user for the plugin from the database, can
+ * be NULL.
+ */
+ char *provider_user_id;
+
+ /**
+ * Process information about the legitimization process for the plugin from the
+ * database, can be NULL.
+ */
+ char *provider_legitimization_id;
+
+ /**
+ * Hash of payment target URI this is about.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * HTTP response to return.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Provider configuration section name of the logic we are running.
+ */
+ const char *provider_section;
+
+ /**
+ * Row in the database for this legitimization operation.
+ */
+ uint64_t process_row;
+
+ /**
+ * HTTP response code to return.
+ */
+ unsigned int response_code;
+
+ /**
+ * True if we are suspended,
+ */
+ bool suspended;
+
+};
+
+
+/**
+ * Contexts are kept in a DLL while suspended.
+ */
+static struct KycProofContext *kpc_head;
+
+/**
+ * Contexts are kept in a DLL while suspended.
+ */
+static struct KycProofContext *kpc_tail;
+
+
+/**
+ * Resume processing the @a kpc request.
+ *
+ * @param kpc request to resume
+ */
+static void
+kpc_resume (struct KycProofContext *kpc)
+{
+ GNUNET_assert (GNUNET_YES == kpc->suspended);
+ kpc->suspended = false;
+ GNUNET_CONTAINER_DLL_remove (kpc_head,
+ kpc_tail,
+ kpc);
+ MHD_resume_connection (kpc->rc->connection);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+void
+TEH_kyc_proof_cleanup (void)
+{
+ struct KycProofContext *kpc;
+
+ while (NULL != (kpc = kpc_head))
+ {
+ if (NULL != kpc->ph)
+ {
+ kpc->logic->proof_cancel (kpc->ph);
+ kpc->ph = NULL;
+ }
+ kpc_resume (kpc);
+ }
+}
+
+
+/**
+ * Function called after the KYC-AML trigger is done.
+ *
+ * @param cls closure
+ * @param http_status final HTTP status to return
+ * @param[in] response final HTTP ro return
+ */
+static void
+proof_finish (
+ void *cls,
+ unsigned int http_status,
+ struct MHD_Response *response)
+{
+ struct KycProofContext *kpc = cls;
+
+ kpc->kat = NULL;
+ kpc->response_code = http_status;
+ kpc->response = response;
+ kpc_resume (kpc);
+}
+
+
+/**
+ * Generate HTML error for @a connection using @a template.
+ *
+ * @param connection HTTP client connection
+ * @param template template to expand
+ * @param[in,out] http_status HTTP status of the response
+ * @param ec Taler error code to return
+ * @param message extended message to return
+ * @return MHD response object
+ */
+struct MHD_Response *
+make_html_error (struct MHD_Connection *connection,
+ const char *template,
+ unsigned int *http_status,
+ enum TALER_ErrorCode ec,
+ const char *message)
+{
+ struct MHD_Response *response = NULL;
+ json_t *body;
+
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("message",
+ message)),
+ TALER_JSON_pack_ec (
+ ec));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (connection,
+ http_status,
+ template,
+ NULL,
+ NULL,
+ body,
+ &response));
+ json_decref (body);
+ return response;
+}
+
+
+/**
+ * Respond with an HTML message on the given @a rc.
+ *
+ * @param[in,out] rc request to respond to
+ * @param http_status HTTP status code to use
+ * @param template template to fill in
+ * @param ec error code to use for the template
+ * @param message additional message to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+respond_html_ec (struct TEH_RequestContext *rc,
+ unsigned int http_status,
+ const char *template,
+ enum TALER_ErrorCode ec,
+ const char *message)
+{
+ struct MHD_Response *response;
+ MHD_RESULT res;
+
+ response = make_html_error (rc->connection,
+ template,
+ &http_status,
+ ec,
+ message);
+ res = MHD_queue_response (rc->connection,
+ http_status,
+ response);
+ MHD_destroy_response (response);
+ return res;
+}
+
+
+/**
+ * Function called with the result of a proof check operation.
+ *
+ * Note that the "decref" for the @a response
+ * will be done by the callee and MUST NOT be done by the plugin.
+ *
+ * @param cls closure
+ * @param status KYC status
+ * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
+ * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
+ * @param 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
+ */
+static void
+proof_cb (
+ void *cls,
+ enum TALER_KYCLOGIC_KycStatus status,
+ 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)
+{
+ struct KycProofContext *kpc = cls;
+ struct TEH_RequestContext *rc = kpc->rc;
+ struct GNUNET_AsyncScopeSave old_scope;
+
+ kpc->ph = NULL;
+ GNUNET_async_scope_enter (&rc->async_scope_id,
+ &old_scope);
+ switch (status)
+ {
+ case TALER_KYCLOGIC_STATUS_SUCCESS:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC process #%llu succeeded with KYC provider\n",
+ (unsigned long long) kpc->process_row);
+ kpc->kat = TEH_kyc_finished (&rc->async_scope_id,
+ kpc->process_row,
+ &kpc->h_payto,
+ kpc->provider_section,
+ provider_user_id,
+ provider_legitimization_id,
+ expiration,
+ attributes,
+ http_status,
+ response,
+ &proof_finish,
+ kpc);
+ if (NULL == kpc->kat)
+ {
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ if (NULL != response)
+ MHD_destroy_response (response);
+ response = make_html_error (kpc->rc->connection,
+ "kyc-proof-internal-error",
+ &http_status,
+ TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+ "[exchange] AML_KYC_TRIGGER");
+ }
+ break;
+ case TALER_KYCLOGIC_STATUS_FAILED:
+ case TALER_KYCLOGIC_STATUS_PROVIDER_FAILED:
+ case TALER_KYCLOGIC_STATUS_USER_ABORTED:
+ case TALER_KYCLOGIC_STATUS_ABORTED:
+ GNUNET_assert (NULL == kpc->kat);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC process %s/%s (Row #%llu) failed: %d\n",
+ provider_user_id,
+ provider_legitimization_id,
+ (unsigned long long) kpc->process_row,
+ status);
+ if (5 == http_status / 100)
+ {
+ char *msg;
+
+ /* OAuth2 server had a problem, do NOT log this as a KYC failure */
+ if (NULL != response)
+ MHD_destroy_response (response);
+ GNUNET_asprintf (&msg,
+ "Failure by KYC provider (HTTP status %u)\n",
+ http_status);
+ http_status = MHD_HTTP_BAD_GATEWAY;
+ response = make_html_error (kpc->rc->connection,
+ "kyc-proof-internal-error",
+ &http_status,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
+ msg);
+ GNUNET_free (msg);
+ }
+ else
+ {
+ if (! TEH_kyc_failed (kpc->process_row,
+ &kpc->h_payto,
+ kpc->provider_section,
+ provider_user_id,
+ provider_legitimization_id))
+ {
+ GNUNET_break (0);
+ if (NULL != response)
+ MHD_destroy_response (response);
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ response = make_html_error (kpc->rc->connection,
+ "kyc-proof-internal-error",
+ &http_status,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_kyc_failure");
+ }
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC status of %s/%s (Row #%llu) is %d\n",
+ provider_user_id,
+ provider_legitimization_id,
+ (unsigned long long) kpc->process_row,
+ (int) status);
+ break;
+ }
+ if (NULL == kpc->kat)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC process #%llu failed with status %d\n",
+ (unsigned long long) kpc->process_row,
+ status);
+ proof_finish (kpc,
+ http_status,
+ response);
+ }
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
+/**
+ * Function called to clean up a context.
+ *
+ * @param rc request context
+ */
+static void
+clean_kpc (struct TEH_RequestContext *rc)
+{
+ struct KycProofContext *kpc = rc->rh_ctx;
+
+ if (NULL != kpc->ph)
+ {
+ kpc->logic->proof_cancel (kpc->ph);
+ kpc->ph = NULL;
+ }
+ if (NULL != kpc->kat)
+ {
+ TEH_kyc_finished_cancel (kpc->kat);
+ kpc->kat = NULL;
+ }
+ if (NULL != kpc->response)
+ {
+ MHD_destroy_response (kpc->response);
+ kpc->response = NULL;
+ }
+ GNUNET_free (kpc->provider_user_id);
+ GNUNET_free (kpc->provider_legitimization_id);
+ GNUNET_free (kpc);
+}
+
+
+MHD_RESULT
+TEH_handler_kyc_proof (
+ struct TEH_RequestContext *rc,
+ const char *const args[1])
+{
+ struct KycProofContext *kpc = rc->rh_ctx;
+ const char *provider_section_or_logic = args[0];
+
+ if (NULL == kpc)
+ {
+ /* first time */
+ if (NULL == provider_section_or_logic)
+ {
+ GNUNET_break_op (0);
+ return respond_html_ec (rc,
+ MHD_HTTP_NOT_FOUND,
+ "kyc-proof-endpoint-unknown",
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ "'/kyc-proof/$PROVIDER_SECTION?state=$H_PAYTO' required");
+ }
+ kpc = GNUNET_new (struct KycProofContext);
+ kpc->rc = rc;
+ rc->rh_ctx = kpc;
+ rc->rh_cleaner = &clean_kpc;
+ TALER_MHD_parse_request_arg_auto_t (rc->connection,
+ "state",
+ &kpc->h_payto);
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_lookup_logic (provider_section_or_logic,
+ &kpc->logic,
+ &kpc->pd,
+ &kpc->provider_section))
+ {
+ GNUNET_break_op (0);
+ return respond_html_ec (rc,
+ MHD_HTTP_NOT_FOUND,
+ "kyc-proof-target-unknown",
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
+ provider_section_or_logic);
+ }
+ if (NULL != kpc->provider_section)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute expiration;
+
+ if (0 != strcmp (provider_section_or_logic,
+ kpc->provider_section))
+ {
+ GNUNET_break_op (0);
+ return respond_html_ec (rc,
+ MHD_HTTP_BAD_REQUEST,
+ "kyc-proof-bad-request",
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "PROVIDER_SECTION");
+ }
+
+ qs = TEH_plugin->lookup_kyc_process_by_account (
+ TEH_plugin->cls,
+ kpc->provider_section,
+ &kpc->h_payto,
+ &kpc->process_row,
+ &expiration,
+ &kpc->provider_user_id,
+ &kpc->provider_legitimization_id);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return respond_html_ec (rc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "kyc-proof-internal-error",
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_kyc_process_by_account");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return respond_html_ec (rc,
+ MHD_HTTP_NOT_FOUND,
+ "kyc-proof-target-unknown",
+ TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
+ kpc->provider_section);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ if (GNUNET_TIME_absolute_is_future (expiration))
+ {
+ /* KYC not required */
+ return respond_html_ec (rc,
+ MHD_HTTP_OK,
+ "kyc-proof-already-done",
+ TALER_EC_NONE,
+ NULL);
+ }
+ }
+ kpc->ph = kpc->logic->proof (kpc->logic->cls,
+ kpc->pd,
+ rc->connection,
+ &kpc->h_payto,
+ kpc->process_row,
+ kpc->provider_user_id,
+ kpc->provider_legitimization_id,
+ &proof_cb,
+ kpc);
+ if (NULL == kpc->ph)
+ {
+ GNUNET_break (0);
+ return respond_html_ec (rc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "kyc-proof-internal-error",
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "could not start proof with KYC logic");
+ }
+
+
+ kpc->suspended = true;
+ GNUNET_CONTAINER_DLL_insert (kpc_head,
+ kpc_tail,
+ kpc);
+ MHD_suspend_connection (rc->connection);
+ return MHD_YES;
+ }
+
+ if (NULL == kpc->response)
+ {
+ GNUNET_break (0);
+ return respond_html_ec (rc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "kyc-proof-internal-error",
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "handler resumed without response");
+ }
+
+ /* return response from KYC logic */
+ return MHD_queue_response (rc->connection,
+ kpc->response_code,
+ kpc->response);
+}
+
+
+/* end of taler-exchange-httpd_kyc-proof.c */
diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.h b/src/exchange/taler-exchange-httpd_kyc-proof.h
new file mode 100644
index 000000000..d40ea90a9
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_kyc-proof.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_kyc-proof.h
+ * @brief Handle /kyc-proof requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_KYC_PROOF_H
+#define TALER_EXCHANGE_HTTPD_KYC_PROOF_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Shutdown kyc-proof subsystem. Resumes all suspended long-polling clients
+ * and cleans up data structures.
+ */
+void
+TEH_kyc_proof_cleanup (void);
+
+
+/**
+ * Handle a "/kyc-proof" request.
+ *
+ * @param rc request to handle
+ * @param args one argument with the legitimization_uuid
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_kyc_proof (
+ struct TEH_RequestContext *rc,
+ const char *const args[1]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_kyc-wallet.c b/src/exchange/taler-exchange-httpd_kyc-wallet.c
new file mode 100644
index 000000000..21d07422d
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_kyc-wallet.c
@@ -0,0 +1,252 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021, 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_kyc-wallet.c
+ * @brief Handle request for wallet for KYC check.
+ * @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-exchange-httpd_kyc-wallet.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Context for the request.
+ */
+struct KycRequestContext
+{
+ /**
+ * Public key of the reserve/wallet this is about.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * The reserve's public key
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * KYC status, with row with the legitimization requirement.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Balance threshold crossed by the wallet.
+ */
+ struct TALER_Amount balance;
+
+ /**
+ * Name of the required check.
+ */
+ char *required;
+
+};
+
+
+/**
+ * Function called to iterate over KYC-relevant
+ * transaction amounts for a particular time range.
+ * Returns the wallet balance.
+ *
+ * @param cls closure, a `struct KycRequestContext`
+ * @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
+balance_iterator (void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ struct KycRequestContext *krc = cls;
+
+ (void) limit;
+ cb (cb_cls,
+ &krc->balance,
+ GNUNET_TIME_absolute_get ());
+}
+
+
+/**
+ * Function implementing database transaction to check wallet's KYC status.
+ * 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 KycRequestContext *`
+ * @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
+wallet_kyc_check (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct KycRequestContext *krc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ 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,
+ &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,
+ &krc->h_payto,
+ &krc->reserve_pub,
+ &krc->kyc.requirement_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_FETCH_FAILED,
+ "insert_kyc_requirement_for_account");
+ return qs;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC requirement inserted for wallet %s (%llu, %d)\n",
+ TALER_B2S (&krc->h_payto),
+ (unsigned long long) krc->kyc.requirement_row,
+ qs);
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_kyc_wallet (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct KycRequestContext krc;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &reserve_sig),
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &krc.reserve_pub),
+ TALER_JSON_spec_amount ("balance",
+ TEH_currency,
+ &krc.balance),
+ GNUNET_JSON_spec_end ()
+ };
+ MHD_RESULT res;
+ enum GNUNET_GenericReturnValue ret;
+
+ (void) args;
+ ret = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == ret)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == ret)
+ return MHD_YES; /* failure */
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_account_setup_verify (&krc.reserve_pub,
+ &krc.balance,
+ &reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_KYC_WALLET_SIGNATURE_INVALID,
+ NULL);
+ }
+ {
+ char *payto_uri;
+
+ payto_uri = TALER_reserve_make_payto (TEH_base_url,
+ &krc.reserve_pub);
+ TALER_payto_hash (payto_uri,
+ &krc.h_payto);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "h_payto of wallet %s is %s\n",
+ payto_uri,
+ TALER_B2S (&krc.h_payto));
+ GNUNET_free (payto_uri);
+ }
+ ret = TEH_DB_run_transaction (rc->connection,
+ "check wallet kyc",
+ TEH_MT_REQUEST_OTHER,
+ &res,
+ &wallet_kyc_check,
+ &krc);
+ if (GNUNET_SYSERR == ret)
+ return res;
+ if (NULL == krc.required)
+ {
+ /* KYC not required or already satisfied */
+ return TALER_MHD_reply_static (
+ rc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ }
+ GNUNET_free (krc.required);
+ return TEH_RESPONSE_reply_kyc_required (rc->connection,
+ &krc.h_payto,
+ &krc.kyc);
+}
+
+
+/* end of taler-exchange-httpd_kyc-wallet.c */
diff --git a/src/exchange/taler-exchange-httpd_kyc-wallet.h b/src/exchange/taler-exchange-httpd_kyc-wallet.h
new file mode 100644
index 000000000..bd8ae1b08
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_kyc-wallet.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_kyc-wallet.h
+ * @brief Handle /kyc-wallet requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_KYC_WALLET_H
+#define TALER_EXCHANGE_HTTPD_KYC_WALLET_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/kyc-wallet" request. Parses the "reserve_pub" EdDSA key of the
+ * reserve and the signature "reserve_sig" which affirms the operation. If OK,
+ * a KYC record is created (if missing) and the KYC status returned.
+ *
+ * @param rc request to handle
+ * @param root uploaded JSON data
+ * @param args empty array
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_kyc_wallet (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_kyc-webhook.c b/src/exchange/taler-exchange-httpd_kyc-webhook.c
new file mode 100644
index 000000000..b92b43e69
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_kyc-webhook.c
@@ -0,0 +1,420 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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_kyc-webhook.c
+ * @brief Handle notification of KYC completion via webhook.
+ * @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_attributes.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_kyclogic_lib.h"
+#include "taler-exchange-httpd_common_kyc.h"
+#include "taler-exchange-httpd_kyc-webhook.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Context for the webhook.
+ */
+struct KycWebhookContext
+{
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct KycWebhookContext *next;
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct KycWebhookContext *prev;
+
+ /**
+ * Details about the connection we are processing.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Handle for the KYC-AML trigger interaction.
+ */
+ struct TEH_KycAmlTrigger *kat;
+
+ /**
+ * Plugin responsible for the webhook.
+ */
+ struct TALER_KYCLOGIC_Plugin *plugin;
+
+ /**
+ * Section in the configuration of the configured
+ * KYC provider.
+ */
+ const char *provider_section;
+
+ /**
+ * Configuration for the specific action.
+ */
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Webhook activity.
+ */
+ struct TALER_KYCLOGIC_WebhookHandle *wh;
+
+ /**
+ * HTTP response to return.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * HTTP response code to return.
+ */
+ unsigned int response_code;
+
+ /**
+ * #GNUNET_YES if we are suspended,
+ * #GNUNET_NO if not.
+ * #GNUNET_SYSERR if we had some error.
+ */
+ enum GNUNET_GenericReturnValue suspended;
+
+};
+
+
+/**
+ * Contexts are kept in a DLL while suspended.
+ */
+static struct KycWebhookContext *kwh_head;
+
+/**
+ * Contexts are kept in a DLL while suspended.
+ */
+static struct KycWebhookContext *kwh_tail;
+
+
+/**
+ * Resume processing the @a kwh request.
+ *
+ * @param kwh request to resume
+ */
+static void
+kwh_resume (struct KycWebhookContext *kwh)
+{
+ GNUNET_assert (GNUNET_YES == kwh->suspended);
+ kwh->suspended = GNUNET_NO;
+ GNUNET_CONTAINER_DLL_remove (kwh_head,
+ kwh_tail,
+ kwh);
+ MHD_resume_connection (kwh->rc->connection);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+void
+TEH_kyc_webhook_cleanup (void)
+{
+ struct KycWebhookContext *kwh;
+
+ while (NULL != (kwh = kwh_head))
+ {
+ if (NULL != kwh->wh)
+ {
+ kwh->plugin->webhook_cancel (kwh->wh);
+ kwh->wh = NULL;
+ }
+ kwh_resume (kwh);
+ }
+}
+
+
+/**
+ * Function called after the KYC-AML trigger is done.
+ *
+ * @param cls closure with a `struct KycWebhookContext *`
+ * @param http_status final HTTP status to return
+ * @param[in] response final HTTP ro return
+ */
+static void
+kyc_aml_webhook_finished (
+ void *cls,
+ unsigned int http_status,
+ struct MHD_Response *response)
+{
+ struct KycWebhookContext *kwh = cls;
+
+ kwh->kat = NULL;
+ kwh->response = response;
+ kwh->response_code = http_status;
+ kwh_resume (kwh);
+}
+
+
+/**
+ * Function called with the result of a KYC webhook operation.
+ *
+ * Note that the "decref" for the @a response
+ * will be done by the plugin.
+ *
+ * @param cls closure
+ * @param process_row legitimization process the webhook was about
+ * @param account_id account the webhook was about
+ * @param provider_section name of the configuration section of the logic that was run
+ * @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 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
+ */
+static void
+webhook_finished_cb (
+ void *cls,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *account_id,
+ const char *provider_section,
+ const char *provider_user_id,
+ 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)
+{
+ struct KycWebhookContext *kwh = cls;
+
+ kwh->wh = NULL;
+ switch (status)
+ {
+ case TALER_KYCLOGIC_STATUS_SUCCESS:
+ kwh->kat = TEH_kyc_finished (
+ &kwh->rc->async_scope_id,
+ process_row,
+ account_id,
+ provider_section,
+ provider_user_id,
+ provider_legitimization_id,
+ expiration,
+ attributes,
+ http_status,
+ response,
+ &kyc_aml_webhook_finished,
+ kwh);
+ if (NULL == kwh->kat)
+ {
+ if (NULL != response)
+ MHD_destroy_response (response);
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ response = TALER_MHD_make_error (
+ TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+ "[exchange] AML_KYC_TRIGGER");
+ break;
+ }
+ return;
+ case TALER_KYCLOGIC_STATUS_FAILED:
+ case TALER_KYCLOGIC_STATUS_PROVIDER_FAILED:
+ case TALER_KYCLOGIC_STATUS_USER_ABORTED:
+ case TALER_KYCLOGIC_STATUS_ABORTED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC process %s/%s (Row #%llu) failed: %d\n",
+ provider_user_id,
+ provider_legitimization_id,
+ (unsigned long long) process_row,
+ status);
+ if (! TEH_kyc_failed (process_row,
+ account_id,
+ provider_section,
+ provider_user_id,
+ provider_legitimization_id))
+ {
+ GNUNET_break (0);
+ if (NULL != response)
+ MHD_destroy_response (response);
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ response = TALER_MHD_make_error (
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_kyc_failure");
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC status of %s/%s (Row #%llu) is %d\n",
+ provider_user_id,
+ provider_legitimization_id,
+ (unsigned long long) process_row,
+ (int) status);
+ break;
+ }
+ GNUNET_break (NULL == kwh->kat);
+ kyc_aml_webhook_finished (kwh,
+ http_status,
+ response);
+}
+
+
+/**
+ * Function called to clean up a context.
+ *
+ * @param rc request context
+ */
+static void
+clean_kwh (struct TEH_RequestContext *rc)
+{
+ struct KycWebhookContext *kwh = rc->rh_ctx;
+
+ if (NULL != kwh->wh)
+ {
+ kwh->plugin->webhook_cancel (kwh->wh);
+ kwh->wh = NULL;
+ }
+ if (NULL != kwh->kat)
+ {
+ TEH_kyc_finished_cancel (kwh->kat);
+ kwh->kat = NULL;
+ }
+ if (NULL != kwh->response)
+ {
+ MHD_destroy_response (kwh->response);
+ kwh->response = NULL;
+ }
+ GNUNET_free (kwh);
+}
+
+
+/**
+ * Handle a (GET or POST) "/kyc-webhook" request.
+ *
+ * @param rc request to handle
+ * @param method HTTP request method used by the client
+ * @param root uploaded JSON body (can be NULL)
+ * @param args one argument with the legitimization_uuid
+ * @return MHD result code
+ */
+static MHD_RESULT
+handler_kyc_webhook_generic (
+ struct TEH_RequestContext *rc,
+ const char *method,
+ const json_t *root,
+ const char *const args[])
+{
+ struct KycWebhookContext *kwh = rc->rh_ctx;
+
+ if (NULL == kwh)
+ { /* first time */
+ kwh = GNUNET_new (struct KycWebhookContext);
+ kwh->rc = rc;
+ rc->rh_ctx = kwh;
+ rc->rh_cleaner = &clean_kwh;
+
+ if ( (NULL == args[0]) ||
+ (GNUNET_OK !=
+ TALER_KYCLOGIC_lookup_logic (args[0],
+ &kwh->plugin,
+ &kwh->pd,
+ &kwh->provider_section)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYC logic `%s' unknown (check KYC provider configuration)\n",
+ args[0]);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
+ args[0]);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC logic `%s' mapped to section %s\n",
+ args[0],
+ kwh->provider_section);
+ kwh->wh = kwh->plugin->webhook (kwh->plugin->cls,
+ kwh->pd,
+ TEH_plugin->kyc_provider_account_lookup,
+ TEH_plugin->cls,
+ method,
+ &args[1],
+ rc->connection,
+ root,
+ &webhook_finished_cb,
+ kwh);
+ if (NULL == kwh->wh)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "failed to run webhook logic");
+ }
+ kwh->suspended = GNUNET_YES;
+ GNUNET_CONTAINER_DLL_insert (kwh_head,
+ kwh_tail,
+ kwh);
+ MHD_suspend_connection (rc->connection);
+ return MHD_YES;
+ }
+ GNUNET_break (GNUNET_NO == kwh->suspended);
+
+ if (NULL != kwh->response)
+ {
+ MHD_RESULT res;
+
+ res = MHD_queue_response (rc->connection,
+ kwh->response_code,
+ kwh->response);
+ GNUNET_break (MHD_YES == res);
+ return res;
+ }
+
+ /* We resumed, but got no response? This should
+ not happen. */
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "resumed without response");
+}
+
+
+MHD_RESULT
+TEH_handler_kyc_webhook_get (
+ struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ return handler_kyc_webhook_generic (rc,
+ MHD_HTTP_METHOD_GET,
+ NULL,
+ args);
+}
+
+
+MHD_RESULT
+TEH_handler_kyc_webhook_post (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ return handler_kyc_webhook_generic (rc,
+ MHD_HTTP_METHOD_POST,
+ root,
+ args);
+}
+
+
+/* end of taler-exchange-httpd_kyc-webhook.c */
diff --git a/src/exchange/taler-exchange-httpd_kyc-webhook.h b/src/exchange/taler-exchange-httpd_kyc-webhook.h
new file mode 100644
index 000000000..ea3821897
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_kyc-webhook.h
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_kyc-webhook.h
+ * @brief Handle /kyc-webhook requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_KYC_WEBHOOK_H
+#define TALER_EXCHANGE_HTTPD_KYC_WEBHOOK_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Shutdown kyc-webhook subsystem. Resumes all suspended long-polling clients
+ * and cleans up data structures.
+ */
+void
+TEH_kyc_webhook_cleanup (void);
+
+
+/**
+ * Handle a GET "/kyc-webhook" request.
+ *
+ * @param rc request to handle
+ * @param args one argument with the legitimization_uuid
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_kyc_webhook_get (
+ struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+
+/**
+ * Handle a POST "/kyc-webhook" request.
+ *
+ * @param rc request to handle
+ * @param root uploaded JSON body
+ * @param args one argument with the legitimization_uuid
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_kyc_webhook_post (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_link.c b/src/exchange/taler-exchange-httpd_link.c
index 4738a435f..3d92a11a3 100644
--- a/src/exchange/taler-exchange-httpd_link.c
+++ b/src/exchange/taler-exchange-httpd_link.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2019 Taler Systems SA
+ Copyright (C) 2014-2019, 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -28,7 +28,6 @@
#include "taler-exchange-httpd_mhd.h"
#include "taler-exchange-httpd_link.h"
#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keystate.h"
/**
@@ -40,7 +39,7 @@ struct HTD_Context
/**
* Public key of the coin for which we are running link.
*/
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ const struct TALER_CoinSpendPublicKeyP *coin_pub;
/**
* Json array with transfer data we collect.
@@ -82,15 +81,23 @@ handle_link_data (void *cls,
{
json_t *obj;
- obj = json_pack ("{s:o, s:o, s:o}",
- "denom_pub",
- GNUNET_JSON_from_rsa_public_key
- (pos->denom_pub.rsa_public_key),
- "ev_sig",
- GNUNET_JSON_from_rsa_signature
- (pos->ev_sig.rsa_signature),
- "link_sig",
- GNUNET_JSON_from_data_auto (&pos->orig_coin_link_sig));
+ obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_denom_pub ("denom_pub",
+ &pos->denom_pub),
+ TALER_JSON_pack_blinded_denom_sig ("ev_sig",
+ &pos->ev_sig),
+ GNUNET_JSON_pack_uint64 ("coin_idx",
+ pos->coin_refresh_offset),
+ TALER_JSON_pack_exchange_withdraw_values ("ewv",
+ &pos->alg_values),
+ GNUNET_JSON_pack_data_auto ("link_sig",
+ &pos->orig_coin_link_sig),
+ GNUNET_JSON_pack_allow_null (
+ pos->have_nonce
+ ? GNUNET_JSON_pack_data_auto ("cs_nonce",
+ &pos->nonce)
+ : GNUNET_JSON_pack_string ("cs_nonce",
+ NULL)));
if ( (NULL == obj) ||
(0 !=
json_array_append_new (list,
@@ -103,11 +110,11 @@ handle_link_data (void *cls,
{
json_t *root;
- root = json_pack ("{s:o, s:o}",
- "new_coins",
- list,
- "transfer_pub",
- GNUNET_JSON_from_data_auto (transfer_pub));
+ root = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("new_coins",
+ list),
+ GNUNET_JSON_pack_data_auto ("transfer_pub",
+ transfer_pub));
if ( (NULL == root) ||
(0 !=
json_array_append_new (ctx->mlist,
@@ -116,7 +123,7 @@ handle_link_data (void *cls,
}
return;
fail:
- ctx->ec = TALER_EC_JSON_ALLOCATION_FAILURE;
+ ctx->ec = TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE;
json_decref (ctx->mlist);
ctx->mlist = NULL;
}
@@ -133,7 +140,6 @@ fail:
*
* @param cls closure
* @param connection MHD request which triggered the transaction
- * @param session database session to use
* @param[out] mhd_ret set to MHD response status for @a connection,
* if transaction failed (!)
* @return transaction status
@@ -141,15 +147,13 @@ fail:
static enum GNUNET_DB_QueryStatus
link_transaction (void *cls,
struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
+ MHD_RESULT *mhd_ret)
{
struct HTD_Context *ctx = cls;
enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->get_link_data (TEH_plugin->cls,
- session,
- &ctx->coin_pub,
+ ctx->coin_pub,
&handle_link_data,
ctx);
if (NULL == ctx->mlist)
@@ -157,65 +161,36 @@ link_transaction (void *cls,
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
ctx->ec,
- "coin_pub");
+ NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
- TALER_EC_LINK_COIN_UNKNOWN,
- "coin_pub");
+ TALER_EC_EXCHANGE_LINK_COIN_UNKNOWN,
+ NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
return qs;
}
-/**
- * Handle a "/coins/$COIN_PUB/link" request.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param args array of additional options (length: 2, first is the coin_pub, second must be "link")
- * @return MHD result code
- */
-int
-TEH_handler_link (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
- const char *const args[2])
+MHD_RESULT
+TEH_handler_link (struct TEH_RequestContext *rc,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
- struct HTD_Context ctx;
- int mhd_ret;
+ struct HTD_Context ctx = {
+ .coin_pub = coin_pub
+ };
+ MHD_RESULT mhd_ret;
- (void) rh;
- memset (&ctx,
- 0,
- sizeof (ctx));
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &ctx.coin_pub,
- sizeof (ctx.coin_pub)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_COINS_INVALID_COIN_PUB,
- "coin public key malformed");
- }
ctx.mlist = json_array ();
- if (NULL == ctx.mlist)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_JSON_ALLOCATION_FAILURE,
- "json_array() call failed");
- }
+ GNUNET_assert (NULL != ctx.mlist);
if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
+ TEH_DB_run_transaction (rc->connection,
"run link",
+ TEH_MT_REQUEST_OTHER,
&mhd_ret,
&link_transaction,
&ctx))
@@ -224,7 +199,7 @@ TEH_handler_link (const struct TEH_RequestHandler *rh,
json_decref (ctx.mlist);
return mhd_ret;
}
- mhd_ret = TALER_MHD_reply_json (connection,
+ mhd_ret = TALER_MHD_reply_json (rc->connection,
ctx.mlist,
MHD_HTTP_OK);
json_decref (ctx.mlist);
diff --git a/src/exchange/taler-exchange-httpd_link.h b/src/exchange/taler-exchange-httpd_link.h
index 6fdcf1cff..255c0ca57 100644
--- a/src/exchange/taler-exchange-httpd_link.h
+++ b/src/exchange/taler-exchange-httpd_link.h
@@ -31,15 +31,13 @@
/**
* Handle a "/coins/$COIN_PUB/link" request.
*
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param args array of additional options (length: 2, first is the coin_pub, second must be "link")
+ * @param rc request context
+ * @param coin_pub the coin public key
* @return MHD result code
*/
-int
-TEH_handler_link (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
- const char *const args[2]);
+MHD_RESULT
+TEH_handler_link (struct TEH_RequestContext *rc,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub);
#endif
diff --git a/src/exchange/taler-exchange-httpd_management.h b/src/exchange/taler-exchange-httpd_management.h
new file mode 100644
index 000000000..2fc1fe8db
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management.h
@@ -0,0 +1,211 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as 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.h
+ * @brief Handlers for the /management/ endpoints
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_MANAGEMENT_H
+#define TALER_EXCHANGE_HTTPD_MANAGEMENT_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+/**
+ * Handle a "/management/auditors" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_management_auditors (
+ struct MHD_Connection *connection,
+ const json_t *root);
+
+
+/**
+ * Handle a "/management/auditors/$AUDITOR_PUB/disable" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param auditor_pub public key of the auditor to disable
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_management_auditors_AP_disable (
+ struct MHD_Connection *connection,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const json_t *root);
+
+
+/**
+ * Handle a "/management/denominations/$HDP/revoke" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param h_denom_pub hash of the public key of the denomination to revoke
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_management_denominations_HDP_revoke (
+ struct MHD_Connection *connection,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const json_t *root);
+
+
+/**
+ * Handle a "/management/signkeys/$EP/revoke" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param exchange_pub exchange online signing public key to revoke
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_management_signkeys_EP_revoke (
+ struct MHD_Connection *connection,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const json_t *root);
+
+
+/**
+ * Handle a POST "/management/keys" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_management_post_keys (
+ struct MHD_Connection *connection,
+ const json_t *root);
+
+
+/**
+ * Handle a POST "/management/wire" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_management_post_wire (
+ struct MHD_Connection *connection,
+ const json_t *root);
+
+
+/**
+ * Handle a POST "/management/wire/disable" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_management_post_wire_disable (
+ struct MHD_Connection *connection,
+ const json_t *root);
+
+
+/**
+ * Handle a POST "/management/wire-fees" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_management_post_wire_fees (
+ struct MHD_Connection *connection,
+ const json_t *root);
+
+
+/**
+ * Handle a POST "/management/global-fees" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_management_post_global_fees (
+ struct MHD_Connection *connection,
+ const json_t *root);
+
+
+/**
+ * Handle a POST "/management/extensions" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_management_post_extensions (
+ struct MHD_Connection *connection,
+ const json_t *root);
+
+
+/**
+ * Handle a POST "/management/drain" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_management_post_drain (
+ struct MHD_Connection *connection,
+ 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.
+ *
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_extensions_init (void);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_management_aml-officers.c b/src/exchange/taler-exchange-httpd_management_aml-officers.c
new file mode 100644
index 000000000..abc7c3d84
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_aml-officers.c
@@ -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 */
diff --git a/src/exchange/taler-exchange-httpd_management_auditors.c b/src/exchange/taler-exchange-httpd_management_auditors.c
new file mode 100644
index 000000000..7e0593534
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_auditors.c
@@ -0,0 +1,207 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 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_management_auditors.c
+ * @brief Handle request to add auditor.
+ * @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-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+/**
+ * Closure for the #add_auditor transaction.
+ */
+struct AddAuditorContext
+{
+ /**
+ * Master signature to store.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Auditor public key this is about.
+ */
+ struct TALER_AuditorPublicKeyP auditor_pub;
+
+ /**
+ * Auditor URL this is about.
+ */
+ const char *auditor_url;
+
+ /**
+ * Human readable name of the auditor.
+ */
+ const char *auditor_name;
+
+ /**
+ * Timestamp for checking against replay attacks.
+ */
+ struct GNUNET_TIME_Timestamp validity_start;
+
+};
+
+
+/**
+ * Function implementing database transaction to add an auditor. 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 AddAuditorContext`
+ * @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
+add_auditor (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct AddAuditorContext *aac = cls;
+ struct GNUNET_TIME_Timestamp last_date;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->lookup_auditor_timestamp (TEH_plugin->cls,
+ &aac->auditor_pub,
+ &last_date);
+ 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,
+ "lookup auditor");
+ return qs;
+ }
+ if ( (0 < qs) &&
+ (GNUNET_TIME_timestamp_cmp (last_date,
+ >,
+ aac->validity_start) ) )
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_MORE_RECENT_PRESENT,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (0 == qs)
+ qs = TEH_plugin->insert_auditor (TEH_plugin->cls,
+ &aac->auditor_pub,
+ aac->auditor_url,
+ aac->auditor_name,
+ aac->validity_start);
+ else
+ qs = TEH_plugin->update_auditor (TEH_plugin->cls,
+ &aac->auditor_pub,
+ aac->auditor_url,
+ aac->auditor_name,
+ aac->validity_start,
+ true);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "add auditor");
+ return qs;
+ }
+ TEH_keys_update_states ();
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_auditors (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct AddAuditorContext aac;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &aac.master_sig),
+ GNUNET_JSON_spec_fixed_auto ("auditor_pub",
+ &aac.auditor_pub),
+ TALER_JSON_spec_web_url ("auditor_url",
+ &aac.auditor_url),
+ GNUNET_JSON_spec_string ("auditor_name",
+ &aac.auditor_name),
+ GNUNET_JSON_spec_timestamp ("validity_start",
+ &aac.validity_start),
+ GNUNET_JSON_spec_end ()
+ };
+ MHD_RESULT res;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == ret)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == ret)
+ return MHD_YES; /* failure */
+ if (GNUNET_OK !=
+ TALER_exchange_offline_auditor_add_verify (
+ &aac.auditor_pub,
+ aac.auditor_url,
+ aac.validity_start,
+ &TEH_master_public_key,
+ &aac.master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_ADD_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ ret = TEH_DB_run_transaction (connection,
+ "add auditor",
+ TEH_MT_REQUEST_OTHER,
+ &res,
+ &add_auditor,
+ &aac);
+ if (GNUNET_SYSERR == ret)
+ return res;
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_auditors.c */
diff --git a/src/exchange/taler-exchange-httpd_management_auditors_AP_disable.c b/src/exchange/taler-exchange-httpd_management_auditors_AP_disable.c
new file mode 100644
index 000000000..07e2933bb
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_auditors_AP_disable.c
@@ -0,0 +1,196 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as 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_auditors_AP_disable.c
+ * @brief Handle request to disable auditor.
+ * @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-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+/**
+ * Closure for the #del_auditor transaction.
+ */
+struct DelAuditorContext
+{
+
+ /**
+ * Auditor public key this is about.
+ */
+ struct TALER_AuditorPublicKeyP auditor_pub;
+
+ /**
+ * Auditor URL this is about.
+ */
+ const char *auditor_url;
+
+ /**
+ * Timestamp for checking against replay attacks.
+ */
+ struct GNUNET_TIME_Timestamp validity_end;
+
+};
+
+
+/**
+ * Function implementing database transaction to del an auditor. 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 DelAuditorContext`
+ * @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
+del_auditor (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct DelAuditorContext *dac = cls;
+ struct GNUNET_TIME_Timestamp last_date;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->lookup_auditor_timestamp (TEH_plugin->cls,
+ &dac->auditor_pub,
+ &last_date);
+ 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,
+ "lookup auditor");
+ return qs;
+ }
+ if (GNUNET_TIME_timestamp_cmp (last_date,
+ >,
+ dac->validity_end))
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_MORE_RECENT_PRESENT,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (0 == qs)
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_NOT_FOUND,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ qs = TEH_plugin->update_auditor (TEH_plugin->cls,
+ &dac->auditor_pub,
+ "", /* auditor URL */
+ "", /* auditor name */
+ dac->validity_end,
+ false);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "del auditor");
+ return qs;
+ }
+ TEH_keys_update_states ();
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_auditors_AP_disable (
+ struct MHD_Connection *connection,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const json_t *root)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct DelAuditorContext dac = {
+ .auditor_pub = *auditor_pub
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_timestamp ("validity_end",
+ &dac.validity_end),
+ GNUNET_JSON_spec_end ()
+ };
+ MHD_RESULT res;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == ret)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == ret)
+ return MHD_YES; /* failure */
+ if (GNUNET_OK !=
+ TALER_exchange_offline_auditor_del_verify (
+ auditor_pub,
+ dac.validity_end,
+ &TEH_master_public_key,
+ &master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_DEL_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ ret = TEH_DB_run_transaction (connection,
+ "del auditor",
+ TEH_MT_REQUEST_OTHER,
+ &res,
+ &del_auditor,
+ &dac);
+ if (GNUNET_SYSERR == ret)
+ return res;
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_auditors_AP_disable.c */
diff --git a/src/exchange/taler-exchange-httpd_management_denominations_HDP_revoke.c b/src/exchange/taler-exchange-httpd_management_denominations_HDP_revoke.c
new file mode 100644
index 000000000..32da72fbd
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_denominations_HDP_revoke.c
@@ -0,0 +1,94 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as 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_denominations_HDP_revoke.c
+ * @brief Handle denomination revocation requests.
+ * @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-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+MHD_RESULT
+TEH_handler_management_denominations_HDP_revoke (
+ struct MHD_Connection *connection,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const json_t *root)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ {
+ 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_denomination_revoke_verify (
+ h_denom_pub,
+ &TEH_master_public_key,
+ &master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_DENOMINATION_REVOKE_SIGNATURE_INVALID,
+ NULL);
+ }
+ qs = TEH_plugin->insert_denomination_revocation (TEH_plugin->cls,
+ h_denom_pub,
+ &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,
+ "denomination revocation");
+ }
+ TEH_keys_denomination_revoke (h_denom_pub);
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_denominations_HDP_revoke.c */
diff --git a/src/exchange/taler-exchange-httpd_management_drain.c b/src/exchange/taler-exchange-httpd_management_drain.c
new file mode 100644
index 000000000..1e490d799
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_drain.c
@@ -0,0 +1,195 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_drain.c
+ * @brief Handle request to drain profits
+ * @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_keys.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Closure for the #drain transaction.
+ */
+struct DrainContext
+{
+ /**
+ * Fee's signature affirming the #TALER_SIGNATURE_MASTER_DRAIN_PROFITS operation.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Wire transfer identifier to use.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ /**
+ * Account to credit.
+ */
+ const char *payto_uri;
+
+ /**
+ * Configuration section with account to debit.
+ */
+ const char *account_section;
+
+ /**
+ * Signature time.
+ */
+ struct GNUNET_TIME_Timestamp date;
+
+ /**
+ * Amount to transfer.
+ */
+ struct TALER_Amount amount;
+
+};
+
+
+/**
+ * Function implementing database transaction to drain profits. 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 DrainContext`
+ * @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
+drain (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct DrainContext *dc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->insert_drain_profit (
+ TEH_plugin->cls,
+ &dc->wtid,
+ dc->account_section,
+ dc->payto_uri,
+ dc->date,
+ &dc->amount,
+ &dc->master_sig);
+ 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 drain profit");
+ return qs;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_drain (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct DrainContext dc;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("debit_account_section",
+ &dc.account_section),
+ TALER_JSON_spec_payto_uri ("credit_payto_uri",
+ &dc.payto_uri),
+ GNUNET_JSON_spec_fixed_auto ("wtid",
+ &dc.wtid),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &dc.master_sig),
+ GNUNET_JSON_spec_timestamp ("date",
+ &dc.date),
+ TALER_JSON_spec_amount ("amount",
+ TEH_currency,
+ &dc.amount),
+ 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_profit_drain_verify (
+ &dc.wtid,
+ dc.date,
+ &dc.amount,
+ dc.account_section,
+ dc.payto_uri,
+ &TEH_master_public_key,
+ &dc.master_sig))
+ {
+ /* signature invalid */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_DRAIN_PROFITS_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ MHD_RESULT ret;
+
+ res = TEH_DB_run_transaction (connection,
+ "insert drain profit",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &drain,
+ &dc);
+ if (GNUNET_SYSERR == res)
+ return ret;
+ }
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_drain.c */
diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c b/src/exchange/taler-exchange-httpd_management_extensions.c
new file mode 100644
index 000000000..3b24bace7
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_extensions.c
@@ -0,0 +1,300 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_management_extensions.c
+ * @brief Handle request to POST /management/extensions
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.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"
+#include "taler_extensions.h"
+#include "taler_dbevents.h"
+
+/**
+ * Extension carries the necessary data for a particular extension.
+ *
+ */
+struct Extension
+{
+ enum TALER_Extension_Type type;
+ json_t *manifest;
+};
+
+/**
+ * Closure for the #set_extensions transaction
+ */
+struct SetExtensionsContext
+{
+ uint32_t num_extensions;
+ struct Extension *extensions;
+ struct TALER_MasterSignatureP extensions_sig;
+};
+
+/**
+ * Function implementing database transaction to set the manifests of
+ * extensions. It 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 SetExtensionsContext`
+ * @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
+set_extensions (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct SetExtensionsContext *sec = cls;
+
+ /* save the manifests of all extensions */
+ for (uint32_t i = 0; i<sec->num_extensions; i++)
+ {
+ struct Extension *ext = &sec->extensions[i];
+ const struct TALER_Extension *taler_ext;
+ enum GNUNET_DB_QueryStatus qs;
+ char *manifest;
+
+ taler_ext = TALER_extensions_get_by_type (ext->type);
+ if (NULL == taler_ext)
+ {
+ /* No such extension found */
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ manifest = json_dumps (ext->manifest, JSON_COMPACT | JSON_SORT_KEYS);
+ if (NULL == manifest)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_INVALID,
+ "convert configuration to string");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ qs = TEH_plugin->set_extension_manifest (
+ TEH_plugin->cls,
+ taler_ext->name,
+ manifest);
+
+ free (manifest);
+
+ 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,
+ "save extension configuration");
+ }
+
+ /* Success, trigger event */
+ {
+ uint32_t nbo_type = htonl (sec->extensions[i].type);
+ struct GNUNET_DB_EventHeaderP ev = {
+ .size = htons (sizeof (ev)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED)
+ };
+
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &ev,
+ &nbo_type,
+ sizeof(nbo_type));
+ }
+
+ }
+
+ /* All extensions configured, update the signature */
+ TEH_extensions_sig = sec->extensions_sig;
+ TEH_extensions_signed = true;
+
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* only 'success', so >=0, matters here */
+}
+
+
+static enum GNUNET_GenericReturnValue
+verify_extensions_from_json (
+ const json_t *extensions,
+ struct SetExtensionsContext *sec)
+{
+ const char*name;
+ const struct TALER_Extension *extension;
+ size_t i = 0;
+ json_t *manifest;
+
+ GNUNET_assert (NULL != extensions);
+ GNUNET_assert (json_is_object (extensions));
+
+ sec->num_extensions = json_object_size (extensions);
+ sec->extensions = GNUNET_new_array (sec->num_extensions,
+ struct Extension);
+
+ json_object_foreach ((json_t *) extensions, name, manifest)
+ {
+ int critical = 0;
+ json_t *config;
+ const char *version = NULL;
+
+ /* load and verify criticality, version, etc. */
+ extension = TALER_extensions_get_by_name (name);
+ if (NULL == extension)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "no such extension: %s\n", name);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_extensions_parse_manifest (
+ manifest, &critical, &version, &config))
+ return GNUNET_SYSERR;
+
+ if (critical != extension->critical
+ || 0 != strcmp (version, extension->version) // FIXME-oec: libtool compare
+ || NULL == config
+ || GNUNET_OK != extension->load_config (config, NULL))
+ return GNUNET_SYSERR;
+
+ sec->extensions[i].type = extension->type;
+ sec->extensions[i].manifest = json_copy (manifest);
+ }
+
+ return GNUNET_OK;
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_extensions (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ MHD_RESULT ret;
+ const json_t *extensions;
+ struct SetExtensionsContext sec = {0};
+ struct GNUNET_JSON_Specification top_spec[] = {
+ GNUNET_JSON_spec_object_const ("extensions",
+ &extensions),
+ GNUNET_JSON_spec_fixed_auto ("extensions_sig",
+ &sec.extensions_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ /* Parse the top level json structure */
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ top_spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+
+ /* Verify the signature */
+ {
+ struct TALER_ExtensionManifestsHashP h_manifests;
+
+ if (GNUNET_OK !=
+ TALER_JSON_extensions_manifests_hash (extensions,
+ &h_manifests) ||
+ GNUNET_OK !=
+ TALER_exchange_offline_extension_manifests_hash_verify (
+ &h_manifests,
+ &TEH_master_public_key,
+ &sec.extensions_sig))
+ {
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "invalid signuture");
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received /management/extensions\n");
+
+ /* Now parse individual extensions and signatures from those objects. */
+ if (GNUNET_OK !=
+ verify_extensions_from_json (extensions, &sec))
+ {
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "invalid object");
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received %u extensions\n",
+ sec.num_extensions);
+
+ /* now run the transaction to persist the configurations */
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TEH_DB_run_transaction (connection,
+ "set extensions",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &set_extensions,
+ &sec);
+
+ if (GNUNET_SYSERR == res)
+ goto CLEANUP;
+ }
+
+ ret = TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+
+CLEANUP:
+ for (unsigned int i = 0; i < sec.num_extensions; i++)
+ {
+ if (NULL != sec.extensions[i].manifest)
+ {
+ json_decref (sec.extensions[i].manifest);
+ }
+ }
+ GNUNET_free (sec.extensions);
+ return ret;
+}
+
+
+/* end of taler-exchange-httpd_management_management_post_extensions.c */
diff --git a/src/exchange/taler-exchange-httpd_management_global_fees.c b/src/exchange/taler-exchange-httpd_management_global_fees.c
new file mode 100644
index 000000000..8203ddefb
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_global_fees.c
@@ -0,0 +1,261 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 2021, 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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_global_fees.c
+ * @brief Handle request to add global fee details
+ * @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_keys.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Closure for the #add_fee transaction.
+ */
+struct AddFeeContext
+{
+ /**
+ * Fee's signature affirming the #TALER_SIGNATURE_MASTER_GLOBAL_FEES operation.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Starting period.
+ */
+ struct GNUNET_TIME_Timestamp start_time;
+
+ /**
+ * End of period.
+ */
+ struct GNUNET_TIME_Timestamp end_time;
+
+ /**
+ * Global fee amounts.
+ */
+ struct TALER_GlobalFeeSet fees;
+
+ /**
+ * When does an unmerged purse expire?
+ */
+ struct GNUNET_TIME_Relative purse_timeout;
+
+ /**
+ * When does an account without KYC expire?
+ */
+ struct GNUNET_TIME_Relative kyc_timeout;
+
+ /**
+ * When does an account history expire?
+ */
+ struct GNUNET_TIME_Relative history_expiration;
+
+ /**
+ * Number of free purses per account.
+ */
+ uint32_t purse_account_limit;
+
+};
+
+
+/**
+ * Function implementing database transaction to add a fee. 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 AddFeeContext`
+ * @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
+add_fee (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct AddFeeContext *afc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_GlobalFeeSet fees;
+ struct GNUNET_TIME_Relative purse_timeout;
+ struct GNUNET_TIME_Relative history_expiration;
+ uint32_t purse_account_limit;
+
+ qs = TEH_plugin->lookup_global_fee_by_time (
+ TEH_plugin->cls,
+ afc->start_time,
+ afc->end_time,
+ &fees,
+ &purse_timeout,
+ &history_expiration,
+ &purse_account_limit);
+ 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,
+ "lookup global fee");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+ {
+ if ( (GNUNET_OK ==
+ TALER_amount_is_valid (&fees.history)) &&
+ (0 ==
+ TALER_global_fee_set_cmp (&fees,
+ &afc->fees)) )
+ {
+ /* this will trigger the 'success' response */
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ else
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_GLOBAL_FEE_MISMATCH,
+ NULL);
+ }
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ qs = TEH_plugin->insert_global_fee (
+ TEH_plugin->cls,
+ afc->start_time,
+ afc->end_time,
+ &afc->fees,
+ afc->purse_timeout,
+ afc->history_expiration,
+ afc->purse_account_limit,
+ &afc->master_sig);
+ 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 fee");
+ return qs;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_global_fees (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct AddFeeContext afc;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &afc.master_sig),
+ GNUNET_JSON_spec_timestamp ("fee_start",
+ &afc.start_time),
+ GNUNET_JSON_spec_timestamp ("fee_end",
+ &afc.end_time),
+ TALER_JSON_spec_amount ("history_fee",
+ TEH_currency,
+ &afc.fees.history),
+ TALER_JSON_spec_amount ("account_fee",
+ TEH_currency,
+ &afc.fees.account),
+ TALER_JSON_spec_amount ("purse_fee",
+ TEH_currency,
+ &afc.fees.purse),
+ GNUNET_JSON_spec_relative_time ("purse_timeout",
+ &afc.purse_timeout),
+ GNUNET_JSON_spec_relative_time ("history_expiration",
+ &afc.history_expiration),
+ GNUNET_JSON_spec_uint32 ("purse_account_limit",
+ &afc.purse_account_limit),
+ 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_global_fee_verify (
+ afc.start_time,
+ afc.end_time,
+ &afc.fees,
+ afc.purse_timeout,
+ afc.history_expiration,
+ afc.purse_account_limit,
+ &TEH_master_public_key,
+ &afc.master_sig))
+ {
+ /* signature invalid */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_GLOBAL_FEE_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ MHD_RESULT ret;
+
+ res = TEH_DB_run_transaction (connection,
+ "add global fee",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &add_fee,
+ &afc);
+ if (GNUNET_SYSERR == res)
+ return ret;
+ }
+ TEH_keys_update_states ();
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_global_fees.c */
diff --git a/src/exchange/taler-exchange-httpd_management_partners.c b/src/exchange/taler-exchange-httpd_management_partners.c
new file mode 100644
index 000000000..fc8a4207d
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_partners.c
@@ -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),
+ TALER_JSON_spec_web_url ("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-#7271: 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 */
diff --git a/src/exchange/taler-exchange-httpd_management_post_keys.c b/src/exchange/taler-exchange-httpd_management_post_keys.c
new file mode 100644
index 000000000..f91f24c41
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_post_keys.c
@@ -0,0 +1,487 @@
+/*
+ This file is part of TALER
+ 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 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_post_keys.c
+ * @brief Handle request to POST /management/keys
+ * @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_keys.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Denomination signature provided.
+ */
+struct DenomSig
+{
+ /**
+ * Hash of a denomination public key.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Master signature for the @e h_denom_pub.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Fee structure for this key, as per our configuration.
+ */
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
+
+ /**
+ * The full public key.
+ */
+ struct TALER_DenominationPublicKey denom_pub;
+
+};
+
+
+/**
+ * Signkey signature provided.
+ */
+struct SigningSig
+{
+ /**
+ * Online signing key of the exchange.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Master signature for the @e exchange_pub.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Our meta data on this key.
+ */
+ struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+
+};
+
+
+/**
+ * Closure for the #add_keys transaction.
+ */
+struct AddKeysContext
+{
+
+ /**
+ * Array of @e nd_sigs denomination signatures.
+ */
+ struct DenomSig *d_sigs;
+
+ /**
+ * Array of @e ns_sigs signkey signatures.
+ */
+ struct SigningSig *s_sigs;
+
+ /**
+ * Our key state.
+ */
+ struct TEH_KeyStateHandle *ksh;
+
+ /**
+ * Length of the d_sigs array.
+ */
+ unsigned int nd_sigs;
+
+ /**
+ * Length of the n_sigs array.
+ */
+ unsigned int ns_sigs;
+
+};
+
+
+/**
+ * Function implementing database transaction to add offline signing keys.
+ * 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 AddKeysContext`
+ * @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
+add_keys (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct AddKeysContext *akc = cls;
+
+ /* activate all denomination keys */
+ for (unsigned int i = 0; i<akc->nd_sigs; i++)
+ {
+ struct DenomSig *d = &akc->d_sigs[i];
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
+
+ /* For idempotency, check if the key is already active */
+ qs = TEH_plugin->lookup_denomination_key (
+ TEH_plugin->cls,
+ &d->h_denom_pub,
+ &meta);
+ 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,
+ "lookup denomination key");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ /* FIXME: assert meta === d->meta might be good */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Denomination key %s already active, skipping\n",
+ GNUNET_h2s (&d->h_denom_pub.hash));
+ continue; /* skip, already known */
+ }
+
+ qs = TEH_plugin->add_denomination_key (
+ TEH_plugin->cls,
+ &d->h_denom_pub,
+ &d->denom_pub,
+ &d->meta,
+ &d->master_sig);
+ 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,
+ "activate denomination key");
+ return qs;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Added offline signature for denomination `%s'\n",
+ GNUNET_h2s (&d->h_denom_pub.hash));
+ GNUNET_assert (0 != qs);
+ }
+
+ for (unsigned int i = 0; i<akc->ns_sigs; i++)
+ {
+ struct SigningSig *s = &akc->s_sigs[i];
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+
+ qs = TEH_plugin->lookup_signing_key (
+ TEH_plugin->cls,
+ &s->exchange_pub,
+ &meta);
+ 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,
+ "lookup signing key");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ /* FIXME: assert meta === d->meta might be good */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing key %s already active, skipping\n",
+ TALER_B2S (&s->exchange_pub));
+ continue; /* skip, already known */
+ }
+ qs = TEH_plugin->activate_signing_key (
+ TEH_plugin->cls,
+ &s->exchange_pub,
+ &s->meta,
+ &s->master_sig);
+ 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,
+ "activate signing key");
+ return qs;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Added offline signature for signing key `%s'\n",
+ TALER_B2S (&s->exchange_pub));
+ GNUNET_assert (0 != qs);
+ }
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* only 'success', so >=0, matters here */
+}
+
+
+/**
+ * Clean up state in @a akc, but do not free @a akc itself
+ *
+ * @param[in,out] akc state to clean up
+ */
+static void
+cleanup_akc (struct AddKeysContext *akc)
+{
+ for (unsigned int i = 0; i<akc->nd_sigs; i++)
+ {
+ struct DenomSig *d = &akc->d_sigs[i];
+
+ TALER_denom_pub_free (&d->denom_pub);
+ }
+ GNUNET_free (akc->d_sigs);
+ GNUNET_free (akc->s_sigs);
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_keys (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct AddKeysContext akc = { 0 };
+ const json_t *denom_sigs;
+ const json_t *signkey_sigs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("denom_sigs",
+ &denom_sigs),
+ GNUNET_JSON_spec_array_const ("signkey_sigs",
+ &signkey_sigs),
+ GNUNET_JSON_spec_end ()
+ };
+ MHD_RESULT ret;
+
+ {
+ 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 */
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received POST /management/keys request\n");
+
+ akc.ksh = TEH_keys_get_state_for_management_only (); /* may start its own transaction, thus must be done here, before we run ours! */
+ if (NULL == akc.ksh)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ "no key state (not even for management)");
+ }
+
+ akc.nd_sigs = json_array_size (denom_sigs);
+ akc.d_sigs = GNUNET_new_array (akc.nd_sigs,
+ struct DenomSig);
+ for (unsigned int i = 0; i<akc.nd_sigs; i++)
+ {
+ struct DenomSig *d = &akc.d_sigs[i];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &d->master_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &d->h_denom_pub),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ json_array_get (denom_sigs,
+ i),
+ ispec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failure to handle /management/keys\n");
+ cleanup_akc (&akc);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+
+ res = TEH_keys_load_fees (akc.ksh,
+ &d->h_denom_pub,
+ &d->denom_pub,
+ &d->meta);
+ switch (res)
+ {
+ case GNUNET_SYSERR:
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+ GNUNET_h2s (&d->h_denom_pub.hash));
+ cleanup_akc (&akc);
+ return ret;
+ case GNUNET_NO:
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
+ GNUNET_h2s (&d->h_denom_pub.hash));
+ cleanup_akc (&akc);
+ return ret;
+ case GNUNET_OK:
+ break;
+ }
+ /* check signature is valid */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_denom_validity_verify (
+ &d->h_denom_pub,
+ d->meta.start,
+ d->meta.expire_withdraw,
+ d->meta.expire_deposit,
+ d->meta.expire_legal,
+ &d->meta.value,
+ &d->meta.fees,
+ &TEH_master_public_key,
+ &d->master_sig))
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_KEYS_DENOMKEY_ADD_SIGNATURE_INVALID,
+ GNUNET_h2s (&d->h_denom_pub.hash));
+ cleanup_akc (&akc);
+ return ret;
+ }
+ }
+
+ akc.ns_sigs = json_array_size (signkey_sigs);
+ akc.s_sigs = GNUNET_new_array (akc.ns_sigs,
+ struct SigningSig);
+ for (unsigned int i = 0; i<akc.ns_sigs; i++)
+ {
+ struct SigningSig *s = &akc.s_sigs[i];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &s->master_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &s->exchange_pub),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ json_array_get (signkey_sigs,
+ i),
+ ispec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failure to handle /management/keys\n");
+ cleanup_akc (&akc);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+ res = TEH_keys_get_timing (&s->exchange_pub,
+ &s->meta);
+ switch (res)
+ {
+ case GNUNET_SYSERR:
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+ TALER_B2S (&s->exchange_pub));
+ cleanup_akc (&akc);
+ return ret;
+ case GNUNET_NO:
+ /* For idempotency, check if the key is already active */
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_UNKNOWN,
+ TALER_B2S (&s->exchange_pub));
+ cleanup_akc (&akc);
+ return ret;
+ case GNUNET_OK:
+ break;
+ }
+
+ /* check signature is valid */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_signkey_validity_verify (
+ &s->exchange_pub,
+ s->meta.start,
+ s->meta.expire_sign,
+ s->meta.expire_legal,
+ &TEH_master_public_key,
+ &s->master_sig))
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_ADD_SIGNATURE_INVALID,
+ TALER_B2S (&s->exchange_pub));
+ cleanup_akc (&akc);
+ return ret;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received %u denomination and %u signing key signatures\n",
+ akc.nd_sigs,
+ akc.ns_sigs);
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TEH_DB_run_transaction (connection,
+ "add keys",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &add_keys,
+ &akc);
+ cleanup_akc (&akc);
+ if (GNUNET_SYSERR == res)
+ return ret;
+ }
+ TEH_keys_update_states ();
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_management_post_keys.c */
diff --git a/src/exchange/taler-exchange-httpd_management_signkey_EP_revoke.c b/src/exchange/taler-exchange-httpd_management_signkey_EP_revoke.c
new file mode 100644
index 000000000..6c3683fc5
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_signkey_EP_revoke.c
@@ -0,0 +1,93 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as 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_signkey_EP_revoke.c
+ * @brief Handle exchange online signing key revocation requests.
+ * @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-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+MHD_RESULT
+TEH_handler_management_signkeys_EP_revoke (
+ struct MHD_Connection *connection,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const json_t *root)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ {
+ 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_signkey_revoke_verify (exchange_pub,
+ &TEH_master_public_key,
+ &master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_SIGNKEY_REVOKE_SIGNATURE_INVALID,
+ NULL);
+ }
+ qs = TEH_plugin->insert_signkey_revocation (TEH_plugin->cls,
+ exchange_pub,
+ &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,
+ "signkey revocation");
+ }
+ TEH_keys_update_states ();
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_signkey_HDP_revoke.c */
diff --git a/src/exchange/taler-exchange-httpd_management_wire_disable.c b/src/exchange/taler-exchange-httpd_management_wire_disable.c
new file mode 100644
index 000000000..e0b8a3de8
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_wire_disable.c
@@ -0,0 +1,205 @@
+/*
+ This file is part of TALER
+ 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 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_wire_disable.c
+ * @brief Handle request to disable wire account.
+ * @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-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+/**
+ * Closure for the #del_wire transaction.
+ */
+struct DelWireContext
+{
+ /**
+ * Master signature affirming the WIRE DEL operation
+ * (includes timestamp).
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Payto:// URI this is about.
+ */
+ const char *payto_uri;
+
+ /**
+ * Timestamp for checking against replay attacks.
+ */
+ struct GNUNET_TIME_Timestamp validity_end;
+
+};
+
+
+/**
+ * Function implementing database transaction to del an wire. 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 DelWireContext`
+ * @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
+del_wire (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct DelWireContext *awc = cls;
+ struct GNUNET_TIME_Timestamp last_date;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->lookup_wire_timestamp (TEH_plugin->cls,
+ awc->payto_uri,
+ &last_date);
+ 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,
+ "lookup wire");
+ return qs;
+ }
+ if (GNUNET_TIME_timestamp_cmp (last_date,
+ >,
+ awc->validity_end))
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_MORE_RECENT_PRESENT,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_NOT_FOUND,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ qs = TEH_plugin->update_wire (TEH_plugin->cls,
+ awc->payto_uri,
+ NULL,
+ NULL,
+ NULL,
+ awc->validity_end,
+ NULL,
+ NULL,
+ 0,
+ false);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "del wire");
+ return qs;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_wire_disable (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct DelWireContext awc;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig_del",
+ &awc.master_sig),
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &awc.payto_uri),
+ GNUNET_JSON_spec_timestamp ("validity_end",
+ &awc.validity_end),
+ 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 */
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_offline_wire_del_verify (
+ awc.payto_uri,
+ awc.validity_end,
+ &TEH_master_public_key,
+ &awc.master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_DEL_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ MHD_RESULT ret;
+
+ res = TEH_DB_run_transaction (connection,
+ "del wire",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &del_wire,
+ &awc);
+ if (GNUNET_SYSERR == res)
+ return ret;
+ }
+ TEH_wire_update_state ();
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_wire_disable.c */
diff --git a/src/exchange/taler-exchange-httpd_management_wire_enable.c b/src/exchange/taler-exchange-httpd_management_wire_enable.c
new file mode 100644
index 000000000..472e19d3e
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_wire_enable.c
@@ -0,0 +1,320 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-2024 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_wire_enable.c
+ * @brief Handle request to add wire account.
+ * @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"
+#include "taler-exchange-httpd_keys.h"
+
+
+/**
+ * Closure for the #add_wire transaction.
+ */
+struct AddWireContext
+{
+ /**
+ * Master signature affirming the WIRE ADD operation
+ * (includes timestamp).
+ */
+ struct TALER_MasterSignatureP master_sig_add;
+
+ /**
+ * Master signature to share with clients affirming the
+ * wire details of the bank.
+ */
+ struct TALER_MasterSignatureP master_sig_wire;
+
+ /**
+ * Payto:// URI this is about.
+ */
+ const char *payto_uri;
+
+ /**
+ * (optional) address of a conversion service for this account.
+ */
+ const char *conversion_url;
+
+ /**
+ * Restrictions imposed when crediting this account.
+ */
+ const json_t *credit_restrictions;
+
+ /**
+ * Restrictions imposed when debiting this account.
+ */
+ const json_t *debit_restrictions;
+
+ /**
+ * Timestamp for checking against replay attacks.
+ */
+ struct GNUNET_TIME_Timestamp validity_start;
+
+ /**
+ * Label to use for this bank. Default is empty.
+ */
+ const char *bank_label;
+
+ /**
+ * Priority of the bank in the list. Default 0.
+ */
+ int64_t priority;
+
+};
+
+
+/**
+ * Function implementing database transaction to add an wire. 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 AddWireContext`
+ * @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
+add_wire (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct AddWireContext *awc = cls;
+ struct GNUNET_TIME_Timestamp last_date;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->lookup_wire_timestamp (TEH_plugin->cls,
+ awc->payto_uri,
+ &last_date);
+ 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,
+ "lookup wire");
+ return qs;
+ }
+ if ( (0 < qs) &&
+ (GNUNET_TIME_timestamp_cmp (last_date,
+ >,
+ awc->validity_start)) )
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_MORE_RECENT_PRESENT,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ qs = TEH_plugin->insert_wire (TEH_plugin->cls,
+ awc->payto_uri,
+ awc->conversion_url,
+ awc->debit_restrictions,
+ awc->credit_restrictions,
+ awc->validity_start,
+ &awc->master_sig_wire,
+ awc->bank_label,
+ awc->priority);
+ else
+ qs = TEH_plugin->update_wire (TEH_plugin->cls,
+ awc->payto_uri,
+ awc->conversion_url,
+ awc->debit_restrictions,
+ awc->credit_restrictions,
+ awc->validity_start,
+ &awc->master_sig_wire,
+ awc->bank_label,
+ awc->priority,
+ true);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "add wire");
+ return qs;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_wire (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct AddWireContext awc = {
+ .conversion_url = NULL
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig_wire",
+ &awc.master_sig_wire),
+ GNUNET_JSON_spec_fixed_auto ("master_sig_add",
+ &awc.master_sig_add),
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &awc.payto_uri),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("conversion_url",
+ &awc.conversion_url),
+ NULL),
+ GNUNET_JSON_spec_array_const ("credit_restrictions",
+ &awc.credit_restrictions),
+ GNUNET_JSON_spec_array_const ("debit_restrictions",
+ &awc.debit_restrictions),
+ GNUNET_JSON_spec_timestamp ("validity_start",
+ &awc.validity_start),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("bank_label",
+ &awc.bank_label),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_int64 ("priority",
+ &awc.priority),
+ 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)
+ return MHD_YES; /* failure */
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ {
+ char *msg = TALER_payto_validate (awc.payto_uri);
+
+ if (NULL != msg)
+ {
+ MHD_RESULT ret;
+
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
+ msg);
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_free (msg);
+ return ret;
+ }
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_offline_wire_add_verify (
+ awc.payto_uri,
+ awc.conversion_url,
+ awc.debit_restrictions,
+ awc.credit_restrictions,
+ awc.validity_start,
+ &TEH_master_public_key,
+ &awc.master_sig_add))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_ADD_SIGNATURE_INVALID,
+ NULL);
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_wire_signature_check (
+ awc.payto_uri,
+ awc.conversion_url,
+ awc.debit_restrictions,
+ awc.credit_restrictions,
+ &TEH_master_public_key,
+ &awc.master_sig_wire))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_DETAILS_SIGNATURE_INVALID,
+ NULL);
+ }
+ {
+ char *wire_method;
+
+ wire_method = TALER_payto_get_method (awc.payto_uri);
+ if (NULL == wire_method)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "payto:// URI `%s' is malformed\n",
+ awc.payto_uri);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "payto_uri");
+ }
+ GNUNET_free (wire_method);
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ MHD_RESULT ret;
+
+ res = TEH_DB_run_transaction (connection,
+ "add wire",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &add_wire,
+ &awc);
+ GNUNET_JSON_parse_free (spec);
+ if (GNUNET_SYSERR == res)
+ return ret;
+ }
+ TEH_wire_update_state ();
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_wire.c */
diff --git a/src/exchange/taler-exchange-httpd_management_wire_fees.c b/src/exchange/taler-exchange-httpd_management_wire_fees.c
new file mode 100644
index 000000000..cb87592a5
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_wire_fees.c
@@ -0,0 +1,230 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 2021, 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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_wire_fees.c
+ * @brief Handle request to add wire fee details
+ * @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"
+#include "taler-exchange-httpd_keys.h"
+
+
+/**
+ * Closure for the #add_fee transaction.
+ */
+struct AddFeeContext
+{
+ /**
+ * Fee's signature affirming the #TALER_SIGNATURE_MASTER_WIRE_FEES operation.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Wire method this is about.
+ */
+ const char *wire_method;
+
+ /**
+ * Starting period.
+ */
+ struct GNUNET_TIME_Timestamp start_time;
+
+ /**
+ * End of period.
+ */
+ struct GNUNET_TIME_Timestamp end_time;
+
+ /**
+ * Wire fee amounts.
+ */
+ struct TALER_WireFeeSet fees;
+
+};
+
+
+/**
+ * Function implementing database transaction to add a fee. 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 AddFeeContext`
+ * @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
+add_fee (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct AddFeeContext *afc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_WireFeeSet fees;
+
+ qs = TEH_plugin->lookup_wire_fee_by_time (
+ TEH_plugin->cls,
+ afc->wire_method,
+ afc->start_time,
+ afc->end_time,
+ &fees);
+ 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,
+ "lookup wire fee");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+ {
+ if ( (GNUNET_OK ==
+ TALER_amount_is_valid (&fees.wire)) &&
+ (0 ==
+ TALER_wire_fee_set_cmp (&fees,
+ &afc->fees)) )
+ {
+ /* this will trigger the 'success' response */
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ else
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_FEE_MISMATCH,
+ NULL);
+ }
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ qs = TEH_plugin->insert_wire_fee (
+ TEH_plugin->cls,
+ afc->wire_method,
+ afc->start_time,
+ afc->end_time,
+ &afc->fees,
+ &afc->master_sig);
+ 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 fee");
+ return qs;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_wire_fees (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct AddFeeContext afc;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &afc.master_sig),
+ GNUNET_JSON_spec_string ("wire_method",
+ &afc.wire_method),
+ GNUNET_JSON_spec_timestamp ("fee_start",
+ &afc.start_time),
+ GNUNET_JSON_spec_timestamp ("fee_end",
+ &afc.end_time),
+ TALER_JSON_spec_amount ("wire_fee",
+ TEH_currency,
+ &afc.fees.wire),
+ TALER_JSON_spec_amount ("closing_fee",
+ TEH_currency,
+ &afc.fees.closing),
+ 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_wire_fee_verify (
+ afc.wire_method,
+ afc.start_time,
+ afc.end_time,
+ &afc.fees,
+ &TEH_master_public_key,
+ &afc.master_sig))
+ {
+ /* signature invalid */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_FEE_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ MHD_RESULT ret;
+
+ res = TEH_DB_run_transaction (connection,
+ "add wire fee",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &add_fee,
+ &afc);
+ if (GNUNET_SYSERR == res)
+ return ret;
+ }
+ TEH_wire_update_state ();
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_wire_fees.c */
diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c
index f5bd0b5aa..b31078f00 100644
--- a/src/exchange/taler-exchange-httpd_melt.c
+++ b/src/exchange/taler-exchange-httpd_melt.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
+ Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -29,61 +29,8 @@
#include "taler-exchange-httpd_mhd.h"
#include "taler-exchange-httpd_melt.h"
#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keystate.h"
-
-
-/**
- * Send a response for a failed "melt" request. The
- * transaction history of the given coin demonstrates that the
- * @a residual value of the coin is below the @a requested
- * contribution of the coin for the melt. Thus, the exchange
- * refuses the melt operation.
- *
- * @param connection the connection to send the response to
- * @param coin_pub public key of the coin
- * @param coin_value original value of the coin
- * @param tl transaction history for the coin
- * @param requested how much this coin was supposed to contribute, including fee
- * @param residual remaining value of the coin (after subtracting @a tl)
- * @return a MHD result code
- */
-static int
-reply_melt_insufficient_funds (
- struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *coin_value,
- struct TALER_EXCHANGEDB_TransactionList *tl,
- const struct TALER_Amount *requested,
- const struct TALER_Amount *residual)
-{
- json_t *history;
-
- history = TEH_RESPONSE_compile_transaction_history (coin_pub,
- tl);
- if (NULL == history)
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS,
- "Failed to compile transaction history");
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_CONFLICT,
- "{s:s, s:I, s:o, s:o, s:o, s:o, s:o}",
- "hint",
- "insufficient funds",
- "code",
- (json_int_t)
- TALER_EC_MELT_INSUFFICIENT_FUNDS,
- "coin_pub",
- GNUNET_JSON_from_data_auto (coin_pub),
- "original_value",
- TALER_JSON_from_amount (coin_value),
- "residual_value",
- TALER_JSON_from_amount (residual),
- "requested_value",
- TALER_JSON_from_amount (requested),
- "history",
- history);
-}
+#include "taler-exchange-httpd_keys.h"
+#include "taler_exchangedb_lib.h"
/**
@@ -94,37 +41,36 @@ reply_melt_insufficient_funds (
* @param noreveal_index which index will the client not have to reveal
* @return a MHD status code
*/
-static int
+static MHD_RESULT
reply_melt_success (struct MHD_Connection *connection,
const struct TALER_RefreshCommitmentP *rc,
uint32_t noreveal_index)
{
struct TALER_ExchangePublicKeyP pub;
struct TALER_ExchangeSignatureP sig;
- struct TALER_RefreshMeltConfirmationPS body = {
- .purpose.size = htonl (sizeof (body)),
- .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT),
- .rc = *rc,
- .noreveal_index = htonl (noreveal_index)
- };
-
- if (GNUNET_OK !=
- TEH_KS_sign (&body.purpose,
- &pub,
- &sig))
+ enum TALER_ErrorCode ec;
+
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_melt_confirmation_sign (
+ &TEH_keys_exchange_sign_,
+ rc,
+ noreveal_index,
+ &pub,
+ &sig)))
{
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_BAD_CONFIGURATION,
- "no keys");
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
}
- return TALER_MHD_reply_json_pack (
+ return TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_OK,
- "{s:i, s:o, s:o}",
- "noreveal_index", (int) noreveal_index,
- "exchange_sig", GNUNET_JSON_from_data_auto (&sig),
- "exchange_pub", GNUNET_JSON_from_data_auto (&pub));
+ GNUNET_JSON_pack_uint64 ("noreveal_index",
+ noreveal_index),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub));
}
@@ -141,6 +87,11 @@ struct MeltContext
struct TALER_EXCHANGEDB_Refresh refresh_session;
/**
+ * UUID of the coin in the known_coins table.
+ */
+ uint64_t known_coin_id;
+
+ /**
* Information about the @e coin's value.
*/
struct TALER_Amount coin_value;
@@ -151,125 +102,28 @@ struct MeltContext
struct TALER_Amount coin_refresh_fee;
/**
- * Set to #GNUNET_YES if this coin's denomination was revoked and the operation
+ * Refresh master secret, if any of the fresh denominations use CS.
+ */
+ struct TALER_RefreshMasterSecretP rms;
+
+ /**
+ * Set to true if this coin's denomination was revoked and the operation
* is thus only allowed for zombie coins where the transaction
* history includes a #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP.
*/
- int zombie_required;
-
-};
-
-
-/**
- * Check that the coin has sufficient funds left for the selected
- * melt operation.
- *
- * @param connection the connection to send errors to
- * @param session the database connection
- * @param[in,out] rmc melt context
- * @param[out] mhd_ret status code to return to MHD on hard error
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-refresh_check_melt (struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- struct MeltContext *rmc,
- int *mhd_ret)
-{
- struct TALER_EXCHANGEDB_TransactionList *tl;
- struct TALER_Amount spent;
- enum GNUNET_DB_QueryStatus qs;
-
- /* Start with cost of this melt transaction */
- spent = rmc->refresh_session.amount_with_fee;
-
- /* get historic transaction costs of this coin, including recoups as
- we might be a zombie coin */
- qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
- session,
- &rmc->refresh_session.coin.coin_pub,
- GNUNET_YES,
- &tl);
- 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_MELT_DB_FETCH_ERROR,
- "failed to fetch old coin history");
- return qs;
- }
- if (rmc->zombie_required)
- {
- /* The denomination key is only usable for a melt if this is a true
- zombie coin, i.e. it was refreshed and the resulting fresh coin was
- then recouped. Check that this is truly the case. */
- for (struct TALER_EXCHANGEDB_TransactionList *tp = tl;
- NULL != tp;
- tp = tp->next)
- {
- if (TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP == tp->type)
- {
- rmc->zombie_required = GNUNET_NO; /* clear flag: was satisfied! */
- break;
- }
- }
- if (GNUNET_YES == rmc->zombie_required)
- {
- /* zombie status not satisfied */
- GNUNET_break_op (0);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_MELT_COIN_EXPIRED_NO_ZOMBIE,
- "denomination expired");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- if (GNUNET_OK !=
- TALER_EXCHANGEDB_calculate_transaction_list_totals (tl,
- &spent,
- &spent))
- {
- GNUNET_break (0);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MELT_COIN_HISTORY_COMPUTATION_FAILED,
- "failed to compute coin transaction history");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
+ bool zombie_required;
- /* Refuse to refresh when the coin's value is insufficient
- for the cost of all transactions. */
- if (0 > TALER_amount_cmp (&rmc->coin_value,
- &spent))
- {
- struct TALER_Amount coin_residual;
-
- GNUNET_assert (GNUNET_SYSERR !=
- TALER_amount_subtract (&coin_residual,
- &spent,
- &rmc->refresh_session.amount_with_fee));
- *mhd_ret = reply_melt_insufficient_funds (
- connection,
- &rmc->refresh_session.coin.coin_pub,
- &rmc->coin_value,
- tl,
- &rmc->refresh_session.amount_with_fee,
- &coin_residual);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
+ /**
+ * We already checked and noticed that the coin is known. Hence we
+ * can skip the "ensure_coin_known" step of the transaction.
+ */
+ bool coin_is_dirty;
- /* we're good, coin has sufficient funds to be melted */
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
+ /**
+ * True if @e rms is missing.
+ */
+ bool no_rms;
+};
/**
@@ -287,7 +141,6 @@ refresh_check_melt (struct MHD_Connection *connection,
*
* @param cls our `struct MeltContext`
* @param connection MHD request which triggered the transaction
- * @param session database session to use
* @param[out] mhd_ret set to MHD response status for @a connection,
* if transaction failed (!)
* @return transaction status
@@ -295,121 +148,121 @@ refresh_check_melt (struct MHD_Connection *connection,
static enum GNUNET_DB_QueryStatus
melt_transaction (void *cls,
struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
+ MHD_RESULT *mhd_ret)
{
struct MeltContext *rmc = cls;
enum GNUNET_DB_QueryStatus qs;
- uint32_t noreveal_index;
-
- /* Check if we already created a matching refresh_session */
- qs = TEH_plugin->get_melt_index (TEH_plugin->cls,
- session,
- &rmc->refresh_session.rc,
- &noreveal_index);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- TALER_LOG_DEBUG ("Coin was previously melted, returning old reply\n");
- *mhd_ret = reply_melt_success (connection,
- &rmc->refresh_session.rc,
- noreveal_index);
- /* Note: we return "hard error" to ensure the wrapper
- does not retry the transaction, and to also not generate
- a "fresh" response (as we would on "success") */
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- 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_MELT_DB_FETCH_ERROR,
- "failed to fetch melt index");
- return qs;
- }
-
- /* check coin has enough funds remaining on it to cover melt cost */
- qs = refresh_check_melt (connection,
- session,
- rmc,
- mhd_ret);
- if (0 > qs)
- return qs; /* if we failed, tell caller */
+ bool balance_ok;
/* pick challenge and persist it */
rmc->refresh_session.noreveal_index
= GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
TALER_CNC_KAPPA);
- if (0 >=
- (qs = TEH_plugin->insert_melt (TEH_plugin->cls,
- session,
- &rmc->refresh_session)))
+
+ if (0 >
+ (qs = TEH_plugin->do_melt (TEH_plugin->cls,
+ rmc->no_rms
+ ? NULL
+ : &rmc->rms,
+ &rmc->refresh_session,
+ rmc->known_coin_id,
+ &rmc->zombie_required,
+ &balance_ok)))
{
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
{
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MELT_DB_STORE_SESSION_ERROR,
- "failed to persist melt data");
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "melt");
return GNUNET_DB_STATUS_HARD_ERROR;
}
return qs;
}
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ if (rmc->zombie_required)
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! balance_ok)
+ {
+ GNUNET_break_op (0);
+ *mhd_ret
+ = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &rmc->refresh_session.coin.denom_pub_hash,
+ &rmc->refresh_session.coin.coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* All good, commit, final response will be generated by caller */
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_MELT]++;
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
/**
* Handle a "melt" request after the first parsing has
- * happened. We now need to validate the coins being melted and the
- * session signature and then hand things of to execute the melt
- * operation. This function parses the JSON arrays and then passes
- * processing on to #melt_transaction().
+ * happened. Performs the database transactions.
*
* @param connection the MHD connection to handle
* @param[in,out] rmc details about the melt request
* @return MHD result code
*/
-static int
-handle_melt (struct MHD_Connection *connection,
- struct MeltContext *rmc)
+static MHD_RESULT
+database_melt (struct MHD_Connection *connection,
+ struct MeltContext *rmc)
{
- /* verify signature of coin for melt operation */
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
{
- struct TALER_RefreshMeltCoinAffirmationPS body;
-
- body.purpose.size = htonl (sizeof (struct
- TALER_RefreshMeltCoinAffirmationPS));
- body.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
- body.rc = rmc->refresh_session.rc;
- TALER_amount_hton (&body.amount_with_fee,
- &rmc->refresh_session.amount_with_fee);
- TALER_amount_hton (&body.melt_fee,
- &rmc->coin_refresh_fee);
- body.coin_pub = rmc->refresh_session.coin.coin_pub;
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure");
+ }
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (
- TALER_SIGNATURE_WALLET_COIN_MELT,
- &body.purpose,
- &rmc->refresh_session.coin_sig.eddsa_signature,
- &rmc->refresh_session.coin.coin_pub.eddsa_pub))
+ /* first, make sure coin is known */
+ if (! rmc->coin_is_dirty)
+ {
+ MHD_RESULT mhd_ret = MHD_NO;
+ enum GNUNET_DB_QueryStatus qs;
+
+ for (unsigned int tries = 0; tries<MAX_TRANSACTION_COMMIT_RETRIES; tries++)
{
- GNUNET_break_op (0);
+ qs = TEH_make_coin_known (&rmc->refresh_session.coin,
+ connection,
+ &rmc->known_coin_id,
+ &mhd_ret);
+ if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+ break;
+ }
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_MELT_COIN_SIGNATURE_INVALID,
- "confirm_sig");
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "make_coin_known");
}
+ if (qs < 0)
+ return mhd_ret;
}
- /* run database transaction */
+ /* run main database transaction */
{
- int mhd_ret;
+ MHD_RESULT mhd_ret;
if (GNUNET_OK !=
TEH_DB_run_transaction (connection,
"run melt",
+ TEH_MT_REQUEST_MELT,
&mhd_ret,
&melt_transaction,
rmc))
@@ -431,159 +284,48 @@ handle_melt (struct MHD_Connection *connection,
* @param rmc parsed request information
* @return MHD status code
*/
-static int
-check_for_denomination_key (struct MHD_Connection *connection,
- struct MeltContext *rmc)
+static MHD_RESULT
+check_melt_valid (struct MHD_Connection *connection,
+ struct MeltContext *rmc)
{
- struct TEH_KS_StateHandle *key_state;
- int coin_is_dirty = GNUNET_NO;
+ /* Baseline: check if deposits/refreshes are generally
+ simply still allowed for this denomination */
+ struct TEH_DenominationKey *dk;
+ MHD_RESULT mret;
+
+ dk = TEH_keys_denomination_by_hash (
+ &rmc->refresh_session.coin.denom_pub_hash,
+ connection,
+ &mret);
+ if (NULL == dk)
+ return mret;
- key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
- if (NULL == key_state)
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_legal.abs_time))
{
- TALER_LOG_ERROR ("Lacking keys to operate\n");
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_BAD_CONFIGURATION,
- "no keys");
+ /* Way too late now, even zombies have expired */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &rmc->refresh_session.coin.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "MELT");
}
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
{
- /* Baseline: check if deposits/refreshs are generally
- simply still allowed for this denomination */
- struct TALER_EXCHANGEDB_DenominationKey *dki;
- unsigned int hc;
- enum TALER_ErrorCode ec;
-
- dki = TEH_KS_denomination_key_lookup_by_hash (
- key_state,
+ /* This denomination is not yet valid */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
&rmc->refresh_session.coin.denom_pub_hash,
- TEH_KS_DKU_DEPOSIT,
- &ec,
- &hc);
- /* Consider case that denomination was revoked but
- this coin was already seen and thus refresh is OK. */
- if (NULL == dki)
- {
- dki = TEH_KS_denomination_key_lookup_by_hash (
- key_state,
- &rmc->refresh_session.coin.denom_pub_hash,
- TEH_KS_DKU_RECOUP,
- &ec,
- &hc);
- if (NULL != dki)
- {
- struct GNUNET_HashCode denom_hash;
- enum GNUNET_DB_QueryStatus qs;
-
- /* Check that the coin is dirty (we have seen it before), as we will
- not just allow melting of a *fresh* coin where the denomination was
- revoked (those must be recouped) */
- qs = TEH_plugin->get_coin_denomination (
- TEH_plugin->cls,
- NULL,
- &rmc->refresh_session.coin.coin_pub,
- &denom_hash);
- if (0 > qs)
- {
- TEH_KS_release (key_state);
- /* There is no good reason for a serialization failure here: */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MELT_DB_FETCH_ERROR,
- "failed to find information about old coin");
- }
- /* sanity check */
- GNUNET_break (0 ==
- GNUNET_memcmp (&denom_hash,
- &rmc->refresh_session.coin.denom_pub_hash));
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- /* We never saw this coin before, so _this_ justification is not OK */
- dki = NULL;
- }
- else
- {
- /* Minor optimization: no need to run the
- #TEH_DB_know_coin_transaction below */
- coin_is_dirty = GNUNET_YES;
- }
- }
- }
-
- /* Consider the case that the denomination expired for deposits, but
- recoup of a refreshed coin refilled the balance of the 'zombie' coin
- and we should thus allow the refresh during the legal period. */
- if (NULL == dki)
- {
- dki = TEH_KS_denomination_key_lookup_by_hash (key_state,
- &rmc->refresh_session.coin.
- denom_pub_hash,
- TEH_KS_DKU_ZOMBIE,
- &ec,
- &hc);
- if (NULL != dki)
- rmc->zombie_required = GNUNET_YES; /* check later that zombie is satisfied */
- }
- if (NULL == dki)
- {
- TEH_KS_release (key_state);
- return TALER_MHD_reply_with_error (connection,
- hc,
- ec,
- "unknown denomination");
- }
-
- TALER_amount_ntoh (&rmc->coin_refresh_fee,
- &dki->issue.properties.fee_refresh);
- TALER_amount_ntoh (&rmc->coin_value,
- &dki->issue.properties.value);
- /* check client used sane currency */
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&rmc->refresh_session.amount_with_fee,
- &rmc->coin_value) )
- {
- GNUNET_break_op (0);
- TEH_KS_release (key_state);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_MELT_CURRENCY_MISMATCH,
- "value_with_fee");
-
- }
- /* check coin is actually properly signed */
- if (GNUNET_OK !=
- TALER_test_coin_valid (&rmc->refresh_session.coin,
- &dki->denom_pub))
- {
- GNUNET_break_op (0);
- TEH_KS_release (key_state);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_MELT_DENOMINATION_SIGNATURE_INVALID,
- "denom_sig");
- }
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "MELT");
}
- TEH_KS_release (key_state);
- /* run actual logic, now that the request was parsed */
- /* First, make sure coin is 'known' in database */
- if (GNUNET_NO == coin_is_dirty)
- {
- struct TEH_DB_KnowCoinContext kcc;
- int mhd_ret;
+ rmc->coin_refresh_fee = dk->meta.fees.refresh;
+ rmc->coin_value = dk->meta.value;
- kcc.coin = &rmc->refresh_session.coin;
- kcc.connection = connection;
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "know coin for melt",
- &mhd_ret,
- &TEH_DB_know_coin_transaction,
- &kcc))
- return mhd_ret;
- }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Melted coin's denomination is worth %s\n",
+ TALER_amount2s (&dk->meta.value));
/* sanity-check that "total melt amount > melt fee" */
if (0 <
@@ -593,60 +335,152 @@ check_for_denomination_key (struct MHD_Connection *connection,
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_MELT_FEES_EXCEED_CONTRIBUTION,
- "melt amount smaller than melting fee");
+ TALER_EC_EXCHANGE_MELT_FEES_EXCEED_CONTRIBUTION,
+ NULL);
+ }
+ switch (dk->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
+ break;
+ default:
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_test_coin_valid (&rmc->refresh_session.coin,
+ &dk->denom_pub))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+ NULL);
}
- return handle_melt (connection,
- rmc);
-}
+ /* verify signature of coin for melt operation */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_melt_verify (&rmc->refresh_session.amount_with_fee,
+ &rmc->coin_refresh_fee,
+ &rmc->refresh_session.rc,
+ &rmc->refresh_session.coin.denom_pub_hash,
+ &rmc->refresh_session.coin.h_age_commitment,
+ &rmc->refresh_session.coin.coin_pub,
+ &rmc->refresh_session.coin_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MELT_COIN_SIGNATURE_INVALID,
+ NULL);
+ }
-/**
- * Handle a "/coins/$COIN_PUB/melt" request. Parses the request into the JSON
- * components and then hands things of to #check_for_denomination_key() to
- * validate the melted coins, the signature and execute the melt using
- * handle_melt().
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
+ {
+ /* We are past deposit expiration time, but maybe this is a zombie? */
+ struct TALER_DenominationHashP denom_hash;
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Check that the coin is dirty (we have seen it before), as we will
+ not just allow melting of a *fresh* coin where the denomination was
+ revoked (those must be recouped) */
+ qs = TEH_plugin->get_coin_denomination (
+ TEH_plugin->cls,
+ &rmc->refresh_session.coin.coin_pub,
+ &rmc->known_coin_id,
+ &denom_hash);
+ if (0 > qs)
+ {
+ /* There is no good reason for a serialization failure here: */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "coin denomination");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ /* We never saw this coin before, so _this_ justification is not OK */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &rmc->refresh_session.coin.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "MELT");
+ }
+ /* Minor optimization: no need to run the
+ "ensure_coin_known" part of the transaction */
+ rmc->coin_is_dirty = true;
+ /* sanity check */
+ if (0 !=
+ GNUNET_memcmp (&denom_hash,
+ &rmc->refresh_session.coin.denom_pub_hash))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
+ TALER_B2S (&denom_hash));
+ }
+ rmc->zombie_required = true; /* check later that zombie is satisfied */
+ }
- * @param connection the MHD connection to handle
- * @param coin_pub public key of the coin
- * @param root uploaded JSON data
- * @return MHD result code
- */
-int
+ return database_melt (connection,
+ rmc);
+}
+
+
+MHD_RESULT
TEH_handler_melt (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const json_t *root)
{
struct MeltContext rmc;
- int res;
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_denomination_signature ("denom_sig",
- &rmc.refresh_session.coin.denom_sig),
+ TALER_JSON_spec_denom_sig ("denom_sig",
+ &rmc.refresh_session.coin.denom_sig),
GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
&rmc.refresh_session.coin.denom_pub_hash),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("age_commitment_hash",
+ &rmc.refresh_session.coin.h_age_commitment),
+ &rmc.refresh_session.coin.no_age_commitment),
GNUNET_JSON_spec_fixed_auto ("confirm_sig",
&rmc.refresh_session.coin_sig),
TALER_JSON_spec_amount ("value_with_fee",
+ TEH_currency,
&rmc.refresh_session.amount_with_fee),
GNUNET_JSON_spec_fixed_auto ("rc",
&rmc.refresh_session.rc),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("rms",
+ &rmc.rms),
+ &rmc.no_rms),
GNUNET_JSON_spec_end ()
};
- memset (&rmc,
- 0,
- sizeof (rmc));
+ memset (&rmc, 0, sizeof (rmc));
rmc.refresh_session.coin.coin_pub = *coin_pub;
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_OK != res)
- return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
-
- res = check_for_denomination_key (connection,
- &rmc);
- GNUNET_JSON_parse_free (spec);
- return res;
+
+ {
+ enum GNUNET_GenericReturnValue ret;
+ ret = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_OK != ret)
+ return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
+ }
+
+ {
+ MHD_RESULT res;
+
+ res = check_melt_valid (connection,
+ &rmc);
+ GNUNET_JSON_parse_free (spec);
+ return res;
+ }
}
diff --git a/src/exchange/taler-exchange-httpd_melt.h b/src/exchange/taler-exchange-httpd_melt.h
index ad25b4717..b15fd07c7 100644
--- a/src/exchange/taler-exchange-httpd_melt.h
+++ b/src/exchange/taler-exchange-httpd_melt.h
@@ -30,16 +30,16 @@
/**
* Handle a "/coins/$COIN_PUB/melt" request. Parses the request into the JSON
- * components and then hands things of to #check_for_denomination_key() to
+ * components and then hands things of to #check_melt_valid() to
* validate the melted coins, the signature and execute the melt using
- * handle_melt().
-
+ * melt_transaction().
+ *
* @param connection the MHD connection to handle
* @param coin_pub public key of the coin
* @param root uploaded JSON data
* @return MHD result code
*/
-int
+MHD_RESULT
TEH_handler_melt (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const json_t *root);
diff --git a/src/exchange/taler-exchange-httpd_metrics.c b/src/exchange/taler-exchange-httpd_metrics.c
new file mode 100644
index 000000000..1542801fe
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_metrics.c
@@ -0,0 +1,165 @@
+/*
+ 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_metrics.c
+ * @brief Handle /metrics requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_json_lib.h>
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_metrics.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include <jansson.h>
+
+
+unsigned long long TEH_METRICS_num_requests[TEH_MT_REQUEST_COUNT];
+
+unsigned long long TEH_METRICS_batch_withdraw_num_coins;
+
+unsigned long long TEH_METRICS_num_conflict[TEH_MT_REQUEST_COUNT];
+
+unsigned long long TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_COUNT];
+
+unsigned long long TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_COUNT];
+
+unsigned long long TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_COUNT];
+
+unsigned long long TEH_METRICS_num_success[TEH_MT_SUCCESS_COUNT];
+
+
+MHD_RESULT
+TEH_handler_metrics (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ char *reply;
+ struct MHD_Response *resp;
+ MHD_RESULT ret;
+
+ (void) args;
+ GNUNET_asprintf (&reply,
+ "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+ "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+ "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+ "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+ "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+ "# HELP taler_exchange_serialization_failures "
+ " number of database serialization errors by type\n"
+ "# TYPE taler_exchange_serialization_failures counter\n"
+ "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
+ "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
+ "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
+ "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
+ "# HELP taler_exchange_received_requests "
+ " number of received requests by type\n"
+ "# TYPE taler_exchange_received_requests counter\n"
+ "taler_exchange_received_requests{type=\"%s\"} %llu\n"
+ "taler_exchange_received_requests{type=\"%s\"} %llu\n"
+ "taler_exchange_received_requests{type=\"%s\"} %llu\n"
+ "taler_exchange_received_requests{type=\"%s\"} %llu\n"
+ "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
+#if NOT_YET_IMPLEMENTED
+ "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
+ "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
+#endif
+ "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
+ "# HELP taler_exchange_num_signatures "
+ " number of signatures created by cipher\n"
+ "# TYPE taler_exchange_num_signatures counter\n"
+ "taler_exchange_num_signatures{type=\"%s\"} %llu\n"
+ "taler_exchange_num_signatures{type=\"%s\"} %llu\n"
+ "taler_exchange_num_signatures{type=\"%s\"} %llu\n"
+ "# HELP taler_exchange_num_signature_verifications "
+ " number of signatures verified by cipher\n"
+ "# TYPE taler_exchange_num_signature_verifications counter\n"
+ "taler_exchange_num_signature_verifications{type=\"%s\"} %llu\n"
+ "taler_exchange_num_signature_verifications{type=\"%s\"} %llu\n"
+ "taler_exchange_num_signature_verifications{type=\"%s\"} %llu\n"
+ "# HELP taler_exchange_num_keyexchanges "
+ " number of key exchanges done by cipher\n"
+ "# TYPE taler_exchange_num_keyexchanges counter\n"
+ "taler_exchange_num_keyexchanges{type=\"%s\"} %llu\n"
+ "# HELP taler_exchange_batch_withdraw_num_coins "
+ " number of coins withdrawn in a batch-withdraw request\n"
+ "# TYPE taler_exchange_batch_withdraw_num_coins counter\n"
+ "taler_exchange_batch_withdraw_num_coins{} %llu\n",
+ "deposit",
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT],
+ "withdraw",
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW],
+ "batch-withdraw",
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_BATCH_WITHDRAW],
+ "melt",
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_MELT],
+ "refresh-reveal",
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_REFRESH_REVEAL],
+ "other",
+ TEH_METRICS_num_conflict[TEH_MT_REQUEST_OTHER],
+ "deposit",
+ TEH_METRICS_num_conflict[TEH_MT_REQUEST_DEPOSIT],
+ "withdraw",
+ TEH_METRICS_num_conflict[TEH_MT_REQUEST_WITHDRAW],
+ "melt",
+ TEH_METRICS_num_conflict[TEH_MT_REQUEST_MELT],
+ "other",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_OTHER],
+ "deposit",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_DEPOSIT],
+ "withdraw",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_WITHDRAW],
+ "melt",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_MELT],
+ "withdraw",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW],
+#if NOT_YET_IMPLEMENTED
+ "deposit",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT],
+ "melt",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_MELT],
+#endif
+ "batch-withdraw",
+ TEH_METRICS_num_requests[
+ TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW],
+ "rsa",
+ TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA],
+ "cs",
+ TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS],
+ "eddsa",
+ TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_EDDSA],
+ "rsa",
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA],
+ "cs",
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS],
+ "eddsa",
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA],
+ "ecdh",
+ TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_ECDH],
+ TEH_METRICS_batch_withdraw_num_coins);
+ resp = MHD_create_response_from_buffer (strlen (reply),
+ reply,
+ MHD_RESPMEM_MUST_FREE);
+ ret = MHD_queue_response (rc->connection,
+ MHD_HTTP_OK,
+ resp);
+ MHD_destroy_response (resp);
+ return ret;
+}
+
+
+/* end of taler-exchange-httpd_metrics.c */
diff --git a/src/exchange/taler-exchange-httpd_metrics.h b/src/exchange/taler-exchange-httpd_metrics.h
new file mode 100644
index 000000000..318113c1f
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_metrics.h
@@ -0,0 +1,136 @@
+/*
+ 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 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_metrics.h
+ * @brief Handle /metrics requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_METRICS_H
+#define TALER_EXCHANGE_HTTPD_METRICS_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Request types for which we collect metrics.
+ */
+enum TEH_MetricTypeRequest
+{
+ TEH_MT_REQUEST_OTHER = 0,
+ TEH_MT_REQUEST_DEPOSIT = 1,
+ TEH_MT_REQUEST_WITHDRAW = 2,
+ TEH_MT_REQUEST_AGE_WITHDRAW = 3,
+ TEH_MT_REQUEST_MELT = 4,
+ TEH_MT_REQUEST_PURSE_CREATE = 5,
+ TEH_MT_REQUEST_PURSE_MERGE = 6,
+ TEH_MT_REQUEST_RESERVE_PURSE = 7,
+ TEH_MT_REQUEST_PURSE_DEPOSIT = 8,
+ TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT = 9,
+ TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW = 10,
+ TEH_MT_REQUEST_IDEMPOTENT_AGE_WITHDRAW = 11,
+ TEH_MT_REQUEST_IDEMPOTENT_MELT = 12,
+ TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW = 13,
+ TEH_MT_REQUEST_BATCH_DEPOSIT = 14,
+ TEH_MT_REQUEST_POLICY_FULFILLMENT = 15,
+ TEH_MT_REQUEST_COUNT = 16 /* MUST BE LAST! */
+};
+
+/**
+ * Success types for which we collect metrics.
+ */
+enum TEH_MetricTypeSuccess
+{
+ TEH_MT_SUCCESS_DEPOSIT = 0,
+ TEH_MT_SUCCESS_WITHDRAW = 1,
+ TEH_MT_SUCCESS_AGE_WITHDRAW = 2,
+ TEH_MT_SUCCESS_BATCH_WITHDRAW = 3,
+ TEH_MT_SUCCESS_MELT = 4,
+ TEH_MT_SUCCESS_REFRESH_REVEAL = 5,
+ TEH_MT_SUCCESS_AGE_WITHDRAW_REVEAL = 6,
+ TEH_MT_SUCCESS_COUNT = 7 /* MUST BE LAST! */
+};
+
+/**
+ * Cipher types for which we collect signature metrics.
+ */
+enum TEH_MetricTypeSignature
+{
+ TEH_MT_SIGNATURE_RSA = 0,
+ TEH_MT_SIGNATURE_CS = 1,
+ TEH_MT_SIGNATURE_EDDSA = 2,
+ TEH_MT_SIGNATURE_COUNT = 3
+};
+
+/**
+ * Cipher types for which we collect key exchange metrics.
+ */
+enum TEH_MetricTypeKeyX
+{
+ TEH_MT_KEYX_ECDH = 0,
+ TEH_MT_KEYX_COUNT = 1
+};
+
+/**
+ * Number of requests handled of the respective type.
+ */
+extern unsigned long long TEH_METRICS_num_requests[TEH_MT_REQUEST_COUNT];
+
+/**
+ * Number of successful requests handled of the respective type.
+ */
+extern unsigned long long TEH_METRICS_num_success[TEH_MT_SUCCESS_COUNT];
+
+/**
+ * Number of coins withdrawn in a batch-withdraw request
+ */
+extern unsigned long long TEH_METRICS_batch_withdraw_num_coins;
+
+/**
+ * Number of serialization errors encountered when
+ * handling requests of the respective type.
+ */
+extern unsigned long long TEH_METRICS_num_conflict[TEH_MT_REQUEST_COUNT];
+
+/**
+ * Number of signatures created by the respective cipher.
+ */
+extern unsigned long long TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_COUNT];
+
+/**
+ * Number of signatures verified by the respective cipher.
+ */
+extern unsigned long long TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_COUNT];
+
+/**
+ * Number of key exchanges done with the respective cipher.
+ */
+extern unsigned long long TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_COUNT];
+
+/**
+ * Handle a "/metrics" request.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be empty for this function)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_metrics (struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_mhd.c b/src/exchange/taler-exchange-httpd_mhd.c
index 6d5df6ff3..8d07c3cfc 100644
--- a/src/exchange/taler-exchange-httpd_mhd.c
+++ b/src/exchange/taler-exchange-httpd_mhd.c
@@ -34,27 +34,18 @@
#include "taler-exchange-httpd_mhd.h"
-/**
- * Function to call to handle the request by sending
- * back static data from the @a rh.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param args array of additional options (must be empty for this function)
- * @return MHD result code
- */
-int
-TEH_handler_static_response (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+MHD_RESULT
+TEH_handler_static_response (struct TEH_RequestContext *rc,
const char *const args[])
{
+ const struct TEH_RequestHandler *rh = rc->rh;
size_t dlen;
(void) args;
dlen = (0 == rh->data_size)
? strlen ((const char *) rh->data)
: rh->data_size;
- return TALER_MHD_reply_static (connection,
+ return TALER_MHD_reply_static (rc->connection,
rh->response_code,
rh->mime_type,
rh->data,
@@ -62,24 +53,13 @@ TEH_handler_static_response (const struct TEH_RequestHandler *rh,
}
-/**
- * Function to call to handle the request by sending
- * back a redirect to the AGPL source code.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param args array of additional options (must be empty for this function)
- * @return MHD result code
- */
-int
-TEH_handler_agpl_redirect (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+MHD_RESULT
+TEH_handler_agpl_redirect (struct TEH_RequestContext *rc,
const char *const args[])
{
- (void) rh;
(void) args;
- return TALER_MHD_reply_agpl (connection,
- "http://www.git.taler.net/?p=exchange.git");
+ return TALER_MHD_reply_agpl (rc->connection,
+ "https://git.taler.net/?p=exchange.git");
}
diff --git a/src/exchange/taler-exchange-httpd_mhd.h b/src/exchange/taler-exchange-httpd_mhd.h
index 1605032a1..563975beb 100644
--- a/src/exchange/taler-exchange-httpd_mhd.h
+++ b/src/exchange/taler-exchange-httpd_mhd.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
+ Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -30,16 +30,14 @@
/**
* Function to call to handle the request by sending
- * back static data from the @a rh.
+ * back static data from the request handler.
*
- * @param rh context of the handler
- * @param connection the MHD connection to handle
+ * @param rc request context
* @param args array of additional options (must be empty for this function)
* @return MHD result code
*/
-int
-TEH_handler_static_response (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+MHD_RESULT
+TEH_handler_static_response (struct TEH_RequestContext *rc,
const char *const args[]);
@@ -47,14 +45,12 @@ TEH_handler_static_response (const struct TEH_RequestHandler *rh,
* Function to call to handle the request by sending
* back a redirect to the AGPL source code.
*
- * @param rh context of the handler
- * @param connection the MHD connection to handle
+ * @param rc request context
* @param args array of additional options (must be empty for this function)
* @return MHD result code
*/
-int
-TEH_handler_agpl_redirect (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+MHD_RESULT
+TEH_handler_agpl_redirect (struct TEH_RequestContext *rc,
const char *const args[]);
diff --git a/src/exchange/taler-exchange-httpd_purses_create.c b/src/exchange/taler-exchange-httpd_purses_create.c
new file mode 100644
index 000000000..2de9468fe
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_purses_create.c
@@ -0,0 +1,651 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_purses_create.c
+ * @brief Handle /purses/$PID/create requests; parses the POST and JSON and
+ * verifies the coin signature before handing things off
+ * to the database.
+ * @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 "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_common_deposit.h"
+#include "taler-exchange-httpd_purses_create.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+/**
+ * Closure for #create_transaction.
+ */
+struct PurseCreateContext
+{
+
+ /**
+ * Total actually deposited by all the coins.
+ */
+ struct TALER_Amount deposit_total;
+
+ /**
+ * Our current time.
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
+ * Merge key for the purse.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Encrypted contract of for the purse.
+ */
+ struct TALER_EncryptedContract econtract;
+
+ /**
+ * Signature of the client affiming this request.
+ */
+ struct TALER_PurseContractSignatureP purse_sig;
+
+ /**
+ * Fundamental details about the purse.
+ */
+ struct TEH_PurseDetails pd;
+
+ /**
+ * Array of coins being deposited.
+ */
+ struct TEH_PurseDepositedCoin *coins;
+
+ /**
+ * Length of the @e coins array.
+ */
+ unsigned int num_coins;
+
+ /**
+ * Minimum age for deposits into this purse.
+ */
+ uint32_t min_age;
+
+ /**
+ * Do we have an @e econtract?
+ */
+ bool no_econtract;
+
+};
+
+
+/**
+ * Execute database transaction for /purses/$PID/create. 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 PurseCreateContext`
+ * @param connection MHD request context
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+create_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct PurseCreateContext *pcc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_Amount purse_fee;
+ bool in_conflict = true;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &purse_fee));
+ /* 1) create purse */
+ qs = TEH_plugin->insert_purse_request (
+ TEH_plugin->cls,
+ &pcc->pd.purse_pub,
+ &pcc->merge_pub,
+ pcc->pd.purse_expiration,
+ &pcc->pd.h_contract_terms,
+ pcc->min_age,
+ TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
+ &purse_fee,
+ &pcc->pd.target_amount,
+ &pcc->purse_sig,
+ &in_conflict);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ TALER_LOG_WARNING (
+ "Failed to store create purse information in database\n");
+ *mhd_ret =
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "purse create");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (in_conflict)
+ {
+ struct TALER_PurseMergePublicKeyP merge_pub;
+ struct GNUNET_TIME_Timestamp purse_expiration;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_Amount target_amount;
+ struct TALER_Amount balance;
+ struct TALER_PurseContractSignatureP purse_sig;
+ uint32_t min_age;
+
+ TEH_plugin->rollback (TEH_plugin->cls);
+ qs = TEH_plugin->get_purse_request (TEH_plugin->cls,
+ &pcc->pd.purse_pub,
+ &merge_pub,
+ &purse_expiration,
+ &h_contract_terms,
+ &min_age,
+ &target_amount,
+ &balance,
+ &purse_sig);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ TALER_LOG_WARNING ("Failed to fetch purse information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse request");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA),
+ TALER_JSON_pack_amount ("amount",
+ &target_amount),
+ GNUNET_JSON_pack_uint64 ("min_age",
+ min_age),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ purse_expiration),
+ GNUNET_JSON_pack_data_auto ("purse_sig",
+ &purse_sig),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("merge_pub",
+ &merge_pub));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* 2) deposit all coins */
+ for (unsigned int i = 0; i<pcc->num_coins; i++)
+ {
+ struct TEH_PurseDepositedCoin *coin = &pcc->coins[i];
+ bool balance_ok = false;
+ bool conflict = true;
+ bool too_late = true;
+
+ qs = TEH_make_coin_known (&coin->cpi,
+ connection,
+ &coin->known_coin_id,
+ mhd_ret);
+ if (qs < 0)
+ return qs;
+ qs = TEH_plugin->do_purse_deposit (TEH_plugin->cls,
+ &pcc->pd.purse_pub,
+ &coin->cpi.coin_pub,
+ &coin->amount,
+ &coin->coin_sig,
+ &coin->amount_minus_fee,
+ &balance_ok,
+ &too_late,
+ &conflict);
+ if (qs <= 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0 != qs);
+ TALER_LOG_WARNING (
+ "Failed to store purse deposit information in database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "purse create deposit");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! balance_ok)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Coin %s has insufficient balance for purse deposit of amount %s\n",
+ TALER_B2S (&coin->cpi.coin_pub),
+ TALER_amount2s (&coin->amount));
+ *mhd_ret
+ = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &coin->cpi.denom_pub_hash,
+ &coin->cpi.coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (too_late)
+ {
+ *mhd_ret
+ = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "too late to deposit on purse creation");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (conflict)
+ {
+ struct TALER_Amount amount;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_AgeCommitmentHash phac;
+ char *partner_url = NULL;
+
+ TEH_plugin->rollback (TEH_plugin->cls);
+ qs = TEH_plugin->get_purse_deposit (TEH_plugin->cls,
+ &pcc->pd.purse_pub,
+ &coin->cpi.coin_pub,
+ &amount,
+ &h_denom_pub,
+ &phac,
+ &coin_sig,
+ &partner_url);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ TALER_LOG_WARNING (
+ "Failed to fetch purse deposit information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get purse deposit");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &coin_pub),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &coin_sig),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_pack_data_auto ("h_age_restrictions",
+ &phac),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("partner_url",
+ partner_url)),
+ TALER_JSON_pack_amount ("amount",
+ &amount));
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ /* 3) if present, persist contract */
+ if (! pcc->no_econtract)
+ {
+ in_conflict = true;
+ qs = TEH_plugin->insert_contract (TEH_plugin->cls,
+ &pcc->pd.purse_pub,
+ &pcc->econtract,
+ &in_conflict);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ TALER_LOG_WARNING ("Failed to store purse information in database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "purse create contract");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (in_conflict)
+ {
+ struct TALER_EncryptedContract econtract;
+ struct GNUNET_HashCode h_econtract;
+
+ qs = TEH_plugin->select_contract_by_purse (
+ TEH_plugin->cls,
+ &pcc->pd.purse_pub,
+ &econtract);
+ if (qs <= 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0 != qs);
+ TALER_LOG_WARNING (
+ "Failed to store fetch contract information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select contract");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_CRYPTO_hash (econtract.econtract,
+ econtract.econtract_size,
+ &h_econtract);
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA),
+ GNUNET_JSON_pack_data_auto ("h_econtract",
+ &h_econtract),
+ GNUNET_JSON_pack_data_auto ("econtract_sig",
+ &econtract.econtract_sig),
+ GNUNET_JSON_pack_data_auto ("contract_pub",
+ &econtract.contract_pub));
+ GNUNET_free (econtract.econtract);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ return qs;
+}
+
+
+/**
+ * Parse a coin and check signature of the coin and the denomination
+ * signature over the coin.
+ *
+ * @param[in,out] connection our HTTP connection
+ * @param[in,out] pcc request context
+ * @param[out] coin coin to initialize
+ * @param jcoin coin to parse
+ * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
+ * #GNUNET_SYSERR on failure and no error could be returned
+ */
+static enum GNUNET_GenericReturnValue
+parse_coin (struct MHD_Connection *connection,
+ struct PurseCreateContext *pcc,
+ struct TEH_PurseDepositedCoin *coin,
+ const json_t *jcoin)
+{
+ enum GNUNET_GenericReturnValue iret;
+
+ if (GNUNET_OK !=
+ (iret = TEH_common_purse_deposit_parse_coin (connection,
+ coin,
+ jcoin)))
+ return iret;
+ if (GNUNET_OK !=
+ (iret = TEH_common_deposit_check_purse_deposit (
+ connection,
+ coin,
+ &pcc->pd.purse_pub,
+ pcc->min_age)))
+ return iret;
+ if (0 >
+ TALER_amount_add (&pcc->deposit_total,
+ &pcc->deposit_total,
+ &coin->amount_minus_fee))
+ {
+ GNUNET_break (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
+ "total deposit contribution"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+MHD_RESULT
+TEH_handler_purses_create (
+ struct MHD_Connection *connection,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *root)
+{
+ struct PurseCreateContext pcc = {
+ .pd.purse_pub = *purse_pub,
+ .exchange_timestamp = GNUNET_TIME_timestamp_get ()
+ };
+ const json_t *deposits;
+ json_t *deposit;
+ unsigned int idx;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("amount",
+ TEH_currency,
+ &pcc.pd.target_amount),
+ GNUNET_JSON_spec_uint32 ("min_age",
+ &pcc.min_age),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_econtract ("econtract",
+ &pcc.econtract),
+ &pcc.no_econtract),
+ GNUNET_JSON_spec_fixed_auto ("merge_pub",
+ &pcc.merge_pub),
+ GNUNET_JSON_spec_fixed_auto ("purse_sig",
+ &pcc.purse_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &pcc.pd.h_contract_terms),
+ GNUNET_JSON_spec_array_const ("deposits",
+ &deposits),
+ GNUNET_JSON_spec_timestamp ("purse_expiration",
+ &pcc.pd.purse_expiration),
+ GNUNET_JSON_spec_end ()
+ };
+ const struct TEH_GlobalFee *gf;
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &pcc.deposit_total));
+ if (GNUNET_TIME_timestamp_cmp (pcc.pd.purse_expiration,
+ <,
+ pcc.exchange_timestamp))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_PURSE_CREATE_EXPIRATION_BEFORE_NOW,
+ NULL);
+ }
+ if (GNUNET_TIME_absolute_is_never (pcc.pd.purse_expiration.abs_time))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_PURSE_CREATE_EXPIRATION_IS_NEVER,
+ NULL);
+ }
+ pcc.num_coins = json_array_size (deposits);
+ if ( (0 == pcc.num_coins) ||
+ (pcc.num_coins > TALER_MAX_FRESH_COINS) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "deposits");
+ }
+ {
+ struct TEH_KeyStateHandle *keys;
+
+ keys = TEH_keys_get_state ();
+ if (NULL == keys)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ }
+ gf = TEH_keys_global_fee_by_time (keys,
+ pcc.exchange_timestamp);
+ }
+ if (NULL == gf)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Cannot create purse: global fees not configured!\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING,
+ NULL);
+ }
+ /* parse deposits */
+ pcc.coins = GNUNET_new_array (pcc.num_coins,
+ struct TEH_PurseDepositedCoin);
+ json_array_foreach (deposits, idx, deposit)
+ {
+ enum GNUNET_GenericReturnValue res;
+ struct TEH_PurseDepositedCoin *coin = &pcc.coins[idx];
+
+ res = parse_coin (connection,
+ &pcc,
+ coin,
+ deposit);
+ if (GNUNET_OK != res)
+ {
+ for (unsigned int i = 0; i<idx; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+ }
+
+ if (0 < TALER_amount_cmp (&gf->fees.purse,
+ &pcc.deposit_total))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (pcc.coins);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_CREATE_PURSE_NEGATIVE_VALUE_AFTER_FEE,
+ NULL);
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+
+ if (GNUNET_OK !=
+ TALER_wallet_purse_create_verify (
+ pcc.pd.purse_expiration,
+ &pcc.pd.h_contract_terms,
+ &pcc.merge_pub,
+ pcc.min_age,
+ &pcc.pd.target_amount,
+ &pcc.pd.purse_pub,
+ &pcc.purse_sig))
+ {
+ TALER_LOG_WARNING ("Invalid signature on /purses/$PID/create request\n");
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID,
+ NULL);
+ }
+ if ( (! pcc.no_econtract) &&
+ (GNUNET_OK !=
+ TALER_wallet_econtract_upload_verify (pcc.econtract.econtract,
+ pcc.econtract.econtract_size,
+ &pcc.econtract.contract_pub,
+ purse_pub,
+ &pcc.econtract.econtract_sig)) )
+ {
+ TALER_LOG_WARNING ("Invalid signature on /purses/$PID/create request\n");
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID,
+ NULL);
+ }
+
+
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure");
+ }
+
+ /* execute transaction */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "execute purse create",
+ TEH_MT_REQUEST_PURSE_CREATE,
+ &mhd_ret,
+ &create_transaction,
+ &pcc))
+ {
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return mhd_ret;
+ }
+ }
+
+ /* generate regular response */
+ {
+ MHD_RESULT res;
+
+ res = TEH_RESPONSE_reply_purse_created (connection,
+ pcc.exchange_timestamp,
+ &pcc.deposit_total,
+ &pcc.pd);
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return res;
+ }
+}
+
+
+/* end of taler-exchange-httpd_purses_create.c */
diff --git a/src/exchange/taler-exchange-httpd_wire.h b/src/exchange/taler-exchange-httpd_purses_create.h
index 219f1711b..7ccba1446 100644
--- a/src/exchange/taler-exchange-httpd_wire.h
+++ b/src/exchange/taler-exchange-httpd_purses_create.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014--2020 Taler Systems SA
+ Copyright (C) 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -14,12 +14,12 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file taler-exchange-httpd_wire.h
- * @brief Handle /wire requests
+ * @file taler-exchange-httpd_purses_create.h
+ * @brief Handle /purses/$PID/create requests
* @author Christian Grothoff
*/
-#ifndef TALER_EXCHANGE_HTTPD_WIRE_H
-#define TALER_EXCHANGE_HTTPD_WIRE_H
+#ifndef TALER_EXCHANGE_HTTPD_PURSES_CREATE_H
+#define TALER_EXCHANGE_HTTPD_PURSES_CREATE_H
#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
@@ -27,35 +27,21 @@
/**
- * Initialize wire subsystem.
+ * Handle a "/purses/$PURSE_PUB/create" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #create_transaction() to further check
+ * the details of the operation specified. If everything checks out, this
+ * will ultimately lead to the "purses create" being executed, or rejected.
*
- * @param cfg configuration to use
- * @return #GNUNET_OK on success, #GNUNET_SYSERR if we found no valid
- * wire methods
- */
-int
-TEH_WIRE_init (const struct GNUNET_CONFIGURATION_Handle *cfg);
-
-
-/**
- * Clean up wire subsystem.
- */
-void
-TEH_WIRE_done (void);
-
-
-/**
- * Handle a "/wire" request.
- *
- * @param rh context of the handler
* @param connection the MHD connection to handle
- * @param args array of additional options (must be empty for this function)
+ * @param purse_pub public key of the purse
+ * @param root uploaded JSON data
* @return MHD result code
*/
-int
-TEH_handler_wire (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
- const char *const args[]);
+MHD_RESULT
+TEH_handler_purses_create (
+ struct MHD_Connection *connection,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *root);
#endif
diff --git a/src/exchange/taler-exchange-httpd_purses_delete.c b/src/exchange/taler-exchange-httpd_purses_delete.c
new file mode 100644
index 000000000..5bf7c24c9
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_purses_delete.c
@@ -0,0 +1,141 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_purses_delete.c
+ * @brief Handle DELETE /purses/$PID requests; parses the request and
+ * verifies the signature before handing deletion to the database.
+ * @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 "taler_dbevents.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_common_deposit.h"
+#include "taler-exchange-httpd_purses_delete.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+MHD_RESULT
+TEH_handler_purses_delete (
+ struct TEH_RequestContext *rc,
+ const char *const args[1])
+{
+ struct MHD_Connection *connection = rc->connection;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PurseContractSignatureP purse_sig;
+ bool found;
+ bool decided;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &purse_pub,
+ sizeof (purse_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED,
+ args[0]);
+ }
+ TALER_MHD_parse_request_header_auto_t (connection,
+ "Taler-Purse-Signature",
+ &purse_sig);
+ if (GNUNET_OK !=
+ TALER_wallet_purse_delete_verify (&purse_pub,
+ &purse_sig))
+ {
+ TALER_LOG_WARNING ("Invalid signature on /purses/$PID/delete request\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_DELETE_SIGNATURE_INVALID,
+ NULL);
+ }
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure");
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->do_purse_delete (TEH_plugin->cls,
+ &purse_pub,
+ &purse_sig,
+ &decided,
+ &found);
+ if (qs <= 0)
+ {
+ TALER_LOG_WARNING (
+ "Failed to store delete purse information in database\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "purse delete");
+ }
+ }
+ if (! found)
+ {
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
+ NULL);
+ }
+ if (decided)
+ {
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_PURSE_DELETE_ALREADY_DECIDED,
+ NULL);
+ }
+ {
+ /* Possible minor optimization: integrate notification with
+ transaction above... */
+ struct TALER_PurseEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
+ .purse_pub = purse_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Notifying about purse deletion %s\n",
+ TALER_B2S (&purse_pub));
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &rep.header,
+ NULL,
+ 0);
+ }
+ /* success */
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_purses_delete.c */
diff --git a/src/exchange/taler-exchange-httpd_purses_delete.h b/src/exchange/taler-exchange-httpd_purses_delete.h
new file mode 100644
index 000000000..912dd43a8
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_purses_delete.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_purses_delete.h
+ * @brief Handle DELETE /purses/$PID requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_PURSES_DELETE_H
+#define TALER_EXCHANGE_HTTPD_PURSES_DELETE_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a DELETE "/purses/$PURSE_PUB" request.
+ *
+ * @param rc request details about the request to handle
+ * @param args argument with the public key of the purse
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_purses_delete (
+ struct TEH_RequestContext *rc,
+ const char *const args[1]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_purses_deposit.c b/src/exchange/taler-exchange-httpd_purses_deposit.c
new file mode 100644
index 000000000..8e4d5e41a
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_purses_deposit.c
@@ -0,0 +1,507 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_purses_deposit.c
+ * @brief Handle /purses/$PID/deposit requests; parses the POST and JSON and
+ * verifies the coin signature before handing things off
+ * to the database.
+ * @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 "taler_dbevents.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_common_deposit.h"
+#include "taler-exchange-httpd_purses_deposit.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+/**
+ * Closure for #deposit_transaction.
+ */
+struct PurseDepositContext
+{
+ /**
+ * Public key of the purse we are creating.
+ */
+ const struct TALER_PurseContractPublicKeyP *purse_pub;
+
+ /**
+ * Total amount to be put into the purse.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Total actually deposited by all the coins.
+ */
+ struct TALER_Amount deposit_total;
+
+ /**
+ * When should the purse expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * Hash of the contract (needed for signing).
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Our current time.
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
+ * Array of coins being deposited.
+ */
+ struct TEH_PurseDepositedCoin *coins;
+
+ /**
+ * Length of the @e coins array.
+ */
+ unsigned int num_coins;
+
+ /**
+ * Minimum age for deposits into this purse.
+ */
+ uint32_t min_age;
+};
+
+
+/**
+ * Send confirmation of purse creation success to client.
+ *
+ * @param connection connection to the client
+ * @param pcc details about the request that succeeded
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_deposit_success (struct MHD_Connection *connection,
+ const struct PurseDepositContext *pcc)
+{
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ enum TALER_ErrorCode ec;
+
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_purse_created_sign (
+ &TEH_keys_exchange_sign_,
+ pcc->exchange_timestamp,
+ pcc->purse_expiration,
+ &pcc->amount,
+ &pcc->deposit_total,
+ pcc->purse_pub,
+ &pcc->h_contract_terms,
+ &pub,
+ &sig)))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("total_deposited",
+ &pcc->deposit_total),
+ TALER_JSON_pack_amount ("purse_value_after_fees",
+ &pcc->amount),
+ GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+ pcc->exchange_timestamp),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ pcc->purse_expiration),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &pcc->h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub));
+}
+
+
+/**
+ * Execute database transaction for /purses/$PID/deposit. 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 PurseDepositContext`
+ * @param connection MHD request context
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+deposit_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct PurseDepositContext *pcc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ for (unsigned int i = 0; i<pcc->num_coins; i++)
+ {
+ struct TEH_PurseDepositedCoin *coin = &pcc->coins[i];
+ bool balance_ok = false;
+ bool conflict = true;
+ bool too_late = true;
+
+ qs = TEH_make_coin_known (&coin->cpi,
+ connection,
+ &coin->known_coin_id,
+ mhd_ret);
+ if (qs < 0)
+ return qs;
+ qs = TEH_plugin->do_purse_deposit (TEH_plugin->cls,
+ pcc->purse_pub,
+ &coin->cpi.coin_pub,
+ &coin->amount,
+ &coin->coin_sig,
+ &coin->amount_minus_fee,
+ &balance_ok,
+ &too_late,
+ &conflict);
+ if (qs <= 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0 != qs);
+ TALER_LOG_WARNING (
+ "Failed to store purse deposit information in database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "do purse deposit");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! balance_ok)
+ {
+ *mhd_ret
+ = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &coin->cpi.denom_pub_hash,
+ &coin->cpi.coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (too_late)
+ {
+ TEH_plugin->rollback (TEH_plugin->cls);
+ *mhd_ret
+ = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_PURSE_DEPOSIT_DECIDED_ALREADY,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (conflict)
+ {
+ struct TALER_Amount amount;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_AgeCommitmentHash phac;
+ char *partner_url = NULL;
+
+ TEH_plugin->rollback (TEH_plugin->cls);
+ qs = TEH_plugin->get_purse_deposit (TEH_plugin->cls,
+ pcc->purse_pub,
+ &coin->cpi.coin_pub,
+ &amount,
+ &h_denom_pub,
+ &phac,
+ &coin_sig,
+ &partner_url);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ TALER_LOG_WARNING (
+ "Failed to fetch purse deposit information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get purse deposit");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &coin_pub),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ &phac),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &coin_sig),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("partner_url",
+ partner_url)),
+ TALER_JSON_pack_amount ("amount",
+ &amount));
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ return qs;
+}
+
+
+/**
+ * Parse a coin and check signature of the coin and the denomination
+ * signature over the coin.
+ *
+ * @param[in,out] connection our HTTP connection
+ * @param[in,out] pcc request context
+ * @param[out] coin coin to initialize
+ * @param jcoin coin to parse
+ * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
+ * #GNUNET_SYSERR on failure and no error could be returned
+ */
+static enum GNUNET_GenericReturnValue
+parse_coin (struct MHD_Connection *connection,
+ struct PurseDepositContext *pcc,
+ struct TEH_PurseDepositedCoin *coin,
+ const json_t *jcoin)
+{
+ enum GNUNET_GenericReturnValue iret;
+
+ if (GNUNET_OK !=
+ (iret = TEH_common_purse_deposit_parse_coin (connection,
+ coin,
+ jcoin)))
+ return iret;
+ if (GNUNET_OK !=
+ (iret = TEH_common_deposit_check_purse_deposit (
+ connection,
+ coin,
+ pcc->purse_pub,
+ pcc->min_age)))
+ return iret;
+ if (0 >
+ TALER_amount_add (&pcc->deposit_total,
+ &pcc->deposit_total,
+ &coin->amount_minus_fee))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
+ "total deposit contribution");
+ }
+ return GNUNET_OK;
+}
+
+
+MHD_RESULT
+TEH_handler_purses_deposit (
+ struct MHD_Connection *connection,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *root)
+{
+ struct PurseDepositContext pcc = {
+ .purse_pub = purse_pub,
+ .exchange_timestamp = GNUNET_TIME_timestamp_get ()
+ };
+ const json_t *deposits;
+ json_t *deposit;
+ unsigned int idx;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("deposits",
+ &deposits),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &pcc.deposit_total));
+ pcc.num_coins = json_array_size (deposits);
+ if ( (0 == pcc.num_coins) ||
+ (pcc.num_coins > TALER_MAX_FRESH_COINS) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "deposits");
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp create_timestamp;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ bool was_deleted;
+ bool was_refunded;
+
+ qs = TEH_plugin->select_purse (
+ TEH_plugin->cls,
+ pcc.purse_pub,
+ &create_timestamp,
+ &pcc.purse_expiration,
+ &pcc.amount,
+ &pcc.deposit_total,
+ &pcc.h_contract_terms,
+ &merge_timestamp,
+ &was_deleted,
+ &was_refunded);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* handled below */
+ }
+ if (was_refunded ||
+ was_deleted)
+ {
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_GONE,
+ was_deleted
+ ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED
+ : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
+ GNUNET_TIME_timestamp2s (pcc.purse_expiration));
+ }
+ }
+
+ /* parse deposits */
+ pcc.coins = GNUNET_new_array (pcc.num_coins,
+ struct TEH_PurseDepositedCoin);
+ json_array_foreach (deposits, idx, deposit)
+ {
+ enum GNUNET_GenericReturnValue res;
+ struct TEH_PurseDepositedCoin *coin = &pcc.coins[idx];
+
+ res = parse_coin (connection,
+ &pcc,
+ coin,
+ deposit);
+ if (GNUNET_OK != res)
+ {
+ for (unsigned int i = 0; i<idx; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+ }
+
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure");
+ }
+
+ /* execute transaction */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "execute purse deposit",
+ TEH_MT_REQUEST_PURSE_DEPOSIT,
+ &mhd_ret,
+ &deposit_transaction,
+ &pcc))
+ {
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return mhd_ret;
+ }
+ }
+ {
+ struct TALER_PurseEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
+ .purse_pub = *pcc.purse_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Notifying about purse deposit %s\n",
+ TALER_B2S (pcc.purse_pub));
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &rep.header,
+ NULL,
+ 0);
+ }
+
+ /* generate regular response */
+ {
+ MHD_RESULT res;
+
+ res = reply_deposit_success (connection,
+ &pcc);
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return res;
+ }
+}
+
+
+/* end of taler-exchange-httpd_purses_deposit.c */
diff --git a/src/exchange/taler-exchange-httpd_purses_deposit.h b/src/exchange/taler-exchange-httpd_purses_deposit.h
new file mode 100644
index 000000000..fa587e977
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_purses_deposit.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_purses_deposit.h
+ * @brief Handle /purses/$PID/deposit requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_PURSES_DEPOSIT_H
+#define TALER_EXCHANGE_HTTPD_PURSES_DEPOSIT_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/purses/$PURSE_PUB/deposit" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #deposit_transaction() to further check
+ * the details of the operation specified. If everything checks out, this
+ * will ultimately lead to the "purses deposit" being executed, or rejected.
+ *
+ * @param connection the MHD connection to handle
+ * @param purse_pub public key of the purse
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_purses_deposit (
+ struct MHD_Connection *connection,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *root);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_purses_get.c b/src/exchange/taler-exchange-httpd_purses_get.c
new file mode 100644
index 000000000..22328fe09
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_purses_get.c
@@ -0,0 +1,433 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_purses_get.c
+ * @brief Handle GET /purses/$PID/$TARGET requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler_mhd_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_purses_get.h"
+#include "taler-exchange-httpd_mhd.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Information about an ongoing /purses GET operation.
+ */
+struct GetContext
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct GetContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct GetContext *prev;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Subscription for the database event we are
+ * waiting for.
+ */
+ struct GNUNET_DB_EventHandler *eh;
+
+ /**
+ * Subscription for refund event we are
+ * waiting for.
+ */
+ struct GNUNET_DB_EventHandler *ehr;
+
+ /**
+ * Public key of our purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * When does this purse expire?
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * When was this purse merged?
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+
+ /**
+ * How much is the purse (supposed) to be worth?
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * How much was deposited into the purse so far?
+ */
+ struct TALER_Amount deposited;
+
+ /**
+ * Hash over the contract of the purse.
+ */
+ struct TALER_PrivateContractHashP h_contract;
+
+ /**
+ * When will this request time out?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * true to wait for merge, false to wait for deposit.
+ */
+ bool wait_for_merge;
+
+ /**
+ * True if we are still suspended.
+ */
+ bool suspended;
+};
+
+
+/**
+ * Head of DLL of suspended GET requests.
+ */
+static struct GetContext *gc_head;
+
+/**
+ * Tail of DLL of suspended GET requests.
+ */
+static struct GetContext *gc_tail;
+
+
+void
+TEH_purses_get_cleanup ()
+{
+ struct GetContext *gc;
+
+ while (NULL != (gc = gc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (gc_head,
+ gc_tail,
+ gc);
+ if (gc->suspended)
+ {
+ gc->suspended = false;
+ MHD_resume_connection (gc->connection);
+ }
+ }
+}
+
+
+/**
+ * Function called once a connection is done to
+ * clean up the `struct GetContext` state.
+ *
+ * @param rc context to clean up for
+ */
+static void
+gc_cleanup (struct TEH_RequestContext *rc)
+{
+ struct GetContext *gc = rc->rh_ctx;
+
+ GNUNET_assert (! gc->suspended);
+ if (NULL != gc->eh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Cancelling DB event listening\n");
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ gc->eh);
+ gc->eh = NULL;
+ }
+ if (NULL != gc->ehr)
+ {
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ gc->ehr);
+ gc->ehr = NULL;
+ }
+ GNUNET_free (gc);
+}
+
+
+/**
+ * Function called on events received from Postgres.
+ * Wakes up long pollers.
+ *
+ * @param cls the `struct TEH_RequestContext *`
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ struct TEH_RequestContext *rc = cls;
+ struct GetContext *gc = rc->rh_ctx;
+ struct GNUNET_AsyncScopeSave old_scope;
+
+ (void) extra;
+ (void) extra_size;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Waking up on %p - %p - %s\n",
+ rc,
+ gc,
+ gc->suspended ? "suspended" : "active");
+ if (NULL == gc)
+ return; /* event triggered while main transaction
+ was still running */
+ if (! gc->suspended)
+ return; /* might get multiple wake-up events */
+ gc->suspended = false;
+ GNUNET_async_scope_enter (&rc->async_scope_id,
+ &old_scope);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming from long-polling on purse\n");
+ TEH_check_invariants ();
+ GNUNET_CONTAINER_DLL_remove (gc_head,
+ gc_tail,
+ gc);
+ MHD_resume_connection (gc->connection);
+ TALER_MHD_daemon_trigger ();
+ TEH_check_invariants ();
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
+MHD_RESULT
+TEH_handler_purses_get (struct TEH_RequestContext *rc,
+ const char *const args[2])
+{
+ struct GetContext *gc = rc->rh_ctx;
+ bool purse_deleted;
+ bool purse_refunded;
+ MHD_RESULT res;
+
+ if (NULL == gc)
+ {
+ gc = GNUNET_new (struct GetContext);
+ rc->rh_ctx = gc;
+ rc->rh_cleaner = &gc_cleanup;
+ gc->connection = rc->connection;
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &gc->purse_pub,
+ sizeof (gc->purse_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED,
+ args[0]);
+ }
+ if (0 == strcmp (args[1],
+ "merge"))
+ gc->wait_for_merge = true;
+ else if (0 == strcmp (args[1],
+ "deposit"))
+ gc->wait_for_merge = false;
+ else
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_PURSES_INVALID_WAIT_TARGET,
+ args[1]);
+ }
+
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &gc->timeout);
+ if ( (GNUNET_TIME_absolute_is_future (gc->timeout)) &&
+ (NULL == gc->eh) )
+ {
+ struct TALER_PurseEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (
+ gc->wait_for_merge
+ ? TALER_DBEVENT_EXCHANGE_PURSE_MERGED
+ : TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
+ .purse_pub = gc->purse_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting DB event listening on purse %s\n",
+ TALER_B2S (&gc->purse_pub));
+ gc->eh = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (gc->timeout),
+ &rep.header,
+ &db_event_cb,
+ rc);
+ if (NULL == gc->eh)
+ {
+ GNUNET_break (0);
+ gc->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
+ }
+ else
+ {
+ struct GNUNET_DB_EventHeaderP repr = {
+ .size = htons (sizeof (repr)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_PURSE_REFUNDED),
+ };
+
+ gc->ehr = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (gc->timeout),
+ &repr,
+ &db_event_cb,
+ rc);
+ }
+ }
+ } /* end first-time initialization */
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp create_timestamp;
+
+ qs = TEH_plugin->select_purse (TEH_plugin->cls,
+ &gc->purse_pub,
+ &create_timestamp,
+ &gc->purse_expiration,
+ &gc->amount,
+ &gc->deposited,
+ &gc->h_contract,
+ &gc->merge_timestamp,
+ &purse_deleted,
+ &purse_refunded);
+ switch (qs)
+ {
+ 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,
+ "select_purse");
+ 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,
+ "select_purse");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* handled below */
+ }
+ }
+ if (purse_refunded ||
+ purse_deleted)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purse refunded or deleted\n");
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_GONE,
+ purse_deleted
+ ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED
+ : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
+ GNUNET_TIME_timestamp2s (
+ gc->purse_expiration));
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Deposited amount is %s\n",
+ TALER_amount2s (&gc->deposited));
+ if (GNUNET_TIME_absolute_is_future (gc->timeout) &&
+ ( ((gc->wait_for_merge) &&
+ GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time)) ||
+ ((! gc->wait_for_merge) &&
+ (0 <
+ TALER_amount_cmp (&gc->amount,
+ &gc->deposited))) ) )
+ {
+ gc->suspended = true;
+ GNUNET_CONTAINER_DLL_insert (gc_head,
+ gc_tail,
+ gc);
+ MHD_suspend_connection (gc->connection);
+ return MHD_YES;
+ }
+
+ {
+ struct GNUNET_TIME_Timestamp dt = GNUNET_TIME_timestamp_get ();
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ enum TALER_ErrorCode ec;
+
+ if (GNUNET_TIME_timestamp_cmp (dt,
+ >,
+ gc->purse_expiration))
+ dt = gc->purse_expiration;
+ if (0 <
+ TALER_amount_cmp (&gc->amount,
+ &gc->deposited))
+ {
+ /* amount > deposited: not yet fully paid */
+ dt = GNUNET_TIME_UNIT_ZERO_TS;
+ }
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_purse_status_sign (
+ &TEH_keys_exchange_sign_,
+ gc->merge_timestamp,
+ dt,
+ &gc->deposited,
+ &exchange_pub,
+ &exchange_sig)))
+ {
+ res = TALER_MHD_reply_with_ec (rc->connection,
+ ec,
+ NULL);
+ }
+ else
+ {
+ /* Make sure merge_timestamp is omitted if not yet merged */
+ if (GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time))
+ gc->merge_timestamp = GNUNET_TIME_UNIT_ZERO_TS;
+ res = TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("balance",
+ &gc->deposited),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &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)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("deposit_timestamp",
+ dt))
+ );
+ }
+ }
+ return res;
+}
+
+
+/* end of taler-exchange-httpd_purses_get.c */
diff --git a/src/exchange/taler-exchange-httpd_purses_get.h b/src/exchange/taler-exchange-httpd_purses_get.h
new file mode 100644
index 000000000..648b01c9a
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_purses_get.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_purses_get.h
+ * @brief Handle /purses/$PURSE_PUB/$TARGET GET requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_PURSES_GET_H
+#define TALER_EXCHANGE_HTTPD_PURSES_GET_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Shutdown purses-get subsystem. Resumes all
+ * suspended long-polling clients and cleans up
+ * data structures.
+ */
+void
+TEH_purses_get_cleanup (void);
+
+
+/**
+ * Handle a GET "/purses/$PID/$TARGET" request. Parses the
+ * given "purse_pub" in @a args (which should contain the
+ * EdDSA public key of a purse) and then respond with the
+ * status of the purse.
+ *
+ * @param rc request context
+ * @param args array of additional options (length: 2, the purse_pub and a target)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_purses_get (struct TEH_RequestContext *rc,
+ const char *const args[2]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_purses_merge.c b/src/exchange/taler-exchange-httpd_purses_merge.c
new file mode 100644
index 000000000..fb5ce4d90
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_purses_merge.c
@@ -0,0 +1,696 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_purses_merge.c
+ * @brief Handle /purses/$PID/merge requests; parses the POST and JSON and
+ * verifies the reserve signature before handing things off
+ * to the database.
+ * @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_dbevents.h"
+#include "taler_json_lib.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_purses_merge.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+/**
+ * Closure for #merge_transaction.
+ */
+struct PurseMergeContext
+{
+ /**
+ * Public key of the purse we are creating.
+ */
+ const struct TALER_PurseContractPublicKeyP *purse_pub;
+
+ /**
+ * Total amount to be put into the purse.
+ */
+ struct TALER_Amount target_amount;
+
+ /**
+ * Current amount in the purse.
+ */
+ struct TALER_Amount balance;
+
+ /**
+ * When should the purse expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * When the client signed the merge.
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+
+ /**
+ * Our current time.
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
+ * Merge key for the purse.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Signature of the reservce affiming this request.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Signature of the client affiming the merge.
+ */
+ struct TALER_PurseMergeSignatureP merge_sig;
+
+ /**
+ * Public key of the reserve, as extracted from @e payto_uri.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Hash of the contract terms of the purse.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Fees that apply to this operation.
+ */
+ const struct TALER_WireFeeSet *wf;
+
+ /**
+ * URI of the account the purse is to be merged into.
+ * Must be of the form 'payto://taler-reserve/$EXCHANGE_URL/RESERVE_PUB'.
+ */
+ const char *payto_uri;
+
+ /**
+ * Hash of the @e payto_uri.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * KYC status of the operation.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Base URL of the exchange provider hosting the reserve.
+ */
+ char *provider_url;
+
+ /**
+ * Minimum age for deposits into this purse.
+ */
+ uint32_t min_age;
+};
+
+
+/**
+ * Send confirmation of purse creation success to client.
+ *
+ * @param connection connection to the client
+ * @param pcc details about the request that succeeded
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_merge_success (struct MHD_Connection *connection,
+ const struct PurseMergeContext *pcc)
+{
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ enum TALER_ErrorCode ec;
+ struct TALER_Amount merge_amount;
+
+ if (0 <
+ TALER_amount_cmp (&pcc->balance,
+ &pcc->target_amount))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_JSON_pack_amount ("balance",
+ &pcc->balance),
+ TALER_JSON_pack_amount ("target_amount",
+ &pcc->target_amount));
+ }
+ if ( (NULL == pcc->provider_url) ||
+ (0 == strcmp (pcc->provider_url,
+ TEH_base_url)) )
+ {
+ /* wad fee is always zero if we stay at our own exchange */
+ merge_amount = pcc->target_amount;
+ }
+ else
+ {
+#if WAD_NOT_IMPLEMENTED
+ /* FIXME: figure out partner, lookup wad fee by partner! #7271 */
+ if (0 >
+ TALER_amount_subtract (&merge_amount,
+ &pcc->target_amount,
+ &wad_fee))
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &merge_amount));
+ }
+#else
+ merge_amount = pcc->target_amount;
+#endif
+ }
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_purse_merged_sign (
+ &TEH_keys_exchange_sign_,
+ pcc->exchange_timestamp,
+ pcc->purse_expiration,
+ &merge_amount,
+ pcc->purse_pub,
+ &pcc->h_contract_terms,
+ &pcc->reserve_pub,
+ (NULL != pcc->provider_url)
+ ? pcc->provider_url
+ : TEH_base_url,
+ &pub,
+ &sig)))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("merge_amount",
+ &merge_amount),
+ GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+ pcc->exchange_timestamp),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub));
+}
+
+
+/**
+ * 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 a `struct PurseMergeContext`
+ * @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
+amount_iterator (void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ struct PurseMergeContext *pcc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ cb (cb_cls,
+ &pcc->target_amount,
+ GNUNET_TIME_absolute_get ());
+ qs = TEH_plugin->select_merge_amounts_for_kyc_check (
+ TEH_plugin->cls,
+ &pcc->h_payto,
+ limit,
+ cb,
+ cb_cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got %d additional transactions for this merge and limit %llu\n",
+ qs,
+ (unsigned long long) limit.abs_value_us);
+ GNUNET_break (qs >= 0);
+}
+
+
+/**
+ * Execute database transaction for /purses/$PID/merge. 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 PurseMergeContext`
+ * @param connection MHD request context
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+merge_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct PurseMergeContext *pcc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ bool in_conflict = true;
+ bool no_balance = true;
+ bool no_partner = true;
+ char *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,
+ &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;
+ qs = TEH_plugin->insert_kyc_requirement_for_account (
+ TEH_plugin->cls,
+ required,
+ &pcc->h_payto,
+ &pcc->reserve_pub,
+ &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 (
+ TEH_plugin->cls,
+ pcc->purse_pub,
+ &pcc->merge_sig,
+ pcc->merge_timestamp,
+ &pcc->reserve_sig,
+ pcc->provider_url,
+ &pcc->reserve_pub,
+ &no_partner,
+ &no_balance,
+ &in_conflict);
+ 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,
+ "purse merge");
+ return qs;
+ }
+ if (no_partner)
+ {
+ *mhd_ret =
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_MERGE_PURSE_PARTNER_UNKNOWN,
+ pcc->provider_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (no_balance)
+ {
+ *mhd_ret =
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_PAYMENT_REQUIRED,
+ TALER_EC_EXCHANGE_PURSE_NOT_FULL,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (in_conflict)
+ {
+ struct TALER_PurseMergeSignatureP merge_sig;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ char *partner_url = NULL;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ bool refunded;
+
+ qs = TEH_plugin->select_purse_merge (TEH_plugin->cls,
+ pcc->purse_pub,
+ &merge_sig,
+ &merge_timestamp,
+ &partner_url,
+ &reserve_pub,
+ &refunded);
+ if (qs <= 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ TALER_LOG_WARNING (
+ "Failed to fetch merge purse information from database\n");
+ *mhd_ret =
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse merge");
+ return qs;
+ }
+ if (refunded)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purse was already refunded\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_GONE,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
+ NULL);
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (0 !=
+ GNUNET_memcmp (&merge_sig,
+ &pcc->merge_sig))
+ {
+ *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ GNUNET_JSON_pack_timestamp ("merge_timestamp",
+ merge_timestamp),
+ GNUNET_JSON_pack_data_auto ("merge_sig",
+ &merge_sig),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("partner_url",
+ partner_url)),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &reserve_pub));
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* idempotent! */
+ *mhd_ret = reply_merge_success (connection,
+ pcc);
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_purses_merge (
+ struct MHD_Connection *connection,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *root)
+{
+ struct PurseMergeContext pcc = {
+ .purse_pub = purse_pub,
+ .exchange_timestamp = GNUNET_TIME_timestamp_get ()
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &pcc.payto_uri),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &pcc.reserve_sig),
+ GNUNET_JSON_spec_fixed_auto ("merge_sig",
+ &pcc.merge_sig),
+ GNUNET_JSON_spec_timestamp ("merge_timestamp",
+ &pcc.merge_timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TALER_PurseContractSignatureP purse_sig;
+ enum GNUNET_DB_QueryStatus qs;
+ bool http;
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+
+ /* Fetch purse details */
+ qs = TEH_plugin->get_purse_request (TEH_plugin->cls,
+ pcc.purse_pub,
+ &pcc.merge_pub,
+ &pcc.purse_expiration,
+ &pcc.h_contract_terms,
+ &pcc.min_age,
+ &pcc.target_amount,
+ &pcc.balance,
+ &purse_sig);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse request");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse request");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* continued below */
+ break;
+ }
+ /* parse 'payto_uri' into pcc.reserve_pub and provider_url */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received payto: `%s'\n",
+ pcc.payto_uri);
+ if ( (0 != strncmp (pcc.payto_uri,
+ "payto://taler-reserve/",
+ strlen ("payto://taler-reserve/"))) &&
+ (0 != strncmp (pcc.payto_uri,
+ "payto://taler-reserve-http/",
+ strlen ("payto://taler-reserve+http/"))) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "payto_uri");
+ }
+ http = (0 == strncmp (pcc.payto_uri,
+ "payto://taler-reserve-http/",
+ strlen ("payto://taler-reserve-http/")));
+
+ {
+ const char *host = &pcc.payto_uri[http
+ ? strlen ("payto://taler-reserve-http/")
+ : strlen ("payto://taler-reserve/")];
+ const char *slash = strchr (host,
+ '/');
+
+ if (NULL == slash)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "payto_uri");
+ }
+ GNUNET_asprintf (&pcc.provider_url,
+ "%s://%.*s/",
+ http ? "http" : "https",
+ (int) (slash - host),
+ host);
+ slash++;
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (slash,
+ strlen (slash),
+ &pcc.reserve_pub,
+ sizeof (pcc.reserve_pub)))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (pcc.provider_url);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "payto_uri");
+ }
+ slash++;
+ }
+ TALER_payto_hash (pcc.payto_uri,
+ &pcc.h_payto);
+ if (0 == strcmp (pcc.provider_url,
+ TEH_base_url))
+ {
+ /* we use NULL to represent 'self' as the provider */
+ GNUNET_free (pcc.provider_url);
+ }
+ else
+ {
+ char *method = GNUNET_strdup ("FIXME-WAD #7271");
+
+ /* FIXME-#7271: lookup wire method by pcc.provider_url! */
+ pcc.wf = TEH_wire_fees_by_time (pcc.exchange_timestamp,
+ method);
+ if (NULL == pcc.wf)
+ {
+ MHD_RESULT res;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Cannot merge purse: wire fees not configured!\n");
+ res = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_WIRE_FEES_MISSING,
+ method);
+ GNUNET_free (method);
+ return res;
+ }
+ GNUNET_free (method);
+ }
+ /* check signatures */
+ if (GNUNET_OK !=
+ TALER_wallet_purse_merge_verify (
+ pcc.payto_uri,
+ pcc.merge_timestamp,
+ pcc.purse_pub,
+ &pcc.merge_pub,
+ &pcc.merge_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (pcc.provider_url);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_MERGE_SIGNATURE,
+ NULL);
+ }
+ {
+ struct TALER_Amount zero_purse_fee;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pcc.target_amount.currency,
+ &zero_purse_fee));
+ if (GNUNET_OK !=
+ TALER_wallet_account_merge_verify (
+ pcc.merge_timestamp,
+ pcc.purse_pub,
+ pcc.purse_expiration,
+ &pcc.h_contract_terms,
+ &pcc.target_amount,
+ &zero_purse_fee,
+ pcc.min_age,
+ TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
+ &pcc.reserve_pub,
+ &pcc.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (pcc.provider_url);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_RESERVE_SIGNATURE,
+ NULL);
+ }
+ }
+
+ /* execute transaction */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "execute purse merge",
+ TEH_MT_REQUEST_PURSE_MERGE,
+ &mhd_ret,
+ &merge_transaction,
+ &pcc))
+ {
+ GNUNET_free (pcc.provider_url);
+ return mhd_ret;
+ }
+ }
+
+
+ GNUNET_free (pcc.provider_url);
+ if (! pcc.kyc.ok)
+ return TEH_RESPONSE_reply_kyc_required (connection,
+ &pcc.h_payto,
+ &pcc.kyc);
+
+ {
+ struct TALER_PurseEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_MERGED),
+ .purse_pub = *pcc.purse_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Notifying about purse merge\n");
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &rep.header,
+ NULL,
+ 0);
+ }
+
+ /* generate regular response */
+ return reply_merge_success (connection,
+ &pcc);
+}
+
+
+/* end of taler-exchange-httpd_purses_merge.c */
diff --git a/src/exchange/taler-exchange-httpd_purses_merge.h b/src/exchange/taler-exchange-httpd_purses_merge.h
new file mode 100644
index 000000000..3bc6e1696
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_purses_merge.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_purses_merge.h
+ * @brief Handle /purses/$PID/merge requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_PURSES_MERGE_H
+#define TALER_EXCHANGE_HTTPD_PURSES_MERGE_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/purses/$PURSE_PUB/merge" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #merge_transaction() to further check
+ * the details of the operation specified. If everything checks out, this
+ * will ultimately lead to the "purses merge" being executed, or rejected.
+ *
+ * @param connection the MHD connection to handle
+ * @param purse_pub public key of the purse
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_purses_merge (struct MHD_Connection *connection,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *root);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_recoup-refresh.c b/src/exchange/taler-exchange-httpd_recoup-refresh.c
new file mode 100644
index 000000000..a5d5b2ab4
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_recoup-refresh.c
@@ -0,0 +1,432 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2017-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_recoup-refresh.c
+ * @brief Handle /recoup-refresh requests; parses the POST and JSON and
+ * verifies the coin signature before handing things off
+ * to the database.
+ * @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-exchange-httpd_db.h"
+#include "taler-exchange-httpd_recoup-refresh.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler_exchangedb_lib.h"
+
+
+/**
+ * Closure for #recoup_refresh_transaction().
+ */
+struct RecoupContext
+{
+
+ /**
+ * Set by #recoup_transaction() to the old coin that will
+ * receive the recoup.
+ */
+ struct TALER_CoinSpendPublicKeyP old_coin_pub;
+
+ /**
+ * Details about the coin.
+ */
+ const struct TALER_CoinPublicInfo *coin;
+
+ /**
+ * Key used to blind the coin.
+ */
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks;
+
+ /**
+ * Signature of the coin requesting recoup.
+ */
+ const struct TALER_CoinSpendSignatureP *coin_sig;
+
+ /**
+ * Unique ID of the coin in the known_coins table.
+ */
+ uint64_t known_coin_id;
+
+ /**
+ * Unique ID of the refresh reveal context of the melt for the new coin.
+ */
+ uint64_t rrc_serial;
+
+ /**
+ * Set by #recoup_transaction to the timestamp when the recoup
+ * was accepted.
+ */
+ struct GNUNET_TIME_Timestamp now;
+
+};
+
+
+/**
+ * Execute a "recoup-refresh". The validity of the coin and signature have
+ * already been checked. The database must now check that the coin is not
+ * (double) spent, and execute the transaction.
+ *
+ * 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 the `struct RecoupContext *`
+ * @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 code
+ */
+static enum GNUNET_DB_QueryStatus
+recoup_refresh_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct RecoupContext *pc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ bool recoup_ok;
+ bool internal_failure;
+
+ /* Finally, store new refund data */
+ pc->now = GNUNET_TIME_timestamp_get ();
+ qs = TEH_plugin->do_recoup_refresh (TEH_plugin->cls,
+ &pc->old_coin_pub,
+ pc->rrc_serial,
+ pc->coin_bks,
+ &pc->coin->coin_pub,
+ pc->known_coin_id,
+ pc->coin_sig,
+ &pc->now,
+ &recoup_ok,
+ &internal_failure);
+ 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_recoup_refresh");
+ return qs;
+ }
+
+ if (internal_failure)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "coin transaction history");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! recoup_ok)
+ {
+ *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &pc->coin->denom_pub_hash,
+ &pc->coin->coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+}
+
+
+/**
+ * We have parsed the JSON information about the recoup request. Do
+ * some basic sanity checks (especially that the signature on the
+ * request and coin is valid) and then execute the recoup operation.
+ * Note that we need the DB to check the fee structure, so this is not
+ * done here but during the recoup_transaction().
+ *
+ * @param connection the MHD connection to handle
+ * @param coin information about the coin
+ * @param exchange_vals values contributed by the exchange
+ * during refresh
+ * @param coin_bks blinding data of the coin (to be checked)
+ * @param nonce withdraw nonce (if CS is used)
+ * @param coin_sig signature of the coin
+ * @return MHD result code
+ */
+static MHD_RESULT
+verify_and_execute_recoup_refresh (
+ struct MHD_Connection *connection,
+ const struct TALER_CoinPublicInfo *coin,
+ const struct TALER_ExchangeWithdrawValues *exchange_vals,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct RecoupContext pc;
+ const struct TEH_DenominationKey *dk;
+ MHD_RESULT mret;
+ struct TALER_BlindedCoinHashP h_blind;
+
+ /* check denomination exists and is in recoup mode */
+ dk = TEH_keys_denomination_by_hash (&coin->denom_pub_hash,
+ connection,
+ &mret);
+ if (NULL == dk)
+ return mret;
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
+ {
+ /* This denomination is past the expiration time for recoup */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "RECOUP-REFRESH");
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ /* This denomination is not yet valid */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "RECOUP-REFRESH");
+ }
+ if (! dk->recoup_possible)
+ {
+ /* This denomination is not eligible for recoup */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->denom_pub_hash,
+ TALER_EC_EXCHANGE_RECOUP_REFRESH_NOT_ELIGIBLE,
+ "RECOUP-REFRESH");
+ }
+
+ /* check denomination signature */
+ switch (dk->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
+ break;
+ default:
+ break;
+ }
+ if (GNUNET_YES !=
+ TALER_test_coin_valid (coin,
+ &dk->denom_pub))
+ {
+ TALER_LOG_WARNING ("Invalid coin passed for recoup\n");
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ /* check recoup request signature */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_recoup_refresh_verify (&coin->denom_pub_hash,
+ coin_bks,
+ &coin->coin_pub,
+ coin_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RECOUP_REFRESH_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ {
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_BlindedPlanchet blinded_planchet;
+
+ if (GNUNET_OK !=
+ TALER_denom_blind (&dk->denom_pub,
+ coin_bks,
+ nonce,
+ &coin->h_age_commitment,
+ &coin->coin_pub,
+ exchange_vals,
+ &c_hash,
+ &blinded_planchet))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_RECOUP_REFRESH_BLINDING_FAILED,
+ NULL);
+ }
+ TALER_coin_ev_hash (&blinded_planchet,
+ &coin->denom_pub_hash,
+ &h_blind);
+ TALER_blinded_planchet_free (&blinded_planchet);
+ }
+
+ pc.coin_sig = coin_sig;
+ pc.coin_bks = coin_bks;
+ pc.coin = coin;
+
+ {
+ MHD_RESULT mhd_ret = MHD_NO;
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* make sure coin is 'known' in database */
+ qs = TEH_make_coin_known (coin,
+ connection,
+ &pc.known_coin_id,
+ &mhd_ret);
+ /* no transaction => no serialization failures should be possible */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ if (qs < 0)
+ return mhd_ret;
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->get_old_coin_by_h_blind (TEH_plugin->cls,
+ &h_blind,
+ &pc.old_coin_pub,
+ &pc.rrc_serial);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_old_coin_by_h_blind");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Recoup-refresh requested for unknown envelope %s\n",
+ GNUNET_h2s (&h_blind.hash));
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_RECOUP_REFRESH_MELT_NOT_FOUND,
+ NULL);
+ }
+ }
+
+ /* Perform actual recoup transaction */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "run recoup-refresh",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &recoup_refresh_transaction,
+ &pc))
+ return mhd_ret;
+ }
+ /* Recoup succeeded, return result */
+ return TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto (
+ "old_coin_pub",
+ &pc.old_coin_pub));
+}
+
+
+/**
+ * Handle a "/coins/$COIN_PUB/recoup-refresh" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_recoup_refresh() to further
+ * check the details of the operation specified. If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
+ *
+ * @param connection the MHD connection to handle
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_recoup_refresh (struct MHD_Connection *connection,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const json_t *root)
+{
+ enum GNUNET_GenericReturnValue ret;
+ struct TALER_CoinPublicInfo coin = {0};
+ union GNUNET_CRYPTO_BlindingSecretP coin_bks;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_ExchangeWithdrawValues exchange_vals;
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+ bool no_nonce;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+ &coin.denom_pub_hash),
+ TALER_JSON_spec_denom_sig ("denom_sig",
+ &coin.denom_sig),
+ TALER_JSON_spec_exchange_withdraw_values ("ewv",
+ &exchange_vals),
+ GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret",
+ &coin_bks),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &coin_sig),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &coin.h_age_commitment),
+ &coin.no_age_commitment),
+ // FIXME: rename to just 'nonce'
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("cs_nonce",
+ &nonce),
+ &no_nonce),
+ GNUNET_JSON_spec_end ()
+ };
+
+ memset (&coin,
+ 0,
+ sizeof (coin));
+ coin.coin_pub = *coin_pub;
+ ret = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == ret)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == ret)
+ return MHD_YES; /* failure */
+ {
+ MHD_RESULT res;
+
+ res = verify_and_execute_recoup_refresh (connection,
+ &coin,
+ &exchange_vals,
+ &coin_bks,
+ no_nonce
+ ? NULL
+ : &nonce,
+ &coin_sig);
+ GNUNET_JSON_parse_free (spec);
+ return res;
+ }
+}
+
+
+/* end of taler-exchange-httpd_recoup-refresh.c */
diff --git a/src/exchange/taler-exchange-httpd_recoup-refresh.h b/src/exchange/taler-exchange-httpd_recoup-refresh.h
new file mode 100644
index 000000000..94ae7f889
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_recoup-refresh.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2017, 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_recoup-refresh.h
+ * @brief Handle /recoup-refresh requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_RECOUP_REFRESH_H
+#define TALER_EXCHANGE_HTTPD_RECOUP_REFRESH_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/coins/$COIN_PUB/recoup-refresh" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_recoup_refresh() to further
+ * check the details of the operation specified. If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
+ *
+ * @param connection the MHD connection to handle
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_recoup_refresh (struct MHD_Connection *connection,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const json_t *root);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c
index d8e7d189f..afbbd7474 100644
--- a/src/exchange/taler-exchange-httpd_recoup.c
+++ b/src/exchange/taler-exchange-httpd_recoup.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2017-2020 Taler Systems SA
+ Copyright (C) 2017-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -28,10 +28,11 @@
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_db.h"
#include "taler-exchange-httpd_recoup.h"
#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keystate.h"
-
+#include "taler-exchange-httpd_keys.h"
+#include "taler_exchangedb_lib.h"
/**
* Closure for #recoup_transaction.
@@ -39,14 +40,15 @@
struct RecoupContext
{
/**
- * Hash of the blinded coin.
+ * Hash identifying the withdraw request.
*/
- struct GNUNET_HashCode h_blind;
+ struct TALER_BlindedCoinHashP h_coin_ev;
/**
- * Full value of the coin.
+ * Set by #recoup_transaction() to the reserve that will
+ * receive the recoup, if #refreshed is #GNUNET_NO.
*/
- struct TALER_Amount value;
+ struct TALER_ReservePublicKeyP reserve_pub;
/**
* Details about the coin.
@@ -56,7 +58,7 @@ struct RecoupContext
/**
* Key used to blind the coin.
*/
- const struct TALER_DenominationBlindingKeyP *coin_bks;
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks;
/**
* Signature of the coin requesting recoup.
@@ -64,39 +66,20 @@ struct RecoupContext
const struct TALER_CoinSpendSignatureP *coin_sig;
/**
- * Where does the value of the recouped coin go? Which member
- * of the union is valid depends on @e refreshed.
+ * Unique ID of the withdraw operation in the reserves_out table.
*/
- union
- {
- /**
- * Set by #recoup_transaction() to the reserve that will
- * receive the recoup, if #refreshed is #GNUNET_NO.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Set by #recoup_transaction() to the old coin that will
- * receive the recoup, if #refreshed is #GNUNET_YES.
- */
- struct TALER_CoinSpendPublicKeyP old_coin_pub;
- } target;
+ uint64_t reserve_out_serial_id;
/**
- * Set by #recoup_transaction() to the amount that will be paid back
+ * Unique ID of the coin in the known_coins table.
*/
- struct TALER_Amount amount;
+ uint64_t known_coin_id;
/**
* Set by #recoup_transaction to the timestamp when the recoup
* was accepted.
*/
- struct GNUNET_TIME_Absolute now;
-
- /**
- * #GNUNET_YES if the client claims the coin originated from a refresh.
- */
- int refreshed;
+ struct GNUNET_TIME_Timestamp now;
};
@@ -114,7 +97,6 @@ struct RecoupContext
*
* @param cls the `struct RecoupContext *`
* @param connection MHD request which triggered the transaction
- * @param session database session to use
* @param[out] mhd_ret set to MHD response status for @a connection,
* if transaction failed (!)
* @return transaction status code
@@ -122,209 +104,56 @@ struct RecoupContext
static enum GNUNET_DB_QueryStatus
recoup_transaction (void *cls,
struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
+ MHD_RESULT *mhd_ret)
{
struct RecoupContext *pc = cls;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- struct TALER_Amount spent;
- struct TALER_Amount recouped;
enum GNUNET_DB_QueryStatus qs;
- int existing_recoup_found;
-
- /* Check whether a recoup is allowed, and if so, to which
- reserve / account the money should go */
- if (pc->refreshed)
- {
- qs = TEH_plugin->get_old_coin_by_h_blind (TEH_plugin->cls,
- session,
- &pc->h_blind,
- &pc->target.old_coin_pub);
- 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_RECOUP_DB_FETCH_FAILED,
- "failed to fetch old coin of blind coin");
- }
- return qs;
- }
- }
- else
- {
- qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls,
- session,
- &pc->h_blind,
- &pc->target.reserve_pub);
- 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_RECOUP_DB_FETCH_FAILED,
- "failed to fetch reserve of blinded coin");
- }
- return qs;
- }
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Recoup requested for unknown envelope %s\n",
- GNUNET_h2s (&pc->h_blind));
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_RECOUP_WITHDRAW_NOT_FOUND,
- "envelope unknown");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- /* Calculate remaining balance, including recoups already applied. */
- qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
- session,
- &pc->coin->coin_pub,
- GNUNET_YES,
- &tl);
+ bool recoup_ok;
+ bool internal_failure;
+
+ /* Finally, store new refund data */
+ pc->now = GNUNET_TIME_timestamp_get ();
+ qs = TEH_plugin->do_recoup (TEH_plugin->cls,
+ &pc->reserve_pub,
+ pc->reserve_out_serial_id,
+ pc->coin_bks,
+ &pc->coin->coin_pub,
+ pc->known_coin_id,
+ pc->coin_sig,
+ &pc->now,
+ &recoup_ok,
+ &internal_failure);
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_RECOUP_DB_FETCH_FAILED,
- "failed to fetch coin transaction history");
- }
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "do_recoup");
return qs;
}
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (pc->value.currency,
- &spent));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (pc->value.currency,
- &recouped));
- /* Check if this coin has been recouped already at least once */
- existing_recoup_found = GNUNET_NO;
- for (struct TALER_EXCHANGEDB_TransactionList *pos = tl;
- NULL != pos;
- pos = pos->next)
- {
- if ( (TALER_EXCHANGEDB_TT_RECOUP == pos->type) ||
- (TALER_EXCHANGEDB_TT_RECOUP_REFRESH == pos->type) )
- {
- existing_recoup_found = GNUNET_YES;
- break;
- }
- }
- if (GNUNET_OK !=
- TALER_EXCHANGEDB_calculate_transaction_list_totals (tl,
- &spent,
- &spent))
+ if (internal_failure)
{
GNUNET_break (0);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_RECOUP_HISTORY_DB_ERROR,
- "failed to calculate old coin transaction history");
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "do_recoup");
return GNUNET_DB_STATUS_HARD_ERROR;
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Recoup: calculated spent %s\n",
- TALER_amount2s (&spent));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Recoup: coin value %s\n",
- TALER_amount2s (&pc->value));
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&pc->amount,
- &pc->value,
- &spent))
+ if (! recoup_ok)
{
- GNUNET_break (0);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_RECOUP_COIN_BALANCE_NEGATIVE,
- "calculated negative coin balance");
+ *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &pc->coin->denom_pub_hash,
+ &pc->coin->coin_pub);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- if ( (0 == pc->amount.fraction) &&
- (0 == pc->amount.value) )
- {
- /* Recoup has no effect: coin fully spent! */
- int ret;
-
- TEH_plugin->rollback (TEH_plugin->cls,
- session);
- if (GNUNET_NO == existing_recoup_found)
- {
- /* Refuse: insufficient funds for recoup */
- *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (connection,
- TALER_EC_RECOUP_COIN_BALANCE_ZERO,
- &pc->coin->coin_pub,
- tl);
- ret = GNUNET_DB_STATUS_HARD_ERROR;
- }
- else
- {
- /* We didn't add any new recoup transaction, but there was at least
- one recoup before, so we give a success response (idempotency!) */
- ret = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- }
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- return ret;
- }
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- pc->now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&pc->now);
-
- /* add coin to list of wire transfers for recoup */
- if (pc->refreshed)
- {
- qs = TEH_plugin->insert_recoup_refresh_request (TEH_plugin->cls,
- session,
- pc->coin,
- pc->coin_sig,
- pc->coin_bks,
- &pc->amount,
- &pc->h_blind,
- pc->now);
- }
- else
- {
- qs = TEH_plugin->insert_recoup_request (TEH_plugin->cls,
- session,
- &pc->target.reserve_pub,
- pc->coin,
- pc->coin_sig,
- pc->coin_bks,
- &pc->amount,
- &pc->h_blind,
- pc->now);
- }
- if (0 > qs)
- {
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- TALER_LOG_WARNING ("Failed to store recoup information in database\n");
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_RECOUP_DB_PUT_FAILED,
- "failed to persist recoup data");
- }
- return qs;
- }
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ return qs;
}
@@ -337,167 +166,197 @@ recoup_transaction (void *cls,
*
* @param connection the MHD connection to handle
* @param coin information about the coin
+ * @param exchange_vals values contributed by the exchange
+ * during withdrawal
* @param coin_bks blinding data of the coin (to be checked)
+ * @param nonce coin's nonce if CS is used
* @param coin_sig signature of the coin
- * @param refreshed #GNUNET_YES if the coin was refreshed
* @return MHD result code
*/
-static int
-verify_and_execute_recoup (struct MHD_Connection *connection,
- const struct TALER_CoinPublicInfo *coin,
- const struct
- TALER_DenominationBlindingKeyP *coin_bks,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- int refreshed)
+static MHD_RESULT
+verify_and_execute_recoup (
+ struct MHD_Connection *connection,
+ const struct TALER_CoinPublicInfo *coin,
+ const struct TALER_ExchangeWithdrawValues *exchange_vals,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
{
struct RecoupContext pc;
- const struct TALER_EXCHANGEDB_DenominationKey *dki;
- struct GNUNET_HashCode c_hash;
- void *coin_ev;
- size_t coin_ev_size;
- enum TALER_ErrorCode ec;
- unsigned int hc;
+ const struct TEH_DenominationKey *dk;
+ MHD_RESULT mret;
/* check denomination exists and is in recoup mode */
+ dk = TEH_keys_denomination_by_hash (&coin->denom_pub_hash,
+ connection,
+ &mret);
+ if (NULL == dk)
+ return mret;
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
+ {
+ /* This denomination is past the expiration time for recoup */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "RECOUP");
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
{
- struct TEH_KS_StateHandle *key_state;
+ /* This denomination is not yet valid */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "RECOUP");
+ }
+ if (! dk->recoup_possible)
+ {
+ /* This denomination is not eligible for recoup */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->denom_pub_hash,
+ TALER_EC_EXCHANGE_RECOUP_NOT_ELIGIBLE,
+ "RECOUP");
+ }
- key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
- if (NULL == key_state)
- {
- TALER_LOG_ERROR ("Lacking keys to operate\n");
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_BAD_CONFIGURATION,
- "no keys");
- }
- dki = TEH_KS_denomination_key_lookup_by_hash (key_state,
- &coin->denom_pub_hash,
- TEH_KS_DKU_RECOUP,
- &ec,
- &hc);
- if (NULL == dki)
- {
- TEH_KS_release (key_state);
- TALER_LOG_WARNING (
- "Denomination key in recoup request not in recoup mode\n");
- return TALER_MHD_reply_with_error (connection,
- hc,
- ec,
- "denomination not valid for recoup");
- }
- TALER_amount_ntoh (&pc.value,
- &dki->issue.properties.value);
+ /* check denomination signature */
+ switch (dk->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
+ break;
+ default:
+ break;
+ }
+ if (GNUNET_YES !=
+ TALER_test_coin_valid (coin,
+ &dk->denom_pub))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+ NULL);
+ }
- /* check denomination signature */
- if (GNUNET_YES !=
- TALER_test_coin_valid (coin,
- &dki->denom_pub))
- {
- TALER_LOG_WARNING ("Invalid coin passed for recoup\n");
- TEH_KS_release (key_state);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_RECOUP_DENOMINATION_SIGNATURE_INVALID,
- "denom_sig");
- }
+ /* check recoup request signature */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_recoup_verify (&coin->denom_pub_hash,
+ coin_bks,
+ &coin->coin_pub,
+ coin_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RECOUP_SIGNATURE_INVALID,
+ NULL);
+ }
- /* check recoup request signature */
- {
- struct TALER_RecoupRequestPS pr = {
- .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP),
- .purpose.size = htonl (sizeof (struct TALER_RecoupRequestPS)),
- .coin_pub = coin->coin_pub,
- .h_denom_pub = dki->issue.properties.denom_hash,
- .coin_blind = *coin_bks
- };
-
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP,
- &pr.purpose,
- &coin_sig->eddsa_signature,
- &coin->coin_pub.eddsa_pub))
- {
- TALER_LOG_WARNING ("Invalid signature on recoup request\n");
- TEH_KS_release (key_state);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_RECOUP_SIGNATURE_INVALID,
- "coin_sig");
- }
- }
- GNUNET_CRYPTO_hash (&coin->coin_pub.eddsa_pub,
- sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
- &c_hash);
- if (GNUNET_YES !=
- GNUNET_CRYPTO_rsa_blind (&c_hash,
- &coin_bks->bks,
- dki->denom_pub.rsa_public_key,
- &coin_ev,
- &coin_ev_size))
+ /* re-compute client-side blinding so we can
+ (a bit later) check that this coin was indeed
+ signed by us. */
+ {
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_BlindedPlanchet blinded_planchet;
+
+ if (GNUNET_OK !=
+ TALER_denom_blind (&dk->denom_pub,
+ coin_bks,
+ nonce,
+ &coin->h_age_commitment,
+ &coin->coin_pub,
+ exchange_vals,
+ &c_hash,
+ &blinded_planchet))
{
GNUNET_break (0);
- TEH_KS_release (key_state);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_RECOUP_BLINDING_FAILED,
- "coin_bks");
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED,
+ NULL);
}
- TEH_KS_release (key_state);
+ TALER_coin_ev_hash (&blinded_planchet,
+ &coin->denom_pub_hash,
+ &pc.h_coin_ev);
+ TALER_blinded_planchet_free (&blinded_planchet);
}
- GNUNET_CRYPTO_hash (coin_ev,
- coin_ev_size,
- &pc.h_blind);
- GNUNET_free (coin_ev);
- /* make sure coin is 'known' in database */
- {
- struct TEH_DB_KnowCoinContext kcc;
- int mhd_ret;
+ pc.coin_sig = coin_sig;
+ pc.coin_bks = coin_bks;
+ pc.coin = coin;
- kcc.coin = coin;
- kcc.connection = connection;
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "know coin for recoup",
- &mhd_ret,
- &TEH_DB_know_coin_transaction,
- &kcc))
+ {
+ MHD_RESULT mhd_ret = MHD_NO;
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* make sure coin is 'known' in database */
+ qs = TEH_make_coin_known (coin,
+ connection,
+ &pc.known_coin_id,
+ &mhd_ret);
+ /* no transaction => no serialization failures should be possible */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ if (qs < 0)
return mhd_ret;
}
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls,
+ &pc.h_coin_ev,
+ &pc.reserve_pub,
+ &pc.reserve_out_serial_id);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_reserve_by_h_blind");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Recoup requested for unknown envelope %s\n",
+ GNUNET_h2s (&pc.h_coin_ev.hash));
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_RECOUP_WITHDRAW_NOT_FOUND,
+ NULL);
+ }
+ }
+
/* Perform actual recoup transaction */
- pc.coin_sig = coin_sig;
- pc.coin_bks = coin_bks;
- pc.coin = coin;
- pc.refreshed = refreshed;
{
- int mhd_ret;
+ MHD_RESULT mhd_ret;
if (GNUNET_OK !=
TEH_DB_run_transaction (connection,
"run recoup",
+ TEH_MT_REQUEST_OTHER,
&mhd_ret,
&recoup_transaction,
&pc))
return mhd_ret;
}
/* Recoup succeeded, return result */
- return (refreshed)
- ? TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:o, s:b}",
- "old_coin_pub",
- GNUNET_JSON_from_data_auto (
- &pc.target.old_coin_pub),
- "refreshed", 1)
- : TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:o, s:b}",
+ return TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto (
"reserve_pub",
- GNUNET_JSON_from_data_auto (
- &pc.target.reserve_pub),
- "refreshed", 0);
+ &pc.reserve_pub));
}
@@ -512,46 +371,66 @@ verify_and_execute_recoup (struct MHD_Connection *connection,
* @param root uploaded JSON data
* @return MHD result code
*/
-int
+MHD_RESULT
TEH_handler_recoup (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const json_t *root)
{
- int res;
+ enum GNUNET_GenericReturnValue ret;
struct TALER_CoinPublicInfo coin;
- struct TALER_DenominationBlindingKeyP coin_bks;
+ union GNUNET_CRYPTO_BlindingSecretP coin_bks;
struct TALER_CoinSpendSignatureP coin_sig;
- int refreshed = GNUNET_NO;
+ struct TALER_ExchangeWithdrawValues exchange_vals;
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+ bool no_nonce;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
&coin.denom_pub_hash),
- TALER_JSON_spec_denomination_signature ("denom_sig",
- &coin.denom_sig),
+ TALER_JSON_spec_denom_sig ("denom_sig",
+ &coin.denom_sig),
+ TALER_JSON_spec_exchange_withdraw_values ("ewv",
+ &exchange_vals),
GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret",
&coin_bks),
GNUNET_JSON_spec_fixed_auto ("coin_sig",
&coin_sig),
- GNUNET_JSON_spec_mark_optional
- (GNUNET_JSON_spec_boolean ("refreshed",
- &refreshed)),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &coin.h_age_commitment),
+ &coin.no_age_commitment),
+ // FIXME: should be renamed to just 'nonce'!
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("cs_nonce",
+ &nonce),
+ &no_nonce),
GNUNET_JSON_spec_end ()
};
+ memset (&coin,
+ 0,
+ sizeof (coin));
coin.coin_pub = *coin_pub;
- res = TALER_MHD_parse_json_data (connection,
+ ret = TALER_MHD_parse_json_data (connection,
root,
spec);
- if (GNUNET_SYSERR == res)
+ if (GNUNET_SYSERR == ret)
return MHD_NO; /* hard failure */
- if (GNUNET_NO == res)
+ if (GNUNET_NO == ret)
return MHD_YES; /* failure */
- res = verify_and_execute_recoup (connection,
- &coin,
- &coin_bks,
- &coin_sig,
- refreshed);
- GNUNET_JSON_parse_free (spec);
- return res;
+ {
+ MHD_RESULT res;
+
+ res = verify_and_execute_recoup (connection,
+ &coin,
+ &exchange_vals,
+ &coin_bks,
+ no_nonce
+ ? NULL
+ : &nonce,
+ &coin_sig);
+ GNUNET_JSON_parse_free (spec);
+ return res;
+ }
}
diff --git a/src/exchange/taler-exchange-httpd_recoup.h b/src/exchange/taler-exchange-httpd_recoup.h
index 85fdc1399..95e60773f 100644
--- a/src/exchange/taler-exchange-httpd_recoup.h
+++ b/src/exchange/taler-exchange-httpd_recoup.h
@@ -37,7 +37,7 @@
* @param root uploaded JSON data
* @return MHD result code
*/
-int
+MHD_RESULT
TEH_handler_recoup (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const json_t *root);
diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
index b7db0a4b4..5630051cf 100644
--- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c
+++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2019 Taler Systems SA
+ Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -28,20 +28,7 @@
#include "taler-exchange-httpd_mhd.h"
#include "taler-exchange-httpd_refreshes_reveal.h"
#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keystate.h"
-
-
-/**
- * Maximum number of fresh coins we allow per refresh operation.
- */
-#define MAX_FRESH_COINS 256
-
-/**
- * How often do we at most retry the reveal transaction sequence?
- * Twice should really suffice in all cases (as the possible conflict
- * cannot happen more than once).
- */
-#define MAX_REVEAL_RETRIES 2
+#include "taler-exchange-httpd_keys.h"
/**
@@ -49,62 +36,38 @@
*
* @param connection the connection to send the response to
* @param num_freshcoins number of new coins for which we reveal data
- * @param sigs array of @a num_freshcoins signatures revealed
+ * @param rrcs array of @a num_freshcoins signatures revealed
* @return a MHD result code
*/
-static int
-reply_refreshes_reveal_success (struct MHD_Connection *connection,
- unsigned int num_freshcoins,
- const struct TALER_DenominationSignature *sigs)
+static MHD_RESULT
+reply_refreshes_reveal_success (
+ struct MHD_Connection *connection,
+ unsigned int num_freshcoins,
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs)
{
json_t *list;
list = json_array ();
- if (NULL == list)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_JSON_ALLOCATION_FAILURE,
- "json_array() call failed");
- }
+ GNUNET_assert (NULL != list);
for (unsigned int freshcoin_index = 0;
freshcoin_index < num_freshcoins;
freshcoin_index++)
{
json_t *obj;
- obj = json_pack ("{s:o}",
- "ev_sig",
- GNUNET_JSON_from_rsa_signature (
- sigs[freshcoin_index].rsa_signature));
- if (NULL == obj)
- {
- json_decref (list);
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_JSON_ALLOCATION_FAILURE,
- "json_pack() failed");
- }
- if (0 !=
- json_array_append_new (list,
- obj))
- {
- json_decref (list);
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_JSON_ALLOCATION_FAILURE,
- "json_array_append_new() failed");
- }
+ obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_denom_sig ("ev_sig",
+ &rrcs[freshcoin_index].coin_sig));
+ GNUNET_assert (0 ==
+ json_array_append_new (list,
+ obj));
}
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:o}",
- "ev_sigs",
- list);
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("ev_sigs",
+ list));
}
@@ -130,201 +93,152 @@ struct RevealContext
struct TALER_TransferPrivateKeyP transfer_privs[TALER_CNC_KAPPA - 1];
/**
+ * Melt data for our session we got from the database for @e rc.
+ */
+ struct TALER_EXCHANGEDB_Melt melt;
+
+ /**
* Denominations being requested.
*/
- const struct TALER_EXCHANGEDB_DenominationKey **dkis;
+ const struct TEH_DenominationKey **dks;
/**
- * Envelopes to be signed.
+ * Age commitment that was used for the original coin. If not NULL, its hash
+ * should be the same as melt.session.h_age_commitment.
*/
- const struct TALER_RefreshCoinData *rcds;
+ struct TALER_AgeCommitment *old_age_commitment;
/**
- * Signatures over the link data (of type
- * #TALER_SIGNATURE_WALLET_COIN_LINK)
+ * Array of information about fresh coins being revealed.
*/
- const struct TALER_CoinSpendSignatureP *link_sigs;
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs;
/**
- * Envelopes with the signatures to be returned. Initially NULL.
+ * Envelopes to be signed.
*/
- struct TALER_DenominationSignature *ev_sigs;
+ struct TALER_RefreshCoinData *rcds;
/**
- * Size of the @e dkis, @e rcds and @e ev_sigs arrays (if non-NULL).
+ * Refresh master secret.
*/
- unsigned int num_fresh_coins;
+ struct TALER_RefreshMasterSecretP rms;
/**
- * Result from preflight checks. #GNUNET_NO for no result,
- * #GNUNET_YES if preflight found previous successful operation,
- * #GNUNET_SYSERR if prefight check failed hard (and generated
- * an MHD response already).
+ * Size of the @e dks, @e rcds and @e ev_sigs arrays (if non-NULL).
*/
- int preflight_ok;
+ unsigned int num_fresh_coins;
+ /**
+ * True if @e rms was not provided.
+ */
+ bool no_rms;
};
/**
- * Function called with information about a refresh order we already
- * persisted. Stores the result in @a cls so we don't do the calculation
- * again.
- *
- * @param cls closure with a `struct RevealContext`
- * @param num_freshcoins size of the @a rrcs array
- * @param rrcs array of @a num_freshcoins information about coins to be created
- * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
- * @param tprivs array of @e num_tprivs transfer private keys
- * @param tp transfer public key information
- */
-static void
-check_exists_cb (void *cls,
- uint32_t num_freshcoins,
- const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
- unsigned int num_tprivs,
- const struct TALER_TransferPrivateKeyP *tprivs,
- const struct TALER_TransferPublicKeyP *tp)
-{
- struct RevealContext *rctx = cls;
-
- if (0 == num_freshcoins)
- {
- GNUNET_break (0);
- return;
- }
- /* This should be a database invariant for us */
- GNUNET_break (TALER_CNC_KAPPA - 1 == num_tprivs);
- /* Given that the $RCH value matched, we don't actually need to check these
- values (we checked before). However, if a client repeats a request with
- invalid values the 2nd time, that's a protocol violation we should at least
- log (but it's safe to ignore it). */
- GNUNET_break_op (0 ==
- GNUNET_memcmp (tp,
- &rctx->gamma_tp));
- GNUNET_break_op (0 ==
- memcmp (tprivs,
- &rctx->transfer_privs,
- sizeof (struct TALER_TransferPrivateKeyP)
- * num_tprivs));
- /* We usually sign early (optimistic!), but in case we change that *and*
- we do find the operation in the database, we could use this: */
- if (NULL == rctx->ev_sigs)
- {
- rctx->ev_sigs = GNUNET_new_array (num_freshcoins,
- struct TALER_DenominationSignature);
- for (unsigned int i = 0; i<num_freshcoins; i++)
- rctx->ev_sigs[i].rsa_signature
- = GNUNET_CRYPTO_rsa_signature_dup (rrcs[i].coin_sig.rsa_signature);
- }
-}
-
-
-/**
- * Check if the "/refreshes/$RCH/reveal" request was already successful
- * before. If so, just return the old result.
- *
- * @param cls closure of type `struct RevealContext`
- * @param connection MHD request which triggered the transaction
- * @param session database session to use
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-refreshes_reveal_preflight (void *cls,
- struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
-{
- struct RevealContext *rctx = cls;
- enum GNUNET_DB_QueryStatus qs;
-
- /* Try to see if we already have given an answer before. */
- qs = TEH_plugin->get_refresh_reveal (TEH_plugin->cls,
- session,
- &rctx->rc,
- &check_exists_cb,
- rctx);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return qs; /* continue normal execution */
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return qs;
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (qs);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_REVEAL_DB_FETCH_REVEAL_ERROR,
- "failed to fetch reveal data");
- rctx->preflight_ok = GNUNET_SYSERR;
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- default:
- /* Hossa, already found our reply! */
- GNUNET_assert (NULL != rctx->ev_sigs);
- rctx->preflight_ok = GNUNET_YES;
- return qs;
- }
-}
-
-
-/**
- * Execute a "/refreshes/$RCH/reveal". The client is revealing to us the
+ * Check client's revelation against the original commitment.
+ * The client is revealing to us the
* transfer keys for @a #TALER_CNC_KAPPA-1 sets of coins. Verify that the
* revealed transfer keys would allow linkage to the blinded coins.
*
- * 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.
+ * IF it returns #GNUNET_OK, the transaction logic MUST
+ * NOT queue a MHD response. IF it returns an error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret.
*
- * @param cls closure of type `struct RevealContext`
+ * @param rctx our operation context
* @param connection MHD request which triggered the transaction
- * @param session database session to use
* @param[out] mhd_ret set to MHD response status for @a connection,
* if transaction failed (!)
- * @return transaction status
+ * @return #GNUNET_OK if commitment was OK
*/
-static enum GNUNET_DB_QueryStatus
-refreshes_reveal_transaction (void *cls,
- struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
+static enum GNUNET_GenericReturnValue
+check_commitment (struct RevealContext *rctx,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
{
- struct RevealContext *rctx = cls;
- struct TALER_EXCHANGEDB_Melt melt;
- enum GNUNET_DB_QueryStatus qs;
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonces[rctx->num_fresh_coins];
- /* Obtain basic information about the refresh operation and what
- gamma we committed to. */
- qs = TEH_plugin->get_melt (TEH_plugin->cls,
- session,
- &rctx->rc,
- &melt);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ memset (nonces,
+ 0,
+ sizeof (nonces));
+ for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
{
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_REVEAL_SESSION_UNKNOWN,
- "rc");
- return GNUNET_DB_STATUS_HARD_ERROR;
+ const struct TALER_DenominationPublicKey *dk = &rctx->dks[j]->denom_pub;
+
+ if (dk->bsign_pub_key->cipher !=
+ rctx->rcds[j].blinded_planchet.blinded_message->cipher)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ switch (dk->bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ return GNUNET_SYSERR;
+ case GNUNET_CRYPTO_BSA_RSA:
+ continue;
+ case GNUNET_CRYPTO_BSA_CS:
+ nonces[j]
+ = (const union GNUNET_CRYPTO_BlindSessionNonce *)
+ &rctx->rcds[j].blinded_planchet.blinded_message->details.
+ cs_blinded_message.nonce;
+ break;
+ }
}
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) ||
- (melt.session.noreveal_index >= TALER_CNC_KAPPA) )
+
+ // OPTIMIZE: do this in batch later!
+ for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
{
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_REVEAL_DB_FETCH_SESSION_ERROR,
- "failed to fetch valid challenge from database");
- return GNUNET_DB_STATUS_HARD_ERROR;
+ const struct TALER_DenominationPublicKey *dk = &rctx->dks[j]->denom_pub;
+ struct TALER_ExchangeWithdrawValues *alg_values
+ = &rctx->rrcs[j].exchange_vals;
+ struct GNUNET_CRYPTO_BlindingInputValues *bi;
+
+ bi = GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues);
+ alg_values->blinding_inputs = bi;
+ bi->rc = 1;
+ bi->cipher = dk->bsign_pub_key->cipher;
+ switch (dk->bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_assert (0);
+ return GNUNET_SYSERR;
+ case GNUNET_CRYPTO_BSA_RSA:
+ continue;
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ enum TALER_ErrorCode ec;
+ const struct TEH_CsDeriveData cdd = {
+ .h_denom_pub = &rctx->rrcs[j].h_denom_pub,
+ .nonce = &nonces[j]->cs_nonce
+ };
+
+ ec = TEH_keys_denomination_cs_r_pub (
+ &cdd,
+ true,
+ &bi->details.cs_values);
+ if (TALER_EC_NONE != ec)
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ ec,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+ }
}
-
/* Verify commitment */
{
/* Note that the contents of rcs[melt.session.noreveal_index]
@@ -339,65 +253,108 @@ refreshes_reveal_transaction (void *cls,
{
struct TALER_RefreshCommitmentEntry *rce = &rcs[i];
- if (i == melt.session.noreveal_index)
+ if (i == rctx->melt.session.noreveal_index)
{
/* Take these coin envelopes from the client */
rce->transfer_pub = rctx->gamma_tp;
- rce->new_coins = (struct TALER_RefreshCoinData *) rctx->rcds;
+ rce->new_coins = rctx->rcds;
off = 1;
}
else
{
/* Reconstruct coin envelopes from transfer private key */
- struct TALER_TransferPrivateKeyP *tpriv
+ const struct TALER_TransferPrivateKeyP *tpriv
= &rctx->transfer_privs[i - off];
struct TALER_TransferSecretP ts;
+ struct TALER_AgeCommitmentHash h = {0};
+ struct TALER_AgeCommitmentHash *hac = NULL;
GNUNET_CRYPTO_ecdhe_key_get_public (&tpriv->ecdhe_priv,
&rce->transfer_pub.ecdhe_pub);
+ TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_ECDH]++;
TALER_link_reveal_transfer_secret (tpriv,
- &melt.session.coin.coin_pub,
+ &rctx->melt.session.coin.coin_pub,
&ts);
rce->new_coins = GNUNET_new_array (rctx->num_fresh_coins,
struct TALER_RefreshCoinData);
for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
{
struct TALER_RefreshCoinData *rcd = &rce->new_coins[j];
- struct TALER_PlanchetSecretsP ps;
- struct TALER_PlanchetDetail pd;
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ const struct TALER_ExchangeWithdrawValues *alg_value
+ = &rctx->rrcs[j].exchange_vals;
+ struct TALER_PlanchetDetail pd = {0};
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_PlanchetMasterSecretP ps;
+
+ rcd->dk = &rctx->dks[j]->denom_pub;
+ TALER_transfer_secret_to_planchet_secret (&ts,
+ j,
+ &ps);
+ TALER_planchet_setup_coin_priv (&ps,
+ alg_value,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (&ps,
+ alg_value,
+ &bks);
+ /* Calculate, if applicable, the age commitment and its hash, from
+ * the transfer_secret and the old age commitment. */
+ if (NULL != rctx->old_age_commitment)
+ {
+ struct TALER_AgeCommitmentProof acp = {
+ /* we only need the commitment, not the proof, for the call to
+ * TALER_age_commitment_derive. */
+ .commitment = *(rctx->old_age_commitment)
+ };
+ struct TALER_AgeCommitmentProof nacp = {0};
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_age_commitment_derive (
+ &acp,
+ &ts.key,
+ &nacp));
+ TALER_age_commitment_hash (&nacp.commitment,
+ &h);
+ TALER_age_commitment_proof_free (&nacp);
+ hac = &h;
+ }
- rcd->dk = &rctx->dkis[j]->denom_pub;
- TALER_planchet_setup_refresh (&ts,
- j,
- &ps);
GNUNET_assert (GNUNET_OK ==
TALER_planchet_prepare (rcd->dk,
- &ps,
+ alg_value,
+ &bks,
+ nonces[j],
+ &coin_priv,
+ hac,
+ &c_hash,
&pd));
- rcd->coin_ev = pd.coin_ev;
- rcd->coin_ev_size = pd.coin_ev_size;
+ rcd->blinded_planchet = pd.blinded_planchet;
}
}
}
TALER_refresh_get_commitment (&rc_expected,
TALER_CNC_KAPPA,
+ rctx->no_rms
+ ? NULL
+ : &rctx->rms,
rctx->num_fresh_coins,
rcs,
- &melt.session.coin.coin_pub,
- &melt.session.amount_with_fee);
+ &rctx->melt.session.coin.coin_pub,
+ &rctx->melt.session.amount_with_fee);
/* Free resources allocated above */
for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
{
struct TALER_RefreshCommitmentEntry *rce = &rcs[i];
- if (i == melt.session.noreveal_index)
+ if (i == rctx->melt.session.noreveal_index)
continue; /* This offset is special: not allocated! */
for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
{
struct TALER_RefreshCoinData *rcd = &rce->new_coins[j];
- GNUNET_free (rcd->coin_ev);
+ TALER_blinded_planchet_free (&rcd->blinded_planchet);
}
GNUNET_free (rce->new_coins);
}
@@ -407,17 +364,14 @@ refreshes_reveal_transaction (void *cls,
&rc_expected))
{
GNUNET_break_op (0);
- *mhd_ret = TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_CONFLICT,
- "{s:s, s:I, s:o}",
- "hint", "commitment violation",
- "code",
- (json_int_t)
- TALER_EC_REVEAL_COMMITMENT_VIOLATION,
- "rc_expected",
- GNUNET_JSON_from_data_auto (
- &rc_expected));
- return GNUNET_DB_STATUS_HARD_ERROR;
+ *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_COMMITMENT_VIOLATION),
+ GNUNET_JSON_pack_data_auto ("rc_expected",
+ &rc_expected));
+ return GNUNET_SYSERR;
}
} /* end of checking "rc_expected" */
@@ -425,22 +379,16 @@ refreshes_reveal_transaction (void *cls,
{
struct TALER_Amount refresh_cost;
- refresh_cost = melt.melt_fee;
+ refresh_cost = rctx->melt.melt_fee;
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
{
- struct TALER_Amount fee_withdraw;
- struct TALER_Amount value;
struct TALER_Amount total;
- TALER_amount_ntoh (&fee_withdraw,
- &rctx->dkis[i]->issue.properties.fee_withdraw);
- TALER_amount_ntoh (&value,
- &rctx->dkis[i]->issue.properties.value);
- if ( (GNUNET_OK !=
+ if ( (0 >
TALER_amount_add (&total,
- &fee_withdraw,
- &value)) ||
- (GNUNET_OK !=
+ &rctx->dks[i]->meta.fees.withdraw,
+ &rctx->dks[i]->meta.value)) ||
+ (0 >
TALER_amount_add (&refresh_cost,
&refresh_cost,
&total)) )
@@ -448,117 +396,134 @@ refreshes_reveal_transaction (void *cls,
GNUNET_break_op (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_REVEAL_COST_CALCULATION_OVERFLOW,
- "failed to add up refresh costs");
- return GNUNET_DB_STATUS_HARD_ERROR;
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
+ NULL);
+ return GNUNET_SYSERR;
}
}
if (0 < TALER_amount_cmp (&refresh_cost,
- &melt.session.amount_with_fee))
+ &rctx->melt.session.amount_with_fee))
{
GNUNET_break_op (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_REVEAL_AMOUNT_INSUFFICIENT,
- "melted coin value is insufficient to cover cost of operation");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
-}
-
-
-/**
- * Persist result of a "/refreshes/$RCH/reveal" operation.
- *
- * @param cls closure of type `struct RevealContext`
- * @param connection MHD request which triggered the transaction
- * @param session database session to use
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-refreshes_reveal_persist (void *cls,
- struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
-{
- struct RevealContext *rctx = cls;
- enum GNUNET_DB_QueryStatus qs;
-
- /* Persist operation result in DB */
- {
- struct TALER_EXCHANGEDB_RefreshRevealedCoin rrcs[rctx->num_fresh_coins];
-
- for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
- {
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
-
- rrc->denom_pub = rctx->dkis[i]->denom_pub;
- rrc->orig_coin_link_sig = rctx->link_sigs[i];
- rrc->coin_ev = rctx->rcds[i].coin_ev;
- rrc->coin_ev_size = rctx->rcds[i].coin_ev_size;
- rrc->coin_sig = rctx->ev_sigs[i];
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_AMOUNT_INSUFFICIENT,
+ NULL);
+ return GNUNET_SYSERR;
}
- qs = TEH_plugin->insert_refresh_reveal (TEH_plugin->cls,
- session,
- &rctx->rc,
- rctx->num_fresh_coins,
- rrcs,
- TALER_CNC_KAPPA - 1,
- rctx->transfer_privs,
- &rctx->gamma_tp);
}
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_REVEAL_DB_COMMIT_ERROR,
- "failed to persist reveal data");
- }
- return qs;
+ return GNUNET_OK;
}
/**
- * Resolve denomination hashes using the @a key_state
+ * Resolve denomination hashes.
*
- * @param key_state the key state
* @param connection the MHD connection to handle
* @param rctx context for the operation, partially built at this time
* @param link_sigs_json link signatures in JSON format
* @param new_denoms_h_json requests for fresh coins to be created
+ * @param old_age_commitment_json age commitment that went into the withdrawal, maybe NULL
* @param coin_evs envelopes of gamma-selected coins to be signed
* @return MHD result code
*/
-static int
-resolve_refreshes_reveal_denominations (struct TEH_KS_StateHandle *key_state,
- struct MHD_Connection *connection,
- struct RevealContext *rctx,
- const json_t *link_sigs_json,
- const json_t *new_denoms_h_json,
- const json_t *coin_evs)
+static MHD_RESULT
+resolve_refreshes_reveal_denominations (
+ struct MHD_Connection *connection,
+ struct RevealContext *rctx,
+ const json_t *link_sigs_json,
+ const json_t *new_denoms_h_json,
+ const json_t *old_age_commitment_json,
+ const json_t *coin_evs)
{
unsigned int num_fresh_coins = json_array_size (new_denoms_h_json);
- /* We know num_fresh_coins is bounded by #MAX_FRESH_COINS, so this is safe */
- const struct TALER_EXCHANGEDB_DenominationKey *dkis[num_fresh_coins];
- struct GNUNET_HashCode dki_h[num_fresh_coins];
+ /* We know num_fresh_coins is bounded by #TALER_MAX_FRESH_COINS, so this is safe */
+ const struct TEH_DenominationKey *dks[num_fresh_coins];
+ const struct TEH_DenominationKey *old_dk;
struct TALER_RefreshCoinData rcds[num_fresh_coins];
- struct TALER_CoinSpendSignatureP link_sigs[num_fresh_coins];
- struct TALER_EXCHANGEDB_Melt melt;
- int res;
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin rrcs[num_fresh_coins];
+ MHD_RESULT ret;
+ struct TEH_KeyStateHandle *ksh;
+ uint64_t melt_serial_id;
+ enum GNUNET_DB_QueryStatus qs;
+
+ memset (dks, 0, sizeof (dks));
+ memset (rrcs, 0, sizeof (rrcs));
+ memset (rcds, 0, sizeof (rcds));
+ rctx->num_fresh_coins = num_fresh_coins;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ }
+
+ /* lookup old_coin_pub in database */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ (qs = TEH_plugin->get_melt (TEH_plugin->cls,
+ &rctx->rc,
+ &rctx->melt,
+ &melt_serial_id)))
+ {
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN,
+ NULL);
+ break;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "melt");
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ default:
+ GNUNET_break (0); /* should be impossible */
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ break;
+ }
+ goto cleanup;
+ }
+ if (rctx->melt.session.noreveal_index >= TALER_CNC_KAPPA)
+ {
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "melt");
+ goto cleanup;
+ }
+ }
+
+ old_dk = TEH_keys_denomination_by_hash_from_state (
+ ksh,
+ &rctx->melt.session.coin.denom_pub_hash,
+ connection,
+ &ret);
+ if (NULL == old_dk)
+ return ret;
/* Parse denomination key hashes */
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto (NULL,
- &dki_h[i]),
+ &rrcs[i].h_denom_pub),
GNUNET_JSON_spec_end ()
};
- unsigned int hc;
- enum TALER_ErrorCode ec;
+ enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_array (connection,
new_denoms_h_json,
@@ -566,36 +531,62 @@ resolve_refreshes_reveal_denominations (struct TEH_KS_StateHandle *key_state,
i,
-1);
if (GNUNET_OK != res)
- {
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ dks[i] = TEH_keys_denomination_by_hash_from_state (ksh,
+ &rrcs[i].h_denom_pub,
+ connection,
+ &ret);
+ if (NULL == dks[i])
+ return ret;
+ if ( (GNUNET_CRYPTO_BSA_CS ==
+ dks[i]->denom_pub.bsign_pub_key->cipher) &&
+ (rctx->no_rms) )
+ {
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ "rms");
}
- dkis[i] = TEH_KS_denomination_key_lookup_by_hash (key_state,
- &dki_h[i],
- TEH_KS_DKU_WITHDRAW,
- &ec,
- &hc);
- if (NULL == dkis[i])
+ if (GNUNET_TIME_absolute_is_past (dks[i]->meta.expire_withdraw.abs_time))
{
- return TALER_MHD_reply_with_error (connection,
- hc,
- ec,
- "failed to find denomination key");
+ /* This denomination is past the expiration time for withdraws */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &rrcs[i].h_denom_pub,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "REVEAL");
+ }
+ if (GNUNET_TIME_absolute_is_future (dks[i]->meta.start.abs_time))
+ {
+ /* This denomination is not yet valid */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &rrcs[i].h_denom_pub,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "REVEAL");
+ }
+ if (dks[i]->recoup_possible)
+ {
+ /* This denomination has been revoked */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_GONE,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+ NULL);
}
- /* #TEH_KS_DKU_WITHDRAW should warrant that we only get denomination
- keys where we did not yet forget the private key */
- GNUNET_assert (NULL != dkis[i]->denom_priv.rsa_private_key);
}
/* Parse coin envelopes */
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
- struct TALER_RefreshCoinData *rcd = &rcds[i];
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_varsize (NULL,
- &rcd->coin_ev,
- &rcd->coin_ev_size),
+ TALER_JSON_spec_blinded_planchet (NULL,
+ &rrc->blinded_planchet),
GNUNET_JSON_spec_end ()
};
+ enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_array (connection,
coin_evs,
@@ -605,55 +596,94 @@ resolve_refreshes_reveal_denominations (struct TEH_KS_StateHandle *key_state,
if (GNUNET_OK != res)
{
for (unsigned int j = 0; j<i; j++)
- GNUNET_free_non_null (rcds[j].coin_ev);
+ TALER_blinded_planchet_free (&rrcs[j].blinded_planchet);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
- rcd->dk = &dkis[i]->denom_pub;
+ TALER_coin_ev_hash (&rrc->blinded_planchet,
+ &rrcs[i].h_denom_pub,
+ &rrc->coin_envelope_hash);
}
- /* lookup old_coin_pub in database */
+ if (TEH_age_restriction_enabled &&
+ ((NULL == old_age_commitment_json) !=
+ TALER_AgeCommitmentHash_isNullOrZero (
+ &rctx->melt.session.coin.h_age_commitment)))
{
- enum GNUNET_DB_QueryStatus qs;
+ GNUNET_break (0);
+ return MHD_NO;
+ }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- (qs = TEH_plugin->get_melt (TEH_plugin->cls,
- NULL,
- &rctx->rc,
- &melt)))
+ /* Reconstruct the old age commitment and verify its hash matches the one
+ * from the melt request */
+ if (TEH_age_restriction_enabled &&
+ (NULL != old_age_commitment_json))
+ {
+ enum GNUNET_GenericReturnValue res;
+ struct TALER_AgeCommitment *oac;
+ size_t ng = json_array_size (old_age_commitment_json);
+ bool failed = true;
+
+ /* Has been checked in handle_refreshes_reveal_json() */
+ GNUNET_assert (ng == TEH_age_restriction_config.num_groups);
+
+ rctx->old_age_commitment = GNUNET_new (struct TALER_AgeCommitment);
+ oac = rctx->old_age_commitment;
+ oac->mask = old_dk->meta.age_mask;
+ oac->num = ng;
+ oac->keys = GNUNET_new_array (ng, struct TALER_AgeCommitmentPublicKeyP);
+
+ /* Extract old age commitment */
+ for (unsigned int i = 0; i< ng; i++)
{
- switch (qs)
- {
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- res = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_REVEAL_SESSION_UNKNOWN,
- "rc");
- break;
- case GNUNET_DB_STATUS_HARD_ERROR:
- res = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_REVEAL_DB_FETCH_SESSION_ERROR,
- "failed to fetch session data");
- break;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- default:
- GNUNET_break (0); /* should be impossible */
- res = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_INTERNAL_INVARIANT_FAILURE,
- "assertion failed (unexpected database serialization error)");
- break;
- }
- goto cleanup;
+ struct GNUNET_JSON_Specification ac_spec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &oac->keys[i]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_array (connection,
+ old_age_commitment_json,
+ ac_spec,
+ i,
+ -1);
+
+ GNUNET_break_op (GNUNET_OK == res);
+ if (GNUNET_OK != res)
+ goto clean_age;
+ }
+
+ /* Sanity check: Compare hash from melting with hash of this age commitment */
+ {
+ struct TALER_AgeCommitmentHash hac = {0};
+ TALER_age_commitment_hash (oac, &hac);
+ if (0 != memcmp (&hac,
+ &rctx->melt.session.coin.h_age_commitment,
+ sizeof(struct TALER_AgeCommitmentHash)))
+ goto clean_age;
+ }
+
+ failed = false;
+
+clean_age:
+ if (failed)
+ {
+ TALER_age_commitment_free (oac);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
+ "old_age_commitment");
}
}
+
/* Parse link signatures array */
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
struct GNUNET_JSON_Specification link_spec[] = {
- GNUNET_JSON_spec_fixed_auto (NULL, &link_sigs[i]),
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &rrcs[i].orig_coin_link_sig),
GNUNET_JSON_spec_end ()
};
+ enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_array (connection,
link_sigs_json,
@@ -662,135 +692,185 @@ resolve_refreshes_reveal_denominations (struct TEH_KS_StateHandle *key_state,
-1);
if (GNUNET_OK != res)
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
- /* Check link_sigs[i] signature */
+
+ /* Check signature */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_link_verify (
+ &rrcs[i].h_denom_pub,
+ &rctx->gamma_tp,
+ &rrcs[i].coin_envelope_hash,
+ &rctx->melt.session.coin.coin_pub,
+ &rrcs[i].orig_coin_link_sig))
{
- struct TALER_LinkDataPS ldp = {
- .purpose.size = htonl (sizeof (ldp)),
- .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),
- .h_denom_pub = dki_h[i],
- .old_coin_pub = melt.session.coin.coin_pub,
- .transfer_pub = rctx->gamma_tp
- };
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_LINK_SIGNATURE_INVALID,
+ NULL);
+ goto cleanup;
+ }
+ }
- GNUNET_CRYPTO_hash (rcds[i].coin_ev,
- rcds[i].coin_ev_size,
- &ldp.coin_envelope_hash);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK,
- &ldp.purpose,
- &link_sigs[i].eddsa_signature,
- &melt.session.coin.coin_pub.
- eddsa_pub))
- {
- GNUNET_break_op (0);
- res = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_REVEAL_LINK_SIGNATURE_INVALID,
- "link_sig");
- goto cleanup;
- }
+ /* prepare for check_commitment */
+ for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
+ {
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
+ struct TALER_RefreshCoinData *rcd = &rcds[i];
+
+ rcd->blinded_planchet = rrc->blinded_planchet;
+ rcd->dk = &dks[i]->denom_pub;
+ if (rcd->blinded_planchet.blinded_message->cipher !=
+ rcd->dk->bsign_pub_key->cipher)
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH));
+ goto cleanup;
}
}
- rctx->num_fresh_coins = num_fresh_coins;
+ rctx->dks = dks;
rctx->rcds = rcds;
- rctx->dkis = dkis;
- rctx->link_sigs = link_sigs;
+ rctx->rrcs = rrcs;
+ if (GNUNET_OK !=
+ check_commitment (rctx,
+ connection,
+ &ret))
+ goto cleanup;
- /* sign _early_ (optimistic!) to keep out of transaction scope! */
- rctx->ev_sigs = GNUNET_new_array (rctx->num_fresh_coins,
- struct TALER_DenominationSignature);
- for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Creating %u signatures\n",
+ (unsigned int) rctx->num_fresh_coins);
+
+ /* create fresh coin signatures */
{
- rctx->ev_sigs[i].rsa_signature
- = GNUNET_CRYPTO_rsa_sign_blinded (
- rctx->dkis[i]->denom_priv.rsa_private_key,
- rctx->rcds[i].coin_ev,
- rctx->rcds[i].coin_ev_size);
- if (NULL == rctx->ev_sigs[i].rsa_signature)
+ struct TEH_CoinSignData csds[rctx->num_fresh_coins];
+ struct TALER_BlindedDenominationSignature bss[rctx->num_fresh_coins];
+ enum TALER_ErrorCode ec;
+
+ for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
+ {
+ csds[i].h_denom_pub = &rrcs[i].h_denom_pub;
+ csds[i].bp = &rcds[i].blinded_planchet;
+ }
+ ec = TEH_keys_denomination_batch_sign (
+ rctx->num_fresh_coins,
+ csds,
+ true,
+ bss);
+ if (TALER_EC_NONE != ec)
{
GNUNET_break (0);
- res = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_REVEAL_SIGNING_ERROR,
- "internal signing error");
+ ret = TALER_MHD_reply_with_ec (connection,
+ ec,
+ 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");
+
- /* We try the three transactions a few times, as theoretically
- the pre-check might be satisfied by a concurrent transaction
- voiding our final commit due to uniqueness violation; naturally,
- on hard errors we exit immediately */
- for (unsigned int retries = 0; retries < MAX_REVEAL_RETRIES; retries++)
+ for (unsigned int r = 0; r<MAX_TRANSACTION_COMMIT_RETRIES; r++)
{
- /* do transactional work */
- rctx->preflight_ok = GNUNET_NO;
- if ( (GNUNET_OK ==
- TEH_DB_run_transaction (connection,
- "reveal pre-check",
- &res,
- &refreshes_reveal_preflight,
- rctx)) &&
- (GNUNET_YES == rctx->preflight_ok) )
+ bool changed;
+
+ /* Persist operation result in DB */
+ if (GNUNET_OK !=
+ TEH_plugin->start (TEH_plugin->cls,
+ "insert_refresh_reveal batch"))
+ {
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ NULL);
+ goto cleanup;
+ }
+
+ qs = TEH_plugin->insert_refresh_reveal (
+ TEH_plugin->cls,
+ melt_serial_id,
+ num_fresh_coins,
+ rrcs,
+ TALER_CNC_KAPPA - 1,
+ rctx->transfer_privs,
+ &rctx->gamma_tp);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
- /* Generate final (positive) response */
- GNUNET_assert (NULL != rctx->ev_sigs);
- res = reply_refreshes_reveal_success (connection,
- num_fresh_coins,
- rctx->ev_sigs);
- GNUNET_break (MHD_NO != res);
- goto cleanup; /* aka 'break' */
+ TEH_plugin->rollback (TEH_plugin->cls);
+ continue;
}
- if (GNUNET_SYSERR == rctx->preflight_ok)
+ /* 0 == qs is ok, as we did not check for repeated requests */
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
- goto cleanup; /* aka 'break' */
+ TEH_plugin->rollback (TEH_plugin->cls);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_refresh_reveal");
+ goto cleanup;
}
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "run reveal",
- &res,
- &refreshes_reveal_transaction,
- rctx))
+ changed = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ qs = TEH_plugin->commit (TEH_plugin->cls);
+ if (qs >= 0)
{
- /* reveal failed, too bad */
- GNUNET_break_op (0);
- goto cleanup; /* aka 'break' */
+ if (changed)
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_REFRESH_REVEAL]++;
+ break; /* success */
}
- if (GNUNET_OK ==
- TEH_DB_run_transaction (connection,
- "persist reveal",
- &res,
- &refreshes_reveal_persist,
- rctx))
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
- /* Generate final (positive) response */
- GNUNET_assert (NULL != rctx->ev_sigs);
- res = reply_refreshes_reveal_success (connection,
- num_fresh_coins,
- rctx->ev_sigs);
- break;
+ GNUNET_break (0);
+ TEH_plugin->rollback (TEH_plugin->cls);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ NULL);
+ goto cleanup;
}
- /* If we get here, the final transaction failed, possibly
- due to a conflict between the pre-flight and us persisting
- the result, so we go again. */
- } /* end for (retries...) */
-
+ TEH_plugin->rollback (TEH_plugin->cls);
+ }
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ GNUNET_break (0);
+ TEH_plugin->rollback (TEH_plugin->cls);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ NULL);
+ goto cleanup;
+ }
+ /* Generate final (positive) response */
+ ret = reply_refreshes_reveal_success (connection,
+ num_fresh_coins,
+ rrcs);
cleanup:
- GNUNET_break (MHD_NO != res);
+ GNUNET_break (MHD_NO != ret);
/* free resources */
- if (NULL != rctx->ev_sigs)
+ for (unsigned int i = 0; i<num_fresh_coins; i++)
{
- for (unsigned int i = 0; i<num_fresh_coins; i++)
- if (NULL != rctx->ev_sigs[i].rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (rctx->ev_sigs[i].rsa_signature);
- GNUNET_free (rctx->ev_sigs);
- rctx->ev_sigs = NULL; /* just to be safe... */
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
+ struct TALER_ExchangeWithdrawValues *alg_values
+ = &rrcs[i].exchange_vals;
+
+ GNUNET_free (alg_values->blinding_inputs);
+ TALER_blinded_denom_sig_free (&rrc->coin_sig);
+ TALER_blinded_planchet_free (&rrc->blinded_planchet);
}
- for (unsigned int i = 0; i<num_fresh_coins; i++)
- GNUNET_free_non_null (rcds[i].coin_ev);
- return res;
+ return ret;
}
@@ -801,34 +881,42 @@ cleanup:
* revealed information is valid then returns the signed refreshed
* coins.
*
+ * If the denomination has age restriction support, the array of EDDSA public
+ * keys, one for each age group that was activated during the withdrawal
+ * by the parent/ward, must be provided in old_age_commitment. The hash of
+ * this array must be the same as the h_age_commitment of the persisted reveal
+ * request.
+ *
* @param connection the MHD connection to handle
* @param rctx context for the operation, partially built at this time
* @param tp_json private transfer keys in JSON format
* @param link_sigs_json link signatures in JSON format
* @param new_denoms_h_json requests for fresh coins to be created
+ * @param old_age_commitment_json array of EDDSA public keys in JSON, used for age restriction, maybe NULL
* @param coin_evs envelopes of gamma-selected coins to be signed
* @return MHD result code
*/
-static int
+static MHD_RESULT
handle_refreshes_reveal_json (struct MHD_Connection *connection,
struct RevealContext *rctx,
const json_t *tp_json,
const json_t *link_sigs_json,
const json_t *new_denoms_h_json,
+ const json_t *old_age_commitment_json,
const json_t *coin_evs)
{
unsigned int num_fresh_coins = json_array_size (new_denoms_h_json);
unsigned int num_tprivs = json_array_size (tp_json);
GNUNET_assert (num_tprivs == TALER_CNC_KAPPA - 1); /* checked just earlier */
- if ( (num_fresh_coins >= MAX_FRESH_COINS) ||
+ if ( (num_fresh_coins >= TALER_MAX_FRESH_COINS) ||
(0 == num_fresh_coins) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_REVEAL_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE,
- "new_denoms_h");
+ TALER_EC_EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE,
+ NULL);
}
if (json_array_size (new_denoms_h_json) !=
@@ -837,7 +925,7 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISMATCH,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISMATCH,
"new_denoms/coin_evs");
}
if (json_array_size (new_denoms_h_json) !=
@@ -846,18 +934,32 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISMATCH,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISMATCH,
"new_denoms/link_sigs");
}
+ /* Sanity check of age commitment: If it was provided, it _must_ be an array
+ * of the size the # of age groups */
+ if (NULL != old_age_commitment_json
+ && TEH_age_restriction_config.num_groups !=
+ json_array_size (old_age_commitment_json))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
+ "old_age_commitment");
+ }
+
/* Parse transfer private keys array */
for (unsigned int i = 0; i<num_tprivs; i++)
{
struct GNUNET_JSON_Specification trans_spec[] = {
- GNUNET_JSON_spec_fixed_auto (NULL, &rctx->transfer_privs[i]),
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &rctx->transfer_privs[i]),
GNUNET_JSON_spec_end ()
};
- int res;
+ enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_array (connection,
tp_json,
@@ -868,67 +970,48 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
- {
- struct TEH_KS_StateHandle *key_state;
- int ret;
-
- key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
- if (NULL == key_state)
- {
- TALER_LOG_ERROR ("Lacking keys to operate\n");
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_REVEAL_KEYS_MISSING,
- "exchange lacks keys");
- }
- ret = resolve_refreshes_reveal_denominations (key_state,
- connection,
- rctx,
- link_sigs_json,
- new_denoms_h_json,
- coin_evs);
- TEH_KS_release (key_state);
- return ret;
- }
+ return resolve_refreshes_reveal_denominations (connection,
+ rctx,
+ link_sigs_json,
+ new_denoms_h_json,
+ old_age_commitment_json,
+ coin_evs);
}
-/**
- * Handle a "/refreshes/$RCH/reveal" request. This time, the client reveals the
- * private transfer keys except for the cut-and-choose value returned from
- * "/coins/$COIN_PUB/melt". This function parses the revealed keys and secrets and
- * ultimately passes everything to resolve_refreshes_reveal_denominations()
- * which will verify that the revealed information is valid then runs the
- * transaction in refreshes_reveal_transaction() and finally returns the signed
- * refreshed coins.
- *
- * @param rh context of the handler
- * @param connection MHD request handle
- * @param root uploaded JSON data
- * @param args array of additional options (length: 2, session hash and the string "reveal")
- * @return MHD result code
- */
-int
-TEH_handler_reveal (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+MHD_RESULT
+TEH_handler_reveal (struct TEH_RequestContext *rc,
const json_t *root,
const char *const args[2])
{
- json_t *coin_evs;
- json_t *transfer_privs;
- json_t *link_sigs;
- json_t *new_denoms_h;
+ const json_t *coin_evs;
+ const json_t *transfer_privs;
+ const json_t *link_sigs;
+ const json_t *new_denoms_h;
+ const json_t *old_age_commitment;
struct RevealContext rctx;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("transfer_pub", &rctx.gamma_tp),
- GNUNET_JSON_spec_json ("transfer_privs", &transfer_privs),
- GNUNET_JSON_spec_json ("link_sigs", &link_sigs),
- GNUNET_JSON_spec_json ("coin_evs", &coin_evs),
- GNUNET_JSON_spec_json ("new_denoms_h", &new_denoms_h),
+ GNUNET_JSON_spec_fixed_auto ("transfer_pub",
+ &rctx.gamma_tp),
+ GNUNET_JSON_spec_array_const ("transfer_privs",
+ &transfer_privs),
+ GNUNET_JSON_spec_array_const ("link_sigs",
+ &link_sigs),
+ GNUNET_JSON_spec_array_const ("coin_evs",
+ &coin_evs),
+ GNUNET_JSON_spec_array_const ("new_denoms_h",
+ &new_denoms_h),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("old_age_commitment",
+ &old_age_commitment),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("rms",
+ &rctx.rms),
+ &rctx.no_rms),
GNUNET_JSON_spec_end ()
};
- (void) rh;
memset (&rctx,
0,
sizeof (rctx));
@@ -939,25 +1022,25 @@ TEH_handler_reveal (const struct TEH_RequestHandler *rh,
sizeof (rctx.rc)))
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
+ return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_REVEAL_INVALID_RCH,
- "refresh commitment hash malformed");
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_INVALID_RCH,
+ args[0]);
}
if (0 != strcmp (args[1],
"reveal"))
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
+ return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_OPERATION_INVALID,
- "expected 'reveal' operation");
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_OPERATION_INVALID,
+ args[1]);
}
{
- int res;
+ enum GNUNET_GenericReturnValue res;
- res = TALER_MHD_parse_json_data (connection,
+ res = TALER_MHD_parse_json_data (rc->connection,
root,
spec);
if (GNUNET_OK != res)
@@ -971,26 +1054,20 @@ TEH_handler_reveal (const struct TEH_RequestHandler *rh,
/* Note we do +1 as 1 row (cut-and-choose!) is missing! */
if (TALER_CNC_KAPPA != json_array_size (transfer_privs) + 1)
{
- GNUNET_JSON_parse_free (spec);
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
+ return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID,
- "transfer_privs");
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID,
+ NULL);
}
- {
- int res;
-
- res = handle_refreshes_reveal_json (connection,
- &rctx,
- transfer_privs,
- link_sigs,
- new_denoms_h,
- coin_evs);
- GNUNET_JSON_parse_free (spec);
- return res;
- }
+ return handle_refreshes_reveal_json (rc->connection,
+ &rctx,
+ transfer_privs,
+ link_sigs,
+ new_denoms_h,
+ old_age_commitment,
+ coin_evs);
}
diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.h b/src/exchange/taler-exchange-httpd_refreshes_reveal.h
index 076ec52d2..30716995a 100644
--- a/src/exchange/taler-exchange-httpd_refreshes_reveal.h
+++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2017 Taler Systems SA
+ Copyright (C) 2014-2017, 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
@@ -37,15 +37,13 @@
* transaction in refresh_reveal_transaction() and finally returns the signed
* refreshed coins.
*
- * @param rh context of the handler
- * @param connection MHD request handle
+ * @param rc request context
* @param root uploaded JSON data
* @param args array of additional options (length: 2, session hash and the string "reveal")
* @return MHD result code
*/
-int
-TEH_handler_reveal (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+MHD_RESULT
+TEH_handler_reveal (struct TEH_RequestContext *rc,
const json_t *root,
const char *const args[2]);
diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c
index 9fd9575f9..b8bcf7c60 100644
--- a/src/exchange/taler-exchange-httpd_refund.c
+++ b/src/exchange/taler-exchange-httpd_refund.c
@@ -32,7 +32,7 @@
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_refund.h"
#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keystate.h"
+#include "taler-exchange-httpd_keys.h"
/**
@@ -43,46 +43,63 @@
* @param refund details about the successful refund
* @return MHD result code
*/
-static int
+static MHD_RESULT
reply_refund_success (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_EXCHANGEDB_RefundListEntry *refund)
{
struct TALER_ExchangePublicKeyP pub;
struct TALER_ExchangeSignatureP sig;
- struct TALER_RefundConfirmationPS rc = {
- .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND),
- .purpose.size = htonl (sizeof (rc)),
- .h_contract_terms = refund->h_contract_terms,
- .coin_pub = *coin_pub,
- .merchant = refund->merchant_pub,
- .rtransaction_id = GNUNET_htonll (refund->rtransaction_id)
- };
-
- TALER_amount_hton (&rc.refund_amount,
- &refund->refund_amount);
- TALER_amount_hton (&rc.refund_fee,
- &refund->refund_fee);
- if (GNUNET_OK !=
- TEH_KS_sign (&rc.purpose,
- &pub,
- &sig))
+ enum TALER_ErrorCode ec;
+
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_refund_confirmation_sign (
+ &TEH_keys_exchange_sign_,
+ &refund->h_contract_terms,
+ coin_pub,
+ &refund->merchant_pub,
+ refund->rtransaction_id,
+ &refund->refund_amount,
+ &pub,
+ &sig)))
{
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_BAD_CONFIGURATION,
- "no online signing key");
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
}
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:s, s:o, s:o}",
- "status", "REFUND_OK",
- "sig", GNUNET_JSON_from_data_auto (&sig),
- "pub", GNUNET_JSON_from_data_auto (&pub));
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub));
}
/**
+ * Closure for refund_transaction().
+ */
+struct RefundContext
+{
+ /**
+ * Details about the deposit operation.
+ */
+ const struct TALER_EXCHANGEDB_Refund *refund;
+
+ /**
+ * Deposit fee of the coin.
+ */
+ struct TALER_Amount deposit_fee;
+
+ /**
+ * Unique ID of the coin in known_coins.
+ */
+ uint64_t known_coin_id;
+};
+
+
+/**
* Execute a "/refund" transaction. Returns a confirmation that the
* refund was successful, or a failure if we are not aware of a
* matching /deposit or if it is too late to do the refund.
@@ -95,7 +112,6 @@ reply_refund_success (struct MHD_Connection *connection,
*
* @param cls closure with a `const struct TALER_EXCHANGEDB_Refund *`
* @param connection MHD request which triggered the transaction
- * @param session database session to use
* @param[out] mhd_ret set to MHD response status for @a connection,
* if transaction failed (!)
* @return transaction status
@@ -103,231 +119,70 @@ reply_refund_success (struct MHD_Connection *connection,
static enum GNUNET_DB_QueryStatus
refund_transaction (void *cls,
struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
+ MHD_RESULT *mhd_ret)
{
- const struct TALER_EXCHANGEDB_Refund *refund = cls;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- const struct TALER_EXCHANGEDB_DepositListEntry *dep;
- const struct TALER_EXCHANGEDB_RefundListEntry *ref;
+ struct RefundContext *rctx = cls;
+ const struct TALER_EXCHANGEDB_Refund *refund = rctx->refund;
enum GNUNET_DB_QueryStatus qs;
- int deposit_found;
- int refund_found;
+ bool not_found;
+ bool refund_ok;
+ bool conflict;
+ bool gone;
- tl = NULL;
- qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
- session,
- &refund->coin.coin_pub,
- GNUNET_NO,
- &tl);
+ /* Finally, store new refund data */
+ qs = TEH_plugin->do_refund (TEH_plugin->cls,
+ refund,
+ &rctx->deposit_fee,
+ rctx->known_coin_id,
+ &not_found,
+ &refund_ok,
+ &gone,
+ &conflict);
if (0 > qs)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_REFUND_COIN_NOT_FOUND,
- "database transaction failure");
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "do refund");
return qs;
}
- dep = NULL;
- ref = NULL;
- deposit_found = GNUNET_NO;
- refund_found = GNUNET_NO;
- for (struct TALER_EXCHANGEDB_TransactionList *tlp = tl;
- NULL != tlp;
- tlp = tlp->next)
- {
- switch (tlp->type)
- {
- case TALER_EXCHANGEDB_TT_DEPOSIT:
- if (GNUNET_NO == deposit_found)
- {
- if ( (0 == memcmp (&tlp->details.deposit->merchant_pub,
- &refund->details.merchant_pub,
- sizeof (struct TALER_MerchantPublicKeyP))) &&
- (0 == memcmp (&tlp->details.deposit->h_contract_terms,
- &refund->details.h_contract_terms,
- sizeof (struct GNUNET_HashCode))) )
- {
- dep = tlp->details.deposit;
- deposit_found = GNUNET_YES;
- break;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_MELT:
- /* Melts cannot be refunded, ignore here */
- break;
- case TALER_EXCHANGEDB_TT_REFUND:
- if (GNUNET_NO == refund_found)
- {
- /* First, check if existing refund request is identical */
- if ( (0 == memcmp (&tlp->details.refund->merchant_pub,
- &refund->details.merchant_pub,
- sizeof (struct TALER_MerchantPublicKeyP))) &&
- (0 == memcmp (&tlp->details.refund->h_contract_terms,
- &refund->details.h_contract_terms,
- sizeof (struct GNUNET_HashCode))) &&
- (tlp->details.refund->rtransaction_id ==
- refund->details.rtransaction_id) )
- {
- ref = tlp->details.refund;
- refund_found = GNUNET_YES;
- break;
- }
- /* Second, check if existing refund request conflicts */
- if ( (0 == memcmp (&tlp->details.refund->merchant_pub,
- &refund->details.merchant_pub,
- sizeof (struct TALER_MerchantPublicKeyP))) &&
- (0 == memcmp (&tlp->details.refund->h_contract_terms,
- &refund->details.h_contract_terms,
- sizeof (struct GNUNET_HashCode))) &&
- (tlp->details.refund->rtransaction_id !=
- refund->details.rtransaction_id) )
- {
- refund_found = GNUNET_SYSERR;
- /* NOTE: Alternatively we could total up all existing
- refunds and check if the sum still permits the
- refund requested (thus allowing multiple, partial
- refunds). Fow now, we keep it simple. */
- break;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
- /* Recoups cannot be refunded, ignore here */
- break;
- case TALER_EXCHANGEDB_TT_RECOUP:
- /* Recoups cannot be refunded, ignore here */
- break;
- case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
- /* Recoups cannot be refunded, ignore here */
- break;
- }
- }
- /* handle if deposit was NOT found */
- if (GNUNET_NO == deposit_found)
+
+ if (gone)
{
- TALER_LOG_WARNING ("Deposit to /refund was not found\n");
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
*mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_REFUND_DEPOSIT_NOT_FOUND,
- "deposit unknown");
+ MHD_HTTP_GONE,
+ TALER_EC_EXCHANGE_REFUND_MERCHANT_ALREADY_PAID,
+ NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- /* handle if conflicting refund found */
- if (GNUNET_SYSERR == refund_found)
+ if (conflict)
{
- *mhd_ret = TALER_MHD_reply_json_pack (
+ GNUNET_break_op (0);
+ *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
connection,
- MHD_HTTP_CONFLICT,
- "{s:s, s:I, s:o}",
- "hint", "conflicting refund",
- "code", (json_int_t) TALER_EC_REFUND_CONFLICT,
- "history", TEH_RESPONSE_compile_transaction_history (
- &refund->coin.coin_pub,
- tl));
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
+ TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT,
+ &refund->coin.denom_pub_hash,
+ &refund->coin.coin_pub);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- /* handle if identical refund found */
- if (GNUNET_YES == refund_found)
+ if (not_found)
{
- /* /refund already done, simply re-transmit confirmation */
- *mhd_ret = reply_refund_success (connection,
- &refund->coin.coin_pub,
- ref);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- /* check currency is compatible */
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&refund->details.refund_amount,
- &dep->amount_with_fee)) ||
- (GNUNET_YES !=
- TALER_amount_cmp_currency (&refund->details.refund_fee,
- &dep->deposit_fee)) )
- {
- GNUNET_break_op (0); /* currency mismatch */
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_PRECONDITION_FAILED,
- TALER_EC_REFUND_CURRENCY_MISMATCH,
- "currencies involved do not match");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- /* check if we already send the money for the /deposit */
- qs = TEH_plugin->test_deposit_done (TEH_plugin->cls,
- session,
- &refund->coin.coin_pub,
- &dep->merchant_pub,
- &dep->h_contract_terms,
- &dep->h_wire);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- /* Internal error, we first had the deposit in the history,
- but now it is gone? */
- GNUNET_break (0);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_REFUND_DB_INCONSISTENT,
- "database inconsistent (deposit data became inaccessible during transaction)");
- return qs;
- }
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs; /* go and retry */
-
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- /* money was already transferred to merchant, can no longer refund */
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
*mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_GONE,
- TALER_EC_REFUND_MERCHANT_ALREADY_PAID,
- "money already sent to merchant");
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_REFUND_DEPOSIT_NOT_FOUND,
+ NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
-
- /* check refund amount is sufficiently low */
- if (1 == TALER_amount_cmp (&refund->details.refund_amount,
- &dep->amount_with_fee) )
+ if (! refund_ok)
{
- GNUNET_break_op (0); /* cannot refund more than original value */
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_PRECONDITION_FAILED,
- TALER_EC_REFUND_INSUFFICIENT_FUNDS,
- "refund requested exceeds original value");
+ *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT,
+ &refund->coin.denom_pub_hash,
+ &refund->coin.coin_pub);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
-
- /* Finally, store new refund data */
- qs = TEH_plugin->insert_refund (TEH_plugin->cls,
- session,
- refund);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- TALER_LOG_WARNING ("Failed to store /refund information in database\n");
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_REFUND_STORE_DB_ERROR,
- "could not persist store information");
- return qs;
- }
- /* Success or soft failure */
return qs;
}
@@ -339,42 +194,31 @@ refund_transaction (void *cls,
* the fee structure, so this is not done here.
*
* @param connection the MHD connection to handle
- * @param refund information about the refund
+ * @param[in,out] refund information about the refund
* @return MHD result code
*/
-static int
+static MHD_RESULT
verify_and_execute_refund (struct MHD_Connection *connection,
- const struct TALER_EXCHANGEDB_Refund *refund)
+ struct TALER_EXCHANGEDB_Refund *refund)
{
- struct GNUNET_HashCode denom_hash;
- struct TALER_Amount expect_fee;
+ struct RefundContext rctx = {
+ .refund = refund
+ };
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_merchant_refund_verify (&refund->coin.coin_pub,
+ &refund->details.h_contract_terms,
+ refund->details.rtransaction_id,
+ &refund->details.refund_amount,
+ &refund->details.merchant_pub,
+ &refund->details.merchant_sig))
{
- struct TALER_RefundRequestPS rr = {
- .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
- .purpose.size = htonl (sizeof (rr)),
- .h_contract_terms = refund->details.h_contract_terms,
- .coin_pub = refund->coin.coin_pub,
- .merchant = refund->details.merchant_pub,
- .rtransaction_id = GNUNET_htonll (refund->details.rtransaction_id)
- };
-
- TALER_amount_hton (&rr.refund_amount,
- &refund->details.refund_amount);
- TALER_amount_hton (&rr.refund_fee,
- &refund->details.refund_fee);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
- &rr.purpose,
- &refund->details.merchant_sig.eddsa_sig,
- &refund->details.merchant_pub.eddsa_pub))
- {
- TALER_LOG_WARNING ("Invalid signature on refund request\n");
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_REFUND_MERCHANT_SIGNATURE_INVALID,
- "merchant_sig");
- }
+ TALER_LOG_WARNING ("Invalid signature on refund request\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_REFUND_MERCHANT_SIGNATURE_INVALID,
+ NULL);
}
/* Fetch the coin's denomination (hash) */
@@ -382,100 +226,57 @@ verify_and_execute_refund (struct MHD_Connection *connection,
enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->get_coin_denomination (TEH_plugin->cls,
- NULL,
&refund->coin.coin_pub,
- &denom_hash);
+ &rctx.known_coin_id,
+ &refund->coin.denom_pub_hash);
if (0 > qs)
{
+ MHD_RESULT res;
+ char *dhs;
+
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_REFUND_COIN_NOT_FOUND,
- "denomination of coin to be refunded not found in DB");
+ dhs = GNUNET_STRINGS_data_to_string_alloc (
+ &refund->coin.denom_pub_hash,
+ sizeof (refund->coin.denom_pub_hash));
+ res = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_REFUND_COIN_NOT_FOUND,
+ dhs);
+ GNUNET_free (dhs);
+ return res;
}
}
{
- struct TEH_KS_StateHandle *key_state;
-
- key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
- if (NULL == key_state)
- {
- TALER_LOG_ERROR ("Lacking keys to operate\n");
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_BAD_CONFIGURATION,
- "no keys");
- }
/* Obtain information about the coin's denomination! */
- {
- struct TALER_EXCHANGEDB_DenominationKey *dki;
- unsigned int hc;
- enum TALER_ErrorCode ec;
-
- dki = TEH_KS_denomination_key_lookup_by_hash (key_state,
- &denom_hash,
- TEH_KS_DKU_DEPOSIT,
- &ec,
- &hc);
- if (NULL == dki)
- {
- /* DKI not found, but we do have a coin with this DK in our database;
- not good... */
- GNUNET_break (0);
- TEH_KS_release (key_state);
- return TALER_MHD_reply_with_error (connection,
- hc,
- ec,
- "denomination not found, but coin known");
- }
- TALER_amount_ntoh (&expect_fee,
- &dki->issue.properties.fee_refund);
- }
- TEH_KS_release (key_state);
- }
+ struct TEH_DenominationKey *dk;
+ MHD_RESULT mret;
- /* Check refund fee matches fee of denomination key! */
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&expect_fee,
- &refund->details.refund_fee) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_REFUND_FEE_CURRENCY_MISMATCH,
- "refund_fee");
- }
- {
- int fee_cmp;
-
- fee_cmp = TALER_amount_cmp (&refund->details.refund_fee,
- &expect_fee);
- if (-1 == fee_cmp)
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_REFUND_FEE_TOO_LOW,
- "refund_fee");
- }
- if (1 == fee_cmp)
+ dk = TEH_keys_denomination_by_hash (&refund->coin.denom_pub_hash,
+ connection,
+ &mret);
+ if (NULL == dk)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Refund fee proposed by merchant is higher than necessary.\n");
+ /* DKI not found, but we do have a coin with this DK in our database;
+ not good... */
+ GNUNET_break (0);
+ return mret;
}
+ refund->details.refund_fee = dk->meta.fees.refund;
+ rctx.deposit_fee = dk->meta.fees.deposit;
}
-
/* Finally run the actual transaction logic */
{
- int mhd_ret;
+ MHD_RESULT mhd_ret;
if (GNUNET_OK !=
TEH_DB_run_transaction (connection,
"run refund",
+ TEH_MT_REQUEST_OTHER,
&mhd_ret,
&refund_transaction,
- (void *) refund))
+ &rctx))
{
return mhd_ret;
}
@@ -497,27 +298,32 @@ verify_and_execute_refund (struct MHD_Connection *connection,
* @param root uploaded JSON data
* @return MHD result code
*/
-int
+MHD_RESULT
TEH_handler_refund (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const json_t *root)
{
- struct TALER_EXCHANGEDB_Refund refund;
+ struct TALER_EXCHANGEDB_Refund refund = {
+ .details.refund_fee.currency = {0} /* set to invalid, just to be sure */
+ };
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("refund_amount", &refund.details.refund_amount),
- TALER_JSON_spec_amount ("refund_fee", &refund.details.refund_fee),
+ TALER_JSON_spec_amount ("refund_amount",
+ TEH_currency,
+ &refund.details.refund_amount),
GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
&refund.details.h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub", &refund.details.merchant_pub),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &refund.details.merchant_pub),
GNUNET_JSON_spec_uint64 ("rtransaction_id",
&refund.details.rtransaction_id),
- GNUNET_JSON_spec_fixed_auto ("merchant_sig", &refund.details.merchant_sig),
+ GNUNET_JSON_spec_fixed_auto ("merchant_sig",
+ &refund.details.merchant_sig),
GNUNET_JSON_spec_end ()
};
refund.coin.coin_pub = *coin_pub;
{
- int res;
+ enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_data (connection,
root,
@@ -527,29 +333,8 @@ TEH_handler_refund (struct MHD_Connection *connection,
if (GNUNET_NO == res)
return MHD_YES; /* failure */
}
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&refund.details.refund_amount,
- &refund.details.refund_fee) )
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_REFUND_FEE_CURRENCY_MISMATCH,
- "refund_amount or refund_fee");
- }
- if (-1 == TALER_amount_cmp (&refund.details.refund_amount,
- &refund.details.refund_fee) )
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_REFUND_FEE_ABOVE_AMOUNT,
- "refund_amount");
- }
{
- int res;
+ MHD_RESULT res;
res = verify_and_execute_refund (connection,
&refund);
diff --git a/src/exchange/taler-exchange-httpd_refund.h b/src/exchange/taler-exchange-httpd_refund.h
index ad6387d67..92480b775 100644
--- a/src/exchange/taler-exchange-httpd_refund.h
+++ b/src/exchange/taler-exchange-httpd_refund.h
@@ -38,8 +38,8 @@
* @param coin_pub public key of the coin
* @param root uploaded JSON data
* @return MHD result code
- */
-int
+ */
+MHD_RESULT
TEH_handler_refund (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const json_t *root);
diff --git a/src/exchange/taler-exchange-httpd_reserves_attest.c b/src/exchange/taler-exchange-httpd_reserves_attest.c
new file mode 100644
index 000000000..7bbebaad7
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_attest.c
@@ -0,0 +1,385 @@
+/*
+ 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 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_reserves_attest.c
+ * @brief Handle /reserves/$RESERVE_PUB/attest requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_dbevents.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_reserves_attest.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * How far do we allow a client's time to be off when
+ * checking the request timestamp?
+ */
+#define TIMESTAMP_TOLERANCE \
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
+
+
+/**
+ * Closure for #reserve_attest_transaction.
+ */
+struct ReserveAttestContext
+{
+ /**
+ * Public key of the reserve the inquiry is about.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Hash of the payto URI of this reserve.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Timestamp of the request.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Expiration time for the attestation.
+ */
+ struct GNUNET_TIME_Timestamp etime;
+
+ /**
+ * List of requested details.
+ */
+ const json_t *details;
+
+ /**
+ * Client signature approving the request.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Attributes we are affirming. JSON object.
+ */
+ json_t *json_attest;
+
+ /**
+ * Database error codes encountered.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+
+ /**
+ * Set to true if we did not find the reserve.
+ */
+ bool not_found;
+
+};
+
+
+/**
+ * Send reserve attest to client.
+ *
+ * @param connection connection to the client
+ * @param rhc reserve attest to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_reserve_attest_success (struct MHD_Connection *connection,
+ const struct ReserveAttestContext *rhc)
+{
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ enum TALER_ErrorCode ec;
+ struct GNUNET_TIME_Timestamp now;
+
+ if (NULL == rhc->json_attest)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+ NULL);
+ }
+ now = GNUNET_TIME_timestamp_get ();
+ ec = TALER_exchange_online_reserve_attest_details_sign (
+ &TEH_keys_exchange_sign_,
+ now,
+ rhc->etime,
+ &rhc->reserve_pub,
+ rhc->json_attest,
+ &exchange_pub,
+ &exchange_sig);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+ now),
+ GNUNET_JSON_pack_timestamp ("expiration_time",
+ rhc->etime),
+ GNUNET_JSON_pack_object_steal ("attributes",
+ rhc->json_attest));
+}
+
+
+/**
+ * Function called with information about all applicable
+ * legitimization processes for the given user. Finds the
+ * available attributes and merges them into our result
+ * set based on the details requested by the client.
+ *
+ * @param cls our `struct ReserveAttestContext *`
+ * @param h_payto account for which the attribute data is stored
+ * @param provider_section provider that must be checked
+ * @param 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,
+ 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;
+ json_t *attrs;
+ json_t *val;
+ const char *name;
+ bool match = false;
+
+ 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)
+ {
+ 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))
+ {
+ requested = true;
+ break;
+ }
+ }
+ if (! requested)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Skipping attribute `%s': not requested\n",
+ name);
+ continue;
+ }
+ match = true;
+ GNUNET_assert (0 ==
+ json_object_set (rsc->json_attest, /* NOT set_new! */
+ name,
+ val));
+ }
+ json_decref (attrs);
+ if (! match)
+ return;
+ rsc->etime = GNUNET_TIME_timestamp_min (expiration_time,
+ rsc->etime);
+}
+
+
+/**
+ * Function implementing /reserves/$RID/attest transaction. Given the public
+ * key of a reserve, return the associated transaction attest. 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 ReserveAttestContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!); unused
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+reserve_attest_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct ReserveAttestContext *rsc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ rsc->json_attest = json_object ();
+ GNUNET_assert (NULL != rsc->json_attest);
+ qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
+ &rsc->h_payto,
+ &kyc_process_cb,
+ rsc);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ *mhd_ret
+ = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_kyc_attributes");
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ rsc->not_found = true;
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ rsc->not_found = false;
+ break;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_attest (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[1])
+{
+ struct ReserveAttestContext rsc = {
+ .etime = GNUNET_TIME_UNIT_FOREVER_TS
+ };
+ MHD_RESULT mhd_ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("request_timestamp",
+ &rsc.timestamp),
+ GNUNET_JSON_spec_array_const ("details",
+ &rsc.details),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rsc.reserve_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_TIME_Timestamp now;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &rsc.reserve_pub,
+ sizeof (rsc.reserve_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
+ args[0]);
+ }
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+ now = GNUNET_TIME_timestamp_get ();
+ if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
+ rsc.timestamp.abs_time,
+ TIMESTAMP_TOLERANCE))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
+ NULL);
+ }
+
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_attest_request_verify (rsc.timestamp,
+ rsc.details,
+ &rsc.reserve_pub,
+ &rsc.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RESERVES_ATTEST_BAD_SIGNATURE,
+ NULL);
+ }
+
+ {
+ char *payto_uri;
+
+ payto_uri = TALER_reserve_make_payto (TEH_base_url,
+ &rsc.reserve_pub);
+ TALER_payto_hash (payto_uri,
+ &rsc.h_payto);
+ GNUNET_free (payto_uri);
+ }
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (rc->connection,
+ "post reserve attest",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &reserve_attest_transaction,
+ &rsc))
+ {
+ return mhd_ret;
+ }
+ if (rsc.not_found)
+ {
+ json_decref (rsc.json_attest);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+ args[0]);
+ }
+ return reply_reserve_attest_success (rc->connection,
+ &rsc);
+}
+
+
+/* end of taler-exchange-httpd_reserves_attest.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_attest.h b/src/exchange/taler-exchange-httpd_reserves_attest.h
new file mode 100644
index 000000000..66bbdf712
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_attest.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_reserves_attest.h
+ * @brief Handle /reserves/$RESERVE_PUB/attest requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_RESERVES_ATTEST_H
+#define TALER_EXCHANGE_HTTPD_RESERVES_ATTEST_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a POST "/reserves-attest/$RID" request.
+ *
+ * @param rc request context
+ * @param root uploaded body from the client
+ * @param args args[0] has public key of the reserve
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_attest (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[1]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_close.c b/src/exchange/taler-exchange-httpd_reserves_close.c
new file mode 100644
index 000000000..bbf234428
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_close.c
@@ -0,0 +1,448 @@
+/*
+ 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 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_reserves_close.c
+ * @brief Handle /reserves/$RESERVE_PUB/close requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_kyclogic_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_json_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_reserves_close.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * How far do we allow a client's time to be off when
+ * checking the request timestamp?
+ */
+#define TIMESTAMP_TOLERANCE \
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
+
+
+/**
+ * Closure for #reserve_close_transaction.
+ */
+struct ReserveCloseContext
+{
+ /**
+ * Public key of the reserve the inquiry is about.
+ */
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+
+ /**
+ * Timestamp of the request.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Client signature approving the request.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Amount that will be wired (after closing fees).
+ */
+ struct TALER_Amount wire_amount;
+
+ /**
+ * Current balance of the reserve.
+ */
+ struct TALER_Amount balance;
+
+ /**
+ * Where to wire the funds, may be NULL.
+ */
+ const char *payto_uri;
+
+ /**
+ * Hash of the @e payto_uri, if given (otherwise zero).
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * KYC status for the request.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Hash of the payto-URI that was used for the KYC decision.
+ */
+ struct TALER_PaytoHashP kyc_payto;
+
+ /**
+ * Query status from the amount_it() helper function.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Send reserve close to client.
+ *
+ * @param connection connection to the client
+ * @param rhc reserve close to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_reserve_close_success (struct MHD_Connection *connection,
+ const struct ReserveCloseContext *rhc)
+{
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("wire_amount",
+ &rhc->wire_amount));
+}
+
+
+/**
+ * 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
+amount_it (void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ struct ReserveCloseContext *rcc = cls;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = cb (cb_cls,
+ &rcc->balance,
+ GNUNET_TIME_absolute_get ());
+ GNUNET_break (GNUNET_SYSERR != ret);
+ if (GNUNET_OK != ret)
+ return;
+ rcc->qs
+ = TEH_plugin->iterate_reserve_close_info (
+ TEH_plugin->cls,
+ &rcc->kyc_payto,
+ limit,
+ cb,
+ cb_cls);
+}
+
+
+/**
+ * Function implementing /reserves/$RID/close transaction. Given the public
+ * key of a reserve, return the associated transaction close. 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 ReserveCloseContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!); unused
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+reserve_close_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct ReserveCloseContext *rcc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ char *payto_uri = NULL;
+ const struct TALER_WireFeeSet *wf;
+
+ qs = TEH_plugin->select_reserve_close_info (
+ TEH_plugin->cls,
+ rcc->reserve_pub,
+ &rcc->balance,
+ &payto_uri);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ *mhd_ret
+ = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_reserve_close_info");
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *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;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+
+ if ( (NULL == rcc->payto_uri) &&
+ (NULL == payto_uri) )
+ {
+ *mhd_ret
+ = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_RESERVES_CLOSE_NO_TARGET_ACCOUNT,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ if ( (NULL != rcc->payto_uri) &&
+ ( (NULL == payto_uri) ||
+ (0 != strcmp (payto_uri,
+ rcc->payto_uri)) ) )
+ {
+ /* KYC check may be needed: we're not returning
+ the money to the account that funded the reserve
+ in the first place. */
+ char *kyc_needed;
+
+ TALER_payto_hash (rcc->payto_uri,
+ &rcc->kyc_payto);
+ rcc->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ qs = TALER_KYCLOGIC_kyc_test_required (
+ TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE,
+ &rcc->kyc_payto,
+ TEH_plugin->select_satisfied_kyc_processes,
+ TEH_plugin->cls,
+ &amount_it,
+ rcc,
+ &kyc_needed);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret
+ = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "iterate_reserve_close_info");
+ return qs;
+ }
+ if (rcc->qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == rcc->qs)
+ return rcc->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 (NULL != kyc_needed)
+ {
+ rcc->kyc.ok = false;
+ qs = TEH_plugin->insert_kyc_requirement_for_account (
+ TEH_plugin->cls,
+ kyc_needed,
+ &rcc->kyc_payto,
+ rcc->reserve_pub,
+ &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;
+ if (NULL == rcc->payto_uri)
+ rcc->payto_uri = payto_uri;
+
+ {
+ char *method;
+
+ method = TALER_payto_get_method (rcc->payto_uri);
+ wf = TEH_wire_fees_by_time (rcc->timestamp,
+ method);
+ if (NULL == wf)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED,
+ method);
+ GNUNET_free (method);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_free (method);
+ }
+
+ if (0 >
+ TALER_amount_subtract (&rcc->wire_amount,
+ &rcc->balance,
+ &wf->closing))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client attempted to close reserve with insufficient balance.\n");
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &rcc->wire_amount));
+ *mhd_ret = reply_reserve_close_success (connection,
+ rcc);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ qs = TEH_plugin->insert_close_request (TEH_plugin->cls,
+ rcc->reserve_pub,
+ payto_uri,
+ &rcc->reserve_sig,
+ rcc->timestamp,
+ &rcc->balance,
+ &wf->closing);
+ GNUNET_free (payto_uri);
+ 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,
+ "insert_close_request");
+ return qs;
+ }
+ if (qs <= 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_close (struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root)
+{
+ struct ReserveCloseContext rcc = {
+ .payto_uri = NULL,
+ .reserve_pub = reserve_pub
+ };
+ MHD_RESULT mhd_ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("request_timestamp",
+ &rcc.timestamp),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &rcc.payto_uri),
+ NULL),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rcc.reserve_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+
+ {
+ struct GNUNET_TIME_Timestamp now;
+
+ now = GNUNET_TIME_timestamp_get ();
+ if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
+ rcc.timestamp.abs_time,
+ TIMESTAMP_TOLERANCE))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
+ NULL);
+ }
+ }
+
+ if (NULL != rcc.payto_uri)
+ TALER_payto_hash (rcc.payto_uri,
+ &rcc.h_payto);
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_close_verify (rcc.timestamp,
+ &rcc.h_payto,
+ reserve_pub,
+ &rcc.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RESERVES_CLOSE_BAD_SIGNATURE,
+ NULL);
+ }
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (rc->connection,
+ "reserve close",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &reserve_close_transaction,
+ &rcc))
+ {
+ return mhd_ret;
+ }
+ if (! rcc.kyc.ok)
+ return TEH_RESPONSE_reply_kyc_required (rc->connection,
+ &rcc.kyc_payto,
+ &rcc.kyc);
+
+ return reply_reserve_close_success (rc->connection,
+ &rcc);
+}
+
+
+/* end of taler-exchange-httpd_reserves_close.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_close.h b/src/exchange/taler-exchange-httpd_reserves_close.h
new file mode 100644
index 000000000..4c70b17cb
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_close.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_reserves_close.h
+ * @brief Handle /reserves/$RESERVE_PUB/close requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_RESERVES_CLOSE_H
+#define TALER_EXCHANGE_HTTPD_RESERVES_CLOSE_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a POST "/reserves/$RID/close" request.
+ *
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
+ * @param root uploaded body from the client
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_close (struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_get.c b/src/exchange/taler-exchange-httpd_reserves_get.c
index dbbb72708..0775a4c65 100644
--- a/src/exchange/taler-exchange-httpd_reserves_get.c
+++ b/src/exchange/taler-exchange-httpd_reserves_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 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
@@ -25,150 +25,239 @@
#include <jansson.h>
#include "taler_mhd_lib.h"
#include "taler_json_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_keys.h"
#include "taler-exchange-httpd_reserves_get.h"
#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keystate.h"
/**
- * Send reserve history to client.
- *
- * @param connection connection to the client
- * @param rh reserve history to return
- * @return MHD result code
+ * Reserve GET request that is long-polling.
*/
-static int
-reply_reserve_history_success (struct MHD_Connection *connection,
- const struct TALER_EXCHANGEDB_ReserveHistory *rh)
+struct ReservePoller
{
- json_t *json_balance;
- json_t *json_history;
- struct TALER_Amount balance;
+ /**
+ * Kept in a DLL.
+ */
+ struct ReservePoller *next;
- json_history = TEH_RESPONSE_compile_reserve_history (rh,
- &balance);
- if (NULL == json_history)
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_RESERVE_STATUS_DB_ERROR,
- "balance calculation failure");
- json_balance = TALER_JSON_from_amount (&balance);
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:o, s:o}",
- "balance", json_balance,
- "history", json_history);
-}
+ /**
+ * Kept in a DLL.
+ */
+ struct ReservePoller *prev;
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Our request context.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Subscription for the database event we are waiting for.
+ */
+ struct GNUNET_DB_EventHandler *eh;
+
+ /**
+ * When will this request time out?
+ */
+ struct GNUNET_TIME_Absolute timeout;
-/**
- * Closure for #reserve_history_transaction.
- */
-struct ReserveHistoryContext
-{
/**
* Public key of the reserve the inquiry is about.
*/
struct TALER_ReservePublicKeyP reserve_pub;
/**
- * History of the reserve, set in the callback.
+ * Balance of the reserve, set in the callback.
*/
- struct TALER_EXCHANGEDB_ReserveHistory *rh;
+ struct TALER_Amount balance;
+
+ /**
+ * True if we are still suspended.
+ */
+ bool suspended;
};
/**
- * 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.
+ * Head of list of requests in long polling.
+ */
+static struct ReservePoller *rp_head;
+
+/**
+ * Tail of list of requests in long polling.
+ */
+static struct ReservePoller *rp_tail;
+
+
+void
+TEH_reserves_get_cleanup ()
+{
+ for (struct ReservePoller *rp = rp_head;
+ NULL != rp;
+ rp = rp->next)
+ {
+ if (rp->suspended)
+ {
+ rp->suspended = false;
+ MHD_resume_connection (rp->connection);
+ }
+ }
+}
+
+
+/**
+ * Function called once a connection is done to
+ * clean up the `struct ReservePoller` state.
*
- * @param cls a `struct ReserveHistoryContext *`
- * @param connection MHD request which triggered the transaction
- * @param session database session to use
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!); unused
- * @return transaction status
+ * @param rc context to clean up for
*/
-static enum GNUNET_DB_QueryStatus
-reserve_history_transaction (void *cls,
- struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
+static void
+rp_cleanup (struct TEH_RequestContext *rc)
{
- struct ReserveHistoryContext *rsc = cls;
-
- (void) connection;
- (void) mhd_ret;
- return TEH_plugin->get_reserve_history (TEH_plugin->cls,
- session,
- &rsc->reserve_pub,
- &rsc->rh);
+ struct ReservePoller *rp = rc->rh_ctx;
+
+ GNUNET_assert (! rp->suspended);
+ if (NULL != rp->eh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "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);
}
/**
- * Handle a GET "/reserves/" request. Parses the
- * given "reserve_pub" in @a args (which should contain the
- * EdDSA public key of a reserve) and then respond with the
- * history of the reserve.
+ * Function called on events received from Postgres.
+ * Wakes up long pollers.
*
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param args array of additional options (length: 1, just the reserve_pub)
- * @return MHD result code
+ * @param cls the `struct TEH_RequestContext *`
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
*/
-int
-TEH_handler_reserves_get (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
- const char *const args[1])
+static void
+db_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
{
- struct ReserveHistoryContext rsc;
- int mhd_ret;
-
- (void) rh;
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &rsc.reserve_pub,
- sizeof (rsc.reserve_pub)))
+ struct ReservePoller *rp = cls;
+ struct GNUNET_AsyncScopeSave old_scope;
+
+ (void) extra;
+ (void) extra_size;
+ if (! rp->suspended)
+ return; /* might get multiple wake-up events */
+ GNUNET_async_scope_enter (&rp->rc->async_scope_id,
+ &old_scope);
+ TEH_check_invariants ();
+ rp->suspended = false;
+ MHD_resume_connection (rp->connection);
+ TALER_MHD_daemon_trigger ();
+ TEH_check_invariants ();
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+ struct ReservePoller *rp = rc->rh_ctx;
+
+ if (NULL == rp)
{
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_RESERVES_INVALID_RESERVE_PUB,
- "reserve public key malformed");
+ rp = GNUNET_new (struct ReservePoller);
+ rp->connection = rc->connection;
+ rp->rc = rc;
+ rc->rh_ctx = rp;
+ rc->rh_cleaner = &rp_cleanup;
+ GNUNET_CONTAINER_DLL_insert (rp_head,
+ rp_tail,
+ rp);
+ rp->reserve_pub = *reserve_pub;
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &rp->timeout);
+ }
+
+ 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 = rp->reserve_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting DB event listening until %s\n",
+ GNUNET_TIME_absolute2s (rp->timeout));
+ rp->eh = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (rp->timeout),
+ &rep.header,
+ &db_event_cb,
+ rp);
+ }
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ 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_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_GENERIC_RESERVE_UNKNOWN,
+ NULL);
+ }
+ 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;
+ }
}
- rsc.rh = NULL;
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "get reserve history",
- &mhd_ret,
- &reserve_history_transaction,
- &rsc))
- return mhd_ret;
-
- /* generate proper response */
- if (NULL == rsc.rh)
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_NOT_FOUND,
- "{s:s, s:s, s:I}",
- "hint", "Reserve not found",
- "parameter", "reserve_pub",
- "code",
- (json_int_t)
- TALER_EC_RESERVE_STATUS_UNKNOWN);
- mhd_ret = reply_reserve_history_success (connection,
- rsc.rh);
- TEH_plugin->free_reserve_history (TEH_plugin->cls,
- rsc.rh);
- return mhd_ret;
+ GNUNET_break (0);
+ return MHD_NO;
}
diff --git a/src/exchange/taler-exchange-httpd_reserves_get.h b/src/exchange/taler-exchange-httpd_reserves_get.h
index e59d3e031..6c453d0cd 100644
--- a/src/exchange/taler-exchange-httpd_reserves_get.h
+++ b/src/exchange/taler-exchange-httpd_reserves_get.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 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
@@ -28,19 +28,27 @@
/**
+ * Shutdown reserves-get subsystem. Resumes all
+ * suspended long-polling clients and cleans up
+ * data structures.
+ */
+void
+TEH_reserves_get_cleanup (void);
+
+
+/**
* Handle a GET "/reserves/" request. Parses the
* given "reserve_pub" in @a args (which should contain the
* EdDSA public key of a reserve) and then respond with the
* status of the reserve.
*
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param args array of additional options (length: 1, just the reserve_pub)
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
* @return MHD result code
*/
-int
-TEH_handler_reserves_get (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
- const char *const args[1]);
+MHD_RESULT
+TEH_handler_reserves_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub);
#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_get_attest.c b/src/exchange/taler-exchange-httpd_reserves_get_attest.c
new file mode 100644
index 000000000..ae983682a
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_get_attest.c
@@ -0,0 +1,232 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_reserves_get_attest.c
+ * @brief Handle GET /reserves/$RESERVE_PUB/attest requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_kyclogic_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_json_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_reserves_get_attest.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Closure for #reserve_attest_transaction.
+ */
+struct ReserveAttestContext
+{
+ /**
+ * Public key of the reserve the inquiry is about.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Hash of the payto URI of this reserve.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Available attributes.
+ */
+ json_t *attributes;
+
+ /**
+ * Set to true if we did not find the reserve.
+ */
+ bool not_found;
+};
+
+
+/**
+ * Function called with information about all applicable
+ * legitimization processes for the given user.
+ *
+ * @param cls our `struct ReserveAttestContext *`
+ * @param h_payto account for which the attribute data is stored
+ * @param provider_section provider that must be checked
+ * @param 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,
+ 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;
+ json_t *attrs;
+ json_t *val;
+ const char *name;
+
+ 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)
+ {
+ bool duplicate = false;
+ size_t idx;
+ json_t *str;
+
+ json_array_foreach (rsc->attributes, idx, str)
+ {
+ if (0 == strcmp (json_string_value (str),
+ name))
+ {
+ duplicate = true;
+ break;
+ }
+ }
+ if (duplicate)
+ continue;
+ GNUNET_assert (0 ==
+ json_array_append (rsc->attributes,
+ json_string (name)));
+ }
+}
+
+
+/**
+ * Function implementing GET /reserves/$RID/attest transaction.
+ * Execute a /reserves/ get attest. Given the public key of a reserve,
+ * return the associated transaction attest. 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 ReserveAttestContext *`
+ * @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_attest_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct ReserveAttestContext *rsc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ rsc->attributes = json_array ();
+ GNUNET_assert (NULL != rsc->attributes);
+ qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
+ &rsc->h_payto,
+ &kyc_process_cb,
+ rsc);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ *mhd_ret
+ = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "iterate_kyc_reference");
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ rsc->not_found = true;
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ rsc->not_found = false;
+ break;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc,
+ const char *const args[1])
+{
+ struct ReserveAttestContext rsc = {
+ .attributes = NULL
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &rsc.reserve_pub,
+ sizeof (rsc.reserve_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
+ args[0]);
+ }
+ {
+ char *payto_uri;
+
+ payto_uri = TALER_reserve_make_payto (TEH_base_url,
+ &rsc.reserve_pub);
+ TALER_payto_hash (payto_uri,
+ &rsc.h_payto);
+ GNUNET_free (payto_uri);
+ }
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (rc->connection,
+ "get-attestable",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &reserve_attest_transaction,
+ &rsc))
+ {
+ json_decref (rsc.attributes);
+ rsc.attributes = NULL;
+ return mhd_ret;
+ }
+ }
+ /* generate proper response */
+ 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]);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("details",
+ rsc.attributes));
+}
+
+
+/* end of taler-exchange-httpd_reserves_get_attest.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_get_attest.h b/src/exchange/taler-exchange-httpd_reserves_get_attest.h
new file mode 100644
index 000000000..8b5e3aba3
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_get_attest.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as 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_reserves_get_attest.h
+ * @brief Handle /reserves/$RESERVE_PUB GET_ATTEST requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_RESERVES_GET_ATTEST_H
+#define TALER_EXCHANGE_HTTPD_RESERVES_GET_ATTEST_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a GET "/reserves/$RID/attest" request. Parses the
+ * given "reserve_pub" in @a args (which should contain the
+ * EdDSA public key of a reserve) and then responds with the
+ * available attestations for the reserve.
+ *
+ * @param rc request context
+ * @param args array of additional options (length: 1, just the reserve_pub)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc,
+ const char *const args[1]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_history.c b/src/exchange/taler-exchange-httpd_reserves_history.c
new file mode 100644
index 000000000..056d4b0ef
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_history.c
@@ -0,0 +1,517 @@
+/*
+ This file is part of TALER
+ 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
+ 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_reserves_history.c
+ * @brief Handle /reserves/$RESERVE_PUB HISTORY requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_json_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_reserves_history.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Compile the history of a reserve into a JSON object.
+ *
+ * @param rh reserve history to JSON-ify
+ * @return json representation of the @a rh, NULL on error
+ */
+static json_t *
+compile_reserve_history (
+ const struct TALER_EXCHANGEDB_ReserveHistory *rh)
+{
+ json_t *json_history;
+
+ json_history = json_array ();
+ GNUNET_assert (NULL != json_history);
+ for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh;
+ NULL != pos;
+ pos = pos->next)
+ {
+ switch (pos->type)
+ {
+ case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
+ {
+ const struct TALER_EXCHANGEDB_BankTransfer *bank =
+ pos->details.bank;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "CREDIT"),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ bank->execution_date),
+ GNUNET_JSON_pack_string ("sender_account_url",
+ bank->sender_account_details),
+ GNUNET_JSON_pack_uint64 ("wire_reference",
+ bank->wire_reference),
+ TALER_JSON_pack_amount ("amount",
+ &bank->amount))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_WITHDRAW_COIN:
+ {
+ const struct TALER_EXCHANGEDB_CollectableBlindcoin *withdraw
+ = pos->details.withdraw;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "WITHDRAW"),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &withdraw->reserve_sig),
+ GNUNET_JSON_pack_data_auto ("h_coin_envelope",
+ &withdraw->h_coin_envelope),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &withdraw->denom_pub_hash),
+ TALER_JSON_pack_amount ("withdraw_fee",
+ &withdraw->withdraw_fee),
+ TALER_JSON_pack_amount ("amount",
+ &withdraw->amount_with_fee))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_RO_RECOUP_COIN:
+ {
+ const struct TALER_EXCHANGEDB_Recoup *recoup
+ = pos->details.recoup;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_confirm_recoup_sign (
+ &TEH_keys_exchange_sign_,
+ recoup->timestamp,
+ &recoup->value,
+ &recoup->coin.coin_pub,
+ &recoup->reserve_pub,
+ &pub,
+ &sig))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RECOUP"),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ recoup->timestamp),
+ TALER_JSON_pack_amount ("amount",
+ &recoup->value),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &recoup->coin.coin_pub))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
+ {
+ const struct TALER_EXCHANGEDB_ClosingTransfer *closing =
+ pos->details.closing;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_reserve_closed_sign (
+ &TEH_keys_exchange_sign_,
+ closing->execution_date,
+ &closing->amount,
+ &closing->closing_fee,
+ closing->receiver_account_details,
+ &closing->wtid,
+ &pos->details.closing->reserve_pub,
+ &pub,
+ &sig))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "CLOSING"),
+ GNUNET_JSON_pack_string ("receiver_account_details",
+ closing->receiver_account_details),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &closing->wtid),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ closing->execution_date),
+ TALER_JSON_pack_amount ("amount",
+ &closing->amount),
+ TALER_JSON_pack_amount ("closing_fee",
+ &closing->closing_fee))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_RO_PURSE_MERGE:
+ {
+ const struct TALER_EXCHANGEDB_PurseMerge *merge =
+ pos->details.merge;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "MERGE"),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &merge->h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("merge_pub",
+ &merge->merge_pub),
+ GNUNET_JSON_pack_uint64 ("min_age",
+ merge->min_age),
+ GNUNET_JSON_pack_uint64 ("flags",
+ merge->flags),
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ &merge->purse_pub),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &merge->reserve_sig),
+ GNUNET_JSON_pack_timestamp ("merge_timestamp",
+ merge->merge_timestamp),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ merge->purse_expiration),
+ TALER_JSON_pack_amount ("purse_fee",
+ &merge->purse_fee),
+ TALER_JSON_pack_amount ("amount",
+ &merge->amount_with_fee),
+ GNUNET_JSON_pack_bool ("merged",
+ merge->merged))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
+ {
+ const struct TALER_EXCHANGEDB_HistoryRequest *history =
+ pos->details.history;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "HISTORY"),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &history->reserve_sig),
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ history->request_timestamp),
+ TALER_JSON_pack_amount ("amount",
+ &history->history_fee))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+
+ case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
+ {
+ const struct TALER_EXCHANGEDB_OpenRequest *orq =
+ pos->details.open_request;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "OPEN"),
+ GNUNET_JSON_pack_uint64 ("requested_min_purses",
+ orq->purse_limit),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &orq->reserve_sig),
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ orq->request_timestamp),
+ GNUNET_JSON_pack_timestamp ("requested_expiration",
+ orq->reserve_expiration),
+ TALER_JSON_pack_amount ("open_fee",
+ &orq->open_fee))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+
+ case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
+ {
+ const struct TALER_EXCHANGEDB_CloseRequest *crq =
+ pos->details.close_request;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "CLOSE"),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &crq->reserve_sig),
+ GNUNET_is_zero (&crq->target_account_h_payto)
+ ? GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("h_payto",
+ NULL))
+ : GNUNET_JSON_pack_data_auto ("h_payto",
+ &crq->target_account_h_payto),
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ crq->request_timestamp))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ }
+ }
+
+ return json_history;
+}
+
+
+/**
+ * Add the headers we want to set for every /keys response.
+ *
+ * @param cls the key state to use
+ * @param[in,out] response the response to modify
+ */
+static void
+add_response_headers (void *cls,
+ struct MHD_Response *response)
+{
+ (void) cls;
+ TALER_MHD_add_global_headers (response);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "no-cache"));
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_history (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+ struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL;
+ uint64_t start_off = 0;
+ struct TALER_Amount balance;
+ uint64_t etag_in;
+ uint64_t etag_out;
+ char etagp[24];
+ struct MHD_Response *resp;
+ unsigned int http_status;
+
+ TALER_MHD_parse_request_number (rc->connection,
+ "start",
+ &start_off);
+ {
+ struct TALER_ReserveSignatureP reserve_sig;
+ bool required = true;
+
+ TALER_MHD_parse_request_header_auto (rc->connection,
+ TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
+ &reserve_sig,
+ required);
+
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_history_verify (start_off,
+ reserve_pub,
+ &reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RESERVE_HISTORY_BAD_SIGNATURE,
+ NULL);
+ }
+ }
+
+ /* Get etag */
+ {
+ const char *etags;
+
+ etags = MHD_lookup_connection_value (rc->connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ if (NULL != etags)
+ {
+ char dummy;
+ unsigned long long ev;
+
+ if (1 != sscanf (etags,
+ "\"%llu\"%c",
+ &ev,
+ &dummy))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client send malformed `If-None-Match' header `%s'\n",
+ etags);
+ etag_in = 0;
+ }
+ else
+ {
+ etag_in = (uint64_t) ev;
+ }
+ }
+ else
+ {
+ etag_in = start_off;
+ }
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
+ reserve_pub,
+ start_off,
+ etag_in,
+ &etag_out,
+ &balance,
+ &rh);
+ switch (qs)
+ {
+ 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_history");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "get_reserve_history");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* Handled below */
+ break;
+ }
+ }
+
+ GNUNET_snprintf (etagp,
+ sizeof (etagp),
+ "\"%llu\"",
+ (unsigned long long) etag_out);
+ if (etag_in == etag_out)
+ {
+ return TEH_RESPONSE_reply_not_modified (rc->connection,
+ etagp,
+ &add_response_headers,
+ NULL);
+ }
+ if (NULL == rh)
+ {
+ /* 204: empty history */
+ resp = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ http_status = MHD_HTTP_NO_CONTENT;
+ }
+ else
+ {
+ json_t *history;
+
+ http_status = MHD_HTTP_OK;
+ history = compile_reserve_history (rh);
+ TEH_plugin->free_reserve_history (TEH_plugin->cls,
+ rh);
+ rh = NULL;
+ if (NULL == history)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+ NULL);
+ }
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_amount ("balance",
+ &balance),
+ GNUNET_JSON_pack_array_steal ("history",
+ history));
+ }
+ add_response_headers (NULL,
+ resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_ETAG,
+ etagp));
+ {
+ MHD_RESULT ret;
+
+ ret = MHD_queue_response (rc->connection,
+ http_status,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+}
+
+
+/* end of taler-exchange-httpd_reserves_history.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_history.h b/src/exchange/taler-exchange-httpd_reserves_history.h
new file mode 100644
index 000000000..e1bd7ae1b
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_history.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as 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_reserves_history.h
+ * @brief Handle /reserves/$RESERVE_PUB/history requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_RESERVES_HISTORY_H
+#define TALER_EXCHANGE_HTTPD_RESERVES_HISTORY_H
+
+#include <microhttpd.h>
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a GET "/reserves/$RID/history" request.
+ *
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_history (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_open.c b/src/exchange/taler-exchange-httpd_reserves_open.c
new file mode 100644
index 000000000..5aadc9e40
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_open.c
@@ -0,0 +1,471 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_reserves_open.c
+ * @brief Handle /reserves/$RESERVE_PUB/open requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_mhd_lib.h"
+#include "taler_json_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_common_deposit.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_reserves_open.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * How far do we allow a client's time to be off when
+ * checking the request timestamp?
+ */
+#define TIMESTAMP_TOLERANCE \
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
+
+
+/**
+ * Closure for #reserve_open_transaction.
+ */
+struct ReserveOpenContext
+{
+ /**
+ * Public key of the reserve the inquiry is about.
+ */
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+
+ /**
+ * Desired (minimum) expiration time for the reserve.
+ */
+ struct GNUNET_TIME_Timestamp desired_expiration;
+
+ /**
+ * Actual expiration time for the reserve.
+ */
+ struct GNUNET_TIME_Timestamp reserve_expiration;
+
+ /**
+ * Timestamp of the request.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Client signature approving the request.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Global fees applying to the request.
+ */
+ const struct TEH_GlobalFee *gf;
+
+ /**
+ * Amount to be paid from the reserve.
+ */
+ struct TALER_Amount reserve_payment;
+
+ /**
+ * Actual cost to open the reserve.
+ */
+ struct TALER_Amount open_cost;
+
+ /**
+ * Total amount that was deposited.
+ */
+ struct TALER_Amount total;
+
+ /**
+ * Information about payments by coin.
+ */
+ struct TEH_PurseDepositedCoin *payments;
+
+ /**
+ * Length of the @e payments array.
+ */
+ unsigned int payments_len;
+
+ /**
+ * Desired minimum purse limit.
+ */
+ uint32_t purse_limit;
+
+ /**
+ * Set to true if the reserve balance is too low
+ * for the operation.
+ */
+ bool no_funds;
+
+};
+
+
+/**
+ * Send reserve open to client.
+ *
+ * @param connection connection to the client
+ * @param rsc reserve open data to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_reserve_open_success (struct MHD_Connection *connection,
+ const struct ReserveOpenContext *rsc)
+{
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_TIME_Timestamp re;
+ unsigned int status;
+
+ status = MHD_HTTP_OK;
+ if (GNUNET_TIME_timestamp_cmp (rsc->reserve_expiration,
+ <,
+ rsc->desired_expiration))
+ status = MHD_HTTP_PAYMENT_REQUIRED;
+ now = GNUNET_TIME_timestamp_get ();
+ if (GNUNET_TIME_timestamp_cmp (rsc->reserve_expiration,
+ <,
+ now))
+ re = now;
+ else
+ re = rsc->reserve_expiration;
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ status,
+ GNUNET_JSON_pack_timestamp ("reserve_expiration",
+ re),
+ TALER_JSON_pack_amount ("open_cost",
+ &rsc->open_cost));
+}
+
+
+/**
+ * Cleans up information in @a rsc, but does not
+ * free @a rsc itself (allocated on the stack!).
+ *
+ * @param[in] rsc struct with information to clean up
+ */
+static void
+cleanup_rsc (struct ReserveOpenContext *rsc)
+{
+ for (unsigned int i = 0; i<rsc->payments_len; i++)
+ {
+ TEH_common_purse_deposit_free_coin (&rsc->payments[i]);
+ }
+ GNUNET_free (rsc->payments);
+}
+
+
+/**
+ * Function implementing /reserves/$RID/open transaction. Given the public
+ * key of a reserve, return the associated transaction open. 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 ReserveOpenContext *`
+ * @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_open_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct ReserveOpenContext *rsc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_Amount reserve_balance;
+
+ for (unsigned int i = 0; i<rsc->payments_len; i++)
+ {
+ struct TEH_PurseDepositedCoin *coin = &rsc->payments[i];
+ bool insufficient_funds = true;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Make coin %u known\n",
+ i);
+ qs = TEH_make_coin_known (&coin->cpi,
+ connection,
+ &coin->known_coin_id,
+ mhd_ret);
+ if (qs < 0)
+ return qs;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Insert open deposit %u known\n",
+ i);
+ qs = TEH_plugin->insert_reserve_open_deposit (
+ TEH_plugin->cls,
+ &coin->cpi,
+ &coin->coin_sig,
+ coin->known_coin_id,
+ &coin->amount,
+ &rsc->reserve_sig,
+ rsc->reserve_pub,
+ &insufficient_funds);
+ /* 0 == qs is fine, then the coin was already
+ spent for this very operation as identified
+ by reserve_sig! */
+ 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_reserve_open_deposit");
+ return qs;
+ }
+ if (insufficient_funds)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handle insufficient funds\n");
+ *mhd_ret
+ = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &coin->cpi.denom_pub_hash,
+ &coin->cpi.coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Do reserve open with reserve payment of %s\n",
+ TALER_amount2s (&rsc->total));
+ qs = TEH_plugin->do_reserve_open (TEH_plugin->cls,
+ /* inputs */
+ rsc->reserve_pub,
+ &rsc->total,
+ &rsc->reserve_payment,
+ rsc->purse_limit,
+ &rsc->reserve_sig,
+ rsc->desired_expiration,
+ rsc->timestamp,
+ &rsc->gf->fees.account,
+ /* outputs */
+ &rsc->no_funds,
+ &reserve_balance,
+ &rsc->open_cost,
+ &rsc->reserve_expiration);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ *mhd_ret
+ = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "do_reserve_open");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *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;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ if (rsc->no_funds)
+ {
+ TEH_plugin->rollback (TEH_plugin->cls);
+ *mhd_ret
+ = TEH_RESPONSE_reply_reserve_insufficient_balance (
+ connection,
+ TALER_EC_EXCHANGE_RESERVES_OPEN_INSUFFICIENT_FUNDS,
+ &reserve_balance,
+ &rsc->reserve_payment,
+ rsc->reserve_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_open (struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root)
+{
+ struct ReserveOpenContext rsc;
+ const json_t *payments;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("request_timestamp",
+ &rsc.timestamp),
+ GNUNET_JSON_spec_timestamp ("reserve_expiration",
+ &rsc.desired_expiration),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rsc.reserve_sig),
+ GNUNET_JSON_spec_uint32 ("purse_limit",
+ &rsc.purse_limit),
+ GNUNET_JSON_spec_array_const ("payments",
+ &payments),
+ TALER_JSON_spec_amount ("reserve_payment",
+ TEH_currency,
+ &rsc.reserve_payment),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rsc.reserve_pub = reserve_pub;
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+
+ {
+ struct GNUNET_TIME_Timestamp now;
+
+ now = GNUNET_TIME_timestamp_get ();
+ if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
+ rsc.timestamp.abs_time,
+ TIMESTAMP_TOLERANCE))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
+ NULL);
+ }
+ }
+
+ rsc.payments_len = json_array_size (payments);
+ rsc.payments = GNUNET_new_array (rsc.payments_len,
+ struct TEH_PurseDepositedCoin);
+ rsc.total = rsc.reserve_payment;
+ for (unsigned int i = 0; i<rsc.payments_len; i++)
+ {
+ struct TEH_PurseDepositedCoin *coin = &rsc.payments[i];
+ enum GNUNET_GenericReturnValue res;
+
+ res = TEH_common_purse_deposit_parse_coin (
+ rc->connection,
+ coin,
+ json_array_get (payments,
+ i));
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ cleanup_rsc (&rsc);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ cleanup_rsc (&rsc);
+ return MHD_YES; /* failure */
+ }
+ if (0 >
+ TALER_amount_add (&rsc.total,
+ &rsc.total,
+ &coin->amount_minus_fee))
+ {
+ GNUNET_break (0);
+ cleanup_rsc (&rsc);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
+ NULL);
+ }
+ }
+
+ {
+ struct TEH_KeyStateHandle *keys;
+
+ keys = TEH_keys_get_state ();
+ if (NULL == keys)
+ {
+ GNUNET_break (0);
+ cleanup_rsc (&rsc);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ }
+ rsc.gf = TEH_keys_global_fee_by_time (keys,
+ rsc.timestamp);
+ }
+ if (NULL == rsc.gf)
+ {
+ GNUNET_break (0);
+ cleanup_rsc (&rsc);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+ NULL);
+ }
+
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_open_verify (&rsc.reserve_payment,
+ rsc.timestamp,
+ rsc.desired_expiration,
+ rsc.purse_limit,
+ reserve_pub,
+ &rsc.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ cleanup_rsc (&rsc);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RESERVES_OPEN_BAD_SIGNATURE,
+ NULL);
+ }
+
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (rc->connection,
+ "reserve open",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &reserve_open_transaction,
+ &rsc))
+ {
+ cleanup_rsc (&rsc);
+ return mhd_ret;
+ }
+ }
+
+ {
+ MHD_RESULT mhd_ret;
+
+ mhd_ret = reply_reserve_open_success (rc->connection,
+ &rsc);
+ cleanup_rsc (&rsc);
+ return mhd_ret;
+ }
+}
+
+
+/* end of taler-exchange-httpd_reserves_open.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_open.h b/src/exchange/taler-exchange-httpd_reserves_open.h
new file mode 100644
index 000000000..e28c22c0b
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_open.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_reserves_open.h
+ * @brief Handle /reserves/$RESERVE_PUB/open requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_RESERVES_OPEN_H
+#define TALER_EXCHANGE_HTTPD_RESERVES_OPEN_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a POST "/reserves/$RID/open" request.
+ *
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
+ * @param root uploaded body from the client
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_open (struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_purse.c b/src/exchange/taler-exchange-httpd_reserves_purse.c
new file mode 100644
index 000000000..5e06db206
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_purse.c
@@ -0,0 +1,774 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_reserves_purse.c
+ * @brief Handle /reserves/$RID/purse requests; parses the POST and JSON and
+ * verifies the coin signature before handing things off
+ * to the database.
+ * @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_kyclogic_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_reserves_purse.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+/**
+ * Closure for #purse_transaction.
+ */
+struct ReservePurseContext
+{
+
+ /**
+ * Public key of the reserve we are creating a purse for.
+ */
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+
+ /**
+ * Fees for the operation.
+ */
+ const struct TEH_GlobalFee *gf;
+
+ /**
+ * Signature of the reserve affirming the merge.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Purse fee the client is willing to pay.
+ */
+ struct TALER_Amount purse_fee;
+
+ /**
+ * Total amount already put into the purse.
+ */
+ struct TALER_Amount deposit_total;
+
+ /**
+ * Merge time.
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+
+ /**
+ * Our current time.
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
+ * Details about an encrypted contract, if any.
+ */
+ struct TALER_EncryptedContract econtract;
+
+ /**
+ * Merge key for the purse.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Merge affirmation by the @e merge_pub.
+ */
+ struct TALER_PurseMergeSignatureP merge_sig;
+
+ /**
+ * Signature of the client affiming this request.
+ */
+ struct TALER_PurseContractSignatureP purse_sig;
+
+ /**
+ * Fundamental details about the purse.
+ */
+ struct TEH_PurseDetails pd;
+
+ /**
+ * Hash of the @e payto_uri.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * KYC status of the operation.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Minimum age for deposits into this purse.
+ */
+ uint32_t min_age;
+
+ /**
+ * Flags for the operation.
+ */
+ enum TALER_WalletAccountMergeFlags flags;
+
+ /**
+ * Do we lack an @e econtract?
+ */
+ bool no_econtract;
+
+};
+
+
+/**
+ * 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 a `struct ReservePurseContext`
+ * @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
+amount_iterator (void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ struct ReservePurseContext *rpc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ cb (cb_cls,
+ &rpc->deposit_total,
+ GNUNET_TIME_absolute_get ());
+ qs = TEH_plugin->select_merge_amounts_for_kyc_check (
+ TEH_plugin->cls,
+ &rpc->h_payto,
+ limit,
+ cb,
+ cb_cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got %d additional transactions for this merge and limit %llu\n",
+ qs,
+ (unsigned long long) limit.abs_value_us);
+ GNUNET_break (qs >= 0);
+}
+
+
+/**
+ * Execute database transaction for /reserves/$PID/purse. 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 ReservePurseContext`
+ * @param connection MHD request context
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+purse_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct ReservePurseContext *rpc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ char *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,
+ &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;
+ qs = TEH_plugin->insert_kyc_requirement_for_account (
+ TEH_plugin->cls,
+ required,
+ &rpc->h_payto,
+ rpc->reserve_pub,
+ &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;
+
+ {
+ bool in_conflict = true;
+
+ /* 1) store purse */
+ qs = TEH_plugin->insert_purse_request (
+ TEH_plugin->cls,
+ &rpc->pd.purse_pub,
+ &rpc->merge_pub,
+ rpc->pd.purse_expiration,
+ &rpc->pd.h_contract_terms,
+ rpc->min_age,
+ rpc->flags,
+ &rpc->purse_fee,
+ &rpc->pd.target_amount,
+ &rpc->purse_sig,
+ &in_conflict);
+ 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 purse request");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return qs;
+ if (in_conflict)
+ {
+ struct TALER_PurseMergePublicKeyP merge_pub;
+ struct GNUNET_TIME_Timestamp purse_expiration;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_Amount target_amount;
+ struct TALER_Amount balance;
+ struct TALER_PurseContractSignatureP purse_sig;
+ uint32_t min_age;
+
+ TEH_plugin->rollback (TEH_plugin->cls);
+ qs = TEH_plugin->get_purse_request (
+ TEH_plugin->cls,
+ &rpc->pd.purse_pub,
+ &merge_pub,
+ &purse_expiration,
+ &h_contract_terms,
+ &min_age,
+ &target_amount,
+ &balance,
+ &purse_sig);
+ if (qs <= 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ GNUNET_break (0 != qs);
+ TALER_LOG_WARNING ("Failed to fetch purse information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse request");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA),
+ TALER_JSON_pack_amount ("amount",
+ &target_amount),
+ GNUNET_JSON_pack_uint64 ("min_age",
+ min_age),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ purse_expiration),
+ GNUNET_JSON_pack_data_auto ("purse_sig",
+ &purse_sig),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("merge_pub",
+ &merge_pub));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ }
+
+ /* 2) create purse with reserve (and debit reserve for purse creation!) */
+ {
+ bool in_conflict = true;
+ bool insufficient_funds = true;
+ bool no_reserve = true;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Creating purse with flags %d\n",
+ rpc->flags);
+ qs = TEH_plugin->do_reserve_purse (
+ TEH_plugin->cls,
+ &rpc->pd.purse_pub,
+ &rpc->merge_sig,
+ rpc->merge_timestamp,
+ &rpc->reserve_sig,
+ (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
+ == rpc->flags)
+ ? NULL
+ : &rpc->gf->fees.purse,
+ rpc->reserve_pub,
+ &in_conflict,
+ &no_reserve,
+ &insufficient_funds);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ TALER_LOG_WARNING (
+ "Failed to store purse merge information in database\n");
+ *mhd_ret =
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "do reserve purse");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (in_conflict)
+ {
+ /* same purse already merged into a different reserve!? */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PurseMergeSignatureP merge_sig;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ char *partner_url;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ bool refunded;
+
+ TEH_plugin->rollback (TEH_plugin->cls);
+ qs = TEH_plugin->select_purse_merge (
+ TEH_plugin->cls,
+ &purse_pub,
+ &merge_sig,
+ &merge_timestamp,
+ &partner_url,
+ &reserve_pub,
+ &refunded);
+ if (qs <= 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ GNUNET_break (0 != qs);
+ TALER_LOG_WARNING (
+ "Failed to fetch purse merge information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse merge");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (refunded)
+ {
+ /* This is a bit of a strange case ... */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purse was already refunded\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_GONE,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
+ NULL);
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA),
+ GNUNET_JSON_pack_string ("partner_url",
+ NULL == partner_url
+ ? TEH_base_url
+ : partner_url),
+ GNUNET_JSON_pack_timestamp ("merge_timestamp",
+ merge_timestamp),
+ GNUNET_JSON_pack_data_auto ("merge_sig",
+ &merge_sig),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &reserve_pub));
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (no_reserve) &&
+ ( (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
+ == rpc->flags) ||
+ (! TALER_amount_is_zero (&rpc->gf->fees.purse)) ) )
+ {
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (insufficient_funds)
+ {
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ /* 3) if present, persist contract */
+ if (! rpc->no_econtract)
+ {
+ bool in_conflict = true;
+
+ qs = TEH_plugin->insert_contract (TEH_plugin->cls,
+ &rpc->pd.purse_pub,
+ &rpc->econtract,
+ &in_conflict);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ TALER_LOG_WARNING ("Failed to store purse information in database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "purse purse contract");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (in_conflict)
+ {
+ struct TALER_EncryptedContract econtract;
+ struct GNUNET_HashCode h_econtract;
+
+ qs = TEH_plugin->select_contract_by_purse (
+ TEH_plugin->cls,
+ &rpc->pd.purse_pub,
+ &econtract);
+ if (qs <= 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0 != qs);
+ TALER_LOG_WARNING (
+ "Failed to store fetch contract information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select contract");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_CRYPTO_hash (econtract.econtract,
+ econtract.econtract_size,
+ &h_econtract);
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA),
+ GNUNET_JSON_pack_data_auto ("h_econtract",
+ &h_econtract),
+ GNUNET_JSON_pack_data_auto ("econtract_sig",
+ &econtract.econtract_sig),
+ GNUNET_JSON_pack_data_auto ("contract_pub",
+ &econtract.contract_pub));
+ GNUNET_free (econtract.econtract);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_purse (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root)
+{
+ struct MHD_Connection *connection = rc->connection;
+ struct ReservePurseContext rpc = {
+ .reserve_pub = reserve_pub,
+ .exchange_timestamp = GNUNET_TIME_timestamp_get ()
+ };
+ bool no_purse_fee = true;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("purse_value",
+ TEH_currency,
+ &rpc.pd.target_amount),
+ GNUNET_JSON_spec_uint32 ("min_age",
+ &rpc.min_age),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount ("purse_fee",
+ TEH_currency,
+ &rpc.purse_fee),
+ &no_purse_fee),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_econtract ("econtract",
+ &rpc.econtract),
+ &rpc.no_econtract),
+ GNUNET_JSON_spec_fixed_auto ("merge_pub",
+ &rpc.merge_pub),
+ GNUNET_JSON_spec_fixed_auto ("merge_sig",
+ &rpc.merge_sig),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rpc.reserve_sig),
+ GNUNET_JSON_spec_fixed_auto ("purse_pub",
+ &rpc.pd.purse_pub),
+ GNUNET_JSON_spec_fixed_auto ("purse_sig",
+ &rpc.purse_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &rpc.pd.h_contract_terms),
+ GNUNET_JSON_spec_timestamp ("merge_timestamp",
+ &rpc.merge_timestamp),
+ GNUNET_JSON_spec_timestamp ("purse_expiration",
+ &rpc.pd.purse_expiration),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+ {
+ char *payto_uri;
+
+ payto_uri = TALER_reserve_make_payto (TEH_base_url,
+ reserve_pub);
+ TALER_payto_hash (payto_uri,
+ &rpc.h_payto);
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_purse_merge_verify (payto_uri,
+ rpc.merge_timestamp,
+ &rpc.pd.purse_pub,
+ &rpc.merge_pub,
+ &rpc.merge_sig))
+ {
+ MHD_RESULT ret;
+
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID,
+ payto_uri);
+ GNUNET_free (payto_uri);
+ return ret;
+ }
+ GNUNET_free (payto_uri);
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &rpc.deposit_total));
+ if (GNUNET_TIME_timestamp_cmp (rpc.pd.purse_expiration,
+ <,
+ rpc.exchange_timestamp))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_BEFORE_NOW,
+ NULL);
+ }
+ if (GNUNET_TIME_absolute_is_never (rpc.pd.purse_expiration.abs_time))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_IS_NEVER,
+ NULL);
+ }
+ {
+ struct TEH_KeyStateHandle *keys;
+
+ keys = TEH_keys_get_state ();
+ if (NULL == keys)
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ }
+ rpc.gf = TEH_keys_global_fee_by_time (keys,
+ rpc.exchange_timestamp);
+ }
+ if (NULL == rpc.gf)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Cannot purse purse: global fees not configured!\n");
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING,
+ NULL);
+ }
+ if (no_purse_fee)
+ {
+ rpc.flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &rpc.purse_fee));
+ }
+ else
+ {
+ rpc.flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
+ if (-1 ==
+ TALER_amount_cmp (&rpc.purse_fee,
+ &rpc.gf->fees.purse))
+ {
+ /* rpc.purse_fee is below gf.fees.purse! */
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_RESERVES_PURSE_FEE_TOO_LOW,
+ TALER_amount2s (&rpc.gf->fees.purse));
+ }
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_purse_create_verify (rpc.pd.purse_expiration,
+ &rpc.pd.h_contract_terms,
+ &rpc.merge_pub,
+ rpc.min_age,
+ &rpc.pd.target_amount,
+ &rpc.pd.purse_pub,
+ &rpc.purse_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID,
+ NULL);
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_account_merge_verify (rpc.merge_timestamp,
+ &rpc.pd.purse_pub,
+ rpc.pd.purse_expiration,
+ &rpc.pd.h_contract_terms,
+ &rpc.pd.target_amount,
+ &rpc.purse_fee,
+ rpc.min_age,
+ rpc.flags,
+ rpc.reserve_pub,
+ &rpc.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RESERVES_RESERVE_MERGE_SIGNATURE_INVALID,
+ NULL);
+ }
+ if ( (! rpc.no_econtract) &&
+ (GNUNET_OK !=
+ TALER_wallet_econtract_upload_verify (rpc.econtract.econtract,
+ rpc.econtract.econtract_size,
+ &rpc.econtract.contract_pub,
+ &rpc.pd.purse_pub,
+ &rpc.econtract.econtract_sig)) )
+ {
+ TALER_LOG_WARNING ("Invalid signature on /reserves/$PID/purse request\n");
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID,
+ NULL);
+ }
+
+
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure");
+ }
+
+ /* execute transaction */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "execute purse purse",
+ TEH_MT_REQUEST_RESERVE_PURSE,
+ &mhd_ret,
+ &purse_transaction,
+ &rpc))
+ {
+ GNUNET_JSON_parse_free (spec);
+ return mhd_ret;
+ }
+ }
+
+ if (! rpc.kyc.ok)
+ return TEH_RESPONSE_reply_kyc_required (connection,
+ &rpc.h_payto,
+ &rpc.kyc);
+ /* generate regular response */
+ {
+ MHD_RESULT res;
+
+ res = TEH_RESPONSE_reply_purse_created (connection,
+ rpc.exchange_timestamp,
+ &rpc.deposit_total,
+ &rpc.pd);
+ GNUNET_JSON_parse_free (spec);
+ return res;
+ }
+}
+
+
+/* end of taler-exchange-httpd_reserves_purse.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_purse.h b/src/exchange/taler-exchange-httpd_reserves_purse.h
new file mode 100644
index 000000000..017e357d2
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_purse.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_reserves_purse.h
+ * @brief Handle /reserves/$RID/purse requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_RESERVES_PURSE_H
+#define TALER_EXCHANGE_HTTPD_RESERVES_PURSE_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/reserves/$RESERVE_PUB/purse" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #create_transaction() to further check
+ * the details of the operation specified. If everything checks out, this
+ * will ultimately lead to the "purses create" being executed, or rejected.
+ *
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_purse (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c
index 452841190..8993ea50f 100644
--- a/src/exchange/taler-exchange-httpd_responses.c
+++ b/src/exchange/taler-exchange-httpd_responses.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2017 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
@@ -23,718 +23,386 @@
* @author Christian Grothoff
*/
#include "platform.h"
+#include <gnunet/gnunet_json_lib.h>
+#include <microhttpd.h>
#include <zlib.h>
#include "taler-exchange-httpd_responses.h"
+#include "taler_exchangedb_plugin.h"
#include "taler_util.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
-#include "taler-exchange-httpd_keystate.h"
+#include "taler-exchange-httpd_keys.h"
-/**
- * Compile the transaction history of a coin into a JSON object.
- *
- * @param coin_pub public key of the coin
- * @param tl transaction history to JSON-ify
- * @return json representation of the @a rh, NULL on error
- */
-json_t *
-TEH_RESPONSE_compile_transaction_history (
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_EXCHANGEDB_TransactionList *tl)
+MHD_RESULT
+TEH_RESPONSE_reply_unknown_denom_pub_hash (
+ struct MHD_Connection *connection,
+ const struct TALER_DenominationHashP *dph)
{
- json_t *history;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+ struct GNUNET_TIME_Timestamp now;
+ enum TALER_ErrorCode ec;
- history = json_array ();
- if (NULL == history)
- {
- GNUNET_break (0); /* out of memory!? */
- return NULL;
- }
- for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
- NULL != pos;
- pos = pos->next)
+ now = GNUNET_TIME_timestamp_get ();
+ ec = TALER_exchange_online_denomination_unknown_sign (
+ &TEH_keys_exchange_sign_,
+ now,
+ dph,
+ &epub,
+ &esig);
+ if (TALER_EC_NONE != ec)
{
- switch (pos->type)
- {
- case TALER_EXCHANGEDB_TT_DEPOSIT:
- {
- const struct TALER_EXCHANGEDB_DepositListEntry *deposit =
- pos->details.deposit;
- struct TALER_DepositRequestPS dr = {
- .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT),
- .purpose.size = htonl (sizeof (dr)),
- .h_contract_terms = deposit->h_contract_terms,
- .h_wire = deposit->h_wire,
- .timestamp = GNUNET_TIME_absolute_hton (deposit->timestamp),
- .refund_deadline = GNUNET_TIME_absolute_hton (
- deposit->refund_deadline),
- .merchant = deposit->merchant_pub,
- .coin_pub = *coin_pub
- };
-
- TALER_amount_hton (&dr.amount_with_fee,
- &deposit->amount_with_fee);
- TALER_amount_hton (&dr.deposit_fee,
- &deposit->deposit_fee);
-#if ENABLE_SANITY_CHECKS
- /* internal sanity check before we hand out a bogus sig... */
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT,
- &dr.purpose,
- &deposit->csig.eddsa_signature,
- &coin_pub->eddsa_pub))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
-#endif
- if (0 !=
- json_array_append_new (
- history,
- json_pack (
- "{s:s, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o}",
- "type",
- "DEPOSIT",
- "amount",
- TALER_JSON_from_amount (&deposit->amount_with_fee),
- "deposit_fee",
- TALER_JSON_from_amount (&deposit->deposit_fee),
- "timestamp",
- GNUNET_JSON_from_time_abs (deposit->timestamp),
- "refund_deadline",
- GNUNET_JSON_from_time_abs (deposit->refund_deadline),
- "merchant_pub",
- GNUNET_JSON_from_data_auto (&deposit->merchant_pub),
- "h_contract_terms",
- GNUNET_JSON_from_data_auto (&deposit->h_contract_terms),
- "h_wire",
- GNUNET_JSON_from_data_auto (&deposit->h_wire),
- "coin_sig",
- GNUNET_JSON_from_data_auto (&deposit->csig))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- break;
- }
- case TALER_EXCHANGEDB_TT_MELT:
- {
- const struct TALER_EXCHANGEDB_MeltListEntry *melt =
- pos->details.melt;
- struct TALER_RefreshMeltCoinAffirmationPS ms = {
- .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
- .purpose.size = htonl (sizeof (ms)),
- .rc = melt->rc,
- .coin_pub = *coin_pub
- };
-
- TALER_amount_hton (&ms.amount_with_fee,
- &melt->amount_with_fee);
- TALER_amount_hton (&ms.melt_fee,
- &melt->melt_fee);
-#if ENABLE_SANITY_CHECKS
- /* internal sanity check before we hand out a bogus sig... */
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT,
- &ms.purpose,
- &melt->coin_sig.eddsa_signature,
- &coin_pub->eddsa_pub))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
-#endif
- if (0 !=
- json_array_append_new (
- history,
- json_pack ("{s:s, s:o, s:o, s:o, s:o}",
- "type",
- "MELT",
- "amount",
- TALER_JSON_from_amount (&melt->amount_with_fee),
- "melt_fee",
- TALER_JSON_from_amount (&melt->melt_fee),
- "rc",
- GNUNET_JSON_from_data_auto (&melt->rc),
- "coin_sig",
- GNUNET_JSON_from_data_auto (&melt->coin_sig))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_REFUND:
- {
- const struct TALER_EXCHANGEDB_RefundListEntry *refund =
- pos->details.refund;
- struct TALER_Amount value;
- struct TALER_RefundRequestPS rr = {
- .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
- .purpose.size = htonl (sizeof (rr)),
- .h_contract_terms = refund->h_contract_terms,
- .coin_pub = *coin_pub,
- .merchant = refund->merchant_pub,
- .rtransaction_id = GNUNET_htonll (refund->rtransaction_id)
- };
-
- TALER_amount_hton (&rr.refund_amount,
- &refund->refund_amount);
- TALER_amount_hton (&rr.refund_fee,
- &refund->refund_fee);
-#if ENABLE_SANITY_CHECKS
- /* internal sanity check before we hand out a bogus sig... */
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
- &rr.purpose,
- &refund->merchant_sig.eddsa_sig,
- &refund->merchant_pub.eddsa_pub))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
-#endif
- if (GNUNET_OK !=
- TALER_amount_subtract (&value,
- &refund->refund_amount,
- &refund->refund_fee))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- if (0 !=
- json_array_append_new (
- history,
- json_pack (
- "{s:s, s:o, s:o, s:o, s:o, s:I, s:o}",
- "type",
- "REFUND",
- "amount",
- TALER_JSON_from_amount (&value),
- "refund_fee",
- TALER_JSON_from_amount (&refund->refund_fee),
- "h_contract_terms",
- GNUNET_JSON_from_data_auto (&refund->h_contract_terms),
- "merchant_pub",
- GNUNET_JSON_from_data_auto (&refund->merchant_pub),
- "rtransaction_id",
- (json_int_t) refund->rtransaction_id,
- "merchant_sig",
- GNUNET_JSON_from_data_auto (&refund->merchant_sig))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
- {
- struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
- pos->details.old_coin_recoup;
- struct TALER_ExchangePublicKeyP epub;
- struct TALER_ExchangeSignatureP esig;
- struct TALER_RecoupRefreshConfirmationPS pc = {
- .purpose.purpose = htonl (
- TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH),
- .purpose.size = htonl (sizeof (pc)),
- .timestamp = GNUNET_TIME_absolute_hton (pr->timestamp),
- .coin_pub = *coin_pub,
- .old_coin_pub = pr->old_coin_pub
- };
-
- TALER_amount_hton (&pc.recoup_amount,
- &pr->value);
- if (GNUNET_OK !=
- TEH_KS_sign (&pc.purpose,
- &epub,
- &esig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- /* NOTE: we could also provide coin_pub's coin_sig, denomination key hash and
- the denomination key's RSA signature over coin_pub, but as the
- wallet should really already have this information (and cannot
- check or do anything with it anyway if it doesn't), it seems
- strictly unnecessary. *///
- if (0 !=
- json_array_append_new (
- history,
- json_pack ("{s:s, s:o, s:o, s:o, s:o, s:o}",
- "type",
- "OLD-COIN-RECOUP",
- "amount",
- TALER_JSON_from_amount (&pr->value),
- "exchange_sig",
- GNUNET_JSON_from_data_auto (&esig),
- "exchange_pub",
- GNUNET_JSON_from_data_auto (&epub),
- "coin_pub",
- GNUNET_JSON_from_data_auto (&pr->coin.coin_pub),
- "timestamp",
- GNUNET_JSON_from_time_abs (pr->timestamp))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- break;
- }
- case TALER_EXCHANGEDB_TT_RECOUP:
- {
- const struct TALER_EXCHANGEDB_RecoupListEntry *recoup =
- pos->details.recoup;
- struct TALER_ExchangePublicKeyP epub;
- struct TALER_ExchangeSignatureP esig;
- struct TALER_RecoupConfirmationPS pc = {
- .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP),
- .purpose.size = htonl (sizeof (pc)),
- .timestamp = GNUNET_TIME_absolute_hton (recoup->timestamp),
- .coin_pub = *coin_pub,
- .reserve_pub = recoup->reserve_pub
- };
-
- TALER_amount_hton (&pc.recoup_amount,
- &recoup->value);
- if (GNUNET_OK !=
- TEH_KS_sign (&pc.purpose,
- &epub,
- &esig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- if (0 !=
- json_array_append_new (
- history,
- json_pack ("{s:s, s:o, s:o, s:o, s:o, s:o}",
- "type",
- "RECOUP",
- "amount",
- TALER_JSON_from_amount (&recoup->value),
- "exchange_sig",
- GNUNET_JSON_from_data_auto (&esig),
- "exchange_pub",
- GNUNET_JSON_from_data_auto (&epub),
- "reserve_pub",
- GNUNET_JSON_from_data_auto (&recoup->reserve_pub),
- "timestamp",
- GNUNET_JSON_from_time_abs (recoup->timestamp))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
- {
- struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
- pos->details.recoup_refresh;
- struct TALER_ExchangePublicKeyP epub;
- struct TALER_ExchangeSignatureP esig;
- struct TALER_RecoupRefreshConfirmationPS pc = {
- .purpose.purpose = htonl (
- TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH),
- .purpose.size = htonl (sizeof (pc)),
- .timestamp = GNUNET_TIME_absolute_hton (pr->timestamp),
- .coin_pub = *coin_pub,
- .old_coin_pub = pr->old_coin_pub
- };
-
- TALER_amount_hton (&pc.recoup_amount,
- &pr->value);
- if (GNUNET_OK !=
- TEH_KS_sign (&pc.purpose,
- &epub,
- &esig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- /* NOTE: we could also provide coin_pub's coin_sig, denomination key
- hash and the denomination key's RSA signature over coin_pub, but as
- the wallet should really already have this information (and cannot
- check or do anything with it anyway if it doesn't), it seems
- strictly unnecessary. *///
- if (0 !=
- json_array_append_new (
- history,
- json_pack ("{s:s, s:o, s:o, s:o, s:o, s:o}",
- "type",
- "RECOUP-REFRESH",
- "amount",
- TALER_JSON_from_amount (&pr->value),
- "exchange_sig",
- GNUNET_JSON_from_data_auto (&esig),
- "exchange_pub",
- GNUNET_JSON_from_data_auto (&epub),
- "old_coin_pub",
- GNUNET_JSON_from_data_auto (&pr->old_coin_pub),
- "timestamp",
- GNUNET_JSON_from_time_abs (pr->timestamp))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- break;
- }
- default:
- GNUNET_assert (0);
- }
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ ec,
+ NULL);
}
- return history;
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_JSON_pack_ec (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ now),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ dph));
}
-/**
- * Send proof that a request is invalid to client because of
- * insufficient funds. This function will create a message with all
- * of the operations affecting the coin that demonstrate that the coin
- * has insufficient value.
- *
- * @param connection connection to the client
- * @param ec error code to return
- * @param coin_pub public key of the coin
- * @param tl transaction list to use to build reply
- * @return MHD result code
- */
-int
-TEH_RESPONSE_reply_coin_insufficient_funds (
+MHD_RESULT
+TEH_RESPONSE_reply_expired_denom_pub_hash (
struct MHD_Connection *connection,
+ const struct TALER_DenominationHashP *dph,
enum TALER_ErrorCode ec,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_EXCHANGEDB_TransactionList *tl)
+ const char *oper)
{
- json_t *history;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+ enum TALER_ErrorCode ecr;
+ struct GNUNET_TIME_Timestamp now
+ = GNUNET_TIME_timestamp_get ();
- history = TEH_RESPONSE_compile_transaction_history (coin_pub,
- tl);
- if (NULL == history)
+ ecr = TALER_exchange_online_denomination_expired_sign (
+ &TEH_keys_exchange_sign_,
+ now,
+ dph,
+ oper,
+ &epub,
+ &esig);
+ if (TALER_EC_NONE != ecr)
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_COIN_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS,
- "failed to convert transaction history to JSON");
+ ec,
+ NULL);
}
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_CONFLICT,
- "{s:s, s:I, s:o}",
- "hint", "insufficient funds",
- "code", (json_int_t) ec,
- "history", history);
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_GONE,
+ TALER_JSON_pack_ec (ec),
+ GNUNET_JSON_pack_string ("oper",
+ oper),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ now),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ dph));
}
-/**
- * Compile the history of a reserve into a JSON object
- * and calculate the total balance.
- *
- * @param rh reserve history to JSON-ify
- * @param[out] balance set to current reserve balance
- * @return json representation of the @a rh, NULL on error
- */
-json_t *
-TEH_RESPONSE_compile_reserve_history (
- const struct TALER_EXCHANGEDB_ReserveHistory *rh,
- struct TALER_Amount *balance)
+MHD_RESULT
+TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
+ struct MHD_Connection *connection,
+ const struct TALER_DenominationHashP *dph)
{
- struct TALER_Amount credit_total;
- struct TALER_Amount withdraw_total;
- json_t *json_history;
- enum InitAmounts
- {
- /** Nothing initialized */
- IA_NONE = 0,
- /** credit_total initialized */
- IA_CREDIT = 1,
- /** withdraw_total initialized */
- IA_WITHDRAW = 2
- } init = IA_NONE;
-
- json_history = json_array ();
- for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh;
- NULL != pos;
- pos = pos->next)
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+ struct GNUNET_TIME_Timestamp now;
+ enum TALER_ErrorCode ec;
+
+ now = GNUNET_TIME_timestamp_get ();
+ ec = TALER_exchange_online_denomination_unknown_sign (
+ &TEH_keys_exchange_sign_,
+ now,
+ dph,
+ &epub,
+ &esig);
+ if (TALER_EC_NONE != ec)
{
- switch (pos->type)
- {
- case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
- {
- const struct TALER_EXCHANGEDB_BankTransfer *bank =
- pos->details.bank;
- if (0 == (IA_CREDIT & init))
- {
- credit_total = bank->amount;
- init |= IA_CREDIT;
- }
- else if (GNUNET_OK !=
- TALER_amount_add (&credit_total,
- &credit_total,
- &bank->amount))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- if (0 !=
- json_array_append_new (
- json_history,
- json_pack ("{s:s, s:o, s:s, s:o, s:o}",
- "type",
- "CREDIT",
- "timestamp",
- GNUNET_JSON_from_time_abs (bank->execution_date),
- "sender_account_url",
- bank->sender_account_details,
- "wire_reference",
- GNUNET_JSON_from_data (bank->wire_reference,
- bank->wire_reference_size),
- "amount",
- TALER_JSON_from_amount (&bank->amount))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- break;
- }
- case TALER_EXCHANGEDB_RO_WITHDRAW_COIN:
- {
- const struct TALER_EXCHANGEDB_CollectableBlindcoin *withdraw
- = pos->details.withdraw;
- struct TALER_Amount value;
-
- value = withdraw->amount_with_fee;
- if (0 == (IA_WITHDRAW & init))
- {
- withdraw_total = value;
- init |= IA_WITHDRAW;
- }
- else
- {
- if (GNUNET_OK !=
- TALER_amount_add (&withdraw_total,
- &withdraw_total,
- &value))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- if (0 !=
- json_array_append_new (
- json_history,
- json_pack ("{s:s, s:o, s:o, s:o, s:o, s:o}",
- "type",
- "WITHDRAW",
- "reserve_sig",
- GNUNET_JSON_from_data_auto (&withdraw->reserve_sig),
- "h_coin_envelope",
- GNUNET_JSON_from_data_auto (
- &withdraw->h_coin_envelope),
- "h_denom_pub",
- GNUNET_JSON_from_data_auto (&withdraw->denom_pub_hash),
- "withdraw_fee",
- TALER_JSON_from_amount (&withdraw->withdraw_fee),
- "amount",
- TALER_JSON_from_amount (&value))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_RO_RECOUP_COIN:
- {
- const struct TALER_EXCHANGEDB_Recoup *recoup
- = pos->details.recoup;
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
-
- if (0 == (IA_CREDIT & init))
- {
- credit_total = recoup->value;
- init |= IA_CREDIT;
- }
- else if (GNUNET_OK !=
- TALER_amount_add (&credit_total,
- &credit_total,
- &recoup->value))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- {
- struct TALER_RecoupConfirmationPS pc = {
- .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP),
- .purpose.size = htonl (sizeof (pc)),
- .timestamp = GNUNET_TIME_absolute_hton (recoup->timestamp),
- .coin_pub = recoup->coin.coin_pub,
- .reserve_pub = recoup->reserve_pub
- };
-
- TALER_amount_hton (&pc.recoup_amount,
- &recoup->value);
- if (GNUNET_OK !=
- TEH_KS_sign (&pc.purpose,
- &pub,
- &sig))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
-
- if (0 !=
- json_array_append_new (json_history,
- json_pack ("{s:s, s:o, s:o, s:o, s:o, s:o}",
- "type", "RECOUP",
- "exchange_pub",
- GNUNET_JSON_from_data_auto (&pub),
- "exchange_sig",
- GNUNET_JSON_from_data_auto (&sig),
- "timestamp",
- GNUNET_JSON_from_time_abs (
- recoup->timestamp),
- "amount", TALER_JSON_from_amount (
- &recoup->value),
- "coin_pub",
- GNUNET_JSON_from_data_auto (
- &recoup->coin.coin_pub))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
- {
- const struct TALER_EXCHANGEDB_ClosingTransfer *closing =
- pos->details.closing;
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
- struct TALER_Amount value;
-
- value = closing->amount;
- if (0 == (IA_WITHDRAW & init))
- {
- withdraw_total = value;
- init |= IA_WITHDRAW;
- }
- else
- {
- if (GNUNET_OK !=
- TALER_amount_add (&withdraw_total,
- &withdraw_total,
- &value))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- {
- struct TALER_ReserveCloseConfirmationPS rcc = {
- .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED),
- .purpose.size = htonl (sizeof (rcc)),
- .timestamp = GNUNET_TIME_absolute_hton (closing->execution_date),
- .reserve_pub = pos->details.closing->reserve_pub,
- .wtid = closing->wtid
- };
-
- TALER_amount_hton (&rcc.closing_amount,
- &value);
- TALER_amount_hton (&rcc.closing_fee,
- &closing->closing_fee);
- GNUNET_CRYPTO_hash (closing->receiver_account_details,
- strlen (closing->receiver_account_details) + 1,
- &rcc.h_wire);
- if (GNUNET_OK !=
- TEH_KS_sign (&rcc.purpose,
- &pub,
- &sig))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- if (0 !=
- json_array_append_new (
- json_history,
- json_pack (
- "{s:s, s:s, s:o, s:o, s:o, s:o, s:o, s:o}",
- "type",
- "CLOSING",
- "receiver_account_details",
- closing->receiver_account_details,
- "wtid",
- GNUNET_JSON_from_data_auto (&closing->wtid),
- "exchange_pub",
- GNUNET_JSON_from_data_auto (&pub),
- "exchange_sig",
- GNUNET_JSON_from_data_auto (&sig),
- "timestamp",
- GNUNET_JSON_from_time_abs (closing->execution_date),
- "amount",
- TALER_JSON_from_amount (&value),
- "closing_fee",
- TALER_JSON_from_amount (&closing->closing_fee))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
- }
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ ec,
+ NULL);
}
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_GENERIC_INVALID_DENOMINATION_CIPHER_FOR_OPERATION),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ now),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ dph));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_coin_insufficient_funds (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ TALER_ErrorCode_get_http_status_safe (ec),
+ TALER_JSON_pack_ec (ec),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ coin_pub),
+ // FIXME - #7267: to be kept only for some of the error types!
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ h_denom_pub));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_coin_conflicting_contract (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const struct TALER_MerchantWireHashP *h_wire)
+{
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ TALER_ErrorCode_get_http_status_safe (ec),
+ GNUNET_JSON_pack_data_auto ("h_wire",
+ h_wire),
+ TALER_JSON_pack_ec (ec));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_coin_denomination_conflict (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_DenominationPublicKey *prev_denom_pub,
+ const struct TALER_DenominationSignature *prev_denom_sig)
+{
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ TALER_ErrorCode_get_http_status_safe (ec),
+ TALER_JSON_pack_ec (ec),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ coin_pub),
+ TALER_JSON_pack_denom_pub ("prev_denom_pub",
+ prev_denom_pub),
+ TALER_JSON_pack_denom_sig ("prev_denom_sig",
+ prev_denom_sig)
+ );
+
+}
+
- if (0 == (IA_CREDIT & init))
+MHD_RESULT
+TEH_RESPONSE_reply_coin_age_commitment_conflict (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ enum TALER_EXCHANGEDB_CoinKnownStatus status,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment)
+{
+ const char *conflict_detail;
+
+ switch (status)
{
- /* We should not have gotten here, without credits no reserve
- should exist! */
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
+
+ case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL:
+ conflict_detail = "expected NULL age commitment hash";
+ h_age_commitment = NULL;
+ break;
+ case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL:
+ conflict_detail = "expected non-NULL age commitment hash";
+ break;
+ case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS:
+ conflict_detail = "expected age commitment hash differs";
+ break;
+ default:
+ GNUNET_assert (0);
}
- if (0 == (IA_WITHDRAW & init))
+
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ TALER_ErrorCode_get_http_status_safe (ec),
+ TALER_JSON_pack_ec (ec),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ coin_pub),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ h_denom_pub),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("expected_age_commitment_hash",
+ h_age_commitment)),
+ GNUNET_JSON_pack_string ("conflict_detail",
+ conflict_detail)
+ );
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_reserve_insufficient_balance (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const struct TALER_Amount *reserve_balance,
+ const struct TALER_Amount *balance_required,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (ec),
+ TALER_JSON_pack_amount ("balance",
+ reserve_balance),
+ TALER_JSON_pack_amount ("requested_amount",
+ balance_required));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_reserve_age_restriction_required (
+ struct MHD_Connection *connection,
+ uint16_t maximum_allowed_age)
+{
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (TALER_EC_EXCHANGE_RESERVES_AGE_RESTRICTION_REQUIRED),
+ GNUNET_JSON_pack_uint64 ("maximum_allowed_age",
+ maximum_allowed_age));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_purse_created (
+ struct MHD_Connection *connection,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ const struct TALER_Amount *purse_balance,
+ const struct TEH_PurseDetails *pd)
+{
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ enum TALER_ErrorCode ec;
+
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_purse_created_sign (
+ &TEH_keys_exchange_sign_,
+ exchange_timestamp,
+ pd->purse_expiration,
+ &pd->target_amount,
+ purse_balance,
+ &pd->purse_pub,
+ &pd->h_contract_terms,
+ &pub,
+ &sig)))
{
- /* did not encounter any withdraw operations, set withdraw_total to zero */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (credit_total.currency,
- &withdraw_total));
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
}
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (balance,
- &credit_total,
- &withdraw_total))
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("total_deposited",
+ purse_balance),
+ GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+ exchange_timestamp),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_kyc_required (struct MHD_Connection *connection,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_EXCHANGEDB_KycStatus *kyc)
+{
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+ TALER_JSON_pack_ec (TALER_EC_EXCHANGE_GENERIC_KYC_REQUIRED),
+ GNUNET_JSON_pack_data_auto ("h_payto",
+ h_payto),
+ GNUNET_JSON_pack_uint64 ("requirement_row",
+ kyc->requirement_row));
+}
+
+
+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);
- json_decref (json_history);
- return NULL;
+ 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));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_not_modified (
+ struct MHD_Connection *connection,
+ const char *etags,
+ TEH_RESPONSE_SetHeaders cb,
+ void *cb_cls)
+{
+ MHD_RESULT ret;
+ struct MHD_Response *resp;
- return json_history;
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ cb (cb_cls,
+ resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_ETAG,
+ etags));
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NOT_MODIFIED,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
}
diff --git a/src/exchange/taler-exchange-httpd_responses.h b/src/exchange/taler-exchange-httpd_responses.h
index 5dd98174f..24b24621f 100644
--- a/src/exchange/taler-exchange-httpd_responses.h
+++ b/src/exchange/taler-exchange-httpd_responses.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2019 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
@@ -24,27 +24,121 @@
*/
#ifndef TALER_EXCHANGE_HTTPD_RESPONSES_H
#define TALER_EXCHANGE_HTTPD_RESPONSES_H
+
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
#include <microhttpd.h>
-#include <pthread.h>
#include "taler_error_codes.h"
#include "taler-exchange-httpd.h"
#include "taler-exchange-httpd_db.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Send assertion that the given denomination key hash
+ * is unknown to us at this time.
+ *
+ * @param connection connection to the client
+ * @param dph denomination public key hash
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_unknown_denom_pub_hash (
+ struct MHD_Connection *connection,
+ const struct TALER_DenominationHashP *dph);
+
+
+/**
+ * Return error message indicating that a reserve had
+ * an insufficient balance for the given operation.
+ *
+ * @param connection connection to the client
+ * @param ec specific error code to return with the reserve history
+ * @param reserve_balance balance remaining in the reserve
+ * @param balance_required the balance required for the operation
+ * @param reserve_pub the reserve with insufficient balance
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_reserve_insufficient_balance (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const struct TALER_Amount *reserve_balance,
+ const struct TALER_Amount *balance_required,
+ const struct TALER_ReservePublicKeyP *reserve_pub);
/**
- * Compile the history of a reserve into a JSON object
- * and calculate the total balance.
+ * Return error message indicating that a reserve requires age
+ * restriction to be set during withdraw, that is: the age-withdraw
+ * protocol MUST be used with commitment to an admissible age.
*
- * @param rh reserve history to JSON-ify
- * @param[out] balance set to current reserve balance
- * @return json representation of the @a rh, NULL on error
+ * @param connection connection to the client
+ * @param maximum_allowed_age the balance required for the operation
+ * @return MHD result code
*/
-json_t *
-TEH_RESPONSE_compile_reserve_history (
- const struct TALER_EXCHANGEDB_ReserveHistory *rh,
- struct TALER_Amount *balance);
+MHD_RESULT
+TEH_RESPONSE_reply_reserve_age_restriction_required (
+ struct MHD_Connection *connection,
+ uint16_t maximum_allowed_age);
+
+
+/**
+ * Send information that a KYC check must be
+ * satisfied to proceed to client.
+ *
+ * @param connection connection to the client
+ * @param h_payto account identifier to include in reply
+ * @param kyc details about the KYC requirements
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_kyc_required (struct MHD_Connection *connection,
+ const struct TALER_PaytoHashP *h_payto,
+ 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.
+ *
+ * @param connection connection to the client
+ * @param dph denomination public key hash
+ * @param ec error code to use
+ * @param oper name of the operation that is not allowed at this time
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_expired_denom_pub_hash (
+ struct MHD_Connection *connection,
+ const struct TALER_DenominationHashP *dph,
+ enum TALER_ErrorCode ec,
+ const char *oper);
+
+
+/**
+ * Send assertion that the given denomination cannot be used for this operation.
+ *
+ * @param connection connection to the client
+ * @param dph denomination public key hash
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
+ struct MHD_Connection *connection,
+ const struct TALER_DenominationHashP *dph);
/**
@@ -55,29 +149,151 @@ TEH_RESPONSE_compile_reserve_history (
*
* @param connection connection to the client
* @param ec error code to return
+ * @param h_denom_pub hash of the denomination of the coin
* @param coin_pub public key of the coin
- * @param tl transaction list to use to build reply
* @return MHD result code
*/
-int
+MHD_RESULT
TEH_RESPONSE_reply_coin_insufficient_funds (
struct MHD_Connection *connection,
enum TALER_ErrorCode ec,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub);
+
+/**
+ * Send proof that a request is invalid to client because of
+ * an conflict with the provided denomination (the exchange had seen
+ * this coin before, signed by a different denomination).
+ * This function will create a message with the denomination's public key
+ * that was seen before.
+ *
+ * @param connection connection to the client
+ * @param ec error code to return
+ * @param coin_pub the public key of the coin
+ * @param prev_denom_pub the denomination of the coin, as seen previously
+ * @param prev_denom_sig the signature with the denomination key over the coin
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_coin_denomination_conflict (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_EXCHANGEDB_TransactionList *tl);
+ const struct TALER_DenominationPublicKey *prev_denom_pub,
+ const struct TALER_DenominationSignature *prev_denom_sig);
+/**
+ * Send the salted hash of the merchant's bank account from conflicting
+ * contract. With this information, the merchant's private key and
+ * the hash of the contract terms, the client can retrieve more details
+ * about the conflicting deposit
+ *
+ * @param connection connection to the client
+ * @param ec error code to return
+ * @param h_wire the salted hash of the merchant's bank account
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_coin_conflicting_contract (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const struct TALER_MerchantWireHashP *h_wire);
/**
- * Compile the transaction history of a coin into a JSON object.
+ * Send proof that a request is invalid to client because of
+ * a conflicting value for the age commitment hash of a coin.
+ * This function will create a message with the conflicting
+ * hash value for the age commitment of the given coin.
*
+ * @param connection connection to the client
+ * @param ec error code to return
+ * @param cks specific conflict type
+ * @param h_denom_pub hash of the denomination of the coin
* @param coin_pub public key of the coin
- * @param tl transaction history to JSON-ify
- * @return json representation of the @a rh, NULL on error
+ * @param h_age_commitment hash of the age commitment as found in the database
+ * @return MHD result code
*/
-json_t *
-TEH_RESPONSE_compile_transaction_history (
+MHD_RESULT
+TEH_RESPONSE_reply_coin_age_commitment_conflict (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ enum TALER_EXCHANGEDB_CoinKnownStatus cks,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_EXCHANGEDB_TransactionList *tl);
+ const struct TALER_AgeCommitmentHash *h_age_commitment);
+
+/**
+ * Fundamental details about a purse.
+ */
+struct TEH_PurseDetails
+{
+ /**
+ * When should the purse expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * Hash of the contract terms of the purse.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Public key of the purse we are creating.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Total amount to be put into the purse.
+ */
+ struct TALER_Amount target_amount;
+};
+
+
+/**
+ * Send confirmation that a purse was created with
+ * the current purse balance.
+ *
+ * @param connection connection to the client
+ * @param pd purse details
+ * @param exchange_timestamp our time for purse creation
+ * @param purse_balance current balance in the purse
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_purse_created (
+ struct MHD_Connection *connection,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ const struct TALER_Amount *purse_balance,
+ const struct TEH_PurseDetails *pd);
+
+
+/**
+ * Callback used to set headers in a response.
+ *
+ * @param cls closure
+ * @param[in,out] resp response to modify
+ */
+typedef void
+(*TEH_RESPONSE_SetHeaders)(void *cls,
+ struct MHD_Response *resp);
+
+
+/**
+ * Generate a HTTP "Not modified" response with the
+ * given @a etags.
+ *
+ * @param connection connection to queue response on
+ * @param etags ETag header to set
+ * @param cb callback to modify response headers
+ * @param cb_cls closure for @a cb
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_not_modified (
+ struct MHD_Connection *connection,
+ const char *etags,
+ TEH_RESPONSE_SetHeaders cb,
+ void *cb_cls);
#endif
diff --git a/src/exchange/taler-exchange-httpd_spa.c b/src/exchange/taler-exchange-httpd_spa.c
new file mode 100644
index 000000000..60bed3d28
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_spa.c
@@ -0,0 +1,362 @@
+/*
+ This file is part of TALER
+ 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
+ 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_spa.c
+ * @brief logic to load the single page app (/)
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Resource from the WebUi.
+ */
+struct WebuiFile
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct WebuiFile *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WebuiFile *prev;
+
+ /**
+ * Path this resource matches.
+ */
+ char *path;
+
+ /**
+ * SPA resource, compressed.
+ */
+ struct MHD_Response *zspa;
+
+ /**
+ * SPA resource, vanilla.
+ */
+ struct MHD_Response *spa;
+
+};
+
+
+/**
+ * Resources of the WebuUI, kept in a DLL.
+ */
+static struct WebuiFile *webui_head;
+
+/**
+ * Resources of the WebuUI, kept in a DLL.
+ */
+static struct WebuiFile *webui_tail;
+
+
+MHD_RESULT
+TEH_handler_spa (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ struct WebuiFile *w = NULL;
+ const char *infix = args[0];
+
+ if ( (NULL == infix) ||
+ (0 == strcmp (infix,
+ "")) )
+ infix = "index.html";
+ for (struct WebuiFile *pos = webui_head;
+ NULL != pos;
+ pos = pos->next)
+ if (0 == strcmp (infix,
+ pos->path))
+ {
+ w = pos;
+ break;
+ }
+ if (NULL == w)
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+ if ( (MHD_YES ==
+ TALER_MHD_can_compress (rc->connection)) &&
+ (NULL != w->zspa) )
+ return MHD_queue_response (rc->connection,
+ MHD_HTTP_OK,
+ w->zspa);
+ return MHD_queue_response (rc->connection,
+ MHD_HTTP_OK,
+ w->spa);
+}
+
+
+/**
+ * Function called on each file to load for the WebUI.
+ *
+ * @param cls NULL
+ * @param dn name of the file to load
+ */
+static enum GNUNET_GenericReturnValue
+build_webui (void *cls,
+ const char *dn)
+{
+ static struct
+ {
+ const char *ext;
+ const char *mime;
+ } mime_map[] = {
+ {
+ .ext = "css",
+ .mime = "text/css"
+ },
+ {
+ .ext = "html",
+ .mime = "text/html"
+ },
+ {
+ .ext = "js",
+ .mime = "text/javascript"
+ },
+ {
+ .ext = "jpg",
+ .mime = "image/jpeg"
+ },
+ {
+ .ext = "jpeg",
+ .mime = "image/jpeg"
+ },
+ {
+ .ext = "png",
+ .mime = "image/png"
+ },
+ {
+ .ext = "svg",
+ .mime = "image/svg+xml"
+ },
+ {
+ .ext = NULL,
+ .mime = NULL
+ },
+ };
+ int fd;
+ struct stat sb;
+ struct MHD_Response *zspa = NULL;
+ struct MHD_Response *spa;
+ const char *ext;
+ const char *mime;
+
+ (void) cls;
+ /* finally open template */
+ fd = open (dn,
+ O_RDONLY);
+ if (-1 == fd)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ dn);
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ fstat (fd,
+ &sb))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ dn);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_SYSERR;
+ }
+
+ mime = NULL;
+ ext = strrchr (dn, '.');
+ if (NULL == ext)
+ {
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ ext++;
+ for (unsigned int i = 0; NULL != mime_map[i].ext; i++)
+ if (0 == strcasecmp (ext,
+ mime_map[i].ext))
+ {
+ mime = mime_map[i].mime;
+ break;
+ }
+
+ {
+ void *in;
+ ssize_t r;
+ size_t csize;
+
+ in = GNUNET_malloc_large (sb.st_size);
+ if (NULL == in)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "malloc");
+ GNUNET_break (0 == close (fd));
+ return GNUNET_SYSERR;
+ }
+ r = read (fd,
+ in,
+ sb.st_size);
+ if ( (-1 == r) ||
+ (sb.st_size != (size_t) r) )
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "read",
+ dn);
+ GNUNET_free (in);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_SYSERR;
+ }
+ csize = (size_t) r;
+ if (MHD_YES ==
+ TALER_MHD_body_compress (&in,
+ &csize))
+ {
+ zspa = MHD_create_response_from_buffer (csize,
+ in,
+ MHD_RESPMEM_MUST_FREE);
+ if (NULL != zspa)
+ {
+ if (MHD_NO ==
+ MHD_add_response_header (zspa,
+ MHD_HTTP_HEADER_CONTENT_ENCODING,
+ "deflate"))
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (zspa);
+ zspa = NULL;
+ }
+ if (NULL != mime)
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (zspa,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ mime));
+ }
+ }
+ else
+ {
+ GNUNET_free (in);
+ }
+ }
+
+ spa = MHD_create_response_from_fd (sb.st_size,
+ fd);
+ if (NULL == spa)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ dn);
+ GNUNET_break (0 == close (fd));
+ if (NULL != zspa)
+ {
+ MHD_destroy_response (zspa);
+ zspa = NULL;
+ }
+ return GNUNET_SYSERR;
+ }
+ if (NULL != mime)
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (spa,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ mime));
+
+ {
+ struct WebuiFile *w;
+ const char *fn;
+
+ fn = strrchr (dn, '/');
+ GNUNET_assert (NULL != fn);
+ w = GNUNET_new (struct WebuiFile);
+ w->path = GNUNET_strdup (fn + 1);
+ w->spa = spa;
+ w->zspa = zspa;
+ GNUNET_CONTAINER_DLL_insert (webui_head,
+ webui_tail,
+ w);
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_spa_init ()
+{
+ char *dn;
+
+ {
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_asprintf (&dn,
+ "%sexchange/spa/",
+ path);
+ GNUNET_free (path);
+ }
+
+ if (-1 ==
+ GNUNET_DISK_directory_scan (dn,
+ &build_webui,
+ NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load WebUI from `%s'\n",
+ dn);
+ GNUNET_free (dn);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (dn);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Nicely shut down.
+ */
+void __attribute__ ((destructor))
+get_spa_fini ()
+{
+ struct WebuiFile *w;
+
+ while (NULL != (w = webui_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (webui_head,
+ webui_tail,
+ w);
+ if (NULL != w->spa)
+ {
+ MHD_destroy_response (w->spa);
+ w->spa = NULL;
+ }
+ if (NULL != w->zspa)
+ {
+ MHD_destroy_response (w->zspa);
+ w->zspa = NULL;
+ }
+ GNUNET_free (w->path);
+ GNUNET_free (w);
+ }
+}
diff --git a/src/exchange/taler-exchange-httpd_spa.h b/src/exchange/taler-exchange-httpd_spa.h
new file mode 100644
index 000000000..4147a853b
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_spa.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of 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_spa.h
+ * @brief logic to preload and serve static files
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_SPA_H
+#define TALER_EXCHANGE_HTTPD_SPA_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Return our single-page-app user interface (see contrib/wallet-core/).
+ *
+ * @param rc context of the handler
+ * @param[in,out] args remaining arguments (ignored)
+ * @return #MHD_YES on success (reply queued), #MHD_NO on error (close connection)
+ */
+MHD_RESULT
+TEH_handler_spa (struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+
+/**
+ * Preload and compress SPA files.
+ *
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_spa_init (void);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_terms.c b/src/exchange/taler-exchange-httpd_terms.c
index 121e1c780..10114f157 100644
--- a/src/exchange/taler-exchange-httpd_terms.c
+++ b/src/exchange/taler-exchange-httpd_terms.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2019 Taler Systems SA
+ Copyright (C) 2019, 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
@@ -38,51 +38,26 @@ static struct TALER_MHD_Legal *tos;
static struct TALER_MHD_Legal *pp;
-/**
- * Handle a "/terms" request.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param args array of additional options (must be empty for this function)
- * @return MHD result code
- */
-int
-TEH_handler_terms (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+MHD_RESULT
+TEH_handler_terms (struct TEH_RequestContext *rc,
const char *const args[])
{
- (void) rh;
(void) args;
- return TALER_MHD_reply_legal (connection,
+ return TALER_MHD_reply_legal (rc->connection,
tos);
}
-/**
- * Handle a "/privacy" request.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param args array of additional options (must be empty for this function)
- * @return MHD result code
- */
-int
-TEH_handler_privacy (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+MHD_RESULT
+TEH_handler_privacy (struct TEH_RequestContext *rc,
const char *const args[])
{
- (void) rh;
(void) args;
- return TALER_MHD_reply_legal (connection,
+ return TALER_MHD_reply_legal (rc->connection,
pp);
}
-/**
- * Load our terms of service as per configuration.
- *
- * @param cfg configuration to process
- */
void
TEH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
diff --git a/src/exchange/taler-exchange-httpd_terms.h b/src/exchange/taler-exchange-httpd_terms.h
index 7fe7774ad..9815080fe 100644
--- a/src/exchange/taler-exchange-httpd_terms.h
+++ b/src/exchange/taler-exchange-httpd_terms.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2019 Taler Systems SA
+ Copyright (C) 2019, 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
@@ -32,28 +32,24 @@
/**
* Handle a "/terms" request.
*
- * @param rh context of the handler
- * @param connection the MHD connection to handle
+ * @param rc request context
* @param args array of additional options (must be empty for this function)
* @return MHD result code
*/
-int
-TEH_handler_terms (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+MHD_RESULT
+TEH_handler_terms (struct TEH_RequestContext *rc,
const char *const args[]);
/**
* Handle a "/privacy" request.
*
- * @param rh context of the handler
- * @param connection the MHD connection to handle
+ * @param rc request context
* @param args array of additional options (must be empty for this function)
* @return MHD result code
*/
-int
-TEH_handler_privacy (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+MHD_RESULT
+TEH_handler_privacy (struct TEH_RequestContext *rc,
const char *const args[]);
diff --git a/src/exchange/taler-exchange-httpd_transfers_get.c b/src/exchange/taler-exchange-httpd_transfers_get.c
index 9407ad791..18d96f955 100644
--- a/src/exchange/taler-exchange-httpd_transfers_get.c
+++ b/src/exchange/taler-exchange-httpd_transfers_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 Taler Systems SA
+ Copyright (C) 2014-2018, 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
@@ -24,7 +24,7 @@
#include <microhttpd.h>
#include <pthread.h>
#include "taler_signatures.h"
-#include "taler-exchange-httpd_keystate.h"
+#include "taler-exchange-httpd_keys.h"
#include "taler-exchange-httpd_transfers_get.h"
#include "taler-exchange-httpd_responses.h"
#include "taler_json_lib.h"
@@ -51,7 +51,7 @@ struct AggregatedDepositDetail
/**
* Hash of the contract terms.
*/
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
/**
* Coin's public key of the deposited coin.
@@ -59,14 +59,20 @@ struct AggregatedDepositDetail
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
- * Total value of the coin in the deposit.
+ * Total value of the coin in the deposit (after
+ * refunds).
*/
struct TALER_Amount deposit_value;
/**
- * Fees charged by the exchange for the deposit of this coin.
+ * Fees charged by the exchange for the deposit of this coin (possibly after reduction due to refunds).
*/
struct TALER_Amount deposit_fee;
+
+ /**
+ * Total amount refunded for this coin.
+ */
+ struct TALER_Amount refund_total;
};
@@ -77,117 +83,113 @@ struct AggregatedDepositDetail
* @param connection connection to the client
* @param total total amount that was transferred
* @param merchant_pub public key of the merchant
- * @param h_wire destination account
+ * @param payto_uri destination account
* @param wire_fee wire fee that was charged
* @param exec_time execution time of the wire transfer
* @param wdd_head linked list with details about the combined deposits
* @return MHD result code
*/
-static int
+static MHD_RESULT
reply_transfer_details (struct MHD_Connection *connection,
const struct TALER_Amount *total,
const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_wire,
+ const char *payto_uri,
const struct TALER_Amount *wire_fee,
- struct GNUNET_TIME_Absolute exec_time,
+ struct GNUNET_TIME_Timestamp exec_time,
const struct AggregatedDepositDetail *wdd_head)
{
json_t *deposits;
- struct TALER_WireDepositDetailP dd;
struct GNUNET_HashContext *hash_context;
- struct TALER_WireDepositDataPS wdp;
+ struct GNUNET_HashCode h_details;
struct TALER_ExchangePublicKeyP pub;
struct TALER_ExchangeSignatureP sig;
+ struct TALER_PaytoHashP h_payto;
- GNUNET_TIME_round_abs (&exec_time);
deposits = json_array ();
- if (NULL == deposits)
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_JSON_ALLOCATION_FAILURE,
- "json_array() failed");
-
- }
+ GNUNET_assert (NULL != deposits);
hash_context = GNUNET_CRYPTO_hash_context_start ();
for (const struct AggregatedDepositDetail *wdd_pos = wdd_head;
NULL != wdd_pos;
wdd_pos = wdd_pos->next)
{
- dd.h_contract_terms = wdd_pos->h_contract_terms;
- dd.execution_time = GNUNET_TIME_absolute_hton (exec_time);
- dd.coin_pub = wdd_pos->coin_pub;
- TALER_amount_hton (&dd.deposit_value,
- &wdd_pos->deposit_value);
- TALER_amount_hton (&dd.deposit_fee,
- &wdd_pos->deposit_fee);
- GNUNET_CRYPTO_hash_context_read (hash_context,
- &dd,
- sizeof (struct TALER_WireDepositDetailP));
+ TALER_exchange_online_wire_deposit_append (hash_context,
+ &wdd_pos->h_contract_terms,
+ exec_time,
+ &wdd_pos->coin_pub,
+ &wdd_pos->deposit_value,
+ &wdd_pos->deposit_fee);
if (0 !=
- json_array_append_new (deposits,
- json_pack ("{s:o, s:o, s:o, s:o}",
- "h_contract_terms",
- GNUNET_JSON_from_data_auto (
- &wdd_pos->h_contract_terms),
- "coin_pub",
- GNUNET_JSON_from_data_auto (
- &wdd_pos->coin_pub),
- "deposit_value",
- TALER_JSON_from_amount (
- &wdd_pos->deposit_value),
- "deposit_fee",
- TALER_JSON_from_amount (
- &wdd_pos->deposit_fee))))
+ json_array_append_new (
+ deposits,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &wdd_pos->h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &wdd_pos->coin_pub),
+
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("refund_total",
+ TALER_amount_is_zero (
+ &wdd_pos->refund_total)
+ ? NULL
+ : &wdd_pos->refund_total)),
+ TALER_JSON_pack_amount ("deposit_value",
+ &wdd_pos->deposit_value),
+ TALER_JSON_pack_amount ("deposit_fee",
+ &wdd_pos->deposit_fee))))
{
json_decref (deposits);
GNUNET_CRYPTO_hash_context_abort (hash_context);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_JSON_ALLOCATION_FAILURE,
+ TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
"json_array_append_new() failed");
}
}
- wdp.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT);
- wdp.purpose.size = htonl (sizeof (struct TALER_WireDepositDataPS));
- TALER_amount_hton (&wdp.total,
- total);
- TALER_amount_hton (&wdp.wire_fee,
- wire_fee);
- wdp.merchant_pub = *merchant_pub;
- wdp.h_wire = *h_wire;
GNUNET_CRYPTO_hash_context_finish (hash_context,
- &wdp.h_details);
- if (GNUNET_OK !=
- TEH_KS_sign (&wdp.purpose,
- &pub,
- &sig))
+ &h_details);
{
- json_decref (deposits);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_BAD_CONFIGURATION,
- "no keys");
+ enum TALER_ErrorCode ec;
+
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_wire_deposit_sign (
+ &TEH_keys_exchange_sign_,
+ total,
+ wire_fee,
+ merchant_pub,
+ payto_uri,
+ &h_details,
+ &pub,
+ &sig)))
+ {
+ json_decref (deposits);
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
}
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o}",
- "total", TALER_JSON_from_amount (total),
- "wire_fee", TALER_JSON_from_amount (
- wire_fee),
- "merchant_pub",
- GNUNET_JSON_from_data_auto (
- merchant_pub),
- "h_wire", GNUNET_JSON_from_data_auto (
- h_wire),
- "execution_time",
- GNUNET_JSON_from_time_abs (exec_time),
- "deposits", deposits,
- "exchange_sig",
- GNUNET_JSON_from_data_auto (&sig),
- "exchange_pub",
- GNUNET_JSON_from_data_auto (&pub));
+ TALER_payto_hash (payto_uri,
+ &h_payto);
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("total",
+ total),
+ TALER_JSON_pack_amount ("wire_fee",
+ wire_fee),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ merchant_pub),
+ GNUNET_JSON_pack_data_auto ("h_payto",
+ &h_payto),
+ GNUNET_JSON_pack_timestamp ("execution_time",
+ exec_time),
+ GNUNET_JSON_pack_array_steal ("deposits",
+ deposits),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub));
}
@@ -217,20 +219,14 @@ struct WtidTransactionContext
struct TALER_MerchantPublicKeyP merchant_pub;
/**
- * Hash of the wire details of the merchant (identical for all
- * deposits), only valid if @e is_valid is #GNUNET_YES.
+ * Wire fees applicable at @e exec_time.
*/
- struct GNUNET_HashCode h_wire;
-
- /**
- * Wire fee applicable at @e exec_time.
- */
- struct TALER_Amount wire_fee;
+ struct TALER_WireFeeSet fees;
/**
* Execution time of the wire transfer
*/
- struct GNUNET_TIME_Absolute exec_time;
+ struct GNUNET_TIME_Timestamp exec_time;
/**
* Head of DLL with deposit details for transfers GET response.
@@ -243,9 +239,9 @@ struct WtidTransactionContext
struct AggregatedDepositDetail *wdd_tail;
/**
- * Which method was used to wire the funds?
+ * Where were the funds wired?
*/
- char *wire_method;
+ char *payto_uri;
/**
* JSON array with details about the individual deposits.
@@ -259,65 +255,150 @@ struct WtidTransactionContext
* (as they should). Set to #GNUNET_SYSERR if we encountered an
* internal error.
*/
- int is_valid;
+ enum GNUNET_GenericReturnValue is_valid;
};
/**
+ * Callback that totals up the applicable refunds.
+ *
+ * @param cls a `struct TALER_Amount` where we keep the total
+ * @param amount_with_fee amount being refunded
+ */
+static enum GNUNET_GenericReturnValue
+add_refunds (void *cls,
+ const struct TALER_Amount *amount_with_fee)
+
+{
+ struct TALER_Amount *total = cls;
+
+ if (0 >
+ TALER_amount_add (total,
+ total,
+ amount_with_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
* Function called with the results of the lookup of the individual deposits
* that were aggregated for the given wire transfer.
*
* @param cls our context for transmission
* @param rowid which row in the DB is the information from (for diagnostics), ignored
* @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls)
- * @param h_wire hash of wire transfer details of the merchant (should be same for all callbacks with the same @e cls)
- * @param wire where the funds were sent
+ * @param account_payto_uri where the funds were sent
+ * @param h_payto hash over @a account_payto_uri as it is in the DB
* @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls)
* @param h_contract_terms which proposal was this payment about
* @param denom_pub denomination public key of the @a coin_pub (ignored)
* @param coin_pub which public key was this payment about
- * @param deposit_value amount contributed by this coin in total
+ * @param deposit_value amount contributed by this coin in total (including fee)
* @param deposit_fee deposit fee charged by exchange for this coin
*/
static void
-handle_deposit_data (void *cls,
- uint64_t rowid,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_wire,
- const json_t *wire,
- struct GNUNET_TIME_Absolute exec_time,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *deposit_value,
- const struct TALER_Amount *deposit_fee)
+handle_deposit_data (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const char *account_payto_uri,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp exec_time,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *deposit_value,
+ const struct TALER_Amount *deposit_fee)
{
struct WtidTransactionContext *ctx = cls;
- char *wire_method;
+ struct TALER_Amount total_refunds;
+ struct TALER_Amount dval;
+ struct TALER_Amount dfee;
+ enum GNUNET_DB_QueryStatus qs;
(void) rowid;
(void) denom_pub;
+ (void) h_payto;
if (GNUNET_SYSERR == ctx->is_valid)
return;
- if (NULL == (wire_method = TALER_JSON_wire_to_method (wire)))
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (deposit_value->currency,
+ &total_refunds));
+ qs = TEH_plugin->select_refunds_by_coin (TEH_plugin->cls,
+ coin_pub,
+ merchant_pub,
+ h_contract_terms,
+ &add_refunds,
+ &total_refunds);
+ if (qs < 0)
{
GNUNET_break (0);
ctx->is_valid = GNUNET_SYSERR;
return;
}
+ if (1 ==
+ TALER_amount_cmp (&total_refunds,
+ deposit_value))
+ {
+ /* Refunds exceeded total deposit? not OK! */
+ GNUNET_break (0);
+ ctx->is_valid = GNUNET_SYSERR;
+ return;
+ }
+ if (0 ==
+ TALER_amount_cmp (&total_refunds,
+ deposit_value))
+ {
+ /* total_refunds == deposit_value;
+ in this case, the total contributed to the
+ wire transfer is zero (as are fees) */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (deposit_value->currency,
+ &dval));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (deposit_value->currency,
+ &dfee));
+
+ }
+ else
+ {
+ /* Compute deposit value by subtracting refunds */
+ GNUNET_assert (0 <
+ TALER_amount_subtract (&dval,
+ deposit_value,
+ &total_refunds));
+ if (-1 ==
+ TALER_amount_cmp (&dval,
+ deposit_fee))
+ {
+ /* dval < deposit_fee, so after refunds less than
+ the deposit fee remains; reduce deposit fee to
+ the remaining value of the coin */
+ dfee = dval;
+ }
+ else
+ {
+ /* Partial refund, deposit fee remains */
+ dfee = *deposit_fee;
+ }
+ }
+
if (GNUNET_NO == ctx->is_valid)
{
/* First one we encounter, setup general information in 'ctx' */
ctx->merchant_pub = *merchant_pub;
- ctx->h_wire = *h_wire;
+ ctx->payto_uri = GNUNET_strdup (account_payto_uri);
ctx->exec_time = exec_time;
- ctx->wire_method = wire_method; /* captures the reference */
ctx->is_valid = GNUNET_YES;
- if (GNUNET_OK !=
+ if (0 >
TALER_amount_subtract (&ctx->total,
- deposit_value,
- deposit_fee))
+ &dval,
+ &dfee))
{
GNUNET_break (0);
ctx->is_valid = GNUNET_SYSERR;
@@ -332,27 +413,23 @@ handle_deposit_data (void *cls,
(it should, otherwise the deposits should not have been aggregated) */
if ( (0 != GNUNET_memcmp (&ctx->merchant_pub,
merchant_pub)) ||
- (0 != strcmp (wire_method,
- ctx->wire_method)) ||
- (0 != GNUNET_memcmp (&ctx->h_wire,
- h_wire)) )
+ (0 != strcmp (account_payto_uri,
+ ctx->payto_uri)) )
{
GNUNET_break (0);
ctx->is_valid = GNUNET_SYSERR;
- GNUNET_free (wire_method);
return;
}
- GNUNET_free (wire_method);
- if (GNUNET_OK !=
+ if (0 >
TALER_amount_subtract (&delta,
- deposit_value,
- deposit_fee))
+ &dval,
+ &dfee))
{
GNUNET_break (0);
ctx->is_valid = GNUNET_SYSERR;
return;
}
- if (GNUNET_OK !=
+ if (0 >
TALER_amount_add (&ctx->total,
&ctx->total,
&delta))
@@ -367,8 +444,9 @@ handle_deposit_data (void *cls,
struct AggregatedDepositDetail *wdd;
wdd = GNUNET_new (struct AggregatedDepositDetail);
- wdd->deposit_value = *deposit_value;
- wdd->deposit_fee = *deposit_fee;
+ wdd->deposit_value = dval;
+ wdd->deposit_fee = dfee;
+ wdd->refund_total = total_refunds;
wdd->h_contract_terms = *h_contract_terms;
wdd->coin_pub = *coin_pub;
GNUNET_CONTAINER_DLL_insert (ctx->wdd_head,
@@ -395,8 +473,7 @@ free_ctx (struct WtidTransactionContext *ctx)
wdd);
GNUNET_free (wdd);
}
- GNUNET_free_non_null (ctx->wire_method);
- ctx->wire_method = NULL;
+ GNUNET_free (ctx->payto_uri);
}
@@ -412,7 +489,6 @@ free_ctx (struct WtidTransactionContext *ctx)
*
* @param cls closure
* @param connection MHD request which triggered the transaction
- * @param session database session to use
* @param[out] mhd_ret set to MHD response status for @a connection,
* if transaction failed (!)
* @return transaction status
@@ -420,21 +496,18 @@ free_ctx (struct WtidTransactionContext *ctx)
static enum GNUNET_DB_QueryStatus
get_transfer_deposits (void *cls,
struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
+ MHD_RESULT *mhd_ret)
{
struct WtidTransactionContext *ctx = cls;
enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute wire_fee_start_date;
- struct GNUNET_TIME_Absolute wire_fee_end_date;
+ struct GNUNET_TIME_Timestamp wire_fee_start_date;
+ struct GNUNET_TIME_Timestamp wire_fee_end_date;
struct TALER_MasterSignatureP wire_fee_master_sig;
- struct TALER_Amount closing_fee;
/* resetting to NULL/0 in case transaction was repeated after
serialization failure */
free_ctx (ctx);
qs = TEH_plugin->lookup_wire_transfer (TEH_plugin->cls,
- session,
&ctx->wtid,
&handle_deposit_data,
ctx);
@@ -445,8 +518,8 @@ get_transfer_deposits (void *cls,
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_TRANSFERS_GET_DB_FETCH_FAILED,
- "failed to fetch transaction data");
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "wire transfer");
}
return qs;
}
@@ -455,27 +528,40 @@ get_transfer_deposits (void *cls,
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_TRANSFERS_GET_DB_INCONSISTENT,
- "exchange database internally inconsistent");
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "wire history malformed");
return GNUNET_DB_STATUS_HARD_ERROR;
}
if (GNUNET_NO == ctx->is_valid)
{
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
- TALER_EC_TRANSFERS_GET_WTID_NOT_FOUND,
- "wtid");
+ TALER_EC_EXCHANGE_TRANSFERS_GET_WTID_NOT_FOUND,
+ NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- qs = TEH_plugin->get_wire_fee (TEH_plugin->cls,
- session,
- ctx->wire_method,
- ctx->exec_time,
- &wire_fee_start_date,
- &wire_fee_end_date,
- &ctx->wire_fee,
- &closing_fee,
- &wire_fee_master_sig);
+ {
+ char *wire_method;
+
+ wire_method = TALER_payto_get_method (ctx->payto_uri);
+ if (NULL == wire_method)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "payto:// without wire method encountered");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ qs = TEH_plugin->get_wire_fee (TEH_plugin->cls,
+ wire_method,
+ ctx->exec_time,
+ &wire_fee_start_date,
+ &wire_fee_end_date,
+ &ctx->fees,
+ &wire_fee_master_sig);
+ GNUNET_free (wire_method);
+ }
if (0 >= qs)
{
if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) ||
@@ -484,44 +570,34 @@ get_transfer_deposits (void *cls,
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_TRANSFERS_GET_WIRE_FEE_NOT_FOUND,
- "did not find wire fee");
+ TALER_EC_EXCHANGE_TRANSFERS_GET_WIRE_FEE_NOT_FOUND,
+ NULL);
}
return qs;
}
- if (GNUNET_OK !=
+ if (0 >
TALER_amount_subtract (&ctx->total,
&ctx->total,
- &ctx->wire_fee))
+ &ctx->fees.wire))
{
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_TRANSFERS_GET_WIRE_FEE_INCONSISTENT,
- "could not subtract wire fee");
+ TALER_EC_EXCHANGE_TRANSFERS_GET_WIRE_FEE_INCONSISTENT,
+ NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
-/**
- * Handle a GET "/transfers/$WTID" request.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param args array of additional options (length: 1, just the wtid)
- * @return MHD result code
- */
-int
-TEH_handler_transfers_get (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+MHD_RESULT
+TEH_handler_transfers_get (struct TEH_RequestContext *rc,
const char *const args[1])
{
struct WtidTransactionContext ctx;
- int mhd_ret;
+ MHD_RESULT mhd_ret;
- (void) rh;
memset (&ctx,
0,
sizeof (ctx));
@@ -532,14 +608,15 @@ TEH_handler_transfers_get (const struct TEH_RequestHandler *rh,
sizeof (ctx.wtid)))
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
+ return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_TRANSFERS_INVALID_WTID,
- "wire transfer identifier malformed");
+ TALER_EC_EXCHANGE_TRANSFERS_GET_WTID_MALFORMED,
+ args[0]);
}
if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
+ TEH_DB_run_transaction (rc->connection,
"run transfers GET",
+ TEH_MT_REQUEST_OTHER,
&mhd_ret,
&get_transfer_deposits,
&ctx))
@@ -547,11 +624,11 @@ TEH_handler_transfers_get (const struct TEH_RequestHandler *rh,
free_ctx (&ctx);
return mhd_ret;
}
- mhd_ret = reply_transfer_details (connection,
+ mhd_ret = reply_transfer_details (rc->connection,
&ctx.total,
&ctx.merchant_pub,
- &ctx.h_wire,
- &ctx.wire_fee,
+ ctx.payto_uri,
+ &ctx.fees.wire,
ctx.exec_time,
ctx.wdd_head);
free_ctx (&ctx);
diff --git a/src/exchange/taler-exchange-httpd_transfers_get.h b/src/exchange/taler-exchange-httpd_transfers_get.h
index 37f7dfbd4..8a92a5ff2 100644
--- a/src/exchange/taler-exchange-httpd_transfers_get.h
+++ b/src/exchange/taler-exchange-httpd_transfers_get.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2017 Taler Systems SA
+ Copyright (C) 2014-2017, 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
@@ -29,14 +29,12 @@
/**
* Handle a GET "/transfers/$WTID" request.
*
- * @param rh context of the handler
- * @param connection the MHD connection to handle
+ * @param rc request context of the handler
* @param args array of additional options (length: 1, just the wtid)
* @return MHD result code
*/
-int
-TEH_handler_transfers_get (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
+MHD_RESULT
+TEH_handler_transfers_get (struct TEH_RequestContext *rc,
const char *const args[1]);
diff --git a/src/exchange/taler-exchange-httpd_wire.c b/src/exchange/taler-exchange-httpd_wire.c
deleted file mode 100644
index cb63355b6..000000000
--- a/src/exchange/taler-exchange-httpd_wire.c
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2015-2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as 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_wire.c
- * @brief Handle /wire requests
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <gnunet/gnunet_json_lib.h>
-#include "taler-exchange-httpd_keystate.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_wire.h"
-#include "taler_json_lib.h"
-#include "taler_mhd_lib.h"
-#include <jansson.h>
-
-/**
- * Cached JSON for /wire response.
- */
-static json_t *wire_methods;
-
-/**
- * Array of wire methods supported by this exchange.
- */
-static json_t *wire_accounts_array;
-
-/**
- * Object mapping wire methods to the respective fee structure.
- */
-static json_t *wire_fee_object;
-
-
-/**
- * Convert fee structure to JSON result to be returned
- * as part of a /wire response.
- *
- * @param af fee structure to convert
- * @return NULL on error, otherwise json data structure for /wire.
- */
-static json_t *
-fees_to_json (struct TALER_EXCHANGEDB_AggregateFees *af)
-{
- json_t *a;
-
- a = json_array ();
- if (NULL == a)
- {
- GNUNET_break (0); /* out of memory? */
- return NULL;
- }
- while (NULL != af)
- {
- if ( (GNUNET_NO == GNUNET_TIME_round_abs (&af->start_date)) ||
- (GNUNET_NO == GNUNET_TIME_round_abs (&af->end_date)) )
- {
- GNUNET_break (0); /* bad timestamps, should not happen */
- json_decref (a);
- return NULL;
- }
- if (0 !=
- json_array_append_new (a,
- json_pack ("{s:o, s:o, s:o, s:o, s:o}",
- "wire_fee", TALER_JSON_from_amount (
- &af->wire_fee),
- "closing_fee",
- TALER_JSON_from_amount (
- &af->closing_fee),
- "start_date",
- GNUNET_JSON_from_time_abs (
- af->start_date),
- "end_date",
- GNUNET_JSON_from_time_abs (
- af->end_date),
- "sig", GNUNET_JSON_from_data_auto (
- &af->master_sig))))
- {
- GNUNET_break (0); /* out of memory? */
- json_decref (a);
- return NULL;
- }
- af = af->next;
- }
- return a;
-}
-
-
-/**
- * Obtain fee structure for @a method wire transfers.
- *
- * @param method method to load fees for
- * @return JSON object (to be freed by caller) with fee structure
- */
-static json_t *
-get_fees (const char *method)
-{
- struct TALER_EXCHANGEDB_AggregateFees *af;
- struct GNUNET_TIME_Absolute now;
-
- af = TALER_EXCHANGEDB_fees_read (TEH_cfg,
- method);
- now = GNUNET_TIME_absolute_get ();
- while ( (NULL != af) &&
- (af->end_date.abs_value_us < now.abs_value_us) )
- {
- struct TALER_EXCHANGEDB_AggregateFees *n = af->next;
-
- GNUNET_free (af);
- af = n;
- }
- if (NULL == af)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to find current wire transfer fees for `%s' at time %s\n",
- method,
- GNUNET_STRINGS_absolute_time_to_string (now));
- return NULL;
- }
- {
- json_t *j;
-
- j = fees_to_json (af);
- TALER_EXCHANGEDB_fees_free (af);
- return j;
- }
-}
-
-
-/**
- * Load wire fees for @a method.
- *
- * @param method wire method to load fee structure for
- * @return #GNUNET_OK on success
- */
-static int
-load_fee (const char *method)
-{
- json_t *fees;
-
- if (NULL != json_object_get (wire_fee_object,
- method))
- return GNUNET_OK; /* already have them */
- fees = get_fees (method);
- if (NULL == fees)
- return GNUNET_SYSERR;
- /* Add fees to #wire_fee_object */
- if (0 !=
- json_object_set_new (wire_fee_object,
- method,
- fees))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Initialize account; checks if @a ai has /wire information, and if so,
- * adds the /wire information (if included) to our responses.
- *
- * @param cls pointer to `int` to set to #GNUNET_SYSERR on errors
- * @param ai details about the account we should load the wire details for
- */
-static void
-load_account (void *cls,
- const struct TALER_EXCHANGEDB_AccountInfo *ai)
-{
- int *ret = cls;
-
- if ( (NULL != ai->wire_response_filename) &&
- (GNUNET_YES == ai->credit_enabled) )
- {
- json_t *wire_s;
- json_error_t error;
-
- if (NULL == (wire_s = json_load_file (ai->wire_response_filename,
- JSON_REJECT_DUPLICATES,
- &error)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse `%s': %s at %d:%d (%d)\n",
- ai->wire_response_filename,
- error.text,
- error.line,
- error.column,
- error.position);
- *ret = GNUNET_SYSERR;
- return;
- }
-
- {
- char *url;
-
- if (NULL == (url = TALER_JSON_wire_to_payto (wire_s)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Wire response file `%s' malformed\n",
- ai->wire_response_filename);
- json_decref (wire_s);
- *ret = GNUNET_SYSERR;
- return;
- }
- if (0 != strcasecmp (url,
- ai->payto_uri))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "URL in wire response file `%s' does not match URL in configuration (%s vs %s)!\n",
- ai->wire_response_filename,
- url,
- ai->payto_uri);
- json_decref (wire_s);
- GNUNET_free (url);
- *ret = GNUNET_SYSERR;
- return;
- }
- GNUNET_free (url);
- }
- /* Provide friendly error message if user forgot to sign wire response. */
- if (NULL == json_object_get (wire_s,
- "master_sig"))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Wire response file `%s' has not been signed."
- " Use taler-exchange-wire to sign it.\n",
- ai->wire_response_filename);
- json_decref (wire_s);
- *ret = GNUNET_SYSERR;
- return;
- }
- if (GNUNET_OK !=
- TALER_JSON_exchange_wire_signature_check (wire_s,
- &TEH_master_public_key))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid signature in `%s' for public key `%s'\n",
- ai->wire_response_filename,
- GNUNET_p2s (&TEH_master_public_key.eddsa_pub));
- json_decref (wire_s);
- *ret = GNUNET_SYSERR;
- return;
- }
- if (GNUNET_OK ==
- load_fee (ai->method))
- {
- if (0 !=
- json_array_append_new (wire_accounts_array,
- wire_s))
- {
- GNUNET_break (0);
- *ret = GNUNET_SYSERR;
- }
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Wire fees not specified for `%s'\n",
- ai->method);
- *ret = GNUNET_SYSERR;
- }
- }
- else if (GNUNET_YES == ai->debit_enabled)
- {
- if (GNUNET_OK !=
- load_fee (ai->method))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Wire transfer fees for `%s' are not given correctly\n",
- ai->method);
- *ret = GNUNET_SYSERR;
- return;
- }
- }
-}
-
-
-/**
- * Handle a "/wire" request.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param args array of additional options (must be empty for this function)
- * @return MHD result code
- */
-int
-TEH_handler_wire (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
- const char *const args[])
-{
- (void) rh;
- (void) args;
- GNUNET_assert (NULL != wire_methods);
- return TALER_MHD_reply_json (connection,
- wire_methods,
- MHD_HTTP_OK);
-}
-
-
-/**
- * Initialize wire subsystem.
- *
- * @param cfg configuration to use
- * @return #GNUNET_OK on success, #GNUNET_SYSERR if we found no valid
- * wire methods
- */
-int
-TEH_WIRE_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- wire_accounts_array = json_array ();
- if (NULL == wire_accounts_array)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- wire_fee_object = json_object ();
- if (NULL == wire_fee_object)
- {
- GNUNET_break (0);
- TEH_WIRE_done ();
- return GNUNET_SYSERR;
- }
- {
- int ret;
-
- ret = GNUNET_OK;
- TALER_EXCHANGEDB_find_accounts (cfg,
- &load_account,
- &ret);
- if (GNUNET_OK != ret)
- {
- TEH_WIRE_done ();
- return GNUNET_SYSERR;
- }
- }
- if ( (0 == json_array_size (wire_accounts_array)) ||
- (0 == json_object_size (wire_fee_object)) )
- {
- TEH_WIRE_done ();
- return GNUNET_SYSERR;
- }
- wire_methods = json_pack ("{s:O, s:O, s:o}",
- "accounts", wire_accounts_array,
- "fees", wire_fee_object,
- "master_public_key",
- GNUNET_JSON_from_data_auto (
- &TEH_master_public_key));
- if (NULL == wire_methods)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to find properly configured wire transfer method\n");
- TEH_WIRE_done ();
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Clean up wire subsystem.
- */
-void
-TEH_WIRE_done ()
-{
- if (NULL != wire_methods)
- {
- json_decref (wire_methods);
- wire_methods = NULL;
- }
- if (NULL != wire_fee_object)
- {
- json_decref (wire_fee_object);
- wire_fee_object = NULL;
- }
- if (NULL != wire_accounts_array)
- {
- json_decref (wire_accounts_array);
- wire_accounts_array = NULL;
- }
-}
-
-
-/* end of taler-exchange-httpd_wire.c */
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c
deleted file mode 100644
index bbd926a5c..000000000
--- a/src/exchange/taler-exchange-httpd_withdraw.c
+++ /dev/null
@@ -1,527 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2019 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_withdraw.c
- * @brief Handle /reserves/$RESERVE_PUB/withdraw requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler_json_lib.h"
-#include "taler_mhd_lib.h"
-#include "taler-exchange-httpd_withdraw.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keystate.h"
-
-
-/**
- * Perform RSA signature before checking with the database?
- * Reduces time spent in transaction, but may cause us to
- * waste CPU time if DB check fails.
- */
-#define OPTIMISTIC_SIGN 1
-
-
-/**
- * Send reserve history information to client with the
- * message that we have insufficient funds for the
- * requested withdraw operation.
- *
- * @param connection connection to the client
- * @param ebalance expected balance based on our database
- * @param rh reserve history to return
- * @return MHD result code
- */
-static int
-reply_withdraw_insufficient_funds (
- struct MHD_Connection *connection,
- const struct TALER_Amount *ebalance,
- const struct TALER_EXCHANGEDB_ReserveHistory *rh)
-{
- json_t *json_history;
- struct TALER_Amount balance;
-
- json_history = TEH_RESPONSE_compile_reserve_history (rh,
- &balance);
- if (NULL == json_history)
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_WITHDRAW_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS,
- "reserve balance calculation failure");
- if (0 !=
- TALER_amount_cmp (&balance,
- ebalance))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_WITHDRAW_RESERVE_BALANCE_CORRUPT,
- "internal balance inconsistency error");
- }
- return TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_CONFLICT,
- "{s:s, s:I, s:o, s:o}",
- "hint", "insufficient funds",
- "code",
- (json_int_t)
- TALER_EC_WITHDRAW_INSUFFICIENT_FUNDS,
- "balance", TALER_JSON_from_amount (
- &balance),
- "history", json_history);
-}
-
-
-/**
- * Context for #withdraw_transaction.
- */
-struct WithdrawContext
-{
- /**
- * Details about the withdrawal request.
- */
- struct TALER_WithdrawRequestPS wsrd;
-
- /**
- * Value of the coin plus withdraw fee.
- */
- struct TALER_Amount amount_required;
-
- /**
- * Hash of the denomination public key.
- */
- struct GNUNET_HashCode denom_pub_hash;
-
- /**
- * Signature over the request.
- */
- struct TALER_ReserveSignatureP signature;
-
- /**
- * Blinded planchet.
- */
- char *blinded_msg;
-
- /**
- * Key state to use to inspect previous withdrawal values.
- */
- struct TEH_KS_StateHandle *key_state;
-
- /**
- * Number of bytes in @e blinded_msg.
- */
- size_t blinded_msg_len;
-
- /**
- * Details about denomination we are about to withdraw.
- */
- struct TALER_EXCHANGEDB_DenominationKey *dki;
-
- /**
- * Set to the resulting signed coin data to be returned to the client.
- */
- struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
-
-};
-
-
-/**
- * Function implementing 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 "wc->collectable.sig" may already be set before entering
- * this function, either because OPTIMISTIC_SIGN was used and we signed
- * before entering the transaction, or because this function is run
- * twice (!) by #TEH_DB_run_transaction() and the first time created
- * the signature and then failed to commit. Furthermore, we may get
- * a 2nd correct signature briefly if "get_withdraw_info" succeeds and
- * finds one in the DB. To avoid signing twice, the function may
- * return a valid signature in "wc->collectable.sig" **even if it failed**.
- * The caller must thus free the signature in either case.
- *
- * @param cls a `struct WithdrawContext *`
- * @param connection MHD request which triggered the transaction
- * @param session database session to use
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-withdraw_transaction (void *cls,
- struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
-{
- struct WithdrawContext *wc = cls;
- struct TALER_EXCHANGEDB_Reserve r;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_DenominationSignature denom_sig;
-
-#if OPTIMISTIC_SIGN
- /* store away optimistic signature to protect
- it from being overwritten by get_withdraw_info */
- denom_sig = wc->collectable.sig;
- wc->collectable.sig.rsa_signature = NULL;
-#endif
- qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
- session,
- &wc->wsrd.h_coin_envelope,
- &wc->collectable);
- if (0 > qs)
- {
- 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_WITHDRAW_DB_FETCH_ERROR,
- "failed to fetch withdraw data");
- wc->collectable.sig = denom_sig;
- return qs;
- }
-
- /* Don't sign again if we have already signed the coin */
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- /* Toss out the optimistic signature, we got another one from the DB;
- optimization trade-off loses in this case: we unnecessarily computed
- a signature :-( */
-#if OPTIMISTIC_SIGN
- GNUNET_CRYPTO_rsa_signature_free (denom_sig.rsa_signature);
-#endif
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
- }
- /* We should never get more than one result, and we handled
- the errors (negative case) above, so that leaves no results. */
- GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
- wc->collectable.sig = denom_sig; /* Note: might still be NULL if we didn't do OPTIMISTIC_SIGN */
-
- /* Check if balance is sufficient */
- r.pub = wc->wsrd.reserve_pub; /* other fields of 'r' initialized in reserves_get (if successful) */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Trying to withdraw from reserve: %s\n",
- TALER_B2S (&r.pub));
- qs = TEH_plugin->reserves_get (TEH_plugin->cls,
- session,
- &r);
- 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_WITHDRAW_DB_FETCH_ERROR,
- "failed to fetch reserve data");
- return qs;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_WITHDRAW_RESERVE_UNKNOWN,
- "reserve_pub");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (0 < TALER_amount_cmp (&wc->amount_required,
- &r.balance))
- {
- struct TALER_EXCHANGEDB_ReserveHistory *rh;
-
- /* The reserve does not have the required amount (actual
- * amount + withdraw fee) */
-#if GNUNET_EXTRA_LOGGING
- {
- char *amount_required;
- char *r_balance;
-
- amount_required = TALER_amount_to_string (&wc->amount_required);
- r_balance = TALER_amount_to_string (&r.balance);
- TALER_LOG_DEBUG ("Asked %s over a reserve worth %s\n",
- amount_required,
- r_balance);
- GNUNET_free (amount_required);
- GNUNET_free (r_balance);
- }
-#endif
- qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
- session,
- &wc->wsrd.reserve_pub,
- &rh);
- if (NULL == rh)
- {
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_WITHDRAW_DB_FETCH_ERROR,
- "failed to fetch reserve history");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- *mhd_ret = reply_withdraw_insufficient_funds (connection,
- &r.balance,
- rh);
- TEH_plugin->free_reserve_history (TEH_plugin->cls,
- rh);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- /* Balance is good, sign the coin! */
-#if ! OPTIMISTIC_SIGN
- if (NULL == wc->collectable.sig.rsa_signature)
- {
- wc->collectable.sig.rsa_signature
- = GNUNET_CRYPTO_rsa_sign_blinded (wc->dki->denom_priv.rsa_private_key,
- wc->blinded_msg,
- wc->blinded_msg_len);
- if (NULL == wc->collectable.sig.rsa_signature)
- {
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_WITHDRAW_SIGNATURE_FAILED,
- "Failed to create blind signature");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
-#endif
- wc->collectable.denom_pub_hash = wc->denom_pub_hash;
- wc->collectable.amount_with_fee = wc->amount_required;
- TALER_amount_ntoh (&wc->collectable.withdraw_fee,
- &wc->dki->issue.properties.fee_withdraw);
- wc->collectable.reserve_pub = wc->wsrd.reserve_pub;
- wc->collectable.h_coin_envelope = wc->wsrd.h_coin_envelope;
- wc->collectable.reserve_sig = wc->signature;
- qs = TEH_plugin->insert_withdraw_info (TEH_plugin->cls,
- session,
- &wc->collectable);
- if (0 > qs)
- {
- 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_WITHDRAW_DB_STORE_ERROR,
- "failed to persist withdraw data");
- return qs;
- }
- return qs;
-}
-
-
-/**
- * Handle a "/reserves/$RESERVE_PUB/withdraw" request. Parses the
- * "reserve_pub" EdDSA key of the reserve and the requested "denom_pub" which
- * specifies the key/value of the coin to be withdrawn, and checks that the
- * signature "reserve_sig" makes this a valid withdrawal request from the
- * specified reserve. If so, the envelope with the blinded coin "coin_ev" is
- * passed down to execute the withdrawal operation.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param root uploaded JSON data
- * @param args array of additional options (first must be the
- * reserve public key, the second one should be "withdraw")
- * @return MHD result code
- */
-int
-TEH_handler_withdraw (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
- const json_t *root,
- const char *const args[2])
-{
- struct WithdrawContext wc;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_varsize ("coin_ev",
- (void **) &wc.blinded_msg,
- &wc.blinded_msg_len),
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &wc.signature),
- GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
- &wc.denom_pub_hash),
- GNUNET_JSON_spec_end ()
- };
-
- (void) rh;
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &wc.wsrd.reserve_pub,
- sizeof (wc.wsrd.reserve_pub)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_RESERVES_INVALID_RESERVE_PUB,
- "reserve public key malformed");
- }
-
- {
- int res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_OK != res)
- return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
- }
- wc.key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
- if (NULL == wc.key_state)
- {
- TALER_LOG_ERROR ("Lacking keys to operate\n");
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_BAD_CONFIGURATION,
- "no keys");
- }
- {
- unsigned int hc;
- enum TALER_ErrorCode ec;
-
- wc.dki = TEH_KS_denomination_key_lookup_by_hash (wc.key_state,
- &wc.denom_pub_hash,
- TEH_KS_DKU_WITHDRAW,
- &ec,
- &hc);
- if (NULL == wc.dki)
- {
- GNUNET_JSON_parse_free (spec);
- TEH_KS_release (wc.key_state);
- return TALER_MHD_reply_with_error (connection,
- hc,
- ec,
- "could not find denomination key");
- }
- }
- GNUNET_assert (NULL != wc.dki->denom_priv.rsa_private_key);
- {
- struct TALER_Amount amount;
- struct TALER_Amount fee_withdraw;
-
- TALER_amount_ntoh (&amount,
- &wc.dki->issue.properties.value);
- TALER_amount_ntoh (&fee_withdraw,
- &wc.dki->issue.properties.fee_withdraw);
- if (GNUNET_OK !=
- TALER_amount_add (&wc.amount_required,
- &amount,
- &fee_withdraw))
- {
- GNUNET_JSON_parse_free (spec);
- TEH_KS_release (wc.key_state);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_WITHDRAW_AMOUNT_FEE_OVERFLOW,
- "amount overflow for value plus withdraw fee");
- }
- TALER_amount_hton (&wc.wsrd.amount_with_fee,
- &wc.amount_required);
- TALER_amount_hton (&wc.wsrd.withdraw_fee,
- &fee_withdraw);
- }
-
- /* verify signature! */
- wc.wsrd.purpose.size
- = htonl (sizeof (struct TALER_WithdrawRequestPS));
- wc.wsrd.purpose.purpose
- = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
- wc.wsrd.h_denomination_pub
- = wc.denom_pub_hash;
- GNUNET_CRYPTO_hash (wc.blinded_msg,
- wc.blinded_msg_len,
- &wc.wsrd.h_coin_envelope);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
- &wc.wsrd.purpose,
- &wc.signature.eddsa_signature,
- &wc.wsrd.reserve_pub.eddsa_pub))
- {
- TALER_LOG_WARNING (
- "Client supplied invalid signature for withdraw request\n");
- GNUNET_JSON_parse_free (spec);
- TEH_KS_release (wc.key_state);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_WITHDRAW_RESERVE_SIGNATURE_INVALID,
- "reserve_sig");
- }
-
-#if OPTIMISTIC_SIGN
- /* Sign before transaction! */
- wc.collectable.sig.rsa_signature
- = GNUNET_CRYPTO_rsa_sign_blinded (wc.dki->denom_priv.rsa_private_key,
- wc.blinded_msg,
- wc.blinded_msg_len);
- if (NULL == wc.collectable.sig.rsa_signature)
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- TEH_KS_release (wc.key_state);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_WITHDRAW_SIGNATURE_FAILED,
- "Failed to sign");
- }
-#endif
-
- /* run transaction and sign (if not optimistically signed before) */
- {
- int mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "run withdraw",
- &mhd_ret,
- &withdraw_transaction,
- &wc))
- {
- TEH_KS_release (wc.key_state);
- /* Even if #withdraw_transaction() failed, it may have created a signature
- (or we might have done it optimistically above). */
- if (NULL != wc.collectable.sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature);
- GNUNET_JSON_parse_free (spec);
- return mhd_ret;
- }
- }
-
- /* Clean up and send back final (positive) response */
- TEH_KS_release (wc.key_state);
- GNUNET_JSON_parse_free (spec);
-
- {
- int ret;
-
- ret = TALER_MHD_reply_json_pack (
- connection,
- MHD_HTTP_OK,
- "{s:o}",
- "ev_sig", GNUNET_JSON_from_rsa_signature (
- wc.collectable.sig.rsa_signature));
- GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature);
- return ret;
- }
-}
-
-
-/* end of taler-exchange-httpd_withdraw.c */
diff --git a/src/exchange/taler-exchange-httpd_withdraw.h b/src/exchange/taler-exchange-httpd_withdraw.h
deleted file mode 100644
index 7fc2fa01f..000000000
--- a/src/exchange/taler-exchange-httpd_withdraw.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as 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_withdraw.h
- * @brief Handle /reserve/withdraw requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_WITHDRAW_H
-#define TALER_EXCHANGE_HTTPD_WITHDRAW_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a "/reserves/$RESERVE_PUB/withdraw" request. Parses the
- * "reserve_pub" EdDSA key of the reserve and the requested "denom_pub" which
- * specifies the key/value of the coin to be withdrawn, and checks that the
- * signature "reserve_sig" makes this a valid withdrawal request from the
- * specified reserve. If so, the envelope with the blinded coin "coin_ev" is
- * passed down to execute the withdrawal operation.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param root uploaded JSON data
- * @param args array of additional options (first must be the
- * reserve public key, the second one should be "withdraw")
- * @return MHD result code
- */
-int
-TEH_handler_withdraw (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection,
- const json_t *root,
- const char *const args[2]);
-
-#endif
diff --git a/src/exchange/taler-exchange-kyc-aml-pep-trigger.sh b/src/exchange/taler-exchange-kyc-aml-pep-trigger.sh
new file mode 100755
index 000000000..9baa32baf
--- /dev/null
+++ b/src/exchange/taler-exchange-kyc-aml-pep-trigger.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# This file is in the public domain.
+# This is an example of how to trigger AML if the
+# KYC attributes include '{"pep":true}'
+#
+# To be used as a script for the KYC_AML_TRIGGER.
+test "false" = $(jq .pep -)
diff --git a/src/exchange/taler-exchange-router.c b/src/exchange/taler-exchange-router.c
new file mode 100644
index 000000000..a1a247194
--- /dev/null
+++ b/src/exchange/taler-exchange-router.c
@@ -0,0 +1,450 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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-router.c
+ * @brief Process that routes P2P payments. Responsible for
+ * aggregating remote payments into the respective wad transfers.
+ * Execution of actual wad transfers is still to be done by taler-exchange-transfer,
+ * and watching for incoming wad transfers is done by taler-exchange-wirewatch.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <pthread.h>
+#include "taler_exchangedb_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_json_lib.h"
+#include "taler_bank_service.h"
+
+
+// FIXME #7271: revisit how (and if) we do sharding!
+// Maybe use different helpers for wads than
+// for local purses?!
+/**
+ * Work shard we are processing.
+ */
+struct Shard
+{
+
+ /**
+ * When did we start processing the shard?
+ */
+ struct GNUNET_TIME_Timestamp start_time;
+
+ /**
+ * Starting row of the shard.
+ */
+ uint32_t shard_start;
+
+ /**
+ * Inclusive end row of the shard.
+ */
+ uint32_t shard_end;
+
+ /**
+ * Number of starting points found in the shard.
+ */
+ uint64_t work_counter;
+
+};
+
+
+/**
+ * What is the smallest unit we support for wire transfers?
+ * We will need to round down to a multiple of this amount.
+ */
+static struct TALER_Amount currency_round_unit;
+
+/**
+ * What is the base URL of this exchange? Used in the
+ * wire transfer subjects so that merchants and governments
+ * can ask for the list of aggregated deposits.
+ */
+static char *exchange_base_url;
+
+/**
+ * Set to #GNUNET_YES if this exchange does not support KYC checks
+ * and thus P2P transfers are to be made regardless of the
+ * KYC status of the target reserve.
+ */
+static int kyc_off;
+
+/**
+ * The exchange's configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Our database plugin.
+ */
+static struct TALER_EXCHANGEDB_Plugin *db_plugin;
+
+/**
+ * Next task to run, if any.
+ */
+static struct GNUNET_SCHEDULER_Task *task;
+
+/**
+ * How long should we sleep when idle before trying to find more work?
+ */
+static struct GNUNET_TIME_Relative router_idle_sleep_interval;
+
+/**
+ * How big are the shards we are processing? Is an inclusive offset, so every
+ * shard ranges from [X,X+shard_size) exclusive. So a shard covers
+ * shard_size slots. The maximum value for shard_size is INT32_MAX+1.
+ */
+static uint32_t shard_size;
+
+/**
+ * Value to return from main(). 0 on success, non-zero on errors.
+ */
+static int global_ret;
+
+/**
+ * #GNUNET_YES if we are in test mode and should exit when idle.
+ */
+static int test_mode;
+
+
+/**
+ * Select a shard to work on.
+ *
+ * @param cls NULL
+ */
+static void
+run_shard (void *cls);
+
+
+/**
+ * We're being aborted with CTRL-C (or SIGTERM). Shut down.
+ *
+ * @param cls closure
+ */
+static void
+shutdown_task (void *cls)
+{
+ (void) cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running shutdown\n");
+ if (NULL != task)
+ {
+ GNUNET_SCHEDULER_cancel (task);
+ task = NULL;
+ }
+ TALER_EXCHANGEDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ TALER_EXCHANGEDB_unload_accounts ();
+ cfg = NULL;
+}
+
+
+/**
+ * Parse the configuration for wirewatch.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_wirewatch_config (void)
+{
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "BASE_URL",
+ &exchange_base_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ "exchange",
+ "ROUTER_IDLE_SLEEP_INTERVAL",
+ &router_idle_sleep_interval))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "ROUTER_IDLE_SLEEP_INTERVAL");
+ return GNUNET_SYSERR;
+ }
+ if ( (GNUNET_OK !=
+ TALER_config_get_amount (cfg,
+ "taler",
+ "CURRENCY_ROUND_UNIT",
+ &currency_round_unit)) ||
+ ( (0 != currency_round_unit.fraction) &&
+ (0 != currency_round_unit.value) ) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Need non-zero value in section `TALER' under `CURRENCY_ROUND_UNIT'\n");
+ return GNUNET_SYSERR;
+ }
+
+ if (NULL ==
+ (db_plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem\n");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGEDB_load_accounts (cfg,
+ TALER_EXCHANGEDB_ALO_DEBIT))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No wire accounts configured for debit!\n");
+ TALER_EXCHANGEDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Perform a database commit. If it fails, print a warning.
+ *
+ * @return status of commit
+ */
+static enum GNUNET_DB_QueryStatus
+commit_or_warn (void)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->commit (db_plugin->cls);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return qs;
+ GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ ? GNUNET_ERROR_TYPE_INFO
+ : GNUNET_ERROR_TYPE_ERROR,
+ "Failed to commit database transaction!\n");
+ return qs;
+}
+
+
+/**
+ * Release lock on shard @a s in the database.
+ * On error, terminates this process.
+ *
+ * @param[in] s shard to free (and memory to release)
+ */
+static void
+release_shard (struct Shard *s)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->release_revolving_shard (
+ db_plugin->cls,
+ "router",
+ s->shard_start,
+ s->shard_end);
+ GNUNET_free (s);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* Strange, but let's just continue */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* normal case */
+ break;
+ }
+}
+
+
+static void
+run_routing (void *cls)
+{
+ struct Shard *s = cls;
+
+ task = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking for ready P2P transfers to route\n");
+ // FIXME #7271: do actual work here!
+ commit_or_warn ();
+ release_shard (s);
+ task = GNUNET_SCHEDULER_add_now (&run_shard,
+ NULL);
+}
+
+
+/**
+ * Select a shard to work on.
+ *
+ * @param cls NULL
+ */
+static void
+run_shard (void *cls)
+{
+ struct Shard *s;
+ enum GNUNET_DB_QueryStatus qs;
+
+ (void) cls;
+ task = NULL;
+ if (GNUNET_SYSERR ==
+ db_plugin->preflight (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain database connection!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ s = GNUNET_new (struct Shard);
+ s->start_time = GNUNET_TIME_timestamp_get ();
+ qs = db_plugin->begin_revolving_shard (db_plugin->cls,
+ "router",
+ shard_size,
+ 1U + INT32_MAX,
+ &s->shard_start,
+ &s->shard_end);
+ if (0 >= qs)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ static struct GNUNET_TIME_Relative delay;
+
+ GNUNET_free (s);
+ delay = GNUNET_TIME_randomized_backoff (delay,
+ GNUNET_TIME_UNIT_SECONDS);
+ task = GNUNET_SCHEDULER_add_delayed (delay,
+ &run_shard,
+ NULL);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to begin shard (%d)!\n",
+ qs);
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting shard [%u:%u]!\n",
+ (unsigned int) s->shard_start,
+ (unsigned int) s->shard_end);
+ task = GNUNET_SCHEDULER_add_now (&run_routing,
+ s);
+}
+
+
+/**
+ * First task.
+ *
+ * @param cls closure, NULL
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param c configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+{
+ unsigned long long ass;
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+
+ cfg = c;
+ if (GNUNET_OK != parse_wirewatch_config ())
+ {
+ cfg = NULL;
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ "exchange",
+ "ROUTER_SHARD_SIZE",
+ &ass))
+ {
+ cfg = NULL;
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if ( (0 == ass) ||
+ (ass > INT32_MAX) )
+ shard_size = 1U + INT32_MAX;
+ else
+ shard_size = (uint32_t) ass;
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_shard,
+ NULL);
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ cls);
+}
+
+
+/**
+ * The main function of the taler-exchange-router.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, non-zero on error, see #global_ret
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_timetravel ('T',
+ "timetravel"),
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
+ GNUNET_GETOPT_option_flag ('y',
+ "kyc-off",
+ "perform wire transfers without KYC checks",
+ &kyc_off),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-exchange-router",
+ gettext_noop (
+ "background process that routes P2P transfers"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+
+/* end of taler-exchange-router.c */
diff --git a/src/exchange/taler-exchange-transfer.c b/src/exchange/taler-exchange-transfer.c
index 1793dc988..9724b41fc 100644
--- a/src/exchange/taler-exchange-transfer.c
+++ b/src/exchange/taler-exchange-transfer.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2016-2020 Taler Systems SA
+ Copyright (C) 2016-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
@@ -27,6 +27,51 @@
#include "taler_json_lib.h"
#include "taler_bank_service.h"
+/**
+ * What is the default batch size we use for credit history
+ * requests with the bank. See `batch_size` below.
+ */
+#define DEFAULT_BATCH_SIZE (4 * 1024)
+
+/**
+ * How often will we retry a request (given certain
+ * HTTP status codes) before giving up?
+ */
+#define MAX_RETRIES 16
+
+/**
+ * Information about our work shard.
+ */
+struct Shard
+{
+
+ /**
+ * Time when we started to work on this shard.
+ */
+ struct GNUNET_TIME_Absolute shard_start_time;
+
+ /**
+ * Offset the shard begins at.
+ */
+ uint64_t shard_start;
+
+ /**
+ * Exclusive offset where the shard ends.
+ */
+ uint64_t shard_end;
+
+ /**
+ * Offset where our current batch begins.
+ */
+ uint64_t batch_start;
+
+ /**
+ * Highest row processed in the current batch.
+ */
+ uint64_t batch_end;
+
+};
+
/**
* Data we keep to #run_transfers(). There is at most
@@ -38,9 +83,16 @@ struct WirePrepareData
{
/**
- * Database session for all of our transactions.
+ * All transfers done in the same transaction
+ * are kept in a DLL.
+ */
+ struct WirePrepareData *next;
+
+ /**
+ * All transfers done in the same transaction
+ * are kept in a DLL.
*/
- struct TALER_EXCHANGEDB_Session *session;
+ struct WirePrepareData *prev;
/**
* Wire execution handle.
@@ -50,13 +102,24 @@ struct WirePrepareData
/**
* Wire account used for this preparation.
*/
- struct TALER_EXCHANGEDB_WireAccount *wa;
+ const struct TALER_EXCHANGEDB_AccountInfo *wa;
/**
* Row ID of the transfer.
*/
unsigned long long row_id;
+ /**
+ * Number of bytes allocated after this struct
+ * with the prewire data.
+ */
+ size_t buf_size;
+
+ /**
+ * How often did we retry so far?
+ */
+ unsigned int retries;
+
};
@@ -76,10 +139,21 @@ static struct TALER_EXCHANGEDB_Plugin *db_plugin;
static struct GNUNET_SCHEDULER_Task *task;
/**
- * If we are currently executing a transfer, information about
- * the active transfer is here. Otherwise, this variable is NULL.
+ * If we are currently executing transfers, information about
+ * the active transfers is here. Otherwise, this variable is NULL.
+ */
+static struct WirePrepareData *wpd_head;
+
+/**
+ * If we are currently executing transfers, information about
+ * the active transfers is here. Otherwise, this variable is NULL.
+ */
+static struct WirePrepareData *wpd_tail;
+
+/**
+ * Information about our work shard.
*/
-static struct WirePrepareData *wpd;
+static struct Shard *shard;
/**
* Handle to the context for interacting with the bank / wire gateway.
@@ -87,39 +161,71 @@ static struct WirePrepareData *wpd;
static struct GNUNET_CURL_Context *ctx;
/**
- * Scheduler context for running the @e ctx.
+ * Randomized back-off we use on serialization errors.
*/
-static struct GNUNET_CURL_RescheduleContext *rc;
+static struct GNUNET_TIME_Relative serialization_delay;
/**
- * How long should we sleep when idle before trying to find more work?
+ * Scheduler context for running the @e ctx.
*/
-static struct GNUNET_TIME_Relative aggregator_idle_sleep_interval;
+static struct GNUNET_CURL_RescheduleContext *rc;
/**
* Value to return from main(). 0 on success, non-zero on errors.
*/
-static enum
-{
- GR_SUCCESS = 0,
- GR_WIRE_TRANSFER_FAILED = 1,
- GR_DATABASE_COMMIT_HARD_FAIL = 2,
- GR_INVARIANT_FAILURE = 3,
- GR_WIRE_ACCOUNT_NOT_CONFIGURED = 4,
- GR_WIRE_TRANSFER_BEGIN_FAIL = 5,
- GR_DATABASE_TRANSACTION_BEGIN_FAIL = 6,
- GR_DATABASE_SESSION_START_FAIL = 7,
- GR_CONFIGURATION_INVALID = 8,
- GR_CMD_LINE_UTF8_ERROR = 9,
- GR_CMD_LINE_OPTIONS_WRONG = 10,
- GR_DATABASE_FETCH_FAILURE = 11,
-} global_ret;
+static int global_ret;
/**
* #GNUNET_YES if we are in test mode and should exit when idle.
*/
static int test_mode;
+/**
+ * How long should we sleep when idle before trying to find more work?
+ * Also used for how long we wait to grab a shard before trying it again.
+ * The value should be set to a bit above the average time it takes to
+ * process a shard.
+ */
+static struct GNUNET_TIME_Relative transfer_idle_sleep_interval;
+
+/**
+ * How long did we take to finish the last shard?
+ */
+static struct GNUNET_TIME_Relative shard_delay;
+
+/**
+ * Size of the shards.
+ */
+static unsigned int shard_size = DEFAULT_BATCH_SIZE;
+
+/**
+ * How many workers should we plan our scheduling with?
+ */
+static unsigned int max_workers = 0;
+
+
+/**
+ * Clean up all active bank interactions.
+ */
+static void
+cleanup_wpd (void)
+{
+ struct WirePrepareData *wpd;
+
+ while (NULL != (wpd = wpd_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (wpd_head,
+ wpd_tail,
+ wpd);
+ if (NULL != wpd->eh)
+ {
+ TALER_BANK_transfer_cancel (wpd->eh);
+ wpd->eh = NULL;
+ }
+ GNUNET_free (wpd);
+ }
+}
+
/**
* We're being aborted with CTRL-C (or SIGTERM). Shut down.
@@ -130,16 +236,6 @@ static void
shutdown_task (void *cls)
{
(void) cls;
- if (NULL != ctx)
- {
- GNUNET_CURL_fini (ctx);
- ctx = NULL;
- }
- if (NULL != rc)
- {
- GNUNET_CURL_gnunet_rc_destroy (rc);
- rc = NULL;
- }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Running shutdown\n");
if (NULL != task)
@@ -147,42 +243,43 @@ shutdown_task (void *cls)
GNUNET_SCHEDULER_cancel (task);
task = NULL;
}
- if (NULL != wpd)
- {
- if (NULL != wpd->eh)
- {
- TALER_BANK_transfer_cancel (wpd->eh);
- wpd->eh = NULL;
- }
- db_plugin->rollback (db_plugin->cls,
- wpd->session);
- GNUNET_free (wpd);
- wpd = NULL;
- }
+ cleanup_wpd ();
+ GNUNET_free (shard);
+ db_plugin->rollback (db_plugin->cls); /* just in case */
TALER_EXCHANGEDB_plugin_unload (db_plugin);
db_plugin = NULL;
TALER_EXCHANGEDB_unload_accounts ();
cfg = NULL;
+ if (NULL != ctx)
+ {
+ GNUNET_CURL_fini (ctx);
+ ctx = NULL;
+ }
+ if (NULL != rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (rc);
+ rc = NULL;
+ }
}
/**
- * Parse the configuration for wirewatch.
+ * Parse the configuration for taler-exchange-transfer.
*
* @return #GNUNET_OK on success
*/
-static int
-parse_wirewatch_config (void)
+static enum GNUNET_GenericReturnValue
+parse_transfer_config (void)
{
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
"exchange",
- "AGGREGATOR_IDLE_SLEEP_INTERVAL",
- &aggregator_idle_sleep_interval))
+ "TRANSFER_IDLE_SLEEP_INTERVAL",
+ &transfer_idle_sleep_interval))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchange",
- "AGGREGATOR_IDLE_SLEEP_INTERVAL");
+ "TRANSFER_IDLE_SLEEP_INTERVAL");
return GNUNET_SYSERR;
}
if (NULL ==
@@ -193,7 +290,9 @@ parse_wirewatch_config (void)
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- TALER_EXCHANGEDB_load_accounts (cfg))
+ TALER_EXCHANGEDB_load_accounts (cfg,
+ TALER_EXCHANGEDB_ALO_DEBIT
+ | TALER_EXCHANGEDB_ALO_AUTHDATA))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No wire accounts configured for debit!\n");
@@ -208,18 +307,19 @@ parse_wirewatch_config (void)
/**
* Perform a database commit. If it fails, print a warning.
*
- * @param session session to perform the commit for.
* @return status of commit
*/
static enum GNUNET_DB_QueryStatus
-commit_or_warn (struct TALER_EXCHANGEDB_Session *session)
+commit_or_warn (void)
{
enum GNUNET_DB_QueryStatus qs;
- qs = db_plugin->commit (db_plugin->cls,
- session);
+ qs = db_plugin->commit (db_plugin->cls);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ serialization_delay = GNUNET_TIME_UNIT_ZERO;
return qs;
+ }
GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs)
? GNUNET_ERROR_TYPE_INFO
: GNUNET_ERROR_TYPE_ERROR,
@@ -238,100 +338,178 @@ static void
run_transfers (void *cls);
+static void
+run_transfers_delayed (void *cls)
+{
+ (void) cls;
+ shard->shard_start_time = GNUNET_TIME_absolute_get ();
+ run_transfers (NULL);
+}
+
+
+/**
+ * Select shard to process.
+ *
+ * @param cls NULL
+ */
+static void
+select_shard (void *cls);
+
+
+/**
+ * We are done with the current batch. Commit
+ * and move on.
+ */
+static void
+batch_done (void)
+{
+ /* batch done */
+ GNUNET_assert (NULL == wpd_head);
+ switch (commit_or_warn ())
+ {
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* try again */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Serialization failure, trying again immediately!\n");
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_transfers,
+ NULL);
+ return;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ shard->batch_start = shard->batch_end + 1;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Batch complete\n");
+ /* continue with #run_transfers(), just to guard
+ against the unlikely case that there are more. */
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_transfers,
+ NULL);
+ return;
+ default:
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
/**
* Function called with the result from the execute step.
* On success, we mark the respective wire transfer as finished,
* and in general we afterwards continue to #run_transfers(),
* except for irrecoverable errors.
*
- * @param cls NULL
- * @param http_status_code #MHD_HTTP_OK on success
- * @param ec taler error code
- * @param row_id unique ID of the wire transfer in the bank's records
- * @param wire_timestamp when did the transfer happen
+ * @param cls `struct WirePrepareData` we are working on
+ * @param tr transfer response
*/
static void
wire_confirm_cb (void *cls,
- unsigned int http_status_code,
- enum TALER_ErrorCode ec,
- uint64_t row_id,
- struct GNUNET_TIME_Absolute wire_timestamp)
+ const struct TALER_BANK_TransferResponse *tr)
{
- struct TALER_EXCHANGEDB_Session *session = wpd->session;
+ struct WirePrepareData *wpd = cls;
enum GNUNET_DB_QueryStatus qs;
- (void) cls;
- (void) row_id;
- (void) wire_timestamp;
wpd->eh = NULL;
- if (MHD_HTTP_OK != http_status_code)
+ switch (tr->http_status)
{
+ case MHD_HTTP_OK:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Wire transfer %llu completed successfully\n",
+ (unsigned long long) wpd->row_id);
+ qs = db_plugin->wire_prepare_data_mark_finished (db_plugin->cls,
+ wpd->row_id);
+ /* continued below */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_CONFLICT:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Wire transaction failed: %u/%d\n",
- http_status_code,
- ec);
- db_plugin->rollback (db_plugin->cls,
- session);
- global_ret = GR_WIRE_TRANSFER_FAILED;
- GNUNET_SCHEDULER_shutdown ();
- GNUNET_free (wpd);
- wpd = NULL;
- return;
- }
- qs = db_plugin->wire_prepare_data_mark_finished (db_plugin->cls,
- session,
+ "Wire transaction %llu failed: %u/%d\n",
+ (unsigned long long) wpd->row_id,
+ tr->http_status,
+ tr->ec);
+ qs = db_plugin->wire_prepare_data_mark_failed (db_plugin->cls,
wpd->row_id);
- if (0 >= qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- db_plugin->rollback (db_plugin->cls,
- session);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- /* try again */
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_transfers,
- NULL);
- }
- else
+ /* continued below */
+ break;
+ case 0:
+ case MHD_HTTP_TOO_MANY_REQUESTS:
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ case MHD_HTTP_BAD_GATEWAY:
+ case MHD_HTTP_SERVICE_UNAVAILABLE:
+ case MHD_HTTP_GATEWAY_TIMEOUT:
+ wpd->retries++;
+ if (wpd->retries < MAX_RETRIES)
{
- global_ret = GR_DATABASE_COMMIT_HARD_FAIL;
- GNUNET_SCHEDULER_shutdown ();
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Wire transfer %llu failed (%u), trying again\n",
+ (unsigned long long) wpd->row_id,
+ tr->http_status);
+ wpd->eh = TALER_BANK_transfer (ctx,
+ wpd->wa->auth,
+ &wpd[1],
+ wpd->buf_size,
+ &wire_confirm_cb,
+ wpd);
+ return;
}
- GNUNET_free (wpd);
- wpd = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Wire transaction %llu failed: %u/%d\n",
+ (unsigned long long) wpd->row_id,
+ tr->http_status,
+ tr->ec);
+ cleanup_wpd ();
+ db_plugin->rollback (db_plugin->cls);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Wire transfer %llu failed: %u/%d\n",
+ (unsigned long long) wpd->row_id,
+ tr->http_status,
+ tr->ec);
+ db_plugin->rollback (db_plugin->cls);
+ cleanup_wpd ();
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
- GNUNET_free (wpd);
- wpd = NULL;
- switch (commit_or_warn (session))
+ shard->batch_end = GNUNET_MAX (wpd->row_id,
+ shard->batch_end);
+ switch (qs)
{
case GNUNET_DB_STATUS_SOFT_ERROR:
- /* try again */
+ db_plugin->rollback (db_plugin->cls);
+ cleanup_wpd ();
GNUNET_assert (NULL == task);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Serialization failure, trying again immediately!\n");
task = GNUNET_SCHEDULER_add_now (&run_transfers,
NULL);
return;
case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- global_ret = GR_DATABASE_COMMIT_HARD_FAIL;
+ db_plugin->rollback (db_plugin->cls);
+ cleanup_wpd ();
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Wire transfer complete\n");
- /* continue with #run_transfers(), just to guard
- against the unlikely case that there are more. */
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_transfers,
- NULL);
- return;
- default:
- GNUNET_break (0);
- global_ret = GR_INVARIANT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_CONTAINER_DLL_remove (wpd_head,
+ wpd_tail,
+ wpd);
+ GNUNET_free (wpd);
+ break;
}
+ if (NULL != wpd_head)
+ return; /* wait for other queries to complete */
+ batch_done ();
}
@@ -352,19 +530,40 @@ wire_prepare_cb (void *cls,
const char *buf,
size_t buf_size)
{
- struct TALER_EXCHANGEDB_WireAccount *wa;
+ struct WirePrepareData *wpd;
(void) cls;
+ if ( (NULL != task) ||
+ (EXIT_SUCCESS != global_ret) )
+ return; /* current transaction was aborted */
+ if (rowid >= shard->shard_end)
+ {
+ /* skip */
+ shard->batch_end = shard->shard_end - 1;
+ if (NULL != wpd_head)
+ return;
+ batch_done ();
+ return;
+ }
if ( (NULL == wire_method) ||
(NULL == buf) )
{
GNUNET_break (0);
- db_plugin->rollback (db_plugin->cls,
- wpd->session);
- global_ret = GR_DATABASE_FETCH_FAILURE;
- goto cleanup;
+ db_plugin->rollback (db_plugin->cls);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
}
+ wpd = GNUNET_malloc (sizeof (struct WirePrepareData)
+ + buf_size);
+ GNUNET_memcpy (&wpd[1],
+ buf,
+ buf_size);
+ wpd->buf_size = buf_size;
wpd->row_id = rowid;
+ GNUNET_CONTAINER_DLL_insert (wpd_head,
+ wpd_tail,
+ wpd);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Starting wire transfer %llu\n",
(unsigned long long) rowid);
@@ -374,31 +573,27 @@ wire_prepare_cb (void *cls,
/* Should really never happen here, as when we get
here the wire account should be in the cache. */
GNUNET_break (0);
- db_plugin->rollback (db_plugin->cls,
- wpd->session);
- global_ret = GR_WIRE_ACCOUNT_NOT_CONFIGURED;
- goto cleanup;
+ cleanup_wpd ();
+ db_plugin->rollback (db_plugin->cls);
+ global_ret = EXIT_NO_RESTART;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
}
- wa = wpd->wa;
wpd->eh = TALER_BANK_transfer (ctx,
- &wa->auth,
+ wpd->wa->auth,
buf,
buf_size,
&wire_confirm_cb,
- NULL);
+ wpd);
if (NULL == wpd->eh)
{
GNUNET_break (0); /* Irrecoverable */
- db_plugin->rollback (db_plugin->cls,
- wpd->session);
- global_ret = GR_WIRE_TRANSFER_BEGIN_FAIL;
- goto cleanup;
+ cleanup_wpd ();
+ db_plugin->rollback (db_plugin->cls);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
}
- return;
-cleanup:
- GNUNET_SCHEDULER_shutdown ();
- GNUNET_free (wpd);
- wpd = NULL;
}
@@ -412,58 +607,98 @@ static void
run_transfers (void *cls)
{
enum GNUNET_DB_QueryStatus qs;
- struct TALER_EXCHANGEDB_Session *session;
+ int64_t limit;
(void) cls;
task = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Checking for pending wire transfers\n");
- if (NULL == (session = db_plugin->get_session (db_plugin->cls)))
+ limit = shard->shard_end - shard->batch_start;
+ if (0 >= limit)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to obtain database session!\n");
- global_ret = GR_DATABASE_SESSION_START_FAIL;
- GNUNET_SCHEDULER_shutdown ();
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Shard [%llu,%llu) completed\n",
+ (unsigned long long) shard->shard_start,
+ (unsigned long long) shard->batch_end);
+ qs = db_plugin->complete_shard (db_plugin->cls,
+ "transfer",
+ shard->shard_start,
+ shard->batch_end + 1);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ GNUNET_free (shard);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Got DB soft error for complete_shard. Rolling back.\n");
+ GNUNET_free (shard);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&select_shard,
+ NULL);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* already existed, ok, let's just continue */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* normal case */
+ break;
+ }
+ shard_delay = GNUNET_TIME_absolute_get_duration (
+ shard->shard_start_time);
+ GNUNET_free (shard);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&select_shard,
+ NULL);
return;
}
+ /* cap number of parallel connections to a reasonable
+ limit for concurrent requests to the bank */
+ limit = GNUNET_MIN (limit,
+ 256);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking for %lld pending wire transfers [%llu-...)\n",
+ (long long) limit,
+ (unsigned long long) shard->batch_start);
if (GNUNET_OK !=
- db_plugin->start (db_plugin->cls,
- session,
- "aggregator run transfer"))
+ db_plugin->start_read_committed (db_plugin->cls,
+ "aggregator run transfer"))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to start database transaction!\n");
- global_ret = GR_DATABASE_TRANSACTION_BEGIN_FAIL;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
- wpd = GNUNET_new (struct WirePrepareData);
- wpd->session = session;
+ GNUNET_assert (NULL == task);
qs = db_plugin->wire_prepare_data_get (db_plugin->cls,
- session,
+ shard->batch_start,
+ limit,
&wire_prepare_cb,
NULL);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- return; /* continued via continuation set in #wire_prepare_cb() */
- db_plugin->rollback (db_plugin->cls,
- session);
- GNUNET_free (wpd);
- wpd = NULL;
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
+ cleanup_wpd ();
+ db_plugin->rollback (db_plugin->cls);
GNUNET_break (0);
- global_ret = GR_DATABASE_COMMIT_HARD_FAIL;
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
/* try again */
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Serialization failure, trying again immediately!\n");
+ cleanup_wpd ();
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_transfers,
NULL);
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
/* no more prepared wire transfers, go sleep a bit! */
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_assert (NULL == wpd_head);
GNUNET_assert (NULL == task);
if (GNUNET_YES == test_mode)
{
@@ -475,15 +710,105 @@ run_transfers (void *cls)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"No more pending wire transfers, going idle\n");
- task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
- &run_transfers,
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_delayed (transfer_idle_sleep_interval,
+ &run_transfers_delayed,
+ NULL);
+ }
+ return;
+ default:
+ /* continued in wire_prepare_cb() */
+ return;
+ }
+}
+
+
+/**
+ * Select shard to process.
+ *
+ * @param cls NULL
+ */
+static void
+select_shard (void *cls)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Relative delay;
+ uint64_t start;
+ uint64_t end;
+
+ (void) cls;
+ task = NULL;
+ GNUNET_assert (NULL == wpd_head);
+ if (GNUNET_SYSERR ==
+ db_plugin->preflight (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain database connection!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (0 == max_workers)
+ delay = GNUNET_TIME_UNIT_ZERO;
+ else
+ delay.rel_value_us = GNUNET_CRYPTO_random_u64 (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ 4 * GNUNET_TIME_relative_max (
+ transfer_idle_sleep_interval,
+ GNUNET_TIME_relative_multiply (shard_delay,
+ max_workers)).rel_value_us);
+ qs = db_plugin->begin_shard (db_plugin->cls,
+ "transfer",
+ delay,
+ shard_size,
+ &start,
+ &end);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain starting point for montoring from database!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* try again */
+ {
+ serialization_delay = GNUNET_TIME_randomized_backoff (serialization_delay,
+ GNUNET_TIME_UNIT_SECONDS);
+ GNUNET_assert (NULL == task);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Serialization failure, trying again in %s!\n",
+ GNUNET_TIME_relative2s (serialization_delay,
+ true));
+ task = GNUNET_SCHEDULER_add_delayed (serialization_delay,
+ &select_shard,
NULL);
}
return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_delayed (transfer_idle_sleep_interval,
+ &select_shard,
+ NULL);
+ return;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* should be impossible */
- GNUNET_assert (0);
+ /* continued below */
+ break;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting with shard [%llu,%llu)\n",
+ (unsigned long long) start,
+ (unsigned long long) end);
+ shard = GNUNET_new (struct Shard);
+ shard->shard_start_time = GNUNET_TIME_absolute_get ();
+ shard->shard_start = start;
+ shard->shard_end = end;
+ shard->batch_start = start;
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_transfers,
+ NULL);
}
@@ -506,10 +831,10 @@ run (void *cls,
(void) cfgfile;
cfg = c;
- if (GNUNET_OK != parse_wirewatch_config ())
+ if (GNUNET_OK != parse_transfer_config ())
{
cfg = NULL;
- global_ret = GR_CONFIGURATION_INVALID;
+ global_ret = EXIT_NOTCONFIGURED;
return;
}
ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
@@ -520,9 +845,17 @@ run (void *cls,
GNUNET_break (0);
return;
}
-
+ if (GNUNET_SYSERR ==
+ db_plugin->preflight (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain database connection!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_transfers,
+ task = GNUNET_SCHEDULER_add_now (&select_shard,
NULL);
GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
cls);
@@ -541,32 +874,44 @@ main (int argc,
char *const *argv)
{
struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_uint ('S',
+ "size",
+ "SIZE",
+ "Size to process per shard (default: 1024)",
+ &shard_size),
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_option_flag ('t',
"test",
"run in test mode and exit when idle",
&test_mode),
+ GNUNET_GETOPT_option_uint ('w',
+ "workers",
+ "COUNT",
+ "Plan work load with up to COUNT worker processes (default: 16)",
+ &max_workers),
GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue ret;
if (GNUNET_OK !=
GNUNET_STRINGS_get_utf8_args (argc, argv,
&argc, &argv))
- return GR_CMD_LINE_UTF8_ERROR;
- if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc, argv,
- "taler-exchange-transfer",
- gettext_noop (
- "background process that executes outgoing wire transfers"),
- options,
- &run, NULL))
- {
- GNUNET_free ((void *) argv);
- return GR_CMD_LINE_OPTIONS_WRONG;
- }
- GNUNET_free ((void *) argv);
+ return EXIT_INVALIDARGUMENT;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-exchange-transfer",
+ gettext_noop (
+ "background process that executes outgoing wire transfers"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
return global_ret;
}
diff --git a/src/exchange/taler-exchange-wirewatch.c b/src/exchange/taler-exchange-wirewatch.c
index fba002970..da5d9c098 100644
--- a/src/exchange/taler-exchange-wirewatch.c
+++ b/src/exchange/taler-exchange-wirewatch.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2016--2020 Taler Systems SA
+ Copyright (C) 2016--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
@@ -13,7 +13,6 @@
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-wirewatch.c
* @brief Process that watches for wire transfers to the exchange's bank account
@@ -29,106 +28,125 @@
#include "taler_json_lib.h"
#include "taler_bank_service.h"
-#define DEBUG_LOGGING 0
/**
- * What is the initial batch size we use for credit history
+ * How long to wait for an HTTP reply if there
+ * are no transactions pending at the server?
+ */
+#define LONGPOLL_TIMEOUT GNUNET_TIME_UNIT_MINUTES
+
+/**
+ * What is the maximum batch size we use for credit history
* requests with the bank. See `batch_size` below.
*/
-#define INITIAL_BATCH_SIZE 1024
+#define MAXIMUM_BATCH_SIZE 1024
/**
- * Information we keep for each supported account.
+ * Information about our account.
*/
-struct WireAccount
-{
- /**
- * Accounts are kept in a DLL.
- */
- struct WireAccount *next;
+static const struct TALER_EXCHANGEDB_AccountInfo *ai;
- /**
- * Plugins are kept in a DLL.
- */
- struct WireAccount *prev;
+/**
+ * Active request for history.
+ */
+static struct TALER_BANK_CreditHistoryHandle *hh;
- /**
- * Name of the section that configures this account.
- */
- char *section_name;
+/**
+ * Set to true if the request for history did actually
+ * return transaction items.
+ */
+static bool hh_returned_data;
- /**
- * Database session we are using for the current transaction.
- */
- struct TALER_EXCHANGEDB_Session *session;
+/**
+ * Set to true if the request for history did not
+ * succeed because the account was unknown.
+ */
+static bool hh_account_404;
- /**
- * Active request for history.
- */
- struct TALER_BANK_CreditHistoryHandle *hh;
+/**
+ * When did we start the last @e hh request?
+ */
+static struct GNUNET_TIME_Absolute hh_start_time;
- /**
- * Authentication data.
- */
- struct TALER_BANK_AuthenticationData auth;
+/**
+ * Until when is processing this wire plugin delayed?
+ */
+static struct GNUNET_TIME_Absolute delayed_until;
- /**
- * Until when is processing this wire plugin delayed?
- */
- struct GNUNET_TIME_Absolute delayed_until;
+/**
+ * Encoded offset in the wire transfer list from where
+ * to start the next query with the bank.
+ */
+static uint64_t batch_start;
- /**
- * Encoded offset in the wire transfer list from where
- * to start the next query with the bank.
- */
- uint64_t last_row_off;
+/**
+ * Latest row offset seen in this transaction, becomes
+ * the new #batch_start upon commit.
+ */
+static uint64_t latest_row_off;
- /**
- * Latest row offset seen in this transaction, becomes
- * the new #last_row_off upon commit.
- */
- uint64_t latest_row_off;
+/**
+ * Offset where our current shard begins (inclusive).
+ */
+static uint64_t shard_start;
- /**
- * How many transactions do we retrieve per batch?
- */
- unsigned int batch_size;
+/**
+ * Offset where our current shard ends (exclusive).
+ */
+static uint64_t shard_end;
- /**
- * How many transactions did we see in the current batch?
- */
- unsigned int current_batch_size;
+/**
+ * When did we start with the shard?
+ */
+static struct GNUNET_TIME_Absolute shard_start_time;
- /**
- * Are we running from scratch and should re-process all transactions
- * for this account?
- */
- int reset_mode;
+/**
+ * For how long did we lock the shard?
+ */
+static struct GNUNET_TIME_Absolute shard_end_time;
- /**
- * Should we delay the next request to the wire plugin a bit? Set to
- * #GNUNET_NO if we actually did some work.
- */
- int delay;
+/**
+ * How long did we take to finish the last shard
+ * for this account?
+ */
+static struct GNUNET_TIME_Relative shard_delay;
-};
+/**
+ * How long did we take to finish the last shard
+ * for this account?
+ */
+static struct GNUNET_TIME_Relative longpoll_timeout;
+/**
+ * Name of our job in the shard table.
+ */
+static char *job_name;
/**
- * Head of list of loaded wire plugins.
+ * How many transactions do we retrieve per batch?
*/
-static struct WireAccount *wa_head;
+static unsigned int batch_size;
/**
- * Tail of list of loaded wire plugins.
+ * How much do we increment @e batch_size on success?
*/
-static struct WireAccount *wa_tail;
+static unsigned int batch_thresh;
/**
- * Wire account we are currently processing. This would go away
- * if we ever start processing all accounts in parallel.
+ * Did work remain in the transaction queue? Set to true
+ * if we did some work and thus there might be more.
*/
-static struct WireAccount *wa_pos;
+static bool progress;
+
+/**
+ * Did we start a transaction yet?
+ */
+static bool started_transaction;
+
+/**
+ * Is this shard still open for processing.
+ */
+static bool shard_open;
/**
* Handle to the context for interacting with the bank.
@@ -152,24 +170,39 @@ static struct TALER_EXCHANGEDB_Plugin *db_plugin;
/**
* How long should we sleep when idle before trying to find more work?
+ * Also used for how long we wait to grab a shard before trying it again.
+ * The value should be set to a bit above the average time it takes to
+ * process a shard.
*/
static struct GNUNET_TIME_Relative wirewatch_idle_sleep_interval;
/**
+ * How long do we sleep on serialization conflicts?
+ */
+static struct GNUNET_TIME_Relative wirewatch_conflict_sleep_interval;
+
+/**
+ * Modulus to apply to group shards. The shard size must ultimately be a
+ * multiple of the batch size. Thus, if this is not a multiple of the
+ * #MAXIMUM_BATCH_SIZE, the batch size will be set to the #shard_size.
+ */
+static unsigned int shard_size = MAXIMUM_BATCH_SIZE;
+
+/**
+ * How many workers should we plan our scheduling with?
+ */
+static unsigned int max_workers = 16;
+
+/**
+ * -e command-line option: exit on errors talking to the bank?
+ */
+static int exit_on_error;
+
+/**
* Value to return from main(). 0 on success, non-zero on
* on serious errors.
*/
-static enum
-{
- GR_SUCCESS = 0,
- GR_DATABASE_SESSION_FAIL = 1,
- GR_DATABASE_TRANSACTION_BEGIN_FAIL = 2,
- GR_DATABASE_SELECT_LATEST_HARD_FAIL = 3,
- GR_BANK_REQUEST_HISTORY_FAIL = 4,
- GR_CONFIGURATION_INVALID = 5,
- GR_CMD_LINE_UTF8_ERROR = 6,
- GR_CMD_LINE_OPTIONS_WRONG = 7,
-} global_ret;
+static int global_ret;
/**
* Are we run in testing mode and should only do one pass?
@@ -177,15 +210,20 @@ static enum
static int test_mode;
/**
- * Are we running from scratch and should re-process all transactions?
+ * Should we ignore if the bank does not know our bank
+ * account?
*/
-static int reset_mode;
+static int ignore_account_404;
/**
* Current task waiting for execution, if any.
*/
static struct GNUNET_SCHEDULER_Task *task;
+/**
+ * Name of the configuration section with the account we should watch.
+ */
+static char *account_section;
/**
* We're being aborted with CTRL-C (or SIGTERM). Shut down.
@@ -195,27 +233,32 @@ static struct GNUNET_SCHEDULER_Task *task;
static void
shutdown_task (void *cls)
{
+ enum GNUNET_DB_QueryStatus qs;
(void) cls;
- {
- struct WireAccount *wa;
- while (NULL != (wa = wa_head))
- {
- if (NULL != wa->hh)
- {
- TALER_BANK_credit_history_cancel (wa->hh);
- wa->hh = NULL;
- }
- GNUNET_CONTAINER_DLL_remove (wa_head,
- wa_tail,
- wa);
- TALER_BANK_auth_free (&wa->auth);
- GNUNET_free (wa->section_name);
- GNUNET_free (wa);
- }
+ if (NULL != hh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "History request cancelled on shutdown\n");
+ TALER_BANK_credit_history_cancel (hh);
+ hh = NULL;
}
- wa_pos = NULL;
-
+ if (started_transaction)
+ {
+ db_plugin->rollback (db_plugin->cls);
+ started_transaction = false;
+ }
+ if (shard_open)
+ {
+ qs = db_plugin->abort_shard (db_plugin->cls,
+ job_name,
+ shard_start,
+ shard_end);
+ if (qs <= 0)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to abort work shard on shutdown\n");
+ }
+ GNUNET_free (job_name);
if (NULL != ctx)
{
GNUNET_CURL_fini (ctx);
@@ -233,6 +276,8 @@ shutdown_task (void *cls)
}
TALER_EXCHANGEDB_plugin_unload (db_plugin);
db_plugin = NULL;
+ TALER_EXCHANGEDB_unload_accounts ();
+ cfg = NULL;
}
@@ -241,35 +286,36 @@ shutdown_task (void *cls)
* account to our list (if it is enabled and we can load the plugin).
*
* @param cls closure, NULL
- * @param ai account information
+ * @param in_ai account information
*/
static void
add_account_cb (void *cls,
- const struct TALER_EXCHANGEDB_AccountInfo *ai)
+ const struct TALER_EXCHANGEDB_AccountInfo *in_ai)
{
- struct WireAccount *wa;
-
(void) cls;
- if (GNUNET_YES != ai->credit_enabled)
+ if (! in_ai->credit_enabled)
return; /* not enabled for us, skip */
- wa = GNUNET_new (struct WireAccount);
- wa->reset_mode = reset_mode;
- if (GNUNET_OK !=
- TALER_BANK_auth_parse_cfg (cfg,
- ai->section_name,
- &wa->auth))
+ if ( (NULL != account_section) &&
+ (0 != strcasecmp (in_ai->section_name,
+ account_section)) )
+ return; /* not enabled for us, skip */
+ if (NULL != ai)
{
- GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
- "Failed to load account `%s'\n",
- ai->section_name);
- GNUNET_free (wa);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Multiple accounts enabled (%s and %s), use '-a' command-line option to select one!\n",
+ ai->section_name,
+ in_ai->section_name);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_INVALIDARGUMENT;
return;
}
- wa->section_name = GNUNET_strdup (ai->section_name);
- wa->batch_size = INITIAL_BATCH_SIZE;
- GNUNET_CONTAINER_DLL_insert (wa_head,
- wa_tail,
- wa);
+ ai = in_ai;
+ GNUNET_asprintf (&job_name,
+ "wirewatch-%s",
+ ai->section_name);
+ batch_size = MAXIMUM_BATCH_SIZE;
+ if (0 != shard_size % batch_size)
+ batch_size = shard_size;
}
@@ -279,7 +325,7 @@ add_account_cb (void *cls,
*
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
exchange_serve_process_config (void)
{
if (GNUNET_OK !=
@@ -296,18 +342,26 @@ exchange_serve_process_config (void)
if (NULL ==
(db_plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
{
- fprintf (stderr,
- "Failed to initialize DB subsystem\n");
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem\n");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGEDB_load_accounts (cfg,
+ TALER_EXCHANGEDB_ALO_CREDIT
+ | TALER_EXCHANGEDB_ALO_AUTHDATA))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No wire accounts configured for credit!\n");
return GNUNET_SYSERR;
}
- TALER_EXCHANGEDB_find_accounts (cfg,
- &add_account_cb,
+ TALER_EXCHANGEDB_find_accounts (&add_account_cb,
NULL);
- if (NULL == wa_head)
+ if (NULL == ai)
{
- fprintf (stderr,
- "No wire accounts configured for credit!\n");
- TALER_EXCHANGEDB_plugin_unload (db_plugin);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No accounts enabled for credit!\n");
+ GNUNET_SCHEDULER_shutdown ();
return GNUNET_SYSERR;
}
return GNUNET_OK;
@@ -315,248 +369,550 @@ exchange_serve_process_config (void)
/**
- * Query for incoming wire transfers.
+ * Lock a shard and then begin to query for incoming wire transfers.
*
* @param cls NULL
*/
static void
-find_transfers (void *cls);
+lock_shard (void *cls);
/**
- * Callbacks of this type are used to serve the result of asking
- * the bank for the transaction history.
+ * Continue with the credit history of the shard.
*
- * @param cls closure with the `struct WioreAccount *` we are processing
- * @param http_status HTTP status code from the server
- * @param ec taler error code
- * @param serial_id identification of the position at which we are querying
- * @param details details about the wire transfer
- * @param json raw JSON response
- * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
- */
-static int
-history_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint64_t serial_id,
- const struct TALER_BANK_CreditDetails *details,
- const json_t *json)
+ * @param cls NULL
+ */
+static void
+continue_with_shard (void *cls);
+
+
+/**
+ * We encountered a serialization error. Rollback the transaction and try
+ * again.
+ */
+static void
+handle_soft_error (void)
+{
+ db_plugin->rollback (db_plugin->cls);
+ started_transaction = false;
+ if (1 < batch_size)
+ {
+ batch_thresh = batch_size;
+ batch_size /= 2;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Reduced batch size to %llu due to serialization issue\n",
+ (unsigned long long) batch_size);
+ }
+ /* Reset to beginning of transaction, and go again
+ from there. */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Encountered soft error, resetting start point to batch start\n");
+ latest_row_off = batch_start;
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
+ NULL);
+}
+
+
+/**
+ * Schedule the #lock_shard() operation.
+ */
+static void
+schedule_transfers (void)
+{
+ if (shard_open)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Will retry my shard (%llu,%llu] of %s in %s\n",
+ (unsigned long long) shard_start,
+ (unsigned long long) shard_end,
+ job_name,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_remaining (delayed_until),
+ true));
+ else
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Will try to lock next shard of %s in %s\n",
+ job_name,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_remaining (delayed_until),
+ true));
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_at (delayed_until,
+ &lock_shard,
+ NULL);
+}
+
+
+/**
+ * We are done with the work that is possible right now (and the transaction
+ * was committed, if there was one to commit). Move on to the next shard.
+ */
+static void
+transaction_completed (void)
+{
+ if ( (batch_start + batch_size ==
+ latest_row_off) &&
+ (batch_size < MAXIMUM_BATCH_SIZE) )
+ {
+ /* The current batch size worked without serialization
+ issues, and we are allowed to grow. Do so slowly. */
+ int delta;
+
+ delta = ((int) batch_thresh - (int) batch_size) / 4;
+ if (delta < 0)
+ delta = -delta;
+ batch_size = GNUNET_MIN (MAXIMUM_BATCH_SIZE,
+ batch_size + delta + 1);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Increasing batch size to %llu\n",
+ (unsigned long long) batch_size);
+ }
+
+ if ( (! progress) && test_mode)
+ {
+ /* Transaction list was drained and we are in
+ test mode. So we are done. */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Transaction list drained and in test mode. Exiting\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (! (hh_returned_data || hh_account_404) )
+ {
+ /* Enforce long-polling delay even if the server ignored it
+ and returned earlier */
+ struct GNUNET_TIME_Relative latency;
+ struct GNUNET_TIME_Relative left;
+
+ 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_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);
+ schedule_transfers ();
+}
+
+
+/**
+ * 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 (const struct TALER_BANK_CreditDetails *details,
+ unsigned int details_length)
{
- struct WireAccount *wa = cls;
- struct TALER_EXCHANGEDB_Session *session = wa->session;
enum GNUNET_DB_QueryStatus qs;
+ bool shard_done;
+ uint64_t lroff = latest_row_off;
- (void) json;
- if (NULL == details)
+ if (0 == details_length)
{
- wa->hh = NULL;
- if (TALER_EC_NONE != ec)
+ /* 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,
- "Error fetching history: ec=%u, http_status=%u\n",
- (unsigned int) ec,
- http_status);
+ "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;
}
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "End of list. Committing progress!\n");
- qs = db_plugin->commit (db_plugin->cls,
- session);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ if (cd->serial_id > shard_end)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Got DB soft error for commit\n");
- /* reduce transaction size to reduce rollback probability */
- if (2 > wa->current_batch_size)
- wa->current_batch_size /= 2;
- /* try again */
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&find_transfers,
- NULL);
- return GNUNET_OK; /* will be ignored anyway */
+ /* 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;
}
- if (0 < qs)
+ 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++)
{
- /* transaction success, update #last_row_off */
- wa->last_row_off = wa->latest_row_off;
- wa->latest_row_off = 0; /* should not be needed */
- wa->session = NULL; /* should not be needed */
- /* if successful at limit, try increasing transaction batch size (AIMD) */
- if ( (wa->current_batch_size == wa->batch_size) &&
- (UINT_MAX > wa->batch_size) )
- wa->batch_size++;
+ 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;
}
- GNUNET_break (0 <= qs);
- if ( (GNUNET_YES == wa->delay) &&
- (test_mode) &&
- (NULL == wa->next) )
+ qs = db_plugin->reserves_in_insert (db_plugin->cls,
+ reserves,
+ details_length,
+ qss);
+ switch (qs)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Shutdown due to test mode!\n");
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
- return GNUNET_OK;
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got DB soft error for reserves_in_insert (%u). Rolling back.\n",
+ details_length);
+ handle_soft_error ();
+ return;
+ default:
+ break;
}
- if (GNUNET_YES == wa->delay)
+ for (unsigned int i = 0; i<details_length; i++)
{
- wa->delayed_until
- = GNUNET_TIME_relative_to_absolute (wirewatch_idle_sleep_interval);
- wa_pos = wa_pos->next;
- if (NULL == wa_pos)
- wa_pos = wa_head;
- GNUNET_assert (NULL != wa_pos);
+ 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.\n",
+ (unsigned long long) cd->serial_id);
+ /* normal case */
+ progress = true;
+ break;
+ }
}
- task = GNUNET_SCHEDULER_add_at (wa_pos->delayed_until,
- &find_transfers,
- NULL);
- return GNUNET_OK; /* will be ignored anyway */
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Adding wire transfer over %s with (hashed) subject `%s'\n",
- TALER_amount2s (&details->amount),
- TALER_B2S (&details->reserve_pub));
-
- /**
- * Debug block.
- */
-#if DEBUG_LOGGING
+
+ latest_row_off = lroff;
+ shard_done = (shard_end <= latest_row_off);
+ if (shard_done)
{
- /** Should be 53, give 80 just to be extra conservative (and aligned). */
-#define PUBSIZE 80
- char wtid_s[PUBSIZE];
-
- GNUNET_break (NULL !=
- GNUNET_STRINGS_data_to_string (&details->reserve_pub,
- sizeof (details->reserve_pub),
- &wtid_s[0],
- PUBSIZE));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Plain text subject (= reserve_pub): %s\n",
- wtid_s);
+ /* 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;
}
-#endif
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
+ NULL);
+}
+
- if (wa->current_batch_size < UINT_MAX)
- wa->current_batch_size++;
- qs = db_plugin->reserves_in_insert (db_plugin->cls,
- session,
- &details->reserve_pub,
- &details->amount,
- details->execution_date,
- details->debit_account_url,
- wa->section_name,
- serial_id);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+/**
+ * Callbacks of this type are used to serve the result of asking
+ * the bank for the transaction history.
+ *
+ * @param cls NULL
+ * @param reply response we got from the bank
+ */
+static void
+history_cb (void *cls,
+ const struct TALER_BANK_CreditHistoryResponse *reply)
+{
+ (void) cls;
+ GNUNET_assert (NULL == task);
+ hh = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "History request returned with HTTP status %u\n",
+ reply->http_status);
+ switch (reply->http_status)
{
- GNUNET_break (0);
- db_plugin->rollback (db_plugin->cls,
- session);
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_SYSERR;
+ case MHD_HTTP_OK:
+ process_reply (reply->details.ok.details,
+ reply->details.ok.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 ();
+ return;
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error fetching history: %s (%u)\n",
+ TALER_ErrorCode_get_hint (reply->ec),
+ reply->http_status);
+ break;
}
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ if (! exit_on_error)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Got DB soft error for reserves_in_insert. Rolling back.\n");
- db_plugin->rollback (db_plugin->cls,
- session);
- /* try again */
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&find_transfers,
- NULL);
- return GNUNET_SYSERR;
+ transaction_completed ();
+ return;
+ }
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+static void
+continue_with_shard (void *cls)
+{
+ unsigned int limit;
+
+ (void) cls;
+ task = NULL;
+ GNUNET_assert (shard_end > latest_row_off);
+ limit = GNUNET_MIN (batch_size,
+ shard_end - latest_row_off);
+ GNUNET_assert (NULL == hh);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "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,
+ limit,
+ test_mode
+ ? GNUNET_TIME_UNIT_ZERO
+ : longpoll_timeout,
+ &history_cb,
+ NULL);
+ if (NULL == hh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start request for account history!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
}
- wa->delay = GNUNET_NO;
- wa->latest_row_off = serial_id;
- return GNUNET_OK;
}
/**
- * Query for incoming wire transfers.
+ * Reserve a shard for us to work on.
*
* @param cls NULL
*/
static void
-find_transfers (void *cls)
+lock_shard (void *cls)
{
- struct TALER_EXCHANGEDB_Session *session;
enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Relative delay;
+ uint64_t last_shard_start = shard_start;
+ uint64_t last_shard_end = shard_end;
(void) cls;
task = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Checking for incoming wire transfers\n");
- if (NULL == (session = db_plugin->get_session (db_plugin->cls)))
+ if (GNUNET_SYSERR ==
+ db_plugin->preflight (db_plugin->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to obtain database session!\n");
- global_ret = GR_DATABASE_SESSION_FAIL;
+ "Failed to obtain database connection!\n");
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
- db_plugin->preflight (db_plugin->cls,
- session);
- if (GNUNET_OK !=
- db_plugin->start (db_plugin->cls,
- session,
- "wirewatch check for incoming wire transfers"))
+ if ( (shard_open) &&
+ (GNUNET_TIME_absolute_is_future (shard_end_time)) )
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to start database transaction!\n");
- global_ret = GR_DATABASE_TRANSACTION_BEGIN_FAIL;
- GNUNET_SCHEDULER_shutdown ();
+ progress = false;
+ batch_start = latest_row_off;
+ task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
+ NULL);
return;
}
- if (! wa_pos->reset_mode)
+ if (shard_open)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Shard not completed in time, will try to re-acquire\n");
+ /* How long we lock a shard depends on the number of
+ workers expected, and how long we usually took to
+ process a shard. */
+ if (0 == max_workers)
+ delay = GNUNET_TIME_UNIT_ZERO;
+ else
+ delay.rel_value_us = GNUNET_CRYPTO_random_u64 (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ 4 * GNUNET_TIME_relative_max (
+ wirewatch_idle_sleep_interval,
+ GNUNET_TIME_relative_multiply (shard_delay,
+ max_workers)).rel_value_us);
+ shard_start_time = GNUNET_TIME_absolute_get ();
+ qs = db_plugin->begin_shard (db_plugin->cls,
+ job_name,
+ delay,
+ shard_size,
+ &shard_start,
+ &shard_end);
+ switch (qs)
{
- qs = db_plugin->get_latest_reserve_in_reference (db_plugin->cls,
- session,
- wa_pos->section_name,
- &wa_pos->last_row_off);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to obtain starting point for montoring from database!\n");
- db_plugin->rollback (db_plugin->cls,
- session);
- global_ret = GR_DATABASE_SELECT_LATEST_HARD_FAIL;
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain starting point for montoring from database!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* try again */
{
- /* try again */
- db_plugin->rollback (db_plugin->cls,
- session);
- task = GNUNET_SCHEDULER_add_now (&find_transfers,
- NULL);
- return;
+ struct GNUNET_TIME_Relative rdelay;
+
+ wirewatch_conflict_sleep_interval
+ = GNUNET_TIME_STD_BACKOFF (wirewatch_conflict_sleep_interval);
+ rdelay = GNUNET_TIME_randomize (wirewatch_conflict_sleep_interval);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Serialization error tying to obtain shard %s, will try again in %s!\n",
+ 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);
}
- wa_pos->reset_mode = GNUNET_NO;
+ GNUNET_assert (NULL == task);
+ schedule_transfers ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No shard available, will try again for %s in %s!\n",
+ job_name,
+ GNUNET_STRINGS_relative_time_to_string (
+ wirewatch_idle_sleep_interval,
+ true));
+ delayed_until = GNUNET_TIME_relative_to_absolute (
+ wirewatch_idle_sleep_interval);
+ shard_open = false;
+ GNUNET_assert (NULL == task);
+ schedule_transfers ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* continued below */
+ wirewatch_conflict_sleep_interval = GNUNET_TIME_UNIT_ZERO;
+ break;
}
- wa_pos->delay = GNUNET_YES;
- wa_pos->current_batch_size = 0; /* reset counter */
-
+ shard_end_time = GNUNET_TIME_relative_to_absolute (delay);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "wirewatch: requesting incoming history from %s\n",
- wa_pos->auth.wire_gateway_url);
- wa_pos->session = session;
- wa_pos->hh = TALER_BANK_credit_history (ctx,
- &wa_pos->auth,
- wa_pos->last_row_off,
- wa_pos->batch_size,
- &history_cb,
- wa_pos);
- if (NULL == wa_pos->hh)
+ "Starting with shard %s at (%llu,%llu] locked for %s\n",
+ job_name,
+ (unsigned long long) shard_start,
+ (unsigned long long) shard_end,
+ GNUNET_STRINGS_relative_time_to_string (delay,
+ true));
+ progress = false;
+ batch_start = shard_start;
+ if ( (shard_open) &&
+ (shard_start == last_shard_start) &&
+ (shard_end == last_shard_end) )
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to start request for account history!\n");
- db_plugin->rollback (db_plugin->cls,
- session);
- global_ret = GR_BANK_REQUEST_HISTORY_FAIL;
- GNUNET_SCHEDULER_shutdown ();
- return;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Continuing from %llu\n",
+ (unsigned long long) latest_row_off);
+ GNUNET_break (latest_row_off >= batch_start); /* resume where we left things */
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resetting shard start to original start point (%d)\n",
+ shard_open ? 1 : 0);
+ latest_row_off = batch_start;
}
+ shard_open = true;
+ task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
+ NULL);
}
@@ -577,27 +933,28 @@ run (void *cls,
(void) cls;
(void) args;
(void) cfgfile;
+
cfg = c;
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ cls);
if (GNUNET_OK !=
exchange_serve_process_config ())
{
- global_ret = GR_CONFIGURATION_INVALID;
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
- wa_pos = wa_head;
- GNUNET_assert (NULL != wa_pos);
- task = GNUNET_SCHEDULER_add_now (&find_transfers,
- NULL);
- GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
- cls);
ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
&rc);
- rc = GNUNET_CURL_gnunet_rc_create (ctx);
if (NULL == ctx)
{
GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NO_RESTART;
return;
}
+ rc = GNUNET_CURL_gnunet_rc_create (ctx);
+ schedule_transfers ();
}
@@ -613,35 +970,63 @@ main (int argc,
char *const *argv)
{
struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_string ('a',
+ "account",
+ "SECTION_NAME",
+ "name of the configuration section with the account we should watch (needed if more than one is enabled for crediting)",
+ &account_section),
+ GNUNET_GETOPT_option_flag ('e',
+ "exit-on-error",
+ "terminate wirewatch if we failed to download information from the bank",
+ &exit_on_error),
+ GNUNET_GETOPT_option_relative_time ('f',
+ "longpoll-timeout",
+ "DELAY",
+ "what is the timeout when asking the bank about new transactions, specify with unit (e.g. --longpoll-timeout=30s)",
+ &longpoll_timeout),
+ GNUNET_GETOPT_option_flag ('I',
+ "ignore-not-found",
+ "continue, even if the bank account of the exchange was not found",
+ &ignore_account_404),
+ GNUNET_GETOPT_option_uint ('S',
+ "size",
+ "SIZE",
+ "Size to process per shard (default: 1024)",
+ &shard_size),
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_option_flag ('t',
"test",
"run in test mode and exit when idle",
&test_mode),
- GNUNET_GETOPT_option_flag ('r',
- "reset",
- "start fresh with all transactions in the history",
- &reset_mode),
+ GNUNET_GETOPT_option_uint ('w',
+ "workers",
+ "COUNT",
+ "Plan work load with up to COUNT worker processes (default: 16)",
+ &max_workers),
+ GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
GNUNET_GETOPT_OPTION_END
};
+ enum GNUNET_GenericReturnValue ret;
+ longpoll_timeout = LONGPOLL_TIMEOUT;
if (GNUNET_OK !=
GNUNET_STRINGS_get_utf8_args (argc, argv,
&argc, &argv))
- return GR_CMD_LINE_UTF8_ERROR;
- if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc, argv,
- "taler-exchange-wirewatch",
- gettext_noop (
- "background process that watches for incoming wire transfers from customers"),
- options,
- &run, NULL))
- {
- GNUNET_free ((void *) argv);
- return GR_CMD_LINE_OPTIONS_WRONG;
- }
- GNUNET_free ((void *) argv);
+ return EXIT_INVALIDARGUMENT;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (
+ argc, argv,
+ "taler-exchange-wirewatch",
+ gettext_noop (
+ "background process that watches for incoming wire transfers from customers"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
return global_ret;
}
diff --git a/src/exchange/test_taler_exchange_httpd.conf b/src/exchange/test_taler_exchange_httpd.conf
index e7f763e79..7e7ff8b45 100644
--- a/src/exchange/test_taler_exchange_httpd.conf
+++ b/src/exchange/test_taler_exchange_httpd.conf
@@ -1,6 +1,7 @@
[PATHS]
-# Persistant data storage for the testcase
+# Persistent data storage for the testcase
TALER_TEST_HOME = test_taler_exchange_httpd_home/
+TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
[taler]
# Currency supported by the exchange (can only be one)
@@ -12,12 +13,15 @@ TINY_AMOUNT = EUR:0.01
[exchange]
+AML_THRESHOLD = EUR:1000000
+
# Directory with our terms of service.
TERMS_DIR = ../../contrib/tos
# Etag / filename for the terms of service.
TERMS_ETAG = 0
+SIGNKEY_LEGAL_DURATION = 2 years
# Directory with our privacy policy.
PRIVACY_DIR = ../../contrib/pp
@@ -29,17 +33,10 @@ PRIVACY_ETAG = 0
# how long is one signkey valid?
SIGNKEY_DURATION = 4 weeks
-# how long are the signatures with the signkey valid?
-LEGAL_DURATION = 2 years
-
# how long do we generate denomination and signing keys
# ahead of time?
LOOKAHEAD_SIGN = 32 weeks 1 day
-# how long do we provide to clients denomination and signing keys
-# ahead of time?
-LOOKAHEAD_PROVIDE = 4 weeks 1 day
-
# HTTP port the exchange listens to
PORT = 8081
@@ -65,44 +62,17 @@ CONFIG = "postgres:///talercheck"
[exchange-account-1]
PAYTO_URI = "payto://x-taler-bank/localhost:8082/3"
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json
ENABLE_DEBIT = YES
ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-1]
WIRE_GATEWAY_AUTH_METHOD = basic
USERNAME = Exchange
PASSWORD = x
-WIRE_GATEWAY_URL = "http://localhost:8082/3/"
-
-
-# Wire fees are specified by wire method
-[fees-x-taler-bank]
-# Fees for the forseeable future...
-# If you see this after 2018, update to match the next 10 years...
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
-WIRE-FEE-2027 = EUR:0.01
-
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-CLOSING-FEE-2027 = EUR:0.01
-
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/3/taler-wire-gateway/"
# Coins for the tests.
-[coin_eur_ct_1]
+[coin_eur_ct_1_rsa]
value = EUR:0.01
duration_withdraw = 7 days
duration_spend = 2 years
@@ -111,9 +81,21 @@ fee_withdraw = EUR:0.00
fee_deposit = EUR:0.00
fee_refresh = EUR:0.01
fee_refund = EUR:0.01
+CIPHER = RSA
rsa_keysize = 1024
-[coin_eur_ct_10]
+[coin_eur_ct_1_cs]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_ct_10_rsa]
value = EUR:0.10
duration_withdraw = 7 days
duration_spend = 2 years
@@ -122,9 +104,21 @@ fee_withdraw = EUR:0.01
fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
+CIPHER = RSA
rsa_keysize = 1024
-[coin_eur_1]
+[coin_eur_ct_10_cs]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_1_rsa]
value = EUR:1
duration_withdraw = 7 days
duration_spend = 2 years
@@ -133,4 +127,16 @@ fee_withdraw = EUR:0.01
fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
+CIPHER = RSA
rsa_keysize = 1024
+
+[coin_eur_1_cs]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
diff --git a/src/exchange/test_taler_exchange_httpd.get b/src/exchange/test_taler_exchange_httpd.get
index 28a9e9bc9..c9058c870 100644
--- a/src/exchange/test_taler_exchange_httpd.get
+++ b/src/exchange/test_taler_exchange_httpd.get
@@ -20,7 +20,7 @@
#
/
/agpl
-/keys
+/seed
/robots.txt
/terms
/privacy
diff --git a/src/exchange/test_taler_exchange_httpd.sh b/src/exchange/test_taler_exchange_httpd.sh
index dabe79cb7..0fe71f3ad 100755
--- a/src/exchange/test_taler_exchange_httpd.sh
+++ b/src/exchange/test_taler_exchange_httpd.sh
@@ -1,7 +1,7 @@
#!/bin/bash
#
# This file is part of TALER
-# Copyright (C) 2015-2020 Taler Systems SA
+# Copyright (C) 2015-2020, 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
@@ -23,18 +23,16 @@
# Clear environment from variables that override config.
unset XDG_DATA_HOME
unset XDG_CONFIG_HOME
+
+set -eu
#
echo -n "Launching exchange ..."
PREFIX=
# Uncomment this line to run with valgrind...
-# PREFIX="valgrind --leak-check=yes --track-fds=yes --error-exitcode=1 --log-file=valgrind.%p"
+#PREFIX="valgrind --leak-check=yes --track-fds=yes --error-exitcode=1 --log-file=valgrind.%p"
# Setup database
-taler-exchange-dbinit -c test_taler_exchange_httpd.conf &> /dev/null
-# Setup keys.
-taler-exchange-keyup -c test_taler_exchange_httpd.conf || exit 1
-# Setup wire accounts.
-taler-exchange-wire -c test_taler_exchange_httpd.conf > /dev/null || exit 1
+taler-exchange-dbinit -c test_taler_exchange_httpd.conf &> /dev/null || exit 77
# Run Exchange HTTPD (in background)
$PREFIX taler-exchange-httpd -c test_taler_exchange_httpd.conf 2> test-exchange.log &
@@ -45,7 +43,7 @@ do
echo -n "."
sleep 0.1
OK=1
- wget http://localhost:8081/ -o /dev/null -O /dev/null >/dev/null && break
+ wget http://localhost:8081/seed -o /dev/null -O /dev/null >/dev/null && break
OK=0
done
if [ 1 != $OK ]
diff --git a/src/exchange/test_taler_exchange_httpd_restart.sh b/src/exchange/test_taler_exchange_httpd_restart.sh
deleted file mode 100755
index a8976fb0c..000000000
--- a/src/exchange/test_taler_exchange_httpd_restart.sh
+++ /dev/null
@@ -1,116 +0,0 @@
-#!/bin/bash
-#
-# This file is part of TALER
-# Copyright (C) 2020 Taler Systems SA
-#
-# TALER is free software; you can redistribute it and/or modify it under the
-# terms of the GNU Affero General Public License as 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, If not, see <http://www.gnu.org/licenses/>
-#
-#
-# This script launches an exchange (binding to a UNIX domain socket) and then
-# restarts it in various ways (SIGHUP to re-read configuration, and SIGUSR1 to
-# re-spawn a new binary). Basically, the goal is to make sure that the HTTP
-# server survives these less common operations.
-#
-#
-set -eu
-
-# Exit, with status code "skip" (no 'real' failure)
-function exit_skip() {
- echo $1
- exit 77
-}
-
-# Exit, with error message (hard failure)
-function exit_fail() {
- echo $1
- kill `jobs -p` >/dev/null 2>/dev/null || true
- wait
- exit 1
-}
-
-echo -n "Testing for curl"
-curl --version >/dev/null </dev/null || exit_skip " MISSING"
-echo " FOUND"
-
-
-# Clear environment from variables that override config.
-unset XDG_DATA_HOME
-unset XDG_CONFIG_HOME
-#
-echo -n "Launching exchange ..."
-PREFIX=
-# Uncomment this line to run with valgrind...
-# PREFIX="valgrind --trace-children=yes --leak-check=yes --track-fds=yes --error-exitcode=1 --log-file=valgrind.%p"
-
-# Setup database
-taler-exchange-dbinit -c test_taler_exchange_unix.conf &> /dev/null
-# Setup keys.
-taler-exchange-keyup -c test_taler_exchange_unix.conf || exit 1
-# Setup wire accounts.
-taler-exchange-wire -c test_taler_exchange_unix.conf > /dev/null || exit 1
-# Run Exchange HTTPD (in background)
-$PREFIX taler-exchange-httpd -c test_taler_exchange_unix.conf 2> test-exchange.log &
-
-# Where should we be bound to?
-UNIXPATH=`taler-config -s exchange -f -o UNIXPATH`
-
-# Give HTTP time to start
-
-for n in `seq 1 100`
-do
- echo -n "."
- sleep 0.1
- OK=1
- curl --unix-socket "${UNIXPATH}" "http://ignored/" >/dev/null 2> /dev/null && break
- OK=0
-done
-if [ 1 != $OK ]
-then
- exit_fail "Failed to launch exchange"
-fi
-echo " DONE"
-
-# Finally run test...
-echo -n "Reloading keys ..."
-kill -SIGUSR1 $!
-sleep 1
-curl --unix-socket "${UNIXPATH}" "http://ignored/" >/dev/null 2> /dev/null || exit_fail "SIGUSR1 killed HTTP service"
-echo " DONE"
-
-# Finally run test...
-echo -n "Restarting program ..."
-kill -SIGHUP $!
-sleep 1
-curl --unix-socket "${UNIXPATH}" "http://ignored/" >/dev/null 2> /dev/null || exit_fail "SIGHUP killed HTTP service"
-echo " DONE"
-
-echo -n "Waiting for parent to die ..."
-wait $!
-echo " DONE"
-
-echo -n "Testing child still alive ..."
-curl --unix-socket "${UNIXPATH}" "http://ignored/" >/dev/null 2> /dev/null || exit_fail "SIGHUP killed HTTP service"
-echo " DONE"
-
-
-echo -n "Killing grandchild ..."
-CPID=`ps x | grep taler-exchange-httpd | grep -v grep | awk '{print $1}'`
-kill -TERM $CPID
-while true
-do
- ps x | grep -v grep | grep taler-exchange-httpd > /dev/null || break
- sleep 0.1
-done
-echo " DONE"
-
-# Return status code from exchange for this script
-exit 0
diff --git a/src/exchange/test_taler_exchange_unix.conf b/src/exchange/test_taler_exchange_unix.conf
index bc870d4bb..e96bfba3f 100644
--- a/src/exchange/test_taler_exchange_unix.conf
+++ b/src/exchange/test_taler_exchange_unix.conf
@@ -1,6 +1,7 @@
[PATHS]
# Persistent data storage for the testcase
TALER_TEST_HOME = test_taler_exchange_httpd_home/
+TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
[taler]
# Currency supported by the exchange (can only be one)
@@ -15,6 +16,8 @@ TERMS_DIR = ../../contrib/tos
# Etag / filename for the terms of service.
TERMS_ETAG = 0
+# how long are the signatures with the signkey valid?
+SIGNKEY_LEGAL_DURATION = 2 years
# Directory with our privacy policy.
PRIVACY_DIR = ../../contrib/pp
@@ -26,17 +29,10 @@ PRIVACY_ETAG = 0
# how long is one signkey valid?
SIGNKEY_DURATION = 4 weeks
-# how long are the signatures with the signkey valid?
-LEGAL_DURATION = 2 years
-
# how long do we generate denomination and signing keys
# ahead of time?
LOOKAHEAD_SIGN = 32 weeks 1 day
-# how long do we provide to clients denomination and signing keys
-# ahead of time?
-LOOKAHEAD_PROVIDE = 4 weeks 1 day
-
# HTTP port the exchange listens to (we want to use UNIX domain sockets,
# so we use a port that just won't work on GNU/Linux without root rights)
PORT = 999
@@ -66,41 +62,15 @@ CONFIG = "postgres:///talercheck"
[exchange-account-1]
PAYTO_URI = "payto://x-taler-bank/localhost:8082/3"
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json
ENABLE_DEBIT = YES
ENABLE_CREDIT = YES
-TALER_BANK_AUTH_METHOD = NONE
-
-# Wire fees are specified by wire method
-[fees-x-taler-bank]
-# Fees for the foreseeable future...
-# If you see this after 2018, update to match the next 10 years...
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
-WIRE-FEE-2027 = EUR:0.01
-
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-CLOSING-FEE-2027 = EUR:0.01
+[exchange-accountcredentials-1]
+TALER_BANK_AUTH_METHOD = NONE
# Coins for the tests.
-[coin_eur_ct_1]
+[coin_eur_ct_1_rsa]
value = EUR:0.01
duration_withdraw = 7 days
duration_spend = 2 years
@@ -109,9 +79,21 @@ fee_withdraw = EUR:0.00
fee_deposit = EUR:0.00
fee_refresh = EUR:0.01
fee_refund = EUR:0.01
+CIPHER = RSA
rsa_keysize = 1024
-[coin_eur_ct_10]
+[coin_eur_ct_1_cs]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_ct_10_rsa]
value = EUR:0.10
duration_withdraw = 7 days
duration_spend = 2 years
@@ -120,9 +102,21 @@ fee_withdraw = EUR:0.01
fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
+CIPHER = RSA
rsa_keysize = 1024
-[coin_eur_1]
+[coin_eur_ct_10_cs]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_1_rsa]
value = EUR:1
duration_withdraw = 7 days
duration_spend = 2 years
@@ -131,4 +125,16 @@ fee_withdraw = EUR:0.01
fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
+CIPHER = RSA
rsa_keysize = 1024
+
+[coin_eur_1_cs]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
diff --git a/src/exchangedb/.gitignore b/src/exchangedb/.gitignore
index 830cf10cc..6e67fadb1 100644
--- a/src/exchangedb/.gitignore
+++ b/src/exchangedb/.gitignore
@@ -1,6 +1,16 @@
-test-exchangedb-auditors
-test-exchangedb-denomkeys
-test-exchangedb-fees
test-exchangedb-postgres
-test-exchangedb-signkeys
-test-perf-taler-exchangedb
+bench-db-postgres
+perf_deposits_get_ready-postgres
+perf_get_link_data-postgres
+perf_reserves_in_insert-postgres
+perf_select_refunds_by_coin-postgres
+exchange-0002.sql
+procedures.sql
+exchange-0003.sql
+perf-exchangedb-reserves-in-insert-postgres
+test-exchangedb-batch-reserves-in-insert-postgres
+test-exchangedb-by-j-postgres
+test-exchangedb-populate-link-data-postgres
+test-exchangedb-populate-ready-deposit-postgres
+test-exchangedb-populate-select-refunds-by-coin-postgres
+exchange-0004.sql
diff --git a/src/exchangedb/0002-account_merges.sql b/src/exchangedb/0002-account_merges.sql
new file mode 100644
index 000000000..1dd7e5bf0
--- /dev/null
+++ b/src/exchangedb/0002-account_merges.sql
@@ -0,0 +1,139 @@
+--
+-- 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 FUNCTION create_table_account_merges(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'account_merges';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I '
+ '(account_merge_request_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ',wallet_h_payto BYTEA NOT NULL CHECK (LENGTH(wallet_h_payto)=32)'
+ ',PRIMARY KEY (purse_pub)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Merge requests where a purse- and account-owner requested merging the purse into the account'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public key of the target reserve'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'signature by the reserve private key affirming the merge, of type TALER_SIGNATURE_WALLET_ACCOUNT_MERGE'
+ ,'reserve_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_account_merges(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'account_merges';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ -- Note: this index *may* be useful in
+ -- pg_get_reserve_history depending on how
+ -- smart the DB is when computing the JOIN.
+ -- Removing it MAY boost performance slightly, at
+ -- the expense of trouble if the "merge_by_reserve"
+ -- query planner goes off the rails. Needs benchmarking
+ -- to be sure.
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_reserve_pub '
+ 'ON ' || table_name || ' '
+ '(reserve_pub);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_account_merge_request_serial_id_key'
+ ' UNIQUE (account_merge_request_serial_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_account_merges()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'account_merges';
+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'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_purse_pub'
+ ' FOREIGN KEY (purse_pub) '
+ ' REFERENCES purse_requests (purse_pub)'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('account_merges'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('account_merges'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('account_merges'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-age_withdraw.sql b/src/exchangedb/0002-age_withdraw.sql
new file mode 100644
index 000000000..87cac7816
--- /dev/null
+++ b/src/exchangedb/0002-age_withdraw.sql
@@ -0,0 +1,157 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+-- @author Özgür Kesim
+
+CREATE FUNCTION create_table_age_withdraw(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'age_withdraw';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(age_withdraw_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_commitment BYTEA NOT NULL CONSTRAINT h_commitment_length CHECK(LENGTH(h_commitment)=64)'
+ ',max_age SMALLINT NOT NULL CONSTRAINT max_age_positive CHECK(max_age>=0)'
+ ',amount_with_fee taler_amount NOT NULL'
+ ',reserve_pub BYTEA NOT NULL CONSTRAINT reserve_pub_length CHECK(LENGTH(reserve_pub)=32)'
+ ',reserve_sig BYTEA NOT NULL CONSTRAINT reserve_sig_length CHECK(LENGTH(reserve_sig)=64)'
+ ',noreveal_index SMALLINT NOT NULL CONSTRAINT noreveal_index_positive CHECK(noreveal_index>=0)'
+ ',h_blind_evs BYTEA[] NOT NULL CONSTRAINT h_blind_evs_length CHECK(cardinality(h_blind_evs)=cardinality(denom_serials))'
+ ',denom_serials INT8[] NOT NULL CONSTRAINT denom_serials_array_length CHECK(cardinality(denom_serials)=cardinality(denom_sigs))'
+ ',denom_sigs BYTEA[] NOT NULL CONSTRAINT denom_sigs_array_length CHECK(cardinality(denom_sigs)=cardinality(denom_serials))'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Commitments made when withdrawing coins with age restriction and the gamma value chosen by the exchange. '
+ 'It also contains the blindly signed coins, their signatures and denominations.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The gamma value chosen by the exchange in the cut-and-choose protocol'
+ ,'noreveal_index'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The maximum age (in years) that the client commits to with this request'
+ ,'max_age'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Commitment made by the client, hash over the various client inputs in the cut-and-choose protocol'
+ ,'h_commitment'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Reference to the public key of the reserve from which the coins are going to be withdrawn'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature of the reserve''s private key over the age-withdraw request'
+ ,'reserve_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Array of references to the denominations'
+ ,'denom_serials'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Array of the blinded envelopes of the chosen fresh coins, with value as given by the denomination in the corresponding slot in denom_serials'
+ ,'h_blind_evs'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Array of signatures over each blinded envelope'
+ ,'denom_sigs'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_age_withdraw(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'age_withdraw';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD PRIMARY KEY (h_commitment);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_h_commitment_reserve_pub_key'
+ ' UNIQUE (h_commitment, reserve_pub);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_age_withdraw_id_key'
+ ' UNIQUE (age_withdraw_id);'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_age_withdraw()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'age_withdraw';
+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;'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+VALUES
+ ('age_withdraw', 'exchange-0002', 'create', TRUE ,FALSE),
+ ('age_withdraw', 'exchange-0002', 'constrain',TRUE ,FALSE),
+ ('age_withdraw', 'exchange-0002', 'foreign', TRUE ,FALSE);
+
diff --git a/src/exchangedb/0002-aggregation_tracking.sql b/src/exchangedb/0002-aggregation_tracking.sql
new file mode 100644
index 000000000..d07960247
--- /dev/null
+++ b/src/exchangedb/0002-aggregation_tracking.sql
@@ -0,0 +1,118 @@
+--
+-- 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 FUNCTION create_table_aggregation_tracking(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aggregation_tracking';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(aggregation_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',batch_deposit_serial_id INT8 PRIMARY KEY'
+ ',wtid_raw BYTEA NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (batch_deposit_serial_id)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'mapping from wire transfer identifiers (WTID) to deposits (and back)'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'identifier of the wire transfer'
+ ,'wtid_raw'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_aggregation_tracking(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aggregation_tracking';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_wtid_raw_index '
+ 'ON ' || table_name || ' '
+ '(wtid_raw);'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_wtid_raw_index '
+ 'IS ' || quote_literal('for lookup_transactions') || ';'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_aggregation_serial_id_key'
+ ' UNIQUE (aggregation_serial_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_aggregation_tracking()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aggregation_tracking';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_deposit'
+ ' FOREIGN KEY (batch_deposit_serial_id)'
+ ' REFERENCES batch_deposits (batch_deposit_serial_id)'
+ ' ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('aggregation_tracking'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('aggregation_tracking'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('aggregation_tracking'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-aggregation_transient.sql b/src/exchangedb/0002-aggregation_transient.sql
new file mode 100644
index 000000000..8e46450f0
--- /dev/null
+++ b/src/exchangedb/0002-aggregation_transient.sql
@@ -0,0 +1,71 @@
+--
+-- 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 FUNCTION create_table_aggregation_transient(
+ IN shard_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aggregation_transient';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(amount taler_amount NOT NULL'
+ ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
+ ',merchant_pub BYTEA CHECK (LENGTH(merchant_pub)=32)'
+ ',exchange_account_section TEXT NOT NULL'
+ ',legitimization_requirement_serial_id INT8 NOT NULL DEFAULT(0)'
+ ',wtid_raw BYTEA NOT NULL CHECK (LENGTH(wtid_raw)=32)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (wire_target_h_payto)'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'aggregations currently happening (lacking wire_out, usually because the amount is too low); this table is not replicated'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Sum of all of the aggregated deposits (without deposit fees)'
+ ,'amount'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'identifier of the wire transfer'
+ ,'wtid_raw'
+ ,table_name
+ ,shard_suffix
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('aggregation_transient'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-aml_history.sql b/src/exchangedb/0002-aml_history.sql
new file mode 100644
index 000000000..af81be9d8
--- /dev/null
+++ b/src/exchangedb/0002-aml_history.sql
@@ -0,0 +1,147 @@
+--
+-- 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 create_table_aml_history(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aml_history';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I'
+ '(aml_history_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_payto BYTEA CHECK (LENGTH(h_payto)=32)'
+ ',new_threshold taler_amount NOT NULL DEFAULT(0,0)'
+ ',new_status INT4 NOT NULL DEFAULT(0)'
+ ',decision_time INT8 NOT NULL DEFAULT(0)'
+ ',justification TEXT NOT NULL'
+ ',kyc_requirements TEXT'
+ ',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 ;'
+ ,table_name
+ ,'PARTITION BY HASH (h_payto)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'AML decision history for a particular payment destination'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'hash of the payto://-URI this AML history is about'
+ ,'h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'new monthly inbound transaction limit below which we are OK'
+ ,'new_threshold'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ '0 for all OK, 1 for AML decision required, 2 for account is frozen (prevents further transactions)'
+ ,'new_status'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'when was the status changed'
+ ,'decision_time'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'human-readable justification for the status change'
+ ,'justification'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the staff member who made the AML decision'
+ ,'decider_pub'
+ ,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'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+COMMENT ON FUNCTION create_table_aml_history
+ IS 'Creates the aml_history table';
+
+
+CREATE OR REPLACE FUNCTION constrain_table_aml_history(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aml_history';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_serial_key '
+ 'UNIQUE (aml_history_serial_id)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_main_index '
+ 'ON ' || table_name || ' '
+ '(h_payto, decision_time DESC);'
+ );
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('aml_history'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('aml_history'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-aml_staff.sql b/src/exchangedb/0002-aml_staff.sql
new file mode 100644
index 000000000..cec18c62b
--- /dev/null
+++ b/src/exchangedb/0002-aml_staff.sql
@@ -0,0 +1,40 @@
+--
+-- 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 TABLE aml_staff
+ (aml_staff_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,decider_pub BYTEA PRIMARY KEY CHECK (LENGTH(decider_pub)=32)
+ ,master_sig BYTEA CHECK (LENGTH(master_sig)=64)
+ ,decider_name TEXT NOT NULL
+ ,is_active BOOLEAN NOT NULL
+ ,read_only BOOLEAN NOT NULL
+ ,last_change INT8 NOT NULL
+ );
+COMMENT ON TABLE aml_staff
+ IS 'Table with AML staff members the exchange uses or has used in the past. Entries never expire as we need to remember the last_change column indefinitely.';
+COMMENT ON COLUMN aml_staff.decider_pub
+ IS 'Public key of the AML staff member.';
+COMMENT ON COLUMN aml_staff.master_sig
+ IS 'The master public key signature on the AML staff member status, of type TALER_SIGNATURE_MASTER_AML_KEY.';
+COMMENT ON COLUMN aml_staff.decider_name
+ IS 'Name of the staff member.';
+COMMENT ON COLUMN aml_staff.is_active
+ IS 'true if we are currently supporting the use of this AML staff member.';
+COMMENT ON COLUMN aml_staff.is_active
+ IS 'true if the member has read-only access.';
+COMMENT ON COLUMN aml_staff.last_change
+ IS 'Latest time when active status changed. Used to detect replays of old messages.';
diff --git a/src/exchangedb/0002-aml_status.sql b/src/exchangedb/0002-aml_status.sql
new file mode 100644
index 000000000..a8b567a82
--- /dev/null
+++ b/src/exchangedb/0002-aml_status.sql
@@ -0,0 +1,101 @@
+--
+-- 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 create_table_aml_status(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aml_status';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I'
+ '(aml_status_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=32)'
+ ',threshold taler_amount NOT NULL DEFAULT(0,0)'
+ ',status INT4 NOT NULL DEFAULT(0)'
+ ',kyc_requirement INT8 NOT NULL DEFAULT(0)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (h_payto)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'AML status for a particular payment destination'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'hash of the payto://-URI this AML status is about'
+ ,'h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'monthly inbound transaction limit below which we are OK (if status is 1)'
+ ,'threshold'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ '0 for all OK, 1 for AML decision required, 2 for account is frozen (prevents further transactions)'
+ ,'status'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+COMMENT ON FUNCTION create_table_aml_status
+ IS 'Creates the aml_status table';
+
+
+CREATE OR REPLACE FUNCTION constrain_table_aml_status(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aml_status';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_serial_key '
+ 'UNIQUE (aml_status_serial_id)'
+ );
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('aml_status'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('aml_status'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-auditor_denom_sigs.sql b/src/exchangedb/0002-auditor_denom_sigs.sql
new file mode 100644
index 000000000..3ed645af5
--- /dev/null
+++ b/src/exchangedb/0002-auditor_denom_sigs.sql
@@ -0,0 +1,32 @@
+--
+-- 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 TABLE auditor_denom_sigs
+ (auditor_denom_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,auditor_uuid INT8 NOT NULL REFERENCES auditors (auditor_uuid) ON DELETE CASCADE
+ ,denominations_serial INT8 NOT NULL REFERENCES denominations (denominations_serial) ON DELETE CASCADE
+ ,auditor_sig BYTEA CHECK (LENGTH(auditor_sig)=64)
+ ,PRIMARY KEY (denominations_serial, auditor_uuid)
+ );
+COMMENT ON TABLE auditor_denom_sigs
+ IS 'Table with auditor signatures on exchange denomination keys.';
+COMMENT ON COLUMN auditor_denom_sigs.auditor_uuid
+ IS 'Identifies the auditor.';
+COMMENT ON COLUMN auditor_denom_sigs.denominations_serial
+ IS 'Denomination the signature is for.';
+COMMENT ON COLUMN auditor_denom_sigs.auditor_sig
+ IS 'Signature of the auditor, of purpose TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS.';
diff --git a/src/exchangedb/0002-auditors.sql b/src/exchangedb/0002-auditors.sql
new file mode 100644
index 000000000..76e43b183
--- /dev/null
+++ b/src/exchangedb/0002-auditors.sql
@@ -0,0 +1,35 @@
+--
+-- 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 TABLE auditors
+ (auditor_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,auditor_pub BYTEA PRIMARY KEY CHECK (LENGTH(auditor_pub)=32)
+ ,auditor_name TEXT NOT NULL
+ ,auditor_url TEXT NOT NULL
+ ,is_active BOOLEAN NOT NULL
+ ,last_change INT8 NOT NULL
+ );
+COMMENT ON TABLE auditors
+ IS 'Table with auditors the exchange uses or has used in the past. Entries never expire as we need to remember the last_change column indefinitely.';
+COMMENT ON COLUMN auditors.auditor_pub
+ IS 'Public key of the auditor.';
+COMMENT ON COLUMN auditors.auditor_url
+ IS 'The base URL of the auditor.';
+COMMENT ON COLUMN auditors.is_active
+ IS 'true if we are currently supporting the use of this auditor.';
+COMMENT ON COLUMN auditors.last_change
+ IS 'Latest time when active status changed. Used to detect replays of old messages.';
diff --git a/src/exchangedb/0002-batch_deposits.sql b/src/exchangedb/0002-batch_deposits.sql
new file mode 100644
index 000000000..71a4b4205
--- /dev/null
+++ b/src/exchangedb/0002-batch_deposits.sql
@@ -0,0 +1,172 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_batch_deposits(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'batch_deposits';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(batch_deposit_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY'
+ ',shard INT8 NOT NULL'
+ ',merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)'
+ ',wallet_timestamp INT8 NOT NULL'
+ ',exchange_timestamp INT8 NOT NULL'
+ ',refund_deadline INT8 NOT NULL'
+ ',wire_deadline INT8 NOT NULL'
+ ',h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)'
+ ',wallet_data_hash BYTEA CHECK (LENGTH(wallet_data_hash)=64) DEFAULT NULL'
+ ',wire_salt BYTEA NOT NULL CHECK (LENGTH(wire_salt)=16)'
+ ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
+ ',policy_details_serial_id INT8'
+ ',policy_blocked BOOLEAN NOT NULL DEFAULT FALSE'
+ ',done BOOLEAN NOT NULL DEFAULT FALSE'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (shard)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Information about the contracts for which we have received (batch) deposits.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Used for load sharding in the materialized indices. Should be set based on merchant_pub. 64-bit value because we need an *unsigned* 32-bit value.'
+ ,'shard'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Unsalted hash of the target bank account; also used to lookup the KYC status'
+ ,'wire_target_h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'hash over data provided by the wallet upon payment to select a more specific contract'
+ ,'wallet_data_hash'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Salt used when hashing the payto://-URI to get the h_wire that was used by the coin deposit signatures; not used to calculate wire_target_h_payto (as that one is unsalted)'
+ ,'wire_salt'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Set to TRUE once we have included this (batch) deposit (and all associated coins) in some aggregate wire transfer to the merchant'
+ ,'done'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'True if the aggregation of the (batch) deposit is currently blocked by some policy extension mechanism. Used to filter out deposits that must not be processed by the canonical deposit logic.'
+ ,'policy_blocked'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'References policy extensions table, NULL if extensions are not used'
+ ,'policy_details_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_batch_deposits(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'batch_deposits';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_batch_deposit_serial_id_pkey'
+ ' PRIMARY KEY (batch_deposit_serial_id) '
+ ',ADD CONSTRAINT ' || table_name || '_merchant_pub_h_contract_terms'
+ ' UNIQUE (shard, merchant_pub, h_contract_terms)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_ready '
+ 'ON ' || table_name || ' '
+ '(shard ASC'
+ ',wire_deadline ASC'
+ ') WHERE NOT (done OR policy_blocked);'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_for_matching '
+ 'ON ' || table_name || ' '
+ '(shard ASC'
+ ',refund_deadline ASC'
+ ',wire_target_h_payto'
+ ') WHERE NOT (done OR policy_blocked);'
+ );
+END
+$$;
+
+CREATE OR REPLACE FUNCTION foreign_table_batch_deposits()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'batch_deposits';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_policy_details'
+ ' FOREIGN KEY (policy_details_serial_id) '
+ ' REFERENCES policy_details (policy_details_serial_id) ON DELETE RESTRICT'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('batch_deposits'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('batch_deposits'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('batch_deposits'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE)
+ ;
diff --git a/src/exchangedb/0002-close_requests.sql b/src/exchangedb/0002-close_requests.sql
new file mode 100644
index 000000000..6a7028095
--- /dev/null
+++ b/src/exchangedb/0002-close_requests.sql
@@ -0,0 +1,178 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_close_requests(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'close_requests';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(close_request_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)'
+ ',close_timestamp INT8 NOT NULL'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',close taler_amount NOT NULL'
+ ',close_fee taler_amount NOT NULL'
+ ',payto_uri TEXT NOT NULL'
+ ',done BOOL NOT NULL DEFAULT(FALSE)'
+ ',PRIMARY KEY (reserve_pub,close_timestamp)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Explicit requests by a reserve owner to close a reserve immediately'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'When the request was created by the client'
+ ,'close_timestamp'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature affirming that the reserve is to be closed'
+ ,'reserve_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Balance of the reserve at the time of closing, to be wired to the associated bank account (minus the closing fee)'
+ ,'close'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the credited bank account. Optional.'
+ ,'payto_uri'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+
+CREATE FUNCTION constrain_table_close_requests(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'close_requests';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_close_request_uuid_index '
+ 'ON ' || table_name || ' '
+ '(close_request_serial_id);'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_close_request_done_index '
+ 'ON ' || table_name || ' '
+ '(done);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_close_request_uuid_pkey'
+ ' UNIQUE (close_request_serial_id)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_close_requests()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'close_requests';
+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'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION close_requests_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.reserve_pub
+ ,'close_requests'
+ ,NEW.close_request_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION close_requests_insert_trigger()
+ IS 'Automatically generate reserve history entry.';
+
+
+CREATE FUNCTION master_table_close_requests()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER close_requests_on_insert
+ AFTER INSERT
+ ON close_requests
+ FOR EACH ROW EXECUTE FUNCTION close_requests_insert_trigger();
+END $$;
+
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('close_requests'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('close_requests'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('close_requests'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('close_requests'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-coin_deposits.sql b/src/exchangedb/0002-coin_deposits.sql
new file mode 100644
index 000000000..c3eef6e5a
--- /dev/null
+++ b/src/exchangedb/0002-coin_deposits.sql
@@ -0,0 +1,157 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_coin_deposits(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'coin_deposits';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(coin_deposit_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY'
+ ',batch_deposit_serial_id INT8 NOT NULL'
+ ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+ ',coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)'
+ ',amount_with_fee taler_amount NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Coins which have been deposited with the respective per-coin signatures.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Link to information about the batch deposit this coin was used for'
+ ,'batch_deposit_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_coin_deposits(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'coin_deposits';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_coin_deposit_serial_id_pkey'
+ ' PRIMARY KEY (coin_deposit_serial_id) '
+ ',ADD CONSTRAINT ' || table_name || '_unique_coin_sig'
+ ' UNIQUE (coin_pub, coin_sig)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_batch '
+ 'ON ' || table_name || ' '
+ '(batch_deposit_serial_id);'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_coin_deposits()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'coin_deposits';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (coin_pub) '
+ ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_batch_deposits_id'
+ ' FOREIGN KEY (batch_deposit_serial_id) '
+ ' REFERENCES batch_deposits (batch_deposit_serial_id) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION coin_deposits_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.coin_pub
+ ,'coin_deposits'
+ ,NEW.coin_deposit_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION coin_deposits_insert_trigger()
+ IS 'Automatically generate coin history entry.';
+
+
+CREATE FUNCTION master_table_coin_deposits()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER coin_deposits_on_insert
+ AFTER INSERT
+ ON coin_deposits
+ FOR EACH ROW EXECUTE FUNCTION coin_deposits_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('coin_deposits'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('coin_deposits'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('coin_deposits'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('coin_deposits'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE)
+ ;
diff --git a/src/exchangedb/0002-coin_history.sql b/src/exchangedb/0002-coin_history.sql
new file mode 100644
index 000000000..9b5efdcb6
--- /dev/null
+++ b/src/exchangedb/0002-coin_history.sql
@@ -0,0 +1,138 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_coin_history (
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'coin_history';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(coin_history_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY'
+ ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+ ',table_name TEXT NOT NULL'
+ ',serial_id INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Links to tables with entries that affected the transaction history of a coin.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'For which coin is this a history entry'
+ ,'coin_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'In which table is the history entry'
+ ,'table_name'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Which is the generated serial ID of the entry in the table'
+ ,'serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Monotonic counter, used to generate Etags for caching'
+ ,'coin_history_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_coin_history(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'coin_history';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_coin_history_serial_id_pkey'
+ ' PRIMARY KEY (coin_history_serial_id) '
+ ',ADD CONSTRAINT ' || table_name || '_coin_entry_key'
+ ' UNIQUE (coin_pub, table_name, serial_id)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_coin_by_time'
+ ' ON ' || table_name || ' '
+ '(coin_pub'
+ ',coin_history_serial_id DESC'
+ ');'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_coin_history()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'coin_history';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (coin_pub) '
+ ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('coin_history'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('coin_history'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('coin_history'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE)
+ ;
diff --git a/src/exchangedb/0002-contracts.sql b/src/exchangedb/0002-contracts.sql
new file mode 100644
index 000000000..c1f92c9aa
--- /dev/null
+++ b/src/exchangedb/0002-contracts.sql
@@ -0,0 +1,109 @@
+--
+-- 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 FUNCTION create_table_contracts(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'contracts';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(contract_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ',pub_ckey BYTEA NOT NULL CHECK (LENGTH(pub_ckey)=32)'
+ ',contract_sig BYTEA NOT NULL CHECK (LENGTH(contract_sig)=64)'
+ ',e_contract BYTEA NOT NULL'
+ ',purse_expiration INT8 NOT NULL'
+ ',PRIMARY KEY (purse_pub)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'encrypted contracts associated with purses'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public key of the purse that the contract is associated with'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'signature over the encrypted contract by the purse contract key'
+ ,'contract_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public ECDH key used to encrypt the contract, to be used with the purse private key for decryption'
+ ,'pub_ckey'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'AES-GCM encrypted contract terms (contains gzip compressed JSON after decryption)'
+ ,'e_contract'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_contracts(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'contracts';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_contract_serial_id_key'
+ ' UNIQUE (contract_serial_id) '
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('contracts'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('contracts'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-cs_nonce_locks.sql b/src/exchangedb/0002-cs_nonce_locks.sql
new file mode 100644
index 000000000..36c0c7a5f
--- /dev/null
+++ b/src/exchangedb/0002-cs_nonce_locks.sql
@@ -0,0 +1,97 @@
+--
+-- 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 FUNCTION create_table_cs_nonce_locks(
+ partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(cs_nonce_lock_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',nonce BYTEA PRIMARY KEY CHECK (LENGTH(nonce)=32)'
+ ',op_hash BYTEA NOT NULL CHECK (LENGTH(op_hash)=64)'
+ ',max_denomination_serial INT8 NOT NULL'
+ ') %s ;'
+ ,'cs_nonce_locks'
+ ,'PARTITION BY HASH (nonce)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'ensures a Clause Schnorr client nonce is locked for use with an operation identified by a hash'
+ ,'cs_nonce_locks'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'actual nonce submitted by the client'
+ ,'nonce'
+ ,'cs_nonce_locks'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'hash (RC for refresh, blind coin hash for withdraw) the nonce may be used with'
+ ,'op_hash'
+ ,'cs_nonce_locks'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Maximum number of a CS denomination serial the nonce could be used with, for GC'
+ ,'max_denomination_serial'
+ ,'cs_nonce_locks'
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_cs_nonce_locks(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'cs_nonce_locks';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_cs_nonce_lock_serial_id_key'
+ ' UNIQUE (cs_nonce_lock_serial_id)'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('cs_nonce_locks'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('cs_nonce_locks'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-denomination_revocations.sql b/src/exchangedb/0002-denomination_revocations.sql
new file mode 100644
index 000000000..96e13cd15
--- /dev/null
+++ b/src/exchangedb/0002-denomination_revocations.sql
@@ -0,0 +1,23 @@
+--
+-- 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 TABLE IF NOT EXISTS denomination_revocations
+ (denom_revocations_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,denominations_serial INT8 PRIMARY KEY REFERENCES denominations (denominations_serial) ON DELETE CASCADE
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ );
+COMMENT ON TABLE denomination_revocations
+ IS 'remembering which denomination keys have been revoked';
diff --git a/src/exchangedb/0002-denominations.sql b/src/exchangedb/0002-denominations.sql
new file mode 100644
index 000000000..a3de2b149
--- /dev/null
+++ b/src/exchangedb/0002-denominations.sql
@@ -0,0 +1,45 @@
+--
+-- 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 TABLE denominations
+ (denominations_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)
+ ,denom_type INT4 NOT NULL DEFAULT (1) -- 1 == RSA (for now, remove default later!)
+ ,age_mask INT4 NOT NULL DEFAULT (0)
+ ,denom_pub BYTEA NOT NULL
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ ,valid_from INT8 NOT NULL
+ ,expire_withdraw INT8 NOT NULL
+ ,expire_deposit INT8 NOT NULL
+ ,expire_legal INT8 NOT NULL
+ ,coin taler_amount NOT NULL
+ ,fee_withdraw taler_amount NOT NULL
+ ,fee_deposit taler_amount NOT NULL
+ ,fee_refresh taler_amount NOT NULL
+ ,fee_refund taler_amount NOT NULL
+ );
+COMMENT ON TABLE denominations
+ IS 'Main denominations table. All the valid denominations the exchange knows about.';
+COMMENT ON COLUMN denominations.denom_type
+ IS 'determines cipher type for blind signatures used with this denomination; 0 is for RSA';
+COMMENT ON COLUMN denominations.age_mask
+ IS 'bitmask with the age restrictions that are being used for this denomination; 0 if denomination does not support the use of age restrictions';
+COMMENT ON COLUMN denominations.denominations_serial
+ IS 'needed for exchange-auditor replication logic';
+
+CREATE INDEX denominations_by_expire_legal_index
+ ON denominations
+ (expire_legal);
diff --git a/src/exchangedb/0002-exchange_sign_keys.sql b/src/exchangedb/0002-exchange_sign_keys.sql
new file mode 100644
index 000000000..d6acc6bb0
--- /dev/null
+++ b/src/exchangedb/0002-exchange_sign_keys.sql
@@ -0,0 +1,36 @@
+--
+-- 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 TABLE exchange_sign_keys
+ (esk_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,exchange_pub BYTEA PRIMARY KEY CHECK (LENGTH(exchange_pub)=32)
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ ,valid_from INT8 NOT NULL
+ ,expire_sign INT8 NOT NULL
+ ,expire_legal INT8 NOT NULL
+ );
+COMMENT ON TABLE exchange_sign_keys
+ IS 'Table with master public key signatures on exchange online signing keys.';
+COMMENT ON COLUMN exchange_sign_keys.exchange_pub
+ IS 'Public online signing key of the exchange.';
+COMMENT ON COLUMN exchange_sign_keys.master_sig
+ IS 'Signature affirming the validity of the signing key of purpose TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY.';
+COMMENT ON COLUMN exchange_sign_keys.valid_from
+ IS 'Time when this online signing key will first be used to sign messages.';
+COMMENT ON COLUMN exchange_sign_keys.expire_sign
+ IS 'Time when this online signing key will no longer be used to sign.';
+COMMENT ON COLUMN exchange_sign_keys.expire_legal
+ IS 'Time when this online signing key legally expires.';
diff --git a/src/exchangedb/0002-extensions.sql b/src/exchangedb/0002-extensions.sql
new file mode 100644
index 000000000..df9e50090
--- /dev/null
+++ b/src/exchangedb/0002-extensions.sql
@@ -0,0 +1,27 @@
+--
+-- 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 TABLE extensions
+ (extension_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,name TEXT NOT NULL UNIQUE
+ ,manifest BYTEA
+ );
+COMMENT ON TABLE extensions
+ IS 'Configurations of the activated extensions';
+COMMENT ON COLUMN extensions.name
+ IS 'Name of the extension';
+COMMENT ON COLUMN extensions.manifest
+ IS 'Manifest of the extension as JSON-blob, maybe NULL. It contains common meta-information and extension-specific configuration.';
diff --git a/src/exchangedb/0002-global_fee.sql b/src/exchangedb/0002-global_fee.sql
new file mode 100644
index 000000000..f6526798d
--- /dev/null
+++ b/src/exchangedb/0002-global_fee.sql
@@ -0,0 +1,37 @@
+--
+-- 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 TABLE global_fee
+ (global_fee_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,start_date INT8 NOT NULL
+ ,end_date INT8 NOT NULL
+ ,history_fee taler_amount NOT NULL
+ ,account_fee taler_amount NOT NULL
+ ,purse_fee taler_amount NOT NULL
+ ,purse_timeout INT8 NOT NULL
+ ,history_expiration INT8 NOT NULL
+ ,purse_account_limit INT4 NOT NULL
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ ,PRIMARY KEY (start_date)
+ );
+COMMENT ON TABLE global_fee
+ IS 'list of the global fees of this exchange, by date';
+COMMENT ON COLUMN global_fee.global_fee_serial
+ IS 'needed for exchange-auditor replication logic';
+
+CREATE INDEX global_fee_by_end_date_index
+ ON global_fee
+ (end_date);
diff --git a/src/exchangedb/0002-history_requests.sql b/src/exchangedb/0002-history_requests.sql
new file mode 100644
index 000000000..0714e1bea
--- /dev/null
+++ b/src/exchangedb/0002-history_requests.sql
@@ -0,0 +1,159 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_history_requests(
+ IN shard_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'history_requests';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(history_request_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)'
+ ',request_timestamp INT8 NOT NULL'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',history_fee taler_amount NOT NULL'
+ ',PRIMARY KEY (reserve_pub,request_timestamp)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Paid history requests issued by a client against a reserve'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'When was the history request made'
+ ,'request_timestamp'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature approving payment for the history request'
+ ,'reserve_sig'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'History fee approved by the signature'
+ ,'history_fee'
+ ,table_name
+ ,shard_suffix
+ );
+END $$;
+
+
+CREATE FUNCTION constrain_table_history_requests(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ partition_name TEXT;
+BEGIN
+ partition_name = concat_ws('_', 'history_requests', partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || partition_name ||
+ ' ADD CONSTRAINT ' || partition_name || '_serial_id'
+ ' UNIQUE (history_request_serial_id)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_history_requests()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'history_requests';
+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'
+ );
+END $$;
+
+
+CREATE OR REPLACE FUNCTION history_requests_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.reserve_pub
+ ,'history_requests'
+ ,NEW.history_request_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION history_requests_insert_trigger()
+ IS 'Automatically generate reserve history entry.';
+
+
+CREATE FUNCTION master_table_history_requests()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER history_requests_on_insert
+ AFTER INSERT
+ ON history_requests
+ FOR EACH ROW EXECUTE FUNCTION history_requests_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('history_requests'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('history_requests'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('history_requests'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('history_requests'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-known_coins.sql b/src/exchangedb/0002-known_coins.sql
new file mode 100644
index 000000000..a13beff6f
--- /dev/null
+++ b/src/exchangedb/0002-known_coins.sql
@@ -0,0 +1,136 @@
+--
+-- 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 FUNCTION create_table_known_coins(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'known_coins';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(known_coin_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',denominations_serial INT8 NOT NULL'
+ ',coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(coin_pub)=32)'
+ ',age_commitment_hash BYTEA CHECK (LENGTH(age_commitment_hash)=32)'
+ ',denom_sig BYTEA NOT NULL'
+ ',remaining taler_amount NOT NULL DEFAULT(0,0)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'information about coins and their signatures, so we do not have to store the signatures more than once if a coin is involved in multiple operations'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Denomination of the coin, determines the value of the original coin and applicable fees for coin-specific operations.'
+ ,'denominations_serial'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'EdDSA public key of the coin'
+ ,'coin_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Value of the coin that remains to be spent'
+ ,'remaining'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Optional hash of the age commitment for age restrictions as per DD 24 (active if denom_type has the respective bit set)'
+ ,'age_commitment_hash'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'This is the signature of the exchange that affirms that the coin is a valid coin. The specific signature type depends on denom_type of the denomination.'
+ ,'denom_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_known_coins(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'known_coins';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_known_coin_id_key'
+ ' UNIQUE (known_coin_id)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_known_coins()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'known_coins';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_denominations'
+ ' FOREIGN KEY (denominations_serial) '
+ ' REFERENCES denominations (denominations_serial) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('known_coins'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('known_coins'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('known_coins'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-kyc_alerts.sql b/src/exchangedb/0002-kyc_alerts.sql
new file mode 100644
index 000000000..8e54846cf
--- /dev/null
+++ b/src/exchangedb/0002-kyc_alerts.sql
@@ -0,0 +1,27 @@
+--
+-- 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 TABLE kyc_alerts
+ (h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=32)
+ ,trigger_type INT4 NOT NULL
+ ,UNIQUE(trigger_type,h_payto)
+ );
+COMMENT ON TABLE kyc_alerts
+ IS 'alerts about completed KYC events reliably notifying other components (even if they are not running)';
+COMMENT ON COLUMN kyc_alerts.h_payto
+ IS 'hash of the payto://-URI for which the KYC status changed';
+COMMENT ON COLUMN kyc_alerts.trigger_type
+ IS 'identifies the receiver of the alert, as the same h_payto may require multiple components to be notified';
diff --git a/src/exchangedb/0002-kyc_attributes.sql b/src/exchangedb/0002-kyc_attributes.sql
new file mode 100644
index 000000000..66f3fc315
--- /dev/null
+++ b/src/exchangedb/0002-kyc_attributes.sql
@@ -0,0 +1,162 @@
+--
+-- 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 create_table_kyc_attributes(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'kyc_attributes';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I'
+ '(kyc_attributes_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=32)'
+ ',kyc_prox BYTEA NOT NULL CHECK (LENGTH(kyc_prox)=32)'
+ ',provider TEXT NOT NULL'
+ ',satisfied_checks TEXT[] NOT NULL'
+ ',collection_time INT8 NOT NULL'
+ ',expiration_time INT8 NOT NULL'
+ ',encrypted_attributes BYTEA NOT NULL'
+ ',legitimization_serial INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (h_payto)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'KYC data about particular payment addresses'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'hash of payto://-URI the attributes are about'
+ ,'h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'short hash of normalized full name and birthdate; used to efficiently find likely duplicate users'
+ ,'kyc_prox'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'time when the attributes were collected by the provider'
+ ,'collection_time'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'time when the attributes should no longer be considered validated'
+ ,'expiration_time'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'configuration section name of the provider that affirmed the attributes'
+ ,'provider'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ '(encrypted) JSON object (as string) with the attributes'
+ ,'encrypted_attributes'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Reference the legitimization process for which these attributes are gathered for.'
+ ,'legitimization_serial'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+COMMENT ON FUNCTION create_table_kyc_attributes
+ IS 'Creates the kyc_attributes table';
+
+
+CREATE OR REPLACE FUNCTION constrain_table_kyc_attributes(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'kyc_attributes';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_serial_key '
+ 'UNIQUE (kyc_attributes_serial_id)'
+ );
+ -- To search similar users (e.g. during AML checks)
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_similarity_index '
+ 'ON ' || table_name || ' '
+ '(kyc_prox);'
+ );
+ -- For garbage collection
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_expiration_time '
+ 'ON ' || table_name || ' '
+ '(expiration_time ASC);'
+ );
+END $$;
+
+
+CREATE OR REPLACE FUNCTION foreign_table_kyc_attributes()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'kyc_attributes';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_legitimization_processes'
+ ' FOREIGN KEY (legitimization_serial) '
+ ' REFERENCES legitimization_processes (legitimization_process_serial_id)' -- ON DELETE CASCADE
+ );
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('kyc_attributes'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('kyc_attributes'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('kyc_attributes'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-legitimization_processes.sql b/src/exchangedb/0002-legitimization_processes.sql
new file mode 100644
index 000000000..3212b1c06
--- /dev/null
+++ b/src/exchangedb/0002-legitimization_processes.sql
@@ -0,0 +1,149 @@
+--
+-- 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 FUNCTION create_table_legitimization_processes(
+ IN shard_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(legitimization_process_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=32)'
+ ',start_time INT8 NOT NULL'
+ ',expiration_time INT8 NOT NULL DEFAULT (0)'
+ ',provider_section TEXT NOT NULL'
+ ',provider_user_id TEXT DEFAULT NULL'
+ ',provider_legitimization_id TEXT DEFAULT NULL'
+ ',redirect_url TEXT DEFAULT NULL'
+ ',finished BOOLEAN DEFAULT (FALSE)'
+ ') %s ;'
+ ,'legitimization_processes'
+ ,'PARTITION BY HASH (h_payto)'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'List of legitimization processes (ongoing and completed) by account and provider'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'unique ID for this legitimization process at the exchange'
+ ,'legitimization_process_serial_id'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'foreign key linking the entry to the wire_targets table, NOT a primary key (multiple legitimizations are possible per wire target)'
+ ,'h_payto'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'time when the KYC check was initiated, useful for garbage collection'
+ ,'expiration_time'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'URL where the user should go to begin the KYC process'
+ ,'redirect_url'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'in the future if the respective KYC check was passed successfully'
+ ,'expiration_time'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Configuration file section with details about this provider'
+ ,'provider_section'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifier for the user at the provider that was used for the legitimization. NULL if provider is unaware.'
+ ,'provider_user_id'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifier for the specific legitimization process at the provider. NULL if legitimization was not started.'
+ ,'provider_legitimization_id'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Set to TRUE when the specific legitimization process is finished.'
+ ,'finished'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+END
+$$;
+
+-- We need a separate function for this, as we call create_table only once but need to add
+-- those constraints to each partition which gets created
+CREATE FUNCTION constrain_table_legitimization_processes(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ partition_name TEXT;
+BEGIN
+ partition_name = concat_ws('_', 'legitimization_processes', partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || partition_name
+ || ' '
+ 'ADD CONSTRAINT ' || partition_name || '_serial_key '
+ 'UNIQUE (legitimization_process_serial_id)');
+ EXECUTE FORMAT (
+ 'CREATE INDEX IF NOT EXISTS ' || partition_name || '_by_provider_and_legi_index '
+ 'ON '|| partition_name || ' '
+ '(provider_section,provider_legitimization_id)'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || partition_name || '_by_provider_and_legi_index '
+ 'IS ' || quote_literal('used (rarely) in kyc_provider_account_lookup') || ';'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('legitimization_processes'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('legitimization_processes'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-legitimization_requirements.sql b/src/exchangedb/0002-legitimization_requirements.sql
new file mode 100644
index 000000000..d806eb424
--- /dev/null
+++ b/src/exchangedb/0002-legitimization_requirements.sql
@@ -0,0 +1,104 @@
+--
+-- 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 FUNCTION create_table_legitimization_requirements(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(legitimization_requirement_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=32)'
+ ',reserve_pub BYTEA'
+ ',required_checks TEXT NOT NULL'
+ ',UNIQUE (h_payto, required_checks)'
+ ') %s ;'
+ ,'legitimization_requirements'
+ ,'PARTITION BY HASH (h_payto)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'List of required legitimizations by account'
+ ,'legitimization_requirements'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'unique ID for this legitimization requirement at the exchange'
+ ,'legitimization_requirement_serial_id'
+ ,'legitimization_requirements'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'foreign key linking the entry to the wire_targets table, NOT a primary key (multiple legitimizations are possible per wire target)'
+ ,'h_payto'
+ ,'legitimization_requirements'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'if h_payto refers to a reserve, this is its public key, NULL otherwise. It allows to lookup the corresponding reserve when the KYC process is done.'
+ ,'reserve_pub'
+ ,'legitimization_requirements'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'space-separated list of required checks'
+ ,'required_checks'
+ ,'legitimization_requirements'
+ ,partition_suffix
+ );
+END
+$$;
+
+-- We need a separate function for this, as we call create_table only once but need to add
+-- those constraints to each partition which gets created
+CREATE FUNCTION constrain_table_legitimization_requirements(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ partition_name TEXT;
+BEGIN
+ partition_name = concat_ws('_', 'legitimization_requirements', partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || partition_name || ' '
+ 'ADD CONSTRAINT ' || partition_name || '_serial_id_key '
+ 'UNIQUE (legitimization_requirement_serial_id)');
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('legitimization_requirements'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('legitimization_requirements'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-partner_accounts.sql b/src/exchangedb/0002-partner_accounts.sql
new file mode 100644
index 000000000..b12dc06e6
--- /dev/null
+++ b/src/exchangedb/0002-partner_accounts.sql
@@ -0,0 +1,33 @@
+--
+-- 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 TABLE partner_accounts
+ (payto_uri TEXT PRIMARY KEY
+ ,partner_serial_id INT8 REFERENCES partners(partner_serial_id) ON DELETE CASCADE
+ ,partner_master_sig BYTEA CHECK (LENGTH(partner_master_sig)=64)
+ ,last_seen INT8 NOT NULL
+ );
+CREATE INDEX IF NOT EXISTS partner_accounts_index_by_partner_and_time
+ ON partner_accounts (partner_serial_id,last_seen);
+COMMENT ON TABLE partner_accounts
+ IS 'Table with bank accounts of the partner exchange. Entries never expire as we need to remember the signature for the auditor.';
+COMMENT ON COLUMN partner_accounts.payto_uri
+ IS 'payto URI (RFC 8905) with the bank account of the partner exchange.';
+COMMENT ON COLUMN partner_accounts.partner_master_sig
+ IS 'Signature of purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS by the partner master public key';
+COMMENT ON COLUMN partner_accounts.last_seen
+ IS 'Last time we saw this account as being active at the partner exchange. Used to select the most recent entry, and to detect when we should check again.';
diff --git a/src/exchangedb/0002-partners.sql b/src/exchangedb/0002-partners.sql
new file mode 100644
index 000000000..ed09378d0
--- /dev/null
+++ b/src/exchangedb/0002-partners.sql
@@ -0,0 +1,49 @@
+--
+-- 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 TABLE partners
+ (partner_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,partner_master_pub BYTEA NOT NULL CHECK(LENGTH(partner_master_pub)=32)
+ ,start_date INT8 NOT NULL
+ ,end_date INT8 NOT NULL
+ ,next_wad INT8 NOT NULL DEFAULT (0)
+ ,wad_frequency INT8 NOT NULL
+ ,wad_fee taler_amount 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';
+COMMENT ON COLUMN partners.partner_master_pub
+ IS 'offline master public key of the partner';
+COMMENT ON COLUMN partners.start_date
+ IS 'starting date of the partnership';
+COMMENT ON COLUMN partners.end_date
+ IS 'end date of the partnership';
+COMMENT ON COLUMN partners.next_wad
+ IS 'at what time should we do the next wad transfer to this partner (frequently updated); set to forever after the end_date';
+COMMENT ON COLUMN partners.wad_frequency
+ IS 'how often do we promise to do wad transfers';
+COMMENT ON COLUMN partners.wad_fee
+ IS 'how high is the fee for a wallet to be added to a wad to this partner';
+COMMENT ON COLUMN partners.partner_base_url
+ IS 'base URL of the REST API for this partner';
+COMMENT ON COLUMN partners.master_sig
+ IS 'signature of our master public key affirming the partnership, of purpose TALER_SIGNATURE_MASTER_PARTNER_DETAILS';
+
+CREATE INDEX IF NOT EXISTS partner_by_wad_time
+ ON partners (next_wad ASC);
diff --git a/src/exchangedb/0002-policy_details.sql b/src/exchangedb/0002-policy_details.sql
new file mode 100644
index 000000000..3acbb5c10
--- /dev/null
+++ b/src/exchangedb/0002-policy_details.sql
@@ -0,0 +1,175 @@
+--
+-- 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/>
+--
+
+-- @author: Özgür Kesim
+
+CREATE FUNCTION create_table_policy_details(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'policy_details';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(policy_details_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',policy_hash_code gnunet_hashcode NOT NULL'
+ ',policy_json TEXT NOT NULL'
+ ',deadline INT8 NOT NULL'
+ ',commitment taler_amount NOT NULL'
+ ',accumulated_total taler_amount NOT NULL'
+ ',fee taler_amount NOT NULL'
+ ',transferable taler_amount NOT NULL'
+ ',fulfillment_state SMALLINT NOT NULL CHECK(fulfillment_state between 0 and 5)'
+ ',h_fulfillment_proof gnunet_hashcode'
+ ') %s;'
+ ,table_name
+ ,'PARTITION BY HASH (h_fulfillment_proof)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Policies that were provided with deposits via policy extensions.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'ID (GNUNET_HashCode) that identifies a policy. Will be calculated by the policy extension based on the content'
+ ,'policy_hash_code'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'JSON object with options set that the exchange needs to consider when executing a deposit. Supported details depend on the policy extensions supported by the exchange.'
+ ,'policy_json'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Deadline until the policy must be marked as fulfilled (maybe "forever")'
+ ,'deadline'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The amount that this policy commits to. Invariant: commitment >= fee'
+ ,'commitment'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The sum of all contributions of all deposit that reference this policy. Invariant: The fulfilment_state must be Insufficient as long as accumulated_total < commitment'
+ ,'accumulated_total'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The fee for this policy, due when the policy is fulfilled or timed out'
+ ,'fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The amount that on fulfillment or timeout will be transferred to the payto-URI''s of the corresponding deposit''s. The policy fees must have been already deducted from it. Invariant: fee+transferable <= accumulated_total. The remaining amount (accumulated_total - fee - transferable) can be refreshed by the owner of the coins when the state is Timeout or Success.'
+ ,'transferable'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'State of the fulfillment:
+ - 0 (Failure)
+ - 1 (Insufficient)
+ - 2 (Ready)
+ - 4 (Success)
+ - 5 (Timeout)'
+ ,'fulfillment_state'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Reference to the proof of the fulfillment of this policy, if it exists. Invariant: If not NULL, this entry''s .hash_code MUST be part of the corresponding policy_fulfillments.policy_hash_codes array.'
+ ,'h_fulfillment_proof'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+COMMENT ON FUNCTION create_table_policy_details
+ IS 'Creates the policy_details table';
+
+
+
+
+CREATE FUNCTION constrain_table_policy_details(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ partition_name TEXT;
+BEGIN
+ partition_name = concat_ws('_', 'policy_details', partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || partition_name ||
+ ' ADD CONSTRAINT ' || partition_name || '_unique_serial_id '
+ ' UNIQUE (policy_details_serial_id)'
+ );
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || partition_name ||
+ ' ADD CONSTRAINT ' || partition_name || '_unique_hash_fulfillment_proof '
+ ' UNIQUE (policy_hash_code, h_fulfillment_proof)'
+ );
+
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || partition_name || '_policy_hash_code'
+ ' ON ' || partition_name ||
+ ' (policy_hash_code);'
+ );
+END
+$$;
+
+CREATE OR REPLACE FUNCTION foreign_table_policy_details()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'policy_details';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_policy_fulfillments'
+ ' FOREIGN KEY (h_fulfillment_proof) '
+ ' REFERENCES policy_fulfillments (h_fulfillment_proof) ON DELETE RESTRICT'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+VALUES
+ ('policy_details', 'exchange-0002', 'create', TRUE ,FALSE),
+ ('policy_details', 'exchange-0002', 'constrain', TRUE ,FALSE),
+ ('policy_details', 'exchange-0002', 'foreign', TRUE ,FALSE);
diff --git a/src/exchangedb/0002-policy_fulfillments.sql b/src/exchangedb/0002-policy_fulfillments.sql
new file mode 100644
index 000000000..c00947019
--- /dev/null
+++ b/src/exchangedb/0002-policy_fulfillments.sql
@@ -0,0 +1,101 @@
+--
+-- 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/>
+--
+
+-- @author: Özgür Kesim
+
+CREATE FUNCTION create_table_policy_fulfillments(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'policy_fulfillments';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(h_fulfillment_proof gnunet_hashcode PRIMARY KEY'
+ ',fulfillment_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',fulfillment_timestamp INT8 NOT NULL'
+ ',fulfillment_proof TEXT'
+ ',policy_hash_codes gnunet_hashcode[] NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (h_fulfillment_proof)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Proofs of fulfillment of policies that were set in deposits'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Timestamp of the arrival of a proof of fulfillment'
+ ,'fulfillment_timestamp'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'JSON object with a proof of the fulfillment of a policy. Supported details depend on the policy extensions supported by the exchange.'
+ ,'fulfillment_proof'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Hash of the fulfillment_proof'
+ ,'h_fulfillment_proof'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Array of the policy_hash_code''s of all policy_details that are fulfilled by this proof'
+ ,'policy_hash_codes'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+COMMENT ON FUNCTION create_table_policy_fulfillments
+ IS 'Creates the policy_fulfillments table';
+
+CREATE FUNCTION constrain_table_policy_fulfillments(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ partition_name TEXT;
+BEGIN
+ partition_name = concat_ws('_', 'policy_fulfillments', partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || partition_name ||
+ ' ADD CONSTRAINT ' || partition_name || '_serial_id '
+ ' UNIQUE (h_fulfillment_proof, fulfillment_id)'
+ );
+END
+$$;
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+VALUES
+ ('policy_fulfillments', 'exchange-0002', 'create', TRUE ,FALSE),
+ ('policy_fulfillments', 'exchange-0002', 'constrain', TRUE ,FALSE);
diff --git a/src/exchangedb/0002-prewire.sql b/src/exchangedb/0002-prewire.sql
new file mode 100644
index 000000000..396a27608
--- /dev/null
+++ b/src/exchangedb/0002-prewire.sql
@@ -0,0 +1,116 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_prewire(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'prewire';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(prewire_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'
+ ',wire_method TEXT NOT NULL'
+ ',finished BOOLEAN NOT NULL DEFAULT FALSE'
+ ',failed BOOLEAN NOT NULL DEFAULT FALSE'
+ ',buf BYTEA NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (prewire_uuid)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'pre-commit data for wire transfers we are about to execute'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'set to TRUE if the bank responded with a non-transient failure to our transfer request'
+ ,'failed'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'set to TRUE once bank confirmed receiving the wire transfer request'
+ ,'finished'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'serialized data to send to the bank to execute the wire transfer'
+ ,'buf'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_prewire(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'prewire';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_finished_index '
+ 'ON ' || table_name || ' '
+ '(finished)'
+ ' WHERE finished;'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_finished_index '
+ 'IS ' || quote_literal('for do_gc') || ';'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_failed_finished_index '
+ 'ON ' || table_name || ' '
+ '(prewire_uuid)'
+ ' WHERE finished=FALSE'
+ ' AND failed=FALSE;'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_failed_finished_index '
+ 'IS ' || quote_literal('for wire_prepare_data_get') || ';'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('prewire'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('prewire'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-profit_drains.sql b/src/exchangedb/0002-profit_drains.sql
new file mode 100644
index 000000000..c4f3a7bd0
--- /dev/null
+++ b/src/exchangedb/0002-profit_drains.sql
@@ -0,0 +1,42 @@
+--
+-- 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 TABLE profit_drains
+ (profit_drain_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,wtid BYTEA PRIMARY KEY CHECK (LENGTH(wtid)=32)
+ ,account_section TEXT NOT NULL
+ ,payto_uri TEXT NOT NULL
+ ,trigger_date INT8 NOT NULL
+ ,amount taler_amount NOT NULL
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ ,executed BOOLEAN NOT NULL DEFAULT FALSE
+ );
+COMMENT ON TABLE profit_drains
+ IS 'transactions to be performed to move profits from the escrow account of the exchange to a regular account';
+COMMENT ON COLUMN profit_drains.wtid
+ IS 'randomly chosen nonce, unique to prevent double-submission';
+COMMENT ON COLUMN profit_drains.account_section
+ IS 'specifies the configuration section in the taler-exchange-drain configuration with the wire account to drain';
+COMMENT ON COLUMN profit_drains.payto_uri
+ IS 'specifies the account to be credited';
+COMMENT ON COLUMN profit_drains.trigger_date
+ IS 'set by taler-exchange-offline at the time of making the signature; not necessarily the exact date of execution of the wire transfer, just for orientation';
+COMMENT ON COLUMN profit_drains.amount
+ IS 'amount to be transferred';
+COMMENT ON COLUMN profit_drains.master_sig
+ IS 'EdDSA signature of type TALER_SIGNATURE_MASTER_DRAIN_PROFIT';
+COMMENT ON COLUMN profit_drains.executed
+ IS 'set to TRUE by taler-exchange-drain on execution of the transaction, not replicated to auditor';
diff --git a/src/exchangedb/0002-purse_actions.sql b/src/exchangedb/0002-purse_actions.sql
new file mode 100644
index 000000000..0dd6cfc4d
--- /dev/null
+++ b/src/exchangedb/0002-purse_actions.sql
@@ -0,0 +1,121 @@
+--
+-- 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 create_table_purse_actions(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_actions';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I'
+ '(purse_pub BYTEA NOT NULL PRIMARY KEY CHECK(LENGTH(purse_pub)=32)'
+ ',action_date INT8 NOT NULL'
+ ',partner_serial_id INT8'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'purses awaiting some action by the router'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public (contract) key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'when is the purse ready for action'
+ ,'action_date'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'wad target of an outgoing wire transfer, 0 for local, NULL if the purse is unmerged and thus the target is still unknown'
+ ,'partner_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+
+CREATE OR REPLACE FUNCTION purse_requests_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO
+ purse_actions
+ (purse_pub
+ ,action_date)
+ VALUES
+ (NEW.purse_pub
+ ,NEW.purse_expiration);
+ RETURN NEW;
+END $$;
+
+COMMENT ON FUNCTION purse_requests_insert_trigger()
+ IS 'When a purse is created, insert it into the purse_action table to take action when the purse expires.';
+
+
+CREATE OR REPLACE FUNCTION master_table_purse_actions()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_actions';
+BEGIN
+ -- Create global index
+ CREATE INDEX IF NOT EXISTS purse_action_by_target
+ ON purse_actions
+ (partner_serial_id,action_date);
+
+ -- Setup trigger
+ CREATE TRIGGER purse_requests_on_insert
+ AFTER INSERT
+ ON purse_requests
+ FOR EACH ROW EXECUTE FUNCTION purse_requests_insert_trigger();
+ COMMENT ON TRIGGER purse_requests_on_insert
+ ON purse_requests
+ IS 'Here we install an entry for the purse expiration.';
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_actions'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('purse_actions'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-purse_decision.sql b/src/exchangedb/0002-purse_decision.sql
new file mode 100644
index 000000000..091bd468b
--- /dev/null
+++ b/src/exchangedb/0002-purse_decision.sql
@@ -0,0 +1,143 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_purse_decision(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_decision';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(purse_decision_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ',action_timestamp INT8 NOT NULL'
+ ',refunded BOOL NOT NULL'
+ ',PRIMARY KEY (purse_pub)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Purses that were decided upon (refund or merge)'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+CREATE FUNCTION constrain_table_purse_decision(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_decision';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_purse_action_serial_id_key'
+ ' UNIQUE (purse_decision_serial_id) '
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION purse_decision_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ UPDATE purse_requests
+ SET was_decided=TRUE
+ WHERE purse_pub=NEW.purse_pub;
+ IF NEW.refunded
+ THEN
+ INSERT INTO coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ SELECT
+ pd.coin_pub
+ ,'purse_decision'
+ ,NEW.purse_decision_serial_id
+ FROM purse_deposits pd
+ WHERE purse_pub = NEW.purse_pub;
+ ELSE
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ SELECT
+ reserve_pub
+ ,'purse_decision'
+ ,NEW.purse_decision_serial_id
+ FROM purse_merges
+ WHERE purse_pub=NEW.purse_pub;
+ END IF;
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION purse_decision_insert_trigger()
+ IS 'Automatically generate coin history entry and update decision status for the purse.';
+
+
+CREATE FUNCTION master_table_purse_decision()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER purse_decision_on_insert
+ AFTER INSERT
+ ON purse_decision
+ FOR EACH ROW EXECUTE FUNCTION purse_decision_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_decision'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('purse_decision'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('purse_decision'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-purse_deletion.sql b/src/exchangedb/0002-purse_deletion.sql
new file mode 100644
index 000000000..45b2e85a9
--- /dev/null
+++ b/src/exchangedb/0002-purse_deletion.sql
@@ -0,0 +1,110 @@
+--
+-- 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 create_table_purse_deletion(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_deletion';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I'
+ '(purse_deletion_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',purse_sig BYTEA CHECK (LENGTH(purse_sig)=64)'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'signatures affirming explicit purse deletions'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'signature of type WALLET_PURSE_DELETE'
+ ,'purse_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+COMMENT ON FUNCTION create_table_purse_deletion
+ IS 'Creates the purse_deletion table';
+
+
+CREATE OR REPLACE FUNCTION constrain_table_purse_deletion(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_deletion';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_delete_serial_key '
+ 'UNIQUE (purse_deletion_serial_id)'
+ );
+END $$;
+
+
+CREATE OR REPLACE FUNCTION master_table_purse_requests_was_deleted (
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_requests';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE exchange.' || table_name ||
+ ' ADD COLUMN'
+ ' was_deleted BOOLEAN NOT NULL DEFAULT(FALSE)'
+ );
+ COMMENT ON COLUMN purse_requests.was_deleted
+ IS 'TRUE if the purse was explicitly deleted (purse must have an entry in the purse_deletion table)';
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_deletion'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('purse_deletion'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('purse_requests_was_deleted'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-purse_deposits.sql b/src/exchangedb/0002-purse_deposits.sql
new file mode 100644
index 000000000..6a07c4b62
--- /dev/null
+++ b/src/exchangedb/0002-purse_deposits.sql
@@ -0,0 +1,176 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_purse_deposits(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_deposits';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(purse_deposit_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',partner_serial_id INT8'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ',coin_pub BYTEA NOT NULL'
+ ',amount_with_fee taler_amount NOT NULL'
+ ',coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)'
+ ',PRIMARY KEY (purse_pub,coin_pub)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Requests depositing coins into a purse'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'identifies the partner exchange, NULL in case the target purse lives at this exchange'
+ ,'partner_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the coin being deposited'
+ ,'coin_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total amount being deposited'
+ ,'amount_with_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature of the coin affirming the deposit into the purse, of type TALER_SIGNATURE_PURSE_DEPOSIT'
+ ,'coin_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_purse_deposits(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_deposits';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_purse_deposit_serial_id_key'
+ ' UNIQUE (purse_deposit_serial_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_purse_deposits()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_deposits';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_partner'
+ ' FOREIGN KEY (partner_serial_id) '
+ ' REFERENCES partners(partner_serial_id) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (coin_pub) '
+ ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION purse_deposits_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.coin_pub
+ ,'purse_deposits'
+ ,NEW.purse_deposit_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION purse_deposits_insert_trigger()
+ IS 'Automatically generate coin history entry.';
+
+
+CREATE FUNCTION master_table_purse_deposits()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER purse_deposits_on_insert
+ AFTER INSERT
+ ON purse_deposits
+ FOR EACH ROW EXECUTE FUNCTION purse_deposits_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_deposits'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('purse_deposits'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('purse_deposits'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('purse_deposits'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-purse_merges.sql b/src/exchangedb/0002-purse_merges.sql
new file mode 100644
index 000000000..0b4d230b3
--- /dev/null
+++ b/src/exchangedb/0002-purse_merges.sql
@@ -0,0 +1,140 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_purse_merges(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_merges';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(purse_merge_request_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',partner_serial_id INT8'
+ ',reserve_pub BYTEA NOT NULL CHECK(length(reserve_pub)=32)'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ',merge_sig BYTEA NOT NULL CHECK (LENGTH(merge_sig)=64)'
+ ',merge_timestamp INT8 NOT NULL'
+ ',PRIMARY KEY (purse_pub)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Merge requests where a purse-owner requested merging the purse into the account'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'identifies the partner exchange, NULL in case the target reserve lives at this exchange'
+ ,'partner_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public key of the target reserve'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'signature by the purse private key affirming the merge, of type TALER_SIGNATURE_WALLET_PURSE_MERGE'
+ ,'merge_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'when was the merge message signed'
+ ,'merge_timestamp'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_purse_merges(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_merges';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_purse_merge_request_serial_id_key'
+ ' UNIQUE (purse_merge_request_serial_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_purse_merges()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_merges';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_partner_serial_id'
+ ' FOREIGN KEY (partner_serial_id) '
+ ' REFERENCES partners(partner_serial_id) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_purse_pub'
+ ' FOREIGN KEY (purse_pub) '
+ ' REFERENCES purse_requests (purse_pub) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_merges'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('purse_merges'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('purse_merges'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-purse_requests.sql b/src/exchangedb/0002-purse_requests.sql
new file mode 100644
index 000000000..0fa076338
--- /dev/null
+++ b/src/exchangedb/0002-purse_requests.sql
@@ -0,0 +1,163 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_purse_requests(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_requests';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(purse_requests_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ',merge_pub BYTEA NOT NULL CHECK (LENGTH(merge_pub)=32)'
+ ',purse_creation INT8 NOT NULL'
+ ',purse_expiration INT8 NOT NULL'
+ ',h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)'
+ ',age_limit INT4 NOT NULL'
+ ',flags INT4 NOT NULL'
+ ',in_reserve_quota BOOLEAN NOT NULL DEFAULT(FALSE)'
+ ',was_decided BOOLEAN NOT NULL DEFAULT(FALSE)'
+ ',amount_with_fee taler_amount NOT NULL'
+ ',purse_fee taler_amount NOT NULL'
+ ',balance taler_amount NOT NULL DEFAULT (0,0)'
+ ',purse_sig BYTEA NOT NULL CHECK(LENGTH(purse_sig)=64)'
+ ',PRIMARY KEY (purse_pub)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Requests establishing purses, associating them with a contract but without a target reserve'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Local time when the purse was created. Determines applicable purse fees.'
+ ,'purse_creation'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'When the purse is set to expire'
+ ,'purse_expiration'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Hash of the contract the parties are to agree to'
+ ,'h_contract_terms'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'see the enum TALER_WalletAccountMergeFlags'
+ ,'flags'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'set to TRUE if this purse currently counts against the number of free purses in the respective reserve'
+ ,'in_reserve_quota'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total amount expected to be in the purse'
+ ,'amount_with_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Purse fee the client agreed to pay from the reserve (accepted by the exchange at the time the purse was created). Zero if in_reserve_quota is TRUE.'
+ ,'purse_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total amount actually in the purse (updated)'
+ ,'balance'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature of the purse affirming the purse parameters, of type TALER_SIGNATURE_PURSE_REQUEST'
+ ,'purse_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+CREATE FUNCTION constrain_table_purse_requests(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_requests';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_merge_pub '
+ 'ON ' || table_name || ' '
+ '(merge_pub);'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_purse_expiration '
+ 'ON ' || table_name || ' '
+ '(purse_expiration) ' ||
+ 'WHERE NOT was_decided;'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_purse_requests_serial_id_key'
+ ' UNIQUE (purse_requests_serial_id) '
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_requests'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('purse_requests'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-recoup.sql b/src/exchangedb/0002-recoup.sql
new file mode 100644
index 000000000..4b3452498
--- /dev/null
+++ b/src/exchangedb/0002-recoup.sql
@@ -0,0 +1,267 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_recoup(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(recoup_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+ ',coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)'
+ ',coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)'
+ ',amount taler_amount NOT NULL'
+ ',recoup_timestamp INT8 NOT NULL'
+ ',reserve_out_serial_id INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub);'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Information about recoups that were executed between a coin and a reserve. In this type of recoup, the amount is credited back to the reserve from which the coin originated.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Coin that is being debited in the recoup. Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!'
+ ,'coin_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the h_blind_ev of the recouped coin and provides the link to the credited reserve.'
+ ,'reserve_out_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature by the coin affirming the recoup, of type TALER_SIGNATURE_WALLET_COIN_RECOUP'
+ ,'coin_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Denomination blinding key used when creating the blinded coin from the planchet. Secret revealed during the recoup to provide the linkage between the coin and the withdraw operation.'
+ ,'coin_blind'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_recoup(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_coin_pub_index '
+ 'ON ' || table_name || ' '
+ '(coin_pub);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_recoup_uuid_key'
+ ' UNIQUE (recoup_uuid) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_recoup()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_reserves_out'
+ ' FOREIGN KEY (reserve_out_serial_id) '
+ ' REFERENCES reserves_out (reserve_out_serial_id) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (coin_pub) '
+ ' REFERENCES known_coins (coin_pub)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION create_table_recoup_by_reserve(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup_by_reserve';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(reserve_out_serial_id INT8 NOT NULL' -- REFERENCES reserves (reserve_out_serial_id) ON DELETE CASCADE
+ ',coin_pub BYTEA CHECK (LENGTH(coin_pub)=32)' -- REFERENCES known_coins (coin_pub)
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_out_serial_id)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Information in this table is strictly redundant with that of recoup, but saved by a different primary key for fast lookups by reserve_out_serial_id.'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_recoup_by_reserve(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup_by_reserve';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_main_index '
+ 'ON ' || table_name || ' '
+ '(reserve_out_serial_id);'
+ );
+END
+$$;
+
+
+CREATE FUNCTION recoup_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO recoup_by_reserve
+ (reserve_out_serial_id
+ ,coin_pub)
+ VALUES
+ (NEW.reserve_out_serial_id
+ ,NEW.coin_pub);
+ INSERT INTO coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.coin_pub
+ ,'recoup'
+ ,NEW.recoup_uuid);
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ SELECT
+ res.reserve_pub
+ ,'recoup'
+ ,NEW.recoup_uuid
+ FROM reserves_out rout
+ JOIN reserves res
+ USING (reserve_uuid)
+ WHERE rout.reserve_out_serial_id = NEW.reserve_out_serial_id;
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION recoup_insert_trigger()
+ IS 'Replicates recoup inserts into recoup_by_reserve table and updates the coin_history table.';
+
+
+CREATE FUNCTION recoup_delete_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ DELETE FROM recoup_by_reserve
+ WHERE reserve_out_serial_id = OLD.reserve_out_serial_id
+ AND coin_pub = OLD.coin_pub;
+ RETURN OLD;
+END $$;
+COMMENT ON FUNCTION recoup_delete_trigger()
+ IS 'Replicate recoup deletions into recoup_by_reserve table.';
+
+
+CREATE FUNCTION master_table_recoup()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER recoup_on_insert
+ AFTER INSERT
+ ON recoup
+ FOR EACH ROW EXECUTE FUNCTION recoup_insert_trigger();
+ CREATE TRIGGER recoup_on_delete
+ AFTER DELETE
+ ON recoup
+ FOR EACH ROW EXECUTE FUNCTION recoup_delete_trigger();
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('recoup'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('recoup'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('recoup'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('recoup_by_reserve'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('recoup_by_reserve'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('recoup'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-recoup_refresh.sql b/src/exchangedb/0002-recoup_refresh.sql
new file mode 100644
index 000000000..8b979a49f
--- /dev/null
+++ b/src/exchangedb/0002-recoup_refresh.sql
@@ -0,0 +1,203 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_recoup_refresh(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup_refresh';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(recoup_refresh_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+ ',known_coin_id BIGINT NOT NULL'
+ ',coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)'
+ ',coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)'
+ ',amount taler_amount NOT NULL'
+ ',recoup_timestamp INT8 NOT NULL'
+ ',rrc_serial INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Table of coins that originated from a refresh operation and that were recouped. Links the (fresh) coin to the melted operation (and thus the old coin). A recoup on a refreshed coin credits the old coin and debits the fresh coin.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Refreshed coin of a revoked denomination where the residual value is credited to the old coin. Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!'
+ ,'coin_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Used for garbage collection (in the absence of foreign constraints, in the future)'
+ ,'known_coin_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Link to the refresh operation. Also identifies the h_blind_ev of the recouped coin (as h_coin_ev).'
+ ,'rrc_serial'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Denomination blinding key used when creating the blinded coin from the planchet. Secret revealed during the recoup to provide the linkage between the coin and the refresh operation.'
+ ,'coin_blind'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_recoup_refresh(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup_refresh';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_rrc_serial_index'
+ ' ON ' || table_name || ' '
+ '(rrc_serial);'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_rrc_serial_index '
+ 'IS ' || quote_literal('used in exchange_do_melt for zombie coins (rare)') || ';'
+ );
+
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_coin_pub_index'
+ ' ON ' || table_name || ' '
+ '(coin_pub);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_recoup_refresh_uuid_key'
+ ' UNIQUE (recoup_refresh_uuid) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_recoup_refresh()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup_refresh';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (coin_pub) '
+ ' REFERENCES known_coins (coin_pub)'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_known_coin_id'
+ ' FOREIGN KEY (known_coin_id) '
+ ' REFERENCES known_coins (known_coin_id) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_rrc_serial'
+ ' FOREIGN KEY (rrc_serial) '
+ ' REFERENCES refresh_revealed_coins (rrc_serial) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION recoup_refresh_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.coin_pub
+ ,'recoup_refresh::NEW'
+ ,NEW.recoup_refresh_uuid);
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ SELECT
+ melt.old_coin_pub
+ ,'recoup_refresh::OLD'
+ ,NEW.recoup_refresh_uuid
+ FROM refresh_revealed_coins rrc
+ JOIN refresh_commitments melt
+ USING (melt_serial_id)
+ WHERE rrc.rrc_serial = NEW.rrc_serial;
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION coin_deposits_insert_trigger()
+ IS 'Automatically generate coin history entry.';
+
+
+CREATE FUNCTION master_table_recoup_refresh()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER recoup_refresh_on_insert
+ AFTER INSERT
+ ON recoup_refresh
+ FOR EACH ROW EXECUTE FUNCTION recoup_refresh_insert_trigger();
+END $$;
+
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('recoup_refresh'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('recoup_refresh'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('recoup_refresh'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('recoup_refresh'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-refresh_commitments.sql b/src/exchangedb/0002-refresh_commitments.sql
new file mode 100644
index 000000000..e577f1e1c
--- /dev/null
+++ b/src/exchangedb/0002-refresh_commitments.sql
@@ -0,0 +1,166 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_refresh_commitments(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_commitments';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(melt_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64)'
+ ',old_coin_pub BYTEA NOT NULL'
+ ',old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64)'
+ ',amount_with_fee taler_amount NOT NULL'
+ ',noreveal_index INT4 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (rc)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Commitments made when melting coins and the gamma value chosen by the exchange.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The gamma value chosen by the exchange in the cut-and-choose protocol'
+ ,'noreveal_index'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Commitment made by the client, hash over the various client inputs in the cut-and-choose protocol'
+ ,'rc'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Coin being melted in the refresh process.'
+ ,'old_coin_pub'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_refresh_commitments(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_commitments';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ -- Note: index spans partitions, may need to be materialized.
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_old_coin_pub_index '
+ 'ON ' || table_name || ' '
+ '(old_coin_pub);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_melt_serial_id_key'
+ ' UNIQUE (melt_serial_id)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_refresh_commitments()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_commitments';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (old_coin_pub) '
+ ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION refresh_commitments_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.old_coin_pub
+ ,'refresh_commitments'
+ ,NEW.melt_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION refresh_commitments_insert_trigger()
+ IS 'Automatically generate coin history entry.';
+
+
+CREATE FUNCTION master_table_refresh_commitments()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER refresh_commitments_on_insert
+ AFTER INSERT
+ ON refresh_commitments
+ FOR EACH ROW EXECUTE FUNCTION refresh_commitments_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('refresh_commitments'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('refresh_commitments'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('refresh_commitments'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('refresh_commitments'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-refresh_revealed_coins.sql b/src/exchangedb/0002-refresh_revealed_coins.sql
new file mode 100644
index 000000000..ad65c9942
--- /dev/null
+++ b/src/exchangedb/0002-refresh_revealed_coins.sql
@@ -0,0 +1,169 @@
+--
+-- 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 FUNCTION create_table_refresh_revealed_coins(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_revealed_coins';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(rrc_serial BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',melt_serial_id INT8 NOT NULL'
+ ',freshcoin_index INT4 NOT NULL'
+ ',link_sig BYTEA NOT NULL CHECK(LENGTH(link_sig)=64)'
+ ',denominations_serial INT8 NOT NULL'
+ ',coin_ev BYTEA NOT NULL'
+ ',h_coin_ev BYTEA NOT NULL CHECK(LENGTH(h_coin_ev)=64)'
+ ',ev_sig BYTEA NOT NULL'
+ ',ewv BYTEA NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (melt_serial_id)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Revelations about the new coins that are to be created during a melting session.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'needed for exchange-auditor replication logic'
+ ,'rrc_serial'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the refresh commitment (rc) of the melt operation.'
+ ,'melt_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'index of the fresh coin being created (one melt operation may result in multiple fresh coins)'
+ ,'freshcoin_index'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature of type WALLET_COIN_LINK, proves exchange did not tamper with the link data'
+ ,'link_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'envelope of the new coin to be signed'
+ ,'coin_ev'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'exchange contributed values in the creation of the fresh coin (see /csr)'
+ ,'ewv'
+ ,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_refresh_revealed_coins(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_revealed_coins';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_coins_by_melt_serial_id_index '
+ 'ON ' || table_name || ' '
+ '(melt_serial_id);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_rrc_serial_key'
+ ' UNIQUE (rrc_serial) '
+ ',ADD CONSTRAINT ' || table_name || '_coin_ev_key'
+ ' UNIQUE (coin_ev) '
+ ',ADD CONSTRAINT ' || table_name || '_h_coin_ev_key'
+ ' UNIQUE (h_coin_ev) '
+ ',ADD PRIMARY KEY (melt_serial_id, freshcoin_index)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_refresh_revealed_coins()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_revealed_coins';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_melt'
+ ' FOREIGN KEY (melt_serial_id)'
+ ' REFERENCES refresh_commitments (melt_serial_id) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_denom'
+ ' FOREIGN KEY (denominations_serial)'
+ ' REFERENCES denominations (denominations_serial) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('refresh_revealed_coins'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('refresh_revealed_coins'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('refresh_revealed_coins'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-refresh_transfer_keys.sql b/src/exchangedb/0002-refresh_transfer_keys.sql
new file mode 100644
index 000000000..9bcb912da
--- /dev/null
+++ b/src/exchangedb/0002-refresh_transfer_keys.sql
@@ -0,0 +1,127 @@
+--
+-- 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 FUNCTION create_table_refresh_transfer_keys(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_transfer_keys';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(rtc_serial BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',melt_serial_id INT8 PRIMARY KEY'
+ ',transfer_pub BYTEA NOT NULL CHECK(LENGTH(transfer_pub)=32)'
+ ',transfer_privs BYTEA NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (melt_serial_id)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Transfer keys of a refresh operation (the data revealed to the exchange).'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'needed for exchange-auditor replication logic'
+ ,'rtc_serial'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the refresh commitment (rc) of the operation.'
+ ,'melt_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'transfer public key for the gamma index'
+ ,'transfer_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'array of TALER_CNC_KAPPA-1 transfer private keys that have been revealed, with the gamma entry being skipped'
+ ,'transfer_privs'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_refresh_transfer_keys(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_transfer_keys';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_rtc_serial_key'
+ ' UNIQUE (rtc_serial)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_refresh_transfer_keys()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_transfer_keys';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || 'foreign_melt_serial_id'
+ ' FOREIGN KEY (melt_serial_id)'
+ ' REFERENCES refresh_commitments (melt_serial_id) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('refresh_transfer_keys'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('refresh_transfer_keys'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('refresh_transfer_keys'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-refunds.sql b/src/exchangedb/0002-refunds.sql
new file mode 100644
index 000000000..2a40bc192
--- /dev/null
+++ b/src/exchangedb/0002-refunds.sql
@@ -0,0 +1,162 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_refunds(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refunds';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(refund_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+ ',batch_deposit_serial_id INT8 NOT NULL'
+ ',merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64)'
+ ',rtransaction_id INT8 NOT NULL'
+ ',amount_with_fee taler_amount NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Data on coins that were refunded. Technically, refunds always apply against specific deposit operations involving a coin. The combination of coin_pub, merchant_pub, h_contract_terms and rtransaction_id MUST be unique, and we usually select by coin_pub so that one goes first.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies ONLY the merchant_pub, h_contract_terms and coin_pub. Multiple deposits may match a refund, this only identifies one of them.'
+ ,'batch_deposit_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'used by the merchant to make refunds unique in case the same coin for the same deposit gets a subsequent (higher) refund'
+ ,'rtransaction_id'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_refunds (
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refunds';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_coin_pub_index '
+ 'ON ' || table_name || ' '
+ '(coin_pub);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_refund_serial_id_key'
+ ' UNIQUE (refund_serial_id) '
+ ',ADD PRIMARY KEY (batch_deposit_serial_id, rtransaction_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_refunds ()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refunds';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (coin_pub) '
+ ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_deposit'
+ ' FOREIGN KEY (batch_deposit_serial_id) '
+ ' REFERENCES batch_deposits (batch_deposit_serial_id) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION refunds_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.coin_pub
+ ,'refunds'
+ ,NEW.refund_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION refunds_insert_trigger()
+ IS 'Automatically generate coin history entry.';
+
+
+CREATE FUNCTION master_table_refunds()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER refunds_on_insert
+ AFTER INSERT
+ ON refunds
+ FOR EACH ROW EXECUTE FUNCTION refunds_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('refunds'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('refunds'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('refunds'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('refunds'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-reserve_history.sql b/src/exchangedb/0002-reserve_history.sql
new file mode 100644
index 000000000..b0c764306
--- /dev/null
+++ b/src/exchangedb/0002-reserve_history.sql
@@ -0,0 +1,138 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_reserve_history (
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'reserve_history';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(reserve_history_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)'
+ ',table_name TEXT NOT NULL'
+ ',serial_id INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Links to tables with entries that affected the transaction history of a reserve.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'For which reserve is this a history entry'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'In which table is the history entry'
+ ,'table_name'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Which is the generated serial ID of the entry in the table'
+ ,'serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Monotonic counter, used to generate Etags for caching'
+ ,'reserve_history_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserve_history(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'reserve_history';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_reserve_history_serial_id_pkey'
+ ' PRIMARY KEY (reserve_history_serial_id) '
+ ',ADD CONSTRAINT ' || table_name || '_reserve_entry_key'
+ ' UNIQUE (reserve_pub, table_name, serial_id)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_reserve_by_time'
+ ' ON ' || table_name || ' '
+ '(reserve_pub'
+ ',reserve_history_serial_id DESC'
+ ');'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_reserve_history()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'reserve_history';
+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'
+ );
+END
+$$;
+
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserve_history'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserve_history'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('reserve_history'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE)
+ ;
diff --git a/src/exchangedb/0002-reserves.sql b/src/exchangedb/0002-reserves.sql
new file mode 100644
index 000000000..d710dd01b
--- /dev/null
+++ b/src/exchangedb/0002-reserves.sql
@@ -0,0 +1,152 @@
+--
+-- 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 FUNCTION create_table_reserves(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'reserves';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(reserve_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA PRIMARY KEY CHECK(LENGTH(reserve_pub)=32)'
+ ',current_balance taler_amount NOT NULL DEFAULT (0, 0)'
+ ',purses_active INT8 NOT NULL DEFAULT(0)'
+ ',purses_allowed INT8 NOT NULL DEFAULT(0)'
+ ',birthday INT4 NOT NULL DEFAULT(0)'
+ ',expiration_date INT8 NOT NULL'
+ ',gc_date INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Summarizes the balance of a reserve. Updated when new funds are added or withdrawn.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'EdDSA public key of the reserve. Knowledge of the private key implies ownership over the balance.'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Current balance remaining with the reserve.'
+ ,'current_balance'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Number of purses that were created by this reserve that are not expired and not fully paid.'
+ ,'purses_active'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Number of purses that this reserve is allowed to have active at most.'
+ ,'purses_allowed'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Used to trigger closing of reserves that have not been drained after some time'
+ ,'expiration_date'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Used to forget all information about a reserve during garbage collection'
+ ,'gc_date'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Birthday of the user in days after 1970, or 0 if user is an adult and is not subject to age restrictions'
+ ,'birthday'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserves(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'reserves';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_unique_uuid'
+ ' UNIQUE (reserve_uuid)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_expiration_index '
+ 'ON ' || table_name || ' '
+ '(expiration_date'
+ ',current_balance'
+ ');'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_expiration_index '
+ 'IS ' || quote_literal('used in get_expired_reserves') || ';'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_reserve_uuid_index '
+ 'ON ' || table_name || ' '
+ '(reserve_uuid);'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_gc_date_index '
+ 'ON ' || table_name || ' '
+ '(gc_date);'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_gc_date_index '
+ 'IS ' || quote_literal('for reserve garbage collection') || ';'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserves'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserves'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-reserves_close.sql b/src/exchangedb/0002-reserves_close.sql
new file mode 100644
index 000000000..16669768d
--- /dev/null
+++ b/src/exchangedb/0002-reserves_close.sql
@@ -0,0 +1,151 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_reserves_close(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_close';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(close_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA NOT NULL'
+ ',execution_date INT8 NOT NULL'
+ ',wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32)'
+ ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
+ ',amount taler_amount NOT NULL'
+ ',closing_fee taler_amount NOT NULL'
+ ',close_request_row INT8 NOT NULL DEFAULT(0)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'wire transfers executed by the reserve to close reserves'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the credited bank account (and KYC status). Note that closing does not depend on KYC.'
+ ,'wire_target_h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserves_close(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_close';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_close_uuid_pkey'
+ ' PRIMARY KEY (close_uuid)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_reserve_pub_index '
+ 'ON ' || table_name || ' (reserve_pub);'
+ );
+END $$;
+
+
+CREATE FUNCTION foreign_table_reserves_close()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_close';
+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'
+ );
+END $$;
+
+
+CREATE OR REPLACE FUNCTION reserves_close_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.reserve_pub
+ ,'reserves_close'
+ ,NEW.close_uuid);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION reserves_close_insert_trigger()
+ IS 'Automatically generate reserve history entry.';
+
+
+CREATE FUNCTION master_table_reserves_close()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER reserves_close_on_insert
+ AFTER INSERT
+ ON reserves_close
+ FOR EACH ROW EXECUTE FUNCTION reserves_close_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserves_close'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserves_close'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('reserves_close'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('reserves_close'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-reserves_in.sql b/src/exchangedb/0002-reserves_in.sql
new file mode 100644
index 000000000..197a815b3
--- /dev/null
+++ b/src/exchangedb/0002-reserves_in.sql
@@ -0,0 +1,175 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_reserves_in(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_in';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(reserve_in_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA PRIMARY KEY'
+ ',wire_reference INT8 NOT NULL'
+ ',credit taler_amount NOT NULL'
+ ',wire_source_h_payto BYTEA CHECK (LENGTH(wire_source_h_payto)=32)'
+ ',exchange_account_section TEXT NOT NULL'
+ ',execution_date INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'list of transfers of funds into the reserves, one per incoming wire transfer'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the debited bank account and KYC status'
+ ,'wire_source_h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the reserve. Private key signifies ownership of the remaining balance.'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Amount that was transferred into the reserve'
+ ,'credit'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+
+CREATE FUNCTION constrain_table_reserves_in(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_in';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_reserve_in_serial_id_key'
+ ' UNIQUE (reserve_in_serial_id)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_reserve_in_serial_id_index '
+ 'ON ' || table_name || ' '
+ '(reserve_in_serial_id);'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_exch_accnt_reserve_in_serial_id_idx '
+ 'ON ' || table_name || ' '
+ '(exchange_account_section'
+ ',reserve_in_serial_id ASC'
+ ');'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_exch_accnt_reserve_in_serial_id_idx '
+ 'IS ' || quote_literal ('for pg_select_reserves_in_above_serial_id_by_account') || ';'
+ );
+
+END
+$$;
+
+CREATE FUNCTION foreign_table_reserves_in()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'reserves_in';
+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'
+ );
+END $$;
+
+
+
+CREATE OR REPLACE FUNCTION reserves_in_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.reserve_pub
+ ,'reserves_in'
+ ,NEW.reserve_in_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION reserves_in_insert_trigger()
+ IS 'Automatically generate reserve history entry.';
+
+
+CREATE FUNCTION master_table_reserves_in()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER reserves_in_on_insert
+ AFTER INSERT
+ ON reserves_in
+ FOR EACH ROW EXECUTE FUNCTION reserves_in_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserves_in'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserves_in'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('reserves_in'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('reserves_in'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-reserves_open_deposits.sql b/src/exchangedb/0002-reserves_open_deposits.sql
new file mode 100644
index 000000000..776859df8
--- /dev/null
+++ b/src/exchangedb/0002-reserves_open_deposits.sql
@@ -0,0 +1,135 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_reserves_open_deposits(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_open_deposits';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(reserve_open_deposit_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)'
+ ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+ ',coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)'
+ ',contribution taler_amount NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'coin contributions paying for a reserve to remain open'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the specific reserve being paid for (possibly together with reserve_sig).'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserves_open_deposits(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_open_deposits';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name || ' '
+ 'ADD CONSTRAINT ' || table_name || '_coin_unique '
+ 'PRIMARY KEY (coin_pub,coin_sig)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_uuid '
+ 'ON ' || table_name || ' '
+ '(reserve_open_deposit_uuid);'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_reserve '
+ 'ON ' || table_name || ' '
+ '(reserve_pub);'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION reserves_open_deposits_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.coin_pub
+ ,'reserves_open_deposits'
+ ,NEW.reserve_open_deposit_uuid);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION reserves_open_deposits_insert_trigger()
+ IS 'Automatically generate coin history entry.';
+
+
+CREATE FUNCTION master_table_reserves_open_deposits()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER reserves_open_deposits_on_insert
+ AFTER INSERT
+ ON reserves_open_deposits
+ FOR EACH ROW EXECUTE FUNCTION reserves_open_deposits_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserves_open_deposits'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserves_open_deposits'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('reserves_open_deposits'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-reserves_open_requests.sql b/src/exchangedb/0002-reserves_open_requests.sql
new file mode 100644
index 000000000..b51168dc0
--- /dev/null
+++ b/src/exchangedb/0002-reserves_open_requests.sql
@@ -0,0 +1,150 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_reserves_open_requests(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_open_requests';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(open_request_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA NOT NULL'
+ ',request_timestamp INT8 NOT NULL'
+ ',expiration_date INT8 NOT NULL'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',reserve_payment taler_amount NOT NULL'
+ ',requested_purse_limit INT4 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table (
+ 'requests to keep a reserve open'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column (
+ 'Fee to pay for the request from the reserve balance itself.'
+ ,'reserve_payment'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserves_open_requests(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_open_requests';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_by_uuid'
+ ' PRIMARY KEY (open_request_uuid)'
+ ',ADD CONSTRAINT ' || table_name || '_by_time'
+ ' UNIQUE (reserve_pub,request_timestamp)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_reserves_open_requests()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_open_requests';
+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'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION reserves_open_requests_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.reserve_pub
+ ,'reserves_open_requests'
+ ,NEW.open_request_uuid);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION reserves_open_requests_insert_trigger()
+ IS 'Automatically generate reserve history entry.';
+
+
+CREATE FUNCTION master_table_reserves_open_requests()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER reserves_open_requests_on_insert
+ AFTER INSERT
+ ON reserves_open_requests
+ FOR EACH ROW EXECUTE FUNCTION reserves_open_requests_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserves_open_requests'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserves_open_requests'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('reserves_open_requests'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('reserves_open_requests'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-reserves_out.sql b/src/exchangedb/0002-reserves_out.sql
new file mode 100644
index 000000000..f0965d222
--- /dev/null
+++ b/src/exchangedb/0002-reserves_out.sql
@@ -0,0 +1,173 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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 FUNCTION create_table_reserves_out(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_out';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(reserve_out_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_blind_ev BYTEA CHECK (LENGTH(h_blind_ev)=64) UNIQUE'
+ ',denominations_serial INT8 NOT NULL'
+ ',denom_sig BYTEA NOT NULL'
+ ',reserve_uuid INT8 NOT NULL'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',execution_date INT8 NOT NULL'
+ ',amount_with_fee taler_amount NOT NULL'
+ ') %s ;'
+ ,'reserves_out'
+ ,'PARTITION BY HASH (h_blind_ev)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table (
+ 'Withdraw operations performed on reserves.'
+ ,'reserves_out'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column (
+ 'Hash of the blinded coin, used as primary key here so that broken clients that use a non-random coin or blinding factor fail to withdraw (otherwise they would fail on deposit when the coin is not unique there).'
+ ,'h_blind_ev'
+ ,'reserves_out'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column (
+ 'We do not CASCADE ON DELETE for the foreign constrain here, as we may keep the denomination data alive'
+ ,'denominations_serial'
+ ,'reserves_out'
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserves_out(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_out';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_reserve_out_serial_id_key'
+ ' UNIQUE (reserve_out_serial_id)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_reserve_uuid_and_execution_date_index '
+ 'ON ' || table_name || ' '
+ '(reserve_uuid, execution_date);'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_reserve_uuid_and_execution_date_index '
+ 'IS ' || quote_literal('for do_gc, do_recoup_by_reserve, select_kyc_relevant_withdraw_events and a few others') || ';'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_reserves_out()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_out';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_denom'
+ ' FOREIGN KEY (denominations_serial)'
+ ' REFERENCES denominations (denominations_serial)'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_reserve '
+ ' FOREIGN KEY (reserve_uuid)'
+ ' REFERENCES reserves (reserve_uuid) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE FUNCTION reserves_out_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ SELECT
+ res.reserve_pub
+ ,'reserves_out'
+ ,NEW.reserve_out_serial_id
+ FROM
+ reserves res
+ WHERE res.reserve_uuid = NEW.reserve_uuid;
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION reserves_out_insert_trigger()
+ IS 'Replicate reserve_out inserts into reserve_history table.';
+
+
+CREATE FUNCTION master_table_reserves_out()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER reserves_out_on_insert
+ AFTER INSERT
+ ON reserves_out
+ FOR EACH ROW EXECUTE FUNCTION reserves_out_insert_trigger();
+END $$;
+COMMENT ON FUNCTION master_table_reserves_out()
+ IS 'Setup triggers to replicate reserve_out into reserve_history.';
+
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserves_out'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserves_out'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('reserves_out'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('reserves_out'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-revolving_work_shards.sql b/src/exchangedb/0002-revolving_work_shards.sql
new file mode 100644
index 000000000..8cfff09b4
--- /dev/null
+++ b/src/exchangedb/0002-revolving_work_shards.sql
@@ -0,0 +1,46 @@
+--
+-- 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 UNLOGGED TABLE revolving_work_shards
+ (shard_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,last_attempt INT8 NOT NULL
+ ,start_row INT4 NOT NULL
+ ,end_row INT4 NOT NULL
+ ,active BOOLEAN NOT NULL DEFAULT FALSE
+ ,job_name TEXT NOT NULL
+ ,PRIMARY KEY (job_name, start_row)
+ );
+COMMENT ON TABLE revolving_work_shards
+ IS 'coordinates work between multiple processes working on the same job with partitions that need to be repeatedly processed; unlogged because on system crashes the locks represented by this table will have to be cleared anyway, typically using "taler-exchange-dbinit -s"';
+COMMENT ON COLUMN revolving_work_shards.shard_serial_id
+ IS 'unique serial number identifying the shard';
+COMMENT ON COLUMN revolving_work_shards.last_attempt
+ IS 'last time a worker attempted to work on the shard';
+COMMENT ON COLUMN revolving_work_shards.active
+ IS 'set to TRUE when a worker is active on the shard';
+COMMENT ON COLUMN revolving_work_shards.start_row
+ IS 'row at which the shard scope starts, inclusive';
+COMMENT ON COLUMN revolving_work_shards.end_row
+ IS 'row at which the shard scope ends, exclusive';
+COMMENT ON COLUMN revolving_work_shards.job_name
+ IS 'unique name of the job the workers on this shard are performing';
+
+CREATE INDEX revolving_work_shards_by_job_name_active_last_attempt_index
+ ON revolving_work_shards
+ (job_name
+ ,active
+ ,last_attempt
+ );
diff --git a/src/exchangedb/0002-signkey_revocations.sql b/src/exchangedb/0002-signkey_revocations.sql
new file mode 100644
index 000000000..37ab32c67
--- /dev/null
+++ b/src/exchangedb/0002-signkey_revocations.sql
@@ -0,0 +1,23 @@
+--
+-- 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 TABLE signkey_revocations
+ (signkey_revocations_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,esk_serial INT8 PRIMARY KEY REFERENCES exchange_sign_keys (esk_serial) ON DELETE CASCADE
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ );
+COMMENT ON TABLE signkey_revocations
+ IS 'Table storing which online signing keys have been revoked';
diff --git a/src/exchangedb/0002-wad_in_entries.sql b/src/exchangedb/0002-wad_in_entries.sql
new file mode 100644
index 000000000..3ef1f1b8e
--- /dev/null
+++ b/src/exchangedb/0002-wad_in_entries.sql
@@ -0,0 +1,175 @@
+--
+-- 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 FUNCTION create_table_wad_in_entries(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wad_in_entries';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(wad_in_entry_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',wad_in_serial_id INT8'
+ ',reserve_pub BYTEA NOT NULL CHECK(LENGTH(reserve_pub)=32)'
+ ',purse_pub BYTEA PRIMARY KEY CHECK(LENGTH(purse_pub)=32)'
+ ',h_contract BYTEA NOT NULL CHECK(LENGTH(h_contract)=64)'
+ ',purse_expiration INT8 NOT NULL'
+ ',merge_timestamp INT8 NOT NULL'
+ ',amount_with_fee taler_amount NOT NULL'
+ ',wad_fee taler_amount NOT NULL'
+ ',deposit_fees taler_amount NOT NULL'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',purse_sig BYTEA NOT NULL CHECK (LENGTH(purse_sig)=64)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'list of purses aggregated in a wad according to the sending exchange'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'wad for which the given purse was included in the aggregation'
+ ,'wad_in_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'target account of the purse (must be at the local exchange)'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public key of the purse that was merged'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'hash of the contract terms of the purse'
+ ,'h_contract'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Time when the purse was set to expire'
+ ,'purse_expiration'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Time when the merge was approved'
+ ,'merge_timestamp'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total amount in the purse'
+ ,'amount_with_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total wad fees paid by the purse'
+ ,'wad_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total deposit fees paid when depositing coins into the purse'
+ ,'deposit_fees'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature by the receiving reserve, of purpose TALER_SIGNATURE_ACCOUNT_MERGE'
+ ,'reserve_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature by the purse of purpose TALER_SIGNATURE_PURSE_MERGE'
+ ,'purse_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+
+CREATE FUNCTION constrain_table_wad_in_entries(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wad_in_entries';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_wad_in_entry_serial_id_key'
+ ' UNIQUE (wad_in_entry_serial_id) '
+ );
+END $$;
+
+
+CREATE FUNCTION foreign_table_wad_in_entries()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wad_in_entries';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_wad_in'
+ ' FOREIGN KEY(wad_in_serial_id)'
+ ' REFERENCES wads_in (wad_in_serial_id) ON DELETE CASCADE'
+ );
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('wad_in_entries'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('wad_in_entries'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('wad_in_entries'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-wad_out_entries.sql b/src/exchangedb/0002-wad_out_entries.sql
new file mode 100644
index 000000000..de921637b
--- /dev/null
+++ b/src/exchangedb/0002-wad_out_entries.sql
@@ -0,0 +1,179 @@
+--
+-- 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 FUNCTION create_table_wad_out_entries(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wad_out_entries';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(wad_out_entry_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',wad_out_serial_id INT8'
+ ',reserve_pub BYTEA NOT NULL CHECK(LENGTH(reserve_pub)=32)'
+ ',purse_pub BYTEA PRIMARY KEY CHECK(LENGTH(purse_pub)=32)'
+ ',h_contract BYTEA NOT NULL CHECK(LENGTH(h_contract)=64)'
+ ',purse_expiration INT8 NOT NULL'
+ ',merge_timestamp INT8 NOT NULL'
+ ',amount_with_fee taler_amount NOT NULL'
+ ',wad_fee taler_amount NOT NULL'
+ ',deposit_fees taler_amount NOT NULL'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',purse_sig BYTEA NOT NULL CHECK (LENGTH(purse_sig)=64)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Purses combined into a wad'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Wad the purse was part of'
+ ,'wad_out_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Target reserve for the purse'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Hash of the contract associated with the purse'
+ ,'h_contract'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Time when the purse expires'
+ ,'purse_expiration'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Time when the merge was approved'
+ ,'merge_timestamp'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total amount in the purse'
+ ,'amount_with_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Wad fee charged to the purse'
+ ,'wad_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total deposit fees charged to the purse'
+ ,'deposit_fees'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature by the receiving reserve, of purpose TALER_SIGNATURE_ACCOUNT_MERGE'
+ ,'reserve_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature by the purse of purpose TALER_SIGNATURE_PURSE_MERGE'
+ ,'purse_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_wad_out_entries(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wad_out_entries';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_wad_out_entry_serial_id_key'
+ ' UNIQUE (wad_out_entry_serial_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_wad_out_entries()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wad_out_entries';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_wad_out'
+ ' FOREIGN KEY(wad_out_serial_id)'
+ ' REFERENCES wads_out (wad_out_serial_id) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('wad_out_entries'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('wad_out_entries'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('wad_out_entries'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-wads_in.sql b/src/exchangedb/0002-wads_in.sql
new file mode 100644
index 000000000..479589ba4
--- /dev/null
+++ b/src/exchangedb/0002-wads_in.sql
@@ -0,0 +1,107 @@
+--
+-- 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 FUNCTION create_table_wads_in(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wads_in';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(wad_in_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',wad_id BYTEA PRIMARY KEY CHECK (LENGTH(wad_id)=24)'
+ ',origin_exchange_url TEXT NOT NULL'
+ ',amount taler_amount NOT NULL'
+ ',arrival_time INT8 NOT NULL'
+ ',UNIQUE (wad_id, origin_exchange_url)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (wad_id)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Incoming exchange-to-exchange wad wire transfers'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Unique identifier of the wad, part of the wire transfer subject'
+ ,'wad_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Base URL of the originating URL, also part of the wire transfer subject'
+ ,'origin_exchange_url'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Actual amount that was received by our exchange'
+ ,'amount'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Time when the wad was received'
+ ,'arrival_time'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+
+CREATE FUNCTION constrain_table_wads_in(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wads_in';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_wad_in_serial_id_key'
+ ' UNIQUE (wad_in_serial_id) '
+ ',ADD CONSTRAINT ' || table_name || '_wad_is_origin_exchange_url_key'
+ ' UNIQUE (wad_id, origin_exchange_url) '
+ );
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('wads_in'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('wads_in'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-wads_out.sql b/src/exchangedb/0002-wads_out.sql
new file mode 100644
index 000000000..e52010e96
--- /dev/null
+++ b/src/exchangedb/0002-wads_out.sql
@@ -0,0 +1,128 @@
+--
+-- 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 FUNCTION create_table_wads_out(
+ IN shard_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wads_out';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(wad_out_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',wad_id BYTEA PRIMARY KEY CHECK (LENGTH(wad_id)=24)'
+ ',partner_serial_id INT8 NOT NULL'
+ ',amount taler_amount NOT NULL'
+ ',execution_time INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (wad_id)'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Wire transfers made to another exchange to transfer purse funds'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Unique identifier of the wad, part of the wire transfer subject'
+ ,'wad_id'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'target exchange of the wad'
+ ,'partner_serial_id'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Amount that was wired'
+ ,'amount'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Time when the wire transfer was scheduled'
+ ,'execution_time'
+ ,table_name
+ ,shard_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_wads_out(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wads_out';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_wad_out_serial_id_key'
+ ' UNIQUE (wad_out_serial_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_wads_out()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wads_out';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_partner'
+ ' FOREIGN KEY(partner_serial_id)'
+ ' REFERENCES partners(partner_serial_id) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('wads_out'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('wads_out'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('wads_out'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-wire_accounts.sql b/src/exchangedb/0002-wire_accounts.sql
new file mode 100644
index 000000000..dba522d7b
--- /dev/null
+++ b/src/exchangedb/0002-wire_accounts.sql
@@ -0,0 +1,45 @@
+--
+-- 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 TABLE wire_accounts
+ (payto_uri TEXT PRIMARY KEY
+ ,master_sig BYTEA CHECK (LENGTH(master_sig)=64)
+ ,is_active BOOLEAN NOT NULL
+ ,last_change INT8 NOT NULL
+ ,conversion_url TEXT DEFAULT (NULL)
+ ,debit_restrictions TEXT DEFAULT (NULL)
+ ,credit_restrictions TEXT DEFAULT (NULL)
+ );
+COMMENT ON TABLE wire_accounts
+ IS 'Table with current and historic bank accounts of the exchange. Entries never expire as we need to remember the last_change column indefinitely.';
+COMMENT ON COLUMN wire_accounts.payto_uri
+ IS 'payto URI (RFC 8905) with the bank account of the exchange.';
+COMMENT ON COLUMN wire_accounts.master_sig
+ IS 'Signature of purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS';
+COMMENT ON COLUMN wire_accounts.is_active
+ IS 'true if we are currently supporting the use of this account.';
+COMMENT ON COLUMN wire_accounts.last_change
+ IS 'Latest time when active status changed. Used to detect replays of old messages.';
+COMMENT ON COLUMN wire_accounts.conversion_url
+ IS 'URL of a currency conversion service if conversion is needed when this account is used; NULL if there is no conversion.';
+COMMENT ON COLUMN wire_accounts.debit_restrictions
+ IS 'JSON array describing restrictions imposed when debiting this account. Empty for no restrictions, NULL if account was migrated from previous database revision or account is disabled.';
+COMMENT ON COLUMN wire_accounts.credit_restrictions
+ IS 'JSON array describing restrictions imposed when crediting this account. Empty for no restrictions, NULL if account was migrated from previous database revision or account is disabled.';
+
+
+-- "wire_accounts" has no sequence because it is a 'mutable' table
+-- and is of no concern to the auditor
diff --git a/src/exchangedb/0002-wire_fee.sql b/src/exchangedb/0002-wire_fee.sql
new file mode 100644
index 000000000..12cb91b98
--- /dev/null
+++ b/src/exchangedb/0002-wire_fee.sql
@@ -0,0 +1,34 @@
+--
+-- 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 TABLE wire_fee
+ (wire_fee_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,wire_method TEXT NOT NULL
+ ,start_date INT8 NOT NULL
+ ,end_date INT8 NOT NULL
+ ,wire_fee taler_amount NOT NULL
+ ,closing_fee taler_amount NOT NULL
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ ,PRIMARY KEY (wire_method, start_date)
+ );
+COMMENT ON TABLE wire_fee
+ IS 'list of the wire fees of this exchange, by date';
+COMMENT ON COLUMN wire_fee.wire_fee_serial
+ IS 'needed for exchange-auditor replication logic';
+
+CREATE INDEX wire_fee_by_end_date_index
+ ON wire_fee
+ (end_date);
diff --git a/src/exchangedb/0002-wire_out.sql b/src/exchangedb/0002-wire_out.sql
new file mode 100644
index 000000000..c0f471b56
--- /dev/null
+++ b/src/exchangedb/0002-wire_out.sql
@@ -0,0 +1,130 @@
+--
+-- 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 FUNCTION create_table_wire_out(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wire_out';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I'
+ '(wireout_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',execution_date INT8 NOT NULL'
+ ',wtid_raw BYTEA UNIQUE NOT NULL CHECK (LENGTH(wtid_raw)=32)'
+ ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
+ ',exchange_account_section TEXT NOT NULL'
+ ',amount taler_amount NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (wtid_raw)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'wire transfers the exchange has executed'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'identifies the configuration section with the debit account of this payment'
+ ,'exchange_account_section'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the credited bank account and KYC status'
+ ,'wire_target_h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_wire_out(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wire_out';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_wire_target_h_payto_index '
+ 'ON ' || table_name || ' '
+ '(wire_target_h_payto);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_wireout_uuid_pkey'
+ ' PRIMARY KEY (wireout_uuid)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION wire_out_delete_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ DELETE FROM exchange.aggregation_tracking
+ WHERE wtid_raw = OLD.wtid_raw;
+ RETURN OLD;
+END $$;
+COMMENT ON FUNCTION wire_out_delete_trigger()
+ IS 'Replicate reserve_out deletions into aggregation_tracking. This replaces an earlier use of an ON DELETE CASCADE that required a DEFERRABLE constraint and conflicted with nice partitioning.';
+
+
+CREATE FUNCTION master_table_wire_out()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER wire_out_on_delete
+ AFTER DELETE
+ ON wire_out
+ FOR EACH ROW EXECUTE FUNCTION wire_out_delete_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('wire_out'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('wire_out'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('wire_out'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-wire_targets.sql b/src/exchangedb/0002-wire_targets.sql
new file mode 100644
index 000000000..88d67d9a5
--- /dev/null
+++ b/src/exchangedb/0002-wire_targets.sql
@@ -0,0 +1,89 @@
+--
+-- 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 FUNCTION create_table_wire_targets(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(wire_target_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',wire_target_h_payto BYTEA PRIMARY KEY CHECK (LENGTH(wire_target_h_payto)=32)'
+ ',payto_uri TEXT NOT NULL'
+ ') %s ;'
+ ,'wire_targets'
+ ,'PARTITION BY HASH (wire_target_h_payto)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'All senders and recipients of money via the exchange'
+ ,'wire_targets'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Can be a regular bank account, or also be a URI identifying a reserve-account (for P2P payments)'
+ ,'payto_uri'
+ ,'wire_targets'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Unsalted hash of payto_uri'
+ ,'wire_target_h_payto'
+ ,'wire_targets'
+ ,partition_suffix
+ );
+END $$;
+
+
+CREATE FUNCTION constrain_table_wire_targets(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wire_targets';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_wire_target_serial_id_key'
+ ' UNIQUE (wire_target_serial_id)'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('wire_targets'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('wire_targets'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-work_shards.sql b/src/exchangedb/0002-work_shards.sql
new file mode 100644
index 000000000..6347d42c5
--- /dev/null
+++ b/src/exchangedb/0002-work_shards.sql
@@ -0,0 +1,56 @@
+--
+-- 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 TABLE work_shards
+ (shard_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,last_attempt INT8 NOT NULL
+ ,start_row INT8 NOT NULL
+ ,end_row INT8 NOT NULL
+ ,completed BOOLEAN NOT NULL DEFAULT FALSE
+ ,job_name TEXT NOT NULL
+ ,PRIMARY KEY (job_name, start_row)
+ );
+COMMENT ON TABLE work_shards
+ IS 'coordinates work between multiple processes working on the same job';
+COMMENT ON COLUMN work_shards.shard_serial_id
+ IS 'unique serial number identifying the shard';
+COMMENT ON COLUMN work_shards.last_attempt
+ IS 'last time a worker attempted to work on the shard';
+COMMENT ON COLUMN work_shards.completed
+ IS 'set to TRUE once the shard is finished by a worker';
+COMMENT ON COLUMN work_shards.start_row
+ IS 'row at which the shard scope starts, inclusive';
+COMMENT ON COLUMN work_shards.end_row
+ IS 'row at which the shard scope ends, exclusive';
+COMMENT ON COLUMN work_shards.job_name
+ IS 'unique name of the job the workers on this shard are performing';
+
+CREATE INDEX work_shards_by_job_name_completed_last_attempt_index
+ ON work_shards
+ (job_name
+ ,completed
+ ,last_attempt ASC
+ );
+
+CREATE INDEX work_shards_by_end_row_index
+ ON work_shards
+ (end_row DESC);
+
+CREATE INDEX work_shards_by_rows
+ ON work_shards
+ (job_name
+ ,start_row
+ ,end_row);
diff --git a/src/exchangedb/0003-purse_deletion.sql b/src/exchangedb/0003-purse_deletion.sql
new file mode 100644
index 000000000..66a95ff03
--- /dev/null
+++ b/src/exchangedb/0003-purse_deletion.sql
@@ -0,0 +1,52 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 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/>
+--
+
+-- Adds a 'unique' constraint to the 'purse_pub'.
+-- This is not only semantically correct, but also
+-- creates a dramatic speed-up on the
+-- pg_select_purse query (which otherwise fails to
+-- use indices correctly).
+
+CREATE FUNCTION constrain_table_purse_decision3(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_decision';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_purse_decision_purse_pub'
+ ' UNIQUE (purse_pub) '
+ );
+END
+$$;
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_decision3'
+ ,'exchange-0003'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0003-wire_accounts.sql b/src/exchangedb/0003-wire_accounts.sql
new file mode 100644
index 000000000..51fc86c80
--- /dev/null
+++ b/src/exchangedb/0003-wire_accounts.sql
@@ -0,0 +1,25 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 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/>
+--
+
+-- new columns for #8000
+ALTER TABLE wire_accounts
+ ADD COLUMN priority INT8 NOT NULL DEFAULT (0),
+ ADD COLUMN bank_label TEXT DEFAULT (NULL);
+
+COMMENT ON COLUMN wire_accounts.priority
+ IS 'priority determines the order in which wallets should display wire accounts';
+COMMENT ON COLUMN wire_accounts.bank_label
+ IS 'label to show in the selector for this bank account in the wallet UI';
diff --git a/src/exchangedb/0004-refunds.sql b/src/exchangedb/0004-refunds.sql
new file mode 100644
index 000000000..eb9e7ad6e
--- /dev/null
+++ b/src/exchangedb/0004-refunds.sql
@@ -0,0 +1,35 @@
+
+CREATE FUNCTION constrain_table_refunds4 (
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refunds';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' DROP CONSTRAINT ' || table_name || '_pkey'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD PRIMARY KEY (batch_deposit_serial_id, coin_pub, rtransaction_id) '
+ );
+END
+$$;
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('refunds4'
+ ,'exchange-0004'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
index c3d0b4302..fd993f968 100644
--- a/src/exchangedb/Makefile.am
+++ b/src/exchangedb/Makefile.am
@@ -14,17 +14,71 @@ pkgcfg_DATA = \
sqldir = $(prefix)/share/taler/sql/exchange/
+sqlinputs = \
+ exchange_do_*.sql \
+ procedures.sql.in \
+ 0002-*.sql \
+ 0003-*.sql \
+ 0004-*.sql \
+ exchange-0002.sql.in \
+ exchange-0003.sql.in \
+ exchange-0004.sql.in
+
sql_DATA = \
- exchange-0000.sql \
+ benchmark-0001.sql \
+ versioning.sql \
+ auditor-triggers-0001.sql \
+ exchange-0001.sql \
+ exchange-0002.sql \
+ exchange-0003.sql \
+ exchange-0004.sql \
+ drop.sql \
+ procedures.sql
+
+BUILT_SOURCES = \
+ benchmark-0001.sql \
+ drop.sql \
exchange-0001.sql \
- drop0001.sql
+ procedures.sql
+
+CLEANFILES = \
+ exchange-0002.sql \
+ exchange-0003.sql \
+ procedures.sql
+
+procedures.sql: procedures.sql.in exchange_do_*.sql
+ chmod +w $@ 2> /dev/null || true
+ gcc -E -P -undef - < procedures.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
+ chmod ugo-w $@
+
+exchange-0002.sql: exchange-0002.sql.in 0002-*.sql
+ chmod +w $@ 2> /dev/null || true
+ gcc -E -P -undef - < exchange-0002.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
+ chmod ugo-w $@
+
+exchange-0003.sql: exchange-0003.sql.in 0003-*.sql
+ chmod +w $@ 2> /dev/null || true
+ gcc -E -P -undef - < exchange-0003.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
+ chmod ugo-w $@
+
+exchange-0004.sql: exchange-0004.sql.in 0004-*.sql
+ chmod +w $@ 2> /dev/null || true
+ gcc -E -P -undef - < exchange-0004.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
+ chmod ugo-w $@
+
+check_SCRIPTS = \
+ test_idempotency.sh
EXTRA_DIST = \
exchangedb.conf \
exchangedb-postgres.conf \
- plugin_exchangedb_common.c \
+ bench-db-postgres.conf \
test-exchange-db-postgres.conf \
- $(sql_DATA)
+ $(sqlinputs) \
+ $(sql_DATA) \
+ $(check_SCRIPTS) \
+ pg_template.h pg_template.c \
+ pg_template.sh
plugindir = $(libdir)/taler
@@ -34,32 +88,217 @@ plugin_LTLIBRARIES = \
endif
libtaler_plugin_exchangedb_postgres_la_SOURCES = \
- plugin_exchangedb_postgres.c
-libtaler_plugin_exchangedb_postgres_la_LIBADD = \
- $(LTLIBINTL)
+ plugin_exchangedb_common.c plugin_exchangedb_common.h \
+ pg_setup_wire_target.h pg_setup_wire_target.c \
+ pg_compute_shard.h pg_compute_shard.c \
+ plugin_exchangedb_postgres.c pg_helper.h \
+ pg_reserves_update.h pg_reserves_update.c \
+ pg_select_aggregation_amounts_for_kyc_check.h pg_select_aggregation_amounts_for_kyc_check.c \
+ pg_lookup_wire_fee_by_time.h pg_lookup_wire_fee_by_time.c \
+ pg_select_satisfied_kyc_processes.h pg_select_satisfied_kyc_processes.c \
+ pg_get_pending_kyc_requirement_process.h pg_get_pending_kyc_requirement_process.c \
+ pg_kyc_provider_account_lookup.h pg_kyc_provider_account_lookup.c \
+ pg_lookup_kyc_requirement_by_row.h pg_lookup_kyc_requirement_by_row.c \
+ pg_insert_kyc_requirement_for_account.h pg_insert_kyc_requirement_for_account.c \
+ pg_lookup_kyc_process_by_account.h pg_lookup_kyc_process_by_account.c \
+ pg_update_kyc_process_by_row.h pg_update_kyc_process_by_row.c \
+ pg_insert_kyc_requirement_process.h pg_insert_kyc_requirement_process.c \
+ pg_select_withdraw_amounts_for_kyc_check.h pg_select_withdraw_amounts_for_kyc_check.c \
+ pg_select_merge_amounts_for_kyc_check.h pg_select_merge_amounts_for_kyc_check.c \
+ pg_profit_drains_set_finished.h pg_profit_drains_set_finished.c \
+ pg_profit_drains_get_pending.h pg_profit_drains_get_pending.c \
+ 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 \
+ pg_insert_drain_profit.h pg_insert_drain_profit.c \
+ pg_insert_kyc_failure.h pg_insert_kyc_failure.c \
+ pg_inject_auditor_triggers.h pg_inject_auditor_triggers.c \
+ pg_create_tables.h pg_create_tables.c \
+ pg_event_listen.h pg_event_listen.c \
+ pg_event_listen_cancel.h pg_event_listen_cancel.c \
+ pg_event_notify.h pg_event_notify.c \
+ pg_get_denomination_info.h pg_get_denomination_info.c \
+ pg_iterate_denomination_info.h pg_iterate_denomination_info.c \
+ pg_iterate_denominations.h pg_iterate_denominations.c \
+ pg_iterate_active_auditors.h pg_iterate_active_auditors.c \
+ pg_iterate_auditor_denominations.h pg_iterate_auditor_denominations.c \
+ pg_reserves_get.h pg_reserves_get.c \
+ pg_reserves_get_origin.h pg_reserves_get_origin.c \
+ 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_do_age_withdraw.h pg_do_age_withdraw.c \
+ pg_get_age_withdraw.h pg_get_age_withdraw.c \
+ 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 \
+ pg_do_deposit.h pg_do_deposit.c \
+ pg_get_wire_hash_for_contract.h pg_get_wire_hash_for_contract.c \
+ pg_add_policy_fulfillment_proof.h pg_add_policy_fulfillment_proof.c \
+ pg_do_melt.h pg_do_melt.c \
+ pg_do_refund.h pg_do_refund.c \
+ pg_do_recoup.h pg_do_recoup.c \
+ pg_do_recoup_refresh.h pg_do_recoup_refresh.c \
+ pg_get_reserve_balance.h pg_get_reserve_balance.c \
+ pg_count_known_coins.h pg_count_known_coins.c \
+ pg_ensure_coin_known.h pg_ensure_coin_known.c \
+ pg_get_known_coin.h pg_get_known_coin.c \
+ pg_get_signature_for_known_coin.h pg_get_signature_for_known_coin.c \
+ pg_get_coin_denomination.h pg_get_coin_denomination.c \
+ pg_have_deposit2.h pg_have_deposit2.c \
+ pg_aggregate.h pg_aggregate.c \
+ pg_create_aggregation_transient.h pg_create_aggregation_transient.c \
+ pg_insert_kyc_attributes.h pg_insert_kyc_attributes.c \
+ pg_select_similar_kyc_attributes.h pg_select_similar_kyc_attributes.c \
+ pg_select_kyc_attributes.h pg_select_kyc_attributes.c \
+ pg_insert_aml_officer.h pg_insert_aml_officer.c \
+ pg_test_aml_officer.h pg_test_aml_officer.c \
+ pg_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 \
+ pg_select_aml_history.h pg_select_aml_history.c \
+ pg_insert_aml_decision.h pg_insert_aml_decision.c \
+ pg_select_aggregation_transient.h pg_select_aggregation_transient.c \
+ pg_find_aggregation_transient.h pg_find_aggregation_transient.c \
+ pg_update_aggregation_transient.h pg_update_aggregation_transient.c \
+ pg_get_ready_deposit.h pg_get_ready_deposit.c \
+ pg_insert_refund.h pg_insert_refund.c \
+ pg_select_refunds_by_coin.h pg_select_refunds_by_coin.c \
+ pg_get_melt.h pg_get_melt.c \
+ pg_insert_refresh_reveal.h pg_insert_refresh_reveal.c \
+ pg_get_refresh_reveal.h pg_get_refresh_reveal.c \
+ pg_lookup_wire_transfer.h pg_lookup_wire_transfer.c \
+ pg_lookup_transfer_by_deposit.h pg_lookup_transfer_by_deposit.c \
+ pg_insert_wire_fee.h pg_insert_wire_fee.c \
+ pg_insert_global_fee.h pg_insert_global_fee.c \
+ pg_get_wire_fee.h pg_get_wire_fee.c \
+ pg_get_global_fee.h pg_get_global_fee.c \
+ pg_get_global_fees.h pg_get_global_fees.c \
+ pg_insert_reserve_closed.h pg_insert_reserve_closed.c \
+ pg_wire_prepare_data_insert.h pg_wire_prepare_data_insert.c \
+ pg_wire_prepare_data_mark_finished.h pg_wire_prepare_data_mark_finished.c \
+ pg_wire_prepare_data_mark_failed.h pg_wire_prepare_data_mark_failed.c \
+ pg_wire_prepare_data_get.h pg_wire_prepare_data_get.c \
+ pg_start_deferred_wire_out.h pg_start_deferred_wire_out.c \
+ pg_store_wire_transfer_out.h pg_store_wire_transfer_out.c \
+ pg_gc.h pg_gc.c \
+ pg_select_coin_deposits_above_serial_id.h pg_select_coin_deposits_above_serial_id.c \
+ pg_select_purse_decisions_above_serial_id.h pg_select_purse_decisions_above_serial_id.c \
+ pg_select_purse_deposits_by_purse.h pg_select_purse_deposits_by_purse.c \
+ pg_select_refreshes_above_serial_id.h pg_select_refreshes_above_serial_id.c \
+ pg_select_refunds_above_serial_id.h pg_select_refunds_above_serial_id.c \
+ pg_select_reserves_in_above_serial_id.h pg_select_reserves_in_above_serial_id.c \
+ pg_select_reserves_in_above_serial_id_by_account.h pg_select_reserves_in_above_serial_id_by_account.c \
+ pg_select_withdrawals_above_serial_id.h pg_select_withdrawals_above_serial_id.c \
+ pg_select_wire_out_above_serial_id.h pg_select_wire_out_above_serial_id.c \
+ pg_select_wire_out_above_serial_id_by_account.h pg_select_wire_out_above_serial_id_by_account.c \
+ pg_select_recoup_above_serial_id.h pg_select_recoup_above_serial_id.c \
+ pg_select_recoup_refresh_above_serial_id.h pg_select_recoup_refresh_above_serial_id.c \
+ pg_get_reserve_by_h_blind.h pg_get_reserve_by_h_blind.c \
+ pg_get_old_coin_by_h_blind.h pg_get_old_coin_by_h_blind.c \
+ pg_insert_denomination_revocation.h pg_insert_denomination_revocation.c \
+ pg_get_denomination_revocation.h pg_get_denomination_revocation.c \
+ pg_select_batch_deposits_missing_wire.h pg_select_batch_deposits_missing_wire.c \
+ pg_select_justification_for_missing_wire.h pg_select_justification_for_missing_wire.c \
+ pg_select_aggregations_above_serial.h pg_select_aggregations_above_serial.c \
+ pg_lookup_auditor_timestamp.h pg_lookup_auditor_timestamp.c \
+ pg_lookup_auditor_status.h pg_lookup_auditor_status.c \
+ pg_insert_auditor.h pg_insert_auditor.c \
+ pg_lookup_wire_timestamp.h pg_lookup_wire_timestamp.c \
+ pg_insert_wire.h pg_insert_wire.c \
+ pg_update_wire.h pg_update_wire.c \
+ pg_get_wire_accounts.h pg_get_wire_accounts.c \
+ pg_get_wire_fees.h pg_get_wire_fees.c \
+ pg_insert_signkey_revocation.h pg_insert_signkey_revocation.c \
+ pg_lookup_signkey_revocation.h pg_lookup_signkey_revocation.c \
+ pg_lookup_denomination_key.h pg_lookup_denomination_key.c \
+ pg_insert_auditor_denom_sig.h pg_insert_auditor_denom_sig.c \
+ pg_select_auditor_denom_sig.h pg_select_auditor_denom_sig.c \
+ pg_add_denomination_key.h pg_add_denomination_key.c \
+ pg_lookup_signing_key.h pg_lookup_signing_key.c \
+ pg_begin_shard.h pg_begin_shard.c \
+ pg_abort_shard.h pg_abort_shard.c \
+ pg_complete_shard.h pg_complete_shard.c \
+ pg_release_revolving_shard.h pg_release_revolving_shard.c \
+ pg_delete_shard_locks.h pg_delete_shard_locks.c \
+ pg_set_extension_manifest.h pg_set_extension_manifest.c \
+ pg_insert_partner.h pg_insert_partner.c \
+ pg_expire_purse.h pg_expire_purse.c \
+ pg_select_purse_by_merge_pub.h pg_select_purse_by_merge_pub.c \
+ pg_set_purse_balance.h pg_set_purse_balance.c \
+ pg_do_reserve_purse.h pg_do_reserve_purse.c \
+ pg_lookup_global_fee_by_time.h pg_lookup_global_fee_by_time.c \
+ pg_do_purse_deposit.h pg_do_purse_deposit.c \
+ pg_activate_signing_key.h pg_activate_signing_key.c \
+ pg_update_auditor.h pg_update_auditor.c \
+ pg_begin_revolving_shard.h pg_begin_revolving_shard.c \
+ pg_get_extension_manifest.h pg_get_extension_manifest.c \
+ pg_do_purse_merge.h pg_do_purse_merge.c \
+ pg_start_read_committed.h pg_start_read_committed.c \
+ pg_start_read_only.h pg_start_read_only.c \
+ pg_insert_denomination_info.h pg_insert_denomination_info.c \
+ pg_do_batch_withdraw_insert.h pg_do_batch_withdraw_insert.c \
+ pg_do_reserve_open.c pg_do_reserve_open.h \
+ pg_do_purse_delete.c pg_do_purse_delete.h \
+ pg_preflight.h pg_preflight.c \
+ pg_iterate_active_signkeys.h pg_iterate_active_signkeys.c \
+ pg_commit.h pg_commit.c \
+ pg_get_coin_transactions.c pg_get_coin_transactions.h \
+ pg_get_expired_reserves.c pg_get_expired_reserves.h \
+ pg_start.h pg_start.c \
+ pg_rollback.h pg_rollback.c \
+ pg_get_purse_request.c pg_get_purse_request.h \
+ pg_get_reserve_history.c pg_get_reserve_history.h \
+ pg_get_unfinished_close_requests.c pg_get_unfinished_close_requests.h \
+ pg_insert_close_request.c pg_insert_close_request.h \
+ pg_delete_aggregation_transient.h pg_delete_aggregation_transient.c \
+ pg_get_link_data.h pg_get_link_data.c \
+ pg_drop_tables.h pg_drop_tables.c \
+ pg_insert_purse_request.h pg_insert_purse_request.c \
+ pg_insert_records_by_table.c pg_insert_records_by_table.h \
+ pg_insert_reserve_open_deposit.c pg_insert_reserve_open_deposit.h \
+ pg_iterate_kyc_reference.c pg_iterate_kyc_reference.h \
+ pg_iterate_reserve_close_info.c pg_iterate_reserve_close_info.h \
+ pg_lookup_records_by_table.c pg_lookup_records_by_table.h \
+ pg_lookup_serial_by_table.c pg_lookup_serial_by_table.h \
+ pg_select_reserve_close_info.c pg_select_reserve_close_info.h \
+ pg_select_reserve_closed_above_serial_id.c pg_select_reserve_closed_above_serial_id.h \
+ pg_select_purse.h pg_select_purse.c \
+ pg_select_purse_requests_above_serial_id.h pg_select_purse_requests_above_serial_id.c \
+ pg_select_purse_merges_above_serial_id.h pg_select_purse_merges_above_serial_id.c \
+ 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_select_reserve_open_above_serial_id.c pg_select_reserve_open_above_serial_id.h
libtaler_plugin_exchangedb_postgres_la_LDFLAGS = \
- $(TALER_PLUGIN_LDFLAGS) \
+ $(TALER_PLUGIN_LDFLAGS)
+libtaler_plugin_exchangedb_postgres_la_LIBADD = \
+ $(LTLIBINTL) \
$(top_builddir)/src/pq/libtalerpq.la \
$(top_builddir)/src/util/libtalerutil.la \
- -lpq \
-lgnunetpq \
- -lgnunetutil $(XLIB)
+ -lgnunetutil \
+ -ljansson \
+ -lpq \
+ $(XLIB)
lib_LTLIBRARIES = \
libtalerexchangedb.la
libtalerexchangedb_la_SOURCES = \
exchangedb_accounts.c \
- exchangedb_auditorkeys.c \
- exchangedb_denomkeys.c \
- exchangedb_fees.c \
exchangedb_plugin.c \
- exchangedb_signkeys.c \
exchangedb_transactions.c
libtalerexchangedb_la_LIBADD = \
$(top_builddir)/src/bank-lib/libtalerbank.la \
$(top_builddir)/src/util/libtalerutil.la \
- -lgnunetutil $(XLIB)
+ -lgnunetutil \
+ $(XLIB)
libtalerexchangedb_la_LDFLAGS = \
$(POSTGRESQL_LDFLAGS) \
-version-info 1:0:0 \
@@ -67,57 +306,94 @@ libtalerexchangedb_la_LDFLAGS = \
check_PROGRAMS = \
- test-exchangedb-auditors \
- test-exchangedb-denomkeys \
- test-exchangedb-fees \
- test-exchangedb-signkeys \
test-exchangedb-postgres
+noinst_PROGRAMS = \
+ bench-db-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-auditors \
- test-exchangedb-denomkeys \
- test-exchangedb-fees \
- test-exchangedb-postgres \
- test-exchangedb-signkeys
-
-test_exchangedb_auditors_SOURCES = \
- test_exchangedb_auditors.c
-test_exchangedb_auditors_LDADD = \
+ $(check_SCRIPTS) \
+ $(check_PROGRAMS)
+
+test_exchangedb_postgres_SOURCES = \
+ test_exchangedb.c
+test_exchangedb_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 = \
libtalerexchangedb.la \
- $(top_srcdir)/src/util/libtalerutil.la \
- -lgnunetutil
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ -lgnunetpq \
+ -lgnunetutil \
+ $(XLIB)
-test_exchangedb_denomkeys_SOURCES = \
- test_exchangedb_denomkeys.c
-test_exchangedb_denomkeys_LDADD = \
+perf_reserves_in_insert_postgres_SOURCES = \
+ perf_reserves_in_insert.c
+perf_reserves_in_insert_postgres_LDADD = \
libtalerexchangedb.la \
- $(top_srcdir)/src/util/libtalerutil.la \
- -lgnunetutil
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ -ljansson \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lm \
+ $(XLIB)
-test_exchangedb_fees_SOURCES = \
- test_exchangedb_fees.c
-test_exchangedb_fees_LDADD = \
+perf_select_refunds_by_coin_postgres_SOURCES = \
+ perf_select_refunds_by_coin.c
+perf_select_refunds_by_coin_postgres_LDADD = \
libtalerexchangedb.la \
- $(top_srcdir)/src/util/libtalerutil.la \
- -lgnunetutil
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ -ljansson \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lm \
+ $(XLIB)
-test_exchangedb_signkeys_SOURCES = \
- test_exchangedb_signkeys.c
-test_exchangedb_signkeys_LDADD = \
+perf_get_link_data_postgres_SOURCES = \
+ perf_get_link_data.c
+perf_get_link_data_postgres_LDADD = \
libtalerexchangedb.la \
- $(top_srcdir)/src/util/libtalerutil.la \
- -lgnunetutil
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ -ljansson \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lm \
+ $(XLIB)
-test_exchangedb_postgres_SOURCES = \
- test_exchangedb.c
-test_exchangedb_postgres_LDADD = \
+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_srcdir)/src/util/libtalerutil.la \
- $(top_srcdir)/src/pq/libtalerpq.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
-ljansson \
- -lgnunetutil
+ -lgnunetjson \
+ -lgnunetutil \
+ -lm \
+ $(XLIB)
+
EXTRA_test_exchangedb_postgres_DEPENDENCIES = \
libtaler_plugin_exchangedb_postgres.la
diff --git a/src/exchangedb/auditor-triggers-0001.sql b/src/exchangedb/auditor-triggers-0001.sql
new file mode 100644
index 000000000..4e2ea66ce
--- /dev/null
+++ b/src/exchangedb/auditor-triggers-0001.sql
@@ -0,0 +1,41 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 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/>
+--
+
+-- Everything in one big transaction
+BEGIN;
+
+SELECT _v.register_patch('auditor-triggers-0001');
+
+SET search_path TO exchange;
+
+CREATE OR REPLACE FUNCTION auditor_new_deposits_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+AS $$
+BEGIN
+ NOTIFY XFIXME;
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION auditor_new_deposits_trigger()
+ IS 'Call XXX on new entry';
+
+CREATE TRIGGER auditor_notify_helper_insert_deposits
+ AFTER INSERT
+ ON exchange.batch_deposits
+EXECUTE PROCEDURE auditor_new_deposits_trigger();
+
+
+COMMIT;
diff --git a/src/exchangedb/bench-db-postgres.conf b/src/exchangedb/bench-db-postgres.conf
new file mode 100644
index 000000000..d51cf9175
--- /dev/null
+++ b/src/exchangedb/bench-db-postgres.conf
@@ -0,0 +1,14 @@
+# This file is in the public domain.
+#
+# Database-backend independent specification for the exchangedb module.
+#
+[bench-db-postgres]
+CONFIG = postgres:///talercheck
+
+# Where are the SQL files to setup our tables?
+# Important: this MUST end with a "/"!
+SQL_DIR = $DATADIR/sql/exchange/
+
+[exchangedb]
+# Number of purses per account by default.
+DEFAULT_PURSE_LIMIT = 1 \ No newline at end of file
diff --git a/src/exchangedb/bench_db.c b/src/exchangedb/bench_db.c
new file mode 100644
index 000000000..302d23062
--- /dev/null
+++ b/src/exchangedb/bench_db.c
@@ -0,0 +1,531 @@
+/*
+ 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 exchangedb/bench_db.c
+ * @brief test cases for DB interaction functions
+ * @author Sree Harsha Totakura
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <gnunet/gnunet_pq_lib.h>
+#include "taler_util.h"
+
+/**
+ * How many elements should we insert?
+ */
+#define TOTAL (1024 * 16)
+
+/**
+ * Global result from the testcase.
+ */
+static int result;
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
+
+
+static bool
+prepare (struct GNUNET_PQ_Context *conn)
+{
+ struct GNUNET_PQ_PreparedStatement ps[] = {
+ GNUNET_PQ_make_prepare (
+ "bm_insert",
+ "INSERT INTO benchmap "
+ "(hc"
+ ",expiration_date"
+ ") VALUES "
+ "($1, $2);"),
+ /* Used in #postgres_iterate_denomination_info() */
+ GNUNET_PQ_make_prepare (
+ "bm_select",
+ "SELECT"
+ " expiration_date"
+ " FROM benchmap"
+ " WHERE hc=$1;"),
+ GNUNET_PQ_make_prepare (
+ "bhm_insert",
+ "INSERT INTO benchhmap "
+ "(hc"
+ ",expiration_date"
+ ") VALUES "
+ "($1, $2);"),
+ /* Used in #postgres_iterate_denomination_info() */
+ GNUNET_PQ_make_prepare (
+ "bhm_select",
+ "SELECT"
+ " expiration_date"
+ " FROM benchhmap"
+ " WHERE hc=$1;"),
+ GNUNET_PQ_make_prepare (
+ "bem_insert",
+ "INSERT INTO benchemap "
+ "(hc"
+ ",ihc"
+ ",expiration_date"
+ ") VALUES "
+ "($1, $2, $3);"),
+ /* Used in #postgres_iterate_denomination_info() */
+ GNUNET_PQ_make_prepare (
+ "bem_select",
+ "SELECT"
+ " expiration_date"
+ " FROM benchemap"
+ " WHERE ihc=$1 AND hc=$2;"),
+ GNUNET_PQ_PREPARED_STATEMENT_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = GNUNET_PQ_prepare_statements (conn,
+ ps);
+ if (GNUNET_OK != ret)
+ return false;
+ return true;
+}
+
+
+static bool
+bm_insert (struct GNUNET_PQ_Context *conn,
+ unsigned int i)
+{
+ uint32_t b = htonl ((uint32_t) i);
+ struct GNUNET_HashCode hc;
+ struct GNUNET_TIME_Absolute now;
+
+ now = GNUNET_TIME_absolute_get ();
+ GNUNET_CRYPTO_hash (&b,
+ sizeof (b),
+ &hc);
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&hc),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_non_select (conn,
+ "bm_insert",
+ params);
+ return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ }
+}
+
+
+static bool
+bhm_insert (struct GNUNET_PQ_Context *conn,
+ unsigned int i)
+{
+ uint32_t b = htonl ((uint32_t) i);
+ struct GNUNET_HashCode hc;
+ struct GNUNET_TIME_Absolute now;
+
+ now = GNUNET_TIME_absolute_get ();
+ GNUNET_CRYPTO_hash (&b,
+ sizeof (b),
+ &hc);
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&hc),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_non_select (conn,
+ "bhm_insert",
+ params);
+ return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ }
+}
+
+
+static bool
+bem_insert (struct GNUNET_PQ_Context *conn,
+ unsigned int i)
+{
+ uint32_t b = htonl ((uint32_t) i);
+ struct GNUNET_HashCode hc;
+ struct GNUNET_TIME_Absolute now;
+ uint32_t ihc;
+
+ now = GNUNET_TIME_absolute_get ();
+ GNUNET_CRYPTO_hash (&b,
+ sizeof (b),
+ &hc);
+ GNUNET_memcpy (&ihc,
+ &hc,
+ sizeof (ihc));
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&hc),
+ GNUNET_PQ_query_param_uint32 (&ihc),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_non_select (conn,
+ "bem_insert",
+ params);
+ return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ }
+}
+
+
+static bool
+bm_select (struct GNUNET_PQ_Context *conn,
+ unsigned int i)
+{
+ uint32_t b = htonl ((uint32_t) i);
+ struct GNUNET_HashCode hc;
+ struct GNUNET_TIME_Absolute now;
+
+ GNUNET_CRYPTO_hash (&b,
+ sizeof (b),
+ &hc);
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&hc),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ &now),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (conn,
+ "bm_select",
+ params,
+ rs);
+ return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ }
+}
+
+
+static bool
+bhm_select (struct GNUNET_PQ_Context *conn,
+ unsigned int i)
+{
+ uint32_t b = htonl ((uint32_t) i);
+ struct GNUNET_HashCode hc;
+ struct GNUNET_TIME_Absolute now;
+
+ GNUNET_CRYPTO_hash (&b,
+ sizeof (b),
+ &hc);
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&hc),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ &now),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (conn,
+ "bhm_select",
+ params,
+ rs);
+ return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ }
+}
+
+
+static bool
+bem_select (struct GNUNET_PQ_Context *conn,
+ unsigned int i)
+{
+ uint32_t b = htonl ((uint32_t) i);
+ struct GNUNET_HashCode hc;
+ struct GNUNET_TIME_Absolute now;
+ uint32_t ihc;
+
+ GNUNET_CRYPTO_hash (&b,
+ sizeof (b),
+ &hc);
+ GNUNET_memcpy (&ihc,
+ &hc,
+ sizeof (ihc));
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint32 (&ihc),
+ GNUNET_PQ_query_param_auto_from_type (&hc),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ &now),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (conn,
+ "bem_select",
+ params,
+ rs);
+ return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ }
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+static void
+run (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct GNUNET_PQ_Context *conn;
+ struct GNUNET_PQ_Context *conn2;
+ struct GNUNET_TIME_Absolute now;
+ pid_t f;
+ int status;
+
+ conn = GNUNET_PQ_connect_with_cfg (cfg,
+ "bench-db-postgres",
+ "benchmark-",
+ NULL,
+ NULL);
+ if (NULL == conn)
+ {
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ conn2 = GNUNET_PQ_connect_with_cfg (cfg,
+ "bench-db-postgres",
+ NULL,
+ NULL,
+ NULL);
+ if (! prepare (conn))
+ {
+ GNUNET_PQ_disconnect (conn);
+ GNUNET_PQ_disconnect (conn2);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ if (! prepare (conn2))
+ {
+ GNUNET_PQ_disconnect (conn);
+ GNUNET_PQ_disconnect (conn2);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ {
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_try_execute ("DELETE FROM benchmap;"),
+ GNUNET_PQ_make_try_execute ("DELETE FROM benchemap;"),
+ GNUNET_PQ_make_try_execute ("DELETE FROM benchhmap;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_exec_statements (conn,
+ es));
+ }
+ now = GNUNET_TIME_absolute_get ();
+ for (unsigned int i = 0; i<TOTAL; i++)
+ if (! bm_insert (conn,
+ i))
+ {
+ GNUNET_PQ_disconnect (conn);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Insertion of %u elements took %s\n",
+ (unsigned int) TOTAL,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+ now = GNUNET_TIME_absolute_get ();
+ f = fork ();
+ for (unsigned int i = 0; i<TOTAL; i++)
+ {
+ uint32_t j;
+
+ j = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
+ TOTAL);
+ if (! bm_select ((0 == f) ? conn2 : conn,
+ j))
+ {
+ GNUNET_PQ_disconnect (conn);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ }
+ if (0 == f)
+ exit (0);
+ waitpid (f, &status, 0);
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Selection of 2x%u elements took %s\n",
+ (unsigned int) TOTAL,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+
+ now = GNUNET_TIME_absolute_get ();
+ for (unsigned int i = 0; i<TOTAL; i++)
+ if (! bhm_insert (conn,
+ i))
+ {
+ GNUNET_PQ_disconnect (conn);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Insertion of %u elements with hash index took %s\n",
+ (unsigned int) TOTAL,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+ now = GNUNET_TIME_absolute_get ();
+ f = fork ();
+ for (unsigned int i = 0; i<TOTAL; i++)
+ {
+ uint32_t j;
+
+ j = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
+ TOTAL);
+ if (! bhm_select ((0 == f) ? conn2 : conn,
+ j))
+ {
+ GNUNET_PQ_disconnect (conn);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ }
+ if (0 == f)
+ exit (0);
+ waitpid (f, &status, 0);
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Selection of 2x%u elements with hash index took %s\n",
+ (unsigned int) TOTAL,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+
+ now = GNUNET_TIME_absolute_get ();
+ for (unsigned int i = 0; i<TOTAL; i++)
+ if (! bem_insert (conn,
+ i))
+ {
+ GNUNET_PQ_disconnect (conn);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Insertion of %u elements with short element took %s\n",
+ (unsigned int) TOTAL,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+ now = GNUNET_TIME_absolute_get ();
+ f = fork ();
+ for (unsigned int i = 0; i<TOTAL; i++)
+ {
+ uint32_t j;
+
+ j = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
+ TOTAL);
+ if (! bem_select ((0 == f) ? conn2 : conn,
+ j))
+ {
+ GNUNET_PQ_disconnect (conn);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ }
+ if (0 == f)
+ exit (0);
+ waitpid (f, &status, 0);
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Selection of 2x%u elements with short element took %s\n",
+ (unsigned int) TOTAL,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+
+ GNUNET_PQ_disconnect (conn);
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ char *testname;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ (void) argc;
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ GNUNET_log_setup (argv[0],
+ "INFO",
+ NULL);
+ plugin_name++;
+ (void) GNUNET_asprintf (&testname,
+ "bench-db-%s",
+ plugin_name);
+ (void) GNUNET_asprintf (&config_filename,
+ "%s.conf",
+ testname);
+ TALER_OS_init ();
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return result;
+}
+
+
+/* end of bench_db.c */
diff --git a/src/exchangedb/benchmark-0001.sql b/src/exchangedb/benchmark-0001.sql
new file mode 100644
index 000000000..34fed6a55
--- /dev/null
+++ b/src/exchangedb/benchmark-0001.sql
@@ -0,0 +1,55 @@
+--
+-- 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/>
+--
+
+-- Everything in one big transaction
+BEGIN;
+
+-- Check patch versioning is in place.
+SELECT _v.register_patch('benchmark-0001', NULL, NULL);
+
+-- Naive, btree version
+CREATE TABLE IF NOT EXISTS benchmap
+ (uuid BIGSERIAL PRIMARY KEY
+ ,hc BYTEA UNIQUE CHECK(LENGTH(hc)=64)
+ ,expiration_date INT8 NOT NULL
+ );
+
+-- Replace btree with hash-based index
+CREATE TABLE IF NOT EXISTS benchhmap
+ (uuid BIGSERIAL PRIMARY KEY
+ ,hc BYTEA NOT NULL CHECK(LENGTH(hc)=64)
+ ,expiration_date INT8 NOT NULL
+ );
+CREATE INDEX IF NOT EXISTS benchhmap_index
+ ON benchhmap
+ USING HASH (hc);
+ALTER TABLE benchhmap
+ ADD CONSTRAINT pk
+ EXCLUDE USING HASH (hc with =);
+
+-- Keep btree, also add 32-bit hash-based index on top
+CREATE TABLE IF NOT EXISTS benchemap
+ (uuid BIGSERIAL PRIMARY KEY
+ ,ihc INT4 NOT NULL
+ ,hc BYTEA UNIQUE CHECK(LENGTH(hc)=64)
+ ,expiration_date INT8 NOT NULL
+ );
+CREATE INDEX IF NOT EXISTS benchemap_index
+ ON benchemap
+ USING HASH (ihc);
+
+-- Complete transaction
+COMMIT;
diff --git a/src/exchangedb/drop.sql b/src/exchangedb/drop.sql
new file mode 100644
index 000000000..b7583f794
--- /dev/null
+++ b/src/exchangedb/drop.sql
@@ -0,0 +1,39 @@
+--
+-- 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/>
+--
+
+-- Everything in one big transaction
+BEGIN;
+
+WITH xpatches AS (
+ SELECT patch_name
+ FROM _v.patches
+ WHERE starts_with(patch_name,'exchange-')
+)
+ SELECT _v.unregister_patch(xpatches.patch_name)
+ FROM xpatches;
+
+
+WITH xpatches AS (
+ SELECT patch_name
+ FROM _v.patches
+ WHERE starts_with(patch_name,'auditor-triggers-')
+)
+ SELECT _v.unregister_patch(xpatches.patch_name)
+ FROM xpatches;
+
+DROP SCHEMA exchange CASCADE;
+
+COMMIT;
diff --git a/src/exchangedb/drop0001.sql b/src/exchangedb/drop0001.sql
deleted file mode 100644
index f0f46a611..000000000
--- a/src/exchangedb/drop0001.sql
+++ /dev/null
@@ -1,50 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2014--2020 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/>
---
-
--- Everything in one big transaction
-BEGIN;
-
--- This script DROPs all of the tables we create, including the
--- versioning schema!
---
--- Unlike the other SQL files, it SHOULD be updated to reflect the
--- latest requirements for dropping tables.
-
--- Drops for 0001.sql
-DROP TABLE IF EXISTS prewire CASCADE;
-DROP TABLE IF EXISTS recoup CASCADE;
-DROP TABLE IF EXISTS recoup_refresh CASCADE;
-DROP TABLE IF EXISTS aggregation_tracking CASCADE;
-DROP TABLE IF EXISTS wire_out CASCADE;
-DROP TABLE IF EXISTS wire_fee CASCADE;
-DROP TABLE IF EXISTS deposits CASCADE;
-DROP TABLE IF EXISTS refunds CASCADE;
-DROP TABLE IF EXISTS refresh_commitments CASCADE;
-DROP TABLE IF EXISTS refresh_revealed_coins CASCADE;
-DROP TABLE IF EXISTS refresh_transfer_keys CASCADE;
-DROP TABLE IF EXISTS known_coins CASCADE;
-DROP TABLE IF EXISTS reserves_close CASCADE;
-DROP TABLE IF EXISTS reserves_out CASCADE;
-DROP TABLE IF EXISTS reserves_in CASCADE;
-DROP TABLE IF EXISTS reserves CASCADE;
-DROP TABLE IF EXISTS denomination_revocations CASCADE;
-DROP TABLE IF EXISTS denominations CASCADE;
-
--- Drop versioning (0000.sql)
-DROP SCHEMA IF EXISTS _v CASCADE;
-
--- And we're out of here...
-COMMIT;
diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql
index bec9af5ba..a4b1c8b9f 100644
--- a/src/exchangedb/exchange-0001.sql
+++ b/src/exchangedb/exchange-0001.sql
@@ -1,6 +1,6 @@
--
-- This file is part of TALER
--- Copyright (C) 2014--2020 Taler Systems SA
+-- Copyright (C) 2014--2022 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
@@ -14,430 +14,283 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
--- Everything in one big transaction
BEGIN;
--- Check patch versioning is in place.
SELECT _v.register_patch('exchange-0001', NULL, NULL);
-
-CREATE TABLE IF NOT EXISTS denominations
- (denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)
- ,denom_pub BYTEA NOT NULL
- ,master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)
- ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
- ,valid_from INT8 NOT NULL
- ,expire_withdraw INT8 NOT NULL
- ,expire_deposit INT8 NOT NULL
- ,expire_legal INT8 NOT NULL
- ,coin_val INT8 NOT NULL
- ,coin_frac INT4 NOT NULL
- ,fee_withdraw_val INT8 NOT NULL
- ,fee_withdraw_frac INT4 NOT NULL
- ,fee_deposit_val INT8 NOT NULL
- ,fee_deposit_frac INT4 NOT NULL
- ,fee_refresh_val INT8 NOT NULL
- ,fee_refresh_frac INT4 NOT NULL
- ,fee_refund_val INT8 NOT NULL
- ,fee_refund_frac INT4 NOT NULL
- );
-COMMENT ON TABLE denominations
- IS 'Main denominations table. All the coins the exchange knows about.';
-
-CREATE INDEX IF NOT EXISTS denominations_expire_legal_index
- ON denominations
- (expire_legal);
-
-
-CREATE TABLE IF NOT EXISTS denomination_revocations
- (denom_revocations_serial_id BIGSERIAL UNIQUE
- ,denom_pub_hash BYTEA PRIMARY KEY REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE
- ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
- );
-COMMENT ON TABLE denomination_revocations
- IS 'remembering which denomination keys have been revoked';
-
-
-CREATE TABLE IF NOT EXISTS reserves
- (reserve_pub BYTEA PRIMARY KEY CHECK(LENGTH(reserve_pub)=32)
- ,account_details TEXT NOT NULL
- ,current_balance_val INT8 NOT NULL
- ,current_balance_frac INT4 NOT NULL
- ,expiration_date INT8 NOT NULL
- ,gc_date INT8 NOT NULL
- );
-COMMENT ON TABLE reserves
- IS 'Summarizes the balance of a reserve. Updated when new funds are added or withdrawn.';
-COMMENT ON COLUMN reserves.expiration_date
- IS 'Used to trigger closing of reserves that have not been drained after some time';
-COMMENT ON COLUMN reserves.gc_date
- IS 'Used to forget all information about a reserve during garbage collection';
-
-
-CREATE INDEX IF NOT EXISTS reserves_expiration_index
- ON reserves
- (expiration_date
- ,current_balance_val
- ,current_balance_frac
- );
-COMMENT ON INDEX reserves_expiration_index
- IS 'used in get_expired_reserves';
-
-CREATE INDEX IF NOT EXISTS reserves_gc_index
- ON reserves
- (gc_date);
-COMMENT ON INDEX reserves_gc_index
- IS 'for reserve garbage collection';
-
-
-CREATE TABLE IF NOT EXISTS reserves_in
- (reserve_in_serial_id BIGSERIAL UNIQUE
- ,reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE
- ,wire_reference INT8 NOT NULL
- ,credit_val INT8 NOT NULL
- ,credit_frac INT4 NOT NULL
- ,sender_account_details TEXT NOT NULL
- ,exchange_account_section TEXT NOT NULL
- ,execution_date INT8 NOT NULL
- ,PRIMARY KEY (reserve_pub, wire_reference)
+CREATE SCHEMA exchange;
+COMMENT ON SCHEMA exchange IS 'taler-exchange data';
+
+SET search_path TO exchange;
+
+---------------------------------------------------------------------------
+-- General procedures for DB setup
+---------------------------------------------------------------------------
+
+CREATE TABLE exchange_tables
+ (table_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY
+ ,name TEXT NOT NULL
+ ,version TEXT NOT NULL
+ ,action TEXT NOT NULL
+ ,partitioned BOOL NOT NULL
+ ,by_range BOOL NOT NULL
+ ,finished BOOL NOT NULL DEFAULT(FALSE));
+COMMENT ON TABLE exchange_tables
+ IS 'Tables of the exchange and their status';
+COMMENT ON COLUMN exchange_tables.name
+ IS 'Base name of the table (without partition/shard)';
+COMMENT ON COLUMN exchange_tables.version
+ IS 'Version of the DB in which the given action happened';
+COMMENT ON COLUMN exchange_tables.action
+ IS 'Action to take on the table (e.g. create, constrain, or foreign). Create is done for the master table and each partition; constrain is only for partitions or for master if there are no partitions; master only on master (takes no argument); foreign only on master if there are no partitions.';
+COMMENT ON COLUMN exchange_tables.partitioned
+ IS 'TRUE if the table is partitioned';
+COMMENT ON COLUMN exchange_tables.by_range
+ IS 'TRUE if the table is partitioned by range';
+COMMENT ON COLUMN exchange_tables.finished
+ IS 'TRUE if the respective migration has been run';
+
+
+CREATE FUNCTION create_partitioned_table(
+ IN table_definition TEXT -- SQL template for table creation
+ ,IN table_name TEXT -- base name of the table
+ ,IN main_table_partition_str TEXT -- declaration for how to partition the table
+ ,IN partition_suffix TEXT DEFAULT NULL -- NULL: no partitioning, 0: yes partitioning, no sharding, >0: sharding
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ IF (partition_suffix IS NULL)
+ THEN
+ -- no partitioning, disable option
+ main_table_partition_str = '';
+ ELSE
+ IF (partition_suffix::int > 0)
+ THEN
+ -- sharding, add shard name
+ table_name=table_name || '_' || partition_suffix;
+ END IF;
+ END IF;
+ EXECUTE FORMAT(
+ table_definition,
+ table_name,
+ main_table_partition_str
);
-COMMENT ON TABLE reserves_in
- IS 'list of transfers of funds into the reserves, one per incoming wire transfer';
-
-CREATE INDEX IF NOT EXISTS reserves_in_execution_index
- ON reserves_in
- (exchange_account_section
- ,execution_date
- );
-CREATE INDEX IF NOT EXISTS reserves_in_exchange_account_serial
- ON reserves_in
- (exchange_account_section,
- reserve_in_serial_id DESC
+END $$;
+
+COMMENT ON FUNCTION create_partitioned_table
+ IS 'Generic function to create a table that is partitioned or sharded.';
+
+
+CREATE FUNCTION comment_partitioned_table(
+ IN table_comment TEXT
+ ,IN table_name TEXT
+ ,IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ IF ( (partition_suffix IS NOT NULL) AND
+ (partition_suffix::int > 0) )
+ THEN
+ -- sharding, add shard name
+ table_name=table_name || '_' || partition_suffix;
+ END IF;
+ EXECUTE FORMAT(
+ 'COMMENT ON TABLE %s IS %s'
+ ,table_name
+ ,quote_literal(table_comment)
);
-
-
-CREATE TABLE IF NOT EXISTS reserves_close
- (close_uuid BIGSERIAL PRIMARY KEY
- ,reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE
- ,execution_date INT8 NOT NULL
- ,wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32)
- ,receiver_account TEXT NOT NULL
- ,amount_val INT8 NOT NULL
- ,amount_frac INT4 NOT NULL
- ,closing_fee_val INT8 NOT NULL
- ,closing_fee_frac INT4 NOT NULL);
-COMMENT ON TABLE reserves_close
- IS 'wire transfers executed by the reserve to close reserves';
-
-CREATE INDEX IF NOT EXISTS reserves_close_by_reserve
- ON reserves_close
- (reserve_pub);
-
-
-CREATE TABLE IF NOT EXISTS reserves_out
- (reserve_out_serial_id BIGSERIAL UNIQUE
- ,h_blind_ev BYTEA PRIMARY KEY CHECK (LENGTH(h_blind_ev)=64)
- ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash)
- ,denom_sig BYTEA NOT NULL
- ,reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE
- ,reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)
- ,execution_date INT8 NOT NULL
- ,amount_with_fee_val INT8 NOT NULL
- ,amount_with_fee_frac INT4 NOT NULL
- );
-COMMENT ON TABLE reserves_out
- IS 'Withdraw operations performed on reserves.';
-COMMENT ON COLUMN reserves_out.h_blind_ev
- IS 'Hash of the blinded coin, used as primary key here so that broken clients that use a non-random coin or blinding factor fail to withdraw (otherwise they would fail on deposit when the coin is not unique there).';
-COMMENT ON COLUMN reserves_out.denom_pub_hash
- IS 'We do not CASCADE ON DELETE here, we may keep the denomination data alive';
-
-CREATE INDEX IF NOT EXISTS reserves_out_reserve_pub_index
- ON reserves_out
- (reserve_pub);
-COMMENT ON INDEX reserves_out_reserve_pub_index
- IS 'for get_reserves_out';
-CREATE INDEX IF NOT EXISTS reserves_out_execution_date
- ON reserves_out
- (execution_date);
-CREATE INDEX IF NOT EXISTS reserves_out_for_get_withdraw_info
- ON reserves_out
- (denom_pub_hash
- ,h_blind_ev
- );
-
-
-CREATE TABLE IF NOT EXISTS known_coins
- (coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(coin_pub)=32)
- ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE
- ,denom_sig BYTEA NOT NULL
- );
-COMMENT ON TABLE known_coins
- IS 'information about coins and their signatures, so we do not have to store the signatures more than once if a coin is involved in multiple operations';
-
-CREATE INDEX IF NOT EXISTS known_coins_by_denomination
- ON known_coins
- (denom_pub_hash);
-
-
-CREATE TABLE IF NOT EXISTS refresh_commitments
- (melt_serial_id BIGSERIAL UNIQUE
- ,rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64)
- ,old_coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE
- ,old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64)
- ,amount_with_fee_val INT8 NOT NULL
- ,amount_with_fee_frac INT4 NOT NULL
- ,noreveal_index INT4 NOT NULL
- );
-COMMENT ON TABLE refresh_commitments
- IS 'Commitments made when melting coins and the gamma value chosen by the exchange.';
-
-CREATE INDEX IF NOT EXISTS refresh_commitments_old_coin_pub_index
- ON refresh_commitments
- (old_coin_pub);
-
-
-CREATE TABLE IF NOT EXISTS refresh_revealed_coins
- (rc BYTEA NOT NULL REFERENCES refresh_commitments (rc) ON DELETE CASCADE
- ,freshcoin_index INT4 NOT NULL
- ,link_sig BYTEA NOT NULL CHECK(LENGTH(link_sig)=64)
- ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE
- ,coin_ev BYTEA UNIQUE NOT NULL
- ,h_coin_ev BYTEA NOT NULL CHECK(LENGTH(h_coin_ev)=64)
- ,ev_sig BYTEA NOT NULL
- ,PRIMARY KEY (rc, freshcoin_index)
- ,UNIQUE (h_coin_ev)
- );
-COMMENT ON TABLE refresh_revealed_coins
- IS 'Revelations about the new coins that are to be created during a melting session.';
-COMMENT ON COLUMN refresh_revealed_coins.rc
- IS 'refresh commitment identifying the melt operation';
-COMMENT ON COLUMN refresh_revealed_coins.freshcoin_index
- IS 'index of the fresh coin being created (one melt operation may result in multiple fresh coins)';
-COMMENT ON COLUMN refresh_revealed_coins.coin_ev
- IS 'envelope of the new coin to be signed';
-COMMENT ON COLUMN refresh_revealed_coins.h_coin_ev
- IS 'hash of the envelope of the new coin to be signed (for lookups)';
-COMMENT ON COLUMN refresh_revealed_coins.ev_sig
- IS 'exchange signature over the envelope';
-
-CREATE INDEX IF NOT EXISTS refresh_revealed_coins_coin_pub_index
- ON refresh_revealed_coins
- (denom_pub_hash);
-
-
-CREATE TABLE IF NOT EXISTS refresh_transfer_keys
- (rc BYTEA NOT NULL PRIMARY KEY REFERENCES refresh_commitments (rc) ON DELETE CASCADE
- ,transfer_pub BYTEA NOT NULL CHECK(LENGTH(transfer_pub)=32)
- ,transfer_privs BYTEA NOT NULL
+END $$;
+
+COMMENT ON FUNCTION comment_partitioned_table
+ IS 'Generic function to create a comment on table that is partitioned.';
+
+
+CREATE FUNCTION comment_partitioned_column(
+ IN table_comment TEXT
+ ,IN column_name TEXT
+ ,IN table_name TEXT
+ ,IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ IF ( (partition_suffix IS NOT NULL) AND
+ (partition_suffix::int > 0) )
+ THEN
+ -- sharding, add shard name
+ table_name=table_name || '_' || partition_suffix;
+ END IF;
+ EXECUTE FORMAT(
+ 'COMMENT ON COLUMN %s.%s IS %s'
+ ,table_name
+ ,column_name
+ ,quote_literal(table_comment)
);
-COMMENT ON TABLE refresh_transfer_keys
- IS 'Transfer keys of a refresh operation (the data revealed to the exchange).';
-COMMENT ON COLUMN refresh_transfer_keys.rc
- IS 'refresh commitment identifying the melt operation';
-COMMENT ON COLUMN refresh_transfer_keys.transfer_pub
- IS 'transfer public key for the gamma index';
-COMMENT ON COLUMN refresh_transfer_keys.transfer_privs
- IS 'array of TALER_CNC_KAPPA - 1 transfer private keys that have been revealed, with the gamma entry being skipped';
-
-CREATE INDEX IF NOT EXISTS refresh_transfer_keys_coin_tpub
- ON refresh_transfer_keys
- (rc
- ,transfer_pub
- );
-COMMENT ON INDEX refresh_transfer_keys_coin_tpub
- IS 'for get_link (unsure if this helps or hurts for performance as there should be very few transfer public keys per rc, but at least in theory this helps the ORDER BY clause)';
-
-
-CREATE TABLE IF NOT EXISTS deposits
- (deposit_serial_id BIGSERIAL PRIMARY KEY
- ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE
- ,amount_with_fee_val INT8 NOT NULL
- ,amount_with_fee_frac INT4 NOT NULL
- ,timestamp INT8 NOT NULL
- ,refund_deadline INT8 NOT NULL
- ,wire_deadline INT8 NOT NULL
- ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
- ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
- ,h_wire BYTEA NOT NULL CHECK (LENGTH(h_wire)=64)
- ,coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)
- ,wire TEXT NOT NULL
- ,tiny BOOLEAN NOT NULL DEFAULT FALSE
- ,done BOOLEAN NOT NULL DEFAULT FALSE
- ,UNIQUE (coin_pub, merchant_pub, h_contract_terms)
- );
-COMMENT ON TABLE deposits
- IS 'Deposits we have received and for which we need to make (aggregate) wire transfers (and manage refunds).';
-COMMENT ON COLUMN deposits.done
- IS 'Set to TRUE once we have included this deposit in some aggregate wire transfer to the merchant';
-COMMENT ON COLUMN deposits.tiny
- IS 'Set to TRUE if we decided that the amount is too small to ever trigger a wire transfer by itself (requires real aggregation)';
-
-CREATE INDEX IF NOT EXISTS deposits_coin_pub_merchant_contract_index
- ON deposits
- (coin_pub
- ,merchant_pub
- ,h_contract_terms
- );
-COMMENT ON INDEX deposits_coin_pub_merchant_contract_index
- IS 'for get_deposit_for_wtid and test_deposit_done';
-CREATE INDEX IF NOT EXISTS deposits_get_ready_index
- ON deposits
- (tiny
- ,done
- ,wire_deadline
- ,refund_deadline
- );
-COMMENT ON INDEX deposits_coin_pub_merchant_contract_index
- IS 'for deposits_get_ready';
-CREATE INDEX IF NOT EXISTS deposits_iterate_matching_index
- ON deposits
- (merchant_pub
- ,h_wire
- ,done
- ,wire_deadline
- );
-COMMENT ON INDEX deposits_iterate_matching_index
- IS 'for deposits_iterate_matching';
-
-
-CREATE TABLE IF NOT EXISTS refunds
- (refund_serial_id BIGSERIAL UNIQUE
- ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE
- ,merchant_pub BYTEA NOT NULL CHECK(LENGTH(merchant_pub)=32)
- ,merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64)
- ,h_contract_terms BYTEA NOT NULL CHECK(LENGTH(h_contract_terms)=64)
- ,rtransaction_id INT8 NOT NULL
- ,amount_with_fee_val INT8 NOT NULL
- ,amount_with_fee_frac INT4 NOT NULL
- ,PRIMARY KEY (coin_pub, merchant_pub, h_contract_terms, rtransaction_id)
- );
-COMMENT ON TABLE refunds
- IS 'Data on coins that were refunded. Technically, refunds always apply against specific deposit operations involving a coin. The combination of coin_pub, merchant_pub, h_contract_terms and rtransaction_id MUST be unique, and we usually select by coin_pub so that one goes first.';
-COMMENT ON COLUMN refunds.rtransaction_id
- IS 'used by the merchant to make refunds unique in case the same coin for the same deposit gets a subsequent (higher) refund';
-
-CREATE INDEX IF NOT EXISTS refunds_coin_pub_index
- ON refunds
- (coin_pub);
-
-
-CREATE TABLE IF NOT EXISTS wire_out
- (wireout_uuid BIGSERIAL PRIMARY KEY
- ,execution_date INT8 NOT NULL
- ,wtid_raw BYTEA UNIQUE NOT NULL CHECK (LENGTH(wtid_raw)=32)
- ,wire_target TEXT NOT NULL
- ,exchange_account_section TEXT NOT NULL
- ,amount_val INT8 NOT NULL
- ,amount_frac INT4 NOT NULL
- );
-COMMENT ON TABLE wire_out
- IS 'wire transfers the exchange has executed';
-
-
-CREATE TABLE IF NOT EXISTS aggregation_tracking
- (aggregation_serial_id BIGSERIAL UNIQUE
- ,deposit_serial_id INT8 PRIMARY KEY REFERENCES deposits (deposit_serial_id) ON DELETE CASCADE
- ,wtid_raw BYTEA CONSTRAINT wire_out_ref REFERENCES wire_out(wtid_raw) ON DELETE CASCADE DEFERRABLE
- );
-COMMENT ON TABLE aggregation_tracking
- IS 'mapping from wire transfer identifiers (WTID) to deposits (and back)';
-
-CREATE INDEX IF NOT EXISTS aggregation_tracking_wtid_index
- ON aggregation_tracking
- (wtid_raw);
-COMMENT ON INDEX aggregation_tracking_wtid_index
- IS 'for lookup_transactions';
-
-
-CREATE TABLE IF NOT EXISTS wire_fee
- (wire_method VARCHAR NOT NULL
- ,start_date INT8 NOT NULL
- ,end_date INT8 NOT NULL
- ,wire_fee_val INT8 NOT NULL
- ,wire_fee_frac INT4 NOT NULL
- ,closing_fee_val INT8 NOT NULL
- ,closing_fee_frac INT4 NOT NULL
- ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
- ,PRIMARY KEY (wire_method, start_date)
- );
-COMMENT ON TABLE wire_fee
- IS 'list of the wire fees of this exchange, by date';
-
-CREATE INDEX IF NOT EXISTS wire_fee_gc_index
- ON wire_fee
- (end_date);
-
-
-CREATE TABLE IF NOT EXISTS recoup
- (recoup_uuid BIGSERIAL UNIQUE
- ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub)
- ,coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)
- ,coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)
- ,amount_val INT8 NOT NULL
- ,amount_frac INT4 NOT NULL
- ,timestamp INT8 NOT NULL
- ,h_blind_ev BYTEA NOT NULL REFERENCES reserves_out (h_blind_ev) ON DELETE CASCADE
- );
-COMMENT ON TABLE recoup
- IS 'Information about recoups that were executed';
-COMMENT ON COLUMN recoup.coin_pub
- IS 'Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!';
-
-CREATE INDEX IF NOT EXISTS recoup_by_coin_index
- ON recoup
- (coin_pub);
-CREATE INDEX IF NOT EXISTS recoup_by_h_blind_ev
- ON recoup
- (h_blind_ev);
-CREATE INDEX IF NOT EXISTS recoup_for_by_reserve
- ON recoup
- (coin_pub
- ,h_blind_ev
- );
-
-
-CREATE TABLE IF NOT EXISTS recoup_refresh
- (recoup_refresh_uuid BIGSERIAL UNIQUE
- ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub)
- ,coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)
- ,coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)
- ,amount_val INT8 NOT NULL
- ,amount_frac INT4 NOT NULL
- ,timestamp INT8 NOT NULL
- ,h_blind_ev BYTEA NOT NULL REFERENCES refresh_revealed_coins (h_coin_ev) ON DELETE CASCADE
- );
-COMMENT ON COLUMN recoup_refresh.coin_pub
- IS 'Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!';
-
-CREATE INDEX IF NOT EXISTS recoup_refresh_by_coin_index
- ON recoup_refresh
- (coin_pub);
-CREATE INDEX IF NOT EXISTS recoup_refresh_by_h_blind_ev
- ON recoup_refresh
- (h_blind_ev);
-CREATE INDEX IF NOT EXISTS recoup_refresh_for_by_reserve
- ON recoup_refresh
- (coin_pub
- ,h_blind_ev
- );
-
-
-CREATE TABLE IF NOT EXISTS prewire
- (prewire_uuid BIGSERIAL PRIMARY KEY
- ,type TEXT NOT NULL
- ,finished BOOLEAN NOT NULL DEFAULT false
- ,buf BYTEA NOT NULL
- );
-COMMENT ON TABLE prewire
- IS 'pre-commit data for wire transfers we are about to execute';
-
-CREATE INDEX IF NOT EXISTS prepare_iteration_index
- ON prewire
- (finished);
-COMMENT ON INDEX prepare_iteration_index
- IS 'for wire_prepare_data_get and gc_prewire';
+END $$;
+
+COMMENT ON FUNCTION comment_partitioned_column
+ IS 'Generic function to create a comment on column of a table that is partitioned.';
+
+
+---------------------------------------------------------------------------
+-- Main DB setup loop
+---------------------------------------------------------------------------
+
+CREATE FUNCTION do_create_tables(
+ num_partitions INTEGER
+-- NULL: no partitions, add foreign constraints
+-- 0: no partitions, no foreign constraints
+-- 1: only 1 default partition
+-- > 1: normal partitions
+)
+ RETURNS VOID
+ LANGUAGE plpgsql
+AS $$
+DECLARE
+ tc CURSOR FOR
+ SELECT table_serial_id
+ ,name
+ ,action
+ ,partitioned
+ ,by_range
+ FROM exchange.exchange_tables
+ WHERE NOT finished
+ ORDER BY table_serial_id ASC;
+BEGIN
+ FOR rec IN tc
+ LOOP
+ CASE rec.action
+ -- "create" actions apply to master and partitions
+ WHEN 'create'
+ THEN
+ IF (rec.partitioned AND
+ (num_partitions IS NOT NULL))
+ THEN
+ -- Create master table with partitioning.
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s (%s)'::text
+ ,rec.action
+ ,rec.name
+ ,quote_literal('0')
+ );
+ IF (rec.by_range OR
+ (num_partitions = 0))
+ THEN
+ -- Create default partition.
+ IF (rec.by_range)
+ THEN
+ -- Range partition
+ EXECUTE FORMAT(
+ 'CREATE TABLE exchange.%s_default'
+ ' PARTITION OF %s'
+ ' DEFAULT'
+ ,rec.name
+ ,rec.name
+ );
+ ELSE
+ -- Hash partition
+ EXECUTE FORMAT(
+ 'CREATE TABLE exchange.%s_default'
+ ' PARTITION OF %s'
+ ' FOR VALUES WITH (MODULUS 1, REMAINDER 0)'
+ ,rec.name
+ ,rec.name
+ );
+ END IF;
+ ELSE
+ FOR i IN 1..num_partitions LOOP
+ -- Create num_partitions
+ EXECUTE FORMAT(
+ 'CREATE TABLE exchange.%I'
+ ' PARTITION OF %I'
+ ' FOR VALUES WITH (MODULUS %s, REMAINDER %s)'
+ ,rec.name || '_' || i
+ ,rec.name
+ ,num_partitions
+ ,i-1
+ );
+ END LOOP;
+ END IF;
+ ELSE
+ -- Only create master table. No partitions.
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s ()'::text
+ ,rec.action
+ ,rec.name
+ );
+ END IF;
+ -- Constrain action apply to master OR each partition
+ WHEN 'constrain'
+ THEN
+ ASSERT rec.partitioned, 'constrain action only applies to partitioned tables';
+ IF (num_partitions IS NULL)
+ THEN
+ -- Constrain master table
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s (NULL)'::text
+ ,rec.action
+ ,rec.name
+ );
+ ELSE
+ IF ( (num_partitions = 0) OR
+ (rec.by_range) )
+ THEN
+ -- Constrain default table
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s (%s)'::text
+ ,rec.action
+ ,rec.name
+ ,quote_literal('default')
+ );
+ ELSE
+ -- Constrain each partition
+ FOR i IN 1..num_partitions LOOP
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s (%s)'::text
+ ,rec.action
+ ,rec.name
+ ,quote_literal(i)
+ );
+ END LOOP;
+ END IF;
+ END IF;
+ -- Foreign actions only apply if partitioning is off
+ WHEN 'foreign'
+ THEN
+ IF (num_partitions IS NULL)
+ THEN
+ -- Add foreign constraints
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s (%s)'::text
+ ,rec.action
+ ,rec.name
+ ,NULL
+ );
+ END IF;
+ WHEN 'master'
+ THEN
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s ()'::text
+ ,rec.action
+ ,rec.name
+ );
+ ELSE
+ ASSERT FALSE, 'unsupported action type: ' || rec.action;
+ END CASE; -- END CASE (rec.action)
+ -- Mark as finished
+ UPDATE exchange.exchange_tables
+ SET finished=TRUE
+ WHERE table_serial_id=rec.table_serial_id;
+ END LOOP; -- create/alter/drop actions
+END $$;
+
+COMMENT ON FUNCTION do_create_tables
+ IS 'Creates all tables for the given number of partitions that need creating. Does NOT support sharding.';
--- Complete transaction
COMMIT;
diff --git a/src/exchangedb/exchange-0002.sql.in b/src/exchangedb/exchange-0002.sql.in
new file mode 100644
index 000000000..ab13b28af
--- /dev/null
+++ b/src/exchangedb/exchange-0002.sql.in
@@ -0,0 +1,118 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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/>
+--
+
+BEGIN;
+
+SELECT _v.register_patch('exchange-0002', NULL, NULL);
+SET search_path TO exchange;
+
+CREATE DOMAIN gnunet_hashcode
+ AS BYTEA
+ CHECK(LENGTH(VALUE) = 32);
+
+CREATE TYPE taler_amount
+ AS
+ (val INT8
+ ,frac INT4
+ );
+COMMENT ON TYPE taler_amount
+ IS 'Stores an amount, fraction is in units of 1/100000000 of the base value';
+
+CREATE TYPE exchange_do_array_reserve_insert_return_type
+ AS
+ (transaction_duplicate BOOLEAN
+ ,ruuid INT8
+ );
+COMMENT ON TYPE exchange_do_array_reserve_insert_return_type
+ IS 'Return type for exchange_do_array_reserves_insert() stored procedure';
+
+CREATE TYPE exchange_do_select_deposits_missing_wire_return_type
+ AS
+ (
+ batch_deposit_serial_id INT8,
+ total_amount taler_amount,
+ wire_target_h_payto BYTEA,
+ deadline INT8
+ );
+COMMENT ON TYPE exchange_do_select_deposits_missing_wire_return_type
+ IS 'Return type for exchange_do_select_deposits_missing_wire';
+
+
+#include "0002-denominations.sql"
+#include "0002-denomination_revocations.sql"
+#include "0002-wire_targets.sql"
+#include "0002-kyc_alerts.sql"
+#include "0002-wire_fee.sql"
+#include "0002-global_fee.sql"
+#include "0002-wire_accounts.sql"
+#include "0002-auditors.sql"
+#include "0002-auditor_denom_sigs.sql"
+#include "0002-exchange_sign_keys.sql"
+#include "0002-signkey_revocations.sql"
+#include "0002-extensions.sql"
+#include "0002-policy_fulfillments.sql"
+#include "0002-policy_details.sql"
+#include "0002-profit_drains.sql"
+#include "0002-legitimization_processes.sql"
+#include "0002-legitimization_requirements.sql"
+#include "0002-reserves.sql"
+#include "0002-reserve_history.sql"
+#include "0002-reserves_in.sql"
+#include "0002-reserves_close.sql"
+#include "0002-close_requests.sql"
+#include "0002-reserves_open_deposits.sql"
+#include "0002-reserves_open_requests.sql"
+#include "0002-reserves_out.sql"
+#include "0002-known_coins.sql"
+#include "0002-coin_history.sql"
+#include "0002-refresh_commitments.sql"
+#include "0002-refresh_revealed_coins.sql"
+#include "0002-refresh_transfer_keys.sql"
+#include "0002-batch_deposits.sql"
+#include "0002-coin_deposits.sql"
+#include "0002-refunds.sql"
+#include "0002-wire_out.sql"
+#include "0002-aggregation_transient.sql"
+#include "0002-aggregation_tracking.sql"
+#include "0002-recoup.sql"
+#include "0002-recoup_refresh.sql"
+#include "0002-prewire.sql"
+#include "0002-cs_nonce_locks.sql"
+#include "0002-purse_requests.sql"
+#include "0002-purse_merges.sql"
+#include "0002-account_merges.sql"
+#include "0002-purse_decision.sql"
+#include "0002-contracts.sql"
+#include "0002-history_requests.sql"
+#include "0002-purse_deposits.sql"
+#include "0002-wads_in.sql"
+#include "0002-wad_in_entries.sql"
+#include "0002-wads_out.sql"
+#include "0002-wad_out_entries.sql"
+#include "0002-work_shards.sql"
+#include "0002-revolving_work_shards.sql"
+#include "0002-partners.sql"
+#include "0002-partner_accounts.sql"
+#include "0002-purse_actions.sql"
+#include "0002-purse_deletion.sql"
+#include "0002-kyc_attributes.sql"
+#include "0002-aml_status.sql"
+#include "0002-aml_staff.sql"
+#include "0002-aml_history.sql"
+#include "0002-age_withdraw.sql"
+
+
+COMMIT;
diff --git a/src/exchangedb/exchange-0003.sql.in b/src/exchangedb/exchange-0003.sql.in
new file mode 100644
index 000000000..c94497531
--- /dev/null
+++ b/src/exchangedb/exchange-0003.sql.in
@@ -0,0 +1,25 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 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/>
+--
+
+BEGIN;
+
+SELECT _v.register_patch('exchange-0003', NULL, NULL);
+SET search_path TO exchange;
+
+#include "0003-wire_accounts.sql"
+#include "0003-purse_deletion.sql"
+
+COMMIT;
diff --git a/src/exchangedb/exchange-0004.sql.in b/src/exchangedb/exchange-0004.sql.in
new file mode 100644
index 000000000..c966aedc5
--- /dev/null
+++ b/src/exchangedb/exchange-0004.sql.in
@@ -0,0 +1,24 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 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/>
+--
+
+BEGIN;
+
+SELECT _v.register_patch('exchange-0004', NULL, NULL);
+SET search_path TO exchange;
+
+#include "0004-refunds.sql"
+
+COMMIT;
diff --git a/src/exchangedb/exchange_do_account_merge.sql b/src/exchangedb/exchange_do_account_merge.sql
new file mode 100644
index 000000000..723154f1d
--- /dev/null
+++ b/src/exchangedb/exchange_do_account_merge.sql
@@ -0,0 +1,15 @@
+--
+-- 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/>
+--
diff --git a/src/exchangedb/exchange_do_age_withdraw.sql b/src/exchangedb/exchange_do_age_withdraw.sql
new file mode 100644
index 000000000..89a291445
--- /dev/null
+++ b/src/exchangedb/exchange_do_age_withdraw.sql
@@ -0,0 +1,165 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+-- @author Özgür Kesim
+
+CREATE OR REPLACE FUNCTION exchange_do_age_withdraw(
+ IN amount_with_fee taler_amount,
+ IN rpub BYTEA,
+ IN rsig BYTEA,
+ IN now INT8,
+ IN min_reserve_gc INT8,
+ IN h_commitment BYTEA,
+ IN maximum_age_committed INT2, -- in years ϵ [0,1..)
+ IN noreveal_index INT2,
+ IN blinded_evs BYTEA[],
+ IN denom_serials INT8[],
+ IN denom_sigs BYTEA[],
+ OUT reserve_found BOOLEAN,
+ OUT balance_ok BOOLEAN,
+ OUT reserve_balance taler_amount,
+ OUT age_ok BOOLEAN,
+ OUT required_age INT2, -- in years ϵ [0,1..)
+ OUT reserve_birthday INT4,
+ OUT conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ reserve RECORD;
+ difference RECORD;
+ balance taler_amount;
+ not_before date;
+ earliest_date date;
+BEGIN
+-- Shards: reserves by reserve_pub (SELECT)
+-- reserves_out (INSERT, with CONFLICT detection) by wih
+-- reserves by reserve_pub (UPDATE)
+-- reserves_in by reserve_pub (SELECT)
+-- wire_targets by wire_target_h_payto
+
+SELECT current_balance
+ ,birthday
+ ,gc_date
+ INTO reserve
+ FROM exchange.reserves
+ WHERE reserves.reserve_pub=rpub;
+
+IF NOT FOUND
+THEN
+ reserve_found=FALSE;
+ age_ok = FALSE;
+ required_age=-1;
+ conflict=FALSE;
+ reserve_balance.val = 0;
+ reserve_balance.frac = 0;
+ balance_ok=FALSE;
+ RETURN;
+END IF;
+
+reserve_found = TRUE;
+conflict=FALSE; -- not really yet determined
+
+reserve_balance = reserve.current_balance;
+reserve_birthday = reserve.birthday;
+
+-- Check age requirements
+IF (reserve.birthday <> 0)
+THEN
+ not_before=date '1970-01-01' + reserve.birthday;
+ earliest_date = current_date - make_interval(maximum_age_committed);
+ --
+ -- 1970-01-01 + birthday == not_before now
+ -- | | |
+ -- <.......not allowed......>[<.....allowed range......>]
+ -- | | |
+ -- ____*_____________________*_________*________________* timeline
+ -- |
+ -- earliest_date ==
+ -- now - maximum_age_committed*year
+ --
+ IF (earliest_date < not_before)
+ THEN
+ required_age = extract(year from age(current_date, not_before));
+ age_ok = FALSE;
+ balance_ok=TRUE; -- NOT REALLY
+ RETURN;
+ END IF;
+END IF;
+
+age_ok = TRUE;
+required_age=0;
+
+-- Check reserve balance is sufficient.
+SELECT *
+INTO difference
+FROM amount_left_minus_right(reserve_balance
+ ,amount_with_fee);
+
+balance_ok = difference.ok;
+
+IF NOT balance_ok
+THEN
+ RETURN;
+END IF;
+
+balance = difference.diff;
+
+-- Calculate new expiration dates.
+min_reserve_gc=GREATEST(min_reserve_gc,reserve.gc_date);
+
+-- Update reserve balance.
+UPDATE reserves SET
+ gc_date=min_reserve_gc
+ ,current_balance=balance
+WHERE
+ reserves.reserve_pub=rpub;
+
+-- Write the commitment into the age-withdraw table
+INSERT INTO exchange.age_withdraw
+ (h_commitment
+ ,max_age
+ ,amount_with_fee
+ ,reserve_pub
+ ,reserve_sig
+ ,noreveal_index
+ ,denom_serials
+ ,h_blind_evs
+ ,denom_sigs)
+VALUES
+ (h_commitment
+ ,maximum_age_committed
+ ,amount_with_fee
+ ,rpub
+ ,rsig
+ ,noreveal_index
+ ,denom_serials
+ ,blinded_evs
+ ,denom_sigs)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Signal a conflict so that the caller
+ -- can fetch the actual data from the DB.
+ conflict=TRUE;
+ RETURN;
+ELSE
+ conflict=FALSE;
+END IF;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_age_withdraw(taler_amount, BYTEA, BYTEA, INT8, INT8, BYTEA, INT2, INT2, BYTEA[], INT8[], BYTEA[])
+ IS 'Checks whether the reserve has sufficient balance for an age-withdraw operation (or the request is repeated and was previously approved) and that age requirements are met. If so updates the database with the result. Includes storing the blinded planchets and denomination signatures, or signaling conflict';
diff --git a/src/exchangedb/exchange_do_amount_specific.sql b/src/exchangedb/exchange_do_amount_specific.sql
new file mode 100644
index 000000000..9b305a3ec
--- /dev/null
+++ b/src/exchangedb/exchange_do_amount_specific.sql
@@ -0,0 +1,92 @@
+--
+-- 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/>
+--
+
+--------------------------------------------------------------
+-- Taler amounts and helper functions
+-------------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION amount_normalize(
+ IN amount taler_amount
+ ,OUT normalized taler_amount
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ normalized.val = amount.val + amount.frac / 100000000;
+ normalized.frac = amount.frac % 100000000;
+END $$;
+
+COMMENT ON FUNCTION amount_normalize
+ IS 'Returns the normalized amount by adding to the .val the value of (.frac / 100000000) and removing the modulus 100000000 from .frac.';
+
+CREATE OR REPLACE FUNCTION amount_add(
+ IN a taler_amount
+ ,IN b taler_amount
+ ,OUT sum taler_amount
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ sum = (a.val + b.val, a.frac + b.frac);
+ CALL amount_normalize(sum ,sum);
+
+ IF (sum.val > (1<<52))
+ THEN
+ RAISE EXCEPTION 'addition overflow';
+ END IF;
+END $$;
+
+COMMENT ON FUNCTION amount_add
+ IS 'Returns the normalized sum of two amounts. It raises an exception when the resulting .val is larger than 2^52';
+
+CREATE OR REPLACE FUNCTION amount_left_minus_right(
+ IN l taler_amount
+ ,IN r taler_amount
+ ,OUT diff taler_amount
+ ,OUT ok BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+IF (l.val > r.val)
+THEN
+ ok = TRUE;
+ IF (l.frac >= r.frac)
+ THEN
+ diff.val = l.val - r.val;
+ diff.frac = l.frac - r.frac;
+ ELSE
+ diff.val = l.val - r.val - 1;
+ diff.frac = l.frac + 100000000 - r.frac;
+ END IF;
+ELSE
+ IF (l.val = r.val) AND (l.frac >= r.frac)
+ THEN
+ diff.val = 0;
+ diff.frac = l.frac - r.frac;
+ ok = TRUE;
+ ELSE
+ diff = (-1, -1);
+ ok = FALSE;
+ END IF;
+END IF;
+
+RETURN;
+END $$;
+
+COMMENT ON FUNCTION amount_left_minus_right
+ IS 'Subtracts the right amount from the left and returns the difference and TRUE, if the left amount is larger than the right, or an invalid amount and FALSE otherwise.';
diff --git a/src/exchangedb/exchange_do_batch_coin_known.sql b/src/exchangedb/exchange_do_batch_coin_known.sql
new file mode 100644
index 000000000..db96cb08c
--- /dev/null
+++ b/src/exchangedb/exchange_do_batch_coin_known.sql
@@ -0,0 +1,469 @@
+--
+-- 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
+ 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
+ )
+ SELECT
+ ir.coin_pub,
+ dd.denominations_serial,
+ ir.age_commitment_hash,
+ ir.denom_sig,
+ dd.coin
+ 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
+ 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
+ )
+ SELECT
+ ir.coin_pub,
+ dd.denominations_serial,
+ ir.age_commitment_hash,
+ ir.denom_sig,
+ dd.coin
+ 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
+ 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
+ )
+ SELECT
+ ir.coin_pub,
+ dd.denominations_serial,
+ ir.age_commitment_hash,
+ ir.denom_sig,
+ dd.coin
+ 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) AS (
+ SELECT denominations_serial, coin
+ 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
+ ) SELECT
+ input_rows.coin_pub,
+ dd.denominations_serial,
+ ins_values.age_commitment_hash,
+ ins_values.denom_sig,
+ coin
+ 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;
+$$;*/
diff --git a/src/exchangedb/exchange_do_batch_reserves_update.sql b/src/exchangedb/exchange_do_batch_reserves_update.sql
new file mode 100644
index 000000000..ebb58a149
--- /dev/null
+++ b/src/exchangedb/exchange_do_batch_reserves_update.sql
@@ -0,0 +1,72 @@
+--
+-- 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_update(
+ IN in_reserve_pub BYTEA,
+ IN in_expiration_date INT8,
+ IN in_wire_ref INT8,
+ IN in_credit taler_amount,
+ IN in_exchange_account_name TEXT,
+ IN in_wire_source_h_payto BYTEA,
+ IN in_notify text,
+ OUT out_duplicate BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ INSERT INTO reserves_in
+ (reserve_pub
+ ,wire_reference
+ ,credit
+ ,exchange_account_section
+ ,wire_source_h_payto
+ ,execution_date)
+ VALUES
+ (in_reserve_pub
+ ,in_wire_ref
+ ,in_credit
+ ,in_exchange_account_name
+ ,in_wire_source_h_payto
+ ,in_expiration_date)
+ ON CONFLICT DO NOTHING;
+ IF FOUND
+ THEN
+ --IF THE INSERTION WAS A SUCCESS IT MEANS NO DUPLICATED TRANSACTION
+ out_duplicate = FALSE;
+ UPDATE reserves rs
+ SET
+ current_balance.frac = (rs.current_balance).frac+in_credit.frac
+ - CASE
+ WHEN (rs.current_balance).frac + in_credit.frac >= 100000000
+ THEN 100000000
+ ELSE 1
+ END
+ ,current_balance.val = (rs.current_balance).val+in_credit.val
+ + CASE
+ WHEN (rs.current_balance).frac + in_credit.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ ,expiration_date=GREATEST(expiration_date,in_expiration_date)
+ ,gc_date=GREATEST(gc_date,in_expiration_date)
+ WHERE reserve_pub=in_reserve_pub;
+ EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,in_notify);
+ ELSE
+ out_duplicate = TRUE;
+ END IF;
+ RETURN;
+END $$;
diff --git a/src/exchangedb/exchange_do_batch_withdraw.sql b/src/exchangedb/exchange_do_batch_withdraw.sql
new file mode 100644
index 000000000..a48561a9a
--- /dev/null
+++ b/src/exchangedb/exchange_do_batch_withdraw.sql
@@ -0,0 +1,126 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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/>
+--
+-- @author Christian Grothoff
+-- @author Özgür Kesim
+
+CREATE OR REPLACE FUNCTION exchange_do_batch_withdraw(
+ IN amount taler_amount,
+ IN rpub BYTEA,
+ IN now INT8,
+ IN min_reserve_gc INT8,
+ IN do_age_check BOOLEAN,
+ OUT reserve_found BOOLEAN,
+ OUT balance_ok BOOLEAN,
+ OUT reserve_balance taler_amount,
+ OUT age_ok BOOLEAN,
+ OUT allowed_maximum_age INT2, -- in years
+ OUT ruuid INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ reserve RECORD;
+ balance taler_amount;
+ not_before date;
+BEGIN
+-- Shards: reserves by reserve_pub (SELECT)
+-- reserves_out (INSERT, with CONFLICT detection) by wih
+-- reserves by reserve_pub (UPDATE)
+-- reserves_in by reserve_pub (SELECT)
+-- wire_targets by wire_target_h_payto
+
+SELECT current_balance
+ ,reserve_uuid
+ ,birthday
+ ,gc_date
+ INTO reserve
+ FROM exchange.reserves
+ WHERE reserves.reserve_pub=rpub;
+
+IF NOT FOUND
+THEN
+ -- reserve unknown
+ reserve_found=FALSE;
+ balance_ok=FALSE;
+ reserve_balance.frac = 0;
+ reserve_balance.val = 0;
+ age_ok=FALSE;
+ allowed_maximum_age=0;
+ ruuid=2;
+ RETURN;
+END IF;
+reserve_found=TRUE;
+reserve_balance = reserve.current_balance;
+ruuid = reserve.reserve_uuid;
+
+-- Check if age requirements are present
+IF ((NOT do_age_check) OR (reserve.birthday = 0))
+THEN
+ age_ok = TRUE;
+ allowed_maximum_age = -1;
+ELSE
+ -- Age requirements are formally not met: The exchange is setup to support
+ -- age restrictions (do_age_check == TRUE) and the reserve has a
+ -- birthday set (reserve_birthday != 0), but the client called the
+ -- batch-withdraw endpoint instead of the age-withdraw endpoint, which it
+ -- should have.
+ not_before=date '1970-01-01' + reserve.birthday;
+ allowed_maximum_age = extract(year from age(current_date, not_before));
+
+ balance_ok=FALSE;
+ age_ok = FALSE;
+ RETURN;
+END IF;
+
+
+-- Check reserve balance is sufficient.
+IF (reserve_balance.val > amount.val)
+THEN
+ IF (reserve_balance.frac >= amount.frac)
+ THEN
+ balance.val=reserve_balance.val - amount.val;
+ balance.frac=reserve_balance.frac - amount.frac;
+ ELSE
+ balance.val=reserve_balance.val - amount.val - 1;
+ balance.frac=reserve_balance.frac + 100000000 - amount.frac;
+ END IF;
+ELSE
+ IF (reserve_balance.val = amount.val) AND (reserve_balance.frac >= amount.frac)
+ THEN
+ balance.val=0;
+ balance.frac=reserve_balance.frac - amount.frac;
+ ELSE
+ balance_ok=FALSE;
+ RETURN;
+ END IF;
+END IF;
+
+-- Calculate new expiration dates.
+min_reserve_gc=GREATEST(min_reserve_gc,reserve.gc_date);
+
+-- Update reserve balance.
+UPDATE reserves SET
+ gc_date=min_reserve_gc
+ ,current_balance=balance
+WHERE
+ reserves.reserve_pub=rpub;
+
+balance_ok=TRUE;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_batch_withdraw(taler_amount, BYTEA, INT8, INT8, BOOLEAN)
+ IS 'Checks whether the reserve has sufficient balance for a withdraw operation (or the request is repeated and was previously approved) and that age requirements are formally met. If so updates the database with the result. Excludes storing the planchets.';
+
diff --git a/src/exchangedb/exchange_do_batch_withdraw_insert.sql b/src/exchangedb/exchange_do_batch_withdraw_insert.sql
new file mode 100644
index 000000000..d36181a6b
--- /dev/null
+++ b/src/exchangedb/exchange_do_batch_withdraw_insert.sql
@@ -0,0 +1,120 @@
+--
+-- 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_withdraw_insert(
+ IN cs_nonce BYTEA,
+ IN amount taler_amount,
+ IN h_denom_pub BYTEA, -- FIXME: denom_serials should really be a parameter to this FUNCTION.
+ IN ruuid INT8,
+ IN reserve_sig BYTEA,
+ IN h_coin_envelope BYTEA,
+ IN denom_sig BYTEA,
+ IN now INT8,
+ OUT out_denom_unknown BOOLEAN,
+ OUT out_nonce_reuse BOOLEAN,
+ OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ denom_serial INT8;
+BEGIN
+-- Shards: reserves by reserve_pub (SELECT)
+-- reserves_out (INSERT, with CONFLICT detection) by wih
+-- reserves by reserve_pub (UPDATE)
+-- reserves_in by reserve_pub (SELECT)
+-- wire_targets by wire_target_h_payto
+
+out_denom_unknown=TRUE;
+out_conflict=TRUE;
+out_nonce_reuse=TRUE;
+
+-- FIXME: denom_serials should really be a parameter to this FUNCTION.
+
+SELECT denominations_serial
+ INTO denom_serial
+ FROM exchange.denominations
+ WHERE denom_pub_hash=h_denom_pub;
+
+IF NOT FOUND
+THEN
+ -- denomination unknown, should be impossible!
+ out_denom_unknown=TRUE;
+ ASSERT false, 'denomination unknown';
+ RETURN;
+END IF;
+out_denom_unknown=FALSE;
+
+INSERT INTO exchange.reserves_out
+ (h_blind_ev
+ ,denominations_serial
+ ,denom_sig
+ ,reserve_uuid
+ ,reserve_sig
+ ,execution_date
+ ,amount_with_fee)
+VALUES
+ (h_coin_envelope
+ ,denom_serial
+ ,denom_sig
+ ,ruuid
+ ,reserve_sig
+ ,now
+ ,amount)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ out_conflict=TRUE;
+ RETURN;
+END IF;
+out_conflict=FALSE;
+
+-- Special actions needed for a CS withdraw?
+out_nonce_reuse=FALSE;
+IF NOT NULL cs_nonce
+THEN
+ -- Cache CS signature to prevent replays in the future
+ -- (and check if cached signature exists at the same time).
+ INSERT INTO exchange.cs_nonce_locks
+ (nonce
+ ,max_denomination_serial
+ ,op_hash)
+ VALUES
+ (cs_nonce
+ ,denom_serial
+ ,h_coin_envelope)
+ ON CONFLICT DO NOTHING;
+
+ IF NOT FOUND
+ THEN
+ -- See if the existing entry is identical.
+ SELECT 1
+ FROM exchange.cs_nonce_locks
+ WHERE nonce=cs_nonce
+ AND op_hash=h_coin_envelope;
+ IF NOT FOUND
+ THEN
+ out_nonce_reuse=TRUE;
+ ASSERT false, 'nonce reuse attempted by client';
+ RETURN;
+ END IF;
+ END IF;
+END IF;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_batch_withdraw_insert(BYTEA, taler_amount, BYTEA, INT8, BYTEA, BYTEA, BYTEA, INT8)
+ IS 'Stores information about a planchet for a batch withdraw operation. Checks if the planchet already exists, and in that case indicates a conflict';
diff --git a/src/exchangedb/exchange_do_deposit.sql b/src/exchangedb/exchange_do_deposit.sql
new file mode 100644
index 000000000..c89e9e470
--- /dev/null
+++ b/src/exchangedb/exchange_do_deposit.sql
@@ -0,0 +1,206 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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_deposit(
+ -- For batch_deposits
+ IN in_shard INT8,
+ IN in_merchant_pub BYTEA,
+ IN in_wallet_timestamp INT8,
+ IN in_exchange_timestamp INT8,
+ IN in_refund_deadline INT8,
+ IN in_wire_deadline INT8,
+ IN in_h_contract_terms BYTEA,
+ IN in_wallet_data_hash BYTEA, -- can be NULL
+ IN in_wire_salt BYTEA,
+ IN in_wire_target_h_payto BYTEA,
+ IN in_policy_details_serial_id INT8, -- can be NULL
+ IN in_policy_blocked BOOLEAN,
+ -- For wire_targets
+ IN in_receiver_wire_account TEXT,
+ -- For coin_deposits
+ IN ina_coin_pub BYTEA[],
+ IN ina_coin_sig BYTEA[],
+ IN ina_amount_with_fee taler_amount[],
+ OUT out_exchange_timestamp INT8,
+ OUT out_insufficient_balance_coin_index INT4, -- index of coin with bad balance, NULL if none
+ OUT out_conflict BOOL
+ )
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ wtsi INT8; -- wire target serial id
+ bdsi INT8; -- batch_deposits serial id
+ i INT4;
+ ini_amount_with_fee taler_amount;
+ ini_coin_pub BYTEA;
+ ini_coin_sig BYTEA;
+BEGIN
+-- Shards:
+-- INSERT wire_targets (by h_payto), ON CONFLICT DO NOTHING;
+-- INSERT batch_deposits (by shard, merchant_pub), ON CONFLICT idempotency check;
+-- INSERT[] coin_deposits (by coin_pub), ON CONFLICT idempotency check;
+-- UPDATE[] known_coins (by coin_pub)
+
+
+-- First, get or create the 'wtsi'
+INSERT INTO wire_targets
+ (wire_target_h_payto
+ ,payto_uri)
+ VALUES
+ (in_wire_target_h_payto
+ ,in_receiver_wire_account)
+ ON CONFLICT DO NOTHING -- for CONFLICT ON (wire_target_h_payto)
+ RETURNING
+ wire_target_serial_id
+ INTO
+ wtsi;
+
+IF NOT FOUND
+THEN
+ SELECT
+ wire_target_serial_id
+ INTO
+ wtsi
+ FROM wire_targets
+ WHERE
+ wire_target_h_payto=in_wire_target_h_payto;
+END IF;
+
+
+-- Second, create the batch_deposits entry
+INSERT INTO batch_deposits
+ (shard
+ ,merchant_pub
+ ,wallet_timestamp
+ ,exchange_timestamp
+ ,refund_deadline
+ ,wire_deadline
+ ,h_contract_terms
+ ,wallet_data_hash
+ ,wire_salt
+ ,wire_target_h_payto
+ ,policy_details_serial_id
+ ,policy_blocked
+ )
+ VALUES
+ (in_shard
+ ,in_merchant_pub
+ ,in_wallet_timestamp
+ ,in_exchange_timestamp
+ ,in_refund_deadline
+ ,in_wire_deadline
+ ,in_h_contract_terms
+ ,in_wallet_data_hash
+ ,in_wire_salt
+ ,in_wire_target_h_payto
+ ,in_policy_details_serial_id
+ ,in_policy_blocked)
+ ON CONFLICT DO NOTHING -- for CONFLICT ON (merchant_pub, h_contract_terms)
+ RETURNING
+ batch_deposit_serial_id
+ INTO
+ bdsi;
+
+IF NOT FOUND
+THEN
+ -- Idempotency check: see if an identical record exists.
+ -- We do select over merchant_pub, h_contract_terms and wire_target_h_payto
+ -- first to maximally increase the chance of using the existing index.
+ SELECT
+ exchange_timestamp
+ ,batch_deposit_serial_id
+ INTO
+ out_exchange_timestamp
+ ,bdsi
+ FROM batch_deposits
+ WHERE shard=in_shard
+ AND merchant_pub=in_merchant_pub
+ AND h_contract_terms=in_h_contract_terms
+ AND wire_target_h_payto=in_wire_target_h_payto
+ -- now check the rest, too
+ AND ( (wallet_data_hash=in_wallet_data_hash) OR
+ (wallet_data_hash IS NULL AND in_wallet_data_hash IS NULL) )
+ AND wire_salt=in_wire_salt
+ AND wallet_timestamp=in_wallet_timestamp
+ AND refund_deadline=in_refund_deadline
+ AND wire_deadline=in_wire_deadline
+ AND ( (policy_details_serial_id=in_policy_details_serial_id) OR
+ (policy_details_serial_id IS NULL AND in_policy_details_serial_id IS NULL) );
+ IF NOT FOUND
+ THEN
+ -- Deposit exists, but with *strange* differences. Not allowed.
+ out_conflict=TRUE;
+ RETURN;
+ END IF;
+END IF;
+
+out_conflict=FALSE;
+
+-- Deposit each coin
+
+FOR i IN 1..array_length(ina_coin_pub,1)
+LOOP
+ ini_coin_pub = ina_coin_pub[i];
+ ini_coin_sig = ina_coin_sig[i];
+ ini_amount_with_fee = ina_amount_with_fee[i];
+
+ INSERT INTO coin_deposits
+ (batch_deposit_serial_id
+ ,coin_pub
+ ,coin_sig
+ ,amount_with_fee
+ )
+ VALUES
+ (bdsi
+ ,ini_coin_pub
+ ,ini_coin_sig
+ ,ini_amount_with_fee
+ )
+ ON CONFLICT DO NOTHING;
+
+ IF FOUND
+ THEN
+ -- Insert did happen, update balance in known_coins!
+
+ UPDATE known_coins kc
+ SET
+ remaining.frac=(kc.remaining).frac-ini_amount_with_fee.frac
+ + CASE
+ WHEN (kc.remaining).frac < ini_amount_with_fee.frac
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val-ini_amount_with_fee.val
+ - CASE
+ WHEN (kc.remaining).frac < ini_amount_with_fee.frac
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub=ini_coin_pub
+ AND ( ((kc.remaining).val > ini_amount_with_fee.val) OR
+ ( ((kc.remaining).frac >= ini_amount_with_fee.frac) AND
+ ((kc.remaining).val >= ini_amount_with_fee.val) ) );
+
+ IF NOT FOUND
+ THEN
+ -- Insufficient balance.
+ -- Note: C arrays are 0 indexed, but i started at 1
+ out_insufficient_balance_coin_index=i-1;
+ RETURN;
+ END IF;
+ END IF;
+END LOOP; -- end FOR all coins
+
+END $$;
diff --git a/src/exchangedb/exchange_do_expire_purse.sql b/src/exchangedb/exchange_do_expire_purse.sql
new file mode 100644
index 000000000..ee9757f03
--- /dev/null
+++ b/src/exchangedb/exchange_do_expire_purse.sql
@@ -0,0 +1,98 @@
+--
+-- 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_expire_purse(
+ IN in_start_time INT8,
+ IN in_end_time INT8,
+ IN in_now INT8,
+ OUT out_found BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_purse_pub BYTEA;
+DECLARE
+ my_deposit record;
+DECLARE
+ my_in_reserve_quota BOOLEAN;
+BEGIN
+
+-- FIXME: we should probably do this in a loop
+-- and expire all at once, instead of one per query
+SELECT purse_pub
+ ,in_reserve_quota
+ INTO my_purse_pub
+ ,my_in_reserve_quota
+ FROM purse_requests
+ WHERE (purse_expiration >= in_start_time) AND
+ (purse_expiration < in_end_time) AND
+ NOT was_decided
+ ORDER BY purse_expiration ASC
+ LIMIT 1;
+out_found = FOUND;
+IF NOT FOUND
+THEN
+ RETURN;
+END IF;
+
+INSERT INTO purse_decision
+ (purse_pub
+ ,action_timestamp
+ ,refunded)
+VALUES
+ (my_purse_pub
+ ,in_now
+ ,TRUE);
+
+-- Code for 'TALER_DBEVENT_EXCHANGE_PURSE_REFUNDED'
+NOTIFY X8DJSPNYJMNZDAP7GN6YQ4EZVSQXMF3HRP4VAR347WP9SZYP1C200;
+
+IF (my_in_reserve_quota)
+THEN
+ UPDATE reserves
+ SET purses_active=purses_active-1
+ WHERE reserve_pub IN
+ (SELECT reserve_pub
+ FROM exchange.purse_merges
+ WHERE purse_pub=my_purse_pub
+ LIMIT 1);
+END IF;
+
+-- restore balance to each coin deposited into the purse
+FOR my_deposit IN
+ SELECT coin_pub
+ ,amount_with_fee
+ FROM purse_deposits
+ WHERE purse_pub = my_purse_pub
+LOOP
+ UPDATE known_coins kc SET
+ remaining.frac=(kc.remaining).frac+(my_deposit.amount_with_fee).frac
+ - CASE
+ WHEN (kc.remaining).frac+(my_deposit.amount_with_fee).frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val+(my_deposit.amount_with_fee).val
+ + CASE
+ WHEN (kc.remaining).frac+(my_deposit.amount_with_fee).frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub = my_deposit.coin_pub;
+ END LOOP;
+END $$;
+
+COMMENT ON FUNCTION exchange_do_expire_purse(INT8,INT8,INT8)
+ IS 'Finds an expired purse in the given time range and refunds the coins (if any).';
diff --git a/src/exchangedb/exchange_do_gc.sql b/src/exchangedb/exchange_do_gc.sql
new file mode 100644
index 000000000..d4ecb3024
--- /dev/null
+++ b/src/exchangedb/exchange_do_gc.sql
@@ -0,0 +1,140 @@
+--
+-- 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 PROCEDURE exchange_do_gc(
+ IN in_ancient_date INT8,
+ IN in_now INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ reserve_uuid_min INT8; -- minimum reserve UUID still alive
+ melt_min INT8; -- minimum melt still alive
+ coin_min INT8; -- minimum known_coin still alive
+ batch_deposit_min INT8; -- minimum deposit still alive
+ reserve_out_min INT8; -- minimum reserve_out still alive
+ denom_min INT8; -- minimum denomination still alive
+BEGIN
+
+DELETE FROM prewire
+ WHERE finished=TRUE;
+
+DELETE FROM wire_fee
+ WHERE end_date < in_ancient_date;
+
+-- FIXME: use closing fee as threshold?
+DELETE FROM reserves
+ WHERE gc_date < in_now
+ AND current_balance = (0, 0);
+
+SELECT
+ reserve_out_serial_id
+ INTO
+ reserve_out_min
+ FROM reserves_out
+ ORDER BY reserve_out_serial_id ASC
+ LIMIT 1;
+
+DELETE FROM recoup
+ WHERE reserve_out_serial_id < reserve_out_min;
+
+SELECT
+ reserve_uuid
+ INTO
+ reserve_uuid_min
+ FROM reserves
+ ORDER BY reserve_uuid ASC
+ LIMIT 1;
+
+DELETE FROM reserves_out
+ WHERE reserve_uuid < reserve_uuid_min;
+
+-- FIXME: this query will be horribly slow;
+-- need to find another way to formulate it...
+DELETE FROM denominations
+ WHERE expire_legal < in_now
+ AND denominations_serial NOT IN
+ (SELECT DISTINCT denominations_serial
+ FROM reserves_out)
+ AND denominations_serial NOT IN
+ (SELECT DISTINCT denominations_serial
+ FROM known_coins
+ WHERE coin_pub IN
+ (SELECT DISTINCT coin_pub
+ FROM recoup))
+ AND denominations_serial NOT IN
+ (SELECT DISTINCT denominations_serial
+ FROM known_coins
+ WHERE coin_pub IN
+ (SELECT DISTINCT coin_pub
+ FROM recoup_refresh));
+
+SELECT
+ melt_serial_id
+ INTO
+ melt_min
+ FROM refresh_commitments
+ ORDER BY melt_serial_id ASC
+ LIMIT 1;
+
+DELETE FROM refresh_revealed_coins
+ WHERE melt_serial_id < melt_min;
+
+DELETE FROM refresh_transfer_keys
+ WHERE melt_serial_id < melt_min;
+
+SELECT
+ known_coin_id
+ INTO
+ coin_min
+ FROM known_coins
+ ORDER BY known_coin_id ASC
+ LIMIT 1;
+
+DELETE FROM recoup_refresh
+ WHERE known_coin_id < coin_min;
+
+DELETE FROM batch_deposits
+ WHERE wire_deadline < in_ancient_date;
+
+SELECT
+ batch_deposit_serial_id
+ INTO
+ batch_deposit_min
+ FROM coin_deposits
+ ORDER BY batch_deposit_serial_id ASC
+ LIMIT 1;
+
+DELETE FROM refunds
+ WHERE batch_deposit_serial_id < batch_deposit_min;
+DELETE FROM aggregation_tracking
+ WHERE batch_deposit_serial_id < batch_deposit_min;
+DELETE FROM coin_deposits
+ WHERE batch_deposit_serial_id < batch_deposit_min;
+
+
+
+SELECT
+ denominations_serial
+ INTO
+ denom_min
+ FROM denominations
+ ORDER BY denominations_serial ASC
+ LIMIT 1;
+
+DELETE FROM cs_nonce_locks
+ WHERE max_denomination_serial <= denom_min;
+
+END $$;
diff --git a/src/exchangedb/exchange_do_get_link_data.sql b/src/exchangedb/exchange_do_get_link_data.sql
new file mode 100644
index 000000000..08611a5ea
--- /dev/null
+++ b/src/exchangedb/exchange_do_get_link_data.sql
@@ -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 $$;
diff --git a/src/exchangedb/exchange_do_insert_aml_decision.sql b/src/exchangedb/exchange_do_insert_aml_decision.sql
new file mode 100644
index 000000000..c8ed7e928
--- /dev/null
+++ b/src/exchangedb/exchange_do_insert_aml_decision.sql
@@ -0,0 +1,127 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_insert_aml_decision(
+ IN in_h_payto BYTEA,
+ IN in_new_threshold taler_amount,
+ IN in_new_status INT4,
+ IN in_decision_time INT8,
+ IN in_justification TEXT,
+ IN in_decider_pub BYTEA,
+ IN in_decider_sig BYTEA,
+ IN in_notify_s TEXT,
+ IN in_kyc_requirements TEXT,
+ IN in_requirement_row INT8,
+ OUT out_invalid_officer BOOLEAN,
+ OUT out_last_date INT8)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+-- Check officer is eligible to make decisions.
+PERFORM
+ FROM aml_staff
+ WHERE decider_pub=in_decider_pub
+ AND is_active
+ AND NOT read_only;
+IF NOT FOUND
+THEN
+ out_invalid_officer=TRUE;
+ out_last_date=0;
+ RETURN;
+END IF;
+out_invalid_officer=FALSE;
+
+-- Check no more recent decision exists.
+SELECT decision_time
+ INTO out_last_date
+ FROM aml_history
+ WHERE h_payto=in_h_payto
+ ORDER BY decision_time DESC;
+IF FOUND
+THEN
+ IF out_last_date >= in_decision_time
+ THEN
+ -- Refuse to insert older decision.
+ RETURN;
+ END IF;
+ UPDATE aml_status
+ SET threshold=in_new_threshold
+ ,status=in_new_status
+ ,kyc_requirement=in_requirement_row
+ WHERE h_payto=in_h_payto;
+ ASSERT FOUND, 'cannot have AML decision history but no AML status';
+ELSE
+ out_last_date = 0;
+ INSERT INTO aml_status
+ (h_payto
+ ,threshold
+ ,status
+ ,kyc_requirement)
+ VALUES
+ (in_h_payto
+ ,in_new_threshold
+ ,in_new_status
+ ,in_requirement_row)
+ ON CONFLICT (h_payto) DO
+ UPDATE SET
+ threshold=in_new_threshold
+ ,status=in_new_status;
+END IF;
+
+
+INSERT INTO aml_history
+ (h_payto
+ ,new_threshold
+ ,new_status
+ ,decision_time
+ ,justification
+ ,kyc_requirements
+ ,kyc_req_row
+ ,decider_pub
+ ,decider_sig
+ ) VALUES
+ (in_h_payto
+ ,in_new_threshold
+ ,in_new_status
+ ,in_decision_time
+ ,in_justification
+ ,in_kyc_requirements
+ ,in_requirement_row
+ ,in_decider_pub
+ ,in_decider_sig);
+
+
+-- wake up taler-exchange-aggregator
+IF 0 = in_new_status
+THEN
+ INSERT INTO kyc_alerts
+ (h_payto
+ ,trigger_type)
+ VALUES
+ (in_h_payto,1);
+
+ EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,in_notify_s);
+
+END IF;
+
+
+END $$;
+
+
+COMMENT ON FUNCTION exchange_do_insert_aml_decision(BYTEA, taler_amount, INT4, INT8, TEXT, BYTEA, BYTEA, TEXT, TEXT, INT8)
+ IS 'Checks whether the AML officer is eligible to make AML decisions and if so inserts the decision into the table';
diff --git a/src/exchangedb/exchange_do_insert_aml_officer.sql b/src/exchangedb/exchange_do_insert_aml_officer.sql
new file mode 100644
index 000000000..429ba11c7
--- /dev/null
+++ b/src/exchangedb/exchange_do_insert_aml_officer.sql
@@ -0,0 +1,74 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_insert_aml_officer(
+ IN in_decider_pub BYTEA,
+ IN in_master_sig BYTEA,
+ IN in_decider_name TEXT,
+ IN in_is_active BOOLEAN,
+ IN in_read_only BOOLEAN,
+ IN in_last_change INT8,
+ OUT out_last_change INT8)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+INSERT INTO exchange.aml_staff
+ (decider_pub
+ ,master_sig
+ ,decider_name
+ ,is_active
+ ,read_only
+ ,last_change
+ ) VALUES
+ (in_decider_pub
+ ,in_master_sig
+ ,in_decider_name
+ ,in_is_active
+ ,in_read_only
+ ,in_last_change)
+ ON CONFLICT DO NOTHING;
+IF FOUND
+THEN
+ out_last_change=0;
+ RETURN;
+END IF;
+
+-- Check update is most recent...
+SELECT last_change
+ INTO out_last_change
+ FROM exchange.aml_staff
+ WHERE decider_pub=in_decider_pub;
+ASSERT FOUND, 'cannot have INSERT conflict but no AML staff record';
+
+IF out_last_change >= in_last_change
+THEN
+ -- Refuse to insert older status
+ RETURN;
+END IF;
+
+-- We are more recent, update existing record.
+UPDATE exchange.aml_staff
+ SET master_sig=in_master_sig
+ ,decider_name=in_decider_name
+ ,is_active=in_is_active
+ ,read_only=in_read_only
+ ,last_change=in_last_change
+ WHERE decider_pub=in_decider_pub;
+END $$;
+
+
+COMMENT ON FUNCTION exchange_do_insert_aml_officer(BYTEA, BYTEA, TEXT, BOOL, BOOL, INT8)
+ IS 'Inserts or updates AML staff record, making sure the update is more recent than the previous change';
diff --git a/src/exchangedb/exchange_do_insert_kyc_attributes.sql b/src/exchangedb/exchange_do_insert_kyc_attributes.sql
new file mode 100644
index 000000000..7db4d80c0
--- /dev/null
+++ b/src/exchangedb/exchange_do_insert_kyc_attributes.sql
@@ -0,0 +1,114 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_insert_kyc_attributes(
+ IN in_process_row INT8,
+ IN in_h_payto BYTEA,
+ IN in_kyc_prox BYTEA,
+ IN in_provider_section TEXT,
+ IN in_satisfied_checks TEXT[],
+ IN in_birthday INT4,
+ IN in_provider_account_id TEXT,
+ IN in_provider_legitimization_id TEXT,
+ IN in_collection_time_ts INT8,
+ IN in_expiration_time INT8,
+ IN in_expiration_time_ts INT8,
+ IN in_enc_attributes BYTEA,
+ IN in_require_aml BOOLEAN,
+ IN in_kyc_completed_notify_s TEXT,
+ OUT out_ok BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ orig_reserve_pub BYTEA;
+ orig_reserve_found BOOLEAN;
+BEGIN
+
+INSERT INTO exchange.kyc_attributes
+ (h_payto
+ ,kyc_prox
+ ,provider
+ ,satisfied_checks
+ ,collection_time
+ ,expiration_time
+ ,encrypted_attributes
+ ,legitimization_serial
+ ) VALUES
+ (in_h_payto
+ ,in_kyc_prox
+ ,in_provider_section
+ ,in_satisfied_checks
+ ,in_collection_time_ts
+ ,in_expiration_time_ts
+ ,in_enc_attributes
+ ,in_process_row);
+
+UPDATE legitimization_processes
+ SET provider_user_id=in_provider_account_id
+ ,provider_legitimization_id=in_provider_legitimization_id
+ ,expiration_time=GREATEST(expiration_time,in_expiration_time)
+ ,finished=TRUE
+ WHERE h_payto=in_h_payto
+ AND legitimization_process_serial_id=in_process_row
+ AND provider_section=in_provider_section;
+out_ok = FOUND;
+
+
+-- If the h_payto refers to a reserve in the original requirements
+-- update the originating reserve's birthday.
+SELECT reserve_pub
+ INTO orig_reserve_pub
+ FROM exchange.legitimization_requirements
+ WHERE h_payto=in_h_payto
+ AND NOT reserve_pub IS NULL;
+orig_reserve_found = FOUND;
+
+IF orig_reserve_found
+THEN
+ UPDATE exchange.reserves
+ SET birthday=in_birthday
+ WHERE reserve_pub=orig_reserve_pub;
+END IF;
+
+IF in_require_aml
+THEN
+ INSERT INTO exchange.aml_status
+ (h_payto
+ ,status)
+ VALUES
+ (in_h_payto
+ ,1)
+ ON CONFLICT (h_payto) DO
+ UPDATE SET status=EXCLUDED.status | 1;
+END IF;
+
+EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,in_kyc_completed_notify_s);
+
+
+INSERT INTO kyc_alerts
+ (h_payto
+ ,trigger_type)
+ VALUES
+ (in_h_payto,1);
+
+
+END $$;
+
+
+COMMENT ON FUNCTION exchange_do_insert_kyc_attributes(INT8, BYTEA, BYTEA, TEXT, TEXT[], INT4, TEXT, TEXT, INT8, INT8, INT8, BYTEA, BOOL, TEXT)
+ IS 'Inserts new KYC attributes and updates the status of the legitimization process and the AML status for the account';
diff --git a/src/exchangedb/exchange_do_insert_or_update_policy_details.sql b/src/exchangedb/exchange_do_insert_or_update_policy_details.sql
new file mode 100644
index 000000000..85e52d3d3
--- /dev/null
+++ b/src/exchangedb/exchange_do_insert_or_update_policy_details.sql
@@ -0,0 +1,114 @@
+--
+-- 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_insert_or_update_policy_details(
+ IN in_policy_hash_code BYTEA,
+ IN in_policy_json TEXT,
+ IN in_deadline INT8,
+ IN in_commitment taler_amount,
+ IN in_accumulated_total taler_amount,
+ IN in_fee taler_amount,
+ IN in_transferable taler_amount,
+ IN in_fulfillment_state SMALLINT,
+ OUT out_policy_details_serial_id INT8,
+ OUT out_accumulated_total taler_amount,
+ OUT out_fulfillment_state SMALLINT)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ cur_commitment taler_amount;
+DECLARE
+ cur_accumulated_total taler_amount;
+DECLARE
+ rval RECORD;
+BEGIN
+ -- First, try to create a new entry.
+ INSERT INTO policy_details
+ (policy_hash_code,
+ policy_json,
+ deadline,
+ commitment,
+ accumulated_total,
+ fee,
+ transferable,
+ fulfillment_state)
+ VALUES (in_policy_hash_code,
+ in_policy_json,
+ in_deadline,
+ in_commitment,
+ in_accumulated_total,
+ in_fee,
+ in_transferable,
+ in_fulfillment_state)
+ ON CONFLICT (policy_hash_code) DO NOTHING
+ RETURNING policy_details_serial_id INTO out_policy_details_serial_id;
+
+ -- If the insert was successful, return
+ -- We assume that the fullfilment_state was correct in first place.
+ IF FOUND THEN
+ out_accumulated_total = in_accumulated_total;
+ out_fulfillment_state = in_fulfillment_state;
+ RETURN;
+ END IF;
+
+ -- We had a conflict, grab the parts we need to update.
+ SELECT policy_details_serial_id
+ ,commitment
+ ,accumulated_total
+ INTO rval
+ FROM policy_details
+ WHERE policy_hash_code = in_policy_hash_code;
+
+ -- We use rval as workaround as we cannot select
+ -- directly into the amount due to Postgres limitations.
+ out_policy_details_serial_id := rval.policy_details_serial_id;
+ cur_commitment := rval.commitment;
+ cur_accumulated_total := rval.accumulated_total;
+
+ -- calculate the new values (overflows throws exception)
+ out_accumulated_total.val = cur_accumulated_total.val + in_accumulated_total.val;
+ out_accumulated_total.frac = cur_accumulated_total.frac + in_accumulated_total.frac;
+ -- normalize
+ out_accumulated_total.val = out_accumulated_total.val + out_accumulated_total.frac / 100000000;
+ out_accumulated_total.frac = out_accumulated_total.frac % 100000000;
+
+ IF (out_accumulated_total.val > (1 << 52))
+ THEN
+ RAISE EXCEPTION 'accumulation overflow';
+ END IF;
+
+
+ -- Set the fulfillment_state according to the values.
+ -- For now, we only update the state when it was INSUFFICIENT.
+ -- FIXME[oec] #7999: What to do in case of Failure or other state?
+ IF (out_fullfillment_state = 2) -- INSUFFICIENT
+ THEN
+ IF (out_accumulated_total.val >= cur_commitment.val OR
+ (out_accumulated_total.val = cur_commitment.val AND
+ out_accumulated_total.frac >= cur_commitment.frac))
+ THEN
+ out_fulfillment_state = 3; -- READY
+ END IF;
+ END IF;
+
+ -- Now, update the record
+ UPDATE exchange.policy_details
+ SET
+ accumulated = out_accumulated_total,
+ fulfillment_state = out_fulfillment_state
+ WHERE
+ policy_details_serial_id = out_policy_details_serial_id;
+END $$;
diff --git a/src/exchangedb/exchange_do_melt.sql b/src/exchangedb/exchange_do_melt.sql
new file mode 100644
index 000000000..0200986fa
--- /dev/null
+++ b/src/exchangedb/exchange_do_melt.sql
@@ -0,0 +1,182 @@
+--
+-- 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_melt(
+ IN in_cs_rms BYTEA,
+ IN in_amount_with_fee taler_amount,
+ IN in_rc BYTEA,
+ IN in_old_coin_pub BYTEA,
+ IN in_old_coin_sig BYTEA,
+ IN in_known_coin_id INT8, -- not used, but that's OK
+ IN in_noreveal_index INT4,
+ IN in_zombie_required BOOLEAN,
+ OUT out_balance_ok BOOLEAN,
+ OUT out_zombie_bad BOOLEAN,
+ OUT out_noreveal_index INT4)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ denom_max INT8;
+BEGIN
+-- Shards: INSERT refresh_commitments (by rc)
+-- (rare:) SELECT refresh_commitments (by old_coin_pub) -- crosses shards!
+-- (rare:) SEELCT refresh_revealed_coins (by melt_serial_id)
+-- (rare:) PERFORM recoup_refresh (by rrc_serial) -- crosses shards!
+-- UPDATE known_coins (by coin_pub)
+
+INSERT INTO exchange.refresh_commitments
+ (rc
+ ,old_coin_pub
+ ,old_coin_sig
+ ,amount_with_fee
+ ,noreveal_index
+ )
+ VALUES
+ (in_rc
+ ,in_old_coin_pub
+ ,in_old_coin_sig
+ ,in_amount_with_fee
+ ,in_noreveal_index)
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Idempotency check: see if an identical record exists.
+ out_noreveal_index=-1;
+ SELECT
+ noreveal_index
+ INTO
+ out_noreveal_index
+ FROM exchange.refresh_commitments
+ WHERE rc=in_rc;
+ out_balance_ok=FOUND;
+ out_zombie_bad=FALSE; -- zombie is OK
+ RETURN;
+END IF;
+
+
+IF in_zombie_required
+THEN
+ -- Check if this coin was part of a refresh
+ -- operation that was subsequently involved
+ -- in a recoup operation. We begin by all
+ -- refresh operations our coin was involved
+ -- with, then find all associated reveal
+ -- operations, and then see if any of these
+ -- reveal operations was involved in a recoup.
+ PERFORM
+ FROM recoup_refresh
+ WHERE rrc_serial IN
+ (SELECT rrc_serial
+ FROM refresh_revealed_coins
+ WHERE melt_serial_id IN
+ (SELECT melt_serial_id
+ FROM refresh_commitments
+ WHERE old_coin_pub=in_old_coin_pub));
+ IF NOT FOUND
+ THEN
+ out_zombie_bad=TRUE;
+ out_balance_ok=FALSE;
+ RETURN;
+ END IF;
+END IF;
+
+out_zombie_bad=FALSE; -- zombie is OK
+
+
+-- Check and update balance of the coin.
+UPDATE known_coins kc
+ SET
+ remaining.frac=(kc.remaining).frac-in_amount_with_fee.frac
+ + CASE
+ WHEN (kc.remaining).frac < in_amount_with_fee.frac
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val-in_amount_with_fee.val
+ - CASE
+ WHEN (kc.remaining).frac < in_amount_with_fee.frac
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub=in_old_coin_pub
+ AND ( ((kc.remaining).val > in_amount_with_fee.val) OR
+ ( ((kc.remaining).frac >= in_amount_with_fee.frac) AND
+ ((kc.remaining).val >= in_amount_with_fee.val) ) );
+
+IF NOT FOUND
+THEN
+ -- Insufficient balance.
+ out_noreveal_index=-1;
+ out_balance_ok=FALSE;
+ RETURN;
+END IF;
+
+
+
+-- Special actions needed for a CS melt?
+IF NOT NULL in_cs_rms
+THEN
+ -- Get maximum denominations serial value in
+ -- existence, this will determine how long the
+ -- nonce will be locked.
+ SELECT
+ denominations_serial
+ INTO
+ denom_max
+ FROM exchange.denominations
+ ORDER BY denominations_serial DESC
+ LIMIT 1;
+
+ -- Cache CS signature to prevent replays in the future
+ -- (and check if cached signature exists at the same time).
+ INSERT INTO exchange.cs_nonce_locks
+ (nonce
+ ,max_denomination_serial
+ ,op_hash)
+ VALUES
+ (cs_rms
+ ,denom_serial
+ ,in_rc)
+ ON CONFLICT DO NOTHING;
+
+ IF NOT FOUND
+ THEN
+ -- Record exists, make sure it is the same
+ SELECT 1
+ FROM exchange.cs_nonce_locks
+ WHERE nonce=cs_rms
+ AND op_hash=in_rc;
+
+ IF NOT FOUND
+ THEN
+ -- Nonce reuse detected
+ out_balance_ok=FALSE;
+ out_zombie_bad=FALSE;
+ out_noreveal_index=42; -- FIXME: return error message more nicely!
+ ASSERT false, 'nonce reuse attempted by client';
+ END IF;
+ END IF;
+END IF;
+
+-- Everything fine, return success!
+out_balance_ok=TRUE;
+out_noreveal_index=in_noreveal_index;
+
+END $$;
diff --git a/src/exchangedb/exchange_do_purse_delete.sql b/src/exchangedb/exchange_do_purse_delete.sql
new file mode 100644
index 000000000..5668f7bec
--- /dev/null
+++ b/src/exchangedb/exchange_do_purse_delete.sql
@@ -0,0 +1,118 @@
+--
+-- 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_purse_delete(
+ IN in_purse_pub BYTEA,
+ IN in_purse_sig BYTEA,
+ IN in_now INT8,
+ OUT out_decided BOOLEAN,
+ OUT out_found BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_deposit record;
+DECLARE
+ my_in_reserve_quota BOOLEAN;
+BEGIN
+
+PERFORM refunded FROM purse_decision
+ WHERE purse_pub=in_purse_pub;
+IF FOUND
+THEN
+ out_found=TRUE;
+ out_decided=TRUE;
+ RETURN;
+END IF;
+out_decided=FALSE;
+
+SELECT in_reserve_quota
+ INTO my_in_reserve_quota
+ FROM exchange.purse_requests
+ WHERE purse_pub=in_purse_pub;
+out_found=FOUND;
+IF NOT FOUND
+THEN
+ RETURN;
+END IF;
+
+-- store reserve deletion
+INSERT INTO exchange.purse_deletion
+ (purse_pub
+ ,purse_sig)
+VALUES
+ (in_purse_pub
+ ,in_purse_sig)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ RETURN;
+END IF;
+
+-- Delete contract associated with purse, if it exists.
+DELETE FROM contracts
+ WHERE purse_pub=in_purse_pub;
+
+-- store purse decision
+INSERT INTO purse_decision
+ (purse_pub
+ ,action_timestamp
+ ,refunded)
+VALUES
+ (in_purse_pub
+ ,in_now
+ ,TRUE);
+
+-- update purse quota at reserve
+IF (my_in_reserve_quota)
+THEN
+ UPDATE reserves
+ SET purses_active=purses_active-1
+ WHERE reserve_pub IN
+ (SELECT reserve_pub
+ FROM exchange.purse_merges
+ WHERE purse_pub=in_purse_pub
+ LIMIT 1);
+END IF;
+
+-- restore balance to each coin deposited into the purse
+FOR my_deposit IN
+ SELECT coin_pub
+ ,amount_with_fee
+ FROM exchange.purse_deposits
+ WHERE purse_pub = in_purse_pub
+LOOP
+ UPDATE known_coins kc SET
+ remaining.frac=(kc.remaining).frac+(my_deposit.amount_with_fee).frac
+ - CASE
+ WHEN (kc.remaining).frac+(my_deposit.amount_with_fee).frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val+(my_deposit.amount_with_fee).val
+ + CASE
+ WHEN (kc.remaining).frac+(my_deposit.amount_with_fee).frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub = my_deposit.coin_pub;
+END LOOP;
+
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_purse_delete(BYTEA,BYTEA,INT8)
+ IS 'Delete a previously undecided purse and refund the coins (if any).';
diff --git a/src/exchangedb/exchange_do_purse_deposit.sql b/src/exchangedb/exchange_do_purse_deposit.sql
new file mode 100644
index 000000000..49d3c919b
--- /dev/null
+++ b/src/exchangedb/exchange_do_purse_deposit.sql
@@ -0,0 +1,267 @@
+--
+-- 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_purse_deposit(
+ IN in_partner_id INT8,
+ IN in_purse_pub BYTEA,
+ IN in_amount_with_fee taler_amount,
+ IN in_coin_pub BYTEA,
+ IN in_coin_sig BYTEA,
+ IN in_amount_without_fee taler_amount,
+ IN in_reserve_expiration INT8,
+ IN in_now INT8,
+ OUT out_balance_ok BOOLEAN,
+ OUT out_late BOOLEAN,
+ OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ was_merged BOOLEAN;
+DECLARE
+ psi INT8; -- partner's serial ID (set if merged)
+DECLARE
+ my_amount taler_amount; -- total in purse
+DECLARE
+ was_paid BOOLEAN;
+DECLARE
+ my_in_reserve_quota BOOLEAN;
+DECLARE
+ my_reserve_pub BYTEA;
+DECLARE
+ rval RECORD;
+BEGIN
+
+-- Store the deposit request.
+INSERT INTO purse_deposits
+ (partner_serial_id
+ ,purse_pub
+ ,coin_pub
+ ,amount_with_fee
+ ,coin_sig)
+ VALUES
+ (in_partner_id
+ ,in_purse_pub
+ ,in_coin_pub
+ ,in_amount_with_fee
+ ,in_coin_sig)
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Idempotency check: check if coin_sig is the same,
+ -- if so, success, otherwise conflict!
+
+ PERFORM
+ FROM purse_deposits
+ WHERE purse_pub = in_purse_pub
+ AND coin_pub = in_coin_pub
+ AND coin_sig = in_coin_sig;
+ IF NOT FOUND
+ THEN
+ -- Deposit exists, but with differences. Not allowed.
+ out_balance_ok=FALSE;
+ out_late=FALSE;
+ out_conflict=TRUE;
+ RETURN;
+ ELSE
+ -- Deposit exists, do not count for balance. Allow.
+ out_late=FALSE;
+ out_balance_ok=TRUE;
+ out_conflict=FALSE;
+ RETURN;
+ END IF;
+END IF;
+
+
+-- Check if purse was deleted, if so, abort and prevent deposit.
+PERFORM
+ FROM exchange.purse_deletion
+ WHERE purse_pub = in_purse_pub;
+IF FOUND
+THEN
+ out_late=TRUE;
+ out_balance_ok=FALSE;
+ out_conflict=FALSE;
+ RETURN;
+END IF;
+
+
+-- Debit the coin
+-- Check and update balance of the coin.
+UPDATE known_coins kc
+ SET
+ remaining.frac=(kc.remaining).frac-in_amount_with_fee.frac
+ + CASE
+ WHEN (kc.remaining).frac < in_amount_with_fee.frac
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val-in_amount_with_fee.val
+ - CASE
+ WHEN (kc.remaining).frac < in_amount_with_fee.frac
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub=in_coin_pub
+ AND ( ((kc.remaining).val > in_amount_with_fee.val) OR
+ ( ((kc.remaining).frac >= in_amount_with_fee.frac) AND
+ ((kc.remaining).val >= in_amount_with_fee.val) ) );
+
+IF NOT FOUND
+THEN
+ -- Insufficient balance.
+ out_balance_ok=FALSE;
+ out_late=FALSE;
+ out_conflict=FALSE;
+ RETURN;
+END IF;
+
+
+-- Credit the purse.
+UPDATE purse_requests pr
+ SET
+ balance.frac=(pr.balance).frac+in_amount_without_fee.frac
+ - CASE
+ WHEN (pr.balance).frac+in_amount_without_fee.frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END,
+ balance.val=(pr.balance).val+in_amount_without_fee.val
+ + CASE
+ WHEN (pr.balance).frac+in_amount_without_fee.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ WHERE purse_pub=in_purse_pub;
+
+out_conflict=FALSE;
+out_balance_ok=TRUE;
+
+-- See if we can finish the merge or need to update the trigger time and partner.
+SELECT COALESCE(partner_serial_id,0)
+ ,reserve_pub
+ INTO psi
+ ,my_reserve_pub
+ FROM purse_merges
+ WHERE purse_pub=in_purse_pub;
+
+IF NOT FOUND
+THEN
+ -- Purse was not yet merged. We are done.
+ out_late=FALSE;
+ RETURN;
+END IF;
+
+SELECT
+ amount_with_fee
+ ,in_reserve_quota
+ INTO
+ rval
+ FROM exchange.purse_requests preq
+ WHERE (purse_pub=in_purse_pub)
+ AND ( ( ( ((preq.amount_with_fee).val <= (preq.balance).val)
+ AND ((preq.amount_with_fee).frac <= (preq.balance).frac) )
+ OR ((preq.amount_with_fee).val < (preq.balance).val) ) );
+IF NOT FOUND
+THEN
+ out_late=FALSE;
+ RETURN;
+END IF;
+
+-- We use rval as workaround as we cannot select
+-- directly into the amount due to Postgres limitations.
+my_amount := rval.amount_with_fee;
+my_in_reserve_quota := rval.in_reserve_quota;
+
+-- Remember how this purse was finished.
+INSERT INTO purse_decision
+ (purse_pub
+ ,action_timestamp
+ ,refunded)
+VALUES
+ (in_purse_pub
+ ,in_now
+ ,FALSE)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Purse already decided, likely expired.
+ out_late=TRUE;
+ RETURN;
+END IF;
+
+out_late=FALSE;
+
+IF (my_in_reserve_quota)
+THEN
+ UPDATE reserves
+ SET purses_active=purses_active-1
+ WHERE reserve_pub IN
+ (SELECT reserve_pub
+ FROM purse_merges
+ WHERE purse_pub=my_purse_pub
+ LIMIT 1);
+END IF;
+
+
+IF (0 != psi)
+THEN
+ -- The taler-exchange-router will take care of this.
+ UPDATE purse_actions
+ SET action_date=0 --- "immediately"
+ ,partner_serial_id=psi
+ WHERE purse_pub=in_purse_pub;
+ELSE
+ -- This is a local reserve, update balance immediately.
+ INSERT INTO reserves
+ (reserve_pub
+ ,current_balance
+ ,expiration_date
+ ,gc_date)
+ VALUES
+ (my_reserve_pub
+ ,my_amount
+ ,in_reserve_expiration
+ ,in_reserve_expiration)
+ ON CONFLICT DO NOTHING;
+
+ IF NOT FOUND
+ THEN
+ -- Reserve existed, thus UPDATE instead of INSERT.
+ UPDATE reserves
+ SET
+ current_balance.frac=(current_balance).frac+my_amount.frac
+ - CASE
+ WHEN (current_balance).frac + my_amount.frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END
+ ,current_balance.val=(current_balance).val+my_amount.val
+ + CASE
+ WHEN (current_balance).frac + my_amount.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ ,expiration_date=GREATEST(expiration_date,in_reserve_expiration)
+ ,gc_date=GREATEST(gc_date,in_reserve_expiration)
+ WHERE reserve_pub=my_reserve_pub;
+ END IF;
+
+END IF;
+
+
+END $$;
diff --git a/src/exchangedb/exchange_do_purse_merge.sql b/src/exchangedb/exchange_do_purse_merge.sql
new file mode 100644
index 000000000..946fd7e97
--- /dev/null
+++ b/src/exchangedb/exchange_do_purse_merge.sql
@@ -0,0 +1,237 @@
+--
+-- 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_purse_merge(
+ IN in_purse_pub BYTEA,
+ IN in_merge_sig BYTEA,
+ IN in_merge_timestamp INT8,
+ IN in_reserve_sig BYTEA,
+ IN in_partner_url TEXT,
+ IN in_reserve_pub BYTEA,
+ IN in_wallet_h_payto BYTEA,
+ IN in_expiration_date INT8,
+ OUT out_no_partner BOOLEAN,
+ OUT out_no_balance BOOLEAN,
+ OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_amount taler_amount;
+DECLARE
+ my_purse_fee taler_amount;
+DECLARE
+ my_partner_serial_id INT8;
+DECLARE
+ my_in_reserve_quota BOOLEAN;
+DECLARE
+ rval RECORD;
+DECLARE
+ reserve RECORD;
+DECLARE
+ balance taler_amount;
+BEGIN
+
+-- Initialize reserve, if not yet exists.
+INSERT INTO reserves
+ (reserve_pub
+ ,expiration_date
+ ,gc_date)
+ VALUES
+ (in_reserve_pub
+ ,in_expiration_date
+ ,in_expiration_date)
+ ON CONFLICT DO NOTHING;
+
+
+IF in_partner_url IS NULL
+THEN
+ my_partner_serial_id=NULL;
+ELSE
+ SELECT
+ partner_serial_id
+ INTO
+ my_partner_serial_id
+ FROM exchange.partners
+ WHERE partner_base_url=in_partner_url
+ AND start_date <= in_merge_timestamp
+ AND end_date > in_merge_timestamp;
+ IF NOT FOUND
+ THEN
+ out_no_partner=TRUE;
+ out_conflict=FALSE;
+ RETURN;
+ END IF;
+END IF;
+
+out_no_partner=FALSE;
+
+-- Check purse is 'full'.
+SELECT amount_with_fee
+ ,purse_fee
+ ,in_reserve_quota
+ INTO rval
+ FROM purse_requests pr
+ WHERE purse_pub=in_purse_pub
+ AND (pr.balance).val >= (pr.amount_with_fee).val
+ AND ( (pr.balance).frac >= (pr.amount_with_fee).frac OR
+ (pr.balance).val > (pr.amount_with_fee).val );
+IF NOT FOUND
+THEN
+ out_no_balance=TRUE;
+ out_conflict=FALSE;
+ RETURN;
+END IF;
+
+-- We use rval as workaround as we cannot select
+-- directly into the amount due to Postgres limitations.
+my_amount := rval.amount_with_fee;
+my_purse_fee := rval.purse_fee;
+my_in_reserve_quota := rval.in_reserve_quota;
+
+out_no_balance=FALSE;
+
+-- Store purse merge signature, checks for purse_pub uniqueness
+INSERT INTO purse_merges
+ (partner_serial_id
+ ,reserve_pub
+ ,purse_pub
+ ,merge_sig
+ ,merge_timestamp)
+ VALUES
+ (my_partner_serial_id
+ ,in_reserve_pub
+ ,in_purse_pub
+ ,in_merge_sig
+ ,in_merge_timestamp)
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Idempotency check: see if an identical record exists.
+ -- Note that by checking 'merge_sig', we implicitly check
+ -- identity over everything that the signature covers.
+ PERFORM
+ FROM purse_merges
+ WHERE purse_pub=in_purse_pub
+ AND merge_sig=in_merge_sig;
+ IF NOT FOUND
+ THEN
+ -- Purse was merged, but to some other reserve. Not allowed.
+ out_conflict=TRUE;
+ RETURN;
+ END IF;
+
+ -- "success"
+ out_conflict=FALSE;
+ RETURN;
+END IF;
+
+
+-- Remember how this purse was finished. This will conflict
+-- if the purse was already decided previously.
+INSERT INTO purse_decision
+ (purse_pub
+ ,action_timestamp
+ ,refunded)
+VALUES
+ (in_purse_pub
+ ,in_merge_timestamp
+ ,FALSE)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Purse was already decided (possibly deleted or merged differently).
+ out_conflict=TRUE;
+ RETURN;
+END IF;
+
+out_conflict=FALSE;
+
+
+
+IF (my_in_reserve_quota)
+THEN
+ UPDATE reserves
+ SET purses_active=purses_active-1
+ WHERE reserve_pub IN
+ (SELECT reserve_pub
+ FROM purse_merges
+ WHERE purse_pub=my_purse_pub
+ LIMIT 1);
+END IF;
+
+-- Store account merge signature.
+INSERT INTO account_merges
+ (reserve_pub
+ ,reserve_sig
+ ,purse_pub
+ ,wallet_h_payto)
+ VALUES
+ (in_reserve_pub
+ ,in_reserve_sig
+ ,in_purse_pub
+ ,in_wallet_h_payto);
+
+-- If we need a wad transfer, mark purse ready for it.
+IF (0 != my_partner_serial_id)
+THEN
+ -- The taler-exchange-router will take care of this.
+ UPDATE purse_actions
+ SET action_date=0 --- "immediately"
+ ,partner_serial_id=my_partner_serial_id
+ WHERE purse_pub=in_purse_pub;
+ELSE
+ -- This is a local reserve, update reserve balance immediately.
+
+ -- Refund the purse fee, by adding it to the purse value:
+ my_amount.val = my_amount.val + my_purse_fee.val;
+ my_amount.frac = my_amount.frac + my_purse_fee.frac;
+ -- normalize result
+ my_amount.val = my_amount.val + my_amount.frac / 100000000;
+ my_amount.frac = my_amount.frac % 100000000;
+
+ SELECT *
+ INTO reserve
+ FROM exchange.reserves
+ WHERE reserve_pub=in_reserve_pub;
+
+ balance = reserve.current_balance;
+ balance.frac=balance.frac+my_amount.frac
+ - CASE
+ WHEN balance.frac + my_amount.frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END;
+ balance.val=balance.val+my_amount.val
+ + CASE
+ WHEN balance.frac + my_amount.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END;
+
+ UPDATE exchange.reserves
+ SET current_balance=balance
+ WHERE reserve_pub=in_reserve_pub;
+
+END IF;
+
+RETURN;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_purse_merge(BYTEA, BYTEA, INT8, BYTEA, TEXT, BYTEA, BYTEA, INT8)
+ IS 'Checks that the partner exists, the purse has not been merged with a different reserve and that the purse is full. If so, persists the merge data and either merges the purse with the reserve or marks it as ready for the taler-exchange-router. Caller MUST abort the transaction on failures so as to not persist data by accident.';
diff --git a/src/exchangedb/exchange_do_recoup_by_reserve.sql b/src/exchangedb/exchange_do_recoup_by_reserve.sql
new file mode 100644
index 000000000..80f953c4a
--- /dev/null
+++ b/src/exchangedb/exchange_do_recoup_by_reserve.sql
@@ -0,0 +1,87 @@
+--
+-- 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_recoup_by_reserve(
+ IN res_pub BYTEA
+)
+RETURNS TABLE
+(
+ denom_sig BYTEA,
+ denominations_serial BIGINT,
+ coin_pub BYTEA,
+ coin_sig BYTEA,
+ coin_blind BYTEA,
+ amount taler_amount,
+ recoup_timestamp BIGINT
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ res_uuid BIGINT;
+ blind_ev BYTEA;
+ c_pub BYTEA;
+BEGIN
+ SELECT reserve_uuid
+ INTO res_uuid
+ FROM reserves
+ WHERE reserve_pub = res_pub;
+
+ FOR blind_ev IN
+ SELECT h_blind_ev
+ FROM reserves_out ro
+ JOIN reserve_history rh
+ ON (rh.serial_id = ro.reserve_out_serial_id)
+ WHERE rh.reserve_pub = res_pub
+ AND rh.table_name='reserves_out'
+ LOOP
+ SELECT robr.coin_pub
+ INTO c_pub
+ FROM exchange.recoup_by_reserve robr
+ WHERE robr.reserve_out_serial_id = (
+ SELECT reserve_out_serial_id
+ FROM reserves_out
+ WHERE h_blind_ev = blind_ev
+ );
+ RETURN QUERY
+ SELECT kc.denom_sig,
+ kc.denominations_serial,
+ rc.coin_pub,
+ rc.coin_sig,
+ rc.coin_blind,
+ rc.amount,
+ rc.recoup_timestamp
+ FROM (
+ SELECT denom_sig
+ ,denominations_serial
+ FROM exchange.known_coins
+ WHERE known_coins.coin_pub = c_pub
+ ) kc
+ JOIN (
+ SELECT coin_pub
+ ,coin_sig
+ ,coin_blind
+ ,amount
+ ,recoup_timestamp
+ FROM exchange.recoup
+ WHERE recoup.coin_pub = c_pub
+ ) rc USING (coin_pub);
+ END LOOP;
+END;
+$$;
+
+COMMENT ON FUNCTION exchange_do_recoup_by_reserve
+ IS 'Recoup by reserve as a function to make sure we hit only the needed partition and not all when joining as joins on distributed tables fetch ALL rows from the shards';
diff --git a/src/exchangedb/exchange_do_recoup_to_coin.sql b/src/exchangedb/exchange_do_recoup_to_coin.sql
new file mode 100644
index 000000000..6cecfb7f8
--- /dev/null
+++ b/src/exchangedb/exchange_do_recoup_to_coin.sql
@@ -0,0 +1,135 @@
+--
+-- 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_recoup_to_coin(
+ IN in_old_coin_pub BYTEA,
+ IN in_rrc_serial INT8,
+ IN in_coin_blind BYTEA,
+ IN in_coin_pub BYTEA,
+ IN in_known_coin_id INT8,
+ IN in_coin_sig BYTEA,
+ IN in_recoup_timestamp INT8,
+ OUT out_recoup_ok BOOLEAN,
+ OUT out_internal_failure BOOLEAN,
+ OUT out_recoup_timestamp INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ rval RECORD;
+DECLARE
+ tmp taler_amount; -- amount recouped
+BEGIN
+
+-- Shards: UPDATE known_coins (by coin_pub)
+-- SELECT recoup_refresh (by coin_pub)
+-- UPDATE known_coins (by coin_pub)
+-- INSERT recoup_refresh (by coin_pub)
+
+out_internal_failure=FALSE;
+
+-- Check remaining balance of the coin.
+SELECT
+ remaining
+ INTO
+ rval
+FROM exchange.known_coins
+ WHERE coin_pub=in_coin_pub;
+
+IF NOT FOUND
+THEN
+ out_internal_failure=TRUE;
+ out_recoup_ok=FALSE;
+ RETURN;
+END IF;
+
+tmp := rval.remaining;
+
+IF tmp.val + tmp.frac = 0
+THEN
+ -- Check for idempotency
+ SELECT
+ recoup_timestamp
+ INTO
+ out_recoup_timestamp
+ FROM recoup_refresh
+ WHERE coin_pub=in_coin_pub;
+ out_recoup_ok=FOUND;
+ RETURN;
+END IF;
+
+-- Update balance of the coin.
+UPDATE known_coins
+ SET
+ remaining.val = 0
+ ,remaining.frac = 0
+ WHERE coin_pub=in_coin_pub;
+
+-- Credit the old coin.
+UPDATE known_coins kc
+ SET
+ remaining.frac=(kc.remaining).frac+tmp.frac
+ - CASE
+ WHEN (kc.remaining).frac+tmp.frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val+tmp.val
+ + CASE
+ WHEN (kc.remaining).frac+tmp.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub=in_old_coin_pub;
+
+IF NOT FOUND
+THEN
+ RAISE NOTICE 'failed to increase old coin balance from recoup';
+ out_recoup_ok=TRUE;
+ out_internal_failure=TRUE;
+ RETURN;
+END IF;
+
+
+INSERT INTO recoup_refresh
+ (coin_pub
+ ,known_coin_id
+ ,coin_sig
+ ,coin_blind
+ ,amount
+ ,recoup_timestamp
+ ,rrc_serial
+ )
+VALUES
+ (in_coin_pub
+ ,in_known_coin_id
+ ,in_coin_sig
+ ,in_coin_blind
+ ,tmp
+ ,in_recoup_timestamp
+ ,in_rrc_serial);
+
+-- Normal end, everything is fine.
+out_recoup_ok=TRUE;
+out_recoup_timestamp=in_recoup_timestamp;
+
+END $$;
+
+
+-- COMMENT ON FUNCTION exchange_do_recoup_to_coin(INT8, INT4, BYTEA, BOOLEAN, BOOLEAN)
+-- IS 'Executes a recoup-refresh of a coin that was obtained from a refresh-reveal process';
diff --git a/src/exchangedb/exchange_do_recoup_to_reserve.sql b/src/exchangedb/exchange_do_recoup_to_reserve.sql
new file mode 100644
index 000000000..10ae063bd
--- /dev/null
+++ b/src/exchangedb/exchange_do_recoup_to_reserve.sql
@@ -0,0 +1,150 @@
+--
+-- 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_recoup_to_reserve(
+ IN in_reserve_pub BYTEA,
+ IN in_reserve_out_serial_id INT8,
+ IN in_coin_blind BYTEA,
+ IN in_coin_pub BYTEA,
+ IN in_known_coin_id INT8,
+ IN in_coin_sig BYTEA,
+ IN in_reserve_gc INT8,
+ IN in_reserve_expiration INT8,
+ IN in_recoup_timestamp INT8,
+ OUT out_recoup_ok BOOLEAN,
+ OUT out_internal_failure BOOLEAN,
+ OUT out_recoup_timestamp INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ tmp taler_amount; -- amount recouped
+ balance taler_amount; -- current balance of the reserve
+ new_balance taler_amount; -- new balance of the reserve
+ reserve RECORD;
+ rval RECORD;
+BEGIN
+-- Shards: SELECT known_coins (by coin_pub)
+-- SELECT recoup (by coin_pub)
+-- UPDATE known_coins (by coin_pub)
+-- UPDATE reserves (by reserve_pub)
+-- INSERT recoup (by coin_pub)
+
+out_internal_failure=FALSE;
+
+
+-- Check remaining balance of the coin.
+SELECT
+ remaining
+ INTO
+ rval
+FROM exchange.known_coins
+ WHERE coin_pub=in_coin_pub;
+
+IF NOT FOUND
+THEN
+ out_internal_failure=TRUE;
+ out_recoup_ok=FALSE;
+ RETURN;
+END IF;
+
+tmp := rval.remaining;
+
+IF tmp.val + tmp.frac = 0
+THEN
+ -- Check for idempotency
+ SELECT
+ recoup_timestamp
+ INTO
+ out_recoup_timestamp
+ FROM exchange.recoup
+ WHERE coin_pub=in_coin_pub;
+
+ out_recoup_ok=FOUND;
+ RETURN;
+END IF;
+
+
+-- Update balance of the coin.
+UPDATE known_coins
+ SET
+ remaining.val = 0
+ ,remaining.frac = 0
+ WHERE coin_pub=in_coin_pub;
+
+-- Get current balance
+SELECT current_balance
+ INTO reserve
+ FROM reserves
+ WHERE reserve_pub=in_reserve_pub;
+
+balance = reserve.current_balance;
+new_balance.frac=balance.frac+tmp.frac
+ - CASE
+ WHEN balance.frac+tmp.frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END;
+
+new_balance.val=balance.val+tmp.val
+ + CASE
+ WHEN balance.frac+tmp.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END;
+
+-- Credit the reserve and update reserve timers.
+UPDATE reserves
+ SET
+ current_balance = new_balance,
+ gc_date=GREATEST(gc_date, in_reserve_gc),
+ expiration_date=GREATEST(expiration_date, in_reserve_expiration)
+ WHERE reserve_pub=in_reserve_pub;
+
+
+IF NOT FOUND
+THEN
+ RAISE NOTICE 'failed to increase reserve balance from recoup';
+ out_recoup_ok=TRUE;
+ out_internal_failure=TRUE;
+ RETURN;
+END IF;
+
+
+INSERT INTO exchange.recoup
+ (coin_pub
+ ,coin_sig
+ ,coin_blind
+ ,amount
+ ,recoup_timestamp
+ ,reserve_out_serial_id
+ )
+VALUES
+ (in_coin_pub
+ ,in_coin_sig
+ ,in_coin_blind
+ ,tmp
+ ,in_recoup_timestamp
+ ,in_reserve_out_serial_id);
+
+-- Normal end, everything is fine.
+out_recoup_ok=TRUE;
+out_recoup_timestamp=in_recoup_timestamp;
+
+END $$;
+
+-- COMMENT ON FUNCTION exchange_do_recoup_to_reserve(INT8, INT4, BYTEA, BOOLEAN, BOOLEAN)
+-- IS 'Executes a recoup of a coin that was withdrawn from a reserve';
diff --git a/src/exchangedb/exchange_do_refund.sql b/src/exchangedb/exchange_do_refund.sql
new file mode 100644
index 000000000..a95746127
--- /dev/null
+++ b/src/exchangedb/exchange_do_refund.sql
@@ -0,0 +1,205 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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_refund(
+ IN in_amount_with_fee taler_amount,
+ IN in_amount taler_amount,
+ IN in_deposit_fee taler_amount,
+ IN in_h_contract_terms BYTEA,
+ IN in_rtransaction_id INT8,
+ IN in_deposit_shard INT8,
+ IN in_known_coin_id INT8,
+ IN in_coin_pub BYTEA,
+ IN in_merchant_pub BYTEA,
+ IN in_merchant_sig BYTEA,
+ OUT out_not_found BOOLEAN,
+ OUT out_refund_ok BOOLEAN,
+ OUT out_gone BOOLEAN,
+ OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ bdsi INT8; -- ID of deposit being refunded
+DECLARE
+ tmp_val INT8; -- total amount refunded
+DECLARE
+ tmp_frac INT8; -- total amount refunded, large fraction to deal with overflows!
+DECLARE
+ tmp taler_amount; -- total amount refunded, normalized
+DECLARE
+ deposit taler_amount; -- amount that was originally deposited
+BEGIN
+-- Shards: SELECT deposits (coin_pub, shard, h_contract_terms, merchant_pub)
+-- INSERT refunds (by coin_pub, rtransaction_id) ON CONFLICT DO NOTHING
+-- SELECT refunds (by coin_pub)
+-- UPDATE known_coins (by coin_pub)
+
+SELECT
+ bdep.batch_deposit_serial_id
+ ,(cdep.amount_with_fee).val
+ ,(cdep.amount_with_fee).frac
+ ,bdep.done
+ INTO
+ bdsi
+ ,deposit.val
+ ,deposit.frac
+ ,out_gone
+ FROM batch_deposits bdep
+ JOIN coin_deposits cdep
+ USING (batch_deposit_serial_id)
+ WHERE cdep.coin_pub=in_coin_pub
+ AND shard=in_deposit_shard
+ AND merchant_pub=in_merchant_pub
+ AND h_contract_terms=in_h_contract_terms;
+
+IF NOT FOUND
+THEN
+ -- No matching deposit found!
+ out_refund_ok=FALSE;
+ out_conflict=FALSE;
+ out_not_found=TRUE;
+ out_gone=FALSE;
+ RETURN;
+END IF;
+
+INSERT INTO refunds
+ (batch_deposit_serial_id
+ ,coin_pub
+ ,merchant_sig
+ ,rtransaction_id
+ ,amount_with_fee
+ )
+ VALUES
+ (bdsi
+ ,in_coin_pub
+ ,in_merchant_sig
+ ,in_rtransaction_id
+ ,in_amount_with_fee
+ )
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Idempotency check: see if an identical record exists.
+ -- Note that by checking 'coin_sig', we implicitly check
+ -- identity over everything that the signature covers.
+ -- We do select over merchant_pub and h_contract_terms
+ -- primarily here to maximally use the existing index.
+ PERFORM
+ FROM exchange.refunds
+ WHERE coin_pub=in_coin_pub
+ AND batch_deposit_serial_id=bdsi
+ AND rtransaction_id=in_rtransaction_id
+ AND amount_with_fee=in_amount_with_fee;
+
+ IF NOT FOUND
+ THEN
+ -- Deposit exists, but have conflicting refund.
+ out_refund_ok=FALSE;
+ out_conflict=TRUE;
+ out_not_found=FALSE;
+ RETURN;
+ END IF;
+
+ -- Idempotent request known, return success.
+ out_refund_ok=TRUE;
+ out_conflict=FALSE;
+ out_not_found=FALSE;
+ out_gone=FALSE;
+ RETURN;
+END IF;
+
+IF out_gone
+THEN
+ -- money already sent to the merchant. Tough luck.
+ out_refund_ok=FALSE;
+ out_conflict=FALSE;
+ out_not_found=FALSE;
+ RETURN;
+END IF;
+
+-- Check refund balance invariant.
+SELECT
+ SUM((refs.amount_with_fee).val) -- overflow here is not plausible
+ ,SUM(CAST((refs.amount_with_fee).frac AS INT8)) -- compute using 64 bits
+ INTO
+ tmp_val
+ ,tmp_frac
+ FROM refunds refs
+ WHERE coin_pub=in_coin_pub
+ AND batch_deposit_serial_id=bdsi;
+IF tmp_val IS NULL
+THEN
+ RAISE NOTICE 'failed to sum up existing refunds';
+ out_refund_ok=FALSE;
+ out_conflict=FALSE;
+ out_not_found=FALSE;
+ RETURN;
+END IF;
+
+-- Normalize result before continuing
+tmp.val = tmp_val + tmp_frac / 100000000;
+tmp.frac = tmp_frac % 100000000;
+
+-- Actually check if the deposits are sufficient for the refund. Verbosely. ;-)
+IF (tmp.val < deposit.val)
+THEN
+ out_refund_ok=TRUE;
+ELSE
+ IF (tmp.val = deposit.val) AND (tmp.frac <= deposit.frac)
+ THEN
+ out_refund_ok=TRUE;
+ ELSE
+ out_refund_ok=FALSE;
+ END IF;
+END IF;
+
+IF (tmp.val = deposit.val) AND (tmp.frac = deposit.frac)
+THEN
+ -- Refunds have reached the full value of the original
+ -- deposit. Also refund the deposit fee.
+ in_amount.frac = in_amount.frac + in_deposit_fee.frac;
+ in_amount.val = in_amount.val + in_deposit_fee.val;
+
+ -- Normalize result before continuing
+ in_amount.val = in_amount.val + in_amount.frac / 100000000;
+ in_amount.frac = in_amount.frac % 100000000;
+END IF;
+
+-- Update balance of the coin.
+UPDATE known_coins kc
+ SET
+ remaining.frac=(kc.remaining).frac+in_amount.frac
+ - CASE
+ WHEN (kc.remaining).frac+in_amount.frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val+in_amount.val
+ + CASE
+ WHEN (kc.remaining).frac+in_amount.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub=in_coin_pub;
+
+out_conflict=FALSE;
+out_not_found=FALSE;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_refund(taler_amount, taler_amount, taler_amount, BYTEA, INT8, INT8, INT8, BYTEA, BYTEA, BYTEA)
+ IS 'Executes a refund operation, checking that the corresponding deposit was sufficient to cover the refunded amount';
diff --git a/src/exchangedb/exchange_do_reserve_open.sql b/src/exchangedb/exchange_do_reserve_open.sql
new file mode 100644
index 000000000..dd7a578ee
--- /dev/null
+++ b/src/exchangedb/exchange_do_reserve_open.sql
@@ -0,0 +1,194 @@
+--
+-- 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_reserve_open(
+ IN in_reserve_pub BYTEA,
+ IN in_total_paid taler_amount,
+ IN in_reserve_payment taler_amount,
+ IN in_min_purse_limit INT4,
+ IN in_default_purse_limit INT4,
+ IN in_reserve_sig BYTEA,
+ IN in_desired_expiration INT8,
+ IN in_reserve_gc_delay INT8,
+ IN in_now INT8,
+ IN in_open_fee taler_amount,
+ OUT out_open_cost taler_amount,
+ OUT out_final_expiration INT8,
+ OUT out_no_reserve BOOLEAN,
+ OUT out_no_funds BOOLEAN,
+ OUT out_reserve_balance taler_amount)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_balance taler_amount;
+ my_cost taler_amount;
+ my_cost_tmp INT8;
+ my_years_tmp INT4;
+ my_years INT4;
+ my_needs_update BOOL;
+ my_expiration_date INT8;
+ reserve RECORD;
+BEGIN
+
+SELECT current_balance
+ ,expiration_date
+ ,purses_allowed
+ INTO reserve
+ FROM reserves
+ WHERE reserve_pub=in_reserve_pub;
+
+IF NOT FOUND
+THEN
+ RAISE NOTICE 'reserve not found';
+ out_no_reserve = TRUE;
+ out_no_funds = TRUE;
+ out_reserve_balance.val = 0;
+ out_reserve_balance.frac = 0;
+ out_open_cost.val = 0;
+ out_open_cost.frac = 0;
+ out_final_expiration = 0;
+ RETURN;
+END IF;
+
+out_no_reserve = FALSE;
+out_reserve_balance = reserve.current_balance;
+
+-- Do not allow expiration time to start in the past already
+IF (reserve.expiration_date < in_now)
+THEN
+ my_expiration_date = in_now;
+ELSE
+ my_expiration_date = reserve.expiration_date;
+END IF;
+
+my_cost.val = 0;
+my_cost.frac = 0;
+my_needs_update = FALSE;
+my_years = 0;
+
+-- Compute years based on desired expiration time
+IF (my_expiration_date < in_desired_expiration)
+THEN
+ my_years = (31535999999999 + in_desired_expiration - my_expiration_date) / 31536000000000;
+ reserve.purses_allowed = in_default_purse_limit;
+ my_expiration_date = my_expiration_date + 31536000000000 * my_years;
+END IF;
+
+-- Increase years based on purses requested
+IF (reserve.purses_allowed < in_min_purse_limit)
+THEN
+ my_years = (31535999999999 + in_desired_expiration - in_now) / 31536000000000;
+ my_expiration_date = in_now + 31536000000000 * my_years;
+ my_years_tmp = (in_min_purse_limit + in_default_purse_limit - reserve.purses_allowed - 1) / in_default_purse_limit;
+ my_years = my_years + my_years_tmp;
+ reserve.purses_allowed = reserve.purses_allowed + (in_default_purse_limit * my_years_tmp);
+END IF;
+
+
+-- Compute cost based on annual fees
+IF (my_years > 0)
+THEN
+ my_cost.val = my_years * in_open_fee.val;
+ my_cost_tmp = my_years * in_open_fee.frac / 100000000;
+ IF (CAST (my_cost.val + my_cost_tmp AS INT8) < my_cost.val)
+ THEN
+ out_open_cost.val=9223372036854775807;
+ out_open_cost.frac=2147483647;
+ out_final_expiration=my_expiration_date;
+ out_no_funds=FALSE;
+ RAISE NOTICE 'arithmetic issue computing amount';
+ RETURN;
+ END IF;
+ my_cost.val = CAST (my_cost.val + my_cost_tmp AS INT8);
+ my_cost.frac = my_years * in_open_fee.frac % 100000000;
+ my_needs_update = TRUE;
+END IF;
+
+-- check if we actually have something to do
+IF NOT my_needs_update
+THEN
+ out_final_expiration = reserve.expiration_date;
+ out_open_cost.val = 0;
+ out_open_cost.frac = 0;
+ out_no_funds=FALSE;
+ RAISE NOTICE 'no change required';
+ RETURN;
+END IF;
+
+-- Check payment (coins and reserve) would be sufficient.
+IF ( (in_total_paid.val < my_cost.val) OR
+ ( (in_total_paid.val = my_cost.val) AND
+ (in_total_paid.frac < my_cost.frac) ) )
+THEN
+ out_open_cost.val = my_cost.val;
+ out_open_cost.frac = my_cost.frac;
+ out_no_funds=FALSE;
+ -- We must return a failure, which is indicated by
+ -- the expiration being below the desired expiration.
+ IF (reserve.expiration_date >= in_desired_expiration)
+ THEN
+ -- This case is relevant especially if the purse
+ -- count was to be increased and the payment was
+ -- insufficient to cover this for the full period.
+ RAISE NOTICE 'forcing low expiration time';
+ out_final_expiration = 0;
+ ELSE
+ out_final_expiration = reserve.expiration_date;
+ END IF;
+ RAISE NOTICE 'amount paid too low';
+ RETURN;
+END IF;
+
+-- Check reserve balance is sufficient.
+IF (out_reserve_balance.val > in_reserve_payment.val)
+THEN
+ IF (out_reserve_balance.frac >= in_reserve_payment.frac)
+ THEN
+ my_balance.val=out_reserve_balance.val - in_reserve_payment.val;
+ my_balance.frac=out_reserve_balance.frac - in_reserve_payment.frac;
+ ELSE
+ my_balance.val=out_reserve_balance.val - in_reserve_payment.val - 1;
+ my_balance.frac=out_reserve_balance.frac + 100000000 - in_reserve_payment.frac;
+ END IF;
+ELSE
+ IF (out_reserve_balance.val = in_reserve_payment.val) AND (out_reserve_balance.frac >= in_reserve_payment.frac)
+ THEN
+ my_balance.val=0;
+ my_balance.frac=out_reserve_balance.frac - in_reserve_payment.frac;
+ ELSE
+ out_final_expiration = reserve.expiration_date;
+ out_open_cost.val = my_cost.val;
+ out_open_cost.frac = my_cost.frac;
+ out_no_funds=TRUE;
+ RAISE NOTICE 'reserve balance too low';
+ RETURN;
+ END IF;
+END IF;
+
+UPDATE reserves SET
+ current_balance=my_balance
+ ,gc_date=reserve.expiration_date + in_reserve_gc_delay
+ ,expiration_date=my_expiration_date
+ ,purses_allowed=reserve.purses_allowed
+WHERE
+ reserve_pub=in_reserve_pub;
+
+out_final_expiration=my_expiration_date;
+out_open_cost = my_cost;
+out_no_funds=FALSE;
+RETURN;
+
+END $$;
diff --git a/src/exchangedb/exchange_do_reserve_open_deposit.sql b/src/exchangedb/exchange_do_reserve_open_deposit.sql
new file mode 100644
index 000000000..aa6f86a9b
--- /dev/null
+++ b/src/exchangedb/exchange_do_reserve_open_deposit.sql
@@ -0,0 +1,84 @@
+--
+-- 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_reserve_open_deposit(
+ IN in_coin_pub BYTEA,
+ IN in_known_coin_id INT8,
+ IN in_coin_sig BYTEA,
+ IN in_reserve_sig BYTEA,
+ IN in_reserve_pub BYTEA,
+ IN in_coin_total taler_amount,
+ OUT out_insufficient_funds BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+INSERT INTO exchange.reserves_open_deposits
+ (reserve_sig
+ ,reserve_pub
+ ,coin_pub
+ ,coin_sig
+ ,contribution
+ )
+ VALUES
+ (in_reserve_sig
+ ,in_reserve_pub
+ ,in_coin_pub
+ ,in_coin_sig
+ ,in_coin_total
+ )
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Idempotent request known, return success.
+ out_insufficient_funds=FALSE;
+ RETURN;
+END IF;
+
+
+-- Check and update balance of the coin.
+UPDATE exchange.known_coins kc
+ SET
+ remaining.frac=(kc.remaining).frac-in_coin_total.frac
+ + CASE
+ WHEN (kc.remaining).frac < in_coin_total.frac
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val-in_coin_total.val
+ - CASE
+ WHEN (kc.remaining).frac < in_coin_total.frac
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub=in_coin_pub
+ AND ( ((kc.remaining).val > in_coin_total.val) OR
+ ( ((kc.remaining).frac >= in_coin_total.frac) AND
+ ((kc.remaining).val >= in_coin_total.val) ) );
+
+IF NOT FOUND
+THEN
+ -- Insufficient balance.
+ out_insufficient_funds=TRUE;
+ RETURN;
+END IF;
+
+-- Everything fine, return success!
+out_insufficient_funds=FALSE;
+
+END $$;
diff --git a/src/exchangedb/exchange_do_reserve_purse.sql b/src/exchangedb/exchange_do_reserve_purse.sql
new file mode 100644
index 000000000..8ae652e69
--- /dev/null
+++ b/src/exchangedb/exchange_do_reserve_purse.sql
@@ -0,0 +1,162 @@
+--
+-- 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_reserve_purse(
+ IN in_purse_pub BYTEA,
+ IN in_merge_sig BYTEA,
+ IN in_merge_timestamp INT8,
+ IN in_reserve_expiration INT8,
+ IN in_reserve_gc INT8,
+ IN in_reserve_sig BYTEA,
+ IN in_reserve_quota BOOLEAN,
+ IN in_purse_fee taler_amount,
+ IN in_reserve_pub BYTEA,
+ IN in_wallet_h_payto BYTEA,
+ OUT out_no_funds BOOLEAN,
+ OUT out_no_reserve BOOLEAN,
+ OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+-- Store purse merge signature, checks for purse_pub uniqueness
+INSERT INTO purse_merges
+ (partner_serial_id
+ ,reserve_pub
+ ,purse_pub
+ ,merge_sig
+ ,merge_timestamp)
+ VALUES
+ (NULL
+ ,in_reserve_pub
+ ,in_purse_pub
+ ,in_merge_sig
+ ,in_merge_timestamp)
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Idempotency check: see if an identical record exists.
+ -- Note that by checking 'merge_sig', we implicitly check
+ -- identity over everything that the signature covers.
+ PERFORM
+ FROM purse_merges
+ WHERE purse_pub=in_purse_pub
+ AND merge_sig=in_merge_sig;
+ IF NOT FOUND
+ THEN
+ -- Purse was merged, but to some other reserve. Not allowed.
+ out_conflict=TRUE;
+ out_no_reserve=FALSE;
+ out_no_funds=FALSE;
+ RETURN;
+ END IF;
+
+ -- "success"
+ out_conflict=FALSE;
+ out_no_funds=FALSE;
+ out_no_reserve=FALSE;
+ RETURN;
+END IF;
+out_conflict=FALSE;
+
+PERFORM
+ FROM exchange.reserves
+ WHERE reserve_pub=in_reserve_pub;
+
+out_no_reserve = NOT FOUND;
+
+IF (in_reserve_quota)
+THEN
+ -- Increment active purses per reserve (and check this is allowed)
+ IF (out_no_reserve)
+ THEN
+ out_no_funds=TRUE;
+ RETURN;
+ END IF;
+ UPDATE exchange.reserves
+ SET purses_active=purses_active+1
+ WHERE reserve_pub=in_reserve_pub
+ AND purses_active < purses_allowed;
+ IF NOT FOUND
+ THEN
+ out_no_funds=TRUE;
+ RETURN;
+ END IF;
+ELSE
+ -- UPDATE reserves balance (and check if balance is enough to pay the fee)
+ IF (out_no_reserve)
+ THEN
+ IF ( (0 != in_purse_fee.val) OR
+ (0 != in_purse_fee.frac) )
+ THEN
+ out_no_funds=TRUE;
+ RETURN;
+ END IF;
+ INSERT INTO exchange.reserves
+ (reserve_pub
+ ,expiration_date
+ ,gc_date)
+ VALUES
+ (in_reserve_pub
+ ,in_reserve_expiration
+ ,in_reserve_gc);
+ ELSE
+ UPDATE exchange.reserves
+ SET
+ current_balance.frac=(current_balance).frac-in_purse_fee.frac
+ + CASE
+ WHEN (current_balance).frac < in_purse_fee.frac
+ THEN 100000000
+ ELSE 0
+ END,
+ current_balance.val=(current_balance).val-in_purse_fee.val
+ - CASE
+ WHEN (current_balance).frac < in_purse_fee.frac
+ THEN 1
+ ELSE 0
+ END
+ WHERE reserve_pub=in_reserve_pub
+ AND ( ((current_balance).val > in_purse_fee.val) OR
+ ( ((current_balance).frac >= in_purse_fee.frac) AND
+ ((current_balance).val >= in_purse_fee.val) ) );
+ IF NOT FOUND
+ THEN
+ out_no_funds=TRUE;
+ RETURN;
+ END IF;
+ END IF;
+END IF;
+
+out_no_funds=FALSE;
+
+
+-- Store account merge signature.
+INSERT INTO account_merges
+ (reserve_pub
+ ,reserve_sig
+ ,purse_pub
+ ,wallet_h_payto)
+ VALUES
+ (in_reserve_pub
+ ,in_reserve_sig
+ ,in_purse_pub
+ ,in_wallet_h_payto);
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_reserve_purse(BYTEA, BYTEA, INT8, INT8, INT8, BYTEA, BOOLEAN, taler_amount, BYTEA, BYTEA)
+ IS 'Create a purse for a reserve.';
diff --git a/src/exchangedb/exchange_do_reserves_in_insert.sql b/src/exchangedb/exchange_do_reserves_in_insert.sql
new file mode 100644
index 000000000..1be06f063
--- /dev/null
+++ b/src/exchangedb/exchange_do_reserves_in_insert.sql
@@ -0,0 +1,122 @@
+--
+-- This file is part of TALER
+-- 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
+-- 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_array_reserves_insert(
+ IN in_gc_date INT8,
+ IN in_reserve_expiration INT8,
+ IN ina_reserve_pub BYTEA[],
+ IN ina_wire_ref INT8[],
+ IN ina_credit taler_amount[],
+ IN ina_exchange_account_name TEXT[],
+ IN ina_execution_date INT8[],
+ IN ina_wire_source_h_payto BYTEA[],
+ IN ina_payto_uri TEXT[],
+ IN ina_notify TEXT[])
+RETURNS SETOF exchange_do_array_reserve_insert_return_type
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ conflict BOOL;
+ dup BOOL;
+ uuid INT8;
+ i INT4;
+ ini_reserve_pub BYTEA;
+ ini_wire_ref INT8;
+ ini_credit taler_amount;
+ ini_exchange_account_name TEXT;
+ ini_execution_date INT8;
+ ini_wire_source_h_payto BYTEA;
+ ini_payto_uri TEXT;
+ ini_notify TEXT;
+BEGIN
+
+ FOR i IN 1..array_length(ina_reserve_pub,1)
+ LOOP
+ ini_reserve_pub = ina_reserve_pub[i];
+ ini_wire_ref = ina_wire_ref[i];
+ ini_credit = ina_credit[i];
+ ini_exchange_account_name = ina_exchange_account_name[i];
+ ini_execution_date = ina_execution_date[i];
+ ini_wire_source_h_payto = ina_wire_source_h_payto[i];
+ ini_payto_uri = ina_payto_uri[i];
+ ini_notify = ina_notify[i];
+
+-- RAISE WARNING 'Starting loop on %', ini_notify;
+
+ INSERT INTO wire_targets
+ (wire_target_h_payto
+ ,payto_uri
+ ) VALUES (
+ ini_wire_source_h_payto
+ ,ini_payto_uri
+ )
+ ON CONFLICT DO NOTHING;
+
+ INSERT INTO reserves
+ (reserve_pub
+ ,current_balance
+ ,expiration_date
+ ,gc_date
+ ) VALUES (
+ ini_reserve_pub
+ ,ini_credit
+ ,in_reserve_expiration
+ ,in_gc_date
+ )
+ ON CONFLICT DO NOTHING
+ RETURNING reserve_uuid
+ INTO uuid;
+ conflict = NOT FOUND;
+
+ INSERT INTO reserves_in
+ (reserve_pub
+ ,wire_reference
+ ,credit
+ ,exchange_account_section
+ ,wire_source_h_payto
+ ,execution_date
+ ) VALUES (
+ ini_reserve_pub
+ ,ini_wire_ref
+ ,ini_credit
+ ,ini_exchange_account_name
+ ,ini_wire_source_h_payto
+ ,ini_execution_date
+ )
+ ON CONFLICT DO NOTHING;
+
+ IF NOT FOUND
+ THEN
+ IF conflict
+ THEN
+ dup = TRUE;
+ else
+ dup = FALSE;
+ END IF;
+ ELSE
+ IF NOT conflict
+ THEN
+ EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,ini_notify);
+ END IF;
+ dup = FALSE;
+ END IF;
+ RETURN NEXT (dup,uuid);
+ END LOOP;
+ RETURN;
+END $$;
diff --git a/src/exchangedb/exchange_do_select_deposits_missing_wire.sql b/src/exchangedb/exchange_do_select_deposits_missing_wire.sql
new file mode 100644
index 000000000..3d44a58c9
--- /dev/null
+++ b/src/exchangedb/exchange_do_select_deposits_missing_wire.sql
@@ -0,0 +1,73 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+-- @author: Christian Grothoff
+
+CREATE OR REPLACE FUNCTION exchange_do_select_deposits_missing_wire(
+ IN in_min_serial_id INT8)
+RETURNS SETOF exchange_do_select_deposits_missing_wire_return_type
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ missing CURSOR
+ FOR
+ SELECT
+ batch_deposit_serial_id
+ ,wire_target_h_payto
+ ,wire_deadline
+ FROM batch_deposits
+ WHERE batch_deposit_serial_id > in_min_serial_id
+ ORDER BY batch_deposit_serial_id ASC;
+DECLARE
+ my_total_val INT8; -- all deposits without wire
+DECLARE
+ my_total_frac INT8; -- all deposits without wire (fraction, not normalized)
+DECLARE
+ my_total taler_amount; -- amount that was originally deposited
+DECLARE
+ my_batch_record RECORD;
+DECLARE
+ i RECORD;
+BEGIN
+
+OPEN missing;
+LOOP
+ FETCH NEXT FROM missing INTO i;
+ EXIT WHEN NOT FOUND;
+
+ SELECT
+ SUM((cdep.amount_with_fee).val) AS total_val
+ ,SUM((cdep.amount_with_fee).frac::INT8) AS total_frac
+ INTO
+ my_batch_record
+ FROM coin_deposits cdep
+ WHERE cdep.batch_deposit_serial_id = i.batch_deposit_serial_id;
+
+ my_total_val=my_batch_record.total_val;
+ my_total_frac=my_batch_record.total_frac;
+
+ -- Normalize total amount
+ my_total.val = my_total_val + my_total_frac / 100000000;
+ my_total.frac = my_total_frac % 100000000;
+ RETURN NEXT (
+ i.batch_deposit_serial_id
+ ,my_total
+ ,i.wire_target_h_payto
+ ,i.wire_deadline);
+
+END LOOP;
+CLOSE missing;
+RETURN;
+END $$;
diff --git a/src/exchangedb/exchange_do_select_justification_for_missing_wire.sql b/src/exchangedb/exchange_do_select_justification_for_missing_wire.sql
new file mode 100644
index 000000000..f02a51d3d
--- /dev/null
+++ b/src/exchangedb/exchange_do_select_justification_for_missing_wire.sql
@@ -0,0 +1,102 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+-- @author: Christian Grothoff
+
+CREATE OR REPLACE FUNCTION exchange_do_select_justification_missing_wire(
+ IN in_wire_target_h_payto BYTEA,
+ IN in_current_time INT8,
+ OUT out_payto_uri TEXT, -- NULL allowed
+ OUT out_kyc_pending TEXT, -- NULL allowed
+ OUT out_aml_status INT4, -- NULL allowed
+ OUT out_aml_limit taler_amount) -- NULL allowed!
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_required_checks TEXT[];
+DECLARE
+ my_aml_data RECORD;
+DECLARE
+ satisfied CURSOR FOR
+ SELECT satisfied_checks
+ FROM kyc_attributes
+ WHERE h_payto=in_wire_target_h_payto
+ AND expiration_time < in_current_time;
+DECLARE
+ i RECORD;
+BEGIN
+
+ -- Fetch payto URI
+ out_payto_uri = NULL;
+ SELECT payto_uri
+ INTO out_payto_uri
+ FROM wire_targets
+ WHERE wire_target_h_payto=my_wire_target_h_payto;
+
+ -- Check KYC status
+ my_required_checks = NULL;
+ SELECT string_to_array (required_checks, ' ')
+ INTO my_required_checks
+ FROM legitimization_requirements
+ WHERE h_payto=my_wire_target_h_payto;
+
+ -- Get last AML decision
+ SELECT
+ new_threshold
+ ,kyc_requirements
+ ,new_status
+ INTO
+ my_aml_data
+ FROM aml_history
+ WHERE h_payto=in_wire_target_h_payto
+ ORDER BY aml_history_serial_id -- get last decision
+ DESC LIMIT 1;
+ IF FOUND
+ THEN
+ out_aml_limit=my_aml_data.new_threshold;
+ out_aml_status=my_aml_data.kyc_status;
+ -- Combine KYC requirements
+ my_required_checks
+ = array_cat (my_required_checks,
+ my_aml_data.kyc_requirements);
+ ELSE
+ out_aml_limit=NULL;
+ out_aml_status=0; -- or NULL? Style question!
+ END IF;
+
+ OPEN satisfied;
+ LOOP
+ FETCH NEXT FROM satisfied INTO i;
+ EXIT WHEN NOT FOUND;
+
+ -- remove all satisfied checks from the list
+ FOR i in 1..array_length(i.satisfied_checks)
+ LOOP
+ my_required_checks
+ = array_remove (my_required_checks,
+ i.satisfied_checks[i]);
+ END LOOP;
+ END LOOP;
+
+ -- Return remaining required checks as one string
+ IF ( (my_required_checks IS NOT NULL) AND
+ (0 < array_length(my_satisfied_checks)) )
+ THEN
+ out_kyc_pending
+ = array_to_string (my_required_checks, ' ');
+ END IF;
+
+ RETURN;
+END $$;
diff --git a/src/exchangedb/exchangedb-postgres.conf b/src/exchangedb/exchangedb-postgres.conf
index 7d600586f..726d28576 100644
--- a/src/exchangedb/exchangedb-postgres.conf
+++ b/src/exchangedb/exchangedb-postgres.conf
@@ -1,6 +1,9 @@
[exchangedb-postgres]
-CONFIG = "postgres:///taler"
+CONFIG = "postgres:///taler-exchange"
# Where are the SQL files to setup our tables?
# Important: this MUST end with a "/"!
-SQL_DIR = $DATADIR/sql/exchange/
+SQL_DIR = ${DATADIR}sql/exchange/
+
+# Number of purses per account by default.
+DEFAULT_PURSE_LIMIT = 1 \ No newline at end of file
diff --git a/src/exchangedb/exchangedb.conf b/src/exchangedb/exchangedb.conf
index 6853b8d18..2bfcb2ca0 100644
--- a/src/exchangedb/exchangedb.conf
+++ b/src/exchangedb/exchangedb.conf
@@ -27,10 +27,10 @@ IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
# the legal expiration timeframe of withdrawn coins.
LEGAL_RESERVE_EXPIRATION_TIME = 7 years
+# What is the desired delay between a transaction being ready and the
+# aggregator triggering on it?
+AGGREGATOR_SHIFT = 1 s
-# How long should generated coins overlap in their validity
-# periods. Should be long enough to avoid problems with
-# wallets picking one key and then due to network latency
-# another key being valid. The DURATION_WITHDRAW period
-# must be longer than this value.
-DURATION_OVERLAP = 5 minutes \ No newline at end of file
+# How many concurrent purses may be opened by a reserve
+# if the reserve is paid for a year?
+DEFAULT_PURSE_LIMIT = 1 \ No newline at end of file
diff --git a/src/exchangedb/exchangedb_accounts.c b/src/exchangedb/exchangedb_accounts.c
index b66b1bcc1..e668134e1 100644
--- a/src/exchangedb/exchangedb_accounts.c
+++ b/src/exchangedb/exchangedb_accounts.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018 Taler Systems SA
+ Copyright (C) 2018-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
@@ -23,149 +23,76 @@
/**
- * Head of list of wire accounts of the exchange.
+ * Information we keep for each supported account of the exchange.
*/
-static struct TALER_EXCHANGEDB_WireAccount *wa_head;
+struct WireAccount
+{
+ /**
+ * Accounts are kept in a DLL.
+ */
+ struct WireAccount *next;
-/**
- * Tail of list of wire accounts of the exchange.
- */
-static struct TALER_EXCHANGEDB_WireAccount *wa_tail;
+ /**
+ * Plugins are kept in a DLL.
+ */
+ struct WireAccount *prev;
+ /**
+ * Externally visible account information.
+ */
+ struct TALER_EXCHANGEDB_AccountInfo ai;
-/**
- * Closure of #check_for_account.
- */
-struct FindAccountContext
-{
/**
- * Configuration we are using.
+ * Authentication data. Only parsed if
+ * #TALER_EXCHANGEDB_ALO_AUTHDATA was set.
*/
- const struct GNUNET_CONFIGURATION_Handle *cfg;
+ struct TALER_BANK_AuthenticationData auth;
/**
- * Callback to invoke.
+ * Name of the section that configures this account.
*/
- TALER_EXCHANGEDB_AccountCallback cb;
+ char *section_name;
/**
- * Closure for @e cb.
+ * Name of the wire method underlying the account.
*/
- void *cb_cls;
+ char *method;
+
};
/**
- * Check if @a section begins with "exchange-account-", and if so if the
- * "PAYTO_URI" is given. If not, a warning is printed, otherwise we also check
- * if "ENABLE_CREDIT" or "ENABLE_DEBIT" options are set to "YES" and then call
- * the callback in @a cls with all of the information gathered.
- *
- * @param cls our `struct FindAccountContext`
- * @param section name of a section in the configuration
+ * Head of list of wire accounts of the exchange.
*/
-static void
-check_for_account (void *cls,
- const char *section)
-{
- struct FindAccountContext *ctx = cls;
- char *method;
- char *payto_uri;
- char *wire_response_filename;
-
- if (0 != strncasecmp (section,
- "exchange-account-",
- strlen ("exchange-account-")))
- return;
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (ctx->cfg,
- section,
- "PAYTO_URI",
- &payto_uri))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
- section,
- "PAYTO_URI");
- return;
- }
- method = TALER_payto_get_method (payto_uri);
- if (NULL == method)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "payto URI in config ([%s]/PAYTO_URI) malformed\n",
- section);
- GNUNET_free (payto_uri);
- return;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (ctx->cfg,
- section,
- "WIRE_RESPONSE",
- &wire_response_filename))
- wire_response_filename = NULL;
- {
- struct TALER_EXCHANGEDB_AccountInfo ai = {
- .section_name = section,
- .method = method,
- .payto_uri = payto_uri,
- .wire_response_filename = wire_response_filename,
- .debit_enabled = (GNUNET_YES ==
- GNUNET_CONFIGURATION_get_value_yesno (
- ctx->cfg,
- section,
- "ENABLE_DEBIT")),
- .credit_enabled = (GNUNET_YES ==
- GNUNET_CONFIGURATION_get_value_yesno (ctx->cfg,
- section,
- "ENABLE_CREDIT"))
- };
-
- ctx->cb (ctx->cb_cls,
- &ai);
- }
- GNUNET_free (payto_uri);
- GNUNET_free (method);
- GNUNET_free_non_null (wire_response_filename);
-}
-
+static struct WireAccount *wa_head;
/**
- * Parse the configuration to find account information.
- *
- * @param cfg configuration to use
- * @param cb callback to invoke
- * @param cb_cls closure for @a cb
+ * Tail of list of wire accounts of the exchange.
*/
+static struct WireAccount *wa_tail;
+
+
void
-TALER_EXCHANGEDB_find_accounts (const struct GNUNET_CONFIGURATION_Handle *cfg,
- TALER_EXCHANGEDB_AccountCallback cb,
+TALER_EXCHANGEDB_find_accounts (TALER_EXCHANGEDB_AccountCallback cb,
void *cb_cls)
{
- struct FindAccountContext ctx;
-
- ctx.cfg = cfg;
- ctx.cb = cb;
- ctx.cb_cls = cb_cls;
- GNUNET_CONFIGURATION_iterate_sections (cfg,
- &check_for_account,
- &ctx);
+ for (struct WireAccount *wa = wa_head;
+ NULL != wa;
+ wa = wa->next)
+ cb (cb_cls,
+ &wa->ai);
}
-/**
- * Find the wire plugin for the given payto:// URL
- *
- * @param method wire method we need an account for
- * @return NULL on error
- */
-struct TALER_EXCHANGEDB_WireAccount *
+const struct TALER_EXCHANGEDB_AccountInfo *
TALER_EXCHANGEDB_find_account_by_method (const char *method)
{
- for (struct TALER_EXCHANGEDB_WireAccount *wa = wa_head; NULL != wa; wa =
- wa->next)
+ for (struct WireAccount *wa = wa_head;
+ NULL != wa;
+ wa = wa->next)
if (0 == strcmp (method,
wa->method))
- return wa;
+ return &wa->ai;
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No wire account known for method `%s'\n",
method);
@@ -173,17 +100,11 @@ TALER_EXCHANGEDB_find_account_by_method (const char *method)
}
-/**
- * Find the wire plugin for the given payto:// URL
- *
- * @param url wire address we need an account for
- * @return NULL on error
- */
-struct TALER_EXCHANGEDB_WireAccount *
+const struct TALER_EXCHANGEDB_AccountInfo *
TALER_EXCHANGEDB_find_account_by_payto_uri (const char *url)
{
char *method;
- struct TALER_EXCHANGEDB_WireAccount *wa;
+ const struct TALER_EXCHANGEDB_AccountInfo *ai;
method = TALER_payto_get_method (url);
if (NULL == method)
@@ -193,108 +114,176 @@ TALER_EXCHANGEDB_find_account_by_payto_uri (const char *url)
url);
return NULL;
}
- wa = TALER_EXCHANGEDB_find_account_by_method (method);
+ ai = TALER_EXCHANGEDB_find_account_by_method (method);
GNUNET_free (method);
- return wa;
+ return ai;
}
/**
+ * Closure for #add_account_cb().
+ */
+struct LoaderContext
+{
+ /**
+ * Configuration to use.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * true if we are to load the authentication data
+ * for the access to the bank account.
+ */
+ bool load_auth_data;
+
+ /**
+ * Load accounts enabled for CREDIT.
+ */
+ bool credit;
+
+ /**
+ * Load accounts enabled for DEBIT.
+ */
+ bool debit;
+
+ /**
+ * Loader status (set by callback).
+ */
+ enum GNUNET_GenericReturnValue res;
+};
+
+
+/**
* Function called with information about a wire account. Adds
* the account to our list.
*
- * @param cls closure, a `struct GNUNET_CONFIGURATION_Handle`
- * @param ai account information
+ * @param cls closure, a `struct LoaderContext`
+ * @param section section to parse account information from
*/
static void
add_account_cb (void *cls,
- const struct TALER_EXCHANGEDB_AccountInfo *ai)
+ const char *section)
{
- const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
- struct TALER_EXCHANGEDB_WireAccount *wa;
+ struct LoaderContext *lc = cls;
+ const struct GNUNET_CONFIGURATION_Handle *cfg = lc->cfg;
+ struct WireAccount *wa;
char *payto_uri;
+ char *method;
+ bool debit;
+ bool credit;
- (void) cls;
- if (GNUNET_YES != ai->debit_enabled)
+ if (0 != strncasecmp (section,
+ "exchange-account-",
+ strlen ("exchange-account-")))
+ return;
+
+ debit = (GNUNET_YES ==
+ GNUNET_CONFIGURATION_get_value_yesno (lc->cfg,
+ section,
+ "ENABLE_DEBIT"));
+ credit = (GNUNET_YES ==
+ GNUNET_CONFIGURATION_get_value_yesno (lc->cfg,
+ section,
+ "ENABLE_CREDIT"));
+ if (! ( ( (debit) &&
+ (lc->debit) ) ||
+ ( (credit) &&
+ (lc->credit) ) ) )
return; /* not enabled for us, skip */
- wa = GNUNET_new (struct TALER_EXCHANGEDB_WireAccount);
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
- ai->section_name,
+ section,
"PAYTO_URI",
&payto_uri))
{
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ai->section_name,
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ section,
"PAYTO_URI");
- GNUNET_free (wa);
return;
}
- wa->method = TALER_payto_get_method (payto_uri);
- if (NULL == wa->method)
+ method = TALER_payto_get_method (payto_uri);
+ GNUNET_free (payto_uri);
+ if (NULL == method)
{
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- ai->section_name,
- "PAYTO_URI",
- "could not obtain wire method from URI");
- GNUNET_free (wa);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "payto URI in config ([%s]/PAYTO_URI) malformed\n",
+ section);
+ lc->res = GNUNET_SYSERR;
return;
}
- GNUNET_free (payto_uri);
- if (GNUNET_OK !=
- TALER_BANK_auth_parse_cfg (cfg,
- ai->section_name,
- &wa->auth))
+ wa = GNUNET_new (struct WireAccount);
+ wa->section_name = GNUNET_strdup (section);
+ wa->method = method;
+ wa->ai.debit_enabled = debit;
+ wa->ai.credit_enabled = credit;
+ wa->ai.auth = NULL;
+ wa->ai.section_name = wa->section_name;
+ wa->ai.method = wa->method;
+ if (lc->load_auth_data)
{
- GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
- "Failed to load exchange account `%s'\n",
- ai->section_name);
- GNUNET_free (wa->method);
- GNUNET_free (wa);
- return;
+ char *csn;
+
+ GNUNET_asprintf (&csn,
+ "exchange-accountcredentials-%s",
+ &section[strlen ("exchange-account-")]);
+ if (GNUNET_OK !=
+ TALER_BANK_auth_parse_cfg (cfg,
+ csn,
+ &wa->auth))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Failed to load exchange account credentials from section `%s'\n",
+ csn);
+ GNUNET_free (csn);
+ GNUNET_free (wa->section_name);
+ GNUNET_free (wa->method);
+ GNUNET_free (wa);
+ return;
+ }
+ wa->ai.auth = &wa->auth;
+ GNUNET_free (csn);
}
- wa->section_name = GNUNET_strdup (ai->section_name);
GNUNET_CONTAINER_DLL_insert (wa_head,
wa_tail,
wa);
}
-/**
- * Load account information opf the exchange from
- * @a cfg.
- *
- * @param cfg configuration to load from
- * @return #GNUNET_OK on success, #GNUNET_NO if no accounts are configured
- */
-int
-TALER_EXCHANGEDB_load_accounts (const struct GNUNET_CONFIGURATION_Handle *cfg)
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGEDB_load_accounts (
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ enum TALER_EXCHANGEDB_AccountLoaderOptions options)
{
- TALER_EXCHANGEDB_find_accounts (cfg,
- &add_account_cb,
- (void *) cfg);
+ struct LoaderContext lc = {
+ .cfg = cfg,
+ .debit = 0 != (options & TALER_EXCHANGEDB_ALO_DEBIT),
+ .credit = 0 != (options & TALER_EXCHANGEDB_ALO_CREDIT),
+ .load_auth_data = 0 != (options & TALER_EXCHANGEDB_ALO_AUTHDATA),
+ };
+
+ GNUNET_CONFIGURATION_iterate_sections (cfg,
+ &add_account_cb,
+ &lc);
+ if (GNUNET_SYSERR == lc.res)
+ return GNUNET_SYSERR;
if (NULL == wa_head)
return GNUNET_NO;
return GNUNET_OK;
}
-/**
- * Free resources allocated by
- * #TALER_EXCHANGEDB_load_accounts().
- */
void
TALER_EXCHANGEDB_unload_accounts (void)
{
- struct TALER_EXCHANGEDB_WireAccount *wa;
+ struct WireAccount *wa;
while (NULL != (wa = wa_head))
{
GNUNET_CONTAINER_DLL_remove (wa_head,
wa_tail,
wa);
- TALER_BANK_auth_free (&wa->auth);
- TALER_EXCHANGEDB_fees_free (wa->af);
+ if (NULL != wa->ai.auth)
+ TALER_BANK_auth_free (&wa->auth);
GNUNET_free (wa->section_name);
GNUNET_free (wa->method);
GNUNET_free (wa);
diff --git a/src/exchangedb/exchangedb_auditorkeys.c b/src/exchangedb/exchangedb_auditorkeys.c
deleted file mode 100644
index 0a0370cfe..000000000
--- a/src/exchangedb/exchangedb_auditorkeys.c
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014--2019 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 exchangedb/exchangedb_auditorkeys.c
- * @brief I/O operations for the Exchange's auditor data
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Sree Harsha Totakura
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_exchangedb_lib.h"
-
-
-/**
- * Closure for #auditor_iter() and
- */
-struct AuditorIterateContext
-{
-
- /**
- * Function to call with the information for each auditor.
- */
- TALER_EXCHANGEDB_AuditorIterator it;
-
- /**
- * Closure for @e it.
- */
- void *it_cls;
-
- /**
- * Status of the iteration.
- */
- int status;
-};
-
-
-GNUNET_NETWORK_STRUCT_BEGIN
-
-/**
- * Header of a file with auditing information.
- */
-struct AuditorFileHeaderP
-{
-
- /**
- * Public key of the auditor.
- */
- struct TALER_AuditorPublicKeyP apub;
-
- /**
- * Master public key of the exchange the auditor is signing
- * information for.
- */
- struct TALER_MasterPublicKeyP mpub;
-
- /**
- * Number of signatures and DKI entries in this file.
- */
- uint32_t dki_len;
-
-};
-GNUNET_NETWORK_STRUCT_END
-
-
-/**
- * Load the auditor signature and the information signed by the
- * auditor and call the callback in @a cls with the information.
- *
- * @param cls the `struct AuditorIterateContext *`
- * @param filename name of a file that should contain
- * a denomination key
- * @return #GNUNET_OK to continue to iterate
- * #GNUNET_NO to abort iteration with success
- * #GNUNET_SYSERR to abort iteration with failure
- */
-static int
-auditor_iter (void *cls,
- const char *filename)
-{
- struct AuditorIterateContext *aic = cls;
- uint64_t size;
- struct AuditorFileHeaderP *af;
- const struct TALER_AuditorSignatureP *sigs;
- const struct TALER_DenominationKeyValidityPS *dki;
- const char *auditor_url;
- uint32_t dki_len;
- size_t url_len;
- int iret;
-
- if (GNUNET_OK !=
- GNUNET_DISK_file_size (filename,
- &size,
- GNUNET_YES,
- GNUNET_YES))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Skipping inaccessible auditor information file `%s'\n",
- filename);
- return GNUNET_OK;
- }
- if (size < sizeof (struct AuditorFileHeaderP))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "File size (%llu bytes) too small for file `%s' to contain auditor data. Skipping it.\n",
- (unsigned long long) size,
- filename);
- return GNUNET_OK;
- }
- if (size >= GNUNET_MAX_MALLOC_CHECKED)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "File size (%llu bytes) too large for file `%s' to contain auditor data. Skipping it.\n",
- (unsigned long long) size,
- filename);
- return GNUNET_OK;
- }
- af = GNUNET_malloc (size);
- if (((ssize_t) size) !=
- GNUNET_DISK_fn_read (filename,
- af,
- size))
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
- "read",
- filename);
- GNUNET_free (af);
- return GNUNET_OK;
- }
- dki_len = ntohl (af->dki_len);
- if (0 == dki_len)
- {
- GNUNET_break_op (0);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "No signed keys in %s\n",
- filename);
- GNUNET_free (af);
- return GNUNET_OK;
- }
- size -= sizeof (struct AuditorFileHeaderP);
- if ( (size / dki_len) <
- (sizeof (struct TALER_DenominationKeyValidityPS)
- + sizeof (struct TALER_AuditorSignatureP)) )
- {
- GNUNET_break_op (0);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Malformed auditor data file %s (file too short for %u keys)\n",
- filename,
- (unsigned int) dki_len);
- GNUNET_free (af);
- return GNUNET_OK;
- }
- url_len = size - dki_len * (sizeof (struct TALER_DenominationKeyValidityPS)
- + sizeof (struct TALER_AuditorSignatureP));
- sigs = (const struct TALER_AuditorSignatureP *) &af[1];
- dki = (const struct TALER_DenominationKeyValidityPS *) &sigs[dki_len];
- auditor_url = (const char *) &dki[dki_len];
- if ( (0 == url_len) ||
- ('\0' != auditor_url[url_len - 1]) )
- {
- GNUNET_break_op (0);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Malformed auditor data file %s (no 0-terminator)\n",
- filename);
- GNUNET_free (af);
- return GNUNET_OK;
- }
- if (GNUNET_OK !=
- (iret = aic->it (aic->it_cls,
- &af->apub,
- auditor_url,
- &af->mpub,
- dki_len,
- sigs,
- dki)))
- {
- GNUNET_free (af);
- if (GNUNET_SYSERR == iret)
- aic->status = GNUNET_SYSERR;
- return GNUNET_SYSERR;
- }
- aic->status++;
- GNUNET_free (af);
- return GNUNET_OK;
-}
-
-
-/**
- * Call @a it with information for each auditor found in the @a exchange_base_dir.
- *
- * @param cfg configuration to use
- * @param it function to call with auditor information
- * @param it_cls closure for @a it
- * @return -1 on error, 0 if no files were found, otherwise
- * a positive number (however, even with a positive
- * number it is possible that @a it was never called
- * as maybe none of the files were well-formed)
- */
-int
-TALER_EXCHANGEDB_auditor_iterate (const struct GNUNET_CONFIGURATION_Handle *cfg,
- TALER_EXCHANGEDB_AuditorIterator it,
- void *it_cls)
-{
- struct AuditorIterateContext aic;
- int ret;
- char *auditor_base_dir;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (cfg,
- "exchangedb",
- "AUDITOR_BASE_DIR",
- &auditor_base_dir))
- return -1;
- aic.it = it;
- aic.it_cls = it_cls;
- aic.status = 0;
- ret = GNUNET_DISK_directory_scan (auditor_base_dir,
- &auditor_iter,
- &aic);
- GNUNET_free (auditor_base_dir);
- if ( (0 != aic.status) ||
- (GNUNET_OK == ret) )
- return aic.status;
- return ret;
-}
-
-
-/**
- * Write auditor information to the given file.
- *
- * @param filename the file where to write the auditor information to
- * @param apub the auditor's public key
- * @param auditor_url the URL of the auditor
- * @param asigs the auditor's signatures, array of length @a dki_len
- * @param mpub the exchange's public key (as expected by the auditor)
- * @param dki_len length of @a dki
- * @param dki array of denomination coin data signed by the auditor
- * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure.
- */
-int
-TALER_EXCHANGEDB_auditor_write (
- const char *filename,
- const struct TALER_AuditorPublicKeyP *apub,
- const char *auditor_url,
- const struct TALER_AuditorSignatureP *asigs,
- const struct TALER_MasterPublicKeyP *mpub,
- uint32_t dki_len,
- const struct TALER_DenominationKeyValidityPS *dki)
-{
- struct GNUNET_DISK_FileHandle *fh;
- ssize_t wrote;
- size_t wsize;
- int eno;
-
- if (GNUNET_OK !=
- GNUNET_DISK_directory_create_for_file (filename))
- {
- eno = errno;
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "mkdir (for file)",
- filename);
- errno = eno;
- return GNUNET_SYSERR;
- }
- if (NULL == (fh = GNUNET_DISK_file_open
- (filename,
- GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE
- | GNUNET_DISK_OPEN_TRUNCATE,
- GNUNET_DISK_PERM_USER_READ
- | GNUNET_DISK_PERM_USER_WRITE)))
- {
- eno = errno;
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "open",
- filename);
- errno = eno;
- return GNUNET_SYSERR;
- }
- {
- struct AuditorFileHeaderP af = {
- .apub = *apub,
- .mpub = *mpub,
- .dki_len = htonl (dki_len)
- };
-
- wsize = sizeof (struct AuditorFileHeaderP);
- if ( (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh,
- &af,
- wsize))) ||
- (wrote != (ssize_t) wsize) )
- goto cleanup;
- }
- wsize = dki_len * sizeof (struct TALER_AuditorSignatureP);
- if (((ssize_t) wsize) !=
- GNUNET_DISK_file_write (fh,
- asigs,
- wsize))
- goto cleanup;
- wsize = dki_len * sizeof (struct TALER_DenominationKeyValidityPS);
- if (((ssize_t) wsize) !=
- GNUNET_DISK_file_write (fh,
- dki,
- wsize))
- goto cleanup;
- wsize = strlen (auditor_url) + 1;
- if (((ssize_t) wsize) !=
- GNUNET_DISK_file_write (fh,
- auditor_url,
- wsize))
- goto cleanup;
- GNUNET_assert (GNUNET_OK ==
- GNUNET_DISK_file_close (fh));
- return GNUNET_OK;
-cleanup:
- eno = errno;
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "write",
- filename);
- GNUNET_break (GNUNET_OK ==
- GNUNET_DISK_file_close (fh));
- /* try to remove the file, as it must be malformed */
- if (0 != unlink (filename))
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "unlink",
- filename);
- errno = eno;
- return GNUNET_SYSERR;
-}
-
-
-/* end of exchangedb_auditorkeys.c */
diff --git a/src/exchangedb/exchangedb_denomkeys.c b/src/exchangedb/exchangedb_denomkeys.c
deleted file mode 100644
index fe74d1560..000000000
--- a/src/exchangedb/exchangedb_denomkeys.c
+++ /dev/null
@@ -1,537 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2019 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 exchangedb/exchangedb_denomkeys.c
- * @brief I/O operations for the Exchange's denomination private keys
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Sree Harsha Totakura
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_exchangedb_lib.h"
-
-
-GNUNET_NETWORK_STRUCT_BEGIN
-
-/**
- * Contents of a file with a revocation certificate.
- */
-struct RevocationFileP
-{
-
- /**
- * Hash of the denomination public key being revoked.
- */
- struct GNUNET_HashCode denom_hash;
-
- /**
- * Master signature over the revocation, must match purpose
- * #TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED.
- */
- struct TALER_MasterSignatureP msig;
-};
-
-GNUNET_NETWORK_STRUCT_END
-
-
-/**
- * Mark the given denomination key as revoked and request the wallets
- * to initiate /recoup.
- *
- * @param revocation_dir where to write the revocation certificate
- * @param denom_hash hash of the denomination key to revoke
- * @param mpriv master private key to sign with
- * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure.
- */
-int
-TALER_EXCHANGEDB_denomination_key_revoke (
- const char *revocation_dir,
- const struct GNUNET_HashCode *denom_hash,
- const struct TALER_MasterPrivateKeyP *mpriv)
-{
- char *fn;
- int ret;
- struct RevocationFileP rd;
-
- {
- struct TALER_MasterDenominationKeyRevocationPS rm = {
- .purpose.purpose = htonl (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED),
- .purpose.size = htonl (sizeof (rm)),
- .h_denom_pub = *denom_hash
- };
-
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (&mpriv->eddsa_priv,
- &rm.purpose,
- &rd.msig.eddsa_signature));
- }
- GNUNET_asprintf (&fn,
- "%s" DIR_SEPARATOR_STR
- "%s.rev",
- revocation_dir,
- GNUNET_h2s_full (denom_hash));
- rd.denom_hash = *denom_hash;
- ret = (sizeof (rd) !=
- GNUNET_DISK_fn_write (fn,
- &rd,
- sizeof (rd),
- GNUNET_DISK_PERM_USER_READ
- | GNUNET_DISK_PERM_USER_WRITE))
- ? GNUNET_SYSERR
- : GNUNET_OK;
- GNUNET_free (fn);
- return ret;
-}
-
-
-/**
- * Import a denomination key from the given file.
- *
- * @param filename the file to import the key from
- * @param[out] dki set to the imported denomination key
- * @return #GNUNET_OK upon success;
- * #GNUNET_SYSERR upon failure
- */
-int
-TALER_EXCHANGEDB_denomination_key_read (
- const char *filename,
- struct TALER_EXCHANGEDB_DenominationKey *dki)
-{
- uint64_t size;
- size_t offset;
- void *data;
- struct GNUNET_CRYPTO_RsaPrivateKey *priv;
-
- if (GNUNET_OK !=
- GNUNET_DISK_file_size (filename,
- &size,
- GNUNET_YES,
- GNUNET_YES))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Skipping inaccessible denomination key file `%s'\n",
- filename);
- return GNUNET_SYSERR;
- }
- offset = sizeof (struct TALER_EXCHANGEDB_DenominationKeyInformationP);
- if (size <= offset)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "File size (%llu bytes) too small for file `%s' to contain denomination key data. Skipping it.\n",
- (unsigned long long) size,
- filename);
- return GNUNET_SYSERR;
- }
- if (size >= GNUNET_MAX_MALLOC_CHECKED)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "File size (%llu bytes) too large for file `%s' to contain denomination key data. Skipping it.\n",
- (unsigned long long) size,
- filename);
- return GNUNET_OK;
- }
- data = GNUNET_malloc (size);
- if (((ssize_t) size) !=
- GNUNET_DISK_fn_read (filename,
- data,
- size))
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
- "read",
- filename);
- GNUNET_free (data);
- return GNUNET_SYSERR;
- }
- if (NULL ==
- (priv = GNUNET_CRYPTO_rsa_private_key_decode (data + offset,
- size - offset)))
- {
- GNUNET_free (data);
- return GNUNET_SYSERR;
- }
- GNUNET_assert (NULL == dki->denom_priv.rsa_private_key);
- dki->denom_priv.rsa_private_key = priv;
- dki->denom_pub.rsa_public_key
- = GNUNET_CRYPTO_rsa_private_key_get_public (priv);
- memcpy (&dki->issue,
- data,
- offset);
- GNUNET_free (data);
- if (0 == GNUNET_TIME_absolute_get_remaining
- (GNUNET_TIME_absolute_ntoh
- (dki->issue.properties.expire_withdraw)).rel_value_us)
- {
- /* key expired for withdrawal, remove private key to
- minimize chance of compromise */
- if (0 != unlink (filename))
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "unlink",
- filename);
- /* yes, we had an error, but the file content
- was fine and is being returned */
- return GNUNET_OK;
- }
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Exports a denomination key to the given file.
- *
- * @param filename the file where to write the denomination key
- * @param dki the denomination key
- * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure.
- */
-int
-TALER_EXCHANGEDB_denomination_key_write (
- const char *filename,
- const struct TALER_EXCHANGEDB_DenominationKey *dki)
-{
- struct GNUNET_DISK_FileHandle *fh;
- ssize_t wrote;
- size_t wsize;
- int eno;
-
- if (GNUNET_OK !=
- GNUNET_DISK_directory_create_for_file (filename))
- {
- eno = errno;
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "mkdir (for file)",
- filename);
- errno = eno;
- return GNUNET_SYSERR;
- }
- if (NULL == (fh = GNUNET_DISK_file_open
- (filename,
- GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE
- | GNUNET_DISK_OPEN_TRUNCATE
- | GNUNET_DISK_OPEN_FAILIFEXISTS,
- GNUNET_DISK_PERM_USER_READ
- | GNUNET_DISK_PERM_USER_WRITE)))
- {
- eno = errno;
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "open",
- filename);
- errno = eno;
- return GNUNET_SYSERR;
- }
- wsize = sizeof (struct TALER_EXCHANGEDB_DenominationKeyInformationP);
- if ( (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh,
- &dki->issue,
- wsize))) ||
- (wrote != (ssize_t) wsize) )
- goto cleanup;
- {
- void *priv_enc;
- size_t priv_enc_size;
-
- priv_enc_size
- = GNUNET_CRYPTO_rsa_private_key_encode (dki->denom_priv.rsa_private_key,
- &priv_enc);
- wrote = GNUNET_DISK_file_write (fh,
- priv_enc,
- priv_enc_size);
- GNUNET_free (priv_enc);
- if ( (GNUNET_SYSERR == wrote) ||
- (wrote != (ssize_t) priv_enc_size) )
- goto cleanup;
- }
- GNUNET_assert (GNUNET_OK ==
- GNUNET_DISK_file_close (fh));
- return GNUNET_OK;
-
-cleanup:
- eno = errno;
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "write",
- filename);
- GNUNET_break (GNUNET_OK ==
- GNUNET_DISK_file_close (fh));
- /* try to remove the file, as it must be malformed */
- if (0 != unlink (filename))
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "unlink",
- filename);
- errno = eno;
- return GNUNET_SYSERR;
-}
-
-
-/**
- * Closure for #denomkeys_iterate_keydir_iter() and
- * #denomkeys_iterate_topdir_iter().
- */
-struct DenomkeysIterateContext
-{
-
- /**
- * Set to the name of the directory below the top-level directory
- * during the call to #denomkeys_iterate_keydir_iter().
- */
- const char *alias;
-
- /**
- * Function to call on each denomination key.
- */
- TALER_EXCHANGEDB_DenominationKeyIterator it;
-
- /**
- * Closure for @e it.
- */
- void *it_cls;
-};
-
-
-/**
- * Decode the denomination key in the given file @a filename and call
- * the callback in @a cls with the information.
- *
- * @param cls the `struct DenomkeysIterateContext *`
- * @param filename name of a file that should contain
- * a denomination key
- * @return #GNUNET_OK to continue to iterate
- * #GNUNET_NO to abort iteration with success
- * #GNUNET_SYSERR to abort iteration with failure
- */
-static int
-denomkeys_iterate_keydir_iter (void *cls,
- const char *filename)
-{
- struct DenomkeysIterateContext *dic = cls;
- struct TALER_EXCHANGEDB_DenominationKey issue;
- int ret;
-
- memset (&issue, 0, sizeof (issue));
- if (GNUNET_OK !=
- TALER_EXCHANGEDB_denomination_key_read (filename,
- &issue))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Invalid denomkey file: '%s'\n",
- filename);
- return GNUNET_OK;
- }
- ret = dic->it (dic->it_cls,
- dic->alias,
- &issue);
- GNUNET_CRYPTO_rsa_private_key_free (issue.denom_priv.rsa_private_key);
- GNUNET_CRYPTO_rsa_public_key_free (issue.denom_pub.rsa_public_key);
- return ret;
-}
-
-
-/**
- * Function called on each subdirectory in the #TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS. Will
- * call the #denomkeys_iterate_keydir_iter() on each file in the
- * subdirectory.
- *
- * @param cls the `struct DenomkeysIterateContext *`
- * @param filename name of the subdirectory to scan
- * @return #GNUNET_OK on success,
- * #GNUNET_SYSERR if we need to abort
- */
-static int
-denomkeys_iterate_topdir_iter (void *cls,
- const char *filename)
-{
- struct DenomkeysIterateContext *dic = cls;
-
- dic->alias = GNUNET_STRINGS_get_short_name (filename);
- if (0 > GNUNET_DISK_directory_scan (filename,
- &denomkeys_iterate_keydir_iter,
- dic))
- return GNUNET_SYSERR;
- return GNUNET_OK;
-}
-
-
-/**
- * Call @a it for each denomination key found in the @a exchange_base_dir.
- *
- * @param exchange_base_dir base directory for the exchange,
- * the signing keys must be in the #TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS
- * subdirectory
- * @param it function to call on each denomination key found
- * @param it_cls closure for @a it
- * @return -1 on error, 0 if no files were found, otherwise
- * a positive number (however, even with a positive
- * number it is possible that @a it was never called
- * as maybe none of the files were well-formed)
- */
-int
-TALER_EXCHANGEDB_denomination_keys_iterate (
- const char *exchange_base_dir,
- TALER_EXCHANGEDB_DenominationKeyIterator it,
- void *it_cls)
-{
- struct DenomkeysIterateContext dic = {
- .it = it,
- .it_cls = it_cls
- };
- char *dir;
- int ret;
-
- GNUNET_asprintf (&dir,
- "%s" DIR_SEPARATOR_STR
- TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS,
- exchange_base_dir);
- ret = GNUNET_DISK_directory_scan (dir,
- &denomkeys_iterate_topdir_iter,
- &dic);
- GNUNET_free (dir);
- return ret;
-}
-
-
-/**
- * Closure for #revocations_iterate_cb().
- */
-struct RevocationsIterateContext
-{
-
- /**
- * Function to call on each revoked denomination key.
- */
- TALER_EXCHANGEDB_RevocationIterator it;
-
- /**
- * Closure for @e it.
- */
- void *it_cls;
-
- /**
- * Master public key to use to validate revocations.
- */
- const struct TALER_MasterPublicKeyP *master_pub;
-
-};
-
-
-/**
- * Decode the revocation certificate in the given file @a filename and call
- * the callback in @a cls with the information.
- *
- * @param cls the `struct RevocationsIterateContext *`
- * @param filename name of a file that should contain
- * a denomination key
- * @return #GNUNET_OK to continue to iterate
- * #GNUNET_NO to abort iteration with success
- * #GNUNET_SYSERR to abort iteration with failure
- */
-static int
-revocations_iterate_cb (void *cls,
- const char *filename)
-{
- struct RevocationsIterateContext *ric = cls;
- struct RevocationFileP rf;
- ssize_t rd;
-
- /* Check if revocation is valid... */
- rd = GNUNET_DISK_fn_read (filename,
- &rf,
- sizeof (rf));
- if (GNUNET_SYSERR == rd)
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
- "read",
- filename);
- return GNUNET_OK;
- }
- if (sizeof (rf) != (size_t) rd)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid revocation file `%s' found and ignored (bad size: %llu)\n",
- filename,
- (unsigned long long) rd);
- return GNUNET_OK;
- }
-
- {
- struct TALER_MasterDenominationKeyRevocationPS rm = {
- .purpose.purpose = htonl (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED),
- .purpose.size = htonl (sizeof (rm)),
- .h_denom_pub = rf.denom_hash
- };
-
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED,
- &rm.purpose,
- &rf.msig.eddsa_signature,
- &ric->master_pub->eddsa_pub))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid revocation file `%s' found and ignored (bad signature)\n",
- filename);
- return GNUNET_OK;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Denomination key `%s' was revoked!\n",
- GNUNET_h2s (&rm.h_denom_pub));
- return ric->it (ric->it_cls,
- &rm.h_denom_pub,
- &rf.msig);
- }
-}
-
-
-/**
- * Call @a it for each revoked denomination key found in the @a revocation_dir.
- *
- * @param revocation_dir base directory where revocations are stored
- * @param master_pub master public key (used to check revocations)
- * @param it function to call on each revoked denomination key found
- * @param it_cls closure for @a it
- * @return -1 on error, 0 if no files were found, otherwise
- * a positive number (however, even with a positive
- * number it is possible that @a it was never called
- * as maybe none of the files were well-formed)
- */
-int
-TALER_EXCHANGEDB_revocations_iterate (const char *revocation_dir,
- const struct
- TALER_MasterPublicKeyP *master_pub,
- TALER_EXCHANGEDB_RevocationIterator it,
- void *it_cls)
-{
- struct RevocationsIterateContext ric = {
- .it = it,
- .it_cls = it_cls,
- .master_pub = master_pub
- };
-
- if (GNUNET_OK !=
- GNUNET_DISK_directory_create (revocation_dir))
- {
- /* directory doesn't exist and we couldn't even create it,
- clearly means there are no revocations there */
- return 0;
- }
- return GNUNET_DISK_directory_scan (revocation_dir,
- &revocations_iterate_cb,
- &ric);
-}
-
-
-/* end of exchangedb_denomkeys.c */
diff --git a/src/exchangedb/exchangedb_fees.c b/src/exchangedb/exchangedb_fees.c
deleted file mode 100644
index 070f16eee..000000000
--- a/src/exchangedb/exchangedb_fees.c
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2017 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 exchangedb/exchangedb_fees.c
- * @brief Logic to read/write/convert aggregation wire fees (not other fees!)
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_exchangedb_lib.h"
-
-
-GNUNET_NETWORK_STRUCT_BEGIN
-
-/**
- * Structure for wire fees on disk.
- */
-struct TALER_WireFeeDiskP
-{
-
- /**
- * Wire fee details.
- */
- struct TALER_MasterWireFeePS wf;
-
-
- /**
- * Signature affirming the above fee structure.
- */
- struct TALER_MasterSignatureP master_sig;
-
-};
-
-GNUNET_NETWORK_STRUCT_END
-
-
-/**
- * Convert @a wd disk format to host format.
- *
- * @param wd aggregate fees, disk format
- * @return fees in host format
- */
-static struct TALER_EXCHANGEDB_AggregateFees *
-wd2af (const struct TALER_WireFeeDiskP *wd)
-{
- struct TALER_EXCHANGEDB_AggregateFees *af;
-
- af = GNUNET_new (struct TALER_EXCHANGEDB_AggregateFees);
- af->start_date = GNUNET_TIME_absolute_ntoh (wd->wf.start_date);
- af->end_date = GNUNET_TIME_absolute_ntoh (wd->wf.end_date);
- TALER_amount_ntoh (&af->wire_fee,
- &wd->wf.wire_fee);
- TALER_amount_ntoh (&af->closing_fee,
- &wd->wf.closing_fee);
- af->master_sig = wd->master_sig;
- return af;
-}
-
-
-/**
- * Read the current fee structure from disk.
- *
- * @param cfg configuration to use
- * @param wireplugin name of the wire plugin to read fees for
- * @return sorted list of aggregation fees, NULL on error
- */
-struct TALER_EXCHANGEDB_AggregateFees *
-TALER_EXCHANGEDB_fees_read (const struct GNUNET_CONFIGURATION_Handle *cfg,
- const char *wireplugin)
-{
- char *wirefee_base_dir;
- char *fn;
- struct GNUNET_DISK_FileHandle *fh;
- struct TALER_WireFeeDiskP wd;
- struct TALER_EXCHANGEDB_AggregateFees *af;
- struct TALER_EXCHANGEDB_AggregateFees *endp;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (cfg,
- "exchangedb",
- "WIREFEE_BASE_DIR",
- &wirefee_base_dir))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
- "exchangedb",
- "WIREFEE_BASE_DIR");
- return NULL;
- }
- GNUNET_asprintf (&fn,
- "%s/%s.fee",
- wirefee_base_dir,
- wireplugin);
- GNUNET_free (wirefee_base_dir);
- fh = GNUNET_DISK_file_open (fn,
- GNUNET_DISK_OPEN_READ,
- GNUNET_DISK_PERM_NONE);
- if (NULL == fh)
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "open",
- fn);
- GNUNET_free (fn);
- return NULL;
- }
-
- af = NULL;
- endp = NULL;
- while (1)
- {
- struct TALER_EXCHANGEDB_AggregateFees *n;
- ssize_t in = GNUNET_DISK_file_read (fh,
- &wd,
- sizeof (wd));
- if (-1 == in)
- {
- /* Unexpected I/O error */
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "read",
- fn);
- GNUNET_break (GNUNET_OK ==
- GNUNET_DISK_file_close (fh));
- GNUNET_free (fn);
- TALER_EXCHANGEDB_fees_free (af);
- return NULL;
- }
- if (0 == in)
- break; /* EOF, terminate normally */
- if (sizeof (wd) != in)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "File `%s' has wrong size for fee structure\n",
- fn);
- GNUNET_break (GNUNET_OK ==
- GNUNET_DISK_file_close (fh));
- GNUNET_free (fn);
- TALER_EXCHANGEDB_fees_free (af);
- return NULL;
- }
- n = wd2af (&wd);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Loaded wire fees starting at %s from file\n",
- GNUNET_STRINGS_absolute_time_to_string (n->start_date));
- if ( ( (NULL == af) ||
- (endp->end_date.abs_value_us == n->start_date.abs_value_us) ) &&
- (n->start_date.abs_value_us < n->end_date.abs_value_us) )
- {
- /* append to list */
- if (NULL != endp)
- endp->next = n;
- else
- af = n;
- endp = n;
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "File `%s' does not have wire fees in chronological order\n",
- fn);
- GNUNET_DISK_file_close (fh);
- GNUNET_free (n);
- GNUNET_free (fn);
- TALER_EXCHANGEDB_fees_free (af);
- return NULL;
- }
- }
- GNUNET_assert (GNUNET_OK ==
- GNUNET_DISK_file_close (fh));
- GNUNET_free (fn);
- return af;
-}
-
-
-/**
- * Convert @a af to @a wf.
- *
- * @param wiremethod name of the wire method the fees are for
- * @param[in,out] af aggregate fees, host format (updated to round time)
- * @param[out] wf aggregate fees, disk / signature format
- */
-void
-TALER_EXCHANGEDB_fees_2_wf (const char *wiremethod,
- struct TALER_EXCHANGEDB_AggregateFees *af,
- struct TALER_MasterWireFeePS *wf)
-{
- (void) GNUNET_TIME_round_abs (&af->start_date);
- (void) GNUNET_TIME_round_abs (&af->end_date);
- wf->purpose.size = htonl (sizeof (*wf));
- wf->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_FEES);
- GNUNET_CRYPTO_hash (wiremethod,
- strlen (wiremethod) + 1,
- &wf->h_wire_method);
- wf->start_date = GNUNET_TIME_absolute_hton (af->start_date);
- wf->end_date = GNUNET_TIME_absolute_hton (af->end_date);
- TALER_amount_hton (&wf->wire_fee,
- &af->wire_fee);
- TALER_amount_hton (&wf->closing_fee,
- &af->closing_fee);
-}
-
-
-/**
- * Write given fee structure to disk.
- *
- * @param filename where to write the fees
- * @param wireplugin which plugin the fees are about
- * @param af fee structure to write
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
- */
-int
-TALER_EXCHANGEDB_fees_write (const char *filename,
- const char *wireplugin,
- struct TALER_EXCHANGEDB_AggregateFees *af)
-{
- struct GNUNET_DISK_FileHandle *fh;
- struct TALER_WireFeeDiskP wd;
- struct TALER_EXCHANGEDB_AggregateFees *last;
-
- if (GNUNET_OK !=
- GNUNET_DISK_directory_create_for_file (filename))
- {
- int eno;
-
- eno = errno;
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "mkdir (for file)",
- filename);
- errno = eno;
- return GNUNET_SYSERR;
- }
-
- fh = GNUNET_DISK_file_open (filename,
- GNUNET_DISK_OPEN_WRITE
- | GNUNET_DISK_OPEN_TRUNCATE
- | GNUNET_DISK_OPEN_CREATE,
- GNUNET_DISK_PERM_USER_READ
- | GNUNET_DISK_PERM_USER_WRITE);
- if (NULL == fh)
- {
- int eno;
-
- eno = errno;
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "open",
- filename);
- errno = eno;
- return GNUNET_SYSERR;
- }
-
- last = NULL;
- while (NULL != af)
- {
- if ( ( (NULL != last) &&
- (last->end_date.abs_value_us != af->start_date.abs_value_us) ) ||
- (af->start_date.abs_value_us >= af->end_date.abs_value_us) )
- {
- /* @a af malformed, refusing to write file that will be rejected */
- GNUNET_break (0);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_DISK_file_close (fh));
- /* try to remove the file, as it would be malformed */
- if (0 != unlink (filename))
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "unlink",
- filename);
- errno = EINVAL; /* invalid inputs */
- return GNUNET_SYSERR;
- }
- TALER_EXCHANGEDB_fees_2_wf (wireplugin,
- af,
- &wd.wf);
- wd.master_sig = af->master_sig;
- last = af;
- af = af->next;
- if (sizeof (wd) !=
- GNUNET_DISK_file_write (fh,
- &wd,
- sizeof (wd)))
- {
- int eno = errno;
-
- GNUNET_break (GNUNET_OK ==
- GNUNET_DISK_file_close (fh));
- /* try to remove the file, as it must be malformed */
- if (0 != unlink (filename))
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "unlink",
- filename);
- errno = eno;
- return GNUNET_SYSERR;
- }
- }
- GNUNET_assert (GNUNET_OK ==
- GNUNET_DISK_file_close (fh));
- return GNUNET_OK;
-}
-
-
-/**
- * Free @a af data structure
- *
- * @param af list to free
- */
-void
-TALER_EXCHANGEDB_fees_free (struct TALER_EXCHANGEDB_AggregateFees *af)
-{
- while (NULL != af)
- {
- struct TALER_EXCHANGEDB_AggregateFees *next;
-
- next = af->next;
- GNUNET_free (af);
- af = next;
- }
-}
-
-
-/**
- * Find the record valid at time @a now in the fee structure.
- *
- * @param wa wire transfer fee data structure to update
- * @param now timestamp to update fees to
- * @return fee valid at @a now, or NULL if unknown
- */
-static struct TALER_EXCHANGEDB_AggregateFees *
-advance_fees (struct TALER_EXCHANGEDB_WireAccount *wa,
- struct GNUNET_TIME_Absolute now)
-{
- struct TALER_EXCHANGEDB_AggregateFees *af;
-
- af = wa->af;
- while ( (NULL != af) &&
- (af->end_date.abs_value_us < now.abs_value_us) )
- af = af->next;
- return af;
-}
-
-
-/**
- * Update wire transfer fee data structure in @a wa.
- *
- * @param cfg configuration to use
- * @param db_plugin database plugin to use
- * @param wa wire account data structure to update
- * @param now timestamp to update fees to
- * @param session DB session to use
- * @return fee valid at @a now, or NULL if unknown
- */
-struct TALER_EXCHANGEDB_AggregateFees *
-TALER_EXCHANGEDB_update_fees (const struct GNUNET_CONFIGURATION_Handle *cfg,
- struct TALER_EXCHANGEDB_Plugin *db_plugin,
- struct TALER_EXCHANGEDB_WireAccount *wa,
- struct GNUNET_TIME_Absolute now,
- struct TALER_EXCHANGEDB_Session *session)
-{
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_EXCHANGEDB_AggregateFees *af;
-
- af = advance_fees (wa,
- now);
- if (NULL != af)
- return af;
- /* Let's try to load it from disk... */
- wa->af = TALER_EXCHANGEDB_fees_read (cfg,
- wa->method);
- for (struct TALER_EXCHANGEDB_AggregateFees *p = wa->af;
- NULL != p;
- p = p->next)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Persisting fees starting at %s in database\n",
- GNUNET_STRINGS_absolute_time_to_string (p->start_date));
- qs = db_plugin->insert_wire_fee (db_plugin->cls,
- session,
- wa->method,
- p->start_date,
- p->end_date,
- &p->wire_fee,
- &p->closing_fee,
- &p->master_sig);
- if (qs < 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- TALER_EXCHANGEDB_fees_free (wa->af);
- wa->af = NULL;
- return NULL;
- }
- }
- af = advance_fees (wa,
- now);
- if (NULL != af)
- return af;
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to find current wire transfer fees for `%s' at %s\n",
- wa->method,
- GNUNET_STRINGS_absolute_time_to_string (now));
- return NULL;
-}
-
-
-/* end of exchangedb_fees.c */
diff --git a/src/exchangedb/exchangedb_plugin.c b/src/exchangedb/exchangedb_plugin.c
index 21bb032f3..5dc41d988 100644
--- a/src/exchangedb/exchangedb_plugin.c
+++ b/src/exchangedb/exchangedb_plugin.c
@@ -24,12 +24,6 @@
#include <ltdl.h>
-/**
- * Initialize the plugin.
- *
- * @param cfg configuration to use
- * @return #GNUNET_OK on success
- */
struct TALER_EXCHANGEDB_Plugin *
TALER_EXCHANGEDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
@@ -62,11 +56,6 @@ TALER_EXCHANGEDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg)
}
-/**
- * Shutdown the plugin.
- *
- * @param plugin the plugin to unload
- */
void
TALER_EXCHANGEDB_plugin_unload (struct TALER_EXCHANGEDB_Plugin *plugin)
{
diff --git a/src/exchangedb/exchangedb_signkeys.c b/src/exchangedb/exchangedb_signkeys.c
deleted file mode 100644
index b1e8aad54..000000000
--- a/src/exchangedb/exchangedb_signkeys.c
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014, 2015, 2016, 2020 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 exchangedb/exchangedb_signkeys.c
- * @brief I/O operations for the Exchange's private online signing keys
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Sree Harsha Totakura
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_exchangedb_lib.h"
-
-
-/**
- * Closure for the #signkeys_iterate_dir_iter().
- */
-struct SignkeysIterateContext
-{
-
- /**
- * Function to call on each signing key.
- */
- TALER_EXCHANGEDB_SigningKeyIterator it;
-
- /**
- * Closure for @e it.
- */
- void *it_cls;
-};
-
-
-/**
- * Function called on each file in the directory with our signing
- * keys. Parses the file and calls the iterator from @a cls.
- *
- * @param cls the `struct SignkeysIterateContext *`
- * @param filename name of the file to parse
- * @return #GNUNET_OK to continue,
- * #GNUNET_NO to stop iteration without error,
- * #GNUNET_SYSERR to stop iteration with error
- */
-static int
-signkeys_iterate_dir_iter (void *cls,
- const char *filename)
-{
- struct SignkeysIterateContext *skc = cls;
- ssize_t nread;
- struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP issue;
-
- nread = GNUNET_DISK_fn_read (filename,
- &issue,
- sizeof (struct
- TALER_EXCHANGEDB_PrivateSigningKeyInformationP));
- if (nread != sizeof (struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Invalid signkey file `%s': wrong size (%d, expected %u)\n",
- filename,
- (int) nread,
- (unsigned int) sizeof (struct
- TALER_EXCHANGEDB_PrivateSigningKeyInformationP));
- return GNUNET_OK;
- }
- if (0 == GNUNET_TIME_absolute_get_remaining
- (GNUNET_TIME_absolute_ntoh (issue.issue.expire)).rel_value_us)
- {
- if (0 != unlink (filename))
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "unlink",
- filename);
- return GNUNET_OK; /* yes, we had an error, but continue to iterate anyway */
- }
- /* Expired file deleted, continue to iterate -without- calling iterator
- as this key is expired */
- return GNUNET_OK;
- }
- return skc->it (skc->it_cls,
- filename,
- &issue);
-}
-
-
-/**
- * Call @a it for each signing key found in the @a exchange_base_dir.
- *
- * @param exchange_base_dir base directory for the exchange,
- * the signing keys must be in the #TALER_EXCHANGEDB_DIR_SIGNING_KEYS
- * subdirectory
- * @param it function to call on each signing key
- * @param it_cls closure for @a it
- * @return number of files found (may not match
- * number of keys given to @a it as malformed
- * files are simply skipped), -1 on error
- */
-int
-TALER_EXCHANGEDB_signing_keys_iterate (const char *exchange_base_dir,
- TALER_EXCHANGEDB_SigningKeyIterator it,
- void *it_cls)
-{
- struct SignkeysIterateContext skc = {
- .it = it,
- .it_cls = it_cls
- };
- char *signkey_dir;
- int ret;
-
- GNUNET_asprintf (&signkey_dir,
- "%s" DIR_SEPARATOR_STR TALER_EXCHANGEDB_DIR_SIGNING_KEYS,
- exchange_base_dir);
- ret = GNUNET_DISK_directory_scan (signkey_dir,
- &signkeys_iterate_dir_iter,
- &skc);
- GNUNET_free (signkey_dir);
- return ret;
-}
-
-
-/**
- * Exports a signing key to the given file.
- *
- * @param exchange_base_dir base directory for the keys
- * @param start start time of the validity for the key
- * @param ski the signing key
- * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure.
- */
-int
-TALER_EXCHANGEDB_signing_key_write (
- const char *exchange_base_dir,
- struct GNUNET_TIME_Absolute start,
- const struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP *ski)
-{
- char *skf;
- ssize_t nwrite;
-
- GNUNET_asprintf (&skf,
- "%s" DIR_SEPARATOR_STR TALER_EXCHANGEDB_DIR_SIGNING_KEYS
- DIR_SEPARATOR_STR "%llu",
- exchange_base_dir,
- (unsigned long long) start.abs_value_us);
- if (GNUNET_OK !=
- GNUNET_DISK_directory_create_for_file (skf))
- {
- int eno;
-
- eno = errno;
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "mkdir (for file)",
- skf);
- errno = eno;
- return GNUNET_SYSERR;
- }
- nwrite = GNUNET_DISK_fn_write (skf,
- ski,
- sizeof (struct
- TALER_EXCHANGEDB_PrivateSigningKeyInformationP),
- GNUNET_DISK_PERM_USER_WRITE
- | GNUNET_DISK_PERM_USER_READ);
- if (sizeof (struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP) !=
- (size_t) nwrite)
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "write",
- skf);
- GNUNET_free (skf);
- return GNUNET_SYSERR;
- }
- GNUNET_free (skf);
- return GNUNET_OK;
-}
-
-
-/* end of exchangedb_signkeys.c */
diff --git a/src/exchangedb/exchangedb_transactions.c b/src/exchangedb/exchangedb_transactions.c
index ade7f9cf2..f78393776 100644
--- a/src/exchangedb/exchangedb_transactions.c
+++ b/src/exchangedb/exchangedb_transactions.c
@@ -22,17 +22,7 @@
#include "taler_exchangedb_lib.h"
-/**
- * Calculate the total value of all transactions performed.
- * Stores @a off plus the cost of all transactions in @a tl
- * in @a ret.
- *
- * @param tl transaction list to process
- * @param off offset to use as the starting value
- * @param[out] ret where the resulting total is to be stored (may alias @a off)
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-int
+enum GNUNET_GenericReturnValue
TALER_EXCHANGEDB_calculate_transaction_list_totals (
struct TALER_EXCHANGEDB_TransactionList *tl,
const struct TALER_Amount *off,
@@ -41,12 +31,12 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
struct TALER_Amount spent = *off;
struct TALER_Amount refunded;
struct TALER_Amount deposit_fee;
- int have_refund;
+ bool have_refund;
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (spent.currency,
+ TALER_amount_set_zero (spent.currency,
&refunded));
- have_refund = GNUNET_NO;
+ have_refund = false;
for (struct TALER_EXCHANGEDB_TransactionList *pos = tl;
NULL != pos;
pos = pos->next)
@@ -55,7 +45,7 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
{
case TALER_EXCHANGEDB_TT_DEPOSIT:
/* spent += pos->amount_with_fee */
- if (GNUNET_OK !=
+ if (0 >
TALER_amount_add (&spent,
&spent,
&pos->details.deposit->amount_with_fee))
@@ -67,7 +57,7 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
break;
case TALER_EXCHANGEDB_TT_MELT:
/* spent += pos->amount_with_fee */
- if (GNUNET_OK !=
+ if (0 >
TALER_amount_add (&spent,
&spent,
&pos->details.melt->amount_with_fee))
@@ -78,7 +68,7 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
break;
case TALER_EXCHANGEDB_TT_REFUND:
/* refunded += pos->refund_amount - pos->refund_fee */
- if (GNUNET_OK !=
+ if (0 >
TALER_amount_add (&refunded,
&refunded,
&pos->details.refund->refund_amount))
@@ -86,7 +76,7 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
GNUNET_break (0);
return GNUNET_SYSERR;
}
- if (GNUNET_OK !=
+ if (0 >
TALER_amount_add (&spent,
&spent,
&pos->details.refund->refund_fee))
@@ -94,11 +84,11 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
GNUNET_break (0);
return GNUNET_SYSERR;
}
- have_refund = GNUNET_YES;
+ have_refund = true;
break;
case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
/* refunded += pos->value */
- if (GNUNET_OK !=
+ if (0 >
TALER_amount_add (&refunded,
&refunded,
&pos->details.old_coin_recoup->value))
@@ -109,7 +99,7 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
break;
case TALER_EXCHANGEDB_TT_RECOUP:
/* spent += pos->value */
- if (GNUNET_OK !=
+ if (0 >
TALER_amount_add (&spent,
&spent,
&pos->details.recoup->value))
@@ -120,7 +110,7 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
break;
case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
/* spent += pos->value */
- if (GNUNET_OK !=
+ if (0 >
TALER_amount_add (&spent,
&spent,
&pos->details.recoup_refresh->value))
@@ -129,12 +119,55 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
return GNUNET_SYSERR;
}
break;
+ case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
+ /* spent += pos->amount_with_fee */
+ if (0 >
+ TALER_amount_add (&spent,
+ &spent,
+ &pos->details.purse_deposit->amount))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ deposit_fee = pos->details.purse_deposit->deposit_fee;
+ break;
+ case TALER_EXCHANGEDB_TT_PURSE_REFUND:
+ /* refunded += pos->refund_amount - pos->refund_fee */
+ if (0 >
+ TALER_amount_add (&refunded,
+ &refunded,
+ &pos->details.purse_refund->refund_amount))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_add (&spent,
+ &spent,
+ &pos->details.purse_refund->refund_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ have_refund = true;
+ break;
+ case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
+ /* spent += pos->amount_with_fee */
+ if (0 >
+ TALER_amount_add (&spent,
+ &spent,
+ &pos->details.reserve_open->coin_contribution))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ break;
}
}
if (have_refund)
{
/* If we gave any refund, also discount ONE deposit fee */
- if (GNUNET_OK !=
+ if (0 >
TALER_amount_add (&refunded,
&refunded,
&deposit_fee))
@@ -144,7 +177,7 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
}
}
/* spent = spent - refunded */
- if (GNUNET_SYSERR ==
+ if (0 >
TALER_amount_subtract (&spent,
&spent,
&refunded))
@@ -152,7 +185,6 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
GNUNET_break (0);
return GNUNET_SYSERR;
}
-
*ret = spent;
return GNUNET_OK;
}
diff --git a/src/exchangedb/perf-exchangedb-reserves-in-insert-postgres b/src/exchangedb/perf-exchangedb-reserves-in-insert-postgres
new file mode 100755
index 000000000..8eafde3ce
--- /dev/null
+++ b/src/exchangedb/perf-exchangedb-reserves-in-insert-postgres
@@ -0,0 +1,210 @@
+#! /bin/bash
+
+# perf-exchangedb-reserves-in-insert-postgres - temporary wrapper script for .libs/perf-exchangedb-reserves-in-insert-postgres
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15
+#
+# The perf-exchangedb-reserves-in-insert-postgres program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.6'
+ notinst_deplibs=' libtalerexchangedb.la ../../src/json/libtalerjson.la ../../src/util/libtalerutil.la ../../src/pq/libtalerpq.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "perf-exchangedb-reserves-in-insert-postgres:perf-exchangedb-reserves-in-insert-postgres:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-15" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "perf-exchangedb-reserves-in-insert-postgres:perf-exchangedb-reserves-in-insert-postgres:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "perf-exchangedb-reserves-in-insert-postgres:perf-exchangedb-reserves-in-insert-postgres:$LINENO: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program='perf-exchangedb-reserves-in-insert-postgres'
+ progdir="$thisdir/.libs"
+
+
+ if test -f "$progdir/$program"; then
+ # Add our own library path to LD_LIBRARY_PATH
+ LD_LIBRARY_PATH="/home/priscilla/exchange/src/exchangedb/.libs:/home/priscilla/exchange/src/json/.libs:/home/priscilla/exchange/src/util/.libs:/home/priscilla/exchange/src/pq/.libs:$LD_LIBRARY_PATH"
+
+ # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
+
+ export LD_LIBRARY_PATH
+
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/exchangedb/perf_deposits_get_ready.c b/src/exchangedb/perf_deposits_get_ready.c
new file mode 100644
index 000000000..005ea6843
--- /dev/null
+++ b/src/exchangedb/perf_deposits_get_ready.c
@@ -0,0 +1,565 @@
+/*
+ This file is part of TALER
+ 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
+ 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 exchangedb/perf_deposits_get_ready.c
+ * @brief benchmark for deposits_get_ready
+git * @author Joseph Xu
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "math.h"
+
+/**
+ * Global result from the testcase.
+ */
+static int result;
+
+/**
+ * Report line of error if @a cond is true, and jump to label "drop".
+ */
+#define FAILIF(cond) \
+ do { \
+ if (! (cond)) {break;} \
+ GNUNET_break (0); \
+ goto drop; \
+ } while (0)
+
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, \
+ sizeof (*ptr))
+
+/**
+ * Initializes @a ptr with zeros.
+ */
+#define ZR_BLK(ptr) \
+ memset (ptr, 0, sizeof (*ptr))
+
+/**
+ * Currency we use. Must match test-exchange-db-*.conf.
+ */
+#define CURRENCY "EUR"
+#define RSA_KEY_SIZE 1024
+#define NUM_ROWS 1000
+#define ROUNDS 100
+#define MELT_NEW_COINS 5
+#define MELT_NOREVEAL_INDEX 1
+
+/**
+ * Database plugin under test.
+ */
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+
+static struct TALER_DenomFeeSet fees;
+
+static struct TALER_MerchantWireHashP h_wire_wt;
+
+/**
+ * Denomination keys used for fresh coins in melt test.
+ */
+static struct DenomKeyPair **new_dkp;
+
+static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins;
+
+struct DenomKeyPair
+{
+ struct TALER_DenominationPrivateKey priv;
+ struct TALER_DenominationPublicKey pub;
+};
+
+
+/**
+ * Destroy a denomination key pair. The key is not necessarily removed from the DB.
+ *
+ * @param dkp the key pair to destroy
+ */
+static void
+destroy_denom_key_pair (struct DenomKeyPair *dkp)
+{
+ TALER_denom_pub_free (&dkp->pub);
+ TALER_denom_priv_free (&dkp->priv);
+ GNUNET_free (dkp);
+}
+
+
+/**
+ * Create a denomination key pair by registering the denomination in the DB.
+ *
+ * @param size the size of the denomination key
+ * @param now time to use for key generation, legal expiration will be 3h later.
+ * @param fees fees to use
+ * @return the denominaiton key pair; NULL upon error
+ */
+static struct DenomKeyPair *
+create_denom_key_pair (unsigned int size,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_Amount *value,
+ const struct TALER_DenomFeeSet *fees)
+{
+ struct DenomKeyPair *dkp;
+ struct TALER_EXCHANGEDB_DenominationKey dki;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue2;
+
+ dkp = GNUNET_new (struct DenomKeyPair);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_priv_create (&dkp->priv,
+ &dkp->pub,
+ GNUNET_CRYPTO_BSA_RSA,
+ size));
+ memset (&dki,
+ 0,
+ sizeof (struct TALER_EXCHANGEDB_DenominationKey));
+ dki.denom_pub = dkp->pub;
+ dki.issue.start = now;
+ dki.issue.expire_withdraw
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_UNIT_HOURS));
+ dki.issue.expire_deposit
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 2)));
+ dki.issue.expire_legal
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 3)));
+ dki.issue.value = *value;
+ dki.issue.fees = *fees;
+ TALER_denom_pub_hash (&dkp->pub,
+ &dki.issue.denom_hash);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_denomination_info (plugin->cls,
+ &dki.denom_pub,
+ &dki.issue))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ memset (&issue2, 0, sizeof (issue2));
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_denomination_info (plugin->cls,
+ &dki.issue.denom_hash,
+ &issue2))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ if (0 != GNUNET_memcmp (&dki.issue,
+ &issue2))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ return dkp;
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+
+static void
+run (void *cls)
+{
+ struct TALER_EXCHANGEDB_Refresh refresh;
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ const uint32_t num_partitions = 10;
+ struct TALER_Amount value;
+ struct TALER_EXCHANGEDB_CollectableBlindcoin cbc;
+ struct TALER_DenominationPublicKey *new_denom_pubs = NULL;
+ struct GNUNET_TIME_Relative times = GNUNET_TIME_UNIT_ZERO;
+ unsigned long long sqrs = 0;
+ struct TALER_EXCHANGEDB_CoinDepositInformation *depos;
+ struct TALER_EXCHANGEDB_BatchDeposit bd;
+ struct TALER_EXCHANGEDB_Refund *ref;
+ unsigned int *perm;
+ unsigned long long duration_sq;
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
+ struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = GNUNET_CRYPTO_BSA_RSA,
+ .rc = 0
+ };
+ struct TALER_ExchangeWithdrawValues alg_values = {
+ .blinding_inputs = &bi
+ };
+
+ ref = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_Refund);
+ depos = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_CoinDepositInformation);
+
+ if (NULL ==
+ (plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_break (0);
+ result = 77;
+ return;
+ }
+ (void) plugin->drop_tables (plugin->cls);
+ if (GNUNET_OK !=
+ plugin->create_tables (plugin->cls,
+ true,
+ num_partitions))
+ {
+ GNUNET_break (0);
+ result = 77;
+ goto cleanup;
+ }
+ if (GNUNET_OK !=
+ plugin->preflight (plugin->cls))
+ {
+ GNUNET_break (0);
+ goto cleanup;
+ }
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.000010",
+ &value));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.withdraw));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.deposit));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.refresh));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.refund));
+ {
+ ZR_BLK (&cbc);
+ new_dkp = GNUNET_new_array (MELT_NEW_COINS,
+ struct DenomKeyPair *);
+ new_denom_pubs = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_DenominationPublicKey);
+ revealed_coins
+ = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin);
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_CRYPTO_RsaBlindedMessage *rp;
+ struct TALER_BlindedPlanchet *bp;
+
+ now = GNUNET_TIME_timestamp_get ();
+ new_dkp[cnt] = create_denom_key_pair (RSA_KEY_SIZE,
+ now,
+ &value,
+ &fees);
+ GNUNET_assert (NULL != new_dkp[cnt]);
+ new_denom_pubs[cnt] = new_dkp[cnt]->pub;
+ ccoin = &revealed_coins[cnt];
+ bp = &ccoin->blinded_planchet;
+ bp->blinded_message = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
+ bp->blinded_message->cipher = GNUNET_CRYPTO_BSA_RSA;
+ bp->blinded_message->rc = 1;
+ rp = &bp->blinded_message->details.rsa_blinded_message;
+ rp->blinded_msg_size = 1 + (size_t) GNUNET_CRYPTO_random_u64 (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ (RSA_KEY_SIZE / 8) - 1);
+ rp->blinded_msg = GNUNET_malloc (rp->blinded_msg_size);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ rp->blinded_msg,
+ rp->blinded_msg_size);
+ TALER_denom_pub_hash (&new_dkp[cnt]->pub,
+ &ccoin->h_denom_pub);
+ ccoin->exchange_vals = alg_values;
+ TALER_coin_ev_hash (bp,
+ &ccoin->h_denom_pub,
+ &ccoin->coin_envelope_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sign_blinded (&ccoin->coin_sig,
+ &new_dkp[cnt]->priv,
+ true,
+ bp));
+ TALER_coin_ev_hash (bp,
+ &cbc.denom_pub_hash,
+ &cbc.h_coin_envelope);
+ GNUNET_assert (
+ GNUNET_OK ==
+ TALER_denom_sign_blinded (
+ &cbc.sig,
+ &new_dkp[cnt]->priv,
+ false,
+ bp));
+ }
+ }
+ perm = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_NONCE,
+ NUM_ROWS);
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "Transaction"));
+ for (unsigned int j = 0; j < NUM_ROWS; j++)
+ {
+ /*** NEED TO INSERT REFRESH COMMITMENTS + ENSURECOIN ***/
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct GNUNET_TIME_Timestamp deadline;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_CoinPubHashP c_hash;
+ unsigned int k = (unsigned int) rand () % 5;
+ unsigned int i = perm[j];
+
+ if (i >= ROUNDS)
+ i = ROUNDS; /* throw-away slot, do not keep around */
+ RND_BLK (&coin_pub);
+ RND_BLK (&c_hash);
+ RND_BLK (&reserve_pub);
+ RND_BLK (&cbc.reserve_sig);
+ TALER_denom_pub_hash (&new_dkp[k]->pub,
+ &cbc.denom_pub_hash);
+ deadline = GNUNET_TIME_timestamp_get ();
+ depos[i].coin.coin_pub = coin_pub;
+ TALER_denom_pub_hash (&new_dkp[k]->pub,
+ &depos[i].coin.denom_pub_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&depos[i].coin.denom_sig,
+ &ccoin->coin_sig,
+ &bks,
+ &c_hash,
+ &alg_values,
+ &new_dkp[k]->pub));
+ RND_BLK (&bd.merchant_pub);
+ RND_BLK (&depos[i].csig);
+ RND_BLK (&bd.h_contract_terms);
+ RND_BLK (&bd.wire_salt);
+ depos[i].amount_with_fee = value;
+ bd.refund_deadline = deadline;
+ bd.wire_deadline = deadline;
+ bd.receiver_wire_account =
+ "payto://iban/DE67830654080004822650?receiver-name=Test";
+ TALER_merchant_wire_signature_hash (
+ bd.receiver_wire_account,
+ &bd.wire_salt,
+ &h_wire_wt);
+ bd.num_cdis = 1;
+ bd.cdis = &depos[i];
+ cbc.reserve_pub = reserve_pub;
+ cbc.amount_with_fee = value;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (CURRENCY,
+ &cbc.withdraw_fee));
+ {
+ bool found;
+ bool nonce_reuse;
+ bool balance_ok;
+ bool age_ok;
+ bool conflict;
+ bool denom_unknown;
+ struct TALER_Amount reserve_balance;
+ uint16_t allowed_minimum_age;
+ uint64_t ruuid;
+ struct GNUNET_TIME_Timestamp now;
+
+ now = GNUNET_TIME_timestamp_get ();
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_batch_withdraw (plugin->cls,
+ now,
+ &reserve_pub,
+ &value,
+ true,
+ &found,
+ &balance_ok,
+ &reserve_balance,
+ &age_ok,
+ &allowed_minimum_age,
+ &ruuid));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_batch_withdraw_insert (plugin->cls,
+ NULL,
+ &cbc,
+ now,
+ ruuid,
+ &denom_unknown,
+ &conflict,
+ &nonce_reuse));
+ }
+ {
+ /* ENSURE_COIN_KNOWN */
+ uint64_t known_coin_id;
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+ FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+ plugin->ensure_coin_known (plugin->cls,
+ &depos[i].coin,
+ &known_coin_id,
+ &dph,
+ &agh));
+ refresh.coin = depos[i].coin;
+ RND_BLK (&refresh.coin_sig);
+ RND_BLK (&refresh.rc);
+ refresh.amount_with_fee = value;
+ refresh.noreveal_index = MELT_NOREVEAL_INDEX;
+ }
+ {
+ struct GNUNET_TIME_Timestamp now;
+ bool balance_ok;
+ uint32_t bad_idx;
+ bool ctr_conflict;
+
+ now = GNUNET_TIME_timestamp_get ();
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_deposit (plugin->cls,
+ &bd,
+ &now,
+ &balance_ok,
+ &bad_idx,
+ &ctr_conflict));
+ }
+ if (ROUNDS == i)
+ TALER_denom_sig_free (&depos[i].coin.denom_sig);
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+ GNUNET_free (perm);
+ /* End of benchmark setup */
+
+ /**** CALL GET READY DEPOSIT ****/
+ for (unsigned int r = 0; r< ROUNDS; r++)
+ {
+ struct GNUNET_TIME_Absolute time;
+ struct GNUNET_TIME_Relative duration;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ char *payto_uri;
+ enum GNUNET_DB_QueryStatus qs;
+
+ time = GNUNET_TIME_absolute_get ();
+ qs = plugin->get_ready_deposit (plugin->cls,
+ 0,
+ INT32_MAX,
+ &merchant_pub,
+ &payto_uri);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
+ duration = GNUNET_TIME_absolute_get_duration (time);
+ times = GNUNET_TIME_relative_add (times,
+ duration);
+ duration_sq = duration.rel_value_us * duration.rel_value_us;
+ GNUNET_assert (duration_sq / duration.rel_value_us ==
+ duration.rel_value_us);
+ GNUNET_assert (sqrs + duration_sq >= sqrs);
+ sqrs += duration_sq;
+ }
+
+ /* evaluation of performance */
+ {
+ struct GNUNET_TIME_Relative avg;
+ double avg_dbl;
+ double variance;
+
+ avg = GNUNET_TIME_relative_divide (times,
+ ROUNDS);
+ avg_dbl = avg.rel_value_us;
+ variance = sqrs - (avg_dbl * avg_dbl * ROUNDS);
+ fprintf (stdout,
+ "%8llu ± %6.0f\n",
+ (unsigned long long) avg.rel_value_us,
+ sqrt (variance / (ROUNDS - 1)));
+ }
+ result = 0;
+drop:
+ // GNUNET_break (GNUNET_OK == plugin->drop_tables (plugin->cls));
+cleanup:
+ if (NULL != revealed_coins)
+ {
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ TALER_blinded_denom_sig_free (&revealed_coins[cnt].coin_sig);
+ TALER_blinded_planchet_free (&revealed_coins[cnt].blinded_planchet);
+ }
+ GNUNET_free (revealed_coins);
+ revealed_coins = NULL;
+ }
+ GNUNET_free (new_denom_pubs);
+ for (unsigned int cnt = 0;
+ (NULL != new_dkp) && (cnt < MELT_NEW_COINS) && (NULL != new_dkp[cnt]);
+ cnt++)
+ destroy_denom_key_pair (new_dkp[cnt]);
+ GNUNET_free (new_dkp);
+ for (unsigned int i = 0; i< ROUNDS; i++)
+ {
+ TALER_denom_sig_free (&depos[i].coin.denom_sig);
+ }
+ GNUNET_free (depos);
+ GNUNET_free (ref);
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ (void) argc;
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ GNUNET_log_setup (argv[0],
+ "WARNING",
+ NULL);
+ plugin_name++;
+ {
+ char *testname;
+
+ GNUNET_asprintf (&testname,
+ "test-exchange-db-%s",
+ plugin_name);
+ GNUNET_asprintf (&config_filename,
+ "%s.conf",
+ testname);
+ GNUNET_free (testname);
+ }
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ return result;
+}
+
+
+/* end of perf_deposits_get_ready.c */
diff --git a/src/exchangedb/perf_get_link_data.c b/src/exchangedb/perf_get_link_data.c
new file mode 100644
index 000000000..817789afc
--- /dev/null
+++ b/src/exchangedb/perf_get_link_data.c
@@ -0,0 +1,543 @@
+/*
+ This file is part of TALER
+ 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
+ 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 exchangedb/perf_get_link_data.c
+ * @brief benchmark for get_link_data
+ * @author Joseph Xu
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "math.h"
+
+/**
+ * Report line of error if @a cond is true, and jump to label "drop".
+ */
+#define FAILIF(cond) \
+ do { \
+ if (! (cond)) {break;} \
+ GNUNET_break (0); \
+ goto drop; \
+ } while (0)
+
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
+
+/**
+ * Initializes @a ptr with zeros.
+ */
+#define ZR_BLK(ptr) \
+ memset (ptr, 0, sizeof (*ptr))
+
+#define CURRENCY "EUR"
+#define RSA_KEY_SIZE 1024
+#define ROUNDS 100
+#define NUM_ROWS 1000
+#define MELT_NEW_COINS 5
+#define DENOMINATIONS 5
+#define MELT_NOREVEAL_INDEX 1
+/**
+ * Database plugin under test.
+ */
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+static struct TALER_DenomFeeSet fees;
+/**
+ * Denomination keys used for fresh coins in melt test.
+ */
+static struct DenomKeyPair **new_dkp;
+static int result;
+
+static struct TALER_TransferPrivateKeyP tprivs[TALER_CNC_KAPPA];
+static struct TALER_TransferPublicKeyP tpub;
+struct DenomKeyPair
+{
+ struct TALER_DenominationPrivateKey priv;
+ struct TALER_DenominationPublicKey pub;
+};
+
+
+/**
+ * Destroy a denomination key pair. The key is not necessarily removed from the DB.
+ *
+ * @param dkp the key pair to destroy
+ */
+static void
+destroy_denom_key_pair (struct DenomKeyPair *dkp)
+{
+ TALER_denom_pub_free (&dkp->pub);
+ TALER_denom_priv_free (&dkp->priv);
+ GNUNET_free (dkp);
+}
+
+
+/**
+ * Create a denomination key pair by registering the denomination in the DB.
+ *
+ * @param size the size of the denomination key
+ * @param now time to use for key generation, legal expiration will be 3h later.
+ * @param fees fees to use
+ * @return the denominaiton key pair; NULL upon error
+ */
+static struct DenomKeyPair *
+create_denom_key_pair (unsigned int size,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_Amount *value,
+ const struct TALER_DenomFeeSet *fees)
+{
+ struct DenomKeyPair *dkp;
+ struct TALER_EXCHANGEDB_DenominationKey dki;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue2;
+
+ dkp = GNUNET_new (struct DenomKeyPair);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_priv_create (&dkp->priv,
+ &dkp->pub,
+ GNUNET_CRYPTO_BSA_RSA,
+ size));
+ memset (&dki,
+ 0,
+ sizeof (struct TALER_EXCHANGEDB_DenominationKey));
+ dki.denom_pub = dkp->pub;
+ dki.issue.start = now;
+ dki.issue.expire_withdraw
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_UNIT_HOURS));
+ dki.issue.expire_deposit
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 2)));
+ dki.issue.expire_legal
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 3)));
+ dki.issue.value = *value;
+ dki.issue.fees = *fees;
+ TALER_denom_pub_hash (&dkp->pub,
+ &dki.issue.denom_hash);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_denomination_info (plugin->cls,
+ &dki.denom_pub,
+ &dki.issue))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ memset (&issue2, 0, sizeof (issue2));
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_denomination_info (plugin->cls,
+ &dki.issue.denom_hash,
+ &issue2))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ if (0 != GNUNET_memcmp (&dki.issue,
+ &issue2))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ return dkp;
+}
+
+
+/**
+ * Function called with the session hashes and transfer secret
+ * information for a given coin.
+ *
+ * @param cls closure
+ * @param transfer_pub public transfer key for the session
+ * @param ldl link data for @a transfer_pub
+ */
+static void
+handle_link_data_cb (void *cls,
+ const struct TALER_TransferPublicKeyP *transfer_pub,
+ const struct TALER_EXCHANGEDB_LinkList *ldl)
+{
+ (void) cls;
+ (void) transfer_pub;
+ (void) ldl;
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+
+static void
+run (void *cls)
+{
+ struct TALER_EXCHANGEDB_Refresh *refresh;
+ uint64_t melt_serial_id;
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ const uint32_t num_partitions = 10;
+ struct DenomKeyPair *dkp = NULL;
+ struct TALER_EXCHANGEDB_Deposit *depos = NULL;
+ struct TALER_Amount value;
+ struct GNUNET_TIME_Relative times = GNUNET_TIME_UNIT_ZERO;
+ unsigned long long sqrs = 0;
+ struct TALER_EXCHANGEDB_Refund *ref = NULL;
+ unsigned int *perm;
+ unsigned long long duration_sq;
+ struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = GNUNET_CRYPTO_BSA_RSA,
+ .rc = 0
+ };
+ struct TALER_ExchangeWithdrawValues alg_values = {
+ .blinding_inputs = &bi
+ };
+
+ ref = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_Refund);
+ depos = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_Deposit);
+ refresh = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_Refresh);
+
+ if (NULL ==
+ (plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_break (0);
+ result = 77;
+ return;
+ }
+ (void) plugin->drop_tables (plugin->cls);
+ if (GNUNET_OK !=
+ plugin->create_tables (plugin->cls,
+ true,
+ num_partitions))
+ {
+ GNUNET_break (0);
+ result = 77;
+ goto cleanup;
+ }
+ if (GNUNET_OK !=
+ plugin->preflight (plugin->cls))
+ {
+ GNUNET_break (0);
+ goto cleanup;
+ }
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.000010",
+ &value));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.withdraw));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.deposit));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.refresh));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.refund));
+ {
+ new_dkp = GNUNET_new_array (MELT_NEW_COINS,
+ struct DenomKeyPair *);
+
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+
+ new_dkp[cnt] = create_denom_key_pair (RSA_KEY_SIZE,
+ now,
+ &value,
+ &fees);
+ GNUNET_assert (NULL != new_dkp[cnt]);
+ }
+ }
+ perm = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_NONCE,
+ NUM_ROWS);
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "Transaction"));
+ for (unsigned int j = 0; j < NUM_ROWS; j++)
+ {
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct TALER_CoinPubHashP c_hash;
+ unsigned int i = perm[j];
+ uint64_t known_coin_id;
+ struct TALER_EXCHANGEDB_CollectableBlindcoin cbc;
+ if (i >= ROUNDS)
+ i = ROUNDS; /* throw-away slot, do not keep around */
+ RND_BLK (&depos[i].coin.coin_pub);
+ ZR_BLK (&cbc);
+ TALER_denom_pub_hash (&new_dkp[(unsigned int) rand ()
+ % MELT_NEW_COINS]->pub,
+ &depos[i].coin.denom_pub_hash);
+
+
+ {
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin
+ revealed_coins[MELT_NEW_COINS];
+
+ for (unsigned int p = 0; p<MELT_NEW_COINS; p++)
+ {
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coin =
+ &revealed_coins[p];
+ struct TALER_BlindedPlanchet *bp = &revealed_coin->blinded_planchet;
+ bp->blinded_message = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
+ struct GNUNET_CRYPTO_RsaBlindedMessage *rp =
+ &bp->blinded_message->details.rsa_blinded_message;
+
+ /* h_coin_ev must be unique, but we only have MELT_NEW_COINS created
+ above for NUM_ROWS iterations; instead of making "all new" coins,
+ we simply randomize the hash here as nobody is checking for consistency
+ anyway ;-) */
+ bp->blinded_message->rc = 1;
+ bp->blinded_message->cipher = GNUNET_CRYPTO_BSA_RSA;
+ rp->blinded_msg_size = 1 + (size_t) GNUNET_CRYPTO_random_u64 (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ (RSA_KEY_SIZE / 8) - 1);
+ rp->blinded_msg = GNUNET_malloc (rp->blinded_msg_size);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ rp->blinded_msg,
+ rp->blinded_msg_size);
+ TALER_denom_pub_hash (&new_dkp[(unsigned int) rand ()
+ % MELT_NEW_COINS]->pub,
+ &revealed_coin->h_denom_pub);
+ revealed_coin->exchange_vals = alg_values;
+ TALER_coin_ev_hash (bp,
+ &revealed_coin->h_denom_pub,
+ &revealed_coin->coin_envelope_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sign_blinded (&revealed_coin->coin_sig,
+ &new_dkp[(unsigned
+ int) rand ()
+ % MELT_NEW_COINS]->
+ priv,
+ true,
+ bp));
+ GNUNET_assert (
+ GNUNET_OK ==
+ TALER_denom_sign_blinded (
+ &cbc.sig,
+ &new_dkp[(unsigned int) rand () % MELT_NEW_COINS]->priv,
+ false,
+ bp));
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&depos[i].coin.denom_sig,
+ &cbc.sig,
+ &bks,
+ &c_hash,
+ &alg_values,
+ &new_dkp[(unsigned int) rand ()
+ % MELT_NEW_COINS]->pub));
+ {
+ /* ENSURE_COIN_KNOWN */
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+ bool zombie_required = false;
+ bool balance_ok;
+
+ FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+ plugin->ensure_coin_known (plugin->cls,
+ &depos[i].coin,
+ &known_coin_id,
+ &dph,
+ &agh));
+ /**** INSERT REFRESH COMMITMENTS ****/
+ refresh[i].coin = depos[i].coin;
+ RND_BLK (&refresh[i].coin_sig);
+ RND_BLK (&refresh[i].rc);
+ refresh[i].amount_with_fee = value;
+ refresh[i].noreveal_index = MELT_NOREVEAL_INDEX;
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_melt (plugin->cls,
+ NULL,
+ &refresh[i],
+ known_coin_id,
+ &zombie_required,
+ &balance_ok));
+ }
+ /****GET melt_serial_id generated by default****/
+ {
+ struct TALER_EXCHANGEDB_Melt ret_refresh_session;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_melt (plugin->cls,
+ &refresh[i].rc,
+ &ret_refresh_session,
+ &melt_serial_id));
+ }
+ /**** INSERT REFRESH_REVEAL + TRANSFER_KEYS *****/
+ {
+ static unsigned int cnt;
+
+ RND_BLK (&tprivs);
+ RND_BLK (&tpub);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_refresh_reveal (plugin->cls,
+ melt_serial_id,
+ MELT_NEW_COINS,
+ revealed_coins,
+ TALER_CNC_KAPPA - 1,
+ tprivs,
+ &tpub));
+ cnt++;
+ // fprintf (stderr, "CNT: %u - %llu\n", cnt, (unsigned long long) melt_serial_id);
+ }
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ TALER_blinded_denom_sig_free (&revealed_coins[cnt].coin_sig);
+ TALER_blinded_planchet_free (&revealed_coins[cnt].blinded_planchet);
+ }
+
+ /* {
+ struct TALER_CoinSpendPublicKeyP ocp;
+ uint64_t rrc_serial;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_old_coin_by_h_blind (plugin->cls,
+ &revealed_coins->coin_envelope_hash,
+ &ocp,
+ &rrc_serial));
+ }*/
+ }
+ if (ROUNDS == i)
+ TALER_denom_sig_free (&depos[i].coin.denom_sig);
+ }
+ /* End of benchmark setup */
+ GNUNET_free (perm);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+ for (unsigned int r = 0; r< ROUNDS; r++)
+ {
+ struct GNUNET_TIME_Absolute time;
+ struct GNUNET_TIME_Relative duration;
+ enum GNUNET_DB_QueryStatus qs;
+ time = GNUNET_TIME_absolute_get ();
+
+ qs = plugin->get_link_data (plugin->cls,
+ &refresh[r].coin.coin_pub,
+ &handle_link_data_cb,
+ NULL);
+ FAILIF (qs < 0);
+
+ duration = GNUNET_TIME_absolute_get_duration (time);
+ times = GNUNET_TIME_relative_add (times,
+ duration);
+ duration_sq = duration.rel_value_us * duration.rel_value_us;
+ GNUNET_assert (duration_sq / duration.rel_value_us ==
+ duration.rel_value_us);
+ GNUNET_assert (sqrs + duration_sq >= sqrs);
+ sqrs += duration_sq;
+ }
+
+ /* evaluation of performance */
+ {
+ struct GNUNET_TIME_Relative avg;
+ double avg_dbl;
+ double variance;
+
+ avg = GNUNET_TIME_relative_divide (times,
+ ROUNDS);
+ avg_dbl = avg.rel_value_us;
+ variance = sqrs - (avg_dbl * avg_dbl * ROUNDS);
+ fprintf (stdout,
+ "%8llu ± %6.0f\n",
+ (unsigned long long) avg.rel_value_us,
+ sqrt (variance / (ROUNDS - 1)));
+ }
+ result = 0;
+drop:
+ // GNUNET_break (GNUNET_OK == plugin->drop_tables (plugin->cls));
+cleanup:
+ if (NULL != dkp)
+ destroy_denom_key_pair (dkp);
+ for (unsigned int cnt = 0;
+ (NULL != new_dkp) && (cnt < MELT_NEW_COINS) && (NULL != new_dkp[cnt]);
+ cnt++)
+ destroy_denom_key_pair (new_dkp[cnt]);
+ GNUNET_free (new_dkp);
+ for (unsigned int i = 0; i< ROUNDS; i++)
+ {
+ TALER_denom_sig_free (&depos[i].coin.denom_sig);
+ }
+ GNUNET_free (depos);
+ GNUNET_free (ref);
+ GNUNET_free (refresh);
+ dkp = NULL;
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ char *testname;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ (void) argc;
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ GNUNET_log_setup (argv[0],
+ "WARNING",
+ NULL);
+ plugin_name++;
+ (void) GNUNET_asprintf (&testname,
+ "test-exchange-db-%s",
+ plugin_name);
+ (void) GNUNET_asprintf (&config_filename,
+ "%s.conf",
+ testname);
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return result;
+}
+
+
+/* end of test_exchangedb_by_j.c */
diff --git a/src/exchangedb/perf_reserves_in_insert.c b/src/exchangedb/perf_reserves_in_insert.c
new file mode 100644
index 000000000..09c4a43c5
--- /dev/null
+++ b/src/exchangedb/perf_reserves_in_insert.c
@@ -0,0 +1,232 @@
+/*
+ This file is part of TALER
+ 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
+ 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 exchangedb/perf_reserves_in_insert.c
+ * @brief benchmark for 'reserves_in_insert'
+ * @author Joseph Xu
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Global result from the testcase.
+ */
+static int result;
+
+/**
+ * Report line of error if @a cond is true, and jump to label "drop".
+ */
+#define FAILIF(cond) \
+ do { \
+ if (! (cond)) {break;} \
+ GNUNET_break (0); \
+ goto drop; \
+ } while (0)
+
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
+
+/**
+ * Initializes @a ptr with zeros.
+ */
+#define ZR_BLK(ptr) \
+ memset (ptr, 0, sizeof (*ptr))
+
+/**
+ * How many rounds do we average over?
+ */
+#define ROUNDS 5
+
+/**
+ * Currency we use. Must match test-exchange-db-*.conf.
+ */
+#define CURRENCY "EUR"
+
+/**
+ * Database plugin under test.
+ */
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+static void
+run (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ const uint32_t num_partitions = 10;
+ static unsigned int batches[] = {1, 1, 2, 3, 4, 16, 32, 64, 128, 256, 512,
+ 1024, 1024, 512, 256, 128, 64, 32,
+ 16, 4, 3, 2, 1 };
+ struct GNUNET_TIME_Relative times[sizeof (batches) / sizeof(*batches)];
+ unsigned long long sqrs[sizeof (batches) / sizeof(*batches)];
+
+ if (NULL ==
+ (plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_break (0);
+ result = 77;
+ return;
+ }
+ (void) plugin->drop_tables (plugin->cls);
+ if (GNUNET_OK !=
+ plugin->create_tables (plugin->cls,
+ true,
+ num_partitions))
+ {
+ GNUNET_break (0);
+ result = 77;
+ goto cleanup;
+ }
+
+ memset (times, 0, sizeof (times));
+ memset (sqrs, 0, sizeof (sqrs));
+ for (unsigned int r = 0; r < ROUNDS; r++)
+ {
+ for (unsigned int i = 0;
+ i< sizeof(batches) / sizeof(*batches);
+ i++)
+ {
+ unsigned int lcm = batches[i];
+ struct TALER_Amount value;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Timestamp ts;
+ unsigned long long duration_sq;
+ struct GNUNET_TIME_Relative duration;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.000010",
+ &value));
+ now = GNUNET_TIME_absolute_get ();
+ ts = GNUNET_TIME_timestamp_get ();
+ {
+ const char *sndr = "payto://x-taler-bank/localhost:8080/1";
+ struct TALER_ReservePublicKeyP reserve_pubs[lcm];
+ struct TALER_EXCHANGEDB_ReserveInInfo reserves[lcm];
+ enum GNUNET_DB_QueryStatus results[lcm];
+
+ for (unsigned int k = 0; k<lcm; k++)
+ {
+ RND_BLK (&reserve_pubs[k]);
+ reserves[k].reserve_pub = &reserve_pubs[k];
+ reserves[k].balance = &value;
+ reserves[k].execution_time = ts;
+ reserves[k].sender_account_details = sndr;
+ reserves[k].exchange_account_name = "name";
+ reserves[k].wire_reference = k;
+ }
+ FAILIF (lcm !=
+ plugin->reserves_in_insert (plugin->cls,
+ reserves,
+ lcm,
+ results));
+ }
+ duration = GNUNET_TIME_absolute_get_duration (now);
+ times[i] = GNUNET_TIME_relative_add (times[i],
+ duration);
+ duration_sq = duration.rel_value_us * duration.rel_value_us;
+ GNUNET_assert (duration_sq / duration.rel_value_us ==
+ duration.rel_value_us);
+ GNUNET_assert (sqrs[i] + duration_sq >= sqrs[i]);
+ sqrs[i] += duration_sq;
+ } /* for 'i' batch size */
+ } /* for 'r' ROUNDS */
+
+ for (unsigned int i = 0;
+ i< sizeof(batches) / sizeof(*batches);
+ i++)
+ {
+ unsigned int lcm = batches[i];
+ struct GNUNET_TIME_Relative avg;
+ double avg_dbl;
+ double variance;
+
+ avg = GNUNET_TIME_relative_divide (times[i],
+ ROUNDS);
+ avg_dbl = avg.rel_value_us;
+ variance = sqrs[i] - (avg_dbl * avg_dbl * ROUNDS);
+ fprintf (stdout,
+ "Batch[%4u]: %8llu us/entry ± %6.0f\n",
+ batches[i],
+ (unsigned long long) avg.rel_value_us / lcm,
+ sqrt (variance / lcm / (ROUNDS - 1)));
+ }
+ result = 0;
+drop:
+ GNUNET_break (GNUNET_OK ==
+ plugin->drop_tables (plugin->cls));
+cleanup:
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ char *testname;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+ (void) argc;
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+
+ GNUNET_log_setup (argv[0],
+ "WARNING",
+ NULL);
+ plugin_name++;
+ (void) GNUNET_asprintf (&testname,
+ "test-exchange-db-%s",
+ plugin_name);
+ (void) GNUNET_asprintf (&config_filename,
+ "%s.conf",
+ testname);
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return result;
+}
+
+
+/* end of perf_reserves_in_insert.c */
diff --git a/src/exchangedb/perf_select_refunds_by_coin.c b/src/exchangedb/perf_select_refunds_by_coin.c
new file mode 100644
index 000000000..84825d6d7
--- /dev/null
+++ b/src/exchangedb/perf_select_refunds_by_coin.c
@@ -0,0 +1,619 @@
+/*
+ This file is part of TALER
+ 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
+ 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 exchangedb/perf_select_refunds_by_coin.c
+ * @brief benchmark for select_refunds_by_coin
+ * @author Joseph Xu
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "math.h"
+
+/**
+ * Global result from the testcase.
+ */
+static int result;
+
+/**
+ * Report line of error if @a cond is true, and jump to label "drop".
+ */
+#define FAILIF(cond) \
+ do { \
+ if (! (cond)) {break;} \
+ GNUNET_break (0); \
+ goto drop; \
+ } while (0)
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, \
+ sizeof (*ptr))
+
+/**
+ * Initializes @a ptr with zeros.
+ */
+#define ZR_BLK(ptr) \
+ memset (ptr, 0, sizeof (*ptr))
+
+/**
+ * Currency we use. Must match test-exchange-db-*.conf.
+ */
+#define CURRENCY "EUR"
+#define RSA_KEY_SIZE 1024
+#define ROUNDS 100
+#define NUM_ROWS 1000
+#define MELT_NEW_COINS 5
+#define MELT_NOREVEAL_INDEX 1
+
+/**
+ * Database plugin under test.
+ */
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+
+static struct TALER_DenomFeeSet fees;
+
+static struct TALER_MerchantWireHashP h_wire_wt;
+
+static struct DenomKeyPair **new_dkp;
+
+static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins;
+
+struct DenomKeyPair
+{
+ struct TALER_DenominationPrivateKey priv;
+ struct TALER_DenominationPublicKey pub;
+};
+
+
+/**
+ * Destroy a denomination key pair. The key is not necessarily removed from the DB.
+ *
+ * @param dkp the key pair to destroy
+ */
+static void
+destroy_denom_key_pair (struct DenomKeyPair *dkp)
+{
+ TALER_denom_pub_free (&dkp->pub);
+ TALER_denom_priv_free (&dkp->priv);
+ GNUNET_free (dkp);
+}
+
+
+/**
+ * Create a denomination key pair by registering the denomination in the DB.
+ *
+ * @param size the size of the denomination key
+ * @param now time to use for key generation, legal expiration will be 3h later.
+ * @param fees fees to use
+ * @return the denominaiton key pair; NULL upon error
+ */
+static struct DenomKeyPair *
+create_denom_key_pair (unsigned int size,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_Amount *value,
+ const struct TALER_DenomFeeSet *fees)
+{
+ struct DenomKeyPair *dkp;
+ struct TALER_EXCHANGEDB_DenominationKey dki;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue2;
+
+ dkp = GNUNET_new (struct DenomKeyPair);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_priv_create (&dkp->priv,
+ &dkp->pub,
+ GNUNET_CRYPTO_BSA_RSA,
+ size));
+ memset (&dki,
+ 0,
+ sizeof (struct TALER_EXCHANGEDB_DenominationKey));
+ dki.denom_pub = dkp->pub;
+ dki.issue.start = now;
+ dki.issue.expire_withdraw
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_UNIT_HOURS));
+ dki.issue.expire_deposit
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 2)));
+ dki.issue.expire_legal
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 3)));
+ dki.issue.value = *value;
+ dki.issue.fees = *fees;
+ TALER_denom_pub_hash (&dkp->pub,
+ &dki.issue.denom_hash);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_denomination_info (plugin->cls,
+ &dki.denom_pub,
+ &dki.issue))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ memset (&issue2, 0, sizeof (issue2));
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_denomination_info (plugin->cls,
+ &dki.issue.denom_hash,
+ &issue2))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ if (0 != GNUNET_memcmp (&dki.issue,
+ &issue2))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ return dkp;
+}
+
+
+/**
+ * Callback invoked with information about refunds applicable
+ * to a particular coin.
+ *
+ * @param cls closure with the `struct TALER_EXCHANGEDB_Refund *` we expect to get
+ * @param amount_with_fee amount being refunded
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+check_refund_cb (void *cls,
+ const struct TALER_Amount *amount_with_fee)
+{
+ const struct TALER_EXCHANGEDB_Refund *refund = cls;
+
+ if (0 != TALER_amount_cmp (amount_with_fee,
+ &refund->details.refund_amount))
+ {
+ GNUNET_break (0);
+ result = 66;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+
+static void
+run (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ const uint32_t num_partitions = 10;
+ struct GNUNET_TIME_Timestamp ts;
+ struct TALER_EXCHANGEDB_CoinDepositInformation *depos = NULL;
+ struct GNUNET_TIME_Timestamp deadline;
+ struct TALER_Amount value;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct TALER_EXCHANGEDB_CollectableBlindcoin cbc;
+ struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = GNUNET_CRYPTO_BSA_RSA,
+ .rc = 0
+ };
+ struct TALER_ExchangeWithdrawValues alg_values = {
+ .blinding_inputs = &bi
+ };
+ struct GNUNET_TIME_Relative times = GNUNET_TIME_UNIT_ZERO;
+ unsigned long long sqrs = 0;
+ struct TALER_EXCHANGEDB_Refund *ref = NULL;
+ unsigned int *perm;
+ unsigned long long duration_sq;
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
+ struct TALER_DenominationPublicKey *new_denom_pubs = NULL;
+ unsigned int count = 0;
+
+ ref = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_Refund);
+ depos = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_CoinDepositInformation);
+ ZR_BLK (&cbc);
+
+ if (NULL ==
+ (plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_break (0);
+ result = 77;
+ return;
+ }
+ (void) plugin->drop_tables (plugin->cls);
+ if (GNUNET_OK !=
+ plugin->create_tables (plugin->cls,
+ true,
+ num_partitions))
+ {
+ GNUNET_break (0);
+ result = 77;
+ goto cleanup;
+ }
+ if (GNUNET_OK !=
+ plugin->preflight (plugin->cls))
+ {
+ GNUNET_break (0);
+ goto cleanup;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.000010",
+ &value));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.withdraw));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.deposit));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.refresh));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.refund));
+ GNUNET_assert (NUM_ROWS >= ROUNDS);
+
+ ts = GNUNET_TIME_timestamp_get ();
+ deadline = GNUNET_TIME_timestamp_get ();
+ {
+ new_dkp = GNUNET_new_array (MELT_NEW_COINS,
+ struct DenomKeyPair *);
+ new_denom_pubs = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_DenominationPublicKey);
+ revealed_coins
+ = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin);
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_CRYPTO_RsaBlindedMessage *rp;
+ struct TALER_BlindedPlanchet *bp;
+
+ now = GNUNET_TIME_timestamp_get ();
+ new_dkp[cnt] = create_denom_key_pair (RSA_KEY_SIZE,
+ now,
+ &value,
+ &fees);
+ GNUNET_assert (NULL != new_dkp[cnt]);
+ new_denom_pubs[cnt] = new_dkp[cnt]->pub;
+ ccoin = &revealed_coins[cnt];
+ bp = &ccoin->blinded_planchet;
+ bp->blinded_message = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
+ bp->blinded_message->rc = 1;
+ bp->blinded_message->cipher = GNUNET_CRYPTO_BSA_RSA;
+ rp = &bp->blinded_message->details.rsa_blinded_message;
+ rp->blinded_msg_size = 1 + (size_t) GNUNET_CRYPTO_random_u64 (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ (RSA_KEY_SIZE / 8) - 1);
+ rp->blinded_msg = GNUNET_malloc (rp->blinded_msg_size);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ rp->blinded_msg,
+ rp->blinded_msg_size);
+ TALER_denom_pub_hash (&new_dkp[cnt]->pub,
+ &ccoin->h_denom_pub);
+ ccoin->exchange_vals = alg_values;
+ TALER_coin_ev_hash (bp,
+ &ccoin->h_denom_pub,
+ &ccoin->coin_envelope_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sign_blinded (&ccoin->coin_sig,
+ &new_dkp[cnt]->priv,
+ true,
+ bp));
+ TALER_coin_ev_hash (bp,
+ &cbc.denom_pub_hash,
+ &cbc.h_coin_envelope);
+ GNUNET_assert (
+ GNUNET_OK ==
+ TALER_denom_sign_blinded (
+ &cbc.sig,
+ &new_dkp[cnt]->priv,
+ false,
+ bp));
+ }
+ }
+
+ perm = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_NONCE,
+ NUM_ROWS);
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "Transaction"));
+ for (unsigned int j = 0; j< NUM_ROWS; j++)
+ {
+ unsigned int i = perm[j];
+ unsigned int k = (unsigned int) rand () % 5;
+ struct TALER_CoinPubHashP c_hash;
+ uint64_t known_coin_id;
+ struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+ = &depos[i];
+ struct TALER_EXCHANGEDB_BatchDeposit bd = {
+ .cdis = cdi,
+ .num_cdis = 1,
+ .wallet_timestamp = ts,
+ .refund_deadline = deadline,
+ .wire_deadline = deadline,
+ .receiver_wire_account
+ = "payto://iban/DE67830654080004822650?receiver-name=Test"
+ };
+
+ if (i >= ROUNDS)
+ i = ROUNDS; /* throw-away slot, do not keep around */
+ RND_BLK (&bd.merchant_pub);
+ RND_BLK (&bd.h_contract_terms);
+ RND_BLK (&bd.wire_salt);
+ TALER_merchant_wire_signature_hash (
+ bd.receiver_wire_account,
+ &bd.wire_salt,
+ &h_wire_wt);
+ RND_BLK (&cdi->coin.coin_pub);
+ RND_BLK (&cdi->csig);
+ RND_BLK (&c_hash);
+ TALER_denom_pub_hash (&new_dkp[k]->pub,
+ &cdi->coin.denom_pub_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&cdi->coin.denom_sig,
+ &cbc.sig,
+ &bks,
+ &c_hash,
+ &alg_values,
+ &new_dkp[k]->pub));
+ cdi->amount_with_fee = value;
+
+ {
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+
+ FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+ plugin->ensure_coin_known (plugin->cls,
+ &cdi->coin,
+ &known_coin_id,
+ &dph,
+ &agh));
+ }
+ {
+ struct GNUNET_TIME_Timestamp now;
+ bool balance_ok;
+ uint32_t bad_idx;
+ bool in_conflict;
+
+ now = GNUNET_TIME_timestamp_get ();
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_deposit (plugin->cls,
+ &bd,
+ &now,
+ &balance_ok,
+ &bad_idx,
+ &in_conflict));
+ }
+ {
+ bool not_found;
+ bool refund_ok;
+ bool gone;
+ bool conflict;
+ unsigned int refund_percent = 0;
+ switch (refund_percent)
+ {
+ case 2: // 100% refund
+ ref[i].coin = depos[i].coin;
+ ref[i].details.merchant_pub = bd.merchant_pub;
+ RND_BLK (&ref[i].details.merchant_sig);
+ ref[i].details.h_contract_terms = bd.h_contract_terms;
+ ref[i].coin.coin_pub = depos[i].coin.coin_pub;
+ ref[i].details.rtransaction_id = i;
+ ref[i].details.refund_amount = value;
+ ref[i].details.refund_fee = fees.refund;
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_refund (plugin->cls,
+ &ref[i],
+ &fees.deposit,
+ known_coin_id,
+ &not_found,
+ &refund_ok,
+ &gone,
+ &conflict));
+ break;
+ case 1:// 10% refund
+ if (count < (NUM_ROWS / 10))
+ {
+ ref[i].coin = depos[i].coin;
+ ref[i].details.merchant_pub = bd.merchant_pub;
+ RND_BLK (&ref[i].details.merchant_sig);
+ ref[i].details.h_contract_terms = bd.h_contract_terms;
+ ref[i].coin.coin_pub = depos[i].coin.coin_pub;
+ ref[i].details.rtransaction_id = i;
+ ref[i].details.refund_amount = value;
+ ref[i].details.refund_fee = fees.refund;
+ }
+ else
+ {
+ ref[i].coin = depos[i].coin;
+ RND_BLK (&ref[i].details.merchant_pub);
+ RND_BLK (&ref[i].details.merchant_sig);
+ RND_BLK (&ref[i].details.h_contract_terms);
+ RND_BLK (&ref[i].coin.coin_pub);
+ ref[i].details.rtransaction_id = i;
+ ref[i].details.refund_amount = value;
+ ref[i].details.refund_fee = fees.refund;
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_refund (plugin->cls,
+ &ref[i],
+ &fees.deposit,
+ known_coin_id,
+ &not_found,
+ &refund_ok,
+ &gone,
+ &conflict));
+ count++;
+ break;
+ case 0:// no refund
+ ref[i].coin = depos[i].coin;
+ RND_BLK (&ref[i].details.merchant_pub);
+ RND_BLK (&ref[i].details.merchant_sig);
+ RND_BLK (&ref[i].details.h_contract_terms);
+ RND_BLK (&ref[i].coin.coin_pub);
+ ref[i].details.rtransaction_id = i;
+ ref[i].details.refund_amount = value;
+ ref[i].details.refund_fee = fees.refund;
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_refund (plugin->cls,
+ &ref[i],
+ &fees.deposit,
+ known_coin_id,
+ &not_found,
+ &refund_ok,
+ &gone,
+ &conflict));
+ break;
+ }/* END OF SWITCH CASE */
+ }
+ if (ROUNDS == i)
+ TALER_denom_sig_free (&depos[i].coin.denom_sig);
+ }
+ /* End of benchmark setup */
+ GNUNET_free (perm);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+ for (unsigned int r = 0; r < ROUNDS; r++)
+ {
+ struct GNUNET_TIME_Absolute time;
+ struct GNUNET_TIME_Relative duration;
+
+ time = GNUNET_TIME_absolute_get ();
+ FAILIF (0 >
+ plugin->select_refunds_by_coin (plugin->cls,
+ &ref[r].coin.coin_pub,
+ &ref[r].details.merchant_pub,
+ &ref[r].details.h_contract_terms,
+ &check_refund_cb,
+ &ref[r]));
+ duration = GNUNET_TIME_absolute_get_duration (time);
+ times = GNUNET_TIME_relative_add (times,
+ duration);
+ duration_sq = duration.rel_value_us * duration.rel_value_us;
+ GNUNET_assert (duration_sq / duration.rel_value_us ==
+ duration.rel_value_us);
+ GNUNET_assert (sqrs + duration_sq >= sqrs);
+ sqrs += duration_sq;
+ }
+ /* evaluation of performance */
+ {
+ struct GNUNET_TIME_Relative avg;
+ double avg_dbl;
+ double variance;
+
+ avg = GNUNET_TIME_relative_divide (times,
+ ROUNDS);
+ avg_dbl = avg.rel_value_us;
+ variance = sqrs - (avg_dbl * avg_dbl * ROUNDS);
+ fprintf (stdout,
+ "%8llu ± %6.0f\n",
+ (unsigned long long) avg.rel_value_us,
+ sqrt (variance / (ROUNDS - 1)));
+ }
+ result = 0;
+drop:
+ GNUNET_break (GNUNET_OK ==
+ plugin->drop_tables (plugin->cls));
+cleanup:
+ if (NULL != revealed_coins)
+ {
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ TALER_blinded_denom_sig_free (&revealed_coins[cnt].coin_sig);
+ TALER_blinded_planchet_free (&revealed_coins[cnt].blinded_planchet);
+ }
+ GNUNET_free (revealed_coins);
+ revealed_coins = NULL;
+ }
+ GNUNET_free (new_denom_pubs);
+ for (unsigned int cnt = 0;
+ (NULL != new_dkp) && (cnt < MELT_NEW_COINS) && (NULL != new_dkp[cnt]);
+ cnt++)
+ destroy_denom_key_pair (new_dkp[cnt]);
+ GNUNET_free (new_dkp);
+ for (unsigned int i = 0; i< ROUNDS + 1; i++)
+ {
+ TALER_denom_sig_free (&depos[i].coin.denom_sig);
+ }
+ GNUNET_free (depos);
+ GNUNET_free (ref);
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ (void) argc;
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ GNUNET_log_setup (argv[0],
+ "WARNING",
+ NULL);
+ plugin_name++;
+ {
+ char *testname;
+
+ GNUNET_asprintf (&testname,
+ "test-exchange-db-%s",
+ plugin_name);
+ GNUNET_asprintf (&config_filename,
+ "%s.conf",
+ testname);
+ GNUNET_free (testname);
+ }
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ return result;
+}
+
+
+/* end of perf_select_refunds_by_coin.c */
diff --git a/src/exchangedb/pg_abort_shard.c b/src/exchangedb/pg_abort_shard.c
new file mode 100644
index 000000000..d04680a81
--- /dev/null
+++ b/src/exchangedb/pg_abort_shard.c
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_abort_shard.c
+ * @brief Implementation of the abort_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_abort_shard.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_abort_shard (void *cls,
+ const char *job_name,
+ uint64_t start_row,
+ uint64_t end_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_uint64 (&start_row),
+ GNUNET_PQ_query_param_uint64 (&end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "abort_shard",
+ "UPDATE work_shards"
+ " SET last_attempt=0"
+ " WHERE job_name=$1"
+ " AND start_row=$2"
+ " AND end_row=$3;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "abort_shard",
+ params);
+}
diff --git a/src/exchangedb/pg_abort_shard.h b/src/exchangedb/pg_abort_shard.h
new file mode 100644
index 000000000..e52ace5f6
--- /dev/null
+++ b/src/exchangedb/pg_abort_shard.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_abort_shard.h
+ * @brief implementation of the abort_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ABORT_SHARD_H
+#define PG_ABORT_SHARD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to abort work on a shard.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to abort a word shard for
+ * @param start_row inclusive start row of the shard
+ * @param end_row exclusive end row of the shard
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_abort_shard (void *cls,
+ const char *job_name,
+ uint64_t start_row,
+ uint64_t end_row);
+
+#endif
diff --git a/src/exchangedb/pg_activate_signing_key.c b/src/exchangedb/pg_activate_signing_key.c
new file mode 100644
index 000000000..fab2a0ffe
--- /dev/null
+++ b/src/exchangedb/pg_activate_signing_key.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_activate_signing_key.c
+ * @brief Implementation of the activate_signing_key function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_activate_signing_key.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_activate_signing_key (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam iparams[] = {
+ GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+ GNUNET_PQ_query_param_timestamp (&meta->start),
+ GNUNET_PQ_query_param_timestamp (&meta->expire_sign),
+ GNUNET_PQ_query_param_timestamp (&meta->expire_legal),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_signkey",
+ "INSERT INTO exchange_sign_keys "
+ "(exchange_pub"
+ ",valid_from"
+ ",expire_sign"
+ ",expire_legal"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_signkey",
+ iparams);
+}
diff --git a/src/exchangedb/pg_activate_signing_key.h b/src/exchangedb/pg_activate_signing_key.h
new file mode 100644
index 000000000..2d4df0671
--- /dev/null
+++ b/src/exchangedb/pg_activate_signing_key.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_activate_signing_key.h
+ * @brief implementation of the activate_signing_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ACTIVATE_SIGNING_KEY_H
+#define PG_ACTIVATE_SIGNING_KEY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Add signing key.
+ *
+ * @param cls closure
+ * @param exchange_pub the exchange online signing public key
+ * @param meta meta data about @a exchange_pub
+ * @param master_sig master signature to add
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_activate_signing_key (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_add_denomination_key.c b/src/exchangedb/pg_add_denomination_key.c
new file mode 100644
index 000000000..eb50304d3
--- /dev/null
+++ b/src/exchangedb/pg_add_denomination_key.c
@@ -0,0 +1,86 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_add_denomination_key.c
+ * @brief Implementation of the add_denomination_key function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_add_denomination_key.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_add_denomination_key (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam iparams[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
+ TALER_PQ_query_param_denom_pub (denom_pub),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_timestamp (&meta->start),
+ GNUNET_PQ_query_param_timestamp (&meta->expire_withdraw),
+ GNUNET_PQ_query_param_timestamp (&meta->expire_deposit),
+ GNUNET_PQ_query_param_timestamp (&meta->expire_legal),
+ TALER_PQ_query_param_amount (pg->conn,
+ &meta->value),
+ TALER_PQ_query_param_amount (pg->conn,
+ &meta->fees.withdraw),
+ TALER_PQ_query_param_amount (pg->conn,
+ &meta->fees.deposit),
+ TALER_PQ_query_param_amount (pg->conn,
+ &meta->fees.refresh),
+ TALER_PQ_query_param_amount (pg->conn,
+ &meta->fees.refund),
+ GNUNET_PQ_query_param_uint32 (&meta->age_mask.bits),
+ GNUNET_PQ_query_param_end
+ };
+
+ /* Sanity check: ensure fees match coin currency */
+ GNUNET_assert (GNUNET_YES ==
+ TALER_denom_fee_check_currency (meta->value.currency,
+ &meta->fees));
+ PREPARE (pg,
+ "denomination_insert",
+ "INSERT INTO denominations "
+ "(denom_pub_hash"
+ ",denom_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin" /* value of this denom */
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",age_mask"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
+ " $11, $12, $13);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "denomination_insert",
+ iparams);
+}
diff --git a/src/exchangedb/pg_add_denomination_key.h b/src/exchangedb/pg_add_denomination_key.h
new file mode 100644
index 000000000..d131679e8
--- /dev/null
+++ b/src/exchangedb/pg_add_denomination_key.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_add_denomination_key.h
+ * @brief implementation of the add_denomination_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ADD_DENOMINATION_KEY_H
+#define PG_ADD_DENOMINATION_KEY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Activate denomination key, turning it into a "current" or "valid"
+ * denomination key by adding the master signature.
+ *
+ * @param cls closure
+ * @param h_denom_pub hash of the denomination public key
+ * @param denom_pub the actual denomination key
+ * @param meta meta data about the denomination
+ * @param master_sig master signature to add
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_add_denomination_key (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig);
+#endif
diff --git a/src/exchangedb/pg_add_policy_fulfillment_proof.c b/src/exchangedb/pg_add_policy_fulfillment_proof.c
new file mode 100644
index 000000000..93d070712
--- /dev/null
+++ b/src/exchangedb/pg_add_policy_fulfillment_proof.c
@@ -0,0 +1,159 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_add_policy_fulfillment_proof.c
+ * @brief Implementation of the add_policy_fulfillment_proof function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_add_policy_fulfillment_proof.h"
+#include "pg_helper.h"
+
+
+/**
+ * Compares two indices into an array of hash codes according to
+ * GNUNET_CRYPTO_hash_cmp of the content at those index positions.
+ *
+ * Used in a call qsort_t in order to generate sorted policy_hash_codes.
+ */
+static int
+hash_code_cmp (
+ const void *hc1,
+ const void *hc2,
+ void *arg)
+{
+ size_t i1 = *(size_t *) hc1;
+ size_t i2 = *(size_t *) hc2;
+ const struct TALER_PolicyDetails *d = arg;
+
+ return GNUNET_CRYPTO_hash_cmp (&d[i1].hash_code,
+ &d[i2].hash_code);
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_add_policy_fulfillment_proof (
+ void *cls,
+ struct TALER_PolicyFulfillmentTransactionData *fulfillment)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct PostgresClosure *pg = cls;
+ size_t count = fulfillment->details_count;
+ /* FIXME: this seems to be prone to VLA attacks */
+ struct GNUNET_HashCode hcs[count];
+
+ /* Create the sorted policy_hash_codes */
+ {
+ size_t idx[count];
+ for (size_t i = 0; i < count; i++)
+ idx[i] = i;
+
+ /* Sort the indices according to the hash codes of the corresponding
+ * details. */
+ qsort_r (idx,
+ count,
+ sizeof(size_t),
+ hash_code_cmp,
+ fulfillment->details);
+
+ /* Finally, concatenate all hash_codes in sorted order */
+ for (size_t i = 0; i < count; i++)
+ hcs[i] = fulfillment->details[idx[i]].hash_code;
+ }
+
+
+ /* Now, add the proof to the policy_fulfillments table, retrieve the
+ * record_id */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&fulfillment->timestamp),
+ TALER_PQ_query_param_json (fulfillment->proof),
+ GNUNET_PQ_query_param_auto_from_type (&fulfillment->h_proof),
+ TALER_PQ_query_param_array_hash_code (count, hcs, pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("fulfillment_id",
+ &fulfillment->fulfillment_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "insert_proof_into_policy_fulfillments",
+ "INSERT INTO policy_fulfillments"
+ "(fulfillment_timestamp"
+ ",fulfillment_proof"
+ ",h_fulfillment_proof"
+ ",policy_hash_codes"
+ ") VALUES ($1, $2, $3, $4)"
+ " ON CONFLICT DO NOTHING;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_proof_into_policy_fulfillments",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ return qs;
+ }
+
+ /* Now, set the states of each entry corresponding to the hash_codes in
+ * policy_details accordingly */
+ for (size_t i = 0; i < count; i++)
+ {
+ struct TALER_PolicyDetails *pos = &fulfillment->details[i];
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&pos->hash_code),
+ GNUNET_PQ_query_param_timestamp (&pos->deadline),
+ TALER_PQ_query_param_amount (pg->conn,
+ &pos->commitment),
+ TALER_PQ_query_param_amount (pg->conn,
+ &pos->accumulated_total),
+ TALER_PQ_query_param_amount (pg->conn,
+ &pos->policy_fee),
+ TALER_PQ_query_param_amount (pg->conn,
+ &pos->transferable_amount),
+ GNUNET_PQ_query_param_auto_from_type (&pos->fulfillment_state),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "update_policy_details",
+ "UPDATE policy_details SET"
+ " deadline=$2"
+ ",commitment=$3"
+ ",accumulated_total=$4"
+ ",fee=$5"
+ ",transferable=$6"
+ ",fulfillment_state=$7"
+ " WHERE policy_hash_code=$1;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_policy_details",
+ params);
+ if (qs < 0)
+ return qs;
+ }
+ }
+
+ /*
+ * FIXME[oec]-#7999: When all policies of a deposit are fulfilled,
+ * unblock it and trigger a wire-transfer.
+ */
+
+ return qs;
+}
diff --git a/src/exchangedb/pg_add_policy_fulfillment_proof.h b/src/exchangedb/pg_add_policy_fulfillment_proof.h
new file mode 100644
index 000000000..77bddaf08
--- /dev/null
+++ b/src/exchangedb/pg_add_policy_fulfillment_proof.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_add_policy_fulfillment_proof.h
+ * @brief implementation of the add_policy_fulfillment_proof function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ADD_POLICY_FULFILLMENT_PROOF_H
+#define PG_ADD_POLICY_FULFILLMENT_PROOF_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Add a proof of fulfillment into the policy_fulfillments table
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param fulfillment fullfilment transaction data to be added
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_add_policy_fulfillment_proof (
+ void *cls,
+ struct TALER_PolicyFulfillmentTransactionData *fulfillment);
+
+#endif
diff --git a/src/exchangedb/pg_aggregate.c b/src/exchangedb/pg_aggregate.c
new file mode 100644
index 000000000..ba03e4a9c
--- /dev/null
+++ b/src/exchangedb/pg_aggregate.c
@@ -0,0 +1,205 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_aggregate.c
+ * @brief Implementation of the aggregate function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_compute_shard.h"
+#include "pg_event_notify.h"
+#include "pg_aggregate.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_aggregate (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_Amount *total)
+{
+ struct PostgresClosure *pg = cls;
+ uint64_t deposit_shard = TEH_PG_compute_shard (merchant_pub);
+ struct GNUNET_TIME_Absolute now = {0};
+ uint64_t sum_deposit_value;
+ uint64_t sum_deposit_frac;
+ uint64_t sum_refund_value;
+ uint64_t sum_refund_frac;
+ uint64_t sum_fee_value;
+ uint64_t sum_fee_frac;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_Amount sum_deposit;
+ struct TALER_Amount sum_refund;
+ struct TALER_Amount sum_fee;
+ struct TALER_Amount delta;
+
+ now = GNUNET_TIME_absolute_round_down (GNUNET_TIME_absolute_get (),
+ pg->aggregator_shift);
+ PREPARE (pg,
+ "aggregate",
+ "WITH bdep AS (" /* restrict to our merchant and account and mark as done */
+ " UPDATE batch_deposits"
+ " SET done=TRUE"
+ " WHERE NOT (done OR policy_blocked)" /* only actually executable deposits */
+ " AND refund_deadline<$1"
+ " AND shard=$5" /* only for efficiency, merchant_pub is what we really filter by */
+ " AND merchant_pub=$2" /* filter by target merchant */
+ " AND wire_target_h_payto=$3" /* merchant could have a 2nd bank account */
+ " RETURNING"
+ " batch_deposit_serial_id)"
+ " ,cdep AS ("
+ " SELECT"
+ " coin_deposit_serial_id"
+ " ,batch_deposit_serial_id"
+ " ,coin_pub"
+ " ,amount_with_fee AS amount"
+ " FROM coin_deposits"
+ " WHERE batch_deposit_serial_id IN (SELECT batch_deposit_serial_id FROM bdep))"
+ " ,ref AS (" /* find applicable refunds -- NOTE: may do a full join on the master, maybe find a left-join way to integrate with query above to push it to the shards? */
+ " SELECT"
+ " amount_with_fee AS refund"
+ " ,coin_pub"
+ " ,batch_deposit_serial_id" /* theoretically, coin could be in multiple refunded transactions */
+ " FROM refunds"
+ " WHERE coin_pub IN (SELECT coin_pub FROM cdep)"
+ " AND batch_deposit_serial_id IN (SELECT batch_deposit_serial_id FROM bdep))"
+ " ,ref_by_coin AS (" /* total up refunds by coin */
+ " SELECT"
+ " SUM((ref.refund).val) AS sum_refund_val"
+ " ,SUM((ref.refund).frac) AS sum_refund_frac"
+ " ,coin_pub"
+ " ,batch_deposit_serial_id" /* theoretically, coin could be in multiple refunded transactions */
+ " FROM ref"
+ " GROUP BY coin_pub, batch_deposit_serial_id)"
+ " ,norm_ref_by_coin AS (" /* normalize */
+ " SELECT"
+ " sum_refund_val + sum_refund_frac / 100000000 AS norm_refund_val"
+ " ,sum_refund_frac % 100000000 AS norm_refund_frac"
+ " ,coin_pub"
+ " ,batch_deposit_serial_id" /* theoretically, coin could be in multiple refunded transactions */
+ " FROM ref_by_coin)"
+ " ,fully_refunded_coins AS (" /* find applicable refunds -- NOTE: may do a full join on the master, maybe find a left-join way to integrate with query above to push it to the shards? */
+ " SELECT"
+ " cdep.coin_pub"
+ " FROM norm_ref_by_coin norm"
+ " JOIN cdep"
+ " ON (norm.coin_pub = cdep.coin_pub"
+ " AND norm.batch_deposit_serial_id = cdep.batch_deposit_serial_id"
+ " AND norm.norm_refund_val = (cdep.amount).val"
+ " AND norm.norm_refund_frac = (cdep.amount).frac))"
+ " ,fees AS (" /* find deposit fees for not fully refunded deposits */
+ " SELECT"
+ " denom.fee_deposit AS fee"
+ " ,cs.batch_deposit_serial_id" /* ensures we get the fee for each coin, not once per denomination */
+ " FROM cdep cs"
+ " JOIN known_coins kc" /* NOTE: may do a full join on the master, maybe find a left-join way to integrate with query above to push it to the shards? */
+ " USING (coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE coin_pub NOT IN (SELECT coin_pub FROM fully_refunded_coins))"
+ " ,dummy AS (" /* add deposits to aggregation_tracking */
+ " INSERT INTO aggregation_tracking"
+ " (batch_deposit_serial_id"
+ " ,wtid_raw)"
+ " SELECT batch_deposit_serial_id,$4"
+ " FROM bdep)"
+ "SELECT" /* calculate totals (deposits, refunds and fees) */
+ " CAST(COALESCE(SUM((cdep.amount).val),0) AS INT8) AS sum_deposit_value"
+ /* cast needed, otherwise we get NUMBER */
+ " ,COALESCE(SUM((cdep.amount).frac),0) AS sum_deposit_fraction" /* SUM over INT returns INT8 */
+ " ,CAST(COALESCE(SUM((ref.refund).val),0) AS INT8) AS sum_refund_value"
+ " ,COALESCE(SUM((ref.refund).frac),0) AS sum_refund_fraction"
+ " ,CAST(COALESCE(SUM((fees.fee).val),0) AS INT8) AS sum_fee_value"
+ " ,COALESCE(SUM((fees.fee).frac),0) AS sum_fee_fraction"
+ " FROM cdep "
+ " FULL OUTER JOIN ref ON (FALSE)" /* We just want all sums */
+ " FULL OUTER JOIN fees ON (FALSE);");
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_uint64 (&deposit_shard),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("sum_deposit_value",
+ &sum_deposit_value),
+ GNUNET_PQ_result_spec_uint64 ("sum_deposit_fraction",
+ &sum_deposit_frac),
+ GNUNET_PQ_result_spec_uint64 ("sum_refund_value",
+ &sum_refund_value),
+ GNUNET_PQ_result_spec_uint64 ("sum_refund_fraction",
+ &sum_refund_frac),
+ GNUNET_PQ_result_spec_uint64 ("sum_fee_value",
+ &sum_fee_value),
+ GNUNET_PQ_result_spec_uint64 ("sum_fee_fraction",
+ &sum_fee_frac),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "aggregate",
+ params,
+ rs);
+ }
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ total));
+ return qs;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ &sum_deposit));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ &sum_refund));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ &sum_fee));
+ sum_deposit.value = sum_deposit_frac / TALER_AMOUNT_FRAC_BASE
+ + sum_deposit_value;
+ sum_deposit.fraction = sum_deposit_frac % TALER_AMOUNT_FRAC_BASE;
+ sum_refund.value = sum_refund_frac / TALER_AMOUNT_FRAC_BASE
+ + sum_refund_value;
+ sum_refund.fraction = sum_refund_frac % TALER_AMOUNT_FRAC_BASE;
+ sum_fee.value = sum_fee_frac / TALER_AMOUNT_FRAC_BASE
+ + sum_fee_value;
+ sum_fee.fraction = sum_fee_frac % TALER_AMOUNT_FRAC_BASE; \
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&delta,
+ &sum_deposit,
+ &sum_refund));
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (total,
+ &delta,
+ &sum_fee));
+ return qs;
+}
diff --git a/src/exchangedb/pg_aggregate.h b/src/exchangedb/pg_aggregate.h
new file mode 100644
index 000000000..1f986ef9e
--- /dev/null
+++ b/src/exchangedb/pg_aggregate.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_aggregate.h
+ * @brief implementation of the aggregate function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_AGGREGATE_H
+#define PG_AGGREGATE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Aggregate all matching deposits for @a h_payto and
+ * @a merchant_pub, returning the total amounts.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto destination of the wire transfer
+ * @param merchant_pub public key of the merchant
+ * @param wtid wire transfer ID to set for the aggregate
+ * @param[out] total set to the sum of the total deposits minus applicable deposit fees and refunds
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_aggregate (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_Amount *total);
+
+#endif
diff --git a/src/exchangedb/pg_batch_ensure_coin_known.c b/src/exchangedb/pg_batch_ensure_coin_known.c
new file mode 100644
index 000000000..aca2732c6
--- /dev/null
+++ b/src/exchangedb/pg_batch_ensure_coin_known.c
@@ -0,0 +1,462 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_batch_ensure_coin_known.c
+ * @brief Implementation of the batch_ensure_coin_known function for Postgres
+ * @author Christian Grothoff
+ *
+ * FIXME: use the array support for postgres to simplify this code!
+ *
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_pq_lib.h"
+#include "pg_batch_ensure_coin_known.h"
+#include "pg_helper.h"
+
+
+static enum GNUNET_DB_QueryStatus
+insert1 (struct PostgresClosure *pg,
+ const struct TALER_CoinPublicInfo coin[1],
+ struct TALER_EXCHANGEDB_CoinInfo result[1])
+{
+ enum GNUNET_DB_QueryStatus qs;
+ bool is_denom_pub_hash_null = false;
+ bool is_age_hash_null = false;
+ PREPARE (pg,
+ "batch1_known_coin",
+ "SELECT"
+ " existed1 AS existed"
+ ",known_coin_id1 AS known_coin_id"
+ ",denom_pub_hash1 AS denom_hash"
+ ",age_commitment_hash1 AS h_age_commitment"
+ " FROM exchange_do_batch1_known_coin"
+ " ($1, $2, $3, $4);"
+ );
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("existed",
+ &result[0].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+ &result[0].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &result[0].denom_hash),
+ &is_denom_pub_hash_null),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &result[0].h_age_commitment),
+ &is_age_hash_null),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "batch1_known_coin",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0); /* should be impossible */
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* continued below */
+ }
+
+ if ( (! is_denom_pub_hash_null) &&
+ (0 != GNUNET_memcmp (&result[0].denom_hash,
+ &coin->denom_pub_hash)) )
+ {
+ GNUNET_break_op (0);
+ result[0].denom_conflict = true;
+ }
+
+ if ( (! is_denom_pub_hash_null) &&
+ (0 != GNUNET_memcmp (&result[0].denom_hash,
+ &coin[0].denom_pub_hash)) )
+ {
+ GNUNET_break_op (0);
+ result[0].denom_conflict = true;
+ }
+
+ result[0].age_conflict = TALER_AgeCommitmentHash_NoConflict;
+
+ if (is_age_hash_null != coin[0].no_age_commitment)
+ {
+ if (is_age_hash_null)
+ {
+ GNUNET_break_op (0);
+ result[0].age_conflict = TALER_AgeCommitmentHash_NullExpected;
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ result[0].age_conflict = TALER_AgeCommitmentHash_ValueExpected;
+ }
+ }
+ else if ( (! is_age_hash_null) &&
+ (0 != GNUNET_memcmp (&result[0].h_age_commitment,
+ &coin[0].h_age_commitment)) )
+ {
+ GNUNET_break_op (0);
+ result[0].age_conflict = TALER_AgeCommitmentHash_ValueDiffers;
+ }
+
+ return qs;
+}
+
+
+static enum GNUNET_DB_QueryStatus
+insert2 (struct PostgresClosure *pg,
+ const struct TALER_CoinPublicInfo coin[2],
+ struct TALER_EXCHANGEDB_CoinInfo result[2])
+{
+ enum GNUNET_DB_QueryStatus qs;
+ bool is_denom_pub_hash_null[2] = {false, false};
+ bool is_age_hash_null[2] = {false, false};
+
+ PREPARE (pg,
+ "batch2_known_coin",
+ "SELECT"
+ " existed1 AS existed"
+ ",known_coin_id1 AS known_coin_id"
+ ",denom_pub_hash1 AS denom_hash"
+ ",age_commitment_hash1 AS h_age_commitment"
+ ",existed2 AS existed2"
+ ",known_coin_id2 AS known_coin_id2"
+ ",denom_pub_hash2 AS denom_hash2"
+ ",age_commitment_hash2 AS h_age_commitment2"
+ " FROM exchange_do_batch2_known_coin"
+ " ($1, $2, $3, $4, $5, $6, $7, $8);"
+ );
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+
+ GNUNET_PQ_query_param_auto_from_type (&coin[1].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[1].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[1].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("existed",
+ &result[0].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+ &result[0].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &result[0].denom_hash),
+ &is_denom_pub_hash_null[0]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &result[0].h_age_commitment),
+ &is_age_hash_null[0]),
+ GNUNET_PQ_result_spec_bool ("existed2",
+ &result[1].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id2",
+ &result[1].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash2",
+ &result[1].denom_hash),
+ &is_denom_pub_hash_null[1]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash2",
+ &result[1].h_age_commitment),
+ &is_age_hash_null[1]),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "batch2_known_coin",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0); /* should be impossible */
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* continued below */
+ }
+
+ for (int i = 0; i < 2; i++)
+ {
+ if ( (! is_denom_pub_hash_null[i]) &&
+ (0 != GNUNET_memcmp (&result[i].denom_hash,
+ &coin[i].denom_pub_hash)) )
+ {
+ GNUNET_break_op (0);
+ result[i].denom_conflict = true;
+ }
+
+ result[i].age_conflict = TALER_AgeCommitmentHash_NoConflict;
+
+ if (is_age_hash_null[i] != coin[i].no_age_commitment)
+ {
+ if (is_age_hash_null[i])
+ {
+ GNUNET_break_op (0);
+ result[i].age_conflict = TALER_AgeCommitmentHash_NullExpected;
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ result[i].age_conflict = TALER_AgeCommitmentHash_ValueExpected;
+ }
+ }
+ else if ( (! is_age_hash_null[i]) &&
+ (0 != GNUNET_memcmp (&result[i].h_age_commitment,
+ &coin[i].h_age_commitment)) )
+ {
+ GNUNET_break_op (0);
+ result[i].age_conflict = TALER_AgeCommitmentHash_ValueDiffers;
+ }
+ }
+
+ return qs;
+}
+
+
+static enum GNUNET_DB_QueryStatus
+insert4 (struct PostgresClosure *pg,
+ const struct TALER_CoinPublicInfo coin[4],
+ struct TALER_EXCHANGEDB_CoinInfo result[4])
+{
+ enum GNUNET_DB_QueryStatus qs;
+ bool is_denom_pub_hash_null[4] = {false, false, false, false};
+ bool is_age_hash_null[4] = {false, false, false, false};
+ PREPARE (pg,
+ "batch4_known_coin",
+ "SELECT"
+ " existed1 AS existed"
+ ",known_coin_id1 AS known_coin_id"
+ ",denom_pub_hash1 AS denom_hash"
+ ",age_commitment_hash1 AS h_age_commitment"
+ ",existed2 AS existed2"
+ ",known_coin_id2 AS known_coin_id2"
+ ",denom_pub_hash2 AS denom_hash2"
+ ",age_commitment_hash2 AS h_age_commitment2"
+ ",existed3 AS existed3"
+ ",known_coin_id3 AS known_coin_id3"
+ ",denom_pub_hash3 AS denom_hash3"
+ ",age_commitment_hash3 AS h_age_commitment3"
+ ",existed4 AS existed4"
+ ",known_coin_id4 AS known_coin_id4"
+ ",denom_pub_hash4 AS denom_hash4"
+ ",age_commitment_hash4 AS h_age_commitment4"
+ " FROM exchange_do_batch2_known_coin"
+ " ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16);"
+ );
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+
+ GNUNET_PQ_query_param_auto_from_type (&coin[1].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[1].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[1].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+
+ GNUNET_PQ_query_param_auto_from_type (&coin[2].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[2].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[2].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[2].denom_sig),
+
+ GNUNET_PQ_query_param_auto_from_type (&coin[3].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[3].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[3].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[3].denom_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("existed",
+ &result[0].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+ &result[0].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &result[0].denom_hash),
+ &is_denom_pub_hash_null[0]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &result[0].h_age_commitment),
+ &is_age_hash_null[0]),
+ GNUNET_PQ_result_spec_bool ("existed2",
+ &result[1].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id2",
+ &result[1].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash2",
+ &result[1].denom_hash),
+ &is_denom_pub_hash_null[1]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash2",
+ &result[1].h_age_commitment),
+ &is_age_hash_null[1]),
+ GNUNET_PQ_result_spec_bool ("existed3",
+ &result[2].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id3",
+ &result[2].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash3",
+ &result[2].denom_hash),
+ &is_denom_pub_hash_null[2]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash3",
+ &result[2].h_age_commitment),
+ &is_age_hash_null[2]),
+ GNUNET_PQ_result_spec_bool ("existed4",
+ &result[3].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id4",
+ &result[3].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash4",
+ &result[3].denom_hash),
+ &is_denom_pub_hash_null[3]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash4",
+ &result[3].h_age_commitment),
+ &is_age_hash_null[3]),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "batch4_known_coin",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0); /* should be impossible */
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* continued below */
+ }
+
+ for (int i = 0; i < 4; i++)
+ {
+ if ( (! is_denom_pub_hash_null[i]) &&
+ (0 != GNUNET_memcmp (&result[i].denom_hash,
+ &coin[i].denom_pub_hash)) )
+ {
+ GNUNET_break_op (0);
+ result[i].denom_conflict = true;
+ }
+
+ result[i].age_conflict = TALER_AgeCommitmentHash_NoConflict;
+
+ if (is_age_hash_null[i] != coin[i].no_age_commitment)
+ {
+ if (is_age_hash_null[i])
+ {
+ GNUNET_break_op (0);
+ result[i].age_conflict = TALER_AgeCommitmentHash_NullExpected;
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ result[i].age_conflict = TALER_AgeCommitmentHash_ValueExpected;
+ }
+ }
+ else if ( (! is_age_hash_null[i]) &&
+ (0 != GNUNET_memcmp (&result[i].h_age_commitment,
+ &coin[i].h_age_commitment)) )
+ {
+ GNUNET_break_op (0);
+ result[i].age_conflict = TALER_AgeCommitmentHash_ValueDiffers;
+ }
+ }
+
+ return qs;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_batch_ensure_coin_known (
+ void *cls,
+ const struct TALER_CoinPublicInfo *coin,
+ struct TALER_EXCHANGEDB_CoinInfo *result,
+ unsigned int coin_length,
+ unsigned int batch_size)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs = 0;
+ unsigned int i = 0;
+
+ while ( (qs >= 0) &&
+ (i < coin_length) )
+ {
+ unsigned int bs = GNUNET_MIN (batch_size,
+ coin_length - i);
+ if (bs >= 4)
+ {
+ qs = insert4 (pg,
+ &coin[i],
+ &result[i]);
+ i += 4;
+ continue;
+ }
+ switch (bs)
+ {
+ case 3:
+ case 2:
+ qs = insert2 (pg,
+ &coin[i],
+ &result[i]);
+ i += 2;
+ break;
+ case 1:
+ qs = insert1 (pg,
+ &coin[i],
+ &result[i]);
+ i += 1;
+ break;
+ case 0:
+ GNUNET_assert (0);
+ break;
+ }
+ } /* end while */
+ if (qs < 0)
+ return qs;
+ return i;
+}
diff --git a/src/exchangedb/pg_batch_ensure_coin_known.h b/src/exchangedb/pg_batch_ensure_coin_known.h
new file mode 100644
index 000000000..2c53676d9
--- /dev/null
+++ b/src/exchangedb/pg_batch_ensure_coin_known.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_batch_ensure_coin_known.h
+ * @brief implementation of the batch_ensure_coin_known function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_BATCH_ENSURE_COIN_KNOWN_H
+#define PG_BATCH_ENSURE_COIN_KNOWN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Make sure the array of given @a coin is known to the database.
+ *
+ * @param cls database connection plugin state
+ * @param coin array of coins that must be made known
+ * @param[out] result array where to store information about each coin
+ * @param coin_length length of the @a coin and @a result arraysf
+ * @param batch_size desired (maximum) batch size
+ * @return database transaction status, non-negative on success
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_batch_ensure_coin_known (
+ void *cls,
+ const struct TALER_CoinPublicInfo *coin,
+ struct TALER_EXCHANGEDB_CoinInfo *result,
+ unsigned int coin_length,
+ unsigned int batch_size);
+
+#endif
diff --git a/src/exchangedb/pg_begin_revolving_shard.c b/src/exchangedb/pg_begin_revolving_shard.c
new file mode 100644
index 000000000..86cdf80fd
--- /dev/null
+++ b/src/exchangedb/pg_begin_revolving_shard.c
@@ -0,0 +1,263 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_begin_revolving_shard.c
+ * @brief Implementation of the begin_revolving_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_begin_revolving_shard.h"
+#include "pg_commit.h"
+#include "pg_helper.h"
+#include "pg_start.h"
+#include "pg_rollback.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_begin_revolving_shard (void *cls,
+ const char *job_name,
+ uint32_t shard_size,
+ uint32_t shard_limit,
+ uint32_t *start_row,
+ uint32_t *end_row)
+{
+ struct PostgresClosure *pg = cls;
+
+ GNUNET_assert (shard_limit <= 1U + (uint32_t) INT_MAX);
+ GNUNET_assert (shard_limit > 0);
+ GNUNET_assert (shard_size > 0);
+ for (unsigned int retries = 0; retries<3; retries++)
+ {
+ if (GNUNET_OK !=
+ TEH_PG_start (pg,
+ "begin_revolving_shard"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ /* First, find last 'end_row' */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ uint32_t last_end;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint32 ("end_row",
+ &last_end),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_begin_revolving_shard() */
+ PREPARE (pg,
+ "get_last_revolving_shard",
+ "SELECT"
+ " end_row"
+ " FROM revolving_work_shards"
+ " WHERE job_name=$1"
+ " ORDER BY end_row DESC"
+ " LIMIT 1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_last_revolving_shard",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ *start_row = 1U + last_end;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *start_row = 0; /* base-case: no shards yet */
+ break; /* continued below */
+ }
+ } /* get_last_shard */
+
+ if (*start_row < shard_limit)
+ {
+ /* Claim fresh shard */
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_uint32 (start_row),
+ GNUNET_PQ_query_param_uint32 (end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ *end_row = GNUNET_MIN (shard_limit,
+ *start_row + shard_size - 1);
+ now = GNUNET_TIME_absolute_get ();
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to claim shard %llu-%llu\n",
+ (unsigned long long) *start_row,
+ (unsigned long long) *end_row);
+
+ /* Used in #postgres_claim_revolving_shard() */
+ PREPARE (pg,
+ "create_revolving_shard",
+ "INSERT INTO revolving_work_shards"
+ "(job_name"
+ ",last_attempt"
+ ",start_row"
+ ",end_row"
+ ",active"
+ ") VALUES "
+ "($1, $2, $3, $4, TRUE);");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "create_revolving_shard",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* continued below (with commit) */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* someone else got this shard already,
+ try again */
+ TEH_PG_rollback (pg);
+ continue;
+ }
+ } /* end create fresh reovlving shard */
+ else
+ {
+ /* claim oldest existing shard */
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint32 ("start_row",
+ start_row),
+ GNUNET_PQ_result_spec_uint32 ("end_row",
+ end_row),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_begin_revolving_shard() */
+ PREPARE (pg,
+ "get_open_revolving_shard",
+ "SELECT"
+ " start_row"
+ ",end_row"
+ " FROM revolving_work_shards"
+ " WHERE job_name=$1"
+ " AND active=FALSE"
+ " ORDER BY last_attempt ASC"
+ " LIMIT 1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_open_revolving_shard",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* no open shards available */
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_uint32 (start_row),
+ GNUNET_PQ_query_param_uint32 (end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ now = GNUNET_TIME_timestamp_get ();
+
+ /* Used in #postgres_begin_revolving_shard() */
+ PREPARE (pg,
+ "reclaim_revolving_shard",
+ "UPDATE revolving_work_shards"
+ " SET last_attempt=$2"
+ " ,active=TRUE"
+ " WHERE job_name=$1"
+ " AND start_row=$3"
+ " AND end_row=$4");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "reclaim_revolving_shard",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* continue with commit */
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0); /* logic error, should be impossible */
+ TEH_PG_rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ break; /* continue with commit */
+ }
+ } /* end claim oldest existing shard */
+
+ /* commit */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_PG_commit (pg);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ }
+ } /* retry 'for' loop */
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
diff --git a/src/exchangedb/pg_begin_revolving_shard.h b/src/exchangedb/pg_begin_revolving_shard.h
new file mode 100644
index 000000000..0755f886b
--- /dev/null
+++ b/src/exchangedb/pg_begin_revolving_shard.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_begin_revolving_shard.h
+ * @brief implementation of the begin_revolving_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_BEGIN_REVOLVING_SHARD_H
+#define PG_BEGIN_REVOLVING_SHARD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to grab a revolving work shard on an operation @a op. Runs
+ * in its own transaction. Returns the oldest inactive shard.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to grab a revolving shard for
+ * @param shard_size desired shard size
+ * @param shard_limit exclusive end of the shard range
+ * @param[out] start_row inclusive start row of the shard (returned)
+ * @param[out] end_row inclusive end row of the shard (returned)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_begin_revolving_shard (void *cls,
+ const char *job_name,
+ uint32_t shard_size,
+ uint32_t shard_limit,
+ uint32_t *start_row,
+ uint32_t *end_row);
+
+#endif
diff --git a/src/exchangedb/pg_begin_shard.c b/src/exchangedb/pg_begin_shard.c
new file mode 100644
index 000000000..48e077990
--- /dev/null
+++ b/src/exchangedb/pg_begin_shard.c
@@ -0,0 +1,266 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_begin_shard.c
+ * @brief Implementation of the begin_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_begin_shard.h"
+#include "pg_helper.h"
+#include "pg_start.h"
+#include "pg_rollback.h"
+#include "pg_commit.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_begin_shard (void *cls,
+ const char *job_name,
+ struct GNUNET_TIME_Relative delay,
+ uint64_t shard_size,
+ uint64_t *start_row,
+ uint64_t *end_row)
+{
+ struct PostgresClosure *pg = cls;
+
+ for (unsigned int retries = 0; retries<10; retries++)
+ {
+ if (GNUNET_OK !=
+ TEH_PG_start (pg,
+ "begin_shard"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ {
+ struct GNUNET_TIME_Absolute past;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_absolute_time (&past),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("start_row",
+ start_row),
+ GNUNET_PQ_result_spec_uint64 ("end_row",
+ end_row),
+ GNUNET_PQ_result_spec_end
+ };
+
+ past = GNUNET_TIME_absolute_get ();
+ PREPARE (pg,
+ "get_open_shard",
+ "SELECT"
+ " start_row"
+ ",end_row"
+ " FROM work_shards"
+ " WHERE job_name=$1"
+ " AND completed=FALSE"
+ " AND last_attempt<$2"
+ " ORDER BY last_attempt ASC"
+ " LIMIT 1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_open_shard",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization error on getting open shard\n");
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_uint64 (start_row),
+ GNUNET_PQ_query_param_uint64 (end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ now = GNUNET_TIME_relative_to_absolute (delay);
+ PREPARE (pg,
+ "reclaim_shard",
+ "UPDATE work_shards"
+ " SET last_attempt=$2"
+ " WHERE job_name=$1"
+ " AND start_row=$3"
+ " AND end_row=$4");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "reclaim_shard",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization error on claiming open shard\n");
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ goto commit;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0); /* logic error, should be impossible */
+ TEH_PG_rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ break; /* actually unreachable */
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ break; /* continued below */
+ }
+ } /* get_open_shard */
+
+ /* No open shard, find last 'end_row' */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("end_row",
+ start_row),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_last_shard",
+ "SELECT"
+ " end_row"
+ " FROM work_shards"
+ " WHERE job_name=$1"
+ " ORDER BY end_row DESC"
+ " LIMIT 1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_last_shard",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization error on getting last shard\n");
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *start_row = 0; /* base-case: no shards yet */
+ break; /* continued below */
+ }
+ *end_row = *start_row + shard_size;
+ } /* get_last_shard */
+
+ /* Claim fresh shard */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_uint64 (start_row),
+ GNUNET_PQ_query_param_uint64 (end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ now = GNUNET_TIME_relative_to_absolute (delay);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to claim shard (%llu-%llu]\n",
+ (unsigned long long) *start_row,
+ (unsigned long long) *end_row);
+
+ PREPARE (pg,
+ "claim_next_shard",
+ "INSERT INTO work_shards"
+ "(job_name"
+ ",last_attempt"
+ ",start_row"
+ ",end_row"
+ ") VALUES "
+ "($1, $2, $3, $4);");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "claim_next_shard",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization error on claiming next shard\n");
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* continued below */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* someone else got this shard already,
+ try again */
+ TEH_PG_rollback (pg);
+ continue;
+ }
+ } /* claim_next_shard */
+
+ /* commit */
+commit:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_PG_commit (pg);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization error on commit for beginning shard\n");
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Claimed new shard\n");
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ }
+ } /* retry 'for' loop */
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
diff --git a/src/exchangedb/pg_begin_shard.h b/src/exchangedb/pg_begin_shard.h
new file mode 100644
index 000000000..16f19491d
--- /dev/null
+++ b/src/exchangedb/pg_begin_shard.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_begin_shard.h
+ * @brief implementation of the begin_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_BEGIN_SHARD_H
+#define PG_BEGIN_SHARD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called to grab a work shard on an operation @a op. Runs in its
+ * own transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to grab a word shard for
+ * @param delay minimum age of a shard to grab
+ * @param shard_size desired shard size
+ * @param[out] start_row inclusive start row of the shard (returned)
+ * @param[out] end_row exclusive end row of the shard (returned)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_begin_shard (void *cls,
+ const char *job_name,
+ struct GNUNET_TIME_Relative delay,
+ uint64_t shard_size,
+ uint64_t *start_row,
+ uint64_t *end_row);
+
+#endif
diff --git a/src/exchangedb/pg_commit.c b/src/exchangedb/pg_commit.c
new file mode 100644
index 000000000..8c4f87c90
--- /dev/null
+++ b/src/exchangedb/pg_commit.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_commit.c
+ * @brief Implementation of the commit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_commit.h"
+#include "pg_helper.h"
+
+
+/**
+ * Commit the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return final transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_commit (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_break (NULL != pg->transaction_name);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Committing transaction `%s'\n",
+ pg->transaction_name);
+ /* used in #postgres_commit */
+ PREPARE (pg,
+ "do_commit",
+ "COMMIT");
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "do_commit",
+ params);
+ pg->transaction_name = NULL;
+ return qs;
+}
diff --git a/src/exchangedb/pg_commit.h b/src/exchangedb/pg_commit.h
new file mode 100644
index 000000000..b1f4f9615
--- /dev/null
+++ b/src/exchangedb/pg_commit.h
@@ -0,0 +1,37 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_commit.h
+ * @brief implementation of the commit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_COMMIT_H
+#define PG_COMMIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Commit the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return final transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_commit (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_complete_shard.c b/src/exchangedb/pg_complete_shard.c
new file mode 100644
index 000000000..8e62809c5
--- /dev/null
+++ b/src/exchangedb/pg_complete_shard.c
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_complete_shard.c
+ * @brief Implementation of the complete_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_complete_shard.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_complete_shard (void *cls,
+ const char *job_name,
+ uint64_t start_row,
+ uint64_t end_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_uint64 (&start_row),
+ GNUNET_PQ_query_param_uint64 (&end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Completing shard %llu-%llu\n",
+ (unsigned long long) start_row,
+ (unsigned long long) end_row);
+ PREPARE (pg,
+ "complete_shard",
+ "UPDATE work_shards"
+ " SET completed=TRUE"
+ " WHERE job_name=$1"
+ " AND start_row=$2"
+ " AND end_row=$3");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "complete_shard",
+ params);
+}
diff --git a/src/exchangedb/pg_complete_shard.h b/src/exchangedb/pg_complete_shard.h
new file mode 100644
index 000000000..f06c0483b
--- /dev/null
+++ b/src/exchangedb/pg_complete_shard.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_complete_shard.h
+ * @brief implementation of the complete_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_COMPLETE_SHARD_H
+#define PG_COMPLETE_SHARD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to persist that work on a shard was completed.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to grab a word shard for
+ * @param start_row inclusive start row of the shard
+ * @param end_row exclusive end row of the shard
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_complete_shard (void *cls,
+ const char *job_name,
+ uint64_t start_row,
+ uint64_t end_row);
+
+#endif
diff --git a/src/exchangedb/pg_compute_shard.c b/src/exchangedb/pg_compute_shard.c
new file mode 100644
index 000000000..1dea591f1
--- /dev/null
+++ b/src/exchangedb/pg_compute_shard.c
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_compute_shard.c
+ * @brief Implementation of the compute_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_compute_shard.h"
+#include "pg_helper.h"
+
+
+uint64_t
+TEH_PG_compute_shard (const struct TALER_MerchantPublicKeyP *merchant_pub)
+{
+ uint32_t res;
+
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (&res,
+ sizeof (res),
+ merchant_pub,
+ sizeof (*merchant_pub),
+ "VOID",
+ 4,
+ NULL, 0));
+ /* interpret hash result as NBO for platform independence,
+ convert to HBO and map to [0..2^31-1] range */
+ res = ntohl (res);
+ if (res > INT32_MAX)
+ res += INT32_MIN;
+ GNUNET_assert (res <= INT32_MAX);
+ return (uint64_t) res;
+}
diff --git a/src/exchangedb/pg_compute_shard.h b/src/exchangedb/pg_compute_shard.h
new file mode 100644
index 000000000..d9bde2d3e
--- /dev/null
+++ b/src/exchangedb/pg_compute_shard.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_compute_shard.h
+ * @brief implementation of the compute_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_COMPUTE_SHARD_H
+#define PG_COMPUTE_SHARD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Compute the shard number of a given @a merchant_pub.
+ *
+ * @param merchant_pub merchant public key to compute shard for
+ * @return shard number
+ */
+uint64_t
+TEH_PG_compute_shard (const struct TALER_MerchantPublicKeyP *merchant_pub);
+
+
+#endif
diff --git a/src/exchangedb/pg_count_known_coins.c b/src/exchangedb/pg_count_known_coins.c
new file mode 100644
index 000000000..872965ac9
--- /dev/null
+++ b/src/exchangedb/pg_count_known_coins.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_count_known_coins.c
+ * @brief Implementation of the count_known_coins function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_count_known_coins.h"
+#include "pg_helper.h"
+
+long long
+TEH_PG_count_known_coins (void *cls,
+ const struct
+ TALER_DenominationHashP *denom_pub_hash)
+{
+ struct PostgresClosure *pg = cls;
+ uint64_t count;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("count",
+ &count),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+
+ PREPARE (pg,
+ "count_known_coins",
+ "SELECT"
+ " COUNT(*) AS count"
+ " FROM known_coins"
+ " WHERE denominations_serial="
+ " (SELECT denominations_serial"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$1);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "count_known_coins",
+ params,
+ rs);
+ if (0 > qs)
+ return (long long) qs;
+ return (long long) count;
+}
diff --git a/src/exchangedb/pg_count_known_coins.h b/src/exchangedb/pg_count_known_coins.h
new file mode 100644
index 000000000..69f07bdf4
--- /dev/null
+++ b/src/exchangedb/pg_count_known_coins.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_count_known_coins.h
+ * @brief implementation of the count_known_coins function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_COUNT_KNOWN_COINS_H
+#define PG_COUNT_KNOWN_COINS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Count the number of known coins by denomination.
+ *
+ * @param cls database connection plugin state
+ * @param denom_pub_hash denomination to count by
+ * @return number of coins if non-negative, otherwise an `enum GNUNET_DB_QueryStatus`
+ */
+long long
+TEH_PG_count_known_coins (void *cls,
+ const struct
+ TALER_DenominationHashP *denom_pub_hash);
+
+#endif
diff --git a/src/exchangedb/pg_create_aggregation_transient.c b/src/exchangedb/pg_create_aggregation_transient.c
new file mode 100644
index 000000000..4ab537d3a
--- /dev/null
+++ b/src/exchangedb/pg_create_aggregation_transient.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_create_aggregation_transient.c
+ * @brief Implementation of the create_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_create_aggregation_transient.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_create_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *exchange_account_section,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t kyc_requirement_row,
+ const struct TALER_Amount *total)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ total),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_uint64 (&kyc_requirement_row),
+ GNUNET_PQ_query_param_string (exchange_account_section),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "create_aggregation_transient",
+ "INSERT INTO aggregation_transient"
+ " (amount"
+ " ,merchant_pub"
+ " ,wire_target_h_payto"
+ " ,legitimization_requirement_serial_id"
+ " ,exchange_account_section"
+ " ,wtid_raw)"
+ " VALUES ($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "create_aggregation_transient",
+ params);
+}
diff --git a/src/exchangedb/pg_create_aggregation_transient.h b/src/exchangedb/pg_create_aggregation_transient.h
new file mode 100644
index 000000000..2f0a348b2
--- /dev/null
+++ b/src/exchangedb/pg_create_aggregation_transient.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_create_aggregation_transient.h
+ * @brief implementation of the create_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_CREATE_AGGREGATION_TRANSIENT_H
+#define PG_CREATE_AGGREGATION_TRANSIENT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Create a new entry in the transient aggregation table.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto destination of the wire transfer
+ * @param exchange_account_section exchange account to use
+ * @param merchant_pub public key of the merchant receiving the transfer
+ * @param wtid the raw wire transfer identifier to be used
+ * @param kyc_requirement_row row in legitimization_requirements that need to be satisfied to continue, or 0 for none
+ * @param total amount to be wired in the future
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_create_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *exchange_account_section,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t kyc_requirement_row,
+ const struct TALER_Amount *total);
+
+#endif
diff --git a/src/exchangedb/pg_create_tables.c b/src/exchangedb/pg_create_tables.c
new file mode 100644
index 000000000..f6a061904
--- /dev/null
+++ b/src/exchangedb/pg_create_tables.c
@@ -0,0 +1,72 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_create_tables.c
+ * @brief Implementation of the create_tables function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_create_tables.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_create_tables (void *cls,
+ bool support_partitions,
+ uint32_t num_partitions)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_Context *conn;
+ enum GNUNET_GenericReturnValue ret = GNUNET_OK;
+ struct GNUNET_PQ_QueryParam params[] = {
+ support_partitions
+ ? GNUNET_PQ_query_param_uint32 (&num_partitions)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_PreparedStatement ps[] = {
+ GNUNET_PQ_make_prepare ("create_tables",
+ "SELECT"
+ " exchange.do_create_tables"
+ " ($1);"),
+ GNUNET_PQ_PREPARED_STATEMENT_END
+ };
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+ "exchangedb-postgres",
+ "exchange-",
+ es,
+ ps);
+ if (NULL == conn)
+ return GNUNET_SYSERR;
+ if (0 >
+ GNUNET_PQ_eval_prepared_non_select (conn,
+ "create_tables",
+ params))
+ ret = GNUNET_SYSERR;
+ if (GNUNET_OK == ret)
+ ret = GNUNET_PQ_exec_sql (conn,
+ "procedures");
+ GNUNET_PQ_disconnect (conn);
+ return ret;
+}
diff --git a/src/exchangedb/pg_create_tables.h b/src/exchangedb/pg_create_tables.h
new file mode 100644
index 000000000..58f5aae73
--- /dev/null
+++ b/src/exchangedb/pg_create_tables.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_create_tables.h
+ * @brief implementation of the create_tables function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_CREATE_TABLES_H
+#define PG_CREATE_TABLES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Create the necessary tables if they are not present
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param support_partitions true to enable partitioning support (disables foreign key constraints)
+ * @param num_partitions number of partitions to create,
+ * (0 to not actually use partitions, 1 to only
+ * setup a default partition, >1 for real partitions)
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_create_tables (void *cls,
+ bool support_partitions,
+ uint32_t num_partitions);
+
+
+#endif
diff --git a/src/exchangedb/pg_delete_aggregation_transient.c b/src/exchangedb/pg_delete_aggregation_transient.c
new file mode 100644
index 000000000..63c5c0a23
--- /dev/null
+++ b/src/exchangedb/pg_delete_aggregation_transient.c
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_delete_aggregation_transient.c
+ * @brief Implementation of the delete_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_delete_aggregation_transient.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_delete_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "delete_aggregation_transient",
+ "DELETE FROM aggregation_transient"
+ " WHERE wire_target_h_payto=$1"
+ " AND wtid_raw=$2");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_aggregation_transient",
+ params);
+}
diff --git a/src/exchangedb/pg_delete_aggregation_transient.h b/src/exchangedb/pg_delete_aggregation_transient.h
new file mode 100644
index 000000000..f74b0179e
--- /dev/null
+++ b/src/exchangedb/pg_delete_aggregation_transient.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_delete_aggregation_transient.h
+ * @brief implementation of the delete_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DELETE_AGGREGATION_TRANSIENT_H
+#define PG_DELETE_AGGREGATION_TRANSIENT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Delete existing entry in the transient aggregation table.
+ * @a h_payto is only needed for query performance.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto destination of the wire transfer
+ * @param wtid the raw wire transfer identifier to update
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_delete_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid);
+
+#endif
diff --git a/src/exchangedb/pg_delete_shard_locks.c b/src/exchangedb/pg_delete_shard_locks.c
new file mode 100644
index 000000000..dbb0f29ac
--- /dev/null
+++ b/src/exchangedb/pg_delete_shard_locks.c
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_delete_shard_locks.c
+ * @brief Implementation of the delete_shard_locks function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_delete_shard_locks.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_delete_shard_locks (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("DELETE FROM work_shards;"),
+ GNUNET_PQ_make_execute ("DELETE FROM revolving_work_shards;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ return GNUNET_PQ_exec_statements (pg->conn,
+ es);
+}
diff --git a/src/exchangedb/pg_delete_shard_locks.h b/src/exchangedb/pg_delete_shard_locks.h
new file mode 100644
index 000000000..e8df2d67d
--- /dev/null
+++ b/src/exchangedb/pg_delete_shard_locks.h
@@ -0,0 +1,38 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_delete_shard_locks.h
+ * @brief implementation of the delete_shard_locks function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DELETE_SHARD_LOCKS_H
+#define PG_DELETE_SHARD_LOCKS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called to delete all revolving shards.
+ * To be used after a crash or when the shard size is
+ * changed.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @return transaction status code
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_delete_shard_locks (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_do_age_withdraw.c b/src/exchangedb/pg_do_age_withdraw.c
new file mode 100644
index 000000000..970e65b5d
--- /dev/null
+++ b/src/exchangedb/pg_do_age_withdraw.c
@@ -0,0 +1,108 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw.c
+ * @brief Implementation of the do_batch_withdraw function for Postgres
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_pq_lib.h"
+#include "taler_pq_lib.h"
+#include "pg_do_batch_withdraw.h"
+#include "pg_helper.h"
+#include <gnunet/gnunet_time_lib.h>
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_age_withdraw (
+ void *cls,
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
+ struct GNUNET_TIME_Timestamp now,
+ bool *found,
+ bool *balance_ok,
+ struct TALER_Amount *reserve_balance,
+ bool *age_ok,
+ uint16_t *required_age,
+ uint32_t *reserve_birthday,
+ bool *conflict)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp gc;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ &commitment->amount_with_fee),
+ GNUNET_PQ_query_param_auto_from_type (&commitment->reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (&commitment->reserve_sig),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_timestamp (&gc),
+ GNUNET_PQ_query_param_auto_from_type (&commitment->h_commitment),
+ GNUNET_PQ_query_param_uint16 (&commitment->max_age),
+ GNUNET_PQ_query_param_uint16 (&commitment->noreveal_index),
+ TALER_PQ_query_param_array_blinded_coin_hash (commitment->num_coins,
+ commitment->h_coin_evs,
+ pg->conn),
+ GNUNET_PQ_query_param_array_uint64 (commitment->num_coins,
+ commitment->denom_serials,
+ pg->conn),
+ TALER_PQ_query_param_array_blinded_denom_sig (commitment->num_coins,
+ commitment->denom_sigs,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("reserve_found",
+ found),
+ GNUNET_PQ_result_spec_bool ("balance_ok",
+ balance_ok),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_balance",
+ reserve_balance),
+ GNUNET_PQ_result_spec_bool ("age_ok",
+ age_ok),
+ GNUNET_PQ_result_spec_uint16 ("required_age",
+ required_age),
+ GNUNET_PQ_result_spec_uint32 ("reserve_birthday",
+ reserve_birthday),
+ GNUNET_PQ_result_spec_bool ("conflict",
+ conflict),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ gc = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (now.abs_time,
+ pg->legal_reserve_expiration_time));
+ PREPARE (pg,
+ "call_age_withdraw",
+ "SELECT "
+ " reserve_found"
+ ",balance_ok"
+ ",reserve_balance"
+ ",age_ok"
+ ",required_age"
+ ",reserve_birthday"
+ ",conflict"
+ " FROM exchange_do_age_withdraw"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_age_withdraw",
+ params,
+ rs);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
+}
diff --git a/src/exchangedb/pg_do_age_withdraw.h b/src/exchangedb/pg_do_age_withdraw.h
new file mode 100644
index 000000000..fb435a309
--- /dev/null
+++ b/src/exchangedb/pg_do_age_withdraw.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_age_withdraw.h
+ * @brief implementation of the do_age_withdraw function for Postgres
+ * @author Özgür Kesim
+ */
+#ifndef PG_DO_AGE_WITHDRAW_H
+#define PG_DO_AGE_WITHDRAW_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform reserve update as part of an age-withdraw operation, checking for
+ * sufficient balance and fulfillment of age requirements. Finally persisting
+ * the withdrawal details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param commitment the commitment with all parameters
+ * @param now current time (rounded)
+ * @param[out] found set to true if the reserve was found
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] reserve_balance set to the original reserve balance (at the start of this transaction)
+ * @param[out] age_ok set to true if no age requirements are present on the reserve
+ * @param[out] required_age if @e age_ok is false, set to the maximum allowed age when withdrawing from this reserve
+ * @param[out] reserve_birthday if @e age_ok is false, set to the birthday of the reserve
+ * @param[out] conflict set to true if there already is an entry in the database for the given pair (h_commitment, reserve_pub)
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_age_withdraw (
+ void *cls,
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
+ const struct GNUNET_TIME_Timestamp now,
+ bool *found,
+ bool *balance_ok,
+ struct TALER_Amount *reserve_balance,
+ bool *age_ok,
+ uint16_t *required_age,
+ uint32_t *reserve_birthday,
+ bool *conflict);
+
+#endif
diff --git a/src/exchangedb/pg_do_batch_withdraw.c b/src/exchangedb/pg_do_batch_withdraw.c
new file mode 100644
index 000000000..f5571ddbb
--- /dev/null
+++ b/src/exchangedb/pg_do_batch_withdraw.c
@@ -0,0 +1,89 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw.c
+ * @brief Implementation of the do_batch_withdraw function for Postgres
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_batch_withdraw.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_batch_withdraw (
+ void *cls,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *amount,
+ bool do_age_check,
+ bool *found,
+ bool *balance_ok,
+ struct TALER_Amount *reserve_balance,
+ bool *age_ok,
+ uint16_t *allowed_maximum_age,
+ uint64_t *ruuid)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp gc;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ amount),
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_timestamp (&gc),
+ GNUNET_PQ_query_param_bool (do_age_check),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("reserve_found",
+ found),
+ GNUNET_PQ_result_spec_bool ("balance_ok",
+ balance_ok),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_balance",
+ reserve_balance),
+ GNUNET_PQ_result_spec_bool ("age_ok",
+ age_ok),
+ GNUNET_PQ_result_spec_uint16 ("allowed_maximum_age",
+ allowed_maximum_age),
+ GNUNET_PQ_result_spec_uint64 ("ruuid",
+ ruuid),
+ GNUNET_PQ_result_spec_end
+ };
+
+ gc = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (now.abs_time,
+ pg->legal_reserve_expiration_time));
+ PREPARE (pg,
+ "call_batch_withdraw",
+ "SELECT "
+ " reserve_found"
+ ",balance_ok"
+ ",reserve_balance"
+ ",age_ok"
+ ",allowed_maximum_age"
+ ",ruuid"
+ " FROM exchange_do_batch_withdraw"
+ " ($1,$2,$3,$4,$5);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_batch_withdraw",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_batch_withdraw.h b/src/exchangedb/pg_do_batch_withdraw.h
new file mode 100644
index 000000000..486f8d1b2
--- /dev/null
+++ b/src/exchangedb/pg_do_batch_withdraw.h
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw.h
+ * @brief implementation of the do_batch_withdraw function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_BATCH_WITHDRAW_H
+#define PG_DO_BATCH_WITHDRAW_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform reserve update as part of a batch withdraw operation, checking
+ * for sufficient balance. Persisting the withdrawal details is done
+ * separately!
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param now current time (rounded)
+ * @param reserve_pub public key of the reserve to debit
+ * @param amount total amount to withdraw
+ * @param age_check_required if true, fail if age requirements are set on the reserve
+ * @param[out] found set to true if the reserve was found
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] reserve_balance set to the original reserve balance (at the start of this transaction)
+ * @param[out] age_ok set to true if no age requirements are present on the reserve
+ * @param[out] allowed_maximum_age if @e age_ok is false, set to the maximum allowed age when withdrawing from this reserve (client needs to call age-withdraw)
+ * @param[out] ruuid set to the reserve's UUID (reserves table row)
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_batch_withdraw (
+ void *cls,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *amount,
+ bool age_check_required,
+ bool *found,
+ bool *balance_ok,
+ struct TALER_Amount *reserve_balance,
+ bool *age_ok,
+ uint16_t *allowed_maximum_age,
+ uint64_t *ruuid);
+
+#endif
diff --git a/src/exchangedb/pg_do_batch_withdraw_insert.c b/src/exchangedb/pg_do_batch_withdraw_insert.c
new file mode 100644
index 000000000..758f502f2
--- /dev/null
+++ b/src/exchangedb/pg_do_batch_withdraw_insert.c
@@ -0,0 +1,77 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw_insert.c
+ * @brief Implementation of the do_batch_withdraw_insert function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_batch_withdraw_insert.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_batch_withdraw_insert (
+ void *cls,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+ struct GNUNET_TIME_Timestamp now,
+ uint64_t ruuid,
+ bool *denom_unknown,
+ bool *conflict,
+ bool *nonce_reuse)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ NULL == nonce
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (nonce),
+ TALER_PQ_query_param_amount (pg->conn,
+ &collectable->amount_with_fee),
+ GNUNET_PQ_query_param_auto_from_type (&collectable->denom_pub_hash),
+ GNUNET_PQ_query_param_uint64 (&ruuid),
+ GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_sig),
+ GNUNET_PQ_query_param_auto_from_type (&collectable->h_coin_envelope),
+ TALER_PQ_query_param_blinded_denom_sig (&collectable->sig),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("denom_unknown",
+ denom_unknown),
+ GNUNET_PQ_result_spec_bool ("conflict",
+ conflict),
+ GNUNET_PQ_result_spec_bool ("nonce_reuse",
+ nonce_reuse),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "call_batch_withdraw_insert",
+ "SELECT "
+ " out_denom_unknown AS denom_unknown"
+ ",out_conflict AS conflict"
+ ",out_nonce_reuse AS nonce_reuse"
+ " FROM exchange_do_batch_withdraw_insert"
+ " ($1,$2,$3,$4,$5,$6,$7,$8);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_batch_withdraw_insert",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_batch_withdraw_insert.h b/src/exchangedb/pg_do_batch_withdraw_insert.h
new file mode 100644
index 000000000..18fcfc9ae
--- /dev/null
+++ b/src/exchangedb/pg_do_batch_withdraw_insert.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw_insert.h
+ * @brief implementation of the do_batch_withdraw_insert function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_BATCH_WITHDRAW_INSERT_H
+#define PG_DO_BATCH_WITHDRAW_INSERT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform insert as part of a batch withdraw operation, and persisting the
+ * withdrawal details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param nonce client-contributed input for CS denominations that must be checked for idempotency, or NULL for non-CS withdrawals
+ * @param collectable corresponding collectable coin (blind signature)
+ * @param now current time (rounded)
+ * @param ruuid reserve UUID
+ * @param[out] denom_unknown set if the denomination is unknown in the DB
+ * @param[out] conflict if the envelope was already in the DB
+ * @param[out] nonce_reuse if @a nonce was non-NULL and reused
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_batch_withdraw_insert (
+ void *cls,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+ struct GNUNET_TIME_Timestamp now,
+ uint64_t ruuid,
+ bool *denom_unknown,
+ bool *conflict,
+ bool *nonce_reuse);
+
+#endif
diff --git a/src/exchangedb/pg_do_deposit.c b/src/exchangedb/pg_do_deposit.c
new file mode 100644
index 000000000..0ba45b628
--- /dev/null
+++ b/src/exchangedb/pg_do_deposit.c
@@ -0,0 +1,119 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_do_deposit.c
+ * @brief Implementation of the do_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_deposit.h"
+#include "pg_helper.h"
+#include "pg_compute_shard.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_deposit (
+ void *cls,
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd,
+ struct GNUNET_TIME_Timestamp *exchange_timestamp,
+ bool *balance_ok,
+ uint32_t *bad_balance_index,
+ bool *ctr_conflict)
+{
+ struct PostgresClosure *pg = cls;
+ uint64_t deposit_shard = TEH_PG_compute_shard (&bd->merchant_pub);
+ const struct TALER_CoinSpendPublicKeyP *coin_pubs[GNUNET_NZL (bd->num_cdis)];
+ const struct TALER_CoinSpendSignatureP *coin_sigs[GNUNET_NZL (bd->num_cdis)];
+ struct TALER_Amount amounts_with_fee[GNUNET_NZL (bd->num_cdis)];
+ struct GNUNET_PQ_QueryParam params[] = {
+ /* data for batch_deposits */
+ GNUNET_PQ_query_param_uint64 (&deposit_shard),
+ GNUNET_PQ_query_param_auto_from_type (&bd->merchant_pub),
+ GNUNET_PQ_query_param_timestamp (&bd->wallet_timestamp),
+ GNUNET_PQ_query_param_timestamp (exchange_timestamp),
+ GNUNET_PQ_query_param_timestamp (&bd->refund_deadline),
+ GNUNET_PQ_query_param_timestamp (&bd->wire_deadline),
+ GNUNET_PQ_query_param_auto_from_type (&bd->h_contract_terms),
+ (bd->no_wallet_data_hash)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (&bd->wallet_data_hash),
+ GNUNET_PQ_query_param_auto_from_type (&bd->wire_salt),
+ GNUNET_PQ_query_param_auto_from_type (&bd->wire_target_h_payto),
+ (0 == bd->policy_details_serial_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (&bd->policy_details_serial_id),
+ GNUNET_PQ_query_param_bool (bd->policy_blocked),
+ /* to create entry in wire_targets */
+ GNUNET_PQ_query_param_string (bd->receiver_wire_account),
+ /* arrays for coin_deposits */
+ GNUNET_PQ_query_param_array_ptrs_auto_from_type (bd->num_cdis,
+ coin_pubs,
+ pg->conn),
+ GNUNET_PQ_query_param_array_ptrs_auto_from_type (bd->num_cdis,
+ coin_sigs,
+ pg->conn),
+ TALER_PQ_query_param_array_amount (bd->num_cdis,
+ amounts_with_fee,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ bool no_time;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("exchange_timestamp",
+ exchange_timestamp),
+ &no_time),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint32 ("insufficient_balance_coin_index",
+ bad_balance_index),
+ balance_ok),
+ GNUNET_PQ_result_spec_bool ("conflicted",
+ ctr_conflict),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ for (unsigned int i = 0; i < bd->num_cdis; i++)
+ {
+ const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+ = &bd->cdis[i];
+
+ amounts_with_fee[i] = cdi->amount_with_fee;
+ coin_pubs[i] = &cdi->coin.coin_pub;
+ coin_sigs[i] = &cdi->csig;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Do deposit %u = %s\n",
+ i,
+ TALER_B2S (&cdi->coin.coin_pub));
+ }
+ PREPARE (pg,
+ "call_deposit",
+ "SELECT "
+ " out_exchange_timestamp AS exchange_timestamp"
+ ",out_insufficient_balance_coin_index AS insufficient_balance_coin_index"
+ ",out_conflict AS conflicted"
+ " FROM exchange_do_deposit"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_deposit",
+ params,
+ rs);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
+}
diff --git a/src/exchangedb/pg_do_deposit.h b/src/exchangedb/pg_do_deposit.h
new file mode 100644
index 000000000..449ec04be
--- /dev/null
+++ b/src/exchangedb/pg_do_deposit.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_deposit.h
+ * @brief implementation of the do_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_DEPOSIT_H
+#define PG_DO_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Perform deposit operation, checking for sufficient balance
+ * of the coins and possibly persisting the deposit details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param bd batch deposit operation details
+ * @param[in,out] exchange_timestamp time to use for the deposit (possibly updated)
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] bad_balance_index set to the first index of a coin for which the balance was insufficient,
+ * only used if @a balance_ok is set to false.
+ * @param[out] in_conflict set to true if the deposit conflicted
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_deposit (
+ void *cls,
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd,
+ struct GNUNET_TIME_Timestamp *exchange_timestamp,
+ bool *balance_ok,
+ uint32_t *bad_balance_index,
+ bool *in_conflict);
+
+#endif
diff --git a/src/exchangedb/pg_do_melt.c b/src/exchangedb/pg_do_melt.c
new file mode 100644
index 000000000..0b26386d8
--- /dev/null
+++ b/src/exchangedb/pg_do_melt.c
@@ -0,0 +1,82 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_melt.c
+ * @brief Implementation of the do_melt function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_melt.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_melt (
+ void *cls,
+ const struct TALER_RefreshMasterSecretP *rms,
+ struct TALER_EXCHANGEDB_Refresh *refresh,
+ uint64_t known_coin_id,
+ bool *zombie_required,
+ bool *balance_ok)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ NULL == rms
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (rms),
+ TALER_PQ_query_param_amount (pg->conn,
+ &refresh->amount_with_fee),
+ GNUNET_PQ_query_param_auto_from_type (&refresh->rc),
+ GNUNET_PQ_query_param_auto_from_type (&refresh->coin.coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&refresh->coin_sig),
+ GNUNET_PQ_query_param_uint64 (&known_coin_id),
+ GNUNET_PQ_query_param_uint32 (&refresh->noreveal_index),
+ GNUNET_PQ_query_param_bool (*zombie_required),
+ GNUNET_PQ_query_param_end
+ };
+ bool is_null;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("balance_ok",
+ balance_ok),
+ GNUNET_PQ_result_spec_bool ("zombie_required",
+ zombie_required),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint32 ("noreveal_index",
+ &refresh->noreveal_index),
+ &is_null),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "call_melt",
+ "SELECT "
+ " out_balance_ok AS balance_ok"
+ ",out_zombie_bad AS zombie_required"
+ ",out_noreveal_index AS noreveal_index"
+ " FROM exchange_do_melt"
+ " ($1,$2,$3,$4,$5,$6,$7,$8);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_melt",
+ params,
+ rs);
+ if (is_null)
+ refresh->noreveal_index = UINT32_MAX; /* set to very invalid value */
+ return qs;
+}
diff --git a/src/exchangedb/pg_do_melt.h b/src/exchangedb/pg_do_melt.h
new file mode 100644
index 000000000..554ce08b2
--- /dev/null
+++ b/src/exchangedb/pg_do_melt.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_melt.h
+ * @brief implementation of the do_melt function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_MELT_H
+#define PG_DO_MELT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform melt operation, checking for sufficient balance
+ * of the coin and possibly persisting the melt details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param rms client-contributed input for CS denominations that must be checked for idempotency, or NULL for non-CS withdrawals
+ * @param[in,out] refresh refresh operation details; the noreveal_index
+ * is set in case the coin was already melted before
+ * @param known_coin_id row of the coin in the known_coins table
+ * @param[in,out] zombie_required true if the melt must only succeed if the coin is a zombie, set to false if the requirement was satisfied
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_melt (
+ void *cls,
+ const struct TALER_RefreshMasterSecretP *rms,
+ struct TALER_EXCHANGEDB_Refresh *refresh,
+ uint64_t known_coin_id,
+ bool *zombie_required,
+ bool *balance_ok);
+
+#endif
diff --git a/src/exchangedb/pg_do_purse_delete.c b/src/exchangedb/pg_do_purse_delete.c
new file mode 100644
index 000000000..27b81cab9
--- /dev/null
+++ b/src/exchangedb/pg_do_purse_delete.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_purse_delete.c
+ * @brief Implementation of the do_purse_delete function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_purse_delete.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_purse_delete (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig,
+ bool *decided,
+ bool *found)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (purse_sig),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("decided",
+ decided),
+ GNUNET_PQ_result_spec_bool ("found",
+ found),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "call_purse_delete",
+ "SELECT "
+ " out_decided AS decided"
+ ",out_found AS found"
+ " FROM exchange_do_purse_delete"
+ " ($1,$2,$3);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_purse_delete",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_purse_delete.h b/src/exchangedb/pg_do_purse_delete.h
new file mode 100644
index 000000000..01c7dd91b
--- /dev/null
+++ b/src/exchangedb/pg_do_purse_delete.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_purse_delete.h
+ * @brief implementation of the do_purse_delete function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_PURSE_DELETE_H
+#define PG_DO_PURSE_DELETE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to explicitly delete a purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to delete
+ * @param purse_sig signature affirming the deletion
+ * @param[out] decided set to true if the purse was
+ * already decided and thus could not be deleted
+ * @param[out] found set to true if the purse was found
+ * (if false, purse could not be deleted)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_purse_delete (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig,
+ bool *decided,
+ bool *found);
+
+#endif
diff --git a/src/exchangedb/pg_do_purse_deposit.c b/src/exchangedb/pg_do_purse_deposit.c
new file mode 100644
index 000000000..bdb1f4749
--- /dev/null
+++ b/src/exchangedb/pg_do_purse_deposit.c
@@ -0,0 +1,90 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_purse_deposit.c
+ * @brief Implementation of the do_purse_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_purse_deposit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_purse_deposit (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *amount,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ const struct TALER_Amount *amount_minus_fee,
+ bool *balance_ok,
+ bool *too_late,
+ bool *conflict)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+ struct GNUNET_TIME_Timestamp reserve_expiration;
+ uint64_t partner_id = 0; /* FIXME #7271: WAD support... */
+ struct GNUNET_PQ_QueryParam params[] = {
+ (0 == partner_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (&partner_id),
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ amount),
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (coin_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ amount_minus_fee),
+ GNUNET_PQ_query_param_timestamp (&reserve_expiration),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("balance_ok",
+ balance_ok),
+ GNUNET_PQ_result_spec_bool ("too_late",
+ too_late),
+ GNUNET_PQ_result_spec_bool ("conflict",
+ conflict),
+ GNUNET_PQ_result_spec_end
+ };
+
+ reserve_expiration
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (),
+ pg->legal_reserve_expiration_time));
+
+ PREPARE (pg,
+ "call_purse_deposit",
+ "SELECT "
+ " out_balance_ok AS balance_ok"
+ ",out_conflict AS conflict"
+ ",out_late AS too_late"
+ " FROM exchange_do_purse_deposit"
+ " ($1,$2,$3,$4,$5,$6,$7,$8);");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_purse_deposit",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_purse_deposit.h b/src/exchangedb/pg_do_purse_deposit.h
new file mode 100644
index 000000000..779b6c0c8
--- /dev/null
+++ b/src/exchangedb/pg_do_purse_deposit.h
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_purse_deposit.h
+ * @brief implementation of the do_purse_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_PURSE_DEPOSIT_H
+#define PG_DO_PURSE_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to execute a transaction crediting
+ * a purse with @a amount from @a coin_pub. Reduces the
+ * value of @a coin_pub and increase the balance of
+ * the @a purse_pub purse. If the balance reaches the
+ * target amount and the purse has been merged, triggers
+ * the updates of the reserve/account balance.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to credit
+ * @param coin_pub coin to deposit (debit)
+ * @param amount fraction of the coin's value to deposit
+ * @param coin_sig signature affirming the operation
+ * @param amount_minus_fee amount to add to the purse
+ * @param[out] balance_ok set to false if the coin's
+ * remaining balance is below @a amount;
+ * in this case, the return value will be
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT despite the failure
+ * @param[out] too_late set to true if it is too late to deposit into the purse
+ * @param[out] conflict set to true if the deposit failed due to a conflict (coin already spent,
+ * or deposited into this purse with a different amount)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_purse_deposit (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *amount,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ const struct TALER_Amount *amount_minus_fee,
+ bool *balance_ok,
+ bool *too_late,
+ bool *conflict);
+
+#endif
diff --git a/src/exchangedb/pg_do_purse_merge.c b/src/exchangedb/pg_do_purse_merge.c
new file mode 100644
index 000000000..5a174ed02
--- /dev/null
+++ b/src/exchangedb/pg_do_purse_merge.c
@@ -0,0 +1,91 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_purse_merge.c
+ * @brief Implementation of the do_purse_merge function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_purse_merge.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_purse_merge (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig,
+ const struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const char *partner_url,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *no_partner,
+ bool *no_balance,
+ bool *in_conflict)
+{
+ struct PostgresClosure *pg = cls;
+ struct TALER_PaytoHashP h_payto;
+ struct GNUNET_TIME_Timestamp expiration
+ = GNUNET_TIME_relative_to_timestamp (pg->legal_reserve_expiration_time);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (merge_sig),
+ GNUNET_PQ_query_param_timestamp (&merge_timestamp),
+ GNUNET_PQ_query_param_auto_from_type (reserve_sig),
+ (NULL == partner_url)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (partner_url),
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (&h_payto),
+ GNUNET_PQ_query_param_timestamp (&expiration),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("no_partner",
+ no_partner),
+ GNUNET_PQ_result_spec_bool ("no_balance",
+ no_balance),
+ GNUNET_PQ_result_spec_bool ("conflict",
+ in_conflict),
+ GNUNET_PQ_result_spec_end
+ };
+
+ {
+ char *payto_uri;
+
+ payto_uri = TALER_reserve_make_payto (pg->exchange_url,
+ reserve_pub);
+ TALER_payto_hash (payto_uri,
+ &h_payto);
+ GNUNET_free (payto_uri);
+ }
+ PREPARE (pg,
+ "call_purse_merge",
+ "SELECT"
+ " out_no_partner AS no_partner"
+ ",out_no_balance AS no_balance"
+ ",out_conflict AS conflict"
+ " FROM exchange_do_purse_merge"
+ " ($1, $2, $3, $4, $5, $6, $7, $8);");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_purse_merge",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_purse_merge.h b/src/exchangedb/pg_do_purse_merge.h
new file mode 100644
index 000000000..a51de47bf
--- /dev/null
+++ b/src/exchangedb/pg_do_purse_merge.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_purse_merge.h
+ * @brief implementation of the do_purse_merge function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_PURSE_MERGE_H
+#define PG_DO_PURSE_MERGE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to approve merging a purse into a
+ * reserve by the respective purse merge key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to merge
+ * @param merge_sig signature affirming the merge
+ * @param merge_timestamp time of the merge
+ * @param reserve_sig signature of the reserve affirming the merge
+ * @param partner_url URL of the partner exchange, can be NULL if the reserves lives with us
+ * @param reserve_pub public key of the reserve to credit
+ * @param[out] no_partner set to true if @a partner_url is unknown
+ * @param[out] no_balance set to true if the @a purse_pub is not paid up yet
+ * @param[out] in_conflict set to true if @a purse_pub was merged into a different reserve already
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_purse_merge (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig,
+ const struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const char *partner_url,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *no_partner,
+ bool *no_balance,
+ bool *in_conflict);
+
+#endif
diff --git a/src/exchangedb/pg_do_recoup.c b/src/exchangedb/pg_do_recoup.c
new file mode 100644
index 000000000..07566a607
--- /dev/null
+++ b/src/exchangedb/pg_do_recoup.c
@@ -0,0 +1,85 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_recoup.c
+ * @brief Implementation of the do_recoup function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_recoup.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_recoup (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t reserve_out_serial_id,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t known_coin_id,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ struct GNUNET_TIME_Timestamp *recoup_timestamp,
+ bool *recoup_ok,
+ bool *internal_failure)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp reserve_gc
+ = GNUNET_TIME_relative_to_timestamp (pg->legal_reserve_expiration_time);
+ struct GNUNET_TIME_Timestamp reserve_expiration
+ = GNUNET_TIME_relative_to_timestamp (pg->idle_reserve_expiration_time);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_uint64 (&reserve_out_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (coin_bks),
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_uint64 (&known_coin_id),
+ GNUNET_PQ_query_param_auto_from_type (coin_sig),
+ GNUNET_PQ_query_param_timestamp (&reserve_gc),
+ GNUNET_PQ_query_param_timestamp (&reserve_expiration),
+ GNUNET_PQ_query_param_timestamp (recoup_timestamp),
+ GNUNET_PQ_query_param_end
+ };
+ bool is_null;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ recoup_timestamp),
+ &is_null),
+ GNUNET_PQ_result_spec_bool ("recoup_ok",
+ recoup_ok),
+ GNUNET_PQ_result_spec_bool ("internal_failure",
+ internal_failure),
+ GNUNET_PQ_result_spec_end
+ };
+
+
+ PREPARE (pg,
+ "call_recoup",
+ "SELECT "
+ " out_recoup_timestamp AS recoup_timestamp"
+ ",out_recoup_ok AS recoup_ok"
+ ",out_internal_failure AS internal_failure"
+ " FROM exchange_do_recoup_to_reserve"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_recoup",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_recoup.h b/src/exchangedb/pg_do_recoup.h
new file mode 100644
index 000000000..2cf3eb976
--- /dev/null
+++ b/src/exchangedb/pg_do_recoup.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_recoup.h
+ * @brief implementation of the do_recoup function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_RECOUP_H
+#define PG_DO_RECOUP_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform recoup operation, checking for sufficient deposits
+ * of the coin and possibly persisting the recoup details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_pub public key of the reserve to credit
+ * @param reserve_out_serial_id row in the reserves_out table justifying the recoup
+ * @param coin_bks coin blinding key secret to persist
+ * @param coin_pub public key of the coin being recouped
+ * @param known_coin_id row of the @a coin_pub in the known_coins table
+ * @param coin_sig signature of the coin requesting the recoup
+ * @param[in,out] recoup_timestamp recoup timestamp, set if recoup existed
+ * @param[out] recoup_ok set if the recoup succeeded (balance ok)
+ * @param[out] internal_failure set on internal failures
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_recoup (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t reserve_out_serial_id,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t known_coin_id,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ struct GNUNET_TIME_Timestamp *recoup_timestamp,
+ bool *recoup_ok,
+ bool *internal_failure);
+
+#endif
diff --git a/src/exchangedb/pg_do_recoup_refresh.c b/src/exchangedb/pg_do_recoup_refresh.c
new file mode 100644
index 000000000..7d099bcd5
--- /dev/null
+++ b/src/exchangedb/pg_do_recoup_refresh.c
@@ -0,0 +1,79 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_recoup_refresh.c
+ * @brief Implementation of the do_recoup_refresh function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_recoup_refresh.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_recoup_refresh (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ uint64_t rrc_serial,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t known_coin_id,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ struct GNUNET_TIME_Timestamp *recoup_timestamp,
+ bool *recoup_ok,
+ bool *internal_failure)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (old_coin_pub),
+ GNUNET_PQ_query_param_uint64 (&rrc_serial),
+ GNUNET_PQ_query_param_auto_from_type (coin_bks),
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_uint64 (&known_coin_id),
+ GNUNET_PQ_query_param_auto_from_type (coin_sig),
+ GNUNET_PQ_query_param_timestamp (recoup_timestamp),
+ GNUNET_PQ_query_param_end
+ };
+ bool is_null;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ recoup_timestamp),
+ &is_null),
+ GNUNET_PQ_result_spec_bool ("recoup_ok",
+ recoup_ok),
+ GNUNET_PQ_result_spec_bool ("internal_failure",
+ internal_failure),
+ GNUNET_PQ_result_spec_end
+ };
+
+
+ PREPARE (pg,
+ "call_recoup_refresh",
+ "SELECT "
+ " out_recoup_timestamp AS recoup_timestamp"
+ ",out_recoup_ok AS recoup_ok"
+ ",out_internal_failure AS internal_failure"
+ " FROM exchange_do_recoup_to_coin"
+ " ($1,$2,$3,$4,$5,$6,$7);");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_recoup_refresh",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_recoup_refresh.h b/src/exchangedb/pg_do_recoup_refresh.h
new file mode 100644
index 000000000..16b0fd208
--- /dev/null
+++ b/src/exchangedb/pg_do_recoup_refresh.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_recoup_refresh.h
+ * @brief implementation of the do_recoup_refresh function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_RECOUP_REFRESH_H
+#define PG_DO_RECOUP_REFRESH_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Perform recoup-refresh operation, checking for sufficient deposits of the
+ * coin and possibly persisting the recoup-refresh details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param old_coin_pub public key of the old coin to credit
+ * @param rrc_serial row in the refresh_revealed_coins table justifying the recoup-refresh
+ * @param coin_bks coin blinding key secret to persist
+ * @param coin_pub public key of the coin being recouped
+ * @param known_coin_id row of the @a coin_pub in the known_coins table
+ * @param coin_sig signature of the coin requesting the recoup
+ * @param[in,out] recoup_timestamp recoup timestamp, set if recoup existed
+ * @param[out] recoup_ok set if the recoup-refresh succeeded (balance ok)
+ * @param[out] internal_failure set on internal failures
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_recoup_refresh (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ uint64_t rrc_serial,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t known_coin_id,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ struct GNUNET_TIME_Timestamp *recoup_timestamp,
+ bool *recoup_ok,
+ bool *internal_failure);
+
+#endif
diff --git a/src/exchangedb/pg_do_refund.c b/src/exchangedb/pg_do_refund.c
new file mode 100644
index 000000000..194dab18d
--- /dev/null
+++ b/src/exchangedb/pg_do_refund.c
@@ -0,0 +1,93 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_refund.c
+ * @brief Implementation of the do_refund function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_refund.h"
+#include "pg_helper.h"
+#include "pg_compute_shard.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_refund (
+ void *cls,
+ const struct TALER_EXCHANGEDB_Refund *refund,
+ const struct TALER_Amount *deposit_fee,
+ uint64_t known_coin_id,
+ bool *not_found,
+ bool *refund_ok,
+ bool *gone,
+ bool *conflict)
+{
+ struct PostgresClosure *pg = cls;
+ uint64_t deposit_shard = TEH_PG_compute_shard (&refund->details.merchant_pub);
+ struct TALER_Amount amount_without_fee;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ &refund->details.refund_amount),
+ TALER_PQ_query_param_amount (pg->conn,
+ &amount_without_fee),
+ TALER_PQ_query_param_amount (pg->conn,
+ deposit_fee),
+ GNUNET_PQ_query_param_auto_from_type (&refund->details.h_contract_terms),
+ GNUNET_PQ_query_param_uint64 (&refund->details.rtransaction_id),
+ GNUNET_PQ_query_param_uint64 (&deposit_shard),
+ GNUNET_PQ_query_param_uint64 (&known_coin_id),
+ GNUNET_PQ_query_param_auto_from_type (&refund->coin.coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("not_found",
+ not_found),
+ GNUNET_PQ_result_spec_bool ("refund_ok",
+ refund_ok),
+ GNUNET_PQ_result_spec_bool ("gone",
+ gone),
+ GNUNET_PQ_result_spec_bool ("conflict",
+ conflict),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (0 >
+ TALER_amount_subtract (&amount_without_fee,
+ &refund->details.refund_amount,
+ &refund->details.refund_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ PREPARE (pg,
+ "call_refund",
+ "SELECT "
+ " out_not_found AS not_found"
+ ",out_refund_ok AS refund_ok"
+ ",out_gone AS gone"
+ ",out_conflict AS conflict"
+ " FROM exchange_do_refund"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_refund",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_refund.h b/src/exchangedb/pg_do_refund.h
new file mode 100644
index 000000000..7118858dc
--- /dev/null
+++ b/src/exchangedb/pg_do_refund.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_refund.h
+ * @brief implementation of the do_refund function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_REFUND_H
+#define PG_DO_REFUND_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform refund operation, checking for sufficient deposits
+ * of the coin and possibly persisting the refund details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param refund refund operation details
+ * @param deposit_fee deposit fee applicable for the coin, possibly refunded
+ * @param known_coin_id row of the coin in the known_coins table
+ * @param[out] not_found set if the deposit was not found
+ * @param[out] refund_ok set if the refund succeeded (below deposit amount)
+ * @param[out] gone if the merchant was already paid
+ * @param[out] conflict set if the refund ID was re-used
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_refund (
+ void *cls,
+ const struct TALER_EXCHANGEDB_Refund *refund,
+ const struct TALER_Amount *deposit_fee,
+ uint64_t known_coin_id,
+ bool *not_found,
+ bool *refund_ok,
+ bool *gone,
+ bool *conflict);
+
+#endif
diff --git a/src/exchangedb/pg_do_reserve_open.c b/src/exchangedb/pg_do_reserve_open.c
new file mode 100644
index 000000000..b15c96dd1
--- /dev/null
+++ b/src/exchangedb/pg_do_reserve_open.c
@@ -0,0 +1,101 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_do_reserve_open.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_reserve_open.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_reserve_open (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *total_paid,
+ const struct TALER_Amount *reserve_payment,
+ uint32_t min_purse_limit,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ struct GNUNET_TIME_Timestamp desired_expiration,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_Amount *open_fee,
+ bool *no_funds,
+ struct TALER_Amount *reserve_balance,
+ struct TALER_Amount *open_cost,
+ struct GNUNET_TIME_Timestamp *final_expiration)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ total_paid),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ reserve_payment),
+ GNUNET_PQ_query_param_uint32 (&min_purse_limit),
+ GNUNET_PQ_query_param_uint32 (&pg->def_purse_limit),
+ GNUNET_PQ_query_param_auto_from_type (reserve_sig),
+ GNUNET_PQ_query_param_timestamp (&desired_expiration),
+ GNUNET_PQ_query_param_relative_time (&pg->legal_reserve_expiration_time),
+ GNUNET_PQ_query_param_timestamp (&now),
+ TALER_PQ_query_param_amount (pg->conn,
+ open_fee),
+ GNUNET_PQ_query_param_end
+ };
+ bool no_reserve = true;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount ("out_open_cost",
+ pg->currency,
+ open_cost),
+ TALER_PQ_result_spec_amount ("out_reserve_balance",
+ pg->currency,
+ reserve_balance),
+ GNUNET_PQ_result_spec_timestamp ("out_final_expiration",
+ final_expiration),
+ GNUNET_PQ_result_spec_bool ("out_no_reserve",
+ &no_reserve),
+ GNUNET_PQ_result_spec_bool ("out_no_funds",
+ no_funds),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "do_reserve_open",
+ "SELECT "
+ " out_open_cost"
+ ",out_final_expiration"
+ ",out_no_funds"
+ ",out_no_reserve"
+ ",out_reserve_balance"
+ " FROM exchange_do_reserve_open"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "do_reserve_open",
+ params,
+ rs);
+ if (qs <= 0)
+ return qs;
+ if (no_reserve)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ return qs;
+}
diff --git a/src/exchangedb/pg_do_reserve_open.h b/src/exchangedb/pg_do_reserve_open.h
new file mode 100644
index 000000000..432f3f664
--- /dev/null
+++ b/src/exchangedb/pg_do_reserve_open.h
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_do_reserve_open.h
+ * @brief implementation of the do_reserve_open function
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_RESERVE_OPEN_H
+#define PG_DO_RESERVE_OPEN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Perform reserve open operation on database.
+ *
+ * @param cls closure
+ * @param reserve_pub which reserve is this about?
+ * @param total_paid total amount paid (coins and reserve)
+ * @param reserve_payment amount to be paid from the reserve
+ * @param min_purse_limit minimum number of purses we should be able to open
+ * @param reserve_sig signature by the reserve for the operation
+ * @param desired_expiration when should the reserve expire (earliest time)
+ * @param now when did we the client initiate the action
+ * @param open_fee annual fee to be charged for the open operation by the exchange
+ * @param[out] no_funds set to true if reserve balance is insufficient
+ * @param[out] reserve_balance set to the original reserve balance (at the start of this transaction)
+ * @param[out] open_cost set to the actual cost
+ * @param[out] final_expiration when will the reserve expire now
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_reserve_open (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *total_paid,
+ const struct TALER_Amount *reserve_payment,
+ uint32_t min_purse_limit,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ struct GNUNET_TIME_Timestamp desired_expiration,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_Amount *open_fee,
+ bool *no_funds,
+ struct TALER_Amount *reserve_balance,
+ struct TALER_Amount *open_cost,
+ struct GNUNET_TIME_Timestamp *final_expiration);
+
+
+#endif
diff --git a/src/exchangedb/pg_do_reserve_purse.c b/src/exchangedb/pg_do_reserve_purse.c
new file mode 100644
index 000000000..e03e23fec
--- /dev/null
+++ b/src/exchangedb/pg_do_reserve_purse.c
@@ -0,0 +1,121 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_reserve_purse.c
+ * @brief Implementation of the do_reserve_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_reserve_purse.h"
+#include "pg_helper.h"
+/**
+ * Function called insert request to merge a purse into a reserve by the
+ * respective purse merge key. The purse must not have been merged into a
+ * different reserve.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to merge
+ * @param merge_sig signature affirming the merge
+ * @param merge_timestamp time of the merge
+ * @param reserve_sig signature of the reserve affirming the merge
+ * @param purse_fee amount to charge the reserve for the purse creation, NULL to use the quota
+ * @param reserve_pub public key of the reserve to credit
+ * @param[out] in_conflict set to true if @a purse_pub was merged into a different reserve already
+ * @param[out] no_reserve set to true if @a reserve_pub is not a known reserve
+ * @param[out] insufficient_funds set to true if @a reserve_pub has insufficient capacity to create another purse
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_reserve_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig,
+ const struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_Amount *purse_fee,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *in_conflict,
+ bool *no_reserve,
+ bool *insufficient_funds)
+{
+ struct PostgresClosure *pg = cls;
+ struct TALER_Amount zero_fee;
+ struct TALER_PaytoHashP h_payto;
+ struct GNUNET_TIME_Timestamp reserve_expiration
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (),
+ pg->idle_reserve_expiration_time));
+ struct GNUNET_TIME_Timestamp reserve_gc
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (),
+ pg->legal_reserve_expiration_time));
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (merge_sig),
+ GNUNET_PQ_query_param_timestamp (&merge_timestamp),
+ GNUNET_PQ_query_param_timestamp (&reserve_expiration),
+ GNUNET_PQ_query_param_timestamp (&reserve_gc),
+ GNUNET_PQ_query_param_auto_from_type (reserve_sig),
+ GNUNET_PQ_query_param_bool (NULL == purse_fee),
+ TALER_PQ_query_param_amount (pg->conn,
+ NULL == purse_fee
+ ? &zero_fee
+ : purse_fee),
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (&h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("insufficient_funds",
+ insufficient_funds),
+ GNUNET_PQ_result_spec_bool ("conflict",
+ in_conflict),
+ GNUNET_PQ_result_spec_bool ("no_reserve",
+ no_reserve),
+ GNUNET_PQ_result_spec_end
+ };
+
+ {
+ char *payto_uri;
+
+ payto_uri = TALER_reserve_make_payto (pg->exchange_url,
+ reserve_pub);
+ TALER_payto_hash (payto_uri,
+ &h_payto);
+ GNUNET_free (payto_uri);
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ &zero_fee));
+ /* Used in #postgres_do_reserve_purse() */
+ PREPARE (pg,
+ "call_reserve_purse",
+ "SELECT"
+ " out_no_funds AS insufficient_funds"
+ ",out_no_reserve AS no_reserve"
+ ",out_conflict AS conflict"
+ " FROM exchange_do_reserve_purse"
+ " ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_reserve_purse",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_reserve_purse.h b/src/exchangedb/pg_do_reserve_purse.h
new file mode 100644
index 000000000..dde2d6ce1
--- /dev/null
+++ b/src/exchangedb/pg_do_reserve_purse.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_do_reserve_purse.h
+ * @brief implementation of the do_reserve_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_RESERVE_PURSE_H
+#define PG_DO_RESERVE_PURSE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called insert request to merge a purse into a reserve by the
+ * respective purse merge key. The purse must not have been merged into a
+ * different reserve.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to merge
+ * @param merge_sig signature affirming the merge
+ * @param merge_timestamp time of the merge
+ * @param reserve_sig signature of the reserve affirming the merge
+ * @param purse_fee amount to charge the reserve for the purse creation, NULL to use the quota
+ * @param reserve_pub public key of the reserve to credit
+ * @param[out] in_conflict set to true if @a purse_pub was merged into a different reserve already
+ * @param[out] no_reserve set to true if @a reserve_pub is not a known reserve
+ * @param[out] insufficient_funds set to true if @a reserve_pub has insufficient capacity to create another purse
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_reserve_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig,
+ const struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_Amount *purse_fee,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *in_conflict,
+ bool *no_reserve,
+ bool *insufficient_funds);
+
+#endif
diff --git a/src/exchangedb/pg_drain_kyc_alert.c b/src/exchangedb/pg_drain_kyc_alert.c
new file mode 100644
index 000000000..4388334e9
--- /dev/null
+++ b/src/exchangedb/pg_drain_kyc_alert.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_drain_kyc_alert.c
+ * @brief Implementation of the drain_kyc_alert function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_drain_kyc_alert.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_drain_kyc_alert (void *cls,
+ uint32_t trigger_type,
+ struct TALER_PaytoHashP *h_payto)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint32 (&trigger_type),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_payto",
+ h_payto),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "drain_kyc_alert",
+ "DELETE FROM kyc_alerts"
+ " WHERE trigger_type=$1"
+ " AND h_payto = "
+ " (SELECT h_payto "
+ " FROM kyc_alerts"
+ " WHERE trigger_type=$1"
+ " LIMIT 1)"
+ " RETURNING h_payto;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "drain_kyc_alert",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_drain_kyc_alert.h b/src/exchangedb/pg_drain_kyc_alert.h
new file mode 100644
index 000000000..7425f472d
--- /dev/null
+++ b/src/exchangedb/pg_drain_kyc_alert.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_drain_kyc_alert.h
+ * @brief implementation of the drain_kyc_alert function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DRAIN_KYC_ALERT_H
+#define PG_DRAIN_KYC_ALERT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Extract next KYC alert. Deletes the alert.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param trigger_type which type of alert to drain
+ * @param[out] h_payto set to hash of payto-URI where KYC status changed
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_drain_kyc_alert (void *cls,
+ uint32_t trigger_type,
+ struct TALER_PaytoHashP *h_payto);
+
+#endif
diff --git a/src/exchangedb/pg_drop_tables.c b/src/exchangedb/pg_drop_tables.c
new file mode 100644
index 000000000..55857018b
--- /dev/null
+++ b/src/exchangedb/pg_drop_tables.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_drop_tables.c
+ * @brief Implementation of the drop_tables function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_drop_tables.h"
+#include "pg_helper.h"
+
+
+/**
+ * Drop all Taler tables. This should only be used by testcases.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_drop_tables (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_Context *conn;
+ enum GNUNET_GenericReturnValue ret;
+
+ if (NULL != pg->conn)
+ {
+ GNUNET_PQ_disconnect (pg->conn);
+ pg->conn = NULL;
+ }
+ conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+ "exchangedb-postgres",
+ NULL,
+ NULL,
+ NULL);
+ if (NULL == conn)
+ return GNUNET_SYSERR;
+ ret = GNUNET_PQ_exec_sql (conn,
+ "drop");
+ GNUNET_PQ_disconnect (conn);
+ return ret;
+}
diff --git a/src/exchangedb/pg_drop_tables.h b/src/exchangedb/pg_drop_tables.h
new file mode 100644
index 000000000..58729d5ec
--- /dev/null
+++ b/src/exchangedb/pg_drop_tables.h
@@ -0,0 +1,38 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_drop_tables.h
+ * @brief implementation of the drop_tables function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DROP_TABLES_H
+#define PG_DROP_TABLES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Drop all Taler tables. This should only be used by testcases.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_drop_tables (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_ensure_coin_known.c b/src/exchangedb/pg_ensure_coin_known.c
new file mode 100644
index 000000000..307b8df52
--- /dev/null
+++ b/src/exchangedb/pg_ensure_coin_known.c
@@ -0,0 +1,169 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_ensure_coin_known.c
+ * @brief Implementation of the ensure_coin_known function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_pq_lib.h"
+#include "pg_ensure_coin_known.h"
+#include "pg_helper.h"
+
+
+enum TALER_EXCHANGEDB_CoinKnownStatus
+TEH_PG_ensure_coin_known (void *cls,
+ const struct TALER_CoinPublicInfo *coin,
+ uint64_t *known_coin_id,
+ struct TALER_DenominationHashP *denom_hash,
+ struct TALER_AgeCommitmentHash *h_age_commitment)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ bool existed;
+ bool is_denom_pub_hash_null = false;
+ bool is_age_hash_null = false;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin->denom_pub_hash),
+ coin->no_age_commitment
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (&coin->h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin->denom_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("existed",
+ &existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+ known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ denom_hash),
+ &is_denom_pub_hash_null),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ h_age_commitment),
+ &is_age_hash_null),
+ GNUNET_PQ_result_spec_end
+ };
+
+ /*
+ See also:
+ https://stackoverflow.com/questions/34708509/how-to-use-returning-with-on-conflict-in-postgresql/37543015#37543015
+ */
+ PREPARE (pg,
+ "insert_known_coin",
+ "WITH dd"
+ " (denominations_serial"
+ " ,coin"
+ " ) AS ("
+ " SELECT "
+ " denominations_serial"
+ " ,coin"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$2"
+ " ), input_rows"
+ " (coin_pub) AS ("
+ " VALUES ($1::BYTEA)"
+ " ), ins AS ("
+ " INSERT INTO known_coins "
+ " (coin_pub"
+ " ,denominations_serial"
+ " ,age_commitment_hash"
+ " ,denom_sig"
+ " ,remaining"
+ " ) SELECT "
+ " $1"
+ " ,denominations_serial"
+ " ,$3"
+ " ,$4"
+ " ,coin"
+ " FROM dd"
+ " ON CONFLICT DO NOTHING" /* CONFLICT on (coin_pub) */
+ " RETURNING "
+ " known_coin_id"
+ " ) "
+ "SELECT "
+ " FALSE AS existed"
+ " ,known_coin_id"
+ " ,NULL AS denom_pub_hash"
+ " ,NULL AS age_commitment_hash"
+ " FROM ins "
+ "UNION ALL "
+ "SELECT "
+ " TRUE AS existed"
+ " ,known_coin_id"
+ " ,denom_pub_hash"
+ " ,kc.age_commitment_hash"
+ " FROM input_rows"
+ " JOIN known_coins kc USING (coin_pub)"
+ " JOIN denominations USING (denominations_serial)"
+ " LIMIT 1");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_known_coin",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_EXCHANGEDB_CKS_HARD_FAIL;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return TALER_EXCHANGEDB_CKS_SOFT_FAIL;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0); /* should be impossible */
+ return TALER_EXCHANGEDB_CKS_HARD_FAIL;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ if (! existed)
+ return TALER_EXCHANGEDB_CKS_ADDED;
+ break; /* continued below */
+ }
+
+ if ( (! is_denom_pub_hash_null) &&
+ (0 != GNUNET_memcmp (&denom_hash->hash,
+ &coin->denom_pub_hash.hash)) )
+ {
+ GNUNET_break_op (0);
+ return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT;
+ }
+
+ if (is_age_hash_null != coin->no_age_commitment)
+ {
+ if (is_age_hash_null)
+ {
+ GNUNET_break_op (0);
+ return TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL;
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ return TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL;
+ }
+ }
+ else if ( (! is_age_hash_null) &&
+ (0 != GNUNET_memcmp (h_age_commitment,
+ &coin->h_age_commitment)) )
+ {
+ GNUNET_break_op (0);
+ return TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS;
+ }
+
+ return TALER_EXCHANGEDB_CKS_PRESENT;
+}
diff --git a/src/exchangedb/pg_ensure_coin_known.h b/src/exchangedb/pg_ensure_coin_known.h
new file mode 100644
index 000000000..68c101735
--- /dev/null
+++ b/src/exchangedb/pg_ensure_coin_known.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_ensure_coin_known.h
+ * @brief implementation of the ensure_coin_known function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ENSURE_COIN_KNOWN_H
+#define PG_ENSURE_COIN_KNOWN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Make sure the given @a coin is known to the database.
+ *
+ * @param cls database connection plugin state
+ * @param coin the coin that must be made known
+ * @param[out] known_coin_id set to the unique row of the coin
+ * @param[out] denom_hash set to the denomination hash of the existing
+ * coin (for conflict error reporting)
+ * @param[out] h_age_commitment set to the conflicting age commitment hash on conflict
+ * @return database transaction status, non-negative on success
+ */
+enum TALER_EXCHANGEDB_CoinKnownStatus
+TEH_PG_ensure_coin_known (void *cls,
+ const struct TALER_CoinPublicInfo *coin,
+ uint64_t *known_coin_id,
+ struct TALER_DenominationHashP *denom_hash,
+ struct TALER_AgeCommitmentHash *h_age_commitment);
+
+#endif
diff --git a/src/exchangedb/pg_event_listen.c b/src/exchangedb/pg_event_listen.c
new file mode 100644
index 000000000..6e1d32843
--- /dev/null
+++ b/src/exchangedb/pg_event_listen.c
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_event_listen.c
+ * @brief Implementation of the event_listen function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_event_listen.h"
+#include "pg_helper.h"
+
+/**
+ * Register callback to be invoked on events of type @a es.
+ *
+ * @param cls database context to use
+ * @param timeout how long until to generate a timeout event
+ * @param es specification of the event to listen for
+ * @param cb function to call when the event happens, possibly
+ * multiple times (until cancel is invoked)
+ * @param cb_cls closure for @a cb
+ * @return handle useful to cancel the listener
+ */
+struct GNUNET_DB_EventHandler *
+TEH_PG_event_listen (void *cls,
+ struct GNUNET_TIME_Relative timeout,
+ const struct GNUNET_DB_EventHeaderP *es,
+ GNUNET_DB_EventCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+
+ return GNUNET_PQ_event_listen (pg->conn,
+ es,
+ timeout,
+ cb,
+ cb_cls);
+}
diff --git a/src/exchangedb/pg_event_listen.h b/src/exchangedb/pg_event_listen.h
new file mode 100644
index 000000000..7e1e83a0e
--- /dev/null
+++ b/src/exchangedb/pg_event_listen.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_event_listen.h
+ * @brief implementation of the event_listen function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_EVENT_LISTEN_H
+#define PG_EVENT_LISTEN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Register callback to be invoked on events of type @a es.
+ *
+ * @param cls database context to use
+ * @param timeout how long until to generate a timeout event
+ * @param es specification of the event to listen for
+ * @param cb function to call when the event happens, possibly
+ * multiple times (until cancel is invoked)
+ * @param cb_cls closure for @a cb
+ * @return handle useful to cancel the listener
+ */
+struct GNUNET_DB_EventHandler *
+TEH_PG_event_listen (void *cls,
+ struct GNUNET_TIME_Relative timeout,
+ const struct GNUNET_DB_EventHeaderP *es,
+ GNUNET_DB_EventCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_event_listen_cancel.c b/src/exchangedb/pg_event_listen_cancel.c
new file mode 100644
index 000000000..9d776684d
--- /dev/null
+++ b/src/exchangedb/pg_event_listen_cancel.c
@@ -0,0 +1,36 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_event_listen_cancel.c
+ * @brief Implementation of the event_listen_cancel function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_event_listen_cancel.h"
+#include "pg_helper.h"
+
+
+void
+TEH_PG_event_listen_cancel (void *cls,
+ struct GNUNET_DB_EventHandler *eh)
+
+{
+ (void) cls;
+ GNUNET_PQ_event_listen_cancel (eh);
+}
diff --git a/src/exchangedb/pg_event_listen_cancel.h b/src/exchangedb/pg_event_listen_cancel.h
new file mode 100644
index 000000000..258d7a58b
--- /dev/null
+++ b/src/exchangedb/pg_event_listen_cancel.h
@@ -0,0 +1,38 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_event_listen_cancel.h
+ * @brief implementation of the event_listen_cancel function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_EVENT_LISTEN_CANCEL_H
+#define PG_EVENT_LISTEN_CANCEL_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Stop notifications.
+ *
+ * @param cls the plugin's `struct PostgresClosure`
+ * @param eh handle to unregister.
+ */
+void
+TEH_PG_event_listen_cancel (void *cls,
+ struct GNUNET_DB_EventHandler *eh);
+#endif
diff --git a/src/exchangedb/pg_event_notify.c b/src/exchangedb/pg_event_notify.c
new file mode 100644
index 000000000..188855775
--- /dev/null
+++ b/src/exchangedb/pg_event_notify.c
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_event_notify.c
+ * @brief Implementation of the event_notify function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_event_notify.h"
+#include "pg_helper.h"
+
+
+void
+TEH_PG_event_notify (void *cls,
+ const struct GNUNET_DB_EventHeaderP *es,
+ const void *extra,
+ size_t extra_size)
+{
+ struct PostgresClosure *pg = cls;
+
+ GNUNET_PQ_event_notify (pg->conn,
+ es,
+ extra,
+ extra_size);
+}
diff --git a/src/exchangedb/pg_event_notify.h b/src/exchangedb/pg_event_notify.h
new file mode 100644
index 000000000..85069659b
--- /dev/null
+++ b/src/exchangedb/pg_event_notify.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_event_notify.h
+ * @brief implementation of the event_notify function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_EVENT_NOTIFY_H
+#define PG_EVENT_NOTIFY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Notify all that listen on @a es of an event.
+ *
+ * @param cls database context to use
+ * @param es specification of the event to generate
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+void
+TEH_PG_event_notify (void *cls,
+ const struct GNUNET_DB_EventHeaderP *es,
+ const void *extra,
+ size_t extra_size);
+
+#endif
diff --git a/src/exchangedb/pg_expire_purse.c b/src/exchangedb/pg_expire_purse.c
new file mode 100644
index 000000000..6ef7a2779
--- /dev/null
+++ b/src/exchangedb/pg_expire_purse.c
@@ -0,0 +1,69 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_expire_purse.c
+ * @brief Implementation of the expire_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_expire_purse.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_expire_purse (
+ void *cls,
+ struct GNUNET_TIME_Absolute start_time,
+ struct GNUNET_TIME_Absolute end_time)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&start_time),
+ GNUNET_PQ_query_param_absolute_time (&end_time),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ bool found = false;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("found",
+ &found),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+
+ PREPARE (pg,
+ "call_expire_purse",
+ "SELECT "
+ " out_found AS found"
+ " FROM exchange_do_expire_purse"
+ " ($1,$2,$3);");
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_expire_purse",
+ params,
+ rs);
+ if (qs < 0)
+ return qs;
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ return found
+ ? GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+ : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+}
diff --git a/src/exchangedb/pg_expire_purse.h b/src/exchangedb/pg_expire_purse.h
new file mode 100644
index 000000000..fe05fd52c
--- /dev/null
+++ b/src/exchangedb/pg_expire_purse.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_expire_purse.h
+ * @brief implementation of the expire_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_EXPIRE_PURSE_H
+#define PG_EXPIRE_PURSE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called to clean up one expired purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param start_time select purse expired after this time
+ * @param end_time select purse expired before this time
+ * @return transaction status code (#GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if no purse expired in the given time interval).
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_expire_purse (
+ void *cls,
+ struct GNUNET_TIME_Absolute start_time,
+ struct GNUNET_TIME_Absolute end_time);
+
+#endif
diff --git a/src/exchangedb/pg_find_aggregation_transient.c b/src/exchangedb/pg_find_aggregation_transient.c
new file mode 100644
index 000000000..b931188a8
--- /dev/null
+++ b/src/exchangedb/pg_find_aggregation_transient.c
@@ -0,0 +1,150 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_find_aggregation_transient.c
+ * @brief Implementation of the find_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_find_aggregation_transient.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_refunds_cb().
+ */
+struct FindAggregationTransientContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_TransientAggregationCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on error.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct SelectRefundContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+get_transients_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct FindAggregationTransientContext *srctx = cls;
+ struct PostgresClosure *pg = srctx->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_Amount amount;
+ char *payto_uri;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &merchant_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
+ &wtid),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_end
+ };
+ bool cont;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ srctx->status = GNUNET_SYSERR;
+ return;
+ }
+ cont = srctx->cb (srctx->cb_cls,
+ payto_uri,
+ &wtid,
+ &merchant_pub,
+ &amount);
+ GNUNET_free (payto_uri);
+ if (! cont)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_find_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_TransientAggregationCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ struct FindAggregationTransientContext srctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+
+ PREPARE (pg,
+ "find_transient_aggregations",
+ "SELECT"
+ " amount"
+ " ,wtid_raw"
+ " ,merchant_pub"
+ " ,payto_uri"
+ " FROM aggregation_transient atr"
+ " JOIN wire_targets wt USING (wire_target_h_payto)"
+ " WHERE atr.wire_target_h_payto=$1;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "find_transient_aggregations",
+ params,
+ &get_transients_cb,
+ &srctx);
+ if (GNUNET_SYSERR == srctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_find_aggregation_transient.h b/src/exchangedb/pg_find_aggregation_transient.h
new file mode 100644
index 000000000..c7ba4ea38
--- /dev/null
+++ b/src/exchangedb/pg_find_aggregation_transient.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_find_aggregation_transient.h
+ * @brief implementation of the find_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_FIND_AGGREGATION_TRANSIENT_H
+#define PG_FIND_AGGREGATION_TRANSIENT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Find existing entry in the transient aggregation table.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto destination of the wire transfer
+ * @param cb function to call on each matching entry
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_find_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_TransientAggregationCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_gc.c b/src/exchangedb/pg_gc.c
new file mode 100644
index 000000000..e01c1e101
--- /dev/null
+++ b/src/exchangedb/pg_gc.c
@@ -0,0 +1,80 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_gc.c
+ * @brief Implementation of the gc function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_gc.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_gc (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Absolute long_ago;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&long_ago),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_Context *conn;
+ enum GNUNET_GenericReturnValue ret;
+
+ /* Keep wire fees for 10 years, that should always
+ be enough _and_ they are tiny so it does not
+ matter to make this tight */
+ long_ago = GNUNET_TIME_absolute_subtract (
+ now,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_YEARS,
+ 10));
+ {
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+ struct GNUNET_PQ_PreparedStatement ps[] = {
+ /* Used in #postgres_gc() */
+ GNUNET_PQ_make_prepare ("run_gc",
+ "CALL"
+ " exchange_do_gc"
+ " ($1,$2);"),
+ GNUNET_PQ_PREPARED_STATEMENT_END
+ };
+
+ conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+ "exchangedb-postgres",
+ NULL,
+ es,
+ ps);
+ }
+ if (NULL == conn)
+ return GNUNET_SYSERR;
+ ret = GNUNET_OK;
+ if (0 > GNUNET_PQ_eval_prepared_non_select (conn,
+ "run_gc",
+ params))
+ ret = GNUNET_SYSERR;
+ GNUNET_PQ_disconnect (conn);
+ return ret;
+}
diff --git a/src/exchangedb/pg_gc.h b/src/exchangedb/pg_gc.h
new file mode 100644
index 000000000..803581488
--- /dev/null
+++ b/src/exchangedb/pg_gc.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_gc.h
+ * @brief implementation of the gc function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GC_H
+#define PG_GC_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to perform "garbage collection" on the
+ * database, expiring records we no longer require.
+ *
+ * @param cls closure
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR on DB errors
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_gc (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_age_withdraw.c b/src/exchangedb/pg_get_age_withdraw.c
new file mode 100644
index 000000000..ea4d3b909
--- /dev/null
+++ b/src/exchangedb/pg_get_age_withdraw.c
@@ -0,0 +1,119 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_age_withdraw.c
+ * @brief Implementation of the get_age_withdraw function for Postgres
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_age_withdraw.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_age_withdraw (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_AgeWithdrawCommitmentHashP *ach,
+ struct TALER_EXCHANGEDB_AgeWithdraw *aw)
+{
+ enum GNUNET_DB_QueryStatus ret;
+ struct PostgresClosure *pg = cls;
+ size_t num_sigs;
+ size_t num_hashes;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (ach),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_commitment",
+ &aw->h_commitment),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &aw->reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &aw->reserve_pub),
+ GNUNET_PQ_result_spec_uint16 ("max_age",
+ &aw->max_age),
+ TALER_PQ_result_spec_amount ("amount_with_fee",
+ pg->currency,
+ &aw->amount_with_fee),
+ GNUNET_PQ_result_spec_uint16 ("noreveal_index",
+ &aw->noreveal_index),
+ TALER_PQ_result_spec_array_blinded_coin_hash (
+ pg->conn,
+ "h_blind_evs",
+ &aw->num_coins,
+ &aw->h_coin_evs),
+ TALER_PQ_result_spec_array_blinded_denom_sig (
+ pg->conn,
+ "denom_sigs",
+ &num_sigs,
+ &aw->denom_sigs),
+ TALER_PQ_result_spec_array_denom_hash (
+ pg->conn,
+ "denom_pub_hashes",
+ &num_hashes,
+ &aw->denom_pub_hashes),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_age_withdraw",
+ "SELECT"
+ " h_commitment"
+ ",reserve_sig"
+ ",reserve_pub"
+ ",max_age"
+ ",amount_with_fee"
+ ",noreveal_index"
+ ",h_blind_evs"
+ ",denom_sigs"
+ ",ARRAY("
+ " SELECT denominations.denom_pub_hash FROM ("
+ " SELECT UNNEST(denom_serials) AS id,"
+ " generate_subscripts(denom_serials, 1) AS nr" /* for order */
+ " ) AS denoms"
+ " LEFT JOIN denominations ON denominations.denominations_serial=denoms.id"
+ ") AS denom_pub_hashes"
+ " FROM age_withdraw"
+ " WHERE reserve_pub=$1 and h_commitment=$2;");
+
+ ret = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_age_withdraw",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ret)
+ return ret;
+
+ if ((aw->num_coins != num_sigs) ||
+ (aw->num_coins != num_hashes))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "got inconsistent number of entries from DB: "
+ "num_coins=%ld, num_sigs=%ld, num_hashes=%ld\n",
+ aw->num_coins,
+ num_sigs,
+ num_hashes);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ return ret;
+}
diff --git a/src/exchangedb/pg_get_age_withdraw.h b/src/exchangedb/pg_get_age_withdraw.h
new file mode 100644
index 000000000..2257aa43c
--- /dev/null
+++ b/src/exchangedb/pg_get_age_withdraw.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 exchangedb/pg_get_age_withdraw.h
+ * @brief implementation of the get_age_withdraw function for Postgres
+ * @author Özgür KESIM
+ */
+#ifndef PG_GET_AGE_WITHDRAW_H
+#define PG_GET_AGE_WITHDRAW_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Locate the response for a age-withdraw request under a hash that uniquely
+ * identifies the age-withdraw operation. Used to ensure idempotency of the
+ * request.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param reserve_pub public key of the reserve for which the age-withdraw request is made
+ * @param ach hash that uniquely identifies the age-withdraw operation
+ * @param[out] aw corresponding details of the previous age-withdraw request if an entry was found
+ * @return statement execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_age_withdraw (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_AgeWithdrawCommitmentHashP *ach,
+ struct TALER_EXCHANGEDB_AgeWithdraw *aw);
+#endif
diff --git a/src/exchangedb/pg_get_coin_denomination.c b/src/exchangedb/pg_get_coin_denomination.c
new file mode 100644
index 000000000..9f9256f6f
--- /dev/null
+++ b/src/exchangedb/pg_get_coin_denomination.c
@@ -0,0 +1,69 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_coin_denomination.c
+ * @brief Implementation of the get_coin_denomination function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_coin_denomination.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_coin_denomination (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t *known_coin_id,
+ struct TALER_DenominationHashP *denom_hash)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ denom_hash),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+ known_coin_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Getting coin denomination of coin %s\n",
+ TALER_B2S (coin_pub));
+
+ /* Used in #postgres_get_coin_denomination() to fetch
+ the denomination public key hash for
+ a coin known to the exchange. */
+ PREPARE (pg,
+ "get_coin_denomination",
+ "SELECT"
+ " denominations.denom_pub_hash"
+ ",known_coin_id"
+ " FROM known_coins"
+ " JOIN denominations USING (denominations_serial)"
+ " WHERE coin_pub=$1"
+ " FOR SHARE;");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_coin_denomination",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_coin_denomination.h b/src/exchangedb/pg_get_coin_denomination.h
new file mode 100644
index 000000000..3b9f03f31
--- /dev/null
+++ b/src/exchangedb/pg_get_coin_denomination.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_coin_denomination.h
+ * @brief implementation of the get_coin_denomination function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_COIN_DENOMINATION_H
+#define PG_GET_COIN_DENOMINATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Retrieve the denomination of a known coin.
+ *
+ * @param cls the plugin closure
+ * @param coin_pub the public key of the coin to search for
+ * @param[out] known_coin_id set to the ID of the coin in the known_coins table
+ * @param[out] denom_hash where to store the hash of the coins denomination
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_coin_denomination (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t *known_coin_id,
+ struct TALER_DenominationHashP *denom_hash);
+
+#endif
diff --git a/src/exchangedb/pg_get_coin_transactions.c b/src/exchangedb/pg_get_coin_transactions.c
new file mode 100644
index 000000000..fef33a486
--- /dev/null
+++ b/src/exchangedb/pg_get_coin_transactions.c
@@ -0,0 +1,1144 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 pg_get_coin_transactions.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_pq_lib.h"
+#include "pg_get_coin_transactions.h"
+#include "pg_helper.h"
+#include "pg_start_read_committed.h"
+#include "pg_commit.h"
+#include "pg_rollback.h"
+#include "plugin_exchangedb_common.h"
+
+/**
+ * How often do we re-try when encountering DB serialization issues?
+ * (We are read-only, so can only happen due to concurrent insert,
+ * which should be very rare.)
+ */
+#define RETRIES 3
+
+/**
+ * Closure for callbacks called from #postgres_get_coin_transactions()
+ */
+struct CoinHistoryContext
+{
+ /**
+ * Head of the coin's history list.
+ */
+ struct TALER_EXCHANGEDB_TransactionList *head;
+
+ /**
+ * Public key of the coin we are building the history for.
+ */
+ const struct TALER_CoinSpendPublicKeyP *coin_pub;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to 'true' if the transaction failed.
+ */
+ bool failed;
+
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_deposit (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_DepositListEntry *deposit;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &deposit->amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &deposit->deposit_fee),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &deposit->h_denom_pub),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &deposit->h_age_commitment),
+ &deposit->no_age_commitment),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("wallet_data_hash",
+ &deposit->wallet_data_hash),
+ &deposit->no_wallet_data_hash),
+ GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
+ &deposit->timestamp),
+ GNUNET_PQ_result_spec_timestamp ("refund_deadline",
+ &deposit->refund_deadline),
+ GNUNET_PQ_result_spec_timestamp ("wire_deadline",
+ &deposit->wire_deadline),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &deposit->merchant_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &deposit->h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+ &deposit->wire_salt),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &deposit->receiver_wire_account),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &deposit->csig),
+ GNUNET_PQ_result_spec_uint64 ("coin_deposit_serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_auto_from_type ("done",
+ &deposit->done),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (deposit);
+ chc->failed = true;
+ return;
+ }
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
+ tl->details.deposit = deposit;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_purse_deposit (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_PurseDepositListEntry *deposit;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ deposit = GNUNET_new (struct TALER_EXCHANGEDB_PurseDepositListEntry);
+ {
+ bool not_finished;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &deposit->amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &deposit->deposit_fee),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &deposit->purse_pub),
+ GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("partner_base_url",
+ &deposit->exchange_base_url),
+ NULL),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &deposit->coin_sig),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &deposit->h_age_commitment),
+ &deposit->no_age_commitment),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_bool ("refunded",
+ &deposit->refunded),
+ &not_finished),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (deposit);
+ chc->failed = true;
+ return;
+ }
+ if (not_finished)
+ deposit->refunded = false;
+ deposit->no_age_commitment = GNUNET_is_zero (&deposit->h_age_commitment);
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_PURSE_DEPOSIT;
+ tl->details.purse_deposit = deposit;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_melt (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_MeltListEntry *melt;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("rc",
+ &melt->rc),
+ /* oldcoin_index not needed */
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &melt->h_denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
+ &melt->coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &melt->amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+ &melt->melt_fee),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &melt->h_age_commitment),
+ &melt->no_age_commitment),
+ GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (melt);
+ chc->failed = true;
+ return;
+ }
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_MELT;
+ tl->details.melt = melt;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_refund (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_RefundListEntry *refund;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ refund = GNUNET_new (struct TALER_EXCHANGEDB_RefundListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &refund->merchant_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
+ &refund->merchant_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &refund->h_contract_terms),
+ GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
+ &refund->rtransaction_id),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &refund->refund_amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+ &refund->refund_fee),
+ GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (refund);
+ chc->failed = true;
+ return;
+ }
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_REFUND;
+ tl->details.refund = refund;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_purse_decision (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ prefund = GNUNET_new (struct TALER_EXCHANGEDB_PurseRefundListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &prefund->purse_pub),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &prefund->refund_amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+ &prefund->refund_fee),
+ GNUNET_PQ_result_spec_uint64 ("purse_decision_serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (prefund);
+ chc->failed = true;
+ return;
+ }
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_PURSE_REFUND;
+ tl->details.purse_refund = prefund;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_old_coin_recoup (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &recoup->coin.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &recoup->coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &recoup->coin_blind),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &recoup->value),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &recoup->timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &recoup->coin.denom_pub_hash),
+ TALER_PQ_result_spec_denom_sig ("denom_sig",
+ &recoup->coin.denom_sig),
+ GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (recoup);
+ chc->failed = true;
+ return;
+ }
+ recoup->old_coin_pub = *chc->coin_pub;
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP;
+ tl->details.old_coin_recoup = recoup;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_recoup (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_RecoupListEntry *recoup;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &recoup->reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &recoup->coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &recoup->h_denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &recoup->coin_blind),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &recoup->value),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &recoup->timestamp),
+ GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (recoup);
+ chc->failed = true;
+ return;
+ }
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_RECOUP;
+ tl->details.recoup = recoup;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_recoup_refresh (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
+ &recoup->old_coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &recoup->coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &recoup->coin_blind),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &recoup->value),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &recoup->timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &recoup->coin.denom_pub_hash),
+ TALER_PQ_result_spec_denom_sig ("denom_sig",
+ &recoup->coin.denom_sig),
+ GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (recoup);
+ chc->failed = true;
+ return;
+ }
+ recoup->coin.coin_pub = *chc->coin_pub;
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH;
+ tl->details.recoup_refresh = recoup;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_reserve_open (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_ReserveOpenListEntry *role;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ role = GNUNET_new (struct TALER_EXCHANGEDB_ReserveOpenListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &role->reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &role->coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("contribution",
+ &role->coin_contribution),
+ GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (role);
+ chc->failed = true;
+ return;
+ }
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN;
+ tl->details.reserve_open = role;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Work we need to do.
+ */
+struct Work
+{
+ /**
+ * Name of the table.
+ */
+ const char *table;
+
+ /**
+ * SQL prepared statement name.
+ */
+ const char *statement;
+
+ /**
+ * Function to call to handle the result(s).
+ */
+ GNUNET_PQ_PostgresResultHandler cb;
+};
+
+
+/**
+ * We found a coin history entry. Lookup details
+ * from the respective table and store in @a cls.
+ *
+ * @param[in,out] cls a `struct CoinHistoryContext`
+ * @param result a coin history entry result set
+ * @param num_results total number of results in @a results
+ */
+static void
+handle_history_entry (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+ static const struct Work work[] = {
+ [TALER_EXCHANGEDB_TT_DEPOSIT] =
+ { "coin_deposits",
+ "get_deposit_with_coin_pub",
+ &add_coin_deposit },
+ [TALER_EXCHANGEDB_TT_MELT] =
+ { "refresh_commitments",
+ "get_refresh_session_by_coin",
+ &add_coin_melt },
+ [TALER_EXCHANGEDB_TT_PURSE_DEPOSIT] =
+ { "purse_deposits",
+ "get_purse_deposit_by_coin_pub",
+ &add_coin_purse_deposit },
+ [TALER_EXCHANGEDB_TT_PURSE_REFUND] =
+ { "purse_decision",
+ "get_purse_decision_by_coin_pub",
+ &add_coin_purse_decision },
+ [TALER_EXCHANGEDB_TT_REFUND] =
+ { "refunds",
+ "get_refunds_by_coin",
+ &add_coin_refund },
+ [TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP] =
+ { "recoup_refresh::OLD",
+ "recoup_by_old_coin",
+ &add_old_coin_recoup },
+ [TALER_EXCHANGEDB_TT_RECOUP] =
+ { "recoup",
+ "recoup_by_coin",
+ &add_coin_recoup },
+ [TALER_EXCHANGEDB_TT_RECOUP_REFRESH] =
+ { "recoup_refresh::NEW",
+ "recoup_by_refreshed_coin",
+ &add_coin_recoup_refresh },
+ [TALER_EXCHANGEDB_TT_RESERVE_OPEN] =
+ { "reserves_open_deposits",
+ "reserve_open_by_coin",
+ &add_coin_reserve_open },
+ { NULL, NULL, NULL }
+ };
+ char *table_name;
+ uint64_t serial_id;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("table_name",
+ &table_name),
+ GNUNET_PQ_result_spec_uint64 ("serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (chc->coin_pub),
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ bool found = false;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ chc->failed = true;
+ return;
+ }
+
+ for (unsigned int s = 0;
+ NULL != work[s].statement;
+ s++)
+ {
+ if (0 != strcmp (table_name,
+ work[s].table))
+ continue;
+ found = true;
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ work[s].statement,
+ params,
+ work[s].cb,
+ chc);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Coin %s had %d transactions at %llu in table %s\n",
+ TALER_B2S (chc->coin_pub),
+ (int) qs,
+ (unsigned long long) serial_id,
+ table_name);
+ if (0 >= qs)
+ chc->failed = true;
+ break;
+ }
+ if (! found)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Coin history includes unsupported table `%s`\n",
+ table_name);
+ chc->failed = true;
+ }
+ GNUNET_PQ_cleanup_result (rs);
+ if (chc->failed)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_coin_transactions (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t start_off,
+ uint64_t etag_in,
+ uint64_t *etag_out,
+ struct TALER_Amount *balance,
+ struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_EXCHANGEDB_TransactionList **tlp)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_QueryParam lparams[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_uint64 (&start_off),
+ GNUNET_PQ_query_param_end
+ };
+ struct CoinHistoryContext chc = {
+ .head = NULL,
+ .coin_pub = coin_pub,
+ .pg = pg
+ };
+
+ *tlp = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Getting transactions for coin %s\n",
+ TALER_B2S (coin_pub));
+ PREPARE (pg,
+ "get_coin_history_etag_balance",
+ "SELECT"
+ " ch.coin_history_serial_id"
+ ",kc.remaining"
+ ",denom.denom_pub_hash"
+ " FROM coin_history ch"
+ " JOIN known_coins kc"
+ " USING (coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE coin_pub=$1"
+ " ORDER BY coin_history_serial_id DESC"
+ " LIMIT 1;");
+ PREPARE (pg,
+ "get_coin_history",
+ "SELECT"
+ " table_name"
+ ",serial_id"
+ " FROM coin_history"
+ " WHERE coin_pub=$1"
+ " AND coin_history_serial_id > $2"
+ " ORDER BY coin_history_serial_id DESC;");
+ PREPARE (pg,
+ "get_deposit_with_coin_pub",
+ "SELECT"
+ " cdep.amount_with_fee"
+ ",denoms.fee_deposit"
+ ",denoms.denom_pub_hash"
+ ",kc.age_commitment_hash"
+ ",bdep.wallet_timestamp"
+ ",bdep.refund_deadline"
+ ",bdep.wire_deadline"
+ ",bdep.merchant_pub"
+ ",bdep.h_contract_terms"
+ ",bdep.wallet_data_hash"
+ ",bdep.wire_salt"
+ ",wt.payto_uri"
+ ",cdep.coin_sig"
+ ",cdep.coin_deposit_serial_id"
+ ",bdep.done"
+ " FROM coin_deposits cdep"
+ " JOIN batch_deposits bdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN wire_targets wt"
+ " USING (wire_target_h_payto)"
+ " JOIN known_coins kc"
+ " ON (kc.coin_pub = cdep.coin_pub)"
+ " JOIN denominations denoms"
+ " USING (denominations_serial)"
+ " WHERE cdep.coin_pub=$1"
+ " AND cdep.coin_deposit_serial_id=$2;");
+ PREPARE (pg,
+ "get_refresh_session_by_coin",
+ "SELECT"
+ " rc"
+ ",old_coin_sig"
+ ",amount_with_fee"
+ ",denoms.denom_pub_hash"
+ ",denoms.fee_refresh"
+ ",kc.age_commitment_hash"
+ ",melt_serial_id"
+ " FROM refresh_commitments"
+ " JOIN known_coins kc"
+ " ON (refresh_commitments.old_coin_pub = kc.coin_pub)"
+ " JOIN denominations denoms"
+ " USING (denominations_serial)"
+ " WHERE old_coin_pub=$1"
+ " AND melt_serial_id=$2;");
+ PREPARE (pg,
+ "get_purse_deposit_by_coin_pub",
+ "SELECT"
+ " partner_base_url"
+ ",pd.amount_with_fee"
+ ",denoms.fee_deposit"
+ ",pd.purse_pub"
+ ",kc.age_commitment_hash"
+ ",pd.coin_sig"
+ ",pd.purse_deposit_serial_id"
+ ",pdes.refunded"
+ " FROM purse_deposits pd"
+ " LEFT JOIN partners"
+ " USING (partner_serial_id)"
+ " JOIN purse_requests pr"
+ " USING (purse_pub)"
+ " LEFT JOIN purse_decision pdes"
+ " USING (purse_pub)"
+ " JOIN known_coins kc"
+ " ON (pd.coin_pub = kc.coin_pub)"
+ " JOIN denominations denoms"
+ " USING (denominations_serial)"
+ " WHERE pd.purse_deposit_serial_id=$2"
+ " AND pd.coin_pub=$1;");
+ PREPARE (pg,
+ "get_purse_decision_by_coin_pub",
+ "SELECT"
+ " pdes.purse_pub"
+ ",pd.amount_with_fee"
+ ",denom.fee_refund"
+ ",pdes.purse_decision_serial_id"
+ " FROM purse_decision pdes"
+ " JOIN purse_deposits pd"
+ " USING (purse_pub)"
+ " JOIN known_coins kc"
+ " ON (pd.coin_pub = kc.coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE pd.coin_pub=$1"
+ " AND pdes.purse_decision_serial_id=$2"
+ " AND pdes.refunded;");
+ PREPARE (pg,
+ "get_refunds_by_coin",
+ "SELECT"
+ " bdep.merchant_pub"
+ ",ref.merchant_sig"
+ ",bdep.h_contract_terms"
+ ",ref.rtransaction_id"
+ ",ref.amount_with_fee"
+ ",denom.fee_refund"
+ ",ref.refund_serial_id"
+ " FROM refunds ref"
+ " JOIN coin_deposits cdep"
+ " ON (ref.coin_pub = cdep.coin_pub AND ref.batch_deposit_serial_id = cdep.batch_deposit_serial_id)"
+ " JOIN batch_deposits bdep"
+ " ON (ref.batch_deposit_serial_id = bdep.batch_deposit_serial_id)"
+ " JOIN known_coins kc"
+ " ON (ref.coin_pub = kc.coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE ref.coin_pub=$1"
+ " AND ref.refund_serial_id=$2;");
+ PREPARE (pg,
+ "recoup_by_old_coin",
+ "SELECT"
+ " coins.coin_pub"
+ ",rr.coin_sig"
+ ",rr.coin_blind"
+ ",rr.amount"
+ ",rr.recoup_timestamp"
+ ",denoms.denom_pub_hash"
+ ",coins.denom_sig"
+ ",rr.recoup_refresh_uuid"
+ " FROM recoup_refresh rr"
+ " JOIN known_coins coins"
+ " USING (coin_pub)"
+ " JOIN denominations denoms"
+ " USING (denominations_serial)"
+ " WHERE recoup_refresh_uuid=$2"
+ " AND rrc_serial IN"
+ " (SELECT rrc.rrc_serial"
+ " FROM refresh_commitments melt"
+ " JOIN refresh_revealed_coins rrc"
+ " USING (melt_serial_id)"
+ " WHERE melt.old_coin_pub=$1);");
+ PREPARE (pg,
+ "recoup_by_coin",
+ "SELECT"
+ " res.reserve_pub"
+ ",denoms.denom_pub_hash"
+ ",rcp.coin_sig"
+ ",rcp.coin_blind"
+ ",rcp.amount"
+ ",rcp.recoup_timestamp"
+ ",rcp.recoup_uuid"
+ " FROM recoup rcp"
+ " JOIN reserves_out ro"
+ " USING (reserve_out_serial_id)"
+ " JOIN reserves res"
+ " USING (reserve_uuid)"
+ " JOIN known_coins coins"
+ " USING (coin_pub)"
+ " JOIN denominations denoms"
+ " ON (denoms.denominations_serial = coins.denominations_serial)"
+ " WHERE rcp.recoup_uuid=$2"
+ " AND coins.coin_pub=$1;");
+ /* Used in #postgres_get_coin_transactions() to obtain recoup transactions
+ for a refreshed coin */
+ PREPARE (pg,
+ "recoup_by_refreshed_coin",
+ "SELECT"
+ " old_coins.coin_pub AS old_coin_pub"
+ ",rr.coin_sig"
+ ",rr.coin_blind"
+ ",rr.amount"
+ ",rr.recoup_timestamp"
+ ",denoms.denom_pub_hash"
+ ",coins.denom_sig"
+ ",recoup_refresh_uuid"
+ " FROM recoup_refresh rr"
+ " JOIN refresh_revealed_coins rrc"
+ " USING (rrc_serial)"
+ " JOIN refresh_commitments rfc"
+ " ON (rrc.melt_serial_id = rfc.melt_serial_id)"
+ " JOIN known_coins old_coins"
+ " ON (rfc.old_coin_pub = old_coins.coin_pub)"
+ " JOIN known_coins coins"
+ " ON (rr.coin_pub = coins.coin_pub)"
+ " JOIN denominations denoms"
+ " ON (denoms.denominations_serial = coins.denominations_serial)"
+ " WHERE rr.recoup_refresh_uuid=$2"
+ " AND coins.coin_pub=$1;");
+ PREPARE (pg,
+ "reserve_open_by_coin",
+ "SELECT"
+ " reserve_open_deposit_uuid"
+ ",coin_sig"
+ ",reserve_sig"
+ ",contribution"
+ " FROM reserves_open_deposits"
+ " WHERE coin_pub=$1"
+ " AND reserve_open_deposit_uuid=$2;");
+
+ for (unsigned int i = 0; i<RETRIES; i++)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t end;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
+ &end),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ h_denom_pub),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("remaining",
+ balance),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ TEH_PG_start_read_committed (pg,
+ "get-coin-transactions"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* First only check the last item, to see if
+ we even need to iterate */
+ qs = GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "get_coin_history_etag_balance",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ *etag_out = end;
+ if (end == etag_in)
+ return qs;
+ }
+ /* We indeed need to iterate over the history */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Current ETag for coin %s is %llu\n",
+ TALER_B2S (coin_pub),
+ (unsigned long long) end);
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "get_coin_history",
+ lparams,
+ &handle_history_entry,
+ &chc);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ default:
+ break;
+ }
+ if (chc.failed)
+ {
+ TEH_PG_rollback (pg);
+ TEH_COMMON_free_coin_transaction_list (pg,
+ chc.head);
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+ }
+ qs = TEH_PG_commit (pg);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ TEH_COMMON_free_coin_transaction_list (pg,
+ chc.head);
+ chc.head = NULL;
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_COMMON_free_coin_transaction_list (pg,
+ chc.head);
+ chc.head = NULL;
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ *tlp = chc.head;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ }
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
diff --git a/src/exchangedb/pg_get_coin_transactions.h b/src/exchangedb/pg_get_coin_transactions.h
new file mode 100644
index 000000000..46e32e094
--- /dev/null
+++ b/src/exchangedb/pg_get_coin_transactions.h
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 pg_get_coin_transactions.h
+ * @brief implementation of the get_coin_transactions function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_COIN_TRANSACTIONS_H
+#define PG_GET_COIN_TRANSACTIONS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Compile a list of (historic) transactions performed with the given coin
+ * (melt, refund, recoup and deposit operations). Should return 0 if the @a
+ * coin_pub is unknown, otherwise determine @a etag_out and if it is past @a
+ * etag_in return the history after @a start_off. @a etag_out should be set
+ * to the last row ID of the given @a coin_pub in the coin history table.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param coin_pub coin to investigate
+ * @param start_off starting offset from which on to return entries
+ * @param etag_in up to this offset the client already has a response, do not
+ * return anything unless @a etag_out will be larger
+ * @param[out] etag_out set to the latest history offset known for this @a coin_pub
+ * @param[out] balance set to current balance of the coin
+ * @param[out] h_denom_pub set to denomination public key of the coin
+ * @param[out] tlp set to list of transactions, set to NULL if coin has no
+ * transaction history past @a start_off or if @a etag_in is equal
+ * to the value written to @a etag_out.
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_coin_transactions (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t start_off,
+ uint64_t etag_in,
+ uint64_t *etag_out,
+ struct TALER_Amount *balance,
+ struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_EXCHANGEDB_TransactionList **tlp);
+
+
+#endif
diff --git a/src/exchangedb/pg_get_denomination_info.c b/src/exchangedb/pg_get_denomination_info.c
new file mode 100644
index 000000000..4bae29795
--- /dev/null
+++ b/src/exchangedb/pg_get_denomination_info.c
@@ -0,0 +1,91 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_denomination_info.c
+ * @brief Implementation of the get_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_denomination_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_denomination_info (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &issue->signature),
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &issue->start),
+ GNUNET_PQ_result_spec_timestamp ("expire_withdraw",
+ &issue->expire_withdraw),
+ GNUNET_PQ_result_spec_timestamp ("expire_deposit",
+ &issue->expire_deposit),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &issue->expire_legal),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
+ &issue->value),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+ &issue->fees.withdraw),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &issue->fees.deposit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+ &issue->fees.refresh),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+ &issue->fees.refund),
+ GNUNET_PQ_result_spec_uint32 ("age_mask",
+ &issue->age_mask.bits),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "denomination_get",
+ "SELECT"
+ " master_sig"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin" /* value of this denom */
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",age_mask"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "denomination_get",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ return qs;
+ issue->denom_hash = *denom_pub_hash;
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_denomination_info.h b/src/exchangedb/pg_get_denomination_info.h
new file mode 100644
index 000000000..843227759
--- /dev/null
+++ b/src/exchangedb/pg_get_denomination_info.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_denomination_info.h
+ * @brief implementation of the get_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_DENOMINATION_INFO_H
+#define PG_GET_DENOMINATION_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Fetch information about a denomination key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param denom_pub_hash hash of the public key used for signing coins of this denomination
+ * @param[out] issue set to issue information with value, fees and other info about the coin
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_denomination_info (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
+
+#endif
diff --git a/src/exchangedb/pg_get_denomination_revocation.c b/src/exchangedb/pg_get_denomination_revocation.c
new file mode 100644
index 000000000..5e7a3a322
--- /dev/null
+++ b/src/exchangedb/pg_get_denomination_revocation.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_denomination_revocation.c
+ * @brief Implementation of the get_denomination_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_denomination_revocation.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_denomination_revocation (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_MasterSignatureP *master_sig,
+ uint64_t *rowid)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_uint64 ("denom_revocations_serial_id",
+ rowid),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "denomination_revocation_get",
+ "SELECT"
+ " master_sig"
+ ",denom_revocations_serial_id"
+ " FROM denomination_revocations"
+ " WHERE denominations_serial="
+ " (SELECT denominations_serial"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$1);");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "denomination_revocation_get",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_denomination_revocation.h b/src/exchangedb/pg_get_denomination_revocation.h
new file mode 100644
index 000000000..5f7f27227
--- /dev/null
+++ b/src/exchangedb/pg_get_denomination_revocation.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_denomination_revocation.h
+ * @brief implementation of the get_denomination_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_DENOMINATION_REVOCATION_H
+#define PG_GET_DENOMINATION_REVOCATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain information about a denomination key's revocation from
+ * the database.
+ *
+ * @param cls closure
+ * @param denom_pub_hash hash of the revoked denomination key
+ * @param[out] master_sig signature affirming the revocation
+ * @param[out] rowid row where the information is stored
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_denomination_revocation (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_MasterSignatureP *master_sig,
+ uint64_t *rowid);
+
+#endif
diff --git a/src/exchangedb/pg_get_drain_profit.c b/src/exchangedb/pg_get_drain_profit.c
new file mode 100644
index 000000000..75fccefcd
--- /dev/null
+++ b/src/exchangedb/pg_get_drain_profit.c
@@ -0,0 +1,76 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_drain_profit.c
+ * @brief Implementation of the get_drain_profit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_drain_profit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_drain_profit (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t *serial,
+ char **account_section,
+ char **payto_uri,
+ struct GNUNET_TIME_Timestamp *request_timestamp,
+ struct TALER_Amount *amount,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("profit_drain_serial_id",
+ serial),
+ GNUNET_PQ_result_spec_string ("account_section",
+ account_section),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ payto_uri),
+ GNUNET_PQ_result_spec_timestamp ("trigger_date",
+ request_timestamp),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ amount),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_profit_drain",
+ "SELECT"
+ " profit_drain_serial_id"
+ ",account_section"
+ ",payto_uri"
+ ",trigger_date"
+ ",amount"
+ ",master_sig"
+ " FROM profit_drains"
+ " WHERE wtid=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_profit_drain",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_drain_profit.h b/src/exchangedb/pg_get_drain_profit.h
new file mode 100644
index 000000000..dd05d8afd
--- /dev/null
+++ b/src/exchangedb/pg_get_drain_profit.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_drain_profit.h
+ * @brief implementation of the get_drain_profit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_DRAIN_PROFIT_H
+#define PG_GET_DRAIN_PROFIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to get information about a profit drain event.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param wtid wire transfer ID to look up drain event for
+ * @param[out] serial set to serial ID of the entry
+ * @param[out] account_section set to account to drain
+ * @param[out] payto_uri set to account to wire funds to
+ * @param[out] request_timestamp set to time of the signature
+ * @param[out] amount set to amount to wire
+ * @param[out] master_sig set to signature affirming the operation
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_drain_profit (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t *serial,
+ char **account_section,
+ char **payto_uri,
+ struct GNUNET_TIME_Timestamp *request_timestamp,
+ struct TALER_Amount *amount,
+ struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_get_expired_reserves.c b/src/exchangedb/pg_get_expired_reserves.c
new file mode 100644
index 000000000..be9ece98a
--- /dev/null
+++ b/src/exchangedb/pg_get_expired_reserves.c
@@ -0,0 +1,174 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_get_expired_reserves.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_expired_reserves.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #reserve_expired_cb().
+ */
+struct ExpiredReserveContext
+{
+ /**
+ * Function to call for each expired reserve.
+ */
+ TALER_EXCHANGEDB_ReserveExpiredCallback rec;
+
+ /**
+ * Closure to give to @e rec.
+ */
+ void *rec_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on error.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserve_expired_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ExpiredReserveContext *erc = cls;
+ struct PostgresClosure *pg = erc->pg;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = GNUNET_OK;
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_TIME_Timestamp exp_date;
+ char *account_details;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_Amount remaining_balance;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ &exp_date),
+ GNUNET_PQ_result_spec_string ("account_details",
+ &account_details),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ TALER_PQ_result_spec_amount ("current_balance",
+ pg->currency,
+ &remaining_balance),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ret = GNUNET_SYSERR;
+ break;
+ }
+ ret = erc->rec (erc->rec_cls,
+ &reserve_pub,
+ &remaining_balance,
+ account_details,
+ exp_date,
+ 0);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+ erc->status = ret;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_expired_reserves (void *cls,
+ struct GNUNET_TIME_Timestamp now,
+ TALER_EXCHANGEDB_ReserveExpiredCallback rec,
+ void *rec_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct ExpiredReserveContext ectx = {
+ .rec = rec,
+ .rec_cls = rec_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "get_expired_reserves",
+ "WITH ed AS MATERIALIZED ( "
+ " SELECT * "
+ " FROM reserves "
+ " WHERE expiration_date <= $1 "
+ " AND ((current_balance).val != 0 OR (current_balance).frac != 0) "
+ " ORDER BY expiration_date ASC "
+ " LIMIT 1 "
+ ") "
+ "SELECT "
+ " ed.expiration_date "
+ " ,payto_uri AS account_details "
+ " ,ed.reserve_pub "
+ " ,current_balance "
+ "FROM ( "
+ " SELECT "
+ " * "
+ " FROM reserves_in "
+ " WHERE reserve_pub = ( "
+ " SELECT reserve_pub FROM ed) "
+ " ) ri "
+ "JOIN wire_targets wt ON (ri.wire_source_h_payto = wt.wire_target_h_payto) "
+ "JOIN ed ON (ri.reserve_pub = ed.reserve_pub);");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_expired_reserves",
+ params,
+ &reserve_expired_cb,
+ &ectx);
+ switch (ectx.status)
+ {
+ case GNUNET_SYSERR:
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_NO:
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+ case GNUNET_OK:
+ break;
+ }
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_expired_reserves.h b/src/exchangedb/pg_get_expired_reserves.h
new file mode 100644
index 000000000..0874b531a
--- /dev/null
+++ b/src/exchangedb/pg_get_expired_reserves.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_get_expired_reserves.h
+ * @brief implementation of the get_expired_reserves function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_EXPIRED_RESERVES_H
+#define PG_GET_EXPIRED_RESERVES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Obtain information about expired reserves and their
+ * remaining balances.
+ *
+ * @param cls closure of the plugin
+ * @param now timestamp based on which we decide expiration
+ * @param rec function to call on expired reserves
+ * @param rec_cls closure for @a rec
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_expired_reserves (void *cls,
+ struct GNUNET_TIME_Timestamp now,
+ TALER_EXCHANGEDB_ReserveExpiredCallback rec,
+ void *rec_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_extension_manifest.c b/src/exchangedb/pg_get_extension_manifest.c
new file mode 100644
index 000000000..c6b5948cf
--- /dev/null
+++ b/src/exchangedb/pg_get_extension_manifest.c
@@ -0,0 +1,67 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_extension_manifest.c
+ * @brief Implementation of the get_extension_manifest function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_extension_manifest.h"
+#include "pg_helper.h"
+
+/**
+ * Function called to get the manifest of an extension
+ * (age-restriction, policy_extension_...)
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param extension_name the name of the extension
+ * @param[out] manifest JSON object of the manifest as string
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_extension_manifest (void *cls,
+ const char *extension_name,
+ char **manifest)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (extension_name),
+ GNUNET_PQ_query_param_end
+ };
+ bool is_null;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("manifest",
+ manifest),
+ &is_null),
+ GNUNET_PQ_result_spec_end
+ };
+
+ *manifest = NULL;
+ PREPARE (pg,
+ "get_extension_manifest",
+ "SELECT"
+ " manifest"
+ " FROM extensions"
+ " WHERE name=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_extension_manifest",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_extension_manifest.h b/src/exchangedb/pg_get_extension_manifest.h
new file mode 100644
index 000000000..e8331ad9b
--- /dev/null
+++ b/src/exchangedb/pg_get_extension_manifest.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_extension_manifest.h
+ * @brief implementation of the get_extension_manifest function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_EXTENSION_MANIFEST_H
+#define PG_GET_EXTENSION_MANIFEST_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to get the manifest of an extension
+ * (age-restriction, policy_extension_...)
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param extension_name the name of the extension
+ * @param[out] manifest JSON object of the manifest as string
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_extension_manifest (void *cls,
+ const char *extension_name,
+ char **manifest);
+
+#endif
diff --git a/src/exchangedb/pg_get_global_fee.c b/src/exchangedb/pg_get_global_fee.c
new file mode 100644
index 000000000..46addfa17
--- /dev/null
+++ b/src/exchangedb/pg_get_global_fee.c
@@ -0,0 +1,86 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_global_fee.c
+ * @brief Implementation of the get_global_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_global_fee.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_global_fee (void *cls,
+ struct GNUNET_TIME_Timestamp date,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative *purse_timeout,
+ struct GNUNET_TIME_Relative *history_expiration,
+ uint32_t *purse_account_limit,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&date),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("start_date",
+ start_date),
+ GNUNET_PQ_result_spec_timestamp ("end_date",
+ end_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("history_fee",
+ &fees->history),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("account_fee",
+ &fees->account),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
+ &fees->purse),
+ GNUNET_PQ_result_spec_relative_time ("purse_timeout",
+ purse_timeout),
+ GNUNET_PQ_result_spec_relative_time ("history_expiration",
+ history_expiration),
+ GNUNET_PQ_result_spec_uint32 ("purse_account_limit",
+ purse_account_limit),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_global_fee",
+ "SELECT "
+ " start_date"
+ ",end_date"
+ ",history_fee"
+ ",account_fee"
+ ",purse_fee"
+ ",purse_timeout"
+ ",history_expiration"
+ ",purse_account_limit"
+ ",master_sig"
+ " FROM global_fee"
+ " WHERE start_date <= $1"
+ " AND end_date > $1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_global_fee",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_global_fee.h b/src/exchangedb/pg_get_global_fee.h
new file mode 100644
index 000000000..1e7c9e94b
--- /dev/null
+++ b/src/exchangedb/pg_get_global_fee.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_global_fee.h
+ * @brief implementation of the get_global_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_GLOBAL_FEE_H
+#define PG_GET_GLOBAL_FEE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Obtain global fees from database.
+ *
+ * @param cls closure
+ * @param date for which date do we want the fee?
+ * @param[out] start_date when does the fee go into effect
+ * @param[out] end_date when does the fee end being valid
+ * @param[out] fees how high are the wire fees
+ * @param[out] purse_timeout set to how long we keep unmerged purses
+ * @param[out] history_expiration set to how long we keep account histories
+ * @param[out] purse_account_limit set to the number of free purses per account
+ * @param[out] master_sig signature over the above by the exchange master key
+ * @return status of the transaction
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_global_fee (void *cls,
+ struct GNUNET_TIME_Timestamp date,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative *purse_timeout,
+ struct GNUNET_TIME_Relative *history_expiration,
+ uint32_t *purse_account_limit,
+ struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_get_global_fees.c b/src/exchangedb/pg_get_global_fees.c
new file mode 100644
index 000000000..21be35989
--- /dev/null
+++ b/src/exchangedb/pg_get_global_fees.c
@@ -0,0 +1,165 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_global_fees.c
+ * @brief Implementation of the get_global_fees function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_global_fees.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #global_fees_cb().
+ */
+struct GlobalFeeContext
+{
+ /**
+ * Function to call for each global fee block.
+ */
+ TALER_EXCHANGEDB_GlobalFeeCallback cb;
+
+ /**
+ * Closure to give to @e rec.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on error.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+global_fees_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GlobalFeeContext *gctx = cls;
+ struct PostgresClosure *pg = gctx->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_GlobalFeeSet fees;
+ struct GNUNET_TIME_Relative purse_timeout;
+ struct GNUNET_TIME_Relative history_expiration;
+ uint32_t purse_account_limit;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("start_date",
+ &start_date),
+ GNUNET_PQ_result_spec_timestamp ("end_date",
+ &end_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("history_fee",
+ &fees.history),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("account_fee",
+ &fees.account),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
+ &fees.purse),
+ GNUNET_PQ_result_spec_relative_time ("purse_timeout",
+ &purse_timeout),
+ GNUNET_PQ_result_spec_relative_time ("history_expiration",
+ &history_expiration),
+ GNUNET_PQ_result_spec_uint32 ("purse_account_limit",
+ &purse_account_limit),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ gctx->status = GNUNET_SYSERR;
+ break;
+ }
+ gctx->cb (gctx->cb_cls,
+ &fees,
+ purse_timeout,
+ history_expiration,
+ purse_account_limit,
+ start_date,
+ end_date,
+ &master_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_global_fees (void *cls,
+ TALER_EXCHANGEDB_GlobalFeeCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp date
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_get (),
+ GNUNET_TIME_UNIT_YEARS));
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&date),
+ GNUNET_PQ_query_param_end
+ };
+ struct GlobalFeeContext gctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+
+ PREPARE (pg,
+ "get_global_fees",
+ "SELECT "
+ " start_date"
+ ",end_date"
+ ",history_fee"
+ ",account_fee"
+ ",purse_fee"
+ ",purse_timeout"
+ ",history_expiration"
+ ",purse_account_limit"
+ ",master_sig"
+ " FROM global_fee"
+ " WHERE start_date >= $1");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_global_fees",
+ params,
+ &global_fees_cb,
+ &gctx);
+}
diff --git a/src/exchangedb/pg_get_global_fees.h b/src/exchangedb/pg_get_global_fees.h
new file mode 100644
index 000000000..80c9b812f
--- /dev/null
+++ b/src/exchangedb/pg_get_global_fees.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_global_fees.h
+ * @brief implementation of the get_global_fees function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_GLOBAL_FEES_H
+#define PG_GET_GLOBAL_FEES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain global fees from database.
+ *
+ * @param cls closure
+ * @param cb function to call on each fee entry
+ * @param cb_cls closure for @a cb
+ * @return status of the transaction
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_global_fees (void *cls,
+ TALER_EXCHANGEDB_GlobalFeeCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_known_coin.c b/src/exchangedb/pg_get_known_coin.c
new file mode 100644
index 000000000..2c4a82d67
--- /dev/null
+++ b/src/exchangedb/pg_get_known_coin.c
@@ -0,0 +1,67 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_known_coin.c
+ * @brief Implementation of the get_known_coin function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_known_coin.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_known_coin (void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_CoinPublicInfo *coin_info)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &coin_info->denom_pub_hash),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &coin_info->h_age_commitment),
+ &coin_info->no_age_commitment),
+ TALER_PQ_result_spec_denom_sig ("denom_sig",
+ &coin_info->denom_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Getting known coin data for coin %s\n",
+ TALER_B2S (coin_pub));
+ coin_info->coin_pub = *coin_pub;
+ PREPARE (pg,
+ "get_known_coin",
+ "SELECT"
+ " denominations.denom_pub_hash"
+ ",age_commitment_hash"
+ ",denom_sig"
+ " FROM known_coins"
+ " JOIN denominations USING (denominations_serial)"
+ " WHERE coin_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_known_coin",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_known_coin.h b/src/exchangedb/pg_get_known_coin.h
new file mode 100644
index 000000000..c34bd2a97
--- /dev/null
+++ b/src/exchangedb/pg_get_known_coin.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_known_coin.h
+ * @brief implementation of the get_known_coin function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_KNOWN_COIN_H
+#define PG_GET_KNOWN_COIN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Retrieve the record for a known coin.
+ *
+ * @param cls the plugin closure
+ * @param coin_pub the public key of the coin to search for
+ * @param coin_info place holder for the returned coin information object
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_known_coin (void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_CoinPublicInfo *coin_info);
+
+#endif
diff --git a/src/exchangedb/pg_get_link_data.c b/src/exchangedb/pg_get_link_data.c
new file mode 100644
index 000000000..1b0cb3e20
--- /dev/null
+++ b/src/exchangedb/pg_get_link_data.c
@@ -0,0 +1,367 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_link_data.c
+ * @brief Implementation of the get_link_data function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_link_data.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #add_ldl().
+ */
+struct LinkDataContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_LinkCallback ldc;
+
+ /**
+ * Closure for @e ldc.
+ */
+ void *ldc_cls;
+
+ /**
+ * Last transfer public key for which we have information in @e last.
+ * Only valid if @e last is non-NULL.
+ */
+ struct TALER_TransferPublicKeyP transfer_pub;
+
+ /**
+ * Status, set to #GNUNET_SYSERR on errors,
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Free memory of the link data list.
+ *
+ * @param ldl link data list to release
+ */
+static void
+free_link_data_list (struct TALER_EXCHANGEDB_LinkList *ldl)
+{
+ struct TALER_EXCHANGEDB_LinkList *next;
+
+ while (NULL != ldl)
+ {
+ next = ldl->next;
+ TALER_denom_pub_free (&ldl->denom_pub);
+ TALER_blinded_denom_sig_free (&ldl->ev_sig);
+ TALER_denom_ewv_free (&ldl->alg_values);
+ GNUNET_free (ldl);
+ ldl = next;
+ }
+}
+
+
+struct Results
+{
+ struct TALER_EXCHANGEDB_LinkList *pos;
+ struct TALER_TransferPublicKeyP transfer_pub;
+};
+
+
+static int
+transfer_pub_cmp (const void *a,
+ const void *b)
+{
+ const struct Results *ra = a;
+ const struct Results *rb = b;
+
+ return GNUNET_memcmp (&ra->transfer_pub,
+ &rb->transfer_pub);
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct LinkDataContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_ldl (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LinkDataContext *ldctx = cls;
+ struct Results *temp = GNUNET_new_array (num_results,
+ struct Results);
+ unsigned int temp_off = 0;
+
+ for (int i = num_results - 1; i >= 0; i--)
+ {
+ struct TALER_EXCHANGEDB_LinkList *pos;
+
+ pos = GNUNET_new (struct TALER_EXCHANGEDB_LinkList);
+ {
+ struct TALER_BlindedPlanchet bp;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("transfer_pub",
+ &temp[temp_off].transfer_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("link_sig",
+ &pos->orig_coin_link_sig),
+ TALER_PQ_result_spec_blinded_denom_sig ("ev_sig",
+ &pos->ev_sig),
+ GNUNET_PQ_result_spec_uint32 ("freshcoin_index",
+ &pos->coin_refresh_offset),
+ TALER_PQ_result_spec_exchange_withdraw_values ("ewv",
+ &pos->alg_values),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &pos->denom_pub),
+ TALER_PQ_result_spec_blinded_planchet ("coin_ev",
+ &bp),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (pos);
+ ldctx->status = GNUNET_SYSERR;
+ return;
+ }
+ if (GNUNET_CRYPTO_BSA_CS == bp.blinded_message->cipher)
+ {
+ pos->nonce.cs_nonce
+ = bp.blinded_message->details.cs_blinded_message.nonce;
+ pos->have_nonce = true;
+ }
+ TALER_blinded_planchet_free (&bp);
+ }
+ temp[temp_off].pos = pos;
+ temp_off++;
+ }
+ qsort (temp,
+ temp_off,
+ sizeof (struct Results),
+ &transfer_pub_cmp);
+ if (temp_off > 0)
+ {
+ struct TALER_EXCHANGEDB_LinkList *head = NULL;
+
+ head = temp[0].pos;
+ for (unsigned int i = 1; i < temp_off; i++)
+ {
+ struct TALER_EXCHANGEDB_LinkList *pos = temp[i].pos;
+ const struct TALER_TransferPublicKeyP *tp = &temp[i].transfer_pub;
+
+ if (0 == GNUNET_memcmp (tp,
+ &temp[i - 1].transfer_pub))
+ {
+ pos->next = head;
+ head = pos;
+ }
+ else
+ {
+ ldctx->ldc (ldctx->ldc_cls,
+ &temp[i - 1].transfer_pub,
+ head);
+ free_link_data_list (head);
+ head = pos;
+ }
+ }
+ ldctx->ldc (ldctx->ldc_cls,
+ &temp[temp_off - 1].transfer_pub,
+ head);
+ free_link_data_list (head);
+ }
+ GNUNET_free (temp);
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_link_data (void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ TALER_EXCHANGEDB_LinkCallback ldc,
+ void *ldc_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ struct LinkDataContext ldctx;
+ static int percent_refund = -2;
+ const char *query;
+
+ if (-2 == percent_refund)
+ {
+ const char *mode = getenv ("TALER_POSTGRES_GET_LINK_DATA_LOGIC");
+ char dummy;
+
+ if ( (NULL==mode) ||
+ (1 != sscanf (mode,
+ "%d%c",
+ &percent_refund,
+ &dummy)) )
+ {
+ if (NULL != mode)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Bad mode `%s' specified\n",
+ mode);
+ percent_refund = 4; /* Fastest known */
+ }
+ }
+ switch (percent_refund)
+ {
+ case 0:
+ query = "get_link";
+ PREPARE (pg,
+ query,
+ "SELECT "
+ " tp.transfer_pub"
+ ",denoms.denom_pub"
+ ",rrc.ev_sig"
+ ",rrc.ewv"
+ ",rrc.link_sig"
+ ",rrc.freshcoin_index"
+ ",rrc.coin_ev"
+ " FROM refresh_commitments"
+ " JOIN refresh_revealed_coins rrc"
+ " USING (melt_serial_id)"
+ " JOIN refresh_transfer_keys tp"
+ " USING (melt_serial_id)"
+ " JOIN denominations denoms"
+ " ON (rrc.denominations_serial = denoms.denominations_serial)"
+ " WHERE old_coin_pub=$1"
+ " ORDER BY tp.transfer_pub, rrc.freshcoin_index ASC");
+ break;
+ case 1:
+ query = "get_link_v1";
+ PREPARE (pg,
+ query,
+ "WITH rc AS MATERIALIZED ("
+ "SELECT"
+ " melt_serial_id"
+ " FROM refresh_commitments"
+ " WHERE old_coin_pub=$1"
+ ")"
+ "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"
+ " USING (melt_serial_id)"
+ " JOIN denominations denoms"
+ " USING (denominations_serial)"
+ " WHERE rrc.melt_serial_id = (SELECT melt_serial_id FROM rc)"
+ " ORDER BY tp.transfer_pub, rrc.freshcoin_index ASC");
+ break;
+ case 2:
+ query = "get_link_v2";
+ PREPARE (pg,
+ query,
+ "SELECT"
+ " *"
+ " FROM"
+ " exchange_do_get_link_data"
+ " ($1) "
+ " AS "
+ " (transfer_pub BYTEA"
+ " ,denom_pub BYTEA"
+ " ,ev_sig BYTEA"
+ " ,ewv BYTEA"
+ " ,link_sig BYTEA"
+ " ,freshcoin_index INT4"
+ " ,coin_ev BYTEA);");
+ break;
+ case 3:
+ query = "get_link_v3";
+ PREPARE (pg,
+ query,
+ "SELECT "
+ " tp.transfer_pub"
+ ",denoms.denom_pub"
+ ",rrc.ev_sig"
+ ",rrc.ewv"
+ ",rrc.link_sig"
+ ",rrc.freshcoin_index"
+ ",rrc.coin_ev"
+ " FROM refresh_commitments"
+ " JOIN refresh_revealed_coins rrc"
+ " USING (melt_serial_id)"
+ " JOIN refresh_transfer_keys tp"
+ " USING (melt_serial_id)"
+ " JOIN denominations denoms"
+ " ON (rrc.denominations_serial = denoms.denominations_serial)"
+ " WHERE old_coin_pub=$1");
+ break;
+ case 4:
+ query = "get_link_v4";
+ PREPARE (pg,
+ query,
+ "WITH rc AS MATERIALIZED ("
+ "SELECT"
+ " melt_serial_id"
+ " FROM refresh_commitments"
+ " WHERE old_coin_pub=$1"
+ ")"
+ "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"
+ " USING (melt_serial_id)"
+ " JOIN denominations denoms"
+ " USING (denominations_serial)"
+ " WHERE rrc.melt_serial_id = (SELECT melt_serial_id FROM rc)"
+ );
+ break;
+ default:
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ ldctx.ldc = ldc;
+ ldctx.ldc_cls = ldc_cls;
+ ldctx.status = GNUNET_OK;
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ query,
+ params,
+ &add_ldl,
+ &ldctx);
+ if (GNUNET_OK != ldctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_link_data.h b/src/exchangedb/pg_get_link_data.h
new file mode 100644
index 000000000..09d3a69fc
--- /dev/null
+++ b/src/exchangedb/pg_get_link_data.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_link_data.h
+ * @brief implementation of the get_link_data function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_LINK_DATA_H
+#define PG_GET_LINK_DATA_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Obtain the link data of a coin, that is the encrypted link
+ * information, the denomination keys and the signatures.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param coin_pub public key of the coin
+ * @param ldc function to call for each session the coin was melted into
+ * @param ldc_cls closure for @a tdc
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_link_data (void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ TALER_EXCHANGEDB_LinkCallback ldc,
+ void *ldc_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_melt.c b/src/exchangedb/pg_get_melt.c
new file mode 100644
index 000000000..2221054ba
--- /dev/null
+++ b/src/exchangedb/pg_get_melt.c
@@ -0,0 +1,124 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_melt.c
+ * @brief Implementation of the get_melt function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_melt.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_melt (void *cls,
+ const struct TALER_RefreshCommitmentP *rc,
+ struct TALER_EXCHANGEDB_Melt *melt,
+ uint64_t *melt_serial_id)
+{
+ struct PostgresClosure *pg = cls;
+ bool h_age_commitment_is_null;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (rc),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &melt->session.coin.
+ denom_pub_hash),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+ &melt->melt_fee),
+ GNUNET_PQ_result_spec_uint32 ("noreveal_index",
+ &melt->session.noreveal_index),
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
+ &melt->session.coin.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
+ &melt->session.coin_sig),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &melt->session.coin.h_age_commitment),
+ &h_age_commitment_is_null),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &melt->session.amount_with_fee),
+ GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
+ melt_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ memset (&melt->session.coin.denom_sig,
+ 0,
+ sizeof (melt->session.coin.denom_sig));
+
+ /* Used in #postgres_get_melt() to fetch
+ high-level information about a melt operation */
+ PREPARE (pg,
+ "get_melt",
+ /* "SELECT"
+ " denoms.denom_pub_hash"
+ ",denoms.fee_refresh"
+ ",old_coin_pub"
+ ",old_coin_sig"
+ ",kc.age_commitment_hash"
+ ",amount_with_fee"
+ ",noreveal_index"
+ ",melt_serial_id"
+ " FROM refresh_commitments"
+ " JOIN known_coins kc"
+ " ON (old_coin_pub = kc.coin_pub)"
+ " JOIN denominations denoms"
+ " ON (kc.denominations_serial = denoms.denominations_serial)"
+ " WHERE rc=$1;", */
+ "WITH rc AS MATERIALIZED ( "
+ " SELECT"
+ " * FROM refresh_commitments"
+ " WHERE rc=$1"
+ ")"
+ "SELECT"
+ " denoms.denom_pub_hash"
+ ",denoms.fee_refresh"
+ ",rc.old_coin_pub"
+ ",rc.old_coin_sig"
+ ",kc.age_commitment_hash"
+ ",amount_with_fee"
+ ",noreveal_index"
+ ",melt_serial_id "
+ "FROM ("
+ " SELECT"
+ " * "
+ " FROM known_coins"
+ " WHERE coin_pub=(SELECT old_coin_pub from rc)"
+ ") kc "
+ "JOIN rc"
+ " ON (kc.coin_pub=rc.old_coin_pub) "
+ "JOIN denominations denoms"
+ " USING (denominations_serial);");
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_melt",
+ params,
+ rs);
+ if (h_age_commitment_is_null)
+ memset (&melt->session.coin.h_age_commitment,
+ 0,
+ sizeof(melt->session.coin.h_age_commitment));
+
+ melt->session.rc = *rc;
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_melt.h b/src/exchangedb/pg_get_melt.h
new file mode 100644
index 000000000..269960bad
--- /dev/null
+++ b/src/exchangedb/pg_get_melt.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_melt.h
+ * @brief implementation of the get_melt function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_MELT_H
+#define PG_GET_MELT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Lookup refresh melt commitment data under the given @a rc.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param rc commitment hash to use to locate the operation
+ * @param[out] melt where to store the result; note that
+ * melt->session.coin.denom_sig will be set to NULL
+ * and is not fetched by this routine (as it is not needed by the client)
+ * @param[out] melt_serial_id set to the row ID of @a rc in the refresh_commitments table
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_melt (void *cls,
+ const struct TALER_RefreshCommitmentP *rc,
+ struct TALER_EXCHANGEDB_Melt *melt,
+ uint64_t *melt_serial_id);
+
+#endif
diff --git a/src/exchangedb/pg_get_old_coin_by_h_blind.c b/src/exchangedb/pg_get_old_coin_by_h_blind.c
new file mode 100644
index 000000000..dcce7b32f
--- /dev/null
+++ b/src/exchangedb/pg_get_old_coin_by_h_blind.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_old_coin_by_h_blind.c
+ * @brief Implementation of the get_old_coin_by_h_blind function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_old_coin_by_h_blind.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_old_coin_by_h_blind (
+ void *cls,
+ const struct TALER_BlindedCoinHashP *h_blind_ev,
+ struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ uint64_t *rrc_serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_blind_ev),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
+ old_coin_pub),
+ GNUNET_PQ_result_spec_uint64 ("rrc_serial",
+ rrc_serial),
+ GNUNET_PQ_result_spec_end
+ };
+
+ /* Used in #postgres_get_old_coin_by_h_blind() */
+ PREPARE (pg,
+ "old_coin_by_h_blind",
+ "SELECT"
+ " okc.coin_pub AS old_coin_pub"
+ ",rrc_serial"
+ " FROM refresh_revealed_coins rrc"
+ " JOIN refresh_commitments rcom USING (melt_serial_id)"
+ " JOIN known_coins okc ON (rcom.old_coin_pub = okc.coin_pub)"
+ " WHERE h_coin_ev=$1"
+ " LIMIT 1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "old_coin_by_h_blind",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_old_coin_by_h_blind.h b/src/exchangedb/pg_get_old_coin_by_h_blind.h
new file mode 100644
index 000000000..93ed541b6
--- /dev/null
+++ b/src/exchangedb/pg_get_old_coin_by_h_blind.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_old_coin_by_h_blind.h
+ * @brief implementation of the get_old_coin_by_h_blind function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_OLD_COIN_BY_H_BLIND_H
+#define PG_GET_OLD_COIN_BY_H_BLIND_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain information about which old coin a coin was refreshed
+ * given the hash of the blinded (fresh) coin.
+ *
+ * @param cls closure
+ * @param h_blind_ev hash of the blinded coin
+ * @param[out] old_coin_pub set to information about the old coin (on success only)
+ * @param[out] rrc_serial set to serial number of the entry in the database
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_old_coin_by_h_blind (
+ void *cls,
+ const struct TALER_BlindedCoinHashP *h_blind_ev,
+ struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ uint64_t *rrc_serial);
+
+#endif
diff --git a/src/exchangedb/pg_get_pending_kyc_requirement_process.c b/src/exchangedb/pg_get_pending_kyc_requirement_process.c
new file mode 100644
index 000000000..b9acddad1
--- /dev/null
+++ b/src/exchangedb/pg_get_pending_kyc_requirement_process.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_pending_kyc_requirement_process.c
+ * @brief Implementation of the get_pending_kyc_requirement_process function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_pending_kyc_requirement_process.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_pending_kyc_requirement_process (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ char **redirect_url)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (provider_section),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("redirect_url",
+ redirect_url),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ *redirect_url = NULL;
+ PREPARE (pg,
+ "get_pending_kyc_requirement_process",
+ "SELECT"
+ " redirect_url"
+ " FROM legitimization_processes"
+ " WHERE provider_section=$1"
+ " AND h_payto=$2"
+ " AND NOT finished"
+ " ORDER BY start_time DESC"
+ " LIMIT 1");
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "get_pending_kyc_requirement_process",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_pending_kyc_requirement_process.h b/src/exchangedb/pg_get_pending_kyc_requirement_process.h
new file mode 100644
index 000000000..738c4d65b
--- /dev/null
+++ b/src/exchangedb/pg_get_pending_kyc_requirement_process.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 exchangedb/pg_get_pending_kyc_requirement_process.h
+ * @brief implementation of the get_pending_kyc_requirement_process function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_PENDING_KYC_REQUIREMENT_PROCESS_H
+#define PG_GET_PENDING_KYC_REQUIREMENT_PROCESS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Fetch information about pending KYC requirement process.
+ *
+ * @param cls closure
+ * @param h_payto account that must be KYC'ed
+ * @param provider_section provider that must be checked
+ * @param[out] redirect_url set to redirect URL for the process
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_pending_kyc_requirement_process (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ char **redirect_url);
+
+#endif
diff --git a/src/exchangedb/pg_get_policy_details.c b/src/exchangedb/pg_get_policy_details.c
new file mode 100644
index 000000000..6e1b5c5dc
--- /dev/null
+++ b/src/exchangedb/pg_get_policy_details.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_policy_details.c
+ * @brief Implementation of the get_policy_details function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_policy_details.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_policy_details (
+ void *cls,
+ const struct GNUNET_HashCode *hc,
+ struct TALER_PolicyDetails *details)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (hc),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("deadline",
+ &details->deadline),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("commitment",
+ &details->commitment),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("accumulated_total",
+ &details->accumulated_total),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("policy_fee",
+ &details->policy_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("transferable_amount",
+ &details->transferable_amount),
+ GNUNET_PQ_result_spec_auto_from_type ("state",
+ &details->fulfillment_state),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("policy_fulfillment_id",
+ &details->policy_fulfillment_id),
+ &details->no_policy_fulfillment_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_policy_details",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_policy_details.h b/src/exchangedb/pg_get_policy_details.h
new file mode 100644
index 000000000..e3d2b0a2c
--- /dev/null
+++ b/src/exchangedb/pg_get_policy_details.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_policy_details.h
+ * @brief implementation of the get_policy_details function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_POLICY_DETAILS_H
+#define PG_GET_POLICY_DETAILS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/* Get the details of a policy, referenced by its hash code
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param hc The hash code under which the details to a particular policy should be found
+ * @param[out] details The found details
+ * @return query execution status
+ * */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_policy_details (
+ void *cls,
+ const struct GNUNET_HashCode *hc,
+ struct TALER_PolicyDetails *details);
+
+#endif
diff --git a/src/exchangedb/pg_get_purse_deposit.c b/src/exchangedb/pg_get_purse_deposit.c
new file mode 100644
index 000000000..cb24855a1
--- /dev/null
+++ b/src/exchangedb/pg_get_purse_deposit.c
@@ -0,0 +1,84 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_purse_deposit.c
+ * @brief Implementation of the get_purse_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_purse_deposit.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_purse_deposit (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_Amount *amount,
+ struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_AgeCommitmentHash *phac,
+ struct TALER_CoinSpendSignatureP *coin_sig,
+ char **partner_url)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ bool is_null;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ h_denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ phac),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ amount),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("partner_base_url",
+ partner_url),
+ &is_null),
+ GNUNET_PQ_result_spec_end
+ };
+
+ *partner_url = NULL;
+ PREPARE (pg,
+ "select_purse_deposit_by_coin_pub",
+ "SELECT "
+ " coin_sig"
+ ",amount_with_fee"
+ ",denom_pub_hash"
+ ",age_commitment_hash"
+ ",partner_base_url"
+ " FROM purse_deposits"
+ " LEFT JOIN partners"
+ " USING (partner_serial_id)"
+ " JOIN known_coins kc"
+ " USING (coin_pub)"
+ " JOIN denominations"
+ " USING (denominations_serial)"
+ " WHERE purse_pub=$1"
+ " AND coin_pub=$2;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_purse_deposit_by_coin_pub",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_purse_deposit.h b/src/exchangedb/pg_get_purse_deposit.h
new file mode 100644
index 000000000..b9c9947f4
--- /dev/null
+++ b/src/exchangedb/pg_get_purse_deposit.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_purse_deposit.h
+ * @brief implementation of the get_purse_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_PURSE_DEPOSIT_H
+#define PG_GET_PURSE_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to obtain a coin deposit data from
+ * depositing the coin into a purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to credit
+ * @param coin_pub coin to deposit (debit)
+ * @param[out] amount set fraction of the coin's value that was deposited (with fee)
+ * @param[out] h_denom_pub set to hash of denomination of the coin
+ * @param[out] phac set to hash of age restriction on the coin
+ * @param[out] coin_sig set to signature affirming the operation
+ * @param[out] partner_url set to the URL of the partner exchange, or NULL for ourselves, must be freed by caller
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_purse_deposit (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_Amount *amount,
+ struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_AgeCommitmentHash *phac,
+ struct TALER_CoinSpendSignatureP *coin_sig,
+ char **partner_url);
+
+#endif
diff --git a/src/exchangedb/pg_get_purse_request.c b/src/exchangedb/pg_get_purse_request.c
new file mode 100644
index 000000000..9d2ee5654
--- /dev/null
+++ b/src/exchangedb/pg_get_purse_request.c
@@ -0,0 +1,79 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_template.c
+ * @brief Implementation of the template function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_purse_request.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_purse_request (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t *age_limit,
+ struct TALER_Amount *target_amount,
+ struct TALER_Amount *balance,
+ struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merge_pub",
+ merge_pub),
+ GNUNET_PQ_result_spec_timestamp ("purse_expiration",
+ purse_expiration),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ h_contract_terms),
+ GNUNET_PQ_result_spec_uint32 ("age_limit",
+ age_limit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ target_amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ balance),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_sig",
+ purse_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_purse_request",
+ "SELECT "
+ " merge_pub"
+ ",purse_expiration"
+ ",h_contract_terms"
+ ",age_limit"
+ ",amount_with_fee"
+ ",balance"
+ ",purse_sig"
+ " FROM purse_requests"
+ " WHERE purse_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_purse_request",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_purse_request.h b/src/exchangedb/pg_get_purse_request.h
new file mode 100644
index 000000000..24620e1b8
--- /dev/null
+++ b/src/exchangedb/pg_get_purse_request.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+
+
+/**
+ * @file exchangedb/pg_get_purse_request.h
+ * @brief implementation of the get_purse_request function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_PURSE_REQUEST_H
+#define PG_GET_PURSE_REQUEST_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to return meta data about a purse by the
+ * purse public key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the purse
+ * @param[out] merge_pub public key representing the merge capability
+ * @param[out] purse_expiration when would an unmerged purse expire
+ * @param[out] h_contract_terms contract associated with the purse
+ * @param[out] age_limit the age limit for deposits into the purse
+ * @param[out] target_amount amount to be put into the purse
+ * @param[out] balance amount put so far into the purse
+ * @param[out] purse_sig signature of the purse over the initialization data
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_purse_request (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t *age_limit,
+ struct TALER_Amount *target_amount,
+ struct TALER_Amount *balance,
+ struct TALER_PurseContractSignatureP *purse_sig);
+
+#endif
diff --git a/src/exchangedb/pg_get_ready_deposit.c b/src/exchangedb/pg_get_ready_deposit.c
new file mode 100644
index 000000000..d8344faf1
--- /dev/null
+++ b/src/exchangedb/pg_get_ready_deposit.c
@@ -0,0 +1,74 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_get_ready_deposit.c
+ * @brief Implementation of the get_ready_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_ready_deposit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_ready_deposit (void *cls,
+ uint64_t start_shard_row,
+ uint64_t end_shard_row,
+ struct TALER_MerchantPublicKeyP *merchant_pub,
+ char **payto_uri)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now
+ = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_uint64 (&start_shard_row),
+ GNUNET_PQ_query_param_uint64 (&end_shard_row),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ merchant_pub),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ payto_uri),
+ GNUNET_PQ_result_spec_end
+ };
+ const char *query = "deposits_get_ready";
+
+ PREPARE (pg,
+ query,
+ "SELECT"
+ " wts.payto_uri"
+ ",bdep.merchant_pub"
+ " FROM batch_deposits bdep"
+ " JOIN wire_targets wts"
+ " USING (wire_target_h_payto)"
+ " WHERE NOT (bdep.done OR bdep.policy_blocked)"
+ " AND bdep.wire_deadline<=$1"
+ " AND bdep.shard >= $2"
+ " AND bdep.shard <= $3"
+ " ORDER BY "
+ " bdep.wire_deadline ASC"
+ " ,bdep.shard ASC"
+ " LIMIT 1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ query,
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_ready_deposit.h b/src/exchangedb/pg_get_ready_deposit.h
new file mode 100644
index 000000000..b1dd7a968
--- /dev/null
+++ b/src/exchangedb/pg_get_ready_deposit.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_ready_deposit.h
+ * @brief implementation of the get_ready_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_READY_DEPOSIT_H
+#define PG_GET_READY_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Obtain information about deposits that are ready to be executed. Such
+ * deposits must not be marked as "done", the execution time must be
+ * in the past, and the KYC status must be 'ok'.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param start_shard_row minimum shard row to select
+ * @param end_shard_row maximum shard row to select (inclusive)
+ * @param[out] merchant_pub set to the public key of a merchant with a ready deposit
+ * @param[out] payto_uri set to the account of the merchant, to be freed by caller
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_ready_deposit (void *cls,
+ uint64_t start_shard_row,
+ uint64_t end_shard_row,
+ struct TALER_MerchantPublicKeyP *merchant_pub,
+ char **payto_uri);
+
+#endif
diff --git a/src/exchangedb/pg_get_refresh_reveal.c b/src/exchangedb/pg_get_refresh_reveal.c
new file mode 100644
index 000000000..08d4b21a5
--- /dev/null
+++ b/src/exchangedb/pg_get_refresh_reveal.c
@@ -0,0 +1,213 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_refresh_reveal.c
+ * @brief Implementation of the get_refresh_reveal function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_refresh_reveal.h"
+#include "pg_helper.h"
+
+
+/**
+ * Context where we aggregate data from the database.
+ * Closure for #add_revealed_coins().
+ */
+struct GetRevealContext
+{
+ /**
+ * Array of revealed coins we obtained from the DB.
+ */
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs;
+
+ /**
+ * Length of the @a rrcs array.
+ */
+ unsigned int rrcs_len;
+
+ /**
+ * Set to an error code if we ran into trouble.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct GetRevealContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_revealed_coins (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GetRevealContext *grctx = cls;
+
+ if (0 == num_results)
+ return;
+ grctx->rrcs = GNUNET_new_array (num_results,
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin);
+ grctx->rrcs_len = num_results;
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint32_t off;
+ struct GNUNET_PQ_ResultSpec rso[] = {
+ GNUNET_PQ_result_spec_uint32 ("freshcoin_index",
+ &off),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rso,
+ i))
+ {
+ GNUNET_break (0);
+ grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ if (off >= num_results)
+ {
+ GNUNET_break (0);
+ grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ {
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &grctx->rrcs[off];
+ struct GNUNET_PQ_ResultSpec rsi[] = {
+ /* NOTE: freshcoin_index selected and discarded here... */
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &rrc->h_denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("link_sig",
+ &rrc->orig_coin_link_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("h_coin_ev",
+ &rrc->coin_envelope_hash),
+ TALER_PQ_result_spec_blinded_planchet ("coin_ev",
+ &rrc->blinded_planchet),
+ TALER_PQ_result_spec_exchange_withdraw_values ("ewv",
+ &rrc->exchange_vals),
+ TALER_PQ_result_spec_blinded_denom_sig ("ev_sig",
+ &rrc->coin_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (NULL !=
+ rrc->blinded_planchet.blinded_message)
+ {
+ /* duplicate offset, not allowed */
+ GNUNET_break (0);
+ grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rsi,
+ i))
+ {
+ GNUNET_break (0);
+ grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ }
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_refresh_reveal (void *cls,
+ const struct TALER_RefreshCommitmentP *rc,
+ TALER_EXCHANGEDB_RefreshCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GetRevealContext grctx;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (rc),
+ GNUNET_PQ_query_param_end
+ };
+
+ memset (&grctx,
+ 0,
+ sizeof (grctx));
+
+ /* Obtain information about the coins created in a refresh
+ operation, used in #postgres_get_refresh_reveal() */
+ PREPARE (pg,
+ "get_refresh_revealed_coins",
+ "SELECT "
+ " rrc.freshcoin_index"
+ ",denom.denom_pub_hash"
+ ",rrc.h_coin_ev"
+ ",rrc.link_sig"
+ ",rrc.coin_ev"
+ ",rrc.ewv"
+ ",rrc.ev_sig"
+ " FROM refresh_commitments"
+ " JOIN refresh_revealed_coins rrc"
+ " USING (melt_serial_id)"
+ " JOIN denominations denom "
+ " USING (denominations_serial)"
+ " WHERE rc=$1;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_refresh_revealed_coins",
+ params,
+ &add_revealed_coins,
+ &grctx);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ goto cleanup;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ default: /* can have more than one result */
+ break;
+ }
+ switch (grctx.qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto cleanup;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* should be impossible */
+ break;
+ }
+
+ /* Pass result back to application */
+ cb (cb_cls,
+ grctx.rrcs_len,
+ grctx.rrcs);
+cleanup:
+ for (unsigned int i = 0; i < grctx.rrcs_len; i++)
+ {
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &grctx.rrcs[i];
+
+ TALER_blinded_denom_sig_free (&rrc->coin_sig);
+ TALER_blinded_planchet_free (&rrc->blinded_planchet);
+ TALER_denom_ewv_free (&rrc->exchange_vals);
+ }
+ GNUNET_free (grctx.rrcs);
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_refresh_reveal.h b/src/exchangedb/pg_get_refresh_reveal.h
new file mode 100644
index 000000000..15b57b343
--- /dev/null
+++ b/src/exchangedb/pg_get_refresh_reveal.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_refresh_reveal.h
+ * @brief implementation of the get_refresh_reveal function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_REFRESH_REVEAL_H
+#define PG_GET_REFRESH_REVEAL_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Lookup in the database the coins that we want to
+ * create in the given refresh operation.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param rc identify commitment and thus refresh operation
+ * @param cb function to call with the results
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_refresh_reveal (void *cls,
+ const struct TALER_RefreshCommitmentP *rc,
+ TALER_EXCHANGEDB_RefreshCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_reserve_balance.c b/src/exchangedb/pg_get_reserve_balance.c
new file mode 100644
index 000000000..140bf3b70
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_balance.c
@@ -0,0 +1,55 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_reserve_balance.c
+ * @brief Implementation of the get_reserve_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_reserve_balance.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_balance (void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_Amount *balance)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount ("current_balance",
+ pg->currency,
+ balance),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_reserve_balance",
+ "SELECT"
+ " current_balance"
+ " FROM reserves"
+ " WHERE reserve_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_reserve_balance",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_reserve_balance.h b/src/exchangedb/pg_get_reserve_balance.h
new file mode 100644
index 000000000..6dc88d906
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_balance.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_reserve_balance.h
+ * @brief implementation of the get_reserve_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_RESERVE_BALANCE_H
+#define PG_GET_RESERVE_BALANCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Get the balance of the specified reserve.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @param[out] balance set to the reserve balance
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_balance (void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_Amount *balance);
+
+#endif
diff --git a/src/exchangedb/pg_get_reserve_by_h_blind.c b/src/exchangedb/pg_get_reserve_by_h_blind.c
new file mode 100644
index 000000000..f87fe6cd4
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_by_h_blind.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_reserve_by_h_blind.c
+ * @brief Implementation of the get_reserve_by_h_blind function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_reserve_by_h_blind.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_by_h_blind (
+ void *cls,
+ const struct TALER_BlindedCoinHashP *bch,
+ struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *reserve_out_serial_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (bch),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ reserve_pub),
+ GNUNET_PQ_result_spec_uint64 ("reserve_out_serial_id",
+ reserve_out_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_get_reserve_by_h_blind() */
+ PREPARE (pg,
+ "reserve_by_h_blind",
+ "SELECT"
+ " reserves.reserve_pub"
+ ",reserve_out_serial_id"
+ " FROM reserves_out"
+ " JOIN reserves"
+ " USING (reserve_uuid)"
+ " WHERE h_blind_ev=$1"
+ " LIMIT 1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "reserve_by_h_blind",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_reserve_by_h_blind.h b/src/exchangedb/pg_get_reserve_by_h_blind.h
new file mode 100644
index 000000000..49c1c8403
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_by_h_blind.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_reserve_by_h_blind.h
+ * @brief implementation of the get_reserve_by_h_blind function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_RESERVE_BY_H_BLIND_H
+#define PG_GET_RESERVE_BY_H_BLIND_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Obtain information about which reserve a coin was generated
+ * from given the hash of the blinded coin.
+ *
+ * @param cls closure
+ * @param bch hash that uniquely identifies the withdraw request
+ * @param[out] reserve_pub set to information about the reserve (on success only)
+ * @param[out] reserve_out_serial_id set to row of the @a h_blind_ev in reserves_out
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_by_h_blind (
+ void *cls,
+ const struct TALER_BlindedCoinHashP *bch,
+ struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *reserve_out_serial_id);
+
+#endif
diff --git a/src/exchangedb/pg_get_reserve_history.c b/src/exchangedb/pg_get_reserve_history.c
new file mode 100644
index 000000000..1f1ca95b5
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_history.c
@@ -0,0 +1,936 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 pg_get_reserve_history.c
+ * @brief Obtain (parts of) the history of a reserve.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_reserve_history.h"
+#include "pg_start_read_committed.h"
+#include "pg_commit.h"
+#include "pg_rollback.h"
+#include "plugin_exchangedb_common.h"
+#include "pg_helper.h"
+
+/**
+ * How often do we re-try when encountering DB serialization issues?
+ * (We are read-only, so can only happen due to concurrent insert,
+ * which should be very rare.)
+ */
+#define RETRIES 3
+
+
+/**
+ * Closure for callbacks invoked via #TEH_PG_get_reserve_history().
+ */
+struct ReserveHistoryContext
+{
+
+ /**
+ * Which reserve are we building the history for?
+ */
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+
+ /**
+ * Where we build the history.
+ */
+ struct TALER_EXCHANGEDB_ReserveHistory *rh;
+
+ /**
+ * Tail of @e rh list.
+ */
+ struct TALER_EXCHANGEDB_ReserveHistory *rh_tail;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Sum of all credit transactions.
+ */
+ struct TALER_Amount balance_in;
+
+ /**
+ * Sum of all debit transactions.
+ */
+ struct TALER_Amount balance_out;
+
+ /**
+ * Set to true on serious internal errors during
+ * the callbacks.
+ */
+ bool failed;
+};
+
+
+/**
+ * Append and return a fresh element to the reserve
+ * history kept in @a rhc.
+ *
+ * @param rhc where the history is kept
+ * @return the fresh element that was added
+ */
+static struct TALER_EXCHANGEDB_ReserveHistory *
+append_rh (struct ReserveHistoryContext *rhc)
+{
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ tail = GNUNET_new (struct TALER_EXCHANGEDB_ReserveHistory);
+ if (NULL != rhc->rh_tail)
+ {
+ rhc->rh_tail->next = tail;
+ rhc->rh_tail = tail;
+ }
+ else
+ {
+ rhc->rh_tail = tail;
+ rhc->rh = tail;
+ }
+ return tail;
+}
+
+
+/**
+ * Add bank transfers to result set for #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_bank_to_exchange (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+ struct PostgresClosure *pg = rhc->pg;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_BankTransfer *bt;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ bt = GNUNET_new (struct TALER_EXCHANGEDB_BankTransfer);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("wire_reference",
+ &bt->wire_reference),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
+ &bt->amount),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &bt->execution_date),
+ GNUNET_PQ_result_spec_string ("sender_account_details",
+ &bt->sender_account_details),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (bt);
+ rhc->failed = true;
+ return;
+ }
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_in,
+ &rhc->balance_in,
+ &bt->amount));
+ bt->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE;
+ tail->details.bank = bt;
+ } /* end of 'while (0 < rows)' */
+}
+
+
+/**
+ * Add coin withdrawals to result set for #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_withdraw_coin (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+ struct PostgresClosure *pg = rhc->pg;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_CollectableBlindcoin *cbc;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ cbc = GNUNET_new (struct TALER_EXCHANGEDB_CollectableBlindcoin);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
+ &cbc->h_coin_envelope),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &cbc->denom_pub_hash),
+ TALER_PQ_result_spec_blinded_denom_sig ("denom_sig",
+ &cbc->sig),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &cbc->reserve_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &cbc->amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+ &cbc->withdraw_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (cbc);
+ rhc->failed = true;
+ return;
+ }
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_out,
+ &rhc->balance_out,
+ &cbc->amount_with_fee));
+ cbc->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COIN;
+ tail->details.withdraw = cbc;
+ }
+}
+
+
+/**
+ * Add recoups to result set for #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_recoup (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+ struct PostgresClosure *pg = rhc->pg;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_Recoup *recoup;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ recoup = GNUNET_new (struct TALER_EXCHANGEDB_Recoup);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &recoup->value),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &recoup->coin.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &recoup->coin_blind),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &recoup->coin_sig),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &recoup->timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &recoup->coin.denom_pub_hash),
+ TALER_PQ_result_spec_denom_sig (
+ "denom_sig",
+ &recoup->coin.denom_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (recoup);
+ rhc->failed = true;
+ return;
+ }
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_in,
+ &rhc->balance_in,
+ &recoup->value));
+ recoup->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN;
+ tail->details.recoup = recoup;
+ } /* end of 'while (0 < rows)' */
+}
+
+
+/**
+ * Add exchange-to-bank transfers to result set for
+ * #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_exchange_to_bank (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+ struct PostgresClosure *pg = rhc->pg;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_ClosingTransfer *closing;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ closing = GNUNET_new (struct TALER_EXCHANGEDB_ClosingTransfer);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &closing->amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
+ &closing->closing_fee),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &closing->execution_date),
+ GNUNET_PQ_result_spec_string ("receiver_account",
+ &closing->receiver_account_details),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid",
+ &closing->wtid),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (closing);
+ rhc->failed = true;
+ return;
+ }
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_out,
+ &rhc->balance_out,
+ &closing->amount));
+ closing->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK;
+ tail->details.closing = closing;
+ } /* end of 'while (0 < rows)' */
+}
+
+
+/**
+ * Add purse merge transfers to result set for
+ * #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_p2p_merge (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+ struct PostgresClosure *pg = rhc->pg;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_PurseMerge *merge;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ merge = GNUNET_new (struct TALER_EXCHANGEDB_PurseMerge);
+ {
+ uint32_t flags32;
+ struct TALER_Amount balance;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
+ &merge->purse_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ &balance),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &merge->amount_with_fee),
+ GNUNET_PQ_result_spec_timestamp ("merge_timestamp",
+ &merge->merge_timestamp),
+ GNUNET_PQ_result_spec_timestamp ("purse_expiration",
+ &merge->purse_expiration),
+ GNUNET_PQ_result_spec_uint32 ("age_limit",
+ &merge->min_age),
+ GNUNET_PQ_result_spec_uint32 ("flags",
+ &flags32),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &merge->h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("merge_pub",
+ &merge->merge_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &merge->purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &merge->reserve_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (merge);
+ rhc->failed = true;
+ return;
+ }
+ merge->flags = (enum TALER_WalletAccountMergeFlags) flags32;
+ if ( (! GNUNET_TIME_absolute_is_future (
+ merge->merge_timestamp.abs_time)) &&
+ (-1 != TALER_amount_cmp (&balance,
+ &merge->amount_with_fee)) )
+ merge->merged = true;
+ }
+ if (merge->merged)
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_in,
+ &rhc->balance_in,
+ &merge->amount_with_fee));
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_out,
+ &rhc->balance_out,
+ &merge->purse_fee));
+ merge->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_PURSE_MERGE;
+ tail->details.merge = merge;
+ }
+}
+
+
+/**
+ * Add paid for history requests to result set for
+ * #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_open_requests (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+ struct PostgresClosure *pg = rhc->pg;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_OpenRequest *orq;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ orq = GNUNET_new (struct TALER_EXCHANGEDB_OpenRequest);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("open_fee",
+ &orq->open_fee),
+ GNUNET_PQ_result_spec_timestamp ("request_timestamp",
+ &orq->request_timestamp),
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ &orq->reserve_expiration),
+ GNUNET_PQ_result_spec_uint32 ("requested_purse_limit",
+ &orq->purse_limit),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &orq->reserve_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (orq);
+ rhc->failed = true;
+ return;
+ }
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_out,
+ &rhc->balance_out,
+ &orq->open_fee));
+ orq->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_OPEN_REQUEST;
+ tail->details.open_request = orq;
+ }
+}
+
+
+/**
+ * Add paid for history requests to result set for
+ * #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_close_requests (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_CloseRequest *crq;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ crq = GNUNET_new (struct TALER_EXCHANGEDB_CloseRequest);
+ {
+ char *payto_uri;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("close_timestamp",
+ &crq->request_timestamp),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &crq->reserve_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (crq);
+ rhc->failed = true;
+ return;
+ }
+ TALER_payto_hash (payto_uri,
+ &crq->target_account_h_payto);
+ GNUNET_free (payto_uri);
+ }
+ crq->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_CLOSE_REQUEST;
+ tail->details.close_request = crq;
+ }
+}
+
+
+/**
+ * Add reserve history entries found.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+handle_history_entry (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ static const struct
+ {
+ /**
+ * Table with reserve history entry we are responsible for.
+ */
+ const char *table;
+ /**
+ * Name of the prepared statement to run.
+ */
+ const char *statement;
+ /**
+ * Function to use to process the results.
+ */
+ GNUNET_PQ_PostgresResultHandler cb;
+ } work[] = {
+ /** #TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE */
+ { "reserves_in",
+ "reserves_in_get_transactions",
+ add_bank_to_exchange },
+ /** #TALER_EXCHANGEDB_RO_WITHDRAW_COIN */
+ { "reserves_out",
+ "get_reserves_out",
+ &add_withdraw_coin },
+ /** #TALER_EXCHANGEDB_RO_RECOUP_COIN */
+ { "recoup",
+ "recoup_by_reserve",
+ &add_recoup },
+ /** #TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK */
+ { "reserves_close",
+ "close_by_reserve",
+ &add_exchange_to_bank },
+ /** #TALER_EXCHANGEDB_RO_PURSE_MERGE */
+ { "purse_decision",
+ "merge_by_reserve",
+ &add_p2p_merge },
+ /** #TALER_EXCHANGEDB_RO_OPEN_REQUEST */
+ { "reserves_open_requests",
+ "open_request_by_reserve",
+ &add_open_requests },
+ /** #TALER_EXCHANGEDB_RO_CLOSE_REQUEST */
+ { "close_requests",
+ "close_request_by_reserve",
+ &add_close_requests },
+ /* List terminator */
+ { NULL, NULL, NULL }
+ };
+ struct ReserveHistoryContext *rhc = cls;
+ char *table_name;
+ uint64_t serial_id;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("table_name",
+ &table_name),
+ GNUNET_PQ_result_spec_uint64 ("serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (rhc->reserve_pub),
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ while (0 < num_results--)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ bool found = false;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ num_results))
+ {
+ GNUNET_break (0);
+ rhc->failed = true;
+ return;
+ }
+
+ for (unsigned int i = 0;
+ NULL != work[i].cb;
+ i++)
+ {
+ if (0 != strcmp (table_name,
+ work[i].table))
+ continue;
+ found = true;
+ qs = GNUNET_PQ_eval_prepared_multi_select (rhc->pg->conn,
+ work[i].statement,
+ params,
+ work[i].cb,
+ rhc);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Reserve %s had %d transactions at %llu in table %s\n",
+ TALER_B2S (rhc->reserve_pub),
+ (int) qs,
+ (unsigned long long) serial_id,
+ table_name);
+ if (0 >= qs)
+ rhc->failed = true;
+ break;
+ }
+ if (! found)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Coin history includes unsupported table `%s`\n",
+ table_name);
+ rhc->failed = true;
+ }
+ GNUNET_PQ_cleanup_result (rs);
+ if (rhc->failed)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_history (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t start_off,
+ uint64_t etag_in,
+ uint64_t *etag_out,
+ struct TALER_Amount *balance,
+ struct TALER_EXCHANGEDB_ReserveHistory **rhp)
+{
+ struct PostgresClosure *pg = cls;
+ struct ReserveHistoryContext rhc = {
+ .pg = pg,
+ .reserve_pub = reserve_pub
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_QueryParam lparams[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_uint64 (&start_off),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ &rhc.balance_in));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ &rhc.balance_out));
+
+ *rhp = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Getting transactions for reserve %s\n",
+ TALER_B2S (reserve_pub));
+ PREPARE (pg,
+ "get_reserve_history_etag",
+ "SELECT"
+ " hist.reserve_history_serial_id"
+ ",r.current_balance"
+ " FROM reserve_history hist"
+ " JOIN reserves r USING (reserve_pub)"
+ " WHERE hist.reserve_pub=$1"
+ " ORDER BY reserve_history_serial_id DESC"
+ " LIMIT 1;");
+ PREPARE (pg,
+ "get_reserve_history",
+ "SELECT"
+ " table_name"
+ ",serial_id"
+ " FROM reserve_history"
+ " WHERE reserve_pub=$1"
+ " AND reserve_history_serial_id > $2"
+ " ORDER BY reserve_history_serial_id DESC;");
+
+ PREPARE (pg,
+ "reserves_in_get_transactions",
+ "SELECT"
+ " ri.wire_reference"
+ ",ri.credit"
+ ",ri.execution_date"
+ ",wt.payto_uri AS sender_account_details"
+ " FROM reserves_in ri"
+ " JOIN wire_targets wt"
+ " ON (wire_source_h_payto = wire_target_h_payto)"
+ " WHERE ri.reserve_pub=$1"
+ " AND ri.reserve_in_serial_id=$2;");
+ PREPARE (pg,
+ "get_reserves_out",
+ "SELECT"
+ " ro.h_blind_ev"
+ ",denom.denom_pub_hash"
+ ",ro.denom_sig"
+ ",ro.reserve_sig"
+ ",ro.execution_date"
+ ",ro.amount_with_fee"
+ ",denom.fee_withdraw"
+ " FROM reserves_out ro"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " JOIN reserves res"
+ " USING (reserve_uuid)"
+ " WHERE ro.reserve_out_serial_id=$2"
+ " AND res.reserve_pub=$1;");
+ PREPARE (pg,
+ "recoup_by_reserve",
+ "SELECT"
+ " rec.coin_pub"
+ ",rec.coin_sig"
+ ",rec.coin_blind"
+ ",rec.amount"
+ ",rec.recoup_timestamp"
+ ",denom.denom_pub_hash"
+ ",kc.denom_sig"
+ " FROM recoup rec"
+ " JOIN reserves_out ro"
+ " USING (reserve_out_serial_id)"
+ " JOIN reserves res"
+ " USING (reserve_uuid)"
+ " JOIN known_coins kc"
+ " USING (coin_pub)"
+ " JOIN denominations denom"
+ " ON (denom.denominations_serial = kc.denominations_serial)"
+ " WHERE rec.recoup_uuid=$2"
+ " AND res.reserve_pub=$1;");
+ PREPARE (pg,
+ "close_by_reserve",
+ "SELECT"
+ " rc.amount"
+ ",rc.closing_fee"
+ ",rc.execution_date"
+ ",wt.payto_uri AS receiver_account"
+ ",rc.wtid"
+ " FROM reserves_close rc"
+ " JOIN wire_targets wt"
+ " USING (wire_target_h_payto)"
+ " WHERE reserve_pub=$1"
+ " AND close_uuid=$2;");
+ PREPARE (pg,
+ "merge_by_reserve",
+ "SELECT"
+ " pr.amount_with_fee"
+ ",pr.balance"
+ ",pr.purse_fee"
+ ",pr.h_contract_terms"
+ ",pr.merge_pub"
+ ",am.reserve_sig"
+ ",pm.purse_pub"
+ ",pm.merge_timestamp"
+ ",pr.purse_expiration"
+ ",pr.age_limit"
+ ",pr.flags"
+ " FROM purse_decision pdes"
+ " JOIN purse_requests pr"
+ " ON (pr.purse_pub = pdes.purse_pub)"
+ " JOIN purse_merges pm"
+ " ON (pm.purse_pub = pdes.purse_pub)"
+ " JOIN account_merges am"
+ " ON (am.purse_pub = pm.purse_pub AND"
+ " am.reserve_pub = pm.reserve_pub)"
+ " WHERE pdes.purse_decision_serial_id=$2"
+ " AND pm.reserve_pub=$1"
+ " AND COALESCE(pm.partner_serial_id,0)=0" /* must be local! */
+ " AND NOT pdes.refunded;");
+ PREPARE (pg,
+ "open_request_by_reserve",
+ "SELECT"
+ " reserve_payment"
+ ",request_timestamp"
+ ",expiration_date"
+ ",requested_purse_limit"
+ ",reserve_sig"
+ " FROM reserves_open_requests"
+ " WHERE reserve_pub=$1"
+ " AND open_request_uuid=$2;");
+ PREPARE (pg,
+ "close_request_by_reserve",
+ "SELECT"
+ " close_timestamp"
+ ",payto_uri"
+ ",reserve_sig"
+ " FROM close_requests"
+ " WHERE reserve_pub=$1"
+ " AND close_request_serial_id=$2;");
+
+ for (unsigned int i = 0; i<RETRIES; i++)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t end;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("reserve_history_serial_id",
+ &end),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance",
+ balance),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ TEH_PG_start_read_committed (pg,
+ "get-reserve-transactions"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* First only check the last item, to see if
+ we even need to iterate */
+ qs = GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "get_reserve_history_etag",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ *etag_out = end;
+ if (end == etag_in)
+ return qs;
+ }
+ /* We indeed need to iterate over the history */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Current ETag for reserve %s is %llu\n",
+ TALER_B2S (reserve_pub),
+ (unsigned long long) end);
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "get_reserve_history",
+ lparams,
+ &handle_history_entry,
+ &rhc);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ default:
+ break;
+ }
+ if (rhc.failed)
+ {
+ TEH_PG_rollback (pg);
+ TEH_COMMON_free_reserve_history (pg,
+ rhc.rh);
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+ }
+ qs = TEH_PG_commit (pg);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ TEH_COMMON_free_reserve_history (pg,
+ rhc.rh);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_COMMON_free_reserve_history (pg,
+ rhc.rh);
+ rhc.rh = NULL;
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ *rhp = rhc.rh;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ }
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
diff --git a/src/exchangedb/pg_get_reserve_history.h b/src/exchangedb/pg_get_reserve_history.h
new file mode 100644
index 000000000..15765f127
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_history.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_get_reserve_history.h
+ * @brief implementation of the get_reserve_history function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_RESERVE_HISTORY_H
+#define PG_GET_RESERVE_HISTORY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Compile a list of (historic) transactions performed with the given reserve
+ * (withdraw, incoming wire, open, close operations). Should return 0 if the @a
+ * reserve_pub is unknown, otherwise determine @a etag_out and if it is past @a
+ * etag_in return the history after @a start_off. @a etag_out should be set
+ * to the last row ID of the given @a reserve_pub in the reserve history table.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @param start_off maximum starting offset in history to exclude from returning
+ * @param etag_in up to this offset the client already has a response, do not
+ * return anything unless @a etag_out will be larger
+ * @param[out] etag_out set to the latest history offset known for this @a coin_pub
+ * @param[out] balance set to the reserve balance
+ * @param[out] rhp set to known transaction history (NULL if reserve is unknown)
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_history (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t start_off,
+ uint64_t etag_in,
+ uint64_t *etag_out,
+ struct TALER_Amount *balance,
+ struct TALER_EXCHANGEDB_ReserveHistory **rhp);
+
+
+#endif
diff --git a/src/exchangedb/pg_get_signature_for_known_coin.c b/src/exchangedb/pg_get_signature_for_known_coin.c
new file mode 100644
index 000000000..06074312f
--- /dev/null
+++ b/src/exchangedb/pg_get_signature_for_known_coin.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_signature_for_known_coin.c
+ * @brief Implementation of the get_signature_for_known_coin function for Postgres
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_signature_for_known_coin.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_signature_for_known_coin (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_DenominationPublicKey *denom_pub,
+ struct TALER_DenominationSignature *denom_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ denom_pub),
+ TALER_PQ_result_spec_denom_sig ("denom_sig",
+ denom_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Getting denomination and signature for (potentially) known coin %s\n",
+ TALER_B2S (coin_pub));
+ PREPARE (pg,
+ "get_signature_for_known_coin",
+ "SELECT"
+ " denominations.denom_pub"
+ ",denom_sig"
+ " FROM known_coins"
+ " JOIN denominations USING (denominations_serial)"
+ " WHERE coin_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_signature_for_known_coin",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_signature_for_known_coin.h b/src/exchangedb/pg_get_signature_for_known_coin.h
new file mode 100644
index 000000000..ec389176b
--- /dev/null
+++ b/src/exchangedb/pg_get_signature_for_known_coin.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_signature_for_known_coin.h
+ * @brief implementation of the get_signature_for_known_coin function for Postgres
+ * @author Özgür Kesim
+ */
+#ifndef PG_GET_SIGNATURE_FOR_KNOWN_COIN_H
+#define PG_GET_SIGNATURE_FOR_KNOWN_COIN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Retrieve the denomination and the corresponding signature for a known coin.
+ *
+ * @param cls the plugin closure
+ * @param coin_pub the public key of the coin to search for
+ * @param[out] denom_pub the denomination of the public key, if coin was present
+ * @param[out] denom_sig the signature with the denomination key of the coin, if coin was present
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_signature_for_known_coin (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_DenominationPublicKey *denom_pub,
+ struct TALER_DenominationSignature *denom_sig);
+
+#endif
diff --git a/src/exchangedb/pg_get_unfinished_close_requests.c b/src/exchangedb/pg_get_unfinished_close_requests.c
new file mode 100644
index 000000000..990e8e00e
--- /dev/null
+++ b/src/exchangedb/pg_get_unfinished_close_requests.c
@@ -0,0 +1,166 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_get_unfinished_close_requests.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_unfinished_close_requests.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #reserve_close_cb().
+ */
+struct CloseReserveContext
+{
+ /**
+ * Function to call for each to be closed reserve.
+ */
+ TALER_EXCHANGEDB_ReserveExpiredCallback rec;
+
+ /**
+ * Closure to give to @e rec.
+ */
+ void *rec_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on error.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserve_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CloseReserveContext *erc = cls;
+ struct PostgresClosure *pg = erc->pg;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = GNUNET_OK;
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_TIME_Timestamp exp_date;
+ char *account_details;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_Amount remaining_balance;
+ uint64_t close_request_row;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ &exp_date),
+ GNUNET_PQ_result_spec_string ("account_details",
+ &account_details),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("close",
+ &remaining_balance),
+ GNUNET_PQ_result_spec_uint64 ("close_request_serial_id",
+ &close_request_row),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ret = GNUNET_SYSERR;
+ break;
+ }
+ ret = erc->rec (erc->rec_cls,
+ &reserve_pub,
+ &remaining_balance,
+ account_details,
+ exp_date,
+ close_request_row);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+ erc->status = ret;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_unfinished_close_requests (
+ void *cls,
+ TALER_EXCHANGEDB_ReserveExpiredCallback rec,
+ void *rec_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct CloseReserveContext ectx = {
+ .rec = rec,
+ .rec_cls = rec_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "get_unfinished_close_requests",
+ "UPDATE close_requests AS rc"
+ " SET done=TRUE"
+ " WHERE done=FALSE"
+ " RETURNING"
+ " reserve_pub"
+ " ,close_request_serial_id"
+ " ,close_timestamp AS expiration_date"
+ " ,close"
+ " ,(SELECT payto_uri"
+ " FROM reserves_in ri"
+ " JOIN wire_targets wt ON (ri.wire_source_h_payto = wt.wire_target_h_payto)"
+ " WHERE ri.reserve_pub=rc.reserve_pub)"
+ " AS account_details;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_unfinished_close_requests",
+ params,
+ &reserve_cb,
+ &ectx);
+ switch (ectx.status)
+ {
+ case GNUNET_SYSERR:
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_NO:
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+ case GNUNET_OK:
+ break;
+ }
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_unfinished_close_requests.h b/src/exchangedb/pg_get_unfinished_close_requests.h
new file mode 100644
index 000000000..4c5aa0d1d
--- /dev/null
+++ b/src/exchangedb/pg_get_unfinished_close_requests.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_get_unfinished_close_requests.h
+ * @brief implementation of the get_unfinished_close_requests function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_UNFINISHED_CLOSE_REQUESTS_H
+#define PG_GET_UNFINISHED_CLOSE_REQUESTS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Obtain information about force-closed reserves
+ * where the close was not yet done (and their remaining
+ * balances). Updates the returned reserve's close
+ * status to "done".
+ *
+ * @param cls closure of the plugin
+ * @param rec function to call on expired reserves
+ * @param rec_cls closure for @a rec
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_unfinished_close_requests (
+ void *cls,
+ TALER_EXCHANGEDB_ReserveExpiredCallback rec,
+ void *rec_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_wire_accounts.c b/src/exchangedb/pg_get_wire_accounts.c
new file mode 100644
index 000000000..9770be719
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_accounts.c
@@ -0,0 +1,169 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2024 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 exchangedb/pg_get_wire_accounts.c
+ * @brief Implementation of the get_wire_accounts function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_wire_accounts.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_wire_accounts_cb().
+ */
+struct GetWireAccountsContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_WireAccountCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct MissingWireContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_wire_accounts_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GetWireAccountsContext *ctx = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *payto_uri;
+ char *conversion_url = NULL;
+ json_t *debit_restrictions = NULL;
+ json_t *credit_restrictions = NULL;
+ struct TALER_MasterSignatureP master_sig;
+ char *bank_label = NULL;
+ int64_t priority;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("conversion_url",
+ &conversion_url),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("bank_label",
+ &bank_label),
+ NULL),
+ GNUNET_PQ_result_spec_int64 ("priority",
+ &priority),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("debit_restrictions",
+ &debit_restrictions),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("credit_restrictions",
+ &credit_restrictions),
+ NULL),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ if (NULL == debit_restrictions)
+ {
+ debit_restrictions = json_array ();
+ GNUNET_assert (NULL != debit_restrictions);
+ }
+ if (NULL == credit_restrictions)
+ {
+ credit_restrictions = json_array ();
+ GNUNET_assert (NULL != credit_restrictions);
+ }
+ ctx->cb (ctx->cb_cls,
+ payto_uri,
+ conversion_url,
+ debit_restrictions,
+ credit_restrictions,
+ &master_sig,
+ bank_label,
+ priority);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_accounts (void *cls,
+ TALER_EXCHANGEDB_WireAccountCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GetWireAccountsContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .status = GNUNET_OK
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "get_wire_accounts",
+ "SELECT"
+ " payto_uri"
+ ",conversion_url"
+ ",debit_restrictions"
+ ",credit_restrictions"
+ ",master_sig"
+ ",bank_label"
+ ",priority"
+ " FROM wire_accounts"
+ " WHERE is_active");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_wire_accounts",
+ params,
+ &get_wire_accounts_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_wire_accounts.h b/src/exchangedb/pg_get_wire_accounts.h
new file mode 100644
index 000000000..f4dc97ce0
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_accounts.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_accounts.h
+ * @brief implementation of the get_wire_accounts function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_WIRE_ACCOUNTS_H
+#define PG_GET_WIRE_ACCOUNTS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Obtain information about the enabled wire accounts of the exchange.
+ *
+ * @param cls closure
+ * @param cb function to call on each account
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_accounts (void *cls,
+ TALER_EXCHANGEDB_WireAccountCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_wire_fee.c b/src/exchangedb/pg_get_wire_fee.c
new file mode 100644
index 000000000..40f517f28
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_fee.c
@@ -0,0 +1,73 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_fee.c
+ * @brief Implementation of the get_wire_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_wire_fee.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_fee (void *cls,
+ const char *type,
+ struct GNUNET_TIME_Timestamp date,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_WireFeeSet *fees,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (type),
+ GNUNET_PQ_query_param_timestamp (&date),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("start_date",
+ start_date),
+ GNUNET_PQ_result_spec_timestamp ("end_date",
+ end_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
+ &fees->wire),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
+ &fees->closing),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_wire_fee",
+ "SELECT "
+ " start_date"
+ ",end_date"
+ ",wire_fee"
+ ",closing_fee"
+ ",master_sig"
+ " FROM wire_fee"
+ " WHERE wire_method=$1"
+ " AND start_date <= $2"
+ " AND end_date > $2;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_wire_fee",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_wire_fee.h b/src/exchangedb/pg_get_wire_fee.h
new file mode 100644
index 000000000..409a5c48b
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_fee.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_fee.h
+ * @brief implementation of the get_wire_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_WIRE_FEE_H
+#define PG_GET_WIRE_FEE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain wire fee from database.
+ *
+ * @param cls closure
+ * @param type type of wire transfer the fee applies for
+ * @param date for which date do we want the fee?
+ * @param[out] start_date when does the fee go into effect
+ * @param[out] end_date when does the fee end being valid
+ * @param[out] fees how high are the wire fees
+ * @param[out] master_sig signature over the above by the exchange master key
+ * @return status of the transaction
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_fee (void *cls,
+ const char *type,
+ struct GNUNET_TIME_Timestamp date,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_WireFeeSet *fees,
+ struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_get_wire_fees.c b/src/exchangedb/pg_get_wire_fees.c
new file mode 100644
index 000000000..193ccff6a
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_fees.c
@@ -0,0 +1,147 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_fees.c
+ * @brief Implementation of the get_wire_fees function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_wire_fees.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #get_wire_fees_cb().
+ */
+struct GetWireFeesContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_WireFeeCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct GetWireFeesContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_wire_fees_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GetWireFeesContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct TALER_MasterSignatureP master_sig;
+ struct TALER_WireFeeSet fees;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
+ &fees.wire),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
+ &fees.closing),
+ GNUNET_PQ_result_spec_timestamp ("start_date",
+ &start_date),
+ GNUNET_PQ_result_spec_timestamp ("end_date",
+ &end_date),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &fees,
+ start_date,
+ end_date,
+ &master_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_fees (void *cls,
+ const char *wire_method,
+ TALER_EXCHANGEDB_WireFeeCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (wire_method),
+ GNUNET_PQ_query_param_end
+ };
+ struct GetWireFeesContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "get_wire_fees",
+ "SELECT"
+ " wire_fee"
+ ",closing_fee"
+ ",start_date"
+ ",end_date"
+ ",master_sig"
+ " FROM wire_fee"
+ " WHERE wire_method=$1");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_wire_fees",
+ params,
+ &get_wire_fees_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_wire_fees.h b/src/exchangedb/pg_get_wire_fees.h
new file mode 100644
index 000000000..798a514db
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_fees.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_fees.h
+ * @brief implementation of the get_wire_fees function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_WIRE_FEES_H
+#define PG_GET_WIRE_FEES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain information about the fee structure of the exchange for
+ * a given @a wire_method
+ *
+ * @param cls closure
+ * @param wire_method which wire method to obtain fees for
+ * @param cb function to call on each account
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_fees (void *cls,
+ const char *wire_method,
+ TALER_EXCHANGEDB_WireFeeCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_wire_hash_for_contract.c b/src/exchangedb/pg_get_wire_hash_for_contract.c
new file mode 100644
index 000000000..afd659b18
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_hash_for_contract.c
@@ -0,0 +1,81 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_hash_for_contract.c
+ * @brief Implementation of the get_wire_hash_for_contract function for Postgres
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_pq_lib.h"
+#include "pg_get_wire_hash_for_contract.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_hash_for_contract (
+ void *cls,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct TALER_MerchantWireHashP *h_wire)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_end
+ };
+ char *payto_uri;
+ struct TALER_WireSaltP wire_salt;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+ &wire_salt),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_end
+ };
+
+ /* check if the necessary records exist and get them */
+ PREPARE (pg,
+ "get_wire_hash_for_contract",
+ "SELECT"
+ " bdep.wire_salt"
+ ",wt.payto_uri"
+ " FROM coin_deposits"
+ " JOIN batch_deposits bdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN wire_targets wt"
+ " USING (wire_target_h_payto)"
+ " WHERE bdep.merchant_pub=$1"
+ " AND bdep.h_contract_terms=$2");
+ /* NOTE: above query might be more efficient if we computed the shard
+ from the merchant_pub and included that in the query */
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_wire_hash_for_contract",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ TALER_merchant_wire_signature_hash (payto_uri,
+ &wire_salt,
+ h_wire);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_wire_hash_for_contract.h b/src/exchangedb/pg_get_wire_hash_for_contract.h
new file mode 100644
index 000000000..f95cd29c1
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_hash_for_contract.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_hash_for_contract.h
+ * @brief implementation of the get_wire_hash_for_contract function for Postgres
+ * @author Özgür Kesim
+ */
+#ifndef PG_GET_WIRE_HASH_FOR_CONTRACT_H
+#define PG_GET_WIRE_HASH_FOR_CONTRACT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Try to get the salted hash of a merchant's bank account to a deposit
+ * contract. This is necessary in the event of a conflict with a given
+ * (merchant_pub, h_contract_terms) during deposit.
+ *
+ * @param cls closure
+ * @param merchant_pub merchant public key
+ * @param h_contract_terms hash of the proposal data
+ * @param[out] h_wire salted hash of a merchant's bank account
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_hash_for_contract (
+ void *cls,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct TALER_MerchantWireHashP *h_wire);
+
+#endif
diff --git a/src/exchangedb/pg_get_withdraw_info.c b/src/exchangedb/pg_get_withdraw_info.c
new file mode 100644
index 000000000..e06fa3764
--- /dev/null
+++ b/src/exchangedb/pg_get_withdraw_info.c
@@ -0,0 +1,79 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_withdraw_info.c
+ * @brief Implementation of the get_withdraw_info function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_withdraw_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_withdraw_info (
+ void *cls,
+ const struct TALER_BlindedCoinHashP *bch,
+ struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (bch),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &collectable->denom_pub_hash),
+ TALER_PQ_result_spec_blinded_denom_sig ("denom_sig",
+ &collectable->sig),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &collectable->reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &collectable->reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
+ &collectable->h_coin_envelope),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &collectable->amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+ &collectable->withdraw_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_withdraw_info",
+ "SELECT"
+ " denom.denom_pub_hash"
+ ",denom_sig"
+ ",reserve_sig"
+ ",reserves.reserve_pub"
+ ",execution_date"
+ ",h_blind_ev"
+ ",amount_with_fee"
+ ",denom.fee_withdraw"
+ " FROM reserves_out"
+ " JOIN reserves"
+ " USING (reserve_uuid)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE h_blind_ev=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_withdraw_info",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_withdraw_info.h b/src/exchangedb/pg_get_withdraw_info.h
new file mode 100644
index 000000000..7c3e06a02
--- /dev/null
+++ b/src/exchangedb/pg_get_withdraw_info.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_get_withdraw_info.h
+ * @brief implementation of the get_withdraw_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_WITHDRAW_INFO_H
+#define PG_GET_WITHDRAW_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Locate the response for a /reserve/withdraw request under the
+ * key of the hash of the blinded message.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param bch hash that uniquely identifies the withdraw operation
+ * @param collectable corresponding collectable coin (blind signature)
+ * if a coin is found
+ * @return statement execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_withdraw_info (
+ void *cls,
+ const struct TALER_BlindedCoinHashP *bch,
+ struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable);
+
+#endif
diff --git a/src/exchangedb/pg_have_deposit2.c b/src/exchangedb/pg_have_deposit2.c
new file mode 100644
index 000000000..e00ad7490
--- /dev/null
+++ b/src/exchangedb/pg_have_deposit2.c
@@ -0,0 +1,117 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_have_deposit2.c
+ * @brief Implementation of the have_deposit2 function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_have_deposit2.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_have_deposit2 (
+ void *cls,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct TALER_Amount *deposit_fee,
+ struct GNUNET_TIME_Timestamp *exchange_timestamp)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_auto_from_type (merchant),
+ GNUNET_PQ_query_param_end
+ };
+ struct TALER_EXCHANGEDB_Deposit deposit2;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &deposit2.amount_with_fee),
+ GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
+ &deposit2.timestamp),
+ GNUNET_PQ_result_spec_timestamp ("exchange_timestamp",
+ exchange_timestamp),
+ GNUNET_PQ_result_spec_timestamp ("refund_deadline",
+ &deposit2.refund_deadline),
+ GNUNET_PQ_result_spec_timestamp ("wire_deadline",
+ &deposit2.wire_deadline),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ deposit_fee),
+ GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+ &deposit2.wire_salt),
+ GNUNET_PQ_result_spec_string ("receiver_wire_account",
+ &deposit2.receiver_wire_account),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_MerchantWireHashP h_wire2;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Getting deposits for coin %s\n",
+ TALER_B2S (coin_pub));
+ PREPARE (pg,
+ "get_deposit",
+ "SELECT"
+ " cdep.amount_with_fee"
+ ",denominations.fee_deposit"
+ ",bdep.wallet_timestamp"
+ ",bdep.exchange_timestamp"
+ ",bdep.refund_deadline"
+ ",bdep.wire_deadline"
+ ",bdep.h_contract_terms"
+ ",bdep.wire_salt"
+ ",wt.payto_uri AS receiver_wire_account"
+ " FROM coin_deposits cdep"
+ " JOIN batch_deposits bdep USING (batch_deposit_serial_id)"
+ " JOIN known_coins kc ON (kc.coin_pub = cdep.coin_pub)"
+ " JOIN denominations USING (denominations_serial)"
+ " JOIN wire_targets wt USING (wire_target_h_payto)"
+ " WHERE cdep.coin_pub=$1"
+ " AND bdep.merchant_pub=$3"
+ " AND bdep.h_contract_terms=$2;");
+ /* Note: query might be made more efficient if we computed the 'shard'
+ from merchant_pub and included that as a constraint on bdep! */
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_deposit",
+ params,
+ rs);
+ if (0 >= qs)
+ return qs;
+ TALER_merchant_wire_signature_hash (deposit2.receiver_wire_account,
+ &deposit2.wire_salt,
+ &h_wire2);
+ GNUNET_free (deposit2.receiver_wire_account);
+ /* Now we check that the other information in @a deposit
+ also matches, and if not report inconsistencies. */
+ if ( (GNUNET_TIME_timestamp_cmp (refund_deadline,
+ !=,
+ deposit2.refund_deadline)) ||
+ (0 != GNUNET_memcmp (h_wire,
+ &h_wire2) ) )
+ {
+ /* Inconsistencies detected! Does not match! */
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
diff --git a/src/exchangedb/pg_have_deposit2.h b/src/exchangedb/pg_have_deposit2.h
new file mode 100644
index 000000000..0e8119c20
--- /dev/null
+++ b/src/exchangedb/pg_have_deposit2.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_have_deposit2.h
+ * @brief implementation of the have_deposit2 function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_HAVE_DEPOSIT2_H
+#define PG_HAVE_DEPOSIT2_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Check if we have the specified deposit already in the database.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param h_contract_terms contract to check for
+ * @param h_wire wire hash to check for
+ * @param coin_pub public key of the coin to check for
+ * @param merchant merchant public key to check for
+ * @param refund_deadline expected refund deadline
+ * @param[out] deposit_fee set to the deposit fee the exchange charged
+ * @param[out] exchange_timestamp set to the time when the exchange received the deposit
+ * @return 1 if we know this operation,
+ * 0 if this exact deposit is unknown to us,
+ * otherwise transaction error status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_have_deposit2 (
+ void *cls,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct TALER_Amount *deposit_fee,
+ struct GNUNET_TIME_Timestamp *exchange_timestamp);
+#endif
diff --git a/src/exchangedb/pg_helper.h b/src/exchangedb/pg_helper.h
new file mode 100644
index 000000000..c63c9111d
--- /dev/null
+++ b/src/exchangedb/pg_helper.h
@@ -0,0 +1,149 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_helper.h
+ * @brief shared internal definitions for postgres DB plugin
+ * @author Christian Grothoff
+ */
+#ifndef PG_HELPER_H
+#define PG_HELPER_H
+
+
+/**
+ * Type of the "cls" argument given to each of the functions in
+ * our API.
+ */
+struct PostgresClosure
+{
+
+ /**
+ * Our configuration.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Directory with SQL statements to run to create tables.
+ */
+ char *sql_dir;
+
+ /**
+ * After how long should idle reserves be closed?
+ */
+ struct GNUNET_TIME_Relative idle_reserve_expiration_time;
+
+ /**
+ * After how long should reserves that have seen withdraw operations
+ * be garbage collected?
+ */
+ struct GNUNET_TIME_Relative legal_reserve_expiration_time;
+
+ /**
+ * What delay should we introduce before ready transactions
+ * are actually aggregated?
+ */
+ struct GNUNET_TIME_Relative aggregator_shift;
+
+ /**
+ * Which currency should we assume all amounts to be in?
+ */
+ char *currency;
+
+ /**
+ * Our base URL.
+ */
+ char *exchange_url;
+
+ /**
+ * Postgres connection handle.
+ */
+ struct GNUNET_PQ_Context *conn;
+
+ /**
+ * Name of the current transaction, for debugging.
+ */
+ const char *transaction_name;
+
+ /**
+ * Counts how often we have established a fresh @e conn
+ * to the database. Used to re-prepare statements.
+ */
+ unsigned long long prep_gen;
+
+ /**
+ * Number of purses we allow to be opened concurrently
+ * for one year per annual fee payment.
+ */
+ uint32_t def_purse_limit;
+
+};
+
+
+/**
+ * Prepares SQL statement @a sql under @a name for
+ * connection @a pg once.
+ * Returns with #GNUNET_DB_STATUS_HARD_ERROR on failure.
+ *
+ * @param pg a `struct PostgresClosure`
+ * @param name name to prepare the statement under
+ * @param sql actual SQL text
+ */
+#define PREPARE(pg,name,sql) \
+ do { \
+ static struct { \
+ unsigned long long cnt; \
+ struct PostgresClosure *pg; \
+ } preps[2]; /* 2 ctrs for taler-auditor-sync*/ \
+ unsigned int off = 0; \
+ \
+ while ( (NULL != preps[off].pg) && \
+ (pg != preps[off].pg) && \
+ (off < sizeof(preps) / sizeof(*preps)) ) \
+ off++; \
+ GNUNET_assert (off < \
+ sizeof(preps) / sizeof(*preps)); \
+ if (preps[off].cnt < pg->prep_gen) \
+ { \
+ struct GNUNET_PQ_PreparedStatement ps[] = { \
+ GNUNET_PQ_make_prepare (name, sql), \
+ GNUNET_PQ_PREPARED_STATEMENT_END \
+ }; \
+ \
+ if (GNUNET_OK != \
+ GNUNET_PQ_prepare_statements (pg->conn, \
+ ps)) \
+ { \
+ GNUNET_break (0); \
+ return GNUNET_DB_STATUS_HARD_ERROR; \
+ } \
+ preps[off].pg = pg; \
+ preps[off].cnt = pg->prep_gen; \
+ } \
+ } while (0)
+
+
+/**
+ * Wrapper macro to add the currency from the plugin's state
+ * when fetching amounts from the database.
+ *
+ * @param field name of the database field to fetch amount from
+ * @param[out] amountp pointer to amount to set
+ */
+#define TALER_PQ_RESULT_SPEC_AMOUNT(field, \
+ amountp) TALER_PQ_result_spec_amount ( \
+ field,pg->currency,amountp)
+
+
+#endif
diff --git a/src/exchangedb/pg_inject_auditor_triggers.c b/src/exchangedb/pg_inject_auditor_triggers.c
new file mode 100644
index 000000000..562a1a22c
--- /dev/null
+++ b/src/exchangedb/pg_inject_auditor_triggers.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_inject_auditor_triggers.c
+ * @brief Implementation of the inject_auditor_triggers function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_gc.h"
+#include "pg_helper.h"
+
+
+/**
+ * Function called to inject auditor triggers into the
+ * database, triggering the real-time auditor upon
+ * relevant INSERTs.
+ *
+ * @param cls closure
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR on DB errors
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_inject_auditor_triggers (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_Context *conn;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+ "exchangedb-postgres",
+ "auditor-triggers-",
+ es,
+ NULL);
+ if (NULL == conn)
+ return GNUNET_SYSERR;
+ GNUNET_PQ_disconnect (conn);
+ return GNUNET_OK;
+}
diff --git a/src/exchangedb/pg_inject_auditor_triggers.h b/src/exchangedb/pg_inject_auditor_triggers.h
new file mode 100644
index 000000000..2dfa9468c
--- /dev/null
+++ b/src/exchangedb/pg_inject_auditor_triggers.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_inject_auditor_triggers.h
+ * @brief implementation of the inject_auditor_triggers function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INJECT_AUDITOR_TRIGGERS_H
+#define PG_INJECT_AUDITOR_TRIGGERS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to inject auditor triggers into the
+ * database, triggering the real-time auditor upon
+ * relevant INSERTs.
+ *
+ * @param cls closure
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR on DB errors
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_inject_auditor_triggers (void *cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_aml_decision.c b/src/exchangedb/pg_insert_aml_decision.c
new file mode 100644
index 000000000..39419be59
--- /dev/null
+++ b/src/exchangedb/pg_insert_aml_decision.c
@@ -0,0 +1,97 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_insert_aml_decision.c
+ * @brief Implementation of the insert_aml_decision function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_aml_decision.h"
+#include "pg_helper.h"
+#include <gnunet/gnunet_pq_lib.h>
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_aml_decision (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_Amount *new_threshold,
+ enum TALER_AmlDecisionState new_status,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const char *justification,
+ const json_t *kyc_requirements,
+ uint64_t requirements_row,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ const struct TALER_AmlOfficerSignatureP *decider_sig,
+ bool *invalid_officer,
+ struct GNUNET_TIME_Timestamp *last_date)
+{
+ struct PostgresClosure *pg = cls;
+ uint32_t ns = (uint32_t) new_status;
+ struct TALER_KycCompletedEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
+ .h_payto = *h_payto
+ };
+ char *notify_s = GNUNET_PQ_get_event_notify_channel (&rep.header);
+ char *kyc_s = (NULL != kyc_requirements)
+ ? json_dumps (kyc_requirements, JSON_COMPACT)
+ : NULL;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ TALER_PQ_query_param_amount (pg->conn,
+ new_threshold),
+ GNUNET_PQ_query_param_uint32 (&ns),
+ GNUNET_PQ_query_param_timestamp (&decision_time),
+ GNUNET_PQ_query_param_string (justification),
+ GNUNET_PQ_query_param_auto_from_type (decider_pub),
+ GNUNET_PQ_query_param_auto_from_type (decider_sig),
+ GNUNET_PQ_query_param_string (notify_s),
+ NULL != kyc_requirements
+ ? GNUNET_PQ_query_param_string (kyc_s)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_uint64 (&requirements_row),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("out_invalid_officer",
+ invalid_officer),
+ GNUNET_PQ_result_spec_timestamp ("out_last_date",
+ last_date),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "do_insert_aml_decision",
+ "SELECT"
+ " out_invalid_officer"
+ ",out_last_date"
+ " FROM exchange_do_insert_aml_decision"
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "do_insert_aml_decision",
+ params,
+ rs);
+ GNUNET_free (notify_s);
+ GNUNET_PQ_event_do_poll (pg->conn);
+ if (NULL != kyc_s)
+ free (kyc_s);
+ return qs;
+}
diff --git a/src/exchangedb/pg_insert_aml_decision.h b/src/exchangedb/pg_insert_aml_decision.h
new file mode 100644
index 000000000..073a2f50a
--- /dev/null
+++ b/src/exchangedb/pg_insert_aml_decision.h
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_insert_aml_decision.h
+ * @brief implementation of the insert_aml_decision function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_AML_DECISION_H
+#define PG_INSERT_AML_DECISION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Insert an AML decision. Inserts into AML history and insert or updates AML
+ * status.
+ *
+ * @param cls closure
+ * @param h_payto account for which the attribute data is stored
+ * @param new_threshold new monthly threshold that would trigger an AML check
+ * @param new_status AML decision status
+ * @param decision_time when was the decision made
+ * @param justification human-readable text justifying the decision
+ * @param kyc_requirements JSON array with KYC requirements
+ * @param requirements_row row in the KYC table for this process, 0 for none
+ * @param decider_pub public key of the staff member
+ * @param decider_sig signature of the staff member
+ * @param[out] invalid_officer set to TRUE if @a decider_pub is not allowed to make decisions right now
+ * @param[out] last_date set to the previous decision time;
+ * the INSERT is not performed if @a last_date is not before @a decision_time
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_aml_decision (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_Amount *new_threshold,
+ enum TALER_AmlDecisionState new_status,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const char *justification,
+ const json_t *kyc_requirements,
+ uint64_t requirements_row,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ const struct TALER_AmlOfficerSignatureP *decider_sig,
+ bool *invalid_officer,
+ struct GNUNET_TIME_Timestamp *last_date);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_aml_officer.c b/src/exchangedb/pg_insert_aml_officer.c
new file mode 100644
index 000000000..c1f635a64
--- /dev/null
+++ b/src/exchangedb/pg_insert_aml_officer.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_insert_aml_officer.c
+ * @brief Implementation of the insert_aml_officer function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_aml_officer.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_aml_officer (
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *decider_name,
+ bool is_active,
+ bool read_only,
+ struct GNUNET_TIME_Timestamp last_change,
+ struct GNUNET_TIME_Timestamp *previous_change)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (decider_pub),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_string (decider_name),
+ GNUNET_PQ_query_param_bool (is_active),
+ GNUNET_PQ_query_param_bool (read_only),
+ GNUNET_PQ_query_param_timestamp (&last_change),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("out_last_change",
+ previous_change),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "do_insert_aml_staff",
+ "SELECT"
+ " out_last_change"
+ " FROM exchange_do_insert_aml_officer"
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "do_insert_aml_staff",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_insert_aml_officer.h b/src/exchangedb/pg_insert_aml_officer.h
new file mode 100644
index 000000000..3c6f5d82e
--- /dev/null
+++ b/src/exchangedb/pg_insert_aml_officer.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_aml_officer.h
+ * @brief implementation of the insert_aml_officer function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_AML_OFFICER_H
+#define PG_INSERT_AML_OFFICER_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Insert AML staff record. If the time given in
+ * @a last_change is before the previous change in the
+ * database, only @e previous_change is returned and
+ * no actual change is committed to the database.
+ *
+ * @param cls closure
+ * @param decider_pub public key of the staff member
+ * @param master_sig offline signature affirming the AML officer
+ * @param decider_name full name of the staff member
+ * @param is_active true to enable, false to set as inactive
+ * @param read_only true to set read-only access
+ * @param last_change when was the change made effective
+ * @param[out] previous_change set to the time of the previous change
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_aml_officer (
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *decider_name,
+ bool is_active,
+ bool read_only,
+ struct GNUNET_TIME_Timestamp last_change,
+ struct GNUNET_TIME_Timestamp *previous_change);
+
+#endif
diff --git a/src/exchangedb/pg_insert_auditor.c b/src/exchangedb/pg_insert_auditor.c
new file mode 100644
index 000000000..2f1de7ba7
--- /dev/null
+++ b/src/exchangedb/pg_insert_auditor.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_auditor.c
+ * @brief Implementation of the insert_auditor function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_auditor.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_auditor (void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp start_date)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (auditor_pub),
+ GNUNET_PQ_query_param_string (auditor_name),
+ GNUNET_PQ_query_param_string (auditor_url),
+ GNUNET_PQ_query_param_timestamp (&start_date),
+ GNUNET_PQ_query_param_end
+ };
+
+ /* used in #postgres_insert_auditor() */
+ PREPARE (pg,
+ "insert_auditor",
+ "INSERT INTO auditors "
+ "(auditor_pub"
+ ",auditor_name"
+ ",auditor_url"
+ ",is_active"
+ ",last_change"
+ ") VALUES "
+ "($1, $2, $3, true, $4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_auditor",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_auditor.h b/src/exchangedb/pg_insert_auditor.h
new file mode 100644
index 000000000..8de388f23
--- /dev/null
+++ b/src/exchangedb/pg_insert_auditor.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_auditor.h
+ * @brief implementation of the insert_auditor function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_AUDITOR_H
+#define PG_INSERT_AUDITOR_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Insert information about an auditor that will audit this exchange.
+ *
+ * @param cls closure
+ * @param auditor_pub key of the auditor
+ * @param auditor_url base URL of the auditor's REST service
+ * @param auditor_name name of the auditor (for humans)
+ * @param start_date date when the auditor was added by the offline system
+ * (only to be used for replay detection)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_auditor (void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp start_date);
+#endif
diff --git a/src/exchangedb/pg_insert_auditor_denom_sig.c b/src/exchangedb/pg_insert_auditor_denom_sig.c
new file mode 100644
index 000000000..3643a87f2
--- /dev/null
+++ b/src/exchangedb/pg_insert_auditor_denom_sig.c
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_auditor_denom_sig.c
+ * @brief Implementation of the insert_auditor_denom_sig function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_auditor_denom_sig.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_auditor_denom_sig (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_AuditorSignatureP *auditor_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (auditor_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
+ GNUNET_PQ_query_param_auto_from_type (auditor_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_auditor_denom_sig",
+ "WITH ax AS"
+ " (SELECT auditor_uuid"
+ " FROM auditors"
+ " WHERE auditor_pub=$1)"
+ "INSERT INTO auditor_denom_sigs "
+ "(auditor_uuid"
+ ",denominations_serial"
+ ",auditor_sig"
+ ") SELECT ax.auditor_uuid, denominations_serial, $3 "
+ " FROM denominations"
+ " CROSS JOIN ax"
+ " WHERE denom_pub_hash=$2;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_auditor_denom_sig",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_auditor_denom_sig.h b/src/exchangedb/pg_insert_auditor_denom_sig.h
new file mode 100644
index 000000000..baa67cfe8
--- /dev/null
+++ b/src/exchangedb/pg_insert_auditor_denom_sig.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_auditor_denom_sig.h
+ * @brief implementation of the insert_auditor_denom_sig function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_AUDITOR_DENOM_SIG_H
+#define PG_INSERT_AUDITOR_DENOM_SIG_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Insert information about an auditor auditing a denomination key.
+ *
+ * @param cls closure
+ * @param h_denom_pub the audited denomination
+ * @param auditor_pub the auditor's key
+ * @param auditor_sig signature affirming the auditor's audit activity
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_auditor_denom_sig (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_AuditorSignatureP *auditor_sig);
+#endif
diff --git a/src/exchangedb/pg_insert_close_request.c b/src/exchangedb/pg_insert_close_request.c
new file mode 100644
index 000000000..b4bc5f4a7
--- /dev/null
+++ b/src/exchangedb/pg_insert_close_request.c
@@ -0,0 +1,68 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_close_request.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_close_request.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_close_request (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *payto_uri,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_Amount *balance,
+ const struct TALER_Amount *closing_fee)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_timestamp (&request_timestamp),
+ GNUNET_PQ_query_param_auto_from_type (reserve_sig),
+ TALER_PQ_query_param_amount (pg->conn,
+ balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ closing_fee),
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_account_close",
+ "INSERT INTO close_requests"
+ "(reserve_pub"
+ ",close_timestamp"
+ ",reserve_sig"
+ ",close"
+ ",close_fee"
+ ",payto_uri"
+ ")"
+ "VALUES "
+ "($1, $2, $3, $4, $5, $6)"
+ " ON CONFLICT DO NOTHING;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_account_close",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_close_request.h b/src/exchangedb/pg_insert_close_request.h
new file mode 100644
index 000000000..c014a10b9
--- /dev/null
+++ b/src/exchangedb/pg_insert_close_request.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_close_request.h
+ * @brief implementation of the insert_close_request function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_CLOSE_REQUEST_H
+#define PG_INSERT_CLOSE_REQUEST_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to initiate closure of an account.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param reserve_pub public key of the account to close
+ * @param payto_uri where to wire the funds
+ * @param reserve_sig signature affiming that the account is to be closed
+ * @param request_timestamp time of the close request (client-side?)
+ * @param balance final balance in the reserve
+ * @param closing_fee closing fee to charge
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_close_request (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *payto_uri,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_Amount *balance,
+ const struct TALER_Amount *closing_fee);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_contract.c b/src/exchangedb/pg_insert_contract.c
new file mode 100644
index 000000000..0274f8d93
--- /dev/null
+++ b/src/exchangedb/pg_insert_contract.c
@@ -0,0 +1,93 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_contract.c
+ * @brief Implementation of the insert_contract function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_contract.h"
+#include "pg_select_contract_by_purse.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_contract (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_EncryptedContract *econtract,
+ bool *in_conflict)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (&econtract->contract_pub),
+ GNUNET_PQ_query_param_fixed_size (econtract->econtract,
+ econtract->econtract_size),
+ GNUNET_PQ_query_param_auto_from_type (&econtract->econtract_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ *in_conflict = false;
+ /* Used in #postgres_insert_contract() */
+ PREPARE (pg,
+ "insert_contract",
+ "INSERT INTO contracts"
+ " (purse_pub"
+ " ,pub_ckey"
+ " ,e_contract"
+ " ,contract_sig"
+ " ,purse_expiration"
+ " ) SELECT "
+ " $1, $2, $3, $4, purse_expiration"
+ " FROM purse_requests"
+ " WHERE purse_pub=$1"
+ " ON CONFLICT DO NOTHING;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_contract",
+ params);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+ return qs;
+ {
+ struct TALER_EncryptedContract econtract2;
+
+ qs = TEH_PG_select_contract_by_purse (pg,
+ purse_pub,
+ &econtract2);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (0 == GNUNET_memcmp (&econtract->contract_pub,
+ &econtract2.contract_pub)) &&
+ (econtract2.econtract_size ==
+ econtract->econtract_size) &&
+ (0 == memcmp (econtract2.econtract,
+ econtract->econtract,
+ econtract->econtract_size)) )
+ {
+ GNUNET_free (econtract2.econtract);
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ GNUNET_free (econtract2.econtract);
+ *in_conflict = true;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+}
diff --git a/src/exchangedb/pg_insert_contract.h b/src/exchangedb/pg_insert_contract.h
new file mode 100644
index 000000000..e2e6b5ee9
--- /dev/null
+++ b/src/exchangedb/pg_insert_contract.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_contract.h
+ * @brief implementation of the insert_contract function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_CONTRACT_H
+#define PG_INSERT_CONTRACT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to persist an encrypted contract associated with a reserve.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub the purse the contract is associated with (must exist)
+ * @param econtract the encrypted contract
+ * @param[out] in_conflict set to true if @a econtract
+ * conflicts with an existing contract;
+ * in this case, the return value will be
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT despite the failure
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_contract (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_EncryptedContract *econtract,
+ bool *in_conflict);
+
+#endif
diff --git a/src/exchangedb/pg_insert_denomination_info.c b/src/exchangedb/pg_insert_denomination_info.c
new file mode 100644
index 000000000..878bc5d81
--- /dev/null
+++ b/src/exchangedb/pg_insert_denomination_info.c
@@ -0,0 +1,101 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_denomination_info.c
+ * @brief Implementation of the insert_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_denomination_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_denomination_info (
+ void *cls,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
+{
+ struct PostgresClosure *pg = cls;
+ struct TALER_DenominationHashP denom_hash;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&issue->denom_hash),
+ TALER_PQ_query_param_denom_pub (denom_pub),
+ GNUNET_PQ_query_param_auto_from_type (&issue->signature),
+ GNUNET_PQ_query_param_timestamp (&issue->start),
+ GNUNET_PQ_query_param_timestamp (&issue->expire_withdraw),
+ GNUNET_PQ_query_param_timestamp (&issue->expire_deposit),
+ GNUNET_PQ_query_param_timestamp (&issue->expire_legal),
+ TALER_PQ_query_param_amount (pg->conn,
+ &issue->value),
+ TALER_PQ_query_param_amount (pg->conn,
+ &issue->fees.withdraw),
+ TALER_PQ_query_param_amount (pg->conn,
+ &issue->fees.deposit),
+ TALER_PQ_query_param_amount (pg->conn,
+ &issue->fees.refresh),
+ TALER_PQ_query_param_amount (pg->conn,
+ &issue->fees.refund),
+ GNUNET_PQ_query_param_uint32 (&denom_pub->age_mask.bits),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_assert (denom_pub->age_mask.bits ==
+ issue->age_mask.bits);
+ TALER_denom_pub_hash (denom_pub,
+ &denom_hash);
+ GNUNET_assert (0 ==
+ GNUNET_memcmp (&denom_hash,
+ &issue->denom_hash));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ issue->start.abs_time));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ issue->expire_withdraw.abs_time));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ issue->expire_deposit.abs_time));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ issue->expire_legal.abs_time));
+ /* check fees match denomination currency */
+ GNUNET_assert (GNUNET_YES ==
+ TALER_denom_fee_check_currency (
+ issue->value.currency,
+ &issue->fees));
+ PREPARE (pg,
+ "denomination_insert",
+ "INSERT INTO denominations "
+ "(denom_pub_hash"
+ ",denom_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin" /* value of this denom */
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",age_mask"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
+ " $11, $12, $13);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "denomination_insert",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_denomination_info.h b/src/exchangedb/pg_insert_denomination_info.h
new file mode 100644
index 000000000..663f45bd4
--- /dev/null
+++ b/src/exchangedb/pg_insert_denomination_info.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_denomination_info.h
+ * @brief implementation of the insert_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_DENOMINATION_INFO_H
+#define PG_INSERT_DENOMINATION_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Insert a denomination key's public information into the database for
+ * reference by auditors and other consistency checks.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param denom_pub the public key used for signing coins of this denomination
+ * @param issue issuing information with value, fees and other info about the coin
+ * @return status of the query
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_denomination_info (
+ void *cls,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
+
+#endif
diff --git a/src/exchangedb/pg_insert_denomination_revocation.c b/src/exchangedb/pg_insert_denomination_revocation.c
new file mode 100644
index 000000000..49445f262
--- /dev/null
+++ b/src/exchangedb/pg_insert_denomination_revocation.c
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_denomination_revocation.c
+ * @brief Implementation of the insert_denomination_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_denomination_revocation.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_denomination_revocation (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ /* Used in #postgres_insert_denomination_revocation() */
+ PREPARE (pg,
+ "denomination_revocation_insert",
+ "INSERT INTO denomination_revocations "
+ "(denominations_serial"
+ ",master_sig"
+ ") SELECT denominations_serial,$2"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "denomination_revocation_insert",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_denomination_revocation.h b/src/exchangedb/pg_insert_denomination_revocation.h
new file mode 100644
index 000000000..e3da87666
--- /dev/null
+++ b/src/exchangedb/pg_insert_denomination_revocation.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_denomination_revocation.h
+ * @brief implementation of the insert_denomination_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_DENOMINATION_REVOCATION_H
+#define PG_INSERT_DENOMINATION_REVOCATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Store information that a denomination key was revoked
+ * in the database.
+ *
+ * @param cls closure
+ * @param denom_pub_hash hash of the revoked denomination key
+ * @param master_sig signature affirming the revocation
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_denomination_revocation (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ const struct TALER_MasterSignatureP *master_sig);
+#endif
diff --git a/src/exchangedb/pg_insert_drain_profit.c b/src/exchangedb/pg_insert_drain_profit.c
new file mode 100644
index 000000000..a0de02e9b
--- /dev/null
+++ b/src/exchangedb/pg_insert_drain_profit.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_drain_profit.c
+ * @brief Implementation of the insert_drain_profit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_drain_profit.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_drain_profit (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const char *account_section,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_Amount *amount,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_string (account_section),
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_timestamp (&request_timestamp),
+ TALER_PQ_query_param_amount (pg->conn,
+ amount),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "drain_profit_insert",
+ "INSERT INTO profit_drains "
+ "(wtid"
+ ",account_section"
+ ",payto_uri"
+ ",trigger_date"
+ ",amount"
+ ",master_sig"
+ ") VALUES ($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "drain_profit_insert",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_drain_profit.h b/src/exchangedb/pg_insert_drain_profit.h
new file mode 100644
index 000000000..90183d850
--- /dev/null
+++ b/src/exchangedb/pg_insert_drain_profit.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_drain_profit.h
+ * @brief implementation of the insert_drain_profit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_DRAIN_PROFIT_H
+#define PG_INSERT_DRAIN_PROFIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to persist a request to drain profits.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param wtid wire transfer ID to use
+ * @param account_section account to drain
+ * @param payto_uri account to wire funds to
+ * @param request_timestamp when was the request made
+ * @param amount amount to wire
+ * @param master_sig signature affirming the operation
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_drain_profit (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const char *account_section,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_Amount *amount,
+ const struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_insert_global_fee.c b/src/exchangedb/pg_insert_global_fee.c
new file mode 100644
index 000000000..e78cd0b83
--- /dev/null
+++ b/src/exchangedb/pg_insert_global_fee.c
@@ -0,0 +1,137 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_global_fee.c
+ * @brief Implementation of the insert_global_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_global_fee.h"
+#include "pg_helper.h"
+#include "pg_get_global_fee.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_global_fee (void *cls,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&start_date),
+ GNUNET_PQ_query_param_timestamp (&end_date),
+ TALER_PQ_query_param_amount (pg->conn,
+ &fees->history),
+ TALER_PQ_query_param_amount (pg->conn,
+ &fees->account),
+ TALER_PQ_query_param_amount (pg->conn,
+ &fees->purse),
+ GNUNET_PQ_query_param_relative_time (&purse_timeout),
+ GNUNET_PQ_query_param_relative_time (&history_expiration),
+ GNUNET_PQ_query_param_uint32 (&purse_account_limit),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct TALER_GlobalFeeSet wx;
+ struct TALER_MasterSignatureP sig;
+ struct GNUNET_TIME_Timestamp sd;
+ struct GNUNET_TIME_Timestamp ed;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Relative pt;
+ struct GNUNET_TIME_Relative he;
+ uint32_t pal;
+
+ qs = TEH_PG_get_global_fee (pg,
+ start_date,
+ &sd,
+ &ed,
+ &wx,
+ &pt,
+ &he,
+ &pal,
+ &sig);
+ if (qs < 0)
+ return qs;
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (0 != GNUNET_memcmp (&sig,
+ master_sig))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (0 !=
+ TALER_global_fee_set_cmp (fees,
+ &wx))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (GNUNET_TIME_timestamp_cmp (sd,
+ !=,
+ start_date)) ||
+ (GNUNET_TIME_timestamp_cmp (ed,
+ !=,
+ end_date)) )
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (GNUNET_TIME_relative_cmp (purse_timeout,
+ !=,
+ pt)) ||
+ (GNUNET_TIME_relative_cmp (history_expiration,
+ !=,
+ he)) )
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (purse_account_limit != pal)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* equal record already exists */
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+
+ /* Used in #postgres_insert_global_fee */
+ PREPARE (pg,
+ "insert_global_fee",
+ "INSERT INTO global_fee "
+ "(start_date"
+ ",end_date"
+ ",history_fee"
+ ",account_fee"
+ ",purse_fee"
+ ",purse_timeout"
+ ",history_expiration"
+ ",purse_account_limit"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_global_fee",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_global_fee.h b/src/exchangedb/pg_insert_global_fee.h
new file mode 100644
index 000000000..411345dc4
--- /dev/null
+++ b/src/exchangedb/pg_insert_global_fee.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_global_fee.h
+ * @brief implementation of the insert_global_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_GLOBAL_FEE_H
+#define PG_INSERT_GLOBAL_FEE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Insert global fee data into database.
+ *
+ * @param cls closure
+ * @param start_date when does the fees go into effect
+ * @param end_date when does the fees end being valid
+ * @param fees how high is are the global fees
+ * @param purse_timeout when do purses time out
+ * @param history_expiration how long are account histories preserved
+ * @param purse_account_limit how many purses are free per account
+ * @param master_sig signature over the above by the exchange master key
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_global_fee (void *cls,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ const struct TALER_MasterSignatureP *master_sig);
+#endif
diff --git a/src/exchangedb/pg_insert_kyc_attributes.c b/src/exchangedb/pg_insert_kyc_attributes.c
new file mode 100644
index 000000000..3c94abb85
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_attributes.c
@@ -0,0 +1,110 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_insert_kyc_attributes.c
+ * @brief Implementation of the insert_kyc_attributes function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_kyc_attributes.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_attributes (
+ void *cls,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct GNUNET_ShortHashCode *kyc_prox,
+ const char *provider_section,
+ unsigned int num_checks,
+ const char *satisfied_checks[static num_checks],
+ uint32_t birthday,
+ struct GNUNET_TIME_Timestamp collection_time,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ struct GNUNET_TIME_Absolute expiration_time,
+ size_t enc_attributes_size,
+ const void *enc_attributes,
+ bool require_aml)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp expiration
+ = GNUNET_TIME_absolute_to_timestamp (expiration_time);
+ struct TALER_KycCompletedEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
+ .h_payto = *h_payto
+ };
+ char *kyc_completed_notify_s
+ = GNUNET_PQ_get_event_notify_channel (&rep.header);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&process_row),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_auto_from_type (kyc_prox),
+ GNUNET_PQ_query_param_string (provider_section),
+ GNUNET_PQ_query_param_array_ptrs_string (num_checks,
+ satisfied_checks,
+ pg->conn),
+ GNUNET_PQ_query_param_uint32 (&birthday),
+ (NULL == provider_account_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (provider_account_id),
+ (NULL == provider_legitimization_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (provider_legitimization_id),
+ GNUNET_PQ_query_param_timestamp (&collection_time),
+ GNUNET_PQ_query_param_absolute_time (&expiration_time),
+ GNUNET_PQ_query_param_timestamp (&expiration),
+ GNUNET_PQ_query_param_fixed_size (enc_attributes,
+ enc_attributes_size),
+ GNUNET_PQ_query_param_bool (require_aml),
+ GNUNET_PQ_query_param_string (kyc_completed_notify_s),
+ GNUNET_PQ_query_param_end
+ };
+ bool ok;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("out_ok",
+ &ok),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Inserting KYC attributes, wake up on %s\n",
+ kyc_completed_notify_s);
+ PREPARE (pg,
+ "insert_kyc_attributes",
+ "SELECT "
+ " out_ok"
+ " FROM exchange_do_insert_kyc_attributes "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_kyc_attributes",
+ params,
+ rs);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ GNUNET_free (kyc_completed_notify_s);
+ GNUNET_PQ_event_do_poll (pg->conn);
+ if (qs < 0)
+ return qs;
+ if (! ok)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ return qs;
+}
diff --git a/src/exchangedb/pg_insert_kyc_attributes.h b/src/exchangedb/pg_insert_kyc_attributes.h
new file mode 100644
index 000000000..35b25bdc8
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_attributes.h
@@ -0,0 +1,69 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_insert_kyc_attributes.h
+ * @brief implementation of the insert_kyc_attributes function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_KYC_ATTRIBUTES_H
+#define PG_INSERT_KYC_ATTRIBUTES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Store KYC attribute data, update KYC process status and
+ * AML status for the given account.
+ *
+ * @param cls closure
+ * @param process_row KYC process row to update
+ * @param h_payto account for which the attribute data is stored
+ * @param kyc_prox key for similarity search
+ * @param provider_section provider that must be checked
+ * @param num_checks how many checks do these attributes satisfy
+ * @param satisfied_checks array of checks satisfied by these attributes
+ * @param provider_account_id provider account ID
+ * @param provider_legitimization_id provider legitimization ID
+ * @param birthday birthdate of user, in days after 1990, or 0 if unknown or definitively adult
+ * @param collection_time when was the data collected
+ * @param expiration_time when does the data expire
+ * @param enc_attributes_size number of bytes in @a enc_attributes
+ * @param enc_attributes encrypted attribute data
+ * @param require_aml true to trigger AML
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_attributes (
+ void *cls,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct GNUNET_ShortHashCode *kyc_prox,
+ const char *provider_section,
+ unsigned int num_checks,
+ const char *satisfied_checks[static num_checks],
+ uint32_t birthday,
+ struct GNUNET_TIME_Timestamp collection_time,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ struct GNUNET_TIME_Absolute expiration_time,
+ size_t enc_attributes_size,
+ const void *enc_attributes,
+ bool require_aml);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_kyc_failure.c b/src/exchangedb/pg_insert_kyc_failure.c
new file mode 100644
index 000000000..568c39ca8
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_failure.c
@@ -0,0 +1,82 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 exchangedb/pg_insert_kyc_failure.c
+ * @brief Implementation of the insert_kyc_failure function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_kyc_failure.h"
+#include "pg_helper.h"
+#include "pg_event_notify.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_failure (
+ void *cls,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ const char *provider_account_id,
+ const char *provider_legitimization_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&process_row),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_string (provider_section),
+ NULL != provider_account_id
+ ? GNUNET_PQ_query_param_string (provider_account_id)
+ : GNUNET_PQ_query_param_null (),
+ NULL != provider_legitimization_id
+ ? GNUNET_PQ_query_param_string (provider_legitimization_id)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "insert_kyc_failure",
+ "UPDATE legitimization_processes"
+ " SET"
+ " finished=TRUE"
+ " ,provider_user_id=$4"
+ " ,provider_legitimization_id=$5"
+ " WHERE h_payto=$2"
+ " AND legitimization_process_serial_id=$1"
+ " AND provider_section=$3;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_kyc_failure",
+ params);
+ if (qs > 0)
+ {
+ /* FIXME: might want to do this eventually in the same transaction... */
+ struct TALER_KycCompletedEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
+ .h_payto = *h_payto
+ };
+
+ TEH_PG_event_notify (pg,
+ &rep.header,
+ NULL,
+ 0);
+ }
+ return qs;
+}
diff --git a/src/exchangedb/pg_insert_kyc_failure.h b/src/exchangedb/pg_insert_kyc_failure.h
new file mode 100644
index 000000000..46d08df9c
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_failure.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 exchangedb/pg_insert_kyc_failure.h
+ * @brief implementation of the insert_kyc_failure function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_KYC_FAILURE_H
+#define PG_INSERT_KYC_FAILURE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Update KYC process status to finished (and failed).
+ *
+ * @param cls closure
+ * @param process_row KYC process row to update
+ * @param h_payto account for which the attribute data is stored
+ * @param provider_section provider that must be checked
+ * @param provider_account_id provider account ID
+ * @param provider_legitimization_id provider legitimization ID
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_failure (
+ void *cls,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ const char *provider_account_id,
+ const char *provider_legitimization_id);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_kyc_requirement_for_account.c b/src/exchangedb/pg_insert_kyc_requirement_for_account.c
new file mode 100644
index 000000000..95f695297
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_requirement_for_account.c
@@ -0,0 +1,67 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_kyc_requirement_for_account.c
+ * @brief Implementation of the insert_kyc_requirement_for_account function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_kyc_requirement_for_account.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_requirement_for_account (
+ void *cls,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *requirement_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ (NULL == reserve_pub)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_string (provider_section),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("legitimization_requirement_serial_id",
+ requirement_row),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_insert_kyc_requirement_for_account() */
+ PREPARE (pg,
+ "insert_legitimization_requirement",
+ "INSERT INTO legitimization_requirements"
+ " (h_payto"
+ " ,reserve_pub"
+ " ,required_checks"
+ " ) VALUES "
+ " ($1, $2, $3)"
+ " ON CONFLICT (h_payto,required_checks) "
+ " DO UPDATE SET h_payto=$1" /* syntax requirement: dummy op */
+ " RETURNING legitimization_requirement_serial_id");
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "insert_legitimization_requirement",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_insert_kyc_requirement_for_account.h b/src/exchangedb/pg_insert_kyc_requirement_for_account.h
new file mode 100644
index 000000000..331c8ba0c
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_requirement_for_account.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_kyc_requirement_for_account.h
+ * @brief implementation of the insert_kyc_requirement_for_account function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_KYC_REQUIREMENT_FOR_ACCOUNT_H
+#define PG_INSERT_KYC_REQUIREMENT_FOR_ACCOUNT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Insert KYC requirement for @a h_payto account into table.
+ *
+ * @param cls closure
+ * @param provider_section provider that must be checked
+ * @param h_payto account that must be KYC'ed
+ * @param reserve_pub if the account is a reserve, its public key. Maybe NULL
+ * @param[out] requirement_row set to legitimization requirement row for this check
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_requirement_for_account (
+ void *cls,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *requirement_row);
+
+#endif
diff --git a/src/exchangedb/pg_insert_kyc_requirement_process.c b/src/exchangedb/pg_insert_kyc_requirement_process.c
new file mode 100644
index 000000000..a20db3388
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_requirement_process.c
@@ -0,0 +1,75 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_kyc_requirement_process.c
+ * @brief Implementation of the insert_kyc_requirement_process function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_kyc_requirement_process.h"
+#include "pg_helper.h"
+#include <gnunet/gnunet_pq_lib.h>
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_requirement_process (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ uint64_t *process_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now
+ = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_string (provider_section),
+ (NULL != provider_account_id)
+ ? GNUNET_PQ_query_param_string (provider_account_id)
+ : GNUNET_PQ_query_param_null (),
+ (NULL != provider_legitimization_id)
+ ? GNUNET_PQ_query_param_string (provider_legitimization_id)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("legitimization_process_serial_id",
+ process_row),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "insert_legitimization_process",
+ "INSERT INTO legitimization_processes"
+ " (h_payto"
+ " ,start_time"
+ " ,provider_section"
+ " ,provider_user_id"
+ " ,provider_legitimization_id"
+ " ) VALUES "
+ " ($1, $2, $3, $4, $5)"
+ " RETURNING legitimization_process_serial_id");
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "insert_legitimization_process",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_insert_kyc_requirement_process.h b/src/exchangedb/pg_insert_kyc_requirement_process.h
new file mode 100644
index 000000000..df21db8cd
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_requirement_process.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_kyc_requirement_process.h
+ * @brief implementation of the insert_kyc_requirement_process function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_KYC_REQUIREMENT_PROCESS_H
+#define PG_INSERT_KYC_REQUIREMENT_PROCESS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Begin KYC requirement process.
+ *
+ * @param cls closure
+ * @param h_payto account that must be KYC'ed
+ * @param provider_section provider that must be checked
+ * @param provider_account_id provider account ID
+ * @param provider_legitimization_id provider legitimization ID
+ * @param[out] process_row row the process is stored under
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_requirement_process (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ uint64_t *process_row);
+
+#endif
diff --git a/src/exchangedb/pg_insert_partner.c b/src/exchangedb/pg_insert_partner.c
new file mode 100644
index 000000000..d1d6069de
--- /dev/null
+++ b/src/exchangedb/pg_insert_partner.c
@@ -0,0 +1,69 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_partner.c
+ * @brief Implementation of the insert_partner function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_partner.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_partner (void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ struct GNUNET_TIME_Relative wad_frequency,
+ const struct TALER_Amount *wad_fee,
+ const char *partner_base_url,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (master_pub),
+ GNUNET_PQ_query_param_timestamp (&start_date),
+ GNUNET_PQ_query_param_timestamp (&end_date),
+ GNUNET_PQ_query_param_relative_time (&wad_frequency),
+ TALER_PQ_query_param_amount (pg->conn,
+ wad_fee),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_string (partner_base_url),
+ GNUNET_PQ_query_param_end
+ };
+
+
+ PREPARE (pg,
+ "insert_partner",
+ "INSERT INTO partners"
+ " (partner_master_pub"
+ " ,start_date"
+ " ,end_date"
+ " ,wad_frequency"
+ " ,wad_fee"
+ " ,master_sig"
+ " ,partner_base_url"
+ " ) VALUES "
+ " ($1, $2, $3, $4, $5, $6, $7)"
+ " ON CONFLICT DO NOTHING;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_partner",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_partner.h b/src/exchangedb/pg_insert_partner.h
new file mode 100644
index 000000000..3ebae786c
--- /dev/null
+++ b/src/exchangedb/pg_insert_partner.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_partner.h
+ * @brief implementation of the insert_partner function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_PARTNER_H
+#define PG_INSERT_PARTNER_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called to store configuration data about a partner
+ * exchange that we are federated with.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param master_pub public offline signing key of the partner exchange
+ * @param start_date when does the following data start to be valid
+ * @param end_date when does the validity end (exclusive)
+ * @param wad_frequency how often do we do exchange-to-exchange settlements?
+ * @param wad_fee how much do we charge for transfers to the partner
+ * @param partner_base_url base URL of the partner exchange
+ * @param master_sig signature with our offline signing key affirming the above
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_partner (void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ struct GNUNET_TIME_Relative wad_frequency,
+ const struct TALER_Amount *wad_fee,
+ const char *partner_base_url,
+ const struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_insert_purse_request.c b/src/exchangedb/pg_insert_purse_request.c
new file mode 100644
index 000000000..d8d68abe8
--- /dev/null
+++ b/src/exchangedb/pg_insert_purse_request.c
@@ -0,0 +1,126 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_purse_request.c
+ * @brief Implementation of the insert_purse_request function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_purse_request.h"
+#include "pg_get_purse_request.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_purse_request (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t age_limit,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_Amount *purse_fee,
+ const struct TALER_Amount *amount,
+ const struct TALER_PurseContractSignatureP *purse_sig,
+ bool *in_conflict)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+ uint32_t flags32 = (uint32_t) flags;
+ bool in_reserve_quota = (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
+ == (flags & TALER_WAMF_MERGE_MODE_MASK));
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (merge_pub),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_timestamp (&purse_expiration),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_uint32 (&age_limit),
+ GNUNET_PQ_query_param_uint32 (&flags32),
+ GNUNET_PQ_query_param_bool (in_reserve_quota),
+ TALER_PQ_query_param_amount (pg->conn,
+ amount),
+ TALER_PQ_query_param_amount (pg->conn,
+ purse_fee),
+ GNUNET_PQ_query_param_auto_from_type (purse_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ *in_conflict = false;
+ PREPARE (pg,
+ "insert_purse_request",
+ "INSERT INTO purse_requests"
+ " (purse_pub"
+ " ,merge_pub"
+ " ,purse_creation"
+ " ,purse_expiration"
+ " ,h_contract_terms"
+ " ,age_limit"
+ " ,flags"
+ " ,in_reserve_quota"
+ " ,amount_with_fee"
+ " ,purse_fee"
+ " ,purse_sig"
+ " ) VALUES "
+ " ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)"
+ " ON CONFLICT DO NOTHING;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_purse_request",
+ params);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+ return qs;
+ {
+ struct TALER_PurseMergePublicKeyP merge_pub2;
+ struct GNUNET_TIME_Timestamp purse_expiration2;
+ struct TALER_PrivateContractHashP h_contract_terms2;
+ uint32_t age_limit2;
+ struct TALER_Amount amount2;
+ struct TALER_Amount balance;
+ struct TALER_PurseContractSignatureP purse_sig2;
+
+ qs = TEH_PG_get_purse_request (pg,
+ purse_pub,
+ &merge_pub2,
+ &purse_expiration2,
+ &h_contract_terms2,
+ &age_limit2,
+ &amount2,
+ &balance,
+ &purse_sig2);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (age_limit2 == age_limit) &&
+ (0 == TALER_amount_cmp (amount,
+ &amount2)) &&
+ (0 == GNUNET_memcmp (&h_contract_terms2,
+ h_contract_terms)) &&
+ (0 == GNUNET_memcmp (&merge_pub2,
+ merge_pub)) )
+ {
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ *in_conflict = true;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+}
diff --git a/src/exchangedb/pg_insert_purse_request.h b/src/exchangedb/pg_insert_purse_request.h
new file mode 100644
index 000000000..37cce2a96
--- /dev/null
+++ b/src/exchangedb/pg_insert_purse_request.h
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_purse_request.h
+ * @brief implementation of the insert_purse_request function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_PURSE_REQUEST_H
+#define PG_INSERT_PURSE_REQUEST_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to create a new purse with certain meta data.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the new purse
+ * @param merge_pub public key providing the merge capability
+ * @param purse_expiration time when the purse will expire
+ * @param h_contract_terms hash of the contract for the purse
+ * @param age_limit age limit to enforce for payments into the purse
+ * @param flags flags for the operation
+ * @param purse_fee fee we are allowed to charge to the reserve (depending on @a flags)
+ * @param amount target amount (with fees) to be put into the purse
+ * @param purse_sig signature with @a purse_pub's private key affirming the above
+ * @param[out] in_conflict set to true if the meta data
+ * conflicts with an existing purse;
+ * in this case, the return value will be
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT despite the failure
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_purse_request (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t age_limit,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_Amount *purse_fee,
+ const struct TALER_Amount *amount,
+ const struct TALER_PurseContractSignatureP *purse_sig,
+ bool *in_conflict);
+
+#endif
diff --git a/src/exchangedb/pg_insert_records_by_table.c b/src/exchangedb/pg_insert_records_by_table.c
new file mode 100644
index 000000000..6ecec5bcf
--- /dev/null
+++ b/src/exchangedb/pg_insert_records_by_table.c
@@ -0,0 +1,2334 @@
+/*
+ This file is part of GNUnet
+ Copyright (C) 2020-2023 Taler Systems SA
+
+ GNUnet 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 of the License,
+ or (at your option) any later version.
+
+ GNUnet 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/**
+ * @file exchangedb/pg_insert_records_by_table.c
+ * @brief replicate_records_by_table implementation
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_records_by_table.h"
+#include "pg_helper.h"
+#include <gnunet/gnunet_pq_lib.h>
+
+
+/**
+ * Signature of helper functions of #TEH_PG_insert_records_by_table().
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ * @return transaction status code
+ */
+typedef enum GNUNET_DB_QueryStatus
+(*InsertRecordCallback)(struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td);
+
+
+/**
+ * Function called with denominations records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_denominations (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct TALER_DenominationHashP denom_hash;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&denom_hash),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.denominations.denom_type),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.denominations.age_mask),
+ TALER_PQ_query_param_denom_pub (
+ &td->details.denominations.denom_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.denominations.master_sig),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.denominations.valid_from),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.denominations.expire_withdraw),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.denominations.expire_deposit),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.denominations.expire_legal),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.denominations.coin),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.denominations.fees.withdraw),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.denominations.fees.deposit),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.denominations.fees.refresh),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.denominations.fees.refund),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_denominations",
+ "INSERT INTO denominations"
+ "(denominations_serial"
+ ",denom_pub_hash"
+ ",denom_type"
+ ",age_mask"
+ ",denom_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin"
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
+ " $11, $12, $13, $14, $15);");
+
+ TALER_denom_pub_hash (
+ &td->details.denominations.denom_pub,
+ &denom_hash);
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_denominations",
+ params);
+}
+
+
+/**
+ * Function called with denomination_revocations records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_denomination_revocations (
+ struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.denomination_revocations.master_sig),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.denomination_revocations.denominations_serial),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_denomination_revocations",
+ "INSERT INTO denomination_revocations"
+ "(denom_revocations_serial_id"
+ ",master_sig"
+ ",denominations_serial"
+ ") VALUES "
+ "($1, $2, $3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_denomination_revocations",
+ params);
+}
+
+
+/**
+ * Function called with denominations records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wire_targets (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct TALER_PaytoHashP payto_hash;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&payto_hash),
+ GNUNET_PQ_query_param_string (
+ td->details.wire_targets.payto_uri),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wire_targets",
+ "INSERT INTO wire_targets"
+ "(wire_target_serial_id"
+ ",wire_target_h_payto"
+ ",payto_uri"
+ ") VALUES "
+ "($1, $2, $3);");
+ TALER_payto_hash (
+ td->details.wire_targets.payto_uri,
+ &payto_hash);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wire_targets",
+ params);
+}
+
+
+/**
+ * Function called with records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_legitimization_processes (struct PostgresClosure *pg,
+ const struct
+ TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.legitimization_processes.h_payto),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.legitimization_processes.expiration_time),
+ GNUNET_PQ_query_param_string (
+ td->details.legitimization_processes.provider_section),
+ GNUNET_PQ_query_param_string (
+ td->details.legitimization_processes.provider_user_id),
+ GNUNET_PQ_query_param_string (
+ td->details.legitimization_processes.provider_legitimization_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_legitimization_processes",
+ "INSERT INTO legitimization_processes"
+ "(legitimization_process_serial_id"
+ ",h_payto"
+ ",expiration_time"
+ ",provider_section"
+ ",provider_user_id"
+ ",provider_legitimization_id"
+ ") VALUES "
+ "($1, $3, $4, $5, $6, %7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_legitimization_processes",
+ params);
+}
+
+
+/**
+ * Function called with records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_legitimization_requirements (struct PostgresClosure *pg,
+ const struct
+ TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.legitimization_requirements.h_payto),
+ td->details.legitimization_requirements.no_reserve_pub
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (
+ &td->details.legitimization_requirements.reserve_pub),
+ GNUNET_PQ_query_param_string (
+ td->details.legitimization_requirements.required_checks),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_legitimization_requirements",
+ "INSERT INTO legitimization_requirements"
+ "(legitimization_requirement_serial_id"
+ ",h_payto"
+ ",reserve_pub"
+ ",required_checks"
+ ") VALUES "
+ "($1, $2, $3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_legitimization_requirements",
+ params);
+}
+
+
+/**
+ * Function called with reserves records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_reserves (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.reserves.reserve_pub),
+ GNUNET_PQ_query_param_timestamp (&td->details.reserves.expiration_date),
+ GNUNET_PQ_query_param_timestamp (&td->details.reserves.gc_date),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_reserves",
+ "INSERT INTO reserves"
+ "(reserve_uuid"
+ ",reserve_pub"
+ ",expiration_date"
+ ",gc_date"
+ ") VALUES "
+ "($1, $2, $3, $4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_reserves",
+ params);
+}
+
+
+/**
+ * Function called with reserves_in records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_reserves_in (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (&td->details.reserves_in.wire_reference),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.reserves_in.credit),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_in.sender_account_h_payto),
+ GNUNET_PQ_query_param_string (
+ td->details.reserves_in.exchange_account_section),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.reserves_in.execution_date),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.reserves_in.reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_reserves_in",
+ "INSERT INTO reserves_in"
+ "(reserve_in_serial_id"
+ ",wire_reference"
+ ",credit"
+ ",wire_source_h_payto"
+ ",exchange_account_section"
+ ",execution_date"
+ ",reserve_pub"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_reserves_in",
+ params);
+}
+
+
+/**
+ * Function called with reserves_open_requests records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_reserves_open_requests (struct PostgresClosure *pg,
+ const struct
+ TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.reserves_open_requests.expiration_date),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_open_requests.reserve_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.reserves_open_requests.reserve_payment),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.reserves_open_requests.requested_purse_limit),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_reserves_open_requests",
+ "INSERT INTO reserves_open_requests"
+ "(open_request_uuid"
+ ",reserve_pub"
+ ",request_timestamp"
+ ",expiration_date"
+ ",reserve_sig"
+ ",reserve_payment"
+ ",requested_purse_limit"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_reserves_open_requests",
+ params);
+}
+
+
+/**
+ * Function called with reserves_open_requests records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_reserves_open_deposits (
+ struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_open_deposits.coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_open_deposits.coin_sig),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_open_deposits.reserve_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.reserves_open_deposits.contribution),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_reserves_open_deposits",
+ "INSERT INTO reserves_open_deposits"
+ "(reserve_open_deposit_uuid"
+ ",reserve_sig"
+ ",reserve_pub"
+ ",coin_pub"
+ ",coin_sig"
+ ",contribution"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_reserves_open_deposits",
+ params);
+}
+
+
+/**
+ * Function called with reserves_close records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_reserves_close (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.reserves_close.execution_date),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_close.wtid),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_close.sender_account_h_payto),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.reserves_close.amount),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.reserves_close.closing_fee),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_close.reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_reserves_close",
+ "INSERT INTO reserves_close"
+ "(close_uuid"
+ ",execution_date"
+ ",wtid"
+ ",wire_target_h_payto"
+ ",amount"
+ ",closing_fee"
+ ",reserve_pub"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_reserves_close",
+ params);
+}
+
+
+/**
+ * Function called with reserves_out records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_reserves_out (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_out.h_blind_ev),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.reserves_out.denominations_serial),
+ TALER_PQ_query_param_blinded_denom_sig (
+ &td->details.reserves_out.denom_sig),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.reserves_out.reserve_uuid),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_out.reserve_sig),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.reserves_out.execution_date),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.reserves_out.amount_with_fee),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_reserves_out",
+ "INSERT INTO reserves_out"
+ "(reserve_out_serial_id"
+ ",h_blind_ev"
+ ",denominations_serial"
+ ",denom_sig"
+ ",reserve_uuid"
+ ",reserve_sig"
+ ",execution_date"
+ ",amount_with_fee"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_reserves_out",
+ params);
+}
+
+
+/**
+ * Function called with auditors records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_auditors (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.auditors.auditor_pub),
+ GNUNET_PQ_query_param_string (td->details.auditors.auditor_name),
+ GNUNET_PQ_query_param_string (td->details.auditors.auditor_url),
+ GNUNET_PQ_query_param_bool (td->details.auditors.is_active),
+ GNUNET_PQ_query_param_timestamp (&td->details.auditors.last_change),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_auditors",
+ "INSERT INTO auditors"
+ "(auditor_uuid"
+ ",auditor_pub"
+ ",auditor_name"
+ ",auditor_url"
+ ",is_active"
+ ",last_change"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_auditors",
+ params);
+}
+
+
+/**
+ * Function called with auditor_denom_sigs records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_auditor_denom_sigs (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (&td->details.auditor_denom_sigs.auditor_uuid),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.auditor_denom_sigs.denominations_serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.auditor_denom_sigs.auditor_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_auditor_denom_sigs",
+ "INSERT INTO auditor_denom_sigs"
+ "(auditor_denom_serial"
+ ",auditor_uuid"
+ ",denominations_serial"
+ ",auditor_sig"
+ ") VALUES "
+ "($1, $2, $3, $4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_auditor_denom_sigs",
+ params);
+}
+
+
+/**
+ * Function called with exchange_sign_keys records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_exchange_sign_keys (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.exchange_sign_keys.exchange_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.exchange_sign_keys.master_sig),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.exchange_sign_keys.meta.start),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.exchange_sign_keys.meta.expire_sign),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.exchange_sign_keys.meta.expire_legal),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_exchange_sign_keys",
+ "INSERT INTO exchange_sign_keys"
+ "(esk_serial"
+ ",exchange_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_sign"
+ ",expire_legal"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_exchange_sign_keys",
+ params);
+}
+
+
+/**
+ * Function called with signkey_revocations records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_signkey_revocations (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (&td->details.signkey_revocations.esk_serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.signkey_revocations.master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_signkey_revocations",
+ "INSERT INTO signkey_revocations"
+ "(signkey_revocations_serial_id"
+ ",esk_serial"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_signkey_revocations",
+ params);
+}
+
+
+/**
+ * Function called with known_coins records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_known_coins (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.known_coins.coin_pub),
+ TALER_PQ_query_param_denom_sig (
+ &td->details.known_coins.denom_sig),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.known_coins.denominations_serial),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_known_coins",
+ "INSERT INTO known_coins"
+ "(known_coin_id"
+ ",coin_pub"
+ ",denom_sig"
+ ",denominations_serial"
+ ") VALUES "
+ "($1, $2, $3, $4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_known_coins",
+ params);
+}
+
+
+/**
+ * Function called with refresh_commitments records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_refresh_commitments (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.refresh_commitments.rc),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.refresh_commitments.old_coin_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.refresh_commitments.amount_with_fee),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.refresh_commitments.noreveal_index),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.refresh_commitments.old_coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_refresh_commitments",
+ "INSERT INTO refresh_commitments"
+ "(melt_serial_id"
+ ",rc"
+ ",old_coin_sig"
+ ",amount_with_fee"
+ ",noreveal_index"
+ ",old_coin_pub"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_refresh_commitments",
+ params);
+}
+
+
+/**
+ * Function called with refresh_revealed_coins records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_refresh_revealed_coins (
+ struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_HashCode h_coin_ev;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.refresh_revealed_coins.freshcoin_index),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.refresh_revealed_coins.link_sig),
+ GNUNET_PQ_query_param_fixed_size (
+ td->details.refresh_revealed_coins.coin_ev,
+ td->details.refresh_revealed_coins.
+ coin_ev_size),
+ GNUNET_PQ_query_param_auto_from_type (&h_coin_ev),
+ TALER_PQ_query_param_blinded_denom_sig (
+ &td->details.refresh_revealed_coins.ev_sig),
+ TALER_PQ_query_param_exchange_withdraw_values (
+ &td->details.refresh_revealed_coins.ewv),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.refresh_revealed_coins.denominations_serial),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.refresh_revealed_coins.melt_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_refresh_revealed_coins",
+ "INSERT INTO refresh_revealed_coins"
+ "(rrc_serial"
+ ",freshcoin_index"
+ ",link_sig"
+ ",coin_ev"
+ ",h_coin_ev"
+ ",ev_sig"
+ ",ewv"
+ ",denominations_serial"
+ ",melt_serial_id"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9);");
+ GNUNET_CRYPTO_hash (td->details.refresh_revealed_coins.coin_ev,
+ td->details.refresh_revealed_coins.coin_ev_size,
+ &h_coin_ev);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_refresh_revealed_coins",
+ params);
+}
+
+
+/**
+ * Function called with refresh_transfer_keys records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_refresh_transfer_keys (
+ struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.refresh_transfer_keys.tp),
+ GNUNET_PQ_query_param_fixed_size (
+ &td->details.refresh_transfer_keys.tprivs[0],
+ (TALER_CNC_KAPPA - 1)
+ * sizeof (struct TALER_TransferPrivateKeyP)),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.refresh_transfer_keys.melt_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_refresh_transfer_keys",
+ "INSERT INTO refresh_transfer_keys"
+ "(rtc_serial"
+ ",transfer_pub"
+ ",transfer_privs"
+ ",melt_serial_id"
+ ") VALUES "
+ "($1, $2, $3, $4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_refresh_transfer_keys",
+ params);
+}
+
+
+/**
+ * Function called with batch deposits records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_batch_deposits (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (&td->details.batch_deposits.shard),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.batch_deposits.merchant_pub),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.batch_deposits.wallet_timestamp),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.batch_deposits.exchange_timestamp),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.batch_deposits.refund_deadline),
+ GNUNET_PQ_query_param_timestamp (&td->details.batch_deposits.wire_deadline),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.batch_deposits.h_contract_terms),
+ td->details.batch_deposits.no_wallet_data_hash
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (
+ &td->details.batch_deposits.wallet_data_hash),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.batch_deposits.wire_salt),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.batch_deposits.wire_target_h_payto),
+ GNUNET_PQ_query_param_bool (td->details.batch_deposits.policy_blocked),
+ td->details.batch_deposits.no_policy_details
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (
+ &td->details.batch_deposits.policy_details_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_batch_deposits",
+ "INSERT INTO batch_deposits"
+ "(batch_deposit_serial_id"
+ ",shard"
+ ",merchant_pub"
+ ",wallet_timestamp"
+ ",exchange_timestamp"
+ ",refund_deadline"
+ ",wire_deadline"
+ ",h_contract_terms"
+ ",wallet_data_hash"
+ ",wire_salt"
+ ",wire_target_h_payto"
+ ",policy_details_serial_id"
+ ",policy_blocked"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
+ " $11, $12, $13);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_batch_deposits",
+ params);
+}
+
+
+/**
+ * Function called with deposits records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_coin_deposits (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.coin_deposits.batch_deposit_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.coin_deposits.coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.coin_deposits.coin_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.coin_deposits.amount_with_fee),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_coin_deposits",
+ "INSERT INTO coin_deposits"
+ "(coin_deposit_serial_id"
+ ",batch_deposit_serial_id"
+ ",coin_pub"
+ ",coin_sig"
+ ",amount_with_fee"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_coin_deposits",
+ params);
+}
+
+
+/**
+ * Function called with refunds records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_refunds (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.refunds.coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.refunds.merchant_sig),
+ GNUNET_PQ_query_param_uint64 (&td->details.refunds.rtransaction_id),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.refunds.amount_with_fee),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.refunds.batch_deposit_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_refunds",
+ "INSERT INTO refunds"
+ "(refund_serial_id"
+ ",coin_pub"
+ ",merchant_sig"
+ ",rtransaction_id"
+ ",amount_with_fee"
+ ",batch_deposit_serial_id"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_refunds",
+ params);
+}
+
+
+/**
+ * Function called with wire_out records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wire_out (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_timestamp (&td->details.wire_out.execution_date),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.wire_out.wtid_raw),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wire_out.wire_target_h_payto),
+ GNUNET_PQ_query_param_string (
+ td->details.wire_out.exchange_account_section),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wire_out.amount),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wire_out",
+ "INSERT INTO wire_out"
+ "(wireout_uuid"
+ ",execution_date"
+ ",wtid_raw"
+ ",wire_target_h_payto"
+ ",exchange_account_section"
+ ",amount"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wire_out",
+ params);
+}
+
+
+/**
+ * Function called with aggregation_tracking records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_aggregation_tracking (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.aggregation_tracking.batch_deposit_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.aggregation_tracking.wtid_raw),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_aggregation_tracking",
+ "INSERT INTO aggregation_tracking"
+ "(aggregation_serial_id"
+ ",batch_deposit_serial_id"
+ ",wtid_raw"
+ ") VALUES "
+ "($1, $2, $3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_aggregation_tracking",
+ params);
+}
+
+
+/**
+ * Function called with wire_fee records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wire_fee (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_string (td->details.wire_fee.wire_method),
+ GNUNET_PQ_query_param_timestamp (&td->details.wire_fee.start_date),
+ GNUNET_PQ_query_param_timestamp (&td->details.wire_fee.end_date),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wire_fee.fees.wire),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wire_fee.fees.closing),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.wire_fee.master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wire_fee",
+ "INSERT INTO wire_fee"
+ "(wire_fee_serial"
+ ",wire_method"
+ ",start_date"
+ ",end_date"
+ ",wire_fee"
+ ",closing_fee"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wire_fee",
+ params);
+}
+
+
+/**
+ * Function called with wire_fee records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_global_fee (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (
+ &td->serial),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.global_fee.start_date),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.global_fee.end_date),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.global_fee.fees.history),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.global_fee.fees.account),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.global_fee.fees.purse),
+ GNUNET_PQ_query_param_relative_time (
+ &td->details.global_fee.purse_timeout),
+ GNUNET_PQ_query_param_relative_time (
+ &td->details.global_fee.history_expiration),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.global_fee.purse_account_limit),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.global_fee.master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_global_fee",
+ "INSERT INTO global_fee"
+ "(global_fee_serial"
+ ",start_date"
+ ",end_date"
+ ",history_fee"
+ ",account_fee"
+ ",purse_fee"
+ ",purse_timeout"
+ ",history_expiration"
+ ",purse_account_limit"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_global_fee",
+ params);
+}
+
+
+/**
+ * Function called with recoup records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_recoup (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.recoup.coin_sig),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.recoup.coin_blind),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.recoup.amount),
+ GNUNET_PQ_query_param_timestamp (&td->details.recoup.timestamp),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.recoup.coin_pub),
+ GNUNET_PQ_query_param_uint64 (&td->details.recoup.reserve_out_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_recoup",
+ "INSERT INTO recoup"
+ "(recoup_uuid"
+ ",coin_sig"
+ ",coin_blind"
+ ",amount"
+ ",recoup_timestamp"
+ ",coin_pub"
+ ",reserve_out_serial_id"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_recoup",
+ params);
+}
+
+
+/**
+ * Function called with recoup_refresh records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_recoup_refresh (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.recoup_refresh.coin_sig),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.recoup_refresh.coin_blind),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.recoup_refresh.amount),
+ GNUNET_PQ_query_param_timestamp (&td->details.recoup_refresh.timestamp),
+ GNUNET_PQ_query_param_uint64 (&td->details.recoup_refresh.known_coin_id),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.recoup.coin_pub),
+ GNUNET_PQ_query_param_uint64 (&td->details.recoup_refresh.rrc_serial),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_recoup_refresh",
+ "INSERT INTO recoup_refresh"
+ "(recoup_refresh_uuid"
+ ",coin_sig"
+ ",coin_blind"
+ ",amount"
+ ",recoup_timestamp"
+ ",known_coin_id"
+ ",coin_pub"
+ ",rrc_serial"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_recoup_refresh",
+ params);
+}
+
+
+/**
+ * Function called with extensions records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_extensions (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_string (td->details.extensions.name),
+ NULL == td->details.extensions.manifest ?
+ GNUNET_PQ_query_param_null () :
+ GNUNET_PQ_query_param_string (td->details.extensions.manifest),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_extensions",
+ "INSERT INTO extensions"
+ "(extension_id"
+ ",name"
+ ",manifest"
+ ") VALUES "
+ "($1, $2, $3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_extensions",
+ params);
+}
+
+
+/**
+ * Function called with policy_details records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_policy_details (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.policy_details.hash_code),
+ (td->details.policy_details.no_policy_json)
+ ? GNUNET_PQ_query_param_null ()
+ : TALER_PQ_query_param_json (td->details.policy_details.policy_json),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.policy_details.commitment),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.policy_details.accumulated_total),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.policy_details.fee),
+ TALER_PQ_query_param_amount (pg->conn,
+ &td->details.policy_details.transferable),
+ GNUNET_PQ_query_param_timestamp (&td->details.policy_details.deadline),
+ GNUNET_PQ_query_param_uint16 (
+ &td->details.policy_details.fulfillment_state),
+ (td->details.policy_details.no_fulfillment_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (
+ &td->details.policy_details.fulfillment_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_policy_details",
+ "INSERT INTO policy_details"
+ "(policy_details_serial_id"
+ ",policy_hash_code"
+ ",policy_json"
+ ",deadline"
+ ",commitment"
+ ",accumulated_total"
+ ",fee"
+ ",transferable"
+ ",fulfillment_state"
+ ",fulfillment_id"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_policy_details",
+ params);
+}
+
+
+/**
+ * Function called with policy_fulfillment records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_policy_fulfillments (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.policy_fulfillments.fulfillment_timestamp),
+ (NULL == td->details.policy_fulfillments.fulfillment_proof)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (
+ td->details.policy_fulfillments.fulfillment_proof),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.policy_fulfillments.h_fulfillment_proof),
+ GNUNET_PQ_query_param_fixed_size (
+ td->details.policy_fulfillments.policy_hash_codes,
+ td->details.policy_fulfillments.policy_hash_codes_count),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_policy_fulfillments",
+ "INSERT INTO policy_fulfillments "
+ "(fulfillment_id"
+ ",fulfillment_timestamp"
+ ",fulfillment_proof"
+ ",h_fulfillment_proof"
+ ",policy_hash_codes"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_policy_fulfillments",
+ params);
+}
+
+
+/**
+ * Function called with purse_requests records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_purse_requests (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_requests.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_requests.merge_pub),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.purse_requests.purse_creation),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.purse_requests.purse_expiration),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_requests.h_contract_terms),
+ GNUNET_PQ_query_param_uint32 (&td->details.purse_requests.age_limit),
+ GNUNET_PQ_query_param_uint32 (&td->details.purse_requests.flags),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.purse_requests.amount_with_fee),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.purse_requests.purse_fee),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_requests.purse_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_purse_requests",
+ "INSERT INTO purse_requests"
+ "(purse_requests_serial_id"
+ ",purse_pub"
+ ",merge_pub"
+ ",purse_creation"
+ ",purse_expiration"
+ ",h_contract_terms"
+ ",age_limit"
+ ",flags"
+ ",amount_with_fee"
+ ",purse_fee"
+ ",purse_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_purse_requests",
+ params);
+}
+
+
+/**
+ * Function called with purse_decision records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_purse_decision (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_decision.purse_pub),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.purse_decision.action_timestamp),
+ GNUNET_PQ_query_param_bool (
+ td->details.purse_decision.refunded),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_purse_refunds",
+ "INSERT INTO purse_refunds"
+ "(purse_refunds_serial_id"
+ ",purse_pub"
+ ",action_timestamp"
+ ",refunded"
+ ") VALUES "
+ "($1, $2, $3, $4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_purse_decision",
+ params);
+}
+
+
+/**
+ * Function called with purse_merges records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_purse_merges (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (&td->details.purse_merges.partner_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_merges.reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.purse_merges.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.purse_merges.merge_sig),
+ GNUNET_PQ_query_param_timestamp (&td->details.purse_merges.merge_timestamp),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_purse_merges",
+ "INSERT INTO purse_merges"
+ "(purse_merge_request_serial_id"
+ ",partner_serial_id"
+ ",reserve_pub"
+ ",purse_pub"
+ ",merge_sig"
+ ",merge_timestamp"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_purse_merges",
+ params);
+}
+
+
+/**
+ * Function called with purse_deposits records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_purse_deposits (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.purse_deposits.partner_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_deposits.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.purse_deposits.coin_pub),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.purse_deposits.amount_with_fee),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.purse_deposits.coin_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_purse_deposits",
+ "INSERT INTO purse_deposits"
+ "(purse_deposit_serial_id"
+ ",partner_serial_id"
+ ",purse_pub"
+ ",coin_pub"
+ ",amount_with_fee"
+ ",coin_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_purse_deposits",
+ params);
+}
+
+
+/**
+x * Function called with account_mergers records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_account_mergers (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.account_merges.reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.account_merges.reserve_sig),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.account_merges.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.account_merges.wallet_h_payto),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_account_merges",
+ "INSERT INTO account_merges"
+ "(account_merge_request_serial_id"
+ ",reserve_pub"
+ ",reserve_sig"
+ ",purse_pub"
+ ",wallet_h_payto"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_account_merges",
+ params);
+}
+
+
+/**
+ * Function called with history_requests records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_history_requests (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.history_requests.reserve_pub),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.history_requests.request_timestamp),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.history_requests.reserve_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.history_requests.history_fee),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_history_requests",
+ "INSERT INTO history_requests"
+ "(history_request_serial_id"
+ ",reserve_pub"
+ ",request_timestamp"
+ ",reserve_sig"
+ ",history_fee"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_history_requests",
+ params);
+}
+
+
+/**
+ * Function called with close_requests records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_close_requests (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.close_requests.reserve_pub),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.close_requests.close_timestamp),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.close_requests.reserve_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.close_requests.close),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.close_requests.close_fee),
+ GNUNET_PQ_query_param_string (
+ td->details.close_requests.payto_uri),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_close_requests",
+ "INSERT INTO close_requests"
+ "(close_request_serial_id"
+ ",reserve_pub"
+ ",close_timestamp"
+ ",reserve_sig"
+ ",close"
+ ",close_fee"
+ ",payto_uri"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_close_requests",
+ params);
+}
+
+
+/**
+ * Function called with wads_out records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wads_out (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.wads_out.wad_id),
+ GNUNET_PQ_query_param_uint64 (&td->details.wads_out.partner_serial_id),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_out.amount),
+ GNUNET_PQ_query_param_timestamp (&td->details.wads_out.execution_time),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wads_out",
+ "INSERT INTO wads_out"
+ "(wad_out_serial_id"
+ ",wad_id"
+ ",partner_serial_id"
+ ",amount"
+ ",execution_time"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wads_out",
+ params);
+}
+
+
+/**
+ * Function called with wads_out_entries records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wads_out_entries (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.wads_out_entries.wad_out_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_out_entries.reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_out_entries.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_out_entries.h_contract),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.wads_out_entries.purse_expiration),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.wads_out_entries.merge_timestamp),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_out_entries.amount_with_fee),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_out_entries.wad_fee),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_out_entries.deposit_fees),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_out_entries.reserve_sig),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_out_entries.purse_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wad_out_entries",
+ "INSERT INTO wad_out_entries"
+ "(wad_out_entry_serial_id"
+ ",wad_out_serial_id"
+ ",reserve_pub"
+ ",purse_pub"
+ ",h_contract"
+ ",purse_expiration"
+ ",merge_timestamp"
+ ",amount_with_fee"
+ ",wad_fee"
+ ",deposit_fees"
+ ",reserve_sig"
+ ",purse_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wads_out_entries",
+ params);
+}
+
+
+/**
+ * Function called with wads_in records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wads_in (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.wads_in.wad_id),
+ GNUNET_PQ_query_param_string (td->details.wads_in.origin_exchange_url),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_in.amount),
+ GNUNET_PQ_query_param_timestamp (&td->details.wads_in.arrival_time),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wads_in",
+ "INSERT INTO wads_in"
+ "(wad_in_serial_id"
+ ",wad_id"
+ ",origin_exchange_url"
+ ",amount"
+ ",arrival_time"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wads_in",
+ params);
+}
+
+
+/**
+ * Function called with wads_in_entries records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wads_in_entries (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_in_entries.reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_in_entries.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_in_entries.h_contract),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.wads_in_entries.purse_expiration),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.wads_in_entries.merge_timestamp),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_in_entries.amount_with_fee),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_in_entries.wad_fee),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_in_entries.deposit_fees),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_in_entries.reserve_sig),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_in_entries.purse_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wad_in_entries",
+ "INSERT INTO wad_in_entries"
+ "(wad_in_entry_serial_id"
+ ",wad_in_serial_id"
+ ",reserve_pub"
+ ",purse_pub"
+ ",h_contract"
+ ",purse_expiration"
+ ",merge_timestamp"
+ ",amount_with_fee"
+ ",wad_fee"
+ ",deposit_fees"
+ ",reserve_sig"
+ ",purse_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wads_in_entries",
+ params);
+}
+
+
+/**
+ * Function called with profit_drains records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_profit_drains (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.profit_drains.wtid),
+ GNUNET_PQ_query_param_string (
+ td->details.profit_drains.account_section),
+ GNUNET_PQ_query_param_string (
+ td->details.profit_drains.payto_uri),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.profit_drains.trigger_date),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.profit_drains.amount),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.profit_drains.master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_profit_drains",
+ "INSERT INTO profit_drains"
+ "(profit_drain_serial_id"
+ ",wtid"
+ ",account_section"
+ ",payto_uri"
+ ",trigger_date"
+ ",amount"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_profit_drains",
+ params);
+}
+
+
+/**
+ * Function called with aml_staff records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_aml_staff (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.aml_staff.decider_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.aml_staff.master_sig),
+ GNUNET_PQ_query_param_string (
+ td->details.aml_staff.decider_name),
+ GNUNET_PQ_query_param_bool (
+ td->details.aml_staff.is_active),
+ GNUNET_PQ_query_param_bool (
+ td->details.aml_staff.read_only),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.aml_staff.last_change),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_aml_staff",
+ "INSERT INTO aml_staff"
+ "(aml_staff_uuid"
+ ",decider_pub"
+ ",master_sig"
+ ",decider_name"
+ ",is_active"
+ ",read_only"
+ ",last_change"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_aml_staff",
+ params);
+}
+
+
+/**
+ * Function called with aml_history records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_aml_history (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ uint32_t status32 = td->details.aml_history.new_status;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.aml_history.h_payto),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.aml_history.new_threshold),
+ GNUNET_PQ_query_param_uint32 (
+ &status32),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.aml_history.decision_time),
+ GNUNET_PQ_query_param_string (
+ td->details.aml_history.justification),
+ (NULL == td->details.aml_history.kyc_requirements)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (
+ td->details.aml_history.kyc_requirements),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.aml_history.kyc_req_row),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.aml_history.decider_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.aml_history.decider_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_aml_history",
+ "INSERT INTO aml_history"
+ "(aml_history_serial_id"
+ ",h_payto"
+ ",new_threshold"
+ ",new_status"
+ ",decision_time"
+ ",justification"
+ ",kyc_requirements"
+ ",kyc_req_row"
+ ",decider_pub"
+ ",decider_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_aml_history",
+ params);
+}
+
+
+/**
+ * Function called with kyc_attributes records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_kyc_attributes (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.kyc_attributes.h_payto),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.kyc_attributes.kyc_prox),
+ GNUNET_PQ_query_param_string (
+ td->details.kyc_attributes.provider),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.kyc_attributes.collection_time),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.kyc_attributes.expiration_time),
+ GNUNET_PQ_query_param_fixed_size (
+ &td->details.kyc_attributes.encrypted_attributes,
+ td->details.kyc_attributes.encrypted_attributes_size),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_kyc_attributes",
+ "INSERT INTO kyc_attributes"
+ "(kyc_attributes_serial_id"
+ ",h_payto"
+ ",kyc_prox"
+ ",provider"
+ ",collection_time"
+ ",expiration_time"
+ ",encrypted_attributes"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_kyc_attributes",
+ params);
+}
+
+
+/**
+ * Function called with purse_deletion records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_purse_deletion (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_deletion.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_deletion.purse_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_purse_deletion",
+ "INSERT INTO purse_deletion"
+ "(purse_deletion_serial_id"
+ ",purse_pub"
+ ",purse_sig"
+ ") VALUES "
+ "($1, $2, $3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_purse_deletion",
+ params);
+}
+
+
+/**
+ * Function called with age_withdraw records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_age_withdraw (struct PostgresClosure *pg,
+ const struct
+ TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.age_withdraw.h_commitment),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.age_withdraw.amount_with_fee),
+ GNUNET_PQ_query_param_uint16 (
+ &td->details.age_withdraw.max_age),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.age_withdraw.reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.age_withdraw.reserve_sig),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.age_withdraw.noreveal_index),
+ /* TODO: other fields, too! */
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_age_withdraw",
+ "INSERT INTO age_withdraw"
+ "(age_withdraw_commitment_id"
+ ",h_commitment"
+ ",amount_with_fee"
+ ",max_age"
+ ",reserve_pub"
+ ",reserve_sig"
+ ",noreveal_index"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_age_withdraw",
+ params);
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_records_by_table (void *cls,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct PostgresClosure *pg = cls;
+ InsertRecordCallback rh = NULL;
+
+ switch (td->table)
+ {
+ case TALER_EXCHANGEDB_RT_DENOMINATIONS:
+ rh = &irbt_cb_table_denominations;
+ break;
+ case TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS:
+ rh = &irbt_cb_table_denomination_revocations;
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_TARGETS:
+ rh = &irbt_cb_table_wire_targets;
+ break;
+ case TALER_EXCHANGEDB_RT_LEGITIMIZATION_PROCESSES:
+ rh = &irbt_cb_table_legitimization_processes;
+ break;
+ case TALER_EXCHANGEDB_RT_LEGITIMIZATION_REQUIREMENTS:
+ rh = &irbt_cb_table_legitimization_requirements;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES:
+ rh = &irbt_cb_table_reserves;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_IN:
+ rh = &irbt_cb_table_reserves_in;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_CLOSE:
+ rh = &irbt_cb_table_reserves_close;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OPEN_REQUESTS:
+ rh = &irbt_cb_table_reserves_open_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OPEN_DEPOSITS:
+ rh = &irbt_cb_table_reserves_open_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OUT:
+ rh = &irbt_cb_table_reserves_out;
+ break;
+ case TALER_EXCHANGEDB_RT_AUDITORS:
+ rh = &irbt_cb_table_auditors;
+ break;
+ case TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS:
+ rh = &irbt_cb_table_auditor_denom_sigs;
+ break;
+ case TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS:
+ rh = &irbt_cb_table_exchange_sign_keys;
+ break;
+ case TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS:
+ rh = &irbt_cb_table_signkey_revocations;
+ break;
+ case TALER_EXCHANGEDB_RT_KNOWN_COINS:
+ rh = &irbt_cb_table_known_coins;
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS:
+ rh = &irbt_cb_table_refresh_commitments;
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS:
+ rh = &irbt_cb_table_refresh_revealed_coins;
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS:
+ rh = &irbt_cb_table_refresh_transfer_keys;
+ break;
+ case TALER_EXCHANGEDB_RT_BATCH_DEPOSITS:
+ rh = &irbt_cb_table_batch_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_COIN_DEPOSITS:
+ rh = &irbt_cb_table_coin_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_REFUNDS:
+ rh = &irbt_cb_table_refunds;
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_OUT:
+ rh = &irbt_cb_table_wire_out;
+ break;
+ case TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING:
+ rh = &irbt_cb_table_aggregation_tracking;
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_FEE:
+ rh = &irbt_cb_table_wire_fee;
+ break;
+ case TALER_EXCHANGEDB_RT_GLOBAL_FEE:
+ rh = &irbt_cb_table_global_fee;
+ break;
+ case TALER_EXCHANGEDB_RT_RECOUP:
+ rh = &irbt_cb_table_recoup;
+ break;
+ case TALER_EXCHANGEDB_RT_RECOUP_REFRESH:
+ rh = &irbt_cb_table_recoup_refresh;
+ break;
+ case TALER_EXCHANGEDB_RT_EXTENSIONS:
+ rh = &irbt_cb_table_extensions;
+ break;
+ case TALER_EXCHANGEDB_RT_POLICY_DETAILS:
+ rh = &irbt_cb_table_policy_details;
+ break;
+ case TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS:
+ rh = &irbt_cb_table_policy_fulfillments;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_REQUESTS:
+ rh = &irbt_cb_table_purse_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DECISION:
+ rh = &irbt_cb_table_purse_decision;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_MERGES:
+ rh = &irbt_cb_table_purse_merges;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DEPOSITS:
+ rh = &irbt_cb_table_purse_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_ACCOUNT_MERGES:
+ rh = &irbt_cb_table_account_mergers;
+ break;
+ case TALER_EXCHANGEDB_RT_HISTORY_REQUESTS:
+ rh = &irbt_cb_table_history_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_CLOSE_REQUESTS:
+ rh = &irbt_cb_table_close_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_OUT:
+ rh = &irbt_cb_table_wads_out;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_OUT_ENTRIES:
+ rh = &irbt_cb_table_wads_out_entries;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_IN:
+ rh = &irbt_cb_table_wads_in;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_IN_ENTRIES:
+ rh = &irbt_cb_table_wads_in_entries;
+ break;
+ case TALER_EXCHANGEDB_RT_PROFIT_DRAINS:
+ rh = &irbt_cb_table_profit_drains;
+ break;
+ case TALER_EXCHANGEDB_RT_AML_STAFF:
+ rh = &irbt_cb_table_aml_staff;
+ break;
+ case TALER_EXCHANGEDB_RT_AML_HISTORY:
+ rh = &irbt_cb_table_aml_history;
+ break;
+ case TALER_EXCHANGEDB_RT_KYC_ATTRIBUTES:
+ rh = &irbt_cb_table_kyc_attributes;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DELETION:
+ rh = &irbt_cb_table_purse_deletion;
+ break;
+ case TALER_EXCHANGEDB_RT_AGE_WITHDRAW:
+ rh = &irbt_cb_table_age_withdraw;
+ break;
+ }
+ if (NULL == rh)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return rh (pg,
+ td);
+}
+
+
+/* end of pg_insert_records_by_table.c */
diff --git a/src/exchangedb/pg_insert_records_by_table.h b/src/exchangedb/pg_insert_records_by_table.h
new file mode 100644
index 000000000..d9817e0ec
--- /dev/null
+++ b/src/exchangedb/pg_insert_records_by_table.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_records_by_table.h
+ * @brief implementation of the insert_records_by_table function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_RECORDS_BY_TABLE_H
+#define PG_INSERT_RECORDS_BY_TABLE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Insert record set into @a table. Used in exchange-auditor database
+ * replication.
+ *
+ * @param cls closure
+ * @param td table data to insert
+ * @return transaction status code, #GNUNET_DB_STATUS_HARD_ERROR if
+ * @e table in @a tr is not supported
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_records_by_table (void *cls,
+ const struct TALER_EXCHANGEDB_TableData *td);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_refresh_reveal.c b/src/exchangedb/pg_insert_refresh_reveal.c
new file mode 100644
index 000000000..bddca472b
--- /dev/null
+++ b/src/exchangedb/pg_insert_refresh_reveal.c
@@ -0,0 +1,109 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_refresh_reveal.c
+ * @brief Implementation of the insert_refresh_reveal function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_refresh_reveal.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_refresh_reveal (
+ void *cls,
+ uint64_t melt_serial_id,
+ uint32_t num_rrcs,
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
+ unsigned int num_tprivs,
+ const struct TALER_TransferPrivateKeyP *tprivs,
+ const struct TALER_TransferPublicKeyP *tp)
+{
+ struct PostgresClosure *pg = cls;
+
+ if (TALER_CNC_KAPPA != num_tprivs + 1)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ PREPARE (pg,
+ "insert_refresh_revealed_coin",
+ "INSERT INTO refresh_revealed_coins "
+ "(melt_serial_id "
+ ",freshcoin_index "
+ ",link_sig "
+ ",denominations_serial "
+ ",coin_ev"
+ ",ewv"
+ ",h_coin_ev"
+ ",ev_sig"
+ ") SELECT $1, $2, $3, "
+ " denominations_serial, $5, $6, $7, $8"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$4"
+ " ON CONFLICT DO NOTHING;");
+ for (uint32_t i = 0; i<num_rrcs; i++)
+ {
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&melt_serial_id),
+ GNUNET_PQ_query_param_uint32 (&i),
+ GNUNET_PQ_query_param_auto_from_type (&rrc->orig_coin_link_sig),
+ GNUNET_PQ_query_param_auto_from_type (&rrc->h_denom_pub),
+ TALER_PQ_query_param_blinded_planchet (&rrc->blinded_planchet),
+ TALER_PQ_query_param_exchange_withdraw_values (&rrc->exchange_vals),
+ GNUNET_PQ_query_param_auto_from_type (&rrc->coin_envelope_hash),
+ TALER_PQ_query_param_blinded_denom_sig (&rrc->coin_sig),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_refresh_revealed_coin",
+ params);
+ if (0 > qs)
+ return qs;
+ }
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&melt_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (tp),
+ GNUNET_PQ_query_param_fixed_size (
+ tprivs,
+ num_tprivs * sizeof (struct TALER_TransferPrivateKeyP)),
+ GNUNET_PQ_query_param_end
+ };
+
+ /* Used in #postgres_insert_refresh_reveal() to store the transfer
+ keys we learned */
+ PREPARE (pg,
+ "insert_refresh_transfer_keys",
+ "INSERT INTO refresh_transfer_keys "
+ "(melt_serial_id"
+ ",transfer_pub"
+ ",transfer_privs"
+ ") VALUES ($1, $2, $3)"
+ " ON CONFLICT DO NOTHING;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_refresh_transfer_keys",
+ params);
+ }
+}
diff --git a/src/exchangedb/pg_insert_refresh_reveal.h b/src/exchangedb/pg_insert_refresh_reveal.h
new file mode 100644
index 000000000..ad53ab197
--- /dev/null
+++ b/src/exchangedb/pg_insert_refresh_reveal.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_refresh_reveal.h
+ * @brief implementation of the insert_refresh_reveal function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_REFRESH_REVEAL_H
+#define PG_INSERT_REFRESH_REVEAL_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Store in the database which coin(s) the wallet wanted to create
+ * in a given refresh operation and all of the other information
+ * we learned or created in the /refresh/reveal step.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param melt_serial_id row ID of the commitment / melt operation in refresh_commitments
+ * @param num_rrcs number of coins to generate, size of the @a rrcs array
+ * @param rrcs information about the new coins
+ * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
+ * @param tprivs transfer private keys to store
+ * @param tp public key to store
+ * @return query status for the transaction
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_refresh_reveal (
+ void *cls,
+ uint64_t melt_serial_id,
+ uint32_t num_rrcs,
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
+ unsigned int num_tprivs,
+ const struct TALER_TransferPrivateKeyP *tprivs,
+ const struct TALER_TransferPublicKeyP *tp);
+
+#endif
diff --git a/src/exchangedb/pg_insert_refund.c b/src/exchangedb/pg_insert_refund.c
new file mode 100644
index 000000000..e989c91bc
--- /dev/null
+++ b/src/exchangedb/pg_insert_refund.c
@@ -0,0 +1,65 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_insert_refund.c
+ * @brief Implementation of the insert_refund function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_refund.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_refund (void *cls,
+ const struct TALER_EXCHANGEDB_Refund *refund)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&refund->coin.coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_sig),
+ GNUNET_PQ_query_param_auto_from_type (&refund->details.h_contract_terms),
+ GNUNET_PQ_query_param_uint64 (&refund->details.rtransaction_id),
+ TALER_PQ_query_param_amount (pg->conn,
+ &refund->details.refund_amount),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_assert (GNUNET_YES ==
+ TALER_amount_cmp_currency (&refund->details.refund_amount,
+ &refund->details.refund_fee));
+ PREPARE (pg,
+ "insert_refund",
+ "INSERT INTO refunds "
+ "(coin_pub"
+ ",batch_deposit_serial_id"
+ ",merchant_sig"
+ ",rtransaction_id"
+ ",amount_with_fee"
+ ") SELECT $1, cdep.batch_deposit_serial_id, $3, $5, $6"
+ " FROM coin_deposits cdep"
+ " JOIN batch_deposits bdep USING (batch_deposit_serial_id)"
+ " WHERE coin_pub=$1"
+ " AND h_contract_terms=$4"
+ " AND merchant_pub=$2");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_refund",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_refund.h b/src/exchangedb/pg_insert_refund.h
new file mode 100644
index 000000000..02190bcc9
--- /dev/null
+++ b/src/exchangedb/pg_insert_refund.h
@@ -0,0 +1,38 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_refund.h
+ * @brief implementation of the insert_refund function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_REFUND_H
+#define PG_INSERT_REFUND_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Insert information about refunded coin into the database.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param refund refund information to store
+ * @return query result status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_refund (void *cls,
+ const struct TALER_EXCHANGEDB_Refund *refund);
+
+#endif
diff --git a/src/exchangedb/pg_insert_reserve_closed.c b/src/exchangedb/pg_insert_reserve_closed.c
new file mode 100644
index 000000000..6644fb892
--- /dev/null
+++ b/src/exchangedb/pg_insert_reserve_closed.c
@@ -0,0 +1,113 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_reserve_closed.c
+ * @brief Implementation of the insert_reserve_closed function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_reserve_closed.h"
+#include "pg_helper.h"
+#include "pg_reserves_get.h"
+#include "pg_reserves_update.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_reserve_closed (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct GNUNET_TIME_Timestamp execution_date,
+ const char *receiver_account,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *closing_fee,
+ uint64_t close_request_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct TALER_EXCHANGEDB_Reserve reserve;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_PaytoHashP h_payto;
+
+ TALER_payto_hash (receiver_account,
+ &h_payto);
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_timestamp (&execution_date),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_auto_from_type (&h_payto),
+ TALER_PQ_query_param_amount (pg->conn,
+ amount_with_fee),
+ TALER_PQ_query_param_amount (pg->conn,
+ closing_fee),
+ GNUNET_PQ_query_param_uint64 (&close_request_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "reserves_close_insert",
+ "INSERT INTO reserves_close "
+ "(reserve_pub"
+ ",execution_date"
+ ",wtid"
+ ",wire_target_h_payto"
+ ",amount"
+ ",closing_fee"
+ ",close_request_row"
+ ") VALUES ($1, $2, $3, $4, $5, $6, $7);");
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "reserves_close_insert",
+ params);
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ return qs;
+
+ /* update reserve balance */
+ reserve.pub = *reserve_pub;
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ (qs = TEH_PG_reserves_get (cls,
+ &reserve)))
+ {
+ /* Existence should have been checked before we got here... */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+ }
+ {
+ enum TALER_AmountArithmeticResult ret;
+
+ ret = TALER_amount_subtract (&reserve.balance,
+ &reserve.balance,
+ amount_with_fee);
+ if (ret < 0)
+ {
+ /* The reserve history was checked to make sure there is enough of a balance
+ left before we tried this; however, concurrent operations may have changed
+ the situation by now. We should re-try the transaction. */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Closing of reserve `%s' refused due to balance mismatch. Retrying.\n",
+ TALER_B2S (reserve_pub));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_break (TALER_AAR_RESULT_ZERO == ret);
+ }
+ return TEH_PG_reserves_update (cls,
+ &reserve);
+}
diff --git a/src/exchangedb/pg_insert_reserve_closed.h b/src/exchangedb/pg_insert_reserve_closed.h
new file mode 100644
index 000000000..2ac1a6e30
--- /dev/null
+++ b/src/exchangedb/pg_insert_reserve_closed.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_reserve_closed.h
+ * @brief implementation of the insert_reserve_closed function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_RESERVE_CLOSED_H
+#define PG_INSERT_RESERVE_CLOSED_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Insert reserve close operation into database.
+ *
+ * @param cls closure
+ * @param reserve_pub which reserve is this about?
+ * @param execution_date when did we perform the transfer?
+ * @param receiver_account to which account do we transfer?
+ * @param wtid wire transfer details
+ * @param amount_with_fee amount we charged to the reserve
+ * @param closing_fee how high is the closing fee
+ * @param close_request_row identifies explicit close request, 0 for none
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_reserve_closed (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct GNUNET_TIME_Timestamp execution_date,
+ const char *receiver_account,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *closing_fee,
+ uint64_t close_request_row);
+
+#endif
diff --git a/src/exchangedb/pg_insert_reserve_open_deposit.c b/src/exchangedb/pg_insert_reserve_open_deposit.c
new file mode 100644
index 000000000..f9cedcbe7
--- /dev/null
+++ b/src/exchangedb/pg_insert_reserve_open_deposit.c
@@ -0,0 +1,67 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_reserve_open_deposit.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_reserve_open_deposit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_reserve_open_deposit (
+ void *cls,
+ const struct TALER_CoinPublicInfo *cpi,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ uint64_t known_coin_id,
+ const struct TALER_Amount *coin_total,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *insufficient_funds)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&cpi->coin_pub),
+ GNUNET_PQ_query_param_uint64 (&known_coin_id),
+ GNUNET_PQ_query_param_auto_from_type (coin_sig),
+ GNUNET_PQ_query_param_auto_from_type (reserve_sig),
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ TALER_PQ_query_param_amount (pg->conn,
+ coin_total),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("out_insufficient_funds",
+ insufficient_funds),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "insert_reserve_open_deposit",
+ "SELECT "
+ " out_insufficient_funds"
+ " FROM exchange_do_reserve_open_deposit"
+ " ($1,$2,$3,$4,$5,$6);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_reserve_open_deposit",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_insert_reserve_open_deposit.h b/src/exchangedb/pg_insert_reserve_open_deposit.h
new file mode 100644
index 000000000..7eb2fe093
--- /dev/null
+++ b/src/exchangedb/pg_insert_reserve_open_deposit.h
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_insert_reserve_open_deposit.h
+ * @brief implementation of the insert_reserve_open_deposit function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_RESERVE_OPEN_DEPOSIT_H
+#define PG_INSERT_RESERVE_OPEN_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Insert reserve open coin deposit data into database.
+ * Subtracts the @a coin_total from the coin's balance.
+ *
+ * @param cls closure
+ * @param cpi public information about the coin
+ * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT
+ * @param known_coin_id ID of the coin in the known_coins table
+ * @param coin_total amount to be spent of the coin (including deposit fee)
+ * @param reserve_sig signature by the reserve affirming the open operation
+ * @param reserve_pub public key of the reserve being opened
+ * @param[out] insufficient_funds set to true if the coin's balance is insufficient, otherwise to false
+ * @return transaction status code, 0 if operation is already in the DB
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_reserve_open_deposit (
+ void *cls,
+ const struct TALER_CoinPublicInfo *cpi,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ uint64_t known_coin_id,
+ const struct TALER_Amount *coin_total,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *insufficient_funds);
+
+#endif
diff --git a/src/exchangedb/pg_insert_signkey_revocation.c b/src/exchangedb/pg_insert_signkey_revocation.c
new file mode 100644
index 000000000..9197be6ad
--- /dev/null
+++ b/src/exchangedb/pg_insert_signkey_revocation.c
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_signkey_revocation.c
+ * @brief Implementation of the insert_signkey_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_signkey_revocation.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_signkey_revocation (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+
+ PREPARE (pg,
+ "insert_signkey_revocation",
+ "INSERT INTO signkey_revocations "
+ "(esk_serial"
+ ",master_sig"
+ ") SELECT esk_serial, $2 "
+ " FROM exchange_sign_keys"
+ " WHERE exchange_pub=$1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_signkey_revocation",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_signkey_revocation.h b/src/exchangedb/pg_insert_signkey_revocation.h
new file mode 100644
index 000000000..534e6d451
--- /dev/null
+++ b/src/exchangedb/pg_insert_signkey_revocation.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_signkey_revocation.h
+ * @brief implementation of the insert_signkey_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_SIGNKEY_REVOCATION_H
+#define PG_INSERT_SIGNKEY_REVOCATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Store information about a revoked online signing key.
+ *
+ * @param cls closure
+ * @param exchange_pub exchange online signing key that was revoked
+ * @param master_sig signature affirming the revocation
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_signkey_revocation (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+#endif
diff --git a/src/exchangedb/pg_insert_wire.c b/src/exchangedb/pg_insert_wire.c
new file mode 100644
index 000000000..b1364cbb3
--- /dev/null
+++ b/src/exchangedb/pg_insert_wire.c
@@ -0,0 +1,74 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023, 2024 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 exchangedb/pg_insert_wire.c
+ * @brief Implementation of the insert_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_wire.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_wire (void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp start_date,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (payto_uri),
+ NULL == conversion_url
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (conversion_url),
+ TALER_PQ_query_param_json (debit_restrictions),
+ TALER_PQ_query_param_json (credit_restrictions),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_timestamp (&start_date),
+ NULL == bank_label
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (bank_label),
+ GNUNET_PQ_query_param_int64 (&priority),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_wire",
+ "INSERT INTO wire_accounts "
+ "(payto_uri"
+ ",conversion_url"
+ ",debit_restrictions"
+ ",credit_restrictions"
+ ",master_sig"
+ ",is_active"
+ ",last_change"
+ ",bank_label"
+ ",priority"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, true, $6, $7, $8);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_wire",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_wire.h b/src/exchangedb/pg_insert_wire.h
new file mode 100644
index 000000000..7a5e4caca
--- /dev/null
+++ b/src/exchangedb/pg_insert_wire.h
@@ -0,0 +1,55 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_wire.h
+ * @brief implementation of the insert_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_WIRE_H
+#define PG_INSERT_WIRE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Insert information about an wire account used by this exchange.
+ *
+ * @param cls closure
+ * @param payto_uri wire account of the exchange
+ * @param conversion_url URL of a conversion service, NULL if there is no conversion
+ * @param debit_restrictions JSON array with debit restrictions on the account
+ * @param credit_restrictions JSON array with credit restrictions on the account
+ * @param start_date date when the account was added by the offline system
+ * (only to be used for replay detection)
+ * @param master_sig public signature affirming the existence of the account,
+ * must be of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS
+ * @param bank_label label to show this entry under in the UI, can be NULL
+ * @param priority determines order in which entries are shown in the UI
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_wire (void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp start_date,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_wire_fee.c b/src/exchangedb/pg_insert_wire_fee.c
new file mode 100644
index 000000000..af818bcca
--- /dev/null
+++ b/src/exchangedb/pg_insert_wire_fee.c
@@ -0,0 +1,108 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_wire_fee.c
+ * @brief Implementation of the insert_wire_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_wire_fee.h"
+#include "pg_helper.h"
+#include "pg_get_wire_fee.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_wire_fee (void *cls,
+ const char *type,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_WireFeeSet *fees,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (type),
+ GNUNET_PQ_query_param_timestamp (&start_date),
+ GNUNET_PQ_query_param_timestamp (&end_date),
+ TALER_PQ_query_param_amount (pg->conn,
+ &fees->wire),
+ TALER_PQ_query_param_amount (pg->conn,
+ &fees->closing),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct TALER_WireFeeSet wx;
+ struct TALER_MasterSignatureP sig;
+ struct GNUNET_TIME_Timestamp sd;
+ struct GNUNET_TIME_Timestamp ed;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_PG_get_wire_fee (pg,
+ type,
+ start_date,
+ &sd,
+ &ed,
+ &wx,
+ &sig);
+ if (qs < 0)
+ return qs;
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (0 != GNUNET_memcmp (&sig,
+ master_sig))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (0 !=
+ TALER_wire_fee_set_cmp (fees,
+ &wx))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (GNUNET_TIME_timestamp_cmp (sd,
+ !=,
+ start_date)) ||
+ (GNUNET_TIME_timestamp_cmp (ed,
+ !=,
+ end_date)) )
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* equal record already exists */
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+
+ PREPARE (pg,
+ "insert_wire_fee",
+ "INSERT INTO wire_fee "
+ "(wire_method"
+ ",start_date"
+ ",end_date"
+ ",wire_fee"
+ ",closing_fee"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_wire_fee",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_wire_fee.h b/src/exchangedb/pg_insert_wire_fee.h
new file mode 100644
index 000000000..15c1a39f8
--- /dev/null
+++ b/src/exchangedb/pg_insert_wire_fee.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_insert_wire_fee.h
+ * @brief implementation of the insert_wire_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_WIRE_FEE_H
+#define PG_INSERT_WIRE_FEE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Insert wire transfer fee into database.
+ *
+ * @param cls closure
+ * @param type type of wire transfer this fee applies for
+ * @param start_date when does the fee go into effect
+ * @param end_date when does the fee end being valid
+ * @param fees how high are the wire fees
+ * @param master_sig signature over the above by the exchange master key
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_wire_fee (void *cls,
+ const char *type,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_WireFeeSet *fees,
+ const struct TALER_MasterSignatureP *master_sig);
+#endif
diff --git a/src/exchangedb/pg_iterate_active_auditors.c b/src/exchangedb/pg_iterate_active_auditors.c
new file mode 100644
index 000000000..4f1c6ec66
--- /dev/null
+++ b/src/exchangedb/pg_iterate_active_auditors.c
@@ -0,0 +1,123 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_iterate_active_auditors.c
+ * @brief Implementation of the iterate_active_auditors function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_iterate_active_auditors.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #auditors_cb_helper()
+ */
+struct AuditorsIteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_AuditorsCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_active_auditors().
+ * Calls the callback with each auditor.
+ *
+ * @param cls a `struct SignkeysIteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+auditors_cb_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AuditorsIteratorContext *dic = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_AuditorPublicKeyP auditor_pub;
+ char *auditor_url;
+ char *auditor_name;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("auditor_pub",
+ &auditor_pub),
+ GNUNET_PQ_result_spec_string ("auditor_url",
+ &auditor_url),
+ GNUNET_PQ_result_spec_string ("auditor_name",
+ &auditor_name),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ dic->cb (dic->cb_cls,
+ &auditor_pub,
+ auditor_url,
+ auditor_name);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_active_auditors (void *cls,
+ TALER_EXCHANGEDB_AuditorsCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct AuditorsIteratorContext dic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ };
+
+ PREPARE (pg,
+ "select_auditors",
+ "SELECT"
+ " auditor_pub"
+ ",auditor_url"
+ ",auditor_name"
+ " FROM auditors"
+ " WHERE"
+ " is_active;");
+
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_auditors",
+ params,
+ &auditors_cb_helper,
+ &dic);
+}
diff --git a/src/exchangedb/pg_iterate_active_auditors.h b/src/exchangedb/pg_iterate_active_auditors.h
new file mode 100644
index 000000000..f0e2808e9
--- /dev/null
+++ b/src/exchangedb/pg_iterate_active_auditors.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_iterate_active_auditors.h
+ * @brief implementation of the iterate_active_auditors function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_ACTIVE_AUDITORS_H
+#define PG_ITERATE_ACTIVE_AUDITORS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to invoke @a cb on every active auditor. Disabled
+ * auditors are skipped. Runs in its own read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each active auditor
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_active_auditors (void *cls,
+ TALER_EXCHANGEDB_AuditorsCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_active_signkeys.c b/src/exchangedb/pg_iterate_active_signkeys.c
new file mode 100644
index 000000000..9c280c95d
--- /dev/null
+++ b/src/exchangedb/pg_iterate_active_signkeys.c
@@ -0,0 +1,144 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_iterate_active_signkeys.c
+ * @brief Implementation of the iterate_active_signkeys function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_iterate_active_signkeys.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #signkeys_cb_helper()
+ */
+struct SignkeysIteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_ActiveSignkeysCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_active_signkeys().
+ * Calls the callback with each signkey.
+ *
+ * @param cls a `struct SignkeysIteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+signkeys_cb_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct SignkeysIteratorContext *dic = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &master_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
+ &exchange_pub),
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &meta.start),
+ GNUNET_PQ_result_spec_timestamp ("expire_sign",
+ &meta.expire_sign),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &meta.expire_legal),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ dic->cb (dic->cb_cls,
+ &exchange_pub,
+ &meta,
+ &master_sig);
+ }
+}
+
+
+/**
+ * Function called to invoke @a cb on every non-revoked exchange signing key
+ * that has been signed by the master key. Revoked and (for signing!)
+ * expired keys are skipped. Runs in its own read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each signing key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_active_signkeys (void *cls,
+ TALER_EXCHANGEDB_ActiveSignkeysCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = {0};
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct SignkeysIteratorContext dic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ };
+
+ PREPARE (pg,
+ "select_signkeys",
+ "SELECT"
+ " master_sig"
+ ",exchange_pub"
+ ",valid_from"
+ ",expire_sign"
+ ",expire_legal"
+ " FROM exchange_sign_keys esk"
+ " WHERE"
+ " expire_sign > $1"
+ " AND NOT EXISTS "
+ " (SELECT esk_serial "
+ " FROM signkey_revocations skr"
+ " WHERE esk.esk_serial = skr.esk_serial);");
+ now = GNUNET_TIME_absolute_get ();
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_signkeys",
+ params,
+ &signkeys_cb_helper,
+ &dic);
+}
diff --git a/src/exchangedb/pg_iterate_active_signkeys.h b/src/exchangedb/pg_iterate_active_signkeys.h
new file mode 100644
index 000000000..5ebba9f5a
--- /dev/null
+++ b/src/exchangedb/pg_iterate_active_signkeys.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_iterate_active_signkeys.h
+ * @brief implementation of the iterate_active_signkeys function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_ACTIVE_SIGNKEYS_H
+#define PG_ITERATE_ACTIVE_SIGNKEYS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to invoke @a cb on every non-revoked exchange signing key
+ * that has been signed by the master key. Revoked and (for signing!)
+ * expired keys are skipped. Runs in its own read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each signing key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_active_signkeys (void *cls,
+ TALER_EXCHANGEDB_ActiveSignkeysCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_auditor_denominations.c b/src/exchangedb/pg_iterate_auditor_denominations.c
new file mode 100644
index 000000000..1fd4cdea6
--- /dev/null
+++ b/src/exchangedb/pg_iterate_auditor_denominations.c
@@ -0,0 +1,121 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_iterate_auditor_denominations.c
+ * @brief Implementation of the iterate_auditor_denominations function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_iterate_auditor_denominations.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #auditor_denoms_cb_helper()
+ */
+struct AuditorDenomsIteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_AuditorDenominationsCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_auditor_denominations().
+ * Calls the callback with each auditor and denomination pair.
+ *
+ * @param cls a `struct AuditorDenomsIteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+auditor_denoms_cb_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AuditorDenomsIteratorContext *dic = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_AuditorPublicKeyP auditor_pub;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_AuditorSignatureP auditor_sig;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("auditor_pub",
+ &auditor_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &h_denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("auditor_sig",
+ &auditor_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ dic->cb (dic->cb_cls,
+ &auditor_pub,
+ &h_denom_pub,
+ &auditor_sig);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_auditor_denominations (
+ void *cls,
+ TALER_EXCHANGEDB_AuditorDenominationsCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct AuditorDenomsIteratorContext dic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ };
+ /* Used in #postgres_iterate_auditor_denominations() */
+ PREPARE (pg,
+ "select_auditor_denoms",
+ "SELECT"
+ " auditors.auditor_pub"
+ ",denominations.denom_pub_hash"
+ ",auditor_denom_sigs.auditor_sig"
+ " FROM auditor_denom_sigs"
+ " JOIN auditors USING (auditor_uuid)"
+ " JOIN denominations USING (denominations_serial)"
+ " WHERE auditors.is_active;");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_auditor_denoms",
+ params,
+ &auditor_denoms_cb_helper,
+ &dic);
+}
diff --git a/src/exchangedb/pg_iterate_auditor_denominations.h b/src/exchangedb/pg_iterate_auditor_denominations.h
new file mode 100644
index 000000000..1278e8a9f
--- /dev/null
+++ b/src/exchangedb/pg_iterate_auditor_denominations.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_iterate_auditor_denominations.h
+ * @brief implementation of the iterate_auditor_denominations function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_AUDITOR_DENOMINATIONS_H
+#define PG_ITERATE_AUDITOR_DENOMINATIONS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to invoke @a cb on every denomination with an active
+ * auditor. Disabled auditors and denominations without auditor are
+ * skipped. Runs in its own read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each active auditor
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_auditor_denominations (
+ void *cls,
+ TALER_EXCHANGEDB_AuditorDenominationsCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_denomination_info.c b/src/exchangedb/pg_iterate_denomination_info.c
new file mode 100644
index 000000000..cab51d5ce
--- /dev/null
+++ b/src/exchangedb/pg_iterate_denomination_info.c
@@ -0,0 +1,180 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_iterate_denomination_info.c
+ * @brief Implementation of the iterate_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_iterate_denomination_info.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #domination_cb_helper()
+ */
+struct DenomIteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_DenominationCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_denomination_info().
+ * Calls the callback with each denomination key.
+ *
+ * @param cls a `struct DenomIteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+domination_cb_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct DenomIteratorContext *dic = cls;
+ struct PostgresClosure *pg = dic->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_DenominationHashP denom_hash;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &issue.signature),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &denom_hash),
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &issue.start),
+ GNUNET_PQ_result_spec_timestamp ("expire_withdraw",
+ &issue.expire_withdraw),
+ GNUNET_PQ_result_spec_timestamp ("expire_deposit",
+ &issue.expire_deposit),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &issue.expire_legal),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
+ &issue.value),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+ &issue.fees.withdraw),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &issue.fees.deposit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+ &issue.fees.refresh),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+ &issue.fees.refund),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_uint32 ("age_mask",
+ &issue.age_mask.bits),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+
+ /* Unfortunately we have to carry the age mask in both, the
+ * TALER_DenominationPublicKey and
+ * TALER_EXCHANGEDB_DenominationKeyInformation at different times.
+ * Here we use _both_ so let's make sure the values are the same. */
+ denom_pub.age_mask = issue.age_mask;
+ TALER_denom_pub_hash (&denom_pub,
+ &issue.denom_hash);
+ if (0 !=
+ GNUNET_memcmp (&issue.denom_hash,
+ &denom_hash))
+ {
+ GNUNET_break (0);
+ }
+ else
+ {
+ dic->cb (dic->cb_cls,
+ &denom_pub,
+ &issue);
+ }
+ TALER_denom_pub_free (&denom_pub);
+ }
+}
+
+
+/**
+ * Fetch information about all known denomination keys.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each denomination key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_denomination_info (void *cls,
+ TALER_EXCHANGEDB_DenominationCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct DenomIteratorContext dic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+
+ PREPARE (pg,
+ "denomination_iterate",
+ "SELECT"
+ " master_sig"
+ ",denom_pub_hash"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin" /* value of this denom */
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",denom_pub"
+ ",age_mask"
+ " FROM denominations;");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "denomination_iterate",
+ params,
+ &domination_cb_helper,
+ &dic);
+}
diff --git a/src/exchangedb/pg_iterate_denomination_info.h b/src/exchangedb/pg_iterate_denomination_info.h
new file mode 100644
index 000000000..27c08d0a9
--- /dev/null
+++ b/src/exchangedb/pg_iterate_denomination_info.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_iterate_denomination_info.h
+ * @brief implementation of the iterate_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_DENOMINATION_INFO_H
+#define PG_ITERATE_DENOMINATION_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Fetch information about all known denomination keys.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each denomination key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_denomination_info (void *cls,
+ TALER_EXCHANGEDB_DenominationCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_denominations.c b/src/exchangedb/pg_iterate_denominations.c
new file mode 100644
index 000000000..684aa165a
--- /dev/null
+++ b/src/exchangedb/pg_iterate_denominations.c
@@ -0,0 +1,172 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_iterate_denominations.c
+ * @brief Implementation of the iterate_denominations function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_iterate_denominations.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #dominations_cb_helper()
+ */
+struct DenomsIteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_DenominationsCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_denominations().
+ * Calls the callback with each denomination key.
+ *
+ * @param cls a `struct DenomsIteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+dominations_cb_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct DenomsIteratorContext *dic = cls;
+ struct PostgresClosure *pg = dic->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0};
+ struct TALER_DenominationPublicKey denom_pub = {0};
+ struct TALER_MasterSignatureP master_sig = {0};
+ struct TALER_DenominationHashP h_denom_pub = {0};
+ bool revoked;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("denominations_serial",
+ &meta.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &master_sig),
+ GNUNET_PQ_result_spec_bool ("revoked",
+ &revoked),
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &meta.start),
+ GNUNET_PQ_result_spec_timestamp ("expire_withdraw",
+ &meta.expire_withdraw),
+ GNUNET_PQ_result_spec_timestamp ("expire_deposit",
+ &meta.expire_deposit),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &meta.expire_legal),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
+ &meta.value),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+ &meta.fees.withdraw),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &meta.fees.deposit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+ &meta.fees.refresh),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+ &meta.fees.refund),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_uint32 ("age_mask",
+ &meta.age_mask.bits),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+
+ /* make sure the mask information is the same */
+ denom_pub.age_mask = meta.age_mask;
+
+ TALER_denom_pub_hash (&denom_pub,
+ &h_denom_pub);
+ dic->cb (dic->cb_cls,
+ &denom_pub,
+ &h_denom_pub,
+ &meta,
+ &master_sig,
+ revoked);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_denominations (void *cls,
+ TALER_EXCHANGEDB_DenominationsCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct DenomsIteratorContext dic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+
+ PREPARE (pg,
+ "select_denominations",
+ "SELECT"
+ " denominations_serial"
+ ",denominations.master_sig"
+ ",denom_revocations_serial_id IS NOT NULL AS revoked"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin" /* value of this denom */
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",denom_type"
+ ",age_mask"
+ ",denom_pub"
+ " FROM denominations"
+ " LEFT JOIN "
+ " denomination_revocations USING (denominations_serial);");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_denominations",
+ params,
+ &dominations_cb_helper,
+ &dic);
+}
diff --git a/src/exchangedb/pg_iterate_denominations.h b/src/exchangedb/pg_iterate_denominations.h
new file mode 100644
index 000000000..9f59fc803
--- /dev/null
+++ b/src/exchangedb/pg_iterate_denominations.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_iterate_denominations.h
+ * @brief implementation of the iterate_denominations function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_DENOMINATIONS_H
+#define PG_ITERATE_DENOMINATIONS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to invoke @a cb on every known denomination key (revoked
+ * and non-revoked) that has been signed by the master key. Runs in its own
+ * read-only transaction.
+ *
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each denomination key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_denominations (void *cls,
+ TALER_EXCHANGEDB_DenominationsCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_kyc_reference.c b/src/exchangedb/pg_iterate_kyc_reference.c
new file mode 100644
index 000000000..772c51e2c
--- /dev/null
+++ b/src/exchangedb/pg_iterate_kyc_reference.c
@@ -0,0 +1,129 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_iterate_kyc_reference.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_iterate_kyc_reference.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #iterate_kyc_reference_cb()
+ */
+struct IteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_LegitimizationProcessCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_kyc_reference().
+ * Calls the callback with each denomination key.
+ *
+ * @param cls a `struct IteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+iterate_kyc_reference_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct IteratorContext *ic = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ char *kyc_provider_section_name;
+ char *provider_user_id;
+ char *legitimization_id;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("provider_section",
+ &kyc_provider_section_name),
+ GNUNET_PQ_result_spec_string ("provider_user_id",
+ &provider_user_id),
+ GNUNET_PQ_result_spec_string ("provider_legitimization_id",
+ &legitimization_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ ic->cb (ic->cb_cls,
+ kyc_provider_section_name,
+ provider_user_id,
+ legitimization_id);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_kyc_reference (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_LegitimizationProcessCallback lpc,
+ void *lpc_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ struct IteratorContext ic = {
+ .cb = lpc,
+ .cb_cls = lpc_cls,
+ .pg = pg
+ };
+
+ PREPARE (pg,
+ "iterate_kyc_reference",
+ "SELECT "
+ " provider_section"
+ ",provider_user_id"
+ ",provider_legitimization_id"
+ " FROM legitimization_processes"
+ " WHERE h_payto=$1;");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "iterate_kyc_reference",
+ params,
+ &iterate_kyc_reference_cb,
+ &ic);
+}
diff --git a/src/exchangedb/pg_iterate_kyc_reference.h b/src/exchangedb/pg_iterate_kyc_reference.h
new file mode 100644
index 000000000..0242fdcf1
--- /dev/null
+++ b/src/exchangedb/pg_iterate_kyc_reference.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_iterate_kyc_reference.h
+ * @brief implementation of the iterate_kyc_reference function
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_KYC_REFERENCE_H
+#define PG_ITERATE_KYC_REFERENCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Call us on KYC legitimization processes satisfied and not expired for the
+ * given account.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param lpc function to call for each satisfied KYC legitimization process
+ * @param lpc_cls closure for @a lpc
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_kyc_reference (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_LegitimizationProcessCallback lpc,
+ void *lpc_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_reserve_close_info.c b/src/exchangedb/pg_iterate_reserve_close_info.c
new file mode 100644
index 000000000..ff0a813c3
--- /dev/null
+++ b/src/exchangedb/pg_iterate_reserve_close_info.c
@@ -0,0 +1,128 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_iterate_reserve_close_info.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_reserve_open_deposit.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #iterate_reserve_close_info_cb()
+ */
+struct IteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_KycAmountCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_reserve_close_info().
+ * Calls the callback with each denomination key.
+ *
+ * @param cls a `struct IteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+iterate_reserve_close_info_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct IteratorContext *ic = cls;
+ struct PostgresClosure *pg = ic->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_Amount amount;
+ struct GNUNET_TIME_Absolute ts;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("execution_date",
+ &ts),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ ic->cb (ic->cb_cls,
+ &amount,
+ ts);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_reserve_close_info (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_absolute_time (&time_limit),
+ GNUNET_PQ_query_param_end
+ };
+ struct IteratorContext ic = {
+ .cb = kac,
+ .cb_cls = kac_cls,
+ .pg = pg
+ };
+
+ PREPARE (pg,
+ "iterate_reserve_close_info",
+ "SELECT"
+ " amount"
+ ",execution_date"
+ " FROM reserves_close"
+ " WHERE wire_target_h_payto=$1"
+ " AND execution_date >= $2"
+ " ORDER BY execution_date DESC");
+ return GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "iterate_reserve_close_info",
+ params,
+ &iterate_reserve_close_info_cb,
+ &ic);
+}
diff --git a/src/exchangedb/pg_iterate_reserve_close_info.h b/src/exchangedb/pg_iterate_reserve_close_info.h
new file mode 100644
index 000000000..34692e656
--- /dev/null
+++ b/src/exchangedb/pg_iterate_reserve_close_info.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_iterate_reserve_close_info.h
+ * @brief implementation of the iterate_reserve_close_info function
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_RESERVE_CLOSE_INFO_H
+#define PG_ITERATE_RESERVE_CLOSE_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select information needed for KYC checks on reserve close: historic
+ * reserve closures going to the same account.
+ *
+ * @param cls closure
+ * @param h_payto which target account is this about?
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_reserve_close_info (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_kyc_provider_account_lookup.c b/src/exchangedb/pg_kyc_provider_account_lookup.c
new file mode 100644
index 000000000..f9db2cbc1
--- /dev/null
+++ b/src/exchangedb/pg_kyc_provider_account_lookup.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_kyc_provider_account_lookup.c
+ * @brief Implementation of the kyc_provider_account_lookup function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_kyc_provider_account_lookup.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_kyc_provider_account_lookup (
+ void *cls,
+ const char *provider_section,
+ const char *provider_legitimization_id,
+ struct TALER_PaytoHashP *h_payto,
+ uint64_t *process_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (provider_legitimization_id),
+ GNUNET_PQ_query_param_string (provider_section),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_payto",
+ h_payto),
+ GNUNET_PQ_result_spec_uint64 ("legitimization_process_serial_id",
+ process_row),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_wire_target_by_legitimization_id",
+ "SELECT "
+ " h_payto"
+ ",legitimization_process_serial_id"
+ " FROM legitimization_processes"
+ " WHERE provider_legitimization_id=$1"
+ " AND provider_section=$2;");
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "get_wire_target_by_legitimization_id",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_kyc_provider_account_lookup.h b/src/exchangedb/pg_kyc_provider_account_lookup.h
new file mode 100644
index 000000000..74f90d88d
--- /dev/null
+++ b/src/exchangedb/pg_kyc_provider_account_lookup.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_kyc_provider_account_lookup.h
+ * @brief implementation of the kyc_provider_account_lookup function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_KYC_PROVIDER_ACCOUNT_LOOKUP_H
+#define PG_KYC_PROVIDER_ACCOUNT_LOOKUP_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup an
+ * @a h_payto by @a provider_legitimization_id.
+ *
+ * @param cls closure
+ * @param provider_section
+ * @param provider_legitimization_id legi to look up
+ * @param[out] h_payto where to write the result
+ * @param[out] process_row where to write the row of the entry
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_kyc_provider_account_lookup (
+ void *cls,
+ const char *provider_section,
+ const char *provider_legitimization_id,
+ struct TALER_PaytoHashP *h_payto,
+ uint64_t *process_row);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_aml_officer.c b/src/exchangedb/pg_lookup_aml_officer.c
new file mode 100644
index 000000000..c18e47eaf
--- /dev/null
+++ b/src/exchangedb/pg_lookup_aml_officer.c
@@ -0,0 +1,71 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_aml_officer.c
+ * @brief Implementation of the lookup_aml_officer function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_aml_officer.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_aml_officer (
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ struct TALER_MasterSignatureP *master_sig,
+ char **decider_name,
+ bool *is_active,
+ bool *read_only,
+ struct GNUNET_TIME_Absolute *last_change)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (decider_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_string ("decider_name",
+ decider_name),
+ GNUNET_PQ_result_spec_bool ("is_active",
+ is_active),
+ GNUNET_PQ_result_spec_bool ("read_only",
+ read_only),
+ GNUNET_PQ_result_spec_absolute_time ("last_change",
+ last_change),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "lookup_aml_officer",
+ "SELECT "
+ " master_sig"
+ ",decider_name"
+ ",is_active"
+ ",read_only"
+ ",last_change"
+ " FROM aml_staff"
+ " WHERE decider_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_aml_officer",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_aml_officer.h b/src/exchangedb/pg_lookup_aml_officer.h
new file mode 100644
index 000000000..161d2e7e7
--- /dev/null
+++ b/src/exchangedb/pg_lookup_aml_officer.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_aml_officer.h
+ * @brief implementation of the lookup_aml_officer function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_AML_OFFICER_H
+#define PG_LOOKUP_AML_OFFICER_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Fetch AML staff record.
+ *
+ * @param cls closure
+ * @param decider_pub public key of the staff member
+ * @param[out] master_sig offline signature affirming the AML officer
+ * @param[out] decider_name full name of the staff member
+ * @param[out] is_active true to enable, false to set as inactive
+ * @param[out] read_only true to set read-only access
+ * @param[out] last_change when was the change made effective
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_aml_officer (
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ struct TALER_MasterSignatureP *master_sig,
+ char **decider_name,
+ bool *is_active,
+ bool *read_only,
+ struct GNUNET_TIME_Absolute *last_change);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_auditor_status.c b/src/exchangedb/pg_lookup_auditor_status.c
new file mode 100644
index 000000000..91afe6eaa
--- /dev/null
+++ b/src/exchangedb/pg_lookup_auditor_status.c
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_auditor_status.c
+ * @brief Implementation of the lookup_auditor_status function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_auditor_status.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_auditor_status (
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ char **auditor_url,
+ bool *enabled)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (auditor_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("auditor_url",
+ auditor_url),
+ GNUNET_PQ_result_spec_bool ("is_active",
+ enabled),
+ GNUNET_PQ_result_spec_end
+ };
+
+ /* Used in #postgres_lookup_auditor_status() */
+ PREPARE (pg,
+ "lookup_auditor_status",
+ "SELECT"
+ " auditor_url"
+ ",is_active"
+ " FROM auditors"
+ " WHERE auditor_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_auditor_status",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_auditor_status.h b/src/exchangedb/pg_lookup_auditor_status.h
new file mode 100644
index 000000000..b75788e11
--- /dev/null
+++ b/src/exchangedb/pg_lookup_auditor_status.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_auditor_status.h
+ * @brief implementation of the lookup_auditor_status function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_AUDITOR_STATUS_H
+#define PG_LOOKUP_AUDITOR_STATUS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Lookup current state of an auditor.
+ *
+ * @param cls closure
+ * @param auditor_pub key to look up information for
+ * @param[out] auditor_url set to the base URL of the auditor's REST API; memory to be
+ * released by the caller!
+ * @param[out] enabled set if the auditor is currently in use
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_auditor_status (
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ char **auditor_url,
+ bool *enabled);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_auditor_timestamp.c b/src/exchangedb/pg_lookup_auditor_timestamp.c
new file mode 100644
index 000000000..eb85876fe
--- /dev/null
+++ b/src/exchangedb/pg_lookup_auditor_timestamp.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_auditor_timestamp.c
+ * @brief Implementation of the lookup_auditor_timestamp function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_auditor_timestamp.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_auditor_timestamp (
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct GNUNET_TIME_Timestamp *last_date)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (auditor_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("last_change",
+ last_date),
+ GNUNET_PQ_result_spec_end
+ };
+
+ /* Used in #postgres_lookup_auditor_timestamp() */
+ PREPARE (pg,
+ "lookup_auditor_timestamp",
+ "SELECT"
+ " last_change"
+ " FROM auditors"
+ " WHERE auditor_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_auditor_timestamp",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_auditor_timestamp.h b/src/exchangedb/pg_lookup_auditor_timestamp.h
new file mode 100644
index 000000000..5674ba225
--- /dev/null
+++ b/src/exchangedb/pg_lookup_auditor_timestamp.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_auditor_timestamp.h
+ * @brief implementation of the lookup_auditor_timestamp function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_AUDITOR_TIMESTAMP_H
+#define PG_LOOKUP_AUDITOR_TIMESTAMP_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Check the last date an auditor was modified.
+ *
+ * @param cls closure
+ * @param auditor_pub key to look up information for
+ * @param[out] last_date last modification date to auditor status
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_auditor_timestamp (
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct GNUNET_TIME_Timestamp *last_date);
+#endif
diff --git a/src/exchangedb/pg_lookup_denomination_key.c b/src/exchangedb/pg_lookup_denomination_key.c
new file mode 100644
index 000000000..a358528ad
--- /dev/null
+++ b/src/exchangedb/pg_lookup_denomination_key.c
@@ -0,0 +1,82 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_denomination_key.c
+ * @brief Implementation of the lookup_denomination_key function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_denomination_key.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_denomination_key (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &meta->start),
+ GNUNET_PQ_result_spec_timestamp ("expire_withdraw",
+ &meta->expire_withdraw),
+ GNUNET_PQ_result_spec_timestamp ("expire_deposit",
+ &meta->expire_deposit),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &meta->expire_legal),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
+ &meta->value),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+ &meta->fees.withdraw),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &meta->fees.deposit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+ &meta->fees.refresh),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+ &meta->fees.refund),
+ GNUNET_PQ_result_spec_uint32 ("age_mask",
+ &meta->age_mask.bits),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "lookup_denomination_key",
+ "SELECT"
+ " valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin"
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",age_mask"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_denomination_key",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_denomination_key.h b/src/exchangedb/pg_lookup_denomination_key.h
new file mode 100644
index 000000000..b7317ac4a
--- /dev/null
+++ b/src/exchangedb/pg_lookup_denomination_key.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_denomination_key.h
+ * @brief implementation of the lookup_denomination_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_DENOMINATION_KEY_H
+#define PG_LOOKUP_DENOMINATION_KEY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Lookup information about current denomination key.
+ *
+ * @param cls closure
+ * @param h_denom_pub hash of the denomination public key
+ * @param[out] meta set to various meta data about the key
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_denomination_key (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_global_fee_by_time.c b/src/exchangedb/pg_lookup_global_fee_by_time.c
new file mode 100644
index 000000000..c3a6ec8b7
--- /dev/null
+++ b/src/exchangedb/pg_lookup_global_fee_by_time.c
@@ -0,0 +1,182 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_global_fee_by_time.c
+ * @brief Implementation of the lookup_global_fee_by_time function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_global_fee_by_time.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #global_fee_by_time_helper()
+ */
+struct GlobalFeeLookupContext
+{
+
+ /**
+ * Set to the wire fees. Set to invalid if fees conflict over
+ * the given time period.
+ */
+ struct TALER_GlobalFeeSet *fees;
+
+ /**
+ * Set to timeout of unmerged purses
+ */
+ struct GNUNET_TIME_Relative *purse_timeout;
+
+ /**
+ * Set to history expiration for reserves.
+ */
+ struct GNUNET_TIME_Relative *history_expiration;
+
+ /**
+ * Set to number of free purses per account.
+ */
+ uint32_t *purse_account_limit;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_lookup_global_fee_by_time().
+ * Calls the callback with each denomination key.
+ *
+ * @param cls a `struct GlobalFeeLookupContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+global_fee_by_time_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GlobalFeeLookupContext *wlc = cls;
+ struct PostgresClosure *pg = wlc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_GlobalFeeSet fs;
+ struct GNUNET_TIME_Relative purse_timeout;
+ struct GNUNET_TIME_Relative history_expiration;
+ uint32_t purse_account_limit;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("history_fee",
+ &fs.history),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("account_fee",
+ &fs.account),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
+ &fs.purse),
+ GNUNET_PQ_result_spec_relative_time ("purse_timeout",
+ &purse_timeout),
+ GNUNET_PQ_result_spec_relative_time ("history_expiration",
+ &history_expiration),
+ GNUNET_PQ_result_spec_uint32 ("purse_account_limit",
+ &purse_account_limit),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ /* invalidate */
+ memset (wlc->fees,
+ 0,
+ sizeof (struct TALER_GlobalFeeSet));
+ return;
+ }
+ if (0 == i)
+ {
+ *wlc->fees = fs;
+ *wlc->purse_timeout = purse_timeout;
+ *wlc->history_expiration = history_expiration;
+ *wlc->purse_account_limit = purse_account_limit;
+ continue;
+ }
+ if ( (0 !=
+ TALER_global_fee_set_cmp (&fs,
+ wlc->fees)) ||
+ (purse_account_limit != *wlc->purse_account_limit) ||
+ (GNUNET_TIME_relative_cmp (purse_timeout,
+ !=,
+ *wlc->purse_timeout)) ||
+ (GNUNET_TIME_relative_cmp (history_expiration,
+ !=,
+ *wlc->history_expiration)) )
+ {
+ /* invalidate */
+ memset (wlc->fees,
+ 0,
+ sizeof (struct TALER_GlobalFeeSet));
+ return;
+ }
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_global_fee_by_time (
+ void *cls,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative *purse_timeout,
+ struct GNUNET_TIME_Relative *history_expiration,
+ uint32_t *purse_account_limit)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&start_time),
+ GNUNET_PQ_query_param_timestamp (&end_time),
+ GNUNET_PQ_query_param_end
+ };
+ struct GlobalFeeLookupContext wlc = {
+ .fees = fees,
+ .purse_timeout = purse_timeout,
+ .history_expiration = history_expiration,
+ .purse_account_limit = purse_account_limit,
+ .pg = pg
+ };
+
+ PREPARE (pg,
+ "lookup_global_fee_by_time",
+ "SELECT"
+ " history_fee"
+ ",account_fee"
+ ",purse_fee"
+ ",purse_timeout"
+ ",history_expiration"
+ ",purse_account_limit"
+ " FROM global_fee"
+ " WHERE end_date > $1"
+ " AND start_date < $2;");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_global_fee_by_time",
+ params,
+ &global_fee_by_time_helper,
+ &wlc);
+}
diff --git a/src/exchangedb/pg_lookup_global_fee_by_time.h b/src/exchangedb/pg_lookup_global_fee_by_time.h
new file mode 100644
index 000000000..c5ff95fc6
--- /dev/null
+++ b/src/exchangedb/pg_lookup_global_fee_by_time.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_global_fee_by_time.h
+ * @brief implementation of the lookup_global_fee_by_time function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_GLOBAL_FEE_BY_TIME_H
+#define PG_LOOKUP_GLOBAL_FEE_BY_TIME_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Lookup information about known global fees.
+ *
+ * @param cls closure
+ * @param start_time starting time of fee
+ * @param end_time end time of fee
+ * @param[out] fees set to wire fees for that time period; if
+ * different global fee exists within this time
+ * period, an 'invalid' amount is returned.
+ * @param[out] purse_timeout set to when unmerged purses expire
+ * @param[out] history_expiration set to when we expire reserve histories
+ * @param[out] purse_account_limit set to number of free purses
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_global_fee_by_time (
+ void *cls,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative *purse_timeout,
+ struct GNUNET_TIME_Relative *history_expiration,
+ uint32_t *purse_account_limit);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_kyc_process_by_account.c b/src/exchangedb/pg_lookup_kyc_process_by_account.c
new file mode 100644
index 000000000..b077661c5
--- /dev/null
+++ b/src/exchangedb/pg_lookup_kyc_process_by_account.c
@@ -0,0 +1,83 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_kyc_process_by_account.c
+ * @brief Implementation of the lookup_kyc_process_by_account function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_kyc_process_by_account.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_kyc_process_by_account (
+ void *cls,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ uint64_t *process_row,
+ struct GNUNET_TIME_Absolute *expiration,
+ char **provider_account_id,
+ char **provider_legitimization_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_string (provider_section),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("legitimization_process_serial_id",
+ process_row),
+ GNUNET_PQ_result_spec_absolute_time ("expiration_time",
+ expiration),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("provider_user_id",
+ provider_account_id),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("provider_legitimization_id",
+ provider_legitimization_id),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ *provider_account_id = NULL;
+ *provider_legitimization_id = NULL;
+ PREPARE (pg,
+ "lookup_process_by_account",
+ "SELECT "
+ " legitimization_process_serial_id"
+ ",expiration_time"
+ ",provider_user_id"
+ ",provider_legitimization_id"
+ " FROM legitimization_processes"
+ " WHERE h_payto=$1"
+ " AND provider_section=$2"
+ " AND NOT finished"
+ /* Note: there *should* only be one unfinished
+ match, so this is just to be safe(r): */
+ " ORDER BY expiration_time DESC"
+ " LIMIT 1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "lookup_process_by_account",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_kyc_process_by_account.h b/src/exchangedb/pg_lookup_kyc_process_by_account.h
new file mode 100644
index 000000000..0300b498c
--- /dev/null
+++ b/src/exchangedb/pg_lookup_kyc_process_by_account.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_kyc_process_by_account.h
+ * @brief implementation of the lookup_kyc_process_by_account function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_KYC_PROCESS_BY_ACCOUNT_H
+#define PG_LOOKUP_KYC_PROCESS_BY_ACCOUNT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup KYC provider meta data.
+ *
+ * @param cls closure
+ * @param provider_section provider that must be checked
+ * @param h_payto account that must be KYC'ed
+ * @param[out] process_row row with the legitimization data
+ * @param[out] expiration how long is this KYC check set to be valid (in the past if invalid)
+ * @param[out] provider_account_id provider account ID
+ * @param[out] provider_legitimization_id provider legitimization ID
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_kyc_process_by_account (
+ void *cls,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ uint64_t *process_row,
+ struct GNUNET_TIME_Absolute *expiration,
+ char **provider_account_id,
+ char **provider_legitimization_id);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_kyc_requirement_by_row.c b/src/exchangedb/pg_lookup_kyc_requirement_by_row.c
new file mode 100644
index 000000000..6f9d76786
--- /dev/null
+++ b/src/exchangedb/pg_lookup_kyc_requirement_by_row.c
@@ -0,0 +1,71 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_kyc_requirement_by_row.c
+ * @brief Implementation of the lookup_kyc_requirement_by_row function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_kyc_requirement_by_row.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_kyc_requirement_by_row (
+ void *cls,
+ uint64_t requirement_row,
+ char **requirements,
+ enum TALER_AmlDecisionState *aml_status,
+ struct TALER_PaytoHashP *h_payto)
+{
+ struct PostgresClosure *pg = cls;
+ uint32_t status = TALER_AML_NORMAL;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&requirement_row),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("required_checks",
+ requirements),
+ GNUNET_PQ_result_spec_auto_from_type ("h_payto",
+ h_payto),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint32 ("status",
+ &status),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "lookup_legitimization_requirement_by_row",
+ "SELECT "
+ " lr.required_checks"
+ ",lr.h_payto"
+ ",aml.status"
+ " FROM legitimization_requirements lr"
+ " LEFT JOIN aml_status aml USING (h_payto)"
+ " WHERE legitimization_requirement_serial_id=$1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "lookup_legitimization_requirement_by_row",
+ params,
+ rs);
+ *aml_status = (enum TALER_AmlDecisionState) status;
+ return qs;
+}
diff --git a/src/exchangedb/pg_lookup_kyc_requirement_by_row.h b/src/exchangedb/pg_lookup_kyc_requirement_by_row.h
new file mode 100644
index 000000000..3d223c985
--- /dev/null
+++ b/src/exchangedb/pg_lookup_kyc_requirement_by_row.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_kyc_requirement_by_row.h
+ * @brief implementation of the lookup_kyc_requirement_by_row function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_KYC_REQUIREMENT_BY_ROW_H
+#define PG_LOOKUP_KYC_REQUIREMENT_BY_ROW_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup KYC requirement.
+ *
+ * @param cls closure
+ * @param requirement_row identifies requirement to look up
+ * @param[out] requirements provider that must be checked
+ * @param[out] aml_status set to the AML status of the account
+ * @param[out] h_payto account that must be KYC'ed
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_kyc_requirement_by_row (
+ void *cls,
+ uint64_t requirement_row,
+ char **requirements,
+ enum TALER_AmlDecisionState *aml_status,
+ struct TALER_PaytoHashP *h_payto);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_records_by_table.c b/src/exchangedb/pg_lookup_records_by_table.c
new file mode 100644
index 000000000..fc4af32a8
--- /dev/null
+++ b/src/exchangedb/pg_lookup_records_by_table.c
@@ -0,0 +1,3607 @@
+/*
+ This file is part of GNUnet
+ Copyright (C) 2020-2023 Taler Systems SA
+
+ GNUnet 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 of the License,
+ or (at your option) any later version.
+
+ GNUnet 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/**
+ * @file exchangedb/pg_lookup_records_by_table.c
+ * @brief implementation of lookup_records_by_table
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_records_by_table.h"
+#include "pg_helper.h"
+#include <gnunet/gnunet_pq_lib.h>
+
+
+/**
+ * Closure for callbacks used by #postgres_lookup_records_by_table.
+ */
+struct LookupRecordsByTableContext
+{
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_ReplicationCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Set to true on errors.
+ */
+ bool error;
+};
+
+
+/**
+ * Function called with denominations table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_denominations (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_DENOMINATIONS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint32 (
+ "denom_type",
+ &td.details.denominations.denom_type),
+ GNUNET_PQ_result_spec_uint32 (
+ "age_mask",
+ &td.details.denominations.age_mask),
+ TALER_PQ_result_spec_denom_pub (
+ "denom_pub",
+ &td.details.denominations.denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "master_sig",
+ &td.details.denominations.master_sig),
+ GNUNET_PQ_result_spec_timestamp (
+ "valid_from",
+ &td.details.denominations.valid_from),
+ GNUNET_PQ_result_spec_timestamp (
+ "expire_withdraw",
+ &td.details.denominations.
+ expire_withdraw),
+ GNUNET_PQ_result_spec_timestamp (
+ "expire_deposit",
+ &td.details.denominations.
+ expire_deposit),
+ GNUNET_PQ_result_spec_timestamp (
+ "expire_legal",
+ &td.details.denominations.expire_legal),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "coin",
+ &td.details.denominations.coin),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "fee_withdraw",
+ &td.details.denominations.fees.withdraw),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "fee_deposit",
+ &td.details.denominations.fees.deposit),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "fee_refresh",
+ &td.details.denominations.fees.refresh),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "fee_refund",
+ &td.details.denominations.fees.refund),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with denomination_revocations table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_denomination_revocations (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "denominations_serial",
+ &td.details.denomination_revocations.denominations_serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "master_sig",
+ &td.details.denomination_revocations.master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wire_targets table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wire_targets (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WIRE_TARGETS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &td.details.wire_targets.payto_uri),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with legitimization_processes table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_legitimization_processes (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_LEGITIMIZATION_PROCESSES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_payto",
+ &td.details.legitimization_processes.h_payto),
+ GNUNET_PQ_result_spec_timestamp (
+ "expiration_time",
+ &td.details.legitimization_processes.expiration_time),
+ GNUNET_PQ_result_spec_string (
+ "provider_section",
+ &td.details.legitimization_processes.provider_section),
+ GNUNET_PQ_result_spec_string (
+ "provider_user_id",
+ &td.details.legitimization_processes.provider_user_id),
+ GNUNET_PQ_result_spec_string (
+ "provider_legitimization_id",
+ &td.details.legitimization_processes.provider_legitimization_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with legitimization_requirements table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_legitimization_requirements (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_LEGITIMIZATION_REQUIREMENTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_payto",
+ &td.details.legitimization_requirements.h_payto),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.legitimization_requirements.reserve_pub),
+ &td.details.legitimization_requirements.no_reserve_pub),
+ GNUNET_PQ_result_spec_string (
+ "required_checks",
+ &td.details.legitimization_requirements.required_checks),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with reserves table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_reserves (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RESERVES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &td.details.reserves.reserve_pub),
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ &td.details.reserves.expiration_date),
+ GNUNET_PQ_result_spec_timestamp ("gc_date",
+ &td.details.reserves.gc_date),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with reserves_in table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_reserves_in (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RESERVES_IN
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.reserves_in.reserve_pub),
+ GNUNET_PQ_result_spec_uint64 (
+ "wire_reference",
+ &td.details.reserves_in.wire_reference),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "credit",
+ &td.details.reserves_in.credit),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wire_source_h_payto",
+ &td.details.reserves_in.sender_account_h_payto),
+ GNUNET_PQ_result_spec_string (
+ "exchange_account_section",
+ &td.details.reserves_in.exchange_account_section),
+ GNUNET_PQ_result_spec_timestamp (
+ "execution_date",
+ &td.details.reserves_in.execution_date),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with reserves_close table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_reserves_close (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RESERVES_CLOSE
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.reserves_close.reserve_pub),
+ GNUNET_PQ_result_spec_timestamp (
+ "execution_date",
+ &td.details.reserves_close.execution_date),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wtid",
+ &td.details.reserves_close.wtid),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wire_target_h_payto",
+ &td.details.reserves_close.sender_account_h_payto),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount",
+ &td.details.reserves_close.amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "closing_fee",
+ &td.details.reserves_close.closing_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with reserves_open_requests table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_reserves_open_requests (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RESERVES_OPEN_REQUESTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.reserves_open_requests.reserve_pub),
+ GNUNET_PQ_result_spec_timestamp (
+ "request_timestamp",
+ &td.details.reserves_open_requests.request_timestamp),
+ GNUNET_PQ_result_spec_timestamp (
+ "expiration_date",
+ &td.details.reserves_open_requests.expiration_date),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.reserves_open_requests.reserve_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "reserve_payment",
+ &td.details.reserves_open_requests.reserve_payment),
+ GNUNET_PQ_result_spec_uint32 (
+ "requested_purse_limit",
+ &td.details.reserves_open_requests.requested_purse_limit),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with reserves_open_deposits table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_reserves_open_deposits (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RESERVES_OPEN_DEPOSITS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.reserves_open_deposits.reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.reserves_open_deposits.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.reserves_open_deposits.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_sig",
+ &td.details.reserves_open_deposits.coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "contribution",
+ &td.details.reserves_open_deposits.contribution),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with reserves_out table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_reserves_out (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RESERVES_OUT
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_blind_ev",
+ &td.details.reserves_out.h_blind_ev),
+ GNUNET_PQ_result_spec_uint64 (
+ "denominations_serial",
+ &td.details.reserves_out.denominations_serial),
+ TALER_PQ_result_spec_blinded_denom_sig (
+ "denom_sig",
+ &td.details.reserves_out.denom_sig),
+ GNUNET_PQ_result_spec_uint64 (
+ "reserve_uuid",
+ &td.details.reserves_out.reserve_uuid),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.reserves_out.reserve_sig),
+ GNUNET_PQ_result_spec_timestamp (
+ "execution_date",
+ &td.details.reserves_out.execution_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.reserves_out.amount_with_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with auditors table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_auditors (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_AUDITORS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("auditor_pub",
+ &td.details.auditors.auditor_pub),
+ GNUNET_PQ_result_spec_string ("auditor_url",
+ &td.details.auditors.auditor_url),
+ GNUNET_PQ_result_spec_string ("auditor_name",
+ &td.details.auditors.auditor_name),
+ GNUNET_PQ_result_spec_bool ("is_active",
+ &td.details.auditors.is_active),
+ GNUNET_PQ_result_spec_timestamp ("last_change",
+ &td.details.auditors.last_change),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with auditor_denom_sigs table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_auditor_denom_sigs (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "auditor_uuid",
+ &td.details.auditor_denom_sigs.auditor_uuid),
+ GNUNET_PQ_result_spec_uint64 (
+ "denominations_serial",
+ &td.details.auditor_denom_sigs.denominations_serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "auditor_sig",
+ &td.details.auditor_denom_sigs.auditor_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with exchange_sign_keys table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_exchange_sign_keys (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
+ &td.details.exchange_sign_keys.
+ exchange_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &td.details.exchange_sign_keys.
+ master_sig),
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &td.details.exchange_sign_keys.meta.
+ start),
+ GNUNET_PQ_result_spec_timestamp ("expire_sign",
+ &td.details.exchange_sign_keys.meta.
+ expire_sign),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &td.details.exchange_sign_keys.meta.
+ expire_legal),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with signkey_revocations table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_signkey_revocations (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 ("esk_serial",
+ &td.details.signkey_revocations.esk_serial),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &td.details.signkey_revocations.
+ master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with known_coins table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_known_coins (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_KNOWN_COINS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.known_coins.coin_pub),
+ TALER_PQ_result_spec_denom_sig (
+ "denom_sig",
+ &td.details.known_coins.denom_sig),
+ GNUNET_PQ_result_spec_uint64 (
+ "denominations_serial",
+ &td.details.known_coins.denominations_serial),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with refresh_commitments table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_refresh_commitments (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "rc",
+ &td.details.refresh_commitments.rc),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "old_coin_sig",
+ &td.details.refresh_commitments.old_coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.refresh_commitments.amount_with_fee),
+ GNUNET_PQ_result_spec_uint32 (
+ "noreveal_index",
+ &td.details.refresh_commitments.noreveal_index),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "old_coin_pub",
+ &td.details.refresh_commitments.old_coin_pub),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with refresh_revealed_coins table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_refresh_revealed_coins (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint32 (
+ "freshcoin_index",
+ &td.details.refresh_revealed_coins.freshcoin_index),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "link_sig",
+ &td.details.refresh_revealed_coins.link_sig),
+ GNUNET_PQ_result_spec_variable_size (
+ "coin_ev",
+ (void **) &td.details.refresh_revealed_coins.coin_ev,
+ &td.details.refresh_revealed_coins.coin_ev_size),
+ TALER_PQ_result_spec_blinded_denom_sig (
+ "ev_sig",
+ &td.details.refresh_revealed_coins.ev_sig),
+ TALER_PQ_result_spec_exchange_withdraw_values (
+ "ewv",
+ &td.details.refresh_revealed_coins.ewv),
+ GNUNET_PQ_result_spec_uint64 (
+ "denominations_serial",
+ &td.details.refresh_revealed_coins.denominations_serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "melt_serial_id",
+ &td.details.refresh_revealed_coins.melt_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with refresh_transfer_keys table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_refresh_transfer_keys (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ void *tpriv;
+ size_t tpriv_size;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("transfer_pub",
+ &td.details.refresh_transfer_keys.tp),
+ GNUNET_PQ_result_spec_variable_size ("transfer_privs",
+ &tpriv,
+ &tpriv_size),
+ GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
+ &td.details.refresh_transfer_keys.
+ melt_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ /* Both conditions should be identical, but we conservatively also guard against
+ unwarranted changes to the structure here. */
+ if ( (tpriv_size !=
+ sizeof (td.details.refresh_transfer_keys.tprivs)) ||
+ (tpriv_size !=
+ (TALER_CNC_KAPPA - 1) * sizeof (struct TALER_TransferPrivateKeyP)) )
+ {
+ GNUNET_break (0);
+ GNUNET_PQ_cleanup_result (rs);
+ ctx->error = true;
+ return;
+ }
+ GNUNET_memcpy (&td.details.refresh_transfer_keys.tprivs[0],
+ tpriv,
+ tpriv_size);
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with batch deposits table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_batch_deposits (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_BATCH_DEPOSITS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "shard",
+ &td.details.batch_deposits.shard),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "merchant_pub",
+ &td.details.batch_deposits.merchant_pub),
+ GNUNET_PQ_result_spec_timestamp (
+ "wallet_timestamp",
+ &td.details.batch_deposits.wallet_timestamp),
+ GNUNET_PQ_result_spec_timestamp (
+ "exchange_timestamp",
+ &td.details.batch_deposits.exchange_timestamp),
+ GNUNET_PQ_result_spec_timestamp (
+ "refund_deadline",
+ &td.details.batch_deposits.refund_deadline),
+ GNUNET_PQ_result_spec_timestamp (
+ "wire_deadline",
+ &td.details.batch_deposits.wire_deadline),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_contract_terms",
+ &td.details.batch_deposits.h_contract_terms),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wallet_data_hash",
+ &td.details.batch_deposits.wallet_data_hash),
+ &td.details.batch_deposits.no_wallet_data_hash),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wire_salt",
+ &td.details.batch_deposits.wire_salt),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wire_target_h_payto",
+ &td.details.batch_deposits.wire_target_h_payto),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "policy_blocked",
+ &td.details.batch_deposits.policy_blocked),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 (
+ "policy_details_serial_id",
+ &td.details.batch_deposits.policy_details_serial_id),
+ &td.details.batch_deposits.no_policy_details),
+ GNUNET_PQ_result_spec_end
+ };
+
+ td.details.batch_deposits.policy_details_serial_id = 0;
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with coin deposits table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_coin_deposits (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_COIN_DEPOSITS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "batch_deposit_serial_id",
+ &td.details.coin_deposits.batch_deposit_serial_id),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.coin_deposits.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_sig",
+ &td.details.coin_deposits.coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.coin_deposits.amount_with_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with refunds table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_refunds (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_REFUNDS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.refunds.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "merchant_sig",
+ &td.details.refunds.merchant_sig),
+ GNUNET_PQ_result_spec_uint64 (
+ "rtransaction_id",
+ &td.details.refunds.rtransaction_id),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.refunds.amount_with_fee),
+ GNUNET_PQ_result_spec_uint64 (
+ "batch_deposit_serial_id",
+ &td.details.refunds.batch_deposit_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wire_out table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wire_out (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WIRE_OUT
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_timestamp (
+ "execution_date",
+ &td.details.wire_out.execution_date),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wtid_raw",
+ &td.details.wire_out.wtid_raw),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wire_target_h_payto",
+ &td.details.wire_out.wire_target_h_payto),
+ GNUNET_PQ_result_spec_string (
+ "exchange_account_section",
+ &td.details.wire_out.exchange_account_section),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount",
+ &td.details.wire_out.amount),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with aggregation_tracking table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_aggregation_tracking (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "batch_deposit_serial_id",
+ &td.details.aggregation_tracking.batch_deposit_serial_id),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wtid_raw",
+ &td.details.aggregation_tracking.wtid_raw),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wire_fee table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wire_fee (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WIRE_FEE
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_string ("wire_method",
+ &td.details.wire_fee.wire_method),
+ GNUNET_PQ_result_spec_timestamp ("start_date",
+ &td.details.wire_fee.start_date),
+ GNUNET_PQ_result_spec_timestamp ("end_date",
+ &td.details.wire_fee.end_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
+ &td.details.wire_fee.fees.wire),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
+ &td.details.wire_fee.fees.closing),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &td.details.wire_fee.master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wire_fee table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_global_fee (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_GLOBAL_FEE
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_timestamp (
+ "start_date",
+ &td.details.global_fee.start_date),
+ GNUNET_PQ_result_spec_timestamp (
+ "end_date",
+ &td.details.global_fee.end_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "history_fee",
+ &td.details.global_fee.fees.history),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "account_fee",
+ &td.details.global_fee.fees.account),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "purse_fee",
+ &td.details.global_fee.fees.purse),
+ GNUNET_PQ_result_spec_relative_time (
+ "purse_timeout",
+ &td.details.global_fee.purse_timeout),
+ GNUNET_PQ_result_spec_relative_time (
+ "history_expiration",
+ &td.details.global_fee.history_expiration),
+ GNUNET_PQ_result_spec_uint32 (
+ "purse_account_limit",
+ &td.details.global_fee.purse_account_limit),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "master_sig",
+ &td.details.global_fee.master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with recoup table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_recoup (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RECOUP
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &td.details.recoup.coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &td.details.recoup.coin_blind),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &td.details.recoup.amount),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &td.details.recoup.timestamp),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.recoup.coin_pub),
+ GNUNET_PQ_result_spec_uint64 ("reserve_out_serial_id",
+ &td.details.recoup.reserve_out_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with recoup_refresh table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_recoup_refresh (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RECOUP_REFRESH
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &td.details.recoup_refresh.coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_blind",
+ &td.details.recoup_refresh.coin_blind),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &td.details.recoup_refresh.amount),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &td.details.recoup_refresh.timestamp),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+ &td.details.recoup_refresh.known_coin_id),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.recoup_refresh.coin_pub),
+ GNUNET_PQ_result_spec_uint64 ("rrc_serial",
+ &td.details.recoup_refresh.rrc_serial),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with extensions table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_extensions (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_EXTENSIONS
+ };
+ bool no_manifest = false;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("extension_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_string ("name",
+ &td.details.extensions.name),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("manifest",
+ &td.details.extensions.manifest),
+ &no_manifest),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with policy_details table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_policy_details (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_POLICY_DETAILS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("policy_details_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("hash_code",
+ &td.details.policy_details.
+ hash_code),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("policy_json",
+ &td.details.policy_details.
+ policy_json),
+ &td.details.policy_details.no_policy_json),
+ GNUNET_PQ_result_spec_timestamp ("deadline",
+ &td.details.policy_details.
+ deadline),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("commitment",
+ &td.details.policy_details.
+ commitment),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("accumulated_total",
+ &td.details.policy_details.
+ accumulated_total),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee",
+ &td.details.policy_details.
+ fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("transferable",
+ &td.details.policy_details.
+ transferable),
+ GNUNET_PQ_result_spec_uint16 ("fulfillment_state",
+ &td.details.policy_details.
+ fulfillment_state),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("fulfillment_id",
+ &td.details.policy_details.
+ fulfillment_id),
+ &td.details.policy_details.no_fulfillment_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with policy_fulfillments table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_policy_fulfillments (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ bool no_proof = false;
+ bool no_timestamp = false;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("fulfillment_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("fulfillment_timestamp",
+ &td.details.policy_fulfillments.
+ fulfillment_timestamp),
+ &no_timestamp),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("fulfillment_proof",
+ &td.details.policy_fulfillments.
+ fulfillment_proof),
+ &no_proof),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with purse_requests table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_purse_requests (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_PURSE_REQUESTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "purse_requests_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.purse_requests.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "merge_pub",
+ &td.details.purse_requests.merge_pub),
+ GNUNET_PQ_result_spec_timestamp (
+ "purse_creation",
+ &td.details.purse_requests.purse_creation),
+ GNUNET_PQ_result_spec_timestamp (
+ "purse_expiration",
+ &td.details.purse_requests.purse_expiration),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_contract_terms",
+ &td.details.purse_requests.h_contract_terms),
+ GNUNET_PQ_result_spec_uint32 (
+ "age_limit",
+ &td.details.purse_requests.age_limit),
+ GNUNET_PQ_result_spec_uint32 (
+ "flags",
+ &td.details.purse_requests.flags),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.purse_requests.amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "purse_fee",
+ &td.details.purse_requests.purse_fee),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_sig",
+ &td.details.purse_requests.purse_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with purse_decision table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_purse_decision (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_PURSE_DECISION
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "purse_refunds_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.purse_decision.purse_pub),
+ GNUNET_PQ_result_spec_timestamp (
+ "action_timestamp",
+ &td.details.purse_decision.action_timestamp),
+ GNUNET_PQ_result_spec_bool (
+ "refunded",
+ &td.details.purse_decision.refunded),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with purse_merges table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_purse_merges (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_PURSE_MERGES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "purse_merge_request_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "partner_serial_id",
+ &td.details.purse_merges.partner_serial_id),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.purse_merges.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.purse_merges.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "merge_sig",
+ &td.details.purse_merges.merge_sig),
+ GNUNET_PQ_result_spec_timestamp (
+ "merge_timestamp",
+ &td.details.purse_merges.merge_timestamp),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with purse_deposits table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_purse_deposits (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_PURSE_DEPOSITS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "purse_deposit_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "partner_serial_id",
+ &td.details.purse_deposits.partner_serial_id),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.purse_deposits.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.purse_deposits.coin_pub),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.purse_deposits.amount_with_fee),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_sig",
+ &td.details.purse_deposits.coin_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with account_merges table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_account_merges (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_ACCOUNT_MERGES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "account_merge_request_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.account_merges.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.account_merges.reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.account_merges.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wallet_h_payto",
+ &td.details.account_merges.wallet_h_payto),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with history_requests table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_history_requests (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_HISTORY_REQUESTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "history_request_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.history_requests.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.history_requests.reserve_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "history_fee",
+ &td.details.history_requests.history_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with close_requests table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_close_requests (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_CLOSE_REQUESTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "close_request_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.close_requests.reserve_pub),
+ GNUNET_PQ_result_spec_timestamp (
+ "close_timestamp",
+ &td.details.close_requests.close_timestamp),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.close_requests.reserve_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "close",
+ &td.details.close_requests.close),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "close_fee",
+ &td.details.close_requests.close_fee),
+ GNUNET_PQ_result_spec_string (
+ "payto_uri",
+ &td.details.close_requests.payto_uri),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wads_out table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wads_out (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WADS_OUT
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "wad_out_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wad_id",
+ &td.details.wads_out.wad_id),
+ GNUNET_PQ_result_spec_uint64 (
+ "partner_serial_id",
+ &td.details.wads_out.partner_serial_id),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount",
+ &td.details.wads_out.amount),
+ GNUNET_PQ_result_spec_timestamp (
+ "execution_time",
+ &td.details.wads_out.execution_time),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wads_out_entries table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wads_out_entries (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WADS_OUT_ENTRIES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "wad_out_entry_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.wads_out_entries.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.wads_out_entries.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_contract",
+ &td.details.wads_out_entries.h_contract),
+ GNUNET_PQ_result_spec_timestamp (
+ "purse_expiration",
+ &td.details.wads_out_entries.purse_expiration),
+ GNUNET_PQ_result_spec_timestamp (
+ "merge_timestamp",
+ &td.details.wads_out_entries.merge_timestamp),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.wads_out_entries.amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "wad_fee",
+ &td.details.wads_out_entries.wad_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "deposit_fees",
+ &td.details.wads_out_entries.deposit_fees),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.wads_out_entries.reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_sig",
+ &td.details.wads_out_entries.purse_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wads_in table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wads_in (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WADS_IN
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "wad_in_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wad_id",
+ &td.details.wads_in.wad_id),
+ GNUNET_PQ_result_spec_string (
+ "origin_exchange_url",
+ &td.details.wads_in.origin_exchange_url),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount",
+ &td.details.wads_in.amount),
+ GNUNET_PQ_result_spec_timestamp (
+ "arrival_time",
+ &td.details.wads_in.arrival_time),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wads_in_entries table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wads_in_entries (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WADS_IN_ENTRIES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "wad_in_entry_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.wads_in_entries.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.wads_in_entries.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_contract",
+ &td.details.wads_in_entries.h_contract),
+ GNUNET_PQ_result_spec_timestamp (
+ "purse_expiration",
+ &td.details.wads_in_entries.purse_expiration),
+ GNUNET_PQ_result_spec_timestamp (
+ "merge_timestamp",
+ &td.details.wads_in_entries.merge_timestamp),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.wads_in_entries.amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "wad_fee",
+ &td.details.wads_in_entries.wad_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "deposit_fees",
+ &td.details.wads_in_entries.deposit_fees),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.wads_in_entries.reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_sig",
+ &td.details.wads_in_entries.purse_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with profit_drains table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_profit_drains (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_PROFIT_DRAINS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "profit_drain_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wtid",
+ &td.details.profit_drains.wtid),
+ GNUNET_PQ_result_spec_string (
+ "account_section",
+ &td.details.profit_drains.account_section),
+ GNUNET_PQ_result_spec_string (
+ "payto_uri",
+ &td.details.profit_drains.payto_uri),
+ GNUNET_PQ_result_spec_timestamp (
+ "trigger_date",
+ &td.details.profit_drains.trigger_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount",
+ &td.details.profit_drains.amount),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "master_sig",
+ &td.details.profit_drains.master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with aml_staff table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_aml_staff (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_AML_STAFF
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "aml_staff_uuid",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "decider_pub",
+ &td.details.aml_staff.decider_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "master_sig",
+ &td.details.aml_staff.master_sig),
+ GNUNET_PQ_result_spec_string (
+ "decider_name",
+ &td.details.aml_staff.decider_name),
+ GNUNET_PQ_result_spec_bool (
+ "is_active",
+ &td.details.aml_staff.is_active),
+ GNUNET_PQ_result_spec_bool (
+ "read_only",
+ &td.details.aml_staff.read_only),
+ GNUNET_PQ_result_spec_timestamp (
+ "last_change",
+ &td.details.aml_staff.last_change),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with aml_history table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_aml_history (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_AML_HISTORY
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint32_t status32;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "aml_history_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_payto",
+ &td.details.aml_history.h_payto),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "new_threshold",
+ &td.details.aml_history.new_threshold),
+ GNUNET_PQ_result_spec_uint32 (
+ "new_status",
+ &status32),
+ GNUNET_PQ_result_spec_timestamp (
+ "decision_time",
+ &td.details.aml_history.decision_time),
+ GNUNET_PQ_result_spec_string (
+ "justification",
+ &td.details.aml_history.justification),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string (
+ "kyc_requirements",
+ &td.details.aml_history.kyc_requirements),
+ NULL),
+ GNUNET_PQ_result_spec_uint64 (
+ "kyc_req_row",
+ &td.details.aml_history.kyc_req_row),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "decider_pub",
+ &td.details.aml_history.decider_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "decider_sig",
+ &td.details.aml_history.decider_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ td.details.aml_history.kyc_requirements = NULL;
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ td.details.aml_history.new_status
+ = (enum TALER_AmlDecisionState) status32;
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with kyc_attributes table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_kyc_attributes (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_KYC_ATTRIBUTES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "kyc_attributes_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_payto",
+ &td.details.kyc_attributes.h_payto),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "kyc_prox",
+ &td.details.kyc_attributes.kyc_prox),
+ GNUNET_PQ_result_spec_string (
+ "provider",
+ &td.details.kyc_attributes.provider),
+ GNUNET_PQ_result_spec_timestamp (
+ "collection_time",
+ &td.details.kyc_attributes.collection_time),
+ GNUNET_PQ_result_spec_timestamp (
+ "expiration_time",
+ &td.details.kyc_attributes.expiration_time),
+ GNUNET_PQ_result_spec_variable_size (
+ "encrypted_attributes",
+ &td.details.kyc_attributes.encrypted_attributes,
+ &td.details.kyc_attributes.encrypted_attributes_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with purse_deletion table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_purse_deletion (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_PURSE_DELETION
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "purse_deletion_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_sig",
+ &td.details.purse_deletion.purse_sig),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.purse_deletion.purse_pub),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with age_withdraw table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_age_withdraw (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_AGE_WITHDRAW
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "age_withdraw_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_commitment",
+ &td.details.age_withdraw.h_commitment),
+ GNUNET_PQ_result_spec_uint16 (
+ "max_age",
+ &td.details.age_withdraw.max_age),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.age_withdraw.amount_with_fee),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.age_withdraw.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.age_withdraw.reserve_sig),
+ GNUNET_PQ_result_spec_uint32 (
+ "noreveal_index",
+ &td.details.age_withdraw.noreveal_index),
+ /* TODO[oec]: more fields! */
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Assign statement to @a n and PREPARE
+ * @a sql under name @a n.
+ */
+#define XPREPARE(n,sql) \
+ statement = n; \
+ PREPARE (pg, n, sql);
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_records_by_table (void *cls,
+ enum TALER_EXCHANGEDB_ReplicatedTable table,
+ uint64_t serial,
+ TALER_EXCHANGEDB_ReplicationCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial),
+ GNUNET_PQ_query_param_end
+ };
+ struct LookupRecordsByTableContext ctx = {
+ .pg = pg,
+ .cb = cb,
+ .cb_cls = cb_cls
+ };
+ GNUNET_PQ_PostgresResultHandler rh = NULL;
+ const char *statement = NULL;
+ enum GNUNET_DB_QueryStatus qs;
+
+ switch (table)
+ {
+ case TALER_EXCHANGEDB_RT_DENOMINATIONS:
+ XPREPARE ("select_above_serial_by_table_denominations",
+ "SELECT"
+ " denominations_serial AS serial"
+ ",denom_type"
+ ",denom_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin"
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",age_mask"
+ " FROM denominations"
+ " WHERE denominations_serial > $1"
+ " ORDER BY denominations_serial ASC;");
+ rh = &lrbt_cb_table_denominations;
+ break;
+ case TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS:
+ XPREPARE ("select_above_serial_by_table_denomination_revocations",
+ "SELECT"
+ " denom_revocations_serial_id AS serial"
+ ",master_sig"
+ ",denominations_serial"
+ " FROM denomination_revocations"
+ " WHERE denom_revocations_serial_id > $1"
+ " ORDER BY denom_revocations_serial_id ASC;");
+ rh = &lrbt_cb_table_denomination_revocations;
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_TARGETS:
+ XPREPARE ("select_above_serial_by_table_wire_targets",
+ "SELECT"
+ " wire_target_serial_id AS serial"
+ ",payto_uri"
+ " FROM wire_targets"
+ " WHERE wire_target_serial_id > $1"
+ " ORDER BY wire_target_serial_id ASC;");
+ rh = &lrbt_cb_table_wire_targets;
+ break;
+ case TALER_EXCHANGEDB_RT_LEGITIMIZATION_PROCESSES:
+ XPREPARE ("select_above_serial_by_table_legitimization_processes",
+ "SELECT"
+ " legitimization_process_serial_id AS serial"
+ ",h_payto"
+ ",reserve_pub"
+ ",expiration_time"
+ ",provider_section"
+ ",provider_user_id"
+ ",provider_legitimization_id"
+ " FROM legitimization_processes"
+ " WHERE legitimization_process_serial_id > $1"
+ " ORDER BY legitimization_process_serial_id ASC;");
+ rh = &lrbt_cb_table_legitimization_processes;
+ break;
+ case TALER_EXCHANGEDB_RT_LEGITIMIZATION_REQUIREMENTS:
+ XPREPARE ("select_above_serial_by_table_legitimization_requirements",
+ "SELECT"
+ " legitimization_requirement_serial_id AS serial"
+ ",h_payto"
+ ",reserve_pub"
+ ",required_checks"
+ " FROM legitimization_requirements"
+ " WHERE legitimization_requirement_serial_id > $1"
+ " ORDER BY legitimization_requirement_serial_id ASC;");
+ rh = &lrbt_cb_table_legitimization_requirements;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES:
+ XPREPARE ("select_above_serial_by_table_reserves",
+ "SELECT"
+ " reserve_uuid AS serial"
+ ",reserve_pub"
+ ",expiration_date"
+ ",gc_date"
+ " FROM reserves"
+ " WHERE reserve_uuid > $1"
+ " ORDER BY reserve_uuid ASC;");
+ rh = &lrbt_cb_table_reserves;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_IN:
+ XPREPARE ("select_above_serial_by_table_reserves_in",
+ "SELECT"
+ " reserve_in_serial_id AS serial"
+ ",reserve_pub"
+ ",wire_reference"
+ ",credit"
+ ",wire_source_h_payto"
+ ",exchange_account_section"
+ ",execution_date"
+ " FROM reserves_in"
+ " WHERE reserve_in_serial_id > $1"
+ " ORDER BY reserve_in_serial_id ASC;");
+ rh = &lrbt_cb_table_reserves_in;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_CLOSE:
+ XPREPARE ("select_above_serial_by_table_reserves_close",
+ "SELECT"
+ " close_uuid AS serial"
+ ",reserve_pub"
+ ",execution_date"
+ ",wtid"
+ ",wire_target_h_payto"
+ ",amount"
+ ",closing_fee"
+ " FROM reserves_close"
+ " WHERE close_uuid > $1"
+ " ORDER BY close_uuid ASC;");
+ rh = &lrbt_cb_table_reserves_close;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OPEN_REQUESTS:
+ XPREPARE ("select_above_serial_by_table_reserves_open_requests",
+ "SELECT"
+ " open_request_uuid AS serial"
+ ",reserve_pub"
+ ",request_timestamp"
+ ",expiration_date"
+ ",reserve_sig"
+ ",reserve_payment"
+ ",requested_purse_limit"
+ " FROM reserves_open_requests"
+ " WHERE open_request_uuid > $1"
+ " ORDER BY open_request_uuid ASC;");
+ rh = &lrbt_cb_table_reserves_open_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OPEN_DEPOSITS:
+ XPREPARE ("select_above_serial_by_table_reserves_open_deposits",
+ "SELECT"
+ " reserves_open_deposit_uuid AS serial"
+ ",reserve_sig"
+ ",reserve_pub"
+ ",coin_pub"
+ ",coin_sig"
+ ",contribution"
+ " FROM reserves_open_deposits"
+ " WHERE reserves_open_deposit_uuid > $1"
+ " ORDER BY reserves_open_deposit_uuid ASC;");
+ rh = &lrbt_cb_table_reserves_open_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OUT:
+ XPREPARE ("select_above_serial_by_table_reserves_out",
+ "SELECT"
+ " reserve_out_serial_id AS serial"
+ ",h_blind_ev"
+ ",denominations_serial"
+ ",denom_sig"
+ ",reserve_uuid"
+ ",reserve_sig"
+ ",execution_date"
+ ",amount_with_fee"
+ " FROM reserves_out"
+ " JOIN reserves USING (reserve_uuid)"
+ " WHERE reserve_out_serial_id > $1"
+ " ORDER BY reserve_out_serial_id ASC;");
+ rh = &lrbt_cb_table_reserves_out;
+ break;
+ case TALER_EXCHANGEDB_RT_AUDITORS:
+ XPREPARE ("select_above_serial_by_table_auditors",
+ "SELECT"
+ " auditor_uuid AS serial"
+ ",auditor_pub"
+ ",auditor_name"
+ ",auditor_url"
+ ",is_active"
+ ",last_change"
+ " FROM auditors"
+ " WHERE auditor_uuid > $1"
+ " ORDER BY auditor_uuid ASC;");
+ rh = &lrbt_cb_table_auditors;
+ break;
+ case TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS:
+ XPREPARE ("select_above_serial_by_table_auditor_denom_sigs",
+ "SELECT"
+ " auditor_denom_serial AS serial"
+ ",auditor_uuid"
+ ",denominations_serial"
+ ",auditor_sig"
+ " FROM auditor_denom_sigs"
+ " WHERE auditor_denom_serial > $1"
+ " ORDER BY auditor_denom_serial ASC;");
+ rh = &lrbt_cb_table_auditor_denom_sigs;
+ break;
+ case TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS:
+ XPREPARE ("select_above_serial_by_table_exchange_sign_keys",
+ "SELECT"
+ " esk_serial AS serial"
+ ",exchange_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_sign"
+ ",expire_legal"
+ " FROM exchange_sign_keys"
+ " WHERE esk_serial > $1"
+ " ORDER BY esk_serial ASC;");
+ rh = &lrbt_cb_table_exchange_sign_keys;
+ break;
+ case TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS:
+ XPREPARE ("select_above_serial_by_table_signkey_revocations",
+ "SELECT"
+ " signkey_revocations_serial_id AS serial"
+ ",esk_serial"
+ ",master_sig"
+ " FROM signkey_revocations"
+ " WHERE signkey_revocations_serial_id > $1"
+ " ORDER BY signkey_revocations_serial_id ASC;");
+ rh = &lrbt_cb_table_signkey_revocations;
+ break;
+ case TALER_EXCHANGEDB_RT_KNOWN_COINS:
+ XPREPARE ("select_above_serial_by_table_known_coins",
+ "SELECT"
+ " known_coin_id AS serial"
+ ",coin_pub"
+ ",denom_sig"
+ ",denominations_serial"
+ " FROM known_coins"
+ " WHERE known_coin_id > $1"
+ " ORDER BY known_coin_id ASC;");
+ rh = &lrbt_cb_table_known_coins;
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS:
+ XPREPARE ("select_above_serial_by_table_refresh_commitments",
+ "SELECT"
+ " melt_serial_id AS serial"
+ ",rc"
+ ",old_coin_sig"
+ ",amount_with_fee"
+ ",noreveal_index"
+ ",old_coin_pub"
+ " FROM refresh_commitments"
+ " WHERE melt_serial_id > $1"
+ " ORDER BY melt_serial_id ASC;");
+ rh = &lrbt_cb_table_refresh_commitments;
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS:
+ XPREPARE ("select_above_serial_by_table_refresh_revealed_coins",
+ "SELECT"
+ " rrc_serial AS serial"
+ ",freshcoin_index"
+ ",link_sig"
+ ",coin_ev"
+ ",ev_sig"
+ ",ewv"
+ ",denominations_serial"
+ ",melt_serial_id"
+ " FROM refresh_revealed_coins"
+ " WHERE rrc_serial > $1"
+ " ORDER BY rrc_serial ASC;");
+ rh = &lrbt_cb_table_refresh_revealed_coins;
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS:
+ XPREPARE ("select_above_serial_by_table_refresh_transfer_keys",
+ "SELECT"
+ " rtc_serial AS serial"
+ ",transfer_pub"
+ ",transfer_privs"
+ ",melt_serial_id"
+ " FROM refresh_transfer_keys"
+ " WHERE rtc_serial > $1"
+ " ORDER BY rtc_serial ASC;");
+ rh = &lrbt_cb_table_refresh_transfer_keys;
+ break;
+ case TALER_EXCHANGEDB_RT_BATCH_DEPOSITS:
+ XPREPARE ("select_above_serial_by_table_batch_deposits",
+ "SELECT"
+ " batch_deposit_serial_id AS serial"
+ ",shard"
+ ",merchant_pub"
+ ",wallet_timestamp"
+ ",exchange_timestamp"
+ ",refund_deadline"
+ ",wire_deadline"
+ ",h_contract_terms"
+ ",wallet_data_hash"
+ ",wire_salt"
+ ",wire_target_h_payto"
+ ",done"
+ ",policy_blocked"
+ ",policy_details_serial_id"
+ " FROM batch_deposits"
+ " WHERE batch_deposit_serial_id > $1"
+ " ORDER BY batch_deposit_serial_id ASC;");
+ rh = &lrbt_cb_table_batch_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_COIN_DEPOSITS:
+ XPREPARE ("select_above_serial_by_table_coin_deposits",
+ "SELECT"
+ " coin_deposit_serial_id AS serial"
+ ",batch_deposit_serial_id"
+ ",coin_pub"
+ ",coin_sig"
+ ",amount_with_fee"
+ " FROM coin_deposits"
+ " WHERE coin_deposit_serial_id > $1"
+ " ORDER BY coin_deposit_serial_id ASC;");
+ rh = &lrbt_cb_table_coin_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_REFUNDS:
+ XPREPARE ("select_above_serial_by_table_refunds",
+ "SELECT"
+ " refund_serial_id AS serial"
+ ",coin_pub"
+ ",merchant_sig"
+ ",rtransaction_id"
+ ",amount_with_fee"
+ ",batch_deposit_serial_id"
+ " FROM refunds"
+ " WHERE refund_serial_id > $1"
+ " ORDER BY refund_serial_id ASC;");
+ rh = &lrbt_cb_table_refunds;
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_OUT:
+ XPREPARE ("select_above_serial_by_table_wire_out",
+ "SELECT"
+ " wireout_uuid AS serial"
+ ",execution_date"
+ ",wtid_raw"
+ ",wire_target_h_payto"
+ ",exchange_account_section"
+ ",amount"
+ " FROM wire_out"
+ " WHERE wireout_uuid > $1"
+ " ORDER BY wireout_uuid ASC;");
+ rh = &lrbt_cb_table_wire_out;
+ break;
+ case TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING:
+ XPREPARE ("select_above_serial_by_table_aggregation_tracking",
+ "SELECT"
+ " aggregation_serial_id AS serial"
+ ",batch_deposit_serial_id"
+ ",wtid_raw"
+ " FROM aggregation_tracking"
+ " WHERE aggregation_serial_id > $1"
+ " ORDER BY aggregation_serial_id ASC;");
+ rh = &lrbt_cb_table_aggregation_tracking;
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_FEE:
+ XPREPARE ("select_above_serial_by_table_wire_fee",
+ "SELECT"
+ " wire_fee_serial AS serial"
+ ",wire_method"
+ ",start_date"
+ ",end_date"
+ ",wire_fee"
+ ",closing_fee"
+ ",master_sig"
+ " FROM wire_fee"
+ " WHERE wire_fee_serial > $1"
+ " ORDER BY wire_fee_serial ASC;");
+ rh = &lrbt_cb_table_wire_fee;
+ break;
+ case TALER_EXCHANGEDB_RT_GLOBAL_FEE:
+ XPREPARE ("select_above_serial_by_table_global_fee",
+ "SELECT"
+ " global_fee_serial AS serial"
+ ",start_date"
+ ",end_date"
+ ",history_fee"
+ ",account_fee"
+ ",purse_fee"
+ ",purse_timeout"
+ ",history_expiration"
+ ",purse_account_limit"
+ ",master_sig"
+ " FROM global_fee"
+ " WHERE global_fee_serial > $1"
+ " ORDER BY global_fee_serial ASC;");
+ rh = &lrbt_cb_table_global_fee;
+ break;
+ case TALER_EXCHANGEDB_RT_RECOUP:
+ XPREPARE ("select_above_serial_by_table_recoup",
+ "SELECT"
+ " recoup_uuid AS serial"
+ ",coin_sig"
+ ",coin_blind"
+ ",amount"
+ ",recoup_timestamp"
+ ",coin_pub"
+ ",reserve_out_serial_id"
+ " FROM recoup"
+ " WHERE recoup_uuid > $1"
+ " ORDER BY recoup_uuid ASC;");
+ rh = &lrbt_cb_table_recoup;
+ break;
+ case TALER_EXCHANGEDB_RT_RECOUP_REFRESH:
+ XPREPARE ("select_above_serial_by_table_recoup_refresh",
+ "SELECT"
+ " recoup_refresh_uuid AS serial"
+ ",coin_sig"
+ ",coin_blind"
+ ",amount"
+ ",recoup_timestamp"
+ ",coin_pub"
+ ",known_coin_id"
+ ",rrc_serial"
+ " FROM recoup_refresh"
+ " WHERE recoup_refresh_uuid > $1"
+ " ORDER BY recoup_refresh_uuid ASC;");
+ rh = &lrbt_cb_table_recoup_refresh;
+ break;
+ case TALER_EXCHANGEDB_RT_EXTENSIONS:
+ statement = "select_above_serial_by_table_extensions";
+ rh = &lrbt_cb_table_extensions;
+ break;
+ case TALER_EXCHANGEDB_RT_POLICY_DETAILS:
+ statement = "select_above_serial_by_table_policy_details";
+ rh = &lrbt_cb_table_policy_details;
+ break;
+ case TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS:
+ statement = "select_above_serial_by_table_policy_fulfillments";
+ rh = &lrbt_cb_table_policy_fulfillments;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_REQUESTS:
+ XPREPARE ("select_above_serial_by_table_purse_requests",
+ "SELECT"
+ " purse_requests_serial_id"
+ ",purse_pub"
+ ",merge_pub"
+ ",purse_creation"
+ ",purse_expiration"
+ ",h_contract_terms"
+ ",age_limit"
+ ",flags"
+ ",amount_with_fee"
+ ",purse_fee"
+ ",purse_sig"
+ " FROM purse_requests"
+ " WHERE purse_requests_serial_id > $1"
+ " ORDER BY purse_requests_serial_id ASC;");
+ rh = &lrbt_cb_table_purse_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DECISION:
+ XPREPARE ("select_above_serial_by_table_purse_decision",
+ "SELECT"
+ " purse_decision_serial_id"
+ ",action_timestamp"
+ ",refunded"
+ ",purse_pub"
+ " FROM purse_decision"
+ " WHERE purse_decision_serial_id > $1"
+ " ORDER BY purse_decision_serial_id ASC;");
+ rh = &lrbt_cb_table_purse_decision;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_MERGES:
+ XPREPARE ("select_above_serial_by_table_purse_merges",
+ "SELECT"
+ " purse_merge_request_serial_id"
+ ",partner_serial_id"
+ ",reserve_pub"
+ ",purse_pub"
+ ",merge_sig"
+ ",merge_timestamp"
+ " FROM purse_merges"
+ " WHERE purse_merge_request_serial_id > $1"
+ " ORDER BY purse_merge_request_serial_id ASC;");
+ rh = &lrbt_cb_table_purse_merges;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DEPOSITS:
+ XPREPARE ("select_above_serial_by_table_purse_deposits",
+ "SELECT"
+ " purse_deposit_serial_id"
+ ",partner_serial_id"
+ ",purse_pub"
+ ",coin_pub"
+ ",amount_with_fee"
+ ",coin_sig"
+ " FROM purse_deposits"
+ " WHERE purse_deposit_serial_id > $1"
+ " ORDER BY purse_deposit_serial_id ASC;");
+ rh = &lrbt_cb_table_purse_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_ACCOUNT_MERGES:
+ XPREPARE ("select_above_serial_by_table_account_merges",
+ "SELECT"
+ " account_merge_request_serial_id"
+ ",reserve_pub"
+ ",reserve_sig"
+ ",purse_pub"
+ ",wallet_h_payto"
+ " FROM account_merges"
+ " WHERE account_merge_request_serial_id > $1"
+ " ORDER BY account_merge_request_serial_id ASC;");
+ rh = &lrbt_cb_table_account_merges;
+ break;
+ case TALER_EXCHANGEDB_RT_HISTORY_REQUESTS:
+ XPREPARE ("select_above_serial_by_table_history_requests",
+ "SELECT"
+ " history_request_serial_id"
+ ",reserve_pub"
+ ",request_timestamp"
+ ",reserve_sig"
+ ",history_fee"
+ " FROM history_requests"
+ " WHERE history_request_serial_id > $1"
+ " ORDER BY history_request_serial_id ASC;");
+ rh = &lrbt_cb_table_history_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_CLOSE_REQUESTS:
+ XPREPARE ("select_above_serial_by_table_close_requests",
+ "SELECT"
+ " close_request_serial_id"
+ ",reserve_pub"
+ ",close_timestamp"
+ ",reserve_sig"
+ ",close"
+ " FROM close_requests"
+ " WHERE close_request_serial_id > $1"
+ " ORDER BY close_request_serial_id ASC;");
+ rh = &lrbt_cb_table_close_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_OUT:
+ XPREPARE ("select_above_serial_by_table_wads_out",
+ "SELECT"
+ " wad_out_serial_id"
+ ",wad_id"
+ ",partner_serial_id"
+ ",amount"
+ ",execution_time"
+ " FROM wads_out"
+ " WHERE wad_out_serial_id > $1"
+ " ORDER BY wad_out_serial_id ASC;");
+ rh = &lrbt_cb_table_wads_out;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_OUT_ENTRIES:
+ XPREPARE ("select_above_serial_by_table_wads_out_entries",
+ "SELECT"
+ " wad_out_entry_serial_id"
+ ",reserve_pub"
+ ",purse_pub"
+ ",h_contract"
+ ",purse_expiration"
+ ",merge_timestamp"
+ ",amount_with_fee"
+ ",wad_fee"
+ ",deposit_fees"
+ ",reserve_sig"
+ ",purse_sig"
+ " FROM wad_out_entries"
+ " WHERE wad_out_entry_serial_id > $1"
+ " ORDER BY wad_out_entry_serial_id ASC;");
+ rh = &lrbt_cb_table_wads_out_entries;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_IN:
+ XPREPARE ("select_above_serial_by_table_wads_in",
+ "SELECT"
+ " wad_in_serial_id"
+ ",wad_id"
+ ",origin_exchange_url"
+ ",amount"
+ ",arrival_time"
+ " FROM wads_in"
+ " WHERE wad_in_serial_id > $1"
+ " ORDER BY wad_in_serial_id ASC;");
+ rh = &lrbt_cb_table_wads_in;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_IN_ENTRIES:
+ XPREPARE ("select_above_serial_by_table_wads_in_entries",
+ "SELECT"
+ " wad_in_entry_serial_id"
+ ",reserve_pub"
+ ",purse_pub"
+ ",h_contract"
+ ",purse_expiration"
+ ",merge_timestamp"
+ ",amount_with_fee"
+ ",wad_fee"
+ ",deposit_fees"
+ ",reserve_sig"
+ ",purse_sig"
+ " FROM wad_in_entries"
+ " WHERE wad_in_entry_serial_id > $1"
+ " ORDER BY wad_in_entry_serial_id ASC;");
+ rh = &lrbt_cb_table_wads_in_entries;
+ break;
+ case TALER_EXCHANGEDB_RT_PROFIT_DRAINS:
+ XPREPARE ("select_above_serial_by_table_profit_drains",
+ "SELECT"
+ " profit_drain_serial_id"
+ ",wtid"
+ ",account_section"
+ ",payto_uri"
+ ",trigger_date"
+ ",amount"
+ ",master_sig"
+ " FROM profit_drains"
+ " WHERE profit_drain_serial_id > $1"
+ " ORDER BY profit_drain_serial_id ASC;");
+ rh = &lrbt_cb_table_profit_drains;
+ break;
+
+ case TALER_EXCHANGEDB_RT_AML_STAFF:
+ XPREPARE ("select_above_serial_by_table_aml_staff",
+ "SELECT"
+ " aml_staff_uuid"
+ ",decider_pub"
+ ",master_sig"
+ ",decider_name"
+ ",is_active"
+ ",read_only"
+ ",last_change"
+ " FROM aml_staff"
+ " WHERE aml_staff_uuid > $1"
+ " ORDER BY aml_staff_uuid ASC;");
+ rh = &lrbt_cb_table_aml_staff;
+ break;
+ case TALER_EXCHANGEDB_RT_AML_HISTORY:
+ XPREPARE ("select_above_serial_by_table_aml_history",
+ "SELECT"
+ " aml_history_serial_id"
+ ",h_payto"
+ ",new_threshold"
+ ",new_status"
+ ",decision_time"
+ ",justification"
+ ",kyc_requirements"
+ ",kyc_req_row"
+ ",decider_pub"
+ ",decider_sig"
+ " FROM aml_history"
+ " WHERE aml_history_serial_id > $1"
+ " ORDER BY aml_history_serial_id ASC;");
+ rh = &lrbt_cb_table_aml_history;
+ break;
+ case TALER_EXCHANGEDB_RT_KYC_ATTRIBUTES:
+ XPREPARE ("select_above_serial_by_table_kyc_attributes",
+ "SELECT"
+ " kyc_attributes_serial_id"
+ ",h_payto"
+ ",kyc_prox"
+ ",provider"
+ ",collection_time"
+ ",expiration_time"
+ ",encrypted_attributes"
+ " FROM kyc_attributes"
+ " WHERE kyc_attributes_serial_id > $1"
+ " ORDER BY kyc_attributes_serial_id ASC;");
+ rh = &lrbt_cb_table_kyc_attributes;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DELETION:
+ XPREPARE ("select_above_serial_by_table_purse_deletion",
+ "SELECT"
+ " purse_deletion_serial_id"
+ ",purse_pub"
+ ",purse_sig"
+ " FROM purse_deletion"
+ " WHERE purse_deletion_serial_id > $1"
+ " ORDER BY purse_deletion_serial_id ASC;");
+ rh = &lrbt_cb_table_purse_deletion;
+ break;
+ case TALER_EXCHANGEDB_RT_AGE_WITHDRAW:
+ XPREPARE ("select_above_serial_by_table_age_withdraw",
+ "SELECT"
+ " age_withdraw_id"
+ ",h_commitment"
+ ",amount_with_fee"
+ ",max_age"
+ ",reserve_pub"
+ ",reserve_sig"
+ ",noreveal_index"
+ " FROM age_withdraw"
+ " WHERE age_withdraw_id > $1"
+ " ORDER BY age_withdraw_id ASC;");
+ /* TODO[oec]: MORE FIELDS! */
+ rh = &lrbt_cb_table_age_withdraw;
+ break;
+ }
+ if (NULL == rh)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ statement,
+ params,
+ rh,
+ &ctx);
+ if (qs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to run `%s'\n",
+ statement);
+ return qs;
+ }
+ if (ctx.error)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+}
+
+
+#undef XPREPARE
+
+/* end of pg_lookup_records_by_table.c */
diff --git a/src/exchangedb/pg_lookup_records_by_table.h b/src/exchangedb/pg_lookup_records_by_table.h
new file mode 100644
index 000000000..9dc25fff4
--- /dev/null
+++ b/src/exchangedb/pg_lookup_records_by_table.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_lookup_records_by_table.h
+ * @brief implementation of the lookup_records_by_table function
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_RECORDS_BY_TABLE_H
+#define PG_LOOKUP_RECORDS_BY_TABLE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup records above @a serial number in @a table. Used in
+ * exchange-auditor database replication.
+ *
+ * @param cls closure
+ * @param table table for which we should return the serial
+ * @param serial largest serial number to exclude
+ * @param cb function to call on the records
+ * @param cb_cls closure for @a cb
+ * @return transaction status code, GNUNET_DB_STATUS_HARD_ERROR if
+ * @a table does not have a serial number
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_records_by_table (void *cls,
+ enum TALER_EXCHANGEDB_ReplicatedTable table,
+ uint64_t serial,
+ TALER_EXCHANGEDB_ReplicationCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_lookup_serial_by_table.c b/src/exchangedb/pg_lookup_serial_by_table.c
new file mode 100644
index 000000000..9fda7ddf8
--- /dev/null
+++ b/src/exchangedb/pg_lookup_serial_by_table.c
@@ -0,0 +1,459 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 pg_lookup_serial_by_table.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_serial_by_table.h"
+#include "pg_helper.h"
+
+
+/**
+ * Assign statement to @a n and PREPARE
+ * @a sql under name @a n.
+ */
+#define XPREPARE(n,sql) \
+ statement = n; \
+ PREPARE (pg, n, sql);
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_serial_by_table (void *cls,
+ enum TALER_EXCHANGEDB_ReplicatedTable table,
+ uint64_t *serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ serial),
+ GNUNET_PQ_result_spec_end
+ };
+ const char *statement = NULL;
+
+ switch (table)
+ {
+ case TALER_EXCHANGEDB_RT_DENOMINATIONS:
+ XPREPARE ("select_serial_by_table_denominations",
+ "SELECT"
+ " denominations_serial AS serial"
+ " FROM denominations"
+ " ORDER BY denominations_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS:
+ XPREPARE ("select_serial_by_table_denomination_revocations",
+ "SELECT"
+ " denom_revocations_serial_id AS serial"
+ " FROM denomination_revocations"
+ " ORDER BY denom_revocations_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_TARGETS:
+ XPREPARE ("select_serial_by_table_wire_targets",
+ "SELECT"
+ " wire_target_serial_id AS serial"
+ " FROM wire_targets"
+ " ORDER BY wire_target_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_LEGITIMIZATION_PROCESSES:
+ XPREPARE ("select_serial_by_table_legitimization_processes",
+ "SELECT"
+ " legitimization_process_serial_id AS serial"
+ " FROM legitimization_processes"
+ " ORDER BY legitimization_process_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_LEGITIMIZATION_REQUIREMENTS:
+ XPREPARE ("select_serial_by_table_legitimization_requiremetns",
+ "SELECT"
+ " legitimization_requirement_serial_id AS serial"
+ " FROM legitimization_requirements"
+ " ORDER BY legitimization_requirement_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES:
+ XPREPARE ("select_serial_by_table_reserves",
+ "SELECT"
+ " reserve_uuid AS serial"
+ " FROM reserves"
+ " ORDER BY reserve_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_IN:
+ XPREPARE ("select_serial_by_table_reserves_in",
+ "SELECT"
+ " reserve_in_serial_id AS serial"
+ " FROM reserves_in"
+ " ORDER BY reserve_in_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_CLOSE:
+ XPREPARE ("select_serial_by_table_reserves_close",
+ "SELECT"
+ " close_uuid AS serial"
+ " FROM reserves_close"
+ " ORDER BY close_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OPEN_REQUESTS:
+ XPREPARE ("select_serial_by_table_reserves_open_requests",
+ "SELECT"
+ " open_request_uuid AS serial"
+ " FROM reserves_open_requests"
+ " ORDER BY open_request_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OPEN_DEPOSITS:
+ XPREPARE ("select_serial_by_table_reserves_open_deposits",
+ "SELECT"
+ " reserve_open_deposit_uuid AS serial"
+ " FROM reserves_open_deposits"
+ " ORDER BY reserve_open_deposit_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OUT:
+ XPREPARE ("select_serial_by_table_reserves_out",
+ "SELECT"
+ " reserve_out_serial_id AS serial"
+ " FROM reserves_out"
+ " ORDER BY reserve_out_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_AUDITORS:
+ XPREPARE ("select_serial_by_table_auditors",
+ "SELECT"
+ " auditor_uuid AS serial"
+ " FROM auditors"
+ " ORDER BY auditor_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS:
+ XPREPARE ("select_serial_by_table_auditor_denom_sigs",
+ "SELECT"
+ " auditor_denom_serial AS serial"
+ " FROM auditor_denom_sigs"
+ " ORDER BY auditor_denom_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS:
+ XPREPARE ("select_serial_by_table_exchange_sign_keys",
+ "SELECT"
+ " esk_serial AS serial"
+ " FROM exchange_sign_keys"
+ " ORDER BY esk_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS:
+ XPREPARE ("select_serial_by_table_signkey_revocations",
+ "SELECT"
+ " signkey_revocations_serial_id AS serial"
+ " FROM signkey_revocations"
+ " ORDER BY signkey_revocations_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_KNOWN_COINS:
+ XPREPARE ("select_serial_by_table_known_coins",
+ "SELECT"
+ " known_coin_id AS serial"
+ " FROM known_coins"
+ " ORDER BY known_coin_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS:
+ XPREPARE ("select_serial_by_table_refresh_commitments",
+ "SELECT"
+ " melt_serial_id AS serial"
+ " FROM refresh_commitments"
+ " ORDER BY melt_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS:
+ XPREPARE ("select_serial_by_table_refresh_revealed_coins",
+ "SELECT"
+ " rrc_serial AS serial"
+ " FROM refresh_revealed_coins"
+ " ORDER BY rrc_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS:
+ XPREPARE ("select_serial_by_table_refresh_transfer_keys",
+ "SELECT"
+ " rtc_serial AS serial"
+ " FROM refresh_transfer_keys"
+ " ORDER BY rtc_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_BATCH_DEPOSITS:
+ XPREPARE ("select_serial_by_table_batch_deposits",
+ "SELECT"
+ " batch_deposit_serial_id AS serial"
+ " FROM batch_deposits"
+ " ORDER BY batch_deposit_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_COIN_DEPOSITS:
+ XPREPARE ("select_serial_by_table_coin_deposits",
+ "SELECT"
+ " coin_deposit_serial_id AS serial"
+ " FROM coin_deposits"
+ " ORDER BY coin_deposit_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_REFUNDS:
+ XPREPARE ("select_serial_by_table_refunds",
+ "SELECT"
+ " refund_serial_id AS serial"
+ " FROM refunds"
+ " ORDER BY refund_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_OUT:
+ XPREPARE ("select_serial_by_table_wire_out",
+ "SELECT"
+ " wireout_uuid AS serial"
+ " FROM wire_out"
+ " ORDER BY wireout_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING:
+ XPREPARE ("select_serial_by_table_aggregation_tracking",
+ "SELECT"
+ " aggregation_serial_id AS serial"
+ " FROM aggregation_tracking"
+ " ORDER BY aggregation_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_FEE:
+ XPREPARE ("select_serial_by_table_wire_fee",
+ "SELECT"
+ " wire_fee_serial AS serial"
+ " FROM wire_fee"
+ " ORDER BY wire_fee_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_GLOBAL_FEE:
+ XPREPARE ("select_serial_by_table_global_fee",
+ "SELECT"
+ " global_fee_serial AS serial"
+ " FROM global_fee"
+ " ORDER BY global_fee_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RECOUP:
+ XPREPARE ("select_serial_by_table_recoup",
+ "SELECT"
+ " recoup_uuid AS serial"
+ " FROM recoup"
+ " ORDER BY recoup_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RECOUP_REFRESH:
+ XPREPARE ("select_serial_by_table_recoup_refresh",
+ "SELECT"
+ " recoup_refresh_uuid AS serial"
+ " FROM recoup_refresh"
+ " ORDER BY recoup_refresh_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_EXTENSIONS:
+ XPREPARE ("select_serial_by_table_extensions",
+ "SELECT"
+ " extension_id AS serial"
+ " FROM extensions"
+ " ORDER BY extension_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_POLICY_DETAILS:
+ XPREPARE ("select_serial_by_table_policy_details",
+ "SELECT"
+ " policy_details_serial_id AS serial"
+ " FROM policy_details"
+ " ORDER BY policy_details_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS:
+ XPREPARE ("select_serial_by_table_policy_fulfillments",
+ "SELECT"
+ " fulfillment_id AS serial"
+ " FROM policy_fulfillments"
+ " ORDER BY fulfillment_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_REQUESTS:
+ XPREPARE ("select_serial_by_table_purse_requests",
+ "SELECT"
+ " purse_requests_serial_id AS serial"
+ " FROM purse_requests"
+ " ORDER BY purse_requests_serial_id DESC"
+ " LIMIT 1;")
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DECISION:
+ XPREPARE ("select_serial_by_table_purse_decision",
+ "SELECT"
+ " purse_decision_serial_id AS serial"
+ " FROM purse_decision"
+ " ORDER BY purse_decision_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_MERGES:
+ XPREPARE ("select_serial_by_table_purse_merges",
+ "SELECT"
+ " purse_merge_request_serial_id AS serial"
+ " FROM purse_merges"
+ " ORDER BY purse_merge_request_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DEPOSITS:
+ XPREPARE ("select_serial_by_table_purse_deposits",
+ "SELECT"
+ " purse_deposit_serial_id AS serial"
+ " FROM purse_deposits"
+ " ORDER BY purse_deposit_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_ACCOUNT_MERGES:
+ XPREPARE ("select_serial_by_table_account_merges",
+ "SELECT"
+ " account_merge_request_serial_id AS serial"
+ " FROM account_merges"
+ " ORDER BY account_merge_request_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_HISTORY_REQUESTS:
+ XPREPARE ("select_serial_by_table_history_requests",
+ "SELECT"
+ " history_request_serial_id AS serial"
+ " FROM history_requests"
+ " ORDER BY history_request_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_CLOSE_REQUESTS:
+ XPREPARE ("select_serial_by_table_close_requests",
+ "SELECT"
+ " close_request_serial_id AS serial"
+ " FROM close_requests"
+ " ORDER BY close_request_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_OUT:
+ XPREPARE ("select_serial_by_table_wads_out",
+ "SELECT"
+ " wad_out_serial_id AS serial"
+ " FROM wads_out"
+ " ORDER BY wad_out_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_OUT_ENTRIES:
+ XPREPARE ("select_serial_by_table_wads_out_entries",
+ "SELECT"
+ " wad_out_entry_serial_id AS serial"
+ " FROM wad_out_entries"
+ " ORDER BY wad_out_entry_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_IN:
+ XPREPARE ("select_serial_by_table_wads_in",
+ "SELECT"
+ " wad_in_serial_id AS serial"
+ " FROM wads_in"
+ " ORDER BY wad_in_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_IN_ENTRIES:
+ XPREPARE ("select_serial_by_table_wads_in_entries",
+ "SELECT"
+ " wad_in_entry_serial_id AS serial"
+ " FROM wad_in_entries"
+ " ORDER BY wad_in_entry_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_PROFIT_DRAINS:
+ XPREPARE ("select_serial_by_table_profit_drains",
+ "SELECT"
+ " profit_drain_serial_id AS serial"
+ " FROM profit_drains"
+ " ORDER BY profit_drain_serial_id DESC"
+ " LIMIT 1;");
+ statement = "select_serial_by_table_profit_drains";
+ break;
+ case TALER_EXCHANGEDB_RT_AML_STAFF:
+ XPREPARE ("select_serial_by_table_aml_staff",
+ "SELECT"
+ " aml_staff_uuid AS serial"
+ " FROM aml_staff"
+ " ORDER BY aml_staff_uuid DESC"
+ " LIMIT 1;");
+ statement = "select_serial_by_table_aml_staff";
+ break;
+ case TALER_EXCHANGEDB_RT_AML_HISTORY:
+ XPREPARE ("select_serial_by_table_aml_history",
+ "SELECT"
+ " aml_history_serial_id AS serial"
+ " FROM aml_history"
+ " ORDER BY aml_history_serial_id DESC"
+ " LIMIT 1;");
+ statement = "select_serial_by_table_aml_history";
+ break;
+ case TALER_EXCHANGEDB_RT_KYC_ATTRIBUTES:
+ XPREPARE ("select_serial_by_table_kyc_attributes",
+ "SELECT"
+ " kyc_attributes_serial_id AS serial"
+ " FROM kyc_attributes"
+ " ORDER BY kyc_attributes_serial_id DESC"
+ " LIMIT 1;");
+ statement = "select_serial_by_table_kyc_attributes";
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DELETION:
+ XPREPARE ("select_serial_by_table_purse_deletion",
+ "SELECT"
+ " purse_deletion_serial_id AS serial"
+ " FROM purse_deletion"
+ " ORDER BY purse_deletion_serial_id DESC"
+ " LIMIT 1;");
+ statement = "select_serial_by_table_purse_deletion";
+ break;
+ case TALER_EXCHANGEDB_RT_AGE_WITHDRAW:
+ XPREPARE ("select_serial_by_table_age_withdraw",
+ "SELECT"
+ " age_withdraw_id AS serial"
+ " FROM age_withdraw"
+ " ORDER BY age_withdraw_id DESC"
+ " LIMIT 1;");
+ statement = "select_serial_by_table_age_withdraw";
+ break;
+ }
+ if (NULL == statement)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ statement,
+ params,
+ rs);
+}
+
+
+#undef XPREPARE
diff --git a/src/exchangedb/pg_lookup_serial_by_table.h b/src/exchangedb/pg_lookup_serial_by_table.h
new file mode 100644
index 000000000..815b6477d
--- /dev/null
+++ b/src/exchangedb/pg_lookup_serial_by_table.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_lookup_serial_by_table.h
+ * @brief implementation of the lookup_serial_by_table function
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_SERIAL_BY_TABLE_H
+#define PG_LOOKUP_SERIAL_BY_TABLE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup the latest serial number of @a table. Used in
+ * exchange-auditor database replication.
+ *
+ * @param cls closure
+ * @param table table for which we should return the serial
+ * @param[out] serial latest serial number in use
+ * @return transaction status code, GNUNET_DB_STATUS_HARD_ERROR if
+ * @a table does not have a serial number
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_serial_by_table (void *cls,
+ enum TALER_EXCHANGEDB_ReplicatedTable table,
+ uint64_t *serial);
+
+
+#endif
diff --git a/src/exchangedb/pg_lookup_signing_key.c b/src/exchangedb/pg_lookup_signing_key.c
new file mode 100644
index 000000000..3803d114f
--- /dev/null
+++ b/src/exchangedb/pg_lookup_signing_key.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_signing_key.c
+ * @brief Implementation of the lookup_signing_key function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_signing_key.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_signing_key (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_EXCHANGEDB_SignkeyMetaData *meta)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &meta->start),
+ GNUNET_PQ_result_spec_timestamp ("expire_sign",
+ &meta->expire_sign),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &meta->expire_legal),
+ GNUNET_PQ_result_spec_end
+ };
+
+
+ PREPARE (pg,
+ "lookup_signing_key",
+ "SELECT"
+ " valid_from"
+ ",expire_sign"
+ ",expire_legal"
+ " FROM exchange_sign_keys"
+ " WHERE exchange_pub=$1");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_signing_key",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_signing_key.h b/src/exchangedb/pg_lookup_signing_key.h
new file mode 100644
index 000000000..487d60d2b
--- /dev/null
+++ b/src/exchangedb/pg_lookup_signing_key.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_signing_key.h
+ * @brief implementation of the lookup_signing_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_SIGNING_KEY_H
+#define PG_LOOKUP_SIGNING_KEY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup signing key meta data.
+ *
+ * @param cls closure
+ * @param exchange_pub the exchange online signing public key
+ * @param[out] meta meta data about @a exchange_pub
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_signing_key (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_EXCHANGEDB_SignkeyMetaData *meta);
+#endif
diff --git a/src/exchangedb/pg_lookup_signkey_revocation.c b/src/exchangedb/pg_lookup_signkey_revocation.c
new file mode 100644
index 000000000..056ecddc4
--- /dev/null
+++ b/src/exchangedb/pg_lookup_signkey_revocation.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_signkey_revocation.c
+ * @brief Implementation of the lookup_signkey_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_signkey_revocation.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_signkey_revocation (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "lookup_signkey_revocation",
+ "SELECT "
+ " master_sig"
+ " FROM signkey_revocations"
+ " WHERE esk_serial="
+ " (SELECT esk_serial"
+ " FROM exchange_sign_keys"
+ " WHERE exchange_pub=$1);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_signkey_revocation",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_signkey_revocation.h b/src/exchangedb/pg_lookup_signkey_revocation.h
new file mode 100644
index 000000000..de0fb1d74
--- /dev/null
+++ b/src/exchangedb/pg_lookup_signkey_revocation.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_signkey_revocation.h
+ * @brief implementation of the lookup_signkey_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_SIGNKEY_REVOCATION_H
+#define PG_LOOKUP_SIGNKEY_REVOCATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain information about a revoked online signing key.
+ *
+ * @param cls closure
+ * @param exchange_pub exchange online signing key
+ * @param[out] master_sig set to signature affirming the revocation (if revoked)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_signkey_revocation (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_transfer_by_deposit.c b/src/exchangedb/pg_lookup_transfer_by_deposit.c
new file mode 100644
index 000000000..192556130
--- /dev/null
+++ b/src/exchangedb/pg_lookup_transfer_by_deposit.c
@@ -0,0 +1,239 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_lookup_transfer_by_deposit.c
+ * @brief Implementation of the lookup_transfer_by_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_transfer_by_deposit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_transfer_by_deposit (
+ void *cls,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ bool *pending,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_TIME_Timestamp *exec_time,
+ struct TALER_Amount *amount_with_fee,
+ struct TALER_Amount *deposit_fee,
+ struct TALER_EXCHANGEDB_KycStatus *kyc,
+ enum TALER_AmlDecisionState *aml_decision)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_end
+ };
+ char *payto_uri;
+ struct TALER_WireSaltP wire_salt;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
+ wtid),
+ GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+ &wire_salt),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ exec_time),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ deposit_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ memset (kyc,
+ 0,
+ sizeof (*kyc));
+ /* check if the aggregation record exists and get it */
+ PREPARE (pg,
+ "lookup_deposit_wtid",
+ "SELECT"
+ " atr.wtid_raw"
+ ",wire_out.execution_date"
+ ",cdep.amount_with_fee"
+ ",bdep.wire_salt"
+ ",wt.payto_uri"
+ ",denom.fee_deposit"
+ " FROM coin_deposits cdep"
+ " JOIN batch_deposits bdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN wire_targets wt"
+ " USING (wire_target_h_payto)"
+ " JOIN aggregation_tracking atr"
+ " ON (cdep.batch_deposit_serial_id = atr.batch_deposit_serial_id)"
+ " JOIN known_coins kc"
+ " ON (kc.coin_pub = cdep.coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " JOIN wire_out"
+ " USING (wtid_raw)"
+ " WHERE cdep.coin_pub=$1"
+ " AND bdep.merchant_pub=$3"
+ " AND bdep.h_contract_terms=$2");
+ /* NOTE: above query might be more efficient if we computed the shard
+ from the merchant_pub and included that in the query */
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_deposit_wtid",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ struct TALER_MerchantWireHashP wh;
+
+ TALER_merchant_wire_signature_hash (payto_uri,
+ &wire_salt,
+ &wh);
+ if (0 ==
+ GNUNET_memcmp (&wh,
+ h_wire))
+ {
+ *pending = false;
+ kyc->ok = true;
+ *aml_decision = TALER_AML_NORMAL;
+ GNUNET_PQ_cleanup_result (rs);
+ return qs;
+ }
+ qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ GNUNET_PQ_cleanup_result (rs);
+ }
+ if (0 > qs)
+ return qs;
+ *pending = true;
+ memset (wtid,
+ 0,
+ sizeof (*wtid));
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "lookup_deposit_wtid returned 0 matching rows\n");
+ {
+ /* Check if transaction exists in deposits, so that we just
+ do not have a WTID yet. In that case, return without wtid
+ (by setting 'pending' true). */
+ uint32_t status32 = TALER_AML_NORMAL;
+ uint64_t aml_kyc_row = 0;
+ struct GNUNET_PQ_ResultSpec rs2[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+ &wire_salt),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ /* See Postgresql bug #18380 */
+#define BUG 1
+#if BUG
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("legitimization_requirement_serial_id",
+ &kyc->requirement_row),
+ NULL),
+#endif
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("kyc_requirement",
+ &aml_kyc_row),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint32 ("status",
+ &status32),
+ NULL),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ deposit_fee),
+ GNUNET_PQ_result_spec_timestamp ("wire_deadline",
+ exec_time),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_deposit_without_wtid",
+ "SELECT"
+ " bdep.wire_salt"
+ ",wt.payto_uri"
+ ",cdep.amount_with_fee"
+ ",denom.fee_deposit"
+ ",bdep.wire_deadline"
+#if BUG
+ ",agt.legitimization_requirement_serial_id"
+#endif
+ ",aml.status"
+ ",aml.kyc_requirement"
+ " FROM coin_deposits cdep"
+ " JOIN batch_deposits bdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN wire_targets wt"
+ " USING (wire_target_h_payto)"
+ " JOIN known_coins kc"
+ " ON (kc.coin_pub = cdep.coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+#if BUG
+ " LEFT JOIN aggregation_transient agt "
+ " ON ( (bdep.wire_target_h_payto = agt.wire_target_h_payto) AND"
+ " (bdep.merchant_pub = agt.merchant_pub) )"
+#endif
+ " LEFT JOIN aml_status aml"
+ " ON (wt.wire_target_h_payto = aml.h_payto)"
+ " WHERE cdep.coin_pub=$1"
+ " AND bdep.merchant_pub=$3"
+ " AND bdep.h_contract_terms=$2"
+ " LIMIT 1;");
+ /* NOTE: above query might be more efficient if we computed the shard
+ from the merchant_pub and included that in the query */
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_deposit_without_wtid",
+ params,
+ rs2);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ struct TALER_MerchantWireHashP wh;
+
+ *aml_decision = (enum TALER_AmlDecisionState) status32;
+ if (0 == kyc->requirement_row)
+ kyc->ok = true; /* technically: unknown */
+ if ( (kyc->ok) &&
+ (TALER_AML_FROZEN == *aml_decision) &&
+ (0 != aml_kyc_row) )
+ {
+ /* KYC required via AML */
+ kyc->ok = false;
+ kyc->requirement_row = aml_kyc_row;
+ }
+ TALER_merchant_wire_signature_hash (payto_uri,
+ &wire_salt,
+ &wh);
+ if (0 !=
+ GNUNET_memcmp (&wh,
+ h_wire))
+ {
+ GNUNET_PQ_cleanup_result (rs2);
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ GNUNET_PQ_cleanup_result (rs2);
+ }
+ *aml_decision = TALER_AML_NORMAL;
+ return qs;
+ }
+}
diff --git a/src/exchangedb/pg_lookup_transfer_by_deposit.h b/src/exchangedb/pg_lookup_transfer_by_deposit.h
new file mode 100644
index 000000000..83782d5a0
--- /dev/null
+++ b/src/exchangedb/pg_lookup_transfer_by_deposit.h
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_transfer_by_deposit.h
+ * @brief implementation of the lookup_transfer_by_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_TRANSFER_BY_DEPOSIT_H
+#define PG_LOOKUP_TRANSFER_BY_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Try to find the wire transfer details for a deposit operation.
+ * If we did not execute the deposit yet, return when it is supposed
+ * to be executed.
+ *
+ * @param cls closure
+ * @param h_contract_terms hash of the proposal data
+ * @param h_wire hash of merchant wire details
+ * @param coin_pub public key of deposited coin
+ * @param merchant_pub merchant public key
+ * @param[out] pending set to true if the transaction is still pending
+ * @param[out] wtid wire transfer identifier, only set if @a pending is false
+ * @param[out] exec_time when was the transaction done, or
+ * when we expect it to be done (if @a pending is false)
+ * @param[out] amount_with_fee set to the total deposited amount
+ * @param[out] deposit_fee set to how much the exchange did charge for the deposit
+ * @param[out] kyc set to the kyc status of the receiver (if @a pending)
+ * @param[out] aml_decision set to the AML status of the receiver
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_transfer_by_deposit (
+ void *cls,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ bool *pending,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_TIME_Timestamp *exec_time,
+ struct TALER_Amount *amount_with_fee,
+ struct TALER_Amount *deposit_fee,
+ struct TALER_EXCHANGEDB_KycStatus *kyc,
+ enum TALER_AmlDecisionState *aml_decision);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_wire_fee_by_time.c b/src/exchangedb/pg_lookup_wire_fee_by_time.c
new file mode 100644
index 000000000..775232a48
--- /dev/null
+++ b/src/exchangedb/pg_lookup_wire_fee_by_time.c
@@ -0,0 +1,156 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_wire_fee_by_time.c
+ * @brief Implementation of the lookup_wire_fee_by_time function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_wire_fee_by_time.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #wire_fee_by_time_helper()
+ */
+struct WireFeeLookupContext
+{
+
+ /**
+ * Set to the wire fees. Set to invalid if fees conflict over
+ * the given time period.
+ */
+ struct TALER_WireFeeSet *fees;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_lookup_wire_fee_by_time().
+ * Calls the callback with the wire fee structure.
+ *
+ * @param cls a `struct WireFeeLookupContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+wire_fee_by_time_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct WireFeeLookupContext *wlc = cls;
+ struct PostgresClosure *pg = wlc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_WireFeeSet fs;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
+ &fs.wire),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
+ &fs.closing),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ /* invalidate */
+ memset (wlc->fees,
+ 0,
+ sizeof (struct TALER_WireFeeSet));
+ return;
+ }
+ if (0 == i)
+ {
+ *wlc->fees = fs;
+ continue;
+ }
+ if (0 !=
+ TALER_wire_fee_set_cmp (&fs,
+ wlc->fees))
+ {
+ /* invalidate */
+ memset (wlc->fees,
+ 0,
+ sizeof (struct TALER_WireFeeSet));
+ return;
+ }
+ }
+}
+
+
+/**
+ * Lookup information about known wire fees. Finds all applicable
+ * fees in the given range. If they are identical, returns the
+ * respective @a fees. If any of the fees
+ * differ between @a start_time and @a end_time, the transaction
+ * succeeds BUT returns an invalid amount for both fees.
+ *
+ * @param cls closure
+ * @param wire_method the wire method to lookup fees for
+ * @param start_time starting time of fee
+ * @param end_time end time of fee
+ * @param[out] fees wire fees for that time period; if
+ * different fees exists within this time
+ * period, an 'invalid' amount is returned.
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_fee_by_time (
+ void *cls,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ struct TALER_WireFeeSet *fees)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (wire_method),
+ GNUNET_PQ_query_param_timestamp (&start_time),
+ GNUNET_PQ_query_param_timestamp (&end_time),
+ GNUNET_PQ_query_param_end
+ };
+ struct WireFeeLookupContext wlc = {
+ .fees = fees,
+ .pg = pg
+ };
+ /* used in #postgres_lookup_wire_fee_by_time() */
+ PREPARE (pg,
+ "lookup_wire_fee_by_time",
+ "SELECT"
+ " wire_fee"
+ ",closing_fee"
+ " FROM wire_fee"
+ " WHERE wire_method=$1"
+ " AND end_date > $2"
+ " AND start_date < $3;");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_wire_fee_by_time",
+ params,
+ &wire_fee_by_time_helper,
+ &wlc);
+}
diff --git a/src/exchangedb/pg_lookup_wire_fee_by_time.h b/src/exchangedb/pg_lookup_wire_fee_by_time.h
new file mode 100644
index 000000000..cbfc36e76
--- /dev/null
+++ b/src/exchangedb/pg_lookup_wire_fee_by_time.h
@@ -0,0 +1,76 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_wire_fee_by_time.h
+ * @brief implementation of the lookup_wire_fee_by_time function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_WIRE_FEE_BY_TIME_H
+#define PG_LOOKUP_WIRE_FEE_BY_TIME_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup information about known wire fees. Finds all applicable
+ * fees in the given range. If they are identical, returns the
+ * respective @a fees. If any of the fees
+ * differ between @a start_time and @a end_time, the transaction
+ * succeeds BUT returns an invalid amount for both fees.
+ *
+ * @param cls closure
+ * @param wire_method the wire method to lookup fees for
+ * @param start_time starting time of fee
+ * @param end_time end time of fee
+ * @param[out] fees wire fees for that time period; if
+ * different fees exists within this time
+ * period, an 'invalid' amount is returned.
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_fee_by_time (
+ void *cls,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ struct TALER_WireFeeSet *fees);
+
+/**
+ * Lookup information about known wire fees. Finds all applicable
+ * fees in the given range. If they are identical, returns the
+ * respective @a fees. If any of the fees
+ * differ between @a start_time and @a end_time, the transaction
+ * succeeds BUT returns an invalid amount for both fees.
+ *
+ * @param cls closure
+ * @param wire_method the wire method to lookup fees for
+ * @param start_time starting time of fee
+ * @param end_time end time of fee
+ * @param[out] fees wire fees for that time period; if
+ * different fees exists within this time
+ * period, an 'invalid' amount is returned.
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_fee_by_time (
+ void *cls,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ struct TALER_WireFeeSet *fees);
+#endif
diff --git a/src/exchangedb/pg_lookup_wire_timestamp.c b/src/exchangedb/pg_lookup_wire_timestamp.c
new file mode 100644
index 000000000..17dffc706
--- /dev/null
+++ b/src/exchangedb/pg_lookup_wire_timestamp.c
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_wire_timestamp.c
+ * @brief Implementation of the lookup_wire_timestamp function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_wire_timestamp.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_timestamp (void *cls,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp *last_date)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("last_change",
+ last_date),
+ GNUNET_PQ_result_spec_end
+ };
+
+
+ PREPARE (pg,
+ "lookup_wire_timestamp",
+ "SELECT"
+ " last_change"
+ " FROM wire_accounts"
+ " WHERE payto_uri=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_wire_timestamp",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_wire_timestamp.h b/src/exchangedb/pg_lookup_wire_timestamp.h
new file mode 100644
index 000000000..f2ee117de
--- /dev/null
+++ b/src/exchangedb/pg_lookup_wire_timestamp.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_wire_timestamp.h
+ * @brief implementation of the lookup_wire_timestamp function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_WIRE_TIMESTAMP_H
+#define PG_LOOKUP_WIRE_TIMESTAMP_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Check the last date an exchange wire account was modified.
+ *
+ * @param cls closure
+ * @param payto_uri key to look up information for
+ * @param[out] last_date last modification date to auditor status
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_timestamp (void *cls,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp *last_date);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_wire_transfer.c b/src/exchangedb/pg_lookup_wire_transfer.c
new file mode 100644
index 000000000..7ab023fe7
--- /dev/null
+++ b/src/exchangedb/pg_lookup_wire_transfer.c
@@ -0,0 +1,188 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_lookup_wire_transfer.c
+ * @brief Implementation of the lookup_wire_transfer function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_wire_transfer.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #handle_wt_result.
+ */
+struct WireTransferResultContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_AggregationDataCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on serious errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results. Helper function
+ * for #TEH_PG_lookup_wire_transfer().
+ *
+ * @param cls closure of type `struct WireTransferResultContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+handle_wt_result (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct WireTransferResultContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_PaytoHashP h_payto;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_TIME_Timestamp exec_time;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount deposit_fee;
+ struct TALER_DenominationPublicKey denom_pub;
+ char *payto_uri;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("aggregation_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_auto_from_type ("wire_target_h_payto",
+ &h_payto),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &merchant_pub),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &exec_time),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &deposit_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ rowid,
+ &merchant_pub,
+ payto_uri,
+ &h_payto,
+ exec_time,
+ &h_contract_terms,
+ &denom_pub,
+ &coin_pub,
+ &amount_with_fee,
+ &deposit_fee);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_transfer (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_EXCHANGEDB_AggregationDataCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_end
+ };
+ struct WireTransferResultContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "lookup_transactions",
+ "SELECT"
+ " aggregation_serial_id"
+ ",bdep.h_contract_terms"
+ ",payto_uri"
+ ",wire_targets.wire_target_h_payto"
+ ",kc.coin_pub"
+ ",bdep.merchant_pub"
+ ",wire_out.execution_date"
+ ",cdep.amount_with_fee"
+ ",denom.fee_deposit"
+ ",denom.denom_pub"
+ " FROM aggregation_tracking"
+ " JOIN batch_deposits bdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN coin_deposits cdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN wire_targets"
+ " USING (wire_target_h_payto)"
+ " JOIN known_coins kc"
+ " USING (coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " JOIN wire_out"
+ " USING (wtid_raw)"
+ " WHERE wtid_raw=$1;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_transactions",
+ params,
+ &handle_wt_result,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_lookup_wire_transfer.h b/src/exchangedb/pg_lookup_wire_transfer.h
new file mode 100644
index 000000000..ae5355f40
--- /dev/null
+++ b/src/exchangedb/pg_lookup_wire_transfer.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_lookup_wire_transfer.h
+ * @brief implementation of the lookup_wire_transfer function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_WIRE_TRANSFER_H
+#define PG_LOOKUP_WIRE_TRANSFER_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Lookup the list of Taler transactions that were aggregated
+ * into a wire transfer by the respective @a wtid.
+ *
+ * @param cls closure
+ * @param wtid the raw wire transfer identifier we used
+ * @param cb function to call on each transaction found
+ * @param cb_cls closure for @a cb
+ * @return query status of the transaction
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_transfer (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_EXCHANGEDB_AggregationDataCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_persist_policy_details.c b/src/exchangedb/pg_persist_policy_details.c
new file mode 100644
index 000000000..d97b92eac
--- /dev/null
+++ b/src/exchangedb/pg_persist_policy_details.c
@@ -0,0 +1,78 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_persist_policy_details.c
+ * @brief Implementation of the persist_policy_details function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_persist_policy_details.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_persist_policy_details (
+ void *cls,
+ const struct TALER_PolicyDetails *details,
+ uint64_t *policy_details_serial_id,
+ struct TALER_Amount *accumulated_total,
+ enum TALER_PolicyFulfillmentState *fulfillment_state)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&details->hash_code),
+ TALER_PQ_query_param_json (details->policy_json),
+ GNUNET_PQ_query_param_timestamp (&details->deadline),
+ TALER_PQ_query_param_amount (pg->conn,
+ &details->commitment),
+ TALER_PQ_query_param_amount (pg->conn,
+ &details->accumulated_total),
+ TALER_PQ_query_param_amount (pg->conn,
+ &details->policy_fee),
+ TALER_PQ_query_param_amount (pg->conn,
+ &details->transferable_amount),
+ GNUNET_PQ_query_param_auto_from_type (&details->fulfillment_state),
+ (details->no_policy_fulfillment_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (&details->policy_fulfillment_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("policy_details_serial_id",
+ policy_details_serial_id),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("accumulated_total",
+ accumulated_total),
+ GNUNET_PQ_result_spec_uint32 ("fulfillment_state",
+ fulfillment_state),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "call_insert_or_update_policy_details",
+ "SELECT"
+ " out_policy_details_serial_id AS policy_details_serial_id"
+ ",out_accumulated_total AS accumulated_total"
+ ",out_fulfillment_state AS fulfillment_state"
+ " FROM exchange_do_insert_or_update_policy_details"
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_insert_or_update_policy_details",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_persist_policy_details.h b/src/exchangedb/pg_persist_policy_details.h
new file mode 100644
index 000000000..4fe709d92
--- /dev/null
+++ b/src/exchangedb/pg_persist_policy_details.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_persist_policy_details.h
+ * @brief implementation of the persist_policy_details function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_PERSIST_POLICY_DETAILS_H
+#define PG_PERSIST_POLICY_DETAILS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/* Persist the details to a policy in the policy_details table. If there
+ * already exists a policy, update the fields accordingly.
+ *
+ * @param details The policy details that should be persisted. If an entry for
+ * the given details->hash_code exists, the values will be updated.
+ * @param[out] policy_details_serial_id The row ID of the policy details
+ * @param[out] accumulated_total The total amount accumulated in that policy
+ * @param[out] fulfillment_state The state of policy. If the state was Insufficient prior to the call and the provided deposit raises the accumulated_total above the commitment, it will be set to Ready.
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_persist_policy_details (
+ void *cls,
+ const struct TALER_PolicyDetails *details,
+ uint64_t *policy_details_serial_id,
+ struct TALER_Amount *accumulated_total,
+ enum TALER_PolicyFulfillmentState *fulfillment_state);
+
+#endif
diff --git a/src/exchangedb/pg_preflight.c b/src/exchangedb/pg_preflight.c
new file mode 100644
index 000000000..4533c9a97
--- /dev/null
+++ b/src/exchangedb/pg_preflight.c
@@ -0,0 +1,69 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_preflight.c
+ * @brief Implementation of the preflight function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_preflight.h"
+#include "pg_helper.h"
+
+
+/**
+ * Connect to the database if the connection does not exist yet.
+ *
+ * @param pg the plugin-specific state
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_internal_setup (struct PostgresClosure *pg);
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_preflight (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("ROLLBACK"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ if (GNUNET_OK !=
+ TEH_PG_internal_setup (pg))
+ return GNUNET_SYSERR;
+ if (NULL == pg->transaction_name)
+ return GNUNET_OK; /* all good */
+ if (GNUNET_OK ==
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "BUG: Preflight check rolled back transaction `%s'!\n",
+ pg->transaction_name);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "BUG: Preflight check failed to rollback transaction `%s'!\n",
+ pg->transaction_name);
+ }
+ pg->transaction_name = NULL;
+ return GNUNET_NO;
+}
diff --git a/src/exchangedb/pg_preflight.h b/src/exchangedb/pg_preflight.h
new file mode 100644
index 000000000..ba994f1e6
--- /dev/null
+++ b/src/exchangedb/pg_preflight.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_preflight.h
+ * @brief implementation of the preflight function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_PREFLIGTH_H
+#define PG_PREFLIGTH_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Do a pre-flight check that we are not in an uncommitted transaction.
+ * If we are, try to commit the previous transaction and output a warning.
+ * Does not return anything, as we will continue regardless of the outcome.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return #GNUNET_OK if everything is fine
+ * #GNUNET_NO if a transaction was rolled back
+ * #GNUNET_SYSERR on hard errors
+ */
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_preflight (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_profit_drains_get_pending.c b/src/exchangedb/pg_profit_drains_get_pending.c
new file mode 100644
index 000000000..c844a3f38
--- /dev/null
+++ b/src/exchangedb/pg_profit_drains_get_pending.c
@@ -0,0 +1,78 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_profit_drains_get_pending.c
+ * @brief Implementation of the profit_drains_get_pending function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_profit_drains_get_pending.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_profit_drains_get_pending (
+ void *cls,
+ uint64_t *serial,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ char **account_section,
+ char **payto_uri,
+ struct GNUNET_TIME_Timestamp *request_timestamp,
+ struct TALER_Amount *amount,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("profit_drain_serial_id",
+ serial),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid",
+ wtid),
+ GNUNET_PQ_result_spec_string ("account_section",
+ account_section),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ payto_uri),
+ GNUNET_PQ_result_spec_timestamp ("trigger_date",
+ request_timestamp),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ amount),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_profit_drains_get_pending() */
+ PREPARE (pg,
+ "get_ready_profit_drain",
+ "SELECT"
+ " profit_drain_serial_id"
+ ",wtid"
+ ",account_section"
+ ",payto_uri"
+ ",trigger_date"
+ ",amount"
+ ",master_sig"
+ " FROM profit_drains"
+ " WHERE NOT executed"
+ " ORDER BY trigger_date ASC;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_ready_profit_drain",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_profit_drains_get_pending.h b/src/exchangedb/pg_profit_drains_get_pending.h
new file mode 100644
index 000000000..cd793a129
--- /dev/null
+++ b/src/exchangedb/pg_profit_drains_get_pending.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_profit_drains_get_pending.h
+ * @brief implementation of the profit_drains_get_pending function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_PROFIT_DRAINS_GET_PENDING_H
+#define PG_PROFIT_DRAINS_GET_PENDING_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Get profit drain operation ready to execute.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param[out] serial set to serial ID of the entry
+ * @param[out] wtid set set to wire transfer ID to use
+ * @param[out] account_section set to account to drain
+ * @param[out] payto_uri set to account to wire funds to
+ * @param[out] request_timestamp set to time of the signature
+ * @param[out] amount set to amount to wire
+ * @param[out] master_sig set to signature affirming the operation
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_profit_drains_get_pending (
+ void *cls,
+ uint64_t *serial,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ char **account_section,
+ char **payto_uri,
+ struct GNUNET_TIME_Timestamp *request_timestamp,
+ struct TALER_Amount *amount,
+ struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_profit_drains_set_finished.c b/src/exchangedb/pg_profit_drains_set_finished.c
new file mode 100644
index 000000000..f0de27945
--- /dev/null
+++ b/src/exchangedb/pg_profit_drains_set_finished.c
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_profit_drains_set_finished.c
+ * @brief Implementation of the profit_drains_set_finished function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_profit_drains_set_finished.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_profit_drains_set_finished (
+ void *cls,
+ uint64_t serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "drain_profit_set_finished",
+ "UPDATE profit_drains"
+ " SET"
+ " executed=TRUE"
+ " WHERE profit_drain_serial_id=$1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "drain_profit_set_finished",
+ params);
+}
diff --git a/src/exchangedb/pg_profit_drains_set_finished.h b/src/exchangedb/pg_profit_drains_set_finished.h
new file mode 100644
index 000000000..b0878b5b8
--- /dev/null
+++ b/src/exchangedb/pg_profit_drains_set_finished.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_profit_drains_set_finished.h
+ * @brief implementation of the profit_drains_set_finished function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_PROFIT_DRAINS_SET_FINISHED_H
+#define PG_PROFIT_DRAINS_SET_FINISHED_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Set profit drain operation to finished.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param serial serial ID of the entry to mark finished
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_profit_drains_set_finished (
+ void *cls,
+ uint64_t serial);
+
+#endif
diff --git a/src/exchangedb/pg_release_revolving_shard.c b/src/exchangedb/pg_release_revolving_shard.c
new file mode 100644
index 000000000..43e45c4bc
--- /dev/null
+++ b/src/exchangedb/pg_release_revolving_shard.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_release_revolving_shard.c
+ * @brief Implementation of the release_revolving_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_release_revolving_shard.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_release_revolving_shard (void *cls,
+ const char *job_name,
+ uint32_t start_row,
+ uint32_t end_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_uint32 (&start_row),
+ GNUNET_PQ_query_param_uint32 (&end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Releasing revolving shard %s %u-%u\n",
+ job_name,
+ (unsigned int) start_row,
+ (unsigned int) end_row);
+
+
+ PREPARE (pg,
+ "release_revolving_shard",
+ "UPDATE revolving_work_shards"
+ " SET active=FALSE"
+ " WHERE job_name=$1"
+ " AND start_row=$2"
+ " AND end_row=$3");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "release_revolving_shard",
+ params);
+}
diff --git a/src/exchangedb/pg_release_revolving_shard.h b/src/exchangedb/pg_release_revolving_shard.h
new file mode 100644
index 000000000..ea65ab605
--- /dev/null
+++ b/src/exchangedb/pg_release_revolving_shard.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_release_revolving_shard.h
+ * @brief implementation of the release_revolving_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RELEASE_REVOLVING_SHARD_H
+#define PG_RELEASE_REVOLVING_SHARD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called to release a revolving shard
+ * back into the work pool. Clears the
+ * "completed" flag.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to grab a word shard for
+ * @param start_row inclusive start row of the shard
+ * @param end_row exclusive end row of the shard
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_release_revolving_shard (void *cls,
+ const char *job_name,
+ uint32_t start_row,
+ uint32_t end_row);
+
+#endif
diff --git a/src/exchangedb/pg_reserves_get.c b/src/exchangedb/pg_reserves_get.c
new file mode 100644
index 000000000..cae4764a5
--- /dev/null
+++ b/src/exchangedb/pg_reserves_get.c
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_reserves_get.c
+ * @brief Implementation of the reserves_get function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_reserves_get.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_get (void *cls,
+ struct TALER_EXCHANGEDB_Reserve *reserve)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount ("current_balance",
+ pg->currency,
+ &reserve->balance),
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ &reserve->expiry),
+ GNUNET_PQ_result_spec_timestamp ("gc_date",
+ &reserve->gc),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_reserves_get() */
+ PREPARE (pg,
+ "reserves_get",
+ "SELECT"
+ " current_balance"
+ ",expiration_date"
+ ",gc_date"
+ " FROM reserves"
+ " WHERE reserve_pub=$1"
+ " LIMIT 1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "reserves_get",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_reserves_get.h b/src/exchangedb/pg_reserves_get.h
new file mode 100644
index 000000000..8a96d53ed
--- /dev/null
+++ b/src/exchangedb/pg_reserves_get.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_reserves_get.h
+ * @brief implementation of the reserves_get function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RESERVES_GET_H
+#define PG_RESERVES_GET_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Get the summary of a reserve.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param[in,out] reserve the reserve data. The public key of the reserve should be
+ * set in this structure; it is used to query the database. The balance
+ * and expiration are then filled accordingly.
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_get (void *cls,
+ struct TALER_EXCHANGEDB_Reserve *reserve);
+
+#endif
diff --git a/src/exchangedb/pg_reserves_get_origin.c b/src/exchangedb/pg_reserves_get_origin.c
new file mode 100644
index 000000000..55d3179d1
--- /dev/null
+++ b/src/exchangedb/pg_reserves_get_origin.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_reserves_get_origin.c
+ * @brief Implementation of the reserves_get_origin function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_reserves_get_origin.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_get_origin (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_PaytoHashP *h_payto)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("wire_source_h_payto",
+ h_payto),
+ GNUNET_PQ_result_spec_end
+ };
+
+
+ PREPARE (pg,
+ "get_h_wire_source_of_reserve",
+ "SELECT"
+ " wire_source_h_payto"
+ " FROM reserves_in"
+ " WHERE reserve_pub=$1");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_h_wire_source_of_reserve",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_reserves_get_origin.h b/src/exchangedb/pg_reserves_get_origin.h
new file mode 100644
index 000000000..22085d8f0
--- /dev/null
+++ b/src/exchangedb/pg_reserves_get_origin.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_reserves_get_origin.h
+ * @brief implementation of the reserves_get_origin function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RESERVES_GET_ORIGIN_H
+#define PG_RESERVES_GET_ORIGIN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Get the origin of funds of a reserve.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @param[out] h_payto set to hash of the wire source payto://-URI
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_get_origin (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_PaytoHashP *h_payto);
+
+#endif
diff --git a/src/exchangedb/pg_reserves_in_insert.c b/src/exchangedb/pg_reserves_in_insert.c
new file mode 100644
index 000000000..21734942a
--- /dev/null
+++ b/src/exchangedb/pg_reserves_in_insert.c
@@ -0,0 +1,373 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_reserves_in_insert.c
+ * @brief Implementation of the reserves_in_insert function for Postgres
+ * @author Christian Grothoff
+ * @author Joseph Xu
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_reserves_in_insert.h"
+#include "pg_helper.h"
+#include "pg_start.h"
+#include "pg_start_read_committed.h"
+#include "pg_commit.h"
+#include "pg_preflight.h"
+#include "pg_rollback.h"
+#include "pg_reserves_get.h"
+#include "pg_reserves_update.h"
+#include "pg_setup_wire_target.h"
+#include "pg_event_notify.h"
+
+
+/**
+ * Generate event notification for the reserve change.
+ *
+ * @param reserve_pub reserve to notfiy on
+ * @return string to pass to postgres for the notification
+ */
+static char *
+compute_notify_on_reserve (const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+ struct TALER_ReserveEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
+ .reserve_pub = *reserve_pub
+ };
+
+ return GNUNET_PQ_get_event_notify_channel (&rep.header);
+}
+
+
+/**
+ * Closure for our helper_cb()
+ */
+struct Context
+{
+ /**
+ * Array of reserve UUIDs to initialize.
+ */
+ uint64_t *reserve_uuids;
+
+ /**
+ * Array with entries set to 'true' for duplicate transactions.
+ */
+ bool *transaction_duplicates;
+
+ /**
+ * Array with entries set to 'true' for rows with conflicts.
+ */
+ bool *conflicts;
+
+ /**
+ * Set to #GNUNET_SYSERR on failures.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+ /**
+ * Single value (no array) set to true if we need
+ * to follow-up with an update.
+ */
+ bool needs_update;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct Context *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct Context *ctx = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool (
+ "transaction_duplicate",
+ &ctx->transaction_duplicates[i]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("ruuid",
+ &ctx->reserve_uuids[i]),
+ &ctx->conflicts[i]),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ if (! ctx->transaction_duplicates[i])
+ ctx->needs_update |= ctx->conflicts[i];
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_in_insert (
+ void *cls,
+ const struct TALER_EXCHANGEDB_ReserveInInfo *reserves,
+ unsigned int reserves_length,
+ enum GNUNET_DB_QueryStatus *results)
+{
+ struct PostgresClosure *pg = cls;
+ unsigned int dups = 0;
+
+ struct TALER_PaytoHashP h_paytos[GNUNET_NZL (reserves_length)];
+ char *notify_s[GNUNET_NZL (reserves_length)];
+ struct TALER_ReservePublicKeyP reserve_pubs[GNUNET_NZL (reserves_length)];
+ struct TALER_Amount balances[GNUNET_NZL (reserves_length)];
+ struct GNUNET_TIME_Timestamp execution_times[GNUNET_NZL (reserves_length)];
+ const char *sender_account_details[GNUNET_NZL (reserves_length)];
+ const char *exchange_account_names[GNUNET_NZL (reserves_length)];
+ uint64_t wire_references[GNUNET_NZL (reserves_length)];
+ uint64_t reserve_uuids[GNUNET_NZL (reserves_length)];
+ bool transaction_duplicates[GNUNET_NZL (reserves_length)];
+ bool conflicts[GNUNET_NZL (reserves_length)];
+ struct GNUNET_TIME_Timestamp reserve_expiration
+ = GNUNET_TIME_relative_to_timestamp (pg->idle_reserve_expiration_time);
+ struct GNUNET_TIME_Timestamp gc
+ = GNUNET_TIME_relative_to_timestamp (pg->legal_reserve_expiration_time);
+ enum GNUNET_DB_QueryStatus qs;
+ bool need_update;
+
+ for (unsigned int i = 0; i<reserves_length; i++)
+ {
+ const struct TALER_EXCHANGEDB_ReserveInInfo *reserve = &reserves[i];
+
+ TALER_payto_hash (reserve->sender_account_details,
+ &h_paytos[i]);
+ notify_s[i] = compute_notify_on_reserve (reserve->reserve_pub);
+ reserve_pubs[i] = *reserve->reserve_pub;
+ balances[i] = *reserve->balance;
+ execution_times[i] = reserve->execution_time;
+ sender_account_details[i] = reserve->sender_account_details;
+ exchange_account_names[i] = reserve->exchange_account_name;
+ wire_references[i] = reserve->wire_reference;
+ }
+
+ /* NOTE: kind-of pointless to explicitly start a transaction here... */
+ if (GNUNET_OK !=
+ TEH_PG_preflight (pg))
+ {
+ GNUNET_break (0);
+ qs = GNUNET_DB_STATUS_HARD_ERROR;
+ goto finished;
+ }
+ if (GNUNET_OK !=
+ TEH_PG_start_read_committed (pg,
+ "READ_COMMITED"))
+ {
+ GNUNET_break (0);
+ qs = GNUNET_DB_STATUS_HARD_ERROR;
+ goto finished;
+ }
+ PREPARE (pg,
+ "reserves_insert_with_array",
+ "SELECT"
+ " transaction_duplicate"
+ ",ruuid"
+ " FROM exchange_do_array_reserves_insert"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);");
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&gc),
+ GNUNET_PQ_query_param_timestamp (&reserve_expiration),
+ GNUNET_PQ_query_param_array_auto_from_type (reserves_length,
+ reserve_pubs,
+ pg->conn),
+ GNUNET_PQ_query_param_array_uint64 (reserves_length,
+ wire_references,
+ pg->conn),
+ TALER_PQ_query_param_array_amount (
+ reserves_length,
+ balances,
+ pg->conn),
+ GNUNET_PQ_query_param_array_ptrs_string (
+ reserves_length,
+ (const char **) exchange_account_names,
+ pg->conn),
+ GNUNET_PQ_query_param_array_timestamp (
+ reserves_length,
+ execution_times,
+ pg->conn),
+ GNUNET_PQ_query_param_array_auto_from_type (
+ reserves_length,
+ h_paytos,
+ pg->conn),
+ GNUNET_PQ_query_param_array_ptrs_string (
+ reserves_length,
+ (const char **) sender_account_details,
+ pg->conn),
+ GNUNET_PQ_query_param_array_ptrs_string (
+ reserves_length,
+ (const char **) notify_s,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ struct Context ctx = {
+ .reserve_uuids = reserve_uuids,
+ .transaction_duplicates = transaction_duplicates,
+ .conflicts = conflicts,
+ .needs_update = false,
+ .status = GNUNET_OK
+ };
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "reserves_insert_with_array",
+ params,
+ &helper_cb,
+ &ctx);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ if ( (qs < 0) ||
+ (GNUNET_OK != ctx.status) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to insert into reserves (%d)\n",
+ qs);
+ goto finished;
+ }
+ need_update = ctx.needs_update;
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus cs;
+
+ cs = TEH_PG_commit (pg);
+ if (cs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to commit\n");
+ qs = cs;
+ goto finished;
+ }
+ }
+
+ for (unsigned int i = 0; i<reserves_length; i++)
+ {
+ if (transaction_duplicates[i])
+ dups++;
+ results[i] = transaction_duplicates[i]
+ ? GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+
+ if (! need_update)
+ {
+ qs = reserves_length;
+ goto finished;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Reserve update needed for some reserves in the batch\n");
+ PREPARE (pg,
+ "reserves_update",
+ "SELECT"
+ " out_duplicate AS duplicate "
+ "FROM exchange_do_batch_reserves_update"
+ " ($1,$2,$3,$4,$5,$6,$7);");
+
+ if (GNUNET_OK !=
+ TEH_PG_start (pg,
+ "reserve-insert-continued"))
+ {
+ GNUNET_break (0);
+ qs = GNUNET_DB_STATUS_HARD_ERROR;
+ goto finished;
+ }
+
+ for (unsigned int i = 0; i<reserves_length; i++)
+ {
+ if (transaction_duplicates[i])
+ continue;
+ if (! conflicts[i])
+ continue;
+ {
+ bool duplicate;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&reserve_pubs[i]),
+ GNUNET_PQ_query_param_timestamp (&reserve_expiration),
+ GNUNET_PQ_query_param_uint64 (&wire_references[i]),
+ TALER_PQ_query_param_amount (pg->conn,
+ &balances[i]),
+ GNUNET_PQ_query_param_string (exchange_account_names[i]),
+ GNUNET_PQ_query_param_auto_from_type (&h_paytos[i]),
+ GNUNET_PQ_query_param_string (notify_s[i]),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("duplicate",
+ &duplicate),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "reserves_update",
+ params,
+ rs);
+ if (qs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to update reserves (%d)\n",
+ qs);
+ results[i] = qs;
+ goto finished;
+ }
+ results[i] = duplicate
+ ? GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ }
+ {
+ enum GNUNET_DB_QueryStatus cs;
+
+ cs = TEH_PG_commit (pg);
+ if (cs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to commit\n");
+ qs = cs;
+ goto finished;
+ }
+ }
+finished:
+ for (unsigned int i = 0; i<reserves_length; i++)
+ GNUNET_free (notify_s[i]);
+ if (qs < 0)
+ return qs;
+ GNUNET_PQ_event_do_poll (pg->conn);
+ if (0 != dups)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "%u/%u duplicates among incoming transactions. Try increasing WIREWATCH_IDLE_SLEEP_INTERVAL in the [exchange] configuration section (if this happens a lot).\n",
+ dups,
+ reserves_length);
+ return qs;
+}
diff --git a/src/exchangedb/pg_reserves_in_insert.h b/src/exchangedb/pg_reserves_in_insert.h
new file mode 100644
index 000000000..938df3adb
--- /dev/null
+++ b/src/exchangedb/pg_reserves_in_insert.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_reserves_in_insert.h
+ * @brief implementation of the reserves_in_insert function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RESERVES_IN_INSERT_H
+#define PG_RESERVES_IN_INSERT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Insert an incoming transaction into reserves. New reserves are also
+ * created through this function. Runs its own transaction(s).
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserves array of reserves to insert
+ * @param reserves_length length of the @a reserves array
+ * @param[out] results set to query status per reserve, must be of length @a reserves_length
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_in_insert (
+ void *cls,
+ const struct TALER_EXCHANGEDB_ReserveInInfo *reserves,
+ unsigned int reserves_length,
+ enum GNUNET_DB_QueryStatus *results);
+
+
+#endif
diff --git a/src/exchangedb/pg_reserves_update.c b/src/exchangedb/pg_reserves_update.c
new file mode 100644
index 000000000..bfd32c6ce
--- /dev/null
+++ b/src/exchangedb/pg_reserves_update.c
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_reserves_update.c
+ * @brief Implementation of the reserves_update function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_reserves_update.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_update (void *cls,
+ const struct TALER_EXCHANGEDB_Reserve *reserve)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&reserve->expiry),
+ GNUNET_PQ_query_param_timestamp (&reserve->gc),
+ TALER_PQ_query_param_amount (pg->conn,
+ &reserve->balance),
+ GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "reserve_update",
+ "UPDATE reserves"
+ " SET"
+ " expiration_date=$1"
+ ",gc_date=$2"
+ ",current_balance=$3"
+ " WHERE reserve_pub=$4;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "reserve_update",
+ params);
+}
diff --git a/src/exchangedb/pg_reserves_update.h b/src/exchangedb/pg_reserves_update.h
new file mode 100644
index 000000000..24cf671da
--- /dev/null
+++ b/src/exchangedb/pg_reserves_update.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_reserves_update.h
+ * @brief implementation of the reserves_update function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RESERVES_UPDATE_H
+#define PG_RESERVES_UPDATE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Updates a reserve with the data from the given reserve structure.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve the reserve structure whose data will be used to update the
+ * corresponding record in the database.
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_update (void *cls,
+ const struct TALER_EXCHANGEDB_Reserve *reserve);
+
+#endif
diff --git a/src/exchangedb/pg_rollback.c b/src/exchangedb/pg_rollback.c
new file mode 100644
index 000000000..3610487f3
--- /dev/null
+++ b/src/exchangedb/pg_rollback.c
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_rollback.c
+ * @brief Implementation of the rollback function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_rollback.h"
+#include "pg_helper.h"
+
+
+void
+TEH_PG_rollback (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("ROLLBACK"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ if (NULL == pg->transaction_name)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Skipping rollback, no transaction active\n");
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Rolling back transaction\n");
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_PQ_exec_statements (pg->conn,
+ es));
+ pg->transaction_name = NULL;
+}
diff --git a/src/exchangedb/pg_rollback.h b/src/exchangedb/pg_rollback.h
new file mode 100644
index 000000000..ddb9e4111
--- /dev/null
+++ b/src/exchangedb/pg_rollback.h
@@ -0,0 +1,36 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_rollback.h
+ * @brief implementation of the rollback function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ROLLBACK_H
+#define PG_ROLLBACK_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Roll back the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ */
+void
+TEH_PG_rollback (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_account_merges_above_serial_id.c b/src/exchangedb/pg_select_account_merges_above_serial_id.c
new file mode 100644
index 000000000..6c3c81121
--- /dev/null
+++ b/src/exchangedb/pg_select_account_merges_above_serial_id.c
@@ -0,0 +1,192 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_account_merges_above_serial_id.c
+ * @brief Implementation of the select_account_merges_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_account_merges_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #account_merge_serial_helper_cb().
+ */
+struct AccountMergeSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_AccountMergeCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct AccountMergeSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+account_merge_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AccountMergeSerialContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct GNUNET_TIME_Timestamp purse_expiration;
+ struct TALER_Amount amount;
+ uint32_t min_age;
+ uint32_t flags32;
+ enum TALER_WalletAccountMergeFlags flags;
+ struct TALER_Amount purse_fee;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ struct TALER_ReserveSignatureP reserve_sig;
+ uint64_t rowid;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
+ &purse_fee),
+ GNUNET_PQ_result_spec_uint32 ("flags",
+ &flags32),
+ GNUNET_PQ_result_spec_uint32 ("age_limit",
+ &min_age),
+ GNUNET_PQ_result_spec_timestamp ("purse_expiration",
+ &purse_expiration),
+ GNUNET_PQ_result_spec_timestamp ("merge_timestamp",
+ &merge_timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_uint64 ("account_merge_request_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ flags = (enum TALER_WalletAccountMergeFlags) flags32;
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ &reserve_pub,
+ &purse_pub,
+ &h_contract_terms,
+ purse_expiration,
+ &amount,
+ min_age,
+ flags,
+ &purse_fee,
+ merge_timestamp,
+ &reserve_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_account_merges_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_AccountMergeCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct AccountMergeSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_account_merge_incr",
+ "SELECT"
+ " am.account_merge_request_serial_id"
+ ",am.reserve_pub"
+ ",am.purse_pub"
+ ",pr.h_contract_terms"
+ ",pr.purse_expiration"
+ ",pr.amount_with_fee"
+ ",pr.age_limit"
+ ",pr.flags"
+ ",pr.purse_fee"
+ ",pm.merge_timestamp"
+ ",am.reserve_sig"
+ " FROM account_merges am"
+ " JOIN purse_requests pr USING (purse_pub)"
+ " JOIN purse_merges pm USING (purse_pub)"
+ " WHERE ("
+ " (account_merge_request_serial_id>=$1)"
+ " )"
+ " ORDER BY account_merge_request_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_account_merge_incr",
+ params,
+ &account_merge_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_account_merges_above_serial_id.h b/src/exchangedb/pg_select_account_merges_above_serial_id.h
new file mode 100644
index 000000000..be3bd7124
--- /dev/null
+++ b/src/exchangedb/pg_select_account_merges_above_serial_id.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_account_merges_above_serial_id.h
+ * @brief implementation of the select_account_merges_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_ACCOUNT_MERGES_ABOVE_SERIAL_ID_H
+#define PG_SELECT_ACCOUNT_MERGES_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select account merges above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_account_merges_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_AccountMergeCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.c b/src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.c
new file mode 100644
index 000000000..0d5d0ee25
--- /dev/null
+++ b/src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.c
@@ -0,0 +1,154 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_aggregation_amounts_for_kyc_check.c
+ * @brief Implementation of the select_aggregation_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_aggregation_amounts_for_kyc_check.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_kyc_amounts_cb().
+ */
+struct KycAmountCheckContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_KycAmountCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct KycAmountCheckContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_kyc_amounts_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct KycAmountCheckContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct GNUNET_TIME_Absolute date;
+ struct TALER_Amount amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_absolute_time ("date",
+ &date),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = ctx->cb (ctx->cb_cls,
+ &amount,
+ date);
+ GNUNET_PQ_cleanup_result (rs);
+ switch (ret)
+ {
+ case GNUNET_OK:
+ continue;
+ case GNUNET_NO:
+ break;
+ case GNUNET_SYSERR:
+ ctx->status = GNUNET_SYSERR;
+ break;
+ }
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aggregation_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_absolute_time (&time_limit),
+ GNUNET_PQ_query_param_end
+ };
+ struct KycAmountCheckContext ctx = {
+ .cb = kac,
+ .cb_cls = kac_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "select_kyc_relevant_aggregation_events",
+ "SELECT"
+ " amount"
+ ",execution_date AS date"
+ " FROM wire_out"
+ " WHERE wire_target_h_payto=$1"
+ " AND execution_date >= $2"
+ " ORDER BY execution_date DESC");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_kyc_relevant_aggregation_events",
+ params,
+ &get_kyc_amounts_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.h b/src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.h
new file mode 100644
index 000000000..b91740581
--- /dev/null
+++ b/src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_aggregation_amounts_for_kyc_check.h
+ * @brief implementation of the select_aggregation_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AGGREGATION_AMOUNTS_FOR_KYC_CHECK_H
+#define PG_SELECT_AGGREGATION_AMOUNTS_FOR_KYC_CHECK_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Call @a kac on deposited amounts after @a time_limit which are relevant for a
+ * KYC trigger for a the (credited) account identified by @a h_payto.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aggregation_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_aggregation_transient.c b/src/exchangedb/pg_select_aggregation_transient.c
new file mode 100644
index 000000000..f9b6193ed
--- /dev/null
+++ b/src/exchangedb/pg_select_aggregation_transient.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_aggregation_transient.c
+ * @brief Implementation of the select_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_aggregation_transient.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const char *exchange_account_section,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_Amount *total)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_string (exchange_account_section),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ total),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
+ wtid),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_select_aggregation_transient() */
+ PREPARE (pg,
+ "select_aggregation_transient",
+ "SELECT"
+ " amount"
+ " ,wtid_raw"
+ " FROM aggregation_transient"
+ " WHERE wire_target_h_payto=$1"
+ " AND merchant_pub=$2"
+ " AND exchange_account_section=$3;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_aggregation_transient",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_select_aggregation_transient.h b/src/exchangedb/pg_select_aggregation_transient.h
new file mode 100644
index 000000000..fd82a97aa
--- /dev/null
+++ b/src/exchangedb/pg_select_aggregation_transient.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_aggregation_transient.h
+ * @brief implementation of the select_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AGGREGATION_TRANSIENT_H
+#define PG_SELECT_AGGREGATION_TRANSIENT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Find existing entry in the transient aggregation table.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto destination of the wire transfer
+ * @param merchant_pub public key of the merchant receiving the transfer
+ * @param exchange_account_section exchange account to use
+ * @param[out] wtid set to the raw wire transfer identifier to be used
+ * @param[out] total existing amount to be wired in the future
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const char *exchange_account_section,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_Amount *total);
+#endif
diff --git a/src/exchangedb/pg_select_aggregations_above_serial.c b/src/exchangedb/pg_select_aggregations_above_serial.c
new file mode 100644
index 000000000..52d202702
--- /dev/null
+++ b/src/exchangedb/pg_select_aggregations_above_serial.c
@@ -0,0 +1,137 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aggregations_above_serial.c
+ * @brief Implementation of the select_aggregations_above_serial function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_aggregations_above_serial.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #aggregation_serial_helper_cb().
+ */
+struct AggregationSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_AggregationCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct AggregationSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+aggregation_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AggregationSerialContext *dsc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t tracking_rowid;
+ uint64_t batch_deposit_serial_id;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("aggregation_serial_id",
+ &tracking_rowid),
+ GNUNET_PQ_result_spec_uint64 ("batch_deposit_serial_id",
+ &batch_deposit_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ dsc->cb (dsc->cb_cls,
+ tracking_rowid,
+ batch_deposit_serial_id);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aggregations_above_serial (
+ void *cls,
+ uint64_t min_tracking_serial_id,
+ TALER_EXCHANGEDB_AggregationCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&min_tracking_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct AggregationSerialContext asc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Fetch aggregations with rowid '\geq' the given parameter */
+ PREPARE (pg,
+ "select_aggregations_above_serial",
+ "SELECT"
+ " aggregation_serial_id"
+ ",batch_deposit_serial_id"
+ " FROM aggregation_tracking"
+ " WHERE aggregation_serial_id>=$1"
+ " ORDER BY aggregation_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_aggregations_above_serial",
+ params,
+ &aggregation_serial_helper_cb,
+ &asc);
+ if (GNUNET_OK != asc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_aggregations_above_serial.h b/src/exchangedb/pg_select_aggregations_above_serial.h
new file mode 100644
index 000000000..2883b19f2
--- /dev/null
+++ b/src/exchangedb/pg_select_aggregations_above_serial.h
@@ -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 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 exchangedb/pg_select_aggregations_above_serial.h
+ * @brief implementation of the select_aggregations_above_serial function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AGGREGATIONS_ABOVE_SERIAL_H
+#define PG_SELECT_AGGREGATIONS_ABOVE_SERIAL_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select all aggregation tracking IDs in the database
+ * above a given @a min_tracking_serial_id.
+ *
+ * @param cls closure
+ * @param min_tracking_serial_id only return entries strictly above this row (and in order)
+ * @param cb function to call on all such aggregations
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aggregations_above_serial (
+ void *cls,
+ uint64_t min_tracking_serial_id,
+ TALER_EXCHANGEDB_AggregationCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.c b/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.c
new file mode 100644
index 000000000..ba3307690
--- /dev/null
+++ b/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.c
@@ -0,0 +1,146 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_all_purse_decisions_above_serial_id.c
+ * @brief Implementation of the select_all_purse_decisions_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_all_purse_decisions_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #all_purse_decision_serial_helper_cb().
+ */
+struct AllPurseDecisionSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_AllPurseDecisionCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct PurseRefundSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+all_purse_decision_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AllPurseDecisionSerialContext *dsc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ bool refunded;
+ uint64_t rowid;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &purse_pub),
+ GNUNET_PQ_result_spec_bool ("refunded",
+ &refunded),
+ GNUNET_PQ_result_spec_uint64 ("purse_decision_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ &purse_pub,
+ refunded);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_all_purse_decisions_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_AllPurseDecisionCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct AllPurseDecisionSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_all_purse_decision_incr",
+ "SELECT"
+ " purse_pub"
+ ",refunded"
+ ",purse_decision_serial_id"
+ " FROM purse_decision"
+ " WHERE purse_decision_serial_id>=$1"
+ " ORDER BY purse_decision_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "audit_get_all_purse_decision_incr",
+ params,
+ &all_purse_decision_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.h b/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.h
new file mode 100644
index 000000000..634be4965
--- /dev/null
+++ b/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_all_purse_decisions_above_serial_id.h
+ * @brief implementation of the select_all_purse_decisions_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_ALL_PURSE_DECISIONS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_ALL_PURSE_DECISIONS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select purse decisions above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_all_purse_decisions_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_AllPurseDecisionCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_aml_history.c b/src/exchangedb/pg_select_aml_history.c
new file mode 100644
index 000000000..0461e0d9b
--- /dev/null
+++ b/src/exchangedb/pg_select_aml_history.c
@@ -0,0 +1,157 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_aml_history.c
+ * @brief Implementation of the select_aml_history function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_aml_history.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #handle_aml_result.
+ */
+struct AmlHistoryResultContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_AmlHistoryCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on serious errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results. Helper function
+ * for #TEH_PG_select_aml_history().
+ *
+ * @param cls closure of type `struct AmlHistoryResultContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+handle_aml_result (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AmlHistoryResultContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_Amount new_threshold;
+ uint32_t ns;
+ struct GNUNET_TIME_Timestamp decision_time;
+ char *justification;
+ struct TALER_AmlOfficerPublicKeyP decider_pub;
+ struct TALER_AmlOfficerSignatureP decider_sig;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("new_threshold",
+ &new_threshold),
+ GNUNET_PQ_result_spec_uint32 ("new_status",
+ &ns),
+ GNUNET_PQ_result_spec_timestamp ("decision_time",
+ &decision_time),
+ GNUNET_PQ_result_spec_string ("justification",
+ &justification),
+ GNUNET_PQ_result_spec_auto_from_type ("decider_pub",
+ &decider_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("decider_sig",
+ &decider_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &new_threshold,
+ (enum TALER_AmlDecisionState) ns,
+ decision_time,
+ justification,
+ &decider_pub,
+ &decider_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aml_history (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_AmlHistoryCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ struct AmlHistoryResultContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "lookup_aml_history",
+ "SELECT"
+ " new_threshold"
+ ",new_status"
+ ",decision_time"
+ ",justification"
+ ",decider_pub"
+ ",decider_sig"
+ " FROM aml_history"
+ " WHERE h_payto=$1;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_aml_history",
+ params,
+ &handle_aml_result,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_aml_history.h b/src/exchangedb/pg_select_aml_history.h
new file mode 100644
index 000000000..78569947f
--- /dev/null
+++ b/src/exchangedb/pg_select_aml_history.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_aml_history.h
+ * @brief implementation of the select_aml_history function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AML_HISTORY_H
+#define PG_SELECT_AML_HISTORY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup AML decision history for a particular account.
+ *
+ * @param cls closure
+ * @param h_payto which account should we return the AML decision history for
+ * @param cb callback to invoke on each match
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aml_history (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_AmlHistoryCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_aml_process.c b/src/exchangedb/pg_select_aml_process.c
new file mode 100644
index 000000000..c34cae4bb
--- /dev/null
+++ b/src/exchangedb/pg_select_aml_process.c
@@ -0,0 +1,170 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_aml_process.c
+ * @brief Implementation of the select_aml_process function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_aml_process.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #handle_aml_result.
+ */
+struct AmlProcessResultContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_AmlStatusCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on serious errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results. Helper function
+ * for #TEH_PG_select_aml_process().
+ *
+ * @param cls closure of type `struct AmlProcessResultContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+handle_aml_result (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AmlProcessResultContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_PaytoHashP h_payto;
+ struct TALER_Amount threshold;
+ uint64_t rowid;
+ uint32_t sv;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("aml_status_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_auto_from_type ("h_payto",
+ &h_payto),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("threshold",
+ &threshold),
+ GNUNET_PQ_result_spec_uint32 ("status",
+ &sv),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ rowid,
+ &h_payto,
+ &threshold,
+ (enum TALER_AmlDecisionState) sv);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aml_process (
+ void *cls,
+ enum TALER_AmlDecisionState decision,
+ uint64_t row_off,
+ uint64_t limit,
+ bool forward,
+ TALER_EXCHANGEDB_AmlStatusCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint32 (&decision),
+ GNUNET_PQ_query_param_uint64 (&row_off),
+ GNUNET_PQ_query_param_uint64 (&limit),
+ GNUNET_PQ_query_param_end
+ };
+ struct AmlProcessResultContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ const char *stmt = forward
+ ? "select_aml_process_inc"
+ : "select_aml_process_dec";
+
+ PREPARE (pg,
+ "select_aml_process_inc",
+ "SELECT"
+ " aml_status_serial_id"
+ ",h_payto"
+ ",threshold"
+ ",status"
+ " FROM aml_status"
+ " WHERE aml_status_serial_id > $2"
+ " AND status = $1"
+ " ORDER BY aml_status_serial_id ASC"
+ " LIMIT $3");
+ PREPARE (pg,
+ "select_aml_process_dec",
+ "SELECT"
+ " aml_status_serial_id"
+ ",h_payto"
+ ",threshold"
+ ",status"
+ " FROM aml_status"
+ " WHERE aml_status_serial_id < $2"
+ " AND status = $1"
+ " ORDER BY aml_status_serial_id DESC"
+ " LIMIT $3");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ stmt,
+ params,
+ &handle_aml_result,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_aml_process.h b/src/exchangedb/pg_select_aml_process.h
new file mode 100644
index 000000000..648cace2e
--- /dev/null
+++ b/src/exchangedb/pg_select_aml_process.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_aml_process.h
+ * @brief implementation of the select_aml_process function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AML_PROCESS_H
+#define PG_SELECT_AML_PROCESS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup AML decisions that have a particular state.
+ *
+ * @param cls closure
+ * @param decision which decision states to filter by
+ * @param row_off offset to start from
+ * @param limit how many rows to return at most
+ * @param forward true to go forward in time, false to go backwards
+ * @param cb callback to invoke on each match
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aml_process (
+ void *cls,
+ enum TALER_AmlDecisionState decision,
+ uint64_t row_off,
+ uint64_t limit,
+ bool forward,
+ TALER_EXCHANGEDB_AmlStatusCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_aml_threshold.c b/src/exchangedb/pg_select_aml_threshold.c
new file mode 100644
index 000000000..23286f029
--- /dev/null
+++ b/src/exchangedb/pg_select_aml_threshold.c
@@ -0,0 +1,70 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aml_threshold.c
+ * @brief Implementation of the select_aml_threshold function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_aml_threshold.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aml_threshold (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_AmlDecisionState *decision,
+ struct TALER_EXCHANGEDB_KycStatus *kyc,
+ struct TALER_Amount *threshold)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ uint32_t status32 = TALER_AML_NORMAL;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("threshold",
+ threshold),
+ GNUNET_PQ_result_spec_uint32 ("status",
+ &status32),
+ GNUNET_PQ_result_spec_uint64 ("kyc_requirement",
+ &kyc->requirement_row),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "select_aml_threshold",
+ "SELECT"
+ " threshold"
+ ",status"
+ ",kyc_requirement"
+ " FROM aml_status"
+ " WHERE h_payto=$1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_aml_threshold",
+ params,
+ rs);
+ *decision = (enum TALER_AmlDecisionState) status32;
+ kyc->ok = (TALER_AML_FROZEN != *decision)
+ || (0 != kyc->requirement_row);
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_aml_threshold.h b/src/exchangedb/pg_select_aml_threshold.h
new file mode 100644
index 000000000..8f0e3bcfc
--- /dev/null
+++ b/src/exchangedb/pg_select_aml_threshold.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aml_threshold.h
+ * @brief implementation of the select_aml_threshold function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AML_THRESHOLD_H
+#define PG_SELECT_AML_THRESHOLD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Obtain the current AML threshold set for an account.
+ *
+ * @param cls closure
+ * @param h_payto account for which the AML threshold is stored
+ * @param[out] decision set to current AML decision
+ * @param[out] kyc set to KYC requirements imposed by AML, if any
+ * @param[out] threshold set to the existing threshold
+ * @return database transaction status, 0 if no threshold was set
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aml_threshold (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_AmlDecisionState *decision,
+ struct TALER_EXCHANGEDB_KycStatus *kyc,
+ struct TALER_Amount *threshold);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_auditor_denom_sig.c b/src/exchangedb/pg_select_auditor_denom_sig.c
new file mode 100644
index 000000000..1dd6bd3d1
--- /dev/null
+++ b/src/exchangedb/pg_select_auditor_denom_sig.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_auditor_denom_sig.c
+ * @brief Implementation of the select_auditor_denom_sig function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_auditor_denom_sig.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_auditor_denom_sig (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct TALER_AuditorSignatureP *auditor_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (auditor_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("auditor_sig",
+ auditor_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "select_auditor_denom_sig",
+ "SELECT"
+ " auditor_sig"
+ " FROM auditor_denom_sigs"
+ " WHERE auditor_uuid="
+ " (SELECT auditor_uuid"
+ " FROM auditors"
+ " WHERE auditor_pub=$1)"
+ " AND denominations_serial="
+ " (SELECT denominations_serial"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$2);");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_auditor_denom_sig",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_select_auditor_denom_sig.h b/src/exchangedb/pg_select_auditor_denom_sig.h
new file mode 100644
index 000000000..0f635cf42
--- /dev/null
+++ b/src/exchangedb/pg_select_auditor_denom_sig.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_auditor_denom_sig.h
+ * @brief implementation of the select_auditor_denom_sig function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AUDITOR_DENOM_SIG_H
+#define PG_SELECT_AUDITOR_DENOM_SIG_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Select information about an auditor auditing a denomination key.
+ *
+ * @param cls closure
+ * @param h_denom_pub the audited denomination
+ * @param auditor_pub the auditor's key
+ * @param[out] auditor_sig set to signature affirming the auditor's audit activity
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_auditor_denom_sig (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct TALER_AuditorSignatureP *auditor_sig);
+
+#endif
diff --git a/src/exchangedb/pg_select_batch_deposits_missing_wire.c b/src/exchangedb/pg_select_batch_deposits_missing_wire.c
new file mode 100644
index 000000000..8f966326a
--- /dev/null
+++ b/src/exchangedb/pg_select_batch_deposits_missing_wire.c
@@ -0,0 +1,144 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_select_batch_deposits_missing_wire.c
+ * @brief Implementation of the select_batch_deposits_missing_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_batch_deposits_missing_wire.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #missing_wire_cb().
+ */
+struct MissingWireContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_WireMissingCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on error.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct MissingWireContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+missing_wire_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct MissingWireContext *mwc = cls;
+ struct PostgresClosure *pg = mwc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t batch_deposit_serial_id;
+ struct GNUNET_TIME_Timestamp deadline;
+ struct TALER_PaytoHashP wire_target_h_payto;
+ struct TALER_Amount total_amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("batch_deposit_serial_id",
+ &batch_deposit_serial_id),
+ GNUNET_PQ_result_spec_auto_from_type ("wire_target_h_payto",
+ &wire_target_h_payto),
+ GNUNET_PQ_result_spec_timestamp ("deadline",
+ &deadline),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("total_amount",
+ &total_amount),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ mwc->status = GNUNET_SYSERR;
+ return;
+ }
+ mwc->cb (mwc->cb_cls,
+ batch_deposit_serial_id,
+ &total_amount,
+ &wire_target_h_payto,
+ deadline);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_batch_deposits_missing_wire (
+ void *cls,
+ uint64_t min_batch_deposit_serial_id,
+ TALER_EXCHANGEDB_WireMissingCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&min_batch_deposit_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct MissingWireContext mwc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "deposits_get_deposits_missing_wire",
+ "SELECT"
+ " batch_deposit_serial_id"
+ ",wire_target_h_payto"
+ ",deadline"
+ ",total_amount"
+ " FROM exchange_do_select_deposits_missing_wire"
+ " ($1);");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "deposits_get_deposits_missing_wire",
+ params,
+ &missing_wire_cb,
+ &mwc);
+ if (GNUNET_OK != mwc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_batch_deposits_missing_wire.h b/src/exchangedb/pg_select_batch_deposits_missing_wire.h
new file mode 100644
index 000000000..16f1d0cb3
--- /dev/null
+++ b/src/exchangedb/pg_select_batch_deposits_missing_wire.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_select_batch_deposits_missing_wire.h
+ * @brief implementation of the select_batch_deposits_missing_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_DEPOSITS_MISSING_WIRE_H
+#define PG_SELECT_DEPOSITS_MISSING_WIRE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Select all of those batch deposits in the database
+ * above the given serial ID.
+ *
+ * @param cls closure
+ * @param min_batch_deposit_serial_id select all batch deposits above this ID
+ * @param cb function to call on all such deposits
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_batch_deposits_missing_wire (
+ void *cls,
+ uint64_t min_batch_deposit_serial_id,
+ TALER_EXCHANGEDB_WireMissingCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_coin_deposits_above_serial_id.c b/src/exchangedb/pg_select_coin_deposits_above_serial_id.c
new file mode 100644
index 000000000..000b908ed
--- /dev/null
+++ b/src/exchangedb/pg_select_coin_deposits_above_serial_id.c
@@ -0,0 +1,204 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_select_coin_deposits_above_serial_id.c
+ * @brief Implementation of the select_coin_deposits_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_coin_deposits_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #deposit_serial_helper_cb().
+ */
+struct CoinDepositSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_DepositCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinDepositSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+coin_deposit_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinDepositSerialContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_Deposit deposit;
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+ struct TALER_DenominationPublicKey denom_pub;
+ bool done;
+ uint64_t rowid;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &deposit.amount_with_fee),
+ GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
+ &deposit.timestamp),
+ GNUNET_PQ_result_spec_timestamp ("exchange_timestamp",
+ &exchange_timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &deposit.merchant_pub),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &deposit.coin.coin_pub),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &deposit.coin.h_age_commitment),
+ &deposit.coin.no_age_commitment),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("wallet_data_hash",
+ &deposit.wallet_data_hash),
+ &deposit.no_wallet_data_hash),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &deposit.csig),
+ GNUNET_PQ_result_spec_timestamp ("refund_deadline",
+ &deposit.refund_deadline),
+ GNUNET_PQ_result_spec_timestamp ("wire_deadline",
+ &deposit.wire_deadline),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &deposit.h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+ &deposit.wire_salt),
+ GNUNET_PQ_result_spec_string ("receiver_wire_account",
+ &deposit.receiver_wire_account),
+ GNUNET_PQ_result_spec_bool ("done",
+ &done),
+ GNUNET_PQ_result_spec_uint64 ("coin_deposit_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ memset (&deposit,
+ 0,
+ sizeof (deposit));
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ exchange_timestamp,
+ &deposit,
+ &denom_pub,
+ done);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_coin_deposits_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_DepositCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct CoinDepositSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Fetch deposits with rowid '\geq' the given parameter */
+ PREPARE (pg,
+ "audit_get_coin_deposits_incr",
+ "SELECT"
+ " cdep.amount_with_fee"
+ ",bdep.wallet_timestamp"
+ ",bdep.exchange_timestamp"
+ ",bdep.merchant_pub"
+ ",bdep.wallet_data_hash"
+ ",denom.denom_pub"
+ ",kc.coin_pub"
+ ",kc.age_commitment_hash"
+ ",cdep.coin_sig"
+ ",bdep.refund_deadline"
+ ",bdep.wire_deadline"
+ ",bdep.h_contract_terms"
+ ",bdep.wire_salt"
+ ",wt.payto_uri AS receiver_wire_account"
+ ",bdep.done"
+ ",cdep.coin_deposit_serial_id"
+ " FROM coin_deposits cdep"
+ " JOIN batch_deposits bdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN wire_targets wt"
+ " USING (wire_target_h_payto)"
+ " JOIN known_coins kc"
+ " USING (coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE (coin_deposit_serial_id>=$1)"
+ " ORDER BY coin_deposit_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_coin_deposits_incr",
+ params,
+ &coin_deposit_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_coin_deposits_above_serial_id.h b/src/exchangedb/pg_select_coin_deposits_above_serial_id.h
new file mode 100644
index 000000000..5202336a4
--- /dev/null
+++ b/src/exchangedb/pg_select_coin_deposits_above_serial_id.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_coin_deposits_above_serial_id.h
+ * @brief implementation of the select_coin_deposits_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_DEPOSITS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_DEPOSITS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Select deposits above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_coin_deposits_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_DepositCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_contract.c b/src/exchangedb/pg_select_contract.c
new file mode 100644
index 000000000..3e6bf22bc
--- /dev/null
+++ b/src/exchangedb/pg_select_contract.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_contract.c
+ * @brief Implementation of the select_contract function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_contract.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_contract (void *cls,
+ const struct TALER_ContractDiffiePublicP *pub_ckey,
+ struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseContractSignatureP *econtract_sig,
+ size_t *econtract_size,
+ void **econtract)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (pub_ckey),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("contract_sig",
+ econtract_sig),
+ GNUNET_PQ_result_spec_variable_size ("e_contract",
+ econtract,
+ econtract_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ /* Used in #postgres_select_contract */
+ PREPARE (pg,
+ "select_contract",
+ "SELECT "
+ " purse_pub"
+ ",e_contract"
+ ",contract_sig"
+ " FROM contracts"
+ " WHERE pub_ckey=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_contract",
+ params,
+ rs);
+
+}
diff --git a/src/exchangedb/pg_select_contract.h b/src/exchangedb/pg_select_contract.h
new file mode 100644
index 000000000..747a82753
--- /dev/null
+++ b/src/exchangedb/pg_select_contract.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_contract.h
+ * @brief implementation of the select_contract function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_CONTRACT_H
+#define PG_SELECT_CONTRACT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to retrieve an encrypted contract.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub key to lookup the contract by
+ * @param[out] pub_ckey set to the ephemeral DH used to encrypt the contract
+ * @param[out] econtract_sig set to the signature over the encrypted contract
+ * @param[out] econtract_size set to the number of bytes in @a econtract
+ * @param[out] econtract set to the encrypted contract on success, to be freed by the caller
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_contract (void *cls,
+ const struct TALER_ContractDiffiePublicP *pub_ckey,
+ struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseContractSignatureP *econtract_sig,
+ size_t *econtract_size,
+ void **econtract);
+
+#endif
diff --git a/src/exchangedb/pg_select_contract_by_purse.c b/src/exchangedb/pg_select_contract_by_purse.c
new file mode 100644
index 000000000..8d29b3954
--- /dev/null
+++ b/src/exchangedb/pg_select_contract_by_purse.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_contract_by_purse.c
+ * @brief Implementation of the select_contract_by_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_contract_by_purse.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_contract_by_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_EncryptedContract *econtract)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("pub_ckey",
+ &econtract->contract_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("contract_sig",
+ &econtract->econtract_sig),
+ GNUNET_PQ_result_spec_variable_size ("e_contract",
+ &econtract->econtract,
+ &econtract->econtract_size),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_select_contract_by_purse */
+ PREPARE (pg,
+ "select_contract_by_purse",
+ "SELECT "
+ " pub_ckey"
+ ",e_contract"
+ ",contract_sig"
+ " FROM contracts"
+ " WHERE purse_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_contract_by_purse",
+ params,
+ rs);
+
+}
diff --git a/src/exchangedb/pg_select_contract_by_purse.h b/src/exchangedb/pg_select_contract_by_purse.h
new file mode 100644
index 000000000..0e33a6ba1
--- /dev/null
+++ b/src/exchangedb/pg_select_contract_by_purse.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_contract_by_purse.h
+ * @brief implementation of the select_contract_by_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_CONTRACT_BY_PURSE_H
+#define PG_SELECT_CONTRACT_BY_PURSE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to retrieve an encrypted contract.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub key to lookup the contract by
+ * @param[out] econtract set to the encrypted contract on success, to be freed by the caller
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_contract_by_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_EncryptedContract *econtract);
+#endif
diff --git a/src/exchangedb/pg_select_justification_for_missing_wire.c b/src/exchangedb/pg_select_justification_for_missing_wire.c
new file mode 100644
index 000000000..77d5b4de7
--- /dev/null
+++ b/src/exchangedb/pg_select_justification_for_missing_wire.c
@@ -0,0 +1,89 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_select_batch_deposits_missing_wire.c
+ * @brief Implementation of the select_batch_deposits_missing_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_batch_deposits_missing_wire.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_justification_for_missing_wire (
+ void *cls,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ char **payto_uri,
+ char **kyc_pending,
+ enum TALER_AmlDecisionState *status,
+ struct TALER_Amount *aml_limit)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now
+ = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (wire_target_h_payto),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ uint32_t aml_status32;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ payto_uri),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("kyc_pending",
+ kyc_pending),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint32 ("aml_status",
+ &aml_status32),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_RESULT_SPEC_AMOUNT ("aml_limit",
+ aml_limit),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "deposits_get_overdue",
+ "SELECT"
+ " out_payto_uri AS payto_uri"
+ ",out_kyc_pending AS kyc_pending"
+ ",out_deadline AS deadline"
+ ",out_aml_status AS aml_status"
+ ",out_aml_limit AS aml_limit"
+ " FROM exchange_do_select_justification_missing_wire"
+ " ($1, $2);");
+ memset (aml_limit,
+ 0,
+ sizeof (*aml_limit));
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "",
+ params,
+ rs);
+ if (qs <= 0)
+ return qs;
+ *status = (enum TALER_AmlDecisionState) aml_status32;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_justification_for_missing_wire.h b/src/exchangedb/pg_select_justification_for_missing_wire.h
new file mode 100644
index 000000000..7f73eb511
--- /dev/null
+++ b/src/exchangedb/pg_select_justification_for_missing_wire.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_justification_for_missing_wire.h
+ * @brief implementation of the select_justification_for_missing_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_JUSTIFICATION_FOR_MISSING_WIRE_H
+#define PG_SELECT_JUSTIFICATION_FOR_MISSING_WIRE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select all of those justifications for why we might not have
+ * done a wire transfer from in the database for a particular target account.
+ *
+ * @param cls closure
+ * @param wire_target_h_payto effected target account
+ * @param[out] payto_uri target account URI, set to NULL if unknown
+ * @param[out] kyc_pending set to string describing missing KYC data
+ * @param[out] status set to AML status
+ * @param[out] aml_limit set to AML limit, or invalid amount for none
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_justification_for_missing_wire (
+ void *cls,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ char **payto_uri,
+ char **kyc_pending,
+ enum TALER_AmlDecisionState *status,
+ struct TALER_Amount *aml_limit);
+
+#endif
diff --git a/src/exchangedb/pg_select_kyc_attributes.c b/src/exchangedb/pg_select_kyc_attributes.c
new file mode 100644
index 000000000..99ac43b3e
--- /dev/null
+++ b/src/exchangedb/pg_select_kyc_attributes.c
@@ -0,0 +1,156 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_kyc_attributes.c
+ * @brief Implementation of the select_kyc_attributes function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_kyc_attributes.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_attributes_cb().
+ */
+struct GetAttributesContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_AttributeCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Key of our query.
+ */
+ const struct TALER_PaytoHashP *h_payto;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct GetAttributesContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_attributes_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GetAttributesContext *ctx = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct GNUNET_TIME_Timestamp collection_time;
+ struct GNUNET_TIME_Timestamp expiration_time;
+ size_t enc_attributes_size;
+ void *enc_attributes;
+ char *provider;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("provider",
+ &provider),
+ GNUNET_PQ_result_spec_timestamp ("collection_time",
+ &collection_time),
+ GNUNET_PQ_result_spec_timestamp ("expiration_time",
+ &expiration_time),
+ GNUNET_PQ_result_spec_variable_size ("encrypted_attributes",
+ &enc_attributes,
+ &enc_attributes_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ ctx->h_payto,
+ provider,
+ collection_time,
+ expiration_time,
+ enc_attributes_size,
+ enc_attributes);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_kyc_attributes (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_AttributeCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ struct GetAttributesContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .h_payto = h_payto,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "select_kyc_attributes",
+ "SELECT "
+ " provider"
+ ",collection_time"
+ ",expiration_time"
+ ",encrypted_attributes"
+ " FROM kyc_attributes"
+ " WHERE h_payto=$1");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_kyc_attributes",
+ params,
+ &get_attributes_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_kyc_attributes.h b/src/exchangedb/pg_select_kyc_attributes.h
new file mode 100644
index 000000000..7458aefe8
--- /dev/null
+++ b/src/exchangedb/pg_select_kyc_attributes.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_kyc_attributes.h
+ * @brief implementation of the select_kyc_attributes function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_KYC_ATTRIBUTES_H
+#define PG_SELECT_KYC_ATTRIBUTES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup KYC attribute data for a specific account.
+ *
+ * @param cls closure
+ * @param h_payto account for which the attribute data is stored
+ * @param cb callback to invoke on each match
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_kyc_attributes (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_AttributeCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c
new file mode 100644
index 000000000..417d78ec7
--- /dev/null
+++ b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c
@@ -0,0 +1,157 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_merge_amounts_for_kyc_check.c
+ * @brief Implementation of the select_merge_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_merge_amounts_for_kyc_check.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_kyc_amounts_cb().
+ */
+struct KycAmountCheckContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_KycAmountCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct KycAmountCheckContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_kyc_amounts_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct KycAmountCheckContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct GNUNET_TIME_Absolute date;
+ struct TALER_Amount amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_absolute_time ("date",
+ &date),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = ctx->cb (ctx->cb_cls,
+ &amount,
+ date);
+ GNUNET_PQ_cleanup_result (rs);
+ switch (ret)
+ {
+ case GNUNET_OK:
+ continue;
+ case GNUNET_NO:
+ break;
+ case GNUNET_SYSERR:
+ ctx->status = GNUNET_SYSERR;
+ break;
+ }
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_merge_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_absolute_time (&time_limit),
+ GNUNET_PQ_query_param_end
+ };
+ struct KycAmountCheckContext ctx = {
+ .cb = kac,
+ .cb_cls = kac_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+
+ PREPARE (pg,
+ "select_kyc_relevant_merge_events",
+ "SELECT"
+ " amount_with_fee AS amount"
+ ",merge_timestamp AS date"
+ " FROM account_merges"
+ " JOIN purse_merges USING (purse_pub)"
+ " JOIN purse_requests USING (purse_pub)"
+ " JOIN purse_decision USING (purse_pub)"
+ " WHERE wallet_h_payto=$1"
+ " AND merge_timestamp >= $2"
+ " AND NOT refunded"
+ " ORDER BY merge_timestamp DESC");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_kyc_relevant_merge_events",
+ params,
+ &get_kyc_amounts_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_merge_amounts_for_kyc_check.h b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.h
new file mode 100644
index 000000000..5d0a96359
--- /dev/null
+++ b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_merge_amounts_for_kyc_check.h
+ * @brief implementation of the select_merge_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_MERGE_AMOUNTS_FOR_KYC_CHECK_H
+#define PG_SELECT_MERGE_AMOUNTS_FOR_KYC_CHECK_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Call @a kac on merged reserve amounts after @a time_limit which are relevant for a
+ * KYC trigger for a the wallet identified by @a h_payto.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_merge_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_purse.c b/src/exchangedb/pg_select_purse.c
new file mode 100644
index 000000000..ffccb905c
--- /dev/null
+++ b/src/exchangedb/pg_select_purse.c
@@ -0,0 +1,94 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_purse.c
+ * @brief Implementation of the select_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp *purse_creation,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_Amount *amount,
+ struct TALER_Amount *deposited,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp *merge_timestamp,
+ bool *purse_deleted,
+ bool *purse_refunded)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("purse_expiration",
+ purse_expiration),
+ GNUNET_PQ_result_spec_timestamp ("purse_creation",
+ purse_creation),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ deposited),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ h_contract_terms),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("merge_timestamp",
+ merge_timestamp),
+ NULL),
+ GNUNET_PQ_result_spec_bool ("purse_deleted",
+ purse_deleted),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_bool ("purse_refunded",
+ purse_refunded),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "select_purse",
+ "SELECT "
+ " pr.merge_pub"
+ ",pr.purse_creation"
+ ",pr.purse_expiration"
+ ",pr.h_contract_terms"
+ ",pr.amount_with_fee"
+ ",pr.balance"
+ ",pm.merge_timestamp"
+ ",pd.purse_sig IS NOT NULL AS purse_deleted"
+ ",pc.refunded AS purse_refunded"
+ " FROM purse_requests pr"
+ " LEFT JOIN purse_merges pm ON (pm.purse_pub = pr.purse_pub)"
+ " LEFT JOIN purse_decision pc ON (pc.purse_pub = pr.purse_pub)"
+ " LEFT JOIN purse_deletion pd ON (pd.purse_pub = pr.purse_pub)"
+ " WHERE pr.purse_pub=$1;");
+ *merge_timestamp = GNUNET_TIME_UNIT_FOREVER_TS;
+ *purse_refunded = false;
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_purse",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_select_purse.h b/src/exchangedb/pg_select_purse.h
new file mode 100644
index 000000000..8f88c5cf7
--- /dev/null
+++ b/src/exchangedb/pg_select_purse.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_purse.h
+ * @brief implementation of the select_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_H
+#define PG_SELECT_PURSE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to obtain information about a purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the new purse
+ * @param[out] purse_creation set to time when the purse was created
+ * @param[out] purse_expiration set to time when the purse will expire
+ * @param[out] amount set to target amount (with fees) to be put into the purse
+ * @param[out] deposited set to actual amount put into the purse so far
+ * @param[out] h_contract_terms set to hash of the contract for the purse
+ * @param[out] merge_timestamp set to time when the purse was merged, or NEVER if not
+ * @param[out] purse_deleted set to true if purse was deleted
+ * @param[out] purse_refunded set to true if purse was refunded
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp *purse_creation,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_Amount *amount,
+ struct TALER_Amount *deposited,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp *merge_timestamp,
+ bool *purse_deleted,
+ bool *purse_refunded);
+
+#endif
diff --git a/src/exchangedb/pg_select_purse_by_merge_pub.c b/src/exchangedb/pg_select_purse_by_merge_pub.c
new file mode 100644
index 000000000..d035b3255
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_by_merge_pub.c
@@ -0,0 +1,79 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_by_merge_pub.c
+ * @brief Implementation of the select_purse_by_merge_pub function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_by_merge_pub.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_by_merge_pub (
+ void *cls,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t *age_limit,
+ struct TALER_Amount *target_amount,
+ struct TALER_Amount *balance,
+ struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (merge_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ purse_pub),
+ GNUNET_PQ_result_spec_timestamp ("purse_expiration",
+ purse_expiration),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ h_contract_terms),
+ GNUNET_PQ_result_spec_uint32 ("age_limit",
+ age_limit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ target_amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ balance),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_sig",
+ purse_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "select_purse_by_merge_pub",
+ "SELECT "
+ " purse_pub"
+ ",purse_expiration"
+ ",h_contract_terms"
+ ",age_limit"
+ ",amount_with_fee"
+ ",balance"
+ ",purse_sig"
+ " FROM purse_requests"
+ " WHERE merge_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_purse_by_merge_pub",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_select_purse_by_merge_pub.h b/src/exchangedb/pg_select_purse_by_merge_pub.h
new file mode 100644
index 000000000..2a7667132
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_by_merge_pub.h
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_by_merge_pub.h
+ * @brief implementation of the select_purse_by_merge_pub function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_BY_MERGE_PUB_H
+#define PG_SELECT_PURSE_BY_MERGE_PUB_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to return meta data about a purse by the
+ * merge capability key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param merge_pub public key representing the merge capability
+ * @param[out] purse_pub public key of the purse
+ * @param[out] purse_expiration when would an unmerged purse expire
+ * @param[out] h_contract_terms contract associated with the purse
+ * @param[out] age_limit the age limit for deposits into the purse
+ * @param[out] target_amount amount to be put into the purse
+ * @param[out] balance amount put so far into the purse
+ * @param[out] purse_sig signature of the purse over the initialization data
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_by_merge_pub (
+ void *cls,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t *age_limit,
+ struct TALER_Amount *target_amount,
+ struct TALER_Amount *balance,
+ struct TALER_PurseContractSignatureP *purse_sig);
+#endif
diff --git a/src/exchangedb/pg_select_purse_decisions_above_serial_id.c b/src/exchangedb/pg_select_purse_decisions_above_serial_id.c
new file mode 100644
index 000000000..f301ea78a
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_decisions_above_serial_id.c
@@ -0,0 +1,162 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_decisions_above_serial_id.c
+ * @brief Implementation of the select_purse_decisions_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_decisions_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #purse_decision_serial_helper_cb().
+ */
+struct PurseDecisionSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_PurseDecisionCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct PurseRefundSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+purse_decision_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct PurseDecisionSerialContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ bool no_reserve = true;
+ uint64_t rowid;
+ struct TALER_Amount val;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &purse_pub),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ &no_reserve),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &val),
+ GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ &purse_pub,
+ no_reserve ? NULL : &reserve_pub,
+ &val);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_decisions_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ bool refunded,
+ TALER_EXCHANGEDB_PurseDecisionCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_bool (refunded),
+ GNUNET_PQ_query_param_end
+ };
+ struct PurseDecisionSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_purse_decisions_incr",
+ "SELECT"
+ " pd.purse_pub"
+ ",pm.reserve_pub"
+ ",pd.purse_decision_serial_id"
+ ",pr.amount_with_fee"
+ " FROM purse_decision pd"
+ " JOIN purse_requests pr ON (pd.purse_pub = pr.purse_pub)"
+ " LEFT JOIN purse_merges pm ON (pm.purse_pub = pd.purse_pub)"
+ " WHERE ("
+ " (purse_decision_serial_id>=$1) AND "
+ " (refunded=$2)"
+ " )"
+ " ORDER BY purse_decision_serial_id ASC;");
+
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_purse_decisions_incr",
+ params,
+ &purse_decision_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_purse_decisions_above_serial_id.h b/src/exchangedb/pg_select_purse_decisions_above_serial_id.h
new file mode 100644
index 000000000..83168d546
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_decisions_above_serial_id.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_decisions_above_serial_id.h
+ * @brief implementation of the select_purse_decisions_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_DECISIONS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_PURSE_DECISIONS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select purse decisions above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param refunded which refund status to select for
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_decisions_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ bool refunded,
+ TALER_EXCHANGEDB_PurseDecisionCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_purse_deposits_above_serial_id.c b/src/exchangedb/pg_select_purse_deposits_above_serial_id.c
new file mode 100644
index 000000000..bb4320663
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_deposits_above_serial_id.c
@@ -0,0 +1,201 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_purse_deposits_above_serial_id.c
+ * @brief Implementation of the select_purse_deposits_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_deposits_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #purse_deposit_serial_helper_cb().
+ */
+struct PurseDepositSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_PurseDepositCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct DepositSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+purse_deposit_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct PurseDepositSerialContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_PurseDeposit deposit = {
+ .exchange_base_url = NULL
+ };
+ struct TALER_DenominationPublicKey denom_pub;
+ uint64_t rowid;
+ uint32_t flags32;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ bool not_merged = false;
+ struct TALER_Amount purse_balance;
+ struct TALER_Amount purse_total;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &deposit.amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ &purse_balance),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("total",
+ &purse_total),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee",
+ &deposit.deposit_fee),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("partner_base_url",
+ &deposit.exchange_base_url),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ &not_merged),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &deposit.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &deposit.coin_sig),
+ GNUNET_PQ_result_spec_uint32 ("flags",
+ &flags32),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &deposit.coin_pub),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &deposit.h_age_commitment),
+ &deposit.no_age_commitment),
+ GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ memset (&deposit,
+ 0,
+ sizeof (deposit));
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ &deposit,
+ not_merged ? NULL : &reserve_pub,
+ (enum TALER_WalletAccountMergeFlags) flags32,
+ &purse_balance,
+ &purse_total,
+ &denom_pub);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_deposits_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseDepositCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct PurseDepositSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_purse_deposits_incr",
+ "SELECT"
+ " pd.amount_with_fee"
+ ",pr.amount_with_fee AS total"
+ ",pr.balance"
+ ",pr.flags"
+ ",pd.purse_pub"
+ ",pd.coin_sig"
+ ",partner_base_url"
+ ",denom.denom_pub"
+ ",pm.reserve_pub"
+ ",kc.coin_pub"
+ ",kc.age_commitment_hash"
+ ",pd.purse_deposit_serial_id"
+ " FROM purse_deposits pd"
+ " LEFT JOIN partners USING (partner_serial_id)"
+ " LEFT JOIN purse_merges pm USING (purse_pub)"
+ " JOIN purse_requests pr USING (purse_pub)"
+ " JOIN known_coins kc USING (coin_pub)"
+ " JOIN denominations denom USING (denominations_serial)"
+ " WHERE ("
+ " (purse_deposit_serial_id>=$1)"
+ " )"
+ " ORDER BY purse_deposit_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_purse_deposits_incr",
+ params,
+ &purse_deposit_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_purse_deposits_above_serial_id.h b/src/exchangedb/pg_select_purse_deposits_above_serial_id.h
new file mode 100644
index 000000000..34d50b2ab
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_deposits_above_serial_id.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_purse_deposits_above_serial_id.h
+ * @brief implementation of the select_purse_deposits_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_DEPOSITS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_PURSE_DEPOSITS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select deposits above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_deposits_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseDepositCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_purse_deposits_by_purse.c b/src/exchangedb/pg_select_purse_deposits_by_purse.c
new file mode 100644
index 000000000..94b935cb4
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_deposits_by_purse.c
@@ -0,0 +1,153 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_deposits_by_purse.c
+ * @brief Implementation of the select_purse_deposits_by_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_deposits_by_purse.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #purse_refund_coin_helper_cb().
+ */
+struct PurseRefundCoinContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_PurseRefundCoinCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct PurseRefundCoinContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+purse_refund_coin_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct PurseRefundCoinContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_Amount amount_with_fee;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_DenominationPublicKey denom_pub;
+ uint64_t rowid;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount_with_fee),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin_pub),
+ GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ &amount_with_fee,
+ &coin_pub,
+ &denom_pub);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_deposits_by_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ TALER_EXCHANGEDB_PurseRefundCoinCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct PurseRefundCoinContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_purse_deposits_by_purse",
+ "SELECT"
+ " pd.purse_deposit_serial_id"
+ ",pd.amount_with_fee"
+ ",pd.coin_pub"
+ ",denom.denom_pub"
+ " FROM purse_deposits pd"
+ " JOIN known_coins kc"
+ " USING (coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE purse_pub=$1;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_purse_deposits_by_purse",
+ params,
+ &purse_refund_coin_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_purse_deposits_by_purse.h b/src/exchangedb/pg_select_purse_deposits_by_purse.h
new file mode 100644
index 000000000..203f9a151
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_deposits_by_purse.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_deposits_by_purse.h
+ * @brief implementation of the select_purse_deposits_by_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_DEPOSITS_BY_PURSE_H
+#define PG_SELECT_PURSE_DEPOSITS_BY_PURSE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select coin affected by purse refund.
+ *
+ * @param cls closure
+ * @param purse_pub purse that was refunded
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_deposits_by_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ TALER_EXCHANGEDB_PurseRefundCoinCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_purse_merge.c b/src/exchangedb/pg_select_purse_merge.c
new file mode 100644
index 000000000..ecc047cc5
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_merge.c
@@ -0,0 +1,80 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_merge.c
+ * @brief Implementation of the select_purse_merge function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_merge.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_merge (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseMergeSignatureP *merge_sig,
+ struct GNUNET_TIME_Timestamp *merge_timestamp,
+ char **partner_url,
+ struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *refunded)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merge_sig",
+ merge_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ reserve_pub),
+ GNUNET_PQ_result_spec_timestamp ("merge_timestamp",
+ merge_timestamp),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("partner_base_url",
+ partner_url),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_bool ("refunded",
+ refunded),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ *partner_url = NULL;
+ *refunded = false;
+ PREPARE (pg,
+ "select_purse_merge",
+ "SELECT "
+ " pm.reserve_pub"
+ ",pm.merge_sig"
+ ",pm.merge_timestamp"
+ ",pr.partner_base_url"
+ ",pd.refunded"
+ " FROM purse_merges pm"
+ " LEFT JOIN purse_decision pd USING (purse_pub)"
+ " LEFT JOIN partners pr USING (partner_serial_id)"
+ " WHERE pm.purse_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_purse_merge",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_select_purse_merge.h b/src/exchangedb/pg_select_purse_merge.h
new file mode 100644
index 000000000..8054974aa
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_merge.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_merge.h
+ * @brief implementation of the select_purse_merge function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_MERGE_H
+#define PG_SELECT_PURSE_MERGE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to approve merging of a purse with
+ * an account, made by the receiving account.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the purse
+ * @param[out] merge_sig set to the signature confirming the merge
+ * @param[out] merge_timestamp set to the time of the merge
+ * @param[out] partner_url set to the URL of the target exchange, or NULL if the target exchange is us. To be freed by the caller.
+ * @param[out] reserve_pub set to the public key of the reserve/account being credited
+ * @param[out] refunded set to true if purse was refunded
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_merge (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseMergeSignatureP *merge_sig,
+ struct GNUNET_TIME_Timestamp *merge_timestamp,
+ char **partner_url,
+ struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *refunded);
+
+#endif
diff --git a/src/exchangedb/pg_select_purse_merges_above_serial_id.c b/src/exchangedb/pg_select_purse_merges_above_serial_id.c
new file mode 100644
index 000000000..cd06d65e9
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_merges_above_serial_id.c
@@ -0,0 +1,190 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_purse_merges_above_serial_id.c
+ * @brief Implementation of the select_purse_merges_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_merges_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #purse_deposit_serial_helper_cb().
+ */
+struct PurseMergeSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_PurseMergeCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct PurseMergeSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+purse_merges_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct PurseMergeSerialContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ char *partner_base_url = NULL;
+ struct TALER_Amount amount;
+ struct TALER_Amount balance;
+ uint32_t flags32;
+ enum TALER_WalletAccountMergeFlags flags;
+ struct TALER_PurseMergePublicKeyP merge_pub;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_PurseMergeSignatureP merge_sig;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ &balance),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("partner_base_url",
+ &partner_base_url),
+ NULL),
+ GNUNET_PQ_result_spec_uint32 ("flags",
+ &flags32),
+ GNUNET_PQ_result_spec_timestamp ("merge_timestamp",
+ &merge_timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("merge_sig",
+ &merge_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("merge_pub",
+ &merge_pub),
+ GNUNET_PQ_result_spec_uint64 ("purse_merge_request_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ flags = (enum TALER_WalletAccountMergeFlags) flags32;
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ partner_base_url,
+ &amount,
+ &balance,
+ flags,
+ &merge_pub,
+ &reserve_pub,
+ &merge_sig,
+ &purse_pub,
+ merge_timestamp);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_merges_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseMergeCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct PurseMergeSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_purse_merge_incr",
+ "SELECT"
+ " pm.purse_merge_request_serial_id"
+ ",partner_base_url"
+ ",pr.amount_with_fee"
+ ",pr.balance"
+ ",pr.flags"
+ ",pr.merge_pub"
+ ",pm.reserve_pub"
+ ",pm.merge_sig"
+ ",pm.purse_pub"
+ ",pm.merge_timestamp"
+ " FROM purse_merges pm"
+ " JOIN purse_requests pr USING (purse_pub)"
+ " LEFT JOIN partners USING (partner_serial_id)"
+ " WHERE ("
+ " (purse_merge_request_serial_id>=$1)"
+ " )"
+ " ORDER BY purse_merge_request_serial_id ASC;");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_purse_merge_incr",
+ params,
+ &purse_merges_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_purse_merges_above_serial_id.h b/src/exchangedb/pg_select_purse_merges_above_serial_id.h
new file mode 100644
index 000000000..d63db55dd
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_merges_above_serial_id.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_purse_merges_above_serial_id.h
+ * @brief implementation of the select_purse_merges_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_MERGES_ABOVE_SERIAL_ID_H
+#define PG_SELECT_PURSE_MERGES_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select purse merges deposits above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_merges_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseMergeCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_purse_requests_above_serial_id.c b/src/exchangedb/pg_select_purse_requests_above_serial_id.c
new file mode 100644
index 000000000..61a4f2041
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_requests_above_serial_id.c
@@ -0,0 +1,178 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_purse_requests_above_serial_id.c
+ * @brief Implementation of the select_purse_requests_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_requests_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #purse_deposit_serial_helper_cb().
+ */
+struct PurseRequestsSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_PurseRequestCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct PurseRequestsSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+purse_requests_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct PurseRequestsSerialContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct TALER_Amount target_amount;
+ uint32_t age_limit;
+ struct TALER_PurseMergePublicKeyP merge_pub;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_PurseContractSignatureP purse_sig;
+ struct GNUNET_TIME_Timestamp purse_creation;
+ struct GNUNET_TIME_Timestamp purse_expiration;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &target_amount),
+ GNUNET_PQ_result_spec_uint32 ("age_limit",
+ &age_limit),
+ GNUNET_PQ_result_spec_timestamp ("purse_creation",
+ &purse_creation),
+ GNUNET_PQ_result_spec_timestamp ("purse_expiration",
+ &purse_expiration),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_sig",
+ &purse_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("merge_pub",
+ &merge_pub),
+ GNUNET_PQ_result_spec_uint64 ("purse_requests_request_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ &purse_pub,
+ &merge_pub,
+ purse_creation,
+ purse_expiration,
+ &h_contract_terms,
+ age_limit,
+ &target_amount,
+ &purse_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_requests_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseRequestCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct PurseRequestsSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_purse_requests_incr",
+ "SELECT"
+ " purse_requests_serial_id"
+ ",purse_pub"
+ ",amount_with_fee"
+ ",age_limit"
+ ",h_contract_terms"
+ ",purse_creation"
+ ",purse_expiration"
+ ",merge_pub"
+ ",purse_sig"
+ " FROM purse_requests"
+ " WHERE ("
+ " (purse_requests_serial_id>=$1)"
+ " )"
+ " ORDER BY purse_requests_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_purse_requests_incr",
+ params,
+ &purse_requests_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_purse_requests_above_serial_id.h b/src/exchangedb/pg_select_purse_requests_above_serial_id.h
new file mode 100644
index 000000000..25323e3d0
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_requests_above_serial_id.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_purse_requests_above_serial_id.h
+ * @brief implementation of the select_purse_requests_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_REQUESTS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_PURSE_REQUESTS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select purse requestss deposits above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_requests_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseRequestCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_recoup_above_serial_id.c b/src/exchangedb/pg_select_recoup_above_serial_id.c
new file mode 100644
index 000000000..5791ee500
--- /dev/null
+++ b/src/exchangedb/pg_select_recoup_above_serial_id.c
@@ -0,0 +1,194 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_recoup_above_serial_id.c
+ * @brief Implementation of the select_recoup_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_recoup_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #recoup_serial_helper_cb().
+ */
+struct RecoupSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_RecoupCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct RecoupSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+recoup_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct RecoupSerialContext *psc = cls;
+ struct PostgresClosure *pg = psc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_CoinPublicInfo coin;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
+ struct TALER_Amount amount;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_BlindedCoinHashP h_blind_ev;
+ struct GNUNET_TIME_Timestamp timestamp;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
+ &rowid),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin.coin_pub),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &coin_blind),
+ GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
+ &h_blind_ev),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &coin.denom_pub_hash),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &coin.h_age_commitment),
+ &coin.no_age_commitment),
+ TALER_PQ_result_spec_denom_sig ("denom_sig",
+ &coin.denom_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_end
+ };
+ int ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ psc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = psc->cb (psc->cb_cls,
+ rowid,
+ timestamp,
+ &amount,
+ &reserve_pub,
+ &coin,
+ &denom_pub,
+ &coin_sig,
+ &coin_blind);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_recoup_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RecoupCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct RecoupSerialContext psc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "recoup_get_incr",
+ "SELECT"
+ " recoup_uuid"
+ ",recoup_timestamp"
+ ",reserves.reserve_pub"
+ ",coins.coin_pub"
+ ",coin_sig"
+ ",coin_blind"
+ ",ro.h_blind_ev"
+ ",denoms.denom_pub_hash"
+ ",coins.denom_sig"
+ ",coins.age_commitment_hash"
+ ",denoms.denom_pub"
+ ",amount"
+ " FROM recoup"
+ " JOIN known_coins coins"
+ " USING (coin_pub)"
+ " JOIN reserves_out ro"
+ " USING (reserve_out_serial_id)"
+ " JOIN reserves"
+ " USING (reserve_uuid)"
+ " JOIN denominations denoms"
+ " ON (coins.denominations_serial = denoms.denominations_serial)"
+ " WHERE recoup_uuid>=$1"
+ " ORDER BY recoup_uuid ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "recoup_get_incr",
+ params,
+ &recoup_serial_helper_cb,
+ &psc);
+ if (GNUNET_OK != psc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_recoup_above_serial_id.h b/src/exchangedb/pg_select_recoup_above_serial_id.h
new file mode 100644
index 000000000..9be0b5c35
--- /dev/null
+++ b/src/exchangedb/pg_select_recoup_above_serial_id.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_recoup_above_serial_id.h
+ * @brief implementation of the select_recoup_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RECOUP_ABOVE_SERIAL_ID_H
+#define PG_SELECT_RECOUP_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called to select recoup requests the exchange
+ * received, ordered by serial ID (monotonically increasing).
+ *
+ * @param cls closure
+ * @param serial_id lowest serial ID to include (select larger or equal)
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_recoup_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RecoupCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_recoup_refresh_above_serial_id.c b/src/exchangedb/pg_select_recoup_refresh_above_serial_id.c
new file mode 100644
index 000000000..22f906738
--- /dev/null
+++ b/src/exchangedb/pg_select_recoup_refresh_above_serial_id.c
@@ -0,0 +1,203 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_recoup_refresh_above_serial_id.c
+ * @brief Implementation of the select_recoup_refresh_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_recoup_refresh_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #recoup_refresh_serial_helper_cb().
+ */
+struct RecoupRefreshSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_RecoupRefreshCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct RecoupRefreshSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+recoup_refresh_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct RecoupRefreshSerialContext *psc = cls;
+ struct PostgresClosure *pg = psc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct TALER_CoinSpendPublicKeyP old_coin_pub;
+ struct TALER_CoinPublicInfo coin;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_DenominationHashP old_denom_pub_hash;
+ struct TALER_Amount amount;
+ struct TALER_BlindedCoinHashP h_blind_ev;
+ struct GNUNET_TIME_Timestamp timestamp;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
+ &rowid),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
+ &old_coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("old_denom_pub_hash",
+ &old_denom_pub_hash),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &coin_blind),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
+ &h_blind_ev),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &coin.denom_pub_hash),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &coin.h_age_commitment),
+ &coin.no_age_commitment),
+ TALER_PQ_result_spec_denom_sig ("denom_sig",
+ &coin.denom_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ psc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = psc->cb (psc->cb_cls,
+ rowid,
+ timestamp,
+ &amount,
+ &old_coin_pub,
+ &old_denom_pub_hash,
+ &coin,
+ &denom_pub,
+ &coin_sig,
+ &coin_blind);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_recoup_refresh_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RecoupRefreshCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct RecoupRefreshSerialContext psc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "recoup_refresh_get_incr",
+ "SELECT"
+ " recoup_refresh_uuid"
+ ",recoup_timestamp"
+ ",old_coins.coin_pub AS old_coin_pub"
+ ",new_coins.age_commitment_hash"
+ ",old_denoms.denom_pub_hash AS old_denom_pub_hash"
+ ",new_coins.coin_pub As coin_pub"
+ ",coin_sig"
+ ",coin_blind"
+ ",new_denoms.denom_pub AS denom_pub"
+ ",rrc.h_coin_ev AS h_blind_ev"
+ ",new_denoms.denom_pub_hash"
+ ",new_coins.denom_sig AS denom_sig"
+ ",amount"
+ " FROM recoup_refresh"
+ " INNER JOIN refresh_revealed_coins rrc"
+ " USING (rrc_serial)"
+ " INNER JOIN refresh_commitments rfc"
+ " ON (rrc.melt_serial_id = rfc.melt_serial_id)"
+ " INNER JOIN known_coins old_coins"
+ " ON (rfc.old_coin_pub = old_coins.coin_pub)"
+ " INNER JOIN known_coins new_coins"
+ " ON (new_coins.coin_pub = recoup_refresh.coin_pub)"
+ " INNER JOIN denominations new_denoms"
+ " ON (new_coins.denominations_serial = new_denoms.denominations_serial)"
+ " INNER JOIN denominations old_denoms"
+ " ON (old_coins.denominations_serial = old_denoms.denominations_serial)"
+ " WHERE recoup_refresh_uuid>=$1"
+ " ORDER BY recoup_refresh_uuid ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "recoup_refresh_get_incr",
+ params,
+ &recoup_refresh_serial_helper_cb,
+ &psc);
+ if (GNUNET_OK != psc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_recoup_refresh_above_serial_id.h b/src/exchangedb/pg_select_recoup_refresh_above_serial_id.h
new file mode 100644
index 000000000..0d7b72fc3
--- /dev/null
+++ b/src/exchangedb/pg_select_recoup_refresh_above_serial_id.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_recoup_refresh_above_serial_id.h
+ * @brief implementation of the select_recoup_refresh_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RECOUP_REFRESH_ABOVE_SERIAL_ID_H
+#define PG_SELECT_RECOUP_REFRESH_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to select recoup requests the exchange received for
+ * refreshed coins, ordered by serial ID (monotonically increasing).
+ *
+ * @param cls closure
+ * @param serial_id lowest serial ID to include (select larger or equal)
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_recoup_refresh_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RecoupRefreshCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_refreshes_above_serial_id.c b/src/exchangedb/pg_select_refreshes_above_serial_id.c
new file mode 100644
index 000000000..db432269c
--- /dev/null
+++ b/src/exchangedb/pg_select_refreshes_above_serial_id.c
@@ -0,0 +1,179 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_refreshes_above_serial_id.c
+ * @brief Implementation of the select_refreshes_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_refreshes_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #refreshs_serial_helper_cb().
+ */
+struct RefreshsSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_RefreshesCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct RefreshsSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+refreshs_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct RefreshsSerialContext *rsc = cls;
+ struct PostgresClosure *pg = rsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_AgeCommitmentHash h_age_commitment;
+ bool ac_isnull;
+ struct TALER_Amount amount_with_fee;
+ uint32_t noreveal_index;
+ uint64_t rowid;
+ struct TALER_RefreshCommitmentP rc;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &h_age_commitment),
+ &ac_isnull),
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
+ &coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
+ &coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount_with_fee),
+ GNUNET_PQ_result_spec_uint32 ("noreveal_index",
+ &noreveal_index),
+ GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_auto_from_type ("rc",
+ &rc),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ rsc->status = GNUNET_SYSERR;
+ return;
+ }
+
+ ret = rsc->cb (rsc->cb_cls,
+ rowid,
+ &denom_pub,
+ ac_isnull ? NULL : &h_age_commitment,
+ &coin_pub,
+ &coin_sig,
+ &amount_with_fee,
+ noreveal_index,
+ &rc);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_refreshes_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RefreshesCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct RefreshsSerialContext rsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_refresh_commitments_incr",
+ "SELECT"
+ " denom.denom_pub"
+ ",kc.coin_pub AS old_coin_pub"
+ ",kc.age_commitment_hash"
+ ",old_coin_sig"
+ ",amount_with_fee"
+ ",noreveal_index"
+ ",melt_serial_id"
+ ",rc"
+ " FROM refresh_commitments"
+ " JOIN known_coins kc"
+ " ON (refresh_commitments.old_coin_pub = kc.coin_pub)"
+ " JOIN denominations denom"
+ " ON (kc.denominations_serial = denom.denominations_serial)"
+ " WHERE melt_serial_id>=$1"
+ " ORDER BY melt_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_refresh_commitments_incr",
+ params,
+ &refreshs_serial_helper_cb,
+ &rsc);
+ if (GNUNET_OK != rsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_refreshes_above_serial_id.h b/src/exchangedb/pg_select_refreshes_above_serial_id.h
new file mode 100644
index 000000000..2d1db275c
--- /dev/null
+++ b/src/exchangedb/pg_select_refreshes_above_serial_id.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_refreshes_above_serial_id.h
+ * @brief implementation of the select_refreshes_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_REFRESHES_ABOVE_SERIAL_ID_H
+#define PG_SELECT_REFRESHES_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select refresh sessions above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_refreshes_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RefreshesCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_refunds_above_serial_id.c b/src/exchangedb/pg_select_refunds_above_serial_id.c
new file mode 100644
index 000000000..396e8d3d5
--- /dev/null
+++ b/src/exchangedb/pg_select_refunds_above_serial_id.c
@@ -0,0 +1,223 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_refunds_above_serial_id.c
+ * @brief Implementation of the select_refunds_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_refunds_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #refunds_serial_helper_cb().
+ */
+struct RefundsSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_RefundCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct RefundsSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+refunds_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct RefundsSerialContext *rsc = cls;
+ struct PostgresClosure *pg = rsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_Refund refund;
+ struct TALER_DenominationPublicKey denom_pub;
+ uint64_t rowid;
+ bool full_refund;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &refund.details.merchant_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
+ &refund.details.merchant_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &refund.details.h_contract_terms),
+ GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
+ &refund.details.rtransaction_id),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &refund.coin.coin_pub),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &refund.details.refund_amount),
+ GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ rsc->status = GNUNET_SYSERR;
+ return;
+ }
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&rowid),
+ GNUNET_PQ_query_param_end
+ };
+ struct TALER_Amount amount_with_fee;
+ uint64_t s_f;
+ uint64_t s_v;
+ struct GNUNET_PQ_ResultSpec rs2[] = {
+ GNUNET_PQ_result_spec_uint64 ("s_v",
+ &s_v),
+ GNUNET_PQ_result_spec_uint64 ("s_f",
+ &s_f),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount_with_fee),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "test_refund_full",
+ params,
+ rs2);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ rsc->status = GNUNET_SYSERR;
+ return;
+ }
+ /* normalize */
+ s_v += s_f / TALER_AMOUNT_FRAC_BASE;
+ s_f %= TALER_AMOUNT_FRAC_BASE;
+ full_refund = (s_v >= amount_with_fee.value) &&
+ (s_f >= amount_with_fee.fraction);
+ }
+ ret = rsc->cb (rsc->cb_cls,
+ rowid,
+ &denom_pub,
+ &refund.coin.coin_pub,
+ &refund.details.merchant_pub,
+ &refund.details.merchant_sig,
+ &refund.details.h_contract_terms,
+ refund.details.rtransaction_id,
+ full_refund,
+ &refund.details.refund_amount);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_refunds_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RefundCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct RefundsSerialContext rsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Fetch refunds with rowid '\geq' the given parameter */
+ PREPARE (pg,
+ "audit_get_refunds_incr",
+ "SELECT"
+ " bdep.merchant_pub"
+ ",ref.merchant_sig"
+ ",bdep.h_contract_terms"
+ ",ref.rtransaction_id"
+ ",denom.denom_pub"
+ ",kc.coin_pub"
+ ",ref.amount_with_fee"
+ ",ref.refund_serial_id"
+ " FROM refunds ref"
+ " JOIN batch_deposits bdep"
+ " ON (ref.batch_deposit_serial_id=bdep.batch_deposit_serial_id)"
+ " JOIN coin_deposits cdep"
+ " ON (ref.coin_pub=cdep.coin_pub AND ref.batch_deposit_serial_id=cdep.batch_deposit_serial_id)"
+ " JOIN known_coins kc"
+ " ON (cdep.coin_pub=kc.coin_pub)"
+ " JOIN denominations denom"
+ " ON (kc.denominations_serial=denom.denominations_serial)"
+ " WHERE ref.refund_serial_id>=$1"
+ " ORDER BY ref.refund_serial_id ASC;");
+ PREPARE (pg,
+ "test_refund_full",
+ "SELECT"
+ " CAST(SUM(CAST((ref.amount_with_fee).frac AS INT8)) AS INT8) AS s_f"
+ ",CAST(SUM((ref.amount_with_fee).val) AS INT8) AS s_v"
+ ",cdep.amount_with_fee"
+ " FROM refunds ref"
+ " JOIN coin_deposits cdep"
+ " ON (ref.coin_pub=cdep.coin_pub AND ref.batch_deposit_serial_id=cdep.batch_deposit_serial_id)"
+ " WHERE ref.refund_serial_id=$1"
+ " GROUP BY (cdep.amount_with_fee);");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_refunds_incr",
+ params,
+ &refunds_serial_helper_cb,
+ &rsc);
+ if (GNUNET_OK != rsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_refunds_above_serial_id.h b/src/exchangedb/pg_select_refunds_above_serial_id.h
new file mode 100644
index 000000000..b33816a94
--- /dev/null
+++ b/src/exchangedb/pg_select_refunds_above_serial_id.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_refunds_above_serial_id.h
+ * @brief implementation of the select_refunds_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_REFUNDS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_REFUNDS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select refunds above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_refunds_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RefundCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_refunds_by_coin.c b/src/exchangedb/pg_select_refunds_by_coin.c
new file mode 100644
index 000000000..d9cd6dd3c
--- /dev/null
+++ b/src/exchangedb/pg_select_refunds_by_coin.c
@@ -0,0 +1,143 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 exchangedb/pg_select_refunds_by_coin.c
+ * @brief Implementation of the select_refunds_by_coin function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_refunds_by_coin.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_refunds_cb().
+ */
+struct SelectRefundContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_RefundCoinCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on error.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct SelectRefundContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+get_refunds_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct SelectRefundContext *srctx = cls;
+ struct PostgresClosure *pg = srctx->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_Amount amount_with_fee;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount_with_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ srctx->status = GNUNET_SYSERR;
+ return;
+ }
+ if (GNUNET_OK !=
+ srctx->cb (srctx->cb_cls,
+ &amount_with_fee))
+ return;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_refunds_by_coin (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PrivateContractHashP *h_contract,
+ TALER_EXCHANGEDB_RefundCoinCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_contract),
+ GNUNET_PQ_query_param_end
+ };
+ struct SelectRefundContext srctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ const char *query = "get_refunds_by_coin_and_contract";
+
+ PREPARE (pg,
+ query,
+ "SELECT"
+ " ref.amount_with_fee"
+ " FROM refunds ref"
+ " JOIN coin_deposits cdep"
+ " USING (coin_pub,batch_deposit_serial_id)"
+ " JOIN batch_deposits bdep"
+ " ON (ref.batch_deposit_serial_id = bdep.batch_deposit_serial_id)"
+ " WHERE ref.coin_pub=$1"
+ " AND bdep.merchant_pub=$2"
+ " AND bdep.h_contract_terms=$3;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ query,
+ params,
+ &get_refunds_cb,
+ &srctx);
+ if (GNUNET_SYSERR == srctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_refunds_by_coin.h b/src/exchangedb/pg_select_refunds_by_coin.h
new file mode 100644
index 000000000..72df13fda
--- /dev/null
+++ b/src/exchangedb/pg_select_refunds_by_coin.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_refunds_by_coin.h
+ * @brief implementation of the select_refunds_by_coin function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_REFUNDS_BY_COIN_H
+#define PG_SELECT_REFUNDS_BY_COIN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select refunds by @a coin_pub, @a merchant_pub and @a h_contract.
+ *
+ * @param cls closure of plugin
+ * @param coin_pub coin to get refunds for
+ * @param merchant_pub merchant to get refunds for
+ * @param h_contract contract (hash) to get refunds for
+ * @param cb function to call for each refund found
+ * @param cb_cls closure for @a cb
+ * @return query result status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_refunds_by_coin (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PrivateContractHashP *h_contract,
+ TALER_EXCHANGEDB_RefundCoinCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_reserve_close_info.c b/src/exchangedb/pg_select_reserve_close_info.c
new file mode 100644
index 000000000..eccba8e4c
--- /dev/null
+++ b/src/exchangedb/pg_select_reserve_close_info.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_reserve_close_info.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_reserve_close_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserve_close_info (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_Amount *balance,
+ char **payto_uri)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount ("current_balance",
+ pg->currency,
+ balance),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ payto_uri),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "select_reserve_close_info",
+ "SELECT "
+ " r.current_balance"
+ ",wt.payto_uri"
+ " FROM reserves r"
+ " LEFT JOIN reserves_in ri USING (reserve_pub)"
+ " LEFT JOIN wire_targets wt ON (ri.wire_source_h_payto = wt.wire_target_h_payto)"
+ " WHERE reserve_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_reserve_close_info",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_select_reserve_close_info.h b/src/exchangedb/pg_select_reserve_close_info.h
new file mode 100644
index 000000000..2b90ffd05
--- /dev/null
+++ b/src/exchangedb/pg_select_reserve_close_info.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_reserve_close_info.h
+ * @brief implementation of the select_reserve_close_info function
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RESERVE_CLOSE_INFO_H
+#define PG_SELECT_RESERVE_CLOSE_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select information needed to see if we can close
+ * a reserve.
+ *
+ * @param cls closure
+ * @param reserve_pub which reserve is this about?
+ * @param[out] balance current reserve balance
+ * @param[out] payto_uri set to URL of account that
+ * originally funded the reserve;
+ * could be set to NULL if not known
+ * @return transaction status code, 0 if reserve unknown
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserve_close_info (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_Amount *balance,
+ char **payto_uri);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_reserve_closed_above_serial_id.c b/src/exchangedb/pg_select_reserve_closed_above_serial_id.c
new file mode 100644
index 000000000..d24d6a600
--- /dev/null
+++ b/src/exchangedb/pg_select_reserve_closed_above_serial_id.c
@@ -0,0 +1,178 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_reserve_closed_above_serial_id.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_reserve_history.h"
+#include "plugin_exchangedb_common.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #reserve_closed_serial_helper_cb().
+ */
+struct ReserveClosedSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_ReserveClosedCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin's context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct ReserveClosedSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserve_closed_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveClosedSerialContext *rcsc = cls;
+ struct PostgresClosure *pg = rcsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ char *receiver_account;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount closing_fee;
+ struct GNUNET_TIME_Timestamp execution_date;
+ uint64_t close_request_row;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("close_uuid",
+ &rowid),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &execution_date),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid",
+ &wtid),
+ GNUNET_PQ_result_spec_string ("receiver_account",
+ &receiver_account),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
+ &closing_fee),
+ GNUNET_PQ_result_spec_uint64 ("close_request_row",
+ &close_request_row),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ rcsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = rcsc->cb (rcsc->cb_cls,
+ rowid,
+ execution_date,
+ &amount_with_fee,
+ &closing_fee,
+ &reserve_pub,
+ receiver_account,
+ &wtid,
+ close_request_row);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserve_closed_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveClosedCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct ReserveClosedSerialContext rcsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Used in #postgres_select_reserve_closed_above_serial_id() to
+ obtain information about closed reserves */
+ PREPARE (
+ pg,
+ "reserves_close_get_incr",
+ "SELECT"
+ " close_uuid"
+ ",reserves.reserve_pub"
+ ",execution_date"
+ ",wtid"
+ ",payto_uri AS receiver_account"
+ ",amount"
+ ",closing_fee"
+ ",close_request_row"
+ " FROM reserves_close"
+ " JOIN wire_targets"
+ " USING (wire_target_h_payto)"
+ " JOIN reserves"
+ " USING (reserve_pub)"
+ " WHERE close_uuid>=$1"
+ " ORDER BY close_uuid ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "reserves_close_get_incr",
+ params,
+ &reserve_closed_serial_helper_cb,
+ &rcsc);
+ if (GNUNET_OK != rcsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_reserve_closed_above_serial_id.h b/src/exchangedb/pg_select_reserve_closed_above_serial_id.h
new file mode 100644
index 000000000..af3c8631e
--- /dev/null
+++ b/src/exchangedb/pg_select_reserve_closed_above_serial_id.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_reserve_closed_above_serial_id.h
+ * @brief implementation of the select_reserve_closed_above_serial_id function
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RESERVE_CLOSED_ABOVE_SERIAL_ID_H
+#define PG_SELECT_RESERVE_CLOSED_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to select reserve close operations the aggregator
+ * triggered, ordered by serial ID (monotonically increasing).
+ *
+ * @param cls closure
+ * @param serial_id lowest serial ID to include (select larger or equal)
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserve_closed_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveClosedCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_reserve_open_above_serial_id.c b/src/exchangedb/pg_select_reserve_open_above_serial_id.c
new file mode 100644
index 000000000..1675e71a7
--- /dev/null
+++ b/src/exchangedb/pg_select_reserve_open_above_serial_id.c
@@ -0,0 +1,168 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_reserve_open_above_serial_id.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_reserve_open_above_serial_id.h"
+#include "plugin_exchangedb_common.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #reserve_open_serial_helper_cb().
+ */
+struct ReserveOpenSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_ReserveOpenCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin's context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct ReserveOpenSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserve_open_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveOpenSerialContext *rcsc = cls;
+ struct PostgresClosure *pg = rcsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_ReserveSignatureP reserve_sig;
+ uint32_t requested_purse_limit;
+ struct GNUNET_TIME_Timestamp request_timestamp;
+ struct GNUNET_TIME_Timestamp reserve_expiration;
+ struct TALER_Amount reserve_payment;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("open_request_uuid",
+ &rowid),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &reserve_sig),
+ GNUNET_PQ_result_spec_timestamp ("request_timestamp",
+ &request_timestamp),
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ &reserve_expiration),
+ GNUNET_PQ_result_spec_uint32 ("requested_purse_limit",
+ &requested_purse_limit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_payment",
+ &reserve_payment),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ rcsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = rcsc->cb (rcsc->cb_cls,
+ rowid,
+ &reserve_payment,
+ request_timestamp,
+ reserve_expiration,
+ requested_purse_limit,
+ &reserve_pub,
+ &reserve_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserve_open_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveOpenCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct ReserveOpenSerialContext rcsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (
+ pg,
+ "reserves_open_get_incr",
+ "SELECT"
+ " open_request_uuid"
+ ",reserve_pub"
+ ",request_timestamp"
+ ",expiration_date"
+ ",reserve_sig"
+ ",reserve_payment"
+ ",requested_purse_limit"
+ " FROM reserves_open_requests"
+ " WHERE open_request_uuid>=$1"
+ " ORDER BY open_request_uuid ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "reserves_open_get_incr",
+ params,
+ &reserve_open_serial_helper_cb,
+ &rcsc);
+ if (GNUNET_OK != rcsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_reserve_open_above_serial_id.h b/src/exchangedb/pg_select_reserve_open_above_serial_id.h
new file mode 100644
index 000000000..4ec5b705a
--- /dev/null
+++ b/src/exchangedb/pg_select_reserve_open_above_serial_id.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file pg_select_reserve_open_above_serial_id.h
+ * @brief implementation of the select_reserve_open_above_serial_id function
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RESERVE_OPEN_ABOVE_SERIAL_ID_H
+#define PG_SELECT_RESERVE_OPEN_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to select reserve open operations, ordered by serial ID
+ * (monotonically increasing).
+ *
+ * @param cls closure
+ * @param serial_id lowest serial ID to include (select larger or equal)
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserve_open_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveOpenCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_reserves_in_above_serial_id.c b/src/exchangedb/pg_select_reserves_in_above_serial_id.c
new file mode 100644
index 000000000..21033e80d
--- /dev/null
+++ b/src/exchangedb/pg_select_reserves_in_above_serial_id.c
@@ -0,0 +1,164 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_reserves_in_above_serial_id.c
+ * @brief Implementation of the select_reserves_in_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_reserves_in_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #reserves_in_serial_helper_cb().
+ */
+struct ReservesInSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_ReserveInCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct ReservesInSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserves_in_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReservesInSerialContext *risc = cls;
+ struct PostgresClosure *pg = risc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_Amount credit;
+ char *sender_account_details;
+ struct GNUNET_TIME_Timestamp execution_date;
+ uint64_t rowid;
+ uint64_t wire_reference;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_uint64 ("wire_reference",
+ &wire_reference),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
+ &credit),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &execution_date),
+ GNUNET_PQ_result_spec_string ("sender_account_details",
+ &sender_account_details),
+ GNUNET_PQ_result_spec_uint64 ("reserve_in_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ risc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = risc->cb (risc->cb_cls,
+ rowid,
+ &reserve_pub,
+ &credit,
+ sender_account_details,
+ wire_reference,
+ execution_date);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserves_in_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveInCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct ReservesInSerialContext risc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_reserves_in_get_transactions_incr",
+ "SELECT"
+ " reserves.reserve_pub"
+ ",wire_reference"
+ ",credit"
+ ",execution_date"
+ ",payto_uri AS sender_account_details"
+ ",reserve_in_serial_id"
+ " FROM reserves_in"
+ " JOIN reserves"
+ " USING (reserve_pub)"
+ " JOIN wire_targets"
+ " ON (wire_source_h_payto = wire_target_h_payto)"
+ " WHERE reserve_in_serial_id>=$1"
+ " ORDER BY reserve_in_serial_id;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_reserves_in_get_transactions_incr",
+ params,
+ &reserves_in_serial_helper_cb,
+ &risc);
+ if (GNUNET_OK != risc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_reserves_in_above_serial_id.h b/src/exchangedb/pg_select_reserves_in_above_serial_id.h
new file mode 100644
index 000000000..5f5dd2ecc
--- /dev/null
+++ b/src/exchangedb/pg_select_reserves_in_above_serial_id.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_reserves_in_above_serial_id.h
+ * @brief implementation of the select_reserves_in_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RESERVES_IN_ABOVE_SERIAL_ID_H
+#define PG_SELECT_RESERVES_IN_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Select inbound wire transfers into reserves_in above @a serial_id
+ * in monotonically increasing order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserves_in_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveInCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.c b/src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.c
new file mode 100644
index 000000000..1c7bc15a0
--- /dev/null
+++ b/src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.c
@@ -0,0 +1,168 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_reserves_in_above_serial_id_by_account.c
+ * @brief Implementation of the select_reserves_in_above_serial_id_by_account function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_reserves_in_above_serial_id_by_account.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #reserves_in_serial_helper_cb().
+ */
+struct ReservesInSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_ReserveInCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct ReservesInSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserves_in_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReservesInSerialContext *risc = cls;
+ struct PostgresClosure *pg = risc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_Amount credit;
+ char *sender_account_details;
+ struct GNUNET_TIME_Timestamp execution_date;
+ uint64_t rowid;
+ uint64_t wire_reference;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_uint64 ("wire_reference",
+ &wire_reference),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
+ &credit),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &execution_date),
+ GNUNET_PQ_result_spec_string ("sender_account_details",
+ &sender_account_details),
+ GNUNET_PQ_result_spec_uint64 ("reserve_in_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ risc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = risc->cb (risc->cb_cls,
+ rowid,
+ &reserve_pub,
+ &credit,
+ sender_account_details,
+ wire_reference,
+ execution_date);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserves_in_above_serial_id_by_account (
+ void *cls,
+ const char *account_name,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveInCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_string (account_name),
+ GNUNET_PQ_query_param_end
+ };
+ struct ReservesInSerialContext risc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_reserves_in_get_transactions_incr_by_account",
+ "SELECT"
+ " reserves.reserve_pub"
+ ",wire_reference"
+ ",credit"
+ ",execution_date"
+ ",payto_uri AS sender_account_details"
+ ",reserve_in_serial_id"
+ " FROM reserves_in"
+ " JOIN reserves "
+ " USING (reserve_pub)"
+ " JOIN wire_targets"
+ " ON (wire_source_h_payto = wire_target_h_payto)"
+ " WHERE reserve_in_serial_id>=$1"
+ " AND exchange_account_section=$2"
+ " ORDER BY reserve_in_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_reserves_in_get_transactions_incr_by_account",
+ params,
+ &reserves_in_serial_helper_cb,
+ &risc);
+ if (GNUNET_OK != risc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.h b/src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.h
new file mode 100644
index 000000000..81855ede9
--- /dev/null
+++ b/src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_reserves_in_above_serial_id_by_account.h
+ * @brief implementation of the select_reserves_in_above_serial_id_by_account function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RESERVES_IN_ABOVE_SERIAL_ID_BY_ACCOUNT_H
+#define PG_SELECT_RESERVES_IN_ABOVE_SERIAL_ID_BY_ACCOUNT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Select inbound wire transfers into reserves_in above @a serial_id
+ * in monotonically increasing order by account.
+ *
+ * @param cls closure
+ * @param account_name name of the account to select by
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserves_in_above_serial_id_by_account (
+ void *cls,
+ const char *account_name,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveInCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_satisfied_kyc_processes.c b/src/exchangedb/pg_select_satisfied_kyc_processes.c
new file mode 100644
index 000000000..e0d884ef1
--- /dev/null
+++ b/src/exchangedb/pg_select_satisfied_kyc_processes.c
@@ -0,0 +1,141 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_satisfied_kyc_processes.c
+ * @brief Implementation of the select_satisfied_kyc_processes function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_satisfied_kyc_processes.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_legitimizations_cb().
+ */
+struct GetLegitimizationsContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_SatisfiedProviderCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct GetLegitimizationsContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_legitimizations_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GetLegitimizationsContext *ctx = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *provider_section;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("provider_section",
+ &provider_section),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found satisfied LEGI: %s\n",
+ provider_section);
+ ctx->cb (ctx->cb_cls,
+ provider_section);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_satisfied_kyc_processes (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
+ void *spc_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now
+ = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct GetLegitimizationsContext ctx = {
+ .cb = spc,
+ .cb_cls = spc_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "get_satisfied_legitimizations",
+ "SELECT "
+ " provider_section"
+ " FROM legitimization_processes"
+ " WHERE h_payto=$1"
+ " AND expiration_time>=$2;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "get_satisfied_legitimizations",
+ params,
+ &get_legitimizations_cb,
+ &ctx);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Satisfied LEGI check returned %d\n",
+ qs);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_satisfied_kyc_processes.h b/src/exchangedb/pg_select_satisfied_kyc_processes.h
new file mode 100644
index 000000000..bc1639ff9
--- /dev/null
+++ b/src/exchangedb/pg_select_satisfied_kyc_processes.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_satisfied_kyc_processes.h
+ * @brief implementation of the select_satisfied_kyc_processes function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_SATISFIED_KYC_PROCESSES_H
+#define PG_SELECT_SATISFIED_KYC_PROCESSES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Call us on KYC processes satisfied for the given
+ * account.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param spc function to call for each satisfied KYC process
+ * @param spc_cls closure for @a spc
+ * @return transaction status code
+ */
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_satisfied_kyc_processes (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
+ void *spc_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_similar_kyc_attributes.c b/src/exchangedb/pg_select_similar_kyc_attributes.c
new file mode 100644
index 000000000..342f9ef33
--- /dev/null
+++ b/src/exchangedb/pg_select_similar_kyc_attributes.c
@@ -0,0 +1,154 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_similar_kyc_attributes.c
+ * @brief Implementation of the select_similar_kyc_attributes function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_similar_kyc_attributes.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_similar_attributes_cb().
+ */
+struct GetAttributesContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_AttributeCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct GetAttributesContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_attributes_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GetAttributesContext *ctx = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct TALER_PaytoHashP h_payto;
+ struct GNUNET_TIME_Timestamp collection_time;
+ struct GNUNET_TIME_Timestamp expiration_time;
+ size_t enc_attributes_size;
+ void *enc_attributes;
+ char *provider;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_payto",
+ &h_payto),
+ GNUNET_PQ_result_spec_string ("provider",
+ &provider),
+ GNUNET_PQ_result_spec_timestamp ("collection_time",
+ &collection_time),
+ GNUNET_PQ_result_spec_timestamp ("expiration_time",
+ &expiration_time),
+ GNUNET_PQ_result_spec_variable_size ("encrypted_attributes",
+ &enc_attributes,
+ &enc_attributes_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &h_payto,
+ provider,
+ collection_time,
+ expiration_time,
+ enc_attributes_size,
+ enc_attributes);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_similar_kyc_attributes (
+ void *cls,
+ const struct GNUNET_ShortHashCode *kyc_prox,
+ TALER_EXCHANGEDB_AttributeCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (kyc_prox),
+ GNUNET_PQ_query_param_end
+ };
+ struct GetAttributesContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "select_similar_kyc_attributes",
+ "SELECT "
+ " h_payto"
+ ",provider"
+ ",collection_time"
+ ",expiration_time"
+ ",encrypted_attributes"
+ " FROM kyc_attributes"
+ " WHERE kyc_prox=$1");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_similar_kyc_attributes",
+ params,
+ &get_attributes_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_similar_kyc_attributes.h b/src/exchangedb/pg_select_similar_kyc_attributes.h
new file mode 100644
index 000000000..72f2407e8
--- /dev/null
+++ b/src/exchangedb/pg_select_similar_kyc_attributes.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_similar_kyc_attributes.h
+ * @brief implementation of the select_similar_kyc_attributes function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_SIMILAR_KYC_ATTRIBUTES_H
+#define PG_SELECT_SIMILAR_KYC_ATTRIBUTES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup similar KYC attribute data.
+ *
+ * @param cls closure
+ * @param kyc_prox key for similarity search
+ * @param cb callback to invoke on each match
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_similar_kyc_attributes (
+ void *cls,
+ const struct GNUNET_ShortHashCode *kyc_prox,
+ TALER_EXCHANGEDB_AttributeCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_wire_out_above_serial_id.c b/src/exchangedb/pg_select_wire_out_above_serial_id.c
new file mode 100644
index 000000000..8668c429d
--- /dev/null
+++ b/src/exchangedb/pg_select_wire_out_above_serial_id.c
@@ -0,0 +1,157 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_wire_out_above_serial_id.c
+ * @brief Implementation of the select_wire_out_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_wire_out_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #wire_out_serial_helper_cb().
+ */
+struct WireOutSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_WireTransferOutCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ int status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct WireOutSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+wire_out_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct WireOutSerialContext *wosc = cls;
+ struct PostgresClosure *pg = wosc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct GNUNET_TIME_Timestamp date;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ char *payto_uri;
+ struct TALER_Amount amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("wireout_uuid",
+ &rowid),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &date),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
+ &wtid),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_end
+ };
+ int ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ wosc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = wosc->cb (wosc->cb_cls,
+ rowid,
+ date,
+ &wtid,
+ payto_uri,
+ &amount);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_wire_out_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_WireTransferOutCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct WireOutSerialContext wosc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ /* Used in #postgres_select_wire_out_above_serial_id() */
+ PREPARE (pg,
+ "audit_get_wire_incr",
+ "SELECT"
+ " wireout_uuid"
+ ",execution_date"
+ ",wtid_raw"
+ ",payto_uri"
+ ",amount"
+ " FROM wire_out"
+ " JOIN wire_targets"
+ " USING (wire_target_h_payto)"
+ " WHERE wireout_uuid>=$1"
+ " ORDER BY wireout_uuid ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_wire_incr",
+ params,
+ &wire_out_serial_helper_cb,
+ &wosc);
+ if (GNUNET_OK != wosc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_wire_out_above_serial_id.h b/src/exchangedb/pg_select_wire_out_above_serial_id.h
new file mode 100644
index 000000000..e42cb9b0f
--- /dev/null
+++ b/src/exchangedb/pg_select_wire_out_above_serial_id.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_wire_out_above_serial_id.h
+ * @brief implementation of the select_wire_out_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_WIRE_OUT_ABOVE_SERIAL_ID_H
+#define PG_SELECT_WIRE_OUT_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to select all wire transfers the exchange
+ * executed.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_wire_out_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_WireTransferOutCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_wire_out_above_serial_id_by_account.c b/src/exchangedb/pg_select_wire_out_above_serial_id_by_account.c
new file mode 100644
index 000000000..3448c5a49
--- /dev/null
+++ b/src/exchangedb/pg_select_wire_out_above_serial_id_by_account.c
@@ -0,0 +1,161 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_wire_out_above_serial_id_by_account.c
+ * @brief Implementation of the select_wire_out_above_serial_id_by_account function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_wire_out_above_serial_id_by_account.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #wire_out_serial_helper_cb().
+ */
+struct WireOutSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_WireTransferOutCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ int status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct WireOutSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+wire_out_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct WireOutSerialContext *wosc = cls;
+ struct PostgresClosure *pg = wosc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct GNUNET_TIME_Timestamp date;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ char *payto_uri;
+ struct TALER_Amount amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("wireout_uuid",
+ &rowid),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &date),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
+ &wtid),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_end
+ };
+ int ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ wosc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = wosc->cb (wosc->cb_cls,
+ rowid,
+ date,
+ &wtid,
+ payto_uri,
+ &amount);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_wire_out_above_serial_id_by_account (
+ void *cls,
+ const char *account_name,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_WireTransferOutCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_string (account_name),
+ GNUNET_PQ_query_param_end
+ };
+ struct WireOutSerialContext wosc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_wire_incr_by_account",
+ "SELECT"
+ " wireout_uuid"
+ ",execution_date"
+ ",wtid_raw"
+ ",payto_uri"
+ ",amount"
+ " FROM wire_out"
+ " JOIN wire_targets"
+ " USING (wire_target_h_payto)"
+ " WHERE "
+ " wireout_uuid>=$1 "
+ " AND exchange_account_section=$2"
+ " ORDER BY wireout_uuid ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_wire_incr_by_account",
+ params,
+ &wire_out_serial_helper_cb,
+ &wosc);
+ if (GNUNET_OK != wosc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_wire_out_above_serial_id_by_account.h b/src/exchangedb/pg_select_wire_out_above_serial_id_by_account.h
new file mode 100644
index 000000000..04c6a62b2
--- /dev/null
+++ b/src/exchangedb/pg_select_wire_out_above_serial_id_by_account.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_wire_out_above_serial_id_by_account.h
+ * @brief implementation of the select_wire_out_above_serial_id_by_account function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_WIRE_OUT_ABOVE_SERIAL_ID_BY_ACCOUNT_H
+#define PG_SELECT_WIRE_OUT_ABOVE_SERIAL_ID_BY_ACCOUNT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to select all wire transfers the exchange
+ * executed by account.
+ *
+ * @param cls closure
+ * @param account_name account to select
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_wire_out_above_serial_id_by_account (
+ void *cls,
+ const char *account_name,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_WireTransferOutCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.c b/src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.c
new file mode 100644
index 000000000..71ed81833
--- /dev/null
+++ b/src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.c
@@ -0,0 +1,158 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_withdraw_amounts_for_kyc_check.c
+ * @brief Implementation of the select_withdraw_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_withdraw_amounts_for_kyc_check.h"
+#include "pg_select_aggregation_amounts_for_kyc_check.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_kyc_amounts_cb().
+ */
+struct KycAmountCheckContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_KycAmountCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct KycAmountCheckContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_kyc_amounts_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct KycAmountCheckContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct GNUNET_TIME_Absolute date;
+ struct TALER_Amount amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_absolute_time ("date",
+ &date),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = ctx->cb (ctx->cb_cls,
+ &amount,
+ date);
+ GNUNET_PQ_cleanup_result (rs);
+ switch (ret)
+ {
+ case GNUNET_OK:
+ continue;
+ case GNUNET_NO:
+ break;
+ case GNUNET_SYSERR:
+ ctx->status = GNUNET_SYSERR;
+ break;
+ }
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_withdraw_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_absolute_time (&time_limit),
+ GNUNET_PQ_query_param_end
+ };
+ struct KycAmountCheckContext ctx = {
+ .cb = kac,
+ .cb_cls = kac_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "select_kyc_relevant_withdraw_events",
+ "SELECT"
+ " ro.amount_with_fee AS amount"
+ ",ro.execution_date AS date"
+ " FROM reserves_in ri"
+ " JOIN reserve_history rh"
+ " ON (rh.reserve_pub = ri.reserve_pub)"
+ " JOIN reserves_out ro"
+ " ON (ro.reserve_out_serial_id = rh.serial_id)"
+ " WHERE ri.wire_source_h_payto=$1"
+ " AND rh.table_name='reserves_out'"
+ " AND ro.execution_date >= $2"
+ " ORDER BY rh.reserve_history_serial_id DESC");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_kyc_relevant_withdraw_events",
+ params,
+ &get_kyc_amounts_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.h b/src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.h
new file mode 100644
index 000000000..9a780adbe
--- /dev/null
+++ b/src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_withdraw_amounts_for_kyc_check.h
+ * @brief implementation of the select_withdraw_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_WITHDRAW_AMOUNTS_FOR_KYC_CHECK_H
+#define PG_SELECT_WITHDRAW_AMOUNTS_FOR_KYC_CHECK_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Call @a kac on withdrawn amounts after @a time_limit which are relevant
+ * for a KYC trigger for a the (debited) account identified by @a h_payto.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_withdraw_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_withdrawals_above_serial_id.c b/src/exchangedb/pg_select_withdrawals_above_serial_id.c
new file mode 100644
index 000000000..9beb0f936
--- /dev/null
+++ b/src/exchangedb/pg_select_withdrawals_above_serial_id.c
@@ -0,0 +1,170 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_withdrawals_above_serial_id.c
+ * @brief Implementation of the select_withdrawals_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_withdrawals_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #reserves_out_serial_helper_cb().
+ */
+struct ReservesOutSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_WithdrawCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct ReservesOutSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserves_out_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReservesOutSerialContext *rosc = cls;
+ struct PostgresClosure *pg = rosc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_BlindedCoinHashP h_blind_ev;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct GNUNET_TIME_Timestamp execution_date;
+ struct TALER_Amount amount_with_fee;
+ uint64_t rowid;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
+ &h_blind_ev),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &reserve_sig),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &execution_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount_with_fee),
+ GNUNET_PQ_result_spec_uint64 ("reserve_out_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ rosc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = rosc->cb (rosc->cb_cls,
+ rowid,
+ &h_blind_ev,
+ &denom_pub,
+ &reserve_pub,
+ &reserve_sig,
+ execution_date,
+ &amount_with_fee);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_withdrawals_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_WithdrawCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct ReservesOutSerialContext rosc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Fetch deposits with rowid '\geq' the given parameter */
+ PREPARE (pg,
+ "audit_get_reserves_out_incr",
+ "SELECT"
+ " h_blind_ev"
+ ",denom.denom_pub"
+ ",reserve_sig"
+ ",reserves.reserve_pub"
+ ",execution_date"
+ ",amount_with_fee"
+ ",reserve_out_serial_id"
+ " FROM reserves_out"
+ " JOIN reserves"
+ " USING (reserve_uuid)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE reserve_out_serial_id>=$1"
+ " ORDER BY reserve_out_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_reserves_out_incr",
+ params,
+ &reserves_out_serial_helper_cb,
+ &rosc);
+ if (GNUNET_OK != rosc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_withdrawals_above_serial_id.h b/src/exchangedb/pg_select_withdrawals_above_serial_id.h
new file mode 100644
index 000000000..2b741a3b4
--- /dev/null
+++ b/src/exchangedb/pg_select_withdrawals_above_serial_id.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_select_withdrawals_above_serial_id.h
+ * @brief implementation of the select_withdrawals_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_WITHDRAWALS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_WITHDRAWALS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select withdraw operations from reserves_out above @a serial_id
+ * in monotonically increasing order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_withdrawals_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_WithdrawCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_set_extension_manifest.c b/src/exchangedb/pg_set_extension_manifest.c
new file mode 100644
index 000000000..c7db04312
--- /dev/null
+++ b/src/exchangedb/pg_set_extension_manifest.c
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_set_extension_manifest.c
+ * @brief Implementation of the set_extension_manifest function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_set_extension_manifest.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_set_extension_manifest (void *cls,
+ const char *extension_name,
+ const char *manifest)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam pcfg =
+ (NULL == manifest || 0 == *manifest)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (manifest);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (extension_name),
+ pcfg,
+ GNUNET_PQ_query_param_end
+ };
+
+
+ PREPARE (pg,
+ "set_extension_manifest",
+ "INSERT INTO extensions (name, manifest) VALUES ($1, $2) "
+ "ON CONFLICT (name) "
+ "DO UPDATE SET manifest=$2");
+
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "set_extension_manifest",
+ params);
+}
diff --git a/src/exchangedb/pg_set_extension_manifest.h b/src/exchangedb/pg_set_extension_manifest.h
new file mode 100644
index 000000000..0befeedd8
--- /dev/null
+++ b/src/exchangedb/pg_set_extension_manifest.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_set_extension_manifest.h
+ * @brief implementation of the set_extension_manifest function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SET_EXTENSION_MANIFEST_H
+#define PG_SET_EXTENSION_MANIFEST_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to save the manifest of an extension
+ * (age-restriction, policy_extension_...) After successful storage of the
+ * configuration it triggers the corresponding event.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param extension_name the name of the extension
+ * @param manifest JSON object of the configuration as string
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_set_extension_manifest (void *cls,
+ const char *extension_name,
+ const char *manifest);
+
+#endif
diff --git a/src/exchangedb/pg_set_purse_balance.c b/src/exchangedb/pg_set_purse_balance.c
new file mode 100644
index 000000000..1e34ea6ed
--- /dev/null
+++ b/src/exchangedb/pg_set_purse_balance.c
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_set_purse_balance.c
+ * @brief Implementation of the set_purse_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_set_purse_balance.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_set_purse_balance (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ TALER_PQ_query_param_amount (pg->conn,
+ balance),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "set_purse_balance",
+ "UPDATE purse_requests"
+ " SET balance=$2"
+ " WHERE purse_pub=$1;");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "set_purse_balance",
+ params);
+}
diff --git a/src/exchangedb/pg_set_purse_balance.h b/src/exchangedb/pg_set_purse_balance.h
new file mode 100644
index 000000000..44b765568
--- /dev/null
+++ b/src/exchangedb/pg_set_purse_balance.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_set_purse_balance.h
+ * @brief implementation of the set_purse_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SET_PURSE_BALANCE_H
+#define PG_SET_PURSE_BALANCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Set the current @a balance in the purse
+ * identified by @a purse_pub. Used by the auditor
+ * to update the balance as calculated by the auditor.
+ *
+ * @param cls closure
+ * @param purse_pub public key of a purse
+ * @param balance new balance to store under the purse
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_set_purse_balance (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance);
+
+#endif
diff --git a/src/exchangedb/pg_setup_wire_target.c b/src/exchangedb/pg_setup_wire_target.c
new file mode 100644
index 000000000..ed6fbe338
--- /dev/null
+++ b/src/exchangedb/pg_setup_wire_target.c
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_setup_wire_target.c
+ * @brief Implementation of the setup_wire_target function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_setup_wire_target.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_setup_wire_target (
+ struct PostgresClosure *pg,
+ const char *payto_uri,
+ struct TALER_PaytoHashP *h_payto)
+{
+ struct GNUNET_PQ_QueryParam iparams[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_end
+ };
+
+ TALER_payto_hash (payto_uri,
+ h_payto);
+
+ PREPARE (pg,
+ "insert_kyc_status",
+ "INSERT INTO wire_targets"
+ " (wire_target_h_payto"
+ " ,payto_uri"
+ " ) VALUES "
+ " ($1, $2)"
+ " ON CONFLICT DO NOTHING");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_kyc_status",
+ iparams);
+}
diff --git a/src/exchangedb/pg_setup_wire_target.h b/src/exchangedb/pg_setup_wire_target.h
new file mode 100644
index 000000000..77512a600
--- /dev/null
+++ b/src/exchangedb/pg_setup_wire_target.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_setup_wire_target.h
+ * @brief implementation of the setup_wire_target function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SETUP_WIRE_TARGET_H
+#define PG_SETUP_WIRE_TARGET_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "pg_helper.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Setup new wire target for @a payto_uri.
+ *
+ * @param pg the plugin-specific state
+ * @param payto_uri the payto URI to check
+ * @param[out] h_payto set to the hash of @a payto_uri
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_setup_wire_target (
+ struct PostgresClosure *pg,
+ const char *payto_uri,
+ struct TALER_PaytoHashP *h_payto);
+
+#endif
diff --git a/src/exchangedb/pg_start.c b/src/exchangedb/pg_start.c
new file mode 100644
index 000000000..de5d698f6
--- /dev/null
+++ b/src/exchangedb/pg_start.c
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_start.c
+ * @brief Implementation of the start function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_preflight.h"
+#include "pg_start.h"
+#include "pg_helper.h"
+
+enum GNUNET_GenericReturnValue
+TEH_PG_start (void *cls,
+ const char *name)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ GNUNET_assert (NULL != name);
+ if (GNUNET_SYSERR ==
+ TEH_PG_preflight (pg))
+ return GNUNET_SYSERR;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting transaction `%s'\n",
+ name);
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR ("Failed to start transaction\n");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ pg->transaction_name = name;
+ return GNUNET_OK;
+}
diff --git a/src/exchangedb/pg_start.h b/src/exchangedb/pg_start.h
new file mode 100644
index 000000000..0a3bb9e43
--- /dev/null
+++ b/src/exchangedb/pg_start.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_start.h
+ * @brief implementation of the start function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_START_H
+#define PG_START_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Start a transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging)
+ * must point to a constant
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_start (void *cls,
+ const char *name);
+
+#endif
diff --git a/src/exchangedb/pg_start_deferred_wire_out.c b/src/exchangedb/pg_start_deferred_wire_out.c
new file mode 100644
index 000000000..abdc16028
--- /dev/null
+++ b/src/exchangedb/pg_start_deferred_wire_out.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_start_deferred_wire_out.c
+ * @brief Implementation of the start_deferred_wire_out function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_start_deferred_wire_out.h"
+#include "pg_helper.h"
+#include "pg_preflight.h"
+#include "pg_rollback.h"
+
+enum GNUNET_GenericReturnValue
+TEH_PG_start_deferred_wire_out (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute (
+ "START TRANSACTION ISOLATION LEVEL READ COMMITTED;"),
+ GNUNET_PQ_make_execute ("SET CONSTRAINTS ALL DEFERRED;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ if (GNUNET_SYSERR ==
+ TEH_PG_preflight (pg))
+ return GNUNET_SYSERR;
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR (
+ "Failed to defer wire_out_ref constraint on transaction\n");
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return GNUNET_SYSERR;
+ }
+ pg->transaction_name = "deferred wire out";
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting READ COMMITTED DEFERRED transaction `%s'\n",
+ pg->transaction_name);
+ return GNUNET_OK;
+}
diff --git a/src/exchangedb/pg_start_deferred_wire_out.h b/src/exchangedb/pg_start_deferred_wire_out.h
new file mode 100644
index 000000000..ed444ef70
--- /dev/null
+++ b/src/exchangedb/pg_start_deferred_wire_out.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_start_deferred_wire_out.h
+ * @brief implementation of the start_deferred_wire_out function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_START_DEFERRED_WIRE_OUT_H
+#define PG_START_DEFERRED_WIRE_OUT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Starts a READ COMMITTED transaction where we transiently violate the foreign
+ * constraints on the "wire_out" table as we insert aggregations
+ * and only add the wire transfer out at the end.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_start_deferred_wire_out (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_start_read_committed.c b/src/exchangedb/pg_start_read_committed.c
new file mode 100644
index 000000000..1c8248ab0
--- /dev/null
+++ b/src/exchangedb/pg_start_read_committed.c
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_start_read_committed.c
+ * @brief Implementation of the start_read_committed function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_start_read_committed.h"
+#include "pg_preflight.h"
+#include "pg_helper.h"
+
+enum GNUNET_GenericReturnValue
+TEH_PG_start_read_committed (void *cls,
+ const char *name)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL READ COMMITTED"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ GNUNET_assert (NULL != name);
+ if (GNUNET_SYSERR ==
+ TEH_PG_preflight (pg))
+ return GNUNET_SYSERR;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting READ COMMITTED transaction `%s`\n",
+ name);
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR ("Failed to start transaction\n");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ pg->transaction_name = name;
+ return GNUNET_OK;
+}
diff --git a/src/exchangedb/pg_start_read_committed.h b/src/exchangedb/pg_start_read_committed.h
new file mode 100644
index 000000000..08b60e688
--- /dev/null
+++ b/src/exchangedb/pg_start_read_committed.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_start_read_committed.h
+ * @brief implementation of the start_read_committed function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_START_READ_COMMITTED_H
+#define PG_START_READ_COMMITTED_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Start a READ COMMITTED transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging)
+ * must point to a constant
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_start_read_committed (void *cls,
+ const char *name);
+
+#endif
diff --git a/src/exchangedb/pg_start_read_only.c b/src/exchangedb/pg_start_read_only.c
new file mode 100644
index 000000000..741d94ba1
--- /dev/null
+++ b/src/exchangedb/pg_start_read_only.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_start_read_only.c
+ * @brief Implementation of the start_read_only function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_start_read_only.h"
+#include "pg_preflight.h"
+#include "pg_helper.h"
+
+enum GNUNET_GenericReturnValue
+TEH_PG_start_read_only (void *cls,
+ const char *name)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute (
+ "START TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ GNUNET_assert (NULL != name);
+ if (GNUNET_SYSERR ==
+ TEH_PG_preflight (pg))
+ return GNUNET_SYSERR;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting READ ONLY transaction `%s`\n",
+ name);
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR ("Failed to start transaction\n");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ pg->transaction_name = name;
+ return GNUNET_OK;
+}
diff --git a/src/exchangedb/pg_start_read_only.h b/src/exchangedb/pg_start_read_only.h
new file mode 100644
index 000000000..bf639c19b
--- /dev/null
+++ b/src/exchangedb/pg_start_read_only.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_start_read_only.h
+ * @brief implementation of the start_read_only function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_START_READ_ONLY_H
+#define PG_START_READ_ONLY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Start a READ ONLY serializable transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging)
+ * must point to a constant
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_start_read_only (void *cls,
+ const char *name);
+
+#endif
diff --git a/src/exchangedb/pg_store_wire_transfer_out.c b/src/exchangedb/pg_store_wire_transfer_out.c
new file mode 100644
index 000000000..337dc5855
--- /dev/null
+++ b/src/exchangedb/pg_store_wire_transfer_out.c
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_store_wire_transfer_out.c
+ * @brief Implementation of the store_wire_transfer_out function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_store_wire_transfer_out.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_store_wire_transfer_out (
+ void *cls,
+ struct GNUNET_TIME_Timestamp date,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *exchange_account_section,
+ const struct TALER_Amount *amount)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&date),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_string (exchange_account_section),
+ TALER_PQ_query_param_amount (pg->conn,
+ amount),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_wire_out",
+ "INSERT INTO wire_out "
+ "(execution_date"
+ ",wtid_raw"
+ ",wire_target_h_payto"
+ ",exchange_account_section"
+ ",amount"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_wire_out",
+ params);
+}
diff --git a/src/exchangedb/pg_store_wire_transfer_out.h b/src/exchangedb/pg_store_wire_transfer_out.h
new file mode 100644
index 000000000..79950e65a
--- /dev/null
+++ b/src/exchangedb/pg_store_wire_transfer_out.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_store_wire_transfer_out.h
+ * @brief implementation of the store_wire_transfer_out function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_STORE_WIRE_TRANSFER_OUT_H
+#define PG_STORE_WIRE_TRANSFER_OUT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Store information about an outgoing wire transfer that was executed.
+ *
+ * @param cls closure
+ * @param date time of the wire transfer
+ * @param wtid subject of the wire transfer
+ * @param h_payto identifies the receiver account of the wire transfer
+ * @param exchange_account_section configuration section of the exchange specifying the
+ * exchange's bank account being used
+ * @param amount amount that was transmitted
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_store_wire_transfer_out (
+ void *cls,
+ struct GNUNET_TIME_Timestamp date,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *exchange_account_section,
+ const struct TALER_Amount *amount);
+
+#endif
diff --git a/src/exchangedb/pg_template.c b/src/exchangedb/pg_template.c
new file mode 100644
index 000000000..69cd45035
--- /dev/null
+++ b/src/exchangedb/pg_template.c
@@ -0,0 +1,26 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 exchangedb/pg_template.c
+ * @brief Implementation of the template function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_template.h"
+#include "pg_helper.h"
diff --git a/src/exchangedb/pg_template.h b/src/exchangedb/pg_template.h
new file mode 100644
index 000000000..d858689fb
--- /dev/null
+++ b/src/exchangedb/pg_template.h
@@ -0,0 +1,29 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 exchangedb/pg_template.h
+ * @brief implementation of the template function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_TEMPLATE_H
+#define PG_TEMPLATE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+#endif
diff --git a/src/exchangedb/pg_template.sh b/src/exchangedb/pg_template.sh
new file mode 100755
index 000000000..73bd7e989
--- /dev/null
+++ b/src/exchangedb/pg_template.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# This file is in the public domain.
+#
+# Instantiates pg_template for a particular function.
+
+for n in $*
+do
+ NCAPS=`echo $n | tr a-z A-Z`
+ if test ! -e pg_$n.c
+ then
+ cat pg_template.c | sed -e s/template/$n/g -e s/TEMPLATE/$NCAPS/g > pg_$n.c
+ cat pg_template.h | sed -e s/template/$n/g -e s/TEMPLATE/$NCAPS/g > pg_$n.h
+ echo " plugin->$n\n = &TEH_PG_$n;" >> tmpl.c
+ echo "#include \"pg_$n.h\"" >> tmpl.inc
+ echo " pg_$n.h pg_$n.c \\" >> tmpl.am
+ fi
+done
+
+echo "Add lines from tmpl.am to Makefile.am"
+echo "Add lines from tmpl.inc to plugin_exchangedb_postgres.c at the beginning"
+echo "Add lines from tmpl.c to plugin_exchangedb_postgres.c at the end"
diff --git a/src/exchangedb/pg_test_aml_officer.c b/src/exchangedb/pg_test_aml_officer.c
new file mode 100644
index 000000000..b00828244
--- /dev/null
+++ b/src/exchangedb/pg_test_aml_officer.c
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_test_aml_officer.c
+ * @brief Implementation of the test_aml_officer function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_test_aml_officer.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_test_aml_officer (
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (decider_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "test_aml_staff",
+ "SELECT 1 FROM aml_staff"
+ " WHERE decider_pub=$1"
+ " AND is_active;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "test_aml_staff",
+ params);
+}
diff --git a/src/exchangedb/pg_test_aml_officer.h b/src/exchangedb/pg_test_aml_officer.h
new file mode 100644
index 000000000..e034007bd
--- /dev/null
+++ b/src/exchangedb/pg_test_aml_officer.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_test_aml_officer.h
+ * @brief implementation of the test_aml_officer function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_TEST_AML_OFFICER_H
+#define PG_TEST_AML_OFFICER_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Test if the given AML staff member is active
+ * (at least read-only).
+ *
+ * @param cls closure
+ * @param decider_pub public key of the staff member
+ * @return database transaction status, if member is unknown or not active, 1 if member is active
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_test_aml_officer (
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub);
+
+
+#endif
diff --git a/src/exchangedb/pg_trigger_aml_process.c b/src/exchangedb/pg_trigger_aml_process.c
new file mode 100644
index 000000000..7534fe3df
--- /dev/null
+++ b/src/exchangedb/pg_trigger_aml_process.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_trigger_aml_process.c
+ * @brief Implementation of the trigger_aml_process function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_trigger_aml_process.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_trigger_aml_process (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_Amount *threshold_crossed)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ TALER_PQ_query_param_amount (pg->conn,
+ threshold_crossed),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "trigger_aml_process",
+ "INSERT INTO aml_status"
+ "(h_payto"
+ ",threshold"
+ ",status)"
+ " VALUES"
+ " ($1, $2, 1)" // 1: decision needed
+ " ON CONFLICT (h_payto) DO"
+ " UPDATE SET"
+ " threshold=$2"
+ " ,status=aml_status.status | 1;"); // do not clear 'frozen' status
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "trigger_aml_process",
+ params);
+}
diff --git a/src/exchangedb/pg_trigger_aml_process.h b/src/exchangedb/pg_trigger_aml_process.h
new file mode 100644
index 000000000..2283571af
--- /dev/null
+++ b/src/exchangedb/pg_trigger_aml_process.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_trigger_aml_process.h
+ * @brief implementation of the trigger_aml_process function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_TRIGGER_AML_PROCESS_H
+#define PG_TRIGGER_AML_PROCESS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Trigger AML process, an account has crossed the threshold. Inserts or
+ * updates the AML status.
+ *
+ * @param cls closure
+ * @param h_payto account for which the attribute data is stored
+ * @param threshold_crossed existing threshold that was crossed
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_trigger_aml_process (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_Amount *threshold_crossed);
+
+
+#endif
diff --git a/src/exchangedb/pg_update_aggregation_transient.c b/src/exchangedb/pg_update_aggregation_transient.c
new file mode 100644
index 000000000..38b65316e
--- /dev/null
+++ b/src/exchangedb/pg_update_aggregation_transient.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_update_aggregation_transient.c
+ * @brief Implementation of the update_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_aggregation_transient.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t kyc_requirement_row,
+ const struct TALER_Amount *total)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ total),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_uint64 (&kyc_requirement_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "update_aggregation_transient",
+ "UPDATE aggregation_transient"
+ " SET amount=$1"
+ " ,legitimization_requirement_serial_id=$4"
+ " WHERE wire_target_h_payto=$2"
+ " AND wtid_raw=$3");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_aggregation_transient",
+ params);
+}
diff --git a/src/exchangedb/pg_update_aggregation_transient.h b/src/exchangedb/pg_update_aggregation_transient.h
new file mode 100644
index 000000000..c444e85bb
--- /dev/null
+++ b/src/exchangedb/pg_update_aggregation_transient.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_update_aggregation_transient.h
+ * @brief implementation of the update_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_AGGREGATION_TRANSIENT_H
+#define PG_UPDATE_AGGREGATION_TRANSIENT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Update existing entry in the transient aggregation table.
+ * @a h_payto is only needed for query performance.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto destination of the wire transfer
+ * @param wtid the raw wire transfer identifier to update
+ * @param kyc_requirement_row row in legitimization_requirements that need to be satisfied to continue, or 0 for none
+ * @param total new total amount to be wired in the future
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t kyc_requirement_row,
+ const struct TALER_Amount *total);
+
+#endif
diff --git a/src/exchangedb/pg_update_auditor.c b/src/exchangedb/pg_update_auditor.c
new file mode 100644
index 000000000..167a270b9
--- /dev/null
+++ b/src/exchangedb/pg_update_auditor.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_update_auditor.c
+ * @brief Implementation of the update_auditor function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_auditor.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_auditor (void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp change_date,
+ bool enabled)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (auditor_pub),
+ GNUNET_PQ_query_param_string (auditor_url),
+ GNUNET_PQ_query_param_string (auditor_name),
+ GNUNET_PQ_query_param_bool (enabled),
+ GNUNET_PQ_query_param_timestamp (&change_date),
+ GNUNET_PQ_query_param_end
+ };
+ /* used in #postgres_update_auditor() */
+ PREPARE (pg,
+ "update_auditor",
+ "UPDATE auditors"
+ " SET"
+ " auditor_url=$2"
+ " ,auditor_name=$3"
+ " ,is_active=$4"
+ " ,last_change=$5"
+ " WHERE auditor_pub=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_auditor",
+ params);
+}
diff --git a/src/exchangedb/pg_update_auditor.h b/src/exchangedb/pg_update_auditor.h
new file mode 100644
index 000000000..ee869f8b7
--- /dev/null
+++ b/src/exchangedb/pg_update_auditor.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_update_auditor.h
+ * @brief implementation of the update_auditor function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_AUDITOR_H
+#define PG_UPDATE_AUDITOR_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Update information about an auditor that will audit this exchange.
+ *
+ * @param cls closure
+ * @param auditor_pub key of the auditor (primary key for the existing record)
+ * @param auditor_url base URL of the auditor's REST service, to be updated
+ * @param auditor_name name of the auditor (for humans)
+ * @param change_date date when the auditor status was last changed
+ * (only to be used for replay detection)
+ * @param enabled true to enable, false to disable
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_auditor (void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp change_date,
+ bool enabled);
+
+#endif
diff --git a/src/exchangedb/pg_update_kyc_process_by_row.c b/src/exchangedb/pg_update_kyc_process_by_row.c
new file mode 100644
index 000000000..c339436a8
--- /dev/null
+++ b/src/exchangedb/pg_update_kyc_process_by_row.c
@@ -0,0 +1,122 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_update_kyc_process_by_row.c
+ * @brief Implementation of the update_kyc_process_by_row function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_kyc_process_by_row.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_kyc_process_by_row (
+ void *cls,
+ uint64_t process_row,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ const char *redirect_url,
+ struct GNUNET_TIME_Absolute expiration)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&process_row),
+ GNUNET_PQ_query_param_string (provider_section),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ (NULL != provider_account_id)
+ ? GNUNET_PQ_query_param_string (provider_account_id)
+ : GNUNET_PQ_query_param_null (),
+ (NULL != provider_legitimization_id)
+ ? GNUNET_PQ_query_param_string (provider_legitimization_id)
+ : GNUNET_PQ_query_param_null (),
+ (NULL != redirect_url)
+ ? GNUNET_PQ_query_param_string (redirect_url)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_absolute_time (&expiration),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Updating KYC data for %llu (%s)\n",
+ (unsigned long long) process_row,
+ provider_section);
+ PREPARE (pg,
+ "update_legitimization_process",
+ "UPDATE legitimization_processes"
+ " SET provider_user_id=$4"
+ " ,provider_legitimization_id=$5"
+ " ,redirect_url=$6"
+ " ,expiration_time=GREATEST(expiration_time,$7)"
+ " WHERE"
+ " h_payto=$3"
+ " AND legitimization_process_serial_id=$1"
+ " AND provider_section=$2;");
+ qs = GNUNET_PQ_eval_prepared_non_select (
+ pg->conn,
+ "update_legitimization_process",
+ params);
+ if (qs <= 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to update legitimization process %llu: %d\n",
+ (unsigned long long) process_row,
+ qs);
+ return qs;
+ }
+ if (GNUNET_TIME_absolute_is_future (expiration))
+ {
+ enum GNUNET_DB_QueryStatus qs2;
+ struct TALER_KycCompletedEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
+ .h_payto = *h_payto
+ };
+ uint32_t trigger_type = 1;
+ struct GNUNET_PQ_QueryParam params2[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_uint32 (&trigger_type),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_PQ_event_notify (pg->conn,
+ &rep.header,
+ NULL,
+ 0);
+ PREPARE (pg,
+ "alert_kyc_status_change",
+ "INSERT INTO kyc_alerts"
+ " (h_payto"
+ " ,trigger_type)"
+ " VALUES"
+ " ($1,$2);");
+ qs2 = GNUNET_PQ_eval_prepared_non_select (
+ pg->conn,
+ "alert_kyc_status_change",
+ params2);
+ if (qs2 < 0)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to store KYC alert: %d\n",
+ qs2);
+ }
+ return qs;
+}
diff --git a/src/exchangedb/pg_update_kyc_process_by_row.h b/src/exchangedb/pg_update_kyc_process_by_row.h
new file mode 100644
index 000000000..7ef5285e9
--- /dev/null
+++ b/src/exchangedb/pg_update_kyc_process_by_row.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_update_kyc_process_by_row.h
+ * @brief implementation of the update_kyc_process_by_row function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_KYC_PROCESS_BY_ROW_H
+#define PG_UPDATE_KYC_PROCESS_BY_ROW_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Update KYC requirement check with provider-linkage and/or
+ * expiration data.
+ *
+ * @param cls closure
+ * @param process_row row to select by
+ * @param provider_section provider that must be checked (technically redundant)
+ * @param h_payto account that must be KYC'ed (helps access by shard, otherwise also redundant)
+ * @param provider_account_id provider account ID
+ * @param provider_legitimization_id provider legitimization ID
+ * @param redirect_url where the user should be redirected to start the KYC process
+ * @param expiration how long is this KYC check set to be valid (in the past if invalid)
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_kyc_process_by_row (
+ void *cls,
+ uint64_t process_row,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ const char *redirect_url,
+ struct GNUNET_TIME_Absolute expiration);
+
+#endif
diff --git a/src/exchangedb/pg_update_wire.c b/src/exchangedb/pg_update_wire.c
new file mode 100644
index 000000000..5c4bb9045
--- /dev/null
+++ b/src/exchangedb/pg_update_wire.c
@@ -0,0 +1,81 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023, 2024 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 exchangedb/pg_update_wire.c
+ * @brief Implementation of the update_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_wire.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_wire (void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp change_date,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority,
+ bool enabled)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_bool (enabled),
+ NULL == conversion_url
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (conversion_url),
+ enabled
+ ? TALER_PQ_query_param_json (debit_restrictions)
+ : GNUNET_PQ_query_param_null (),
+ enabled
+ ? TALER_PQ_query_param_json (credit_restrictions)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_timestamp (&change_date),
+ NULL == master_sig
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (master_sig),
+ NULL == bank_label
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (bank_label),
+ GNUNET_PQ_query_param_int64 (&priority),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "update_wire",
+ "UPDATE wire_accounts"
+ " SET"
+ " is_active=$2"
+ " ,conversion_url=$3"
+ " ,debit_restrictions=$4"
+ " ,credit_restrictions=$5"
+ " ,last_change=$6"
+ " ,master_sig=$7"
+ " ,bank_label=$8"
+ " ,priority=$9"
+ " WHERE payto_uri=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_wire",
+ params);
+}
diff --git a/src/exchangedb/pg_update_wire.h b/src/exchangedb/pg_update_wire.h
new file mode 100644
index 000000000..a596a0802
--- /dev/null
+++ b/src/exchangedb/pg_update_wire.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_update_wire.h
+ * @brief implementation of the update_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_WIRE_H
+#define PG_UPDATE_WIRE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Update information about a wire account of the exchange.
+ *
+ * @param cls closure
+ * @param payto_uri account the update is about
+ * @param conversion_url URL of a conversion service, NULL if there is no conversion
+ * @param debit_restrictions JSON array with debit restrictions on the account; NULL allowed if not @a enabled
+ * @param credit_restrictions JSON array with credit restrictions on the account; NULL allowed if not @a enabled
+ * @param change_date date when the account status was last changed
+ * (only to be used for replay detection)
+ * @param master_sig master signature to store, can be NULL (if @a enabled is false)
+ * @param bank_label label to show this entry under in the UI, can be NULL
+ * @param priority determines order in which entries are shown in the UI
+ * @param enabled true to enable, false to disable (the actual change)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_wire (void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp change_date,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority,
+ bool enabled);
+
+#endif
diff --git a/src/exchangedb/pg_wire_prepare_data_get.c b/src/exchangedb/pg_wire_prepare_data_get.c
new file mode 100644
index 000000000..0cc57e41f
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_get.c
@@ -0,0 +1,140 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_get.c
+ * @brief Implementation of the wire_prepare_data_get function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_wire_prepare_data_get.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #prewire_cb().
+ */
+struct PrewireContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_WirePreparationIterator cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * #GNUNET_OK if everything went fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct MissingWireContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+prewire_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct PrewireContext *pc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t prewire_uuid;
+ char *wire_method;
+ void *buf = NULL;
+ size_t buf_size;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("prewire_uuid",
+ &prewire_uuid),
+ GNUNET_PQ_result_spec_string ("wire_method",
+ &wire_method),
+ GNUNET_PQ_result_spec_variable_size ("buf",
+ &buf,
+ &buf_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ pc->status = GNUNET_SYSERR;
+ return;
+ }
+ pc->cb (pc->cb_cls,
+ prewire_uuid,
+ wire_method,
+ buf,
+ buf_size);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_get (void *cls,
+ uint64_t start_row,
+ uint64_t limit,
+ TALER_EXCHANGEDB_WirePreparationIterator cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&start_row),
+ GNUNET_PQ_query_param_uint64 (&limit),
+ GNUNET_PQ_query_param_end
+ };
+ struct PrewireContext pc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "wire_prepare_data_get",
+ "SELECT"
+ " prewire_uuid"
+ ",wire_method"
+ ",buf"
+ " FROM prewire"
+ " WHERE prewire_uuid >= $1"
+ " AND finished=FALSE"
+ " AND failed=FALSE"
+ " ORDER BY prewire_uuid ASC"
+ " LIMIT $2;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "wire_prepare_data_get",
+ params,
+ &prewire_cb,
+ &pc);
+ if (GNUNET_OK != pc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_wire_prepare_data_get.h b/src/exchangedb/pg_wire_prepare_data_get.h
new file mode 100644
index 000000000..815c14bdf
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_get.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_get.h
+ * @brief implementation of the wire_prepare_data_get function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_WIRE_PREPARE_DATA_GET_H
+#define PG_WIRE_PREPARE_DATA_GET_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to get an unfinished wire transfer
+ * preparation data. Fetches at most one item.
+ *
+ * @param cls closure
+ * @param start_row offset to query table at
+ * @param limit maximum number of results to return
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_get (void *cls,
+ uint64_t start_row,
+ uint64_t limit,
+ TALER_EXCHANGEDB_WirePreparationIterator cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_wire_prepare_data_insert.c b/src/exchangedb/pg_wire_prepare_data_insert.c
new file mode 100644
index 000000000..919fccdaf
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_insert.c
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_insert.c
+ * @brief Implementation of the wire_prepare_data_insert function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_wire_prepare_data_insert.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_insert (void *cls,
+ const char *type,
+ const char *buf,
+ size_t buf_size)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (type),
+ GNUNET_PQ_query_param_fixed_size (buf, buf_size),
+ GNUNET_PQ_query_param_end
+ };
+
+
+ /* Used in #postgres_wire_prepare_data_insert() to store
+ wire transfer information before actually committing it with the bank */
+ PREPARE (pg,
+ "wire_prepare_data_insert",
+ "INSERT INTO prewire "
+ "(wire_method"
+ ",buf"
+ ") VALUES "
+ "($1, $2);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "wire_prepare_data_insert",
+ params);
+}
diff --git a/src/exchangedb/pg_wire_prepare_data_insert.h b/src/exchangedb/pg_wire_prepare_data_insert.h
new file mode 100644
index 000000000..e73ee152d
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_insert.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_insert.h
+ * @brief implementation of the wire_prepare_data_insert function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_WIRE_PREPARE_DATA_INSERT_H
+#define PG_WIRE_PREPARE_DATA_INSERT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to insert wire transfer commit data into the DB.
+ *
+ * @param cls closure
+ * @param type type of the wire transfer (i.e. "iban")
+ * @param buf buffer with wire transfer preparation data
+ * @param buf_size number of bytes in @a buf
+ * @return query status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_insert (void *cls,
+ const char *type,
+ const char *buf,
+ size_t buf_size);
+
+#endif
diff --git a/src/exchangedb/pg_wire_prepare_data_mark_failed.c b/src/exchangedb/pg_wire_prepare_data_mark_failed.c
new file mode 100644
index 000000000..1d46c84d4
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_mark_failed.c
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_mark_failed.c
+ * @brief Implementation of the wire_prepare_data_mark_failed function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_wire_prepare_data_mark_failed.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_mark_failed (
+ void *cls,
+ uint64_t rowid)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&rowid),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "wire_prepare_data_mark_failed",
+ "UPDATE prewire"
+ " SET failed=TRUE"
+ " WHERE prewire_uuid=$1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "wire_prepare_data_mark_failed",
+ params);
+}
diff --git a/src/exchangedb/pg_wire_prepare_data_mark_failed.h b/src/exchangedb/pg_wire_prepare_data_mark_failed.h
new file mode 100644
index 000000000..98846b284
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_mark_failed.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_mark_failed.h
+ * @brief implementation of the wire_prepare_data_mark_failed function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_WIRE_PREPARE_DATA_MARK_FAILED_H
+#define PG_WIRE_PREPARE_DATA_MARK_FAILED_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to mark wire transfer commit data as failed.
+ *
+ * @param cls closure
+ * @param rowid which entry to mark as failed
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_mark_failed (
+ void *cls,
+ uint64_t rowid);
+
+#endif
diff --git a/src/exchangedb/pg_wire_prepare_data_mark_finished.c b/src/exchangedb/pg_wire_prepare_data_mark_finished.c
new file mode 100644
index 000000000..998b9d731
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_mark_finished.c
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_mark_finished.c
+ * @brief Implementation of the wire_prepare_data_mark_finished function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_wire_prepare_data_mark_finished.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_mark_finished (
+ void *cls,
+ uint64_t rowid)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&rowid),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "wire_prepare_data_mark_done",
+ "UPDATE prewire"
+ " SET finished=TRUE"
+ " WHERE prewire_uuid=$1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "wire_prepare_data_mark_done",
+ params);
+}
diff --git a/src/exchangedb/pg_wire_prepare_data_mark_finished.h b/src/exchangedb/pg_wire_prepare_data_mark_finished.h
new file mode 100644
index 000000000..ba2e384cd
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_mark_finished.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_mark_finished.h
+ * @brief implementation of the wire_prepare_data_mark_finished function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_WIRE_PREPARE_DATA_MARK_FINISHED_H
+#define PG_WIRE_PREPARE_DATA_MARK_FINISHED_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to mark wire transfer commit data as finished.
+ *
+ * @param cls closure
+ * @param rowid which entry to mark as finished
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_mark_finished (
+ void *cls,
+ uint64_t rowid);
+
+#endif
diff --git a/src/exchangedb/plugin_exchangedb_common.c b/src/exchangedb/plugin_exchangedb_common.c
index ce425f824..562710eaa 100644
--- a/src/exchangedb/plugin_exchangedb_common.c
+++ b/src/exchangedb/plugin_exchangedb_common.c
@@ -19,16 +19,14 @@
* included in each plugin.
* @author Christian Grothoff
*/
+#include "platform.h"
+#include "plugin_exchangedb_common.h"
-/**
- * Free memory associated with the given reserve history.
- *
- * @param cls the @e cls of this struct with the plugin-specific state (unused)
- * @param rh history to free.
- */
-static void
-common_free_reserve_history (void *cls,
- struct TALER_EXCHANGEDB_ReserveHistory *rh)
+
+void
+TEH_COMMON_free_reserve_history (
+ void *cls,
+ struct TALER_EXCHANGEDB_ReserveHistory *rh)
{
(void) cls;
while (NULL != rh)
@@ -40,8 +38,7 @@ common_free_reserve_history (void *cls,
struct TALER_EXCHANGEDB_BankTransfer *bt;
bt = rh->details.bank;
- GNUNET_free_non_null (bt->sender_account_details);
- GNUNET_free_non_null (bt->wire_reference);
+ GNUNET_free (bt->sender_account_details);
GNUNET_free (bt);
break;
}
@@ -50,7 +47,7 @@ common_free_reserve_history (void *cls,
struct TALER_EXCHANGEDB_CollectableBlindcoin *cbc;
cbc = rh->details.withdraw;
- GNUNET_CRYPTO_rsa_signature_free (cbc->sig.rsa_signature);
+ TALER_blinded_denom_sig_free (&cbc->sig);
GNUNET_free (cbc);
break;
}
@@ -59,7 +56,7 @@ common_free_reserve_history (void *cls,
struct TALER_EXCHANGEDB_Recoup *recoup;
recoup = rh->details.recoup;
- GNUNET_CRYPTO_rsa_signature_free (recoup->coin.denom_sig.rsa_signature);
+ TALER_denom_sig_free (&recoup->coin.denom_sig);
GNUNET_free (recoup);
break;
}
@@ -68,10 +65,42 @@ common_free_reserve_history (void *cls,
struct TALER_EXCHANGEDB_ClosingTransfer *closing;
closing = rh->details.closing;
- GNUNET_free_non_null (closing->receiver_account_details);
+ GNUNET_free (closing->receiver_account_details);
GNUNET_free (closing);
break;
}
+ case TALER_EXCHANGEDB_RO_PURSE_MERGE:
+ {
+ struct TALER_EXCHANGEDB_PurseMerge *merge;
+
+ merge = rh->details.merge;
+ GNUNET_free (merge);
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
+ {
+ struct TALER_EXCHANGEDB_HistoryRequest *history;
+
+ history = rh->details.history;
+ GNUNET_free (history);
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
+ {
+ struct TALER_EXCHANGEDB_OpenRequest *or;
+
+ or = rh->details.open_request;
+ GNUNET_free (or);
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
+ {
+ struct TALER_EXCHANGEDB_CloseRequest *cr;
+
+ cr = rh->details.close_request;
+ GNUNET_free (cr);
+ break;
+ }
}
{
struct TALER_EXCHANGEDB_ReserveHistory *next;
@@ -84,15 +113,10 @@ common_free_reserve_history (void *cls,
}
-/**
- * Free linked list of transactions.
- *
- * @param cls the @e cls of this struct with the plugin-specific state (unused)
- * @param tl list to free
- */
-static void
-common_free_coin_transaction_list (void *cls,
- struct TALER_EXCHANGEDB_TransactionList *tl)
+void
+TEH_COMMON_free_coin_transaction_list (
+ void *cls,
+ struct TALER_EXCHANGEDB_TransactionList *tl)
{
(void) cls;
while (NULL != tl)
@@ -104,8 +128,7 @@ common_free_coin_transaction_list (void *cls,
struct TALER_EXCHANGEDB_DepositListEntry *deposit;
deposit = tl->details.deposit;
- if (NULL != deposit->receiver_wire_account)
- json_decref (deposit->receiver_wire_account);
+ GNUNET_free (deposit->receiver_wire_account);
GNUNET_free (deposit);
break;
}
@@ -117,8 +140,7 @@ common_free_coin_transaction_list (void *cls,
struct TALER_EXCHANGEDB_RecoupRefreshListEntry *rr;
rr = tl->details.old_coin_recoup;
- if (NULL != rr->coin.denom_sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (rr->coin.denom_sig.rsa_signature);
+ TALER_denom_sig_free (&rr->coin.denom_sig);
GNUNET_free (rr);
break;
}
@@ -133,11 +155,35 @@ common_free_coin_transaction_list (void *cls,
struct TALER_EXCHANGEDB_RecoupRefreshListEntry *rr;
rr = tl->details.recoup_refresh;
- if (NULL != rr->coin.denom_sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (rr->coin.denom_sig.rsa_signature);
+ TALER_denom_sig_free (&rr->coin.denom_sig);
GNUNET_free (rr);
break;
}
+ case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
+ {
+ struct TALER_EXCHANGEDB_PurseDepositListEntry *deposit;
+
+ deposit = tl->details.purse_deposit;
+ GNUNET_free (deposit->exchange_base_url);
+ GNUNET_free (deposit);
+ break;
+ }
+ case TALER_EXCHANGEDB_TT_PURSE_REFUND:
+ {
+ struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund;
+
+ prefund = tl->details.purse_refund;
+ GNUNET_free (prefund);
+ break;
+ }
+ case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
+ {
+ struct TALER_EXCHANGEDB_ReserveOpenListEntry *role;
+
+ role = tl->details.reserve_open;
+ GNUNET_free (role);
+ break;
+ }
}
{
struct TALER_EXCHANGEDB_TransactionList *next;
diff --git a/src/exchangedb/plugin_exchangedb_common.h b/src/exchangedb/plugin_exchangedb_common.h
new file mode 100644
index 000000000..0355c44ab
--- /dev/null
+++ b/src/exchangedb/plugin_exchangedb_common.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file plugin_exchangedb_common.h
+ * @brief implementation of database-independent functions
+ * @author Christian Grothoff
+ */
+#ifndef PLUGIN_EXCHANGEDB_COMMON_H
+#define PLUGIN_EXCHANGEDB_COMMON_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Free memory associated with the given reserve history.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state (unused)
+ * @param[in] rh history to free.
+ */
+void
+TEH_COMMON_free_reserve_history (
+ void *cls,
+ struct TALER_EXCHANGEDB_ReserveHistory *rh);
+
+
+/**
+ * Free linked list of transactions.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state (unused)
+ * @param[in] tl list to free
+ */
+void
+TEH_COMMON_free_coin_transaction_list (
+ void *cls,
+ struct TALER_EXCHANGEDB_TransactionList *tl);
+
+#endif
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c
index a639b133d..108b55219 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -1,18 +1,18 @@
/*
- This file is part of TALER
- Copyright (C) 2014--2020 Taler Systems SA
+ This file is part of TALER
+ 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
- Foundation; either version 3, or (at your option) any later version.
+ 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.
+ 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/>
-*/
+ 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 plugin_exchangedb_postgres.c
@@ -21,49 +21,213 @@
* @author Christian Grothoff
* @author Sree Harsha Totakura
* @author Marcello Stanisci
+ * @author Özgür Kesim
*/
#include "platform.h"
+#include <poll.h>
+#include <pthread.h>
+#include <libpq-fe.h>
#include "taler_error_codes.h"
+#include "taler_dbevents.h"
#include "taler_pq_lib.h"
+#include "taler_util.h"
+#include "taler_json_lib.h"
#include "taler_exchangedb_plugin.h"
-#include <pthread.h>
-#include <libpq-fe.h>
-
-#include "plugin_exchangedb_common.c"
+#include "plugin_exchangedb_common.h"
+#include "pg_delete_aggregation_transient.h"
+#include "pg_get_link_data.h"
+#include "pg_helper.h"
+#include "pg_do_reserve_open.h"
+#include "pg_get_coin_transactions.h"
+#include "pg_get_expired_reserves.h"
+#include "pg_get_purse_request.h"
+#include "pg_get_reserve_history.h"
+#include "pg_get_unfinished_close_requests.h"
+#include "pg_insert_close_request.h"
+#include "pg_insert_records_by_table.h"
+#include "pg_insert_reserve_open_deposit.h"
+#include "pg_get_pending_kyc_requirement_process.h"
+#include "pg_iterate_kyc_reference.h"
+#include "pg_iterate_reserve_close_info.h"
+#include "pg_lookup_records_by_table.h"
+#include "pg_lookup_serial_by_table.h"
+#include "pg_select_account_merges_above_serial_id.h"
+#include "pg_select_aml_threshold.h"
+#include "pg_select_all_purse_decisions_above_serial_id.h"
+#include "pg_select_purse.h"
+#include "pg_select_purse_deposits_above_serial_id.h"
+#include "pg_select_purse_merges_above_serial_id.h"
+#include "pg_select_purse_requests_above_serial_id.h"
+#include "pg_select_reserve_close_info.h"
+#include "pg_select_reserve_closed_above_serial_id.h"
+#include "pg_select_reserve_open_above_serial_id.h"
+#include "pg_insert_purse_request.h"
+#include "pg_iterate_active_signkeys.h"
+#include "pg_preflight.h"
+#include "pg_commit.h"
+#include "pg_drop_tables.h"
+#include "pg_select_satisfied_kyc_processes.h"
+#include "pg_select_aggregation_amounts_for_kyc_check.h"
+#include "pg_kyc_provider_account_lookup.h"
+#include "pg_lookup_kyc_requirement_by_row.h"
+#include "pg_insert_kyc_requirement_for_account.h"
+#include "pg_lookup_kyc_process_by_account.h"
+#include "pg_update_kyc_process_by_row.h"
+#include "pg_insert_kyc_requirement_process.h"
+#include "pg_select_withdraw_amounts_for_kyc_check.h"
+#include "pg_select_merge_amounts_for_kyc_check.h"
+#include "pg_profit_drains_set_finished.h"
+#include "pg_profit_drains_get_pending.h"
+#include "pg_get_drain_profit.h"
+#include "pg_get_purse_deposit.h"
+#include "pg_insert_contract.h"
+#include "pg_insert_kyc_failure.h"
+#include "pg_select_contract.h"
+#include "pg_select_purse_merge.h"
+#include "pg_select_contract_by_purse.h"
+#include "pg_insert_drain_profit.h"
+#include "pg_do_reserve_purse.h"
+#include "pg_lookup_global_fee_by_time.h"
+#include "pg_do_purse_deposit.h"
+#include "pg_activate_signing_key.h"
+#include "pg_update_auditor.h"
+#include "pg_begin_revolving_shard.h"
+#include "pg_get_extension_manifest.h"
+#include "pg_do_purse_delete.h"
+#include "pg_do_purse_merge.h"
+#include "pg_start_read_committed.h"
+#include "pg_start_read_only.h"
+#include "pg_insert_denomination_info.h"
+#include "pg_do_batch_withdraw_insert.h"
+#include "pg_lookup_wire_fee_by_time.h"
+#include "pg_start.h"
+#include "pg_rollback.h"
+#include "pg_create_tables.h"
+#include "pg_event_listen.h"
+#include "pg_event_listen_cancel.h"
+#include "pg_event_notify.h"
+#include "pg_get_denomination_info.h"
+#include "pg_iterate_denomination_info.h"
+#include "pg_iterate_denominations.h"
+#include "pg_iterate_active_auditors.h"
+#include "pg_iterate_auditor_denominations.h"
+#include "pg_reserves_get.h"
+#include "pg_reserves_get_origin.h"
+#include "pg_drain_kyc_alert.h"
+#include "pg_reserves_in_insert.h"
+#include "pg_get_withdraw_info.h"
+#include "pg_get_age_withdraw.h"
+#include "pg_do_batch_withdraw.h"
+#include "pg_do_age_withdraw.h"
+#include "pg_get_policy_details.h"
+#include "pg_persist_policy_details.h"
+#include "pg_do_deposit.h"
+#include "pg_get_wire_hash_for_contract.h"
+#include "pg_add_policy_fulfillment_proof.h"
+#include "pg_do_melt.h"
+#include "pg_do_refund.h"
+#include "pg_do_recoup.h"
+#include "pg_do_recoup_refresh.h"
+#include "pg_get_reserve_balance.h"
+#include "pg_count_known_coins.h"
+#include "pg_ensure_coin_known.h"
+#include "pg_get_known_coin.h"
+#include "pg_get_signature_for_known_coin.h"
+#include "pg_get_coin_denomination.h"
+#include "pg_have_deposit2.h"
+#include "pg_aggregate.h"
+#include "pg_create_aggregation_transient.h"
+#include "pg_select_aggregation_transient.h"
+#include "pg_find_aggregation_transient.h"
+#include "pg_update_aggregation_transient.h"
+#include "pg_get_ready_deposit.h"
+#include "pg_insert_refund.h"
+#include "pg_select_refunds_by_coin.h"
+#include "pg_get_melt.h"
+#include "pg_insert_refresh_reveal.h"
+#include "pg_get_refresh_reveal.h"
+#include "pg_lookup_wire_transfer.h"
+#include "pg_lookup_transfer_by_deposit.h"
+#include "pg_insert_wire_fee.h"
+#include "pg_insert_global_fee.h"
+#include "pg_get_wire_fee.h"
+#include "pg_get_global_fee.h"
+#include "pg_get_global_fees.h"
+#include "pg_insert_reserve_closed.h"
+#include "pg_wire_prepare_data_insert.h"
+#include "pg_wire_prepare_data_mark_finished.h"
+#include "pg_wire_prepare_data_mark_failed.h"
+#include "pg_wire_prepare_data_get.h"
+#include "pg_start_deferred_wire_out.h"
+#include "pg_store_wire_transfer_out.h"
+#include "pg_gc.h"
+#include "pg_inject_auditor_triggers.h"
+#include "pg_select_coin_deposits_above_serial_id.h"
+#include "pg_select_purse_decisions_above_serial_id.h"
+#include "pg_select_purse_deposits_by_purse.h"
+#include "pg_select_refreshes_above_serial_id.h"
+#include "pg_select_refunds_above_serial_id.h"
+#include "pg_select_reserves_in_above_serial_id.h"
+#include "pg_select_reserves_in_above_serial_id_by_account.h"
+#include "pg_select_withdrawals_above_serial_id.h"
+#include "pg_select_wire_out_above_serial_id.h"
+#include "pg_select_wire_out_above_serial_id_by_account.h"
+#include "pg_select_recoup_above_serial_id.h"
+#include "pg_select_recoup_refresh_above_serial_id.h"
+#include "pg_get_reserve_by_h_blind.h"
+#include "pg_get_old_coin_by_h_blind.h"
+#include "pg_insert_denomination_revocation.h"
+#include "pg_get_denomination_revocation.h"
+#include "pg_select_batch_deposits_missing_wire.h"
+#include "pg_select_justification_for_missing_wire.h"
+#include "pg_select_aggregations_above_serial.h"
+#include "pg_lookup_auditor_timestamp.h"
+#include "pg_lookup_auditor_status.h"
+#include "pg_insert_auditor.h"
+#include "pg_lookup_wire_timestamp.h"
+#include "pg_insert_wire.h"
+#include "pg_update_wire.h"
+#include "pg_get_wire_accounts.h"
+#include "pg_get_wire_fees.h"
+#include "pg_insert_signkey_revocation.h"
+#include "pg_lookup_signkey_revocation.h"
+#include "pg_lookup_denomination_key.h"
+#include "pg_insert_auditor_denom_sig.h"
+#include "pg_select_auditor_denom_sig.h"
+#include "pg_add_denomination_key.h"
+#include "pg_lookup_signing_key.h"
+#include "pg_begin_shard.h"
+#include "pg_abort_shard.h"
+#include "pg_complete_shard.h"
+#include "pg_release_revolving_shard.h"
+#include "pg_delete_shard_locks.h"
+#include "pg_set_extension_manifest.h"
+#include "pg_insert_partner.h"
+#include "pg_expire_purse.h"
+#include "pg_select_purse_by_merge_pub.h"
+#include "pg_set_purse_balance.h"
+#include "pg_reserves_update.h"
+#include "pg_setup_wire_target.h"
+#include "pg_compute_shard.h"
+#include "pg_insert_kyc_attributes.h"
+#include "pg_select_similar_kyc_attributes.h"
+#include "pg_select_kyc_attributes.h"
+#include "pg_insert_aml_officer.h"
+#include "pg_test_aml_officer.h"
+#include "pg_lookup_aml_officer.h"
+#include "pg_trigger_aml_process.h"
+#include "pg_select_aml_process.h"
+#include "pg_select_aml_history.h"
+#include "pg_insert_aml_decision.h"
+#include "pg_batch_ensure_coin_known.h"
/**
* Set to 1 to enable Postgres auto_explain module. This will
* slow down things a _lot_, but also provide extensive logging
* in the Postgres database logger for performance analysis.
*/
-#define AUTO_EXPLAIN 1
+#define AUTO_EXPLAIN 0
-/**
- * Should we explicitly lock certain individual tables prior to SELECT+INSERT
- * combis?
- */
-#define EXPLICIT_LOCKS 0
-
-/**
- * Wrapper macro to add the currency from the plugin's state
- * when fetching amounts from the database.
- *
- * @param field name of the database field to fetch amount from
- * @param[out] amountp pointer to amount to set
- */
-#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) TALER_PQ_result_spec_amount ( \
- field,pg->currency,amountp)
-
-/**
- * Wrapper macro to add the currency from the plugin's state
- * when fetching amounts from the database. NBO variant.
- *
- * @param field name of the database field to fetch amount from
- * @param[out] amountp pointer to amount to set
- */
-#define TALER_PQ_RESULT_SPEC_AMOUNT_NBO(field, \
- amountp) TALER_PQ_result_spec_amount_nbo ( \
- field,pg->currency,amountp)
/**
* Log a really unexpected PQ error with all the details we can get hold of.
@@ -84,167 +248,15 @@
/**
- * Handler for a database session (per-thread, for transactions).
- */
-struct TALER_EXCHANGEDB_Session
-{
- /**
- * Postgres connection handle.
- */
- struct GNUNET_PQ_Context *conn;
-
- /**
- * Name of the current transaction, for debugging.
- */
- const char *transaction_name;
-
-};
-
-
-/**
- * Type of the "cls" argument given to each of the functions in
- * our API.
- */
-struct PostgresClosure
-{
-
- /**
- * Thread-local database connection.
- * Contains a pointer to `struct GNUNET_PQ_Context` or NULL.
- */
- pthread_key_t db_conn_threadlocal;
-
- /**
- * Our configuration.
- */
- const struct GNUNET_CONFIGURATION_Handle *cfg;
-
- /**
- * Directory with SQL statements to run to create tables.
- */
- char *sql_dir;
-
- /**
- * After how long should idle reserves be closed?
- */
- struct GNUNET_TIME_Relative idle_reserve_expiration_time;
-
- /**
- * After how long should reserves that have seen withdraw operations
- * be garbage collected?
- */
- struct GNUNET_TIME_Relative legal_reserve_expiration_time;
-
- /**
- * Which currency should we assume all amounts to be in?
- */
- char *currency;
-
- /**
- * Session to be used if the thread is @e main_self.
- */
- struct TALER_EXCHANGEDB_Session *main_session;
-
- /**
- * Handle for the main() thread of the program.
- */
- pthread_t main_self;
-};
-
-
-/**
- * Drop all Taler tables. This should only be used by testcases.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
- */
-static int
-postgres_drop_tables (void *cls)
-{
- struct PostgresClosure *pc = cls;
- struct GNUNET_PQ_Context *conn;
-
- conn = GNUNET_PQ_connect_with_cfg (pc->cfg,
- "exchangedb-postgres",
- "drop",
- NULL,
- NULL);
- if (NULL == conn)
- return GNUNET_SYSERR;
- GNUNET_PQ_disconnect (conn);
- return GNUNET_OK;
-}
-
-
-/**
- * Create the necessary tables if they are not present
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
- */
-static int
-postgres_create_tables (void *cls)
-{
- struct PostgresClosure *pc = cls;
- struct GNUNET_PQ_Context *conn;
-
- conn = GNUNET_PQ_connect_with_cfg (pc->cfg,
- "exchangedb-postgres",
- "exchange-",
- NULL,
- NULL);
- if (NULL == conn)
- return GNUNET_SYSERR;
- GNUNET_PQ_disconnect (conn);
- return GNUNET_OK;
-}
-
-
-/**
- * Close thread-local database connection when a thread is destroyed.
- *
- * @param cls closure we get from pthreads (the db handle)
- */
-static void
-db_conn_destroy (void *cls)
-{
- struct TALER_EXCHANGEDB_Session *session = cls;
- struct GNUNET_PQ_Context *db_conn;
-
- if (NULL == session)
- return;
- db_conn = session->conn;
- session->conn = NULL;
- if (NULL != db_conn)
- GNUNET_PQ_disconnect (db_conn);
- GNUNET_free (session);
-}
-
-
-/**
- * Get the thread-local database-handle.
- * Connect to the db if the connection does not exist yet.
+ * Connect to the database if the connection does not exist yet.
*
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @return the database connection, or NULL on error
+ * @param pg the plugin-specific state
+ * @return #GNUNET_OK on success
*/
-static struct TALER_EXCHANGEDB_Session *
-postgres_get_session (void *cls)
+enum GNUNET_GenericReturnValue
+TEH_PG_internal_setup (struct PostgresClosure *pg)
{
- struct PostgresClosure *pc = cls;
- struct GNUNET_PQ_Context *db_conn;
- struct TALER_EXCHANGEDB_Session *session;
-
- if (pthread_equal (pc->main_self,
- pthread_self ()))
- session = pc->main_session;
- else
- session = pthread_getspecific (pc->db_conn_threadlocal);
- if (NULL != session)
- {
- GNUNET_PQ_reconnect_if_down (session->conn);
- return session;
- }
+ if (NULL == pg->conn)
{
#if AUTO_EXPLAIN
/* Enable verbose logging to see where queries do not
@@ -254,6947 +266,49 @@ postgres_get_session (void *cls)
GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"),
GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"),
GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"),
+ /* https://wiki.postgresql.org/wiki/Serializable suggests to really
+ force the default to 'serializable' if SSI is to be used. */
+ GNUNET_PQ_make_try_execute (
+ "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
+ GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+ /* Mergejoin causes issues, see Postgres #18380 */
+ GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
#else
- struct GNUNET_PQ_ExecuteStatement *es = NULL;
-#endif
- struct GNUNET_PQ_PreparedStatement ps[] = {
- /* Used in #postgres_insert_denomination_info() */
- GNUNET_PQ_make_prepare ("denomination_insert",
- "INSERT INTO denominations "
- "(denom_pub_hash"
- ",denom_pub"
- ",master_pub"
- ",master_sig"
- ",valid_from"
- ",expire_withdraw"
- ",expire_deposit"
- ",expire_legal"
- ",coin_val" /* value of this denom */
- ",coin_frac" /* fractional value of this denom */
- ",fee_withdraw_val"
- ",fee_withdraw_frac"
- ",fee_deposit_val"
- ",fee_deposit_frac"
- ",fee_refresh_val"
- ",fee_refresh_frac"
- ",fee_refund_val"
- ",fee_refund_frac"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
- " $11, $12, $13, $14, $15, $16, $17, $18);",
- 18),
- /* Used in #postgres_iterate_denomination_info() */
- GNUNET_PQ_make_prepare ("denomination_iterate",
- "SELECT"
- " master_pub"
- ",master_sig"
- ",valid_from"
- ",expire_withdraw"
- ",expire_deposit"
- ",expire_legal"
- ",coin_val" /* value of this denom */
- ",coin_frac" /* fractional value of this denom */
- ",fee_withdraw_val"
- ",fee_withdraw_frac"
- ",fee_deposit_val"
- ",fee_deposit_frac"
- ",fee_refresh_val"
- ",fee_refresh_frac"
- ",fee_refund_val"
- ",fee_refund_frac"
- ",denom_pub"
- " FROM denominations;",
- 0),
- /* Used in #postgres_get_denomination_info() */
- GNUNET_PQ_make_prepare ("denomination_get",
- "SELECT"
- " master_pub"
- ",master_sig"
- ",valid_from"
- ",expire_withdraw"
- ",expire_deposit"
- ",expire_legal"
- ",coin_val" /* value of this denom */
- ",coin_frac" /* fractional value of this denom */
- ",fee_withdraw_val"
- ",fee_withdraw_frac"
- ",fee_deposit_val"
- ",fee_deposit_frac"
- ",fee_refresh_val"
- ",fee_refresh_frac"
- ",fee_refund_val"
- ",fee_refund_frac"
- " FROM denominations"
- " WHERE denom_pub_hash=$1;",
- 1),
- /* Used in #postgres_insert_denomination_revocation() */
- GNUNET_PQ_make_prepare ("denomination_revocation_insert",
- "INSERT INTO denomination_revocations "
- "(denom_pub_hash"
- ",master_sig"
- ") VALUES "
- "($1, $2);",
- 2),
- /* Used in #postgres_get_denomination_revocation() */
- GNUNET_PQ_make_prepare ("denomination_revocation_get",
- "SELECT"
- " master_sig"
- ",denom_revocations_serial_id"
- " FROM denomination_revocations"
- " WHERE denom_pub_hash=$1;",
- 1),
- /* Used in #postgres_reserves_get() */
- GNUNET_PQ_make_prepare ("reserves_get",
- "SELECT"
- " current_balance_val"
- ",current_balance_frac"
- ",expiration_date"
- ",gc_date"
- " FROM reserves"
- " WHERE reserve_pub=$1"
- " LIMIT 1"
- " FOR UPDATE;",
- 1),
- /* Used in #postgres_reserves_in_insert() when the reserve is new */
- GNUNET_PQ_make_prepare ("reserve_create",
- "INSERT INTO reserves "
- "(reserve_pub"
- ",account_details"
- ",current_balance_val"
- ",current_balance_frac"
- ",expiration_date"
- ",gc_date"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6);",
- 6),
- /* Used in #postgres_insert_reserve_closed() */
- GNUNET_PQ_make_prepare ("reserves_close_insert",
- "INSERT INTO reserves_close "
- "(reserve_pub"
- ",execution_date"
- ",wtid"
- ",receiver_account"
- ",amount_val"
- ",amount_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8);",
- 8),
- /* Used in #reserves_update() when the reserve is updated */
- GNUNET_PQ_make_prepare ("reserve_update",
- "UPDATE reserves"
- " SET"
- " expiration_date=$1"
- ",gc_date=$2"
- ",current_balance_val=$3"
- ",current_balance_frac=$4"
- " WHERE"
- " reserve_pub=$5;",
- 5),
- /* Used in #postgres_reserves_in_insert() to store transaction details */
- GNUNET_PQ_make_prepare ("reserves_in_add_transaction",
- "INSERT INTO reserves_in "
- "(reserve_pub"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",exchange_account_section"
- ",sender_account_details"
- ",execution_date"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7) "
- "ON CONFLICT DO NOTHING;",
- 7),
- /* Used in postgres_select_reserves_in_above_serial_id() to obtain inbound
- transactions for reserves with serial id '\geq' the given parameter */
- GNUNET_PQ_make_prepare ("reserves_in_get_latest_wire_reference",
- "SELECT"
- " wire_reference"
- " FROM reserves_in"
- " WHERE exchange_account_section=$1"
- " ORDER BY reserve_in_serial_id DESC"
- " LIMIT 1;",
- 1),
- /* Used in postgres_select_reserves_in_above_serial_id() to obtain inbound
- transactions for reserves with serial id '\geq' the given parameter */
- GNUNET_PQ_make_prepare ("audit_reserves_in_get_transactions_incr",
- "SELECT"
- " reserve_pub"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",execution_date"
- ",sender_account_details"
- ",reserve_in_serial_id"
- " FROM reserves_in"
- " WHERE reserve_in_serial_id>=$1"
- " ORDER BY reserve_in_serial_id;",
- 1),
- /* Used in postgres_select_reserves_in_above_serial_id() to obtain inbound
- transactions for reserves with serial id '\geq' the given parameter */
- GNUNET_PQ_make_prepare (
- "audit_reserves_in_get_transactions_incr_by_account",
- "SELECT"
- " reserve_pub"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",execution_date"
- ",sender_account_details"
- ",reserve_in_serial_id"
- " FROM reserves_in"
- " WHERE reserve_in_serial_id>=$1 AND exchange_account_section=$2"
- " ORDER BY reserve_in_serial_id;",
- 2),
- /* Used in #postgres_get_reserve_history() to obtain inbound transactions
- for a reserve */
- GNUNET_PQ_make_prepare ("reserves_in_get_transactions",
- "SELECT"
- " wire_reference"
- ",credit_val"
- ",credit_frac"
- ",execution_date"
- ",sender_account_details"
- " FROM reserves_in"
- " WHERE reserve_pub=$1"
- " FOR UPDATE;",
- 1),
- /* Lock withdraw table; NOTE: we may want to eventually shard the
- deposit table to avoid this lock being the main point of
- contention limiting transaction performance. */
- GNUNET_PQ_make_prepare ("lock_withdraw",
- "LOCK TABLE reserves_out;",
- 0),
- /* Used in #postgres_insert_withdraw_info() to store
- the signature of a blinded coin with the blinded coin's
- details before returning it during /reserve/withdraw. We store
- the coin's denomination information (public key, signature)
- and the blinded message as well as the reserve that the coin
- is being withdrawn from and the signature of the message
- authorizing the withdrawal. */GNUNET_PQ_make_prepare ("insert_withdraw_info",
- "INSERT INTO reserves_out "
- "(h_blind_ev"
- ",denom_pub_hash"
- ",denom_sig"
- ",reserve_pub"
- ",reserve_sig"
- ",execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8);",
- 8),
- /* Used in #postgres_get_withdraw_info() to
- locate the response for a /reserve/withdraw request
- using the hash of the blinded message. Used to
- make sure /reserve/withdraw requests are idempotent. */
- GNUNET_PQ_make_prepare ("get_withdraw_info",
- "SELECT"
- " denom_pub_hash"
- ",denom_sig"
- ",reserve_sig"
- ",reserve_pub"
- ",execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_withdraw_val"
- ",denom.fee_withdraw_frac"
- " FROM reserves_out"
- " JOIN denominations denom"
- " USING (denom_pub_hash)"
- " WHERE h_blind_ev=$1"
- " FOR UPDATE;",
- 1),
- /* Used during #postgres_get_reserve_history() to
- obtain all of the /reserve/withdraw operations that
- have been performed on a given reserve. (i.e. to
- demonstrate double-spending) */
- GNUNET_PQ_make_prepare ("get_reserves_out",
- "SELECT"
- " h_blind_ev"
- ",denom_pub_hash"
- ",denom_sig"
- ",reserve_sig"
- ",execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_withdraw_val"
- ",denom.fee_withdraw_frac"
- " FROM reserves_out"
- " JOIN denominations denom"
- " USING (denom_pub_hash)"
- " WHERE reserve_pub=$1"
- " FOR UPDATE",
- 1),
- /* Used in #postgres_select_withdrawals_above_serial_id() */
- GNUNET_PQ_make_prepare ("audit_get_reserves_out_incr",
- "SELECT"
- " h_blind_ev"
- ",denom.denom_pub"
- ",reserve_sig"
- ",reserve_pub"
- ",execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",reserve_out_serial_id"
- " FROM reserves_out"
- " JOIN denominations denom"
- " USING (denom_pub_hash)"
- " WHERE reserve_out_serial_id>=$1"
- " ORDER BY reserve_out_serial_id ASC;",
- 1),
-
- /* Used in #postgres_count_known_coins() */
- GNUNET_PQ_make_prepare ("count_known_coins",
- "SELECT"
- " COUNT(*) AS count"
- " FROM known_coins"
- " WHERE denom_pub_hash=$1;",
- 1),
- /* Used in #postgres_get_known_coin() to fetch
- the denomination public key and signature for
- a coin known to the exchange. */
- GNUNET_PQ_make_prepare ("get_known_coin",
- "SELECT"
- " denom_pub_hash"
- ",denom_sig"
- " FROM known_coins"
- " WHERE coin_pub=$1"
- " FOR UPDATE;",
- 1),
- /* Used in #postgres_get_coin_denomination() to fetch
- the denomination public key hash for
- a coin known to the exchange. */
- GNUNET_PQ_make_prepare ("get_coin_denomination",
- "SELECT"
- " denom_pub_hash"
- " FROM known_coins"
- " WHERE coin_pub=$1"
- " FOR SHARE;",
- 1),
- /* Lock deposit table; NOTE: we may want to eventually shard the
- deposit table to avoid this lock being the main point of
- contention limiting transaction performance. */
- GNUNET_PQ_make_prepare ("lock_known_coins",
- "LOCK TABLE known_coins;",
- 0),
- /* Used in #postgres_insert_known_coin() to store
- the denomination public key and signature for
- a coin known to the exchange. */
- GNUNET_PQ_make_prepare ("insert_known_coin",
- "INSERT INTO known_coins "
- "(coin_pub"
- ",denom_pub_hash"
- ",denom_sig"
- ") VALUES "
- "($1,$2,$3);",
- 3),
-
- /* Used in #postgres_insert_melt() to store
- high-level information about a melt operation */
- GNUNET_PQ_make_prepare ("insert_melt",
- "INSERT INTO refresh_commitments "
- "(rc "
- ",old_coin_pub "
- ",old_coin_sig "
- ",amount_with_fee_val "
- ",amount_with_fee_frac "
- ",noreveal_index "
- ") VALUES "
- "($1, $2, $3, $4, $5, $6);",
- 6),
- /* Used in #postgres_get_melt() to fetch
- high-level information about a melt operation */
- GNUNET_PQ_make_prepare ("get_melt",
- "SELECT"
- " kc.denom_pub_hash"
- ",denom.fee_refresh_val"
- ",denom.fee_refresh_frac"
- ",old_coin_pub"
- ",old_coin_sig"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",noreveal_index"
- " FROM refresh_commitments"
- " JOIN known_coins kc"
- " ON (refresh_commitments.old_coin_pub = kc.coin_pub)"
- " JOIN denominations denom"
- " ON (kc.denom_pub_hash = denom.denom_pub_hash)"
- " WHERE rc=$1;",
- 1),
- /* Used in #postgres_get_melt_index() to fetch
- the noreveal index from a previous melt operation */
- GNUNET_PQ_make_prepare ("get_melt_index",
- "SELECT"
- " noreveal_index"
- " FROM refresh_commitments"
- " WHERE rc=$1;",
- 1),
- /* Used in #postgres_select_refreshes_above_serial_id() to fetch
- refresh session with id '\geq' the given parameter */
- GNUNET_PQ_make_prepare ("audit_get_refresh_commitments_incr",
- "SELECT"
- " denom.denom_pub"
- ",old_coin_pub"
- ",old_coin_sig"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",noreveal_index"
- ",melt_serial_id"
- ",rc"
- " FROM refresh_commitments"
- " JOIN known_coins kc"
- " ON (refresh_commitments.old_coin_pub = kc.coin_pub)"
- " JOIN denominations denom"
- " ON (kc.denom_pub_hash = denom.denom_pub_hash)"
- " WHERE melt_serial_id>=$1"
- " ORDER BY melt_serial_id ASC;",
- 1),
- /* Query the 'refresh_commitments' by coin public key */
- GNUNET_PQ_make_prepare ("get_refresh_session_by_coin",
- "SELECT"
- " rc"
- ",old_coin_sig"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_refresh_val "
- ",denom.fee_refresh_frac "
- ",melt_serial_id"
- " FROM refresh_commitments"
- " JOIN known_coins "
- " ON (refresh_commitments.old_coin_pub = known_coins.coin_pub)"
- " JOIN denominations denom USING (denom_pub_hash)"
- " WHERE old_coin_pub=$1;",
- 1),
-
- /* Store information about the desired denominations for a
- refresh operation, used in #postgres_insert_refresh_reveal() */
- GNUNET_PQ_make_prepare ("insert_refresh_revealed_coin",
- "INSERT INTO refresh_revealed_coins "
- "(rc "
- ",freshcoin_index "
- ",link_sig "
- ",denom_pub_hash "
- ",coin_ev"
- ",h_coin_ev"
- ",ev_sig"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7);",
- 7),
- /* Obtain information about the coins created in a refresh
- operation, used in #postgres_get_refresh_reveal() */
- GNUNET_PQ_make_prepare ("get_refresh_revealed_coins",
- "SELECT "
- " freshcoin_index"
- ",denom.denom_pub"
- ",link_sig"
- ",coin_ev"
- ",ev_sig"
- " FROM refresh_revealed_coins"
- " JOIN denominations denom "
- " USING (denom_pub_hash)"
- " WHERE rc=$1"
- " ORDER BY freshcoin_index ASC"
- " FOR UPDATE;",
- 1),
-
- /* Used in #postgres_insert_refresh_reveal() to store the transfer
- keys we learned */
- GNUNET_PQ_make_prepare ("insert_refresh_transfer_keys",
- "INSERT INTO refresh_transfer_keys "
- "(rc"
- ",transfer_pub"
- ",transfer_privs"
- ") VALUES "
- "($1, $2, $3);",
- 3),
- /* Used in #postgres_get_refresh_reveal() to retrieve transfer
- keys from /refresh/reveal */
- GNUNET_PQ_make_prepare ("get_refresh_transfer_keys",
- "SELECT"
- " transfer_pub"
- ",transfer_privs"
- " FROM refresh_transfer_keys"
- " WHERE rc=$1;",
- 1),
-
-
- /* Used in #postgres_insert_refund() to store refund information */
- GNUNET_PQ_make_prepare ("insert_refund",
- "INSERT INTO refunds "
- "(coin_pub "
- ",merchant_pub "
- ",merchant_sig "
- ",h_contract_terms "
- ",rtransaction_id "
- ",amount_with_fee_val "
- ",amount_with_fee_frac "
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7);",
- 7),
- /* Query the 'refunds' by coin public key */
- GNUNET_PQ_make_prepare ("get_refunds_by_coin",
- "SELECT"
- " merchant_pub"
- ",merchant_sig"
- ",h_contract_terms"
- ",rtransaction_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_refund_val "
- ",denom.fee_refund_frac "
- ",refund_serial_id"
- " FROM refunds"
- " JOIN known_coins USING (coin_pub)"
- " JOIN denominations denom USING (denom_pub_hash)"
- " WHERE coin_pub=$1;",
- 1),
- /* Query the 'refunds' by coin public key, merchant_pub and contract hash */
- GNUNET_PQ_make_prepare ("get_refunds_by_coin_and_contract",
- "SELECT"
- " amount_with_fee_val"
- ",amount_with_fee_frac"
- " FROM refunds"
- " WHERE"
- " coin_pub=$1"
- " AND merchant_pub=$2"
- " AND h_contract_terms=$3;",
- 3),
- /* Fetch refunds with rowid '\geq' the given parameter */
- GNUNET_PQ_make_prepare ("audit_get_refunds_incr",
- "SELECT"
- " merchant_pub"
- ",merchant_sig"
- ",h_contract_terms"
- ",rtransaction_id"
- ",denom.denom_pub"
- ",coin_pub"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",refund_serial_id"
- " FROM refunds"
- " JOIN known_coins kc USING (coin_pub)"
- " JOIN denominations denom ON (kc.denom_pub_hash = denom.denom_pub_hash)"
- " WHERE refund_serial_id>=$1"
- " ORDER BY refund_serial_id ASC;",
- 1),
- /* Lock deposit table; NOTE: we may want to eventually shard the
- deposit table to avoid this lock being the main point of
- contention limiting transaction performance. */
- GNUNET_PQ_make_prepare ("lock_deposit",
- "LOCK TABLE deposits;",
- 0),
- /* Store information about a /deposit the exchange is to execute.
- Used in #postgres_insert_deposit(). */
- GNUNET_PQ_make_prepare ("insert_deposit",
- "INSERT INTO deposits "
- "(coin_pub"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",timestamp"
- ",refund_deadline"
- ",wire_deadline"
- ",merchant_pub"
- ",h_contract_terms"
- ",h_wire"
- ",coin_sig"
- ",wire"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
- " $11);",
- 11),
- /* Fetch an existing deposit request, used to ensure idempotency
- during /deposit processing. Used in #postgres_have_deposit(). */
- GNUNET_PQ_make_prepare ("get_deposit",
- "SELECT"
- " amount_with_fee_val"
- ",amount_with_fee_frac"
- ",timestamp"
- ",refund_deadline"
- ",wire_deadline"
- ",h_contract_terms"
- ",h_wire"
- " FROM deposits"
- " WHERE ((coin_pub=$1)"
- " AND (merchant_pub=$3)"
- " AND (h_contract_terms=$2))"
- " FOR UPDATE;",
- 3),
- /* Fetch deposits with rowid '\geq' the given parameter */
- GNUNET_PQ_make_prepare ("audit_get_deposits_incr",
- "SELECT"
- " amount_with_fee_val"
- ",amount_with_fee_frac"
- ",timestamp"
- ",merchant_pub"
- ",denom.denom_pub"
- ",coin_pub"
- ",coin_sig"
- ",refund_deadline"
- ",wire_deadline"
- ",h_contract_terms"
- ",wire"
- ",done"
- ",deposit_serial_id"
- " FROM deposits"
- " JOIN known_coins USING (coin_pub)"
- " JOIN denominations denom USING (denom_pub_hash)"
- " WHERE ("
- " (deposit_serial_id>=$1)"
- " )"
- " ORDER BY deposit_serial_id ASC;",
- 1),
- /* Fetch an existing deposit request.
- Used in #postgres_lookup_transfer_by_deposit(). */
- GNUNET_PQ_make_prepare ("get_deposit_for_wtid",
- "SELECT"
- " amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_deposit_val"
- ",denom.fee_deposit_frac"
- ",wire_deadline"
- " FROM deposits"
- " JOIN known_coins USING (coin_pub)"
- " JOIN denominations denom USING (denom_pub_hash)"
- " WHERE ("
- " (coin_pub=$1)"
- " AND (merchant_pub=$2)"
- " AND (h_contract_terms=$3)"
- " AND (h_wire=$4)"
- " );",
- 4),
- /* Used in #postgres_get_ready_deposit() */
- GNUNET_PQ_make_prepare ("deposits_get_ready",
- "SELECT"
- " deposit_serial_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_deposit_val"
- ",denom.fee_deposit_frac"
- ",wire_deadline"
- ",h_contract_terms"
- ",wire"
- ",merchant_pub"
- ",coin_pub"
- " FROM deposits"
- " JOIN known_coins USING (coin_pub)"
- " JOIN denominations denom USING (denom_pub_hash)"
- " WHERE tiny=FALSE"
- " AND done=FALSE"
- " AND wire_deadline<=$1"
- " AND refund_deadline<$1"
- " ORDER BY wire_deadline ASC"
- " LIMIT 1;",
- 1),
- /* Used in #postgres_iterate_matching_deposits() */
- GNUNET_PQ_make_prepare ("deposits_iterate_matching",
- "SELECT"
- " deposit_serial_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_deposit_val"
- ",denom.fee_deposit_frac"
- ",wire_deadline"
- ",h_contract_terms"
- ",coin_pub"
- " FROM deposits"
- " JOIN known_coins"
- " USING (coin_pub)"
- " JOIN denominations denom"
- " USING (denom_pub_hash)"
- " WHERE"
- " merchant_pub=$1 AND"
- " h_wire=$2 AND"
- " done=FALSE"
- " ORDER BY wire_deadline ASC"
- " LIMIT "
- TALER_QUOTE (
- TALER_EXCHANGEDB_MATCHING_DEPOSITS_LIMIT) ";",
- 2),
- /* Used in #postgres_mark_deposit_tiny() */
- GNUNET_PQ_make_prepare ("mark_deposit_tiny",
- "UPDATE deposits"
- " SET tiny=TRUE"
- " WHERE deposit_serial_id=$1",
- 1),
- /* Used in #postgres_mark_deposit_done() */
- GNUNET_PQ_make_prepare ("mark_deposit_done",
- "UPDATE deposits"
- " SET done=TRUE"
- " WHERE deposit_serial_id=$1;",
- 1),
- /* Used in #postgres_test_deposit_done() */
- GNUNET_PQ_make_prepare ("test_deposit_done",
- "SELECT done"
- " FROM deposits"
- " WHERE coin_pub=$1"
- " AND merchant_pub=$2"
- " AND h_contract_terms=$3"
- " AND h_wire=$4;",
- 5),
- /* Used in #postgres_get_coin_transactions() to obtain information
- about how a coin has been spend with /deposit requests. */
- GNUNET_PQ_make_prepare ("get_deposit_with_coin_pub",
- "SELECT"
- " amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_deposit_val"
- ",denom.fee_deposit_frac"
- ",timestamp"
- ",refund_deadline"
- ",wire_deadline"
- ",merchant_pub"
- ",h_contract_terms"
- ",h_wire"
- ",wire"
- ",coin_sig"
- ",deposit_serial_id"
- " FROM deposits"
- " JOIN known_coins"
- " USING (coin_pub)"
- " JOIN denominations denom"
- " USING (denom_pub_hash)"
- " WHERE coin_pub=$1"
- " FOR UPDATE;",
- 1),
-
- /* Used in #postgres_get_link_data(). */
- GNUNET_PQ_make_prepare ("get_link",
- "SELECT "
- " tp.transfer_pub"
- ",denoms.denom_pub"
- ",rrc.ev_sig"
- ",rrc.link_sig"
- " FROM refresh_commitments"
- " JOIN refresh_revealed_coins rrc"
- " USING (rc)"
- " JOIN refresh_transfer_keys tp"
- " USING (rc)"
- " JOIN denominations denoms"
- " ON (rrc.denom_pub_hash = denoms.denom_pub_hash)"
- " WHERE old_coin_pub=$1"
- " ORDER BY tp.transfer_pub",
- 1),
- /* Used in #postgres_lookup_wire_transfer */
- GNUNET_PQ_make_prepare ("lookup_transactions",
- "SELECT"
- " aggregation_serial_id"
- ",deposits.h_contract_terms"
- ",deposits.wire"
- ",deposits.h_wire"
- ",deposits.coin_pub"
- ",deposits.merchant_pub"
- ",wire_out.execution_date"
- ",deposits.amount_with_fee_val"
- ",deposits.amount_with_fee_frac"
- ",denom.fee_deposit_val"
- ",denom.fee_deposit_frac"
- ",denom.denom_pub"
- " FROM aggregation_tracking"
- " JOIN deposits"
- " USING (deposit_serial_id)"
- " JOIN known_coins"
- " USING (coin_pub)"
- " JOIN denominations denom"
- " USING (denom_pub_hash)"
- " JOIN wire_out"
- " USING (wtid_raw)"
- " WHERE wtid_raw=$1;",
- 1),
- /* Used in #postgres_lookup_transfer_by_deposit */
- GNUNET_PQ_make_prepare ("lookup_deposit_wtid",
- "SELECT"
- " aggregation_tracking.wtid_raw"
- ",wire_out.execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_deposit_val"
- ",denom.fee_deposit_frac"
- " FROM deposits"
- " JOIN aggregation_tracking"
- " USING (deposit_serial_id)"
- " JOIN known_coins"
- " USING (coin_pub)"
- " JOIN denominations denom"
- " USING (denom_pub_hash)"
- " JOIN wire_out"
- " USING (wtid_raw)"
- " WHERE coin_pub=$1"
- " AND h_contract_terms=$2"
- " AND h_wire=$3"
- " AND merchant_pub=$4;",
- 4),
- /* Used in #postgres_insert_aggregation_tracking */
- GNUNET_PQ_make_prepare ("insert_aggregation_tracking",
- "INSERT INTO aggregation_tracking "
- "(deposit_serial_id"
- ",wtid_raw"
- ") VALUES "
- "($1, $2);",
- 2),
- /* Used in #postgres_get_wire_fee() */
- GNUNET_PQ_make_prepare ("get_wire_fee",
- "SELECT "
- " start_date"
- ",end_date"
- ",wire_fee_val"
- ",wire_fee_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",master_sig"
- " FROM wire_fee"
- " WHERE wire_method=$1"
- " AND start_date <= $2"
- " AND end_date > $2;",
- 2),
- /* Used in #postgres_insert_wire_fee */
- GNUNET_PQ_make_prepare ("insert_wire_fee",
- "INSERT INTO wire_fee "
- "(wire_method"
- ",start_date"
- ",end_date"
- ",wire_fee_val"
- ",wire_fee_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",master_sig"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8);",
- 8),
- /* Used in #postgres_store_wire_transfer_out */
- GNUNET_PQ_make_prepare ("insert_wire_out",
- "INSERT INTO wire_out "
- "(execution_date"
- ",wtid_raw"
- ",wire_target"
- ",exchange_account_section"
- ",amount_val"
- ",amount_frac"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6);",
- 6),
- /* Used in #postgres_wire_prepare_data_insert() to store
- wire transfer information before actually committing it with the bank */
- GNUNET_PQ_make_prepare ("wire_prepare_data_insert",
- "INSERT INTO prewire "
- "(type"
- ",buf"
- ") VALUES "
- "($1, $2);",
- 2),
- /* Used in #postgres_wire_prepare_data_mark_finished() */
- GNUNET_PQ_make_prepare ("wire_prepare_data_mark_done",
- "UPDATE prewire"
- " SET finished=true"
- " WHERE prewire_uuid=$1;",
- 1),
- /* Used in #postgres_wire_prepare_data_get() */
- GNUNET_PQ_make_prepare ("wire_prepare_data_get",
- "SELECT"
- " prewire_uuid"
- ",type"
- ",buf"
- " FROM prewire"
- " WHERE finished=false"
- " ORDER BY prewire_uuid ASC"
- " LIMIT 1;",
- 0),
-
- /* Used in #postgres_select_deposits_missing_wire */
- GNUNET_PQ_make_prepare ("deposits_get_overdue",
- "SELECT"
- " deposit_serial_id"
- ",coin_pub"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",wire"
- ",wire_deadline"
- ",tiny"
- ",done"
- " FROM deposits"
- " WHERE wire_deadline >= $1"
- " AND wire_deadline < $2"
- " AND NOT (EXISTS (SELECT 1"
- " FROM refunds"
- " WHERE (refunds.coin_pub = deposits.coin_pub))"
- " OR EXISTS (SELECT 1"
- " FROM aggregation_tracking"
- " WHERE (aggregation_tracking.deposit_serial_id = deposits.deposit_serial_id)))"
- " ORDER BY wire_deadline ASC",
- 2),
- /* Used in #postgres_select_wire_out_above_serial_id() */
- GNUNET_PQ_make_prepare ("audit_get_wire_incr",
- "SELECT"
- " wireout_uuid"
- ",execution_date"
- ",wtid_raw"
- ",wire_target"
- ",amount_val"
- ",amount_frac"
- " FROM wire_out"
- " WHERE wireout_uuid>=$1"
- " ORDER BY wireout_uuid ASC;",
- 1),
- /* Used in #postgres_select_wire_out_above_serial_id_by_account() */
- GNUNET_PQ_make_prepare ("audit_get_wire_incr_by_account",
- "SELECT"
- " wireout_uuid"
- ",execution_date"
- ",wtid_raw"
- ",wire_target"
- ",amount_val"
- ",amount_frac"
- " FROM wire_out"
- " WHERE wireout_uuid>=$1 AND exchange_account_section=$2"
- " ORDER BY wireout_uuid ASC;",
- 2),
- /* Used in #postgres_insert_recoup_request() to store recoup
- information */
- GNUNET_PQ_make_prepare ("recoup_insert",
- "INSERT INTO recoup "
- "(coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",h_blind_ev"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7);",
- 7),
- /* Used in #postgres_insert_recoup_refresh_request() to store recoup-refresh
- information */
- GNUNET_PQ_make_prepare ("recoup_refresh_insert",
- "INSERT INTO recoup_refresh "
- "(coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",h_blind_ev"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7);",
- 7),
- /* Used in #postgres_select_recoup_above_serial_id() to obtain recoup transactions */
- GNUNET_PQ_make_prepare ("recoup_get_incr",
- "SELECT"
- " recoup_uuid"
- ",timestamp"
- ",ro.reserve_pub"
- ",coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",h_blind_ev"
- ",coins.denom_pub_hash"
- ",coins.denom_sig"
- ",denoms.denom_pub"
- ",amount_val"
- ",amount_frac"
- " FROM recoup"
- " JOIN known_coins coins"
- " USING (coin_pub)"
- " JOIN reserves_out ro"
- " USING (h_blind_ev)"
- " JOIN denominations denoms"
- " ON (coins.denom_pub_hash = denoms.denom_pub_hash)"
- " WHERE recoup_uuid>=$1"
- " ORDER BY recoup_uuid ASC;",
- 1),
- /* Used in #postgres_select_recoup_refresh_above_serial_id() to obtain
- recoup-refresh transactions */
- GNUNET_PQ_make_prepare ("recoup_refresh_get_incr",
- "SELECT"
- " recoup_refresh_uuid"
- ",timestamp"
- ",rc.old_coin_pub"
- ",old_coins.denom_pub_hash AS old_denom_pub_hash"
- ",recoup_refresh.coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",denoms.denom_pub"
- ",h_blind_ev"
- ",new_coins.denom_pub_hash"
- ",new_coins.denom_sig"
- ",amount_val"
- ",amount_frac"
- " FROM recoup_refresh"
- " INNER JOIN refresh_revealed_coins rrc"
- " ON (rrc.h_coin_ev = h_blind_ev)"
- " INNER JOIN refresh_commitments rc"
- " ON (rrc.rc = rc.rc)"
- " INNER JOIN known_coins old_coins"
- " ON (rc.old_coin_pub = old_coins.coin_pub)"
- " INNER JOIN known_coins new_coins"
- " ON (new_coins.coin_pub = recoup_refresh.coin_pub)"
- " INNER JOIN denominations denoms"
- " ON (new_coins.denom_pub_hash = denoms.denom_pub_hash)"
- " WHERE recoup_refresh_uuid>=$1"
- " ORDER BY recoup_refresh_uuid ASC;",
- 1),
- /* Used in #postgres_select_reserve_closed_above_serial_id() to
- obtain information about closed reserves */
- GNUNET_PQ_make_prepare ("reserves_close_get_incr",
- "SELECT"
- " close_uuid"
- ",reserve_pub"
- ",execution_date"
- ",wtid"
- ",receiver_account"
- ",amount_val"
- ",amount_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- " FROM reserves_close"
- " WHERE close_uuid>=$1"
- " ORDER BY close_uuid ASC;",
- 1),
- /* Used in #postgres_get_reserve_history() to obtain recoup transactions
- for a reserve */
- GNUNET_PQ_make_prepare ("recoup_by_reserve",
- "SELECT"
- " coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",coins.denom_pub_hash"
- ",coins.denom_sig"
- " FROM recoup"
- " JOIN known_coins coins"
- " USING (coin_pub)"
- " JOIN reserves_out ro"
- " USING (h_blind_ev)"
- " WHERE ro.reserve_pub=$1"
- " FOR UPDATE;",
- 1),
- /* Used in #postgres_get_coin_transactions() to obtain recoup transactions
- affecting old coins of refreshed coins */
- GNUNET_PQ_make_prepare ("recoup_by_old_coin",
- "SELECT"
- " coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",coins.denom_pub_hash"
- ",coins.denom_sig"
- ",recoup_refresh_uuid"
- " FROM recoup_refresh"
- " JOIN known_coins coins"
- " USING (coin_pub)"
- " WHERE h_blind_ev IN"
- " (SELECT rrc.h_coin_ev"
- " FROM refresh_commitments"
- " JOIN refresh_revealed_coins rrc"
- " USING (rc)"
- " WHERE old_coin_pub=$1)"
- " FOR UPDATE;",
- 1),
- /* Used in #postgres_get_reserve_history() */
- GNUNET_PQ_make_prepare ("close_by_reserve",
- "SELECT"
- " amount_val"
- ",amount_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",execution_date"
- ",receiver_account"
- ",wtid"
- " FROM reserves_close"
- " WHERE reserve_pub=$1"
- " FOR UPDATE",
- 1),
- /* Used in #postgres_get_expired_reserves() */
- GNUNET_PQ_make_prepare ("get_expired_reserves",
- "SELECT"
- " expiration_date"
- ",account_details"
- ",reserve_pub"
- ",current_balance_val"
- ",current_balance_frac"
- " FROM reserves"
- " WHERE expiration_date<=$1"
- " AND (current_balance_val != 0 "
- " OR current_balance_frac != 0)"
- " ORDER BY expiration_date ASC"
- " LIMIT 1;",
- 1),
- /* Used in #postgres_get_coin_transactions() to obtain recoup transactions
- for a coin */
- GNUNET_PQ_make_prepare ("recoup_by_coin",
- "SELECT"
- " ro.reserve_pub"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",recoup_uuid"
- " FROM recoup"
- " JOIN reserves_out ro"
- " USING (h_blind_ev)"
- " WHERE recoup.coin_pub=$1"
- " FOR UPDATE;",
- 1),
- /* Used in #postgres_get_coin_transactions() to obtain recoup transactions
- for a refreshed coin */
- GNUNET_PQ_make_prepare ("recoup_by_refreshed_coin",
- "SELECT"
- " rc.old_coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",coins.denom_pub_hash"
- ",coins.denom_sig"
- ",recoup_refresh_uuid"
- " FROM recoup_refresh"
- " JOIN refresh_revealed_coins rrc"
- " ON (rrc.h_coin_ev = h_blind_ev)"
- " JOIN refresh_commitments rc"
- " ON (rrc.rc = rc.rc)"
- " JOIN known_coins coins"
- " USING (coin_pub)"
- " WHERE coin_pub=$1"
- " FOR UPDATE;",
- 1),
- /* Used in #postgres_get_reserve_by_h_blind() */
- GNUNET_PQ_make_prepare ("reserve_by_h_blind",
- "SELECT"
- " reserve_pub"
- " FROM reserves_out"
- " WHERE h_blind_ev=$1"
- " LIMIT 1"
- " FOR UPDATE;",
- 1),
- /* Used in #postgres_get_old_coin_by_h_blind() */
- GNUNET_PQ_make_prepare ("old_coin_by_h_blind",
- "SELECT"
- " rcom.old_coin_pub"
- " FROM refresh_revealed_coins"
- " JOIN refresh_commitments rcom"
- " USING (rc)"
- " WHERE h_coin_ev=$1"
- " LIMIT 1"
- " FOR UPDATE;",
- 1),
- /* used in #postgres_commit */
- GNUNET_PQ_make_prepare ("do_commit",
- "COMMIT",
- 0),
- GNUNET_PQ_PREPARED_STATEMENT_END
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_try_execute (
+ "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
+ GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
+ GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
+ /* Mergejoin causes issues, see Postgres #18380 */
+ GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"),
+ GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
};
+#endif
+ struct GNUNET_PQ_Context *db_conn;
- db_conn = GNUNET_PQ_connect_with_cfg (pc->cfg,
+ db_conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
"exchangedb-postgres",
NULL,
es,
- ps);
- }
- if (NULL == db_conn)
- return NULL;
- session = GNUNET_new (struct TALER_EXCHANGEDB_Session);
- session->conn = db_conn;
- if (pthread_equal (pc->main_self,
- pthread_self ()))
- {
- pc->main_session = session;
- }
- else
- {
- if (0 != pthread_setspecific (pc->db_conn_threadlocal,
- session))
- {
- GNUNET_break (0);
- GNUNET_PQ_disconnect (db_conn);
- GNUNET_free (session);
- return NULL;
- }
- }
- return session;
-}
-
-
-/**
- * Do a pre-flight check that we are not in an uncommitted transaction.
- * If we are, try to commit the previous transaction and output a warning.
- * Does not return anything, as we will continue regardless of the outcome.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session the database connection
- */
-static void
-postgres_preflight (void *cls,
- struct TALER_EXCHANGEDB_Session *session);
-
-/**
- * Start a transaction.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session the database connection
- * @param name unique name identifying the transaction (for debugging)
- * must point to a constant
- * @return #GNUNET_OK on success
- */
-static int
-postgres_start (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const char *name)
-{
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Starting transaction named: %s\n",
- name);
-
- postgres_preflight (cls,
- session);
-
- (void) cls;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Starting transaction on %p\n",
- session->conn);
- if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (session->conn,
- es))
- {
- TALER_LOG_ERROR ("Failed to start transaction\n");
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- session->transaction_name = name;
- return GNUNET_OK;
-}
-
-
-/**
- * Roll back the current transaction of a database connection.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session the database connection
- * @return #GNUNET_OK on success
- */
-static void
-postgres_rollback (void *cls,
- struct TALER_EXCHANGEDB_Session *session)
-{
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("ROLLBACK"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- (void) cls;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Rolling back transaction on %p\n",
- session->conn);
- GNUNET_break (GNUNET_OK ==
- GNUNET_PQ_exec_statements (session->conn,
- es));
- session->transaction_name = NULL;
-}
-
-
-/**
- * Commit the current transaction of a database connection.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session the database connection
- * @return final transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_commit (void *cls,
- struct TALER_EXCHANGEDB_Session *session)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- (void) cls;
- qs = GNUNET_PQ_eval_prepared_non_select (session->conn,
- "do_commit",
- params);
- session->transaction_name = NULL;
- return qs;
-}
-
-
-/**
- * Do a pre-flight check that we are not in an uncommitted transaction.
- * If we are, try to commit the previous transaction and output a warning.
- * Does not return anything, as we will continue regardless of the outcome.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session the database connection
- */
-static void
-postgres_preflight (void *cls,
- struct TALER_EXCHANGEDB_Session *session)
-{
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("ROLLBACK"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- (void) cls;
- if (NULL == session->transaction_name)
- return; /* all good */
- if (GNUNET_OK ==
- GNUNET_PQ_exec_statements (session->conn,
- es))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "BUG: Preflight check rolled back transaction `%s'!\n",
- session->transaction_name);
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "BUG: Preflight check failed to rollback transaction `%s'!\n",
- session->transaction_name);
- }
- session->transaction_name = NULL;
-}
-
-
-/**
- * Insert a denomination key's public information into the database for
- * reference by auditors and other consistency checks.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param denom_pub the public key used for signing coins of this denomination
- * @param issue issuing information with value, fees and other info about the coin
- * @return status of the query
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_denomination_info (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&issue->properties.denom_hash),
- GNUNET_PQ_query_param_rsa_public_key (denom_pub->rsa_public_key),
- GNUNET_PQ_query_param_auto_from_type (&issue->properties.master),
- GNUNET_PQ_query_param_auto_from_type (&issue->signature),
- TALER_PQ_query_param_absolute_time_nbo (&issue->properties.start),
- TALER_PQ_query_param_absolute_time_nbo (&issue->properties.expire_withdraw),
- TALER_PQ_query_param_absolute_time_nbo (&issue->properties.expire_deposit),
- TALER_PQ_query_param_absolute_time_nbo (&issue->properties.expire_legal),
- TALER_PQ_query_param_amount_nbo (&issue->properties.value),
- TALER_PQ_query_param_amount_nbo (&issue->properties.fee_withdraw),
- TALER_PQ_query_param_amount_nbo (&issue->properties.fee_deposit),
- TALER_PQ_query_param_amount_nbo (&issue->properties.fee_refresh),
- TALER_PQ_query_param_amount_nbo (&issue->properties.fee_refund),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- /* check fees match coin currency */
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency_nbo (&issue->properties.value,
- &issue->properties.fee_withdraw));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency_nbo (&issue->properties.value,
- &issue->properties.fee_deposit));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency_nbo (&issue->properties.value,
- &issue->properties.fee_refresh));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency_nbo (&issue->properties.value,
- &issue->properties.fee_refund));
-
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "denomination_insert",
- params);
-}
-
-
-/**
- * Fetch information about a denomination key.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param denom_pub_hash hash of the public key used for signing coins of this denomination
- * @param[out] issue set to issue information with value, fees and other info about the coin
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_denomination_info (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash,
- struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("master_pub",
- &issue->properties.master),
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- &issue->signature),
- TALER_PQ_result_spec_absolute_time_nbo ("valid_from",
- &issue->properties.start),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_withdraw",
- &issue->properties.expire_withdraw),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_deposit",
- &issue->properties.expire_deposit),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_legal",
- &issue->properties.expire_legal),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("coin",
- &issue->properties.value),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_withdraw",
- &issue->properties.fee_withdraw),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_deposit",
- &issue->properties.fee_deposit),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refresh",
- &issue->properties.fee_refresh),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refund",
- &issue->properties.fee_refund),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "denomination_get",
- params,
- rs);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- return qs;
- issue->properties.purpose.size = htonl (sizeof (struct
- TALER_DenominationKeyValidityPS));
- issue->properties.purpose.purpose = htonl (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY);
- issue->properties.denom_hash = *denom_pub_hash;
- return qs;
-}
-
-
-/**
- * Closure for #domination_cb_helper()
- */
-struct DenomIteratorContext
-{
- /**
- * Function to call with the results.
- */
- TALER_EXCHANGEDB_DenominationCallback cb;
-
- /**
- * Closure to pass to @e cb
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-};
-
-
-/**
- * Helper function for #postgres_iterate_denomination_info().
- * Calls the callback with each denomination key.
- *
- * @param cls a `struct DenomIteratorContext`
- * @param result db results
- * @param num_results number of results in @a result
- */
-static void
-domination_cb_helper (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct DenomIteratorContext *dic = cls;
- struct PostgresClosure *pg = dic->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_DenominationKeyInformationP issue;
- struct TALER_DenominationPublicKey denom_pub;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("master_pub",
- &issue.properties.master),
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- &issue.signature),
- TALER_PQ_result_spec_absolute_time_nbo ("valid_from",
- &issue.properties.start),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_withdraw",
- &issue.properties.expire_withdraw),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_deposit",
- &issue.properties.expire_deposit),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_legal",
- &issue.properties.expire_legal),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("coin",
- &issue.properties.value),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_withdraw",
- &issue.properties.fee_withdraw),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_deposit",
- &issue.properties.fee_deposit),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refresh",
- &issue.properties.fee_refresh),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refund",
- &issue.properties.fee_refund),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- return;
- }
- issue.properties.purpose.size
- = htonl (sizeof (struct TALER_DenominationKeyValidityPS));
- issue.properties.purpose.purpose
- = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY);
- GNUNET_CRYPTO_rsa_public_key_hash (denom_pub.rsa_public_key,
- &issue.properties.denom_hash);
- dic->cb (dic->cb_cls,
- &denom_pub,
- &issue);
- GNUNET_CRYPTO_rsa_public_key_free (denom_pub.rsa_public_key);
- }
-}
-
-
-/**
- * Fetch information about all known denomination keys.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param cb function to call on each denomination key
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_iterate_denomination_info (void *cls,
- TALER_EXCHANGEDB_DenominationCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pc = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- struct DenomIteratorContext dic = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pc
- };
-
- return GNUNET_PQ_eval_prepared_multi_select (postgres_get_session (pc)->conn,
- "denomination_iterate",
- params,
- &domination_cb_helper,
- &dic);
-}
-
-
-/**
- * Get the summary of a reserve.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session the database connection handle
- * @param[in,out] reserve the reserve data. The public key of the reserve should be
- * set in this structure; it is used to query the database. The balance
- * and expiration are then filled accordingly.
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_reserves_get (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- struct TALER_EXCHANGEDB_Reserve *reserve)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance", &reserve->balance),
- TALER_PQ_result_spec_absolute_time ("expiration_date", &reserve->expiry),
- TALER_PQ_result_spec_absolute_time ("gc_date", &reserve->gc),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "reserves_get",
- params,
- rs);
-}
-
-
-/**
- * Updates a reserve with the data from the given reserve structure.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session the database connection
- * @param reserve the reserve structure whose data will be used to update the
- * corresponding record in the database.
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-reserves_update (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_EXCHANGEDB_Reserve *reserve)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_absolute_time (&reserve->expiry),
- TALER_PQ_query_param_absolute_time (&reserve->gc),
- TALER_PQ_query_param_amount (&reserve->balance),
- GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "reserve_update",
- params);
-}
-
-
-/**
- * Insert an incoming transaction into reserves. New reserves are also created
- * through this function.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session the database connection handle
- * @param reserve_pub public key of the reserve
- * @param balance the amount that has to be added to the reserve
- * @param execution_time when was the amount added
- * @param sender_account_details account information for the sender (payto://-URL)
- * @param exchange_account_section name of the section in the configuration for the exchange's
- * account into which the deposit was made
- * @param wire_ref unique reference identifying the wire transfer
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_reserves_in_insert (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_Amount *balance,
- struct GNUNET_TIME_Absolute execution_time,
- const char *sender_account_details,
- const char *exchange_account_section,
- uint64_t wire_ref)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus reserve_exists;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_EXCHANGEDB_Reserve reserve;
- struct GNUNET_TIME_Absolute expiry;
-
- reserve.pub = *reserve_pub;
- reserve_exists = postgres_reserves_get (cls,
- session,
- &reserve);
- if (0 > reserve_exists)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == reserve_exists);
- return reserve_exists;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Creating reserve %s with expiration in %s\n",
- TALER_B2S (reserve_pub),
- GNUNET_STRINGS_relative_time_to_string (
- pg->idle_reserve_expiration_time,
- GNUNET_NO));
- expiry = GNUNET_TIME_absolute_add (execution_time,
- pg->idle_reserve_expiration_time);
- (void) GNUNET_TIME_round_abs (&expiry);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == reserve_exists)
- {
- /* New reserve, create balance for the first time; we do this
- before adding the actual transaction to "reserves_in", as
- for a new reserve it can't be a duplicate 'add' operation,
- and as the 'add' operation may need the reserve entry
- as a foreign key. */struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_string (sender_account_details),
- TALER_PQ_query_param_amount (balance),
- TALER_PQ_query_param_absolute_time (&expiry),
- TALER_PQ_query_param_absolute_time (&expiry),
- GNUNET_PQ_query_param_end
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Reserve does not exist; creating a new one\n");
- qs = GNUNET_PQ_eval_prepared_non_select (session->conn,
- "reserve_create",
- params);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
- return qs;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- /* Maybe DB did not detect serializiability error already,
- but clearly there must be one. Still odd. */
- GNUNET_break (0);
- return GNUNET_DB_STATUS_SOFT_ERROR;
- }
- }
- /* Create new incoming transaction, "ON CONFLICT DO NOTHING"
- is used to guard against duplicates. */
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&reserve.pub),
- GNUNET_PQ_query_param_uint64 (&wire_ref),
- TALER_PQ_query_param_amount (balance),
- GNUNET_PQ_query_param_string (exchange_account_section),
- GNUNET_PQ_query_param_string (sender_account_details),
- TALER_PQ_query_param_absolute_time (&execution_time),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (session->conn,
- "reserves_in_add_transaction",
- params);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
- return qs;
- }
- }
-
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == reserve_exists)
- {
- /* If the reserve already existed, we need to still update the
- balance; we do this after checking for duplication, as
- otherwise we might have to actually pay the cost to roll this
- back for duplicate transactions; like this, we should virtually
- never actually have to rollback anything. */struct TALER_EXCHANGEDB_Reserve updated_reserve;
-
- updated_reserve.pub = reserve.pub;
- if (GNUNET_OK !=
- TALER_amount_add (&updated_reserve.balance,
- &reserve.balance,
- balance))
- {
- /* currency overflow or incompatible currency */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Attempt to deposit incompatible amount into reserve\n");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- updated_reserve.expiry = GNUNET_TIME_absolute_max (expiry,
- reserve.expiry);
- (void) GNUNET_TIME_round_abs (&updated_reserve.expiry);
- updated_reserve.gc = GNUNET_TIME_absolute_max (updated_reserve.expiry,
- reserve.gc);
- (void) GNUNET_TIME_round_abs (&updated_reserve.gc);
- return reserves_update (cls,
- session,
- &updated_reserve);
- }
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
-
-
-/**
- * Obtain the most recent @a wire_reference that was inserted via @e reserves_in_insert.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session the database session handle
- * @param exchange_account_name name of the section in the exchange's configuration
- * for the account that we are tracking here
- * @param[out] wire_reference set to unique reference identifying the wire transfer
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_latest_reserve_in_reference (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const char *exchange_account_name,
- uint64_t *wire_reference)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (exchange_account_name),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("wire_reference",
- wire_reference),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "reserves_in_get_latest_wire_reference",
- params,
- rs);
-}
-
-
-/**
- * Locate the response for a /reserve/withdraw request under the
- * key of the hash of the blinded message.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session database connection to use
- * @param h_blind hash of the blinded coin to be signed (will match
- * `h_coin_envelope` in the @a collectable to be returned)
- * @param collectable corresponding collectable coin (blind signature)
- * if a coin is found
- * @return statement execution status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_withdraw_info (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *h_blind,
- struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (h_blind),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &collectable->denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &collectable->sig.rsa_signature),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
- &collectable->reserve_sig),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &collectable->reserve_pub),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &collectable->amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
- &collectable->withdraw_fee),
- GNUNET_PQ_result_spec_end
- };
-#if EXPLICIT_LOCKS
- struct GNUNET_PQ_QueryParam no_params[] = {
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- if (0 > (qs = GNUNET_PQ_eval_prepared_non_select (session->conn,
- "lock_withdraw",
- no_params)))
- return qs;
-#endif
- collectable->h_coin_envelope = *h_blind;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "get_withdraw_info",
- params,
- rs);
-}
-
-
-/**
- * Store collectable bit coin under the corresponding
- * hash of the blinded message.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session database connection to use
- * @param collectable corresponding collectable coin (blind signature)
- * if a coin is found
- * @return query execution status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_withdraw_info (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
-{
- struct PostgresClosure *pg = cls;
- struct TALER_EXCHANGEDB_Reserve reserve;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Absolute expiry;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&collectable->h_coin_envelope),
- GNUNET_PQ_query_param_auto_from_type (&collectable->denom_pub_hash),
- GNUNET_PQ_query_param_rsa_signature (collectable->sig.rsa_signature),
- GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_pub),
- GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_sig),
- TALER_PQ_query_param_absolute_time (&now),
- TALER_PQ_query_param_amount (&collectable->amount_with_fee),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
- qs = GNUNET_PQ_eval_prepared_non_select (session->conn,
- "insert_withdraw_info",
- params);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
-
- /* update reserve balance */
- reserve.pub = collectable->reserve_pub;
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- (qs = postgres_reserves_get (cls,
- session,
- &reserve)))
- {
- /* Should have been checked before we got here... */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
- }
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&reserve.balance,
- &reserve.balance,
- &collectable->amount_with_fee))
- {
- /* The reserve history was checked to make sure there is enough of a balance
- left before we tried this; however, concurrent operations may have changed
- the situation by now. We should re-try the transaction. */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Withdrawal from reserve `%s' refused due to balance mismatch. Retrying.\n",
- TALER_B2S (&collectable->reserve_pub));
- return GNUNET_DB_STATUS_SOFT_ERROR;
- }
- expiry = GNUNET_TIME_absolute_add (now,
- pg->legal_reserve_expiration_time);
- reserve.gc = GNUNET_TIME_absolute_max (expiry,
- reserve.gc);
- (void) GNUNET_TIME_round_abs (&reserve.gc);
- qs = reserves_update (cls,
- session,
- &reserve);
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_break (0);
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- }
- return qs;
-}
-
-
-/**
- * Closure for callbacks invoked via #postgres_get_reserve_history.
- */
-struct ReserveHistoryContext
-{
-
- /**
- * Which reserve are we building the history for?
- */
- const struct TALER_ReservePublicKeyP *reserve_pub;
-
- /**
- * Where we build the history.
- */
- struct TALER_EXCHANGEDB_ReserveHistory *rh;
-
- /**
- * Tail of @e rh list.
- */
- struct TALER_EXCHANGEDB_ReserveHistory *rh_tail;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to #GNUNET_SYSERR on serious internal errors during
- * the callbacks.
- */
- int status;
-};
-
-
-/**
- * Append and return a fresh element to the reserve
- * history kept in @a rhc.
- *
- * @param rhc where the history is kept
- * @return the fresh element that was added
- */
-static struct TALER_EXCHANGEDB_ReserveHistory *
-append_rh (struct ReserveHistoryContext *rhc)
-{
- struct TALER_EXCHANGEDB_ReserveHistory *tail;
-
- tail = GNUNET_new (struct TALER_EXCHANGEDB_ReserveHistory);
- if (NULL != rhc->rh_tail)
- {
- rhc->rh_tail->next = tail;
- rhc->rh_tail = tail;
- }
- else
- {
- rhc->rh_tail = tail;
- rhc->rh = tail;
- }
- return tail;
-}
-
-
-/**
- * Add bank transfers to result set for #postgres_get_reserve_history.
- *
- * @param cls a `struct ReserveHistoryContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-add_bank_to_exchange (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReserveHistoryContext *rhc = cls;
- struct PostgresClosure *pg = rhc->pg;
-
- while (0 < num_results)
- {
- struct TALER_EXCHANGEDB_BankTransfer *bt;
- struct TALER_EXCHANGEDB_ReserveHistory *tail;
-
- bt = GNUNET_new (struct TALER_EXCHANGEDB_BankTransfer);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_variable_size ("wire_reference",
- &bt->wire_reference,
- &bt->wire_reference_size),
- TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
- &bt->amount),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &bt->execution_date),
- GNUNET_PQ_result_spec_string ("sender_account_details",
- &bt->sender_account_details),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- --num_results))
- {
- GNUNET_break (0);
- GNUNET_free (bt);
- rhc->status = GNUNET_SYSERR;
- return;
- }
- }
- bt->reserve_pub = *rhc->reserve_pub;
- tail = append_rh (rhc);
- tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE;
- tail->details.bank = bt;
- } /* end of 'while (0 < rows)' */
-}
-
-
-/**
- * Add coin withdrawals to result set for #postgres_get_reserve_history.
- *
- * @param cls a `struct ReserveHistoryContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-add_withdraw_coin (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReserveHistoryContext *rhc = cls;
- struct PostgresClosure *pg = rhc->pg;
-
- while (0 < num_results)
- {
- struct TALER_EXCHANGEDB_CollectableBlindcoin *cbc;
- struct TALER_EXCHANGEDB_ReserveHistory *tail;
-
- cbc = GNUNET_new (struct TALER_EXCHANGEDB_CollectableBlindcoin);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
- &cbc->h_coin_envelope),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &cbc->denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &cbc->sig.rsa_signature),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
- &cbc->reserve_sig),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &cbc->amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
- &cbc->withdraw_fee),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- --num_results))
- {
- GNUNET_break (0);
- GNUNET_free (cbc);
- rhc->status = GNUNET_SYSERR;
- return;
- }
- }
- cbc->reserve_pub = *rhc->reserve_pub;
- tail = append_rh (rhc);
- tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COIN;
- tail->details.withdraw = cbc;
- }
-}
-
-
-/**
- * Add recoups to result set for #postgres_get_reserve_history.
- *
- * @param cls a `struct ReserveHistoryContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-add_recoup (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReserveHistoryContext *rhc = cls;
- struct PostgresClosure *pg = rhc->pg;
-
- while (0 < num_results)
- {
- struct TALER_EXCHANGEDB_Recoup *recoup;
- struct TALER_EXCHANGEDB_ReserveHistory *tail;
-
- recoup = GNUNET_new (struct TALER_EXCHANGEDB_Recoup);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &recoup->value),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &recoup->coin.coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &recoup->coin_blind),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &recoup->coin_sig),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &recoup->timestamp),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &recoup->coin.denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &recoup->coin.denom_sig.
- rsa_signature),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- --num_results))
- {
- GNUNET_break (0);
- GNUNET_free (recoup);
- rhc->status = GNUNET_SYSERR;
- return;
- }
- }
- recoup->reserve_pub = *rhc->reserve_pub;
- tail = append_rh (rhc);
- tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN;
- tail->details.recoup = recoup;
- } /* end of 'while (0 < rows)' */
-}
-
-
-/**
- * Add exchange-to-bank transfers to result set for
- * #postgres_get_reserve_history.
- *
- * @param cls a `struct ReserveHistoryContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-add_exchange_to_bank (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReserveHistoryContext *rhc = cls;
- struct PostgresClosure *pg = rhc->pg;
-
- while (0 < num_results)
- {
- struct TALER_EXCHANGEDB_ClosingTransfer *closing;
- struct TALER_EXCHANGEDB_ReserveHistory *tail;
-
- closing = GNUNET_new (struct TALER_EXCHANGEDB_ClosingTransfer);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &closing->amount),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
- &closing->closing_fee),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &closing->execution_date),
- GNUNET_PQ_result_spec_string ("receiver_account",
- &closing->receiver_account_details),
- GNUNET_PQ_result_spec_auto_from_type ("wtid",
- &closing->wtid),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- --num_results))
- {
- GNUNET_break (0);
- GNUNET_free (closing);
- rhc->status = GNUNET_SYSERR;
- return;
- }
- }
- closing->reserve_pub = *rhc->reserve_pub;
- tail = append_rh (rhc);
- tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK;
- tail->details.closing = closing;
- } /* end of 'while (0 < rows)' */
-}
-
-
-/**
- * Get all of the transaction history associated with the specified
- * reserve.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session connection to use
- * @param reserve_pub public key of the reserve
- * @param[out] rhp set to known transaction history (NULL if reserve is unknown)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_reserve_history (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- struct TALER_EXCHANGEDB_ReserveHistory **rhp)
-{
- struct PostgresClosure *pg = cls;
- struct ReserveHistoryContext rhc;
- struct
- {
- /**
- * Name of the prepared statement to run.
- */
- const char *statement;
- /**
- * Function to use to process the results.
- */
- GNUNET_PQ_PostgresResultHandler cb;
- } work[] = {
- /** #TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE */
- { "reserves_in_get_transactions",
- add_bank_to_exchange },
- /** #TALER_EXCHANGEDB_RO_WITHDRAW_COIN */
- { "get_reserves_out",
- &add_withdraw_coin },
- /** #TALER_EXCHANGEDB_RO_RECOUP_COIN */
- { "recoup_by_reserve",
- &add_recoup },
- /** #TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK */
- { "close_by_reserve",
- &add_exchange_to_bank },
- /* List terminator */
- { NULL,
- NULL }
- };
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_end
- };
-
- rhc.reserve_pub = reserve_pub;
- rhc.rh = NULL;
- rhc.rh_tail = NULL;
- rhc.pg = pg;
- rhc.status = GNUNET_OK;
- qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; /* make static analysis happy */
- for (unsigned int i = 0; NULL != work[i].cb; i++)
- {
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- work[i].statement,
- params,
- work[i].cb,
- &rhc);
- if ( (0 > qs) ||
- (GNUNET_OK != rhc.status) )
- break;
- }
- if ( (qs < 0) ||
- (rhc.status != GNUNET_OK) )
- {
- common_free_reserve_history (cls,
- rhc.rh);
- rhc.rh = NULL;
- if (qs >= 0)
- {
- /* status == SYSERR is a very hard error... */
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- *rhp = rhc.rh;
- return qs;
-}
-
-
-/**
- * Check if we have the specified deposit already in the database.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session database connection
- * @param deposit deposit to search for
- * @param check_extras whether to check extra fields match or not
- * @return 1 if we know this operation,
- * 0 if this exact deposit is unknown to us,
- * otherwise transaction error status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_have_deposit (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_EXCHANGEDB_Deposit *deposit,
- int check_extras)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub),
- GNUNET_PQ_query_param_auto_from_type (&deposit->h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (&deposit->merchant_pub),
- GNUNET_PQ_query_param_end
- };
- struct TALER_EXCHANGEDB_Deposit deposit2;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &deposit2.amount_with_fee),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &deposit2.timestamp),
- TALER_PQ_result_spec_absolute_time ("refund_deadline",
- &deposit2.refund_deadline),
- TALER_PQ_result_spec_absolute_time ("wire_deadline",
- &deposit2.wire_deadline),
- GNUNET_PQ_result_spec_auto_from_type ("h_wire",
- &deposit2.h_wire),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-#if EXPLICIT_LOCKS
- struct GNUNET_PQ_QueryParam no_params[] = {
- GNUNET_PQ_query_param_end
- };
-
- if (0 > (qs = GNUNET_PQ_eval_prepared_non_select (session->conn,
- "lock_deposit",
- no_params)))
- return qs;
-#endif
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Getting deposits for coin %s\n",
- TALER_B2S (&deposit->coin.coin_pub));
- qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "get_deposit",
- params,
- rs);
- if (0 >= qs)
- return qs;
- /* Now we check that the other information in @a deposit
- also matches, and if not report inconsistencies. */
- if ( ( (check_extras) &&
- ( (0 != TALER_amount_cmp (&deposit->amount_with_fee,
- &deposit2.amount_with_fee)) ||
- (deposit->timestamp.abs_value_us !=
- deposit2.timestamp.abs_value_us) ) ) ||
- (deposit->refund_deadline.abs_value_us !=
- deposit2.refund_deadline.abs_value_us) ||
- (0 != GNUNET_memcmp (&deposit->h_wire,
- &deposit2.h_wire) ) )
- {
- /* Inconsistencies detected! Does not match! (We might want to
- expand the API with a 'get_deposit' function to return the
- original transaction details to be used for an error message
- in the future!) #3838 */
- return 0; /* Counts as if the transaction was not there */
- }
- return 1;
-}
-
-
-/**
- * Mark a deposit as tiny, thereby declaring that it cannot be
- * executed by itself and should no longer be returned by
- * @e iterate_ready_deposits()
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param rowid identifies the deposit row to modify
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_mark_deposit_tiny (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t rowid)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&rowid),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "mark_deposit_tiny",
- params);
-}
-
-
-/**
- * Test if a deposit was marked as done, thereby declaring that it cannot be
- * refunded anymore.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param coin_pub the coin to check for deposit
- * @param merchant_pub merchant to receive the deposit
- * @param h_contract_terms contract terms of the deposit
- * @param h_wire hash of the merchant's wire details
- * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if is is marked done,
- * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if not,
- * otherwise transaction error status (incl. deposit unknown)
- */
-static enum GNUNET_DB_QueryStatus
-postgres_test_deposit_done (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct GNUNET_HashCode *h_wire)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_auto_from_type (merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (h_wire),
- GNUNET_PQ_query_param_end
- };
- uint8_t done = 0;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("done",
- &done),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- (void) cls;
- qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "test_deposit_done",
- params,
- rs);
- if (qs < 0)
- return qs;
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- return GNUNET_DB_STATUS_HARD_ERROR; /* deposit MUST exist */
- return (done
- ? GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
- : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS);
-}
-
-
-/**
- * Mark a deposit as done, thereby declaring that it cannot be
- * executed at all anymore, and should no longer be returned by
- * @e iterate_ready_deposits() or @e iterate_matching_deposits().
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param rowid identifies the deposit row to modify
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_mark_deposit_done (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t rowid)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&rowid),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "mark_deposit_done",
- params);
-}
-
-
-/**
- * Obtain information about deposits that are ready to be executed.
- * Such deposits must not be marked as "tiny" or "done", and the
- * execution time must be in the past.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param deposit_cb function to call for ONE such deposit
- * @param deposit_cb_cls closure for @a deposit_cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_ready_deposit (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- TALER_EXCHANGEDB_DepositIterator deposit_cb,
- void *deposit_cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_end
- };
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount deposit_fee;
- struct GNUNET_TIME_Absolute wire_deadline;
- struct GNUNET_HashCode h_contract_terms;
- struct TALER_MerchantPublicKeyP merchant_pub;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- uint64_t serial_id;
- json_t *wire;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
- &serial_id),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- &deposit_fee),
- TALER_PQ_result_spec_absolute_time ("wire_deadline",
- &wire_deadline),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
- &merchant_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin_pub),
- TALER_PQ_result_spec_json ("wire",
- &wire),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- (void) GNUNET_TIME_round_abs (&now);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Finding ready deposits by deadline %s (%llu)\n",
- GNUNET_STRINGS_absolute_time_to_string (now),
- (unsigned long long) now.abs_value_us);
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "deposits_get_ready",
- params,
- rs);
- if (qs <= 0)
- return qs;
-
- qs = deposit_cb (deposit_cb_cls,
- serial_id,
- &merchant_pub,
- &coin_pub,
- &amount_with_fee,
- &deposit_fee,
- &h_contract_terms,
- wire_deadline,
- wire);
- GNUNET_PQ_cleanup_result (rs);
- return qs;
-}
-
-
-/**
- * Closure for #match_deposit_cb().
- */
-struct MatchingDepositContext
-{
- /**
- * Function to call for each result
- */
- TALER_EXCHANGEDB_DepositIterator deposit_cb;
-
- /**
- * Closure for @e deposit_cb.
- */
- void *deposit_cb_cls;
-
- /**
- * Public key of the merchant against which we are matching.
- */
- const struct TALER_MerchantPublicKeyP *merchant_pub;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Maximum number of results to return.
- */
- uint32_t limit;
-
- /**
- * Loop counter, actual number of results returned.
- */
- unsigned int i;
-
- /**
- * Set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function for #postgres_iterate_matching_deposits().
- * To be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct MatchingDepositContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-match_deposit_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct MatchingDepositContext *mdc = cls;
- struct PostgresClosure *pg = mdc->pg;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Found %u/%u matching deposits\n",
- num_results,
- mdc->limit);
- num_results = GNUNET_MIN (num_results,
- mdc->limit);
- for (mdc->i = 0; mdc->i<num_results; mdc->i++)
- {
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount deposit_fee;
- struct GNUNET_TIME_Absolute wire_deadline;
- struct GNUNET_HashCode h_contract_terms;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- uint64_t serial_id;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
- &serial_id),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- &deposit_fee),
- TALER_PQ_result_spec_absolute_time ("wire_deadline",
- &wire_deadline),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin_pub),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- mdc->i))
- {
- GNUNET_break (0);
- mdc->status = GNUNET_SYSERR;
- return;
- }
- qs = mdc->deposit_cb (mdc->deposit_cb_cls,
- serial_id,
- mdc->merchant_pub,
- &coin_pub,
- &amount_with_fee,
- &deposit_fee,
- &h_contract_terms,
- wire_deadline,
- NULL);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- break;
- }
-}
-
-
-/**
- * Obtain information about other pending deposits for the same
- * destination. Those deposits must not already be "done".
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param h_wire destination of the wire transfer
- * @param merchant_pub public key of the merchant
- * @param deposit_cb function to call for each deposit
- * @param deposit_cb_cls closure for @a deposit_cb
- * @param limit maximum number of matching deposits to return
- * @return transaction status code, if positive:
- * number of rows processed, 0 if none exist
- */
-static enum GNUNET_DB_QueryStatus
-postgres_iterate_matching_deposits (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *h_wire,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- TALER_EXCHANGEDB_DepositIterator deposit_cb,
- void *deposit_cb_cls,
- uint32_t limit)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (h_wire),
- GNUNET_PQ_query_param_end
- };
- struct MatchingDepositContext mdc;
- enum GNUNET_DB_QueryStatus qs;
-
- mdc.deposit_cb = deposit_cb;
- mdc.deposit_cb_cls = deposit_cb_cls;
- mdc.merchant_pub = merchant_pub;
- mdc.pg = pg;
- mdc.limit = limit;
- mdc.status = GNUNET_OK;
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "deposits_iterate_matching",
- params,
- &match_deposit_cb,
- &mdc);
- if (GNUNET_OK != mdc.status)
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (qs >= 0)
- return mdc.i;
- return qs;
-}
-
-
-/**
- * Retrieve the record for a known coin.
- *
- * @param cls the plugin closure
- * @param session the database session handle
- * @param coin_pub the public key of the coin to search for
- * @param coin_info place holder for the returned coin information object
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_known_coin (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- struct TALER_CoinPublicInfo *coin_info)
-{
- struct PostgresClosure *pc = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &coin_info->denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &coin_info->denom_sig.rsa_signature),
- GNUNET_PQ_result_spec_end
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Getting known coin data for coin %s\n",
- TALER_B2S (coin_pub));
- coin_info->coin_pub = *coin_pub;
- if (NULL == session)
- session = postgres_get_session (pc);
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "get_known_coin",
- params,
- rs);
-}
-
-
-/**
- * Retrieve the denomination of a known coin.
- *
- * @param cls the plugin closure
- * @param session the database session handle
- * @param coin_pub the public key of the coin to search for
- * @param[out] denom_hash where to store the hash of the coins denomination
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_coin_denomination (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- struct GNUNET_HashCode *denom_hash)
-{
- struct PostgresClosure *pc = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- denom_hash),
- GNUNET_PQ_result_spec_end
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Getting coin denomination of coin %s\n",
- TALER_B2S (coin_pub));
- if (NULL == session)
- session = postgres_get_session (pc);
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "get_coin_denomination",
- params,
- rs);
-}
-
-
-/**
- * Insert a coin we know of into the DB. The coin can then be
- * referenced by tables for deposits, refresh and refund
- * functionality.
- *
- * @param cls plugin closure
- * @param session the shared database session
- * @param coin_info the public coin info
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-insert_known_coin (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinPublicInfo *coin_info)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&coin_info->coin_pub),
- GNUNET_PQ_query_param_auto_from_type (&coin_info->denom_pub_hash),
- GNUNET_PQ_query_param_rsa_signature (coin_info->denom_sig.rsa_signature),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Creating known coin %s\n",
- TALER_B2S (&coin_info->coin_pub));
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "insert_known_coin",
- params);
-}
-
-
-/**
- * Count the number of known coins by denomination.
- *
- * @param cls database connection plugin state
- * @param session database session
- * @param denom_pub_hash denomination to count by
- * @return number of coins if non-negative, otherwise an `enum GNUNET_DB_QueryStatus`
- */
-static long long
-postgres_count_known_coins (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash)
-{
- uint64_t count;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("count",
- &count),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- (void) cls;
- qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "count_known_coins",
- params,
- rs);
- if (0 > qs)
- return (long long) qs;
- return (long long) count;
-}
-
-
-/**
- * Make sure the given @a coin is known to the database.
- *
- * @param cls database connection plugin state
- * @param session database session
- * @param coin the coin that must be made known
- * @return database transaction status, non-negative on success
- */
-static enum GNUNET_DB_QueryStatus
-postgres_ensure_coin_known (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinPublicInfo *coin)
-{
- struct PostgresClosure *pc = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_CoinPublicInfo known_coin;
-#if EXPLICIT_LOCKS
- struct GNUNET_PQ_QueryParam no_params[] = {
- GNUNET_PQ_query_param_end
- };
-
- if (0 > (qs = GNUNET_PQ_eval_prepared_non_select (session->conn,
- "lock_known_coins",
- no_params)))
- return qs;
-#endif
-
- /* check if the coin is already known */
- qs = postgres_get_known_coin (pc,
- session,
- &coin->coin_pub,
- &known_coin);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return GNUNET_SYSERR;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- GNUNET_CRYPTO_rsa_signature_free (known_coin.denom_sig.rsa_signature);
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; /* no change! */
- }
- GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
- /* if not known, insert it */
- qs = insert_known_coin (pc,
- session,
- coin);
- if (0 >= qs)
- {
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- qs = GNUNET_DB_STATUS_HARD_ERROR; /* should be impossible */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- return qs;
-}
-
-
-/**
- * Insert information about deposited coin into the database.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session connection to the database
- * @param deposit deposit information to store
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_deposit (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_EXCHANGEDB_Deposit *deposit)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub),
- TALER_PQ_query_param_amount (&deposit->amount_with_fee),
- TALER_PQ_query_param_absolute_time (&deposit->timestamp),
- TALER_PQ_query_param_absolute_time (&deposit->refund_deadline),
- TALER_PQ_query_param_absolute_time (&deposit->wire_deadline),
- GNUNET_PQ_query_param_auto_from_type (&deposit->merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (&deposit->h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (&deposit->h_wire),
- GNUNET_PQ_query_param_auto_from_type (&deposit->csig),
- TALER_PQ_query_param_json (deposit->receiver_wire_account),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Inserting deposit to be executed at %s (%llu/%llu)\n",
- GNUNET_STRINGS_absolute_time_to_string (deposit->wire_deadline),
- (unsigned long long) deposit->wire_deadline.abs_value_us,
- (unsigned long long) deposit->refund_deadline.abs_value_us);
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "insert_deposit",
- params);
-}
-
-
-/**
- * Insert information about refunded coin into the database.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param refund refund information to store
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_refund (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_EXCHANGEDB_Refund *refund)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&refund->coin.coin_pub),
- GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_sig),
- GNUNET_PQ_query_param_auto_from_type (&refund->details.h_contract_terms),
- GNUNET_PQ_query_param_uint64 (&refund->details.rtransaction_id),
- TALER_PQ_query_param_amount (&refund->details.refund_amount),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (&refund->details.refund_amount,
- &refund->details.refund_fee));
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "insert_refund",
- params);
-}
-
-
-/**
- * Closure for #get_refunds_cb().
- */
-struct SelectRefundContext
-{
- /**
- * Function to call on each result.
- */
- TALER_EXCHANGEDB_RefundCoinCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to #GNUNET_SYSERR on error.
- */
- int status;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct SelectRefundContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-get_refunds_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct SelectRefundContext *srctx = cls;
- struct PostgresClosure *pg = srctx->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_Amount amount_with_fee;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- srctx->status = GNUNET_SYSERR;
- return;
- }
- if (GNUNET_OK !=
- srctx->cb (srctx->cb_cls,
- &amount_with_fee))
- return;
- }
-}
-
-
-/**
- * Select refunds by @a coin_pub, @a merchant_pub and @a h_contract.
- *
- * @param cls closure of plugin
- * @param session database handle to use
- * @param coin_pub coin to get refunds for
- * @param merchant_pub merchant to get refunds for
- * @param h_contract contract (hash) to get refunds for
- * @param cb function to call for each refund found
- * @param cb_cls closure for @a cb
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_refunds_by_coin (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_contract,
- TALER_EXCHANGEDB_RefundCoinCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_auto_from_type (merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (h_contract),
- GNUNET_PQ_query_param_end
- };
- struct SelectRefundContext srctx = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "get_refunds_by_coin_and_contract",
- params,
- &get_refunds_cb,
- &srctx);
- if (GNUNET_SYSERR == srctx.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Lookup refresh melt commitment data under the given @a rc.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session database handle to use, NULL if not run in any transaction
- * @param rc commitment hash to use to locate the operation
- * @param[out] melt where to store the result; note that
- * melt->session.coin.denom_sig will be set to NULL
- * and is not fetched by this routine (as it is not needed by the client)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_melt (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_RefreshCommitmentP *rc,
- struct TALER_EXCHANGEDB_Melt *melt)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (rc),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &melt->session.coin.
- denom_pub_hash),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
- &melt->melt_fee),
- GNUNET_PQ_result_spec_uint32 ("noreveal_index",
- &melt->session.noreveal_index),
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
- &melt->session.coin.coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
- &melt->session.coin_sig),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &melt->session.amount_with_fee),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- melt->session.coin.denom_sig.rsa_signature = NULL;
- if (NULL == session)
- session = postgres_get_session (pg);
- qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "get_melt",
- params,
- rs);
- melt->session.rc = *rc;
- return qs;
-}
-
-
-/**
- * Lookup noreveal index of a previous melt operation under the given
- * @a rc.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session database handle to use
- * @param rc commitment hash to use to locate the operation
- * @param[out] noreveal_index returns the "gamma" value selected by the
- * exchange which is the index of the transfer key that is
- * not to be revealed to the exchange
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_melt_index (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_RefreshCommitmentP *rc,
- uint32_t *noreveal_index)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (rc),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint32 ("noreveal_index",
- noreveal_index),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "get_melt_index",
- params,
- rs);
-}
-
-
-/**
- * Store new refresh melt commitment data.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session database handle to use
- * @param refresh_session session data to store
- * @return query status for the transaction
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_melt (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_EXCHANGEDB_Refresh *refresh_session)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&refresh_session->rc),
- GNUNET_PQ_query_param_auto_from_type (&refresh_session->coin.coin_pub),
- GNUNET_PQ_query_param_auto_from_type (&refresh_session->coin_sig),
- TALER_PQ_query_param_amount (&refresh_session->amount_with_fee),
- GNUNET_PQ_query_param_uint32 (&refresh_session->noreveal_index),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "insert_melt",
- params);
-}
-
-
-/**
- * Store in the database which coin(s) the wallet wanted to create
- * in a given refresh operation and all of the other information
- * we learned or created in the /refresh/reveal step.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session database connection
- * @param rc identify commitment and thus refresh operation
- * @param num_rrcs number of coins to generate, size of the @a rrcs array
- * @param rrcs information about the new coins
- * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
- * @param tprivs transfer private keys to store
- * @param tp public key to store
- * @return query status for the transaction
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_refresh_reveal (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_RefreshCommitmentP *rc,
- uint32_t num_rrcs,
- const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
- unsigned int num_tprivs,
- const struct TALER_TransferPrivateKeyP *tprivs,
- const struct TALER_TransferPublicKeyP *tp)
-{
- (void) cls;
- if (TALER_CNC_KAPPA != num_tprivs + 1)
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- for (uint32_t i = 0; i<num_rrcs; i++)
- {
- const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
- struct GNUNET_HashCode denom_pub_hash;
- struct GNUNET_HashCode h_coin_ev;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (rc),
- GNUNET_PQ_query_param_uint32 (&i),
- GNUNET_PQ_query_param_auto_from_type (&rrc->orig_coin_link_sig),
- GNUNET_PQ_query_param_auto_from_type (&denom_pub_hash),
- GNUNET_PQ_query_param_fixed_size (rrc->coin_ev,
- rrc->coin_ev_size),
- GNUNET_PQ_query_param_auto_from_type (&h_coin_ev),
- GNUNET_PQ_query_param_rsa_signature (rrc->coin_sig.rsa_signature),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- GNUNET_CRYPTO_rsa_public_key_hash (rrc->denom_pub.rsa_public_key,
- &denom_pub_hash);
- GNUNET_CRYPTO_hash (rrc->coin_ev,
- rrc->coin_ev_size,
- &h_coin_ev);
- qs = GNUNET_PQ_eval_prepared_non_select (session->conn,
- "insert_refresh_revealed_coin",
- params);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- return qs;
- }
-
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (rc),
- GNUNET_PQ_query_param_auto_from_type (tp),
- GNUNET_PQ_query_param_fixed_size (tprivs,
- num_tprivs * sizeof (struct
- TALER_TransferPrivateKeyP)),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "insert_refresh_transfer_keys",
- params);
- }
-}
-
-
-/**
- * Context where we aggregate data from the database.
- * Closure for #add_revealed_coins().
- */
-struct GetRevealContext
-{
- /**
- * Array of revealed coins we obtained from the DB.
- */
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs;
-
- /**
- * Length of the @a rrcs array.
- */
- unsigned int rrcs_len;
-
- /**
- * Set to an error code if we ran into trouble.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct GetRevealContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_revealed_coins (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct GetRevealContext *grctx = cls;
-
- if (0 == num_results)
- return;
- grctx->rrcs = GNUNET_new_array (num_results,
- struct TALER_EXCHANGEDB_RefreshRevealedCoin);
- grctx->rrcs_len = num_results;
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &grctx->rrcs[i];
- uint32_t off;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint32 ("freshcoin_index",
- &off),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &rrc->denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("link_sig",
- &rrc->orig_coin_link_sig),
- GNUNET_PQ_result_spec_variable_size ("coin_ev",
- (void **) &rrc->coin_ev,
- &rrc->coin_ev_size),
- GNUNET_PQ_result_spec_rsa_signature ("ev_sig",
- &rrc->coin_sig.rsa_signature),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- if (off != i)
- {
- GNUNET_break (0);
- grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- }
-}
-
-
-/**
- * Lookup in the database the coins that we want to
- * create in the given refresh operation.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session database connection
- * @param rc identify commitment and thus refresh operation
- * @param cb function to call with the results
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_refresh_reveal (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_RefreshCommitmentP *rc,
- TALER_EXCHANGEDB_RefreshCallback cb,
- void *cb_cls)
-{
- struct GetRevealContext grctx;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_TransferPublicKeyP tp;
- void *tpriv;
- size_t tpriv_size;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (rc),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("transfer_pub",
- &tp),
- GNUNET_PQ_result_spec_variable_size ("transfer_privs",
- &tpriv,
- &tpriv_size),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- /* First get the coins */
- memset (&grctx,
- 0,
- sizeof (grctx));
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "get_refresh_revealed_coins",
- params,
- &add_revealed_coins,
- &grctx);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- goto cleanup;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- default: /* can have more than one result */
- break;
- }
- switch (grctx.qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- goto cleanup;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* should be impossible */
- break;
- }
-
- /* now also get the transfer keys (public and private) */
- qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "get_refresh_transfer_keys",
- params,
- rs);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- goto cleanup;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- default:
- GNUNET_assert (0);
- }
- if ( (0 != tpriv_size % sizeof (struct TALER_TransferPrivateKeyP)) ||
- (TALER_CNC_KAPPA - 1 != tpriv_size / sizeof (struct
- TALER_TransferPrivateKeyP)) )
- {
- GNUNET_break (0);
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- GNUNET_PQ_cleanup_result (rs);
- goto cleanup;
- }
-
- /* Pass result back to application */
- cb (cb_cls,
- grctx.rrcs_len,
- grctx.rrcs,
- tpriv_size / sizeof (struct TALER_TransferPrivateKeyP),
- (const struct TALER_TransferPrivateKeyP *) tpriv,
- &tp);
- GNUNET_PQ_cleanup_result (rs);
-
-cleanup:
- for (unsigned int i = 0; i < grctx.rrcs_len; i++)
- {
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &grctx.rrcs[i];
-
- if (NULL != rrc->denom_pub.rsa_public_key)
- GNUNET_CRYPTO_rsa_public_key_free (rrc->denom_pub.rsa_public_key);
- if (NULL != rrc->coin_sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (rrc->coin_sig.rsa_signature);
- GNUNET_free_non_null (rrc->coin_ev);
- }
- GNUNET_free_non_null (grctx.rrcs);
-
- return qs;
-}
-
-
-/**
- * Closure for #add_ldl().
- */
-struct LinkDataContext
-{
- /**
- * Function to call on each result.
- */
- TALER_EXCHANGEDB_LinkCallback ldc;
-
- /**
- * Closure for @e ldc.
- */
- void *ldc_cls;
-
- /**
- * Last transfer public key for which we have information in @e last.
- * Only valid if @e last is non-NULL.
- */
- struct TALER_TransferPublicKeyP transfer_pub;
-
- /**
- * Link data for @e transfer_pub
- */
- struct TALER_EXCHANGEDB_LinkList *last;
-
- /**
- * Status, set to #GNUNET_SYSERR on errors,
- */
- int status;
-};
-
-
-/**
- * Free memory of the link data list.
- *
- * @param cls the @e cls of this struct with the plugin-specific state (unused)
- * @param ldl link data list to release
- */
-static void
-free_link_data_list (void *cls,
- struct TALER_EXCHANGEDB_LinkList *ldl)
-{
- struct TALER_EXCHANGEDB_LinkList *next;
-
- (void) cls;
- while (NULL != ldl)
- {
- next = ldl->next;
- if (NULL != ldl->denom_pub.rsa_public_key)
- GNUNET_CRYPTO_rsa_public_key_free (ldl->denom_pub.rsa_public_key);
- if (NULL != ldl->ev_sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (ldl->ev_sig.rsa_signature);
- GNUNET_free (ldl);
- ldl = next;
- }
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct LinkDataContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_ldl (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LinkDataContext *ldctx = cls;
-
- for (int i = num_results - 1; i >= 0; i--)
- {
- struct TALER_EXCHANGEDB_LinkList *pos;
- struct TALER_TransferPublicKeyP transfer_pub;
-
- pos = GNUNET_new (struct TALER_EXCHANGEDB_LinkList);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("transfer_pub",
- &transfer_pub),
- GNUNET_PQ_result_spec_auto_from_type ("link_sig",
- &pos->orig_coin_link_sig),
- GNUNET_PQ_result_spec_rsa_signature ("ev_sig",
- &pos->ev_sig.rsa_signature),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &pos->denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (pos);
- ldctx->status = GNUNET_SYSERR;
- return;
- }
- }
- if ( (NULL != ldctx->last) &&
- (0 == GNUNET_memcmp (&transfer_pub,
- &ldctx->transfer_pub)) )
- {
- pos->next = ldctx->last;
- }
- else
- {
- if (NULL != ldctx->last)
- {
- ldctx->ldc (ldctx->ldc_cls,
- &ldctx->transfer_pub,
- ldctx->last);
- free_link_data_list (cls,
- ldctx->last);
- }
- ldctx->transfer_pub = transfer_pub;
- }
- ldctx->last = pos;
- }
-}
-
-
-/**
- * Obtain the link data of a coin, that is the encrypted link
- * information, the denomination keys and the signatures.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session database connection
- * @param coin_pub public key of the coin
- * @param ldc function to call for each session the coin was melted into
- * @param ldc_cls closure for @a tdc
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_link_data (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- TALER_EXCHANGEDB_LinkCallback ldc,
- void *ldc_cls)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
- struct LinkDataContext ldctx;
-
- ldctx.ldc = ldc;
- ldctx.ldc_cls = ldc_cls;
- ldctx.last = NULL;
- ldctx.status = GNUNET_OK;
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "get_link",
- params,
- &add_ldl,
- &ldctx);
- if (NULL != ldctx.last)
- {
- if (GNUNET_OK == ldctx.status)
- {
- /* call callback one more time! */
- ldc (ldc_cls,
- &ldctx.transfer_pub,
- ldctx.last);
- }
- free_link_data_list (cls,
- ldctx.last);
- ldctx.last = NULL;
- }
- if (GNUNET_OK != ldctx.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for callbacks called from #postgres_get_coin_transactions()
- */
-struct CoinHistoryContext
-{
- /**
- * Head of the coin's history list.
- */
- struct TALER_EXCHANGEDB_TransactionList *head;
-
- /**
- * Public key of the coin we are building the history for.
- */
- const struct TALER_CoinSpendPublicKeyP *coin_pub;
-
- /**
- * Closure for all callbacks of this database plugin.
- */
- void *db_cls;
-
- /**
- * Database session we are using.
- */
- struct TALER_EXCHANGEDB_Session *session;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to transaction status.
- */
- enum GNUNET_DB_QueryStatus status;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct CoinHistoryContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_coin_deposit (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct CoinHistoryContext *chc = cls;
- struct PostgresClosure *pg = chc->pg;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_EXCHANGEDB_DepositListEntry *deposit;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- uint64_t serial_id;
-
- deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &deposit->amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- &deposit->deposit_fee),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &deposit->timestamp),
- TALER_PQ_result_spec_absolute_time ("refund_deadline",
- &deposit->refund_deadline),
- TALER_PQ_result_spec_absolute_time ("wire_deadline",
- &deposit->wire_deadline),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
- &deposit->merchant_pub),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &deposit->h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type ("h_wire",
- &deposit->h_wire),
- TALER_PQ_result_spec_json ("wire",
- &deposit->receiver_wire_account),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &deposit->csig),
- GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
- &serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (deposit);
- chc->status = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
- tl->details.deposit = deposit;
- tl->serial_id = serial_id;
- chc->head = tl;
- }
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct CoinHistoryContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_coin_melt (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct CoinHistoryContext *chc = cls;
- struct PostgresClosure *pg = chc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_MeltListEntry *melt;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- uint64_t serial_id;
-
- melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("rc",
- &melt->rc),
- /* oldcoin_index not needed */
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
- &melt->coin_sig),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &melt->amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
- &melt->melt_fee),
- GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
- &serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (melt);
- chc->status = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_MELT;
- tl->details.melt = melt;
- tl->serial_id = serial_id;
- chc->head = tl;
- }
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct CoinHistoryContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_coin_refund (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct CoinHistoryContext *chc = cls;
- struct PostgresClosure *pg = chc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_RefundListEntry *refund;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- uint64_t serial_id;
-
- refund = GNUNET_new (struct TALER_EXCHANGEDB_RefundListEntry);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
- &refund->merchant_pub),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
- &refund->merchant_sig),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &refund->h_contract_terms),
- GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
- &refund->rtransaction_id),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &refund->refund_amount),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
- &refund->refund_fee),
- GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
- &serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (refund);
- chc->status = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_REFUND;
- tl->details.refund = refund;
- tl->serial_id = serial_id;
- chc->head = tl;
- }
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct CoinHistoryContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_old_coin_recoup (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct CoinHistoryContext *chc = cls;
- struct PostgresClosure *pg = chc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- uint64_t serial_id;
-
- recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &recoup->coin.coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &recoup->coin_sig),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &recoup->coin_blind),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &recoup->value),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &recoup->timestamp),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &recoup->coin.denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &recoup->coin.denom_sig.
- rsa_signature),
- GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
- &serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (recoup);
- chc->status = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- recoup->old_coin_pub = *chc->coin_pub;
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP;
- tl->details.old_coin_recoup = recoup;
- tl->serial_id = serial_id;
- chc->head = tl;
- }
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct CoinHistoryContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_coin_recoup (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct CoinHistoryContext *chc = cls;
- struct PostgresClosure *pg = chc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_RecoupListEntry *recoup;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- uint64_t serial_id;
-
- recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupListEntry);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &recoup->reserve_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &recoup->coin_sig),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &recoup->coin_blind),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &recoup->value),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &recoup->timestamp),
- GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
- &serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (recoup);
- chc->status = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_RECOUP;
- tl->details.recoup = recoup;
- tl->serial_id = serial_id;
- chc->head = tl;
- }
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct CoinHistoryContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_coin_recoup_refresh (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct CoinHistoryContext *chc = cls;
- struct PostgresClosure *pg = chc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- uint64_t serial_id;
-
- recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
- &recoup->old_coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &recoup->coin_sig),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &recoup->coin_blind),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &recoup->value),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &recoup->timestamp),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &recoup->coin.denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &recoup->coin.denom_sig.
- rsa_signature),
- GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
- &serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (recoup);
- chc->status = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- recoup->coin.coin_pub = *chc->coin_pub;
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH;
- tl->details.recoup_refresh = recoup;
- tl->serial_id = serial_id;
- chc->head = tl;
- }
-}
-
-
-/**
- * Work we need to do.
- */
-struct Work
-{
- /**
- * SQL prepared statement name.
- */
- const char *statement;
-
- /**
- * Function to call to handle the result(s).
- */
- GNUNET_PQ_PostgresResultHandler cb;
-};
-
-
-/**
- * Compile a list of all (historic) transactions performed with the given coin
- * (/refresh/melt, /deposit, /refund and /recoup operations).
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session database connection
- * @param coin_pub coin to investigate
- * @param include_recoup should recoup transactions be included in the @a tlp
- * @param[out] tlp set to list of transactions, NULL if coin is fresh
- * @return database transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_coin_transactions (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- int include_recoup,
- struct TALER_EXCHANGEDB_TransactionList **tlp)
-{
- struct PostgresClosure *pg = cls;
- static const struct Work work_op[] = {
- /** #TALER_EXCHANGEDB_TT_DEPOSIT */
- { "get_deposit_with_coin_pub",
- &add_coin_deposit },
- /** #TALER_EXCHANGEDB_TT_MELT */
- { "get_refresh_session_by_coin",
- &add_coin_melt },
- /** #TALER_EXCHANGEDB_TT_REFUND */
- { "get_refunds_by_coin",
- &add_coin_refund },
- { NULL, NULL }
- };
- static const struct Work work_wp[] = {
- /** #TALER_EXCHANGEDB_TT_DEPOSIT */
- { "get_deposit_with_coin_pub",
- &add_coin_deposit },
- /** #TALER_EXCHANGEDB_TT_MELT */
- { "get_refresh_session_by_coin",
- &add_coin_melt },
- /** #TALER_EXCHANGEDB_TT_REFUND */
- { "get_refunds_by_coin",
- &add_coin_refund },
- /** #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP */
- { "recoup_by_old_coin",
- &add_old_coin_recoup },
- /** #TALER_EXCHANGEDB_TT_RECOUP */
- { "recoup_by_coin",
- &add_coin_recoup },
- /** #TALER_EXCHANGEDB_TT_RECOUP_REFRESH */
- { "recoup_by_refreshed_coin",
- &add_coin_recoup_refresh },
- { NULL, NULL }
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
- const struct Work *work;
- struct CoinHistoryContext chc = {
- .head = NULL,
- .status = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
- .coin_pub = coin_pub,
- .session = session,
- .pg = pg,
- .db_cls = cls
- };
-
- work = (GNUNET_YES == include_recoup) ? work_wp : work_op;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Getting transactions for coin %s\n",
- TALER_B2S (coin_pub));
- for (unsigned int i = 0; NULL != work[i].statement; i++)
- {
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- work[i].statement,
- params,
- work[i].cb,
- &chc);
- if ( (0 > qs) ||
- (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != chc.status) )
- {
- if (NULL != chc.head)
- common_free_coin_transaction_list (cls,
- chc.head);
- *tlp = NULL;
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != chc.status)
- qs = chc.status;
- return qs;
- }
- }
- *tlp = chc.head;
- if (NULL == chc.head)
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
-
-
-/**
- * Closure for #handle_wt_result.
- */
-struct WireTransferResultContext
-{
- /**
- * Function to call on each result.
- */
- TALER_EXCHANGEDB_AggregationDataCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to #GNUNET_SYSERR on serious errors.
- */
- int status;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results. Helper function
- * for #postgres_lookup_wire_transfer().
- *
- * @param cls closure of type `struct WireTransferResultContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-handle_wt_result (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct WireTransferResultContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint64_t rowid;
- struct GNUNET_HashCode h_contract_terms;
- struct GNUNET_HashCode h_wire;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_MerchantPublicKeyP merchant_pub;
- struct GNUNET_TIME_Absolute exec_time;
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount deposit_fee;
- struct TALER_DenominationPublicKey denom_pub;
- json_t *wire;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("aggregation_serial_id", &rowid),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &h_contract_terms),
- TALER_PQ_result_spec_json ("wire", &wire),
- GNUNET_PQ_result_spec_auto_from_type ("h_wire", &h_wire),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub", &coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", &merchant_pub),
- TALER_PQ_result_spec_absolute_time ("execution_date", &exec_time),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", &deposit_fee),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->status = GNUNET_SYSERR;
- return;
- }
- ctx->cb (ctx->cb_cls,
- rowid,
- &merchant_pub,
- &h_wire,
- wire,
- exec_time,
- &h_contract_terms,
- &denom_pub,
- &coin_pub,
- &amount_with_fee,
- &deposit_fee);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Lookup the list of Taler transactions that were aggregated
- * into a wire transfer by the respective @a wtid.
- *
- * @param cls closure
- * @param session database connection
- * @param wtid the raw wire transfer identifier we used
- * @param cb function to call on each transaction found
- * @param cb_cls closure for @a cb
- * @return query status of the transaction
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_wire_transfer (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- TALER_EXCHANGEDB_AggregationDataCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (wtid),
- GNUNET_PQ_query_param_end
- };
- struct WireTransferResultContext ctx;
- enum GNUNET_DB_QueryStatus qs;
-
- ctx.cb = cb;
- ctx.cb_cls = cb_cls;
- ctx.pg = pg;
- ctx.status = GNUNET_OK;
- /* check if the melt record exists and get it */
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "lookup_transactions",
- params,
- &handle_wt_result,
- &ctx);
- if (GNUNET_OK != ctx.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Try to find the wire transfer details for a deposit operation.
- * If we did not execute the deposit yet, return when it is supposed
- * to be executed.
- *
- * @param cls closure
- * @param session database connection
- * @param h_contract_terms hash of the proposal data
- * @param h_wire hash of merchant wire details
- * @param coin_pub public key of deposited coin
- * @param merchant_pub merchant public key
- * @param cb function to call with the result
- * @param cb_cls closure to pass to @a cb
- * @return transaction status code
- - */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_transfer_by_deposit (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct GNUNET_HashCode *h_wire,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- TALER_EXCHANGEDB_WireTransferByCoinCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (h_wire),
- GNUNET_PQ_query_param_auto_from_type (merchant_pub),
- GNUNET_PQ_query_param_end
- };
- struct TALER_WireTransferIdentifierRawP wtid;
- struct GNUNET_TIME_Absolute exec_time;
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount deposit_fee;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("wtid_raw", &wtid),
- TALER_PQ_result_spec_absolute_time ("execution_date", &exec_time),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", &deposit_fee),
- GNUNET_PQ_result_spec_end
- };
-
- /* check if the melt record exists and get it */
- qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "lookup_deposit_wtid",
- params,
- rs);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- cb (cb_cls,
- &wtid,
- &amount_with_fee,
- &deposit_fee,
- exec_time);
- return qs;
- }
- if (0 > qs)
- return qs;
-
- GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "lookup_deposit_wtid returned 0 matching rows\n");
- {
- /* Check if transaction exists in deposits, so that we just
- do not have a WTID yet, if so, do call the CB with a NULL wtid
- and return #GNUNET_YES! */
- struct GNUNET_PQ_QueryParam params2[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_auto_from_type (merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (h_wire),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_TIME_Absolute exec_time;
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount deposit_fee;
- struct GNUNET_PQ_ResultSpec rs2[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", &deposit_fee),
- TALER_PQ_result_spec_absolute_time ("wire_deadline", &exec_time),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "get_deposit_for_wtid",
- params2,
- rs2);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- /* Ok, we're aware of the transaction, but it has not yet been
- executed */
- cb (cb_cls,
- NULL,
- &amount_with_fee,
- &deposit_fee,
- exec_time);
- return qs;
- }
- return qs;
- }
-}
-
-
-/**
- * Function called to insert aggregation information into the DB.
- *
- * @param cls closure
- * @param session database connection
- * @param wtid the raw wire transfer identifier we used
- * @param deposit_serial_id row in the deposits table for which this is aggregation data
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_aggregation_tracking (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- unsigned long long deposit_serial_id)
-{
- uint64_t rid = deposit_serial_id;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&rid),
- GNUNET_PQ_query_param_auto_from_type (wtid),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "insert_aggregation_tracking",
- params);
-}
-
-
-/**
- * Obtain wire fee from database.
- *
- * @param cls closure
- * @param session database connection
- * @param type type of wire transfer the fee applies for
- * @param date for which date do we want the fee?
- * @param[out] start_date when does the fee go into effect
- * @param[out] end_date when does the fee end being valid
- * @param[out] wire_fee how high is the wire transfer fee
- * @param[out] closing_fee how high is the closing fee
- * @param[out] master_sig signature over the above by the exchange master key
- * @return status of the transaction
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_wire_fee (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const char *type,
- struct GNUNET_TIME_Absolute date,
- struct GNUNET_TIME_Absolute *start_date,
- struct GNUNET_TIME_Absolute *end_date,
- struct TALER_Amount *wire_fee,
- struct TALER_Amount *closing_fee,
- struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (type),
- TALER_PQ_query_param_absolute_time (&date),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_result_spec_absolute_time ("start_date", start_date),
- TALER_PQ_result_spec_absolute_time ("end_date", end_date),
- TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", wire_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee", closing_fee),
- GNUNET_PQ_result_spec_auto_from_type ("master_sig", master_sig),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "get_wire_fee",
- params,
- rs);
-}
-
-
-/**
- * Insert wire transfer fee into database.
- *
- * @param cls closure
- * @param session database connection
- * @param type type of wire transfer this fee applies for
- * @param start_date when does the fee go into effect
- * @param end_date when does the fee end being valid
- * @param wire_fee how high is the wire transfer fee
- * @param closing_fee how high is the closing fee
- * @param master_sig signature over the above by the exchange master key
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_wire_fee (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const char *type,
- struct GNUNET_TIME_Absolute start_date,
- struct GNUNET_TIME_Absolute end_date,
- const struct TALER_Amount *wire_fee,
- const struct TALER_Amount *closing_fee,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (type),
- TALER_PQ_query_param_absolute_time (&start_date),
- TALER_PQ_query_param_absolute_time (&end_date),
- TALER_PQ_query_param_amount (wire_fee),
- TALER_PQ_query_param_amount (closing_fee),
- GNUNET_PQ_query_param_auto_from_type (master_sig),
- GNUNET_PQ_query_param_end
- };
- struct TALER_Amount wf;
- struct TALER_Amount cf;
- struct TALER_MasterSignatureP sig;
- struct GNUNET_TIME_Absolute sd;
- struct GNUNET_TIME_Absolute ed;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = postgres_get_wire_fee (pg,
- session,
- type,
- start_date,
- &sd,
- &ed,
- &wf,
- &cf,
- &sig);
- if (qs < 0)
- return qs;
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- if (0 != GNUNET_memcmp (&sig,
- master_sig))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (0 != TALER_amount_cmp (wire_fee,
- &wf))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (0 != TALER_amount_cmp (closing_fee,
- &cf))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if ( (sd.abs_value_us != start_date.abs_value_us) ||
- (ed.abs_value_us != end_date.abs_value_us) )
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- /* equal record already exists */
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- }
-
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "insert_wire_fee",
- params);
-}
-
-
-/**
- * Closure for #reserve_expired_cb().
- */
-struct ExpiredReserveContext
-{
- /**
- * Function to call for each expired reserve.
- */
- TALER_EXCHANGEDB_ReserveExpiredCallback rec;
-
- /**
- * Closure to give to @e rec.
- */
- void *rec_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to #GNUNET_SYSERR on error.
- */
- int status;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-reserve_expired_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ExpiredReserveContext *erc = cls;
- struct PostgresClosure *pg = erc->pg;
- int ret;
+ NULL);
+ if (NULL == db_conn)
+ return GNUNET_SYSERR;
- ret = GNUNET_OK;
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_TIME_Absolute exp_date;
- char *account_details;
- struct TALER_ReservePublicKeyP reserve_pub;
- struct TALER_Amount remaining_balance;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_result_spec_absolute_time ("expiration_date",
- &exp_date),
- GNUNET_PQ_result_spec_string ("account_details",
- &account_details),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &reserve_pub),
- TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance",
- &remaining_balance),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ret = GNUNET_SYSERR;
- break;
- }
- ret = erc->rec (erc->rec_cls,
- &reserve_pub,
- &remaining_balance,
- account_details,
- exp_date);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
- erc->status = ret;
-}
-
-
-/**
- * Obtain information about expired reserves and their
- * remaining balances.
- *
- * @param cls closure of the plugin
- * @param session database connection
- * @param now timestamp based on which we decide expiration
- * @param rec function to call on expired reserves
- * @param rec_cls closure for @a rec
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_expired_reserves (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- struct GNUNET_TIME_Absolute now,
- TALER_EXCHANGEDB_ReserveExpiredCallback rec,
- void *rec_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_end
- };
- struct ExpiredReserveContext ectx;
- enum GNUNET_DB_QueryStatus qs;
-
- ectx.rec = rec;
- ectx.rec_cls = rec_cls;
- ectx.pg = pg;
- ectx.status = GNUNET_OK;
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "get_expired_reserves",
- params,
- &reserve_expired_cb,
- &ectx);
- if (GNUNET_OK != ectx.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Insert reserve close operation into database.
- *
- * @param cls closure
- * @param session database connection
- * @param reserve_pub which reserve is this about?
- * @param execution_date when did we perform the transfer?
- * @param receiver_account to which account do we transfer?
- * @param wtid wire transfer details
- * @param amount_with_fee amount we charged to the reserve
- * @param closing_fee how high is the closing fee
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_reserve_closed (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- struct GNUNET_TIME_Absolute execution_date,
- const char *receiver_account,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *closing_fee)
-{
- struct TALER_EXCHANGEDB_Reserve reserve;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- TALER_PQ_query_param_absolute_time (&execution_date),
- GNUNET_PQ_query_param_auto_from_type (wtid),
- GNUNET_PQ_query_param_string (receiver_account),
- TALER_PQ_query_param_amount (amount_with_fee),
- TALER_PQ_query_param_amount (closing_fee),
- GNUNET_PQ_query_param_end
- };
- int ret;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_non_select (session->conn,
- "reserves_close_insert",
- params);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- return qs;
-
- /* update reserve balance */
- reserve.pub = *reserve_pub;
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- (qs = postgres_reserves_get (cls,
- session,
- &reserve)))
- {
- /* Existence should have been checked before we got here... */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
- }
- ret = TALER_amount_subtract (&reserve.balance,
- &reserve.balance,
- amount_with_fee);
- if (GNUNET_SYSERR == ret)
- {
- /* The reserve history was checked to make sure there is enough of a balance
- left before we tried this; however, concurrent operations may have changed
- the situation by now. We should re-try the transaction. */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Closing of reserve `%s' refused due to balance mismatch. Retrying.\n",
- TALER_B2S (reserve_pub));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- GNUNET_break (GNUNET_NO == ret);
- return reserves_update (cls,
- session,
- &reserve);
-}
-
-
-/**
- * Function called to insert wire transfer commit data into the DB.
- *
- * @param cls closure
- * @param session database connection
- * @param type type of the wire transfer (i.e. "iban")
- * @param buf buffer with wire transfer preparation data
- * @param buf_size number of bytes in @a buf
- * @return query status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_wire_prepare_data_insert (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const char *type,
- const char *buf,
- size_t buf_size)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (type),
- GNUNET_PQ_query_param_fixed_size (buf, buf_size),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "wire_prepare_data_insert",
- params);
-}
-
-
-/**
- * Function called to mark wire transfer commit data as finished.
- *
- * @param cls closure
- * @param session database connection
- * @param rowid which entry to mark as finished
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_wire_prepare_data_mark_finished (void *cls,
- struct TALER_EXCHANGEDB_Session *
- session,
- uint64_t rowid)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&rowid),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "wire_prepare_data_mark_done",
- params);
-}
-
-
-/**
- * Function called to get an unfinished wire transfer
- * preparation data. Fetches at most one item.
- *
- * @param cls closure
- * @param session database connection
- * @param cb function to call for ONE unfinished item
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_wire_prepare_data_get (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- TALER_EXCHANGEDB_WirePreparationIterator cb,
- void *cb_cls)
-{
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- uint64_t prewire_uuid;
- char *type;
- void *buf = NULL;
- size_t buf_size;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("prewire_uuid",
- &prewire_uuid),
- GNUNET_PQ_result_spec_string ("type",
- &type),
- GNUNET_PQ_result_spec_variable_size ("buf",
- &buf,
- &buf_size),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "wire_prepare_data_get",
- params,
- rs);
- if (0 >= qs)
- return qs;
- cb (cb_cls,
- prewire_uuid,
- type,
- buf,
- buf_size);
- GNUNET_PQ_cleanup_result (rs);
- return qs;
-}
-
-
-/**
- * Start a transaction where we transiently violate the foreign
- * constraints on the "wire_out" table as we insert aggregations
- * and only add the wire transfer out at the end.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @return #GNUNET_OK on success
- */
-static int
-postgres_start_deferred_wire_out (void *cls,
- struct TALER_EXCHANGEDB_Session *session)
-{
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("SET CONSTRAINTS wire_out_ref DEFERRED"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- postgres_preflight (cls,
- session);
- if (GNUNET_OK !=
- postgres_start (cls,
- session,
- "deferred wire out"))
- return GNUNET_SYSERR;
- if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (session->conn,
- es))
- {
- TALER_LOG_ERROR (
- "Failed to defer wire_out_ref constraint on transaction\n");
- GNUNET_break (0);
- postgres_rollback (cls,
- session);
- return GNUNET_SYSERR;
+ pg->prep_gen++;
+ pg->conn = db_conn;
}
+ if (NULL == pg->transaction_name)
+ GNUNET_PQ_reconnect_if_down (pg->conn);
return GNUNET_OK;
}
/**
- * Store information about an outgoing wire transfer that was executed.
- *
- * @param cls closure
- * @param session database connection
- * @param date time of the wire transfer
- * @param wtid subject of the wire transfer
- * @param wire_account details about the receiver account of the wire transfer
- * @param exchange_account_section configuration section of the exchange specifying the
- * exchange's bank account being used
- * @param amount amount that was transmitted
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_store_wire_transfer_out (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- struct GNUNET_TIME_Absolute date,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const json_t *wire_account,
- const char *exchange_account_section,
- const struct TALER_Amount *amount)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_absolute_time (&date),
- GNUNET_PQ_query_param_auto_from_type (wtid),
- TALER_PQ_query_param_json (wire_account),
- GNUNET_PQ_query_param_string (exchange_account_section),
- TALER_PQ_query_param_amount (amount),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "insert_wire_out",
- params);
-}
-
-
-/**
- * Function called to perform "garbage collection" on the
- * database, expiring records we no longer require.
- *
- * @param cls closure
- * @return #GNUNET_OK on success,
- * #GNUNET_SYSERR on DB errors
- */
-static int
-postgres_gc (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Absolute long_ago;
- struct GNUNET_PQ_QueryParam params_none[] = {
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_QueryParam params_time[] = {
- TALER_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_QueryParam params_ancient_time[] = {
- TALER_PQ_query_param_absolute_time (&long_ago),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_Context *conn;
- int ret;
-
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
- /* Keep wire fees for 10 years, that should always
- be enough _and_ they are tiny so it does not
- matter to make this tight */
- long_ago = GNUNET_TIME_absolute_subtract (now,
- GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_YEARS,
- 10));
- {
- struct GNUNET_PQ_PreparedStatement ps[] = {
- /* Used in #postgres_gc() */
- GNUNET_PQ_make_prepare ("gc_prewire",
- "DELETE"
- " FROM prewire"
- " WHERE finished=true;",
- 0),
- GNUNET_PQ_make_prepare ("gc_reserves",
- "DELETE"
- " FROM reserves"
- " WHERE gc_date < $1"
- " AND current_balance_val = 0"
- " AND current_balance_frac = 0;",
- 1),
- GNUNET_PQ_make_prepare ("gc_wire_fee",
- "DELETE"
- " FROM wire_fee"
- " WHERE end_date < $1;",
- 1),
- GNUNET_PQ_make_prepare ("gc_denominations",
- "DELETE"
- " FROM denominations"
- " WHERE expire_legal < $1;",
- 1),
- GNUNET_PQ_PREPARED_STATEMENT_END
- };
-
- conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
- "exchangedb-postgres",
- NULL,
- NULL,
- ps);
- }
- if (NULL == conn)
- return GNUNET_SYSERR;
- ret = GNUNET_OK;
- if ( (0 > GNUNET_PQ_eval_prepared_non_select (conn,
- "gc_reserves",
- params_time)) ||
- (0 > GNUNET_PQ_eval_prepared_non_select (conn,
- "gc_prewire",
- params_none)) ||
- (0 > GNUNET_PQ_eval_prepared_non_select (conn,
- "gc_wire_fee",
- params_ancient_time)) )
- ret = GNUNET_SYSERR;
- /* This one may fail due to foreign key constraints from
- recoup and reserves_out tables to known_coins; these
- are NOT using 'ON DROP CASCADE' and might keep denomination
- keys alive for a bit longer, thus causing this statement
- to fail. */(void) GNUNET_PQ_eval_prepared_non_select (conn,
- "gc_denominations",
- params_time);
- GNUNET_PQ_disconnect (conn);
- return ret;
-}
-
-
-/**
- * Closure for #deposit_serial_helper_cb().
- */
-struct DepositSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_DepositCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct DepositSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-deposit_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct DepositSerialContext *dsc = cls;
- struct PostgresClosure *pg = dsc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_Deposit deposit;
- struct TALER_DenominationPublicKey denom_pub;
- uint8_t done = 0;
- uint64_t rowid;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &deposit.amount_with_fee),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &deposit.timestamp),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
- &deposit.merchant_pub),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &deposit.coin.coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &deposit.csig),
- TALER_PQ_result_spec_absolute_time ("refund_deadline",
- &deposit.refund_deadline),
- TALER_PQ_result_spec_absolute_time ("wire_deadline",
- &deposit.wire_deadline),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &deposit.h_contract_terms),
- TALER_PQ_result_spec_json ("wire",
- &deposit.receiver_wire_account),
- GNUNET_PQ_result_spec_auto_from_type ("done",
- &done),
- GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
- &rowid),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- dsc->status = GNUNET_SYSERR;
- return;
- }
- ret = dsc->cb (dsc->cb_cls,
- rowid,
- deposit.timestamp,
- &deposit.merchant_pub,
- &denom_pub,
- &deposit.coin.coin_pub,
- &deposit.csig,
- &deposit.amount_with_fee,
- &deposit.h_contract_terms,
- deposit.refund_deadline,
- deposit.wire_deadline,
- deposit.receiver_wire_account,
- done);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Select deposits above @a serial_id in monotonically increasing
- * order.
- *
- * @param cls closure
- * @param session database connection
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call on each result
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_deposits_above_serial_id (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t serial_id,
- TALER_EXCHANGEDB_DepositCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct DepositSerialContext dsc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "audit_get_deposits_incr",
- params,
- &deposit_serial_helper_cb,
- &dsc);
- if (GNUNET_OK != dsc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #refreshs_serial_helper_cb().
- */
-struct RefreshsSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_RefreshesCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct RefreshsSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-refreshs_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct RefreshsSerialContext *rsc = cls;
- struct PostgresClosure *pg = rsc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_DenominationPublicKey denom_pub;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct TALER_Amount amount_with_fee;
- uint32_t noreveal_index;
- uint64_t rowid;
- struct TALER_RefreshCommitmentP rc;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
- &coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
- &coin_sig),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- GNUNET_PQ_result_spec_uint32 ("noreveal_index",
- &noreveal_index),
- GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
- &rowid),
- GNUNET_PQ_result_spec_auto_from_type ("rc",
- &rc),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- rsc->status = GNUNET_SYSERR;
- return;
- }
- ret = rsc->cb (rsc->cb_cls,
- rowid,
- &denom_pub,
- &coin_pub,
- &coin_sig,
- &amount_with_fee,
- noreveal_index,
- &rc);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Select refresh sessions above @a serial_id in monotonically increasing
- * order.
- *
- * @param cls closure
- * @param session database connection
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call on each result
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_refreshes_above_serial_id (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t serial_id,
- TALER_EXCHANGEDB_RefreshesCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct RefreshsSerialContext rsc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "audit_get_refresh_commitments_incr",
- params,
- &refreshs_serial_helper_cb,
- &rsc);
- if (GNUNET_OK != rsc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #refunds_serial_helper_cb().
- */
-struct RefundsSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_RefundCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct RefundsSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-refunds_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct RefundsSerialContext *rsc = cls;
- struct PostgresClosure *pg = rsc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_Refund refund;
- struct TALER_DenominationPublicKey denom_pub;
- uint64_t rowid;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
- &refund.details.merchant_pub),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
- &refund.details.merchant_sig),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &refund.details.h_contract_terms),
- GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
- &refund.details.rtransaction_id),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &refund.coin.coin_pub),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &refund.details.refund_amount),
- GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
- &rowid),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- rsc->status = GNUNET_SYSERR;
- return;
- }
- ret = rsc->cb (rsc->cb_cls,
- rowid,
- &denom_pub,
- &refund.coin.coin_pub,
- &refund.details.merchant_pub,
- &refund.details.merchant_sig,
- &refund.details.h_contract_terms,
- refund.details.rtransaction_id,
- &refund.details.refund_amount);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Select refunds above @a serial_id in monotonically increasing
- * order.
- *
- * @param cls closure
- * @param session database connection
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call on each result
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_refunds_above_serial_id (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t serial_id,
- TALER_EXCHANGEDB_RefundCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct RefundsSerialContext rsc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "audit_get_refunds_incr",
- params,
- &refunds_serial_helper_cb,
- &rsc);
- if (GNUNET_OK != rsc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #reserves_in_serial_helper_cb().
- */
-struct ReservesInSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_ReserveInCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct ReservesInSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-reserves_in_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReservesInSerialContext *risc = cls;
- struct PostgresClosure *pg = risc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_ReservePublicKeyP reserve_pub;
- struct TALER_Amount credit;
- char *sender_account_details;
- struct GNUNET_TIME_Absolute execution_date;
- uint64_t rowid;
- uint64_t wire_reference;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &reserve_pub),
- GNUNET_PQ_result_spec_uint64 ("wire_reference",
- &wire_reference),
- TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
- &credit),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &execution_date),
- GNUNET_PQ_result_spec_string ("sender_account_details",
- &sender_account_details),
- GNUNET_PQ_result_spec_uint64 ("reserve_in_serial_id",
- &rowid),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- risc->status = GNUNET_SYSERR;
- return;
- }
- ret = risc->cb (risc->cb_cls,
- rowid,
- &reserve_pub,
- &credit,
- sender_account_details,
- wire_reference,
- execution_date);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Select inbound wire transfers into reserves_in above @a serial_id
- * in monotonically increasing order.
- *
- * @param cls closure
- * @param session database connection
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call on each result
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_reserves_in_above_serial_id (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t serial_id,
- TALER_EXCHANGEDB_ReserveInCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct ReservesInSerialContext risc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "audit_reserves_in_get_transactions_incr",
- params,
- &reserves_in_serial_helper_cb,
- &risc);
- if (GNUNET_OK != risc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Select inbound wire transfers into reserves_in above @a serial_id
- * in monotonically increasing order by account.
- *
- * @param cls closure
- * @param session database connection
- * @param account_name name of the account to select by
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call on each result
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_reserves_in_above_serial_id_by_account (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const char *account_name,
- uint64_t serial_id,
- TALER_EXCHANGEDB_ReserveInCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_string (account_name),
- GNUNET_PQ_query_param_end
- };
- struct ReservesInSerialContext risc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "audit_reserves_in_get_transactions_incr_by_account",
- params,
- &reserves_in_serial_helper_cb,
- &risc);
- if (GNUNET_OK != risc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #reserves_out_serial_helper_cb().
- */
-struct ReservesOutSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_WithdrawCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct ReservesOutSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-reserves_out_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReservesOutSerialContext *rosc = cls;
- struct PostgresClosure *pg = rosc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_HashCode h_blind_ev;
- struct TALER_DenominationPublicKey denom_pub;
- struct TALER_ReservePublicKeyP reserve_pub;
- struct TALER_ReserveSignatureP reserve_sig;
- struct GNUNET_TIME_Absolute execution_date;
- struct TALER_Amount amount_with_fee;
- uint64_t rowid;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
- &h_blind_ev),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &reserve_pub),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
- &reserve_sig),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &execution_date),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- GNUNET_PQ_result_spec_uint64 ("reserve_out_serial_id",
- &rowid),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- rosc->status = GNUNET_SYSERR;
- return;
- }
- ret = rosc->cb (rosc->cb_cls,
- rowid,
- &h_blind_ev,
- &denom_pub,
- &reserve_pub,
- &reserve_sig,
- execution_date,
- &amount_with_fee);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Select withdraw operations from reserves_out above @a serial_id
- * in monotonically increasing order.
- *
- * @param cls closure
- * @param session database connection
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call on each result
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_withdrawals_above_serial_id (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t serial_id,
- TALER_EXCHANGEDB_WithdrawCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct ReservesOutSerialContext rosc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "audit_get_reserves_out_incr",
- params,
- &reserves_out_serial_helper_cb,
- &rosc);
- if (GNUNET_OK != rosc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #wire_out_serial_helper_cb().
- */
-struct WireOutSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_WireTransferOutCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct WireOutSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-wire_out_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct WireOutSerialContext *wosc = cls;
- struct PostgresClosure *pg = wosc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint64_t rowid;
- struct GNUNET_TIME_Absolute date;
- struct TALER_WireTransferIdentifierRawP wtid;
- json_t *wire;
- struct TALER_Amount amount;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("wireout_uuid",
- &rowid),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &date),
- GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
- &wtid),
- TALER_PQ_result_spec_json ("wire_target",
- &wire),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &amount),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- wosc->status = GNUNET_SYSERR;
- return;
- }
- ret = wosc->cb (wosc->cb_cls,
- rowid,
- date,
- &wtid,
- wire,
- &amount);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Function called to select all wire transfers the exchange
- * executed.
- *
- * @param cls closure
- * @param session database connection
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call for ONE unfinished item
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_wire_out_above_serial_id (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t serial_id,
- TALER_EXCHANGEDB_WireTransferOutCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct WireOutSerialContext wosc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "audit_get_wire_incr",
- params,
- &wire_out_serial_helper_cb,
- &wosc);
- if (GNUNET_OK != wosc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Function called to select all wire transfers the exchange
- * executed by account.
- *
- * @param cls closure
- * @param session database connection
- * @param account_name account to select
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call for ONE unfinished item
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_wire_out_above_serial_id_by_account (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const char *account_name,
- uint64_t serial_id,
- TALER_EXCHANGEDB_WireTransferOutCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_string (account_name),
- GNUNET_PQ_query_param_end
- };
- struct WireOutSerialContext wosc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "audit_get_wire_incr_by_account",
- params,
- &wire_out_serial_helper_cb,
- &wosc);
- if (GNUNET_OK != wosc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #recoup_serial_helper_cb().
- */
-struct RecoupSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_RecoupCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct RecoupSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-recoup_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct RecoupSerialContext *psc = cls;
- struct PostgresClosure *pg = psc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint64_t rowid;
- struct TALER_ReservePublicKeyP reserve_pub;
- struct TALER_CoinPublicInfo coin;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct TALER_DenominationBlindingKeyP coin_blind;
- struct TALER_Amount amount;
- struct TALER_DenominationPublicKey denom_pub;
- struct GNUNET_HashCode h_blind_ev;
- struct GNUNET_TIME_Absolute timestamp;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
- &rowid),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &timestamp),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &reserve_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin.coin_pub),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &coin_sig),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &coin_blind),
- GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
- &h_blind_ev),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &coin.denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &coin.denom_sig.rsa_signature),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &amount),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- psc->status = GNUNET_SYSERR;
- return;
- }
- ret = psc->cb (psc->cb_cls,
- rowid,
- timestamp,
- &amount,
- &reserve_pub,
- &coin,
- &denom_pub,
- &coin_sig,
- &coin_blind);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Function called to select recoup requests the exchange
- * received, ordered by serial ID (monotonically increasing).
- *
- * @param cls closure
- * @param session database connection
- * @param serial_id lowest serial ID to include (select larger or equal)
- * @param cb function to call for ONE unfinished item
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_recoup_above_serial_id (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t serial_id,
- TALER_EXCHANGEDB_RecoupCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct RecoupSerialContext psc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "recoup_get_incr",
- params,
- &recoup_serial_helper_cb,
- &psc);
- if (GNUNET_OK != psc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #recoup_refresh_serial_helper_cb().
- */
-struct RecoupRefreshSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_RecoupRefreshCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct RecoupRefreshSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-recoup_refresh_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct RecoupRefreshSerialContext *psc = cls;
- struct PostgresClosure *pg = psc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint64_t rowid;
- struct TALER_CoinSpendPublicKeyP old_coin_pub;
- struct TALER_CoinPublicInfo coin;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct TALER_DenominationBlindingKeyP coin_blind;
- struct TALER_DenominationPublicKey denom_pub;
- struct GNUNET_HashCode old_denom_pub_hash;
- struct TALER_Amount amount;
- struct GNUNET_HashCode h_blind_ev;
- struct GNUNET_TIME_Absolute timestamp;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
- &rowid),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &timestamp),
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
- &old_coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("old_denom_pub_hash",
- &old_denom_pub_hash),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin.coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &coin_sig),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &coin_blind),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
- &h_blind_ev),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &coin.denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &coin.denom_sig.rsa_signature),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &amount),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- psc->status = GNUNET_SYSERR;
- return;
- }
- ret = psc->cb (psc->cb_cls,
- rowid,
- timestamp,
- &amount,
- &old_coin_pub,
- &old_denom_pub_hash,
- &coin,
- &denom_pub,
- &coin_sig,
- &coin_blind);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Function called to select recoup requests the exchange received for
- * refreshed coins, ordered by serial ID (monotonically increasing).
- *
- * @param cls closure
- * @param session database connection
- * @param serial_id lowest serial ID to include (select larger or equal)
- * @param cb function to call for ONE unfinished item
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_recoup_refresh_above_serial_id (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t serial_id,
- TALER_EXCHANGEDB_RecoupRefreshCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct RecoupRefreshSerialContext psc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "recoup_refresh_get_incr",
- params,
- &recoup_refresh_serial_helper_cb,
- &psc);
- if (GNUNET_OK != psc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #reserve_closed_serial_helper_cb().
- */
-struct ReserveClosedSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_ReserveClosedCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin's context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct ReserveClosedSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-reserve_closed_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReserveClosedSerialContext *rcsc = cls;
- struct PostgresClosure *pg = rcsc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint64_t rowid;
- struct TALER_ReservePublicKeyP reserve_pub;
- char *receiver_account;
- struct TALER_WireTransferIdentifierRawP wtid;
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount closing_fee;
- struct GNUNET_TIME_Absolute execution_date;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("close_uuid",
- &rowid),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &reserve_pub),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &execution_date),
- GNUNET_PQ_result_spec_auto_from_type ("wtid",
- &wtid),
- GNUNET_PQ_result_spec_string ("receiver_account",
- &receiver_account),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
- &closing_fee),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- rcsc->status = GNUNET_SYSERR;
- return;
- }
- ret = rcsc->cb (rcsc->cb_cls,
- rowid,
- execution_date,
- &amount_with_fee,
- &closing_fee,
- &reserve_pub,
- receiver_account,
- &wtid);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Function called to select reserve close operations the aggregator
- * triggered, ordered by serial ID (monotonically increasing).
- *
- * @param cls closure
- * @param session database connection
- * @param serial_id lowest serial ID to include (select larger or equal)
- * @param cb function to call for ONE unfinished item
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_reserve_closed_above_serial_id (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t serial_id,
- TALER_EXCHANGEDB_ReserveClosedCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct ReserveClosedSerialContext rcsc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "reserves_close_get_incr",
- params,
- &reserve_closed_serial_helper_cb,
- &rcsc);
- if (GNUNET_OK != rcsc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Function called to add a request for an emergency recoup for a
- * coin. The funds are to be added back to the reserve. The function
- * should return the @a deadline by which the exchange will trigger a
- * wire transfer back to the customer's account for the reserve.
- *
- * @param cls closure
- * @param session database connection
- * @param reserve_pub public key of the reserve that is being refunded
- * @param coin information about the coin
- * @param coin_sig signature of the coin of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
- * @param coin_blind blinding key of the coin
- * @param amount total amount to be paid back
- * @param h_blind_ev hash of the blinded coin's envelope (must match reserves_out entry)
- * @param timestamp current time (rounded)
- * @return transaction result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_recoup_request (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_CoinPublicInfo *coin,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind,
- const struct TALER_Amount *amount,
- const struct GNUNET_HashCode *h_blind_ev,
- struct GNUNET_TIME_Absolute timestamp)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Absolute expiry;
- struct TALER_EXCHANGEDB_Reserve reserve;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub),
- GNUNET_PQ_query_param_auto_from_type (coin_sig),
- GNUNET_PQ_query_param_auto_from_type (coin_blind),
- TALER_PQ_query_param_amount (amount),
- TALER_PQ_query_param_absolute_time (&timestamp),
- GNUNET_PQ_query_param_auto_from_type (h_blind_ev),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- /* now store actual recoup information */
- qs = GNUNET_PQ_eval_prepared_non_select (session->conn,
- "recoup_insert",
- params);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
-
- /* Update reserve balance */
- reserve.pub = *reserve_pub;
- qs = postgres_reserves_get (cls,
- session,
- &reserve);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- if (GNUNET_SYSERR ==
- TALER_amount_add (&reserve.balance,
- &reserve.balance,
- amount))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- expiry = GNUNET_TIME_absolute_add (timestamp,
- pg->legal_reserve_expiration_time);
- reserve.gc = GNUNET_TIME_absolute_max (expiry,
- reserve.gc);
- (void) GNUNET_TIME_round_abs (&reserve.gc);
- expiry = GNUNET_TIME_absolute_add (timestamp,
- pg->idle_reserve_expiration_time);
- reserve.expiry = GNUNET_TIME_absolute_max (expiry,
- reserve.expiry);
- (void) GNUNET_TIME_round_abs (&reserve.expiry);
- qs = reserves_update (cls,
- session,
- &reserve);
- if (0 >= qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- return qs;
-}
-
-
-/**
- * Function called to add a request for an emergency recoup for a
- * refreshed coin. The funds are to be added back to the original coin
- * (which is implied via @a h_blind_ev, see the prepared statement
- * "recoup_by_old_coin" used in #postgres_get_coin_transactions()).
- *
- * @param cls closure
- * @param session database connection
- * @param coin public information about the refreshed coin
- * @param coin_sig signature of the coin of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
- * @param coin_blind blinding key of the coin
- * @param h_blind_ev blinded envelope, as calculated by the exchange
- * @param amount total amount to be paid back
- * @param h_blind_ev hash of the blinded coin's envelope (must match reserves_out entry)
- * @param timestamp a timestamp to store
- * @return transaction result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_recoup_refresh_request (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinPublicInfo *coin,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind,
- const struct TALER_Amount *amount,
- const struct GNUNET_HashCode *h_blind_ev,
- struct GNUNET_TIME_Absolute timestamp)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub),
- GNUNET_PQ_query_param_auto_from_type (coin_sig),
- GNUNET_PQ_query_param_auto_from_type (coin_blind),
- TALER_PQ_query_param_amount (amount),
- TALER_PQ_query_param_absolute_time (&timestamp),
- GNUNET_PQ_query_param_auto_from_type (h_blind_ev),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- (void) cls;
- /* now store actual recoup information */
- qs = GNUNET_PQ_eval_prepared_non_select (session->conn,
- "recoup_refresh_insert",
- params);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- return qs;
-}
-
-
-/**
- * Obtain information about which reserve a coin was generated
- * from given the hash of the blinded coin.
- *
- * @param cls closure
- * @param session a session
- * @param h_blind_ev hash of the blinded coin
- * @param[out] reserve_pub set to information about the reserve (on success only)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_reserve_by_h_blind (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *h_blind_ev,
- struct TALER_ReservePublicKeyP *reserve_pub)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (h_blind_ev),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- reserve_pub),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "reserve_by_h_blind",
- params,
- rs);
-}
-
-
-/**
- * Obtain information about which old coin a coin was refreshed
- * given the hash of the blinded (fresh) coin.
- *
- * @param cls closure
- * @param session a session
- * @param h_blind_ev hash of the blinded coin
- * @param[out] old_coin_pub set to information about the old coin (on success only)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_old_coin_by_h_blind (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *h_blind_ev,
- struct TALER_CoinSpendPublicKeyP *old_coin_pub)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (h_blind_ev),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
- old_coin_pub),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "old_coin_by_h_blind",
- params,
- rs);
-}
-
-
-/**
- * Store information that a denomination key was revoked
- * in the database.
- *
- * @param cls closure
- * @param session a session
- * @param denom_pub_hash hash of the revoked denomination key
- * @param master_sig signature affirming the revocation
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_denomination_revocation (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
- GNUNET_PQ_query_param_auto_from_type (master_sig),
- GNUNET_PQ_query_param_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_non_select (session->conn,
- "denomination_revocation_insert",
- params);
-}
-
-
-/**
- * Obtain information about a denomination key's revocation from
- * the database.
- *
- * @param cls closure
- * @param session a session
- * @param denom_pub_hash hash of the revoked denomination key
- * @param[out] master_sig signature affirming the revocation
- * @param[out] rowid row where the information is stored
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_denomination_revocation (
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash,
- struct TALER_MasterSignatureP *master_sig,
- uint64_t *rowid)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("master_sig", master_sig),
- GNUNET_PQ_result_spec_uint64 ("denom_revocations_serial_id", rowid),
- GNUNET_PQ_result_spec_end
- };
-
- (void) cls;
- return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
- "denomination_revocation_get",
- params,
- rs);
-}
-
-
-/**
- * Closure for #missing_wire_cb().
- */
-struct MissingWireContext
-{
- /**
- * Function to call per result.
- */
- TALER_EXCHANGEDB_WireMissingCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to #GNUNET_SYSERR on error.
- */
- int status;
-};
-
-
-/**
- * Invoke the callback for each result.
- *
- * @param cls a `struct MissingWireContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-missing_wire_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct MissingWireContext *mwc = cls;
- struct PostgresClosure *pg = mwc->pg;
-
- while (0 < num_results)
- {
- uint64_t rowid;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_Amount amount;
- json_t *wire;
- struct GNUNET_TIME_Absolute deadline;
- uint8_t tiny;
- uint8_t done;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
- &rowid),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin_pub),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount),
- TALER_PQ_result_spec_json ("wire",
- &wire),
- TALER_PQ_result_spec_absolute_time ("wire_deadline",
- &deadline),
- GNUNET_PQ_result_spec_auto_from_type ("tiny",
- &tiny),
- GNUNET_PQ_result_spec_auto_from_type ("done",
- &done),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- --num_results))
- {
- GNUNET_break (0);
- mwc->status = GNUNET_SYSERR;
- return;
- }
- mwc->cb (mwc->cb_cls,
- rowid,
- &coin_pub,
- &amount,
- wire,
- deadline,
- tiny,
- done);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Select all of those deposits in the database for which we do
- * not have a wire transfer (or a refund) and which should have
- * been deposited between @a start_date and @a end_date.
- *
- * @param cls closure
- * @param session a session
- * @param start_date lower bound on the requested wire execution date
- * @param end_date upper bound on the requested wire execution date
- * @param cb function to call on all such deposits
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_deposits_missing_wire (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- struct GNUNET_TIME_Absolute start_date,
- struct GNUNET_TIME_Absolute end_date,
- TALER_EXCHANGEDB_WireMissingCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_absolute_time (&start_date),
- TALER_PQ_query_param_absolute_time (&end_date),
- GNUNET_PQ_query_param_end
- };
- struct MissingWireContext mwc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
- "deposits_get_overdue",
- params,
- &missing_wire_cb,
- &mwc);
- if (GNUNET_OK != mwc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
* Initialize Postgres database subsystem.
*
* @param cls a configuration instance
@@ -7207,10 +321,10 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
struct PostgresClosure *pg;
struct TALER_EXCHANGEDB_Plugin *plugin;
+ unsigned long long dpl;
pg = GNUNET_new (struct PostgresClosure);
pg->cfg = cfg;
- pg->main_self = pthread_self (); /* loaded while single-threaded! */
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (cfg,
"exchangedb-postgres",
@@ -7219,136 +333,466 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchangedb-postgres",
- "CONFIG");
+ "SQL_DIR");
GNUNET_free (pg);
return NULL;
}
- if (0 != pthread_key_create (&pg->db_conn_threadlocal,
- &db_conn_destroy))
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "BASE_URL",
+ &pg->exchange_url))
{
- TALER_LOG_ERROR ("Cannot create pthread key.\n");
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
GNUNET_free (pg->sql_dir);
GNUNET_free (pg);
return NULL;
}
- if ( (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- "exchangedb",
- "IDLE_RESERVE_EXPIRATION_TIME",
- &pg->idle_reserve_expiration_time))
- ||
- (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- "exchangedb",
- "LEGAL_RESERVE_EXPIRATION_TIME",
- &pg->legal_reserve_expiration_time)) )
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ "exchangedb",
+ "IDLE_RESERVE_EXPIRATION_TIME",
+ &pg->idle_reserve_expiration_time))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchangedb",
+ "IDLE_RESERVE_EXPIRATION_TIME");
+ GNUNET_free (pg->exchange_url);
+ GNUNET_free (pg->sql_dir);
+ GNUNET_free (pg);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ "exchangedb",
+ "LEGAL_RESERVE_EXPIRATION_TIME",
+ &pg->legal_reserve_expiration_time))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchangedb",
- "LEGAL/IDLE_RESERVE_EXPIRATION_TIME");
+ "LEGAL_RESERVE_EXPIRATION_TIME");
+ GNUNET_free (pg->exchange_url);
GNUNET_free (pg->sql_dir);
GNUNET_free (pg);
return NULL;
}
if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ "exchangedb",
+ "AGGREGATOR_SHIFT",
+ &pg->aggregator_shift))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ "exchangedb",
+ "AGGREGATOR_SHIFT");
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ "exchangedb",
+ "DEFAULT_PURSE_LIMIT",
+ &dpl))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ "exchangedb",
+ "DEFAULT_PURSE_LIMIT");
+ pg->def_purse_limit = 1;
+ }
+ else
+ {
+ pg->def_purse_limit = (uint32_t) dpl;
+ }
+
+ if (GNUNET_OK !=
TALER_config_get_currency (cfg,
&pg->currency))
{
+ GNUNET_free (pg->exchange_url);
+ GNUNET_free (pg->sql_dir);
+ GNUNET_free (pg);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ TEH_PG_internal_setup (pg))
+ {
+ GNUNET_free (pg->exchange_url);
+ GNUNET_free (pg->currency);
GNUNET_free (pg->sql_dir);
GNUNET_free (pg);
return NULL;
}
plugin = GNUNET_new (struct TALER_EXCHANGEDB_Plugin);
plugin->cls = pg;
- plugin->get_session = &postgres_get_session;
- plugin->drop_tables = &postgres_drop_tables;
- plugin->create_tables = &postgres_create_tables;
- plugin->start = &postgres_start;
- plugin->commit = &postgres_commit;
- plugin->preflight = &postgres_preflight;
- plugin->rollback = &postgres_rollback;
- plugin->insert_denomination_info = &postgres_insert_denomination_info;
- plugin->get_denomination_info = &postgres_get_denomination_info;
- plugin->iterate_denomination_info = &postgres_iterate_denomination_info;
- plugin->reserves_get = &postgres_reserves_get;
- plugin->reserves_in_insert = &postgres_reserves_in_insert;
- plugin->get_latest_reserve_in_reference =
- &postgres_get_latest_reserve_in_reference;
- plugin->get_withdraw_info = &postgres_get_withdraw_info;
- plugin->insert_withdraw_info = &postgres_insert_withdraw_info;
- plugin->get_reserve_history = &postgres_get_reserve_history;
- plugin->free_reserve_history = &common_free_reserve_history;
- plugin->count_known_coins = &postgres_count_known_coins;
- plugin->ensure_coin_known = &postgres_ensure_coin_known;
- plugin->get_known_coin = &postgres_get_known_coin;
- plugin->get_coin_denomination = &postgres_get_coin_denomination;
- plugin->have_deposit = &postgres_have_deposit;
- plugin->mark_deposit_tiny = &postgres_mark_deposit_tiny;
- plugin->test_deposit_done = &postgres_test_deposit_done;
- plugin->mark_deposit_done = &postgres_mark_deposit_done;
- plugin->get_ready_deposit = &postgres_get_ready_deposit;
- plugin->iterate_matching_deposits = &postgres_iterate_matching_deposits;
- plugin->insert_deposit = &postgres_insert_deposit;
- plugin->insert_refund = &postgres_insert_refund;
- plugin->select_refunds_by_coin = &postgres_select_refunds_by_coin;
- plugin->insert_melt = &postgres_insert_melt;
- plugin->get_melt = &postgres_get_melt;
- plugin->get_melt_index = &postgres_get_melt_index;
- plugin->insert_refresh_reveal = &postgres_insert_refresh_reveal;
- plugin->get_refresh_reveal = &postgres_get_refresh_reveal;
- plugin->get_link_data = &postgres_get_link_data;
- plugin->get_coin_transactions = &postgres_get_coin_transactions;
- plugin->free_coin_transaction_list = &common_free_coin_transaction_list;
- plugin->lookup_wire_transfer = &postgres_lookup_wire_transfer;
- plugin->lookup_transfer_by_deposit = &postgres_lookup_transfer_by_deposit;
- plugin->insert_aggregation_tracking = &postgres_insert_aggregation_tracking;
- plugin->insert_wire_fee = &postgres_insert_wire_fee;
- plugin->get_wire_fee = &postgres_get_wire_fee;
- plugin->get_expired_reserves = &postgres_get_expired_reserves;
- plugin->insert_reserve_closed = &postgres_insert_reserve_closed;
- plugin->wire_prepare_data_insert = &postgres_wire_prepare_data_insert;
- plugin->wire_prepare_data_mark_finished =
- &postgres_wire_prepare_data_mark_finished;
- plugin->wire_prepare_data_get = &postgres_wire_prepare_data_get;
- plugin->start_deferred_wire_out = &postgres_start_deferred_wire_out;
- plugin->store_wire_transfer_out = &postgres_store_wire_transfer_out;
- plugin->gc = &postgres_gc;
- plugin->select_deposits_above_serial_id
- = &postgres_select_deposits_above_serial_id;
+ plugin->do_reserve_open
+ = &TEH_PG_do_reserve_open;
+ plugin->drop_tables
+ = &TEH_PG_drop_tables;
+ plugin->free_coin_transaction_list
+ = &TEH_COMMON_free_coin_transaction_list;
+ plugin->free_reserve_history
+ = &TEH_COMMON_free_reserve_history;
+ plugin->get_coin_transactions
+ = &TEH_PG_get_coin_transactions;
+ plugin->get_expired_reserves
+ = &TEH_PG_get_expired_reserves;
+ plugin->get_purse_request
+ = &TEH_PG_get_purse_request;
+ plugin->get_reserve_history
+ = &TEH_PG_get_reserve_history;
+ plugin->get_unfinished_close_requests
+ = &TEH_PG_get_unfinished_close_requests;
+ plugin->insert_records_by_table
+ = &TEH_PG_insert_records_by_table;
+ plugin->insert_reserve_open_deposit
+ = &TEH_PG_insert_reserve_open_deposit;
+ plugin->insert_close_request
+ = &TEH_PG_insert_close_request;
+ plugin->delete_aggregation_transient
+ = &TEH_PG_delete_aggregation_transient;
+ plugin->get_link_data
+ = &TEH_PG_get_link_data;
+ plugin->iterate_reserve_close_info
+ = &TEH_PG_iterate_reserve_close_info;
+ plugin->iterate_kyc_reference
+ = &TEH_PG_iterate_kyc_reference;
+ plugin->lookup_records_by_table
+ = &TEH_PG_lookup_records_by_table;
+ plugin->lookup_serial_by_table
+ = &TEH_PG_lookup_serial_by_table;
+ plugin->select_account_merges_above_serial_id
+ = &TEH_PG_select_account_merges_above_serial_id;
+ plugin->select_all_purse_decisions_above_serial_id
+ = &TEH_PG_select_all_purse_decisions_above_serial_id;
+ plugin->select_aml_threshold
+ = &TEH_PG_select_aml_threshold;
+ plugin->select_purse
+ = &TEH_PG_select_purse;
+ plugin->select_purse_deposits_above_serial_id
+ = &TEH_PG_select_purse_deposits_above_serial_id;
+ plugin->select_purse_merges_above_serial_id
+ = &TEH_PG_select_purse_merges_above_serial_id;
+ plugin->select_purse_requests_above_serial_id
+ = &TEH_PG_select_purse_requests_above_serial_id;
+ plugin->select_reserve_close_info
+ = &TEH_PG_select_reserve_close_info;
+ plugin->select_reserve_closed_above_serial_id
+ = &TEH_PG_select_reserve_closed_above_serial_id;
+ plugin->select_reserve_open_above_serial_id
+ = &TEH_PG_select_reserve_open_above_serial_id;
+ plugin->insert_purse_request
+ = &TEH_PG_insert_purse_request;
+ plugin->iterate_active_signkeys
+ = &TEH_PG_iterate_active_signkeys;
+ plugin->commit
+ = &TEH_PG_commit;
+ plugin->preflight
+ = &TEH_PG_preflight;
+ plugin->select_aggregation_amounts_for_kyc_check
+ = &TEH_PG_select_aggregation_amounts_for_kyc_check;
+ plugin->select_satisfied_kyc_processes
+ = &TEH_PG_select_satisfied_kyc_processes;
+ plugin->kyc_provider_account_lookup
+ = &TEH_PG_kyc_provider_account_lookup;
+ plugin->lookup_kyc_requirement_by_row
+ = &TEH_PG_lookup_kyc_requirement_by_row;
+ plugin->insert_kyc_requirement_for_account
+ = &TEH_PG_insert_kyc_requirement_for_account;
+ plugin->lookup_kyc_process_by_account
+ = &TEH_PG_lookup_kyc_process_by_account;
+ plugin->update_kyc_process_by_row
+ = &TEH_PG_update_kyc_process_by_row;
+ plugin->insert_kyc_requirement_process
+ = &TEH_PG_insert_kyc_requirement_process;
+ plugin->select_withdraw_amounts_for_kyc_check
+ = &TEH_PG_select_withdraw_amounts_for_kyc_check;
+ plugin->select_merge_amounts_for_kyc_check
+ = &TEH_PG_select_merge_amounts_for_kyc_check;
+ plugin->profit_drains_set_finished
+ = &TEH_PG_profit_drains_set_finished;
+ plugin->profit_drains_get_pending
+ = &TEH_PG_profit_drains_get_pending;
+ plugin->get_drain_profit
+ = &TEH_PG_get_drain_profit;
+ plugin->get_purse_deposit
+ = &TEH_PG_get_purse_deposit;
+ plugin->insert_contract
+ = &TEH_PG_insert_contract;
+ plugin->select_contract
+ = &TEH_PG_select_contract;
+ plugin->select_purse_merge
+ = &TEH_PG_select_purse_merge;
+ plugin->select_contract_by_purse
+ = &TEH_PG_select_contract_by_purse;
+ plugin->insert_drain_profit
+ = &TEH_PG_insert_drain_profit;
+ plugin->do_reserve_purse
+ = &TEH_PG_do_reserve_purse;
+ plugin->lookup_global_fee_by_time
+ = &TEH_PG_lookup_global_fee_by_time;
+ plugin->do_purse_deposit
+ = &TEH_PG_do_purse_deposit;
+ plugin->activate_signing_key
+ = &TEH_PG_activate_signing_key;
+ plugin->update_auditor
+ = &TEH_PG_update_auditor;
+ plugin->begin_revolving_shard
+ = &TEH_PG_begin_revolving_shard;
+ plugin->get_extension_manifest
+ = &TEH_PG_get_extension_manifest;
+ plugin->do_purse_merge
+ = &TEH_PG_do_purse_merge;
+ plugin->do_purse_delete
+ = &TEH_PG_do_purse_delete;
+ plugin->start_read_committed
+ = &TEH_PG_start_read_committed;
+ plugin->start_read_only
+ = &TEH_PG_start_read_only;
+ plugin->insert_denomination_info
+ = &TEH_PG_insert_denomination_info;
+ plugin->do_batch_withdraw_insert
+ = &TEH_PG_do_batch_withdraw_insert;
+ plugin->lookup_wire_fee_by_time
+ = &TEH_PG_lookup_wire_fee_by_time;
+ plugin->start
+ = &TEH_PG_start;
+ plugin->rollback
+ = &TEH_PG_rollback;
+ plugin->create_tables
+ = &TEH_PG_create_tables;
+ plugin->event_listen
+ = &TEH_PG_event_listen;
+ plugin->event_listen_cancel
+ = &TEH_PG_event_listen_cancel;
+ plugin->event_notify
+ = &TEH_PG_event_notify;
+ plugin->get_denomination_info
+ = &TEH_PG_get_denomination_info;
+ plugin->iterate_denomination_info
+ = &TEH_PG_iterate_denomination_info;
+ plugin->iterate_denominations
+ = &TEH_PG_iterate_denominations;
+ plugin->iterate_active_auditors
+ = &TEH_PG_iterate_active_auditors;
+ plugin->iterate_auditor_denominations
+ = &TEH_PG_iterate_auditor_denominations;
+ plugin->reserves_get
+ = &TEH_PG_reserves_get;
+ plugin->reserves_get_origin
+ = &TEH_PG_reserves_get_origin;
+ plugin->drain_kyc_alert
+ = &TEH_PG_drain_kyc_alert;
+ plugin->reserves_in_insert
+ = &TEH_PG_reserves_in_insert;
+ plugin->get_withdraw_info
+ = &TEH_PG_get_withdraw_info;
+ plugin->do_batch_withdraw
+ = &TEH_PG_do_batch_withdraw;
+ plugin->do_age_withdraw
+ = &TEH_PG_do_age_withdraw;
+ plugin->get_age_withdraw
+ = &TEH_PG_get_age_withdraw;
+ plugin->get_policy_details
+ = &TEH_PG_get_policy_details;
+ plugin->persist_policy_details
+ = &TEH_PG_persist_policy_details;
+ plugin->do_deposit
+ = &TEH_PG_do_deposit;
+ plugin->get_wire_hash_for_contract
+ = &TEH_PG_get_wire_hash_for_contract;
+ plugin->add_policy_fulfillment_proof
+ = &TEH_PG_add_policy_fulfillment_proof;
+ plugin->do_melt
+ = &TEH_PG_do_melt;
+ plugin->do_refund
+ = &TEH_PG_do_refund;
+ plugin->do_recoup
+ = &TEH_PG_do_recoup;
+ plugin->do_recoup_refresh
+ = &TEH_PG_do_recoup_refresh;
+ plugin->get_reserve_balance
+ = &TEH_PG_get_reserve_balance;
+ plugin->count_known_coins
+ = &TEH_PG_count_known_coins;
+ plugin->ensure_coin_known
+ = &TEH_PG_ensure_coin_known;
+ plugin->get_known_coin
+ = &TEH_PG_get_known_coin;
+ plugin->get_signature_for_known_coin
+ = &TEH_PG_get_signature_for_known_coin;
+ plugin->get_coin_denomination
+ = &TEH_PG_get_coin_denomination;
+ plugin->have_deposit2
+ = &TEH_PG_have_deposit2;
+ plugin->aggregate
+ = &TEH_PG_aggregate;
+ plugin->create_aggregation_transient
+ = &TEH_PG_create_aggregation_transient;
+ plugin->select_aggregation_transient
+ = &TEH_PG_select_aggregation_transient;
+ plugin->find_aggregation_transient
+ = &TEH_PG_find_aggregation_transient;
+ plugin->update_aggregation_transient
+ = &TEH_PG_update_aggregation_transient;
+ plugin->get_ready_deposit
+ = &TEH_PG_get_ready_deposit;
+ plugin->insert_refund
+ = &TEH_PG_insert_refund;
+ plugin->select_refunds_by_coin
+ = &TEH_PG_select_refunds_by_coin;
+ plugin->get_melt
+ = &TEH_PG_get_melt;
+ plugin->insert_refresh_reveal
+ = &TEH_PG_insert_refresh_reveal;
+ plugin->get_refresh_reveal
+ = &TEH_PG_get_refresh_reveal;
+ plugin->lookup_wire_transfer
+ = &TEH_PG_lookup_wire_transfer;
+ plugin->lookup_transfer_by_deposit
+ = &TEH_PG_lookup_transfer_by_deposit;
+ plugin->insert_wire_fee
+ = &TEH_PG_insert_wire_fee;
+ plugin->insert_global_fee
+ = &TEH_PG_insert_global_fee;
+ plugin->get_wire_fee
+ = &TEH_PG_get_wire_fee;
+ plugin->get_global_fee
+ = &TEH_PG_get_global_fee;
+ plugin->get_global_fees
+ = &TEH_PG_get_global_fees;
+ plugin->insert_reserve_closed
+ = &TEH_PG_insert_reserve_closed;
+ plugin->wire_prepare_data_insert
+ = &TEH_PG_wire_prepare_data_insert;
+ plugin->wire_prepare_data_mark_finished
+ = &TEH_PG_wire_prepare_data_mark_finished;
+ plugin->wire_prepare_data_mark_failed
+ = &TEH_PG_wire_prepare_data_mark_failed;
+ plugin->wire_prepare_data_get
+ = &TEH_PG_wire_prepare_data_get;
+ plugin->start_deferred_wire_out
+ = &TEH_PG_start_deferred_wire_out;
+ plugin->store_wire_transfer_out
+ = &TEH_PG_store_wire_transfer_out;
+ plugin->gc
+ = &TEH_PG_gc;
+ plugin->select_coin_deposits_above_serial_id
+ = &TEH_PG_select_coin_deposits_above_serial_id;
+ plugin->select_purse_decisions_above_serial_id
+ = &TEH_PG_select_purse_decisions_above_serial_id;
+ plugin->select_purse_deposits_by_purse
+ = &TEH_PG_select_purse_deposits_by_purse;
plugin->select_refreshes_above_serial_id
- = &postgres_select_refreshes_above_serial_id;
+ = &TEH_PG_select_refreshes_above_serial_id;
plugin->select_refunds_above_serial_id
- = &postgres_select_refunds_above_serial_id;
+ = &TEH_PG_select_refunds_above_serial_id;
plugin->select_reserves_in_above_serial_id
- = &postgres_select_reserves_in_above_serial_id;
+ = &TEH_PG_select_reserves_in_above_serial_id;
plugin->select_reserves_in_above_serial_id_by_account
- = &postgres_select_reserves_in_above_serial_id_by_account;
+ = &TEH_PG_select_reserves_in_above_serial_id_by_account;
plugin->select_withdrawals_above_serial_id
- = &postgres_select_withdrawals_above_serial_id;
+ = &TEH_PG_select_withdrawals_above_serial_id;
plugin->select_wire_out_above_serial_id
- = &postgres_select_wire_out_above_serial_id;
+ = &TEH_PG_select_wire_out_above_serial_id;
plugin->select_wire_out_above_serial_id_by_account
- = &postgres_select_wire_out_above_serial_id_by_account;
+ = &TEH_PG_select_wire_out_above_serial_id_by_account;
plugin->select_recoup_above_serial_id
- = &postgres_select_recoup_above_serial_id;
+ = &TEH_PG_select_recoup_above_serial_id;
plugin->select_recoup_refresh_above_serial_id
- = &postgres_select_recoup_refresh_above_serial_id;
- plugin->select_reserve_closed_above_serial_id
- = &postgres_select_reserve_closed_above_serial_id;
- plugin->insert_recoup_request
- = &postgres_insert_recoup_request;
- plugin->insert_recoup_refresh_request
- = &postgres_insert_recoup_refresh_request;
+ = &TEH_PG_select_recoup_refresh_above_serial_id;
plugin->get_reserve_by_h_blind
- = &postgres_get_reserve_by_h_blind;
+ = &TEH_PG_get_reserve_by_h_blind;
plugin->get_old_coin_by_h_blind
- = &postgres_get_old_coin_by_h_blind;
+ = &TEH_PG_get_old_coin_by_h_blind;
plugin->insert_denomination_revocation
- = &postgres_insert_denomination_revocation;
+ = &TEH_PG_insert_denomination_revocation;
plugin->get_denomination_revocation
- = &postgres_get_denomination_revocation;
- plugin->select_deposits_missing_wire
- = &postgres_select_deposits_missing_wire;
+ = &TEH_PG_get_denomination_revocation;
+ plugin->select_batch_deposits_missing_wire
+ = &TEH_PG_select_batch_deposits_missing_wire;
+ plugin->select_justification_for_missing_wire
+ = &TEH_PG_select_justification_for_missing_wire;
+ plugin->select_aggregations_above_serial
+ = &TEH_PG_select_aggregations_above_serial;
+ plugin->lookup_auditor_timestamp
+ = &TEH_PG_lookup_auditor_timestamp;
+ plugin->lookup_auditor_status
+ = &TEH_PG_lookup_auditor_status;
+ plugin->insert_auditor
+ = &TEH_PG_insert_auditor;
+ plugin->lookup_wire_timestamp
+ = &TEH_PG_lookup_wire_timestamp;
+ plugin->insert_wire
+ = &TEH_PG_insert_wire;
+ plugin->update_wire
+ = &TEH_PG_update_wire;
+ plugin->get_wire_accounts
+ = &TEH_PG_get_wire_accounts;
+ plugin->get_wire_fees
+ = &TEH_PG_get_wire_fees;
+ plugin->insert_signkey_revocation
+ = &TEH_PG_insert_signkey_revocation;
+ plugin->lookup_signkey_revocation
+ = &TEH_PG_lookup_signkey_revocation;
+ plugin->lookup_denomination_key
+ = &TEH_PG_lookup_denomination_key;
+ plugin->insert_auditor_denom_sig
+ = &TEH_PG_insert_auditor_denom_sig;
+ plugin->select_auditor_denom_sig
+ = &TEH_PG_select_auditor_denom_sig;
+ plugin->add_denomination_key
+ = &TEH_PG_add_denomination_key;
+ plugin->lookup_signing_key
+ = &TEH_PG_lookup_signing_key;
+ plugin->begin_shard
+ = &TEH_PG_begin_shard;
+ plugin->abort_shard
+ = &TEH_PG_abort_shard;
+ plugin->insert_kyc_failure
+ = &TEH_PG_insert_kyc_failure;
+ plugin->complete_shard
+ = &TEH_PG_complete_shard;
+ plugin->release_revolving_shard
+ = &TEH_PG_release_revolving_shard;
+ plugin->delete_shard_locks
+ = &TEH_PG_delete_shard_locks;
+ plugin->set_extension_manifest
+ = &TEH_PG_set_extension_manifest;
+ plugin->insert_partner
+ = &TEH_PG_insert_partner;
+ plugin->expire_purse
+ = &TEH_PG_expire_purse;
+ plugin->select_purse_by_merge_pub
+ = &TEH_PG_select_purse_by_merge_pub;
+ plugin->set_purse_balance
+ = &TEH_PG_set_purse_balance;
+ plugin->get_pending_kyc_requirement_process
+ = &TEH_PG_get_pending_kyc_requirement_process;
+ plugin->insert_kyc_attributes
+ = &TEH_PG_insert_kyc_attributes;
+ plugin->select_similar_kyc_attributes
+ = &TEH_PG_select_similar_kyc_attributes;
+ plugin->select_kyc_attributes
+ = &TEH_PG_select_kyc_attributes;
+ plugin->insert_aml_officer
+ = &TEH_PG_insert_aml_officer;
+ plugin->test_aml_officer
+ = &TEH_PG_test_aml_officer;
+ plugin->lookup_aml_officer
+ = &TEH_PG_lookup_aml_officer;
+ plugin->trigger_aml_process
+ = &TEH_PG_trigger_aml_process;
+ plugin->select_aml_process
+ = &TEH_PG_select_aml_process;
+ plugin->select_aml_history
+ = &TEH_PG_select_aml_history;
+ plugin->insert_aml_decision
+ = &TEH_PG_insert_aml_decision;
+
+ plugin->batch_ensure_coin_known
+ = &TEH_PG_batch_ensure_coin_known;
+ plugin->inject_auditor_triggers
+ = &TEH_PG_inject_auditor_triggers;
return plugin;
}
@@ -7366,9 +810,12 @@ libtaler_plugin_exchangedb_postgres_done (void *cls)
struct TALER_EXCHANGEDB_Plugin *plugin = cls;
struct PostgresClosure *pg = plugin->cls;
- /* If we launched a session for the main thread,
- kill it here before we unload */
- db_conn_destroy (pg->main_session);
+ if (NULL != pg->conn)
+ {
+ GNUNET_PQ_disconnect (pg->conn);
+ pg->conn = NULL;
+ }
+ GNUNET_free (pg->exchange_url);
GNUNET_free (pg->sql_dir);
GNUNET_free (pg->currency);
GNUNET_free (pg);
diff --git a/src/exchangedb/procedures.sql.in b/src/exchangedb/procedures.sql.in
new file mode 100644
index 000000000..7afb01f0b
--- /dev/null
+++ b/src/exchangedb/procedures.sql.in
@@ -0,0 +1,49 @@
+--
+-- 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/>
+--
+
+BEGIN;
+
+SET search_path TO exchange;
+
+#include "exchange_do_amount_specific.sql"
+#include "exchange_do_batch_withdraw.sql"
+#include "exchange_do_batch_withdraw_insert.sql"
+#include "exchange_do_age_withdraw.sql"
+#include "exchange_do_deposit.sql"
+#include "exchange_do_melt.sql"
+#include "exchange_do_select_deposits_missing_wire.sql"
+#include "exchange_do_select_justification_for_missing_wire.sql"
+#include "exchange_do_refund.sql"
+#include "exchange_do_recoup_to_reserve.sql"
+#include "exchange_do_recoup_to_coin.sql"
+#include "exchange_do_gc.sql"
+#include "exchange_do_purse_delete.sql"
+#include "exchange_do_purse_deposit.sql"
+#include "exchange_do_purse_merge.sql"
+#include "exchange_do_reserve_purse.sql"
+#include "exchange_do_expire_purse.sql"
+#include "exchange_do_reserve_open_deposit.sql"
+#include "exchange_do_reserve_open.sql"
+#include "exchange_do_insert_or_update_policy_details.sql"
+#include "exchange_do_insert_aml_decision.sql"
+#include "exchange_do_insert_aml_officer.sql"
+#include "exchange_do_insert_kyc_attributes.sql"
+#include "exchange_do_reserves_in_insert.sql"
+#include "exchange_do_batch_reserves_update.sql"
+#include "exchange_do_get_link_data.sql"
+#include "exchange_do_batch_coin_known.sql"
+
+COMMIT;
diff --git a/src/exchangedb/spi/Makefile b/src/exchangedb/spi/Makefile
new file mode 100644
index 000000000..d654d91e9
--- /dev/null
+++ b/src/exchangedb/spi/Makefile
@@ -0,0 +1,9 @@
+EXTENSION = own_test
+MODULES = own_test
+DATA = own_test.sql
+PG_CPPFLAGS = -I /usr/include/postgresql
+
+# postgresql build stuff
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
diff --git a/src/exchangedb/spi/README.md b/src/exchangedb/spi/README.md
new file mode 100644
index 000000000..47eb37b94
--- /dev/null
+++ b/src/exchangedb/spi/README.md
@@ -0,0 +1,37 @@
+ Server Programming Interface (SPI)
+
+
+Overview
+========
+
+This folder contains results from an experiment by Joseph Xu
+to use the Postgres SPI. They are not currently used at all
+by the GNU Taler exchange.
+
+
+Dependencies
+============
+
+These are the direct dependencies for compiling the code:
+
+# apt-get install libpq-dev postgresql-server-dev-13
+# apt-get install libkrb5-dev
+# apt-get install libssl-dev
+
+
+Compilation
+===========
+
+$ make
+
+Loading functions
+=================
+
+# make install
+$ psql "$DB_NAME" < own_test.sql
+
+
+Calling functions
+==================
+
+$ psql -c "SELECT $FUNCTION_NAME($ARGS);" "$DB_NAME"
diff --git a/src/exchangedb/spi/own_test.c b/src/exchangedb/spi/own_test.c
new file mode 100644
index 000000000..ac72fad7b
--- /dev/null
+++ b/src/exchangedb/spi/own_test.c
@@ -0,0 +1,873 @@
+#include "postgres.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <postgresql/libpq-fe.h>
+#include <libpq-int.h>
+#include <catalog/pg_type.h>
+#include <executor/spi.h>
+#include <funcapi.h>
+#include <fmgr.h>
+#include <utils/builtins.h>
+#include <utils/array.h>
+#include <sys/time.h>
+#include <utils/numeric.h>
+#include <utils/timestamp.h>
+#include <utils/bytea.h>
+
+#ifdef PG_MODULE_MAGIC
+PG_MODULE_MAGIC;
+#endif
+
+typedef struct
+{
+ Datum col1;
+ Datum col2;
+} valuest;
+
+void _PG_init (void);
+
+void _PG_fini (void);
+
+void
+_PG_init (void)
+{
+}
+
+
+PG_FUNCTION_INFO_V1 (pg_spi_insert_int);
+PG_FUNCTION_INFO_V1 (pg_spi_select_from_x);
+PG_FUNCTION_INFO_V1 (pg_spi_select_pair_from_y);
+// PG_FUNCTION_INFO_V1(pg_spi_select_with_cond);
+PG_FUNCTION_INFO_V1 (pg_spi_update_y);
+PG_FUNCTION_INFO_V1 (pg_spi_prepare_example);
+PG_FUNCTION_INFO_V1 (pg_spi_prepare_example_without_saveplan);
+PG_FUNCTION_INFO_V1 (pg_spi_prepare_insert);
+PG_FUNCTION_INFO_V1 (pg_spi_prepare_insert_without_saveplan);
+// PG_FUNCTION_INFO_V1(pg_spi_prepare_select_with_cond);
+PG_FUNCTION_INFO_V1 (pg_spi_prepare_select_with_cond_without_saveplan);
+PG_FUNCTION_INFO_V1 (pg_spi_prepare_update);
+PG_FUNCTION_INFO_V1 (pg_spi_get_dep_ref_fees);
+// SIMPLE SELECT
+Datum
+pg_spi_prepare_example (PG_FUNCTION_ARGS)
+{
+ static SPIPlanPtr prepared_plan;
+ int ret;
+ int64 result;
+ char *value;
+ SPIPlanPtr new_plan;
+
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "DB connection failed ! \n");
+ }
+ {
+ if (prepared_plan == NULL)
+ {
+ new_plan = SPI_prepare ("SELECT 1 FROM X", 0, NULL);
+ prepared_plan = SPI_saveplan (new_plan);
+
+ if (prepared_plan == NULL)
+ {
+ elog (ERROR, "FAIL TO SAVE !\n");
+ }
+ }
+
+ ret = SPI_execute_plan (prepared_plan, NULL, 0,false, 0);
+ if (ret != SPI_OK_SELECT)
+ {
+ elog (ERROR, "SELECT FAILED %d !\n", ret);
+ }
+
+ if (SPI_tuptable != NULL && SPI_tuptable->vals != NULL &&
+ SPI_tuptable->tupdesc != NULL)
+ {
+ value = SPI_getvalue (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1);
+ result = atoi (value);
+ }
+ else
+ {
+ elog (ERROR, "EMPTY TABLE !\n");
+ }
+ }
+ SPI_finish ();
+ PG_RETURN_INT64 (result);
+}
+
+
+Datum
+pg_spi_prepare_example_without_saveplan (PG_FUNCTION_ARGS)
+{
+ int ret;
+ int64 result;
+ char *value;
+ SPIPlanPtr new_plan;
+
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "DB connection failed ! \n");
+ }
+
+ {
+ new_plan = SPI_prepare ("SELECT 1 FROM X", 0, NULL);
+ ret = SPI_execute_plan (new_plan, NULL, 0,false, 0);
+ if (ret != SPI_OK_SELECT)
+ {
+ elog (ERROR, "SELECT FAILED %d !\n", ret);
+ }
+
+ if (SPI_tuptable != NULL
+ && SPI_tuptable->vals != NULL
+ && SPI_tuptable->tupdesc != NULL)
+ {
+ value = SPI_getvalue (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1);
+ result = atoi (value);
+ }
+ else
+ {
+ elog (ERROR, "EMPTY TABLE !\n");
+ }
+ }
+ SPI_finish ();
+ PG_RETURN_INT64 (result);// PG_RETURN_INT64(result);
+}
+
+
+// SELECT 1 FROM X
+// V1
+Datum
+pg_spi_select_from_x (PG_FUNCTION_ARGS)
+{
+ int ret;
+ char *query = "SELECT 1 FROM X";
+ uint64 proc;
+ ret = SPI_connect ();
+
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "SPI_connect failed");
+ }
+
+ ret = SPI_exec (query, 10);
+ proc = SPI_processed;
+ if (ret != SPI_OK_SELECT)
+ {
+ elog (ERROR, "SPI_exec failed");
+ }
+
+ SPI_finish ();
+
+ PG_RETURN_INT64 (proc);
+}
+
+
+// INSERT INTO X VALUES (1)
+Datum
+pg_spi_insert_int (PG_FUNCTION_ARGS)
+{
+ int ret;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ char *query = "INSERT INTO X (a) VALUES ($1)";
+
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "SPI_connect failed");
+ }
+
+ nargs = 1;
+ argtypes[0] = INT4OID;
+ values[0] = Int32GetDatum (3);
+
+ ret = SPI_execute_with_args (query, nargs, argtypes, values, NULL, false, 0);
+ if (ret != SPI_OK_INSERT)
+ {
+ elog (ERROR, "SPI_execute_with_args failed");
+ }
+
+ SPI_finish ();
+
+ PG_RETURN_VOID ();
+}
+
+
+Datum
+pg_spi_prepare_insert (PG_FUNCTION_ARGS)
+{
+ static SPIPlanPtr prepared_plan = NULL;
+ int ret;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ const char *query = "INSERT INTO X (a) VALUES ($1)";
+ SPIPlanPtr new_plan;
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "SPI_connect failed ! \n");
+ }
+ if (prepared_plan == NULL)
+ {
+
+ argtypes[0] = INT4OID;
+ nargs = 1;
+ values[0] = Int32GetDatum (3);
+ new_plan = SPI_prepare (query, nargs, argtypes);
+ if (new_plan== NULL)
+ {
+ elog (ERROR, "SPI_prepare failed ! \n");
+ }
+ prepared_plan = SPI_saveplan (new_plan);
+ if (prepared_plan == NULL)
+ {
+ elog (ERROR, "SPI_saveplan failed ! \n");
+ }
+ }
+
+ ret = SPI_execute_plan (prepared_plan, values, NULL, false, 0);
+ if (ret != SPI_OK_INSERT)
+ {
+ elog (ERROR, "SPI_execute_plan failed ! \n");
+ }
+
+ SPI_finish ();
+
+ PG_RETURN_VOID ();
+}
+
+
+/*
+Datum
+pg_spi_prepare_insert_bytea(PG_FUNCTION_ARGS)
+{
+ static SPIPlanPtr prepared_plan = NULL;
+ int ret;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ Oid argtypes2[1];
+ Datum val[1];
+ char *query = "INSERT INTO X (a) VALUES ($1)";
+ SPIPlanPtr new_plan;
+ ret = SPI_connect();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog(ERROR, "SPI_connect failed ! \n");
+ }
+ if (prepared_plan == NULL) {
+ argtypes2[0] = BOOLOID;
+ val[0] = BoolGetDatum();
+ argtypes[0] = BYTEAOID;
+ nargs = 1;
+ values[0] = Int32GetDatum(3);
+ new_plan = SPI_prepare(query, nargs, argtypes);
+ if (new_plan== NULL)
+ {
+ elog(ERROR, "SPI_prepare failed ! \n");
+ }
+ prepared_plan = SPI_saveplan(new_plan);
+ if (prepared_plan == NULL)
+ {
+ elog(ERROR, "SPI_saveplan failed ! \n");
+ }
+ }
+
+ ret = SPI_execute_plan(prepared_plan, values, NULL, false, 0);
+ if (ret != SPI_OK_INSERT)
+ {
+ elog(ERROR, "SPI_execute_plan failed ! \n");
+ }
+
+ SPI_finish();
+
+ PG_RETURN_VOID();
+}
+*/
+
+Datum
+pg_spi_prepare_insert_without_saveplan (PG_FUNCTION_ARGS)
+{
+ int ret;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ const char *query = "INSERT INTO X (a) VALUES ($1)";
+ SPIPlanPtr new_plan;
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "SPI_connect failed");
+ }
+ {
+ argtypes[0] = INT4OID;
+ nargs = 1;
+ values[0] = Int32GetDatum (3);
+ new_plan = SPI_prepare (query, nargs, argtypes);
+ if (new_plan== NULL)
+ {
+ elog (ERROR, "SPI_prepare failed");
+ }
+ }
+
+ ret = SPI_execute_plan (new_plan, values, NULL, false, 0);
+ if (ret != SPI_OK_INSERT)
+ {
+ elog (ERROR, "SPI_execute_plan failed");
+ }
+
+ SPI_finish ();
+
+ PG_RETURN_VOID ();
+}
+
+
+/*
+Datum
+pg_spi_select_pair_from_y(PG_FUNCTION_ARGS)
+{
+ int ret;
+ valuest result;
+ bool isnull;
+ char *query = "SELECT 1,1 FROM Y";
+ result.col1 = 0;
+ result.col2 = 0;
+
+ if ((ret = SPI_connect()) < 0) {
+ fprintf(stderr, "SPI_connect returned %d\n", ret);
+ exit(1);
+ }
+ ret = SPI_exec(query, 0);
+ if (ret == SPI_OK_SELECT && SPI_processed > 0) {
+ int i;
+ SPITupleTable *tuptable = SPI_tuptable;
+ TupleDesc tupdesc = tuptable->tupdesc;
+ for (i = 0; i < SPI_processed; i++) {
+ HeapTuple tuple = tuptable->vals[i];
+ result.col1 = SPI_getbinval(tuple, tupdesc, 1, &isnull);
+ result.col2 = SPI_getbinval(tuple, tupdesc, 2, &isnull);
+ }
+ }
+ SPI_finish();
+ PG_RETURN_TEXT_P(result);
+}
+*/
+
+// SELECT X FROM Y WHERE Z=$1
+/*
+Datum
+pg_spi_select_with_cond(PG_FUNCTION_ARGS)
+{
+ int ret;
+ char *query;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ uint64 proc;
+ query = "SELECT col1 FROM Y WHERE col2 = $1";
+
+ ret = SPI_connect();
+ if (ret != SPI_OK_CONNECT) {
+ elog(ERROR, "SPI_connect failed: %d", ret);
+ }
+ nargs = 1;
+ argtypes[0] = INT4OID;
+ values[0] = Int32GetDatum(2);
+
+ ret = SPI_execute_with_args(query, nargs, argtypes, values, NULL, false, 0);
+ proc = SPI_processed;
+ if (ret != SPI_OK_SELECT)
+ {
+ elog(ERROR, "SPI_execute_with_args failed");
+ }
+
+ SPI_finish();
+
+
+ PG_RETURN_INT64(proc);
+ }*/
+
+////////SELECT WITH COND
+/*
+Datum pg_spi_prepare_select_with_cond(PG_FUNCTION_ARGS) {
+ static SPIPlanPtr prepared_plan = NULL;
+ SPIPlanPtr new_plan;
+ int ret;
+ Datum values[1];
+ uint64 proc;
+ int nargs;
+ Oid argtypes[1];
+ char *query = "SELECT col1 FROM Y WHERE col1 = $1";
+ int result = 0;
+
+ ret = SPI_connect();
+ if (ret != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed ! \n");
+
+ if (prepared_plan == NULL) {
+
+ argtypes[0] = INT4OID;
+ nargs = 1;
+ values[0] = DatumGetByteaP(SPI_getbinval(tuptable->vals[0], tupdesc, 1, &isnull)); //Value col2
+
+ new_plan = SPI_prepare(query, nargs, argtypes);
+ if (new_plan == NULL)
+ elog(ERROR, "SPI_prepare failed ! \n");
+
+ prepared_plan = SPI_saveplan(new_plan);
+ if (prepared_plan == NULL)
+ elog(ERROR, "SPI_saveplan failed ! \n");
+ }
+
+
+ ret = SPI_execute_plan(prepared_plan, values, NULL, false, 0);
+
+ if (ret != SPI_OK_SELECT) {
+ elog(ERROR, "SPI_execute_plan failed: %d \n", ret);
+ }
+
+ proc = SPI_processed;
+
+ if (proc > 0) {
+ SPITupleTable *tuptable = SPI_tuptable;
+ TupleDesc tupdesc = tuptable->tupdesc;
+ HeapTuple tuple;
+ int i;
+
+ for (i = 0; i < proc; i++) {
+ tuple = tuptable->vals[i];
+ for (int j = 1; j <= tupdesc->natts; j++) {
+ char * value = SPI_getvalue(tuple, tupdesc, j);
+ result += atoi(value);
+ }
+ }
+ }
+ SPI_finish();
+ PG_RETURN_INT64(result);
+}
+*/
+
+Datum
+pg_spi_prepare_select_with_cond_without_saveplan (PG_FUNCTION_ARGS)
+{
+
+ SPIPlanPtr new_plan;
+ int ret;
+ Datum values[1];
+ uint64 proc;
+ int nargs;
+ Oid argtypes[1];
+ char *query = "SELECT col1 FROM Y WHERE col2 = $1";
+ int result = 0;
+
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ elog (ERROR, "SPI_connect failed ! \n");
+
+ {
+
+ argtypes[0] = INT4OID;
+ nargs = 1;
+ values[0] = Int32GetDatum (2); // Value col2
+
+ new_plan = SPI_prepare (query, nargs, argtypes);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare failed ! \n");
+
+ }
+
+
+ ret = SPI_execute_plan (new_plan, values, NULL, false, 0);
+
+ if (ret != SPI_OK_SELECT)
+ {
+ elog (ERROR, "SPI_execute_plan failed: %d \n", ret);
+ }
+
+ proc = SPI_processed;
+
+ if (proc > 0)
+ {
+ SPITupleTable *tuptable = SPI_tuptable;
+ TupleDesc tupdesc = tuptable->tupdesc;
+ HeapTuple tuple;
+ int i;
+
+ for (i = 0; i < proc; i++)
+ {
+ tuple = tuptable->vals[i];
+ for (int j = 1; j <= tupdesc->natts; j++)
+ {
+ char *value = SPI_getvalue (tuple, tupdesc, j);
+ result += atoi (value);
+ }
+ }
+ }
+ SPI_finish ();
+ PG_RETURN_INT64 (result);
+}
+
+
+Datum
+pg_spi_update_y (PG_FUNCTION_ARGS)
+{
+ int ret;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ const char *query = "UPDATE Y SET col1 = 4 WHERE (col2 = $1)";
+
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "SPI_connect failed ! \n");
+ }
+
+ nargs = 1;
+ argtypes[0] = INT4OID;
+ values[0] = Int32GetDatum (0);
+
+ ret = SPI_execute_with_args (query, nargs, argtypes, values, NULL, false, 0);
+ if (ret != SPI_OK_UPDATE)
+ {
+ elog (ERROR, "SPI_execute_with_args failed ! \n");
+ }
+
+ SPI_finish ();
+
+ PG_RETURN_VOID ();
+}
+
+
+Datum
+pg_spi_prepare_update (PG_FUNCTION_ARGS)
+{
+ static SPIPlanPtr prepared_plan = NULL;
+ SPIPlanPtr new_plan;
+ int ret;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ const char *query = "UPDATE Y SET col1 = 4 WHERE (col2 = $1)";
+
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "SPI_connect failed ! \n");
+ }
+
+ if (prepared_plan == NULL)
+ {
+ argtypes[0] = INT4OID;
+ nargs = 1;
+ values[0] = Int32GetDatum (3);
+ // PREPARE
+ new_plan = SPI_prepare (query, nargs, argtypes);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare failed ! \n");
+ // SAVEPLAN
+ prepared_plan = SPI_saveplan (new_plan);
+ if (prepared_plan == NULL)
+ elog (ERROR, "SPI_saveplan failed ! \n");
+ }
+ ret = SPI_execute_plan (prepared_plan, values, NULL, false, 0);
+ if (ret != SPI_OK_UPDATE)
+ elog (ERROR, "SPI_execute_plan failed ! \n");
+
+ SPI_finish ();
+ PG_RETURN_VOID ();
+}
+
+
+/*
+Datum
+pg_spi_prepare_update_without_saveplan(PG_FUNCTION_ARGS)
+{}*/
+void
+_PG_fini (void)
+{
+}
+
+
+/*
+
+*/
+
+
+Datum
+pg_spi_get_dep_ref_fees (PG_FUNCTION_ARGS)
+{
+ /* Define plan to save */
+ static SPIPlanPtr deposit_plan;
+ static SPIPlanPtr ref_plan;
+ static SPIPlanPtr fees_plan;
+ static SPIPlanPtr dummy_plan;
+ /* Define variables to update */
+ Timestamp refund_deadline = PG_GETARG_TIMESTAMP (0);
+ bytea *merchant_pub = PG_GETARG_BYTEA_P (1);
+ bytea *wire_target_h_payto = PG_GETARG_BYTEA_P (2);
+ bytea *wtid_raw = PG_GETARG_BYTEA_P (3);
+ bool is_null;
+ /* Define variables to store the results of each SPI query */
+ uint64_t sum_deposit_val = 0;
+ uint32_t sum_deposit_frac = 0;
+ uint64_t s_refund_val = 0;
+ uint32_t s_refund_frac = 0;
+ uint64_t sum_dep_fee_val = 0;
+ uint32_t sum_dep_fee_frac = 0;
+ uint64_t norm_refund_val = 0;
+ uint32_t norm_refund_frac = 0;
+ uint64_t sum_refund_val = 0;
+ uint32_t sum_refund_frac = 0;
+ /* Define variables to store the Tuptable */
+ SPITupleTable *dep_res;
+ SPITupleTable *ref_res;
+ SPITupleTable *ref_by_coin_res;
+ SPITupleTable *norm_ref_by_coin_res;
+ SPITupleTable *fully_refunded_coins_res;
+ SPITupleTable *fees_res;
+ SPITupleTable *dummys_res;
+ /* Define variable to update */
+ Datum values_refund[2];
+ Datum values_deposit[3];
+ Datum values_fees[2];
+ Datum values_dummys[2];
+ TupleDesc tupdesc;
+ /* Define variables to replace some tables */
+ bytea *ref_by_coin_coin_pub;
+ int64 ref_by_coin_deposit_serial_id = 0;
+ bytea *norm_ref_by_coin_coin_pub;
+ int64_t norm_ref_by_coin_deposit_serial_id = 0;
+ bytea *new_dep_coin_pub = NULL;
+ int res = SPI_connect ();
+
+ /* Connect to SPI */
+ if (res < 0)
+ {
+ elog (ERROR, "Could not connect to SPI manager");
+ }
+ if (deposit_plan == NULL)
+ {
+ const char *dep_sql;
+ SPIPlanPtr new_plan;
+
+ // Execute first query and store results in variables
+ dep_sql =
+ "UPDATE deposits SET done=TRUE "
+ "WHERE NOT (done OR policy_blocked) "
+ "AND refund_deadline=$1 "
+ "AND merchant_pub=$2 "
+ "AND wire_target_h_payto=$3 "
+ "RETURNING "
+ "deposit_serial_id,"
+ "coin_pub,"
+ "amount_with_fee_val,"
+ "amount_with_fee_frac;";
+ fprintf (stderr, "dep sql %d\n", 1);
+ new_plan =
+ SPI_prepare (dep_sql, 4,(Oid[]){INT8OID, BYTEAOID, BYTEAOID});
+ fprintf (stderr, "dep sql %d\n", 2);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare failed for dep \n");
+ deposit_plan = SPI_saveplan (new_plan);
+ if (deposit_plan == NULL)
+ elog (ERROR, "SPI_saveplan failed for dep \n");
+ }
+ fprintf (stdout, "dep sql %d\n", 3);
+
+ values_deposit[0] = Int64GetDatum (refund_deadline);
+ values_deposit[1] = PointerGetDatum (merchant_pub);
+ values_deposit[2] = PointerGetDatum (wire_target_h_payto);
+
+ res = SPI_execute_plan (deposit_plan,
+ values_deposit,
+ NULL,
+ true,
+ 0);
+ fprintf (stdout, "dep sql %d\n", 4);
+ if (res != SPI_OK_UPDATE)
+ {
+ elog (ERROR, "Failed to execute subquery 1 \n");
+ }
+ // STORE TUPTABLE deposit
+ dep_res = SPI_tuptable;
+
+ for (unsigned int i = 0; i < SPI_processed; i++)
+ {
+ int64 dep_deposit_serial_ids = DatumGetInt64 (SPI_getbinval (
+ SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc, 1,
+ &is_null));
+ bytea *dep_coin_pub = DatumGetByteaP (SPI_getbinval (SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc,
+ 2, &is_null));
+ int64 dep_amount_val = DatumGetInt64 (SPI_getbinval (SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc,
+ 3, &is_null));
+ int32 dep_amount_frac = DatumGetInt32 (SPI_getbinval (SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc,
+ 4, &is_null));
+
+ if (is_null)
+ elog (ERROR, "Failed to retrieve data from deposit \n");
+ if (ref_plan == NULL)
+ {
+ // Execute second query with parameters from first query and store results in variables
+ const char *ref_sql =
+ "SELECT amount_with_fee_val, amount_with_fee_frac, coin_pub, deposit_serial_id "
+ "FROM refunds "
+ "WHERE coin_pub=$1 "
+ "AND deposit_serial_id=$2;";
+ SPIPlanPtr new_plan = SPI_prepare (ref_sql, 3, (Oid[]){BYTEAOID,
+ INT8OID});
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare failed for refund\n");
+ ref_plan = SPI_saveplan (new_plan);
+ if (ref_plan == NULL)
+ elog (ERROR, "SPI_saveplan failed for refund\n");
+ }
+ values_refund[0] = PointerGetDatum (dep_coin_pub);
+ values_refund[1] = Int64GetDatum (dep_deposit_serial_ids);
+ res = SPI_execute_plan (ref_plan,
+ values_refund,
+ NULL,
+ false,
+ 0);
+ if (res != SPI_OK_SELECT)
+ elog (ERROR, "Failed to execute subquery 2\n");
+ // STORE TUPTABLE refund
+ ref_res = SPI_tuptable;
+ for (unsigned int j = 0; j < SPI_processed; j++)
+ {
+ int64 ref_refund_val = DatumGetInt64 (SPI_getbinval (
+ SPI_tuptable->vals[j],
+ SPI_tuptable->tupdesc, 1,
+ &is_null));
+ int32 ref_refund_frac = DatumGetInt32 (SPI_getbinval (
+ SPI_tuptable->vals[j],
+ SPI_tuptable->tupdesc, 2,
+ &is_null));
+ bytea *ref_coin_pub = DatumGetByteaP (SPI_getbinval (
+ SPI_tuptable->vals[j],
+ SPI_tuptable->tupdesc, 3,
+ &is_null));
+ int64 ref_deposit_serial_id = DatumGetInt64 (SPI_getbinval (
+ SPI_tuptable->vals[j],
+ SPI_tuptable->tupdesc, 4,
+ &is_null));
+ // Execute third query with parameters from second query and store results in variables
+ ref_by_coin_coin_pub = ref_coin_pub;
+ ref_by_coin_deposit_serial_id = ref_deposit_serial_id;
+ // LOOP TO GET THE SUM FROM REFUND BY COIN
+ for (unsigned int i = 0; i<SPI_processed; i++)
+ {
+ if ((ref_by_coin_coin_pub ==
+ DatumGetByteaP (SPI_getbinval (SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc, 1,
+ &is_null)))
+ &&
+ (ref_by_coin_deposit_serial_id ==
+ DatumGetUInt64 (SPI_getbinval (SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc, 2,
+ &is_null)))
+ )
+ {
+ sum_refund_val += ref_refund_val;
+ sum_refund_frac += ref_refund_frac;
+ norm_ref_by_coin_coin_pub = ref_by_coin_coin_pub;
+ norm_ref_by_coin_deposit_serial_id = ref_by_coin_deposit_serial_id;
+ }
+ }// END SUM CALCULATION
+ // NORMALIZE REFUND VAL FRAC
+ norm_refund_val =
+ (sum_refund_val + sum_refund_frac) / 100000000;
+ norm_refund_frac =
+ sum_refund_frac % 100000000;
+ // Get refund values
+ s_refund_val += sum_refund_val;
+ s_refund_frac = sum_refund_frac;
+ }// END REFUND
+ if (norm_ref_by_coin_coin_pub == dep_coin_pub
+ && ref_by_coin_deposit_serial_id == dep_deposit_serial_ids
+ && norm_refund_val == dep_amount_val
+ && norm_refund_frac == dep_amount_frac)
+ {
+ new_dep_coin_pub = dep_coin_pub;
+ }
+ // Ensure we get the fee for each coin and not only once per denomination
+ if (fees_plan == NULL)
+ {
+ const char *fees_sql =
+ "SELECT "
+ " denom.fee_deposit_val AS fee_val, "
+ " denom.fee_deposit_frac AS fee_frac, "
+ "FROM known_coins kc"
+ "JOIN denominations denom USING (denominations_serial) "
+ "WHERE kc.coin_pub = $1 AND kc.coin_pub != $2;";
+ SPIPlanPtr new_plan = SPI_prepare (fees_sql, 3, (Oid[]){BYTEAOID,
+ BYTEAOID});
+ if (new_plan == NULL)
+ {
+ elog (ERROR, "SPI_prepare for fees failed ! \n");
+ }
+ fees_plan = SPI_saveplan (new_plan);
+ if (fees_plan == NULL)
+ {
+ elog (ERROR, "SPI_saveplan for fees failed ! \n");
+ }
+ }
+ values_fees[0] = PointerGetDatum (dep_coin_pub);
+ values_fees[1] = PointerGetDatum (new_dep_coin_pub);
+ res = SPI_execute_plan (fees_plan, values_fees, NULL, false, 0);
+ if (res != SPI_OK_SELECT)
+ elog (ERROR, "SPI_execute_plan failed for fees \n");
+ fees_res = SPI_tuptable;
+ tupdesc = fees_res->tupdesc;
+ for (unsigned int i = 0; i<SPI_processed; i++)
+ {
+ HeapTuple tuple = fees_res->vals[i];
+ bool is_null;
+ uint64_t fee_val = DatumGetUInt64 (SPI_getbinval (tuple, tupdesc, 1,
+ &is_null));
+ uint32_t fee_frac = DatumGetUInt32 (SPI_getbinval (tuple, tupdesc, 2,
+ &is_null));
+ uint64_t fees_deposit_serial_id = DatumGetUInt64 (SPI_getbinval (tuple,
+ tupdesc,
+ 3,
+ &is_null));
+ if (dummy_plan == NULL)
+ {
+ const char *insert_dummy_sql =
+ "INSERT INTO "
+ "aggregation_tracking(deposit_serial_id, wtid_raw)"
+ " VALUES ($1, $2)";
+
+ SPIPlanPtr new_plan = SPI_prepare (insert_dummy_sql, 2, (Oid[]){INT8OID,
+ BYTEAOID});
+ if (new_plan == NULL)
+ elog (ERROR, "FAILED to prepare aggregation tracking \n");
+ dummy_plan = SPI_saveplan (new_plan);
+ if (dummy_plan == NULL)
+ elog (ERROR, "FAILED to saveplan aggregation tracking\n");
+ }
+ values_dummys[0] = Int64GetDatum (dep_deposit_serial_ids);
+ values_dummys[1] = PointerGetDatum (wtid_raw);
+ res = SPI_execute_plan (dummy_plan, values_dummys, NULL, false, 0);
+ if (res != SPI_OK_INSERT)
+ elog (ERROR, "Failed to insert dummy\n");
+ dummys_res = SPI_tuptable;
+ // Calculation of deposit fees for not fully refunded deposits
+ sum_dep_fee_val += fee_val;
+ sum_dep_fee_frac += fee_frac;
+ }
+ // Get deposit values
+ sum_deposit_val += dep_amount_val;
+ sum_deposit_frac += dep_amount_frac;
+ }// END DEPOSIT
+ SPI_finish ();
+ PG_RETURN_VOID ();
+}
diff --git a/src/exchangedb/spi/own_test.control b/src/exchangedb/spi/own_test.control
new file mode 100644
index 000000000..4e73e207f
--- /dev/null
+++ b/src/exchangedb/spi/own_test.control
@@ -0,0 +1,4 @@
+comment = 'Example extension for testing purposes'
+default_version = '1.0'
+module_pathname = '$libdir/own_test'
+relocatable = true
diff --git a/src/exchangedb/spi/own_test.sql b/src/exchangedb/spi/own_test.sql
new file mode 100644
index 000000000..12729d068
--- /dev/null
+++ b/src/exchangedb/spi/own_test.sql
@@ -0,0 +1,201 @@
+DROP TABLE IF EXISTS X;
+CREATE TABLE X (
+ a integer
+);
+
+INSERT INTO X (a)
+ VALUES (1), (2), (3), (4), (5), (6), (7);
+
+DROP TABLE IF EXISTS Y;
+CREATE TABLE Y (col1 INT, col2 INT);
+INSERT INTO Y (col1,col2)
+ VALUES (1,2), (2,0), (0,4), (4,0), (0,6), (6,7), (7,8);
+
+DROP TABLE IF EXISTS Z;
+CREATE TABLE Z (col1 BYTEA);
+
+DROP TABLE IF EXISTS deposits;
+CREATE TABLE deposits(
+ deposit_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY
+ ,shard INT8 NOT NULL
+ ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)
+ ,known_coin_id INT8 NOT NULL
+ ,amount_with_fee_val INT8 NOT NULL
+ ,amount_with_fee_frac INT4 NOT NULL
+ ,wallet_timestamp INT8 NOT NULL
+ ,exchange_timestamp INT8 NOT NULL
+ ,refund_deadline INT8 NOT NULL
+ ,wire_deadline INT8 NOT NULL
+ ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
+ ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
+ ,coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)
+ ,wire_salt BYTEA NOT NULL CHECK (LENGTH(wire_salt)=16)
+ ,wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)
+ ,done BOOLEAN NOT NULL DEFAULT FALSE
+ ,policy_blocked BOOLEAN NOT NULL DEFAULT FALSE
+ ,policy_details_serial_id INT8);
+
+
+DROP FUNCTION IF EXISTS pg_spi_insert_int;
+CREATE FUNCTION pg_spi_insert_int()
+ RETURNS VOID
+ LANGUAGE c VOLATILE COST 100
+AS '$libdir/own_test', 'pg_spi_insert_int';
+
+DROP FUNCTION IF EXISTS pg_spi_select_from_x;
+CREATE FUNCTION pg_spi_select_from_x()
+ RETURNS INT8
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_select_from_x';
+
+/*
+CREATE FUNCTION pg_spi_select_pair_from_y()
+ RETURNS valuest
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_select_pair_from_y';
+*/
+/*CREATE FUNCTION pg_spi_select_with_cond()
+ RETURNS INT8
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_select_with_cond';
+*/
+
+DROP FUNCTION IF EXISTS pg_spi_update_y;
+CREATE FUNCTION pg_spi_update_y()
+ RETURNS VOID
+ LANGUAGE c VOLATILE COST 100
+AS '$libdir/own_test', 'pg_spi_update_y';
+
+DROP FUNCTION IF EXISTS pg_spi_prepare_example;
+CREATE FUNCTION pg_spi_prepare_example()
+ RETURNS INT8
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_example';
+
+DROP FUNCTION IF EXISTS pg_spi_prepare_example_without_saveplan;
+CREATE FUNCTION pg_spi_prepare_example_without_saveplan()
+ RETURNS INT8
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_example_without_saveplan';
+
+DROP FUNCTION IF EXISTS pg_spi_prepare_insert;
+CREATE FUNCTION pg_spi_prepare_insert()
+ RETURNS VOID
+ LANGUAGE c VOLATILE COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_insert';
+
+DROP FUNCTION IF EXISTS pg_spi_prepare_insert_without_saveplan;
+CREATE FUNCTION pg_spi_prepare_insert_without_saveplan()
+ RETURNS VOID
+ LANGUAGE c VOLATILE COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_insert_without_saveplan';
+
+/*
+CREATE FUNCTION pg_spi_prepare_select_with_cond()
+ RETURNS INT8
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_select_with_cond';
+*/
+
+DROP FUNCTION IF EXISTS pg_spi_prepare_select_with_cond_without_saveplan;
+CREATE FUNCTION pg_spi_prepare_select_with_cond_without_saveplan()
+ RETURNS INT8
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_select_with_cond_without_saveplan';
+
+DROP FUNCTION IF EXISTS pg_spi_prepare_update;
+CREATE FUNCTION pg_spi_prepare_update()
+ RETURNS VOID
+ LANGUAGE c VOLATILE COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_update';
+
+DROP FUNCTION IF EXISTS pg_spi_get_dep_ref_fees;
+CREATE FUNCTION pg_spi_get_dep_ref_fees(
+ IN in_timestamp INT8
+ ,IN merchant_pub BYTEA
+ ,IN wire_target_h_payto BYTEA
+ ,IN wtid BYTEA
+)
+ RETURNS VOID
+ LANGUAGE c VOLATILE COST 100
+AS '$libdir/own_test', 'pg_spi_get_dep_ref_fees';
+
+DROP FUNCTION IF EXISTS update_pg_spi_get_dep_ref_fees;
+CREATE FUNCTION update_pg_spi_get_dep_ref_fees(
+ IN in_refund_deadline INT8,
+ IN in_merchant_pub BYTEA,
+ IN in_wire_target_h_payto BYTEA
+)
+RETURNS SETOF record
+LANGUAGE plpgsql VOLATILE
+AS $$
+DECLARE
+
+BEGIN
+RETURN QUERY
+ UPDATE deposits
+ SET done = TRUE
+ WHERE NOT (done OR policy_blocked)
+ AND refund_deadline < in_refund_deadline
+ AND merchant_pub = in_merchant_pub
+ AND wire_target_h_payto = in_wire_target_h_payto
+ RETURNING
+ deposit_serial_id,
+ coin_pub,
+ amount_with_fee_val,
+ amount_with_fee_frac;
+END $$;
+
+DROP FUNCTION IF EXISTS stored_procedure_update;
+CREATE FUNCTION stored_procedure_update(
+IN in_number INT8
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ UPDATE Y
+ SET col1 = 4
+ WHERE col2 = in_number;
+END $$;
+
+DROP FUNCTION IF EXISTS stored_procedure_select;
+CREATE FUNCTION stored_procedure_select(OUT out_value INT8)
+RETURNS INT8
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ SELECT 1
+ INTO out_value
+ FROM X;
+ RETURN;
+END $$;
+
+
+DROP FUNCTION IF EXISTS stored_procedure_insert;
+CREATE FUNCTION stored_procedure_insert(
+IN in_number INT8,
+OUT out_number INT8)
+RETURNS INT8
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ INSERT INTO X (a)
+ VALUES (in_number)
+ RETURNING a INTO out_number;
+END $$;
+
+DROP FUNCTION IF EXISTS stored_procedure_select_with_cond;
+CREATE FUNCTION stored_procedure_select_with_cond(
+IN in_number INT8,
+OUT out_number INT8
+)
+RETURNS INT8
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ SELECT col1 INTO out_number
+ FROM Y
+ WHERE col2 = in_number;
+ RETURN;
+END $$;
diff --git a/src/exchangedb/spi/perf_own_test.c b/src/exchangedb/spi/perf_own_test.c
new file mode 100644
index 000000000..92be2235e
--- /dev/null
+++ b/src/exchangedb/spi/perf_own_test.c
@@ -0,0 +1,25 @@
+/*
+ This file is part of TALER
+ 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
+ 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 exchangedb/spi/perf_own_test.c
+ * @brief benchmark for 'own_test'
+ * @author Joseph Xu
+ */
+#include "exchangedb/platform.h"
+#include "exchangedb/taler_exchangedb_lib.h"
+#include "exchangedb/taler_json_lib.h"
+#include "exchangedb/taler_exchangedb_plugin.h"
+#include "own_test.sql"
diff --git a/src/exchangedb/spi/pg_aggregate.c b/src/exchangedb/spi/pg_aggregate.c
new file mode 100644
index 000000000..721f247c7
--- /dev/null
+++ b/src/exchangedb/spi/pg_aggregate.c
@@ -0,0 +1,411 @@
+#include "postgres.h"
+#include "fmgr.h"
+#include "utils/numeric.h"
+#include "utils/builtins.h"
+#include "executor/spi.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1 (get_deposit_summary);
+
+Datum
+get_deposit_summary (PG_FUNCTION_ARGS)
+{
+
+ static SPIPlanPtr deposit_plan;
+ static SPIPlanPtr refund_plan;
+ static SPIPlanPtr refund_by_coin_plan;
+ static SPIPlanPtr norm_refund_by_coin_plan;
+ static SPIPlanPtr fully_refunded_by_coins_plan;
+ static SPIPlanPtr fees_plan;
+
+ int shard = PG_GETARG_INT32 (0);
+ char *sql;
+ char *merchant_pub = text_to_cstring (PG_GETARG_TEXT_P (1));
+ char *wire_target_h_payto = text_to_cstring (PG_GETARG_TEXT_P (2));
+ char *wtid_raw = text_to_cstring (PG_GETARG_TEXT_P (3));
+ int refund_deadline = PG_GETARG_INT32 (4);
+ int conn = SPI_connect ();
+ if (conn != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "DB connection failed ! \n");
+ }
+
+ if (deposit_plan == NULL
+ || refund_plan == NULL
+ || refund_by_coin_plan == NULL
+ || norm_refund_by_coin_plan = NULL
+ || fully_refunded_coins_plan = NULL
+ || fees_plan
+ == NULL)
+ {
+ if (deposit_plan == NULL)
+ {
+ int nargs = 3;
+ Oid argtypes[3];
+ argtypes[0] = INT8OID;
+ argtypes[1] = BYTEAOID;
+ argtypes[2] = BYTEAOID;
+ const char *dep_sql =
+ " UPDATE deposits"
+ " SET done=TRUE"
+ " WHERE NOT (done OR policy_blocked)"
+ " AND refund_deadline < $1"
+ " AND merchant_pub = $2"
+ " AND wire_target_h_payto = $3"
+ " RETURNING"
+ " deposit_serial_id"
+ " ,coin_pub"
+ " ,amount_with_fee_val AS amount_val"
+ " ,amount_with_fee_frac AS amount_frac";
+ SPIPlanPtr new_plan =
+ SPI_prepare (dep_sql, 4, argtypes);
+ if (new_plan == NULL)
+ {
+ elog (ERROR, "SPI_prepare for deposit failed ! \n");
+ }
+ deposit_plan = SPI_saveplan (new_plan);
+ if (deposit_plan == NULL)
+ {
+ elog (ERROR, "SPI_saveplan for deposit failed ! \n");
+ }
+ }
+
+ Datum values[4];
+ values[0] = Int64GetDatum (refund_deadline);
+ values[1] = CStringGetDatum (merchant_pub);
+ values[2] = CStringGetDatum (wire_target_h_payto);
+ int ret = SPI_execute_plan (deposit_plan,
+ values,
+ NULL,
+ true,
+ 0);
+ if (ret != SPI_OK_UPDATE)
+ {
+ elog (ERROR, "Failed to execute subquery 1\n");
+ }
+ uint64_t *dep_deposit_serial_ids = palloc (sizeof(uint64_t)
+ * SPI_processed);
+ BYTEA **dep_coin_pubs = palloc (sizeof(BYTEA *) * SPI_processed);
+ uint64_t *dep_amount_vals = palloc (sizeof(uint64_t) * SPI_processed);
+ uint32_t *dep_amount_fracs = palloc (sizeof(uint32_t) * SPI_processed);
+ for (unsigned int i = 0; i < SPI_processed; i++)
+ {
+ HeapTuple tuple = SPI_tuptable->vals[i];
+ dep_deposit_serial_ids[i] =
+ DatumGetInt64 (SPI_getbinval (tuple, SPI_tuptable->tupdesc, 1, &ret));
+ dep_coin_pubs[i] =
+ DatumGetByteaP (SPI_getbinval (tuple, SPI_tuptable->tupdesc, 2, &ret));
+ dep_amount_vals[i] =
+ DatumGetInt64 (SPI_getbinval (tuple, SPI_tuptable->tupdesc, 3, &ret));
+ dep_amount_fracs[i] =
+ DatumGetInt32 (SPI_getbinval (tuple, SPI_tuptable->tupdesc, 4, &ret));
+ }
+
+
+ if (refund_plan == NULL)
+ {
+ const char *ref_sql =
+ "ref AS ("
+ " SELECT"
+ " amount_with_fee_val AS refund_val"
+ " ,amount_with_fee_frac AS refund_frac"
+ " ,coin_pub"
+ " ,deposit_serial_id"
+ " FROM refunds"
+ " WHERE coin_pub IN (SELECT coin_pub FROM dep)"
+ " AND deposit_serial_id IN (SELECT deposit_serial_id FROM dep)) ";
+ SPIPlanPtr new_plan = SPI_prepare (ref_sql, 0, NULL);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare for refund failed ! \n");
+ refund_plan = SPI_saveplan (new_plan);
+ if (refund_plan == NULL)
+ {
+ elog (ERROR, "SPI_saveplan for refund failed ! \n");
+ }
+ }
+
+ int64t_t *ref_deposit_serial_ids = palloc (sizeof(int64_t) * SPI_processed);
+
+ int res = SPI_execute_plan (refund_plan, NULL, NULL, false, 0);
+ if (res != SPI_OK_SELECT)
+ {
+ elog (ERROR, "Failed to execute subquery 2\n");
+ }
+ SPITupleTable *tuptable = SPI_tuptable;
+ TupleDesc tupdesc = tuptable->tupdesc;
+ for (unsigned int i = 0; i < SPI_processed; i++)
+ {
+ HeapTuple tuple = tuptable->vals[i];
+ Datum refund_val = SPI_getbinval (tuple, tupdesc, 1, &refund_val_isnull);
+ Datum refund_frac = SPI_getbinval (tuple, tupdesc, 2,
+ &refund_frac_isnull);
+ Datum coin_pub = SPI_getbinval (tuple, tupdesc, 3, &coin_pub_isnull);
+ Datum deposit_serial_id = SPI_getbinval (tuple, tupdesc, 4,
+ &deposit_serial_id_isnull);
+ if (refund_val_isnull
+ || refund_frac_isnull
+ || coin_pub_isnull
+ || deposit_serial_id_isnull)
+ {
+ elog (ERROR, "Failed to retrieve data from subquery 2");
+ }
+ uint64_t refund_val_int = DatumGetUInt64 (refund_val);
+ uint32_t refund_frac_int = DatumGetUInt32 (refund_frac);
+ BYTEA coin_pub = DatumGetByteaP (coin_pub);
+ ref_deposit_serial_ids = DatumGetInt64 (deposit_serial_id);
+
+ refund *new_refund = (refund*) palloc (sizeof(refund));
+ new_refund->coin_pub = coin_pub_str;
+ new_refund->deposit_serial_id = deposit_serial_id_int;
+ new_refund->amount_with_fee_val = refund_val_int;
+ new_refund->amount_with_fee_frac = refund_frac_int;
+ }
+
+
+ if (refund_by_coin_plan == NULL)
+ {
+ const char *ref_by_coin_sql =
+ "ref_by_coin AS ("
+ " SELECT"
+ " SUM(refund_val) AS sum_refund_val"
+ " ,SUM(refund_frac) AS sum_refund_frac"
+ " ,coin_pub"
+ " ,deposit_serial_id"
+ " FROM ref"
+ " GROUP BY coin_pub, deposit_serial_id) ";
+ SPIPlanPtr new_plan = SPI_prepare (ref_by_coin_sql, 0, NULL);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare for refund by coin failed ! \n");
+ refund_by_coin_plan = SPI_saveplan (new_plan);
+ if (refund_by_coin_plan == NULL)
+ elog (ERROR, "SPI_saveplan for refund failed");
+ }
+
+
+ int res = SPI_execute_plan (refund_by_coin_plan, NULL, NULL, false, 0);
+ if (res != SPI_OK_SELECT)
+ {
+ elog (ERROR, "Failed to execute subquery 2\n");
+ }
+
+ SPITupleTable *tuptable = SPI_tuptable;
+ TupleDesc tupdesc = tuptable->tupdesc;
+ for (unsigned int i = 0; i < SPI_processed; i++)
+ {
+ HeapTuple tuple = tuptable->vals[i];
+ Datum sum_refund_val = SPI_getbinval (tuple, tupdesc, 1,
+ &refund_val_isnull);
+ Datum sum_refund_frac = SPI_getbinval (tuple, tupdesc, 2,
+ &refund_frac_isnull);
+ Datum coin_pub = SPI_getbinval (tuple, tupdesc, 3, &coin_pub_isnull);
+ Datum deposit_serial_id_int = SPI_getbinval (tuple, tupdesc, 4,
+ &deposit_serial_id_isnull);
+ if (refund_val_isnull
+ || refund_frac_isnull
+ || coin_pub_isnull
+ || deposit_serial_id_isnull)
+ {
+ elog (ERROR, "Failed to retrieve data from subquery 2");
+ }
+ uint64_t s_refund_val_int = DatumGetUInt64 (sum_refund_val);
+ uint32_t s_refund_frac_int = DatumGetUInt32 (sum_refund_frac);
+ BYTEA coin_pub = DatumGetByteaP (coin_pub);
+ uint64_t deposit_serial_id_int = DatumGetInt64 (deposit_serial_id_int);
+ refund *new_refund_by_coin = (refund*) palloc (sizeof(refund));
+ new_refund_by_coin->coin_pub = coin_pub;
+ new_refund_by_coin->deposit_serial_id = deposit_serial_id_int;
+ new_refund_by_coin->refund_amount_with_fee_val = s_refund_val_int;
+ new_refund_by_coin->refund_amount_with_fee_frac = s_refund_frac_int;
+ }
+
+
+ if (norm_refund_by_coin_plan == NULL)
+ {
+ const char *norm_ref_by_coin_sql =
+ "norm_ref_by_coin AS ("
+ " SELECT"
+ " coin_pub"
+ " ,deposit_serial_id"
+ " FROM ref_by_coin) ";
+ SPIPlanPtr new_plan = SPI_prepare (norm_ref_by_coin_sql, 0, NULL);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare for norm refund by coin failed ! \n");
+ norm_refund_by_coin_plan = SPI_saveplan (new_plan);
+ if (norm_refund_by_coin_plan == NULL)
+ elog (ERROR, "SPI_saveplan for norm refund by coin failed ! \n");
+ }
+
+ double norm_refund_val =
+ ((double) new_refund_by_coin->refund_amount_with_fee_val
+ + (double) new_refund_by_coin->refund_amount_with_fee_frac) / 100000000;
+ double norm_refund_frac =
+ (double) new_refund_by_coin->refund_amount_with_fee_frac % 100000000;
+
+ if (fully_refunded_coins_plan == NULL)
+ {
+ const char *fully_refunded_coins_sql =
+ "fully_refunded_coins AS ("
+ " SELECT"
+ " dep.coin_pub"
+ " FROM norm_ref_by_coin norm"
+ " JOIN dep"
+ " ON (norm.coin_pub = dep.coin_pub"
+ " AND norm.deposit_serial_id = dep.deposit_serial_id"
+ " AND norm.norm_refund_val = dep.amount_val"
+ " AND norm.norm_refund_frac = dep.amount_frac)) ";
+ SPIPlanPtr new_plan =
+ SPI_prepare (fully_refunded_coins_sql, 0, NULL);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare for fully refunded coins failed ! \n");
+ fully_refunded_coins_plan = SPI_saveplan (new_plan);
+ if (fully_refunded_coins_plan == NULL)
+ elog (ERROR, "SPI_saveplan for fully refunded coins failed ! \n");
+ }
+
+ int res = SPI_execute_plan (fully_refunded_coins_sql);
+ if (res != SPI_OK_SELECT)
+ elog (ERROR, "Failed to execute subquery 4\n");
+ SPITupleTable *tuptable = SPI_tuptable;
+ TupleDesc tupdesc = tuptable->tupdesc;
+
+ BYTEA coin_pub = SPI_getbinval (tuple, tupdesc, 1, &coin_pub_isnull);
+ if (fees_plan == NULL)
+ {
+ const char *fees_sql =
+ "SELECT "
+ " denom.fee_deposit_val AS fee_val, "
+ " denom.fee_deposit_frac AS fee_frac, "
+ " cs.deposit_serial_id "
+ "FROM dep cs "
+ "JOIN known_coins kc USING (coin_pub) "
+ "JOIN denominations denom USING (denominations_serial) "
+ "WHERE coin_pub NOT IN (SELECT coin_pub FROM fully_refunded_coins)";
+ SPIPlanPtr new_plan =
+ SPI_prepare (fees_sql, 0, NULL);
+ if (new_plan == NULL)
+ {
+ elog (ERROR, "SPI_prepare for fees failed ! \n");
+ }
+ fees_plan = SPI_saveplan (new_plan);
+ if (fees_plan == NULL)
+ {
+ elog (ERROR, "SPI_saveplan for fees failed ! \n");
+ }
+ }
+ }
+ int fees_ntuples;
+ SPI_execute (fees_sql, true, 0);
+ if (SPI_result_code () != SPI_OK_SELECT)
+ {
+ ereport (
+ ERROR,
+ (errcode (ERRCODE_INTERNAL_ERROR),
+ errmsg ("deposit fee query failed: error code %d \n",
+ SPI_result_code ())));
+ }
+ fees_ntuples = SPI_processed;
+
+ if (fees_ntuples > 0)
+ {
+ for (i = 0; i < fees_ntuples; i++)
+ {
+ Datum fee_val_datum =
+ SPI_getbinval (SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1,
+ &fee_null);
+ Datum fee_frac_datum =
+ SPI_getbinval (SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2,
+ &fee_null);
+ Datum deposit_id_datum =
+ SPI_getbinval (SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 3,
+ &deposit_null);
+ if (! fee_null && ! deposit_null)
+ {
+ int64 fee_val = DatumGetInt64 (fee_val_datum);
+ int32 fee_frac = DatumGetInt32 (fee_frac_datum);
+ int64 deposit_id = DatumGetInt64 (deposit_id_datum);
+ sum_fee_value += fee_val;
+ sum_fee_fraction += fee_frac;
+ char *insert_agg_sql =
+ psprintf (
+ "INSERT INTO "
+ "aggregation_tracking(deposit_serial_id, wtid_raw)"
+ " VALUES (%lld, '%s')",
+ deposit_id, wtid_raw);
+ SPI_execute (insert_agg_sql, false, 0);
+ }
+ }
+ }
+
+ TupleDesc tupdesc;
+ SPITupleTable *tuptable = SPI_tuptable;
+ HeapTuple tuple;
+ Datum result;
+
+ if (tuptable == NULL || SPI_processed != 1)
+ {
+ ereport (
+ ERROR,
+ (errcode (ERRCODE_INTERNAL_ERROR),
+ errmsg ("Unexpected result \n")));
+ }
+ tupdesc = SPI_tuptable->tupdesc;
+ tuple = SPI_tuptable->vals[0];
+ result = HeapTupleGetDatum (tuple);
+
+ TupleDesc result_desc = CreateTemplateTupleDesc (6, false);
+ TupleDescInitEntry (result_desc, (AttrNumber) 1, "sum_deposit_value", INT8OID,
+ -1, 0);
+ TupleDescInitEntry (result_desc, (AttrNumber) 2, "sum_deposit_fraction",
+ INT4OID, -1, 0);
+ TupleDescInitEntry (result_desc, (AttrNumber) 3, "sum_refund_value", INT8OID,
+ -1, 0);
+ TupleDescInitEntry (result_desc, (AttrNumber) 4, "sum_refund_fraction",
+ INT4OID, -1, 0);
+ TupleDescInitEntry (result_desc, (AttrNumber) 5, "sum_fee_value", INT8OID, -1,
+ 0);
+ TupleDescInitEntry (result_desc, (AttrNumber) 6, "sum_fee_fraction", INT4OID,
+ -1, 0);
+
+ int ret = SPI_prepare (sql, 4, argtypes);
+ if (ret != SPI_OK_PREPARE)
+ {
+ elog (ERROR, "Failed to prepare statement: %s \n", sql);
+ }
+
+ ret = SPI_execute_plan (plan, args, nulls, true, 0);
+ if (ret != SPI_OK_SELECT)
+ {
+ elog (ERROR, "Failed to execute statement: %s \n", sql);
+ }
+
+ if (SPI_processed > 0)
+ {
+ HeapTuple tuple;
+ Datum values[6];
+ bool nulls[6] = {false};
+ values[0] =
+ SPI_getbinval (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1,
+ &nulls[0]);
+ values[1] =
+ SPI_getbinval (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2,
+ &nulls[1]);
+ values[2] =
+ SPI_getbinval (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 3,
+ &nulls[2]);
+ values[3] =
+ SPI_getbinval (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 4,
+ &nulls[3]);
+ values[4] =
+ SPI_getbinval (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 5,
+ &nulls[4]);
+ values[5] =
+ SPI_getbinval (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 6,
+ &nulls[5]);
+ tuple = heap_form_tuple (result_desc, values, nulls);
+ PG_RETURN_DATUM (HeapTupleGetDatum (tuple));
+ }
+ SPI_finish ();
+
+ PG_RETURN_NULL ();
+}
diff --git a/src/exchangedb/test-exchange-db-postgres.conf b/src/exchangedb/test-exchange-db-postgres.conf
index f6db76942..7f0332686 100644
--- a/src/exchangedb/test-exchange-db-postgres.conf
+++ b/src/exchangedb/test-exchange-db-postgres.conf
@@ -2,6 +2,8 @@
#The DB plugin to use
DB = postgres
+BASE_URL = http://localhost/
+
[exchangedb-postgres]
#The connection string the plugin has to use for connecting to the database
@@ -26,3 +28,9 @@ IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
# After how long do we forget about reserves? Should be above
# the legal expiration timeframe of withdrawn coins.
LEGAL_RESERVE_EXPIRATION_TIME = 7 years
+
+# Shift to apply before aggregating.
+AGGREGATOR_SHIFT = 1s
+
+# Number of purses per account by default.
+DEFAULT_PURSE_LIMIT = 1
diff --git a/src/exchangedb/test-exchangedb-batch-reserves-in-insert-postgres b/src/exchangedb/test-exchangedb-batch-reserves-in-insert-postgres
new file mode 100755
index 000000000..bc044232a
--- /dev/null
+++ b/src/exchangedb/test-exchangedb-batch-reserves-in-insert-postgres
@@ -0,0 +1,210 @@
+#! /bin/bash
+
+# test-exchangedb-batch-reserves-in-insert-postgres - temporary wrapper script for .libs/test-exchangedb-batch-reserves-in-insert-postgres
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15
+#
+# The test-exchangedb-batch-reserves-in-insert-postgres program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.6'
+ notinst_deplibs=' libtalerexchangedb.la ../../src/json/libtalerjson.la ../../src/util/libtalerutil.la ../../src/pq/libtalerpq.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "test-exchangedb-batch-reserves-in-insert-postgres:test-exchangedb-batch-reserves-in-insert-postgres:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-15" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "test-exchangedb-batch-reserves-in-insert-postgres:test-exchangedb-batch-reserves-in-insert-postgres:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "test-exchangedb-batch-reserves-in-insert-postgres:test-exchangedb-batch-reserves-in-insert-postgres:$LINENO: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program='test-exchangedb-batch-reserves-in-insert-postgres'
+ progdir="$thisdir/.libs"
+
+
+ if test -f "$progdir/$program"; then
+ # Add our own library path to LD_LIBRARY_PATH
+ LD_LIBRARY_PATH="/home/priscilla/exchange/src/exchangedb/.libs:/home/priscilla/exchange/src/json/.libs:/home/priscilla/exchange/src/util/.libs:/home/priscilla/exchange/src/pq/.libs:$LD_LIBRARY_PATH"
+
+ # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
+
+ export LD_LIBRARY_PATH
+
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/exchangedb/test-exchangedb-by-j-postgres b/src/exchangedb/test-exchangedb-by-j-postgres
new file mode 100755
index 000000000..11d295cc2
--- /dev/null
+++ b/src/exchangedb/test-exchangedb-by-j-postgres
@@ -0,0 +1,210 @@
+#! /bin/bash
+
+# test-exchangedb-by-j-postgres - temporary wrapper script for .libs/test-exchangedb-by-j-postgres
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15
+#
+# The test-exchangedb-by-j-postgres program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.6'
+ notinst_deplibs=' libtalerexchangedb.la ../../src/json/libtalerjson.la ../../src/util/libtalerutil.la ../../src/pq/libtalerpq.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "test-exchangedb-by-j-postgres:test-exchangedb-by-j-postgres:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-15" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "test-exchangedb-by-j-postgres:test-exchangedb-by-j-postgres:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "test-exchangedb-by-j-postgres:test-exchangedb-by-j-postgres:$LINENO: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program='test-exchangedb-by-j-postgres'
+ progdir="$thisdir/.libs"
+
+
+ if test -f "$progdir/$program"; then
+ # Add our own library path to LD_LIBRARY_PATH
+ LD_LIBRARY_PATH="/home/priscilla/exchange/src/exchangedb/.libs:/home/priscilla/exchange/src/json/.libs:/home/priscilla/exchange/src/util/.libs:/home/priscilla/exchange/src/pq/.libs:$LD_LIBRARY_PATH"
+
+ # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
+
+ export LD_LIBRARY_PATH
+
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/exchangedb/test-exchangedb-populate-link-data-postgres b/src/exchangedb/test-exchangedb-populate-link-data-postgres
new file mode 100755
index 000000000..f3d673519
--- /dev/null
+++ b/src/exchangedb/test-exchangedb-populate-link-data-postgres
@@ -0,0 +1,210 @@
+#! /bin/bash
+
+# test-exchangedb-populate-link-data-postgres - temporary wrapper script for .libs/test-exchangedb-populate-link-data-postgres
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15
+#
+# The test-exchangedb-populate-link-data-postgres program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.6'
+ notinst_deplibs=' libtalerexchangedb.la ../../src/json/libtalerjson.la ../../src/util/libtalerutil.la ../../src/pq/libtalerpq.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "test-exchangedb-populate-link-data-postgres:test-exchangedb-populate-link-data-postgres:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-15" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "test-exchangedb-populate-link-data-postgres:test-exchangedb-populate-link-data-postgres:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "test-exchangedb-populate-link-data-postgres:test-exchangedb-populate-link-data-postgres:$LINENO: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program='test-exchangedb-populate-link-data-postgres'
+ progdir="$thisdir/.libs"
+
+
+ if test -f "$progdir/$program"; then
+ # Add our own library path to LD_LIBRARY_PATH
+ LD_LIBRARY_PATH="/home/priscilla/exchange/src/exchangedb/.libs:/home/priscilla/exchange/src/json/.libs:/home/priscilla/exchange/src/util/.libs:/home/priscilla/exchange/src/pq/.libs:$LD_LIBRARY_PATH"
+
+ # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
+
+ export LD_LIBRARY_PATH
+
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/exchangedb/test-exchangedb-populate-ready-deposit-postgres b/src/exchangedb/test-exchangedb-populate-ready-deposit-postgres
new file mode 100755
index 000000000..7747f381c
--- /dev/null
+++ b/src/exchangedb/test-exchangedb-populate-ready-deposit-postgres
@@ -0,0 +1,210 @@
+#! /bin/bash
+
+# test-exchangedb-populate-ready-deposit-postgres - temporary wrapper script for .libs/test-exchangedb-populate-ready-deposit-postgres
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15
+#
+# The test-exchangedb-populate-ready-deposit-postgres program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.6'
+ notinst_deplibs=' libtalerexchangedb.la ../../src/json/libtalerjson.la ../../src/util/libtalerutil.la ../../src/pq/libtalerpq.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "test-exchangedb-populate-ready-deposit-postgres:test-exchangedb-populate-ready-deposit-postgres:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-15" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "test-exchangedb-populate-ready-deposit-postgres:test-exchangedb-populate-ready-deposit-postgres:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "test-exchangedb-populate-ready-deposit-postgres:test-exchangedb-populate-ready-deposit-postgres:$LINENO: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program='test-exchangedb-populate-ready-deposit-postgres'
+ progdir="$thisdir/.libs"
+
+
+ if test -f "$progdir/$program"; then
+ # Add our own library path to LD_LIBRARY_PATH
+ LD_LIBRARY_PATH="/home/priscilla/exchange/src/exchangedb/.libs:/home/priscilla/exchange/src/json/.libs:/home/priscilla/exchange/src/util/.libs:/home/priscilla/exchange/src/pq/.libs:$LD_LIBRARY_PATH"
+
+ # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
+
+ export LD_LIBRARY_PATH
+
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/exchangedb/test-exchangedb-populate-select-refunds-by-coin-postgres b/src/exchangedb/test-exchangedb-populate-select-refunds-by-coin-postgres
new file mode 100755
index 000000000..ce7ebb712
--- /dev/null
+++ b/src/exchangedb/test-exchangedb-populate-select-refunds-by-coin-postgres
@@ -0,0 +1,210 @@
+#! /bin/bash
+
+# test-exchangedb-populate-select-refunds-by-coin-postgres - temporary wrapper script for .libs/test-exchangedb-populate-select-refunds-by-coin-postgres
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15
+#
+# The test-exchangedb-populate-select-refunds-by-coin-postgres program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.6'
+ notinst_deplibs=' libtalerexchangedb.la ../../src/json/libtalerjson.la ../../src/util/libtalerutil.la ../../src/pq/libtalerpq.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "test-exchangedb-populate-select-refunds-by-coin-postgres:test-exchangedb-populate-select-refunds-by-coin-postgres:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-15" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "test-exchangedb-populate-select-refunds-by-coin-postgres:test-exchangedb-populate-select-refunds-by-coin-postgres:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "test-exchangedb-populate-select-refunds-by-coin-postgres:test-exchangedb-populate-select-refunds-by-coin-postgres:$LINENO: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program='test-exchangedb-populate-select-refunds-by-coin-postgres'
+ progdir="$thisdir/.libs"
+
+
+ if test -f "$progdir/$program"; then
+ # Add our own library path to LD_LIBRARY_PATH
+ LD_LIBRARY_PATH="/home/priscilla/exchange/src/exchangedb/.libs:/home/priscilla/exchange/src/json/.libs:/home/priscilla/exchange/src/util/.libs:/home/priscilla/exchange/src/pq/.libs:$LD_LIBRARY_PATH"
+
+ # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
+
+ export LD_LIBRARY_PATH
+
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
index a92721641..22788a562 100644
--- a/src/exchangedb/test_exchangedb.c
+++ b/src/exchangedb/test_exchangedb.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 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
@@ -33,9 +33,9 @@ static int result;
/**
* Report line of error if @a cond is true, and jump to label "drop".
*/
-#define FAILIF(cond) \
+#define FAILIF(cond) \
do { \
- if (! (cond)) { break;} \
+ if (! (cond)) { break;} \
GNUNET_break (0); \
goto drop; \
} while (0)
@@ -45,7 +45,8 @@ static int result;
* Initializes @a ptr with random data.
*/
#define RND_BLK(ptr) \
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, \
+ sizeof (*ptr))
/**
* Initializes @a ptr with zeros.
@@ -95,8 +96,7 @@ mark_prepare_cb (void *cls,
const char *buf,
size_t buf_size)
{
- struct TALER_EXCHANGEDB_Session *session = cls;
-
+ (void) cls;
GNUNET_assert (11 == buf_size);
GNUNET_assert (0 == strcasecmp (wire_method,
"testcase"));
@@ -105,39 +105,97 @@ mark_prepare_cb (void *cls,
buf_size));
GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
plugin->wire_prepare_data_mark_finished (plugin->cls,
- session,
rowid));
}
/**
+ * Simple check that config retrieval and setting for extensions work
+ */
+static enum GNUNET_GenericReturnValue
+test_extension_manifest (void)
+{
+ char *manifest;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->get_extension_manifest (plugin->cls,
+ "fnord",
+ &manifest));
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->set_extension_manifest (plugin->cls,
+ "fnord",
+ "bar"));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_extension_manifest (plugin->cls,
+ "fnord",
+ &manifest));
+
+ FAILIF (0 != strcmp ("bar", manifest));
+ GNUNET_free (manifest);
+
+ /* let's do this again! */
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->set_extension_manifest (plugin->cls,
+ "fnord",
+ "buzz"));
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_extension_manifest (plugin->cls,
+ "fnord",
+ &manifest));
+
+ FAILIF (0 != strcmp ("buzz", manifest));
+ GNUNET_free (manifest);
+
+ /* let's do this again, with NULL */
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->set_extension_manifest (plugin->cls,
+ "fnord",
+ NULL));
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_extension_manifest (plugin->cls,
+ "fnord",
+ &manifest));
+
+ FAILIF (NULL != manifest);
+
+ return GNUNET_OK;
+drop:
+ return GNUNET_SYSERR;
+}
+
+
+/**
* Test API relating to persisting the wire plugins preparation data.
*
- * @param session database session to use for the test
* @return #GNUNET_OK on success
*/
-static int
-test_wire_prepare (struct TALER_EXCHANGEDB_Session *session)
+static enum GNUNET_GenericReturnValue
+test_wire_prepare (void)
{
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->wire_prepare_data_get (plugin->cls,
- session,
+ 0,
+ 1,
&dead_prepare_cb,
NULL));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->wire_prepare_data_insert (plugin->cls,
- session,
"testcase",
"hello world",
11));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->wire_prepare_data_get (plugin->cls,
- session,
+ 0,
+ 1,
&mark_prepare_cb,
- session));
+ NULL));
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->wire_prepare_data_get (plugin->cls,
- session,
+ 0,
+ 1,
&dead_prepare_cb,
NULL));
return GNUNET_OK;
@@ -149,7 +207,6 @@ drop:
/**
* Checks if the given reserve has the given amount of balance and expiry
*
- * @param session the database connection
* @param pub the public key of the reserve
* @param value balance value
* @param fraction balance fraction
@@ -157,9 +214,8 @@ drop:
* @return #GNUNET_OK if the given reserve has the same balance and expiration
* as the given parameters; #GNUNET_SYSERR if not
*/
-static int
-check_reserve (struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_ReservePublicKeyP *pub,
+static enum GNUNET_GenericReturnValue
+check_reserve (const struct TALER_ReservePublicKeyP *pub,
uint64_t value,
uint32_t fraction,
const char *currency)
@@ -169,12 +225,11 @@ check_reserve (struct TALER_EXCHANGEDB_Session *session,
reserve.pub = *pub;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->reserves_get (plugin->cls,
- session,
&reserve));
FAILIF (value != reserve.balance.value);
FAILIF (fraction != reserve.balance.fraction);
- FAILIF (0 != strcmp (currency, reserve.balance.currency));
-
+ FAILIF (0 != strcmp (currency,
+ reserve.balance.currency));
return GNUNET_OK;
drop:
return GNUNET_SYSERR;
@@ -191,85 +246,71 @@ struct DenomKeyPair
/**
* Destroy a denomination key pair. The key is not necessarily removed from the DB.
*
- * @param dkp the keypair to destroy
+ * @param dkp the key pair to destroy
*/
static void
destroy_denom_key_pair (struct DenomKeyPair *dkp)
{
- GNUNET_CRYPTO_rsa_public_key_free (dkp->pub.rsa_public_key);
- GNUNET_CRYPTO_rsa_private_key_free (dkp->priv.rsa_private_key);
+ TALER_denom_pub_free (&dkp->pub);
+ TALER_denom_priv_free (&dkp->priv);
GNUNET_free (dkp);
}
/**
- * Create a denominaiton key pair by registering the denomination in the DB.
+ * Create a denomination key pair by registering the denomination in the DB.
*
* @param size the size of the denomination key
- * @param session the DB session
* @param now time to use for key generation, legal expiration will be 3h later.
- * @param fee_withdraw withdraw fee to use
- * @param fee_deposit deposit fee to use
- * @param fee_refresh refresh fee to use
- * @param fee_refund refund fee to use
+ * @param fees fees to use
* @return the denominaiton key pair; NULL upon error
*/
static struct DenomKeyPair *
create_denom_key_pair (unsigned int size,
- struct TALER_EXCHANGEDB_Session *session,
- struct GNUNET_TIME_Absolute now,
+ struct GNUNET_TIME_Timestamp now,
const struct TALER_Amount *value,
- const struct TALER_Amount *fee_withdraw,
- const struct TALER_Amount *fee_deposit,
- const struct TALER_Amount *fee_refresh,
- const struct TALER_Amount *fee_refund)
+ const struct TALER_DenomFeeSet *fees)
{
struct DenomKeyPair *dkp;
struct TALER_EXCHANGEDB_DenominationKey dki;
- struct TALER_EXCHANGEDB_DenominationKeyInformationP issue2;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue2;
dkp = GNUNET_new (struct DenomKeyPair);
- dkp->priv.rsa_private_key = GNUNET_CRYPTO_rsa_private_key_create (size);
- GNUNET_assert (NULL != dkp->priv.rsa_private_key);
- dkp->pub.rsa_public_key
- = GNUNET_CRYPTO_rsa_private_key_get_public (dkp->priv.rsa_private_key);
-
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_priv_create (&dkp->priv,
+ &dkp->pub,
+ GNUNET_CRYPTO_BSA_RSA,
+ size));
/* Using memset() as fields like master key and signature
are not properly initialized for this test. */
memset (&dki,
0,
sizeof (struct TALER_EXCHANGEDB_DenominationKey));
dki.denom_pub = dkp->pub;
- GNUNET_TIME_round_abs (&now);
- dki.issue.properties.start = GNUNET_TIME_absolute_hton (now);
- dki.issue.properties.expire_withdraw = GNUNET_TIME_absolute_hton
- (GNUNET_TIME_absolute_add (now,
- GNUNET_TIME_UNIT_HOURS));
- dki.issue.properties.expire_deposit = GNUNET_TIME_absolute_hton
- (GNUNET_TIME_absolute_add
- (now,
- GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_HOURS, 2)));
- dki.issue.properties.expire_legal = GNUNET_TIME_absolute_hton
- (GNUNET_TIME_absolute_add
- (now,
- GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_HOURS, 3)));
- TALER_amount_hton (&dki.issue.properties.value, value);
- TALER_amount_hton (&dki.issue.properties.fee_withdraw, fee_withdraw);
- TALER_amount_hton (&dki.issue.properties.fee_deposit, fee_deposit);
- TALER_amount_hton (&dki.issue.properties.fee_refresh, fee_refresh);
- TALER_amount_hton (&dki.issue.properties.fee_refund, fee_refund);
- GNUNET_CRYPTO_rsa_public_key_hash (dkp->pub.rsa_public_key,
- &dki.issue.properties.denom_hash);
-
- dki.issue.properties.purpose.size = htonl (sizeof (struct
- TALER_DenominationKeyValidityPS));
- dki.issue.properties.purpose.purpose = htonl (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY);
+ dki.issue.start = now;
+ dki.issue.expire_withdraw
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_UNIT_HOURS));
+ dki.issue.expire_deposit
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 2)));
+ dki.issue.expire_legal
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 3)));
+ dki.issue.value = *value;
+ dki.issue.fees = *fees;
+ TALER_denom_pub_hash (&dkp->pub,
+ &dki.issue.denom_hash);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_denomination_info (plugin->cls,
- session,
&dki.denom_pub,
&dki.issue))
{
@@ -277,10 +318,10 @@ create_denom_key_pair (unsigned int size,
destroy_denom_key_pair (dkp);
return NULL;
}
+ memset (&issue2, 0, sizeof (issue2));
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->get_denomination_info (plugin->cls,
- session,
- &dki.issue.properties.denom_hash,
+ &dki.issue.denom_hash,
&issue2))
{
GNUNET_break (0);
@@ -299,10 +340,7 @@ create_denom_key_pair (unsigned int size,
static struct TALER_Amount value;
-static struct TALER_Amount fee_withdraw;
-static struct TALER_Amount fee_deposit;
-static struct TALER_Amount fee_refresh;
-static struct TALER_Amount fee_refund;
+static struct TALER_DenomFeeSet fees;
static struct TALER_Amount fee_closing;
static struct TALER_Amount amount_with_fee;
@@ -318,9 +356,9 @@ static struct TALER_Amount amount_with_fee;
#define MELT_NOREVEAL_INDEX 1
/**
- * How big do we make the coin envelopes?
+ * How big do we make the RSA keys?
*/
-#define COIN_ENC_MAX_SIZE 512
+#define RSA_KEY_SIZE 1024
static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins;
@@ -337,24 +375,15 @@ static struct TALER_TransferPublicKeyP tpub;
* @param rowid unique serial ID for the row in our database
* @param num_freshcoins size of the @a rrcs array
* @param rrcs array of @a num_freshcoins information about coins to be created
- * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
- * @param tprivs array of @e num_tprivs transfer private keys
- * @param tp transfer public key information
*/
static void
never_called_cb (void *cls,
uint32_t num_freshcoins,
- const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
- unsigned int num_tprivs,
- const struct TALER_TransferPrivateKeyP *tprivs,
- const struct TALER_TransferPublicKeyP *tp)
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs)
{
(void) cls;
(void) num_freshcoins;
(void) rrcs;
- (void) num_tprivs;
- (void) tprivs;
- (void) tp;
GNUNET_assert (0); /* should never be called! */
}
@@ -367,18 +396,12 @@ never_called_cb (void *cls,
* @param rowid unique serial ID for the row in our database
* @param num_freshcoins size of the @a rrcs array
* @param rrcs array of @a num_freshcoins information about coins to be created
- * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
- * @param tprivsr array of @e num_tprivs transfer private keys
- * @param tpr transfer public key information
*/
static void
check_refresh_reveal_cb (
void *cls,
uint32_t num_freshcoins,
- const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
- unsigned int num_tprivs,
- const struct TALER_TransferPrivateKeyP *tprivsr,
- const struct TALER_TransferPublicKeyP *tpr)
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs)
{
(void) cls;
/* compare the refresh commit coin arrays */
@@ -388,20 +411,13 @@ check_refresh_reveal_cb (
&revealed_coins[cnt];
const struct TALER_EXCHANGEDB_RefreshRevealedCoin *bcoin = &rrcs[cnt];
- GNUNET_assert (acoin->coin_ev_size == bcoin->coin_ev_size);
GNUNET_assert (0 ==
- GNUNET_memcmp (acoin->coin_ev,
- bcoin->coin_ev));
+ TALER_blinded_planchet_cmp (&acoin->blinded_planchet,
+ &bcoin->blinded_planchet));
GNUNET_assert (0 ==
- GNUNET_CRYPTO_rsa_public_key_cmp (
- acoin->denom_pub.rsa_public_key,
- bcoin->denom_pub.
- rsa_public_key));
+ GNUNET_memcmp (&acoin->h_denom_pub,
+ &bcoin->h_denom_pub));
}
- GNUNET_assert (0 == GNUNET_memcmp (&tpub, tpr));
- GNUNET_assert (0 == memcmp (tprivs, tprivsr,
- sizeof(struct TALER_TransferPrivateKeyP)
- * (TALER_CNC_KAPPA - 1)));
}
@@ -420,6 +436,7 @@ static unsigned int auditor_row_cnt;
* @param cls closure
* @param rowid unique serial ID for the refresh session in our DB
* @param denom_pub denomination of the @a coin_pub
+ * @param h_age_commitment hash of age commitment that went into the minting, may be NULL
* @param coin_pub public key of the coin
* @param coin_sig signature from the coin
* @param amount_with_fee amount that was deposited including fee
@@ -428,23 +445,27 @@ static unsigned int auditor_row_cnt;
* @param rc what is the session hash
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
-audit_refresh_session_cb (void *cls,
- uint64_t rowid,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_Amount *amount_with_fee,
- uint32_t noreveal_index,
- const struct TALER_RefreshCommitmentP *rc)
+static enum GNUNET_GenericReturnValue
+audit_refresh_session_cb (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ const struct TALER_Amount *amount_with_fee,
+ uint32_t noreveal_index,
+ const struct TALER_RefreshCommitmentP *rc)
{
(void) cls;
(void) rowid;
(void) denom_pub;
+ (void) coin_pub;
(void) coin_sig;
(void) amount_with_fee;
(void) noreveal_index;
(void) rc;
+ (void) h_age_commitment;
auditor_row_cnt++;
return GNUNET_OK;
}
@@ -475,22 +496,19 @@ handle_link_data_cb (void *cls,
NULL != ldlp;
ldlp = ldlp->next)
{
- int found;
+ bool found;
- found = GNUNET_NO;
+ found = false;
for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
{
- GNUNET_assert (NULL != ldlp->ev_sig.rsa_signature);
if ( (0 ==
- GNUNET_CRYPTO_rsa_public_key_cmp (ldlp->denom_pub.rsa_public_key,
- new_dkp[cnt]->pub.rsa_public_key))
- &&
+ TALER_denom_pub_cmp (&ldlp->denom_pub,
+ &new_dkp[cnt]->pub)) &&
(0 ==
- GNUNET_CRYPTO_rsa_signature_cmp (ldlp->ev_sig.rsa_signature,
- revealed_coins[cnt].coin_sig.
- rsa_signature)) )
+ TALER_blinded_denom_sig_cmp (&ldlp->ev_sig,
+ &revealed_coins[cnt].coin_sig)) )
{
- found = GNUNET_YES;
+ found = true;
break;
}
}
@@ -500,264 +518,44 @@ handle_link_data_cb (void *cls,
/**
- * Function to test melting of coins as part of a refresh session
- *
- * @param session the database session
- * @param refresh_session the refresh session
- * @return #GNUNET_OK if everything went well; #GNUNET_SYSERR if not
- */
-static int
-test_melting (struct TALER_EXCHANGEDB_Session *session)
-{
- struct TALER_EXCHANGEDB_Refresh refresh_session;
- struct TALER_EXCHANGEDB_Melt ret_refresh_session;
- struct DenomKeyPair *dkp;
- struct TALER_DenominationPublicKey *new_denom_pubs;
- int ret;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute now;
-
- ret = GNUNET_SYSERR;
- RND_BLK (&refresh_session);
- dkp = NULL;
- new_dkp = NULL;
- new_denom_pubs = NULL;
- /* create and test a refresh session */
- refresh_session.noreveal_index = MELT_NOREVEAL_INDEX;
- /* create a denomination (value: 1; fraction: 100) */
- now = GNUNET_TIME_absolute_get ();
- GNUNET_TIME_round_abs (&now);
- dkp = create_denom_key_pair (512,
- session,
- now,
- &value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund);
- GNUNET_assert (NULL != dkp);
- /* initialize refresh session melt data */
- {
- struct GNUNET_HashCode hc;
-
- RND_BLK (&refresh_session.coin.coin_pub);
- GNUNET_CRYPTO_hash (&refresh_session.coin.coin_pub,
- sizeof (refresh_session.coin.coin_pub),
- &hc);
- refresh_session.coin.denom_sig.rsa_signature =
- GNUNET_CRYPTO_rsa_sign_fdh (dkp->priv.rsa_private_key,
- &hc);
- GNUNET_assert (NULL != refresh_session.coin.denom_sig.rsa_signature);
- GNUNET_CRYPTO_rsa_public_key_hash (dkp->pub.rsa_public_key,
- &refresh_session.coin.denom_pub_hash);
- refresh_session.amount_with_fee = amount_with_fee;
- }
-
- /* test insert_melt & get_melt */
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->get_melt (plugin->cls,
- session,
- &refresh_session.rc,
- &ret_refresh_session));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->ensure_coin_known (plugin->cls,
- session,
- &refresh_session.coin));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_melt (plugin->cls,
- session,
- &refresh_session));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_melt (plugin->cls,
- session,
- &refresh_session.rc,
- &ret_refresh_session));
- FAILIF (refresh_session.noreveal_index !=
- ret_refresh_session.session.noreveal_index);
- FAILIF (0 !=
- TALER_amount_cmp (&refresh_session.amount_with_fee,
- &ret_refresh_session.session.amount_with_fee));
- FAILIF (0 !=
- TALER_amount_cmp (&fee_refresh,
- &ret_refresh_session.melt_fee));
- FAILIF (0 !=
- GNUNET_memcmp (&refresh_session.rc, &ret_refresh_session.session.rc));
- FAILIF (0 != GNUNET_memcmp (&refresh_session.coin_sig,
- &ret_refresh_session.session.coin_sig));
- FAILIF (NULL !=
- ret_refresh_session.session.coin.denom_sig.rsa_signature);
- FAILIF (0 != memcmp (&refresh_session.coin.coin_pub,
- &ret_refresh_session.session.coin.coin_pub,
- sizeof (refresh_session.coin.coin_pub)));
- FAILIF (0 !=
- GNUNET_memcmp (&refresh_session.coin.denom_pub_hash,
- &ret_refresh_session.session.coin.denom_pub_hash));
-
- /* test 'select_refreshes_above_serial_id' */
- auditor_row_cnt = 0;
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->select_refreshes_above_serial_id (plugin->cls,
- session,
- 0,
- &audit_refresh_session_cb,
- NULL));
- FAILIF (1 != auditor_row_cnt);
-
- new_dkp = GNUNET_new_array (MELT_NEW_COINS,
- struct DenomKeyPair *);
- new_denom_pubs = GNUNET_new_array (MELT_NEW_COINS,
- struct TALER_DenominationPublicKey);
- revealed_coins
- = GNUNET_new_array (MELT_NEW_COINS,
- struct TALER_EXCHANGEDB_RefreshRevealedCoin);
- for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
- {
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
- struct GNUNET_HashCode hc;
- struct GNUNET_TIME_Absolute now;
-
- now = GNUNET_TIME_absolute_get ();
- GNUNET_TIME_round_abs (&now);
- new_dkp[cnt] = create_denom_key_pair (1024,
- session,
- now,
- &value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund);
- GNUNET_assert (NULL != new_dkp[cnt]);
- new_denom_pubs[cnt] = new_dkp[cnt]->pub;
- ccoin = &revealed_coins[cnt];
- ccoin->coin_ev_size = (size_t) GNUNET_CRYPTO_random_u64 (
- GNUNET_CRYPTO_QUALITY_WEAK,
- COIN_ENC_MAX_SIZE);
- ccoin->coin_ev = GNUNET_malloc (ccoin->coin_ev_size);
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
- ccoin->coin_ev,
- ccoin->coin_ev_size);
- RND_BLK (&hc);
- ccoin->denom_pub = new_dkp[cnt]->pub;
- ccoin->coin_sig.rsa_signature
- = GNUNET_CRYPTO_rsa_sign_fdh (new_dkp[cnt]->priv.rsa_private_key,
- &hc);
- }
- RND_BLK (&tprivs);
- RND_BLK (&tpub);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->get_refresh_reveal (plugin->cls,
- session,
- &refresh_session.rc,
- &never_called_cb,
- NULL));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_refresh_reveal (plugin->cls,
- session,
- &refresh_session.rc,
- MELT_NEW_COINS,
- revealed_coins,
- TALER_CNC_KAPPA - 1,
- tprivs,
- &tpub));
- FAILIF (0 >=
- plugin->get_refresh_reveal (plugin->cls,
- session,
- &refresh_session.rc,
- &check_refresh_reveal_cb,
- NULL));
-
-
- qs = plugin->get_link_data (plugin->cls,
- session,
- &refresh_session.coin.coin_pub,
- &handle_link_data_cb,
- NULL);
- FAILIF (0 >= qs);
- {
- /* Just to test fetching a coin with melt history */
- struct TALER_EXCHANGEDB_TransactionList *tl;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = plugin->get_coin_transactions (plugin->cls,
- session,
- &refresh_session.coin.coin_pub,
- GNUNET_YES,
- &tl);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
- plugin->free_coin_transaction_list (plugin->cls,
- tl);
- }
-
-
- ret = GNUNET_OK;
-drop:
- if (NULL != revealed_coins)
- {
- for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
- {
- if (NULL != revealed_coins[cnt].coin_sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (
- revealed_coins[cnt].coin_sig.rsa_signature);
- GNUNET_free (revealed_coins[cnt].coin_ev);
- }
- GNUNET_free (revealed_coins);
- revealed_coins = NULL;
- }
- destroy_denom_key_pair (dkp);
- GNUNET_CRYPTO_rsa_signature_free (
- refresh_session.coin.denom_sig.rsa_signature);
- GNUNET_free_non_null (new_denom_pubs);
- for (unsigned int cnt = 0;
- (NULL != new_dkp) && (cnt < MELT_NEW_COINS) && (NULL != new_dkp[cnt]);
- cnt++)
- destroy_denom_key_pair (new_dkp[cnt]);
- GNUNET_free_non_null (new_dkp);
- return ret;
-}
-
-
-/**
* Callback that should never be called.
*/
static void
cb_wt_never (void *cls,
uint64_t serial_id,
const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_wire,
- const json_t *wire,
- struct GNUNET_TIME_Absolute exec_time,
- const struct GNUNET_HashCode *h_contract_terms,
+ const char *account_payto_uri,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp exec_time,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *coin_value,
const struct TALER_Amount *coin_fee)
{
+ (void) cls;
+ (void) serial_id;
+ (void) merchant_pub;
+ (void) account_payto_uri;
+ (void) h_payto;
+ (void) exec_time;
+ (void) h_contract_terms;
+ (void) denom_pub;
+ (void) coin_pub;
+ (void) coin_value;
+ (void) coin_fee;
GNUNET_assert (0); /* this statement should be unreachable */
}
-/**
- * Callback that should never be called.
- */
-static void
-cb_wtid_never (void *cls,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *coin_contribution,
- const struct TALER_Amount *coin_fee,
- struct GNUNET_TIME_Absolute execution_time)
-{
- GNUNET_assert (0);
-}
-
-
static struct TALER_MerchantPublicKeyP merchant_pub_wt;
-static struct GNUNET_HashCode h_wire_wt;
-static struct GNUNET_HashCode h_contract_terms_wt;
+static struct TALER_MerchantWireHashP h_wire_wt;
+static struct TALER_PrivateContractHashP h_contract_terms_wt;
static struct TALER_CoinSpendPublicKeyP coin_pub_wt;
static struct TALER_Amount coin_value_wt;
static struct TALER_Amount coin_fee_wt;
static struct TALER_Amount transfer_value_wt;
-static struct GNUNET_TIME_Absolute wire_out_date;
+static struct GNUNET_TIME_Timestamp wire_out_date;
static struct TALER_WireTransferIdentifierRawP wire_out_wtid;
@@ -768,24 +566,26 @@ static void
cb_wt_check (void *cls,
uint64_t rowid,
const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_wire,
- const json_t *wire,
- struct GNUNET_TIME_Absolute exec_time,
- const struct GNUNET_HashCode *h_contract_terms,
+ const char *account_payto_uri,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp exec_time,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *coin_value,
const struct TALER_Amount *coin_fee)
{
+ (void) rowid;
+ (void) denom_pub;
+ (void) h_payto;
GNUNET_assert (cls == &cb_wt_never);
GNUNET_assert (0 == GNUNET_memcmp (merchant_pub,
&merchant_pub_wt));
- GNUNET_assert (0 == strcmp (json_string_value (json_object_get (wire,
- "payto_uri")),
- "payto://sepa/DE67830654080004822650"));
- GNUNET_assert (0 == GNUNET_memcmp (h_wire,
- &h_wire_wt));
- GNUNET_assert (exec_time.abs_value_us == wire_out_date.abs_value_us);
+ GNUNET_assert (0 == strcmp (account_payto_uri,
+ "payto://iban/DE67830654080004822650?receiver-name=Test"));
+ GNUNET_assert (GNUNET_TIME_timestamp_cmp (exec_time,
+ ==,
+ wire_out_date));
GNUNET_assert (0 == GNUNET_memcmp (h_contract_terms,
&h_contract_terms_wt));
GNUNET_assert (0 == GNUNET_memcmp (coin_pub,
@@ -798,128 +598,36 @@ cb_wt_check (void *cls,
/**
- * Callback that should be called with the WT data.
+ * Here we store the hash of the payto URI.
*/
-static void
-cb_wtid_check (void *cls,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *coin_contribution,
- const struct TALER_Amount *coin_fee,
- struct GNUNET_TIME_Absolute execution_time)
-{
- GNUNET_assert (cls == &cb_wtid_never);
- GNUNET_assert (0 == GNUNET_memcmp (wtid,
- &wire_out_wtid));
- GNUNET_assert (execution_time.abs_value_us ==
- wire_out_date.abs_value_us);
- GNUNET_assert (0 == TALER_amount_cmp (coin_contribution,
- &coin_value_wt));
- GNUNET_assert (0 == TALER_amount_cmp (coin_fee,
- &coin_fee_wt));
-}
-
-
-/**
- * Here #deposit_cb() will store the row ID of the deposit.
- */
-static uint64_t deposit_rowid;
-
-
-/**
- * Function called with details about deposits that
- * have been made. Called in the test on the
- * deposit given in @a cls.
- *
- * @param cls closure a `struct TALER_EXCHANGEDB_Deposit *`
- * @param rowid unique ID for the deposit in our DB, used for marking
- * it as 'tiny' or 'done'
- * @param merchant_pub public key of the merchant
- * @param coin_pub public key of the coin
- * @param amount_with_fee amount that was deposited including fee
- * @param deposit_fee amount the exchange gets to keep as transaction fees
- * @param h_contract_terms hash of the proposal data known to merchant and customer
- * @param wire_deadline by which the merchant advised that he would like the
- * wire transfer to be executed
- * @param wire wire details for the merchant, NULL from iterate_matching_deposits()
- * @return transaction status code, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT to continue to iterate
- */
-static enum GNUNET_DB_QueryStatus
-deposit_cb (void *cls,
- uint64_t rowid,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct GNUNET_HashCode *h_contract_terms,
- struct GNUNET_TIME_Absolute wire_deadline,
- const json_t *wire)
-{
- struct TALER_EXCHANGEDB_Deposit *deposit = cls;
- struct GNUNET_HashCode h_wire;
-
- deposit_rowid = rowid;
- if (NULL != wire)
- GNUNET_assert (GNUNET_OK ==
- TALER_JSON_merchant_wire_signature_hash (wire,
- &h_wire));
- if ( (0 != GNUNET_memcmp (merchant_pub,
- &deposit->merchant_pub)) ||
- (0 != TALER_amount_cmp (amount_with_fee,
- &deposit->amount_with_fee)) ||
- (0 != TALER_amount_cmp (deposit_fee,
- &deposit->deposit_fee)) ||
- (0 != GNUNET_memcmp (h_contract_terms,
- &deposit->h_contract_terms)) ||
- (0 != memcmp (coin_pub,
- &deposit->coin.coin_pub,
- sizeof (struct TALER_CoinSpendPublicKeyP))) ||
- ( (NULL != wire) &&
- (0 != GNUNET_memcmp (&h_wire,
- &deposit->h_wire)) ) )
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
+static struct TALER_PaytoHashP wire_target_h_payto;
/**
- * Callback for #select_deposits_above_serial_id ()
+ * Callback for #select_coin_deposits_above_serial_id ()
*
* @param cls closure
* @param rowid unique serial ID for the deposit in our DB
- * @param timestamp when did the deposit happen
- * @param merchant_pub public key of the merchant
+ * @param exchange_timestamp when did the deposit happen
+ * @param deposit deposit details
* @param denom_pub denomination of the @a coin_pub
- * @param coin_pub public key of the coin
- * @param coin_sig signature from the coin
- * @param amount_with_fee amount that was deposited including fee
- * @param h_contract_terms hash of the proposal data known to merchant and customer
- * @param refund_deadline by which the merchant advised that he might want
- * to get a refund
- * @param wire_deadline by which the merchant advised that he would like the
- * wire transfer to be executed
- * @param receiver_wire_account wire details for the merchant, NULL from iterate_matching_deposits()
* @param done flag set if the deposit was already executed (or not)
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
audit_deposit_cb (void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute timestamp,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ const struct TALER_EXCHANGEDB_Deposit *deposit,
const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_Amount *amount_with_fee,
- const struct GNUNET_HashCode *h_contract_terms,
- struct GNUNET_TIME_Absolute refund_deadline,
- struct GNUNET_TIME_Absolute wire_deadline,
- const json_t *receiver_wire_account,
- int done)
+ bool done)
{
+ (void) cls;
+ (void) rowid;
+ (void) exchange_timestamp;
+ (void) deposit;
+ (void) denom_pub;
+ (void) done;
auditor_row_cnt++;
return GNUNET_OK;
}
@@ -938,18 +646,20 @@ audit_deposit_cb (void *cls,
* @param h_contract_terms hash of the proposal data in
* the contract between merchant and customer
* @param rtransaction_id refund transaction ID chosen by the merchant
+ * @param full_refund the deposit
* @param amount_with_fee amount that was deposited including fee
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
audit_refund_cb (void *cls,
uint64_t rowid,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantSignatureP *merchant_sig,
- const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
uint64_t rtransaction_id,
+ bool full_refund,
const struct TALER_Amount *amount_with_fee)
{
(void) cls;
@@ -961,6 +671,7 @@ audit_refund_cb (void *cls,
(void) h_contract_terms;
(void) rtransaction_id;
(void) amount_with_fee;
+ (void) full_refund;
auditor_row_cnt++;
return GNUNET_OK;
}
@@ -978,14 +689,14 @@ audit_refund_cb (void *cls,
* @param execution_date when did we receive the funds
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
audit_reserve_in_cb (void *cls,
uint64_t rowid,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_Amount *credit,
const char *sender_account_details,
uint64_t wire_reference,
- struct GNUNET_TIME_Absolute execution_date)
+ struct GNUNET_TIME_Timestamp execution_date)
{
(void) cls;
(void) rowid;
@@ -1012,14 +723,14 @@ audit_reserve_in_cb (void *cls,
* @param amount_with_fee amount that was withdrawn
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
audit_reserve_out_cb (void *cls,
uint64_t rowid,
- const struct GNUNET_HashCode *h_blind_ev,
+ const struct TALER_BlindedCoinHashP *h_blind_ev,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_ReserveSignatureP *reserve_sig,
- struct GNUNET_TIME_Absolute execution_date,
+ struct GNUNET_TIME_Timestamp execution_date,
const struct TALER_Amount *amount_with_fee)
{
(void) cls;
@@ -1038,32 +749,27 @@ audit_reserve_out_cb (void *cls,
/**
* Test garbage collection.
*
- * @param session DB session to use
* @return #GNUNET_OK on success
*/
-static int
-test_gc (struct TALER_EXCHANGEDB_Session *session)
+static enum GNUNET_GenericReturnValue
+test_gc (void)
{
struct DenomKeyPair *dkp;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Absolute past;
- struct TALER_EXCHANGEDB_DenominationKeyInformationP issue2;
- struct GNUNET_HashCode denom_hash;
-
- now = GNUNET_TIME_absolute_get ();
- GNUNET_TIME_round_abs (&now);
- past = GNUNET_TIME_absolute_subtract (now,
- GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_HOURS,
- 4));
- dkp = create_denom_key_pair (1024,
- session,
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_TIME_Timestamp past;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue2;
+ struct TALER_DenominationHashP denom_hash;
+
+ now = GNUNET_TIME_timestamp_get ();
+ past = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_subtract (now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS,
+ 4)));
+ dkp = create_denom_key_pair (RSA_KEY_SIZE,
past,
&value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund);
+ &fees);
GNUNET_assert (NULL != dkp);
if (GNUNET_OK !=
plugin->gc (plugin->cls))
@@ -1072,12 +778,11 @@ test_gc (struct TALER_EXCHANGEDB_Session *session)
destroy_denom_key_pair (dkp);
return GNUNET_SYSERR;
}
- GNUNET_CRYPTO_rsa_public_key_hash (dkp->pub.rsa_public_key,
- &denom_hash);
+ TALER_denom_pub_hash (&dkp->pub,
+ &denom_hash);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->get_denomination_info (plugin->cls,
- session,
&denom_hash,
&issue2))
{
@@ -1093,44 +798,37 @@ test_gc (struct TALER_EXCHANGEDB_Session *session)
/**
* Test wire fee storage.
*
- * @param session DB session to use
* @return #GNUNET_OK on success
*/
-static int
-test_wire_fees (struct TALER_EXCHANGEDB_Session *session)
+static enum GNUNET_GenericReturnValue
+test_wire_fees (void)
{
- struct GNUNET_TIME_Absolute start_date;
- struct GNUNET_TIME_Absolute end_date;
- struct TALER_Amount wire_fee;
- struct TALER_Amount closing_fee;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct TALER_WireFeeSet fees;
struct TALER_MasterSignatureP master_sig;
- struct GNUNET_TIME_Absolute sd;
- struct GNUNET_TIME_Absolute ed;
- struct TALER_Amount fee;
- struct TALER_Amount fee2;
+ struct GNUNET_TIME_Timestamp sd;
+ struct GNUNET_TIME_Timestamp ed;
+ struct TALER_WireFeeSet fees2;
struct TALER_MasterSignatureP ms;
- start_date = GNUNET_TIME_absolute_get ();
- GNUNET_TIME_round_abs (&start_date);
- end_date = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
- GNUNET_TIME_round_abs (&end_date);
+ start_date = GNUNET_TIME_timestamp_get ();
+ end_date = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MINUTES);
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":1.424242",
- &wire_fee));
+ &fees.wire));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":2.424242",
- &closing_fee));
+ &fees.closing));
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
&master_sig,
sizeof (master_sig));
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_wire_fee (plugin->cls,
- session,
"wire-method",
start_date,
end_date,
- &wire_fee,
- &closing_fee,
+ &fees,
&master_sig))
{
GNUNET_break (0);
@@ -1138,12 +836,10 @@ test_wire_fees (struct TALER_EXCHANGEDB_Session *session)
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->insert_wire_fee (plugin->cls,
- session,
"wire-method",
start_date,
end_date,
- &wire_fee,
- &closing_fee,
+ &fees,
&master_sig))
{
GNUNET_break (0);
@@ -1153,13 +849,11 @@ test_wire_fees (struct TALER_EXCHANGEDB_Session *session)
half-open interval [start_date,end_date) */
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->get_wire_fee (plugin->cls,
- session,
"wire-method",
end_date,
&sd,
&ed,
- &fee,
- &fee2,
+ &fees2,
&ms))
{
GNUNET_break (0);
@@ -1167,24 +861,24 @@ test_wire_fees (struct TALER_EXCHANGEDB_Session *session)
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->get_wire_fee (plugin->cls,
- session,
"wire-method",
start_date,
&sd,
&ed,
- &fee,
- &fee2,
+ &fees2,
&ms))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
- if ( (sd.abs_value_us != start_date.abs_value_us) ||
- (ed.abs_value_us != end_date.abs_value_us) ||
- (0 != TALER_amount_cmp (&fee,
- &wire_fee)) ||
- (0 != TALER_amount_cmp (&fee2,
- &closing_fee)) ||
+ if ( (GNUNET_TIME_timestamp_cmp (sd,
+ !=,
+ start_date)) ||
+ (GNUNET_TIME_timestamp_cmp (ed,
+ !=,
+ end_date)) ||
+ (0 != TALER_wire_fee_set_cmp (&fees,
+ &fees2)) ||
(0 != GNUNET_memcmp (&ms,
&master_sig)) )
{
@@ -1209,14 +903,17 @@ static struct TALER_Amount wire_out_amount;
* @param amount amount that was wired
* @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration
*/
-static int
+static enum GNUNET_GenericReturnValue
audit_wire_cb (void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute date,
+ struct GNUNET_TIME_Timestamp date,
const struct TALER_WireTransferIdentifierRawP *wtid,
- const json_t *wire,
+ const char *payto_uri,
const struct TALER_Amount *amount)
{
+ (void) cls;
+ (void) rowid;
+ (void) payto_uri;
auditor_row_cnt++;
GNUNET_assert (0 ==
TALER_amount_cmp (amount,
@@ -1224,7 +921,9 @@ audit_wire_cb (void *cls,
GNUNET_assert (0 ==
GNUNET_memcmp (wtid,
&wire_out_wtid));
- GNUNET_assert (date.abs_value_us == wire_out_date.abs_value_us);
+ GNUNET_assert (GNUNET_TIME_timestamp_cmp (date,
+ ==,
+ wire_out_date));
return GNUNET_OK;
}
@@ -1232,19 +931,23 @@ audit_wire_cb (void *cls,
/**
* Test API relating to wire_out handling.
*
- * @param session database session to use for the test
+ * @param bd batch deposit to test
* @return #GNUNET_OK on success
*/
-static int
-test_wire_out (struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_EXCHANGEDB_Deposit *deposit)
+static enum GNUNET_GenericReturnValue
+test_wire_out (const struct TALER_EXCHANGEDB_BatchDeposit *bd)
{
+ const struct TALER_EXCHANGEDB_CoinDepositInformation *deposit = &bd->cdis[0];
+ struct TALER_PaytoHashP h_payto;
+
+ GNUNET_assert (0 < bd->num_cdis);
+ TALER_payto_hash (bd->receiver_wire_account,
+ &h_payto);
auditor_row_cnt = 0;
memset (&wire_out_wtid,
- 42,
+ 41,
sizeof (wire_out_wtid));
- wire_out_date = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&wire_out_date);
+ wire_out_date = GNUNET_TIME_timestamp_get ();
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":1",
&wire_out_amount));
@@ -1253,95 +956,109 @@ test_wire_out (struct TALER_EXCHANGEDB_Session *session,
the aggregation table, so we need to start the special
transaction where this is allowed... */
FAILIF (GNUNET_OK !=
- plugin->start_deferred_wire_out (plugin->cls,
- session));
+ plugin->start_deferred_wire_out (plugin->cls));
/* setup values for wire transfer aggregation data */
- merchant_pub_wt = deposit->merchant_pub;
- h_wire_wt = deposit->h_wire;
- h_contract_terms_wt = deposit->h_contract_terms;
+ merchant_pub_wt = bd->merchant_pub;
+ h_contract_terms_wt = bd->h_contract_terms;
coin_pub_wt = deposit->coin.coin_pub;
coin_value_wt = deposit->amount_with_fee;
- coin_fee_wt = fee_deposit;
- GNUNET_assert (GNUNET_OK ==
+ coin_fee_wt = fees.deposit;
+ GNUNET_assert (0 <
TALER_amount_subtract (&transfer_value_wt,
&coin_value_wt,
&coin_fee_wt));
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_wire_transfer (plugin->cls,
- session,
&wire_out_wtid,
&cb_wt_never,
NULL));
{
- struct GNUNET_HashCode h_contract_terms_wt2 = h_contract_terms_wt;
-
- h_contract_terms_wt2.bits[0]++;
+ struct TALER_PrivateContractHashP h_contract_terms_wt2 =
+ h_contract_terms_wt;
+ bool pending;
+ struct TALER_WireTransferIdentifierRawP wtid2;
+ struct TALER_Amount coin_contribution2;
+ struct TALER_Amount coin_fee2;
+ struct GNUNET_TIME_Timestamp execution_time2;
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+ enum TALER_AmlDecisionState aml;
+
+ h_contract_terms_wt2.hash.bits[0]++;
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_transfer_by_deposit (plugin->cls,
- session,
&h_contract_terms_wt2,
&h_wire_wt,
&coin_pub_wt,
&merchant_pub_wt,
- &cb_wtid_never,
- NULL));
+ &pending,
+ &wtid2,
+ &execution_time2,
+ &coin_contribution2,
+ &coin_fee2,
+ &kyc,
+ &aml));
}
- /* insert WT data */
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_aggregation_tracking (plugin->cls,
- session,
- &wire_out_wtid,
- deposit_rowid));
-
- /* Now let's fix the transient constraint violation by
- putting in the WTID into the wire_out table */
{
- json_t *wire_out_account;
-
- wire_out_account = json_pack ("{s:s,s:s}",
- "payto_uri",
- "payto://x-taler-bank/localhost:8080/1",
- "salt", "this-is-my-salt");
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->store_wire_transfer_out (plugin->cls,
- session,
- wire_out_date,
- &wire_out_wtid,
- wire_out_account,
- "my-config-section",
- &wire_out_amount))
- {
- json_decref (wire_out_account);
- FAILIF (1);
- }
- json_decref (wire_out_account);
+ struct TALER_ReservePublicKeyP rpub;
+
+ memset (&rpub,
+ 44,
+ sizeof (rpub));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->store_wire_transfer_out (plugin->cls,
+ wire_out_date,
+ &wire_out_wtid,
+ &h_payto,
+ "my-config-section",
+ &wire_out_amount));
}
/* And now the commit should still succeed! */
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->commit (plugin->cls,
- session));
+ plugin->commit (plugin->cls));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->lookup_wire_transfer (plugin->cls,
- session,
&wire_out_wtid,
&cb_wt_check,
&cb_wt_never));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->lookup_transfer_by_deposit (plugin->cls,
- session,
- &h_contract_terms_wt,
- &h_wire_wt,
- &coin_pub_wt,
- &merchant_pub_wt,
- &cb_wtid_check,
- &cb_wtid_never));
+ {
+ bool pending;
+ struct TALER_WireTransferIdentifierRawP wtid2;
+ struct TALER_Amount coin_contribution2;
+ struct TALER_Amount coin_fee2;
+ struct GNUNET_TIME_Timestamp execution_time2;
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+ enum TALER_AmlDecisionState aml = TALER_AML_FROZEN;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->lookup_transfer_by_deposit (plugin->cls,
+ &h_contract_terms_wt,
+ &h_wire_wt,
+ &coin_pub_wt,
+ &merchant_pub_wt,
+ &pending,
+ &wtid2,
+ &execution_time2,
+ &coin_contribution2,
+ &coin_fee2,
+ &kyc,
+ &aml));
+ FAILIF (TALER_AML_NORMAL != aml);
+ GNUNET_assert (0 == GNUNET_memcmp (&wtid2,
+ &wire_out_wtid));
+ GNUNET_assert (GNUNET_TIME_timestamp_cmp (execution_time2,
+ ==,
+ wire_out_date));
+ GNUNET_assert (0 == TALER_amount_cmp (&coin_contribution2,
+ &coin_value_wt));
+ GNUNET_assert (0 == TALER_amount_cmp (&coin_fee2,
+ &coin_fee_wt));
+ }
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->select_wire_out_above_serial_id (plugin->cls,
- session,
0,
&audit_wire_cb,
NULL));
@@ -1367,19 +1084,26 @@ drop:
* @param coin_blind blinding factor used to blind the coin
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
recoup_cb (void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute timestamp,
+ struct GNUNET_TIME_Timestamp timestamp,
const struct TALER_Amount *amount,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind)
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_blind)
{
- const struct TALER_DenominationBlindingKeyP *cb = cls;
+ const union GNUNET_CRYPTO_BlindingSecretP *cb = cls;
+ (void) rowid;
+ (void) timestamp;
+ (void) amount;
+ (void) reserve_pub;
+ (void) coin_sig;
+ (void) coin;
+ (void) denom_pub;
FAILIF (NULL == cb);
FAILIF (0 != GNUNET_memcmp (cb,
coin_blind));
@@ -1390,68 +1114,32 @@ drop:
/**
- * Function called on deposits that are past their due date
- * and have not yet seen a wire transfer.
+ * Function called on batch deposits that may require a
+ * wire transfer.
*
* @param cls closure a `struct TALER_EXCHANGEDB_Deposit *`
- * @param rowid deposit table row of the coin's deposit
- * @param coin_pub public key of the coin
- * @param amount value of the deposit, including fee
- * @param wire where should the funds be wired
- * @param deadline what was the requested wire transfer deadline
- * @param tiny did the exchange defer this transfer because it is too small?
- * @param done did the exchange claim that it made a transfer?
+ * @param batch_deposit_serial_id where in the table are we
+ * @param total_amount value of all missing deposits, including fees
+ * @param wire_target_h_payto hash of the recipient account's payto URI
+ * @param deadline what was the earliest requested wire transfer deadline
*/
static void
-wire_missing_cb (void *cls,
- uint64_t rowid,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount,
- const json_t *wire,
- struct GNUNET_TIME_Absolute deadline,
- /* bool? */ int tiny,
- /* bool? */ int done)
+wire_missing_cb (
+ void *cls,
+ uint64_t batch_deposit_serial_id,
+ const struct TALER_Amount *total_amount,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ struct GNUNET_TIME_Timestamp deadline)
{
- const struct TALER_EXCHANGEDB_Deposit *deposit = cls;
- struct GNUNET_HashCode h_wire;
-
- (void) done;
- if (NULL != wire)
- GNUNET_assert (GNUNET_OK ==
- TALER_JSON_merchant_wire_signature_hash (wire,
- &h_wire));
- else
- memset (&h_wire,
- 0,
- sizeof (h_wire));
- if (GNUNET_NO != tiny)
- {
- GNUNET_break (0);
- result = 66;
- }
- if (GNUNET_NO != done)
- {
- GNUNET_break (0);
- result = 66;
- }
- if (0 != TALER_amount_cmp (amount,
- &deposit->amount_with_fee))
- {
- GNUNET_break (0);
- result = 66;
- }
- if (0 != GNUNET_memcmp (coin_pub,
- &deposit->coin.coin_pub))
- {
- GNUNET_break (0);
- result = 66;
- }
- if (0 != GNUNET_memcmp (&h_wire,
- &deposit->h_wire))
- {
- GNUNET_break (0);
- result = 66;
- }
+ const struct TALER_EXCHANGEDB_CoinDepositInformation *deposit = cls;
+
+ (void) batch_deposit_serial_id;
+ (void) deadline;
+ (void) wire_target_h_payto;
+ if (0 ==
+ TALER_amount_cmp (total_amount,
+ &deposit->amount_with_fee))
+ result = 8;
}
@@ -1463,7 +1151,7 @@ wire_missing_cb (void *cls,
* @param amount_with_fee amount being refunded
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
check_refund_cb (void *cls,
const struct TALER_Amount *amount_with_fee)
{
@@ -1488,41 +1176,62 @@ static void
run (void *cls)
{
struct GNUNET_CONFIGURATION_Handle *cfg = cls;
- struct TALER_EXCHANGEDB_Session *session;
struct TALER_CoinSpendSignatureP coin_sig;
- struct GNUNET_TIME_Absolute deadline;
- struct TALER_DenominationBlindingKeyP coin_blind;
+ struct GNUNET_TIME_Timestamp deadline;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
struct TALER_ReservePublicKeyP reserve_pub;
struct TALER_ReservePublicKeyP reserve_pub2;
- struct DenomKeyPair *dkp;
- struct GNUNET_HashCode dkp_pub_hash;
+ struct TALER_ReservePublicKeyP reserve_pub3;
+ struct DenomKeyPair *dkp = NULL;
struct TALER_MasterSignatureP master_sig;
struct TALER_EXCHANGEDB_CollectableBlindcoin cbc;
struct TALER_EXCHANGEDB_CollectableBlindcoin cbc2;
- struct TALER_EXCHANGEDB_ReserveHistory *rh;
+ struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL;
struct TALER_EXCHANGEDB_ReserveHistory *rh_head;
struct TALER_EXCHANGEDB_BankTransfer *bt;
struct TALER_EXCHANGEDB_CollectableBlindcoin *withdraw;
- struct TALER_EXCHANGEDB_Deposit deposit;
- struct TALER_EXCHANGEDB_Deposit deposit2;
+ struct TALER_EXCHANGEDB_CoinDepositInformation deposit;
+ struct TALER_EXCHANGEDB_BatchDeposit bd;
+ struct TALER_CoinSpendPublicKeyP cpub2;
+ struct TALER_MerchantPublicKeyP mpub2;
struct TALER_EXCHANGEDB_Refund refund;
struct TALER_EXCHANGEDB_TransactionList *tl;
struct TALER_EXCHANGEDB_TransactionList *tlp;
- json_t *wire;
const char *sndr = "payto://x-taler-bank/localhost:8080/1";
+ const char *rcvr = "payto://x-taler-bank/localhost:8080/2";
+ const uint32_t num_partitions = 10;
unsigned int matched;
unsigned int cnt;
- uint64_t rr;
enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Timestamp now;
+ struct TALER_WireSaltP salt;
+ struct TALER_CoinPubHashP c_hash;
+ uint64_t known_coin_id;
+ uint64_t rrc_serial;
+ struct TALER_EXCHANGEDB_Refresh refresh;
+ struct TALER_DenominationPublicKey *new_denom_pubs = NULL;
+ uint64_t reserve_out_serial_id;
+ uint64_t melt_serial_id;
+ struct TALER_PlanchetMasterSecretP ps;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ const struct TALER_ExchangeWithdrawValues *alg_values
+ = TALER_denom_ewv_rsa_singleton ();
- dkp = NULL;
- rh = NULL;
- session = NULL;
- deposit.coin.denom_sig.rsa_signature = NULL;
- wire = json_pack ("{s:s, s:s}",
- "payto_uri", "payto://sepa/DE67830654080004822650",
- "salt", "this-is-a-salt-value");
+ memset (&deposit,
+ 0,
+ sizeof (deposit));
+ memset (&bd,
+ 0,
+ sizeof (bd));
+ bd.receiver_wire_account = (char *) rcvr;
+ bd.cdis = &deposit;
+ bd.num_cdis = 1;
+ memset (&salt,
+ 45,
+ sizeof (salt));
+ memset (&refresh,
+ 0,
+ sizeof (refresh));
ZR_BLK (&cbc);
ZR_BLK (&cbc2);
if (NULL ==
@@ -1533,202 +1242,591 @@ run (void *cls)
}
(void) plugin->drop_tables (plugin->cls);
if (GNUNET_OK !=
- plugin->create_tables (plugin->cls))
- {
- result = 77;
- goto drop;
- }
- if (NULL ==
- (session = plugin->get_session (plugin->cls)))
+ plugin->create_tables (plugin->cls,
+ true,
+ num_partitions))
{
result = 77;
- goto drop;
+ goto cleanup;
}
-
+ plugin->preflight (plugin->cls);
FAILIF (GNUNET_OK !=
plugin->start (plugin->cls,
- session,
"test-1"));
/* test DB is empty */
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->select_recoup_above_serial_id (plugin->cls,
- session,
0,
&recoup_cb,
NULL));
+ /* simple extension check */
+ FAILIF (GNUNET_OK !=
+ test_extension_manifest ());
+
RND_BLK (&reserve_pub);
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":1.000010",
&value));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":0.000010",
- &fee_withdraw));
+ &fees.withdraw));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":0.000010",
- &fee_deposit));
+ &fees.deposit));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":0.000010",
- &fee_refresh));
+ &fees.refresh));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":0.000010",
- &fee_refund));
+ &fees.refund));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":1.000010",
&amount_with_fee));
-
result = 4;
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->get_latest_reserve_in_reference (plugin->cls,
- session,
- "exchange-account-1",
- &rr));
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->reserves_in_insert (plugin->cls,
- session,
- &reserve_pub,
- &value,
- now,
- sndr,
- "exchange-account-1",
- 4));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_latest_reserve_in_reference (plugin->cls,
- session,
- "exchange-account-1",
- &rr));
- FAILIF (4 != rr);
+ plugin->commit (plugin->cls));
+ now = GNUNET_TIME_timestamp_get ();
+ {
+ struct TALER_EXCHANGEDB_ReserveInInfo reserve = {
+ .reserve_pub = &reserve_pub,
+ .balance = &value,
+ .execution_time = now,
+ .sender_account_details = sndr,
+ .exchange_account_name = "exchange-account-1",
+ .wire_reference = 4
+ };
+ enum GNUNET_DB_QueryStatus qsr;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->reserves_in_insert (plugin->cls,
+ &reserve,
+ 1,
+ &qsr));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ qsr);
+ }
FAILIF (GNUNET_OK !=
- check_reserve (session,
- &reserve_pub,
+ check_reserve (&reserve_pub,
value.value,
value.fraction,
value.currency));
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->reserves_in_insert (plugin->cls,
- session,
- &reserve_pub,
- &value,
- now,
- sndr,
- "exchange-account-1",
- 5));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_latest_reserve_in_reference (plugin->cls,
- session,
- "exchange-account-1",
- &rr));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_latest_reserve_in_reference (plugin->cls,
- session,
- "exchange-account-1",
- &rr));
- FAILIF (5 != rr);
+ now = GNUNET_TIME_timestamp_get ();
+ RND_BLK (&reserve_pub2);
+ {
+ struct TALER_EXCHANGEDB_ReserveInInfo reserve = {
+ .reserve_pub = &reserve_pub2,
+ .balance = &value,
+ .execution_time = now,
+ .sender_account_details = sndr,
+ .exchange_account_name = "exchange-account-1",
+ .wire_reference = 5
+ };
+ enum GNUNET_DB_QueryStatus qsr;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->reserves_in_insert (plugin->cls,
+ &reserve,
+ 1,
+ &qsr));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ qsr);
+ }
FAILIF (GNUNET_OK !=
- check_reserve (session,
- &reserve_pub,
- value.value * 2,
- value.fraction * 2,
+ plugin->start (plugin->cls,
+ "test-2"));
+ FAILIF (GNUNET_OK !=
+ check_reserve (&reserve_pub,
+ value.value,
+ value.fraction,
+ value.currency));
+ FAILIF (GNUNET_OK !=
+ check_reserve (&reserve_pub2,
+ value.value,
+ value.fraction,
value.currency));
result = 5;
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
- dkp = create_denom_key_pair (1024,
- session,
+ now = GNUNET_TIME_timestamp_get ();
+ dkp = create_denom_key_pair (RSA_KEY_SIZE,
now,
&value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund);
+ &fees);
GNUNET_assert (NULL != dkp);
- GNUNET_CRYPTO_rsa_public_key_hash (dkp->pub.rsa_public_key,
- &dkp_pub_hash);
- RND_BLK (&cbc.h_coin_envelope);
+ TALER_denom_pub_hash (&dkp->pub,
+ &cbc.denom_pub_hash);
RND_BLK (&cbc.reserve_sig);
- cbc.denom_pub_hash = dkp_pub_hash;
- cbc.sig.rsa_signature
- = GNUNET_CRYPTO_rsa_sign_fdh (dkp->priv.rsa_private_key,
- &cbc.h_coin_envelope);
+ RND_BLK (&ps);
+ TALER_planchet_blinding_secret_create (&ps,
+ alg_values,
+ &bks);
+ {
+ struct TALER_PlanchetDetail pd;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_AgeCommitmentHash age_hash;
+ struct TALER_AgeCommitmentHash *p_ah[2] = {
+ NULL,
+ &age_hash
+ };
+
+ /* Call TALER_denom_blind()/TALER_denom_sign_blinded() twice, once without
+ * age_hash, once with age_hash */
+ RND_BLK (&age_hash);
+ for (size_t i = 0; i < sizeof(p_ah) / sizeof(p_ah[0]); i++)
+ {
+
+ RND_BLK (&coin_pub);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_blind (&dkp->pub,
+ &bks,
+ NULL,
+ p_ah[i],
+ &coin_pub,
+ alg_values,
+ &c_hash,
+ &pd.blinded_planchet));
+ TALER_coin_ev_hash (&pd.blinded_planchet,
+ &cbc.denom_pub_hash,
+ &cbc.h_coin_envelope);
+ if (i != 0)
+ TALER_blinded_denom_sig_free (&cbc.sig);
+ GNUNET_assert (
+ GNUNET_OK ==
+ TALER_denom_sign_blinded (
+ &cbc.sig,
+ &dkp->priv,
+ false,
+ &pd.blinded_planchet));
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
+ }
+ }
+
cbc.reserve_pub = reserve_pub;
cbc.amount_with_fee = value;
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (CURRENCY, &cbc.withdraw_fee));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_withdraw_info (plugin->cls,
- session,
- &cbc));
+ TALER_amount_set_zero (CURRENCY,
+ &cbc.withdraw_fee));
+
+ {
+ bool found;
+ bool nonce_reuse;
+ bool balance_ok;
+ bool age_ok;
+ bool conflict;
+ bool denom_unknown;
+ uint16_t maximum_age;
+ uint64_t ruuid;
+ struct TALER_Amount reserve_balance;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_batch_withdraw (plugin->cls,
+ now,
+ &reserve_pub,
+ &value,
+ true,
+ &found,
+ &balance_ok,
+ &reserve_balance,
+ &age_ok,
+ &maximum_age,
+ &ruuid));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_batch_withdraw_insert (plugin->cls,
+ NULL,
+ &cbc,
+ now,
+ ruuid,
+ &denom_unknown,
+ &conflict,
+ &nonce_reuse));
+ GNUNET_assert (found);
+ GNUNET_assert (! nonce_reuse);
+ GNUNET_assert (! denom_unknown);
+ GNUNET_assert (balance_ok);
+ }
+
+
FAILIF (GNUNET_OK !=
- check_reserve (session,
- &reserve_pub,
+ check_reserve (&reserve_pub,
+ 0,
+ 0,
+ value.currency));
+ FAILIF (GNUNET_OK !=
+ check_reserve (&reserve_pub2,
value.value,
value.fraction,
value.currency));
-
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->get_reserve_by_h_blind (plugin->cls,
- session,
&cbc.h_coin_envelope,
- &reserve_pub2));
+ &reserve_pub3,
+ &reserve_out_serial_id));
FAILIF (0 != GNUNET_memcmp (&reserve_pub,
- &reserve_pub2));
+ &reserve_pub3));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->get_withdraw_info (plugin->cls,
- session,
&cbc.h_coin_envelope,
&cbc2));
- FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_sig, &cbc.reserve_sig));
- FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_pub, &cbc.reserve_pub));
+ FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_sig,
+ &cbc.reserve_sig));
+ FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_pub,
+ &cbc.reserve_pub));
result = 6;
- FAILIF (GNUNET_OK !=
- GNUNET_CRYPTO_rsa_verify (&cbc.h_coin_envelope,
- cbc2.sig.rsa_signature,
- dkp->pub.rsa_public_key));
+ {
+ struct TALER_DenominationSignature ds;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&ds,
+ &cbc2.sig,
+ &bks,
+ &c_hash,
+ alg_values,
+ &dkp->pub));
+ FAILIF (GNUNET_OK !=
+ TALER_denom_pub_verify (&dkp->pub,
+ &ds,
+ &c_hash));
+ TALER_denom_sig_free (&ds);
+ }
RND_BLK (&coin_sig);
RND_BLK (&coin_blind);
RND_BLK (&deposit.coin.coin_pub);
- GNUNET_CRYPTO_rsa_public_key_hash (dkp->pub.rsa_public_key,
- &deposit.coin.denom_pub_hash);
- deposit.coin.denom_sig = cbc.sig;
- deadline = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&deadline);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->ensure_coin_known (plugin->cls,
- session,
- &deposit.coin));
+ TALER_denom_pub_hash (&dkp->pub,
+ &deposit.coin.denom_pub_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&deposit.coin.denom_sig,
+ &cbc.sig,
+ &bks,
+ &c_hash,
+ alg_values,
+ &dkp->pub));
+ deadline = GNUNET_TIME_timestamp_get ();
+ {
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+
+ FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+ plugin->ensure_coin_known (plugin->cls,
+ &deposit.coin,
+ &known_coin_id,
+ &dph,
+ &agh));
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+ {
+ struct GNUNET_TIME_Timestamp deposit_timestamp
+ = GNUNET_TIME_timestamp_get ();
+ bool balance_ok;
+ uint32_t bad_balance_idx;
+ bool in_conflict;
+ struct TALER_PaytoHashP h_payto;
+
+ RND_BLK (&h_payto);
+ bd.refund_deadline
+ = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MONTHS);
+ bd.wire_deadline
+ = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MONTHS);
+ deposit.amount_with_fee = value;
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_deposit (plugin->cls,
+ &bd,
+ &deposit_timestamp,
+ &balance_ok,
+ &bad_balance_idx,
+ &in_conflict));
+ FAILIF (! balance_ok);
+ FAILIF (in_conflict);
+ }
+
+ {
+ bool not_found;
+ bool refund_ok;
+ bool gone;
+ bool conflict;
+
+ refund.coin = deposit.coin;
+ refund.details.merchant_pub = bd.merchant_pub;
+ RND_BLK (&refund.details.merchant_sig);
+ refund.details.h_contract_terms = bd.h_contract_terms;
+ refund.details.rtransaction_id = 1;
+ refund.details.refund_amount = value;
+ refund.details.refund_fee = fees.refund;
+ RND_BLK (&refund.details.merchant_sig);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_refund (plugin->cls,
+ &refund,
+ &fees.deposit,
+ known_coin_id,
+ &not_found,
+ &refund_ok,
+ &gone,
+ &conflict));
+ FAILIF (not_found);
+ FAILIF (! refund_ok);
+ FAILIF (gone);
+ FAILIF (conflict);
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->select_refunds_by_coin (plugin->cls,
+ &refund.coin.coin_pub,
+ &refund.details.merchant_pub,
+ &refund.details.h_contract_terms,
+ &check_refund_cb,
+ &refund));
+ }
+
+ /* test do_melt */
+ {
+ bool zombie_required = false;
+ bool balance_ok;
+
+ refresh.coin = deposit.coin;
+ RND_BLK (&refresh.coin_sig);
+ RND_BLK (&refresh.rc);
+ refresh.amount_with_fee = value;
+ refresh.noreveal_index = MELT_NOREVEAL_INDEX;
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_melt (plugin->cls,
+ NULL,
+ &refresh,
+ known_coin_id,
+ &zombie_required,
+ &balance_ok));
+ FAILIF (! balance_ok);
+ FAILIF (zombie_required);
+ }
+
+ /* test get_melt */
+ {
+ struct TALER_EXCHANGEDB_Melt ret_refresh_session;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_melt (plugin->cls,
+ &refresh.rc,
+ &ret_refresh_session,
+ &melt_serial_id));
+ FAILIF (refresh.noreveal_index !=
+ ret_refresh_session.session.noreveal_index);
+ FAILIF (0 !=
+ TALER_amount_cmp (&refresh.amount_with_fee,
+ &ret_refresh_session.session.amount_with_fee));
+ FAILIF (0 !=
+ TALER_amount_cmp (&fees.refresh,
+ &ret_refresh_session.melt_fee));
+ FAILIF (0 !=
+ GNUNET_memcmp (&refresh.rc,
+ &ret_refresh_session.session.rc));
+ FAILIF (0 != GNUNET_memcmp (&refresh.coin_sig,
+ &ret_refresh_session.session.coin_sig));
+ FAILIF (0 !=
+ GNUNET_memcmp (&refresh.coin.coin_pub,
+ &ret_refresh_session.session.coin.coin_pub));
+ FAILIF (0 !=
+ GNUNET_memcmp (&refresh.coin.denom_pub_hash,
+ &ret_refresh_session.session.coin.denom_pub_hash));
+ }
+
+ {
+ /* test 'select_refreshes_above_serial_id' */
+ auditor_row_cnt = 0;
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->select_refreshes_above_serial_id (plugin->cls,
+ 0,
+ &audit_refresh_session_cb,
+ NULL));
+ FAILIF (1 != auditor_row_cnt);
+ }
+
+ /* do refresh-reveal */
+ {
+ new_dkp = GNUNET_new_array (MELT_NEW_COINS,
+ struct DenomKeyPair *);
+ new_denom_pubs = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_DenominationPublicKey);
+ revealed_coins
+ = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin);
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_CRYPTO_BlindedMessage *rp;
+ struct GNUNET_CRYPTO_RsaBlindedMessage *rsa;
+ struct TALER_BlindedPlanchet *bp;
+
+ now = GNUNET_TIME_timestamp_get ();
+ new_dkp[cnt] = create_denom_key_pair (RSA_KEY_SIZE,
+ now,
+ &value,
+ &fees);
+ GNUNET_assert (NULL != new_dkp[cnt]);
+ new_denom_pubs[cnt] = new_dkp[cnt]->pub;
+ ccoin = &revealed_coins[cnt];
+ bp = &ccoin->blinded_planchet;
+ rp = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
+ bp->blinded_message = rp;
+ rp->cipher = GNUNET_CRYPTO_BSA_RSA;
+ rp->rc = 1;
+ rsa = &rp->details.rsa_blinded_message;
+ rsa->blinded_msg_size = 1 + (size_t) GNUNET_CRYPTO_random_u64 (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ (RSA_KEY_SIZE / 8) - 1);
+ rsa->blinded_msg = GNUNET_malloc (rsa->blinded_msg_size);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ rsa->blinded_msg,
+ rsa->blinded_msg_size);
+ TALER_denom_pub_hash (&new_dkp[cnt]->pub,
+ &ccoin->h_denom_pub);
+ TALER_denom_ewv_copy (&ccoin->exchange_vals,
+ alg_values);
+ TALER_coin_ev_hash (bp,
+ &ccoin->h_denom_pub,
+ &ccoin->coin_envelope_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sign_blinded (&ccoin->coin_sig,
+ &new_dkp[cnt]->priv,
+ true,
+ bp));
+ }
+ RND_BLK (&tprivs);
+ RND_BLK (&tpub);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->get_refresh_reveal (plugin->cls,
+ &refresh.rc,
+ &never_called_cb,
+ NULL));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_refresh_reveal (plugin->cls,
+ melt_serial_id,
+ MELT_NEW_COINS,
+ revealed_coins,
+ TALER_CNC_KAPPA - 1,
+ tprivs,
+ &tpub));
+ {
+ struct TALER_BlindedCoinHashP h_coin_ev;
+ struct TALER_CoinSpendPublicKeyP ocp;
+ struct TALER_DenominationHashP denom_hash;
+
+ TALER_denom_pub_hash (&new_denom_pubs[0],
+ &denom_hash);
+ TALER_coin_ev_hash (&revealed_coins[0].blinded_planchet,
+ &denom_hash,
+ &h_coin_ev);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_old_coin_by_h_blind (plugin->cls,
+ &h_coin_ev,
+ &ocp,
+ &rrc_serial));
+ FAILIF (0 !=
+ GNUNET_memcmp (&ocp,
+ &refresh.coin.coin_pub));
+ }
+ FAILIF (0 >=
+ plugin->get_refresh_reveal (plugin->cls,
+ &refresh.rc,
+ &check_refresh_reveal_cb,
+ NULL));
+ qs = plugin->get_link_data (plugin->cls,
+ &refresh.coin.coin_pub,
+ &handle_link_data_cb,
+ NULL);
+ FAILIF (0 >= qs);
+ {
+ /* Just to test fetching a coin with melt history */
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t etag;
+ struct TALER_Amount balance;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ qs = plugin->get_coin_transactions (plugin->cls,
+ &refresh.coin.coin_pub,
+ 0,
+ 0,
+ &etag,
+ &balance,
+ &h_denom_pub,
+ &tl);
+ FAILIF (0 >= qs);
+ FAILIF (NULL == tl);
+ plugin->free_coin_transaction_list (plugin->cls,
+ tl);
+ }
+ }
+
+ /* do recoup-refresh */
+ {
+ struct GNUNET_TIME_Timestamp recoup_timestamp
+ = GNUNET_TIME_timestamp_get ();
+ union GNUNET_CRYPTO_BlindingSecretP coin_bks;
+ uint64_t new_known_coin_id;
+ struct TALER_CoinPublicInfo new_coin;
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+ bool recoup_ok;
+ bool internal_failure;
+
+ new_coin = deposit.coin; /* steal basic data */
+ RND_BLK (&new_coin.coin_pub);
+ FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+ plugin->ensure_coin_known (plugin->cls,
+ &new_coin,
+ &new_known_coin_id,
+ &dph,
+ &agh));
+ RND_BLK (&coin_bks);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_recoup_refresh (plugin->cls,
+ &deposit.coin.coin_pub,
+ rrc_serial,
+ &coin_bks,
+ &new_coin.coin_pub,
+ new_known_coin_id,
+ &coin_sig,
+ &recoup_timestamp,
+ &recoup_ok,
+ &internal_failure));
+ FAILIF (! recoup_ok);
+ FAILIF (internal_failure);
+ }
+
+ /* do recoup */
{
struct TALER_EXCHANGEDB_Reserve pre_reserve;
struct TALER_EXCHANGEDB_Reserve post_reserve;
struct TALER_Amount delta;
+ bool recoup_ok;
+ bool internal_failure;
+ struct GNUNET_TIME_Timestamp recoup_timestamp
+ = GNUNET_TIME_timestamp_get ();
pre_reserve.pub = reserve_pub;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->reserves_get (plugin->cls,
- session,
&pre_reserve));
+ FAILIF (! TALER_amount_is_zero (&pre_reserve.balance));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_recoup_request (plugin->cls,
- session,
- &reserve_pub,
- &deposit.coin,
- &coin_sig,
- &coin_blind,
- &value,
- &cbc.h_coin_envelope,
- deadline));
+ plugin->do_recoup (plugin->cls,
+ &reserve_pub,
+ reserve_out_serial_id,
+ &coin_blind,
+ &deposit.coin.coin_pub,
+ known_coin_id,
+ &coin_sig,
+ &recoup_timestamp,
+ &recoup_ok,
+ &internal_failure));
+ FAILIF (internal_failure);
+ FAILIF (! recoup_ok);
post_reserve.pub = reserve_pub;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->reserves_get (plugin->cls,
- session,
&post_reserve));
- FAILIF (GNUNET_OK !=
+ FAILIF (0 >=
TALER_amount_subtract (&delta,
&post_reserve.balance,
&pre_reserve.balance));
@@ -1736,43 +1834,67 @@ run (void *cls)
TALER_amount_cmp (&delta,
&value));
}
+
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-3"));
+
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->select_recoup_above_serial_id (plugin->cls,
- session,
0,
&recoup_cb,
&coin_blind));
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&amount_with_fee,
- &value,
- &value));
+ /* Do reserve close */
+ now = GNUNET_TIME_timestamp_get ();
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":0.000010",
&fee_closing));
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_reserve_closed (plugin->cls,
- session,
- &reserve_pub,
+ &reserve_pub2,
now,
sndr,
&wire_out_wtid,
&amount_with_fee,
- &fee_closing));
+ &fee_closing,
+ 0));
FAILIF (GNUNET_OK !=
- check_reserve (session,
- &reserve_pub,
+ check_reserve (&reserve_pub2,
+ 0,
+ 0,
+ value.currency));
+ now = GNUNET_TIME_timestamp_get ();
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_reserve_closed (plugin->cls,
+ &reserve_pub,
+ now,
+ sndr,
+ &wire_out_wtid,
+ &value,
+ &fee_closing,
+ 0));
+ FAILIF (GNUNET_OK !=
+ check_reserve (&reserve_pub,
0,
0,
value.currency));
-
result = 7;
- qs = plugin->get_reserve_history (plugin->cls,
- session,
- &reserve_pub,
- &rh);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+
+ /* check reserve history */
+ {
+ struct TALER_Amount balance;
+ uint64_t etag_out;
+
+ qs = plugin->get_reserve_history (plugin->cls,
+ &reserve_pub,
+ 0,
+ 0,
+ &etag_out,
+ &balance,
+ &rh);
+ }
FAILIF (0 > qs);
FAILIF (NULL == rh);
rh_head = rh;
@@ -1782,9 +1904,9 @@ run (void *cls)
{
case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
bt = rh_head->details.bank;
- FAILIF (0 != memcmp (&bt->reserve_pub,
- &reserve_pub,
- sizeof (reserve_pub)));
+ FAILIF (0 !=
+ GNUNET_memcmp (&bt->reserve_pub,
+ &reserve_pub));
/* this is the amount we transferred twice*/
FAILIF (1 != bt->amount.value);
FAILIF (1000 != bt->amount.fraction);
@@ -1793,31 +1915,32 @@ run (void *cls)
break;
case TALER_EXCHANGEDB_RO_WITHDRAW_COIN:
withdraw = rh_head->details.withdraw;
- FAILIF (0 != memcmp (&withdraw->reserve_pub,
- &reserve_pub,
- sizeof (reserve_pub)));
- FAILIF (0 != memcmp (&withdraw->h_coin_envelope,
- &cbc.h_coin_envelope,
- sizeof (cbc.h_coin_envelope)));
+ FAILIF (0 !=
+ GNUNET_memcmp (&withdraw->reserve_pub,
+ &reserve_pub));
+ FAILIF (0 !=
+ GNUNET_memcmp (&withdraw->h_coin_envelope,
+ &cbc.h_coin_envelope));
break;
case TALER_EXCHANGEDB_RO_RECOUP_COIN:
{
struct TALER_EXCHANGEDB_Recoup *recoup = rh_head->details.recoup;
- FAILIF (0 != memcmp (&recoup->coin_sig,
- &coin_sig,
- sizeof (coin_sig)));
- FAILIF (0 != memcmp (&recoup->coin_blind,
- &coin_blind,
- sizeof (coin_blind)));
- FAILIF (0 != memcmp (&recoup->reserve_pub,
- &reserve_pub,
- sizeof (reserve_pub)));
- FAILIF (0 != memcmp (&recoup->coin.coin_pub,
- &deposit.coin.coin_pub,
- sizeof (deposit.coin.coin_pub)));
- FAILIF (0 != TALER_amount_cmp (&recoup->value,
- &value));
+ FAILIF (0 !=
+ GNUNET_memcmp (&recoup->coin_sig,
+ &coin_sig));
+ FAILIF (0 !=
+ GNUNET_memcmp (&recoup->coin_blind,
+ &coin_blind));
+ FAILIF (0 !=
+ GNUNET_memcmp (&recoup->reserve_pub,
+ &reserve_pub));
+ FAILIF (0 !=
+ GNUNET_memcmp (&recoup->coin.coin_pub,
+ &deposit.coin.coin_pub));
+ FAILIF (0 !=
+ TALER_amount_cmp (&recoup->value,
+ &value));
}
break;
case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
@@ -1825,277 +1948,75 @@ run (void *cls)
struct TALER_EXCHANGEDB_ClosingTransfer *closing
= rh_head->details.closing;
- FAILIF (0 != memcmp (&closing->reserve_pub,
- &reserve_pub,
- sizeof (reserve_pub)));
+ FAILIF (0 !=
+ GNUNET_memcmp (&closing->reserve_pub,
+ &reserve_pub));
FAILIF (0 != TALER_amount_cmp (&closing->amount,
&amount_with_fee));
FAILIF (0 != TALER_amount_cmp (&closing->closing_fee,
&fee_closing));
}
break;
+ case TALER_EXCHANGEDB_RO_PURSE_MERGE:
+ {
+ /* FIXME: not yet tested */
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
+ {
+ /* FIXME: not yet tested */
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
+ {
+ /* FIXME: not yet tested */
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
+ {
+ /* FIXME: not yet tested */
+ break;
+ }
}
}
- FAILIF (5 != cnt);
+ GNUNET_assert (4 == cnt);
+ FAILIF (4 != cnt);
auditor_row_cnt = 0;
FAILIF (0 >=
plugin->select_reserves_in_above_serial_id (plugin->cls,
- session,
0,
&audit_reserve_in_cb,
NULL));
FAILIF (0 >=
plugin->select_withdrawals_above_serial_id (plugin->cls,
- session,
0,
&audit_reserve_out_cb,
NULL));
FAILIF (3 != auditor_row_cnt);
- /* Tests for deposits */
- memset (&deposit,
- 0,
- sizeof (deposit));
- RND_BLK (&deposit.coin.coin_pub);
- GNUNET_CRYPTO_rsa_public_key_hash (dkp->pub.rsa_public_key,
- &deposit.coin.denom_pub_hash);
- deposit.coin.denom_sig = cbc.sig;
- RND_BLK (&deposit.csig);
- RND_BLK (&deposit.merchant_pub);
- RND_BLK (&deposit.h_contract_terms);
- GNUNET_assert (GNUNET_OK ==
- TALER_JSON_merchant_wire_signature_hash (wire,
- &deposit.h_wire));
- deposit.receiver_wire_account = wire;
- deposit.amount_with_fee = value;
- deposit.deposit_fee = fee_deposit;
-
- deposit.refund_deadline = deadline;
- deposit.wire_deadline = deadline;
- result = 8;
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->ensure_coin_known (plugin->cls,
- session,
- &deposit.coin));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_deposit (plugin->cls,
- session,
- &deposit));
- FAILIF (1 !=
- plugin->have_deposit (plugin->cls,
- session,
- &deposit,
- GNUNET_YES));
- {
- struct GNUNET_TIME_Absolute start_range;
- struct GNUNET_TIME_Absolute end_range;
-
- start_range = GNUNET_TIME_absolute_subtract (deadline,
- GNUNET_TIME_UNIT_SECONDS);
- end_range = GNUNET_TIME_absolute_add (deadline,
- GNUNET_TIME_UNIT_SECONDS);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->select_deposits_missing_wire (plugin->cls,
- session,
- start_range,
- end_range,
- &wire_missing_cb,
- &deposit));
- FAILIF (8 != result);
- }
- auditor_row_cnt = 0;
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->select_deposits_above_serial_id (plugin->cls,
- session,
- 0,
- &audit_deposit_cb,
- NULL));
- FAILIF (1 != auditor_row_cnt);
- result = 9;
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->iterate_matching_deposits (plugin->cls,
- session,
- &deposit.h_wire,
- &deposit.merchant_pub,
- &deposit_cb,
- &deposit,
- 2));
- sleep (2); /* giv deposit time to be ready */
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_ready_deposit (plugin->cls,
- session,
- &deposit_cb,
- &deposit));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->commit (plugin->cls,
- session));
- FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls,
- session,
- "test-2"));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->mark_deposit_tiny (plugin->cls,
- session,
- deposit_rowid));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->get_ready_deposit (plugin->cls,
- session,
- &deposit_cb,
- &deposit));
- plugin->rollback (plugin->cls,
- session);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_ready_deposit (plugin->cls,
- session,
- &deposit_cb,
- &deposit));
- FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls,
- session,
- "test-3"));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->test_deposit_done (plugin->cls,
- session,
- &deposit.coin.coin_pub,
- &deposit.merchant_pub,
- &deposit.h_contract_terms,
- &deposit.h_wire));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->mark_deposit_done (plugin->cls,
- session,
- deposit_rowid));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->commit (plugin->cls,
- session));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->test_deposit_done (plugin->cls,
- session,
- &deposit.coin.coin_pub,
- &deposit.merchant_pub,
- &deposit.h_contract_terms,
- &deposit.h_wire));
-
- result = 10;
- deposit2 = deposit;
- FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls,
- session,
- "test-2"));
- RND_BLK (&deposit2.merchant_pub); /* should fail if merchant is different */
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->have_deposit (plugin->cls,
- session,
- &deposit2,
- GNUNET_YES));
- deposit2.merchant_pub = deposit.merchant_pub;
- RND_BLK (&deposit2.coin.coin_pub); /* should fail if coin is different */
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->have_deposit (plugin->cls,
- session,
- &deposit2,
- GNUNET_YES));
- FAILIF (GNUNET_OK !=
- test_melting (session));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->commit (plugin->cls,
- session));
-
-
- /* test insert_refund! */
- refund.coin = deposit.coin;
- refund.details.merchant_pub = deposit.merchant_pub;
- RND_BLK (&refund.details.merchant_sig);
- refund.details.h_contract_terms = deposit.h_contract_terms;
- refund.details.rtransaction_id
- = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
- UINT64_MAX);
- refund.details.refund_amount = deposit.amount_with_fee;
- refund.details.refund_fee = fee_refund;
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_refund (plugin->cls,
- session,
- &refund));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->select_refunds_by_coin (plugin->cls,
- session,
- &refund.coin.coin_pub,
- &refund.details.merchant_pub,
- &refund.details.h_contract_terms,
- &check_refund_cb,
- &refund));
-
- /* test recoup / revocation */
- RND_BLK (&master_sig);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_denomination_revocation (plugin->cls,
- session,
- &dkp_pub_hash,
- &master_sig));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->commit (plugin->cls,
- session));
- plugin->preflight (plugin->cls,
- session);
- FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls,
- session,
- "test-4"));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->insert_denomination_revocation (plugin->cls,
- session,
- &dkp_pub_hash,
- &master_sig));
- plugin->rollback (plugin->cls,
- session);
- plugin->preflight (plugin->cls,
- session);
- FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls,
- session,
- "test-5"));
- {
- struct TALER_MasterSignatureP msig;
- uint64_t rev_rowid;
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_denomination_revocation (plugin->cls,
- session,
- &dkp_pub_hash,
- &msig,
- &rev_rowid));
- FAILIF (0 != GNUNET_memcmp (&msig,
- &master_sig));
- }
-
-
- RND_BLK (&coin_sig);
- RND_BLK (&coin_blind);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_recoup_request (plugin->cls,
- session,
- &reserve_pub,
- &deposit.coin,
- &coin_sig,
- &coin_blind,
- &value,
- &cbc.h_coin_envelope,
- deadline));
auditor_row_cnt = 0;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->select_refunds_above_serial_id (plugin->cls,
- session,
0,
&audit_refund_cb,
NULL));
-
FAILIF (1 != auditor_row_cnt);
- qs = plugin->get_coin_transactions (plugin->cls,
- session,
- &refund.coin.coin_pub,
- GNUNET_YES,
- &tl);
+ {
+ uint64_t etag = 0;
+ struct TALER_Amount balance;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ qs = plugin->get_coin_transactions (plugin->cls,
+ &refund.coin.coin_pub,
+ 0,
+ 0,
+ &etag,
+ &balance,
+ &h_denom_pub,
+ &tl);
+ }
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
GNUNET_assert (NULL != tl);
matched = 0;
@@ -2110,40 +2031,39 @@ run (void *cls)
/* Note: we're not comparing the denomination keys, as there is
still the question of whether we should even bother exporting
them here. */
- FAILIF (0 != memcmp (&have->csig,
- &deposit.csig,
- sizeof (struct TALER_CoinSpendSignatureP)));
- FAILIF (0 != memcmp (&have->merchant_pub,
- &deposit.merchant_pub,
- sizeof (struct TALER_MerchantPublicKeyP)));
- FAILIF (0 != memcmp (&have->h_contract_terms,
- &deposit.h_contract_terms,
- sizeof (struct GNUNET_HashCode)));
- FAILIF (0 != memcmp (&have->h_wire,
- &deposit.h_wire,
- sizeof (struct GNUNET_HashCode)));
- /* Note: not comparing 'wire', seems truly redundant and would be tricky */
- FAILIF (have->timestamp.abs_value_us != deposit.timestamp.abs_value_us);
- FAILIF (have->refund_deadline.abs_value_us !=
- deposit.refund_deadline.abs_value_us);
- FAILIF (have->wire_deadline.abs_value_us !=
- deposit.wire_deadline.abs_value_us);
+ FAILIF (0 !=
+ GNUNET_memcmp (&have->csig,
+ &deposit.csig));
+ FAILIF (0 !=
+ GNUNET_memcmp (&have->merchant_pub,
+ &bd.merchant_pub));
+ FAILIF (0 !=
+ GNUNET_memcmp (&have->h_contract_terms,
+ &bd.h_contract_terms));
+ FAILIF (0 !=
+ GNUNET_memcmp (&have->wire_salt,
+ &bd.wire_salt));
+ FAILIF (GNUNET_TIME_timestamp_cmp (have->timestamp,
+ !=,
+ bd.wallet_timestamp));
+ FAILIF (GNUNET_TIME_timestamp_cmp (have->refund_deadline,
+ !=,
+ bd.refund_deadline));
+ FAILIF (GNUNET_TIME_timestamp_cmp (have->wire_deadline,
+ !=,
+ bd.wire_deadline));
FAILIF (0 != TALER_amount_cmp (&have->amount_with_fee,
&deposit.amount_with_fee));
- FAILIF (0 != TALER_amount_cmp (&have->deposit_fee,
- &deposit.deposit_fee));
matched |= 1;
break;
}
-#if 0
/* this coin pub was actually never melted... */
case TALER_EXCHANGEDB_TT_MELT:
- FAILIF (0 != memcmp (&melt,
- &tlp->details.melt,
- sizeof (struct TALER_EXCHANGEDB_Melt)));
+ FAILIF (0 !=
+ GNUNET_memcmp (&refresh.rc,
+ &tlp->details.melt->rc));
matched |= 2;
break;
-#endif
case TALER_EXCHANGEDB_TT_REFUND:
{
struct TALER_EXCHANGEDB_RefundListEntry *have = tlp->details.refund;
@@ -2181,52 +2101,357 @@ run (void *cls)
matched |= 8;
break;
}
+ case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
+ /* TODO: check fields better... */
+ matched |= 16;
+ break;
default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected coin history transaction type: %d\n",
+ tlp->type);
FAILIF (1);
break;
}
}
- FAILIF (13 != matched);
+ FAILIF (31 != matched);
plugin->free_coin_transaction_list (plugin->cls,
tl);
- plugin->rollback (plugin->cls,
- session);
+
+ /* Tests for deposits+wire */
+ TALER_denom_sig_free (&deposit.coin.denom_sig);
+ memset (&deposit,
+ 0,
+ sizeof (deposit));
+ RND_BLK (&deposit.coin.coin_pub);
+ TALER_denom_pub_hash (&dkp->pub,
+ &deposit.coin.denom_pub_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&deposit.coin.denom_sig,
+ &cbc.sig,
+ &bks,
+ &c_hash,
+ alg_values,
+ &dkp->pub));
+ RND_BLK (&deposit.csig);
+ RND_BLK (&bd.merchant_pub);
+ RND_BLK (&bd.h_contract_terms);
+ RND_BLK (&bd.wire_salt);
+ bd.receiver_wire_account =
+ "payto://iban/DE67830654080004822650?receiver-name=Test";
+ TALER_merchant_wire_signature_hash (
+ "payto://iban/DE67830654080004822650?receiver-name=Test",
+ &bd.wire_salt,
+ &h_wire_wt);
+ deposit.amount_with_fee = value;
+ bd.refund_deadline = deadline;
+ bd.wire_deadline = deadline;
+ result = 8;
FAILIF (GNUNET_OK !=
- test_wire_prepare (session));
+ plugin->start (plugin->cls,
+ "test-3"));
+ {
+ uint64_t known_coin_id;
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+
+ FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+ plugin->ensure_coin_known (plugin->cls,
+ &deposit.coin,
+ &known_coin_id,
+ &dph,
+ &agh));
+ }
+ {
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_TIME_Timestamp r;
+ struct TALER_Amount deposit_fee;
+ struct TALER_MerchantWireHashP h_wire;
+ bool balance_ok;
+ uint32_t bad_idx;
+ bool ctr_conflict;
+
+ now = GNUNET_TIME_timestamp_get ();
+ TALER_payto_hash (bd.receiver_wire_account,
+ &bd.wire_target_h_payto);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_deposit (plugin->cls,
+ &bd,
+ &now,
+ &balance_ok,
+ &bad_idx,
+ &ctr_conflict));
+ TALER_merchant_wire_signature_hash (bd.receiver_wire_account,
+ &bd.wire_salt,
+ &h_wire);
+ FAILIF (1 !=
+ plugin->have_deposit2 (plugin->cls,
+ &bd.h_contract_terms,
+ &h_wire,
+ &deposit.coin.coin_pub,
+ &bd.merchant_pub,
+ bd.refund_deadline,
+ &deposit_fee,
+ &r));
+ FAILIF (GNUNET_TIME_timestamp_cmp (now,
+ !=,
+ r));
+ }
+ {
+ result = 66;
+ FAILIF (0 >=
+ plugin->select_batch_deposits_missing_wire (plugin->cls,
+ 0,
+ &wire_missing_cb,
+ &deposit));
+ FAILIF (8 != result);
+ }
+ auditor_row_cnt = 0;
+ FAILIF (0 >=
+ plugin->select_coin_deposits_above_serial_id (plugin->cls,
+ 0,
+ &audit_deposit_cb,
+ NULL));
+ FAILIF (0 == auditor_row_cnt);
+ result = 8;
+ sleep (2); /* give deposit time to be ready */
+ {
+ struct TALER_MerchantPublicKeyP merchant_pub2;
+ char *payto_uri2;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_ready_deposit (plugin->cls,
+ 0,
+ INT32_MAX,
+ &merchant_pub2,
+ &payto_uri2));
+ FAILIF (0 != GNUNET_memcmp (&merchant_pub2,
+ &bd.merchant_pub));
+ FAILIF (0 != strcmp (payto_uri2,
+ bd.receiver_wire_account));
+ TALER_payto_hash (payto_uri2,
+ &wire_target_h_payto);
+ GNUNET_free (payto_uri2);
+ }
+
+ {
+ struct TALER_Amount total;
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ memset (&wtid,
+ 41,
+ sizeof (wtid));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->aggregate (plugin->cls,
+ &wire_target_h_payto,
+ &bd.merchant_pub,
+ &wtid,
+ &total));
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
FAILIF (GNUNET_OK !=
- test_wire_out (session,
- &deposit));
+ plugin->start (plugin->cls,
+ "test-3"));
+ {
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct TALER_Amount total;
+ struct TALER_WireTransferIdentifierRawP wtid2;
+ struct TALER_Amount total2;
+
+ memset (&wtid,
+ 42,
+ sizeof (wtid));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":42",
+ &total));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->select_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ &bd.merchant_pub,
+ "x-bank",
+ &wtid2,
+ &total2));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->create_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ "x-bank",
+ &bd.merchant_pub,
+ &wtid,
+ 0,
+ &total));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->select_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ &bd.merchant_pub,
+ "x-bank",
+ &wtid2,
+ &total2));
+ FAILIF (0 !=
+ GNUNET_memcmp (&wtid2,
+ &wtid));
+ FAILIF (0 !=
+ TALER_amount_cmp (&total2,
+ &total));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":43",
+ &total));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->update_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ &wtid,
+ 0,
+ &total));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->select_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ &bd.merchant_pub,
+ "x-bank",
+ &wtid2,
+ &total2));
+ FAILIF (0 !=
+ GNUNET_memcmp (&wtid2,
+ &wtid));
+ FAILIF (0 !=
+ TALER_amount_cmp (&total2,
+ &total));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->delete_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ &wtid));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->select_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ &bd.merchant_pub,
+ "x-bank",
+ &wtid2,
+ &total2));
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+
+ result = 10;
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-2"));
+ RND_BLK (&mpub2); /* should fail if merchant is different */
+ {
+ struct TALER_MerchantWireHashP h_wire;
+ struct GNUNET_TIME_Timestamp r;
+ struct TALER_Amount deposit_fee;
+
+ TALER_merchant_wire_signature_hash (bd.receiver_wire_account,
+ &bd.wire_salt,
+ &h_wire);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->have_deposit2 (plugin->cls,
+ &bd.h_contract_terms,
+ &h_wire,
+ &deposit.coin.coin_pub,
+ &mpub2,
+ bd.refund_deadline,
+ &deposit_fee,
+ &r));
+ RND_BLK (&cpub2); /* should fail if coin is different */
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->have_deposit2 (plugin->cls,
+ &bd.h_contract_terms,
+ &h_wire,
+ &cpub2,
+ &bd.merchant_pub,
+ bd.refund_deadline,
+ &deposit_fee,
+ &r));
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+
+
+ /* test revocation */
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-3b"));
+ RND_BLK (&master_sig);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_denomination_revocation (plugin->cls,
+ &cbc.denom_pub_hash,
+ &master_sig));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+ plugin->preflight (plugin->cls);
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-4"));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->insert_denomination_revocation (plugin->cls,
+ &cbc.denom_pub_hash,
+ &master_sig));
+ plugin->rollback (plugin->cls);
+ plugin->preflight (plugin->cls);
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-5"));
+ {
+ struct TALER_MasterSignatureP msig;
+ uint64_t rev_rowid;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_denomination_revocation (plugin->cls,
+ &cbc.denom_pub_hash,
+ &msig,
+ &rev_rowid));
+ FAILIF (0 != GNUNET_memcmp (&msig,
+ &master_sig));
+ }
+
+
+ plugin->rollback (plugin->cls);
+ FAILIF (GNUNET_OK !=
+ test_wire_prepare ());
+ FAILIF (GNUNET_OK !=
+ test_wire_out (&bd));
FAILIF (GNUNET_OK !=
- test_gc (session));
+ test_gc ());
FAILIF (GNUNET_OK !=
- test_wire_fees (session));
+ test_wire_fees ());
- plugin->preflight (plugin->cls,
- session);
+ plugin->preflight (plugin->cls);
result = 0;
drop:
- if ( (0 != result) &&
- (NULL != session) )
- plugin->rollback (plugin->cls,
- session);
+ if (0 != result)
+ plugin->rollback (plugin->cls);
if (NULL != rh)
plugin->free_reserve_history (plugin->cls,
rh);
rh = NULL;
GNUNET_break (GNUNET_OK ==
plugin->drop_tables (plugin->cls));
+cleanup:
if (NULL != dkp)
destroy_denom_key_pair (dkp);
- if (NULL != cbc.sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (cbc.sig.rsa_signature);
- if (NULL != cbc2.sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (cbc2.sig.rsa_signature);
+ if (NULL != revealed_coins)
+ {
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ TALER_blinded_denom_sig_free (&revealed_coins[cnt].coin_sig);
+ TALER_blinded_planchet_free (&revealed_coins[cnt].blinded_planchet);
+ }
+ GNUNET_free (revealed_coins);
+ revealed_coins = NULL;
+ }
+ GNUNET_free (new_denom_pubs);
+ for (unsigned int cnt = 0;
+ (NULL != new_dkp) && (cnt < MELT_NEW_COINS) && (NULL != new_dkp[cnt]);
+ cnt++)
+ destroy_denom_key_pair (new_dkp[cnt]);
+ GNUNET_free (new_dkp);
+ TALER_denom_sig_free (&deposit.coin.denom_sig);
+ TALER_blinded_denom_sig_free (&cbc.sig);
+ TALER_blinded_denom_sig_free (&cbc2.sig);
dkp = NULL;
- json_decref (wire);
TALER_EXCHANGEDB_plugin_unload (plugin);
plugin = NULL;
}
@@ -2241,6 +2466,7 @@ main (int argc,
char *testname;
struct GNUNET_CONFIGURATION_Handle *cfg;
+ (void) argc;
result = -1;
if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
{
@@ -2248,8 +2474,9 @@ main (int argc,
return -1;
}
GNUNET_log_setup (argv[0],
- "WARNING",
+ "INFO",
NULL);
+ TALER_OS_init ();
plugin_name++;
(void) GNUNET_asprintf (&testname,
"test-exchange-db-%s",
diff --git a/src/exchangedb/test_exchangedb_auditors.c b/src/exchangedb/test_exchangedb_auditors.c
deleted file mode 100644
index efd3ffe14..000000000
--- a/src/exchangedb/test_exchangedb_auditors.c
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2016 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 exchangedb/test_exchangedb_auditors.c
- * @brief test cases for some functions in exchangedb/exchangedb_auditorkeys.c
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "gnunet/gnunet_util_lib.h"
-#include "taler_signatures.h"
-#include "taler_exchangedb_lib.h"
-
-
-#define RSA_KEY_SIZE 1024
-
-
-#define EXITIF(cond) \
- do { \
- if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
- } while (0)
-
-
-static struct TALER_AuditorPublicKeyP want_apub;
-
-static struct TALER_AuditorSignatureP want_asigs;
-
-static struct TALER_MasterPublicKeyP want_mpub;
-
-static struct TALER_DenominationKeyValidityPS want_dki;
-
-
-/**
- * @brief Function called with auditor information.
- *
- * @param cls NULL
- * @param apub the auditor's public key
- * @param auditor_url URL of the auditor
- * @param mpub the exchange's public key (as expected by the auditor)
- * @param dki_len length of @a asig and @a dki arrays
- * @param asigs array of the auditor's signatures over the @a dks, of length @a dki_len
- * @param dki array of denomination coin data signed by the auditor, of length @a dki_len
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-static int
-auditor_cb (void *cls,
- const struct TALER_AuditorPublicKeyP *apub,
- const char *auditor_url,
- const struct TALER_MasterPublicKeyP *mpub,
- unsigned int dki_len,
- const struct TALER_AuditorSignatureP *asigs,
- const struct TALER_DenominationKeyValidityPS *dki)
-{
- GNUNET_assert (NULL == cls);
- if (0 != strcmp (auditor_url,
- "http://auditor/"))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (1 != dki_len)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (0 != GNUNET_memcmp (&want_apub,
- apub))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (0 != GNUNET_memcmp (&want_mpub,
- mpub))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (0 != GNUNET_memcmp (&want_asigs,
- asigs))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (0 != GNUNET_memcmp (&want_dki,
- dki))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-int
-main (int argc,
- const char *const argv[])
-{
- char *tmpfile = NULL;
- char *tmpdir;
- struct GNUNET_CONFIGURATION_Handle *cfg = NULL;
- int ret;
-
- (void) argc;
- (void) argv;
- ret = 1;
- GNUNET_log_setup ("test-exchangedb-auditors",
- "WARNING",
- NULL);
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
- &want_apub,
- sizeof (want_apub));
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
- &want_asigs,
- sizeof (want_asigs));
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
- &want_mpub,
- sizeof (want_mpub));
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
- &want_dki,
- sizeof (struct TALER_DenominationKeyValidityPS));
- EXITIF (NULL == (tmpdir = GNUNET_DISK_mkdtemp ("test_exchangedb_auditors")));
- GNUNET_asprintf (&tmpfile,
- "%s/%s",
- tmpdir,
- "testauditor");
- EXITIF (GNUNET_OK !=
- TALER_EXCHANGEDB_auditor_write (tmpfile,
- &want_apub,
- "http://auditor/",
- &want_asigs,
- &want_mpub,
- 1,
- &want_dki));
- cfg = GNUNET_CONFIGURATION_create ();
-
- GNUNET_CONFIGURATION_set_value_string (cfg,
- "exchangedb",
- "AUDITOR_BASE_DIR",
- tmpdir);
- EXITIF (1 !=
- TALER_EXCHANGEDB_auditor_iterate (cfg,
- &auditor_cb,
- NULL));
- ret = 0;
-EXITIF_exit:
- if (NULL != tmpdir)
- {
- (void) GNUNET_DISK_directory_remove (tmpdir);
- GNUNET_free (tmpdir);
- }
- if (NULL != cfg)
- GNUNET_CONFIGURATION_destroy (cfg);
- GNUNET_free_non_null (tmpfile);
- return ret;
-}
diff --git a/src/exchangedb/test_exchangedb_by_j.c b/src/exchangedb/test_exchangedb_by_j.c
new file mode 100644
index 000000000..24b24d5b0
--- /dev/null
+++ b/src/exchangedb/test_exchangedb_by_j.c
@@ -0,0 +1,232 @@
+/*
+ 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/>
+*/
+/**
+ * @file exchangedb/test_exchangedb_by_j.c
+ * @brief test cases for DB interaction functions
+ * @author Joseph Xu
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "math.h"
+#define ROUNDS 10
+
+/**
+ * Global result from the testcase.
+ */
+static int result;
+
+/**
+ * Report line of error if @a cond is true, and jump to label "drop".
+ */
+#define FAILIF(cond) \
+ do { \
+ if (! (cond)) {break;} \
+ GNUNET_break (0); \
+ goto drop; \
+ } while (0)
+
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
+
+/**
+ * Initializes @a ptr with zeros.
+ */
+#define ZR_BLK(ptr) \
+ memset (ptr, 0, sizeof (*ptr))
+
+
+/**
+ * Currency we use. Must match test-exchange-db-*.conf.
+ */
+#define CURRENCY "EUR"
+
+/**
+ * Database plugin under test.
+ */
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+static void
+run (void *cls)
+{
+ static const unsigned int batches[] = {1, 2, 3, 4, 8, 16 };
+ struct GNUNET_TIME_Relative times[sizeof (batches) / sizeof(*batches)];
+ unsigned long long sqrs[sizeof (batches) / sizeof(*batches)];
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ const uint32_t num_partitions = 10;
+
+ if (NULL ==
+ (plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_break (0);
+ result = 77;
+ return;
+ }
+
+
+ if (GNUNET_OK !=
+ plugin->create_tables (plugin->cls,
+ true,
+ num_partitions))
+ {
+ GNUNET_break (0);
+ result = 77;
+ goto cleanup;
+ }
+
+ memset (times, 0, sizeof (times));
+ memset (sqrs, 0, sizeof (sqrs));
+ for (unsigned int r = 0; r < ROUNDS; r++)
+ {
+ for (unsigned int i = 0; i< 6; i++)
+ {
+ const char *sndr = "payto://x-taler-bank/localhost:8080/1";
+ struct TALER_Amount value;
+ unsigned int batch_size = batches[i];
+ unsigned int iterations = 16; // 1024*10;
+ struct TALER_ReservePublicKeyP reserve_pubs[iterations];
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Timestamp ts;
+ struct GNUNET_TIME_Relative duration;
+ struct TALER_EXCHANGEDB_ReserveInInfo reserves[iterations];
+ enum GNUNET_DB_QueryStatus results[iterations];
+ unsigned long long duration_sq;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.000010",
+ &value));
+ now = GNUNET_TIME_absolute_get ();
+ ts = GNUNET_TIME_timestamp_get ();
+ for (unsigned int r = 0; r<iterations; r++)
+ {
+ RND_BLK (&reserve_pubs[r]);
+ reserves[r].reserve_pub = &reserve_pubs[r];
+ reserves[r].balance = &value;
+ reserves[r].execution_time = ts;
+ reserves[r].sender_account_details = sndr;
+ reserves[r].exchange_account_name = "name";
+ reserves[r].wire_reference = r;
+ }
+ FAILIF (iterations !=
+ plugin->batch2_reserves_in_insert (plugin->cls,
+ reserves,
+ iterations,
+ batch_size,
+ results));
+ duration = GNUNET_TIME_absolute_get_duration (now);
+ times[i] = GNUNET_TIME_relative_add (times[i],
+ duration);
+ duration_sq = duration.rel_value_us * duration.rel_value_us;
+ GNUNET_assert (duration_sq / duration.rel_value_us ==
+ duration.rel_value_us);
+ GNUNET_assert (sqrs[i] + duration_sq >= sqrs[i]);
+ sqrs[i] += duration_sq;
+ fprintf (stdout,
+ "for a batchsize equal to %d it took %s\n",
+ batch_size,
+ GNUNET_STRINGS_relative_time_to_string (duration,
+ GNUNET_NO) );
+
+ system ("./test.sh"); // DELETE AFTER TIMER
+ }
+ }
+ for (unsigned int i = 0; i< 6; i++)
+ {
+ struct GNUNET_TIME_Relative avg;
+ double avg_dbl;
+ double variance;
+
+ avg = GNUNET_TIME_relative_divide (times[i],
+ ROUNDS);
+ avg_dbl = avg.rel_value_us;
+ variance = sqrs[i] - (avg_dbl * avg_dbl * ROUNDS);
+ fprintf (stdout,
+ "Batch[%2u]: %8llu ± %6.0f\n",
+ batches[i],
+ (unsigned long long) avg.rel_value_us,
+ sqrt (variance / (ROUNDS - 1)));
+ }
+
+ result = 0;
+drop:
+ GNUNET_break (GNUNET_OK ==
+ plugin->drop_tables (plugin->cls));
+cleanup:
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ char *testname;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+ (void) argc;
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+
+ GNUNET_log_setup (argv[0],
+ "WARNING",
+ NULL);
+ plugin_name++;
+ (void) GNUNET_asprintf (&testname,
+ "test-exchange-db-%s",
+ plugin_name);
+ (void) GNUNET_asprintf (&config_filename,
+ "%s.conf",
+ testname);
+ fprintf (stdout,
+ "Using config: %s\n",
+ config_filename);
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return result;
+}
+
+
+/* end of test_exchangedb_by_j.c */
diff --git a/src/exchangedb/test_exchangedb_denomkeys.c b/src/exchangedb/test_exchangedb_denomkeys.c
deleted file mode 100644
index 12add14da..000000000
--- a/src/exchangedb/test_exchangedb_denomkeys.c
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2017 Taler Systems SA (and other contributing authors)
-
- 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 exchangedb/test_exchangedb_denomkeys.c
- * @brief test cases for some functions in exchangedb/exchangedb_denomkeys.c
- * @author Sree Harsha Totakura <sreeharsha@totakura.in>
- */
-#include "platform.h"
-#include "gnunet/gnunet_util_lib.h"
-#include "taler_signatures.h"
-#include "taler_exchangedb_lib.h"
-
-#define RSA_KEY_SIZE 1024
-
-
-#define EXITIF(cond) \
- do { \
- if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
- } while (0)
-
-
-/**
- * @brief Iterator called on denomination key.
- *
- * @param cls closure with expected DKI
- * @param dki the denomination key
- * @param alias coin alias
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-static int
-dki_iter (void *cls,
- const char *alias,
- const struct TALER_EXCHANGEDB_DenominationKey *dki)
-{
- const struct TALER_EXCHANGEDB_DenominationKey *exp = cls;
-
- (void) alias;
- if (0 != GNUNET_memcmp (&exp->issue,
- &dki->issue))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (0 !=
- GNUNET_CRYPTO_rsa_private_key_cmp (exp->denom_priv.rsa_private_key,
- dki->denom_priv.rsa_private_key))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (0 !=
- GNUNET_CRYPTO_rsa_public_key_cmp (exp->denom_pub.rsa_public_key,
- dki->denom_pub.rsa_public_key))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * @brief Iterator called on revoked denomination key.
- *
- * @param cls closure with expected DKI
- * @param denom_hash hash of the revoked denomination key
- * @param revocation_master_sig non-NULL if @a dki was revoked
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-static int
-dki_iter_revoked (void *cls,
- const struct GNUNET_HashCode *denom_hash,
- const struct TALER_MasterSignatureP *revocation_master_sig)
-{
- const struct TALER_EXCHANGEDB_DenominationKey *exp = cls;
-
- if (NULL == revocation_master_sig)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (0 != GNUNET_memcmp (denom_hash,
- &exp->issue.properties.denom_hash))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-int
-main (int argc,
- const char *const argv[])
-{
- struct TALER_EXCHANGEDB_DenominationKey dki;
- void *enc;
- size_t enc_size;
- struct TALER_EXCHANGEDB_DenominationKey dki_read;
- struct GNUNET_CRYPTO_EddsaPrivateKey *pk;
- struct TALER_MasterPrivateKeyP master_priv;
- struct TALER_MasterPublicKeyP master_pub;
- void *enc_read;
- size_t enc_read_size;
- char *tmpfile;
- char *tmpdir;
- char *revdir;
- int ret;
- struct GNUNET_TIME_Absolute start;
-
- (void) argc;
- (void) argv;
- ret = 1;
- GNUNET_log_setup ("test-exchangedb-denomkeys",
- "WARNING",
- NULL);
- enc = NULL;
- enc_read = NULL;
- tmpfile = NULL;
- dki.denom_priv.rsa_private_key = NULL;
- dki_read.denom_priv.rsa_private_key = NULL;
- pk = GNUNET_CRYPTO_eddsa_key_create ();
- master_priv.eddsa_priv = *pk;
- GNUNET_CRYPTO_eddsa_key_get_public (pk,
- &master_pub.eddsa_pub);
- GNUNET_free (pk);
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
- &dki.issue,
- sizeof (struct
- TALER_EXCHANGEDB_DenominationKeyInformationP));
- dki.denom_priv.rsa_private_key
- = GNUNET_CRYPTO_rsa_private_key_create (RSA_KEY_SIZE);
- dki.denom_pub.rsa_public_key
- = GNUNET_CRYPTO_rsa_private_key_get_public (dki.denom_priv.rsa_private_key);
- enc_size = GNUNET_CRYPTO_rsa_private_key_encode (
- dki.denom_priv.rsa_private_key,
- &enc);
- EXITIF (NULL == (tmpdir = GNUNET_DISK_mkdtemp ("test_exchangedb_dki")));
- start = GNUNET_TIME_absolute_ntoh (dki.issue.properties.start);
- GNUNET_asprintf (&tmpfile,
- "%s/%s/%s/%llu",
- tmpdir,
- TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS,
- "cur-unit-uuid",
- (unsigned long long) start.abs_value_us);
- GNUNET_asprintf (&revdir,
- "%s/revocations/",
- tmpdir,
- TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS);
- EXITIF (GNUNET_OK !=
- TALER_EXCHANGEDB_denomination_key_write (tmpfile,
- &dki));
- EXITIF (GNUNET_OK !=
- TALER_EXCHANGEDB_denomination_key_read (tmpfile,
- &dki_read));
- EXITIF (1 !=
- TALER_EXCHANGEDB_denomination_keys_iterate (tmpdir,
- &dki_iter,
- &dki));
-
- EXITIF (GNUNET_OK !=
- TALER_EXCHANGEDB_denomination_key_revoke (revdir,
- &dki.issue.properties.
- denom_hash,
- &master_priv));
- EXITIF (1 !=
- TALER_EXCHANGEDB_revocations_iterate (revdir,
- &master_pub,
- &dki_iter_revoked,
- &dki));
- GNUNET_free (revdir);
-
- enc_read_size = GNUNET_CRYPTO_rsa_private_key_encode (
- dki_read.denom_priv.rsa_private_key,
- &enc_read);
- EXITIF (enc_size != enc_read_size);
- EXITIF (0 != memcmp (enc,
- enc_read,
- enc_size));
- ret = 0;
-
-EXITIF_exit:
- GNUNET_free_non_null (enc);
- GNUNET_free_non_null (tmpfile);
- if (NULL != tmpdir)
- {
- (void) GNUNET_DISK_directory_remove (tmpdir);
- GNUNET_free (tmpdir);
- }
- GNUNET_free_non_null (enc_read);
- if (NULL != dki.denom_priv.rsa_private_key)
- GNUNET_CRYPTO_rsa_private_key_free (dki.denom_priv.rsa_private_key);
- if (NULL != dki.denom_pub.rsa_public_key)
- GNUNET_CRYPTO_rsa_public_key_free (dki.denom_pub.rsa_public_key);
- if (NULL != dki_read.denom_priv.rsa_private_key)
- GNUNET_CRYPTO_rsa_private_key_free (dki_read.denom_priv.rsa_private_key);
- if (NULL != dki_read.denom_pub.rsa_public_key)
- GNUNET_CRYPTO_rsa_public_key_free (dki_read.denom_pub.rsa_public_key);
- return ret;
-}
diff --git a/src/exchangedb/test_exchangedb_fees.c b/src/exchangedb/test_exchangedb_fees.c
deleted file mode 100644
index 7cd890eb3..000000000
--- a/src/exchangedb/test_exchangedb_fees.c
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2017 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 exchangedb/test_exchangedb_fees.c
- * @brief test cases for functions in exchangedb/exchangedb_fees.c
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "gnunet/gnunet_util_lib.h"
-#include "taler_signatures.h"
-#include "taler_exchangedb_lib.h"
-
-
-/**
- * Sign @a af with @a priv
- *
- * @param[in|out] af fee structure to sign
- * @param priv private key to use for signing
- */
-static void
-sign_af (struct TALER_EXCHANGEDB_AggregateFees *af,
- const struct GNUNET_CRYPTO_EddsaPrivateKey *priv)
-{
- struct TALER_MasterWireFeePS wf;
-
- TALER_EXCHANGEDB_fees_2_wf ("test",
- af,
- &wf);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (priv,
- &wf.purpose,
- &af->master_sig.eddsa_signature));
-}
-
-
-int
-main (int argc,
- const char *const argv[])
-{
- struct GNUNET_CONFIGURATION_Handle *cfg;
- struct TALER_EXCHANGEDB_AggregateFees *af;
- struct TALER_EXCHANGEDB_AggregateFees *n;
- struct TALER_MasterPublicKeyP master_pub;
- struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
- char *tmpdir;
- char *tmpfile = NULL;
- int ret;
- unsigned int year;
-
- (void) argc;
- (void) argv;
- GNUNET_log_setup ("test-exchangedb-fees",
- "WARNING",
- NULL);
- tmpdir = GNUNET_DISK_mkdtemp ("test_exchangedb_fees");
- if (NULL == tmpdir)
- return 77; /* skip test */
- priv = GNUNET_CRYPTO_eddsa_key_create ();
- GNUNET_CRYPTO_eddsa_key_get_public (priv,
- &master_pub.eddsa_pub);
- cfg = GNUNET_CONFIGURATION_create ();
- GNUNET_CONFIGURATION_set_value_string (cfg,
- "exchangedb",
- "WIREFEE_BASE_DIR",
- tmpdir);
- GNUNET_asprintf (&tmpfile,
- "%s/%s.fee",
- tmpdir,
- "test");
- ret = 0;
- af = GNUNET_new (struct TALER_EXCHANGEDB_AggregateFees);
- year = GNUNET_TIME_get_current_year ();
- af->start_date = GNUNET_TIME_year_to_time (year);
- af->end_date = GNUNET_TIME_year_to_time (year + 1);
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:1.0",
- &af->wire_fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:1.0",
- &af->closing_fee));
- sign_af (af,
- priv);
- n = GNUNET_new (struct TALER_EXCHANGEDB_AggregateFees);
- n->start_date = GNUNET_TIME_year_to_time (year + 1);
- n->end_date = GNUNET_TIME_year_to_time (year + 2);
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:0.1",
- &n->wire_fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:0.1",
- &n->closing_fee));
- sign_af (n,
- priv);
- af->next = n;
-
- if (GNUNET_OK !=
- TALER_EXCHANGEDB_fees_write (tmpfile,
- "test",
- af))
- {
- GNUNET_break (0);
- ret = 1;
- }
- TALER_EXCHANGEDB_fees_free (af);
- GNUNET_free (tmpfile);
- af = TALER_EXCHANGEDB_fees_read (cfg,
- "test");
- if (NULL == af)
- {
- GNUNET_break (0);
- ret = 1;
- }
- else
- {
- for (struct TALER_EXCHANGEDB_AggregateFees *p = af;
- NULL != p;
- p = p->next)
- {
- struct TALER_MasterWireFeePS wf;
-
- TALER_EXCHANGEDB_fees_2_wf ("test",
- p,
- &wf);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_FEES,
- &wf.purpose,
- &p->master_sig.eddsa_signature,
- &master_pub.eddsa_pub))
- {
- GNUNET_break (0);
- ret = 1;
- }
- }
- TALER_EXCHANGEDB_fees_free (af);
- }
-
- (void) GNUNET_DISK_directory_remove (tmpdir);
- GNUNET_free (tmpdir);
- GNUNET_free (priv);
- GNUNET_CONFIGURATION_destroy (cfg);
- return ret;
-}
diff --git a/src/exchangedb/test_exchangedb_signkeys.c b/src/exchangedb/test_exchangedb_signkeys.c
deleted file mode 100644
index 3969c9708..000000000
--- a/src/exchangedb/test_exchangedb_signkeys.c
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2016 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 exchangedb/test_exchangedb_signkeys.c
- * @brief test cases for some functions in exchangedb/exchangedb_signkeys.c
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "gnunet/gnunet_util_lib.h"
-#include "taler_signatures.h"
-#include "taler_exchangedb_lib.h"
-
-
-#define EXITIF(cond) \
- do { \
- if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
- } while (0)
-
-
-/**
- * @brief Iterator over signing keys.
- *
- * @param cls closure
- * @param filename name of the file the key came from
- * @param ski the sign key
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-static int
-ski_iter (void *cls,
- const char *filename,
- const struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP *ski)
-{
- const struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP *exp = cls;
-
- (void) filename;
- if (0 != GNUNET_memcmp (ski,
- exp))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-int
-main (int argc,
- const char *const argv[])
-{
- struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP ski;
- struct GNUNET_TIME_Absolute now;
- char *tmpfile;
- int ret;
-
- (void) argc;
- (void) argv;
- ret = 1;
- tmpfile = NULL;
- GNUNET_log_setup ("test-exchangedb-signkeys",
- "WARNING",
- NULL);
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
- &ski,
- sizeof (struct
- TALER_EXCHANGEDB_PrivateSigningKeyInformationP));
- now = GNUNET_TIME_absolute_get ();
- EXITIF (NULL == (tmpfile = GNUNET_DISK_mkdtemp ("test_exchangedb_ski")));
- EXITIF (GNUNET_OK !=
- TALER_EXCHANGEDB_signing_key_write (tmpfile,
- now,
- &ski));
- EXITIF (1 !=
- TALER_EXCHANGEDB_signing_keys_iterate (tmpfile,
- &ski_iter,
- &ski));
- ret = 0;
-EXITIF_exit:
- if (NULL != tmpfile)
- {
- (void) GNUNET_DISK_directory_remove (tmpfile);
- GNUNET_free (tmpfile);
- }
- return ret;
-}
diff --git a/src/exchangedb/test_idempotency.sh b/src/exchangedb/test_idempotency.sh
new file mode 100755
index 000000000..7314b8c3f
--- /dev/null
+++ b/src/exchangedb/test_idempotency.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+# This file is in the public domain.
+set -eu
+psql talercheck < /dev/null || exit 77
+echo "Initializing DB"
+taler-exchange-dbinit -r -c test-exchange-db-postgres.conf
+echo "Re-initializing DB"
+taler-exchange-dbinit -c test-exchange-db-postgres.conf
+echo "Re-loading procedures"
+psql talercheck < procedures.sql
+echo "Test PASSED"
+exit 0
diff --git a/src/auditordb/auditor-0000.sql b/src/exchangedb/versioning.sql
index 116f409b7..444cf95ed 100644
--- a/src/auditordb/auditor-0000.sql
+++ b/src/exchangedb/versioning.sql
@@ -146,12 +146,13 @@
BEGIN;
+
-- This file adds versioning support to database it will be loaded to.
-- It requires that PL/pgSQL is already loaded - will raise exception otherwise.
-- All versioning "stuff" (tables, functions) is in "_v" schema.
-- All functions are defined as 'RETURNS SETOF INT4' to be able to make them to RETURN literally nothing (0 rows).
--- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling.
+-- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling
CREATE SCHEMA IF NOT EXISTS _v;
COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.';
diff --git a/src/extensions/Makefile.am b/src/extensions/Makefile.am
new file mode 100644
index 000000000..c867a9512
--- /dev/null
+++ b/src/extensions/Makefile.am
@@ -0,0 +1,34 @@
+# This Makefile.am is in the public domain
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/include \
+ $(LIBGCRYPT_CFLAGS) \
+ $(POSTGRESQL_CPPFLAGS)
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+
+# Basic extension handling library
+
+lib_LTLIBRARIES = \
+ libtalerextensions.la
+
+libtalerextensions_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+
+libtalerextensions_la_SOURCES = \
+ extensions.c \
+ age_restriction_helper.c
+
+libtalerextensions_la_LIBADD = \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
diff --git a/src/extensions/age_restriction/Makefile.am b/src/extensions/age_restriction/Makefile.am
new file mode 100644
index 000000000..bf5b2f5f5
--- /dev/null
+++ b/src/extensions/age_restriction/Makefile.am
@@ -0,0 +1,32 @@
+# This Makefile.am is in the public domain
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/include \
+ $(LIBGCRYPT_CFLAGS) \
+ $(POSTGRESQL_CPPFLAGS)
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+# Age restriction as extension library
+
+plugindir = $(libdir)/taler
+
+plugin_LTLIBRARIES = \
+ libtaler_extension_age_restriction.la
+
+libtaler_extension_age_restriction_la_LDFLAGS = \
+ $(TALER_PLUGIN_LDFLAGS) \
+ -no-undefined
+
+libtaler_extension_age_restriction_la_SOURCES = \
+ age_restriction.c
+libtaler_extension_age_restriction_la_LIBADD = \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
diff --git a/src/extensions/age_restriction/age_restriction.c b/src/extensions/age_restriction/age_restriction.c
new file mode 100644
index 000000000..08b598d50
--- /dev/null
+++ b/src/extensions/age_restriction/age_restriction.c
@@ -0,0 +1,256 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021-2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 age_restriction.c
+ * @brief Utility functions regarding age restriction
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_extensions.h"
+#include "stdint.h"
+
+/* ==================================================
+ *
+ * Age Restriction TALER_Extension implementation
+ *
+ * ==================================================
+ */
+
+/**
+ * @brief local configuration
+ */
+
+static struct TALER_AgeRestrictionConfig AR_config = {0};
+
+/**
+ * @brief implements the TALER_Extension.disable interface.
+ *
+ * @param ext Pointer to the current extension
+ */
+static void
+age_restriction_disable (
+ struct TALER_Extension *ext)
+{
+ if (NULL == ext)
+ return;
+
+ ext->enabled = false;
+ ext->config = NULL;
+
+ AR_config.mask.bits = 0;
+ AR_config.num_groups = 0;
+}
+
+
+/**
+ * @brief implements the TALER_Extension.load_config interface.
+ *
+ * @param ext if NULL, only tests the configuration
+ * @param jconfig the configuration as json
+ */
+static enum GNUNET_GenericReturnValue
+age_restriction_load_config (
+ const json_t *jconfig,
+ struct TALER_Extension *ext)
+{
+ struct TALER_AgeMask mask = {0};
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = TALER_JSON_parse_age_groups (jconfig, &mask);
+ if (GNUNET_OK != ret)
+ return ret;
+
+ /* only testing the parser */
+ if (ext == NULL)
+ return GNUNET_OK;
+
+ if (TALER_Extension_AgeRestriction != ext->type)
+ return GNUNET_SYSERR;
+
+ if (mask.bits > 0)
+ {
+ /* if the mask is not zero, the first bit MUST be set */
+ if (0 == (mask.bits & 1))
+ return GNUNET_SYSERR;
+
+ AR_config.mask.bits = mask.bits;
+ AR_config.num_groups = __builtin_popcount (mask.bits) - 1;
+ }
+
+ ext->config = &AR_config;
+ ext->enabled = true;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "loaded new age restriction config with age groups: %s\n",
+ TALER_age_mask_to_string (&mask));
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * @brief implements the TALER_Extension.manifest interface.
+ *
+ * @param ext if NULL, only tests the configuration
+ * @return configuration as json_t* object, maybe NULL
+ */
+static json_t *
+age_restriction_manifest (
+ const struct TALER_Extension *ext)
+{
+ json_t *conf;
+
+ GNUNET_assert (NULL != ext);
+
+ if (NULL == ext->config)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "age restriction not configured");
+ return json_null ();
+ }
+
+ conf = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("age_groups",
+ TALER_age_mask_to_string (&AR_config.mask))
+ );
+ return GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_bool ("critical",
+ ext->critical),
+ GNUNET_JSON_pack_string ("version",
+ ext->version),
+ GNUNET_JSON_pack_object_steal ("config",
+ conf)
+ );
+}
+
+
+/* The extension for age restriction */
+struct TALER_Extension TE_age_restriction = {
+ .type = TALER_Extension_AgeRestriction,
+ .name = "age_restriction",
+ .critical = false,
+ .version = "1",
+ .enabled = false, /* disabled per default */
+ .config = NULL,
+ .disable = &age_restriction_disable,
+ .load_config = &age_restriction_load_config,
+ .manifest = &age_restriction_manifest,
+
+ /* This extension is not a policy extension */
+ .create_policy_details = NULL,
+ .policy_get_handler = NULL,
+ .policy_post_handler = NULL,
+};
+
+
+/**
+ * @brief implements the init() function for GNUNET_PLUGIN_load
+ *
+ * @param arg Pointer to the GNUNET_CONFIGURATION_Handle
+ * @return pointer to TALER_Extension on success or NULL otherwise.
+ */
+void *
+libtaler_extension_age_restriction_init (void *arg)
+{
+ const struct GNUNET_CONFIGURATION_Handle *cfg = arg;
+ char *groups = NULL;
+ struct TALER_AgeMask mask = {0};
+
+ if ((GNUNET_YES !=
+ GNUNET_CONFIGURATION_have_value (cfg,
+ TALER_EXTENSION_SECTION_AGE_RESTRICTION,
+ "ENABLED"))
+ ||
+ (GNUNET_YES !=
+ GNUNET_CONFIGURATION_get_value_yesno (cfg,
+ TALER_EXTENSION_SECTION_AGE_RESTRICTION,
+ "ENABLED")))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "[age restriction] no section %s found in configuration\n",
+ TALER_EXTENSION_SECTION_AGE_RESTRICTION);
+
+ return NULL;
+ }
+
+ /* Age restriction is enabled, extract age groups */
+ if ((GNUNET_YES ==
+ GNUNET_CONFIGURATION_have_value (cfg,
+ TALER_EXTENSION_SECTION_AGE_RESTRICTION,
+ "AGE_GROUPS"))
+ &&
+ (GNUNET_YES !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ TALER_EXTENSION_SECTION_AGE_RESTRICTION,
+ "AGE_GROUPS",
+ &groups)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "[age restriction] AGE_GROUPS in %s is not a string\n",
+ TALER_EXTENSION_SECTION_AGE_RESTRICTION);
+
+ return NULL;
+ }
+
+ if (NULL == groups)
+ groups = GNUNET_strdup (TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_GROUPS);
+
+ if (GNUNET_OK != TALER_parse_age_group_string (groups, &mask))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "[age restriction] couldn't parse age groups: '%s'\n",
+ groups);
+ return NULL;
+ }
+
+ AR_config.mask = mask;
+ AR_config.num_groups = __builtin_popcount (mask.bits) - 1; /* no underflow, first bit always set */
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "[age restriction] setting age mask to %s with #groups: %d\n",
+ TALER_age_mask_to_string (&AR_config.mask),
+ __builtin_popcount (AR_config.mask.bits) - 1);
+
+ TE_age_restriction.config = &AR_config;
+
+ /* Note: we do now have TE_age_restriction_config set, however the extension
+ * is not yet enabled! For age restriction to become active, load_config must
+ * have been called. */
+
+ GNUNET_free (groups);
+ return &TE_age_restriction;
+}
+
+
+/**
+ * @brief implements the done() function for GNUNET_PLUGIN_load
+ *
+ * @param arg unused
+ * @return pointer to TALER_Extension on success or NULL otherwise.
+ */
+void *
+libtaler_extension_age_restriction_done (void *arg)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "[age restriction] disabling and unloading");
+ AR_config.mask.bits = 0;
+ AR_config.num_groups = 0;
+ return NULL;
+}
+
+
+/* end of age_restriction.c */
diff --git a/src/extensions/age_restriction_helper.c b/src/extensions/age_restriction_helper.c
new file mode 100644
index 000000000..8ba835117
--- /dev/null
+++ b/src/extensions/age_restriction_helper.c
@@ -0,0 +1,73 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file age_restriction_helper.c
+ * @brief Helper functions for age restriction
+ * @author Özgür Kesim
+ */
+
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_extensions.h"
+#include "stdint.h"
+
+
+const struct TALER_AgeRestrictionConfig *
+TALER_extensions_get_age_restriction_config ()
+{
+ const struct TALER_Extension *ext;
+
+ ext = TALER_extensions_get_by_type (TALER_Extension_AgeRestriction);
+ if (NULL == ext)
+ return NULL;
+
+ return ext->config;
+}
+
+
+bool
+TALER_extensions_is_age_restriction_enabled ()
+{
+ const struct TALER_Extension *ext;
+
+ ext = TALER_extensions_get_by_type (TALER_Extension_AgeRestriction);
+ if (NULL == ext)
+ return false;
+
+ return ext->enabled;
+}
+
+
+struct TALER_AgeMask
+TALER_extensions_get_age_restriction_mask ()
+{
+ const struct TALER_Extension *ext;
+ const struct TALER_AgeRestrictionConfig *conf;
+
+ ext = TALER_extensions_get_by_type (TALER_Extension_AgeRestriction);
+
+ if ((NULL == ext) ||
+ (NULL == ext->config))
+ return (struct TALER_AgeMask) {0}
+ ;
+
+ conf = ext->config;
+ return conf->mask;
+}
+
+
+/* end age_restriction_helper.c */
diff --git a/src/extensions/extensions.c b/src/extensions/extensions.c
new file mode 100644
index 000000000..999e9317a
--- /dev/null
+++ b/src/extensions/extensions.c
@@ -0,0 +1,452 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021-2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 extensions.c
+ * @brief Utility functions for extensions
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_extensions_policy.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_extensions.h"
+#include "stdint.h"
+
+/* head of the list of all registered extensions */
+static struct TALER_Extensions TE_extensions = {
+ .next = NULL,
+ .extension = NULL,
+};
+
+const struct TALER_Extensions *
+TALER_extensions_get_head ()
+{
+ return &TE_extensions;
+}
+
+
+static enum GNUNET_GenericReturnValue
+add_extension (
+ const struct TALER_Extension *extension)
+{
+ /* Sanity checks */
+ if ((NULL == extension) ||
+ (NULL == extension->name) ||
+ (NULL == extension->version) ||
+ (NULL == extension->disable) ||
+ (NULL == extension->load_config) ||
+ (NULL == extension->manifest))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "invalid extension\n");
+ return GNUNET_SYSERR;
+ }
+
+ if (NULL == TE_extensions.extension) /* first extension ?*/
+ TE_extensions.extension = extension;
+ else
+ {
+ struct TALER_Extensions *iter;
+ struct TALER_Extensions *last;
+
+ /* Check for collisions */
+ for (iter = &TE_extensions;
+ NULL != iter && NULL != iter->extension;
+ iter = iter->next)
+ {
+ const struct TALER_Extension *ext = iter->extension;
+ last = iter;
+ if (extension->type == ext->type ||
+ 0 == strcasecmp (extension->name,
+ ext->name))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "extension collision for `%s'\n",
+ extension->name);
+ return GNUNET_NO;
+ }
+ }
+
+ /* No collisions found, so add this extension to the list */
+ {
+ struct TALER_Extensions *extn = GNUNET_new (struct TALER_Extensions);
+ extn->extension = extension;
+ last->next = extn;
+ }
+ }
+
+ return GNUNET_OK;
+}
+
+
+const struct TALER_Extension *
+TALER_extensions_get_by_type (
+ enum TALER_Extension_Type type)
+{
+ for (const struct TALER_Extensions *it = &TE_extensions;
+ NULL != it && NULL != it->extension;
+ it = it->next)
+ {
+ if (it->extension->type == type)
+ return it->extension;
+ }
+
+ /* No extension found. */
+ return NULL;
+}
+
+
+bool
+TALER_extensions_is_enabled_type (
+ enum TALER_Extension_Type type)
+{
+ const struct TALER_Extension *ext =
+ TALER_extensions_get_by_type (type);
+
+ return (NULL != ext && ext->enabled);
+}
+
+
+const struct TALER_Extension *
+TALER_extensions_get_by_name (
+ const char *name)
+{
+ for (const struct TALER_Extensions *it = &TE_extensions;
+ NULL != it;
+ it = it->next)
+ {
+ if (0 == strcasecmp (name, it->extension->name))
+ return it->extension;
+ }
+ /* No extension found, try to load it. */
+
+ return NULL;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_extensions_verify_manifests_signature (
+ const json_t *manifests,
+ struct TALER_MasterSignatureP *extensions_sig,
+ struct TALER_MasterPublicKeyP *master_pub)
+{
+ struct TALER_ExtensionManifestsHashP h_manifests;
+
+ if (GNUNET_OK !=
+ TALER_JSON_extensions_manifests_hash (manifests,
+ &h_manifests))
+ return GNUNET_SYSERR;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_extension_manifests_hash_verify (
+ &h_manifests,
+ master_pub,
+ extensions_sig))
+ return GNUNET_NO;
+ return GNUNET_OK;
+}
+
+
+/*
+ * Closure used in TALER_extensions_load_taler_config during call to
+ * GNUNET_CONFIGURATION_iterate_sections with configure_extension.
+ */
+struct LoadConfClosure
+{
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+ enum GNUNET_GenericReturnValue error;
+};
+
+
+/*
+ * Used in TALER_extensions_load_taler_config during call to
+ * GNUNET_CONFIGURATION_iterate_sections to load the configuration
+ * of supported extensions.
+ *
+ * @param cls Closure of type LoadConfClosure
+ * @param section name of the current section
+ */
+static void
+configure_extension (
+ void *cls,
+ const char *section)
+{
+ struct LoadConfClosure *col = cls;
+ const char *name;
+ char lib_name[1024] = {0};
+ struct TALER_Extension *extension;
+
+ if (GNUNET_OK != col->error)
+ return;
+
+ if (0 != strncasecmp (section,
+ TALER_EXTENSION_SECTION_PREFIX,
+ sizeof(TALER_EXTENSION_SECTION_PREFIX) - 1))
+ return;
+
+ name = section + sizeof(TALER_EXTENSION_SECTION_PREFIX) - 1;
+
+
+ /* Load the extension library */
+ GNUNET_snprintf (lib_name,
+ sizeof(lib_name),
+ "libtaler_extension_%s",
+ name);
+ /* Lower-case extension name, config is case-insensitive */
+ for (unsigned int i = 0; i < strlen (lib_name); i++)
+ lib_name[i] = tolower (lib_name[i]);
+
+ extension = GNUNET_PLUGIN_load (lib_name,
+ (void *) col->cfg);
+ if (NULL == extension)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Couldn't load extension library to `%s` (section [%s]).\n",
+ name,
+ section);
+ col->error = GNUNET_SYSERR;
+ return;
+ }
+
+
+ if (GNUNET_OK != add_extension (extension))
+ {
+ /* TODO: Ignoring return values here */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Couldn't add extension `%s` (section [%s]).\n",
+ name,
+ section);
+ col->error = GNUNET_SYSERR;
+ GNUNET_PLUGIN_unload (
+ lib_name,
+ (void *) col->cfg);
+ return;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "extension library '%s' loaded\n",
+ lib_name);
+}
+
+
+static bool extensions_loaded = false;
+
+enum GNUNET_GenericReturnValue
+TALER_extensions_init (
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ struct LoadConfClosure col = {
+ .cfg = cfg,
+ .error = GNUNET_OK,
+ };
+
+ if (extensions_loaded)
+ return GNUNET_OK;
+
+ GNUNET_CONFIGURATION_iterate_sections (cfg,
+ &configure_extension,
+ &col);
+
+ if (GNUNET_OK == col.error)
+ extensions_loaded = true;
+
+ return col.error;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_extensions_parse_manifest (
+ json_t *obj,
+ int *critical,
+ const char **version,
+ json_t **config)
+{
+ enum GNUNET_GenericReturnValue ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_boolean ("critical",
+ critical),
+ GNUNET_JSON_spec_string ("version",
+ version),
+ GNUNET_JSON_spec_json ("config",
+ config),
+ GNUNET_JSON_spec_end ()
+ };
+
+ *config = NULL;
+ if (GNUNET_OK !=
+ (ret = GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL)))
+ return ret;
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_extensions_load_manifests (
+ const json_t *extensions)
+{
+ const char *name;
+ json_t *manifest;
+
+ GNUNET_assert (NULL != extensions);
+ GNUNET_assert (json_is_object (extensions));
+
+ json_object_foreach ((json_t *) extensions, name, manifest)
+ {
+ int critical;
+ const char *version;
+ json_t *config;
+ struct TALER_Extension *extension
+ = (struct TALER_Extension *)
+ TALER_extensions_get_by_name (name);
+
+ if (NULL == extension)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "no such extension: %s\n",
+ name);
+ return GNUNET_SYSERR;
+ }
+
+ /* load and verify criticality, version, etc. */
+ if (GNUNET_OK !=
+ TALER_extensions_parse_manifest (
+ manifest,
+ &critical,
+ &version,
+ &config))
+ return GNUNET_SYSERR;
+
+ if (critical != extension->critical
+ || 0 != strcmp (version,
+ extension->version) // TODO: libtool compare?
+ || NULL == config
+ || (GNUNET_OK !=
+ extension->load_config (config,
+ NULL)) )
+ return GNUNET_SYSERR;
+
+ /* This _should_ work now */
+ if (GNUNET_OK !=
+ extension->load_config (config,
+ extension))
+ return GNUNET_SYSERR;
+
+ extension->enabled = true;
+ }
+
+ /* make sure to disable all extensions that weren't mentioned in the json */
+ for (const struct TALER_Extensions *it = TALER_extensions_get_head ();
+ NULL != it;
+ it = it->next)
+ {
+ if (NULL == json_object_get (extensions, it->extension->name))
+ it->extension->disable ((struct TALER_Extension *) it);
+ }
+
+ return GNUNET_OK;
+}
+
+
+/*
+ * Policy related
+ */
+
+static char *fulfillment2str[] = {
+ [TALER_PolicyFulfillmentInitial] = "<init>",
+ [TALER_PolicyFulfillmentReady] = "Ready",
+ [TALER_PolicyFulfillmentSuccess] = "Success",
+ [TALER_PolicyFulfillmentFailure] = "Failure",
+ [TALER_PolicyFulfillmentTimeout] = "Timeout",
+ [TALER_PolicyFulfillmentInsufficient] = "Insufficient",
+};
+
+const char *
+TALER_policy_fulfillment_state_str (
+ enum TALER_PolicyFulfillmentState state)
+{
+ GNUNET_assert (TALER_PolicyFulfillmentStateCount > state);
+ return fulfillment2str[state];
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_extensions_create_policy_details (
+ const char *currency,
+ const json_t *policy_options,
+ struct TALER_PolicyDetails *details,
+ const char **error_hint)
+{
+ enum GNUNET_GenericReturnValue ret;
+ const struct TALER_Extension *extension;
+ const json_t *jtype;
+ const char *type;
+
+ *error_hint = NULL;
+
+ if ((NULL == policy_options) ||
+ (! json_is_object (policy_options)))
+ {
+ *error_hint = "invalid policy object";
+ return GNUNET_SYSERR;
+ }
+
+ jtype = json_object_get (policy_options, "type");
+ if (NULL == jtype)
+ {
+ *error_hint = "no type in policy object";
+ return GNUNET_SYSERR;
+ }
+
+ type = json_string_value (jtype);
+ if (NULL == type)
+ {
+ *error_hint = "invalid type in policy object";
+ return GNUNET_SYSERR;
+ }
+
+ extension = TALER_extensions_get_by_name (type);
+ if ((NULL == extension) ||
+ (NULL == extension->create_policy_details))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unsupported extension policy '%s' requested\n",
+ type);
+ return GNUNET_NO;
+ }
+
+ /* Set state fields in the policy details to initial values. */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (currency,
+ &details->accumulated_total));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (currency,
+ &details->policy_fee));
+ details->deadline = GNUNET_TIME_UNIT_FOREVER_TS;
+ details->fulfillment_state = TALER_PolicyFulfillmentInitial;
+ details->no_policy_fulfillment_id = true;
+ ret = extension->create_policy_details (currency,
+ policy_options,
+ details,
+ error_hint);
+ return ret;
+
+}
+
+
+/* end of extensions.c */
diff --git a/src/include/.gitignore b/src/include/.gitignore
new file mode 100644
index 000000000..beba883b6
--- /dev/null
+++ b/src/include/.gitignore
@@ -0,0 +1 @@
+taler_signatures.h
diff --git a/src/include/Makefile.am b/src/include/Makefile.am
index b8ea0d175..45d74ab8e 100644
--- a/src/include/Makefile.am
+++ b/src/include/Makefile.am
@@ -2,25 +2,34 @@
talerincludedir = $(includedir)/taler
talerinclude_HEADERS = \
- platform.h \
+ platform.h gettext.h \
taler_auditor_service.h \
taler_amount_lib.h \
+ taler_attributes.h \
taler_auditordb_lib.h \
taler_auditordb_plugin.h \
taler_bank_service.h \
taler_crypto_lib.h \
taler_curl_lib.h \
+ taler_dbevents.h \
taler_error_codes.h \
taler_exchange_service.h \
taler_exchangedb_lib.h \
taler_exchangedb_plugin.h \
+ taler_extensions.h \
+ taler_extensions_policy.h \
taler_fakebank_lib.h \
+ taler_kyclogic_lib.h \
+ taler_kyclogic_plugin.h \
taler_json_lib.h \
taler_testing_lib.h \
taler_util.h \
taler_mhd_lib.h \
taler_pq_lib.h \
- taler_signatures.h
+ taler_signatures.h \
+ taler_sq_lib.h \
+ taler_templating_lib.h \
+ taler_twister_testing_lib.h
EXTRA_DIST = \
backoff.h \
diff --git a/src/include/gettext.h b/src/include/gettext.h
new file mode 100644
index 000000000..458512657
--- /dev/null
+++ b/src/include/gettext.h
@@ -0,0 +1,71 @@
+/* Convenience header for conditional use of GNU <libintl.h>.
+ Copyright Copyright (C) 1995-1998, 2000-2002 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published
+ by the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA. */
+
+#ifndef _LIBGETTEXT_H
+#define _LIBGETTEXT_H 1
+
+/* NLS can be disabled through the configure --disable-nls option. */
+#if ENABLE_NLS
+
+/* Get declarations of GNU message catalog functions. */
+#include <libintl.h>
+
+#else
+
+/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which
+ chokes if dcgettext is defined as a macro. So include it now, to make
+ later inclusions of <locale.h> a NOP. We don't include <libintl.h>
+ as well because people using "gettext.h" will not include <libintl.h>,
+ and also including <libintl.h> would fail on SunOS 4, whereas <locale.h>
+ is GNUNET_OK. */
+#if defined(__sun)
+#include <locale.h>
+#endif
+
+/* Disabled NLS.
+ The casts to 'const char *' serve the purpose of producing warnings
+ for invalid uses of the value returned from these functions.
+ On pre-ANSI systems without 'const', the config.h file is supposed to
+ contain "#define const". */
+#define gettext(Msgid) ((const char *) (Msgid))
+#define dgettext(Domainname, Msgid) ((const char *) (Msgid))
+#define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid))
+#define ngettext(Msgid1, Msgid2, N) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+#define dngettext(Domainname, Msgid1, Msgid2, N) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+#define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+/* slight modification here to avoid warnings: generate GNUNET_NO code,
+ not even the cast... */
+#define textdomain(Domainname)
+#define bindtextdomain(Domainname, Dirname)
+#define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset))
+
+#endif
+
+/* A pseudo function call that serves as a marker for the automated
+ extraction of messages, but does not call gettext(). The run-time
+ translation is done at a different place in the code.
+ The argument, String, should be a literal string. Concatenated strings
+ and other string expressions won't work.
+ The macro's expansion is not parenthesized, so that it is suitable as
+ initializer for static 'char[]' or 'const char[]' variables. */
+#define gettext_noop(String) String
+
+#endif /* _LIBGETTEXT_H */
diff --git a/src/include/platform.h b/src/include/platform.h
index ec2f70768..db04cb972 100644
--- a/src/include/platform.h
+++ b/src/include/platform.h
@@ -20,18 +20,21 @@
* rest of the modules
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
*/
-
#ifndef PLATFORM_H_
#define PLATFORM_H_
/* Include our configuration header */
#ifndef HAVE_USED_CONFIG_H
-# define HAVE_USED_CONFIG_H
-# ifdef HAVE_CONFIG_H
-# include "taler_config.h"
-# endif
+#define HAVE_USED_CONFIG_H
+#ifdef HAVE_CONFIG_H
+#include "taler_config.h"
+#endif
#endif
+/* For the exchange build, we do NOT want gettext, even
+ if it is available! */
+#undef ENABLE_NLS
+
#if (GNUNET_EXTRA_LOGGING >= 1)
#define VERBOSE(cmd) cmd
@@ -42,9 +45,6 @@
/* Include the features available for GNU source */
#define _GNU_SOURCE
-/* Include GNUnet's platform file */
-#include <gnunet/platform.h>
-
/* Do not use shortcuts for gcrypt mpi */
#define GCRYPT_NO_MPI_MACROS 1
@@ -63,6 +63,237 @@
is needed for performance reasons. */
#define ENABLE_SANITY_CHECKS 1
+
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#if HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#if HAVE_NETINET_IN_SYSTM_H
+#include <netinet/in_systm.h>
+#endif
+#if HAVE_NETINET_IP_H
+#include <netinet/ip.h> /* superset of previous */
+#endif
+#include <arpa/inet.h>
+#include <netinet/tcp.h>
+#include <pwd.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <grp.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <signal.h>
+#include <libgen.h>
+#ifdef HAVE_MALLOC_H
+#include <malloc.h> /* for mallinfo on GNU */
+#endif
+#include <unistd.h> /* KLB_FIX */
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h> /* KLB_FIX */
+#include <fcntl.h>
+#include <math.h>
+#if HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#if HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <time.h>
+#ifdef BSD
+#include <net/if.h>
+#endif
+#if defined(BSD) && defined(__FreeBSD__) && defined(__FreeBSD_kernel__)
+#include <semaphore.h>
+#endif
+#ifdef DARWIN
+#include <dlfcn.h>
+#include <semaphore.h>
+#include <net/if.h>
+#endif
+#if defined(__linux__) || defined(GNU)
+#include <net/if.h>
+#endif
+#ifdef SOLARIS
+#include <sys/sockio.h>
+#include <sys/filio.h>
+#include <sys/loadavg.h>
+#include <semaphore.h>
+#endif
+#if HAVE_UCRED_H
+#include <ucred.h>
+#endif
+#if HAVE_SYS_UCRED_H
+#include <sys/ucred.h>
+#endif
+#if HAVE_IFADDRS_H
+#include <ifaddrs.h>
+#endif
+#include <errno.h>
+#include <limits.h>
+
+#if HAVE_VFORK_H
+#include <vfork.h>
+#endif
+
+#include <ctype.h>
+#if HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+#if HAVE_ENDIAN_H
+#include <endian.h>
+#endif
+#if HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#endif
+
+#define DIR_SEPARATOR '/'
+#define DIR_SEPARATOR_STR "/"
+#define PATH_SEPARATOR ':'
+#define PATH_SEPARATOR_STR ":"
+#define NEWLINE "\n"
+
+
+#include <locale.h>
+#include "gettext.h"
+/**
+ * GNU gettext support macro.
+ */
+#define _(String) dgettext (PACKAGE, String)
+
+
+#include <sys/mman.h>
+
+/* FreeBSD_kernel is not defined on the now discontinued kFreeBSD */
+#if defined(BSD) && defined(__FreeBSD__) && defined(__FreeBSD_kernel__)
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+#endif
+
+#ifdef DARWIN
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+/* not available on darwin, override configure */
+#undef HAVE_STAT64
+#undef HAVE_MREMAP
+#endif
+
+#if ! HAVE_ATOLL
+long long
+atoll (const char *nptr);
+
+#endif
+
+#if ENABLE_NLS
+#include "langinfo.h"
+#endif
+
+#ifndef SIZE_MAX
+#define SIZE_MAX ((size_t) (-1))
+#endif
+
+#ifndef O_LARGEFILE
+#define O_LARGEFILE 0
+#endif
+
+
+#if defined(__sparc__)
+#define MAKE_UNALIGNED(val) ({ __typeof__((val)) __tmp; memmove (&__tmp, &(val), \
+ sizeof((val))); \
+ __tmp; })
+#else
+#define MAKE_UNALIGNED(val) val
+#endif
+
+
+#ifndef PATH_MAX
+/**
+ * Assumed maximum path length.
+ */
+#define PATH_MAX 4096
+#endif
+
+#if HAVE_THREAD_LOCAL_GCC
+#define TALER_THREAD_LOCAL __thread
+#else
+#define TALER_THREAD_LOCAL
+#endif
+
+
+/* LSB-style exit status codes */
+#ifndef EXIT_INVALIDARGUMENT
+/**
+ * Command-line arguments are invalid.
+ * Restarting useless.
+ */
+#define EXIT_INVALIDARGUMENT 2
+#endif
+
+#ifndef EXIT_NOTIMPLEMENTED
+/**
+ * The requested operation is not implemented.
+ * Restarting useless.
+ */
+#define EXIT_NOTIMPLEMENTED 3
+#endif
+
+#ifndef EXIT_NOPERMISSION
+/**
+ * Permissions needed to run are not available.
+ * Restarting useless.
+ */
+#define EXIT_NOPERMISSION 4
+#endif
+
+#ifndef EXIT_NOTINSTALLED
+/**
+ * Key resources are not installed.
+ * Restarting useless.
+ */
+#define EXIT_NOTINSTALLED 5
+#endif
+
+#ifndef EXIT_NOTCONFIGURED
+/**
+ * Key configuration settings are missing or invalid.
+ * Restarting useless.
+ */
+#define EXIT_NOTCONFIGURED 6
+#endif
+
+#ifndef EXIT_NOTRUNNING
+#define EXIT_NOTRUNNING 7
+#endif
+
+
+#ifndef EXIT_NO_RESTART
+/**
+ * Exit code from 'main' if we do not want to be restarted,
+ * except by manual intervention (hard failure).
+ */
+#define EXIT_NO_RESTART 9
+#endif
+
+
+/**
+ * clang et al do not have such an attribute
+ */
+#if __has_attribute (__nonstring__)
+# define __nonstring __attribute__((__nonstring__))
+#else
+# define __nonstring
+#endif
+
+
#endif /* PLATFORM_H_ */
/* end of platform.h */
diff --git a/src/include/taler_amount_lib.h b/src/include/taler_amount_lib.h
index 3da2b851e..937238d15 100644
--- a/src/include/taler_amount_lib.h
+++ b/src/include/taler_amount_lib.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014, 2015 Taler Systems SA
+ Copyright (C) 2014, 2015, 2020 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
@@ -18,9 +18,16 @@
* @brief amount-representation utility functions
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
*/
+#if ! defined (__TALER_UTIL_LIB_H_INSIDE__)
+#error "Only <taler_util.h> can be included directly."
+#endif
+
#ifndef TALER_AMOUNT_LIB_H
#define TALER_AMOUNT_LIB_H
+#include <stdint.h>
+#include "gnunet/gnunet_common.h"
+
#ifdef __cplusplus
extern "C"
{
@@ -51,6 +58,9 @@ extern "C"
*
* Note that we need sub-cent precision here as transaction fees might
* be that low, and as we want to support microdonations.
+ *
+ * An actual `struct Amount a` thus represents
+ * "a.value + (a.fraction / #TALER_AMOUNT_FRAC_BASE)" units of "a.currency".
*/
#define TALER_AMOUNT_FRAC_BASE 100000000
@@ -61,6 +71,11 @@ extern "C"
*/
#define TALER_AMOUNT_FRAC_LEN 8
+/**
+ * Maximum legal 'value' for an amount, based on IEEE double (for JavaScript compatibility).
+ */
+#define TALER_AMOUNT_MAX_VALUE (1LLU << 52)
+
GNUNET_NETWORK_STRUCT_BEGIN
@@ -76,7 +91,7 @@ struct TALER_AmountNBO
uint64_t value GNUNET_PACKED;
/**
- * Additinal fractional value, in NBO.
+ * Fraction (integer multiples of #TALER_AMOUNT_FRAC_BASE), in NBO.
*/
uint32_t fraction GNUNET_PACKED;
@@ -100,7 +115,7 @@ struct TALER_Amount
uint64_t value;
/**
- * Fraction (denominator of fraction)
+ * Fraction (integer multiples of #TALER_AMOUNT_FRAC_BASE).
*/
uint32_t fraction;
@@ -113,6 +128,16 @@ struct TALER_Amount
/**
+ * Check that the currency code in @a str is well-formed.
+ *
+ * @param str currency code name to validate
+ * @return #GNUNET_OK if @a str is a valid currency code
+ */
+enum GNUNET_GenericReturnValue
+TALER_check_currency (const char *str);
+
+
+/**
* Parse monetary amount, in the format "T:V.F".
*
* @param str amount string
@@ -120,7 +145,7 @@ struct TALER_Amount
* @return #GNUNET_OK if the string is a valid monetary amount specification,
* #GNUNET_SYSERR if it is invalid.
*/
-int
+enum GNUNET_GenericReturnValue
TALER_string_to_amount (const char *str,
struct TALER_Amount *amount);
@@ -134,7 +159,7 @@ TALER_string_to_amount (const char *str,
* @return #GNUNET_OK if the string is a valid amount specification,
* #GNUNET_SYSERR if it is invalid.
*/
-int
+enum GNUNET_GenericReturnValue
TALER_string_to_amount_nbo (const char *str,
struct TALER_AmountNBO *amount_nbo);
@@ -147,22 +172,45 @@ TALER_string_to_amount_nbo (const char *str,
* @return #GNUNET_OK if @a cur is a valid currency specification,
* #GNUNET_SYSERR if it is invalid.
*/
-int
-TALER_amount_get_zero (const char *cur,
+enum GNUNET_GenericReturnValue
+TALER_amount_set_zero (const char *cur,
struct TALER_Amount *amount);
/**
+ * Test if the given @a amount is zero.
+ *
+ * @param amount amount to compare to zero
+ * @return true if the amount is zero,
+ * false if it is non-zero or invalid
+ */
+bool
+TALER_amount_is_zero (const struct TALER_Amount *amount);
+
+
+/**
* Test if the given amount is valid.
*
* @param amount amount to check
* @return #GNUNET_OK if @a amount is valid
*/
-int
+enum GNUNET_GenericReturnValue
TALER_amount_is_valid (const struct TALER_Amount *amount);
/**
+ * Test if the given amount is in the given currency
+ *
+ * @param amount amount to check
+ * @param currency currency to check for
+ * @return #GNUNET_OK if @a amount is in @a currency
+ */
+enum GNUNET_GenericReturnValue
+TALER_amount_is_currency (const struct TALER_Amount *amount,
+ const char *currency);
+
+
+/**
* Convert amount from host to network representation.
*
* @param[out] res where to store amount in network representation
@@ -203,6 +251,24 @@ TALER_amount_cmp (const struct TALER_Amount *a1,
/**
+ * Compare the value/fraction of two amounts. Does not compare the currency.
+ * Comparing amounts of different currencies will cause the program to abort().
+ * If unsure, check with #TALER_amount_cmp_currency() first to be sure that
+ * the currencies of the two amounts are identical. NBO variant.
+ *
+ * @param a1 first amount
+ * @param a2 second amount
+ * @return result of the comparison
+ * -1 if `a1 < a2`
+ * 1 if `a1 > a2`
+ * 0 if `a1 == a2`.
+ */
+int
+TALER_amount_cmp_nbo (const struct TALER_AmountNBO *a1,
+ const struct TALER_AmountNBO *a2);
+
+
+/**
* Test if @a a1 and @a a2 are the same currency.
*
* @param a1 amount to test
@@ -211,7 +277,7 @@ TALER_amount_cmp (const struct TALER_Amount *a1,
* #GNUNET_NO if the currencies are different
* #GNUNET_SYSERR if either amount is invalid
*/
-int
+enum GNUNET_GenericReturnValue
TALER_amount_cmp_currency (const struct TALER_Amount *a1,
const struct TALER_Amount *a2);
@@ -225,23 +291,60 @@ TALER_amount_cmp_currency (const struct TALER_Amount *a1,
* #GNUNET_NO if the currencies are different
* #GNUNET_SYSERR if either amount is invalid
*/
-int
+enum GNUNET_GenericReturnValue
TALER_amount_cmp_currency_nbo (const struct TALER_AmountNBO *a1,
const struct TALER_AmountNBO *a2);
/**
+ * Possible results from calling #TALER_amount_subtract() and
+ * possibly other arithmetic operations. Negative values
+ * indicate that the operation did not generate a result.
+ */
+enum TALER_AmountArithmeticResult
+{
+
+ /**
+ * Operation succeeded, result is positive.
+ */
+ TALER_AAR_RESULT_POSITIVE = 1,
+
+ /**
+ * Operation succeeded, result is exactly zero.
+ */
+ TALER_AAR_RESULT_ZERO = 0,
+
+ /**
+ * Operation failed, the result would have been negative.
+ */
+ TALER_AAR_INVALID_NEGATIVE_RESULT = -1,
+
+ /**
+ * Operation failed, result outside of the representable range.
+ */
+ TALER_AAR_INVALID_RESULT_OVERFLOW = -2,
+
+ /**
+ * Operation failed, inputs could not be normalized.
+ */
+ TALER_AAR_INVALID_NORMALIZATION_FAILED = -3,
+
+ /**
+ * Operation failed, input currencies were not identical.
+ */
+ TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE = -4
+
+};
+
+/**
* Perform saturating subtraction of amounts.
*
* @param[out] diff where to store (@a a1 - @a a2), or invalid if @a a2 > @a a1
* @param a1 amount to subtract from
* @param a2 amount to subtract
- * @return #GNUNET_OK if the subtraction worked,
- * #GNUNET_NO if @a a1 = @a a2
- * #GNUNET_SYSERR if @a a2 > @a a1 or currencies are incompatible;
- * @a diff is set to invalid
+ * @return operation status, negative on failures
*/
-int
+enum TALER_AmountArithmeticResult
TALER_amount_subtract (struct TALER_Amount *diff,
const struct TALER_Amount *a1,
const struct TALER_Amount *a2);
@@ -253,10 +356,9 @@ TALER_amount_subtract (struct TALER_Amount *diff,
* @param[out] sum where to store @a a1 + @a a2, set to "invalid" on overflow
* @param a1 first amount to add
* @param a2 second amount to add
- * @return #GNUNET_OK if the addition worked,
- * #GNUNET_SYSERR on overflow
+ * @return operation status, negative on failures
*/
-int
+enum TALER_AmountArithmeticResult
TALER_amount_add (struct TALER_Amount *sum,
const struct TALER_Amount *a1,
const struct TALER_Amount *a2);
@@ -275,6 +377,32 @@ TALER_amount_divide (struct TALER_Amount *result,
const struct TALER_Amount *dividend,
uint32_t divisor);
+/**
+ * Divide one amount by another. Note that this function
+ * may introduce a rounding error. It rounds down.
+ *
+ * @param dividend amount to divide
+ * @param divisor by what to divide, must be positive
+ * @return @a dividend / @a divisor, rounded down. -1 on currency mismatch,
+ * INT_MAX for division by zero
+ */
+int
+TALER_amount_divide2 (const struct TALER_Amount *dividend,
+ const struct TALER_Amount *divisor);
+
+
+/**
+ * Multiply an @a amount by a @ factor.
+ *
+ * @param[out] result where to store @a amount * @a factor
+ * @param amount amount to multiply
+ * @param factor factor by which to multiply
+ */
+enum TALER_AmountArithmeticResult
+TALER_amount_multiply (struct TALER_Amount *result,
+ const struct TALER_Amount *amount,
+ uint32_t factor);
+
/**
* Normalize the given amount.
@@ -284,7 +412,7 @@ TALER_amount_divide (struct TALER_Amount *result,
* #GNUNET_NO if value was already normalized
* #GNUNET_SYSERR if value was invalid or could not be normalized
*/
-int
+enum GNUNET_GenericReturnValue
TALER_amount_normalize (struct TALER_Amount *amount);
@@ -322,7 +450,7 @@ TALER_amount2s (const struct TALER_Amount *amount);
* @return #GNUNET_OK on success, #GNUNET_NO if rounding was unnecessary,
* #GNUNET_SYSERR if the amount or currency or @a round_unit was invalid
*/
-int
+enum GNUNET_GenericReturnValue
TALER_amount_round_down (struct TALER_Amount *amount,
const struct TALER_Amount *round_unit);
diff --git a/src/include/taler_attributes.h b/src/include/taler_attributes.h
new file mode 100644
index 000000000..862a26928
--- /dev/null
+++ b/src/include/taler_attributes.h
@@ -0,0 +1,129 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2023 Taler Systems SA
+
+ GNU Taler is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNU 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: LGPL3.0-or-later
+
+ Note: the LGPL does not apply to all components of GNU Taler,
+ but it does apply to this file.
+ */
+/**
+ * @file src/include/taler_attributes.h
+ * @brief GNU Taler database event types, TO BE generated via https://gana.gnunet.org/
+ */
+#ifndef GNU_TALER_ATTRIBUTES_H
+#define GNU_TALER_ATTRIBUTES_H
+
+#ifdef __cplusplus
+extern "C" {
+#if 0 /* keep Emacsens' auto-indent happy */
+}
+#endif
+#endif
+
+/**
+ * Legal name of the business/company.
+ */
+#define TALER_ATTRIBUTE_COMPANY_NAME "company_name"
+
+/**
+ * Legal country of registration of the business/company,
+ * 2-letter country code using ISO 3166-2.
+ */
+#define TALER_ATTRIBUTE_REGISTRATION_COUNTRY "registration_country"
+
+/**
+ * Full name, when known/possible using "Lastname, Firstname(s)" format,
+ * but "Firstname(s) Lastname" or "Firstname M. Lastname" should also be
+ * tolerated (as is "Name", especially if the person only has one name).
+ * If the person has no name, an empty string must be given.
+ * NULL for not collected.
+ */
+#define TALER_ATTRIBUTE_FULL_NAME "full_name"
+
+/**
+ * True/false indicator if the individual is a politically
+ * exposed person.
+ */
+#define TALER_ATTRIBUTE_PEP "pep"
+
+/**
+ * Street-level address. Usually includes the street and the house number. May
+ * consist of multiple lines (separated by '\n'). Identifies a house in a city. The city is not
+ * part of the street.
+ */
+#define TALER_ATTRIBUTE_ADDRESS_STREET "street"
+
+/**
+ * City including postal code. If available, a 2-letter country-code prefixes
+ * the postal code, which is before the city (e.g. "DE-42289 Wuppertal"). If
+ * the country code is unknown, the "CC-" prefix is missing. If the ZIP code
+ * is unknown, the hyphen is followed by a space ("DE- Wuppertal"). If only
+ * the city name is known, it is prefixed by a space (" ").
+ * If the city name is unknown, a space is at the end of the value.
+ */
+#define TALER_ATTRIBUTE_ADDRESS_CITY "city"
+
+/**
+ * Phone number (of business or individual). Should come with the "+CC"
+ * prefix including the country code.
+ */
+#define TALER_ATTRIBUTE_PHONE "phone"
+
+/**
+ * Email address (of business or individual). Should be
+ * in the format "user@hostname".
+ */
+#define TALER_ATTRIBUTE_EMAIL "email"
+
+/**
+ * Birthdate of the person, as far as known. YYYY-MM-DD, a value
+ * of 0 (for DD, MM or even YYYY) is to be used for 'unknown'
+ * according to official records.
+ * Thus, 1950-00-00 stands for a birthdate in 1950 with unknown
+ * day and month. If official documents record January 1st or
+ * some other date instead, that day may also be specified.
+ * NULL for not collected.
+ */
+#define TALER_ATTRIBUTE_BIRTHDATE "birthdate"
+
+/**
+ * Citizenship(s) of the person using 2-letter country codes ("US", "DE",
+ * "FR", "IT", etc.) separated by commas if multiple citizenships are
+ * confirmed ("EN,US,DE"). Note that in the latter case it is not guaranteed
+ * that all nationalities were necessarily recorded. Empty string for
+ * stateless persons. NULL for not collected.
+ */
+#define TALER_ATTRIBUTE_NATIONALITIES "nationalities"
+
+/**
+ * Residence countries(s) of the person using 2-letter country codes ("US",
+ * "DE", "FR", "IT", etc.) separated by commas if multiple residences are
+ * confirmed ("EN,US,DE"). Note that in the latter case it is not guaranteed
+ * that all residences were necessarily recorded. Empty string for
+ * international nomads. NULL for not collected.
+ */
+#define TALER_ATTRIBUTE_RESIDENCES "residences"
+
+
+#if 0 /* keep Emacsens' auto-indent happy */
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/include/taler_auditor_service.h b/src/include/taler_auditor_service.h
index 0f69da11b..36ea781b1 100644
--- a/src/include/taler_auditor_service.h
+++ b/src/include/taler_auditor_service.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 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
@@ -16,6 +16,8 @@
/**
* @file include/taler_auditor_service.h
* @brief C interface of libtalerauditor, a C library to use auditor's HTTP API
+ * This library is not thread-safe, all APIs must only be used from a single thread.
+ * This library calls abort() if it runs out of memory. Be aware of these limitations.
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
* @author Christian Grothoff
*/
@@ -28,12 +30,12 @@
#include <gnunet/gnunet_curl_lib.h>
-/* ********************* /version *********************** */
+/* ********************* /config *********************** */
/**
- * @brief Information we get from the auditor about auditors.
+ * @brief Information we get from the auditor about itself.
*/
-struct TALER_AUDITOR_VersionInformation
+struct TALER_AUDITOR_ConfigInformation
{
/**
* Public key of the auditing institution. Wallets and merchants
@@ -44,12 +46,17 @@ struct TALER_AUDITOR_VersionInformation
struct TALER_AuditorPublicKeyP auditor_pub;
/**
+ * Master public key of the audited exchange.
+ */
+ struct TALER_MasterPublicKeyP exchange_master_public_key;
+
+ /**
* Supported Taler protocol version by the auditor.
* String in the format current:revision:age using the
* semantics of GNU libtool. See
* https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
*/
- char *version;
+ const char *version;
};
@@ -107,55 +114,130 @@ enum TALER_AUDITOR_VersionCompatibility
/**
+ * General information about the HTTP response we obtained
+ * from the auditor for a request.
+ */
+struct TALER_AUDITOR_HttpResponse
+{
+
+ /**
+ * The complete JSON reply. NULL if we failed to parse the
+ * reply (too big, invalid JSON).
+ */
+ const json_t *reply;
+
+ /**
+ * Set to the human-readable 'hint' that is optionally
+ * provided by the exchange together with errors. NULL
+ * if no hint was provided or if there was no error.
+ */
+ const char *hint;
+
+ /**
+ * HTTP status code for the response. 0 if the
+ * HTTP request failed and we did not get any answer, or
+ * if the answer was invalid and we set @a ec to a
+ * client-side error code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Taler error code. #TALER_EC_NONE if everything was
+ * OK. Usually set to the "code" field of an error
+ * response, but may be set to values created at the
+ * client side, for example when the response was
+ * not in JSON format or was otherwise ill-formed.
+ */
+ enum TALER_ErrorCode ec;
+
+};
+
+
+/**
+ * Response to /config request.
+ */
+struct TALER_AUDITOR_ConfigResponse
+{
+ /**
+ * HTTP response.
+ */
+ struct TALER_AUDITOR_HttpResponse hr;
+
+ /**
+ * Details depending on HTTP status.
+ */
+ union
+ {
+
+ /**
+ * Details for #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * Protocol compatibility evaluation.
+ */
+ enum TALER_AUDITOR_VersionCompatibility compat;
+
+ /**
+ * Config data returned by /config.
+ */
+ struct TALER_AUDITOR_ConfigInformation vi;
+
+ } ok;
+
+ } details;
+
+};
+
+
+/**
* Function called with information about the auditor.
*
* @param cls closure
- * @param vi basic information about the auditor
- * @param compat protocol compatibility information
+ * @param vr response data
*/
typedef void
-(*TALER_AUDITOR_VersionCallback) (
+(*TALER_AUDITOR_ConfigCallback) (
void *cls,
- const struct
- TALER_AUDITOR_VersionInformation *vi,
- enum TALER_AUDITOR_VersionCompatibility compat);
+ const struct TALER_AUDITOR_ConfigResponse *vr);
/**
* @brief Handle to the auditor. This is where we interact with
* a particular auditor and keep the per-auditor information.
*/
-struct TALER_AUDITOR_Handle;
+struct TALER_AUDITOR_GetConfigHandle;
/**
- * Initialise a connection to the auditor. Will connect to the
+ * Obtain meta data about an auditor. Will connect to the
* auditor and obtain information about the auditor's master public
* key and the auditor's auditor. The respective information will
- * be passed to the @a version_cb once available, and all future
- * interactions with the auditor will be checked to be signed
- * (where appropriate) by the respective master key.
+ * be passed to the @a config_cb once available.
*
- * @param ctx the context
+ * @param ctx the context for CURL requests
* @param url HTTP base URL for the auditor
- * @param version_cb function to call with the auditor's version information
- * @param version_cb_cls closure for @a version_cb
+ * @param config_cb function to call with the auditor's config information
+ * @param config_cb_cls closure for @a config_cb
* @return the auditor handle; NULL upon error
*/
-struct TALER_AUDITOR_Handle *
-TALER_AUDITOR_connect (struct GNUNET_CURL_Context *ctx,
- const char *url,
- TALER_AUDITOR_VersionCallback version_cb,
- void *version_cb_cls);
+struct TALER_AUDITOR_GetConfigHandle *
+TALER_AUDITOR_get_config (struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ TALER_AUDITOR_ConfigCallback config_cb,
+ void *config_cb_cls);
/**
- * Disconnect from the auditor.
+ * Cancel auditor config request.
*
- * @param auditor the auditor handle
+ * @param[in] auditor the auditor handle
*/
void
-TALER_AUDITOR_disconnect (struct TALER_AUDITOR_Handle *auditor);
+TALER_AUDITOR_get_config_cancel (
+ struct TALER_AUDITOR_GetConfigHandle *auditor);
/**
@@ -165,19 +247,28 @@ struct TALER_AUDITOR_DepositConfirmationHandle;
/**
+ * Response to /deposit-confirmation request.
+ */
+struct TALER_AUDITOR_DepositConfirmationResponse
+{
+ /**
+ * HTTP response.
+ */
+ struct TALER_AUDITOR_HttpResponse hr;
+};
+
+
+/**
* Signature of functions called with the result from our call to the
* auditor's /deposit-confirmation handler.
*
* @param cls closure
- * @param http_status HTTP status code, 200 on success
- * @param ec taler protocol error status code, 0 on success
- * @param json raw json response
+ * @param dcr response data
*/
typedef void
-(*TALER_AUDITOR_DepositConfirmationResultCallback)(void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const json_t *json);
+(*TALER_AUDITOR_DepositConfirmationResultCallback)(
+ void *cls,
+ const struct TALER_AUDITOR_DepositConfirmationResponse *dcr);
/**
@@ -187,19 +278,23 @@ typedef void
* that the response is well-formed. If the auditor's reply is not
* well-formed, we return an HTTP status code of zero to @a cb.
*
- * We also verify that the @a exchange_sig is valid for this deposit-confirmation
- * request, and that the @a master_sig is a valid signature for @a
- * exchange_pub. Also, the @a auditor must be ready to operate (i.e. have
- * finished processing the /version reply). If either check fails, we do
- * NOT initiate the transaction with the auditor and instead return NULL.
+ * We also verify that the @a exchange_sig is valid for this
+ * deposit-confirmation request, and that the @a master_sig is a valid
+ * signature for @a exchange_pub. If the check fails, we do NOT initiate the
+ * transaction with the auditor and instead return NULL.
*
- * @param auditor the auditor handle; the auditor must be ready to operate
+ * @param ctx the context for CURL requests
+ * @param url HTTP base URL for the auditor
* @param h_wire hash of merchant wire details
+ * @param h_policy hash over the policy, if any
* @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor)
- * @param timestamp timestamp when the contract was finalized, must not be too far in the future
+ * @param exchange_timestamp timestamp when the contract was finalized, must not be too far in the future
+ * @param wire_deadline date until which the exchange should wire the funds
* @param refund_deadline date until which the merchant can issue a refund to the customer via the auditor (can be zero if refunds are not allowed); must not be after the @a wire_deadline
- * @param amount_without_fee the amount confirmed to be wired by the exchange to the merchant
- * @param coin_pub coin’s public key
+ * @param total_without_fee the amount confirmed to be wired by the exchange to the merchant
+ * @param num_coins number of coins involved in the batch deposit
+ * @param coin_pubs array of the coin’s public keys
+ * @param coin_sigs array of the original deposit signatures of the coins in the batch
* @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
* @param exchange_sig the signature made with purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT
* @param exchange_pub the public key of the exchange that matches @a exchange_sig
@@ -215,20 +310,25 @@ typedef void
*/
struct TALER_AUDITOR_DepositConfirmationHandle *
TALER_AUDITOR_deposit_confirmation (
- struct TALER_AUDITOR_Handle *auditor,
- const struct GNUNET_HashCode *h_wire,
- const struct GNUNET_HashCode *h_contract_terms,
- struct GNUNET_TIME_Absolute timestamp,
- struct GNUNET_TIME_Absolute refund_deadline,
- const struct TALER_Amount *amount_without_fee,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ struct GNUNET_TIME_Timestamp wire_deadline,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_Amount *total_without_fee,
+ unsigned int num_coins,
+ const struct TALER_CoinSpendPublicKeyP *coin_pubs[static num_coins],
+ const struct TALER_CoinSpendSignatureP *coin_sigs[static num_coins],
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_ExchangePublicKeyP *exchange_pub,
const struct TALER_ExchangeSignatureP *exchange_sig,
const struct TALER_MasterPublicKeyP *master_pub,
- struct GNUNET_TIME_Absolute ep_start,
- struct GNUNET_TIME_Absolute ep_expire,
- struct GNUNET_TIME_Absolute ep_end,
+ struct GNUNET_TIME_Timestamp ep_start,
+ struct GNUNET_TIME_Timestamp ep_expire,
+ struct GNUNET_TIME_Timestamp ep_end,
const struct TALER_MasterSignatureP *master_sig,
TALER_AUDITOR_DepositConfirmationResultCallback cb,
void *cb_cls);
@@ -245,76 +345,4 @@ TALER_AUDITOR_deposit_confirmation_cancel (
struct TALER_AUDITOR_DepositConfirmationHandle *deposit_confirmation);
-/**
- * Handle for /exchanges API returned by
- * #TALER_AUDITOR_list_exchanges() so that the operation can be
- * cancelled with #TALER_AUDITOR_list_exchanges_cancel()
- */
-struct TALER_AUDITOR_ListExchangesHandle;
-
-
-/**
- * Information about an exchange kept by the auditor.
- */
-struct TALER_AUDITOR_ExchangeInfo
-{
- /**
- * Master public key of the exchange.
- */
- struct TALER_MasterPublicKeyP master_pub;
-
- /**
- * Base URL of the exchange's API.
- */
- const char *exchange_url;
-};
-
-
-/**
- * Function called with the result from /exchagnes.
- *
- * @param cls closure
- * @param http_status the HTTP status code, 200 on success
- * @param ec detailed Taler error status code, #TALER_EC_NONE on success
- * @param num_exchanges length of array at @a ei
- * @param ei information about exchanges returned by the auditor
- * @param raw_response raw JSON response
- */
-typedef void
-(*TALER_AUDITOR_ListExchangesResultCallback)(
- void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- unsigned int num_exchanges,
- const struct TALER_AUDITOR_ExchangeInfo *ei,
- const json_t *raw_response);
-
-/**
- * Submit an /exchanges request to the auditor and get the
- * auditor's response. If the auditor's reply is not
- * well-formed, we return an HTTP status code of zero to @a cb.
- *
- * @param auditor the auditor handle; the auditor must be ready to operate
- * @param cb the callback to call when a reply for this request is available
- * @param cb_cls closure for the above callback
- * @return a handle for this request; NULL if the inputs are invalid (i.e.
- * signatures fail to verify). In this case, the callback is not called.
- */
-struct TALER_AUDITOR_ListExchangesHandle *
-TALER_AUDITOR_list_exchanges (struct TALER_AUDITOR_Handle *auditor,
- TALER_AUDITOR_ListExchangesResultCallback cb,
- void *cb_cls);
-
-
-/**
- * Cancel a list exchanges request. This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param leh the list exchanges request handle
- */
-void
-TALER_AUDITOR_list_exchanges_cancel (
- struct TALER_AUDITOR_ListExchangesHandle *leh);
-
-
#endif /* _TALER_AUDITOR_SERVICE_H */
diff --git a/src/include/taler_auditordb_plugin.h b/src/include/taler_auditordb_plugin.h
index 9a7f6ed7a..c0a771343 100644
--- a/src/include/taler_auditordb_plugin.h
+++ b/src/include/taler_auditordb_plugin.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 Taler Systems SA
+ Copyright (C) 2014-2024 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
@@ -25,43 +25,12 @@
#include <jansson.h>
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_db_lib.h>
+#include "taler_util.h"
#include "taler_auditordb_lib.h"
#include "taler_signatures.h"
/**
- * Function called with information about exchanges this
- * auditor is monitoring.
- *
- * @param cls closure
- * @param master_pub master public key of the exchange
- * @param exchange_url base URL of the exchange's API
- */
-typedef void
-(*TALER_AUDITORDB_ExchangeCallback)(
- void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *exchange_url);
-
-
-/**
- * Function called with the results of select_denomination_info()
- *
- * @param cls closure
- * @param issue issuing information with value, fees and other info about the denomination.
- *
- * @return sets the return value of select_denomination_info(),
- * #GNUNET_OK to continue,
- * #GNUNET_NO to stop processing further rows
- * #GNUNET_SYSERR or other values on error.
- */
-typedef int
-(*TALER_AUDITORDB_DenominationInfoDataCallback)(
- void *cls,
- const struct TALER_DenominationKeyValidityPS *issue);
-
-
-/**
* Function called with the results of select_historic_denom_revenue()
*
* @param cls closure
@@ -76,11 +45,11 @@ typedef int
* #GNUNET_NO to stop processing further rows
* #GNUNET_SYSERR or other values on error.
*/
-typedef int
+typedef enum GNUNET_GenericReturnValue
(*TALER_AUDITORDB_HistoricDenominationRevenueDataCallback)(
void *cls,
- const struct GNUNET_HashCode *denom_pub_hash,
- struct GNUNET_TIME_Absolute revenue_timestamp,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct GNUNET_TIME_Timestamp revenue_timestamp,
const struct TALER_Amount *revenue_balance,
const struct TALER_Amount *loss_balance);
@@ -98,262 +67,210 @@ typedef int
* #GNUNET_NO to stop processing further rows
* #GNUNET_SYSERR or other values on error.
*/
-typedef int
+typedef enum GNUNET_GenericReturnValue
(*TALER_AUDITORDB_HistoricReserveRevenueDataCallback)(
void *cls,
- struct GNUNET_TIME_Absolute start_time,
- struct GNUNET_TIME_Absolute end_time,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
const struct TALER_Amount *reserve_profits);
/**
- * Structure for remembering the wire auditor's progress over the
- * various tables and (auditor) transactions.
- */
-struct TALER_AUDITORDB_WireProgressPoint
-{
-
- /**
- * Time until which we have confirmed that all wire transactions
- * that the exchange should do, have indeed been done.
- */
- struct GNUNET_TIME_Absolute last_timestamp;
-
- /**
- * reserves_close uuid until which we have checked
- * reserve closures.
- */
- uint64_t last_reserve_close_uuid;
-};
-
-
-/**
- * Structure for remembering the wire auditor's progress over the
- * various tables and (auditor) transactions per wire account.
+ * Information about a signing key of an exchange.
*/
-struct TALER_AUDITORDB_WireAccountProgressPoint
+struct TALER_AUDITORDB_ExchangeSigningKey
{
- /**
- * serial ID of the last reserve_in transfer the wire auditor processed
- */
- uint64_t last_reserve_in_serial_id;
/**
- * serial ID of the last wire_out the wire auditor processed
+ * When does @e exchange_pub start to be used?
*/
- uint64_t last_wire_out_serial_id;
+ struct GNUNET_TIME_Timestamp ep_start;
-};
-
-
-/**
- * Structure for remembering the auditor's progress over the various
- * tables and (auditor) transactions when analyzing reserves.
- */
-struct TALER_AUDITORDB_ProgressPointReserve
-{
/**
- * serial ID of the last reserve_in transfer the auditor processed
+ * When will the exchange stop signing with @e exchange_pub?
*/
- uint64_t last_reserve_in_serial_id;
+ struct GNUNET_TIME_Timestamp ep_expire;
/**
- * serial ID of the last reserve_out the auditor processed
+ * When does the signing key expire (for legal disputes)?
*/
- uint64_t last_reserve_out_serial_id;
+ struct GNUNET_TIME_Timestamp ep_end;
/**
- * serial ID of the last recoup entry the auditor processed when
- * considering reserves.
+ * What is the public offline signing key this is all about?
*/
- uint64_t last_reserve_recoup_serial_id;
+ struct TALER_ExchangePublicKeyP exchange_pub;
/**
- * serial ID of the last reserve_close
- * entry the auditor processed.
+ * Signature by the offline master key affirming the above.
*/
- uint64_t last_reserve_close_serial_id;
-
+ struct TALER_MasterSignatureP master_sig;
};
/**
- * Structure for remembering the auditor's progress over the various
- * tables and (auditor) transactions when analyzing reserves.
+ * Information about a deposit confirmation we received from
+ * a merchant.
*/
-struct TALER_AUDITORDB_ProgressPointDepositConfirmation
+struct TALER_AUDITORDB_DepositConfirmation
{
+
/**
- * serial ID of the last deposit_confirmation the auditor processed
+ * Hash over the contract for which this deposit is made.
*/
- uint64_t last_deposit_confirmation_serial_id;
-
-
-};
-
-
-/**
- * Structure for remembering the auditor's progress over the various
- * tables and (auditor) transactions when analyzing aggregations.
- */
-struct TALER_AUDITORDB_ProgressPointAggregation
-{
+ struct TALER_PrivateContractHashP h_contract_terms;
/**
- * serial ID of the last prewire transfer the auditor processed
+ * Hash over the policy extension for the deposit.
*/
- uint64_t last_wire_out_serial_id;
-};
-
+ struct TALER_ExtensionPolicyHashP h_policy;
-/**
- * Structure for remembering the auditor's progress over the various
- * tables and (auditor) transactions when analyzing coins.
- */
-struct TALER_AUDITORDB_ProgressPointCoin
-{
/**
- * serial ID of the last withdraw the auditor processed
+ * Hash over the wiring information of the merchant.
*/
- uint64_t last_withdraw_serial_id;
+ struct TALER_MerchantWireHashP h_wire;
/**
- * serial ID of the last deposit the auditor processed
+ * Time when this deposit confirmation was generated by the exchange.
*/
- uint64_t last_deposit_serial_id;
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
/**
- * serial ID of the last refresh the auditor processed
+ * How much time does the @e merchant have to issue a refund
+ * request? Zero if refunds are not allowed. After this time, the
+ * coin cannot be refunded. Note that the wire transfer will not be
+ * performed by the exchange until the refund deadline. This value
+ * is taken from the original deposit request.
*/
- uint64_t last_melt_serial_id;
+ struct GNUNET_TIME_Timestamp refund_deadline;
/**
- * serial ID of the last refund the auditor processed
+ * How much time does the @e exchange have to wire the funds?
*/
- uint64_t last_refund_serial_id;
+ struct GNUNET_TIME_Timestamp wire_deadline;
/**
- * Serial ID of the last recoup operation the auditor processed.
+ * Amount to be deposited, excluding fee. Calculated from the
+ * amount with fee and the fee from the deposit request.
*/
- uint64_t last_recoup_serial_id;
+ struct TALER_Amount total_without_fee;
/**
- * Serial ID of the last recoup-of-refresh operation the auditor processed.
+ * Length of the @e coin_pubs and @e coin_sigs arrays.
*/
- uint64_t last_recoup_refresh_serial_id;
-
-};
+ unsigned int num_coins;
-
-/**
- * Information about a signing key of an exchange.
- */
-struct TALER_AUDITORDB_ExchangeSigningKey
-{
/**
- * Public master key of the exchange that certified @e master_sig.
+ * Array of the coin public keys involved in the
+ * batch deposit operation.
*/
- struct TALER_MasterPublicKeyP master_public_key;
+ const struct TALER_CoinSpendPublicKeyP *coin_pubs;
/**
- * When does @e exchange_pub start to be used?
+ * Array of coin deposit signatures from the deposit operation.
*/
- struct GNUNET_TIME_Absolute ep_start;
+ const struct TALER_CoinSpendSignatureP *coin_sigs;
/**
- * When will the exchange stop signing with @e exchange_pub?
+ * The Merchant's public key. Allows the merchant to later refund
+ * the transaction or to inquire about the wire transfer identifier.
*/
- struct GNUNET_TIME_Absolute ep_expire;
+ struct TALER_MerchantPublicKeyP merchant;
/**
- * When does the signing key expire (for legal disputes)?
+ * Signature from the exchange of type
+ * #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT.
*/
- struct GNUNET_TIME_Absolute ep_end;
+ struct TALER_ExchangeSignatureP exchange_sig;
/**
- * What is the public offline signing key this is all about?
+ * Public signing key from the exchange matching @e exchange_sig.
*/
struct TALER_ExchangePublicKeyP exchange_pub;
/**
- * Signature by the offline master key affirming the above.
+ * Exchange master signature over @e exchange_sig.
*/
struct TALER_MasterSignatureP master_sig;
+
};
/**
- * Information about a deposit confirmation we received from
- * a merchant.
+ * Balance values for a reserve (or all reserves).
*/
-struct TALER_AUDITORDB_DepositConfirmation
+struct TALER_AUDITORDB_ReserveFeeBalance
{
-
/**
- * Hash over the contract for which this deposit is made.
+ * Remaining funds.
*/
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_Amount reserve_balance;
/**
- * Hash over the wiring information of the merchant.
+ * Losses from operations that should not have
+ * happened (e.g. negative balance).
*/
- struct GNUNET_HashCode h_wire;
+ struct TALER_Amount reserve_loss;
/**
- * Time when this confirmation was generated.
+ * Fees charged for withdraw.
*/
- struct GNUNET_TIME_Absolute timestamp;
+ struct TALER_Amount withdraw_fee_balance;
/**
- * How much time does the @e merchant have to issue a refund
- * request? Zero if refunds are not allowed. After this time, the
- * coin cannot be refunded. Note that the wire transfer will not be
- * performed by the exchange until the refund deadline. This value
- * is taken from the original deposit request.
+ * Fees charged for closing.
*/
- struct GNUNET_TIME_Absolute refund_deadline;
+ struct TALER_Amount close_fee_balance;
/**
- * Amount to be deposited, excluding fee. Calculated from the
- * amount with fee and the fee from the deposit request.
+ * Fees charged for purse creation.
*/
- struct TALER_Amount amount_without_fee;
+ struct TALER_Amount purse_fee_balance;
/**
- * The coin's public key. This is the value that must have been
- * signed (blindly) by the Exchange. The deposit request is to be
- * signed by the corresponding private key (using EdDSA).
+ * Opening fees charged.
*/
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_Amount open_fee_balance;
/**
- * The Merchant's public key. Allows the merchant to later refund
- * the transaction or to inquire about the wire transfer identifier.
+ * History fees charged.
*/
- struct TALER_MerchantPublicKeyP merchant;
+ struct TALER_Amount history_fee_balance;
+};
+
+/**
+ * Balance data for denominations in circulation.
+ */
+struct TALER_AUDITORDB_DenominationCirculationData
+{
/**
- * Signature from the exchange of type
- * #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT.
+ * Amount of outstanding coins in circulation.
*/
- struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_Amount denom_balance;
/**
- * Public signing key from the exchange matching @e exchange_sig.
+ * Amount lost due coins illicitly accepted (effectively, a
+ * negative @a denom_balance).
*/
- struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_Amount denom_loss;
/**
- * Exchange master signature over @e exchange_sig.
+ * Total amount that could still be theoretically lost in the future due to
+ * recoup operations. (Total put into circulation minus @e recoup_loss).
*/
- struct TALER_MasterSignatureP master_sig;
+ struct TALER_Amount denom_risk;
/**
- * Master public key of the exchange corresponding to @e master_sig.
- * Identifies the exchange this is about.
+ * Amount lost due to recoups.
*/
- struct TALER_MasterPublicKeyP master_public_key;
+ struct TALER_Amount recoup_loss;
+ /**
+ * Number of coins of this denomination that the exchange signed into
+ * existence.
+ */
+ uint64_t num_issued;
};
@@ -364,8 +281,9 @@ struct TALER_AUDITORDB_DepositConfirmation
* @param cls closure
* @param serial_id location of the @a dc in the database
* @param dc the deposit confirmation itself
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop iterating
*/
-typedef void
+typedef enum GNUNET_GenericReturnValue
(*TALER_AUDITORDB_DepositConfirmationCallback)(
void *cls,
uint64_t serial_id,
@@ -373,9 +291,39 @@ typedef void
/**
- * Handle for one session with the database.
+ * Function called on deposits that are past their due date
+ * and have not yet seen a wire transfer.
+ *
+ * @param cls closure
+ * @param batch_deposit_serial_id where in the table are we
+ * @param total_amount value of all missing deposits, including fees
+ * @param wire_target_h_payto hash of the recipient account's payto URI
+ * @param deadline what was the earliest requested wire transfer deadline
+ */
+typedef void
+(*TALER_AUDITORDB_WireMissingCallback)(
+ void *cls,
+ uint64_t batch_deposit_serial_id,
+ const struct TALER_Amount *total_amount,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ struct GNUNET_TIME_Timestamp deadline);
+
+
+/**
+ * Function called on expired purses.
+ *
+ * @param cls closure
+ * @param purse_pub public key of the purse
+ * @param balance amount of money in the purse
+ * @param expiration_date when did the purse expire?
+ * @return #GNUNET_OK to continue to iterate
*/
-struct TALER_AUDITORDB_Session;
+typedef enum GNUNET_GenericReturnValue
+(*TALER_AUDITORDB_ExpiredPurseCallback)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance,
+ struct GNUNET_TIME_Timestamp expiration_date);
/**
@@ -400,14 +348,58 @@ struct TALER_AUDITORDB_Plugin
char *library_name;
/**
- * Get the thread-local database-handle.
- * Connect to the db if the connection does not exist yet.
+ * Fully connect to the db if the connection does not exist yet
+ * and check that there is no transaction currently running.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param the database connection, or NULL on error
+ * @return #GNUNET_OK on success
+ * #GNUNET_NO if we rolled back an earlier transaction
+ * #GNUNET_SYSERR if we have no DB connection
*/
- struct TALER_AUDITORDB_Session *
- (*get_session) (void *cls);
+ enum GNUNET_GenericReturnValue
+ (*preflight)(void *cls);
+
+
+ /**
+ * Register callback to be invoked on events of type @a es.
+ *
+ * @param cls database context to use
+ * @param es specification of the event to listen for
+ * @param timeout how long to wait for the event
+ * @param cb function to call when the event happens, possibly
+ * mulrewardle times (until cancel is invoked)
+ * @param cb_cls closure for @a cb
+ * @return handle useful to cancel the listener
+ */
+ struct GNUNET_DB_EventHandler *
+ (*event_listen)(void *cls,
+ const struct GNUNET_DB_EventHeaderP *es,
+ struct GNUNET_TIME_Relative timeout,
+ GNUNET_DB_EventCallback cb,
+ void *cb_cls);
+
+ /**
+ * Stop notifications.
+ *
+ * @param eh handle to unregister.
+ */
+ void
+ (*event_listen_cancel)(struct GNUNET_DB_EventHandler *eh);
+
+
+ /**
+ * Notify all that listen on @a es of an event.
+ *
+ * @param cls database context to use
+ * @param es specification of the event to generate
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+ void
+ (*event_notify)(void *cls,
+ const struct GNUNET_DB_EventHeaderP *es,
+ const void *extra,
+ size_t extra_size);
/**
@@ -421,54 +413,52 @@ struct TALER_AUDITORDB_Plugin
* used when restarting the auditor
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
- int
- (*drop_tables) (void *cls,
- int drop_exchangelist);
+ enum GNUNET_GenericReturnValue
+ (*drop_tables)(void *cls,
+ bool drop_exchangelist);
/**
* Create the necessary tables if they are not present
*
* @param cls the @e cls of this struct with the plugin-specific state
+ * @param support_partitions true to support partitioning
+ * @param num_partitions number of partitions to use
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
- int
- (*create_tables) (void *cls);
+ enum GNUNET_GenericReturnValue
+ (*create_tables)(void *cls,
+ bool support_partitions,
+ uint32_t num_partitions);
/**
* Start a transaction.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @return #GNUNET_OK on success
*/
- int
- (*start) (void *cls,
- struct TALER_AUDITORDB_Session *session);
+ enum GNUNET_GenericReturnValue
+ (*start)(void *cls);
/**
* Commit a transaction.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*commit)(void *cls,
- struct TALER_AUDITORDB_Session *session);
+ (*commit)(void *cls);
/**
* Abort/rollback a transaction.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
*/
void
- (*rollback) (void *cls,
- struct TALER_AUDITORDB_Session *session);
+ (*rollback) (void *cls);
/**
@@ -479,263 +469,26 @@ struct TALER_AUDITORDB_Plugin
* @return #GNUNET_OK on success,
* #GNUNET_SYSERR on DB errors
*/
- int
- (*gc) (void *cls);
-
-
- /**
- * Insert information about an exchange this auditor will be auditing.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param master_pub master public key of the exchange
- * @param exchange_url public (base) URL of the API of the exchange
- * @return query result status
- */
- enum GNUNET_DB_QueryStatus
- (*insert_exchange)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *exchange_url);
-
-
- /**
- * Delete an exchange from the list of exchanges this auditor is auditing.
- * Warning: this will cascade and delete all knowledge of this auditor related
- * to this exchange!
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param master_pub master public key of the exchange
- * @return query result status
- */
- enum GNUNET_DB_QueryStatus
- (*delete_exchange)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub);
-
-
- /**
- * Obtain information about exchanges this auditor is auditing.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param cb function to call with the results
- * @param cb_cls closure for @a cb
- * @return query result status
- */
- enum GNUNET_DB_QueryStatus
- (*list_exchanges)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- TALER_AUDITORDB_ExchangeCallback cb,
- void *cb_cls);
-
- /**
- * Insert information about a signing key of the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param sk signing key information to store
- * @return query result status
- */
- enum GNUNET_DB_QueryStatus
- (*insert_exchange_signkey)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_AUDITORDB_ExchangeSigningKey *sk);
-
-
- /**
- * Insert information about a deposit confirmation into the database.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param dc deposit confirmation information to store
- * @return query result status
- */
- enum GNUNET_DB_QueryStatus
- (*insert_deposit_confirmation)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_AUDITORDB_DepositConfirmation *dc);
-
-
- /**
- * Get information about deposit confirmations from the database.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param master_public_key for which exchange do we want to get deposit confirmations
- * @param start_id row/serial ID where to start the iteration (0 from
- * the start, exclusive, i.e. serial_ids must start from 1)
- * @param cb function to call with results
- * @param cb_cls closure for @a cb
- * @return query result status
- */
- enum GNUNET_DB_QueryStatus
- (*get_deposit_confirmations)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_public_key,
- uint64_t start_id,
- TALER_AUDITORDB_DepositConfirmationCallback cb,
- void *cb_cls);
-
-
- /**
- * Insert information about a denomination key and in particular
- * the properties (value, fees, expiration times) the coins signed
- * with this key have.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param issue issuing information with value, fees and other info about the denomination
- * @return status of database operation
- */
- enum GNUNET_DB_QueryStatus
- (*insert_denomination_info)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_DenominationKeyValidityPS *issue);
-
-
- /**
- * Get information about denomination keys of a particular exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param cb function to call with the results
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*select_denomination_info)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- TALER_AUDITORDB_DenominationInfoDataCallback cb,
- void *cb_cls);
-
-
- /**
- * Insert information about the auditor's progress with an exchange's
- * data.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppc where is the auditor in processing
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*insert_auditor_progress_coin)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointCoin *ppc);
-
-
- /**
- * Update information about the progress of the auditor. There
- * must be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppc where is the auditor in processing
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*update_auditor_progress_coin)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointCoin *ppc);
-
-
- /**
- * Get information about the progress of the auditor.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param[out] ppc set to where the auditor is in processing
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*get_auditor_progress_coin)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_ProgressPointCoin *ppc);
-
- /**
- * Insert information about the auditor's progress with an exchange's
- * data.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppr where is the auditor in processing
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*insert_auditor_progress_reserve)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointReserve *ppr);
-
+ enum GNUNET_GenericReturnValue
+ (*gc)(void *cls);
- /**
- * Update information about the progress of the auditor. There
- * must be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppr where is the auditor in processing
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*update_auditor_progress_reserve)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointReserve *ppr);
-
-
- /**
- * Get information about the progress of the auditor.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param[out] ppr set to where the auditor is in processing
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*get_auditor_progress_reserve)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_ProgressPointReserve *ppr);
/**
* Insert information about the auditor's progress with an exchange's
* data.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppdc where is the auditor in processing
+ * @param progress_key name of the progress indicator
+ * @param progress_offset offset until which we have made progress
+ * @param ... NULL terminated list of additional key-value pairs to insert
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_auditor_progress_deposit_confirmation)(
+ (*insert_auditor_progress)(
void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointDepositConfirmation *ppdc);
+ const char *progress_key,
+ uint64_t progress_offset,
+ ...);
/**
@@ -743,213 +496,142 @@ struct TALER_AUDITORDB_Plugin
* must be an existing record for the exchange.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppdc where is the auditor in processing
+ * @param progress_key name of the progress indicator
+ * @param progress_offset offset until which we have made progress
+ * @param ... NULL terminated list of additional key-value pairs to update
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*update_auditor_progress_deposit_confirmation)(
+ (*update_auditor_progress)(
void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointDepositConfirmation *ppdc);
+ const char *progress_key,
+ uint64_t progress_offset,
+ ...);
/**
* Get information about the progress of the auditor.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param[out] ppdc set to where the auditor is in processing
+ * @param progress_key name of the progress indicator
+ * @param[out] progress_offset set to offset until which we have made progress, set to 0 if key was not found
+ * @param ... NULL terminated list of additional key-value pairs to fetch
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*get_auditor_progress_deposit_confirmation)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_ProgressPointDepositConfirmation *ppdc);
+ (*get_auditor_progress)(void *cls,
+ const char *progress_key,
+ uint64_t *progress_offset,
+ ...);
/**
- * Insert information about the auditor's progress with an exchange's
- * data.
+ * Insert information about a balance tracked by the auditor. There must not be an
+ * existing record.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppa where is the auditor in processing
+ * @param balance_key key of the balance to store
+ * @param balance_value value to store
+ * @param ... NULL terminated list of additional key-value pairs to insert
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_auditor_progress_aggregation)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointAggregation *ppa);
+ (*insert_balance)(void *cls,
+ const char *balance_key,
+ const struct TALER_Amount *balance_value,
+ ...);
/**
- * Update information about the progress of the auditor. There
- * must be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param ppa where is the auditor in processing
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*update_auditor_progress_aggregation)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointAggregation *ppa);
-
-
- /**
- * Get information about the progress of the auditor.
+ * Insert information about a balance tracked by the auditor. Destructively updates an
+ * existing record, which must already exist.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param[out] ppa set to where the auditor is in processing
+ * @param balance_key key of the balance to store
+ * @param balance_amount value to store
+ * @param ... NULL terminated list of additional key-value pairs to update
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*get_auditor_progress_aggregation)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_ProgressPointAggregation *ppa);
+ (*update_balance)(void *cls,
+ const char *balance_key,
+ const struct TALER_Amount *balance_amount,
+ ...);
/**
- * Insert information about the wire auditor's progress with an exchange's
- * data.
+ * Get summary information about balance tracked by the auditor.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param account_name name of the wire account we are auditing
- * @param pp where is the auditor in processing
- * @param in_wire_off how far are we in the incoming wire transaction history
- * @param out_wire_off how far are we in the outgoing wire transaction history
+ * @param balance_key key of the balance to store
+ * @param[out] balance_value set to amount stored under @a balance_key, set to invalid amount (all zero) if key was not found
+ * @param ... NULL terminated list of additional key-value pairs to fetch
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_wire_auditor_account_progress)(
- void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *account_name,
- const struct TALER_AUDITORDB_WireAccountProgressPoint *pp,
- uint64_t in_wire_off,
- uint64_t out_wire_off);
+ (*get_balance)(void *cls,
+ const char *balance_key,
+ struct TALER_Amount *balance_value,
+ ...);
/**
- * Update information about the progress of the wire auditor. There
- * must be an existing record for the exchange.
+ * Insert information about a signing key of the exchange.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param account_name name of the wire account we are auditing
- * @param pp where is the auditor in processing
- * @param in_wire_off how far are we in the incoming wire transaction history
- * @param out_wire_off how far are we in the outgoing wire transaction history
- * @return transaction status code
+ * @param sk signing key information to store
+ * @return query result status
*/
enum GNUNET_DB_QueryStatus
- (*update_wire_auditor_account_progress)(
+ (*insert_exchange_signkey)(
void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *account_name,
- const struct TALER_AUDITORDB_WireAccountProgressPoint *pp,
- uint64_t in_wire_off,
- uint64_t out_wire_off);
+ const struct TALER_AUDITORDB_ExchangeSigningKey *sk);
/**
- * Get information about the progress of the wire auditor.
+ * Insert information about a deposit confirmation into the database.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param account_name name of the wire account we are auditing
- * @param[out] pp where is the auditor in processing
- * @param[out] in_wire_off how far are we in the incoming wire transaction history
- * @param[out] out_wire_off how far are we in the outgoing wire transaction history
- * @return transaction status code
+ * @param dc deposit confirmation information to store
+ * @return query result status
*/
enum GNUNET_DB_QueryStatus
- (*get_wire_auditor_account_progress)(
+ (*insert_deposit_confirmation)(
void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *account_name,
- struct TALER_AUDITORDB_WireAccountProgressPoint *pp,
- uint64_t *in_wire_off,
- uint64_t *out_wire_off);
+ const struct TALER_AUDITORDB_DepositConfirmation *dc);
/**
- * Insert information about the wire auditor's progress with an exchange's
- * data.
+ * Get information about deposit confirmations from the database.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param account_name name of the wire account we are auditing
- * @param pp where is the auditor in processing
- * @return transaction status code
+ * @param start_id row/serial ID where to start the iteration (0 from
+ * the start, exclusive, i.e. serial_ids must start from 1)
+ * @param return_suppressed should suppressed rows be returned anyway?
+ * @param cb function to call with results
+ * @param cb_cls closure for @a cb
+ * @return query result status
*/
enum GNUNET_DB_QueryStatus
- (*insert_wire_auditor_progress)(
+ (*get_deposit_confirmations)(
void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_WireProgressPoint *pp);
+ uint64_t start_id,
+ bool return_suppressed,
+ TALER_AUDITORDB_DepositConfirmationCallback cb,
+ void *cb_cls);
/**
- * Update information about the progress of the wire auditor. There
- * must be an existing record for the exchange.
+ * Delete information about a deposit confirmation from the database.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param account_name name of the wire account we are auditing
- * @param pp where is the auditor in processing
- * @return transaction status code
+ * @param row_id row to delete
+ * @return query result status
*/
enum GNUNET_DB_QueryStatus
- (*update_wire_auditor_progress)(
+ (*delete_deposit_confirmation)(
void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_WireProgressPoint *pp);
-
-
- /**
- * Get information about the progress of the wire auditor.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param account_name name of the wire account we are auditing
- * @param[out] pp set to where the auditor is in processing
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*get_wire_auditor_progress)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_WireProgressPoint *pp);
+ uint64_t row_id);
/**
@@ -957,25 +639,19 @@ struct TALER_AUDITORDB_Plugin
* existing record for the reserve.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @param reserve_pub public key of the reserve
- * @param master_pub master public key of the exchange
- * @param reserve_balance amount stored in the reserve
- * @param withdraw_fee_balance amount the exchange gained in withdraw fees
- * due to withdrawals from this reserve
+ * @param rfb balance amounts for the reserve
* @param expiration_date expiration date of the reserve
* @param origin_account where did the money in the reserve originally come from
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_reserve_info)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *reserve_balance,
- const struct TALER_Amount *withdraw_fee_balance,
- struct GNUNET_TIME_Absolute expiration_date,
- const char *origin_account);
+ (*insert_reserve_info)(
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_AUDITORDB_ReserveFeeBalance *rfb,
+ struct GNUNET_TIME_Timestamp expiration_date,
+ const char *origin_account);
/**
@@ -983,352 +659,241 @@ struct TALER_AUDITORDB_Plugin
* existing record, which must already exist.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @param reserve_pub public key of the reserve
- * @param master_pub master public key of the exchange
- * @param reserve_balance amount stored in the reserve
- * @param withdraw_fee_balance amount the exchange gained in withdraw fees
- * due to withdrawals from this reserve
+ * @param rfb balance amounts for the reserve
* @param expiration_date expiration date of the reserve
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*update_reserve_info)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *reserve_balance,
- const struct TALER_Amount *withdraw_fee_balance,
- struct GNUNET_TIME_Absolute expiration_date);
+ (*update_reserve_info)(
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_AUDITORDB_ReserveFeeBalance *rfb,
+ struct GNUNET_TIME_Timestamp expiration_date);
/**
* Get information about a reserve.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @param reserve_pub public key of the reserve
- * @param master_pub master public key of the exchange
* @param[out] rowid which row did we get the information from
- * @param[out] reserve_balance amount stored in the reserve
- * @param[out] withdraw_fee_balance amount the exchange gained in withdraw fees
- * due to withdrawals from this reserve
+ * @param[out] rfb set to balances associated with the reserve
* @param[out] expiration_date expiration date of the reserve
* @param[out] sender_account from where did the money in the reserve originally come from
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*get_reserve_info)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_MasterPublicKeyP *master_pub,
- uint64_t *rowid,
- struct TALER_Amount *reserve_balance,
- struct TALER_Amount *withdraw_fee_balance,
- struct GNUNET_TIME_Absolute *expiration_date,
- char **sender_account);
+ (*get_reserve_info)(
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *rowid,
+ struct TALER_AUDITORDB_ReserveFeeBalance *rfb,
+ struct GNUNET_TIME_Timestamp *expiration_date,
+ char **sender_account);
/**
* Delete information about a reserve.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @param reserve_pub public key of the reserve
- * @param master_pub master public key of the exchange
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
(*del_reserve_info)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_MasterPublicKeyP *master_pub);
+ const struct TALER_ReservePublicKeyP *reserve_pub);
/**
- * Insert information about all reserves. There must not be an
- * existing record for the @a master_pub.
+ * Insert new row into the pending deposits table.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param reserve_balance amount stored in the reserve
- * @param withdraw_fee_balance amount the exchange gained in withdraw fees
- * due to withdrawals from this reserve
+ * @param batch_deposit_serial_id where in the table are we
+ * @param total_amount value of all missing deposits, including fees
+ * @param wire_target_h_payto hash of the recipient account's payto URI
+ * @param deadline what was the requested wire transfer deadline
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_reserve_summary)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *reserve_balance,
- const struct TALER_Amount *withdraw_fee_balance);
+ (*insert_pending_deposit)(
+ void *cls,
+ uint64_t batch_deposit_serial_id,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ const struct TALER_Amount *total_amount,
+ struct GNUNET_TIME_Timestamp deadline);
/**
- * Update information about all reserves. Destructively updates an
- * existing record, which must already exist.
+ * Delete a row from the pending deposit table.
+ * Usually done when the respective wire transfer
+ * was finally detected.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param reserve_balance amount stored in the reserve
- * @param withdraw_fee_balance amount the exchange gained in withdraw fees
- * due to withdrawals from this reserve
+ * @param batch_deposit_serial_id which entry to delete
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*update_reserve_summary)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *reserve_balance,
- const struct TALER_Amount *withdraw_fee_balance);
+ (*delete_pending_deposit)(
+ void *cls,
+ uint64_t batch_deposit_serial_id);
/**
- * Get summary information about all reserves.
+ * Return (batch) deposits for which we have not yet
+ * seen the required wire transfer.
*
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param[out] reserve_balance amount stored in the reserve
- * @param[out] withdraw_fee_balance amount the exchange gained in withdraw fees
- * due to withdrawals from this reserve
+ * @param deadline only return up to this deadline
+ * @param cb function to call on each entry
+ * @param cb_cls closure for @a cb
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*get_reserve_summary)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_Amount *reserve_balance,
- struct TALER_Amount *withdraw_fee_balance);
+ (*select_pending_deposits)(
+ void *cls,
+ struct GNUNET_TIME_Absolute deadline,
+ TALER_AUDITORDB_WireMissingCallback cb,
+ void *cb_cls);
/**
- * Insert information about exchange's wire fee balance. There must not be an
- * existing record for the same @a master_pub.
+ * Insert information about a purse. There must not be an
+ * existing record for the purse.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param wire_fee_balance amount the exchange gained in wire fees
+ * @param purse_pub public key of the purse
+ * @param balance balance of the purse
+ * @param expiration_date expiration date of the purse
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_wire_fee_summary)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *wire_fee_balance);
+ (*insert_purse_info)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance,
+ struct GNUNET_TIME_Timestamp expiration_date);
/**
- * Insert information about exchange's wire fee balance. Destructively updates an
+ * Update information about a purse. Destructively updates an
* existing record, which must already exist.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param wire_fee_balance amount the exchange gained in wire fees
+ * @param purse_pub public key of the purse
+ * @param balance new balance for the purse
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*update_wire_fee_summary)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *wire_fee_balance);
+ (*update_purse_info)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance);
/**
- * Get summary information about an exchanges wire fee balance.
+ * Get information about a purse.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master public key of the exchange
- * @param[out] wire_fee_balance set amount the exchange gained in wire fees
+ * @param purse_pub public key of the purse
+ * @param[out] rowid which row did we get the information from
+ * @param[out] balance set to balance of the purse
+ * @param[out] expiration_date expiration date of the purse
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*get_wire_fee_summary)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_Amount *wire_fee_balance);
+ (*get_purse_info)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ uint64_t *rowid,
+ struct TALER_Amount *balance,
+ struct GNUNET_TIME_Timestamp *expiration_date);
/**
- * Insert information about a denomination key's balances. There
- * must not be an existing record for the denomination key.
+ * Delete information about a purse.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param denom_pub_hash hash of the denomination public key
- * @param denom_balance value of coins outstanding with this denomination key
- * @param denom_loss value of coins redeemed that were not outstanding (effectively, negative @a denom_balance)
- * @param denom_risk value of coins issued with this denomination key
- * @param denom_recoup value of coins paid back if this denomination key was revoked
- * @param num_issued how many coins of this denomination did the exchange blind-sign
+ * @param purse_pub public key of the reserve
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_denomination_balance)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash,
- const struct TALER_Amount *denom_balance,
- const struct TALER_Amount *denom_loss,
- const struct TALER_Amount *denom_risk,
- const struct TALER_Amount *recoup_loss,
- uint64_t num_issued);
+ (*delete_purse_info)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub);
/**
- * Update information about a denomination key's balances. There
- * must be an existing record for the denomination key.
+ * Get information about expired purses.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param denom_pub_hash hash of the denomination public key
- * @param denom_balance value of coins outstanding with this denomination key
- * @param denom_loss value of coins redeemed that were not outstanding (effectively, negative @a denom_balance)
- * @param denom_risk value of coins issued with this denomination key
- * @param denom_recoup value of coins paid back if this denomination key was revoked
- * @param num_issued how many coins of this denomination did the exchange blind-sign
+ * @param cb function to call on expired purses
+ * @param cb_cls closure for @a cb
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*update_denomination_balance)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash,
- const struct TALER_Amount *denom_balance,
- const struct TALER_Amount *denom_loss,
- const struct TALER_Amount *denom_risk,
- const struct TALER_Amount *recoup_loss,
- uint64_t num_issued);
+ (*select_purse_expired)(
+ void *cls,
+ TALER_AUDITORDB_ExpiredPurseCallback cb,
+ void *cb_cls);
/**
- * Get information about a denomination key's balances.
+ * Insert information about a denomination key's balances. There
+ * must not be an existing record for the denomination key.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @param denom_pub_hash hash of the denomination public key
- * @param[out] denom_balance value of coins outstanding with this denomination key
- * @param[out] denom_loss value of coins redeemed that were not outstanding (effectively, negative @a denom_balance)
- * @param[out] denom_risk value of coins issued with this denomination key
- * @param[out] denom_recoup value of coins paid back if this denomination key was revoked
- * @param[out] num_issued how many coins of this denomination did the exchange blind-sign
+ * @param dcd denomination circulation data to store
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*get_denomination_balance)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash,
- struct TALER_Amount *denom_balance,
- struct TALER_Amount *denom_loss,
- struct TALER_Amount *denom_risk,
- struct TALER_Amount *recoup_loss,
- uint64_t *num_issued);
+ (*insert_denomination_balance)(
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ const struct TALER_AUDITORDB_DenominationCirculationData *dcd);
/**
- * Delete information about a denomination key's balances.
+ * Update information about a denomination key's balances. There
+ * must be an existing record for the denomination key.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @param denom_pub_hash hash of the denomination public key
+ * @param dcd denomination circulation data to store
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*del_denomination_balance)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash);
-
-
- /**
- * Insert information about an exchange's denomination balances. There
- * must not be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param denom_balance value of coins outstanding with this denomination key
- * @param deposit_fee_balance total deposit fees collected for this DK
- * @param melt_fee_balance total melt fees collected for this DK
- * @param refund_fee_balance total refund fees collected for this DK
- * @param risk maximum risk exposure of the exchange
- * @param recoup_loss actual losses from recoup (actualized @a risk)
- * @param irregular_recoups recoups made of non-revoked coins (reduces
- * risk, but should never happen)
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*insert_balance_summary)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *denom_balance,
- const struct TALER_Amount *deposit_fee_balance,
- const struct TALER_Amount *melt_fee_balance,
- const struct TALER_Amount *refund_fee_balance,
- const struct TALER_Amount *risk,
- const struct TALER_Amount *recoup_loss,
- const struct TALER_Amount *irregular_recoups);
-
+ (*update_denomination_balance)(
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ const struct TALER_AUDITORDB_DenominationCirculationData *dcd);
/**
- * Update information about an exchange's denomination balances. There
- * must be an existing record for the exchange.
+ * Delete information about a denomination key's balances.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param denom_balance value of coins outstanding with this denomination key
- * @param deposit_fee_balance total deposit fees collected for this DK
- * @param melt_fee_balance total melt fees collected for this DK
- * @param refund_fee_balance total refund fees collected for this DK
- * @param risk maximum risk exposure of the exchange
- * @param recoup_loss actual losses from recoup (actualized @a risk)
- * @param irregular_recoups recoups made of non-revoked coins (reduces
- * risk, but should never happen)
+ * @param denom_pub_hash hash of the denomination public key
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*update_balance_summary)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *denom_balance,
- const struct TALER_Amount *deposit_fee_balance,
- const struct TALER_Amount *melt_fee_balance,
- const struct TALER_Amount *refund_fee_balance,
- const struct TALER_Amount *risk,
- const struct TALER_Amount *recoup_loss,
- const struct TALER_Amount *irregular_recoups);
+ (*del_denomination_balance)(
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash);
/**
- * Get information about an exchange's denomination balances.
+ * Get information about a denomination key's balances.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param[out] denom_balance value of coins outstanding with this denomination key
- * @param[out] deposit_fee_balance total deposit fees collected for this DK
- * @param[out] melt_fee_balance total melt fees collected for this DK
- * @param[out] refund_fee_balance total refund fees collected for this DK
- * @param[out] risk maximum risk exposure of the exchange
- * @param[out] recoup_loss actual losses from recoup (actualized @a risk)
- * @param[out] irregular_recoups recoups made of non-revoked coins (reduces
- * risk, but should never happen)
+ * @param denom_pub_hash hash of the denomination public key
+ * @param[out] dcd denomination circulation data to initialize
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*get_balance_summary)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_Amount *denom_balance,
- struct TALER_Amount *deposit_fee_balance,
- struct TALER_Amount *melt_fee_balance,
- struct TALER_Amount *refund_fee_balance,
- struct TALER_Amount *risk,
- struct TALER_Amount *recoup_loss,
- struct TALER_Amount *irregular_recoup);
+ (*get_denomination_balance)(
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_AUDITORDB_DenominationCirculationData *dcd);
/**
@@ -1336,8 +901,6 @@ struct TALER_AUDITORDB_Plugin
* revenue about a denomination key.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
* @param denom_pub_hash hash of the denomination key
* @param revenue_timestamp when did this profit get realized
* @param revenue_balance what was the total profit made from
@@ -1349,21 +912,16 @@ struct TALER_AUDITORDB_Plugin
enum GNUNET_DB_QueryStatus
(*insert_historic_denom_revenue)(
void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct GNUNET_HashCode *denom_pub_hash,
- struct GNUNET_TIME_Absolute revenue_timestamp,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct GNUNET_TIME_Timestamp revenue_timestamp,
const struct TALER_Amount *revenue_balance,
const struct TALER_Amount *recoup_loss_balance);
/**
- * Obtain all of the historic denomination key revenue
- * of the given @a master_pub.
+ * Obtain all of the historic denomination key revenue.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
* @param cb function to call with the results
* @param cb_cls closure for @a cb
* @return transaction status code
@@ -1371,8 +929,6 @@ struct TALER_AUDITORDB_Plugin
enum GNUNET_DB_QueryStatus
(*select_historic_denom_revenue)(
void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
TALER_AUDITORDB_HistoricDenominationRevenueDataCallback cb,
void *cb_cls);
@@ -1381,8 +937,6 @@ struct TALER_AUDITORDB_Plugin
* Insert information about an exchange's historic revenue from reserves.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
* @param start_time beginning of aggregated time interval
* @param end_time end of aggregated time interval
* @param reserve_profits total profits made
@@ -1391,10 +945,8 @@ struct TALER_AUDITORDB_Plugin
enum GNUNET_DB_QueryStatus
(*insert_historic_reserve_revenue)(
void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct GNUNET_TIME_Absolute start_time,
- struct GNUNET_TIME_Absolute end_time,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
const struct TALER_Amount *reserve_profits);
@@ -1402,8 +954,6 @@ struct TALER_AUDITORDB_Plugin
* Return information about an exchange's historic revenue from reserves.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
* @param cb function to call with results
* @param cb_cls closure for @a cb
* @return transaction status code
@@ -1411,62 +961,9 @@ struct TALER_AUDITORDB_Plugin
enum GNUNET_DB_QueryStatus
(*select_historic_reserve_revenue)(
void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
TALER_AUDITORDB_HistoricReserveRevenueDataCallback cb,
void *cb_cls);
-
- /**
- * Insert information about the predicted exchange's bank
- * account balance.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param balance what the bank account balance of the exchange should show
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*insert_predicted_result)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *balance);
-
-
- /**
- * Update information about an exchange's predicted balance. There
- * must be an existing record for the exchange.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param balance what the bank account balance of the exchange should show
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*update_predicted_result)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *balance);
-
-
- /**
- * Get an exchange's predicted balance.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
- * @param master_pub master key of the exchange
- * @param[out] balance expected bank account balance of the exchange
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*get_predicted_balance)(void *cls,
- struct TALER_AUDITORDB_Session *session,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_Amount *balance);
-
-
};
diff --git a/src/include/taler_bank_service.h b/src/include/taler_bank_service.h
index 0212e61f1..e8e32947b 100644
--- a/src/include/taler_bank_service.h
+++ b/src/include/taler_bank_service.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2020 Taler Systems SA
+ 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
@@ -100,24 +100,64 @@ struct TALER_BANK_AdminAddIncomingHandle;
/**
+ * Response details for a history request.
+ */
+struct TALER_BANK_AdminAddIncomingResponse
+{
+
+ /**
+ * HTTP status.
+ */
+ unsigned int http_status;
+
+ /**
+ * Taler error code, #TALER_EC_NONE on success.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * Full response, NULL if body was not in JSON format.
+ */
+ const json_t *response;
+
+ /**
+ * Details returned depending on the @e http_status.
+ */
+ union
+ {
+
+ /**
+ * Details if status was #MHD_HTTP_OK
+ */
+ struct
+ {
+ /**
+ * unique ID of the wire transfer in the bank's records
+ */
+ uint64_t serial_id;
+
+ /**
+ * time when the transaction was made.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ } ok;
+
+ } details;
+
+};
+
+/**
* Callbacks of this type are used to return the result of submitting
* a request to transfer funds to the exchange.
*
* @param cls closure
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
- * 0 if the bank's reply is bogus (fails to follow the protocol)
- * @param ec detailed error code
- * @param serial_id unique ID of the wire transfer in the bank's records; UINT64_MAX on error
- * @param timestamp time when the transaction was made.
- * @param json detailed response from the HTTPD, or NULL if reply was not in JSON
+ * @param air response details
*/
typedef void
-(*TALER_BANK_AdminAddIncomingCallback) (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint64_t serial_id,
- struct GNUNET_TIME_Absolute timestamp,
- const json_t *json);
+(*TALER_BANK_AdminAddIncomingCallback) (
+ void *cls,
+ const struct TALER_BANK_AdminAddIncomingResponse *air);
/**
@@ -190,20 +230,64 @@ struct TALER_BANK_TransferHandle;
/**
+ * Response details for a history request.
+ */
+struct TALER_BANK_TransferResponse
+{
+
+ /**
+ * HTTP status.
+ */
+ unsigned int http_status;
+
+ /**
+ * Taler error code, #TALER_EC_NONE on success.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * Full response, NULL if body was not in JSON format.
+ */
+ const json_t *response;
+
+ /**
+ * Details returned depending on the @e http_status.
+ */
+ union
+ {
+
+ /**
+ * Details if status was #MHD_HTTP_OK
+ */
+ struct
+ {
+
+
+ /**
+ * unique ID of the wire transfer in the bank's records
+ */
+ uint64_t row_id;
+
+ /**
+ * when did the transaction go into effect
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ } ok;
+ } details;
+};
+
+
+/**
* Function called with the result from the execute step.
*
* @param cls closure
- * @param response_code HTTP status code
- * @param ec taler error code
- * @param row_id unique ID of the wire transfer in the bank's records
- * @param timestamp when did the transaction go into effect
+ * @param tr response details
*/
typedef void
-(*TALER_BANK_TransferCallback)(void *cls,
- unsigned int response_code,
- enum TALER_ErrorCode ec,
- uint64_t row_id,
- struct GNUNET_TIME_Absolute timestamp);
+(*TALER_BANK_TransferCallback)(
+ void *cls,
+ const struct TALER_BANK_TransferResponse *tr);
/**
@@ -259,6 +343,11 @@ struct TALER_BANK_CreditHistoryHandle;
struct TALER_BANK_CreditDetails
{
/**
+ * Serial ID of the wire transfer.
+ */
+ uint64_t serial_id;
+
+ /**
* Amount that was transferred
*/
struct TALER_Amount amount;
@@ -266,25 +355,75 @@ struct TALER_BANK_CreditDetails
/**
* Time of the the transfer
*/
- struct GNUNET_TIME_Absolute execution_date;
+ struct GNUNET_TIME_Timestamp execution_date;
/**
- * Reserve public key encoded in the wire
- * transfer subject.
+ * Reserve public key encoded in the wire transfer subject.
*/
struct TALER_ReservePublicKeyP reserve_pub;
/**
- * payto://-URL of the source account that
- * send the funds.
+ * payto://-URL of the source account that send the funds.
+ */
+ const char *debit_account_uri;
+
+};
+
+
+/**
+ * Response details for a history request.
+ */
+struct TALER_BANK_CreditHistoryResponse
+{
+
+ /**
+ * HTTP status. Note that #MHD_HTTP_OK and #MHD_HTTP_NO_CONTENT are both
+ * successful replies, but @e details will only contain @e success information
+ * if this is set to #MHD_HTTP_OK.
+ */
+ unsigned int http_status;
+
+ /**
+ * Taler error code, #TALER_EC_NONE on success.
*/
- const char *debit_account_url;
+ enum TALER_ErrorCode ec;
/**
- * payto://-URL of the target account that
- * received the funds.
+ * Full response, NULL if body was not in JSON format.
*/
- const char *credit_account_url;
+ const json_t *response;
+
+ /**
+ * Details returned depending on the @e http_status.
+ */
+ union
+ {
+
+ /**
+ * Details if status was #MHD_HTTP_OK
+ */
+ struct
+ {
+
+ /**
+ * payto://-URL of the target account that received the funds.
+ */
+ const char *credit_account_uri;
+
+ /**
+ * Array of transactions received.
+ */
+ const struct TALER_BANK_CreditDetails *details;
+
+ /**
+ * Length of the @e details array.
+ */
+ unsigned int details_length;
+
+ } ok;
+
+ } details;
+
};
@@ -293,25 +432,12 @@ struct TALER_BANK_CreditDetails
* the bank for the credit transaction history.
*
* @param cls closure
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
- * 0 if the bank's reply is bogus (fails to follow the protocol),
- * #MHD_HTTP_NO_CONTENT if there are no more results; on success the
- * last callback is always of this status (even if `abs(num_results)` were
- * already returned).
- * @param ec detailed error code
- * @param serial_id monotonically increasing counter corresponding to the transaction
- * @param details details about the wire transfer
- * @param json detailed response from the HTTPD, or NULL if reply was not in JSON
- * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
+ * @param reply details about the response
*/
-typedef int
-(*TALER_BANK_CreditHistoryCallback) (
+typedef void
+(*TALER_BANK_CreditHistoryCallback)(
void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint64_t serial_id,
- const struct TALER_BANK_CreditDetails *details,
- const json_t *json);
+ const struct TALER_BANK_CreditHistoryResponse *reply);
/**
@@ -323,6 +449,8 @@ typedef int
* @param num_results how many results do we want; negative numbers to go into the past,
* positive numbers to go into the future starting at @a start_row;
* must not be zero.
+ * @param timeout how long the client is willing to wait for more results
+ * (only useful if @a num_results is positive)
* @param hres_cb the callback to call with the transaction history
* @param hres_cb_cls closure for the above callback
* @return NULL
@@ -330,12 +458,14 @@ typedef int
* In this case, the callback is not called.
*/
struct TALER_BANK_CreditHistoryHandle *
-TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
- const struct TALER_BANK_AuthenticationData *auth,
- uint64_t start_row,
- int64_t num_results,
- TALER_BANK_CreditHistoryCallback hres_cb,
- void *hres_cb_cls);
+TALER_BANK_credit_history (
+ struct GNUNET_CURL_Context *ctx,
+ const struct TALER_BANK_AuthenticationData *auth,
+ uint64_t start_row,
+ int64_t num_results,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_BANK_CreditHistoryCallback hres_cb,
+ void *hres_cb_cls);
/**
@@ -364,6 +494,11 @@ struct TALER_BANK_DebitHistoryHandle;
struct TALER_BANK_DebitDetails
{
/**
+ * Serial ID of the wire transfer.
+ */
+ uint64_t serial_id;
+
+ /**
* Amount that was transferred
*/
struct TALER_Amount amount;
@@ -371,7 +506,7 @@ struct TALER_BANK_DebitDetails
/**
* Time of the the transfer
*/
- struct GNUNET_TIME_Absolute execution_date;
+ struct GNUNET_TIME_Timestamp execution_date;
/**
* Wire transfer identifier used by the exchange.
@@ -384,16 +519,66 @@ struct TALER_BANK_DebitDetails
const char *exchange_base_url;
/**
- * payto://-URL of the source account that
- * send the funds.
+ * payto://-URI of the target account that received the funds.
*/
- const char *debit_account_url;
+ const char *credit_account_uri;
+
+};
+
+
+/**
+ * Response details for a history request.
+ */
+struct TALER_BANK_DebitHistoryResponse
+{
/**
- * payto://-URL of the target account that
- * received the funds.
+ * HTTP status. Note that #MHD_HTTP_OK and #MHD_HTTP_NO_CONTENT are both
+ * successful replies, but @e details will only contain @e success information
+ * if this is set to #MHD_HTTP_OK.
*/
- const char *credit_account_url;
+ unsigned int http_status;
+
+ /**
+ * Taler error code, #TALER_EC_NONE on success.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * Full response, NULL if body was not in JSON format.
+ */
+ const json_t *response;
+
+ /**
+ * Details returned depending on the @e http_status.
+ */
+ union
+ {
+
+ /**
+ * Details if status was #MHD_HTTP_OK
+ */
+ struct
+ {
+
+ /**
+ * payto://-URI of the source account that send the funds.
+ */
+ const char *debit_account_uri;
+
+ /**
+ * Array of transactions initiated.
+ */
+ const struct TALER_BANK_DebitDetails *details;
+
+ /**
+ * Length of the @e details array.
+ */
+ unsigned int details_length;
+
+ } ok;
+
+ } details;
};
@@ -403,25 +588,12 @@ struct TALER_BANK_DebitDetails
* the bank for the debit transaction history.
*
* @param cls closure
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
- * 0 if the bank's reply is bogus (fails to follow the protocol),
- * #MHD_HTTP_NO_CONTENT if there are no more results; on success the
- * last callback is always of this status (even if `abs(num_results)` were
- * already returned).
- * @param ec detailed error code
- * @param serial_id monotonically increasing counter corresponding to the transaction
- * @param details details about the wire transfer
- * @param json detailed response from the HTTPD, or NULL if reply was not in JSON
- * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
+ * @param reply details about the response
*/
-typedef int
-(*TALER_BANK_DebitHistoryCallback) (
+typedef void
+(*TALER_BANK_DebitHistoryCallback)(
void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint64_t serial_id,
- const struct TALER_BANK_DebitDetails *details,
- const json_t *json);
+ const struct TALER_BANK_DebitHistoryResponse *reply);
/**
@@ -433,6 +605,8 @@ typedef int
* @param num_results how many results do we want; negative numbers to go into the past,
* positive numbers to go into the future starting at @a start_row;
* must not be zero.
+ * @param timeout how long the client is willing to wait for more results
+ * (only useful if @a num_results is positive)
* @param hres_cb the callback to call with the transaction history
* @param hres_cb_cls closure for the above callback
* @return NULL
@@ -444,6 +618,7 @@ TALER_BANK_debit_history (struct GNUNET_CURL_Context *ctx,
const struct TALER_BANK_AuthenticationData *auth,
uint64_t start_row,
int64_t num_results,
+ struct GNUNET_TIME_Relative timeout,
TALER_BANK_DebitHistoryCallback hres_cb,
void *hres_cb_cls);
@@ -471,7 +646,7 @@ TALER_BANK_debit_history_cancel (struct TALER_BANK_DebitHistoryHandle *hh);
* @param[out] auth set to the configuration data found
* @return #GNUNET_OK on success
*/
-int
+enum GNUNET_GenericReturnValue
TALER_BANK_auth_parse_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
struct TALER_BANK_AuthenticationData *auth);
diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h
index 868bbebe9..b941316b5 100644
--- a/src/include/taler_crypto_lib.h
+++ b/src/include/taler_crypto_lib.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 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
@@ -18,14 +18,88 @@
* @brief taler-specific crypto functions
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
* @author Christian Grothoff <christian@grothoff.org>
+ * @author Özgür Kesim <oec-taler@kesim.org>
*/
+#if ! defined (__TALER_UTIL_LIB_H_INSIDE__)
+#error "Only <taler_util.h> can be included directly."
+#endif
+
#ifndef TALER_CRYPTO_LIB_H
#define TALER_CRYPTO_LIB_H
#include <gnunet/gnunet_util_lib.h>
-#include "taler_util.h"
-
+#include "taler_error_codes.h"
#include <gcrypt.h>
+#include <jansson.h>
+
+
+/**
+ * Maximum number of coins we allow per operation.
+ */
+#define TALER_MAX_FRESH_COINS 256
+
+/**
+ * Cut-and-choose size for refreshing. Client looses the gamble (of
+ * unaccountable transfers) with probability 1/TALER_CNC_KAPPA. Refresh cost
+ * increases linearly with TALER_CNC_KAPPA, and 3 is sufficient up to a
+ * income/sales tax of 66% of total transaction value. As there is
+ * no good reason to change this security parameter, we declare it
+ * fixed and part of the protocol.
+ */
+#define TALER_CNC_KAPPA 3
+#define TALER_CNC_KAPPA_MINUS_ONE_STR "2"
+
+
+/**
+ * Possible AML decision states.
+ */
+enum TALER_AmlDecisionState
+{
+
+ /**
+ * All AML requirements are currently satisfied.
+ */
+ TALER_AML_NORMAL = 0,
+
+ /**
+ * An AML investigation is pending.
+ */
+ TALER_AML_PENDING = 1,
+
+ /**
+ * An AML decision has concluded that the funds must be frozen.
+ */
+ TALER_AML_FROZEN = 2,
+
+ /**
+ * Maximum allowed numeric value for AML status.
+ */
+ TALER_AML_MAX = 2
+};
+
+
+/**
+ * Possible algorithms for confirmation code generation.
+ */
+enum TALER_MerchantConfirmationAlgorithm
+{
+
+ /**
+ * No purchase confirmation.
+ */
+ TALER_MCA_NONE = 0,
+
+ /**
+ * Purchase confirmation without payment
+ */
+ TALER_MCA_WITHOUT_PRICE = 1,
+
+ /**
+ * Purchase confirmation with payment
+ */
+ TALER_MCA_WITH_PRICE = 2
+
+};
/* ****************** Coin crypto primitives ************* */
@@ -33,6 +107,64 @@
GNUNET_NETWORK_STRUCT_BEGIN
/**
+ * @brief Type of public keys for Taler security modules (software or hardware).
+ * Note that there are usually at least two security modules (RSA and EdDSA),
+ * each with its own private key.
+ */
+struct TALER_SecurityModulePublicKeyP
+{
+ /**
+ * Taler uses EdDSA for security modules.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+/**
+ * @brief Set of the public keys of the security modules
+ */
+struct TALER_SecurityModulePublicKeySetP
+{
+ /**
+ * Public key of the RSA security module
+ */
+ struct TALER_SecurityModulePublicKeyP rsa;
+
+ /**
+ * Public key of the CS security module
+ */
+ struct TALER_SecurityModulePublicKeyP cs;
+
+ /**
+ * Public key of the eddsa security module
+ */
+ struct TALER_SecurityModulePublicKeyP eddsa;
+};
+
+/**
+ * @brief Type of private keys for Taler security modules (software or hardware).
+ */
+struct TALER_SecurityModulePrivateKeyP
+{
+ /**
+ * Taler uses EdDSA for security modules.
+ */
+ struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of signatures used for Taler security modules (software or hardware).
+ */
+struct TALER_SecurityModuleSignatureP
+{
+ /**
+ * Taler uses EdDSA for security modules.
+ */
+ struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
+};
+
+
+/**
* @brief Type of public keys for Taler reserves.
*/
struct TALER_ReservePublicKeyP
@@ -69,6 +201,18 @@ struct TALER_ReserveSignatureP
/**
+ * (Symmetric) key used to encrypt KYC attribute data in the database.
+ */
+struct TALER_AttributeKeyP
+{
+ /**
+ * Actual key material.
+ */
+ struct GNUNET_HashCode key;
+};
+
+
+/**
* @brief Type of public keys to for merchant authorizations.
* Merchants can issue refunds using the corresponding
* private key.
@@ -122,7 +266,7 @@ struct TALER_TransferPublicKeyP
/**
- * @brief Type of transfer public keys used during refresh
+ * @brief Type of transfer private keys used during refresh
* operations.
*/
struct TALER_TransferPrivateKeyP
@@ -135,6 +279,32 @@ struct TALER_TransferPrivateKeyP
/**
+ * @brief Type of public keys used for contract
+ * encryption.
+ */
+struct TALER_ContractDiffiePublicP
+{
+ /**
+ * Taler uses ECDHE for contract encryption.
+ */
+ struct GNUNET_CRYPTO_EcdhePublicKey ecdhe_pub;
+};
+
+
+/**
+ * @brief Type of private keys used for contract
+ * encryption.
+ */
+struct TALER_ContractDiffiePrivateP
+{
+ /**
+ * Taler uses ECDHE for contract encryption.
+ */
+ struct GNUNET_CRYPTO_EcdhePrivateKey ecdhe_priv;
+};
+
+
+/**
* @brief Type of online public keys used by the exchange to sign
* messages.
*/
@@ -185,50 +355,62 @@ struct TALER_MasterPublicKeyP
/**
- * @brief Type of the public key used by the auditor.
+ * @brief Type of the offline master public keys used by the exchange.
*/
-struct TALER_AuditorPublicKeyP
+struct TALER_MasterPrivateKeyP
{
/**
- * Taler uses EdDSA for the auditor's signing key.
+ * Taler uses EdDSA for the long-term offline master key.
*/
- struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+ struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
};
/**
- * @brief Type of signatures used by the auditor.
+ * @brief Type of signatures by the offline master public key used by the exchange.
*/
-struct TALER_AuditorSignatureP
+struct TALER_MasterSignatureP
{
/**
- * Taler uses EdDSA signatures for auditors.
+ * Taler uses EdDSA for the long-term offline master key.
*/
- struct GNUNET_CRYPTO_EddsaSignature eddsa_sig;
+ struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
};
/**
- * @brief Type of the offline master public keys used by the exchange.
+ * @brief Type of the private key used by the auditor.
*/
-struct TALER_MasterPrivateKeyP
+struct TALER_AuditorPrivateKeyP
{
/**
- * Taler uses EdDSA for the long-term offline master key.
+ * Taler uses EdDSA for the auditor's signing key.
*/
struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
};
/**
- * @brief Type of signatures by the offline master public key used by the exchange.
+ * @brief Type of the public key used by the auditor.
*/
-struct TALER_MasterSignatureP
+struct TALER_AuditorPublicKeyP
{
/**
- * Taler uses EdDSA for the long-term offline master key.
+ * Taler uses EdDSA for the auditor's signing key.
*/
- struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
+ struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+
+/**
+ * @brief Type of signatures used by the auditor.
+ */
+struct TALER_AuditorSignatureP
+{
+ /**
+ * Taler uses EdDSA signatures for auditors.
+ */
+ struct GNUNET_CRYPTO_EddsaSignature eddsa_sig;
};
@@ -258,7 +440,6 @@ struct TALER_CoinSpendPrivateKeyP
struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
};
-
/**
* @brief Type of signatures made with Taler coins.
*/
@@ -272,14 +453,163 @@ struct TALER_CoinSpendSignatureP
/**
- * @brief Type of blinding keys for Taler.
+ * @brief Type of private keys for age commitment in coins.
+ */
+struct TALER_AgeCommitmentPrivateKeyP
+{
+#ifdef AGE_RESTRICTION_WITH_ECDSA
+ /**
+ * Taler uses EcDSA for coins when signing age verification attestation.
+ */
+ struct GNUNET_CRYPTO_EcdsaPrivateKey priv;
+#else
+ /**
+ * Taler uses Edx25519 for coins when signing age verification attestation.
+ */
+ struct GNUNET_CRYPTO_Edx25519PrivateKey priv;
+#endif
+};
+
+
+/**
+ * @brief Type of public keys for age commitment in coins.
+ */
+struct TALER_AgeCommitmentPublicKeyP
+{
+#ifdef AGE_RESTRICTION_WITH_ECDSA
+ /**
+ * Taler uses EcDSA for coins when signing age verification attestation.
+ */
+ struct GNUNET_CRYPTO_EcdsaPublicKey pub;
+#else
+ /**
+ * Taler uses Edx25519 for coins when signing age verification attestation.
+ */
+ struct GNUNET_CRYPTO_Edx25519PublicKey pub;
+#endif
+};
+
+
+/*
+ * @brief Hash to represent the commitment to n*kappa blinded keys during a
+ * age-withdrawal. It is the running SHA512 hash over the hashes of the blinded
+ * envelopes of n*kappa coins.
+ */
+struct TALER_AgeWithdrawCommitmentHashP
+{
+ struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * @brief Type of online public keys used by the wallet to establish a purse and the associated contract meta data.
*/
-struct TALER_DenominationBlindingKeyP
+struct TALER_PurseContractPublicKeyP
{
/**
- * Taler uses RSA for blind signatures.
+ * Taler uses EdDSA for purse message signing.
*/
- struct GNUNET_CRYPTO_RsaBlindingKeySecret bks;
+ struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+
+/**
+ * @brief Type of online private keys used by the wallet to
+ * bind a purse to a particular contract (and other meta data).
+ */
+struct TALER_PurseContractPrivateKeyP
+{
+ /**
+ * Taler uses EdDSA for online signatures sessions.
+ */
+ struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of signatures used by the wallet to sign purse creation messages online.
+ */
+struct TALER_PurseContractSignatureP
+{
+ /**
+ * Taler uses EdDSA for online signatures sessions.
+ */
+ struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
+};
+
+
+/**
+ * @brief Type of online public keys used by the wallet to
+ * sign a merge of a purse into an account.
+ */
+struct TALER_PurseMergePublicKeyP
+{
+ /**
+ * Taler uses EdDSA for purse message signing.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+
+/**
+ * @brief Type of online private keys used by the wallet to
+ * sign a merge of a purse into an account.
+ */
+struct TALER_PurseMergePrivateKeyP
+{
+ /**
+ * Taler uses EdDSA for online signatures sessions.
+ */
+ struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of signatures used by the wallet to sign purse merge requests online.
+ */
+struct TALER_PurseMergeSignatureP
+{
+ /**
+ * Taler uses EdDSA for online signatures sessions.
+ */
+ struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
+};
+
+
+/**
+ * @brief Type of online public keys used by AML officers.
+ */
+struct TALER_AmlOfficerPublicKeyP
+{
+ /**
+ * Taler uses EdDSA for AML decision signing.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+
+/**
+ * @brief Type of online private keys used to identify
+ * AML officers.
+ */
+struct TALER_AmlOfficerPrivateKeyP
+{
+ /**
+ * Taler uses EdDSA for AML decision signing.
+ */
+ struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of signatures used by AML officers.
+ */
+struct TALER_AmlOfficerSignatureP
+{
+ /**
+ * Taler uses EdDSA for AML decision signing.
+ */
+ struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
};
@@ -296,30 +626,635 @@ struct TALER_RefreshCommitmentP
};
+/**
+ * Symmetric key we use to encrypt KYC attributes
+ * in our database.
+ */
+struct TALER_AttributeEncryptionKeyP
+{
+ /**
+ * The key is a hash code.
+ */
+ struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Token used for access control to the merchant's unclaimed
+ * orders.
+ */
+struct TALER_ClaimTokenP
+{
+ /**
+ * The token is a 128-bit UUID.
+ */
+ struct GNUNET_Uuid token;
+};
+
+
+/**
+ * Salt used to hash a merchant's payto:// URI to
+ * compute the "h_wire" (say for deposit requests).
+ */
+struct TALER_WireSaltP
+{
+ /**
+ * Actual 128-bit salt value.
+ */
+ uint32_t salt[4];
+};
+
+
+/**
+ * Hash used to represent an CS public key. Does not include age
+ * restrictions and is ONLY for CS. Used ONLY for interactions with the CS
+ * security module.
+ */
+struct TALER_CsPubHashP
+{
+ /**
+ * Actual hash value.
+ */
+ struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Hash used to represent an RSA public key. Does not include age
+ * restrictions and is ONLY for RSA. Used ONLY for interactions with the RSA
+ * security module.
+ */
+struct TALER_RsaPubHashP
+{
+ /**
+ * Actual hash value.
+ */
+ struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Master key material for the deriviation of
+ * private coins and blinding factors during
+ * withdraw or refresh.
+ */
+struct TALER_PlanchetMasterSecretP
+{
+
+ /**
+ * Key material.
+ */
+ uint32_t key_data[8];
+
+};
+
+
+/**
+ * Master key material for the deriviation of
+ * private coins and blinding factors.
+ */
+struct TALER_RefreshMasterSecretP
+{
+
+ /**
+ * Key material.
+ */
+ uint32_t key_data[8];
+
+};
+
+
+/**
+ * Hash used to represent a denomination public key
+ * and associated age restrictions (if any).
+ */
+struct TALER_DenominationHashP
+{
+ /**
+ * Actual hash value.
+ */
+ struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Hash used to represent the private part
+ * of a contract between merchant and consumer.
+ */
+struct TALER_PrivateContractHashP
+{
+ /**
+ * Actual hash value.
+ */
+ struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Hash used to represent the policy extension to a deposit
+ */
+struct TALER_ExtensionPolicyHashP
+{
+ /**
+ * Actual hash value.
+ */
+ struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Hash used to represent the salted hash of a
+ * merchant's bank account.
+ */
+struct TALER_MerchantWireHashP
+{
+ /**
+ * Actual hash value.
+ */
+ struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Hash used to represent the unsalted hash of a
+ * payto:// URI representing a bank account.
+ */
+struct TALER_PaytoHashP
+{
+ /**
+ * Actual hash value.
+ */
+ struct GNUNET_ShortHashCode hash;
+};
+
+
+/**
+ * Hash used to represent a commitment to a blinded
+ * coin, i.e. the hash of the envelope.
+ */
+struct TALER_BlindedCoinHashP
+{
+ /**
+ * Actual hash value.
+ */
+ struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Hash used to represent the hash of the public
+ * key of a coin (without blinding).
+ */
+struct TALER_CoinPubHashP
+{
+ /**
+ * Actual hash value.
+ */
+ struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * @brief Value that uniquely identifies a reward.
+ */
+struct TALER_RewardIdentifierP
+{
+ /**
+ * The tip identifier is a SHA-512 hash code.
+ */
+ struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * @brief Value that uniquely identifies a tip pick up operation.
+ */
+struct TALER_PickupIdentifierP
+{
+ /**
+ * The pickup identifier is a SHA-512 hash code.
+ */
+ struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * @brief Salted hash over the JSON object representing the manifests of
+ * extensions.
+ */
+struct TALER_ExtensionManifestsHashP
+{
+ /**
+ * Actual hash value.
+ */
+ struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Set of the fees applying to a denomination.
+ */
+struct TALER_DenomFeeSetNBOP
+{
+
+ /**
+ * The fee the exchange charges when a coin of this type is withdrawn.
+ * (can be zero).
+ */
+ struct TALER_AmountNBO withdraw;
+
+ /**
+ * The fee the exchange charges when a coin of this type is deposited.
+ * (can be zero).
+ */
+ struct TALER_AmountNBO deposit;
+
+ /**
+ * The fee the exchange charges when a coin of this type is refreshed.
+ * (can be zero).
+ */
+ struct TALER_AmountNBO refresh;
+
+ /**
+ * The fee the exchange charges when a coin of this type is refunded.
+ * (can be zero). Note that refund fees are charged to the customer;
+ * if a refund is given, the deposit fee is also refunded.
+ */
+ struct TALER_AmountNBO refund;
+
+};
+
+
+/**
+ * Set of the fees applying for a given
+ * time-range and wire method.
+ */
+struct TALER_WireFeeSetNBOP
+{
+
+ /**
+ * The fee the exchange charges for wiring funds
+ * to a merchant.
+ */
+ struct TALER_AmountNBO wire;
+
+ /**
+ * The fee the exchange charges for closing a reserve
+ * and wiring the funds back to the origin account.
+ */
+ struct TALER_AmountNBO closing;
+
+};
+
+
+/**
+ * Set of the fees applying globally for a given
+ * time-range.
+ */
+struct TALER_GlobalFeeSetNBOP
+{
+
+ /**
+ * The fee the exchange charges for returning the history of a reserve or
+ * account.
+ */
+ struct TALER_AmountNBO history;
+
+ /**
+ * The fee the exchange charges for keeping an account or reserve open for a
+ * year.
+ */
+ struct TALER_AmountNBO account;
+
+ /**
+ * The fee the exchange charges if a purse is abandoned and this was not
+ * covered by the account limit.
+ */
+ struct TALER_AmountNBO purse;
+};
+
+
GNUNET_NETWORK_STRUCT_END
/**
+ * Compute RFC 3548 base32 decoding of @a val and write
+ * result to @a udata.
+ *
+ * @param val value to decode
+ * @param val_size number of bytes in @a val
+ * @param key is the val in bits
+ * @param key_len is the size of @a key
+ */
+int
+TALER_rfc3548_base32decode (const char *val,
+ size_t val_size,
+ void *key,
+ size_t key_len);
+
+
+/**
+ * @brief Builds POS confirmation token to verify payment.
+ *
+ * @param pos_key encoded key for verification payment
+ * @param pos_alg algorithm to compute the payment verification
+ * @param total of the order paid
+ * @param ts is the time given
+ * @return POS token on success, NULL otherwise
+ */
+char *
+TALER_build_pos_confirmation (const char *pos_key,
+ enum TALER_MerchantConfirmationAlgorithm pos_alg,
+ const struct TALER_Amount *total,
+ struct GNUNET_TIME_Timestamp ts);
+
+
+/**
+ * Set of the fees applying to a denomination.
+ */
+struct TALER_DenomFeeSet
+{
+
+ /**
+ * The fee the exchange charges when a coin of this type is withdrawn.
+ * (can be zero).
+ */
+ struct TALER_Amount withdraw;
+
+ /**
+ * The fee the exchange charges when a coin of this type is deposited.
+ * (can be zero).
+ */
+ struct TALER_Amount deposit;
+
+ /**
+ * The fee the exchange charges when a coin of this type is refreshed.
+ * (can be zero).
+ */
+ struct TALER_Amount refresh;
+
+ /**
+ * The fee the exchange charges when a coin of this type is refunded.
+ * (can be zero). Note that refund fees are charged to the customer;
+ * if a refund is given, the deposit fee is also refunded.
+ */
+ struct TALER_Amount refund;
+
+};
+
+
+/**
+ * Set of the fees applying for a given time-range and wire method.
+ */
+struct TALER_WireFeeSet
+{
+
+ /**
+ * The fee the exchange charges for wiring funds to a merchant.
+ */
+ struct TALER_Amount wire;
+
+ /**
+ * The fee the exchange charges for closing a reserve
+ * and wiring the funds back to the origin account.
+ */
+ struct TALER_Amount closing;
+
+};
+
+
+/**
+ * Set of the fees applying globally for a given
+ * time-range.
+ */
+struct TALER_GlobalFeeSet
+{
+
+ /**
+ * The fee the exchange charges for returning the
+ * history of a reserve or account.
+ */
+ struct TALER_Amount history;
+
+ /**
+ * The fee the exchange charges for keeping
+ * an account or reserve open for a year.
+ */
+ struct TALER_Amount account;
+
+ /**
+ * The fee the exchange charges if a purse
+ * is abandoned and this was not covered by
+ * the account limit.
+ */
+ struct TALER_Amount purse;
+};
+
+
+/**
+ * Convert fee set from host to network byte order.
+ *
+ * @param[out] nbo where to write the result
+ * @param fees fee set to convert
+ */
+void
+TALER_denom_fee_set_hton (struct TALER_DenomFeeSetNBOP *nbo,
+ const struct TALER_DenomFeeSet *fees);
+
+
+/**
+ * Convert fee set from network to host network byte order.
+ *
+ * @param[out] fees where to write the result
+ * @param nbo fee set to convert
+ */
+void
+TALER_denom_fee_set_ntoh (struct TALER_DenomFeeSet *fees,
+ const struct TALER_DenomFeeSetNBOP *nbo);
+
+
+/**
+ * Convert global fee set from host to network byte order.
+ *
+ * @param[out] nbo where to write the result
+ * @param fees fee set to convert
+ */
+void
+TALER_global_fee_set_hton (struct TALER_GlobalFeeSetNBOP *nbo,
+ const struct TALER_GlobalFeeSet *fees);
+
+
+/**
+ * Convert global fee set from network to host network byte order.
+ *
+ * @param[out] fees where to write the result
+ * @param nbo fee set to convert
+ */
+void
+TALER_global_fee_set_ntoh (struct TALER_GlobalFeeSet *fees,
+ const struct TALER_GlobalFeeSetNBOP *nbo);
+
+
+/**
+ * Compare global fee sets.
+ *
+ * @param f1 first set to compare
+ * @param f2 second set to compare
+ * @return 0 if sets are equal
+ */
+int
+TALER_global_fee_set_cmp (const struct TALER_GlobalFeeSet *f1,
+ const struct TALER_GlobalFeeSet *f2);
+
+
+/**
+ * Convert wire fee set from host to network byte order.
+ *
+ * @param[out] nbo where to write the result
+ * @param fees fee set to convert
+ */
+void
+TALER_wire_fee_set_hton (struct TALER_WireFeeSetNBOP *nbo,
+ const struct TALER_WireFeeSet *fees);
+
+
+/**
+ * Convert wire fee set from network to host network byte order.
+ *
+ * @param[out] fees where to write the result
+ * @param nbo fee set to convert
+ */
+void
+TALER_wire_fee_set_ntoh (struct TALER_WireFeeSet *fees,
+ const struct TALER_WireFeeSetNBOP *nbo);
+
+
+/**
+ * Compare wire fee sets.
+ *
+ * @param f1 first set to compare
+ * @param f2 second set to compare
+ * @return 0 if sets are equal
+ */
+int
+TALER_wire_fee_set_cmp (const struct TALER_WireFeeSet *f1,
+ const struct TALER_WireFeeSet *f2);
+
+
+/**
+ * Hash @a rsa.
+ *
+ * @param rsa key to hash
+ * @param[out] h_rsa where to write the result
+ */
+void
+TALER_rsa_pub_hash (const struct GNUNET_CRYPTO_RsaPublicKey *rsa,
+ struct TALER_RsaPubHashP *h_rsa);
+
+
+/**
+ * Hash @a cs.
+ *
+ * @param cs key to hash
+ * @param[out] h_cs where to write the result
+ */
+void
+TALER_cs_pub_hash (const struct GNUNET_CRYPTO_CsPublicKey *cs,
+ struct TALER_CsPubHashP *h_cs);
+
+
+/**
* @brief Type of (unblinded) coin signatures for Taler.
*/
struct TALER_DenominationSignature
{
/**
- * Taler uses RSA for blinding.
+ * Denominations use blind signatures.
*/
- struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
+ struct GNUNET_CRYPTO_UnblindedSignature *unblinded_sig;
+};
+
+
+/**
+ * @brief Type for *blinded* denomination signatures for Taler.
+ * Must be unblinded before it becomes valid.
+ */
+struct TALER_BlindedDenominationSignature
+{
+ /**
+ * Denominations use blind signatures.
+ */
+ struct GNUNET_CRYPTO_BlindedSignature *blinded_sig;
+};
+
+
+/* *************** Age Restriction *********************************** */
+
+/*
+ * @brief Type of a list of age groups, represented as bit mask.
+ *
+ * The bits set in the mask mark the edges at the beginning of a next age
+ * group. F.e. for the age groups
+ * 0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-21, 21-*
+ * the following bits are set:
+ *
+ * 31 24 16 8 0
+ * | | | | |
+ * oooooooo oo1oo1o1 o1o1o1o1 ooooooo1
+ *
+ * A value of 0 means that the exchange does not support the extension for
+ * age-restriction.
+ *
+ * For a non-0 age mask, the 0th bit always must be set, otherwise the age
+ * mask is considered invalid.
+ */
+struct TALER_AgeMask
+{
+ uint32_t bits;
+};
+
+/**
+ * @brief Age commitment of a coin.
+ */
+struct TALER_AgeCommitmentHash
+{
+ /**
+ * The commitment is a SHA-256 hash code.
+ */
+ struct GNUNET_ShortHashCode shash;
+};
+
+/**
+ * @brief Signature of an age with the private key for the corresponding age group of an age commitment.
+ */
+struct TALER_AgeAttestation
+{
+#ifdef AGE_RESTRICTION_WITH_ECDSA
+ struct GNUNET_CRYPTO_EcdsaSignature signature;
+#else
+ struct GNUNET_CRYPTO_Edx25519Signature signature;
+#endif
};
+#define TALER_AgeCommitmentHash_isNullOrZero(ph) ((NULL == ph) || \
+ GNUNET_is_zero (ph))
/**
* @brief Type of public signing keys for verifying blindly signed coins.
*/
struct TALER_DenominationPublicKey
{
+
/**
- * Taler uses RSA for signing coins.
+ * Age restriction mask used for the key.
*/
- struct GNUNET_CRYPTO_RsaPublicKey *rsa_public_key;
+ struct TALER_AgeMask age_mask;
+
+ /**
+ * Type of the public key.
+ */
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub_key;
+
};
@@ -328,10 +1263,22 @@ struct TALER_DenominationPublicKey
*/
struct TALER_DenominationPrivateKey
{
+
+ struct GNUNET_CRYPTO_BlindSignPrivateKey *bsign_priv_key;
+
+};
+
+
+/**
+ * @brief Blinded planchet send to exchange for blind signing.
+ */
+struct TALER_BlindedPlanchet
+{
/**
- * Taler uses RSA for signing coins.
+ * A blinded message.
*/
- struct GNUNET_CRYPTO_RsaPrivateKey *rsa_private_key;
+ struct GNUNET_CRYPTO_BlindedMessage *blinded_message;
+
};
@@ -351,7 +1298,18 @@ struct TALER_CoinPublicInfo
* Hash of the public key representing the denomination of the coin that is
* being deposited.
*/
- struct GNUNET_HashCode denom_pub_hash;
+ struct TALER_DenominationHashP denom_pub_hash;
+
+ /**
+ * Hash of the age commitment. If no age commitment was provided, it must be
+ * set to all zeroes.
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
+ * True, if age commitment is not applicable.
+ */
+ bool no_age_commitment;
/**
* (Unblinded) signature over @e coin_pub with @e denom_pub,
@@ -370,7 +1328,7 @@ struct TALER_TrackTransferDetails
/**
* Hash of the proposal data.
*/
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
/**
* Which coin was deposited?
@@ -378,19 +1336,391 @@ struct TALER_TrackTransferDetails
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
- * Value of the deposit (including fee).
+ * Value of the deposit (including fee), after refunds.
*/
struct TALER_Amount coin_value;
/**
- * Fee charged by the exchange for the deposit.
+ * Fee charged by the exchange for the deposit,
+ * possibly reduced (or waived) due to refunds.
*/
struct TALER_Amount coin_fee;
+ /**
+ * Total amount of refunds applied to this coin.
+ */
+ struct TALER_Amount refund_total;
+
+};
+
+
+/**
+ * @brief Inputs needed from the exchange for blind signing.
+ */
+struct TALER_ExchangeWithdrawValues
+{
+
+ /**
+ * Input values.
+ */
+ struct GNUNET_CRYPTO_BlindingInputValues *blinding_inputs;
};
/**
+ * Return the alg value singleton for creation of
+ * blinding secrets for RSA.
+ *
+ * @return singleton to use for RSA blinding
+ */
+const struct TALER_ExchangeWithdrawValues *
+TALER_denom_ewv_rsa_singleton (void);
+
+
+/**
+ * Make a (deep) copy of the given @a bi_src to
+ * @a bi_dst.
+ *
+ * @param[out] bi_dst target to copy to
+ * @param bi_src blinding input values to copy
+ */
+void
+TALER_denom_ewv_copy (
+ struct TALER_ExchangeWithdrawValues *bi_dst,
+ const struct TALER_ExchangeWithdrawValues *bi_src);
+
+
+/**
+ * Create private key for a Taler coin.
+ * @param ps planchet secret to derive coin priv key
+ * @param alg_values includes algorithm specific values
+ * @param[out] coin_priv private key to initialize
+ */
+void
+TALER_planchet_setup_coin_priv (
+ const struct TALER_PlanchetMasterSecretP *ps,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ struct TALER_CoinSpendPrivateKeyP *coin_priv);
+
+
+/**
+ * @brief Method to derive withdraw /csr nonce
+ *
+ * @param ps planchet secrets of the coin
+ * @param[out] nonce withdraw nonce included in the request to generate R_0 and R_1
+ */
+void
+TALER_cs_withdraw_nonce_derive (
+ const struct TALER_PlanchetMasterSecretP *ps,
+ struct GNUNET_CRYPTO_CsSessionNonce *nonce);
+
+
+/**
+ * @brief Method to derive /csr nonce
+ * to be used during refresh/melt operation.
+ *
+ * @param rms secret input for the refresh operation
+ * @param idx index of the fresh coin
+ * @param[out] nonce set to nonce included in the request to generate R_0 and R_1
+ */
+void
+TALER_cs_refresh_nonce_derive (
+ const struct TALER_RefreshMasterSecretP *rms,
+ uint32_t idx,
+ struct GNUNET_CRYPTO_CsSessionNonce *nonce);
+
+
+/**
+ * Initialize denomination public-private key pair.
+ *
+ * For #GNUNET_CRYPTO_BSA_RSA, an additional "unsigned int"
+ * argument with the number of bits for 'n' (e.g. 2048) must
+ * be passed.
+ *
+ * @param[out] denom_priv where to write the private key
+ * @param[out] denom_pub where to write the public key
+ * @param cipher which type of cipher to use
+ * @param ... RSA key size (eg. 2048/3072/4096)
+ * @return #GNUNET_OK on success, #GNUNET_NO if parameters were invalid
+ */
+enum GNUNET_GenericReturnValue
+TALER_denom_priv_create (struct TALER_DenominationPrivateKey *denom_priv,
+ struct TALER_DenominationPublicKey *denom_pub,
+ enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher,
+ ...);
+
+
+/**
+ * Free internals of @a denom_pub, but not @a denom_pub itself.
+ *
+ * @param[in] denom_pub key to free
+ */
+void
+TALER_denom_pub_free (struct TALER_DenominationPublicKey *denom_pub);
+
+
+/**
+ * Free internals of @a ewv, but not @a ewv itself.
+ *
+ * @param[in] ewv input values to free
+ */
+void
+TALER_denom_ewv_free (struct TALER_ExchangeWithdrawValues *ewv);
+
+
+/**
+ * Free internals of @a denom_priv, but not @a denom_priv itself.
+ *
+ * @param[in] denom_priv key to free
+ */
+void
+TALER_denom_priv_free (struct TALER_DenominationPrivateKey *denom_priv);
+
+
+/**
+ * Free internals of @a denom_sig, but not @a denom_sig itself.
+ *
+ * @param[in] denom_sig signature to free
+ */
+void
+TALER_denom_sig_free (struct TALER_DenominationSignature *denom_sig);
+
+
+/**
+ * Blind coin for blind signing with @a dk using blinding secret @a coin_bks.
+ *
+ * NOTE: As a particular oddity, the @a blinded_planchet is only partially
+ * initialized by this function in the case of CS-denominations. Here, the
+ * 'nonce' must be initialized separately!
+ *
+ * @param dk denomination public key to blind for
+ * @param coin_bks blinding secret to use
+ * @param nonce nonce used to derive session values,
+ * could be NULL for ciphers that do not use it
+ * @param age_commitment_hash hash of the age commitment to be used for the coin. NULL if no commitment is made.
+ * @param coin_pub public key of the coin to blind
+ * @param alg_values algorithm specific values to blind the planchet
+ * @param[out] c_hash resulting hashed coin
+ * @param[out] blinded_planchet planchet data to initialize
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_denom_blind (const struct TALER_DenominationPublicKey *dk,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_AgeCommitmentHash *age_commitment_hash,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ struct TALER_CoinPubHashP *c_hash,
+ struct TALER_BlindedPlanchet *blinded_planchet);
+
+
+/**
+ * Create blinded signature.
+ *
+ * @param[out] denom_sig where to write the signature
+ * @param denom_priv private key to use for signing
+ * @param for_melt true to use the HKDF for melt
+ * @param blinded_planchet the planchet already blinded
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_denom_sign_blinded (struct TALER_BlindedDenominationSignature *denom_sig,
+ const struct TALER_DenominationPrivateKey *denom_priv,
+ bool for_melt,
+ const struct TALER_BlindedPlanchet *blinded_planchet);
+
+
+/**
+ * Unblind blinded signature.
+ *
+ * @param[out] denom_sig where to write the unblinded signature
+ * @param bdenom_sig the blinded signature
+ * @param bks blinding secret to use
+ * @param c_hash hash of the coin's public key for verification of the signature
+ * @param alg_values algorithm specific values
+ * @param denom_pub public key used for signing
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_denom_sig_unblind (
+ struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_BlindedDenominationSignature *bdenom_sig,
+ const union GNUNET_CRYPTO_BlindingSecretP *bks,
+ const struct TALER_CoinPubHashP *c_hash,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ const struct TALER_DenominationPublicKey *denom_pub);
+
+
+/**
+ * Free internals of @a denom_sig, but not @a denom_sig itself.
+ *
+ * @param[in] denom_sig signature to free
+ */
+void
+TALER_blinded_denom_sig_free (
+ struct TALER_BlindedDenominationSignature *denom_sig);
+
+
+/**
+ * Compute the hash of the given @a denom_pub.
+ *
+ * @param denom_pub public key to hash
+ * @param[out] denom_hash resulting hash value
+ */
+void
+TALER_denom_pub_hash (const struct TALER_DenominationPublicKey *denom_pub,
+ struct TALER_DenominationHashP *denom_hash);
+
+
+/**
+ * Make a (deep) copy of the given @a denom_src to
+ * @a denom_dst.
+ *
+ * @param[out] denom_dst target to copy to
+ * @param denom_src public key to copy
+ */
+void
+TALER_denom_pub_copy (struct TALER_DenominationPublicKey *denom_dst,
+ const struct TALER_DenominationPublicKey *denom_src);
+
+
+/**
+ * Make a (deep) copy of the given @a denom_src to
+ * @a denom_dst.
+ *
+ * @param[out] denom_dst target to copy to
+ * @param denom_src public key to copy
+ */
+void
+TALER_denom_sig_copy (struct TALER_DenominationSignature *denom_dst,
+ const struct TALER_DenominationSignature *denom_src);
+
+
+/**
+ * Make a (deep) copy of the given @a denom_src to
+ * @a denom_dst.
+ *
+ * @param[out] denom_dst target to copy to
+ * @param denom_src public key to copy
+ */
+void
+TALER_blinded_denom_sig_copy (
+ struct TALER_BlindedDenominationSignature *denom_dst,
+ const struct TALER_BlindedDenominationSignature *denom_src);
+
+
+/**
+ * Compare two denomination public keys.
+ *
+ * @param denom1 first key
+ * @param denom2 second key
+ * @return 0 if the keys are equal, otherwise -1 or 1
+ */
+int
+TALER_denom_pub_cmp (const struct TALER_DenominationPublicKey *denom1,
+ const struct TALER_DenominationPublicKey *denom2);
+
+
+/**
+ * Compare two denomination signatures.
+ *
+ * @param sig1 first signature
+ * @param sig2 second signature
+ * @return 0 if the keys are equal, otherwise -1 or 1
+ */
+int
+TALER_denom_sig_cmp (const struct TALER_DenominationSignature *sig1,
+ const struct TALER_DenominationSignature *sig2);
+
+
+/**
+ * Compare two blinded denomination signatures.
+ *
+ * @param sig1 first signature
+ * @param sig2 second signature
+ * @return 0 if the keys are equal, otherwise -1 or 1
+ */
+int
+TALER_blinded_denom_sig_cmp (
+ const struct TALER_BlindedDenominationSignature *sig1,
+ const struct TALER_BlindedDenominationSignature *sig2);
+
+
+/**
+ * Compare two blinded planchets.
+ *
+ * @param bp1 first blinded planchet
+ * @param bp2 second blinded planchet
+ * @return 0 if the keys are equal, otherwise -1 or 1
+ */
+int
+TALER_blinded_planchet_cmp (
+ const struct TALER_BlindedPlanchet *bp1,
+ const struct TALER_BlindedPlanchet *bp2);
+
+
+/**
+ * Verify signature made with a denomination public key
+ * over a coin.
+ *
+ * @param denom_pub public denomination key
+ * @param denom_sig signature made with the private key
+ * @param c_hash hash over the coin
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_denom_pub_verify (const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_CoinPubHashP *c_hash);
+
+
+/**
+ * Encrypts KYC attributes for storage in the database.
+ *
+ * @param key encryption key to use
+ * @param attr set of attributes to encrypt
+ * @param[out] enc_attr encrypted attribute data
+ * @param[out] enc_attr_size number of bytes in @a enc_attr
+ */
+void
+TALER_CRYPTO_kyc_attributes_encrypt (
+ const struct TALER_AttributeEncryptionKeyP *key,
+ const json_t *attr,
+ void **enc_attr,
+ size_t *enc_attr_size);
+
+
+/**
+ * Encrypts KYC attributes for storage in the database.
+ *
+ * @param key encryption key to use
+ * @param enc_attr encrypted attribute data
+ * @param enc_attr_size number of bytes in @a enc_attr
+ * @return set of decrypted attributes, NULL on failure
+ */
+json_t *
+TALER_CRYPTO_kyc_attributes_decrypt (
+ const struct TALER_AttributeEncryptionKeyP *key,
+ const void *enc_attr,
+ size_t enc_attr_size);
+
+
+/**
+ * Takes a set of KYC attributes and extracts key
+ * data that we use to detect similar / duplicate
+ * entries in the database.
+ *
+ * @param attr set of KYC attributes
+ * @param[out] kyc_prox set to the proximity hash
+ */
+void
+TALER_CRYPTO_attributes_to_kyc_prox (
+ const json_t *attr,
+ struct GNUNET_ShortHashCode *kyc_prox);
+
+
+/**
* Check if a coin is valid; that is, whether the denomination key exists,
* is not expired, and the signature is correct.
*
@@ -400,36 +1730,46 @@ struct TALER_TrackTransferDetails
* #GNUNET_NO if it is invalid
* #GNUNET_SYSERR if an internal error occurred
*/
-int
+enum GNUNET_GenericReturnValue
TALER_test_coin_valid (const struct TALER_CoinPublicInfo *coin_public_info,
const struct TALER_DenominationPublicKey *denom_pub);
-GNUNET_NETWORK_STRUCT_BEGIN
-
/**
- * Header for serializations of coin-specific information about the
- * fresh coins we generate. These are the secrets that arise during
- * planchet generation, which is the first stage of creating a new
- * coin.
+ * Compute the hash of a blinded coin.
+ *
+ * @param blinded_planchet blinded planchet
+ * @param denom_hash hash of the denomination public key
+ * @param[out] bch where to write the hash
*/
-struct TALER_PlanchetSecretsP
-{
-
- /**
- * Private key of the coin.
- */
- struct TALER_CoinSpendPrivateKeyP coin_priv;
+void
+TALER_coin_ev_hash (const struct TALER_BlindedPlanchet *blinded_planchet,
+ const struct TALER_DenominationHashP *denom_hash,
+ struct TALER_BlindedCoinHashP *bch);
- /**
- * The blinding key.
- */
- struct TALER_DenominationBlindingKeyP blinding_key;
-};
+/**
+ * Compute the hash of a coin.
+ *
+ * @param coin_pub public key of the coin
+ * @param age_commitment_hash hash of the age commitment vector. NULL, if no age commitment was set
+ * @param[out] coin_h where to write the hash
+ */
+void
+TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_AgeCommitmentHash *age_commitment_hash,
+ struct TALER_CoinPubHashP *coin_h);
-GNUNET_NETWORK_STRUCT_END
+/**
+ * Compute the hash of a payto URI.
+ *
+ * @param payto URI to hash
+ * @param[out] h_payto where to write the hash
+ */
+void
+TALER_payto_hash (const char *payto,
+ struct TALER_PaytoHashP *h_payto);
/**
@@ -443,24 +1783,12 @@ struct TALER_PlanchetDetail
/**
* Hash of the denomination public key.
*/
- struct GNUNET_HashCode denom_pub_hash;
+ struct TALER_DenominationHashP denom_pub_hash;
/**
- * Hash of the coin's public key. Kept around so we do not need to
- * compute it again. Can be recomputed by hashing the public key
- * of @a coin_priv if storage is at a premium.
+ * The blinded planchet
*/
- struct GNUNET_HashCode c_hash;
-
- /**
- * Blinded coin (see GNUNET_CRYPTO_rsa_blind()). Note: is malloc()'ed!
- */
- void *coin_ev;
-
- /**
- * Number of bytes in @a coin_ev.
- */
- size_t coin_ev_size;
+ struct TALER_BlindedPlanchet blinded_planchet;
};
@@ -482,6 +1810,10 @@ struct TALER_FreshCoin
*/
struct TALER_CoinSpendPrivateKeyP coin_priv;
+ /**
+ * Optional hash of an age commitment bound to this coin, maybe NULL.
+ */
+ const struct TALER_AgeCommitmentHash *h_age_commitment;
};
@@ -536,6 +1868,19 @@ struct TALER_WireTransferIdentifierRawP
/**
+ * Raw value of a wire transfer subject for a wad.
+ */
+struct TALER_WadIdentifierP
+{
+
+ /**
+ * Wad identifier, in binary encoding.
+ */
+ uint8_t raw[24];
+};
+
+
+/**
* Binary information encoded in Crockford's Base32 in wire transfer
* subjects of transfers from Taler to a merchant. The actual value
* is chosen by the exchange and has no particular semantics, other than
@@ -561,42 +1906,121 @@ GNUNET_NETWORK_STRUCT_END
/**
- * Setup information for a fresh coin, deriving the coin private key
- * and the blinding factor from the @a secret_seed with a KDF salted
- * by the @a coin_num_salt.
+ * Setup information for a fresh coin, deriving the coin planchet secrets from
+ * which we will later derive the private key and the blinding factor. The
+ * planchet secrets derivation is based on the @a secret_seed with a KDF
+ * salted by the @a coin_num_salt.
*
* @param secret_seed seed to use for KDF to derive coin keys
* @param coin_num_salt number of the coin to include in KDF
* @param[out] ps value to initialize
*/
void
-TALER_planchet_setup_refresh (const struct TALER_TransferSecretP *secret_seed,
- uint32_t coin_num_salt,
- struct TALER_PlanchetSecretsP *ps);
+TALER_transfer_secret_to_planchet_secret (
+ const struct TALER_TransferSecretP *secret_seed,
+ uint32_t coin_num_salt,
+ struct TALER_PlanchetMasterSecretP *ps);
+
+
+/**
+ * Derive the @a coin_num transfer private key @a tpriv from a refresh from
+ * the @a rms seed and the @a old_coin_pub of the refresh operation. The
+ * transfer private key derivation is based on the @a ps with a KDF salted by
+ * the @a coin_num.
+ *
+ * @param rms seed to use for KDF to derive transfer keys
+ * @param old_coin_priv private key of the old coin
+ * @param cnc_num cut and choose number to include in KDF
+ * @param[out] tpriv value to initialize
+ */
+void
+TALER_planchet_secret_to_transfer_priv (
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
+ uint32_t cnc_num,
+ struct TALER_TransferPrivateKeyP *tpriv);
/**
- * Setup information for a fresh coin.
+ * Setup secret seed information for fresh coins to be
+ * withdrawn.
*
* @param[out] ps value to initialize
*/
void
-TALER_planchet_setup_random (struct TALER_PlanchetSecretsP *ps);
+TALER_planchet_master_setup_random (
+ struct TALER_PlanchetMasterSecretP *ps);
+
+
+/**
+ * Setup secret seed for fresh coins to be refreshed.
+ *
+ * @param[out] rms value to initialize
+ */
+void
+TALER_refresh_master_setup_random (
+ struct TALER_RefreshMasterSecretP *rms);
/**
- * Prepare a planchet for tipping. Creates and blinds a coin.
+ * Create a blinding secret @a bks given the client's @a ps and the alg_values
+ * from the exchange.
+ *
+ * @param ps secret to derive blindings from
+ * @param alg_values withdraw values containing cipher and additional CS values
+ * @param[out] bks blinding secrets
+ */
+void
+TALER_planchet_blinding_secret_create (
+ const struct TALER_PlanchetMasterSecretP *ps,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ union GNUNET_CRYPTO_BlindingSecretP *bks);
+
+
+/**
+ * Prepare a planchet for withdrawal. Creates and blinds a coin.
*
* @param dk denomination key for the coin to be created
- * @param ps secret planchet internals (for #TALER_planchet_to_coin)
+ * @param alg_values algorithm specific values
+ * @param bks blinding secrets
+ * @param nonce session nonce used to get @a alg_values
+ * @param coin_priv coin private key
+ * @param ach hash of age commitment to bind to this coin, maybe NULL
+ * @param[out] c_hash set to the hash of the public key of the coin (needed later)
* @param[out] pd set to the planchet detail for TALER_MERCHANT_tip_pickup() and
- * other withdraw operations
+ * other withdraw operations, `pd->blinded_planchet.cipher` will be set
+ * to cipher from @a dk
* @return #GNUNET_OK on success
*/
-int
-TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,
- const struct TALER_PlanchetSecretsP *ps,
- struct TALER_PlanchetDetail *pd);
+enum GNUNET_GenericReturnValue
+TALER_planchet_prepare (
+ const struct TALER_DenominationPublicKey *dk,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ const union GNUNET_CRYPTO_BlindingSecretP *bks,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ const struct TALER_AgeCommitmentHash *ach,
+ struct TALER_CoinPubHashP *c_hash,
+ struct TALER_PlanchetDetail *pd);
+
+
+/**
+ * Frees blinded message inside blinded planchet depending on `blinded_planchet->cipher`.
+ * Does not free the @a blinded_planchet itself!
+ *
+ * @param[in] blinded_planchet blinded planchet
+ */
+void
+TALER_blinded_planchet_free (struct TALER_BlindedPlanchet *blinded_planchet);
+
+
+/**
+ * Frees blinded message inside planchet detail @a pd.
+ *
+ * @param[in] pd planchet detail to free
+ */
+void
+TALER_planchet_detail_free (struct TALER_PlanchetDetail *pd);
/**
@@ -605,20 +2029,36 @@ TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,
*
* @param dk denomination key, must match what was given to #TALER_planchet_prepare()
* @param blind_sig blind signature from the exchange
- * @param ps secrets from #TALER_planchet_prepare()
+ * @param bks blinding key secret
+ * @param coin_priv private key of the coin
+ * @param ach hash of age commitment that is bound to this coin, maybe NULL
* @param c_hash hash of the coin's public key for verification of the signature
+ * @param alg_values values obtained from the exchange for the withdrawal
* @param[out] coin set to the details of the fresh coin
* @return #GNUNET_OK on success
*/
-int
-TALER_planchet_to_coin (const struct TALER_DenominationPublicKey *dk,
- const struct GNUNET_CRYPTO_RsaSignature *blind_sig,
- const struct TALER_PlanchetSecretsP *ps,
- const struct GNUNET_HashCode *c_hash,
- struct TALER_FreshCoin *coin);
+enum GNUNET_GenericReturnValue
+TALER_planchet_to_coin (
+ const struct TALER_DenominationPublicKey *dk,
+ const struct TALER_BlindedDenominationSignature *blind_sig,
+ const union GNUNET_CRYPTO_BlindingSecretP *bks,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ const struct TALER_AgeCommitmentHash *ach,
+ const struct TALER_CoinPubHashP *c_hash,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ struct TALER_FreshCoin *coin);
-/* ****************** Refresh crypto primitives ************* */
+/**
+ * Add the hash of the @a bp (in some canonicalized form)
+ * to the @a hash_context.
+ *
+ * @param bp blinded planchet to hash
+ * @param[in,out] hash_context hash context to use
+ */
+void
+TALER_blinded_planchet_hash_ (const struct TALER_BlindedPlanchet *bp,
+ struct GNUNET_HashContext *hash_context);
/**
@@ -681,14 +2121,9 @@ struct TALER_RefreshCoinData
const struct TALER_DenominationPublicKey *dk;
/**
- * The envelope with the blinded coin.
- */
- void *coin_ev;
-
- /**
- * Number of bytes in @a coin_ev
+ * The blinded planchet (details depend on cipher).
*/
- size_t coin_ev_size;
+ struct TALER_BlindedPlanchet blinded_planchet;
};
@@ -716,6 +2151,7 @@ struct TALER_RefreshCommitmentEntry
*
* @param[out] rc set to the value the wallet must commit to
* @param kappa number of transfer public keys involved (must be #TALER_CNC_KAPPA)
+ * @param rms refresh master secret to include, can be NULL!
* @param num_new_coins number of new coins to be created
* @param rcs array of @a kappa commitments
* @param coin_pub public key of the coin to be melted
@@ -724,38 +2160,3306 @@ struct TALER_RefreshCommitmentEntry
void
TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc,
uint32_t kappa,
+ const struct TALER_RefreshMasterSecretP *rms,
uint32_t num_new_coins,
const struct TALER_RefreshCommitmentEntry *rcs,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee);
+/**
+ * Encrypt contract for transmission to a party that will
+ * merge it into a reserve.
+ *
+ * @param purse_pub public key of the purse
+ * @param contract_priv private key of the contract
+ * @param merge_priv merge capability to include
+ * @param contract_terms contract terms to encrypt
+ * @param[out] econtract set to encrypted contract
+ * @param[out] econtract_size set to number of bytes in @a econtract
+ */
+void
+TALER_CRYPTO_contract_encrypt_for_merge (
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const struct TALER_PurseMergePrivateKeyP *merge_priv,
+ const json_t *contract_terms,
+ void **econtract,
+ size_t *econtract_size);
+
+
+/**
+ * Decrypt contract for the party that will
+ * merge it into a reserve.
+ *
+ * @param purse_pub public key of the purse
+ * @param contract_priv private key of the contract
+ * @param econtract encrypted contract
+ * @param econtract_size number of bytes in @a econtract
+ * @param[out] merge_priv set to merge capability
+ * @return decrypted contract terms, or NULL on failure
+ */
+json_t *
+TALER_CRYPTO_contract_decrypt_for_merge (
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const void *econtract,
+ size_t econtract_size,
+ struct TALER_PurseMergePrivateKeyP *merge_priv);
+
+
+/**
+ * Encrypt contract for transmission to a party that will
+ * pay for it.
+ *
+ * @param purse_pub public key of the purse
+ * @param contract_priv private key of the contract
+ * @param contract_terms contract terms to encrypt
+ * @param[out] econtract set to encrypted contract
+ * @param[out] econtract_size set to number of bytes in @a econtract
+ */
+void
+TALER_CRYPTO_contract_encrypt_for_deposit (
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const json_t *contract_terms,
+ void **econtract,
+ size_t *econtract_size);
+
+
+/**
+ * Decrypt contract for the party that will pay for it.
+ *
+ * @param contract_priv private key of the contract
+ * @param econtract encrypted contract
+ * @param econtract_size number of bytes in @a econtract
+ * @return decrypted contract terms, or NULL on failure
+ */
+json_t *
+TALER_CRYPTO_contract_decrypt_for_deposit (
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const void *econtract,
+ size_t econtract_size);
+
+
+/* **************** AML officer signatures **************** */
+
+/**
+ * Sign AML query. Simple authentication, doesn't actually
+ * sign anything.
+ *
+ * @param officer_priv private key of AML officer
+ * @param[out] officer_sig where to write the signature
+ */
+void
+TALER_officer_aml_query_sign (
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ struct TALER_AmlOfficerSignatureP *officer_sig);
+
+
+/**
+ * Verify AML query authorization.
+ *
+ * @param officer_pub public key of AML officer
+ * @param officer_sig signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_officer_aml_query_verify (
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const struct TALER_AmlOfficerSignatureP *officer_sig);
+
+
+/**
+ * Sign AML decision.
+ *
+ * @param justification human-readable justification
+ * @param decision_time when was the decision made
+ * @param new_threshold at what monthly amount threshold
+ * should a revision be triggered
+ * @param h_payto payto URI hash of the account the
+ * decision is about
+ * @param new_state updated AML state
+ * @param kyc_requirements additional KYC requirements to
+ * impose, can be NULL
+ * @param officer_priv private key of AML officer
+ * @param[out] officer_sig where to write the signature
+ */
+void
+TALER_officer_aml_decision_sign (
+ const char *justification,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const struct TALER_Amount *new_threshold,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_AmlDecisionState new_state,
+ const json_t *kyc_requirements,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ struct TALER_AmlOfficerSignatureP *officer_sig);
+
+
+/**
+ * Verify AML decision.
+ *
+ * @param justification human-readable justification
+ * @param decision_time when was the decision made
+ * @param new_threshold at what monthly amount threshold
+ * should a revision be triggered
+ * @param h_payto payto URI hash of the account the
+ * decision is about
+ * @param new_state updated AML state
+ * @param kyc_requirements additional KYC requirements to
+ * impose, can be NULL
+ * @param officer_pub public key of AML officer
+ * @param officer_sig signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_officer_aml_decision_verify (
+ const char *justification,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const struct TALER_Amount *new_threshold,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_AmlDecisionState new_state,
+ const json_t *kyc_requirements,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const struct TALER_AmlOfficerSignatureP *officer_sig);
+
+
+/* **************** Helper-based RSA operations **************** */
+
+/**
+ * Handle for talking to an Denomination key signing helper.
+ */
+struct TALER_CRYPTO_RsaDenominationHelper;
+
+/**
+ * Function called with information about available keys for signing. Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure
+ * @param section_name name of the denomination type in the configuration;
+ * NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ * zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ * zero if the key has been revoked or purged
+ * @param h_rsa hash of the RSA @a denom_pub that is available (or was purged)
+ * @param bs_pub the public key itself, NULL if the key was revoked or purged
+ * @param sm_pub public key of the security module, NULL if the key was revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was revoked or purged
+ * The signature was already verified against @a sm_pub.
+ */
+typedef void
+(*TALER_CRYPTO_RsaDenominationKeyStatusCallback)(
+ void *cls,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Relative validity_duration,
+ const struct TALER_RsaPubHashP *h_rsa,
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
+ const struct TALER_SecurityModulePublicKeyP *sm_pub,
+ const struct TALER_SecurityModuleSignatureP *sm_sig);
+
+
+/**
+ * Initiate connection to an denomination key helper.
+ *
+ * @param cfg configuration to use
+ * @param section configuration section prefix to use, usually 'taler' or 'donau'
+ * @param dkc function to call with key information
+ * @param dkc_cls closure for @a dkc
+ * @return NULL on error (such as bad @a cfg).
+ */
+struct TALER_CRYPTO_RsaDenominationHelper *
+TALER_CRYPTO_helper_rsa_connect (
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section,
+ TALER_CRYPTO_RsaDenominationKeyStatusCallback dkc,
+ void *dkc_cls);
+
+
+/**
+ * Function to call to 'poll' for updates to the available key material.
+ * Should be called whenever it is important that the key material status is
+ * current, like when handling a "/keys" request. This function basically
+ * briefly checks if there are messages from the helper announcing changes to
+ * denomination keys.
+ *
+ * @param dh helper process connection
+ */
+void
+TALER_CRYPTO_helper_rsa_poll (struct TALER_CRYPTO_RsaDenominationHelper *dh);
+
+
+/**
+ * Information needed for an RSA signature request.
+ */
+struct TALER_CRYPTO_RsaSignRequest
+{
+ /**
+ * Hash of the RSA public key.
+ */
+ const struct TALER_RsaPubHashP *h_rsa;
+
+ /**
+ * Message to be (blindly) signed.
+ */
+ const void *msg;
+
+ /**
+ * Number of bytes in @e msg.
+ */
+ size_t msg_size;
+};
+
+
+/**
+ * Request helper @a dh to sign message in @a rsr using the public key
+ * corresponding to the key in @a rsr.
+ *
+ * This operation will block until the signature has been obtained. Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail. Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters. Retrying in this case may work.
+ *
+ * @param dh helper process connection
+ * @param rsr details about the requested signature
+ * @param[out] bs set to the blind signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_rsa_sign (
+ struct TALER_CRYPTO_RsaDenominationHelper *dh,
+ const struct TALER_CRYPTO_RsaSignRequest *rsr,
+ struct TALER_BlindedDenominationSignature *bs);
+
+
+/**
+ * Request helper @a dh to batch sign messages in @a rsrs using the public key
+ * corresponding to the keys in @a rsrs.
+ *
+ * This operation will block until all the signatures have been obtained. Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail. Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters. Retrying in this case may work.
+ *
+ * Note that in case of errors, the @a bss array may still have been partially
+ * filled with signatures, which in this case must be freed by the caller
+ * (this is in contrast to the #TALER_CRYPTO_helper_rsa_sign() API which never
+ * returns any signatures if there was an error).
+ *
+ * @param dh helper process connection
+ * @param rsrs array with details about the requested signatures
+ * @param rsrs_length length of the @a rsrs array
+ * @param[out] bss array set to the blind signatures, must be of length @a rsrs_length!
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_rsa_batch_sign (
+ struct TALER_CRYPTO_RsaDenominationHelper *dh,
+ unsigned int rsrs_length,
+ const struct TALER_CRYPTO_RsaSignRequest rsrs[static rsrs_length],
+ struct TALER_BlindedDenominationSignature bss[static rsrs_length]);
+
+
+/**
+ * Ask the helper to revoke the public key associated with @a h_denom_pub.
+ * Will cause the helper to tell all clients that the key is now unavailable,
+ * and to create a replacement key.
+ *
+ * This operation will block until the revocation request has been
+ * transmitted. Should this process receive a signal (that is not ignored)
+ * while the operation is pending, the operation may fail. If the key is
+ * unknown, this function will also appear to have succeeded. To be sure that
+ * the revocation worked, clients must watch the denomination key status
+ * callback.
+ *
+ * @param dh helper to process connection
+ * @param h_rsa hash of the RSA public key to revoke
+ */
+void
+TALER_CRYPTO_helper_rsa_revoke (
+ struct TALER_CRYPTO_RsaDenominationHelper *dh,
+ const struct TALER_RsaPubHashP *h_rsa);
+
+
+/**
+ * Close connection to @a dh.
+ *
+ * @param[in] dh connection to close
+ */
+void
+TALER_CRYPTO_helper_rsa_disconnect (
+ struct TALER_CRYPTO_RsaDenominationHelper *dh);
+
+
+/* **************** Helper-based CS operations **************** */
+
+/**
+ * Handle for talking to an Denomination key signing helper.
+ */
+struct TALER_CRYPTO_CsDenominationHelper;
+
+/**
+ * Function called with information about available keys for signing. Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure
+ * @param section_name name of the denomination type in the configuration;
+ * NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ * zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ * zero if the key has been revoked or purged
+ * @param h_cs hash of the CS @a denom_pub that is available (or was purged)
+ * @param bsign_pub the public key itself, NULL if the key was revoked or purged
+ * @param sm_pub public key of the security module, NULL if the key was revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was revoked or purged
+ * The signature was already verified against @a sm_pub.
+ */
+typedef void
+(*TALER_CRYPTO_CsDenominationKeyStatusCallback)(
+ void *cls,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Relative validity_duration,
+ const struct TALER_CsPubHashP *h_cs,
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub,
+ const struct TALER_SecurityModulePublicKeyP *sm_pub,
+ const struct TALER_SecurityModuleSignatureP *sm_sig);
+
+
+/**
+ * Initiate connection to an denomination key helper.
+ *
+ * @param cfg configuration to use
+ * @param section configuration section prefix to use, usually 'taler' or 'donau'
+ * @param dkc function to call with key information
+ * @param dkc_cls closure for @a dkc
+ * @return NULL on error (such as bad @a cfg).
+ */
+struct TALER_CRYPTO_CsDenominationHelper *
+TALER_CRYPTO_helper_cs_connect (
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section,
+ TALER_CRYPTO_CsDenominationKeyStatusCallback dkc,
+ void *dkc_cls);
+
+
+/**
+ * Function to call to 'poll' for updates to the available key material.
+ * Should be called whenever it is important that the key material status is
+ * current, like when handling a "/keys" request. This function basically
+ * briefly checks if there are messages from the helper announcing changes to
+ * denomination keys.
+ *
+ * @param dh helper process connection
+ */
+void
+TALER_CRYPTO_helper_cs_poll (struct TALER_CRYPTO_CsDenominationHelper *dh);
+
+
+/**
+ * Information about what we should sign over.
+ */
+struct TALER_CRYPTO_CsSignRequest
+{
+ /**
+ * Hash of the CS public key to use to sign.
+ */
+ const struct TALER_CsPubHashP *h_cs;
+
+ /**
+ * Blinded planchet containing c and the nonce.
+ */
+ const struct GNUNET_CRYPTO_CsBlindedMessage *blinded_planchet;
+
+};
+
+
+/**
+ * Request helper @a dh to sign @a req.
+ *
+ * This operation will block until the signature has been obtained. Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail. Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters. Retrying in this case may work.
+ *
+ * @param dh helper process connection
+ * @param req information about the key to sign with and the value to sign
+ * @param for_melt true if for melt operation
+ * @param[out] bs set to the blind signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_cs_sign (
+ struct TALER_CRYPTO_CsDenominationHelper *dh,
+ const struct TALER_CRYPTO_CsSignRequest *req,
+ bool for_melt,
+ struct TALER_BlindedDenominationSignature *bs);
+
+
+/**
+ * Request helper @a dh to sign batch of @a reqs requests.
+ *
+ * This operation will block until the signature has been obtained. Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail. Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters. Retrying in this case may work.
+ *
+ * @param dh helper process connection
+ * @param reqs information about the keys to sign with and the values to sign
+ * @param reqs_length length of the @a reqs array
+ * @param for_melt true if this is for a melt operation
+ * @param[out] bss array set to the blind signatures, must be of length @a reqs_length!
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_cs_batch_sign (
+ struct TALER_CRYPTO_CsDenominationHelper *dh,
+ unsigned int reqs_length,
+ const struct TALER_CRYPTO_CsSignRequest reqs[static reqs_length],
+ bool for_melt,
+ struct TALER_BlindedDenominationSignature bss[static reqs_length]);
+
+
+/**
+ * Ask the helper to revoke the public key associated with @a h_cs.
+ * Will cause the helper to tell all clients that the key is now unavailable,
+ * and to create a replacement key.
+ *
+ * This operation will block until the revocation request has been
+ * transmitted. Should this process receive a signal (that is not ignored)
+ * while the operation is pending, the operation may fail. If the key is
+ * unknown, this function will also appear to have succeeded. To be sure that
+ * the revocation worked, clients must watch the denomination key status
+ * callback.
+ *
+ * @param dh helper to process connection
+ * @param h_cs hash of the CS public key to revoke
+ */
+void
+TALER_CRYPTO_helper_cs_revoke (
+ struct TALER_CRYPTO_CsDenominationHelper *dh,
+ const struct TALER_CsPubHashP *h_cs);
+
+
+/**
+ * Information about what we should derive for.
+ */
+struct TALER_CRYPTO_CsDeriveRequest
+{
+ /**
+ * Hash of the CS public key to use to sign.
+ */
+ const struct TALER_CsPubHashP *h_cs;
+
+ /**
+ * Nonce to use for the /csr request.
+ */
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce;
+};
+
+
+/**
+ * Ask the helper to derive R using the information
+ * from @a cdr.
+ *
+ * This operation will block until the R has been obtained. Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail. Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters. Retrying in this case may work.
+ *
+ * @param dh helper to process connection
+ * @param cdr derivation input data
+ * @param for_melt true if this is for a melt operation
+ * @param[out] crp set to the pair of R values
+ * @return set to the error code (or #TALER_EC_NONE on success)
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_cs_r_derive (
+ struct TALER_CRYPTO_CsDenominationHelper *dh,
+ const struct TALER_CRYPTO_CsDeriveRequest *cdr,
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP *crp);
+
+
+/**
+ * Ask the helper to derive R using the information from @a cdrs.
+ *
+ * This operation will block until the R has been obtained. Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail. Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters. Retrying in this case may work.
+ *
+ * @param dh helper to process connection
+ * @param cdrs_length length of the @a cdrs array
+ * @param cdrs array with derivation input data
+ * @param for_melt true if this is for a melt operation
+ * @param[out] crps array set to the pair of R values, must be of length @a cdrs_length
+ * @return set to the error code (or #TALER_EC_NONE on success)
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_cs_r_batch_derive (
+ struct TALER_CRYPTO_CsDenominationHelper *dh,
+ unsigned int cdrs_length,
+ const struct TALER_CRYPTO_CsDeriveRequest cdrs[static cdrs_length],
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP crps[static cdrs_length]);
+
+
+/**
+ * Close connection to @a dh.
+ *
+ * @param[in] dh connection to close
+ */
+void
+TALER_CRYPTO_helper_cs_disconnect (
+ struct TALER_CRYPTO_CsDenominationHelper *dh);
+
+/**
+ * Handle for talking to an online key signing helper.
+ */
+struct TALER_CRYPTO_ExchangeSignHelper;
+
+/**
+ * Function called with information about available keys for signing. Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure
+ * @param start_time when does the key become available for signing;
+ * zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ * zero if the key has been revoked or purged
+ * @param exchange_pub the public key itself, NULL if the key was revoked or purged
+ * @param sm_pub public key of the security module, NULL if the key was revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was revoked or purged
+ * The signature was already verified against @a sm_pub.
+ */
+typedef void
+(*TALER_CRYPTO_ExchangeKeyStatusCallback)(
+ void *cls,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Relative validity_duration,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_SecurityModulePublicKeyP *sm_pub,
+ const struct TALER_SecurityModuleSignatureP *sm_sig);
+
+
+/**
+ * Initiate connection to an online signing key helper.
+ *
+ * @param cfg configuration to use
+ * @param section configuration section prefix to use, usually 'taler' or 'donau'
+ * @param ekc function to call with key information
+ * @param ekc_cls closure for @a ekc
+ * @return NULL on error (such as bad @a cfg).
+ */
+struct TALER_CRYPTO_ExchangeSignHelper *
+TALER_CRYPTO_helper_esign_connect (
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section,
+ TALER_CRYPTO_ExchangeKeyStatusCallback ekc,
+ void *ekc_cls);
+
+
+/**
+ * Function to call to 'poll' for updates to the available key material.
+ * Should be called whenever it is important that the key material status is
+ * current, like when handling a "/keys" request. This function basically
+ * briefly checks if there are messages from the helper announcing changes to
+ * exchange online signing keys.
+ *
+ * @param esh helper process connection
+ */
+void
+TALER_CRYPTO_helper_esign_poll (struct TALER_CRYPTO_ExchangeSignHelper *esh);
+
+
+/**
+ * Request helper @a esh to sign @a msg using the current online
+ * signing key.
+ *
+ * This operation will block until the signature has been obtained. Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail. Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters. Retrying in this case may work.
+ *
+ * @param esh helper process connection
+ * @param purpose message to sign (must extend beyond the purpose)
+ * @param[out] exchange_pub set to the public key used for the signature upon success
+ * @param[out] exchange_sig set to the signature upon success
+ * @return the error code (or #TALER_EC_NONE on success)
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_esign_sign_ (
+ struct TALER_CRYPTO_ExchangeSignHelper *esh,
+ const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+ struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_ExchangeSignatureP *exchange_sig);
+
+
+/**
+ * Request helper @a esh to sign @a msg using the current online
+ * signing key.
+ *
+ * This operation will block until the signature has been obtained. Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail. Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters. Retrying in this case may work.
+ *
+ * @param esh helper process connection
+ * @param ps message to sign (MUST begin with a purpose)
+ * @param[out] epub set to the public key used for the signature upon success
+ * @param[out] esig set to the signature upon success
+ * @return the error code (or #TALER_EC_NONE on success)
+ */
+#define TALER_CRYPTO_helper_esign_sign(esh,ps,epub,esig) ( \
+ /* check size is set correctly */ \
+ GNUNET_assert (ntohl ((ps)->purpose.size) == sizeof (*ps)), \
+ /* check 'ps' begins with the purpose */ \
+ GNUNET_static_assert (((void*) (ps)) == \
+ ((void*) &(ps)->purpose)), \
+ TALER_CRYPTO_helper_esign_sign_ (esh, \
+ &(ps)->purpose, \
+ epub, \
+ esig) )
+
+
+/**
+ * Ask the helper to revoke the public key @a exchange_pub .
+ * Will cause the helper to tell all clients that the key is now unavailable,
+ * and to create a replacement key.
+ *
+ * This operation will block until the revocation request has been
+ * transmitted. Should this process receive a signal (that is not ignored)
+ * while the operation is pending, the operation may fail. If the key is
+ * unknown, this function will also appear to have succeeded. To be sure that
+ * the revocation worked, clients must watch the signing key status callback.
+ *
+ * @param esh helper to process connection
+ * @param exchange_pub the public key to revoke
+ */
+void
+TALER_CRYPTO_helper_esign_revoke (
+ struct TALER_CRYPTO_ExchangeSignHelper *esh,
+ const struct TALER_ExchangePublicKeyP *exchange_pub);
+
+
+/**
+ * Close connection to @a esh.
+ *
+ * @param[in] esh connection to close
+ */
+void
+TALER_CRYPTO_helper_esign_disconnect (
+ struct TALER_CRYPTO_ExchangeSignHelper *esh);
+
+
+/* ********************* wallet signing ************************** */
+
+
+/**
+ * Sign a request to create a purse.
+ *
+ * @param purse_expiration when should the purse expire
+ * @param h_contract_terms contract the two parties agree on
+ * @param merge_pub public key defining the merge capability
+ * @param min_age age restriction to apply for deposits into the purse
+ * @param amount total amount in the purse (including fees)
+ * @param purse_priv key identifying the purse
+ * @param[out] purse_sig resulting signature
+ */
+void
+TALER_wallet_purse_create_sign (
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ uint32_t min_age,
+ const struct TALER_Amount *amount,
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Verify a purse creation request.
+ *
+ * @param purse_expiration when should the purse expire
+ * @param h_contract_terms contract the two parties agree on
+ * @param merge_pub public key defining the merge capability
+ * @param min_age age restriction to apply for deposits into the purse
+ * @param amount total amount in the purse (including fees)
+ * @param purse_pub purse’s public key
+ * @param purse_sig the signature made with purpose #TALER_SIGNATURE_WALLET_PURSE_CREATE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_create_verify (
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ uint32_t min_age,
+ const struct TALER_Amount *amount,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Sign a request to delete a purse.
+ *
+ * @param purse_priv key identifying the purse
+ * @param[out] purse_sig resulting signature
+ */
+void
+TALER_wallet_purse_delete_sign (
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Verify a purse deletion request.
+ *
+ * @param purse_pub purse’s public key
+ * @param purse_sig the signature made with purpose #TALER_SIGNATURE_WALLET_PURSE_DELETE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_delete_verify (
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Sign a request to upload an encrypted contract.
+ *
+ * @param econtract encrypted contract
+ * @param econtract_size number of bytes in @a econtract
+ * @param contract_pub public key for the DH-encryption
+ * @param purse_priv key identifying the purse
+ * @param[out] purse_sig resulting signature
+ */
+void
+TALER_wallet_econtract_upload_sign (
+ const void *econtract,
+ size_t econtract_size,
+ const struct TALER_ContractDiffiePublicP *contract_pub,
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Verify a signature over encrypted contract.
+ *
+ * @param econtract encrypted contract
+ * @param econtract_size number of bytes in @a econtract
+ * @param contract_pub public key for the DH-encryption
+ * @param purse_pub purse’s public key
+ * @param purse_sig the signature made with purpose #TALER_SIGNATURE_WALLET_PURSE_CREATE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_econtract_upload_verify (
+ const void *econtract,
+ size_t econtract_size,
+ const struct TALER_ContractDiffiePublicP *contract_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Verify a signature over encrypted contract.
+ *
+ * @param h_econtract hashed encrypted contract
+ * @param contract_pub public key for the DH-encryption
+ * @param purse_pub purse’s public key
+ * @param purse_sig the signature made with purpose #TALER_SIGNATURE_WALLET_PURSE_CREATE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_econtract_upload_verify2 (
+ const struct GNUNET_HashCode *h_econtract,
+ const struct TALER_ContractDiffiePublicP *contract_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Sign a request to inquire about a purse's status.
+ *
+ * @param purse_priv key identifying the purse
+ * @param[out] purse_sig resulting signature
+ */
+void
+TALER_wallet_purse_status_sign (
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Verify a purse status request signature.
+ *
+ * @param purse_pub purse’s public key
+ * @param purse_sig the signature made with purpose #TALER_SIGNATURE_WALLET_PURSE_STATUS
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_status_verify (
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Sign a request to deposit a coin into a purse.
+ *
+ * @param exchange_base_url URL of the exchange hosting the purse
+ * @param purse_pub purse’s public key
+ * @param amount amount of the coin's value to transfer to the purse
+ * @param h_denom_pub hash of the coin's denomination
+ * @param h_age_commitment hash of the coin's age commitment
+ * @param coin_priv key identifying the coin to be deposited
+ * @param[out] coin_sig resulting signature
+ */
+void
+TALER_wallet_purse_deposit_sign (
+ const char *exchange_base_url,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *amount,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify a purse deposit request.
+ *
+ * @param exchange_base_url URL of the exchange hosting the purse
+ * @param purse_pub purse’s public key
+ * @param amount amount of the coin's value to transfer to the purse
+ * @param h_denom_pub hash of the coin's denomination
+ * @param h_age_commitment hash of the coin's age commitment
+ * @param coin_pub key identifying the coin that is being deposited
+ * @param[out] coin_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_deposit_verify (
+ const char *exchange_base_url,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *amount,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Sign a request by a purse to merge it into an account.
+ *
+ * @param reserve_uri identifies the location of the reserve
+ * @param merge_timestamp time when the merge happened
+ * @param purse_pub key identifying the purse
+ * @param merge_priv key identifying the merge capability
+ * @param[out] merge_sig resulting signature
+ */
+void
+TALER_wallet_purse_merge_sign (
+ const char *reserve_uri,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePrivateKeyP *merge_priv,
+ struct TALER_PurseMergeSignatureP *merge_sig);
+
+
+/**
+ * Verify a purse merge request.
+ *
+ * @param reserve_uri identifies the location of the reserve
+ * @param merge_timestamp time when the merge happened
+ * @param purse_pub public key of the purse to merge
+ * @param merge_pub public key of the merge capability
+ * @param merge_sig the signature made with purpose #TALER_SIGNATURE_WALLET_PURSE_MERGE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_merge_verify (
+ const char *reserve_uri,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig);
+
+
+/**
+ * Flags for a merge signature.
+ */
+enum TALER_WalletAccountMergeFlags
+{
+
+ /**
+ * A mode must be set. None is not a legal mode!
+ */
+ TALER_WAMF_MODE_NONE = 0,
+
+ /**
+ * We are merging a fully paid-up purse into a reserve.
+ */
+ TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE = 1,
+
+ /**
+ * We are creating a fresh purse, from the contingent
+ * of free purses that our account brings.
+ */
+ TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA = 2,
+
+ /**
+ * The account owner is willing to pay the purse_fee for the purse to be
+ * created from the account balance.
+ */
+ TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE = 3,
+
+ /**
+ * Bitmask to AND the full flags with to get the mode.
+ */
+ TALER_WAMF_MERGE_MODE_MASK = 3
+
+};
+
+
+/**
+ * Sign a request by an account to merge a purse.
+ *
+ * @param merge_timestamp time when the merge happened
+ * @param purse_pub public key of the purse to merge
+ * @param purse_expiration when should the purse expire
+ * @param h_contract_terms contract the two parties agree on
+ * @param amount total amount in the purse (including fees)
+ * @param purse_fee purse fee the reserve will pay,
+ * only used if @a flags is #TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE
+ * @param min_age age restriction to apply for deposits into the purse
+ * @param flags flags for the operation
+ * @param reserve_priv key identifying the reserve
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_account_merge_sign (
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *purse_fee,
+ uint32_t min_age,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify an account's request to merge a purse.
+ *
+ * @param merge_timestamp time when the merge happened
+ * @param purse_pub public key of the purse to merge
+ * @param purse_expiration when should the purse expire
+ * @param h_contract_terms contract the two parties agree on
+ * @param amount total amount in the purse (including fees)
+ * @param purse_fee purse fee the reserve will pay,
+ * only used if @a flags is #TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE
+ * @param min_age age restriction to apply for deposits into the purse
+ * @param flags flags for the operation
+ * @param reserve_pub account’s public key
+ * @param reserve_sig the signature made with purpose #TALER_SIGNATURE_WALLET_ACCOUNT_MERGE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_account_merge_verify (
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *purse_fee,
+ uint32_t min_age,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Sign a request to keep a reserve open.
+ *
+ * @param reserve_payment how much to pay from the
+ * reserve's own balance for opening the reserve
+ * @param request_timestamp when was the request created
+ * @param reserve_expiration desired expiration time for the reserve
+ * @param purse_limit minimum number of purses the client
+ * wants to have concurrently open for this reserve
+ * @param reserve_priv key identifying the reserve
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_reserve_open_sign (
+ const struct TALER_Amount *reserve_payment,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ struct GNUNET_TIME_Timestamp reserve_expiration,
+ uint32_t purse_limit,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify a request to keep a reserve open.
+ *
+ * @param reserve_payment how much to pay from the
+ * reserve's own balance for opening the reserve
+ * @param request_timestamp when was the request created
+ * @param reserve_expiration desired expiration time for the reserve
+ * @param purse_limit minimum number of purses the client
+ * wants to have concurrently open for this reserve
+ * @param reserve_pub key identifying the reserve
+ * @param reserve_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_open_verify (
+ const struct TALER_Amount *reserve_payment,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ struct GNUNET_TIME_Timestamp reserve_expiration,
+ uint32_t purse_limit,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Sign to deposit coin to pay for keeping a reserve open.
+ *
+ * @param coin_contribution how much the coin should contribute
+ * @param reserve_sig signature over the reserve open operation
+ * @param coin_priv private key of the coin
+ * @param[out] coin_sig signature by the coin
+ */
+void
+TALER_wallet_reserve_open_deposit_sign (
+ const struct TALER_Amount *coin_contribution,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify signature that deposits coin to pay for keeping a reserve open.
+ *
+ * @param coin_contribution how much the coin should contribute
+ * @param reserve_sig signature over the reserve open operation
+ * @param coin_pub public key of the coin
+ * @param coin_sig signature by the coin
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_open_deposit_verify (
+ const struct TALER_Amount *coin_contribution,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Sign a request to close a reserve.
+ *
+ * @param request_timestamp when was the request created
+ * @param h_payto where to send the funds (NULL allowed to send
+ * to origin of the reserve)
+ * @param reserve_priv key identifying the reserve
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_reserve_close_sign (
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify wallet request to close an account.
+ *
+ * @param request_timestamp when was the request created
+ * @param h_payto where to send the funds (NULL/all zeros
+ * allowed to send to origin of the reserve)
+ * @param reserve_pub account’s public key
+ * @param reserve_sig the signature made with purpose #TALER_SIGNATURE_WALLET_RESERVE_CLOSE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_close_verify (
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Sign a request by a wallet to perform a KYC check.
+ *
+ * @param reserve_priv key identifying the wallet/account
+ * @param balance_threshold the balance threshold the wallet is about to cross
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_account_setup_sign (
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_Amount *balance_threshold,
+ struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify account setup request.
+ *
+ * @param reserve_pub reserve the setup request was for
+ * @param balance_threshold the balance threshold the wallet is about to cross
+ * @param reserve_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_account_setup_verify (
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *balance_threshold,
+ const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Sign request to the exchange to confirm certain
+ * @a details about the owner of a reserve.
+ *
+ * @param request_timestamp when was the request created
+ * @param details which attributes are requested
+ * @param reserve_priv private key of the reserve
+ * @param[out] reserve_sig where to store the signature
+ */
+void
+TALER_wallet_reserve_attest_request_sign (
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const json_t *details,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify request to the exchange to confirm certain
+ * @a details about the owner of a reserve.
+ *
+ * @param request_timestamp when was the request created
+ * @param details which attributes are requested
+ * @param reserve_pub public key of the reserve
+ * @param reserve_sig where to store the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_attest_request_verify (
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const json_t *details,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Sign a deposit permission. Function for wallets.
+ *
+ * @param amount the amount to be deposited
+ * @param deposit_fee the deposit fee we expect to pay
+ * @param h_wire hash of the merchant’s account details
+ * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)
+ * @param wallet_data_hash hash over wallet inputs into the contract (maybe NULL)
+ * @param h_age_commitment hash over the age commitment, if applicable to the denomination (maybe NULL)
+ * @param h_policy hash over the policy extension
+ * @param h_denom_pub hash of the coin denomination's public key
+ * @param coin_priv coin’s private key
+ * @param wallet_timestamp timestamp when the contract was finalized, must not be too far in the future
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline
+ * @param[out] coin_sig set to the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT
+ */
+void
+TALER_wallet_deposit_sign (
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct GNUNET_HashCode *wallet_data_hash,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct GNUNET_TIME_Timestamp wallet_timestamp,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify a deposit permission.
+ *
+ * @param amount the amount to be deposited
+ * @param deposit_fee the deposit fee we expect to pay
+ * @param h_wire hash of the merchant’s account details
+ * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)
+ * @param wallet_data_hash hash over wallet inputs into the contract (maybe NULL)
+ * @param h_age_commitment hash over the age commitment (maybe all zeroes, if not applicable to the denomination)
+ * @param h_policy hash over the policy extension
+ * @param h_denom_pub hash of the coin denomination's public key
+ * @param wallet_timestamp timestamp when the contract was finalized, must not be too far in the future
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline
+ * @param coin_pub coin’s public key
+ * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_deposit_verify (
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct GNUNET_HashCode *wallet_data_hash,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct GNUNET_TIME_Timestamp wallet_timestamp,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Sign a melt request.
+ *
+ * @param amount_with_fee the amount to be melted (with fee)
+ * @param melt_fee the melt fee we expect to pay
+ * @param rc refresh session we are committed to
+ * @param h_denom_pub hash of the coin denomination's public key
+ * @param h_age_commitment hash of the age commitment (may be NULL)
+ * @param coin_priv coin’s private key
+ * @param[out] coin_sig set to the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_MELT
+ */
+void
+TALER_wallet_melt_sign (
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *melt_fee,
+ const struct TALER_RefreshCommitmentP *rc,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify a melt request.
+ *
+ * @param amount_with_fee the amount to be melted (with fee)
+ * @param melt_fee the melt fee we expect to pay
+ * @param rc refresh session we are committed to
+ * @param h_denom_pub hash of the coin denomination's public key
+ * @param h_age_commitment hash of the age commitment (may be NULL)
+ * @param coin_pub coin’s public key
+ * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_MELT
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_melt_verify (
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *melt_fee,
+ const struct TALER_RefreshCommitmentP *rc,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Sign link data.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the new coin
+ * @param transfer_pub transfer public key
+ * @param bch blinded coin hash
+ * @param old_coin_priv private key to sign with
+ * @param[out] coin_sig resulting signature
+ */
+void
+TALER_wallet_link_sign (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_TransferPublicKeyP *transfer_pub,
+ const struct TALER_BlindedCoinHashP *bch,
+ const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify link signature.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the new coin
+ * @param transfer_pub transfer public key
+ * @param h_coin_ev hash of the coin envelope
+ * @param old_coin_pub old coin key that the link signature is for
+ * @param coin_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_link_verify (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_TransferPublicKeyP *transfer_pub,
+ const struct TALER_BlindedCoinHashP *h_coin_ev,
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Sign withdraw request.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the coin to withdraw
+ * @param amount_with_fee amount to debit the reserve for
+ * @param bch blinded coin hash
+ * @param reserve_priv private key to sign with
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_withdraw_sign (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_BlindedCoinHashP *bch,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify withdraw request.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the coin to withdraw
+ * @param amount_with_fee amount to debit the reserve for
+ * @param bch blinded coin hash
+ * @param reserve_pub public key of the reserve
+ * @param reserve_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_withdraw_verify (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_BlindedCoinHashP *bch,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Sign age-withdraw request.
+ *
+ * @param h_commitment hash over all n*kappa blinded coins in the commitment for the age-withdraw
+ * @param amount_with_fee amount to debit the reserve for
+ * @param mask the mask that defines the age groups
+ * @param max_age maximum age from which the age group is derived, that the withdrawn coins must be restricted to.
+ * @param reserve_priv private key to sign with
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_age_withdraw_sign (
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_AgeMask *mask,
+ uint8_t max_age,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig);
+
+/**
+ * Verify an age-withdraw request.
+ *
+ * @param h_commitment hash all n*kappa blinded coins in the commitment for the age-withdraw
+ * @param amount_with_fee amount to debit the reserve for
+ * @param mask the mask that defines the age groups
+ * @param max_age maximum age from which the age group is derived, that the withdrawn coins must be restricted to.
+ * @param reserve_pub public key of the reserve
+ * @param reserve_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_age_withdraw_verify (
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_AgeMask *mask,
+ uint8_t max_age,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig);
+
+/**
+ * Verify exchange melt confirmation.
+ *
+ * @param rc refresh session this is about
+ * @param noreveal_index gamma value chosen by the exchange
+ * @param exchange_pub public signing key used
+ * @param exchange_sig signature to check
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_melt_confirmation_verify (
+ const struct TALER_RefreshCommitmentP *rc,
+ uint32_t noreveal_index,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig);
+
+
+/**
+ * Verify recoup signature.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the coin
+ * @param coin_bks blinding factor used when withdrawing the coin
+ * @param coin_pub coin key of the coin to be recouped
+ * @param coin_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_recoup_verify (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Create recoup signature.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the coin
+ * @param coin_bks blinding factor used when withdrawing the coin
+ * @param coin_priv coin key of the coin to be recouped
+ * @param[out] coin_sig resulting signature
+ */
+void
+TALER_wallet_recoup_sign (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify recoup-refresh signature.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the coin
+ * @param coin_bks blinding factor used when withdrawing the coin
+ * @param coin_pub coin key of the coin to be recouped
+ * @param coin_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_recoup_refresh_verify (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Create recoup-refresh signature.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the coin
+ * @param coin_bks blinding factor used when withdrawing the coin
+ * @param coin_priv coin key of the coin to be recouped
+ * @param[out] coin_sig resulting signature
+ */
+void
+TALER_wallet_recoup_refresh_sign (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify reserve history request signature.
+ *
+ * @param start_off start of the requested range
+ * @param reserve_pub reserve the history request was for
+ * @param reserve_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_history_verify (
+ uint64_t start_off,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Create reserve status request signature.
+ *
+ * @param start_off start of the requested range
+ * @param reserve_priv private key of the reserve the history request is for
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_reserve_history_sign (
+ uint64_t start_off,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify coin history request signature.
+ *
+ * @param start_off start of the requested range
+ * @param coin_pub coin the history request was for
+ * @param coin_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_coin_history_verify (
+ uint64_t start_off,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Create coin status request signature.
+ *
+ * @param start_off start of the requested range
+ * @param coin_priv private key of the coin the history request is for
+ * @param[out] coin_sig resulting signature
+ */
+void
+TALER_wallet_coin_history_sign (
+ uint64_t start_off,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/* ********************* merchant signing ************************** */
+
+
+/**
+ * Create merchant signature approving a refund.
+ *
+ * @param coin_pub coin to be refunded
+ * @param h_contract_terms contract to be refunded
+ * @param rtransaction_id unique ID for this (partial) refund
+ * @param amount amount to be refunded
+ * @param merchant_priv private key to sign with
+ * @param[out] merchant_sig where to write the signature
+ */
+void
+TALER_merchant_refund_sign (
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint64_t rtransaction_id,
+ const struct TALER_Amount *amount,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ struct TALER_MerchantSignatureP *merchant_sig);
+
+
+/**
+ * Verify merchant signature approving a refund.
+ *
+ * @param coin_pub coin to be refunded
+ * @param h_contract_terms contract to be refunded
+ * @param rtransaction_id unique ID for this (partial) refund
+ * @param amount amount to be refunded
+ * @param merchant_pub public key of the merchant
+ * @param merchant_sig signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_merchant_refund_verify (
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint64_t rtransaction_id,
+ const struct TALER_Amount *amount,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig);
+
+
+/* ********************* exchange deposit signing ************************* */
+
+/**
+ * Sign a deposit.
+ *
+ * @param h_contract_terms hash of contract terms
+ * @param h_wire hash of the merchant account details
+ * @param coin_pub coin to be deposited
+ * @param merchant_priv private key to sign with
+ * @param[out] merchant_sig where to write the signature
+ */
+void
+TALER_merchant_deposit_sign (
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ struct TALER_MerchantSignatureP *merchant_sig);
+
+
+/**
+ * Verify a deposit.
+ *
+ * @param merchant merchant public key
+ * @param coin_pub public key of the deposited coin
+ * @param h_contract_terms hash of contract terms
+ * @param h_wire hash of the merchant account details
+ * @param merchant_sig signature of the merchant
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_merchant_deposit_verify (
+ const struct TALER_MerchantPublicKeyP *merchant,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_MerchantSignatureP *merchant_sig);
+
+
+/* ********************* exchange online signing ************************** */
+
+
+/**
+ * Signature of a function that signs the message in @a purpose with the
+ * exchange's signing key.
+ *
+ * The @a purpose data is the beginning of the data of which the signature is
+ * to be created. The `size` field in @a purpose must correctly indicate the
+ * number of bytes of the data structure, including its header. *
+ * @param purpose the message to sign
+ * @param[out] pub set to the current public signing key of the exchange
+ * @param[out] sig signature over purpose using current signing key
+ * @return #TALER_EC_NONE on success
+ */
+typedef enum TALER_ErrorCode
+(*TALER_ExchangeSignCallback)(
+ const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Signature of a function that signs the message in @a purpose with the
+ * exchange's signing key.
+ *
+ * The @a purpose data is the beginning of the data of which the signature is
+ * to be created. The `size` field in @a purpose must correctly indicate the
+ * number of bytes of the data structure, including its header. *
+ * @param cls closure
+ * @param purpose the message to sign
+ * @param[out] pub set to the current public signing key of the exchange
+ * @param[out] sig signature over purpose using current signing key
+ * @return #TALER_EC_NONE on success
+ */
+typedef enum TALER_ErrorCode
+(*TALER_ExchangeSignCallback2)(
+ void *cls,
+ const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create deposit confirmation signature.
+ *
+ * @param scb function to call to create the signature
+ * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)
+ * @param h_wire hash of the merchant’s account details
+ * @param h_policy hash over the policy extension, can be NULL
+ * @param exchange_timestamp timestamp when the contract was finalized, must not be too far off
+ * @param wire_deadline date until which the exchange should wire the funds
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline
+ * @param total_without_fee the total amount to be deposited after fees over all coins
+ * @param num_coins length of @a coin_sigs array
+ * @param coin_sigs signatures of the deposited coins
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_deposit_confirmation_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ struct GNUNET_TIME_Timestamp wire_deadline,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_Amount *total_without_fee,
+ unsigned int num_coins,
+ const struct TALER_CoinSpendSignatureP *coin_sigs[static num_coins],
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify deposit confirmation signature.
+ *
+ * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)
+ * @param h_wire hash of the merchant’s account details
+ * @param h_policy hash over the policy extension, can be NULL
+ * @param exchange_timestamp timestamp when the contract was finalized, must not be too far off
+ * @param wire_deadline date until which the exchange should wire the funds
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline
+ * @param total_without_fee the total amount to be deposited after fees over all coins
+ * @param num_coins length of @a coin_sigs array
+ * @param coin_sigs signatures of the deposited coins
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_deposit_confirmation_verify (
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ struct GNUNET_TIME_Timestamp wire_deadline,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_Amount *total_without_fee,
+ unsigned int num_coins,
+ const struct TALER_CoinSpendSignatureP *coin_sigs[static num_coins],
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create refund confirmation signature.
+ *
+ * @param scb function to call to create the signature
+ * @param h_contract_terms hash of contract being refunded
+ * @param coin_pub public key of the coin receiving the refund
+ * @param merchant public key of the merchant that granted the refund
+ * @param rtransaction_id refund transaction ID used by the merchant
+ * @param refund_amount amount refunded
+ * @param[out] pub where to write the exchange public key
+ * @param[out] sig where to write the exchange signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_refund_confirmation_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant,
+ uint64_t rtransaction_id,
+ const struct TALER_Amount *refund_amount,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify refund confirmation signature.
+ *
+ * @param h_contract_terms hash of contract being refunded
+ * @param coin_pub public key of the coin receiving the refund
+ * @param merchant public key of the merchant that granted the refund
+ * @param rtransaction_id refund transaction ID used by the merchant
+ * @param refund_amount amount refunded
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_refund_confirmation_verify (
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant,
+ uint64_t rtransaction_id,
+ const struct TALER_Amount *refund_amount,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create refresh melt confirmation signature.
+ *
+ * @param scb function to call to create the signature
+ * @param rc refresh commitment that identifies the melt operation
+ * @param noreveal_index gamma cut-and-choose value chosen by the exchange
+ * @param[out] pub where to write the exchange public key
+ * @param[out] sig where to write the exchange signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_melt_confirmation_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_RefreshCommitmentP *rc,
+ uint32_t noreveal_index,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify refresh melt confirmation signature.
+ *
+ * @param rc refresh commitment that identifies the melt operation
+ * @param noreveal_index gamma cut-and-choose value chosen by the exchange
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_melt_confirmation_verify (
+ const struct TALER_RefreshCommitmentP *rc,
+ uint32_t noreveal_index,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create exchange purse refund confirmation signature.
+ *
+ * @param scb function to call to create the signature
+ * @param amount_without_fee refunded amount
+ * @param refund_fee refund fee charged
+ * @param coin_pub coin that was refunded
+ * @param purse_pub public key of the expired purse
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_purse_refund_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_Amount *refund_fee,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify signature of exchange affirming purse refund
+ * from purse expiration.
+ *
+ * @param amount_without_fee refunded amount
+ * @param refund_fee refund fee charged
+ * @param coin_pub coin that was refunded
+ * @param purse_pub public key of the expired purse
+ * @param pub public key to verify signature against
+ * @param sig signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_refund_verify (
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_Amount *refund_fee,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create exchange key set signature.
+ *
+ * @param scb function to call to create the signature
+ * @param cls closure for @a scb
+ * @param timestamp time when the key set was issued
+ * @param hc hash over all the keys
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_key_set_sign (
+ TALER_ExchangeSignCallback2 scb,
+ void *cls,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct GNUNET_HashCode *hc,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify key set signature.
+ *
+ * @param timestamp time when the key set was issued
+ * @param hc hash over all the keys
+ * @param pub public key to verify signature against
+ * @param sig signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_key_set_verify (
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct GNUNET_HashCode *hc,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create account KYC setup success signature.
+ *
+ * @param scb function to call to create the signature
+ * @param h_payto target of the KYC account
+ * @param kyc JSON data describing which KYC checks
+ * were satisfied
+ * @param timestamp time when the KYC was confirmed
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_account_setup_success_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_PaytoHashP *h_payto,
+ const json_t *kyc,
+ struct GNUNET_TIME_Timestamp timestamp,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify account KYC setup success signature.
+ *
+ * @param h_payto target of the KYC account
+ * @param kyc JSON data describing which KYC checks
+ * were satisfied
+ * @param timestamp time when the KYC was confirmed
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_account_setup_success_verify (
+ const struct TALER_PaytoHashP *h_payto,
+ const json_t *kyc,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Hash normalized @a j JSON object or array and
+ * store the result in @a hc.
+ *
+ * @param j JSON to hash
+ * @param[out] hc where to write the hash
+ */
+void
+TALER_json_hash (const json_t *j,
+ struct GNUNET_HashCode *hc);
+
+
+/**
+ * Update the @a hash_context in the computation of the
+ * h_details for a wire status signature.
+ *
+ * @param[in,out] hash_context context to update
+ * @param h_contract_terms hash of the contract
+ * @param execution_time when was the wire transfer initiated
+ * @param coin_pub deposited coin
+ * @param deposit_value contribution of the coin
+ * @param deposit_fee how high was the deposit fee
+ */
+void
+TALER_exchange_online_wire_deposit_append (
+ struct GNUNET_HashContext *hash_context,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp execution_time,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *deposit_value,
+ const struct TALER_Amount *deposit_fee);
+
+
+/**
+ * Create wire deposit signature.
+ *
+ * @param scb function to call to create the signature
+ * @param total amount the merchant was credited
+ * @param wire_fee fee charged by the exchange for the wire transfer
+ * @param merchant_pub which merchant was credited
+ * @param payto payto://-URI of the merchant account
+ * @param h_details hash over the aggregation details
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_wire_deposit_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_Amount *total,
+ const struct TALER_Amount *wire_fee,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const char *payto,
+ const struct GNUNET_HashCode *h_details,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify wire deposit signature.
+ *
+ * @param total amount the merchant was credited
+ * @param wire_fee fee charged by the exchange for the wire transfer
+ * @param merchant_pub which merchant was credited
+ * @param h_payto hash of the payto://-URI of the merchant account
+ * @param h_details hash over the aggregation details
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_wire_deposit_verify (
+ const struct TALER_Amount *total,
+ const struct TALER_Amount *wire_fee,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct GNUNET_HashCode *h_details,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create wire confirmation signature.
+ *
+ * @param scb function to call to create the signature
+ * @param h_wire hash of the merchant's account
+ * @param h_contract_terms hash of the contract
+ * @param wtid wire transfer this deposit was aggregated into
+ * @param coin_pub public key of the deposited coin
+ * @param execution_time when was wire transfer initiated
+ * @param coin_contribution what was @a coin_pub's contribution to the wire transfer
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_confirm_wire_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct GNUNET_TIME_Timestamp execution_time,
+ const struct TALER_Amount *coin_contribution,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify confirm wire signature.
+ *
+ * @param h_wire hash of the merchant's account
+ * @param h_contract_terms hash of the contract
+ * @param wtid wire transfer this deposit was aggregated into
+ * @param coin_pub public key of the deposited coin
+ * @param execution_time when was wire transfer initiated
+ * @param coin_contribution what was @a coin_pub's contribution to the wire transfer
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_confirm_wire_verify (
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct GNUNET_TIME_Timestamp execution_time,
+ const struct TALER_Amount *coin_contribution,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create confirm recoup signature.
+ *
+ * @param scb function to call to create the signature
+ * @param timestamp when was the recoup done
+ * @param recoup_amount how much was recouped
+ * @param coin_pub coin that was recouped
+ * @param reserve_pub reserve that was credited
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_confirm_recoup_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_Amount *recoup_amount,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify confirm recoup signature.
+ *
+ * @param timestamp when was the recoup done
+ * @param recoup_amount how much was recouped
+ * @param coin_pub coin that was recouped
+ * @param reserve_pub reserve that was credited
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_confirm_recoup_verify (
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_Amount *recoup_amount,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create confirm recoup refresh signature.
+ *
+ * @param scb function to call to create the signature
+ * @param timestamp when was the recoup done
+ * @param recoup_amount how much was recouped
+ * @param coin_pub coin that was recouped
+ * @param old_coin_pub old coin that was credited
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_confirm_recoup_refresh_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_Amount *recoup_amount,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify confirm recoup refresh signature.
+ *
+ * @param timestamp when was the recoup done
+ * @param recoup_amount how much was recouped
+ * @param coin_pub coin that was recouped
+ * @param old_coin_pub old coin that was credited
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_confirm_recoup_refresh_verify (
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_Amount *recoup_amount,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create denomination unknown signature.
+ *
+ * @param scb function to call to create the signature
+ * @param timestamp when was the error created
+ * @param h_denom_pub hash of denomination that is unknown
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_denomination_unknown_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify denomination unknown signature.
+ *
+ * @param timestamp when was the error created
+ * @param h_denom_pub hash of denomination that is unknown
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_denomination_unknown_verify (
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create denomination expired signature.
+ *
+ * @param scb function to call to create the signature
+ * @param timestamp when was the error created
+ * @param h_denom_pub hash of denomination that is expired
+ * @param op character string describing the operation for which
+ * the denomination is expired
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_denomination_expired_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const char *op,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify denomination expired signature.
+ *
+ * @param timestamp when was the error created
+ * @param h_denom_pub hash of denomination that is expired
+ * @param op character string describing the operation for which
+ * the denomination is expired
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_denomination_expired_verify (
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const char *op,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create reserve closure signature.
+ *
+ * @param scb function to call to create the signature
+ * @param timestamp time when the reserve was closed
+ * @param closing_amount amount left in the reserve
+ * @param closing_fee closing fee charged
+ * @param payto target of the wire transfer
+ * @param wtid wire transfer subject used
+ * @param reserve_pub public key of the closed reserve
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_reserve_closed_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_Amount *closing_amount,
+ const struct TALER_Amount *closing_fee,
+ const char *payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify reserve closure signature.
+ *
+ * @param timestamp time when the reserve was closed
+ * @param closing_amount amount left in the reserve
+ * @param closing_fee closing fee charged
+ * @param payto target of the wire transfer
+ * @param wtid wire transfer subject used
+ * @param reserve_pub public key of the closed reserve
+ * @param pub the public key of the exchange to check against
+ * @param sig the signature to check
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_reserve_closed_verify (
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_Amount *closing_amount,
+ const struct TALER_Amount *closing_fee,
+ const char *payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create signature by exchange affirming that a reserve
+ * has had certain attributes verified via KYC.
+ *
+ * @param scb function to call to create the signature
+ * @param attest_timestamp our time
+ * @param expiration_time when does the KYC data expire
+ * @param reserve_pub for which reserve are attributes attested
+ * @param attributes JSON object with attributes being attested to
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_reserve_attest_details_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp attest_timestamp,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *attributes,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify signature by exchange affirming that a reserve
+ * has had certain attributes verified via KYC.
+ *
+ * @param attest_timestamp our time
+ * @param expiration_time when does the KYC data expire
+ * @param reserve_pub for which reserve are attributes attested
+ * @param attributes JSON object with attributes being attested to
+ * @param pub exchange public key
+ * @param sig exchange signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_reserve_attest_details_verify (
+ struct GNUNET_TIME_Timestamp attest_timestamp,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *attributes,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create signature by exchange affirming that a purse was created.
+ *
+ * @param scb function to call to create the signature
+ * @param exchange_time our time
+ * @param purse_expiration when will the purse expire
+ * @param amount_without_fee total amount to be put into the purse (without deposit fees)
+ * @param total_deposited total currently in the purse
+ * @param purse_pub public key of the purse
+ * @param h_contract_terms hash of the contract for the purse
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_purse_created_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp exchange_time,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_Amount *total_deposited,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify exchange signature about a purse creation and balance.
+ *
+ * @param exchange_time our time
+ * @param purse_expiration when will the purse expire
+ * @param amount_without_fee total amount to be put into the purse (without deposit fees)
+ * @param total_deposited total currently in the purse
+ * @param purse_pub public key of the purse
+ * @param h_contract_terms hash of the contract for the purse
+ * @param pub the public key of the exchange to check against
+ * @param sig the signature to check
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_created_verify (
+ struct GNUNET_TIME_Timestamp exchange_time,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_Amount *total_deposited,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Sign affirmation that a purse was merged.
+ *
+ * @param scb function to call to create the signature
+ * @param exchange_time our time
+ * @param purse_expiration when does the purse expire
+ * @param amount_without_fee total amount that should be in the purse without deposit fees
+ * @param purse_pub public key of the purse
+ * @param h_contract_terms hash of the contract of the purse
+ * @param reserve_pub reserve the purse will be merged into
+ * @param exchange_url exchange at which the @a reserve_pub lives
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_purse_merged_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp exchange_time,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *exchange_url,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify affirmation that a purse will be merged.
+ *
+ * @param exchange_time our time
+ * @param purse_expiration when does the purse expire
+ * @param amount_without_fee total amount that should be in the purse without deposit fees
+ * @param purse_pub public key of the purse
+ * @param h_contract_terms hash of the contract of the purse
+ * @param reserve_pub reserve the purse will be merged into
+ * @param exchange_url exchange at which the @a reserve_pub lives
+ * @param pub the public key of the exchange to check against
+ * @param sig the signature to check
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_merged_verify (
+ struct GNUNET_TIME_Timestamp exchange_time,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *exchange_url,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Sign information about the status of a purse.
+ *
+ * @param scb function to call to create the signature
+ * @param merge_timestamp when was the purse merged (can be never)
+ * @param deposit_timestamp when was the purse fully paid up (can be never)
+ * @param balance current balance of the purse
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_purse_status_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
+ const struct TALER_Amount *balance,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify signature over information about the status of a purse.
+ *
+ * @param merge_timestamp when was the purse merged (can be never)
+ * @param deposit_timestamp when was the purse fully paid up (can be never)
+ * @param balance current balance of the purse
+ * @param exchange_pub the public key of the exchange to check against
+ * @param exchange_sig the signature to check
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_status_verify (
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
+ const struct TALER_Amount *balance,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig);
+
+
+/**
+ * Create age-withdraw confirmation signature.
+ *
+ * @param scb function to call to create the signature
+ * @param h_commitment age-withdraw commitment that identifies the n*kappa blinded coins
+ * @param noreveal_index gamma cut-and-choose value chosen by the exchange
+ * @param[out] pub where to write the exchange public key
+ * @param[out] sig where to write the exchange signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_age_withdraw_confirmation_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ uint32_t noreveal_index,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify an exchange age-withdraw confirmation
+ *
+ * @param h_commitment Commitment over all n*kappa coin candidates from the original request to age-withdraw
+ * @param noreveal_index The index returned by the exchange
+ * @param exchange_pub The public key used for signing
+ * @param exchange_sig The signature from the exchange
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_age_withdraw_confirmation_verify (
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ uint32_t noreveal_index,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig);
+
+
+/* ********************* offline signing ************************** */
+
+
+/**
+ * Create AML officer status change signature.
+ *
+ * @param officer_pub public key of the AML officer
+ * @param officer_name name of the officer
+ * @param change_date when to affect the status change
+ * @param is_active true to enable the officer
+ * @param read_only true to only allow read-only access
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_aml_officer_status_sign (
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *officer_name,
+ struct GNUNET_TIME_Timestamp change_date,
+ bool is_active,
+ bool read_only,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify AML officer status change signature.
+ *
+ * @param officer_pub public key of the AML officer
+ * @param officer_name name of the officer
+ * @param change_date when to affect the status change
+ * @param is_active true to enable the officer
+ * @param read_only true to only allow read-only access
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_aml_officer_status_verify (
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *officer_name,
+ struct GNUNET_TIME_Timestamp change_date,
+ bool is_active,
+ bool read_only,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create auditor addition signature.
+ *
+ * @param auditor_pub public key of the auditor
+ * @param auditor_url URL of the auditor
+ * @param start_date when to enable the auditor (for replay detection)
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_auditor_add_sign (
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ struct GNUNET_TIME_Timestamp start_date,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify auditor add signature.
+ *
+ * @param auditor_pub public key of the auditor
+ * @param auditor_url URL of the auditor
+ * @param start_date when to enable the auditor (for replay detection)
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_auditor_add_verify (
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ struct GNUNET_TIME_Timestamp start_date,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create auditor deletion signature.
+ *
+ * @param auditor_pub public key of the auditor
+ * @param end_date when to disable the auditor (for replay detection)
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_auditor_del_sign (
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify auditor del signature.
+ *
+ * @param auditor_pub public key of the auditor
+ * @param end_date when to disable the auditor (for replay detection)
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_auditor_del_verify (
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create denomination revocation signature.
+ *
+ * @param h_denom_pub hash of public denomination key to revoke
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_denomination_revoke_sign (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify denomination revocation signature.
+ *
+ * @param h_denom_pub hash of public denomination key to revoke
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_denomination_revoke_verify (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create signkey revocation signature.
+ *
+ * @param exchange_pub public signing key to revoke
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_signkey_revoke_sign (
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify signkey revocation signature.
+ *
+ * @param exchange_pub public signkey key to revoke
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_signkey_revoke_verify (
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create signkey validity signature.
+ *
+ * @param exchange_pub public signing key to validate
+ * @param start_sign starting point of validity for signing
+ * @param end_sign end point (exclusive) for validity for signing
+ * @param end_legal legal end point of signature validity
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_signkey_validity_sign (
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Timestamp end_sign,
+ struct GNUNET_TIME_Timestamp end_legal,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify signkey validitity signature.
+ *
+ * @param exchange_pub public signkey key to validate
+ * @param start_sign starting point of validity for signing
+ * @param end_sign end point (exclusive) for validity for signing
+ * @param end_legal legal end point of signature validity
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_signkey_validity_verify (
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Timestamp end_sign,
+ struct GNUNET_TIME_Timestamp end_legal,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create denomination key validity signature.
+ *
+ * @param h_denom_pub hash of the denomination's public key
+ * @param stamp_start when does the exchange begin signing with this key
+ * @param stamp_expire_withdraw when does the exchange end signing with this key
+ * @param stamp_expire_deposit how long does the exchange accept the deposit of coins with this key
+ * @param stamp_expire_legal how long does the exchange preserve information for legal disputes with this key
+ * @param coin_value what is the value of coins signed with this key
+ * @param fees fees for this denomination
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_denom_validity_sign (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct GNUNET_TIME_Timestamp stamp_start,
+ struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+ struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+ struct GNUNET_TIME_Timestamp stamp_expire_legal,
+ const struct TALER_Amount *coin_value,
+ const struct TALER_DenomFeeSet *fees,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify denomination key validity signature.
+ *
+ * @param h_denom_pub hash of the denomination's public key
+ * @param stamp_start when does the exchange begin signing with this key
+ * @param stamp_expire_withdraw when does the exchange end signing with this key
+ * @param stamp_expire_deposit how long does the exchange accept the deposit of coins with this key
+ * @param stamp_expire_legal how long does the exchange preserve information for legal disputes with this key
+ * @param coin_value what is the value of coins signed with this key
+ * @param fees fees for this denomination
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_denom_validity_verify (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct GNUNET_TIME_Timestamp stamp_start,
+ struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+ struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+ struct GNUNET_TIME_Timestamp stamp_expire_legal,
+ const struct TALER_Amount *coin_value,
+ const struct TALER_DenomFeeSet *fees,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create offline signature about an exchange's partners.
+ *
+ * @param partner_pub master public key of the partner
+ * @param start_date validity period start
+ * @param end_date validity period end
+ * @param wad_frequency how often will we do wad transfers to this partner
+ * @param wad_fee what is the wad fee to this partner
+ * @param partner_base_url what is the base URL of the @a partner_pub exchange
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_partner_details_sign (
+ const struct TALER_MasterPublicKeyP *partner_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ struct GNUNET_TIME_Relative wad_frequency,
+ const struct TALER_Amount *wad_fee,
+ const char *partner_base_url,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify signature about an exchange's partners.
+ *
+ * @param partner_pub master public key of the partner
+ * @param start_date validity period start
+ * @param end_date validity period end
+ * @param wad_frequency how often will we do wad transfers to this partner
+ * @param wad_fee what is the wad fee to this partner
+ * @param partner_base_url what is the base URL of the @a partner_pub exchange
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_partner_details_verify (
+ const struct TALER_MasterPublicKeyP *partner_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ struct GNUNET_TIME_Relative wad_frequency,
+ const struct TALER_Amount *wad_fee,
+ const char *partner_base_url,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create offline signature about wiring profits to a
+ * regular non-escrowed account of the exchange.
+ *
+ * @param wtid (random) wire transfer ID to be used
+ * @param date when was the profit drain approved (not exact time of execution)
+ * @param amount how much should be wired
+ * @param account_section configuration section of the
+ * exchange specifying the account to be debited
+ * @param payto_uri target account to be credited
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_profit_drain_sign (
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_TIME_Timestamp date,
+ const struct TALER_Amount *amount,
+ const char *account_section,
+ const char *payto_uri,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify offline signature about wiring profits to a
+ * regular non-escrowed account of the exchange.
+ *
+ * @param wtid (random) wire transfer ID to be used
+ * @param date when was the profit drain approved (not exact time of execution)
+ * @param amount how much should be wired
+ * @param account_section configuration section of the
+ * exchange specifying the account to be debited
+ * @param payto_uri target account to be credited
+ * @param master_pub public key to verify signature against
+ * @param master_sig the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_profit_drain_verify (
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_TIME_Timestamp date,
+ const struct TALER_Amount *amount,
+ const char *account_section,
+ const char *payto_uri,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create security module EdDSA signature.
+ *
+ * @param exchange_pub public signing key to validate
+ * @param start_sign starting point of validity for signing
+ * @param duration how long will the key be in use
+ * @param secm_priv security module key to sign with
+ * @param[out] secm_sig where to write the signature
+ */
+void
+TALER_exchange_secmod_eddsa_sign (
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Relative duration,
+ const struct TALER_SecurityModulePrivateKeyP *secm_priv,
+ struct TALER_SecurityModuleSignatureP *secm_sig);
+
+
+/**
+ * Verify security module EdDSA signature.
+ *
+ * @param exchange_pub public signing key to validate
+ * @param start_sign starting point of validity for signing
+ * @param duration how long will the key be in use
+ * @param secm_pub public key to verify against
+ * @param secm_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_secmod_eddsa_verify (
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Relative duration,
+ const struct TALER_SecurityModulePublicKeyP *secm_pub,
+ const struct TALER_SecurityModuleSignatureP *secm_sig);
+
+
+/**
+ * Create security module denomination signature.
+ *
+ * @param h_rsa hash of the RSA public key to sign
+ * @param section_name name of the section in the configuration
+ * @param start_sign starting point of validity for signing
+ * @param duration how long will the key be in use
+ * @param secm_priv security module key to sign with
+ * @param[out] secm_sig where to write the signature
+ */
+void
+TALER_exchange_secmod_rsa_sign (
+ const struct TALER_RsaPubHashP *h_rsa,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Relative duration,
+ const struct TALER_SecurityModulePrivateKeyP *secm_priv,
+ struct TALER_SecurityModuleSignatureP *secm_sig);
+
+
+/**
+ * Verify security module denomination signature.
+ *
+ * @param h_rsa hash of the public key to validate
+ * @param section_name name of the section in the configuration
+ * @param start_sign starting point of validity for signing
+ * @param duration how long will the key be in use
+ * @param secm_pub public key to verify against
+ * @param secm_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_secmod_rsa_verify (
+ const struct TALER_RsaPubHashP *h_rsa,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Relative duration,
+ const struct TALER_SecurityModulePublicKeyP *secm_pub,
+ const struct TALER_SecurityModuleSignatureP *secm_sig);
+
+
+/**
+ * Create security module denomination signature.
+ *
+ * @param h_cs hash of the CS public key to sign
+ * @param section_name name of the section in the configuration
+ * @param start_sign starting point of validity for signing
+ * @param duration how long will the key be in use
+ * @param secm_priv security module key to sign with
+ * @param[out] secm_sig where to write the signature
+ */
+void
+TALER_exchange_secmod_cs_sign (
+ const struct TALER_CsPubHashP *h_cs,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Relative duration,
+ const struct TALER_SecurityModulePrivateKeyP *secm_priv,
+ struct TALER_SecurityModuleSignatureP *secm_sig);
+
+
+/**
+ * Verify security module denomination signature.
+ *
+ * @param h_cs hash of the public key to validate
+ * @param section_name name of the section in the configuration
+ * @param start_sign starting point of validity for signing
+ * @param duration how long will the key be in use
+ * @param secm_pub public key to verify against
+ * @param secm_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_secmod_cs_verify (
+ const struct TALER_CsPubHashP *h_cs,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Relative duration,
+ const struct TALER_SecurityModulePublicKeyP *secm_pub,
+ const struct TALER_SecurityModuleSignatureP *secm_sig);
+
+
+/**
+ * Create denomination key validity signature by the auditor.
+ *
+ * @param auditor_url BASE URL of the auditor's API
+ * @param h_denom_pub hash of the denomination's public key
+ * @param master_pub master public key of the exchange
+ * @param stamp_start when does the exchange begin signing with this key
+ * @param stamp_expire_withdraw when does the exchange end signing with this key
+ * @param stamp_expire_deposit how long does the exchange accept the deposit of coins with this key
+ * @param stamp_expire_legal how long does the exchange preserve information for legal disputes with this key
+ * @param coin_value what is the value of coins signed with this key
+ * @param fees fees the exchange charges for this denomination
+ * @param auditor_priv private key to sign with
+ * @param[out] auditor_sig where to write the signature
+ */
+void
+TALER_auditor_denom_validity_sign (
+ const char *auditor_url,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Timestamp stamp_start,
+ struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+ struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+ struct GNUNET_TIME_Timestamp stamp_expire_legal,
+ const struct TALER_Amount *coin_value,
+ const struct TALER_DenomFeeSet *fees,
+ const struct TALER_AuditorPrivateKeyP *auditor_priv,
+ struct TALER_AuditorSignatureP *auditor_sig);
+
+
+/**
+ * Verify denomination key validity signature from auditor.
+ *
+ * @param auditor_url BASE URL of the auditor's API
+ * @param h_denom_pub hash of the denomination's public key
+ * @param master_pub master public key of the exchange
+ * @param stamp_start when does the exchange begin signing with this key
+ * @param stamp_expire_withdraw when does the exchange end signing with this key
+ * @param stamp_expire_deposit how long does the exchange accept the deposit of coins with this key
+ * @param stamp_expire_legal how long does the exchange preserve information for legal disputes with this key
+ * @param coin_value what is the value of coins signed with this key
+ * @param fees fees the exchange charges for this denomination
+ * @param auditor_pub public key to verify against
+ * @param auditor_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_auditor_denom_validity_verify (
+ const char *auditor_url,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Timestamp stamp_start,
+ struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+ struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+ struct GNUNET_TIME_Timestamp stamp_expire_legal,
+ const struct TALER_Amount *coin_value,
+ const struct TALER_DenomFeeSet *fees,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_AuditorSignatureP *auditor_sig);
+
+
/* **************** /wire account offline signing **************** */
/**
- * Compute the hash of the given wire details. The resulting
- * hash is what is signed by the master key.
+ * Create wire fee signature.
+ *
+ * @param payment_method the payment method
+ * @param start_time when do the fees start to apply
+ * @param end_time when do the fees start to apply
+ * @param fees the wire fees
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_wire_fee_sign (
+ const char *payment_method,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ const struct TALER_WireFeeSet *fees,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify wire fee signature.
+ *
+ * @param payment_method the payment method
+ * @param start_time when do the fees start to apply
+ * @param end_time when do the fees start to apply
+ * @param fees the wire fees
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_wire_fee_verify (
+ const char *payment_method,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ const struct TALER_WireFeeSet *fees,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create global fees signature.
+ *
+ * @param start_time when do the fees start to apply
+ * @param end_time when do the fees start to apply
+ * @param fees the global fees
+ * @param purse_timeout how long do unmerged purses stay around
+ * @param history_expiration how long do we keep the history of an account
+ * @param purse_account_limit how many concurrent purses are free per account holder
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_global_fee_sign (
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify global fees signature.
+ *
+ * @param start_time when do the fees start to apply
+ * @param end_time when do the fees start to apply
+ * @param fees the global fees
+ * @param purse_timeout how long do unmerged purses stay around
+ * @param history_expiration how long do we keep the history of an account
+ * @param purse_account_limit how many concurrent purses are free per account holder
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_global_fee_verify (
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create wire account addition signature.
*
* @param payto_uri bank account
- * @param[out] hc set to the hash
+ * @param conversion_url URL of the conversion service, or NULL if none
+ * @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec
+ * @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec
+ * @param now timestamp to use for the signature (rounded)
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
*/
void
-TALER_exchange_wire_signature_hash (const char *payto_uri,
- struct GNUNET_HashCode *hc);
+TALER_exchange_offline_wire_add_sign (
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify wire account addition signature.
+ *
+ * @param payto_uri bank account
+ * @param conversion_url URL of the conversion service, or NULL if none
+ * @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec
+ * @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec
+ * @param sign_time timestamp when signature was created
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_wire_add_verify (
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp sign_time,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create wire account removal signature.
+ *
+ * @param payto_uri bank account
+ * @param now timestamp to use for the signature (rounded)
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_wire_del_sign (
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify wire account deletion signature.
+ *
+ * @param payto_uri bank account
+ * @param sign_time timestamp when signature was created
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_wire_del_verify (
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp sign_time,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig);
/**
* Check the signature in @a master_sig.
*
- * @param payto_uri URL that is signed
+ * @param payto_uri URI that is signed
+ * @param conversion_url URL of the conversion service, or NULL if none
+ * @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec
+ * @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec
* @param master_pub master public key of the exchange
* @param master_sig signature of the exchange
* @return #GNUNET_OK if signature is valid
*/
-int
+enum GNUNET_GenericReturnValue
TALER_exchange_wire_signature_check (
const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_MasterSignatureP *master_sig);
@@ -764,12 +5468,18 @@ TALER_exchange_wire_signature_check (
* Create a signed wire statement for the given account.
*
* @param payto_uri account specification
+ * @param conversion_url URL of the conversion service, or NULL if none
+ * @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec
+ * @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec
* @param master_priv private key to sign with
* @param[out] master_sig where to write the signature
*/
void
TALER_exchange_wire_signature_make (
const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
const struct TALER_MasterPrivateKeyP *master_priv,
struct TALER_MasterSignatureP *master_sig);
@@ -785,8 +5495,8 @@ TALER_exchange_wire_signature_make (
*/
void
TALER_merchant_wire_signature_hash (const char *payto_uri,
- const char *salt,
- struct GNUNET_HashCode *hc);
+ const struct TALER_WireSaltP *salt,
+ struct TALER_MerchantWireHashP *hc);
/**
@@ -798,10 +5508,10 @@ TALER_merchant_wire_signature_hash (const char *payto_uri,
* @param merch_sig signature of the merchant
* @return #GNUNET_OK if signature is valid
*/
-int
+enum GNUNET_GenericReturnValue
TALER_merchant_wire_signature_check (
const char *payto_uri,
- const char *salt,
+ const struct TALER_WireSaltP *salt,
const struct TALER_MerchantPublicKeyP *merch_pub,
const struct TALER_MerchantSignatureP *merch_sig);
@@ -817,9 +5527,444 @@ TALER_merchant_wire_signature_check (
void
TALER_merchant_wire_signature_make (
const char *payto_uri,
- const char *salt,
+ const struct TALER_WireSaltP *salt,
const struct TALER_MerchantPrivateKeyP *merch_priv,
struct TALER_MerchantSignatureP *merch_sig);
+/**
+ * Sign a payment confirmation.
+ *
+ * @param h_contract_terms hash of the contact of the merchant with the customer
+ * @param merch_priv private key to sign with
+ * @param[out] merch_sig where to write the signature
+ */
+void
+TALER_merchant_pay_sign (
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantPrivateKeyP *merch_priv,
+ struct TALER_MerchantSignatureP *merch_sig);
+
+
+/**
+ * Verify payment confirmation signature.
+ *
+ * @param h_contract_terms hash of the contact of the merchant with the customer
+ * @param merchant_pub public key of the merchant
+ * @param merchant_sig signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_merchant_pay_verify (
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig);
+
+
+/**
+ * Sign contract sent by the merchant to the wallet.
+ *
+ * @param h_contract_terms hash of the contract terms
+ * @param merch_priv private key to sign with
+ * @param[out] merch_sig where to write the signature
+ */
+void
+TALER_merchant_contract_sign (
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantPrivateKeyP *merch_priv,
+ struct GNUNET_CRYPTO_EddsaSignature *merch_sig);
+
+
+/* **************** /management/extensions offline signing **************** */
+
+/**
+ * Create a signature for the hash of the manifests of extensions
+ *
+ * @param h_manifests hash of the JSON object representing the manifests
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_extension_manifests_hash_sign (
+ const struct TALER_ExtensionManifestsHashP *h_manifests,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify the signature in @a master_sig of the given hash, taken over the JSON
+ * blob representing the manifests of extensions
+ *
+ * @param h_manifest hash of the JSON blob of manifests of extensions
+ * @param master_pub master public key of the exchange
+ * @param master_sig signature of the exchange
+ * @return #GNUNET_OK if signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_extension_manifests_hash_verify (
+ const struct TALER_ExtensionManifestsHashP *h_manifest,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig
+ );
+
+
+/**
+ * @brief Representation of an age commitment: one public key per age group.
+ *
+ * The number of keys must be be the same as the number of bits set in the
+ * corresponding age mask.
+ */
+struct TALER_AgeCommitment
+{
+
+ /**
+ * The age mask defines the age groups that were a parameter during the
+ * generation of this age commitment
+ */
+ struct TALER_AgeMask mask;
+
+ /**
+ * The number of public keys, which must be the same as the number of
+ * groups in the mask.
+ */
+ size_t num;
+
+ /**
+ * The list of @e num public keys. In must have same size as the number of
+ * age groups defined in the mask.
+ *
+ * A hash of this list is the hashed commitment that goes into FDC
+ * calculation during the withdraw and refresh operations for new coins. That
+ * way, the particular age commitment becomes mandatory and bound to a coin.
+ *
+ * The list has been allocated via GNUNET_malloc().
+ */
+ struct TALER_AgeCommitmentPublicKeyP *keys;
+};
+
+
+/**
+ * @brief Proof for a particular age commitment, used in age attestation
+ *
+ * This struct is used in a call to TALER_age_commitment_attest to create an
+ * attestation for a minimum age (if that minimum age is less or equal to the
+ * committed age for this proof). It consists of a list private keys, one per
+ * age group, for which the committed age is either lager or within that
+ * particular group.
+ */
+struct TALER_AgeProof
+{
+ /**
+ * The number of private keys, which must be at most num_pub_keys. One minus
+ * this number corresponds to the largest age group that is supported with
+ * this age commitment.
+ * **Note**, that this and the next field are only relevant on the wallet
+ * side for attestation and derive operations.
+ */
+ size_t num;
+
+ /**
+ * List of @e num private keys.
+ *
+ * Note that the list can be _smaller_ than the corresponding list of public
+ * keys. In that case, the wallet can sign off only for a subset of the age
+ * groups.
+ *
+ * The list has been allocated via GNUNET_malloc.
+ */
+ struct TALER_AgeCommitmentPrivateKeyP *keys;
+};
+
+
+/**
+ * @brief Commitment and Proof for a maximum age
+ *
+ * Calling TALER_age_restriction_commit on an (maximum) age value returns this
+ * data structure. It consists of the proof, which is used to create
+ * attestations for compatible minimum ages, and the commitment, which is used
+ * to verify the attestations and derived commitments.
+ *
+ * The hash value of the commitment is bound to a particular coin with age
+ * restriction.
+ */
+struct TALER_AgeCommitmentProof
+{
+ /**
+ * The commitment is used to verify a particular attestation. Its hash value
+ * is bound to a particular coin with age restriction. This structure is
+ * sent to the merchant in order to verify a particular attestation for a
+ * minimum age.
+ * In itself, it does not convey any information about the maximum age that
+ * went into the call to TALER_age_restriction_commit.
+ */
+ struct TALER_AgeCommitment commitment;
+
+ /**
+ * The proof is used to create an attestation for a (compatible) minimum age.
+ */
+ struct TALER_AgeProof proof;
+};
+
+
+/**
+ * @brief Generates a hash of the public keys in the age commitment.
+ *
+ * @param commitment the age commitment - one public key per age group
+ * @param[out] hash resulting hash
+ */
+void
+TALER_age_commitment_hash (
+ const struct TALER_AgeCommitment *commitment,
+ struct TALER_AgeCommitmentHash *hash);
+
+
+/**
+ * @brief Generates an age commitent for the given age.
+ *
+ * @param mask The age mask the defines the age groups
+ * @param age The actual age for which an age commitment is generated
+ * @param seed The seed that goes into the key generation. MUST be chosen uniformly random.
+ * @param[out] comm_proof The generated age commitment, ->priv and ->pub allocated via GNUNET_malloc() on success
+ */
+void
+TALER_age_restriction_commit (
+ const struct TALER_AgeMask *mask,
+ uint8_t age,
+ const struct GNUNET_HashCode *seed,
+ struct TALER_AgeCommitmentProof *comm_proof);
+
+
+/**
+ * @brief Derives another, equivalent age commitment for a given one.
+ *
+ * @param orig Original age commitment
+ * @param salt Salt to randomly move the points on the elliptic curve in order to generate another, equivalent commitment.
+ * @param[out] derived The resulting age commitment, ->priv and ->pub allocated via GNUNET_malloc() on success.
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_age_commitment_derive (
+ const struct TALER_AgeCommitmentProof *orig,
+ const struct GNUNET_HashCode *salt,
+ struct TALER_AgeCommitmentProof *derived);
+
+
+/**
+ * @brief Provide attestation for a given age, from a given age commitment, if possible.
+ *
+ * @param comm_proof The age commitment to be used for attestation. For successful attestation, it must contain the private key for the corresponding age group.
+ * @param age Age (not age group) for which the an attestation should be done
+ * @param[out] attest Signature of the age with the appropriate key from the age commitment for the corresponding age group, if applicable.
+ * @return #GNUNET_OK on success, #GNUNET_NO when no attestation can be made for that age with the given commitment, #GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_age_commitment_attest (
+ const struct TALER_AgeCommitmentProof *comm_proof,
+ uint8_t age,
+ struct TALER_AgeAttestation *attest);
+
+
+/**
+ * @brief Verify the attestation for an given age and age commitment
+ *
+ * @param commitment The age commitment that went into the attestation. Only the public keys are needed.
+ * @param age Age (not age group) for which the an attestation should be done
+ * @param attest Signature of the age with the appropriate key from the age commitment for the corresponding age group, if applicable.
+ * @return #GNUNET_OK when the attestation was successful, #GNUNET_NO no attestation couldn't be verified, #GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_age_commitment_verify (
+ const struct TALER_AgeCommitment *commitment,
+ uint8_t age,
+ const struct TALER_AgeAttestation *attest);
+
+
+/**
+ * @brief helper function to free memory of a struct TALER_AgeCommitment
+ *
+ * @param ac the commitment from which all memory should be freed.
+ */
+void
+TALER_age_commitment_free (
+ struct TALER_AgeCommitment *ac);
+
+
+/**
+ * @brief helper function to free memory of a struct TALER_AgeProof
+ *
+ * @param ap the proof of commitment from which all memory should be freed.
+ */
+void
+TALER_age_proof_free (
+ struct TALER_AgeProof *ap);
+
+
+/**
+ * @brief helper function to free memory of a struct TALER_AgeCommitmentProof
+ *
+ * @param acp the commitment and its proof from which all memory should be freed.
+ */
+void
+TALER_age_commitment_proof_free (
+ struct TALER_AgeCommitmentProof *acp);
+
+
+/**
+ * @brief helper function to allocate and copy a struct TALER_AgeCommitmentProof
+ *
+ * @param[in] acp The original age commitment proof
+ * @return The deep copy of @e acp, allocated
+ */
+struct TALER_AgeCommitmentProof *
+TALER_age_commitment_proof_duplicate (
+ const struct TALER_AgeCommitmentProof *acp);
+
+/**
+ * @brief helper function to copy a struct TALER_AgeCommitmentProof
+ *
+ * @param[in] acp The original age commitment proof
+ * @param[out] nacp The struct to copy the data into, with freshly allocated and copied keys.
+ */
+void
+TALER_age_commitment_proof_deep_copy (
+ const struct TALER_AgeCommitmentProof *acp,
+ struct TALER_AgeCommitmentProof *nacp);
+
+/**
+ * @brief For age-withdraw, clients have to prove that the public keys for all
+ * age groups larger than the allowed maximum age group are derived by scalar
+ * multiplication from this Edx25519 public key (in Crockford Base32 encoding):
+ *
+ * DZJRF6HXN520505XDAWM8NMH36QV9J3VH77265WQ09EBQ76QSKCG
+ *
+ * Its private key was chosen randomly and then deleted.
+ */
+extern struct
+#ifndef AGE_RESTRICTION_WITH_ECDSA
+GNUNET_CRYPTO_Edx25519PublicKey
+#else
+GNUNET_CRYPTO_EcdsaPublicKey
+#endif
+TALER_age_commitment_base_public_key;
+
+/**
+ * @brief Similar to TALER_age_restriction_commit, but takes the coin's
+ * private key as seed input and calculates the public keys in the slots larger
+ * than the given age as derived from TALER_age_commitment_base_public_key.
+ *
+ * See https://docs.taler.net/core/api-exchange.html#withdraw-with-age-restriction
+ *
+ * @param secret The master secret of the coin from which we derive the age restriction
+ * @param mask The age mask, defining the age groups
+ * @param max_age The maximum age for this coin.
+ * @param[out] comm_proof The commitment and proof for age restriction for age @a max_age
+ */
+void
+TALER_age_restriction_from_secret (
+ const struct TALER_PlanchetMasterSecretP *secret,
+ const struct TALER_AgeMask *mask,
+ const uint8_t max_age,
+ struct TALER_AgeCommitmentProof *comm_proof);
+
+
+/**
+ * Group of Denominations. These are the common fields of an array of
+ * denominations.
+ *
+ * The corresponding JSON-blob will also contain an array of particular
+ * denominations with only the timestamps, cipher-specific public key and the
+ * master signature.
+ */
+struct TALER_DenominationGroup
+{
+
+ /**
+ * Value of coins in this denomination group.
+ */
+ struct TALER_Amount value;
+
+ /**
+ * Fee structure for all coins in the group.
+ */
+ struct TALER_DenomFeeSet fees;
+
+ /**
+ * Cipher used for the denomination.
+ */
+ enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher;
+
+ /**
+ * Age mask for the denomination.
+ */
+ struct TALER_AgeMask age_mask;
+
+};
+
+
+/**
+ * Compute a unique key for the meta data of a denomination group.
+ *
+ * @param dg denomination group to evaluate
+ * @param[out] key key to set
+ */
+void
+TALER_denomination_group_get_key (
+ const struct TALER_DenominationGroup *dg,
+ struct GNUNET_HashCode *key);
+
+
+/**
+ * Token family public key.
+ */
+struct TALER_TokenFamilyPublicKey
+{
+ /**
+ * Type of the signature.
+ */
+ struct GNUNET_CRYPTO_BlindSignPublicKey public_key;
+};
+
+/**
+ * Hash of a public key of a token family.
+ */
+struct TALER_TokenFamilyPublicKeyHash
+{
+ /**
+ * Hash of the token public key.
+ */
+ struct GNUNET_HashCode hash;
+};
+
+/**
+ * Token family private key.
+ */
+struct TALER_TokenFamilyPrivateKey
+{
+ struct GNUNET_CRYPTO_BlindSignPrivateKey private_key;
+};
+
+/**
+ * Token public key.
+ */
+struct TALER_TokenPublicKey
+{
+ struct GNUNET_CRYPTO_EddsaPublicKey public_key;
+};
+
+/**
+ * Signature made using a token private key.
+ */
+struct TALER_TokenSignature
+{
+ struct GNUNET_CRYPTO_EddsaSignature signature;
+};
+
+/**
+ * Blind signature for a token (signed by merchant).
+ */
+struct TALER_TokenBlindSignature
+{
+ struct GNUNET_CRYPTO_BlindedSignature signature;
+};
+
#endif
diff --git a/src/include/taler_curl_lib.h b/src/include/taler_curl_lib.h
index d7c24a13b..f108e6158 100644
--- a/src/include/taler_curl_lib.h
+++ b/src/include/taler_curl_lib.h
@@ -30,7 +30,7 @@
/**
* Should we compress PUT/POST bodies with 'deflate' encoding?
*/
-#define COMPRESS_BODIES 1
+#define TALER_CURL_COMPRESS_BODIES 1
/**
* State used for #TALER_curl_easy_post() and
@@ -47,6 +47,11 @@ struct TALER_CURL_PostContext
* Custom headers.
*/
struct curl_slist *headers;
+
+ /**
+ * Set to true to disable compression of the body.
+ */
+ bool disable_compression;
};
@@ -59,7 +64,7 @@ struct TALER_CURL_PostContext
* @param body JSON body to add to @e ctx
* @return #GNUNET_OK on success #GNUNET_SYSERR on failure
*/
-int
+enum GNUNET_GenericReturnValue
TALER_curl_easy_post (struct TALER_CURL_PostContext *ctx,
CURL *eh,
const json_t *body);
@@ -74,4 +79,17 @@ void
TALER_curl_easy_post_finished (struct TALER_CURL_PostContext *ctx);
+/**
+ * Set a secure redirection policy, allowing a limited
+ * number of redirects and only going from HTTP to HTTPS
+ * but not from HTTPS to HTTP.
+ *
+ * @param[in,out] eh easy handle to modify
+ * @param url URL to base the redirect policy on;
+ * must start with "http://" or "https://"
+ */
+void
+TALER_curl_set_secure_redirect_policy (CURL *eh,
+ const char *url);
+
#endif
diff --git a/src/include/taler_error_codes.h b/src/include/taler_error_codes.h
deleted file mode 100644
index 94475f19c..000000000
--- a/src/include/taler_error_codes.h
+++ /dev/null
@@ -1,2160 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2016, 2017, 2019 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 taler_error_codes.h
- * @brief error codes returned by GNU Taler
- *
- * This file defines constants for error codes returned
- * in Taler APIs. We use codes above 1000 to avoid any
- * confusing with HTTP status codes. All constants have the
- * shared prefix "TALER_EC_" to indicate that they are error
- * codes.
- *
- * THIS FILE IS AUTO-GENERATED, DO NOT MODIFY!
- * If you want to add an error code, please add it in the
- * taler-util.git repository. Instructions
- * for this are in the README in taler-util.git.
- */
-#ifndef TALER_ERROR_CODES_H
-#define TALER_ERROR_CODES_H
-
-/**
- * Enumeration with all possible Taler error codes.
- */
-enum TALER_ErrorCode
-{
-
- /**
- * Special code to indicate no error (or no "code" present).
- */
- TALER_EC_NONE = 0,
-
- /**
- * Special code to indicate that a non-integer error code was returned
- * in the JSON response.
- */
- TALER_EC_INVALID = 1,
-
- /**
- * The response we got from the server was not even in JSON format.
- */
- TALER_EC_INVALID_RESPONSE = 2,
-
- /**
- * Generic implementation error: this function was not yet
- * implemented.
- */
- TALER_EC_NOT_IMPLEMENTED = 3,
-
- /**
- * Exchange is badly configured and thus cannot operate.
- */
- TALER_EC_EXCHANGE_BAD_CONFIGURATION = 4,
-
- /**
- * Internal assertion error.
- */
- TALER_EC_INTERNAL_INVARIANT_FAILURE = 5,
-
- /**
- * Operation timed out.
- */
- TALER_EC_TIMEOUT = 6,
-
- /**
- * Exchange failed to allocate memory for building JSON reply.
- */
- TALER_EC_JSON_ALLOCATION_FAILURE = 7,
-
- /**
- * HTTP method invalid for this URL.
- */
- TALER_EC_METHOD_INVALID = 8,
-
- /**
- * Operation specified invalid for this URL (resulting in a "NOT
- * FOUND" for the overall response).
- */
- TALER_EC_OPERATION_INVALID = 9,
-
- /**
- * There is no endpoint defined for the URL provided by the client
- * (returned together with a #MHD_HTTP_NOT_FOUND status code).
- */
- TALER_EC_ENDPOINT_UNKNOWN = 10,
-
- /**
- * The URI is longer than the longest URI the HTTP server is willing
- * to parse. Returned together with an HTTP status code of
- * #MHD_HTTP_URI_TOO_LONG.
- */
- TALER_EC_URI_TOO_LONG = 11,
-
- /**
- * The number of segments included in the URI does not match the
- * number of segments expected by the endpoint. (returned together
- * with a #MHD_HTTP_NOT_FOUND status code).
- */
- TALER_EC_WRONG_NUMBER_OF_SEGMENTS = 12,
-
- /**
- * The start and end-times in the wire fee structure leave a hole.
- * This is not allowed. Generated as an error on the client-side.
- */
- TALER_EC_HOLE_IN_WIRE_FEE_STRUCTURE = 13,
-
- /**
- * The exchange failed to even just initialize its connection to the
- * database. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_DB_SETUP_FAILED = 1001,
-
- /**
- * The exchange encountered an error event to just start the database
- * transaction. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_DB_START_FAILED = 1002,
-
- /**
- * The exchange encountered an error event to commit the database
- * transaction (hard, unrecoverable error). This response is provided
- * with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_DB_COMMIT_FAILED_HARD = 1003,
-
- /**
- * The exchange encountered an error event to commit the database
- * transaction, even after repeatedly retrying it there was always a
- * conflicting transaction. (This indicates a repeated serialization
- * error; should only happen if some client maliciously tries to
- * create conflicting concurrent transactions.) This response is
- * provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_DB_COMMIT_FAILED_ON_RETRY = 1004,
-
- /**
- * The exchange had insufficient memory to parse the request. This
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PARSER_OUT_OF_MEMORY = 1005,
-
- /**
- * The JSON in the client's request to the exchange was malformed.
- * (Generic parse error). This response is provided with HTTP status
- * code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_JSON_INVALID = 1006,
-
- /**
- * The JSON in the client's request to the exchange was malformed.
- * Details about the location of the parse error are provided. This
- * response is provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_JSON_INVALID_WITH_DETAILS = 1007,
-
- /**
- * A required parameter in the request to the exchange was missing.
- * This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_PARAMETER_MISSING = 1008,
-
- /**
- * A parameter in the request to the exchange was malformed. This
- * response is provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_PARAMETER_MALFORMED = 1009,
-
- /**
- * The exchange failed to obtain the transaction history of the given
- * coin from the database while generating an insufficient funds
- * errors. This can happen during /deposit or /recoup requests. This
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_COIN_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS = 1010,
-
- /**
- * Internal logic error. Some server-side function failed that really
- * should not. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_INTERNAL_LOGIC_ERROR = 1011,
-
- /**
- * The method specified in a payto:// URI is not one we expected.
- */
- TALER_EC_PAYTO_WRONG_METHOD = 1012,
-
- /**
- * The payto:// URI is malformed.
- */
- TALER_EC_PAYTO_MALFORMED = 1013,
-
- /**
- * We failed to update the database of known coins.
- */
- TALER_EC_DB_COIN_HISTORY_STORE_ERROR = 1014,
-
- /**
- * The public key of given to a /coins/ handler was malformed.
- */
- TALER_EC_COINS_INVALID_COIN_PUB = 1050,
-
- /**
- * The reserve key of given to a /reserves/ handler was malformed.
- */
- TALER_EC_RESERVES_INVALID_RESERVE_PUB = 1051,
-
- /**
- * The public key of given to a /transfers/ handler was malformed.
- */
- TALER_EC_TRANSFERS_INVALID_WTID = 1052,
-
- /**
- * The wire hash of given to a /deposits/ handler was malformed.
- */
- TALER_EC_DEPOSITS_INVALID_H_WIRE = 1053,
-
- /**
- * The merchant key of given to a /deposits/ handler was malformed.
- */
- TALER_EC_DEPOSITS_INVALID_MERCHANT_PUB = 1054,
-
- /**
- * The hash of the contract terms given to a /deposits/ handler was
- * malformed.
- */
- TALER_EC_DEPOSITS_INVALID_H_CONTRACT_TERMS = 1055,
-
- /**
- * The coin public key of given to a /deposits/ handler was malformed.
- */
- TALER_EC_DEPOSITS_INVALID_COIN_PUB = 1056,
-
- /**
- * The body returned by the exchange for a /deposits/ request was
- * malformed. Error created client-side.
- */
- TALER_EC_DEPOSITS_INVALID_BODY_BY_EXCHANGE = 1057,
-
- /**
- * The signature returned by the exchange in a /deposits/ request was
- * malformed. Error created client-side.
- */
- TALER_EC_DEPOSITS_INVALID_SIGNATURE_BY_EXCHANGE = 1058,
-
- /**
- * The given reserve does not have sufficient funds to admit the
- * requested withdraw operation at this time. The response includes
- * the current "balance" of the reserve as well as the transaction
- * "history" that lead to this balance. This response is provided
- * with HTTP status code #MHD_HTTP_CONFLICT.
- */
- TALER_EC_WITHDRAW_INSUFFICIENT_FUNDS = 1100,
-
- /**
- * The exchange has no information about the "reserve_pub" that was
- * given. This response is provided with HTTP status code
- * #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_WITHDRAW_RESERVE_UNKNOWN = 1101,
-
- /**
- * The amount to withdraw together with the fee exceeds the numeric
- * range for Taler amounts. This is not a client failure, as the coin
- * value and fees come from the exchange's configuration. This
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_WITHDRAW_AMOUNT_FEE_OVERFLOW = 1102,
-
- /**
- * All of the deposited amounts into this reserve total up to a value
- * that is too big for the numeric range for Taler amounts. This is
- * not a client failure, as the transaction history comes from the
- * exchange's configuration. This response is provided with HTTP
- * status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_AMOUNT_DEPOSITS_OVERFLOW = 1103,
-
- /**
- * For one of the historic withdrawals from this reserve, the exchange
- * could not find the denomination key. This is not a client failure,
- * as the transaction history comes from the exchange's configuration.
- * This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_WITHDRAW_HISTORIC_DENOMINATION_KEY_NOT_FOUND = 1104,
-
- /**
- * All of the withdrawals from reserve total up to a value that is too
- * big for the numeric range for Taler amounts. This is not a client
- * failure, as the transaction history comes from the exchange's
- * configuration. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_WITHDRAW_AMOUNT_WITHDRAWALS_OVERFLOW = 1105,
-
- /**
- * The exchange somehow knows about this reserve, but there seem to
- * have been no wire transfers made. This is not a client failure, as
- * this is a database consistency issue of the exchange. This
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_WITHDRAW_RESERVE_WITHOUT_WIRE_TRANSFER = 1106,
-
- /**
- * The exchange failed to create the signature using the denomination
- * key. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_WITHDRAW_SIGNATURE_FAILED = 1107,
-
- /**
- * The exchange failed to store the withdraw operation in its
- * database. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_WITHDRAW_DB_STORE_ERROR = 1108,
-
- /**
- * The exchange failed to check against historic withdraw data from
- * database (as part of ensuring the idempotency of the operation).
- * This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_WITHDRAW_DB_FETCH_ERROR = 1109,
-
- /**
- * The exchange is not aware of the denomination key the wallet
- * requested for the withdrawal. This response is provided with HTTP
- * status code #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_WITHDRAW_DENOMINATION_KEY_NOT_FOUND = 1110,
-
- /**
- * The signature of the reserve is not valid. This response is
- * provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_WITHDRAW_RESERVE_SIGNATURE_INVALID = 1111,
-
- /**
- * When computing the reserve history, we ended up with a negative
- * overall balance, which should be impossible. This response is
- * provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_WITHDRAW_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS = 1112,
-
- /**
- * When computing the reserve history, we ended up with a negative
- * overall balance, which should be impossible. This response is
- * provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_WITHDRAW_RESERVE_HISTORY_IMPOSSIBLE = 1113,
-
- /**
- * Validity period of the coin to be withdrawn is in the future.
- * Returned with an HTTP status of #MHD_HTTP_PRECONDITION_FAILED.
- */
- TALER_EC_WITHDRAW_VALIDITY_IN_FUTURE = 1114,
-
- /**
- * Withdraw period of the coin to be withdrawn is in the past.
- * Returned with an HTTP status of #MHD_HTTP_GONE.
- */
- TALER_EC_WITHDRAW_VALIDITY_IN_PAST = 1115,
-
- /**
- * Withdraw period of the coin to be withdrawn is in the past.
- * Returned with an HTTP status of #MHD_HTTP_GONE.
- */
- TALER_EC_DENOMINATION_KEY_LOST = 1116,
-
- /**
- * The exchange's database entry with the reserve balance summary is
- * inconsistent with its own history of the reserve. Returned with an
- * HTTP status of #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_WITHDRAW_RESERVE_BALANCE_CORRUPT = 1117,
-
- /**
- * The exchange responded with a reply that did not satsify the
- * protocol. This error is not used in the protocol but created
- * client-side.
- */
- TALER_EC_WITHDRAW_REPLY_MALFORMED = 1118,
-
- /**
- * The exchange failed to obtain the transaction history of the given
- * reserve from the database. This response is provided with HTTP
- * status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_RESERVE_STATUS_DB_ERROR = 1150,
-
- /**
- * The reserve status was requested using a unknown key, to be
- * returned with 404 Not Found.
- */
- TALER_EC_RESERVE_STATUS_UNKNOWN = 1151,
-
- /**
- * The exchange responded with a reply that did not satsify the
- * protocol. This error is not used in the protocol but created
- * client-side.
- */
- TALER_EC_RESERVE_STATUS_REPLY_MALFORMED = 1152,
-
- /**
- * The respective coin did not have sufficient residual value for the
- * /deposit operation (i.e. due to double spending). The "history" in
- * the respose provides the transaction history of the coin proving
- * this fact. This response is provided with HTTP status code
- * #MHD_HTTP_CONFLICT.
- */
- TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS = 1200,
-
- /**
- * The exchange failed to obtain the transaction history of the given
- * coin from the database (this does not happen merely because the
- * coin is seen by the exchange for the first time). This response is
- * provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_DEPOSIT_HISTORY_DB_ERROR = 1201,
-
- /**
- * The exchange failed to store the /depost information in the
- * database. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_DEPOSIT_STORE_DB_ERROR = 1202,
-
- /**
- * The exchange database is unaware of the denomination key that
- * signed the coin (however, the exchange process is; this is not
- * supposed to happen; it can happen if someone decides to purge the
- * DB behind the back of the exchange process). Hence the deposit is
- * being refused. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_DEPOSIT_DB_DENOMINATION_KEY_UNKNOWN = 1203,
-
- /**
- * The exchange was trying to lookup the denomination key for the
- * purpose of a DEPOSIT operation. However, the denomination key is
- * unavailable for that purpose. This can be because it is entirely
- * unknown to the exchange or not in the validity period for the
- * deposit operation. Hence the deposit is being refused. This
- * response is provided with HTTP status code #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_DEPOSIT_DENOMINATION_KEY_UNKNOWN = 1204,
-
- /**
- * The signature made by the coin over the deposit permission is not
- * valid. This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_DEPOSIT_COIN_SIGNATURE_INVALID = 1205,
-
- /**
- * The signature of the denomination key over the coin is not valid.
- * This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_DEPOSIT_DENOMINATION_SIGNATURE_INVALID = 1206,
-
- /**
- * The stated value of the coin after the deposit fee is subtracted
- * would be negative. This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE = 1207,
-
- /**
- * The stated refund deadline is after the wire deadline. This
- * response is provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE = 1208,
-
- /**
- * The exchange does not recognize the validity of or support the
- * given wire format type. This response is provided with HTTP status
- * code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_DEPOSIT_INVALID_WIRE_FORMAT_TYPE = 1209,
-
- /**
- * The exchange failed to canonicalize and hash the given wire format.
- * For example, the merchant failed to provide the "salt" or a valid
- * payto:// URI in the wire details. Note that while the exchange
- * will do some basic sanity checking on the wire details, it cannot
- * warrant that the banking system will ultimately be able to route to
- * the specified address, even if this check passed. This response is
- * provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_DEPOSIT_INVALID_WIRE_FORMAT_JSON = 1210,
-
- /**
- * The hash of the given wire address does not match the hash
- * specified in the proposal data. This response is provided with
- * HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_DEPOSIT_INVALID_WIRE_FORMAT_CONTRACT_HASH_CONFLICT = 1211,
-
- /**
- * The exchange detected that the given account number is invalid for
- * the selected wire format type. This response is provided with HTTP
- * status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_DEPOSIT_INVALID_WIRE_FORMAT_ACCOUNT_NUMBER = 1213,
-
- /**
- * Timestamp included in deposit permission is intolerably far off
- * with respect to the clock of the exchange.
- */
- TALER_EC_DEPOSIT_INVALID_TIMESTAMP = 1218,
-
- /**
- * Validity period of the denomination key is in the future. Returned
- * with an HTTP status of #MHD_HTTP_PRECONDITION_FAILED.
- */
- TALER_EC_DEPOSIT_DENOMINATION_VALIDITY_IN_FUTURE = 1219,
-
- /**
- * Denomination key of the coin is past the deposit deadline.
- * Returned with an HTTP status of #MHD_HTTP_GONE.
- */
- TALER_EC_DEPOSIT_DENOMINATION_EXPIRED = 1220,
-
- /**
- * The signature provided by the exchange is not valid. Error created
- * client-side.
- */
- TALER_EC_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE = 1221,
-
- /**
- * The currency specified for the deposit is different from the
- * currency of the coin. This response is provided with HTTP status
- * code #MHD_HTTP_PRECONDITION_FAILED.
- */
- TALER_EC_DEPOSIT_CURRENCY_MISMATCH = 1222,
-
- /**
- * The respective coin did not have sufficient residual value for the
- * /refresh/melt operation. The "history" in this response provdes
- * the "residual_value" of the coin, which may be less than its
- * "original_value". This response is provided with HTTP status code
- * #MHD_HTTP_CONFLICT.
- */
- TALER_EC_MELT_INSUFFICIENT_FUNDS = 1300,
-
- /**
- * The respective coin did not have sufficient residual value for the
- * /refresh/melt operation. The "history" in this response provdes
- * the "residual_value" of the coin, which may be less than its
- * "original_value". This response is provided with HTTP status code
- * #MHD_HTTP_CONFLICT.
- */
- TALER_EC_MELT_DENOMINATION_KEY_NOT_FOUND = 1301,
-
- /**
- * The exchange had an internal error reconstructing the transaction
- * history of the coin that was being melted. This response is
- * provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_MELT_COIN_HISTORY_COMPUTATION_FAILED = 1302,
-
- /**
- * The exchange failed to check against historic melt data from
- * database (as part of ensuring the idempotency of the operation).
- * This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_MELT_DB_FETCH_ERROR = 1303,
-
- /**
- * The exchange failed to store session data in the database. This
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_MELT_DB_STORE_SESSION_ERROR = 1304,
-
- /**
- * The exchange encountered melt fees exceeding the melted coin's
- * contribution. This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_MELT_FEES_EXCEED_CONTRIBUTION = 1305,
-
- /**
- * The denomination key signature on the melted coin is invalid. This
- * response is provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_MELT_DENOMINATION_SIGNATURE_INVALID = 1306,
-
- /**
- * The signature made with the coin to be melted is invalid. This
- * response is provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_MELT_COIN_SIGNATURE_INVALID = 1307,
-
- /**
- * The exchange failed to obtain the transaction history of the given
- * coin from the database while generating an insufficient funds
- * errors. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS = 1308,
-
- /**
- * The denomination of the given coin has past its expiration date and
- * it is also not a valid zombie (that is, was not refreshed with the
- * fresh coin being subjected to recoup).
- */
- TALER_EC_MELT_COIN_EXPIRED_NO_ZOMBIE = 1309,
-
- /**
- * The signature returned by the exchange in a melt request was
- * malformed. Error created client-side.
- */
- TALER_EC_MELT_INVALID_SIGNATURE_BY_EXCHANGE = 1310,
-
- /**
- * The currency specified for the melt amount is different from the
- * currency of the coin. This response is provided with HTTP status
- * code #MHD_HTTP_PRECONDITION_FAILED.
- */
- TALER_EC_MELT_CURRENCY_MISMATCH = 1311,
-
- /**
- * The exchange is unaware of the denomination key that was used to
- * sign the melted zombie coin. This response is provided with HTTP
- * status code #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_REFRESH_RECOUP_DENOMINATION_KEY_NOT_FOUND = 1351,
-
- /**
- * Validity period of the denomination key is in the future. Returned
- * with an HTTP status of #MHD_HTTP_PRECONDITION_FAILED.
- */
- TALER_EC_REFRESH_RECOUP_DENOMINATION_VALIDITY_IN_FUTURE = 1352,
-
- /**
- * Denomination key of the coin is past the deposit deadline.
- * Returned with an HTTP status of #MHD_HTTP_GONE.
- */
- TALER_EC_REFRESH_RECOUP_DENOMINATION_EXPIRED = 1353,
-
- /**
- * Denomination key of the coin is past the deposit deadline.
- * Returned with an HTTP status of #MHD_HTTP_GONE.
- */
- TALER_EC_REFRESH_ZOMBIE_DENOMINATION_EXPIRED = 1354,
-
- /**
- * The provided transfer keys do not match up with the original
- * commitment. Information about the original commitment is included
- * in the response. This response is provided with HTTP status code
- * #MHD_HTTP_CONFLICT.
- */
- TALER_EC_REVEAL_COMMITMENT_VIOLATION = 1370,
-
- /**
- * Failed to produce the blinded signatures over the coins to be
- * returned. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_REVEAL_SIGNING_ERROR = 1371,
-
- /**
- * The exchange is unaware of the refresh session specified in the
- * request. This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_REVEAL_SESSION_UNKNOWN = 1372,
-
- /**
- * The exchange failed to retrieve valid session data from the
- * database. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_REVEAL_DB_FETCH_SESSION_ERROR = 1373,
-
- /**
- * The exchange failed to retrieve previously revealed data from the
- * database. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_REVEAL_DB_FETCH_REVEAL_ERROR = 1374,
-
- /**
- * The exchange failed to retrieve commitment data from the database.
- * This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_REVEAL_DB_COMMIT_ERROR = 1375,
-
- /**
- * The size of the cut-and-choose dimension of the private transfer
- * keys request does not match #TALER_CNC_KAPPA - 1. This response is
- * provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID = 1376,
-
- /**
- * The number of coins to be created in refresh exceeds the limits of
- * the exchange. private transfer keys request does not match
- * #TALER_CNC_KAPPA - 1. This response is provided with HTTP status
- * code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_REVEAL_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE = 1377,
-
- /**
- * The number of envelopes given does not match the number of
- * denomination keys given. This response is provided with HTTP status
- * code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISMATCH = 1378,
-
- /**
- * The exchange encountered a numeric overflow totaling up the cost
- * for the refresh operation. This response is provided with HTTP
- * status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_REVEAL_COST_CALCULATION_OVERFLOW = 1379,
-
- /**
- * The exchange's cost calculation shows that the melt amount is below
- * the costs of the transaction. This response is provided with HTTP
- * status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_REVEAL_AMOUNT_INSUFFICIENT = 1380,
-
- /**
- * The exchange is unaware of the denomination key that was requested
- * for one of the fresh coins. This response is provided with HTTP
- * status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_REVEAL_FRESH_DENOMINATION_KEY_NOT_FOUND = 1381,
-
- /**
- * The signature made with the coin over the link data is invalid.
- * This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_REVEAL_LINK_SIGNATURE_INVALID = 1382,
-
- /**
- * The exchange failed to generate the signature as it could not find
- * the signing key for the denomination. This response is provided
- * with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_REVEAL_KEYS_MISSING = 1383,
-
- /**
- * The refresh session hash given to a /refreshes/ handler was
- * malformed.
- */
- TALER_EC_REVEAL_INVALID_RCH = 1384,
-
- /**
- * The exchange responded with a reply that did not satsify the
- * protocol. This error is not used in the protocol but created
- * client-side.
- */
- TALER_EC_REVEAL_REPLY_MALFORMED = 1385,
-
- /**
- * The coin specified in the link request is unknown to the exchange.
- * This response is provided with HTTP status code
- * #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_LINK_COIN_UNKNOWN = 1400,
-
- /**
- * The exchange responded with a reply that did not satsify the
- * protocol. This error is not used in the protocol but created
- * client-side.
- */
- TALER_EC_LINK_REPLY_MALFORMED = 1401,
-
- /**
- * The exchange knows literally nothing about the coin we were asked
- * to refund. But without a transaction history, we cannot issue a
- * refund. This is kind-of OK, the owner should just refresh it
- * directly without executing the refund. This response is provided
- * with HTTP status code #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_REFUND_COIN_NOT_FOUND = 1500,
-
- /**
- * We could not process the refund request as the coin's transaction
- * history does not permit the requested refund at this time. The
- * "history" in the response proves this. This response is provided
- * with HTTP status code #MHD_HTTP_CONFLICT.
- */
- TALER_EC_REFUND_CONFLICT = 1501,
-
- /**
- * The exchange knows about the coin we were asked to refund, but not
- * about the specific /deposit operation. Hence, we cannot issue a
- * refund (as we do not know if this merchant public key is authorized
- * to do a refund). This response is provided with HTTP status code
- * #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_REFUND_DEPOSIT_NOT_FOUND = 1503,
-
- /**
- * The currency specified for the refund is different from the
- * currency of the coin. This response is provided with HTTP status
- * code #MHD_HTTP_PRECONDITION_FAILED.
- */
- TALER_EC_REFUND_CURRENCY_MISMATCH = 1504,
-
- /**
- * When we tried to check if we already paid out the coin, the
- * exchange's database suddenly disagreed with data it previously
- * provided (internal inconsistency). This response is provided with
- * HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_REFUND_DB_INCONSISTENT = 1505,
-
- /**
- * The exchange can no longer refund the customer/coin as the money
- * was already transferred (paid out) to the merchant. (It should be
- * past the refund deadline.) This response is provided with HTTP
- * status code #MHD_HTTP_GONE.
- */
- TALER_EC_REFUND_MERCHANT_ALREADY_PAID = 1506,
-
- /**
- * The amount the exchange was asked to refund exceeds (with fees) the
- * total amount of the deposit (including fees). This response is
- * provided with HTTP status code #MHD_HTTP_PRECONDITION_FAILED.
- */
- TALER_EC_REFUND_INSUFFICIENT_FUNDS = 1507,
-
- /**
- * The exchange failed to recover information about the denomination
- * key of the refunded coin (even though it recognizes the key).
- * Hence it could not check the fee strucutre. This response is
- * provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_REFUND_DENOMINATION_KEY_NOT_FOUND = 1508,
-
- /**
- * The refund fee specified for the request is lower than the refund
- * fee charged by the exchange for the given denomination key of the
- * refunded coin. This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_REFUND_FEE_TOO_LOW = 1509,
-
- /**
- * The exchange failed to store the refund information to its
- * database. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_REFUND_STORE_DB_ERROR = 1510,
-
- /**
- * The refund fee is specified in a different currency than the refund
- * amount. This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_REFUND_FEE_CURRENCY_MISMATCH = 1511,
-
- /**
- * The refunded amount is smaller than the refund fee, which would
- * result in a negative refund. This response is provided with HTTP
- * status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_REFUND_FEE_ABOVE_AMOUNT = 1512,
-
- /**
- * The signature of the merchant is invalid. This response is provided
- * with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_REFUND_MERCHANT_SIGNATURE_INVALID = 1513,
-
- /**
- * Merchant backend failed to create the refund confirmation
- * signature. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_REFUND_MERCHANT_SIGNING_FAILED = 1514,
-
- /**
- * The signature returned by the exchange in a refund request was
- * malformed. Error created client-side.
- */
- TALER_EC_REFUND_INVALID_SIGNATURE_BY_EXCHANGE = 1515,
-
- /**
- * The wire format specified in the "sender_account_details" is not
- * understood or not supported by this exchange. Returned with an HTTP
- * status code of #MHD_HTTP_NOT_FOUND. (As we did not find an
- * interpretation of the wire format.)
- */
- TALER_EC_ADMIN_ADD_INCOMING_WIREFORMAT_UNSUPPORTED = 1600,
-
- /**
- * The currency specified in the "amount" parameter is not supported
- * by this exhange. Returned with an HTTP status code of
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_ADMIN_ADD_INCOMING_CURRENCY_UNSUPPORTED = 1601,
-
- /**
- * The exchange failed to store information about the incoming
- * transfer in its database. This response is provided with HTTP
- * status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_ADMIN_ADD_INCOMING_DB_STORE = 1602,
-
- /**
- * The exchange encountered an error (that is not about not finding
- * the wire transfer) trying to lookup a wire transfer identifier in
- * the database. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TRANSFERS_GET_DB_FETCH_FAILED = 1700,
-
- /**
- * The exchange found internally inconsistent data when resolving a
- * wire transfer identifier in the database. This response is
- * provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TRANSFERS_GET_DB_INCONSISTENT = 1701,
-
- /**
- * The exchange did not find information about the specified wire
- * transfer identifier in the database. This response is provided
- * with HTTP status code #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_TRANSFERS_GET_WTID_NOT_FOUND = 1702,
-
- /**
- * The exchange did not find information about the wire transfer fees
- * it charged. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TRANSFERS_GET_WIRE_FEE_NOT_FOUND = 1703,
-
- /**
- * The exchange found a wire fee that was above the total transfer
- * value (and thus could not have been charged). This response is
- * provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TRANSFERS_GET_WIRE_FEE_INCONSISTENT = 1704,
-
- /**
- * The exchange responded with a reply that did not satsify the
- * protocol. This error is not used in the protocol but created
- * client-side.
- */
- TALER_EC_TRANSFERS_GET_REPLY_MALFORMED = 1705,
-
- /**
- * The exchange found internally inconsistent fee data when resolving
- * a transaction in the database. This response is provided with HTTP
- * status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_DEPOSITS_GET_DB_FEE_INCONSISTENT = 1800,
-
- /**
- * The exchange encountered an error (that is not about not finding
- * the transaction) trying to lookup a transaction in the database.
- * This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_DEPOSITS_GET_DB_FETCH_FAILED = 1801,
-
- /**
- * The exchange did not find information about the specified
- * transaction in the database. This response is provided with HTTP
- * status code #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_DEPOSITS_GET_NOT_FOUND = 1802,
-
- /**
- * The exchange failed to identify the wire transfer of the
- * transaction (or information about the plan that it was supposed to
- * still happen in the future). This response is provided with HTTP
- * status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_DEPOSITS_GET_WTID_RESOLUTION_ERROR = 1803,
-
- /**
- * The signature of the merchant is invalid. This response is provided
- * with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID = 1804,
-
- /**
- * The given denomination key is not in the "recoup" set of the
- * exchange right now. This response is provided with an HTTP status
- * code of #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_RECOUP_DENOMINATION_KEY_UNKNOWN = 1850,
-
- /**
- * The given coin signature is invalid for the request. This response
- * is provided with an HTTP status code of #MHD_HTTP_FORBIDDEN.
- */
- TALER_EC_RECOUP_SIGNATURE_INVALID = 1851,
-
- /**
- * The signature of the denomination key over the coin is not valid.
- * This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_RECOUP_DENOMINATION_SIGNATURE_INVALID = 1852,
-
- /**
- * The exchange failed to access its own database about reserves. This
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_RECOUP_DB_FETCH_FAILED = 1853,
-
- /**
- * The exchange could not find the corresponding withdraw operation.
- * The request is denied. This response is provided with an HTTP
- * status code of #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_RECOUP_WITHDRAW_NOT_FOUND = 1854,
-
- /**
- * The exchange obtained an internally inconsistent transaction
- * history for the given coin. This response is provided with HTTP
- * status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_RECOUP_HISTORY_DB_ERROR = 1855,
-
- /**
- * The exchange failed to store information about the recoup to be
- * performed in the database. This response is provided with HTTP
- * status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_RECOUP_DB_PUT_FAILED = 1856,
-
- /**
- * The coin's remaining balance is zero. The request is denied. This
- * response is provided with an HTTP status code of
- * #MHD_HTTP_FORBIDDEN.
- */
- TALER_EC_RECOUP_COIN_BALANCE_ZERO = 1857,
-
- /**
- * The exchange failed to reproduce the coin's blinding. This response
- * is provided with an HTTP status code of
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_RECOUP_BLINDING_FAILED = 1858,
-
- /**
- * The coin's remaining balance is zero. The request is denied. This
- * response is provided with an HTTP status code of
- * #MHD_HTTP_INTERNAL_SERVER_ERROR
- */
- TALER_EC_RECOUP_COIN_BALANCE_NEGATIVE = 1859,
-
- /**
- * Validity period of the denomination key is in the future. Returned
- * with an HTTP status of #MHD_HTTP_PRECONDITION_FAILED.
- */
- TALER_EC_RECOUP_DENOMINATION_VALIDITY_IN_FUTURE = 1860,
-
- /**
- * The exchange responded with a reply that did not satsify the
- * protocol. This error is not used in the protocol but created
- * client-side.
- */
- TALER_EC_RECOUP_REPLY_MALFORMED = 1861,
-
- /**
- * The "have" parameter was not a natural number. This response is
- * provied with an HTTP status code of #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_KEYS_HAVE_NOT_NUMERIC = 1900,
-
- /**
- * We currently cannot find any keys. This response is provied with an
- * HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_KEYS_MISSING = 1901,
-
- /**
- * This exchange does not allow clients to request /keys for times
- * other than the current (exchange) time. This response is provied
- * with an HTTP status code of #MHD_HTTP_FORBIDDEN.
- */
- TALER_EC_KEYS_TIMETRAVEL_FORBIDDEN = 1902,
-
- /**
- * The backend could not find the merchant instance specified in the
- * request. This response is provided with HTTP status code
- * #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_INSTANCE_UNKNOWN = 2000,
-
- /**
- * The backend lacks a wire transfer method configuration option for
- * the given instance.
- */
- TALER_EC_PROPOSAL_INSTANCE_CONFIGURATION_LACKS_WIRE = 2002,
-
- /**
- * The exchange failed to provide a meaningful response to a /deposit
- * request. This response is provided with HTTP status code
- * #MHD_HTTP_FAILED_DEPENDENCY, or #MHD_HTTP_CONFLICT in case the
- * exchange reports #TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS (aka double
- * spending).
- */
- TALER_EC_PAY_EXCHANGE_FAILED = 2101,
-
- /**
- * The merchant failed to commit the exchanges' response to a /deposit
- * request to its database. This response is provided with HTTP
- * status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PAY_DB_STORE_PAY_ERROR = 2102,
-
- /**
- * The specified exchange is not supported/trusted by this merchant.
- * This response is provided with HTTP status code
- * #MHD_HTTP_PRECONDITION_FAILED.
- */
- TALER_EC_PAY_EXCHANGE_REJECTED = 2103,
-
- /**
- * The denomination key used for payment is not listed among the
- * denomination keys of the exchange. This response is provided with
- * HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_PAY_DENOMINATION_KEY_NOT_FOUND = 2104,
-
- /**
- * The denomination key used for payment is not audited by an auditor
- * approved by the merchant. This response is provided with HTTP
- * status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_PAY_DENOMINATION_KEY_AUDITOR_FAILURE = 2105,
-
- /**
- * There was an integer overflow totaling up the amounts or deposit
- * fees in the payment. This response is provided with HTTP status
- * code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PAY_AMOUNT_OVERFLOW = 2106,
-
- /**
- * The deposit fees exceed the total value of the payment. This
- * response is provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_PAY_FEES_EXCEED_PAYMENT = 2107,
-
- /**
- * After considering deposit and wire fees, the payment is
- * insufficient to satisfy the required amount for the contract. The
- * client should revisit the logic used to calculate fees it must
- * cover. This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES = 2108,
-
- /**
- * Even if we do not consider deposit and wire fees, the payment is
- * insufficient to satisfy the required amount for the contract. This
- * response is provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_PAY_PAYMENT_INSUFFICIENT = 2109,
-
- /**
- * The signature over the contract of one of the coins was invalid.
- * This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_PAY_COIN_SIGNATURE_INVALID = 2110,
-
- /**
- * We failed to contact the exchange for the /pay request. This
- * response is provided with HTTP status code
- * #MHD_HTTP_REQUEST_TIMEOUT.
- */
- TALER_EC_PAY_EXCHANGE_TIMEOUT = 2111,
-
- /**
- * When we tried to find information about the exchange to issue the
- * deposit, we failed. This usually only happens if the merchant
- * backend is somehow unable to get its own HTTP client logic to work.
- * This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PAY_EXCHANGE_LOOKUP_FAILED = 2112,
-
- /**
- * The refund deadline in the contract is after the transfer deadline.
- * This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR as this should have been caught
- * when the offer was first setup.
- */
- TALER_EC_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE = 2114,
-
- /**
- * The request fails to provide coins for the payment. This response
- * is provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_PAY_COINS_ARRAY_EMPTY = 2115,
-
- /**
- * The merchant failed to fetch the contract terms from the merchant's
- * database. This response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PAY_DB_FETCH_PAY_ERROR = 2116,
-
- /**
- * The merchant failed to fetch the merchant's previous state with
- * respect to transactions from its database. This response is
- * provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR = 2117,
-
- /**
- * The merchant failed to store the merchant's state with respect to
- * the transaction in its database. This response is provided with
- * HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR = 2119,
-
- /**
- * The exchange failed to provide a valid response to the merchant's
- * /keys request. This response is provided with HTTP status code
- * #MHD_HTTP_FAILED_DEPENDENDCY.
- */
- TALER_EC_PAY_EXCHANGE_KEYS_FAILURE = 2120,
-
- /**
- * The payment is too late, the offer has expired. This response is
- * provided with HTTP status code #MHD_HTTP_GONE.
- */
- TALER_EC_PAY_OFFER_EXPIRED = 2121,
-
- /**
- * The "merchant" field is missing in the proposal data. This response
- * is provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_PAY_MERCHANT_FIELD_MISSING = 2122,
-
- /**
- * Failed computing a hash code (likely server out-of-memory). This
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PAY_FAILED_COMPUTE_PROPOSAL_HASH = 2123,
-
- /**
- * Failed to locate merchant's account information matching the wire
- * hash given in the proposal. This response is provided with HTTP
- * status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PAY_WIRE_HASH_UNKNOWN = 2124,
-
- /**
- * We got different currencies for the wire fee and the maximum wire
- * fee. This response is provided with HTTP status code of
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PAY_WIRE_FEE_CURRENCY_MISMATCH = 2125,
-
- /**
- * The merchant refuses to abort and refund the payment operation as
- * the payment succeeded already. This response is provided with HTTP
- * status code of #MHD_HTTP_FORBIDDEN.
- */
- TALER_EC_PAY_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE = 2126,
-
- /**
- * A unknown merchant public key was included in the payment. That
- * happens typically when the wallet sends the payment to the wrong
- * merchant instance. This response is provided with an HTTP status
- * code of #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_PAY_WRONG_INSTANCE = 2127,
-
- /**
- * The exchange failed to give us a response when we asked for /keys.
- * This response is provided with HTTP status code
- * #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_PAY_EXCHANGE_HAS_NO_KEYS = 2128,
-
- /**
- * The deposit time for the denomination has expired. This response is
- * provided with HTTP status code #MHD_HTTP_GONE.
- */
- TALER_EC_PAY_DENOMINATION_DEPOSIT_EXPIRED = 2129,
-
- /**
- * The proposal is not known to the backend. This response is provided
- * with an HTTP status code of #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_PAY_PROPOSAL_NOT_FOUND = 2130,
-
- /**
- * The exchange of the deposited coin charges a wire fee that could
- * not be added to the total (total amount too high). This response
- * is provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED = 2131,
-
- /**
- * The contract was not fully paid because of refunds. Note that
- * clients MAY treat this as paid if, for example, contracts must be
- * executed despite of refunds. This response is provided with HTTP
- * status code #MHD_HTTP_PAYMENT_REQUIRED.
- */
- TALER_EC_PAY_REFUNDED = 2132,
-
- /**
- * According to our database, we have refunded more than we were paid
- * (which should not be possible). This response is provided with HTTP
- * status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PAY_REFUNDS_EXCEED_PAYMENTS = 2133,
-
- /**
- * Integer overflow with specified timestamp argument detected. This
- * response is provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_HISTORY_TIMESTAMP_OVERFLOW = 2200,
-
- /**
- * Failed to retrieve history from merchant database. This response is
- * provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_HISTORY_DB_FETCH_ERROR = 2201,
-
- /**
- * The backend could not find the contract specified in the request.
- * This response is provided with HTTP status code
- * #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_POLL_PAYMENT_CONTRACT_NOT_FOUND = 2250,
-
- /**
- * We failed to contact the exchange for the /track/transaction
- * request. This response is provided with HTTP status code
- * #MHD_HTTP_SERVICE_UNAVAILABLE.
- */
- TALER_EC_TRACK_TRANSACTION_EXCHANGE_TIMEOUT = 2300,
-
- /**
- * We failed to get a valid /keys response from the exchange for the
- * /track/transaction request. This response is provided with HTTP
- * status code #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TRACK_TRANSACTION_EXCHANGE_KEYS_FAILURE = 2301,
-
- /**
- * The backend could not find the transaction specified in the
- * request. This response is provided with HTTP status code
- * #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_TRACK_TRANSACTION_TRANSACTION_UNKNOWN = 2302,
-
- /**
- * The backend had a database access error trying to retrieve
- * transaction data from its database. The response is provided with
- * HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TRACK_TRANSACTION_DB_FETCH_TRANSACTION_ERROR = 2303,
-
- /**
- * The backend had a database access error trying to retrieve payment
- * data from its database. The response is provided with HTTP status
- * code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TRACK_TRANSACTION_DB_FETCH_PAYMENT_ERROR = 2304,
-
- /**
- * The backend found no applicable deposits in the database. This is
- * odd, as we know about the transaction, but not about deposits we
- * made for the transaction. The response is provided with HTTP
- * status code #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_TRACK_TRANSACTION_DB_NO_DEPOSITS_ERROR = 2305,
-
- /**
- * We failed to obtain a wire transfer identifier for one of the coins
- * in the transaction. The response is provided with HTTP status code
- * #MHD_HTTP_FAILED_DEPENDENCY if the exchange had a hard error, or
- * #MHD_HTTP_ACCEPTED if the exchange signaled that the transfer was
- * in progress.
- */
- TALER_EC_TRACK_TRANSACTION_COIN_TRACE_ERROR = 2306,
-
- /**
- * We failed to obtain the full wire transfer identifier for the
- * transfer one of the coins was aggregated into. The response is
- * provided with HTTP status code #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TRACK_TRANSACTION_WIRE_TRANSFER_TRACE_ERROR = 2307,
-
- /**
- * We got conflicting reports from the exhange with respect to which
- * transfers are included in which aggregate. The response is provided
- * with HTTP status code #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TRACK_TRANSACTION_CONFLICTING_REPORTS = 2308,
-
- /**
- * We failed to contact the exchange for the /track/transfer request.
- * This response is provided with HTTP status code
- * #MHD_HTTP_SERVICE_UNAVAILABLE.
- */
- TALER_EC_TRACK_TRANSFER_EXCHANGE_TIMEOUT = 2400,
-
- /**
- * We failed to obtain an acceptable /keys response from the exchange
- * for the /track/transfer request. This response is provided with
- * HTTP status code #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TRACK_TRANSFER_EXCHANGE_KEYS_FAILURE = 2401,
-
- /**
- * We failed to persist coin wire transfer information in our merchant
- * database. The response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TRACK_TRANSFER_DB_STORE_COIN_ERROR = 2402,
-
- /**
- * We internally failed to execute the /track/transfer request. The
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TRACK_TRANSFER_REQUEST_ERROR = 2403,
-
- /**
- * We failed to persist wire transfer information in our merchant
- * database. The response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TRACK_TRANSFER_DB_STORE_TRANSFER_ERROR = 2404,
-
- /**
- * The exchange returned an error from /track/transfer. The response
- * is provided with HTTP status code #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TRACK_TRANSFER_EXCHANGE_ERROR = 2405,
-
- /**
- * We failed to fetch deposit information from our merchant database.
- * The response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TRACK_TRANSFER_DB_FETCH_DEPOSIT_ERROR = 2406,
-
- /**
- * We encountered an internal logic error. The response is provided
- * with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TRACK_TRANSFER_DB_INTERNAL_LOGIC_ERROR = 2407,
-
- /**
- * The exchange gave conflicting information about a coin which has
- * been wire transferred. The response is provided with HTTP status
- * code #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TRACK_TRANSFER_CONFLICTING_REPORTS = 2408,
-
- /**
- * The merchant backend had problems in creating the JSON response.
- */
- TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR = 2409,
-
- /**
- * The exchange charged a different wire fee than what it originally
- * advertised, and it is higher. The response is provied with an HTTP
- * status of #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TRACK_TRANSFER_JSON_BAD_WIRE_FEE = 2410,
-
- /**
- * The hash provided in the request of /map/in does not match the
- * contract sent alongside in the same request.
- */
- TALER_EC_MAP_IN_UNMATCHED_HASH = 2500,
-
- /**
- * The backend encountered an error while trying to store the
- * h_contract_terms into the database. The response is provided with
- * HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PROPOSAL_STORE_DB_ERROR = 2501,
-
- /**
- * The backend encountered an error while trying to retrieve the
- * proposal data from database. Likely to be an internal error.
- */
- TALER_EC_PROPOSAL_LOOKUP_DB_ERROR = 2502,
-
- /**
- * The proposal being looked up is not found on this merchant.
- */
- TALER_EC_PROPOSAL_LOOKUP_NOT_FOUND = 2503,
-
- /**
- * The proposal had no timestamp and the backend failed to obtain the
- * local time. Likely to be an internal error.
- */
- TALER_EC_PROPOSAL_NO_LOCALTIME = 2504,
-
- /**
- * The order provided to the backend could not be parsed, some
- * required fields were missing or ill-formed. Returned as a bad
- * request.
- */
- TALER_EC_PROPOSAL_ORDER_PARSE_ERROR = 2505,
-
- /**
- * The backend encountered an error while trying to find the existing
- * proposal in the database. The response is provided with HTTP status
- * code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PROPOSAL_STORE_DB_ERROR_HARD = 2506,
-
- /**
- * The backend encountered an error while trying to find the existing
- * proposal in the database. The response is provided with HTTP status
- * code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PROPOSAL_STORE_DB_ERROR_SOFT = 2507,
-
- /**
- * The backend encountered an error: the proposal already exists. The
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_PROPOSAL_STORE_DB_ERROR_ALREADY_EXISTS = 2508,
-
- /**
- * The order provided to the backend uses an amount in a currency that
- * does not match the backend's configuration. Returned as a bad
- * request.
- */
- TALER_EC_PROPOSAL_ORDER_BAD_CURRENCY = 2509,
-
- /**
- * The frontend gave an unknown order id to issue the refund to.
- */
- TALER_EC_REFUND_ORDER_ID_UNKNOWN = 2601,
-
- /**
- * The amount to be refunded is inconsistent: either is lower than the
- * previous amount being awarded, or it is too big to be paid back. In
- * this second case, the fault stays on the business dept. side.
- * Returned with an HTTP status of #MHD_HTTP_CONFLICT.
- */
- TALER_EC_REFUND_INCONSISTENT_AMOUNT = 2602,
-
- /**
- * The backend encountered an error while trying to retrieve the
- * payment data from database. Likely to be an internal error.
- */
- TALER_EC_REFUND_LOOKUP_DB_ERROR = 2603,
-
- /**
- * The backend encountered an error while trying to retrieve the
- * payment data from database. Likely to be an internal error.
- */
- TALER_EC_REFUND_MERCHANT_DB_COMMIT_ERROR = 2604,
-
- /**
- * Payments are stored in a single db transaction; this error
- * indicates that one db operation within that transaction failed.
- * This might involve storing of coins or other related db operations,
- * like starting/committing the db transaction or marking a contract
- * as paid.
- */
- TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR = 2605,
-
- /**
- * The backend failed to sign the refund request.
- */
- TALER_EC_PAY_REFUND_SIGNATURE_FAILED = 2606,
-
- /**
- * The backend knows the instance that was supposed to support the
- * tip, but it was not configured for tipping (i.e. has no exchange
- * associated with it). Likely to be a configuration error. Returned
- * with an HTTP status code of #MHD_HTTP_PRECONDITION_FAILED.
- */
- TALER_EC_TIP_AUTHORIZE_INSTANCE_DOES_NOT_TIP = 2701,
-
- /**
- * The reserve that was used to fund the tips has expired. Returned
- * with an HTTP status code of #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED = 2702,
-
- /**
- * The reserve that was used to fund the tips was not found in the DB.
- * Returned with an HTTP status code of #MHD_HTTP_SERVICE_UNAVAILABLE.
- */
- TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN = 2703,
-
- /**
- * The backend knows the instance that was supposed to support the
- * tip, and it was configured for tipping. However, the funds
- * remaining are insufficient to cover the tip, and the merchant
- * should top up the reserve. Returned with an HTTP status code of
- * #MHD_HTTP_PRECONDITION FAILED.
- */
- TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS = 2704,
-
- /**
- * The backend had trouble accessing the database to persist
- * information about the tip authorization. Returned with an HTTP
- * status code of internal error.
- */
- TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR = 2705,
-
- /**
- * The backend had trouble accessing the database to persist
- * information about the tip authorization. The problem might be
- * fixable by repeating the transaction.
- */
- TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR = 2706,
-
- /**
- * The backend failed to obtain a reserve status from the exchange.
- * This response is provided with HTTP status code
- * #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TIP_QUERY_RESERVE_STATUS_FAILED_EXCHANGE_DOWN = 2707,
-
- /**
- * The backend got an empty (!) reserve history from the exchange.
- * This response is provided with HTTP status code
- * #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TIP_QUERY_RESERVE_HISTORY_FAILED_EMPTY = 2708,
-
- /**
- * The backend got an invalid reserve history (fails to start with a
- * deposit) from the exchange. This response is provided with HTTP
- * status code #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TIP_QUERY_RESERVE_HISTORY_INVALID_NO_DEPOSIT = 2709,
-
- /**
- * The backend got an 404 response from the exchange when it inquired
- * about the reserve history. The response is provided with HTTP
- * status code #MHD_HTTP_SERVICE_UNAVAILABLE.
- */
- TALER_EC_TIP_QUERY_RESERVE_UNKNOWN_TO_EXCHANGE = 2710,
-
- /**
- * The backend got a reserve with a currency that does not match the
- * backend's currency. The response is provided with HTTP status code
- * #MHD_HTTP_SERVICE_UNAVAILABLE.
- */
- TALER_EC_TIP_QUERY_RESERVE_CURRENCY_MISMATCH = 2711,
-
- /**
- * The backend got a reserve history with amounts it cannot process
- * (addition failure in deposits). The response is provided with HTTP
- * status code #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TIP_QUERY_RESERVE_HISTORY_ARITHMETIC_ISSUE_DEPOSIT = 2712,
-
- /**
- * The backend got a reserve history with amounts it cannot process
- * (addition failure in withdraw amounts). The response is provided
- * with HTTP status code #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TIP_QUERY_RESERVE_HISTORY_ARITHMETIC_ISSUE_WITHDRAW = 2713,
-
- /**
- * The backend got a reserve history with amounts it cannot process
- * (addition failure in closing amounts). The response is provided
- * with HTTP status code #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TIP_QUERY_RESERVE_HISTORY_ARITHMETIC_ISSUE_CLOSED = 2714,
-
- /**
- * The backend got a reserve history with inconsistent amounts.
- */
- TALER_EC_TIP_QUERY_RESERVE_HISTORY_ARITHMETIC_ISSUE_INCONSISTENT = 2715,
-
- /**
- * The backend encountered a database error querying tipping reserves.
- */
- TALER_EC_TIP_QUERY_DB_ERROR = 2716,
-
- /**
- * The backend got an unexpected resever history reply from the
- * exchange. This response is provided with HTTP status code
- * #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TIP_QUERY_RESERVE_HISTORY_FAILED = 2717,
-
- /**
- * The backend got a reserve history with amounts it cannot process
- * (addition failure in withdraw amounts). The response is provided
- * with HTTP status code #MHD_HTTP_FAILED_DEPENDENCY.
- */
- TALER_EC_TIP_QUERY_RESERVE_HISTORY_ARITHMETIC_ISSUE_RECOUP = 2718,
-
- /**
- * The backend knows the instance that was supposed to support the
- * tip, but it was not configured for tipping (i.e. has no exchange
- * associated with it). Likely to be a configuration error. Returned
- * with an HTTP status code of #MHD_HTTP_PRECONDITION_FAILED.
- */
- TALER_EC_TIP_QUERY_INSTANCE_DOES_NOT_TIP = 2719,
-
- /**
- * The backend had trouble accessing the database to persist
- * information about enabling tips. Returned with an HTTP status code
- * of internal error.
- */
- TALER_EC_TIP_ENABLE_DB_TRANSACTION_ERROR = 2750,
-
- /**
- * The tip ID is unknown. This could happen if the tip has expired.
- * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN = 2800,
-
- /**
- * The amount requested exceeds the remaining tipping balance for this
- * tip ID. Returned with an HTTP status code of "Conflict" (as it
- * conflicts with a previous pickup operation).
- */
- TALER_EC_TIP_PICKUP_NO_FUNDS = 2801,
-
- /**
- * We encountered a DB error, repeating the request may work.
- */
- TALER_EC_TIP_PICKUP_DB_ERROR_SOFT = 2802,
-
- /**
- * We encountered a DB error, repeating the request will not help.
- * This is an internal server error.
- */
- TALER_EC_TIP_PICKUP_DB_ERROR_HARD = 2803,
-
- /**
- * The same pickup ID was already used for picking up a different
- * amount. This points to a very strange internal error as the pickup
- * ID is derived from the denomination key which is tied to a
- * particular amount. Hence this should also be an internal server
- * error.
- */
- TALER_EC_TIP_PICKUP_AMOUNT_CHANGED = 2804,
-
- /**
- * We failed to contact the exchange to obtain the denomination keys.
- * Returned with a response code "failed dependency" (424).
- */
- TALER_EC_TIP_PICKUP_EXCHANGE_DOWN = 2805,
-
- /**
- * We contacted the exchange to obtain any denomination keys, but got
- * no valid keys. Returned with a response code "failed dependency"
- * (424).
- */
- TALER_EC_TIP_PICKUP_EXCHANGE_LACKED_KEYS = 2806,
-
- /**
- * We contacted the exchange to obtain at least one of the
- * denomination keys specified in the request. Returned with a
- * response code "not found" (404).
- */
- TALER_EC_TIP_PICKUP_EXCHANGE_LACKED_KEY = 2807,
-
- /**
- * We encountered an arithmetic issue totaling up the amount to
- * withdraw. Returned with a response code of "bad request".
- */
- TALER_EC_TIP_PICKUP_EXCHANGE_AMOUNT_OVERFLOW = 2808,
-
- /**
- * The number of planchets specified exceeded the limit. Returned with
- * a response code of "bad request".
- */
- TALER_EC_TIP_PICKUP_EXCHANGE_TOO_MANY_PLANCHETS = 2809,
-
- /**
- * The tip id is unknown. This could happen if the tip id is wrong or
- * the tip authorization expired.
- */
- TALER_EC_TIP_QUERY_TIP_ID_UNKNOWN = 2810,
-
- /**
- * We failed to contract terms from our merchant database. The
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_CHECK_PAYMENT_DB_FETCH_CONTRACT_TERMS_ERROR = 2911,
-
- /**
- * We failed to contract terms from our merchant database. The
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR = 2912,
-
- /**
- * The order id we're checking is unknown, likely the frontend did not
- * create the order first.
- */
- TALER_EC_CHECK_PAYMENT_ORDER_ID_UNKNOWN = 2913,
-
- /**
- * Failed computing a hash code (likely server out-of-memory). This
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_CHECK_PAYMENT_FAILED_COMPUTE_PROPOSAL_HASH = 2914,
-
- /**
- * Signature "session_sig" failed to verify. This response is provided
- * with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_CHECK_PAYMENT_SESSION_SIGNATURE_INVALID = 2915,
-
- /**
- * The signature from the exchange on the deposit confirmation is
- * invalid. Returned with a "400 Bad Request" status code.
- */
- TALER_EC_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID = 3000,
-
- /**
- * The auditor had trouble storing the deposit confirmation in its
- * database. Returned with an HTTP status code of
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_DEPOSIT_CONFIRMATION_STORE_DB_ERROR = 3001,
-
- /**
- * The auditor had trouble retrieving the exchange list from its
- * database. Returned with an HTTP status code of
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_LIST_EXCHANGES_DB_ERROR = 3002,
-
- /**
- * The auditor had trouble storing an exchange in its database.
- * Returned with an HTTP status code of
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_AUDITOR_EXCHANGE_STORE_DB_ERROR = 3003,
-
- /**
- * The auditor (!) responded with a reply that did not satsify the
- * protocol. This error is not used in the protocol but created
- * client-side.
- */
- TALER_EC_AUDITOR_EXCHANGES_REPLY_MALFORMED = 3004,
-
- /**
- * The exchange failed to compute ECDH. This response is provided
- * with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TEST_ECDH_ERROR = 4000,
-
- /**
- * The EdDSA test signature is invalid. This response is provided
- * with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_TEST_EDDSA_INVALID = 4001,
-
- /**
- * The exchange failed to compute the EdDSA test signature. This
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TEST_EDDSA_ERROR = 4002,
-
- /**
- * The exchange failed to generate an RSA key. This response is
- * provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TEST_RSA_GEN_ERROR = 4003,
-
- /**
- * The exchange failed to compute the public RSA key. This response
- * is provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TEST_RSA_PUB_ERROR = 4004,
-
- /**
- * The exchange failed to compute the RSA signature. This response is
- * provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_TEST_RSA_SIGN_ERROR = 4005,
-
- /**
- * The JSON in the server's response was malformed. This response is
- * provided with HTTP status code of 0.
- */
- TALER_EC_SERVER_JSON_INVALID = 5000,
-
- /**
- * A signature in the server's response was malformed. This response
- * is provided with HTTP status code of 0.
- */
- TALER_EC_SERVER_SIGNATURE_INVALID = 5001,
-
- /**
- * Wire transfer attempted with credit and debit party being the same
- * bank account.
- */
- TALER_EC_BANK_SAME_ACCOUNT = 5102,
-
- /**
- * Wire transfer impossible, due to financial limitation of the party
- * that attempted the payment.
- */
- TALER_EC_BANK_UNALLOWED_DEBIT = 5103,
-
- /**
- * Arithmetic operation between two amounts of different currency was
- * attempted.
- */
- TALER_EC_BANK_CURRENCY_MISMATCH = 5104,
-
- /**
- * At least one GET parameter was either missing or invalid for the
- * requested operation.
- */
- TALER_EC_BANK_PARAMETER_MISSING_OR_INVALID = 5105,
-
- /**
- * JSON body sent was invalid for the requested operation.
- */
- TALER_EC_BANK_JSON_INVALID = 5106,
-
- /**
- * Negative number was used (as value and/or fraction) to initiate a
- * Amount object.
- */
- TALER_EC_BANK_NEGATIVE_NUMBER_AMOUNT = 5107,
-
- /**
- * A number too big was used (as value and/or fraction) to initiate a
- * amount object.
- */
- TALER_EC_BANK_NUMBER_TOO_BIG = 5108,
-
- /**
- * Could not login for the requested operation.
- */
- TALER_EC_BANK_LOGIN_FAILED = 5109,
-
- /**
- * The bank account referenced in the requested operation was not
- * found. Returned along "400 Not found".
- */
- TALER_EC_BANK_UNKNOWN_ACCOUNT = 5110,
-
- /**
- * The transaction referenced in the requested operation (typically a
- * reject operation), was not found.
- */
- TALER_EC_BANK_TRANSACTION_NOT_FOUND = 5111,
-
- /**
- * Bank received a malformed amount string.
- */
- TALER_EC_BANK_BAD_FORMAT_AMOUNT = 5112,
-
- /**
- * The client does not own the account credited by the transaction
- * which is to be rejected, so it has no rights do reject it. To be
- * returned along HTTP 403 Forbidden.
- */
- TALER_EC_BANK_REJECT_NO_RIGHTS = 5200,
-
- /**
- * This error code is returned when no known exception types captured
- * the exception, and comes along with a 500 Internal Server Error.
- */
- TALER_EC_BANK_UNMANAGED_EXCEPTION = 5300,
-
- /**
- * This error code is used for all those exceptions that do not really
- * need a specific error code to return to the client, but need to
- * signal the middleware that the bank is not responding with 500
- * Internal Server Error. Used for example when a client is trying to
- * register with a unavailable username.
- */
- TALER_EC_BANK_SOFT_EXCEPTION = 5400,
-
- /**
- * The request UID for a request to transfer funds has already been
- * used, but with different details for the transfer.
- */
- TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED = 5500,
-
- /**
- * The sync service failed to access its database. This response is
- * provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_SYNC_DB_FETCH_ERROR = 6000,
-
- /**
- * The sync service failed find the record in its database. This
- * response is provided with HTTP status code #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_SYNC_BACKUP_UNKNOWN = 6001,
-
- /**
- * The sync service failed find the account in its database. This
- * response is provided with HTTP status code #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_SYNC_ACCOUNT_UNKNOWN = 6002,
-
- /**
- * The SHA-512 hash provided in the If-None-Match header is malformed.
- * This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_SYNC_BAD_IF_NONE_MATCH = 6003,
-
- /**
- * The SHA-512 hash provided in the If-Match header is malformed or
- * missing. This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_SYNC_BAD_IF_MATCH = 6004,
-
- /**
- * The signature provided in the "Sync-Signature" header is malformed
- * or missing. This response is provided with HTTP status code
- * #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_SYNC_BAD_SYNC_SIGNATURE = 6005,
-
- /**
- * The signature provided in the "Sync-Signature" header does not
- * match the account, old or new Etags. This response is provided with
- * HTTP status code #MHD_HTTP_FORBIDDEN.
- */
- TALER_EC_SYNC_INVALID_SIGNATURE = 6007,
-
- /**
- * The "Content-length" field for the upload is either not a number,
- * or too big, or missing. This response is provided with HTTP status
- * code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_SYNC_BAD_CONTENT_LENGTH = 6008,
-
- /**
- * The "Content-length" field for the upload is too big based on the
- * server's terms of service. This response is provided with HTTP
- * status code #MHD_HTTP_PAYLOAD_TOO_LARGE.
- */
- TALER_EC_SYNC_EXCESSIVE_CONTENT_LENGTH = 6009,
-
- /**
- * The server is out of memory to handle the upload. Trying again
- * later may succeed. This response is provided with HTTP status code
- * #MHD_HTTP_PAYLOAD_TOO_LARGE.
- */
- TALER_EC_SYNC_OUT_OF_MEMORY_ON_CONTENT_LENGTH = 6010,
-
- /**
- * The uploaded data does not match the Etag. This response is
- * provided with HTTP status code #MHD_HTTP_BAD_REQUEST.
- */
- TALER_EC_SYNC_INVALID_UPLOAD = 6011,
-
- /**
- * We failed to check for existing upload data in the database. This
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_SYNC_DATABASE_FETCH_ERROR = 6012,
-
- /**
- * HTTP server was being shutdown while this operation was pending.
- * This response is provided with HTTP status code
- * #MHD_HTTP_SERVICE_UNAVAILABLE.
- */
- TALER_EC_SYNC_SHUTDOWN = 6013,
-
- /**
- * HTTP server experienced a timeout while awaiting promised payment.
- * This response is provided with HTTP status code
- * #MHD_HTTP_REQUEST_TIMEOUT.
- */
- TALER_EC_SYNC_PAYMENT_TIMEOUT = 6014,
-
- /**
- * Sync could not store order data in its own database. This response
- * is provided with HTTP status code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_SYNC_PAYMENT_CREATE_DB_ERROR = 6015,
-
- /**
- * Sync could not store payment confirmation in its own database. This
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_SYNC_PAYMENT_CONFIRM_DB_ERROR = 6016,
-
- /**
- * Sync could not fetch information about possible existing orders
- * from its own database. This response is provided with HTTP status
- * code #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_SYNC_PAYMENT_CHECK_ORDER_DB_ERROR = 6017,
-
- /**
- * Sync could not setup the payment request with its own backend. This
- * response is provided with HTTP status code
- * #MHD_HTTP_INTERNAL_SERVER_ERROR.
- */
- TALER_EC_SYNC_PAYMENT_CREATE_BACKEND_ERROR = 6018,
-
- /**
- * The sync service failed find the backup to be updated in its
- * database. This response is provided with HTTP status code
- * #MHD_HTTP_NOT_FOUND.
- */
- TALER_EC_SYNC_PREVIOUS_BACKUP_UNKNOWN = 6019,
-
- /**
- * End of error code range.
- */
- TALER_EC_END = 9999,
-
-};
-
-
-#endif
diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h
index 5f7cf6033..0597799b5 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -1,23 +1,26 @@
/*
- This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
+ This file is part of TALER
+ Copyright (C) 2014-2024 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 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.
+ 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/>
-*/
+ 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 include/taler_exchange_service.h
* @brief C interface of libtalerexchange, a C library to use exchange's HTTP API
+ * This library is not thread-safe, all APIs must only be used from a single thread.
+ * This library calls abort() if it runs out of memory. Be aware of these limitations.
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
* @author Christian Grothoff
+ * @author Özgür Kesim
*/
#ifndef _TALER_EXCHANGE_SERVICE_H
#define _TALER_EXCHANGE_SERVICE_H
@@ -25,31 +28,17 @@
#include <jansson.h>
#include "taler_util.h"
#include "taler_error_codes.h"
+#include "taler_kyclogic_lib.h"
#include <gnunet/gnunet_curl_lib.h>
-/* ********************* /keys *********************** */
-
/**
- * List of possible options to be passed to
- * #TALER_EXCHANGE_connect().
+ * Version of the Taler Exchange API, in hex.
+ * Thus 0.8.4-1 = 0x00080401.
*/
-enum TALER_EXCHANGE_Option
-{
- /**
- * Terminator (end of option list).
- */
- TALER_EXCHANGE_OPTION_END = 0,
-
- /**
- * Followed by a "const json_t *" that was previously returned for
- * this exchange URL by #TALER_EXCHANGE_serialize_data(). Used to
- * resume a connection to an exchange without having to re-download
- * /keys data (or at least only download the deltas).
- */
- TALER_EXCHANGE_OPTION_DATA
+#define TALER_EXCHANGE_API_VERSION 0x00100000
-};
+/* ********************* /keys *********************** */
/**
@@ -70,17 +59,17 @@ struct TALER_EXCHANGE_SigningPublicKey
/**
* Validity start time
*/
- struct GNUNET_TIME_Absolute valid_from;
+ struct GNUNET_TIME_Timestamp valid_from;
/**
* Validity expiration time (how long the exchange may use it).
*/
- struct GNUNET_TIME_Absolute valid_until;
+ struct GNUNET_TIME_Timestamp valid_until;
/**
* Validity expiration time for legal disputes.
*/
- struct GNUNET_TIME_Absolute valid_legal;
+ struct GNUNET_TIME_Timestamp valid_legal;
};
@@ -97,7 +86,7 @@ struct TALER_EXCHANGE_DenomPublicKey
/**
* The hash of the public key.
*/
- struct GNUNET_HashCode h_key;
+ struct TALER_DenominationHashP h_key;
/**
* Exchange's master signature over this denomination record.
@@ -107,18 +96,18 @@ struct TALER_EXCHANGE_DenomPublicKey
/**
* Timestamp indicating when the denomination key becomes valid
*/
- struct GNUNET_TIME_Absolute valid_from;
+ struct GNUNET_TIME_Timestamp valid_from;
/**
* Timestamp indicating when the denomination key can’t be used anymore to
* withdraw new coins.
*/
- struct GNUNET_TIME_Absolute withdraw_valid_until;
+ struct GNUNET_TIME_Timestamp withdraw_valid_until;
/**
* Timestamp indicating when coins of this denomination become invalid.
*/
- struct GNUNET_TIME_Absolute expire_deposit;
+ struct GNUNET_TIME_Timestamp expire_deposit;
/**
* When do signatures with this denomination key become invalid?
@@ -127,7 +116,7 @@ struct TALER_EXCHANGE_DenomPublicKey
* of the evidence. @e expire_legal is expected to be significantly
* larger than @e expire_deposit (by a year or more).
*/
- struct GNUNET_TIME_Absolute expire_legal;
+ struct GNUNET_TIME_Timestamp expire_legal;
/**
* The value of this denomination
@@ -135,30 +124,23 @@ struct TALER_EXCHANGE_DenomPublicKey
struct TALER_Amount value;
/**
- * The applicable fee for withdrawing a coin of this denomination
- */
- struct TALER_Amount fee_withdraw;
-
- /**
- * The applicable fee to spend a coin of this denomination
- */
- struct TALER_Amount fee_deposit;
-
- /**
- * The applicable fee to melt/refresh a coin of this denomination
+ * The applicable fees for this denomination
*/
- struct TALER_Amount fee_refresh;
+ struct TALER_DenomFeeSet fees;
/**
- * The applicable fee to refund a coin of this denomination
+ * Set to true if the private denomination key has been
+ * lost by the exchange and thus the key cannot be
+ * used for withdrawing at this time.
*/
- struct TALER_Amount fee_refund;
+ bool lost;
/**
- * Set to #GNUNET_YES if this denomination key has been
+ * Set to true if this denomination key has been
* revoked by the exchange.
*/
- int revoked;
+ bool revoked;
+
};
@@ -219,6 +201,227 @@ struct TALER_EXCHANGE_AuditorInformation
/**
+ * Global fees and options of an exchange for a given time period.
+ */
+struct TALER_EXCHANGE_GlobalFee
+{
+
+ /**
+ * Signature affirming all of the data.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Starting time of the validity period (inclusive).
+ */
+ struct GNUNET_TIME_Timestamp start_date;
+
+ /**
+ * End time of the validity period (exclusive).
+ */
+ struct GNUNET_TIME_Timestamp end_date;
+
+ /**
+ * Unmerged purses will be timed out after at most this time.
+ */
+ struct GNUNET_TIME_Relative purse_timeout;
+
+ /**
+ * Account history is limited to this timeframe.
+ */
+ struct GNUNET_TIME_Relative history_expiration;
+
+ /**
+ * Fees that apply globally, independent of denomination
+ * and wire method.
+ */
+ struct TALER_GlobalFeeSet fees;
+
+ /**
+ * Number of free purses per account.
+ */
+ uint32_t purse_account_limit;
+};
+
+
+/**
+ * List sorted by @a start_date with fees to be paid for aggregate wire transfers.
+ */
+struct TALER_EXCHANGE_WireAggregateFees
+{
+ /**
+ * This is a linked list.
+ */
+ struct TALER_EXCHANGE_WireAggregateFees *next;
+
+ /**
+ * Fee to be paid whenever the exchange wires funds to the merchant.
+ */
+ struct TALER_WireFeeSet fees;
+
+ /**
+ * Time when this fee goes into effect (inclusive)
+ */
+ struct GNUNET_TIME_Timestamp start_date;
+
+ /**
+ * Time when this fee stops being in effect (exclusive).
+ */
+ struct GNUNET_TIME_Timestamp end_date;
+
+ /**
+ * Signature affirming the above fee structure.
+ */
+ struct TALER_MasterSignatureP master_sig;
+};
+
+
+/**
+ * Information about wire fees by wire method.
+ */
+struct TALER_EXCHANGE_WireFeesByMethod
+{
+ /**
+ * Wire method with the given @e fees.
+ */
+ char *method;
+
+ /**
+ * Linked list of wire fees the exchange charges for
+ * accounts of the wire @e method.
+ */
+ struct TALER_EXCHANGE_WireAggregateFees *fees_head;
+
+};
+
+
+/**
+ * Type of an account restriction.
+ */
+enum TALER_EXCHANGE_AccountRestrictionType
+{
+ /**
+ * Invalid restriction.
+ */
+ TALER_EXCHANGE_AR_INVALID = 0,
+
+ /**
+ * Account must not be used for this operation.
+ */
+ TALER_EXCHANGE_AR_DENY = 1,
+
+ /**
+ * Other account must match given regular expression.
+ */
+ TALER_EXCHANGE_AR_REGEX = 2
+};
+
+/**
+ * Restrictions that apply to using a given exchange bank account.
+ */
+struct TALER_EXCHANGE_AccountRestriction
+{
+
+ /**
+ * Type of the account restriction.
+ */
+ enum TALER_EXCHANGE_AccountRestrictionType type;
+
+ /**
+ * Restriction details depending on @e type.
+ */
+ union
+ {
+ /**
+ * Details if type is #TALER_EXCHANGE_AR_REGEX.
+ */
+ struct
+ {
+ /**
+ * Regular expression that the payto://-URI of the partner account must
+ * follow. The regular expression should follow posix-egrep, but
+ * without support for character classes, GNU extensions,
+ * back-references or intervals. See
+ * https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html
+ * for a description of the posix-egrep syntax. Applications may support
+ * regexes with additional features, but exchanges must not use such
+ * regexes.
+ */
+ char *posix_egrep;
+
+ /**
+ * Hint for a human to understand the restriction.
+ */
+ char *human_hint;
+
+ /**
+ * Internationalizations for the @e human_hint. Map from IETF BCP 47
+ * language tax to localized human hints.
+ */
+ json_t *human_hint_i18n;
+ } regex;
+ } details;
+
+};
+
+
+/**
+ * Information about a wire account of the exchange.
+ */
+struct TALER_EXCHANGE_WireAccount
+{
+ /**
+ * payto://-URI of the exchange.
+ */
+ char *payto_uri;
+
+ /**
+ * URL of a conversion service in case using this account is subject to
+ * currency conversion. NULL for no conversion needed.
+ */
+ char *conversion_url;
+
+ /**
+ * Array of restrictions that apply when crediting
+ * this account.
+ */
+ struct TALER_EXCHANGE_AccountRestriction *credit_restrictions;
+
+ /**
+ * Array of restrictions that apply when debiting
+ * this account.
+ */
+ struct TALER_EXCHANGE_AccountRestriction *debit_restrictions;
+
+ /**
+ * Length of the @e credit_restrictions array.
+ */
+ unsigned int credit_restrictions_length;
+
+ /**
+ * Length of the @e debit_restrictions array.
+ */
+ unsigned int debit_restrictions_length;
+
+ /**
+ * Signature of the exchange over the account (was checked by the API).
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Display label for the account, can be NULL.
+ */
+ char *bank_label;
+
+ /**
+ * Priority for ordering the account in the display.
+ */
+ int64_t priority;
+
+};
+
+
+/**
* @brief Information about keys from the exchange.
*/
struct TALER_EXCHANGE_Keys
@@ -230,6 +433,11 @@ struct TALER_EXCHANGE_Keys
struct TALER_MasterPublicKeyP master_pub;
/**
+ * Signature over extension configuration data, if any.
+ */
+ struct TALER_MasterSignatureP extensions_sig;
+
+ /**
* Array of the exchange's online signing keys.
*/
struct TALER_EXCHANGE_SigningPublicKey *sign_keys;
@@ -245,6 +453,16 @@ struct TALER_EXCHANGE_Keys
struct TALER_EXCHANGE_AuditorInformation *auditors;
/**
+ * Array with the global fees of the exchange.
+ */
+ struct TALER_EXCHANGE_GlobalFee *global_fees;
+
+ /**
+ * Configuration data for extensions.
+ */
+ json_t *extensions;
+
+ /**
* Supported Taler protocol version by the exchange.
* String in the format current:revision:age using the
* semantics of GNU libtool. See
@@ -253,6 +471,46 @@ struct TALER_EXCHANGE_Keys
char *version;
/**
+ * Supported currency of the exchange.
+ */
+ char *currency;
+
+ /**
+ * What is the base URL of the exchange that returned
+ * these keys?
+ */
+ char *exchange_url;
+
+ /**
+ * Asset type used by the exchange. Typical values
+ * are "fiat" or "crypto" or "regional" or "stock".
+ * Wallets should adjust their UI/UX based on this
+ * value.
+ */
+ char *asset_type;
+
+ /**
+ * Array of amounts a wallet is allowed to hold from
+ * this exchange before it must undergo further KYC checks.
+ */
+ struct TALER_Amount *wallet_balance_limit_without_kyc;
+
+ /**
+ * Array of accounts of the exchange.
+ */
+ struct TALER_EXCHANGE_WireAccount *accounts;
+
+ /**
+ * Array of wire fees by wire method.
+ */
+ struct TALER_EXCHANGE_WireFeesByMethod *fees;
+
+ /**
+ * Currency rendering specification for this exchange.
+ */
+ struct TALER_CurrencySpecification cspec;
+
+ /**
* How long after a reserve went idle will the exchange close it?
* This is an approximate number, not cryptographically signed by
* the exchange (advisory-only, may change anytime).
@@ -262,14 +520,60 @@ struct TALER_EXCHANGE_Keys
/**
* Timestamp indicating the /keys generation.
*/
- struct GNUNET_TIME_Absolute list_issue_date;
+ struct GNUNET_TIME_Timestamp list_issue_date;
+
+ /**
+ * When does this keys data expire?
+ */
+ struct GNUNET_TIME_Timestamp key_data_expiration;
/**
* Timestamp indicating the creation time of the last
* denomination key in /keys.
* Used to fetch /keys incrementally.
*/
- struct GNUNET_TIME_Absolute last_denom_issue_date;
+ struct GNUNET_TIME_Timestamp last_denom_issue_date;
+
+ /**
+ * If age restriction is enabled on the exchange, we get an non-zero age_mask
+ */
+ struct TALER_AgeMask age_mask;
+
+ /**
+ * Absolute STEFAN parameter.
+ */
+ struct TALER_Amount stefan_abs;
+
+ /**
+ * Logarithmic STEFAN parameter.
+ */
+ struct TALER_Amount stefan_log;
+
+ /**
+ * Linear STEFAN parameter.
+ */
+ double stefan_lin;
+
+ /**
+ * Length of @e accounts array.
+ */
+ unsigned int accounts_len;
+
+ /**
+ * Length of @e fees array.
+ */
+ unsigned int fees_len;
+
+ /**
+ * Length of the @e wallet_balance_limit_without_kyc
+ * array.
+ */
+ unsigned int wblwk_length;
+
+ /**
+ * Length of the @e global_fees array.
+ */
+ unsigned int num_global_fees;
/**
* Length of the @e sign_keys array (number of valid entries).
@@ -296,6 +600,16 @@ struct TALER_EXCHANGE_Keys
*/
unsigned int denom_keys_size;
+ /**
+ * Reference counter for this structure.
+ * Freed when it reaches 0.
+ */
+ unsigned int rc;
+
+ /**
+ * Set to true if rewards are allowed at this exchange.
+ */
+ bool rewards_allowed;
};
@@ -352,143 +666,235 @@ enum TALER_EXCHANGE_VersionCompatibility
/**
+ * General information about the HTTP response we obtained
+ * from the exchange for a request.
+ */
+struct TALER_EXCHANGE_HttpResponse
+{
+
+ /**
+ * The complete JSON reply. NULL if we failed to parse the
+ * reply (too big, invalid JSON).
+ */
+ const json_t *reply;
+
+ /**
+ * Set to the human-readable 'hint' that is optionally
+ * provided by the exchange together with errors. NULL
+ * if no hint was provided or if there was no error.
+ */
+ const char *hint;
+
+ /**
+ * HTTP status code for the response. 0 if the
+ * HTTP request failed and we did not get any answer, or
+ * if the answer was invalid and we set @a ec to a
+ * client-side error code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Taler error code. #TALER_EC_NONE if everything was
+ * OK. Usually set to the "code" field of an error
+ * response, but may be set to values created at the
+ * client side, for example when the response was
+ * not in JSON format or was otherwise ill-formed.
+ */
+ enum TALER_ErrorCode ec;
+
+};
+
+
+/**
+ * Response from /keys.
+ */
+struct TALER_EXCHANGE_KeysResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on the HTTP status code.
+ */
+ union
+ {
+
+ /**
+ * Details on #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Information about the various keys used by the exchange.
+ */
+ const struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * Protocol compatibility information
+ */
+ enum TALER_EXCHANGE_VersionCompatibility compat;
+ } ok;
+ } details;
+
+};
+
+
+/**
* Function called with information about who is auditing
- * a particular exchange and what key the exchange is using.
+ * a particular exchange and what keys the exchange is using.
+ * The ownership over the @a keys object is passed to
+ * the callee, thus it is given explicitly and not
+ * (only) via @a kr.
*
* @param cls closure
- * @param keys information about the various keys used
- * by the exchange, NULL if /keys failed
- * @param compat protocol compatibility information
- * @param ec error code, #TALER_EC_NONE on success
- * @param http_status status returned by /keys, #MHD_HTTP_OK on success
- * @param full_reply JSON body of /keys request, NULL if reply was not in JSON
+ * @param kr response from /keys
+ * @param[in] keys keys object passed to callback with
+ * reference counter of 1. Must be freed by callee
+ * using #TALER_EXCHANGE_keys_decref(). NULL on failure.
*/
typedef void
-(*TALER_EXCHANGE_CertificationCallback) (
+(*TALER_EXCHANGE_GetKeysCallback) (
void *cls,
- const struct TALER_EXCHANGE_Keys *keys,
- enum TALER_EXCHANGE_VersionCompatibility compat,
- enum TALER_ErrorCode ec,
- unsigned int http_status,
- const json_t *full_reply);
+ const struct TALER_EXCHANGE_KeysResponse *kr,
+ struct TALER_EXCHANGE_Keys *keys);
/**
- * @brief Handle to the exchange. This is where we interact with
- * a particular exchange and keep the per-exchange information.
+ * @brief Handle for a GET /keys request.
*/
-struct TALER_EXCHANGE_Handle;
+struct TALER_EXCHANGE_GetKeysHandle;
/**
- * Initialise a connection to the exchange. Will connect to the
- * exchange and obtain information about the exchange's master public
- * key and the exchange's auditor. The respective information will
- * be passed to the @a cert_cb once available, and all future
- * interactions with the exchange will be checked to be signed
- * (where appropriate) by the respective master key.
+ * Fetch the main /keys resources from an exchange. Does an incremental
+ * fetch if @a last_keys is given. The obtained information will be passed to
+ * the @a cert_cb (possibly after first merging it with @a last_keys to
+ * produce a full picture; expired keys (for deposit) will be removed from @a
+ * last_keys if there are any).
*
* @param ctx the context
* @param url HTTP base URL for the exchange
+ * @param[in,out] last_keys previous keys object, NULL for none
* @param cert_cb function to call with the exchange's certification information,
* possibly called repeatedly if the information changes
* @param cert_cb_cls closure for @a cert_cb
- * @param ... list of additional arguments, terminated by #TALER_EXCHANGE_OPTION_END.
* @return the exchange handle; NULL upon error
*/
-struct TALER_EXCHANGE_Handle *
-TALER_EXCHANGE_connect (struct GNUNET_CURL_Context *ctx,
- const char *url,
- TALER_EXCHANGE_CertificationCallback cert_cb,
- void *cert_cb_cls,
- ...);
+struct TALER_EXCHANGE_GetKeysHandle *
+TALER_EXCHANGE_get_keys (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *last_keys,
+ TALER_EXCHANGE_GetKeysCallback cert_cb,
+ void *cert_cb_cls);
/**
- * Serialize the latest key data from @a exchange to be persisted
- * on disk (to be used with #TALER_EXCHANGE_OPTION_DATA to more
- * efficiently recover the state).
+ * Serialize the latest data from @a keys to be persisted
+ * (for example, to be used as @a last_keys later).
*
- * @param exchange which exchange's key and wire data should be serialized
- * @return NULL on error (i.e. no current data available); otherwise
- * json object owned by the caller
+ * @param kd the key data to serialize
+ * @return NULL on error; otherwise JSON object owned by the caller
*/
json_t *
-TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange);
+TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd);
/**
- * Disconnect from the exchange.
+ * Deserialize keys data stored in @a j.
*
- * @param exchange the exchange handle
+ * @param j JSON keys data previously returned from #TALER_EXCHANGE_keys_to_json()
+ * @return NULL on error (i.e. invalid JSON); otherwise
+ * keys object with reference counter 1 owned by the caller
*/
-void
-TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange);
+struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_keys_from_json (const json_t *j);
/**
- * Obtain the keys from the exchange.
+ * Cancel GET /keys operation.
*
- * @param exchange the exchange handle
- * @return the exchange's key set
+ * @param[in] gkh the GET /keys handle
*/
-const struct TALER_EXCHANGE_Keys *
-TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange);
+void
+TALER_EXCHANGE_get_keys_cancel (struct TALER_EXCHANGE_GetKeysHandle *gkh);
/**
- * Set the fake now to be used when requesting "/keys".
+ * Increment reference counter for @a keys
*
- * @param exchange exchange handle.
- * @param now fake now to use. Note: this value will be
- * used _until_ its use will be unset via @a TALER_EXCHANGE_unset_now()
+ * @param[in,out] keys object to increment reference counter for
+ * @return keys, with incremented reference counter
*/
-void
-TALER_EXCHANGE_set_now (struct TALER_EXCHANGE_Handle *exchange,
- struct GNUNET_TIME_Absolute now);
+struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_keys_incref (struct TALER_EXCHANGE_Keys *keys);
+
/**
- * Unset the fake now to be used when requesting "/keys".
+ * Decrement reference counter for @a keys.
+ * Frees @a keys if reference counter becomes zero.
*
- * @param exchange exchange handle.
+ * @param[in,out] keys object to decrement reference counter for
*/
void
-TALER_EXCHANGE_unset_now (struct TALER_EXCHANGE_Handle *exchange);
+TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys);
/**
- * Let the user set the last valid denomination time manually.
+ * Use STEFAN curve in @a keys to convert @a brut to @a net. Computes the
+ * expected minimum (!) @a net amount that should for sure arrive in the
+ * target amount at cost of @a brut to the wallet. Note that STEFAN curves by
+ * design over-estimate actual fees and a wallet may be able to achieve the
+ * same @a net amount with less fees --- or if the available coins are
+ * abnormal in structure, it may take more.
*
- * @param exchange the exchange handle.
- * @param last_denom_new new last denomination time.
+ * @param keys exchange key data
+ * @param brut gross amount (actual cost including fees)
+ * @param[out] net net amount (effective amount)
+ * @return #GNUNET_OK on success, #GNUNET_NO if the
+ * resulting @a net is zero (or lower)
*/
-void
-TALER_EXCHANGE_set_last_denom (struct TALER_EXCHANGE_Handle *exchange,
- struct GNUNET_TIME_Absolute last_denom_new);
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_keys_stefan_b2n (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_Amount *brut,
+ struct TALER_Amount *net);
/**
- * Check if our current response for /keys is valid, and if
- * not, trigger /keys download.
+ * Use STEFAN curve in @a keys to convert @a net to @a brut. Computes the
+ * expected maximum (!) @a brut amount that should be needed in the wallet to
+ * transfer @a net amount to the target account. Note that STEFAN curves by
+ * design over-estimate actual fees and a wallet may be able to achieve the
+ * same @a net amount with less fees --- or if the available coins are
+ * abnormal in structure, it may take more.
*
- * @param exchange exchange to check keys for
- * @param force_download #GNUNET_YES to force download even if /keys is still valid
- * @return until when the response is current, 0 if we are re-downloading
+ * @param keys exchange key data
+ * @param net net amount (effective amount)
+ * @param[out] brut gross amount (actual cost including fees)
+ * @return #GNUNET_OK on success, #GNUNET_NO if the
+ * resulting @a brut is zero (only if @a net was zero)
*/
-struct GNUNET_TIME_Absolute
-TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange,
- int force_download,
- int pull_all_keys);
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_keys_stefan_n2b (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_Amount *net,
+ struct TALER_Amount *brut);
/**
- * Obtain the keys from the exchange in the raw JSON format.
+ * Round brutto or netto value computed via STEFAN
+ * curve to decimal places commonly used at the exchange.
*
- * @param exchange the exchange handle
- * @return the exchange's keys in raw JSON
+ * @param keys exchange keys response data
+ * @param[in,out] val value to round
*/
-json_t *
-TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange);
+void
+TALER_EXCHANGE_keys_stefan_round (
+ const struct TALER_EXCHANGE_Keys *keys,
+ struct TALER_Amount *val);
/**
@@ -499,19 +905,10 @@ TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange);
* @param pub claimed current online signing key for the exchange
* @return #GNUNET_OK if @a pub is (according to /keys) a current signing key
*/
-int
-TALER_EXCHANGE_test_signing_key (const struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_ExchangePublicKeyP *pub);
-
-
-/**
- * Get exchange's base URL.
- *
- * @param exchange exchange handle.
- * @return the base URL from the handle.
- */
-const char *
-TALER_EXCHANGE_get_base_url (const struct TALER_EXCHANGE_Handle *exchange);
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_test_signing_key (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ExchangePublicKeyP *pub);
/**
@@ -529,10 +926,24 @@ TALER_EXCHANGE_get_denomination_key (
/**
+ * Obtain the global fee details from the exchange.
+ *
+ * @param keys the exchange's key set
+ * @param ts time for when to fetch the fees
+ * @return details about the fees, NULL if no fees are known at @a ts
+ */
+const struct TALER_EXCHANGE_GlobalFee *
+TALER_EXCHANGE_get_global_fee (
+ const struct TALER_EXCHANGE_Keys *keys,
+ struct GNUNET_TIME_Timestamp ts);
+
+
+/**
* Create a copy of a denomination public key.
*
* @param key key to copy
- * @returns a copy, must be freed with #TALER_EXCHANGE_destroy_denomination_key
+ * @returns a copy, must be freed with #TALER_EXCHANGE_destroy_denomination_key()
+ * @deprecated
*/
struct TALER_EXCHANGE_DenomPublicKey *
TALER_EXCHANGE_copy_denomination_key (
@@ -541,9 +952,10 @@ TALER_EXCHANGE_copy_denomination_key (
/**
* Destroy a denomination public key.
- * Should only be called with keys created by #TALER_EXCHANGE_copy_denomination_key.
+ * Should only be called with keys created by #TALER_EXCHANGE_copy_denomination_key().
*
* @param key key to destroy.
+ * @deprecated
*/
void
TALER_EXCHANGE_destroy_denomination_key (
@@ -560,7 +972,7 @@ TALER_EXCHANGE_destroy_denomination_key (
const struct TALER_EXCHANGE_DenomPublicKey *
TALER_EXCHANGE_get_denomination_key_by_hash (
const struct TALER_EXCHANGE_Keys *keys,
- const struct GNUNET_HashCode *hc);
+ const struct TALER_DenominationHashP *hc);
/**
@@ -577,144 +989,209 @@ TALER_EXCHANGE_get_signing_key_info (
const struct TALER_ExchangePublicKeyP *exchange_pub);
-/* ********************* /wire *********************** */
+/* ********************* wire helpers *********************** */
/**
- * Sorted list of fees to be paid for aggregate wire transfers.
+ * Parse array of @a accounts of the exchange into @a was.
+ *
+ * @param master_pub master public key of the exchange, NULL to not verify signatures
+ * @param accounts array of accounts to parse
+ * @param[out] was where to write the result (already allocated)
+ * @param was_length length of the @a was array, must match the length of @a accounts
+ * @return #GNUNET_OK if parsing @a accounts succeeded
*/
-struct TALER_EXCHANGE_WireAggregateFees
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_parse_accounts (
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const json_t *accounts,
+ unsigned int was_length,
+ struct TALER_EXCHANGE_WireAccount was[static was_length]);
+
+
+/**
+ * Free data within @a was, but not @a was itself.
+ *
+ * @param was array of wire account data
+ * @param was_len length of the @a was array
+ */
+void
+TALER_EXCHANGE_free_accounts (
+ unsigned int was_len,
+ struct TALER_EXCHANGE_WireAccount was[static was_len]);
+
+
+/* ********************* /coins/$COIN_PUB/deposit *********************** */
+
+
+/**
+ * Information needed for a coin to be deposited.
+ */
+struct TALER_EXCHANGE_CoinDepositDetail
{
+
/**
- * This is a linked list.
+ * The amount to be deposited.
*/
- struct TALER_EXCHANGE_WireAggregateFees *next;
+ struct TALER_Amount amount;
/**
- * Fee to be paid whenever the exchange wires funds to the merchant.
+ * Hash over the age commitment of the coin.
*/
- struct TALER_Amount wire_fee;
+ struct TALER_AgeCommitmentHash h_age_commitment;
/**
- * Fee to be paid when the exchange closes a reserve and wires funds
- * back to a customer.
+ * The coin’s public key.
*/
- struct TALER_Amount closing_fee;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
/**
- * Time when this fee goes into effect (inclusive)
+ * The signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made
+ * by the customer with the coin’s private key.
*/
- struct GNUNET_TIME_Absolute start_date;
+ struct TALER_CoinSpendSignatureP coin_sig;
/**
- * Time when this fee stops being in effect (exclusive).
+ * Exchange’s unblinded signature of the coin.
*/
- struct GNUNET_TIME_Absolute end_date;
+ struct TALER_DenominationSignature denom_sig;
/**
- * Signature affirming the above fee structure.
+ * Hash of the public key of the coin.
*/
- struct TALER_MasterSignatureP master_sig;
+ struct TALER_DenominationHashP h_denom_pub;
};
/**
- * Information about a wire account of the exchange.
+ * Meta information about the contract relevant for a coin's deposit
+ * operation.
*/
-struct TALER_EXCHANGE_WireAccount
+struct TALER_EXCHANGE_DepositContractDetail
{
+
/**
- * payto://-URI of the exchange.
+ * Hash of the contact of the merchant with the customer (further details
+ * are never disclosed to the exchange)
*/
- const char *payto_uri;
+ struct TALER_PrivateContractHashP h_contract_terms;
/**
- * Signature of the exchange over the account (was checked by the API).
+ * The public key of the merchant (used to identify the merchant for refund
+ * requests).
*/
- struct TALER_MasterSignatureP master_sig;
+ struct TALER_MerchantPublicKeyP merchant_pub;
/**
- * Linked list of wire fees the exchange charges for
- * accounts of the wire method matching @e payto_uri.
+ * Salt used to hash the @e merchant_payto_uri.
+ */
+ struct TALER_WireSaltP wire_salt;
+
+ /**
+ * Hash over data provided by the wallet to customize the contract.
+ * All zero if not used.
+ */
+ struct GNUNET_HashCode wallet_data_hash;
+
+ /**
+ * Date until which the merchant can issue a refund to the customer via the
+ * exchange (can be zero if refunds are not allowed); must not be after the
+ * @e wire_deadline.
+ */
+ struct GNUNET_TIME_Timestamp refund_deadline;
+
+ /**
+ * Execution date, until which the merchant would like the exchange to
+ * settle the balance (advisory, the exchange cannot be forced to settle in
+ * the past or upon very short notice, but of course a well-behaved exchange
+ * will limit aggregation based on the advice received).
+ */
+ struct GNUNET_TIME_Timestamp wire_deadline;
+
+ /**
+ * Timestamp when the contract was finalized, must match approximately the
+ * current time of the exchange.
+ */
+ struct GNUNET_TIME_Timestamp wallet_timestamp;
+
+ /**
+ * The merchant’s account details, in the payto://-format supported by the
+ * exchange.
+ */
+ const char *merchant_payto_uri;
+
+ /**
+ * Policy extension specific details about the deposit relevant to the exchange.
*/
- const struct TALER_EXCHANGE_WireAggregateFees *fees;
+ const json_t *policy_details;
};
/**
- * Callbacks of this type are used to serve the result of submitting a
- * wire format inquiry request to a exchange.
- *
- * If the request fails to generate a valid response from the
- * exchange, @a http_status will also be zero.
- *
- * @param cls closure
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful request;
- * 0 if the exchange's reply is bogus (fails to follow the protocol)
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param accounts_len length of the @a accounts array
- * @param accounts list of wire accounts of the exchange, NULL on error
- * @param full_reply the complete reply from the exchange (if it was in JSON)
+ * @brief A Batch Deposit Handle
*/
-typedef void
-(*TALER_EXCHANGE_WireCallback) (
- void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- unsigned int accounts_len,
- const struct TALER_EXCHANGE_WireAccount *accounts,
- const json_t *full_reply);
+struct TALER_EXCHANGE_BatchDepositHandle;
/**
- * @brief A Wire format inquiry handle
+ * Structure with information about a batch deposit
+ * operation's result.
*/
-struct TALER_EXCHANGE_WireHandle;
+struct TALER_EXCHANGE_BatchDepositResult
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+ union
+ {
-/**
- * Obtain information about a exchange's wire instructions. A
- * exchange may provide wire instructions for creating a reserve. The
- * wire instructions also indicate which wire formats merchants may
- * use with the exchange. This API is typically used by a wallet for
- * wiring funds, and possibly by a merchant to determine supported
- * wire formats.
- *
- * Note that while we return the (main) response verbatim to the
- * caller for further processing, we do already verify that the
- * response is well-formed (i.e. that signatures included in the
- * response are all valid). If the exchange's reply is not
- * well-formed, we return an HTTP status code of zero to @a cb.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param wire_cb the callback to call when a reply for this request is available
- * @param wire_cb_cls closure for the above callback
- * @return a handle for this request
- */
-struct TALER_EXCHANGE_WireHandle *
-TALER_EXCHANGE_wire (struct TALER_EXCHANGE_Handle *exchange,
- TALER_EXCHANGE_WireCallback wire_cb,
- void *wire_cb_cls);
+ /**
+ * Information returned if the HTTP status is
+ * #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Time when the exchange generated the batch deposit confirmation
+ */
+ struct GNUNET_TIME_Timestamp deposit_timestamp;
+ /**
+ * Deposit confirmation signature provided by the exchange
+ */
+ const struct TALER_ExchangeSignatureP *exchange_sig;
-/**
- * Cancel a wire information request. This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param wh the wire information request handle
- */
-void
-TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh);
+ /**
+ * exchange key used to sign @a exchange_sig.
+ */
+ const struct TALER_ExchangePublicKeyP *exchange_pub;
+ /**
+ * Base URL for looking up wire transfers, or
+ * NULL to use the default base URL.
+ */
+ const char *transaction_base_url;
-/* ********************* /coins/$COIN_PUB/deposit *********************** */
+ } ok;
+
+ /**
+ * Information returned if the HTTP status is
+ * #MHD_HTTP_CONFLICT.
+ */
+ struct
+ {
+ /**
+ * The coin that had a conflict.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ } conflict;
-/**
- * @brief A Deposit Handle
- */
-struct TALER_EXCHANGE_DepositHandle;
+ } details;
+};
/**
@@ -722,91 +1199,73 @@ struct TALER_EXCHANGE_DepositHandle;
* deposit permission request to a exchange.
*
* @param cls closure
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit;
- * 0 if the exchange's reply is bogus (fails to follow the protocol)
- * @param exchange_sig signature provided by the exchange
- * @param sign_key exchange key used to sign @a obj, or NULL
- * @param obj the received JSON reply, should be kept as proof (and, in case of errors,
- * be forwarded to the customer)
+ * @param dr deposit response details
*/
typedef void
-(*TALER_EXCHANGE_DepositResultCallback) (
+(*TALER_EXCHANGE_BatchDepositResultCallback) (
void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_ExchangePublicKeyP *sign_key,
- const json_t *obj);
+ const struct TALER_EXCHANGE_BatchDepositResult *dr);
/**
- * Submit a deposit permission to the exchange and get the exchange's
- * response. This API is typically used by a merchant. Note that
- * while we return the response verbatim to the caller for further
- * processing, we do already verify that the response is well-formed
- * (i.e. that signatures included in the response are all valid). If
- * the exchange's reply is not well-formed, we return an HTTP status code
- * of zero to @a cb.
+ * Submit a batch of deposit permissions to the exchange and get the
+ * exchange's response. This API is typically used by a merchant. Note that
+ * while we return the response verbatim to the caller for further processing,
+ * we do already verify that the response is well-formed (i.e. that signatures
+ * included in the response are all valid). If the exchange's reply is not
+ * well-formed, we return an HTTP status code of zero to @a cb.
*
- * We also verify that the @a coin_sig is valid for this deposit
- * request, and that the @a ub_sig is a valid signature for @a
+ * We also verify that the @a cdds.coin_sig are valid for this deposit
+ * request, and that the @a cdds.ub_sig are a valid signatures for @a
* coin_pub. Also, the @a exchange must be ready to operate (i.e. have
* finished processing the /keys reply). If either check fails, we do
* NOT initiate the transaction with the exchange and instead return NULL.
*
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param amount the amount to be deposited
- * @param wire_deadline execution date, until which the merchant would like the exchange to settle the balance (advisory, the exchange cannot be
- * forced to settle in the past or upon very short notice, but of course a well-behaved exchange will limit aggregation based on the advice received)
- * @param wire_details the merchant’s account details, in a format supported by the exchange
- * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)
- * @param coin_pub coin’s public key
- * @param denom_pub denomination key with which the coin is signed
- * @param denom_sig exchange’s unblinded signature of the coin
- * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the exchange
- * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
- * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline
- * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param dcd details about the contract the deposit is for
+ * @param num_cdds length of the @a cdds array
+ * @param cdds array with details about the coins to be deposited
* @param cb the callback to call when a reply for this request is available
* @param cb_cls closure for the above callback
+ * @param[out] ec if NULL is returned, set to the error code explaining why the operation failed
* @return a handle for this request; NULL if the inputs are invalid (i.e.
* signatures fail to verify). In this case, the callback is not called.
*/
-struct TALER_EXCHANGE_DepositHandle *
-TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_Amount *amount,
- struct GNUNET_TIME_Absolute wire_deadline,
- json_t *wire_details,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_DenominationPublicKey *denom_pub,
- struct GNUNET_TIME_Absolute timestamp,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- struct GNUNET_TIME_Absolute refund_deadline,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- TALER_EXCHANGE_DepositResultCallback cb,
- void *cb_cls);
+struct TALER_EXCHANGE_BatchDepositHandle *
+TALER_EXCHANGE_batch_deposit (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DepositContractDetail *dcd,
+ unsigned int num_cdds,
+ const struct TALER_EXCHANGE_CoinDepositDetail cdds[static num_cdds],
+ TALER_EXCHANGE_BatchDepositResultCallback cb,
+ void *cb_cls,
+ enum TALER_ErrorCode *ec);
/**
* Change the chance that our deposit confirmation will be given to the
* auditor to 100%.
*
- * @param deposit the deposit permission request handle
+ * @param[in,out] deposit the batch deposit permission request handle
*/
void
-TALER_EXCHANGE_deposit_force_dc (struct TALER_EXCHANGE_DepositHandle *deposit);
+TALER_EXCHANGE_batch_deposit_force_dc (
+ struct TALER_EXCHANGE_BatchDepositHandle *deposit);
/**
- * Cancel a deposit permission request. This function cannot be used
+ * Cancel a batch deposit permission request. This function cannot be used
* on a request handle if a response is already served for it.
*
- * @param deposit the deposit permission request handle
+ * @param[in] deposit the deposit permission request handle
*/
void
-TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit);
+TALER_EXCHANGE_batch_deposit_cancel (
+ struct TALER_EXCHANGE_BatchDepositHandle *deposit);
/* ********************* /coins/$COIN_PUB/refund *********************** */
@@ -816,46 +1275,70 @@ TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit);
*/
struct TALER_EXCHANGE_RefundHandle;
+/**
+ * Response from the /refund API.
+ */
+struct TALER_EXCHANGE_RefundResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Response details depending on the HTTP status code.
+ */
+ union
+ {
+ /**
+ * Details on #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Exchange key used to sign.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * The actual signature
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+ } ok;
+ } details;
+};
+
/**
* Callbacks of this type are used to serve the result of submitting a
* refund request to an exchange.
*
* @param cls closure
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit;
- * 0 if the exchange's reply is bogus (fails to follow the protocol)
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param sign_key exchange key used to sign @a obj, or NULL
- * @param obj the received JSON reply, should be kept as proof (and, in particular,
- * be forwarded to the customer)
+ * @param rr refund response
*/
typedef void
(*TALER_EXCHANGE_RefundCallback) (
void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const struct TALER_ExchangePublicKeyP *sign_key,
- const json_t *obj);
-
+ const struct TALER_EXCHANGE_RefundResponse *rr);
/**
- * Submit a refund request to the exchange and get the exchange's
- * response. This API is used by a merchant. Note that
- * while we return the response verbatim to the caller for further
- * processing, we do already verify that the response is well-formed
- * (i.e. that signatures included in the response are all valid). If
- * the exchange's reply is not well-formed, we return an HTTP status code
- * of zero to @a cb.
+ * Submit a refund request to the exchange and get the exchange's response.
+ * This API is used by a merchant. Note that while we return the response
+ * verbatim to the caller for further processing, we do already verify that
+ * the response is well-formed (i.e. that signatures included in the response
+ * are all valid). If the exchange's reply is not well-formed, we return an
+ * HTTP status code of zero to @a cb.
*
* The @a exchange must be ready to operate (i.e. have
* finished processing the /keys reply). If this check fails, we do
* NOT initiate the transaction with the exchange and instead return NULL.
*
- * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
* @param amount the amount to be refunded; must be larger than the refund fee
* (as that fee is still being subtracted), and smaller than the amount
* (with deposit fee) of the original deposit contribution of this coin
- * @param refund_fee fee applicable to this coin for the refund
* @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded
* @param coin_pub coin’s public key of the coin from the original deposit operation
* @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation);
@@ -869,59 +1352,17 @@ typedef void
* signatures fail to verify). In this case, the callback is not called.
*/
struct TALER_EXCHANGE_RefundHandle *
-TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_Amount *amount,
- const struct TALER_Amount *refund_fee,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- uint64_t rtransaction_id,
- const struct TALER_MerchantPrivateKeyP *merchant_priv,
- TALER_EXCHANGE_RefundCallback cb,
- void *cb_cls);
-
-
-/**
- * Submit a refund request to the exchange and get the exchange's
- * response. This API is used by a merchant. Note that
- * while we return the response verbatim to the caller for further
- * processing, we do already verify that the response is well-formed
- * (i.e. that signatures included in the response are all valid). If
- * the exchange's reply is not well-formed, we return an HTTP status code
- * of zero to @a cb.
- *
- * The @a exchange must be ready to operate (i.e. have
- * finished processing the /keys reply). If this check fails, we do
- * NOT initiate the transaction with the exchange and instead return NULL.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param amount the amount to be refunded; must be larger than the refund fee
- * (as that fee is still being subtracted), and smaller than the amount
- * (with deposit fee) of the original deposit contribution of this coin
- * @param refund_fee fee applicable to this coin for the refund
- * @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded
- * @param coin_pub coin’s public key of the coin from the original deposit operation
- * @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation);
- * this is needed as we may first do a partial refund and later a full refund. If both
- * refunds are also over the same amount, we need the @a rtransaction_id to make the disjoint
- * refund requests different (as requests are idempotent and otherwise the 2nd refund might not work).
- * @param merchant_pub public key of the merchant
- * @param merchant_sig signature affirming the refund from the merchant
- * @param cb the callback to call when a reply for this request is available
- * @param cb_cls closure for the above callback
- * @return a handle for this request; NULL if the inputs are invalid (i.e.
- * signatures fail to verify). In this case, the callback is not called.
- */
-struct TALER_EXCHANGE_RefundHandle *
-TALER_EXCHANGE_refund2 (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_Amount *amount,
- const struct TALER_Amount *refund_fee,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- uint64_t rtransaction_id,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_MerchantSignatureP *merchant_sig,
- TALER_EXCHANGE_RefundCallback cb,
- void *cb_cls);
+TALER_EXCHANGE_refund (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_Amount *amount,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t rtransaction_id,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ TALER_EXCHANGE_RefundCallback cb,
+ void *cb_cls);
/**
@@ -936,14 +1377,547 @@ void
TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund);
-/* ********************* GET /reserves/$RESERVE_PUB *********************** */
+/* ********************* POST /csr-melt *********************** */
/**
- * @brief A /reserves/ GET Handle
+ * @brief A /csr-melt Handle
*/
-struct TALER_EXCHANGE_ReservesGetHandle;
+struct TALER_EXCHANGE_CsRMeltHandle;
+
+
+/**
+ * Details about a response for a CS R request.
+ */
+struct TALER_EXCHANGE_CsRMeltResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details about the response.
+ */
+ union
+ {
+ /**
+ * Details if the status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Length of the @e alg_values array.
+ */
+ unsigned int alg_values_len;
+
+ /**
+ * Values contributed by the exchange for the
+ * respective coin's withdraw operation.
+ */
+ const struct TALER_ExchangeWithdrawValues *alg_values;
+ } ok;
+
+ /**
+ * Details if the status is #MHD_HTTP_GONE.
+ */
+ struct
+ {
+ /* FIXME: returning full details is not implemented */
+ } gone;
+
+ } details;
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * CS R request to a exchange.
+ *
+ * @param cls closure
+ * @param csrr response details
+ */
+typedef void
+(*TALER_EXCHANGE_CsRMeltCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_CsRMeltResponse *csrr);
+
+
+/**
+ * Information we pass per coin to a /csr-melt request.
+ */
+struct TALER_EXCHANGE_NonceKey
+{
+ /**
+ * Which denomination key is the /csr-melt request for?
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+ /**
+ * What is number to derive the client nonce for the
+ * fresh coin?
+ */
+ uint32_t cnc_num;
+};
+
+
+/**
+ * Get a set of CS R values using a /csr-melt request.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param rms master key used for the derivation of the CS values
+ * @param nks_len length of the @a nks array
+ * @param nks array of denominations and nonces
+ * @param res_cb the callback to call when the final result for this request is available
+ * @param res_cb_cls closure for the above callback
+ * @return handle for the operation on success, NULL on error, i.e.
+ * if the inputs are invalid (i.e. denomination key not with this exchange).
+ * In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_CsRMeltHandle *
+TALER_EXCHANGE_csr_melt (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_RefreshMasterSecretP *rms,
+ unsigned int nks_len,
+ struct TALER_EXCHANGE_NonceKey nks[static nks_len],
+ TALER_EXCHANGE_CsRMeltCallback res_cb,
+ void *res_cb_cls);
+
+
+/**
+ *
+ * Cancel a CS R melt request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param csrh the withdraw handle
+ */
+void
+TALER_EXCHANGE_csr_melt_cancel (struct TALER_EXCHANGE_CsRMeltHandle *csrh);
+
+
+/* ********************* POST /csr-withdraw *********************** */
+
+
+/**
+ * @brief A /csr-withdraw Handle
+ */
+struct TALER_EXCHANGE_CsRWithdrawHandle;
+
+
+/**
+ * Details about a response for a CS R request.
+ */
+struct TALER_EXCHANGE_CsRWithdrawResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details about the response.
+ */
+ union
+ {
+ /**
+ * Details if the status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Values contributed by the exchange for the
+ * respective coin's withdraw operation.
+ */
+ struct TALER_ExchangeWithdrawValues alg_values;
+
+ } ok;
+
+ /**
+ * Details if the status is #MHD_HTTP_GONE.
+ */
+ struct
+ {
+ /* TODO: returning full details is not implemented */
+ } gone;
+
+ } details;
+};
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * CS R withdraw request to a exchange.
+ *
+ * @param cls closure
+ * @param csrr response details
+ */
+typedef void
+(*TALER_EXCHANGE_CsRWithdrawCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr);
+
+
+/**
+ * Get a CS R using a /csr-withdraw request.
+ *
+ * @param curl_ctx The curl context to use for the requests
+ * @param exchange_url Base-URL to the excnange
+ * @param pk Which denomination key is the /csr request for
+ * @param nonce client nonce for the request
+ * @param res_cb the callback to call when the final result for this request is available
+ * @param res_cb_cls closure for the above callback
+ * @return handle for the operation on success, NULL on error, i.e.
+ * if the inputs are invalid (i.e. denomination key not with this exchange).
+ * In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_CsRWithdrawHandle *
+TALER_EXCHANGE_csr_withdraw (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce,
+ TALER_EXCHANGE_CsRWithdrawCallback res_cb,
+ void *res_cb_cls);
+
+
+/**
+ *
+ * Cancel a CS R withdraw request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param csrh the withdraw handle
+ */
+void
+TALER_EXCHANGE_csr_withdraw_cancel (
+ struct TALER_EXCHANGE_CsRWithdrawHandle *csrh);
+
+
+/* ********************* GET /coins/$COIN_PUB *********************** */
+
+/**
+ * Ways how a coin's balance may change.
+ */
+enum TALER_EXCHANGE_CoinTransactionType
+{
+
+ /**
+ * Reserved for uninitialized / none.
+ */
+ TALER_EXCHANGE_CTT_NONE,
+
+ /**
+ * Deposit into a contract.
+ */
+ TALER_EXCHANGE_CTT_DEPOSIT,
+
+ /**
+ * Spent on melt.
+ */
+ TALER_EXCHANGE_CTT_MELT,
+
+ /**
+ * Refunded by merchant.
+ */
+ TALER_EXCHANGE_CTT_REFUND,
+
+ /**
+ * Debited in recoup (to reserve) operation.
+ */
+ TALER_EXCHANGE_CTT_RECOUP,
+
+ /**
+ * Debited in recoup-and-refresh operation.
+ */
+ TALER_EXCHANGE_CTT_RECOUP_REFRESH,
+
+ /**
+ * Credited in recoup-refresh.
+ */
+ TALER_EXCHANGE_CTT_OLD_COIN_RECOUP,
+
+ /**
+ * Deposited into purse.
+ */
+ TALER_EXCHANGE_CTT_PURSE_DEPOSIT,
+
+ /**
+ * Refund from purse.
+ */
+ TALER_EXCHANGE_CTT_PURSE_REFUND,
+
+ /**
+ * Reserve open payment operation.
+ */
+ TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT
+
+};
+
+
+/**
+ * @brief Entry in the coin's transaction history.
+ */
+struct TALER_EXCHANGE_CoinHistoryEntry
+{
+
+ /**
+ * Type of the transaction.
+ */
+ enum TALER_EXCHANGE_CoinTransactionType type;
+
+ /**
+ * Amount transferred (in or out).
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Details depending on @e type.
+ */
+ union
+ {
+
+ struct
+ {
+ struct TALER_MerchantWireHashP h_wire;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_ExtensionPolicyHashP h_policy;
+ bool no_h_policy;
+ struct GNUNET_HashCode wallet_data_hash;
+ bool no_wallet_data_hash;
+ struct GNUNET_TIME_Timestamp wallet_timestamp;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_TIME_Timestamp refund_deadline;
+ struct TALER_CoinSpendSignatureP sig;
+ struct TALER_AgeCommitmentHash hac;
+ bool no_hac;
+ struct TALER_Amount deposit_fee;
+ } deposit;
+
+ struct
+ {
+ struct TALER_CoinSpendSignatureP sig;
+ struct TALER_RefreshCommitmentP rc;
+ struct TALER_AgeCommitmentHash h_age_commitment;
+ bool no_hac;
+ struct TALER_Amount melt_fee;
+ } melt;
+
+ struct
+ {
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct TALER_MerchantSignatureP sig;
+ struct TALER_Amount refund_fee;
+ struct TALER_Amount sig_amount;
+ uint64_t rtransaction_id;
+ } refund;
+
+ struct
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct GNUNET_TIME_Timestamp timestamp;
+ union GNUNET_CRYPTO_BlindingSecretP coin_bks;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ } recoup;
+
+ struct
+ {
+ struct TALER_CoinSpendPublicKeyP old_coin_pub;
+ union GNUNET_CRYPTO_BlindingSecretP coin_bks;
+ struct GNUNET_TIME_Timestamp timestamp;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ } recoup_refresh;
+
+ struct
+ {
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_CoinSpendPublicKeyP new_coin_pub;
+ struct GNUNET_TIME_Timestamp timestamp;
+ } old_coin_recoup;
+
+ struct
+ {
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ const char *exchange_base_url;
+ bool refunded;
+ struct TALER_AgeCommitmentHash phac;
+ } purse_deposit;
+
+ struct
+ {
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_Amount refund_fee;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ } purse_refund;
+
+ struct
+ {
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ } reserve_open_deposit;
+
+ } details;
+
+};
+
+
+/**
+ * @brief A /coins/$RID/history Handle
+ */
+struct TALER_EXCHANGE_CoinsHistoryHandle;
+
+
+/**
+ * Parses and verifies a coin's transaction history as
+ * returned by the exchange. Note that in case of
+ * incremental histories, the client must first combine
+ * the incremental histories into one complete history.
+ *
+ * @param keys /keys data of the exchange
+ * @param dk denomination key of the coin
+ * @param history JSON array with the coin's history
+ * @param coin_pub public key of the coin
+ * @param[out] total_in set to total amount credited to the coin in @a history
+ * @param[out] total_out set to total amount debited to the coin in @a history
+ * @param rlen length of the @a rhistory array
+ * @param[out] rhistory array where to write the parsed @a history
+ * @return #GNUNET_OK if @a history is valid,
+ * #GNUNET_SYSERR if not
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_parse_coin_history (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DenomPublicKey *dk,
+ const json_t *history,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_Amount *total_in,
+ struct TALER_Amount *total_out,
+ unsigned int rlen,
+ struct TALER_EXCHANGE_CoinHistoryEntry rhistory[static rlen]);
+
+
+/**
+ * Verify that @a coin_sig does NOT appear in the @a history of a coin's
+ * transactions and thus whatever transaction is authorized by @a coin_sig is
+ * a conflict with @a proof.
+ *
+ * @param history coin history to check
+ * @param coin_sig signature that must not be in @a history
+ * @return #GNUNET_OK if @a coin_sig is not in @a history
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_signature_conflict (
+ const json_t *history,
+ const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Response to a GET /coins/$COIN_PUB/history request.
+ */
+struct TALER_EXCHANGE_CoinHistory
+{
+ /**
+ * High-level HTTP response details.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on @e hr.http_status.
+ */
+ union
+ {
+
+ /**
+ * Information returned on success, if
+ * @e hr.http_status is #MHD_HTTP_OK
+ */
+ struct
+ {
+
+ /**
+ * Coin transaction history (possibly partial).
+ * Not yet validated, combine with other already
+ * known history data for this coin and then use
+ * #TALER_EXCHANGE_parse_coin_history() to validate
+ * the complete history and obtain it in binary
+ * format.
+ */
+ const json_t *history;
+
+ /**
+ * The hash of the coin denomination's public key
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Coin balance.
+ */
+ struct TALER_Amount balance;
+
+ } ok;
+
+ } details;
+
+};
+
+
+/**
+ * Signature of functions called with the result of
+ * a coin transaction history request.
+ *
+ * @param cls closure
+ * @param ch transaction history for the coin
+ */
+typedef void
+(*TALER_EXCHANGE_CoinsHistoryCallback)(
+ void *cls,
+ const struct TALER_EXCHANGE_CoinHistory *ch);
+
+
+/**
+ * Parses and verifies a coin's transaction history as
+ * returned by the exchange. Note that a client may
+ * have to combine multiple partial coin histories
+ * into one coherent history before calling this function.
+ *
+ * @param ctx context for managing request
+ * @param url base URL of the exchange
+ * @param coin_priv private key of the coin
+ * @param start_off offset from which on to request history
+ * @param cb function to call with results
+ * @param cb_cls closure for @a cb
+ * @return #GNUNET_OK if @a history is valid,
+ * #GNUNET_SYSERR if not
+ */
+struct TALER_EXCHANGE_CoinsHistoryHandle *
+TALER_EXCHANGE_coins_history (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ uint64_t start_off,
+ TALER_EXCHANGE_CoinsHistoryCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_coins_history() operation.
+ *
+ * @param[in] rsh operation to cancel
+ */
+void
+TALER_EXCHANGE_coins_history_cancel (
+ struct TALER_EXCHANGE_CoinsHistoryHandle *rsh);
+
+
+/* ********************* GET /reserves/$RESERVE_PUB *********************** */
/**
* Ways how a reserve's balance may change.
@@ -962,6 +1936,11 @@ enum TALER_EXCHANGE_ReserveTransactionType
TALER_EXCHANGE_RTT_WITHDRAWAL,
/**
+ * Age-Withdrawal from the reserve.
+ */
+ TALER_EXCHANGE_RTT_AGEWITHDRAWAL,
+
+ /**
* /recoup operation.
*/
TALER_EXCHANGE_RTT_RECOUP,
@@ -969,6 +1948,21 @@ enum TALER_EXCHANGE_ReserveTransactionType
/**
* Reserve closed operation.
*/
+ TALER_EXCHANGE_RTT_CLOSING,
+
+ /**
+ * Reserve purse merge operation.
+ */
+ TALER_EXCHANGE_RTT_MERGE,
+
+ /**
+ * Reserve open request operation.
+ */
+ TALER_EXCHANGE_RTT_OPEN,
+
+ /**
+ * Reserve close request operation.
+ */
TALER_EXCHANGE_RTT_CLOSE
};
@@ -977,7 +1971,7 @@ enum TALER_EXCHANGE_ReserveTransactionType
/**
* @brief Entry in the reserve's transaction history.
*/
-struct TALER_EXCHANGE_ReserveHistory
+struct TALER_EXCHANGE_ReserveHistoryEntry
{
/**
@@ -1010,17 +2004,12 @@ struct TALER_EXCHANGE_ReserveHistory
/**
* Information that uniquely identifies the wire transfer.
*/
- void *wire_reference;
+ uint64_t wire_reference;
/**
* When did the wire transfer happen?
*/
- struct GNUNET_TIME_Absolute timestamp;
-
- /**
- * Number of bytes stored in @e wire_reference.
- */
- size_t wire_reference_size;
+ struct GNUNET_TIME_Timestamp timestamp;
} in_details;
@@ -1042,6 +2031,28 @@ struct TALER_EXCHANGE_ReserveHistory
} withdraw;
/**
+ * Information about withdraw operation.
+ * @e type is #TALER_EXCHANGE_RTT_AGEWITHDRAWAL.
+ */
+ struct
+ {
+ /**
+ * Signature authorizing the withdrawal for outgoing transaction.
+ */
+ json_t *out_authorization_sig;
+
+ /**
+ * Maximum age committed
+ */
+ uint8_t max_age;
+
+ /**
+ * Fee that was charged for the withdrawal.
+ */
+ struct TALER_Amount fee;
+ } age_withdraw;
+
+ /**
* Information provided if the reserve was filled via /recoup.
* @e type is #TALER_EXCHANGE_RTT_RECOUP.
*/
@@ -1067,7 +2078,7 @@ struct TALER_EXCHANGE_ReserveHistory
/**
* When did the /recoup operation happen?
*/
- struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_TIME_Timestamp timestamp;
} recoup_details;
@@ -1078,7 +2089,7 @@ struct TALER_EXCHANGE_ReserveHistory
struct
{
/**
- * Receiver account information for the outgoing wire transfer.
+ * Receiver account information for the outgoing wire transfer as a payto://-URI.
*/
const char *receiver_account_details;
@@ -1101,7 +2112,7 @@ struct TALER_EXCHANGE_ReserveHistory
/**
* When did the wire transfer happen?
*/
- struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_TIME_Timestamp timestamp;
/**
* Fee that was charged for the closing.
@@ -1110,6 +2121,173 @@ struct TALER_EXCHANGE_ReserveHistory
} close_details;
+ /**
+ * Information about a merge operation on the reserve.
+ * @e type is #TALER_EXCHANGE_RTT_MERGE.
+ */
+ struct
+ {
+
+ /**
+ * Fee paid for the purse.
+ */
+ struct TALER_Amount purse_fee;
+
+ /**
+ * Hash over the contract.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Merge capability key.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Purse public key.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Signature by the reserve approving the merge.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * When was the merge made.
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+
+ /**
+ * When was the purse set to expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * Minimum age required for depositing into the purse.
+ */
+ uint32_t min_age;
+
+ /**
+ * Flags of the purse.
+ */
+ enum TALER_WalletAccountMergeFlags flags;
+
+ /**
+ * True if the purse was actually merged, false
+ * if only the @e purse_fee was charged.
+ */
+ bool merged;
+
+ } merge_details;
+
+ /**
+ * Information about an open request operation on the reserve.
+ * @e type is #TALER_EXCHANGE_RTT_OPEN.
+ */
+ struct
+ {
+
+ /**
+ * Signature by the reserve approving the open.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Amount to be paid from the reserve balance to open
+ * the reserve.
+ */
+ struct TALER_Amount reserve_payment;
+
+ /**
+ * When was the request created.
+ */
+ struct GNUNET_TIME_Timestamp request_timestamp;
+
+ /**
+ * For how long should the reserve be kept open.
+ * (Determines amount to be paid.)
+ */
+ struct GNUNET_TIME_Timestamp reserve_expiration;
+
+ /**
+ * How many open purses should be included with the
+ * open reserve?
+ * (Determines amount to be paid.)
+ */
+ uint32_t purse_limit;
+
+ } open_request;
+
+ /**
+ * Information about an close request operation on the reserve.
+ * @e type is #TALER_EXCHANGE_RTT_CLOSE.
+ */
+ struct
+ {
+
+ /**
+ * Signature by the reserve approving the close.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * When was the request created.
+ */
+ struct GNUNET_TIME_Timestamp request_timestamp;
+
+ /**
+ * Hash of the payto://-URI of the target account
+ * for the closure, or all zeros for the reserve
+ * origin account.
+ */
+ struct TALER_PaytoHashP target_account_h_payto;
+
+ } close_request;
+
+
+ } details;
+
+};
+
+
+/**
+ * @brief A /reserves/ GET Handle
+ */
+struct TALER_EXCHANGE_ReservesGetHandle;
+
+
+/**
+ * @brief Reserve summary.
+ */
+struct TALER_EXCHANGE_ReserveSummary
+{
+
+ /**
+ * High-level HTTP response details.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on @e hr.http_status.
+ */
+ union
+ {
+
+ /**
+ * Information returned on success, if
+ * @e hr.http_status is #MHD_HTTP_OK
+ */
+ struct
+ {
+
+ /**
+ * Reserve balance.
+ */
+ struct TALER_Amount balance;
+
+ } ok;
+
} details;
};
@@ -1120,24 +2298,12 @@ struct TALER_EXCHANGE_ReserveHistory
* reserve status request to a exchange.
*
* @param cls closure
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
- * 0 if the exchange's reply is bogus (fails to follow the protocol)
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param json original response in JSON format (useful only for diagnostics)
- * @param balance current balance in the reserve, NULL on error
- * @param history_length number of entries in the transaction history, 0 on error
- * @param history detailed transaction history, NULL on error
+ * @param rs HTTP response data
*/
typedef void
(*TALER_EXCHANGE_ReservesGetCallback) (
void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const json_t *json,
- const struct
- TALER_Amount *balance,
- unsigned int history_length,
- const struct TALER_EXCHANGE_ReserveHistory *history);
+ const struct TALER_EXCHANGE_ReserveSummary *rs);
/**
@@ -1149,8 +2315,11 @@ typedef void
* reply is not well-formed, we return an HTTP status code of zero to
* @a cb.
*
- * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param ctx curl context
+ * @param url exchange base URL
* @param reserve_pub public key of the reserve to inspect
+ * @param timeout how long to wait for an affirmative reply
+ * (enables long polling if the reserve does not yet exist)
* @param cb the callback to call when a reply for this request is available
* @param cb_cls closure for the above callback
* @return a handle for this request; NULL if the inputs are invalid (i.e.
@@ -1158,8 +2327,10 @@ typedef void
*/
struct TALER_EXCHANGE_ReservesGetHandle *
TALER_EXCHANGE_reserves_get (
- struct TALER_EXCHANGE_Handle *exchange,
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct GNUNET_TIME_Relative timeout,
TALER_EXCHANGE_ReservesGetCallback cb,
void *cb_cls);
@@ -1175,96 +2346,397 @@ TALER_EXCHANGE_reserves_get_cancel (
struct TALER_EXCHANGE_ReservesGetHandle *rgh);
-/* ********************* POST /reserves/$RESERVE_PUB/withdraw *********************** */
+/**
+ * @brief A /reserves/$RID/history Handle
+ */
+struct TALER_EXCHANGE_ReservesHistoryHandle;
/**
- * @brief A /reserves/$RESERVE_PUB/withdraw Handle
+ * @brief Reserve history details.
*/
-struct TALER_EXCHANGE_WithdrawHandle;
+struct TALER_EXCHANGE_ReserveHistory
+{
+
+ /**
+ * High-level HTTP response details.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on @e hr.http_history.
+ */
+ union
+ {
+
+ /**
+ * Information returned on success, if
+ * @e hr.http_history is #MHD_HTTP_OK
+ */
+ struct
+ {
+
+ /**
+ * Current reserve balance. May not be the difference between
+ * @e total_in and @e total_out because the @e may be truncated.
+ */
+ struct TALER_Amount balance;
+
+ /**
+ * Total of all inbound transactions in @e history.
+ */
+ struct TALER_Amount total_in;
+
+ /**
+ * Total of all outbound transactions in @e history.
+ */
+ struct TALER_Amount total_out;
+
+ /**
+ * Current etag / last entry in the history.
+ * Useful to filter requests by starting offset.
+ * Offsets are not necessarily contiguous.
+ */
+ uint64_t etag;
+
+ /**
+ * Reserve history.
+ */
+ const struct TALER_EXCHANGE_ReserveHistoryEntry *history;
+
+ /**
+ * Length of the @e history array.
+ */
+ unsigned int history_len;
+
+ } ok;
+
+ } details;
+
+};
/**
* Callbacks of this type are used to serve the result of submitting a
- * withdraw request to a exchange.
+ * reserve history request to a exchange.
*
* @param cls closure
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
- * 0 if the exchange's reply is bogus (fails to follow the protocol)
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param sig signature over the coin, NULL on error
- * @param full_response full response from the exchange (for logging, in case of errors)
+ * @param rs HTTP response data
*/
typedef void
-(*TALER_EXCHANGE_WithdrawCallback) (
+(*TALER_EXCHANGE_ReservesHistoryCallback) (
void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const struct TALER_DenominationSignature *sig,
- const json_t *full_response);
+ const struct TALER_EXCHANGE_ReserveHistory *rs);
/**
- * Withdraw a coin from the exchange using a /reserves/$RESERVE_PUB/withdraw
- * request. This API is typically used by a wallet to withdraw from a
- * reserve.
+ * Submit a request to obtain the reserve history.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param reserve_priv private key of the reserve to inspect
+ * @param start_off offset of the oldest history entry to exclude from the response
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ * signatures fail to verify). In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_ReservesHistoryHandle *
+TALER_EXCHANGE_reserves_history (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ uint64_t start_off,
+ TALER_EXCHANGE_ReservesHistoryCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel a reserve history request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param rsh the reserve request handle
+ */
+void
+TALER_EXCHANGE_reserves_history_cancel (
+ struct TALER_EXCHANGE_ReservesHistoryHandle *rsh);
+
+
+/**
+ * Information input into the withdraw process per coin.
+ */
+struct TALER_EXCHANGE_WithdrawCoinInput
+{
+ /**
+ * Denomination of the coin.
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+ /**
+ * Master key material for the coin.
+ */
+ const struct TALER_PlanchetMasterSecretP *ps;
+
+ /**
+ * Age commitment for the coin.
+ */
+ const struct TALER_AgeCommitmentHash *ach;
+
+};
+
+
+/**
+ * All the details about a coin that are generated during withdrawal and that
+ * may be needed for future operations on the coin.
+ */
+struct TALER_EXCHANGE_PrivateCoinDetails
+{
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Value used to blind the key for the signature.
+ * Needed for recoup operations.
+ */
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+
+ /**
+ * Signature over the coin.
+ */
+ struct TALER_DenominationSignature sig;
+
+ /**
+ * Values contributed from the exchange during the
+ * withdraw protocol.
+ */
+ struct TALER_ExchangeWithdrawValues exchange_vals;
+};
+
+
+/**
+ * @brief A /reserves/$RESERVE_PUB/batch-withdraw Handle
+ */
+struct TALER_EXCHANGE_BatchWithdrawHandle;
+
+
+/**
+ * Details about a response for a batch withdraw request.
+ */
+struct TALER_EXCHANGE_BatchWithdrawResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details about the response.
+ */
+ union
+ {
+ /**
+ * Details if the status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * Array of coins returned by the batch withdraw operation.
+ */
+ struct TALER_EXCHANGE_PrivateCoinDetails *coins;
+
+ /**
+ * Length of the @e coins array.
+ */
+ unsigned int num_coins;
+ } ok;
+
+ /**
+ * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+ */
+ struct
+ {
+
+ /**
+ * Hash of the payto-URI of the account to KYC;
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Legitimization requirement that the merchant should use
+ * to check for its KYC status, 0 if not known.
+ */
+ uint64_t requirement_row;
+ } unavailable_for_legal_reasons;
+
+ /**
+ * Details if the status is #MHD_HTTP_CONFLICT.
+ */
+ struct
+ {
+ /* TODO: returning full details is not implemented */
+ } conflict;
+
+ /**
+ * Details if the status is #MHD_HTTP_GONE.
+ */
+ struct
+ {
+ /* TODO: returning full details is not implemented */
+ } gone;
+
+ } details;
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * batch withdraw request to a exchange.
+ *
+ * @param cls closure
+ * @param wr response details
+ */
+typedef void
+(*TALER_EXCHANGE_BatchWithdrawCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_BatchWithdrawResponse *wr);
+
+
+/**
+ * Withdraw multiple coins from the exchange using a /reserves/$RESERVE_PUB/batch-withdraw
+ * request. This API is typically used by a wallet to withdraw many coins from a
+ * reserve. The blind signatures are unblinded and verified before being returned
+ * to the caller at @a res_cb.
*
* Note that to ensure that no money is lost in case of hardware
* failures, the caller must have committed (most of) the arguments to
* disk before calling, and be ready to repeat the request with the
* same arguments in case of failures.
*
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param pk kind of coin to create
+ * @param curl_ctx The curl context to use
+ * @param exchange_url The base-URL of the exchange
+ * @param keys The /keys material from the exchange
* @param reserve_priv private key of the reserve to withdraw from
- * @param ps secrets of the planchet
- * caller must have committed this value to disk before the call (with @a pk)
+ * @param wci_length number of entries in @a wcis
+ * @param wcis inputs that determine the planchets
* @param res_cb the callback to call when the final result for this request is available
* @param res_cb_cls closure for @a res_cb
* @return NULL
* if the inputs are invalid (i.e. denomination key not with this exchange).
* In this case, the callback is not called.
*/
-struct TALER_EXCHANGE_WithdrawHandle *
-TALER_EXCHANGE_withdraw (
- struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
+struct TALER_EXCHANGE_BatchWithdrawHandle *
+TALER_EXCHANGE_batch_withdraw (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_Keys *keys,
const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_PlanchetSecretsP *ps,
- TALER_EXCHANGE_WithdrawCallback res_cb,
+ unsigned int wci_length,
+ const struct TALER_EXCHANGE_WithdrawCoinInput wcis[static wci_length],
+ TALER_EXCHANGE_BatchWithdrawCallback res_cb,
void *res_cb_cls);
/**
+ * Cancel a batch withdraw status request. This function cannot be used on a
+ * request handle if a response is already served for it.
+ *
+ * @param wh the batch withdraw handle
+ */
+void
+TALER_EXCHANGE_batch_withdraw_cancel (
+ struct TALER_EXCHANGE_BatchWithdrawHandle *wh);
+
+
+/**
+ * Response from a withdraw2 request.
+ */
+struct TALER_EXCHANGE_Withdraw2Response
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Response details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Details if HTTP status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * blind signature over the coin
+ */
+ struct TALER_BlindedDenominationSignature blind_sig;
+ } ok;
+ } details;
+
+};
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * withdraw request to a exchange without the (un)blinding factor.
+ *
+ * @param cls closure
+ * @param w2r response data
+ */
+typedef void
+(*TALER_EXCHANGE_Withdraw2Callback) (
+ void *cls,
+ const struct TALER_EXCHANGE_Withdraw2Response *w2r);
+
+
+/**
+ * @brief A /reserves/$RESERVE_PUB/withdraw Handle, 2nd variant.
+ * This variant does not do the blinding/unblinding and only
+ * fetches the blind signature on the already blinded planchet.
+ * Used internally by the `struct TALER_EXCHANGE_WithdrawHandle`
+ * implementation as well as for the tipping logic of merchants.
+ */
+struct TALER_EXCHANGE_Withdraw2Handle;
+
+
+/**
* Withdraw a coin from the exchange using a /reserves/$RESERVE_PUB/withdraw
- * request. This API is typically used by a wallet to withdraw a tip
- * where the reserve's signature was created by the merchant already.
+ * request. This API is typically used by a merchant to withdraw a tip
+ * where the blinding factor is unknown to the merchant. Note that unlike
+ * the #TALER_EXCHANGE_batch_withdraw() API, this API neither unblinds the signatures
+ * nor can it verify that the exchange signatures are valid, so these tasks
+ * are left to the caller. Wallets probably should use #TALER_EXCHANGE_batch_withdraw()
+ * which integrates these steps.
*
* Note that to ensure that no money is lost in case of hardware
* failures, the caller must have committed (most of) the arguments to
* disk before calling, and be ready to repeat the request with the
* same arguments in case of failures.
*
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param pk kind of coin to create
- * @param reserve_sig signature from the reserve authorizing the withdrawal
- * @param reserve_pub public key of the reserve to withdraw from
- * @param ps secrets of the planchet
- * caller must have committed this value to disk before the call (with @a pk)
+ * @param curl_ctx The curl-context to use
+ * @param exchange_url The base-URL of the exchange
+ * @param keys The /keys material from the exchange
+ * @param pd planchet details of the planchet to withdraw
+ * @param reserve_priv private key of the reserve to withdraw from
* @param res_cb the callback to call when the final result for this request is available
* @param res_cb_cls closure for @a res_cb
* @return NULL
* if the inputs are invalid (i.e. denomination key not with this exchange).
* In this case, the callback is not called.
*/
-struct TALER_EXCHANGE_WithdrawHandle *
+struct TALER_EXCHANGE_Withdraw2Handle *
TALER_EXCHANGE_withdraw2 (
- struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct TALER_ReserveSignatureP *reserve_sig,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_PlanchetSecretsP *ps,
- TALER_EXCHANGE_WithdrawCallback res_cb,
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_PlanchetDetail *pd,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ TALER_EXCHANGE_Withdraw2Callback res_cb,
void *res_cb_cls);
@@ -1275,59 +2747,595 @@ TALER_EXCHANGE_withdraw2 (
* @param wh the withdraw handle
*/
void
-TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh);
+TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh);
-/* ********************* /refresh/melt+reveal ***************************** */
+/**
+ * Response from a batch-withdraw request (2nd variant).
+ */
+struct TALER_EXCHANGE_BatchWithdraw2Response
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Response details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Details if HTTP status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * array of blind signatures over the coins.
+ */
+ const struct TALER_BlindedDenominationSignature *blind_sigs;
+
+ /**
+ * length of @e blind_sigs
+ */
+ unsigned int blind_sigs_length;
+
+ } ok;
+
+ struct
+ {
+ /**
+ * Hash of the payto-URI of the account to KYC;
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * ID identifying the KYC requirement to withdraw.
+ */
+ uint64_t kyc_requirement_id;
+
+ } unavailable_for_legal_reasons;
+
+ } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a batch
+ * withdraw request to a exchange without the (un)blinding factor.
+ *
+ * @param cls closure
+ * @param bw2r response data
+ */
+typedef void
+(*TALER_EXCHANGE_BatchWithdraw2Callback) (
+ void *cls,
+ const struct TALER_EXCHANGE_BatchWithdraw2Response *bw2r);
+
+
+/**
+ * @brief A /reserves/$RESERVE_PUB/batch-withdraw Handle, 2nd variant.
+ * This variant does not do the blinding/unblinding and only
+ * fetches the blind signatures on the already blinded planchets.
+ * Used internally by the `struct TALER_EXCHANGE_BatchWithdrawHandle`
+ * implementation as well as for the tipping logic of merchants.
+ */
+struct TALER_EXCHANGE_BatchWithdraw2Handle;
+
+
+/**
+ * Withdraw a coin from the exchange using a /reserves/$RESERVE_PUB/batch-withdraw
+ * request. This API is typically used by a merchant to withdraw a tip
+ * where the blinding factor is unknown to the merchant.
+ *
+ * Note that to ensure that no money is lost in case of hardware
+ * failures, the caller must have committed (most of) the arguments to
+ * disk before calling, and be ready to repeat the request with the
+ * same arguments in case of failures.
+ *
+ * @param curl_ctx The curl context to use
+ * @param exchange_url The base-URL of the exchange
+ * @param keys The /keys material from the exchange
+ * @param pds array of planchet details of the planchet to withdraw
+ * @param pds_length number of entries in the @a pds array
+ * @param reserve_priv private key of the reserve to withdraw from
+ * @param res_cb the callback to call when the final result for this request is available
+ * @param res_cb_cls closure for @a res_cb
+ * @return NULL
+ * if the inputs are invalid (i.e. denomination key not with this exchange).
+ * In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_BatchWithdraw2Handle *
+TALER_EXCHANGE_batch_withdraw2 (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ unsigned int pds_length,
+ const struct TALER_PlanchetDetail pds[static pds_length],
+ TALER_EXCHANGE_BatchWithdraw2Callback res_cb,
+ void *res_cb_cls);
/**
- * Melt (partially spent) coins to obtain fresh coins that are
- * unlinkable to the original coin(s). Note that melting more
- * than one coin in a single request will make those coins linkable,
- * so the safest operation only melts one coin at a time.
+ * Cancel a batch withdraw request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param wh the withdraw handle
+ */
+void
+TALER_EXCHANGE_batch_withdraw2_cancel (
+ struct TALER_EXCHANGE_BatchWithdraw2Handle *wh);
+
+
+/* ********************* /reserve/$RESERVE_PUB/age-withdraw *************** */
+
+/**
+ * @brief Information needed to withdraw (and reveal) age restricted coins.
+ */
+struct TALER_EXCHANGE_AgeWithdrawCoinInput
+{
+ /**
+ * The master secret from which we derive all other relevant values for
+ * the coin: private key, nonces (if applicable) and age restriction
+ */
+ struct TALER_PlanchetMasterSecretP secrets[TALER_CNC_KAPPA];
+
+ /**
+ * The denomination of the coin. Must support age restriction, i.e
+ * its .keys.age_mask MUST not be 0
+ */
+ struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+};
+
+
+/**
+ * All the details about a coin that are generated during age-withdrawal and
+ * that may be needed for future operations on the coin.
+ */
+struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails
+{
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Hash of the public key of the coin.
+ */
+ struct TALER_CoinPubHashP h_coin_pub;
+
+ /**
+ * Value used to blind the key for the signature.
+ * Needed for recoup operations.
+ */
+ union GNUNET_CRYPTO_BlindingSecretP blinding_key;
+
+ /**
+ * The age commitment, proof for the coin, derived from the
+ * Master secret and maximum age in the originating request
+ */
+ struct TALER_AgeCommitmentProof age_commitment_proof;
+
+ /**
+ * The hash of the age commitment
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
+ * Values contributed from the exchange during the
+ * withdraw protocol.
+ */
+ struct TALER_ExchangeWithdrawValues alg_values;
+
+ /**
+ * The planchet constructed
+ */
+ struct TALER_PlanchetDetail planchet;
+};
+
+/**
+ * @brief A handle to a /reserves/$RESERVE_PUB/age-withdraw request
+ */
+struct TALER_EXCHANGE_AgeWithdrawHandle;
+
+/**
+ * @brief Details about the response for a age withdraw request.
+ */
+struct TALER_EXCHANGE_AgeWithdrawResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details about the response
+ */
+ union
+ {
+ /**
+ * Details if the status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Index that should not be revealed during the age-withdraw reveal
+ * phase.
+ */
+ uint8_t noreveal_index;
+
+ /**
+ * The commitment of the age-withdraw request, needed for the
+ * subsequent call to /age-withdraw/$ACH/reveal
+ */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+ /**
+ * The number of elements in @e coins, each referring to
+ * TALER_CNC_KAPPA elements
+ */
+ size_t num_coins;
+
+ /**
+ * The computed details of the non-revealed @e num_coins coins to keep.
+ */
+ const struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails *coin_details;
+
+ /**
+ * The array of blinded hashes of the non-revealed
+ * @e num_coins coins, needed for the reveal step;
+ */
+ const struct TALER_BlindedCoinHashP *blinded_coin_hs;
+
+ /**
+ * Key used by the exchange to sign the response.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ } ok;
+ } details;
+};
+
+
+typedef void
+(*TALER_EXCHANGE_AgeWithdrawCallback)(
+ void *cls,
+ const struct TALER_EXCHANGE_AgeWithdrawResponse *awr);
+
+/**
+ * Submit an age-withdraw request to the exchange and get the exchange's
+ * response.
*
* This API is typically used by a wallet. Note that to ensure that
- * no money is lost in case of hardware failures, is operation does
- * not actually initiate the request. Instead, it generates a buffer
- * which the caller must store before proceeding with the actual call
- * to #TALER_EXCHANGE_melt() that will generate the request.
- *
- * This function does verify that the given request data is internally
- * consistent. However, the @a melts_sigs are NOT verified.
- *
- * Aside from some non-trivial cryptographic operations that might
- * take a bit of CPU time to complete, this function returns
- * its result immediately and does not start any asynchronous
- * processing. This function is also thread-safe.
- *
- * @param melt_priv private keys of the coin to melt
- * @param melt_amount amount specifying how much
- * the coin will contribute to the melt (including fee)
- * @param melt_sig signatures affirming the
- * validity of the public keys corresponding to the
- * @a melt_priv private key
- * @param melt_pk denomination key information
- * record corresponding to the @a melt_sig
- * validity of the keys
- * @param fresh_pks_len length of the @a pks array
- * @param fresh_pks array of @a pks_len denominations of fresh coins to create
- * @param[out] res_size set to the size of the return value, or 0 on error
+ * no money is lost in case of hardware failures, the provided
+ * argument @a rd should be committed to persistent storage
+ * prior to calling this function.
+ *
+ * @param curl_ctx The curl context
+ * @param exchange_url The base url of the exchange
+ * @param keys The denomination keys from the exchange
+ * @param reserve_priv The private key to the reserve
+ * @param num_coins The number of elements in @e coin_inputs
+ * @param coin_inputs The input for the coins to withdraw
+ * @param max_age The maximum age we commit to.
+ * @param res_cb A callback for the result, maybe NULL
+ * @param res_cb_cls A closure for @e res_cb, maybe NULL
+ * @return a handle for this request; NULL if the argument was invalid.
+ * In this case, the callback will not be called.
+ */
+struct TALER_EXCHANGE_AgeWithdrawHandle *
+TALER_EXCHANGE_age_withdraw (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[static
+ num_coins],
+ uint8_t max_age,
+ TALER_EXCHANGE_AgeWithdrawCallback res_cb,
+ void *res_cb_cls);
+
+/**
+ * Cancel a age-withdraw request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param awh the age-withdraw handle
+ */
+void
+TALER_EXCHANGE_age_withdraw_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh);
+
+
+/**++++++ age-withdraw with pre-blinded planchets ***************************/
+
+/**
+ * @brief Information needed to withdraw (and reveal) age restricted coins.
+ */
+struct TALER_EXCHANGE_AgeWithdrawBlindedInput
+{
+ /**
+ * The denomination of the coin. Must support age restriction, i.e
+ * its .keys.age_mask MUST not be 0
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+
+ /**
+ * Blinded Planchets
+ */
+ struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA];
+};
+
+/**
+ * Response from an age-withdraw request with pre-blinded planchets
+ */
+struct TALER_EXCHANGE_AgeWithdrawBlindedResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Response details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Details if HTTP status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Index that should not be revealed during the age-withdraw reveal phase.
+ * The struct TALER_PlanchetMasterSecretP * from the request
+ * with this index are the ones to keep.
+ */
+ uint8_t noreveal_index;
+
+ /**
+ * The commitment of the call to age-withdraw, needed for the subsequent
+ * call to /age-withdraw/$ACH/reveal.
+ */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+ /**
+ * Key used by the exchange to sign the response.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ } ok;
+ } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting an
+ * age-withdraw request to a exchange with pre-blinded planchets
+ * without the (un)blinding factor.
+ *
+ * @param cls closure
+ * @param awbr response data
+ */
+typedef void
+(*TALER_EXCHANGE_AgeWithdrawBlindedCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedResponse *awbr);
+
+
+/**
+ * @brief A /reserves/$RESERVE_PUB/age-withdraw Handle, 2nd variant with
+ * pre-blinded planchets.
+ *
+ * This variant does not do the blinding/unblinding and only
+ * fetches the blind signatures on the already blinded planchets.
+ * Used internally by the `struct TALER_EXCHANGE_BatchWithdrawHandle`
+ * implementation as well as for the reward logic of merchants.
+ */
+struct TALER_EXCHANGE_AgeWithdrawBlindedHandle;
+
+/**
+ * Withdraw age-restricted coins from the exchange using a
+ * /reserves/$RESERVE_PUB/age-withdraw request. This API is typically used
+ * by a merchant to withdraw a reward where the planchets are pre-blinded and
+ * the blinding factor is unknown to the merchant.
+ *
+ * Note that to ensure that no money is lost in case of hardware
+ * failures, the caller must have committed (most of) the arguments to
+ * disk before calling, and be ready to repeat the request with the
+ * same arguments in case of failures.
+ *
+ * @param curl_ctx The curl context to use
+ * @param exchange_url The base-URL of the exchange
+ * @param keys The /keys material from the exchange
+ * @param max_age The maximum age that the coins are committed to.
+ * @param num_input number of entries in the @a blinded_input array
+ * @param blinded_input array of planchet details of the planchet to withdraw
+ * @param reserve_priv private key of the reserve to withdraw from
+ * @param res_cb the callback to call when the final result for this request is available
+ * @param res_cb_cls closure for @a res_cb
* @return NULL
* if the inputs are invalid (i.e. denomination key not with this exchange).
- * Otherwise, pointer to a buffer of @a res_size to store persistently
- * before proceeding to #TALER_EXCHANGE_melt().
- * Non-null results should be freed using GNUNET_free().
+ * In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *
+TALER_EXCHANGE_age_withdraw_blinded (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ uint8_t max_age,
+ unsigned int num_input,
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedInput blinded_input[static
+ num_input],
+ TALER_EXCHANGE_AgeWithdrawBlindedCallback res_cb,
+ void *res_cb_cls);
+
+
+/**
+ * Cancel an age-withdraw request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param awbh the age-withdraw handle
+ */
+void
+TALER_EXCHANGE_age_withdraw_blinded_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh);
+
+
+/* ********************* /age-withdraw/$ACH/reveal ************************ */
+
+/**
+ * @brief A handle to a /age-withdraw/$ACH/reveal request
+ */
+struct TALER_EXCHANGE_AgeWithdrawRevealHandle;
+
+/**
+ * The response from a /age-withdraw/$ACH/reveal request
+ */
+struct TALER_EXCHANGE_AgeWithdrawRevealResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details about the response
+ */
+ union
+ {
+ /**
+ * Details if the status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Number of signatures returned.
+ */
+ unsigned int num_sigs;
+
+ /**
+ * Array of @e num_coins blinded denomination signatures, giving each
+ * coin its value and validity. The array give these coins in the same
+ * order (and should have the same length) in which the original
+ * age-withdraw request specified the respective denomination keys.
+ */
+ const struct TALER_BlindedDenominationSignature *blinded_denom_sigs;
+
+ } ok;
+ } details;
+
+};
+
+typedef void
+(*TALER_EXCHANGE_AgeWithdrawRevealCallback)(
+ void *cls,
+ const struct TALER_EXCHANGE_AgeWithdrawRevealResponse *awr);
+
+/**
+ * Submit an age-withdraw-reveal request to the exchange and get the exchange's
+ * response.
+ *
+ * This API is typically used by a wallet. Note that to ensure that
+ * no money is lost in case of hardware failures, the provided
+ * argument @a rd should be committed to persistent storage
+ * prior to calling this function.
+ *
+ * @param curl_ctx The curl context
+ * @param exchange_url The base url of the exchange
+ * @param num_coins The number of elements in @e coin_inputs and @e alg_values
+ * @param coin_inputs The input for the coins to withdraw, same as in the previous call to /age-withdraw
+ * @param noreveal_index The index into each of the kappa coin candidates, that should not be revealed to the exchange
+ * @param h_commitment The commmitment from the previous call to /age-withdraw
+ * @param reserve_pub The public key of the reserve the original call to /age-withdraw was made to
+ * @param res_cb A callback for the result, maybe NULL
+ * @param res_cb_cls A closure for @e res_cb, maybe NULL
+ * @return a handle for this request; NULL if the argument was invalid.
+ * In this case, the callback will not be called.
+ */
+struct TALER_EXCHANGE_AgeWithdrawRevealHandle *
+TALER_EXCHANGE_age_withdraw_reveal (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[static
+ num_coins],
+ uint8_t noreveal_index,
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ TALER_EXCHANGE_AgeWithdrawRevealCallback res_cb,
+ void *res_cb_cls);
+
+
+/**
+ * @brief Cancel an age-withdraw-reveal request
+ *
+ * @param awrh Handle to an age-withdraw-reqveal request
*/
-char *
-TALER_EXCHANGE_refresh_prepare (
- const struct TALER_CoinSpendPrivateKeyP *melt_priv,
- const struct TALER_Amount *melt_amount,
- const struct TALER_DenominationSignature *melt_sig,
- const struct TALER_EXCHANGE_DenomPublicKey *melt_pk,
- unsigned int fresh_pks_len,
- const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks,
- size_t *res_size);
+void
+TALER_EXCHANGE_age_withdraw_reveal_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh);
+
+
+/* ********************* /refresh/melt+reveal ***************************** */
+
+
+/**
+ * Information needed to melt (partially spent) coins to obtain fresh coins
+ * that are unlinkable to the original coin(s). Note that melting more than
+ * one coin in a single request will make those coins linkable, so we only melt
+ * one coin at a time.
+ */
+struct TALER_EXCHANGE_RefreshData
+{
+ /**
+ * private key of the coin to melt
+ */
+ struct TALER_CoinSpendPrivateKeyP melt_priv;
+
+ /**
+ * age commitment and proof that went into the original coin,
+ * might be NULL.
+ */
+ const struct TALER_AgeCommitmentProof *melt_age_commitment_proof;
+
+ /**
+ * Hash of age commitment and proof that went into the original coin,
+ * might be NULL.
+ */
+ const struct TALER_AgeCommitmentHash *melt_h_age_commitment;
+
+ /**
+ * amount specifying how much the coin will contribute to the melt
+ * (including fee)
+ */
+ struct TALER_Amount melt_amount;
+
+ /**
+ * signatures affirming the validity of the public keys corresponding to the
+ * @e melt_priv private key
+ */
+ struct TALER_DenominationSignature melt_sig;
+
+ /**
+ * denomination key information record corresponding to the @e melt_sig
+ * validity of the keys
+ */
+ struct TALER_EXCHANGE_DenomPublicKey melt_pk;
+
+ /**
+ * array of @e pks_len denominations of fresh coins to create
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks;
+
+ /**
+ * length of the @e pks array
+ */
+ unsigned int fresh_pks_len;
+};
/* ********************* /coins/$COIN_PUB/melt ***************************** */
@@ -1339,27 +3347,79 @@ struct TALER_EXCHANGE_MeltHandle;
/**
+ * Information we obtain per coin during melting.
+ */
+struct TALER_EXCHANGE_MeltBlindingDetail
+{
+ /**
+ * Exchange values contributed to the refresh operation
+ */
+ struct TALER_ExchangeWithdrawValues alg_value;
+
+};
+
+
+/**
+ * Response returned to a /melt request.
+ */
+struct TALER_EXCHANGE_MeltResponse
+{
+ /**
+ * Full HTTP response details.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Parsed response details, variant depending on the
+ * @e hr.http_status.
+ */
+ union
+ {
+ /**
+ * Results for status #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * Information returned per coin.
+ */
+ const struct TALER_EXCHANGE_MeltBlindingDetail *mbds;
+
+ /**
+ * Key used by the exchange to sign the response.
+ */
+ struct TALER_ExchangePublicKeyP sign_key;
+
+ /**
+ * Length of the @a mbds array with the exchange values
+ * and blinding keys we are using.
+ */
+ unsigned int num_mbds;
+
+ /**
+ * Gamma value chosen by the exchange.
+ */
+ uint32_t noreveal_index;
+ } ok;
+
+ } details;
+};
+
+
+/**
* Callbacks of this type are used to notify the application about the result
* of the /coins/$COIN_PUB/melt stage. If successful, the @a noreveal_index
* should be committed to disk prior to proceeding
* #TALER_EXCHANGE_refreshes_reveal().
*
* @param cls closure
- * @param http_status HTTP response code, never #MHD_HTTP_OK (200) as for successful intermediate response this callback is skipped.
- * 0 if the exchange's reply is bogus (fails to follow the protocol)
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param noreveal_index choice by the exchange in the cut-and-choose protocol,
- * UINT32_MAX on error
- * @param sign_key exchange key used to sign @a full_response, or NULL
- * @param full_response full response from the exchange (for logging, in case of errors)
+ * @param mr response details
*/
typedef void
-(*TALER_EXCHANGE_MeltCallback) (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint32_t noreveal_index,
- const struct TALER_ExchangePublicKeyP *sign_key,
- const json_t *full_response);
+(*TALER_EXCHANGE_MeltCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_MeltResponse *mr);
/**
@@ -1368,26 +3428,28 @@ typedef void
*
* This API is typically used by a wallet. Note that to ensure that
* no money is lost in case of hardware failures, the provided
- * argument should have been constructed using
- * #TALER_EXCHANGE_refresh_prepare and committed to persistent storage
+ * argument @a rd should be committed to persistent storage
* prior to calling this function.
*
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param refresh_data_length size of the @a refresh_data (returned
- * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare())
- * @param refresh_data the refresh data as returned from
- #TALER_EXCHANGE_refresh_prepare())
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param rms the fresh secret that defines the refresh operation
+ * @param rd the refresh data specifying the characteristics of the operation
* @param melt_cb the callback to call with the result
* @param melt_cb_cls closure for @a melt_cb
* @return a handle for this request; NULL if the argument was invalid.
* In this case, neither callback will be called.
*/
struct TALER_EXCHANGE_MeltHandle *
-TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
- size_t refresh_data_length,
- const char *refresh_data,
- TALER_EXCHANGE_MeltCallback melt_cb,
- void *melt_cb_cls);
+TALER_EXCHANGE_melt (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_EXCHANGE_RefreshData *rd,
+ TALER_EXCHANGE_MeltCallback melt_cb,
+ void *melt_cb_cls);
/**
@@ -1404,31 +3466,92 @@ TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh);
/**
+ * Information about a coin obtained via /refreshes/$RCH/reveal.
+ */
+struct TALER_EXCHANGE_RevealedCoinInfo
+{
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Master secret of this coin.
+ */
+ struct TALER_PlanchetMasterSecretP ps;
+
+ /**
+ * Age commitment and its hash of the coin, might be NULL.
+ */
+ struct TALER_AgeCommitmentProof *age_commitment_proof;
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
+ * Blinding keys used to blind the fresh coin.
+ */
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+
+ /**
+ * Signature affirming the validity of the coin.
+ */
+ struct TALER_DenominationSignature sig;
+
+};
+
+
+/**
+ * Result of a /refreshes/$RCH/reveal request.
+ */
+struct TALER_EXCHANGE_RevealResult
+{
+ /**
+ * HTTP status.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Parsed response details, variant depending on the
+ * @e hr.http_status.
+ */
+ union
+ {
+ /**
+ * Results for status #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Array of @e num_coins values about the coins obtained via the refresh
+ * operation. The array give the coins in the same order (and should
+ * have the same length) in which the original melt request specified the
+ * respective denomination keys.
+ */
+ const struct TALER_EXCHANGE_RevealedCoinInfo *coins;
+
+ /**
+ * Number of coins returned.
+ */
+ unsigned int num_coins;
+ } ok;
+
+ } details;
+
+};
+
+
+/**
* Callbacks of this type are used to return the final result of
* submitting a refresh request to a exchange. If the operation was
* successful, this function returns the signatures over the coins
- * that were remelted. The @a coin_privs and @a sigs arrays give the
- * coins in the same order (and should have the same length) in which
- * the original request specified the respective denomination keys.
+ * that were remelted.
*
* @param cls closure
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
- * 0 if the exchange's reply is bogus (fails to follow the protocol)
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param num_coins number of fresh coins created, length of the @a sigs and @a coin_privs arrays, 0 if the operation failed
- * @param coin_privs array of @a num_coins private keys for the coins that were created, NULL on error
- * @param sigs array of signature over @a num_coins coins, NULL on error
- * @param full_response full response from the exchange (for logging, in case of errors)
+ * @param rr result of the reveal operation
*/
typedef void
(*TALER_EXCHANGE_RefreshesRevealCallback)(
void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- unsigned int num_coins,
- const struct TALER_PlanchetSecretsP *coin_privs,
- const struct TALER_DenominationSignature *sigs,
- const json_t *full_response);
+ const struct TALER_EXCHANGE_RevealResult *rr);
/**
@@ -1446,11 +3569,12 @@ struct TALER_EXCHANGE_RefreshesRevealHandle;
* arguments should have been committed to persistent storage
* prior to calling this function.
*
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param refresh_data_length size of the @a refresh_data (returned
- * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare())
- * @param refresh_data the refresh data as returned from
- #TALER_EXCHANGE_refresh_prepare())
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param rms the fresh secret that defines the refresh operation
+ * @param rd the refresh data that characterizes the refresh operation
+ * @param num_coins number of fresh coins to be created, length of the @a exchange_vals array, must match value in @a rd
+ * @param alg_values array @a num_coins of exchange values contributed to the refresh operation
* @param noreveal_index response from the exchange to the
* #TALER_EXCHANGE_melt() invocation
* @param reveal_cb the callback to call with the final result of the
@@ -1461,9 +3585,12 @@ struct TALER_EXCHANGE_RefreshesRevealHandle;
*/
struct TALER_EXCHANGE_RefreshesRevealHandle *
TALER_EXCHANGE_refreshes_reveal (
- struct TALER_EXCHANGE_Handle *exchange,
- size_t refresh_data_length,
- const char *refresh_data,
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_EXCHANGE_RefreshData *rd,
+ unsigned int num_coins,
+ const struct TALER_ExchangeWithdrawValues alg_values[static num_coins],
uint32_t noreveal_index,
TALER_EXCHANGE_RefreshesRevealCallback reveal_cb,
void *reveal_cb_cls);
@@ -1490,31 +3617,90 @@ struct TALER_EXCHANGE_LinkHandle;
/**
+ * Information about a coin obtained via /link.
+ */
+struct TALER_EXCHANGE_LinkedCoinInfo
+{
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Age commitment and its hash, if applicable.
+ */
+ bool has_age_commitment;
+ struct TALER_AgeCommitmentProof age_commitment_proof;
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
+ * Master secret of this coin.
+ */
+ struct TALER_PlanchetMasterSecretP ps;
+
+ /**
+ * Signature affirming the validity of the coin.
+ */
+ struct TALER_DenominationSignature sig;
+
+ /**
+ * Denomination public key of the coin.
+ */
+ struct TALER_DenominationPublicKey pub;
+};
+
+
+/**
+ * Result of a /link request.
+ */
+struct TALER_EXCHANGE_LinkResult
+{
+ /**
+ * HTTP status.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Parsed response details, variant depending on the
+ * @e hr.http_status.
+ */
+ union
+ {
+ /**
+ * Results for status #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Array of @e num_coins values about the
+ * coins obtained via linkage.
+ */
+ const struct TALER_EXCHANGE_LinkedCoinInfo *coins;
+
+ /**
+ * Number of coins returned.
+ */
+ unsigned int num_coins;
+ } ok;
+
+ } details;
+
+};
+
+
+/**
* Callbacks of this type are used to return the final result of submitting a
* /coins/$COIN_PUB/link request to a exchange. If the operation was
* successful, this function returns the signatures over the coins that were
* created when the original coin was melted.
*
* @param cls closure
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
- * 0 if the exchange's reply is bogus (fails to follow the protocol)
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param num_coins number of fresh coins created, length of the @a sigs and @a coin_privs arrays, 0 if the operation failed
- * @param coin_privs array of @a num_coins private keys for the coins that were created, NULL on error
- * @param sigs array of signature over @a num_coins coins, NULL on error
- * @param pubs array of public keys for the @a sigs, NULL on error
- * @param full_response full response from the exchange (for logging, in case of errors)
+ * @param lr result of the /link operation
*/
typedef void
(*TALER_EXCHANGE_LinkCallback) (
void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- unsigned int num_coins,
- const struct TALER_CoinSpendPrivateKeyP *coin_privs,
- const struct TALER_DenominationSignature *sigs,
- const struct TALER_DenominationPublicKey *pubs,
- const json_t *full_response);
+ const struct TALER_EXCHANGE_LinkResult *lr);
/**
@@ -1523,18 +3709,23 @@ typedef void
* This API is typically not used by anyone, it is more a threat against those
* trying to receive a funds transfer by abusing the refresh protocol.
*
- * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param ctx CURL context
+ * @param url exchange base URL
* @param coin_priv private key to request link data for
+ * @param age_commitment_proof age commitment to the corresponding coin, might be NULL
* @param link_cb the callback to call with the useful result of the
* refresh operation the @a coin_priv was involved in (if any)
* @param link_cb_cls closure for @a link_cb
* @return a handle for this request
*/
struct TALER_EXCHANGE_LinkHandle *
-TALER_EXCHANGE_link (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_CoinSpendPrivateKeyP *coin_priv,
- TALER_EXCHANGE_LinkCallback link_cb,
- void *link_cb_cls);
+TALER_EXCHANGE_link (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ const struct TALER_AgeCommitmentProof *age_commitment_proof,
+ TALER_EXCHANGE_LinkCallback link_cb,
+ void *link_cb_cls);
/**
@@ -1556,43 +3747,101 @@ struct TALER_EXCHANGE_TransfersGetHandle;
/**
+ * Information the exchange returns per wire transfer.
+ */
+struct TALER_EXCHANGE_TransferData
+{
+
+ /**
+ * exchange key used to sign
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * exchange signature over the transfer data
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+ /**
+ * hash of the payto:// URI the transfer went to
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * time when the exchange claims to have performed the wire transfer
+ */
+ struct GNUNET_TIME_Timestamp execution_time;
+
+ /**
+ * Actual amount of the wire transfer, excluding the wire fee.
+ */
+ struct TALER_Amount total_amount;
+
+ /**
+ * wire fee that was charged by the exchange
+ */
+ struct TALER_Amount wire_fee;
+
+ /**
+ * length of the @e details array
+ */
+ unsigned int details_length;
+
+ /**
+ * array with details about the combined transactions
+ */
+ const struct TALER_TrackTransferDetails *details;
+
+};
+
+
+/**
+ * Response for a GET /transfers request.
+ */
+struct TALER_EXCHANGE_TransfersGetResponse
+{
+ /**
+ * HTTP response.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on HTTP status code.
+ */
+ union
+ {
+ /**
+ * Details if status code is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ struct TALER_EXCHANGE_TransferData td;
+ } ok;
+
+ } details;
+};
+
+
+/**
* Function called with detailed wire transfer data, including all
* of the coin transactions that were combined into the wire transfer.
*
* @param cls closure
- * @param http_status HTTP status code we got, 0 on exchange protocol violation
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param sign_key exchange key used to sign @a json, or NULL
- * @param json original json reply (may include signatures, those have then been
- * validated already)
- * @param h_wire hash of the wire transfer address the transfer went to, or NULL on error
- * @param execution_time time when the exchange claims to have performed the wire transfer
- * @param total_amount total amount of the wire transfer, or NULL if the exchange could
- * not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK)
- * @param wire_fee wire fee that was charged by the exchange
- * @param details_length length of the @a details array
- * @param details array with details about the combined transactions
+ * @param tgr response data
*/
typedef void
(*TALER_EXCHANGE_TransfersGetCallback)(
void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const struct TALER_ExchangePublicKeyP *sign_key,
- const json_t *json,
- const struct GNUNET_HashCode *h_wire,
- struct GNUNET_TIME_Absolute execution_time,
- const struct TALER_Amount *total_amount,
- const struct TALER_Amount *wire_fee,
- unsigned int details_length,
- const struct TALER_TrackTransferDetails *details);
+ const struct TALER_EXCHANGE_TransfersGetResponse *tgr);
/**
* Query the exchange about which transactions were combined
* to create a wire transfer.
*
- * @param exchange exchange to query
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
* @param wtid raw wire transfer identifier to get information about
* @param cb callback to call
* @param cb_cls closure for @a cb
@@ -1600,7 +3849,9 @@ typedef void
*/
struct TALER_EXCHANGE_TransfersGetHandle *
TALER_EXCHANGE_transfers_get (
- struct TALER_EXCHANGE_Handle *exchange,
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
const struct TALER_WireTransferIdentifierRawP *wtid,
TALER_EXCHANGE_TransfersGetCallback cb,
void *cb_cls);
@@ -1627,30 +3878,102 @@ struct TALER_EXCHANGE_DepositGetHandle;
/**
+ * Data returned for a successful GET /deposits/ request.
+ */
+struct TALER_EXCHANGE_GetDepositResponse
+{
+
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details about the response.
+ */
+ union
+ {
+
+ /**
+ * Response if the status was #MHD_HTTP_OK
+ */
+ struct TALER_EXCHANGE_DepositData
+ {
+ /**
+ * exchange key used to sign, all zeros if exchange did not
+ * yet execute the transaction
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * signature from the exchange over the deposit data, all zeros if exchange did not
+ * yet execute the transaction
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+ /**
+ * wire transfer identifier used by the exchange, all zeros if exchange did not
+ * yet execute the transaction
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ /**
+ * actual execution time for the wire transfer
+ */
+ struct GNUNET_TIME_Timestamp execution_time;
+
+ /**
+ * contribution to the total amount by this coin, all zeros if exchange did not
+ * yet execute the transaction
+ */
+ struct TALER_Amount coin_contribution;
+
+ } ok;
+
+ /**
+ * Response if the status was #MHD_HTTP_ACCEPTED
+ */
+ struct
+ {
+
+ /**
+ * planned execution time for the wire transfer
+ */
+ struct GNUNET_TIME_Timestamp execution_time;
+
+ /**
+ * KYC legitimization requirement that the merchant should use to check
+ * for its KYC status.
+ */
+ uint64_t requirement_row;
+
+ /**
+ * Current AML state for the account. May explain why transfers are
+ * not happening.
+ */
+ enum TALER_AmlDecisionState aml_decision;
+
+ /**
+ * Set to 'true' if the KYC check is already finished and
+ * the exchange is merely waiting for the @e execution_time.
+ */
+ bool kyc_ok;
+ } accepted;
+
+ } details;
+};
+
+
+/**
* Function called with detailed wire transfer data.
*
* @param cls closure
- * @param http_status HTTP status code we got, 0 on exchange protocol violation
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param sign_key exchange key used to sign @a json, or NULL
- * @param json original json reply (may include signatures, those have then been
- * validated already)
- * @param wtid wire transfer identifier used by the exchange, NULL if exchange did not
- * yet execute the transaction
- * @param execution_time actual or planned execution time for the wire transfer
- * @param coin_contribution contribution to the total amount by this coin (can be NULL)
+ * @param dr details about the deposit response
*/
typedef void
(*TALER_EXCHANGE_DepositGetCallback)(
void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const struct TALER_ExchangePublicKeyP *sign_key,
- const json_t *json,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- struct GNUNET_TIME_Absolute
- execution_time,
- const struct TALER_Amount *coin_contribution);
+ const struct TALER_EXCHANGE_GetDepositResponse *dr);
/**
@@ -1658,22 +3981,28 @@ typedef void
* which aggregate wire transfer the deposit operation identified by @a coin_pub,
* @a merchant_priv and @a h_contract_terms contributed to.
*
- * @param exchange the exchange to query
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
* @param merchant_priv the merchant's private key
* @param h_wire hash of merchant's wire transfer details
* @param h_contract_terms hash of the proposal data
* @param coin_pub public key of the coin
+ * @param timeout timeout to use for long-polling, 0 for no long polling
* @param cb function to call with the result
* @param cb_cls closure for @a cb
* @return handle to abort request
*/
struct TALER_EXCHANGE_DepositGetHandle *
TALER_EXCHANGE_deposits_get (
- struct TALER_EXCHANGE_Handle *exchange,
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
- const struct GNUNET_HashCode *h_wire,
- const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct GNUNET_TIME_Relative timeout,
TALER_EXCHANGE_DepositGetCallback cb,
void *cb_cls);
@@ -1689,137 +4018,3310 @@ TALER_EXCHANGE_deposits_get_cancel (
struct TALER_EXCHANGE_DepositGetHandle *dwh);
+/* ********************* /recoup *********************** */
+
+
/**
- * Convenience function. Verifies a coin's transaction history as
- * returned by the exchange.
+ * @brief A /recoup Handle
+ */
+struct TALER_EXCHANGE_RecoupHandle;
+
+
+/**
+ * Response from a recoup request.
+ */
+struct TALER_EXCHANGE_RecoupResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Response details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Details if HTTP status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * public key of the reserve receiving the recoup
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ } ok;
+ } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to return the final result of
+ * submitting a recoup request to a exchange. If the operation was
+ * successful, this function returns the @a reserve_pub of the
+ * reserve that was credited.
*
- * @param dk fee structure for the coin, NULL to skip verifying fees
- * @param currency expected currency for the coin
- * @param coin_pub public key of the coin
- * @param history history of the coin in json encoding
- * @param[out] total how much of the coin has been spent according to @a history
- * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not
+ * @param cls closure
+ * @param rr response data
*/
-int
-TALER_EXCHANGE_verify_coin_history (
- const struct TALER_EXCHANGE_DenomPublicKey *dk,
- const char *currency,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- json_t *history,
- struct TALER_Amount *total);
+typedef void
+(*TALER_EXCHANGE_RecoupResultCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_RecoupResponse *rr);
/**
- * Parse history given in JSON format and return it in binary
- * format.
+ * Ask the exchange to pay back a coin due to the exchange triggering
+ * the emergency recoup protocol for a given denomination. The value
+ * of the coin will be refunded to the original customer (without fees).
*
- * @param exchange connection to the exchange we can use
- * @param history JSON array with the history
- * @param reserve_pub public key of the reserve to inspect
- * @param currency currency we expect the balance to be in
- * @param[out] balance final balance
- * @param history_length number of entries in @a history
- * @param[out] rhistory array of length @a history_length, set to the
- * parsed history entries
- * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
- * were set,
- * #GNUNET_SYSERR if there was a protocol violation in @a history
- */
-int
-TALER_EXCHANGE_parse_reserve_history (
- struct TALER_EXCHANGE_Handle *exchange,
- const json_t *history,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const char *currency,
- struct TALER_Amount *balance,
- unsigned int history_length,
- struct TALER_EXCHANGE_ReserveHistory *rhistory);
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param pk kind of coin to pay back
+ * @param denom_sig signature over the coin by the exchange using @a pk
+ * @param exchange_vals contribution from the exchange on the withdraw
+ * @param ps secret internals of the original planchet
+ * @param recoup_cb the callback to call when the final result for this request is available
+ * @param recoup_cb_cls closure for @a recoup_cb
+ * @return NULL
+ * if the inputs are invalid (i.e. denomination key not with this exchange).
+ * In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_RecoupHandle *
+TALER_EXCHANGE_recoup (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_ExchangeWithdrawValues *exchange_vals,
+ const struct TALER_PlanchetMasterSecretP *ps,
+ TALER_EXCHANGE_RecoupResultCallback recoup_cb,
+ void *recoup_cb_cls);
/**
- * Free memory (potentially) allocated by #TALER_EXCHANGE_parse_reserve_history().
+ * Cancel a recoup request. This function cannot be used on a
+ * request handle if the callback was already invoked.
*
- * @param rhistory result to free
- * @param len number of entries in @a rhistory
+ * @param ph the recoup handle
*/
void
-TALER_EXCHANGE_free_reserve_history (
- struct TALER_EXCHANGE_ReserveHistory *rhistory,
- unsigned int len);
+TALER_EXCHANGE_recoup_cancel (struct TALER_EXCHANGE_RecoupHandle *ph);
-/* ********************* /recoup *********************** */
+/* ********************* /recoup-refresh *********************** */
/**
- * @brief A /recoup Handle
+ * @brief A /recoup-refresh Handle
*/
-struct TALER_EXCHANGE_RecoupHandle;
+struct TALER_EXCHANGE_RecoupRefreshHandle;
+
+
+/**
+ * Response from a /recoup-refresh request.
+ */
+struct TALER_EXCHANGE_RecoupRefreshResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Response details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Details if HTTP status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * public key of the dirty coin that was credited
+ */
+ struct TALER_CoinSpendPublicKeyP old_coin_pub;
+
+ } ok;
+ } details;
+
+};
/**
* Callbacks of this type are used to return the final result of
- * submitting a refresh request to a exchange. If the operation was
- * successful, this function returns the signatures over the coins
- * that were remelted. The @a coin_privs and @a sigs arrays give the
- * coins in the same order (and should have the same length) in which
- * the original request specified the respective denomination keys.
+ * submitting a recoup-refresh request to a exchange.
*
* @param cls closure
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
- * 0 if the exchange's reply is bogus (fails to follow the protocol)
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param amount amount the exchange will wire back for this coin,
- * on error the total balance remaining, or NULL
- * @param timestamp what time did the exchange receive the /recoup request
- * @param reserve_pub public key of the reserve receiving the recoup, NULL if refreshed or on error
- * @param old_coin_pub public key of the dirty coin, NULL if not refreshed or on error
- * @param full_response full response from the exchange (for logging, in case of errors)
+ * @param rrr response data
*/
typedef void
-(*TALER_EXCHANGE_RecoupResultCallback) (
+(*TALER_EXCHANGE_RecoupRefreshResultCallback) (
void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
- const json_t *full_response);
+ const struct TALER_EXCHANGE_RecoupRefreshResponse *rrr);
/**
* Ask the exchange to pay back a coin due to the exchange triggering
* the emergency recoup protocol for a given denomination. The value
- * of the coin will be refunded to the original customer (without fees).
+ * of the coin will be refunded to the original coin that the
+ * revoked coin was refreshed from. The original coin is then
+ * considered a zombie.
*
- * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
* @param pk kind of coin to pay back
* @param denom_sig signature over the coin by the exchange using @a pk
- * @param ps secret internals of the original planchet
- * @param was_refreshed #GNUNET_YES if the coin in @a ps was refreshed
+ * @param exchange_vals contribution from the exchange on the withdraw
+ * @param rms melt secret of the refreshing operation
+ * @param ps coin-specific secrets derived for this coin during the refreshing operation
+ * @param idx index of the fresh coin in the refresh operation that is now being recouped
* @param recoup_cb the callback to call when the final result for this request is available
* @param recoup_cb_cls closure for @a recoup_cb
* @return NULL
* if the inputs are invalid (i.e. denomination key not with this exchange).
* In this case, the callback is not called.
*/
-struct TALER_EXCHANGE_RecoupHandle *
-TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_PlanchetSecretsP *ps,
- int was_refreshed,
- TALER_EXCHANGE_RecoupResultCallback recoup_cb,
- void *recoup_cb_cls);
+struct TALER_EXCHANGE_RecoupRefreshHandle *
+TALER_EXCHANGE_recoup_refresh (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_ExchangeWithdrawValues *exchange_vals,
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_PlanchetMasterSecretP *ps,
+ unsigned int idx,
+ TALER_EXCHANGE_RecoupRefreshResultCallback recoup_cb,
+ void *recoup_cb_cls);
/**
- * Cancel a recoup request. This function cannot be used on a
- * request handle if the callback was already invoked.
+ * Cancel a recoup-refresh request. This function cannot be used on a request
+ * handle if the callback was already invoked.
*
* @param ph the recoup handle
*/
void
-TALER_EXCHANGE_recoup_cancel (struct TALER_EXCHANGE_RecoupHandle *ph);
+TALER_EXCHANGE_recoup_refresh_cancel (
+ struct TALER_EXCHANGE_RecoupRefreshHandle *ph);
+
+
+/* ********************* /kyc* *********************** */
+
+/**
+ * Handle for a ``/kyc-check`` operation.
+ */
+struct TALER_EXCHANGE_KycCheckHandle;
+
+
+/**
+ * KYC status response details.
+ */
+struct TALER_EXCHANGE_KycStatus
+{
+ /**
+ * HTTP status code returned by the exchange.
+ */
+ unsigned int http_status;
+
+ /**
+ * Taler error code, if any.
+ */
+ enum TALER_ErrorCode ec;
+
+ union
+ {
+
+ /**
+ * KYC is OK, affirmation returned by the exchange.
+ */
+ struct
+ {
+
+ /**
+ * Details about which KYC check(s) were passed.
+ */
+ const json_t *kyc_details;
+ /**
+ * Time of the affirmation.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * The signing public key used for @e exchange_sig.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Signature of purpose
+ * #TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS affirming
+ * the successful KYC process.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+ /**
+ * AML status for the account.
+ */
+ enum TALER_AmlDecisionState aml_status;
+
+ } ok;
+
+ /**
+ * KYC is required.
+ */
+ struct
+ {
+
+ /**
+ * URL the user should open in a browser if
+ * the KYC process is to be run. Returned if
+ * @e http_status is #MHD_HTTP_ACCEPTED.
+ */
+ const char *kyc_url;
+
+ /**
+ * AML status for the account.
+ */
+ enum TALER_AmlDecisionState aml_status;
+
+ } accepted;
+
+ /**
+ * KYC is OK, but account needs positive AML decision.
+ */
+ struct
+ {
+
+ /**
+ * AML status for the account.
+ */
+ enum TALER_AmlDecisionState aml_status;
+
+ } unavailable_for_legal_reasons;
+
+ } details;
+
+};
+
+/**
+ * Function called with the result of a KYC check.
+ *
+ * @param cls closure
+ * @param ks the account's KYC status details
+ */
+typedef void
+(*TALER_EXCHANGE_KycStatusCallback)(
+ void *cls,
+ const struct TALER_EXCHANGE_KycStatus *ks);
+
+
+/**
+ * Run interaction with exchange to check KYC status
+ * of a merchant.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param keys keys of the exchange
+ * @param requirement_row number identifying the KYC requirement
+ * @param h_payto hash of the payto:// URI at @a payment_target
+ * @param ut type of the entity performing the KYC check
+ * @param timeout how long to wait for a positive KYC status
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return NULL on error
+ */
+struct TALER_EXCHANGE_KycCheckHandle *
+TALER_EXCHANGE_kyc_check (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ uint64_t requirement_row,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_KYCLOGIC_KycUserType ut,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_EXCHANGE_KycStatusCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel KYC check operation.
+ *
+ * @param kyc handle for operation to cancel
+ */
+void
+TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kyc);
+
+
+/**
+ * KYC proof response details.
+ */
+struct TALER_EXCHANGE_KycProofResponse
+{
+ /**
+ * HTTP status code returned by the exchange.
+ */
+ unsigned int http_status;
+
+ union
+ {
+
+ /**
+ * KYC is OK, affirmation returned by the exchange.
+ */
+ struct
+ {
+
+ /**
+ * Where to redirect the client next.
+ */
+ const char *redirect_url;
+
+ } found;
+
+ } details;
+
+};
+
+/**
+ * Function called with the result of a KYC check.
+ *
+ * @param cls closure
+ * @param kpr the account's KYC status details
+ */
+typedef void
+(*TALER_EXCHANGE_KycProofCallback)(
+ void *cls,
+ const struct TALER_EXCHANGE_KycProofResponse *kpr);
+
+
+/**
+ * Handle for a /kyc-proof operation.
+ */
+struct TALER_EXCHANGE_KycProofHandle;
+
+
+/**
+ * Run interaction with exchange to provide proof of KYC status.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param h_payto hash of payto URI identifying the target account
+ * @param logic name of the KYC logic to run
+ * @param args additional args to pass, can be NULL
+ * or a string to append to the URL. Must then begin with '&'.
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return NULL on error
+ */
+struct TALER_EXCHANGE_KycProofHandle *
+TALER_EXCHANGE_kyc_proof (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *logic,
+ const char *args,
+ TALER_EXCHANGE_KycProofCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel KYC proof operation.
+ *
+ * @param kph handle for operation to cancel
+ */
+void
+TALER_EXCHANGE_kyc_proof_cancel (struct TALER_EXCHANGE_KycProofHandle *kph);
+
+
+/**
+ * Handle for a ``/kyc-wallet`` operation.
+ */
+struct TALER_EXCHANGE_KycWalletHandle;
+
+
+/**
+ * KYC status response details.
+ */
+struct TALER_EXCHANGE_WalletKycResponse
+{
+
+ /**
+ * HTTP status code returned by the exchange.
+ */
+ unsigned int http_status;
+
+ /**
+ * Taler error code, if any.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * Variants depending on @e http_status.
+ */
+ union
+ {
+
+ /**
+ * In case @e http_status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+ */
+ struct
+ {
+ /**
+ * Wallet's KYC requirement row.
+ */
+ uint64_t requirement_row;
+
+ /**
+ * Hash of the payto-URI identifying the wallet to KYC.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ } unavailable_for_legal_reasons;
+
+ } details;
+
+};
+
+/**
+ * Function called with the result for a wallet looking
+ * up its KYC payment target.
+ *
+ * @param cls closure
+ * @param ks the wallets KYC payment target details
+ */
+typedef void
+(*TALER_EXCHANGE_KycWalletCallback)(
+ void *cls,
+ const struct TALER_EXCHANGE_WalletKycResponse *ks);
+
+
+/**
+ * Run interaction with exchange to find out the wallet's KYC
+ * identifier.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param reserve_priv wallet private key to check
+ * @param balance balance (or balance threshold) crossed by the wallet
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return NULL on error
+ */
+struct TALER_EXCHANGE_KycWalletHandle *
+TALER_EXCHANGE_kyc_wallet (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_Amount *balance,
+ TALER_EXCHANGE_KycWalletCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel KYC wallet operation
+ *
+ * @param kwh handle for operation to cancel
+ */
+void
+TALER_EXCHANGE_kyc_wallet_cancel (struct TALER_EXCHANGE_KycWalletHandle *kwh);
+
+
+/* ********************* /management *********************** */
+
+
+/**
+ * @brief Future Exchange's signature key
+ */
+struct TALER_EXCHANGE_FutureSigningPublicKey
+{
+ /**
+ * The signing public key
+ */
+ struct TALER_ExchangePublicKeyP key;
+
+ /**
+ * Signature by the security module affirming it owns this key.
+ */
+ struct TALER_SecurityModuleSignatureP signkey_secmod_sig;
+
+ /**
+ * Validity start time
+ */
+ struct GNUNET_TIME_Timestamp valid_from;
+
+ /**
+ * Validity expiration time (how long the exchange may use it).
+ */
+ struct GNUNET_TIME_Timestamp valid_until;
+
+ /**
+ * Validity expiration time for legal disputes.
+ */
+ struct GNUNET_TIME_Timestamp valid_legal;
+};
+
+
+/**
+ * @brief Public information about a future exchange's denomination key
+ */
+struct TALER_EXCHANGE_FutureDenomPublicKey
+{
+ /**
+ * The public key
+ */
+ struct TALER_DenominationPublicKey key;
+
+ /**
+ * Signature by the security module affirming it owns this key.
+ */
+ struct TALER_SecurityModuleSignatureP denom_secmod_sig;
+
+ /**
+ * Timestamp indicating when the denomination key becomes valid
+ */
+ struct GNUNET_TIME_Timestamp valid_from;
+
+ /**
+ * Timestamp indicating when the denomination key can’t be used anymore to
+ * withdraw new coins.
+ */
+ struct GNUNET_TIME_Timestamp withdraw_valid_until;
+
+ /**
+ * Timestamp indicating when coins of this denomination become invalid.
+ */
+ struct GNUNET_TIME_Timestamp expire_deposit;
+
+ /**
+ * When do signatures with this denomination key become invalid?
+ * After this point, these signatures cannot be used in (legal)
+ * disputes anymore, as the Exchange is then allowed to destroy its side
+ * of the evidence. @e expire_legal is expected to be significantly
+ * larger than @e expire_deposit (by a year or more).
+ */
+ struct GNUNET_TIME_Timestamp expire_legal;
+
+ /**
+ * The value of this denomination
+ */
+ struct TALER_Amount value;
+
+ /**
+ * The applicable fee for withdrawing a coin of this denomination
+ */
+ struct TALER_Amount fee_withdraw;
+
+ /**
+ * The applicable fee to spend a coin of this denomination
+ */
+ struct TALER_Amount fee_deposit;
+
+ /**
+ * The applicable fee to melt/refresh a coin of this denomination
+ */
+ struct TALER_Amount fee_refresh;
+
+ /**
+ * The applicable fee to refund a coin of this denomination
+ */
+ struct TALER_Amount fee_refund;
+
+};
+
+
+/**
+ * @brief Information about future keys from the exchange.
+ */
+struct TALER_EXCHANGE_FutureKeys
+{
+
+ /**
+ * Array of the exchange's online signing keys.
+ */
+ struct TALER_EXCHANGE_FutureSigningPublicKey *sign_keys;
+
+ /**
+ * Array of the exchange's denomination keys.
+ */
+ struct TALER_EXCHANGE_FutureDenomPublicKey *denom_keys;
+
+ /**
+ * Public key of the signkey security module.
+ */
+ struct TALER_SecurityModulePublicKeyP signkey_secmod_public_key;
+
+ /**
+ * Public key of the RSA denomination security module.
+ */
+ struct TALER_SecurityModulePublicKeyP denom_secmod_public_key;
+
+ /**
+ * Public key of the CS denomination security module.
+ */
+ struct TALER_SecurityModulePublicKeyP denom_secmod_cs_public_key;
+
+ /**
+ * Offline master public key used by this exchange.
+ */
+ struct TALER_MasterPublicKeyP master_pub;
+
+ /**
+ * Length of the @e sign_keys array (number of valid entries).
+ */
+ unsigned int num_sign_keys;
+
+ /**
+ * Length of the @e denom_keys array.
+ */
+ unsigned int num_denom_keys;
+
+};
+
+
+/**
+ * Response from a /management/keys request.
+ */
+struct TALER_EXCHANGE_ManagementGetKeysResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Response details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Details if HTTP status is #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * information about the various keys used
+ * by the exchange
+ */
+ struct TALER_EXCHANGE_FutureKeys keys;
+
+ } ok;
+ } details;
+
+};
+
+
+/**
+ * Function called with information about future keys.
+ *
+ * @param cls closure
+ * @param mgr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementGetKeysCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementGetKeysResponse *mgr);
+
+
+/**
+ * @brief Handle for a GET /management/keys request.
+ */
+struct TALER_EXCHANGE_ManagementGetKeysHandle;
+
+
+/**
+ * Request future keys from the exchange. The obtained information will be
+ * passed to the @a cb.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param cb function to call with the exchange's future keys result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementGetKeysHandle *
+TALER_EXCHANGE_get_management_keys (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ TALER_EXCHANGE_ManagementGetKeysCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_get_management_keys() operation.
+ *
+ * @param gh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_get_management_keys_cancel (
+ struct TALER_EXCHANGE_ManagementGetKeysHandle *gh);
+
+
+/**
+ * @brief Public information about a signature on an exchange's online signing key
+ */
+struct TALER_EXCHANGE_SigningKeySignature
+{
+ /**
+ * The signing public key
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Signature over this signing key by the exchange's master signature.
+ * Of purpose #TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+};
+
+
+/**
+ * @brief Public information about a signature on an exchange's denomination key
+ */
+struct TALER_EXCHANGE_DenominationKeySignature
+{
+ /**
+ * The hash of the denomination's public key
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Signature over this denomination key by the exchange's master signature.
+ * Of purpose #TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+};
+
+
+/**
+ * Information needed for a POST /management/keys operation.
+ */
+struct TALER_EXCHANGE_ManagementPostKeysData
+{
+
+ /**
+ * Array of the master signatures for the exchange's online signing keys.
+ */
+ struct TALER_EXCHANGE_SigningKeySignature *sign_sigs;
+
+ /**
+ * Array of the master signatures for the exchange's denomination keys.
+ */
+ struct TALER_EXCHANGE_DenominationKeySignature *denom_sigs;
+
+ /**
+ * Length of the @e sign_keys array (number of valid entries).
+ */
+ unsigned int num_sign_sigs;
+
+ /**
+ * Length of the @e denom_keys array.
+ */
+ unsigned int num_denom_sigs;
+};
+
+
+/**
+ * Response from a POST /management/keys request.
+ */
+struct TALER_EXCHANGE_ManagementPostKeysResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+};
+
+
+/**
+ * Function called with information about the post keys operation result.
+ *
+ * @param cls closure
+ * @param mr response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementPostKeysCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementPostKeysResponse *mr);
+
+
+/**
+ * @brief Handle for a POST /management/keys request.
+ */
+struct TALER_EXCHANGE_ManagementPostKeysHandle;
+
+
+/**
+ * Provide master-key signatures to the exchange.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param pkd signature data to POST
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementPostKeysHandle *
+TALER_EXCHANGE_post_management_keys (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_EXCHANGE_ManagementPostKeysData *pkd,
+ TALER_EXCHANGE_ManagementPostKeysCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_post_management_keys() operation.
+ *
+ * @param ph handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_post_management_keys_cancel (
+ struct TALER_EXCHANGE_ManagementPostKeysHandle *ph);
+
+
+/**
+ * Information needed for a POST /management/extensions operation.
+ *
+ * It represents the interface ExchangeKeysResponse as defined in
+ * https://docs.taler.net/design-documents/006-extensions.html#exchange
+ */
+struct TALER_EXCHANGE_ManagementPostExtensionsData
+{
+ const json_t *extensions;
+ struct TALER_MasterSignatureP extensions_sig;
+};
+
+
+/**
+ * Response from a POST /management/extensions request.
+ */
+struct TALER_EXCHANGE_ManagementPostExtensionsResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+};
+
+
+/**
+ * Function called with information about the post extensions operation result.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementPostExtensionsCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementPostExtensionsResponse *hr);
+
+/**
+ * @brief Handle for a POST /management/extensions request.
+ */
+struct TALER_EXCHANGE_ManagementPostExtensionsHandle;
+
+
+/**
+ * Uploads the configurations of enabled extensions to the exchange, signed
+ * with the master key.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param ped signature data to POST
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementPostExtensionsHandle *
+TALER_EXCHANGE_management_post_extensions (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_EXCHANGE_ManagementPostExtensionsData *ped,
+ TALER_EXCHANGE_ManagementPostExtensionsCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_post_extensions() operation.
+ *
+ * @param ph handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_post_extensions_cancel (
+ struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph);
+
+
+/**
+ * Response from a POST /management/drain request.
+ */
+struct TALER_EXCHANGE_ManagementDrainResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+};
+
+
+/**
+ * Function called with information about the drain profits result.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementDrainProfitsCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementDrainResponse *hr);
+
+
+/**
+ * @brief Handle for a POST /management/drain request.
+ */
+struct TALER_EXCHANGE_ManagementDrainProfitsHandle;
+
+
+/**
+ * Uploads the drain profits request.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param wtid wire transfer identifier to use
+ * @param amount total to transfer
+ * @param date when was the request created
+ * @param account_section configuration section identifying account to debit
+ * @param payto_uri RFC 8905 URI of the account to credit
+ * @param master_sig signature affirming the operation
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementDrainProfitsHandle *
+TALER_EXCHANGE_management_drain_profits (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *amount,
+ struct GNUNET_TIME_Timestamp date,
+ const char *account_section,
+ const char *payto_uri,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementDrainProfitsCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_drain_profits() operation.
+ *
+ * @param dp handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_drain_profits_cancel (
+ struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp);
+
+
+/**
+ * Response from a POST /management/denominations/$DENOM/revoke request.
+ */
+struct TALER_EXCHANGE_ManagementRevokeDenominationResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+};
+
+
+/**
+ * Function called with information about the post revocation operation result.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementRevokeDenominationResponse *hr);
+
+
+/**
+ * @brief Handle for a POST /management/denominations/$H_DENOM_PUB/revoke request.
+ */
+struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle;
+
+
+/**
+ * Inform the exchange that a denomination key was revoked.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param h_denom_pub hash of the denomination public key that was revoked
+ * @param master_sig signature affirming the revocation
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *
+TALER_EXCHANGE_management_revoke_denomination_key (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_revoke_denomination_key() operation.
+ *
+ * @param rh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_revoke_denomination_key_cancel (
+ struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh);
+
+
+/**
+ * Response from a POST /management/signkeys/$SK/revoke request.
+ */
+struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+};
+
+/**
+ * Function called with information about the post revocation operation result.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementRevokeSigningKeyCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse *hr);
+
+
+/**
+ * @brief Handle for a POST /management/signkeys/$H_DENOM_PUB/revoke request.
+ */
+struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle;
+
+
+/**
+ * Inform the exchange that a signing key was revoked.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param exchange_pub the public signing key that was revoked
+ * @param master_sig signature affirming the revocation
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *
+TALER_EXCHANGE_management_revoke_signing_key (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementRevokeSigningKeyCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_revoke_signing_key() operation.
+ *
+ * @param rh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_revoke_signing_key_cancel (
+ struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh);
+
+
+/**
+ * Response from a POST /management/aml-officers request.
+ */
+struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+};
+
+/**
+ * Function called with information about the change to
+ * an AML officer status.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse *hr);
+
+
+/**
+ * @brief Handle for a POST /management/aml-officers/$OFFICER_PUB request.
+ */
+struct TALER_EXCHANGE_ManagementUpdateAmlOfficer;
+
+
+/**
+ * Inform the exchange that the status of an AML officer has changed.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param officer_pub the public signing key of the officer
+ * @param officer_name name of the officer
+ * @param change_date when to affect the status change
+ * @param is_active true to enable the officer
+ * @param read_only true to only allow read-only access
+ * @param master_sig signature affirming the change
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *
+TALER_EXCHANGE_management_update_aml_officer (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *officer_name,
+ struct GNUNET_TIME_Timestamp change_date,
+ bool is_active,
+ bool read_only,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_update_aml_officer() operation.
+ *
+ * @param rh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_update_aml_officer_cancel (
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *rh);
+
+
+/**
+ * Summary data about an AML decision.
+ */
+struct TALER_EXCHANGE_AmlDecisionSummary
+{
+ /**
+ * What is the current monthly threshold.
+ */
+ struct TALER_Amount threshold;
+
+ /**
+ * Account the decision was made for.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * RowID of this decision.
+ */
+ uint64_t rowid;
+
+ /**
+ * Current decision state.
+ */
+ enum TALER_AmlDecisionState current_state;
+};
+
+
+/**
+ * Information about AML decisions returned by the exchange.
+ */
+struct TALER_EXCHANGE_AmlDecisionsResponse
+{
+ /**
+ * HTTP response details.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on the HTTP response code.
+ */
+ union
+ {
+
+ /**
+ * Information returned on success (#MHD_HTTP_OK).
+ */
+ struct
+ {
+
+ /**
+ * Array of AML decision summaries returned by the exchange.
+ */
+ const struct TALER_EXCHANGE_AmlDecisionSummary *decisions;
+
+ /**
+ * Length of the @e decisions array.
+ */
+ unsigned int decisions_length;
+
+ } ok;
+
+ } details;
+};
+
+
+/**
+ * Function called with summary information about
+ * AML decisions.
+ *
+ * @param cls closure
+ * @param adr response data
+ */
+typedef void
+(*TALER_EXCHANGE_LookupAmlDecisionsCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_AmlDecisionsResponse *adr);
+
+
+/**
+ * @brief Handle for a POST /aml/$OFFICER_PUB/decisions/$STATUS request.
+ */
+struct TALER_EXCHANGE_LookupAmlDecisions;
+
+
+/**
+ * Inform the exchange that an AML decision has been taken.
+ *
+ * @param ctx the context
+ * @param exchange_url HTTP base URL for the exchange
+ * @param start row number starting point (exclusive rowid)
+ * @param delta number of records to return, negative for descending, positive for ascending from start
+ * @param state type of AML decisions to return
+ * @param officer_priv private key of the deciding AML officer
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_LookupAmlDecisions *
+TALER_EXCHANGE_lookup_aml_decisions (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_url,
+ uint64_t start,
+ int delta,
+ enum TALER_AmlDecisionState state,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ TALER_EXCHANGE_LookupAmlDecisionsCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_lookup_aml_decisions() operation.
+ *
+ * @param lh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_lookup_aml_decisions_cancel (
+ struct TALER_EXCHANGE_LookupAmlDecisions *lh);
+
+
+/**
+ * Detailed data about an AML decision.
+ */
+struct TALER_EXCHANGE_AmlDecisionDetail
+{
+ /**
+ * When was the decision made.
+ */
+ struct GNUNET_TIME_Timestamp decision_time;
+
+ /**
+ * New threshold set by this decision.
+ */
+ struct TALER_Amount new_threshold;
+
+ /**
+ * Who made the decision?
+ */
+ struct TALER_AmlOfficerPublicKeyP decider_pub;
+
+ /**
+ * Justification given for the decision.
+ */
+ const char *justification;
+
+ /**
+ * New decision state.
+ */
+ enum TALER_AmlDecisionState new_state;
+};
+
+
+/**
+ * Detailed data collected during a KYC process for the account.
+ */
+struct TALER_EXCHANGE_KycHistoryDetail
+{
+ /**
+ * Configuration section name of the KYC provider that contributed the data.
+ */
+ const char *provider_section;
+
+ /**
+ * The collected KYC data.
+ */
+ const json_t *attributes;
+
+ /**
+ * When was the data collection made.
+ */
+ struct GNUNET_TIME_Timestamp collection_time;
+
+};
+
+
+/**
+ * Information about AML decision details returned by the exchange.
+ */
+struct TALER_EXCHANGE_AmlDecisionResponse
+{
+ /**
+ * HTTP response details.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on the HTTP response code.
+ */
+ union
+ {
+
+ /**
+ * Information returned on success (#MHD_HTTP_OK).
+ */
+ struct
+ {
+
+ /**
+ * Array of AML decision details returned by the exchange.
+ */
+ const struct TALER_EXCHANGE_AmlDecisionDetail *aml_history;
+
+ /**
+ * Length of the @e aml_history array.
+ */
+ unsigned int aml_history_length;
+
+ /**
+ * Array of KYC data collections returned by the exchange.
+ */
+ const struct TALER_EXCHANGE_KycHistoryDetail *kyc_attributes;
+
+ /**
+ * Length of the @e kyc_attributes array.
+ */
+ unsigned int kyc_attributes_length;
+
+ } ok;
+
+ } details;
+};
+
+
+/**
+ * Function called with summary information about
+ * AML decisions.
+ *
+ * @param cls closure
+ * @param adr response data
+ */
+typedef void
+(*TALER_EXCHANGE_LookupAmlDecisionCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_AmlDecisionResponse *adr);
+
+
+/**
+ * @brief Handle for a POST /aml/$OFFICER_PUB/decision/$H_PAYTO request.
+ */
+struct TALER_EXCHANGE_LookupAmlDecision;
+
+
+/**
+ * Inform the exchange that an AML decision has been taken.
+ *
+ * @param ctx the context
+ * @param exchange_url HTTP base URL for the exchange
+ * @param h_payto which account to return the decision history for
+ * @param officer_priv private key of the deciding AML officer
+ * @param history true to return the full history, otherwise only the last decision
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_LookupAmlDecision *
+TALER_EXCHANGE_lookup_aml_decision (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_url,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ bool history,
+ TALER_EXCHANGE_LookupAmlDecisionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_lookup_aml_decision() operation.
+ *
+ * @param rh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_lookup_aml_decision_cancel (
+ struct TALER_EXCHANGE_LookupAmlDecision *rh);
+
+
+/**
+ * @brief Handle for a POST /aml-decision/$OFFICER_PUB request.
+ */
+struct TALER_EXCHANGE_AddAmlDecision;
+
+
+/**
+ * Response when making an AML decision.
+ */
+struct TALER_EXCHANGE_AddAmlDecisionResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+
+/**
+ * Function called with information about storing an
+ * an AML decision.
+ *
+ * @param cls closure
+ * @param adr response data
+ */
+typedef void
+(*TALER_EXCHANGE_AddAmlDecisionCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_AddAmlDecisionResponse *adr);
+
+/**
+ * Inform the exchange that an AML decision has been taken.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param justification human-readable justification
+ * @param decision_time when was the decision made
+ * @param new_threshold at what monthly amount threshold
+ * should a revision be triggered
+ * @param h_payto payto URI hash of the account the
+ * decision is about
+ * @param new_state updated AML state
+ * @param kyc_requirements JSON array of KYC requirements being imposed, NULL for none
+ * @param officer_priv private key of the deciding AML officer
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_AddAmlDecision *
+TALER_EXCHANGE_add_aml_decision (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const char *justification,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const struct TALER_Amount *new_threshold,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_AmlDecisionState new_state,
+ const json_t *kyc_requirements,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ TALER_EXCHANGE_AddAmlDecisionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_add_aml_decision() operation.
+ *
+ * @param rh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_add_aml_decision_cancel (
+ struct TALER_EXCHANGE_AddAmlDecision *rh);
+
+
+/**
+ * Response when adding a partner exchange.
+ */
+struct TALER_EXCHANGE_ManagementAddPartnerResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+/**
+ * Function called with information about the change to
+ * an AML officer status.
+ *
+ * @param cls closure
+ * @param apr response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementAddPartnerCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementAddPartnerResponse *apr);
+
+
+/**
+ * @brief Handle for a POST /management/partners/$PARTNER_PUB request.
+ */
+struct TALER_EXCHANGE_ManagementAddPartner;
+
+
+/**
+ * Inform the exchange that the status of a partnering
+ * exchange was defined.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param partner_pub the offline signing key of the partner
+ * @param start_date validity period start
+ * @param end_date validity period end
+ * @param wad_frequency how often will we do wad transfers to this partner
+ * @param wad_fee what is the wad fee to this partner
+ * @param partner_base_url what is the base URL of the @a partner_pub exchange
+ * @param master_sig the signature the signature
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementAddPartner *
+TALER_EXCHANGE_management_add_partner (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_MasterPublicKeyP *partner_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ struct GNUNET_TIME_Relative wad_frequency,
+ const struct TALER_Amount *wad_fee,
+ const char *partner_base_url,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementAddPartnerCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_add_partner() operation.
+ *
+ * @param rh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_add_partner_cancel (
+ struct TALER_EXCHANGE_ManagementAddPartner *rh);
+
+
+/**
+ * Response when enabling an auditor.
+ */
+struct TALER_EXCHANGE_ManagementAuditorEnableResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+/**
+ * Function called with information about the auditor setup operation result.
+ *
+ * @param cls closure
+ * @param aer response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementAuditorEnableCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementAuditorEnableResponse *aer);
+
+
+/**
+ * @brief Handle for a POST /management/auditors request.
+ */
+struct TALER_EXCHANGE_ManagementAuditorEnableHandle;
+
+
+/**
+ * Inform the exchange that an auditor should be enable or enabled.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param auditor_pub the public signing key of the auditor
+ * @param auditor_url base URL of the auditor
+ * @param auditor_name human readable name for the auditor
+ * @param validity_start when was this decided?
+ * @param master_sig signature affirming the auditor addition
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementAuditorEnableHandle *
+TALER_EXCHANGE_management_enable_auditor (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp validity_start,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementAuditorEnableCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_enable_auditor() operation.
+ *
+ * @param ah handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_enable_auditor_cancel (
+ struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah);
+
+/**
+ * Response when disabling an auditor.
+ */
+struct TALER_EXCHANGE_ManagementAuditorDisableResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+/**
+ * Function called with information about the auditor disable operation result.
+ *
+ * @param cls closure
+ * @param adr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementAuditorDisableCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementAuditorDisableResponse *adr);
+
+
+/**
+ * @brief Handle for a POST /management/auditors/$AUDITOR_PUB/disable request.
+ */
+struct TALER_EXCHANGE_ManagementAuditorDisableHandle;
+
+
+/**
+ * Inform the exchange that an auditor should be disabled.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param auditor_pub the public signing key of the auditor
+ * @param validity_end when was this decided?
+ * @param master_sig signature affirming the auditor addition
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementAuditorDisableHandle *
+TALER_EXCHANGE_management_disable_auditor (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct GNUNET_TIME_Timestamp validity_end,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementAuditorDisableCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_disable_auditor() operation.
+ *
+ * @param ah handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_disable_auditor_cancel (
+ struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah);
+
+
+/**
+ * Response from an exchange account/enable operation.
+ */
+struct TALER_EXCHANGE_ManagementWireEnableResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+
+/**
+ * Function called with information about the wire enable operation result.
+ *
+ * @param cls closure
+ * @param wer HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementWireEnableCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementWireEnableResponse *wer);
+
+
+/**
+ * @brief Handle for a POST /management/wire request.
+ */
+struct TALER_EXCHANGE_ManagementWireEnableHandle;
+
+
+/**
+ * Inform the exchange that a wire account should be enabled.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param payto_uri RFC 8905 URI of the exchange's bank account
+ * @param conversion_url URL of the conversion service, or NULL if none
+ * @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec
+ * @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec
+ * @param validity_start when was this decided?
+ * @param master_sig1 signature affirming the wire addition
+ * of purpose #TALER_SIGNATURE_MASTER_ADD_WIRE
+ * @param master_sig2 signature affirming the validity of the account for clients;
+ * of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS.
+ * @param bank_label label to use when showing the account, can be NULL
+ * @param priority priority for ordering the bank accounts
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementWireEnableHandle *
+TALER_EXCHANGE_management_enable_wire (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp validity_start,
+ const struct TALER_MasterSignatureP *master_sig1,
+ const struct TALER_MasterSignatureP *master_sig2,
+ const char *bank_label,
+ int64_t priority,
+ TALER_EXCHANGE_ManagementWireEnableCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_enable_wire() operation.
+ *
+ * @param wh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_enable_wire_cancel (
+ struct TALER_EXCHANGE_ManagementWireEnableHandle *wh);
+
+
+/**
+ * Response from an exchange account/disable operation.
+ */
+struct TALER_EXCHANGE_ManagementWireDisableResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+/**
+ * Function called with information about the wire disable operation result.
+ *
+ * @param cls closure
+ * @param wdr response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementWireDisableCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementWireDisableResponse *wdr);
+
+
+/**
+ * @brief Handle for a POST /management/wire/disable request.
+ */
+struct TALER_EXCHANGE_ManagementWireDisableHandle;
+
+
+/**
+ * Inform the exchange that a wire account should be disabled.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param payto_uri RFC 8905 URI of the exchange's bank account
+ * @param validity_end when was this decided?
+ * @param master_sig signature affirming the wire addition
+ * of purpose #TALER_SIGNATURE_MASTER_DEL_WIRE
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementWireDisableHandle *
+TALER_EXCHANGE_management_disable_wire (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp validity_end,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementWireDisableCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_disable_wire() operation.
+ *
+ * @param wh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_disable_wire_cancel (
+ struct TALER_EXCHANGE_ManagementWireDisableHandle *wh);
+
+
+/**
+ * Response when setting wire fees.
+ */
+struct TALER_EXCHANGE_ManagementSetWireFeeResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+/**
+ * Function called with information about the wire enable operation result.
+ *
+ * @param cls closure
+ * @param wfr response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementSetWireFeeCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementSetWireFeeResponse *wfr);
+
+
+/**
+ * @brief Handle for a POST /management/wire-fees request.
+ */
+struct TALER_EXCHANGE_ManagementSetWireFeeHandle;
+
+
+/**
+ * Inform the exchange about future wire fees.
+ *
+ * @param ctx the context
+ * @param exchange_base_url HTTP base URL for the exchange
+ * @param wire_method for which wire method are fees provided
+ * @param validity_start start date for the provided wire fees
+ * @param validity_end end date for the provided wire fees
+ * @param fees the wire fees for this time period
+ * @param master_sig signature affirming the wire fees;
+ * of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementSetWireFeeHandle *
+TALER_EXCHANGE_management_set_wire_fees (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_base_url,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp validity_start,
+ struct GNUNET_TIME_Timestamp validity_end,
+ const struct TALER_WireFeeSet *fees,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementSetWireFeeCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_enable_wire() operation.
+ *
+ * @param swfh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_set_wire_fees_cancel (
+ struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh);
+
+
+/**
+ * Response when setting global fees.
+ */
+struct TALER_EXCHANGE_ManagementSetGlobalFeeResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+
+/**
+ * Function called with information about the global fee setting operation result.
+ *
+ * @param cls closure
+ * @param gfr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementSetGlobalFeeCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementSetGlobalFeeResponse *gfr);
+
+
+/**
+ * @brief Handle for a POST /management/global-fees request.
+ */
+struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle;
+
+
+/**
+ * Inform the exchange about global fees.
+ *
+ * @param ctx the context
+ * @param exchange_base_url HTTP base URL for the exchange
+ * @param validity_start start date for the provided wire fees
+ * @param validity_end end date for the provided wire fees
+ * @param fees the wire fees for this time period
+ * @param purse_timeout when do purses time out
+ * @param history_expiration how long are account histories preserved
+ * @param purse_account_limit how many purses are free per account
+ * @param master_sig signature affirming the wire fees;
+ * of purpose #TALER_SIGNATURE_MASTER_GLOBAL_FEES
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *
+TALER_EXCHANGE_management_set_global_fees (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_base_url,
+ struct GNUNET_TIME_Timestamp validity_start,
+ struct GNUNET_TIME_Timestamp validity_end,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementSetGlobalFeeCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_enable_wire() operation.
+ *
+ * @param sgfh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_set_global_fees_cancel (
+ struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh);
+
+
+/**
+ * Response when adding denomination signature by auditor.
+ */
+struct TALER_EXCHANGE_AuditorAddDenominationResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+
+/**
+ * Function called with information about the POST
+ * /auditor/$AUDITOR_PUB/$H_DENOM_PUB operation result.
+ *
+ * @param cls closure
+ * @param adr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_AuditorAddDenominationCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_AuditorAddDenominationResponse *adr);
+
+
+/**
+ * @brief Handle for a POST /auditor/$AUDITOR_PUB/$H_DENOM_PUB request.
+ */
+struct TALER_EXCHANGE_AuditorAddDenominationHandle;
+
+
+/**
+ * Provide auditor signatures for a denomination to the exchange.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param h_denom_pub hash of the public key of the denomination
+ * @param auditor_pub public key of the auditor
+ * @param auditor_sig signature of the auditor, of
+ * purpose #TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_AuditorAddDenominationHandle *
+TALER_EXCHANGE_add_auditor_denomination (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_AuditorSignatureP *auditor_sig,
+ TALER_EXCHANGE_AuditorAddDenominationCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_add_auditor_denomination() operation.
+ *
+ * @param ah handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_add_auditor_denomination_cancel (
+ struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah);
+
+
+/* ********************* W2W API ****************** */
+
+
+/**
+ * Response generated for a contract get request.
+ */
+struct TALER_EXCHANGE_ContractGetResponse
+{
+ /**
+ * Full HTTP response.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on the HTTP status code.
+ */
+ union
+ {
+ /**
+ * Information returned on #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Encrypted contract.
+ */
+ const void *econtract;
+
+ /**
+ * Number of bytes in @e econtract.
+ */
+ size_t econtract_size;
+
+ } ok;
+
+ } details;
+
+};
+
+/**
+ * Function called with information about the a purse.
+ *
+ * @param cls closure
+ * @param cgr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ContractGetCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ContractGetResponse *cgr);
+
+
+/**
+ * @brief Handle for a GET /contracts/$CPUB request.
+ */
+struct TALER_EXCHANGE_ContractsGetHandle;
+
+
+/**
+ * Request information about a contract from the exchange.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param contract_priv private key of the contract
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ContractsGetHandle *
+TALER_EXCHANGE_contract_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ TALER_EXCHANGE_ContractGetCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_contract_get() operation.
+ *
+ * @param cgh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_contract_get_cancel (
+ struct TALER_EXCHANGE_ContractsGetHandle *cgh);
+
+
+/**
+ * Response generated for a purse get request.
+ */
+struct TALER_EXCHANGE_PurseGetResponse
+{
+ /**
+ * Full HTTP response.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Response on #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * Time when the purse was merged (or zero if it
+ * was not merged).
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+
+ /**
+ * Time when the full amount was deposited into
+ * the purse (or zero if a sufficient amount
+ * was not yet deposited).
+ */
+ struct GNUNET_TIME_Timestamp deposit_timestamp;
+
+ /**
+ * Reserve balance (how much was deposited in
+ * total into the reserve, minus deposit fees).
+ */
+ struct TALER_Amount balance;
+
+ /**
+ * Time when the purse will expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ } ok;
+
+ } details;
+
+};
+
+
+/**
+ * Function called with information about the a purse.
+ *
+ * @param cls closure
+ * @param pgr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_PurseGetCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_PurseGetResponse *pgr);
+
+
+/**
+ * @brief Handle for a GET /purses/$PPUB request.
+ */
+struct TALER_EXCHANGE_PurseGetHandle;
+
+
+/**
+ * Request information about a purse from the exchange.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param purse_pub public key of the purse
+ * @param timeout how long to wait for a change to happen
+ * @param wait_for_merge true to wait for a merge event, otherwise wait for a deposit event
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_PurseGetHandle *
+TALER_EXCHANGE_purse_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Relative timeout,
+ bool wait_for_merge,
+ TALER_EXCHANGE_PurseGetCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_purse_get() operation.
+ *
+ * @param pgh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_purse_get_cancel (
+ struct TALER_EXCHANGE_PurseGetHandle *pgh);
+
+
+/**
+ * Response generated for a purse creation request.
+ */
+struct TALER_EXCHANGE_PurseCreateDepositResponse
+{
+ /**
+ * Full HTTP response.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on the HTTP status.
+ */
+ union
+ {
+
+ /**
+ * Detailed returned on #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Signing key used by the exchange to sign the
+ * purse create with deposit confirmation.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Signature from the exchange on the
+ * purse create with deposit confirmation.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+
+ } ok;
+
+ } details;
+
+};
+
+/**
+ * Function called with information about the creation
+ * of a new purse.
+ *
+ * @param cls closure
+ * @param pcr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_PurseCreateDepositCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_PurseCreateDepositResponse *pcr);
+
+
+/**
+ * @brief Handle for a POST /purses/$PID/create request.
+ */
+struct TALER_EXCHANGE_PurseCreateDepositHandle;
+
+
+/**
+ * Information about a coin to be deposited into a purse or reserve.
+ */
+struct TALER_EXCHANGE_PurseDeposit
+{
+ /**
+ * Age commitment data, might be NULL.
+ */
+ const struct TALER_AgeCommitmentProof *age_commitment_proof;
+
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Signature proving the validity of the coin.
+ */
+ struct TALER_DenominationSignature denom_sig;
+
+ /**
+ * Hash of the denomination's public key.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Amount of the coin to transfer into the purse.
+ */
+ struct TALER_Amount amount;
+
+};
+
+
+/**
+ * Inform the exchange that a purse should be created
+ * and coins deposited into it.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param purse_priv private key of the purse
+ * @param merge_priv the merge credential
+ * @param contract_priv key needed to obtain and decrypt the contract
+ * @param contract_terms contract the purse is about
+ * @param num_deposits length of the @a deposits array
+ * @param deposits array of deposits to make into the purse
+ * @param upload_contract true to upload the contract; must
+ * be FALSE for repeated calls to this API for the
+ * same purse (i.e. when adding more deposits).
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_PurseCreateDepositHandle *
+TALER_EXCHANGE_purse_create_with_deposit (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ const struct TALER_PurseMergePrivateKeyP *merge_priv,
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const json_t *contract_terms,
+ unsigned int num_deposits,
+ const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
+ bool upload_contract,
+ TALER_EXCHANGE_PurseCreateDepositCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_purse_create_with_deposit() operation.
+ *
+ * @param pch handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_purse_create_with_deposit_cancel (
+ struct TALER_EXCHANGE_PurseCreateDepositHandle *pch);
+
+
+/**
+ * Response generated for a purse deletion request.
+ */
+struct TALER_EXCHANGE_PurseDeleteResponse
+{
+ /**
+ * Full HTTP response.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+
+/**
+ * Function called with information about the deletion
+ * of a purse.
+ *
+ * @param cls closure
+ * @param pdr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_PurseDeleteCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_PurseDeleteResponse *pdr);
+
+
+/**
+ * @brief Handle for a DELETE /purses/$PID request.
+ */
+struct TALER_EXCHANGE_PurseDeleteHandle;
+
+
+/**
+ * Asks the exchange to delete a purse. Will only succeed if
+ * the purse was not yet merged and did not yet time out.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param purse_priv private key of the purse
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_PurseDeleteHandle *
+TALER_EXCHANGE_purse_delete (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ TALER_EXCHANGE_PurseDeleteCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_purse_delete() operation.
+ *
+ * @param pdh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_purse_delete_cancel (
+ struct TALER_EXCHANGE_PurseDeleteHandle *pdh);
+
+
+/**
+ * Response generated for an account merge request.
+ */
+struct TALER_EXCHANGE_AccountMergeResponse
+{
+ /**
+ * Full HTTP response.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Reserve signature affirming the merge.
+ */
+ const struct TALER_ReserveSignatureP *reserve_sig;
+
+ /**
+ * Details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Detailed returned on #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Signature by the exchange affirming the merge.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+ /**
+ * Online signing key used by the exchange.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Timestamp of the exchange for @e exchange_sig.
+ */
+ struct GNUNET_TIME_Timestamp etime;
+
+ } ok;
+
+ /**
+ * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+ */
+ struct
+ {
+ /**
+ * Requirement row target that the merchant should use
+ * to check for its KYC status.
+ */
+ uint64_t requirement_row;
+ } unavailable_for_legal_reasons;
+
+ } details;
+
+};
+
+/**
+ * Function called with information about an account merge
+ * operation.
+ *
+ * @param cls closure
+ * @param amr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_AccountMergeCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_AccountMergeResponse *amr);
+
+
+/**
+ * @brief Handle for a POST /purses/$PID/merge request.
+ */
+struct TALER_EXCHANGE_AccountMergeHandle;
+
+
+/**
+ * Inform the exchange that a purse should be merged
+ * with a reserve.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param reserve_exchange_url base URL of the exchange with the reserve
+ * @param reserve_priv private key of the reserve to merge into
+ * @param purse_pub public key of the purse to merge
+ * @param merge_priv private key granting us the right to merge
+ * @param h_contract_terms hash of the purses' contract
+ * @param min_age minimum age of deposits into the purse
+ * @param purse_value_after_fees amount that should be in the purse
+ * @param purse_expiration when will the purse expire
+ * @param merge_timestamp when is the merge happening (current time)
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_AccountMergeHandle *
+TALER_EXCHANGE_account_merge (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *reserve_exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePrivateKeyP *merge_priv,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint8_t min_age,
+ const struct TALER_Amount *purse_value_after_fees,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ TALER_EXCHANGE_AccountMergeCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_account_merge() operation.
+ *
+ * @param amh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_account_merge_cancel (
+ struct TALER_EXCHANGE_AccountMergeHandle *amh);
+
+
+/**
+ * Response generated for a purse creation request.
+ */
+struct TALER_EXCHANGE_PurseCreateMergeResponse
+{
+ /**
+ * Full HTTP response.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Reserve signature generated for the request
+ * (client-side).
+ */
+ const struct TALER_ReserveSignatureP *reserve_sig;
+
+ /**
+ * Details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Details returned on #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ } ok;
+
+ /**
+ * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+ */
+ struct
+ {
+ /**
+ * Requirement row that the merchant should use
+ * to check for its KYC status.
+ */
+ uint64_t requirement_row;
+ } unavailable_for_legal_reasons;
+
+ } details;
+
+};
+
+/**
+ * Function called with information about the creation
+ * of a new purse.
+ *
+ * @param cls closure
+ * @param pcr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_PurseCreateMergeCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_PurseCreateMergeResponse *pcr);
+
+
+/**
+ * @brief Handle for a POST /reserves/$RID/purse request.
+ */
+struct TALER_EXCHANGE_PurseCreateMergeHandle;
+
+
+/**
+ * Inform the exchange that a purse should be created
+ * and merged with a reserve.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param reserve_priv private key of the reserve
+ * @param purse_priv private key of the purse
+ * @param merge_priv private key of the merge capability
+ * @param contract_priv private key to get the contract
+ * @param contract_terms contract the purse is about
+ * @param upload_contract true to upload the contract
+ * @param pay_for_purse true to pay for purse creation
+ * @param merge_timestamp when should the merge happen (use current time)
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_PurseCreateMergeHandle *
+TALER_EXCHANGE_purse_create_with_merge (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ const struct TALER_PurseMergePrivateKeyP *merge_priv,
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const json_t *contract_terms,
+ bool upload_contract,
+ bool pay_for_purse,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ TALER_EXCHANGE_PurseCreateMergeCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_purse_create_with_merge() operation.
+ *
+ * @param pcm handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_purse_create_with_merge_cancel (
+ struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm);
+
+
+/**
+ * Response generated for purse deposit request.
+ */
+struct TALER_EXCHANGE_PurseDepositResponse
+{
+ /**
+ * Full HTTP response.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Detailed returned on #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * When does the purse expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * How much was actually deposited into the purse.
+ */
+ struct TALER_Amount total_deposited;
+
+ /**
+ * How much should be in the purse in total in the end.
+ */
+ struct TALER_Amount purse_value_after_fees;
+
+ /**
+ * Hash of the contract (needed to verify signature).
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ } ok;
+ } details;
+
+};
+
+/**
+ * Function called with information about a purse-deposit
+ * operation.
+ *
+ * @param cls closure
+ * @param pdr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_PurseDepositCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_PurseDepositResponse *pdr);
+
+
+/**
+ * @brief Handle for a POST /purses/$PID/deposit request.
+ */
+struct TALER_EXCHANGE_PurseDepositHandle;
+
+
+/**
+ * Inform the exchange that a deposit should be made into
+ * a purse.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param purse_exchange_url base URL of the exchange hosting the purse
+ * @param purse_pub public key of the purse to merge
+ * @param min_age minimum age we need to prove for the purse
+ * @param num_deposits length of the @a deposits array
+ * @param deposits array of deposits to make into the purse
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_PurseDepositHandle *
+TALER_EXCHANGE_purse_deposit (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *purse_exchange_url,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ uint8_t min_age,
+ unsigned int num_deposits,
+ const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
+ TALER_EXCHANGE_PurseDepositCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_purse_deposit() operation.
+ *
+ * @param amh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_purse_deposit_cancel (
+ struct TALER_EXCHANGE_PurseDepositHandle *amh);
+
+
+/* ********************* /reserves/$RID/open *********************** */
+
+
+/**
+ * @brief A /reserves/$RID/open Handle
+ */
+struct TALER_EXCHANGE_ReservesOpenHandle;
+
+
+/**
+ * @brief Reserve open result details.
+ */
+struct TALER_EXCHANGE_ReserveOpenResult
+{
+
+ /**
+ * High-level HTTP response details.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on @e hr.http_status.
+ */
+ union
+ {
+
+ /**
+ * Information returned on success, if
+ * @e hr.http_status is #MHD_HTTP_OK
+ */
+ struct
+ {
+ /**
+ * New expiration time
+ */
+ struct GNUNET_TIME_Timestamp expiration_time;
+
+ /**
+ * Actual cost of the open operation.
+ */
+ struct TALER_Amount open_cost;
+
+ } ok;
+
+
+ /**
+ * Information returned if the payment provided is insufficient, if
+ * @e hr.http_status is #MHD_HTTP_PAYMENT_REQUIRED
+ */
+ struct
+ {
+ /**
+ * Current expiration time of the reserve.
+ */
+ struct GNUNET_TIME_Timestamp expiration_time;
+
+ /**
+ * Actual cost of the open operation that should have been paid.
+ */
+ struct TALER_Amount open_cost;
+
+ } payment_required;
+
+ /**
+ * Information returned if status is
+ * #MHD_HTTP_CONFLICT.
+ */
+ struct
+ {
+ /**
+ * Public key of the coin that caused the conflict.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ } conflict;
+
+ /**
+ * Information returned if KYC is required to proceed, set if
+ * @e hr.http_status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+ */
+ struct
+ {
+ /**
+ * Requirement row that the merchant should use
+ * to check for its KYC status.
+ */
+ uint64_t requirement_row;
+
+ /**
+ * Hash of the payto-URI of the account to KYC;
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ } unavailable_for_legal_reasons;
+
+ } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * reserve open request to a exchange.
+ *
+ * @param cls closure
+ * @param ror HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ReservesOpenCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ReserveOpenResult *ror);
+
+
+/**
+ * Submit a request to open a reserve.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param reserve_priv private key of the reserve to open
+ * @param reserve_contribution amount to pay from the reserve's balance for the operation
+ * @param coin_payments_length length of the @a coin_payments array
+ * @param coin_payments array of coin payments to use for opening the reserve
+ * @param expiration_time desired new expiration time for the reserve
+ * @param min_purses minimum number of purses to allow being concurrently opened per reserve
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ * signatures fail to verify). In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_ReservesOpenHandle *
+TALER_EXCHANGE_reserves_open (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_Amount *reserve_contribution,
+ unsigned int coin_payments_length,
+ const struct TALER_EXCHANGE_PurseDeposit coin_payments[
+ static coin_payments_length],
+ struct GNUNET_TIME_Timestamp expiration_time,
+ uint32_t min_purses,
+ TALER_EXCHANGE_ReservesOpenCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel a reserve status request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param[in] roh the reserve open request handle
+ */
+void
+TALER_EXCHANGE_reserves_open_cancel (
+ struct TALER_EXCHANGE_ReservesOpenHandle *roh);
+
+
+/* ********************* /reserves/$RID/attest *********************** */
+
+
+/**
+ * @brief A Get /reserves/$RID/attest Handle
+ */
+struct TALER_EXCHANGE_ReservesGetAttestHandle;
+
+
+/**
+ * @brief Reserve GET attest result details.
+ */
+struct TALER_EXCHANGE_ReserveGetAttestResult
+{
+
+ /**
+ * High-level HTTP response details.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on @e hr.http_status.
+ */
+ union
+ {
+
+ /**
+ * Information returned on success, if
+ * @e hr.http_status is #MHD_HTTP_OK
+ */
+ struct
+ {
+
+ /**
+ * Length of the @e attributes array.
+ */
+ unsigned int attributes_length;
+
+ /**
+ * Array of attributes available about the user.
+ */
+ const char **attributes;
+
+ } ok;
+
+ } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * reserve attest request to a exchange.
+ *
+ * @param cls closure
+ * @param ror HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ReservesGetAttestCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ReserveGetAttestResult *ror);
+
+
+/**
+ * Submit a request to get the list of attestable attributes for a reserve.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param reserve_pub public key of the reserve to get available attributes for
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ * signatures fail to verify). In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_ReservesGetAttestHandle *
+TALER_EXCHANGE_reserves_get_attestable (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ TALER_EXCHANGE_ReservesGetAttestCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel a request to get attestable attributes. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param rgah the reserve get attestable request handle
+ */
+void
+TALER_EXCHANGE_reserves_get_attestable_cancel (
+ struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah);
+
+
+/**
+ * @brief A POST /reserves/$RID/attest Handle
+ */
+struct TALER_EXCHANGE_ReservesPostAttestHandle;
+
+
+/**
+ * @brief Reserve attest result details.
+ */
+struct TALER_EXCHANGE_ReservePostAttestResult
+{
+
+ /**
+ * High-level HTTP response details.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on @e hr.http_status.
+ */
+ union
+ {
+
+ /**
+ * Information returned on success, if
+ * @e hr.http_status is #MHD_HTTP_OK
+ */
+ struct
+ {
+ /**
+ * Time when the exchange made the signature.
+ */
+ struct GNUNET_TIME_Timestamp exchange_time;
+
+ /**
+ * Expiration time of the attested attributes.
+ */
+ struct GNUNET_TIME_Timestamp expiration_time;
+
+ /**
+ * Signature by the exchange affirming the attributes.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+ /**
+ * Online signing key used by the exchange.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Attributes being confirmed by the exchange.
+ */
+ const json_t *attributes;
+
+ } ok;
+
+ } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * reserve attest request to a exchange.
+ *
+ * @param cls closure
+ * @param ror HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ReservesPostAttestCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ReservePostAttestResult *ror);
+
+
+/**
+ * Submit a request to attest attributes about the owner of a reserve.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param keys exchange key data
+ * @param reserve_priv private key of the reserve to attest
+ * @param attributes_length length of the @a attributes array
+ * @param attributes array of names of attributes to get attestations for
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ * signatures fail to verify). In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_ReservesAttestHandle *
+TALER_EXCHANGE_reserves_attest (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ unsigned int attributes_length,
+ const char *attributes[const static attributes_length],
+ TALER_EXCHANGE_ReservesPostAttestCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel a reserve attestation request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param rah the reserve attest request handle
+ */
+void
+TALER_EXCHANGE_reserves_attest_cancel (
+ struct TALER_EXCHANGE_ReservesAttestHandle *rah);
+
+
+/* ********************* /reserves/$RID/close *********************** */
+
+
+/**
+ * @brief A /reserves/$RID/close Handle
+ */
+struct TALER_EXCHANGE_ReservesCloseHandle;
+
+
+/**
+ * @brief Reserve close result details.
+ */
+struct TALER_EXCHANGE_ReserveCloseResult
+{
+
+ /**
+ * High-level HTTP response details.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on @e hr.http_status.
+ */
+ union
+ {
+
+ /**
+ * Information returned on success, if
+ * @e hr.http_status is #MHD_HTTP_OK
+ */
+ struct
+ {
+
+ /**
+ * Amount wired to the target account.
+ */
+ struct TALER_Amount wire_amount;
+ } ok;
+
+ /**
+ * Information returned if KYC is required to proceed, set if
+ * @e hr.http_status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+ */
+ struct
+ {
+ /**
+ * Requirement row that the merchant should use
+ * to check for its KYC status.
+ */
+ uint64_t requirement_row;
+
+ /**
+ * Hash of the payto-URI of the account to KYC;
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ } unavailable_for_legal_reasons;
+
+ } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * reserve close request to a exchange.
+ *
+ * @param cls closure
+ * @param ror HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ReservesCloseCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ReserveCloseResult *ror);
+
+
+/**
+ * Submit a request to close a reserve.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param reserve_priv private key of the reserve to close
+ * @param target_payto_uri where to send the payment, NULL to send to reserve origin
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ * signatures fail to verify). In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_ReservesCloseHandle *
+TALER_EXCHANGE_reserves_close (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const char *target_payto_uri,
+ TALER_EXCHANGE_ReservesCloseCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel a reserve status request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param rch the reserve request handle
+ */
+void
+TALER_EXCHANGE_reserves_close_cancel (
+ struct TALER_EXCHANGE_ReservesCloseHandle *rch);
#endif /* _TALER_EXCHANGE_SERVICE_H */
diff --git a/src/include/taler_exchangedb_lib.h b/src/include/taler_exchangedb_lib.h
index 33ead98bb..d93cf9d6c 100644
--- a/src/include/taler_exchangedb_lib.h
+++ b/src/include/taler_exchangedb_lib.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2017 Taler Systems SA
+ Copyright (C) 2014-2020 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
@@ -27,250 +27,6 @@
#include "taler_exchangedb_plugin.h"
#include "taler_bank_service.h"
-/**
- * Subdirectroy under the exchange's base directory which contains
- * the exchange's signing keys.
- */
-#define TALER_EXCHANGEDB_DIR_SIGNING_KEYS "signkeys"
-
-/**
- * Subdirectory under the exchange's base directory which contains
- * the exchange's denomination keys.
- */
-#define TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS "denomkeys"
-
-
-/**
- * @brief Iterator over signing keys.
- *
- * @param cls closure
- * @param filename name of the file the key came from
- * @param ski the sign key
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-typedef int
-(*TALER_EXCHANGEDB_SigningKeyIterator)(
- void *cls,
- const char *filename,
- const struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP *ski);
-
-
-/**
- * Call @a it for each signing key found in the @a exchange_base_dir.
- *
- * @param exchange_base_dir base directory for the exchange,
- * the signing keys must be in the #TALER_EXCHANGEDB_DIR_SIGNING_KEYS
- * subdirectory
- * @param it function to call on each signing key
- * @param it_cls closure for @a it
- * @return number of files found (may not match
- * number of keys given to @a it as malformed
- * files are simply skipped), -1 on error
- */
-int
-TALER_EXCHANGEDB_signing_keys_iterate (const char *exchange_base_dir,
- TALER_EXCHANGEDB_SigningKeyIterator it,
- void *it_cls);
-
-
-/**
- * Exports a signing key to the given file.
- *
- * @param exchange_base_dir base directory for the keys
- * @param start start time of the validity for the key
- * @param ski the signing key
- * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure.
- */
-int
-TALER_EXCHANGEDB_signing_key_write (
- const char *exchange_base_dir,
- struct GNUNET_TIME_Absolute start,
- const struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP *ski);
-
-
-/**
- * @brief Iterator over denomination keys.
- *
- * @param cls closure
- * @param alias coin alias
- * @param dki the denomination key
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-typedef int
-(*TALER_EXCHANGEDB_DenominationKeyIterator)(
- void *cls,
- const char *alias,
- const struct TALER_EXCHANGEDB_DenominationKey *dki);
-
-
-/**
- * @brief Iterator over revoked denomination keys.
- *
- * @param cls closure
- * @param denom_hash hash of the denomination public key
- * @param revocation_master_sig signature showing @a denom_hash was revoked
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-typedef int
-(*TALER_EXCHANGEDB_RevocationIterator)(
- void *cls,
- const struct GNUNET_HashCode *denom_hash,
- const struct TALER_MasterSignatureP *revocation_master_sig);
-
-
-/**
- * Call @a it for each denomination key found in the @a exchange_base_dir.
- *
- * @param exchange_base_dir base directory for the exchange,
- * the signing keys must be in the #TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS
- * subdirectory
- * @param it function to call on each denomination key found
- * @param it_cls closure for @a it
- * @return -1 on error, 0 if no files were found, otherwise
- * a positive number (however, even with a positive
- * number it is possible that @a it was never called
- * as maybe none of the files were well-formed)
- */
-int
-TALER_EXCHANGEDB_denomination_keys_iterate (
- const char *exchange_base_dir,
- TALER_EXCHANGEDB_DenominationKeyIterator it,
- void *it_cls);
-
-
-/**
- * Call @a it for each revoked denomination key found in the @a revocation_dir.
- *
- * @param revocation_dir base directory where revocations are stored
- * @param master_pub master public key (used to check revocations)
- * @param it function to call on each revoked denomination key found
- * @param it_cls closure for @a it
- * @return -1 on error, 0 if no files were found, otherwise
- * a positive number (however, even with a positive
- * number it is possible that @a it was never called
- * as maybe none of the files were well-formed)
- */
-int
-TALER_EXCHANGEDB_revocations_iterate (
- const char *revocation_dir,
- const struct TALER_MasterPublicKeyP *master_pub,
- TALER_EXCHANGEDB_RevocationIterator it,
- void *it_cls);
-
-
-/**
- * Mark the given denomination key as revoked and request the wallets
- * to initiate recoup.
- *
- * @param revocation_dir where to write the revocation certificate
- * @param denom_hash hash of the denomination key to revoke
- * @param mpriv master private key to sign with
- * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure.
- */
-int
-TALER_EXCHANGEDB_denomination_key_revoke (
- const char *revocation_dir,
- const struct GNUNET_HashCode *denom_hash,
- const struct TALER_MasterPrivateKeyP *mpriv);
-
-
-/**
- * Exports a denomination key to the given file.
- *
- * @param filename the file where to write the denomination key
- * @param dki the denomination key
- * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure.
- */
-int
-TALER_EXCHANGEDB_denomination_key_write (
- const char *filename,
- const struct TALER_EXCHANGEDB_DenominationKey *dki);
-
-
-/**
- * Import a denomination key from the given file.
- *
- * @param filename the file to import the key from
- * @param[out] dki set to the imported denomination key
- * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
- */
-int
-TALER_EXCHANGEDB_denomination_key_read (
- const char *filename,
- struct TALER_EXCHANGEDB_DenominationKey *dki);
-
-
-/**
- * @brief Iterator over auditor information.
- *
- * @param cls closure
- * @param apub the auditor's public key
- * @param auditor_url URL of the auditor
- * @param mpub the exchange's public key (as expected by the auditor)
- * @param dki_len length of @a asig and @a dki arrays
- * @param asigs array of the auditor's signatures over the @a dks, of length @a dki_len
- * @param dki array of denomination coin data signed by the auditor, of length @a dki_len
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to stop iteration with no error,
- * #GNUNET_SYSERR to abort iteration with error!
- */
-typedef int
-(*TALER_EXCHANGEDB_AuditorIterator)(
- void *cls,
- const struct TALER_AuditorPublicKeyP *apub,
- const char *auditor_url,
- const struct TALER_MasterPublicKeyP *mpub,
- unsigned int dki_len,
- const struct TALER_AuditorSignatureP *asigs,
- const struct TALER_DenominationKeyValidityPS *dki);
-
-
-/**
- * Call @a it with information for each auditor found in the
- * directory with auditor information as specified in @a cfg.
- *
- * @param cfg configuration to use
- * @param it function to call with auditor information
- * @param it_cls closure for @a it
- * @return -1 on error, 0 if no files were found, otherwise
- * a positive number (however, even with a positive
- * number it is possible that @a it was never called
- * as maybe none of the files were well-formed)
- */
-int
-TALER_EXCHANGEDB_auditor_iterate (const struct GNUNET_CONFIGURATION_Handle *cfg,
- TALER_EXCHANGEDB_AuditorIterator it,
- void *it_cls);
-
-
-/**
- * Write auditor information to the given file.
- *
- * @param filename the file where to write the auditor information to
- * @param apub the auditor's public key
- * @param auditor_url the URL of the auditor
- * @param asigs the auditor's signatures, array of length @a dki_len
- * @param mpub the exchange's public key (as expected by the auditor)
- * @param dki_len length of @a dki and @a asigs arrays
- * @param dki array of denomination coin data signed by the auditor
- * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure.
- */
-int
-TALER_EXCHANGEDB_auditor_write (
- const char *filename,
- const struct TALER_AuditorPublicKeyP *apub,
- const char *auditor_url,
- const struct TALER_AuditorSignatureP *asigs,
- const struct TALER_MasterPublicKeyP *mpub,
- uint32_t dki_len,
- const struct TALER_DenominationKeyValidityPS *dki);
-
/**
* Initialize the plugin.
@@ -290,99 +46,17 @@ TALER_EXCHANGEDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg);
void
TALER_EXCHANGEDB_plugin_unload (struct TALER_EXCHANGEDB_Plugin *plugin);
-
/**
- * Sorted list of fees to be paid for aggregate wire transfers.
- * Sorted by @e start_date or @e end_date --- both work fine as
- * the resulting order must be the same.
+ * Information about an account from the configuration.
*/
-struct TALER_EXCHANGEDB_AggregateFees
+struct TALER_EXCHANGEDB_AccountInfo
{
/**
- * This is a linked list.
+ * Authentication data. Only parsed if
+ * #TALER_EXCHANGEDB_ALO_AUTHDATA was set.
*/
- struct TALER_EXCHANGEDB_AggregateFees *next;
-
- /**
- * Fee to be paid for wire transfers to a merchant.
- */
- struct TALER_Amount wire_fee;
-
- /**
- * Fee to be paid when we close a reserve and send funds back.
- */
- struct TALER_Amount closing_fee;
-
- /**
- * Time when this fee goes into effect (inclusive)
- */
- struct GNUNET_TIME_Absolute start_date;
-
- /**
- * Time when this fee stops being in effect (exclusive).
- */
- struct GNUNET_TIME_Absolute end_date;
-
- /**
- * Signature affirming the above fee structure.
- */
- struct TALER_MasterSignatureP master_sig;
-};
-
-
-/**
- * Read the current fee structure from disk.
- *
- * @param cfg configuration to use
- * @param wireplugin name of the wire plugin to read fees for
- * @return sorted list of aggregation fees, NULL on error
- */
-struct TALER_EXCHANGEDB_AggregateFees *
-TALER_EXCHANGEDB_fees_read (const struct GNUNET_CONFIGURATION_Handle *cfg,
- const char *wireplugin);
-
-
-/**
- * Convert @a af to @a wf.
- *
- * @param wiremethod name of the wire method the fees are for
- * @param[in,out] af aggregate fees, host format (updated to round time)
- * @param[out] wf aggregate fees, disk / signature format
- */
-void
-TALER_EXCHANGEDB_fees_2_wf (const char *wiremethod,
- struct TALER_EXCHANGEDB_AggregateFees *af,
- struct TALER_MasterWireFeePS *wf);
-
-
-/**
- * Write given fee structure to disk.
- *
- * @param filename where to write the fees
- * @param wireplugin name of the plugin for which we write the fees
- * @param af fee structure to write
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
- */
-int
-TALER_EXCHANGEDB_fees_write (const char *filename,
- const char *wireplugin,
- struct TALER_EXCHANGEDB_AggregateFees *af);
-
-
-/**
- * Free @a af data structure
- *
- * @param af list to free
- */
-void
-TALER_EXCHANGEDB_fees_free (struct TALER_EXCHANGEDB_AggregateFees *af);
+ const struct TALER_BANK_AuthenticationData *auth;
-
-/**
- * Information about an account from the configuration.
- */
-struct TALER_EXCHANGEDB_AccountInfo
-{
/**
* Section in the configuration file that specifies the
* account. Must start with "exchange-account-".
@@ -395,57 +69,22 @@ struct TALER_EXCHANGEDB_AccountInfo
const char *method;
/**
- * payto://-URL of the account.
- */
- const char *payto_uri;
-
- /**
- * Filename containing the signed /wire response, or NULL
- * if not given.
- */
- const char *wire_response_filename;
-
- /**
- * #GNUNET_YES if this account is enabed to be debited
+ * true if this account is enabled to be debited
* by the taler-exchange-aggregator.
*/
- int debit_enabled;
+ bool debit_enabled;
/**
- * #GNUNET_YES if this account is enabed to be credited by wallets
+ * true if this account is enabled to be credited by wallets
* and needs to be watched by the taler-exchange-wirewatch.
* Also, the account will only be included in /wire if credit
* is enabled.
*/
- int credit_enabled;
+ bool credit_enabled;
};
/**
- * Function called with information about a wire account.
- *
- * @param cls closure
- * @param ai account information
- */
-typedef void
-(*TALER_EXCHANGEDB_AccountCallback)(
- void *cls,
- const struct TALER_EXCHANGEDB_AccountInfo *ai);
-
-/**
- * Parse the configuration to find account information.
- *
- * @param cfg configuration to use
- * @param cb callback to invoke
- * @param cb_cls closure for @a cb
- */
-void
-TALER_EXCHANGEDB_find_accounts (const struct GNUNET_CONFIGURATION_Handle *cfg,
- TALER_EXCHANGEDB_AccountCallback cb,
- void *cb_cls);
-
-
-/**
* Calculate the total value of all transactions performed.
* Stores @a off plus the cost of all transactions in @a tl
* in @a ret.
@@ -455,69 +94,35 @@ TALER_EXCHANGEDB_find_accounts (const struct GNUNET_CONFIGURATION_Handle *cfg,
* @param[out] ret where the resulting total is to be stored
* @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
*/
-int
+enum GNUNET_GenericReturnValue
TALER_EXCHANGEDB_calculate_transaction_list_totals (
struct TALER_EXCHANGEDB_TransactionList *tl,
const struct TALER_Amount *off,
struct TALER_Amount *ret);
-/* ***************** convenience functions ******** */
-
/**
- * Information we keep for each supported account of the exchange.
+ * Function called with information about a wire account.
+ *
+ * @param cls closure
+ * @param ai account information
*/
-struct TALER_EXCHANGEDB_WireAccount
-{
- /**
- * Accounts are kept in a DLL.
- */
- struct TALER_EXCHANGEDB_WireAccount *next;
-
- /**
- * Plugins are kept in a DLL.
- */
- struct TALER_EXCHANGEDB_WireAccount *prev;
-
- /**
- * Authentication data.
- */
- struct TALER_BANK_AuthenticationData auth;
-
- /**
- * Wire transfer fee structure.
- */
- struct TALER_EXCHANGEDB_AggregateFees *af;
-
- /**
- * Name of the section that configures this account.
- */
- char *section_name;
-
- /**
- * Name of the wire method underlying the account.
- */
- char *method;
-
-};
+typedef void
+(*TALER_EXCHANGEDB_AccountCallback)(
+ void *cls,
+ const struct TALER_EXCHANGEDB_AccountInfo *ai);
/**
- * Update wire transfer fee data structure in @a wa.
+ * Return information about all accounts that
+ * were loaded by #TALER_EXCHANGEDB_load_accounts().
*
- * @param cfg configuration to use
- * @param db_plugin database plugin to use
- * @param wa wire account data structure to update
- * @param now timestamp to update fees to
- * @param session DB session to use
- * @return fee valid at @a now, or NULL if unknown
+ * @param cb callback to invoke
+ * @param cb_cls closure for @a cb
*/
-struct TALER_EXCHANGEDB_AggregateFees *
-TALER_EXCHANGEDB_update_fees (const struct GNUNET_CONFIGURATION_Handle *cfg,
- struct TALER_EXCHANGEDB_Plugin *db_plugin,
- struct TALER_EXCHANGEDB_WireAccount *wa,
- struct GNUNET_TIME_Absolute now,
- struct TALER_EXCHANGEDB_Session *session);
+void
+TALER_EXCHANGEDB_find_accounts (TALER_EXCHANGEDB_AccountCallback cb,
+ void *cb_cls);
/**
@@ -528,7 +133,7 @@ TALER_EXCHANGEDB_update_fees (const struct GNUNET_CONFIGURATION_Handle *cfg,
* @param method wire method we need an account for
* @return NULL on error
*/
-struct TALER_EXCHANGEDB_WireAccount *
+const struct TALER_EXCHANGEDB_AccountInfo *
TALER_EXCHANGEDB_find_account_by_method (const char *method);
@@ -540,19 +145,48 @@ TALER_EXCHANGEDB_find_account_by_method (const char *method);
* @param url wire address we need an account for
* @return NULL on error
*/
-struct TALER_EXCHANGEDB_WireAccount *
+const struct TALER_EXCHANGEDB_AccountInfo *
TALER_EXCHANGEDB_find_account_by_payto_uri (const char *url);
/**
+ * Options for #TALER_EXCHANGEDB_load_accounts()
+ */
+enum TALER_EXCHANGEDB_AccountLoaderOptions
+{
+ TALER_EXCHANGEDB_ALO_NONE = 0,
+
+ /**
+ * Load accounts enabled for DEBITs.
+ */
+ TALER_EXCHANGEDB_ALO_DEBIT = 1,
+
+ /**
+ * Load accounts enabled for CREDITs.
+ */
+ TALER_EXCHANGEDB_ALO_CREDIT = 2,
+
+ /**
+ * Load authentication data from the
+ * "taler-accountcredentials-" section
+ * to access the account at the bank.
+ */
+ TALER_EXCHANGEDB_ALO_AUTHDATA = 4
+};
+
+
+/**
* Load account information opf the exchange from
* @a cfg.
*
* @param cfg configuration to load from
+ * @param options loader options
* @return #GNUNET_OK on success, #GNUNET_NO if no accounts are configured
*/
-int
-TALER_EXCHANGEDB_load_accounts (const struct GNUNET_CONFIGURATION_Handle *cfg);
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGEDB_load_accounts (
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ enum TALER_EXCHANGEDB_AccountLoaderOptions options);
/**
diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h
index 4fd580724..2d5857677 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
+ Copyright (C) 2014-2024 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
@@ -18,47 +18,89 @@
* @brief Low-level (statement-level) database access for the exchange
* @author Florian Dold
* @author Christian Grothoff
+ * @author Özgür Kesim
*/
#ifndef TALER_EXCHANGEDB_PLUGIN_H
#define TALER_EXCHANGEDB_PLUGIN_H
#include <jansson.h>
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_db_lib.h>
+#include "taler_json_lib.h"
#include "taler_signatures.h"
+#include "taler_extensions_policy.h"
+/**
+ * The conflict that can occur for the age restriction
+ */
+enum TALER_EXCHANGEDB_AgeCommitmentHash_Conflict
+{
+ /**
+ * Value OK, no conflict
+ */
+ TALER_AgeCommitmentHash_NoConflict = 0,
-GNUNET_NETWORK_STRUCT_BEGIN
+ /**
+ * Given hash had a value, but NULL (or zero) was expected
+ */
+ TALER_AgeCommitmentHash_NullExpected = 1,
+
+ /**
+ * Given hash was NULL, but value was expected
+ */
+ TALER_AgeCommitmentHash_ValueExpected = 2,
+
+ /**
+ * Given hash differs from value in the known coin
+ */
+ TALER_AgeCommitmentHash_ValueDiffers = 3,
+};
/**
- * @brief On disk format used for a exchange signing key. Signing keys are used
- * by the exchange to affirm its messages, but not to create coins.
- * Includes the private key followed by the public information about
- * the signing key.
+ * Per-coin information returned when doing a batch insert.
*/
-struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP
+struct TALER_EXCHANGEDB_CoinInfo
{
/**
- * Private key part of the exchange's signing key.
+ * Row of the coin in the known_coins table.
+ */
+ uint64_t known_coin_id;
+
+ /**
+ * Hash of the denomination, relevant on @e denom_conflict.
*/
- struct TALER_ExchangePrivateKeyP signkey_priv;
+ struct TALER_DenominationHashP denom_hash;
/**
- * Signature over @e issue
+ * Hash of the age commitment, relevant on @e age_conflict.
*/
- struct TALER_MasterSignatureP master_sig;
+ struct TALER_AgeCommitmentHash h_age_commitment;
/**
- * Public information about a exchange signing key.
+ * True if the coin was known previously.
*/
- struct TALER_ExchangeSigningKeyValidityPS issue;
+ bool existed;
+ /**
+ * True if the known coin has a different denomination;
+ * application will find denomination of the already
+ * known coin in @e denom_hash.
+ */
+ bool denom_conflict;
+
+ /**
+ * Indicates if and what kind of conflict with the age
+ * restriction of the known coin was present;
+ * application will find age commitment of the already
+ * known coin in @e h_age_commitment.
+ */
+ enum TALER_EXCHANGEDB_AgeCommitmentHash_Conflict age_conflict;
};
/**
* Information about a denomination key.
*/
-struct TALER_EXCHANGEDB_DenominationKeyInformationP
+struct TALER_EXCHANGEDB_DenominationKeyInformation
{
/**
@@ -67,14 +109,732 @@ struct TALER_EXCHANGEDB_DenominationKeyInformationP
struct TALER_MasterSignatureP signature;
/**
- * Signed properties of the denomination key.
+ * Start time of the validity period for this key.
+ */
+ struct GNUNET_TIME_Timestamp start;
+
+ /**
+ * The exchange will sign fresh coins between @e start and this time.
+ * @e expire_withdraw will be somewhat larger than @e start to
+ * ensure a sufficiently large anonymity set, while also allowing
+ * the Exchange to limit the financial damage in case of a key being
+ * compromised. Thus, exchanges with low volume are expected to have a
+ * longer withdraw period (@e expire_withdraw - @e start) than exchanges
+ * with high transaction volume. The period may also differ between
+ * types of coins. A exchange may also have a few denomination keys
+ * with the same value with overlapping validity periods, to address
+ * issues such as clock skew.
+ */
+ struct GNUNET_TIME_Timestamp expire_withdraw;
+
+ /**
+ * Coins signed with the denomination key must be spent or refreshed
+ * between @e start and this expiration time. After this time, the
+ * exchange will refuse transactions involving this key as it will
+ * "drop" the table with double-spending information (shortly after)
+ * this time. Note that wallets should refresh coins significantly
+ * before this time to be on the safe side. @e expire_deposit must be
+ * significantly larger than @e expire_withdraw (by months or even
+ * years).
+ */
+ struct GNUNET_TIME_Timestamp expire_deposit;
+
+ /**
+ * When do signatures with this denomination key become invalid?
+ * After this point, these signatures cannot be used in (legal)
+ * disputes anymore, as the Exchange is then allowed to destroy its side
+ * of the evidence. @e expire_legal is expected to be significantly
+ * larger than @e expire_deposit (by a year or more).
+ */
+ struct GNUNET_TIME_Timestamp expire_legal;
+
+ /**
+ * The value of the coins signed with this denomination key.
+ */
+ struct TALER_Amount value;
+
+ /**
+ * Fees for the coin.
+ */
+ struct TALER_DenomFeeSet fees;
+
+ /**
+ * Hash code of the denomination public key. (Used to avoid having
+ * the variable-size RSA key in this struct.)
+ */
+ struct TALER_DenominationHashP denom_hash;
+
+ /**
+ * If denomination was setup for age restriction, non-zero age mask.
+ * Note that the mask is not part of the signature.
+ */
+ struct TALER_AgeMask age_mask;
+};
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Events signalling that a coin deposit status
+ * changed.
+ */
+struct TALER_CoinDepositEventP
+{
+ /**
+ * Of type #TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED.
+ */
+ struct GNUNET_DB_EventHeaderP header;
+
+ /**
+ * Public key of the merchant.
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+
+};
+
+/**
+ * Events signalling a reserve got funding.
+ */
+struct TALER_ReserveEventP
+{
+ /**
+ * Of type #TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING.
+ */
+ struct GNUNET_DB_EventHeaderP header;
+
+ /**
+ * Public key of the reserve the event is about.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+};
+
+
+/**
+ * Signature of events signalling a purse changed its status.
+ */
+struct TALER_PurseEventP
+{
+ /**
+ * Of type #TALER_DBEVENT_EXCHANGE_PURSE_MERGED or
+ * #TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED.
+ */
+ struct GNUNET_DB_EventHeaderP header;
+
+ /**
+ * Public key of the purse the event is about.
*/
- struct TALER_DenominationKeyValidityPS properties;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+};
+
+
+/**
+ * Signature of events signalling a KYC process was completed.
+ */
+struct TALER_KycCompletedEventP
+{
+ /**
+ * Of type #TALER_DBEVENT_EXCHANGE_KYC_COMPLETED.
+ */
+ struct GNUNET_DB_EventHeaderP header;
+
+ /**
+ * Public key of the reserve the event is about.
+ */
+ struct TALER_PaytoHashP h_payto;
};
GNUNET_NETWORK_STRUCT_END
+/**
+ * Meta data about an exchange online signing key.
+ */
+struct TALER_EXCHANGEDB_SignkeyMetaData
+{
+ /**
+ * Start time of the validity period for this key.
+ */
+ struct GNUNET_TIME_Timestamp start;
+
+ /**
+ * The exchange will sign messages with this key between @e start and this time.
+ */
+ struct GNUNET_TIME_Timestamp expire_sign;
+
+ /**
+ * When do signatures with this sign key become invalid?
+ * After this point, these signatures cannot be used in (legal)
+ * disputes anymore, as the Exchange is then allowed to destroy its side
+ * of the evidence. @e expire_legal is expected to be significantly
+ * larger than @e expire_sign (by a year or more).
+ */
+ struct GNUNET_TIME_Timestamp expire_legal;
+
+};
+
+
+/**
+ * Enumeration of all of the tables replicated by exchange-auditor
+ * database replication.
+ */
+enum TALER_EXCHANGEDB_ReplicatedTable
+{
+ /* From exchange-0002.sql: */
+ TALER_EXCHANGEDB_RT_DENOMINATIONS,
+ TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS,
+ TALER_EXCHANGEDB_RT_WIRE_TARGETS,
+ TALER_EXCHANGEDB_RT_LEGITIMIZATION_PROCESSES,
+ TALER_EXCHANGEDB_RT_LEGITIMIZATION_REQUIREMENTS,
+ TALER_EXCHANGEDB_RT_RESERVES,
+ TALER_EXCHANGEDB_RT_RESERVES_IN,
+ TALER_EXCHANGEDB_RT_RESERVES_CLOSE,
+ TALER_EXCHANGEDB_RT_RESERVES_OPEN_REQUESTS,
+ TALER_EXCHANGEDB_RT_RESERVES_OPEN_DEPOSITS,
+ TALER_EXCHANGEDB_RT_RESERVES_OUT,
+ TALER_EXCHANGEDB_RT_AUDITORS,
+ TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS,
+ TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS,
+ TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS,
+ TALER_EXCHANGEDB_RT_KNOWN_COINS,
+ TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS,
+ TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS,
+ TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS,
+ TALER_EXCHANGEDB_RT_BATCH_DEPOSITS,
+ TALER_EXCHANGEDB_RT_COIN_DEPOSITS,
+ TALER_EXCHANGEDB_RT_REFUNDS,
+ TALER_EXCHANGEDB_RT_WIRE_OUT,
+ TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING,
+ TALER_EXCHANGEDB_RT_WIRE_FEE,
+ TALER_EXCHANGEDB_RT_GLOBAL_FEE,
+ TALER_EXCHANGEDB_RT_RECOUP,
+ TALER_EXCHANGEDB_RT_RECOUP_REFRESH,
+ TALER_EXCHANGEDB_RT_EXTENSIONS,
+ TALER_EXCHANGEDB_RT_POLICY_DETAILS,
+ TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS,
+ TALER_EXCHANGEDB_RT_PURSE_REQUESTS,
+ TALER_EXCHANGEDB_RT_PURSE_DECISION,
+ TALER_EXCHANGEDB_RT_PURSE_MERGES,
+ TALER_EXCHANGEDB_RT_PURSE_DEPOSITS,
+ TALER_EXCHANGEDB_RT_ACCOUNT_MERGES,
+ TALER_EXCHANGEDB_RT_HISTORY_REQUESTS,
+ TALER_EXCHANGEDB_RT_CLOSE_REQUESTS,
+ TALER_EXCHANGEDB_RT_WADS_OUT,
+ TALER_EXCHANGEDB_RT_WADS_OUT_ENTRIES,
+ TALER_EXCHANGEDB_RT_WADS_IN,
+ TALER_EXCHANGEDB_RT_WADS_IN_ENTRIES,
+ TALER_EXCHANGEDB_RT_PROFIT_DRAINS,
+ /* From exchange-0003.sql: */
+ TALER_EXCHANGEDB_RT_AML_STAFF,
+ TALER_EXCHANGEDB_RT_AML_HISTORY,
+ TALER_EXCHANGEDB_RT_KYC_ATTRIBUTES,
+ TALER_EXCHANGEDB_RT_PURSE_DELETION,
+ TALER_EXCHANGEDB_RT_AGE_WITHDRAW,
+};
+
+
+/**
+ * Record of a single entry in a replicated table.
+ */
+struct TALER_EXCHANGEDB_TableData
+{
+ /**
+ * Data of which table is returned here?
+ */
+ enum TALER_EXCHANGEDB_ReplicatedTable table;
+
+ /**
+ * Serial number of the record.
+ */
+ uint64_t serial;
+
+ /**
+ * Table-specific details.
+ */
+ union
+ {
+
+ /**
+ * Details from the 'denominations' table.
+ */
+ struct
+ {
+ uint32_t denom_type;
+ uint32_t age_mask;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_TIME_Timestamp valid_from;
+ struct GNUNET_TIME_Timestamp expire_withdraw;
+ struct GNUNET_TIME_Timestamp expire_deposit;
+ struct GNUNET_TIME_Timestamp expire_legal;
+ struct TALER_Amount coin;
+ struct TALER_DenomFeeSet fees;
+ } denominations;
+
+ struct
+ {
+ struct TALER_MasterSignatureP master_sig;
+ uint64_t denominations_serial;
+ } denomination_revocations;
+
+ struct
+ {
+ char *payto_uri;
+ } wire_targets;
+
+ struct
+ {
+ struct TALER_PaytoHashP h_payto;
+ struct GNUNET_TIME_Timestamp expiration_time;
+ char *provider_section;
+ char *provider_user_id;
+ char *provider_legitimization_id;
+ } legitimization_processes;
+
+ struct
+ {
+ struct TALER_PaytoHashP h_payto;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ bool no_reserve_pub;
+ char *required_checks;
+ } legitimization_requirements;
+
+ struct
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct GNUNET_TIME_Timestamp expiration_date;
+ struct GNUNET_TIME_Timestamp gc_date;
+ } reserves;
+
+ struct
+ {
+ uint64_t wire_reference;
+ struct TALER_Amount credit;
+ struct TALER_PaytoHashP sender_account_h_payto;
+ char *exchange_account_section;
+ struct GNUNET_TIME_Timestamp execution_date;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ } reserves_in;
+
+ struct
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct GNUNET_TIME_Timestamp request_timestamp;
+ struct GNUNET_TIME_Timestamp expiration_date;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct TALER_Amount reserve_payment;
+ uint32_t requested_purse_limit;
+ } reserves_open_requests;
+
+ struct
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct TALER_Amount contribution;
+ } reserves_open_deposits;
+
+ struct
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct GNUNET_TIME_Timestamp execution_date;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct TALER_PaytoHashP sender_account_h_payto;
+ struct TALER_Amount amount;
+ struct TALER_Amount closing_fee;
+ } reserves_close;
+
+ struct
+ {
+ struct TALER_BlindedCoinHashP h_blind_ev;
+ uint64_t denominations_serial;
+ struct TALER_BlindedDenominationSignature denom_sig;
+ uint64_t reserve_uuid;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct GNUNET_TIME_Timestamp execution_date;
+ struct TALER_Amount amount_with_fee;
+ } reserves_out;
+
+ struct
+ {
+ struct TALER_AuditorPublicKeyP auditor_pub;
+ char *auditor_url;
+ char *auditor_name;
+ bool is_active;
+ struct GNUNET_TIME_Timestamp last_change;
+ } auditors;
+
+ struct
+ {
+ uint64_t auditor_uuid;
+ uint64_t denominations_serial;
+ struct TALER_AuditorSignatureP auditor_sig;
+ } auditor_denom_sigs;
+
+ struct
+ {
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_MasterSignatureP master_sig;
+ struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+ } exchange_sign_keys;
+
+ struct
+ {
+ uint64_t esk_serial;
+ struct TALER_MasterSignatureP master_sig;
+ } signkey_revocations;
+
+ struct
+ {
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_AgeCommitmentHash age_hash;
+ uint64_t denominations_serial;
+ struct TALER_DenominationSignature denom_sig;
+ } known_coins;
+
+ struct
+ {
+ struct TALER_RefreshCommitmentP rc;
+ struct TALER_CoinSpendPublicKeyP old_coin_pub;
+ struct TALER_CoinSpendSignatureP old_coin_sig;
+ struct TALER_Amount amount_with_fee;
+ uint32_t noreveal_index;
+ } refresh_commitments;
+
+ struct
+ {
+ uint64_t melt_serial_id;
+ uint32_t freshcoin_index;
+ struct TALER_CoinSpendSignatureP link_sig;
+ uint64_t denominations_serial;
+ void *coin_ev;
+ size_t coin_ev_size;
+ struct TALER_ExchangeWithdrawValues ewv;
+ // h_coin_ev omitted, to be recomputed!
+ struct TALER_BlindedDenominationSignature ev_sig;
+ } refresh_revealed_coins;
+
+ struct
+ {
+ uint64_t melt_serial_id;
+ struct TALER_TransferPublicKeyP tp;
+ struct TALER_TransferPrivateKeyP tprivs[TALER_CNC_KAPPA - 1];
+ } refresh_transfer_keys;
+
+ struct
+ {
+ uint64_t shard;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_TIME_Timestamp wallet_timestamp;
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+ struct GNUNET_TIME_Timestamp refund_deadline;
+ struct GNUNET_TIME_Timestamp wire_deadline;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct GNUNET_HashCode wallet_data_hash;
+ bool no_wallet_data_hash;
+ struct TALER_WireSaltP wire_salt;
+ struct TALER_PaytoHashP wire_target_h_payto;
+ bool policy_blocked;
+ uint64_t policy_details_serial_id;
+ bool no_policy_details;
+ } batch_deposits;
+
+ struct
+ {
+ uint64_t batch_deposit_serial_id;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_Amount amount_with_fee;
+ } coin_deposits;
+
+ struct
+ {
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ uint64_t batch_deposit_serial_id;
+ struct TALER_MerchantSignatureP merchant_sig;
+ uint64_t rtransaction_id;
+ struct TALER_Amount amount_with_fee;
+ } refunds;
+
+ struct
+ {
+ struct GNUNET_TIME_Timestamp execution_date;
+ struct TALER_WireTransferIdentifierRawP wtid_raw;
+ struct TALER_PaytoHashP wire_target_h_payto;
+ char *exchange_account_section;
+ struct TALER_Amount amount;
+ } wire_out;
+
+ struct
+ {
+ uint64_t batch_deposit_serial_id;
+ struct TALER_WireTransferIdentifierRawP wtid_raw;
+ } aggregation_tracking;
+
+ struct
+ {
+ char *wire_method;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct TALER_WireFeeSet fees;
+ struct TALER_MasterSignatureP master_sig;
+ } wire_fee;
+
+ struct
+ {
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct TALER_GlobalFeeSet fees;
+ struct GNUNET_TIME_Relative purse_timeout;
+ struct GNUNET_TIME_Relative history_expiration;
+ uint32_t purse_account_limit;
+ struct TALER_MasterSignatureP master_sig;
+ } global_fee;
+
+ struct
+ {
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
+ struct TALER_Amount amount;
+ struct GNUNET_TIME_Timestamp timestamp;
+ uint64_t reserve_out_serial_id;
+ } recoup;
+
+ struct
+ {
+ uint64_t known_coin_id;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
+ struct TALER_Amount amount;
+ struct GNUNET_TIME_Timestamp timestamp;
+ uint64_t rrc_serial;
+ } recoup_refresh;
+
+ struct
+ {
+ char *name;
+ char *manifest;
+ } extensions;
+
+ struct
+ {
+ struct GNUNET_HashCode hash_code;
+ json_t *policy_json;
+ bool no_policy_json;
+ struct GNUNET_TIME_Timestamp deadline;
+ struct TALER_Amount commitment;
+ struct TALER_Amount accumulated_total;
+ struct TALER_Amount fee;
+ struct TALER_Amount transferable;
+ uint16_t fulfillment_state; /* will also be recomputed */
+ uint64_t fulfillment_id;
+ bool no_fulfillment_id;
+ } policy_details;
+
+ struct
+ {
+ struct GNUNET_TIME_Timestamp fulfillment_timestamp;
+ char *fulfillment_proof;
+ struct GNUNET_HashCode h_fulfillment_proof;
+ struct GNUNET_HashCode *policy_hash_codes;
+ size_t policy_hash_codes_count;
+ } policy_fulfillments;
+
+ struct
+ {
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PurseMergePublicKeyP merge_pub;
+ struct GNUNET_TIME_Timestamp purse_creation;
+ struct GNUNET_TIME_Timestamp purse_expiration;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ uint32_t age_limit;
+ uint32_t flags;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount purse_fee;
+ struct TALER_PurseContractSignatureP purse_sig;
+ } purse_requests;
+
+ struct
+ {
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct GNUNET_TIME_Timestamp action_timestamp;
+ bool refunded;
+ } purse_decision;
+
+ struct
+ {
+ uint64_t partner_serial_id;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PurseMergeSignatureP merge_sig;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ } purse_merges;
+
+ struct
+ {
+ uint64_t partner_serial_id;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ } purse_deposits;
+
+ struct
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PaytoHashP wallet_h_payto;
+ } account_merges;
+
+ struct
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct GNUNET_TIME_Timestamp request_timestamp;
+ struct TALER_Amount history_fee;
+ } history_requests;
+
+ struct
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct GNUNET_TIME_Timestamp close_timestamp;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct TALER_Amount close;
+ struct TALER_Amount close_fee;
+ char *payto_uri;
+ } close_requests;
+
+ struct
+ {
+ struct TALER_WadIdentifierP wad_id;
+ uint64_t partner_serial_id;
+ struct TALER_Amount amount;
+ struct GNUNET_TIME_Timestamp execution_time;
+ } wads_out;
+
+ struct
+ {
+ uint64_t wad_out_serial_id;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PrivateContractHashP h_contract;
+ struct GNUNET_TIME_Timestamp purse_expiration;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount wad_fee;
+ struct TALER_Amount deposit_fees;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct TALER_PurseContractSignatureP purse_sig;
+ } wads_out_entries;
+
+ struct
+ {
+ struct TALER_WadIdentifierP wad_id;
+ char *origin_exchange_url;
+ struct TALER_Amount amount;
+ struct GNUNET_TIME_Timestamp arrival_time;
+ } wads_in;
+
+ struct
+ {
+ uint64_t wad_in_serial_id;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PrivateContractHashP h_contract;
+ struct GNUNET_TIME_Timestamp purse_expiration;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount wad_fee;
+ struct TALER_Amount deposit_fees;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct TALER_PurseContractSignatureP purse_sig;
+ } wads_in_entries;
+
+ struct
+ {
+ struct TALER_WireTransferIdentifierRawP wtid;
+ char *account_section;
+ char *payto_uri;
+ struct GNUNET_TIME_Timestamp trigger_date;
+ struct TALER_Amount amount;
+ struct TALER_MasterSignatureP master_sig;
+ } profit_drains;
+
+ struct
+ {
+ struct TALER_AmlOfficerPublicKeyP decider_pub;
+ struct TALER_MasterSignatureP master_sig;
+ char *decider_name;
+ bool is_active;
+ bool read_only;
+ struct GNUNET_TIME_Timestamp last_change;
+ } aml_staff;
+
+ struct
+ {
+ struct TALER_PaytoHashP h_payto;
+ struct TALER_Amount new_threshold;
+ enum TALER_AmlDecisionState new_status;
+ struct GNUNET_TIME_Timestamp decision_time;
+ char *justification;
+ char *kyc_requirements; /* NULL allowed! */
+ uint64_t kyc_req_row;
+ struct TALER_AmlOfficerPublicKeyP decider_pub;
+ struct TALER_AmlOfficerSignatureP decider_sig;
+ } aml_history;
+
+ struct
+ {
+ struct TALER_PaytoHashP h_payto;
+ struct GNUNET_ShortHashCode kyc_prox;
+ char *provider;
+ struct GNUNET_TIME_Timestamp collection_time;
+ struct GNUNET_TIME_Timestamp expiration_time;
+ void *encrypted_attributes;
+ size_t encrypted_attributes_size;
+ } kyc_attributes;
+
+ struct
+ {
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PurseContractSignatureP purse_sig;
+ } purse_deletion;
+
+ struct
+ {
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+ struct TALER_Amount amount_with_fee;
+ uint16_t max_age;
+ uint32_t noreveal_index;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_ReserveSignatureP reserve_sig;
+ uint64_t num_coins;
+ uint64_t *denominations_serials;
+ void *h_blind_evs;
+ struct TALER_BlindedDenominationSignature denom_sigs;
+ } age_withdraw;
+
+ } details;
+
+};
+
+
+/**
+ * Function called on data to replicate in the auditor's database.
+ *
+ * @param cls closure
+ * @param td record from an exchange table
+ * @return #GNUNET_OK to continue to iterate,
+ * #GNUNET_SYSERR to fail with an error
+ */
+typedef int
+(*TALER_EXCHANGEDB_ReplicationCallback)(
+ void *cls,
+ const struct TALER_EXCHANGEDB_TableData *td);
+
/**
* @brief All information about a denomination key (which is used to
@@ -98,7 +858,7 @@ struct TALER_EXCHANGEDB_DenominationKey
/**
* Signed public information about a denomination key.
*/
- struct TALER_EXCHANGEDB_DenominationKeyInformationP issue;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
};
@@ -123,7 +883,7 @@ struct TALER_EXCHANGEDB_BankTransfer
* (This is the execution date of the exchange's database,
* the execution date of the bank should be in @e wire).
*/
- struct GNUNET_TIME_Absolute execution_date;
+ struct GNUNET_TIME_Timestamp execution_date;
/**
* Detailed wire information about the sending account
@@ -134,12 +894,7 @@ struct TALER_EXCHANGEDB_BankTransfer
/**
* Data uniquely identifying the wire transfer (wire transfer-type specific)
*/
- void *wire_reference;
-
- /**
- * Number of bytes in @e wire_reference.
- */
- size_t wire_reference_size;
+ uint64_t wire_reference;
};
@@ -157,7 +912,7 @@ struct TALER_EXCHANGEDB_ClosingTransfer
struct TALER_ReservePublicKeyP reserve_pub;
/**
- * Amount that was transferred to the exchange.
+ * Amount that was transferred from the exchange.
*/
struct TALER_Amount amount;
@@ -169,7 +924,7 @@ struct TALER_EXCHANGEDB_ClosingTransfer
/**
* When did the exchange execute the transaction?
*/
- struct GNUNET_TIME_Absolute execution_date;
+ struct GNUNET_TIME_Timestamp execution_date;
/**
* Detailed wire information about the receiving account
@@ -205,17 +960,203 @@ struct TALER_EXCHANGEDB_Reserve
* The expiration date of this reserve; funds will be wired back
* at this time.
*/
- struct GNUNET_TIME_Absolute expiry;
+ struct GNUNET_TIME_Timestamp expiry;
/**
* The legal expiration date of this reserve; we will forget about
* it at this time.
*/
- struct GNUNET_TIME_Absolute gc;
+ struct GNUNET_TIME_Timestamp gc;
+};
+
+
+/**
+ * Meta data about a denomination public key.
+ */
+struct TALER_EXCHANGEDB_DenominationKeyMetaData
+{
+ /**
+ * Serial of the denomination key as in the DB.
+ * Can be used in calls to stored procedures in order to spare
+ * additional lookups.
+ */
+ uint64_t serial;
+
+ /**
+ * Start time of the validity period for this key.
+ */
+ struct GNUNET_TIME_Timestamp start;
+
+ /**
+ * The exchange will sign fresh coins between @e start and this time.
+ * @e expire_withdraw will be somewhat larger than @e start to
+ * ensure a sufficiently large anonymity set, while also allowing
+ * the Exchange to limit the financial damage in case of a key being
+ * compromised. Thus, exchanges with low volume are expected to have a
+ * longer withdraw period (@e expire_withdraw - @e start) than exchanges
+ * with high transaction volume. The period may also differ between
+ * types of coins. A exchange may also have a few denomination keys
+ * with the same value with overlapping validity periods, to address
+ * issues such as clock skew.
+ */
+ struct GNUNET_TIME_Timestamp expire_withdraw;
+
+ /**
+ * Coins signed with the denomination key must be spent or refreshed
+ * between @e start and this expiration time. After this time, the
+ * exchange will refuse transactions involving this key as it will
+ * "drop" the table with double-spending information (shortly after)
+ * this time. Note that wallets should refresh coins significantly
+ * before this time to be on the safe side. @e expire_deposit must be
+ * significantly larger than @e expire_withdraw (by months or even
+ * years).
+ */
+ struct GNUNET_TIME_Timestamp expire_deposit;
+
+ /**
+ * When do signatures with this denomination key become invalid?
+ * After this point, these signatures cannot be used in (legal)
+ * disputes anymore, as the Exchange is then allowed to destroy its side
+ * of the evidence. @e expire_legal is expected to be significantly
+ * larger than @e expire_deposit (by a year or more).
+ */
+ struct GNUNET_TIME_Timestamp expire_legal;
+
+ /**
+ * The value of the coins signed with this denomination key.
+ */
+ struct TALER_Amount value;
+
+ /**
+ * The fees the exchange charges for operations with
+ * coins of this denomination.
+ */
+ struct TALER_DenomFeeSet fees;
+
+ /**
+ * Age restriction for the denomination. (can be zero). If not zero, the bits
+ * set in the mask mark the edges at the beginning of a next age group. F.e.
+ * for the age groups
+ * 0-7, 8-9, 10-11, 12-14, 14-15, 16-17, 18-21, 21-*
+ * the following bits are set:
+ *
+ * 31 24 16 8 0
+ * | | | | |
+ * oooooooo oo1oo1o1 o1o1o1o1 ooooooo1
+ *
+ * A value of 0 means that the denomination does not support the extension for
+ * age-restriction.
+ */
+ struct TALER_AgeMask age_mask;
};
/**
+ * Signature of a function called with information about the exchange's
+ * denomination keys.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param denom_pub public key of the denomination
+ * @param h_denom_pub hash of @a denom_pub
+ * @param meta meta data information about the denomination type (value, expirations, fees)
+ * @param master_sig master signature affirming the validity of this denomination
+ * @param recoup_possible true if the key was revoked and clients can currently recoup
+ * coins of this denomination
+ */
+typedef void
+(*TALER_EXCHANGEDB_DenominationsCallback)(
+ void *cls,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig,
+ bool recoup_possible);
+
+
+/**
+ * Signature of a function called with information about the exchange's
+ * online signing keys.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param exchange_pub public key of the exchange
+ * @param meta meta data information about the signing type (expirations)
+ * @param master_sig master signature affirming the validity of this denomination
+ */
+typedef void
+(*TALER_EXCHANGEDB_ActiveSignkeysCallback)(
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Function called on all KYC process names that the given
+ * account has already passed.
+ *
+ * @param cls closure
+ * @param kyc_provider_section_name configuration section
+ * of the respective KYC process
+ */
+typedef void
+(*TALER_EXCHANGEDB_SatisfiedProviderCallback)(
+ void *cls,
+ const char *kyc_provider_section_name);
+
+
+/**
+ * Function called on all legitimization operations
+ * we have performed for the given account so far
+ * (and that have not yet expired).
+ *
+ * @param cls closure
+ * @param kyc_provider_section_name configuration section
+ * of the respective KYC process
+ * @param provider_user_id UID at a provider (can be NULL)
+ * @param legi_id legitimization process ID (can be NULL)
+ */
+typedef void
+(*TALER_EXCHANGEDB_LegitimizationProcessCallback)(
+ void *cls,
+ const char *kyc_provider_section_name,
+ const char *provider_user_id,
+ const char *legi_id);
+
+
+/**
+ * Function called with information about the exchange's auditors.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param auditor_pub the public key of the auditor
+ * @param auditor_url URL of the REST API of the auditor
+ * @param auditor_name human readable official name of the auditor
+ */
+typedef void
+(*TALER_EXCHANGEDB_AuditorsCallback)(
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name);
+
+
+/**
+ * Function called with information about the denominations
+ * audited by the exchange's auditors.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param auditor_pub the public key of an auditor
+ * @param h_denom_pub hash of a denomination key audited by this auditor
+ * @param auditor_sig signature from the auditor affirming this
+ */
+typedef void
+(*TALER_EXCHANGEDB_AuditorDenominationsCallback)(
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorSignatureP *auditor_sig);
+
+
+/**
* @brief Information we keep for a withdrawn coin to reproduce
* the /withdraw operation if needed, and to have proof
* that a reserve was drained by this amount.
@@ -224,14 +1165,14 @@ struct TALER_EXCHANGEDB_CollectableBlindcoin
{
/**
- * Our signature over the (blinded) coin.
+ * Our (blinded) signature over the (blinded) coin.
*/
- struct TALER_DenominationSignature sig;
+ struct TALER_BlindedDenominationSignature sig;
/**
* Hash of the denomination key (which coin was generated).
*/
- struct GNUNET_HashCode denom_pub_hash;
+ struct TALER_DenominationHashP denom_pub_hash;
/**
* Value of the coin being exchangeed (matching the denomination key)
@@ -264,7 +1205,7 @@ struct TALER_EXCHANGEDB_CollectableBlindcoin
* Hash over the blinded message, needed to verify
* the @e reserve_sig.
*/
- struct GNUNET_HashCode h_coin_envelope;
+ struct TALER_BlindedCoinHashP h_coin_envelope;
/**
* Signature confirming the withdrawal, matching @e reserve_pub,
@@ -275,6 +1216,79 @@ struct TALER_EXCHANGEDB_CollectableBlindcoin
/**
+ * @brief Information we keep for an age-withdraw request
+ * to reproduce the /age-withdraw operation if needed, and to have proof
+ * that a reserve was drained by this amount.
+ */
+struct TALER_EXCHANGEDB_AgeWithdraw
+{
+ /**
+ * Total amount (with fee) committed to withdraw
+ */
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Maximum age (in years) that the coins are restricted to.
+ */
+ uint16_t max_age;
+
+ /**
+ * The hash of the commitment of all n*kappa coins
+ */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+ /**
+ * Index (smaller #TALER_CNC_KAPPA) which the exchange has chosen to not have
+ * revealed during cut and choose. This value applies to all n coins in the
+ * commitment.
+ */
+ uint16_t noreveal_index;
+
+ /**
+ * Public key of the reserve that was drained.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Signature confirming the age withdrawal commitment, matching @e
+ * reserve_pub, @e max_age and @e h_commitment and @e amount_with_fee.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Number of coins to be withdrawn.
+ */
+ size_t num_coins;
+
+ /**
+ * Array of @a num_coins blinded coins. These are the chosen coins
+ * (according to @a noreveal_index) from the request, which contained
+ * kappa*num_coins blinded coins.
+ */
+ struct TALER_BlindedCoinHashP *h_coin_evs;
+
+ /**
+ * Array of @a num_coins denomination signatures of the blinded coins @a
+ * h_coin_evs.
+ */
+ struct TALER_BlindedDenominationSignature *denom_sigs;
+
+ /**
+ * Array of @a num_coins serial id's of the denominations, corresponding to
+ * the coins in @a h_coin_evs.
+ */
+ uint64_t *denom_serials;
+
+ /**
+ * [out]-Array of @a num_coins hashes of the public keys of the denominations
+ * identified by @e denom_serials. This field is set when calling
+ * get_age_withdraw
+ */
+ struct TALER_DenominationHashP *denom_pub_hashes;
+};
+
+
+/**
* Information the exchange records about a recoup request
* in a reserve history.
*/
@@ -290,7 +1304,7 @@ struct TALER_EXCHANGEDB_Recoup
* Blinding factor supplied to prove to the exchange that
* the coin came from this reserve.
*/
- struct TALER_DenominationBlindingKeyP coin_blind;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
/**
* Signature of the coin of type
@@ -311,8 +1325,25 @@ struct TALER_EXCHANGEDB_Recoup
/**
* When did the recoup operation happen?
*/
- struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_TIME_Timestamp timestamp;
+
+};
+
+/**
+ * Public key to which a nonce is locked.
+ */
+union TALER_EXCHANGEDB_NonceLockTargetP
+{
+ /**
+ * Nonce is locked to this coin key.
+ */
+ struct TALER_CoinSpendPublicKeyP coin;
+
+ /**
+ * Nonce is locked to this reserve key.
+ */
+ struct TALER_ReservePublicKeyP reserve;
};
@@ -327,7 +1358,7 @@ struct TALER_EXCHANGEDB_RecoupListEntry
* Blinding factor supplied to prove to the exchange that
* the coin came from this reserve.
*/
- struct TALER_DenominationBlindingKeyP coin_blind;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
/**
* Signature of the coin of type
@@ -336,6 +1367,11 @@ struct TALER_EXCHANGEDB_RecoupListEntry
struct TALER_CoinSpendSignatureP coin_sig;
/**
+ * Hash of the public denomination key used to sign the coin.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
* Public key of the reserve the coin was paid back into.
*/
struct TALER_ReservePublicKeyP reserve_pub;
@@ -348,7 +1384,7 @@ struct TALER_EXCHANGEDB_RecoupListEntry
/**
* When did the /recoup operation happen?
*/
- struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_TIME_Timestamp timestamp;
};
@@ -368,9 +1404,9 @@ struct TALER_EXCHANGEDB_RecoupRefreshListEntry
/**
* Blinding factor supplied to prove to the exchange that
- * the coin came from this reserve.
+ * the coin came from this @e old_coin_pub.
*/
- struct TALER_DenominationBlindingKeyP coin_blind;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
/**
* Signature of the coin of type
@@ -391,7 +1427,174 @@ struct TALER_EXCHANGEDB_RecoupRefreshListEntry
/**
* When did the recoup operation happen?
*/
- struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_TIME_Timestamp timestamp;
+
+};
+
+
+/**
+ * Details about a purse merge operation.
+ */
+struct TALER_EXCHANGEDB_PurseMerge
+{
+
+ /**
+ * Public key of the reserve the coin was merged into.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Amount in the purse, with fees.
+ */
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Fee paid for the purse.
+ */
+ struct TALER_Amount purse_fee;
+
+ /**
+ * Hash over the contract.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Merge capability key.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Purse public key.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Signature by the reserve approving the merge.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * When was the merge made.
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+
+ /**
+ * When was the purse set to expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * Minimum age required for depositing into the purse.
+ */
+ uint32_t min_age;
+
+ /**
+ * Flags of the purse.
+ */
+ enum TALER_WalletAccountMergeFlags flags;
+
+ /**
+ * true if the purse was actually successfully merged,
+ * false if the @e purse_fee was charged but the
+ * @e amount was not credited to the reserve.
+ */
+ bool merged;
+};
+
+
+/**
+ * Details about a (paid for) reserve history request.
+ */
+struct TALER_EXCHANGEDB_HistoryRequest
+{
+ /**
+ * Public key of the reserve the history request was for.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Fee paid for the request.
+ */
+ struct TALER_Amount history_fee;
+
+ /**
+ * When was the request made.
+ */
+ struct GNUNET_TIME_Timestamp request_timestamp;
+
+ /**
+ * Signature by the reserve approving the history request.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+};
+
+
+/**
+ * Details about a (paid for) reserve open request.
+ */
+struct TALER_EXCHANGEDB_OpenRequest
+{
+ /**
+ * Public key of the reserve the open request was for.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Fee paid for the request from the reserve.
+ */
+ struct TALER_Amount open_fee;
+
+ /**
+ * When was the request made.
+ */
+ struct GNUNET_TIME_Timestamp request_timestamp;
+
+ /**
+ * How long was the reserve supposed to be open.
+ */
+ struct GNUNET_TIME_Timestamp reserve_expiration;
+
+ /**
+ * Signature by the reserve approving the open request,
+ * with purpose #TALER_SIGNATURE_WALLET_RESERVE_OPEN.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * How many open purses should be included with the
+ * open reserve?
+ */
+ uint32_t purse_limit;
+
+};
+
+
+/**
+ * Details about an (explicit) reserve close request.
+ */
+struct TALER_EXCHANGEDB_CloseRequest
+{
+ /**
+ * Public key of the reserve the history request was for.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * When was the request made.
+ */
+ struct GNUNET_TIME_Timestamp request_timestamp;
+
+ /**
+ * Hash of the payto://-URI of the target account
+ * for the closure, or all zeros for the reserve
+ * origin account.
+ */
+ struct TALER_PaytoHashP target_account_h_payto;
+
+ /**
+ * Signature by the reserve approving the history request.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
};
@@ -422,7 +1625,27 @@ enum TALER_EXCHANGEDB_ReserveOperation
* customer's bank account. This happens when the exchange
* closes a reserve with a non-zero amount left in it.
*/
- TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK = 3
+ TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK = 3,
+
+ /**
+ * Event where a purse was merged into a reserve.
+ */
+ TALER_EXCHANGEDB_RO_PURSE_MERGE = 4,
+
+ /**
+ * Event where a wallet paid for a full reserve history.
+ */
+ TALER_EXCHANGEDB_RO_HISTORY_REQUEST = 5,
+
+ /**
+ * Event where a wallet paid to open a reserve for longer.
+ */
+ TALER_EXCHANGEDB_RO_OPEN_REQUEST = 6,
+
+ /**
+ * Event where a wallet requested a reserve to be closed.
+ */
+ TALER_EXCHANGEDB_RO_CLOSE_REQUEST = 7
};
@@ -473,12 +1696,156 @@ struct TALER_EXCHANGEDB_ReserveHistory
*/
struct TALER_EXCHANGEDB_ClosingTransfer *closing;
+ /**
+ * Details about a purse merge operation.
+ */
+ struct TALER_EXCHANGEDB_PurseMerge *merge;
+
+ /**
+ * Details about a (paid for) reserve history request.
+ */
+ struct TALER_EXCHANGEDB_HistoryRequest *history;
+
+ /**
+ * Details about a (paid for) open reserve request.
+ */
+ struct TALER_EXCHANGEDB_OpenRequest *open_request;
+
+ /**
+ * Details about an (explicit) reserve close request.
+ */
+ struct TALER_EXCHANGEDB_CloseRequest *close_request;
+
} details;
};
/**
+ * @brief Data about a coin for a deposit operation.
+ */
+struct TALER_EXCHANGEDB_CoinDepositInformation
+{
+ /**
+ * Information about the coin that is being deposited.
+ */
+ struct TALER_CoinPublicInfo coin;
+
+ /**
+ * ECDSA signature affirming that the customer intends
+ * this coin to be deposited at the merchant identified
+ * by @e h_wire in relation to the proposal data identified
+ * by @e h_contract_terms.
+ */
+ struct TALER_CoinSpendSignatureP csig;
+
+ /**
+ * Fraction of the coin's remaining value to be deposited, including
+ * depositing fee (if any). The coin is identified by @e coin_pub.
+ */
+ struct TALER_Amount amount_with_fee;
+
+};
+
+
+/**
+ * @brief Data from a batch deposit operation.
+ */
+struct TALER_EXCHANGEDB_BatchDeposit
+{
+
+ /**
+ * Public key of the merchant. Enables later identification
+ * of the merchant in case of a need to rollback transactions.
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+
+ /**
+ * Hash over the proposal data between merchant and customer
+ * (remains unknown to the Exchange).
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Hash over additional inputs by the wallet.
+ */
+ struct GNUNET_HashCode wallet_data_hash;
+
+ /**
+ * Unsalted hash over @e receiver_wire_account.
+ */
+ struct TALER_PaytoHashP wire_target_h_payto;
+
+ /**
+ * Salt used by the merchant to compute "h_wire".
+ */
+ struct TALER_WireSaltP wire_salt;
+
+ /**
+ * Time when this request was generated. Used, for example, to
+ * assess when (roughly) the income was achieved for tax purposes.
+ * Note that the Exchange will only check that the timestamp is not "too
+ * far" into the future (i.e. several days). The fact that the
+ * timestamp falls within the validity period of the coin's
+ * denomination key is irrelevant for the validity of the deposit
+ * request, as obviously the customer and merchant could conspire to
+ * set any timestamp. Also, the Exchange must accept very old deposit
+ * requests, as the merchant might have been unable to transmit the
+ * deposit request in a timely fashion (so back-dating is not
+ * prevented).
+ */
+ struct GNUNET_TIME_Timestamp wallet_timestamp;
+
+ /**
+ * How much time does the merchant have to issue a refund request?
+ * Zero if refunds are not allowed. After this time, the coin
+ * cannot be refunded.
+ */
+ struct GNUNET_TIME_Timestamp refund_deadline;
+
+ /**
+ * How much time does the merchant have to execute the wire transfer?
+ * This time is advisory for aggregating transactions, not a hard
+ * constraint (as the merchant can theoretically pick any time,
+ * including one in the past).
+ */
+ struct GNUNET_TIME_Timestamp wire_deadline;
+
+ /**
+ * Row ID of the policy details; 0 if no policy applies.
+ */
+ uint64_t policy_details_serial_id;
+
+ /**
+ * Information about the receiver for executing the transaction. URI in
+ * payto://-format.
+ */
+ const char *receiver_wire_account;
+
+ /**
+ * Array about the coins that are being deposited.
+ */
+ const struct TALER_EXCHANGEDB_CoinDepositInformation *cdis;
+
+ /**
+ * Length of the @e cdis array.
+ */
+ unsigned int num_cdis;
+
+ /**
+ * False if @e wallet_data_hash was provided
+ */
+ bool no_wallet_data_hash;
+
+ /**
+ * True if further processing is blocked by policy.
+ */
+ bool policy_blocked;
+
+};
+
+
+/**
* @brief Data from a deposit operation. The combination of
* the coin's public key, the merchant's public key and the
* transaction ID must be unique. While a coin can (theoretically) be
@@ -510,24 +1877,26 @@ struct TALER_EXCHANGEDB_Deposit
struct TALER_MerchantPublicKeyP merchant_pub;
/**
- * Hash over the proposa data between merchant and customer
+ * Hash over the proposal data between merchant and customer
* (remains unknown to the Exchange).
*/
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
/**
- * Hash of the (canonical) representation of @e wire, used
- * to check the signature on the request. Generated by
- * the exchange from the detailed wire data provided by the
- * merchant.
+ * Salt used by the merchant to compute "h_wire".
*/
- struct GNUNET_HashCode h_wire;
+ struct TALER_WireSaltP wire_salt;
/**
- * Detailed information about the receiver for executing the transaction.
- * Includes URL in payto://-format and salt.
+ * Hash over inputs from the wallet to customize the contract.
+ */
+ struct GNUNET_HashCode wallet_data_hash;
+
+ /**
+ * Hash over the policy data for this deposit (remains unknown to the
+ * Exchange). Needed for the verification of the deposit's signature
*/
- json_t *receiver_wire_account;
+ struct TALER_ExtensionPolicyHashP h_policy;
/**
* Time when this request was generated. Used, for example, to
@@ -542,14 +1911,14 @@ struct TALER_EXCHANGEDB_Deposit
* deposit request in a timely fashion (so back-dating is not
* prevented).
*/
- struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_TIME_Timestamp timestamp;
/**
* How much time does the merchant have to issue a refund request?
* Zero if refunds are not allowed. After this time, the coin
* cannot be refunded.
*/
- struct GNUNET_TIME_Absolute refund_deadline;
+ struct GNUNET_TIME_Timestamp refund_deadline;
/**
* How much time does the merchant have to execute the wire transfer?
@@ -557,7 +1926,7 @@ struct TALER_EXCHANGEDB_Deposit
* constraint (as the merchant can theoretically pick any time,
* including one in the past).
*/
- struct GNUNET_TIME_Absolute wire_deadline;
+ struct GNUNET_TIME_Timestamp wire_deadline;
/**
* Fraction of the coin's remaining value to be deposited, including
@@ -570,6 +1939,22 @@ struct TALER_EXCHANGEDB_Deposit
*/
struct TALER_Amount deposit_fee;
+ /**
+ * Information about the receiver for executing the transaction. URI in
+ * payto://-format.
+ */
+ char *receiver_wire_account;
+
+ /**
+ * True if @e policy_json was provided
+ */
+ bool has_policy;
+
+ /**
+ * True if @e wallet_data_hash is not in use.
+ */
+ bool no_wallet_data_hash;
+
};
@@ -598,21 +1983,45 @@ struct TALER_EXCHANGEDB_DepositListEntry
* Hash over the proposa data between merchant and customer
* (remains unknown to the Exchange).
*/
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
/**
- * Hash of the (canonical) representation of @e wire, used
- * to check the signature on the request. Generated by
- * the exchange from the detailed wire data provided by the
- * merchant.
+ * Hash over inputs from the wallet to customize the contract.
*/
- struct GNUNET_HashCode h_wire;
+ struct GNUNET_HashCode wallet_data_hash;
/**
- * Detailed information about the receiver for executing the transaction.
- * Includes URL in payto://-format and salt.
+ * Hash of the public denomination key used to sign the coin.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Age commitment hash, if applicable to the denomination. Should be all
+ * zeroes if age commitment is not applicable to the denonimation.
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
+ * Salt used to compute h_wire from the @e receiver_wire_account.
+ */
+ struct TALER_WireSaltP wire_salt;
+
+ /**
+ * Hash over the policy data for this deposit (remains unknown to the
+ * Exchange). Needed for the verification of the deposit's signature
+ */
+ struct TALER_ExtensionPolicyHashP h_policy;
+
+ /**
+ * Fraction of the coin's remaining value to be deposited, including
+ * depositing fee (if any). The coin is identified by @e coin_pub.
*/
- json_t *receiver_wire_account;
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Depositing fee.
+ */
+ struct TALER_Amount deposit_fee;
/**
* Time when this request was generated. Used, for example, to
@@ -627,14 +2036,14 @@ struct TALER_EXCHANGEDB_DepositListEntry
* deposit request in a timely fashion (so back-dating is not
* prevented).
*/
- struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_TIME_Timestamp timestamp;
/**
* How much time does the merchant have to issue a refund request?
* Zero if refunds are not allowed. After this time, the coin
* cannot be refunded.
*/
- struct GNUNET_TIME_Absolute refund_deadline;
+ struct GNUNET_TIME_Timestamp refund_deadline;
/**
* How much time does the merchant have to execute the wire transfer?
@@ -642,18 +2051,33 @@ struct TALER_EXCHANGEDB_DepositListEntry
* constraint (as the merchant can theoretically pick any time,
* including one in the past).
*/
- struct GNUNET_TIME_Absolute wire_deadline;
+ struct GNUNET_TIME_Timestamp wire_deadline;
/**
- * Fraction of the coin's remaining value to be deposited, including
- * depositing fee (if any). The coin is identified by @e coin_pub.
+ * Detailed information about the receiver for executing the transaction.
+ * URL in payto://-format.
*/
- struct TALER_Amount amount_with_fee;
+ char *receiver_wire_account;
/**
- * Depositing fee.
+ * true, if age commitment is not applicable
*/
- struct TALER_Amount deposit_fee;
+ bool no_age_commitment;
+
+ /**
+ * true, if wallet data hash is not present
+ */
+ bool no_wallet_data_hash;
+
+ /**
+ * True if a policy was provided with the deposit request
+ */
+ bool has_policy;
+
+ /**
+ * Has the deposit been wired?
+ */
+ bool done;
};
@@ -678,7 +2102,7 @@ struct TALER_EXCHANGEDB_RefundListEntry
* Hash over the proposal data between merchant and customer
* (remains unknown to the Exchange).
*/
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
/**
* Merchant-generated REFUND transaction ID to detect duplicate
@@ -782,6 +2206,23 @@ struct TALER_EXCHANGEDB_MeltListEntry
struct TALER_RefreshCommitmentP rc;
/**
+ * Hash of the public denomination key used to sign the coin.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Hash of the age commitment used to sign the coin, if age restriction was
+ * applicable to the denomination. May be all zeroes if no age restriction
+ * applies.
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
+ * true, if no h_age_commitment is applicable
+ */
+ bool no_age_commitment;
+
+ /**
* How much value is being melted? This amount includes the fees,
* so the final amount contributed to the melt is this value minus
* the fee for melting the coin. We include the fee in what is
@@ -806,6 +2247,158 @@ struct TALER_EXCHANGEDB_MeltListEntry
/**
+ * Information about a /purses/$PID/deposit operation in a coin transaction history.
+ */
+struct TALER_EXCHANGEDB_PurseDepositListEntry
+{
+
+ /**
+ * Exchange hosting the purse, NULL for this exchange.
+ */
+ char *exchange_base_url;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Contribution of the coin to the purse, including
+ * deposit fee.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Depositing fee.
+ */
+ struct TALER_Amount deposit_fee;
+
+ /**
+ * Signature by the coin affirming the deposit.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * Hash of the age commitment used to sign the coin, if age restriction was
+ * applicable to the denomination.
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
+ * Set to true if the coin was refunded.
+ */
+ bool refunded;
+
+ /**
+ * Set to true if there was no age commitment.
+ */
+ bool no_age_commitment;
+
+};
+
+
+/**
+ * @brief Specification for a purse refund operation in a coin's transaction list.
+ */
+struct TALER_EXCHANGEDB_PurseRefundListEntry
+{
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Fraction of the original deposit's value to be refunded, including
+ * refund fee (if any). The coin is identified by @e coin_pub.
+ */
+ struct TALER_Amount refund_amount;
+
+ /**
+ * Refund fee to be covered by the customer.
+ */
+ struct TALER_Amount refund_fee;
+
+};
+
+
+/**
+ * Information about a /reserves/$RID/open operation in a coin transaction history.
+ */
+struct TALER_EXCHANGEDB_ReserveOpenListEntry
+{
+
+ /**
+ * Signature of the reserve.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Contribution of the coin to the open fee, including
+ * deposit fee.
+ */
+ struct TALER_Amount coin_contribution;
+
+ /**
+ * Signature by the coin affirming the open deposit.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+};
+
+
+/**
+ * Information about a /purses/$PID/deposit operation.
+ */
+struct TALER_EXCHANGEDB_PurseDeposit
+{
+
+ /**
+ * Exchange hosting the purse, NULL for this exchange.
+ */
+ char *exchange_base_url;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Contribution of the coin to the purse, including
+ * deposit fee.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Depositing fee.
+ */
+ struct TALER_Amount deposit_fee;
+
+ /**
+ * Signature by the coin affirming the deposit.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * Public key of the coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Hash of the age commitment used to sign the coin, if age restriction was
+ * applicable to the denomination. May be all zeroes if no age restriction
+ * applies.
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
+ * Set to true if @e h_age_commitment is not available.
+ */
+ bool no_age_commitment;
+
+};
+
+/**
* Information about a melt operation.
*/
struct TALER_EXCHANGEDB_Melt
@@ -842,13 +2435,34 @@ struct TALER_EXCHANGEDB_LinkList
/**
* Signature over the blinded envelope.
*/
- struct TALER_DenominationSignature ev_sig;
+ struct TALER_BlindedDenominationSignature ev_sig;
+
+ /**
+ * Exchange-provided values during the coin generation.
+ */
+ struct TALER_ExchangeWithdrawValues alg_values;
/**
* Signature of the original coin being refreshed over the
* link data, of type #TALER_SIGNATURE_WALLET_COIN_LINK
*/
struct TALER_CoinSpendSignatureP orig_coin_link_sig;
+
+ /**
+ * Session nonce, if cipher has one.
+ */
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+
+ /**
+ * Offset that generated this coin in the refresh
+ * operation.
+ */
+ uint32_t coin_refresh_offset;
+
+ /**
+ * Set to true if @e nonce was initialized.
+ */
+ bool have_nonce;
};
@@ -887,7 +2501,22 @@ enum TALER_EXCHANGEDB_TransactionType
/**
* Recoup-refresh operation (on the new coin, eliminating its value)
*/
- TALER_EXCHANGEDB_TT_RECOUP_REFRESH = 5
+ TALER_EXCHANGEDB_TT_RECOUP_REFRESH = 5,
+
+ /**
+ * Purse deposit operation.
+ */
+ TALER_EXCHANGEDB_TT_PURSE_DEPOSIT = 6,
+
+ /**
+ * Purse deposit operation.
+ */
+ TALER_EXCHANGEDB_TT_PURSE_REFUND = 7,
+
+ /**
+ * Reserve open deposit operation.
+ */
+ TALER_EXCHANGEDB_TT_RESERVE_OPEN = 8
};
@@ -957,46 +2586,27 @@ struct TALER_EXCHANGEDB_TransactionList
*/
struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup_refresh;
- } details;
-
-};
+ /**
+ * Coin was deposited into a purse.
+ * (#TALER_EXCHANGEDB_TT_PURSE_DEPOSIT)
+ */
+ struct TALER_EXCHANGEDB_PurseDepositListEntry *purse_deposit;
+ /**
+ * Coin was refunded upon purse expiration
+ * (#TALER_EXCHANGEDB_TT_PURSE_REFUND)
+ */
+ struct TALER_EXCHANGEDB_PurseRefundListEntry *purse_refund;
-/**
- * @brief Handle for a database session (per-thread, for transactions).
- */
-struct TALER_EXCHANGEDB_Session;
+ /**
+ * Coin was used to pay to open a reserve.
+ * (#TALER_EXCHANGEDB_TT_RESERVE_OPEN)
+ */
+ struct TALER_EXCHANGEDB_ReserveOpenListEntry *reserve_open;
+ } details;
-/**
- * Function called with details about deposits that have been made,
- * with the goal of executing the corresponding wire transaction.
- *
- * @param cls closure
- * @param rowid unique ID for the deposit in our DB, used for marking
- * it as 'tiny' or 'done'
- * @param merchant_pub public key of the merchant
- * @param coin_pub public key of the coin
- * @param amount_with_fee amount that was deposited including fee
- * @param deposit_fee amount the exchange gets to keep as transaction fees
- * @param h_contract_terms hash of the proposal data known to merchant and customer
- * @param wire_deadline by which the merchant advised that he would like the
- * wire transfer to be executed
- * @param receiver_wire_account wire details for the merchant, includes
- * 'url' in payto://-format; NULL from iterate_matching_deposits()
- * @return transaction status code, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT to continue to iterate
- */
-typedef enum GNUNET_DB_QueryStatus
-(*TALER_EXCHANGEDB_DepositIterator)(
- void *cls,
- uint64_t rowid,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct GNUNET_HashCode *h_contract_terms,
- struct GNUNET_TIME_Absolute wire_deadline,
- const json_t *receiver_wire_account);
+};
/**
@@ -1017,42 +2627,200 @@ typedef void
/**
+ * Callback with KYC attributes about a particular user.
+ *
+ * @param cls closure
+ * @param h_payto account for which the attribute data is stored
+ * @param provider_section provider that must be checked
+ * @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
+ */
+typedef void
+(*TALER_EXCHANGEDB_AttributeCallback)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ struct GNUNET_TIME_Timestamp collection_time,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ size_t enc_attributes_size,
+ const void *enc_attributes);
+
+
+/**
* Function called with details about deposits that have been made,
* with the goal of auditing the deposit's execution.
*
* @param cls closure
* @param rowid unique serial ID for the deposit in our DB
- * @param timestamp when did the deposit happen
- * @param merchant_pub public key of the merchant
+ * @param exchange_timestamp when did the deposit happen
+ * @param deposit deposit details
* @param denom_pub denomination public key of @a coin_pub
- * @param coin_pub public key of the coin
- * @param coin_sig signature from the coin
- * @param amount_with_fee amount that was deposited including fee
- * @param h_contract_terms hash of the proposal data known to merchant and customer
- * @param refund_deadline by which the merchant advised that he might want
- * to get a refund
- * @param wire_deadline by which the merchant advised that he would like the
- * wire transfer to be executed
- * @param receiver_wire_account wire details for the merchant including 'url' in payto://-format;
- * NULL from iterate_matching_deposits()
* @param done flag set if the deposit was already executed (or not)
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-typedef int
+typedef enum GNUNET_GenericReturnValue
(*TALER_EXCHANGEDB_DepositCallback)(
void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute timestamp,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ const struct TALER_EXCHANGEDB_Deposit *deposit,
const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_CoinSpendSignatureP *coin_sig,
+ bool done);
+
+
+/**
+ * Function called with details about purse deposits that have been made, with
+ * the goal of auditing the deposit's execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param deposit deposit details
+ * @param reserve_pub which reserve is the purse merged into, NULL if unknown
+ * @param flags purse flags
+ * @param auditor_balance purse balance (according to the
+ * auditor during auditing)
+ * @param purse_total target amount the purse should reach
+ * @param denom_pub denomination public key of @a coin_pub
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_PurseDepositCallback)(
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_EXCHANGEDB_PurseDeposit *deposit,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_Amount *auditor_balance,
+ const struct TALER_Amount *purse_total,
+ const struct TALER_DenominationPublicKey *denom_pub);
+
+
+/**
+ * Function called with details about
+ * account merge requests that have been made, with
+ * the goal of auditing the account merge execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param reserve_pub reserve affected by the merge
+ * @param purse_pub purse being merged
+ * @param h_contract_terms hash over contract of the purse
+ * @param purse_expiration when would the purse expire
+ * @param amount total amount in the purse
+ * @param min_age minimum age of all coins deposited into the purse
+ * @param flags how was the purse created
+ * @param purse_fee if a purse fee was paid, how high is it
+ * @param merge_timestamp when was the merge approved
+ * @param reserve_sig signature by reserve approving the merge
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_AccountMergeCallback)(
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_Amount *amount,
+ uint32_t min_age,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_Amount *purse_fee,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Function called with details about purse
+ * merges that have been made, with
+ * the goal of auditing the purse merge execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param partner_base_url where is the reserve, NULL for this exchange
+ * @param amount total amount expected in the purse
+ * @param balance current balance in the purse (according to the auditor)
+ * @param flags purse flags
+ * @param merge_pub merge capability key
+ * @param reserve_pub reserve the merge affects
+ * @param merge_sig signature affirming the merge
+ * @param purse_pub purse key
+ * @param merge_timestamp when did the merge happen
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_PurseMergeCallback)(
+ void *cls,
+ uint64_t rowid,
+ const char *partner_base_url,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *balance,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp merge_timestamp);
+
+
+/**
+ * Function called with details about purse decisions that have been made, with
+ * the goal of auditing the purse's execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param purse_pub public key of the purse
+ * @param reserve_pub public key of the target reserve, NULL if not known / refunded
+ * @param purse_value what is the (target) value of the purse
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_PurseDecisionCallback)(
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *purse_value);
+
+
+/**
+ * Function called with details about purse decisions that have been made, with
+ * the goal of auditing the purse's execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param purse_pub public key of the purse
+ * @param refunded true if decision was to refund
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_AllPurseDecisionCallback)(
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ bool refunded);
+
+
+/**
+ * Function called with details about purse refunds that have been made, with
+ * the goal of auditing the purse refund's execution.
+ *
+ * @param cls closure
+ * @param rowid row of the refund event
+ * @param amount_with_fee amount of the deposit into the purse
+ * @param coin_pub coin that is to be refunded the @a given amount_with_fee
+ * @param denom_pub denomination of @a coin_pub
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_PurseRefundCoinCallback)(
+ void *cls,
+ uint64_t rowid,
const struct TALER_Amount *amount_with_fee,
- const struct GNUNET_HashCode *h_contract_terms,
- struct GNUNET_TIME_Absolute refund_deadline,
- struct GNUNET_TIME_Absolute wire_deadline,
- const json_t *receiver_wire_account,
- int done);
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_DenominationPublicKey *denom_pub);
/**
@@ -1062,6 +2830,7 @@ typedef int
* @param cls closure
* @param rowid unique serial ID for the refresh session in our DB
* @param denom_pub denomination public key of @a coin_pub
+ * @param h_age_commitment age commitment that went into the signing of the coin, may be NULL
* @param coin_pub public key of the coin
* @param coin_sig signature from the coin
* @param amount_with_fee amount that was deposited including fee
@@ -1069,11 +2838,12 @@ typedef int
* @param rc what is the commitment
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-typedef int
+typedef enum GNUNET_GenericReturnValue
(*TALER_EXCHANGEDB_RefreshesCallback)(
void *cls,
uint64_t rowid,
const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
const struct TALER_Amount *amount_with_fee,
@@ -1089,7 +2859,7 @@ typedef int
* @param amount_with_fee amount being refunded
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-typedef int
+typedef enum GNUNET_GenericReturnValue
(*TALER_EXCHANGEDB_RefundCoinCallback)(
void *cls,
const struct TALER_Amount *amount_with_fee);
@@ -1102,9 +2872,9 @@ typedef int
struct TALER_EXCHANGEDB_RefreshRevealedCoin
{
/**
- * Public denomination key of the coin.
+ * Hash of the public denomination key of the coin.
*/
- struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
/**
* Signature of the original coin being refreshed over the
@@ -1113,41 +2883,117 @@ struct TALER_EXCHANGEDB_RefreshRevealedCoin
struct TALER_CoinSpendSignatureP orig_coin_link_sig;
/**
- * Blinded message to be signed (in envelope), with @e coin_env_size bytes.
+ * Hash of the blinded new coin, that is @e coin_ev.
*/
- char *coin_ev;
+ struct TALER_BlindedCoinHashP coin_envelope_hash;
/**
- * Number of bytes in @e coin_ev.
+ * Signature generated by the exchange over the coin (in blinded format).
*/
- size_t coin_ev_size;
+ struct TALER_BlindedDenominationSignature coin_sig;
/**
- * Signature generated by the exchange over the coin (in blinded format).
+ * Values contributed from the exchange to the
+ * coin generation (see /csr).
*/
- struct TALER_DenominationSignature coin_sig;
+ struct TALER_ExchangeWithdrawValues exchange_vals;
+
+ /**
+ * Blinded message to be signed (in envelope).
+ */
+ struct TALER_BlindedPlanchet blinded_planchet;
+
};
/**
+ * Information per Clause-Schnorr (CS) fresh coin to
+ * be persisted for idempotency during refreshes-reveal.
+ */
+struct TALER_EXCHANGEDB_CsRevealFreshCoinData
+{
+ /**
+ * Denomination of the fresh coin.
+ */
+ struct TALER_DenominationHashP new_denom_pub_hash;
+
+ /**
+ * Blind signature of the fresh coin (possibly updated
+ * in case if a replay!).
+ */
+ struct TALER_BlindedDenominationSignature bsig;
+
+ /**
+ * Offset of the fresh coin in the reveal operation.
+ * (May not match the array offset as we may have
+ * a mixture of RSA and CS coins being created, and
+ * this request is only made for the CS subset).
+ */
+ uint32_t coin_off;
+};
+
+
+/**
+ * Generic KYC status for some operation.
+ */
+struct TALER_EXCHANGEDB_KycStatus
+{
+ /**
+ * Number that identifies the KYC requirement the operation
+ * was about.
+ */
+ uint64_t requirement_row;
+
+ /**
+ * True if the KYC status is "satisfied".
+ */
+ bool ok;
+
+};
+
+
+struct TALER_EXCHANGEDB_ReserveInInfo
+{
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+ const struct TALER_Amount *balance;
+ struct GNUNET_TIME_Timestamp execution_time;
+ const char *sender_account_details;
+ const char *exchange_account_name;
+ uint64_t wire_reference;
+};
+
+
+/**
+ * Function called on each @a amount that was found to
+ * be relevant for a KYC check.
+ *
+ * @param cls closure to allow the KYC module to
+ * total up amounts and evaluate rules
+ * @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)
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_KycAmountCallback)(
+ void *cls,
+ const struct TALER_Amount *amount,
+ struct GNUNET_TIME_Absolute date);
+
+
+/**
* Function called with information about a refresh order.
*
* @param cls closure
- * @param rowid unique serial ID for the row in our database
* @param num_freshcoins size of the @a rrcs array
* @param rrcs array of @a num_freshcoins information about coins to be created
- * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
- * @param tprivs array of @e num_tprivs transfer private keys
- * @param tp transfer public key information
*/
typedef void
(*TALER_EXCHANGEDB_RefreshCallback)(
void *cls,
uint32_t num_freshcoins,
- const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
- unsigned int num_tprivs,
- const struct TALER_TransferPrivateKeyP *tprivs,
- const struct TALER_TransferPublicKeyP *tp);
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs);
/**
@@ -1162,10 +3008,11 @@ typedef void
* @param merchant_sig signature of the merchant
* @param h_contract_terms hash of the proposal data known to merchant and customer
* @param rtransaction_id refund transaction ID chosen by the merchant
+ * @param full_refund true if the refunds total up to the entire value of the deposit
* @param amount_with_fee amount that was deposited including fee
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-typedef int
+typedef enum GNUNET_GenericReturnValue
(*TALER_EXCHANGEDB_RefundCallback)(
void *cls,
uint64_t rowid,
@@ -1173,8 +3020,9 @@ typedef int
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantSignatureP *merchant_sig,
- const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
uint64_t rtransaction_id,
+ bool full_refund,
const struct TALER_Amount *amount_with_fee);
@@ -1183,14 +3031,14 @@ typedef int
*
* @param cls closure
* @param rowid unique serial ID for the refresh session in our DB
- * @param reserve_pub public key of the reserve (also the WTID)
+ * @param reserve_pub public key of the reserve (also the wire subject)
* @param credit amount that was received
* @param sender_account_details information about the sender's bank account, in payto://-format
* @param wire_reference unique identifier for the wire transfer
* @param execution_date when did we receive the funds
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-typedef int
+typedef enum GNUNET_GenericReturnValue
(*TALER_EXCHANGEDB_ReserveInCallback)(
void *cls,
uint64_t rowid,
@@ -1198,7 +3046,76 @@ typedef int
const struct TALER_Amount *credit,
const char *sender_account_details,
uint64_t wire_reference,
- struct GNUNET_TIME_Absolute execution_date);
+ struct GNUNET_TIME_Timestamp execution_date);
+
+
+/**
+ * Provide information about a wire account.
+ *
+ * @param cls closure
+ * @param payto_uri the exchange bank account URI
+ * @param conversion_url URL of a conversion service, NULL if there is no conversion
+ * @param debit_restrictions JSON array with debit restrictions on the account
+ * @param credit_restrictions JSON array with credit restrictions on the account
+ * @param master_sig master key signature affirming that this is a bank
+ * account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS)
+ * @param bank_label label the wallet should use to display the account, can be NULL
+ * @param priority priority for ordering bank account labels
+ */
+typedef void
+(*TALER_EXCHANGEDB_WireAccountCallback)(
+ void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority);
+
+
+/**
+ * Provide information about wire fees.
+ *
+ * @param cls closure
+ * @param fees the wire fees we charge
+ * @param start_date from when are these fees valid (start date)
+ * @param end_date until when are these fees valid (end date, exclusive)
+ * @param master_sig master key signature affirming that this is the correct
+ * fee (of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES)
+ */
+typedef void
+(*TALER_EXCHANGEDB_WireFeeCallback)(
+ void *cls,
+ const struct TALER_WireFeeSet *fees,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Provide information about global fees.
+ *
+ * @param cls closure
+ * @param fees the global fees we charge
+ * @param purse_timeout when do purses time out
+ * @param history_expiration how long are account histories preserved
+ * @param purse_account_limit how many purses are free per account
+ * @param start_date from when are these fees valid (start date)
+ * @param end_date until when are these fees valid (end date, exclusive)
+ * @param master_sig master key signature affirming that this is the correct
+ * fee (of purpose #TALER_SIGNATURE_MASTER_GLOBAL_FEES)
+ */
+typedef void
+(*TALER_EXCHANGEDB_GlobalFeeCallback)(
+ void *cls,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterSignatureP *master_sig);
/**
@@ -1214,15 +3131,15 @@ typedef int
* @param amount_with_fee amount that was withdrawn
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-typedef int
+typedef enum GNUNET_GenericReturnValue
(*TALER_EXCHANGEDB_WithdrawCallback)(
void *cls,
uint64_t rowid,
- const struct GNUNET_HashCode *h_blind_ev,
+ const struct TALER_BlindedCoinHashP *h_blind_ev,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_ReserveSignatureP *reserve_sig,
- struct GNUNET_TIME_Absolute execution_date,
+ struct GNUNET_TIME_Timestamp execution_date,
const struct TALER_Amount *amount_with_fee);
@@ -1242,37 +3159,14 @@ typedef void
/**
- * Function called with the results of the lookup of the wire transfer
- * identifier information. Only called if we are at least aware of the
- * transaction existing.
- *
- * @param cls closure
- * @param wtid wire transfer identifier, NULL
- * if the transaction was not yet done
- * @param coin_contribution how much did the coin we asked about
- * contribute to the total transfer value? (deposit value including fee)
- * @param coin_fee how much did the exchange charge for the deposit fee
- * @param execution_time when was the transaction done, or
- * when we expect it to be done (if @a wtid was NULL)
- */
-typedef void
-(*TALER_EXCHANGEDB_WireTransferByCoinCallback)(
- void *cls,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *coin_contribution,
- const struct TALER_Amount *coin_fee,
- struct GNUNET_TIME_Absolute execution_time);
-
-
-/**
* Function called with the results of the lookup of the
* transaction data associated with a wire transfer identifier.
*
* @param cls closure
* @param rowid which row in the table is the information from (for diagnostics)
* @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls)
- * @param h_wire hash of wire transfer details of the merchant (should be same for all callbacks with the same @e cls)
- * @param account_details which account did the transfer go to?
+ * @param account_payto_uri which account did the transfer go to?
+ * @param h_payto hash over @a account_payto_uri as it is in the DB
* @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls)
* @param h_contract_terms which proposal was this payment about
* @param denom_pub denomination of @a coin_pub
@@ -1285,10 +3179,10 @@ typedef void
void *cls,
uint64_t rowid,
const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_wire,
- const json_t *account_details,
- struct GNUNET_TIME_Absolute exec_time,
- const struct GNUNET_HashCode *h_contract_terms,
+ const char *account_payto_uri,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp exec_time,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *coin_value,
@@ -1303,21 +3197,41 @@ typedef void
* @param rowid identifier of the respective row in the database
* @param date timestamp of the wire transfer (roughly)
* @param wtid wire transfer subject
- * @param wire wire transfer details of the receiver, including "url" in payto://-format
+ * @param payto_uri details of the receiver, URI in payto://-format
* @param amount amount that was wired
* @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration
*/
-typedef int
+typedef enum GNUNET_GenericReturnValue
(*TALER_EXCHANGEDB_WireTransferOutCallback)(
void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute date,
+ struct GNUNET_TIME_Timestamp date,
const struct TALER_WireTransferIdentifierRawP *wtid,
- const json_t *wire,
+ const char *payto_uri,
const struct TALER_Amount *amount);
/**
+ * Function called on transient aggregations matching
+ * a particular hash of a payto URI.
+ *
+ * @param cls
+ * @param payto_uri corresponding payto URI
+ * @param wtid wire transfer identifier of transient aggregation
+ * @param merchant_pub public key of the merchant
+ * @param total amount aggregated so far
+ * @return true to continue iterating
+ */
+typedef bool
+(*TALER_EXCHANGEDB_TransientAggregationCallback)(
+ void *cls,
+ const char *payto_uri,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_Amount *total);
+
+
+/**
* Callback with data about a prepared wire transfer.
*
* @param cls closure
@@ -1328,7 +3242,7 @@ typedef int
* @param finished did we complete the transfer yet?
* @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration
*/
-typedef int
+typedef enum GNUNET_GenericReturnValue
(*TALER_EXCHANGEDB_WirePreparationCallback)(void *cls,
uint64_t rowid,
const char *wire_method,
@@ -1351,17 +3265,17 @@ typedef int
* @param coin_blind blinding factor used to blind the coin
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-typedef int
+typedef enum GNUNET_GenericReturnValue
(*TALER_EXCHANGEDB_RecoupCallback)(
void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute timestamp,
+ struct GNUNET_TIME_Timestamp timestamp,
const struct TALER_Amount *amount,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind);
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_blind);
/**
@@ -1380,18 +3294,45 @@ typedef int
* @param coin_blind blinding factor used to blind the coin
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-typedef int
+typedef enum GNUNET_GenericReturnValue
(*TALER_EXCHANGEDB_RecoupRefreshCallback)(
void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute timestamp,
+ struct GNUNET_TIME_Timestamp timestamp,
const struct TALER_Amount *amount,
const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
- const struct GNUNET_HashCode *old_denom_pub_hash,
+ const struct TALER_DenominationHashP *old_denom_pub_hash,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind);
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_blind);
+
+
+/**
+ * Function called about reserve opening operations.
+ *
+ * @param cls closure
+ * @param rowid row identifier used to uniquely identify the reserve closing operation
+ * @param reserve_payment how much to pay from the
+ * reserve's own balance for opening the reserve
+ * @param request_timestamp when was the request created
+ * @param reserve_expiration desired expiration time for the reserve
+ * @param purse_limit minimum number of purses the client
+ * wants to have concurrently open for this reserve
+ * @param reserve_pub public key of the reserve
+ * @param reserve_sig signature affirming the operation
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_ReserveOpenCallback)(
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_Amount *reserve_payment,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ struct GNUNET_TIME_Timestamp reserve_expiration,
+ uint32_t purse_limit,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig);
/**
@@ -1406,21 +3347,36 @@ typedef int
* @param reserve_pub public key of the reserve
* @param receiver_account where did we send the funds, in payto://-format
* @param wtid identifier used for the wire transfer
+ * @param close_request_row row with the responsible close
+ * request, 0 if regular expiration triggered close
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-typedef int
+typedef enum GNUNET_GenericReturnValue
(*TALER_EXCHANGEDB_ReserveClosedCallback)(
void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute execution_date,
+ struct GNUNET_TIME_Timestamp execution_date,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *closing_fee,
const struct TALER_ReservePublicKeyP *reserve_pub,
const char *receiver_account,
- const struct TALER_WireTransferIdentifierRawP *wtid);
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t close_request_row);
/**
+ * Function called with the amounts historically
+ * withdrawn from the same origin account.
+ *
+ * @param cls closure
+ * @param val one of the withdrawn amounts
+ */
+typedef void
+(*TALER_EXCHANGEDB_WithdrawHistoryCallback)(
+ void *cls,
+ const struct TALER_Amount *val);
+
+/**
* Function called with details about expired reserves.
*
* @param cls closure
@@ -1428,15 +3384,20 @@ typedef int
* @param left amount left in the reserve
* @param account_details information about the reserve's bank account, in payto://-format
* @param expiration_date when did the reserve expire
- * @return transaction status code to pass on
+ * @param close_request_row row that caused the reserve
+ * to be closed, 0 if it expired without request
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO to retry
+ * #GNUNET_SYSERR on hard failures (exit)
*/
-typedef enum GNUNET_DB_QueryStatus
+typedef enum GNUNET_GenericReturnValue
(*TALER_EXCHANGEDB_ReserveExpiredCallback)(
void *cls,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_Amount *left,
const char *account_details,
- struct GNUNET_TIME_Absolute expiration_date);
+ struct GNUNET_TIME_Timestamp expiration_date,
+ uint64_t close_request_row);
/**
@@ -1448,7 +3409,7 @@ typedef enum GNUNET_DB_QueryStatus
* @param coin information about the coin
* @param coin_sig signature of the coin of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
* @param coin_blind blinding key of the coin
- * @param h_blind_ev blinded envelope, as calculated by the exchange
+ * @param h_blinded_ev blinded envelope, as calculated by the exchange
* @param amount total amount to be paid back
*/
typedef void
@@ -1457,49 +3418,128 @@ typedef void
uint64_t rowid,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind,
- const struct GNUNET_HashCode *h_blinded_ev,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_blind,
+ const struct TALER_BlindedCoinHashP *h_blinded_ev,
const struct TALER_Amount *amount);
/**
- * Function called on deposits that are past their due date
- * and have not yet seen a wire transfer.
+ * Function called on (batch) deposits will need a wire
+ * transfer.
*
* @param cls closure
- * @param rowid deposit table row of the coin's deposit
- * @param coin_pub public key of the coin
- * @param amount value of the deposit, including fee
- * @param wire where should the funds be wired, including 'url' in payto://-format
- * @param deadline what was the requested wire transfer deadline
- * @param tiny did the exchange defer this transfer because it is too small?
- * @param done did the exchange claim that it made a transfer?
+ * @param batch_deposit_serial_id where in the table are we
+ * @param total_amount value of all missing deposits, including fees
+ * @param wire_target_h_payto hash of the recipient account's payto URI
+ * @param deadline what was the earliest requested wire transfer deadline
*/
typedef void
(*TALER_EXCHANGEDB_WireMissingCallback)(
void *cls,
+ uint64_t batch_deposit_serial_id,
+ const struct TALER_Amount *total_amount,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ struct GNUNET_TIME_Timestamp deadline);
+
+
+/**
+ * Function called on aggregations that were done for
+ * a (batch) deposit.
+ *
+ * @param cls closure
+ * @param tracking_serial_id where in the table are we
+ * @param batch_deposit_serial_id which batch deposit was aggregated
+ */
+typedef void
+(*TALER_EXCHANGEDB_AggregationCallback)(
+ void *cls,
+ uint64_t tracking_serial_id,
+ uint64_t batch_deposit_serial_id);
+
+
+/**
+ * Function called on purse requests.
+ *
+ * @param cls closure
+ * @param rowid purse request table row of the purse
+ * @param purse_pub public key of the purse
+ * @param merge_pub public key representing the merge capability
+ * @param purse_creation when was the purse created?
+ * @param purse_expiration when would an unmerged purse expire
+ * @param h_contract_terms contract associated with the purse
+ * @param age_limit the age limit for deposits into the purse
+ * @param target_amount amount to be put into the purse
+ * @param purse_sig signature of the purse over the initialization data
+ * @return #GNUNET_OK to continue to iterate
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_PurseRequestCallback)(
+ void *cls,
uint64_t rowid,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount,
- const json_t *wire,
- struct GNUNET_TIME_Absolute deadline,
- /* bool? */ int tiny,
- /* bool? */ int done);
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct GNUNET_TIME_Timestamp purse_creation,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t age_limit,
+ const struct TALER_Amount *target_amount,
+ const struct TALER_PurseContractSignatureP *purse_sig);
/**
* Function called with information about the exchange's denomination keys.
+ * Note that the 'master' field in @a issue will not yet be initialized when
+ * this function is called!
*
* @param cls closure
* @param denom_pub public key of the denomination
- * @param issue detailed information about the denomination (value, expiration times, fees)
+ * @param issue detailed information about the denomination (value, expiration times, fees);
*/
typedef void
(*TALER_EXCHANGEDB_DenominationCallback)(
void *cls,
- const struct
- TALER_DenominationPublicKey *denom_pub,
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue);
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
+
+
+/**
+ * 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
+ */
+typedef void
+(*TALER_EXCHANGEDB_AmlStatusCallback)(
+ void *cls,
+ uint64_t row_id,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_Amount *threshold,
+ enum TALER_AmlDecisionState status);
+
+
+/**
+ * Return historic AML decision.
+ *
+ * @param cls closure
+ * @param new_threshold new monthly threshold that would trigger an AML check
+ * @param new_status 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
+ */
+typedef void
+(*TALER_EXCHANGEDB_AmlHistoryCallback)(
+ void *cls,
+ const struct TALER_Amount *new_threshold,
+ enum TALER_AmlDecisionState new_status,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const char *justification,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ const struct TALER_AmlOfficerSignatureP *decider_sig);
/**
@@ -1520,16 +3560,6 @@ struct TALER_EXCHANGEDB_Plugin
*/
char *library_name;
- /**
- * Get the thread-local (!) database-handle.
- * Connect to the db if the connection does not exist yet.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @returns the database connection, or NULL on error
- */
- struct TALER_EXCHANGEDB_Session *
- (*get_session) (void *cls);
-
/**
* Drop the Taler tables. This should only be used in testcases.
@@ -1537,45 +3567,71 @@ struct TALER_EXCHANGEDB_Plugin
* @param cls the @e cls of this struct with the plugin-specific state
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
- int
- (*drop_tables) (void *cls);
-
+ enum GNUNET_GenericReturnValue
+ (*drop_tables)(void *cls);
/**
* Create the necessary tables if they are not present
*
* @param cls the @e cls of this struct with the plugin-specific state
+ * @param support_partitions true to enable partitioning support (disables foreign key constraints)
+ * @param num_partitions number of partitions to create,
+ * (0 to not actually use partitions, 1 to only
+ * setup a default partition, >1 for real partitions)
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
- int
- (*create_tables) (void *cls);
+ enum GNUNET_GenericReturnValue
+ (*create_tables)(void *cls,
+ bool support_partitions,
+ uint32_t num_partitions);
/**
* Start a transaction.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @param name unique name identifying the transaction (for debugging),
* must point to a constant
* @return #GNUNET_OK on success
*/
- int
- (*start) (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const char *name);
+ enum GNUNET_GenericReturnValue
+ (*start)(void *cls,
+ const char *name);
+
+
+ /**
+ * Start a READ COMMITTED transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging)
+ * must point to a constant
+ * @return #GNUNET_OK on success
+ */
+ enum GNUNET_GenericReturnValue
+ (*start_read_committed)(void *cls,
+ const char *name);
+
+ /**
+ * Start a READ ONLY serializable transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging)
+ * must point to a constant
+ * @return #GNUNET_OK on success
+ */
+ enum GNUNET_GenericReturnValue
+ (*start_read_only)(void *cls,
+ const char *name);
/**
* Commit a transaction.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*commit)(void *cls,
- struct TALER_EXCHANGEDB_Session *session);
+ (*commit)(void *cls);
/**
@@ -1584,22 +3640,65 @@ struct TALER_EXCHANGEDB_Plugin
* Does not return anything, as we will continue regardless of the outcome.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session the database connection
+ * @return #GNUNET_OK if everything is fine
+ * #GNUNET_NO if a transaction was rolled back
+ * #GNUNET_SYSERR on hard errors
*/
- void
- (*preflight) (void *cls,
- struct TALER_EXCHANGEDB_Session *session);
+ enum GNUNET_GenericReturnValue
+ (*preflight)(void *cls);
/**
* Abort/rollback a transaction.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
*/
void
- (*rollback) (void *cls,
- struct TALER_EXCHANGEDB_Session *session);
+ (*rollback) (void *cls);
+
+
+ /**
+ * Register callback to be invoked on events of type @a es.
+ *
+ * @param cls database context to use
+ * @param timeout how long to wait at most
+ * @param es specification of the event to listen for
+ * @param cb function to call when the event happens, possibly
+ * multiple times (until cancel is invoked)
+ * @param cb_cls closure for @a cb
+ * @return handle useful to cancel the listener
+ */
+ struct GNUNET_DB_EventHandler *
+ (*event_listen)(void *cls,
+ struct GNUNET_TIME_Relative timeout,
+ const struct GNUNET_DB_EventHeaderP *es,
+ GNUNET_DB_EventCallback cb,
+ void *cb_cls);
+
+ /**
+ * Stop notifications.
+ *
+ * @param cls database context to use
+ * @param eh handle to unregister.
+ */
+ void
+ (*event_listen_cancel)(void *cls,
+ struct GNUNET_DB_EventHandler *eh);
+
+
+ /**
+ * Notify all that listen on @a es of an event.
+ *
+ * @param cls database context to use
+ * @param es specification of the event to generate
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+ void
+ (*event_notify)(void *cls,
+ const struct GNUNET_DB_EventHeaderP *es,
+ const void *extra,
+ size_t extra_size);
/**
@@ -1608,7 +3707,6 @@ struct TALER_EXCHANGEDB_Plugin
* with this key have.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @param denom_pub the public key used for signing coins of this denomination
* @param issue issuing information with value, fees and other info about the denomination
* @return status of the query
@@ -1616,16 +3714,14 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*insert_denomination_info)(
void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue);
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
/**
* Fetch information about a denomination key.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @param denom_pub_hash hash of the public key used for signing coins of this denomination
* @param[out] issue set to issue information with value, fees and other info about the coin
* @return transaction status code
@@ -1633,14 +3729,15 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*get_denomination_info)(
void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash,
- struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue);
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
/**
* Function called on every known denomination key. Runs in its
- * own read-only transaction (hence no session provided).
+ * own read-only transaction (hence no session provided). Note that
+ * the "master" field in the callback's 'issue' argument will NOT
+ * be initialized yet.
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param cb function to call on each denomination key
@@ -1652,11 +3749,74 @@ struct TALER_EXCHANGEDB_Plugin
TALER_EXCHANGEDB_DenominationCallback cb,
void *cb_cls);
+
+ /**
+ * Function called to invoke @a cb on every known denomination key (revoked
+ * and non-revoked) that has been signed by the master key. Runs in its own
+ * read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each denomination key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*iterate_denominations)(void *cls,
+ TALER_EXCHANGEDB_DenominationsCallback cb,
+ void *cb_cls);
+
+ /**
+ * Function called to invoke @a cb on every non-revoked exchange signing key
+ * that has been signed by the master key. Revoked and (for signing!)
+ * expired keys are skipped. Runs in its own read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each signing key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*iterate_active_signkeys)(void *cls,
+ TALER_EXCHANGEDB_ActiveSignkeysCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Function called to invoke @a cb on every active auditor. Disabled
+ * auditors are skipped. Runs in its own read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each active auditor
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*iterate_active_auditors)(void *cls,
+ TALER_EXCHANGEDB_AuditorsCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Function called to invoke @a cb on every denomination with an active
+ * auditor. Disabled auditors and denominations without auditor are
+ * skipped. Runs in its own read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each active auditor-denomination pair
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*iterate_auditor_denominations)(
+ void *cls,
+ TALER_EXCHANGEDB_AuditorDenominationsCallback cb,
+ void *cb_cls);
+
+
/**
* Get the summary of a reserve.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session the database connection handle
* @param[in,out] reserve the reserve data. The public key of the reserve should be set
* in this structure; it is used to query the database. The balance
* and expiration are then filled accordingly.
@@ -1664,107 +3824,447 @@ struct TALER_EXCHANGEDB_Plugin
*/
enum GNUNET_DB_QueryStatus
(*reserves_get)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
struct TALER_EXCHANGEDB_Reserve *reserve);
/**
- * Insert a incoming transaction into reserves. New reserves are
+ * Get the origin of funds of a reserve.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @param[out] h_payto set to hash of the wire source payto://-URI
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*reserves_get_origin)(
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_PaytoHashP *h_payto);
+
+
+ /**
+ * Extract next KYC alert. Deletes the alert.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param trigger_type which type of alert to drain
+ * @param[out] h_payto set to hash of payto-URI where KYC status changed
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*drain_kyc_alert)(void *cls,
+ uint32_t trigger_type,
+ struct TALER_PaytoHashP *h_payto);
+
+
+ /**
+ * Insert a batch of incoming transaction into reserves. New reserves are
* also created through this function.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session the database session handle
- * @param reserve_pub public key of the reserve
- * @param balance the amount that has to be added to the reserve
- * @param execution_time when was the amount added
- * @param sender_account_details information about the sender's bank account, in payto://-format
- * @param wire_reference unique reference identifying the wire transfer
- * @return transaction status code
+ * @param reserves
+ * @param reserves_length length of the @a reserves array
+ * @param[out] results array of transaction status codes of length @a reserves_length,
+ * set to the status of the
*/
enum GNUNET_DB_QueryStatus
- (*reserves_in_insert)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_Amount *balance,
- struct GNUNET_TIME_Absolute execution_time,
- const char *sender_account_details,
- const char *exchange_account_name,
- uint64_t wire_reference);
+ (*reserves_in_insert)(
+ void *cls,
+ const struct TALER_EXCHANGEDB_ReserveInInfo *reserves,
+ unsigned int reserves_length,
+ enum GNUNET_DB_QueryStatus *results);
/**
- * Obtain the most recent @a wire_reference that was inserted via @e reserves_in_insert.
- * Used by the wirewatch process when resuming.
+ * Locate a nonce for use with a particular public key.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session the database connection handle
- * @param exchange_account_name name of the section in the exchange's configuration
- * for the account that we are tracking here
- * @param[out] wire_reference set to unique reference identifying the wire transfer
- * @return transaction status code
+ * @param nonce the nonce to be locked
+ * @param denom_pub_hash hash of the public key of the denomination
+ * @param target public key the nonce is to be locked to
+ * @return statement execution status
*/
enum GNUNET_DB_QueryStatus
- (*get_latest_reserve_in_reference)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const char *exchange_account_name,
- uint64_t *wire_reference);
+ (*lock_nonce)(void *cls,
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ const union TALER_EXCHANGEDB_NonceLockTargetP *target);
/**
- * Locate the response for a withdraw request under the
- * key of the hash of the blinded message. Used to ensure
- * idempotency of the request.
+ * Locate the response for a withdraw request under a hash that uniquely
+ * identifies the withdraw operation. Used to ensure idempotency of the
+ * request.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session database connection to use
- * @param h_blind hash of the blinded coin to be signed (will match
- * `h_coin_envelope` in the @a collectable to be returned)
- * @param collectable corresponding collectable coin (blind signature)
+ * @param bch hash that uniquely identifies the withdraw operation
+ * @param[out] collectable corresponding collectable coin (blind signature)
* if a coin is found
* @return statement execution status
*/
enum GNUNET_DB_QueryStatus
(*get_withdraw_info)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *h_blind,
- struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable);
+ const struct TALER_BlindedCoinHashP *bch,
+ struct TALER_EXCHANGEDB_CollectableBlindcoin *
+ collectable);
/**
- * Store collectable coin under the corresponding hash of the blinded
- * message.
+ * FIXME: merge do_batch_withdraw and do_batch_withdraw_insert into one API,
+ * which takes as input (among others)
+ * - denom_serial[]
+ * - blinded_coin_evs[]
+ * - denom_sigs[]
+ * The implementation should persist the data as _arrays_ in the DB.
+ */
+
+ /**
+ * Perform reserve update as part of a batch withdraw operation, checking
+ * for sufficient balance. Persisting the withdrawal details is done
+ * separately!
*
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session database connection to use
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param now current time (rounded)
+ * @param reserve_pub public key of the reserve to debit
+ * @param amount total amount to withdraw
+ * @param do_age_check if set, the batch-withdrawal can only succeed when the reserve has no age restriction (birthday) set.
+ * @param[out] found set to true if the reserve was found
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] reserve_balance set to original balance of the reserve
+ * @param[out] age_ok set to true if no age requirements were defined on the reserve or @e do_age_check was false
+ * @param[out] allowed_maximum_age when @e age_ok is false, set to the allowed maximum age for withdrawal from the reserve. The client MUST then use the age-withdraw endpoint
+ * @param[out] ruuid set to the reserve's UUID (reserves table row)
+ * @return query execution status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_batch_withdraw)(
+ void *cls,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *amount,
+ bool do_age_check,
+ bool *found,
+ bool *balance_ok,
+ struct TALER_Amount *reserve_balance,
+ bool *age_ok,
+ uint16_t *allowed_maximum_age,
+ uint64_t *ruuid);
+
+
+ /**
+ * Perform insert as part of a batch withdraw operation, and persisting the
+ * withdrawal details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param nonce client-contributed input for CS denominations that must be checked for idempotency, or NULL for non-CS withdrawals
* @param collectable corresponding collectable coin (blind signature)
- * if a coin is found
+ * @param now current time (rounded)
+ * @param ruuid reserve UUID
+ * @param[out] denom_unknown set if the denomination is unknown in the DB
+ * @param[out] conflict if the envelope was already in the DB
+ * @param[out] nonce_reuse if @a nonce was non-NULL and reused
+ * @return query execution status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_batch_withdraw_insert)(
+ void *cls,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+ struct GNUNET_TIME_Timestamp now,
+ uint64_t ruuid,
+ bool *denom_unknown,
+ bool *conflict,
+ bool *nonce_reuse);
+
+ /**
+ * Locate the response for a age-withdraw request under a hash of the
+ * commitment and reserve_pub that uniquely identifies the age-withdraw
+ * operation. Used to ensure idempotency of the request.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param reserve_pub public key of the reserve for which the age-withdraw request is made
+ * @param ach hash that uniquely identifies the age-withdraw operation
+ * @param[out] aw corresponding details of the previous age-withdraw request if an entry was found
* @return statement execution status
*/
enum GNUNET_DB_QueryStatus
- (*insert_withdraw_info)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct
- TALER_EXCHANGEDB_CollectableBlindcoin *collectable);
+ (*get_age_withdraw)(
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_AgeWithdrawCommitmentHashP *ach,
+ struct TALER_EXCHANGEDB_AgeWithdraw *aw);
+
+ /**
+ * Perform an age-withdraw operation, checking for sufficient balance and
+ * fulfillment of age requirements and possibly persisting the withdrawal
+ * details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param commitment corresponding commitment for the age-withdraw
+ * @param[out] found set to true if the reserve was found
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] reserve_balance set to original balance of the reserve
+ * @param[out] age_ok set to true if age requirements were met
+ * @param[out] allowed_maximum_age if @e age_ok is FALSE, this is set to the allowed maximum age
+ * @param[out] reserve_birthday if @e age_ok is FALSE, this is set to the reserve's birthday
+ * @return query execution status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_age_withdraw)(
+ void *cls,
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
+ struct GNUNET_TIME_Timestamp now,
+ bool *found,
+ bool *balance_ok,
+ struct TALER_Amount *reserve_balance,
+ bool *age_ok,
+ uint16_t *allowed_maximum_age,
+ uint32_t *reserve_birthday,
+ bool *conflict);
+
+ /**
+ * Retrieve the details to a policy given by its hash_code
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param hc Hash code that identifies the policy
+ * @param[out] detail retrieved policy details
+ * @return query execution status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_policy_details)(
+ void *cls,
+ const struct GNUNET_HashCode *hc,
+ struct TALER_PolicyDetails *detail);
+
+ /**
+ * Persist the policy details that extends a deposit. The particular policy
+ * - referenced by details->hash_code - might already exist in the table, in
+ * which case the call will update the contents of the record with @e details
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param details The parsed `struct TALER_PolicyDetails` according to the responsible policy extension.
+ * @param[out] policy_details_serial_id The ID of the entry in the policy_details table
+ * @param[out] accumulated_total The total amount accumulated in that policy
+ * @param[out] fulfillment_state The state of policy. If the state was Insufficient prior to the call and the provided deposit raises the accumulated_total above the commitment, it will be set to Ready.
+ * @return query execution status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*persist_policy_details)(
+ void *cls,
+ const struct TALER_PolicyDetails *details,
+ uint64_t *policy_details_serial_id,
+ struct TALER_Amount *accumulated_total,
+ enum TALER_PolicyFulfillmentState *fulfillment_state);
/**
- * Get all of the transaction history associated with the specified
- * reserve.
+ * Perform deposit operation, checking for sufficient balance
+ * of the coin and possibly persisting the deposit details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param bd batch deposit operation details
+ * @param[in,out] exchange_timestamp time to use for the deposit (possibly updated)
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] bad_balance_index set to the first index of a coin for which the balance was insufficient,
+ * only used if @a balance_ok is set to false.
+ * @param[out] ctr_conflict set to true if the same contract terms hash was previously submitted with other meta data (deadlines, wallet_data_hash, wire data etc.)
+ * @return query execution status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_deposit)(
+ void *cls,
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd,
+ struct GNUNET_TIME_Timestamp *exchange_timestamp,
+ bool *balance_ok,
+ uint32_t *bad_balance_index,
+ bool *ctr_conflict);
+
+
+ /**
+ * Perform melt operation, checking for sufficient balance
+ * of the coin and possibly persisting the melt details.
+ *
+ * @param cls the plugin-specific state
+ * @param rms client-contributed input for CS denominations that must be checked for idempotency, or NULL for non-CS withdrawals
+ * @param[in,out] refresh refresh operation details; the noreveal_index
+ * is set in case the coin was already melted before
+ * @param known_coin_id row of the coin in the known_coins table
+ * @param[in,out] zombie_required true if the melt must only succeed if the coin is a zombie, set to false if the requirement was satisfied
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @return query execution status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_melt)(
+ void *cls,
+ const struct TALER_RefreshMasterSecretP *rms,
+ struct TALER_EXCHANGEDB_Refresh *refresh,
+ uint64_t known_coin_id,
+ bool *zombie_required,
+ bool *balance_ok);
+
+
+ /**
+ * Add a proof of fulfillment of an policy
+ *
+ * @param cls the plugin-specific state
+ * @param[in,out] fulfillment The proof of fulfillment and serial_ids of the policy_details along with their new state and potential new amounts.
+ * @return query execution status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*add_policy_fulfillment_proof)(
+ void *cls,
+ struct TALER_PolicyFulfillmentTransactionData *fulfillment);
+
+
+ /**
+ * Check if the given @a nonce was properly locked to the given @a old_coin_pub. If so, check if we already
+ * created CS signatures for the given @a nonce and @a new_denom_pub_hashes,
+ * and if so, return them in @a s_scalars. Otherwise, persist the
+ * signatures from @a s_scalars in the database.
+ *
+ * @param cls the plugin-specific state
+ * @param nonce the client-provided nonce where we must prevent reuse
+ * @param old_coin_pub public key the nonce was locked to
+ * @param num_fresh_coins array length, number of fresh coins revealed
+ * @param[in,out] crfcds array of data about the fresh coins, of length @a num_fresh_coins
+ * @return query execution status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*cs_refreshes_reveal)(
+ void *cls,
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce,
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ unsigned int num_fresh_coins,
+ struct TALER_EXCHANGEDB_CsRevealFreshCoinData *crfcds);
+
+
+ /**
+ * Perform refund operation, checking for sufficient deposits
+ * of the coin and possibly persisting the refund details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param refund refund operation details
+ * @param deposit_fee deposit fee applicable for the coin, possibly refunded
+ * @param known_coin_id row of the coin in the known_coins table
+ * @param[out] not_found set if the deposit was not found
+ * @param[out] refund_ok set if the refund succeeded (below deposit amount)
+ * @param[out] gone if the merchant was already paid
+ * @param[out] conflict set if the refund ID was re-used
+ * @return query execution status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_refund)(
+ void *cls,
+ const struct TALER_EXCHANGEDB_Refund *refund,
+ const struct TALER_Amount *deposit_fee,
+ uint64_t known_coin_id,
+ bool *not_found,
+ bool *refund_ok,
+ bool *gone,
+ bool *conflict);
+
+
+ /**
+ * Perform recoup operation, checking for sufficient deposits
+ * of the coin and possibly persisting the recoup details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_pub public key of the reserve to credit
+ * @param reserve_out_serial_id row in the reserves_out table justifying the recoup
+ * @param coin_bks coin blinding key secret to persist
+ * @param coin_pub public key of the coin being recouped
+ * @param known_coin_id row of the @a coin_pub in the known_coins table
+ * @param coin_sig signature of the coin requesting the recoup
+ * @param[in,out] recoup_timestamp recoup timestamp, set if recoup existed
+ * @param[out] recoup_ok set if the recoup succeeded (balance ok)
+ * @param[out] internal_failure set on internal failures
+ * @return query execution status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_recoup)(
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t reserve_out_serial_id,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t known_coin_id,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ struct GNUNET_TIME_Timestamp *recoup_timestamp,
+ bool *recoup_ok,
+ bool *internal_failure);
+
+
+ /**
+ * Perform recoup-refresh operation, checking for sufficient deposits of the
+ * coin and possibly persisting the recoup-refresh details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param old_coin_pub public key of the old coin to credit
+ * @param rrc_serial row in the refresh_revealed_coins table justifying the recoup-refresh
+ * @param coin_bks coin blinding key secret to persist
+ * @param coin_pub public key of the coin being recouped
+ * @param known_coin_id row of the @a coin_pub in the known_coins table
+ * @param coin_sig signature of the coin requesting the recoup
+ * @param[in,out] recoup_timestamp recoup timestamp, set if recoup existed
+ * @param[out] recoup_ok set if the recoup-refresh succeeded (balance ok)
+ * @param[out] internal_failure set on internal failures
+ * @return query execution status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_recoup_refresh)(
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ uint64_t rrc_serial,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t known_coin_id,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ struct GNUNET_TIME_Timestamp *recoup_timestamp,
+ bool *recoup_ok,
+ bool *internal_failure);
+
+
+ /**
+ * Compile a list of (historic) transactions performed with the given reserve
+ * (withdraw, incoming wire, open, close operations). Should return 0 if the @a
+ * reserve_pub is unknown, otherwise determine @a etag_out and if it is past @a
+ * etag_in return the history after @a start_off. @a etag_out should be set
+ * to the last row ID of the given @a reserve_pub in the reserve history table.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @param reserve_pub public key of the reserve
+ * @param start_off maximum starting offset in history to exclude from returning
+ * @param etag_in up to this offset the client already has a response, do not
+ * return anything unless @a etag_out will be larger
+ * @param[out] etag_out set to the latest history offset known for this @a coin_pub
+ * @param[out] balance set to the reserve balance
* @param[out] rhp set to known transaction history (NULL if reserve is unknown)
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
(*get_reserve_history)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t start_off,
+ uint64_t etag_in,
+ uint64_t *etag_out,
+ struct TALER_Amount *balance,
struct TALER_EXCHANGEDB_ReserveHistory **rhp);
/**
+ * The current reserve balance of the specified reserve.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @param[out] balance set to the reserve balance
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_reserve_balance)(void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_Amount *balance);
+
+
+ /**
* Free memory associated with the given reserve history.
*
* @param cls the @e cls of this struct with the plugin-specific state
@@ -1779,104 +4279,195 @@ struct TALER_EXCHANGEDB_Plugin
* Count the number of known coins by denomination.
*
* @param cls database connection plugin state
- * @param session database session
* @param denom_pub_hash denomination to count by
* @return number of coins if non-negative, otherwise an `enum GNUNET_DB_QueryStatus`
*/
long long
(*count_known_coins) (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash);
+ const struct TALER_DenominationHashP *denom_pub_hash);
/**
* Make sure the given @a coin is known to the database.
*
* @param cls database connection plugin state
- * @param session database session
* @param coin the coin that must be made known
+ * @param[out] known_coin_id set to the unique row of the coin
+ * @param[out] denom_pub_hash set to the conflicting denomination hash on conflict
+ * @param[out] age_hash set to the conflicting age hash on conflict
* @return database transaction status, non-negative on success
*/
- enum GNUNET_DB_QueryStatus
+ enum TALER_EXCHANGEDB_CoinKnownStatus
+ {
+ /**
+ * The coin was successfully added.
+ */
+ TALER_EXCHANGEDB_CKS_ADDED = 1,
+
+ /**
+ * The coin was already present.
+ */
+ TALER_EXCHANGEDB_CKS_PRESENT = 0,
+
+ /**
+ * Serialization failure.
+ */
+ TALER_EXCHANGEDB_CKS_SOFT_FAIL = -1,
+
+ /**
+ * Hard database failure.
+ */
+ TALER_EXCHANGEDB_CKS_HARD_FAIL = -2,
+
+ /**
+ * Conflicting coin (different denomination key) already in database.
+ */
+ TALER_EXCHANGEDB_CKS_DENOM_CONFLICT = -3,
+
+ /**
+ * Conflicting coin (expected NULL age hash) already in database.
+ */
+ TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL = -4,
+
+ /**
+ * Conflicting coin (unexpected NULL age hash) already in database.
+ */
+ TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL = -5,
+
+ /**
+ * Conflicting coin (different age hash) already in database.
+ */
+ TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS = -6,
+
+ }
(*ensure_coin_known)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinPublicInfo *coin);
+ const struct TALER_CoinPublicInfo *coin,
+ uint64_t *known_coin_id,
+ struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_AgeCommitmentHash *age_hash);
+
+
+ /**
+ * Make sure the array of given @a coin is known to the database.
+ *
+ * @param cls database connection plugin state
+ * @param coin array of coins that must be made known
+ * @param[out] result array where to store information about each coin
+ * @param coin_length length of the @a coin and @a result arraysf
+ * @param batch_size desired (maximum) batch size
+ * @return database transaction status, non-negative on success
+ */
+ enum GNUNET_DB_QueryStatus
+ (*batch_ensure_coin_known)(
+ void *cls,
+ const struct TALER_CoinPublicInfo *coin,
+ struct TALER_EXCHANGEDB_CoinInfo *result,
+ unsigned int coin_length,
+ unsigned int batch_size);
/**
* Retrieve information about the given @a coin from the database.
*
* @param cls database connection plugin state
- * @param session database session
- * @param coin the coin that must be made known
+ * @param coin_pub the coin that must be made known
+ * @param[out] coin_info detailed information about the coin
* @return database transaction status, non-negative on success
*/
enum GNUNET_DB_QueryStatus
(*get_known_coin)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
struct TALER_CoinPublicInfo *coin_info);
+ /**
+ * Retrieve the signature and corresponding denomination for a given @a coin
+ * from the database
+ *
+ * @param cls database connection plugin state
+ * @param coin_pub the public key of the coin we search for
+ * @param[out] denom_pub the public key of the denomination that the coin was signed with
+ * @param[out] denom_sig the signature with the denomination's private key over the coin_pub
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_signature_for_known_coin)(
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_DenominationPublicKey *denom_pub,
+ struct TALER_DenominationSignature *denom_sig);
/**
* Retrieve the denomination of a known coin.
*
* @param cls the plugin closure
- * @param session the database session handle
* @param coin_pub the public key of the coin to search for
+ * @param[out] known_coin_id set to the ID of the coin in the known_coins table
* @param[out] denom_hash where to store the hash of the coins denomination
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
(*get_coin_denomination)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
- struct GNUNET_HashCode *denom_hash);
+ uint64_t *known_coin_id,
+ struct TALER_DenominationHashP *denom_hash);
/**
- * Check if we have the specified deposit already in the database.
+ * Try to retrieve the salted hash of the merchant's bank account to a
+ * deposit contract. Used in case of conflicts for a given (merchant_pub,
+ * h_contract_terms) to provide the client the necessary input to retrieve
+ * more details about the conflict.
*
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session database connection
- * @param deposit deposit to search for
- * @param check_extras whether to check extra fields or not
- * @return 1 if we know this operation,
- * 0 if this exact deposit is unknown to us,
- * otherwise transaction error status
+ * @param cls the plugin closure
+ * @param merchant_pub public key of the merchant
+ * @param h_contract_terms contract to check for
+ * @param[out] h_wire hash of the wire details
*/
enum GNUNET_DB_QueryStatus
- (*have_deposit)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_EXCHANGEDB_Deposit *deposit,
- int check_extras);
+ (*get_wire_hash_for_contract)(
+ void *cls,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct TALER_MerchantWireHashP *h_wire);
/**
- * Insert information about deposited coin into the database.
+ * Check if we have the specified deposit already in the database.
*
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param deposit deposit information to store
- * @return query result status
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param h_contract_terms contract to check for
+ * @param h_wire wire hash to check for
+ * @param coin_pub public key of the coin to check for
+ * @param merchant merchant public key to check for
+ * @param refund_deadline expected refund deadline
+ * @param[out] deposit_fee set to the deposit fee the exchange charged
+ * @param[out] exchange_timestamp set to the time when the exchange received the deposit
+ * @return 1 if we know this operation,
+ * 0 if this exact deposit is unknown to us,
+ * otherwise transaction error status
*/
+ // FIXME: rename!
enum GNUNET_DB_QueryStatus
- (*insert_deposit)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_EXCHANGEDB_Deposit *deposit);
+ (*have_deposit2)(
+ void *cls,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct TALER_Amount *deposit_fee,
+ struct GNUNET_TIME_Timestamp *exchange_timestamp);
/**
* Insert information about refunded coin into the database.
+ * Used in tests and for benchmarking.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
* @param refund refund information to store
* @return query result status
*/
enum GNUNET_DB_QueryStatus
(*insert_refund)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const struct TALER_EXCHANGEDB_Refund *refund);
@@ -1884,7 +4475,6 @@ struct TALER_EXCHANGEDB_Plugin
* Select refunds by @a coin_pub, @a merchant_pub and @a h_contract.
*
* @param cls closure of plugin
- * @param session database handle to use
* @param coin_pub coin to get refunds for
* @param merchant_pub merchant to get refunds for
* @param h_contract_pub contract (hash) to get refunds for
@@ -1893,174 +4483,169 @@ struct TALER_EXCHANGEDB_Plugin
* @return query result status
*/
enum GNUNET_DB_QueryStatus
- (*select_refunds_by_coin)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_contract,
- TALER_EXCHANGEDB_RefundCoinCallback cb,
- void *cb_cls);
+ (*select_refunds_by_coin)(
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PrivateContractHashP *h_contract,
+ TALER_EXCHANGEDB_RefundCoinCallback cb,
+ void *cb_cls);
/**
- * Mark a deposit as tiny, thereby declaring that it cannot be executed by
- * itself (only included in a larger aggregation) and should no longer be
- * returned by @e iterate_ready_deposits()
+ * Obtain information about deposits that are ready to be executed.
+ * Such deposits must not be marked as "done", and the
+ * execution time, the refund deadlines must both be in the past and
+ * the KYC status must be 'ok'.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param deposit_rowid identifies the deposit row to modify
- * @return query result status
+ * @param start_shard_row minimum shard row to select
+ * @param end_shard_row maximum shard row to select (inclusive)
+ * @param[out] merchant_pub set to the public key of a merchant with a ready deposit
+ * @param[out] payto_uri set to the account of the merchant, to be freed by caller
+ * @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*mark_deposit_tiny)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t rowid);
+ (*get_ready_deposit)(void *cls,
+ uint64_t start_shard_row,
+ uint64_t end_shard_row,
+ struct TALER_MerchantPublicKeyP *merchant_pub,
+ char **payto_uri);
/**
- * Test if a deposit was marked as done, thereby declaring that it
- * cannot be refunded anymore.
+ * Aggregate all matching deposits for @a h_payto and
+ * @a merchant_pub, returning the total amounts.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param coin_pub the coin to check for deposit
- * @param merchant_pub merchant to receive the deposit
- * @param h_contract_terms contract terms of the deposit
- * @param h_wire hash of the merchant's wire details
- * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if is is marked done,
- * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if not,
- * otherwise transaction error status (incl. deposit unknown)
+ * @param h_payto destination of the wire transfer
+ * @param merchant_pub public key of the merchant
+ * @param wtid wire transfer ID to set for the aggregate
+ * @param[out] total set to the sum of the total deposits minus applicable deposit fees and refunds
+ * @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*test_deposit_done)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct GNUNET_HashCode *h_wire);
+ (*aggregate)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_Amount *total);
/**
- * Mark a deposit as done, thereby declaring that it cannot be
- * executed at all anymore, and should no longer be returned by
- * @e iterate_ready_deposits() or @e iterate_matching_deposits().
+ * Create a new entry in the transient aggregation table.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param deposit_rowid identifies the deposit row to modify
- * @return query result status
+ * @param h_payto destination of the wire transfer
+ * @param exchange_account_section exchange account to use
+ * @param merchant_pub public key of the merchant
+ * @param wtid the raw wire transfer identifier to be used
+ * @param kyc_requirement_row row in legitimization_requirements that need to be satisfied to continue, or 0 for none
+ * @param total amount to be wired in the future
+ * @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*mark_deposit_done)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t rowid);
+ (*create_aggregation_transient)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *exchange_account_section,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t kyc_requirement_row,
+ const struct TALER_Amount *total);
/**
- * Obtain information about deposits that are ready to be executed.
- * Such deposits must not be marked as "tiny" or "done", and the
- * execution time and refund deadlines must both be in the past.
+ * Select existing entry in the transient aggregation table.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param deposit_cb function to call for ONE such deposit
- * @param deposit_cb_cls closure for @a deposit_cb
- * @return transaction status code
+ * @param h_payto destination of the wire transfer
+ * @param merchant_pub public key of the merchant
+ * @param exchange_account_section exchange account to use
+ * @param[out] wtid set to the raw wire transfer identifier to be used
+ * @param[out] total existing amount to be wired in the future
+ * @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*get_ready_deposit)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- TALER_EXCHANGEDB_DepositIterator deposit_cb,
- void *deposit_cb_cls);
+ (*select_aggregation_transient)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const char *exchange_account_section,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_Amount *total);
-/**
- * Maximum number of results we return from iterate_matching_deposits().
- *
- * Limit on the number of transactions we aggregate at once. Note
- * that the limit must be big enough to ensure that when transactions
- * of the smallest possible unit are aggregated, they do surpass the
- * "tiny" threshold beyond which we never trigger a wire transaction!
- */
-#define TALER_EXCHANGEDB_MATCHING_DEPOSITS_LIMIT 10000
+ /**
+ * Find existing entry in the transient aggregation table.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto destination of the wire transfer
+ * @param cb function to call on each matching entry
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*find_aggregation_transient)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_TransientAggregationCallback cb,
+ void *cb_cls);
+
/**
- * Obtain information about other pending deposits for the same
- * destination. Those deposits must not already be "done".
+ * Update existing entry in the transient aggregation table.
+ * @a h_payto is only needed for query performance.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to the database
- * @param h_wire destination of the wire transfer
- * @param merchant_pub public key of the merchant
- * @param deposit_cb function to call for each deposit
- * @param deposit_cb_cls closure for @a deposit_cb
- * @param limit maximum number of matching deposits to return; should
- * be #TALER_EXCHANGEDB_MATCHING_DEPOSITS_LIMIT, larger values
- * are not supported, smaller values would be inefficient.
- * @return number of rows processed, 0 if none exist,
- * transaction status code on error
+ * @param h_payto destination of the wire transfer
+ * @param wtid the raw wire transfer identifier to update
+ * @param kyc_requirement_row row in legitimization_requirements that need to be satisfied to continue, or 0 for none
+ * @param total new total amount to be wired in the future
+ * @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*iterate_matching_deposits)(
+ (*update_aggregation_transient)(
void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *h_wire,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- TALER_EXCHANGEDB_DepositIterator deposit_cb,
- void *deposit_cb_cls,
- uint32_t limit);
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t kyc_requirement_row,
+ const struct TALER_Amount *total);
/**
- * Store new melt commitment data.
+ * Delete existing entry in the transient aggregation table.
+ * @a h_payto is only needed for query performance.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session database handle to use
- * @param refresh_session operational data to store
- * @return query status for the transaction
+ * @param h_payto destination of the wire transfer
+ * @param wtid the raw wire transfer identifier to update
+ * @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*insert_melt)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_EXCHANGEDB_Refresh *refresh_session);
+ (*delete_aggregation_transient)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid);
/**
* Lookup melt commitment data under the given @a rc.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session database handle to use
* @param rc commitment to use for the lookup
* @param[out] melt where to store the result; note that
* melt->session.coin.denom_sig will be set to NULL
* and is not fetched by this routine (as it is not needed by the client)
+ * @param[out] melt_serial_id set to the row ID of @a rc in the refresh_commitments table
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
(*get_melt)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const struct TALER_RefreshCommitmentP *rc,
- struct TALER_EXCHANGEDB_Melt *melt);
-
-
- /**
- * Lookup noreveal index of a previous melt operation under the given
- * @a rc.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session database handle to use
- * @param rc commitment hash to use to locate the operation
- * @param[out] noreveal_index returns the "gamma" value selected by the
- * exchange which is the index of the transfer key that is
- * not to be revealed to the exchange
- * @return transaction status
- */
- enum GNUNET_DB_QueryStatus
- (*get_melt_index)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_RefreshCommitmentP *rc,
- uint32_t *noreveal_index);
+ struct TALER_EXCHANGEDB_Melt *melt,
+ uint64_t *melt_serial_id);
/**
@@ -2069,8 +4654,7 @@ struct TALER_EXCHANGEDB_Plugin
* we learned or created in the reveal step.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session database connection
- * @param rc identify commitment and thus refresh operation
+ * @param melt_serial_id row ID of the commitment / melt operation in refresh_commitments
* @param num_rrcs number of coins to generate, size of the @a rrcs array
* @param rrcs information about the new coins
* @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
@@ -2081,8 +4665,7 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*insert_refresh_reveal)(
void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_RefreshCommitmentP *rc,
+ uint64_t melt_serial_id,
uint32_t num_rrcs,
const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
unsigned int num_tprivs,
@@ -2091,11 +4674,10 @@ struct TALER_EXCHANGEDB_Plugin
/**
- * Lookup in the database for the @a num_freshcoins coins that we
+ * Lookup in the database for the fresh coins that we
* created in the given refresh operation.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session database connection
* @param rc identify commitment and thus refresh operation
* @param cb function to call with the results
* @param cb_cls closure for @a cb
@@ -2103,7 +4685,6 @@ struct TALER_EXCHANGEDB_Plugin
*/
enum GNUNET_DB_QueryStatus
(*get_refresh_reveal)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const struct TALER_RefreshCommitmentP *rc,
TALER_EXCHANGEDB_RefreshCallback cb,
void *cb_cls);
@@ -2116,7 +4697,6 @@ struct TALER_EXCHANGEDB_Plugin
* the private keys of the new coins after the melt.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session database connection
* @param coin_pub public key of the coin
* @param ldc function to call for each session the coin was melted into
* @param ldc_cls closure for @a tdc
@@ -2124,29 +4704,41 @@ struct TALER_EXCHANGEDB_Plugin
*/
enum GNUNET_DB_QueryStatus
(*get_link_data)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
TALER_EXCHANGEDB_LinkCallback ldc,
void *tdc_cls);
/**
- * Compile a list of all (historic) transactions performed
- * with the given coin (melt, refund, recoup and deposit operations).
+ * Compile a list of (historic) transactions performed with the given coin
+ * (melt, refund, recoup and deposit operations). Should return 0 if the @a
+ * coin_pub is unknown, otherwise determine @a etag_out and if it is past @a
+ * etag_in return the history after @a start_off. @a etag_out should be set
+ * to the last row ID of the given @a coin_pub in the coin history table.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session database connection
* @param coin_pub coin to investigate
- * @param include_recoup include recoup transactions of the coin?
- * @param[out] tlp set to list of transactions, NULL if coin is fresh
+ * @param start_off starting offset from which on to return entries
+ * @param etag_in up to this offset the client already has a response, do not
+ * return anything unless @a etag_out will be larger
+ * @param[out] etag_out set to the latest history offset known for this @a coin_pub
+ * @param[out] balance set to current balance of the coin
+ * @param[out] h_denom_pub set to denomination public key of the coin
+ * @param[out] tlp set to list of transactions, set to NULL if coin has no
+ * transaction history past @a start_off or if @a etag_in is equal
+ * to the value written to @a etag_out.
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
- (*get_coin_transactions)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- int include_recoup,
- struct TALER_EXCHANGEDB_TransactionList **tlp);
+ (*get_coin_transactions)(
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t start_off,
+ uint64_t etag_in,
+ uint64_t *etag_out,
+ struct TALER_Amount *balance,
+ struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_EXCHANGEDB_TransactionList **tlp);
/**
@@ -2165,7 +4757,6 @@ struct TALER_EXCHANGEDB_Plugin
* into a wire transfer by the respective @a raw_wtid.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session database connection
* @param wtid the raw wire transfer identifier we used
* @param cb function to call on each transaction found
* @param cb_cls closure for @a cb
@@ -2173,7 +4764,6 @@ struct TALER_EXCHANGEDB_Plugin
*/
enum GNUNET_DB_QueryStatus
(*lookup_wire_transfer)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const struct TALER_WireTransferIdentifierRawP *wtid,
TALER_EXCHANGEDB_AggregationDataCallback cb,
void *cb_cls);
@@ -2185,100 +4775,135 @@ struct TALER_EXCHANGEDB_Plugin
* to be executed.
*
* @param cls closure
- * @param session database connection
* @param h_contract_terms hash of the proposal data
* @param h_wire hash of merchant wire details
* @param coin_pub public key of deposited coin
* @param merchant_pub merchant public key
- * @param cb function to call with the result
- * @param cb_cls closure to pass to @a cb
+ * @param[out] pending set to true if the transaction is still pending
+ * @param[out] wtid wire transfer identifier, only set if @a pending is false
+ * @param[out] coin_contribution how much did the coin we asked about
+ * contribute to the total transfer value? (deposit value including fee)
+ * @param[out] coin_fee how much did the exchange charge for the deposit fee
+ * @param[out] execution_time when was the transaction done, or
+ * when we expect it to be done (if @a pending is false)
+ * @param[out] kyc set to the kyc status of the receiver (if @a pending)
+ * @param[out] aml_decision set to the current AML status for the target account
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
(*lookup_transfer_by_deposit)(
void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct GNUNET_HashCode *h_wire,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_MerchantPublicKeyP *merchant_pub,
- TALER_EXCHANGEDB_WireTransferByCoinCallback cb,
- void *cb_cls);
+ bool *pending,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_TIME_Timestamp *exec_time,
+ struct TALER_Amount *amount_with_fee,
+ struct TALER_Amount *deposit_fee,
+ struct TALER_EXCHANGEDB_KycStatus *kyc,
+ enum TALER_AmlDecisionState *aml_decision);
/**
- * Function called to insert aggregation information into the DB.
+ * Insert wire transfer fee into database.
*
* @param cls closure
- * @param session database connection
- * @param wtid the raw wire transfer identifier we used
- * @param deposit_serial_id row in the deposits table for which this is aggregation data
+ * @param wire_method which wire method is the fee about?
+ * @param start_date when does the fee go into effect
+ * @param end_date when does the fee end being valid
+ * @param fees how high is are the wire fees
+ * @param master_sig signature over the above by the exchange master key
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_aggregation_tracking)(
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- unsigned long long deposit_serial_id);
+ (*insert_wire_fee)(void *cls,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_WireFeeSet *fees,
+ const struct TALER_MasterSignatureP *master_sig);
/**
- * Insert wire transfer fee into database.
+ * Insert global fee set into database.
*
* @param cls closure
- * @param session database connection
- * @param wire_method which wire method is the fee about?
- * @param start_date when does the fee go into effect
- * @param end_date when does the fee end being valid
- * @param wire_fee how high is the wire transfer fee
- * @param closing_fee how high is the closing fee
+ * @param start_date when does the fees go into effect
+ * @param end_date when does the fees end being valid
+ * @param fees how high is are the global fees
+ * @param purse_timeout when do purses time out
+ * @param history_expiration how long are account histories preserved
+ * @param purse_account_limit how many purses are free per account
* @param master_sig signature over the above by the exchange master key
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_wire_fee)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const char *wire_method,
- struct GNUNET_TIME_Absolute start_date,
- struct GNUNET_TIME_Absolute end_date,
- const struct TALER_Amount *wire_fee,
- const struct TALER_Amount *closing_fee,
- const struct TALER_MasterSignatureP *master_sig);
+ (*insert_global_fee)(void *cls,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+
+ const struct TALER_MasterSignatureP *master_sig);
/**
* Obtain wire fee from database.
*
* @param cls closure
- * @param session database connection
* @param type type of wire transfer the fee applies for
* @param date for which date do we want the fee?
* @param[out] start_date when does the fee go into effect
* @param[out] end_date when does the fee end being valid
- * @param[out] wire_fee how high is the wire transfer fee
- * @param[out] closing_fee how high is the closing fee
+ * @param[out] fees how high are the wire fees
* @param[out] master_sig signature over the above by the exchange master key
* @return query status of the transaction
*/
enum GNUNET_DB_QueryStatus
(*get_wire_fee)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const char *type,
- struct GNUNET_TIME_Absolute date,
- struct GNUNET_TIME_Absolute *start_date,
- struct GNUNET_TIME_Absolute *end_date,
- struct TALER_Amount *wire_fee,
- struct TALER_Amount *closing_fee,
+ struct GNUNET_TIME_Timestamp date,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_WireFeeSet *fees,
struct TALER_MasterSignatureP *master_sig);
/**
+ * Obtain global fees from database.
+ *
+ * @param cls closure
+ * @param date for which date do we want the fee?
+ * @param[out] start_date when does the fee go into effect
+ * @param[out] end_date when does the fee end being valid
+ * @param[out] fees how high are the global fees
+ * @param[out] purse_timeout when do purses time out
+ * @param[out] history_expiration how long are account histories preserved
+ * @param[out] purse_account_limit how many purses are free per account
+ * @param[out] master_sig signature over the above by the exchange master key
+ * @return query status of the transaction
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_global_fee)(void *cls,
+ struct GNUNET_TIME_Timestamp date,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative *purse_timeout,
+ struct GNUNET_TIME_Relative *history_expiration,
+ uint32_t *purse_account_limit,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+ /**
* Obtain information about expired reserves and their
* remaining balances.
*
* @param cls closure of the plugin
- * @param session database connection
* @param now timestamp based on which we decide expiration
* @param rec function to call on expired reserves
* @param rec_cls closure for @a rec
@@ -2286,41 +4911,186 @@ struct TALER_EXCHANGEDB_Plugin
*/
enum GNUNET_DB_QueryStatus
(*get_expired_reserves)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- struct GNUNET_TIME_Absolute now,
+ struct GNUNET_TIME_Timestamp now,
TALER_EXCHANGEDB_ReserveExpiredCallback rec,
void *rec_cls);
/**
+ * Obtain information about force-closed reserves
+ * where the close was not yet done (and their remaining
+ * balances). Updates the returned reserve's close
+ * status to "done".
+ *
+ * @param cls closure of the plugin
+ * @param rec function to call on (to be) closed reserves
+ * @param rec_cls closure for @a rec
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_unfinished_close_requests)(
+ void *cls,
+ TALER_EXCHANGEDB_ReserveExpiredCallback rec,
+ void *rec_cls);
+
+
+ /**
+ * Insert reserve open coin deposit data into database.
+ * Subtracts the @a coin_total from the coin's balance.
+ *
+ * @param cls closure
+ * @param cpi public information about the coin
+ * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT
+ * @param known_coin_id ID of the coin in the known_coins table
+ * @param coin_total amount to be spent of the coin (including deposit fee)
+ * @param reserve_sig signature by the reserve affirming the open operation
+ * @param reserve_pub public key of the reserve being opened
+ * @param[out] insufficient_funds set to true if the coin's balance is insufficient, otherwise to false
+ * @return transaction status code, 0 if operation is already in the DB
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_reserve_open_deposit)(
+ void *cls,
+ const struct TALER_CoinPublicInfo *cpi,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ uint64_t known_coin_id,
+ const struct TALER_Amount *coin_total,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *insufficient_funds);
+
+
+ /**
+ * Insert reserve close operation into database.
+ *
+ * @param cls closure
+ * @param reserve_pub which reserve is this about?
+ * @param total_paid total amount paid (coins and reserve)
+ * @param reserve_payment amount to be paid from the reserve
+ * @param min_purse_limit minimum number of purses we should be able to open
+ * @param reserve_sig signature by the reserve for the operation
+ * @param desired_expiration when should the reserve expire (earliest time)
+ * @param now when did we the client initiate the action
+ * @param open_fee annual fee to be charged for the open operation by the exchange
+ * @param[out] no_funds set to true if reserve balance is insufficient
+ * @param[out] reserve_balance set to original balance of the reserve
+ * @param[out] open_cost set to the actual cost
+ * @param[out] final_expiration when will the reserve expire now
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_reserve_open)(void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *total_paid,
+ const struct TALER_Amount *reserve_payment,
+ uint32_t min_purse_limit,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ struct GNUNET_TIME_Timestamp desired_expiration,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_Amount *open_fee,
+ bool *no_funds,
+ struct TALER_Amount *reserve_balance,
+ struct TALER_Amount *open_cost,
+ struct GNUNET_TIME_Timestamp *final_expiration);
+
+
+ /**
+ * Select information needed to see if we can close
+ * a reserve.
+ *
+ * @param cls closure
+ * @param reserve_pub which reserve is this about?
+ * @param[out] balance current reserve balance
+ * @param[out] payto_uri set to URL of account that
+ * originally funded the reserve;
+ * could be set to NULL if not known
+ * @return transaction status code, 0 if reserve unknown
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_reserve_close_info)(
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_Amount *balance,
+ char **payto_uri);
+
+
+ /**
+ * Select information about reserve close requests.
+ *
+ * @param cls closure
+ * @param reserve_pub which reserve is this about?
+ * @param rowid row ID of the close request
+ * @param[out] reserve_sig reserve signature affirming
+ * @param[out] request_timestamp when was the request made
+ * @param[out] close_balance reserve balance at close time
+ * @param[out] close_fee closing fee to be charged
+ * @param[out] payto_uri set to URL of account that
+ * should receive the money;
+ * could be set to NULL for origin
+ * @return transaction status code, 0 if reserve unknown
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_reserve_close_request_info)(
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t rowid,
+ struct TALER_ReserveSignatureP *reserve_sig,
+ struct GNUNET_TIME_Timestamp *request_timestamp,
+ struct TALER_Amount *close_balance,
+ struct TALER_Amount *close_fee,
+ char **payto_uri);
+
+
+ /**
+ * Select information needed for KYC checks on reserve close: historic
+ * reserve closures going to the same account.
+ *
+ * @param cls closure
+ * @param h_payto which target account is this about?
+ * @param h_payto account identifier
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+ enum GNUNET_DB_QueryStatus
+ (*iterate_reserve_close_info)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+
+ /**
* Insert reserve close operation into database.
*
* @param cls closure
- * @param session database connection
* @param reserve_pub which reserve is this about?
* @param execution_date when did we perform the transfer?
* @param receiver_account to which account do we transfer, in payto://-format
* @param wtid identifier for the wire transfer
* @param amount_with_fee amount we charged to the reserve
* @param closing_fee how high is the closing fee
+ * @param close_request_row identifies explicit close request, 0 for none
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
(*insert_reserve_closed)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const struct TALER_ReservePublicKeyP *reserve_pub,
- struct GNUNET_TIME_Absolute execution_date,
+ struct GNUNET_TIME_Timestamp execution_date,
const char *receiver_account,
- const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct
+ TALER_WireTransferIdentifierRawP *wtid,
const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *closing_fee);
+ const struct TALER_Amount *closing_fee,
+ uint64_t close_request_row);
/**
* Function called to insert wire transfer commit data into the DB.
*
* @param cls closure
- * @param session database connection
* @param type type of the wire transfer (i.e. "iban")
* @param buf buffer with wire transfer preparation data
* @param buf_size number of bytes in @a buf
@@ -2328,7 +5098,6 @@ struct TALER_EXCHANGEDB_Plugin
*/
enum GNUNET_DB_QueryStatus
(*wire_prepare_data_insert)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const char *type,
const char *buf,
size_t buf_size);
@@ -2338,54 +5107,63 @@ struct TALER_EXCHANGEDB_Plugin
* Function called to mark wire transfer commit data as finished.
*
* @param cls closure
- * @param session database connection
* @param rowid which entry to mark as finished
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
(*wire_prepare_data_mark_finished)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
uint64_t rowid);
/**
+ * Function called to mark wire transfer as failed.
+ *
+ * @param cls closure
+ * @param rowid which entry to mark as failed
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*wire_prepare_data_mark_failed)(void *cls,
+ uint64_t rowid);
+
+
+ /**
* Function called to get an unfinished wire transfer
- * preparation data. Fetches at most one item.
+ * preparation data.
*
* @param cls closure
- * @param session database connection
- * @param cb function to call for ONE unfinished item
+ * @param start_row offset to query table at
+ * @param limit maximum number of results to return
+ * @param cb function to call for unfinished work
* @param cb_cls closure for @a cb
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
(*wire_prepare_data_get)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
+ uint64_t start_row,
+ uint64_t limit,
TALER_EXCHANGEDB_WirePreparationIterator cb,
void *cb_cls);
/**
- * Start a transaction where we transiently violate the foreign
+ * Starts a READ COMMITTED transaction where we transiently violate the foreign
* constraints on the "wire_out" table as we insert aggregations
* and only add the wire transfer out at the end.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param session connection to use
* @return #GNUNET_OK on success
*/
- int
- (*start_deferred_wire_out) (void *cls,
- struct TALER_EXCHANGEDB_Session *session);
+ enum GNUNET_GenericReturnValue
+ (*start_deferred_wire_out)(void *cls);
/**
* Store information about an outgoing wire transfer that was executed.
*
* @param cls closure
- * @param session database connection
* @param date time of the wire transfer
- * @param wtid subject of the wire transfer
+ * @param h_payto identifies the receiver account of the wire transfer
* @param wire_account details about the receiver account of the wire transfer,
* including 'url' in payto://-format
* @param amount amount that was transmitted
@@ -2396,10 +5174,9 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*store_wire_transfer_out)(
void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- struct GNUNET_TIME_Absolute date,
+ struct GNUNET_TIME_Timestamp date,
const struct TALER_WireTransferIdentifierRawP *wtid,
- const json_t *wire_account,
+ const struct TALER_PaytoHashP *h_payto,
const char *exchange_account_section,
const struct TALER_Amount *amount);
@@ -2412,8 +5189,8 @@ struct TALER_EXCHANGEDB_Plugin
* @return #GNUNET_OK on success,
* #GNUNET_SYSERR on DB errors
*/
- int
- (*gc) (void *cls);
+ enum GNUNET_GenericReturnValue
+ (*gc)(void *cls);
/**
@@ -2421,25 +5198,150 @@ struct TALER_EXCHANGEDB_Plugin
* order.
*
* @param cls closure
- * @param session database connection
* @param serial_id highest serial ID to exclude (select strictly larger)
* @param cb function to call on each result
* @param cb_cls closure for @a cb
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*select_deposits_above_serial_id)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- uint64_t serial_id,
- TALER_EXCHANGEDB_DepositCallback cb,
- void *cb_cls);
+ (*select_coin_deposits_above_serial_id)(void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_DepositCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Function called to return meta data about a purses
+ * above a certain serial ID.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param serial_id number to select requests by
+ * @param cb function to call on each request
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_purse_requests_above_serial_id)(
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseRequestCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Select purse deposits above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_purse_deposits_above_serial_id)(
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseDepositCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Select account merges above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_account_merges_above_serial_id)(
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_AccountMergeCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Select purse merges deposits above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_purse_merges_above_serial_id)(
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseMergeCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Select purse refunds above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param refunded which refund status to select for
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_purse_decisions_above_serial_id)(
+ void *cls,
+ uint64_t serial_id,
+ bool refunded,
+ TALER_EXCHANGEDB_PurseDecisionCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Select all purse refunds above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_all_purse_decisions_above_serial_id)(
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_AllPurseDecisionCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Select coins deposited into a purse.
+ *
+ * @param cls closure
+ * @param purse_pub public key of the purse
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_purse_deposits_by_purse)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ TALER_EXCHANGEDB_PurseRefundCoinCallback cb,
+ void *cb_cls);
+
/**
* Select refresh sessions above @a serial_id in monotonically increasing
* order.
*
* @param cls closure
- * @param session database connection
* @param serial_id highest serial ID to exclude (select strictly larger)
* @param cb function to call on each result
* @param cb_cls closure for @a cb
@@ -2447,7 +5349,6 @@ struct TALER_EXCHANGEDB_Plugin
*/
enum GNUNET_DB_QueryStatus
(*select_refreshes_above_serial_id)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
uint64_t serial_id,
TALER_EXCHANGEDB_RefreshesCallback cb,
void *cb_cls);
@@ -2458,7 +5359,6 @@ struct TALER_EXCHANGEDB_Plugin
* order.
*
* @param cls closure
- * @param session database connection
* @param serial_id highest serial ID to exclude (select strictly larger)
* @param cb function to call on each result
* @param cb_cls closure for @a cb
@@ -2466,7 +5366,6 @@ struct TALER_EXCHANGEDB_Plugin
*/
enum GNUNET_DB_QueryStatus
(*select_refunds_above_serial_id)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
uint64_t serial_id,
TALER_EXCHANGEDB_RefundCallback cb,
void *cb_cls);
@@ -2477,7 +5376,6 @@ struct TALER_EXCHANGEDB_Plugin
* in monotonically increasing order.
*
* @param cls closure
- * @param session database connection
* @param serial_id highest serial ID to exclude (select strictly larger)
* @param cb function to call on each result
* @param cb_cls closure for @a cb
@@ -2485,7 +5383,6 @@ struct TALER_EXCHANGEDB_Plugin
*/
enum GNUNET_DB_QueryStatus
(*select_reserves_in_above_serial_id)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
uint64_t serial_id,
TALER_EXCHANGEDB_ReserveInCallback cb,
void *cb_cls);
@@ -2496,7 +5393,6 @@ struct TALER_EXCHANGEDB_Plugin
* in monotonically increasing order by @a account_name.
*
* @param cls closure
- * @param session database connection
* @param account_name name of the account for which we do the selection
* @param serial_id highest serial ID to exclude (select strictly larger)
* @param cb function to call on each result
@@ -2506,7 +5402,6 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*select_reserves_in_above_serial_id_by_account)(
void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const char *account_name,
uint64_t serial_id,
TALER_EXCHANGEDB_ReserveInCallback cb,
@@ -2518,7 +5413,6 @@ struct TALER_EXCHANGEDB_Plugin
* in monotonically increasing order.
*
* @param cls closure
- * @param session database connection
* @param account_name name of the account for which we do the selection
* @param serial_id highest serial ID to exclude (select strictly larger)
* @param cb function to call on each result
@@ -2528,7 +5422,6 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*select_withdrawals_above_serial_id)(
void *cls,
- struct TALER_EXCHANGEDB_Session *session,
uint64_t serial_id,
TALER_EXCHANGEDB_WithdrawCallback cb,
void *cb_cls);
@@ -2539,7 +5432,6 @@ struct TALER_EXCHANGEDB_Plugin
* executed, ordered by serial ID (monotonically increasing).
*
* @param cls closure
- * @param session database connection
* @param serial_id lowest serial ID to include (select larger or equal)
* @param cb function to call for ONE unfinished item
* @param cb_cls closure for @a cb
@@ -2547,9 +5439,9 @@ struct TALER_EXCHANGEDB_Plugin
*/
enum GNUNET_DB_QueryStatus
(*select_wire_out_above_serial_id)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
uint64_t serial_id,
- TALER_EXCHANGEDB_WireTransferOutCallback cb,
+ TALER_EXCHANGEDB_WireTransferOutCallback
+ cb,
void *cb_cls);
/**
@@ -2557,7 +5449,6 @@ struct TALER_EXCHANGEDB_Plugin
* executed, ordered by serial ID (monotonically increasing).
*
* @param cls closure
- * @param session database connection
* @param account_name name to select by
* @param serial_id lowest serial ID to include (select larger or equal)
* @param cb function to call for ONE unfinished item
@@ -2567,7 +5458,6 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*select_wire_out_above_serial_id_by_account)(
void *cls,
- struct TALER_EXCHANGEDB_Session *session,
const char *account_name,
uint64_t serial_id,
TALER_EXCHANGEDB_WireTransferOutCallback cb,
@@ -2579,7 +5469,6 @@ struct TALER_EXCHANGEDB_Plugin
* received, ordered by serial ID (monotonically increasing).
*
* @param cls closure
- * @param session database connection
* @param serial_id lowest serial ID to include (select larger or equal)
* @param cb function to call for ONE unfinished item
* @param cb_cls closure for @a cb
@@ -2587,7 +5476,6 @@ struct TALER_EXCHANGEDB_Plugin
*/
enum GNUNET_DB_QueryStatus
(*select_recoup_above_serial_id)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
uint64_t serial_id,
TALER_EXCHANGEDB_RecoupCallback cb,
void *cb_cls);
@@ -2598,7 +5486,6 @@ struct TALER_EXCHANGEDB_Plugin
* refreshed coins, ordered by serial ID (monotonically increasing).
*
* @param cls closure
- * @param session database connection
* @param serial_id lowest serial ID to include (select larger or equal)
* @param cb function to call for ONE unfinished item
* @param cb_cls closure for @a cb
@@ -2607,86 +5494,45 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*select_recoup_refresh_above_serial_id)(
void *cls,
- struct TALER_EXCHANGEDB_Session *session,
uint64_t serial_id,
TALER_EXCHANGEDB_RecoupRefreshCallback cb,
void *cb_cls);
/**
- * Function called to select reserve close operations the aggregator
- * triggered, ordered by serial ID (monotonically increasing).
+ * Function called to select reserve open operations, ordered by serial ID
+ * (monotonically increasing).
*
* @param cls closure
- * @param session database connection
* @param serial_id lowest serial ID to include (select larger or equal)
* @param cb function to call
* @param cb_cls closure for @a cb
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*select_reserve_closed_above_serial_id)(
+ (*select_reserve_open_above_serial_id)(
void *cls,
- struct TALER_EXCHANGEDB_Session *session,
uint64_t serial_id,
- TALER_EXCHANGEDB_ReserveClosedCallback cb,
+ TALER_EXCHANGEDB_ReserveOpenCallback cb,
void *cb_cls);
/**
- * Function called to add a request for an emergency recoup for a
- * coin. The funds are to be added back to the reserve.
- *
- * @param cls closure
- * @param session database connection
- * @param reserve_pub public key of the reserve that is being refunded
- * @param coin public information about a coin
- * @param coin_sig signature of the coin of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
- * @param coin_blind blinding key of the coin
- * @param h_blind_ev blinded envelope, as calculated by the exchange
- * @param amount total amount to be paid back
- * @param h_blind_ev hash of the blinded coin's envelope (must match reserves_out entry)
- * @param timestamp the timestamp to store
- * @return transaction result status
- */
- enum GNUNET_DB_QueryStatus
- (*insert_recoup_request)(
- void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_CoinPublicInfo *coin,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind,
- const struct TALER_Amount *amount,
- const struct GNUNET_HashCode *h_blind_ev,
- struct GNUNET_TIME_Absolute timestamp);
-
-
- /**
- * Function called to add a request for an emergency recoup for a
- * refreshed coin. The funds are to be added back to the original coin.
- *
- * @param cls closure
- * @param session database connection
- * @param coin public information about the refreshed coin
- * @param coin_sig signature of the coin of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
- * @param coin_blind blinding key of the coin
- * @param h_blind_ev blinded envelope, as calculated by the exchange
- * @param amount total amount to be paid back
- * @param h_blind_ev hash of the blinded coin's envelope (must match reserves_out entry)
- * @param timestamp a timestamp to store
- * @return transaction result status
- */
+ * Function called to select reserve close operations the aggregator
+ * triggered, ordered by serial ID (monotonically increasing).
+ *
+ * @param cls closure
+ * @param serial_id lowest serial ID to include (select larger or equal)
+ * @param cb function to call
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
enum GNUNET_DB_QueryStatus
- (*insert_recoup_refresh_request)(
+ (*select_reserve_closed_above_serial_id)(
void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinPublicInfo *coin,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind,
- const struct TALER_Amount *amount,
- const struct GNUNET_HashCode *h_blind_ev,
- struct GNUNET_TIME_Absolute timestamp);
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveClosedCallback cb,
+ void *cb_cls);
/**
@@ -2694,16 +5540,17 @@ struct TALER_EXCHANGEDB_Plugin
* from given the hash of the blinded coin.
*
* @param cls closure
- * @param session a session
- * @param h_blind_ev hash of the blinded coin
+ * @param bch hash identifying the withdraw operation
* @param[out] reserve_pub set to information about the reserve (on success only)
+ * @param[out] reserve_out_serial_id set to row of the @a h_blind_ev in reserves_out
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*get_reserve_by_h_blind)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *h_blind_ev,
- struct TALER_ReservePublicKeyP *reserve_pub);
+ (*get_reserve_by_h_blind)(
+ void *cls,
+ const struct TALER_BlindedCoinHashP *bch,
+ struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *reserve_out_serial_id);
/**
@@ -2711,16 +5558,17 @@ struct TALER_EXCHANGEDB_Plugin
* given the hash of the blinded (fresh) coin.
*
* @param cls closure
- * @param session a session
* @param h_blind_ev hash of the blinded coin
* @param[out] old_coin_pub set to information about the old coin (on success only)
+ * @param[out] rrc_serial set to the row of the @a h_blind_ev in the refresh_revealed_coins table
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*get_old_coin_by_h_blind)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *h_blind_ev,
- struct TALER_CoinSpendPublicKeyP *old_coin_pub);
+ (*get_old_coin_by_h_blind)(
+ void *cls,
+ const struct TALER_BlindedCoinHashP *h_blind_ev,
+ struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ uint64_t *rrc_serial);
/**
@@ -2728,7 +5576,6 @@ struct TALER_EXCHANGEDB_Plugin
* in the database.
*
* @param cls closure
- * @param session a session
* @param denom_pub_hash hash of the revoked denomination key
* @param master_sig signature affirming the revocation
* @return transaction status code
@@ -2736,8 +5583,7 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*insert_denomination_revocation)(
void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash,
+ const struct TALER_DenominationHashP *denom_pub_hash,
const struct TALER_MasterSignatureP *master_sig);
@@ -2746,40 +5592,1652 @@ struct TALER_EXCHANGEDB_Plugin
* the database.
*
* @param cls closure
- * @param session a session
* @param denom_pub_hash hash of the revoked denomination key
* @param[out] master_sig signature affirming the revocation
* @param[out] rowid row where the information is stored
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*get_denomination_revocation)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct GNUNET_HashCode *denom_pub_hash,
- struct TALER_MasterSignatureP *master_sig,
- uint64_t *rowid);
+ (*get_denomination_revocation)(
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_MasterSignatureP *master_sig,
+ uint64_t *rowid);
/**
- * Select all of those deposits in the database for which we do
- * not have a wire transfer (or a refund) and which should have
- * been deposited between @a start_date and @a end_date.
+ * Select all (batch) deposits in the database
+ * above a given @a min_batch_deposit_serial_id.
*
* @param cls closure
- * @param session a session
- * @param start_date lower bound on the requested wire execution date
- * @param end_date upper bound on the requested wire execution date
+ * @param min_batch_deposit_serial_id only return entries strictly above this row (and in order)
* @param cb function to call on all such deposits
* @param cb_cls closure for @a cb
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*select_deposits_missing_wire)(void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- struct GNUNET_TIME_Absolute start_date,
- struct GNUNET_TIME_Absolute end_date,
- TALER_EXCHANGEDB_WireMissingCallback cb,
- void *cb_cls);
+ (*select_batch_deposits_missing_wire)(
+ void *cls,
+ uint64_t min_batch_deposit_serial_id,
+ TALER_EXCHANGEDB_WireMissingCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Select all aggregation tracking IDs in the database
+ * above a given @a min_tracking_serial_id.
+ *
+ * @param cls closure
+ * @param min_tracking_serial_id only return entries strictly above this row (and in order)
+ * @param cb function to call on all such aggregations
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_aggregations_above_serial)(
+ void *cls,
+ uint64_t min_tracking_serial_id,
+ TALER_EXCHANGEDB_AggregationCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Return any applicable justification as to why
+ * a wire transfer might have been held. Used
+ * by the auditor to determine if a wire transfer
+ * is legitimately stalled.
+ *
+ * @param cls closure
+ * @param wire_target_h_payto effected target account
+ * @param[out] payto_uri target account URI, set to NULL if unknown
+ * @param[out] kyc_pending set to string describing missing KYC data
+ * @param[out] status set to AML status
+ * @param[out] aml_limit set to AML limit, or invalid amount for none
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_justification_for_missing_wire)(
+ void *cls,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ char **payto_uri,
+ char **kyc_pending,
+ enum TALER_AmlDecisionState *status,
+ struct TALER_Amount *aml_limit);
+
+
+ /**
+ * Check the last date an auditor was modified.
+ *
+ * @param cls closure
+ * @param auditor_pub key to look up information for
+ * @param[out] last_date last modification date to auditor status
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_auditor_timestamp)(
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct GNUNET_TIME_Timestamp *last_date);
+
+
+ /**
+ * Lookup current state of an auditor.
+ *
+ * @param cls closure
+ * @param auditor_pub key to look up information for
+ * @param[out] auditor_url set to the base URL of the auditor's REST API; memory to be
+ * released by the caller!
+ * @param[out] enabled set if the auditor is currently in use
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_auditor_status)(
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ char **auditor_url,
+ bool *enabled);
+
+
+ /**
+ * Insert information about an auditor that will audit this exchange.
+ *
+ * @param cls closure
+ * @param auditor_pub key of the auditor
+ * @param auditor_url base URL of the auditor's REST service
+ * @param auditor_name name of the auditor (for humans)
+ * @param start_date date when the auditor was added by the offline system
+ * (only to be used for replay detection)
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_auditor)(
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp start_date);
+
+
+ /**
+ * Update information about an auditor that will audit this exchange.
+ *
+ * @param cls closure
+ * @param auditor_pub key of the auditor (primary key for the existing record)
+ * @param auditor_url base URL of the auditor's REST service, to be updated
+ * @param auditor_name name of the auditor (for humans)
+ * @param change_date date when the auditor status was last changed
+ * (only to be used for replay detection)
+ * @param enabled true to enable, false to disable
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_auditor)(
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp change_date,
+ bool enabled);
+
+
+ /**
+ * Check the last date an exchange wire account was modified.
+ *
+ * @param cls closure
+ * @param payto_uri key to look up information for
+ * @param[out] last_date last modification date to auditor status
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_wire_timestamp)(void *cls,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp *last_date);
+
+
+ /**
+ * Insert information about an wire account used by this exchange.
+ *
+ * @param cls closure
+ * @param payto_uri wire account of the exchange
+ * @param conversion_url URL of a conversion service, NULL if there is no conversion
+ * @param debit_restrictions JSON array with debit restrictions on the account
+ * @param credit_restrictions JSON array with credit restrictions on the account
+ * @param start_date date when the account was added by the offline system
+ * (only to be used for replay detection)
+ * @param master_sig public signature affirming the existence of the account,
+ * must be of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS
+ * @param bank_label label to show this entry under in the UI, can be NULL
+ * @param priority determines order in which entries are shown in the UI
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_wire)(void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp start_date,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority);
+
+
+ /**
+ * Update information about a wire account of the exchange.
+ *
+ * @param cls closure
+ * @param payto_uri account the update is about
+ * @param conversion_url URL of a conversion service, NULL if there is no conversion
+ * @param debit_restrictions JSON array with debit restrictions on the account; NULL allowed if not @a enabled
+ * @param credit_restrictions JSON array with credit restrictions on the account; NULL allowed if not @a enabled
+ * @param change_date date when the account status was last changed
+ * (only to be used for replay detection)
+ * @param master_sig master signature to store, can be NULL (if @a enabled is false)
+ * @param bank_label label to show this entry under in the UI, can be NULL
+ * @param priority determines order in which entries are shown in the UI
+ * @param enabled true to enable, false to disable (the actual change)
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_wire)(void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp change_date,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority,
+ bool enabled);
+
+
+ /**
+ * Obtain information about the enabled wire accounts of the exchange.
+ *
+ * @param cls closure
+ * @param cb function to call on each account
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_wire_accounts)(void *cls,
+ TALER_EXCHANGEDB_WireAccountCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Obtain information about the fee structure of the exchange for
+ * a given @a wire_method
+ *
+ * @param cls closure
+ * @param wire_method which wire method to obtain fees for
+ * @param cb function to call on each account
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_wire_fees)(void *cls,
+ const char *wire_method,
+ TALER_EXCHANGEDB_WireFeeCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Obtain information about the global fee structure of the exchange.
+ *
+ * @param cls closure
+ * @param cb function to call on each fee entry
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_global_fees)(void *cls,
+ TALER_EXCHANGEDB_GlobalFeeCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Store information about a revoked online signing key.
+ *
+ * @param cls closure
+ * @param exchange_pub exchange online signing key that was revoked
+ * @param master_sig signature affirming the revocation
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_signkey_revocation)(
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+ /**
+ * Obtain information about a revoked online signing key.
+ *
+ * @param cls closure
+ * @param exchange_pub exchange online signing key that was revoked
+ * @param[out] master_sig signature affirming the revocation
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_signkey_revocation)(
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+ /**
+ * Lookup information about current denomination key.
+ *
+ * @param cls closure
+ * @param h_denom_pub hash of the denomination public key
+ * @param[out] meta set to various meta data about the key
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_denomination_key)(
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta);
+
+
+ /**
+ * Add denomination key.
+ *
+ * @param cls closure
+ * @param h_denom_pub hash of the denomination public key
+ * @param denom_pub the denomination public key
+ * @param meta meta data about the denomination
+ * @param master_sig master signature to add
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*add_denomination_key)(
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+ /**
+ * Activate future signing key, turning it into a "current" or "valid"
+ * denomination key by adding the master signature.
+ *
+ * @param cls closure
+ * @param exchange_pub the exchange online signing public key
+ * @param meta meta data about @a exchange_pub
+ * @param master_sig master signature to add
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*activate_signing_key)(
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+ /**
+ * Lookup signing key meta data.
+ *
+ * @param cls closure
+ * @param exchange_pub the exchange online signing public key
+ * @param[out] meta meta data about @a exchange_pub
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_signing_key)(
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_EXCHANGEDB_SignkeyMetaData *meta);
+
+
+ /**
+ * Insert information about an auditor auditing a denomination key.
+ *
+ * @param cls closure
+ * @param h_denom_pub the audited denomination
+ * @param auditor_pub the auditor's key
+ * @param auditor_sig signature affirming the auditor's audit activity
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_auditor_denom_sig)(
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_AuditorSignatureP *auditor_sig);
+
+
+ /**
+ * Obtain information about an auditor auditing a denomination key.
+ *
+ * @param cls closure
+ * @param h_denom_pub the audited denomination
+ * @param auditor_pub the auditor's key
+ * @param[out] auditor_sig set to signature affirming the auditor's audit activity
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_auditor_denom_sig)(
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct TALER_AuditorSignatureP *auditor_sig);
+
+
+ /**
+ * Lookup information about known wire fees.
+ *
+ * @param cls closure
+ * @param wire_method the wire method to lookup fees for
+ * @param start_time starting time of fee
+ * @param end_time end time of fee
+ * @param[out] fees set to wire fees for that time period; if
+ * different wire fee exists within this time
+ * period, an 'invalid' amount is returned.
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_wire_fee_by_time)(
+ void *cls,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ struct TALER_WireFeeSet *fees);
+
+
+ /**
+ * Lookup information about known global fees.
+ *
+ * @param cls closure
+ * @param start_time starting time of fee
+ * @param end_time end time of fee
+ * @param[out] fees set to wire fees for that time period; if
+ * different global fee exists within this time
+ * period, an 'invalid' amount is returned.
+ * @param[out] purse_timeout set to when unmerged purses expire
+ * @param[out] history_expiration set to when we expire reserve histories
+ * @param[out] purse_account_limit set to number of free purses
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_global_fee_by_time)(
+ void *cls,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative *purse_timeout,
+ struct GNUNET_TIME_Relative *history_expiration,
+ uint32_t *purse_account_limit);
+
+
+ /**
+ * Lookup the latest serial number of @a table. Used in
+ * exchange-auditor database replication.
+ *
+ * @param cls closure
+ * @param table table for which we should return the serial
+ * @param[out] latest serial number in use
+ * @return transaction status code, #GNUNET_DB_STATUS_HARD_ERROR if
+ * @a table does not have a serial number
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_serial_by_table)(void *cls,
+ enum TALER_EXCHANGEDB_ReplicatedTable table,
+ uint64_t *serial);
+
+ /**
+ * Lookup records above @a serial number in @a table. Used in
+ * exchange-auditor database replication.
+ *
+ * @param cls closure
+ * @param table table for which we should return the serial
+ * @param serial largest serial number to exclude
+ * @param cb function to call on the records
+ * @param cb_cls closure for @a cb
+ * @return transaction status code, GNUNET_DB_STATUS_HARD_ERROR if
+ * @a table does not have a serial number
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_records_by_table)(void *cls,
+ enum TALER_EXCHANGEDB_ReplicatedTable table,
+ uint64_t serial,
+ TALER_EXCHANGEDB_ReplicationCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Insert record set into @a table. Used in exchange-auditor database
+ * replication.
+ *
+ memset (&awc, 0, sizeof (awc));
+ * @param cls closure
+ * @param tb table data to insert
+ * @return transaction status code, #GNUNET_DB_STATUS_HARD_ERROR if
+ * @a table does not have a serial number
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_records_by_table)(void *cls,
+ const struct TALER_EXCHANGEDB_TableData *td);
+
+
+ /**
+ * Function called to grab a work shard on an operation @a op. Runs in its
+ * own transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to grab a word shard for
+ * @param delay minimum age of a shard to grab
+ * @param size desired shard size
+ * @param[out] start_row inclusive start row of the shard (returned)
+ * @param[out] end_row exclusive end row of the shard (returned)
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*begin_shard)(void *cls,
+ const char *job_name,
+ struct GNUNET_TIME_Relative delay,
+ uint64_t shard_size,
+ uint64_t *start_row,
+ uint64_t *end_row);
+
+ /**
+ * Function called to abort work on a shard.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to abort a word shard for
+ * @param start_row inclusive start row of the shard
+ * @param end_row exclusive end row of the shard
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*abort_shard)(void *cls,
+ const char *job_name,
+ uint64_t start_row,
+ uint64_t end_row);
+
+ /**
+ * Function called to persist that work on a shard was completed.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to grab a word shard for
+ * @param start_row inclusive start row of the shard
+ * @param end_row exclusive end row of the shard
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*complete_shard)(void *cls,
+ const char *job_name,
+ uint64_t start_row,
+ uint64_t end_row);
+
+
+ /**
+ * Function called to grab a revolving work shard on an operation @a op. Runs
+ * in its own transaction. Returns the oldest inactive shard.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to grab a revolving shard for
+ * @param shard_size desired shard size
+ * @param shard_limit exclusive end of the shard range
+ * @param[out] start_row inclusive start row of the shard (returned)
+ * @param[out] end_row inclusive end row of the shard (returned)
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*begin_revolving_shard)(void *cls,
+ const char *job_name,
+ uint32_t shard_size,
+ uint32_t shard_limit,
+ uint32_t *start_row,
+ uint32_t *end_row);
+
+
+ /**
+ * Function called to release a revolving shard back into the work pool.
+ * Clears the "completed" flag.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to grab a word shard for
+ * @param start_row inclusive start row of the shard
+ * @param end_row inclusive end row of the shard
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*release_revolving_shard)(void *cls,
+ const char *job_name,
+ uint32_t start_row,
+ uint32_t end_row);
+
+
+ /**
+ * Function called to delete all revolving shards.
+ * To be used after a crash or when the shard size is
+ * changed.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @return #GNUNET_OK on success
+ * #GNUNET_SYSERR on failure
+ */
+ enum GNUNET_GenericReturnValue
+ (*delete_shard_locks)(void *cls);
+
+
+ /**
+ * Function called to save the manifest of an extension
+ * (age-restriction, policy-extension, ...)
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param extension_name the name of the extension
+ * @param manifest JSON object of the Manifest as string, maybe NULL (== disabled extension)
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*set_extension_manifest)(void *cls,
+ const char *extension_name,
+ const char *manifest);
+
+
+ /**
+ * Function called to retrieve the manifest of an extension
+ * (age-restriction, policy-extension, ...)
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param extension_name the name of the extension
+ * @param[out] manifest Manifest of the extension in JSON encoding, maybe NULL (== disabled extension)
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_extension_manifest)(void *cls,
+ const char *extension_name,
+ char **manifest);
+
+
+ /**
+ * Function called to store configuration data about a partner
+ * exchange that we are federated with.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param master_pub public offline signing key of the partner exchange
+ * @param start_date when does the following data start to be valid
+ * @param end_date when does the validity end (exclusive)
+ * @param wad_frequency how often do we do exchange-to-exchange settlements?
+ * @param wad_fee how much do we charge for transfers to the partner
+ * @param partner_base_url base URL of the partner exchange
+ * @param master_sig signature with our offline signing key affirming the above
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_partner)(void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ struct GNUNET_TIME_Relative wad_frequency,
+ const struct TALER_Amount *wad_fee,
+ const char *partner_base_url,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+ /**
+ * Function called to persist an encrypted contract associated with a reserve.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param econtract the encrypted contract
+ * @param[out] econtract_sig set to the signature over the encrypted contract
+ * @param[out] in_conflict set to true if @a econtract
+ * conflicts with an existing contract;
+ * in this case, the return value will be
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT despite the failure
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_contract)(void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_EncryptedContract *econtract,
+ bool *in_conflict);
+
+
+ /**
+ * Function called to retrieve an encrypted contract.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pub_ckey set to the ephemeral DH used to encrypt the contract, key used to lookup the contract by
+ * @param[out] purse_pub public key of the purse of the contract
+ * @param[out] econtract_sig set to the signature over the encrypted contract
+ * @param[out] econtract_size set to the number of bytes in @a econtract
+ * @param[out] econtract set to the encrypted contract on success, to be freed by the caller
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_contract)(
+ void *cls,
+ const struct TALER_ContractDiffiePublicP *pub_ckey,
+ struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseContractSignatureP *econtract_sig,
+ size_t *econtract_size,
+ void **econtract);
+
+
+ /**
+ * Function called to retrieve an encrypted contract.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub key to lookup the contract by
+ * @param[out] econtract set to the encrypted contract on success, to be freed by the caller
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_contract_by_purse)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_EncryptedContract *econtract);
+
+
+ /**
+ * Function called to create a new purse with certain meta data.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the new purse
+ * @param merge_pub public key providing the merge capability
+ * @param purse_expiration time when the purse will expire
+ * @param h_contract_terms hash of the contract for the purse
+ * @param age_limit age limit to enforce for payments into the purse
+ * @param flags flags for the operation
+ * @param purse_fee fee we are allowed to charge to the reserve (depending on @a flags)
+ * @param amount target amount (with fees) to be put into the purse
+ * @param purse_sig signature with @a purse_pub's private key affirming the above
+ * @param[out] in_conflict set to true if the meta data
+ * conflicts with an existing purse;
+ * in this case, the return value will be
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT despite the failure
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_purse_request)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t age_limit,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_Amount *purse_fee,
+ const struct TALER_Amount *amount,
+ const struct TALER_PurseContractSignatureP *purse_sig,
+ bool *in_conflict);
+
+
+ /**
+ * Function called to clean up one expired purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param start_time select purse expired after this time
+ * @param end_time select purse expired before this time
+ * @return transaction status code (#GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if no purse expired in the given time interval).
+ */
+ enum GNUNET_DB_QueryStatus
+ (*expire_purse)(
+ void *cls,
+ struct GNUNET_TIME_Absolute start_time,
+ struct GNUNET_TIME_Absolute end_time);
+
+
+ /**
+ * Function called to obtain information about a purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the new purse
+ * @param[out] purse_creation set to time when the purse was created
+ * @param[out] purse_expiration set to time when the purse will expire
+ * @param[out] amount set to target amount (with fees) to be put into the purse
+ * @param[out] deposited set to actual amount put into the purse so far
+ * @param[out] h_contract_terms set to hash of the contract for the purse
+ * @param[out] merge_timestamp set to time when the purse was merged, or NEVER if not
+ * @param[out] purse_deleted set to true if purse was deleted
+ * @param[out] purse_refunded set to true if purse was refunded (after expiration)
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_purse)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp *purse_creation,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_Amount *amount,
+ struct TALER_Amount *deposited,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp *merge_timestamp,
+ bool *purse_deleted,
+ bool *purse_refunded);
+
+
+ /**
+ * Function called to return meta data about a purse by the
+ * purse public key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the purse
+ * @param[out] merge_pub public key representing the merge capability
+ * @param[out] purse_expiration when would an unmerged purse expire
+ * @param[out] h_contract_terms contract associated with the purse
+ * @param[out] age_limit the age limit for deposits into the purse
+ * @param[out] target_amount amount to be put into the purse
+ * @param[out] balance amount put so far into the purse
+ * @param[out] purse_sig signature of the purse over the initialization data
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_purse_request)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t *age_limit,
+ struct TALER_Amount *target_amount,
+ struct TALER_Amount *balance,
+ struct TALER_PurseContractSignatureP *purse_sig);
+
+
+ /**
+ * Function called to return meta data about a purse by the
+ * merge capability key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param merge_pub public key representing the merge capability
+ * @param[out] purse_pub public key of the purse
+ * @param[out] purse_expiration when would an unmerged purse expire
+ * @param[out] h_contract_terms contract associated with the purse
+ * @param[out] age_limit the age limit for deposits into the purse
+ * @param[out] target_amount amount to be put into the purse
+ * @param[out] balance amount put so far into the purse
+ * @param[out] purse_sig signature of the purse over the initialization data
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_purse_by_merge_pub)(
+ void *cls,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t *age_limit,
+ struct TALER_Amount *target_amount,
+ struct TALER_Amount *balance,
+ struct TALER_PurseContractSignatureP *purse_sig);
+
+
+ /**
+ * Function called to execute a transaction crediting
+ * a purse with @a amount from @a coin_pub. Reduces the
+ * value of @a coin_pub and increase the balance of
+ * the @a purse_pub purse. If the balance reaches the
+ * target amount and the purse has been merged, triggers
+ * the updates of the reserve/account balance.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to credit
+ * @param coin_pub coin to deposit (debit)
+ * @param amount fraction of the coin's value to deposit
+ * @param coin_sig signature affirming the operation
+ * @param amount_minus_fee amount to add to the purse
+ * @param[out] balance_ok set to false if the coin's
+ * remaining balance is below @a amount;
+ * in this case, the return value will be
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT despite the failure
+ * @param[out] too_late it is too late to deposit into this purse
+ * @param[out] conflict the same coin was deposited into
+ * this purse with a different amount already
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_purse_deposit)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *amount,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ const struct TALER_Amount *amount_minus_fee,
+ bool *balance_ok,
+ bool *too_late,
+ bool *conflict);
+
+
+ /**
+ * Function called to explicitly delete a purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to delete
+ * @param purse_sig signature affirming the deletion
+ * @param[out] decided set to true if the purse was
+ * already decided and thus could not be deleted
+ * @param[out] found set to true if the purse was found
+ * (if false, purse could not be deleted)
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_purse_delete)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig,
+ bool *decided,
+ bool *found);
+
+
+ /**
+ * Set the current @a balance in the purse
+ * identified by @a purse_pub. Used by the auditor
+ * to update the balance as calculated by the auditor.
+ *
+ * @param cls closure
+ * @param purse_pub public key of a purse
+ * @param balance new balance to store under the purse
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*set_purse_balance)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance);
+
+
+ /**
+ * Function called to obtain a coin deposit data from
+ * depositing the coin into a purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to credit
+ * @param coin_pub coin to deposit (debit)
+ * @param[out] amount set fraction of the coin's value that was deposited (with fee)
+ * @param[out] h_denom_pub set to hash of denomination of the coin
+ * @param[out] phac set to hash of age restriction on the coin
+ * @param[out] coin_sig set to signature affirming the operation
+ * @param[out] partner_url set to the URL of the partner exchange, or NULL for ourselves, must be freed by caller
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_purse_deposit)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_Amount *amount,
+ struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_AgeCommitmentHash *phac,
+ struct TALER_CoinSpendSignatureP *coin_sig,
+ char **partner_url);
+
+
+ /**
+ * Function called to approve merging a purse into a
+ * reserve by the respective purse merge key. The purse
+ * must not have been merged into a different reserve.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to merge
+ * @param merge_sig signature affirming the merge
+ * @param merge_timestamp time of the merge
+ * @param reserve_sig signature of the reserve affirming the merge
+ * @param partner_url URL of the partner exchange, can be NULL if the reserves lives with us
+ * @param reserve_pub public key of the reserve to credit
+ * @param[out] no_partner set to true if @a partner_url is unknown
+ * @param[out] no_balance set to true if the @a purse_pub is not paid up yet
+ * @param[out] no_reserve set to true if the @a reserve_pub is not known
+ * @param[out] in_conflict set to true if @a purse_pub was merged into a different reserve already
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_purse_merge)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig,
+ const struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const char *partner_url,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *no_partner,
+ bool *no_balance,
+ bool *in_conflict);
+
+
+ /**
+ * Function called insert request to merge a purse into a reserve by the
+ * respective purse merge key. The purse must not have been merged into a
+ * different reserve.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to merge
+ * @param merge_sig signature affirming the merge
+ * @param merge_timestamp time of the merge
+ * @param reserve_sig signature of the reserve affirming the merge
+ * @param purse_fee amount to charge the reserve for the purse creation, NULL to use the quota
+ * @param reserve_pub public key of the reserve to credit
+ * @param[out] in_conflict set to true if @a purse_pub was merged into a different reserve already
+ * @param[out] no_reserve set to true if @a reserve_pub is not a known reserve
+ * @param[out] insufficient_funds set to true if @a reserve_pub has insufficient capacity to create another purse
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_reserve_purse)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig,
+ const struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_Amount *purse_fee,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *in_conflict,
+ bool *no_reserve,
+ bool *insufficient_funds);
+
+
+ /**
+ * Function called to approve merging of a purse with
+ * an account, made by the receiving account.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the purse
+ * @param[out] merge_sig set to the signature confirming the merge
+ * @param[out] merge_timestamp set to the time of the merge
+ * @param[out] partner_url set to the URL of the target exchange, or NULL if the target exchange is us. To be freed by the caller.
+ * @param[out] reserve_pub set to the public key of the reserve/account being credited
+ * @param[out] refunded set to true if purse was refunded
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_purse_merge)(
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseMergeSignatureP *merge_sig,
+ struct GNUNET_TIME_Timestamp *merge_timestamp,
+ char **partner_url,
+ struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *refunded);
+
+
+ /**
+ * Function called to initiate closure of an account.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param reserve_pub public key of the account to close
+ * @param payto_uri where to wire the funds
+ * @param reserve_sig signature affiming that the account is to be closed
+ * @param request_timestamp timestamp of the close request
+ * @param balance balance at the time of closing
+ * @param closing_fee closing fee to charge
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_close_request)(void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *payto_uri,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_Amount *balance,
+ const struct TALER_Amount *closing_fee);
+
+
+ /**
+ * Function called to persist a request to drain profits.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param wtid wire transfer ID to use
+ * @param account_section account to drain
+ * @param payto_uri account to wire funds to
+ * @param request_timestamp time of the signature
+ * @param amount amount to wire
+ * @param master_sig signature affirming the operation
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_drain_profit)(void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const char *account_section,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_Amount *amount,
+ const struct TALER_MasterSignatureP *master_sig);
+
+
+ /**
+ * Function called to get information about a profit drain event.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param wtid wire transfer ID to look up drain event for
+ * @param[out] serial set to serial ID of the entry
+ * @param[out] account_section set to account to drain
+ * @param[out] payto_uri set to account to wire funds to
+ * @param[out] request_timestamp set to time of the signature
+ * @param[out] amount set to amount to wire
+ * @param[out] master_sig set to signature affirming the operation
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_drain_profit)(void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t *serial,
+ char **account_section,
+ char **payto_uri,
+ struct GNUNET_TIME_Timestamp *request_timestamp,
+ struct TALER_Amount *amount,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+ /**
+ * Get profit drain operation ready to execute.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param[out] serial set to serial ID of the entry
+ * @param[out] wtid set set to wire transfer ID to use
+ * @param[out] account_section set to account to drain
+ * @param[out] payto_uri set to account to wire funds to
+ * @param[out] request_timestamp set to time of the signature
+ * @param[out] amount set to amount to wire
+ * @param[out] master_sig set to signature affirming the operation
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*profit_drains_get_pending)(
+ void *cls,
+ uint64_t *serial,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ char **account_section,
+ char **payto_uri,
+ struct GNUNET_TIME_Timestamp *request_timestamp,
+ struct TALER_Amount *amount,
+ struct TALER_MasterSignatureP *master_sig);
+
+
+ /**
+ * Set profit drain operation to finished.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param serial serial ID of the entry to mark finished
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*profit_drains_set_finished)(
+ void *cls,
+ uint64_t serial);
+
+
+ /**
+ * Insert KYC requirement for @a h_payto account into table.
+ *
+ * @param cls closure
+ * @param requirements requirements that must be checked
+ * @param h_payto account that must be KYC'ed
+ * @param reserve_pub if account is a reserve, its public key, NULL otherwise
+ * @param[out] requirement_row set to legitimization requirement row for this check
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_kyc_requirement_for_account)(
+ void *cls,
+ const char *requirements,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *requirement_row);
+
+
+ /**
+ * Begin KYC requirement process.
+ *
+ * @param cls closure
+ * @param h_payto account that must be KYC'ed
+ * @param provider_section provider that must be checked
+ * @param provider_account_id provider account ID
+ * @param provider_legitimization_id provider legitimization ID
+ * @param[out] process_row row the process is stored under
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_kyc_requirement_process)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ uint64_t *process_row);
+
+
+ /**
+ * Fetch information about pending KYC requirement process.
+ *
+ * @param cls closure
+ * @param h_payto account that must be KYC'ed
+ * @param provider_section provider that must be checked
+ * @param[out] redirect_url set to redirect URL for the process
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_pending_kyc_requirement_process)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ char **redirect_url);
+
+
+ /**
+ * Update KYC process with updated provider-linkage and/or
+ * expiration data.
+ *
+ * @param cls closure
+ * @param process_row row to select by
+ * @param provider_section provider that must be checked (technically redundant)
+ * @param h_payto account that must be KYC'ed (helps access by shard, otherwise also redundant)
+ * @param provider_account_id provider account ID
+ * @param provider_legitimization_id provider legitimization ID
+ * @param redirect_url where the user should be redirected to start the KYC process
+ * @param expiration how long is this KYC check set to be valid (in the past if invalid)
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_kyc_process_by_row)(
+ void *cls,
+ uint64_t process_row,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ const char *redirect_url,
+ struct GNUNET_TIME_Absolute expiration);
+
+
+ /**
+ * Lookup KYC requirement.
+ *
+ * @param cls closure
+ * @param legi_row identifies requirement to look up
+ * @param[out] requirements space-separated list of requirements
+ * @param[out] aml_status set to the AML status of the account
+ * @param[out] h_payto account that must be KYC'ed
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_kyc_requirement_by_row)(
+ void *cls,
+ uint64_t requirement_row,
+ char **requirements,
+ enum TALER_AmlDecisionState *aml_status,
+ struct TALER_PaytoHashP *h_payto);
+
+
+ /**
+ * Lookup KYC process meta data.
+ *
+ * @param cls closure
+ * @param provider_section provider that must be checked
+ * @param h_payto account that must be KYC'ed
+ * @param[out] process_row set to row with the legitimization data
+ * @param[out] expiration how long is this KYC check set to be valid (in the past if invalid)
+ * @param[out] provider_account_id provider account ID
+ * @param[out] provider_legitimization_id provider legitimization ID
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_kyc_process_by_account)(
+ void *cls,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ uint64_t *process_row,
+ struct GNUNET_TIME_Absolute *expiration,
+ char **provider_account_id,
+ char **provider_legitimization_id);
+
+
+ /**
+ * Lookup an
+ * @a h_payto by @a provider_legitimization_id.
+ *
+ * @param cls closure
+ * @param provider_section
+ * @param provider_legitimization_id legi to look up
+ * @param[out] h_payto where to write the result
+ * @param[out] process_row identifies the legitimization process on our end
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*kyc_provider_account_lookup)(
+ void *cls,
+ const char *provider_section,
+ const char *provider_legitimization_id,
+ struct TALER_PaytoHashP *h_payto,
+ uint64_t *process_row);
+
+
+ /**
+ * Call us on KYC processes satisfied for the given
+ * account.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param spc function to call for each satisfied KYC process
+ * @param spc_cls closure for @a spc
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_satisfied_kyc_processes)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
+ void *spc_cls);
+
+
+ /**
+ * Call us on KYC legitimization processes satisfied and not expired for the
+ * given account.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param lpc function to call for each satisfied KYC legitimization process
+ * @param lpc_cls closure for @a lpc
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*iterate_kyc_reference)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_LegitimizationProcessCallback lpc,
+ void *lpc_cls);
+
+
+ /**
+ * Call @a kac on withdrawn amounts after @a time_limit which are relevant
+ * for a KYC trigger for a the (debited) account identified by @a h_payto.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_withdraw_amounts_for_kyc_check)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+
+ /**
+ * Call @a kac on aggregated amounts after @a time_limit which are relevant for a
+ * KYC trigger for a the (credited) account identified by @a h_payto.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_aggregation_amounts_for_kyc_check)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+
+ /**
+ * Call @a kac on merged reserve amounts after @a time_limit which are relevant for a
+ * KYC trigger for a the wallet identified by @a h_payto.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_merge_amounts_for_kyc_check)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+
+ /**
+ * Store KYC attribute data, update KYC process status and
+ * AML status for the given account.
+ *
+ * @param cls closure
+ * @param process_row KYC process row to update
+ * @param h_payto account for which the attribute data is stored
+ * @param kyc_prox key for similarity search
+ * @param provider_section provider that must be checked
+ * @param num_checks how many checks do these attributes satisfy
+ * @param satisfied_checks array of checks satisfied by these attributes
+ * @param provider_account_id provider account ID
+ * @param provider_legitimization_id provider legitimization ID
+ * @param birthday birthdate of user, in days after 1990, or 0 if unknown or definitively adult
+ * @param collection_time when was the data collected
+ * @param expiration_time when does the data expire
+ * @param enc_attributes_size number of bytes in @a enc_attributes
+ * @param enc_attributes encrypted attribute data
+ * @param require_aml true to trigger AML
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_kyc_attributes)(
+ void *cls,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct GNUNET_ShortHashCode *kyc_prox,
+ const char *provider_section,
+ unsigned int num_checks,
+ const char *satisfied_checks[static num_checks],
+ uint32_t birthday,
+ struct GNUNET_TIME_Timestamp collection_time,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ struct GNUNET_TIME_Absolute expiration_time,
+ size_t enc_attributes_size,
+ const void *enc_attributes,
+ bool require_aml);
+
+
+ /**
+ * Lookup similar KYC attribute data.
+ *
+ * @param cls closure
+ * @param kyc_prox key for similarity search
+ * @param cb callback to invoke on each match
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_similar_kyc_attributes)(
+ void *cls,
+ const struct GNUNET_ShortHashCode *kyc_prox,
+ TALER_EXCHANGEDB_AttributeCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Lookup KYC attribute data for a specific account.
+ *
+ * @param cls closure
+ * @param h_payto account for which the attribute data is stored
+ * @param cb callback to invoke on each match
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_kyc_attributes)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_AttributeCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Insert AML staff record.
+ *
+ * @param cls closure
+ * @param decider_pub public key of the staff member
+ * @param master_sig offline signature affirming the AML officer
+ * @param decider_name full name of the staff member
+ * @param is_active true to enable, false to set as inactive
+ * @param read_only true to set read-only access
+ * @param last_change when was the change made effective
+ * @param[out] previous_change when was the previous change made
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_aml_officer)(
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *decider_name,
+ bool is_active,
+ bool read_only,
+ struct GNUNET_TIME_Timestamp last_change,
+ struct GNUNET_TIME_Timestamp *previous_change);
+
+
+ /**
+ * Test if the given AML staff member is active
+ * (at least read-only).
+ *
+ * @param cls closure
+ * @param decider_pub public key of the staff member
+ * @return database transaction status, if member is unknown or not active, 1 if member is active
+ */
+ enum GNUNET_DB_QueryStatus
+ (*test_aml_officer)(
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub);
+
+
+ /**
+ * Fetch AML staff record.
+ *
+ * @param cls closure
+ * @param decider_pub public key of the staff member
+ * @param[out] master_sig offline signature affirming the AML officer
+ * @param[out] decider_name full name of the staff member
+ * @param[out] is_active true to enable, false to set as inactive
+ * @param[out] read_only true to set read-only access
+ * @param[out] last_change when was the change made effective
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_aml_officer)(
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ struct TALER_MasterSignatureP *master_sig,
+ char **decider_name,
+ bool *is_active,
+ bool *read_only,
+ struct GNUNET_TIME_Absolute *last_change);
+
+
+ /**
+ * Obtain the current AML threshold set for an account.
+ *
+ * @param cls closure
+ * @param h_payto account for which the AML threshold is stored
+ * @param[out] decision set to current AML decision
+ * @param[out] threshold set to the existing threshold
+ * @return database transaction status, 0 if no threshold was set
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_aml_threshold)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_AmlDecisionState *decision,
+ struct TALER_EXCHANGEDB_KycStatus *kyc,
+ struct TALER_Amount *threshold);
+
+
+ /**
+ * Trigger AML process, an account has crossed the threshold. Inserts or
+ * updates the AML status.
+ *
+ * @param cls closure
+ * @param h_payto account for which the attribute data is stored
+ * @param threshold_crossed existing threshold that was crossed
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*trigger_aml_process)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_Amount *threshold_crossed);
+
+
+ /**
+ * Lookup AML decisions that have a particular state.
+ *
+ * @param cls closure
+ * @param decision which decision states to filter by
+ * @param row_off offset to start from
+ * @param forward true to go forward in time, false to go backwards
+ * @param cb callback to invoke on each match
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_aml_process)(
+ void *cls,
+ enum TALER_AmlDecisionState decision,
+ uint64_t row_off,
+ uint64_t limit,
+ bool forward,
+ TALER_EXCHANGEDB_AmlStatusCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Lookup AML decision history for a particular account.
+ *
+ * @param cls closure
+ * @param h_payto which account should we return the AML decision history for
+ * @param cb callback to invoke on each match
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_aml_history)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_AmlHistoryCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Insert an AML decision. Inserts into AML history and insert or updates AML
+ * status.
+ *
+ * @param cls closure
+ * @param h_payto account for which the attribute data is stored
+ * @param new_threshold new monthly threshold that would trigger an AML check
+ * @param new_status AML decision status
+ * @param decision_time when was the decision made
+ * @param justification human-readable text justifying the decision
+ * @param kyc_requirements specific KYC requirements being imposed
+ * @param requirements_row row in the KYC table for this process, 0 for none
+ * @param decider_pub public key of the staff member
+ * @param decider_sig signature of the staff member
+ * @param[out] invalid_officer set to TRUE if @a decider_pub is not allowed to make decisions right now
+ * @param[out] last_date set to the previous decision time;
+ * the INSERT is not performed if @a last_date is not before @a decision_time
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_aml_decision)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_Amount *new_threshold,
+ enum TALER_AmlDecisionState new_status,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const char *justification,
+ const json_t *kyc_requirements,
+ uint64_t requirements_row,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ const struct TALER_AmlOfficerSignatureP *decider_sig,
+ bool *invalid_officer,
+ struct GNUNET_TIME_Timestamp *last_date);
+
+
+ /**
+ * Update KYC process status to finished (and failed).
+ *
+ * @param cls closure
+ * @param process_row KYC process row to update
+ * @param h_payto account for which the attribute data is stored
+ * @param provider_section provider that must be checked
+ * @param provider_account_id provider account ID
+ * @param provider_legitimization_id provider legitimization ID
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_kyc_failure)(
+ void *cls,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ const char *provider_account_id,
+ const char *provider_legitimization_id);
+
+ /**
+ * Function called to inject auditor triggers into the
+ * database, triggering the real-time auditor upon
+ * relevant INSERTs.
+ *
+ * @param cls closure
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR on DB errors
+ */
+ enum GNUNET_GenericReturnValue
+ (*inject_auditor_triggers)(void *cls);
};
diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h
new file mode 100644
index 000000000..1eb567f72
--- /dev/null
+++ b/src/include/taler_extensions.h
@@ -0,0 +1,386 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file include/taler_extensions.h
+ * @brief Interface for extensions
+ * @author Özgür Kesim
+ */
+#ifndef TALER_EXTENSIONS_H
+#define TALER_EXTENSIONS_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_extensions_policy.h"
+
+
+#define TALER_EXTENSION_SECTION_PREFIX "exchange-extension-"
+
+enum TALER_Extension_Type
+{
+ TALER_Extension_PolicyNull = 0,
+
+ TALER_Extension_AgeRestriction = 1,
+ TALER_Extension_PolicyMerchantRefund = 2,
+ TALER_Extension_PolicyBrandtVickeryAuction = 3,
+ TALER_Extension_PolicyEscrowedPayment = 4,
+
+ TALER_Extension_MaxPredefined = 5 // Must be last of the predefined
+};
+
+
+/* Forward declarations */
+enum TALER_PolicyFulfillmentState;
+struct TALER_PolicyFulfillmentOutcome;
+
+/*
+ * @brief Represents the implementation of an extension.
+ *
+ * An "Extension" is an optional feature for the Exchange.
+ * There are only two types of extensions:
+ *
+ * a) Age restriction: This is a special feature that directly interacts with
+ * denominations and coins, but is not define policies during deposits, see b).
+ * The implementation of this extension doesn't have to implement any of the
+ * http- or depost-handlers in the struct.
+ *
+ * b) Policies for deposits: These are extensions that define policies (such
+ * as refund, escrow or auctions) for deposit requests. These extensions have
+ * to implement at least the deposit- and post-http-handler in the struct to be
+ * functional.
+ *
+ * In addition to the handlers defined in this struct, an extension must also
+ * be a plugin in the GNUNET_Plugin sense. That is, it must implement the
+ * functions
+ * 1: (void *ext)libtaler_extension_<name>_init(void *cfg)
+ * and
+ * 2: (void *)libtaler_extension_<name>_done(void *)
+ *
+ * In 1:, the input will be the GNUNET_CONFIGURATION_Handle to the TALER
+ * configuration and the output must be the struct TALER_Extension * on
+ * success, NULL otherwise.
+ *
+ * In 2:, no arguments are passed and NULL is expected to be returned.
+ */
+struct TALER_Extension
+{
+ /**
+ * Type of the extension. Only one extension of a type can be loaded
+ * at any time.
+ */
+ enum TALER_Extension_Type type;
+
+ /**
+ * The name of the extension, must be unique among all loaded extensions. It
+ * is used in URLs for /extension/$NAME as well.
+ */
+ char *name;
+
+ /**
+ * Criticality of the extension. It has the same semantics as "critical" has
+ * for extensions in X.509:
+ * - if "true", the client must "understand" the extension before proceeding,
+ * - if "false", clients can safely skip extensions they do not understand.
+ * (see https://datatracker.ietf.org/doc/html/rfc5280#section-4.2)
+ */
+ bool critical;
+
+ /**
+ * Version of the extension must be provided in Taler's protocol version ranges notation, see
+ * https://docs.taler.net/core/api-common.html#protocol-version-ranges
+ */
+ char *version;
+
+ /**
+ * If the extension is marked as enabled, it will be listed in the
+ * "extensions" field in the "/keys" response.
+ */
+ bool enabled;
+
+ /**
+ * Opaque (public) configuration object, set by the extension.
+ */
+ void *config;
+
+
+ /**
+ * @brief Handler to to disable the extension.
+ *
+ * @param ext The current extension object
+ */
+ void (*disable)(struct TALER_Extension *ext);
+
+ /**
+ * @brief Handler to read an extension-specific configuration in JSON
+ * encoding and enable the extension. Must be implemented by the extension.
+ *
+ * @param[in] ext The extension object. If NULL, the configuration will only be checked.
+ * @param[in,out] config A JSON blob
+ * @return GNUNET_OK if the json was a valid configuration for the extension.
+ */
+ enum GNUNET_GenericReturnValue (*load_config)(
+ const json_t *config,
+ struct TALER_Extension *ext);
+
+ /**
+ * @brief Handler to return the manifest of the extension in JSON encoding.
+ *
+ * See
+ * https://docs.taler.net/design-documents/006-extensions.html#tsref-type-Extension
+ * for the definition.
+ *
+ * @param ext The extension object
+ * @return The JSON encoding of the extension, if enabled, NULL otherwise.
+ */
+ json_t *(*manifest)(
+ const struct TALER_Extension *ext);
+
+ /* =========================
+ * Policy related handlers
+ * =========================
+ */
+
+ /**
+ * @brief Handler to check an incoming policy and create a
+ * TALER_PolicyDetails. Can be NULL;
+ *
+ * When a deposit request refers to this extension in its policy
+ * (see https://docs.taler.net/core/api-exchange.html#deposit), this handler
+ * will be called before the deposit transaction.
+ *
+ * @param[in] currency Currency used in the exchange
+ * @param[in] policy_json Details about the policy, provided by the client
+ * during a deposit request.
+ * @param[out] details On success, will contain the details to the policy,
+ * evaluated by the corresponding policy handler.
+ * @param[out] error_hint On error, will contain a hint
+ * @return GNUNET_OK if the data was accepted by the extension.
+ */
+ enum GNUNET_GenericReturnValue (*create_policy_details)(
+ const char *currency,
+ const json_t *policy_json,
+ struct TALER_PolicyDetails *details,
+ const char **error_hint);
+
+ /**
+ * @brief Handler for POST-requests to the /extensions/$name endpoint. Can be NULL.
+ *
+ * @param[in] root The JSON body from the request
+ * @param[in] args Additional query parameters of the request.
+ * @param[in,out] details List of policy details related to the incoming fulfillment proof
+ * @param[in] details_len Size of the list @e details
+ * @param[out] output JSON output to return to the client
+ * @return GNUNET_OK on success.
+ */
+ enum GNUNET_GenericReturnValue (*policy_post_handler)(
+ const json_t *root,
+ const char *const args[],
+ struct TALER_PolicyDetails *details,
+ size_t details_len,
+ json_t **output);
+
+ /**
+ * @brief Handler for GET-requests to the /extensions/$name endpoint. Can be NULL.
+ *
+ * @param connection The current connection
+ * @param root The JSON body from the request
+ * @param args Additional query parameters of the request.
+ * @return MDH result
+ */
+ MHD_RESULT (*policy_get_handler)(
+ struct MHD_Connection *connection,
+ const char *const args[]);
+};
+
+
+/*
+ * @brief simply linked list of extensions
+ */
+
+struct TALER_Extensions
+{
+ struct TALER_Extensions *next;
+ const struct TALER_Extension *extension;
+};
+
+/**
+ * Generic functions for extensions
+ */
+
+/**
+ * @brief Loads the extensions as shared libraries, as specified in the given
+ * TALER configuration.
+ *
+ * @param cfg Handle to the TALER configuration
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if unknown extensions were found
+ * or any particular configuration couldn't be parsed.
+ */
+enum GNUNET_GenericReturnValue
+TALER_extensions_init (
+ const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+/*
+ * @brief Parses a given JSON object as an extension manifest.
+ *
+ * @param[in] obj JSON object to parse as an extension manifest
+ * @param{out] critical will be set to 1 if the extension is critical according to obj
+ * @param[out] version will be set to the version of the extension according to obj
+ * @param[out] config will be set to the configuration of the extension according to obj
+ * @return OK on success, Error otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_extensions_parse_manifest (
+ json_t *obj,
+ int *critical,
+ const char **version,
+ json_t **config);
+
+/*
+ * @brief Loads extensions according to the manifests.
+ *
+ * The JSON object must be of type ExtensionsManifestsResponse as described
+ * in https://docs.taler.net/design-documents/006-extensions.html#exchange
+ *
+ * @param cfg JSON object containing the manifests for all extensions
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if unknown extensions were
+ * found or any particular configuration couldn't be parsed.
+ */
+enum GNUNET_GenericReturnValue
+TALER_extensions_load_manifests (
+ const json_t *manifests);
+
+/*
+ * @brief Returns the head of the linked list of extensions.
+ */
+const struct TALER_Extensions *
+TALER_extensions_get_head (void);
+
+/**
+ * @brief Finds and returns a supported extension by a given type.
+ *
+ * @param type of the extension to lookup
+ * @return extension found, or NULL (should not happen!)
+ */
+const struct TALER_Extension *
+TALER_extensions_get_by_type (
+ enum TALER_Extension_Type type);
+
+
+/**
+ * @brief Finds and returns a supported extension by a given name.
+ *
+ * @param name name of the extension to lookup
+ * @return the extension, if found, NULL otherwise
+ */
+const struct TALER_Extension *
+TALER_extensions_get_by_name (
+ const char *name);
+
+/**
+ * @brief Check if a given type of an extension is enabled
+ *
+ * @param type type of to check
+ * @return true enabled, false if not enabled, will assert if type is not found.
+ */
+bool
+TALER_extensions_is_enabled_type (
+ enum TALER_Extension_Type type);
+
+/**
+ * @brief Check if an extension is enabled
+ *
+ * @param extension The extension handler.
+ * @return true enabled, false if not enabled, will assert if type is not found.
+ */
+bool
+TALER_extensions_is_enabled (
+ const struct TALER_Extension *extension);
+
+/*
+ * Verify the signature of a given JSON object for extensions with the master
+ * key of the exchange.
+ *
+ * The JSON object must be of type ExtensionsManifestsResponse as described in
+ * https://docs.taler.net/design-documents/006-extensions.html#exchange
+ *
+ * @param extensions JSON object with the extension configuration
+ * @param extensions_sig signature of the hash of the JSON object
+ * @param master_pub public key to verify the signature
+ * @return GNUNET_OK on success, GNUNET_SYSERR when hashing of the JSON fails
+ * and GNUNET_NO if the signature couldn't be verified.
+ */
+enum GNUNET_GenericReturnValue
+TALER_extensions_verify_manifests_signature (
+ const json_t *manifests,
+ struct TALER_MasterSignatureP *extensions_sig,
+ struct TALER_MasterPublicKeyP *master_pub);
+
+
+/*
+ * TALER Age Restriction Extension
+ *
+ * This extension is special insofar as it directly interacts with coins and
+ * denominations.
+ *
+ * At the same time, it doesn't implement and http- or deposit-handlers.
+ */
+
+#define TALER_EXTENSION_SECTION_AGE_RESTRICTION (TALER_EXTENSION_SECTION_PREFIX \
+ "age_restriction")
+
+/**
+ * The default age mask represents the age groups
+ * 0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-20, 21-...
+ */
+#define TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_GROUPS "8:10:12:14:16:18:21"
+
+
+/*
+ * @brief Configuration for Age Restriction
+ */
+struct TALER_AgeRestrictionConfig
+{
+ struct TALER_AgeMask mask;
+ uint8_t num_groups;
+};
+
+
+/**
+ * @brief Retrieve the age restriction configuration
+ *
+ * @return age restriction configuration if present, otherwise NULL.
+ */
+const struct TALER_AgeRestrictionConfig *
+TALER_extensions_get_age_restriction_config (void);
+
+/**
+ * @brief Check if age restriction is enabled
+ *
+ * @return true, if age restriction is loaded, configured and enabled; otherwise false.
+ */
+bool
+TALER_extensions_is_age_restriction_enabled (void);
+
+/**
+ * @brief Return the age mask for age restriction
+ *
+ * @return configured age mask, if age restriction is loaded, configured and enabled; otherwise zero mask.
+ */
+struct TALER_AgeMask
+TALER_extensions_get_age_restriction_mask (void);
+
+#endif
diff --git a/src/include/taler_extensions_policy.h b/src/include/taler_extensions_policy.h
new file mode 100644
index 000000000..b10c0d8a2
--- /dev/null
+++ b/src/include/taler_extensions_policy.h
@@ -0,0 +1,205 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file include/taler_extensions_policy.h
+ * @brief Interface for policy extensions
+ * @author Özgür Kesim
+ */
+#ifndef TALER_EXTENSIONS_POLICY_H
+#define TALER_EXTENSIONS_POLICY_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+
+/*
+ * @brief Describes the states of fulfillment of a policy bound to a deposit
+ * NOTE: These values must be in sync with their use in stored procedures, f.e.
+ * exchange_do_insert_or_update_policy_details.
+ */
+enum TALER_PolicyFulfillmentState
+{
+ /* Initial state of an fulfillment, before any other state. */
+ TALER_PolicyFulfillmentInitial = 0,
+
+ /* General error state of an fulfillment. */
+ TALER_PolicyFulfillmentFailure = 1,
+
+ /* The policy is not yet ready due to insufficient funding. More deposits are
+ * necessary for it to become ready . */
+ TALER_PolicyFulfillmentInsufficient = 2,
+
+ /* The policy is funded and ready, pending */
+ TALER_PolicyFulfillmentReady = 3,
+
+ /* Policy is provably fulfilled. */
+ TALER_PolicyFulfillmentSuccess = 4,
+
+ /* Policy fulfillment has timed out */
+ TALER_PolicyFulfillmentTimeout = 5,
+
+ TALER_PolicyFulfillmentStateCount = TALER_PolicyFulfillmentTimeout + 1
+};
+
+
+/*
+ * @brief Returns a string representation of the state of a policy fulfillment
+ */
+const char *
+TALER_policy_fulfillment_state_str (enum TALER_PolicyFulfillmentState state);
+
+
+/* @brief Details of a policy for a deposit request */
+struct TALER_PolicyDetails
+{
+ /* Hash code that should be used for the .policy_hash_code field when
+ * this policy is saved in the policy_details table. */
+ struct GNUNET_HashCode hash_code;
+
+ /* Content of the policy in its original JSON form */
+ json_t *policy_json;
+
+ /* When the deadline is met and the policy is still in "Ready" state,
+ * a timeout-handler will transfer the amount
+ * (total_amount - policy_fee - refreshable_amount)
+ * to the payto-URI from the corresponding deposit. The value
+ * amount_refreshable will be refreshable by the owner of the
+ * associated deposits's coins */
+ struct GNUNET_TIME_Timestamp deadline;
+
+ /* The amount to which this policy commits to. It must be at least as
+ * large as @e policy_fee. */
+ struct TALER_Amount commitment;
+
+ /* The total sum of contributions from coins so far to fund this
+ * policy. It must be at least as large as @commitment in order to be
+ * sufficiently funded. */
+ struct TALER_Amount accumulated_total;
+
+ /* The fee from the exchange for handling the policy. It is due when
+ * the state changes to Timeout or Success. */
+ struct TALER_Amount policy_fee;
+
+ /* The amount that will be transferred to the payto-URIs from the
+ * corresponding deposits when the fulfillment state changes to Timeout
+ * or Success. Note that a fulfillment handler can alter this upon
+ * arrival of a proof of fulfillment. The remaining amount
+ * (accumulated_amount - policy_amount - transferable_amount) */
+ struct TALER_Amount transferable_amount;
+
+ /* The state of fulfillment of a policy.
+ * - If the state is Insufficient, the client is required to call
+ * /deposit -maybe multiple times- with enough coins and the same
+ * policy details in order to reach the required amount. The state is
+ * then changed to Ready.
+ * - If the state changes to Timeout or Success, a handler will transfer
+ * the amount (total_amount - policy_fee - refreshable_amount) to the
+ * payto-URI from the corresponding deposit. The value
+ * amount_refreshable will be refreshable by the owner of the
+ * associated deposits's coins. */
+ enum TALER_PolicyFulfillmentState fulfillment_state;
+
+ /* If there is a proof of fulfillment, the row ID from the
+ * policy_fulfillment table */
+ uint64_t policy_fulfillment_id;
+ bool no_policy_fulfillment_id;
+};
+
+/*
+ * @brief All information required for the database transaction when handling a
+ * proof of fulfillment request.
+ */
+struct TALER_PolicyFulfillmentTransactionData
+{
+ /* The incoming proof, provided by a client */
+ const json_t *proof;
+
+ /* The Hash of the proof */
+ struct GNUNET_HashCode h_proof;
+
+ /* The timestamp of retrieval of the proof */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /* The ID of the proof in the policy_fulfillment table. Will be set
+ * during the transaction. Needed to fill the table
+ * policy_details_fulfillments. */
+ uint64_t fulfillment_id;
+
+ /* The list of policy details. Will be updated by the policy handler */
+ struct TALER_PolicyDetails *details;
+ size_t details_count;
+};
+
+
+/*
+ * @brief Extracts policy details from the deposit's policy options and the policy extensions
+ *
+ * @param[in] currency Currency used in the exchange
+ * @param[in] policy_options JSON of the policy options from a deposit request
+ * @param[out] details On GNUNET_OK, the parsed details
+ * @param[out] error_hint On GNUNET_SYSERR, will contain a hint for the reason why it failed
+ * @return GNUNET_OK on success, GNUNET_NO, when no extension was found. GNUNET_SYSERR when the JSON was
+ * invalid, with *error_hint maybe non-NULL.
+ */
+enum GNUNET_GenericReturnValue
+TALER_extensions_create_policy_details (
+ const char *currency,
+ const json_t *policy_options,
+ struct TALER_PolicyDetails *details,
+ const char **error_hint);
+
+
+/*
+ * ================================
+ * Merchant refund policy
+ * ================================
+ */
+struct TALER_ExtensionPolicyMerchantRefundPolicyConfig
+{
+ struct GNUNET_TIME_Relative max_timeout;
+};
+
+/*
+ * ================================
+ * Brandt-Vickrey Auctions policy
+ * ================================
+ */
+/*
+ * @brief Configuration for Brandt-Vickrey auctions policy
+ */
+struct TALER_ExtensionPolicyBrandtVickreyAuctionConfig
+{
+ uint16_t max_bidders;
+ uint16_t max_prices;
+ struct TALER_Amount auction_fee;
+};
+
+
+/*
+ * ================================
+ * Escrowed Payments policy
+ * ================================
+ */
+/*
+ * @brief Configuration for escrowed payments policy
+ */
+struct TALER_ExtensionPolicyEscrowedPaymentsConfig
+{
+ struct GNUNET_TIME_Relative max_timeout;
+};
+
+#endif
diff --git a/src/include/taler_fakebank_lib.h b/src/include/taler_fakebank_lib.h
index bd4376695..6b34f4730 100644
--- a/src/include/taler_fakebank_lib.h
+++ b/src/include/taler_fakebank_lib.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2016-2020 Taler Systems SA
+ (C) 2016-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
@@ -53,61 +53,71 @@ TALER_FAKEBANK_start (uint16_t port,
/**
- * Check that no wire transfers were ordered (or at least none
- * that have not been taken care of via #TALER_FAKEBANK_check_debit()
- * or #TALER_FAKEBANK_check_credit()).
- * If any transactions are onrecord, return #GNUNET_SYSERR.
+ * Start the fake bank. The fake bank will, like the normal bank, listen for
+ * requests for /admin/add/incoming and /transfer. However, instead of
+ * executing or storing those requests, it will simply allow querying whether
+ * such a request has been made via #TALER_FAKEBANK_check_debit() and
+ * #TALER_FAKEBANK_check_credit() as well as the history API.
*
- * @param h bank instance
- * @return #GNUNET_OK on success
+ * This is useful for writing testcases to check whether the exchange
+ * would have issued the correct wire transfer orders.
+ *
+ * @param port port to listen to
+ * @param currency which currency should the bank offer
+ * @param ram_limit how much memory do we use at most
+ * @param num_threads size of the thread pool, 0 to use the GNUnet scheduler
+ * @return NULL on error
*/
-int
-TALER_FAKEBANK_check_empty (struct TALER_FAKEBANK_Handle *h);
+struct TALER_FAKEBANK_Handle *
+TALER_FAKEBANK_start2 (uint16_t port,
+ const char *currency,
+ uint64_t ram_limit,
+ unsigned int num_threads);
/**
- * Tell the fakebank to create another wire transfer *from* an exchange.
+ * Start the fake bank. The fake bank will, like the normal bank, listen for
+ * requests for /admin/add/incoming and /transfer. However, instead of
+ * executing or storing those requests, it will simply allow querying whether
+ * such a request has been made via #TALER_FAKEBANK_check_debit() and
+ * #TALER_FAKEBANK_check_credit() as well as the history API.
+ *
+ * This is useful for writing testcases to check whether the exchange
+ * would have issued the correct wire transfer orders.
*
- * @param h fake bank handle
- * @param debit_account account to debit
- * @param credit_account account to credit
- * @param amount amount to transfer
- * @param subject wire transfer subject to use
- * @param exchange_base_url exchange URL
- * @param request_uid unique number to make the request unique, or NULL to create one
- * @param[out] ret_row_id pointer to store the row ID of this transaction
- * @return #GNUNET_YES if the transfer was successful,
- * #GNUNET_SYSERR if the request_uid was reused for a different transfer
+ * @param hostname hostname to use in URLs and URIs.
+ * @param port port to listen to
+ * @param exchange_url suggested exchange base URL
+ * @param currency which currency should the bank offer
+ * @param ram_limit how much memory do we use at most
+ * @param num_threads size of the thread pool, 0 to use the GNUnet scheduler
+ * @param signup_bonus how much to credit new users
+ * @return NULL on error
*/
-int
-TALER_FAKEBANK_make_transfer (
- struct TALER_FAKEBANK_Handle *h,
- const char *debit_account,
- const char *credit_account,
- const struct TALER_Amount *amount,
- const struct TALER_WireTransferIdentifierRawP *subject,
- const char *exchange_base_url,
- const struct GNUNET_HashCode *request_uid,
- uint64_t *ret_row_id);
+struct TALER_FAKEBANK_Handle *
+TALER_FAKEBANK_start3 (const char *hostname,
+ uint16_t port,
+ const char *exchange_url,
+ const char *currency,
+ uint64_t ram_limit,
+ unsigned int num_threads,
+ const struct TALER_Amount *signup_bonus);
/**
- * Tell the fakebank to create another wire transfer *to* an exchange.
+ * Check that no wire transfers were ordered (or at least none
+ * that have not been taken care of via #TALER_FAKEBANK_check_debit()
+ * or #TALER_FAKEBANK_check_credit()).
+ * If any transactions are onrecord, return #GNUNET_SYSERR.
+ *
+ * Note that this function only works in
+ * single-threaded mode while nothing else is happening.
*
- * @param h fake bank handle
- * @param debit_account account to debit
- * @param credit_account account to credit
- * @param amount amount to transfer
- * @param reserve_pub reserve public key to use in subject
- * @return serial_id of the transfer
+ * @param h bank instance
+ * @return #GNUNET_OK on success
*/
-uint64_t
-TALER_FAKEBANK_make_admin_transfer (
- struct TALER_FAKEBANK_Handle *h,
- const char *debit_account,
- const char *credit_account,
- const struct TALER_Amount *amount,
- const struct TALER_ReservePublicKeyP *reserve_pub);
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_check_empty (struct TALER_FAKEBANK_Handle *h);
/**
@@ -116,16 +126,19 @@ TALER_FAKEBANK_make_admin_transfer (
* to the transfer identifier and remove the transaction from the
* list. If the transaction was not recorded, return #GNUNET_SYSERR.
*
+ * Note that this function only works in
+ * single-threaded mode while nothing else is happening.
+ *
* @param h bank instance
* @param want_amount transfer amount desired
* @param want_debit account that should have been debited
- * @param want_debit account that should have been credited
+ * @param want_credit account that should have been credited
* @param exchange_base_url expected base URL of the exchange,
* i.e. "https://example.com/"; may include a port
* @param[out] wtid set to the wire transfer identifier
* @return #GNUNET_OK on success
*/
-int
+enum GNUNET_GenericReturnValue
TALER_FAKEBANK_check_debit (struct TALER_FAKEBANK_Handle *h,
const struct TALER_Amount *want_amount,
const char *want_debit,
@@ -139,14 +152,17 @@ TALER_FAKEBANK_check_debit (struct TALER_FAKEBANK_Handle *h,
* @a want_credit account with the @a subject. If so, remove the transaction
* from the list. If the transaction was not recorded, return #GNUNET_SYSERR.
*
+ * Note that this function only works in
+ * single-threaded mode while nothing else is happening.
+ *
* @param h bank instance
* @param want_amount transfer amount desired
* @param want_debit account that should have been debited
- * @param want_debit account that should have been credited
+ * @param want_credit account that should have been credited
* @param reserve_pub reserve public key expected in wire subject
* @return #GNUNET_OK on success
*/
-int
+enum GNUNET_GenericReturnValue
TALER_FAKEBANK_check_credit (struct TALER_FAKEBANK_Handle *h,
const struct TALER_Amount *want_amount,
const char *want_debit,
diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h
index fa14dc0bc..98e565f0c 100644
--- a/src/include/taler_json_lib.h
+++ b/src/include/taler_json_lib.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014, 2015, 2016 Taler Systems SA
+ Copyright (C) 2014-2024 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
@@ -29,6 +29,40 @@
#include "taler_error_codes.h"
/**
+ * Version of this API, for compatibility tests.
+ */
+#define TALER_JSON_LIB_VERSION 0x00020000
+
+/**
+ * Details about an encrypted contract.
+ */
+struct TALER_EncryptedContract
+{
+
+ /**
+ * Signature of the client affiming this encrypted contract.
+ */
+ struct TALER_PurseContractSignatureP econtract_sig;
+
+ /**
+ * Contract decryption key for the purse.
+ */
+ struct TALER_ContractDiffiePublicP contract_pub;
+
+ /**
+ * Encrypted contract, can be NULL.
+ */
+ void *econtract;
+
+ /**
+ * Number of bytes in @e econtract.
+ */
+ size_t econtract_size;
+
+};
+
+
+/**
* Print JSON parsing related error information
* @deprecated
*/
@@ -37,14 +71,139 @@
"JSON parsing failed at %s:%u: %s (%s)\n", \
__FILE__, __LINE__, error.text, error.source)
+
/**
- * Convert a TALER amount to a JSON object.
+ * Generate packer instruction for a JSON field of type
+ * absolute time creating a human-readable timestamp.
*
- * @param amount the amount
- * @return a json object describing the amount
+ * @param name name of the field to add to the object
+ * @param at absolute time to pack
+ * @return json pack specification
*/
-json_t *
-TALER_JSON_from_amount (const struct TALER_Amount *amount);
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_time_abs_human (const char *name,
+ struct GNUNET_TIME_Absolute at);
+
+
+/**
+ * Put an error code into a JSON reply, including
+ * both the numeric value and the hint.
+ *
+ * @param ec error code to encode using canonical field names
+ */
+#define TALER_JSON_pack_ec(ec) \
+ GNUNET_JSON_pack_string ("hint", TALER_ErrorCode_get_hint (ec)), \
+ GNUNET_JSON_pack_uint64 ("code", ec)
+
+
+/**
+ * Generate packer instruction for a JSON field of type
+ * denomination public key.
+ *
+ * @param name name of the field to add to the object
+ * @param pk public key
+ * @return json pack specification
+ */
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_denom_pub (
+ const char *name,
+ const struct TALER_DenominationPublicKey *pk);
+
+
+/**
+ * Generate packer instruction for a JSON field of type
+ * denomination signature.
+ *
+ * @param name name of the field to add to the object
+ * @param sig signature
+ * @return json pack specification
+ */
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_denom_sig (
+ const char *name,
+ const struct TALER_DenominationSignature *sig);
+
+
+/**
+ * Generate packer instruction for a JSON field of type
+ * blinded denomination signature (that needs to be
+ * unblinded before it becomes valid).
+ *
+ * @param name name of the field to add to the object
+ * @param sig signature
+ * @return json pack specification
+ */
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_blinded_denom_sig (
+ const char *name,
+ const struct TALER_BlindedDenominationSignature *sig);
+
+
+/**
+ * Generate packer instruction for a JSON field of type
+ * blinded planchet.
+ *
+ * @param name name of the field to add to the object
+ * @param blinded_planchet blinded planchet
+ * @return json pack specification
+ */
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_blinded_planchet (
+ const char *name,
+ const struct TALER_BlindedPlanchet *blinded_planchet);
+
+
+/**
+ * Generate packer instruction for a JSON field of type
+ * exchange withdraw values (/csr).
+ *
+ * @param name name of the field to add to the object
+ * @param ewv values to transmit
+ * @return json pack specification
+ */
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_exchange_withdraw_values (
+ const char *name,
+ const struct TALER_ExchangeWithdrawValues *ewv);
+
+
+/**
+ * Generate packer instruction for a JSON field of type
+ * amount.
+ *
+ * @param name name of the field to add to the object
+ * @param amount valid amount to pack
+ * @return json pack specification
+ */
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_amount (const char *name,
+ const struct TALER_Amount *amount);
+
+
+/**
+ * Generate packer instruction for a JSON field of type
+ * encrypted contract.
+ *
+ * @param name name of the field to add to the object
+ * @param econtract the encrypted contract
+ * @return json pack specification
+ */
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_econtract (
+ const char *name,
+ const struct TALER_EncryptedContract *econtract);
+
+/**
+ * Generate packer instruction for a JSON field of type age_commitment
+ *
+ * @param name name of the field to add to the object
+ * @param age_commitment age commitment to add
+ * @return json pack specification
+ */
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_age_commitment (
+ const char *name,
+ const struct TALER_AgeCommitment *age_commitment);
/**
@@ -54,31 +213,169 @@ TALER_JSON_from_amount (const struct TALER_Amount *amount);
* @return a json object describing the amount
*/
json_t *
-TALER_JSON_from_amount_nbo (const struct TALER_AmountNBO *amount);
+TALER_JSON_from_amount (const struct TALER_Amount *amount);
/**
* Provide specification to parse given JSON object to an amount.
+ * The @a currency must be a valid pointer while the
+ * parsing is done, a copy is not made.
*
* @param name name of the amount field in the JSON
+ * @param currency the currency the amount must be in
* @param[out] r_amount where the amount has to be written
+ * @return spec for parsing an amount
*/
struct GNUNET_JSON_Specification
TALER_JSON_spec_amount (const char *name,
+ const char *currency,
struct TALER_Amount *r_amount);
/**
+ * Provide specification to parse given JSON object to
+ * a currency specification.
+ *
+ * @param name name of the amount field in the JSON
+ * @param currency_code currency code to parse
+ * @param[out] r_cspec where the currency spec has to be written
+ * @return spec for parsing an amount
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_currency_specification (
+ const char *name,
+ const char *currency_code,
+ struct TALER_CurrencySpecification *r_cspec);
+
+
+/**
* Provide specification to parse given JSON object to an amount
- * in network byte order.
+ * in any currency.
*
* @param name name of the amount field in the JSON
* @param[out] r_amount where the amount has to be written
+ * @return spec for parsing an amount
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_amount_any (const char *name,
+ struct TALER_Amount *r_amount);
+
+
+/**
+ * Provide specification to parse given JSON object to an encrypted contract.
+ *
+ * @param name name of the amount field in the JSON
+ * @param[out] econtract where to store the encrypted contract
+ * @return spec for parsing an amount
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_econtract (const char *name,
+ struct TALER_EncryptedContract *econtract);
+
+
+/**
+ * Provide specification to parse a given JSON object to an age commitment.
+ *
+ * @param name name of the age commitment field in the JSON
+ * @param[out] age_commitment where to store the age commitment
+ * @return spec for parsing an age commitment
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_age_commitment (const char *name,
+ struct TALER_AgeCommitment *age_commitment);
+
+
+/**
+ * Provide specification to parse an OTP key.
+ * An OTP key must be an RFC 3548 base32-encoded
+ * value (so NOT our usual Crockford-base32 encoding!).
+ *
+ * @param name name of the OTP key field in the JSON
+ * @param[out] otp_key where to store the OTP key
+ * @return spec for parsing an age commitment
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_otp_key (const char *name,
+ const char **otp_key);
+
+
+/**
+ * Provide specification to parse an OTP method type.
+ * The value could be provided as an integer or
+ * as a descriptive string.
+ *
+ * @param name name of the OTP method type in the JSON
+ * @param[out] mca where to store the method type
+ * @return spec for parsing an age commitment
*/
struct GNUNET_JSON_Specification
-TALER_JSON_spec_amount_nbo (const char *name,
- struct TALER_AmountNBO *r_amount);
+TALER_JSON_spec_otp_type (const char *name,
+ enum TALER_MerchantConfirmationAlgorithm *mca);
+
+/**
+ * Generate specification to parse all fees for
+ * a denomination under a prefix @a pfx.
+ *
+ * @param pfx string prefix to use
+ * @param currency which currency to expect
+ * @param[out] dfs a `struct TALER_DenomFeeSet` to initialize
+ */
+#define TALER_JSON_SPEC_DENOM_FEES(pfx,currency,dfs) \
+ TALER_JSON_spec_amount (pfx "_withdraw", (currency), &(dfs)->withdraw), \
+ TALER_JSON_spec_amount (pfx "_deposit", (currency), &(dfs)->deposit), \
+ TALER_JSON_spec_amount (pfx "_refresh", (currency), &(dfs)->refresh), \
+ TALER_JSON_spec_amount (pfx "_refund", (currency), &(dfs)->refund)
+
+
+/**
+ * Macro to pack all of a denominations' fees under
+ * a given @a pfx.
+ *
+ * @param pfx string prefix to use
+ * @param dfs a `struct TALER_DenomFeeSet` to pack
+ */
+#define TALER_JSON_PACK_DENOM_FEES(pfx, dfs) \
+ TALER_JSON_pack_amount (pfx "_withdraw", &(dfs)->withdraw), \
+ TALER_JSON_pack_amount (pfx "_deposit", &(dfs)->deposit), \
+ TALER_JSON_pack_amount (pfx "_refresh", &(dfs)->refresh), \
+ TALER_JSON_pack_amount (pfx "_refund", &(dfs)->refund)
+
+
+/**
+ * Generate specification to parse all global fees.
+ *
+ * @param currency which currency to expect
+ * @param[out] gfs a `struct TALER_GlobalFeeSet` to initialize
+ */
+#define TALER_JSON_SPEC_GLOBAL_FEES(currency,gfs) \
+ TALER_JSON_spec_amount ("history_fee", (currency), &(gfs)->history), \
+ TALER_JSON_spec_amount ("account_fee", (currency), &(gfs)->account), \
+ TALER_JSON_spec_amount ("purse_fee", (currency), &(gfs)->purse)
+
+/**
+ * Macro to pack all of the global fees.
+ *
+ * @param gfs a `struct TALER_GlobalFeeSet` to pack
+ */
+#define TALER_JSON_PACK_GLOBAL_FEES(gfs) \
+ TALER_JSON_pack_amount ("history_fee", &(gfs)->history), \
+ TALER_JSON_pack_amount ("account_fee", &(gfs)->account), \
+ TALER_JSON_pack_amount ("purse_fee", &(gfs)->purse)
+
+
+/**
+ * Generate a parser for a group of denominations.
+ *
+ * @param[in] field name of the field, maybe NULL
+ * @param[in] currency name of the currency
+ * @param[out] group denomination group information
+ * @return corresponding field spec
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denomination_group (const char *field,
+ const char *currency,
+ struct TALER_DenominationGroup *group);
/**
* Generate line in parser specification for denomination public key.
@@ -88,20 +385,206 @@ TALER_JSON_spec_amount_nbo (const char *name,
* @return corresponding field spec
*/
struct GNUNET_JSON_Specification
-TALER_JSON_spec_denomination_public_key (const char *field,
- struct TALER_DenominationPublicKey *pk);
+TALER_JSON_spec_denom_pub (const char *field,
+ struct TALER_DenominationPublicKey *pk);
+
+
+/**
+ * Generate line in parser specification for error codes.
+ *
+ * @param field name of the field
+ * @param[out] ec error code to initialize
+ * @return corresponding field spec
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_ec (const char *field,
+ enum TALER_ErrorCode *ec);
+
+
+/**
+ * Generate line in parser specification for
+ * HTTP/HTTPS URLs.
+ *
+ * @param field name of the field
+ * @param[out] url web URL to initialize
+ * @return corresponding field spec
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_web_url (const char *field,
+ const char **url);
+
+
+/**
+ * Generate line in parser specification for
+ * "payto://" URIs.
+ *
+ * @param field name of the field
+ * @param[out] payto_uri RFC 8905 URI to initialize
+ * @return corresponding field spec
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_payto_uri (const char *field,
+ const char **payto_uri);
+
+
+/**
+ * Generate line in parser specification for AML decision states.
+ *
+ * @param field name of the field
+ * @param[out] aml_state AML state to initialize
+ * @return corresponding field spec
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_aml_decision (const char *field,
+ enum TALER_AmlDecisionState *aml_state);
+
+
+/**
+ * Representation of a protocol version.
+ */
+struct TALER_JSON_ProtocolVersion
+{
+ /**
+ * Current version of the protocol.
+ */
+ unsigned int current;
+
+ /**
+ * Implementation revision for the @e current
+ * version.
+ */
+ unsigned int revision;
+
+ /**
+ * Number of protocol versions this @e revision is
+ * backwards-compatible with. Subtract this number
+ * from @e current to get the minimum protocol version
+ * required from the client.
+ */
+ unsigned int age;
+};
+
+
+/**
+ * Generate line in parser specification for protocol
+ * versions (``/config``). The field must be a string
+ * encoding the version as "$CURRENT:$REVISION:$AGE".
+ *
+ * @param field name of the field (usually "version")
+ * @param[out] ver protocol versions to initialize
+ * @return corresponding field spec
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_version (const char *field,
+ struct TALER_JSON_ProtocolVersion *ver);
+
+
+/**
+ * Generate a parser specification for a denomination public key of a given
+ * cipher.
+ *
+ * @param field name of the field
+ * @param cipher which cipher type to parse for
+ * @param[out] pk key to fill
+ * @return corresponding field spec
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denom_pub_cipher (
+ const char *field,
+ enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher,
+ struct TALER_DenominationPublicKey *pk);
/**
* Generate line in parser specification for denomination signature.
*
* @param field name of the field
- * @param sig the signature to initialize
+ * @param[out] sig the signature to initialize
+ * @return corresponding field spec
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denom_sig (const char *field,
+ struct TALER_DenominationSignature *sig);
+
+
+/**
+ * Generate line in parser specification for a
+ * blinded denomination signature.
+ *
+ * @param field name of the field
+ * @param[out] sig the blinded signature to initialize
+ * @return corresponding field spec
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_blinded_denom_sig (
+ const char *field,
+ struct TALER_BlindedDenominationSignature *sig);
+
+
+/**
+ * Generate line in parser specification for
+ * exchange withdraw values (/csr).
+ *
+ * @param field name of the field
+ * @param[out] ewv the exchange withdraw values to initialize
* @return corresponding field spec
*/
struct GNUNET_JSON_Specification
-TALER_JSON_spec_denomination_signature (const char *field,
- struct TALER_DenominationSignature *sig);
+TALER_JSON_spec_exchange_withdraw_values (
+ const char *field,
+ struct TALER_ExchangeWithdrawValues *ewv);
+
+
+/**
+ * Generate line in parser specification for a
+ * blinded planchet.
+ *
+ * @param field name of the field
+ * @param[out] blinded_planchet the blinded planchet to initialize
+ * @return corresponding field spec
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_blinded_planchet (const char *field,
+ struct TALER_BlindedPlanchet *blinded_planchet);
+
+
+/**
+ * The expected field stores a possibly internationalized string.
+ * Internationalization means that there is another field "$name_i18n"
+ * which is an object where the keys are languages. If this is
+ * present, and if @a language_pattern is non-NULL, this function
+ * should return the best match from @a language pattern from the
+ * "_i18n" field. If no language matches, the normal field under
+ * @a name is to be returned.
+ *
+ * The @a language_pattern is given using the format from
+ * https://tools.ietf.org/html/rfc7231#section-5.3.1
+ * so that #TALER_language_matches() can be used.
+ *
+ * @param name name of the JSON field
+ * @param language_pattern language pattern to use to find best match, possibly NULL
+ * @param[out] strptr where to store a pointer to the field with the best variant
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_i18n_string (const char *name,
+ const char *language_pattern,
+ const char **strptr);
+
+
+/**
+ * The expected field stores a possibly internationalized string.
+ * Internationalization means that there is another field "$name_i18n" which
+ * is an object where the keys are languages. If this is present, this
+ * function should return the best match based on the locale from the "_i18n"
+ * field. If no language matches, the normal field under @a name is to be
+ * returned.
+ *
+ * @param name name of the JSON field
+ * @param[out] strptr where to store a pointer to the field with the best variant
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_i18n_str (const char *name,
+ const char **strptr);
/**
@@ -115,11 +598,87 @@ TALER_JSON_spec_denomination_signature (const char *field,
*
* @param[in] json some JSON value to hash
* @param[out] hc resulting hash code
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if @a json was malformed
+ * #GNUNET_SYSERR on internal error
+ */
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_hash (const json_t *json,
+ struct TALER_PrivateContractHashP *hc);
+
+
+/**
+ * Take a given @a contract with "forgettable" fields marked in the @a spec
+ * with 'True' instead of a real salt. Replaces all 'True' values with proper
+ * random salts in the actual @a contract. Fails if any forgettable markers
+ * are neither 'True' nor valid salts.
+ *
+ * @param spec specification with forgettable fields
+ * @param[in,out] contract JSON contract to transform
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_seed_forgettable (const json_t *spec,
+ json_t *contract);
+
+
+/**
+ * Mark part of a contract object as 'forgettable'.
+ *
+ * @param[in,out] json some JSON object to modify
+ * @param field name of the field to mark as forgettable
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
*/
-int
-TALER_JSON_hash (const json_t *json,
- struct GNUNET_HashCode *hc);
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_mark_forgettable (json_t *json,
+ const char *field);
+
+
+/**
+ * Forget part of a contract object.
+ *
+ * @param[in,out] json some JSON object to modify
+ * @param field name of the field to forget
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if the field was already forgotten before
+ * #GNUNET_SYSERR on error
+ */
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_part_forget (json_t *json,
+ const char *field);
+
+
+/**
+ * Called for each path found after expanding a path.
+ *
+ * @param cls the closure.
+ * @param object_id the name of the object that is pointed to.
+ * @param parent the parent of the object at @e object_id.
+ */
+typedef void
+(*TALER_JSON_ExpandPathCallback) (
+ void *cls,
+ const char *object_id,
+ json_t *parent);
+
+
+/**
+ * Expands a path for a json object. May call the callback several times
+ * if the path contains a wildcard.
+ *
+ * @param json the json object the path references.
+ * @param path the path to expand. Must begin with "$." and follow dot notation,
+ * and may include array indices and wildcards.
+ * @param cb the callback.
+ * @param cb_cls closure for the callback.
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if @e path is invalid.
+ */
+enum GNUNET_GenericReturnValue
+TALER_JSON_expand_path (json_t *json,
+ const char *path,
+ TALER_JSON_ExpandPathCallback cb,
+ void *cb_cls);
+
/**
* Extract the Taler error code from the given @a json object.
@@ -133,6 +692,17 @@ TALER_JSON_get_error_code (const json_t *json);
/**
+ * Extract the Taler error hint from the given @a json object.
+ * Note that NULL is returned if no "hint" is present.
+ *
+ * @param json response to extract the error hint from
+ * @return the "hint" value from @a json; only valid as long as @a json is valid
+ */
+const char *
+TALER_JSON_get_error_hint (const json_t *json);
+
+
+/**
* Extract the Taler error code from the given @a data object, which is expected to be in JSON.
* Note that #TALER_EC_INVALID is returned if no "code" is present or if @a data is not in JSON.
*
@@ -156,36 +726,50 @@ TALER_JSON_get_error_code2 (const void *data,
* @param[out] hc set to the hash
* @return #GNUNET_OK on success, #GNUNET_SYSERR if @a wire_s is malformed
*/
-int
+enum GNUNET_GenericReturnValue
TALER_JSON_merchant_wire_signature_hash (const json_t *wire_s,
- struct GNUNET_HashCode *hc);
+ struct TALER_MerchantWireHashP *hc);
/**
- * Check the signature in @a wire_s. Also performs rudimentary
- * checks on the account data *if* supported.
+ * Extract a string from @a object under the field @a field, but respecting
+ * the Taler i18n rules and the language preferences expressed in @a
+ * language_pattern.
*
- * @param wire_s signed wire information of an exchange
- * @param master_pub master public key of the exchange
- * @return #GNUNET_OK if signature is valid
+ * Basically, the @a object may optionally contain a sub-object
+ * "${field}_i18n" with a map from IETF BCP 47 language tags to a localized
+ * version of the string. If this map exists and contains an entry that
+ * matches the @a language pattern, that object (usually a string) is
+ * returned. If the @a language_pattern does not match any entry, or if the
+ * i18n sub-object does not exist, we simply return @a field of @a object
+ * (also usually a string).
+ *
+ * If @a object does not have a member @a field we return NULL (error).
+ *
+ * @param object the object to extract internationalized
+ * content from
+ * @param language_pattern a language preferences string
+ * like "fr-CH, fr;q=0.9, en;q=0.8, *;q=0.1", following
+ * https://tools.ietf.org/html/rfc7231#section-5.3.1
+ * @param field name of the field to extract
+ * @return NULL on error, otherwise the member from
+ * @a object. Note that the reference counter is
+ * NOT incremented.
*/
-int
-TALER_JSON_exchange_wire_signature_check (
- const json_t *wire_s,
- const struct TALER_MasterPublicKeyP *master_pub);
+const json_t *
+TALER_JSON_extract_i18n (const json_t *object,
+ const char *language_pattern,
+ const char *field);
/**
- * Create a signed wire statement for the given account.
+ * Check whether a given @a i18n object is wellformed.
*
- * @param payto_uri account specification
- * @param master_priv private key to sign with
- * @return NULL if @a payto_uri is malformed
+ * @param i18n object with internationalized content
+ * @return true if @a i18n is well-formed
*/
-json_t *
-TALER_JSON_exchange_wire_signature_make (
- const char *payto_uri,
- const struct TALER_MasterPrivateKeyP *master_priv);
+bool
+TALER_JSON_check_i18n (const json_t *i18n);
/**
@@ -210,6 +794,33 @@ char *
TALER_JSON_wire_to_payto (const json_t *wire_s);
+/**
+ * Hash @a policy extensions in deposits.
+ *
+ * @param policy contract policy extension to hash
+ * @param[out] ech where to write the policy hash
+ */
+void
+TALER_deposit_policy_hash (const json_t *policy,
+ struct TALER_ExtensionPolicyHashP *ech);
+
+/**
+ * Hash the @a manifests of extensions, given as JSON
+ *
+ * @param manifests Manifests of the extensions
+ * @param[out] eh where to write the hash
+ * @return GNUNET_OK on success, GNUNET_SYSERR on failure
+ */
+enum GNUNET_GenericReturnValue
+TALER_JSON_extensions_manifests_hash (const json_t *manifests,
+ struct TALER_ExtensionManifestsHashP *eh);
+
+/**
+ * Canonicalize a JSON input to a string according to RFC 8785.
+ */
+char *
+TALER_JSON_canonicalize (const json_t *input);
+
#endif /* TALER_JSON_LIB_H_ */
/* End of taler_json_lib.h */
diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h
new file mode 100644
index 000000000..4d0c18fa4
--- /dev/null
+++ b/src/include/taler_kyclogic_lib.h
@@ -0,0 +1,374 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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_kyclogic_lib.h
+ * @brief server-side KYC API
+ * @author Christian Grothoff
+ */
+#ifndef TALER_KYCLOGIC_LIB_H
+#define TALER_KYCLOGIC_LIB_H
+
+#include <microhttpd.h>
+#include "taler_exchangedb_plugin.h"
+#include "taler_kyclogic_plugin.h"
+
+
+/**
+ * Enumeration for our KYC user types.
+ */
+enum TALER_KYCLOGIC_KycUserType
+{
+ /**
+ * KYC rule is for an individual.
+ */
+ TALER_KYCLOGIC_KYC_UT_INDIVIDUAL = 0,
+
+ /**
+ * KYC rule is for a business.
+ */
+ TALER_KYCLOGIC_KYC_UT_BUSINESS = 1
+};
+
+
+/**
+ * Enumeration of possible events that may trigger
+ * KYC requirements.
+ */
+enum TALER_KYCLOGIC_KycTriggerEvent
+{
+
+ /**
+ * Customer withdraws coins.
+ */
+ TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW = 0,
+
+ /**
+ * Merchant deposits coins.
+ */
+ TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT = 1,
+
+ /**
+ * Wallet receives P2P payment.
+ */
+ TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE = 2,
+
+ /**
+ * Wallet balance exceeds threshold.
+ */
+ TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE = 3,
+
+ /**
+ * Reserve is being closed by force.
+ */
+ TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE = 4,
+
+ /**
+ * Customer withdraws coins via age-withdraw.
+ */
+ TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW = 5,
+};
+
+
+/**
+ * Parse KYC trigger string value from a string
+ * into enumeration value.
+ *
+ * @param trigger_s string to parse
+ * @param[out] trigger set to the value found
+ * @return #GNUNET_OK on success, #GNUNET_NO if option
+ * does not exist, #GNUNET_SYSERR if option is
+ * malformed
+ */
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_trigger_from_string (
+ const char *trigger_s,
+ enum TALER_KYCLOGIC_KycTriggerEvent *trigger);
+
+
+/**
+ * Convert KYC trigger value to human-readable string.
+ *
+ * @param trigger value to convert
+ * @return human-readable representation of the @a trigger
+ */
+const char *
+TALER_KYCLOGIC_kyc_trigger2s (enum TALER_KYCLOGIC_KycTriggerEvent trigger);
+
+
+/**
+ * Parse user type string into enumeration value.
+ *
+ * @param ut_s string to parse
+ * @param[out] ut set to the value found
+ * @return #GNUNET_OK on success, #GNUNET_NO if option
+ * does not exist, #GNUNET_SYSERR if option is
+ * malformed
+ */
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_user_type_from_string (const char *ut_s,
+ enum TALER_KYCLOGIC_KycUserType *ut);
+
+
+/**
+ * Convert KYC user type to human-readable string.
+ *
+ * @param ut value to convert
+ * @return human-readable representation of the @a ut
+ */
+const char *
+TALER_KYCLOGIC_kyc_user_type2s (enum TALER_KYCLOGIC_KycUserType ut);
+
+
+/**
+ * Initialize KYC subsystem. Loads the KYC configuration.
+ *
+ * @param cfg configuration to parse
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+
+/**
+ * Shut down the KYC subsystem.
+ */
+void
+TALER_KYCLOGIC_kyc_done (void);
+
+
+/**
+ * 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
+ */
+typedef void
+(*TALER_KYCLOGIC_KycAmountIterator)(void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Function called to iterate over KYC-relevant
+ * transaction thresholds amounts.
+ *
+ * @param cls closure, identifies the event type and
+ * account to iterate over events for
+ * @param threshold a relevant threshold amount
+ */
+typedef void
+(*TALER_KYCLOGIC_KycThresholdIterator)(void *cls,
+ const struct TALER_Amount *threshold);
+
+
+/**
+ * Call us on KYC processes satisfied for the given
+ * account. Must match the ``select_satisfied_kyc_processes`` of the exchange database plugin.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param spc function to call for each satisfied KYC process
+ * @param spc_cls closure for @a spc
+ * @return transaction status code
+ */
+typedef enum GNUNET_DB_QueryStatus
+(*TALER_KYCLOGIC_KycSatisfiedIterator)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
+ void *spc_cls);
+
+
+/**
+ * Check if KYC is provided for a particular operation. Returns the set of checks that still need to be satisfied.
+ *
+ * Called within a database transaction, so must
+ * not start a new one.
+ *
+ * @param event what type of operation is triggering the
+ * test if KYC is required
+ * @param h_payto account the event is about
+ * @param ki callback that returns list of already
+ * satisfied KYC checks, implemented by ``select_satisfied_kyc_processes`` of the exchangedb
+ * @param ki_cls closure for @a ki
+ * @param ai callback offered to inquire about historic
+ * amounts involved in this type of operation
+ * at the given account
+ * @param ai_cls closure for @a ai
+ * @param[out] required set to NULL if no check is needed,
+ * otherwise space-separated list of required checks
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_KYCLOGIC_KycSatisfiedIterator ki,
+ void *ki_cls,
+ TALER_KYCLOGIC_KycAmountIterator ai,
+ void *ai_cls,
+ char **required);
+
+
+/**
+ * Check if the @a requirements are now satsified for
+ * @a h_payto account.
+ *
+ * @param[in,out] requirements space-spearated list of requirements,
+ * already satisfied requirements are removed from the list
+ * @param h_payto hash over the account
+ * @param[out] kyc_details if satisfied, set to what kind of
+ * KYC information was collected
+ * @param ki iterator over satisfied providers
+ * @param ki_cls closure for @a ki
+ * @param[out] satisfied set to true if the KYC check was satisfied
+ * @return transaction status (from @a ki)
+ */
+enum GNUNET_DB_QueryStatus
+TALER_KYCLOGIC_check_satisfied (char **requirements,
+ const struct TALER_PaytoHashP *h_payto,
+ json_t **kyc_details,
+ TALER_KYCLOGIC_KycSatisfiedIterator ki,
+ void *ki_cls,
+ bool *satisfied);
+
+
+/**
+ * Iterate over all thresholds that are applicable
+ * to a particular type of @a event
+ *
+ * @param event thresholds to look up
+ * @param it function to call on each
+ * @param it_cls closure for @a it
+ */
+void
+TALER_KYCLOGIC_kyc_iterate_thresholds (
+ enum TALER_KYCLOGIC_KycTriggerEvent event,
+ TALER_KYCLOGIC_KycThresholdIterator it,
+ void *it_cls);
+
+
+/**
+ * Function called with the provider details and
+ * associated plugin closures for matching logics.
+ *
+ * @param cls closure
+ * @param pd provider details of a matching logic
+ * @param plugin_cls closure of the plugin
+ * @return #GNUNET_OK to continue to iterate
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_KYCLOGIC_DetailsCallback)(
+ void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ void *plugin_cls);
+
+
+/**
+ * Call @a cb for all logics with name @a logic_name,
+ * providing the plugin closure and the @a pd configurations.
+ *
+ * @param logic_name name of the logic to match
+ * @param cb function to call on matching results
+ * @param cb_cls closure for @a cb
+ */
+void
+TALER_KYCLOGIC_kyc_get_details (
+ const char *logic_name,
+ TALER_KYCLOGIC_DetailsCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Check if a given @a check_name is a legal name (properly
+ * configured) and can be satisfied in principle.
+ *
+ * @param check_name name of the check to see if it is configured
+ * @return #GNUNET_OK if the check can be satisfied,
+ * #GNUNET_NO if the check can never be satisfied,
+ * #GNUNET_SYSERR if the type of the check is unknown
+ */
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_check_satisfiable (
+ const char *check_name);
+
+
+/**
+ * Return list of all KYC checks that are possible.
+ *
+ * @return JSON array of strings with the allowed KYC checks
+ */
+json_t *
+TALER_KYCLOGIC_get_satisfiable (void);
+
+
+/**
+ * Obtain the provider logic for a given set of @a requirements.
+ *
+ * @param requirements space-separated list of required checks
+ * @param ut type of the entity performing the check
+ * @param[out] plugin set to the KYC logic API
+ * @param[out] pd set to the specific operation context
+ * @param[out] configuration_section set to the name of the KYC logic configuration section * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_requirements_to_logic (const char *requirements,
+ enum TALER_KYCLOGIC_KycUserType ut,
+ struct TALER_KYCLOGIC_Plugin **plugin,
+ struct TALER_KYCLOGIC_ProviderDetails **pd,
+ const char **configuration_section);
+
+
+/**
+ * Obtain the provider logic for a given @a name.
+ *
+ * @param name name of the logic or provider section
+ * @param[out] plugin set to the KYC logic API
+ * @param[out] pd set to the specific operation context
+ * @param[out] configuration_section set to the name of the KYC logic configuration section
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_lookup_logic (const char *name,
+ struct TALER_KYCLOGIC_Plugin **plugin,
+ struct TALER_KYCLOGIC_ProviderDetails **pd,
+ const char **configuration_section);
+
+
+/**
+ * Obtain array of KYC checks provided by the provider
+ * configured in @a section_name.
+ *
+ * @param section_name configuration section name
+ * @param[out] num_checks set to the length of the array
+ * @param[out] provided_checks set to an array with the
+ * names of the checks provided by this KYC provider
+ */
+void
+TALER_KYCLOGIC_lookup_checks (const char *section_name,
+ unsigned int *num_checks,
+ char ***provided_checks);
+
+#endif
diff --git a/src/include/taler_kyclogic_plugin.h b/src/include/taler_kyclogic_plugin.h
new file mode 100644
index 000000000..a9a4dd97a
--- /dev/null
+++ b/src/include/taler_kyclogic_plugin.h
@@ -0,0 +1,384 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+*/
+/**
+ * @file include/taler_kyclogic_plugin.h
+ * @brief KYC API specific logic C interface
+ * @author Christian Grothoff
+ */
+#ifndef TALER_KYCLOGIC_PLUGIN_H
+#define TALER_KYCLOGIC_PLUGIN_H
+
+#include <jansson.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_db_lib.h>
+#include "taler_util.h"
+
+
+/**
+ * Possible states of a KYC check.
+ */
+enum TALER_KYCLOGIC_KycStatus
+{
+
+ /**
+ * The provider has passed the customer.
+ */
+ TALER_KYCLOGIC_STATUS_SUCCESS = 0,
+
+ /**
+ * Something to do with the user (bit!).
+ */
+ TALER_KYCLOGIC_STATUS_USER = 1,
+
+ /**
+ * Something to do with the provider (bit!).
+ */
+ TALER_KYCLOGIC_STATUS_PROVIDER = 2,
+
+ /**
+ * The interaction ended in definitive failure.
+ * (kind of with both parties).
+ */
+ TALER_KYCLOGIC_STATUS_FAILED
+ = TALER_KYCLOGIC_STATUS_USER
+ | TALER_KYCLOGIC_STATUS_PROVIDER,
+
+ /**
+ * The interaction is still ongoing.
+ */
+ TALER_KYCLOGIC_STATUS_PENDING = 4,
+
+ /**
+ * One of the parties hat a temporary failure.
+ */
+ TALER_KYCLOGIC_STATUS_ABORTED = 8,
+
+ /**
+ * The interaction with the user is ongoing.
+ */
+ TALER_KYCLOGIC_STATUS_USER_PENDING
+ = TALER_KYCLOGIC_STATUS_USER
+ | TALER_KYCLOGIC_STATUS_PENDING,
+
+ /**
+ * The provider is still checking.
+ */
+ TALER_KYCLOGIC_STATUS_PROVIDER_PENDING
+ = TALER_KYCLOGIC_STATUS_PROVIDER
+ | TALER_KYCLOGIC_STATUS_PENDING,
+
+ /**
+ * The user aborted the check (possibly recoverable)
+ * or made some other type of (recoverable) mistake.
+ */
+ TALER_KYCLOGIC_STATUS_USER_ABORTED
+ = TALER_KYCLOGIC_STATUS_USER
+ | TALER_KYCLOGIC_STATUS_ABORTED,
+
+ /**
+ * The provider had an (internal) failure.
+ */
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED
+ = TALER_KYCLOGIC_STATUS_PROVIDER
+ | TALER_KYCLOGIC_STATUS_ABORTED,
+
+ /**
+ * Return code set to not update the KYC status
+ * at all.
+ */
+ TALER_KYCLOGIC_STATUS_KEEP = 16,
+
+ /**
+ * We had an internal logic failure.
+ */
+ TALER_KYCLOGIC_STATUS_INTERNAL_ERROR = 32
+
+};
+
+
+/**
+ * Plugin-internal specification of the configuration
+ * of the plugin for a given KYC provider.
+ */
+struct TALER_KYCLOGIC_ProviderDetails;
+
+/**
+ * Handle for an initiation operation.
+ */
+struct TALER_KYCLOGIC_InitiateHandle;
+
+/**
+ * Handle for an KYC proof operation.
+ */
+struct TALER_KYCLOGIC_ProofHandle;
+
+/**
+ * Handle for an KYC Web hook operation.
+ */
+struct TALER_KYCLOGIC_WebhookHandle;
+
+
+/**
+ * Function called with the result of a KYC initiation
+ * operation.
+ *
+ * @param cls closure
+ * @param ec #TALER_EC_NONE on success
+ * @param redirect_url set to where to redirect the user on success, NULL on failure
+ * @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 error_msg_hint set to additional details to return to user, NULL on success
+ */
+typedef void
+(*TALER_KYCLOGIC_InitiateCallback)(
+ void *cls,
+ enum TALER_ErrorCode ec,
+ const char *redirect_url,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ const char *error_msg_hint);
+
+
+/**
+ * Function called with the result of a proof check operation.
+ *
+ * Note that the "decref" for the @a response
+ * will be done by the callee and MUST NOT be done by the plugin.
+ *
+ * @param cls closure
+ * @param status KYC status
+ * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
+ * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
+ * @param attributes user attributes returned by the provider
+ * @param expiration until when is the KYC check valid
+ * @param http_status HTTP status code of @a response
+ * @param[in] response to return to the HTTP client
+ */
+typedef void
+(*TALER_KYCLOGIC_ProofCallback)(
+ void *cls,
+ enum TALER_KYCLOGIC_KycStatus status,
+ 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);
+
+
+/**
+ * Function called with the result of a webhook operation.
+ *
+ * Note that the "decref" for the @a response will be done by the callee and
+ * MUST NOT be done by the plugin!
+ *
+ * @param cls closure
+ * @param process_row legitimization process the webhook was about
+ * @param account_id account the webhook was about
+ * @param provider_section name of the configuration section of the logic that was run
+ * @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 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
+ */
+typedef void
+(*TALER_KYCLOGIC_WebhookCallback)(
+ void *cls,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *account_id,
+ const char *provider_section,
+ const char *provider_user_id,
+ 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);
+
+
+/**
+ * Function the plugin can use to lookup an @a h_payto by @a
+ * provider_legitimization_id. Must match the `kyc_provider_account_lookup`
+ * of the exchange's database plugin.
+ *
+ * @param cls closure
+ * @param provider_section
+ * @param provider_legitimization_id legi to look up
+ * @param[out] h_payto where to write the result
+ * @param[out] process_row where to write the row of the entry
+ * @return database transaction status
+ */
+typedef enum GNUNET_DB_QueryStatus
+(*TALER_KYCLOGIC_ProviderLookupCallback)(
+ void *cls,
+ const char *provider_section,
+ const char *provider_legitimization_id,
+ struct TALER_PaytoHashP *h_payto,
+ uint64_t *process_row);
+
+
+/**
+ * @brief The plugin API, returned from the plugin's "init" function.
+ * The argument given to "init" is simply a configuration handle.
+ */
+struct TALER_KYCLOGIC_Plugin
+{
+
+ /**
+ * Closure for all callbacks.
+ */
+ void *cls;
+
+ /**
+ * Name of the library which generated this plugin. Set by the
+ * plugin loader.
+ */
+ char *library_name;
+
+ /**
+ * Name of the logic, for webhook matching. Set by the
+ * plugin loader.
+ */
+ char *name;
+
+ /**
+ * Load the configuration of the KYC provider.
+ *
+ * @param cls closure
+ * @param provider_section_name configuration section to parse
+ * @return NULL if configuration is invalid
+ */
+ struct TALER_KYCLOGIC_ProviderDetails *
+ (*load_configuration)(void *cls,
+ const char *provider_section_name);
+
+ /**
+ * Release configuration resources previously loaded
+ *
+ * @param[in] pd configuration to release
+ */
+ void
+ (*unload_configuration)(struct TALER_KYCLOGIC_ProviderDetails *pd);
+
+
+ /**
+ * Initiate KYC check.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param account_id which account to trigger process for
+ * @param process_row unique ID for the legitimization process
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+ struct TALER_KYCLOGIC_InitiateHandle *
+ (*initiate)(void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ const struct TALER_PaytoHashP *account_id,
+ uint64_t process_row,
+ TALER_KYCLOGIC_InitiateCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Cancel KYC check initiation.
+ *
+ * @param[in] ih handle of operation to cancel
+ */
+ void
+ (*initiate_cancel) (struct TALER_KYCLOGIC_InitiateHandle *ih);
+
+
+ /**
+ * Check KYC status and return status to human.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param connection MHD connection object (for HTTP headers)
+ * @param account_id which account to trigger process for
+ * @param process_row row in the legitimization processes table the legitimization is for
+ * @param provider_user_id user ID (or NULL) the proof is for
+ * @param provider_legitimization_id legitimization ID the proof is for
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+ struct TALER_KYCLOGIC_ProofHandle *
+ (*proof)(void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ struct MHD_Connection *connection,
+ const struct TALER_PaytoHashP *account_id,
+ uint64_t process_row,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ TALER_KYCLOGIC_ProofCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Cancel KYC proof.
+ *
+ * @param[in] ph handle of operation to cancel
+ */
+ void
+ (*proof_cancel) (struct TALER_KYCLOGIC_ProofHandle *ph);
+
+
+ /**
+ * Check KYC status and return result for Webhook.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param plc callback to lookup accounts with
+ * @param plc_cls closure for @a plc
+ * @param http_method HTTP method used for the webhook
+ * @param url_path rest of the URL after `/kyc-webhook/$LOGIC/`
+ * @param connection MHD connection object (for HTTP headers)
+ * @param body_size number of bytes in @a body
+ * @param body HTTP request body
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+ struct TALER_KYCLOGIC_WebhookHandle *
+ (*webhook)(void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ TALER_KYCLOGIC_ProviderLookupCallback plc,
+ void *plc_cls,
+ const char *http_method,
+ const char *const url_path[],
+ struct MHD_Connection *connection,
+ const json_t *upload,
+ TALER_KYCLOGIC_WebhookCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Cancel KYC webhook execution.
+ *
+ * @param[in] wh handle of operation to cancel
+ */
+ void
+ (*webhook_cancel) (struct TALER_KYCLOGIC_WebhookHandle *wh);
+
+};
+
+
+#endif /* _TALER_KYCLOGIC_PLUGIN_H */
diff --git a/src/include/taler_mhd_lib.h b/src/include/taler_mhd_lib.h
index 7100ad95c..57d041758 100644
--- a/src/include/taler_mhd_lib.h
+++ b/src/include/taler_mhd_lib.h
@@ -27,6 +27,15 @@
#include <jansson.h>
#include <microhttpd.h>
#include "taler_error_codes.h"
+#include "taler_util.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * Maximum POST request size.
+ */
+#define TALER_MHD_REQUEST_BUFFER_MAX (1024 * 1024 * 16)
+
/**
* Global options for response generation.
@@ -79,7 +88,7 @@ TALER_MHD_add_global_headers (struct MHD_Response *response);
* @param[in,out] buf_size pointer to initial size of @a buf
* @return #MHD_YES if @a buf was compressed
*/
-int
+MHD_RESULT
TALER_MHD_body_compress (void **buf,
size_t *buf_size);
@@ -90,11 +99,25 @@ TALER_MHD_body_compress (void **buf,
* @param connection connection to check
* @return #MHD_YES if 'deflate' compression is allowed
*/
-int
+MHD_RESULT
TALER_MHD_can_compress (struct MHD_Connection *connection);
/**
+ * Check if @a mime matches the @a accept_pattern. For this function, the @a
+ * accept_pattern may include multiple values separated by ";".
+ *
+ * @param accept_pattern a mime pattern like "text/plain"
+ * or "image/STAR" or "text/plain; text/xml"
+ * @param mime the mime type to match
+ * @return true if @a mime matches the @a accept_pattern
+ */
+bool
+TALER_MHD_xmime_matches (const char *accept_pattern,
+ const char *mime);
+
+
+/**
* Send JSON object as response.
*
* @param connection the MHD connection
@@ -102,13 +125,28 @@ TALER_MHD_can_compress (struct MHD_Connection *connection);
* @param response_code the http response code
* @return MHD result code
*/
-int
+MHD_RESULT
TALER_MHD_reply_json (struct MHD_Connection *connection,
const json_t *json,
unsigned int response_code);
/**
+ * Send JSON object as response, and free the @a json
+ * object.
+ *
+ * @param connection the MHD connection
+ * @param json the json object (freed!)
+ * @param response_code the http response code
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_MHD_reply_json_steal (struct MHD_Connection *connection,
+ json_t *json,
+ unsigned int response_code);
+
+
+/**
* Function to call to handle the request by building a JSON
* reply from a format string and varargs.
*
@@ -118,7 +156,7 @@ TALER_MHD_reply_json (struct MHD_Connection *connection,
* @param ... varargs
* @return MHD result code
*/
-int
+MHD_RESULT
TALER_MHD_reply_json_pack (struct MHD_Connection *connection,
unsigned int response_code,
const char *fmt,
@@ -126,19 +164,60 @@ TALER_MHD_reply_json_pack (struct MHD_Connection *connection,
/**
+ * Function to call to handle the request by building a JSON
+ * reply from varargs.
+ *
+ * @param connection the MHD connection to handle
+ * @param response_code HTTP response code to use
+ * @param ... varargs of JSON pack specification
+ * @return MHD result code
+ */
+#define TALER_MHD_REPLY_JSON_PACK(connection,response_code,...) \
+ TALER_MHD_reply_json_steal (connection, GNUNET_JSON_PACK (__VA_ARGS__), \
+ response_code)
+
+
+/**
* Send a response indicating an error.
*
* @param connection the MHD connection to use
* @param ec error code uniquely identifying the error
* @param http_status HTTP status code to use
- * @param hint human readable hint about the error
+ * @param detail additional optional detail about the error
* @return a MHD result code
*/
-int
+MHD_RESULT
TALER_MHD_reply_with_error (struct MHD_Connection *connection,
unsigned int http_status,
enum TALER_ErrorCode ec,
- const char *hint);
+ const char *detail);
+
+
+/**
+ * Send a response indicating an error. The HTTP status code is
+ * to be derived from the @a ec.
+ *
+ * @param connection the MHD connection to use
+ * @param ec error code uniquely identifying the error
+ * @param detail additional optional detail about the error
+ * @return a MHD result code
+ */
+MHD_RESULT
+TALER_MHD_reply_with_ec (struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const char *detail);
+
+
+/**
+ * Produce HTTP "Date:" header.
+ *
+ * @param at time to write to @a date
+ * @param[out] date where to write the header, with
+ * at least 128 bytes available space.
+ */
+void
+TALER_MHD_get_date_string (struct GNUNET_TIME_Absolute at,
+ char date[128]);
/**
@@ -152,6 +231,16 @@ TALER_MHD_make_json (const json_t *json);
/**
+ * Make JSON response object and free @a json.
+ *
+ * @param json the json object, freed.
+ * @return MHD response object
+ */
+struct MHD_Response *
+TALER_MHD_make_json_steal (json_t *json);
+
+
+/**
* Make JSON response object.
*
* @param fmt format string for pack
@@ -164,15 +253,36 @@ TALER_MHD_make_json_pack (const char *fmt,
/**
+ * Make JSON response object.
+ *
+ * @param ... varargs
+ * @return MHD response object
+ */
+#define TALER_MHD_MAKE_JSON_PACK(...) \
+ TALER_MHD_make_json_steal (GNUNET_JSON_PACK (__VA_ARGS__))
+
+
+/**
+ * Pack Taler error code @a ec and associated hint into a
+ * JSON object.
+ *
+ * @param ec error code to pack
+ * @return packer array entries (two!)
+ */
+#define TALER_MHD_PACK_EC(ec) \
+ GNUNET_JSON_pack_uint64 ("code", ec), \
+ GNUNET_JSON_pack_string ("hint", TALER_ErrorCode_get_hint (ec))
+
+/**
* Create a response indicating an internal error.
*
* @param ec error code to return
- * @param hint hint about the internal error's nature
+ * @param detail additional optional detail about the error, can be NULL
* @return a MHD response object
*/
struct MHD_Response *
TALER_MHD_make_error (enum TALER_ErrorCode ec,
- const char *hint);
+ const char *detail);
/**
@@ -181,7 +291,7 @@ TALER_MHD_make_error (enum TALER_ErrorCode ec,
* @param connection the MHD connection to use
* @return a MHD result code
*/
-int
+MHD_RESULT
TALER_MHD_reply_request_too_large (struct MHD_Connection *connection);
@@ -193,7 +303,7 @@ TALER_MHD_reply_request_too_large (struct MHD_Connection *connection);
* @param url where to redirect for the sources
* @return MHD result code
*/
-int
+MHD_RESULT
TALER_MHD_reply_agpl (struct MHD_Connection *connection,
const char *url);
@@ -209,7 +319,7 @@ TALER_MHD_reply_agpl (struct MHD_Connection *connection,
* @param body_size number of bytes in @a body
* @return MHD result code
*/
-int
+MHD_RESULT
TALER_MHD_reply_static (struct MHD_Connection *connection,
unsigned int http_status,
const char *mime_type,
@@ -241,7 +351,7 @@ TALER_MHD_reply_static (struct MHD_Connection *connection,
* (we could not even queue an error message,
* close HTTP session with MHD_NO)
*/
-int
+enum GNUNET_GenericReturnValue
TALER_MHD_parse_post_json (struct MHD_Connection *connection,
void **con_cls,
const char *upload_data,
@@ -262,7 +372,8 @@ TALER_MHD_parse_post_cleanup_callback (void *con_cls);
/**
* Parse JSON object into components based on the given field
- * specification.
+ * specification. If parsing fails, we return an HTTP
+ * status code of 400 (#MHD_HTTP_BAD_REQUEST).
*
* @param connection the connection to send an error response to
* @param root the JSON node to start the navigation at.
@@ -274,13 +385,37 @@ TALER_MHD_parse_post_cleanup_callback (void *con_cls);
* #GNUNET_NO if json is malformed, error response was generated
* #GNUNET_SYSERR on internal error
*/
-int
+enum GNUNET_GenericReturnValue
TALER_MHD_parse_json_data (struct MHD_Connection *connection,
const json_t *root,
struct GNUNET_JSON_Specification *spec);
/**
+ * Parse JSON object that we (the server!) generated into components based on
+ * the given field specification. The difference to
+ * #TALER_MHD_parse_json_data() is that this function will fail
+ * with an HTTP failure of 500 (internal server error) in case
+ * parsing fails, instead of blaming it on the client with a
+ * 400 (#MHD_HTTP_BAD_REQUEST).
+ *
+ * @param connection the connection to send an error response to
+ * @param root the JSON node to start the navigation at.
+ * @param spec field specification for the parser
+ * @return
+ * #GNUNET_YES if navigation was successful (caller is responsible
+ * for freeing allocated variable-size data using
+ * GNUNET_JSON_parse_free() when done)
+ * #GNUNET_NO if json is malformed, error response was generated
+ * #GNUNET_SYSERR on internal error
+ */
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_internal_json_data (struct MHD_Connection *connection,
+ const json_t *root,
+ struct GNUNET_JSON_Specification *spec);
+
+
+/**
* Parse JSON array into components based on the given field
* specification. Generates error response on parse errors.
*
@@ -295,7 +430,7 @@ TALER_MHD_parse_json_data (struct MHD_Connection *connection,
* #GNUNET_NO if json is malformed, error response was generated
* #GNUNET_SYSERR on internal error
*/
-int
+enum GNUNET_GenericReturnValue
TALER_MHD_parse_json_array (struct MHD_Connection *connection,
const json_t *root,
struct GNUNET_JSON_Specification *spec,
@@ -303,7 +438,201 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection,
/**
- * Extraxt fixed-size base32crockford encoded data from request.
+ * Extract optional "timeout_ms" argument from request.
+ *
+ * @param connection the MHD connection
+ * @param[out] expiration set to #GNUNET_TIME_UNIT_ZERO_ABS if there was no timeout,
+ * the current time plus the value given under "timeout_ms" otherwise
+ * @return #GNUNET_OK on success, #GNUNET_NO if an
+ * error was returned on @a connection (caller should return #MHD_YES) and
+ * #GNUNET_SYSERR if we failed to return an error (caller should return #MHD_NO)
+ */
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_timeout (struct MHD_Connection *connection,
+ struct GNUNET_TIME_Absolute *expiration);
+
+
+/**
+ * Extract optional "timeout_ms" argument from request.
+ * Macro that *returns* #MHD_YES/#MHD_NO if the "timeout_ms"
+ * argument existed but failed to parse.
+ *
+ * @param connection the MHD connection
+ * @param[out] expiration set to #GNUNET_TIME_UNIT_ZERO_ABS if there was no timeout,
+ * the current time plus the value given under "timeout_ms" otherwise
+ */
+#define TALER_MHD_parse_request_timeout(connection,expiration) \
+ do { \
+ switch (TALER_MHD_parse_request_arg_timeout (connection, \
+ expiration)) \
+ { \
+ case GNUNET_SYSERR: \
+ GNUNET_break (0); \
+ return MHD_NO; \
+ case GNUNET_NO: \
+ GNUNET_break_op (0); \
+ case GNUNET_OK: \
+ break; \
+ } \
+ } while (0)
+
+
+/**
+ * Extract optional numeric limit argument from request.
+ *
+ * @param connection the MHD connection
+ * @param name name of the query parameter
+ * @param[out] off set to the offset, unchanged if the
+ * option was not given
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if an error was returned on @a connection (caller should return #MHD_YES) and
+ * #GNUNET_SYSERR if we failed to return an error (caller should return #MHD_NO)
+ */
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_number (struct MHD_Connection *connection,
+ const char *name,
+ uint64_t *off);
+
+
+/**
+ * Extract optional numeric argument from request.
+ * Macro that *returns* #MHD_YES/#MHD_NO if the
+ * requested argument existed but failed to parse.
+ *
+ * @param connection the MHD connection
+ * @param name name of the argument to parse
+ * @param[out] off set to the given numeric value,
+ * unchanged if value was not specified
+ */
+#define TALER_MHD_parse_request_number(connection,name,off) \
+ do { \
+ switch (TALER_MHD_parse_request_arg_number (connection, \
+ name, \
+ off)) \
+ { \
+ case GNUNET_SYSERR: \
+ GNUNET_break (0); \
+ return MHD_NO; \
+ case GNUNET_NO: \
+ GNUNET_break_op (0); \
+ case GNUNET_OK: \
+ break; \
+ } \
+ } while (0)
+
+
+/**
+ * Extract optional signed numeric limit argument from request.
+ *
+ * @param connection the MHD connection
+ * @param name name of the query parameter
+ * @param[out] val set to the signed value, unchanged if the
+ * option was not given
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if an error was returned on @a connection (caller should return #MHD_YES) and
+ * #GNUNET_SYSERR if we failed to return an error (caller should return #MHD_NO)
+ */
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_snumber (struct MHD_Connection *connection,
+ const char *name,
+ int64_t *val);
+
+
+/**
+ * Extract optional numeric argument from request.
+ * Macro that *returns* #MHD_YES/#MHD_NO if the
+ * requested argument existed but failed to parse.
+ *
+ * @param connection the MHD connection
+ * @param name name of the argument to parse
+ * @param[out] val set to the given numeric value,
+ * unchanged if value was not specified
+ */
+#define TALER_MHD_parse_request_snumber(connection,name,val) \
+ do { \
+ switch (TALER_MHD_parse_request_arg_snumber (connection, \
+ name, \
+ val)) \
+ { \
+ case GNUNET_SYSERR: \
+ GNUNET_break (0); \
+ return MHD_NO; \
+ case GNUNET_NO: \
+ GNUNET_break_op (0); \
+ case GNUNET_OK: \
+ break; \
+ } \
+ } while (0)
+
+
+/**
+ * Extract optional amount argument from request.
+ *
+ * @param connection the MHD connection
+ * @param name name of the query parameter
+ * @param[out] val set to the amount, unchanged if the
+ * option was not given
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if an error was returned on @a connection (caller should return #MHD_YES) and
+ * #GNUNET_SYSERR if we failed to return an error (caller should return #MHD_NO)
+ */
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_amount (struct MHD_Connection *connection,
+ const char *name,
+ struct TALER_Amount *val);
+
+
+/**
+ * Extract optional amount argument from request.
+ * Macro that *returns* #MHD_YES/#MHD_NO if the
+ * requested argument existed but failed to parse.
+ *
+ * @param connection the MHD connection
+ * @param name name of the argument to parse
+ * @param[out] val set to the given amount,
+ * unchanged if value was not specified
+ */
+#define TALER_MHD_parse_request_amount(connection,name,val) \
+ do { \
+ switch (TALER_MHD_parse_request_arg_amount (connection, \
+ name, \
+ val)) \
+ { \
+ case GNUNET_SYSERR: \
+ GNUNET_break (0); \
+ return MHD_NO; \
+ case GNUNET_NO: \
+ GNUNET_break_op (0); \
+ case GNUNET_OK: \
+ break; \
+ } \
+ } while (0)
+
+
+/**
+ * Determines which of the given choices is preferred
+ * by the client. Parses an HTTP header such as
+ * "Accept:" or "Language:" to find entries matching
+ * the list of strings given in the variadic argument
+ * list. Returns the index of the preferred choice.
+ *
+ * @param connection HTTP request handle
+ * @param header type of HTTP header to evaluate
+ * @param ... NULL-terminated list of choices to
+ * check for in the header
+ * @return -1 if none of the given choices is in
+ * the header, -2 if the header is missing,
+ * otherwise index of preferred choice in
+ * the varargs list
+ */
+int
+TALER_MHD_check_accept (struct MHD_Connection *connection,
+ const char *header,
+ ...);
+
+
+/**
+ * Extract fixed-size base32crockford encoded data from request argument.
*
* Queues an error response to the connection if the parameter is missing or
* invalid.
@@ -312,16 +641,195 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection,
* @param param_name the name of the parameter with the key
* @param[out] out_data pointer to store the result
* @param out_size expected size of @a out_data
+ * @param[out] present set to true if argument was found
* @return
* #GNUNET_YES if the the argument is present
- * #GNUNET_NO if the argument is absent or malformed
+ * #GNUNET_NO if the argument is malformed
* #GNUNET_SYSERR on internal error (error response could not be sent)
*/
-int
+enum GNUNET_GenericReturnValue
TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection,
const char *param_name,
void *out_data,
- size_t out_size);
+ size_t out_size,
+ bool *present);
+
+
+/**
+ * Extract fixed-size base32crockford encoded data from request header.
+ *
+ * Queues an error response to the connection if the parameter is missing or
+ * invalid.
+ *
+ * @param connection the MHD connection
+ * @param header_name the name of the HTTP header with the value
+ * @param[out] out_data pointer to store the result
+ * @param out_size expected size of @a out_data
+ * @param[out] present set to true if argument was found
+ * @return
+ * #GNUNET_YES if the the argument is present
+ * #GNUNET_NO if the argument is malformed
+ * #GNUNET_SYSERR on internal error (error response could not be sent)
+ */
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_header_data (struct MHD_Connection *connection,
+ const char *header_name,
+ void *out_data,
+ size_t out_size,
+ bool *present);
+
+/**
+ * Extract fixed-size base32crockford encoded data from request.
+ *
+ * @param connection the MHD connection
+ * @param name the name of the parameter with the key
+ * @param[out] val pointer to store the result, type must determine size
+ * @param[in,out] required pass true to require presence of this argument; if 'false'
+ * set to true if the argument was found
+ * @return
+ * #GNUNET_YES if the the argument is present
+ * #GNUNET_NO if the argument is absent or malformed
+ * #GNUNET_SYSERR on internal error (error response could not be sent)
+ */
+#define TALER_MHD_parse_request_arg_auto(connection,name,val,required) \
+ do { \
+ bool p; \
+ switch (TALER_MHD_parse_request_arg_data (connection, name, \
+ val, sizeof (*val), &p)) \
+ { \
+ case GNUNET_SYSERR: \
+ GNUNET_break (0); \
+ return MHD_NO; \
+ case GNUNET_NO: \
+ GNUNET_break_op (0); \
+ return MHD_YES; \
+ case GNUNET_OK: \
+ if (required & (! p)) \
+ return TALER_MHD_reply_with_error ( \
+ connection, \
+ MHD_HTTP_BAD_REQUEST, \
+ TALER_EC_GENERIC_PARAMETER_MISSING, \
+ name); \
+ required = p; \
+ break; \
+ } \
+ } while (0)
+
+
+/**
+ * Extract required fixed-size base32crockford encoded data from request.
+ *
+ * @param connection the MHD connection
+ * @param name the name of the parameter with the key
+ * @param[out] val pointer to store the result, type must determine size
+ * @return
+ * #GNUNET_YES if the the argument is present
+ * #GNUNET_NO if the argument is absent or malformed
+ * #GNUNET_SYSERR on internal error (error response could not be sent)
+ */
+#define TALER_MHD_parse_request_arg_auto_t(connection,name,val) \
+ do { \
+ bool b = true; \
+ TALER_MHD_parse_request_arg_auto (connection,name,val,b); \
+ } while (0)
+
+/**
+ * Extract fixed-size base32crockford encoded data from request.
+ *
+ * @param connection the MHD connection
+ * @param name the name of the header with the key
+ * @param[out] val pointer to store the result, type must determine size
+ * @param[in,out] required pass true to require presence of this argument; if 'false'
+ * set to true if the argument was found
+ * @return
+ * #GNUNET_YES if the the argument is present
+ * #GNUNET_NO if the argument is absent or malformed
+ * #GNUNET_SYSERR on internal error (error response could not be sent)
+ */
+#define TALER_MHD_parse_request_header_auto(connection,name,val,required) \
+ do { \
+ bool p; \
+ switch (TALER_MHD_parse_request_header_data (connection, name, \
+ val, sizeof (*val), &p)) \
+ { \
+ case GNUNET_SYSERR: \
+ GNUNET_break (0); \
+ return MHD_NO; \
+ case GNUNET_NO: \
+ GNUNET_break_op (0); \
+ return MHD_YES; \
+ case GNUNET_OK: \
+ if (required & (! p)) \
+ return TALER_MHD_reply_with_error ( \
+ connection, \
+ MHD_HTTP_BAD_REQUEST, \
+ TALER_EC_GENERIC_PARAMETER_MISSING, \
+ name); \
+ required = p; \
+ break; \
+ } \
+ } while (0)
+
+
+/**
+ * Extract required fixed-size base32crockford encoded data from request.
+ *
+ * @param connection the MHD connection
+ * @param name the name of the header with the key
+ * @param[out] val pointer to store the result, type must determine size
+ * @return
+ * #GNUNET_YES if the the argument is present
+ * #GNUNET_NO if the argument is absent or malformed
+ * #GNUNET_SYSERR on internal error (error response could not be sent)
+ */
+#define TALER_MHD_parse_request_header_auto_t(connection,name,val) \
+ do { \
+ bool b = true; \
+ TALER_MHD_parse_request_header_auto (connection,name,val,b); \
+ } while (0)
+
+
+/**
+ * Check that the 'Content-Length' header is giving
+ * a length below @a max_len. If not, return an
+ * appropriate error response and return the
+ * correct #MHD_YES/#MHD_NO value from this function.
+ *
+ * @param connection the MHD connection
+ * @param max_len maximum allowed content length
+ * @return
+ * #GNUNET_YES if the the argument is present
+ * #GNUNET_NO if the argument is absent or malformed
+ * #GNUNET_SYSERR on internal error (error response could not be sent)
+ */
+enum GNUNET_GenericReturnValue
+TALER_MHD_check_content_length_ (struct MHD_Connection *connection,
+ unsigned long long max_len);
+
+
+/**
+ * Check that the 'Content-Length' header is giving
+ * a length below @a max_len. If not, return an
+ * appropriate error response and return the
+ * correct #MHD_YES/#MHD_NO value from this function.
+ *
+ * @param connection the MHD connection
+ * @param max_len maximum allowed content length
+ */
+#define TALER_MHD_check_content_length(connection,max_len) \
+ do { \
+ switch (TALER_MHD_check_content_length_ (connection, max_len)) \
+ { \
+ case GNUNET_SYSERR: \
+ GNUNET_break (0); \
+ return MHD_NO; \
+ case GNUNET_NO: \
+ GNUNET_break_op (0); \
+ return MHD_YES; \
+ case GNUNET_OK: \
+ break; \
+ } \
+ } while (0)
/**
@@ -335,7 +843,7 @@ TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection,
* @param[out] unix_mode set to the mode to be used for @a unix_path
* @return #GNUNET_OK on success
*/
-int
+enum GNUNET_GenericReturnValue
TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
uint16_t *rport,
@@ -388,6 +896,35 @@ TALER_MHD_bind (const struct GNUNET_CONFIGURATION_Handle *cfg,
/**
+ * Start to run an event loop for @a daemon.
+ * Only one daemon can be running per process
+ * using this API.
+ *
+ * @param daemon the MHD service to run
+ */
+void
+TALER_MHD_daemon_start (struct MHD_Daemon *daemon);
+
+
+/**
+ * Stop running the event loop for MHD.
+ *
+ * @return the daemon that we were previously running,
+ * or NULL if none was active
+ */
+struct MHD_Daemon *
+TALER_MHD_daemon_stop (void);
+
+
+/**
+ * Trigger MHD daemon that is running. Needed when
+ * a connection was resumed.
+ */
+void
+TALER_MHD_daemon_trigger (void);
+
+
+/**
* Prepared responses for legal documents
* (terms of service, privacy policy).
*/
@@ -396,7 +933,7 @@ struct TALER_MHD_Legal;
/**
* Load set of legal documents as specified in @a cfg in section @a section
- * where the Etag is given under the @param tagoption and the directory under
+ * where the Etag is given under the @a tagoption and the directory under
* the @a diroption.
*
* @param cfg configuration to use
@@ -431,7 +968,7 @@ TALER_MHD_legal_free (struct TALER_MHD_Legal *legal);
* @param legal legal document to serve
* @return MHD result code
*/
-int
+MHD_RESULT
TALER_MHD_reply_legal (struct MHD_Connection *conn,
struct TALER_MHD_Legal *legal);
@@ -443,7 +980,7 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn,
* @param connection the MHD connection
* @return MHD result code
*/
-int
+MHD_RESULT
TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection);
#endif
diff --git a/src/include/taler_pq_lib.h b/src/include/taler_pq_lib.h
index 4545b6d5d..f45de61d9 100644
--- a/src/include/taler_pq_lib.h
+++ b/src/include/taler_pq_lib.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014, 2015, 2016 Taler Systems SA
+ Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -19,37 +19,112 @@
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
* @author Florian Dold
* @author Christian Grothoff
+ * @author Özgür Kesim
*/
#ifndef TALER_PQ_LIB_H_
#define TALER_PQ_LIB_H_
#include <libpq-fe.h>
#include <jansson.h>
+#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_pq_lib.h>
#include "taler_util.h"
/**
- * Generate query parameter for a currency, consisting of the three
- * components "value", "fraction" and "currency" in this order. The
- * types must be a 64-bit integer, 32-bit integer and a
- * #TALER_CURRENCY_LEN-sized BLOB/VARCHAR respectively.
+ * API version. Bump on every change.
+ */
+#define TALER_PQ_VERSION 0x09040000
+
+/**
+ * Generate query parameter (as record tuple) for an amount, consisting
+ * of the two components "value" and "fraction" in this order. The
+ * types must be a 64-bit integer and a 32-bit integer
+ * respectively. The currency is dropped.
+ *
+ * @param db The database context for OID lookup
+ * @param amount pointer to the query parameter to pass
+ */
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_amount (
+ const struct GNUNET_PQ_Context *db,
+ const struct TALER_Amount *amount);
+
+
+/**
+ * Generate query parameter (as record tuple) for an amount, consisting of the
+ * three components "value", "fraction" and "currency" in this order. The
+ * types must be a 64-bit integer, a 32-bit integer and a TEXT field of 12
+ * characters respectively.
+ *
+ * @param db The database context for OID lookup
+ * @param amount pointer to the query parameter to pass
+ */
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_amount_with_currency (
+ const struct GNUNET_PQ_Context *db,
+ const struct TALER_Amount *amount);
+
+
+/**
+ * Generate query parameter for a denomination public
+ * key. Internally, the various attributes of the
+ * public key will be serialized into on variable-size
+ * BLOB.
+ *
+ * @param denom_pub pointer to the query parameter to pass
+ */
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_denom_pub (
+ const struct TALER_DenominationPublicKey *denom_pub);
+
+
+/**
+ * Generate query parameter for a denomination signature. Internally, the
+ * various attributes of the signature will be serialized into on
+ * variable-size BLOB.
+ *
+ * @param denom_sig pointer to the query parameter to pass
+ */
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_denom_sig (
+ const struct TALER_DenominationSignature *denom_sig);
+
+
+/**
+ * Generate query parameter for a blinded planchet.
+ * Internally, various attributes of the blinded
+ * planchet will be serialized into on
+ * variable-size BLOB.
+ *
+ * @param bp pointer to the query parameter to pass
+ */
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_blinded_planchet (
+ const struct TALER_BlindedPlanchet *bp);
+
+
+/**
+ * Generate query parameter for a blinded denomination signature. Internally,
+ * the various attributes of the signature will be serialized into on
+ * variable-size BLOB.
*
- * @param x pointer to the query parameter to pass
+ * @param denom_sig pointer to the query parameter to pass
*/
struct GNUNET_PQ_QueryParam
-TALER_PQ_query_param_amount_nbo (const struct TALER_AmountNBO *x);
+TALER_PQ_query_param_blinded_denom_sig (
+ const struct TALER_BlindedDenominationSignature *denom_sig);
/**
- * Generate query parameter for a currency, consisting of the three
- * components "value", "fraction" and "currency" in this order. The
- * types must be a 64-bit integer, 32-bit integer and a
- * #TALER_CURRENCY_LEN-sized BLOB/VARCHAR respectively.
+ * Generate query parameter for the exchange's contribution during a
+ * withdraw. Internally, the various attributes of the @a alg_values will be
+ * serialized into on variable-size BLOB.
*
- * @param x pointer to the query parameter to pass
+ * @param alg_values pointer to the query parameter to pass
*/
struct GNUNET_PQ_QueryParam
-TALER_PQ_query_param_amount (const struct TALER_Amount *x);
+TALER_PQ_query_param_exchange_withdraw_values (
+ const struct TALER_ExchangeWithdrawValues *alg_values);
/**
@@ -65,48 +140,115 @@ TALER_PQ_query_param_json (const json_t *x);
/**
- * Generate query parameter for an absolute time value.
- * In contrast to
- * #GNUNET_PQ_query_param_absolute_time(), this function
- * will abort (!) if the time given is not rounded!
- * The database must store a 64-bit integer.
+ * Generate query parameter for an array of blinded denomination signatures
+ *
+ * @param num number of elements in @e denom_sigs
+ * @param denom_sigs array of blinded denomination signatures
+ * @param db context for the db-connection
+ */
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_blinded_denom_sig (
+ size_t num,
+ const struct TALER_BlindedDenominationSignature *denom_sigs,
+ struct GNUNET_PQ_Context *db
+ );
+
+
+/**
+ * Generate query parameter for an array of blinded hashes of coin envelopes
+ *
+ * @param num number of elements in @e denom_sigs
+ * @param coin_evs array of blinded hashes of coin envelopes
+ * @param db context for the db-connection
+ */
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_blinded_coin_hash (
+ size_t num,
+ const struct TALER_BlindedCoinHashP *coin_evs,
+ struct GNUNET_PQ_Context *db);
+
+
+/**
+ * Generate query parameter for an array of GNUNET_HashCode
+ *
+ * @param num number of elements in @e hash_codes
+ * @param hashes array of GNUNET_HashCode
+ * @param db context for the db-connection
+ */
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_hash_code (
+ size_t num,
+ const struct GNUNET_HashCode *hashes,
+ struct GNUNET_PQ_Context *db);
+
+
+/**
+ * Generate query parameter for an array of amounts
+ *
+ * @param num of elements in @e amounts
+ * @param amounts continuous array of amounts
+ * @param db context for db-connection, needed for OID-lookup
+ */
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_amount (
+ size_t num,
+ const struct TALER_Amount *amounts,
+ struct GNUNET_PQ_Context *db);
+
+
+/**
+ * Generate query parameter for an array of amounts
+ *
+ * @param num of elements in @e amounts
+ * @param amounts continuous array of amounts
+ * @param db context for db-connection, needed for OID-lookup
+ */
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_amount_with_currency (
+ size_t num,
+ const struct TALER_Amount *amounts,
+ struct GNUNET_PQ_Context *db);
+
+
+/**
+ * Generate query parameter for a blind sign public key of variable size.
*
- * @param x pointer to the query parameter to pass
+ * @param public_key pointer to the query parameter to pass
*/
struct GNUNET_PQ_QueryParam
-TALER_PQ_query_param_absolute_time (const struct GNUNET_TIME_Absolute *x);
+TALER_PQ_query_param_blind_sign_pub (
+ const struct GNUNET_CRYPTO_BlindSignPublicKey *public_key);
/**
- * Generate query parameter for an absolute time value.
- * In contrast to
- * #GNUNET_PQ_query_param_absolute_time(), this function
- * will abort (!) if the time given is not rounded!
- * The database must store a 64-bit integer.
+ * Generate query parameter for a blind sign private key of variable size.
*
- * @param x pointer to the query parameter to pass
+ * @param private_key pointer to the query parameter to pass
*/
struct GNUNET_PQ_QueryParam
-TALER_PQ_query_param_absolute_time_nbo (const struct
- GNUNET_TIME_AbsoluteNBO *x);
+TALER_PQ_query_param_blind_sign_priv (
+ const struct GNUNET_CRYPTO_BlindSignPrivateKey *private_key);
/**
- * Currency amount expected.
+ * Currency amount expected, from a record-field of (DB)
+ * taler_amount_with_currency type. The currency must be stored in the
+ * database when using this function.
*
* @param name name of the field in the table
- * @param currency currency to use for @a amount
* @param[out] amount where to store the result
* @return array entry for the result specification to use
*/
struct GNUNET_PQ_ResultSpec
-TALER_PQ_result_spec_amount_nbo (const char *name,
- const char *currency,
- struct TALER_AmountNBO *amount);
+TALER_PQ_result_spec_amount_with_currency (
+ const char *name,
+ struct TALER_Amount *amount);
/**
- * Currency amount expected.
+ * Currency amount expected, from a record-field of (DB) taler_amount type.
+ * The currency is NOT stored in the database when using this function, but
+ * instead passed as the @a currency argument.
*
* @param name name of the field in the table
* @param currency currency to use for @a amount
@@ -120,6 +262,69 @@ TALER_PQ_result_spec_amount (const char *name,
/**
+ * Denomination public key expected.
+ *
+ * @param name name of the field in the table
+ * @param[out] denom_pub where to store the public key
+ * @return array entry for the result specification to use
+ */
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_denom_pub (const char *name,
+ struct TALER_DenominationPublicKey *denom_pub);
+
+
+/**
+ * Denomination signature expected.
+ *
+ * @param name name of the field in the table
+ * @param[out] denom_sig where to store the denomination signature
+ * @return array entry for the result specification to use
+ */
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_denom_sig (const char *name,
+ struct TALER_DenominationSignature *denom_sig);
+
+
+/**
+ * Blinded denomination signature expected.
+ *
+ * @param name name of the field in the table
+ * @param[out] denom_sig where to store the denomination signature
+ * @return array entry for the result specification to use
+ */
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_blinded_denom_sig (
+ const char *name,
+ struct TALER_BlindedDenominationSignature *denom_sig);
+
+
+/**
+ * Exchange withdraw values expected.
+ *
+ * @param name name of the field in the table
+ * @param[out] ewv where to store the exchange values
+ * @return array entry for the result specification to use
+ */
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_exchange_withdraw_values (
+ const char *name,
+ struct TALER_ExchangeWithdrawValues *ewv);
+
+
+/**
+ * Blinded planchet expected.
+ *
+ * @param name name of the field in the table
+ * @param[out] bp where to store the blinded planchet
+ * @return array entry for the result specification to use
+ */
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_blinded_planchet (
+ const char *name,
+ struct TALER_BlindedPlanchet *bp);
+
+
+/**
* json_t expected.
*
* @param name name of the field in the table
@@ -132,33 +337,89 @@ TALER_PQ_result_spec_json (const char *name,
/**
- * Rounded absolute time expected.
- * In contrast to #GNUNET_PQ_query_param_absolute_time_nbo(),
- * this function ensures that the result is rounded and can
- * be converted to JSON.
+ * Array of blinded denomination signature expected
+ *
+ * @param db context of the database connection
+ * @param name name of the field in the table
+ * @param[out] num number of elements in @e denom_sigs
+ * @param[out] denom_sigs where to store the result
+ * @return array entry for the result specification to use
+ */
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_array_blinded_denom_sig (
+ struct GNUNET_PQ_Context *db,
+ const char *name,
+ size_t *num,
+ struct TALER_BlindedDenominationSignature **denom_sigs);
+
+
+/**
+ * Array of blinded hashes of coin envelopes
*
+ * @param db context of the database connection
* @param name name of the field in the table
- * @param[out] at where to store the result
+ * @param[out] num number of elements in @e denom_sigs
+ * @param[out] h_coin_evs where to store the result
* @return array entry for the result specification to use
*/
struct GNUNET_PQ_ResultSpec
-TALER_PQ_result_spec_absolute_time (const char *name,
- struct GNUNET_TIME_Absolute *at);
+TALER_PQ_result_spec_array_blinded_coin_hash (
+ struct GNUNET_PQ_Context *db,
+ const char *name,
+ size_t *num,
+ struct TALER_BlindedCoinHashP **h_coin_evs);
/**
- * Rounded absolute time expected.
- * In contrast to #GNUNET_PQ_result_spec_absolute_time_nbo(),
- * this function ensures that the result is rounded and can
- * be converted to JSON.
+ * Array of hashes of denominations
+ *
+ * @param db context of the database connection
+ * @param name name of the field in the table
+ * @param[out] num number of elements in @e denom_sigs
+ * @param[out] denom_hs where to store the result
+ * @return array entry for the result specification to use
+ */
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_array_denom_hash (
+ struct GNUNET_PQ_Context *db,
+ const char *name,
+ size_t *num,
+ struct TALER_DenominationHashP **denom_hs);
+
+
+/**
+ * Array of GNUNET_HashCode
+ *
+ * @param db context of the database connection
+ * @param name name of the field in the table
+ * @param[out] num number of elements in @e denom_sigs
+ * @param[out] hashes where to store the result
+ * @return array entry for the result specification to use
+ */
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_array_hash_code (
+ struct GNUNET_PQ_Context *db,
+ const char *name,
+ size_t *num,
+ struct GNUNET_HashCode **hashes);
+
+/**
+ * Array of amounts
*
+ * @param db context of the database connection
* @param name name of the field in the table
- * @param[out] at where to store the result
+ * @param currency The currency
+ * @param[out] num number of elements in @e amounts
+ * @param[out] amounts where to store the result
* @return array entry for the result specification to use
*/
struct GNUNET_PQ_ResultSpec
-TALER_PQ_result_spec_absolute_time_nbo (const char *name,
- struct GNUNET_TIME_AbsoluteNBO *at);
+TALER_PQ_result_spec_array_amount (
+ struct GNUNET_PQ_Context *db,
+ const char *name,
+ const char *currency,
+ size_t *num,
+ struct TALER_Amount **amounts);
#endif /* TALER_PQ_LIB_H_ */
diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h
deleted file mode 100644
index e8fc57ded..000000000
--- a/src/include/taler_signatures.h
+++ /dev/null
@@ -1,1422 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2017 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 taler_signatures.h
- * @brief message formats and signature constants used to define
- * the binary formats of signatures in Taler
- * @author Florian Dold
- * @author Benedikt Mueller
- *
- * This file should define the constants and C structs that one needs
- * to know to implement Taler clients (wallets or merchants or
- * auditor) that need to produce or verify Taler signatures.
- */
-#ifndef TALER_SIGNATURES_H
-#define TALER_SIGNATURES_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include "taler_amount_lib.h"
-#include "taler_crypto_lib.h"
-
-/**
- * Cut-and-choose size for refreshing. Client looses the gamble (of
- * unaccountable transfers) with probability 1/TALER_CNC_KAPPA. Refresh cost
- * increases linearly with TALER_CNC_KAPPA, and 3 is sufficient up to a
- * income/sales tax of 66% of total transaction value. As there is
- * no good reason to change this security parameter, we declare it
- * fixed and part of the protocol.
- */
-#define TALER_CNC_KAPPA 3
-
-
-/*********************************************/
-/* Exchange offline signatures (with master key) */
-/*********************************************/
-
-/**
- * Purpose for signing public keys signed by the exchange master key.
- */
-#define TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY 1024
-
-/**
- * Purpose for denomination keys signed by the exchange master key.
- */
-#define TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY 1025
-
-/**
- * Fees charged per (aggregate) wire transfer to the merchant.
- */
-#define TALER_SIGNATURE_MASTER_WIRE_FEES 1028
-
-/**
- * The given revocation key was revoked and must no longer be used.
- */
-#define TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED 1029
-
-/**
- * Signature where the Exchange confirms its IBAN details in
- * the /wire response.
- */
-#define TALER_SIGNATURE_MASTER_WIRE_DETAILS 1030
-
-/*********************************************/
-/* Exchange online signatures (with signing key) */
-/*********************************************/
-
-/**
- * Purpose for the state of a reserve, signed by the exchange's signing
- * key.
- */
-#define TALER_SIGNATURE_EXCHANGE_RESERVE_STATUS 1032
-
-/**
- * Signature where the Exchange confirms a deposit request.
- */
-#define TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT 1033
-
-/**
- * Signature where the exchange (current signing key) confirms the
- * no-reveal index for cut-and-choose and the validity of the melted
- * coins.
- */
-#define TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT 1034
-
-/**
- * Signature where the Exchange confirms the full /keys response set.
- */
-#define TALER_SIGNATURE_EXCHANGE_KEY_SET 1035
-
-/**
- * Signature where the Exchange confirms the /track/transaction response.
- */
-#define TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE 1036
-
-/**
- * Signature where the Exchange confirms the /wire/deposit response.
- */
-#define TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT 1037
-
-/**
- * Signature where the Exchange confirms a refund request.
- */
-#define TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND 1038
-
-/**
- * Signature where the Exchange confirms a recoup.
- */
-#define TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP 1039
-
-/**
- * Signature where the Exchange confirms it closed a reserve.
- */
-#define TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED 1040
-
-/**
- * Signature where the Exchange confirms a recoup-refresh operation.
- */
-#define TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH 1041
-
-
-/**********************/
-/* Auditor signatures */
-/**********************/
-
-/**
- * Signature where the auditor confirms that he is
- * aware of certain denomination keys from the exchange.
- */
-#define TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS 1064
-
-
-/***********************/
-/* Merchant signatures */
-/***********************/
-
-/**
- * Signature where the merchant confirms a contract (to the customer).
- */
-#define TALER_SIGNATURE_MERCHANT_CONTRACT 1101
-
-/**
- * Signature where the merchant confirms a refund (of a coin).
- */
-#define TALER_SIGNATURE_MERCHANT_REFUND 1102
-
-/**
- * Signature where the merchant confirms that he needs the wire
- * transfer identifier for a deposit operation.
- */
-#define TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION 1103
-
-/**
- * Signature where the merchant confirms that the payment was
- * successful
- */
-#define TALER_SIGNATURE_MERCHANT_PAYMENT_OK 1104
-
-/**
- * Signature where the merchant confirms a refund increase
- */
-#define TALER_SIGNATURE_MERCHANT_REFUND_OK 1105
-
-/**
- * Signature where the merchant confirms that the user replayed
- * a payment for a browser session.
- */
-#define TALER_SIGNATURE_MERCHANT_PAY_SESSION 1106
-
-/**
- * Signature where the merchant confirms its own (salted)
- * wire details (not yet really used).
- */
-#define TALER_SIGNATURE_MERCHANT_WIRE_DETAILS 1107
-
-
-/*********************/
-/* Wallet signatures */
-/*********************/
-
-/**
- * Signature where the reserve key confirms a withdraw request.
- */
-#define TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW 1200
-
-/**
- * Signature made by the wallet of a user to confirm a deposit of a coin.
- */
-#define TALER_SIGNATURE_WALLET_COIN_DEPOSIT 1201
-
-/**
- * Signature using a coin key confirming the melting of a coin.
- */
-#define TALER_SIGNATURE_WALLET_COIN_MELT 1202
-
-/**
- * Signature using a coin key requesting recoup.
- */
-#define TALER_SIGNATURE_WALLET_COIN_RECOUP 1203
-
-/**
- * Signature using a coin key authenticating link data.
- */
-#define TALER_SIGNATURE_WALLET_COIN_LINK 1204
-
-
-/*******************/
-/* Test signatures */
-/*******************/
-
-/**
- * EdDSA test signature.
- */
-#define TALER_SIGNATURE_CLIENT_TEST_EDDSA 1302
-
-/**
- * EdDSA test signature.
- */
-#define TALER_SIGNATURE_EXCHANGE_TEST_EDDSA 1303
-
-
-/************************/
-/* Anastasis signatures */
-/************************/
-
-/**
- * EdDSA signature for a policy upload.
- */
-#define TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD 1400
-
-/**
- * EdDSA signature for a policy download.
- */
-#define TALER_SIGNATURE_ANASTASIS_POLICY_DOWNLOAD 1401
-
-
-/*******************/
-/* Sync signatures */
-/*******************/
-
-
-/**
- * EdDSA signature for a backup upload.
- */
-#define TALER_SIGNATURE_SYNC_BACKUP_UPLOAD 1450
-
-
-GNUNET_NETWORK_STRUCT_BEGIN
-
-/**
- * @brief Format used for to allow the wallet to authenticate
- * link data provided by the exchange.
- */
-struct TALER_LinkDataPS
-{
-
- /**
- * Purpose must be #TALER_SIGNATURE_WALLET_COIN_LINK.
- * Used with an EdDSA signature of a `struct TALER_CoinPublicKeyP`.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hash of the denomination public key of the new coin.
- */
- struct GNUNET_HashCode h_denom_pub;
-
- /**
- * Public key of the old coin being refreshed.
- */
- struct TALER_CoinSpendPublicKeyP old_coin_pub;
-
- /**
- * Transfer public key (for which the private key was not revealed)
- */
- struct TALER_TransferPublicKeyP transfer_pub;
-
- /**
- * Hash of the blinded new coin.
- */
- struct GNUNET_HashCode coin_envelope_hash;
-};
-
-
-/**
- * @brief Format used for to generate the signature on a request to withdraw
- * coins from a reserve.
- */
-struct TALER_WithdrawRequestPS
-{
-
- /**
- * Purpose must be #TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW.
- * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Reserve public key (which reserve to withdraw from). This is
- * the public key which must match the signature.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Value of the coin being exchangeed (matching the denomination key)
- * plus the transaction fee. We include this in what is being
- * signed so that we can verify a reserve's remaining total balance
- * without needing to access the respective denomination key
- * information each time.
- */
- struct TALER_AmountNBO amount_with_fee;
-
- /**
- * Withdrawal fee charged by the exchange. This must match the Exchange's
- * denomination key's withdrawal fee. If the client puts in an
- * invalid withdrawal fee (too high or too low) that does not match
- * the Exchange's denomination key, the withdraw operation is invalid
- * and will be rejected by the exchange. The @e amount_with_fee minus
- * the @e withdraw_fee is must match the value of the generated
- * coin. We include this in what is being signed so that we can
- * verify a exchange's accounting without needing to access the
- * respective denomination key information each time.
- */
- struct TALER_AmountNBO withdraw_fee;
-
- /**
- * Hash of the denomination public key for the coin that is withdrawn.
- */
- struct GNUNET_HashCode h_denomination_pub GNUNET_PACKED;
-
- /**
- * Hash of the (blinded) message to be signed by the Exchange.
- */
- struct GNUNET_HashCode h_coin_envelope GNUNET_PACKED;
-};
-
-
-/**
- * @brief Format used to generate the signature on a request to deposit
- * a coin into the account of a merchant.
- */
-struct TALER_DepositRequestPS
-{
- /**
- * Purpose must be #TALER_SIGNATURE_WALLET_COIN_DEPOSIT.
- * Used for an EdDSA signature with the `struct TALER_CoinSpendPublicKeyP`.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hash over the contract for which this deposit is made.
- */
- struct GNUNET_HashCode h_contract_terms GNUNET_PACKED;
-
- /**
- * Hash over the wiring information of the merchant.
- */
- struct GNUNET_HashCode h_wire GNUNET_PACKED;
-
- /**
- * Time when this request was generated. Used, for example, to
- * assess when (roughly) the income was achieved for tax purposes.
- * Note that the Exchange will only check that the timestamp is not "too
- * far" into the future (i.e. several days). The fact that the
- * timestamp falls within the validity period of the coin's
- * denomination key is irrelevant for the validity of the deposit
- * request, as obviously the customer and merchant could conspire to
- * set any timestamp. Also, the Exchange must accept very old deposit
- * requests, as the merchant might have been unable to transmit the
- * deposit request in a timely fashion (so back-dating is not
- * prevented).
- */
- struct GNUNET_TIME_AbsoluteNBO timestamp;
-
- /**
- * How much time does the merchant have to issue a refund request?
- * Zero if refunds are not allowed. After this time, the coin
- * cannot be refunded.
- */
- struct GNUNET_TIME_AbsoluteNBO refund_deadline;
-
- /**
- * Amount to be deposited, including deposit fee charged by the
- * exchange. This is the total amount that the coin's value at the exchange
- * will be reduced by.
- */
- struct TALER_AmountNBO amount_with_fee;
-
- /**
- * Depositing fee charged by the exchange. This must match the Exchange's
- * denomination key's depositing fee. If the client puts in an
- * invalid deposit fee (too high or too low) that does not match the
- * Exchange's denomination key, the deposit operation is invalid and
- * will be rejected by the exchange. The @e amount_with_fee minus the
- * @e deposit_fee is the amount that will be transferred to the
- * account identified by @e h_wire.
- */
- struct TALER_AmountNBO deposit_fee;
-
- /**
- * The Merchant's public key. Allows the merchant to later refund
- * the transaction or to inquire about the wire transfer identifier.
- */
- struct TALER_MerchantPublicKeyP merchant;
-
- /**
- * The coin's public key. This is the value that must have been
- * signed (blindly) by the Exchange. The deposit request is to be
- * signed by the corresponding private key (using EdDSA).
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
-};
-
-
-/**
- * @brief Format used to generate the signature on a confirmation
- * from the exchange that a deposit request succeeded.
- */
-struct TALER_DepositConfirmationPS
-{
- /**
- * Purpose must be #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT. Signed
- * by a `struct TALER_ExchangePublicKeyP` using EdDSA.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hash over the contract for which this deposit is made.
- */
- struct GNUNET_HashCode h_contract_terms GNUNET_PACKED;
-
- /**
- * Hash over the wiring information of the merchant.
- */
- struct GNUNET_HashCode h_wire GNUNET_PACKED;
-
- /**
- * Time when this confirmation was generated.
- */
- struct GNUNET_TIME_AbsoluteNBO timestamp;
-
- /**
- * How much time does the @e merchant have to issue a refund
- * request? Zero if refunds are not allowed. After this time, the
- * coin cannot be refunded. Note that the wire transfer will not be
- * performed by the exchange until the refund deadline. This value
- * is taken from the original deposit request.
- */
- struct GNUNET_TIME_AbsoluteNBO refund_deadline;
-
- /**
- * Amount to be deposited, excluding fee. Calculated from the
- * amount with fee and the fee from the deposit request.
- */
- struct TALER_AmountNBO amount_without_fee;
-
- /**
- * The coin's public key. This is the value that must have been
- * signed (blindly) by the Exchange. The deposit request is to be
- * signed by the corresponding private key (using EdDSA).
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * The Merchant's public key. Allows the merchant to later refund
- * the transaction or to inquire about the wire transfer identifier.
- */
- struct TALER_MerchantPublicKeyP merchant;
-
-};
-
-
-/**
- * @brief Format used to generate the signature on a request to refund
- * a coin into the account of the customer.
- */
-struct TALER_RefundRequestPS
-{
- /**
- * Purpose must be #TALER_SIGNATURE_MERCHANT_REFUND.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hash over the proposal data to identify the contract
- * which is being refunded.
- */
- struct GNUNET_HashCode h_contract_terms GNUNET_PACKED;
-
- /**
- * The coin's public key. This is the value that must have been
- * signed (blindly) by the Exchange.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * The Merchant's public key. Allows the merchant to later refund
- * the transaction or to inquire about the wire transfer identifier.
- */
- struct TALER_MerchantPublicKeyP merchant;
-
- /**
- * Merchant-generated transaction ID for the refund.
- */
- uint64_t rtransaction_id GNUNET_PACKED;
-
- /**
- * Amount to be refunded, including refund fee charged by the
- * exchange to the customer.
- */
- struct TALER_AmountNBO refund_amount;
-
- /**
- * Refund fee charged by the exchange. This must match the
- * Exchange's denomination key's refund fee. If the client puts in
- * an invalid refund fee (too high or too low) that does not match
- * the Exchange's denomination key, the refund operation is invalid
- * and will be rejected by the exchange. The @e amount_with_fee
- * minus the @e refund_fee is the amount that will be credited to
- * the original coin.
- */
- struct TALER_AmountNBO refund_fee;
-
-};
-
-
-/**
- * @brief Format used to generate the signature on a request to refund
- * a coin into the account of the customer.
- */
-struct TALER_RefundConfirmationPS
-{
- /**
- * Purpose must be #TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hash over the proposal data to identify the contract
- * which is being refunded.
- */
- struct GNUNET_HashCode h_contract_terms GNUNET_PACKED;
-
- /**
- * The coin's public key. This is the value that must have been
- * signed (blindly) by the Exchange.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * The Merchant's public key. Allows the merchant to later refund
- * the transaction or to inquire about the wire transfer identifier.
- */
- struct TALER_MerchantPublicKeyP merchant;
-
- /**
- * Merchant-generated transaction ID for the refund.
- */
- uint64_t rtransaction_id GNUNET_PACKED;
-
- /**
- * Amount to be refunded, including refund fee charged by the
- * exchange to the customer.
- */
- struct TALER_AmountNBO refund_amount;
-
- /**
- * Refund fee charged by the exchange. This must match the
- * Exchange's denomination key's refund fee. If the client puts in
- * an invalid refund fee (too high or too low) that does not match
- * the Exchange's denomination key, the refund operation is invalid
- * and will be rejected by the exchange. The @e amount_with_fee
- * minus the @e refund_fee is the amount that will be credited to
- * the original coin.
- */
- struct TALER_AmountNBO refund_fee;
-
-};
-
-
-/**
- * @brief Message signed by a coin to indicate that the coin should be
- * melted.
- */
-struct TALER_RefreshMeltCoinAffirmationPS
-{
- /**
- * Purpose is #TALER_SIGNATURE_WALLET_COIN_MELT.
- * Used for an EdDSA signature with the `struct TALER_CoinSpendPublicKeyP`.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Which melt commitment is made by the wallet.
- */
- struct TALER_RefreshCommitmentP rc GNUNET_PACKED;
-
- /**
- * How much of the value of the coin should be melted? This amount
- * includes the fees, so the final amount contributed to the melt is
- * this value minus the fee for melting the coin. We include the
- * fee in what is being signed so that we can verify a reserve's
- * remaining total balance without needing to access the respective
- * denomination key information each time.
- */
- struct TALER_AmountNBO amount_with_fee;
-
- /**
- * Melting fee charged by the exchange. This must match the Exchange's
- * denomination key's melting fee. If the client puts in an invalid
- * melting fee (too high or too low) that does not match the Exchange's
- * denomination key, the melting operation is invalid and will be
- * rejected by the exchange. The @e amount_with_fee minus the @e
- * melt_fee is the amount that will be credited to the melting
- * session.
- */
- struct TALER_AmountNBO melt_fee;
-
- /**
- * The coin's public key. This is the value that must have been
- * signed (blindly) by the Exchange. The deposit request is to be
- * signed by the corresponding private key (using EdDSA).
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-};
-
-
-/**
- * @brief Format of the block signed by the Exchange in response to a successful
- * "/refresh/melt" request. Hereby the exchange affirms that all of the
- * coins were successfully melted. This also commits the exchange to a
- * particular index to not be revealed during the refresh.
- */
-struct TALER_RefreshMeltConfirmationPS
-{
- /**
- * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT. Signed
- * by a `struct TALER_ExchangePublicKeyP` using EdDSA.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Commitment made in the /refresh/melt.
- */
- struct TALER_RefreshCommitmentP rc GNUNET_PACKED;
-
- /**
- * Index that the client will not have to reveal, in NBO.
- * Must be smaller than #TALER_CNC_KAPPA.
- */
- uint32_t noreveal_index GNUNET_PACKED;
-
-};
-
-
-/**
- * @brief Information about a signing key of the exchange. Signing keys are used
- * to sign exchange messages other than coins, i.e. to confirm that a
- * deposit was successful or that a refresh was accepted.
- */
-struct TALER_ExchangeSigningKeyValidityPS
-{
-
- /**
- * Purpose is #TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Master public key of the exchange corresponding to @e signature.
- * This is the long-term offline master key of the exchange.
- */
- struct TALER_MasterPublicKeyP master_public_key;
-
- /**
- * When does this signing key begin to be valid?
- */
- struct GNUNET_TIME_AbsoluteNBO start;
-
- /**
- * When does this signing key expire? Note: This is currently when
- * the Exchange will definitively stop using it. Signatures made with
- * the key remain valid until @e end. When checking validity periods,
- * clients should allow for some overlap between keys and tolerate
- * the use of either key during the overlap time (due to the
- * possibility of clock skew).
- */
- struct GNUNET_TIME_AbsoluteNBO expire;
-
- /**
- * When do signatures with this signing key become invalid? After
- * this point, these signatures cannot be used in (legal) disputes
- * anymore, as the Exchange is then allowed to destroy its side of the
- * evidence. @e end is expected to be significantly larger than @e
- * expire (by a year or more).
- */
- struct GNUNET_TIME_AbsoluteNBO end;
-
- /**
- * The public online signing key that the exchange will use
- * between @e start and @e expire.
- */
- struct TALER_ExchangePublicKeyP signkey_pub;
-};
-
-
-/**
- * @brief Signature made by the exchange over the full set of keys, used
- * to detect cheating exchanges that give out different sets to
- * different users.
- */
-struct TALER_ExchangeKeySetPS
-{
-
- /**
- * Purpose is #TALER_SIGNATURE_EXCHANGE_KEY_SET. Signed
- * by a `struct TALER_ExchangePublicKeyP` using EdDSA.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Time of the key set issue.
- */
- struct GNUNET_TIME_AbsoluteNBO list_issue_date;
-
- /**
- * Hash over the various denomination signing keys returned.
- */
- struct GNUNET_HashCode hc GNUNET_PACKED;
-};
-
-
-/**
- * @brief Information about a denomination key. Denomination keys
- * are used to sign coins of a certain value into existence.
- */
-struct TALER_DenominationKeyValidityPS
-{
-
- /**
- * Purpose is #TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * The long-term offline master key of the exchange that was
- * used to create @e signature.
- */
- struct TALER_MasterPublicKeyP master;
-
- /**
- * Start time of the validity period for this key.
- */
- struct GNUNET_TIME_AbsoluteNBO start;
-
- /**
- * The exchange will sign fresh coins between @e start and this time.
- * @e expire_withdraw will be somewhat larger than @e start to
- * ensure a sufficiently large anonymity set, while also allowing
- * the Exchange to limit the financial damage in case of a key being
- * compromised. Thus, exchanges with low volume are expected to have a
- * longer withdraw period (@e expire_withdraw - @e start) than exchanges
- * with high transaction volume. The period may also differ between
- * types of coins. A exchange may also have a few denomination keys
- * with the same value with overlapping validity periods, to address
- * issues such as clock skew.
- */
- struct GNUNET_TIME_AbsoluteNBO expire_withdraw;
-
- /**
- * Coins signed with the denomination key must be spent or refreshed
- * between @e start and this expiration time. After this time, the
- * exchange will refuse transactions involving this key as it will
- * "drop" the table with double-spending information (shortly after)
- * this time. Note that wallets should refresh coins significantly
- * before this time to be on the safe side. @e expire_deposit must be
- * significantly larger than @e expire_withdraw (by months or even
- * years).
- */
- struct GNUNET_TIME_AbsoluteNBO expire_deposit;
-
- /**
- * When do signatures with this denomination key become invalid?
- * After this point, these signatures cannot be used in (legal)
- * disputes anymore, as the Exchange is then allowed to destroy its side
- * of the evidence. @e expire_legal is expected to be significantly
- * larger than @e expire_deposit (by a year or more).
- */
- struct GNUNET_TIME_AbsoluteNBO expire_legal;
-
- /**
- * The value of the coins signed with this denomination key.
- */
- struct TALER_AmountNBO value;
-
- /**
- * The fee the exchange charges when a coin of this type is withdrawn.
- * (can be zero).
- */
- struct TALER_AmountNBO fee_withdraw;
-
- /**
- * The fee the exchange charges when a coin of this type is deposited.
- * (can be zero).
- */
- struct TALER_AmountNBO fee_deposit;
-
- /**
- * The fee the exchange charges when a coin of this type is refreshed.
- * (can be zero).
- */
- struct TALER_AmountNBO fee_refresh;
-
- /**
- * The fee the exchange charges when a coin of this type is refunded.
- * (can be zero). Note that refund fees are charged to the customer;
- * if a refund is given, the deposit fee is also refunded.
- */
- struct TALER_AmountNBO fee_refund;
-
- /**
- * Hash code of the denomination public key. (Used to avoid having
- * the variable-size RSA key in this struct.)
- */
- struct GNUNET_HashCode denom_hash GNUNET_PACKED;
-
-};
-
-
-/**
- * @brief Information signed by an auditor affirming
- * the master public key and the denomination keys
- * of a exchange.
- */
-struct TALER_ExchangeKeyValidityPS
-{
-
- /**
- * Purpose is #TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hash of the auditor's URL (including 0-terminator).
- */
- struct GNUNET_HashCode auditor_url_hash;
-
- /**
- * The long-term offline master key of the exchange, affirmed by the
- * auditor. Hashed string, including 0-terminator.
- */
- struct TALER_MasterPublicKeyP master;
-
- /**
- * Start time of the validity period for this key.
- */
- struct GNUNET_TIME_AbsoluteNBO start;
-
- /**
- * The exchange will sign fresh coins between @e start and this time.
- * @e expire_withdraw will be somewhat larger than @e start to
- * ensure a sufficiently large anonymity set, while also allowing
- * the Exchange to limit the financial damage in case of a key being
- * compromised. Thus, exchanges with low volume are expected to have a
- * longer withdraw period (@e expire_withdraw - @e start) than exchanges
- * with high transaction volume. The period may also differ between
- * types of coins. A exchange may also have a few denomination keys
- * with the same value with overlapping validity periods, to address
- * issues such as clock skew.
- */
- struct GNUNET_TIME_AbsoluteNBO expire_withdraw;
-
- /**
- * Coins signed with the denomination key must be spent or refreshed
- * between @e start and this expiration time. After this time, the
- * exchange will refuse transactions involving this key as it will
- * "drop" the table with double-spending information (shortly after)
- * this time. Note that wallets should refresh coins significantly
- * before this time to be on the safe side. @e expire_deposit must be
- * significantly larger than @e expire_withdraw (by months or even
- * years).
- */
- struct GNUNET_TIME_AbsoluteNBO expire_deposit;
-
- /**
- * When do signatures with this denomination key become invalid?
- * After this point, these signatures cannot be used in (legal)
- * disputes anymore, as the Exchange is then allowed to destroy its side
- * of the evidence. @e expire_legal is expected to be significantly
- * larger than @e expire_deposit (by a year or more).
- */
- struct GNUNET_TIME_AbsoluteNBO expire_legal;
-
- /**
- * The value of the coins signed with this denomination key.
- */
- struct TALER_AmountNBO value;
-
- /**
- * The fee the exchange charges when a coin of this type is withdrawn.
- * (can be zero).
- */
- struct TALER_AmountNBO fee_withdraw;
-
- /**
- * The fee the exchange charges when a coin of this type is deposited.
- * (can be zero).
- */
- struct TALER_AmountNBO fee_deposit;
-
- /**
- * The fee the exchange charges when a coin of this type is refreshed.
- * (can be zero).
- */
- struct TALER_AmountNBO fee_refresh;
-
- /**
- * The fee the exchange charges when a coin of this type is refreshed.
- * (can be zero).
- */
- struct TALER_AmountNBO fee_refund;
-
- /**
- * Hash code of the denomination public key. (Used to avoid having
- * the variable-size RSA key in this struct.)
- */
- struct GNUNET_HashCode denom_hash GNUNET_PACKED;
-
-};
-
-
-/**
- * @brief Information signed by the exchange's master
- * key affirming the IBAN details for the exchange.
- */
-struct TALER_MasterWireDetailsPS
-{
-
- /**
- * Purpose is #TALER_SIGNATURE_MASTER_WIRE_DETAILS.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hash over the account holder's payto:// URL and
- * the salt, as done by #TALER_exchange_wire_signature_hash().
- */
- struct GNUNET_HashCode h_wire_details GNUNET_PACKED;
-
-};
-
-
-/**
- * @brief Information signed by the exchange's master
- * key stating the wire fee to be paid per wire transfer.
- */
-struct TALER_MasterWireFeePS
-{
-
- /**
- * Purpose is #TALER_SIGNATURE_MASTER_WIRE_FEES.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hash over the wire method (yes, H("x-taler-bank") or H("iban")), in lower
- * case, including 0-terminator. Used to uniquely identify which
- * wire method these fees apply to.
- */
- struct GNUNET_HashCode h_wire_method;
-
- /**
- * Start date when the fee goes into effect.
- */
- struct GNUNET_TIME_AbsoluteNBO start_date;
-
- /**
- * End date when the fee stops being in effect (exclusive)
- */
- struct GNUNET_TIME_AbsoluteNBO end_date;
-
- /**
- * Fee charged to the merchant per wire transfer.
- */
- struct TALER_AmountNBO wire_fee;
-
- /**
- * Closing fee charged when we wire back funds of a reserve.
- */
- struct TALER_AmountNBO closing_fee;
-
-};
-
-
-/**
- * @brief Message confirming that a denomination key was revoked.
- */
-struct TALER_MasterDenominationKeyRevocationPS
-{
- /**
- * Purpose is #TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hash of the denomination key.
- */
- struct GNUNET_HashCode h_denom_pub;
-
-};
-
-
-/**
- * @brief Format used to generate the signature on a request to obtain
- * the wire transfer identifier associated with a deposit.
- */
-struct TALER_DepositTrackPS
-{
- /**
- * Purpose must be #TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hash over the proposal data of the contract for which this deposit is made.
- */
- struct GNUNET_HashCode h_contract_terms GNUNET_PACKED;
-
- /**
- * Hash over the wiring information of the merchant.
- */
- struct GNUNET_HashCode h_wire GNUNET_PACKED;
-
- /**
- * The Merchant's public key. The deposit inquiry request is to be
- * signed by the corresponding private key (using EdDSA).
- */
- struct TALER_MerchantPublicKeyP merchant;
-
- /**
- * The coin's public key. This is the value that must have been
- * signed (blindly) by the Exchange.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
-};
-
-
-/**
- * @brief Format internally used for packing the detailed information
- * to generate the signature for /track/transfer signatures.
- */
-struct TALER_WireDepositDetailP
-{
-
- /**
- * Hash of the contract
- */
- struct GNUNET_HashCode h_contract_terms;
-
- /**
- * Time when the wire transfer was performed by the exchange.
- */
- struct GNUNET_TIME_AbsoluteNBO execution_time;
-
- /**
- * Coin's public key.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Total value of the coin.
- */
- struct TALER_AmountNBO deposit_value;
-
- /**
- * Fees charged by the exchange for the deposit.
- */
- struct TALER_AmountNBO deposit_fee;
-
-};
-
-
-/**
- * @brief Format used to generate the signature for /wire/deposit
- * replies.
- */
-struct TALER_WireDepositDataPS
-{
- /**
- * Purpose header for the signature over the contract with
- * purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Total amount that was transferred.
- */
- struct TALER_AmountNBO total;
-
- /**
- * Wire fee that was charged.
- */
- struct TALER_AmountNBO wire_fee;
-
- /**
- * Public key of the merchant (for all aggregated transactions).
- */
- struct TALER_MerchantPublicKeyP merchant_pub;
-
- /**
- * Hash of wire details of the merchant.
- */
- struct GNUNET_HashCode h_wire;
-
- /**
- * Hash of the individual deposits that were aggregated,
- * each in the format of a `struct TALER_WireDepositDetailP`.
- */
- struct GNUNET_HashCode h_details;
-
-};
-
-/**
- * The contract sent by the merchant to the wallet.
- */
-struct TALER_ProposalDataPS
-{
- /**
- * Purpose header for the signature over the proposal data
- * with purpose #TALER_SIGNATURE_MERCHANT_CONTRACT.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hash of the JSON contract in UTF-8 including 0-termination,
- * using JSON_COMPACT | JSON_SORT_KEYS
- */
- struct GNUNET_HashCode hash;
-};
-
-/**
- * Used by merchants to return signed responses to /pay requests.
- * Currently only used to return 200 OK signed responses.
- */
-struct PaymentResponsePS
-{
- /**
- * Set to TALER_SIGNATURE_MERCHANT_PAYMENT_OK so far. Note that
- * unsuccessful payments are usually proven by some exchange's signature,
- * thus it is unlikely that a merchant needs to set a purpose other than
- * the above mentioned
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hash of the proposal data associated with this confirmation
- */
- struct GNUNET_HashCode h_contract_terms;
-};
-
-
-/**
- * Details affirmed by the exchange about a wire transfer the exchange
- * claims to have done with respect to a deposit operation.
- */
-struct TALER_ConfirmWirePS
-{
- /**
- * Purpose header for the signature over the contract with
- * purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hash over the wiring information of the merchant.
- */
- struct GNUNET_HashCode h_wire GNUNET_PACKED;
-
- /**
- * Hash over the contract for which this deposit is made.
- */
- struct GNUNET_HashCode h_contract_terms GNUNET_PACKED;
-
- /**
- * Raw value (binary encoding) of the wire transfer subject.
- */
- struct TALER_WireTransferIdentifierRawP wtid;
-
- /**
- * The coin's public key. This is the value that must have been
- * signed (blindly) by the Exchange.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * When did the exchange execute this transfer? Note that the
- * timestamp may not be exactly the same on the wire, i.e.
- * because the wire has a different timezone or resolution.
- */
- struct GNUNET_TIME_AbsoluteNBO execution_time;
-
- /**
- * The contribution of @e coin_pub to the total transfer volume.
- * This is the value of the deposit minus the fee.
- */
- struct TALER_AmountNBO coin_contribution;
-
-};
-
-
-/**
- * Signed data to request that a coin should be refunded as part of
- * the "emergency" /recoup protocol. The refund will go back to the bank
- * account that created the reserve.
- */
-struct TALER_RecoupRequestPS
-{
- /**
- * Purpose is #TALER_SIGNATURE_WALLET_COIN_RECOUP
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Public key of the coin to be refunded.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Hash of the denomination public key of the coin.
- */
- struct GNUNET_HashCode h_denom_pub;
-
- /**
- * Blinding factor that was used to withdraw the coin.
- */
- struct TALER_DenominationBlindingKeyP coin_blind;
-};
-
-
-/**
- * Response by which the exchange affirms that it will
- * refund a coin as part of the emergency /recoup
- * protocol. The recoup will go back to the bank
- * account that created the reserve.
- */
-struct TALER_RecoupConfirmationPS
-{
-
- /**
- * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * When did the exchange receive the recoup request?
- * Indirectly determines when the wire transfer is (likely)
- * to happen.
- */
- struct GNUNET_TIME_AbsoluteNBO timestamp;
-
- /**
- * How much of the coin's value will the exchange transfer?
- * (Needed in case the coin was partially spent.)
- */
- struct TALER_AmountNBO recoup_amount;
-
- /**
- * Public key of the coin.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Public key of the reserve that will receive the recoup.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-};
-
-
-/**
- * Response by which the exchange affirms that it will refund a refreshed coin
- * as part of the emergency /recoup protocol. The recoup will go back to the
- * old coin's balance.
- */
-struct TALER_RecoupRefreshConfirmationPS
-{
-
- /**
- * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * When did the exchange receive the recoup request?
- * Indirectly determines when the wire transfer is (likely)
- * to happen.
- */
- struct GNUNET_TIME_AbsoluteNBO timestamp;
-
- /**
- * How much of the coin's value will the exchange transfer?
- * (Needed in case the coin was partially spent.)
- */
- struct TALER_AmountNBO recoup_amount;
-
- /**
- * Public key of the refreshed coin.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Public key of the old coin that will receive the recoup.
- */
- struct TALER_CoinSpendPublicKeyP old_coin_pub;
-};
-
-
-/**
- * Response by which the exchange affirms that it has
- * closed a reserve and send back the funds.
- */
-struct TALER_ReserveCloseConfirmationPS
-{
-
- /**
- * Purpose is #TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * When did the exchange initiate the wire transfer.
- */
- struct GNUNET_TIME_AbsoluteNBO timestamp;
-
- /**
- * How much did the exchange send?
- */
- struct TALER_AmountNBO closing_amount;
-
- /**
- * How much did the exchange charge for closing the reserve?
- */
- struct TALER_AmountNBO closing_fee;
-
- /**
- * Public key of the reserve that received the recoup.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Hash of the receiver's bank account.
- */
- struct GNUNET_HashCode h_wire;
-
- /**
- * Wire transfer subject.
- */
- struct TALER_WireTransferIdentifierRawP wtid;
-};
-
-
-/**
- * Used by the merchant to confirm with a signature that the refund has
- * been successfully done. Even though the frontend doesn't usually do crypto,
- * this signature may turn useful in court.
- */
-struct TALER_MerchantRefundConfirmationPS
-{
- /**
- * Set to #TALER_SIGNATURE_MERCHANT_REFUND_OK.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hashed order id; in case frontend wants to check it.
- * Hashed without the 0-termination.
- */
- struct GNUNET_HashCode h_order_id GNUNET_PACKED;
-
-};
-
-/**
- * Used by the merchant to confirm to the frontend that
- * the user did a payment replay with the current browser session.
- */
-struct TALER_MerchantPaySessionSigPS
-{
- /**
- * Set to #TALER_SIGNATURE_MERCHANT_PAY_SESSION.
- */
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
- /**
- * Hashed order id.
- * Hashed without the 0-termination.
- */
- struct GNUNET_HashCode h_order_id GNUNET_PACKED;
-
- /**
- * Hashed session id.
- * Hashed without the 0-termination.
- */
- struct GNUNET_HashCode h_session_id GNUNET_PACKED;
-
-};
-
-
-GNUNET_NETWORK_STRUCT_END
-
-#endif
diff --git a/src/include/taler_sq_lib.h b/src/include/taler_sq_lib.h
new file mode 100644
index 000000000..b5749308e
--- /dev/null
+++ b/src/include/taler_sq_lib.h
@@ -0,0 +1,99 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 include/taler_sq_lib.h
+ * @brief helper functions for DB interactions with SQLite
+ * @author Jonathan Buchanan
+ */
+#ifndef TALER_SQ_LIB_H_
+#define TALER_SQ_LIB_H_
+
+#include <sqlite3.h>
+#include <jansson.h>
+#include <gnunet/gnunet_sq_lib.h>
+#include "taler_util.h"
+
+/**
+ * Generate query parameter for a currency, consisting of the
+ * components "value", "fraction" in this order. The
+ * types must be a 64-bit integer and a 64-bit integer.
+ *
+ * @param x pointer to the query parameter to pass
+ */
+struct GNUNET_SQ_QueryParam
+TALER_SQ_query_param_amount_nbo (const struct TALER_AmountNBO *x);
+
+
+/**
+ * Generate query parameter for a currency, consisting of the
+ * components "value", "fraction" in this order. The
+ * types must be a 64-bit integer and a 64-bit integer.
+ *
+ * @param x pointer to the query parameter to pass
+ */
+struct GNUNET_SQ_QueryParam
+TALER_SQ_query_param_amount (const struct TALER_Amount *x);
+
+
+/**
+ * Generate query parameter for a JSON object (stored as a string
+ * in the DB). Note that @a x must really be a JSON object or array,
+ * passing just a value (string, integer) is not supported and will
+ * result in an abort.
+ *
+ * @param x pointer to the json object to pass
+ */
+struct GNUNET_SQ_QueryParam
+TALER_SQ_query_param_json (const json_t *x);
+
+
+/**
+ * Currency amount expected.
+ *
+ * @param currency currency to use for @a amount
+ * @param[out] amount where to store the result
+ * @return array entry for the result specification to use
+ */
+struct GNUNET_SQ_ResultSpec
+TALER_SQ_result_spec_amount_nbo (const char *currency,
+ struct TALER_AmountNBO *amount);
+
+
+/**
+ * Currency amount expected.
+ *
+ * @param currency currency to use for @a amount
+ * @param[out] amount where to store the result
+ * @return array entry for the result specification to use
+ */
+struct GNUNET_SQ_ResultSpec
+TALER_SQ_result_spec_amount (const char *currency,
+ struct TALER_Amount *amount);
+
+
+/**
+ * json_t expected.
+ *
+ * @param[out] jp where to store the result
+ * @return array entry for the result specification to use
+ */
+struct GNUNET_SQ_ResultSpec
+TALER_SQ_result_spec_json (json_t **jp);
+
+
+#endif /* TALER_SQ_LIB_H_ */
+
+/* end of include/taler_sq_lib.h */
diff --git a/src/include/taler_templating_lib.h b/src/include/taler_templating_lib.h
new file mode 100644
index 000000000..6af6db715
--- /dev/null
+++ b/src/include/taler_templating_lib.h
@@ -0,0 +1,130 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 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/>
+*/
+/**
+ * @file taler_templating_lib.h
+ * @brief logic to load and complete HTML templates
+ * @author Christian Grothoff
+ */
+#ifndef TALER_TEMPLATING_LIB_H
+#define TALER_TEMPLATING_LIB_H
+
+#include <microhttpd.h>
+#include "taler_mhd_lib.h"
+
+/**
+ * Fill in Mustach template @a tmpl using the data from @a root
+ * and return the result in @a result.
+ *
+ * @param tmpl 0-terminated string with Mustach template
+ * @param root JSON data to fill into the template
+ * @param[out] result where to write the result
+ * @param[out] result_size where to write the length of the result
+ * @return 0 on success, otherwise Mustach-specific error code
+ */
+int
+TALER_TEMPLATING_fill (const char *tmpl,
+ const json_t *root,
+ void **result,
+ size_t *result_size);
+
+
+/**
+ * Load a @a template and substitute using @a root, returning the result in a
+ * @a reply encoded suitable for the @a connection with the given @a
+ * http_status code. On errors, the @a http_status code
+ * is updated to reflect the type of error encoded in the
+ * @a reply.
+ *
+ * @param connection the connection we act upon
+ * @param[in,out] http_status code to use on success,
+ * set to alternative code on failure
+ * @param template basename of the template to load
+ * @param instance_id instance ID, used to compute static files URL
+ * @param taler_uri value for "Taler:" header to set, or NULL
+ * @param root JSON object to pass as the root context
+ * @param[out] reply where to write the response object
+ * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued,
+ * #GNUNET_SYSERR on failure (to queue an error)
+ */
+enum GNUNET_GenericReturnValue
+TALER_TEMPLATING_build (struct MHD_Connection *connection,
+ unsigned int *http_status,
+ const char *template,
+ const char *instance_id,
+ const char *taler_uri,
+ const json_t *root,
+ struct MHD_Response **reply);
+
+
+/**
+ * Load a @a template and substitute using @a root, returning
+ * the result to the @a connection with the given
+ * @a http_status code.
+ *
+ * @param connection the connection we act upon
+ * @param http_status code to use on success
+ * @param template basename of the template to load
+ * @param instance_id instance ID, used to compute static files URL
+ * @param taler_uri value for "Taler:" header to set, or NULL
+ * @param root JSON object to pass as the root context
+ * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued,
+ * #GNUNET_SYSERR on failure (to queue an error)
+ */
+enum GNUNET_GenericReturnValue
+TALER_TEMPLATING_reply (struct MHD_Connection *connection,
+ unsigned int http_status,
+ const char *template,
+ const char *instance_id,
+ const char *taler_uri,
+ const json_t *root);
+
+
+/**
+ * Load a @a template and substitute an error message based on @a ec and @a
+ * detail, returning the result to the @a connection with the given @a
+ * http_status code.
+ *
+ * @param connection the connection we act upon
+ * @param template_basename basename of the template to load
+ * @param http_status code to use on success
+ * @param ec error code to return
+ * @param detail optional text to add to the template
+ * @return #MHD_YES on success, #MHD_NO to just close the connection
+ */
+MHD_RESULT
+TALER_TEMPLATING_reply_error (struct MHD_Connection *connection,
+ const char *template_basename,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const char *detail);
+
+/**
+ * Preload templates.
+ *
+ * @param subsystem name of the subsystem, "merchant" or "exchange"
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_TEMPLATING_init (const char *subsystem);
+
+
+/**
+ * Nicely shut down templating subsystem.
+ */
+void
+TALER_TEMPLATING_done (void);
+
+#endif
diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h
index be20e0cd7..f07d9be20 100644
--- a/src/include/taler_testing_lib.h
+++ b/src/include/taler_testing_lib.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2018 Taler Systems SA
+ (C) 2018-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
@@ -20,6 +20,8 @@
/**
* @file include/taler_testing_lib.h
* @brief API for writing an interpreter to test Taler components
+ * This library is not thread-safe, all APIs must only be used from a single thread.
+ * This library calls abort() if it runs out of memory. Be aware of these limitations.
* @author Christian Grothoff <christian@grothoff.org>
* @author Marcello Stanisci
*/
@@ -27,11 +29,13 @@
#define TALER_TESTING_LIB_H
#include "taler_util.h"
-#include "taler_exchange_service.h"
+#include <microhttpd.h>
#include <gnunet/gnunet_json_lib.h>
#include "taler_json_lib.h"
+#include "taler_auditor_service.h"
#include "taler_bank_service.h"
-#include <microhttpd.h>
+#include "taler_exchange_service.h"
+#include "taler_fakebank_lib.h"
/* ********************* Helper functions ********************* */
@@ -50,137 +54,156 @@
/**
- * Allocate and return a piece of wire-details. Combines
- * a @a payto -URL and adds some salt to create the JSON.
+ * Log an error message about us receiving an unexpected HTTP
+ * status code at the current command and fail the test.
*
- * @param payto payto://-URL to encapsulate
- * @return JSON describing the account, including the
- * payto://-URL of the account, must be manually decref'd
+ * @param is interpreter to fail
+ * @param status unexpected HTTP status code received
+ * @param expected expected HTTP status code
*/
-json_t *
-TALER_TESTING_make_wire_details (const char *payto);
+#define TALER_TESTING_unexpected_status(is,status,expected) \
+ do { \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
+ "Unexpected response code %u (expected: %u) to command %s in %s:%u\n", \
+ status, \
+ expected, \
+ TALER_TESTING_interpreter_get_current_label (is), \
+ __FILE__, \
+ __LINE__); \
+ TALER_TESTING_interpreter_fail (is); \
+ } while (0)
+
+/**
+ * Log an error message about us receiving an unexpected HTTP
+ * status code at the current command and fail the test and print the response
+ * body (expected as json).
+ *
+ * @param is interpreter to fail
+ * @param status unexpected HTTP status code received
+ * @param expected expected HTTP status code
+ * @param body received JSON-reply
+ */
+#define TALER_TESTING_unexpected_status_with_body(is,status,expected,body) \
+ do { \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
+ "Unexpected response code %u (expected: %u) to " \
+ "command %s in %s:%u\nwith body:\n>>%s<<\n", \
+ status, \
+ expected, \
+ TALER_TESTING_interpreter_get_current_label (is), \
+ __FILE__, \
+ __LINE__, \
+ json_dumps (body, JSON_INDENT (2))); \
+ TALER_TESTING_interpreter_fail (is); \
+ } while (0)
/**
- * Find denomination key matching the given amount.
+ * Log an error message about a command not having
+ * run to completion.
*
- * @param keys array of keys to search
- * @param amount coin value to look for
- * @return NULL if no matching key was found
+ * @param is interpreter
+ * @param label command label of the incomplete command
*/
-const struct TALER_EXCHANGE_DenomPublicKey *
-TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_Amount *amount);
+#define TALER_TESTING_command_incomplete(is,label) \
+ do { \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
+ "Command %s (%s:%u) did not complete (at %s)\n", \
+ label, \
+ __FILE__, \
+ __LINE__, \
+ TALER_TESTING_interpreter_get_current_label (is)); \
+ } while (0)
/**
- * Configuration data for an exchange.
+ * Common credentials used in a test.
*/
-struct TALER_TESTING_ExchangeConfiguration
+struct TALER_TESTING_Credentials
{
/**
- * Exchange base URL as it appears in the configuration. Note
- * that it might differ from the one where the exchange actually
- * listens from.
+ * Bank authentication details for the exchange bank
+ * account.
*/
- char *exchange_url;
+ struct TALER_BANK_AuthenticationData ba;
/**
- * Auditor base URL as it appears in the configuration. Note
- * that it might differ from the one where the auditor actually
- * listens from.
+ * Bank authentication details for the admin bank
+ * account.
*/
- char *auditor_url;
+ struct TALER_BANK_AuthenticationData ba_admin;
-};
+ /**
+ * Configuration file data.
+ */
+ struct GNUNET_CONFIGURATION_Handle *cfg;
-/**
- * Connection to the database: aggregates
- * plugin and session handles.
- */
-struct TALER_TESTING_DatabaseConnection
-{
/**
- * Database plugin.
+ * Base URL of the exchange.
*/
- struct TALER_EXCHANGEDB_Plugin *plugin;
+ char *exchange_url;
/**
- * Session with the database.
+ * Base URL of the auditor.
*/
- struct TALER_EXCHANGEDB_Session *session;
-};
+ char *auditor_url;
-/**
- * Prepare launching an exchange. Checks that the configured
- * port is available, runs taler-exchange-keyup,
- * taler-auditor-sign and taler-exchange-dbinit. Does not
- * launch the exchange process itself.
- *
- * @param config_filename configuration file to use
- * @param reset_db should we reset the database
- * @param[out] ec will be set to the exchange configuration data
- * @return #GNUNET_OK on success, #GNUNET_NO if test should be
- * skipped, #GNUNET_SYSERR on test failure
- */
-int
-TALER_TESTING_prepare_exchange (const char *config_filename,
- int reset_db,
- struct TALER_TESTING_ExchangeConfiguration *ec);
+ /**
+ * RFC 8905 URI of the exchange.
+ */
+ char *exchange_payto;
+ /**
+ * RFC 8905 URI of a user.
+ */
+ char *user42_payto;
-/**
- * "Canonical" cert_cb used when we are connecting to the
- * Exchange.
- *
- * @param cls closure, typically, the "run" method containing
- * all the commands to be run, and a closure for it.
- * @param keys the exchange's keys.
- * @param compat protocol compatibility information.
- * @param ec error code, #TALER_EC_NONE on success
- * @param http_status status returned by /keys, #MHD_HTTP_OK on success
- * @param full_reply JSON body of /keys request, NULL if reply was not in JSON
- */
-void
-TALER_TESTING_cert_cb (void *cls,
- const struct TALER_EXCHANGE_Keys *keys,
- enum TALER_EXCHANGE_VersionCompatibility compat,
- enum TALER_ErrorCode ec,
- unsigned int http_status,
- const json_t *full_reply);
+ /**
+ * RFC 8905 URI of a user.
+ */
+ char *user43_payto;
+};
/**
- * Wait for the exchange to have started. Waits for at
- * most 10s, after that returns 77 to indicate an error.
- *
- * @param base_url what URL should we expect the exchange
- * to be running at
- * @return 0 on success
+ * What type of bank are we using?
*/
-int
-TALER_TESTING_wait_exchange_ready (const char *base_url);
+enum TALER_TESTING_BankSystem
+{
+ TALER_TESTING_BS_FAKEBANK = 1,
+ TALER_TESTING_BS_IBAN = 2
+};
/**
- * Wait for the auditor to have started. Waits for at
- * most 10s, after that returns 77 to indicate an error.
+ * Obtain bank credentials for a given @a cfg_file using
+ * @a exchange_account_section as the basis for the
+ * exchange account.
*
- * @param base_url what URL should we expect the auditor
- * to be running at
- * @return 0 on success
+ * @param cfg_file name of configuration to parse
+ * @param exchange_account_section configuration section name for the exchange account to use
+ * @param bs type of bank to use
+ * @param[out] ua where to write user account details
+ * and other credentials
*/
-int
-TALER_TESTING_wait_auditor_ready (const char *base_url);
+enum GNUNET_GenericReturnValue
+TALER_TESTING_get_credentials (
+ const char *cfg_file,
+ const char *exchange_account_section,
+ enum TALER_TESTING_BankSystem bs,
+ struct TALER_TESTING_Credentials *ua);
/**
- * Remove files from previous runs
+ * Allocate and return a piece of wire-details. Combines
+ * a @a payto -URL and adds some salt to create the JSON.
*
- * @param config_name configuration file to use+
+ * @param payto payto://-URL to encapsulate
+ * @return JSON describing the account, including the
+ * payto://-URL of the account, must be manually decref'd
*/
-void
-TALER_TESTING_cleanup_files (const char *config_name);
+json_t *
+TALER_TESTING_make_wire_details (const char *payto);
/**
@@ -190,75 +213,23 @@ TALER_TESTING_cleanup_files (const char *config_name);
* @param cfg configuration
* @return #GNUNET_OK on success
*/
-int
+enum GNUNET_GenericReturnValue
TALER_TESTING_cleanup_files_cfg (void *cls,
const struct GNUNET_CONFIGURATION_Handle *cfg);
/**
- * Run `taler-exchange-keyup`.
- *
- * @param config_filename configuration file to use
- * @param output_filename where to write the output for the auditor
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_run_keyup (const char *config_filename,
- const char *output_filename);
-
-
-/**
- * Run `taler-auditor-dbinit -r` (reset auditor database).
- *
- * @param config_filename configuration file to use
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_auditor_db_reset (const char *config_filename);
-
-
-/**
- * Run `taler-exchange-dbinit -r` (reset exchange database).
- *
- * @param config_filename configuration file to use
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_exchange_db_reset (const char *config_filename);
-
-
-/**
- * Run `taler-auditor-sign`.
- *
- * @param config_filename configuration file to use
- * @param exchange_master_pub master public key of the exchange
- * @param auditor_base_url what is the base URL of the auditor
- * @param signdata_in where is the information from taler-exchange-keyup
- * @param signdata_out where to write the output for the exchange
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_run_auditor_sign (const char *config_filename,
- const char *exchange_master_pub,
- const char *auditor_base_url,
- const char *signdata_in,
- const char *signdata_out);
-
-
-/**
- * Run `taler-auditor-exchange`.
+ * Find denomination key matching the given amount.
*
- * @param config_filename configuration file to use
- * @param exchange_master_pub master public key of the exchange
- * @param exchange_base_url what is the base URL of the exchange
- * @param do_remove #GNUNET_NO to add exchange, #GNUNET_YES to remove
- * @return #GNUNET_OK on success
+ * @param keys array of keys to search
+ * @param amount coin value to look for
+ * @param age_restricted must the denomination be age restricted?
+ * @return NULL if no matching key was found
*/
-int
-TALER_TESTING_run_auditor_exchange (const char *config_filename,
- const char *exchange_master_pub,
- const char *exchange_base_url,
- int do_remove);
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_Amount *amount,
+ bool age_restricted);
/**
@@ -267,176 +238,17 @@ TALER_TESTING_run_auditor_exchange (const char *config_filename,
* @param url URL to extract port from, 80 is default
* @return #GNUNET_OK if the port is free
*/
-int
+enum GNUNET_GenericReturnValue
TALER_TESTING_url_port_free (const char *url);
-/**
- * Configuration data for a bank.
- */
-struct TALER_TESTING_BankConfiguration
-{
-
- /**
- * Authentication data for the exchange user at the bank.
- */
- struct TALER_BANK_AuthenticationData exchange_auth;
-
- /**
- * Payto URL of the exchange's account ("2")
- */
- char *exchange_payto;
-
- /**
- * Payto URL of a user account ("42")
- */
- char *user42_payto;
-
- /**
- * Payto URL of another user's account ("43")
- */
- char *user43_payto;
-
-};
-
-/**
- * Prepare launching a fakebank. Check that the configuration
- * file has the right option, and that the port is available.
- * If everything is OK, return the configuration data of the fakebank.
- *
- * @param config_filename configuration file to use
- * @param config_section which account to use
- * (must match x-taler-bank)
- * @param[out] bc set to the bank's configuration data
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_prepare_fakebank (const char *config_filename,
- const char *config_section,
- struct TALER_TESTING_BankConfiguration *bc);
-
-
/* ******************* Generic interpreter logic ************ */
/**
* Global state of the interpreter, used by a command
* to access information about other commands.
*/
-struct TALER_TESTING_Interpreter
-{
-
- /**
- * Commands the interpreter will run.
- */
- struct TALER_TESTING_Command *commands;
-
- /**
- * Interpreter task (if one is scheduled).
- */
- struct GNUNET_SCHEDULER_Task *task;
-
- /**
- * ID of task called whenever we get a SIGCHILD.
- * Used for #TALER_TESTING_wait_for_sigchld().
- */
- struct GNUNET_SCHEDULER_Task *child_death_task;
-
- /**
- * Main execution context for the main loop.
- */
- struct GNUNET_CURL_Context *ctx;
-
- /**
- * Our configuration.
- */
- const struct GNUNET_CONFIGURATION_Handle *cfg;
-
- /**
- * Context for running the CURL event loop.
- */
- struct GNUNET_CURL_RescheduleContext *rc;
-
- /**
- * Handle to our fakebank, if #TALER_TESTING_run_with_fakebank()
- * was used. Otherwise NULL.
- */
- struct TALER_FAKEBANK_Handle *fakebank;
-
- /**
- * Task run on timeout.
- */
- struct GNUNET_SCHEDULER_Task *timeout_task;
-
- /**
- * Function to call for cleanup at the end. Can be NULL.
- */
- GNUNET_SCHEDULER_TaskCallback final_cleanup_cb;
-
- /**
- * Closure for #final_cleanup_cb().
- */
- void *final_cleanup_cb_cls;
-
- /**
- * Instruction pointer. Tells #interpreter_run() which
- * instruction to run next. Need (signed) int because
- * it gets -1 when rewinding the interpreter to the first
- * CMD.
- */
- int ip;
-
- /**
- * Result of the testcases, #GNUNET_OK on success
- */
- int result;
-
- /**
- * Handle to the exchange.
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * Handle to the auditor. NULL unless specifically initialized
- * as part of #TALER_TESTING_auditor_setup().
- */
- struct TALER_AUDITOR_Handle *auditor;
-
- /**
- * Handle to exchange process; some commands need it
- * to send signals. E.g. to trigger the key state reload.
- */
- struct GNUNET_OS_Process *exchanged;
-
- /**
- * GNUNET_OK if key state should be reloaded. NOTE: this
- * field can be removed because a new "send signal" command
- * has been introduced.
- */
- int reload_keys;
-
- /**
- * Is the interpreter running (#GNUNET_YES) or waiting
- * for /keys (#GNUNET_NO)?
- */
- int working;
-
- /**
- * Is the auditor running (#GNUNET_YES) or waiting
- * for /version (#GNUNET_NO)?
- */
- int auditor_working;
-
- /**
- * How often have we gotten a /keys response so far?
- */
- unsigned int key_generation;
-
- /**
- * Exchange keys from last download.
- */
- const struct TALER_EXCHANGE_Keys *keys;
-
-};
+struct TALER_TESTING_Interpreter;
/**
@@ -457,6 +269,11 @@ struct TALER_TESTING_Command
const char *label;
/**
+ * Variable name for the command, NULL for none.
+ */
+ const char *name;
+
+ /**
* Runs the command. Note that upon return, the interpreter
* will not automatically run the next command, as the command
* may continue asynchronously in other scheduler tasks. Thus,
@@ -466,12 +283,12 @@ struct TALER_TESTING_Command
*
* @param cls closure
* @param cmd command being run
- * @param i interpreter state
+ * @param is interpreter state
*/
void
(*run)(void *cls,
const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *i);
+ struct TALER_TESTING_Interpreter *is);
/**
@@ -495,7 +312,7 @@ struct TALER_TESTING_Command
* @param index index number of the object to extract.
* @return #GNUNET_OK on success
*/
- int
+ enum GNUNET_GenericReturnValue
(*traits)(void *cls,
const void **ret,
const char *trait,
@@ -538,8 +355,43 @@ const struct TALER_TESTING_Command *
TALER_TESTING_interpreter_lookup_command (struct TALER_TESTING_Interpreter *is,
const char *label);
+
/**
- * Obtain main execution context for the main loop.
+ * Get command from hash map by variable name.
+ *
+ * @param is interpreter state.
+ * @param name name of the variable to get command by
+ * @return the command, if it is found, or NULL.
+ */
+const struct TALER_TESTING_Command *
+TALER_TESTING_interpreter_get_command (struct TALER_TESTING_Interpreter *is,
+ const char *name);
+
+
+/**
+ * Update the last request time of the current command
+ * to the current time.
+ *
+ * @param[in,out] is interpreter state where to show
+ * that we are doing something
+ */
+void
+TALER_TESTING_touch_cmd (struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Increment the 'num_tries' counter for the current
+ * command.
+ *
+ * @param[in,out] is interpreter state where to
+ * increment the counter
+ */
+void
+TALER_TESTING_inc_tries (struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Obtain CURL context for the main loop.
*
* @param is interpreter state.
* @return CURL execution context.
@@ -547,6 +399,7 @@ TALER_TESTING_interpreter_lookup_command (struct TALER_TESTING_Interpreter *is,
struct GNUNET_CURL_Context *
TALER_TESTING_interpreter_get_context (struct TALER_TESTING_Interpreter *is);
+
/**
* Obtain label of the command being now run.
*
@@ -559,15 +412,6 @@ TALER_TESTING_interpreter_get_current_label (
/**
- * Get connection handle to the fakebank.
- *
- * @param is interpreter state.
- * @return the handle.
- */
-struct TALER_FAKEBANK_Handle *
-TALER_TESTING_interpreter_get_fakebank (struct TALER_TESTING_Interpreter *is);
-
-/**
* Current command is done, run the next one.
*
* @param is interpreter state.
@@ -583,13 +427,20 @@ TALER_TESTING_interpreter_next (struct TALER_TESTING_Interpreter *is);
void
TALER_TESTING_interpreter_fail (struct TALER_TESTING_Interpreter *is);
+
/**
- * Create command array terminator.
+ * Make the instruction pointer point to @a target_label
+ * only if @a counter is greater than zero.
*
- * @return a end-command.
+ * @param label command label
+ * @param target_label label of the new instruction pointer's destination after the jump;
+ * must be before the current instruction
+ * @param counter counts how many times the rewinding is to happen.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_end ();
+TALER_TESTING_cmd_rewind_ip (const char *label,
+ const char *target_label,
+ unsigned int counter);
/**
@@ -632,20 +483,6 @@ TALER_TESTING_run2 (struct TALER_TESTING_Interpreter *is,
/**
- * First launch the fakebank, then schedule the first CMD
- * in the array of all the CMDs to execute.
- *
- * @param is interpreter state.
- * @param commands array of all the commands to execute.
- * @param bank_url base URL of the fake bank.
- */
-void
-TALER_TESTING_run_with_fakebank (struct TALER_TESTING_Interpreter *is,
- struct TALER_TESTING_Command *commands,
- const char *bank_url);
-
-
-/**
* The function that contains the array of all the CMDs to run,
* which is then on charge to call some fashion of
* TALER_TESTING_run*. In all the test cases, this function is
@@ -660,202 +497,212 @@ typedef void
/**
- * Install signal handlers plus schedules the main wrapper
- * around the "run" method.
+ * Run Taler testing loop. Starts the GNUnet SCHEDULER (event loop).
*
- * @param main_cb the "run" method which coontains all the
- * commands.
- * @param main_cb_cls a closure for "run", typically NULL.
- * @param cfg configuration to use
- * @param exchanged exchange process handle: will be put in the
- * state as some commands - e.g. revoke - need to send
- * signal to it, for example to let it know to reload the
- * key state.. if NULL, the interpreter will run without
- * trying to connect to the exchange first.
- * @param exchange_connect GNUNET_YES if the test should connect
- * to the exchange, GNUNET_NO otherwise
- * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise.
- * non-GNUNET_OK codes are #GNUNET_SYSERR most of the
- * times.
+ * @param main_cb main function to run
+ * @param main_cb_cls closure for @a main_cb
*/
-int
-TALER_TESTING_setup (TALER_TESTING_Main main_cb,
- void *main_cb_cls,
- const struct GNUNET_CONFIGURATION_Handle *cfg,
- struct GNUNET_OS_Process *exchanged,
- int exchange_connect);
+enum GNUNET_GenericReturnValue
+TALER_TESTING_loop (TALER_TESTING_Main main_cb,
+ void *main_cb_cls);
/**
- * Install signal handlers plus schedules the main wrapper
- * around the "run" method.
+ * Convenience function to run a test.
*
- * @param main_cb the "run" method which contains all the
- * commands.
- * @param main_cb_cls a closure for "run", typically NULL.
- * @param config_filename configuration filename.
- * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise.
- * non-GNUNET_OK codes are #GNUNET_SYSERR most of the
- * times.
+ * @param argv command-line arguments given
+ * @param loglevel log level to use
+ * @param cfg_file configuration file to use
+ * @param exchange_account_section configuration section
+ * with exchange bank account to use
+ * @param bs bank system to use
+ * @param[in,out] cred global credentials to initialize
+ * @param main_cb main test function to run
+ * @param main_cb_cls closure for @a main_cb
+ * @return 0 on success, 77 on setup trouble, non-zero process status code otherwise
*/
int
-TALER_TESTING_auditor_setup (TALER_TESTING_Main main_cb,
- void *main_cb_cls,
- const char *config_filename);
+TALER_TESTING_main (char *const *argv,
+ const char *loglevel,
+ const char *cfg_file,
+ const char *exchange_account_section,
+ enum TALER_TESTING_BankSystem bs,
+ struct TALER_TESTING_Credentials *cred,
+ TALER_TESTING_Main main_cb,
+ void *main_cb_cls);
/**
- * Closure for #TALER_TESTING_setup_with_exchange_cfg().
+ * Callback over commands of an interpreter.
+ *
+ * @param cls closure
+ * @param cmd a command to process
*/
-struct TALER_TESTING_SetupContext
-{
- /**
- * Main function of the test to run.
- */
- TALER_TESTING_Main main_cb;
+typedef void
+(*TALER_TESTING_CommandIterator)(
+ void *cls,
+ const struct TALER_TESTING_Command *cmd);
- /**
- * Closure for @e main_cb.
- */
- void *main_cb_cls;
- /**
- * Name of the configuration file.
- */
- const char *config_filename;
-};
+/**
+ * Iterates over all of the top-level commands of an
+ * interpreter.
+ *
+ * @param[in] is interpreter to iterate over
+ * @param asc true in execution order, false for reverse execution order
+ * @param cb function to call on each command
+ * @param cb_cls closure for cb
+ */
+void
+TALER_TESTING_iterate (struct TALER_TESTING_Interpreter *is,
+ bool asc,
+ TALER_TESTING_CommandIterator cb,
+ void *cb_cls);
/**
- * Initialize scheduler loop and curl context for the test case
- * including starting and stopping the exchange using the given
- * configuration file.
+ * Look for substring in a programs' name.
*
- * @param cls must be a `struct TALER_TESTING_SetupContext *`
- * @param cfg configuration to use.
- * @return #GNUNET_OK if no errors occurred.
+ * @param prog program's name to look into
+ * @param marker chunk to find in @a prog
+ * @return true if @a marker is in @a prog
*/
-int
-TALER_TESTING_setup_with_exchange_cfg (
- void *cls,
- const struct GNUNET_CONFIGURATION_Handle *cfg);
+bool
+TALER_TESTING_has_in_name (const char *prog,
+ const char *marker);
/**
- * Initialize scheduler loop and curl context for the test case
- * including starting and stopping the exchange using the given
- * configuration file.
+ * Wait for an HTTPD service to have started. Waits for at
+ * most 10s, after that returns 77 to indicate an error.
*
- * @param main_cb main method.
- * @param main_cb_cls main method closure.
- * @param config_file configuration file name. Is is used
- * by both this function and the exchange itself. In the
- * first case it gives out the exchange port number and
- * the exchange base URL so as to check whether the port
- * is available and the exchange responds when requested
- * at its base URL.
- * @return #GNUNET_OK if no errors occurred.
+ * @param base_url what URL should we expect the exchange
+ * to be running at
+ * @return 0 on success
*/
int
-TALER_TESTING_setup_with_exchange (TALER_TESTING_Main main_cb,
- void *main_cb_cls,
- const char *config_file);
+TALER_TESTING_wait_httpd_ready (const char *base_url);
/**
- * Initialize scheduler loop and curl context for the test case
- * including starting and stopping the auditor and exchange using
- * the given configuration file.
+ * Parse reference to a coin.
*
- * @param cls must be a `struct TALER_TESTING_SetupContext *`
- * @param cfg configuration to use.
- * @return #GNUNET_OK if no errors occurred.
+ * @param coin_reference of format $LABEL['#' $INDEX]?
+ * @param[out] cref where we return a copy of $LABEL
+ * @param[out] idx where we set $INDEX
+ * @return #GNUNET_SYSERR if $INDEX is present but not numeric
*/
-int
-TALER_TESTING_setup_with_auditor_and_exchange_cfg (
- void *cls,
- const struct GNUNET_CONFIGURATION_Handle *cfg);
+enum GNUNET_GenericReturnValue
+TALER_TESTING_parse_coin_reference (
+ const char *coin_reference,
+ char **cref,
+ unsigned int *idx);
+
+
+/* ************** Specific interpreter commands ************ */
/**
- * Initialize scheduler loop and curl context for the test case
- * including starting and stopping the auditor and exchange using
- * the given configuration file.
+ * Create command array terminator.
*
- * @param main_cb main method.
- * @param main_cb_cls main method closure.
- * @param config_file configuration file name. Is is used
- * by both this function and the exchange itself. In the
- * first case it gives out the exchange port number and
- * the exchange base URL so as to check whether the port
- * is available and the exchange responds when requested
- * at its base URL.
- * @return #GNUNET_OK if no errors occurred.
+ * @return a end-command.
*/
-int
-TALER_TESTING_setup_with_auditor_and_exchange (TALER_TESTING_Main main_cb,
- void *main_cb_cls,
- const char *config_file);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_end (void);
/**
- * Start the (Python) bank process. Assume the port
- * is available and the database is clean. Use the "prepare
- * bank" function to do such tasks.
+ * Set variable to command as side-effect of
+ * running a command.
*
- * @param config_filename configuration filename.
- * @param bank_url base URL of the bank, used by `wget' to check
- * that the bank was started right.
+ * @param name name of the variable to set
+ * @param cmd command to set to variable when run
+ * @return modified command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_var (const char *name,
+ struct TALER_TESTING_Command cmd);
+
+
+/**
+ * Launch GNU Taler setup.
*
- * @return the process, or NULL if the process could not
- * be started.
+ * @param label command label.
+ * @param config_file configuration file to use
+ * @param ... NULL-terminated (const char *) arguments to pass to taler-benchmark-setup.sh
+ * @return the command.
*/
-struct GNUNET_OS_Process *
-TALER_TESTING_run_bank (const char *config_filename,
- const char *bank_url);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_system_start (
+ const char *label,
+ const char *config_file,
+ ...);
+
/**
- * Runs the Fakebank by guessing / extracting the portnumber
- * from the base URL.
+ * Connects to the exchange.
*
- * @param bank_url bank's base URL.
- * @param currency currency the bank uses
- * @return the fakebank process handle, or NULL if any
- * error occurs.
+ * @param label command label
+ * @param cfg configuration to use
+ * @param last_keys_ref reference to command with prior /keys response, NULL for none
+ * @param wait_for_keys block until we got /keys
+ * @param load_private_key obtain private key from file indicated in @a cfg
+ * @return the command.
*/
-struct TALER_FAKEBANK_Handle *
-TALER_TESTING_run_fakebank (const char *bank_url,
- const char *currency);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_exchange (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *last_keys_ref,
+ bool wait_for_keys,
+ bool load_private_key);
/**
- * Prepare the bank execution. Check if the port is available
- * and reset database.
+ * Connects to the auditor.
*
- * @param config_filename configuration file name.
- * @param reset_db should we reset the bank's database
- * @param[out] bc set to the bank's configuration data
- * @return #GNUNET_OK on success
+ * @param label command label
+ * @param cfg configuration to use
+ * @param load_auditor_keys obtain auditor keys from file indicated in @a cfg
+ * @return the command.
*/
-int
-TALER_TESTING_prepare_bank (const char *config_filename,
- int reset_db,
- const char *config_section,
- struct TALER_TESTING_BankConfiguration *bc);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_auditor (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ bool load_auditor_keys);
/**
- * Look for substring in a programs' name.
+ * Runs the Fakebank in-process by guessing / extracting the portnumber
+ * from the base URL.
*
- * @param prog program's name to look into
- * @param marker chunk to find in @a prog
+ * @param label command label
+ * @param cfg configuration to use
+ * @param exchange_account_section configuration section
+ * to use to determine bank account of the exchange
+ * @return the command.
*/
-int
-TALER_TESTING_has_in_name (const char *prog,
- const char *marker);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_run_fakebank (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *exchange_account_section);
-/* ************** Specific interpreter commands ************ */
+/**
+ * Command to modify authorization header used in the CURL context.
+ * This will destroy the existing CURL context and create a fresh
+ * one. The command will fail (badly) if the existing CURL context
+ * still has active HTTP requests associated with it.
+ *
+ * @param label command label.
+ * @param auth_token auth token to use henceforth, can be NULL
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_authorization (const char *label,
+ const char *auth_token);
/**
@@ -899,10 +746,11 @@ TALER_TESTING_cmd_bank_debits (const char *label,
/**
* Create transfer command.
*
- * @param label command label.
- * @param amount amount to transfer.
+ * @param label command label
+ * @param amount amount to transfer
* @param auth authentication data to use
- * @param payto_credit_account which account receives money.
+ * @param payto_debit_account which account to withdraw money from
+ * @param payto_credit_account which account receives money
* @param wtid wire transfer identifier to use
* @param exchange_base_url exchange URL to use
* @return the command.
@@ -918,6 +766,17 @@ TALER_TESTING_cmd_transfer (const char *label,
/**
+ * Modify a transfer command to enable retries when the reserve is not yet
+ * full or we get other transient errors from the bank.
+ *
+ * @param cmd a fakebank transfer command
+ * @return the command with retries enabled
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_transfer_retry (struct TALER_TESTING_Command cmd);
+
+
+/**
* Make the "exec-auditor" CMD.
*
* @param label command label.
@@ -945,24 +804,20 @@ TALER_TESTING_cmd_exec_auditor_dbinit (const char *label,
* Create a "deposit-confirmation" command.
*
* @param label command label.
- * @param auditor auditor connection.
* @param deposit_reference reference to any operation that can
* provide a coin.
- * @param coin_index if @a deposit_reference offers an array of
- * coins, this parameter selects which one in that array.
- * This value is currently ignored, as only one-coin
- * deposits are implemented.
+ * @param num_coins number of coins expected in the batch deposit
* @param amount_without_fee deposited amount without the fee
* @param expected_response_code expected HTTP response code.
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_deposit_confirmation (const char *label,
- struct TALER_AUDITOR_Handle *auditor,
- const char *deposit_reference,
- unsigned int coin_index,
- const char *amount_without_fee,
- unsigned int expected_response_code);
+TALER_TESTING_cmd_deposit_confirmation (
+ const char *label,
+ const char *deposit_reference,
+ unsigned int num_coins,
+ const char *amount_without_fee,
+ unsigned int expected_response_code);
/**
@@ -974,53 +829,10 @@ TALER_TESTING_cmd_deposit_confirmation (const char *label,
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_deposit_confirmation_with_retry (
- struct TALER_TESTING_Command
- cmd);
-
-
-/**
- * Create a "list exchanges" command.
- *
- * @param label command label.
- * @param auditor auditor connection.
- * @param expected_response_code expected HTTP response code.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_exchanges (const char *label,
- struct TALER_AUDITOR_Handle *auditor,
- unsigned int expected_response_code);
+ struct TALER_TESTING_Command cmd);
/**
- * Create a "list exchanges" command and check whether
- * a particular exchange belongs to the returned bundle.
- *
- * @param label command label.
- * @param expected_response_code expected HTTP response code.
- * @param exchange_url URL of the exchange supposed to
- * be included in the response.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_exchanges_with_url (const char *label,
- unsigned int expected_response_code,
- const char *exchange_url);
-
-/**
- * Modify an exchanges command to enable retries when we get
- * transient errors from the auditor.
- *
- * @param cmd a deposit confirmation command
- * @return the command with retries enabled
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_exchanges_with_retry (struct TALER_TESTING_Command cmd);
-
-
-/* ***** Commands ONLY for testing (/admin-API) **** */
-
-/**
* Create /admin/add-incoming command.
*
* @param label command label.
@@ -1048,7 +860,8 @@ TALER_TESTING_cmd_admin_add_incoming (
* @param payto_debit_account which account sends money.
* @param auth authentication data
* @param ref reference to a command that can offer a reserve
- * private key.
+ * private key or public key.
+ * @param http_status expected HTTP status
* @return the command.
*/
struct TALER_TESTING_Command
@@ -1057,35 +870,8 @@ TALER_TESTING_cmd_admin_add_incoming_with_ref (
const char *amount,
const struct TALER_BANK_AuthenticationData *auth,
const char *payto_debit_account,
- const char *ref);
-
-
-/**
- * Create "fakebank transfer" CMD, letting the caller specifying
- * the merchant instance. This version is useful when a tip
- * reserve should be topped up, in fact the interpreter will need
- * the "tipping instance" in order to get the instance public key
- * and make a wire transfer subject out of it.
- *
- * @param label command label.
- * @param amount amount to transfer.
- * @param payto_debit_account which account sends money.
- * @param auth authentication data
- * @param instance the instance that runs the tipping. Under this
- * instance, the configuration file will provide the private
- * key of the tipping reserve. This data will then used to
- * construct the wire transfer subject line.
- * @param config_filename configuration file to use.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_admin_add_incoming_with_instance (
- const char *label,
- const char *amount,
- const struct TALER_BANK_AuthenticationData *auth,
- const char *payto_debit_account,
- const char *instance,
- const char *config_filename);
+ const char *ref,
+ unsigned int http_status);
/**
@@ -1105,15 +891,65 @@ TALER_TESTING_cmd_admin_add_incoming_retry (struct TALER_TESTING_Command cmd);
*
* @param label command label.
* @param config_filename configuration filename.
- *
* @return the command.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_exec_wirewatch (const char *label,
const char *config_filename);
+
+/**
+ * Make a "wirewatch" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @param account_section section to run wirewatch against
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_wirewatch2 (const char *label,
+ const char *config_filename,
+ const char *account_section);
+
+
+/**
+ * Request URL via "wget".
+ *
+ * @param label command label.
+ * @param url URL to fetch
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_wget (const char *label,
+ const char *url);
+
+
+/**
+ * Make a "expire" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_expire (const char *label,
+ const char *config_filename);
+
+
+/**
+ * Make a "router" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_router (const char *label,
+ const char *config_filename);
+
+
/**
- * Make a "aggregator" CMD.
+ * Run a "taler-exchange-aggregator" CMD.
*
* @param label command label.
* @param config_filename configuration file for the
@@ -1126,6 +962,32 @@ TALER_TESTING_cmd_exec_aggregator (const char *label,
/**
+ * Run a "taler-auditor-offline" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration file for the
+ * aggregator to use.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_auditor_offline (const char *label,
+ const char *config_filename);
+
+
+/**
+ * Make a "aggregator" CMD and do not disable KYC checks.
+ *
+ * @param label command label.
+ * @param config_filename configuration file for the
+ * aggregator to use.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_aggregator_with_kyc (const char *label,
+ const char *config_filename);
+
+
+/**
* Make a "closer" CMD. Note that it is right now not supported to run the
* closer to close multiple reserves in combination with a subsequent reserve
* status call, as we cannot generate the traits necessary for multiple closed
@@ -1163,81 +1025,145 @@ TALER_TESTING_cmd_exec_transfer (const char *label,
/**
- * Make the "keyup" CMD.
+ * Create a withdraw command, letting the caller specify
+ * the desired amount as string.
*
* @param label command label.
- * @param config_filename configuration filename.
- * @return the command.
+ * @param reserve_reference command providing us with a reserve to withdraw from
+ * @param amount how much we withdraw.
+ * @param age if > 0, age restriction applies
+ * @param expected_response_code which HTTP response code
+ * we expect from the exchange.
+ * @return the withdraw command to be executed by the interpreter.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_exec_keyup (const char *label,
- const char *config_filename);
+TALER_TESTING_cmd_withdraw_amount (const char *label,
+ const char *reserve_reference,
+ const char *amount,
+ uint8_t age,
+ unsigned int expected_response_code);
/**
- * Make the "keyup" CMD, with "--timestamp" option.
+ * Create a batch withdraw command, letting the caller specify the type of
+ * conflict between the coins and the desired amounts as string.
+ *
+ * Takes a variable, non-empty list of the denomination amounts via VARARGS,
+ * similar to #TALER_TESTING_cmd_withdraw_amount(), just using a batch
+ * withdraw.
*
* @param label command label.
- * @param config_filename configuration filename.
- * @param now Unix timestamp representing the fake "now".
- * @return the command.
+ * @param reserve_reference command providing us with a reserve to withdraw from
+ * @param conflict if true, enforce a conflict (same priv key, different denom and age commiment)
+ * @param age if > 0, age restriction applies (same for all coins)
+ * @param expected_response_code which HTTP response code
+ * we expect from the exchange.
+ * @param amount how much we withdraw for the first coin
+ * @param ... NULL-terminated list of additional amounts to withdraw (one per coin)
+ * @return the withdraw command to be executed by the interpreter.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_exec_keyup_with_now (const char *label,
- const char *config_filename,
- struct GNUNET_TIME_Absolute now);
-
+TALER_TESTING_cmd_batch_withdraw_with_conflict (
+ const char *label,
+ const char *reserve_reference,
+ bool conflict,
+ uint8_t age,
+ unsigned int expected_response_code,
+ const char *amount,
+ ...);
/**
- * Make a "check keys" command. This type of command
- * checks whether the number of denomination keys from
- * @a exchange matches @a num_denom_keys.
+ * Create a batch withdraw command, letting the caller specify
+ * the desired amounts as string. Takes a variable, non-empty
+ * list of the denomination amounts via VARARGS, similar to
+ * #TALER_TESTING_cmd_withdraw_amount(), just using a batch withdraw.
+ * The coins are generated without a conflict (different private keys).
*
- * @param label command label
- * @param generation when this command is run, exactly @a
- * generation /keys downloads took place. If the number
- * of downloads is less than @a generation, the logic will
- * first make sure that @a generation downloads are done,
- * and _then_ execute the rest of the command.
- * @param num_denom_keys expected number of denomination keys.
- * @param now timestamp to use when fetching keys
- * @return the command.
+ * @param label command label.
+ * @param reserve_reference command providing us with a reserve to withdraw from
+ * @param age if > 0, age restriction applies (same for all coins)
+ * @param expected_response_code which HTTP response code
+ * we expect from the exchange.
+ * @param amount how much we withdraw for the first coin
+ * @param ... NULL-terminated list of additional amounts to withdraw (one per coin)
+ * @return the withdraw command to be executed by the interpreter.
+ */
+#define TALER_TESTING_cmd_batch_withdraw(label, \
+ reserve_reference, \
+ age, \
+ expected_response_code, \
+ amount, \
+ ...) \
+ TALER_TESTING_cmd_batch_withdraw_with_conflict ( \
+ (label), \
+ (reserve_reference), \
+ false, \
+ (age), \
+ (expected_response_code), \
+ (amount), \
+ __VA_ARGS__)
+
+/**
+ * Create an age-withdraw command, letting the caller specify
+ * the maximum agend and desired amounts as string. Takes a variable,
+ * non-empty list of the denomination amounts via VARARGS, similar to
+ * #TALER_TESTING_cmd_withdraw_amount(), just using a batch withdraw.
+ *
+ * @param label command label.
+ * @param reserve_reference command providing us with a reserve to withdraw from
+ * @param max_age maximum allowed age, same for each coin
+ * @param expected_response_code which HTTP response code
+ * we expect from the exchange.
+ * @param amount how much we withdraw for the first coin
+ * @param ... NULL-terminated list of additional amounts to withdraw (one per coin)
+ * @return the withdraw command to be executed by the interpreter.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys_with_now (const char *label,
- unsigned int generation,
- unsigned int num_denom_keys,
- struct GNUNET_TIME_Absolute now);
-
+TALER_TESTING_cmd_age_withdraw (const char *label,
+ const char *reserve_reference,
+ uint8_t max_age,
+ unsigned int expected_response_code,
+ const char *amount,
+ ...);
/**
- * Make a "auditor sign" CMD.
+ * Create a "age-withdraw reveal" command.
*
- * @param label command label
- * @param config_filename configuration filename
+ * @param label command label.
+ * @param age_withdraw_reference reference to a "age-withdraw" command.
+ * @param expected_response_code expected HTTP response code.
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_exec_auditor_sign (const char *label,
- const char *config_filename);
-
+TALER_TESTING_cmd_age_withdraw_reveal (
+ const char *label,
+ const char *age_withdraw_reference,
+ unsigned int expected_response_code);
/**
* Create a withdraw command, letting the caller specify
- * the desired amount as string.
+ * the desired amount as string and also re-using an existing
+ * coin private key in the process (violating the specification,
+ * which will result in an error when spending the coin!).
*
* @param label command label.
* @param reserve_reference command providing us with a reserve to withdraw from
* @param amount how much we withdraw.
+ * @param age if > 0, age restriction applies.
+ * @param coin_ref reference to (withdraw/reveal) command of a coin
+ * from which we should reuse the private key
* @param expected_response_code which HTTP response code
* we expect from the exchange.
* @return the withdraw command to be executed by the interpreter.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_withdraw_amount (const char *label,
- const char *reserve_reference,
- const char *amount,
- unsigned int expected_response_code);
+TALER_TESTING_cmd_withdraw_amount_reuse_key (
+ const char *label,
+ const char *reserve_reference,
+ const char *amount,
+ uint8_t age,
+ const char *coin_ref,
+ unsigned int expected_response_code);
/**
@@ -1272,37 +1198,155 @@ TALER_TESTING_cmd_withdraw_with_retry (struct TALER_TESTING_Command cmd);
/**
- * Create a "wire" command.
+ * Create a GET "reserves" command.
*
* @param label the command label.
- * @param expected_method which wire-transfer method is expected
- * to be offered by the exchange.
- * @param expected_fee the fee the exchange should charge.
- * @param expected_response_code the HTTP response the exchange
- * should return.
+ * @param reserve_reference reference to the reserve to check.
+ * @param expected_balance expected balance for the reserve.
+ * @param expected_response_code expected HTTP response code.
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_wire (const char *label,
- const char *expected_method,
- const char *expected_fee,
- unsigned int expected_response_code);
+TALER_TESTING_cmd_status (const char *label,
+ const char *reserve_reference,
+ const char *expected_balance,
+ unsigned int expected_response_code);
/**
- * Create a GET "reserves" command.
+ * Create a GET "reserves" command with a @a timeout.
*
* @param label the command label.
* @param reserve_reference reference to the reserve to check.
* @param expected_balance expected balance for the reserve.
+ * @param timeout how long to long-poll for the reserve to exist.
* @param expected_response_code expected HTTP response code.
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_status (const char *label,
- const char *reserve_reference,
- const char *expected_balance,
- unsigned int expected_response_code);
+TALER_TESTING_cmd_reserve_poll (const char *label,
+ const char *reserve_reference,
+ const char *expected_balance,
+ struct GNUNET_TIME_Relative timeout,
+ unsigned int expected_response_code);
+
+
+/**
+ * Wait for #TALER_TESTING_cmd_reserve_poll() to finish.
+ * Fail if it did not conclude by the timeout.
+ *
+ * @param label our label
+ * @param timeout how long to give the long poll to finish
+ * @param poll_reference reference to a #TALER_TESTING_cmd_reserve_poll() command
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_poll_finish (const char *label,
+ struct GNUNET_TIME_Relative timeout,
+ const char *poll_reference);
+
+
+/**
+ * Create a GET "/reserves/$RID/history" command.
+ *
+ * @param label the command label.
+ * @param reserve_reference reference to the reserve to check.
+ * @param expected_balance expected balance for the reserve.
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_history (const char *label,
+ const char *reserve_reference,
+ const char *expected_balance,
+ unsigned int expected_response_code);
+
+
+/**
+ * Create a GET "/coins/$COIN_PUB/history" command.
+ *
+ * @param label the command label.
+ * @param coin_reference reference to the coin to check.
+ * @param expected_balance expected balance for the coin.
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_coin_history (const char *label,
+ const char *coin_reference,
+ const char *expected_balance,
+ unsigned int expected_response_code);
+
+
+/**
+ * Create a POST "/reserves/$RID/open" command.
+ *
+ * @param label the command label.
+ * @param reserve_reference reference to the reserve to open.
+ * @param reserve_pay amount to pay from the reserve balance
+ * @param expiration_time how long into the future should the reserve remain open
+ * @param min_purses minimum number of purses to allow
+ * @param expected_response_code expected HTTP response code.
+ * @param ... NULL terminated list of pairs of coin references and amounts
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_open (const char *label,
+ const char *reserve_reference,
+ const char *reserve_pay,
+ struct GNUNET_TIME_Relative expiration_time,
+ uint32_t min_purses,
+ unsigned int expected_response_code,
+ ...);
+
+
+/**
+ * Create a GET "/reserves/$RID/attest" command.
+ *
+ * @param label the command label.
+ * @param reserve_reference reference to the reserve to get attestable attributes of.
+ * @param expected_response_code expected HTTP response code.
+ * @param ... NULL-terminated list of attributes expected
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_get_attestable (const char *label,
+ const char *reserve_reference,
+ unsigned int expected_response_code,
+ ...);
+
+
+/**
+ * Create a POST "/reserves/$RID/attest" command.
+ *
+ * @param label the command label.
+ * @param reserve_reference reference to the reserve to get attests for
+ * @param expected_response_code expected HTTP response code.
+ * @param ... NULL-terminated list of attributes that should be attested
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_attest (const char *label,
+ const char *reserve_reference,
+ unsigned int expected_response_code,
+ ...);
+
+
+/**
+ * Create a POST "/reserves/$RID/close" command.
+ *
+ * @param label the command label.
+ * @param reserve_reference reference to the reserve to close.
+ * @param target_account where to wire funds remaining, can be NULL
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_close (const char *label,
+ const char *reserve_reference,
+ const char *target_account,
+ unsigned int expected_response_code);
+
/**
* Create a "deposit" command.
@@ -1395,6 +1439,31 @@ TALER_TESTING_cmd_deposit_replay (const char *label,
/**
+ * Create a "batch deposit" command.
+ *
+ * @param label command label.
+ * @param target_account_payto target account for the "deposit"
+ * request.
+ * @param contract_terms contract terms to be signed over by the
+ * coin.
+ * @param refund_deadline refund deadline, zero means 'no refunds'.
+ * @param expected_response_code expected HTTP response code.
+ * @param ... NULL-terminated list with an even number of
+ * strings that alternate referring to coins
+ * (possibly with index using label#index notation)
+ * and the amount of that coin to deposit
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_batch_deposit (const char *label,
+ const char *target_account_payto,
+ const char *contract_terms,
+ struct GNUNET_TIME_Relative refund_deadline,
+ unsigned int expected_response_code,
+ ...);
+
+
+/**
* Create a "refresh melt" command.
*
* @param label command label.
@@ -1499,7 +1568,6 @@ TALER_TESTING_cmd_refresh_link_with_retry (struct TALER_TESTING_Command cmd);
* @param bank_transfer_reference reference to a command that
* can offer a WTID so as to check that against what WTID
* the tracked operation has. Set as NULL if not needed.
- *
* @return the command.
*/
struct TALER_TESTING_Command
@@ -1520,15 +1588,12 @@ TALER_TESTING_cmd_track_transaction (const char *label,
* a wtid. If NULL is given, then a all zeroed WTID is
* used that will at 99.9999% probability NOT match any
* existing WTID known to the exchange.
- * @param index index number of the WTID to track, in case there
- * are multiple on offer.
* @param expected_response_code expected HTTP response code.
* @return the command.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_track_transfer_empty (const char *label,
const char *wtid_reference,
- unsigned int index,
unsigned int expected_response_code);
@@ -1539,8 +1604,6 @@ TALER_TESTING_cmd_track_transfer_empty (const char *label,
* @param label the command label.
* @param wtid_reference reference to any command which can provide
* a wtid. Will be the one tracked.
- * @param index in case there are multiple WTID offered, this
- * parameter selects a particular one.
* @param expected_response_code expected HTTP response code.
* @param expected_total_amount how much money we expect being moved
* with this wire-transfer.
@@ -1550,11 +1613,11 @@ TALER_TESTING_cmd_track_transfer_empty (const char *label,
struct TALER_TESTING_Command
TALER_TESTING_cmd_track_transfer (const char *label,
const char *wtid_reference,
- unsigned int index,
unsigned int expected_response_code,
const char *expected_total_amount,
const char *expected_wire_fee);
+
/**
* Make a "bank check" CMD. It checks whether a particular wire transfer from
* the exchange (debit) has been made or not.
@@ -1629,20 +1692,17 @@ TALER_TESTING_cmd_check_bank_empty (const char *label);
* @param label command label.
* @param expected_response_code expected HTTP status code.
* @param refund_amount the amount to ask a refund for.
- * @param refund_fee expected refund fee.
* @param coin_reference reference to a command that can
* provide a coin to be refunded.
* @param refund_transaction_id transaction id to use
* in the request.
- *
* @return the command.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_refund_with_id (const char *label,
unsigned int expected_response_code,
const char *refund_amount,
- const char *refund_fee,
- const char *deposit_reference,
+ const char *coin_reference,
uint64_t refund_transaction_id);
@@ -1652,18 +1712,15 @@ TALER_TESTING_cmd_refund_with_id (const char *label,
* @param label command label.
* @param expected_response_code expected HTTP status code.
* @param refund_amount the amount to ask a refund for.
- * @param refund_fee expected refund fee.
* @param coin_reference reference to a command that can
* provide a coin to be refunded.
- *
* @return the command.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_refund (const char *label,
unsigned int expected_response_code,
const char *refund_amount,
- const char *refund_fee,
- const char *deposit_reference);
+ const char *coin_reference);
/**
@@ -1675,7 +1732,6 @@ TALER_TESTING_cmd_refund (const char *label,
* offers a coin and reserve private key. May specify
* the index of the coin using "$LABEL#$INDEX" syntax.
* Here, $INDEX must be a non-negative number.
- * @param melt_reference NULL if coin was not refreshed, otherwise label of the melt operation
* @param amount how much do we expect to recoup, NULL for nothing
* @return the command.
*/
@@ -1683,11 +1739,31 @@ struct TALER_TESTING_Command
TALER_TESTING_cmd_recoup (const char *label,
unsigned int expected_response_code,
const char *coin_reference,
- const char *melt_reference,
const char *amount);
/**
+ * Make a "recoup-refresh" command.
+ *
+ * @param label the command label
+ * @param expected_response_code expected HTTP status code
+ * @param coin_reference reference to any command which
+ * offers a coin and reserve private key. May specify
+ * the index of the coin using "$LABEL#$INDEX" syntax.
+ * Here, $INDEX must be a non-negative number.
+ * @param melt_reference label of the melt operation
+ * @param amount how much do we expect to recoup, NULL for nothing
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_recoup_refresh (const char *label,
+ unsigned int expected_response_code,
+ const char *coin_reference,
+ const char *melt_reference,
+ const char *amount);
+
+
+/**
* Make a "revoke" command.
*
* @param label the command label.
@@ -1743,71 +1819,6 @@ struct TALER_TESTING_Command
TALER_TESTING_cmd_wait_service (const char *label,
const char *url);
-
-/**
- * Make a "check keys" command. This type of command
- * checks whether the number of denomination keys from
- * @a exchange matches @a num_denom_keys.
- *
- * @param label command label
- * @param generation how many /keys responses are expected to
- * have been returned when this CMD will be run.
- * @param num_denom_keys expected number of denomination keys.
- *
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys (const char *label,
- unsigned int generation,
- unsigned int num_denom_keys);
-
-
-/**
- * Make a "check keys" command that forcedly does NOT cherry pick;
- * just redownload the whole /keys. Then checks whether the number
- * of denomination keys from @a exchange matches @a num_denom_keys.
- *
- * @param label command label
- * @param generation when this command is run, exactly @a
- * generation /keys downloads took place. If the number
- * of downloads is less than @a generation, the logic will
- * first make sure that @a generation downloads are done,
- * and _then_ execute the rest of the command.
- * @param num_denom_keys expected number of denomination keys.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys_pull_all_keys (const char *label,
- unsigned int generation,
- unsigned int num_denom_keys);
-
-
-/**
- * Make a "check keys" command. This type of command
- * checks whether the number of denomination keys from
- * @a exchange matches @a num_denom_keys. Additionally,
- * it lets the user set a last denom issue date to be
- * used in the request for /keys.
- *
- * @param label command label
- * @param generation when this command is run, exactly @a
- * generation /keys downloads took place. If the number
- * of downloads is less than @a generation, the logic will
- * first make sure that @a generation downloads are done,
- * and _then_ execute the rest of the command.
- * @param num_denom_keys expected number of denomination keys.
- * @param last_denom_date date to be set in the "last_denom_issue"
- * URL parameter of /keys.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys_with_last_denom (const char *label,
- unsigned int generation,
- unsigned int num_denom_keys,
- struct GNUNET_TIME_Absolute
- last_denom_date);
-
-
/**
* Create a "batch" command. Such command takes a
* end_CMD-terminated array of CMDs and executed them.
@@ -1830,16 +1841,21 @@ TALER_TESTING_cmd_batch (const char *label,
*
* @return false if not, true if it is a batch command
*/
-int
+bool
TALER_TESTING_cmd_is_batch (const struct TALER_TESTING_Command *cmd);
+
/**
* Advance internal pointer to next command.
*
* @param is interpreter state.
+ * @param[in,out] cls closure of the batch
+ * @return true to advance IP in parent
*/
-void
-TALER_TESTING_cmd_batch_next (struct TALER_TESTING_Interpreter *is);
+bool
+TALER_TESTING_cmd_batch_next (struct TALER_TESTING_Interpreter *is,
+ void *cls);
+
/**
* Obtain what command the batch is at.
@@ -1849,37 +1865,26 @@ TALER_TESTING_cmd_batch_next (struct TALER_TESTING_Interpreter *is);
struct TALER_TESTING_Command *
TALER_TESTING_cmd_batch_get_current (const struct TALER_TESTING_Command *cmd);
-/**
- * Make a serialize-keys CMD.
- *
- * @param label CMD label
- * @return the CMD.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_serialize_keys (const char *label);
-
/**
- * Make a connect-with-state CMD. This command
- * will use a serialized key state to reconnect
- * to the exchange.
+ * Set what command the batch should be at.
*
- * @param label command label
- * @param state_reference label of a CMD offering
- * a serialized key state.
- * @return the CMD.
+ * @param cmd current batch command
+ * @param new_ip where to move the IP
*/
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_connect_with_state (const char *label,
- const char *state_reference);
+void
+TALER_TESTING_cmd_batch_set_current (const struct TALER_TESTING_Command *cmd,
+ unsigned int new_ip);
+
/**
* Make the "insert-deposit" CMD.
*
* @param label command label.
- * @param dbc collects plugin and session handles
+ * @param db_cfg configuration to talk to the DB
* @param merchant_name Human-readable name of the merchant.
* @param merchant_account merchant's account name (NOT a payto:// URI)
+ * @param exchange_timestamp when did the exchange receive the deposit
* @param wire_deadline point in time where the aggregator should have
* wired money to the merchant.
* @param amount_with_fee amount to deposit (inclusive of deposit fee)
@@ -1887,14 +1892,15 @@ TALER_TESTING_cmd_connect_with_state (const char *label,
* @return the command.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_insert_deposit (const char *label,
- const struct
- TALER_TESTING_DatabaseConnection *dbc,
- const char *merchant_name,
- const char *merchant_account,
- struct GNUNET_TIME_Relative wire_deadline,
- const char *amount_with_fee,
- const char *deposit_fee);
+TALER_TESTING_cmd_insert_deposit (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *db_cfg,
+ const char *merchant_name,
+ const char *merchant_account,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ struct GNUNET_TIME_Relative wire_deadline,
+ const char *amount_with_fee,
+ const char *deposit_fee);
/**
@@ -1913,7 +1919,7 @@ struct TALER_TESTING_Timer
struct GNUNET_TIME_Relative total_duration;
/**
- * Total time spend waiting for the *successful* exeuction
+ * Total time spend waiting for the *successful* execution
* in all commands of this type.
*/
struct GNUNET_TIME_Relative success_latency;
@@ -1940,877 +1946,792 @@ struct TALER_TESTING_Command
TALER_TESTING_cmd_stat (struct TALER_TESTING_Timer *timers);
-/* *** Generic trait logic for implementing traits ********* */
-
-/**
- * A trait.
- */
-struct TALER_TESTING_Trait
-{
- /**
- * Index number associated with the trait. This gives the
- * possibility to have _multiple_ traits on offer under the
- * same name.
- */
- unsigned int index;
-
- /**
- * Trait type, for example "reserve-pub" or "coin-priv".
- */
- const char *trait_name;
-
- /**
- * Pointer to the piece of data to offer.
- */
- const void *ptr;
-};
-
-
-/**
- * "end" trait. Because traits are offered into arrays,
- * this type of trait is used to mark the end of such arrays;
- * useful when iterating over those.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_trait_end (void);
-
-
/**
- * Extract a trait.
+ * Add the auditor to the exchange's list of auditors.
+ * The information about the auditor is taken from the
+ * "[auditor]" section in the configuration file.
*
- * @param traits the array of all the traits.
- * @param[out] ret where to store the result.
- * @param trait type of the trait to extract.
- * @param index index number of the trait to extract.
- * @return #GNUNET_OK when the trait is found.
- */
-int
-TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
- const void **ret,
- const char *trait,
- unsigned int index);
-
-
-/* ****** Specific traits supported by this component ******* */
-
-
-/**
- * Obtain a bank transaction row value from @a cmd.
- *
- * @param cmd command to extract the number from.
- * @param[out] row set to the number coming from @a cmd.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_bank_row (const struct TALER_TESTING_Command *cmd,
- const uint64_t **row);
-
-
-/**
- * Offer bank transaction row trait.
- *
- * @param row number to offer.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_bank_row (const uint64_t *row);
-
-
-/**
- * Offer a reserve private key.
- *
- * @param index reserve priv's index number.
- * @param reserve_priv reserve private key to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_reserve_priv (
- unsigned int index,
- const struct TALER_ReservePrivateKeyP *reserve_priv);
-
-
-/**
- * Obtain a reserve private key from a @a cmd.
- *
- * @param cmd command to extract the reserve priv from.
- * @param index reserve priv's index number.
- * @param[out] reserve_priv set to the reserve priv.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_reserve_priv (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_ReservePrivateKeyP **reserve_priv);
-
-
-/**
- * Offer a reserve public key.
- *
- * @param index reserve pubs's index number.
- * @param reserve_pub reserve public key to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_reserve_pub (
- unsigned int index,
- const struct TALER_ReservePublicKeyP *reserve_pub);
-
-
-/**
- * Obtain a reserve public key from a @a cmd.
- *
- * @param cmd command to extract the reserve pub from.
- * @param index reserve pub's index number.
- * @param[out] reserve_pub set to the reserve pub.
- * @return #GNUNET_OK on success.
+ * @param label command label.
+ * @param expected_http_status expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_reserve_pub (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_ReservePublicKeyP **reserve_pub);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_auditor_add (const char *label,
+ unsigned int expected_http_status,
+ bool bad_sig);
/**
- * Offer a reserve history entry.
+ * Remove the auditor from the exchange's list of auditors.
+ * The information about the auditor is taken from the
+ * "[auditor]" section in the configuration file.
*
- * @param index reserve pubs's index number.
- * @param rh reserve history entry to offer.
- * @return the trait.
+ * @param label command label.
+ * @param expected_http_status expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @return the command
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_reserve_history (
- unsigned int index,
- const struct TALER_EXCHANGE_ReserveHistory *rh);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_auditor_del (const char *label,
+ unsigned int expected_http_status,
+ bool bad_sig);
/**
- * Obtain a reserve history entry from a @a cmd.
+ * Add affirmation that the auditor is auditing the given
+ * denomination.
+ * The information about the auditor is taken from the
+ * "[auditor]" section in the configuration file.
*
- * @param cmd command to extract the reserve history from.
- * @param index reserve history's index number.
- * @param[out] rhp set to the reserve history.
- * @return #GNUNET_OK on success.
+ * @param label command label.
+ * @param expected_http_status expected HTTP status from exchange
+ * @param denom_ref reference to a command identifying a denomination key
+ * @param bad_sig should we use a bogus signature?
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_reserve_history (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_EXCHANGE_ReserveHistory **rhp);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_auditor_add_denom_sig (const char *label,
+ unsigned int expected_http_status,
+ const char *denom_ref,
+ bool bad_sig);
/**
- * Make a trait for a exchange signature.
- *
- * @param index index number to associate to the offered exchange pub.
- * @param exchange_sig exchange signature to offer with this trait.
+ * Add statement about wire fees of the exchange. This is always
+ * done for a few hours around the current time (for the test).
*
- * @return the trait.
+ * @param label command label.
+ * @param wire_method wire method to set wire fees for
+ * @param wire_fee the wire fee to affirm
+ * @param closing_fee the closing fee to affirm
+ * @param expected_http_status expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @return the command
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_exchange_sig (
- unsigned int index,
- const struct TALER_ExchangeSignatureP *exchange_sig);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_wire_fee (const char *label,
+ const char *wire_method,
+ const char *wire_fee,
+ const char *closing_fee,
+ unsigned int expected_http_status,
+ bool bad_sig);
/**
- * Obtain a exchange signature (online sig) from a @a cmd.
+ * Add the given payto-URI bank account to the list of bank
+ * accounts used by the exchange.
*
- * @param cmd command to extract trait from
- * @param index index number of the exchange to obtain.
- * @param[out] exchange_sig set to the offered exchange signature.
- * @return #GNUNET_OK on success.
+ * @param label command label.
+ * @param payto_uri URI identifying the bank account
+ * @param expected_http_status expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_exchange_sig (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_ExchangeSignatureP **exchange_sig);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wire_add (const char *label,
+ const char *payto_uri,
+ unsigned int expected_http_status,
+ bool bad_sig);
/**
- * Make a trait for a exchange public key.
+ * Remove the given payto-URI bank account from the list of bank
+ * accounts used by the exchange.
*
- * @param index index number to associate to the offered exchange pub.
- * @param exchange_pub exchange pub to offer with this trait.
- *
- * @return the trait.
+ * @param label command label.
+ * @param payto_uri URI identifying the bank account
+ * @param expected_http_status expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @return the command
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_exchange_pub (
- unsigned int index,
- const struct TALER_ExchangePublicKeyP *exchange_pub);
-
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wire_del (const char *label,
+ const char *payto_uri,
+ unsigned int expected_http_status,
+ bool bad_sig);
/**
- * Obtain a exchange public key from a @a cmd.
+ * Sign all extensions that the exchange has to offer, f. e. the extension for
+ * age restriction. This has to be run before any withdrawal of age restricted
+ * can be performed.
*
- * @param cmd command to extract trait from
- * @param index index number of the exchange to obtain.
- * @param[out] exchange_pub set to the offered exchange pub.
- * @return #GNUNET_OK on success.
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_exchange_pub (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_ExchangePublicKeyP **exchange_pub);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label,
+ const char *config_filename);
/**
- * Obtain location where a command stores a pointer to a process.
+ * Sign all exchange denomination and online signing keys
+ * with the "offline" key and provide those signatures to
+ * the exchange. (Downloads the keys, makes the signature
+ * and uploads the result, all in one.)
*
- * @param cmd command to extract trait from.
- * @param index which process to pick if @a cmd
- * has multiple on offer.
- * @param[out] processp set to the address of the pointer to the
- * process.
- * @return #GNUNET_OK on success.
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_process (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- struct GNUNET_OS_Process ***processp);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_keys (const char *label,
+ const char *config_filename);
/**
- * Offer location where a command stores a pointer to a process.
+ * Sign a wire fee structure.
*
- * @param index offered location index number, in case there are
- * multiple on offer.
- * @param processp process location to offer.
- * @return the trait.
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @param wire_fee the wire fee to affirm (for the current year)
+ * @param closing_fee the closing fee to affirm (for the current year)
+ * @return the command
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_process (unsigned int index,
- struct GNUNET_OS_Process **processp);
-
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_fees (const char *label,
+ const char *config_filename,
+ const char *wire_fee,
+ const char *closing_fee);
-/**
- * Offer coin private key.
- *
- * @param index index number to associate with offered coin priv.
- * @param coin_priv coin private key to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_coin_priv (
- unsigned int index,
- const struct TALER_CoinSpendPrivateKeyP *coin_priv);
/**
- * Obtain a coin private key from a @a cmd.
+ * Sign global fee structure.
*
- * @param cmd command to extract trait from.
- * @param index index of the coin priv to obtain.
- * @param[out] coin_priv set to the private key of the coin.
- * @return #GNUNET_OK on success.
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @param history_fee the history fee to charge (for the current year)
+ * @param account_fee the account fee to charge (for the current year)
+ * @param purse_fee the purse fee to charge (for the current year)
+ * @param purse_timeout when do purses time out
+ * @param history_expiration when does an account history expire
+ * @param num_purses number of (free) active purses per account
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_coin_priv (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_CoinSpendPrivateKeyP **coin_priv);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_global_fees (
+ const char *label,
+ const char *config_filename,
+ const char *history_fee,
+ const char *account_fee,
+ const char *purse_fee,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ unsigned int num_purses);
/**
- * Offer blinding key.
+ * Revoke an exchange denomination key.
*
- * @param index index number to associate to the offered key.
- * @param blinding_key blinding key to offer.
- * @return the trait.
+ * @param label command label.
+ * @param expected_response_code expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @param denom_ref reference to a command that identifies
+ * a denomination key (i.e. because it was used to
+ * withdraw a coin).
+ * @return the command
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_blinding_key (
- unsigned int index,
- const struct TALER_DenominationBlindingKeyP *blinding_key);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_revoke_denom_key (
+ const char *label,
+ unsigned int expected_response_code,
+ bool bad_sig,
+ const char *denom_ref);
/**
- * Obtain a blinding key from a @a cmd.
+ * Revoke an exchange online signing key.
*
- * @param cmd command to extract trait from
- * @param index which coin to pick if @a cmd has multiple on offer.
- * @param[out] blinding_key set to the offered blinding key.
- * @return #GNUNET_OK on success.
+ * @param label command label.
+ * @param expected_response_code expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @param signkey_ref reference to a command that identifies
+ * a signing key (i.e. because it was used to
+ * sign a deposit confirmation).
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_blinding_key (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_DenominationBlindingKeyP **blinding_key);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_revoke_sign_key (
+ const char *label,
+ unsigned int expected_response_code,
+ bool bad_sig,
+ const char *signkey_ref);
/**
- * Make a trait for a denomination public key.
+ * Create a request for a wallet's KYC UUID.
*
- * @param index index number to associate to the offered denom pub.
- * @param denom_pub denom pub to offer with this trait.
- * @return the trait.
+ * @param label command label.
+ * @param reserve_reference command with reserve private key to use (or NULL to create a fresh reserve key).
+ * @param threshold_balance balance amount to pass to the exchange
+ * @param expected_response_code expected HTTP status
+ * @return the command
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_denom_pub (
- unsigned int index,
- const struct TALER_EXCHANGE_DenomPublicKey *dpk);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wallet_kyc_get (const char *label,
+ const char *reserve_reference,
+ const char *threshold_balance,
+ unsigned int expected_response_code);
/**
- * Obtain a denomination public key from a @a cmd.
+ * Create a request for an account's KYC status.
*
- * @param cmd command to extract trait from
- * @param index index number of the denom to obtain.
- * @param[out] denom_pub set to the offered denom pub.
- * @return #GNUNET_OK on success.
+ * @param label command label.
+ * @param payment_target_reference command with a payment target to query
+ * @param expected_response_code expected HTTP status
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_denom_pub (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_EXCHANGE_DenomPublicKey **dpk);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_kyc_get (const char *label,
+ const char *payment_target_reference,
+ unsigned int expected_response_code);
/**
- * Obtain a denomination signature from a @a cmd.
+ * Create a KYC proof request. Only useful in conjunction with the OAuth2.0
+ * logic, as it generates an OAuth2.0-specific request.
*
- * @param cmd command to extract the denom sig from.
- * @param index index number associated with the denom sig.
- * @param[out] denom_sig set to the offered signature.
- * @return #GNUNET_OK on success.
+ * @param label command label.
+ * @param payment_target_reference command with a payment target to query
+ * @param logic_section name of the KYC provider section
+ * in the exchange configuration for this proof
+ * @param code OAuth 2.0 code to use
+ * @param expected_response_code expected HTTP status
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_denom_sig (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_DenominationSignature **dpk);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_proof_kyc_oauth2 (
+ const char *label,
+ const char *payment_target_reference,
+ const char *logic_section,
+ const char *code,
+ unsigned int expected_response_code);
/**
- * Offer denom sig.
+ * Starts a fake OAuth 2.0 service on @a port for testing
+ * KYC processes which also provides a @a birthdate in a response
*
- * @param index index number to associate to the signature on
- * offer.
- * @param denom_sig the denom sig on offer.
- * @return the trait.
+ * @param label command label
+ * @param birthdate fixed birthdate, such as "2022-03-04", "2022-03-00", "2022-00-00"
+ * @param port the TCP port to listen on
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_denom_sig (
- unsigned int index,
- const struct TALER_DenominationSignature *sig);
-
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_oauth_with_birthdate (const char *label,
+ const char *birthdate,
+ uint16_t port);
/**
- * Offer number trait, 64-bit version.
+ * Starts a fake OAuth 2.0 service on @a port for testing
+ * KYC processes.
*
- * @param index the number's index number.
- * @param n number to offer.
+ * @param label command label
+ * @param port the TCP port to listen on
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_uint64 (unsigned int index,
- const uint64_t *n);
+#define TALER_TESTING_cmd_oauth(label, port) \
+ TALER_TESTING_cmd_oauth_with_birthdate ((label), NULL, (port))
-/**
- * Obtain a "number" value from @a cmd, 64-bit version.
- *
- * @param cmd command to extract the number from.
- * @param index the number's index number.
- * @param[out] n set to the number coming from @a cmd.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_uint64 (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const uint64_t **n);
+/* ****************** P2P payment commands ****************** */
/**
- * Offer a number.
+ * Creates a purse with deposits.
*
- * @param index the number's index number.
- * @param n the number to offer.
- * @return #GNUNET_OK on success.
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned from the exchange
+ * @param contract_terms contract, JSON string
+ * @param upload_contract should we upload the contract
+ * @param purse_expiration how long until the purse expires
+ * @param ... NULL-terminated list of references to coins to be deposited
+ * @return the command
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_uint (unsigned int index,
- const unsigned int *i);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_create_with_deposit (
+ const char *label,
+ unsigned int expected_http_status,
+ const char *contract_terms,
+ bool upload_contract,
+ struct GNUNET_TIME_Relative purse_expiration,
+ ...);
/**
- * Obtain a number from @a cmd.
+ * Deletes a purse.
*
- * @param cmd command to extract the number from.
- * @param index the number's index number.
- * @param[out] n set to the number coming from @a cmd.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_uint (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const unsigned int **n);
-
-
-/**
- * Opaque handle to fresh coins generated during refresh.
- * Details are internal to the refresh logic.
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned from the exchange
+ * @param purse_cmd command that created the purse
+ * @return the command
*/
-struct TALER_TESTING_FreshCoinData;
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_delete (
+ const char *label,
+ unsigned int expected_http_status,
+ const char *purse_cmd);
/**
- * Offer a _array_ of fresh coins.
+ * Retrieve contract (also checks that the contract matches
+ * the upload command).
*
- * @param index which array of fresh coins to offer,
- * if there are multiple on offer. Typically passed as
- * zero.
- * @param fresh_coins the array of fresh coins to offer
- * @return the trait,
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned from the exchange
+ * @param for_merge true if for merge, false if for deposit
+ * @param contract_ref reference to a command providing us with the contract private key
+ * @return the command
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_fresh_coins (
- unsigned int index,
- const struct TALER_TESTING_FreshCoinData *fresh_coins);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_contract_get (
+ const char *label,
+ unsigned int expected_http_status,
+ bool for_merge,
+ const char *contract_ref);
/**
- * Get a array of fresh coins.
+ * Retrieve purse state by merge private key.
*
- * @param cmd command to extract the fresh coin from.
- * @param index which array to pick if @a cmd has multiple
- * on offer.
- * @param[out] fresh_coins will point to the offered array.
- * @return #GNUNET_OK on success.
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned from the exchange
+ * @param merge_ref reference to a command providing us with the merge private key
+ * @param reserve_ref reference to a command providing us with a reserve private key; if NULL, we create a fresh reserve
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_fresh_coins (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_TESTING_FreshCoinData **fresh_coins);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_merge (
+ const char *label,
+ unsigned int expected_http_status,
+ const char *merge_ref,
+ const char *reserve_ref);
/**
- * Obtain contract terms from @a cmd.
+ * Retrieve purse state.
*
- * @param cmd command to extract the contract terms from.
- * @param index contract terms index number.
- * @param[out] contract_terms where to write the contract
- * terms.
- * @return #GNUNET_OK on success.
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned from the exchange
+ * @param purse_ref reference to a command providing us with the purse private key
+ * @param expected_balance how much should be in the purse
+ * @param wait_for_merge true to wait for a merge event, otherwise wait for a deposit event
+ * @param timeout how long to wait
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_contract_terms (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const json_t **contract_terms);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_poll (
+ const char *label,
+ unsigned int expected_http_status,
+ const char *purse_ref,
+ const char *expected_balance,
+ bool wait_for_merge,
+ struct GNUNET_TIME_Relative timeout);
/**
- * Offer contract terms.
+ * Wait for the poll command to complete.
*
- * @param index contract terms index number.
- * @param contract_terms contract terms to offer.
- * @return the trait.
+ * @param label command label
+ * @param timeout how long to wait at most
+ * @param poll_reference which poll command to wait for
+ * @return the command
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_contract_terms (unsigned int index,
- const json_t *contract_terms);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_poll_finish (const char *label,
+ struct GNUNET_TIME_Relative timeout,
+ const char *poll_reference);
/**
- * Obtain wire details from @a cmd.
+ * Creates a purse with reserve.
*
- * @param cmd command to extract the wire details from.
- * @param index index number associate with the wire details
- * on offer; usually zero, as one command sticks to
- * one bank account.
- * @param[out] wire_details where to write the wire details.
- * @return #GNUNET_OK on success.
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned from the exchange
+ * @param contract_terms contract, JSON string
+ * @param upload_contract should we upload the contract
+ * @param pay_purse_fee should we pay a fee to create the purse
+ * @param expiration when should the purse expire
+ * @param reserve_ref reference to reserve key, or NULL to create a new reserve
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_wire_details (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const json_t **wire_details);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_create_with_reserve (
+ const char *label,
+ unsigned int expected_http_status,
+ const char *contract_terms,
+ bool upload_contract,
+ bool pay_purse_fee,
+ struct GNUNET_TIME_Relative expiration,
+ const char *reserve_ref);
/**
- * Offer wire details in a trait.
+ * Deposit coins into a purse.
*
- * @param index index number associate with the wire details
- * on offer; usually zero, as one command sticks to
- * one bank account.
- * @param wire_details wire details to offer.
- *
- * @return the trait.
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned from the exchange
+ * @param min_age age restriction of the purse
+ * @param purse_ref reference to the purse
+ * @param ... NULL-terminated list of references to coins to be deposited
+ * @return the command
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_wire_details (unsigned int index,
- const json_t *wire_details);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_deposit_coins (
+ const char *label,
+ unsigned int expected_http_status,
+ uint8_t min_age,
+ const char *purse_ref,
+ ...);
/**
- * Obtain serialized exchange keys from @a cmd.
+ * Setup AML officer.
*
- * @param cmd command to extract the keys from.
- * @param index index number associate with the keys on offer.
- * @param[out] keys where to write the serialized keys.
- * @return #GNUNET_OK on success.
+ * @param label command label
+ * @param ref_cmd command that previously created the
+ * officer, NULL to create one this time
+ * @param name full legal name of the officer to use
+ * @param is_active true to set the officer to active
+ * @param read_only true to restrict the officer to read-only
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_exchange_keys (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const json_t **keys);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_officer (
+ const char *label,
+ const char *ref_cmd,
+ const char *name,
+ bool is_active,
+ bool read_only);
/**
- * Offer serialized keys in a trait.
+ * Make AML decision.
*
- * @param index index number associate with the serial keys
- * on offer.
- * @param keys serialized keys to offer.
- * @return the trait.
+ * @param label command label
+ * @param ref_officer command that previously created an
+ * officer
+ * @param ref_operation command that previously created an
+ * h_payto which to make an AML decision about
+ * @param new_threshold new threshold to set
+ * @param justification justification given for the decision
+ * @param new_state new AML state for the account
+ * @param kyc_requirement KYC requirement to impose
+ * @param expected_response expected HTTP return status
+ * @return the command
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_exchange_keys (unsigned int index,
- const json_t *keys);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_take_aml_decision (
+ const char *label,
+ const char *ref_officer,
+ const char *ref_operation,
+ const char *new_threshold,
+ const char *justification,
+ enum TALER_AmlDecisionState new_state,
+ const char *kyc_requirement,
+ unsigned int expected_response);
/**
- * Obtain a private key from a "merchant". Used e.g. to obtain
- * a merchant's priv to sign a /track request.
+ * Fetch AML decision.
*
- * @param cmd command that is offering the key.
- * @param index (typically zero) which key to return if there
- * are multiple on offer.
- * @param[out] priv set to the key coming from @a cmd.
- * @return #GNUNET_OK on success.
+ * @param label command label
+ * @param ref_officer command that previously created an
+ * officer
+ * @param ref_operation command that previously created an
+ * h_payto which to make an AML decision about
+ * @param expected_http_status expected HTTP response status
+ * @return the command
*/
-int
-TALER_TESTING_get_trait_merchant_priv (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_MerchantPrivateKeyP **priv);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_aml_decision (
+ const char *label,
+ const char *ref_officer,
+ const char *ref_operation,
+ unsigned int expected_http_status);
/**
- * Offer private key of a merchant, typically done when CMD_1 needs it to
- * sign a request.
+ * Fetch AML decisions.
*
- * @param index (typically zero) which key to return if there are
- * multiple on offer.
- * @param priv which object should be offered.
- * @return the trait.
+ * @param label command label
+ * @param ref_officer command that previously created an
+ * officer
+ * @param filter AML state to filter by
+ * @param expected_http_status expected HTTP response status
+ * @return the command
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_merchant_priv (
- unsigned int index,
- const struct TALER_MerchantPrivateKeyP *priv);
-
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_aml_decisions (
+ const char *label,
+ const char *ref_officer,
+ enum TALER_AmlDecisionState filter,
+ unsigned int expected_http_status);
-/**
- * Obtain a public key from a "merchant". Used e.g. to obtain
- * a merchant's public key to use backend's API.
- *
- * @param cmd command offering the key.
- * @param index (typically zero) which key to return if there
- * are multiple on offer.
- * @param[out] pub set to the key coming from @a cmd.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_merchant_pub (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_MerchantPublicKeyP **pub);
+/* ****************** convenience functions ************** */
/**
- * Offer public key.
+ * Get exchange URL from interpreter. Convenience function.
*
- * @param index (typically zero) which key to return if there
- * are multiple on offer. NOTE: if one key is offered, it
- * is mandatory to set this as zero.
- * @param pub which object should be returned.
- * @return the trait.
+ * @param is interpreter state.
+ * @return the exchange URL, or NULL on error
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_merchant_pub (
- unsigned int index,
- const struct TALER_MerchantPublicKeyP *pub);
+const char *
+TALER_TESTING_get_exchange_url (
+ struct TALER_TESTING_Interpreter *is);
/**
- * Obtain a string from @a cmd.
+ * Get exchange keys from interpreter. Convenience function.
*
- * @param cmd command to extract the subject from.
- * @param index index number associated with the transfer
- * subject to offer.
- * @param[out] s where to write the offered
- * string.
- * @return #GNUNET_OK on success.
+ * @param is interpreter state.
+ * @return the exchange keys, or NULL on error
*/
-int
-TALER_TESTING_get_trait_string (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const char **s);
+struct TALER_EXCHANGE_Keys *
+TALER_TESTING_get_keys (
+ struct TALER_TESTING_Interpreter *is);
-/**
- * Offer string subject.
- *
- * @param index index number associated with the transfer
- * subject being offered.
- * @param s string to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_string (unsigned int index,
- const char *s);
+/* *** Generic trait logic for implementing traits ********* */
/**
- * Obtain a WTID value from @a cmd.
- *
- * @param cmd command to extract trait from
- * @param index which WTID to pick if @a cmd has multiple on
- * offer
- * @param[out] wtid set to the wanted WTID.
- * @return #GNUNET_OK on success
+ * Opaque handle to fresh coins generated during refresh.
+ * Details are internal to the refresh logic.
*/
-int
-TALER_TESTING_get_trait_wtid (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_WireTransferIdentifierRawP **wtid);
+struct TALER_TESTING_FreshCoinData;
/**
- * Offer a WTID.
- *
- * @param index associate the WTID with this index.
- * @param wtid pointer to the WTID to offer.
- * @return the trait.
+ * A trait.
*/
struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_wtid (
- unsigned int index,
- const struct TALER_WireTransferIdentifierRawP *wtid);
-
-
-/**
- * Different types of URLs that appear in traits.
- */
-enum TALER_TESTING_URL_Type
{
/**
- * Category of last resort. Should not be used.
- */
- TALER_TESTING_UT_UNDEFINED = 0,
-
- /**
- * HTTP base URL of an exchange (API), as for example
- * given in wire transfers subjects made by the aggregator.
+ * Index number associated with the trait. This gives the
+ * possibility to have _multiple_ traits on offer under the
+ * same name.
*/
- TALER_TESTING_UT_EXCHANGE_BASE_URL = 1,
+ unsigned int index;
/**
- * HTTP URL of the exchange's bank account at the bank.
+ * Trait type, for example "reserve-pub" or "coin-priv".
*/
- TALER_TESTING_UT_EXCHANGE_BANK_ACCOUNT_URL = 2
-};
-
-
-/**
- * Offer HTTP url in a trait.
- *
- * @param index which url is to be picked,
- * in case multiple are offered.
- * @param url the url to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_url (enum TALER_TESTING_URL_Type index,
- const char *url);
-
-
-/**
- * Obtain a HTTP url from @a cmd.
- *
- * @param cmd command to extract the url from.
- * @param index which url is to be picked, in case
- * multiple are offered.
- * @param[out] url where to write the url.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_url (const struct TALER_TESTING_Command *cmd,
- enum TALER_TESTING_URL_Type index,
- const char **url);
-
+ const char *trait_name;
-/**
- * Used as the "index" in payto traits, to identify what kind of
- * payto URL we are returning.
- */
-enum TALER_TESTING_PaytoType
-{
- /**
- * We don't know / not credit or debit.
- */
- TALER_TESTING_PT_NEUTRAL,
/**
- * Credit side of a transaction.
- */
- TALER_TESTING_PT_CREDIT,
- /**
- * Debit side of a transaction.
+ * Pointer to the piece of data to offer.
*/
- TALER_TESTING_PT_DEBIT
+ const void *ptr;
};
/**
- * Offer a payto uri in a trait.
- *
- * @param pt which url is to be picked,
- * in case multiple are offered.
- * @param payto_uri the uri to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_payto (enum TALER_TESTING_PaytoType pt,
- const char *payto_uri);
-
-
-/**
- * Obtain a PAYTO url from @a cmd.
- *
- * @param cmd command to extract the url from.
- * @param pt which url is to be picked, in case
- * multiple are offered.
- * @param[out] url where to write the url.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_payto (const struct TALER_TESTING_Command *cmd,
- enum TALER_TESTING_PaytoType pt,
- const char **url);
-
-
-/**
- * Obtain a order id from @a cmd.
- *
- * @param cmd command to extract the order id from.
- * @param index which order id is to be picked, in case
- * multiple are offered.
- * @param[out] order_id where to write the order id.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_order_id (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const char **order_id);
-
-
-/**
- * Offer order id in a trait.
- *
- * @param index which order id is to be offered,
- * in case multiple are offered.
- * @param order_id the order id to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_order_id (unsigned int index,
- const char *order_id);
-
-
-/**
- * Obtain an amount from a @a cmd.
- *
- * @param cmd command to extract the amount from.
- * @param index which amount to pick if @a cmd has multiple
- * on offer
- * @param[out] amount set to the amount.
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_get_trait_amount_obj (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_Amount **amount);
-
-
-/**
- * Offer amount.
- *
- * @param index which amount to offer, in case there are
- * multiple available.
- * @param amount the amount to offer.
- *
- * @return the trait.
+ * "end" trait. Because traits are offered into arrays,
+ * this type of trait is used to mark the end of such arrays;
+ * useful when iterating over those.
*/
struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_amount_obj (unsigned int index,
- const struct TALER_Amount *amount);
+TALER_TESTING_trait_end (void);
/**
- * Offer a command in a trait.
- *
- * @param index always zero. Commands offering this
- * kind of traits do not need this index. For
- * example, a "meta" CMD returns always the
- * CMD currently being executed.
- * @param cmd wire details to offer.
+ * Extract a trait.
*
- * @return the trait.
+ * @param traits the array of all the traits.
+ * @param[out] ret where to store the result.
+ * @param trait type of the trait to extract.
+ * @param index index number of the trait to extract.
+ * @return #GNUNET_OK when the trait is found.
*/
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_cmd (unsigned int index,
- const struct TALER_TESTING_Command *cmd);
+enum GNUNET_GenericReturnValue
+TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
+ const void **ret,
+ const char *trait,
+ unsigned int index);
-/**
- * Obtain a command from @a cmd.
- *
- * @param cmd command to extract the command from.
- * @param index always zero. Commands offering this
- * kind of traits do not need this index. For
- * example, a "meta" CMD returns always the
- * CMD currently being executed.
- * @param[out] _cmd where to write the wire details.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_cmd (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- struct TALER_TESTING_Command **_cmd);
+/* ****** Specific traits supported by this component ******* */
/**
- * Obtain a absolute time from @a cmd.
- *
- * @param cmd command to extract trait from
- * @param index which time stamp to pick if
- * @a cmd has multiple on offer.
- * @param[out] time set to the wanted WTID.
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_get_trait_absolute_time (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct GNUNET_TIME_Absolute **time);
+ * Create headers for a trait with name @a name for
+ * statically allocated data of type @a type.
+ */
+#define TALER_TESTING_MAKE_DECL_SIMPLE_TRAIT(name,type) \
+ enum GNUNET_GenericReturnValue \
+ TALER_TESTING_get_trait_ ## name ( \
+ const struct TALER_TESTING_Command *cmd, \
+ type **ret); \
+ struct TALER_TESTING_Trait \
+ TALER_TESTING_make_trait_ ## name ( \
+ type * value);
+
+
+/**
+ * Create C implementation for a trait with name @a name for statically
+ * allocated data of type @a type.
+ */
+#define TALER_TESTING_MAKE_IMPL_SIMPLE_TRAIT(name,type) \
+ enum GNUNET_GenericReturnValue \
+ TALER_TESTING_get_trait_ ## name ( \
+ const struct TALER_TESTING_Command *cmd, \
+ type **ret) \
+ { \
+ if (NULL == cmd->traits) return GNUNET_SYSERR; \
+ return cmd->traits (cmd->cls, \
+ (const void **) ret, \
+ TALER_S (name), \
+ 0); \
+ } \
+ struct TALER_TESTING_Trait \
+ TALER_TESTING_make_trait_ ## name ( \
+ type * value) \
+ { \
+ struct TALER_TESTING_Trait ret = { \
+ .trait_name = TALER_S (name), \
+ .ptr = (const void *) value \
+ }; \
+ return ret; \
+ }
+
+
+/**
+ * Create headers for a trait with name @a name for
+ * statically allocated data of type @a type.
+ */
+#define TALER_TESTING_MAKE_DECL_INDEXED_TRAIT(name,type) \
+ enum GNUNET_GenericReturnValue \
+ TALER_TESTING_get_trait_ ## name ( \
+ const struct TALER_TESTING_Command *cmd, \
+ unsigned int index, \
+ type **ret); \
+ struct TALER_TESTING_Trait \
+ TALER_TESTING_make_trait_ ## name ( \
+ unsigned int index, \
+ type * value);
+
+
+/**
+ * Create C implementation for a trait with name @a name for statically
+ * allocated data of type @a type.
+ */
+#define TALER_TESTING_MAKE_IMPL_INDEXED_TRAIT(name,type) \
+ enum GNUNET_GenericReturnValue \
+ TALER_TESTING_get_trait_ ## name ( \
+ const struct TALER_TESTING_Command *cmd, \
+ unsigned int index, \
+ type **ret) \
+ { \
+ if (NULL == cmd->traits) return GNUNET_SYSERR; \
+ return cmd->traits (cmd->cls, \
+ (const void **) ret, \
+ TALER_S (name), \
+ index); \
+ } \
+ struct TALER_TESTING_Trait \
+ TALER_TESTING_make_trait_ ## name ( \
+ unsigned int index, \
+ type * value) \
+ { \
+ struct TALER_TESTING_Trait ret = { \
+ .index = index, \
+ .trait_name = TALER_S (name), \
+ .ptr = (const void *) value \
+ }; \
+ return ret; \
+ }
+
+
+/**
+ * Call #op on all simple traits.
+ */
+#define TALER_TESTING_SIMPLE_TRAITS(op) \
+ op (bank_row, const uint64_t) \
+ op (officer_pub, const struct TALER_AmlOfficerPublicKeyP) \
+ op (officer_priv, const struct TALER_AmlOfficerPrivateKeyP) \
+ op (officer_name, const char) \
+ op (aml_decision, enum TALER_AmlDecisionState) \
+ op (aml_justification, const char) \
+ op (auditor_priv, const struct TALER_AuditorPrivateKeyP) \
+ op (auditor_pub, const struct TALER_AuditorPublicKeyP) \
+ op (master_priv, const struct TALER_MasterPrivateKeyP) \
+ op (master_pub, const struct TALER_MasterPublicKeyP) \
+ op (purse_priv, const struct TALER_PurseContractPrivateKeyP) \
+ op (purse_pub, const struct TALER_PurseContractPublicKeyP) \
+ op (merge_priv, const struct TALER_PurseMergePrivateKeyP) \
+ op (merge_pub, const struct TALER_PurseMergePublicKeyP) \
+ op (contract_priv, const struct TALER_ContractDiffiePrivateP) \
+ op (reserve_priv, const struct TALER_ReservePrivateKeyP) \
+ op (reserve_sig, const struct TALER_ReserveSignatureP) \
+ op (h_payto, const struct TALER_PaytoHashP) \
+ op (planchet_secret, const struct TALER_PlanchetMasterSecretP) \
+ op (refresh_secret, const struct TALER_RefreshMasterSecretP) \
+ op (reserve_pub, const struct TALER_ReservePublicKeyP) \
+ op (merchant_priv, const struct TALER_MerchantPrivateKeyP) \
+ op (merchant_pub, const struct TALER_MerchantPublicKeyP) \
+ op (merchant_sig, const struct TALER_MerchantSignatureP) \
+ op (wtid, const struct TALER_WireTransferIdentifierRawP) \
+ op (bank_auth_data, const struct TALER_BANK_AuthenticationData) \
+ op (contract_terms, const json_t) \
+ op (wire_details, const json_t) \
+ op (exchange_url, const char) \
+ op (auditor_url, const char) \
+ op (exchange_bank_account_url, const char) \
+ op (taler_uri, const char) \
+ op (payto_uri, const char) \
+ op (kyc_url, const char) \
+ op (web_url, const char) \
+ op (row, const uint64_t) \
+ op (legi_requirement_row, const uint64_t) \
+ op (array_length, const unsigned int) \
+ op (credit_payto_uri, const char) \
+ op (debit_payto_uri, const char) \
+ op (order_id, const char) \
+ op (amount, const struct TALER_Amount) \
+ op (amount_with_fee, const struct TALER_Amount) \
+ op (batch_cmds, struct TALER_TESTING_Command) \
+ op (uuid, const struct GNUNET_Uuid) \
+ op (fresh_coins, const struct TALER_TESTING_FreshCoinData *) \
+ op (claim_token, const struct TALER_ClaimTokenP) \
+ op (relative_time, const struct GNUNET_TIME_Relative) \
+ op (fakebank, struct TALER_FAKEBANK_Handle) \
+ op (keys, struct TALER_EXCHANGE_Keys) \
+ op (process, struct GNUNET_OS_Process *)
+
+
+/**
+ * Call #op on all indexed traits.
+ */
+#define TALER_TESTING_INDEXED_TRAITS(op) \
+ op (denom_pub, const struct TALER_EXCHANGE_DenomPublicKey) \
+ op (denom_sig, const struct TALER_DenominationSignature) \
+ op (amounts, const struct TALER_Amount) \
+ op (deposit_amount, const struct TALER_Amount) \
+ op (deposit_fee_amount, const struct TALER_Amount) \
+ op (age_commitment, const struct TALER_AgeCommitment) \
+ op (age_commitment_proof, const struct TALER_AgeCommitmentProof) \
+ op (h_age_commitment, const struct TALER_AgeCommitmentHash) \
+ op (reserve_history, const struct TALER_EXCHANGE_ReserveHistoryEntry) \
+ op (coin_history, const struct TALER_EXCHANGE_CoinHistoryEntry) \
+ op (planchet_secrets, const struct TALER_PlanchetMasterSecretP) \
+ op (exchange_wd_value, const struct TALER_ExchangeWithdrawValues) \
+ op (coin_priv, const struct TALER_CoinSpendPrivateKeyP) \
+ op (coin_pub, const struct TALER_CoinSpendPublicKeyP) \
+ op (coin_sig, const struct TALER_CoinSpendSignatureP) \
+ op (absolute_time, const struct GNUNET_TIME_Absolute) \
+ op (timestamp, const struct GNUNET_TIME_Timestamp) \
+ op (wire_deadline, const struct GNUNET_TIME_Timestamp) \
+ op (refund_deadline, const struct GNUNET_TIME_Timestamp) \
+ op (exchange_pub, const struct TALER_ExchangePublicKeyP) \
+ op (exchange_sig, const struct TALER_ExchangeSignatureP) \
+ op (blinding_key, const union GNUNET_CRYPTO_BlindingSecretP) \
+ op (h_blinded_coin, const struct TALER_BlindedCoinHashP)
+
+TALER_TESTING_SIMPLE_TRAITS (TALER_TESTING_MAKE_DECL_SIMPLE_TRAIT)
+
+TALER_TESTING_INDEXED_TRAITS (TALER_TESTING_MAKE_DECL_INDEXED_TRAIT)
-/**
- * Offer a absolute time.
- *
- * @param index associate the object with this index
- * @param time which object should be returned
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_absolute_time (
- unsigned int index,
- const struct GNUNET_TIME_Absolute *time);
-
#endif
diff --git a/src/include/taler_twister_testing_lib.h b/src/include/taler_twister_testing_lib.h
index fb0c352df..cdafab04b 100644
--- a/src/include/taler_twister_testing_lib.h
+++ b/src/include/taler_twister_testing_lib.h
@@ -26,7 +26,7 @@
#ifndef TALER_TWISTER_TESTING_LIB_H
#define TALER_TWISTER_TESTING_LIB_H
-#include <taler/taler_testing_lib.h>
+#include "taler_testing_lib.h"
#define TWISTER_FAIL() \
do {GNUNET_break (0); return NULL; } while (0)
@@ -100,6 +100,23 @@ TALER_TESTING_cmd_modify_object_ul (const char *label,
/**
+ * Create a "modify header" CMD. This command instructs
+ * the twister to modify a header in the next HTTP response.
+ *
+ * @param label command label
+ * @param config_filename configuration filename.
+ * @param path path identifying where to modify.
+ * @param value value to set the header to.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_modify_header_dl (const char *label,
+ const char *config_filename,
+ const char *path,
+ const char *value);
+
+
+/**
* Create a "malform response" CMD. This command makes
* the next response randomly malformed (by truncating it).
*
diff --git a/src/include/taler_util.h b/src/include/taler_util.h
index c7bf9c02a..8feb8451c 100644
--- a/src/include/taler_util.h
+++ b/src/include/taler_util.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
+ Copyright (C) 2014-2024 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
@@ -16,17 +16,29 @@
/**
* @file include/taler_util.h
* @brief Interface for common utility functions
+ * This library is not thread-safe, all APIs must only be used from a single thread.
+ * This library calls abort() if it runs out of memory. Be aware of these limitations.
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
*/
#ifndef TALER_UTIL_H
#define TALER_UTIL_H
+#include <gnunet/gnunet_common.h>
+#define __TALER_UTIL_LIB_H_INSIDE__
+
#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
#include "taler_amount_lib.h"
#include "taler_crypto_lib.h"
/**
+ * Version of the Taler API, in hex.
+ * Thus 0.8.4-1 = 0x00080401.
+ */
+#define TALER_API_VERSION 0x00090401
+
+/**
* Stringify operator.
*
* @param a some expression to stringify. Must NOT be a macro.
@@ -73,6 +85,22 @@
/**
+ * HTTP header with an AML officer signature to approve the inquiry.
+ * Used only in GET Requests.
+ */
+#define TALER_AML_OFFICER_SIGNATURE_HEADER "Taler-AML-Officer-Signature"
+
+/**
+ * Header with signature for reserve history requests.
+ */
+#define TALER_RESERVE_HISTORY_SIGNATURE_HEADER "Taler-Reserve-History-Signature"
+
+/**
+ * Header with signature for coin history requests.
+ */
+#define TALER_COIN_HISTORY_SIGNATURE_HEADER "Taler-Coin-History-Signature"
+
+/**
* Log an error message at log-level 'level' that indicates
* a failure of the command 'cmd' with the message given
* by gcry_strerror(rc).
@@ -119,18 +147,19 @@ TALER_b2s (const void *buf,
* @param obj address of object to convert
* @return string representing the binary obj buffer
*/
-#define TALER_B2S(obj) TALER_b2s (obj, sizeof (*obj))
+#define TALER_B2S(obj) TALER_b2s ((obj), sizeof (*(obj)))
/**
* Obtain denomination amount from configuration file.
*
+ * @param cfg configuration to extract data from
* @param section section of the configuration to access
* @param option option of the configuration to access
* @param[out] denom set to the amount found in configuration
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
*/
-int
+enum GNUNET_GenericReturnValue
TALER_config_get_amount (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
@@ -138,6 +167,39 @@ TALER_config_get_amount (const struct GNUNET_CONFIGURATION_Handle *cfg,
/**
+ * Obtain denomination fee structure of a
+ * denomination from configuration file. All
+ * fee options must start with "fee_" and have
+ * names typical for the respective fees.
+ *
+ * @param cfg configuration to extract data from
+ * @param currency expected currency
+ * @param section section of the configuration to access
+ * @param[out] fees set to the denomination fees
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+enum GNUNET_GenericReturnValue
+TALER_config_get_denom_fees (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *currency,
+ const char *section,
+ struct TALER_DenomFeeSet *fees);
+
+
+/**
+ * Check that all denominations in @a fees use
+ * @a currency
+ *
+ * @param currency desired currency
+ * @param fees fee set to check
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_denom_fee_check_currency (
+ const char *currency,
+ const struct TALER_DenomFeeSet *fees);
+
+
+/**
* Load our currency from the @a cfg (in section [taler]
* the option "CURRENCY").
*
@@ -145,12 +207,107 @@ TALER_config_get_amount (const struct GNUNET_CONFIGURATION_Handle *cfg,
* @param[out] currency where to write the result
* @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
*/
-int
+enum GNUNET_GenericReturnValue
TALER_config_get_currency (const struct GNUNET_CONFIGURATION_Handle *cfg,
char **currency);
/**
+ * Details about how to render a currency.
+ */
+struct TALER_CurrencySpecification
+{
+ /**
+ * Currency code of the currency.
+ */
+ char currency[TALER_CURRENCY_LEN];
+
+ /**
+ * Human-readable long name of the currency, e.g.
+ * "Japanese Yen".
+ */
+ char *name;
+
+ /**
+ * how many digits the user may enter at most after the @e decimal_separator
+ */
+ unsigned int num_fractional_input_digits;
+
+ /**
+ * how many digits we render in normal scale after the @e decimal_separator
+ */
+ unsigned int num_fractional_normal_digits;
+
+ /**
+ * how many digits we render in after the @e decimal_separator even if all
+ * remaining digits are zero.
+ */
+ unsigned int num_fractional_trailing_zero_digits;
+
+ /**
+ * Mapping of powers of 10 to alternative currency names or symbols.
+ * Keys are the decimal powers, values the currency symbol to use.
+ * Map MUST contain an entry for "0" to the default currency symbol.
+ */
+ json_t *map_alt_unit_names;
+
+};
+
+
+/**
+ * Parse information about supported currencies from
+ * our configuration.
+ *
+ * @param cfg configuration to parse
+ * @param[out] num_currencies set to number of enabled currencies, length of @e cspecs
+ * @param[out] cspecs set to currency specification array
+ * @return #GNUNET_OK on success, #GNUNET_NO if zero
+ * currency specifications were enabled,
+ * #GNUNET_SYSERR if the configuration was malformed
+ */
+enum GNUNET_GenericReturnValue
+TALER_CONFIG_parse_currencies (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ unsigned int *num_currencies,
+ struct TALER_CurrencySpecification **cspecs);
+
+
+/**
+ * Free @a cspecs array.
+ *
+ * @param num_currencies length of @a cspecs array
+ * @param[in] cspecs array to free
+ */
+void
+TALER_CONFIG_free_currencies (
+ unsigned int num_currencies,
+ struct TALER_CurrencySpecification cspecs[static num_currencies]);
+
+
+/**
+ * Convert a currency specification to the
+ * respective JSON object.
+ *
+ * @param cspec currency specification
+ * @return JSON object encoding @a cspec for `/config`.
+ */
+json_t *
+TALER_CONFIG_currency_specs_to_json (
+ const struct TALER_CurrencySpecification *cspec);
+
+
+/**
+ * Check that @a map contains a valid currency scale
+ * map that maps integers from [-12,24] to currency
+ * symbols given as strings.
+ *
+ * @param map map to check
+ * @return #GNUNET_OK if @a map is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_check_currency_scale_map (const json_t *map);
+
+
+/**
* Allow user to specify an amount on the command line.
*
* @param shortName short name of the option
@@ -175,6 +332,23 @@ TALER_project_data_default (void);
/**
+ * Initialize libtalerutil.
+ */
+void
+TALER_OS_init (void);
+
+
+/**
+ * Re-encode string at @a inp to match RFC 8785 (section 3.2.2.2).
+ *
+ * @param[in,out] inp pointer to string to re-encode
+ * @return number of bytes in resulting @a inp
+ */
+size_t
+TALER_rfc8785encode (char **inp);
+
+
+/**
* URL-encode a string according to rfc3986.
*
* @param s string to encode
@@ -185,6 +359,44 @@ TALER_urlencode (const char *s);
/**
+ * Test if all characters in @a url are valid for
+ * a URL.
+ *
+ * @param url URL to sanity-check
+ * @return true if @a url only contains valid characters
+ */
+bool
+TALER_url_valid_charset (const char *url);
+
+
+/**
+ * Test if the URL is a valid "http" (or "https")
+ * URL (includes test for #TALER_url_valid_charset()).
+ *
+ * @param url a string to test if it could be a valid URL
+ * @return true if @a url is well-formed
+ */
+bool
+TALER_is_web_url (const char *url);
+
+
+/**
+ * Check if @a lang matches the @a language_pattern, and if so with
+ * which preference.
+ * See also: https://tools.ietf.org/html/rfc7231#section-5.3.1
+ *
+ * @param language_pattern a language preferences string
+ * like "fr-CH, fr;q=0.9, en;q=0.8, *;q=0.1"
+ * @param lang the 2-digit language to match
+ * @return q-weight given for @a lang in @a language_pattern, 1.0 if no weights are given;
+ * 0 if @a lang is not in @a language_pattern
+ */
+double
+TALER_language_matches (const char *language_pattern,
+ const char *lang);
+
+
+/**
* Find out if an MHD connection is using HTTPS (either
* directly or via proxy).
*
@@ -193,7 +405,7 @@ TALER_urlencode (const char *s);
* #GNUNET_NO if the MHD connection is using http,
* #GNUNET_SYSERR if the connection type couldn't be determined
*/
-int
+enum GNUNET_GenericReturnValue
TALER_mhd_is_https (struct MHD_Connection *connection);
@@ -204,8 +416,9 @@ TALER_mhd_is_https (struct MHD_Connection *connection);
* that a NULL value does not terminate the list, only a NULL key signals the
* end of the list of arguments.
*
- * @param base_url absolute base URL to use
- * @param path path of the url
+ * @param base_url absolute base URL to use, must either
+ * end with '/' *or* @a path must be the empty string
+ * @param path path of the url to append to the @a base_url
* @param ... NULL-terminated key-value pairs (char *) for query parameters,
* only the value will be url-encoded
* @returns the URL, must be freed with #GNUNET_free
@@ -288,6 +501,20 @@ TALER_payto_get_method (const char *payto_uri);
/**
+ * Normalize payto://-URI to make "strcmp()" sufficient
+ * to check if two payto-URIs refer to the same bank
+ * account. Removes optional arguments (everything after
+ * "?") and applies method-specific normalizations to
+ * the main part of the URI.
+ *
+ * @param input a payto://-URI
+ * @return normalized URI, or NULL if @a input was not well-formed
+ */
+char *
+TALER_payto_normalize (const char *input);
+
+
+/**
* Obtain the account name from a payto URL.
*
* @param payto an x-taler-bank payto URL
@@ -298,4 +525,305 @@ char *
TALER_xtalerbank_account_from_payto (const char *payto);
+/**
+ * Obtain the receiver name from a payto URL.
+ *
+ * @param payto an x-taler-bank payto URL
+ * @return only the receiver name from the @a payto URL, NULL if not an x-taler-bank payto URL
+ */
+char *
+TALER_payto_get_receiver_name (const char *payto);
+
+
+/**
+ * Extract the subject value from the URI parameters.
+ *
+ * @param payto_uri the URL to parse
+ * @return NULL if the subject parameter is not found.
+ * The caller should free the returned value.
+ */
+char *
+TALER_payto_get_subject (const char *payto_uri);
+
+
+/**
+ * Check that a payto:// URI is well-formed.
+ *
+ * @param payto_uri the URL to check
+ * @return NULL on success, otherwise an error
+ * message to be freed by the caller!
+ */
+char *
+TALER_payto_validate (const char *payto_uri);
+
+
+/**
+ * Create payto://-URI for a given exchange base URL
+ * and a @a reserve_pub.
+ *
+ * @param exchange_url the base URL of the exchange
+ * @param reserve_pub the public key of the reserve
+ * @return payto://-URI for the reserve (without receiver-name!)
+ */
+char *
+TALER_reserve_make_payto (const char *exchange_url,
+ const struct TALER_ReservePublicKeyP *reserve_pub);
+
+
+/**
+ * Check that an IBAN number is well-formed.
+ *
+ * Validates given IBAN according to the European Banking Standards. See:
+ * http://www.europeanpaymentscouncil.eu/documents/ECBS%20IBAN%20standard%20EBS204_V3.2.pdf
+ *
+ * @param iban the IBAN to check
+ * @return NULL on success, otherwise an error
+ * message to be freed by the caller!
+ */
+char *
+TALER_iban_validate (const char *iban);
+
+
+/**
+ * Possible values for a binary filter.
+ */
+enum TALER_EXCHANGE_YesNoAll
+{
+ /**
+ * If condition is yes.
+ */
+ TALER_EXCHANGE_YNA_YES = 1,
+
+ /**
+ * If condition is no.
+ */
+ TALER_EXCHANGE_YNA_NO = 2,
+
+ /**
+ * Condition disabled.
+ */
+ TALER_EXCHANGE_YNA_ALL = 3
+};
+
+
+/**
+ * Convert query argument to @a yna value.
+ *
+ * @param connection connection to take query argument from
+ * @param arg argument to try for
+ * @param default_val value to assign if the argument is not present
+ * @param[out] yna value to set
+ * @return true on success, false if the parameter was malformed
+ */
+bool
+TALER_arg_to_yna (struct MHD_Connection *connection,
+ const char *arg,
+ enum TALER_EXCHANGE_YesNoAll default_val,
+ enum TALER_EXCHANGE_YesNoAll *yna);
+
+
+/**
+ * Convert YNA value to a string.
+ *
+ * @param yna value to convert
+ * @return string representation ("yes"/"no"/"all").
+ */
+const char *
+TALER_yna_to_string (enum TALER_EXCHANGE_YesNoAll yna);
+
+
+#ifdef __APPLE__
+/**
+ * Returns the first occurrence of `c` in `s`, or returns the null-byte
+ * terminating the string if it does not occur.
+ *
+ * @param s the string to search in
+ * @param c the character to search for
+ * @return char* the first occurrence of `c` in `s`
+ */
+char *strchrnul (const char *s, int c);
+
+#endif
+
+/**
+ * @brief Parses a date information into days after 1970-01-01 (or 0)
+ *
+ * The input MUST be of the form
+ *
+ * 1) YYYY-MM-DD, representing a valid date
+ * 2) YYYY-MM-00, representing a valid month in a particular year
+ * 3) YYYY-00-00, representing a valid year.
+ *
+ * In the cases 2) and 3) the out parameter is set to the beginning of the
+ * time, f.e. 1950-00-00 == 1950-01-01 and 1888-03-00 == 1888-03-01
+ *
+ * The output will set to the number of days after 1970-01-01 or 0, if the input
+ * represents a date belonging to the largest allowed age group.
+ *
+ * @param in Input string representation of the date
+ * @param mask Age mask
+ * @param[out] out Where to write the result
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_parse_coarse_date (
+ const char *in,
+ const struct TALER_AgeMask *mask,
+ uint32_t *out);
+
+
+/**
+ * @brief Parses a string as a list of age groups.
+ *
+ * The string must consist of a colon-separated list of increasing integers
+ * between 0 and 31. Each entry represents the beginning of a new age group.
+ * F.e. the string
+ *
+ * "8:10:12:14:16:18:21"
+ *
+ * represents the following list of eight age groups:
+ *
+ * | Group | Ages |
+ * | -----:|:------------- |
+ * | 0 | 0, 1, ..., 7 |
+ * | 1 | 8, 9 |
+ * | 2 | 10, 11 |
+ * | 3 | 12, 13 |
+ * | 4 | 14, 15 |
+ * | 5 | 16, 17 |
+ * | 6 | 18, 19, 20 |
+ * | 7 | 21, ... |
+ *
+ * which is then encoded as a bit mask with the corresponding bits set:
+ *
+ * 31 24 16 8 0
+ * | | | | |
+ * oooooooo oo1oo1o1 o1o1o1o1 ooooooo1
+ *
+ * @param groups String representation of age groups
+ * @param[out] mask Mask representation for age restriction.
+ * @return Error, if age groups were invalid, OK otherwise.
+ */
+enum GNUNET_GenericReturnValue
+TALER_parse_age_group_string (
+ const char *groups,
+ struct TALER_AgeMask *mask);
+
+
+/**
+ * @brief Encodes the age mask into a string, like "8:10:12:14:16:18:21"
+ *
+ * NOTE: This function uses a static buffer. It is not safe to call this
+ * function concurrently.
+ *
+ * @param mask Age mask
+ * @return String representation of the age mask.
+ * Can be used as value in the TALER config.
+ */
+const char *
+TALER_age_mask_to_string (
+ const struct TALER_AgeMask *mask);
+
+
+/**
+ * @brief returns the age group of a given age for a given age mask
+ *
+ * @param mask Age mask
+ * @param age The given age
+ * @return age group
+ */
+uint8_t
+TALER_get_age_group (
+ const struct TALER_AgeMask *mask,
+ uint8_t age);
+
+
+/**
+ * @brief Parses a JSON object { "age_groups": "a:b:...y:z" }.
+ *
+ * @param root is the json object
+ * @param[out] mask on success, will contain the age mask
+ * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure.
+ */
+enum GNUNET_GenericReturnValue
+TALER_JSON_parse_age_groups (const json_t *root,
+ struct TALER_AgeMask *mask);
+
+
+/**
+ * @brief Return the lowest age in the corresponding group for a given age
+ * according the given age mask.
+ *
+ * @param mask age mask
+ * @param age age to check
+ * @return lowest age in corresponding age group
+ */
+uint8_t
+TALER_get_lowest_age (
+ const struct TALER_AgeMask *mask,
+ uint8_t age);
+
+
+/**
+ * @brief Get the lowest age for the largest age group
+ *
+ * @param mask the age mask
+ * @return lowest age for the largest age group
+ */
+#define TALER_adult_age(mask) \
+ sizeof((mask)->bits) * 8 - __builtin_clz ((mask)->bits) - 1
+
+/**
+ * Handle to an external process that will assist
+ * with some JSON-to-JSON conversion.
+ */
+struct TALER_JSON_ExternalConversion;
+
+/**
+ * Type of a callback that receives a JSON @a result.
+ *
+ * @param cls closure
+ * @param status_type how did the process die
+ * @param code termination status code from the process
+ * @param result some JSON result, NULL if we failed to get an JSON output
+ */
+typedef void
+(*TALER_JSON_JsonCallback) (void *cls,
+ enum GNUNET_OS_ProcessStatusType status_type,
+ unsigned long code,
+ const json_t *result);
+
+
+/**
+ * Launch some external helper @a binary to convert some @a input
+ * and eventually call @a cb with the result.
+ *
+ * @param input JSON to serialize and pass to the helper process
+ * @param cb function to call on the result
+ * @param cb_cls closure for @a cb
+ * @param binary name of the binary to execute
+ * @param ... NULL-terminated list of arguments for the @a binary,
+ * usually starting with again the name of the binary
+ * @return handle to cancel the operation (and kill the helper)
+ */
+struct TALER_JSON_ExternalConversion *
+TALER_JSON_external_conversion_start (const json_t *input,
+ TALER_JSON_JsonCallback cb,
+ void *cb_cls,
+ const char *binary,
+ ...);
+
+/**
+ * Abort external conversion, killing the process and preventing
+ * the callback from being called. Must not be called after the
+ * callback was invoked.
+ *
+ * @param[in] ec external conversion handle to cancel
+ */
+void
+TALER_JSON_external_conversion_stop (
+ struct TALER_JSON_ExternalConversion *ec);
+
+#undef __TALER_UTIL_LIB_H_INSIDE__
+
#endif
diff --git a/src/json/Makefile.am b/src/json/Makefile.am
index 2910d0773..ce863cb7e 100644
--- a/src/json/Makefile.am
+++ b/src/json/Makefile.am
@@ -10,26 +10,28 @@ lib_LTLIBRARIES = \
libtalerjson.la
libtalerjson_la_SOURCES = \
+ i18n.c \
json.c \
json_helper.c \
+ json_pack.c \
json_wire.c
libtalerjson_la_LDFLAGS = \
- -version-info 1:0:1 \
- -export-dynamic -no-undefined
+ -version-info 3:0:1 \
+ -no-undefined
libtalerjson_la_LIBADD = \
- -lgnunetjson \
$(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetjson \
-lgnunetutil \
+ -lunistring \
-ljansson \
+ -lm \
$(XLIB)
TESTS = \
- test_json \
- test_json_wire
+ test_json
check_PROGRAMS= \
- test_json \
- test_json_wire
+ test_json
test_json_SOURCES = \
test_json.c
@@ -39,13 +41,3 @@ test_json_LDADD = \
$(top_builddir)/src/util/libtalerutil.la \
-lgnunetutil \
-ljansson
-
-
-test_json_wire_SOURCES = \
- test_json_wire.c
-test_json_wire_LDADD = \
- $(top_builddir)/src/json/libtalerjson.la \
- -lgnunetjson \
- $(top_builddir)/src/util/libtalerutil.la \
- -lgnunetutil \
- -ljansson
diff --git a/src/json/i18n.c b/src/json/i18n.c
new file mode 100644
index 000000000..f927a71e1
--- /dev/null
+++ b/src/json/i18n.c
@@ -0,0 +1,134 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 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 json/i18n.c
+ * @brief helper functions for i18n in JSON processing
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_json_lib.h"
+
+
+const json_t *
+TALER_JSON_extract_i18n (const json_t *object,
+ const char *language_pattern,
+ const char *field)
+{
+ const json_t *ret;
+ json_t *i18n;
+ double quality = -1;
+
+ ret = json_object_get (object,
+ field);
+ if (NULL == ret)
+ return NULL; /* field MUST exist in object */
+ {
+ char *name;
+
+ GNUNET_asprintf (&name,
+ "%s_i18n",
+ field);
+ i18n = json_object_get (object,
+ name);
+ GNUNET_free (name);
+ }
+ if (NULL == i18n)
+ return ret;
+ {
+ const char *key;
+ json_t *value;
+
+ json_object_foreach (i18n, key, value) {
+ double q = TALER_language_matches (language_pattern,
+ key);
+ if (q > quality)
+ {
+ quality = q;
+ ret = value;
+ }
+ }
+ }
+ return ret;
+}
+
+
+bool
+TALER_JSON_check_i18n (const json_t *i18n)
+{
+ const char *field;
+ json_t *member;
+
+ if (! json_is_object (i18n))
+ return false;
+ json_object_foreach ((json_t *) i18n, field, member)
+ {
+ if (! json_is_string (member))
+ return false;
+ /* Field name must be either of format "en_UK"
+ or just "en"; we do not care about capitalization;
+ for syntax, see GNU Gettext manual, including
+ appendix A for rare language codes. */
+ switch (strlen (field))
+ {
+ case 0:
+ case 1:
+ return false;
+ case 2:
+ if (! isalpha (field[0]))
+ return false;
+ if (! isalpha (field[1]))
+ return false;
+ break;
+ case 3:
+ case 4:
+ return false;
+ case 5:
+ if (! isalpha (field[0]))
+ return false;
+ if (! isalpha (field[1]))
+ return false;
+ if ('_' != field[2])
+ return false;
+ if (! isalpha (field[3]))
+ return false;
+ if (! isalpha (field[4]))
+ return false;
+ break;
+ case 6:
+ if (! isalpha (field[0]))
+ return false;
+ if (! isalpha (field[1]))
+ return false;
+ if ('_' != field[2])
+ return false;
+ if (! isalpha (field[3]))
+ return false;
+ if (! isalpha (field[4]))
+ return false;
+ if (! isalpha (field[5]))
+ return false;
+ break;
+ default:
+ return false;
+ }
+ }
+ return true;
+}
+
+
+/* end of i18n.c */
diff --git a/src/json/json.c b/src/json/json.c
index f0c0aff57..639bd530c 100644
--- a/src/json/json.c
+++ b/src/json/json.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014, 2015, 2016 Taler Systems SA
+ Copyright (C) 2014, 2015, 2016, 2020, 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
@@ -17,71 +17,793 @@
* @file json/json.c
* @brief helper functions for JSON processing using libjansson
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include "taler_util.h"
#include "taler_json_lib.h"
+#include <unistr.h>
/**
- * Hash a JSON object for binary signing.
+ * Check if @a json contains a 'real' value anywhere.
*
- * See https://tools.ietf.org/html/draft-rundgren-json-canonicalization-scheme-15
- * for fun JSON canonicalization problems. Callers must ensure that
- * those are avoided in the input. We will use libjanson's "JSON_COMPACT"
- * encoding for whitespace and "JSON_SORT_KEYS" to canonicalize as best
- * as we can.
+ * @param json json to check
+ * @return true if a real is in it somewhere
+ */
+static bool
+contains_real (const json_t *json)
+{
+ if (json_is_real (json))
+ return true;
+ if (json_is_object (json))
+ {
+ json_t *member;
+ const char *name;
+
+ json_object_foreach ((json_t *) json, name, member)
+ if (contains_real (member))
+ return true;
+ return false;
+ }
+ if (json_is_array (json))
+ {
+ json_t *member;
+ size_t index;
+
+ json_array_foreach ((json_t *) json, index, member)
+ if (contains_real (member))
+ return true;
+ return false;
+ }
+ return false;
+}
+
+
+/**
+ * Dump the @a json to a string and hash it.
*
- * @param[in] json some JSON value
- * @param[out] hc resulting hash code
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ * @param json value to hash
+ * @param salt salt value to include when using HKDF,
+ * NULL to not use any salt and to use SHA512
+ * @param[out] hc where to store the hash
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if @a json was not hash-able
+ * #GNUNET_SYSERR on failure
*/
-int
-TALER_JSON_hash (const json_t *json,
- struct GNUNET_HashCode *hc)
+static enum GNUNET_GenericReturnValue
+dump_and_hash (const json_t *json,
+ const char *salt,
+ struct GNUNET_HashCode *hc)
{
char *wire_enc;
size_t len;
+ if (NULL == json)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_NO;
+ }
+ if (contains_real (json))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_NO;
+ }
if (NULL == (wire_enc = json_dumps (json,
- JSON_COMPACT | JSON_SORT_KEYS)))
+ JSON_ENCODE_ANY
+ | JSON_COMPACT
+ | JSON_SORT_KEYS)))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
- len = strlen (wire_enc) + 1;
- GNUNET_CRYPTO_hash (wire_enc,
- len,
- hc);
+ len = TALER_rfc8785encode (&wire_enc);
+ if (NULL == salt)
+ {
+ GNUNET_CRYPTO_hash (wire_enc,
+ len,
+ hc);
+ }
+ else
+ {
+ if (GNUNET_YES !=
+ GNUNET_CRYPTO_kdf (hc,
+ sizeof (*hc),
+ salt,
+ strlen (salt) + 1,
+ wire_enc,
+ len,
+ NULL,
+ 0))
+ {
+ GNUNET_break (0);
+ free (wire_enc);
+ return GNUNET_SYSERR;
+ }
+ }
free (wire_enc);
return GNUNET_OK;
}
/**
- * Extract the Taler error code from the given @a json object.
- * Note that #TALER_EC_NONE is returned if no "code" is present.
+ * Replace "forgettable" parts of a JSON object with their salted hash.
*
- * @param json response to extract the error code from
- * @return the "code" value from @a json
+ * @param[in] in some JSON value
+ * @param[out] out resulting JSON value
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if @a json was not hash-able
+ * #GNUNET_SYSERR on failure
*/
+static enum GNUNET_GenericReturnValue
+forget (const json_t *in,
+ json_t **out)
+{
+ if (json_is_real (in))
+ {
+ /* floating point is not allowed! */
+ GNUNET_break_op (0);
+ return GNUNET_NO;
+ }
+ if (json_is_array (in))
+ {
+ /* array is a JSON array */
+ size_t index;
+ json_t *value;
+ json_t *ret;
+
+ ret = json_array ();
+ if (NULL == ret)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ json_array_foreach (in, index, value) {
+ enum GNUNET_GenericReturnValue iret;
+ json_t *t;
+
+ iret = forget (value,
+ &t);
+ if (GNUNET_OK != iret)
+ {
+ json_decref (ret);
+ return iret;
+ }
+ if (0 != json_array_append_new (ret,
+ t))
+ {
+ GNUNET_break (0);
+ json_decref (ret);
+ return GNUNET_SYSERR;
+ }
+ }
+ *out = ret;
+ return GNUNET_OK;
+ }
+ if (json_is_object (in))
+ {
+ json_t *ret;
+ const char *key;
+ json_t *value;
+ json_t *fg;
+ json_t *rx;
+
+ fg = json_object_get (in,
+ "$forgettable");
+ rx = json_object_get (in,
+ "$forgotten");
+ if (NULL != rx)
+ {
+ rx = json_deep_copy (rx); /* should be shallow
+ by structure, but
+ deep copy is safer */
+ if (NULL == rx)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ ret = json_object ();
+ if (NULL == ret)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ json_object_foreach ((json_t*) in, key, value) {
+ json_t *t;
+ json_t *salt;
+ enum GNUNET_GenericReturnValue iret;
+
+ if (fg == value)
+ continue; /* skip! */
+ if (rx == value)
+ continue; /* skip! */
+ if ( (NULL != rx) &&
+ (NULL !=
+ json_object_get (rx,
+ key)) )
+ {
+ (void) json_object_del (ret,
+ key);
+ continue; /* already forgotten earlier */
+ }
+ iret = forget (value,
+ &t);
+ if (GNUNET_OK != iret)
+ {
+ json_decref (ret);
+ json_decref (rx);
+ return iret;
+ }
+ if ( (NULL != fg) &&
+ (NULL != (salt = json_object_get (fg,
+ key))) )
+ {
+ /* 't' is to be forgotten! */
+ struct GNUNET_HashCode hc;
+
+ if (! json_is_string (salt))
+ {
+ GNUNET_break_op (0);
+ json_decref (ret);
+ json_decref (rx);
+ json_decref (t);
+ return GNUNET_NO;
+ }
+ iret = dump_and_hash (t,
+ json_string_value (salt),
+ &hc);
+ if (GNUNET_OK != iret)
+ {
+ json_decref (ret);
+ json_decref (rx);
+ json_decref (t);
+ return iret;
+ }
+ json_decref (t);
+ /* scrub salt */
+ if (0 !=
+ json_object_del (fg,
+ key))
+ {
+ GNUNET_break_op (0);
+ json_decref (ret);
+ json_decref (rx);
+ return GNUNET_NO;
+ }
+ if (NULL == rx)
+ rx = json_object ();
+ if (NULL == rx)
+ {
+ GNUNET_break (0);
+ json_decref (ret);
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ json_object_set_new (rx,
+ key,
+ GNUNET_JSON_from_data_auto (&hc)))
+ {
+ GNUNET_break (0);
+ json_decref (ret);
+ json_decref (rx);
+ return GNUNET_SYSERR;
+ }
+ }
+ else
+ {
+ /* 't' to be used without 'forgetting' */
+ if (0 !=
+ json_object_set_new (ret,
+ key,
+ t))
+ {
+ GNUNET_break (0);
+ json_decref (ret);
+ json_decref (rx);
+ return GNUNET_SYSERR;
+ }
+ }
+ } /* json_object_foreach */
+ if ( (NULL != rx) &&
+ (0 !=
+ json_object_set_new (ret,
+ "$forgotten",
+ rx)) )
+ {
+ GNUNET_break (0);
+ json_decref (ret);
+ return GNUNET_SYSERR;
+ }
+ *out = ret;
+ return GNUNET_OK;
+ }
+ *out = json_incref ((json_t *) in);
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_hash (const json_t *json,
+ struct TALER_PrivateContractHashP *hc)
+{
+ enum GNUNET_GenericReturnValue ret;
+ json_t *cjson;
+ json_t *dc;
+
+ dc = json_deep_copy (json);
+ ret = forget (dc,
+ &cjson);
+ json_decref (dc);
+ if (GNUNET_OK != ret)
+ return ret;
+ ret = dump_and_hash (cjson,
+ NULL,
+ &hc->hash);
+ json_decref (cjson);
+ return ret;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_mark_forgettable (json_t *json,
+ const char *field)
+{
+ json_t *fg;
+ struct GNUNET_ShortHashCode salt;
+
+ if (! json_is_object (json))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* check field name is legal for forgettable field */
+ for (const char *f = field; '\0' != *f; f++)
+ {
+ char c = *f;
+
+ if ( (c >= 'a') && (c <= 'z') )
+ continue;
+ if ( (c >= 'A') && (c <= 'Z') )
+ continue;
+ if ( (c >= '0') && (c <= '9') )
+ continue;
+ if ('_' == c)
+ continue;
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (NULL == json_object_get (json,
+ field))
+ {
+ /* field must exist */
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ fg = json_object_get (json,
+ "$forgettable");
+ if (NULL == fg)
+ {
+ fg = json_object ();
+ if (0 !=
+ json_object_set_new (json,
+ "$forgettable",
+ fg))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &salt,
+ sizeof (salt));
+ if (0 !=
+ json_object_set_new (fg,
+ field,
+ GNUNET_JSON_from_data_auto (&salt)))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_part_forget (json_t *json,
+ const char *field)
+{
+ json_t *fg;
+ const json_t *part;
+ json_t *fp;
+ json_t *rx;
+ struct GNUNET_HashCode hc;
+ const char *salt;
+ enum GNUNET_GenericReturnValue ret;
+
+ if (! json_is_object (json))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (NULL == (part = json_object_get (json,
+ field)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Did not find field `%s' we were asked to forget\n",
+ field);
+ return GNUNET_SYSERR;
+ }
+ fg = json_object_get (json,
+ "$forgettable");
+ if (NULL == fg)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Did not find '$forgettable' attribute trying to forget field `%s'\n",
+ field);
+ return GNUNET_SYSERR;
+ }
+ rx = json_object_get (json,
+ "$forgotten");
+ if (NULL == rx)
+ {
+ rx = json_object ();
+ if (0 !=
+ json_object_set_new (json,
+ "$forgotten",
+ rx))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ if (NULL !=
+ json_object_get (rx,
+ field))
+ {
+ if (! json_is_null (json_object_get (json,
+ field)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field `%s' market as forgotten, but still exists!\n",
+ field);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Already forgot field `%s'\n",
+ field);
+ return GNUNET_NO;
+ }
+ salt = json_string_value (json_object_get (fg,
+ field));
+ if (NULL == salt)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Did not find required salt to forget field `%s'\n",
+ field);
+ return GNUNET_SYSERR;
+ }
+
+ /* need to recursively forget to compute 'hc' */
+ ret = forget (part,
+ &fp);
+ if (GNUNET_OK != ret)
+ return ret;
+ if (GNUNET_OK !=
+ dump_and_hash (fp,
+ salt,
+ &hc))
+ {
+ json_decref (fp);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ json_decref (fp);
+ /* drop salt */
+ if (0 !=
+ json_object_del (fg,
+ field))
+ {
+ json_decref (fp);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* remember field as 'forgotten' */
+ if (0 !=
+ json_object_set_new (rx,
+ field,
+ GNUNET_JSON_from_data_auto (&hc)))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* finally, set 'forgotten' field to null */
+ if (0 !=
+ json_object_del (json,
+ field))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Loop over all of the values of a '$forgettable' object. Replace 'True'
+ * values with proper random salts. Fails if any forgettable values are
+ * neither 'True' nor valid salts (strings).
+ *
+ * @param[in,out] f JSON to transform
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+seed_forgettable (json_t *f)
+{
+ const char *key;
+ json_t *val;
+
+ json_object_foreach (f,
+ key,
+ val)
+ {
+ if (json_is_string (val))
+ continue;
+ if (json_is_true (val))
+ {
+ struct GNUNET_ShortHashCode sh;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &sh,
+ sizeof (sh));
+ if (0 !=
+ json_object_set_new (f,
+ key,
+ GNUNET_JSON_from_data_auto (&sh)))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ continue;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Forgettable field `%s' has invalid value\n",
+ key);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_seed_forgettable (const json_t *spec,
+ json_t *contract)
+{
+ if (json_is_object (spec))
+ {
+ const char *key;
+ json_t *val;
+
+ json_object_foreach ((json_t *) spec,
+ key,
+ val)
+ {
+ json_t *cval = json_object_get (contract,
+ key);
+
+ if (0 == strcmp ("$forgettable",
+ key))
+ {
+ json_t *xval = json_deep_copy (val);
+
+ if (GNUNET_OK !=
+ seed_forgettable (xval))
+ {
+ json_decref (xval);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (contract,
+ "$forgettable",
+ xval));
+ continue;
+ }
+ if (NULL == cval)
+ continue;
+ if (GNUNET_OK !=
+ TALER_JSON_contract_seed_forgettable (val,
+ cval))
+ return GNUNET_SYSERR;
+ }
+ }
+ if (json_is_array (spec))
+ {
+ size_t index;
+ json_t *val;
+
+ json_array_foreach ((json_t *) spec,
+ index,
+ val)
+ {
+ json_t *ival = json_array_get (contract,
+ index);
+
+ if (NULL == ival)
+ continue;
+ if (GNUNET_OK !=
+ TALER_JSON_contract_seed_forgettable (val,
+ ival))
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse a json path.
+ *
+ * @param obj the object that the path is relative to.
+ * @param prev the parent of @e obj.
+ * @param path the path to parse.
+ * @param cb the callback to call, if we get to the end of @e path.
+ * @param cb_cls the closure for the callback.
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if @e path is malformed.
+ */
+static enum GNUNET_GenericReturnValue
+parse_path (json_t *obj,
+ json_t *prev,
+ const char *path,
+ TALER_JSON_ExpandPathCallback cb,
+ void *cb_cls)
+{
+ char *id = GNUNET_strdup (path);
+ char *next_id = strchr (id,
+ '.');
+ char *next_path;
+ char *bracket;
+ json_t *next_obj = NULL;
+ char *next_dot;
+
+ GNUNET_assert (NULL != id); /* make stupid compiler happy */
+ if (NULL == next_id)
+ {
+ cb (cb_cls,
+ id,
+ prev);
+ GNUNET_free (id);
+ return GNUNET_OK;
+ }
+ bracket = strchr (next_id,
+ '[');
+ *next_id = '\0';
+ next_id++;
+ next_path = GNUNET_strdup (next_id);
+ next_dot = strchr (next_id,
+ '.');
+ if (NULL != next_dot)
+ *next_dot = '\0';
+ /* If this is the first time this is called, make sure id is "$" */
+ if ( (NULL == prev) &&
+ (0 != strcmp (id,
+ "$")))
+ {
+ GNUNET_free (id);
+ GNUNET_free (next_path);
+ return GNUNET_SYSERR;
+ }
+
+ /* Check for bracketed indices */
+ if (NULL != bracket)
+ {
+ char *end_bracket = strchr (bracket,
+ ']');
+ if (NULL == end_bracket)
+ {
+ GNUNET_free (id);
+ GNUNET_free (next_path);
+ return GNUNET_SYSERR;
+ }
+ *end_bracket = '\0';
+
+ *bracket = '\0';
+ bracket++;
+
+ json_t *array = json_object_get (obj,
+ next_id);
+ if (0 == strcmp (bracket,
+ "*"))
+ {
+ size_t index;
+ json_t *value;
+ int ret = GNUNET_OK;
+
+ json_array_foreach (array, index, value) {
+ ret = parse_path (value,
+ obj,
+ next_path,
+ cb,
+ cb_cls);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_free (id);
+ GNUNET_free (next_path);
+ return ret;
+ }
+ }
+ }
+ else
+ {
+ unsigned int index;
+ char dummy;
+
+ if (1 != sscanf (bracket,
+ "%u%c",
+ &index,
+ &dummy))
+ {
+ GNUNET_free (id);
+ GNUNET_free (next_path);
+ return GNUNET_SYSERR;
+ }
+ next_obj = json_array_get (array,
+ index);
+ }
+ }
+ else
+ {
+ /* No brackets, so just fetch the object by name */
+ next_obj = json_object_get (obj,
+ next_id);
+ }
+
+ if (NULL != next_obj)
+ {
+ int ret = parse_path (next_obj,
+ obj,
+ next_path,
+ cb,
+ cb_cls);
+ GNUNET_free (id);
+ GNUNET_free (next_path);
+ return ret;
+ }
+ GNUNET_free (id);
+ GNUNET_free (next_path);
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_expand_path (json_t *json,
+ const char *path,
+ TALER_JSON_ExpandPathCallback cb,
+ void *cb_cls)
+{
+ return parse_path (json,
+ NULL,
+ path,
+ cb,
+ cb_cls);
+}
+
+
enum TALER_ErrorCode
TALER_JSON_get_error_code (const json_t *json)
{
const json_t *jc;
if (NULL == json)
- {
- GNUNET_break_op (0);
- return TALER_EC_INVALID_RESPONSE;
- }
+ return TALER_EC_GENERIC_INVALID_RESPONSE;
jc = json_object_get (json, "code");
/* The caller already knows that the JSON represents an error,
so we are dealing with a missing error code here. */
if (NULL == jc)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Expected Taler error code `code' in JSON, but field does not exist!\n");
return TALER_EC_INVALID;
}
@@ -92,14 +814,27 @@ TALER_JSON_get_error_code (const json_t *json)
}
-/**
- * Extract the Taler error code from the given @a data object, which is expected to be in JSON.
- * Note that #TALER_EC_INVALID is returned if no "code" is present or if @a data is not in JSON.
- *
- * @param data response to extract the error code from
- * @param data_size number of bytes in @a data
- * @return the "code" value from @a json
- */
+const char *
+TALER_JSON_get_error_hint (const json_t *json)
+{
+ const json_t *jc;
+
+ if (NULL == json)
+ return NULL;
+ jc = json_object_get (json,
+ "hint");
+ if (NULL == jc)
+ return NULL; /* no hint, is allowed */
+ if (! json_is_string (jc))
+ {
+ /* Hints must be strings */
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ return json_string_value (jc);
+}
+
+
enum TALER_ErrorCode
TALER_JSON_get_error_code2 (const void *data,
size_t data_size)
@@ -108,8 +843,9 @@ TALER_JSON_get_error_code2 (const void *data,
enum TALER_ErrorCode ec;
json_error_t err;
- json = json_loads (data,
+ json = json_loadb (data,
data_size,
+ JSON_REJECT_DUPLICATES,
&err);
if (NULL == json)
return TALER_EC_INVALID;
@@ -121,4 +857,43 @@ TALER_JSON_get_error_code2 (const void *data,
}
+void
+TALER_deposit_policy_hash (const json_t *policy,
+ struct TALER_ExtensionPolicyHashP *ech)
+{
+ GNUNET_assert (GNUNET_OK ==
+ dump_and_hash (policy,
+ "taler-extensions-policy",
+ &ech->hash));
+}
+
+
+char *
+TALER_JSON_canonicalize (const json_t *input)
+{
+ char *wire_enc;
+
+ if (NULL == (wire_enc = json_dumps (input,
+ JSON_ENCODE_ANY
+ | JSON_COMPACT
+ | JSON_SORT_KEYS)))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ TALER_rfc8785encode (&wire_enc);
+ return wire_enc;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_extensions_manifests_hash (const json_t *manifests,
+ struct TALER_ExtensionManifestsHashP *ech)
+{
+ return dump_and_hash (manifests,
+ "taler-extensions-manifests",
+ &ech->hash);
+}
+
+
/* End of json/json.c */
diff --git a/src/json/json_helper.c b/src/json/json_helper.c
index 746b39e49..0a533610b 100644
--- a/src/json/json_helper.c
+++ b/src/json/json_helper.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 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
@@ -27,11 +27,28 @@
/**
- * Convert a TALER amount to a JSON object.
+ * Convert string value to numeric cipher value.
*
- * @param amount the amount
- * @return a json object describing the amount
+ * @param cipher_s input string
+ * @return numeric cipher value
*/
+static enum GNUNET_CRYPTO_BlindSignatureAlgorithm
+string_to_cipher (const char *cipher_s)
+{
+ if ((0 == strcasecmp (cipher_s,
+ "RSA")) ||
+ (0 == strcasecmp (cipher_s,
+ "RSA+age_restricted")))
+ return GNUNET_CRYPTO_BSA_RSA;
+ if ((0 == strcasecmp (cipher_s,
+ "CS")) ||
+ (0 == strcasecmp (cipher_s,
+ "CS+age_restricted")))
+ return GNUNET_CRYPTO_BSA_CS;
+ return GNUNET_CRYPTO_BSA_INVALID;
+}
+
+
json_t *
TALER_JSON_from_amount (const struct TALER_Amount *amount)
{
@@ -48,35 +65,19 @@ TALER_JSON_from_amount (const struct TALER_Amount *amount)
/**
- * Convert a TALER amount to a JSON object.
- *
- * @param amount the amount
- * @return a json object describing the amount
- */
-json_t *
-TALER_JSON_from_amount_nbo (const struct TALER_AmountNBO *amount)
-{
- struct TALER_Amount a;
-
- TALER_amount_ntoh (&a,
- amount);
- return TALER_JSON_from_amount (&a);
-}
-
-
-/**
* Parse given JSON object to Amount
*
- * @param cls closure, NULL
+ * @param cls closure, expected currency, or NULL
* @param root the json object representing data
* @param[out] spec where to write the data
* @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
*/
-static int
+static enum GNUNET_GenericReturnValue
parse_amount (void *cls,
json_t *root,
struct GNUNET_JSON_Specification *spec)
{
+ const char *currency = cls;
struct TALER_Amount *r_amount = spec->ptr;
(void) cls;
@@ -92,57 +93,303 @@ parse_amount (void *cls,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
+ if ( (NULL != currency) &&
+ (0 !=
+ strcasecmp (currency,
+ r_amount->currency)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Expected currency `%s', but amount used currency `%s' in field `%s'\n",
+ currency,
+ r_amount->currency,
+ spec->field);
+ return GNUNET_SYSERR;
+ }
return GNUNET_OK;
}
-/**
- * Provide specification to parse given JSON object to an amount.
- *
- * @param name name of the amount field in the JSON
- * @param[out] r_amount where the amount has to be written
- */
struct GNUNET_JSON_Specification
TALER_JSON_spec_amount (const char *name,
+ const char *currency,
struct TALER_Amount *r_amount)
{
struct GNUNET_JSON_Specification ret = {
.parser = &parse_amount,
.cleaner = NULL,
+ .cls = (void *) currency,
+ .field = name,
+ .ptr = r_amount,
+ .ptr_size = 0,
+ .size_ptr = NULL
+ };
+
+ GNUNET_assert (NULL != currency);
+ return ret;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_amount_any (const char *name,
+ struct TALER_Amount *r_amount)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_amount,
+ .cleaner = NULL,
.cls = NULL,
.field = name,
.ptr = r_amount,
.ptr_size = 0,
.size_ptr = NULL
};
+
return ret;
}
/**
- * Parse given JSON object to Amount in NBO.
+ * Parse given JSON object to currency spec.
*
* @param cls closure, NULL
* @param root the json object representing data
* @param[out] spec where to write the data
* @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
*/
-static int
-parse_amount_nbo (void *cls,
- json_t *root,
- struct GNUNET_JSON_Specification *spec)
+static enum GNUNET_GenericReturnValue
+parse_cspec (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
{
- struct TALER_AmountNBO *r_amount = spec->ptr;
+ struct TALER_CurrencySpecification *r_cspec = spec->ptr;
+ const char *currency = spec->cls;
+ const char *name;
+ uint32_t fid;
+ uint32_t fnd;
+ uint32_t ftzd;
+ const json_t *map;
+ struct GNUNET_JSON_Specification gspec[] = {
+ GNUNET_JSON_spec_string ("name",
+ &name),
+ GNUNET_JSON_spec_uint32 ("num_fractional_input_digits",
+ &fid),
+ GNUNET_JSON_spec_uint32 ("num_fractional_normal_digits",
+ &fnd),
+ GNUNET_JSON_spec_uint32 ("num_fractional_trailing_zero_digits",
+ &ftzd),
+ GNUNET_JSON_spec_object_const ("alt_unit_names",
+ &map),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *emsg;
+ unsigned int eline;
+
+ memset (r_cspec->currency,
+ 0,
+ sizeof (r_cspec->currency));
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ gspec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ spec[eline].field,
+ eline,
+ emsg);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (strlen (currency) >= TALER_CURRENCY_LEN)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if ( (fid > TALER_AMOUNT_FRAC_LEN) ||
+ (fnd > TALER_AMOUNT_FRAC_LEN) ||
+ (ftzd > TALER_AMOUNT_FRAC_LEN) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_check_currency (currency))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ strcpy (r_cspec->currency,
+ currency);
+ if (GNUNET_OK !=
+ TALER_check_currency_scale_map (map))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ r_cspec->name = GNUNET_strdup (name);
+ r_cspec->map_alt_unit_names = json_incref ((json_t *) map);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Cleanup data left from parsing encrypted contract.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_cspec (void *cls,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_CurrencySpecification *cspec = spec->ptr;
(void) cls;
- if (! json_is_string (root))
+ GNUNET_free (cspec->name);
+ json_decref (cspec->map_alt_unit_names);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_currency_specification (
+ const char *name,
+ const char *currency,
+ struct TALER_CurrencySpecification *r_cspec)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_cspec,
+ .cleaner = &clean_cspec,
+ .cls = (void *) currency,
+ .field = name,
+ .ptr = r_cspec,
+ .ptr_size = sizeof (*r_cspec),
+ .size_ptr = NULL
+ };
+
+ memset (r_cspec,
+ 0,
+ sizeof (*r_cspec));
+ return ret;
+}
+
+
+static enum GNUNET_GenericReturnValue
+parse_denomination_group (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_DenominationGroup *group = spec->ptr;
+ const char *cipher;
+ const char *currency = cls;
+ bool age_mask_missing = false;
+ bool has_age_restricted_suffix = false;
+ struct GNUNET_JSON_Specification gspec[] = {
+ GNUNET_JSON_spec_string ("cipher",
+ &cipher),
+ TALER_JSON_spec_amount ("value",
+ currency,
+ &group->value),
+ TALER_JSON_SPEC_DENOM_FEES ("fee",
+ currency,
+ &group->fees),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("age_mask",
+ &group->age_mask.bits),
+ &age_mask_missing),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *emsg;
+ unsigned int eline;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ gspec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ spec[eline].field,
+ eline,
+ emsg);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ group->cipher = string_to_cipher (cipher);
+ if (GNUNET_CRYPTO_BSA_INVALID == group->cipher)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* age_mask and suffix must be consistent */
+ has_age_restricted_suffix =
+ (NULL != strstr (cipher, "+age_restricted"));
+ if (has_age_restricted_suffix && age_mask_missing)
{
- GNUNET_break (0);
+ GNUNET_break_op (0);
return GNUNET_SYSERR;
}
+
+ if (age_mask_missing)
+ group->age_mask.bits = 0;
+
+ return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denomination_group (const char *name,
+ const char *currency,
+ struct TALER_DenominationGroup *group)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .cls = (void *) currency,
+ .parser = &parse_denomination_group,
+ .field = name,
+ .ptr = group,
+ .ptr_size = sizeof(*group)
+ };
+
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to an encrypted contract.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_econtract (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_EncryptedContract *econtract = spec->ptr;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_varsize ("econtract",
+ &econtract->econtract,
+ &econtract->econtract_size),
+ GNUNET_JSON_spec_fixed_auto ("econtract_sig",
+ &econtract->econtract_sig),
+ GNUNET_JSON_spec_fixed_auto ("contract_pub",
+ &econtract->contract_pub),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *emsg;
+ unsigned int eline;
+
+ (void) cls;
if (GNUNET_OK !=
- TALER_string_to_amount_nbo (json_string_value (root),
- r_amount))
+ GNUNET_JSON_parse (root,
+ ispec,
+ &emsg,
+ &eline))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
@@ -152,57 +399,1420 @@ parse_amount_nbo (void *cls,
/**
- * Provide specification to parse given JSON object to an amount.
+ * Cleanup data left from parsing encrypted contract.
*
- * @param name name of the amount field in the JSON
- * @param[out] r_amount where the amount has to be written
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
*/
+static void
+clean_econtract (void *cls,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_EncryptedContract *econtract = spec->ptr;
+
+ (void) cls;
+ GNUNET_free (econtract->econtract);
+}
+
+
struct GNUNET_JSON_Specification
-TALER_JSON_spec_amount_nbo (const char *name,
- struct TALER_AmountNBO *r_amount)
+TALER_JSON_spec_econtract (const char *name,
+ struct TALER_EncryptedContract *econtract)
{
struct GNUNET_JSON_Specification ret = {
- .parser = &parse_amount_nbo,
- .cleaner = NULL,
- .cls = NULL,
+ .parser = &parse_econtract,
+ .cleaner = &clean_econtract,
.field = name,
- .ptr = r_amount,
+ .ptr = econtract
+ };
+
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to an age commitmnet
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_age_commitment (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_AgeCommitment *age_commitment = spec->ptr;
+ json_t *pk;
+ unsigned int idx;
+ size_t num;
+
+ (void) cls;
+ if ( (NULL == root) ||
+ (! json_is_array (root)))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ num = json_array_size (root);
+ if (32 <= num || 0 == num)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ age_commitment->num = num;
+ age_commitment->keys =
+ GNUNET_new_array (num,
+ struct TALER_AgeCommitmentPublicKeyP);
+
+ json_array_foreach (root, idx, pk) {
+ const char *emsg;
+ unsigned int eline;
+ struct GNUNET_JSON_Specification pkspec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ NULL,
+ &age_commitment->keys[idx].pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (pk,
+ pkspec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ };
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Cleanup data left from parsing age commitment
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_age_commitment (void *cls,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_AgeCommitment *age_commitment = spec->ptr;
+
+ (void) cls;
+
+ if (NULL == age_commitment ||
+ NULL == age_commitment->keys)
+ return;
+
+ age_commitment->num = 0;
+ GNUNET_free (age_commitment->keys);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_age_commitment (const char *name,
+ struct TALER_AgeCommitment *age_commitment)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_age_commitment,
+ .cleaner = &clean_age_commitment,
+ .field = name,
+ .ptr = age_commitment
+ };
+
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to denomination public key.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_denom_pub (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_DenominationPublicKey *denom_pub = spec->ptr;
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub;
+ const char *cipher;
+ bool age_mask_missing = false;
+ struct GNUNET_JSON_Specification dspec[] = {
+ GNUNET_JSON_spec_string ("cipher",
+ &cipher),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("age_mask",
+ &denom_pub->age_mask.bits),
+ &age_mask_missing),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *emsg;
+ unsigned int eline;
+
+ (void) cls;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ dspec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (age_mask_missing)
+ denom_pub->age_mask.bits = 0;
+ bsign_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
+ bsign_pub->rc = 1;
+ bsign_pub->cipher = string_to_cipher (cipher);
+ switch (bsign_pub->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_rsa_public_key (
+ "rsa_public_key",
+ &bsign_pub->details.rsa_public_key),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ ispec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (bsign_pub);
+ return GNUNET_SYSERR;
+ }
+ denom_pub->bsign_pub_key = bsign_pub;
+ return GNUNET_OK;
+ }
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed ("cs_public_key",
+ &bsign_pub->details.cs_public_key,
+ sizeof (bsign_pub->details.cs_public_key)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ ispec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (bsign_pub);
+ return GNUNET_SYSERR;
+ }
+ denom_pub->bsign_pub_key = bsign_pub;
+ return GNUNET_OK;
+ }
+ }
+ GNUNET_break_op (0);
+ GNUNET_free (bsign_pub);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Cleanup data left from parsing denomination public key.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_denom_pub (void *cls,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_DenominationPublicKey *denom_pub = spec->ptr;
+
+ (void) cls;
+ TALER_denom_pub_free (denom_pub);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denom_pub (const char *field,
+ struct TALER_DenominationPublicKey *pk)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_denom_pub,
+ .cleaner = &clean_denom_pub,
+ .field = field,
+ .ptr = pk
+ };
+
+ pk->bsign_pub_key = NULL;
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object partially into a denomination public key.
+ *
+ * Depending on the cipher in cls, it parses the corresponding public key type.
+ *
+ * @param cls closure, enum GNUNET_CRYPTO_BlindSignatureAlgorithm
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_denom_pub_cipher (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_DenominationPublicKey *denom_pub = spec->ptr;
+ enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher =
+ (enum GNUNET_CRYPTO_BlindSignatureAlgorithm) (long) cls;
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub;
+ const char *emsg;
+ unsigned int eline;
+
+ bsign_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
+ bsign_pub->cipher = cipher;
+ bsign_pub->rc = 1;
+ switch (cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_rsa_public_key (
+ "rsa_pub",
+ &bsign_pub->details.rsa_public_key),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ ispec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (bsign_pub);
+ return GNUNET_SYSERR;
+ }
+ denom_pub->bsign_pub_key = bsign_pub;
+ return GNUNET_OK;
+ }
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed ("cs_pub",
+ &bsign_pub->details.cs_public_key,
+ sizeof (bsign_pub->details.cs_public_key)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ ispec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (bsign_pub);
+ return GNUNET_SYSERR;
+ }
+ denom_pub->bsign_pub_key = bsign_pub;
+ return GNUNET_OK;
+ }
+ }
+ GNUNET_break_op (0);
+ GNUNET_free (bsign_pub);
+ return GNUNET_SYSERR;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denom_pub_cipher (const char *field,
+ enum GNUNET_CRYPTO_BlindSignatureAlgorithm
+ cipher,
+ struct TALER_DenominationPublicKey *pk)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_denom_pub_cipher,
+ .cleaner = &clean_denom_pub,
+ .field = field,
+ .cls = (void *) cipher,
+ .ptr = pk
+ };
+
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to denomination signature.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_denom_sig (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_DenominationSignature *denom_sig = spec->ptr;
+ struct GNUNET_CRYPTO_UnblindedSignature *unblinded_sig;
+ const char *cipher;
+ struct GNUNET_JSON_Specification dspec[] = {
+ GNUNET_JSON_spec_string ("cipher",
+ &cipher),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *emsg;
+ unsigned int eline;
+
+ (void) cls;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ dspec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ unblinded_sig = GNUNET_new (struct GNUNET_CRYPTO_UnblindedSignature);
+ unblinded_sig->cipher = string_to_cipher (cipher);
+ unblinded_sig->rc = 1;
+ switch (unblinded_sig->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_rsa_signature (
+ "rsa_signature",
+ &unblinded_sig->details.rsa_signature),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ ispec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (unblinded_sig);
+ return GNUNET_SYSERR;
+ }
+ denom_sig->unblinded_sig = unblinded_sig;
+ return GNUNET_OK;
+ }
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("cs_signature_r",
+ &unblinded_sig->details.cs_signature.
+ r_point),
+ GNUNET_JSON_spec_fixed_auto ("cs_signature_s",
+ &unblinded_sig->details.cs_signature.
+ s_scalar),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ ispec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (unblinded_sig);
+ return GNUNET_SYSERR;
+ }
+ denom_sig->unblinded_sig = unblinded_sig;
+ return GNUNET_OK;
+ }
+ }
+ GNUNET_break_op (0);
+ GNUNET_free (unblinded_sig);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Cleanup data left from parsing denomination public key.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_denom_sig (void *cls,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_DenominationSignature *denom_sig = spec->ptr;
+
+ (void) cls;
+ TALER_denom_sig_free (denom_sig);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denom_sig (const char *field,
+ struct TALER_DenominationSignature *sig)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_denom_sig,
+ .cleaner = &clean_denom_sig,
+ .field = field,
+ .ptr = sig
+ };
+
+ sig->unblinded_sig = NULL;
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to blinded denomination signature.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_blinded_denom_sig (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_BlindedDenominationSignature *denom_sig = spec->ptr;
+ struct GNUNET_CRYPTO_BlindedSignature *blinded_sig;
+ const char *cipher;
+ struct GNUNET_JSON_Specification dspec[] = {
+ GNUNET_JSON_spec_string ("cipher",
+ &cipher),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *emsg;
+ unsigned int eline;
+
+ (void) cls;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ dspec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ blinded_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
+ blinded_sig->cipher = string_to_cipher (cipher);
+ blinded_sig->rc = 1;
+ switch (blinded_sig->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_rsa_signature (
+ "blinded_rsa_signature",
+ &blinded_sig->details.blinded_rsa_signature),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ ispec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (blinded_sig);
+ return GNUNET_SYSERR;
+ }
+ denom_sig->blinded_sig = blinded_sig;
+ return GNUNET_OK;
+ }
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_uint32 ("b",
+ &blinded_sig->details.blinded_cs_answer.b),
+ GNUNET_JSON_spec_fixed_auto ("s",
+ &blinded_sig->details.blinded_cs_answer.
+ s_scalar),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ ispec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (blinded_sig);
+ return GNUNET_SYSERR;
+ }
+ denom_sig->blinded_sig = blinded_sig;
+ return GNUNET_OK;
+ }
+ }
+ GNUNET_break_op (0);
+ GNUNET_free (blinded_sig);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Cleanup data left from parsing denomination public key.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_blinded_denom_sig (void *cls,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_BlindedDenominationSignature *denom_sig = spec->ptr;
+
+ (void) cls;
+ TALER_blinded_denom_sig_free (denom_sig);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_blinded_denom_sig (
+ const char *field,
+ struct TALER_BlindedDenominationSignature *sig)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_blinded_denom_sig,
+ .cleaner = &clean_blinded_denom_sig,
+ .field = field,
+ .ptr = sig
+ };
+
+ sig->blinded_sig = NULL;
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to blinded planchet.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_blinded_planchet (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_BlindedPlanchet *blinded_planchet = spec->ptr;
+ struct GNUNET_CRYPTO_BlindedMessage *blinded_message;
+ const char *cipher;
+ struct GNUNET_JSON_Specification dspec[] = {
+ GNUNET_JSON_spec_string ("cipher",
+ &cipher),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *emsg;
+ unsigned int eline;
+
+ (void) cls;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ dspec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ blinded_message = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
+ blinded_message->rc = 1;
+ blinded_message->cipher = string_to_cipher (cipher);
+ switch (blinded_message->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_varsize (
+ "rsa_blinded_planchet",
+ &blinded_message->details.rsa_blinded_message.blinded_msg,
+ &blinded_message->details.rsa_blinded_message.blinded_msg_size),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ ispec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (blinded_message);
+ return GNUNET_SYSERR;
+ }
+ blinded_planchet->blinded_message = blinded_message;
+ return GNUNET_OK;
+ }
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "cs_nonce",
+ &blinded_message->details.cs_blinded_message.nonce),
+ GNUNET_JSON_spec_fixed_auto (
+ "cs_blinded_c0",
+ &blinded_message->details.cs_blinded_message.c[0]),
+ GNUNET_JSON_spec_fixed_auto (
+ "cs_blinded_c1",
+ &blinded_message->details.cs_blinded_message.c[1]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ ispec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (blinded_message);
+ return GNUNET_SYSERR;
+ }
+ blinded_planchet->blinded_message = blinded_message;
+ return GNUNET_OK;
+ }
+ }
+ GNUNET_break_op (0);
+ GNUNET_free (blinded_message);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Cleanup data left from parsing blinded planchet.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_blinded_planchet (void *cls,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_BlindedPlanchet *blinded_planchet = spec->ptr;
+
+ (void) cls;
+ TALER_blinded_planchet_free (blinded_planchet);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_blinded_planchet (const char *field,
+ struct TALER_BlindedPlanchet *blinded_planchet)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_blinded_planchet,
+ .cleaner = &clean_blinded_planchet,
+ .field = field,
+ .ptr = blinded_planchet
+ };
+
+ blinded_planchet->blinded_message = NULL;
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to exchange withdraw values (/csr).
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_exchange_withdraw_values (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_ExchangeWithdrawValues *ewv = spec->ptr;
+ struct GNUNET_CRYPTO_BlindingInputValues *bi;
+ const char *cipher;
+ struct GNUNET_JSON_Specification dspec[] = {
+ GNUNET_JSON_spec_string ("cipher",
+ &cipher),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *emsg;
+ unsigned int eline;
+ enum GNUNET_CRYPTO_BlindSignatureAlgorithm ci;
+
+ (void) cls;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ dspec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ ci = string_to_cipher (cipher);
+ switch (ci)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ ewv->blinding_inputs = TALER_denom_ewv_rsa_singleton ()->blinding_inputs;
+ return GNUNET_OK;
+ case GNUNET_CRYPTO_BSA_CS:
+ bi = GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues);
+ bi->cipher = GNUNET_CRYPTO_BSA_CS;
+ bi->rc = 1;
+ {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed (
+ "r_pub_0",
+ &bi->details.cs_values.r_pub[0],
+ sizeof (struct GNUNET_CRYPTO_CsRPublic)),
+ GNUNET_JSON_spec_fixed (
+ "r_pub_1",
+ &bi->details.cs_values.r_pub[1],
+ sizeof (struct GNUNET_CRYPTO_CsRPublic)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ ispec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (bi);
+ return GNUNET_SYSERR;
+ }
+ ewv->blinding_inputs = bi;
+ return GNUNET_OK;
+ }
+ }
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Cleanup data left from parsing withdraw values
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_exchange_withdraw_values (
+ void *cls,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_ExchangeWithdrawValues *ewv = spec->ptr;
+
+ (void) cls;
+ TALER_denom_ewv_free (ewv);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_exchange_withdraw_values (
+ const char *field,
+ struct TALER_ExchangeWithdrawValues *ewv)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_exchange_withdraw_values,
+ .cleaner = &clean_exchange_withdraw_values,
+ .field = field,
+ .ptr = ewv
+ };
+
+ ewv->blinding_inputs = NULL;
+ return ret;
+}
+
+
+/**
+ * Closure for #parse_i18n_string.
+ */
+struct I18nContext
+{
+ /**
+ * Language pattern to match.
+ */
+ char *lp;
+
+ /**
+ * Name of the field to match.
+ */
+ const char *field;
+};
+
+
+/**
+ * Parse given JSON object to internationalized string.
+ *
+ * @param cls closure, our `struct I18nContext *`
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_i18n_string (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct I18nContext *ctx = cls;
+ json_t *i18n;
+ json_t *val;
+
+ {
+ char *i18nf;
+
+ GNUNET_asprintf (&i18nf,
+ "%s_i18n",
+ ctx->field);
+ i18n = json_object_get (root,
+ i18nf);
+ GNUNET_free (i18nf);
+ }
+
+ val = json_object_get (root,
+ ctx->field);
+ if ( (NULL != i18n) &&
+ (NULL != ctx->lp) )
+ {
+ double best = 0.0;
+ json_t *pos;
+ const char *lang;
+
+ json_object_foreach (i18n, lang, pos)
+ {
+ double score;
+
+ score = TALER_language_matches (ctx->lp,
+ lang);
+ if (score > best)
+ {
+ best = score;
+ val = pos;
+ }
+ }
+ }
+
+ {
+ const char *str;
+
+ str = json_string_value (val);
+ *(const char **) spec->ptr = str;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called to clean up data from earlier parsing.
+ *
+ * @param cls closure
+ * @param spec our specification entry with data to clean.
+ */
+static void
+i18n_cleaner (void *cls,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct I18nContext *ctx = cls;
+
+ (void) spec;
+ if (NULL != ctx)
+ {
+ GNUNET_free (ctx->lp);
+ GNUNET_free (ctx);
+ }
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_i18n_string (const char *name,
+ const char *language_pattern,
+ const char **strptr)
+{
+ struct I18nContext *ctx = GNUNET_new (struct I18nContext);
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_i18n_string,
+ .cleaner = &i18n_cleaner,
+ .cls = ctx,
+ .field = NULL, /* we want the main object */
+ .ptr = strptr,
.ptr_size = 0,
.size_ptr = NULL
};
+
+ ctx->lp = (NULL != language_pattern)
+ ? GNUNET_strdup (language_pattern)
+ : NULL;
+ ctx->field = name;
+ *strptr = NULL;
+ return ret;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_i18n_str (const char *name,
+ const char **strptr)
+{
+ const char *lang = getenv ("LANG");
+ char *dot;
+ char *l;
+ struct GNUNET_JSON_Specification ret;
+
+ if (NULL != lang)
+ {
+ dot = strchr (lang,
+ '.');
+ if (NULL == dot)
+ l = GNUNET_strdup (lang);
+ else
+ l = GNUNET_strndup (lang,
+ dot - lang);
+ }
+ else
+ {
+ l = NULL;
+ }
+ ret = TALER_JSON_spec_i18n_string (name,
+ l,
+ strptr);
+ GNUNET_free (l);
return ret;
}
/**
- * Generate line in parser specification for denomination public key.
+ * Parse given JSON object with Taler error code.
*
- * @param field name of the field
- * @param[out] pk key to initialize
- * @return corresponding field spec
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
*/
+static enum GNUNET_GenericReturnValue
+parse_ec (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ enum TALER_ErrorCode *ec = spec->ptr;
+ json_int_t num;
+
+ (void) cls;
+ if (! json_is_integer (root))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ num = json_integer_value (root);
+ if (num < 0)
+ {
+ GNUNET_break_op (0);
+ *ec = TALER_EC_INVALID;
+ return GNUNET_SYSERR;
+ }
+ *ec = (enum TALER_ErrorCode) num;
+ return GNUNET_OK;
+}
+
+
struct GNUNET_JSON_Specification
-TALER_JSON_spec_denomination_public_key (const char *field,
- struct TALER_DenominationPublicKey *pk)
+TALER_JSON_spec_ec (const char *field,
+ enum TALER_ErrorCode *ec)
{
- return GNUNET_JSON_spec_rsa_public_key (field,
- &pk->rsa_public_key);
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_ec,
+ .field = field,
+ .ptr = ec
+ };
+
+ *ec = TALER_EC_NONE;
+ return ret;
}
/**
- * Generate line in parser specification for denomination signature.
+ * Parse given JSON object with AML decision.
*
- * @param field name of the field
- * @param sig the signature to initialize
- * @return corresponding field spec
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
*/
+static enum GNUNET_GenericReturnValue
+parse_aml_decision (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ enum TALER_AmlDecisionState *aml = spec->ptr;
+ json_int_t num;
+
+ (void) cls;
+ if (! json_is_integer (root))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ num = json_integer_value (root);
+ if ( (num > TALER_AML_MAX) ||
+ (num < 0) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ *aml = (enum TALER_AmlDecisionState) num;
+ return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_aml_decision (const char *field,
+ enum TALER_AmlDecisionState *aml_state)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_aml_decision,
+ .field = field,
+ .ptr = aml_state
+ };
+
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to web URL.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_web_url (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ const char *str;
+
+ (void) cls;
+ str = json_string_value (root);
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (! TALER_is_web_url (str))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ *(const char **) spec->ptr = str;
+ return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_web_url (const char *field,
+ const char **url)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_web_url,
+ .field = field,
+ .ptr = url
+ };
+
+ *url = NULL;
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to payto:// URI.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_payto_uri (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ const char *str;
+ char *err;
+
+ (void) cls;
+ str = json_string_value (root);
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ err = TALER_payto_validate (str);
+ if (NULL != err)
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (err);
+ return GNUNET_SYSERR;
+ }
+ *(const char **) spec->ptr = str;
+ return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_payto_uri (const char *field,
+ const char **payto_uri)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_payto_uri,
+ .field = field,
+ .ptr = payto_uri
+ };
+
+ *payto_uri = NULL;
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object with protocol version.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_protocol_version (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_JSON_ProtocolVersion *pv = spec->ptr;
+ const char *ver;
+ char dummy;
+
+ (void) cls;
+ if (! json_is_string (root))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ ver = json_string_value (root);
+ if (3 != sscanf (ver,
+ "%u:%u:%u%c",
+ &pv->current,
+ &pv->revision,
+ &pv->age,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_version (const char *field,
+ struct TALER_JSON_ProtocolVersion *ver)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_protocol_version,
+ .field = field,
+ .ptr = ver
+ };
+
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to an OTP key.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_otp_key (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ const char *pos_key;
+
+ (void) cls;
+ pos_key = json_string_value (root);
+ if (NULL == pos_key)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ size_t pos_key_length = strlen (pos_key);
+ void *key; /* pos_key in binary */
+ size_t key_len; /* length of the key */
+ int dret;
+
+ key_len = pos_key_length * 5 / 8;
+ key = GNUNET_malloc (key_len);
+ dret = TALER_rfc3548_base32decode (pos_key,
+ pos_key_length,
+ key,
+ key_len);
+ if (-1 == dret)
+ {
+ GNUNET_free (key);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (key);
+ }
+ *(const char **) spec->ptr = pos_key;
+ return GNUNET_OK;
+}
+
+
struct GNUNET_JSON_Specification
-TALER_JSON_spec_denomination_signature (const char *field,
- struct TALER_DenominationSignature *sig)
+TALER_JSON_spec_otp_key (const char *name,
+ const char **otp_key)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_otp_key,
+ .field = name,
+ .ptr = otp_key
+ };
+
+ *otp_key = NULL;
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to `enum TALER_MerchantConfirmationAlgorithm`
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_otp_type (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
{
- return GNUNET_JSON_spec_rsa_signature (field,
- &sig->rsa_signature);
+ static const struct Entry
+ {
+ const char *name;
+ enum TALER_MerchantConfirmationAlgorithm val;
+ } lt [] = {
+ { .name = "NONE",
+ .val = TALER_MCA_NONE },
+ { .name = "TOTP_WITHOUT_PRICE",
+ .val = TALER_MCA_WITHOUT_PRICE },
+ { .name = "TOTP_WITH_PRICE",
+ .val = TALER_MCA_WITH_PRICE },
+ { .name = NULL,
+ .val = TALER_MCA_NONE },
+ };
+ enum TALER_MerchantConfirmationAlgorithm *res
+ = (enum TALER_MerchantConfirmationAlgorithm *) spec->ptr;
+
+ (void) cls;
+ if (json_is_string (root))
+ {
+ const char *str;
+
+ str = json_string_value (root);
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ for (unsigned int i = 0; NULL != lt[i].name; i++)
+ {
+ if (0 == strcasecmp (str,
+ lt[i].name))
+ {
+ *res = lt[i].val;
+ return GNUNET_OK;
+ }
+ }
+ GNUNET_break_op (0);
+ }
+ if (json_is_integer (root))
+ {
+ json_int_t val;
+
+ val = json_integer_value (root);
+ for (unsigned int i = 0; NULL != lt[i].name; i++)
+ {
+ if (val == lt[i].val)
+ {
+ *res = lt[i].val;
+ return GNUNET_OK;
+ }
+ }
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_otp_type (const char *name,
+ enum TALER_MerchantConfirmationAlgorithm *mca)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_otp_type,
+ .field = name,
+ .ptr = mca
+ };
+
+ *mca = TALER_MCA_NONE;
+ return ret;
}
diff --git a/src/json/json_pack.c b/src/json/json_pack.c
new file mode 100644
index 000000000..71c8db9d2
--- /dev/null
+++ b/src/json/json_pack.c
@@ -0,0 +1,324 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021, 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 json/json_pack.c
+ * @brief helper functions for JSON object packing
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_json_lib.h"
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_time_abs_human (const char *name,
+ struct GNUNET_TIME_Absolute at)
+{
+ struct GNUNET_JSON_PackSpec ps = {
+ .field_name = name,
+ .object = json_string (
+ GNUNET_STRINGS_absolute_time_to_string (at))
+ };
+
+ return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_econtract (
+ const char *name,
+ const struct TALER_EncryptedContract *econtract)
+{
+ struct GNUNET_JSON_PackSpec ps = {
+ .field_name = name,
+ };
+
+ if (NULL == econtract)
+ return ps;
+ ps.object
+ = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_varsize ("econtract",
+ econtract->econtract,
+ econtract->econtract_size),
+ GNUNET_JSON_pack_data_auto ("econtract_sig",
+ &econtract->econtract_sig),
+ GNUNET_JSON_pack_data_auto ("contract_pub",
+ &econtract->contract_pub));
+ return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_age_commitment (
+ const char *name,
+ const struct TALER_AgeCommitment *age_commitment)
+{
+ struct GNUNET_JSON_PackSpec ps = {
+ .field_name = name,
+ };
+ json_t *keys;
+
+ if (NULL == age_commitment ||
+ 0 == age_commitment->num)
+ return ps;
+
+ GNUNET_assert (NULL !=
+ (keys = json_array ()));
+
+ for (size_t i = 0;
+ i < age_commitment->num;
+ i++)
+ {
+ json_t *val;
+ val = GNUNET_JSON_from_data (&age_commitment->keys[i],
+ sizeof(age_commitment->keys[i]));
+ GNUNET_assert (NULL != val);
+ GNUNET_assert (0 ==
+ json_array_append_new (keys, val));
+ }
+
+ ps.object = keys;
+ return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_denom_pub (
+ const char *name,
+ const struct TALER_DenominationPublicKey *pk)
+{
+ const struct GNUNET_CRYPTO_BlindSignPublicKey *bsp;
+ struct GNUNET_JSON_PackSpec ps = {
+ .field_name = name,
+ };
+
+ if (NULL == pk)
+ return ps;
+ bsp = pk->bsign_pub_key;
+ switch (bsp->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ ps.object
+ = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ "RSA"),
+ GNUNET_JSON_pack_uint64 ("age_mask",
+ pk->age_mask.bits),
+ GNUNET_JSON_pack_rsa_public_key ("rsa_public_key",
+ bsp->details.rsa_public_key));
+ return ps;
+ case GNUNET_CRYPTO_BSA_CS:
+ ps.object
+ = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ "CS"),
+ GNUNET_JSON_pack_uint64 ("age_mask",
+ pk->age_mask.bits),
+ GNUNET_JSON_pack_data_varsize ("cs_public_key",
+ &bsp->details.cs_public_key,
+ sizeof (bsp->details.cs_public_key)));
+ return ps;
+ }
+ GNUNET_assert (0);
+ return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_denom_sig (
+ const char *name,
+ const struct TALER_DenominationSignature *sig)
+{
+ const struct GNUNET_CRYPTO_UnblindedSignature *bs;
+ struct GNUNET_JSON_PackSpec ps = {
+ .field_name = name,
+ };
+
+ if (NULL == sig)
+ return ps;
+ bs = sig->unblinded_sig;
+ switch (bs->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ ps.object = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ "RSA"),
+ GNUNET_JSON_pack_rsa_signature ("rsa_signature",
+ bs->details.rsa_signature));
+ return ps;
+ case GNUNET_CRYPTO_BSA_CS:
+ ps.object = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ "CS"),
+ GNUNET_JSON_pack_data_auto ("cs_signature_r",
+ &bs->details.cs_signature.r_point),
+ GNUNET_JSON_pack_data_auto ("cs_signature_s",
+ &bs->details.cs_signature.s_scalar));
+ return ps;
+ }
+ GNUNET_assert (0);
+ return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_exchange_withdraw_values (
+ const char *name,
+ const struct TALER_ExchangeWithdrawValues *ewv)
+{
+ const struct GNUNET_CRYPTO_BlindingInputValues *biv;
+ struct GNUNET_JSON_PackSpec ps = {
+ .field_name = name,
+ };
+
+ if (NULL == ewv)
+ return ps;
+ biv = ewv->blinding_inputs;
+ switch (biv->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ ps.object = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ "RSA"));
+ return ps;
+ case GNUNET_CRYPTO_BSA_CS:
+ ps.object = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ "CS"),
+ GNUNET_JSON_pack_data_varsize (
+ "r_pub_0",
+ &biv->details.cs_values.r_pub[0],
+ sizeof(struct GNUNET_CRYPTO_CsRPublic)),
+ GNUNET_JSON_pack_data_varsize (
+ "r_pub_1",
+ &biv->details.cs_values.r_pub[1],
+ sizeof(struct GNUNET_CRYPTO_CsRPublic))
+ );
+ return ps;
+ }
+ GNUNET_assert (0);
+ return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_blinded_denom_sig (
+ const char *name,
+ const struct TALER_BlindedDenominationSignature *sig)
+{
+ const struct GNUNET_CRYPTO_BlindedSignature *bs;
+ struct GNUNET_JSON_PackSpec ps = {
+ .field_name = name,
+ };
+
+ if (NULL == sig)
+ return ps;
+ bs = sig->blinded_sig;
+ switch (bs->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ ps.object = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ "RSA"),
+ GNUNET_JSON_pack_rsa_signature ("blinded_rsa_signature",
+ bs->details.blinded_rsa_signature));
+ return ps;
+ case GNUNET_CRYPTO_BSA_CS:
+ ps.object = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ "CS"),
+ GNUNET_JSON_pack_uint64 ("b",
+ bs->details.blinded_cs_answer.b),
+ GNUNET_JSON_pack_data_auto ("s",
+ &bs->details.blinded_cs_answer.s_scalar));
+ return ps;
+ }
+ GNUNET_assert (0);
+ return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_blinded_planchet (
+ const char *name,
+ const struct TALER_BlindedPlanchet *blinded_planchet)
+{
+ const struct GNUNET_CRYPTO_BlindedMessage *bm;
+ struct GNUNET_JSON_PackSpec ps = {
+ .field_name = name,
+ };
+
+ if (NULL == blinded_planchet)
+ return ps;
+ bm = blinded_planchet->blinded_message;
+ switch (bm->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ ps.object = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ "RSA"),
+ GNUNET_JSON_pack_data_varsize (
+ "rsa_blinded_planchet",
+ bm->details.rsa_blinded_message.blinded_msg,
+ bm->details.rsa_blinded_message.blinded_msg_size));
+ return ps;
+ case GNUNET_CRYPTO_BSA_CS:
+ ps.object = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ "CS"),
+ GNUNET_JSON_pack_data_auto (
+ "cs_nonce",
+ &bm->details.cs_blinded_message.nonce),
+ GNUNET_JSON_pack_data_auto (
+ "cs_blinded_c0",
+ &bm->details.cs_blinded_message.c[0]),
+ GNUNET_JSON_pack_data_auto (
+ "cs_blinded_c1",
+ &bm->details.cs_blinded_message.c[1]));
+ return ps;
+ }
+ GNUNET_assert (0);
+ return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_amount (const char *name,
+ const struct TALER_Amount *amount)
+{
+ struct GNUNET_JSON_PackSpec ps = {
+ .field_name = name,
+ .object = (NULL != amount)
+ ? TALER_JSON_from_amount (amount)
+ : NULL
+ };
+
+ return ps;
+}
+
+
+/* End of json/json_pack.c */
diff --git a/src/json/json_wire.c b/src/json/json_wire.c
index 8fd99ab6d..9d22d28ea 100644
--- a/src/json/json_wire.c
+++ b/src/json/json_wire.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018 Taler Systems SA
+ Copyright (C) 2018, 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
@@ -24,385 +24,17 @@
#include "taler_json_lib.h"
-/* Taken from GNU gettext */
-
-/**
- * Entry in the country table.
- */
-struct CountryTableEntry
-{
- /**
- * 2-Character international country code.
- */
- const char *code;
-
- /**
- * Long English name of the country.
- */
- const char *english;
-};
-
-
-/* Keep the following table in sync with gettext.
- WARNING: the entries should stay sorted according to the code */
-/**
- * List of country codes.
- */
-static const struct CountryTableEntry country_table[] = {
- { "AE", "U.A.E." },
- { "AF", "Afghanistan" },
- { "AL", "Albania" },
- { "AM", "Armenia" },
- { "AN", "Netherlands Antilles" },
- { "AR", "Argentina" },
- { "AT", "Austria" },
- { "AU", "Australia" },
- { "AZ", "Azerbaijan" },
- { "BA", "Bosnia and Herzegovina" },
- { "BD", "Bangladesh" },
- { "BE", "Belgium" },
- { "BG", "Bulgaria" },
- { "BH", "Bahrain" },
- { "BN", "Brunei Darussalam" },
- { "BO", "Bolivia" },
- { "BR", "Brazil" },
- { "BT", "Bhutan" },
- { "BY", "Belarus" },
- { "BZ", "Belize" },
- { "CA", "Canada" },
- { "CG", "Congo" },
- { "CH", "Switzerland" },
- { "CI", "Cote d'Ivoire" },
- { "CL", "Chile" },
- { "CM", "Cameroon" },
- { "CN", "People's Republic of China" },
- { "CO", "Colombia" },
- { "CR", "Costa Rica" },
- { "CS", "Serbia and Montenegro" },
- { "CZ", "Czech Republic" },
- { "DE", "Germany" },
- { "DK", "Denmark" },
- { "DO", "Dominican Republic" },
- { "DZ", "Algeria" },
- { "EC", "Ecuador" },
- { "EE", "Estonia" },
- { "EG", "Egypt" },
- { "ER", "Eritrea" },
- { "ES", "Spain" },
- { "ET", "Ethiopia" },
- { "FI", "Finland" },
- { "FO", "Faroe Islands" },
- { "FR", "France" },
- { "GB", "United Kingdom" },
- { "GD", "Caribbean" },
- { "GE", "Georgia" },
- { "GL", "Greenland" },
- { "GR", "Greece" },
- { "GT", "Guatemala" },
- { "HK", "Hong Kong" },
- { "HK", "Hong Kong S.A.R." },
- { "HN", "Honduras" },
- { "HR", "Croatia" },
- { "HT", "Haiti" },
- { "HU", "Hungary" },
- { "ID", "Indonesia" },
- { "IE", "Ireland" },
- { "IL", "Israel" },
- { "IN", "India" },
- { "IQ", "Iraq" },
- { "IR", "Iran" },
- { "IS", "Iceland" },
- { "IT", "Italy" },
- { "JM", "Jamaica" },
- { "JO", "Jordan" },
- { "JP", "Japan" },
- { "KE", "Kenya" },
- { "KG", "Kyrgyzstan" },
- { "KH", "Cambodia" },
- { "KR", "South Korea" },
- { "KW", "Kuwait" },
- { "KZ", "Kazakhstan" },
- { "LA", "Laos" },
- { "LB", "Lebanon" },
- { "LI", "Liechtenstein" },
- { "LK", "Sri Lanka" },
- { "LT", "Lithuania" },
- { "LU", "Luxembourg" },
- { "LV", "Latvia" },
- { "LY", "Libya" },
- { "MA", "Morocco" },
- { "MC", "Principality of Monaco" },
- { "MD", "Moldava" },
- { "MD", "Moldova" },
- { "ME", "Montenegro" },
- { "MK", "Former Yugoslav Republic of Macedonia" },
- { "ML", "Mali" },
- { "MM", "Myanmar" },
- { "MN", "Mongolia" },
- { "MO", "Macau S.A.R." },
- { "MT", "Malta" },
- { "MV", "Maldives" },
- { "MX", "Mexico" },
- { "MY", "Malaysia" },
- { "NG", "Nigeria" },
- { "NI", "Nicaragua" },
- { "NL", "Netherlands" },
- { "NO", "Norway" },
- { "NP", "Nepal" },
- { "NZ", "New Zealand" },
- { "OM", "Oman" },
- { "PA", "Panama" },
- { "PE", "Peru" },
- { "PH", "Philippines" },
- { "PK", "Islamic Republic of Pakistan" },
- { "PL", "Poland" },
- { "PR", "Puerto Rico" },
- { "PT", "Portugal" },
- { "PY", "Paraguay" },
- { "QA", "Qatar" },
- { "RE", "Reunion" },
- { "RO", "Romania" },
- { "RS", "Serbia" },
- { "RU", "Russia" },
- { "RW", "Rwanda" },
- { "SA", "Saudi Arabia" },
- { "SE", "Sweden" },
- { "SG", "Singapore" },
- { "SI", "Slovenia" },
- { "SK", "Slovak" },
- { "SN", "Senegal" },
- { "SO", "Somalia" },
- { "SR", "Suriname" },
- { "SV", "El Salvador" },
- { "SY", "Syria" },
- { "TH", "Thailand" },
- { "TJ", "Tajikistan" },
- { "TM", "Turkmenistan" },
- { "TN", "Tunisia" },
- { "TR", "Turkey" },
- { "TT", "Trinidad and Tobago" },
- { "TW", "Taiwan" },
- { "TZ", "Tanzania" },
- { "UA", "Ukraine" },
- { "US", "United States" },
- { "UY", "Uruguay" },
- { "VA", "Vatican" },
- { "VE", "Venezuela" },
- { "VN", "Viet Nam" },
- { "YE", "Yemen" },
- { "ZA", "South Africa" },
- { "ZW", "Zimbabwe" }
-};
-
-
-/**
- * Country code comparator function, for binary search with bsearch().
- *
- * @param ptr1 pointer to a `struct table_entry`
- * @param ptr2 pointer to a `struct table_entry`
- * @return result of memcmp()'ing the 2-digit country codes of the entries
- */
-static int
-cmp_country_code (const void *ptr1,
- const void *ptr2)
-{
- const struct CountryTableEntry *cc1 = ptr1;
- const struct CountryTableEntry *cc2 = ptr2;
-
- return memcmp (cc1->code,
- cc2->code,
- 2);
-}
-
-
-/**
- * Validates given IBAN according to the European Banking Standards. See:
- * http://www.europeanpaymentscouncil.eu/documents/ECBS%20IBAN%20standard%20EBS204_V3.2.pdf
- *
- * @param iban the IBAN number to validate
- * @return #GNUNET_YES if correctly formatted; #GNUNET_NO if not
- */
-static int
-validate_iban (const char *iban)
-{
- char cc[2];
- char ibancpy[35];
- struct CountryTableEntry cc_entry;
- unsigned int len;
- char *nbuf;
- unsigned long long dividend;
- unsigned long long remainder;
- unsigned int i;
- unsigned int j;
-
- len = strlen (iban);
- if (len > 34)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "IBAN number too long to be valid\n");
- return GNUNET_NO;
- }
- memcpy (cc, iban, 2);
- memcpy (ibancpy, iban + 4, len - 4);
- memcpy (ibancpy + len - 4, iban, 4);
- ibancpy[len] = '\0';
- cc_entry.code = cc;
- cc_entry.english = NULL;
- if (NULL ==
- bsearch (&cc_entry,
- country_table,
- sizeof (country_table) / sizeof (struct CountryTableEntry),
- sizeof (struct CountryTableEntry),
- &cmp_country_code))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Country code `%c%c' not supported\n",
- cc[0],
- cc[1]);
- return GNUNET_NO;
- }
- nbuf = GNUNET_malloc ((len * 2) + 1);
- for (i = 0, j = 0; i < len; i++)
- {
- if (isalpha ((unsigned char) ibancpy[i]))
- {
- if (2 != snprintf (&nbuf[j],
- 3,
- "%2u",
- (ibancpy[i] - 'A' + 10)))
- {
- GNUNET_free (nbuf);
- return GNUNET_NO;
- }
- j += 2;
- continue;
- }
- nbuf[j] = ibancpy[i];
- j++;
- }
- for (j = 0; '\0' != nbuf[j]; j++)
- GNUNET_assert (isdigit ( (unsigned char) nbuf[j]));
- GNUNET_assert (sizeof(dividend) >= 8);
- remainder = 0;
- for (unsigned int i = 0; i<j; i += 16)
- {
- int nread;
-
- if (1 !=
- sscanf (&nbuf[i],
- "%16llu %n",
- &dividend,
- &nread))
- {
- GNUNET_free (nbuf);
- GNUNET_break_op (0);
- return GNUNET_NO;
- }
- if (0 != remainder)
- dividend += remainder * (pow (10, nread));
- remainder = dividend % 97;
- }
- GNUNET_free (nbuf);
- if (1 != remainder)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "IBAN `%s' has the wrong checksum\n",
- iban);
- return GNUNET_NO;
- }
- return GNUNET_YES;
-}
-
-
-/**
- * Validate payto://iban/ account URL (only account information,
- * wire subject and amount are ignored).
- *
- * @param account_url URL to parse
- * @return #GNUNET_YES if @a account_url is a valid payto://iban URI,
- * #GNUNET_NO if @a account_url is a payto URI of a different type,
- * #GNUNET_SYSERR if the IBAN (checksum) is incorrect or this is not a payto://-URI
- */
-static int
-validate_payto_iban (const char *account_url)
-{
- const char *iban;
- const char *q;
- char *result;
-
-#define IBAN_PREFIX "payto://iban/"
- if (0 != strncasecmp (account_url,
- IBAN_PREFIX,
- strlen (IBAN_PREFIX)))
- return GNUNET_NO;
- iban = &account_url[strlen (IBAN_PREFIX)];
-#undef IBAN_PREFIX
- q = strchr (iban,
- '?');
- if (NULL != q)
- {
- result = GNUNET_strndup (iban,
- q - iban);
- }
- else
- {
- result = GNUNET_strdup (iban);
- }
- if (GNUNET_OK !=
- validate_iban (result))
- {
- GNUNET_free (result);
- return GNUNET_SYSERR;
- }
- GNUNET_free (result);
- return GNUNET_YES;
-}
-
-
-/**
- * Validate payto:// account URL (only account information,
- * wire subject and amount are ignored).
- *
- * @param account_url URL to parse
- * @return #GNUNET_YES if @a account_url is a valid payto://iban URI
- * #GNUNET_NO if @a account_url is a payto URI of an unsupported type (but may be valid)
- * #GNUNET_SYSERR if the account incorrect or this is not a payto://-URI at all
- */
-static int
-validate_payto (const char *account_url)
-{
- int ret;
-
-#define PAYTO_PREFIX "payto://"
- if (0 != strncasecmp (account_url,
- PAYTO_PREFIX,
- strlen (PAYTO_PREFIX)))
- return GNUNET_SYSERR; /* not payto */
-#undef PAYTO_PREFIX
- if (GNUNET_NO != (ret = validate_payto_iban (account_url)))
- return ret; /* got a definitive answer */
- /* Insert other bank account validation methods here later! */
- return GNUNET_NO;
-}
-
-
-/**
- * Compute the hash of the given wire details. The resulting
- * hash is what is put into the contract.
- *
- * @param wire_s wire details to hash
- * @param[out] hc set to the hash
- * @return #GNUNET_OK on success, #GNUNET_SYSERR if @a wire_s is malformed
- */
-int
+enum GNUNET_GenericReturnValue
TALER_JSON_merchant_wire_signature_hash (const json_t *wire_s,
- struct GNUNET_HashCode *hc)
+ struct TALER_MerchantWireHashP *hc)
{
const char *payto_uri;
- const char *salt;
+ struct TALER_WireSaltP salt;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("payto_uri", &payto_uri),
- GNUNET_JSON_spec_string ("salt", &salt),
+ GNUNET_JSON_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &salt),
GNUNET_JSON_spec_end ()
};
@@ -414,96 +46,36 @@ TALER_JSON_merchant_wire_signature_hash (const json_t *wire_s,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- TALER_merchant_wire_signature_hash (payto_uri,
- salt,
- hc);
- return GNUNET_OK;
-}
-
-
-/**
- * Check the signature in @a wire_s. Also performs rudimentary
- * checks on the account data *if* supported.
- *
- * @param wire_s signed wire information of an exchange
- * @param master_pub master public key of the exchange
- * @return #GNUNET_OK if signature is valid
- */
-int
-TALER_JSON_exchange_wire_signature_check (const json_t *wire_s,
- const struct
- TALER_MasterPublicKeyP *master_pub)
-{
- const char *payto_uri;
- struct TALER_MasterSignatureP master_sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("payto_uri", &payto_uri),
- GNUNET_JSON_spec_fixed_auto ("master_sig", &master_sig),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (wire_s,
- spec,
- NULL, NULL))
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Validating `%s'\n",
+ payto_uri);
{
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_SYSERR == validate_payto (payto_uri))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- return TALER_exchange_wire_signature_check (payto_uri,
- master_pub,
- &master_sig);
-}
-
-
-/**
- * Create a signed wire statement for the given account.
- *
- * @param payto_uri account specification
- * @param master_priv private key to sign with
- * @return NULL if @a payto_uri is malformed
- */
-json_t *
-TALER_JSON_exchange_wire_signature_make (const char *payto_uri,
- const struct
- TALER_MasterPrivateKeyP *master_priv)
-{
- struct TALER_MasterSignatureP master_sig;
+ char *err;
- if (GNUNET_SYSERR == validate_payto (payto_uri))
- {
- GNUNET_break_op (0);
- return NULL;
+ err = TALER_payto_validate (payto_uri);
+ if (NULL != err)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "URI `%s' ill-formed: %s\n",
+ payto_uri,
+ err);
+ GNUNET_free (err);
+ return GNUNET_SYSERR;
+ }
}
-
- TALER_exchange_wire_signature_make (payto_uri,
- master_priv,
- &master_sig);
- return json_pack ("{s:s, s:o}",
- "payto_uri", payto_uri,
- "master_sig", GNUNET_JSON_from_data_auto (&master_sig));
+ TALER_merchant_wire_signature_hash (payto_uri,
+ &salt,
+ hc);
+ return GNUNET_OK;
}
-/**
- * Obtain the wire method associated with the given
- * wire account details. @a wire_s must contain a payto://-URL
- * under 'payto_uri'.
- *
- * @return NULL on error
- */
char *
TALER_JSON_wire_to_payto (const json_t *wire_s)
{
json_t *payto_o;
const char *payto_str;
+ char *err;
payto_o = json_object_get (wire_s,
"payto_uri");
@@ -514,24 +86,20 @@ TALER_JSON_wire_to_payto (const json_t *wire_s)
"Malformed wire record encountered: lacks payto://-url\n");
return NULL;
}
- if (GNUNET_SYSERR == validate_payto (payto_str))
+ if (NULL !=
+ (err = TALER_payto_validate (payto_str)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Malformed wire record encountered: payto URI `%s' invalid\n",
- payto_str);
+ "Malformed wire record encountered: payto URI `%s' invalid: %s\n",
+ payto_str,
+ err);
+ GNUNET_free (err);
return NULL;
}
return GNUNET_strdup (payto_str);
}
-/**
- * Obtain the wire method associated with the given
- * wire account details. @a wire_s must contain a payto://-URL
- * under 'url'.
- *
- * @return NULL on error
- */
char *
TALER_JSON_wire_to_method (const json_t *wire_s)
{
diff --git a/src/json/test_json.c b/src/json/test_json.c
index 732a3d774..fba72f84b 100644
--- a/src/json/test_json.c
+++ b/src/json/test_json.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2015, 2016 Taler Systems SA
+ (C) 2015, 2016, 2020 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
@@ -36,7 +36,9 @@ test_amount (void)
struct TALER_Amount a1;
struct TALER_Amount a2;
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("amount", &a2),
+ TALER_JSON_spec_amount ("amount",
+ "EUR",
+ &a2),
GNUNET_JSON_spec_end ()
};
@@ -56,6 +58,364 @@ test_amount (void)
}
+struct TestPath_Closure
+{
+ const char **object_ids;
+
+ const json_t **parents;
+
+ unsigned int results_length;
+
+ int cmp_result;
+};
+
+
+static void
+path_cb (void *cls,
+ const char *object_id,
+ json_t *parent)
+{
+ struct TestPath_Closure *cmp = cls;
+ if (NULL == cmp)
+ return;
+ unsigned int i = cmp->results_length;
+ if ((0 != strcmp (cmp->object_ids[i],
+ object_id)) ||
+ (1 != json_equal (cmp->parents[i],
+ parent)))
+ cmp->cmp_result = 1;
+ cmp->results_length += 1;
+}
+
+
+static int
+test_contract (void)
+{
+ struct TALER_PrivateContractHashP h1;
+ struct TALER_PrivateContractHashP h2;
+ json_t *c1;
+ json_t *c2;
+ json_t *c3;
+ json_t *c4;
+
+ c1 = json_pack ("{s:s, s:{s:s, s:{s:b}}}",
+ "k1", "v1",
+ "k2", "n1", "n2",
+ /***/ "$forgettable", "n1", true);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_seed_forgettable (c1,
+ c1));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_hash (c1,
+ &h1));
+ json_decref (c1);
+
+ c1 = json_pack ("{s:s, s:{s:s, s:{s:s}}}",
+ "k1", "v1",
+ "k2", "n1", "n2",
+ /***/ "$forgettable", "n1", "salt");
+ GNUNET_assert (NULL != c1);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_mark_forgettable (c1,
+ "k1"));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_mark_forgettable (c1,
+ "k2"));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_hash (c1,
+ &h1));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_part_forget (c1,
+ "k1"));
+ /* check salt was forgotten */
+ GNUNET_assert (NULL ==
+ json_object_get (json_object_get (c1,
+ "$forgettable"),
+ "k1"));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_hash (c1,
+ &h2));
+ if (0 !=
+ GNUNET_memcmp (&h1,
+ &h2))
+ {
+ GNUNET_break (0);
+ json_decref (c1);
+ return 1;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_part_forget (json_object_get (c1,
+ "k2"),
+ "n1"));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_hash (c1,
+ &h2));
+ if (0 !=
+ GNUNET_memcmp (&h1,
+ &h2))
+ {
+ GNUNET_break (0);
+ json_decref (c1);
+ return 1;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_part_forget (c1,
+ "k2"));
+ // json_dumpf (c1, stderr, JSON_INDENT (2));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_hash (c1,
+ &h2));
+ json_decref (c1);
+ if (0 !=
+ GNUNET_memcmp (&h1,
+ &h2))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+
+ c1 = json_pack ("{s:I, s:{s:s}, s:{s:b, s:{s:s}}, s:{s:s}}",
+ "k1", 1,
+ "$forgettable", "k1", "SALT",
+ "k2", "n1", true,
+ /***/ "$forgettable", "n1", "salt",
+ "k3", "n1", "string");
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_hash (c1,
+ &h1));
+ // json_dumpf (c1, stderr, JSON_INDENT (2));
+ json_decref (c1);
+ {
+ char *s;
+
+ s = GNUNET_STRINGS_data_to_string_alloc (&h1,
+ sizeof (h1));
+ if (0 !=
+ strcmp (s,
+ "VDE8JPX0AEEE3EX1K8E11RYEWSZQKGGZCV6BWTE4ST1C8711P7H850Z7F2Q2HSSYETX87ERC2JNHWB7GTDWTDWMM716VKPSRBXD7SRR"))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid reference hash: %s\n",
+ s);
+ GNUNET_free (s);
+ return 1;
+ }
+ GNUNET_free (s);
+ }
+
+
+ c2 = json_pack ("{s:s}",
+ "n1", "n2");
+ GNUNET_assert (NULL != c2);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_mark_forgettable (c2,
+ "n1"));
+ c3 = json_pack ("{s:s, s:o}",
+ "k1", "v1",
+ "k2", c2);
+ GNUNET_assert (NULL != c3);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_mark_forgettable (c3,
+ "k1"));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_hash (c3,
+ &h1));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_part_forget (c2,
+ "n1"));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_hash (c3,
+ &h2));
+ json_decref (c3);
+ c4 = json_pack ("{s:{s:s}, s:[{s:s}, {s:s}, {s:s}]}",
+ "abc1",
+ "xyz", "value",
+ "fruit",
+ "name", "banana",
+ "name", "apple",
+ "name", "orange");
+ GNUNET_assert (NULL != c4);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_JSON_expand_path (c4,
+ "%.xyz",
+ &path_cb,
+ NULL));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_expand_path (c4,
+ "$.nonexistent_id",
+ &path_cb,
+ NULL));
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_JSON_expand_path (c4,
+ "$.fruit[n]",
+ &path_cb,
+ NULL));
+
+ {
+ const char *object_ids[] = { "xyz" };
+ const json_t *parents[] = {
+ json_object_get (c4,
+ "abc1")
+ };
+ struct TestPath_Closure tp = {
+ .object_ids = object_ids,
+ .parents = parents,
+ .results_length = 0,
+ .cmp_result = 0
+ };
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_expand_path (c4,
+ "$.abc1.xyz",
+ &path_cb,
+ &tp));
+ GNUNET_assert (1 == tp.results_length);
+ GNUNET_assert (0 == tp.cmp_result);
+ }
+ {
+ const char *object_ids[] = { "name" };
+ const json_t *parents[] = {
+ json_array_get (json_object_get (c4,
+ "fruit"),
+ 0)
+ };
+ struct TestPath_Closure tp = {
+ .object_ids = object_ids,
+ .parents = parents,
+ .results_length = 0,
+ .cmp_result = 0
+ };
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_expand_path (c4,
+ "$.fruit[0].name",
+ &path_cb,
+ &tp));
+ GNUNET_assert (1 == tp.results_length);
+ GNUNET_assert (0 == tp.cmp_result);
+ }
+ {
+ const char *object_ids[] = { "name", "name", "name" };
+ const json_t *parents[] = {
+ json_array_get (json_object_get (c4,
+ "fruit"),
+ 0),
+ json_array_get (json_object_get (c4,
+ "fruit"),
+ 1),
+ json_array_get (json_object_get (c4,
+ "fruit"),
+ 2)
+ };
+ struct TestPath_Closure tp = {
+ .object_ids = object_ids,
+ .parents = parents,
+ .results_length = 0,
+ .cmp_result = 0
+ };
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_expand_path (c4,
+ "$.fruit[*].name",
+ &path_cb,
+ &tp));
+ GNUNET_assert (3 == tp.results_length);
+ GNUNET_assert (0 == tp.cmp_result);
+ }
+ json_decref (c4);
+ if (0 !=
+ GNUNET_memcmp (&h1,
+ &h2))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ return 0;
+}
+
+
+static int
+test_json_canon (void)
+{
+ {
+ json_t *c1;
+ char *canon;
+ c1 = json_pack ("{s:s}",
+ "k1", "Hello\nWorld");
+
+ canon = TALER_JSON_canonicalize (c1);
+ GNUNET_assert (NULL != canon);
+
+ printf ("canon: '%s'\n", canon);
+
+ GNUNET_assert (0 == strcmp (canon,
+ "{\"k1\":\"Hello\\nWorld\"}"));
+ }
+ {
+ json_t *c1;
+ char *canon;
+ c1 = json_pack ("{s:s}",
+ "k1", "Testing “unicode†characters");
+
+ canon = TALER_JSON_canonicalize (c1);
+ GNUNET_assert (NULL != canon);
+
+ printf ("canon: '%s'\n", canon);
+
+ GNUNET_assert (0 == strcmp (canon,
+ "{\"k1\":\"Testing “unicode†characters\"}"));
+ }
+ {
+ json_t *c1;
+ char *canon;
+ c1 = json_pack ("{s:s}",
+ "k1", "low range \x05 chars");
+
+ canon = TALER_JSON_canonicalize (c1);
+ GNUNET_assert (NULL != canon);
+
+ printf ("canon: '%s'\n", canon);
+
+ GNUNET_assert (0 == strcmp (canon,
+ "{\"k1\":\"low range \\u0005 chars\"}"));
+ }
+
+
+ return 0;
+}
+
+
+static int
+test_rfc8785 (void)
+{
+ struct TALER_PrivateContractHashP h1;
+ json_t *c1;
+
+ c1 = json_pack ("{s:s}",
+ "k1", "\x08\x0B\t\1\\\x0d");
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_contract_hash (c1,
+ &h1));
+ {
+ char *s;
+
+ s = GNUNET_STRINGS_data_to_string_alloc (&h1,
+ sizeof (h1));
+ if (0 !=
+ strcmp (s,
+ "531S33T8ZRGW6548G7T67PMDNGS4Z1D8A2GMB87G3PNKYTW6KGF7Q99XVCGXBKVA2HX6PR5ENJ1PQ5ZTYMMXQB6RM7S82VP7ZG2X5G8"))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid reference hash: %s\n",
+ s);
+ GNUNET_free (s);
+ json_decref (c1);
+ return 1;
+ }
+ GNUNET_free (s);
+ }
+ json_decref (c1);
+ return 0;
+}
+
+
int
main (int argc,
const char *const argv[])
@@ -67,6 +427,12 @@ main (int argc,
NULL);
if (0 != test_amount ())
return 1;
+ if (0 != test_contract ())
+ return 2;
+ if (0 != test_json_canon ())
+ return 2;
+ if (0 != test_rfc8785 ())
+ return 2;
return 0;
}
diff --git a/src/json/test_json_wire.c b/src/json/test_json_wire.c
deleted file mode 100644
index 3b2123e26..000000000
--- a/src/json/test_json_wire.c
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- This file is part of TALER
- (C) 2015, 2016 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 json/test_json_wire.c
- * @brief Tests for Taler-specific crypto logic
- * @author Christian Grothoff <christian@grothoff.org>
- */
-#include "platform.h"
-#include "taler_util.h"
-#include "taler_json_lib.h"
-
-
-int
-main (int argc,
- const char *const argv[])
-{
- struct TALER_MasterPublicKeyP master_pub;
- struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
- struct TALER_MasterPrivateKeyP master_priv;
- json_t *wire;
- const char *payto = "payto://x-taler-bank/42";
- char *p;
-
- (void) argc;
- (void) argv;
- GNUNET_log_setup ("test-json-wire",
- "WARNING",
- NULL);
- priv = GNUNET_CRYPTO_eddsa_key_create ();
- master_priv.eddsa_priv = *priv;
- GNUNET_free (priv);
- GNUNET_CRYPTO_eddsa_key_get_public (&master_priv.eddsa_priv,
- &master_pub.eddsa_pub);
- wire = TALER_JSON_exchange_wire_signature_make (payto,
- &master_priv);
- p = TALER_JSON_wire_to_payto (wire);
- GNUNET_assert (0 == strcmp (p, payto));
- GNUNET_free (p);
- GNUNET_assert (GNUNET_OK ==
- TALER_JSON_exchange_wire_signature_check (wire,
- &master_pub));
- json_decref (wire);
-
- return 0;
-}
-
-
-/* end of test_json_wire.c */
diff --git a/src/kyclogic/Makefile.am b/src/kyclogic/Makefile.am
new file mode 100644
index 000000000..0281553fc
--- /dev/null
+++ b/src/kyclogic/Makefile.am
@@ -0,0 +1,144 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+pkgcfgdir = $(prefix)/share/taler/config.d/
+
+pkgcfg_DATA = \
+ kyclogic.conf \
+ kyclogic-kycaid.conf \
+ kyclogic-oauth2.conf \
+ kyclogic-persona.conf
+
+bin_SCRIPTS = \
+ taler-exchange-kyc-kycaid-converter.sh \
+ taler-exchange-kyc-persona-converter.sh \
+ taler-exchange-kyc-oauth2-test-converter.sh \
+ taler-exchange-kyc-oauth2-challenger.sh \
+ taler-exchange-kyc-oauth2-nda.sh
+
+EXTRA_DIST = \
+ $(pkgcfg_DATA) \
+ $(bin_SCRIPTS) \
+ sample.conf
+
+lib_LTLIBRARIES = \
+ libtalerkyclogic.la
+
+libtalerkyclogic_la_SOURCES = \
+ kyclogic_api.c
+libtalerkyclogic_la_LIBADD = \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -ljansson \
+ -lgnunetutil \
+ $(XLIB)
+libtalerkyclogic_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+
+
+bin_PROGRAMS = \
+ taler-exchange-kyc-tester
+
+taler_exchange_kyc_tester_SOURCES = \
+ taler-exchange-kyc-tester.c
+taler_exchange_kyc_tester_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ libtalerkyclogic.la \
+ $(top_builddir)/src/mhd/libtalermhd.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/templating/libtalertemplating.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lmicrohttpd \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -lgnunetjson \
+ -ljansson \
+ -lcurl \
+ -lz \
+ $(XLIB)
+
+
+
+plugindir = $(libdir)/taler
+
+plugin_LTLIBRARIES = \
+ libtaler_plugin_kyclogic_kycaid.la \
+ libtaler_plugin_kyclogic_oauth2.la \
+ libtaler_plugin_kyclogic_persona.la \
+ libtaler_plugin_kyclogic_template.la
+
+libtaler_plugin_kyclogic_template_la_SOURCES = \
+ plugin_kyclogic_template.c
+libtaler_plugin_kyclogic_template_la_LIBADD = \
+ $(LTLIBINTL)
+libtaler_plugin_kyclogic_template_la_LDFLAGS = \
+ $(TALER_PLUGIN_LDFLAGS) \
+ -lgnunetcurl \
+ -lgnunetutil \
+ $(XLIB)
+
+libtaler_plugin_kyclogic_oauth2_la_SOURCES = \
+ plugin_kyclogic_oauth2.c
+libtaler_plugin_kyclogic_oauth2_la_LIBADD = \
+ $(LTLIBINTL)
+libtaler_plugin_kyclogic_oauth2_la_LDFLAGS = \
+ $(TALER_PLUGIN_LDFLAGS) \
+ $(top_builddir)/src/templating/libtalertemplating.la \
+ $(top_builddir)/src/mhd/libtalermhd.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lmicrohttpd \
+ -ljansson \
+ -lcurl \
+ $(XLIB)
+
+libtaler_plugin_kyclogic_kycaid_la_SOURCES = \
+ plugin_kyclogic_kycaid.c
+libtaler_plugin_kyclogic_kycaid_la_LIBADD = \
+ $(LTLIBINTL)
+libtaler_plugin_kyclogic_kycaid_la_LDFLAGS = \
+ $(TALER_PLUGIN_LDFLAGS) \
+ $(top_builddir)/src/templating/libtalertemplating.la \
+ $(top_builddir)/src/mhd/libtalermhd.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/curl/libtalercurl.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lmicrohttpd \
+ -ljansson \
+ -lcurl \
+ $(XLIB)
+
+libtaler_plugin_kyclogic_persona_la_SOURCES = \
+ plugin_kyclogic_persona.c
+libtaler_plugin_kyclogic_persona_la_LIBADD = \
+ $(LTLIBINTL)
+libtaler_plugin_kyclogic_persona_la_DEPENDENCIES = \
+ libtalerkyclogic.la
+libtaler_plugin_kyclogic_persona_la_LDFLAGS = \
+ $(TALER_PLUGIN_LDFLAGS) \
+ libtalerkyclogic.la \
+ $(top_builddir)/src/mhd/libtalermhd.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/curl/libtalercurl.la \
+ $(top_builddir)/src/templating/libtalertemplating.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lmicrohttpd \
+ -ljansson \
+ -lcurl \
+ $(XLIB)
+
+AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
diff --git a/src/kyclogic/kyclogic-kycaid.conf b/src/kyclogic/kyclogic-kycaid.conf
new file mode 100644
index 000000000..753fb689d
--- /dev/null
+++ b/src/kyclogic/kyclogic-kycaid.conf
@@ -0,0 +1,26 @@
+# This file is in the public domain.
+
+# Example kycaid provider configuration.
+
+[kyc-provider-example-kycaid]
+
+COST = 42
+LOGIC = kycaid
+USER_TYPE = INDIVIDUAL
+PROVIDED_CHECKS = EXAMPLE_DO_NOT_USE
+
+# How long is the KYC check valid?
+KYC_KYCAID_VALIDITY = forever
+
+# Program that converts Persona KYC data into the
+# GNU Taler format.
+KYC_KYCAID_CONVERTER_HELPER = taler-exchange-kyc-kycaid-converter.sh
+
+# Authentication token to use.
+KYC_KYCAID_AUTH_TOKEN = XXX
+
+# Form to use.
+KYC_KYCAID_FORM_ID = XXX
+
+# URL to go to after the process is complete.
+KYC_KYCAID_POST_URL = https://example.com/
diff --git a/src/kyclogic/kyclogic-oauth2.conf b/src/kyclogic/kyclogic-oauth2.conf
new file mode 100644
index 000000000..57e1fc13a
--- /dev/null
+++ b/src/kyclogic/kyclogic-oauth2.conf
@@ -0,0 +1,35 @@
+# This file is in the public domain.
+
+# Example Oauth2.0 provider configuration.
+
+[kyc-provider-example-oauth2]
+
+COST = 42
+LOGIC = oauth2
+USER_TYPE = INDIVIDUAL
+PROVIDED_CHECKS = EXAMPLE_DO_NOT_USE
+
+# How long is the KYC check valid?
+KYC_OAUTH2_VALIDITY = forever
+
+# URL where we initiate the user's login process
+KYC_OAUTH2_AUTHORIZE_URL = https://kyc.example.com/authorize
+# URL where we send the user's authentication information
+KYC_OAUTH2_TOKEN_URL = https://kyc.example.com/token
+# URL of the user info access point.
+KYC_OAUTH2_INFO_URL = https://kyc.example.com/info
+
+# Where does the client get redirected upon completion?
+KYC_OAUTH2_POST_URL = http://example.com/thank-you
+
+# For authentication to the OAuth2.0 service
+KYC_OAUTH2_CLIENT_ID = testcase
+KYC_OAUTH2_CLIENT_SECRET = password
+
+# Mustach template that converts OAuth2.0 data about the user
+# into GNU Taler standardized attribute data.
+#
+# This is just an example, you need to pick the right converter
+# for the provider!
+#
+KYC_OAUTH2_CONVERTER_HELPER = taler-exchange-kyc-oauth2-converter.sh
diff --git a/src/kyclogic/kyclogic-persona.conf b/src/kyclogic/kyclogic-persona.conf
new file mode 100644
index 000000000..2d52a9ee0
--- /dev/null
+++ b/src/kyclogic/kyclogic-persona.conf
@@ -0,0 +1,44 @@
+# This file is in the public domain.
+
+# FIXME: add to taler.conf man page!
+
+# Example persona provider configuration.
+
+[kyclogic-persona]
+
+# Optional authorization token for the webhook.
+# This must be the same for all uses of the
+# Persona provider, and is thus not in a
+# template-specific section.
+#WEBHOOK_AUTH_TOKEN = wbhsec_698b5a19-c790-47f6-b396-deb572ec82f9
+
+
+[kyc-provider-example-persona]
+
+COST = 42
+LOGIC = persona
+USER_TYPE = INDIVIDUAL
+PROVIDED_CHECKS = EXAMPLE_DO_NOT_USE
+
+# How long is the KYC check valid?
+KYC_PERSONA_VALIDITY = forever
+
+# Which subdomain is used for our API?
+KYC_PERSONA_SUBDOMAIN = taler
+
+# Authentication token to use.
+KYC_PERSONA_AUTH_TOKEN = persona_sandbox_42
+
+# Program that converts Persona KYC data into the
+# GNU Taler format.
+KYC_PERSONA_CONVERTER_HELPER = taler-exchange-kyc-persona-converter.sh
+
+# Form to use.
+KYC_PERSONA_TEMPLATE_ID = itempl_Uj6Xxxxx
+
+# Where do we redirect to after KYC finished successfully.
+KYC_PERSONA_POST_URL = https://taler.net/
+
+# Salt to give to requests for idempotency.
+# Optional.
+# KYC_PERSONA_SALT = salt \ No newline at end of file
diff --git a/src/kyclogic/kyclogic.conf b/src/kyclogic/kyclogic.conf
new file mode 100644
index 000000000..eca3b24c2
--- /dev/null
+++ b/src/kyclogic/kyclogic.conf
@@ -0,0 +1,15 @@
+# This file is in the public domain.
+#
+# Sample legitimization rule set.
+
+#[kyc-legitimization-withdraw-high]
+# KYC hook is this rule is about.
+#OPERATION_TYPE = WITHDRAW
+# Which checks must be done. Give names used by providers.
+#REQUIRED_CHECKS = PHONE GOVID SSN
+# Threshold amount above which the checks are required.
+#THRESHOLD = KUDOS:100
+# Timeframe over which amounts involved in the
+# operation type are accumulated to test against
+# the threshold.
+#TIMEFRAME = 1a
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
new file mode 100644
index 000000000..186799dbb
--- /dev/null
+++ b/src/kyclogic/kyclogic_api.c
@@ -0,0 +1,1512 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 kyclogic_api.c
+ * @brief server-side KYC API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_kyclogic_lib.h"
+
+/**
+ * Name of the KYC check that may never be passed. Useful if some
+ * operations/amounts are categorically forbidden.
+ */
+#define KYC_CHECK_IMPOSSIBLE "impossible"
+
+/**
+ * Information about a KYC provider.
+ */
+struct TALER_KYCLOGIC_KycProvider;
+
+
+/**
+ * Abstract representation of a KYC check.
+ */
+struct TALER_KYCLOGIC_KycCheck
+{
+ /**
+ * Human-readable name given to the KYC check.
+ */
+ char *name;
+
+ /**
+ * Array of @e num_providers providers that offer this type of KYC check.
+ */
+ struct TALER_KYCLOGIC_KycProvider **providers;
+
+ /**
+ * Length of the @e providers array.
+ */
+ unsigned int num_providers;
+
+};
+
+
+struct TALER_KYCLOGIC_KycProvider
+{
+ /**
+ * Name of the provider (configuration section name).
+ */
+ const char *provider_section_name;
+
+ /**
+ * Array of @e num_checks checks performed by this provider.
+ */
+ struct TALER_KYCLOGIC_KycCheck **provided_checks;
+
+ /**
+ * Logic to run for this provider.
+ */
+ struct TALER_KYCLOGIC_Plugin *logic;
+
+ /**
+ * @e provider_section_name specific details to
+ * pass to the @e logic functions.
+ */
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Cost of running this provider's KYC.
+ */
+ unsigned long long cost;
+
+ /**
+ * Length of the @e checks array.
+ */
+ unsigned int num_checks;
+
+ /**
+ * Type of user this provider supports.
+ */
+ enum TALER_KYCLOGIC_KycUserType user_type;
+};
+
+
+/**
+ * Condition that triggers a need to perform KYC.
+ */
+struct TALER_KYCLOGIC_KycTrigger
+{
+
+ /**
+ * Timeframe to consider for computing the amount
+ * to compare against the @e limit. Zero for the
+ * wallet balance trigger (as not applicable).
+ */
+ struct GNUNET_TIME_Relative timeframe;
+
+ /**
+ * Maximum amount that can be transacted until
+ * the rule triggers.
+ */
+ struct TALER_Amount threshold;
+
+ /**
+ * Array of @e num_checks checks to apply on this trigger.
+ */
+ struct TALER_KYCLOGIC_KycCheck **required_checks;
+
+ /**
+ * Length of the @e checks array.
+ */
+ unsigned int num_checks;
+
+ /**
+ * What event is this trigger for?
+ */
+ enum TALER_KYCLOGIC_KycTriggerEvent trigger;
+
+};
+
+
+/**
+ * Array of @e num_kyc_logics KYC logic plugins we have loaded.
+ */
+static struct TALER_KYCLOGIC_Plugin **kyc_logics;
+
+/**
+ * Length of the #kyc_logics array.
+ */
+static unsigned int num_kyc_logics;
+
+/**
+ * Array of @e num_kyc_checks known types of
+ * KYC checks.
+ */
+static struct TALER_KYCLOGIC_KycCheck **kyc_checks;
+
+/**
+ * Length of the #kyc_checks array.
+ */
+static unsigned int num_kyc_checks;
+
+/**
+ * Array of configured triggers.
+ */
+static struct TALER_KYCLOGIC_KycTrigger **kyc_triggers;
+
+/**
+ * Length of the #kyc_triggers array.
+ */
+static unsigned int num_kyc_triggers;
+
+/**
+ * Array of configured providers.
+ */
+static struct TALER_KYCLOGIC_KycProvider **kyc_providers;
+
+/**
+ * Length of the #kyc_providers array.
+ */
+static unsigned int num_kyc_providers;
+
+
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_trigger_from_string (const char *trigger_s,
+ enum TALER_KYCLOGIC_KycTriggerEvent *
+ trigger)
+{
+ struct
+ {
+ const char *in;
+ enum TALER_KYCLOGIC_KycTriggerEvent out;
+ } map [] = {
+ { "withdraw", TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW },
+ { "age-withdraw", TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW },
+ { "deposit", TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT },
+ { "merge", TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE },
+ { "balance", TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE },
+ { "close", TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE },
+ { NULL, 0 }
+ };
+
+ for (unsigned int i = 0; NULL != map[i].in; i++)
+ if (0 == strcasecmp (map[i].in,
+ trigger_s))
+ {
+ *trigger = map[i].out;
+ return GNUNET_OK;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid KYC trigger `%s'\n",
+ trigger_s);
+ return GNUNET_SYSERR;
+}
+
+
+const char *
+TALER_KYCLOGIC_kyc_trigger2s (enum TALER_KYCLOGIC_KycTriggerEvent trigger)
+{
+ switch (trigger)
+ {
+ case TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW:
+ return "withdraw";
+ case TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW:
+ return "age-withdraw";
+ case TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT:
+ return "deposit";
+ case TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE:
+ return "merge";
+ case TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE:
+ return "balance";
+ case TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE:
+ return "close";
+ }
+ GNUNET_break (0);
+ return NULL;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_user_type_from_string (const char *ut_s,
+ enum TALER_KYCLOGIC_KycUserType *ut)
+{
+ struct
+ {
+ const char *in;
+ enum TALER_KYCLOGIC_KycUserType out;
+ } map [] = {
+ { "individual", TALER_KYCLOGIC_KYC_UT_INDIVIDUAL },
+ { "business", TALER_KYCLOGIC_KYC_UT_BUSINESS },
+ { NULL, 0 }
+ };
+
+ for (unsigned int i = 0; NULL != map[i].in; i++)
+ if (0 == strcasecmp (map[i].in,
+ ut_s))
+ {
+ *ut = map[i].out;
+ return GNUNET_OK;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid user type `%s'\n",
+ ut_s);
+ return GNUNET_SYSERR;
+}
+
+
+const char *
+TALER_KYCLOGIC_kyc_user_type2s (enum TALER_KYCLOGIC_KycUserType ut)
+{
+ switch (ut)
+ {
+ case TALER_KYCLOGIC_KYC_UT_INDIVIDUAL:
+ return "individual";
+ case TALER_KYCLOGIC_KYC_UT_BUSINESS:
+ return "business";
+ }
+ GNUNET_break (0);
+ return NULL;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_check_satisfiable (
+ const char *check_name)
+{
+ for (unsigned int i = 0; i<num_kyc_checks; i++)
+ if (0 == strcmp (check_name,
+ kyc_checks[i]->name))
+ return GNUNET_OK;
+ if (0 == strcmp (check_name,
+ KYC_CHECK_IMPOSSIBLE))
+ return GNUNET_NO;
+ return GNUNET_SYSERR;
+}
+
+
+json_t *
+TALER_KYCLOGIC_get_satisfiable ()
+{
+ json_t *requirements;
+
+ requirements = json_array ();
+ GNUNET_assert (NULL != requirements);
+ for (unsigned int i = 0; i<num_kyc_checks; i++)
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ requirements,
+ json_string (kyc_checks[i]->name)));
+ return requirements;
+}
+
+
+/**
+ * Load KYC logic plugin.
+ *
+ * @param cfg configuration to use
+ * @param name name of the plugin
+ * @return NULL on error
+ */
+static struct TALER_KYCLOGIC_Plugin *
+load_logic (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *name)
+{
+ char *lib_name;
+ struct TALER_KYCLOGIC_Plugin *plugin;
+
+ GNUNET_asprintf (&lib_name,
+ "libtaler_plugin_kyclogic_%s",
+ name);
+ for (unsigned int i = 0; i<num_kyc_logics; i++)
+ if (0 == strcmp (lib_name,
+ kyc_logics[i]->library_name))
+ {
+ GNUNET_free (lib_name);
+ return kyc_logics[i];
+ }
+ plugin = GNUNET_PLUGIN_load (lib_name,
+ (void *) cfg);
+ if (NULL == plugin)
+ {
+ GNUNET_free (lib_name);
+ return NULL;
+ }
+ plugin->library_name = lib_name;
+ plugin->name = GNUNET_strdup (name);
+ GNUNET_array_append (kyc_logics,
+ num_kyc_logics,
+ plugin);
+ return plugin;
+}
+
+
+/**
+ * Add check type to global array of checks. First checks if the type already
+ * exists, otherwise adds a new one.
+ *
+ * @param check name of the check
+ * @return pointer into the global list
+ */
+static struct TALER_KYCLOGIC_KycCheck *
+add_check (const char *check)
+{
+ struct TALER_KYCLOGIC_KycCheck *kc;
+
+ for (unsigned int i = 0; i<num_kyc_checks; i++)
+ if (0 == strcasecmp (check,
+ kyc_checks[i]->name))
+ return kyc_checks[i];
+ kc = GNUNET_new (struct TALER_KYCLOGIC_KycCheck);
+ kc->name = GNUNET_strdup (check);
+ GNUNET_array_append (kyc_checks,
+ num_kyc_checks,
+ kc);
+ return kc;
+}
+
+
+/**
+ * Parse list of checks from @a checks and build an array of aliases into the
+ * global checks array in @a provided_checks.
+ *
+ * @param[in,out] checks list of checks; clobbered
+ * @param[out] p_checks where to put array of aliases
+ * @param[out] num_p_checks set to length of @a p_checks array
+ */
+static void
+add_checks (char *checks,
+ struct TALER_KYCLOGIC_KycCheck ***p_checks,
+ unsigned int *num_p_checks)
+{
+ char *sptr;
+ struct TALER_KYCLOGIC_KycCheck **rchecks = NULL;
+ unsigned int num_rchecks = 0;
+
+ for (char *tok = strtok_r (checks, " ", &sptr);
+ NULL != tok;
+ tok = strtok_r (NULL, " ", &sptr))
+ {
+ struct TALER_KYCLOGIC_KycCheck *kc;
+
+ kc = add_check (tok);
+ GNUNET_array_append (rchecks,
+ num_rchecks,
+ kc);
+ }
+ *p_checks = rchecks;
+ *num_p_checks = num_rchecks;
+}
+
+
+/**
+ * Parse configuration of a KYC provider.
+ *
+ * @param cfg configuration to parse
+ * @param section name of the section to analyze
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+add_provider (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section)
+{
+ unsigned long long cost;
+ char *logic;
+ char *ut_s;
+ enum TALER_KYCLOGIC_KycUserType ut;
+ char *checks;
+ struct TALER_KYCLOGIC_Plugin *lp;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ section,
+ "COST",
+ &cost))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "COST",
+ "number required");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "USER_TYPE",
+ &ut_s))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "USER_TYPE");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_user_type_from_string (ut_s,
+ &ut))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "USER_TYPE",
+ "valid user type required");
+ GNUNET_free (ut_s);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (ut_s);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "LOGIC",
+ &logic))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "LOGIC");
+ return GNUNET_SYSERR;
+ }
+ lp = load_logic (cfg,
+ logic);
+ if (NULL == lp)
+ {
+ GNUNET_free (logic);
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "LOGIC",
+ "logic plugin could not be loaded");
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (logic);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "PROVIDED_CHECKS",
+ &checks))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "PROVIDED_CHECKS");
+ return GNUNET_SYSERR;
+ }
+ {
+ struct TALER_KYCLOGIC_KycProvider *kp;
+
+ kp = GNUNET_new (struct TALER_KYCLOGIC_KycProvider);
+ kp->provider_section_name = section;
+ kp->user_type = ut;
+ kp->logic = lp;
+ kp->cost = cost;
+ add_checks (checks,
+ &kp->provided_checks,
+ &kp->num_checks);
+ GNUNET_free (checks);
+ kp->pd = lp->load_configuration (lp->cls,
+ section);
+ if (NULL == kp->pd)
+ {
+ GNUNET_free (kp);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_array_append (kyc_providers,
+ num_kyc_providers,
+ kp);
+ for (unsigned int i = 0; i<kp->num_checks; i++)
+ {
+ struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[i];
+
+ GNUNET_array_append (kc->providers,
+ kc->num_providers,
+ kp);
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse configuration @a cfg in section @a section for
+ * the specification of a KYC trigger.
+ *
+ * @param cfg configuration to parse
+ * @param section configuration section to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+add_trigger (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section)
+{
+ char *ot_s;
+ struct TALER_Amount threshold;
+ struct GNUNET_TIME_Relative timeframe;
+ char *checks;
+ enum TALER_KYCLOGIC_KycTriggerEvent ot;
+
+ if (GNUNET_OK !=
+ TALER_config_get_amount (cfg,
+ section,
+ "THRESHOLD",
+ &threshold))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "THRESHOLD",
+ "amount required");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "OPERATION_TYPE",
+ &ot_s))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "OPERATION_TYPE");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_trigger_from_string (ot_s,
+ &ot))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "OPERATION_TYPE",
+ "valid trigger type required");
+ GNUNET_free (ot_s);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (ot_s);
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ section,
+ "TIMEFRAME",
+ &timeframe))
+ {
+ if (TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE == ot)
+ {
+ timeframe = GNUNET_TIME_UNIT_ZERO;
+ }
+ else
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "TIMEFRAME",
+ "duration required");
+ return GNUNET_SYSERR;
+ }
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "REQUIRED_CHECKS",
+ &checks))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "REQUIRED_CHECKS");
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct TALER_KYCLOGIC_KycTrigger *kt;
+
+ kt = GNUNET_new (struct TALER_KYCLOGIC_KycTrigger);
+ kt->timeframe = timeframe;
+ kt->threshold = threshold;
+ kt->trigger = ot;
+ add_checks (checks,
+ &kt->required_checks,
+ &kt->num_checks);
+ GNUNET_free (checks);
+ GNUNET_array_append (kyc_triggers,
+ num_kyc_triggers,
+ kt);
+ for (unsigned int i = 0; i<kt->num_checks; i++)
+ {
+ const struct TALER_KYCLOGIC_KycCheck *ck = kt->required_checks[i];
+
+ if (0 != ck->num_providers)
+ continue;
+ if (0 == strcmp (ck->name,
+ KYC_CHECK_IMPOSSIBLE))
+ continue;
+ {
+ char *msg;
+
+ GNUNET_asprintf (&msg,
+ "Required check `%s' cannot be satisfied: not provided by any provider",
+ ck->name);
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "REQUIRED_CHECKS",
+ msg);
+ GNUNET_free (msg);
+ }
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #handle_section().
+ */
+struct SectionContext
+{
+ /**
+ * Configuration to handle.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Result to return, set to false on failures.
+ */
+ bool result;
+};
+
+
+/**
+ * Function to iterate over configuration sections.
+ *
+ * @param cls a `struct SectionContext *`
+ * @param section name of the section
+ */
+static void
+handle_provider_section (void *cls,
+ const char *section)
+{
+ struct SectionContext *sc = cls;
+
+ if (0 == strncasecmp (section,
+ "kyc-provider-",
+ strlen ("kyc-provider-")))
+ {
+ if (GNUNET_OK !=
+ add_provider (sc->cfg,
+ section))
+ sc->result = false;
+ return;
+ }
+}
+
+
+/**
+ * Function to iterate over configuration sections.
+ *
+ * @param cls a `struct SectionContext *`
+ * @param section name of the section
+ */
+static void
+handle_trigger_section (void *cls,
+ const char *section)
+{
+ struct SectionContext *sc = cls;
+
+ if (0 == strncasecmp (section,
+ "kyc-legitimization-",
+ strlen ("kyc-legitimization-")))
+ {
+ if (GNUNET_OK !=
+ add_trigger (sc->cfg,
+ section))
+ sc->result = false;
+ return;
+ }
+}
+
+
+/**
+ * Comparator for qsort. Compares two triggers
+ * by timeframe to sort triggers by time.
+ *
+ * @param p1 first trigger to compare
+ * @param p2 second trigger to compare
+ * @return -1 if p1 < p2, 0 if p1==p2, 1 if p1 > p2.
+ */
+static int
+sort_by_timeframe (const void *p1,
+ const void *p2)
+{
+ struct TALER_KYCLOGIC_KycTrigger **t1 = (struct
+ TALER_KYCLOGIC_KycTrigger **) p1;
+ struct TALER_KYCLOGIC_KycTrigger **t2 = (struct
+ TALER_KYCLOGIC_KycTrigger **) p2;
+
+ if (GNUNET_TIME_relative_cmp ((*t1)->timeframe,
+ <,
+ (*t2)->timeframe))
+ return -1;
+ if (GNUNET_TIME_relative_cmp ((*t1)->timeframe,
+ >,
+ (*t2)->timeframe))
+ return 1;
+ return 0;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ struct SectionContext sc = {
+ .cfg = cfg,
+ .result = true
+ };
+
+ GNUNET_CONFIGURATION_iterate_sections (cfg,
+ &handle_provider_section,
+ &sc);
+ GNUNET_CONFIGURATION_iterate_sections (cfg,
+ &handle_trigger_section,
+ &sc);
+ if (! sc.result)
+ {
+ TALER_KYCLOGIC_kyc_done ();
+ return GNUNET_SYSERR;
+ }
+
+ /* sanity check: ensure at least one provider exists
+ for any trigger and indidivual or business. */
+ for (unsigned int i = 0; i<num_kyc_checks; i++)
+ if (0 == kyc_checks[i]->num_providers)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No provider available for required KYC check `%s'\n",
+ kyc_checks[i]->name);
+ TALER_KYCLOGIC_kyc_done ();
+ return GNUNET_SYSERR;
+ }
+ if (0 != num_kyc_triggers)
+ qsort (kyc_triggers,
+ num_kyc_triggers,
+ sizeof (struct TALER_KYCLOGIC_KycTrigger *),
+ &sort_by_timeframe);
+ return GNUNET_OK;
+}
+
+
+void
+TALER_KYCLOGIC_kyc_done (void)
+{
+ for (unsigned int i = 0; i<num_kyc_triggers; i++)
+ {
+ struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
+
+ GNUNET_array_grow (kt->required_checks,
+ kt->num_checks,
+ 0);
+ GNUNET_free (kt);
+ }
+ GNUNET_array_grow (kyc_triggers,
+ num_kyc_triggers,
+ 0);
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+
+ kp->logic->unload_configuration (kp->pd);
+ GNUNET_array_grow (kp->provided_checks,
+ kp->num_checks,
+ 0);
+ GNUNET_free (kp);
+ }
+ GNUNET_array_grow (kyc_providers,
+ num_kyc_providers,
+ 0);
+ for (unsigned int i = 0; i<num_kyc_logics; i++)
+ {
+ struct TALER_KYCLOGIC_Plugin *lp = kyc_logics[i];
+ char *lib_name = lp->library_name;
+
+ GNUNET_free (lp->name);
+ GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name,
+ lp));
+ GNUNET_free (lib_name);
+ }
+ GNUNET_array_grow (kyc_logics,
+ num_kyc_logics,
+ 0);
+ for (unsigned int i = 0; i<num_kyc_checks; i++)
+ {
+ struct TALER_KYCLOGIC_KycCheck *kc = kyc_checks[i];
+
+ GNUNET_array_grow (kc->providers,
+ kc->num_providers,
+ 0);
+ GNUNET_free (kc->name);
+ GNUNET_free (kc);
+ }
+ GNUNET_array_grow (kyc_checks,
+ num_kyc_checks,
+ 0);
+}
+
+
+/**
+ * Closure for the #eval_trigger().
+ */
+struct ThresholdTestContext
+{
+ /**
+ * Total amount so far.
+ */
+ struct TALER_Amount total;
+
+ /**
+ * Trigger event to evaluate triggers of.
+ */
+ enum TALER_KYCLOGIC_KycTriggerEvent event;
+
+ /**
+ * Offset in the triggers array where we need to start
+ * checking for triggers. All trigges below this
+ * offset were already hit.
+ */
+ unsigned int start;
+
+ /**
+ * Array of checks needed so far.
+ */
+ struct TALER_KYCLOGIC_KycCheck **needed;
+
+ /**
+ * Pointer to number of entries used in @a needed.
+ */
+ unsigned int *needed_cnt;
+
+ /**
+ * Has @e total been initialized yet?
+ */
+ bool have_total;
+};
+
+
+/**
+ * Function called on each @a amount that was found to
+ * be relevant for a KYC check.
+ *
+ * @param cls closure to allow the KYC module to
+ * total up amounts and evaluate rules
+ * @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
+eval_trigger (void *cls,
+ const struct TALER_Amount *amount,
+ struct GNUNET_TIME_Absolute date)
+{
+ struct ThresholdTestContext *ttc = cls;
+ struct GNUNET_TIME_Relative duration;
+ bool bump = true;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check with new amount %s\n",
+ TALER_amount2s (amount));
+ duration = GNUNET_TIME_absolute_get_duration (date);
+ if (ttc->have_total)
+ {
+ if (0 >
+ TALER_amount_add (&ttc->total,
+ &ttc->total,
+ amount))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ else
+ {
+ ttc->total = *amount;
+ ttc->have_total = true;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check: new total is %s\n",
+ TALER_amount2s (&ttc->total));
+ for (unsigned int i = ttc->start; i<num_kyc_triggers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
+
+ if (ttc->event != kt->trigger)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check #%u: trigger type does not match\n",
+ i);
+ continue;
+ }
+ duration = GNUNET_TIME_relative_max (duration,
+ kt->timeframe);
+ if (GNUNET_TIME_relative_cmp (kt->timeframe,
+ >,
+ duration))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check #%u: amount is beyond time limit\n",
+ i);
+ if (bump)
+ ttc->start = i;
+ return GNUNET_OK;
+ }
+ if (-1 ==
+ TALER_amount_cmp (&ttc->total,
+ &kt->threshold))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check #%u: amount is below threshold\n",
+ i);
+ if (bump)
+ ttc->start = i;
+ bump = false;
+ continue; /* amount too low to trigger */
+ }
+ /* add check to list of required checks, unless
+ already present... */
+ for (unsigned int j = 0; j<kt->num_checks; j++)
+ {
+ struct TALER_KYCLOGIC_KycCheck *rc = kt->required_checks[j];
+ bool found = false;
+
+ for (unsigned int k = 0; k<*ttc->needed_cnt; k++)
+ if (ttc->needed[k] == rc)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC rule #%u already listed\n",
+ j);
+ found = true;
+ break;
+ }
+ if (! found)
+ {
+ ttc->needed[*ttc->needed_cnt] = rc;
+ (*ttc->needed_cnt)++;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check #%u (%s) is applicable, %u checks needed so far\n",
+ i,
+ ttc->needed[(*ttc->needed_cnt) - 1]->name,
+ *ttc->needed_cnt);
+ }
+ if (bump)
+ return GNUNET_NO; /* we hit all possible triggers! */
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for the #remove_satisfied().
+ */
+struct RemoveContext
+{
+
+ /**
+ * Array of checks needed so far.
+ */
+ struct TALER_KYCLOGIC_KycCheck **needed;
+
+ /**
+ * Pointer to number of entries used in @a needed.
+ */
+ unsigned int *needed_cnt;
+
+ /**
+ * Object with information about collected KYC data.
+ */
+ json_t *kyc_details;
+};
+
+
+/**
+ * Remove all checks satisfied by @a provider_name from
+ * our list of checks.
+ *
+ * @param cls a `struct RemoveContext`
+ * @param provider_name section name of provider that was already run previously
+ */
+static void
+remove_satisfied (void *cls,
+ const char *provider_name)
+{
+ struct RemoveContext *rc = cls;
+
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+
+ if (0 != strcasecmp (provider_name,
+ kp->provider_section_name))
+ continue;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Provider `%s' satisfied\n",
+ provider_name);
+ for (unsigned int j = 0; j<kp->num_checks; j++)
+ {
+ const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j];
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Provider satisfies check `%s'\n",
+ kc->name);
+ if (NULL != rc->kyc_details)
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ rc->kyc_details,
+ kc->name,
+ json_object ()));
+ }
+ for (unsigned int k = 0; k<*rc->needed_cnt; k++)
+ if (kc == rc->needed[k])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Removing check `%s' from list\n",
+ kc->name);
+ rc->needed[k] = rc->needed[*rc->needed_cnt - 1];
+ (*rc->needed_cnt)--;
+ if (0 == *rc->needed_cnt)
+ return; /* for sure finished */
+ break;
+ }
+ }
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_KYCLOGIC_KycSatisfiedIterator ki,
+ void *ki_cls,
+ TALER_KYCLOGIC_KycAmountIterator ai,
+ void *ai_cls,
+ char **required)
+{
+ struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks];
+ unsigned int needed_cnt = 0;
+ char *ret;
+ struct GNUNET_TIME_Relative timeframe;
+
+ timeframe = GNUNET_TIME_UNIT_ZERO;
+ for (unsigned int i = 0; i<num_kyc_triggers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
+
+ if (event != kt->trigger)
+ continue;
+ timeframe = GNUNET_TIME_relative_max (timeframe,
+ kt->timeframe);
+ }
+ {
+ struct GNUNET_TIME_Absolute now;
+ struct ThresholdTestContext ttc = {
+ .event = event,
+ .needed = needed,
+ .needed_cnt = &needed_cnt
+ };
+
+ now = GNUNET_TIME_absolute_get ();
+ ai (ai_cls,
+ GNUNET_TIME_absolute_subtract (now,
+ timeframe),
+ &eval_trigger,
+ &ttc);
+ }
+ if (0 == needed_cnt)
+ {
+ *required = NULL;
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ timeframe = GNUNET_TIME_UNIT_ZERO;
+ for (unsigned int i = 0; i<num_kyc_triggers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
+
+ if (event != kt->trigger)
+ continue;
+ timeframe = GNUNET_TIME_relative_max (timeframe,
+ kt->timeframe);
+ }
+ {
+ struct GNUNET_TIME_Absolute now;
+ struct ThresholdTestContext ttc = {
+ .event = event,
+ .needed = needed,
+ .needed_cnt = &needed_cnt
+ };
+
+ now = GNUNET_TIME_absolute_get ();
+ ai (ai_cls,
+ GNUNET_TIME_absolute_subtract (now,
+ timeframe),
+ &eval_trigger,
+ &ttc);
+ }
+ if (0 == needed_cnt)
+ {
+ *required = NULL;
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ {
+ struct RemoveContext rc = {
+ .needed = needed,
+ .needed_cnt = &needed_cnt
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Check what provider checks are already satisfied for h_payto (with
+ database), remove those from the 'needed' array. */
+ qs = ki (ki_cls,
+ h_payto,
+ &remove_satisfied,
+ &rc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ }
+ if (0 == needed_cnt)
+ {
+ *required = NULL;
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ {
+ struct RemoveContext rc = {
+ .needed = needed,
+ .needed_cnt = &needed_cnt
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Check what provider checks are already satisfied for h_payto (with
+ database), remove those from the 'needed' array. */
+ qs = ki (ki_cls,
+ h_payto,
+ &remove_satisfied,
+ &rc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ }
+ if (0 == needed_cnt)
+ {
+ *required = NULL;
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ ret = NULL;
+ for (unsigned int k = 0; k<needed_cnt; k++)
+ {
+ const struct TALER_KYCLOGIC_KycCheck *kc = needed[k];
+
+ if (NULL == ret)
+ {
+ ret = GNUNET_strdup (kc->name);
+ }
+ else /* append */
+ {
+ char *tmp = ret;
+
+ GNUNET_asprintf (&ret,
+ "%s %s",
+ tmp,
+ kc->name);
+ GNUNET_free (tmp);
+ }
+ }
+ *required = ret;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+void
+TALER_KYCLOGIC_kyc_get_details (
+ const char *logic_name,
+ TALER_KYCLOGIC_DetailsCallback cb,
+ void *cb_cls)
+{
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+
+ if (0 !=
+ strcmp (kp->logic->name,
+ logic_name))
+ continue;
+ if (GNUNET_OK !=
+ cb (cb_cls,
+ kp->pd,
+ kp->logic->cls))
+ return;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TALER_KYCLOGIC_check_satisfied (char **requirements,
+ const struct TALER_PaytoHashP *h_payto,
+ json_t **kyc_details,
+ TALER_KYCLOGIC_KycSatisfiedIterator ki,
+ void *ki_cls,
+ bool *satisfied)
+{
+ struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks];
+ unsigned int needed_cnt = 0;
+
+ if (NULL == requirements)
+ {
+ *satisfied = true;
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ {
+ char *req = *requirements;
+
+ for (const char *tok = strtok (req, " ");
+ NULL != tok;
+ tok = strtok (NULL, " "))
+ needed[needed_cnt++] = add_check (tok);
+ GNUNET_free (req);
+ *requirements = NULL;
+ }
+
+ {
+ struct RemoveContext rc = {
+ .needed = needed,
+ .needed_cnt = &needed_cnt,
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ rc.kyc_details = json_object ();
+ GNUNET_assert (NULL != rc.kyc_details);
+
+ /* Check what provider checks are already satisfied for h_payto (with
+ database), remove those from the 'needed' array. */
+ qs = ki (ki_cls,
+ h_payto,
+ &remove_satisfied,
+ &rc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ *satisfied = false;
+ return qs;
+ }
+ if (0 != needed_cnt)
+ {
+ json_decref (rc.kyc_details);
+ *kyc_details = NULL;
+ }
+ else
+ {
+ *kyc_details = rc.kyc_details;
+ }
+ }
+ *satisfied = (0 == needed_cnt);
+
+ {
+ char *res = NULL;
+
+ for (unsigned int i = 0; i<needed_cnt; i++)
+ {
+ const struct TALER_KYCLOGIC_KycCheck *need = needed[i];
+
+ if (NULL == res)
+ {
+ res = GNUNET_strdup (need->name);
+ }
+ else
+ {
+ char *tmp;
+
+ GNUNET_asprintf (&tmp,
+ "%s %s",
+ res,
+ need->name);
+ GNUNET_free (res);
+ res = tmp;
+ }
+ }
+ *requirements = res;
+ }
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_requirements_to_logic (const char *requirements,
+ enum TALER_KYCLOGIC_KycUserType ut,
+ struct TALER_KYCLOGIC_Plugin **plugin,
+ struct TALER_KYCLOGIC_ProviderDetails **pd,
+ const char **configuration_section)
+{
+ struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks];
+ unsigned int needed_cnt = 0;
+ unsigned long long min_cost = ULLONG_MAX;
+ unsigned int max_checks = 0;
+ const struct TALER_KYCLOGIC_KycProvider *kp_best = NULL;
+
+ if (NULL == requirements)
+ return GNUNET_NO;
+ {
+ char *req = GNUNET_strdup (requirements);
+
+ for (const char *tok = strtok (req, " ");
+ NULL != tok;
+ tok = strtok (NULL, " "))
+ needed[needed_cnt++] = add_check (tok);
+ GNUNET_free (req);
+ }
+
+ /* Count maximum number of remaining checks covered by any
+ provider */
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+ unsigned int matched = 0;
+
+ if (kp->user_type != ut)
+ continue;
+ for (unsigned int j = 0; j<kp->num_checks; j++)
+ {
+ const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j];
+
+ for (unsigned int k = 0; k<needed_cnt; k++)
+ if (kc == needed[k])
+ {
+ matched++;
+ break;
+ }
+ }
+ max_checks = GNUNET_MAX (max_checks,
+ matched);
+ }
+ if (0 == max_checks)
+ return GNUNET_SYSERR;
+
+ /* Find min-cost provider covering max_checks. */
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+ unsigned int matched = 0;
+
+ if (kp->user_type != ut)
+ continue;
+ for (unsigned int j = 0; j<kp->num_checks; j++)
+ {
+ const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j];
+
+ for (unsigned int k = 0; k<needed_cnt; k++)
+ if (kc == needed[k])
+ {
+ matched++;
+ break;
+ }
+ }
+ if ( (max_checks == matched) &&
+ (kp->cost < min_cost) )
+ {
+ min_cost = kp->cost;
+ kp_best = kp;
+ }
+ }
+ GNUNET_assert (NULL != kp_best);
+ *plugin = kp_best->logic;
+ *pd = kp_best->pd;
+ *configuration_section = kp_best->provider_section_name;
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_lookup_logic (const char *name,
+ struct TALER_KYCLOGIC_Plugin **plugin,
+ struct TALER_KYCLOGIC_ProviderDetails **pd,
+ const char **provider_section)
+{
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+
+ if (0 !=
+ strcasecmp (name,
+ kp->provider_section_name))
+ continue;
+ *plugin = kp->logic;
+ *pd = kp->pd;
+ *provider_section = kp->provider_section_name;
+ return GNUNET_OK;
+ }
+ for (unsigned int i = 0; i<num_kyc_logics; i++)
+ {
+ struct TALER_KYCLOGIC_Plugin *logic = kyc_logics[i];
+
+ if (0 !=
+ strcasecmp (logic->name,
+ name))
+ continue;
+ *plugin = logic;
+ *pd = NULL;
+ *provider_section = NULL;
+ return GNUNET_OK;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Provider `%s' unknown\n",
+ name);
+ return GNUNET_SYSERR;
+}
+
+
+void
+TALER_KYCLOGIC_kyc_iterate_thresholds (
+ enum TALER_KYCLOGIC_KycTriggerEvent event,
+ TALER_KYCLOGIC_KycThresholdIterator it,
+ void *it_cls)
+{
+ for (unsigned int i = 0; i<num_kyc_triggers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
+
+ if (event != kt->trigger)
+ continue;
+ it (it_cls,
+ &kt->threshold);
+ }
+}
+
+
+void
+TALER_KYCLOGIC_lookup_checks (const char *section_name,
+ unsigned int *num_checks,
+ char ***provided_checks)
+{
+ *num_checks = 0;
+ *provided_checks = NULL;
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+
+ if (0 !=
+ strcasecmp (section_name,
+ kp->provider_section_name))
+ continue;
+ *num_checks = kp->num_checks;
+ if (0 != kp->num_checks)
+ {
+ char **pc = GNUNET_new_array (kp->num_checks,
+ char *);
+ for (unsigned int i = 0; i<kp->num_checks; i++)
+ pc[i] = GNUNET_strdup (kp->provided_checks[i]->name);
+ *provided_checks = pc;
+ }
+ return;
+ }
+}
+
+
+/* end of taler-exchange-httpd_kyc.c */
diff --git a/src/kyclogic/plugin_kyclogic_kycaid.c b/src/kyclogic/plugin_kyclogic_kycaid.c
new file mode 100644
index 000000000..243ff7c34
--- /dev/null
+++ b/src/kyclogic/plugin_kyclogic_kycaid.c
@@ -0,0 +1,1480 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2022--2024 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.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file plugin_kyclogic_kycaid.c
+ * @brief kycaid for an authentication flow logic
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_attributes.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_kyclogic_plugin.h"
+#include "taler_mhd_lib.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+#include "taler_templating_lib.h"
+#include <regex.h>
+#include "taler_util.h"
+
+
+/**
+ * Saves the state of a plugin.
+ */
+struct PluginState
+{
+
+ /**
+ * Our base URL.
+ */
+ char *exchange_base_url;
+
+ /**
+ * Our global configuration.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Context for CURL operations (useful to the event loop)
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ /**
+ * Context for integrating @e curl_ctx with the
+ * GNUnet event loop.
+ */
+ struct GNUNET_CURL_RescheduleContext *curl_rc;
+
+};
+
+
+/**
+ * Keeps the plugin-specific state for
+ * a given configuration section.
+ */
+struct TALER_KYCLOGIC_ProviderDetails
+{
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+
+ /**
+ * Configuration section that configured us.
+ */
+ char *section;
+
+ /**
+ * Authorization token to use when talking
+ * to the service.
+ */
+ char *auth_token;
+
+ /**
+ * Form ID for the KYC check to perform.
+ */
+ char *form_id;
+
+ /**
+ * Helper binary to convert attributes returned by
+ * KYCAID into our internal format.
+ */
+ char *conversion_helper;
+
+ /**
+ * Validity time for a successful KYC process.
+ */
+ struct GNUNET_TIME_Relative validity;
+
+ /**
+ * Curl-ready authentication header to use.
+ */
+ struct curl_slist *slist;
+
+};
+
+
+/**
+ * Handle for an initiation operation.
+ */
+struct TALER_KYCLOGIC_InitiateHandle
+{
+
+ /**
+ * Hash of the payto:// URI we are initiating
+ * the KYC for.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * UUID being checked.
+ */
+ uint64_t legitimization_uuid;
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Continuation to call.
+ */
+ TALER_KYCLOGIC_InitiateCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * URL of the cURL request.
+ */
+ char *url;
+
+};
+
+
+/**
+ * Handle for an KYC proof operation.
+ */
+struct TALER_KYCLOGIC_ProofHandle
+{
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Continuation to call.
+ */
+ TALER_KYCLOGIC_ProofCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Task for asynchronous execution.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+};
+
+
+/**
+ * Handle for an KYC Web hook operation.
+ */
+struct TALER_KYCLOGIC_WebhookHandle
+{
+
+ /**
+ * Continuation to call when done.
+ */
+ TALER_KYCLOGIC_WebhookCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Task for asynchronous execution.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+
+ /**
+ * Handle to helper process to extract attributes
+ * we care about.
+ */
+ struct TALER_JSON_ExternalConversion *econ;
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * JSON response we got back, or NULL for none.
+ */
+ json_t *json_response;
+
+ /**
+ * Verification ID from the service.
+ */
+ char *verification_id;
+
+ /**
+ * Applicant ID from the service.
+ */
+ char *applicant_id;
+
+ /**
+ * URL of the cURL request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Response to return asynchronously.
+ */
+ struct MHD_Response *resp;
+
+ /**
+ * Our account ID.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Row in legitimizations for the given
+ * @e verification_id.
+ */
+ uint64_t process_row;
+
+ /**
+ * HTTP response code we got from KYCAID.
+ */
+ unsigned int kycaid_response_code;
+
+ /**
+ * HTTP response code to return asynchronously.
+ */
+ unsigned int response_code;
+};
+
+
+/**
+ * Release configuration resources previously loaded
+ *
+ * @param[in] pd configuration to release
+ */
+static void
+kycaid_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
+{
+ curl_slist_free_all (pd->slist);
+ GNUNET_free (pd->conversion_helper);
+ GNUNET_free (pd->auth_token);
+ GNUNET_free (pd->form_id);
+ GNUNET_free (pd->section);
+ GNUNET_free (pd);
+}
+
+
+/**
+ * Load the configuration of the KYC provider.
+ *
+ * @param cls closure
+ * @param provider_section_name configuration section to parse
+ * @return NULL if configuration is invalid
+ */
+static struct TALER_KYCLOGIC_ProviderDetails *
+kycaid_load_configuration (void *cls,
+ const char *provider_section_name)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
+ pd->ps = ps;
+ pd->section = GNUNET_strdup (provider_section_name);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (ps->cfg,
+ provider_section_name,
+ "KYC_KYCAID_VALIDITY",
+ &pd->validity))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_KYCAID_VALIDITY");
+ kycaid_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_KYCAID_AUTH_TOKEN",
+ &pd->auth_token))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_KYCAID_AUTH_TOKEN");
+ kycaid_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_KYCAID_FORM_ID",
+ &pd->form_id))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_KYCAID_FORM_ID");
+ kycaid_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_KYCAID_CONVERTER_HELPER",
+ &pd->conversion_helper))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_KYCAID_CONVERTER_HELPER");
+ kycaid_unload_configuration (pd);
+ return NULL;
+ }
+ {
+ char *auth;
+
+ GNUNET_asprintf (&auth,
+ "%s: Token %s",
+ MHD_HTTP_HEADER_AUTHORIZATION,
+ pd->auth_token);
+ pd->slist = curl_slist_append (NULL,
+ auth);
+ GNUNET_free (auth);
+ }
+ return pd;
+}
+
+
+/**
+ * Cancel KYC check initiation.
+ *
+ * @param[in] ih handle of operation to cancel
+ */
+static void
+kycaid_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
+{
+ if (NULL != ih->job)
+ {
+ GNUNET_CURL_job_cancel (ih->job);
+ ih->job = NULL;
+ }
+ GNUNET_free (ih->url);
+ TALER_curl_easy_post_finished (&ih->ctx);
+ GNUNET_free (ih);
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP "/forms/{form_id}/urls" request.
+ *
+ * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_initiate_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
+ const json_t *j = response;
+
+ ih->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const char *verification_id;
+ const char *form_url;
+ const char *form_id;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("verification_id",
+ &verification_id),
+ GNUNET_JSON_spec_string ("form_url",
+ &form_url),
+ GNUNET_JSON_spec_string ("form_id",
+ &form_id),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
+ NULL,
+ NULL,
+ NULL,
+ json_string_value (json_object_get (j,
+ "type")));
+ break;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Started new verification `%s' using form %s\n",
+ verification_id,
+ form_id);
+ ih->cb (ih->cb_cls,
+ TALER_EC_NONE,
+ form_url,
+ NULL, /* no provider_user_id */
+ verification_id,
+ NULL /* no error */);
+ GNUNET_JSON_parse_free (spec);
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_CONFLICT:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYCAID failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
+ NULL,
+ NULL,
+ NULL,
+ json_string_value (json_object_get (j,
+ "type")));
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refused access with HTTP status code %u\n",
+ (unsigned int) response_code);
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED,
+ NULL,
+ NULL,
+ NULL,
+ json_string_value (json_object_get (j,
+ "type")));
+ break;
+ case MHD_HTTP_REQUEST_TIMEOUT:
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT,
+ NULL,
+ NULL,
+ NULL,
+ json_string_value (json_object_get (j,
+ "type")));
+ break;
+ case MHD_HTTP_UNPROCESSABLE_ENTITY: /* validation */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYCAID failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
+ NULL,
+ NULL,
+ NULL,
+ json_string_value (json_object_get (j,
+ "type")));
+ break;
+ case MHD_HTTP_TOO_MANY_REQUESTS:
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED,
+ NULL,
+ NULL,
+ NULL,
+ json_string_value (json_object_get (j,
+ "type")));
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
+ NULL,
+ NULL,
+ NULL,
+ json_string_value (json_object_get (j,
+ "type")));
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected KYCAID response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
+ NULL,
+ NULL,
+ NULL,
+ json_string_value (json_object_get (j,
+ "type")));
+ break;
+ }
+ kycaid_initiate_cancel (ih);
+}
+
+
+/**
+ * Initiate KYC check.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param account_id which account to trigger process for
+ * @param legitimization_uuid unique ID for the legitimization process
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_InitiateHandle *
+kycaid_initiate (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ const struct TALER_PaytoHashP *account_id,
+ uint64_t legitimization_uuid,
+ TALER_KYCLOGIC_InitiateCallback cb,
+ void *cb_cls)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_InitiateHandle *ih;
+ json_t *body;
+ CURL *eh;
+
+ eh = curl_easy_init ();
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
+ ih->legitimization_uuid = legitimization_uuid;
+ ih->cb = cb;
+ ih->cb_cls = cb_cls;
+ ih->h_payto = *account_id;
+ ih->pd = pd;
+ GNUNET_asprintf (&ih->url,
+ "https://api.kycaid.com/forms/%s/urls",
+ pd->form_id);
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data64_auto ("external_applicant_id",
+ account_id)
+ );
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_VERBOSE,
+ 0));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_MAXREDIRS,
+ 1L));
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ ih->url));
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&ih->ctx,
+ eh,
+ body))
+ {
+ GNUNET_break (0);
+ GNUNET_free (ih->url);
+ GNUNET_free (ih);
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ return NULL;
+ }
+ json_decref (body);
+ ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
+ eh,
+ ih->ctx.headers,
+ &handle_initiate_finished,
+ ih);
+ GNUNET_CURL_extend_headers (ih->job,
+ pd->slist);
+ return ih;
+}
+
+
+/**
+ * Cancel KYC proof.
+ *
+ * @param[in] ph handle of operation to cancel
+ */
+static void
+kycaid_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
+{
+ if (NULL != ph->task)
+ {
+ GNUNET_SCHEDULER_cancel (ph->task);
+ ph->task = NULL;
+ }
+ GNUNET_free (ph);
+}
+
+
+/**
+ * Call @a ph callback with HTTP error response.
+ *
+ * @param cls proof handle to generate reply for
+ */
+static void
+proof_reply (void *cls)
+{
+ struct TALER_KYCLOGIC_ProofHandle *ph = cls;
+ struct MHD_Response *resp;
+ enum GNUNET_GenericReturnValue ret;
+ json_t *body;
+ unsigned int http_status;
+
+ http_status = MHD_HTTP_BAD_REQUEST;
+ body = GNUNET_JSON_PACK (
+ TALER_JSON_pack_ec (TALER_EC_GENERIC_ENDPOINT_UNKNOWN));
+ GNUNET_assert (NULL != body);
+ ret = TALER_TEMPLATING_build (ph->connection,
+ &http_status,
+ "kycaid-invalid-request",
+ NULL,
+ NULL,
+ body,
+ &resp);
+ json_decref (body);
+ GNUNET_break (GNUNET_SYSERR != ret);
+ ph->cb (ph->cb_cls,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ NULL, /* user id */
+ NULL, /* provider legi ID */
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL, /* attributes */
+ http_status,
+ resp);
+}
+
+
+/**
+ * Check KYC status and return status to human. Not
+ * used by KYC AID!
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param connection MHD connection object (for HTTP headers)
+ * @param account_id which account to trigger process for
+ * @param process_row row in the legitimization processes table the legitimization is for
+ * @param provider_user_id user ID (or NULL) the proof is for
+ * @param provider_legitimization_id legitimization ID the proof is for
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_ProofHandle *
+kycaid_proof (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ struct MHD_Connection *connection,
+ const struct TALER_PaytoHashP *account_id,
+ uint64_t process_row,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ TALER_KYCLOGIC_ProofCallback cb,
+ void *cb_cls)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_ProofHandle *ph;
+
+ ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
+ ph->ps = ps;
+ ph->pd = pd;
+ ph->cb = cb;
+ ph->cb_cls = cb_cls;
+ ph->connection = connection;
+ ph->task = GNUNET_SCHEDULER_add_now (&proof_reply,
+ ph);
+ return ph;
+}
+
+
+/**
+ * Cancel KYC webhook execution.
+ *
+ * @param[in] wh handle of operation to cancel
+ */
+static void
+kycaid_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
+{
+ if (NULL != wh->task)
+ {
+ GNUNET_SCHEDULER_cancel (wh->task);
+ wh->task = NULL;
+ }
+ if (NULL != wh->econ)
+ {
+ TALER_JSON_external_conversion_stop (wh->econ);
+ wh->econ = NULL;
+ }
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ if (NULL != wh->json_response)
+ {
+ json_decref (wh->json_response);
+ wh->json_response = NULL;
+ }
+ GNUNET_free (wh->verification_id);
+ GNUNET_free (wh->applicant_id);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
+
+
+/**
+ * Extract KYC failure reasons and log those
+ *
+ * @param verifications JSON object with failure details
+ */
+static void
+log_failure (const json_t *verifications)
+{
+ const json_t *member;
+ const char *name;
+
+ json_object_foreach ((json_t *) verifications, name, member)
+ {
+ bool iverified;
+ const char *comment;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_bool ("verified",
+ &iverified),
+ GNUNET_JSON_spec_string ("comment",
+ &comment),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (member,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ json_dumpf (member,
+ stderr,
+ JSON_INDENT (2));
+ continue;
+ }
+ if (iverified)
+ continue;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC verification of attribute `%s' failed: %s\n",
+ name,
+ comment);
+ }
+}
+
+
+/**
+ * Type of a callback that receives a JSON @a result.
+ *
+ * @param cls closure our `struct TALER_KYCLOGIC_WebhookHandle *`
+ * @param status_type how did the process die
+ * @param code termination status code from the process
+ * @param result converted attribute data, NULL on failure
+ */
+static void
+webhook_conversion_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType status_type,
+ unsigned long code,
+ const json_t *result)
+{
+ struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
+ struct GNUNET_TIME_Absolute expiration;
+ struct MHD_Response *resp;
+
+ wh->econ = NULL;
+ if ( (0 == code) &&
+ (NULL == result) )
+ {
+ /* No result, but *our helper* was OK => bad input */
+ GNUNET_break_op (0);
+ json_dumpf (wh->json_response,
+ stderr,
+ JSON_INDENT (2));
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("kycaid_http_status",
+ wh->kycaid_response_code),
+ GNUNET_JSON_pack_object_incref ("kycaid_body",
+ (json_t *) wh->json_response));
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ MHD_HTTP_BAD_GATEWAY,
+ resp);
+ kycaid_webhook_cancel (wh);
+ return;
+ }
+ if (NULL == result)
+ {
+ /* Failure in our helper */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Helper exited with status code %d\n",
+ (int) code);
+ json_dumpf (wh->json_response,
+ stderr,
+ JSON_INDENT (2));
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("kycaid_http_status",
+ wh->kycaid_response_code),
+ GNUNET_JSON_pack_object_incref ("kycaid_body",
+ (json_t *) wh->json_response));
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ MHD_HTTP_BAD_GATEWAY,
+ resp);
+ kycaid_webhook_cancel (wh);
+ return;
+ }
+ expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
+ resp = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_SUCCESS,
+ expiration,
+ result,
+ MHD_HTTP_NO_CONTENT,
+ resp);
+ kycaid_webhook_cancel (wh);
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP "/applicants/{verification_id}" request.
+ *
+ * @param cls the `struct TALER_KYCLOGIC_WebhookHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_webhook_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
+ const json_t *j = response;
+ struct MHD_Response *resp;
+
+ wh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Webhook returned with HTTP status %u\n",
+ (unsigned int) response_code);
+ wh->kycaid_response_code = response_code;
+ wh->json_response = json_incref ((json_t *) j);
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const char *profile_status;
+
+ profile_status = json_string_value (
+ json_object_get (
+ j,
+ "profile_status"));
+ if (0 != strcasecmp ("valid",
+ profile_status))
+ {
+ enum TALER_KYCLOGIC_KycStatus ks;
+
+ ks = (0 == strcasecmp ("pending",
+ profile_status))
+ ? TALER_KYCLOGIC_STATUS_PENDING
+ : TALER_KYCLOGIC_STATUS_USER_ABORTED;
+ resp = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ ks,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ NULL,
+ MHD_HTTP_NO_CONTENT,
+ resp);
+ break;
+ }
+ wh->econ
+ = TALER_JSON_external_conversion_start (
+ j,
+ &webhook_conversion_cb,
+ wh,
+ wh->pd->conversion_helper,
+ wh->pd->conversion_helper,
+ "-a",
+ wh->pd->auth_token,
+ NULL);
+ if (NULL == wh->econ)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start KYCAID conversion helper `%s'\n",
+ wh->pd->conversion_helper);
+ resp = TALER_MHD_make_error (
+ TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED,
+ NULL);
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_INTERNAL_ERROR,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ resp);
+ break;
+ }
+ return;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_CONFLICT:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYCAID failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("kycaid_http_status",
+ response_code));
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ resp);
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refused access with HTTP status code %u\n",
+ (unsigned int) response_code);
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("kycaid_http_status",
+ response_code),
+ GNUNET_JSON_pack_object_incref ("kycaid_body",
+ (json_t *) j));
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ MHD_HTTP_NETWORK_AUTHENTICATION_REQUIRED,
+ resp);
+ break;
+ case MHD_HTTP_REQUEST_TIMEOUT:
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("kycaid_http_status",
+ response_code),
+ GNUNET_JSON_pack_object_incref ("kycaid_body",
+ (json_t *) j));
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ MHD_HTTP_GATEWAY_TIMEOUT,
+ resp);
+ break;
+ case MHD_HTTP_UNPROCESSABLE_ENTITY: /* validation */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYCAID failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("kycaid_http_status",
+ response_code),
+ GNUNET_JSON_pack_object_incref ("kycaid_body",
+ (json_t *) j));
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ MHD_HTTP_BAD_GATEWAY,
+ resp);
+ break;
+ case MHD_HTTP_TOO_MANY_REQUESTS:
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("kycaid_http_status",
+ response_code),
+ GNUNET_JSON_pack_object_incref ("kycaid_body",
+ (json_t *) j));
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ resp);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("kycaid_http_status",
+ response_code),
+ GNUNET_JSON_pack_object_incref ("kycaid_body",
+ (json_t *) j));
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ MHD_HTTP_BAD_GATEWAY,
+ resp);
+ break;
+ default:
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("kycaid_http_status",
+ response_code),
+ GNUNET_JSON_pack_object_incref ("kycaid_body",
+ (json_t *) j));
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected KYCAID response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ MHD_HTTP_BAD_GATEWAY,
+ resp);
+ break;
+ }
+ kycaid_webhook_cancel (wh);
+}
+
+
+/**
+ * Asynchronously return a reply for the webhook.
+ *
+ * @param cls a `struct TALER_KYCLOGIC_WebhookHandle *`
+ */
+static void
+async_webhook_reply (void *cls)
+{
+ struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
+
+ wh->task = NULL;
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ (0 == wh->process_row)
+ ? NULL
+ : &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id, /* provider user ID */
+ wh->verification_id, /* provider legi ID */
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ wh->response_code,
+ wh->resp);
+ kycaid_webhook_cancel (wh);
+}
+
+
+/**
+ * Check KYC status and return result for Webhook. We do NOT implement the
+ * authentication check proposed by the KYCAID documentation, as it would
+ * allow an attacker who learns the access token to easily bypass the KYC
+ * checks. Instead, we insist on explicitly requesting the KYC status from the
+ * provider (at least on success).
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param plc callback to lookup accounts with
+ * @param plc_cls closure for @a plc
+ * @param http_method HTTP method used for the webhook
+ * @param url_path rest of the URL after `/kyc-webhook/`
+ * @param connection MHD connection object (for HTTP headers)
+ * @param body HTTP request body
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_WebhookHandle *
+kycaid_webhook (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ TALER_KYCLOGIC_ProviderLookupCallback plc,
+ void *plc_cls,
+ const char *http_method,
+ const char *const url_path[],
+ struct MHD_Connection *connection,
+ const json_t *body,
+ TALER_KYCLOGIC_WebhookCallback cb,
+ void *cb_cls)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_WebhookHandle *wh;
+ CURL *eh;
+ const char *request_id;
+ const char *type;
+ const char *verification_id; /* = provider_legitimization_id */
+ const char *applicant_id;
+ const char *form_id;
+ const char *status = NULL;
+ bool verified = false;
+ bool no_verified = true;
+ const json_t *verifications = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("request_id",
+ &request_id),
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_string ("verification_id",
+ &verification_id),
+ GNUNET_JSON_spec_string ("applicant_id",
+ &applicant_id),
+ GNUNET_JSON_spec_string ("form_id",
+ &form_id),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("status",
+ &status),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("verified",
+ &verified),
+ &no_verified),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("verifications",
+ &verifications),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ps = ps;
+ wh->pd = pd;
+ wh->connection = connection;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYCAID webhook of `%s' triggered with %s\n",
+ pd->section,
+ http_method);
+#if 1
+ if (NULL != body)
+ json_dumpf (body,
+ stderr,
+ JSON_INDENT (2));
+#endif
+ if (NULL == pd)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (body,
+ stderr,
+ JSON_INDENT (2));
+ wh->resp = TALER_MHD_make_error (
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
+ "kycaid");
+ wh->response_code = MHD_HTTP_NOT_FOUND;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (body,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ json_dumpf (body,
+ stderr,
+ JSON_INDENT (2));
+ wh->resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_object_incref ("webhook_body",
+ (json_t *) body));
+ wh->response_code = MHD_HTTP_BAD_REQUEST;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+ qs = plc (plc_cls,
+ pd->section,
+ verification_id,
+ &wh->h_payto,
+ &wh->process_row);
+ if (qs < 0)
+ {
+ wh->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "provider-legitimization-lookup");
+ wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Received webhook for unknown verification ID `%s' and section `%s'\n",
+ verification_id,
+ pd->section);
+ wh->resp = TALER_MHD_make_error (
+ TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
+ verification_id);
+ wh->response_code = MHD_HTTP_NOT_FOUND;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+ wh->verification_id = GNUNET_strdup (verification_id);
+ wh->applicant_id = GNUNET_strdup (applicant_id);
+ if ( (0 != strcasecmp (type,
+ "VERIFICATION_COMPLETED")) ||
+ (no_verified) ||
+ (! verified) )
+ {
+ /* We don't need to re-confirm the failure by
+ asking the API again. */
+ log_failure (verifications);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Webhook called with non-completion status: %s\n",
+ type);
+ wh->response_code = MHD_HTTP_NO_CONTENT;
+ wh->resp = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+
+ eh = curl_easy_init ();
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ wh->resp = TALER_MHD_make_error (
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ NULL);
+ wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+
+ GNUNET_asprintf (&wh->url,
+ "https://api.kycaid.com/applicants/%s",
+ applicant_id);
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_VERBOSE,
+ 0));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_MAXREDIRS,
+ 1L));
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ wh->url));
+ wh->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
+ eh,
+ pd->slist,
+ &handle_webhook_finished,
+ wh);
+ return wh;
+}
+
+
+/**
+ * Initialize kycaid logic plugin
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
+ */
+void *
+libtaler_plugin_kyclogic_kycaid_init (void *cls)
+{
+ const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct TALER_KYCLOGIC_Plugin *plugin;
+ struct PluginState *ps;
+
+ ps = GNUNET_new (struct PluginState);
+ ps->cfg = cfg;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "BASE_URL",
+ &ps->exchange_base_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ GNUNET_free (ps);
+ return NULL;
+ }
+
+ ps->curl_ctx
+ = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &ps->curl_rc);
+ if (NULL == ps->curl_ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_free (ps->exchange_base_url);
+ GNUNET_free (ps);
+ return NULL;
+ }
+ ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
+
+ plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
+ plugin->cls = ps;
+ plugin->load_configuration
+ = &kycaid_load_configuration;
+ plugin->unload_configuration
+ = &kycaid_unload_configuration;
+ plugin->initiate
+ = &kycaid_initiate;
+ plugin->initiate_cancel
+ = &kycaid_initiate_cancel;
+ plugin->proof
+ = &kycaid_proof;
+ plugin->proof_cancel
+ = &kycaid_proof_cancel;
+ plugin->webhook
+ = &kycaid_webhook;
+ plugin->webhook_cancel
+ = &kycaid_webhook_cancel;
+ return plugin;
+}
+
+
+/**
+ * Unload authorization plugin
+ *
+ * @param cls a `struct TALER_KYCLOGIC_Plugin`
+ * @return NULL (always)
+ */
+void *
+libtaler_plugin_kyclogic_kycaid_done (void *cls)
+{
+ struct TALER_KYCLOGIC_Plugin *plugin = cls;
+ struct PluginState *ps = plugin->cls;
+
+ if (NULL != ps->curl_ctx)
+ {
+ GNUNET_CURL_fini (ps->curl_ctx);
+ ps->curl_ctx = NULL;
+ }
+ if (NULL != ps->curl_rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
+ ps->curl_rc = NULL;
+ }
+ GNUNET_free (ps->exchange_base_url);
+ GNUNET_free (ps);
+ GNUNET_free (plugin);
+ return NULL;
+}
diff --git a/src/kyclogic/plugin_kyclogic_oauth2.c b/src/kyclogic/plugin_kyclogic_oauth2.c
new file mode 100644
index 000000000..3a1f50bcf
--- /dev/null
+++ b/src/kyclogic/plugin_kyclogic_oauth2.c
@@ -0,0 +1,1780 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2022-2024 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.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file plugin_kyclogic_oauth2.c
+ * @brief oauth2.0 based authentication flow logic
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_kyclogic_plugin.h"
+#include "taler_mhd_lib.h"
+#include "taler_templating_lib.h"
+#include "taler_json_lib.h"
+#include <regex.h>
+#include "taler_util.h"
+
+
+/**
+ * Saves the state of a plugin.
+ */
+struct PluginState
+{
+
+ /**
+ * Our global configuration.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Our base URL.
+ */
+ char *exchange_base_url;
+
+ /**
+ * Context for CURL operations (useful to the event loop)
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ /**
+ * Context for integrating @e curl_ctx with the
+ * GNUnet event loop.
+ */
+ struct GNUNET_CURL_RescheduleContext *curl_rc;
+
+};
+
+
+/**
+ * Keeps the plugin-specific state for
+ * a given configuration section.
+ */
+struct TALER_KYCLOGIC_ProviderDetails
+{
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+
+ /**
+ * Configuration section that configured us.
+ */
+ char *section;
+
+ /**
+ * URL of the Challenger ``/setup`` endpoint for
+ * approving address validations. NULL if not used.
+ */
+ char *setup_url;
+
+ /**
+ * URL of the OAuth2.0 endpoint for KYC checks.
+ */
+ char *authorize_url;
+
+ /**
+ * URL of the OAuth2.0 endpoint for KYC checks.
+ * (token/auth)
+ */
+ char *token_url;
+
+ /**
+ * URL of the user info access endpoint.
+ */
+ char *info_url;
+
+ /**
+ * Our client ID for OAuth2.0.
+ */
+ char *client_id;
+
+ /**
+ * Our client secret for OAuth2.0.
+ */
+ char *client_secret;
+
+ /**
+ * Where to redirect clients after the
+ * Web-based KYC process is done?
+ */
+ char *post_kyc_redirect_url;
+
+ /**
+ * Name of the program we use to convert outputs
+ * from Persona into our JSON inputs.
+ */
+ char *conversion_binary;
+
+ /**
+ * Validity time for a successful KYC process.
+ */
+ struct GNUNET_TIME_Relative validity;
+
+ /**
+ * Set to true if we are operating in DEBUG
+ * mode and may return private details in HTML
+ * responses to make diagnostics easier.
+ */
+ bool debug_mode;
+};
+
+
+/**
+ * Handle for an initiation operation.
+ */
+struct TALER_KYCLOGIC_InitiateHandle
+{
+
+ /**
+ * Hash of the payto:// URI we are initiating
+ * the KYC for.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * UUID being checked.
+ */
+ uint64_t legitimization_uuid;
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * The task for asynchronous response generation.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Handle for the OAuth 2.0 setup request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Continuation to call.
+ */
+ TALER_KYCLOGIC_InitiateCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Handle for an KYC proof operation.
+ */
+struct TALER_KYCLOGIC_ProofHandle
+{
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * HTTP connection we are processing.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Handle to an external process that converts the
+ * Persona response to our internal format.
+ */
+ struct TALER_JSON_ExternalConversion *ec;
+
+ /**
+ * Hash of the payto URI that this is about.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Continuation to call.
+ */
+ TALER_KYCLOGIC_ProofCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Curl request we are running to the OAuth 2.0 service.
+ */
+ CURL *eh;
+
+ /**
+ * Body for the @e eh POST request.
+ */
+ char *post_body;
+
+ /**
+ * KYC attributes returned about the user by the OAuth 2.0 server.
+ */
+ json_t *attributes;
+
+ /**
+ * Response to return.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * The task for asynchronous response generation.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Handle for the OAuth 2.0 CURL request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * User ID to return, the 'id' from OAuth.
+ */
+ char *provider_user_id;
+
+ /**
+ * Legitimization ID to return, the 64-bit row ID
+ * as a string.
+ */
+ char provider_legitimization_id[32];
+
+ /**
+ * KYC status to return.
+ */
+ enum TALER_KYCLOGIC_KycStatus status;
+
+ /**
+ * HTTP status to return.
+ */
+ unsigned int http_status;
+
+
+};
+
+
+/**
+ * Handle for an KYC Web hook operation.
+ */
+struct TALER_KYCLOGIC_WebhookHandle
+{
+
+ /**
+ * Continuation to call when done.
+ */
+ TALER_KYCLOGIC_WebhookCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Task for asynchronous execution.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+};
+
+
+/**
+ * Release configuration resources previously loaded
+ *
+ * @param[in] pd configuration to release
+ */
+static void
+oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
+{
+ GNUNET_free (pd->section);
+ GNUNET_free (pd->token_url);
+ GNUNET_free (pd->setup_url);
+ GNUNET_free (pd->authorize_url);
+ GNUNET_free (pd->info_url);
+ GNUNET_free (pd->client_id);
+ GNUNET_free (pd->client_secret);
+ GNUNET_free (pd->post_kyc_redirect_url);
+ GNUNET_free (pd->conversion_binary);
+ GNUNET_free (pd);
+}
+
+
+/**
+ * Load the configuration of the KYC provider.
+ *
+ * @param cls closure
+ * @param provider_section_name configuration section to parse
+ * @return NULL if configuration is invalid
+ */
+static struct TALER_KYCLOGIC_ProviderDetails *
+oauth2_load_configuration (void *cls,
+ const char *provider_section_name)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+ char *s;
+
+ pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
+ pd->ps = ps;
+ pd->section = GNUNET_strdup (provider_section_name);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (ps->cfg,
+ provider_section_name,
+ "KYC_OAUTH2_VALIDITY",
+ &pd->validity))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_OAUTH2_VALIDITY");
+ oauth2_unload_configuration (pd);
+ return NULL;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_OAUTH2_CLIENT_ID",
+ &s))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_OAUTH2_CLIENT_ID");
+ oauth2_unload_configuration (pd);
+ return NULL;
+ }
+ pd->client_id = s;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_OAUTH2_TOKEN_URL",
+ &s))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_OAUTH2_TOKEN_URL");
+ oauth2_unload_configuration (pd);
+ return NULL;
+ }
+ if ( (! TALER_url_valid_charset (s)) ||
+ ( (0 != strncasecmp (s,
+ "http://",
+ strlen ("http://"))) &&
+ (0 != strncasecmp (s,
+ "https://",
+ strlen ("https://"))) ) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_OAUTH2_TOKEN_URL",
+ "not a valid URL");
+ GNUNET_free (s);
+ oauth2_unload_configuration (pd);
+ return NULL;
+ }
+ pd->token_url = s;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_OAUTH2_AUTHORIZE_URL",
+ &s))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_OAUTH2_AUTHORIZE_URL");
+ oauth2_unload_configuration (pd);
+ return NULL;
+ }
+ if ( (! TALER_url_valid_charset (s)) ||
+ ( (0 != strncasecmp (s,
+ "http://",
+ strlen ("http://"))) &&
+ (0 != strncasecmp (s,
+ "https://",
+ strlen ("https://"))) ) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_OAUTH2_AUTHORIZE_URL",
+ "not a valid URL");
+ oauth2_unload_configuration (pd);
+ GNUNET_free (s);
+ return NULL;
+ }
+ if (NULL != strchr (s, '#'))
+ {
+ const char *extra = strchr (s, '#');
+ const char *slash = strrchr (s, '/');
+
+ if ( (0 != strcmp (extra,
+ "#setup")) ||
+ (NULL == slash) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_OAUTH2_AUTHORIZE_URL",
+ "not a valid authorze URL (bad fragment)");
+ oauth2_unload_configuration (pd);
+ GNUNET_free (s);
+ return NULL;
+ }
+ pd->authorize_url = GNUNET_strndup (s,
+ extra - s);
+ GNUNET_asprintf (&pd->setup_url,
+ "%.*s/setup/%s",
+ (int) (slash - s),
+ s,
+ pd->client_id);
+ GNUNET_free (s);
+ }
+ else
+ {
+ pd->authorize_url = s;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_OAUTH2_INFO_URL",
+ &s))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_OAUTH2_INFO_URL");
+ oauth2_unload_configuration (pd);
+ return NULL;
+ }
+ if ( (! TALER_url_valid_charset (s)) ||
+ ( (0 != strncasecmp (s,
+ "http://",
+ strlen ("http://"))) &&
+ (0 != strncasecmp (s,
+ "https://",
+ strlen ("https://"))) ) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_INFO_URL",
+ "not a valid URL");
+ GNUNET_free (s);
+ oauth2_unload_configuration (pd);
+ return NULL;
+ }
+ pd->info_url = s;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_OAUTH2_CLIENT_SECRET",
+ &s))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_OAUTH2_CLIENT_SECRET");
+ oauth2_unload_configuration (pd);
+ return NULL;
+ }
+ pd->client_secret = s;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_OAUTH2_POST_URL",
+ &s))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_OAUTH2_POST_URL");
+ oauth2_unload_configuration (pd);
+ return NULL;
+ }
+ pd->post_kyc_redirect_url = s;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_OAUTH2_CONVERTER_HELPER",
+ &pd->conversion_binary))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_OAUTH2_CONVERTER_HELPER");
+ oauth2_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK ==
+ GNUNET_CONFIGURATION_get_value_yesno (ps->cfg,
+ provider_section_name,
+ "KYC_OAUTH2_DEBUG_MODE"))
+ pd->debug_mode = true;
+
+ return pd;
+}
+
+
+/**
+ * Logic to asynchronously return the response for
+ * how to begin the OAuth2.0 checking process to
+ * the client.
+ *
+ * @param ih process to redirect for
+ * @param authorize_url authorization URL to use
+ */
+static void
+initiate_with_url (struct TALER_KYCLOGIC_InitiateHandle *ih,
+ const char *authorize_url)
+{
+
+ const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
+ struct PluginState *ps = pd->ps;
+ char *hps;
+ char *url;
+ char legi_s[42];
+
+ GNUNET_snprintf (legi_s,
+ sizeof (legi_s),
+ "%llu",
+ (unsigned long long) ih->legitimization_uuid);
+ hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
+ sizeof (ih->h_payto));
+ {
+ char *redirect_uri_encoded;
+
+ {
+ char *redirect_uri;
+
+ GNUNET_asprintf (&redirect_uri,
+ "%skyc-proof/%s",
+ ps->exchange_base_url,
+ pd->section);
+ redirect_uri_encoded = TALER_urlencode (redirect_uri);
+ GNUNET_free (redirect_uri);
+ }
+ GNUNET_asprintf (&url,
+ "%s?response_type=code&client_id=%s&redirect_uri=%s&state=%s",
+ authorize_url,
+ pd->client_id,
+ redirect_uri_encoded,
+ hps);
+ GNUNET_free (redirect_uri_encoded);
+ }
+ ih->cb (ih->cb_cls,
+ TALER_EC_NONE,
+ url,
+ NULL /* unknown user_id here */,
+ legi_s,
+ NULL /* no error */);
+ GNUNET_free (url);
+ GNUNET_free (hps);
+ GNUNET_free (ih);
+}
+
+
+/**
+ * After we are done with the CURL interaction we
+ * need to update our database state with the information
+ * retrieved.
+ *
+ * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
+ * @param response_code HTTP response code from server, 0 on hard error
+ * @param response in JSON, NULL if response was not in JSON format
+ */
+static void
+handle_curl_setup_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
+ const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
+ const json_t *j = response;
+
+ ih->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "/setup URL failed to return HTTP response\n");
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
+ NULL,
+ NULL,
+ NULL,
+ "/setup request to OAuth 2.0 backend returned no response");
+ GNUNET_free (ih);
+ return;
+ case MHD_HTTP_OK:
+ {
+ const char *nonce;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("nonce",
+ &nonce),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ const char *emsg;
+ unsigned int line;
+ char *url;
+
+ res = GNUNET_JSON_parse (j,
+ spec,
+ &emsg,
+ &line);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
+ NULL,
+ NULL,
+ NULL,
+ "Unexpected response from KYC gateway: setup must return a nonce");
+ GNUNET_free (ih);
+ return;
+ }
+ GNUNET_asprintf (&url,
+ "%s/%s",
+ pd->authorize_url,
+ nonce);
+ initiate_with_url (ih,
+ url);
+ GNUNET_free (url);
+ return;
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "/setup URL returned HTTP status %u\n",
+ (unsigned int) response_code);
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
+ NULL,
+ NULL,
+ NULL,
+ "/setup request to OAuth 2.0 backend returned unexpected HTTP status code");
+ GNUNET_free (ih);
+ return;
+ }
+}
+
+
+/**
+ * Logic to asynchronously return the response for how to begin the OAuth2.0
+ * checking process to the client. May first request a dynamic URL via
+ * ``/setup`` if configured to use a client-authenticated setup process.
+ *
+ * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
+ */
+static void
+initiate_task (void *cls)
+{
+ struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
+ const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
+ struct PluginState *ps = pd->ps;
+ char *hdr;
+ struct curl_slist *slist;
+ CURL *eh;
+
+ ih->task = NULL;
+ if (NULL == pd->setup_url)
+ {
+ initiate_with_url (ih,
+ pd->authorize_url);
+ return;
+ }
+ eh = curl_easy_init ();
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ ih->cb (ih->cb_cls,
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ NULL,
+ NULL,
+ NULL,
+ "curl_easy_init() failed");
+ GNUNET_free (ih);
+ return;
+ }
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ pd->setup_url));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POST,
+ 1));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ ""));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_FOLLOWLOCATION,
+ 1L));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_MAXREDIRS,
+ 5L));
+ GNUNET_asprintf (&hdr,
+ "%s: Bearer %s",
+ MHD_HTTP_HEADER_AUTHORIZATION,
+ pd->client_secret);
+ slist = curl_slist_append (NULL,
+ hdr);
+ ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
+ eh,
+ slist,
+ &handle_curl_setup_finished,
+ ih);
+ curl_slist_free_all (slist);
+ GNUNET_free (hdr);
+}
+
+
+/**
+ * Initiate KYC check.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param account_id which account to trigger process for
+ * @param legitimization_uuid unique ID for the legitimization process
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_InitiateHandle *
+oauth2_initiate (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ const struct TALER_PaytoHashP *account_id,
+ uint64_t legitimization_uuid,
+ TALER_KYCLOGIC_InitiateCallback cb,
+ void *cb_cls)
+{
+ struct TALER_KYCLOGIC_InitiateHandle *ih;
+
+ (void) cls;
+ ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
+ ih->legitimization_uuid = legitimization_uuid;
+ ih->cb = cb;
+ ih->cb_cls = cb_cls;
+ ih->h_payto = *account_id;
+ ih->pd = pd;
+ ih->task = GNUNET_SCHEDULER_add_now (&initiate_task,
+ ih);
+ return ih;
+}
+
+
+/**
+ * Cancel KYC check initiation.
+ *
+ * @param[in] ih handle of operation to cancel
+ */
+static void
+oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
+{
+ if (NULL != ih->task)
+ {
+ GNUNET_SCHEDULER_cancel (ih->task);
+ ih->task = NULL;
+ }
+ if (NULL != ih->job)
+ {
+ GNUNET_CURL_job_cancel (ih->job);
+ ih->job = NULL;
+ }
+ GNUNET_free (ih);
+}
+
+
+/**
+ * Cancel KYC proof.
+ *
+ * @param[in] ph handle of operation to cancel
+ */
+static void
+oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
+{
+ if (NULL != ph->ec)
+ {
+ TALER_JSON_external_conversion_stop (ph->ec);
+ ph->ec = NULL;
+ }
+ if (NULL != ph->task)
+ {
+ GNUNET_SCHEDULER_cancel (ph->task);
+ ph->task = NULL;
+ }
+ if (NULL != ph->job)
+ {
+ GNUNET_CURL_job_cancel (ph->job);
+ ph->job = NULL;
+ }
+ if (NULL != ph->response)
+ {
+ MHD_destroy_response (ph->response);
+ ph->response = NULL;
+ }
+ GNUNET_free (ph->provider_user_id);
+ if (NULL != ph->attributes)
+ json_decref (ph->attributes);
+ GNUNET_free (ph->post_body);
+ GNUNET_free (ph);
+}
+
+
+/**
+ * Function called to asynchronously return the final
+ * result to the callback.
+ *
+ * @param cls a `struct TALER_KYCLOGIC_ProofHandle`
+ */
+static void
+return_proof_response (void *cls)
+{
+ struct TALER_KYCLOGIC_ProofHandle *ph = cls;
+
+ ph->task = NULL;
+ ph->cb (ph->cb_cls,
+ ph->status,
+ ph->provider_user_id,
+ ph->provider_legitimization_id,
+ GNUNET_TIME_relative_to_absolute (ph->pd->validity),
+ ph->attributes,
+ ph->http_status,
+ ph->response);
+ ph->response = NULL; /*Ownership passed to 'ph->cb'!*/
+ oauth2_proof_cancel (ph);
+}
+
+
+/**
+ * The request for @a ph failed. We may have gotten a useful error
+ * message in @a j. Generate a failure response.
+ *
+ * @param[in,out] ph request that failed
+ * @param j reply from the server (or NULL)
+ */
+static void
+handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph,
+ const json_t *j)
+{
+ enum GNUNET_GenericReturnValue res;
+
+ {
+ const char *msg;
+ const char *desc;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("error",
+ &msg),
+ GNUNET_JSON_spec_string ("error_description",
+ &desc),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *emsg;
+ unsigned int line;
+
+ res = GNUNET_JSON_parse (j,
+ spec,
+ &emsg,
+ &line);
+ }
+
+ if (GNUNET_OK != res)
+ {
+ json_t *body;
+
+ GNUNET_break_op (0);
+ ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
+ ph->http_status
+ = MHD_HTTP_BAD_GATEWAY;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("server_response",
+ (json_t *) j)),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_assert (NULL != body);
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-authorization-failure-malformed",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ return;
+ }
+ ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED;
+ ph->http_status = MHD_HTTP_FORBIDDEN;
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-authorization-failure",
+ NULL,
+ NULL,
+ j,
+ &ph->response));
+}
+
+
+/**
+ * Type of a callback that receives a JSON @a result.
+ *
+ * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *`
+ * @param status_type how did the process die
+ * @param code termination status code from the process
+ * @param attr result some JSON result, NULL if we failed to get an JSON output
+ */
+static void
+converted_proof_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType status_type,
+ unsigned long code,
+ const json_t *attr)
+{
+ struct TALER_KYCLOGIC_ProofHandle *ph = cls;
+ const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
+
+ ph->ec = NULL;
+ if ( (NULL == attr) ||
+ (0 != code) )
+ {
+ json_t *body;
+ char *msg;
+
+ GNUNET_break_op (0);
+ ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
+ ph->http_status = MHD_HTTP_BAD_GATEWAY;
+ if (0 != code)
+ GNUNET_asprintf (&msg,
+ "Attribute converter exited with status %ld",
+ code);
+ else
+ msg = GNUNET_strdup (
+ "Attribute converter response was not in JSON format");
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("converter",
+ pd->conversion_binary),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("attributes",
+ (json_t *) attr)),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ GNUNET_JSON_pack_string ("message",
+ msg),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_free (msg);
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-conversion-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
+ ph);
+ return;
+ }
+
+ {
+ const char *id;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("id",
+ &id),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ const char *emsg;
+ unsigned int line;
+
+ res = GNUNET_JSON_parse (attr,
+ ispec,
+ &emsg,
+ &line);
+ if (GNUNET_OK != res)
+ {
+ json_t *body;
+
+ GNUNET_break_op (0);
+ ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
+ ph->http_status = MHD_HTTP_BAD_GATEWAY;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("converter",
+ pd->conversion_binary),
+ GNUNET_JSON_pack_string ("message",
+ "Unexpected response from KYC attribute converter: returned JSON data must contain 'id' field"),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ GNUNET_JSON_pack_object_incref ("attributes",
+ (json_t *) attr),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-conversion-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
+ ph);
+ return;
+ }
+ ph->provider_user_id = GNUNET_strdup (id);
+ }
+ ph->status = TALER_KYCLOGIC_STATUS_SUCCESS;
+ ph->response = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ GNUNET_assert (NULL != ph->response);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (
+ ph->response,
+ MHD_HTTP_HEADER_LOCATION,
+ ph->pd->post_kyc_redirect_url));
+ ph->http_status = MHD_HTTP_SEE_OTHER;
+ ph->attributes = json_incref ((json_t *) attr);
+ ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
+ ph);
+}
+
+
+/**
+ * The request for @a ph succeeded (presumably).
+ * Call continuation with the result.
+ *
+ * @param[in,out] ph request that succeeded
+ * @param j reply from the server
+ */
+static void
+parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
+ const json_t *j)
+{
+ const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Calling converter `%s' with JSON\n",
+ pd->conversion_binary);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ ph->ec = TALER_JSON_external_conversion_start (
+ j,
+ &converted_proof_cb,
+ ph,
+ pd->conversion_binary,
+ pd->conversion_binary,
+ NULL);
+ if (NULL != ph->ec)
+ return;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start KYCAID conversion helper `%s'\n",
+ pd->conversion_binary);
+ ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
+ ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ {
+ json_t *body;
+
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("converter",
+ pd->conversion_binary),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ GNUNET_JSON_pack_string ("message",
+ "Failed to launch KYC conversion helper process."),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-conversion-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ }
+ ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
+ ph);
+}
+
+
+/**
+ * After we are done with the CURL interaction we
+ * need to update our database state with the information
+ * retrieved.
+ *
+ * @param cls our `struct TALER_KYCLOGIC_ProofHandle`
+ * @param response_code HTTP response code from server, 0 on hard error
+ * @param response in JSON, NULL if response was not in JSON format
+ */
+static void
+handle_curl_proof_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_KYCLOGIC_ProofHandle *ph = cls;
+ const json_t *j = response;
+
+ ph->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ {
+ json_t *body;
+
+ ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
+ ph->http_status = MHD_HTTP_BAD_GATEWAY;
+
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("message",
+ "No response from KYC gateway"),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-provider-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ }
+ break;
+ case MHD_HTTP_OK:
+ parse_proof_success_reply (ph,
+ j);
+ return;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "OAuth2.0 info URL returned HTTP status %u\n",
+ (unsigned int) response_code);
+ handle_proof_error (ph,
+ j);
+ break;
+ }
+ ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
+ ph);
+}
+
+
+/**
+ * After we are done with the CURL interaction we
+ * need to fetch the user's account details.
+ *
+ * @param cls our `struct KycProofContext`
+ * @param response_code HTTP response code from server, 0 on hard error
+ * @param response in JSON, NULL if response was not in JSON format
+ */
+static void
+handle_curl_login_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_KYCLOGIC_ProofHandle *ph = cls;
+ const json_t *j = response;
+
+ ph->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const char *access_token;
+ const char *token_type;
+ uint64_t expires_in_s;
+ const char *refresh_token;
+ bool no_expires;
+ bool no_refresh;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("access_token",
+ &access_token),
+ GNUNET_JSON_spec_string ("token_type",
+ &token_type),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("expires_in",
+ &expires_in_s),
+ &no_expires),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("refresh_token",
+ &refresh_token),
+ &no_refresh),
+ GNUNET_JSON_spec_end ()
+ };
+ CURL *eh;
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ const char *emsg;
+ unsigned int line;
+
+ res = GNUNET_JSON_parse (j,
+ spec,
+ &emsg,
+ &line);
+ if (GNUNET_OK != res)
+ {
+ json_t *body;
+
+ GNUNET_break_op (0);
+ ph->http_status
+ = MHD_HTTP_BAD_GATEWAY;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_incref ("server_response",
+ (json_t *) j),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ GNUNET_JSON_pack_string ("message",
+ "Unexpected response from KYC gateway: required fields missing or malformed"),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-provider-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ break;
+ }
+ }
+ if (0 != strcasecmp (token_type,
+ "bearer"))
+ {
+ json_t *body;
+
+ GNUNET_break_op (0);
+ ph->http_status = MHD_HTTP_BAD_GATEWAY;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_incref ("server_response",
+ (json_t *) j),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ GNUNET_JSON_pack_string ("message",
+ "Unexpected 'token_type' in response from KYC gateway: 'bearer' token required"),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-provider-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ break;
+ }
+
+ /* We guard against a few characters that could
+ conceivably be abused to mess with the HTTP header */
+ if ( (NULL != strchr (access_token,
+ '\n')) ||
+ (NULL != strchr (access_token,
+ '\r')) ||
+ (NULL != strchr (access_token,
+ ' ')) ||
+ (NULL != strchr (access_token,
+ ';')) )
+ {
+ json_t *body;
+
+ GNUNET_break_op (0);
+ ph->http_status = MHD_HTTP_BAD_GATEWAY;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_incref ("server_response",
+ (json_t *) j),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ GNUNET_JSON_pack_string ("message",
+ "Illegal character in access token"),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-provider-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ break;
+ }
+
+ eh = curl_easy_init ();
+ GNUNET_assert (NULL != eh);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ ph->pd->info_url));
+ {
+ char *hdr;
+ struct curl_slist *slist;
+
+ GNUNET_asprintf (&hdr,
+ "%s: Bearer %s",
+ MHD_HTTP_HEADER_AUTHORIZATION,
+ access_token);
+ slist = curl_slist_append (NULL,
+ hdr);
+ ph->job = GNUNET_CURL_job_add2 (ph->pd->ps->curl_ctx,
+ eh,
+ slist,
+ &handle_curl_proof_finished,
+ ph);
+ curl_slist_free_all (slist);
+ GNUNET_free (hdr);
+ }
+ return;
+ }
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "OAuth2.0 login URL returned HTTP status %u\n",
+ (unsigned int) response_code);
+ handle_proof_error (ph,
+ j);
+ break;
+ }
+ return_proof_response (ph);
+}
+
+
+/**
+ * Check KYC status and return status to human.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param connection MHD connection object (for HTTP headers)
+ * @param account_id which account to trigger process for
+ * @param process_row row in the legitimization processes table the legitimization is for
+ * @param provider_user_id user ID (or NULL) the proof is for
+ * @param provider_legitimization_id legitimization ID the proof is for
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_ProofHandle *
+oauth2_proof (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ struct MHD_Connection *connection,
+ const struct TALER_PaytoHashP *account_id,
+ uint64_t process_row,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ TALER_KYCLOGIC_ProofCallback cb,
+ void *cb_cls)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_ProofHandle *ph;
+ const char *code;
+
+ GNUNET_break (NULL == provider_user_id);
+ ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
+ GNUNET_snprintf (ph->provider_legitimization_id,
+ sizeof (ph->provider_legitimization_id),
+ "%llu",
+ (unsigned long long) process_row);
+ if ( (NULL != provider_legitimization_id) &&
+ (0 != strcmp (provider_legitimization_id,
+ ph->provider_legitimization_id)))
+ {
+ GNUNET_break (0);
+ GNUNET_free (ph);
+ return NULL;
+ }
+
+ ph->pd = pd;
+ ph->connection = connection;
+ ph->h_payto = *account_id;
+ ph->cb = cb;
+ ph->cb_cls = cb_cls;
+ code = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "code");
+ if (NULL == code)
+ {
+ const char *err;
+ const char *desc;
+ const char *euri;
+ json_t *body;
+
+ err = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "error");
+ if (NULL == err)
+ {
+ GNUNET_break_op (0);
+ ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING;
+ ph->http_status = MHD_HTTP_BAD_REQUEST;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("message",
+ "'code' parameter malformed"),
+ TALER_JSON_pack_ec (
+ TALER_EC_GENERIC_PARAMETER_MALFORMED));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-bad-request",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
+ ph);
+ return ph;
+ }
+ desc = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "error_description");
+ euri = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "error_uri");
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "OAuth2 process %llu failed with error `%s'\n",
+ (unsigned long long) process_row,
+ err);
+ if (0 == strcmp (err,
+ "server_error"))
+ ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
+ else if (0 == strcmp (err,
+ "unauthorized_client"))
+ ph->status = TALER_KYCLOGIC_STATUS_FAILED;
+ else if (0 == strcmp (err,
+ "temporarily_unavailable"))
+ ph->status = TALER_KYCLOGIC_STATUS_PENDING;
+ else
+ ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
+ ph->http_status = MHD_HTTP_FORBIDDEN;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("error",
+ err),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("error_details",
+ desc)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("error_uri",
+ euri)));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-authentication-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
+ ph);
+ return ph;
+
+ }
+
+ ph->eh = curl_easy_init ();
+ GNUNET_assert (NULL != ph->eh);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requesting OAuth 2.0 data via HTTP POST `%s'\n",
+ pd->token_url);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (ph->eh,
+ CURLOPT_URL,
+ pd->token_url));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (ph->eh,
+ CURLOPT_VERBOSE,
+ 1));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (ph->eh,
+ CURLOPT_POST,
+ 1));
+ {
+ char *client_id;
+ char *client_secret;
+ char *authorization_code;
+ char *redirect_uri_encoded;
+ char *hps;
+
+ hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto,
+ sizeof (ph->h_payto));
+ {
+ char *redirect_uri;
+
+ GNUNET_asprintf (&redirect_uri,
+ "%skyc-proof/%s",
+ ps->exchange_base_url,
+ pd->section);
+ redirect_uri_encoded = TALER_urlencode (redirect_uri);
+ GNUNET_free (redirect_uri);
+ }
+ GNUNET_assert (NULL != redirect_uri_encoded);
+ client_id = curl_easy_escape (ph->eh,
+ pd->client_id,
+ 0);
+ GNUNET_assert (NULL != client_id);
+ client_secret = curl_easy_escape (ph->eh,
+ pd->client_secret,
+ 0);
+ GNUNET_assert (NULL != client_secret);
+ authorization_code = curl_easy_escape (ph->eh,
+ code,
+ 0);
+ GNUNET_assert (NULL != authorization_code);
+ GNUNET_asprintf (&ph->post_body,
+ "client_id=%s&redirect_uri=%s&state=%s&client_secret=%s&code=%s&grant_type=authorization_code",
+ client_id,
+ redirect_uri_encoded,
+ hps,
+ client_secret,
+ authorization_code);
+ curl_free (authorization_code);
+ curl_free (client_secret);
+ GNUNET_free (redirect_uri_encoded);
+ GNUNET_free (hps);
+ curl_free (client_id);
+ }
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (ph->eh,
+ CURLOPT_POSTFIELDS,
+ ph->post_body));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (ph->eh,
+ CURLOPT_FOLLOWLOCATION,
+ 1L));
+ /* limit MAXREDIRS to 5 as a simple security measure against
+ a potential infinite loop caused by a malicious target */
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (ph->eh,
+ CURLOPT_MAXREDIRS,
+ 5L));
+
+ ph->job = GNUNET_CURL_job_add (ps->curl_ctx,
+ ph->eh,
+ &handle_curl_login_finished,
+ ph);
+ return ph;
+}
+
+
+/**
+ * Function to asynchronously return the 404 not found
+ * page for the webhook.
+ *
+ * @param cls the `struct TALER_KYCLOGIC_WebhookHandle *`
+ */
+static void
+wh_return_not_found (void *cls)
+{
+ struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
+ struct MHD_Response *response;
+
+ wh->task = NULL;
+ response = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ wh->cb (wh->cb_cls,
+ 0LLU,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ TALER_KYCLOGIC_STATUS_KEEP,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ NULL,
+ MHD_HTTP_NOT_FOUND,
+ response);
+ GNUNET_free (wh);
+}
+
+
+/**
+ * Check KYC status and return result for Webhook.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param plc callback to lookup accounts with
+ * @param plc_cls closure for @a plc
+ * @param http_method HTTP method used for the webhook
+ * @param url_path rest of the URL after `/kyc-webhook/$LOGIC/`, as NULL-terminated array
+ * @param connection MHD connection object (for HTTP headers)
+ * @param body HTTP request body, or NULL if not available
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_WebhookHandle *
+oauth2_webhook (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ TALER_KYCLOGIC_ProviderLookupCallback plc,
+ void *plc_cls,
+ const char *http_method,
+ const char *const url_path[],
+ struct MHD_Connection *connection,
+ const json_t *body,
+ TALER_KYCLOGIC_WebhookCallback cb,
+ void *cb_cls)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_WebhookHandle *wh;
+
+ (void) pd;
+ (void) plc;
+ (void) plc_cls;
+ (void) http_method;
+ (void) url_path;
+ (void) connection;
+ (void) body;
+ wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ps = ps;
+ wh->task = GNUNET_SCHEDULER_add_now (&wh_return_not_found,
+ wh);
+ return wh;
+}
+
+
+/**
+ * Cancel KYC webhook execution.
+ *
+ * @param[in] wh handle of operation to cancel
+ */
+static void
+oauth2_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
+{
+ GNUNET_SCHEDULER_cancel (wh->task);
+ GNUNET_free (wh);
+}
+
+
+/**
+ * Initialize OAuth2.0 KYC logic plugin
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
+ */
+void *
+libtaler_plugin_kyclogic_oauth2_init (void *cls)
+{
+ const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct TALER_KYCLOGIC_Plugin *plugin;
+ struct PluginState *ps;
+
+ ps = GNUNET_new (struct PluginState);
+ ps->cfg = cfg;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "BASE_URL",
+ &ps->exchange_base_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ GNUNET_free (ps);
+ return NULL;
+ }
+ ps->curl_ctx
+ = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &ps->curl_rc);
+ if (NULL == ps->curl_ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_free (ps->exchange_base_url);
+ GNUNET_free (ps);
+ return NULL;
+ }
+ ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
+
+ plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
+ plugin->cls = ps;
+ plugin->load_configuration
+ = &oauth2_load_configuration;
+ plugin->unload_configuration
+ = &oauth2_unload_configuration;
+ plugin->initiate
+ = &oauth2_initiate;
+ plugin->initiate_cancel
+ = &oauth2_initiate_cancel;
+ plugin->proof
+ = &oauth2_proof;
+ plugin->proof_cancel
+ = &oauth2_proof_cancel;
+ plugin->webhook
+ = &oauth2_webhook;
+ plugin->webhook_cancel
+ = &oauth2_webhook_cancel;
+ return plugin;
+}
+
+
+/**
+ * Unload authorization plugin
+ *
+ * @param cls a `struct TALER_KYCLOGIC_Plugin`
+ * @return NULL (always)
+ */
+void *
+libtaler_plugin_kyclogic_oauth2_done (void *cls)
+{
+ struct TALER_KYCLOGIC_Plugin *plugin = cls;
+ struct PluginState *ps = plugin->cls;
+
+ if (NULL != ps->curl_ctx)
+ {
+ GNUNET_CURL_fini (ps->curl_ctx);
+ ps->curl_ctx = NULL;
+ }
+ if (NULL != ps->curl_rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
+ ps->curl_rc = NULL;
+ }
+ GNUNET_free (ps->exchange_base_url);
+ GNUNET_free (ps);
+ GNUNET_free (plugin);
+ return NULL;
+}
diff --git a/src/kyclogic/plugin_kyclogic_persona.c b/src/kyclogic/plugin_kyclogic_persona.c
new file mode 100644
index 000000000..c68b7f881
--- /dev/null
+++ b/src/kyclogic/plugin_kyclogic_persona.c
@@ -0,0 +1,2268 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file plugin_kyclogic_persona.c
+ * @brief persona for an authentication flow logic
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_attributes.h"
+#include "taler_kyclogic_plugin.h"
+#include "taler_mhd_lib.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_templating_lib.h"
+#include <regex.h>
+#include "taler_util.h"
+
+
+/**
+ * Which version of the persona API are we implementing?
+ */
+#define PERSONA_VERSION "2021-07-05"
+
+/**
+ * Saves the state of a plugin.
+ */
+struct PluginState
+{
+
+ /**
+ * Our base URL.
+ */
+ char *exchange_base_url;
+
+ /**
+ * Our global configuration.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Context for CURL operations (useful to the event loop)
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ /**
+ * Context for integrating @e curl_ctx with the
+ * GNUnet event loop.
+ */
+ struct GNUNET_CURL_RescheduleContext *curl_rc;
+
+ /**
+ * Authorization token to use when receiving webhooks from the Persona
+ * service. Optional. Note that webhooks are *global* and not per
+ * template.
+ */
+ char *webhook_token;
+
+
+};
+
+
+/**
+ * Keeps the plugin-specific state for
+ * a given configuration section.
+ */
+struct TALER_KYCLOGIC_ProviderDetails
+{
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+
+ /**
+ * Configuration section that configured us.
+ */
+ char *section;
+
+ /**
+ * Salt to use for idempotency.
+ */
+ char *salt;
+
+ /**
+ * Authorization token to use when talking
+ * to the service.
+ */
+ char *auth_token;
+
+ /**
+ * Template ID for the KYC check to perform.
+ */
+ char *template_id;
+
+ /**
+ * Subdomain to use.
+ */
+ char *subdomain;
+
+ /**
+ * Name of the program we use to convert outputs
+ * from Persona into our JSON inputs.
+ */
+ char *conversion_binary;
+
+ /**
+ * Where to redirect the client upon completion.
+ */
+ char *post_kyc_redirect_url;
+
+ /**
+ * Validity time for a successful KYC process.
+ */
+ struct GNUNET_TIME_Relative validity;
+
+ /**
+ * Curl-ready authentication header to use.
+ */
+ struct curl_slist *slist;
+
+};
+
+
+/**
+ * Handle for an initiation operation.
+ */
+struct TALER_KYCLOGIC_InitiateHandle
+{
+
+ /**
+ * Hash of the payto:// URI we are initiating the KYC for.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * UUID being checked.
+ */
+ uint64_t legitimization_uuid;
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Continuation to call.
+ */
+ TALER_KYCLOGIC_InitiateCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * URL of the cURL request.
+ */
+ char *url;
+
+ /**
+ * Request-specific headers to use.
+ */
+ struct curl_slist *slist;
+
+};
+
+
+/**
+ * Handle for an KYC proof operation.
+ */
+struct TALER_KYCLOGIC_ProofHandle
+{
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Continuation to call.
+ */
+ TALER_KYCLOGIC_ProofCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Task for asynchronous execution.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * URL of the cURL request.
+ */
+ char *url;
+
+ /**
+ * Handle to an external process that converts the
+ * Persona response to our internal format.
+ */
+ struct TALER_JSON_ExternalConversion *ec;
+
+ /**
+ * Hash of the payto:// URI we are checking the KYC for.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Row in the legitimization processes of the
+ * legitimization proof that is being checked.
+ */
+ uint64_t process_row;
+
+ /**
+ * Account ID at the provider.
+ */
+ char *provider_user_id;
+
+ /**
+ * Account ID from the service.
+ */
+ char *account_id;
+
+ /**
+ * Inquiry ID at the provider.
+ */
+ char *inquiry_id;
+};
+
+
+/**
+ * Handle for an KYC Web hook operation.
+ */
+struct TALER_KYCLOGIC_WebhookHandle
+{
+
+ /**
+ * Continuation to call when done.
+ */
+ TALER_KYCLOGIC_WebhookCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Task for asynchronous execution.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Verification ID from the service.
+ */
+ char *inquiry_id;
+
+ /**
+ * Account ID from the service.
+ */
+ char *account_id;
+
+ /**
+ * URL of the cURL request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Response to return asynchronously.
+ */
+ struct MHD_Response *resp;
+
+ /**
+ * ID of the template the webhook is about,
+ * according to the service.
+ */
+ const char *template_id;
+
+ /**
+ * Handle to an external process that converts the
+ * Persona response to our internal format.
+ */
+ struct TALER_JSON_ExternalConversion *ec;
+
+ /**
+ * Our account ID.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * UUID being checked.
+ */
+ uint64_t process_row;
+
+ /**
+ * HTTP response code to return asynchronously.
+ */
+ unsigned int response_code;
+};
+
+
+/**
+ * Release configuration resources previously loaded
+ *
+ * @param[in] pd configuration to release
+ */
+static void
+persona_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
+{
+ curl_slist_free_all (pd->slist);
+ GNUNET_free (pd->auth_token);
+ GNUNET_free (pd->template_id);
+ GNUNET_free (pd->subdomain);
+ GNUNET_free (pd->conversion_binary);
+ GNUNET_free (pd->salt);
+ GNUNET_free (pd->section);
+ GNUNET_free (pd->post_kyc_redirect_url);
+ GNUNET_free (pd);
+}
+
+
+/**
+ * Load the configuration of the KYC provider.
+ *
+ * @param cls closure
+ * @param provider_section_name configuration section to parse
+ * @return NULL if configuration is invalid
+ */
+static struct TALER_KYCLOGIC_ProviderDetails *
+persona_load_configuration (void *cls,
+ const char *provider_section_name)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
+ pd->ps = ps;
+ pd->section = GNUNET_strdup (provider_section_name);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (ps->cfg,
+ provider_section_name,
+ "KYC_PERSONA_VALIDITY",
+ &pd->validity))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_PERSONA_VALIDITY");
+ persona_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_PERSONA_AUTH_TOKEN",
+ &pd->auth_token))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_PERSONA_AUTH_TOKEN");
+ persona_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_PERSONA_SALT",
+ &pd->salt))
+ {
+ uint32_t salt[8];
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ salt,
+ sizeof (salt));
+ pd->salt = GNUNET_STRINGS_data_to_string_alloc (salt,
+ sizeof (salt));
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_PERSONA_SUBDOMAIN",
+ &pd->subdomain))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_PERSONA_SUBDOMAIN");
+ persona_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_PERSONA_CONVERTER_HELPER",
+ &pd->conversion_binary))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_PERSONA_CONVERTER_HELPER");
+ persona_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_PERSONA_POST_URL",
+ &pd->post_kyc_redirect_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_PERSONA_POST_URL");
+ persona_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_PERSONA_TEMPLATE_ID",
+ &pd->template_id))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_PERSONA_TEMPLATE_ID");
+ persona_unload_configuration (pd);
+ return NULL;
+ }
+ {
+ char *auth;
+
+ GNUNET_asprintf (&auth,
+ "%s: Bearer %s",
+ MHD_HTTP_HEADER_AUTHORIZATION,
+ pd->auth_token);
+ pd->slist = curl_slist_append (NULL,
+ auth);
+ GNUNET_free (auth);
+ GNUNET_asprintf (&auth,
+ "%s: %s",
+ MHD_HTTP_HEADER_ACCEPT,
+ "application/json");
+ pd->slist = curl_slist_append (pd->slist,
+ "Persona-Version: "
+ PERSONA_VERSION);
+ GNUNET_free (auth);
+ }
+ return pd;
+}
+
+
+/**
+ * Cancel KYC check initiation.
+ *
+ * @param[in] ih handle of operation to cancel
+ */
+static void
+persona_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
+{
+ if (NULL != ih->job)
+ {
+ GNUNET_CURL_job_cancel (ih->job);
+ ih->job = NULL;
+ }
+ GNUNET_free (ih->url);
+ TALER_curl_easy_post_finished (&ih->ctx);
+ curl_slist_free_all (ih->slist);
+ GNUNET_free (ih);
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST "/api/v1/inquiries" request.
+ *
+ * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_initiate_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
+ const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
+ const json_t *j = response;
+ char *url;
+ json_t *data;
+ const char *type;
+ const char *inquiry_id;
+ const char *persona_account_id;
+ const char *ename;
+ unsigned int eline;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_string ("id",
+ &inquiry_id),
+ GNUNET_JSON_spec_end ()
+ };
+
+ ih->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_CREATED:
+ /* handled below */
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ case MHD_HTTP_FORBIDDEN:
+ {
+ const char *msg;
+
+ msg = json_string_value (
+ json_object_get (
+ json_array_get (
+ json_object_get (j,
+ "errors"),
+ 0),
+ "title"));
+
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED,
+ NULL,
+ NULL,
+ NULL,
+ msg);
+ persona_initiate_cancel (ih);
+ return;
+ }
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_CONFLICT:
+ {
+ const char *msg;
+
+ msg = json_string_value (
+ json_object_get (
+ json_array_get (
+ json_object_get (j,
+ "errors"),
+ 0),
+ "title"));
+
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
+ NULL,
+ NULL,
+ NULL,
+ msg);
+ persona_initiate_cancel (ih);
+ return;
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_UNPROCESSABLE_ENTITY:
+ {
+ const char *msg;
+
+ GNUNET_break (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ msg = json_string_value (
+ json_object_get (
+ json_array_get (
+ json_object_get (j,
+ "errors"),
+ 0),
+ "title"));
+
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
+ NULL,
+ NULL,
+ NULL,
+ msg);
+ persona_initiate_cancel (ih);
+ return;
+ }
+ case MHD_HTTP_TOO_MANY_REQUESTS:
+ {
+ const char *msg;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Rate limiting requested:\n");
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ msg = json_string_value (
+ json_object_get (
+ json_array_get (
+ json_object_get (j,
+ "errors"),
+ 0),
+ "title"));
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED,
+ NULL,
+ NULL,
+ NULL,
+ msg);
+ persona_initiate_cancel (ih);
+ return;
+ }
+ default:
+ {
+ char *err;
+
+ GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_asprintf (&err,
+ "Unexpected HTTP status %u from Persona\n",
+ (unsigned int) response_code);
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
+ NULL,
+ NULL,
+ NULL,
+ err);
+ GNUNET_free (err);
+ persona_initiate_cancel (ih);
+ return;
+ }
+ }
+ data = json_object_get (j,
+ "data");
+ if (NULL == data)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ persona_initiate_cancel (ih);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (data,
+ spec,
+ &ename,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
+ NULL,
+ NULL,
+ NULL,
+ ename);
+ persona_initiate_cancel (ih);
+ return;
+ }
+ persona_account_id
+ = json_string_value (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (data,
+ "relationships"),
+ "account"),
+ "data"),
+ "id"));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting inquiry %s for Persona account %s\n",
+ inquiry_id,
+ persona_account_id);
+ GNUNET_asprintf (&url,
+ "https://%s.withpersona.com/verify"
+ "?inquiry-id=%s",
+ pd->subdomain,
+ inquiry_id);
+ ih->cb (ih->cb_cls,
+ TALER_EC_NONE,
+ url,
+ persona_account_id,
+ inquiry_id,
+ NULL);
+ GNUNET_free (url);
+ persona_initiate_cancel (ih);
+}
+
+
+/**
+ * Initiate KYC check.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param account_id which account to trigger process for
+ * @param legitimization_uuid unique ID for the legitimization process
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_InitiateHandle *
+persona_initiate (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ const struct TALER_PaytoHashP *account_id,
+ uint64_t legitimization_uuid,
+ TALER_KYCLOGIC_InitiateCallback cb,
+ void *cb_cls)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_InitiateHandle *ih;
+ json_t *body;
+ CURL *eh;
+
+ eh = curl_easy_init ();
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
+ ih->legitimization_uuid = legitimization_uuid;
+ ih->cb = cb;
+ ih->cb_cls = cb_cls;
+ ih->h_payto = *account_id;
+ ih->pd = pd;
+ GNUNET_asprintf (&ih->url,
+ "https://withpersona.com/api/v1/inquiries");
+ {
+ char *payto_s;
+ char *proof_url;
+ char ref_s[24];
+
+ GNUNET_snprintf (ref_s,
+ sizeof (ref_s),
+ "%llu",
+ (unsigned long long) ih->legitimization_uuid);
+ payto_s = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
+ sizeof (ih->h_payto));
+ GNUNET_break ('/' ==
+ pd->ps->exchange_base_url[strlen (
+ pd->ps->exchange_base_url) - 1]);
+ GNUNET_asprintf (&proof_url,
+ "%skyc-proof/%s?state=%s",
+ pd->ps->exchange_base_url,
+ pd->section,
+ payto_s);
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_steal (
+ "data",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_steal (
+ "attributes",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("inquiry_template_id",
+ pd->template_id),
+ GNUNET_JSON_pack_string ("reference_id",
+ ref_s),
+ GNUNET_JSON_pack_string ("redirect_uri",
+ proof_url)
+ )))));
+ GNUNET_assert (NULL != body);
+ GNUNET_free (payto_s);
+ GNUNET_free (proof_url);
+ }
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_VERBOSE,
+ 0));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_MAXREDIRS,
+ 1L));
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ ih->url));
+ ih->ctx.disable_compression = true;
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&ih->ctx,
+ eh,
+ body))
+ {
+ GNUNET_break (0);
+ GNUNET_free (ih->url);
+ GNUNET_free (ih);
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ return NULL;
+ }
+ json_decref (body);
+ ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
+ eh,
+ ih->ctx.headers,
+ &handle_initiate_finished,
+ ih);
+ GNUNET_CURL_extend_headers (ih->job,
+ pd->slist);
+ {
+ char *ikh;
+
+ GNUNET_asprintf (&ikh,
+ "Idempotency-Key: %llu-%s",
+ (unsigned long long) ih->legitimization_uuid,
+ pd->salt);
+ ih->slist = curl_slist_append (NULL,
+ ikh);
+ GNUNET_free (ikh);
+ }
+ GNUNET_CURL_extend_headers (ih->job,
+ ih->slist);
+ return ih;
+}
+
+
+/**
+ * Cancel KYC proof.
+ *
+ * @param[in] ph handle of operation to cancel
+ */
+static void
+persona_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
+{
+ if (NULL != ph->job)
+ {
+ GNUNET_CURL_job_cancel (ph->job);
+ ph->job = NULL;
+ }
+ if (NULL != ph->ec)
+ {
+ TALER_JSON_external_conversion_stop (ph->ec);
+ ph->ec = NULL;
+ }
+ GNUNET_free (ph->url);
+ GNUNET_free (ph->provider_user_id);
+ GNUNET_free (ph->account_id);
+ GNUNET_free (ph->inquiry_id);
+ GNUNET_free (ph);
+}
+
+
+/**
+ * Call @a ph callback with the operation result.
+ *
+ * @param ph proof handle to generate reply for
+ * @param status status to return
+ * @param account_id account to return
+ * @param inquiry_id inquiry ID to supply
+ * @param http_status HTTP status to use
+ * @param template template to instantiate
+ * @param[in] body body for the template to use (reference
+ * is consumed)
+ */
+static void
+proof_generic_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
+ enum TALER_KYCLOGIC_KycStatus status,
+ const char *account_id,
+ const char *inquiry_id,
+ unsigned int http_status,
+ const char *template,
+ json_t *body)
+{
+ struct MHD_Response *resp;
+ enum GNUNET_GenericReturnValue ret;
+
+ /* This API is not usable for successful replies */
+ GNUNET_assert (TALER_KYCLOGIC_STATUS_SUCCESS != status);
+ ret = TALER_TEMPLATING_build (ph->connection,
+ &http_status,
+ template,
+ NULL,
+ NULL,
+ body,
+ &resp);
+ json_decref (body);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_break (0);
+ resp = NULL; /* good luck */
+ }
+ ph->cb (ph->cb_cls,
+ status,
+ account_id,
+ inquiry_id,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ NULL,
+ http_status,
+ resp);
+}
+
+
+/**
+ * Call @a ph callback with HTTP error response.
+ *
+ * @param ph proof handle to generate reply for
+ * @param inquiry_id inquiry ID to supply
+ * @param http_status HTTP status to use
+ * @param template template to instantiate
+ * @param[in] body body for the template to use (reference
+ * is consumed)
+ */
+static void
+proof_reply_error (struct TALER_KYCLOGIC_ProofHandle *ph,
+ const char *inquiry_id,
+ unsigned int http_status,
+ const char *template,
+ json_t *body)
+{
+ proof_generic_reply (ph,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ NULL, /* user id */
+ inquiry_id,
+ http_status,
+ template,
+ body);
+}
+
+
+/**
+ * Return a response for the @a ph request indicating a
+ * protocol violation by the Persona server.
+ *
+ * @param[in,out] ph request we are processing
+ * @param response_code HTTP status returned by Persona
+ * @param inquiry_id ID of the inquiry this is about
+ * @param detail where the response was wrong
+ * @param data full response data to output
+ */
+static void
+return_invalid_response (struct TALER_KYCLOGIC_ProofHandle *ph,
+ unsigned int response_code,
+ const char *inquiry_id,
+ const char *detail,
+ const json_t *data)
+{
+ proof_reply_error (
+ ph,
+ inquiry_id,
+ MHD_HTTP_BAD_GATEWAY,
+ "persona-invalid-response",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ GNUNET_JSON_pack_string ("persona_inquiry_id",
+ inquiry_id),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
+ GNUNET_JSON_pack_string ("detail",
+ detail),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+}
+
+
+/**
+ * Start the external conversion helper.
+ *
+ * @param pd configuration details
+ * @param attr attributes to give to the helper
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle for the helper
+ */
+static struct TALER_JSON_ExternalConversion *
+start_conversion (const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ const json_t *attr,
+ TALER_JSON_JsonCallback cb,
+ void *cb_cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Calling converter `%s' with JSON\n",
+ pd->conversion_binary);
+ json_dumpf (attr,
+ stderr,
+ JSON_INDENT (2));
+ return TALER_JSON_external_conversion_start (
+ attr,
+ cb,
+ cb_cls,
+ pd->conversion_binary,
+ pd->conversion_binary,
+ "-a",
+ pd->auth_token,
+ NULL
+ );
+}
+
+
+/**
+ * Type of a callback that receives a JSON @a result.
+ *
+ * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *`
+ * @param status_type how did the process die
+ * @param code termination status code from the process
+ * @param attr result some JSON result, NULL if we failed to get an JSON output
+ */
+static void
+proof_post_conversion_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType status_type,
+ unsigned long code,
+ const json_t *attr)
+{
+ struct TALER_KYCLOGIC_ProofHandle *ph = cls;
+ struct MHD_Response *resp;
+ struct GNUNET_TIME_Absolute expiration;
+
+ ph->ec = NULL;
+ if ( (NULL == attr) ||
+ (0 != code) )
+ {
+ GNUNET_break_op (0);
+ return_invalid_response (ph,
+ MHD_HTTP_OK,
+ ph->inquiry_id,
+ "converter",
+ NULL);
+ persona_proof_cancel (ph);
+ return;
+ }
+ expiration = GNUNET_TIME_relative_to_absolute (ph->pd->validity);
+ resp = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_LOCATION,
+ ph->pd->post_kyc_redirect_url));
+ TALER_MHD_add_global_headers (resp);
+ ph->cb (ph->cb_cls,
+ TALER_KYCLOGIC_STATUS_SUCCESS,
+ ph->account_id,
+ ph->inquiry_id,
+ expiration,
+ attr,
+ MHD_HTTP_SEE_OTHER,
+ resp);
+ persona_proof_cancel (ph);
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP "/api/v1/inquiries/{inquiry-id}" request.
+ *
+ * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_proof_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_KYCLOGIC_ProofHandle *ph = cls;
+ const json_t *j = response;
+ const json_t *data = json_object_get (j,
+ "data");
+
+ ph->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const char *inquiry_id;
+ const char *account_id;
+ const char *type = NULL;
+ const json_t *attributes;
+ const json_t *relationships;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_string ("id",
+ &inquiry_id),
+ GNUNET_JSON_spec_object_const ("attributes",
+ &attributes),
+ GNUNET_JSON_spec_object_const ("relationships",
+ &relationships),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if ( (NULL == data) ||
+ (GNUNET_OK !=
+ GNUNET_JSON_parse (data,
+ spec,
+ NULL, NULL)) ||
+ (0 != strcmp (type,
+ "inquiry")) )
+ {
+ GNUNET_break_op (0);
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data",
+ data);
+ break;
+ }
+
+ {
+ const char *status; /* "completed", what else? */
+ const char *reference_id; /* or legitimization number */
+ const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("status",
+ &status),
+ GNUNET_JSON_spec_string ("reference-id",
+ &reference_id),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("expired-at",
+ &expired_at),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (attributes,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data-attributes",
+ data);
+ break;
+ }
+ {
+ unsigned long long idr;
+ char dummy;
+
+ if ( (1 != sscanf (reference_id,
+ "%llu%c",
+ &idr,
+ &dummy)) ||
+ (idr != ph->process_row) )
+ {
+ GNUNET_break_op (0);
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data-attributes-reference_id",
+ data);
+ break;
+ }
+ }
+
+ if (0 != strcmp (inquiry_id,
+ ph->inquiry_id))
+ {
+ GNUNET_break_op (0);
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data-id",
+ data);
+ break;
+ }
+
+ account_id = json_string_value (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ relationships,
+ "account"),
+ "data"),
+ "id"));
+
+ if (0 != strcmp (status,
+ "completed"))
+ {
+ proof_generic_reply (
+ ph,
+ TALER_KYCLOGIC_STATUS_FAILED,
+ account_id,
+ inquiry_id,
+ MHD_HTTP_OK,
+ "persona-kyc-failed",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ GNUNET_JSON_pack_string ("persona_inquiry_id",
+ inquiry_id),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ }
+
+ if (NULL == account_id)
+ {
+ GNUNET_break_op (0);
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data-relationships-account-data-id",
+ data);
+ break;
+ }
+ ph->account_id = GNUNET_strdup (account_id);
+ ph->ec = start_conversion (ph->pd,
+ j,
+ &proof_post_conversion_cb,
+ ph);
+ if (NULL == ph->ec)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start Persona conversion helper\n");
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY,
+ "persona-logic-failure",
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED)));
+ break;
+ }
+ }
+ return; /* continued in proof_post_conversion_cb */
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_CONFLICT:
+ case MHD_HTTP_UNPROCESSABLE_ENTITY:
+ /* These are errors with this code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY,
+ "persona-logic-failure",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
+
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* These are failures of the exchange operator */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refused access with HTTP status code %u\n",
+ (unsigned int) response_code);
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY,
+ "persona-exchange-unauthorized",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ /* These are failures of the exchange operator */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refused access with HTTP status code %u\n",
+ (unsigned int) response_code);
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ "persona-exchange-unpaid",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ case MHD_HTTP_REQUEST_TIMEOUT:
+ /* These are networking issues */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_GATEWAY_TIMEOUT,
+ "persona-network-timeout",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ case MHD_HTTP_TOO_MANY_REQUESTS:
+ /* This is a load issue */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ "persona-load-failure",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* This is an issue with Persona */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY,
+ "persona-provider-failure",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_ERROR),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ default:
+ /* This is an issue with Persona */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY,
+ "persona-invalid-response",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ }
+ persona_proof_cancel (ph);
+}
+
+
+/**
+ * Check KYC status and return final result to human.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param connection MHD connection object (for HTTP headers)
+ * @param account_id which account to trigger process for
+ * @param process_row row in the legitimization processes table the legitimization is for
+ * @param provider_user_id user ID (or NULL) the proof is for
+ * @param inquiry_id legitimization ID the proof is for
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_ProofHandle *
+persona_proof (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ struct MHD_Connection *connection,
+ const struct TALER_PaytoHashP *account_id,
+ uint64_t process_row,
+ const char *provider_user_id,
+ const char *inquiry_id,
+ TALER_KYCLOGIC_ProofCallback cb,
+ void *cb_cls)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_ProofHandle *ph;
+ CURL *eh;
+
+ eh = curl_easy_init ();
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
+ ph->ps = ps;
+ ph->pd = pd;
+ ph->cb = cb;
+ ph->cb_cls = cb_cls;
+ ph->connection = connection;
+ ph->process_row = process_row;
+ ph->h_payto = *account_id;
+ /* Note: we do not expect this to be non-NULL */
+ if (NULL != provider_user_id)
+ ph->provider_user_id = GNUNET_strdup (provider_user_id);
+ if (NULL != inquiry_id)
+ ph->inquiry_id = GNUNET_strdup (inquiry_id);
+ GNUNET_asprintf (&ph->url,
+ "https://withpersona.com/api/v1/inquiries/%s",
+ inquiry_id);
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_VERBOSE,
+ 0));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_MAXREDIRS,
+ 1L));
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ ph->url));
+ ph->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
+ eh,
+ pd->slist,
+ &handle_proof_finished,
+ ph);
+ return ph;
+}
+
+
+/**
+ * Cancel KYC webhook execution.
+ *
+ * @param[in] wh handle of operation to cancel
+ */
+static void
+persona_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
+{
+ if (NULL != wh->task)
+ {
+ GNUNET_SCHEDULER_cancel (wh->task);
+ wh->task = NULL;
+ }
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ if (NULL != wh->ec)
+ {
+ TALER_JSON_external_conversion_stop (wh->ec);
+ wh->ec = NULL;
+ }
+ GNUNET_free (wh->account_id);
+ GNUNET_free (wh->inquiry_id);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
+
+
+/**
+ * Call @a wh callback with the operation result.
+ *
+ * @param wh proof handle to generate reply for
+ * @param status status to return
+ * @param account_id account to return
+ * @param inquiry_id inquiry ID to supply
+ * @param attr KYC attribute data for the client
+ * @param http_status HTTP status to use
+ */
+static void
+webhook_generic_reply (struct TALER_KYCLOGIC_WebhookHandle *wh,
+ enum TALER_KYCLOGIC_KycStatus status,
+ const char *account_id,
+ const char *inquiry_id,
+ const json_t *attr,
+ unsigned int http_status)
+{
+ struct MHD_Response *resp;
+ struct GNUNET_TIME_Absolute expiration;
+
+ if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
+ expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
+ else
+ expiration = GNUNET_TIME_UNIT_ZERO_ABS;
+ resp = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ account_id,
+ inquiry_id,
+ status,
+ expiration,
+ attr,
+ http_status,
+ resp);
+}
+
+
+/**
+ * Call @a wh callback with HTTP error response.
+ *
+ * @param wh proof handle to generate reply for
+ * @param inquiry_id inquiry ID to supply
+ * @param http_status HTTP status to use
+ */
+static void
+webhook_reply_error (struct TALER_KYCLOGIC_WebhookHandle *wh,
+ const char *inquiry_id,
+ unsigned int http_status)
+{
+ webhook_generic_reply (wh,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ NULL, /* user id */
+ inquiry_id,
+ NULL, /* attributes */
+ http_status);
+}
+
+
+/**
+ * Type of a callback that receives a JSON @a result.
+ *
+ * @param cls closure with a `struct TALER_KYCLOGIC_WebhookHandle *`
+ * @param status_type how did the process die
+ * @param code termination status code from the process
+ * @param attr some JSON result, NULL if we failed to get an JSON output
+ */
+static void
+webhook_post_conversion_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType status_type,
+ unsigned long code,
+ const json_t *attr)
+{
+ struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
+
+ wh->ec = NULL;
+ webhook_generic_reply (wh,
+ TALER_KYCLOGIC_STATUS_SUCCESS,
+ wh->account_id,
+ wh->inquiry_id,
+ attr,
+ MHD_HTTP_OK);
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP "/api/v1/inquiries/{inquiry_id}" request.
+ *
+ * @param cls the `struct TALER_KYCLOGIC_WebhookHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_webhook_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
+ const json_t *j = response;
+ const json_t *data = json_object_get (j,
+ "data");
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const char *inquiry_id;
+ const char *account_id;
+ const char *type = NULL;
+ const json_t *attributes;
+ const json_t *relationships;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_string ("id",
+ &inquiry_id),
+ GNUNET_JSON_spec_object_const ("attributes",
+ &attributes),
+ GNUNET_JSON_spec_object_const ("relationships",
+ &relationships),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if ( (NULL == data) ||
+ (GNUNET_OK !=
+ GNUNET_JSON_parse (data,
+ spec,
+ NULL, NULL)) ||
+ (0 != strcmp (type,
+ "inquiry")) )
+ {
+ GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ }
+
+ {
+ const char *status; /* "completed", what else? */
+ const char *reference_id; /* or legitimization number */
+ const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("status",
+ &status),
+ GNUNET_JSON_spec_string ("reference-id",
+ &reference_id),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("expired-at",
+ &expired_at),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (attributes,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ }
+ {
+ unsigned long long idr;
+ char dummy;
+
+ if ( (1 != sscanf (reference_id,
+ "%llu%c",
+ &idr,
+ &dummy)) ||
+ (idr != wh->process_row) )
+ {
+ GNUNET_break_op (0);
+ webhook_reply_error (wh,
+ inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ }
+ }
+
+ if (0 != strcmp (inquiry_id,
+ wh->inquiry_id))
+ {
+ GNUNET_break_op (0);
+ webhook_reply_error (wh,
+ inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ }
+
+ account_id = json_string_value (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ relationships,
+ "account"),
+ "data"),
+ "id"));
+
+ if (0 != strcmp (status,
+ "completed"))
+ {
+ webhook_generic_reply (wh,
+ TALER_KYCLOGIC_STATUS_FAILED,
+ account_id,
+ inquiry_id,
+ NULL,
+ MHD_HTTP_OK);
+ break;
+ }
+
+ if (NULL == account_id)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (data,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ }
+ wh->account_id = GNUNET_strdup (account_id);
+ wh->ec = start_conversion (wh->pd,
+ j,
+ &webhook_post_conversion_cb,
+ wh);
+ if (NULL == wh->ec)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start Persona conversion helper\n");
+ webhook_reply_error (wh,
+ inquiry_id,
+ MHD_HTTP_INTERNAL_SERVER_ERROR);
+ break;
+ }
+ }
+ return; /* continued in webhook_post_conversion_cb */
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_CONFLICT:
+ case MHD_HTTP_UNPROCESSABLE_ENTITY:
+ /* These are errors with this code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* These are failures of the exchange operator */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refused access with HTTP status code %u\n",
+ (unsigned int) response_code);
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_INTERNAL_SERVER_ERROR);
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ /* These are failures of the exchange operator */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refused access with HTTP status code %u\n",
+ (unsigned int) response_code);
+
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_INTERNAL_SERVER_ERROR);
+ break;
+ case MHD_HTTP_REQUEST_TIMEOUT:
+ /* These are networking issues */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_GATEWAY_TIMEOUT);
+ break;
+ case MHD_HTTP_TOO_MANY_REQUESTS:
+ /* This is a load issue */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_SERVICE_UNAVAILABLE);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* This is an issue with Persona */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ default:
+ /* This is an issue with Persona */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ }
+
+ persona_webhook_cancel (wh);
+}
+
+
+/**
+ * Asynchronously return a reply for the webhook.
+ *
+ * @param cls a `struct TALER_KYCLOGIC_WebhookHandle *`
+ */
+static void
+async_webhook_reply (void *cls)
+{
+ struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
+
+ wh->task = NULL;
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ (0 == wh->process_row)
+ ? NULL
+ : &wh->h_payto,
+ wh->pd->section,
+ NULL,
+ wh->inquiry_id, /* provider legi ID */
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ wh->response_code,
+ wh->resp);
+ persona_webhook_cancel (wh);
+}
+
+
+/**
+ * Function called with the provider details and
+ * associated plugin closures for matching logics.
+ *
+ * @param cls closure
+ * @param pd provider details of a matching logic
+ * @param plugin_cls closure of the plugin
+ * @return #GNUNET_OK to continue to iterate
+ */
+static enum GNUNET_GenericReturnValue
+locate_details_cb (
+ void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ void *plugin_cls)
+{
+ struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
+
+ /* This type-checks 'pd' */
+ GNUNET_assert (plugin_cls == wh->ps);
+ if (0 == strcmp (pd->template_id,
+ wh->template_id))
+ {
+ wh->pd = pd;
+ return GNUNET_NO;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check KYC status and return result for Webhook. We do NOT implement the
+ * authentication check proposed by the PERSONA documentation, as it would
+ * allow an attacker who learns the access token to easily bypass the KYC
+ * checks. Instead, we insist on explicitly requesting the KYC status from the
+ * provider (at least on success).
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param plc callback to lookup accounts with
+ * @param plc_cls closure for @a plc
+ * @param http_method HTTP method used for the webhook
+ * @param url_path rest of the URL after `/kyc-webhook/`
+ * @param connection MHD connection object (for HTTP headers)
+ * @param body HTTP request body
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_WebhookHandle *
+persona_webhook (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ TALER_KYCLOGIC_ProviderLookupCallback plc,
+ void *plc_cls,
+ const char *http_method,
+ const char *const url_path[],
+ struct MHD_Connection *connection,
+ const json_t *body,
+ TALER_KYCLOGIC_WebhookCallback cb,
+ void *cb_cls)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_WebhookHandle *wh;
+ CURL *eh;
+ enum GNUNET_DB_QueryStatus qs;
+ const char *persona_inquiry_id;
+ const char *auth_header;
+
+ /* Persona webhooks are expected by logic, not by template */
+ GNUNET_break_op (NULL == pd);
+ wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ps = ps;
+ wh->connection = connection;
+ wh->pd = pd;
+ auth_header = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_AUTHORIZATION);
+ if ( (NULL != ps->webhook_token) &&
+ ( (NULL == auth_header) ||
+ (0 != strcmp (ps->webhook_token,
+ auth_header)) ) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid authorization header `%s' received for Persona webhook\n",
+ auth_header);
+ wh->resp = TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_WEBHOOK_UNAUTHORIZED),
+ GNUNET_JSON_pack_string ("detail",
+ "unexpected 'Authorization' header"));
+ wh->response_code = MHD_HTTP_UNAUTHORIZED;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+
+ wh->template_id
+ = json_string_value (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ body,
+ "data"),
+ "attributes"),
+ "payload"),
+ "data"),
+ "relationships"),
+ "inquiry-template"),
+ "data"),
+ "id"));
+ if (NULL == wh->template_id)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (body,
+ stderr,
+ JSON_INDENT (2));
+ wh->resp = TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
+ GNUNET_JSON_pack_string ("detail",
+ "data-attributes-payload-data-id"),
+ GNUNET_JSON_pack_object_incref ("webhook_body",
+ (json_t *) body));
+ wh->response_code = MHD_HTTP_BAD_REQUEST;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+ TALER_KYCLOGIC_kyc_get_details ("persona",
+ &locate_details_cb,
+ wh);
+ if (NULL == wh->pd)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (body,
+ stderr,
+ JSON_INDENT (2));
+ wh->resp = TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN),
+ GNUNET_JSON_pack_string ("detail",
+ wh->template_id),
+ GNUNET_JSON_pack_object_incref ("webhook_body",
+ (json_t *) body));
+ wh->response_code = MHD_HTTP_BAD_REQUEST;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+
+ persona_inquiry_id
+ = json_string_value (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ body,
+ "data"),
+ "attributes"),
+ "payload"),
+ "data"),
+ "id"));
+ if (NULL == persona_inquiry_id)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (body,
+ stderr,
+ JSON_INDENT (2));
+ wh->resp = TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
+ GNUNET_JSON_pack_string ("detail",
+ "data-attributes-payload-data-id"),
+ GNUNET_JSON_pack_object_incref ("webhook_body",
+ (json_t *) body));
+ wh->response_code = MHD_HTTP_BAD_REQUEST;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+ qs = plc (plc_cls,
+ wh->pd->section,
+ persona_inquiry_id,
+ &wh->h_payto,
+ &wh->process_row);
+ if (qs < 0)
+ {
+ wh->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "provider-legitimization-lookup");
+ wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Received Persona kyc-webhook for unknown verification ID `%s'\n",
+ persona_inquiry_id);
+ wh->resp = TALER_MHD_make_error (
+ TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
+ persona_inquiry_id);
+ wh->response_code = MHD_HTTP_NOT_FOUND;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+ wh->inquiry_id = GNUNET_strdup (persona_inquiry_id);
+
+ eh = curl_easy_init ();
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ wh->resp = TALER_MHD_make_error (
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ NULL);
+ wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+
+ GNUNET_asprintf (&wh->url,
+ "https://withpersona.com/api/v1/inquiries/%s",
+ persona_inquiry_id);
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_VERBOSE,
+ 0));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_MAXREDIRS,
+ 1L));
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ wh->url));
+ wh->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
+ eh,
+ wh->pd->slist,
+ &handle_webhook_finished,
+ wh);
+ return wh;
+}
+
+
+/**
+ * Initialize persona logic plugin
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
+ */
+void *
+libtaler_plugin_kyclogic_persona_init (void *cls)
+{
+ const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct TALER_KYCLOGIC_Plugin *plugin;
+ struct PluginState *ps;
+
+ ps = GNUNET_new (struct PluginState);
+ ps->cfg = cfg;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "BASE_URL",
+ &ps->exchange_base_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ GNUNET_free (ps);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ "kyclogic-persona",
+ "WEBHOOK_AUTH_TOKEN",
+ &ps->webhook_token))
+ {
+ /* optional */
+ ps->webhook_token = NULL;
+ }
+
+ ps->curl_ctx
+ = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &ps->curl_rc);
+ if (NULL == ps->curl_ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_free (ps->exchange_base_url);
+ GNUNET_free (ps);
+ return NULL;
+ }
+ ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
+
+ plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
+ plugin->cls = ps;
+ plugin->load_configuration
+ = &persona_load_configuration;
+ plugin->unload_configuration
+ = &persona_unload_configuration;
+ plugin->initiate
+ = &persona_initiate;
+ plugin->initiate_cancel
+ = &persona_initiate_cancel;
+ plugin->proof
+ = &persona_proof;
+ plugin->proof_cancel
+ = &persona_proof_cancel;
+ plugin->webhook
+ = &persona_webhook;
+ plugin->webhook_cancel
+ = &persona_webhook_cancel;
+ return plugin;
+}
+
+
+/**
+ * Unload authorization plugin
+ *
+ * @param cls a `struct TALER_KYCLOGIC_Plugin`
+ * @return NULL (always)
+ */
+void *
+libtaler_plugin_kyclogic_persona_done (void *cls)
+{
+ struct TALER_KYCLOGIC_Plugin *plugin = cls;
+ struct PluginState *ps = plugin->cls;
+
+ if (NULL != ps->curl_ctx)
+ {
+ GNUNET_CURL_fini (ps->curl_ctx);
+ ps->curl_ctx = NULL;
+ }
+ if (NULL != ps->curl_rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
+ ps->curl_rc = NULL;
+ }
+ GNUNET_free (ps->exchange_base_url);
+ GNUNET_free (ps->webhook_token);
+ GNUNET_free (ps);
+ GNUNET_free (plugin);
+ return NULL;
+}
+
+
+/* end of plugin_kyclogic_persona.c */
diff --git a/src/kyclogic/plugin_kyclogic_template.c b/src/kyclogic/plugin_kyclogic_template.c
new file mode 100644
index 000000000..54f36e6f2
--- /dev/null
+++ b/src/kyclogic/plugin_kyclogic_template.c
@@ -0,0 +1,468 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2022 Taler Systems SA
+
+ Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file plugin_kyclogic_template.c
+ * @brief template for an authentication flow logic
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_kyclogic_plugin.h"
+#include "taler_mhd_lib.h"
+#include "taler_json_lib.h"
+#include <regex.h>
+#include "taler_util.h"
+
+
+/**
+ * Saves the state of a plugin.
+ */
+struct PluginState
+{
+
+ /**
+ * Our base URL.
+ */
+ char *exchange_base_url;
+
+ /**
+ * Our global configuration.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Context for CURL operations (useful to the event loop)
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ /**
+ * Context for integrating @e curl_ctx with the
+ * GNUnet event loop.
+ */
+ struct GNUNET_CURL_RescheduleContext *curl_rc;
+
+};
+
+
+/**
+ * Keeps the plugin-specific state for
+ * a given configuration section.
+ */
+struct TALER_KYCLOGIC_ProviderDetails
+{
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+
+ /**
+ * Configuration section that configured us.
+ */
+ char *section;
+
+};
+
+
+/**
+ * Handle for an initiation operation.
+ */
+struct TALER_KYCLOGIC_InitiateHandle
+{
+
+ /**
+ * Hash of the payto:// URI we are initiating
+ * the KYC for.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * UUID being checked.
+ */
+ uint64_t legitimization_uuid;
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Continuation to call.
+ */
+ TALER_KYCLOGIC_InitiateCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+};
+
+
+/**
+ * Handle for an KYC proof operation.
+ */
+struct TALER_KYCLOGIC_ProofHandle
+{
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Continuation to call.
+ */
+ TALER_KYCLOGIC_ProofCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+};
+
+
+/**
+ * Handle for an KYC Web hook operation.
+ */
+struct TALER_KYCLOGIC_WebhookHandle
+{
+
+ /**
+ * Continuation to call when done.
+ */
+ TALER_KYCLOGIC_WebhookCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Task for asynchronous execution.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+};
+
+
+/**
+ * Release configuration resources previously loaded
+ *
+ * @param[in] pd configuration to release
+ */
+static void
+template_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
+{
+ GNUNET_free (pd);
+}
+
+
+/**
+ * Load the configuration of the KYC provider.
+ *
+ * @param cls closure
+ * @param provider_section_name configuration section to parse
+ * @return NULL if configuration is invalid
+ */
+static struct TALER_KYCLOGIC_ProviderDetails *
+template_load_configuration (void *cls,
+ const char *provider_section_name)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
+ pd->ps = ps;
+ pd->section = GNUNET_strdup (provider_section_name);
+ GNUNET_break (0); // FIXME: parse config here!
+ return pd;
+}
+
+
+/**
+ * Cancel KYC check initiation.
+ *
+ * @param[in] ih handle of operation to cancel
+ */
+static void
+template_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
+{
+ GNUNET_break (0); // FIXME: add cancel logic here
+ GNUNET_free (ih);
+}
+
+
+/**
+ * Initiate KYC check.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param account_id which account to trigger process for
+ * @param legitimization_uuid unique ID for the legitimization process
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_InitiateHandle *
+template_initiate (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ const struct TALER_PaytoHashP *account_id,
+ uint64_t legitimization_uuid,
+ TALER_KYCLOGIC_InitiateCallback cb,
+ void *cb_cls)
+{
+ struct TALER_KYCLOGIC_InitiateHandle *ih;
+
+ (void) cls;
+ ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
+ ih->legitimization_uuid = legitimization_uuid;
+ ih->cb = cb;
+ ih->cb_cls = cb_cls;
+ ih->h_payto = *account_id;
+ ih->pd = pd;
+ GNUNET_break (0); // FIXME: add actual initiation logic!
+ return ih;
+}
+
+
+/**
+ * Cancel KYC proof.
+ *
+ * @param[in] ph handle of operation to cancel
+ */
+static void
+template_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
+{
+ GNUNET_break (0); // FIXME: stop activities...
+ GNUNET_free (ph);
+}
+
+
+/**
+ * Check KYC status and return status to human.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param connection MHD connection object (for HTTP headers)
+ * @param account_id which account to trigger process for
+ * @param process_row row in the legitimization processes table the legitimization is for
+ * @param provider_user_id user ID (or NULL) the proof is for
+ * @param provider_legitimization_id legitimization ID the proof is for
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_ProofHandle *
+template_proof (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ struct MHD_Connection *connection,
+ const struct TALER_PaytoHashP *account_id,
+ uint64_t process_row,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ TALER_KYCLOGIC_ProofCallback cb,
+ void *cb_cls)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_ProofHandle *ph;
+
+ (void) account_id;
+ (void) process_row;
+ (void) provider_user_id;
+ (void) provider_legitimization_id;
+ ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
+ ph->ps = ps;
+ ph->pd = pd;
+ ph->cb = cb;
+ ph->cb_cls = cb_cls;
+ ph->connection = connection;
+
+ GNUNET_break (0); // FIXME: start check!
+ return ph;
+}
+
+
+/**
+ * Cancel KYC webhook execution.
+ *
+ * @param[in] wh handle of operation to cancel
+ */
+static void
+template_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
+{
+ GNUNET_break (0); /* FIXME: stop activity */
+ GNUNET_free (wh);
+}
+
+
+/**
+ * Check KYC status and return result for Webhook.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param plc callback to lookup accounts with
+ * @param plc_cls closure for @a plc
+ * @param http_method HTTP method used for the webhook
+ * @param url_path rest of the URL after `/kyc-webhook/`
+ * @param connection MHD connection object (for HTTP headers)
+ * @param body HTTP request body
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_WebhookHandle *
+template_webhook (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ TALER_KYCLOGIC_ProviderLookupCallback plc,
+ void *plc_cls,
+ const char *http_method,
+ const char *const url_path[],
+ struct MHD_Connection *connection,
+ const json_t *body,
+ TALER_KYCLOGIC_WebhookCallback cb,
+ void *cb_cls)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_WebhookHandle *wh;
+
+ (void) plc;
+ (void) plc_cls;
+ (void) http_method;
+ (void) url_path;
+ (void) body;
+ wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ps = ps;
+ wh->pd = pd;
+ wh->connection = connection;
+ GNUNET_break (0); /* FIXME: start activity */
+ return wh;
+}
+
+
+/**
+ * Initialize Template.0 KYC logic plugin
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
+ */
+void *
+libtaler_plugin_kyclogic_template_init (void *cls)
+{
+ const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct TALER_KYCLOGIC_Plugin *plugin;
+ struct PluginState *ps;
+
+ ps = GNUNET_new (struct PluginState);
+ ps->cfg = cfg;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "BASE_URL",
+ &ps->exchange_base_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ GNUNET_free (ps);
+ return NULL;
+ }
+
+ ps->curl_ctx
+ = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &ps->curl_rc);
+ if (NULL == ps->curl_ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_free (ps->exchange_base_url);
+ GNUNET_free (ps);
+ return NULL;
+ }
+ ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
+
+ plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
+ plugin->cls = ps;
+ plugin->load_configuration
+ = &template_load_configuration;
+ plugin->unload_configuration
+ = &template_unload_configuration;
+ plugin->initiate
+ = &template_initiate;
+ plugin->initiate_cancel
+ = &template_initiate_cancel;
+ plugin->proof
+ = &template_proof;
+ plugin->proof_cancel
+ = &template_proof_cancel;
+ plugin->webhook
+ = &template_webhook;
+ plugin->webhook_cancel
+ = &template_webhook_cancel;
+ return plugin;
+}
+
+
+/**
+ * Unload authorization plugin
+ *
+ * @param cls a `struct TALER_KYCLOGIC_Plugin`
+ * @return NULL (always)
+ */
+void *
+libtaler_plugin_kyclogic_template_done (void *cls)
+{
+ struct TALER_KYCLOGIC_Plugin *plugin = cls;
+ struct PluginState *ps = plugin->cls;
+
+ if (NULL != ps->curl_ctx)
+ {
+ GNUNET_CURL_fini (ps->curl_ctx);
+ ps->curl_ctx = NULL;
+ }
+ if (NULL != ps->curl_rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
+ ps->curl_rc = NULL;
+ }
+ GNUNET_free (ps->exchange_base_url);
+ GNUNET_free (ps);
+ GNUNET_free (plugin);
+ return NULL;
+}
diff --git a/src/kyclogic/sample.conf b/src/kyclogic/sample.conf
new file mode 100644
index 000000000..b9a88c292
--- /dev/null
+++ b/src/kyclogic/sample.conf
@@ -0,0 +1,33 @@
+# This file is in the public domain.
+#
+
+[exchange]
+
+# HTTP port the exchange listens to
+PORT = 8081
+
+# Base URL of the exchange. Must be set to a URL where the
+# exchange (or the twister) is actually listening.
+BASE_URL = "http://localhost:8081/"
+
+[kyc-provider-test-oauth2]
+
+COST = 0
+LOGIC = oauth2
+USER_TYPE = INDIVIDUAL
+PROVIDED_CHECKS = DUMMY
+
+KYC_OAUTH2_VALIDITY = forever
+KYC_OAUTH2_AUTH_URL = http://kyc.taler.net/auth
+KYC_OAUTH2_LOGIN_URL = http://kyc.taler.net/login
+KYC_OAUTH2_INFO_URL = http://kyc.taler.net/info
+KYC_OAUTH2_POST_URL = http://kyc.taler.net/thank-you
+KYC_OAUTH2_CLIENT_ID = testcase
+KYC_OAUTH2_CLIENT_SECRET = password
+
+[kyc-legitimization-withdraw-high]
+
+OPERATION_TYPE = WITHDRAW
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = KUDOS:100
+TIMEFRAME = 1a
diff --git a/src/kyclogic/taler-exchange-kyc-kycaid-converter.sh b/src/kyclogic/taler-exchange-kyc-kycaid-converter.sh
new file mode 100755
index 000000000..68a1b6a0d
--- /dev/null
+++ b/src/kyclogic/taler-exchange-kyc-kycaid-converter.sh
@@ -0,0 +1,90 @@
+#!/bin/bash
+# This file is in the public domain.
+#
+# This code converts (some of) the JSON output from KYCAID into the GNU Taler
+# specific KYC attribute data (again in JSON format). We may need to download
+# and inline file data in the process, for authorization pass "-a" with the
+# respective bearer token.
+#
+
+# Die if anything goes wrong.
+set -eu
+
+# Parse command-line options
+while getopts ':a:' OPTION; do
+ case "$OPTION" in
+ a)
+ TOKEN="$OPTARG"
+ ;;
+ ?)
+ echo "Unrecognized command line option"
+ exit 1
+ ;;
+ esac
+done
+
+# First, extract everything from stdin.
+J=$(jq '{"type":.type,"email":.email,"phone":.phone,"first_name":.first_name,"name-middle":.middle_name,"last_name":.last_name,"dob":.dob,"residence_country":.residence_country,"gender":.gender,"pep":.pep,"addresses":.addresses,"documents":.documents,"company_name":.company_name,"business_activity_id":.business_activity_id,"registration_country":.registration_country,"documents":.documents,"decline_reasons":.decline_reasons}')
+
+# TODO:
+# log_failure (json_object_get (j, "decline_reasons"));
+
+TYPE=$(echo "$J" | jq -r '.type')
+
+N=0
+DOCS_RAW=""
+DOCS_JSON=""
+for ID in $(jq -r '.documents[]|select(.status=="valid")|.id')
+do
+ TYPE=$(jq -r ".documents[]|select(.id==\"$ID\")|.type")
+ EXPIRY=$(jq -r ".documents[]|select(.id==\"$ID\")|.expiry_date")
+ DOCUMENT_FILE=$(mktemp -t tmp.XXXXXXXXXX)
+ # Authorization: Token $TOKEN
+ DOCUMENT_URL="https://api.kycaid.com/documents/$ID"
+ if [ -z "${TOKEN:-}" ]
+ then
+ wget -q --output-document=- "$DOCUMENT_URL" \
+ | gnunet-base32 > ${DOCUMENT_FILE}
+ else
+ wget -q --output-document=- "$DOCUMENT_URL" \
+ --header "Authorization: Token $TOKEN" \
+ | gnunet-base32 > ${DOCUMENT_FILE}
+ fi
+ DOCS_RAW="$DOCS_RAW --rawfile photo$N \"${DOCUMENT_FILE}\""
+ if [ "$N" = 0 ]
+ then
+ DOCS_JSON="{\"type\":\"$TYPE\",\"image\":\$photo$N}"
+ else
+ DOCS_JSON="{\"type\":\"$TYPE\",\"image\":\$photo$N},$DOCS_JSON"
+ fi
+ N=$(expr $N + 1)
+done
+
+
+if [ "PERSON" = "${TYPE}" ]
+then
+
+ # Next, combine some fields into larger values.
+ FULLNAME=$(echo "$J" | jq -r '[.first_name,.middle_name,.last_name]|join(" ")')
+# STREET=$(echo $J | jq -r '[."street-1",."street-2"]|join(" ")')
+# CITY=$(echo $J | jq -r '[.postcode,.city,."address-subdivision,.cc"]|join(" ")')
+
+ # Combine into final result for individual.
+ echo "$J" \
+ | jq \
+ --arg full_name "${FULLNAME}" \
+ '{$full_name,"birthdate":.dob,"pep":.pep,"phone":.phone,"email":.email,"residences":.residence_country}' \
+ | jq \
+ 'del(..|select(.==null))'
+
+else
+ # Combine into final result for business.
+ echo "$J" \
+ | jq \
+ $DOCS_RAW \
+ "{\"company_name\":.company_name,\"phone\":.phone,\"email\":.email,\"registration_country\":.registration_country,\"documents\":[${DOCS_JSON}]}" \
+ | jq \
+ 'del(..|select(.==null))'
+fi
+
+exit 0
diff --git a/src/kyclogic/taler-exchange-kyc-oauth2-challenger.sh b/src/kyclogic/taler-exchange-kyc-oauth2-challenger.sh
new file mode 100755
index 000000000..729abc504
--- /dev/null
+++ b/src/kyclogic/taler-exchange-kyc-oauth2-challenger.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# This file is in the public domain.
+#
+# This code converts (some of) the JSON output from
+# Challenger into the GNU Taler
+# specific KYC attribute data (again in JSON format).
+#
+
+# Die if anything goes wrong.
+set -eu
+
+# First, extract everything from stdin.
+J=$(jq '{"id":.id,"email":.address,"type":.address_type,"expires":.address_expiration}')
+
+ADDRESS_TYPE=$(echo "$J" | jq -r '.type')
+ROWID=$(echo "$J" | jq -r '.id')
+if [ "$ADDRESS_TYPE" != "email" ]
+then
+ return 1
+fi
+
+echo "$J" \
+ | jq \
+ --arg id "${ROWID}" \
+ '{$id,"email":.email,"expires":.expires}'
+
+exit 0
diff --git a/src/kyclogic/taler-exchange-kyc-oauth2-nda.sh b/src/kyclogic/taler-exchange-kyc-oauth2-nda.sh
new file mode 100755
index 000000000..5af785f19
--- /dev/null
+++ b/src/kyclogic/taler-exchange-kyc-oauth2-nda.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+# This file is in the public domain.
+#
+# This code converts (some of) the JSON output from NDA into the GNU Taler
+# specific KYC attribute data (again in JSON format).
+#
+
+# Die if anything goes wrong.
+set -eu
+
+# First, extract everything from stdin.
+J=$(jq '{"status":.status,"id":.data.id,"last":.data.last_name,"first":.data.first_name,"phone":.data.phone}')
+
+STATUS=$(echo "$J" | jq -r '.status')
+if [ "$STATUS" != "success" ]
+then
+ return 1
+fi
+
+# Next, combine some fields into larger values.
+FULLNAME=$(echo "$J" | jq -r '[.first_name,.last_name]|join(" ")')
+
+echo "$J" \
+ | jq \
+ --arg full_name "${FULLNAME}" \
+ '{$full_name,"phone":.phone,"id":.id}' \
+ | jq \
+ 'del(..|select(.==null))'
+
+exit 0
diff --git a/src/kyclogic/taler-exchange-kyc-oauth2-test-converter.sh b/src/kyclogic/taler-exchange-kyc-oauth2-test-converter.sh
new file mode 100755
index 000000000..76f9f16c4
--- /dev/null
+++ b/src/kyclogic/taler-exchange-kyc-oauth2-test-converter.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+# This file is in the public domain.
+#
+# This code converts (some of) the JSON output from
+# Challenger into the GNU Taler
+# specific KYC attribute data (again in JSON format).
+#
+
+# Die if anything goes wrong.
+set -eu
+
+
+# First, extract everything from stdin.
+J=$(jq '{"id":.data.id,"first":.data.first_name,"last":.data.last_name,"birthdate":.data.birthdate,"status":.status}')
+
+# Next, combine some fields into larger values.
+STATUS=$(echo "$J" | jq -r '.status')
+if [ "$STATUS" != "success" ]
+then
+ exit 1
+fi
+
+FULLNAME=$(echo "$J" | jq -r '[.first,.last]|join(" ")')
+
+echo $J \
+ | jq \
+ --arg full_name "${FULLNAME}" \
+ '{$full_name,"birthdate":.birthdate,"id":.id}' \
+ | jq \
+ 'del(..|select(.==null))'
+exit 0
diff --git a/src/kyclogic/taler-exchange-kyc-persona-converter.sh b/src/kyclogic/taler-exchange-kyc-persona-converter.sh
new file mode 100755
index 000000000..13142d0e5
--- /dev/null
+++ b/src/kyclogic/taler-exchange-kyc-persona-converter.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+# This file is in the public domain.
+#
+# This code converts (some of) the JSON output from Persona into the GNU Taler
+# specific KYC attribute data (again in JSON format). We may need to download
+# and inline file data in the process, for authorization pass "-a" with the
+# respective bearer token.
+#
+
+# Die if anything goes wrong.
+set -eu
+
+# Parse command-line options
+while getopts ':a:' OPTION; do
+ case "$OPTION" in
+ a)
+ TOKEN="$OPTARG"
+ ;;
+ ?)
+ echo "Unrecognized command line option"
+ exit 1
+ ;;
+ esac
+done
+
+
+# First, extract everything from stdin.
+J=$(jq '{"first":.data.attributes."name-first","middle":.data.attributes."name-middle","last":.data.attributes."name-last","cc":.data.attributes.fields."address-country-code".value,"birthdate":.data.attributes.birthdate,"city":.data.attributes."address-city","postcode":.data.attributes."address-postal-code","street-1":.data.attributes."address-street-1","street-2":.data.attributes."address-street-2","address-subdivision":.data.attributes."address-subdivision","identification-number":.data.attributes."identification-number","photo":.included[]|select(.type=="verification/government-id")|.attributes|select(.status=="passed")|."front-photo-url"}')
+
+
+# Next, combine some fields into larger values.
+FULLNAME=$(echo "$J" | jq -r '[.first,.middle,.last]|join(" ")')
+STREET=$(echo $J | jq -r '[."street-1",."street-2"]|join(" ")')
+CITY=$(echo $J | jq -r '[.postcode,.city,."address-subdivision,.cc"]|join(" ")')
+
+# Download and base32-encode the photo
+PHOTO_URL=$(echo "$J" | jq -r '.photo')
+PHOTO_FILE=$(mktemp -t tmp.XXXXXXXXXX)
+if [ -z "${TOKEN:-}" ]
+then
+ wget -q --output-document=- "$PHOTO_URL" | gnunet-base32 > ${PHOTO_FILE}
+else
+ wget -q --output-document=- --header "Authorization: Bearer $TOKEN" "$PHOTO_URL" | gnunet-base32 > ${PHOTO_FILE}
+fi
+
+# Combine into final result.
+echo "$J" \
+ | jq \
+ --arg full_name "${FULLNAME}" \
+ --arg street "${STREET}" \
+ --arg city "${CITY}" \
+ --rawfile photo "${PHOTO_FILE}" \
+ '{$full_name,$street,$city,"birthdate":.birthdate,"residences":.cc,"identification_number":."identification-number",$photo}' \
+ | jq \
+ 'del(..|select(.==null))'
+
+exit 0
diff --git a/src/kyclogic/taler-exchange-kyc-tester.c b/src/kyclogic/taler-exchange-kyc-tester.c
new file mode 100644
index 000000000..c2efafd72
--- /dev/null
+++ b/src/kyclogic/taler-exchange-kyc-tester.c
@@ -0,0 +1,1646 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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-kyc-tester.c
+ * @brief tool to test KYC integrations
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <sched.h>
+#include <sys/resource.h>
+#include <limits.h>
+#include "taler_mhd_lib.h"
+#include "taler_json_lib.h"
+#include "taler_templating_lib.h"
+#include "taler_util.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_kyclogic_plugin.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * @brief Context in which the exchange is processing
+ * all requests
+ */
+struct TEKT_RequestContext
+{
+
+ /**
+ * Opaque parsing context.
+ */
+ void *opaque_post_parsing_context;
+
+ /**
+ * Request handler responsible for this request.
+ */
+ const struct TEKT_RequestHandler *rh;
+
+ /**
+ * Request URL (for logging).
+ */
+ const char *url;
+
+ /**
+ * Connection we are processing.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * HTTP response to return (or NULL).
+ */
+ struct MHD_Response *response;
+
+ /**
+ * @e rh-specific cleanup routine. Function called
+ * upon completion of the request that should
+ * clean up @a rh_ctx. Can be NULL.
+ */
+ void
+ (*rh_cleaner)(struct TEKT_RequestContext *rc);
+
+ /**
+ * @e rh-specific context. Place where the request
+ * handler can associate state with this request.
+ * Can be NULL.
+ */
+ void *rh_ctx;
+
+ /**
+ * Uploaded JSON body, if any.
+ */
+ json_t *root;
+
+ /**
+ * HTTP status to return upon resume if @e response
+ * is non-NULL.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * @brief Struct describing an URL and the handler for it.
+ */
+struct TEKT_RequestHandler
+{
+
+ /**
+ * URL the handler is for (first part only).
+ */
+ const char *url;
+
+ /**
+ * Method the handler is for.
+ */
+ const char *method;
+
+ /**
+ * Callbacks for handling of the request. Which one is used
+ * depends on @e method.
+ */
+ union
+ {
+ /**
+ * Function to call to handle a GET requests (and those
+ * with @e method NULL).
+ *
+ * @param rc context for the request
+ * @param mime_type the @e mime_type for the reply (hint, can be NULL)
+ * @param args array of arguments, needs to be of length @e args_expected
+ * @return MHD result code
+ */
+ MHD_RESULT
+ (*get)(struct TEKT_RequestContext *rc,
+ const char *const args[]);
+
+
+ /**
+ * Function to call to handle a POST request.
+ *
+ * @param rc context for the request
+ * @param json uploaded JSON data
+ * @param args array of arguments, needs to be of length @e args_expected
+ * @return MHD result code
+ */
+ MHD_RESULT
+ (*post)(struct TEKT_RequestContext *rc,
+ const json_t *root,
+ const char *const args[]);
+
+ } 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).
+ */
+ const char *mime_type;
+
+ /**
+ * Raw data for the @e handler, can be NULL for none provided.
+ */
+ const void *data;
+
+ /**
+ * Number of bytes in @e data, 0 for data is 0-terminated (!).
+ */
+ size_t data_size;
+
+ /**
+ * Default response code. 0 for none provided.
+ */
+ unsigned int response_code;
+};
+
+
+/**
+ * Information we track per ongoing kyc-proof request.
+ */
+struct ProofRequestState
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct ProofRequestState *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ProofRequestState *prev;
+
+ /**
+ * Handle for operation with the plugin.
+ */
+ struct TALER_KYCLOGIC_ProofHandle *ph;
+
+ /**
+ * Logic plugin we are using.
+ */
+ struct TALER_KYCLOGIC_Plugin *logic;
+
+ /**
+ * HTTP request details.
+ */
+ struct TEKT_RequestContext *rc;
+
+};
+
+/**
+ * Head of DLL.
+ */
+static struct ProofRequestState *rs_head;
+
+/**
+ * Tail of DLL.
+ */
+static struct ProofRequestState *rs_tail;
+
+/**
+ * The exchange's configuration (global)
+ */
+static const struct GNUNET_CONFIGURATION_Handle *TEKT_cfg;
+
+/**
+ * Handle to the HTTP server.
+ */
+static struct MHD_Daemon *mhd;
+
+/**
+ * Our base URL.
+ */
+static char *TEKT_base_url;
+
+/**
+ * Payto set via command-line (or otherwise random).
+ */
+static struct TALER_PaytoHashP cmd_line_h_payto;
+
+/**
+ * Provider user ID to use.
+ */
+static char *cmd_provider_user_id;
+
+/**
+ * Provider legitimization ID to use.
+ */
+static char *cmd_provider_legitimization_id;
+
+/**
+ * Name of the configuration section with the
+ * configuration data of the selected provider.
+ */
+static const char *provider_section_name;
+
+/**
+ * Row ID to use, override with '-r'
+ */
+static unsigned int kyc_row_id = 42;
+
+/**
+ * -P command-line option.
+ */
+static int print_h_payto;
+
+/**
+ * -w command-line option.
+ */
+static int run_webservice;
+
+/**
+ * Value to return from main()
+ */
+static int global_ret;
+
+/**
+ * -r command-line flag.
+ */
+static char *requirements;
+
+/**
+ * -i command-line flag.
+ */
+static char *ut_s = "individual";
+
+/**
+ * Handle for ongoing initiation operation.
+ */
+static struct TALER_KYCLOGIC_InitiateHandle *ih;
+
+/**
+ * KYC logic running for @e ih.
+ */
+static struct TALER_KYCLOGIC_Plugin *ih_logic;
+
+/**
+ * Port to run the daemon on.
+ */
+static uint16_t serve_port;
+
+/**
+ * Context for all CURL operations (useful to the event loop)
+ */
+static struct GNUNET_CURL_Context *TEKT_curl_ctx;
+
+/**
+ * Context for integrating #TEKT_curl_ctx with the
+ * GNUnet event loop.
+ */
+static struct GNUNET_CURL_RescheduleContext *exchange_curl_rc;
+
+
+/**
+ * Context for the webhook.
+ */
+struct KycWebhookContext
+{
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct KycWebhookContext *next;
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct KycWebhookContext *prev;
+
+ /**
+ * Details about the connection we are processing.
+ */
+ struct TEKT_RequestContext *rc;
+
+ /**
+ * Plugin responsible for the webhook.
+ */
+ struct TALER_KYCLOGIC_Plugin *plugin;
+
+ /**
+ * Configuration for the specific action.
+ */
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Webhook activity.
+ */
+ struct TALER_KYCLOGIC_WebhookHandle *wh;
+
+ /**
+ * HTTP response to return.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Name of the configuration
+ * section defining the KYC logic.
+ */
+ const char *section_name;
+
+ /**
+ * HTTP response code to return.
+ */
+ unsigned int response_code;
+
+ /**
+ * #GNUNET_YES if we are suspended,
+ * #GNUNET_NO if not.
+ * #GNUNET_SYSERR if we had some error.
+ */
+ enum GNUNET_GenericReturnValue suspended;
+
+};
+
+
+/**
+ * Contexts are kept in a DLL while suspended.
+ */
+static struct KycWebhookContext *kwh_head;
+
+/**
+ * Contexts are kept in a DLL while suspended.
+ */
+static struct KycWebhookContext *kwh_tail;
+
+
+/**
+ * Resume processing the @a kwh request.
+ *
+ * @param kwh request to resume
+ */
+static void
+kwh_resume (struct KycWebhookContext *kwh)
+{
+ GNUNET_assert (GNUNET_YES == kwh->suspended);
+ kwh->suspended = GNUNET_NO;
+ GNUNET_CONTAINER_DLL_remove (kwh_head,
+ kwh_tail,
+ kwh);
+ MHD_resume_connection (kwh->rc->connection);
+}
+
+
+static void
+kyc_webhook_cleanup (void)
+{
+ struct KycWebhookContext *kwh;
+
+ while (NULL != (kwh = kwh_head))
+ {
+ if (NULL != kwh->wh)
+ {
+ kwh->plugin->webhook_cancel (kwh->wh);
+ kwh->wh = NULL;
+ }
+ kwh_resume (kwh);
+ }
+}
+
+
+/**
+ * Function called with the result of a webhook
+ * operation.
+ *
+ * Note that the "decref" for the @a response
+ * will be done by the plugin.
+ *
+ * @param cls closure
+ * @param process_row legitimization process request the webhook was about
+ * @param account_id account the webhook was about
+ * @param provider_section configuration section of the logic
+ * @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 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
+ */
+static void
+webhook_finished_cb (
+ void *cls,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *account_id,
+ const char *provider_section,
+ const char *provider_user_id,
+ 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)
+{
+ struct KycWebhookContext *kwh = cls;
+
+ (void) expiration;
+ (void) provider_section;
+ kwh->wh = NULL;
+ if ( (NULL != account_id) &&
+ (0 != GNUNET_memcmp (account_id,
+ &cmd_line_h_payto)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Received webhook for unexpected account\n");
+ }
+ if ( (NULL != provider_user_id) &&
+ (NULL != cmd_provider_user_id) &&
+ (0 != strcmp (provider_user_id,
+ cmd_provider_user_id)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Received webhook for unexpected provider user ID (%s)\n",
+ provider_user_id);
+ }
+ if ( (NULL != provider_legitimization_id) &&
+ (NULL != cmd_provider_legitimization_id) &&
+ (0 != strcmp (provider_legitimization_id,
+ cmd_provider_legitimization_id)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Received webhook for unexpected provider legitimization ID (%s)\n",
+ provider_legitimization_id);
+ }
+ switch (status)
+ {
+ case TALER_KYCLOGIC_STATUS_SUCCESS:
+ /* _successfully_ resumed case */
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "KYC successful for user `%s' (legi: %s)\n",
+ provider_user_id,
+ provider_legitimization_id);
+ GNUNET_break (NULL != attributes);
+ fprintf (stderr,
+ "Extracted attributes:\n");
+ json_dumpf (attributes,
+ stderr,
+ JSON_INDENT (2));
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC status of %s/%s (process #%llu) is %d\n",
+ provider_user_id,
+ provider_legitimization_id,
+ (unsigned long long) process_row,
+ status);
+ break;
+ }
+ kwh->response = response;
+ kwh->response_code = http_status;
+ kwh_resume (kwh);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+/**
+ * Function called to clean up a context.
+ *
+ * @param rc request context
+ */
+static void
+clean_kwh (struct TEKT_RequestContext *rc)
+{
+ struct KycWebhookContext *kwh = rc->rh_ctx;
+
+ if (NULL != kwh->wh)
+ {
+ kwh->plugin->webhook_cancel (kwh->wh);
+ kwh->wh = NULL;
+ }
+ if (NULL != kwh->response)
+ {
+ MHD_destroy_response (kwh->response);
+ kwh->response = NULL;
+ }
+ GNUNET_free (kwh);
+}
+
+
+/**
+ * Function the plugin can use to lookup an
+ * @a h_payto by @a provider_legitimization_id.
+ *
+ * @param cls closure, NULL
+ * @param provider_section
+ * @param provider_legitimization_id legi to look up
+ * @param[out] h_payto where to write the result
+ * @param[out] legi_row where to write the row ID for the legitimization ID
+ * @return database transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+kyc_provider_account_lookup (
+ void *cls,
+ const char *provider_section,
+ const char *provider_legitimization_id,
+ struct TALER_PaytoHashP *h_payto,
+ uint64_t *legi_row)
+{
+ (void) cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Simulated account lookup using `%s/%s'\n",
+ provider_section,
+ provider_legitimization_id);
+ *h_payto = cmd_line_h_payto;
+ *legi_row = kyc_row_id;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+/**
+ * Handle a (GET or POST) "/kyc-webhook" request.
+ *
+ * @param rc request to handle
+ * @param method HTTP request method used by the client
+ * @param root uploaded JSON body (can be NULL)
+ * @param args one argument with the legitimization_uuid
+ * @return MHD result code
+ */
+static MHD_RESULT
+handler_kyc_webhook_generic (
+ struct TEKT_RequestContext *rc,
+ const char *method,
+ const json_t *root,
+ const char *const args[])
+{
+ struct KycWebhookContext *kwh = rc->rh_ctx;
+
+ if (NULL == kwh)
+ { /* first time */
+ kwh = GNUNET_new (struct KycWebhookContext);
+ kwh->rc = rc;
+ rc->rh_ctx = kwh;
+ rc->rh_cleaner = &clean_kwh;
+
+ if ( (NULL == args[0]) ||
+ (GNUNET_OK !=
+ TALER_KYCLOGIC_lookup_logic (args[0],
+ &kwh->plugin,
+ &kwh->pd,
+ &kwh->section_name)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYC logic `%s' unknown (check KYC provider configuration)\n",
+ args[0]);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
+ args[0]);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Calling KYC provider specific webhook\n");
+ kwh->wh = kwh->plugin->webhook (kwh->plugin->cls,
+ kwh->pd,
+ &kyc_provider_account_lookup,
+ NULL,
+ method,
+ &args[1],
+ rc->connection,
+ root,
+ &webhook_finished_cb,
+ kwh);
+ if (NULL == kwh->wh)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "failed to run webhook logic");
+ }
+ kwh->suspended = GNUNET_YES;
+ GNUNET_CONTAINER_DLL_insert (kwh_head,
+ kwh_tail,
+ kwh);
+ MHD_suspend_connection (rc->connection);
+ return MHD_YES;
+ }
+
+ if (NULL != kwh->response)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning queued reply for KWH\n");
+ /* handle _failed_ resumed cases */
+ return MHD_queue_response (rc->connection,
+ kwh->response_code,
+ kwh->response);
+ }
+
+ /* We resumed, but got no response? This should
+ not happen. */
+ GNUNET_assert (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "resumed without response");
+}
+
+
+/**
+ * Handle a GET "/kyc-webhook" request.
+ *
+ * @param rc request to handle
+ * @param args one argument with the legitimization_uuid
+ * @return MHD result code
+ */
+static MHD_RESULT
+handler_kyc_webhook_get (
+ struct TEKT_RequestContext *rc,
+ const char *const args[])
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Webhook GET triggered\n");
+ return handler_kyc_webhook_generic (rc,
+ MHD_HTTP_METHOD_GET,
+ NULL,
+ args);
+}
+
+
+/**
+ * Handle a POST "/kyc-webhook" request.
+ *
+ * @param rc request to handle
+ * @param root uploaded JSON body (can be NULL)
+ * @param args one argument with the legitimization_uuid
+ * @return MHD result code
+ */
+static MHD_RESULT
+handler_kyc_webhook_post (
+ struct TEKT_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Webhook POST triggered\n");
+ return handler_kyc_webhook_generic (rc,
+ MHD_HTTP_METHOD_POST,
+ root,
+ args);
+}
+
+
+/**
+ * Function called with the result of a proof check operation.
+ *
+ * Note that the "decref" for the @a response
+ * will be done by the callee and MUST NOT be done by the plugin.
+ *
+ * @param cls closure with the `struct ProofRequestState`
+ * @param status KYC status
+ * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
+ * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
+ * @param expiration until when is the KYC check valid
+ * @param attributes attributes about the user
+ * @param http_status HTTP status code of @a response
+ * @param[in] response to return to the HTTP client
+ */
+static void
+proof_cb (
+ void *cls,
+ enum TALER_KYCLOGIC_KycStatus status,
+ 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)
+{
+ struct ProofRequestState *rs = cls;
+
+ (void) expiration;
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "KYC legitimization %s completed with status %d (%u) for %s\n",
+ provider_legitimization_id,
+ status,
+ http_status,
+ provider_user_id);
+ if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
+ {
+ GNUNET_break (NULL != attributes);
+ fprintf (stderr,
+ "Extracted attributes:\n");
+ json_dumpf (attributes,
+ stderr,
+ JSON_INDENT (2));
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning response %p with status %u\n",
+ response,
+ http_status);
+ rs->rc->response = response;
+ rs->rc->http_status = http_status;
+ GNUNET_CONTAINER_DLL_remove (rs_head,
+ rs_tail,
+ rs);
+ MHD_resume_connection (rs->rc->connection);
+ TALER_MHD_daemon_trigger ();
+ GNUNET_free (rs);
+}
+
+
+/**
+ * Function called when we receive a 'GET' to the
+ * '/kyc-proof' endpoint.
+ *
+ * @param rc request context
+ * @param args remaining URL arguments;
+ * args[0] should be the logic plugin name
+ */
+static MHD_RESULT
+handler_kyc_proof_get (
+ struct TEKT_RequestContext *rc,
+ const char *const args[1])
+{
+ struct TALER_PaytoHashP h_payto;
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+ struct TALER_KYCLOGIC_Plugin *logic;
+ struct ProofRequestState *rs;
+ const char *section_name;
+ const char *h_paytos;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "GET /kyc-proof triggered\n");
+ if (NULL == args[0])
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ "'/kyc-proof/$PROVIDER_SECTION?state=$H_PAYTO' required");
+ }
+ h_paytos = MHD_lookup_connection_value (rc->connection,
+ MHD_GET_ARGUMENT_KIND,
+ "state");
+ if (NULL == h_paytos)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ "h_payto");
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (h_paytos,
+ strlen (h_paytos),
+ &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 (0 !=
+ GNUNET_memcmp (&h_payto,
+ &cmd_line_h_payto))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
+ "h_payto");
+ }
+
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_lookup_logic (args[0],
+ &logic,
+ &pd,
+ &section_name))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not initiate KYC with provider `%s' (configuration error?)\n",
+ args[0]);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
+ args[0]);
+ }
+ rs = GNUNET_new (struct ProofRequestState);
+ rs->rc = rc;
+ rs->logic = logic;
+ MHD_suspend_connection (rc->connection);
+ GNUNET_CONTAINER_DLL_insert (rs_head,
+ rs_tail,
+ rs);
+ rs->ph = logic->proof (logic->cls,
+ pd,
+ rc->connection,
+ &h_payto,
+ kyc_row_id,
+ cmd_provider_user_id,
+ cmd_provider_legitimization_id,
+ &proof_cb,
+ rs);
+ GNUNET_assert (NULL != rs->ph);
+ return MHD_YES;
+}
+
+
+/**
+ * Function called whenever MHD is done with a request. If the
+ * request was a POST, we may have stored a `struct Buffer *` in the
+ * @a con_cls that might still need to be cleaned up. Call the
+ * respective function to free the memory.
+ *
+ * @param cls client-defined closure
+ * @param connection connection handle
+ * @param con_cls value as set by the last call to
+ * the #MHD_AccessHandlerCallback
+ * @param toe reason for request termination
+ * @see #MHD_OPTION_NOTIFY_COMPLETED
+ * @ingroup request
+ */
+static void
+handle_mhd_completion_callback (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct TEKT_RequestContext *rc = *con_cls;
+
+ (void) cls;
+ if (NULL == rc)
+ return;
+ if (NULL != rc->rh_cleaner)
+ rc->rh_cleaner (rc);
+ {
+#if MHD_VERSION >= 0x00097304
+ const union MHD_ConnectionInfo *ci;
+ unsigned int http_status = 0;
+
+ ci = MHD_get_connection_info (connection,
+ MHD_CONNECTION_INFO_HTTP_STATUS);
+ if (NULL != ci)
+ http_status = ci->http_status;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Request for `%s' completed with HTTP status %u (%d)\n",
+ rc->url,
+ http_status,
+ toe);
+#else
+ (void) connection;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Request for `%s' completed (%d)\n",
+ rc->url,
+ toe);
+#endif
+ }
+
+ TALER_MHD_parse_post_cleanup_callback (rc->opaque_post_parsing_context);
+ /* Sanity-check that we didn't leave any transactions hanging */
+ if (NULL != rc->root)
+ json_decref (rc->root);
+ GNUNET_free (rc);
+ *con_cls = NULL;
+}
+
+
+/**
+ * We found a request handler responsible for handling a request. Parse the
+ * @a upload_data (if applicable) and the @a url and call the
+ * handler.
+ *
+ * @param rc request context
+ * @param url rest of the URL to parse
+ * @param upload_data upload data to parse (if available)
+ * @param[in,out] upload_data_size number of bytes in @a upload_data
+ * @return MHD result code
+ */
+static MHD_RESULT
+proceed_with_handler (struct TEKT_RequestContext *rc,
+ const char *url,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ const struct TEKT_RequestHandler *rh = rc->rh;
+ const char *args[rh->nargs + 2];
+ size_t ulen = strlen (url) + 1;
+ MHD_RESULT ret;
+
+ /* We do check for "ulen" here, because we'll later stack-allocate a buffer
+ of that size and don't want to enable malicious clients to cause us
+ huge stack allocations. */
+ if (ulen > 512)
+ {
+ /* 512 is simply "big enough", as it is bigger than "6 * 54",
+ which is the longest URL format we ever get (for
+ /deposits/). The value should be adjusted if we ever define protocol
+ endpoints with plausibly longer inputs. */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_URI_TOO_LONG,
+ TALER_EC_GENERIC_URI_TOO_LONG,
+ url);
+ }
+
+ /* All POST endpoints come with a body in JSON format. So we parse
+ the JSON here. */
+ if ( (NULL == rc->root) &&
+ (0 == strcasecmp (rh->method,
+ MHD_HTTP_METHOD_POST)) )
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_post_json (rc->connection,
+ &rc->opaque_post_parsing_context,
+ upload_data,
+ upload_data_size,
+ &rc->root);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_assert (NULL == rc->root);
+ GNUNET_break (0);
+ return MHD_NO; /* bad upload, could not even generate error */
+ }
+ if ( (GNUNET_NO == res) ||
+ (NULL == rc->root) )
+ {
+ GNUNET_assert (NULL == rc->root);
+ return MHD_YES; /* so far incomplete upload or parser error */
+ }
+ }
+
+ {
+ char d[ulen];
+ unsigned int i;
+ char *sp;
+
+ /* Parse command-line arguments */
+ /* make a copy of 'url' because 'strtok_r()' will modify */
+ GNUNET_memcpy (d,
+ url,
+ ulen);
+ i = 0;
+ args[i++] = strtok_r (d, "/", &sp);
+ while ( (NULL != args[i - 1]) &&
+ (i <= rh->nargs + 1) )
+ args[i++] = strtok_r (NULL, "/", &sp);
+ /* make sure above loop ran nicely until completion, and also
+ that there is no excess data in 'd' afterwards */
+ if ( ( (rh->nargs_is_upper_bound) &&
+ (i - 1 > rh->nargs) ) ||
+ ( (! rh->nargs_is_upper_bound) &&
+ (i - 1 != rh->nargs) ) )
+ {
+ char emsg[128 + 512];
+
+ GNUNET_snprintf (emsg,
+ sizeof (emsg),
+ "Got %u+/%u segments for `%s' request (`%s')",
+ i - 1,
+ rh->nargs,
+ rh->url,
+ url);
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS,
+ emsg);
+ }
+ GNUNET_assert (NULL == args[i - 1]);
+
+ /* Above logic ensures that 'root' is exactly non-NULL for POST operations,
+ so we test for 'root' to decide which handler to invoke. */
+ if (NULL != rc->root)
+ ret = rh->handler.post (rc,
+ rc->root,
+ args);
+ else /* We also only have "POST" or "GET" in the API for at this point
+ (OPTIONS/HEAD are taken care of earlier) */
+ ret = rh->handler.get (rc,
+ args);
+ }
+ return ret;
+}
+
+
+static void
+rh_cleaner_cb (struct TEKT_RequestContext *rc)
+{
+ if (NULL != rc->response)
+ {
+ MHD_destroy_response (rc->response);
+ rc->response = NULL;
+ }
+ if (NULL != rc->root)
+ {
+ json_decref (rc->root);
+ rc->root = NULL;
+ }
+}
+
+
+/**
+ * Handle incoming HTTP request.
+ *
+ * @param cls closure for MHD daemon (unused)
+ * @param connection the connection
+ * @param url the requested url
+ * @param method the method (POST, GET, ...)
+ * @param version HTTP version (ignored)
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request (a `struct TEKT_RequestContext *`)
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_mhd_request (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ static struct TEKT_RequestHandler handlers[] = {
+ /* simulated KYC endpoints */
+ {
+ .url = "kyc-proof",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &handler_kyc_proof_get,
+ .nargs = 1
+ },
+ {
+ .url = "kyc-webhook",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &handler_kyc_webhook_post,
+ .nargs = 128,
+ .nargs_is_upper_bound = true
+ },
+ {
+ .url = "kyc-webhook",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &handler_kyc_webhook_get,
+ .nargs = 128,
+ .nargs_is_upper_bound = true
+ },
+ /* mark end of list */
+ {
+ .url = NULL
+ }
+ };
+ struct TEKT_RequestContext *rc = *con_cls;
+
+ (void) cls;
+ (void) version;
+ if (NULL == rc)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling new request\n");
+ /* We're in a new async scope! */
+ rc = *con_cls = GNUNET_new (struct TEKT_RequestContext);
+ rc->url = url;
+ rc->connection = connection;
+ rc->rh_cleaner = &rh_cleaner_cb;
+ }
+ if (NULL != rc->response)
+ {
+ return MHD_queue_response (rc->connection,
+ rc->http_status,
+ rc->response);
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling request (%s) for URL '%s'\n",
+ method,
+ url);
+ /* on repeated requests, check our cache first */
+ if (NULL != rc->rh)
+ {
+ const char *start;
+
+ if ('\0' == url[0])
+ /* strange, should start with '/', treat as just "/" */
+ url = "/";
+ start = strchr (url + 1, '/');
+ if (NULL == start)
+ start = "";
+ return proceed_with_handler (rc,
+ start,
+ upload_data,
+ upload_data_size);
+ }
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_HEAD))
+ method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */
+
+ /* parse first part of URL */
+ {
+ bool found = false;
+ size_t tok_size;
+ const char *tok;
+ const char *rest;
+
+ if ('\0' == url[0])
+ /* strange, should start with '/', treat as just "/" */
+ url = "/";
+ tok = url + 1;
+ rest = strchr (tok, '/');
+ if (NULL == rest)
+ {
+ tok_size = strlen (tok);
+ }
+ else
+ {
+ tok_size = rest - tok;
+ rest++; /* skip over '/' */
+ }
+ for (unsigned int i = 0; NULL != handlers[i].url; i++)
+ {
+ struct TEKT_RequestHandler *rh = &handlers[i];
+
+ if ( (0 != strncmp (tok,
+ rh->url,
+ tok_size)) ||
+ (tok_size != strlen (rh->url) ) )
+ continue;
+ found = true;
+ /* The URL is a match! What we now do depends on the method. */
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_OPTIONS))
+ {
+ return TALER_MHD_reply_cors_preflight (connection);
+ }
+ GNUNET_assert (NULL != rh->method);
+ if (0 != strcasecmp (method,
+ rh->method))
+ {
+ found = true;
+ continue;
+ }
+ /* cache to avoid the loop next time */
+ rc->rh = rh;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handler found for %s '%s'\n",
+ method,
+ url);
+ return MHD_YES;
+ }
+
+ if (found)
+ {
+ /* we found a matching address, but the method is wrong */
+ struct MHD_Response *reply;
+ MHD_RESULT ret;
+ char *allowed = NULL;
+
+ GNUNET_break_op (0);
+ for (unsigned int i = 0; NULL != handlers[i].url; i++)
+ {
+ struct TEKT_RequestHandler *rh = &handlers[i];
+
+ if ( (0 != strncmp (tok,
+ rh->url,
+ tok_size)) ||
+ (tok_size != strlen (rh->url) ) )
+ continue;
+ if (NULL == allowed)
+ {
+ allowed = GNUNET_strdup (rh->method);
+ }
+ else
+ {
+ char *tmp;
+
+ GNUNET_asprintf (&tmp,
+ "%s, %s",
+ allowed,
+ rh->method);
+ GNUNET_free (allowed);
+ allowed = tmp;
+ }
+ if (0 == strcasecmp (rh->method,
+ MHD_HTTP_METHOD_GET))
+ {
+ char *tmp;
+
+ GNUNET_asprintf (&tmp,
+ "%s, %s",
+ allowed,
+ MHD_HTTP_METHOD_HEAD);
+ GNUNET_free (allowed);
+ allowed = tmp;
+ }
+ }
+ reply = TALER_MHD_make_error (TALER_EC_GENERIC_METHOD_INVALID,
+ method);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (reply,
+ MHD_HTTP_HEADER_ALLOW,
+ allowed));
+ GNUNET_free (allowed);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_METHOD_NOT_ALLOWED,
+ reply);
+ MHD_destroy_response (reply);
+ return ret;
+ }
+ }
+
+ /* No handler matches, generate not found */
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ url);
+}
+
+
+/**
+ * Load configuration parameters for the exchange
+ * server into the corresponding global variables.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+exchange_serve_process_config (void)
+{
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (TEKT_cfg,
+ "exchange",
+ "BASE_URL",
+ &TEKT_base_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ return GNUNET_SYSERR;
+ }
+ if (! TALER_url_valid_charset (TEKT_base_url))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL",
+ "invalid URL");
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function run on shutdown.
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ struct MHD_Daemon *mhd;
+ struct ProofRequestState *rs;
+
+ (void) cls;
+ while (NULL != (rs = rs_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (rs_head,
+ rs_tail,
+ rs);
+ rs->logic->proof_cancel (rs->ph);
+ MHD_resume_connection (rs->rc->connection);
+ GNUNET_free (rs);
+ }
+ if (NULL != ih)
+ {
+ ih_logic->initiate_cancel (ih);
+ ih = NULL;
+ }
+ kyc_webhook_cleanup ();
+ TALER_KYCLOGIC_kyc_done ();
+ mhd = TALER_MHD_daemon_stop ();
+ if (NULL != mhd)
+ MHD_stop_daemon (mhd);
+ if (NULL != TEKT_curl_ctx)
+ {
+ GNUNET_CURL_fini (TEKT_curl_ctx);
+ TEKT_curl_ctx = NULL;
+ }
+ if (NULL != exchange_curl_rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (exchange_curl_rc);
+ exchange_curl_rc = NULL;
+ }
+ TALER_TEMPLATING_done ();
+}
+
+
+/**
+ * Function called with the result of a KYC initiation
+ * operation.
+ *
+ * @param cls closure
+ * @param ec #TALER_EC_NONE on success
+ * @param redirect_url set to where to redirect the user on success, NULL on failure
+ * @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 error_msg_hint set to additional details to return to user, NULL on success
+ */
+static void
+initiate_cb (
+ void *cls,
+ enum TALER_ErrorCode ec,
+ const char *redirect_url,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ const char *error_msg_hint)
+{
+ (void) cls;
+ ih = NULL;
+ if (TALER_EC_NONE != ec)
+ {
+ fprintf (stderr,
+ "Failed to start KYC process: %s (#%d)\n",
+ error_msg_hint,
+ ec);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ {
+ char *s;
+
+ s = GNUNET_STRINGS_data_to_string_alloc (&cmd_line_h_payto,
+ sizeof (cmd_line_h_payto));
+ if (NULL != provider_user_id)
+ {
+ fprintf (stdout,
+ "Visit `%s' to begin KYC process.\nAlso use: taler-exchange-kyc-tester -w -u '%s' -U '%s' -p %s\n",
+ redirect_url,
+ provider_user_id,
+ provider_legitimization_id,
+ s);
+ }
+ else
+ {
+ fprintf (stdout,
+ "Visit `%s' to begin KYC process.\nAlso use: taler-exchange-kyc-tester -w -U '%s' -p %s\n",
+ redirect_url,
+ provider_legitimization_id,
+ s);
+ }
+ GNUNET_free (s);
+ }
+ GNUNET_free (cmd_provider_user_id);
+ GNUNET_free (cmd_provider_legitimization_id);
+ if (NULL != provider_user_id)
+ cmd_provider_user_id = GNUNET_strdup (provider_user_id);
+ if (NULL != provider_legitimization_id)
+ cmd_provider_legitimization_id = GNUNET_strdup (provider_legitimization_id);
+ if (! run_webservice)
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be
+ * NULL!)
+ * @param config configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *config)
+{
+ int fh;
+
+ (void) cls;
+ (void) args;
+ (void ) cfgfile;
+ if (GNUNET_OK !=
+ TALER_TEMPLATING_init ("exchange"))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not load templates. Installation broken.\n");
+ return;
+ }
+ if (print_h_payto)
+ {
+ char *s;
+
+ s = GNUNET_STRINGS_data_to_string_alloc (&cmd_line_h_payto,
+ sizeof (cmd_line_h_payto));
+ fprintf (stdout,
+ "%s\n",
+ s);
+ GNUNET_free (s);
+ }
+ TALER_MHD_setup (TALER_MHD_GO_NONE);
+ TEKT_cfg = config;
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_init (config))
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ exchange_serve_process_config ())
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ global_ret = EXIT_SUCCESS;
+ if (NULL != requirements)
+ {
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+ enum TALER_KYCLOGIC_KycUserType ut;
+
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_user_type_from_string (ut_s,
+ &ut))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid user type specified ('-i')\n");
+ global_ret = EXIT_INVALIDARGUMENT;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_requirements_to_logic (requirements,
+ ut,
+ &ih_logic,
+ &pd,
+ &provider_section_name))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not initiate KYC for requirements `%s' (configuration error?)\n",
+ requirements);
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ ih = ih_logic->initiate (ih_logic->cls,
+ pd,
+ &cmd_line_h_payto,
+ kyc_row_id,
+ &initiate_cb,
+ NULL);
+ GNUNET_break (NULL != ih);
+ }
+ if (run_webservice)
+ {
+ TEKT_curl_ctx
+ = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &exchange_curl_rc);
+ if (NULL == TEKT_curl_ctx)
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ exchange_curl_rc = GNUNET_CURL_gnunet_rc_create (TEKT_curl_ctx);
+ fh = TALER_MHD_bind (TEKT_cfg,
+ "exchange",
+ &serve_port);
+ if ( (0 == serve_port) &&
+ (-1 == fh) )
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting daemon on port %u\n",
+ (unsigned int) serve_port);
+ mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME
+ | MHD_USE_PIPE_FOR_SHUTDOWN
+ | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
+ | MHD_USE_TCP_FASTOPEN,
+ (-1 == fh) ? serve_port : 0,
+ NULL, NULL,
+ &handle_mhd_request, NULL,
+ MHD_OPTION_LISTEN_SOCKET,
+ fh,
+ MHD_OPTION_EXTERNAL_LOGGER,
+ &TALER_MHD_handle_logs,
+ NULL,
+ MHD_OPTION_NOTIFY_COMPLETED,
+ &handle_mhd_completion_callback,
+ NULL,
+ MHD_OPTION_END);
+ if (NULL == mhd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to launch HTTP service. Is the port in use?\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ TALER_MHD_daemon_start (mhd);
+ }
+}
+
+
+/**
+ * The main function of the taler-exchange-httpd server ("the exchange").
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ const struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_help (
+ "tool to test KYC provider integrations"),
+ GNUNET_GETOPT_option_flag (
+ 'P',
+ "print-payto-hash",
+ "output the hash of the payto://-URI",
+ &print_h_payto),
+ GNUNET_GETOPT_option_uint (
+ 'r',
+ "rowid",
+ "NUMBER",
+ "override row ID to use in simulation (default: 42)",
+ &kyc_row_id),
+ GNUNET_GETOPT_option_flag (
+ 'w',
+ "run-webservice",
+ "run the integrated HTTP service",
+ &run_webservice),
+ GNUNET_GETOPT_option_string (
+ 'R',
+ "requirements",
+ "CHECKS",
+ "initiate KYC check for the given list of (space-separated) checks",
+ &requirements),
+ GNUNET_GETOPT_option_string (
+ 'i',
+ "identify",
+ "USERTYPE",
+ "self-identify as USERTYPE 'business' or 'individual' (defaults to 'individual')",
+ &requirements),
+ GNUNET_GETOPT_option_string (
+ 'u',
+ "user",
+ "ID",
+ "use the given provider user ID (overridden if -i is also used)",
+ &cmd_provider_user_id),
+ GNUNET_GETOPT_option_string (
+ 'U',
+ "legitimization",
+ "ID",
+ "use the given provider legitimization ID (overridden if -i is also used)",
+ &cmd_provider_legitimization_id),
+ GNUNET_GETOPT_option_base32_fixed_size (
+ 'p',
+ "payto-hash",
+ "HASH",
+ "base32 encoding of the hash of a payto://-URI to use for the account (otherwise a random value will be used)",
+ &cmd_line_h_payto,
+ sizeof (cmd_line_h_payto)),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ TALER_OS_init ();
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &cmd_line_h_payto,
+ sizeof (cmd_line_h_payto));
+ ret = GNUNET_PROGRAM_run (argc, argv,
+ "taler-exchange-kyc-tester",
+ "tool to test KYC provider integrations",
+ options,
+ &run, NULL);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+
+/* end of taler-exchange-kyc-tester.c */
diff --git a/src/lib/.gitignore b/src/lib/.gitignore
new file mode 100644
index 000000000..6664876f2
--- /dev/null
+++ b/src/lib/.gitignore
@@ -0,0 +1 @@
+test_stefan
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 87f343094..63dab7c80 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -18,34 +18,77 @@ lib_LTLIBRARIES = \
libtalerexchange.la
libtalerexchange_la_LDFLAGS = \
- -version-info 4:0:0 \
+ -version-info 7:0:0 \
-no-undefined
libtalerexchange_la_SOURCES = \
+ exchange_api_add_aml_decision.c \
+ exchange_api_age_withdraw.c \
+ exchange_api_age_withdraw_reveal.c \
+ exchange_api_auditor_add_denomination.c \
+ exchange_api_batch_deposit.c \
+ exchange_api_batch_withdraw.c \
+ exchange_api_batch_withdraw2.c \
exchange_api_curl_defaults.c exchange_api_curl_defaults.h \
- exchange_api_common.c \
+ exchange_api_coins_history.c \
+ exchange_api_common.c exchange_api_common.h \
+ exchange_api_contracts_get.c \
+ exchange_api_csr_melt.c \
+ exchange_api_csr_withdraw.c \
exchange_api_handle.c exchange_api_handle.h \
- exchange_api_deposit.c \
exchange_api_deposits_get.c \
+ exchange_api_kyc_check.c \
+ exchange_api_kyc_proof.c \
+ exchange_api_kyc_wallet.c \
exchange_api_link.c \
+ exchange_api_lookup_aml_decision.c \
+ exchange_api_lookup_aml_decisions.c \
+ exchange_api_management_add_partner.c \
+ exchange_api_management_auditor_disable.c \
+ exchange_api_management_auditor_enable.c \
+ exchange_api_management_drain_profits.c \
+ exchange_api_management_get_keys.c \
+ exchange_api_management_post_keys.c \
+ exchange_api_management_post_extensions.c \
+ exchange_api_management_revoke_denomination_key.c \
+ exchange_api_management_revoke_signing_key.c \
+ exchange_api_management_set_global_fee.c \
+ exchange_api_management_set_wire_fee.c \
+ exchange_api_management_update_aml_officer.c \
+ exchange_api_management_wire_disable.c \
+ exchange_api_management_wire_enable.c \
exchange_api_melt.c \
+ exchange_api_purse_create_with_deposit.c \
+ exchange_api_purse_create_with_merge.c \
+ exchange_api_purse_delete.c \
+ exchange_api_purse_deposit.c \
+ exchange_api_purse_merge.c \
+ exchange_api_purses_get.c \
exchange_api_recoup.c \
+ exchange_api_recoup_refresh.c \
exchange_api_refresh_common.c exchange_api_refresh_common.h \
exchange_api_refreshes_reveal.c \
exchange_api_refund.c \
+ exchange_api_reserves_attest.c \
+ exchange_api_reserves_close.c \
exchange_api_reserves_get.c \
- exchange_api_transfers_get.c \
- exchange_api_withdraw.c \
- exchange_api_wire.c
+ exchange_api_reserves_get_attestable.c \
+ exchange_api_reserves_history.c \
+ exchange_api_reserves_open.c \
+ exchange_api_stefan.c \
+ exchange_api_transfers_get.c
libtalerexchange_la_LIBADD = \
libtalerauditor.la \
$(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/kyclogic/libtalerkyclogic.la \
$(top_builddir)/src/curl/libtalercurl.la \
$(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
-lgnunetcurl \
-lgnunetjson \
-lgnunetutil \
-ljansson \
- $(LIBGNURLCURL_LIBS) \
+ -lcurl \
+ -lm \
$(XLIB)
libtalerauditor_la_LDFLAGS = \
@@ -53,9 +96,8 @@ libtalerauditor_la_LDFLAGS = \
-no-undefined
libtalerauditor_la_SOURCES = \
auditor_api_curl_defaults.c auditor_api_curl_defaults.h \
- auditor_api_handle.c auditor_api_handle.h \
- auditor_api_deposit_confirmation.c \
- auditor_api_exchanges.c
+ auditor_api_get_config.c \
+ auditor_api_deposit_confirmation.c
libtalerauditor_la_LIBADD = \
$(top_builddir)/src/curl/libtalercurl.la \
$(top_builddir)/src/json/libtalerjson.la \
@@ -64,5 +106,21 @@ libtalerauditor_la_LIBADD = \
-lgnunetjson \
-lgnunetutil \
-ljansson \
- $(LIBGNURLCURL_LIBS) \
+ -lcurl \
+ -lm \
$(XLIB)
+
+
+check_PROGRAMS = \
+ test_stefan
+
+TESTS = \
+ $(check_PROGRAMS)
+
+
+test_stefan_SOURCES = \
+ test_stefan.c
+test_stefan_LDADD = \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetutil
diff --git a/src/lib/auditor_api_curl_defaults.c b/src/lib/auditor_api_curl_defaults.c
index d8c6f619c..972f28ca6 100644
--- a/src/lib/auditor_api_curl_defaults.c
+++ b/src/lib/auditor_api_curl_defaults.c
@@ -19,15 +19,11 @@
* @brief curl easy handle defaults
* @author Florian Dold
*/
+#include "platform.h"
+#include "taler_curl_lib.h"
#include "auditor_api_curl_defaults.h"
-/**
- * Get a curl handle with the right defaults
- * for the exchange lib. In the future, we might manage a pool of connections here.
- *
- * @param url URL to query
- */
CURL *
TALER_AUDITOR_curl_easy_get_ (const char *url)
{
@@ -43,16 +39,14 @@ TALER_AUDITOR_curl_easy_get_ (const char *url)
curl_easy_setopt (eh,
CURLOPT_URL,
url));
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_FOLLOWLOCATION,
- 1L));
- /* limit MAXREDIRS to 5 as a simple security measure against
- a potential infinite loop caused by a malicious target */
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_MAXREDIRS,
- 5L));
+ TALER_curl_set_secure_redirect_policy (eh,
+ url);
+ /* Enable compression (using whatever curl likes), see
+ https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html */
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_ACCEPT_ENCODING,
+ ""));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_TCP_FASTOPEN,
diff --git a/src/lib/auditor_api_deposit_confirmation.c b/src/lib/auditor_api_deposit_confirmation.c
index e0135bb44..172a12ece 100644
--- a/src/lib/auditor_api_deposit_confirmation.c
+++ b/src/lib/auditor_api_deposit_confirmation.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 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
@@ -25,9 +25,10 @@
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_curl_lib.h>
+#include "taler_util.h"
+#include "taler_curl_lib.h"
#include "taler_json_lib.h"
#include "taler_auditor_service.h"
-#include "auditor_api_handle.h"
#include "taler_signatures.h"
#include "auditor_api_curl_defaults.h"
@@ -39,11 +40,6 @@ struct TALER_AUDITOR_DepositConfirmationHandle
{
/**
- * The connection to auditor this request handle will use
- */
- struct TALER_AUDITOR_Handle *auditor;
-
- /**
* The url for this request.
*/
char *url;
@@ -87,53 +83,64 @@ handle_deposit_confirmation_finished (void *cls,
{
const json_t *json = djson;
struct TALER_AUDITOR_DepositConfirmationHandle *dh = cls;
- enum TALER_ErrorCode ec;
+ struct TALER_AUDITOR_DepositConfirmationResponse dcr = {
+ .hr.reply = json,
+ .hr.http_status = (unsigned int) response_code
+ };
dh->job = NULL;
switch (response_code)
{
case 0:
- ec = TALER_EC_INVALID_RESPONSE;
+ dcr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
- ec = TALER_EC_NONE;
+ dcr.hr.ec = TALER_EC_NONE;
break;
case MHD_HTTP_BAD_REQUEST:
- ec = TALER_JSON_get_error_code (json);
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
/* This should never happen, either us or the auditor is buggy
(or API version conflict); just pass JSON reply to the application */
break;
case MHD_HTTP_FORBIDDEN:
- ec = TALER_JSON_get_error_code (json);
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, auditor says one of the signatures is
invalid; as we checked them, this should never happen, we
should pass the JSON reply to the application */
break;
case MHD_HTTP_NOT_FOUND:
- ec = TALER_JSON_get_error_code (json);
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */
break;
+ case MHD_HTTP_GONE:
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, auditor says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
- ec = TALER_JSON_get_error_code (json);
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
break;
default:
/* unexpected response code */
- ec = TALER_JSON_get_error_code (json);
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
+ "Unexpected response code %u/%d for auditor deposit confirmation\n",
(unsigned int) response_code,
- ec);
- GNUNET_break (0);
- response_code = 0;
+ dcr.hr.ec);
break;
}
dh->cb (dh->cb_cls,
- response_code,
- ec,
- json);
+ &dcr);
TALER_AUDITOR_deposit_confirmation_cancel (dh);
}
@@ -142,11 +149,14 @@ handle_deposit_confirmation_finished (void *cls,
* Verify signature information about the deposit-confirmation.
*
* @param h_wire hash of merchant wire details
+ * @param h_policy hash over the policy extension, if any
* @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor)
- * @param timestamp timestamp when the contract was finalized, must not be too far in the future
+ * @param exchange_timestamp timestamp when the deposit was received by the wallet
+ * @param wire_deadline by what time must the amount be wired to the merchant
* @param refund_deadline date until which the merchant can issue a refund to the customer via the auditor (can be zero if refunds are not allowed); must not be after the @a wire_deadline
* @param amount_without_fee the amount confirmed to be wired by the exchange to the merchant
- * @param coin_pub coin’s public key
+ * @param num_coins number of coins involved
+ * @param coin_sigs array of @a num_coins coin signatures
* @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
* @param exchange_sig the signature made with purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT
* @param exchange_pub the public key of the exchange that matches @a exchange_sig
@@ -157,67 +167,66 @@ handle_deposit_confirmation_finished (void *cls,
* @param master_sig master signature affirming validity of @a exchange_pub
* @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not
*/
-static int
-verify_signatures (const struct GNUNET_HashCode *h_wire,
- const struct GNUNET_HashCode *h_contract_terms,
- struct GNUNET_TIME_Absolute timestamp,
- struct GNUNET_TIME_Absolute refund_deadline,
- const struct TALER_Amount *amount_without_fee,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct GNUNET_TIME_Absolute ep_start,
- struct GNUNET_TIME_Absolute ep_expire,
- struct GNUNET_TIME_Absolute ep_end,
- const struct TALER_MasterSignatureP *master_sig)
+static enum GNUNET_GenericReturnValue
+verify_signatures (
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ struct GNUNET_TIME_Timestamp wire_deadline,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_Amount *amount_without_fee,
+ unsigned int num_coins,
+ const struct TALER_CoinSpendSignatureP *coin_sigs[
+ static num_coins],
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Timestamp ep_start,
+ struct GNUNET_TIME_Timestamp ep_expire,
+ struct GNUNET_TIME_Timestamp ep_end,
+ const struct TALER_MasterSignatureP *master_sig)
{
- struct TALER_DepositConfirmationPS dc;
- struct TALER_ExchangeSigningKeyValidityPS sv;
-
- dc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT);
- dc.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS));
- dc.h_contract_terms = *h_contract_terms;
- dc.h_wire = *h_wire;
- dc.timestamp = GNUNET_TIME_absolute_hton (timestamp);
- dc.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
- TALER_amount_hton (&dc.amount_without_fee,
- amount_without_fee);
- dc.coin_pub = *coin_pub;
- dc.merchant = *merchant_pub;
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT,
- &dc.purpose,
- &exchange_sig->eddsa_signature,
- &exchange_pub->eddsa_pub))
+ TALER_exchange_online_deposit_confirmation_verify (
+ h_contract_terms,
+ h_wire,
+ h_policy,
+ exchange_timestamp,
+ wire_deadline,
+ refund_deadline,
+ amount_without_fee,
+ num_coins,
+ coin_sigs,
+ merchant_pub,
+ exchange_pub,
+ exchange_sig))
{
GNUNET_break_op (0);
- TALER_LOG_WARNING ("Invalid signature on /deposit-confirmation request!\n");
+ TALER_LOG_WARNING (
+ "Invalid signature on /deposit-confirmation request!\n");
{
TALER_LOG_DEBUG ("... amount_without_fee was %s\n",
TALER_amount2s (amount_without_fee));
}
return GNUNET_SYSERR;
}
- sv.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY);
- sv.purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS));
- sv.master_public_key = *master_pub;
- sv.start = GNUNET_TIME_absolute_hton (ep_start);
- sv.expire = GNUNET_TIME_absolute_hton (ep_expire);
- sv.end = GNUNET_TIME_absolute_hton (ep_end);
- sv.signkey_pub = *exchange_pub;
+
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY,
- &sv.purpose,
- &master_sig->eddsa_signature,
- &master_pub->eddsa_pub))
+ TALER_exchange_offline_signkey_validity_verify (
+ exchange_pub,
+ ep_start,
+ ep_expire,
+ ep_end,
+ master_pub,
+ master_sig))
{
GNUNET_break (0);
TALER_LOG_WARNING ("Invalid signature on exchange signing key!\n");
return GNUNET_SYSERR;
}
- if (0 == GNUNET_TIME_absolute_get_remaining (ep_end).rel_value_us)
+ if (GNUNET_TIME_absolute_is_past (ep_end.abs_time))
{
GNUNET_break (0);
TALER_LOG_WARNING ("Exchange signing key is no longer valid!\n");
@@ -227,78 +236,54 @@ verify_signatures (const struct GNUNET_HashCode *h_wire,
}
-/**
- * Submit a deposit-confirmation permission to the auditor and get the
- * auditor's response. Note that while we return the response
- * verbatim to the caller for further processing, we do already verify
- * that the response is well-formed. If the auditor's reply is not
- * well-formed, we return an HTTP status code of zero to @a cb.
- *
- * We also verify that the @a exchange_sig is valid for this deposit-confirmation
- * request, and that the @a master_sig is a valid signature for @a
- * exchange_pub. Also, the @a auditor must be ready to operate (i.e. have
- * finished processing the /version reply). If either check fails, we do
- * NOT initiate the transaction with the auditor and instead return NULL.
- *
- * @param auditor the auditor handle; the auditor must be ready to operate
- * @param h_wire hash of merchant wire details
- * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor)
- * @param timestamp timestamp when the contract was finalized, must not be too far in the future
- * @param refund_deadline date until which the merchant can issue a refund to the customer via the auditor (can be zero if refunds are not allowed); must not be after the @a wire_deadline
- * @param amount_without_fee the amount confirmed to be wired by the exchange to the merchant
- * @param coin_pub coin’s public key
- * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
- * @param exchange_sig the signature made with purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT
- * @param exchange_pub the public key of the exchange that matches @a exchange_sig
- * @param master_pub master public key of the exchange
- * @param ep_start when does @a exchange_pub validity start
- * @param ep_expire when does @a exchange_pub usage end
- * @param ep_end when does @a exchange_pub legal validity end
- * @param master_sig master signature affirming validity of @a exchange_pub
- * @param cb the callback to call when a reply for this request is available
- * @param cb_cls closure for the above callback
- * @return a handle for this request; NULL if the inputs are invalid (i.e.
- * signatures fail to verify). In this case, the callback is not called.
- */
struct TALER_AUDITOR_DepositConfirmationHandle *
TALER_AUDITOR_deposit_confirmation (
- struct TALER_AUDITOR_Handle *auditor,
- const struct GNUNET_HashCode *h_wire,
- const struct GNUNET_HashCode *h_contract_terms,
- struct GNUNET_TIME_Absolute timestamp,
- struct GNUNET_TIME_Absolute refund_deadline,
- const struct TALER_Amount *amount_without_fee,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ struct GNUNET_TIME_Timestamp wire_deadline,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_Amount *total_without_fee,
+ unsigned int num_coins,
+ const struct TALER_CoinSpendPublicKeyP *coin_pubs[
+ static num_coins],
+ const struct TALER_CoinSpendSignatureP *coin_sigs[
+ static num_coins],
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_ExchangePublicKeyP *exchange_pub,
const struct TALER_ExchangeSignatureP *exchange_sig,
const struct TALER_MasterPublicKeyP *master_pub,
- struct GNUNET_TIME_Absolute ep_start,
- struct GNUNET_TIME_Absolute ep_expire,
- struct GNUNET_TIME_Absolute ep_end,
+ struct GNUNET_TIME_Timestamp ep_start,
+ struct GNUNET_TIME_Timestamp ep_expire,
+ struct GNUNET_TIME_Timestamp ep_end,
const struct TALER_MasterSignatureP *master_sig,
TALER_AUDITOR_DepositConfirmationResultCallback cb,
void *cb_cls)
{
struct TALER_AUDITOR_DepositConfirmationHandle *dh;
- struct GNUNET_CURL_Context *ctx;
json_t *deposit_confirmation_obj;
CURL *eh;
+ json_t *jcoin_sigs;
+ json_t *jcoin_pubs;
- (void) GNUNET_TIME_round_abs (&timestamp);
- (void) GNUNET_TIME_round_abs (&refund_deadline);
- (void) GNUNET_TIME_round_abs (&ep_start);
- (void) GNUNET_TIME_round_abs (&ep_expire);
- (void) GNUNET_TIME_round_abs (&ep_end);
- GNUNET_assert (GNUNET_YES ==
- TALER_AUDITOR_handle_is_ready_ (auditor));
+ if (0 == num_coins)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
if (GNUNET_OK !=
verify_signatures (h_wire,
+ h_policy,
h_contract_terms,
- timestamp,
+ exchange_timestamp,
+ wire_deadline,
refund_deadline,
- amount_without_fee,
- coin_pub,
+ total_without_fee,
+ num_coins,
+ coin_sigs,
merchant_pub,
exchange_pub,
exchange_sig,
@@ -311,46 +296,70 @@ TALER_AUDITOR_deposit_confirmation (
GNUNET_break_op (0);
return NULL;
}
-
- deposit_confirmation_obj
- = json_pack ("{s:o, s:o," /* h_wire, h_contract_terms */
- " s:o, s:o," /* timestamp, refund_deadline */
- " s:o, s:o," /* amount_without_fees, coin_pub */
- " s:o, s:o," /* merchant_pub, exchange_sig */
- " s:o, s:o," /* master_pub, ep_start */
- " s:o, s:o," /* ep_expire, ep_end */
- " s:o, s:o}", /* master_sig, exchange_pub */
- "h_wire", GNUNET_JSON_from_data_auto (h_wire),
- "h_contract_terms", GNUNET_JSON_from_data_auto (
- h_contract_terms),
- "timestamp", GNUNET_JSON_from_time_abs (timestamp),
- "refund_deadline", GNUNET_JSON_from_time_abs (refund_deadline),
- "amount_without_fee", TALER_JSON_from_amount (
- amount_without_fee),
- "coin_pub", GNUNET_JSON_from_data_auto (coin_pub),
- "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub),
- "exchange_sig", GNUNET_JSON_from_data_auto (exchange_sig),
- "master_pub", GNUNET_JSON_from_data_auto (master_pub),
- "ep_start", GNUNET_JSON_from_time_abs (ep_start),
- "ep_expire", GNUNET_JSON_from_time_abs (ep_expire),
- "ep_end", GNUNET_JSON_from_time_abs (ep_end),
- "master_sig", GNUNET_JSON_from_data_auto (master_sig),
- "exchange_pub", GNUNET_JSON_from_data_auto (exchange_pub));
-
- if (NULL == deposit_confirmation_obj)
+ jcoin_sigs = json_array ();
+ GNUNET_assert (NULL != jcoin_sigs);
+ jcoin_pubs = json_array ();
+ GNUNET_assert (NULL != jcoin_pubs);
+ for (unsigned int i = 0; i<num_coins; i++)
{
- GNUNET_break (0);
- return NULL;
+ GNUNET_assert (0 ==
+ json_array_append_new (jcoin_sigs,
+ GNUNET_JSON_from_data_auto (
+ coin_sigs[i])));
+ GNUNET_assert (0 ==
+ json_array_append_new (jcoin_pubs,
+ GNUNET_JSON_from_data_auto (
+ coin_pubs[i])));
}
-
+ deposit_confirmation_obj
+ = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_wire",
+ h_wire),
+ GNUNET_JSON_pack_data_auto ("h_policy",
+ h_policy),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ h_contract_terms),
+ GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+ exchange_timestamp),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("refund_deadline",
+ refund_deadline)),
+ GNUNET_JSON_pack_timestamp ("wire_deadline",
+ wire_deadline),
+ TALER_JSON_pack_amount ("total_without_fee",
+ total_without_fee),
+ GNUNET_JSON_pack_array_steal ("coin_pubs",
+ jcoin_pubs),
+ GNUNET_JSON_pack_array_steal ("coin_sigs",
+ jcoin_sigs),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ merchant_pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ exchange_sig),
+ GNUNET_JSON_pack_data_auto ("master_pub",
+ master_pub),
+ GNUNET_JSON_pack_timestamp ("ep_start",
+ ep_start),
+ GNUNET_JSON_pack_timestamp ("ep_expire",
+ ep_expire),
+ GNUNET_JSON_pack_timestamp ("ep_end",
+ ep_end),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ exchange_pub));
dh = GNUNET_new (struct TALER_AUDITOR_DepositConfirmationHandle);
- dh->auditor = auditor;
dh->cb = cb;
dh->cb_cls = cb_cls;
- dh->url = TALER_AUDITOR_path_to_url_ (auditor,
- "/deposit-confirmation");
+ dh->url = TALER_url_join (url,
+ "deposit-confirmation",
+ NULL);
+ if (NULL == dh->url)
+ {
+ GNUNET_free (dh);
+ return NULL;
+ }
eh = TALER_AUDITOR_curl_easy_get_ (dh->url);
-
if ( (NULL == eh) ||
(CURLE_OK !=
curl_easy_setopt (eh,
@@ -373,22 +382,25 @@ TALER_AUDITOR_deposit_confirmation (
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for deposit-confirmation: `%s'\n",
dh->url);
- ctx = TALER_AUDITOR_handle_to_context_ (auditor);
dh->job = GNUNET_CURL_job_add2 (ctx,
eh,
dh->ctx.headers,
&handle_deposit_confirmation_finished,
dh);
+ {
+ /* Disable 100 continue processing */
+ struct curl_slist *x_headers;
+
+ x_headers = curl_slist_append (NULL,
+ "Expect:");
+ GNUNET_CURL_extend_headers (dh->job,
+ x_headers);
+ curl_slist_free_all (x_headers);
+ }
return dh;
}
-/**
- * Cancel a deposit-confirmation permission request. This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param deposit_confirmation the deposit-confirmation permission request handle
- */
void
TALER_AUDITOR_deposit_confirmation_cancel (
struct TALER_AUDITOR_DepositConfirmationHandle *deposit_confirmation)
diff --git a/src/lib/auditor_api_exchanges.c b/src/lib/auditor_api_exchanges.c
deleted file mode 100644
index 4e23267fd..000000000
--- a/src/lib/auditor_api_exchanges.c
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2018 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 lib/auditor_api_exchanges.c
- * @brief Implementation of the /exchanges request of the auditor's HTTP API
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_json_lib.h"
-#include "taler_auditor_service.h"
-#include "auditor_api_handle.h"
-#include "taler_signatures.h"
-#include "auditor_api_curl_defaults.h"
-
-/**
- * How many exchanges do we allow a single auditor to
- * audit at most?
- */
-#define MAX_EXCHANGES 1024
-
-
-/**
- * @brief A ListExchanges Handle
- */
-struct TALER_AUDITOR_ListExchangesHandle
-{
-
- /**
- * The connection to auditor this request handle will use
- */
- struct TALER_AUDITOR_Handle *auditor;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_AUDITOR_ListExchangesResultCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP /deposit-confirmation request.
- *
- * @param cls the `struct TALER_AUDITOR_ListExchangesHandle`
- * @param response_code HTTP response code, 0 on error
- * @param djson parsed JSON result, NULL on error
- */
-static void
-handle_exchanges_finished (void *cls,
- long response_code,
- const void *djson)
-{
- const json_t *json = djson;
- const json_t *ja;
- unsigned int ja_len;
- struct TALER_AUDITOR_ListExchangesHandle *leh = cls;
- enum TALER_ErrorCode ec;
-
- leh->job = NULL;
- switch (response_code)
- {
- case 0:
- ec = TALER_EC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- ja = json_object_get (json,
- "exchanges");
- if ( (NULL == ja) ||
- (! json_is_array (ja)) )
- {
- GNUNET_break (0);
- ec = TALER_EC_AUDITOR_EXCHANGES_REPLY_MALFORMED;
- response_code = 0;
- break;
- }
-
- ja_len = json_array_size (ja);
- if (ja_len > MAX_EXCHANGES)
- {
- GNUNET_break (0);
- ec = TALER_EC_AUDITOR_EXCHANGES_REPLY_MALFORMED;
- response_code = 0;
- break;
- }
- {
- struct TALER_AUDITOR_ExchangeInfo ei[ja_len];
- int ok;
-
- ok = GNUNET_YES;
- for (unsigned int i = 0; i<ja_len; i++)
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("master_pub", &ei[i].master_pub),
- GNUNET_JSON_spec_string ("exchange_url", &ei[i].exchange_url),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json_array_get (ja,
- i),
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ok = GNUNET_NO;
- ec = TALER_EC_AUDITOR_EXCHANGES_REPLY_MALFORMED;
- break;
- }
- }
- if (GNUNET_YES != ok)
- break;
- leh->cb (leh->cb_cls,
- response_code,
- TALER_EC_NONE,
- ja_len,
- ei,
- json);
- TALER_AUDITOR_list_exchanges_cancel (leh);
- return;
- }
- case MHD_HTTP_BAD_REQUEST:
- /* This should never happen, either us or the auditor is buggy
- (or API version conflict); just pass JSON reply to the application */
- ec = TALER_JSON_get_error_code (json);
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (json);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- ec = TALER_JSON_get_error_code (json);
- break;
- default:
- /* unexpected response code */
- ec = TALER_JSON_get_error_code (json);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) ec);
- GNUNET_break (0);
- response_code = 0;
- break;
- }
- if (NULL != leh->cb)
- leh->cb (leh->cb_cls,
- response_code,
- TALER_JSON_get_error_code (json),
- 0,
- NULL,
- json);
- TALER_AUDITOR_list_exchanges_cancel (leh);
-}
-
-
-/**
- * Submit an /exchanges request to the auditor and get the
- * auditor's response. If the auditor's reply is not
- * well-formed, we return an HTTP status code of zero to @a cb.
- *
- * @param auditor the auditor handle; the auditor must be ready to operate
- * @param cb the callback to call when a reply for this request is available
- * @param cb_cls closure for the above callback
- * @return a handle for this request; NULL if the inputs are invalid (i.e.
- * signatures fail to verify). In this case, the callback is not called.
- */
-struct TALER_AUDITOR_ListExchangesHandle *
-TALER_AUDITOR_list_exchanges (struct TALER_AUDITOR_Handle *auditor,
- TALER_AUDITOR_ListExchangesResultCallback cb,
- void *cb_cls)
-{
- struct TALER_AUDITOR_ListExchangesHandle *leh;
- struct GNUNET_CURL_Context *ctx;
- CURL *eh;
-
- GNUNET_assert (GNUNET_YES ==
- TALER_AUDITOR_handle_is_ready_ (auditor));
-
- leh = GNUNET_new (struct TALER_AUDITOR_ListExchangesHandle);
- leh->auditor = auditor;
- leh->cb = cb;
- leh->cb_cls = cb_cls;
- leh->url = TALER_AUDITOR_path_to_url_ (auditor, "/exchanges");
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "URL for list-exchanges: `%s'\n",
- leh->url);
- eh = TALER_AUDITOR_curl_easy_get_ (leh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (leh->url);
- GNUNET_free (leh);
- return NULL;
- }
- ctx = TALER_AUDITOR_handle_to_context_ (auditor);
- leh->job = GNUNET_CURL_job_add (ctx,
- eh,
- GNUNET_NO,
- &handle_exchanges_finished,
- leh);
- return leh;
-}
-
-
-/**
- * Cancel a list exchanges request. This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param leh the list exchanges request handle
- */
-void
-TALER_AUDITOR_list_exchanges_cancel (struct
- TALER_AUDITOR_ListExchangesHandle *leh)
-{
- if (NULL != leh->job)
- {
- GNUNET_CURL_job_cancel (leh->job);
- leh->job = NULL;
- }
- GNUNET_free (leh->url);
- GNUNET_free (leh);
-}
-
-
-/* end of auditor_api_exchanges.c */
diff --git a/src/lib/auditor_api_get_config.c b/src/lib/auditor_api_get_config.c
new file mode 100644
index 000000000..1e8e0bb30
--- /dev/null
+++ b/src/lib/auditor_api_get_config.c
@@ -0,0 +1,278 @@
+/*
+ This file is part of TALER
+ 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
+ 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 lib/auditor_api_get_config.c
+ * @brief Implementation of /config for the auditor's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_auditor_service.h"
+#include "taler_signatures.h"
+#include "auditor_api_curl_defaults.h"
+
+
+/**
+ * Which revision of the Taler auditor protocol is implemented
+ * by this library? Used to determine compatibility.
+ */
+#define TALER_PROTOCOL_CURRENT 1
+
+/**
+ * How many revisions back are we compatible to?
+ */
+#define TALER_PROTOCOL_AGE 0
+
+
+/**
+ * Log error related to CURL operations.
+ *
+ * @param type log level
+ * @param function which function failed to run
+ * @param code what was the curl error code
+ */
+#define CURL_STRERROR(type, function, code) \
+ GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \
+ function, __FILE__, __LINE__, curl_easy_strerror (code));
+
+
+/**
+ * Handle for the get config request.
+ */
+struct TALER_AUDITOR_GetConfigHandle
+{
+ /**
+ * The context of this handle
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Function to call with the auditor's certification data,
+ * NULL if this has already been done.
+ */
+ TALER_AUDITOR_ConfigCallback config_cb;
+
+ /**
+ * Closure to pass to @e config_cb.
+ */
+ void *config_cb_cls;
+
+ /**
+ * Data for the request to get the /config of a auditor,
+ * NULL once we are past stage #MHS_INIT.
+ */
+ struct GNUNET_CURL_Job *vr;
+
+ /**
+ * The url for the @e vr job.
+ */
+ char *vr_url;
+
+};
+
+
+/* ***************** Internal /config fetching ************* */
+
+/**
+ * Decode the JSON in @a resp_obj from the /config response and store the data
+ * in the @a key_data.
+ *
+ * @param[in] resp_obj JSON object to parse
+ * @param[in,out] vi where to store the results we decoded
+ * @param[out] vc where to store config compatibility data
+ * @return #TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+decode_config_json (const json_t *resp_obj,
+ struct TALER_AUDITOR_ConfigInformation *vi,
+ enum TALER_AUDITOR_VersionCompatibility *vc)
+{
+ struct TALER_JSON_ProtocolVersion pv;
+ const char *ver;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_version ("version",
+ &pv),
+ GNUNET_JSON_spec_string ("version",
+ &ver),
+ GNUNET_JSON_spec_fixed_auto ("exchange_master_public_key",
+ &vi->exchange_master_public_key),
+ GNUNET_JSON_spec_fixed_auto ("auditor_public_key",
+ &vi->auditor_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (JSON_OBJECT != json_typeof (resp_obj))
+ {
+ GNUNET_break_op (0);
+ return TALER_EC_GENERIC_JSON_INVALID;
+ }
+ /* check the config */
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (resp_obj,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return TALER_EC_GENERIC_JSON_INVALID;
+ }
+ vi->version = ver;
+ *vc = TALER_AUDITOR_VC_MATCH;
+ if (TALER_PROTOCOL_CURRENT < pv.current)
+ {
+ *vc |= TALER_AUDITOR_VC_NEWER;
+ if (TALER_PROTOCOL_CURRENT < pv.current - pv.age)
+ *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
+ }
+ if (TALER_PROTOCOL_CURRENT > pv.current)
+ {
+ *vc |= TALER_AUDITOR_VC_OLDER;
+ if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > pv.current)
+ *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
+ }
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Callback used when downloading the reply to a /config request
+ * is complete.
+ *
+ * @param cls the `struct TALER_AUDITOR_GetConfigHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param gresp_obj parsed JSON result, NULL on error, must be a `const json_t *`
+ */
+static void
+config_completed_cb (void *cls,
+ long response_code,
+ const void *gresp_obj)
+{
+ struct TALER_AUDITOR_GetConfigHandle *auditor = cls;
+ const json_t *resp_obj = gresp_obj;
+ struct TALER_AUDITOR_ConfigResponse vr = {
+ .hr.reply = resp_obj,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ auditor->vr = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received config from URL `%s' with status %ld.\n",
+ auditor->vr_url,
+ response_code);
+ switch (response_code)
+ {
+ case 0:
+ GNUNET_break_op (0);
+ vr.hr.ec = TALER_EC_INVALID;
+ break;
+ case MHD_HTTP_OK:
+ if (NULL == resp_obj)
+ {
+ GNUNET_break_op (0);
+ vr.hr.http_status = 0;
+ vr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ vr.hr.ec = decode_config_json (resp_obj,
+ &vr.details.ok.vi,
+ &vr.details.ok.compat);
+ if (TALER_EC_NONE != vr.hr.ec)
+ {
+ GNUNET_break_op (0);
+ vr.hr.http_status = 0;
+ break;
+ }
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ vr.hr.ec = TALER_JSON_get_error_code (resp_obj);
+ vr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
+ break;
+ default:
+ vr.hr.ec = TALER_JSON_get_error_code (resp_obj);
+ vr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) vr.hr.ec);
+ break;
+ }
+ auditor->config_cb (auditor->config_cb_cls,
+ &vr);
+ TALER_AUDITOR_get_config_cancel (auditor);
+}
+
+
+struct TALER_AUDITOR_GetConfigHandle *
+TALER_AUDITOR_get_config (struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ TALER_AUDITOR_ConfigCallback config_cb,
+ void *config_cb_cls)
+{
+ struct TALER_AUDITOR_GetConfigHandle *auditor;
+ CURL *eh;
+
+ auditor = GNUNET_new (struct TALER_AUDITOR_GetConfigHandle);
+ auditor->config_cb = config_cb;
+ auditor->config_cb_cls = config_cb_cls;
+ auditor->ctx = ctx;
+ auditor->vr_url = TALER_url_join (url,
+ "config",
+ NULL);
+ if (NULL == auditor->vr_url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (auditor);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requesting auditor config with URL `%s'.\n",
+ auditor->vr_url);
+ eh = TALER_AUDITOR_curl_easy_get_ (auditor->vr_url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ TALER_AUDITOR_get_config_cancel (auditor);
+ return NULL;
+ }
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT,
+ (long) 300));
+ auditor->vr = GNUNET_CURL_job_add (auditor->ctx,
+ eh,
+ &config_completed_cb,
+ auditor);
+ return auditor;
+}
+
+
+void
+TALER_AUDITOR_get_config_cancel (struct TALER_AUDITOR_GetConfigHandle *auditor)
+{
+ if (NULL != auditor->vr)
+ {
+ GNUNET_CURL_job_cancel (auditor->vr);
+ auditor->vr = NULL;
+ }
+ GNUNET_free (auditor->vr_url);
+ GNUNET_free (auditor);
+}
+
+
+/* end of auditor_api_get_config.c */
diff --git a/src/lib/auditor_api_handle.c b/src/lib/auditor_api_handle.c
deleted file mode 100644
index 85f42c3f6..000000000
--- a/src/lib/auditor_api_handle.c
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2018 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 lib/auditor_api_handle.c
- * @brief Implementation of the "handle" component of the auditor's HTTP API
- * @author Sree Harsha Totakura <sreeharsha@totakura.in>
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <microhttpd.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_json_lib.h"
-#include "taler_auditor_service.h"
-#include "taler_signatures.h"
-#include "auditor_api_handle.h"
-#include "auditor_api_curl_defaults.h"
-#include "backoff.h"
-
-/**
- * Which revision of the Taler auditor protocol is implemented
- * by this library? Used to determine compatibility.
- */
-#define TALER_PROTOCOL_CURRENT 0
-
-/**
- * How many revisions back are we compatible to?
- */
-#define TALER_PROTOCOL_AGE 0
-
-
-/**
- * Log error related to CURL operations.
- *
- * @param type log level
- * @param function which function failed to run
- * @param code what was the curl error code
- */
-#define CURL_STRERROR(type, function, code) \
- GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \
- function, __FILE__, __LINE__, curl_easy_strerror (code));
-
-/**
- * Stages of initialization for the `struct TALER_AUDITOR_Handle`
- */
-enum AuditorHandleState
-{
- /**
- * Just allocated.
- */
- MHS_INIT = 0,
-
- /**
- * Obtained the auditor's versioning data and version.
- */
- MHS_VERSION = 1,
-
- /**
- * Failed to initialize (fatal).
- */
- MHS_FAILED = 2
-};
-
-
-/**
- * Data for the request to get the /version of a auditor.
- */
-struct VersionRequest;
-
-
-/**
- * Handle to the auditor
- */
-struct TALER_AUDITOR_Handle
-{
- /**
- * The context of this handle
- */
- struct GNUNET_CURL_Context *ctx;
-
- /**
- * The URL of the auditor (i.e. "http://auditor.taler.net/")
- */
- char *url;
-
- /**
- * Function to call with the auditor's certification data,
- * NULL if this has already been done.
- */
- TALER_AUDITOR_VersionCallback version_cb;
-
- /**
- * Closure to pass to @e version_cb.
- */
- void *version_cb_cls;
-
- /**
- * Data for the request to get the /version of a auditor,
- * NULL once we are past stage #MHS_INIT.
- */
- struct VersionRequest *vr;
-
- /**
- * Task for retrying /version request.
- */
- struct GNUNET_SCHEDULER_Task *retry_task;
-
- /**
- * /version data of the auditor, only valid if
- * @e handshake_complete is past stage #MHS_VERSION.
- */
- struct TALER_AUDITOR_VersionInformation vi;
-
- /**
- * Retry /version frequency.
- */
- struct GNUNET_TIME_Relative retry_delay;
-
- /**
- * Stage of the auditor's initialization routines.
- */
- enum AuditorHandleState state;
-
-};
-
-
-/* ***************** Internal /version fetching ************* */
-
-/**
- * Data for the request to get the /version of a auditor.
- */
-struct VersionRequest
-{
- /**
- * The connection to auditor this request handle will use
- */
- struct TALER_AUDITOR_Handle *auditor;
-
- /**
- * The url for this handle
- */
- char *url;
-
- /**
- * Entry for this request with the `struct GNUNET_CURL_Context`.
- */
- struct GNUNET_CURL_Job *job;
-
-};
-
-
-/**
- * Release memory occupied by a version request.
- * Note that this does not cancel the request
- * itself.
- *
- * @param vr request to free
- */
-static void
-free_version_request (struct VersionRequest *vr)
-{
- GNUNET_free (vr->url);
- GNUNET_free (vr);
-}
-
-
-/**
- * Free version data object.
- *
- * @param vi data to free (pointer itself excluded)
- */
-static void
-free_version_info (struct TALER_AUDITOR_VersionInformation *vi)
-{
- GNUNET_free_non_null (vi->version);
- vi->version = NULL;
-}
-
-
-/**
- * Decode the JSON in @a resp_obj from the /version response and store the data
- * in the @a key_data.
- *
- * @param[in] resp_obj JSON object to parse
- * @param[out] vi where to store the results we decoded
- * @param[out] vc where to store version compatibility data
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on error (malformed JSON)
- */
-static int
-decode_version_json (const json_t *resp_obj,
- struct TALER_AUDITOR_VersionInformation *vi,
- enum TALER_AUDITOR_VersionCompatibility *vc)
-{
- unsigned int age;
- unsigned int revision;
- unsigned int current;
- const char *ver;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("version",
- &ver),
- GNUNET_JSON_spec_fixed_auto ("auditor_public_key",
- &vi->auditor_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (JSON_OBJECT != json_typeof (resp_obj))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* check the version */
- if (GNUNET_OK !=
- GNUNET_JSON_parse (resp_obj,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (3 != sscanf (ver,
- "%u:%u:%u",
- &current,
- &revision,
- &age))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- vi->version = GNUNET_strdup (ver);
- *vc = TALER_AUDITOR_VC_MATCH;
- if (TALER_PROTOCOL_CURRENT < current)
- {
- *vc |= TALER_AUDITOR_VC_NEWER;
- if (TALER_PROTOCOL_CURRENT < current - age)
- *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
- }
- if (TALER_PROTOCOL_CURRENT > current)
- {
- *vc |= TALER_AUDITOR_VC_OLDER;
- if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current)
- *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Initiate download of /version from the auditor.
- *
- * @param cls auditor where to download /version from
- */
-static void
-request_version (void *cls);
-
-
-/**
- * Callback used when downloading the reply to a /version request
- * is complete.
- *
- * @param cls the `struct VersionRequest`
- * @param response_code HTTP response code, 0 on error
- * @param gresp_obj parsed JSON result, NULL on error, must be a `const json_t *`
- */
-static void
-version_completed_cb (void *cls,
- long response_code,
- const void *gresp_obj)
-{
- const json_t *resp_obj = gresp_obj;
- struct VersionRequest *vr = cls;
- struct TALER_AUDITOR_Handle *auditor = vr->auditor;
- enum TALER_AUDITOR_VersionCompatibility vc;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received version from URL `%s' with status %ld.\n",
- vr->url,
- response_code);
- vc = TALER_AUDITOR_VC_PROTOCOL_ERROR;
- switch (response_code)
- {
- case 0:
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- free_version_request (vr);
- auditor->vr = NULL;
- GNUNET_assert (NULL == auditor->retry_task);
- auditor->retry_delay = EXCHANGE_LIB_BACKOFF (auditor->retry_delay);
- auditor->retry_task = GNUNET_SCHEDULER_add_delayed (auditor->retry_delay,
- &request_version,
- auditor);
- return;
- case MHD_HTTP_OK:
- if (NULL == resp_obj)
- {
- GNUNET_break_op (0);
- TALER_LOG_WARNING ("NULL body for a 200-OK /version\n");
- response_code = 0;
- break;
- }
- if (GNUNET_OK !=
- decode_version_json (resp_obj,
- &auditor->vi,
- &vc))
- {
- GNUNET_break_op (0);
- response_code = 0;
- break;
- }
- auditor->retry_delay = GNUNET_TIME_UNIT_ZERO; /* restart quickly */
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u\n",
- (unsigned int) response_code);
- break;
- }
- if (MHD_HTTP_OK != response_code)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "/version failed for auditor %p: %u!\n",
- auditor,
- (unsigned int) response_code);
- auditor->vr = NULL;
- free_version_request (vr);
- auditor->state = MHS_FAILED;
- free_version_info (&auditor->vi);
- /* notify application that we failed */
- auditor->version_cb (auditor->version_cb_cls,
- NULL,
- vc);
- return;
- }
-
- auditor->vr = NULL;
- free_version_request (vr);
- TALER_LOG_DEBUG ("Switching auditor state to 'version'\n");
- auditor->state = MHS_VERSION;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Auditor %p is now READY!\n",
- auditor);
- /* notify application about the key information */
- auditor->version_cb (auditor->version_cb_cls,
- &auditor->vi,
- vc);
-}
-
-
-/* ********************* library internal API ********* */
-
-
-/**
- * Get the context of a auditor.
- *
- * @param h the auditor handle to query
- * @return ctx context to execute jobs in
- */
-struct GNUNET_CURL_Context *
-TALER_AUDITOR_handle_to_context_ (struct TALER_AUDITOR_Handle *h)
-{
- return h->ctx;
-}
-
-
-/**
- * Check if the handle is ready to process requests.
- *
- * @param h the auditor handle to query
- * @return #GNUNET_YES if we are ready, #GNUNET_NO if not
- */
-int
-TALER_AUDITOR_handle_is_ready_ (struct TALER_AUDITOR_Handle *h)
-{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Checking if auditor %p (%s) is now ready: %s\n",
- h,
- h->url,
- (MHD_VERSION == h->state) ? "yes" : "no");
- return (MHS_VERSION == h->state) ? GNUNET_YES : GNUNET_NO;
-}
-
-
-/**
- * Obtain the URL to use for an API request.
- *
- * @param h handle for the auditor
- * @param path Taler API path (i.e. "/deposit-confirmation")
- * @return the full URL to use with cURL
- */
-char *
-TALER_AUDITOR_path_to_url_ (struct TALER_AUDITOR_Handle *h,
- const char *path)
-{
- char *ret;
- GNUNET_assert ('/' == path[0]);
- ret = TALER_url_join (h->url,
- path + 1,
- NULL);
- GNUNET_assert (NULL != ret);
- return ret;
-}
-
-
-/* ********************* public API ******************* */
-
-
-/**
- * Initialise a connection to the auditor. Will connect to the
- * auditor and obtain information about the auditor's master public
- * key and the auditor's auditor. The respective information will
- * be passed to the @a version_cb once available, and all future
- * interactions with the auditor will be checked to be signed
- * (where appropriate) by the respective master key.
- *
- * @param ctx the context
- * @param url HTTP base URL for the auditor
- * @param version_cb function to call with the
- * auditor's version information
- * @param version_cb_cls closure for @a version_cb
- * @return the auditor handle; NULL upon error
- */
-struct TALER_AUDITOR_Handle *
-TALER_AUDITOR_connect (struct GNUNET_CURL_Context *ctx,
- const char *url,
- TALER_AUDITOR_VersionCallback version_cb,
- void *version_cb_cls)
-{
- struct TALER_AUDITOR_Handle *auditor;
-
- /* Disable 100 continue processing */
- GNUNET_break (GNUNET_OK ==
- GNUNET_CURL_append_header (ctx,
- "Expect:"));
- auditor = GNUNET_new (struct TALER_AUDITOR_Handle);
- auditor->retry_delay = GNUNET_TIME_UNIT_SECONDS; /* start slowly */
- auditor->ctx = ctx;
- auditor->url = GNUNET_strdup (url);
- auditor->version_cb = version_cb;
- auditor->version_cb_cls = version_cb_cls;
- auditor->retry_task = GNUNET_SCHEDULER_add_now (&request_version,
- auditor);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Connecting to auditor at URL `%s' (%p).\n",
- url,
- auditor);
- return auditor;
-}
-
-
-/**
- * Initiate download of /version from the auditor.
- *
- * @param cls auditor where to download /version from
- */
-static void
-request_version (void *cls)
-{
- struct TALER_AUDITOR_Handle *auditor = cls;
- struct VersionRequest *vr;
- CURL *eh;
-
- auditor->retry_task = NULL;
- GNUNET_assert (NULL == auditor->vr);
- vr = GNUNET_new (struct VersionRequest);
- vr->auditor = auditor;
- vr->url = TALER_AUDITOR_path_to_url_ (auditor,
- "/version");
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Requesting auditor version with URL `%s'.\n",
- vr->url);
- eh = TALER_AUDITOR_curl_easy_get_ (vr->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- auditor->retry_delay = EXCHANGE_LIB_BACKOFF (auditor->retry_delay);
- auditor->retry_task = GNUNET_SCHEDULER_add_delayed (auditor->retry_delay,
- &request_version,
- auditor);
- return;
- }
- GNUNET_break (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_TIMEOUT,
- (long) 300));
- vr->job = GNUNET_CURL_job_add (auditor->ctx,
- eh,
- GNUNET_NO,
- &version_completed_cb,
- vr);
- auditor->vr = vr;
-}
-
-
-/**
- * Disconnect from the auditor
- *
- * @param auditor the auditor handle
- */
-void
-TALER_AUDITOR_disconnect (struct TALER_AUDITOR_Handle *auditor)
-{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Disconnecting from auditor at URL `%s' (%p).\n",
- auditor->url,
- auditor);
- if (NULL != auditor->vr)
- {
- GNUNET_CURL_job_cancel (auditor->vr->job);
- free_version_request (auditor->vr);
- auditor->vr = NULL;
- }
- free_version_info (&auditor->vi);
- if (NULL != auditor->retry_task)
- {
- GNUNET_SCHEDULER_cancel (auditor->retry_task);
- auditor->retry_task = NULL;
- }
- GNUNET_free (auditor->url);
- GNUNET_free (auditor);
-}
-
-
-/* end of auditor_api_handle.c */
diff --git a/src/lib/auditor_api_handle.h b/src/lib/auditor_api_handle.h
deleted file mode 100644
index 7ff5bfcdb..000000000
--- a/src/lib/auditor_api_handle.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014, 2015 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 lib/auditor_api_handle.h
- * @brief Internal interface to the handle part of the auditor's HTTP API
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_auditor_service.h"
-#include "taler_curl_lib.h"
-
-/**
- * Get the context of a auditor.
- *
- * @param h the auditor handle to query
- * @return ctx context to execute jobs in
- */
-struct GNUNET_CURL_Context *
-TALER_AUDITOR_handle_to_context_ (struct TALER_AUDITOR_Handle *h);
-
-
-/**
- * Check if the handle is ready to process requests.
- *
- * @param h the auditor handle to query
- * @return #GNUNET_YES if we are ready, #GNUNET_NO if not
- */
-int
-TALER_AUDITOR_handle_is_ready_ (struct TALER_AUDITOR_Handle *h);
-
-
-/**
- * Obtain the URL to use for an API request.
- *
- * @param h the auditor handle to query
- * @param path Taler API path (i.e. "/deposit-confirmation")
- * @return the full URL to use with cURL
- */
-char *
-TALER_AUDITOR_path_to_url_ (struct TALER_AUDITOR_Handle *h,
- const char *path);
-
-
-/* end of auditor_api_handle.h */
diff --git a/src/lib/exchange_api_add_aml_decision.c b/src/lib/exchange_api_add_aml_decision.c
new file mode 100644
index 000000000..342e1e3dc
--- /dev/null
+++ b/src/lib/exchange_api_add_aml_decision.c
@@ -0,0 +1,246 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_add_aml_decision.c
+ * @brief functions to add an AML decision by an AML officer
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_AddAmlDecision
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_AddAmlDecisionCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /aml/$OFFICER_PUB/decision request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AddAmlDecision *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_add_aml_decision_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_AddAmlDecision *wh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_AddAmlDecisionResponse adr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ adr.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_CONFLICT:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange AML decision\n",
+ (unsigned int) response_code,
+ (int) adr.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &adr);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_add_aml_decision_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_AddAmlDecision *
+TALER_EXCHANGE_add_aml_decision (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const char *justification,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const struct TALER_Amount *new_threshold,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_AmlDecisionState new_state,
+ const json_t *kyc_requirements,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ TALER_EXCHANGE_AddAmlDecisionCallback cb,
+ void *cb_cls)
+{
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+ struct TALER_EXCHANGE_AddAmlDecision *wh;
+ CURL *eh;
+ json_t *body;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
+ &officer_pub.eddsa_pub);
+ TALER_officer_aml_decision_sign (justification,
+ decision_time,
+ new_threshold,
+ h_payto,
+ new_state,
+ kyc_requirements,
+ officer_priv,
+ &officer_sig);
+ wh = GNUNET_new (struct TALER_EXCHANGE_AddAmlDecision);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ctx = ctx;
+ {
+ char *path;
+ char opus[sizeof (officer_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_pub,
+ sizeof (officer_pub),
+ opus,
+ sizeof (opus));
+ *end = '\0';
+ GNUNET_asprintf (&path,
+ "aml/%s/decision",
+ opus);
+ wh->url = TALER_url_join (url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == wh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("justification",
+ justification),
+ GNUNET_JSON_pack_data_auto ("officer_sig",
+ &officer_sig),
+ GNUNET_JSON_pack_data_auto ("h_payto",
+ h_payto),
+ GNUNET_JSON_pack_uint64 ("new_state",
+ (uint32_t) new_state),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_incref ("kyc_requirements",
+ (json_t *) kyc_requirements)),
+ TALER_JSON_pack_amount ("new_threshold",
+ new_threshold),
+ GNUNET_JSON_pack_timestamp ("decision_time",
+ decision_time));
+ eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&wh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (wh->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ wh->url);
+ wh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ wh->post_ctx.headers,
+ &handle_add_aml_decision_finished,
+ wh);
+ if (NULL == wh->job)
+ {
+ TALER_EXCHANGE_add_aml_decision_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_add_aml_decision_cancel (
+ struct TALER_EXCHANGE_AddAmlDecision *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wh->post_ctx);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_age_withdraw.c b/src/lib/exchange_api_age_withdraw.c
new file mode 100644
index 000000000..ca1a11cb8
--- /dev/null
+++ b/src/lib/exchange_api_age_withdraw.c
@@ -0,0 +1,1125 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_age_withdraw.c
+ * @brief Implementation of /reserves/$RESERVE_PUB/age-withdraw requests
+ * @author Özgür Kesim
+ */
+
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include <sys/wait.h>
+#include "taler_curl_lib.h"
+#include "taler_error_codes.h"
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_util.h"
+
+/**
+ * A CoinCandidate is populated from a master secret
+ */
+struct CoinCandidate
+{
+ /**
+ * Master key material for the coin candidates.
+ */
+ struct TALER_PlanchetMasterSecretP secret;
+
+ /**
+ * The details derived form the master secrets
+ */
+ struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details;
+
+ /**
+ * Blinded hash of the coin
+ **/
+ struct TALER_BlindedCoinHashP blinded_coin_h;
+
+};
+
+
+/**
+ * Closure for a call to /csr-withdraw, contains data that is needed to process
+ * the result.
+ */
+struct CSRClosure
+{
+ /**
+ * Points to the actual candidate in CoinData.coin_candidates, to continue
+ * to build its contents based on the results from /csr-withdraw
+ */
+ struct CoinCandidate *candidate;
+
+ /**
+ * The planchet to finally generate. Points to the corresponding candidate
+ * in CoindData.planchet_details
+ */
+ struct TALER_PlanchetDetail *planchet;
+
+ /**
+ * Handler to the originating call to /age-withdraw, needed to either
+ * cancel the running age-withdraw request (on failure of the current call
+ * to /csr-withdraw), or to eventually perform the protocol, once all
+ * csr-withdraw requests have successfully finished.
+ */
+ struct TALER_EXCHANGE_AgeWithdrawHandle *age_withdraw_handle;
+
+ /**
+ * Session nonce.
+ */
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+
+ /**
+ * Denomination information, needed for CS coins for the
+ * step after /csr-withdraw
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+
+ /**
+ * Handler for the CS R request
+ */
+ struct TALER_EXCHANGE_CsRWithdrawHandle *csr_withdraw_handle;
+};
+
+/**
+ * Data we keep per coin in the batch.
+ */
+struct CoinData
+{
+ /**
+ * The denomination of the coin. Must support age restriction, i.e
+ * its .keys.age_mask MUST not be 0
+ */
+ struct TALER_EXCHANGE_DenomPublicKey denom_pub;
+
+ /**
+ * The Candidates for the coin
+ */
+ struct CoinCandidate coin_candidates[TALER_CNC_KAPPA];
+
+ /**
+ * Details of the planchet(s).
+ */
+ struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA];
+
+ /**
+ * Closure for each candidate of type CS for the preflight request to
+ * /csr-withdraw
+ */
+ struct CSRClosure csr_cls[TALER_CNC_KAPPA];
+};
+
+/**
+ * A /reserves/$RESERVE_PUB/age-withdraw request-handle for calls with
+ * pre-blinded planchets. Returned by TALER_EXCHANGE_age_withdraw_blinded.
+ */
+struct TALER_EXCHANGE_AgeWithdrawBlindedHandle
+{
+
+ /**
+ * Reserve private key.
+ */
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ /**
+ * Reserve public key, calculated
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Signature of the reserve for the request, calculated after all
+ * parameters for the coins are collected.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /*
+ * The denomination keys of the exchange
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The age mask, extracted from the denominations.
+ * MUST be the same for all denominations
+ *
+ */
+ struct TALER_AgeMask age_mask;
+
+ /**
+ * Maximum age to commit to.
+ */
+ uint8_t max_age;
+
+ /**
+ * The commitment calculated as SHA512 hash over all blinded_coin_h
+ */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+ /**
+ * Total amount requested (value plus withdraw fee).
+ */
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Length of the @e blinded_input Array
+ */
+ size_t num_input;
+
+ /**
+ * The blinded planchet input for the call to /age-withdraw via
+ * TALER_EXCHANGE_age_withdraw_blinded
+ */
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedInput *blinded_input;
+
+ /**
+ * The url for this request.
+ */
+ char *request_url;
+
+ /**
+ * Context for curl.
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ /**
+ * CURL handle for the request job.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Post Context
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Function to call with age-withdraw response results.
+ */
+ TALER_EXCHANGE_AgeWithdrawBlindedCallback callback;
+
+ /**
+ * Closure for @e blinded_callback
+ */
+ void *callback_cls;
+};
+
+/**
+ * A /reserves/$RESERVE_PUB/age-withdraw request-handle for calls from
+ * a wallet, i. e. when blinding data is available.
+ */
+struct TALER_EXCHANGE_AgeWithdrawHandle
+{
+
+ /**
+ * Length of the @e coin_data Array
+ */
+ size_t num_coins;
+
+ /**
+ * The base-URL of the exchange.
+ */
+ const char *exchange_url;
+
+ /**
+ * Reserve private key.
+ */
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ /**
+ * Reserve public key, calculated
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Signature of the reserve for the request, calculated after all
+ * parameters for the coins are collected.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /*
+ * The denomination keys of the exchange
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The age mask, extracted from the denominations.
+ * MUST be the same for all denominations
+ *
+ */
+ struct TALER_AgeMask age_mask;
+
+ /**
+ * Maximum age to commit to.
+ */
+ uint8_t max_age;
+
+ /**
+ * Array of per-coin data
+ */
+ struct CoinData *coin_data;
+
+ /**
+ * Context for curl.
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ struct
+ {
+ /**
+ * Number of /csr-withdraw requests still pending.
+ */
+ unsigned int pending;
+
+ /**
+ * CURL handle for the request job.
+ */
+ struct GNUNET_CURL_Job *job;
+ } csr;
+
+
+ /**
+ * Function to call with age-withdraw response results.
+ */
+ TALER_EXCHANGE_AgeWithdrawCallback callback;
+
+ /**
+ * Closure for @e age_withdraw_cb
+ */
+ void *callback_cls;
+
+ /* The Handler for the actual call to the exchange */
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *procotol_handle;
+};
+
+/**
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/age-withdraw operation.
+ * Extract the noreveal_index and return it to the caller.
+ *
+ * @param awbh operation handle
+ * @param j_response reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+reserve_age_withdraw_ok (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh,
+ const json_t *j_response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedResponse response = {
+ .hr.reply = j_response,
+ .hr.http_status = MHD_HTTP_OK,
+ .details.ok.h_commitment = awbh->h_commitment
+ };
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint8 ("noreveal_index",
+ &response.details.ok.noreveal_index),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &response.details.ok.exchange_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK!=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_exchange_online_age_withdraw_confirmation_verify (
+ &awbh->h_commitment,
+ response.details.ok.noreveal_index,
+ &response.details.ok.exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+
+ }
+
+ awbh->callback (awbh->callback_cls,
+ &response);
+ /* make sure the callback isn't called again */
+ awbh->callback = NULL;
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RESERVE_PUB/age-withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AgeWithdrawHandle`
+ * @param response_code The HTTP response code
+ * @param response response data
+ */
+static void
+handle_reserve_age_withdraw_blinded_finished (
+ void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh = cls;
+ const json_t *j_response = response;
+ struct TALER_EXCHANGE_AgeWithdrawBlindedResponse awbr = {
+ .hr.reply = j_response,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ awbh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ awbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ reserve_age_withdraw_ok (awbh,
+ j_response))
+ {
+ GNUNET_break_op (0);
+ awbr.hr.http_status = 0;
+ awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == awbh->callback);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+ return;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ GNUNET_break_op (0);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, the exchange basically just says
+ that it doesn't know this reserve. Can happen if we
+ query before the wire transfer went through.
+ We should simply pass the JSON reply to the application. */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* The age requirements might not have been met */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* only validate reply is well-formed */
+ {
+ uint64_t ptu;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 ("requirement_row",
+ &ptu),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ awbr.hr.http_status = 0;
+ awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange age-withdraw\n",
+ (unsigned int) response_code,
+ (int) awbr.hr.ec);
+ break;
+ }
+ awbh->callback (awbh->callback_cls,
+ &awbr);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+}
+
+
+/**
+ * Runs the actual age-withdraw operation with the blinded planchets.
+ *
+ * @param[in,out] awbh age withdraw handler
+ */
+static void
+perform_protocol (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh)
+{
+#define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while (0)
+
+ struct GNUNET_HashContext *coins_hctx = NULL;
+ json_t *j_denoms = NULL;
+ json_t *j_array_candidates = NULL;
+ json_t *j_request_body = NULL;
+ CURL *curlh = NULL;
+
+ GNUNET_assert (0 < awbh->num_input);
+ awbh->age_mask = awbh->blinded_input[0].denom_pub->key.age_mask;
+
+ FAIL_IF (GNUNET_OK !=
+ TALER_amount_set_zero (awbh->keys->currency,
+ &awbh->amount_with_fee));
+ /* Accumulate total value with fees */
+ for (size_t i = 0; i < awbh->num_input; i++)
+ {
+ struct TALER_Amount coin_total;
+ const struct TALER_EXCHANGE_DenomPublicKey *dpub =
+ awbh->blinded_input[i].denom_pub;
+
+ FAIL_IF (0 >
+ TALER_amount_add (&coin_total,
+ &dpub->fees.withdraw,
+ &dpub->value));
+ FAIL_IF (0 >
+ TALER_amount_add (&awbh->amount_with_fee,
+ &awbh->amount_with_fee,
+ &coin_total));
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Attempting to age-withdraw from reserve %s with maximum age %d\n",
+ TALER_B2S (&awbh->reserve_pub),
+ awbh->max_age);
+
+ coins_hctx = GNUNET_CRYPTO_hash_context_start ();
+ FAIL_IF (NULL == coins_hctx);
+
+
+ j_denoms = json_array ();
+ j_array_candidates = json_array ();
+ FAIL_IF ((NULL == j_denoms) ||
+ (NULL == j_array_candidates));
+
+ for (size_t i = 0; i< awbh->num_input; i++)
+ {
+ /* Build the denomination array */
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
+ awbh->blinded_input[i].denom_pub;
+ const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key;
+ json_t *jdenom;
+
+ /* The mask must be the same for all coins */
+ FAIL_IF (awbh->age_mask.bits != denom_pub->key.age_mask.bits);
+
+ jdenom = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto (NULL,
+ denom_h));
+ FAIL_IF (NULL == jdenom);
+ FAIL_IF (0 > json_array_append_new (j_denoms,
+ jdenom));
+
+ /* Build the candidate array */
+ {
+ json_t *j_can = json_array ();
+ FAIL_IF (NULL == j_can);
+
+ for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct TALER_BlindedCoinHashP bch;
+ const struct TALER_PlanchetDetail *planchet =
+ &awbh->blinded_input[i].planchet_details[k];
+ json_t *jc = GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_planchet (
+ NULL,
+ &planchet->blinded_planchet));
+
+ FAIL_IF (NULL == jc);
+ FAIL_IF (0 > json_array_append_new (j_can,
+ jc));
+
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &bch);
+
+ GNUNET_CRYPTO_hash_context_read (coins_hctx,
+ &bch,
+ sizeof(bch));
+ }
+
+ FAIL_IF (0 > json_array_append_new (j_array_candidates,
+ j_can));
+ }
+ }
+ }
+
+ /* Build the hash of the commitment */
+ GNUNET_CRYPTO_hash_context_finish (coins_hctx,
+ &awbh->h_commitment.hash);
+ coins_hctx = NULL;
+
+ /* Sign the request */
+ TALER_wallet_age_withdraw_sign (&awbh->h_commitment,
+ &awbh->amount_with_fee,
+ &awbh->age_mask,
+ awbh->max_age,
+ awbh->reserve_priv,
+ &awbh->reserve_sig);
+
+ /* Initiate the POST-request */
+ j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("denom_hs", j_denoms),
+ GNUNET_JSON_pack_array_steal ("blinded_coin_evs", j_array_candidates),
+ GNUNET_JSON_pack_uint64 ("max_age", awbh->max_age),
+ GNUNET_JSON_pack_data_auto ("reserve_sig", &awbh->reserve_sig));
+ FAIL_IF (NULL == j_request_body);
+
+ curlh = TALER_EXCHANGE_curl_easy_get_ (awbh->request_url);
+ FAIL_IF (NULL == curlh);
+ FAIL_IF (GNUNET_OK !=
+ TALER_curl_easy_post (&awbh->post_ctx,
+ curlh,
+ j_request_body));
+ json_decref (j_request_body);
+ j_request_body = NULL;
+
+ awbh->job = GNUNET_CURL_job_add2 (
+ awbh->curl_ctx,
+ curlh,
+ awbh->post_ctx.headers,
+ &handle_reserve_age_withdraw_blinded_finished,
+ awbh);
+ FAIL_IF (NULL == awbh->job);
+
+ /* No errors, return */
+ return;
+
+ERROR:
+ if (NULL != j_denoms)
+ json_decref (j_denoms);
+ if (NULL != j_array_candidates)
+ json_decref (j_array_candidates);
+ if (NULL != j_request_body)
+ json_decref (j_request_body);
+ if (NULL != curlh)
+ curl_easy_cleanup (curlh);
+ if (NULL != coins_hctx)
+ GNUNET_CRYPTO_hash_context_abort (coins_hctx);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+ return;
+#undef FAIL_IF
+}
+
+
+/**
+ * @brief Callback to copy the results from the call to TALER_age_withdraw_blinded
+ * to the result for the originating call from TALER_age_withdraw.
+ *
+ * @param cls struct TALER_AgeWithdrawHandle
+ * @param awbr The response
+ */
+static void
+copy_results (
+ void *cls,
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedResponse *awbr)
+{
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh = cls;
+ uint8_t k = awbr->details.ok.noreveal_index;
+ struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details[awh->num_coins];
+ struct TALER_BlindedCoinHashP blinded_coin_hs[awh->num_coins];
+ struct TALER_EXCHANGE_AgeWithdrawResponse resp = {
+ .hr = awbr->hr,
+ .details = {
+ .ok = {
+ .noreveal_index = awbr->details.ok.noreveal_index,
+ .h_commitment = awbr->details.ok.h_commitment,
+ .exchange_pub = awbr->details.ok.exchange_pub,
+ .num_coins = awh->num_coins,
+ .coin_details = details,
+ .blinded_coin_hs = blinded_coin_hs
+ },
+ },
+ };
+
+ for (size_t n = 0; n< awh->num_coins; n++)
+ {
+ details[n] = awh->coin_data[n].coin_candidates[k].details;
+ details[n].planchet = awh->coin_data[n].planchet_details[k];
+ blinded_coin_hs[n] = awh->coin_data[n].coin_candidates[k].blinded_coin_h;
+ }
+ awh->callback (awh->callback_cls,
+ &resp);
+ awh->callback = NULL;
+}
+
+
+/**
+ * @brief Prepares and executes TALER_EXCHANGE_age_withdraw_blinded.
+ * If there were CS-denominations involved, started once the all calls
+ * to /csr-withdraw are done.
+ */
+static void
+call_age_withdraw_blinded (
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedInput blinded_input[awh->num_coins];
+
+ /* Prepare the blinded planchets as input */
+ for (size_t n = 0; n < awh->num_coins; n++)
+ {
+ blinded_input[n].denom_pub = &awh->coin_data[n].denom_pub;
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ blinded_input[n].planchet_details[k] =
+ awh->coin_data[n].planchet_details[k];
+ }
+
+ awh->procotol_handle =
+ TALER_EXCHANGE_age_withdraw_blinded (
+ awh->curl_ctx,
+ awh->keys,
+ awh->exchange_url,
+ awh->reserve_priv,
+ awh->max_age,
+ awh->num_coins,
+ blinded_input,
+ copy_results,
+ awh);
+}
+
+
+/**
+ * Prepares the request URL for the age-withdraw request
+ *
+ * @param awbh The handler
+ * @param exchange_url The base-URL to the exchange
+ */
+static
+enum GNUNET_GenericReturnValue
+prepare_url (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh,
+ const char *exchange_url)
+{
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &awbh->reserve_pub,
+ sizeof (awbh->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/age-withdraw",
+ pub_str);
+
+ awbh->request_url = TALER_url_join (exchange_url,
+ arg_str,
+ NULL);
+ if (NULL == awbh->request_url)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * @brief Function called when CSR withdraw retrieval is finished
+ *
+ * @param cls the `struct CSRClosure *`
+ * @param csrr replies from the /csr-withdraw request
+ */
+static void
+csr_withdraw_done (
+ void *cls,
+ const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
+{
+ struct CSRClosure *csr = cls;
+ struct CoinCandidate *can;
+ struct TALER_PlanchetDetail *planchet;
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh;
+
+ GNUNET_assert (NULL != csr);
+ awh = csr->age_withdraw_handle;
+ planchet = csr->planchet;
+ can = csr->candidate;
+
+ GNUNET_assert (NULL != can);
+ GNUNET_assert (NULL != planchet);
+ GNUNET_assert (NULL != awh);
+
+ csr->csr_withdraw_handle = NULL;
+
+ switch (csrr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ bool success = false;
+ /* Complete the initialization of the coin with CS denomination */
+
+ TALER_denom_ewv_copy (&can->details.alg_values,
+ &csrr->details.ok.alg_values);
+ GNUNET_assert (can->details.alg_values.blinding_inputs->cipher
+ == GNUNET_CRYPTO_BSA_CS);
+ TALER_planchet_setup_coin_priv (&can->secret,
+ &can->details.alg_values,
+ &can->details.coin_priv);
+ TALER_planchet_blinding_secret_create (&can->secret,
+ &can->details.alg_values,
+ &can->details.blinding_key);
+ /* This initializes the 2nd half of the
+ can->planchet_detail.blinded_planchet! */
+ do {
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&csr->denom_pub->key,
+ &can->details.alg_values,
+ &can->details.blinding_key,
+ &csr->nonce,
+ &can->details.coin_priv,
+ &can->details.h_age_commitment,
+ &can->details.h_coin_pub,
+ planchet))
+ {
+ GNUNET_break (0);
+ break;
+ }
+
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &can->blinded_coin_h);
+ success = true;
+ } while (0);
+
+ awh->csr.pending--;
+
+ /* No more pending requests to /csr-withdraw, we can now perform the
+ * actual age-withdraw operation */
+ if (0 == awh->csr.pending && success)
+ call_age_withdraw_blinded (awh);
+ return;
+ }
+ default:
+ break;
+ }
+ TALER_EXCHANGE_age_withdraw_cancel (awh);
+}
+
+
+/**
+ * @brief Prepare the coins for the call to age-withdraw and calculates
+ * the total amount with fees.
+ *
+ * For denomination with CS as cipher, initiates the preflight to retrieve the
+ * csr-parameter via /csr-withdraw.
+ *
+ * @param awh The handler to the age-withdraw
+ * @param num_coins The number of coins in @e coin_inputs
+ * @param coin_inputs The input for the individual coin(-candidates)
+ * @return GNUNET_OK on success, GNUNET_SYSERR on failure
+ */
+static
+enum GNUNET_GenericReturnValue
+prepare_coins (
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[
+ static num_coins])
+{
+#define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while (0)
+
+ GNUNET_assert (0 < num_coins);
+ awh->age_mask = coin_inputs[0].denom_pub->key.age_mask;
+
+ awh->coin_data = GNUNET_new_array (awh->num_coins,
+ struct CoinData);
+
+ for (size_t i = 0; i < num_coins; i++)
+ {
+ struct CoinData *cd = &awh->coin_data[i];
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput *input = &coin_inputs[i];
+
+ cd->denom_pub = *input->denom_pub;
+ /* The mask must be the same for all coins */
+ FAIL_IF (awh->age_mask.bits != input->denom_pub->key.age_mask.bits);
+ TALER_denom_pub_copy (&cd->denom_pub.key,
+ &input->denom_pub->key);
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct CoinCandidate *can = &cd->coin_candidates[k];
+ struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
+
+ can->secret = input->secrets[k];
+ /* Derive the age restriction from the given secret and
+ * the maximum age */
+ TALER_age_restriction_from_secret (
+ &can->secret,
+ &input->denom_pub->key.age_mask,
+ awh->max_age,
+ &can->details.age_commitment_proof);
+
+ TALER_age_commitment_hash (&can->details.age_commitment_proof.commitment,
+ &can->details.h_age_commitment);
+
+ switch (input->denom_pub->key.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ TALER_denom_ewv_copy (&can->details.alg_values,
+ TALER_denom_ewv_rsa_singleton ());
+ TALER_planchet_setup_coin_priv (&can->secret,
+ &can->details.alg_values,
+ &can->details.coin_priv);
+ TALER_planchet_blinding_secret_create (&can->secret,
+ &can->details.alg_values,
+ &can->details.blinding_key);
+ FAIL_IF (GNUNET_OK !=
+ TALER_planchet_prepare (&cd->denom_pub.key,
+ &can->details.alg_values,
+ &can->details.blinding_key,
+ NULL,
+ &can->details.coin_priv,
+ &can->details.h_age_commitment,
+ &can->details.h_coin_pub,
+ planchet));
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &can->blinded_coin_h);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ struct CSRClosure *cls = &cd->csr_cls[k];
+ /**
+ * Save the handler and the denomination for the callback
+ * after the call to csr-withdraw */
+ cls->age_withdraw_handle = awh;
+ cls->candidate = can;
+ cls->planchet = planchet;
+ cls->denom_pub = &cd->denom_pub;
+ TALER_cs_withdraw_nonce_derive (
+ &can->secret,
+ &cls->nonce.cs_nonce);
+ cls->csr_withdraw_handle =
+ TALER_EXCHANGE_csr_withdraw (
+ awh->curl_ctx,
+ awh->exchange_url,
+ &cd->denom_pub,
+ &cls->nonce.cs_nonce,
+ &csr_withdraw_done,
+ cls);
+ FAIL_IF (NULL == cls->csr_withdraw_handle);
+
+ awh->csr.pending++;
+ break;
+ }
+ default:
+ FAIL_IF (1);
+ }
+ }
+ }
+ return GNUNET_OK;
+
+ERROR:
+ TALER_EXCHANGE_age_withdraw_cancel (awh);
+ return GNUNET_SYSERR;
+#undef FAIL_IF
+};
+
+struct TALER_EXCHANGE_AgeWithdrawHandle *
+TALER_EXCHANGE_age_withdraw (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[const static
+ num_coins],
+ uint8_t max_age,
+ TALER_EXCHANGE_AgeWithdrawCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh;
+
+ awh = GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawHandle);
+ awh->exchange_url = exchange_url;
+ awh->keys = TALER_EXCHANGE_keys_incref (keys);
+ awh->curl_ctx = curl_ctx;
+ awh->reserve_priv = reserve_priv;
+ awh->callback = res_cb;
+ awh->callback_cls = res_cb_cls;
+ awh->num_coins = num_coins;
+ awh->max_age = max_age;
+
+
+ if (GNUNET_OK != prepare_coins (awh,
+ num_coins,
+ coin_inputs))
+ {
+ GNUNET_free (awh);
+ return NULL;
+ }
+
+ /* If there were no CS denominations, we can now perform the actual
+ * age-withdraw protocol. Otherwise, there are calls to /csr-withdraw
+ * in flight and once they finish, the age-withdraw-protocol will be
+ * called from within the csr_withdraw_done-function.
+ */
+ if (0 == awh->csr.pending)
+ call_age_withdraw_blinded (awh);
+
+ return awh;
+}
+
+
+void
+TALER_EXCHANGE_age_withdraw_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh)
+{
+ /* Cleanup coin data */
+ for (unsigned int i = 0; i<awh->num_coins; i++)
+ {
+ struct CoinData *cd = &awh->coin_data[i];
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
+ struct CSRClosure *cls = &cd->csr_cls[k];
+ struct CoinCandidate *can = &cd->coin_candidates[k];
+
+ if (NULL != cls->csr_withdraw_handle)
+ {
+ TALER_EXCHANGE_csr_withdraw_cancel (cls->csr_withdraw_handle);
+ cls->csr_withdraw_handle = NULL;
+ }
+ TALER_blinded_planchet_free (&planchet->blinded_planchet);
+ TALER_denom_ewv_free (&can->details.alg_values);
+ }
+ TALER_denom_pub_free (&cd->denom_pub.key);
+ }
+ GNUNET_free (awh->coin_data);
+ TALER_EXCHANGE_keys_decref (awh->keys);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awh->procotol_handle);
+ awh->procotol_handle = NULL;
+ GNUNET_free (awh);
+}
+
+
+struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *
+TALER_EXCHANGE_age_withdraw_blinded (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ uint8_t max_age,
+ unsigned int num_input,
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedInput blinded_input[static
+ num_input],
+ TALER_EXCHANGE_AgeWithdrawBlindedCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh =
+ GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawBlindedHandle);
+
+ awbh->num_input = num_input;
+ awbh->blinded_input = blinded_input;
+ awbh->keys = TALER_EXCHANGE_keys_incref (keys);
+ awbh->curl_ctx = curl_ctx;
+ awbh->reserve_priv = reserve_priv;
+ awbh->callback = res_cb;
+ awbh->callback_cls = res_cb_cls;
+ awbh->max_age = max_age;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&awbh->reserve_priv->eddsa_priv,
+ &awbh->reserve_pub.eddsa_pub);
+
+ if (GNUNET_OK != prepare_url (awbh,
+ exchange_url))
+ return NULL;
+
+ perform_protocol (awbh);
+ return awbh;
+}
+
+
+void
+TALER_EXCHANGE_age_withdraw_blinded_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh)
+{
+ if (NULL == awbh)
+ return;
+
+ if (NULL != awbh->job)
+ {
+ GNUNET_CURL_job_cancel (awbh->job);
+ awbh->job = NULL;
+ }
+ GNUNET_free (awbh->request_url);
+ TALER_EXCHANGE_keys_decref (awbh->keys);
+ TALER_curl_easy_post_finished (&awbh->post_ctx);
+ GNUNET_free (awbh);
+}
+
+
+/* exchange_api_age_withdraw.c */
diff --git a/src/lib/exchange_api_age_withdraw_reveal.c b/src/lib/exchange_api_age_withdraw_reveal.c
new file mode 100644
index 000000000..cade528d2
--- /dev/null
+++ b/src/lib/exchange_api_age_withdraw_reveal.c
@@ -0,0 +1,477 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_age_withdraw_reveal.c
+ * @brief Implementation of /age-withdraw/$ACH/reveal requests
+ * @author Özgür Kesim
+ */
+
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+/**
+ * Handler for a running age-withdraw-reveal request
+ */
+struct TALER_EXCHANGE_AgeWithdrawRevealHandle
+{
+
+ /* The index not to be disclosed */
+ uint8_t noreveal_index;
+
+ /* The age-withdraw commitment */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+ /* The reserve's public key */
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+
+ /* Number of coins */
+ size_t num_coins;
+
+ /* The @e num_coins * kappa coin secrets from the age-withdraw commitment */
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput *coins_input;
+
+ /* The url for the reveal request */
+ char *request_url;
+
+ /**
+ * CURL handle for the request job.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Post Context
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /* Callback */
+ TALER_EXCHANGE_AgeWithdrawRevealCallback callback;
+
+ /* Reveal */
+ void *callback_cls;
+};
+
+
+/**
+ * We got a 200 OK response for the /age-withdraw/$ACH/reveal operation.
+ * Extract the signed blindedcoins and return it to the caller.
+ *
+ * @param awrh operation handle
+ * @param j_response reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+age_withdraw_reveal_ok (
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh,
+ const json_t *j_response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawRevealResponse response = {
+ .hr.reply = j_response,
+ .hr.http_status = MHD_HTTP_OK,
+ };
+ const json_t *j_sigs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("ev_sigs",
+ &j_sigs),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK != GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (awrh->num_coins != json_array_size (j_sigs))
+ {
+ /* Number of coins generated does not match our expectation */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct TALER_BlindedDenominationSignature denom_sigs[awrh->num_coins];
+ json_t *j_sig;
+ size_t n;
+
+ /* Reconstruct the coins and unblind the signatures */
+ json_array_foreach (j_sigs, n, j_sig)
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_blinded_denom_sig (NULL,
+ &denom_sigs[n]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK != GNUNET_JSON_parse (j_sig,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ }
+
+ response.details.ok.num_sigs = awrh->num_coins;
+ response.details.ok.blinded_denom_sigs = denom_sigs;
+ awrh->callback (awrh->callback_cls,
+ &response);
+ /* Make sure the callback isn't called again */
+ awrh->callback = NULL;
+ /* Free resources */
+ for (size_t i = 0; i < awrh->num_coins; i++)
+ TALER_blinded_denom_sig_free (&denom_sigs[i]);
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /age-withdraw/$ACH/reveal request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AgeWithdrawRevealHandle`
+ * @param response_code The HTTP response code
+ * @param response response data
+ */
+static void
+handle_age_withdraw_reveal_finished (
+ void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh = cls;
+ const json_t *j_response = response;
+ struct TALER_EXCHANGE_AgeWithdrawRevealResponse awr = {
+ .hr.reply = j_response,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ awrh->job = NULL;
+ /* FIXME[oec]: Only handle response-codes that are in the spec */
+ switch (response_code)
+ {
+ case 0:
+ awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = age_withdraw_reveal_ok (awrh,
+ j_response);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break_op (0);
+ awr.hr.http_status = 0;
+ awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == awrh->callback);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh);
+ return;
+ }
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* only validate reply is well-formed */
+ {
+ uint64_t ptu;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 ("legitimization_uuid",
+ &ptu),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ awr.hr.http_status = 0;
+ awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ GNUNET_break_op (0);
+ /**
+ * This should never happen, as we don't sent any signatures
+ * to the exchange to verify. We should simply pass the JSON reply
+ * to the application
+ **/
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, the exchange basically just says
+ that it doesn't know this age-withdraw commitment. */
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* An age commitment for one of the coins did not fulfill
+ * the required maximum age requirement of the corresponding
+ * reserve.
+ * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE.
+ */
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange age-withdraw\n",
+ (unsigned int) response_code,
+ (int) awr.hr.ec);
+ break;
+ }
+ awrh->callback (awrh->callback_cls,
+ &awr);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh);
+}
+
+
+/**
+ * Prepares the request URL for the age-withdraw-reveal request
+ *
+ * @param exchange_url The base-URL to the exchange
+ * @param[in,out] awrh The handler
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
+ */
+static
+enum GNUNET_GenericReturnValue
+prepare_url (
+ const char *exchange_url,
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh)
+{
+ char arg_str[sizeof (struct TALER_AgeWithdrawCommitmentHashP) * 2 + 32];
+ char pub_str[sizeof (struct TALER_AgeWithdrawCommitmentHashP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (&awrh->h_commitment,
+ sizeof (awrh->h_commitment),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "age-withdraw/%s/reveal",
+ pub_str);
+
+ awrh->request_url = TALER_url_join (exchange_url,
+ arg_str,
+ NULL);
+ if (NULL == awrh->request_url)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Call /age-withdraw/$ACH/reveal
+ *
+ * @param curl_ctx The context for CURL
+ * @param awrh The handler
+ */
+static
+void
+perform_protocol (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh)
+{
+ CURL *curlh = NULL;
+ json_t *j_request_body = NULL;
+ json_t *j_array_of_secrets = NULL;
+ json_t *j_secrets = NULL;
+ json_t *j_sec = NULL;
+
+#define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while (0)
+
+ j_array_of_secrets = json_array ();
+ FAIL_IF (NULL == j_array_of_secrets);
+
+ for (size_t n = 0; n < awrh->num_coins; n++)
+ {
+ const struct TALER_PlanchetMasterSecretP *secrets =
+ awrh->coins_input[n].secrets;
+
+ j_secrets = json_array ();
+ FAIL_IF (NULL == j_secrets);
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ const struct TALER_PlanchetMasterSecretP *secret = &secrets[k];
+ if (awrh->noreveal_index == k)
+ continue;
+
+ j_sec = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto (NULL, secret));
+
+ FAIL_IF (NULL == j_sec);
+ FAIL_IF (0 < json_array_append_new (j_secrets,
+ j_sec));
+ }
+
+ FAIL_IF (0 < json_array_append_new (j_array_of_secrets,
+ j_secrets));
+ }
+ j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ awrh->reserve_pub),
+ GNUNET_JSON_pack_array_steal ("disclosed_coin_secrets",
+ j_array_of_secrets));
+ FAIL_IF (NULL == j_request_body);
+
+ curlh = TALER_EXCHANGE_curl_easy_get_ (awrh->request_url);
+ FAIL_IF (NULL == curlh);
+ FAIL_IF (GNUNET_OK !=
+ TALER_curl_easy_post (&awrh->post_ctx,
+ curlh,
+ j_request_body));
+ json_decref (j_request_body);
+ j_request_body = NULL;
+
+ awrh->job = GNUNET_CURL_job_add2 (curl_ctx,
+ curlh,
+ awrh->post_ctx.headers,
+ &handle_age_withdraw_reveal_finished,
+ awrh);
+ FAIL_IF (NULL == awrh->job);
+
+ /* No error, return */
+ return;
+
+ERROR:
+ if (NULL != j_sec)
+ json_decref (j_sec);
+ if (NULL != j_secrets)
+ json_decref (j_secrets);
+ if (NULL != j_array_of_secrets)
+ json_decref (j_array_of_secrets);
+ if (NULL != j_request_body)
+ json_decref (j_request_body);
+ if (NULL != curlh)
+ curl_easy_cleanup (curlh);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh);
+ return;
+#undef FAIL_IF
+}
+
+
+struct TALER_EXCHANGE_AgeWithdrawRevealHandle *
+TALER_EXCHANGE_age_withdraw_reveal (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput coins_input[static
+ num_coins],
+ uint8_t noreveal_index,
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ TALER_EXCHANGE_AgeWithdrawRevealCallback reveal_cb,
+ void *reveal_cb_cls)
+{
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh =
+ GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawRevealHandle);
+ awrh->noreveal_index = noreveal_index;
+ awrh->h_commitment = *h_commitment;
+ awrh->num_coins = num_coins;
+ awrh->coins_input = coins_input;
+ awrh->callback = reveal_cb;
+ awrh->callback_cls = reveal_cb_cls;
+ awrh->reserve_pub = reserve_pub;
+
+ if (GNUNET_OK !=
+ prepare_url (exchange_url,
+ awrh))
+ return NULL;
+
+ perform_protocol (curl_ctx, awrh);
+
+ return awrh;
+}
+
+
+void
+TALER_EXCHANGE_age_withdraw_reveal_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh)
+{
+ if (NULL != awrh->job)
+ {
+ GNUNET_CURL_job_cancel (awrh->job);
+ awrh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&awrh->post_ctx);
+
+ if (NULL != awrh->request_url)
+ GNUNET_free (awrh->request_url);
+
+ GNUNET_free (awrh);
+}
+
+
+/* exchange_api_age_withdraw_reveal.c */
diff --git a/src/lib/exchange_api_auditor_add_denomination.c b/src/lib/exchange_api_auditor_add_denomination.c
new file mode 100644
index 000000000..89de0d7f1
--- /dev/null
+++ b/src/lib/exchange_api_auditor_add_denomination.c
@@ -0,0 +1,238 @@
+/*
+ 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 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 lib/exchange_api_auditor_add_denomination.c
+ * @brief functions for the auditor to add its signature for denomination at the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "auditor_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_AuditorAddDenominationHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_AuditorAddDenominationCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /auditor/$AUDITOR_PUB/$H_DENOM_PUB request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AuditorAddDenominationHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_auditor_add_denomination_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_AuditorAddDenominationResponse adr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ ah->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_GONE:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ if (NULL != json)
+ {
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange auditor-add-denomination at URL `%s'\n",
+ (unsigned int) response_code,
+ (int) adr.hr.ec,
+ ah->url);
+ }
+ else
+ {
+ adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ adr.hr.hint = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected HTTP response code %u (no JSON returned) at URL `%s'\n",
+ (unsigned int) response_code,
+ ah->url);
+ }
+ break;
+ }
+ if (NULL != ah->cb)
+ {
+ ah->cb (ah->cb_cls,
+ &adr);
+ ah->cb = NULL;
+ }
+ TALER_EXCHANGE_add_auditor_denomination_cancel (ah);
+}
+
+
+struct TALER_EXCHANGE_AuditorAddDenominationHandle *
+TALER_EXCHANGE_add_auditor_denomination (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_AuditorSignatureP *auditor_sig,
+ TALER_EXCHANGE_AuditorAddDenominationCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah;
+ CURL *eh;
+ json_t *body;
+
+ ah = GNUNET_new (struct TALER_EXCHANGE_AuditorAddDenominationHandle);
+ ah->cb = cb;
+ ah->cb_cls = cb_cls;
+ ah->ctx = ctx;
+ {
+ char apub_str[sizeof (*auditor_pub) * 2];
+ char denom_str[sizeof (*h_denom_pub) * 2];
+ char arg_str[sizeof (apub_str) + sizeof (denom_str) + 32];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (auditor_pub,
+ sizeof (*auditor_pub),
+ apub_str,
+ sizeof (apub_str));
+ *end = '\0';
+ end = GNUNET_STRINGS_data_to_string (h_denom_pub,
+ sizeof (*h_denom_pub),
+ denom_str,
+ sizeof (denom_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "auditors/%s/%s",
+ apub_str,
+ denom_str);
+ ah->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ }
+ if (NULL == ah->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (ah);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("auditor_sig",
+ auditor_sig));
+ eh = TALER_AUDITOR_curl_easy_get_ (ah->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&ah->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (ah->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ ah->url);
+ ah->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ ah->post_ctx.headers,
+ &handle_auditor_add_denomination_finished,
+ ah);
+ if (NULL == ah->job)
+ {
+ TALER_EXCHANGE_add_auditor_denomination_cancel (ah);
+ return NULL;
+ }
+ return ah;
+}
+
+
+void
+TALER_EXCHANGE_add_auditor_denomination_cancel (
+ struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah)
+{
+ if (NULL != ah->job)
+ {
+ GNUNET_CURL_job_cancel (ah->job);
+ ah->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&ah->post_ctx);
+ GNUNET_free (ah->url);
+ GNUNET_free (ah);
+}
diff --git a/src/lib/exchange_api_batch_deposit.c b/src/lib/exchange_api_batch_deposit.c
new file mode 100644
index 000000000..3dab64526
--- /dev/null
+++ b/src/lib/exchange_api_batch_deposit.c
@@ -0,0 +1,726 @@
+/*
+ This file is part of TALER
+ 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
+ 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 lib/exchange_api_batch_deposit.c
+ * @brief Implementation of the /batch-deposit request of the exchange's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_auditor_service.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * 1:#AUDITOR_CHANCE is the probability that we report deposits
+ * to the auditor.
+ *
+ * 20==5% of going to auditor. This is possibly still too high, but set
+ * deliberately this high for testing
+ */
+#define AUDITOR_CHANCE 20
+
+
+/**
+ * Entry in list of ongoing interactions with an auditor.
+ */
+struct TEAH_AuditorInteractionEntry
+{
+ /**
+ * DLL entry.
+ */
+ struct TEAH_AuditorInteractionEntry *next;
+
+ /**
+ * DLL entry.
+ */
+ struct TEAH_AuditorInteractionEntry *prev;
+
+ /**
+ * URL of our auditor. For logging.
+ */
+ const char *auditor_url;
+
+ /**
+ * Interaction state.
+ */
+ struct TALER_AUDITOR_DepositConfirmationHandle *dch;
+
+ /**
+ * Batch deposit this is for.
+ */
+ struct TALER_EXCHANGE_BatchDepositHandle *dh;
+};
+
+
+/**
+ * @brief A Deposit Handle
+ */
+struct TALER_EXCHANGE_BatchDepositHandle
+{
+
+ /**
+ * The keys of the exchange.
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * Context for our curl request(s).
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_BatchDepositResultCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Details about the contract.
+ */
+ struct TALER_EXCHANGE_DepositContractDetail dcd;
+
+ /**
+ * Array with details about the coins.
+ */
+ struct TALER_EXCHANGE_CoinDepositDetail *cdds;
+
+ /**
+ * Hash of the merchant's wire details.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * Hash over the extensions, or all zero.
+ */
+ struct TALER_ExtensionPolicyHashP h_policy;
+
+ /**
+ * Time when this confirmation was generated / when the exchange received
+ * the deposit request.
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
+ * Exchange signature, set for #auditor_cb.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+ /**
+ * Head of DLL of interactions with this auditor.
+ */
+ struct TEAH_AuditorInteractionEntry *ai_head;
+
+ /**
+ * Tail of DLL of interactions with this auditor.
+ */
+ struct TEAH_AuditorInteractionEntry *ai_tail;
+
+ /**
+ * Result to return to the application once @e ai_head is empty.
+ */
+ struct TALER_EXCHANGE_BatchDepositResult dr;
+
+ /**
+ * Exchange signing public key, set for #auditor_cb.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Total amount deposited without fees as calculated by us.
+ */
+ struct TALER_Amount total_without_fee;
+
+ /**
+ * Response object to free at the end.
+ */
+ json_t *response;
+
+ /**
+ * Chance that we will inform the auditor about the deposit
+ * is 1:n, where the value of this field is "n".
+ */
+ unsigned int auditor_chance;
+
+ /**
+ * Length of the @e cdds array.
+ */
+ unsigned int num_cdds;
+
+};
+
+
+/**
+ * Finish batch deposit operation by calling the callback.
+ *
+ * @param[in] dh handle to finished batch deposit operation
+ */
+static void
+finish_dh (struct TALER_EXCHANGE_BatchDepositHandle *dh)
+{
+ dh->cb (dh->cb_cls,
+ &dh->dr);
+ TALER_EXCHANGE_batch_deposit_cancel (dh);
+}
+
+
+/**
+ * Function called with the result from our call to the
+ * auditor's /deposit-confirmation handler.
+ *
+ * @param cls closure of type `struct TEAH_AuditorInteractionEntry *`
+ * @param dcr response
+ */
+static void
+acc_confirmation_cb (
+ void *cls,
+ const struct TALER_AUDITOR_DepositConfirmationResponse *dcr)
+{
+ struct TEAH_AuditorInteractionEntry *aie = cls;
+ struct TALER_EXCHANGE_BatchDepositHandle *dh = aie->dh;
+
+ if (MHD_HTTP_OK != dcr->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to submit deposit confirmation to auditor `%s' with HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n",
+ aie->auditor_url,
+ dcr->hr.http_status,
+ dcr->hr.ec);
+ }
+ GNUNET_CONTAINER_DLL_remove (dh->ai_head,
+ dh->ai_tail,
+ aie);
+ GNUNET_free (aie);
+ if (NULL == dh->ai_head)
+ finish_dh (dh);
+}
+
+
+/**
+ * Function called for each auditor to give us a chance to possibly
+ * launch a deposit confirmation interaction.
+ *
+ * @param cls closure
+ * @param auditor_url base URL of the auditor
+ * @param auditor_pub public key of the auditor
+ */
+static void
+auditor_cb (void *cls,
+ const char *auditor_url,
+ const struct TALER_AuditorPublicKeyP *auditor_pub)
+{
+ struct TALER_EXCHANGE_BatchDepositHandle *dh = cls;
+ const struct TALER_EXCHANGE_SigningPublicKey *spk;
+ struct TEAH_AuditorInteractionEntry *aie;
+ const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (
+ dh->num_cdds)];
+ const struct TALER_CoinSpendPublicKeyP *cpubs[GNUNET_NZL (
+ dh->num_cdds)];
+
+ for (unsigned int i = 0; i<dh->num_cdds; i++)
+ {
+ const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &dh->cdds[i];
+
+ csigs[i] = &cdd->coin_sig;
+ cpubs[i] = &cdd->coin_pub;
+ }
+
+ if (0 !=
+ GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
+ dh->auditor_chance))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Not providing deposit confirmation to auditor\n");
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Will provide deposit confirmation to auditor `%s'\n",
+ TALER_B2S (auditor_pub));
+ spk = TALER_EXCHANGE_get_signing_key_info (dh->keys,
+ &dh->exchange_pub);
+ if (NULL == spk)
+ {
+ GNUNET_break_op (0);
+ return;
+ }
+ aie = GNUNET_new (struct TEAH_AuditorInteractionEntry);
+ aie->dh = dh;
+ aie->auditor_url = auditor_url;
+ aie->dch = TALER_AUDITOR_deposit_confirmation (
+ dh->ctx,
+ auditor_url,
+ &dh->h_wire,
+ &dh->h_policy,
+ &dh->dcd.h_contract_terms,
+ dh->exchange_timestamp,
+ dh->dcd.wire_deadline,
+ dh->dcd.refund_deadline,
+ &dh->total_without_fee,
+ dh->num_cdds,
+ cpubs,
+ csigs,
+ &dh->dcd.merchant_pub,
+ &dh->exchange_pub,
+ &dh->exchange_sig,
+ &dh->keys->master_pub,
+ spk->valid_from,
+ spk->valid_until,
+ spk->valid_legal,
+ &spk->master_sig,
+ &acc_confirmation_cb,
+ aie);
+ GNUNET_CONTAINER_DLL_insert (dh->ai_head,
+ dh->ai_tail,
+ aie);
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /deposit request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_BatchDepositHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_deposit_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_BatchDepositHandle *dh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_BatchDepositResult *dr = &dh->dr;
+
+ dh->job = NULL;
+ dh->response = json_incref ((json_t*) j);
+ dr->hr.reply = dh->response;
+ dr->hr.http_status = (unsigned int) response_code;
+ switch (response_code)
+ {
+ case 0:
+ dr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &dh->exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &dh->exchange_pub),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("transaction_base_url",
+ &dr->details.ok.transaction_base_url),
+ NULL),
+ GNUNET_JSON_spec_timestamp ("exchange_timestamp",
+ &dh->exchange_timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (dh->keys,
+ &dh->exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
+ }
+ {
+ const struct TALER_CoinSpendSignatureP *csigs[
+ GNUNET_NZL (dh->num_cdds)];
+
+ for (unsigned int i = 0; i<dh->num_cdds; i++)
+ csigs[i] = &dh->cdds[i].coin_sig;
+ if (GNUNET_OK !=
+ TALER_exchange_online_deposit_confirmation_verify (
+ &dh->dcd.h_contract_terms,
+ &dh->h_wire,
+ &dh->h_policy,
+ dh->exchange_timestamp,
+ dh->dcd.wire_deadline,
+ dh->dcd.refund_deadline,
+ &dh->total_without_fee,
+ dh->num_cdds,
+ csigs,
+ &dh->dcd.merchant_pub,
+ &dh->exchange_pub,
+ &dh->exchange_sig))
+ {
+ GNUNET_break_op (0);
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
+ }
+ }
+ TEAH_get_auditors_for_dc (dh->keys,
+ &auditor_cb,
+ dh);
+ }
+ dr->details.ok.exchange_sig = &dh->exchange_sig;
+ dr->details.ok.exchange_pub = &dh->exchange_pub;
+ dr->details.ok.deposit_timestamp = dh->exchange_timestamp;
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &dr->details.conflict.coin_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
+ }
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange deposit\n",
+ (unsigned int) response_code,
+ dr->hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ if (NULL != dh->ai_head)
+ return;
+ finish_dh (dh);
+}
+
+
+struct TALER_EXCHANGE_BatchDepositHandle *
+TALER_EXCHANGE_batch_deposit (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DepositContractDetail *dcd,
+ unsigned int num_cdds,
+ const struct TALER_EXCHANGE_CoinDepositDetail cdds[static num_cdds],
+ TALER_EXCHANGE_BatchDepositResultCallback cb,
+ void *cb_cls,
+ enum TALER_ErrorCode *ec)
+{
+ struct TALER_EXCHANGE_BatchDepositHandle *dh;
+ json_t *deposit_obj;
+ json_t *deposits;
+ CURL *eh;
+ const struct GNUNET_HashCode *wallet_data_hashp;
+
+ if (0 == num_cdds)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (GNUNET_TIME_timestamp_cmp (dcd->refund_deadline,
+ >,
+ dcd->wire_deadline))
+ {
+ GNUNET_break_op (0);
+ *ec = TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE;
+ return NULL;
+ }
+ dh = GNUNET_new (struct TALER_EXCHANGE_BatchDepositHandle);
+ dh->auditor_chance = AUDITOR_CHANCE;
+ dh->cb = cb;
+ dh->cb_cls = cb_cls;
+ dh->cdds = GNUNET_memdup (cdds,
+ num_cdds * sizeof (*cdds));
+ dh->num_cdds = num_cdds;
+ dh->dcd = *dcd;
+ if (NULL != dcd->policy_details)
+ TALER_deposit_policy_hash (dcd->policy_details,
+ &dh->h_policy);
+ TALER_merchant_wire_signature_hash (dcd->merchant_payto_uri,
+ &dcd->wire_salt,
+ &dh->h_wire);
+ deposits = json_array ();
+ GNUNET_assert (NULL != deposits);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (cdds[0].amount.currency,
+ &dh->total_without_fee));
+ for (unsigned int i = 0; i<num_cdds; i++)
+ {
+ const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i];
+ const struct TALER_EXCHANGE_DenomPublicKey *dki;
+ const struct TALER_AgeCommitmentHash *h_age_commitmentp;
+ struct TALER_Amount amount_without_fee;
+
+ dki = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+ &cdd->h_denom_pub);
+ if (NULL == dki)
+ {
+ *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ GNUNET_break_op (0);
+ json_decref (deposits);
+ return NULL;
+ }
+ if (0 >
+ TALER_amount_subtract (&amount_without_fee,
+ &cdd->amount,
+ &dki->fees.deposit))
+ {
+ *ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT;
+ GNUNET_break_op (0);
+ GNUNET_free (dh->cdds);
+ GNUNET_free (dh);
+ json_decref (deposits);
+ return NULL;
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&dh->total_without_fee,
+ &dh->total_without_fee,
+ &amount_without_fee));
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_verify_deposit_signature_ (dcd,
+ &dh->h_policy,
+ &dh->h_wire,
+ cdd,
+ dki))
+ {
+ *ec = TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID;
+ GNUNET_break_op (0);
+ GNUNET_free (dh->cdds);
+ GNUNET_free (dh);
+ json_decref (deposits);
+ return NULL;
+ }
+ if (GNUNET_is_zero (&cdd->h_age_commitment))
+ h_age_commitmentp = NULL;
+ else
+ h_age_commitmentp = &cdd->h_age_commitment;
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ deposits,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("contribution",
+ &cdd->amount),
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &cdd->h_denom_pub),
+ TALER_JSON_pack_denom_sig ("ub_sig",
+ &cdd->denom_sig),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &cdd->coin_pub),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ h_age_commitmentp)),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &cdd->coin_sig)
+ )));
+ }
+ dh->url = TALER_url_join (url,
+ "batch-deposit",
+ NULL);
+ if (NULL == dh->url)
+ {
+ GNUNET_break (0);
+ *ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
+ GNUNET_free (dh->url);
+ GNUNET_free (dh->cdds);
+ GNUNET_free (dh);
+ json_decref (deposits);
+ return NULL;
+ }
+
+ if (GNUNET_is_zero (&dcd->wallet_data_hash))
+ wallet_data_hashp = NULL;
+ else
+ wallet_data_hashp = &dcd->wallet_data_hash;
+
+ deposit_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("merchant_payto_uri",
+ dcd->merchant_payto_uri),
+ GNUNET_JSON_pack_data_auto ("wire_salt",
+ &dcd->wire_salt),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &dcd->h_contract_terms),
+ GNUNET_JSON_pack_array_steal ("coins",
+ deposits),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("wallet_data_hash",
+ wallet_data_hashp)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_steal ("policy_details",
+ (json_t *) dcd->policy_details)),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ dcd->wallet_timestamp),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ &dcd->merchant_pub),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("refund_deadline",
+ dcd->refund_deadline)),
+ GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
+ dcd->wire_deadline));
+ GNUNET_assert (NULL != deposit_obj);
+ eh = TALER_EXCHANGE_curl_easy_get_ (dh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&dh->post_ctx,
+ eh,
+ deposit_obj)) )
+ {
+ *ec = TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE;
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (deposit_obj);
+ GNUNET_free (dh->cdds);
+ GNUNET_free (dh->url);
+ GNUNET_free (dh);
+ return NULL;
+ }
+ json_decref (deposit_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for deposit: `%s'\n",
+ dh->url);
+ dh->ctx = ctx;
+ dh->keys = TALER_EXCHANGE_keys_incref (keys);
+ dh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ dh->post_ctx.headers,
+ &handle_deposit_finished,
+ dh);
+ return dh;
+}
+
+
+void
+TALER_EXCHANGE_batch_deposit_force_dc (
+ struct TALER_EXCHANGE_BatchDepositHandle *deposit)
+{
+ deposit->auditor_chance = 1;
+}
+
+
+void
+TALER_EXCHANGE_batch_deposit_cancel (
+ struct TALER_EXCHANGE_BatchDepositHandle *deposit)
+{
+ struct TEAH_AuditorInteractionEntry *aie;
+
+ while (NULL != (aie = deposit->ai_head))
+ {
+ GNUNET_assert (aie->dh == deposit);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Not sending deposit confirmation to auditor `%s' due to cancellation\n",
+ aie->auditor_url);
+ TALER_AUDITOR_deposit_confirmation_cancel (aie->dch);
+ GNUNET_CONTAINER_DLL_remove (deposit->ai_head,
+ deposit->ai_tail,
+ aie);
+ GNUNET_free (aie);
+ }
+ if (NULL != deposit->job)
+ {
+ GNUNET_CURL_job_cancel (deposit->job);
+ deposit->job = NULL;
+ }
+ TALER_EXCHANGE_keys_decref (deposit->keys);
+ GNUNET_free (deposit->url);
+ GNUNET_free (deposit->cdds);
+ TALER_curl_easy_post_finished (&deposit->post_ctx);
+ json_decref (deposit->response);
+ GNUNET_free (deposit);
+}
+
+
+/* end of exchange_api_batch_deposit.c */
diff --git a/src/lib/exchange_api_batch_withdraw.c b/src/lib/exchange_api_batch_withdraw.c
new file mode 100644
index 000000000..a1b21f347
--- /dev/null
+++ b/src/lib/exchange_api_batch_withdraw.c
@@ -0,0 +1,463 @@
+/*
+ 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/>
+*/
+/**
+ * @file lib/exchange_api_batch_withdraw.c
+ * @brief Implementation of /reserves/$RESERVE_PUB/batch-withdraw requests with blinding/unblinding
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Data we keep per coin in the batch.
+ */
+struct CoinData
+{
+
+ /**
+ * Denomination key we are withdrawing.
+ */
+ struct TALER_EXCHANGE_DenomPublicKey pk;
+
+ /**
+ * Master key material for the coin.
+ */
+ struct TALER_PlanchetMasterSecretP ps;
+
+ /**
+ * Age commitment for the coin.
+ */
+ const struct TALER_AgeCommitmentHash *ach;
+
+ /**
+ * blinding secret
+ */
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+
+ /**
+ * Session nonce.
+ */
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+
+ /**
+ * Private key of the coin we are withdrawing.
+ */
+ struct TALER_CoinSpendPrivateKeyP priv;
+
+ /**
+ * Details of the planchet.
+ */
+ struct TALER_PlanchetDetail pd;
+
+ /**
+ * Values of the cipher selected
+ */
+ struct TALER_ExchangeWithdrawValues alg_values;
+
+ /**
+ * Hash of the public key of the coin we are signing.
+ */
+ struct TALER_CoinPubHashP c_hash;
+
+ /**
+ * Handler for the CS R request (only used for GNUNET_CRYPTO_BSA_CS denominations)
+ */
+ struct TALER_EXCHANGE_CsRWithdrawHandle *csrh;
+
+ /**
+ * Batch withdraw this coin is part of.
+ */
+ struct TALER_EXCHANGE_BatchWithdrawHandle *wh;
+};
+
+
+/**
+ * @brief A batch withdraw handle
+ */
+struct TALER_EXCHANGE_BatchWithdrawHandle
+{
+
+ /**
+ * The curl context to use
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ /**
+ * The base URL to the exchange
+ */
+ const char *exchange_url;
+
+ /**
+ * The /keys information from the exchange
+ */
+ const struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * Handle for the actual (internal) batch withdraw operation.
+ */
+ struct TALER_EXCHANGE_BatchWithdraw2Handle *wh2;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_BatchWithdrawCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reserve private key.
+ */
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ /**
+ * Array of per-coin data.
+ */
+ struct CoinData *coins;
+
+ /**
+ * Length of the @e coins array.
+ */
+ unsigned int num_coins;
+
+ /**
+ * Number of CS requests still pending.
+ */
+ unsigned int cs_pending;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RESERVE_PUB/batch-withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_BatchWithdrawHandle`
+ * @param bw2r response data
+ */
+static void
+handle_reserve_batch_withdraw_finished (
+ void *cls,
+ const struct TALER_EXCHANGE_BatchWithdraw2Response *bw2r)
+{
+ struct TALER_EXCHANGE_BatchWithdrawHandle *wh = cls;
+ struct TALER_EXCHANGE_BatchWithdrawResponse wr = {
+ .hr = bw2r->hr
+ };
+ struct TALER_EXCHANGE_PrivateCoinDetails coins[GNUNET_NZL (wh->num_coins)];
+
+ wh->wh2 = NULL;
+ memset (coins,
+ 0,
+ sizeof (coins));
+ switch (bw2r->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ if (bw2r->details.ok.blind_sigs_length != wh->num_coins)
+ {
+ GNUNET_break_op (0);
+ wr.hr.http_status = 0;
+ wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ for (unsigned int i = 0; i<wh->num_coins; i++)
+ {
+ struct CoinData *cd = &wh->coins[i];
+ struct TALER_EXCHANGE_PrivateCoinDetails *coin = &coins[i];
+ struct TALER_FreshCoin fc;
+
+ if (GNUNET_OK !=
+ TALER_planchet_to_coin (&cd->pk.key,
+ &bw2r->details.ok.blind_sigs[i],
+ &cd->bks,
+ &cd->priv,
+ cd->ach,
+ &cd->c_hash,
+ &cd->alg_values,
+ &fc))
+ {
+ wr.hr.http_status = 0;
+ wr.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
+ break;
+ }
+ coin->coin_priv = cd->priv;
+ coin->bks = cd->bks;
+ coin->sig = fc.sig;
+ coin->exchange_vals = cd->alg_values;
+ }
+ wr.details.ok.coins = coins;
+ wr.details.ok.num_coins = wh->num_coins;
+ break;
+ }
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &wr.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &wr.details.unavailable_for_legal_reasons.requirement_row),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (bw2r->hr.reply,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ wr.hr.http_status = 0;
+ wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ wh->cb (wh->cb_cls,
+ &wr);
+ for (unsigned int i = 0; i<wh->num_coins; i++)
+ TALER_denom_sig_free (&coins[i].sig);
+ TALER_EXCHANGE_batch_withdraw_cancel (wh);
+}
+
+
+/**
+ * Runs phase two, the actual withdraw operation.
+ * Started once the preparation for CS-denominations is
+ * done.
+ *
+ * @param[in,out] wh batch withdraw to start phase 2 for
+ */
+static void
+phase_two (struct TALER_EXCHANGE_BatchWithdrawHandle *wh)
+{
+ struct TALER_PlanchetDetail pds[wh->num_coins];
+
+ for (unsigned int i = 0; i<wh->num_coins; i++)
+ {
+ struct CoinData *cd = &wh->coins[i];
+
+ pds[i] = cd->pd;
+ }
+ wh->wh2 = TALER_EXCHANGE_batch_withdraw2 (
+ wh->curl_ctx,
+ wh->exchange_url,
+ wh->keys,
+ wh->reserve_priv,
+ wh->num_coins,
+ pds,
+ &handle_reserve_batch_withdraw_finished,
+ wh);
+}
+
+
+/**
+ * Function called when stage 1 of CS withdraw is finished (request r_pub's)
+ *
+ * @param cls the `struct CoinData *`
+ * @param csrr replies from the /csr-withdraw request
+ */
+static void
+withdraw_cs_stage_two_callback (
+ void *cls,
+ const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
+{
+ struct CoinData *cd = cls;
+ struct TALER_EXCHANGE_BatchWithdrawHandle *wh = cd->wh;
+ struct TALER_EXCHANGE_BatchWithdrawResponse wr = {
+ .hr = csrr->hr
+ };
+
+ cd->csrh = NULL;
+ GNUNET_assert (GNUNET_CRYPTO_BSA_CS ==
+ cd->pk.key.bsign_pub_key->cipher);
+ switch (csrr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ GNUNET_assert (NULL ==
+ cd->alg_values.blinding_inputs);
+ TALER_denom_ewv_copy (&cd->alg_values,
+ &csrr->details.ok.alg_values);
+ TALER_planchet_setup_coin_priv (&cd->ps,
+ &cd->alg_values,
+ &cd->priv);
+ TALER_planchet_blinding_secret_create (&cd->ps,
+ &cd->alg_values,
+ &cd->bks);
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&cd->pk.key,
+ &cd->alg_values,
+ &cd->bks,
+ &cd->nonce,
+ &cd->priv,
+ cd->ach,
+ &cd->c_hash,
+ &cd->pd))
+ {
+ GNUNET_break (0);
+ wr.hr.http_status = 0;
+ wr.hr.ec = TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR;
+ wh->cb (wh->cb_cls,
+ &wr);
+ TALER_EXCHANGE_batch_withdraw_cancel (wh);
+ return;
+ }
+ wh->cs_pending--;
+ if (0 == wh->cs_pending)
+ phase_two (wh);
+ return;
+ default:
+ break;
+ }
+ wh->cb (wh->cb_cls,
+ &wr);
+ TALER_EXCHANGE_batch_withdraw_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_BatchWithdrawHandle *
+TALER_EXCHANGE_batch_withdraw (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ unsigned int wci_length,
+ const struct TALER_EXCHANGE_WithdrawCoinInput wcis[static wci_length],
+ TALER_EXCHANGE_BatchWithdrawCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_BatchWithdrawHandle *wh;
+
+ wh = GNUNET_new (struct TALER_EXCHANGE_BatchWithdrawHandle);
+ wh->curl_ctx = curl_ctx;
+ wh->exchange_url = exchange_url;
+ wh->keys = keys;
+ wh->cb = res_cb;
+ wh->cb_cls = res_cb_cls;
+ wh->reserve_priv = reserve_priv;
+ wh->num_coins = wci_length;
+ wh->coins = GNUNET_new_array (wh->num_coins,
+ struct CoinData);
+ for (unsigned int i = 0; i<wci_length; i++)
+ {
+ struct CoinData *cd = &wh->coins[i];
+ const struct TALER_EXCHANGE_WithdrawCoinInput *wci = &wcis[i];
+
+ cd->wh = wh;
+ cd->ps = *wci->ps;
+ cd->ach = wci->ach;
+ cd->pk = *wci->pk;
+ TALER_denom_pub_copy (&cd->pk.key,
+ &wci->pk->key);
+ switch (wci->pk->key.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ TALER_denom_ewv_copy (&cd->alg_values,
+ TALER_denom_ewv_rsa_singleton ());
+ TALER_planchet_setup_coin_priv (&cd->ps,
+ &cd->alg_values,
+ &cd->priv);
+ TALER_planchet_blinding_secret_create (&cd->ps,
+ &cd->alg_values,
+ &cd->bks);
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&cd->pk.key,
+ &cd->alg_values,
+ &cd->bks,
+ NULL,
+ &cd->priv,
+ cd->ach,
+ &cd->c_hash,
+ &cd->pd))
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_batch_withdraw_cancel (wh);
+ return NULL;
+ }
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ TALER_cs_withdraw_nonce_derive (
+ &cd->ps,
+ &cd->nonce.cs_nonce);
+ cd->csrh = TALER_EXCHANGE_csr_withdraw (
+ curl_ctx,
+ exchange_url,
+ &cd->pk,
+ &cd->nonce.cs_nonce,
+ &withdraw_cs_stage_two_callback,
+ cd);
+ if (NULL == cd->csrh)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_batch_withdraw_cancel (wh);
+ return NULL;
+ }
+ wh->cs_pending++;
+ break;
+ default:
+ GNUNET_break (0);
+ TALER_EXCHANGE_batch_withdraw_cancel (wh);
+ return NULL;
+ }
+ }
+ if (0 == wh->cs_pending)
+ phase_two (wh);
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_batch_withdraw_cancel (
+ struct TALER_EXCHANGE_BatchWithdrawHandle *wh)
+{
+ for (unsigned int i = 0; i<wh->num_coins; i++)
+ {
+ struct CoinData *cd = &wh->coins[i];
+
+ if (NULL != cd->csrh)
+ {
+ TALER_EXCHANGE_csr_withdraw_cancel (cd->csrh);
+ cd->csrh = NULL;
+ }
+ TALER_denom_ewv_free (&cd->alg_values);
+ TALER_blinded_planchet_free (&cd->pd.blinded_planchet);
+ TALER_denom_pub_free (&cd->pk.key);
+ }
+ GNUNET_free (wh->coins);
+ if (NULL != wh->wh2)
+ {
+ TALER_EXCHANGE_batch_withdraw2_cancel (wh->wh2);
+ wh->wh2 = NULL;
+ }
+ GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_batch_withdraw2.c b/src/lib/exchange_api_batch_withdraw2.c
new file mode 100644
index 000000000..ff1496466
--- /dev/null
+++ b/src/lib/exchange_api_batch_withdraw2.c
@@ -0,0 +1,441 @@
+/*
+ This file is part of TALER
+ 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
+ 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 lib/exchange_api_batch_withdraw2.c
+ * @brief Implementation of /reserves/$RESERVE_PUB/batch-withdraw requests without blinding/unblinding
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A batch withdraw handle
+ */
+struct TALER_EXCHANGE_BatchWithdraw2Handle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * The /keys material from the exchange
+ */
+ const struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_BatchWithdraw2Callback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Total amount requested (value plus withdraw fee).
+ */
+ struct TALER_Amount requested_amount;
+
+ /**
+ * Public key of the reserve we are withdrawing from.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Number of coins expected.
+ */
+ unsigned int num_coins;
+};
+
+
+/**
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/batch-withdraw operation.
+ * Extract the coin's signature and return it to the caller. The signature we
+ * get from the exchange is for the blinded value. As we do not have the
+ * blinding factor, the signature CANNOT be verified.
+ *
+ * If everything checks out, we return the unblinded signature
+ * to the application via the callback.
+ *
+ * @param wh operation handle
+ * @param json reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+reserve_batch_withdraw_ok (struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
+ const json_t *json)
+{
+ struct TALER_BlindedDenominationSignature blind_sigs[GNUNET_NZL (
+ wh->num_coins)];
+ const json_t *ja = json_object_get (json,
+ "ev_sigs");
+ const json_t *j;
+ size_t index;
+ struct TALER_EXCHANGE_BatchWithdraw2Response bwr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+
+ if ( (NULL == ja) ||
+ (! json_is_array (ja)) ||
+ (wh->num_coins != json_array_size (ja)) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ json_array_foreach (ja, index, j)
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_blinded_denom_sig ("ev_sig",
+ &blind_sigs[index]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ for (size_t i = 0; i<index; i++)
+ TALER_blinded_denom_sig_free (&blind_sigs[i]);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ /* signature is valid, return it to the application */
+ bwr.details.ok.blind_sigs = blind_sigs;
+ bwr.details.ok.blind_sigs_length = wh->num_coins;
+ wh->cb (wh->cb_cls,
+ &bwr);
+ /* make sure callback isn't called again after return */
+ wh->cb = NULL;
+ for (unsigned int i = 0; i<wh->num_coins; i++)
+ TALER_blinded_denom_sig_free (&blind_sigs[i]);
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RESERVE_PUB/batch-withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_BatchWithdraw2Handle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserve_batch_withdraw_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_BatchWithdraw2Handle *wh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_BatchWithdraw2Response bwr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ bwr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ reserve_batch_withdraw_ok (wh,
+ j))
+ {
+ GNUNET_break_op (0);
+ bwr.hr.http_status = 0;
+ bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == wh->cb);
+ TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+ return;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ GNUNET_break_op (0);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, the exchange basically just says
+ that it doesn't know this reserve. Can happen if we
+ query before the wire transfer went through.
+ We should simply pass the JSON reply to the application. */
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &bwr.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 ("requirement_row",
+ &bwr.details.unavailable_for_legal_reasons.
+ kyc_requirement_id),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ bwr.hr.http_status = 0;
+ bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange batch withdraw\n",
+ (unsigned int) response_code,
+ (int) bwr.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &bwr);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_BatchWithdraw2Handle *
+TALER_EXCHANGE_batch_withdraw2 (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ unsigned int pds_length,
+ const struct TALER_PlanchetDetail pds[static pds_length],
+ TALER_EXCHANGE_BatchWithdraw2Callback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_BatchWithdraw2Handle *wh;
+ const struct TALER_EXCHANGE_DenomPublicKey *dk;
+ struct TALER_ReserveSignatureP reserve_sig;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+ struct TALER_BlindedCoinHashP bch;
+ json_t *jc;
+
+ GNUNET_assert (NULL != keys);
+ wh = GNUNET_new (struct TALER_EXCHANGE_BatchWithdraw2Handle);
+ wh->keys = keys;
+ wh->cb = res_cb;
+ wh->cb_cls = res_cb_cls;
+ wh->num_coins = pds_length;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (keys->currency,
+ &wh->requested_amount));
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &wh->reserve_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &wh->reserve_pub,
+ sizeof (struct TALER_ReservePublicKeyP),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/batch-withdraw",
+ pub_str);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Attempting to batch-withdraw from reserve %s\n",
+ TALER_B2S (&wh->reserve_pub));
+ wh->url = TALER_url_join (exchange_url,
+ arg_str,
+ NULL);
+ if (NULL == wh->url)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+ return NULL;
+ }
+ jc = json_array ();
+ GNUNET_assert (NULL != jc);
+ for (unsigned int i = 0; i<pds_length; i++)
+ {
+ const struct TALER_PlanchetDetail *pd = &pds[i];
+ struct TALER_Amount coin_total;
+ json_t *withdraw_obj;
+
+ dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+ &pd->denom_pub_hash);
+ if (NULL == dk)
+ {
+ TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+ json_decref (jc);
+ GNUNET_break (0);
+ return NULL;
+ }
+ /* Compute how much we expected to charge to the reserve */
+ if (0 >
+ TALER_amount_add (&coin_total,
+ &dk->fees.withdraw,
+ &dk->value))
+ {
+ /* Overflow here? Very strange, our CPU must be fried... */
+ GNUNET_break (0);
+ TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+ json_decref (jc);
+ return NULL;
+ }
+ if (0 >
+ TALER_amount_add (&wh->requested_amount,
+ &wh->requested_amount,
+ &coin_total))
+ {
+ /* Overflow here? Very strange, our CPU must be fried... */
+ GNUNET_break (0);
+ TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+ json_decref (jc);
+ return NULL;
+ }
+ TALER_coin_ev_hash (&pd->blinded_planchet,
+ &pd->denom_pub_hash,
+ &bch);
+ TALER_wallet_withdraw_sign (&pd->denom_pub_hash,
+ &coin_total,
+ &bch,
+ reserve_priv,
+ &reserve_sig);
+ withdraw_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &pd->denom_pub_hash),
+ TALER_JSON_pack_blinded_planchet ("coin_ev",
+ &pd->blinded_planchet),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &reserve_sig));
+ GNUNET_assert (NULL != withdraw_obj);
+ GNUNET_assert (0 ==
+ json_array_append_new (jc,
+ withdraw_obj));
+ }
+ {
+ CURL *eh;
+ json_t *req;
+
+ req = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("planchets",
+ jc));
+ eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&wh->post_ctx,
+ eh,
+ req)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (req);
+ TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+ return NULL;
+ }
+ json_decref (req);
+ wh->job = GNUNET_CURL_job_add2 (curl_ctx,
+ eh,
+ wh->post_ctx.headers,
+ &handle_reserve_batch_withdraw_finished,
+ wh);
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_batch_withdraw2_cancel (
+ struct TALER_EXCHANGE_BatchWithdraw2Handle *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ GNUNET_free (wh->url);
+ TALER_curl_easy_post_finished (&wh->post_ctx);
+ GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_coins_history.c b/src/lib/exchange_api_coins_history.c
new file mode 100644
index 000000000..0999e185e
--- /dev/null
+++ b/src/lib/exchange_api_coins_history.c
@@ -0,0 +1,1230 @@
+/*
+ This file is part of TALER
+ 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
+ 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 lib/exchange_api_coins_history.c
+ * @brief Implementation of the POST /coins/$COIN_PUB/history requests
+ * @author Christian Grothoff
+ *
+ * NOTE: this is an incomplete draft, never finished!
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP history codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /coins/$RID/history Handle
+ */
+struct TALER_EXCHANGE_CoinsHistoryHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_CoinsHistoryCallback cb;
+
+ /**
+ * Public key of the coin we are querying.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Context for coin helpers.
+ */
+struct CoinHistoryParseContext
+{
+
+ /**
+ * Keys of the exchange.
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * Denomination of the coin.
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *dk;
+
+ /**
+ * Our coin public key.
+ */
+ const struct TALER_CoinSpendPublicKeyP *coin_pub;
+
+ /**
+ * Where to sum up total refunds.
+ */
+ struct TALER_Amount *total_in;
+
+ /**
+ * Total amount encountered.
+ */
+ struct TALER_Amount *total_out;
+
+};
+
+
+/**
+ * Signature of functions that operate on one of
+ * the coin's history entries.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh where to write the history entry
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+typedef enum GNUNET_GenericReturnValue
+(*CoinCheckHelper)(struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction);
+
+
+/**
+ * Handle deposit entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_deposit (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.deposit.sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &rh->details.deposit.h_contract_terms),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
+ &rh->details.deposit.wallet_data_hash),
+ &rh->details.deposit.no_wallet_data_hash),
+ GNUNET_JSON_spec_fixed_auto ("h_wire",
+ &rh->details.deposit.h_wire),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &rh->details.deposit.hac),
+ &rh->details.deposit.no_hac),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_policy",
+ &rh->details.deposit.h_policy),
+ &rh->details.deposit.no_h_policy),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rh->details.deposit.wallet_timestamp),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("refund_deadline",
+ &rh->details.deposit.refund_deadline),
+ NULL),
+ TALER_JSON_spec_amount_any ("deposit_fee",
+ &rh->details.deposit.deposit_fee),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &rh->details.deposit.merchant_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->details.deposit.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_deposit_verify (
+ amount,
+ &rh->details.deposit.deposit_fee,
+ &rh->details.deposit.h_wire,
+ &rh->details.deposit.h_contract_terms,
+ rh->details.deposit.no_wallet_data_hash
+ ? NULL
+ : &rh->details.deposit.wallet_data_hash,
+ rh->details.deposit.no_hac
+ ? NULL
+ : &rh->details.deposit.hac,
+ rh->details.deposit.no_h_policy
+ ? NULL
+ : &rh->details.deposit.h_policy,
+ &pc->dk->h_key,
+ rh->details.deposit.wallet_timestamp,
+ &rh->details.deposit.merchant_pub,
+ rh->details.deposit.refund_deadline,
+ pc->coin_pub,
+ &rh->details.deposit.sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* check that deposit fee matches our expectations from /keys! */
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&rh->details.deposit.deposit_fee,
+ &pc->dk->fees.deposit)) ||
+ (0 !=
+ TALER_amount_cmp (&rh->details.deposit.deposit_fee,
+ &pc->dk->fees.deposit)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_YES;
+}
+
+
+/**
+ * Handle melt entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_melt (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.melt.sig),
+ GNUNET_JSON_spec_fixed_auto ("rc",
+ &rh->details.melt.rc),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &rh->details.melt.h_age_commitment),
+ &rh->details.melt.no_hac),
+ TALER_JSON_spec_amount_any ("melt_fee",
+ &rh->details.melt.melt_fee),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* check that melt fee matches our expectations from /keys! */
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&rh->details.melt.melt_fee,
+ &pc->dk->fees.refresh)) ||
+ (0 !=
+ TALER_amount_cmp (&rh->details.melt.melt_fee,
+ &pc->dk->fees.refresh)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_melt_verify (
+ amount,
+ &rh->details.melt.melt_fee,
+ &rh->details.melt.rc,
+ &pc->dk->h_key,
+ rh->details.melt.no_hac
+ ? NULL
+ : &rh->details.melt.h_age_commitment,
+ pc->coin_pub,
+ &rh->details.melt.sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_YES;
+}
+
+
+/**
+ * Handle refund entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_refund (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("refund_fee",
+ &rh->details.refund.refund_fee),
+ GNUNET_JSON_spec_fixed_auto ("merchant_sig",
+ &rh->details.refund.sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &rh->details.refund.h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &rh->details.refund.merchant_pub),
+ GNUNET_JSON_spec_uint64 ("rtransaction_id",
+ &rh->details.refund.rtransaction_id),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_add (&rh->details.refund.sig_amount,
+ &rh->details.refund.refund_fee,
+ amount))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_merchant_refund_verify (pc->coin_pub,
+ &rh->details.refund.h_contract_terms,
+ rh->details.refund.rtransaction_id,
+ &rh->details.refund.sig_amount,
+ &rh->details.refund.merchant_pub,
+ &rh->details.refund.sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* NOTE: theoretically, we could also check that the given
+ merchant_pub and h_contract_terms appear in the
+ history under deposits. However, there is really no benefit
+ for the exchange to lie here, so not checking is probably OK
+ (an auditor ought to check, though). Then again, we similarly
+ had no reason to check the merchant's signature (other than a
+ well-formendess check). */
+
+ /* check that refund fee matches our expectations from /keys! */
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&rh->details.refund.refund_fee,
+ &pc->dk->fees.refund)) ||
+ (0 !=
+ TALER_amount_cmp (&rh->details.refund.refund_fee,
+ &pc->dk->fees.refund)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_NO;
+}
+
+
+/**
+ * Handle recoup entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_recoup (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rh->details.recoup.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rh->details.recoup.exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &rh->details.recoup.reserve_pub),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.recoup.coin_sig),
+ GNUNET_JSON_spec_fixed_auto ("coin_blind",
+ &rh->details.recoup.coin_bks),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rh->details.recoup.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_confirm_recoup_verify (
+ rh->details.recoup.timestamp,
+ amount,
+ pc->coin_pub,
+ &rh->details.recoup.reserve_pub,
+ &rh->details.recoup.exchange_pub,
+ &rh->details.recoup.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_recoup_verify (&pc->dk->h_key,
+ &rh->details.recoup.coin_bks,
+ pc->coin_pub,
+ &rh->details.recoup.coin_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_YES;
+}
+
+
+/**
+ * Handle recoup-refresh entry in the coin's history.
+ * This is the coin that was subjected to a recoup,
+ * the value being credited to the old coin.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_recoup_refresh (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rh->details.recoup_refresh.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rh->details.recoup_refresh.exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.recoup_refresh.coin_sig),
+ GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
+ &rh->details.recoup_refresh.old_coin_pub),
+ GNUNET_JSON_spec_fixed_auto ("coin_blind",
+ &rh->details.recoup_refresh.coin_bks),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rh->details.recoup_refresh.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_confirm_recoup_refresh_verify (
+ rh->details.recoup_refresh.timestamp,
+ amount,
+ pc->coin_pub,
+ &rh->details.recoup_refresh.old_coin_pub,
+ &rh->details.recoup_refresh.exchange_pub,
+ &rh->details.recoup_refresh.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_recoup_verify (&pc->dk->h_key,
+ &rh->details.recoup_refresh.coin_bks,
+ pc->coin_pub,
+ &rh->details.recoup_refresh.coin_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_YES;
+}
+
+
+/**
+ * Handle old coin recoup entry in the coin's history.
+ * This is the coin that was credited in a recoup,
+ * the value being credited to the this coin.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_old_coin_recoup (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rh->details.old_coin_recoup.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rh->details.old_coin_recoup.exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &rh->details.old_coin_recoup.new_coin_pub),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rh->details.old_coin_recoup.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_confirm_recoup_refresh_verify (
+ rh->details.old_coin_recoup.timestamp,
+ amount,
+ &rh->details.old_coin_recoup.new_coin_pub,
+ pc->coin_pub,
+ &rh->details.old_coin_recoup.exchange_pub,
+ &rh->details.old_coin_recoup.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_NO;
+}
+
+
+/**
+ * Handle purse deposit entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_purse_deposit (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("purse_pub",
+ &rh->details.purse_deposit.purse_pub),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.purse_deposit.coin_sig),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &rh->details.purse_deposit.phac),
+ NULL),
+ TALER_JSON_spec_web_url ("exchange_base_url",
+ &rh->details.purse_deposit.exchange_base_url),
+ GNUNET_JSON_spec_bool ("refunded",
+ &rh->details.purse_deposit.refunded),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_purse_deposit_verify (
+ rh->details.purse_deposit.exchange_base_url,
+ &rh->details.purse_deposit.purse_pub,
+ amount,
+ &pc->dk->h_key,
+ &rh->details.purse_deposit.phac,
+ pc->coin_pub,
+ &rh->details.purse_deposit.coin_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (rh->details.purse_deposit.refunded)
+ {
+ /* We wave the deposit fee. */
+ if (0 >
+ TALER_amount_add (pc->total_in,
+ pc->total_in,
+ &pc->dk->fees.deposit))
+ {
+ /* overflow in refund history? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_YES;
+}
+
+
+/**
+ * Handle purse refund entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_purse_refund (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("refund_fee",
+ &rh->details.purse_refund.refund_fee),
+ GNUNET_JSON_spec_fixed_auto ("purse_pub",
+ &rh->details.purse_refund.purse_pub),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rh->details.purse_refund.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rh->details.purse_refund.exchange_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_purse_refund_verify (
+ amount,
+ &rh->details.purse_refund.refund_fee,
+ pc->coin_pub,
+ &rh->details.purse_refund.purse_pub,
+ &rh->details.purse_refund.exchange_pub,
+ &rh->details.purse_refund.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&rh->details.purse_refund.refund_fee,
+ &pc->dk->fees.refund)) ||
+ (0 !=
+ TALER_amount_cmp (&rh->details.purse_refund.refund_fee,
+ &pc->dk->fees.refund)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_NO;
+}
+
+
+/**
+ * Handle reserve deposit entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_reserve_open_deposit (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rh->details.reserve_open_deposit.reserve_sig),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.reserve_open_deposit.coin_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_open_deposit_verify (
+ amount,
+ &rh->details.reserve_open_deposit.reserve_sig,
+ pc->coin_pub,
+ &rh->details.reserve_open_deposit.coin_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_YES;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_parse_coin_history (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DenomPublicKey *dk,
+ const json_t *history,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_Amount *total_in,
+ struct TALER_Amount *total_out,
+ unsigned int rlen,
+ struct TALER_EXCHANGE_CoinHistoryEntry rhistory[static rlen])
+{
+ const struct
+ {
+ const char *type;
+ CoinCheckHelper helper;
+ enum TALER_EXCHANGE_CoinTransactionType ctt;
+ } map[] = {
+ { "DEPOSIT",
+ &help_deposit,
+ TALER_EXCHANGE_CTT_DEPOSIT },
+ { "MELT",
+ &help_melt,
+ TALER_EXCHANGE_CTT_MELT },
+ { "REFUND",
+ &help_refund,
+ TALER_EXCHANGE_CTT_REFUND },
+ { "RECOUP",
+ &help_recoup,
+ TALER_EXCHANGE_CTT_RECOUP },
+ { "RECOUP-REFRESH",
+ &help_recoup_refresh,
+ TALER_EXCHANGE_CTT_RECOUP_REFRESH },
+ { "OLD-COIN-RECOUP",
+ &help_old_coin_recoup,
+ TALER_EXCHANGE_CTT_OLD_COIN_RECOUP },
+ { "PURSE-DEPOSIT",
+ &help_purse_deposit,
+ TALER_EXCHANGE_CTT_PURSE_DEPOSIT },
+ { "PURSE-REFUND",
+ &help_purse_refund,
+ TALER_EXCHANGE_CTT_PURSE_REFUND },
+ { "RESERVE-OPEN-DEPOSIT",
+ &help_reserve_open_deposit,
+ TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT },
+ { NULL, NULL, TALER_EXCHANGE_CTT_NONE }
+ };
+ struct CoinHistoryParseContext pc = {
+ .dk = dk,
+ .coin_pub = coin_pub,
+ .total_out = total_out,
+ .total_in = total_in
+ };
+ size_t len;
+
+ if (NULL == history)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ len = json_array_size (history);
+ if (0 == len)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ *total_in = dk->value;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (total_in->currency,
+ total_out));
+ for (size_t off = 0; off<len; off++)
+ {
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh = &rhistory[off];
+ json_t *transaction = json_array_get (history,
+ off);
+ enum GNUNET_GenericReturnValue add;
+ const char *type;
+ struct GNUNET_JSON_Specification spec_glob[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &rh->amount),
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec_glob,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_YES !=
+ TALER_amount_cmp_currency (&rh->amount,
+ total_in))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Operation of type %s with amount %s\n",
+ type,
+ TALER_amount2s (&rh->amount));
+ add = GNUNET_SYSERR;
+ for (unsigned int i = 0; NULL != map[i].type; i++)
+ {
+ if (0 == strcasecmp (type,
+ map[i].type))
+ {
+ rh->type = map[i].ctt;
+ add = map[i].helper (&pc,
+ rh,
+ &rh->amount,
+ transaction);
+ break;
+ }
+ }
+ switch (add)
+ {
+ case GNUNET_SYSERR:
+ /* entry type not supported, new version on server? */
+ rh->type = TALER_EXCHANGE_CTT_NONE;
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected type `%s' in response\n",
+ type);
+ return GNUNET_SYSERR;
+ case GNUNET_YES:
+ /* This amount should be debited from the coin */
+ if (0 >
+ TALER_amount_add (total_out,
+ total_out,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ break;
+ case GNUNET_NO:
+ /* This amount should be credited to the coin. */
+ if (0 >
+ TALER_amount_add (total_in,
+ total_in,
+ &rh->amount))
+ {
+ /* overflow in refund history? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ break;
+ } /* end of switch(add) */
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * We received an #MHD_HTTP_OK history code. Handle the JSON
+ * response.
+ *
+ * @param rsh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_coins_history_ok (struct TALER_EXCHANGE_CoinsHistoryHandle *rsh,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_CoinHistory rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("balance",
+ &rs.details.ok.balance),
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &rs.details.ok.h_denom_pub),
+ GNUNET_JSON_spec_array_const ("history",
+ &rs.details.ok.history),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (NULL != rsh->cb)
+ {
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ }
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /coins/$RID/history request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_CoinsHistoryHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_coins_history_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_CoinsHistoryHandle *rsh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_CoinHistory rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ rsh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_coins_history_ok (rsh,
+ j))
+ {
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for coins history\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec);
+ break;
+ }
+ if (NULL != rsh->cb)
+ {
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ }
+ TALER_EXCHANGE_coins_history_cancel (rsh);
+}
+
+
+struct TALER_EXCHANGE_CoinsHistoryHandle *
+TALER_EXCHANGE_coins_history (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ uint64_t start_off,
+ TALER_EXCHANGE_CoinsHistoryCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_CoinsHistoryHandle *rsh;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 64];
+ struct curl_slist *job_headers;
+
+ rsh = GNUNET_new (struct TALER_EXCHANGE_CoinsHistoryHandle);
+ rsh->cb = cb;
+ rsh->cb_cls = cb_cls;
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+ &rsh->coin_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &rsh->coin_pub,
+ sizeof (rsh->coin_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ if (0 != start_off)
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "coins/%s/history?start=%llu",
+ pub_str,
+ (unsigned long long) start_off);
+ else
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "coins/%s/history",
+ pub_str);
+ }
+ rsh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rsh->url)
+ {
+ GNUNET_free (rsh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (rsh->url);
+ GNUNET_free (rsh);
+ return NULL;
+ }
+
+ {
+ struct TALER_CoinSpendSignatureP coin_sig;
+ char *sig_hdr;
+ char *hdr;
+
+ TALER_wallet_coin_history_sign (start_off,
+ coin_priv,
+ &coin_sig);
+
+ sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
+ &coin_sig,
+ sizeof (coin_sig));
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_COIN_HISTORY_SIGNATURE_HEADER,
+ sig_hdr);
+ GNUNET_free (sig_hdr);
+ job_headers = curl_slist_append (NULL,
+ hdr);
+ GNUNET_free (hdr);
+ if (NULL == job_headers)
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ return NULL;
+ }
+ }
+
+ rsh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ job_headers,
+ &handle_coins_history_finished,
+ rsh);
+ curl_slist_free_all (job_headers);
+ return rsh;
+}
+
+
+void
+TALER_EXCHANGE_coins_history_cancel (
+ struct TALER_EXCHANGE_CoinsHistoryHandle *rsh)
+{
+ if (NULL != rsh->job)
+ {
+ GNUNET_CURL_job_cancel (rsh->job);
+ rsh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&rsh->post_ctx);
+ GNUNET_free (rsh->url);
+ GNUNET_free (rsh);
+}
+
+
+/**
+ * Verify that @a coin_sig does NOT appear in the @a history of a coin's
+ * transactions and thus whatever transaction is authorized by @a coin_sig is
+ * a conflict with @a proof.
+ *
+ * @param history coin history to check
+ * @param coin_sig signature that must not be in @a history
+ * @return #GNUNET_OK if @a coin_sig is not in @a history
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_signature_conflict (
+ const json_t *history,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ size_t off;
+ json_t *entry;
+
+ json_array_foreach (history, off, entry)
+ {
+ struct TALER_CoinSpendSignatureP cs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &cs),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (entry,
+ spec,
+ NULL, NULL))
+ continue; /* entry without coin signature */
+ if (0 ==
+ GNUNET_memcmp (&cs,
+ coin_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+#if FIXME_IMPLEMENT
+/**
+ * FIXME-Oec: we need some specific routines that show
+ * that certain coin operations are indeed in conflict,
+ * for example that the coin is of a different denomination
+ * or different age restrictions.
+ * This relates to unimplemented error handling for
+ * coins in the exchange!
+ *
+ * Check that the provided @a proof indeeds indicates
+ * a conflict for @a coin_pub.
+ *
+ * @param keys exchange keys
+ * @param proof provided conflict proof
+ * @param dk denomination of @a coin_pub that the client
+ * used
+ * @param coin_pub public key of the coin
+ * @param required balance required on the coin for the operation
+ * @return #GNUNET_OK if @a proof holds
+ */
+// FIXME: should be properly defined and implemented!
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_conflict_ (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const json_t *proof,
+ const struct TALER_EXCHANGE_DenomPublicKey *dk,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *required)
+{
+ enum TALER_ErrorCode ec;
+
+ ec = TALER_JSON_get_error_code (proof);
+ switch (ec)
+ {
+ case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
+ /* Nothing to check anymore here, proof needs to be
+ checked in the GET /coins/$COIN_PUB handler */
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
+ // FIXME: write check!
+ break;
+ default:
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+#endif
+
+
+/* end of exchange_api_coins_history.c */
diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c
index af77d29e5..bd731ad37 100644
--- a/src/lib/exchange_api_common.c
+++ b/src/lib/exchange_api_common.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2020 Taler Systems SA
+ Copyright (C) 2015-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
@@ -22,388 +22,146 @@
#include "platform.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_common.h"
#include "exchange_api_handle.h"
#include "taler_signatures.h"
-/**
- * Parse history given in JSON format and return it in binary
- * format.
- *
- * @param exchange connection to the exchange we can use
- * @param history JSON array with the history
- * @param reserve_pub public key of the reserve to inspect
- * @param currency currency we expect the balance to be in
- * @param[out] balance final balance
- * @param history_length number of entries in @a history
- * @param[out] rhistory array of length @a history_length, set to the
- * parsed history entries
- * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
- * were set,
- * #GNUNET_SYSERR if there was a protocol violation in @a history
- */
-int
-TALER_EXCHANGE_parse_reserve_history (
- struct TALER_EXCHANGE_Handle *exchange,
- const json_t *history,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const char *currency,
- struct TALER_Amount *balance,
- unsigned int history_length,
- struct TALER_EXCHANGE_ReserveHistory *rhistory)
+const struct TALER_EXCHANGE_SigningPublicKey *
+TALER_EXCHANGE_get_signing_key_info (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ExchangePublicKeyP *exchange_pub)
{
- struct GNUNET_HashCode uuid[history_length];
- unsigned int uuid_off;
- struct TALER_Amount total_in;
- struct TALER_Amount total_out;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
- &total_in));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
- &total_out));
- uuid_off = 0;
- for (unsigned int off = 0; off<history_length; off++)
+ for (unsigned int i = 0; i<keys->num_sign_keys; i++)
{
- struct TALER_EXCHANGE_ReserveHistory *rh = &rhistory[off];
- json_t *transaction;
- struct TALER_Amount amount;
- const char *type;
- struct GNUNET_JSON_Specification hist_spec[] = {
- GNUNET_JSON_spec_string ("type",
- &type),
- TALER_JSON_spec_amount ("amount",
- &amount),
- /* 'wire' and 'signature' are optional depending on 'type'! */
- GNUNET_JSON_spec_end ()
- };
+ const struct TALER_EXCHANGE_SigningPublicKey *spk
+ = &keys->sign_keys[i];
- transaction = json_array_get (history,
- off);
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- hist_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rhistory[off].amount = amount;
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&amount,
- &total_in))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 == strcasecmp (type,
- "CREDIT"))
- {
- const char *wire_url;
- void *wire_reference;
- size_t wire_reference_size;
- struct GNUNET_TIME_Absolute timestamp;
-
- struct GNUNET_JSON_Specification withdraw_spec[] = {
- GNUNET_JSON_spec_varsize ("wire_reference",
- &wire_reference,
- &wire_reference_size),
- GNUNET_JSON_spec_absolute_time ("timestamp",
- &timestamp),
- GNUNET_JSON_spec_string ("sender_account_url",
- &wire_url),
- GNUNET_JSON_spec_end ()
- };
+ if (0 == GNUNET_memcmp (exchange_pub,
+ &spk->key))
+ return spk;
+ }
+ return NULL;
+}
- rh->type = TALER_EXCHANGE_RTT_CREDIT;
- if (GNUNET_OK !=
- TALER_amount_add (&total_in,
- &total_in,
- &amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- withdraw_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rh->details.in_details.sender_url = GNUNET_strdup (wire_url);
- rh->details.in_details.wire_reference = wire_reference;
- rh->details.in_details.wire_reference_size =
- wire_reference_size;
- rh->details.in_details.timestamp = timestamp;
- /* end type==DEPOSIT */
- }
- else if (0 == strcasecmp (type,
- "WITHDRAW"))
- {
- struct TALER_ReserveSignatureP sig;
- struct TALER_WithdrawRequestPS withdraw_purpose;
- struct GNUNET_JSON_Specification withdraw_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &sig),
- TALER_JSON_spec_amount_nbo ("withdraw_fee",
- &withdraw_purpose.withdraw_fee),
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- &withdraw_purpose.h_denomination_pub),
- GNUNET_JSON_spec_fixed_auto ("h_coin_envelope",
- &withdraw_purpose.h_coin_envelope),
- GNUNET_JSON_spec_end ()
- };
- rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- withdraw_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- withdraw_purpose.purpose.size
- = htonl (sizeof (withdraw_purpose));
- withdraw_purpose.purpose.purpose
- = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
- withdraw_purpose.reserve_pub = *reserve_pub;
- TALER_amount_hton (&withdraw_purpose.amount_with_fee,
- &amount);
- /* Check that the signature is a valid withdraw request */
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
- &withdraw_purpose.purpose,
- &sig.eddsa_signature,
- &reserve_pub->eddsa_pub))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- /* check that withdraw fee matches expectations! */
- {
- const struct TALER_EXCHANGE_Keys *key_state;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
- struct TALER_Amount fee;
-
- key_state = TALER_EXCHANGE_get_keys (exchange);
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
- &withdraw_purpose.
- h_denomination_pub);
- TALER_amount_ntoh (&fee,
- &withdraw_purpose.withdraw_fee);
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&fee,
- &dki->fee_withdraw)) ||
- (0 !=
- TALER_amount_cmp (&fee,
- &dki->fee_withdraw)) )
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- rh->details.withdraw.fee = fee;
- }
- rh->details.withdraw.out_authorization_sig
- = json_object_get (transaction,
- "signature");
- /* Check check that the same withdraw transaction
- isn't listed twice by the exchange. We use the
- "uuid" array to remember the hashes of all
- purposes, and compare the hashes to find
- duplicates. *///
- GNUNET_CRYPTO_hash (&withdraw_purpose,
- ntohl (withdraw_purpose.purpose.size),
- &uuid[uuid_off]);
- for (unsigned int i = 0; i<uuid_off; i++)
- {
- if (0 == GNUNET_memcmp (&uuid[uuid_off],
- &uuid[i]))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- }
- uuid_off++;
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_create_conflict_ (
+ const struct TALER_PurseContractSignatureP *cpurse_sig,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *proof)
+{
+ struct TALER_Amount amount;
+ uint32_t min_age;
+ struct GNUNET_TIME_Timestamp purse_expiration;
+ struct TALER_PurseContractSignatureP purse_sig;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_PurseMergePublicKeyP merge_pub;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &amount),
+ GNUNET_JSON_spec_uint32 ("min_age",
+ &min_age),
+ GNUNET_JSON_spec_timestamp ("purse_expiration",
+ &purse_expiration),
+ GNUNET_JSON_spec_fixed_auto ("purse_sig",
+ &purse_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("merge_pub",
+ &merge_pub),
+ GNUNET_JSON_spec_end ()
+ };
- if (GNUNET_OK !=
- TALER_amount_add (&total_out,
- &total_out,
- &amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- /* end type==WITHDRAW */
- }
- else if (0 == strcasecmp (type,
- "RECOUP"))
- {
- struct TALER_RecoupConfirmationPS pc;
- struct GNUNET_TIME_Absolute timestamp;
- const struct TALER_EXCHANGE_Keys *key_state;
- struct GNUNET_JSON_Specification recoup_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &pc.coin_pub),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rh->details.recoup_details.exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rh->details.recoup_details.exchange_pub),
- GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
- &pc.timestamp),
- GNUNET_JSON_spec_end ()
- };
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (proof,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_purse_create_verify (purse_expiration,
+ &h_contract_terms,
+ &merge_pub,
+ min_age,
+ &amount,
+ purse_pub,
+ &purse_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 ==
+ GNUNET_memcmp (&purse_sig,
+ cpurse_sig))
+ {
+ /* Must be the SAME data, not a conflict! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
- rh->type = TALER_EXCHANGE_RTT_RECOUP;
- rh->amount = amount;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- recoup_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rh->details.recoup_details.coin_pub = pc.coin_pub;
- TALER_amount_hton (&pc.recoup_amount,
- &amount);
- pc.purpose.size = htonl (sizeof (pc));
- pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP);
- pc.reserve_pub = *reserve_pub;
- timestamp = GNUNET_TIME_absolute_ntoh (pc.timestamp);
- rh->details.recoup_details.timestamp = timestamp;
-
- key_state = TALER_EXCHANGE_get_keys (exchange);
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- &rh->details.
- recoup_details.exchange_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (
- TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP,
- &pc.purpose,
- &rh->details.recoup_details.exchange_sig.eddsa_signature,
- &rh->details.recoup_details.exchange_pub.eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (&total_in,
- &total_in,
- &rh->amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* end type==RECOUP */
- }
- else if (0 == strcasecmp (type,
- "CLOSING"))
- {
- const struct TALER_EXCHANGE_Keys *key_state;
- struct TALER_ReserveCloseConfirmationPS rcc;
- struct GNUNET_TIME_Absolute timestamp;
- struct GNUNET_JSON_Specification closing_spec[] = {
- GNUNET_JSON_spec_string (
- "receiver_account_details",
- &rh->details.close_details.receiver_account_details),
- GNUNET_JSON_spec_fixed_auto ("wtid",
- &rh->details.close_details.wtid),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rh->details.close_details.exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rh->details.close_details.exchange_pub),
- TALER_JSON_spec_amount_nbo ("closing_fee",
- &rcc.closing_fee),
- GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
- &rcc.timestamp),
- GNUNET_JSON_spec_end ()
- };
- rh->type = TALER_EXCHANGE_RTT_CLOSE;
- rh->amount = amount;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- closing_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- TALER_amount_hton (&rcc.closing_amount,
- &amount);
- GNUNET_CRYPTO_hash (
- rh->details.close_details.receiver_account_details,
- strlen (rh->details.close_details.receiver_account_details) + 1,
- &rcc.h_wire);
- rcc.wtid = rh->details.close_details.wtid;
- rcc.purpose.size = htonl (sizeof (rcc));
- rcc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED);
- rcc.reserve_pub = *reserve_pub;
- timestamp = GNUNET_TIME_absolute_ntoh (rcc.timestamp);
- rh->details.close_details.timestamp = timestamp;
- TALER_amount_ntoh (&rh->details.close_details.fee,
- &rcc.closing_fee);
- key_state = TALER_EXCHANGE_get_keys (exchange);
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- &rh->details.close_details.
- exchange_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (
- TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED,
- &rcc.purpose,
- &rh->details.close_details.exchange_sig.eddsa_signature,
- &rh->details.close_details.exchange_pub.eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (&total_out,
- &total_out,
- &rh->amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* end type==CLOSING */
- }
- else
- {
- /* unexpected 'type', protocol incompatibility, complain! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_merge_conflict_ (
+ const struct TALER_PurseMergeSignatureP *cmerge_sig,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const char *exchange_url,
+ const json_t *proof)
+{
+ struct TALER_PurseMergeSignatureP merge_sig;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ const char *partner_url = NULL;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("partner_url",
+ &partner_url),
+ NULL),
+ GNUNET_JSON_spec_timestamp ("merge_timestamp",
+ &merge_timestamp),
+ GNUNET_JSON_spec_fixed_auto ("merge_sig",
+ &merge_sig),
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &reserve_pub),
+ GNUNET_JSON_spec_end ()
+ };
+ char *payto_uri;
- /* check balance = total_in - total_out < withdraw-amount */
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (balance,
- &total_in,
- &total_out))
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (proof,
+ spec,
+ NULL, NULL))
{
- /* total_in < total_out, why did the exchange ever allow this!? */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (NULL == partner_url)
+ partner_url = exchange_url;
+ payto_uri = TALER_reserve_make_payto (partner_url,
+ &reserve_pub);
+ if (GNUNET_OK !=
+ TALER_wallet_purse_merge_verify (
+ payto_uri,
+ merge_timestamp,
+ purse_pub,
+ merge_pub,
+ &merge_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (payto_uri);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (payto_uri);
+ if (0 ==
+ GNUNET_memcmp (&merge_sig,
+ cmerge_sig))
+ {
+ /* Must be the SAME data, not a conflict! */
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
@@ -411,413 +169,484 @@ TALER_EXCHANGE_parse_reserve_history (
}
-/**
- * Free memory (potentially) allocated by #TALER_EXCHANGE_parse_reserve_history().
- *
- * @param rhistory result to free
- * @param len number of entries in @a rhistory
- */
-void
-TALER_EXCHANGE_free_reserve_history (
- struct TALER_EXCHANGE_ReserveHistory *rhistory,
- unsigned int len)
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_coin_conflict_ (
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const char *exchange_url,
+ const json_t *proof,
+ struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_AgeCommitmentHash *phac,
+ struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_CoinSpendSignatureP *coin_sig)
{
- for (unsigned int i = 0; i<len; i++)
+ const char *partner_url = NULL;
+ struct TALER_Amount amount;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ h_denom_pub),
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ phac),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ coin_sig),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ coin_pub),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("partner_url",
+ &partner_url),
+ NULL),
+ TALER_JSON_spec_amount_any ("amount",
+ &amount),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (proof,
+ spec,
+ NULL, NULL))
{
- switch (rhistory[i].type)
- {
- case TALER_EXCHANGE_RTT_CREDIT:
- GNUNET_free_non_null (rhistory[i].details.in_details.wire_reference);
- GNUNET_free_non_null (rhistory[i].details.in_details.sender_url);
- break;
- case TALER_EXCHANGE_RTT_WITHDRAWAL:
- break;
- case TALER_EXCHANGE_RTT_RECOUP:
- break;
- case TALER_EXCHANGE_RTT_CLOSE:
- break;
- }
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (NULL == partner_url)
+ partner_url = exchange_url;
+ if (GNUNET_OK !=
+ TALER_wallet_purse_deposit_verify (
+ partner_url,
+ purse_pub,
+ &amount,
+ h_denom_pub,
+ phac,
+ coin_pub,
+ coin_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
}
- GNUNET_free (rhistory);
+ return GNUNET_OK;
}
-/**
- * Verify a coins transaction history as returned by the exchange.
- *
- * @param dk fee structure for the coin, NULL to skip verifying fees
- * @param currency expected currency for the coin
- * @param coin_pub public key of the coin
- * @param history history of the coin in json encoding
- * @param[out] total how much of the coin has been spent according to @a history
- * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not
- */
-int
-TALER_EXCHANGE_verify_coin_history (
- const struct TALER_EXCHANGE_DenomPublicKey *dk,
- const char *currency,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- json_t *history,
- struct TALER_Amount *total)
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_econtract_conflict_ (
+ const struct TALER_PurseContractSignatureP *ccontract_sig,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *proof)
{
- size_t len;
- struct TALER_Amount rtotal;
- struct TALER_Amount fee;
+ struct TALER_ContractDiffiePublicP contract_pub;
+ struct TALER_PurseContractSignatureP contract_sig;
+ struct GNUNET_HashCode h_econtract;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_econtract",
+ &h_econtract),
+ GNUNET_JSON_spec_fixed_auto ("econtract_sig",
+ &contract_sig),
+ GNUNET_JSON_spec_fixed_auto ("contract_pub",
+ &contract_pub),
+ GNUNET_JSON_spec_end ()
+ };
- if (NULL == history)
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (proof,
+ spec,
+ NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- len = json_array_size (history);
- if (0 == len)
+ if (GNUNET_OK !=
+ TALER_wallet_econtract_upload_verify2 (
+ &h_econtract,
+ &contract_pub,
+ purse_pub,
+ &contract_sig))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
- total));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
- &rtotal));
- for (size_t off = 0; off<len; off++)
+ if (0 ==
+ GNUNET_memcmp (&contract_sig,
+ ccontract_sig))
{
- int add;
- json_t *transaction;
- struct TALER_Amount amount;
- const char *type;
- struct GNUNET_JSON_Specification spec_glob[] = {
- TALER_JSON_spec_amount ("amount",
- &amount),
- GNUNET_JSON_spec_string ("type",
- &type),
- GNUNET_JSON_spec_end ()
- };
+ /* Must be the SAME data, not a conflict! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
- transaction = json_array_get (history,
- off);
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec_glob,
- NULL, NULL))
+
+// FIXME: should be used...
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_denomination_conflict_ (
+ const json_t *proof,
+ const struct TALER_DenominationHashP *ch_denom_pub)
+{
+ struct TALER_DenominationHashP h_denom_pub;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (proof,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 ==
+ GNUNET_memcmp (ch_denom_pub,
+ &h_denom_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_OK;
+ }
+ /* indeed, proof with different denomination key provided */
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_get_min_denomination_ (
+ const struct TALER_EXCHANGE_Keys *keys,
+ struct TALER_Amount *min)
+{
+ bool have_min = false;
+ for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dk = &keys->denom_keys[i];
+
+ if (! have_min)
{
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
+ *min = dk->value;
+ have_min = true;
+ continue;
}
+ if (1 != TALER_amount_cmp (min,
+ &dk->value))
+ continue;
+ *min = dk->value;
+ }
+ if (! have_min)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_verify_deposit_signature_ (
+ const struct TALER_EXCHANGE_DepositContractDetail *dcd,
+ const struct TALER_ExtensionPolicyHashP *ech,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_EXCHANGE_CoinDepositDetail *cdd,
+ const struct TALER_EXCHANGE_DenomPublicKey *dki)
+{
+ if (GNUNET_OK !=
+ TALER_wallet_deposit_verify (&cdd->amount,
+ &dki->fees.deposit,
+ h_wire,
+ &dcd->h_contract_terms,
+ &dcd->wallet_data_hash,
+ &cdd->h_age_commitment,
+ ech,
+ &cdd->h_denom_pub,
+ dcd->wallet_timestamp,
+ &dcd->merchant_pub,
+ dcd->refund_deadline,
+ &cdd->coin_pub,
+ &cdd->coin_sig))
+ {
+ GNUNET_break_op (0);
+ TALER_LOG_WARNING ("Invalid coin signature on /deposit request!\n");
+ TALER_LOG_DEBUG ("... amount_with_fee was %s\n",
+ TALER_amount2s (&cdd->amount));
+ TALER_LOG_DEBUG ("... deposit_fee was %s\n",
+ TALER_amount2s (&dki->fees.deposit));
+ return GNUNET_SYSERR;
+ }
+
+ /* check coin signature */
+ {
+ struct TALER_CoinPublicInfo coin_info = {
+ .coin_pub = cdd->coin_pub,
+ .denom_pub_hash = cdd->h_denom_pub,
+ .denom_sig = cdd->denom_sig,
+ .h_age_commitment = cdd->h_age_commitment,
+ };
+
if (GNUNET_YES !=
- TALER_amount_cmp_currency (&amount,
- &rtotal))
+ TALER_test_coin_valid (&coin_info,
+ &dki->key))
{
GNUNET_break_op (0);
+ TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
return GNUNET_SYSERR;
}
- add = GNUNET_SYSERR;
- if (0 == strcasecmp (type,
- "DEPOSIT"))
- {
- struct TALER_DepositRequestPS dr;
- struct TALER_CoinSpendSignatureP sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &sig),
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &dr.h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("h_wire",
- &dr.h_wire),
- GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
- &dr.timestamp),
- GNUNET_JSON_spec_absolute_time_nbo ("refund_deadline",
- &dr.refund_deadline),
- TALER_JSON_spec_amount_nbo ("deposit_fee",
- &dr.deposit_fee),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &dr.merchant),
- GNUNET_JSON_spec_end ()
- };
+ }
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- dr.purpose.size = htonl (sizeof (dr));
- dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT);
- TALER_amount_hton (&dr.amount_with_fee,
- &amount);
- dr.coin_pub = *coin_pub;
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT,
- &dr.purpose,
- &sig.eddsa_signature,
- &coin_pub->eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (NULL != dk)
- {
- /* check that deposit fee matches our expectations from /keys! */
- TALER_amount_ntoh (&fee,
- &dr.deposit_fee);
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&fee,
- &dk->fee_deposit)) ||
- (0 !=
- TALER_amount_cmp (&fee,
- &dk->fee_deposit)) )
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- add = GNUNET_YES;
- }
- else if (0 == strcasecmp (type,
- "MELT"))
- {
- struct TALER_RefreshMeltCoinAffirmationPS rm;
- struct TALER_CoinSpendSignatureP sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &sig),
- GNUNET_JSON_spec_fixed_auto ("rc",
- &rm.rc),
- TALER_JSON_spec_amount_nbo ("melt_fee",
- &rm.melt_fee),
- GNUNET_JSON_spec_end ()
- };
+ /* Check coin does make a contribution */
+ if (0 < TALER_amount_cmp (&dki->fees.deposit,
+ &cdd->amount))
+ {
+ GNUNET_break_op (0);
+ TALER_LOG_WARNING ("Deposit amount smaller than fee\n");
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rm.purpose.size = htonl (sizeof (rm));
- rm.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
- TALER_amount_hton (&rm.amount_with_fee,
- &amount);
- rm.coin_pub = *coin_pub;
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT,
- &rm.purpose,
- &sig.eddsa_signature,
- &coin_pub->eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (NULL != dk)
- {
- /* check that melt fee matches our expectations from /keys! */
- TALER_amount_ntoh (&fee,
- &rm.melt_fee);
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&fee,
- &dk->fee_refresh)) ||
- (0 !=
- TALER_amount_cmp (&fee,
- &dk->fee_refresh)) )
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- add = GNUNET_YES;
+
+/**
+ * Parse account restriction in @a jrest into @a rest.
+ *
+ * @param jresta array of account restrictions in JSON
+ * @param[out] resta_len set to length of @a resta
+ * @param[out] resta account restriction array to set
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_restrictions (const json_t *jresta,
+ unsigned int *resta_len,
+ struct TALER_EXCHANGE_AccountRestriction **resta)
+{
+ size_t alen;
+
+ if (! json_is_array (jresta))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ alen = json_array_size (jresta);
+ if (0 == alen)
+ {
+ /* no restrictions, perfectly OK */
+ *resta = NULL;
+ return GNUNET_OK;
+ }
+ *resta_len = (unsigned int) alen;
+ GNUNET_assert (alen == *resta_len);
+ *resta = GNUNET_new_array (*resta_len,
+ struct TALER_EXCHANGE_AccountRestriction);
+ for (unsigned int i = 0; i<*resta_len; i++)
+ {
+ const json_t *jr = json_array_get (jresta,
+ i);
+ struct TALER_EXCHANGE_AccountRestriction *ar = &(*resta)[i];
+ const char *type = json_string_value (json_object_get (jr,
+ "type"));
+
+ if (NULL == type)
+ {
+ GNUNET_break (0);
+ goto fail;
}
- else if (0 == strcasecmp (type,
- "REFUND"))
+ if (0 == strcmp (type,
+ "deny"))
{
- struct TALER_RefundRequestPS rr;
- struct TALER_MerchantSignatureP sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("merchant_sig",
- &sig),
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &rr.h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &rr.merchant),
- GNUNET_JSON_spec_uint64 ("rtransaction_id",
- &rr.rtransaction_id),
- TALER_JSON_spec_amount_nbo ("refund_fee",
- &rr.refund_fee),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rr.purpose.size = htonl (sizeof (rr));
- rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND);
- rr.coin_pub = *coin_pub;
- TALER_amount_hton (&rr.refund_amount,
- &amount);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
- &rr.purpose,
- &sig.eddsa_sig,
- &rr.merchant.eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* NOTE: theoretically, we could also check that the given
- merchant_pub and h_contract_terms appear in the
- history under deposits. However, there is really no benefit
- for the exchange to lie here, so not checking is probably OK
- (an auditor ought to check, though). Then again, we similarly
- had no reason to check the merchant's signature (other than a
- well-formendess check). *///
-
- /* check that refund fee matches our expectations from /keys! */
- if (NULL != dk)
- {
- TALER_amount_ntoh (&fee,
- &rr.refund_fee);
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&fee,
- &dk->fee_refund)) ||
- (0 !=
- TALER_amount_cmp (&fee,
- &dk->fee_refund)) )
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- add = GNUNET_NO;
+ ar->type = TALER_EXCHANGE_AR_DENY;
+ continue;
}
- else if (0 == strcasecmp (type,
- "RECOUP"))
+ if (0 == strcmp (type,
+ "regex"))
{
- struct TALER_RecoupConfirmationPS pc;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP exchange_sig;
+ const char *regex;
+ const char *hint;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &exchange_pub),
- GNUNET_JSON_spec_fixed_auto ("reserve_pub",
- &pc.reserve_pub),
- GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
- &pc.timestamp),
+ GNUNET_JSON_spec_string (
+ "payto_regex",
+ &regex),
+ GNUNET_JSON_spec_string (
+ "human_hint",
+ &hint),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json (
+ "human_hint_i18n",
+ &ar->details.regex.human_hint_i18n),
+ NULL),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
+ GNUNET_JSON_parse (jr,
spec,
NULL, NULL))
{
+ /* bogus reply */
GNUNET_break_op (0);
- return GNUNET_SYSERR;
+ goto fail;
}
- pc.purpose.size = htonl (sizeof (pc));
- pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP);
- pc.coin_pub = *coin_pub;
- TALER_amount_hton (&pc.recoup_amount,
- &amount);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP,
- &pc.purpose,
- &exchange_sig.eddsa_signature,
- &exchange_pub.eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- add = GNUNET_YES;
+ ar->type = TALER_EXCHANGE_AR_REGEX;
+ ar->details.regex.posix_egrep = GNUNET_strdup (regex);
+ ar->details.regex.human_hint = GNUNET_strdup (hint);
+ continue;
}
- else
+ /* unsupported type */
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+fail:
+ GNUNET_free (*resta);
+ *resta_len = 0;
+ return GNUNET_SYSERR;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_parse_accounts (
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const json_t *accounts,
+ unsigned int was_length,
+ struct TALER_EXCHANGE_WireAccount was[static was_length])
+{
+ memset (was,
+ 0,
+ sizeof (struct TALER_EXCHANGE_WireAccount) * was_length);
+ GNUNET_assert (was_length ==
+ json_array_size (accounts));
+ for (unsigned int i = 0;
+ i<was_length;
+ i++)
+ {
+ struct TALER_EXCHANGE_WireAccount *wa = &was[i];
+ const char *payto_uri;
+ const char *conversion_url = NULL;
+ const char *bank_label = NULL;
+ int64_t priority = 0;
+ const json_t *credit_restrictions;
+ const json_t *debit_restrictions;
+ struct GNUNET_JSON_Specification spec_account[] = {
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &payto_uri),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("conversion_url",
+ &conversion_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_int64 ("priority",
+ &priority),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("bank_label",
+ &bank_label),
+ NULL),
+ GNUNET_JSON_spec_array_const ("credit_restrictions",
+ &credit_restrictions),
+ GNUNET_JSON_spec_array_const ("debit_restrictions",
+ &debit_restrictions),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &wa->master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *account;
+
+ account = json_array_get (accounts,
+ i);
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (account,
+ spec_account,
+ NULL, NULL))
{
- /* signature not supported, new version on server? */
+ /* bogus reply */
GNUNET_break_op (0);
- GNUNET_assert (GNUNET_SYSERR == add);
return GNUNET_SYSERR;
}
- if (GNUNET_YES == add)
+ if ( (NULL != master_pub) &&
+ (GNUNET_OK !=
+ TALER_exchange_wire_signature_check (
+ payto_uri,
+ conversion_url,
+ debit_restrictions,
+ credit_restrictions,
+ master_pub,
+ &wa->master_sig)) )
{
- /* This amount should be added to the total */
- if (GNUNET_OK !=
- TALER_amount_add (total,
- total,
- &amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
+ /* bogus reply */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
}
- else
+ if ( (GNUNET_OK !=
+ parse_restrictions (credit_restrictions,
+ &wa->credit_restrictions_length,
+ &wa->credit_restrictions)) ||
+ (GNUNET_OK !=
+ parse_restrictions (debit_restrictions,
+ &wa->debit_restrictions_length,
+ &wa->debit_restrictions)) )
{
- /* This amount should be subtracted from the total.
-
- However, for the implementation, we first *add* up all of
- these negative amounts, as we might get refunds before
- deposits from a semi-evil exchange. Then, at the end, we do
- the subtraction by calculating "total = total - rtotal" */GNUNET_assert (GNUNET_NO == add);
- if (GNUNET_OK !=
- TALER_amount_add (&rtotal,
- &rtotal,
- &amount))
- {
- /* overflow in refund history? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
+ /* bogus reply */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
}
- }
-
- /* Finally, subtract 'rtotal' from total to handle the subtractions */
- if (GNUNET_OK !=
- TALER_amount_subtract (total,
- total,
- &rtotal))
- {
- /* underflow in history? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
+ wa->payto_uri = GNUNET_strdup (payto_uri);
+ wa->priority = priority;
+ if (NULL != conversion_url)
+ wa->conversion_url = GNUNET_strdup (conversion_url);
+ if (NULL != bank_label)
+ wa->bank_label = GNUNET_strdup (bank_label);
+ } /* end 'for all accounts */
return GNUNET_OK;
}
/**
- * Obtain meta data about an exchange (online) signing
- * key.
+ * Free array of account restrictions.
*
- * @param keys from where to obtain the meta data
- * @param exchange_pub public key to lookup
- * @return NULL on error (@a exchange_pub not known)
+ * @param ar_len length of @a ar
+ * @param[in] ar array to free contents of (but not @a ar itself)
*/
-const struct TALER_EXCHANGE_SigningPublicKey *
-TALER_EXCHANGE_get_signing_key_info (
- const struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_ExchangePublicKeyP *exchange_pub)
+static void
+free_restrictions (unsigned int ar_len,
+ struct TALER_EXCHANGE_AccountRestriction ar[static ar_len])
{
- for (unsigned int i = 0; i<keys->num_sign_keys; i++)
+ for (unsigned int i = 0; i<ar_len; i++)
{
- const struct TALER_EXCHANGE_SigningPublicKey *spk
- = &keys->sign_keys[i];
+ struct TALER_EXCHANGE_AccountRestriction *a = &ar[i];
+ switch (a->type)
+ {
+ case TALER_EXCHANGE_AR_INVALID:
+ GNUNET_break (0);
+ break;
+ case TALER_EXCHANGE_AR_DENY:
+ break;
+ case TALER_EXCHANGE_AR_REGEX:
+ GNUNET_free (ar->details.regex.posix_egrep);
+ GNUNET_free (ar->details.regex.human_hint);
+ json_decref (ar->details.regex.human_hint_i18n);
+ break;
+ }
+ }
+}
- if (0 == GNUNET_memcmp (exchange_pub,
- &spk->key))
- return spk;
+
+void
+TALER_EXCHANGE_free_accounts (
+ unsigned int was_len,
+ struct TALER_EXCHANGE_WireAccount was[static was_len])
+{
+ for (unsigned int i = 0; i<was_len; i++)
+ {
+ struct TALER_EXCHANGE_WireAccount *wa = &was[i];
+
+ GNUNET_free (wa->payto_uri);
+ GNUNET_free (wa->conversion_url);
+ GNUNET_free (wa->bank_label);
+ free_restrictions (wa->credit_restrictions_length,
+ wa->credit_restrictions);
+ GNUNET_array_grow (wa->credit_restrictions,
+ wa->credit_restrictions_length,
+ 0);
+ free_restrictions (wa->debit_restrictions_length,
+ wa->debit_restrictions);
+ GNUNET_array_grow (wa->debit_restrictions,
+ wa->debit_restrictions_length,
+ 0);
}
- return NULL;
}
diff --git a/src/lib/exchange_api_common.h b/src/lib/exchange_api_common.h
new file mode 100644
index 000000000..f1f0fd7fa
--- /dev/null
+++ b/src/lib/exchange_api_common.h
@@ -0,0 +1,180 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-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/>
+*/
+/**
+ * @file lib/exchange_api_common.h
+ * @brief common functions for the exchange API
+ * @author Christian Grothoff
+ */
+#ifndef EXCHANGE_API_COMMON_H
+#define EXCHANGE_API_COMMON_H
+
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+
+
+/**
+ * Check proof of a purse creation conflict.
+ *
+ * @param cpurse_sig conflicting signature (must
+ * not match the signature from the proof)
+ * @param purse_pub the public key (must match
+ * the signature from the proof)
+ * @param proof the proof to check
+ * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and conflicts with @a cpurse_sig
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_create_conflict_ (
+ const struct TALER_PurseContractSignatureP *cpurse_sig,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *proof);
+
+
+/**
+ * Check proof of a purse merge conflict.
+ *
+ * @param cmerge_sig conflicting signature (must
+ * not match the signature from the proof)
+ * @param merge_pub the public key (must match
+ * the signature from the proof)
+ * @param purse_pub the public key of the purse
+ * @param exchange_url the base URL of this exchange
+ * @param proof the proof to check
+ * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and @a merge_pub and conflicts with @a cmerge_sig
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_merge_conflict_ (
+ const struct TALER_PurseMergeSignatureP *cmerge_sig,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const char *exchange_url,
+ const json_t *proof);
+
+
+/**
+ * Check @a proof that claims this coin was spend
+ * differently on the same purse already. Note that
+ * the caller must still check that @a coin_pub is
+ * in the list of coins that were used, and that
+ * @a coin_sig is different from the signature the
+ * caller used.
+ *
+ * @param purse_pub the public key of the purse
+ * @param exchange_url base URL of our exchange
+ * @param proof the proof to check
+ * @param[out] h_denom_pub hash of the coin's denomination
+ * @param[out] phac age commitment hash of the coin
+ * @param[out] coin_pub set to the conflicting coin
+ * @param[out] coin_sig set to the conflicting signature
+ * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and showing that @a coin_pub was spent using @a coin_sig.
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_coin_conflict_ (
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const char *exchange_url,
+ const json_t *proof,
+ struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_AgeCommitmentHash *phac,
+ struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Check proof of a contract conflict.
+ *
+ * @param ccontract_sig conflicting signature (must
+ * not match the signature from the proof)
+ * @param purse_pub public key of the purse
+ * @param proof the proof to check
+ * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and conflicts with @a ccontract_sig
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_econtract_conflict_ (
+ const struct TALER_PurseContractSignatureP *ccontract_sig,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *proof);
+
+
+/**
+ * Check proof of a coin spend value conflict.
+ *
+ * @param keys exchange /keys structure
+ * @param proof the proof to check
+ * @param[out] coin_pub set to the public key of the
+ * coin that is claimed to have an insufficient
+ * balance
+ * @param[out] remaining set to the remaining balance
+ * of the coin as provided by the proof
+ * @return #GNUNET_OK if the @a proof is OK for @a purse_pub demonstrating that @a coin_pub has only @a remaining balance.
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_amount_conflict_ (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const json_t *proof,
+ struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_Amount *remaining);
+
+
+/**
+ * Verify that @a proof contains a coin history that demonstrates that @a
+ * coin_pub was previously used with a denomination key that is different from
+ * @a ch_denom_pub. Note that the coin history MUST have been checked before
+ * using #TALER_EXCHANGE_check_coin_amount_conflict_().
+ *
+ * @param proof a proof to check
+ * @param ch_denom_pub hash of the conflicting denomination
+ * @return #GNUNET_OK if @a ch_denom_pub differs from the
+ * denomination hash given by the history of the coin
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_denomination_conflict_ (
+ const json_t *proof,
+ const struct TALER_DenominationHashP *ch_denom_pub);
+
+
+/**
+ * Find the smallest denomination amount in @e keys.
+ *
+ * @param keys keys to search
+ * @param[out] min set to the smallest amount
+ * @return #GNUNET_SYSERR if there are no denominations in @a keys
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_get_min_denomination_ (
+ const struct TALER_EXCHANGE_Keys *keys,
+ struct TALER_Amount *min);
+
+
+/**
+ * Verify signature information about the deposit.
+ *
+ * @param dcd contract details
+ * @param ech hashed policy (passed to avoid recomputation)
+ * @param h_wire hashed wire details (passed to avoid recomputation)
+ * @param cdd coin-specific details
+ * @param dki denomination of the coin
+ * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_verify_deposit_signature_ (
+ const struct TALER_EXCHANGE_DepositContractDetail *dcd,
+ const struct TALER_ExtensionPolicyHashP *ech,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_EXCHANGE_CoinDepositDetail *cdd,
+ const struct TALER_EXCHANGE_DenomPublicKey *dki);
+
+
+#endif
diff --git a/src/lib/exchange_api_contracts_get.c b/src/lib/exchange_api_contracts_get.c
new file mode 100644
index 000000000..aece7733a
--- /dev/null
+++ b/src/lib/exchange_api_contracts_get.c
@@ -0,0 +1,262 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+*/
+/**
+ * @file lib/exchange_api_contracts_get.c
+ * @brief Implementation of the /contracts/ GET request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Contract Get Handle
+ */
+struct TALER_EXCHANGE_ContractsGetHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ContractGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Private key needed to decrypt the contract.
+ */
+ struct TALER_ContractDiffiePrivateP contract_priv;
+
+ /**
+ * Public key matching @e contract_priv.
+ */
+ struct TALER_ContractDiffiePublicP cpub;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /track/transaction request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ContractsGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_contract_get_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ContractsGetHandle *cgh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_ContractGetResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ cgh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ void *econtract;
+ size_t econtract_size;
+ struct TALER_PurseContractSignatureP econtract_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("purse_pub",
+ &dr.details.ok.purse_pub),
+ GNUNET_JSON_spec_fixed_auto ("econtract_sig",
+ &econtract_sig),
+ GNUNET_JSON_spec_varsize ("econtract",
+ &econtract,
+ &econtract_size),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_econtract_upload_verify (
+ econtract,
+ econtract_size,
+ &cgh->cpub,
+ &dr.details.ok.purse_pub,
+ &econtract_sig))
+ {
+ GNUNET_break (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_CONTRACTS_SIGNATURE_INVALID;
+ GNUNET_JSON_parse_free (spec);
+ break;
+ }
+ dr.details.ok.econtract = econtract;
+ dr.details.ok.econtract_size = econtract_size;
+ cgh->cb (cgh->cb_cls,
+ &dr);
+ GNUNET_JSON_parse_free (spec);
+ TALER_EXCHANGE_contract_get_cancel (cgh);
+ return;
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Exchange does not know about transaction;
+ we should pass the reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange GET contracts\n",
+ (unsigned int) response_code,
+ (int) dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ cgh->cb (cgh->cb_cls,
+ &dr);
+ TALER_EXCHANGE_contract_get_cancel (cgh);
+}
+
+
+struct TALER_EXCHANGE_ContractsGetHandle *
+TALER_EXCHANGE_contract_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ TALER_EXCHANGE_ContractGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ContractsGetHandle *cgh;
+ CURL *eh;
+ char arg_str[sizeof (cgh->cpub) * 2 + 48];
+
+ cgh = GNUNET_new (struct TALER_EXCHANGE_ContractsGetHandle);
+ cgh->cb = cb;
+ cgh->cb_cls = cb_cls;
+ GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
+ &cgh->cpub.ecdhe_pub);
+ {
+ char cpub_str[sizeof (cgh->cpub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (&cgh->cpub,
+ sizeof (cgh->cpub),
+ cpub_str,
+ sizeof (cpub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "contracts/%s",
+ cpub_str);
+ }
+
+ cgh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == cgh->url)
+ {
+ GNUNET_free (cgh);
+ return NULL;
+ }
+ cgh->contract_priv = *contract_priv;
+
+ eh = TALER_EXCHANGE_curl_easy_get_ (cgh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (cgh->url);
+ GNUNET_free (cgh);
+ return NULL;
+ }
+ cgh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_contract_get_finished,
+ cgh);
+ return cgh;
+}
+
+
+void
+TALER_EXCHANGE_contract_get_cancel (
+ struct TALER_EXCHANGE_ContractsGetHandle *cgh)
+{
+ if (NULL != cgh->job)
+ {
+ GNUNET_CURL_job_cancel (cgh->job);
+ cgh->job = NULL;
+ }
+ GNUNET_free (cgh->url);
+ GNUNET_free (cgh);
+}
+
+
+/* end of exchange_api_contracts_get.c */
diff --git a/src/lib/exchange_api_csr_melt.c b/src/lib/exchange_api_csr_melt.c
new file mode 100644
index 000000000..bf6f4bfe1
--- /dev/null
+++ b/src/lib/exchange_api_csr_melt.c
@@ -0,0 +1,320 @@
+/*
+ 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/>
+*/
+/**
+ * @file lib/exchange_api_csr_melt.c
+ * @brief Implementation of /csr-melt requests (get R in exchange used for Clause Schnorr refresh)
+ * @author Lucien Heuzeveldt
+ * @author Gian Demarmels
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Clause Schnorr R Handle
+ */
+struct TALER_EXCHANGE_CsRMeltHandle
+{
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_CsRMeltCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+};
+
+
+/**
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation.
+ * Extract the coin's signature and return it to the caller. The signature we
+ * get from the exchange is for the blinded value. Thus, we first must
+ * unblind it and then should verify its validity against our coin's hash.
+ *
+ * If everything checks out, we return the unblinded signature
+ * to the application via the callback.
+ *
+ * @param csrh operation handle
+ * @param arr reply from the exchange
+ * @param hr http response details
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+csr_ok (struct TALER_EXCHANGE_CsRMeltHandle *csrh,
+ const json_t *arr,
+ struct TALER_EXCHANGE_HttpResponse *hr)
+{
+ size_t alen = json_array_size (arr);
+ struct TALER_ExchangeWithdrawValues alg_values[GNUNET_NZL (alen)];
+ struct TALER_EXCHANGE_CsRMeltResponse csrr = {
+ .hr = *hr,
+ .details.ok.alg_values_len = alen,
+ .details.ok.alg_values = alg_values
+ };
+
+ for (size_t i = 0; i<alen; i++)
+ {
+ json_t *av = json_array_get (arr,
+ i);
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_exchange_withdraw_values (
+ "ewv",
+ &alg_values[i]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (av,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ csrh->cb (csrh->cb_cls,
+ &csrr);
+ for (size_t i = 0; i<alen; i++)
+ TALER_denom_ewv_free (&alg_values[i]);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the HTTP /csr request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_CsRMeltHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_csr_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_CsRMeltHandle *csrh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_HttpResponse hr = {
+ .reply = j,
+ .http_status = (unsigned int) response_code
+ };
+ struct TALER_EXCHANGE_CsRMeltResponse csrr = {
+ .hr = hr
+ };
+
+ csrh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ csrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ json_t *arr;
+
+ arr = json_object_get (j,
+ "ewvs");
+ if ( (NULL == arr) ||
+ (0 == json_array_size (arr)) ||
+ (GNUNET_OK !=
+ csr_ok (csrh,
+ arr,
+ &hr)) )
+ {
+ GNUNET_break_op (0);
+ csrr.hr.http_status = 0;
+ csrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ TALER_EXCHANGE_csr_melt_cancel (csrh);
+ return;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, the exchange basically just says
+ that it doesn't know the /csr endpoint or denomination.
+ Can happen if the exchange doesn't support Clause Schnorr.
+ We should simply pass the JSON reply to the application. */
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for CS R request\n",
+ (unsigned int) response_code,
+ (int) hr.ec);
+ break;
+ }
+ csrh->cb (csrh->cb_cls,
+ &csrr);
+ csrh->cb = NULL;
+ TALER_EXCHANGE_csr_melt_cancel (csrh);
+}
+
+
+struct TALER_EXCHANGE_CsRMeltHandle *
+TALER_EXCHANGE_csr_melt (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_RefreshMasterSecretP *rms,
+ unsigned int nks_len,
+ struct TALER_EXCHANGE_NonceKey nks[static nks_len],
+ TALER_EXCHANGE_CsRMeltCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_CsRMeltHandle *csrh;
+ json_t *csr_arr;
+
+ if (0 == nks_len)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ for (unsigned int i = 0; i<nks_len; i++)
+ if (GNUNET_CRYPTO_BSA_CS !=
+ nks[i].pk->key.bsign_pub_key->cipher)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ csrh = GNUNET_new (struct TALER_EXCHANGE_CsRMeltHandle);
+ csrh->cb = res_cb;
+ csrh->cb_cls = res_cb_cls;
+ csr_arr = json_array ();
+ GNUNET_assert (NULL != csr_arr);
+ for (unsigned int i = 0; i<nks_len; i++)
+ {
+ const struct TALER_EXCHANGE_NonceKey *nk = &nks[i];
+ json_t *csr_obj;
+
+ csr_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("coin_offset",
+ nk->cnc_num),
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &nk->pk->h_key));
+ GNUNET_assert (NULL != csr_obj);
+ GNUNET_assert (0 ==
+ json_array_append_new (csr_arr,
+ csr_obj));
+ }
+ csrh->url = TALER_url_join (url,
+ "csr-melt",
+ NULL);
+ if (NULL == csrh->url)
+ {
+ json_decref (csr_arr);
+ GNUNET_free (csrh);
+ return NULL;
+ }
+ {
+ CURL *eh;
+ json_t *req;
+
+ req = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("rms",
+ rms),
+ GNUNET_JSON_pack_array_steal ("nks",
+ csr_arr));
+ eh = TALER_EXCHANGE_curl_easy_get_ (csrh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&csrh->post_ctx,
+ eh,
+ req)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (req);
+ GNUNET_free (csrh->url);
+ GNUNET_free (csrh);
+ return NULL;
+ }
+ json_decref (req);
+ csrh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ csrh->post_ctx.headers,
+ &handle_csr_finished,
+ csrh);
+ }
+ return csrh;
+}
+
+
+void
+TALER_EXCHANGE_csr_melt_cancel (struct TALER_EXCHANGE_CsRMeltHandle *csrh)
+{
+ if (NULL != csrh->job)
+ {
+ GNUNET_CURL_job_cancel (csrh->job);
+ csrh->job = NULL;
+ }
+ GNUNET_free (csrh->url);
+ TALER_curl_easy_post_finished (&csrh->post_ctx);
+ GNUNET_free (csrh);
+}
diff --git a/src/lib/exchange_api_csr_withdraw.c b/src/lib/exchange_api_csr_withdraw.c
new file mode 100644
index 000000000..0fe731cd5
--- /dev/null
+++ b/src/lib/exchange_api_csr_withdraw.c
@@ -0,0 +1,281 @@
+/*
+ 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/>
+*/
+/**
+ * @file lib/exchange_api_csr_withdraw.c
+ * @brief Implementation of /csr-withdraw requests (get R in exchange used for Clause Schnorr withdraw and refresh)
+ * @author Lucien Heuzeveldt
+ * @author Gian Demarmels
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Clause Schnorr R Handle
+ */
+struct TALER_EXCHANGE_CsRWithdrawHandle
+{
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_CsRWithdrawCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+};
+
+
+/**
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation.
+ * Extract the coin's signature and return it to the caller. The signature we
+ * get from the exchange is for the blinded value. Thus, we first must
+ * unblind it and then should verify its validity against our coin's hash.
+ *
+ * If everything checks out, we return the unblinded signature
+ * to the application via the callback.
+ *
+ * @param csrh operation handle
+ * @param av reply from the exchange
+ * @param hr http response details
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+csr_ok (struct TALER_EXCHANGE_CsRWithdrawHandle *csrh,
+ const json_t *av,
+ struct TALER_EXCHANGE_HttpResponse *hr)
+{
+ struct TALER_EXCHANGE_CsRWithdrawResponse csrr = {
+ .hr = *hr,
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_exchange_withdraw_values (
+ "ewv",
+ &csrr.details.ok.alg_values),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (av,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ csrh->cb (csrh->cb_cls,
+ &csrr);
+ TALER_denom_ewv_free (&csrr.details.ok.alg_values);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the HTTP /csr request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_CsRWithdrawHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_csr_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_CsRWithdrawHandle *csrh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_HttpResponse hr = {
+ .reply = j,
+ .http_status = (unsigned int) response_code
+ };
+ struct TALER_EXCHANGE_CsRWithdrawResponse csrr = {
+ .hr = hr
+ };
+
+ csrh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ csrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ if (GNUNET_OK !=
+ csr_ok (csrh,
+ response,
+ &hr))
+ {
+ GNUNET_break_op (0);
+ csrr.hr.http_status = 0;
+ csrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ TALER_EXCHANGE_csr_withdraw_cancel (csrh);
+ return;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, the exchange basically just says
+ that it doesn't know the /csr endpoint or denomination.
+ Can happen if the exchange doesn't support Clause Schnorr.
+ We should simply pass the JSON reply to the application. */
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for CS R request\n",
+ (unsigned int) response_code,
+ (int) hr.ec);
+ break;
+ }
+ csrh->cb (csrh->cb_cls,
+ &csrr);
+ csrh->cb = NULL;
+ TALER_EXCHANGE_csr_withdraw_cancel (csrh);
+}
+
+
+struct TALER_EXCHANGE_CsRWithdrawHandle *
+TALER_EXCHANGE_csr_withdraw (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce,
+ TALER_EXCHANGE_CsRWithdrawCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_CsRWithdrawHandle *csrh;
+
+ if (GNUNET_CRYPTO_BSA_CS !=
+ pk->key.bsign_pub_key->cipher)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ csrh = GNUNET_new (struct TALER_EXCHANGE_CsRWithdrawHandle);
+ csrh->cb = res_cb;
+ csrh->cb_cls = res_cb_cls;
+ csrh->url = TALER_url_join (exchange_url,
+ "csr-withdraw",
+ NULL);
+ if (NULL == csrh->url)
+ {
+ GNUNET_free (csrh);
+ return NULL;
+ }
+
+ {
+ CURL *eh;
+ json_t *req;
+
+ req = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_varsize ("nonce",
+ nonce,
+ sizeof(*nonce)),
+ GNUNET_JSON_pack_data_varsize ("denom_pub_hash",
+ &pk->h_key,
+ sizeof(pk->h_key)));
+ GNUNET_assert (NULL != req);
+ eh = TALER_EXCHANGE_curl_easy_get_ (csrh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&csrh->post_ctx,
+ eh,
+ req)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (req);
+ GNUNET_free (csrh->url);
+ GNUNET_free (csrh);
+ return NULL;
+ }
+ json_decref (req);
+ csrh->job = GNUNET_CURL_job_add2 (curl_ctx,
+ eh,
+ csrh->post_ctx.headers,
+ &handle_csr_finished,
+ csrh);
+ }
+ return csrh;
+}
+
+
+void
+TALER_EXCHANGE_csr_withdraw_cancel (
+ struct TALER_EXCHANGE_CsRWithdrawHandle *csrh)
+{
+ if (NULL != csrh->job)
+ {
+ GNUNET_CURL_job_cancel (csrh->job);
+ csrh->job = NULL;
+ }
+ GNUNET_free (csrh->url);
+ TALER_curl_easy_post_finished (&csrh->post_ctx);
+ GNUNET_free (csrh);
+}
diff --git a/src/lib/exchange_api_curl_defaults.c b/src/lib/exchange_api_curl_defaults.c
index 82d3ace13..85a32189b 100644
--- a/src/lib/exchange_api_curl_defaults.c
+++ b/src/lib/exchange_api_curl_defaults.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 Taler Systems SA
+ Copyright (C) 2014-2018, 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
@@ -19,16 +19,11 @@
* @brief curl easy handle defaults
* @author Florian Dold
*/
-
+#include "platform.h"
+#include "taler_curl_lib.h"
#include "exchange_api_curl_defaults.h"
-/**
- * Get a curl handle with the right defaults for the exchange lib. In the
- * future, we might manage a pool of connections here.
- *
- * @param url URL to query
- */
CURL *
TALER_EXCHANGE_curl_easy_get_ (const char *url)
{
@@ -36,21 +31,22 @@ TALER_EXCHANGE_curl_easy_get_ (const char *url)
eh = curl_easy_init ();
if (NULL == eh)
+ {
+ GNUNET_break (0);
return NULL;
+ }
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_URL,
url));
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_FOLLOWLOCATION,
- 1L));
- /* limit MAXREDIRS to 5 as a simple security measure against
- a potential infinite loop caused by a malicious target */
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_MAXREDIRS,
- 5L));
+ TALER_curl_set_secure_redirect_policy (eh,
+ url);
+ /* Enable compression (using whatever curl likes), see
+ https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html */
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_ACCEPT_ENCODING,
+ ""));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_TCP_FASTOPEN,
diff --git a/src/lib/exchange_api_curl_defaults.h b/src/lib/exchange_api_curl_defaults.h
index 009d72ab8..c4ba04fc5 100644
--- a/src/lib/exchange_api_curl_defaults.h
+++ b/src/lib/exchange_api_curl_defaults.h
@@ -25,7 +25,6 @@
#define _TALER_CURL_DEFAULTS_H
-#include "platform.h"
#include <gnunet/gnunet_curl_lib.h>
diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c
deleted file mode 100644
index 54dad7473..000000000
--- a/src/lib/exchange_api_deposit.c
+++ /dev/null
@@ -1,727 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2020 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 lib/exchange_api_deposit.c
- * @brief Implementation of the /deposit request of the exchange's HTTP API
- * @author Sree Harsha Totakura <sreeharsha@totakura.in>
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_json_lib.h"
-#include "taler_auditor_service.h"
-#include "taler_exchange_service.h"
-#include "exchange_api_handle.h"
-#include "taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * 1:#AUDITOR_CHANCE is the probability that we report deposits
- * to the auditor.
- *
- * 20==5% of going to auditor. This is possibly still too high, but set
- * deliberately this high for testing
- */
-#define AUDITOR_CHANCE 20
-
-/**
- * @brief A Deposit Handle
- */
-struct TALER_EXCHANGE_DepositHandle
-{
-
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Context for #TEH_curl_easy_post(). Keeps the data that must
- * persist for Curl to make the upload.
- */
- struct TALER_CURL_PostContext ctx;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_DepositResultCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Information the exchange should sign in response.
- */
- struct TALER_DepositConfirmationPS depconf;
-
- /**
- * Exchange signature, set for #auditor_cb.
- */
- struct TALER_ExchangeSignatureP exchange_sig;
-
- /**
- * Exchange signing public key, set for #auditor_cb.
- */
- struct TALER_ExchangePublicKeyP exchange_pub;
-
- /**
- * Value of the /deposit transaction, including fee.
- */
- struct TALER_Amount amount_with_fee;
-
- /**
- * @brief Public information about the coin's denomination key.
- * Note that the "key" field itself has been zero'ed out.
- */
- struct TALER_EXCHANGE_DenomPublicKey dki;
-
- /**
- * Chance that we will inform the auditor about the deposit
- * is 1:n, where the value of this field is "n".
- */
- unsigned int auditor_chance;
-
-};
-
-
-/**
- * Function called for each auditor to give us a chance to possibly
- * launch a deposit confirmation interaction.
- *
- * @param cls closure
- * @param ah handle to the auditor
- * @param auditor_pub public key of the auditor
- * @return NULL if no deposit confirmation interaction was launched
- */
-static struct TEAH_AuditorInteractionEntry *
-auditor_cb (void *cls,
- struct TALER_AUDITOR_Handle *ah,
- const struct TALER_AuditorPublicKeyP *auditor_pub)
-{
- struct TALER_EXCHANGE_DepositHandle *dh = cls;
- const struct TALER_EXCHANGE_Keys *key_state;
- const struct TALER_EXCHANGE_SigningPublicKey *spk;
- struct TALER_Amount amount_without_fee;
- struct TEAH_AuditorInteractionEntry *aie;
-
- if (0 != GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
- dh->auditor_chance))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not providing deposit confirmation to auditor\n");
- return NULL;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Will provide deposit confirmation to auditor `%s'\n",
- TALER_B2S (auditor_pub));
- key_state = TALER_EXCHANGE_get_keys (dh->exchange);
- spk = TALER_EXCHANGE_get_signing_key_info (key_state,
- &dh->exchange_pub);
- if (NULL == spk)
- {
- GNUNET_break_op (0);
- return NULL;
- }
- TALER_amount_ntoh (&amount_without_fee,
- &dh->depconf.amount_without_fee);
- aie = GNUNET_new (struct TEAH_AuditorInteractionEntry);
- aie->dch = TALER_AUDITOR_deposit_confirmation (ah,
- &dh->depconf.h_wire,
- &dh->depconf.h_contract_terms,
- GNUNET_TIME_absolute_ntoh (
- dh->depconf.timestamp),
- GNUNET_TIME_absolute_ntoh (
- dh->depconf.refund_deadline),
- &amount_without_fee,
- &dh->depconf.coin_pub,
- &dh->depconf.merchant,
- &dh->exchange_pub,
- &dh->exchange_sig,
- &key_state->master_pub,
- spk->valid_from,
- spk->valid_until,
- spk->valid_legal,
- &spk->master_sig,
- &TEAH_acc_confirmation_cb,
- aie);
- return aie;
-}
-
-
-/**
- * Verify that the signature on the "200 OK" response
- * from the exchange is valid.
- *
- * @param dh deposit handle
- * @param json json reply with the signature
- * @param[out] exchange_sig set to the exchange's signature
- * @param[out] exchange_pub set to the exchange's public key
- * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
- */
-static int
-verify_deposit_signature_ok (struct TALER_EXCHANGE_DepositHandle *dh,
- const json_t *json,
- struct TALER_ExchangeSignatureP *exchange_sig,
- struct TALER_ExchangePublicKeyP *exchange_pub)
-{
- const struct TALER_EXCHANGE_Keys *key_state;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("sig", exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("pub", exchange_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- key_state = TALER_EXCHANGE_get_keys (dh->exchange);
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- exchange_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT,
- &dh->depconf.purpose,
- &exchange_sig->eddsa_signature,
- &exchange_pub->eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- dh->exchange_sig = *exchange_sig;
- dh->exchange_pub = *exchange_pub;
- TEAH_get_auditors_for_dc (dh->exchange,
- &auditor_cb,
- dh);
- return GNUNET_OK;
-}
-
-
-/**
- * Verify that the signatures on the "403 FORBIDDEN" response from the
- * exchange demonstrating customer double-spending are valid.
- *
- * @param dh deposit handle
- * @param json json reply with the signature(s) and transaction history
- * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not
- */
-static int
-verify_deposit_signature_forbidden (
- const struct TALER_EXCHANGE_DepositHandle *dh,
- const json_t *json)
-{
- json_t *history;
- struct TALER_Amount total;
-
- history = json_object_get (json,
- "history");
- if (GNUNET_OK !=
- TALER_EXCHANGE_verify_coin_history (&dh->dki,
- dh->dki.value.currency,
- &dh->depconf.coin_pub,
- history,
- &total))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (&total,
- &total,
- &dh->amount_with_fee))
- {
- /* clearly not OK if our transaction would have caused
- the overflow... */
- return GNUNET_OK;
- }
-
- if (0 >= TALER_amount_cmp (&total,
- &dh->dki.value))
- {
- /* transaction should have still fit */
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- /* everything OK, proof of double-spending was provided */
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /deposit request.
- *
- * @param cls the `struct TALER_EXCHANGE_DepositHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_deposit_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_DepositHandle *dh = cls;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP *es = NULL;
- struct TALER_ExchangePublicKeyP *ep = NULL;
- const json_t *j = response;
- enum TALER_ErrorCode ec;
-
- dh->job = NULL;
- switch (response_code)
- {
- case 0:
- ec = TALER_EC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- verify_deposit_signature_ok (dh,
- j,
- &exchange_sig,
- &exchange_pub))
- {
- GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
- }
- else
- {
- es = &exchange_sig;
- ep = &exchange_pub;
- ec = TALER_EC_NONE;
- }
- break;
- case MHD_HTTP_BAD_REQUEST:
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
- break;
- case MHD_HTTP_CONFLICT:
- /* Double spending; check signatures on transaction history */
- ec = TALER_JSON_get_error_code (j);
- if (GNUNET_OK !=
- verify_deposit_signature_forbidden (dh,
- j))
- {
- GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
- }
- break;
- case MHD_HTTP_FORBIDDEN:
- ec = TALER_JSON_get_error_code (j);
- /* Nothing really to verify, exchange says one of the signatures is
- invalid; as we checked them, this should never happen, we
- should pass the JSON reply to the application */
- break;
- case MHD_HTTP_NOT_FOUND:
- ec = TALER_JSON_get_error_code (j);
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- ec = TALER_JSON_get_error_code (j);
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- break;
- default:
- /* unexpected response code */
- ec = TALER_JSON_get_error_code (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- ec);
- GNUNET_break (0);
- response_code = 0;
- break;
- }
- dh->cb (dh->cb_cls,
- response_code,
- ec,
- es,
- ep,
- j);
- TALER_EXCHANGE_deposit_cancel (dh);
-}
-
-
-/**
- * Verify signature information about the deposit.
- *
- * @param dki public key information
- * @param amount the amount to be deposited
- * @param h_wire hash of the merchant’s account details
- * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)
- * @param coin_pub coin’s public key
- * @param denom_pub denomination key with which the coin is signed
- * @param denom_pub_hash hash of @a denom_pub
- * @param denom_sig exchange’s unblinded signature of the coin
- * @param timestamp timestamp when the deposit was finalized
- * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
- * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed)
- * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
- * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not
- */
-static int
-verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki,
- const struct TALER_Amount *amount,
- const struct GNUNET_HashCode *h_wire,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct GNUNET_HashCode *denom_pub_hash,
- struct GNUNET_TIME_Absolute timestamp,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- struct GNUNET_TIME_Absolute refund_deadline,
- const struct TALER_CoinSpendSignatureP *coin_sig)
-{
- {
- struct TALER_DepositRequestPS dr = {
- .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT),
- .purpose.size = htonl (sizeof (dr)),
- .h_contract_terms = *h_contract_terms,
- .h_wire = *h_wire,
- .timestamp = GNUNET_TIME_absolute_hton (timestamp),
- .refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline),
- .merchant = *merchant_pub,
- .coin_pub = *coin_pub
- };
-
- TALER_amount_hton (&dr.amount_with_fee,
- amount);
- TALER_amount_hton (&dr.deposit_fee,
- &dki->fee_deposit);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT,
- &dr.purpose,
- &coin_sig->eddsa_signature,
- &coin_pub->eddsa_pub))
- {
- GNUNET_break_op (0);
- TALER_LOG_WARNING ("Invalid coin signature on /deposit request!\n");
- {
- TALER_LOG_DEBUG ("... amount_with_fee was %s\n",
- TALER_amount2s (amount));
- TALER_LOG_DEBUG ("... deposit_fee was %s\n",
- TALER_amount2s (&dki->fee_deposit));
- }
- return GNUNET_SYSERR;
- }
- }
-
- /* check coin signature */
- {
- struct TALER_CoinPublicInfo coin_info = {
- .coin_pub = *coin_pub,
- .denom_pub_hash = *denom_pub_hash,
- .denom_sig = *denom_sig
- };
-
- if (GNUNET_YES !=
- TALER_test_coin_valid (&coin_info,
- denom_pub))
- {
- GNUNET_break_op (0);
- TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
- return GNUNET_SYSERR;
- }
- }
-
- /* Check coin does make a contribution */
- if (0 < TALER_amount_cmp (&dki->fee_deposit,
- amount))
- {
- GNUNET_break_op (0);
- TALER_LOG_WARNING ("Deposit amount smaller than fee\n");
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Submit a deposit permission to the exchange and get the exchange's response.
- * Note that while we return the response verbatim to the caller for
- * further processing, we do already verify that the response is
- * well-formed (i.e. that signatures included in the response are all
- * valid). If the exchange's reply is not well-formed, we return an
- * HTTP status code of zero to @a cb.
- *
- * We also verify that the @a coin_sig is valid for this deposit
- * request, and that the @a ub_sig is a valid signature for @a
- * coin_pub. Also, the @a exchange must be ready to operate (i.e. have
- * finished processing the /keys reply). If either check fails, we do
- * NOT initiate the transaction with the exchange and instead return NULL.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param amount the amount to be deposited
- * @param wire_deadline date until which the merchant would like the exchange to settle the balance (advisory, the exchange cannot be
- * forced to settle in the past or upon very short notice, but of course a well-behaved exchange will limit aggregation based on the advice received)
- * @param wire_details the merchant’s account details, in a format supported by the exchange
- * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)
- * @param coin_pub coin’s public key
- * @param denom_pub denomination key with which the coin is signed
- * @param denom_sig exchange’s unblinded signature of the coin
- * @param timestamp timestamp when the contract was finalized, must not be too far in the future
- * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
- * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline
- * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
- * @param cb the callback to call when a reply for this request is available
- * @param cb_cls closure for the above callback
- * @return a handle for this request; NULL if the inputs are invalid (i.e.
- * signatures fail to verify). In this case, the callback is not called.
- */
-struct TALER_EXCHANGE_DepositHandle *
-TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_Amount *amount,
- struct GNUNET_TIME_Absolute wire_deadline,
- json_t *wire_details,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_DenominationPublicKey *denom_pub,
- struct GNUNET_TIME_Absolute timestamp,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- struct GNUNET_TIME_Absolute refund_deadline,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- TALER_EXCHANGE_DepositResultCallback cb,
- void *cb_cls)
-{
- const struct TALER_EXCHANGE_Keys *key_state;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
- struct TALER_EXCHANGE_DepositHandle *dh;
- struct GNUNET_CURL_Context *ctx;
- json_t *deposit_obj;
- CURL *eh;
- struct GNUNET_HashCode h_wire;
- struct GNUNET_HashCode denom_pub_hash;
- struct TALER_Amount amount_without_fee;
- char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
-
- {
- char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (coin_pub,
- sizeof (struct
- TALER_CoinSpendPublicKeyP),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "/coins/%s/deposit",
- pub_str);
- }
- (void) GNUNET_TIME_round_abs (&wire_deadline);
- (void) GNUNET_TIME_round_abs (&refund_deadline);
- if (refund_deadline.abs_value_us > wire_deadline.abs_value_us)
- {
- GNUNET_break (0);
- return NULL;
- }
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
- /* initialize h_wire */
- if (GNUNET_OK !=
- TALER_JSON_merchant_wire_signature_hash (wire_details,
- &h_wire))
- {
- GNUNET_break (0);
- return NULL;
- }
- key_state = TALER_EXCHANGE_get_keys (exchange);
- dki = TALER_EXCHANGE_get_denomination_key (key_state,
- denom_pub);
- if (NULL == dki)
- {
- GNUNET_break (0);
- return NULL;
- }
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&amount_without_fee,
- amount,
- &dki->fee_deposit))
- {
- GNUNET_break_op (0);
- return NULL;
- }
- GNUNET_CRYPTO_rsa_public_key_hash (denom_pub->rsa_public_key,
- &denom_pub_hash);
- if (GNUNET_OK !=
- verify_signatures (dki,
- amount,
- &h_wire,
- h_contract_terms,
- coin_pub,
- denom_sig,
- denom_pub,
- &denom_pub_hash,
- timestamp,
- merchant_pub,
- refund_deadline,
- coin_sig))
- {
- GNUNET_break_op (0);
- return NULL;
- }
-
- deposit_obj = json_pack ("{s:o, s:O," /* f/wire */
- " s:o, s:o," /* h_wire, h_contract_terms */
- " s:o," /* denom_pub */
- " s:o, s:o," /* ub_sig, timestamp */
- " s:o," /* merchant_pub */
- " s:o, s:o," /* refund_deadline, wire_deadline */
- " s:o}", /* coin_sig */
- "contribution", TALER_JSON_from_amount (amount),
- "wire", wire_details,
- "h_wire", GNUNET_JSON_from_data_auto (&h_wire),
- "h_contract_terms", GNUNET_JSON_from_data_auto (
- h_contract_terms),
- "denom_pub_hash", GNUNET_JSON_from_data_auto (
- &denom_pub_hash),
- "ub_sig", GNUNET_JSON_from_rsa_signature (
- denom_sig->rsa_signature),
- "timestamp", GNUNET_JSON_from_time_abs (timestamp),
- "merchant_pub", GNUNET_JSON_from_data_auto (
- merchant_pub),
- "refund_deadline", GNUNET_JSON_from_time_abs (
- refund_deadline),
- "wire_transfer_deadline", GNUNET_JSON_from_time_abs (
- wire_deadline),
- "coin_sig", GNUNET_JSON_from_data_auto (coin_sig)
- );
- if (NULL == deposit_obj)
- {
- GNUNET_break (0);
- return NULL;
- }
-
- dh = GNUNET_new (struct TALER_EXCHANGE_DepositHandle);
- dh->auditor_chance = AUDITOR_CHANCE;
- dh->exchange = exchange;
- dh->cb = cb;
- dh->cb_cls = cb_cls;
- dh->url = TEAH_path_to_url (exchange,
- arg_str);
- dh->depconf.purpose.size = htonl (sizeof (struct
- TALER_DepositConfirmationPS));
- dh->depconf.purpose.purpose = htonl (
- TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT);
- dh->depconf.h_contract_terms = *h_contract_terms;
- dh->depconf.h_wire = h_wire;
- dh->depconf.timestamp = GNUNET_TIME_absolute_hton (timestamp);
- dh->depconf.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
- TALER_amount_hton (&dh->depconf.amount_without_fee,
- &amount_without_fee);
- dh->depconf.coin_pub = *coin_pub;
- dh->depconf.merchant = *merchant_pub;
- dh->amount_with_fee = *amount;
- dh->dki = *dki;
- dh->dki.key.rsa_public_key = NULL; /* lifetime not warranted, so better
- not copy the pointer */
-
- eh = TALER_EXCHANGE_curl_easy_get_ (dh->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&dh->ctx,
- eh,
- deposit_obj)) )
- {
- GNUNET_break (0);
- if (NULL != eh)
- curl_easy_cleanup (eh);
- json_decref (deposit_obj);
- GNUNET_free (dh->url);
- GNUNET_free (dh);
- return NULL;
- }
- json_decref (deposit_obj);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "URL for deposit: `%s'\n",
- dh->url);
- ctx = TEAH_handle_to_context (exchange);
- dh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- dh->ctx.headers,
- &handle_deposit_finished,
- dh);
- return dh;
-}
-
-
-/**
- * Change the chance that our deposit confirmation will be given to the
- * auditor to 100%.
- *
- * @param deposit the deposit permission request handle
- */
-void
-TALER_EXCHANGE_deposit_force_dc (struct TALER_EXCHANGE_DepositHandle *deposit)
-{
- deposit->auditor_chance = 1;
-}
-
-
-/**
- * Cancel a deposit permission request. This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param deposit the deposit permission request handle
- */
-void
-TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit)
-{
- if (NULL != deposit->job)
- {
- GNUNET_CURL_job_cancel (deposit->job);
- deposit->job = NULL;
- }
- GNUNET_free (deposit->url);
- TALER_curl_easy_post_finished (&deposit->ctx);
- GNUNET_free (deposit);
-}
-
-
-/* end of exchange_api_deposit.c */
diff --git a/src/lib/exchange_api_deposits_get.c b/src/lib/exchange_api_deposits_get.c
index 0ab926eab..20eaea3d3 100644
--- a/src/lib/exchange_api_deposits_get.c
+++ b/src/lib/exchange_api_deposits_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 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
@@ -33,15 +33,15 @@
/**
- * @brief A Deposit Wtid Handle
+ * @brief A Deposit Get Handle
*/
struct TALER_EXCHANGE_DepositGetHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -70,64 +70,22 @@ struct TALER_EXCHANGE_DepositGetHandle
void *cb_cls;
/**
- * Information the exchange should sign in response.
- * (with pre-filled fields from the request).
+ * Hash over the wiring information of the merchant.
*/
- struct TALER_ConfirmWirePS depconf;
-
-};
+ struct TALER_MerchantWireHashP h_wire;
+ /**
+ * Hash over the contract for which this deposit is made.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
-/**
- * Verify that the signature on the "200 OK" response
- * from the exchange is valid.
- *
- * @param dwh deposit wtid handle
- * @param json json reply with the signature
- * @param[out] exchange_pub set to the exchange's public key
- * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
- */
-static int
-verify_deposit_wtid_signature_ok (
- const struct TALER_EXCHANGE_DepositGetHandle *dwh,
- const json_t *json,
- struct TALER_ExchangePublicKeyP *exchange_pub)
-{
- struct TALER_ExchangeSignatureP exchange_sig;
- const struct TALER_EXCHANGE_Keys *key_state;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub", exchange_pub),
- GNUNET_JSON_spec_end ()
- };
+ /**
+ * The coin's public key. This is the value that must have been
+ * signed (blindly) by the Exchange.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- key_state = TALER_EXCHANGE_get_keys (dwh->exchange);
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- exchange_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE,
- &dwh->depconf.purpose,
- &exchange_sig.eddsa_signature,
- &exchange_pub->eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
+};
/**
@@ -144,66 +102,92 @@ handle_deposit_wtid_finished (void *cls,
const void *response)
{
struct TALER_EXCHANGE_DepositGetHandle *dwh = cls;
- const struct TALER_WireTransferIdentifierRawP *wtid = NULL;
- struct GNUNET_TIME_Absolute execution_time = GNUNET_TIME_UNIT_FOREVER_ABS;
- const struct TALER_Amount *coin_contribution = NULL;
- struct TALER_Amount coin_contribution_s;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangePublicKeyP *ep = NULL;
const json_t *j = response;
- enum TALER_ErrorCode ec;
+ struct TALER_EXCHANGE_GetDepositResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
dwh->job = NULL;
switch (response_code)
{
case 0:
- ec = TALER_EC_INVALID_RESPONSE;
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
{
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("wtid", &dwh->depconf.wtid),
- GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time),
- TALER_JSON_spec_amount ("coin_contribution", &coin_contribution_s),
+ GNUNET_JSON_spec_fixed_auto ("wtid",
+ &dr.details.ok.wtid),
+ GNUNET_JSON_spec_timestamp ("execution_time",
+ &dr.details.ok.execution_time),
+ TALER_JSON_spec_amount_any ("coin_contribution",
+ &dr.details.ok.coin_contribution),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &dr.details.ok.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &dr.details.ok.exchange_pub),
GNUNET_JSON_spec_end ()
};
+ const struct TALER_EXCHANGE_Keys *key_state;
+ key_state = dwh->keys;
+ GNUNET_assert (NULL != key_state);
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_DEPOSITS_INVALID_BODY_BY_EXCHANGE;
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
- wtid = &dwh->depconf.wtid;
- dwh->depconf.execution_time = GNUNET_TIME_absolute_hton (execution_time);
- TALER_amount_hton (&dwh->depconf.coin_contribution,
- &coin_contribution_s);
- coin_contribution = &coin_contribution_s;
if (GNUNET_OK !=
- verify_deposit_wtid_signature_ok (dwh,
- j,
- &exchange_pub))
+ TALER_EXCHANGE_test_signing_key (key_state,
+ &dr.details.ok.exchange_pub))
{
GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_DEPOSITS_INVALID_SIGNATURE_BY_EXCHANGE;
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
}
- else
+ if (GNUNET_OK !=
+ TALER_exchange_online_confirm_wire_verify (
+ &dwh->h_wire,
+ &dwh->h_contract_terms,
+ &dr.details.ok.wtid,
+ &dwh->coin_pub,
+ dr.details.ok.execution_time,
+ &dr.details.ok.coin_contribution,
+ &dr.details.ok.exchange_pub,
+ &dr.details.ok.exchange_sig))
{
- ep = &exchange_pub;
- ec = TALER_EC_NONE;
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
}
+ dwh->cb (dwh->cb_cls,
+ &dr);
+ TALER_EXCHANGE_deposits_get_cancel (dwh);
+ return;
}
- break;
case MHD_HTTP_ACCEPTED:
{
/* Transaction known, but not executed yet */
+ bool no_legi = false;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time),
+ GNUNET_JSON_spec_timestamp ("execution_time",
+ &dr.details.accepted.execution_time),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("requirement_row",
+ &dr.details.accepted.requirement_row),
+ &no_legi),
+ TALER_JSON_spec_aml_decision ("aml_decision",
+ &dr.details.accepted.aml_decision),
+ GNUNET_JSON_spec_bool ("kyc_ok",
+ &dr.details.accepted.kyc_ok),
GNUNET_JSON_spec_end ()
};
@@ -213,169 +197,167 @@ handle_deposit_wtid_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_DEPOSITS_INVALID_BODY_BY_EXCHANGE;
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
- ec = TALER_EC_NONE;
+ if (no_legi)
+ dr.details.accepted.requirement_row = 0;
+ dwh->cb (dwh->cb_cls,
+ &dr);
+ TALER_EXCHANGE_deposits_get_cancel (dwh);
+ return;
}
- break;
case MHD_HTTP_BAD_REQUEST:
- ec = TALER_JSON_get_error_code (j);
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
/* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */
break;
case MHD_HTTP_FORBIDDEN:
- ec = TALER_JSON_get_error_code (j);
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
/* Nothing really to verify, exchange says one of the signatures is
invalid; as we checked them, this should never happen, we
should pass the JSON reply to the application */
break;
case MHD_HTTP_NOT_FOUND:
- ec = TALER_JSON_get_error_code (j);
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
/* Exchange does not know about transaction;
we should pass the reply to the application */
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
- ec = TALER_JSON_get_error_code (j);
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
break;
default:
/* unexpected response code */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u\n",
- (unsigned int) response_code);
- GNUNET_break (0);
- ec = TALER_JSON_get_error_code (j);
- response_code = 0;
+ "Unexpected response code %u/%d for exchange GET deposits\n",
+ (unsigned int) response_code,
+ (int) dr.hr.ec);
+ GNUNET_break_op (0);
break;
}
dwh->cb (dwh->cb_cls,
- response_code,
- ec,
- ep,
- j,
- wtid,
- execution_time,
- coin_contribution);
+ &dr);
TALER_EXCHANGE_deposits_get_cancel (dwh);
}
-/**
- * Obtain wire transfer details about an existing deposit operation.
- *
- * @param exchange the exchange to query
- * @param merchant_priv the merchant's private key
- * @param h_wire hash of merchant's wire transfer details
- * @param h_contract_terms hash of the proposal data from the contract
- * between merchant and customer
- * @param coin_pub public key of the coin
- * @param cb function to call with the result
- * @param cb_cls closure for @a cb
- * @return handle to abort request
- */
struct TALER_EXCHANGE_DepositGetHandle *
TALER_EXCHANGE_deposits_get (
- struct TALER_EXCHANGE_Handle *exchange,
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
- const struct GNUNET_HashCode *h_wire,
- const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct GNUNET_TIME_Relative timeout,
TALER_EXCHANGE_DepositGetCallback cb,
void *cb_cls)
{
- struct TALER_DepositTrackPS dtp;
+ struct TALER_MerchantPublicKeyP merchant;
struct TALER_MerchantSignatureP merchant_sig;
struct TALER_EXCHANGE_DepositGetHandle *dwh;
- struct GNUNET_CURL_Context *ctx;
CURL *eh;
char arg_str[(sizeof (struct TALER_CoinSpendPublicKeyP)
- + sizeof (struct GNUNET_HashCode)
+ + sizeof (struct TALER_MerchantWireHashP)
+ sizeof (struct TALER_MerchantPublicKeyP)
- + sizeof (struct GNUNET_HashCode)
+ + sizeof (struct TALER_PrivateContractHashP)
+ sizeof (struct TALER_MerchantSignatureP)) * 2 + 48];
+ unsigned int tms
+ = (unsigned int) timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
- dtp.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION);
- dtp.purpose.size = htonl (sizeof (dtp));
- dtp.h_contract_terms = *h_contract_terms;
- dtp.h_wire = *h_wire;
GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
- &dtp.merchant.eddsa_pub);
-
- dtp.coin_pub = *coin_pub;
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv,
- &dtp.purpose,
- &merchant_sig.eddsa_sig));
+ &merchant.eddsa_pub);
+ TALER_merchant_deposit_sign (h_contract_terms,
+ h_wire,
+ coin_pub,
+ merchant_priv,
+ &merchant_sig);
{
char cpub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
char mpub_str[sizeof (struct TALER_MerchantPublicKeyP) * 2];
char msig_str[sizeof (struct TALER_MerchantSignatureP) * 2];
- char chash_str[sizeof (struct GNUNET_HashCode) * 2];
- char whash_str[sizeof (struct GNUNET_HashCode) * 2];
+ char chash_str[sizeof (struct TALER_PrivateContractHashP) * 2];
+ char whash_str[sizeof (struct TALER_MerchantWireHashP) * 2];
+ char timeout_str[24];
char *end;
end = GNUNET_STRINGS_data_to_string (h_wire,
- sizeof (struct
- GNUNET_HashCode),
+ sizeof (*h_wire),
whash_str,
sizeof (whash_str));
*end = '\0';
- end = GNUNET_STRINGS_data_to_string (&dtp.merchant,
- sizeof (struct
- TALER_MerchantPublicKeyP),
+ end = GNUNET_STRINGS_data_to_string (&merchant,
+ sizeof (merchant),
mpub_str,
sizeof (mpub_str));
*end = '\0';
end = GNUNET_STRINGS_data_to_string (h_contract_terms,
- sizeof (struct
- GNUNET_HashCode),
+ sizeof (*h_contract_terms),
chash_str,
sizeof (chash_str));
*end = '\0';
end = GNUNET_STRINGS_data_to_string (coin_pub,
- sizeof (struct
- TALER_CoinSpendPublicKeyP),
+ sizeof (*coin_pub),
cpub_str,
sizeof (cpub_str));
*end = '\0';
end = GNUNET_STRINGS_data_to_string (&merchant_sig,
- sizeof (struct
- TALER_MerchantSignatureP),
+ sizeof (merchant_sig),
msig_str,
sizeof (msig_str));
*end = '\0';
+ if (GNUNET_TIME_relative_is_zero (timeout))
+ {
+ timeout_str[0] = '\0';
+ }
+ else
+ {
+ GNUNET_snprintf (
+ timeout_str,
+ sizeof (timeout_str),
+ "%u",
+ tms);
+ }
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/deposits/%s/%s/%s/%s?merchant_sig=%s",
+ "deposits/%s/%s/%s/%s?merchant_sig=%s%s%s",
whash_str,
mpub_str,
chash_str,
cpub_str,
- msig_str);
+ msig_str,
+ 0 == tms
+ ? ""
+ : "&timeout_ms=",
+ timeout_str);
}
dwh = GNUNET_new (struct TALER_EXCHANGE_DepositGetHandle);
- dwh->exchange = exchange;
dwh->cb = cb;
dwh->cb_cls = cb_cls;
- dwh->url = TEAH_path_to_url (exchange,
- arg_str);
- dwh->depconf.purpose.size = htonl (sizeof (struct TALER_ConfirmWirePS));
- dwh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE);
- dwh->depconf.h_wire = *h_wire;
- dwh->depconf.h_contract_terms = *h_contract_terms;
- dwh->depconf.coin_pub = *coin_pub;
-
+ dwh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == dwh->url)
+ {
+ GNUNET_free (dwh);
+ return NULL;
+ }
+ dwh->h_wire = *h_wire;
+ dwh->h_contract_terms = *h_contract_terms;
+ dwh->coin_pub = *coin_pub;
eh = TALER_EXCHANGE_curl_easy_get_ (dwh->url);
if (NULL == eh)
{
@@ -384,22 +366,22 @@ TALER_EXCHANGE_deposits_get (
GNUNET_free (dwh);
return NULL;
}
- ctx = TEAH_handle_to_context (exchange);
+ if (0 != tms)
+ {
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 100L)));
+ }
dwh->job = GNUNET_CURL_job_add (ctx,
eh,
- GNUNET_NO,
&handle_deposit_wtid_finished,
dwh);
+ dwh->keys = TALER_EXCHANGE_keys_incref (keys);
return dwh;
}
-/**
- * Cancel /deposits/$WTID request. This function cannot be used on a request
- * handle if a response is already served for it.
- *
- * @param dwh the wire deposits request handle
- */
void
TALER_EXCHANGE_deposits_get_cancel (struct TALER_EXCHANGE_DepositGetHandle *dwh)
{
@@ -410,6 +392,7 @@ TALER_EXCHANGE_deposits_get_cancel (struct TALER_EXCHANGE_DepositGetHandle *dwh)
}
GNUNET_free (dwh->url);
TALER_curl_easy_post_finished (&dwh->ctx);
+ TALER_EXCHANGE_keys_decref (dwh->keys);
GNUNET_free (dwh);
}
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
index 8ae5b06d2..fdadc8d2a 100644
--- a/src/lib/exchange_api_handle.c
+++ b/src/lib/exchange_api_handle.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 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
@@ -30,6 +30,7 @@
#include "taler_exchange_service.h"
#include "taler_auditor_service.h"
#include "taler_signatures.h"
+#include "taler_extensions.h"
#include "exchange_api_handle.h"
#include "exchange_api_curl_defaults.h"
#include "backoff.h"
@@ -39,315 +40,341 @@
* Which version of the Taler protocol is implemented
* by this library? Used to determine compatibility.
*/
-#define TALER_PROTOCOL_CURRENT 7
+#define EXCHANGE_PROTOCOL_CURRENT 19
/**
* How many versions are we backwards compatible with?
*/
-#define TALER_PROTOCOL_AGE 0
+#define EXCHANGE_PROTOCOL_AGE 2
+
+/**
+ * Set to 1 for extra debug logging.
+ */
+#define DEBUG 0
/**
* Current version for (local) JSON serialization of persisted
* /keys data.
*/
-#define TALER_SERIALIZATION_FORMAT_VERSION 0
-
+#define EXCHANGE_SERIALIZATION_FORMAT_VERSION 0
/**
- * Log error related to CURL operations.
- *
- * @param type log level
- * @param function which function failed to run
- * @param code what was the curl error code
+ * How far off do we allow key lifetimes to be?
*/
-#define CURL_STRERROR(type, function, code) \
- GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \
- function, __FILE__, __LINE__, curl_easy_strerror (code));
+#define LIFETIME_TOLERANCE GNUNET_TIME_UNIT_HOURS
/**
- * Stages of initialization for the `struct TALER_EXCHANGE_Handle`
+ * If the "Expire" cache control header is missing, for
+ * how long do we assume the reply to be valid at least?
*/
-enum ExchangeHandleState
-{
- /**
- * Just allocated.
- */
- MHS_INIT = 0,
-
- /**
- * Obtained the exchange's certification data and keys.
- */
- MHS_CERT = 1,
-
- /**
- * Failed to initialize (fatal).
- */
- MHS_FAILED = 2
-};
-
+#define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS
/**
- * Data for the request to get the /keys of a exchange.
+ * If the "Expire" cache control header is missing, for
+ * how long do we assume the reply to be valid at least?
*/
-struct KeysRequest;
+#define MINIMUM_EXPIRATION GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MINUTES, 2)
/**
- * Entry in DLL of auditors used by an exchange.
+ * Handle for a GET /keys request.
*/
-struct TEAH_AuditorListEntry
+struct TALER_EXCHANGE_GetKeysHandle
{
- /**
- * Next pointer of DLL.
- */
- struct TEAH_AuditorListEntry *next;
-
- /**
- * Prev pointer of DLL.
- */
- struct TEAH_AuditorListEntry *prev;
-
- /**
- * Base URL of the auditor.
- */
- char *auditor_url;
-
- /**
- * Handle to the auditor.
- */
- struct TALER_AUDITOR_Handle *ah;
/**
- * Head of DLL of interactions with this auditor.
+ * The exchange base URL (i.e. "https://exchange.demo.taler.net/")
*/
- struct TEAH_AuditorInteractionEntry *ai_head;
+ char *exchange_url;
/**
- * Tail of DLL of interactions with this auditor.
+ * The url for the /keys request.
*/
- struct TEAH_AuditorInteractionEntry *ai_tail;
+ char *url;
/**
- * Public key of the auditor.
+ * Previous /keys response, NULL for none.
*/
- struct TALER_AuditorPublicKeyP auditor_pub;
+ struct TALER_EXCHANGE_Keys *prev_keys;
/**
- * Flag indicating that the auditor is available and that protocol
- * version compatibility is given.
- */
- int is_up;
-
-};
-
-
-/**
- * Handle to the exchange
- */
-struct TALER_EXCHANGE_Handle
-{
- /**
- * The context of this handle
+ * Entry for this request with the `struct GNUNET_CURL_Context`.
*/
- struct GNUNET_CURL_Context *ctx;
+ struct GNUNET_CURL_Job *job;
/**
- * The URL of the exchange (i.e. "http://exchange.taler.net/")
+ * Expiration time according to "Expire:" header.
+ * 0 if not provided by the server.
*/
- char *url;
+ struct GNUNET_TIME_Timestamp expire;
/**
* Function to call with the exchange's certification data,
* NULL if this has already been done.
*/
- TALER_EXCHANGE_CertificationCallback cert_cb;
+ TALER_EXCHANGE_GetKeysCallback cert_cb;
/**
* Closure to pass to @e cert_cb.
*/
void *cert_cb_cls;
- /**
- * Data for the request to get the /keys of a exchange,
- * NULL once we are past stage #MHS_INIT.
- */
- struct KeysRequest *kr;
-
- /**
- * Task for retrying /keys request.
- */
- struct GNUNET_SCHEDULER_Task *retry_task;
-
- /**
- * Raw key data of the exchange, only valid if
- * @e handshake_complete is past stage #MHS_CERT.
- */
- json_t *key_data_raw;
+};
- /**
- * Head of DLL of auditors of this exchange.
- */
- struct TEAH_AuditorListEntry *auditors_head;
- /**
- * Tail of DLL of auditors of this exchange.
- */
- struct TEAH_AuditorListEntry *auditors_tail;
+/**
+ * Element in the `struct SignatureContext` array.
+ */
+struct SignatureElement
+{
/**
- * Key data of the exchange, only valid if
- * @e handshake_complete is past stage #MHS_CERT.
+ * Offset of the denomination in the group array,
+ * for sorting (2nd rank, ascending).
*/
- struct TALER_EXCHANGE_Keys key_data;
+ unsigned int offset;
/**
- * Retry /keys frequency.
+ * Offset of the group in the denominations array,
+ * for sorting (2nd rank, ascending).
*/
- struct GNUNET_TIME_Relative retry_delay;
+ unsigned int group_offset;
/**
- * When does @e key_data expire?
+ * Pointer to actual master signature to hash over.
*/
- struct GNUNET_TIME_Absolute key_data_expiration;
+ struct TALER_MasterSignatureP master_sig;
+};
+/**
+ * Context for collecting the array of master signatures
+ * needed to verify the exchange_sig online signature.
+ */
+struct SignatureContext
+{
/**
- * Stage of the exchange's initialization routines.
+ * Array of signatures to hash over.
*/
- enum ExchangeHandleState state;
+ struct SignatureElement *elements;
/**
- * If #GNUNET_YES, use fake now given by the user, in
- * request of "/keys".
+ * Write offset in the @e elements array.
*/
- int with_now;
+ unsigned int elements_pos;
/**
- * Fake now given by the user.
+ * Allocated space for @e elements.
*/
- struct GNUNET_TIME_Absolute now;
-
+ unsigned int elements_size;
};
-/* ***************** Internal /keys fetching ************* */
-
/**
- * Data for the request to get the /keys of a exchange.
+ * Determine order to sort two elements by before
+ * we hash the master signatures. Used for
+ * sorting with qsort().
+ *
+ * @param a pointer to a `struct SignatureElement`
+ * @param b pointer to a `struct SignatureElement`
+ * @return 0 if equal, -1 if a < b, 1 if a > b.
*/
-struct KeysRequest
+static int
+signature_context_sort_cb (const void *a,
+ const void *b)
{
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * The url for this handle
- */
- char *url;
-
- /**
- * Entry for this request with the `struct GNUNET_CURL_Context`.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Expiration time according to "Expire:" header.
- * 0 if not provided by the server.
- */
- struct GNUNET_TIME_Absolute expire;
+ const struct SignatureElement *sa = a;
+ const struct SignatureElement *sb = b;
-};
+ if (sa->group_offset < sb->group_offset)
+ return -1;
+ if (sa->group_offset > sb->group_offset)
+ return 1;
+ if (sa->offset < sb->offset)
+ return -1;
+ if (sa->offset > sb->offset)
+ return 1;
+ /* We should never have two disjoint elements
+ with same time and offset */
+ GNUNET_assert (sa == sb);
+ return 0;
+}
/**
- * Signature of functions called with the result from our call to the
- * auditor's /deposit-confirmation handler.
+ * Append a @a master_sig to the @a sig_ctx using the
+ * given attributes for (later) sorting.
*
- * @param cls closure of type `struct TEAH_AuditorInteractionEntry *`
- * @param http_status HTTP status code, 200 on success
- * @param ec taler protocol error status code, 0 on success
- * @param json raw json response
+ * @param[in,out] sig_ctx signature context to update
+ * @param group_offset offset for the group
+ * @param offset offset for the entry
+ * @param master_sig master signature for the entry
*/
-void
-TEAH_acc_confirmation_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const json_t *json)
+static void
+append_signature (struct SignatureContext *sig_ctx,
+ unsigned int group_offset,
+ unsigned int offset,
+ const struct TALER_MasterSignatureP *master_sig)
{
- struct TEAH_AuditorInteractionEntry *aie = cls;
- struct TEAH_AuditorListEntry *ale = aie->ale;
+ struct SignatureElement *element;
+ unsigned int new_size;
- (void) json;
- if (MHD_HTTP_OK != http_status)
+ if (sig_ctx->elements_pos == sig_ctx->elements_size)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to submit deposit confirmation to auditor `%s' with HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n",
- ale->auditor_url,
- http_status,
- (int) ec);
+ if (0 == sig_ctx->elements_size)
+ new_size = 1024;
+ else
+ new_size = sig_ctx->elements_size * 2;
+ GNUNET_array_grow (sig_ctx->elements,
+ sig_ctx->elements_size,
+ new_size);
}
- GNUNET_CONTAINER_DLL_remove (ale->ai_head,
- ale->ai_tail,
- aie);
- GNUNET_free (aie);
+ element = &sig_ctx->elements[sig_ctx->elements_pos++];
+ element->offset = offset;
+ element->group_offset = group_offset;
+ element->master_sig = *master_sig;
}
/**
- * Iterate over all available auditors for @a h, calling
- * @a ac and giving it a chance to start a deposit
- * confirmation interaction.
+ * Frees @a wfm array.
*
- * @param h exchange to go over auditors for
- * @param ac function to call per auditor
- * @param ac_cls closure for @a ac
+ * @param wfm fee array to release
+ * @param wfm_len length of the @a wfm array
*/
-void
-TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h,
- TEAH_AuditorCallback ac,
- void *ac_cls)
+static void
+free_fees (struct TALER_EXCHANGE_WireFeesByMethod *wfm,
+ unsigned int wfm_len)
{
- if (NULL == h->auditors_head)
+ for (unsigned int i = 0; i<wfm_len; i++)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No auditor available for exchange `%s'. Not submitting deposit confirmations.\n",
- h->url);
- return;
- }
- for (struct TEAH_AuditorListEntry *ale = h->auditors_head;
- NULL != ale;
- ale = ale->next)
- {
- struct TEAH_AuditorInteractionEntry *aie;
+ struct TALER_EXCHANGE_WireFeesByMethod *wfmi = &wfm[i];
- if (GNUNET_NO == ale->is_up)
- continue;
- aie = ac (ac_cls,
- ale->ah,
- &ale->auditor_pub);
- if (NULL != aie)
+ while (NULL != wfmi->fees_head)
{
- aie->ale = ale;
- GNUNET_CONTAINER_DLL_insert (ale->ai_head,
- ale->ai_tail,
- aie);
+ struct TALER_EXCHANGE_WireAggregateFees *fe
+ = wfmi->fees_head;
+
+ wfmi->fees_head = fe->next;
+ GNUNET_free (fe);
}
+ GNUNET_free (wfmi->method);
}
+ GNUNET_free (wfm);
}
/**
- * Release memory occupied by a keys request. Note that this does not
- * cancel the request itself.
+ * Parse wire @a fees and return array.
*
- * @param kr request to free
+ * @param master_pub master public key to use to check signatures
+ * @param currency currency amounts are expected in
+ * @param fees json AggregateTransferFee to parse
+ * @param[out] fees_len set to length of returned array
+ * @return NULL on error
*/
-static void
-free_keys_request (struct KeysRequest *kr)
+static struct TALER_EXCHANGE_WireFeesByMethod *
+parse_fees (const struct TALER_MasterPublicKeyP *master_pub,
+ const char *currency,
+ const json_t *fees,
+ unsigned int *fees_len)
+{
+ struct TALER_EXCHANGE_WireFeesByMethod *fbm;
+ size_t fbml = json_object_size (fees);
+ unsigned int i = 0;
+ const char *key;
+ const json_t *fee_array;
+
+ if (UINT_MAX < fbml)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ fbm = GNUNET_new_array (fbml,
+ struct TALER_EXCHANGE_WireFeesByMethod);
+ *fees_len = (unsigned int) fbml;
+ json_object_foreach ((json_t *) fees, key, fee_array) {
+ struct TALER_EXCHANGE_WireFeesByMethod *fe = &fbm[i++];
+ size_t idx;
+ json_t *fee;
+
+ fe->method = GNUNET_strdup (key);
+ fe->fees_head = NULL;
+ json_array_foreach (fee_array, idx, fee)
+ {
+ struct TALER_EXCHANGE_WireAggregateFees *wa
+ = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees);
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("sig",
+ &wa->master_sig),
+ TALER_JSON_spec_amount ("wire_fee",
+ currency,
+ &wa->fees.wire),
+ TALER_JSON_spec_amount ("closing_fee",
+ currency,
+ &wa->fees.closing),
+ GNUNET_JSON_spec_timestamp ("start_date",
+ &wa->start_date),
+ GNUNET_JSON_spec_timestamp ("end_date",
+ &wa->end_date),
+ GNUNET_JSON_spec_end ()
+ };
+
+ wa->next = fe->fees_head;
+ fe->fees_head = wa;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (fee,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ free_fees (fbm,
+ i);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_offline_wire_fee_verify (
+ key,
+ wa->start_date,
+ wa->end_date,
+ &wa->fees,
+ master_pub,
+ &wa->master_sig))
+ {
+ GNUNET_break_op (0);
+ free_fees (fbm,
+ i);
+ return NULL;
+ }
+ } /* for all fees over time */
+ } /* for all methods */
+ GNUNET_assert (i == fbml);
+ return fbm;
+}
+
+
+void
+TEAH_get_auditors_for_dc (
+ struct TALER_EXCHANGE_Keys *keys,
+ TEAH_AuditorCallback ac,
+ void *ac_cls)
{
- GNUNET_free (kr->url);
- GNUNET_free (kr);
+ if (0 == keys->num_auditors)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No auditor available. Not submitting deposit confirmations.\n");
+ return;
+ }
+ for (unsigned int i = 0; i<keys->num_auditors; i++)
+ {
+ const struct TALER_EXCHANGE_AuditorInformation *auditor
+ = &keys->auditors[i];
+
+ ac (ac_cls,
+ auditor->auditor_url,
+ &auditor->auditor_pub);
+ }
}
@@ -362,29 +389,28 @@ free_keys_request (struct KeysRequest *kr)
*
* @param[out] sign_key where to return the result
* @param check_sigs should we check signatures?
- * @param[in] sign_key_obj json to parse
+ * @param sign_key_obj json to parse
* @param master_key master key to use to verify signature
* @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
- * invalid or the json malformed.
+ * invalid or the @a sign_key_obj is malformed.
*/
-static int
+static enum GNUNET_GenericReturnValue
parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key,
- int check_sigs,
- json_t *sign_key_obj,
+ bool check_sigs,
+ const json_t *sign_key_obj,
const struct TALER_MasterPublicKeyP *master_key)
{
- struct TALER_MasterSignatureP sign_key_issue_sig;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("master_sig",
- &sign_key_issue_sig),
+ &sign_key->master_sig),
GNUNET_JSON_spec_fixed_auto ("key",
&sign_key->key),
- GNUNET_JSON_spec_absolute_time ("stamp_start",
- &sign_key->valid_from),
- GNUNET_JSON_spec_absolute_time ("stamp_expire",
- &sign_key->valid_until),
- GNUNET_JSON_spec_absolute_time ("stamp_end",
- &sign_key->valid_legal),
+ GNUNET_JSON_spec_timestamp ("stamp_start",
+ &sign_key->valid_from),
+ GNUNET_JSON_spec_timestamp ("stamp_expire",
+ &sign_key->valid_until),
+ GNUNET_JSON_spec_timestamp ("stamp_end",
+ &sign_key->valid_legal),
GNUNET_JSON_spec_end ()
};
@@ -396,76 +422,73 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
-
if (! check_sigs)
return GNUNET_OK;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_signkey_validity_verify (
+ &sign_key->key,
+ sign_key->valid_from,
+ sign_key->valid_until,
+ sign_key->valid_legal,
+ master_key,
+ &sign_key->master_sig))
{
- struct TALER_ExchangeSigningKeyValidityPS sign_key_issue = {
- .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY),
- .purpose.size = htonl (sizeof (sign_key_issue)),
- .signkey_pub = sign_key->key,
- .master_public_key = *master_key,
- .start = GNUNET_TIME_absolute_hton (sign_key->valid_from),
- .expire = GNUNET_TIME_absolute_hton (sign_key->valid_until),
- .end = GNUNET_TIME_absolute_hton (sign_key->valid_legal)
- };
-
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY,
- &sign_key_issue.purpose,
- &sign_key_issue_sig.eddsa_signature,
- &master_key->eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
}
- sign_key->master_sig = sign_key_issue_sig;
return GNUNET_OK;
}
/**
- * Parse a exchange's denomination key encoded in JSON.
+ * Parse a exchange's denomination key encoded in JSON partially.
+ *
+ * Only the values for master_sig, timestamps and the cipher-specific public
+ * key are parsed. All other fields (fees, age_mask, value) MUST have been set
+ * prior to calling this function, otherwise the signature verification
+ * performed within this function will fail.
*
* @param[out] denom_key where to return the result
+ * @param cipher cipher type to parse
* @param check_sigs should we check signatures?
- * @param[in] denom_key_obj json to parse
+ * @param denom_key_obj json to parse
* @param master_key master key to use to verify signature
- * @param hash_context where to accumulate data for signature verification
+ * @param group_offset offset for the group
+ * @param index index of this denomination key in the group
+ * @param sig_ctx where to write details about encountered
+ * master signatures, NULL if not used
* @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
* invalid or the json malformed.
*/
-static int
-parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key,
- int check_sigs,
- json_t *denom_key_obj,
- struct TALER_MasterPublicKeyP *master_key,
- struct GNUNET_HashContext *hash_context)
+static enum GNUNET_GenericReturnValue
+parse_json_denomkey_partially (
+ struct TALER_EXCHANGE_DenomPublicKey *denom_key,
+ enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher,
+ bool check_sigs,
+ const json_t *denom_key_obj,
+ struct TALER_MasterPublicKeyP *master_key,
+ unsigned int group_offset,
+ unsigned int index,
+ struct SignatureContext *sig_ctx)
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("master_sig",
&denom_key->master_sig),
- GNUNET_JSON_spec_absolute_time ("stamp_expire_deposit",
- &denom_key->expire_deposit),
- GNUNET_JSON_spec_absolute_time ("stamp_expire_withdraw",
- &denom_key->withdraw_valid_until),
- GNUNET_JSON_spec_absolute_time ("stamp_start",
- &denom_key->valid_from),
- GNUNET_JSON_spec_absolute_time ("stamp_expire_legal",
- &denom_key->expire_legal),
- TALER_JSON_spec_amount ("value",
- &denom_key->value),
- TALER_JSON_spec_amount ("fee_withdraw",
- &denom_key->fee_withdraw),
- TALER_JSON_spec_amount ("fee_deposit",
- &denom_key->fee_deposit),
- TALER_JSON_spec_amount ("fee_refresh",
- &denom_key->fee_refresh),
- TALER_JSON_spec_amount ("fee_refund",
- &denom_key->fee_refund),
- GNUNET_JSON_spec_rsa_public_key ("denom_pub",
- &denom_key->key.rsa_public_key),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
+ &denom_key->expire_deposit),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
+ &denom_key->withdraw_valid_until),
+ GNUNET_JSON_spec_timestamp ("stamp_start",
+ &denom_key->valid_from),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
+ &denom_key->expire_legal),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("lost",
+ &denom_key->lost),
+ NULL),
+ TALER_JSON_spec_denom_pub_cipher (NULL,
+ cipher,
+ &denom_key->key),
GNUNET_JSON_spec_end ()
};
@@ -477,54 +500,33 @@ parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
-
- GNUNET_CRYPTO_rsa_public_key_hash (denom_key->key.rsa_public_key,
- &denom_key->h_key);
- if (NULL != hash_context)
- GNUNET_CRYPTO_hash_context_read (hash_context,
- &denom_key->h_key,
- sizeof (struct GNUNET_HashCode));
+ TALER_denom_pub_hash (&denom_key->key,
+ &denom_key->h_key);
+ if (NULL != sig_ctx)
+ append_signature (sig_ctx,
+ group_offset,
+ index,
+ &denom_key->master_sig);
if (! check_sigs)
return GNUNET_OK;
- {
- struct TALER_DenominationKeyValidityPS denom_key_issue = {
- .purpose.purpose
- = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY),
- .purpose.size = htonl (sizeof (denom_key_issue)),
- .master = *master_key,
- .denom_hash = denom_key->h_key,
- .start = GNUNET_TIME_absolute_hton (denom_key->valid_from),
- .expire_withdraw
- = GNUNET_TIME_absolute_hton (denom_key->withdraw_valid_until),
- .expire_deposit = GNUNET_TIME_absolute_hton (denom_key->expire_deposit),
- .expire_legal = GNUNET_TIME_absolute_hton (denom_key->expire_legal)
- };
-
- TALER_amount_hton (&denom_key_issue.value,
- &denom_key->value);
- TALER_amount_hton (&denom_key_issue.fee_withdraw,
- &denom_key->fee_withdraw);
- TALER_amount_hton (&denom_key_issue.fee_deposit,
- &denom_key->fee_deposit);
- TALER_amount_hton (&denom_key_issue.fee_refresh,
- &denom_key->fee_refresh);
- TALER_amount_hton (&denom_key_issue.fee_refund,
- &denom_key->fee_refund);
- EXITIF (GNUNET_SYSERR ==
- GNUNET_CRYPTO_eddsa_verify (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY,
- &denom_key_issue.purpose,
- &denom_key->master_sig.eddsa_signature,
- &master_key->eddsa_pub));
- }
+ EXITIF (GNUNET_SYSERR ==
+ TALER_exchange_offline_denom_validity_verify (
+ &denom_key->h_key,
+ denom_key->valid_from,
+ denom_key->withdraw_valid_until,
+ denom_key->expire_deposit,
+ denom_key->expire_legal,
+ &denom_key->value,
+ &denom_key->fees,
+ master_key,
+ &denom_key->master_sig));
return GNUNET_OK;
-
EXITIF_exit:
+ GNUNET_JSON_parse_free (spec);
/* invalidate denom_key, just to be sure */
memset (denom_key,
0,
sizeof (*denom_key));
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
@@ -534,31 +536,29 @@ EXITIF_exit:
*
* @param[out] auditor where to return the result
* @param check_sigs should we check signatures
- * @param[in] auditor_obj json to parse
+ * @param auditor_obj json to parse
* @param key_data information about denomination keys
* @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
* invalid or the json malformed.
*/
-static int
+static enum GNUNET_GenericReturnValue
parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
- int check_sigs,
- json_t *auditor_obj,
+ bool check_sigs,
+ const json_t *auditor_obj,
const struct TALER_EXCHANGE_Keys *key_data)
{
- json_t *keys;
+ const json_t *keys;
json_t *key;
- unsigned int len;
- unsigned int off;
- unsigned int i;
+ size_t off;
+ size_t pos;
const char *auditor_url;
- struct TALER_ExchangeKeyValidityPS kv;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("auditor_pub",
&auditor->auditor_pub),
- GNUNET_JSON_spec_string ("auditor_url",
+ TALER_JSON_spec_web_url ("auditor_url",
&auditor_url),
- GNUNET_JSON_spec_json ("denomination_keys",
- &keys),
+ GNUNET_JSON_spec_array_const ("denomination_keys",
+ &keys),
GNUNET_JSON_spec_end ()
};
@@ -568,25 +568,23 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
NULL, NULL))
{
GNUNET_break_op (0);
+#if DEBUG
+ json_dumpf (auditor_obj,
+ stderr,
+ JSON_INDENT (2));
+#endif
return GNUNET_SYSERR;
}
auditor->auditor_url = GNUNET_strdup (auditor_url);
- kv.purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS);
- kv.purpose.size = htonl (sizeof (struct TALER_ExchangeKeyValidityPS));
- GNUNET_CRYPTO_hash (auditor_url,
- strlen (auditor_url) + 1,
- &kv.auditor_url_hash);
- kv.master = key_data->master_pub;
- len = json_array_size (keys);
- auditor->denom_keys = GNUNET_new_array (len,
- struct
- TALER_EXCHANGE_AuditorDenominationInfo);
- off = 0;
- json_array_foreach (keys, i, key) {
+ auditor->denom_keys
+ = GNUNET_new_array (json_array_size (keys),
+ struct TALER_EXCHANGE_AuditorDenominationInfo);
+ pos = 0;
+ json_array_foreach (keys, off, key) {
struct TALER_AuditorSignatureP auditor_sig;
- struct GNUNET_HashCode denom_h;
- const struct TALER_EXCHANGE_DenomPublicKey *dk;
- unsigned int dk_off;
+ struct TALER_DenominationHashP denom_h;
+ const struct TALER_EXCHANGE_DenomPublicKey *dk = NULL;
+ unsigned int dk_off = UINT_MAX;
struct GNUNET_JSON_Specification kspec[] = {
GNUNET_JSON_spec_fixed_auto ("auditor_sig",
&auditor_sig),
@@ -603,8 +601,6 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
GNUNET_break_op (0);
continue;
}
- dk = NULL;
- dk_off = UINT_MAX;
for (unsigned int j = 0; j<key_data->num_denom_keys; j++)
{
if (0 == GNUNET_memcmp (&denom_h,
@@ -617,143 +613,112 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
}
if (NULL == dk)
{
- GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Auditor signed denomination %s, which we do not know. Ignoring signature.\n",
+ GNUNET_h2s (&denom_h.hash));
continue;
}
if (check_sigs)
{
- kv.start = GNUNET_TIME_absolute_hton (dk->valid_from);
- kv.expire_withdraw = GNUNET_TIME_absolute_hton (dk->withdraw_valid_until);
- kv.expire_deposit = GNUNET_TIME_absolute_hton (dk->expire_deposit);
- kv.expire_legal = GNUNET_TIME_absolute_hton (dk->expire_legal);
- TALER_amount_hton (&kv.value,
- &dk->value);
- TALER_amount_hton (&kv.fee_withdraw,
- &dk->fee_withdraw);
- TALER_amount_hton (&kv.fee_deposit,
- &dk->fee_deposit);
- TALER_amount_hton (&kv.fee_refresh,
- &dk->fee_refresh);
- TALER_amount_hton (&kv.fee_refund,
- &dk->fee_refund);
- kv.denom_hash = dk->h_key;
-
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS,
- &kv.purpose,
- &auditor_sig.eddsa_sig,
- &auditor->auditor_pub.eddsa_pub))
+ TALER_auditor_denom_validity_verify (
+ auditor_url,
+ &dk->h_key,
+ &key_data->master_pub,
+ dk->valid_from,
+ dk->withdraw_valid_until,
+ dk->expire_deposit,
+ dk->expire_legal,
+ &dk->value,
+ &dk->fees,
+ &auditor->auditor_pub,
+ &auditor_sig))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
}
- auditor->denom_keys[off].denom_key_offset = dk_off;
- auditor->denom_keys[off].auditor_sig = auditor_sig;
- off++;
+ auditor->denom_keys[pos].denom_key_offset = dk_off;
+ auditor->denom_keys[pos].auditor_sig = auditor_sig;
+ pos++;
}
- auditor->num_denom_keys = off;
- GNUNET_JSON_parse_free (spec);
+ if (pos > UINT_MAX)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ auditor->num_denom_keys = (unsigned int) pos;
return GNUNET_OK;
}
/**
- * Function called with information about the auditor. Marks an
- * auditor as 'up'.
+ * Parse a exchange's global fee information encoded in JSON.
*
- * @param cls closure, a `struct TEAH_AuditorListEntry *`
- * @param vi basic information about the auditor
- * @param compat protocol compatibility information
+ * @param[out] gf where to return the result
+ * @param check_sigs should we check signatures
+ * @param fee_obj json to parse
+ * @param key_data already parsed information about the exchange
+ * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
+ * invalid or the json malformed.
*/
-static void
-auditor_version_cb (
- void *cls,
- const struct TALER_AUDITOR_VersionInformation *vi,
- enum TALER_AUDITOR_VersionCompatibility compat)
+static enum GNUNET_GenericReturnValue
+parse_global_fee (struct TALER_EXCHANGE_GlobalFee *gf,
+ bool check_sigs,
+ const json_t *fee_obj,
+ const struct TALER_EXCHANGE_Keys *key_data)
{
- struct TEAH_AuditorListEntry *ale = cls;
-
- if (NULL == vi)
- {
- /* In this case, we don't mark the auditor as 'up' */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Auditor `%s' gave unexpected version response.\n",
- ale->auditor_url);
- return;
- }
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("start_date",
+ &gf->start_date),
+ GNUNET_JSON_spec_timestamp ("end_date",
+ &gf->end_date),
+ GNUNET_JSON_spec_relative_time ("purse_timeout",
+ &gf->purse_timeout),
+ GNUNET_JSON_spec_relative_time ("history_expiration",
+ &gf->history_expiration),
+ GNUNET_JSON_spec_uint32 ("purse_account_limit",
+ &gf->purse_account_limit),
+ TALER_JSON_SPEC_GLOBAL_FEES (key_data->currency,
+ &gf->fees),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &gf->master_sig),
+ GNUNET_JSON_spec_end ()
+ };
- if (0 != (TALER_AUDITOR_VC_INCOMPATIBLE & compat))
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (fee_obj,
+ spec,
+ NULL, NULL))
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Auditor `%s' runs incompatible protocol version!\n",
- ale->auditor_url);
- if (0 != (TALER_AUDITOR_VC_OLDER & compat))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Auditor `%s' runs outdated protocol version!\n",
- ale->auditor_url);
- }
- if (0 != (TALER_AUDITOR_VC_NEWER & compat))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Auditor `%s' runs more recent incompatible version. We should upgrade!\n",
- ale->auditor_url);
- }
- return;
+ GNUNET_break_op (0);
+#if DEBUG
+ json_dumpf (fee_obj,
+ stderr,
+ JSON_INDENT (2));
+#endif
+ return GNUNET_SYSERR;
}
- ale->is_up = GNUNET_YES;
-}
-
-
-/**
- * Recalculate our auditor list, we got /keys and it may have
- * changed.
- *
- * @param exchange exchange for which to update the list.
- */
-static void
-update_auditors (struct TALER_EXCHANGE_Handle *exchange)
-{
- struct TALER_EXCHANGE_Keys *kd = &exchange->key_data;
-
- TALER_LOG_DEBUG ("Updating auditors\n");
- for (unsigned int i = 0; i<kd->num_auditors; i++)
+ if (check_sigs)
{
- /* Compare auditor data from /keys with auditor data
- * from owned exchange structures. */
- struct TALER_EXCHANGE_AuditorInformation *auditor = &kd->auditors[i];
- struct TEAH_AuditorListEntry *ale = NULL;
-
- for (struct TEAH_AuditorListEntry *a = exchange->auditors_head;
- NULL != a;
- a = a->next)
+ if (GNUNET_OK !=
+ TALER_exchange_offline_global_fee_verify (
+ gf->start_date,
+ gf->end_date,
+ &gf->fees,
+ gf->purse_timeout,
+ gf->history_expiration,
+ gf->purse_account_limit,
+ &key_data->master_pub,
+ &gf->master_sig))
{
- if (0 == GNUNET_memcmp (&auditor->auditor_pub,
- &a->auditor_pub))
- {
- ale = a;
- break;
- }
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
}
- if (NULL != ale)
- continue; /* found, no need to add */
-
- /* new auditor, add */
- TALER_LOG_DEBUG ("Found new auditor!\n");
- ale = GNUNET_new (struct TEAH_AuditorListEntry);
- ale->auditor_pub = auditor->auditor_pub;
- ale->auditor_url = GNUNET_strdup (auditor->auditor_url);
- GNUNET_CONTAINER_DLL_insert (exchange->auditors_head,
- exchange->auditors_tail,
- ale);
-
- ale->ah = TALER_AUDITOR_connect (exchange->ctx,
- ale->auditor_url,
- &auditor_version_cb,
- ale);
}
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
}
@@ -763,39 +728,31 @@ update_auditors (struct TALER_EXCHANGE_Handle *exchange)
* @param denom1 first denomination key
* @param denom2 second denomination key
* @return 0 if the two keys are equal (not necessarily
- * the same object), 1 otherwise.
+ * the same object), non-zero otherwise.
*/
static unsigned int
-denoms_cmp (struct TALER_EXCHANGE_DenomPublicKey *denom1,
- struct TALER_EXCHANGE_DenomPublicKey *denom2)
+denoms_cmp (const struct TALER_EXCHANGE_DenomPublicKey *denom1,
+ const struct TALER_EXCHANGE_DenomPublicKey *denom2)
{
- struct GNUNET_CRYPTO_RsaPublicKey *tmp1;
- struct GNUNET_CRYPTO_RsaPublicKey *tmp2;
- int r1;
- int r2;
- int ret;
-
- /* First check if pub is the same. */
- if (0 != GNUNET_CRYPTO_rsa_public_key_cmp
- (denom1->key.rsa_public_key,
- denom2->key.rsa_public_key))
- return 1;
+ struct TALER_EXCHANGE_DenomPublicKey tmp1;
+ struct TALER_EXCHANGE_DenomPublicKey tmp2;
- tmp1 = denom1->key.rsa_public_key;
- tmp2 = denom2->key.rsa_public_key;
- r1 = denom1->revoked;
- r2 = denom2->revoked;
-
- denom1->key.rsa_public_key = NULL;
- denom2->key.rsa_public_key = NULL;
- /* Then proceed with the rest of the object. */
- ret = GNUNET_memcmp (denom1,
- denom2);
- denom1->revoked = r1;
- denom2->revoked = r2;
- denom1->key.rsa_public_key = tmp1;
- denom2->key.rsa_public_key = tmp2;
- return ret;
+ if (0 !=
+ TALER_denom_pub_cmp (&denom1->key,
+ &denom2->key))
+ return 1;
+ tmp1 = *denom1;
+ tmp2 = *denom2;
+ tmp1.revoked = false;
+ tmp2.revoked = false;
+ memset (&tmp1.key,
+ 0,
+ sizeof (tmp1.key));
+ memset (&tmp2.key,
+ 0,
+ sizeof (tmp2.key));
+ return GNUNET_memcmp (&tmp1,
+ &tmp2);
}
@@ -804,51 +761,50 @@ denoms_cmp (struct TALER_EXCHANGE_DenomPublicKey *denom1,
* and store the data in the @a key_data.
*
* @param[in] resp_obj JSON object to parse
- * @param check_sig #GNUNET_YES if we should check the signature
+ * @param check_sig true if we should check the signature
* @param[out] key_data where to store the results we decoded
* @param[out] vc where to store version compatibility data
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
* (malformed JSON)
*/
-static int
+static enum GNUNET_GenericReturnValue
decode_keys_json (const json_t *resp_obj,
- int check_sig,
+ bool check_sig,
struct TALER_EXCHANGE_Keys *key_data,
enum TALER_EXCHANGE_VersionCompatibility *vc)
{
- struct TALER_ExchangeSignatureP sig;
- struct GNUNET_HashContext *hash_context;
- struct TALER_ExchangePublicKeyP pub;
- unsigned int age;
- unsigned int revision;
- unsigned int current;
- struct GNUNET_JSON_Specification mspec[] = {
- GNUNET_JSON_spec_fixed_auto ("eddsa_sig",
- &sig),
- GNUNET_JSON_spec_fixed_auto ("eddsa_pub",
- &pub),
- /* sig and pub must be first, as we skip those if
- check_sig is false! */
- GNUNET_JSON_spec_fixed_auto ("master_public_key",
- &key_data->master_pub),
- GNUNET_JSON_spec_absolute_time ("list_issue_date",
- &key_data->list_issue_date),
- GNUNET_JSON_spec_relative_time ("reserve_closing_delay",
- &key_data->reserve_closing_delay),
- GNUNET_JSON_spec_end ()
- };
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ const json_t *wblwk = NULL;
+ const json_t *global_fees;
+ const json_t *sign_keys_array;
+ const json_t *denominations_by_group;
+ const json_t *auditors_array;
+ const json_t *recoup_array = NULL;
+ const json_t *manifests = NULL;
+ bool no_extensions = false;
+ bool no_signature = false;
+ const json_t *accounts;
+ const json_t *fees;
+ const json_t *wads;
+ struct SignatureContext sig_ctx = { 0 };
if (JSON_OBJECT != json_typeof (resp_obj))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- /* check the version */
+#if DEBUG
+ json_dumpf (resp_obj,
+ stderr,
+ JSON_INDENT (2));
+#endif
+ /* check the version first */
{
- const char *ver;
+ struct TALER_JSON_ProtocolVersion pv;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("version",
- &ver),
+ TALER_JSON_spec_version ("version",
+ &pv),
GNUNET_JSON_spec_end ()
};
@@ -860,53 +816,180 @@ decode_keys_json (const json_t *resp_obj,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (3 != sscanf (ver,
- "%u:%u:%u",
- &current,
- &revision,
- &age))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
*vc = TALER_EXCHANGE_VC_MATCH;
- if (TALER_PROTOCOL_CURRENT < current)
+ if (EXCHANGE_PROTOCOL_CURRENT < pv.current)
{
*vc |= TALER_EXCHANGE_VC_NEWER;
- if (TALER_PROTOCOL_CURRENT < current - age)
+ if (EXCHANGE_PROTOCOL_CURRENT < pv.current - pv.age)
*vc |= TALER_EXCHANGE_VC_INCOMPATIBLE;
}
- if (TALER_PROTOCOL_CURRENT > current)
+ if (EXCHANGE_PROTOCOL_CURRENT > pv.current)
{
*vc |= TALER_EXCHANGE_VC_OLDER;
- if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current)
+ if (EXCHANGE_PROTOCOL_CURRENT - EXCHANGE_PROTOCOL_AGE > pv.current)
*vc |= TALER_EXCHANGE_VC_INCOMPATIBLE;
}
- key_data->version = GNUNET_strdup (ver);
}
- hash_context = NULL;
- EXITIF (GNUNET_OK !=
+ {
+ const char *ver;
+ const char *currency;
+ const char *asset_type;
+ struct GNUNET_JSON_Specification mspec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto (
+ "exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_spec_fixed_auto (
+ "master_public_key",
+ &key_data->master_pub),
+ GNUNET_JSON_spec_array_const ("accounts",
+ &accounts),
+ GNUNET_JSON_spec_object_const ("wire_fees",
+ &fees),
+ GNUNET_JSON_spec_array_const ("wads",
+ &wads),
+ GNUNET_JSON_spec_timestamp (
+ "list_issue_date",
+ &key_data->list_issue_date),
+ GNUNET_JSON_spec_relative_time (
+ "reserve_closing_delay",
+ &key_data->reserve_closing_delay),
+ GNUNET_JSON_spec_string (
+ "currency",
+ &currency),
+ GNUNET_JSON_spec_string (
+ "asset_type",
+ &asset_type),
+ GNUNET_JSON_spec_array_const (
+ "global_fees",
+ &global_fees),
+ GNUNET_JSON_spec_array_const (
+ "signkeys",
+ &sign_keys_array),
+ GNUNET_JSON_spec_array_const (
+ "denominations",
+ &denominations_by_group),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const (
+ "recoup",
+ &recoup_array),
+ NULL),
+ GNUNET_JSON_spec_array_const (
+ "auditors",
+ &auditors_array),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool (
+ "rewards_allowed",
+ &key_data->rewards_allowed),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("extensions",
+ &manifests),
+ &no_extensions),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto (
+ "extensions_sig",
+ &key_data->extensions_sig),
+ &no_signature),
+ GNUNET_JSON_spec_string ("version",
+ &ver),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const (
+ "wallet_balance_limit_without_kyc",
+ &wblwk),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *emsg;
+ unsigned int eline;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (resp_obj,
+ (check_sig) ? mspec : &mspec[2],
+ &emsg,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Parsing /keys failed for `%s' (%u)\n",
+ emsg,
+ eline);
+ EXITIF (1);
+ }
+ {
+ struct GNUNET_JSON_Specification sspec[] = {
+ TALER_JSON_spec_currency_specification (
+ "currency_specification",
+ currency,
+ &key_data->cspec),
+ TALER_JSON_spec_amount (
+ "stefan_abs",
+ currency,
+ &key_data->stefan_abs),
+ TALER_JSON_spec_amount (
+ "stefan_log",
+ currency,
+ &key_data->stefan_log),
+ GNUNET_JSON_spec_double (
+ "stefan_lin",
+ &key_data->stefan_lin),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
GNUNET_JSON_parse (resp_obj,
- (check_sig) ? mspec : &mspec[2],
- NULL, NULL));
+ sspec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Parsing /keys failed for `%s' (%u)\n",
+ emsg,
+ eline);
+ EXITIF (1);
+ }
+ }
- /* parse the master public key and issue date of the response */
- if (check_sig)
- hash_context = GNUNET_CRYPTO_hash_context_start ();
+ key_data->currency = GNUNET_strdup (currency);
+ key_data->version = GNUNET_strdup (ver);
+ key_data->asset_type = GNUNET_strdup (asset_type);
+ if (! no_extensions)
+ key_data->extensions = json_incref ((json_t *) manifests);
+ }
+
+ /* parse the global fees */
+ EXITIF (json_array_size (global_fees) > UINT_MAX);
+ key_data->num_global_fees
+ = (unsigned int) json_array_size (global_fees);
+ if (0 != key_data->num_global_fees)
+ {
+ json_t *global_fee;
+ size_t index;
+
+ key_data->global_fees
+ = GNUNET_new_array (key_data->num_global_fees,
+ struct TALER_EXCHANGE_GlobalFee);
+ json_array_foreach (global_fees, index, global_fee)
+ {
+ EXITIF (GNUNET_SYSERR ==
+ parse_global_fee (&key_data->global_fees[index],
+ check_sig,
+ global_fee,
+ key_data));
+ }
+ }
/* parse the signing keys */
+ EXITIF (json_array_size (sign_keys_array) > UINT_MAX);
+ key_data->num_sign_keys
+ = (unsigned int) json_array_size (sign_keys_array);
+ if (0 != key_data->num_sign_keys)
{
- json_t *sign_keys_array;
json_t *sign_key_obj;
- unsigned int index;
+ size_t index;
- EXITIF (NULL == (sign_keys_array =
- json_object_get (resp_obj,
- "signkeys")));
- EXITIF (JSON_ARRAY != json_typeof (sign_keys_array));
- EXITIF (0 == (key_data->num_sign_keys =
- json_array_size (sign_keys_array)));
key_data->sign_keys
= GNUNET_new_array (key_data->num_sign_keys,
struct TALER_EXCHANGE_SigningPublicKey);
@@ -919,80 +1002,194 @@ decode_keys_json (const json_t *resp_obj,
}
}
- /* parse the denomination keys, merging with the
- possibly EXISTING array as required (/keys cherry picking) */
+ /* Parse balance limits */
+ if (NULL != wblwk)
{
- json_t *denom_keys_array;
- json_t *denom_key_obj;
- unsigned int index;
+ EXITIF (json_array_size (wblwk) > UINT_MAX);
+ key_data->wblwk_length
+ = (unsigned int) json_array_size (wblwk);
+ key_data->wallet_balance_limit_without_kyc
+ = GNUNET_new_array (key_data->wblwk_length,
+ struct TALER_Amount);
+ for (unsigned int i = 0; i<key_data->wblwk_length; i++)
+ {
+ struct TALER_Amount *a = &key_data->wallet_balance_limit_without_kyc[i];
+ const json_t *aj = json_array_get (wblwk,
+ i);
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount (NULL,
+ key_data->currency,
+ a),
+ GNUNET_JSON_spec_end ()
+ };
- EXITIF (NULL == (denom_keys_array =
- json_object_get (resp_obj,
- "denoms")));
- EXITIF (JSON_ARRAY != json_typeof (denom_keys_array));
+ EXITIF (GNUNET_OK !=
+ GNUNET_JSON_parse (aj,
+ spec,
+ NULL, NULL));
+ }
+ }
- json_array_foreach (denom_keys_array, index, denom_key_obj) {
- struct TALER_EXCHANGE_DenomPublicKey dk;
- int found = GNUNET_NO;
+ /* Parse wire accounts */
+ key_data->fees = parse_fees (&key_data->master_pub,
+ key_data->currency,
+ fees,
+ &key_data->fees_len);
+ EXITIF (NULL == key_data->fees);
+ /* parse accounts */
+ EXITIF (json_array_size (accounts) > UINT_MAX);
+ GNUNET_array_grow (key_data->accounts,
+ key_data->accounts_len,
+ json_array_size (accounts));
+ EXITIF (GNUNET_OK !=
+ TALER_EXCHANGE_parse_accounts (&key_data->master_pub,
+ accounts,
+ key_data->accounts_len,
+ key_data->accounts));
- memset (&dk,
- 0,
- sizeof (dk));
- EXITIF (GNUNET_SYSERR ==
- parse_json_denomkey (&dk,
- check_sig,
- denom_key_obj,
- &key_data->master_pub,
- hash_context));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Parsed %u wire accounts from JSON\n",
+ key_data->accounts_len);
- for (unsigned int j = 0;
- j<key_data->num_denom_keys;
- j++)
+
+ /* Parse the supported extension(s): age-restriction. */
+ /* TODO: maybe lift all this into a FP in TALER_Extension ? */
+ if (! no_extensions)
+ {
+ if (no_signature)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "found extensions without signature\n");
+ }
+ else
+ {
+ /* We have an extensions object. Verify its signature. */
+ EXITIF (GNUNET_OK !=
+ TALER_extensions_verify_manifests_signature (
+ manifests,
+ &key_data->extensions_sig,
+ &key_data->master_pub));
+
+ /* Parse and set the the configuration of the extensions accordingly */
+ EXITIF (GNUNET_OK !=
+ TALER_extensions_load_manifests (manifests));
+ }
+
+ /* Assuming we might have now a new value for age_mask, set it in key_data */
+ key_data->age_mask = TALER_extensions_get_age_restriction_mask ();
+ }
+
+ /*
+ * Parse the denomination keys, merging with the
+ * possibly EXISTING array as required (/keys cherry picking).
+ *
+ * The denominations are grouped by common values of
+ * {cipher, value, fee, age_mask}.
+ */
+ {
+ json_t *group_obj;
+ unsigned int group_idx;
+
+ json_array_foreach (denominations_by_group,
+ group_idx,
+ group_obj)
+ {
+ /* First, parse { cipher, fees, value, age_mask, hash } of the current
+ group. */
+ struct TALER_DenominationGroup group = {0};
+ const json_t *denom_keys_array;
+ struct GNUNET_JSON_Specification group_spec[] = {
+ TALER_JSON_spec_denomination_group (NULL,
+ key_data->currency,
+ &group),
+ GNUNET_JSON_spec_array_const ("denoms",
+ &denom_keys_array),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *denom_key_obj;
+ unsigned int index;
+
+ EXITIF (GNUNET_SYSERR ==
+ GNUNET_JSON_parse (group_obj,
+ group_spec,
+ NULL,
+ NULL));
+
+ /* Now, parse the individual denominations */
+ json_array_foreach (denom_keys_array,
+ index,
+ denom_key_obj)
{
- if (0 == denoms_cmp (&dk,
- &key_data->denom_keys[j]))
+ /* Set the common fields from the group for this particular
+ denomination. Required to make the validity check inside
+ parse_json_denomkey_partially pass */
+ struct TALER_EXCHANGE_DenomPublicKey dk = {
+ .value = group.value,
+ .fees = group.fees,
+ .key.age_mask = group.age_mask
+ };
+ bool found = false;
+
+ EXITIF (GNUNET_SYSERR ==
+ parse_json_denomkey_partially (&dk,
+ group.cipher,
+ check_sig,
+ denom_key_obj,
+ &key_data->master_pub,
+ group_idx,
+ index,
+ check_sig
+ ? &sig_ctx
+ : NULL));
+ for (unsigned int j = 0;
+ j<key_data->num_denom_keys;
+ j++)
{
- found = GNUNET_YES;
- break;
+ if (0 == denoms_cmp (&dk,
+ &key_data->denom_keys[j]))
+ {
+ found = true;
+ break;
+ }
}
- }
- if (GNUNET_YES == found)
- {
- /* 0:0:0 did not support /keys cherry picking */
- TALER_LOG_DEBUG ("Skipping denomination key: already know it\n");
- GNUNET_CRYPTO_rsa_public_key_free (dk.key.rsa_public_key);
- continue;
- }
- if (key_data->denom_keys_size == key_data->num_denom_keys)
- GNUNET_array_grow (key_data->denom_keys,
- key_data->denom_keys_size,
- key_data->denom_keys_size * 2 + 2);
- key_data->denom_keys[key_data->num_denom_keys++] = dk;
-
- /* Update "last_denom_issue_date" */
- TALER_LOG_DEBUG ("Adding denomination key that is valid_from %s\n",
- GNUNET_STRINGS_absolute_time_to_string (dk.valid_from));
- key_data->last_denom_issue_date
- = GNUNET_TIME_absolute_max (key_data->last_denom_issue_date,
- dk.valid_from);
- };
- }
+
+ if (found)
+ {
+ /* 0:0:0 did not support /keys cherry picking */
+ TALER_LOG_DEBUG ("Skipping denomination key: already know it\n");
+ TALER_denom_pub_free (&dk.key);
+ continue;
+ }
+
+ if (key_data->denom_keys_size == key_data->num_denom_keys)
+ GNUNET_array_grow (key_data->denom_keys,
+ key_data->denom_keys_size,
+ key_data->denom_keys_size * 2 + 2);
+ GNUNET_assert (key_data->denom_keys_size >
+ key_data->num_denom_keys);
+ GNUNET_assert (key_data->num_denom_keys < UINT_MAX);
+ key_data->denom_keys[key_data->num_denom_keys++] = dk;
+
+ /* Update "last_denom_issue_date" */
+ TALER_LOG_DEBUG ("Adding denomination key that is valid_until %s\n",
+ GNUNET_TIME_timestamp2s (dk.valid_from));
+ key_data->last_denom_issue_date
+ = GNUNET_TIME_timestamp_max (key_data->last_denom_issue_date,
+ dk.valid_from);
+ }; /* end of json_array_foreach over denominations */
+ } /* end of json_array_foreach over groups of denominations */
+ } /* end of scope for group_ojb/group_idx */
/* parse the auditor information */
{
- json_t *auditors_array;
json_t *auditor_info;
unsigned int index;
- EXITIF (NULL == (auditors_array =
- json_object_get (resp_obj,
- "auditors")));
- EXITIF (JSON_ARRAY != json_typeof (auditors_array));
-
/* Merge with the existing auditor information we have (/keys cherry picking) */
- json_array_foreach (auditors_array, index, auditor_info) {
+ json_array_foreach (auditors_array, index, auditor_info)
+ {
struct TALER_EXCHANGE_AuditorInformation ai;
- int found = GNUNET_NO;
+ bool found = false;
memset (&ai,
0,
@@ -1009,25 +1206,35 @@ decode_keys_json (const json_t *resp_obj,
if (0 == GNUNET_memcmp (&ai.auditor_pub,
&aix->auditor_pub))
{
- found = GNUNET_YES;
+ found = true;
/* Merge denomination key signatures of downloaded /keys into existing
auditor information 'aix'. */
TALER_LOG_DEBUG (
"Merging %u new audited keys with %u known audited keys\n",
aix->num_denom_keys,
ai.num_denom_keys);
-
- GNUNET_array_grow (aix->denom_keys,
- aix->num_denom_keys,
- aix->num_denom_keys + ai.num_denom_keys);
- memcpy (&aix->denom_keys[aix->num_denom_keys - ai.num_denom_keys],
- ai.denom_keys,
- ai.num_denom_keys * sizeof (struct
- TALER_EXCHANGE_AuditorDenominationInfo));
+ for (unsigned int i = 0; i<ai.num_denom_keys; i++)
+ {
+ bool kfound = false;
+
+ for (unsigned int k = 0; k<aix->num_denom_keys; k++)
+ {
+ if (aix->denom_keys[k].denom_key_offset ==
+ ai.denom_keys[i].denom_key_offset)
+ {
+ kfound = true;
+ break;
+ }
+ }
+ if (! kfound)
+ GNUNET_array_append (aix->denom_keys,
+ aix->num_denom_keys,
+ ai.denom_keys[i]);
+ }
break;
}
}
- if (GNUNET_YES == found)
+ if (found)
{
GNUNET_array_grow (ai.denom_keys,
ai.num_denom_keys,
@@ -1039,201 +1246,94 @@ decode_keys_json (const json_t *resp_obj,
GNUNET_array_grow (key_data->auditors,
key_data->auditors_size,
key_data->auditors_size * 2 + 2);
+ GNUNET_assert (key_data->auditors_size >
+ key_data->num_auditors);
GNUNET_assert (NULL != ai.auditor_url);
+ GNUNET_assert (key_data->num_auditors < UINT_MAX);
key_data->auditors[key_data->num_auditors++] = ai;
};
}
/* parse the revocation/recoup information */
+ if (NULL != recoup_array)
{
- json_t *recoup_array;
json_t *recoup_info;
unsigned int index;
- if (NULL != (recoup_array =
- json_object_get (resp_obj,
- "recoup")))
+ json_array_foreach (recoup_array, index, recoup_info)
{
- EXITIF (JSON_ARRAY != json_typeof (recoup_array));
-
- json_array_foreach (recoup_array, index, recoup_info) {
- struct GNUNET_HashCode h_denom_pub;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- &h_denom_pub),
- GNUNET_JSON_spec_end ()
- };
+ struct TALER_DenominationHashP h_denom_pub;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_spec_end ()
+ };
- EXITIF (GNUNET_OK !=
- GNUNET_JSON_parse (recoup_info,
- spec,
- NULL, NULL));
- for (unsigned int j = 0;
- j<key_data->num_denom_keys;
- j++)
+ EXITIF (GNUNET_OK !=
+ GNUNET_JSON_parse (recoup_info,
+ spec,
+ NULL, NULL));
+ for (unsigned int j = 0;
+ j<key_data->num_denom_keys;
+ j++)
+ {
+ if (0 == GNUNET_memcmp (&h_denom_pub,
+ &key_data->denom_keys[j].h_key))
{
- if (0 == GNUNET_memcmp (&h_denom_pub,
- &key_data->denom_keys[j].h_key))
- {
- key_data->denom_keys[j].revoked = GNUNET_YES;
- break;
- }
+ key_data->denom_keys[j].revoked = true;
+ break;
}
- };
+ }
}
}
if (check_sig)
{
- struct TALER_ExchangeKeySetPS ks;
+ struct GNUNET_HashContext *hash_context;
+ struct GNUNET_HashCode hc;
- /* Validate signature... */
- ks.purpose.size = htonl (sizeof (ks));
- ks.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET);
- ks.list_issue_date = GNUNET_TIME_absolute_hton (key_data->list_issue_date);
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+ qsort (sig_ctx.elements,
+ sig_ctx.elements_pos,
+ sizeof (struct SignatureElement),
+ &signature_context_sort_cb);
+ for (unsigned int i = 0; i<sig_ctx.elements_pos; i++)
+ {
+ struct SignatureElement *element = &sig_ctx.elements[i];
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Adding %u,%u,%s\n",
+ element->group_offset,
+ element->offset,
+ TALER_B2S (&element->master_sig));
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &element->master_sig,
+ sizeof (element->master_sig));
+ }
+ GNUNET_array_grow (sig_ctx.elements,
+ sig_ctx.elements_size,
+ 0);
GNUNET_CRYPTO_hash_context_finish (hash_context,
- &ks.hc);
- hash_context = NULL;
+ &hc);
EXITIF (GNUNET_OK !=
TALER_EXCHANGE_test_signing_key (key_data,
- &pub));
+ &exchange_pub));
EXITIF (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_KEY_SET,
- &ks.purpose,
- &sig.eddsa_signature,
- &pub.eddsa_pub));
+ TALER_exchange_online_key_set_verify (
+ key_data->list_issue_date,
+ &hc,
+ &exchange_pub,
+ &exchange_sig));
}
return GNUNET_OK;
-EXITIF_exit:
+EXITIF_exit:
*vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR;
- if (NULL != hash_context)
- GNUNET_CRYPTO_hash_context_abort (hash_context);
return GNUNET_SYSERR;
}
/**
- * Free key data object.
- *
- * @param key_data data to free (pointer itself excluded)
- */
-static void
-free_key_data (struct TALER_EXCHANGE_Keys *key_data)
-{
- GNUNET_array_grow (key_data->sign_keys,
- key_data->num_sign_keys,
- 0);
- for (unsigned int i = 0; i<key_data->num_denom_keys; i++)
- GNUNET_CRYPTO_rsa_public_key_free (
- key_data->denom_keys[i].key.rsa_public_key);
-
- GNUNET_array_grow (key_data->denom_keys,
- key_data->denom_keys_size,
- 0);
- for (unsigned int i = 0; i<key_data->num_auditors; i++)
- {
- GNUNET_array_grow (key_data->auditors[i].denom_keys,
- key_data->auditors[i].num_denom_keys,
- 0);
- GNUNET_free (key_data->auditors[i].auditor_url);
- }
- GNUNET_array_grow (key_data->auditors,
- key_data->auditors_size,
- 0);
- GNUNET_free_non_null (key_data->version);
- key_data->version = NULL;
-}
-
-
-/**
- * Initiate download of /keys from the exchange.
- *
- * @param cls exchange where to download /keys from
- */
-static void
-request_keys (void *cls);
-
-
-/**
- * Set the fake now to be used when requesting "/keys".
- *
- * @param exchange exchange handle.
- * @param now fake now to use. Note: this value will be
- * used _until_ its use will be unset via @a TALER_EXCHANGE_unset_now()
- */
-void
-TALER_EXCHANGE_set_now (struct TALER_EXCHANGE_Handle *exchange,
- struct GNUNET_TIME_Absolute now)
-{
- exchange->with_now = GNUNET_YES;
- exchange->now = now;
-}
-
-
-/**
- * Unset the fake now to be used when requesting "/keys".
- *
- * @param exchange exchange handle.
- */
-void
-TALER_EXCHANGE_unset_now (struct TALER_EXCHANGE_Handle *exchange)
-{
- exchange->with_now = GNUNET_NO;
-}
-
-
-/**
- * Let the user set the last valid denomination time manually.
- *
- * @param exchange the exchange handle.
- * @param last_denom_new new last denomination time.
- */
-void
-TALER_EXCHANGE_set_last_denom (struct TALER_EXCHANGE_Handle *exchange,
- struct GNUNET_TIME_Absolute last_denom_new)
-{
- exchange->key_data.last_denom_issue_date = last_denom_new;
-}
-
-
-/**
- * Check if our current response for /keys is valid, and if
- * not trigger download.
- *
- * @param exchange exchange to check keys for
- * @param force_download #GNUNET_YES to force download even if /keys is still valid
- * @param pull_all_keys if #GNUNET_YES, then the exchange state is reset to #MHS_INIT,
- * and all denoms will be redownloaded.
- * @return until when the response is current, 0 if we are re-downloading
- */
-struct GNUNET_TIME_Absolute
-TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange,
- int force_download,
- int pull_all_keys)
-{
- if (NULL != exchange->kr)
- return GNUNET_TIME_UNIT_ZERO_ABS;
-
- if (GNUNET_YES == pull_all_keys)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Forcing re-download of all exchange keys\n");
- GNUNET_break (GNUNET_YES == force_download);
- exchange->state = MHS_INIT;
- }
- if ( (GNUNET_NO == force_download) &&
- (0 < GNUNET_TIME_absolute_get_remaining (
- exchange->key_data_expiration).rel_value_us) )
- return exchange->key_data_expiration;
- if (NULL == exchange->retry_task)
- exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys,
- exchange);
- return GNUNET_TIME_UNIT_ZERO_ABS;
-}
-
-
-/**
* Callback used when downloading the reply to a /keys request
* is complete.
*
@@ -1246,246 +1346,251 @@ keys_completed_cb (void *cls,
long response_code,
const void *resp_obj)
{
- struct KeysRequest *kr = cls;
- struct TALER_EXCHANGE_Handle *exchange = kr->exchange;
- struct TALER_EXCHANGE_Keys kd;
- struct TALER_EXCHANGE_Keys kd_old;
- enum TALER_EXCHANGE_VersionCompatibility vc;
+ struct TALER_EXCHANGE_GetKeysHandle *gkh = cls;
const json_t *j = resp_obj;
+ struct TALER_EXCHANGE_Keys *kd = NULL;
+ struct TALER_EXCHANGE_KeysResponse kresp = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code,
+ .details.ok.compat = TALER_EXCHANGE_VC_PROTOCOL_ERROR,
+ };
+ gkh->job = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received keys from URL `%s' with status %ld.\n",
- kr->url,
- response_code);
- kd_old = exchange->key_data;
- memset (&kd,
- 0,
- sizeof (struct TALER_EXCHANGE_Keys));
- vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR;
+ "Received keys from URL `%s' with status %ld and expiration %s.\n",
+ gkh->url,
+ response_code,
+ GNUNET_TIME_timestamp2s (gkh->expire));
+ if (GNUNET_TIME_absolute_is_past (gkh->expire.abs_time))
+ {
+ if (MHD_HTTP_OK == response_code)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Exchange failed to give expiration time, assuming in %s\n",
+ GNUNET_TIME_relative2s (DEFAULT_EXPIRATION,
+ true));
+ gkh->expire
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION));
+ }
switch (response_code)
{
case 0:
- free_keys_request (kr);
- exchange->kr = NULL;
- GNUNET_assert (NULL == exchange->retry_task);
- exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay);
- exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay,
- &request_keys,
- exchange);
- return;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to receive /keys response from exchange %s\n",
+ gkh->exchange_url);
+ break;
case MHD_HTTP_OK:
if (NULL == j)
{
+ GNUNET_break (0);
response_code = 0;
break;
}
- /* We keep the denomination keys and auditor signatures from the
- previous iteration (/keys cherry picking) */
- kd.num_denom_keys = kd_old.num_denom_keys;
- kd.last_denom_issue_date = kd_old.last_denom_issue_date;
- GNUNET_array_grow (kd.denom_keys,
- kd.denom_keys_size,
- kd.num_denom_keys);
-
- /* First make a shallow copy, we then need another pass for the RSA key... */
- memcpy (kd.denom_keys,
- kd_old.denom_keys,
- kd_old.num_denom_keys * sizeof (struct
- TALER_EXCHANGE_DenomPublicKey));
-
- for (unsigned int i = 0; i<kd_old.num_denom_keys; i++)
- kd.denom_keys[i].key.rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_dup (
- kd_old.denom_keys[i].key.rsa_public_key);
-
- kd.num_auditors = kd_old.num_auditors;
- kd.auditors = GNUNET_new_array (kd.num_auditors,
- struct TALER_EXCHANGE_AuditorInformation);
- /* Now the necessary deep copy... */
- for (unsigned int i = 0; i<kd_old.num_auditors; i++)
- {
- const struct TALER_EXCHANGE_AuditorInformation *aold =
- &kd_old.auditors[i];
- struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i];
-
- anew->auditor_pub = aold->auditor_pub;
- GNUNET_assert (NULL != aold->auditor_url);
- anew->auditor_url = GNUNET_strdup (aold->auditor_url);
- GNUNET_array_grow (anew->denom_keys,
- anew->num_denom_keys,
- aold->num_denom_keys);
- memcpy (anew->denom_keys,
- aold->denom_keys,
- aold->num_denom_keys * sizeof (struct
- TALER_EXCHANGE_AuditorDenominationInfo));
- }
-
- /* Old auditors got just copied into new ones. */
- if (GNUNET_OK !=
- decode_keys_json (j,
- GNUNET_YES,
- &kd,
- &vc))
+ kd = GNUNET_new (struct TALER_EXCHANGE_Keys);
+ kd->exchange_url = GNUNET_strdup (gkh->exchange_url);
+ if (NULL != gkh->prev_keys)
{
- TALER_LOG_ERROR ("Could not decode /keys response\n");
- response_code = 0;
- for (unsigned int i = 0; i<kd.num_auditors; i++)
+ const struct TALER_EXCHANGE_Keys *kd_old = gkh->prev_keys;
+
+ /* We keep the denomination keys and auditor signatures from the
+ previous iteration (/keys cherry picking) */
+ kd->num_denom_keys
+ = kd_old->num_denom_keys;
+ kd->last_denom_issue_date
+ = kd_old->last_denom_issue_date;
+ GNUNET_array_grow (kd->denom_keys,
+ kd->denom_keys_size,
+ kd->num_denom_keys);
+ /* First make a shallow copy, we then need another pass for the RSA key... */
+ GNUNET_memcpy (kd->denom_keys,
+ kd_old->denom_keys,
+ kd_old->num_denom_keys
+ * sizeof (struct TALER_EXCHANGE_DenomPublicKey));
+ for (unsigned int i = 0; i<kd_old->num_denom_keys; i++)
+ TALER_denom_pub_copy (&kd->denom_keys[i].key,
+ &kd_old->denom_keys[i].key);
+ kd->num_auditors = kd_old->num_auditors;
+ kd->auditors = GNUNET_new_array (kd->num_auditors,
+ struct TALER_EXCHANGE_AuditorInformation);
+ /* Now the necessary deep copy... */
+ for (unsigned int i = 0; i<kd_old->num_auditors; i++)
{
- struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i];
+ const struct TALER_EXCHANGE_AuditorInformation *aold =
+ &kd_old->auditors[i];
+ struct TALER_EXCHANGE_AuditorInformation *anew = &kd->auditors[i];
+ anew->auditor_pub = aold->auditor_pub;
+ anew->auditor_url = GNUNET_strdup (aold->auditor_url);
GNUNET_array_grow (anew->denom_keys,
anew->num_denom_keys,
- 0);
- GNUNET_free (anew->auditor_url);
+ aold->num_denom_keys);
+ GNUNET_memcpy (
+ anew->denom_keys,
+ aold->denom_keys,
+ aold->num_denom_keys
+ * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo));
}
- GNUNET_free (kd.auditors);
- kd.auditors = NULL;
- kd.num_auditors = 0;
- for (unsigned int i = 0; i<kd_old.num_denom_keys; i++)
- GNUNET_CRYPTO_rsa_public_key_free (kd.denom_keys[i].key.rsa_public_key);
- GNUNET_array_grow (kd.denom_keys,
- kd.denom_keys_size,
- 0);
- kd.num_denom_keys = 0;
+ }
+ /* Now decode fresh /keys response */
+ if (GNUNET_OK !=
+ decode_keys_json (j,
+ true,
+ kd,
+ &kresp.details.ok.compat))
+ {
+ TALER_LOG_ERROR ("Could not decode /keys response\n");
+ kd->rc = 1;
+ TALER_EXCHANGE_keys_decref (kd);
+ kd = NULL;
+ kresp.hr.http_status = 0;
+ kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
- json_decref (exchange->key_data_raw);
- exchange->key_data_raw = json_deep_copy (j);
- exchange->retry_delay = GNUNET_TIME_UNIT_ZERO;
+ kd->rc = 1;
+ kd->key_data_expiration = gkh->expire;
+ if (GNUNET_TIME_relative_cmp (
+ GNUNET_TIME_absolute_get_remaining (gkh->expire.abs_time),
+ <,
+ MINIMUM_EXPIRATION))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Exchange returned keys with expiration time below %s. Compensating.\n",
+ GNUNET_TIME_relative2s (MINIMUM_EXPIRATION,
+ true));
+ kd->key_data_expiration
+ = GNUNET_TIME_relative_to_timestamp (MINIMUM_EXPIRATION);
+ }
+
+ kresp.details.ok.keys = kd;
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_UNAUTHORIZED:
+ case MHD_HTTP_FORBIDDEN:
+ case MHD_HTTP_NOT_FOUND:
+ if (NULL == j)
+ {
+ kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
+ }
+ else
+ {
+ kresp.hr.ec = TALER_JSON_get_error_code (j);
+ kresp.hr.hint = TALER_JSON_get_error_hint (j);
+ }
break;
default:
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u\n",
- (unsigned int) response_code);
- break;
- }
- exchange->key_data = kd;
- TALER_LOG_DEBUG ("Last DK issue date update to: %s\n",
- GNUNET_STRINGS_absolute_time_to_string
- (exchange->key_data.last_denom_issue_date));
-
-
- if (MHD_HTTP_OK != response_code)
- {
- exchange->kr = NULL;
- free_keys_request (kr);
- exchange->state = MHS_FAILED;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Exchange keys download failed\n");
- if (NULL != exchange->key_data_raw)
+ if (NULL == j)
{
- json_decref (exchange->key_data_raw);
- exchange->key_data_raw = NULL;
+ kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
}
- free_key_data (&kd_old);
- /* notify application that we failed */
- exchange->cert_cb (exchange->cert_cb_cls,
- NULL,
- vc,
- TALER_JSON_get_error_code (j),
- response_code,
- j);
- return;
+ else
+ {
+ kresp.hr.ec = TALER_JSON_get_error_code (j);
+ kresp.hr.hint = TALER_JSON_get_error_hint (j);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) kresp.hr.ec);
+ break;
}
-
- exchange->kr = NULL;
- exchange->key_data_expiration = kr->expire;
- free_keys_request (kr);
- exchange->state = MHS_CERT;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Successfully downloaded exchange's keys\n");
- update_auditors (exchange);
- /* notify application about the key information */
- exchange->cert_cb (exchange->cert_cb_cls,
- &exchange->key_data,
- vc,
- TALER_EC_NONE,
- MHD_HTTP_OK,
- j);
- free_key_data (&kd_old);
+ gkh->cert_cb (gkh->cert_cb_cls,
+ &kresp,
+ kd);
+ TALER_EXCHANGE_get_keys_cancel (gkh);
}
-/* ********************* library internal API ********* */
-
-
/**
- * Get the context of a exchange.
- *
- * @param h the exchange handle to query
- * @return ctx context to execute jobs in
+ * Define a max length for the HTTP "Expire:" header
*/
-struct GNUNET_CURL_Context *
-TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h)
-{
- return h->ctx;
-}
-
-
-/**
- * Check if the handle is ready to process requests.
- *
- * @param h the exchange handle to query
- * @return #GNUNET_YES if we are ready, #GNUNET_NO if not
- */
-int
-TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h)
-{
- return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO;
-}
-
-
-/**
- * Obtain the URL to use for an API request.
- *
- * @param h handle for the exchange
- * @param path Taler API path (i.e. "/reserve/withdraw")
- * @return the full URL to use with cURL
- */
-char *
-TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h,
- const char *path)
-{
- char *ret;
-
- GNUNET_assert ('/' == path[0]);
- ret = TALER_url_join (h->url,
- path + 1,
- NULL);
- GNUNET_assert (NULL != ret);
- return ret;
-}
+#define MAX_DATE_LINE_LEN 32
/**
* Parse HTTP timestamp.
*
- * @param date header to parse header
- * @param at where to write the result
+ * @param dateline header to parse header
+ * @param[out] at where to write the result
* @return #GNUNET_OK on success
*/
-static int
-parse_date_string (const char *date,
- struct GNUNET_TIME_Absolute *at)
+static enum GNUNET_GenericReturnValue
+parse_date_string (const char *dateline,
+ struct GNUNET_TIME_Timestamp *at)
{
- struct tm now;
+ static const char *MONTHS[] =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL };
+ int year;
+ int mon;
+ int day;
+ int hour;
+ int min;
+ int sec;
+ char month[4];
+ struct tm tm;
time_t t;
- const char *end;
- memset (&now,
- 0,
- sizeof (now));
- end = strptime (date,
- "%a, %d %b %Y %H:%M:%S %Z", /* RFC-1123 standard spec */
- &now);
- if ( (NULL == end) ||
- ( (*end != '\n') &&
- (*end != '\r') ) )
+ /* We recognize the three formats in RFC2616, section 3.3.1. Month
+ names are always in English. The formats are:
+ Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
+ Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
+ Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
+ Note that the first is preferred.
+ */
+
+ if (strlen (dateline) > MAX_DATE_LINE_LEN)
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- t = mktime (&now);
+ while (*dateline == ' ')
+ ++dateline;
+ while (*dateline && *dateline != ' ')
+ ++dateline;
+ while (*dateline == ' ')
+ ++dateline;
+ /* We just skipped over the day of the week. Now we have:*/
+ if ( (sscanf (dateline,
+ "%d %3s %d %d:%d:%d",
+ &day, month, &year, &hour, &min, &sec) != 6) &&
+ (sscanf (dateline,
+ "%d-%3s-%d %d:%d:%d",
+ &day, month, &year, &hour, &min, &sec) != 6) &&
+ (sscanf (dateline,
+ "%3s %d %d:%d:%d %d",
+ month, &day, &hour, &min, &sec, &year) != 6) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* Two digit dates are defined to be relative to 1900; all other dates
+ * are supposed to be represented as four digits. */
+ if (year < 100)
+ year += 1900;
+
+ for (mon = 0; ; mon++)
+ {
+ if (! MONTHS[mon])
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 == strcasecmp (month,
+ MONTHS[mon]))
+ break;
+ }
+
+ memset (&tm, 0, sizeof(tm));
+ tm.tm_year = year - 1900;
+ tm.tm_mon = mon;
+ tm.tm_mday = day;
+ tm.tm_hour = hour;
+ tm.tm_min = min;
+ tm.tm_sec = sec;
+
+ t = mktime (&tm);
if (((time_t) -1) == t)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
@@ -1494,7 +1599,7 @@ parse_date_string (const char *date,
}
if (t < 0)
t = 0; /* can happen due to timezone issues if date was 1.1.1970 */
- at->abs_value_us = 1000LL * 1000LL * t;
+ *at = GNUNET_TIME_timestamp_from_s (t);
return GNUNET_OK;
}
@@ -1502,12 +1607,12 @@ parse_date_string (const char *date,
/**
* Function called for each header in the HTTP /keys response.
* Finds the "Expire:" header and parses it, storing the result
- * in the "expire" field fo the keys request.
+ * in the "expire" field of the keys request.
*
* @param buffer header data received
* @param size size of an item in @a buffer
* @param nitems number of items in @a buffer
- * @param userdata the `struct KeysRequest`
+ * @param userdata the `struct TALER_EXCHANGE_GetKeysHandle`
* @return `size * nitems` on success (everything else aborts)
*/
static size_t
@@ -1516,7 +1621,7 @@ header_cb (char *buffer,
size_t nitems,
void *userdata)
{
- struct KeysRequest *kr = userdata;
+ struct TALER_EXCHANGE_GetKeysHandle *kr = userdata;
size_t total = size * nitems;
char *val;
@@ -1528,6 +1633,10 @@ header_cb (char *buffer,
return total;
val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")],
total - strlen (MHD_HTTP_HEADER_EXPIRES ": "));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found %s header `%s'\n",
+ MHD_HTTP_HEADER_EXPIRES,
+ val);
if (GNUNET_OK !=
parse_date_string (val,
&kr->expire))
@@ -1536,478 +1645,73 @@ header_cb (char *buffer,
"Failed to parse %s-header `%s'\n",
MHD_HTTP_HEADER_EXPIRES,
val);
- kr->expire = GNUNET_TIME_UNIT_ZERO_ABS;
+ kr->expire = GNUNET_TIME_UNIT_ZERO_TS;
}
GNUNET_free (val);
return total;
}
-/* ********************* public API ******************* */
-
-
-/**
- * Deserialize the key data and use it to bootstrap @a exchange to
- * more efficiently recover the state. Errors in @a data must be
- * tolerated (i.e. by re-downloading instead).
- *
- * @param exchange which exchange's key and wire data should be deserialized
- * @param data the data to deserialize
- */
-static void
-deserialize_data (struct TALER_EXCHANGE_Handle *exchange,
- const json_t *data)
-{
- enum TALER_EXCHANGE_VersionCompatibility vc;
- json_t *keys;
- const char *url;
- uint32_t version;
- struct GNUNET_TIME_Absolute expire;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint32 ("version",
- &version),
- GNUNET_JSON_spec_json ("keys",
- &keys),
- GNUNET_JSON_spec_string ("exchange_url",
- &url),
- GNUNET_JSON_spec_absolute_time ("expire",
- &expire),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_EXCHANGE_Keys key_data;
-
- if (NULL == data)
- return;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (data,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return;
- }
- if (0 != version)
- {
- GNUNET_JSON_parse_free (spec);
- return; /* unsupported version */
- }
- if (0 != strcmp (url,
- exchange->url))
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return;
- }
- memset (&key_data,
- 0,
- sizeof (struct TALER_EXCHANGE_Keys));
- if (GNUNET_OK !=
- decode_keys_json (keys,
- GNUNET_NO,
- &key_data,
- &vc))
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return;
- }
- /* decode successful, initialize with the result */
- GNUNET_assert (NULL == exchange->key_data_raw);
- exchange->key_data_raw = json_deep_copy (keys);
- exchange->key_data = key_data;
- exchange->key_data_expiration = expire;
- exchange->state = MHS_CERT;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Successfully loaded exchange's keys via deserialization\n");
- update_auditors (exchange);
- /* notify application about the key information */
- exchange->cert_cb (exchange->cert_cb_cls,
- &exchange->key_data,
- vc,
- TALER_EC_NONE,
- MHD_HTTP_OK,
- data);
- GNUNET_JSON_parse_free (spec);
-}
-
-
-/**
- * Serialize the latest key data from @a
- * exchange to be persisted on disk (to be used with
- * #TALER_EXCHANGE_OPTION_DATA to more efficiently recover
- * the state).
- *
- * @param exchange which exchange's key and wire data should be
- * serialized
- * @return NULL on error (i.e. no current data available);
- * otherwise JSON object owned by the caller
- */
-json_t *
-TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange)
-{
- const struct TALER_EXCHANGE_Keys *kd = &exchange->key_data;
- struct GNUNET_TIME_Absolute now;
- json_t *keys;
- json_t *signkeys;
- json_t *denoms;
- json_t *auditors;
-
- now = GNUNET_TIME_absolute_get ();
- signkeys = json_array ();
- if (NULL == signkeys)
- {
- GNUNET_break (0);
- return NULL;
- }
- for (unsigned int i = 0; i<kd->num_sign_keys; i++)
- {
- const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i];
- json_t *signkey;
-
- if (now.abs_value_us > sk->valid_until.abs_value_us)
- continue; /* skip keys that have expired */
- signkey = json_pack ("{s:o, s:o, s:o, s:o, s:o}",
- "key",
- GNUNET_JSON_from_data_auto
- (&sk->key),
- "master_sig",
- GNUNET_JSON_from_data_auto
- (&sk->master_sig),
- "stamp_start",
- GNUNET_JSON_from_time_abs
- (sk->valid_from),
- "stamp_expire",
- GNUNET_JSON_from_time_abs
- (sk->valid_until),
- "stamp_end",
- GNUNET_JSON_from_time_abs
- (sk->valid_legal));
- if (NULL == signkey)
- {
- GNUNET_break (0);
- continue;
- }
- if (0 != json_array_append_new (signkeys,
- signkey))
- {
- GNUNET_break (0);
- json_decref (signkey);
- json_decref (signkeys);
- return NULL;
- }
- }
- denoms = json_array ();
- if (NULL == denoms)
- {
- GNUNET_break (0);
- json_decref (signkeys);
- return NULL;
- }
- for (unsigned int i = 0; i<kd->num_denom_keys; i++)
- {
- const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i];
- json_t *denom;
-
- if (now.abs_value_us > dk->expire_deposit.abs_value_us)
- continue; /* skip keys that have expired */
- denom = json_pack ("{s:o, s:o, s:o, s:o, s:o "
- ",s:o, s:o, s:o, s:o, s:o "
- ",s:o}",
- "stamp_expire_deposit",
- GNUNET_JSON_from_time_abs (dk->expire_deposit),
- "stamp_expire_withdraw",
- GNUNET_JSON_from_time_abs (dk->withdraw_valid_until),
- "stamp_start",
- GNUNET_JSON_from_time_abs (dk->valid_from),
- "stamp_expire_legal",
- GNUNET_JSON_from_time_abs (dk->expire_legal),
- "value",
- TALER_JSON_from_amount (&dk->value),
- "fee_withdraw",
- /* #6 */
- TALER_JSON_from_amount (&dk->fee_withdraw),
- "fee_deposit",
- TALER_JSON_from_amount (&dk->fee_deposit),
- "fee_refresh",
- TALER_JSON_from_amount (&dk->fee_refresh),
- "fee_refund",
- TALER_JSON_from_amount (&dk->fee_refund),
- "master_sig",
- GNUNET_JSON_from_data_auto (&dk->master_sig),
- /* #10 */
- "denom_pub",
- GNUNET_JSON_from_rsa_public_key (
- dk->key.rsa_public_key));
- if (NULL == denom)
- {
- GNUNET_break (0);
- continue;
- }
- if (0 != json_array_append_new (denoms,
- denom))
- {
- GNUNET_break (0);
- json_decref (denom);
- json_decref (denoms);
- json_decref (signkeys);
- return NULL;
- }
- }
- auditors = json_array ();
- if (NULL == auditors)
- {
- GNUNET_break (0);
- json_decref (denoms);
- json_decref (signkeys);
- return NULL;
- }
- for (unsigned int i = 0; i<kd->num_auditors; i++)
- {
- const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i];
- json_t *a;
- json_t *adenoms;
-
- adenoms = json_array ();
- if (NULL == adenoms)
- {
- GNUNET_break (0);
- json_decref (denoms);
- json_decref (signkeys);
- json_decref (auditors);
- return NULL;
- }
- for (unsigned int j = 0; j<ai->num_denom_keys; j++)
- {
- const struct TALER_EXCHANGE_AuditorDenominationInfo *adi =
- &ai->denom_keys[j];
- const struct TALER_EXCHANGE_DenomPublicKey *dk =
- &kd->denom_keys[adi->denom_key_offset];
- json_t *k;
-
- if (now.abs_value_us > dk->expire_deposit.abs_value_us)
- continue; /* skip auditor signatures for denomination keys that have expired */
- GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys);
- k = json_pack ("{s:o, s:o}",
- "denom_pub_h",
- GNUNET_JSON_from_data_auto (&dk->h_key),
- "auditor_sig",
- GNUNET_JSON_from_data_auto (&adi->auditor_sig));
- if (NULL == k)
- {
- GNUNET_break (0);
- json_decref (adenoms);
- json_decref (denoms);
- json_decref (signkeys);
- json_decref (auditors);
- return NULL;
- }
- if (0 != json_array_append_new (adenoms,
- k))
- {
- GNUNET_break (0);
- json_decref (k);
- json_decref (adenoms);
- json_decref (denoms);
- json_decref (signkeys);
- json_decref (auditors);
- return NULL;
- }
- }
-
- a = json_pack ("{s:o, s:s, s:o}",
- "auditor_pub",
- GNUNET_JSON_from_data_auto (&ai->auditor_pub),
- "auditor_url",
- ai->auditor_url,
- "denomination_keys",
- adenoms);
- if (NULL == a)
- {
- json_decref (adenoms);
- json_decref (denoms);
- json_decref (signkeys);
- json_decref (auditors);
- return NULL;
- }
- if (0 != json_array_append_new (auditors,
- a))
- {
- json_decref (a);
- json_decref (denoms);
- json_decref (signkeys);
- json_decref (auditors);
- return NULL;
- }
- }
- keys = json_pack ("{s:s, s:o, s:o, s:o, s:o"
- ",s:o, s:o}",
- /* 1 */
- "version",
- kd->version,
- "master_public_key",
- GNUNET_JSON_from_data_auto (&kd->master_pub),
- "reserve_closing_delay",
- GNUNET_JSON_from_time_rel (kd->reserve_closing_delay),
- "list_issue_date",
- GNUNET_JSON_from_time_abs (kd->list_issue_date),
- "signkeys",
- signkeys,
- /* #6 */
- "denoms",
- denoms,
- "auditors",
- auditors);
- if (NULL == keys)
- {
- GNUNET_break (0);
- return NULL;
- }
- return json_pack ("{s:I, s:o, s:s, s:o}",
- "version",
- (json_int_t) TALER_SERIALIZATION_FORMAT_VERSION,
- "expire",
- GNUNET_JSON_from_time_abs (exchange->key_data_expiration),
- "exchange_url",
- exchange->url,
- "keys",
- keys);
-}
-
-
-/**
- * Initialise a connection to the exchange. Will connect to the
- * exchange and obtain information about the exchange's master
- * public key and the exchange's auditor.
- * The respective information will be passed to the @a cert_cb
- * once available, and all future interactions with the exchange
- * will be checked to be signed (where appropriate) by the
- * respective master key.
- *
- * @param ctx the context
- * @param url HTTP base URL for the exchange
- * @param cert_cb function to call with the exchange's
- * certification information
- * @param cert_cb_cls closure for @a cert_cb
- * @param ... list of additional arguments,
- * terminated by #TALER_EXCHANGE_OPTION_END.
- * @return the exchange handle; NULL upon error
- */
-struct TALER_EXCHANGE_Handle *
-TALER_EXCHANGE_connect (
+struct TALER_EXCHANGE_GetKeysHandle *
+TALER_EXCHANGE_get_keys (
struct GNUNET_CURL_Context *ctx,
const char *url,
- TALER_EXCHANGE_CertificationCallback cert_cb,
- void *cert_cb_cls,
- ...)
+ struct TALER_EXCHANGE_Keys *last_keys,
+ TALER_EXCHANGE_GetKeysCallback cert_cb,
+ void *cert_cb_cls)
{
- struct TALER_EXCHANGE_Handle *exchange;
- va_list ap;
- enum TALER_EXCHANGE_Option opt;
+ struct TALER_EXCHANGE_GetKeysHandle *gkh;
+ CURL *eh;
+ char last_date[80] = { 0 };
TALER_LOG_DEBUG ("Connecting to the exchange (%s)\n",
url);
- /* Disable 100 continue processing */
- GNUNET_break (GNUNET_OK ==
- GNUNET_CURL_append_header (ctx,
- "Expect:"));
- exchange = GNUNET_new (struct TALER_EXCHANGE_Handle);
- exchange->ctx = ctx;
- exchange->url = GNUNET_strdup (url);
- exchange->cert_cb = cert_cb;
- exchange->cert_cb_cls = cert_cb_cls;
- exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys,
- exchange);
- va_start (ap, cert_cb_cls);
- while (TALER_EXCHANGE_OPTION_END !=
- (opt = va_arg (ap, int)))
- {
- switch (opt)
- {
- case TALER_EXCHANGE_OPTION_END:
- GNUNET_assert (0);
- break;
- case TALER_EXCHANGE_OPTION_DATA:
- {
- const json_t *data = va_arg (ap, const json_t *);
-
- deserialize_data (exchange,
- data);
- break;
- }
- default:
- GNUNET_assert (0);
- break;
- }
- }
- va_end (ap);
- return exchange;
-}
-
-
-/**
- * Initiate download of /keys from the exchange.
- *
- * @param cls exchange where to download /keys from
- */
-static void
-request_keys (void *cls)
-{
- struct TALER_EXCHANGE_Handle *exchange = cls;
- struct KeysRequest *kr;
- CURL *eh;
- char url[200] = "/keys?";
-
- exchange->retry_task = NULL;
- GNUNET_assert (NULL == exchange->kr);
- kr = GNUNET_new (struct KeysRequest);
- kr->exchange = exchange;
-
- if (GNUNET_YES == TEAH_handle_is_ready (exchange))
+ gkh = GNUNET_new (struct TALER_EXCHANGE_GetKeysHandle);
+ gkh->exchange_url = GNUNET_strdup (url);
+ gkh->cert_cb = cert_cb;
+ gkh->cert_cb_cls = cert_cb_cls;
+ if (NULL != last_keys)
{
+ gkh->prev_keys = TALER_EXCHANGE_keys_incref (last_keys);
TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n",
- GNUNET_STRINGS_absolute_time_to_string (
- exchange->key_data.last_denom_issue_date));
- sprintf (&url[strlen (url)],
- "last_issue_date=%llu&",
- (unsigned long
- long) exchange->key_data.last_denom_issue_date.abs_value_us
- / 1000000LLU);
- }
-
- if (GNUNET_YES == exchange->with_now)
- {
- TALER_LOG_DEBUG ("Faking now to GET /keys: %s\n",
- GNUNET_STRINGS_absolute_time_to_string (exchange->now));
- sprintf (&url[strlen (url)],
- "now=%llu&",
- (unsigned long long) exchange->now.abs_value_us / 1000000LLU);
+ GNUNET_TIME_timestamp2s (
+ last_keys->last_denom_issue_date));
+ GNUNET_snprintf (last_date,
+ sizeof (last_date),
+ "%llu",
+ (unsigned long long)
+ last_keys->last_denom_issue_date.abs_time.abs_value_us
+ / 1000000LLU);
}
-
- /* Clean the last '&'/'?' sign that we optimistically put. */
- url[strlen (url) - 1] = '\0';
- kr->url = TEAH_path_to_url (exchange,
- url);
-
+ gkh->url = TALER_url_join (url,
+ "keys",
+ (NULL != last_keys)
+ ? "last_issue_date"
+ : NULL,
+ (NULL != last_keys)
+ ? last_date
+ : NULL,
+ NULL);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Requesting keys with URL `%s'.\n",
- kr->url);
- eh = TALER_EXCHANGE_curl_easy_get_ (kr->url);
+ gkh->url);
+ eh = TALER_EXCHANGE_curl_easy_get_ (gkh->url);
if (NULL == eh)
{
- exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay);
- exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay,
- &request_keys,
- exchange);
- return;
+ GNUNET_break (0);
+ GNUNET_free (gkh->exchange_url);
+ GNUNET_free (gkh->url);
+ GNUNET_free (gkh);
+ return NULL;
}
GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_VERBOSE,
- 0));
+ 1));
GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_TIMEOUT,
- (long) 300));
+ 120 /* seconds */));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_HEADERFUNCTION,
@@ -2015,142 +1719,96 @@ request_keys (void *cls)
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_HEADERDATA,
- kr));
- kr->job = GNUNET_CURL_job_add (exchange->ctx,
- eh,
- GNUNET_YES,
- &keys_completed_cb,
- kr);
- exchange->kr = kr;
+ gkh));
+ gkh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
+ eh,
+ &keys_completed_cb,
+ gkh);
+ return gkh;
}
-/**
- * Disconnect from the exchange
- *
- * @param exchange the exchange handle
- */
void
-TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange)
+TALER_EXCHANGE_get_keys_cancel (
+ struct TALER_EXCHANGE_GetKeysHandle *gkh)
{
- struct TEAH_AuditorListEntry *ale;
-
- while (NULL != (ale = exchange->auditors_head))
+ if (NULL != gkh->job)
{
- struct TEAH_AuditorInteractionEntry *aie;
-
- while (NULL != (aie = ale->ai_head))
- {
- GNUNET_assert (aie->ale == ale);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not sending deposit confirmation to auditor `%s' due to exchange disconnect\n",
- ale->auditor_url);
- TALER_AUDITOR_deposit_confirmation_cancel (aie->dch);
- GNUNET_CONTAINER_DLL_remove (ale->ai_head,
- ale->ai_tail,
- aie);
- GNUNET_free (aie);
- }
- GNUNET_CONTAINER_DLL_remove (exchange->auditors_head,
- exchange->auditors_tail,
- ale);
- TALER_LOG_DEBUG ("Disconnecting the auditor `%s'\n",
- ale->auditor_url);
- TALER_AUDITOR_disconnect (ale->ah);
- GNUNET_free (ale->auditor_url);
- GNUNET_free (ale);
- }
- if (NULL != exchange->kr)
- {
- GNUNET_CURL_job_cancel (exchange->kr->job);
- free_keys_request (exchange->kr);
- exchange->kr = NULL;
- }
- free_key_data (&exchange->key_data);
- if (NULL != exchange->key_data_raw)
- {
- json_decref (exchange->key_data_raw);
- exchange->key_data_raw = NULL;
+ GNUNET_CURL_job_cancel (gkh->job);
+ gkh->job = NULL;
}
- if (NULL != exchange->retry_task)
- {
- GNUNET_SCHEDULER_cancel (exchange->retry_task);
- exchange->retry_task = NULL;
- }
- GNUNET_free (exchange->url);
- GNUNET_free (exchange);
+ TALER_EXCHANGE_keys_decref (gkh->prev_keys);
+ GNUNET_free (gkh->exchange_url);
+ GNUNET_free (gkh->url);
+ GNUNET_free (gkh);
}
-/**
- * Test if the given @a pub is a the current signing key from the exchange
- * according to @a keys.
- *
- * @param keys the exchange's key set
- * @param pub claimed current online signing key for the exchange
- * @return #GNUNET_OK if @a pub is (according to /keys) a current signing key
- */
-int
-TALER_EXCHANGE_test_signing_key (const struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_ExchangePublicKeyP *pub)
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_test_signing_key (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ExchangePublicKeyP *pub)
{
struct GNUNET_TIME_Absolute now;
/* we will check using a tolerance of 1h for the time */
now = GNUNET_TIME_absolute_get ();
for (unsigned int i = 0; i<keys->num_sign_keys; i++)
- if ( (keys->sign_keys[i].valid_from.abs_value_us <= now.abs_value_us + 60
- * 60 * 1000LL * 1000LL) &&
- (keys->sign_keys[i].valid_until.abs_value_us > now.abs_value_us - 60
- * 60 * 1000LL * 1000LL) &&
+ if ( (GNUNET_TIME_absolute_cmp (
+ keys->sign_keys[i].valid_from.abs_time,
+ <=,
+ GNUNET_TIME_absolute_add (now,
+ LIFETIME_TOLERANCE))) &&
+ (GNUNET_TIME_absolute_cmp (
+ keys->sign_keys[i].valid_until.abs_time,
+ >,
+ GNUNET_TIME_absolute_subtract (now,
+ LIFETIME_TOLERANCE))) &&
(0 == GNUNET_memcmp (pub,
&keys->sign_keys[i].key)) )
return GNUNET_OK;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Signing key not valid at time %s\n",
+ GNUNET_TIME_absolute2s (now));
return GNUNET_SYSERR;
}
-/**
- * Get exchange's base URL.
- *
- * @param exchange exchange handle.
- * @return the base URL from the handle.
- */
-const char *
-TALER_EXCHANGE_get_base_url (const struct TALER_EXCHANGE_Handle *exchange)
-{
- return exchange->url;
-}
-
-
-/**
- * Obtain the denomination key details from the exchange.
- *
- * @param keys the exchange's key set
- * @param pk public key of the denomination to lookup
- * @return details about the given denomination key, NULL if the key is
- * not found
- */
const struct TALER_EXCHANGE_DenomPublicKey *
TALER_EXCHANGE_get_denomination_key (
const struct TALER_EXCHANGE_Keys *keys,
const struct TALER_DenominationPublicKey *pk)
{
for (unsigned int i = 0; i<keys->num_denom_keys; i++)
- if (0 == GNUNET_CRYPTO_rsa_public_key_cmp (pk->rsa_public_key,
- keys->denom_keys[i].key.
- rsa_public_key))
+ if (0 ==
+ TALER_denom_pub_cmp (pk,
+ &keys->denom_keys[i].key))
return &keys->denom_keys[i];
return NULL;
}
-/**
- * Create a copy of a denomination public key.
- *
- * @param key key to copy
- * @returns a copy, must be freed with #TALER_EXCHANGE_destroy_denomination_key
- */
+const struct TALER_EXCHANGE_GlobalFee *
+TALER_EXCHANGE_get_global_fee (
+ const struct TALER_EXCHANGE_Keys *keys,
+ struct GNUNET_TIME_Timestamp ts)
+{
+ for (unsigned int i = 0; i<keys->num_global_fees; i++)
+ {
+ const struct TALER_EXCHANGE_GlobalFee *gf = &keys->global_fees[i];
+
+ if (GNUNET_TIME_timestamp_cmp (ts,
+ >=,
+ gf->start_date) &&
+ GNUNET_TIME_timestamp_cmp (ts,
+ <,
+ gf->end_date))
+ return gf;
+ }
+ return NULL;
+}
+
+
struct TALER_EXCHANGE_DenomPublicKey *
TALER_EXCHANGE_copy_denomination_key (
const struct TALER_EXCHANGE_DenomPublicKey *key)
@@ -2159,39 +1817,25 @@ TALER_EXCHANGE_copy_denomination_key (
copy = GNUNET_new (struct TALER_EXCHANGE_DenomPublicKey);
*copy = *key;
- copy->key.rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (
- key->key.rsa_public_key);
-
+ TALER_denom_pub_copy (&copy->key,
+ &key->key);
return copy;
}
-/**
- * Destroy a denomination public key.
- * Should only be called with keys created by #TALER_EXCHANGE_copy_denomination_key.
- *
- * @param key key to destroy.
- */
void
TALER_EXCHANGE_destroy_denomination_key (
struct TALER_EXCHANGE_DenomPublicKey *key)
{
- GNUNET_CRYPTO_rsa_public_key_free (key->key.rsa_public_key);;
+ TALER_denom_pub_free (&key->key);
GNUNET_free (key);
}
-/**
- * Obtain the denomination key details from the exchange.
- *
- * @param keys the exchange's key set
- * @param hc hash of the public key of the denomination to lookup
- * @return details about the given denomination key
- */
const struct TALER_EXCHANGE_DenomPublicKey *
TALER_EXCHANGE_get_denomination_key_by_hash (
const struct TALER_EXCHANGE_Keys *keys,
- const struct GNUNET_HashCode *hc)
+ const struct TALER_DenominationHashP *hc)
{
for (unsigned int i = 0; i<keys->num_denom_keys; i++)
if (0 == GNUNET_memcmp (hc,
@@ -2201,36 +1845,627 @@ TALER_EXCHANGE_get_denomination_key_by_hash (
}
+struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_keys_incref (struct TALER_EXCHANGE_Keys *keys)
+{
+ GNUNET_assert (keys->rc < UINT_MAX);
+ keys->rc++;
+ return keys;
+}
+
+
+void
+TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys)
+{
+ if (NULL == keys)
+ return;
+ GNUNET_assert (0 < keys->rc);
+ keys->rc--;
+ if (0 != keys->rc)
+ return;
+ GNUNET_array_grow (keys->sign_keys,
+ keys->num_sign_keys,
+ 0);
+ for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+ TALER_denom_pub_free (&keys->denom_keys[i].key);
+
+ GNUNET_array_grow (keys->denom_keys,
+ keys->denom_keys_size,
+ 0);
+ for (unsigned int i = 0; i<keys->num_auditors; i++)
+ {
+ GNUNET_array_grow (keys->auditors[i].denom_keys,
+ keys->auditors[i].num_denom_keys,
+ 0);
+ GNUNET_free (keys->auditors[i].auditor_url);
+ }
+ GNUNET_array_grow (keys->auditors,
+ keys->auditors_size,
+ 0);
+ TALER_EXCHANGE_free_accounts (keys->accounts_len,
+ keys->accounts);
+ GNUNET_array_grow (keys->accounts,
+ keys->accounts_len,
+ 0);
+ free_fees (keys->fees,
+ keys->fees_len);
+ json_decref (keys->extensions);
+ GNUNET_free (keys->cspec.name);
+ json_decref (keys->cspec.map_alt_unit_names);
+ GNUNET_free (keys->wallet_balance_limit_without_kyc);
+ GNUNET_free (keys->version);
+ GNUNET_free (keys->currency);
+ GNUNET_free (keys->asset_type);
+ GNUNET_free (keys->global_fees);
+ GNUNET_free (keys->exchange_url);
+ GNUNET_free (keys);
+}
+
+
+struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_keys_from_json (const json_t *j)
+{
+ const json_t *jkeys;
+ const char *url;
+ uint32_t version;
+ struct GNUNET_TIME_Timestamp expire
+ = GNUNET_TIME_UNIT_ZERO_TS;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint32 ("version",
+ &version),
+ GNUNET_JSON_spec_object_const ("keys",
+ &jkeys),
+ TALER_JSON_spec_web_url ("exchange_url",
+ &url),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("expire",
+ &expire),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TALER_EXCHANGE_Keys *keys;
+ enum TALER_EXCHANGE_VersionCompatibility compat;
+
+ if (NULL == j)
+ return NULL;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ if (0 != version)
+ {
+ return NULL; /* unsupported version */
+ }
+ keys = GNUNET_new (struct TALER_EXCHANGE_Keys);
+ if (GNUNET_OK !=
+ decode_keys_json (jkeys,
+ false,
+ keys,
+ &compat))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ keys->rc = 1;
+ keys->key_data_expiration = expire;
+ keys->exchange_url = GNUNET_strdup (url);
+ return keys;
+}
+
+
/**
- * Obtain the keys from the exchange.
+ * Data we track per denomination group.
+ */
+struct GroupData
+{
+ /**
+ * The json blob with the group meta-data and list of denominations
+ */
+ json_t *json;
+
+ /**
+ * Meta data for this group.
+ */
+ struct TALER_DenominationGroup meta;
+};
+
+
+/**
+ * Add denomination group represented by @a value
+ * to list of denominations in @a cls. Also frees
+ * the @a value.
*
- * @param exchange the exchange handle
- * @return the exchange's key set
+ * @param[in,out] cls a `json_t *` with an array to build
+ * @param key unused
+ * @param value a `struct GroupData *`
+ * @return #GNUNET_OK (continue to iterate)
*/
-const struct TALER_EXCHANGE_Keys *
-TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange)
+static enum GNUNET_GenericReturnValue
+add_grp (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
{
- (void) TALER_EXCHANGE_check_keys_current (exchange,
- GNUNET_NO,
- GNUNET_NO);
- return &exchange->key_data;
+ json_t *denominations_by_group = cls;
+ struct GroupData *gd = value;
+ const char *cipher;
+ json_t *ge;
+ bool age_restricted = gd->meta.age_mask.bits != 0;
+
+ (void) key;
+ switch (gd->meta.cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ cipher = age_restricted ? "RSA+age_restricted" : "RSA";
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ cipher = age_restricted ? "CS+age_restricted" : "CS";
+ break;
+ default:
+ GNUNET_assert (false);
+ }
+
+ ge = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ cipher),
+ GNUNET_JSON_pack_array_steal ("denoms",
+ gd->json),
+ TALER_JSON_PACK_DENOM_FEES ("fee",
+ &gd->meta.fees),
+ GNUNET_JSON_pack_allow_null (
+ age_restricted
+ ? GNUNET_JSON_pack_uint64 ("age_mask",
+ gd->meta.age_mask.bits)
+ : GNUNET_JSON_pack_string ("dummy",
+ NULL)),
+ TALER_JSON_pack_amount ("value",
+ &gd->meta.value));
+ GNUNET_assert (0 ==
+ json_array_append_new (denominations_by_group,
+ ge));
+ GNUNET_free (gd);
+ return GNUNET_OK;
}
/**
- * Obtain the keys from the exchange in the
- * raw JSON format
+ * Convert array of account restrictions @a ars to JSON.
*
- * @param exchange the exchange handle
- * @return the exchange's keys in raw JSON
+ * @param ar_len length of @a ars
+ * @param ars account restrictions to convert
+ * @return JSON representation
*/
+static json_t *
+ar_to_json (unsigned int ar_len,
+ const struct TALER_EXCHANGE_AccountRestriction ars[static ar_len])
+{
+ json_t *rval;
+
+ rval = json_array ();
+ GNUNET_assert (NULL != rval);
+ for (unsigned int i = 0; i<ar_len; i++)
+ {
+ const struct TALER_EXCHANGE_AccountRestriction *ar = &ars[i];
+
+ switch (ar->type)
+ {
+ case TALER_EXCHANGE_AR_INVALID:
+ GNUNET_break (0);
+ json_decref (rval);
+ return NULL;
+ case TALER_EXCHANGE_AR_DENY:
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ rval,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "deny"))));
+ break;
+ case TALER_EXCHANGE_AR_REGEX:
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ rval,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string (
+ "type",
+ "regex"),
+ GNUNET_JSON_pack_string (
+ "payto_regex",
+ ar->details.regex.posix_egrep),
+ GNUNET_JSON_pack_string (
+ "human_hint",
+ ar->details.regex.human_hint),
+ GNUNET_JSON_pack_object_incref (
+ "human_hint_i18n",
+ (json_t *) ar->details.regex.human_hint_i18n)
+ )));
+ break;
+ }
+ }
+ return rval;
+}
+
+
json_t *
-TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange)
+TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd)
{
- (void) TALER_EXCHANGE_check_keys_current (exchange,
- GNUNET_NO,
- GNUNET_NO);
- return json_deep_copy (exchange->key_data_raw);
+ struct GNUNET_TIME_Timestamp now;
+ json_t *keys;
+ json_t *signkeys;
+ json_t *denominations_by_group;
+ json_t *auditors;
+ json_t *recoup;
+ json_t *wire_fees;
+ json_t *accounts;
+ json_t *global_fees;
+ json_t *wblwk = NULL;
+
+ now = GNUNET_TIME_timestamp_get ();
+ signkeys = json_array ();
+ GNUNET_assert (NULL != signkeys);
+ for (unsigned int i = 0; i<kd->num_sign_keys; i++)
+ {
+ const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i];
+ json_t *signkey;
+
+ if (GNUNET_TIME_timestamp_cmp (now,
+ >,
+ sk->valid_until))
+ continue; /* skip keys that have expired */
+ signkey = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("key",
+ &sk->key),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &sk->master_sig),
+ GNUNET_JSON_pack_timestamp ("stamp_start",
+ sk->valid_from),
+ GNUNET_JSON_pack_timestamp ("stamp_expire",
+ sk->valid_until),
+ GNUNET_JSON_pack_timestamp ("stamp_end",
+ sk->valid_legal));
+ GNUNET_assert (NULL != signkey);
+ GNUNET_assert (0 ==
+ json_array_append_new (signkeys,
+ signkey));
+ }
+
+ denominations_by_group = json_array ();
+ GNUNET_assert (NULL != denominations_by_group);
+ {
+ struct GNUNET_CONTAINER_MultiHashMap *dbg;
+
+ dbg = GNUNET_CONTAINER_multihashmap_create (128,
+ false);
+ for (unsigned int i = 0; i<kd->num_denom_keys; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i];
+ struct TALER_DenominationGroup meta = {
+ .cipher = dk->key.bsign_pub_key->cipher,
+ .value = dk->value,
+ .fees = dk->fees,
+ .age_mask = dk->key.age_mask
+ };
+ struct GNUNET_HashCode key;
+ struct GroupData *gd;
+ json_t *denom;
+ struct GNUNET_JSON_PackSpec key_spec;
+
+ if (GNUNET_TIME_timestamp_cmp (now,
+ >,
+ dk->expire_deposit))
+ continue; /* skip keys that have expired */
+ TALER_denomination_group_get_key (&meta,
+ &key);
+ gd = GNUNET_CONTAINER_multihashmap_get (dbg,
+ &key);
+ if (NULL == gd)
+ {
+ gd = GNUNET_new (struct GroupData);
+ gd->meta = meta;
+ gd->json = json_array ();
+ GNUNET_assert (NULL != gd->json);
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (dbg,
+ &key,
+ gd,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+
+ }
+ switch (meta.cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ key_spec =
+ GNUNET_JSON_pack_rsa_public_key (
+ "rsa_pub",
+ dk->key.bsign_pub_key->details.rsa_public_key);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ key_spec =
+ GNUNET_JSON_pack_data_varsize (
+ "cs_pub",
+ &dk->key.bsign_pub_key->details.cs_public_key,
+ sizeof (dk->key.bsign_pub_key->details.cs_public_key));
+ break;
+ default:
+ GNUNET_assert (false);
+ }
+ denom = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
+ dk->expire_deposit),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
+ dk->withdraw_valid_until),
+ GNUNET_JSON_pack_timestamp ("stamp_start",
+ dk->valid_from),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
+ dk->expire_legal),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &dk->master_sig),
+ key_spec
+ );
+ GNUNET_assert (0 ==
+ json_array_append_new (gd->json,
+ denom));
+ }
+ GNUNET_CONTAINER_multihashmap_iterate (dbg,
+ &add_grp,
+ denominations_by_group);
+ GNUNET_CONTAINER_multihashmap_destroy (dbg);
+ }
+
+ auditors = json_array ();
+ GNUNET_assert (NULL != auditors);
+ for (unsigned int i = 0; i<kd->num_auditors; i++)
+ {
+ const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i];
+ json_t *a;
+ json_t *adenoms;
+
+ adenoms = json_array ();
+ GNUNET_assert (NULL != adenoms);
+ for (unsigned int j = 0; j<ai->num_denom_keys; j++)
+ {
+ const struct TALER_EXCHANGE_AuditorDenominationInfo *adi =
+ &ai->denom_keys[j];
+ const struct TALER_EXCHANGE_DenomPublicKey *dk =
+ &kd->denom_keys[adi->denom_key_offset];
+ json_t *k;
+
+ GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys);
+ if (GNUNET_TIME_timestamp_cmp (now,
+ >,
+ dk->expire_deposit))
+ continue; /* skip auditor signatures for denomination keys that have expired */
+ GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys);
+ k = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denom_pub_h",
+ &dk->h_key),
+ GNUNET_JSON_pack_data_auto ("auditor_sig",
+ &adi->auditor_sig));
+ GNUNET_assert (0 ==
+ json_array_append_new (adenoms,
+ k));
+ }
+
+ a = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("auditor_pub",
+ &ai->auditor_pub),
+ GNUNET_JSON_pack_string ("auditor_url",
+ ai->auditor_url),
+ GNUNET_JSON_pack_array_steal ("denomination_keys",
+ adenoms));
+ GNUNET_assert (0 ==
+ json_array_append_new (auditors,
+ a));
+ }
+
+ global_fees = json_array ();
+ GNUNET_assert (NULL != global_fees);
+ for (unsigned int i = 0; i<kd->num_global_fees; i++)
+ {
+ const struct TALER_EXCHANGE_GlobalFee *gf
+ = &kd->global_fees[i];
+
+ if (GNUNET_TIME_absolute_is_past (gf->end_date.abs_time))
+ continue;
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ global_fees,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_timestamp ("start_date",
+ gf->start_date),
+ GNUNET_JSON_pack_timestamp ("end_date",
+ gf->end_date),
+ TALER_JSON_PACK_GLOBAL_FEES (&gf->fees),
+ GNUNET_JSON_pack_time_rel ("history_expiration",
+ gf->history_expiration),
+ GNUNET_JSON_pack_time_rel ("purse_timeout",
+ gf->purse_timeout),
+ GNUNET_JSON_pack_uint64 ("purse_account_limit",
+ gf->purse_account_limit),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &gf->master_sig))));
+ }
+
+ accounts = json_array ();
+ GNUNET_assert (NULL != accounts);
+ for (unsigned int i = 0; i<kd->accounts_len; i++)
+ {
+ const struct TALER_EXCHANGE_WireAccount *acc
+ = &kd->accounts[i];
+ json_t *credit_restrictions;
+ json_t *debit_restrictions;
+
+ credit_restrictions
+ = ar_to_json (acc->credit_restrictions_length,
+ acc->credit_restrictions);
+ GNUNET_assert (NULL != credit_restrictions);
+ debit_restrictions
+ = ar_to_json (acc->debit_restrictions_length,
+ acc->debit_restrictions);
+ GNUNET_assert (NULL != debit_restrictions);
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ accounts,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ acc->payto_uri),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("conversion_url",
+ acc->conversion_url)),
+ GNUNET_JSON_pack_int64 ("priority",
+ acc->priority),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("bank_label",
+ acc->bank_label)),
+ GNUNET_JSON_pack_array_steal ("debit_restrictions",
+ debit_restrictions),
+ GNUNET_JSON_pack_array_steal ("credit_restrictions",
+ credit_restrictions),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &acc->master_sig))));
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Serialized %u/%u wire accounts to JSON\n",
+ (unsigned int) json_array_size (accounts),
+ kd->accounts_len);
+
+ wire_fees = json_object ();
+ GNUNET_assert (NULL != wire_fees);
+ for (unsigned int i = 0; i<kd->fees_len; i++)
+ {
+ const struct TALER_EXCHANGE_WireFeesByMethod *fbw
+ = &kd->fees[i];
+ json_t *wf;
+
+ wf = json_array ();
+ GNUNET_assert (NULL != wf);
+ for (struct TALER_EXCHANGE_WireAggregateFees *p = fbw->fees_head;
+ NULL != p;
+ p = p->next)
+ {
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ wf,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("wire_fee",
+ &p->fees.wire),
+ TALER_JSON_pack_amount ("closing_fee",
+ &p->fees.closing),
+ GNUNET_JSON_pack_timestamp ("start_date",
+ p->start_date),
+ GNUNET_JSON_pack_timestamp ("end_date",
+ p->end_date),
+ GNUNET_JSON_pack_data_auto ("sig",
+ &p->master_sig))));
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (wire_fees,
+ fbw->method,
+ wf));
+ }
+
+ recoup = json_array ();
+ GNUNET_assert (NULL != recoup);
+ for (unsigned int i = 0; i<kd->num_denom_keys; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dk
+ = &kd->denom_keys[i];
+ if (! dk->revoked)
+ continue;
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ recoup,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &dk->h_key))));
+ }
+
+ wblwk = json_array ();
+ GNUNET_assert (NULL != wblwk);
+ for (unsigned int i = 0; i<kd->wblwk_length; i++)
+ {
+ const struct TALER_Amount *a = &kd->wallet_balance_limit_without_kyc[i];
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ wblwk,
+ TALER_JSON_from_amount (a)));
+ }
+
+ keys = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("version",
+ kd->version),
+ GNUNET_JSON_pack_string ("currency",
+ kd->currency),
+ GNUNET_JSON_pack_object_steal ("currency_specification",
+ TALER_CONFIG_currency_specs_to_json (
+ &kd->cspec)),
+ TALER_JSON_pack_amount ("stefan_abs",
+ &kd->stefan_abs),
+ TALER_JSON_pack_amount ("stefan_log",
+ &kd->stefan_log),
+ GNUNET_JSON_pack_double ("stefan_lin",
+ kd->stefan_lin),
+ GNUNET_JSON_pack_string ("asset_type",
+ kd->asset_type),
+ GNUNET_JSON_pack_data_auto ("master_public_key",
+ &kd->master_pub),
+ GNUNET_JSON_pack_time_rel ("reserve_closing_delay",
+ kd->reserve_closing_delay),
+ GNUNET_JSON_pack_timestamp ("list_issue_date",
+ kd->list_issue_date),
+ GNUNET_JSON_pack_array_steal ("global_fees",
+ global_fees),
+ GNUNET_JSON_pack_array_steal ("signkeys",
+ signkeys),
+ GNUNET_JSON_pack_object_steal ("wire_fees",
+ wire_fees),
+ GNUNET_JSON_pack_array_steal ("accounts",
+ accounts),
+ GNUNET_JSON_pack_array_steal ("wads",
+ json_array ()),
+ GNUNET_JSON_pack_array_steal ("denominations",
+ denominations_by_group),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("recoup",
+ recoup)),
+ GNUNET_JSON_pack_array_steal ("auditors",
+ auditors),
+ GNUNET_JSON_pack_bool ("rewards_allowed",
+ kd->rewards_allowed),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("extensions",
+ kd->extensions)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_is_zero (&kd->extensions_sig)
+ ? GNUNET_JSON_pack_string ("dummy",
+ NULL)
+ : GNUNET_JSON_pack_data_auto ("extensions_sig",
+ &kd->extensions_sig)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("wallet_balance_limit_without_kyc",
+ wblwk))
+
+ );
+ return GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("version",
+ EXCHANGE_SERIALIZATION_FORMAT_VERSION),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("expire",
+ kd->key_data_expiration)),
+ GNUNET_JSON_pack_string ("exchange_url",
+ kd->exchange_url),
+ GNUNET_JSON_pack_object_steal ("keys",
+ keys));
}
diff --git a/src/lib/exchange_api_handle.h b/src/lib/exchange_api_handle.h
index 63f789c8e..7c01b9a9f 100644
--- a/src/lib/exchange_api_handle.h
+++ b/src/lib/exchange_api_handle.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014, 2015 Taler Systems SA
+ Copyright (C) 2014, 2015, 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
@@ -19,75 +19,29 @@
* @brief Internal interface to the handle part of the exchange's HTTP API
* @author Christian Grothoff
*/
-#include "platform.h"
+#ifndef EXCHANGE_API_HANDLE_H
+#define EXCHANGE_API_HANDLE_H
+
#include <gnunet/gnunet_curl_lib.h>
#include "taler_auditor_service.h"
#include "taler_exchange_service.h"
-#include "taler_crypto_lib.h"
+#include "taler_util.h"
#include "taler_curl_lib.h"
-/**
- * Entry in DLL of auditors used by an exchange.
- */
-struct TEAH_AuditorListEntry;
-
-
-/**
- * Entry in list of ongoing interactions with an auditor.
- */
-struct TEAH_AuditorInteractionEntry
-{
- /**
- * DLL entry.
- */
- struct TEAH_AuditorInteractionEntry *next;
-
- /**
- * DLL entry.
- */
- struct TEAH_AuditorInteractionEntry *prev;
-
- /**
- * Which auditor is this action associated with?
- */
- struct TEAH_AuditorListEntry *ale;
-
- /**
- * Interaction state.
- */
- struct TALER_AUDITOR_DepositConfirmationHandle *dch;
-};
-
/**
* Function called for each auditor to give us a chance to possibly
* launch a deposit confirmation interaction.
*
* @param cls closure
- * @param ah handle to the auditor
+ * @param auditor_url base URL of the auditor
* @param auditor_pub public key of the auditor
- * @return NULL if no deposit confirmation interaction was launched
*/
-typedef struct TEAH_AuditorInteractionEntry *
-(*TEAH_AuditorCallback)(void *cls,
- struct TALER_AUDITOR_Handle *ah,
- const struct TALER_AuditorPublicKeyP *auditor_pub);
-
-
-/**
- * Signature of functions called with the result from our call to the
- * auditor's /deposit-confirmation handler.
- *
- * @param cls closure of type `struct TEAH_AuditorInteractionEntry *`
- * @param http_status HTTP status code, 200 on success
- * @param ec taler protocol error status code, 0 on success
- * @param json raw json response
- */
-void
-TEAH_acc_confirmation_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const json_t *json);
+typedef void
+(*TEAH_AuditorCallback)(
+ void *cls,
+ const char *auditor_url,
+ const struct TALER_AuditorPublicKeyP *auditor_pub);
/**
@@ -95,45 +49,16 @@ TEAH_acc_confirmation_cb (void *cls,
* @a ac and giving it a chance to start a deposit
* confirmation interaction.
*
- * @param h exchange to go over auditors for
+ * @param keys the keys to go over auditors for
* @param ac function to call per auditor
* @param ac_cls closure for @a ac
*/
void
-TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h,
- TEAH_AuditorCallback ac,
- void *ac_cls);
-
-
-/**
- * Get the context of a exchange.
- *
- * @param h the exchange handle to query
- * @return ctx context to execute jobs in
- */
-struct GNUNET_CURL_Context *
-TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h);
+TEAH_get_auditors_for_dc (
+ struct TALER_EXCHANGE_Keys *keys,
+ TEAH_AuditorCallback ac,
+ void *ac_cls);
-/**
- * Check if the handle is ready to process requests.
- *
- * @param h the exchange handle to query
- * @return #GNUNET_YES if we are ready, #GNUNET_NO if not
- */
-int
-TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h);
-
-
-/**
- * Obtain the URL to use for an API request.
- *
- * @param h the exchange handle to query
- * @param path Taler API path (i.e. "/reserve/withdraw")
- * @return the full URL to use with cURL
- */
-char *
-TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h,
- const char *path);
-
/* end of exchange_api_handle.h */
+#endif
diff --git a/src/lib/exchange_api_kyc_check.c b/src/lib/exchange_api_kyc_check.c
new file mode 100644
index 000000000..5d3b3792b
--- /dev/null
+++ b/src/lib/exchange_api_kyc_check.c
@@ -0,0 +1,321 @@
+/*
+ This file is part of TALER
+ 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 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 lib/exchange_api_kyc_check.c
+ * @brief Implementation of the /kyc-check request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP check codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A ``/kyc-check`` handle
+ */
+struct TALER_EXCHANGE_KycCheckHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Keys of the exchange.
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_KycStatusCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Hash of the payto:// URL that is being KYC'ed.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /kyc-check request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_KycCheckHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_kyc_check_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_KycCheckHandle *kch = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_KycStatus ks = {
+ .http_status = (unsigned int) response_code
+ };
+
+ kch->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ const json_t *kyc_details;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &ks.details.ok.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &ks.details.ok.exchange_pub),
+ GNUNET_JSON_spec_timestamp ("now",
+ &ks.details.ok.timestamp),
+ GNUNET_JSON_spec_object_const ("kyc_details",
+ &kyc_details),
+ TALER_JSON_spec_aml_decision ("aml_status",
+ &ks.details.ok.aml_status),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ks.http_status = 0;
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ ks.details.ok.kyc_details = kyc_details;
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (kch->keys,
+ &ks.details.ok.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ ks.http_status = 0;
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ GNUNET_JSON_parse_free (spec);
+ break;
+ }
+
+ if (GNUNET_OK !=
+ TALER_exchange_online_account_setup_success_verify (
+ &kch->h_payto,
+ ks.details.ok.kyc_details,
+ ks.details.ok.timestamp,
+ &ks.details.ok.exchange_pub,
+ &ks.details.ok.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ ks.http_status = 0;
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ GNUNET_JSON_parse_free (spec);
+ break;
+ }
+ kch->cb (kch->cb_cls,
+ &ks);
+ GNUNET_JSON_parse_free (spec);
+ TALER_EXCHANGE_kyc_check_cancel (kch);
+ return;
+ }
+ case MHD_HTTP_ACCEPTED:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_web_url ("kyc_url",
+ &ks.details.accepted.kyc_url),
+ TALER_JSON_spec_aml_decision ("aml_status",
+ &ks.details.accepted.aml_status),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ks.http_status = 0;
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ kch->cb (kch->cb_cls,
+ &ks);
+ GNUNET_JSON_parse_free (spec);
+ TALER_EXCHANGE_kyc_check_cancel (kch);
+ return;
+ }
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ ks.ec = TALER_JSON_get_error_code (j);
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ ks.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ ks.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_aml_decision (
+ "aml_status",
+ &ks.details.unavailable_for_legal_reasons.aml_status),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ks.http_status = 0;
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ kch->cb (kch->cb_cls,
+ &ks);
+ GNUNET_JSON_parse_free (spec);
+ TALER_EXCHANGE_kyc_check_cancel (kch);
+ return;
+ }
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ ks.ec = TALER_JSON_get_error_code (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ ks.ec = TALER_JSON_get_error_code (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange kyc_check\n",
+ (unsigned int) response_code,
+ (int) ks.ec);
+ break;
+ }
+ kch->cb (kch->cb_cls,
+ &ks);
+ TALER_EXCHANGE_kyc_check_cancel (kch);
+}
+
+
+struct TALER_EXCHANGE_KycCheckHandle *
+TALER_EXCHANGE_kyc_check (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ uint64_t requirement_row,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_KYCLOGIC_KycUserType ut,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_EXCHANGE_KycStatusCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_KycCheckHandle *kch;
+ CURL *eh;
+ char *arg_str;
+
+ {
+ char payto_str[sizeof (*h_payto) * 2];
+ char *end;
+ unsigned long long timeout_ms;
+
+ end = GNUNET_STRINGS_data_to_string (
+ h_payto,
+ sizeof (*h_payto),
+ payto_str,
+ sizeof (payto_str) - 1);
+ *end = '\0';
+ timeout_ms = timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
+ GNUNET_asprintf (&arg_str,
+ "kyc-check/%llu/%s/%s?timeout_ms=%llu",
+ (unsigned long long) requirement_row,
+ payto_str,
+ TALER_KYCLOGIC_kyc_user_type2s (ut),
+ timeout_ms);
+ }
+ kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle);
+ kch->h_payto = *h_payto;
+ kch->cb = cb;
+ kch->cb_cls = cb_cls;
+ kch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ GNUNET_free (arg_str);
+ if (NULL == kch->url)
+ {
+ GNUNET_free (kch);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (kch->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (kch->url);
+ GNUNET_free (kch);
+ return NULL;
+ }
+ kch->keys = TALER_EXCHANGE_keys_incref (keys);
+ kch->job = GNUNET_CURL_job_add_with_ct_json (ctx,
+ eh,
+ &handle_kyc_check_finished,
+ kch);
+ return kch;
+}
+
+
+void
+TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kch)
+{
+ if (NULL != kch->job)
+ {
+ GNUNET_CURL_job_cancel (kch->job);
+ kch->job = NULL;
+ }
+ TALER_EXCHANGE_keys_decref (kch->keys);
+ GNUNET_free (kch->url);
+ GNUNET_free (kch);
+}
+
+
+/* end of exchange_api_kyc_check.c */
diff --git a/src/lib/exchange_api_kyc_proof.c b/src/lib/exchange_api_kyc_proof.c
new file mode 100644
index 000000000..e7cc9c4cf
--- /dev/null
+++ b/src/lib/exchange_api_kyc_proof.c
@@ -0,0 +1,217 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021, 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 lib/exchange_api_kyc_proof.c
+ * @brief Implementation of the /kyc-proof request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP proof codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A ``/kyc-proof`` handle
+ */
+struct TALER_EXCHANGE_KycProofHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle to our CURL request.
+ */
+ CURL *eh;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_KycProofCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /kyc-proof request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_KycProofHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param body response body
+ * @param body_size number of bytes in @a body
+ */
+static void
+handle_kyc_proof_finished (void *cls,
+ long response_code,
+ const void *body,
+ size_t body_size)
+{
+ struct TALER_EXCHANGE_KycProofHandle *kph = cls;
+ struct TALER_EXCHANGE_KycProofResponse kpr = {
+ .http_status = (unsigned int) response_code
+ };
+
+ (void) body;
+ (void) body_size;
+ kph->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_SEE_OTHER:
+ {
+ char *redirect_url;
+
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_getinfo (kph->eh,
+ CURLINFO_REDIRECT_URL,
+ &redirect_url));
+ kpr.details.found.redirect_url = redirect_url;
+ break;
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ break;
+ case MHD_HTTP_BAD_GATEWAY:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ case MHD_HTTP_GATEWAY_TIMEOUT:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u for exchange kyc_proof\n",
+ (unsigned int) response_code);
+ break;
+ }
+ kph->cb (kph->cb_cls,
+ &kpr);
+ TALER_EXCHANGE_kyc_proof_cancel (kph);
+}
+
+
+struct TALER_EXCHANGE_KycProofHandle *
+TALER_EXCHANGE_kyc_proof (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *logic,
+ const char *args,
+ TALER_EXCHANGE_KycProofCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_KycProofHandle *kph;
+ char *arg_str;
+
+ if (NULL == args)
+ args = "";
+ else
+ GNUNET_assert (args[0] == '&');
+ {
+ char hstr[sizeof (struct TALER_PaytoHashP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (h_payto,
+ sizeof (*h_payto),
+ hstr,
+ sizeof (hstr));
+ *end = '\0';
+ GNUNET_asprintf (&arg_str,
+ "kyc-proof/%s?state=%s%s",
+ logic,
+ hstr,
+ args);
+ }
+ kph = GNUNET_new (struct TALER_EXCHANGE_KycProofHandle);
+ kph->cb = cb;
+ kph->cb_cls = cb_cls;
+ kph->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ GNUNET_free (arg_str);
+ if (NULL == kph->url)
+ {
+ GNUNET_free (kph);
+ return NULL;
+ }
+ kph->eh = TALER_EXCHANGE_curl_easy_get_ (kph->url);
+ if (NULL == kph->eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (kph->url);
+ GNUNET_free (kph);
+ return NULL;
+ }
+ /* disable location following, we want to learn the
+ result of a 303 redirect! */
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (kph->eh,
+ CURLOPT_FOLLOWLOCATION,
+ 0L));
+ kph->job = GNUNET_CURL_job_add_raw (ctx,
+ kph->eh,
+ NULL,
+ &handle_kyc_proof_finished,
+ kph);
+ return kph;
+}
+
+
+void
+TALER_EXCHANGE_kyc_proof_cancel (struct TALER_EXCHANGE_KycProofHandle *kph)
+{
+ if (NULL != kph->job)
+ {
+ GNUNET_CURL_job_cancel (kph->job);
+ kph->job = NULL;
+ }
+ GNUNET_free (kph->url);
+ GNUNET_free (kph);
+}
+
+
+/* end of exchange_api_kyc_proof.c */
diff --git a/src/lib/exchange_api_kyc_wallet.c b/src/lib/exchange_api_kyc_wallet.c
new file mode 100644
index 000000000..7197694ae
--- /dev/null
+++ b/src/lib/exchange_api_kyc_wallet.c
@@ -0,0 +1,230 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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 lib/exchange_api_kyc_wallet.c
+ * @brief Implementation of the /kyc-wallet request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP wallet codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A ``/kyc-wallet`` handle
+ */
+struct TALER_EXCHANGE_KycWalletHandle
+{
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_KycWalletCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /kyc-wallet request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_KycWalletHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_kyc_wallet_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_KycWalletHandle *kwh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_WalletKycResponse ks = {
+ .http_status = (unsigned int) response_code
+ };
+
+ kwh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ ks.ec = TALER_JSON_get_error_code (j);
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ ks.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ ks.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &ks.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &ks.details.unavailable_for_legal_reasons.requirement_row),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ks.http_status = 0;
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ break;
+ }
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ ks.ec = TALER_JSON_get_error_code (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ ks.ec = TALER_JSON_get_error_code (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange /kyc-wallet\n",
+ (unsigned int) response_code,
+ (int) ks.ec);
+ break;
+ }
+ kwh->cb (kwh->cb_cls,
+ &ks);
+ TALER_EXCHANGE_kyc_wallet_cancel (kwh);
+}
+
+
+struct TALER_EXCHANGE_KycWalletHandle *
+TALER_EXCHANGE_kyc_wallet (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_Amount *balance,
+ TALER_EXCHANGE_KycWalletCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_KycWalletHandle *kwh;
+ CURL *eh;
+ json_t *req;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &reserve_pub.eddsa_pub);
+ TALER_wallet_account_setup_sign (reserve_priv,
+ balance,
+ &reserve_sig);
+ req = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("balance",
+ balance),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &reserve_pub),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &reserve_sig));
+ GNUNET_assert (NULL != req);
+ kwh = GNUNET_new (struct TALER_EXCHANGE_KycWalletHandle);
+ kwh->cb = cb;
+ kwh->cb_cls = cb_cls;
+ kwh->url = TALER_url_join (url,
+ "kyc-wallet",
+ NULL);
+ if (NULL == kwh->url)
+ {
+ json_decref (req);
+ GNUNET_free (kwh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (kwh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&kwh->ctx,
+ eh,
+ req)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (req);
+ GNUNET_free (kwh->url);
+ GNUNET_free (kwh);
+ return NULL;
+ }
+ json_decref (req);
+ kwh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ kwh->ctx.headers,
+ &handle_kyc_wallet_finished,
+ kwh);
+ return kwh;
+}
+
+
+void
+TALER_EXCHANGE_kyc_wallet_cancel (struct TALER_EXCHANGE_KycWalletHandle *kwh)
+{
+ if (NULL != kwh->job)
+ {
+ GNUNET_CURL_job_cancel (kwh->job);
+ kwh->job = NULL;
+ }
+ GNUNET_free (kwh->url);
+ TALER_curl_easy_post_finished (&kwh->ctx);
+ GNUNET_free (kwh);
+}
+
+
+/* end of exchange_api_kyc_wallet.c */
diff --git a/src/lib/exchange_api_link.c b/src/lib/exchange_api_link.c
index 088e4aa36..4b1adc723 100644
--- a/src/lib/exchange_api_link.c
+++ b/src/lib/exchange_api_link.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2020 Taler Systems SA
+ Copyright (C) 2015-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
@@ -37,11 +37,6 @@ struct TALER_EXCHANGE_LinkHandle
{
/**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
* The url for this request.
*/
char *url;
@@ -66,6 +61,12 @@ struct TALER_EXCHANGE_LinkHandle
*/
struct TALER_CoinSpendPrivateKeyP coin_priv;
+ /**
+ * Age commitment and proof of the original coin, might be NULL.
+ * Required to derive the new age commitment and proof.
+ */
+ const struct TALER_AgeCommitmentProof *age_commitment_proof;
+
};
@@ -75,33 +76,45 @@ struct TALER_EXCHANGE_LinkHandle
*
* @param lh link handle
* @param json json reply with the data for one coin
- * @param coin_num number of the coin to decode
* @param trans_pub our transfer public key
- * @param[out] coin_priv where to return private coin key
- * @param[out] sig where to return private coin signature
- * @param[out] pub where to return the public key for the coin
+ * @param[out] lci where to return coin details
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
*/
-static int
+static enum GNUNET_GenericReturnValue
parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
const json_t *json,
- unsigned int coin_num,
const struct TALER_TransferPublicKeyP *trans_pub,
- struct TALER_CoinSpendPrivateKeyP *coin_priv,
- struct TALER_DenominationSignature *sig,
- struct TALER_DenominationPublicKey *pub)
+ struct TALER_EXCHANGE_LinkedCoinInfo *lci)
{
- struct GNUNET_CRYPTO_RsaSignature *bsig;
- struct GNUNET_CRYPTO_RsaPublicKey *rpub;
+ struct TALER_BlindedDenominationSignature bsig;
+ struct TALER_DenominationPublicKey rpub;
struct TALER_CoinSpendSignatureP link_sig;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct TALER_ExchangeWithdrawValues alg_values;
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+ bool no_nonce;
+ uint32_t coin_idx;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_rsa_public_key ("denom_pub", &rpub),
- GNUNET_JSON_spec_rsa_signature ("ev_sig", &bsig),
- GNUNET_JSON_spec_fixed_auto ("link_sig", &link_sig),
+ TALER_JSON_spec_denom_pub ("denom_pub",
+ &rpub),
+ TALER_JSON_spec_blinded_denom_sig ("ev_sig",
+ &bsig),
+ TALER_JSON_spec_exchange_withdraw_values ("ewv",
+ &alg_values),
+ GNUNET_JSON_spec_fixed_auto ("link_sig",
+ &link_sig),
+ GNUNET_JSON_spec_uint32 ("coin_idx",
+ &coin_idx),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("cs_nonce",
+ &nonce),
+ &no_nonce),
GNUNET_JSON_spec_end ()
};
struct TALER_TransferSecretP secret;
- struct TALER_PlanchetSecretsP fc;
+ struct TALER_PlanchetDetail pd;
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_AgeCommitmentHash *pah = NULL;
/* parse reply */
if (GNUNET_OK !=
@@ -112,60 +125,97 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
-
TALER_link_recover_transfer_secret (trans_pub,
&lh->coin_priv,
&secret);
- TALER_planchet_setup_refresh (&secret,
- coin_num,
- &fc);
+ TALER_transfer_secret_to_planchet_secret (&secret,
+ coin_idx,
+ &lci->ps);
+ TALER_planchet_setup_coin_priv (&lci->ps,
+ &alg_values,
+ &lci->coin_priv);
+ TALER_planchet_blinding_secret_create (&lci->ps,
+ &alg_values,
+ &bks);
+
+ lci->has_age_commitment = false;
+
+ /* Derive the age commitment and calculate the hash */
+ if (NULL != lh->age_commitment_proof)
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_age_commitment_derive (
+ lh->age_commitment_proof,
+ &secret.key,
+ &lci->age_commitment_proof));
+
+ TALER_age_commitment_hash (
+ &lci->age_commitment_proof.commitment,
+ &lci->h_age_commitment);
+
+ lci->has_age_commitment = true;
+ pah = &lci->h_age_commitment;
+ }
+
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (
+ &rpub,
+ &alg_values,
+ &bks,
+ no_nonce
+ ? NULL
+ : &nonce,
+ &lci->coin_priv,
+ pah,
+ &c_hash,
+ &pd))
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
/* extract coin and signature */
- *coin_priv = fc.coin_priv;
- sig->rsa_signature
- = GNUNET_CRYPTO_rsa_unblind (bsig,
- &fc.blinding_key.bks,
- rpub);
+ if (GNUNET_OK !=
+ TALER_denom_sig_unblind (&lci->sig,
+ &bsig,
+ &bks,
+ &c_hash,
+ &alg_values,
+ &rpub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
/* verify link_sig */
{
- struct TALER_LinkDataPS ldp;
- struct TALER_PlanchetDetail pd;
+ struct TALER_CoinSpendPublicKeyP old_coin_pub;
+ struct TALER_BlindedCoinHashP coin_envelope_hash;
- ldp.purpose.size = htonl (sizeof (ldp));
- ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK);
GNUNET_CRYPTO_eddsa_key_get_public (&lh->coin_priv.eddsa_priv,
- &ldp.old_coin_pub.eddsa_pub);
- ldp.transfer_pub = *trans_pub;
- pub->rsa_public_key = rpub;
- if (GNUNET_OK !=
- TALER_planchet_prepare (pub,
- &fc,
- &pd))
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- ldp.h_denom_pub = pd.denom_pub_hash;
- GNUNET_CRYPTO_hash (pd.coin_ev,
- pd.coin_ev_size,
- &ldp.coin_envelope_hash);
- GNUNET_free (pd.coin_ev);
+ &old_coin_pub.eddsa_pub);
+ TALER_coin_ev_hash (&pd.blinded_planchet,
+ &pd.denom_pub_hash,
+ &coin_envelope_hash);
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK,
- &ldp.purpose,
- &link_sig.eddsa_signature,
- &ldp.old_coin_pub.eddsa_pub))
+ TALER_wallet_link_verify (&pd.denom_pub_hash,
+ trans_pub,
+ &coin_envelope_hash,
+ &old_coin_pub,
+ &link_sig))
{
GNUNET_break_op (0);
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
}
/* clean up */
- pub->rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (rpub);
+ TALER_denom_pub_copy (&lci->pub,
+ &rpub);
GNUNET_JSON_parse_free (spec);
return GNUNET_OK;
}
@@ -179,13 +229,17 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
* @param json json reply with the data for one coin
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
*/
-static int
+static enum GNUNET_GenericReturnValue
parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
const json_t *json)
{
unsigned int session;
unsigned int num_coins;
int ret;
+ struct TALER_EXCHANGE_LinkResult lr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
if (! json_is_array (json))
{
@@ -206,9 +260,10 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
whilst 'i' and 'session' track the 2d array. *///
for (session = 0; session<json_array_size (json); session++)
{
- json_t *jsona;
+ const json_t *jsona;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("new_coins", &jsona),
+ GNUNET_JSON_spec_array_const ("new_coins",
+ &jsona),
GNUNET_JSON_spec_end ()
};
@@ -221,36 +276,25 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (! json_is_array (jsona))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
-
/* count all coins over all sessions */
num_coins += json_array_size (jsona);
- GNUNET_JSON_parse_free (spec);
}
/* Now that we know how big the 1d array is, allocate
and fill it. */
{
unsigned int off_coin; /* index into 1d array */
unsigned int i;
- struct TALER_CoinSpendPrivateKeyP coin_privs[GNUNET_NZL (num_coins)];
- struct TALER_DenominationSignature sigs[GNUNET_NZL (num_coins)];
- struct TALER_DenominationPublicKey pubs[GNUNET_NZL (num_coins)];
+ struct TALER_EXCHANGE_LinkedCoinInfo lcis[GNUNET_NZL (num_coins)];
- memset (sigs, 0, sizeof (sigs));
- memset (pubs, 0, sizeof (pubs));
+ memset (lcis, 0, sizeof (lcis));
off_coin = 0;
for (session = 0; session<json_array_size (json); session++)
{
- json_t *jsona;
+ const json_t *jsona;
struct TALER_TransferPublicKeyP trans_pub;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("new_coins",
- &jsona),
+ GNUNET_JSON_spec_array_const ("new_coins",
+ &jsona),
GNUNET_JSON_spec_fixed_auto ("transfer_pub",
&trans_pub),
GNUNET_JSON_spec_end ()
@@ -265,26 +309,20 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (! json_is_array (jsona))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
/* decode all coins */
for (i = 0; i<json_array_size (jsona); i++)
{
+ struct TALER_EXCHANGE_LinkedCoinInfo *lci;
+
+ lci = &lcis[i + off_coin];
GNUNET_assert (i + off_coin < num_coins);
if (GNUNET_OK !=
parse_link_coin (lh,
json_array_get (jsona,
i),
- i,
&trans_pub,
- &coin_privs[i + off_coin],
- &sigs[i + off_coin],
- &pubs[i + off_coin]))
+ lci))
{
GNUNET_break_op (0);
break;
@@ -296,22 +334,16 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
{
GNUNET_break_op (0);
ret = GNUNET_SYSERR;
- GNUNET_JSON_parse_free (spec);
break;
}
- GNUNET_JSON_parse_free (spec);
} /* end of for (session) */
if (off_coin == num_coins)
{
+ lr.details.ok.num_coins = num_coins;
+ lr.details.ok.coins = lcis;
lh->link_cb (lh->link_cb_cls,
- MHD_HTTP_OK,
- TALER_EC_NONE,
- num_coins,
- coin_privs,
- sigs,
- pubs,
- json);
+ &lr);
lh->link_cb = NULL;
ret = GNUNET_OK;
}
@@ -325,10 +357,10 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
GNUNET_assert (off_coin <= num_coins);
for (i = 0; i<off_coin; i++)
{
- if (NULL != sigs[i].rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature);
- if (NULL != pubs[i].rsa_public_key)
- GNUNET_CRYPTO_rsa_public_key_free (pubs[i].rsa_public_key);
+ TALER_denom_sig_free (&lcis[i].sig);
+ TALER_denom_pub_free (&lcis[i].pub);
+ if (lcis[i].has_age_commitment)
+ TALER_age_commitment_proof_free (&lcis[i].age_commitment_proof);
}
}
return ret;
@@ -350,13 +382,16 @@ handle_link_finished (void *cls,
{
struct TALER_EXCHANGE_LinkHandle *lh = cls;
const json_t *j = response;
- enum TALER_ErrorCode ec;
+ struct TALER_EXCHANGE_LinkResult lr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
lh->job = NULL;
switch (response_code)
{
case 0:
- ec = TALER_EC_INVALID_RESPONSE;
+ lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
@@ -364,107 +399,93 @@ handle_link_finished (void *cls,
j))
{
GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_LINK_REPLY_MALFORMED;
+ lr.hr.http_status = 0;
+ lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
GNUNET_assert (NULL == lh->link_cb);
TALER_EXCHANGE_link_cancel (lh);
return;
case MHD_HTTP_BAD_REQUEST:
- ec = TALER_JSON_get_error_code (j);
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
/* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */
break;
case MHD_HTTP_NOT_FOUND:
- ec = TALER_JSON_get_error_code (j);
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
/* Nothing really to verify, exchange says this coin was not melted; we
should pass the JSON reply to the application */
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
- ec = TALER_JSON_get_error_code (j);
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
break;
default:
/* unexpected response code */
+ GNUNET_break_op (0);
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u\n",
- (unsigned int) response_code);
- GNUNET_break (0);
- response_code = 0;
- ec = TALER_JSON_get_error_code (j);
+ "Unexpected response code %u/%d for exchange link\n",
+ (unsigned int) response_code,
+ (int) lr.hr.ec);
break;
}
if (NULL != lh->link_cb)
lh->link_cb (lh->link_cb_cls,
- response_code,
- ec,
- 0,
- NULL,
- NULL,
- NULL,
- j);
+ &lr);
TALER_EXCHANGE_link_cancel (lh);
}
-/**
- * Submit a link request to the exchange and get the exchange's response.
- *
- * This API is typically not used by anyone, it is more a threat against those
- * trying to receive a funds transfer by abusing the refresh protocol.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param coin_priv private key to request link data for
- * @param link_cb the callback to call with the useful result of the
- * refresh operation the @a coin_priv was involved in (if any)
- * @param link_cb_cls closure for @a link_cb
- * @return a handle for this request
- */
struct TALER_EXCHANGE_LinkHandle *
-TALER_EXCHANGE_link (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_CoinSpendPrivateKeyP *coin_priv,
- TALER_EXCHANGE_LinkCallback link_cb,
- void *link_cb_cls)
+TALER_EXCHANGE_link (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ const struct TALER_AgeCommitmentProof *age_commitment_proof,
+ TALER_EXCHANGE_LinkCallback link_cb,
+ void *link_cb_cls)
{
struct TALER_EXCHANGE_LinkHandle *lh;
CURL *eh;
- struct GNUNET_CURL_Context *ctx;
struct TALER_CoinSpendPublicKeyP coin_pub;
char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
-
GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
&coin_pub.eddsa_pub);
{
char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
char *end;
- end = GNUNET_STRINGS_data_to_string (&coin_pub,
- sizeof (struct
- TALER_CoinSpendPublicKeyP),
- pub_str,
- sizeof (pub_str));
+ end = GNUNET_STRINGS_data_to_string (
+ &coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ pub_str,
+ sizeof (pub_str));
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/coins/%s/link",
+ "coins/%s/link",
pub_str);
}
lh = GNUNET_new (struct TALER_EXCHANGE_LinkHandle);
- lh->exchange = exchange;
lh->link_cb = link_cb;
lh->link_cb_cls = link_cb_cls;
lh->coin_priv = *coin_priv;
- lh->url = TEAH_path_to_url (exchange,
- arg_str);
+ lh->age_commitment_proof = age_commitment_proof;
+ lh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == lh->url)
+ {
+ GNUNET_free (lh);
+ return NULL;
+ }
eh = TALER_EXCHANGE_curl_easy_get_ (lh->url);
if (NULL == eh)
{
@@ -473,22 +494,14 @@ TALER_EXCHANGE_link (struct TALER_EXCHANGE_Handle *exchange,
GNUNET_free (lh);
return NULL;
}
- ctx = TEAH_handle_to_context (exchange);
- lh->job = GNUNET_CURL_job_add (ctx,
- eh,
- GNUNET_YES,
- &handle_link_finished,
- lh);
+ lh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
+ eh,
+ &handle_link_finished,
+ lh);
return lh;
}
-/**
- * Cancel a link request. This function cannot be used
- * on a request handle if the callback was already invoked.
- *
- * @param lh the link handle
- */
void
TALER_EXCHANGE_link_cancel (struct TALER_EXCHANGE_LinkHandle *lh)
{
@@ -497,6 +510,7 @@ TALER_EXCHANGE_link_cancel (struct TALER_EXCHANGE_LinkHandle *lh)
GNUNET_CURL_job_cancel (lh->job);
lh->job = NULL;
}
+
GNUNET_free (lh->url);
GNUNET_free (lh);
}
diff --git a/src/lib/exchange_api_lookup_aml_decision.c b/src/lib/exchange_api_lookup_aml_decision.c
new file mode 100644
index 000000000..501b9d185
--- /dev/null
+++ b/src/lib/exchange_api_lookup_aml_decision.c
@@ -0,0 +1,417 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_lookup_aml_decision.c
+ * @brief Implementation of the /aml/$OFFICER_PUB/decision request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /coins/$COIN_PUB/link Handle
+ */
+struct TALER_EXCHANGE_LookupAmlDecision
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_LookupAmlDecisionCallback decision_cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *decision_cb_cls;
+
+ /**
+ * HTTP headers for the job.
+ */
+ struct curl_slist *job_headers;
+};
+
+
+/**
+ * Parse AML decision history.
+ *
+ * @param aml_history JSON array with AML history
+ * @param[out] aml_history_ar where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_aml_history (const json_t *aml_history,
+ struct TALER_EXCHANGE_AmlDecisionDetail *aml_history_ar)
+{
+ json_t *obj;
+ size_t idx;
+
+ json_array_foreach (aml_history, idx, obj)
+ {
+ struct TALER_EXCHANGE_AmlDecisionDetail *aml = &aml_history_ar[idx];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("decision_time",
+ &aml->decision_time),
+ GNUNET_JSON_spec_string ("justification",
+ &aml->justification),
+ TALER_JSON_spec_amount_any ("new_threshold",
+ &aml->new_threshold),
+ TALER_JSON_spec_aml_decision ("new_state",
+ &aml->new_state),
+ GNUNET_JSON_spec_fixed_auto ("decider_pub",
+ &aml->decider_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse KYC response array.
+ *
+ * @param kyc_attributes JSON array with KYC details
+ * @param[out] kyc_attributes_ar where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_kyc_attributes (const json_t *kyc_attributes,
+ struct TALER_EXCHANGE_KycHistoryDetail *kyc_attributes_ar)
+{
+ json_t *obj;
+ size_t idx;
+
+ json_array_foreach (kyc_attributes, idx, obj)
+ {
+ struct TALER_EXCHANGE_KycHistoryDetail *kyc = &kyc_attributes_ar[idx];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("collection_time",
+ &kyc->collection_time),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("attributes",
+ &kyc->attributes),
+ NULL),
+ GNUNET_JSON_spec_string ("provider_section",
+ &kyc->provider_section),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse the provided decision data from the "200 OK" response.
+ *
+ * @param[in,out] lh handle (callback may be zero'ed out)
+ * @param json json reply with the data for one coin
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static enum GNUNET_GenericReturnValue
+parse_decision_ok (struct TALER_EXCHANGE_LookupAmlDecision *lh,
+ const json_t *json)
+{
+ struct TALER_EXCHANGE_AmlDecisionResponse lr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ const json_t *aml_history;
+ const json_t *kyc_attributes;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("aml_history",
+ &aml_history),
+ GNUNET_JSON_spec_array_const ("kyc_attributes",
+ &kyc_attributes),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ lr.details.ok.aml_history_length = json_array_size (aml_history);
+ lr.details.ok.kyc_attributes_length = json_array_size (kyc_attributes);
+ {
+ struct TALER_EXCHANGE_AmlDecisionDetail aml_history_ar[
+ GNUNET_NZL (lr.details.ok.aml_history_length)];
+ struct TALER_EXCHANGE_KycHistoryDetail kyc_attributes_ar[
+ GNUNET_NZL (lr.details.ok.kyc_attributes_length)];
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+
+ memset (aml_history_ar,
+ 0,
+ sizeof (aml_history_ar));
+ memset (kyc_attributes_ar,
+ 0,
+ sizeof (kyc_attributes_ar));
+ lr.details.ok.aml_history = aml_history_ar;
+ lr.details.ok.kyc_attributes = kyc_attributes_ar;
+ ret = parse_aml_history (aml_history,
+ aml_history_ar);
+ if (GNUNET_OK == ret)
+ ret = parse_kyc_attributes (kyc_attributes,
+ kyc_attributes_ar);
+ if (GNUNET_OK == ret)
+ {
+ lh->decision_cb (lh->decision_cb_cls,
+ &lr);
+ lh->decision_cb = NULL;
+ }
+ return ret;
+ }
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /aml/$OFFICER_PUB/decision request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_LookupAmlDecision`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_lookup_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_LookupAmlDecision *lh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_AmlDecisionResponse lr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ lh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ parse_decision_ok (lh,
+ j))
+ {
+ GNUNET_break_op (0);
+ lr.hr.http_status = 0;
+ lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == lh->decision_cb);
+ TALER_EXCHANGE_lookup_aml_decision_cancel (lh);
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says this coin was not melted; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says this coin was not melted; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange lookup AML decision\n",
+ (unsigned int) response_code,
+ (int) lr.hr.ec);
+ break;
+ }
+ if (NULL != lh->decision_cb)
+ lh->decision_cb (lh->decision_cb_cls,
+ &lr);
+ TALER_EXCHANGE_lookup_aml_decision_cancel (lh);
+}
+
+
+struct TALER_EXCHANGE_LookupAmlDecision *
+TALER_EXCHANGE_lookup_aml_decision (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_url,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ bool history,
+ TALER_EXCHANGE_LookupAmlDecisionCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_LookupAmlDecision *lh;
+ CURL *eh;
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+ char arg_str[sizeof (officer_pub) * 2
+ + sizeof (*h_payto) * 2 + 32];
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
+ &officer_pub.eddsa_pub);
+ TALER_officer_aml_query_sign (officer_priv,
+ &officer_sig);
+ {
+ char pub_str[sizeof (officer_pub) * 2];
+ char pt_str[sizeof (*h_payto) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_pub,
+ sizeof (officer_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ end = GNUNET_STRINGS_data_to_string (
+ h_payto,
+ sizeof (*h_payto),
+ pt_str,
+ sizeof (pt_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "aml/%s/decision/%s",
+ pub_str,
+ pt_str);
+ }
+ lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecision);
+ lh->decision_cb = cb;
+ lh->decision_cb_cls = cb_cls;
+ lh->url = TALER_url_join (exchange_url,
+ arg_str,
+ "history",
+ history
+ ? "true"
+ : NULL,
+ NULL);
+ if (NULL == lh->url)
+ {
+ GNUNET_free (lh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (lh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
+ return NULL;
+ }
+ {
+ char *hdr;
+ char sig_str[sizeof (officer_sig) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_sig,
+ sizeof (officer_sig),
+ sig_str,
+ sizeof (sig_str));
+ *end = '\0';
+
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_AML_OFFICER_SIGNATURE_HEADER,
+ sig_str);
+ lh->job_headers = curl_slist_append (NULL,
+ hdr);
+ GNUNET_free (hdr);
+ lh->job_headers = curl_slist_append (lh->job_headers,
+ "Content-type: application/json");
+ lh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ lh->job_headers,
+ &handle_lookup_finished,
+ lh);
+ }
+ return lh;
+}
+
+
+void
+TALER_EXCHANGE_lookup_aml_decision_cancel (
+ struct TALER_EXCHANGE_LookupAmlDecision *lh)
+{
+ if (NULL != lh->job)
+ {
+ GNUNET_CURL_job_cancel (lh->job);
+ lh->job = NULL;
+ }
+ curl_slist_free_all (lh->job_headers);
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
+}
+
+
+/* end of exchange_api_lookup_aml_decision.c */
diff --git a/src/lib/exchange_api_lookup_aml_decisions.c b/src/lib/exchange_api_lookup_aml_decisions.c
new file mode 100644
index 000000000..bb3c18b68
--- /dev/null
+++ b/src/lib/exchange_api_lookup_aml_decisions.c
@@ -0,0 +1,376 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_lookup_aml_decisions.c
+ * @brief Implementation of the /aml/$OFFICER_PUB/decisions request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /coins/$COIN_PUB/link Handle
+ */
+struct TALER_EXCHANGE_LookupAmlDecisions
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_LookupAmlDecisionsCallback decisions_cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *decisions_cb_cls;
+
+ /**
+ * HTTP headers for the job.
+ */
+ struct curl_slist *job_headers;
+};
+
+
+/**
+ * Parse AML decision summary array.
+ *
+ * @param decisions JSON array with AML decision summaries
+ * @param[out] decision_ar where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_aml_decisions (const json_t *decisions,
+ struct TALER_EXCHANGE_AmlDecisionSummary *decision_ar)
+{
+ json_t *obj;
+ size_t idx;
+
+ json_array_foreach (decisions, idx, obj)
+ {
+ struct TALER_EXCHANGE_AmlDecisionSummary *decision = &decision_ar[idx];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_payto",
+ &decision->h_payto),
+ TALER_JSON_spec_aml_decision ("current_state",
+ &decision->current_state),
+ TALER_JSON_spec_amount_any ("threshold",
+ &decision->threshold),
+ GNUNET_JSON_spec_uint64 ("rowid",
+ &decision->rowid),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse the provided decision data from the "200 OK" response.
+ *
+ * @param[in,out] lh handle (callback may be zero'ed out)
+ * @param json json reply with the data for one coin
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static enum GNUNET_GenericReturnValue
+parse_decisions_ok (struct TALER_EXCHANGE_LookupAmlDecisions *lh,
+ const json_t *json)
+{
+ struct TALER_EXCHANGE_AmlDecisionsResponse lr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ const json_t *records;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("records",
+ &records),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ lr.details.ok.decisions_length = json_array_size (records);
+ {
+ struct TALER_EXCHANGE_AmlDecisionSummary decisions[
+ GNUNET_NZL (lr.details.ok.decisions_length)];
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+
+ lr.details.ok.decisions = decisions;
+ ret = parse_aml_decisions (records,
+ decisions);
+ if (GNUNET_OK == ret)
+ {
+ lh->decisions_cb (lh->decisions_cb_cls,
+ &lr);
+ lh->decisions_cb = NULL;
+ }
+ return ret;
+ }
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /aml/$OFFICER_PUB/decisions request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_LookupAmlDecisions`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_lookup_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_LookupAmlDecisions *lh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_AmlDecisionsResponse lr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ lh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ parse_decisions_ok (lh,
+ j))
+ {
+ GNUNET_break_op (0);
+ lr.hr.http_status = 0;
+ lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == lh->decisions_cb);
+ TALER_EXCHANGE_lookup_aml_decisions_cancel (lh);
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says this coin was not melted; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says this coin was not melted; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for lookup AML decisions\n",
+ (unsigned int) response_code,
+ (int) lr.hr.ec);
+ break;
+ }
+ if (NULL != lh->decisions_cb)
+ lh->decisions_cb (lh->decisions_cb_cls,
+ &lr);
+ TALER_EXCHANGE_lookup_aml_decisions_cancel (lh);
+}
+
+
+struct TALER_EXCHANGE_LookupAmlDecisions *
+TALER_EXCHANGE_lookup_aml_decisions (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_url,
+ uint64_t start,
+ int delta,
+ enum TALER_AmlDecisionState state,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ TALER_EXCHANGE_LookupAmlDecisionsCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_LookupAmlDecisions *lh;
+ CURL *eh;
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+ char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 + 32];
+ const char *state_str = NULL;
+
+ switch (state)
+ {
+ case TALER_AML_NORMAL:
+ state_str = "normal";
+ break;
+ case TALER_AML_PENDING:
+ state_str = "pending";
+ break;
+ case TALER_AML_FROZEN:
+ state_str = "frozen";
+ break;
+ }
+ GNUNET_assert (NULL != state_str);
+ GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
+ &officer_pub.eddsa_pub);
+ TALER_officer_aml_query_sign (officer_priv,
+ &officer_sig);
+ {
+ char pub_str[sizeof (officer_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_pub,
+ sizeof (officer_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "aml/%s/decisions/%s",
+ pub_str,
+ state_str);
+ }
+ lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecisions);
+ lh->decisions_cb = cb;
+ lh->decisions_cb_cls = cb_cls;
+ {
+ char delta_s[24];
+ char start_s[24];
+
+ GNUNET_snprintf (delta_s,
+ sizeof (delta_s),
+ "%d",
+ delta);
+ GNUNET_snprintf (start_s,
+ sizeof (start_s),
+ "%llu",
+ (unsigned long long) start);
+ lh->url = TALER_url_join (exchange_url,
+ arg_str,
+ "delta",
+ delta_s,
+ "start",
+ start_s,
+ NULL);
+ }
+ if (NULL == lh->url)
+ {
+ GNUNET_free (lh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (lh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
+ return NULL;
+ }
+ {
+ char *hdr;
+ char sig_str[sizeof (officer_sig) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_sig,
+ sizeof (officer_sig),
+ sig_str,
+ sizeof (sig_str));
+ *end = '\0';
+
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_AML_OFFICER_SIGNATURE_HEADER,
+ sig_str);
+ lh->job_headers = curl_slist_append (NULL,
+ hdr);
+ GNUNET_free (hdr);
+ lh->job_headers = curl_slist_append (lh->job_headers,
+ "Content-type: application/json");
+ lh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ lh->job_headers,
+ &handle_lookup_finished,
+ lh);
+ }
+ return lh;
+}
+
+
+void
+TALER_EXCHANGE_lookup_aml_decisions_cancel (
+ struct TALER_EXCHANGE_LookupAmlDecisions *lh)
+{
+ if (NULL != lh->job)
+ {
+ GNUNET_CURL_job_cancel (lh->job);
+ lh->job = NULL;
+ }
+ curl_slist_free_all (lh->job_headers);
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
+}
+
+
+/* end of exchange_api_lookup_aml_decisions.c */
diff --git a/src/lib/exchange_api_management_add_partner.c b/src/lib/exchange_api_management_add_partner.c
new file mode 100644
index 000000000..fec66c567
--- /dev/null
+++ b/src/lib/exchange_api_management_add_partner.c
@@ -0,0 +1,218 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_add_partner.c
+ * @brief functions to add an partner by an AML officer
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementAddPartner
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementAddPartnerCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /management/partners request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAddPartner *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_add_partner_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementAddPartner *wh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementAddPartnerResponse apr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ apr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ apr.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_CONFLICT:
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for adding exchange partner\n",
+ (unsigned int) response_code,
+ (int) apr.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &apr);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_add_partner_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_ManagementAddPartner *
+TALER_EXCHANGE_management_add_partner (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_MasterPublicKeyP *partner_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ struct GNUNET_TIME_Relative wad_frequency,
+ const struct TALER_Amount *wad_fee,
+ const char *partner_base_url,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementAddPartnerCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementAddPartner *wh;
+ CURL *eh;
+ json_t *body;
+
+ wh = GNUNET_new (struct TALER_EXCHANGE_ManagementAddPartner);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ctx = ctx;
+ wh->url = TALER_url_join (url,
+ "management/partners",
+ NULL);
+ if (NULL == wh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("partner_base_url",
+ partner_base_url),
+ GNUNET_JSON_pack_timestamp ("start_date",
+ start_date),
+ GNUNET_JSON_pack_timestamp ("end_date",
+ end_date),
+ GNUNET_JSON_pack_time_rel ("wad_frequency",
+ wad_frequency),
+ GNUNET_JSON_pack_data_auto ("partner_pub",
+ &partner_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &master_sig),
+ TALER_JSON_pack_amount ("wad_fee",
+ wad_fee)
+ );
+ eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&wh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (wh->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ wh->url);
+ wh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ wh->post_ctx.headers,
+ &handle_add_partner_finished,
+ wh);
+ if (NULL == wh->job)
+ {
+ TALER_EXCHANGE_management_add_partner_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_management_add_partner_cancel (
+ struct TALER_EXCHANGE_ManagementAddPartner *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wh->post_ctx);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_management_auditor_disable.c b/src/lib/exchange_api_management_auditor_disable.c
new file mode 100644
index 000000000..8bce7f74f
--- /dev/null
+++ b/src/lib/exchange_api_management_auditor_disable.c
@@ -0,0 +1,219 @@
+/*
+ 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 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 lib/exchange_api_management_auditor_disable.c
+ * @brief functions to disable an auditor
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+/**
+ * @brief Handle for a POST /management/auditors/$AUDITOR_PUB/disable request.
+ */
+struct TALER_EXCHANGE_ManagementAuditorDisableHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementAuditorDisableCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/auditors/%s/disable request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorDisableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_auditor_disable_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementAuditorDisableResponse adr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ ah->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_CONFLICT:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management auditor disable\n",
+ (unsigned int) response_code,
+ (int) adr.hr.ec);
+ break;
+ }
+ if (NULL != ah->cb)
+ {
+ ah->cb (ah->cb_cls,
+ &adr);
+ ah->cb = NULL;
+ }
+ TALER_EXCHANGE_management_disable_auditor_cancel (ah);
+}
+
+
+struct TALER_EXCHANGE_ManagementAuditorDisableHandle *
+TALER_EXCHANGE_management_disable_auditor (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct GNUNET_TIME_Timestamp validity_end,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementAuditorDisableCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah;
+ CURL *eh;
+ json_t *body;
+
+ ah = GNUNET_new (struct TALER_EXCHANGE_ManagementAuditorDisableHandle);
+ ah->cb = cb;
+ ah->cb_cls = cb_cls;
+ ah->ctx = ctx;
+ {
+ char epub_str[sizeof (*auditor_pub) * 2];
+ char arg_str[sizeof (epub_str) + 64];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (auditor_pub,
+ sizeof (*auditor_pub),
+ epub_str,
+ sizeof (epub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "management/auditors/%s/disable",
+ epub_str);
+ ah->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ }
+ if (NULL == ah->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (ah);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_timestamp ("validity_end",
+ validity_end));
+ eh = TALER_EXCHANGE_curl_easy_get_ (ah->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&ah->post_ctx,
+ eh,
+ body)))
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (ah->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ ah->url);
+ ah->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ ah->post_ctx.headers,
+ &handle_auditor_disable_finished,
+ ah);
+ if (NULL == ah->job)
+ {
+ TALER_EXCHANGE_management_disable_auditor_cancel (ah);
+ return NULL;
+ }
+ return ah;
+}
+
+
+void
+TALER_EXCHANGE_management_disable_auditor_cancel (
+ struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah)
+{
+ if (NULL != ah->job)
+ {
+ GNUNET_CURL_job_cancel (ah->job);
+ ah->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&ah->post_ctx);
+ GNUNET_free (ah->url);
+ GNUNET_free (ah);
+}
diff --git a/src/lib/exchange_api_management_auditor_enable.c b/src/lib/exchange_api_management_auditor_enable.c
new file mode 100644
index 000000000..41c5049c2
--- /dev/null
+++ b/src/lib/exchange_api_management_auditor_enable.c
@@ -0,0 +1,224 @@
+/*
+ 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 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 lib/exchange_api_management_auditor_enable.c
+ * @brief functions to enable an auditor
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+/**
+ * @brief Handle for a POST /management/auditors request.
+ */
+struct TALER_EXCHANGE_ManagementAuditorEnableHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementAuditorEnableCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /management/auditors request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_auditor_enable_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementAuditorEnableResponse aer = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ ah->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ aer.hr.ec = TALER_JSON_get_error_code (json);
+ aer.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ ah->url);
+ if (NULL != json)
+ {
+ aer.hr.ec = TALER_JSON_get_error_code (json);
+ aer.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ aer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ aer.hr.hint = TALER_ErrorCode_get_hint (aer.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ aer.hr.ec = TALER_JSON_get_error_code (json);
+ aer.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ aer.hr.ec = TALER_JSON_get_error_code (json);
+ aer.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management auditor enable\n",
+ (unsigned int) response_code,
+ (int) aer.hr.ec);
+ break;
+ }
+ if (NULL != ah->cb)
+ {
+ ah->cb (ah->cb_cls,
+ &aer);
+ ah->cb = NULL;
+ }
+ TALER_EXCHANGE_management_enable_auditor_cancel (ah);
+}
+
+
+struct TALER_EXCHANGE_ManagementAuditorEnableHandle *
+TALER_EXCHANGE_management_enable_auditor (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp validity_start,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementAuditorEnableCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah;
+ CURL *eh;
+ json_t *body;
+
+ ah = GNUNET_new (struct TALER_EXCHANGE_ManagementAuditorEnableHandle);
+ ah->cb = cb;
+ ah->cb_cls = cb_cls;
+ ah->ctx = ctx;
+ ah->url = TALER_url_join (url,
+ "management/auditors",
+ NULL);
+ if (NULL == ah->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (ah);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("auditor_url",
+ auditor_url),
+ GNUNET_JSON_pack_string ("auditor_name",
+ auditor_name),
+ GNUNET_JSON_pack_data_auto ("auditor_pub",
+ auditor_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_timestamp ("validity_start",
+ validity_start));
+ eh = TALER_EXCHANGE_curl_easy_get_ (ah->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&ah->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ json_decref (body);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ GNUNET_free (ah->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ ah->url);
+ ah->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ ah->post_ctx.headers,
+ &handle_auditor_enable_finished,
+ ah);
+ if (NULL == ah->job)
+ {
+ TALER_EXCHANGE_management_enable_auditor_cancel (ah);
+ return NULL;
+ }
+ return ah;
+}
+
+
+void
+TALER_EXCHANGE_management_enable_auditor_cancel (
+ struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah)
+{
+ if (NULL != ah->job)
+ {
+ GNUNET_CURL_job_cancel (ah->job);
+ ah->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&ah->post_ctx);
+ GNUNET_free (ah->url);
+ GNUNET_free (ah);
+}
diff --git a/src/lib/exchange_api_management_drain_profits.c b/src/lib/exchange_api_management_drain_profits.c
new file mode 100644
index 000000000..bc7232b87
--- /dev/null
+++ b/src/lib/exchange_api_management_drain_profits.c
@@ -0,0 +1,213 @@
+/*
+ This file is part of TALER
+ 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
+ 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 lib/exchange_api_management_drain_profits.c
+ * @brief functions to set wire fees at an exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_curl_defaults.h"
+#include "taler_exchange_service.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementDrainProfitsHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementDrainProfitsCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/drain request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementDrainProfitsHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_drain_profits_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementDrainResponse dr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ dp->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (json);
+ dr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_CONFLICT:
+ dr.hr.ec = TALER_JSON_get_error_code (json);
+ dr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ dr.hr.ec = TALER_JSON_get_error_code (json);
+ dr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ dr.hr.ec = TALER_JSON_get_error_code (json);
+ dr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management drain profits\n",
+ (unsigned int) response_code,
+ (int) dr.hr.ec);
+ break;
+ }
+ if (NULL != dp->cb)
+ {
+ dp->cb (dp->cb_cls,
+ &dr);
+ dp->cb = NULL;
+ }
+ TALER_EXCHANGE_management_drain_profits_cancel (dp);
+}
+
+
+struct TALER_EXCHANGE_ManagementDrainProfitsHandle *
+TALER_EXCHANGE_management_drain_profits (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *amount,
+ struct GNUNET_TIME_Timestamp date,
+ const char *account_section,
+ const char *payto_uri,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementDrainProfitsCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp;
+ CURL *eh;
+ json_t *body;
+
+ dp = GNUNET_new (struct TALER_EXCHANGE_ManagementDrainProfitsHandle);
+ dp->cb = cb;
+ dp->cb_cls = cb_cls;
+ dp->ctx = ctx;
+ dp->url = TALER_url_join (url,
+ "management/drain",
+ NULL);
+ if (NULL == dp->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (dp);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("debit_account_section",
+ account_section),
+ GNUNET_JSON_pack_string ("credit_payto_uri",
+ payto_uri),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ wtid),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_timestamp ("date",
+ date),
+ TALER_JSON_pack_amount ("amount",
+ amount));
+ eh = TALER_EXCHANGE_curl_easy_get_ (dp->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&dp->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (dp->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ dp->url);
+ dp->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ dp->post_ctx.headers,
+ &handle_drain_profits_finished,
+ dp);
+ if (NULL == dp->job)
+ {
+ TALER_EXCHANGE_management_drain_profits_cancel (dp);
+ return NULL;
+ }
+ return dp;
+}
+
+
+void
+TALER_EXCHANGE_management_drain_profits_cancel (
+ struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp)
+{
+ if (NULL != dp->job)
+ {
+ GNUNET_CURL_job_cancel (dp->job);
+ dp->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&dp->post_ctx);
+ GNUNET_free (dp->url);
+ GNUNET_free (dp);
+}
diff --git a/src/lib/exchange_api_management_get_keys.c b/src/lib/exchange_api_management_get_keys.c
new file mode 100644
index 000000000..b88ddc205
--- /dev/null
+++ b/src/lib/exchange_api_management_get_keys.c
@@ -0,0 +1,426 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_get_keys.c
+ * @brief functions to obtain future online keys of the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_util.h"
+#include "taler_json_lib.h"
+
+/**
+ * Set to 1 for extra debug logging.
+ */
+#define DEBUG 0
+
+
+/**
+ * @brief Handle for a GET /management/keys request.
+ */
+struct TALER_EXCHANGE_ManagementGetKeysHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementGetKeysCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Handle the case that the response was of type #MHD_HTTP_OK.
+ *
+ * @param[in,out] gh request handle
+ * @param response the response
+ * @return #GNUNET_OK if the response was well-formed
+ */
+static enum GNUNET_GenericReturnValue
+handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
+ const json_t *response)
+{
+ struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = {
+ .hr.http_status = MHD_HTTP_OK,
+ .hr.reply = response,
+ };
+ struct TALER_EXCHANGE_FutureKeys *fk
+ = &gkr.details.ok.keys;
+ const json_t *sk;
+ const json_t *dk;
+ bool ok;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("future_denoms",
+ &dk),
+ GNUNET_JSON_spec_array_const ("future_signkeys",
+ &sk),
+ GNUNET_JSON_spec_fixed_auto ("master_pub",
+ &fk->master_pub),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key",
+ &fk->denom_secmod_public_key),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key",
+ &fk->denom_secmod_cs_public_key),
+ GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key",
+ &fk->signkey_secmod_public_key),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ fk->num_sign_keys = json_array_size (sk);
+ fk->num_denom_keys = json_array_size (dk);
+ fk->sign_keys = GNUNET_new_array (
+ fk->num_sign_keys,
+ struct TALER_EXCHANGE_FutureSigningPublicKey);
+ fk->denom_keys = GNUNET_new_array (
+ fk->num_denom_keys,
+ struct TALER_EXCHANGE_FutureDenomPublicKey);
+ ok = true;
+ for (unsigned int i = 0; i<fk->num_sign_keys; i++)
+ {
+ json_t *j = json_array_get (sk,
+ i);
+ struct TALER_EXCHANGE_FutureSigningPublicKey *sign_key
+ = &fk->sign_keys[i];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("key",
+ &sign_key->key),
+ GNUNET_JSON_spec_fixed_auto ("signkey_secmod_sig",
+ &sign_key->signkey_secmod_sig),
+ GNUNET_JSON_spec_timestamp ("stamp_start",
+ &sign_key->valid_from),
+ GNUNET_JSON_spec_timestamp ("stamp_expire",
+ &sign_key->valid_until),
+ GNUNET_JSON_spec_timestamp ("stamp_end",
+ &sign_key->valid_legal),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ok = false;
+ break;
+ }
+ {
+ struct GNUNET_TIME_Relative duration
+ = GNUNET_TIME_absolute_get_difference (sign_key->valid_from.abs_time,
+ sign_key->valid_until.abs_time);
+
+ if (GNUNET_OK !=
+ TALER_exchange_secmod_eddsa_verify (
+ &sign_key->key,
+ sign_key->valid_from,
+ duration,
+ &fk->signkey_secmod_public_key,
+ &sign_key->signkey_secmod_sig))
+ {
+ GNUNET_break_op (0);
+ ok = false;
+ break;
+ }
+ }
+ }
+ for (unsigned int i = 0; i<fk->num_denom_keys; i++)
+ {
+ json_t *j = json_array_get (dk,
+ i);
+ struct TALER_EXCHANGE_FutureDenomPublicKey *denom_key
+ = &fk->denom_keys[i];
+ const char *section_name;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("value",
+ &denom_key->value),
+ GNUNET_JSON_spec_timestamp ("stamp_start",
+ &denom_key->valid_from),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
+ &denom_key->withdraw_valid_until),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
+ &denom_key->expire_deposit),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
+ &denom_key->expire_legal),
+ TALER_JSON_spec_denom_pub ("denom_pub",
+ &denom_key->key),
+ TALER_JSON_spec_amount_any ("fee_withdraw",
+ &denom_key->fee_withdraw),
+ TALER_JSON_spec_amount_any ("fee_deposit",
+ &denom_key->fee_deposit),
+ TALER_JSON_spec_amount_any ("fee_refresh",
+ &denom_key->fee_refresh),
+ TALER_JSON_spec_amount_any ("fee_refund",
+ &denom_key->fee_refund),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_sig",
+ &denom_key->denom_secmod_sig),
+ GNUNET_JSON_spec_string ("section_name",
+ &section_name),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+#if DEBUG
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+#endif
+ ok = false;
+ break;
+ }
+
+ {
+ struct TALER_DenominationHashP h_denom_pub;
+ struct GNUNET_TIME_Relative duration
+ = GNUNET_TIME_absolute_get_difference (
+ denom_key->valid_from.abs_time,
+ denom_key->withdraw_valid_until.abs_time);
+
+ TALER_denom_pub_hash (&denom_key->key,
+ &h_denom_pub);
+ switch (denom_key->key.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ {
+ struct TALER_RsaPubHashP h_rsa;
+
+ TALER_rsa_pub_hash (
+ denom_key->key.bsign_pub_key->details.rsa_public_key,
+ &h_rsa);
+ if (GNUNET_OK !=
+ TALER_exchange_secmod_rsa_verify (&h_rsa,
+ section_name,
+ denom_key->valid_from,
+ duration,
+ &fk->denom_secmod_public_key,
+ &denom_key->denom_secmod_sig))
+ {
+ GNUNET_break_op (0);
+ ok = false;
+ break;
+ }
+ }
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ struct TALER_CsPubHashP h_cs;
+
+ TALER_cs_pub_hash (
+ &denom_key->key.bsign_pub_key->details.cs_public_key,
+ &h_cs);
+ if (GNUNET_OK !=
+ TALER_exchange_secmod_cs_verify (&h_cs,
+ section_name,
+ denom_key->valid_from,
+ duration,
+ &fk->denom_secmod_cs_public_key,
+ &denom_key->denom_secmod_sig))
+ {
+ GNUNET_break_op (0);
+ ok = false;
+ break;
+ }
+ }
+ break;
+ default:
+ GNUNET_break_op (0);
+ ok = false;
+ break;
+ }
+ }
+ if (! ok)
+ break;
+ }
+ if (ok)
+ {
+ gh->cb (gh->cb_cls,
+ &gkr);
+ }
+ for (unsigned int i = 0; i<fk->num_denom_keys; i++)
+ TALER_denom_pub_free (&fk->denom_keys[i].key);
+ GNUNET_free (fk->sign_keys);
+ GNUNET_free (fk->denom_keys);
+ return (ok) ? GNUNET_OK : GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /management/keys request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementGetKeysHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_get_keys_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementGetKeysHandle *gh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ gh->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ if (GNUNET_OK ==
+ handle_ok (gh,
+ response))
+ {
+ gh->cb = NULL;
+ }
+ else
+ {
+ response_code = 0;
+ }
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ gh->url);
+ if (NULL != json)
+ {
+ gkr.hr.ec = TALER_JSON_get_error_code (json);
+ gkr.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ gkr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ gkr.hr.hint = TALER_ErrorCode_get_hint (gkr.hr.ec);
+ }
+ break;
+ default:
+ /* unexpected response code */
+ if (NULL != json)
+ {
+ gkr.hr.ec = TALER_JSON_get_error_code (json);
+ gkr.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ gkr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ gkr.hr.hint = TALER_ErrorCode_get_hint (gkr.hr.ec);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management get keys\n",
+ (unsigned int) response_code,
+ (int) gkr.hr.ec);
+ break;
+ }
+ if (NULL != gh->cb)
+ {
+ gh->cb (gh->cb_cls,
+ &gkr);
+ gh->cb = NULL;
+ }
+ TALER_EXCHANGE_get_management_keys_cancel (gh);
+};
+
+
+struct TALER_EXCHANGE_ManagementGetKeysHandle *
+TALER_EXCHANGE_get_management_keys (struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ TALER_EXCHANGE_ManagementGetKeysCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementGetKeysHandle *gh;
+ CURL *eh;
+
+ gh = GNUNET_new (struct TALER_EXCHANGE_ManagementGetKeysHandle);
+ gh->cb = cb;
+ gh->cb_cls = cb_cls;
+ gh->ctx = ctx;
+ gh->url = TALER_url_join (url,
+ "management/keys",
+ NULL);
+ if (NULL == gh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (gh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (gh->url);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ gh->url);
+ gh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_get_keys_finished,
+ gh);
+ if (NULL == gh->job)
+ {
+ TALER_EXCHANGE_get_management_keys_cancel (gh);
+ return NULL;
+ }
+ return gh;
+}
+
+
+void
+TALER_EXCHANGE_get_management_keys_cancel (
+ struct TALER_EXCHANGE_ManagementGetKeysHandle *gh)
+{
+ if (NULL != gh->job)
+ {
+ GNUNET_CURL_job_cancel (gh->job);
+ gh->job = NULL;
+ }
+ GNUNET_free (gh->url);
+ GNUNET_free (gh);
+}
diff --git a/src/lib/exchange_api_management_post_extensions.c b/src/lib/exchange_api_management_post_extensions.c
new file mode 100644
index 000000000..00d1c5e3f
--- /dev/null
+++ b/src/lib/exchange_api_management_post_extensions.c
@@ -0,0 +1,213 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file lib/exchange_api_management_post_extensions.c
+ * @brief functions to handle the settings for extensions (p2p and age restriction)
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_extensions.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_exchange_service.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+/**
+ * @brief Handle for a POST /management/extensions request.
+ */
+struct TALER_EXCHANGE_ManagementPostExtensionsHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementPostExtensionsCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /management/extensions request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementPostExtensionsHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_post_extensions_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementPostExtensionsResponse per = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ ph->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ per.hr.ec = TALER_JSON_get_error_code (json);
+ per.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ ph->url);
+ if (NULL != json)
+ {
+ per.hr.ec = TALER_JSON_get_error_code (json);
+ per.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ per.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ per.hr.hint = TALER_ErrorCode_get_hint (per.hr.ec);
+ }
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ per.hr.ec = TALER_JSON_get_error_code (json);
+ per.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management post extensions\n",
+ (unsigned int) response_code,
+ (int) per.hr.ec);
+ break;
+ }
+ if (NULL != ph->cb)
+ {
+ ph->cb (ph->cb_cls,
+ &per);
+ ph->cb = NULL;
+ }
+ TALER_EXCHANGE_management_post_extensions_cancel (ph);
+}
+
+
+struct TALER_EXCHANGE_ManagementPostExtensionsHandle *
+TALER_EXCHANGE_management_post_extensions (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_EXCHANGE_ManagementPostExtensionsData *ped,
+ TALER_EXCHANGE_ManagementPostExtensionsCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph;
+ CURL *eh = NULL;
+ json_t *body = NULL;
+
+ ph = GNUNET_new (struct TALER_EXCHANGE_ManagementPostExtensionsHandle);
+ ph->cb = cb;
+ ph->cb_cls = cb_cls;
+ ph->ctx = ctx;
+ ph->url = TALER_url_join (url,
+ "management/extensions",
+ NULL);
+ if (NULL == ph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (ph);
+ return NULL;
+ }
+
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_steal ("extensions",
+ (json_t *) ped->extensions),
+ GNUNET_JSON_pack_data_auto ("extensions_sig",
+ &ped->extensions_sig));
+
+ eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&ph->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (ph->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requesting URL '%s'\n",
+ ph->url);
+ ph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ ph->post_ctx.headers,
+ &handle_post_extensions_finished,
+ ph);
+ if (NULL == ph->job)
+ {
+ TALER_EXCHANGE_management_post_extensions_cancel (ph);
+ return NULL;
+ }
+ return ph;
+}
+
+
+void
+TALER_EXCHANGE_management_post_extensions_cancel (
+ struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph)
+{
+ if (NULL != ph->job)
+ {
+ GNUNET_CURL_job_cancel (ph->job);
+ ph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&ph->post_ctx);
+ GNUNET_free (ph->url);
+ GNUNET_free (ph);
+}
diff --git a/src/lib/exchange_api_management_post_keys.c b/src/lib/exchange_api_management_post_keys.c
new file mode 100644
index 000000000..a46124d90
--- /dev/null
+++ b/src/lib/exchange_api_management_post_keys.c
@@ -0,0 +1,237 @@
+/*
+ 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 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 lib/exchange_api_management_post_keys.c
+ * @brief functions to affirm the validity of exchange keys using the master private key
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+/**
+ * @brief Handle for a POST /management/keys request.
+ */
+struct TALER_EXCHANGE_ManagementPostKeysHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementPostKeysCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /management/keys request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementPostKeysHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_post_keys_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementPostKeysHandle *ph = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementPostKeysResponse pkr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ ph->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ pkr.hr.ec = TALER_JSON_get_error_code (json);
+ pkr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ pkr.hr.ec = TALER_JSON_get_error_code (json);
+ pkr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_REQUEST_ENTITY_TOO_LARGE:
+ pkr.hr.ec = TALER_JSON_get_error_code (json);
+ pkr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ pkr.hr.ec = TALER_JSON_get_error_code (json);
+ pkr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management post keys\n",
+ (unsigned int) response_code,
+ (int) pkr.hr.ec);
+ break;
+ }
+ if (NULL != ph->cb)
+ {
+ ph->cb (ph->cb_cls,
+ &pkr);
+ ph->cb = NULL;
+ }
+ TALER_EXCHANGE_post_management_keys_cancel (ph);
+}
+
+
+struct TALER_EXCHANGE_ManagementPostKeysHandle *
+TALER_EXCHANGE_post_management_keys (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_EXCHANGE_ManagementPostKeysData *pkd,
+ TALER_EXCHANGE_ManagementPostKeysCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementPostKeysHandle *ph;
+ CURL *eh;
+ json_t *body;
+ json_t *denom_sigs;
+ json_t *signkey_sigs;
+
+ ph = GNUNET_new (struct TALER_EXCHANGE_ManagementPostKeysHandle);
+ ph->cb = cb;
+ ph->cb_cls = cb_cls;
+ ph->ctx = ctx;
+ ph->url = TALER_url_join (url,
+ "management/keys",
+ NULL);
+ if (NULL == ph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (ph);
+ return NULL;
+ }
+ denom_sigs = json_array ();
+ GNUNET_assert (NULL != denom_sigs);
+ for (unsigned int i = 0; i<pkd->num_denom_sigs; i++)
+ {
+ const struct TALER_EXCHANGE_DenominationKeySignature *dks
+ = &pkd->denom_sigs[i];
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ denom_sigs,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &dks->h_denom_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &dks->master_sig))));
+ }
+ signkey_sigs = json_array ();
+ GNUNET_assert (NULL != signkey_sigs);
+ for (unsigned int i = 0; i<pkd->num_sign_sigs; i++)
+ {
+ const struct TALER_EXCHANGE_SigningKeySignature *sks
+ = &pkd->sign_sigs[i];
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ signkey_sigs,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &sks->exchange_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &sks->master_sig))));
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("denom_sigs",
+ denom_sigs),
+ GNUNET_JSON_pack_array_steal ("signkey_sigs",
+ signkey_sigs));
+ eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&ph->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (ph->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ ph->url);
+ ph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ ph->post_ctx.headers,
+ &handle_post_keys_finished,
+ ph);
+ if (NULL == ph->job)
+ {
+ TALER_EXCHANGE_post_management_keys_cancel (ph);
+ return NULL;
+ }
+ return ph;
+}
+
+
+void
+TALER_EXCHANGE_post_management_keys_cancel (
+ struct TALER_EXCHANGE_ManagementPostKeysHandle *ph)
+{
+ if (NULL != ph->job)
+ {
+ GNUNET_CURL_job_cancel (ph->job);
+ ph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&ph->post_ctx);
+ GNUNET_free (ph->url);
+ GNUNET_free (ph);
+}
diff --git a/src/lib/exchange_api_management_revoke_denomination_key.c b/src/lib/exchange_api_management_revoke_denomination_key.c
new file mode 100644
index 000000000..a57704776
--- /dev/null
+++ b/src/lib/exchange_api_management_revoke_denomination_key.c
@@ -0,0 +1,222 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2024 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 lib/exchange_api_management_revoke_denomination_key.c
+ * @brief functions to revoke an exchange denomination key
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+/**
+ * @brief Handle for a POST /management/denominations/$H_DENOM_PUB/revoke request.
+ */
+struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/denominations/$H_DENOM_PUB/revoke request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_revoke_denomination_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementRevokeDenominationResponse rdr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ rh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ rdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rdr.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ rdr.hr.ec = TALER_JSON_get_error_code (json);
+ rdr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rdr.hr.ec = TALER_JSON_get_error_code (json);
+ rdr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management revoke denomination\n",
+ (unsigned int) response_code,
+ (int) rdr.hr.ec);
+ break;
+ }
+ if (NULL != rh->cb)
+ {
+ rh->cb (rh->cb_cls,
+ &rdr);
+ rh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_revoke_denomination_key_cancel (rh);
+}
+
+
+struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *
+TALER_EXCHANGE_management_revoke_denomination_key (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh;
+ CURL *eh;
+ json_t *body;
+
+ rh = GNUNET_new (struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle);
+ rh->cb = cb;
+ rh->cb_cls = cb_cls;
+ rh->ctx = ctx;
+ {
+ char epub_str[sizeof (*h_denom_pub) * 2];
+ char arg_str[sizeof (epub_str) + 64];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (h_denom_pub,
+ sizeof (*h_denom_pub),
+ epub_str,
+ sizeof (epub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "management/denominations/%s/revoke",
+ epub_str);
+ rh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ }
+ if (NULL == rh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (rh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig));
+ if (NULL == body)
+ {
+ GNUNET_break (0);
+ GNUNET_free (rh->url);
+ GNUNET_free (rh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (rh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&rh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (rh->url);
+ GNUNET_free (rh);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ rh->url);
+ rh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ rh->post_ctx.headers,
+ &handle_revoke_denomination_finished,
+ rh);
+ if (NULL == rh->job)
+ {
+ TALER_EXCHANGE_management_revoke_denomination_key_cancel (rh);
+ return NULL;
+ }
+ return rh;
+}
+
+
+void
+TALER_EXCHANGE_management_revoke_denomination_key_cancel (
+ struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh)
+{
+ if (NULL != rh->job)
+ {
+ GNUNET_CURL_job_cancel (rh->job);
+ rh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&rh->post_ctx);
+ GNUNET_free (rh->url);
+ GNUNET_free (rh);
+}
diff --git a/src/lib/exchange_api_management_revoke_signing_key.c b/src/lib/exchange_api_management_revoke_signing_key.c
new file mode 100644
index 000000000..d2fa78264
--- /dev/null
+++ b/src/lib/exchange_api_management_revoke_signing_key.c
@@ -0,0 +1,212 @@
+/*
+ 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 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 lib/exchange_api_management_revoke_signing_key.c
+ * @brief functions to revoke an exchange online signing key
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementRevokeSigningKeyCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/signkeys/%s/revoke request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_revoke_signing_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse rsr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ rh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ rsr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rsr.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ rsr.hr.ec = TALER_JSON_get_error_code (json);
+ rsr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rsr.hr.ec = TALER_JSON_get_error_code (json);
+ rsr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management revoke signkey\n",
+ (unsigned int) response_code,
+ (int) rsr.hr.ec);
+ break;
+ }
+ if (NULL != rh->cb)
+ {
+ rh->cb (rh->cb_cls,
+ &rsr);
+ rh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_revoke_signing_key_cancel (rh);
+}
+
+
+struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *
+TALER_EXCHANGE_management_revoke_signing_key (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementRevokeSigningKeyCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh;
+ CURL *eh;
+ json_t *body;
+
+ rh = GNUNET_new (struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle);
+ rh->cb = cb;
+ rh->cb_cls = cb_cls;
+ rh->ctx = ctx;
+ {
+ char epub_str[sizeof (*exchange_pub) * 2];
+ char arg_str[sizeof (epub_str) + 64];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (exchange_pub,
+ sizeof (*exchange_pub),
+ epub_str,
+ sizeof (epub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "management/signkeys/%s/revoke",
+ epub_str);
+ rh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ }
+ if (NULL == rh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (rh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig));
+ eh = TALER_EXCHANGE_curl_easy_get_ (rh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&rh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (rh->url);
+ GNUNET_free (rh);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ rh->url);
+ rh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ rh->post_ctx.headers,
+ &handle_revoke_signing_finished,
+ rh);
+ if (NULL == rh->job)
+ {
+ TALER_EXCHANGE_management_revoke_signing_key_cancel (rh);
+ return NULL;
+ }
+ return rh;
+}
+
+
+void
+TALER_EXCHANGE_management_revoke_signing_key_cancel (
+ struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh)
+{
+ if (NULL != rh->job)
+ {
+ GNUNET_CURL_job_cancel (rh->job);
+ rh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&rh->post_ctx);
+ GNUNET_free (rh->url);
+ GNUNET_free (rh);
+}
diff --git a/src/lib/exchange_api_management_set_global_fee.c b/src/lib/exchange_api_management_set_global_fee.c
new file mode 100644
index 000000000..f6282a812
--- /dev/null
+++ b/src/lib/exchange_api_management_set_global_fee.c
@@ -0,0 +1,236 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-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/>
+*/
+/**
+ * @file lib/exchange_api_management_set_global_fee.c
+ * @brief functions to set global fees at an exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_curl_defaults.h"
+#include "taler_exchange_service.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementSetGlobalFeeCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/global request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_set_global_fee_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementSetGlobalFeeResponse sfr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ sgfh->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ sfr.hr.ec = TALER_JSON_get_error_code (json);
+ sfr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ sgfh->url);
+ if (NULL != json)
+ {
+ sfr.hr.ec = TALER_JSON_get_error_code (json);
+ sfr.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ sfr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ sfr.hr.hint = TALER_ErrorCode_get_hint (sfr.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ sfr.hr.ec = TALER_JSON_get_error_code (json);
+ sfr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ sfr.hr.ec = TALER_JSON_get_error_code (json);
+ sfr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ sfr.hr.ec = TALER_JSON_get_error_code (json);
+ sfr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management set global fee\n",
+ (unsigned int) response_code,
+ (int) sfr.hr.ec);
+ break;
+ }
+ if (NULL != sgfh->cb)
+ {
+ sgfh->cb (sgfh->cb_cls,
+ &sfr);
+ sgfh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_set_global_fees_cancel (sgfh);
+}
+
+
+struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *
+TALER_EXCHANGE_management_set_global_fees (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_base_url,
+ struct GNUNET_TIME_Timestamp validity_start,
+ struct GNUNET_TIME_Timestamp validity_end,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementSetGlobalFeeCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh;
+ CURL *eh;
+ json_t *body;
+
+ sgfh = GNUNET_new (struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle);
+ sgfh->cb = cb;
+ sgfh->cb_cls = cb_cls;
+ sgfh->ctx = ctx;
+ sgfh->url = TALER_url_join (exchange_base_url,
+ "management/global-fee",
+ NULL);
+ if (NULL == sgfh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (sgfh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_timestamp ("fee_start",
+ validity_start),
+ GNUNET_JSON_pack_timestamp ("fee_end",
+ validity_end),
+ TALER_JSON_pack_amount ("history_fee",
+ &fees->history),
+ TALER_JSON_pack_amount ("account_fee",
+ &fees->account),
+ TALER_JSON_pack_amount ("purse_fee",
+ &fees->purse),
+ GNUNET_JSON_pack_time_rel ("purse_timeout",
+ purse_timeout),
+ GNUNET_JSON_pack_time_rel ("history_expiration",
+ history_expiration),
+ GNUNET_JSON_pack_uint64 ("purse_account_limit",
+ purse_account_limit));
+ eh = TALER_EXCHANGE_curl_easy_get_ (sgfh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&sgfh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (sgfh->url);
+ GNUNET_free (sgfh);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ sgfh->url);
+ sgfh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ sgfh->post_ctx.headers,
+ &handle_set_global_fee_finished,
+ sgfh);
+ if (NULL == sgfh->job)
+ {
+ TALER_EXCHANGE_management_set_global_fees_cancel (sgfh);
+ return NULL;
+ }
+ return sgfh;
+}
+
+
+void
+TALER_EXCHANGE_management_set_global_fees_cancel (
+ struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh)
+{
+ if (NULL != sgfh->job)
+ {
+ GNUNET_CURL_job_cancel (sgfh->job);
+ sgfh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&sgfh->post_ctx);
+ GNUNET_free (sgfh->url);
+ GNUNET_free (sgfh);
+}
diff --git a/src/lib/exchange_api_management_set_wire_fee.c b/src/lib/exchange_api_management_set_wire_fee.c
new file mode 100644
index 000000000..aaeae21f4
--- /dev/null
+++ b/src/lib/exchange_api_management_set_wire_fee.c
@@ -0,0 +1,228 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-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/>
+*/
+/**
+ * @file lib/exchange_api_management_set_wire_fee.c
+ * @brief functions to set wire fees at an exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_curl_defaults.h"
+#include "taler_exchange_service.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementSetWireFeeHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementSetWireFeeCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/wire request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_set_wire_fee_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementSetWireFeeResponse swr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ swfh->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ swr.hr.ec = TALER_JSON_get_error_code (json);
+ swr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ swfh->url);
+ if (NULL != json)
+ {
+ swr.hr.ec = TALER_JSON_get_error_code (json);
+ swr.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ swr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ swr.hr.hint = TALER_ErrorCode_get_hint (swr.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ swr.hr.ec = TALER_JSON_get_error_code (json);
+ swr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ swr.hr.ec = TALER_JSON_get_error_code (json);
+ swr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ swr.hr.ec = TALER_JSON_get_error_code (json);
+ swr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management set wire fee\n",
+ (unsigned int) response_code,
+ (int) swr.hr.ec);
+ break;
+ }
+ if (NULL != swfh->cb)
+ {
+ swfh->cb (swfh->cb_cls,
+ &swr);
+ swfh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_set_wire_fees_cancel (swfh);
+}
+
+
+struct TALER_EXCHANGE_ManagementSetWireFeeHandle *
+TALER_EXCHANGE_management_set_wire_fees (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_base_url,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp validity_start,
+ struct GNUNET_TIME_Timestamp validity_end,
+ const struct TALER_WireFeeSet *fees,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementSetWireFeeCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh;
+ CURL *eh;
+ json_t *body;
+
+ swfh = GNUNET_new (struct TALER_EXCHANGE_ManagementSetWireFeeHandle);
+ swfh->cb = cb;
+ swfh->cb_cls = cb_cls;
+ swfh->ctx = ctx;
+ swfh->url = TALER_url_join (exchange_base_url,
+ "management/wire-fee",
+ NULL);
+ if (NULL == swfh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (swfh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("wire_method",
+ wire_method),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_timestamp ("fee_start",
+ validity_start),
+ GNUNET_JSON_pack_timestamp ("fee_end",
+ validity_end),
+ TALER_JSON_pack_amount ("closing_fee",
+ &fees->closing),
+ TALER_JSON_pack_amount ("wire_fee",
+ &fees->wire));
+ eh = TALER_EXCHANGE_curl_easy_get_ (swfh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&swfh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (swfh->url);
+ GNUNET_free (swfh);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ swfh->url);
+ swfh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ swfh->post_ctx.headers,
+ &handle_set_wire_fee_finished,
+ swfh);
+ if (NULL == swfh->job)
+ {
+ TALER_EXCHANGE_management_set_wire_fees_cancel (swfh);
+ return NULL;
+ }
+ return swfh;
+}
+
+
+void
+TALER_EXCHANGE_management_set_wire_fees_cancel (
+ struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh)
+{
+ if (NULL != swfh->job)
+ {
+ GNUNET_CURL_job_cancel (swfh->job);
+ swfh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&swfh->post_ctx);
+ GNUNET_free (swfh->url);
+ GNUNET_free (swfh);
+}
diff --git a/src/lib/exchange_api_management_update_aml_officer.c b/src/lib/exchange_api_management_update_aml_officer.c
new file mode 100644
index 000000000..af0169b02
--- /dev/null
+++ b/src/lib/exchange_api_management_update_aml_officer.c
@@ -0,0 +1,230 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_update_aml_officer.c
+ * @brief functions to update AML officer status
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementUpdateAmlOfficer
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/wire request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_update_aml_officer_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse uar = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ uar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ uar.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ uar.hr.ec = TALER_JSON_get_error_code (json);
+ uar.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ wh->url);
+ if (NULL != json)
+ {
+ uar.hr.ec = TALER_JSON_get_error_code (json);
+ uar.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ uar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ uar.hr.hint = TALER_ErrorCode_get_hint (uar.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ uar.hr.ec = TALER_JSON_get_error_code (json);
+ uar.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ uar.hr.ec = TALER_JSON_get_error_code (json);
+ uar.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management update AML officer\n",
+ (unsigned int) response_code,
+ (int) uar.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &uar);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_update_aml_officer_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *
+TALER_EXCHANGE_management_update_aml_officer (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *officer_name,
+ struct GNUNET_TIME_Timestamp change_date,
+ bool is_active,
+ bool read_only,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh;
+ CURL *eh;
+ json_t *body;
+
+ wh = GNUNET_new (struct TALER_EXCHANGE_ManagementUpdateAmlOfficer);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ctx = ctx;
+ wh->url = TALER_url_join (url,
+ "management/aml-officers",
+ NULL);
+ if (NULL == wh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("officer_name",
+ officer_name),
+ GNUNET_JSON_pack_data_auto ("officer_pub",
+ officer_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_bool ("is_active",
+ is_active),
+ GNUNET_JSON_pack_bool ("read_only",
+ read_only),
+ GNUNET_JSON_pack_timestamp ("change_date",
+ change_date));
+ eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&wh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ wh->url);
+ wh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ wh->post_ctx.headers,
+ &handle_update_aml_officer_finished,
+ wh);
+ if (NULL == wh->job)
+ {
+ TALER_EXCHANGE_management_update_aml_officer_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_management_update_aml_officer_cancel (
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wh->post_ctx);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_management_wire_disable.c b/src/lib/exchange_api_management_wire_disable.c
new file mode 100644
index 000000000..23b10c58c
--- /dev/null
+++ b/src/lib/exchange_api_management_wire_disable.c
@@ -0,0 +1,221 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_wire_disable.c
+ * @brief functions to disable an exchange wire method / bank account
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementWireDisableHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementWireDisableCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/wire/disable request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorDisableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_auditor_disable_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementWireDisableHandle *wh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementWireDisableResponse wdr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ wdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ wdr.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ wdr.hr.ec = TALER_JSON_get_error_code (json);
+ wdr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ wh->url);
+ if (NULL != json)
+ {
+ wdr.hr.ec = TALER_JSON_get_error_code (json);
+ wdr.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ wdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ wdr.hr.hint = TALER_ErrorCode_get_hint (wdr.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ wdr.hr.ec = TALER_JSON_get_error_code (json);
+ wdr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ wdr.hr.ec = TALER_JSON_get_error_code (json);
+ wdr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d exchange management disable wire\n",
+ (unsigned int) response_code,
+ (int) wdr.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &wdr);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_disable_wire_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_ManagementWireDisableHandle *
+TALER_EXCHANGE_management_disable_wire (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp validity_end,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementWireDisableCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementWireDisableHandle *wh;
+ CURL *eh;
+ json_t *body;
+
+ wh = GNUNET_new (struct TALER_EXCHANGE_ManagementWireDisableHandle);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ctx = ctx;
+ wh->url = TALER_url_join (url,
+ "management/wire/disable",
+ NULL);
+ if (NULL == wh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ payto_uri),
+ GNUNET_JSON_pack_data_auto ("master_sig_del",
+ master_sig),
+ GNUNET_JSON_pack_timestamp ("validity_end",
+ validity_end));
+ eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&wh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ wh->url);
+ wh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ wh->post_ctx.headers,
+ &handle_auditor_disable_finished,
+ wh);
+ if (NULL == wh->job)
+ {
+ TALER_EXCHANGE_management_disable_wire_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_management_disable_wire_cancel (
+ struct TALER_EXCHANGE_ManagementWireDisableHandle *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wh->post_ctx);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_management_wire_enable.c b/src/lib/exchange_api_management_wire_enable.c
new file mode 100644
index 000000000..9a163b558
--- /dev/null
+++ b/src/lib/exchange_api_management_wire_enable.c
@@ -0,0 +1,253 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_wire_enable.c
+ * @brief functions to enable an exchange wire method / bank account
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementWireEnableHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementWireEnableCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/wire request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_auditor_enable_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementWireEnableHandle *wh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementWireEnableResponse wer = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ wer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ wer.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ wer.hr.ec = TALER_JSON_get_error_code (json);
+ wer.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ wh->url);
+ if (NULL != json)
+ {
+ wer.hr.ec = TALER_JSON_get_error_code (json);
+ wer.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ wer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ wer.hr.hint = TALER_ErrorCode_get_hint (wer.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ wer.hr.ec = TALER_JSON_get_error_code (json);
+ wer.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ wer.hr.ec = TALER_JSON_get_error_code (json);
+ wer.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management enable wire\n",
+ (unsigned int) response_code,
+ (int) wer.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &wer);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_enable_wire_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_ManagementWireEnableHandle *
+TALER_EXCHANGE_management_enable_wire (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp validity_start,
+ const struct TALER_MasterSignatureP *master_sig1,
+ const struct TALER_MasterSignatureP *master_sig2,
+ const char *bank_label,
+ int64_t priority,
+ TALER_EXCHANGE_ManagementWireEnableCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementWireEnableHandle *wh;
+ CURL *eh;
+ json_t *body;
+
+ {
+ char *msg = TALER_payto_validate (payto_uri);
+
+ if (NULL != msg)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "payto URI is malformed: %s\n",
+ msg);
+ GNUNET_free (msg);
+ return NULL;
+ }
+ }
+ wh = GNUNET_new (struct TALER_EXCHANGE_ManagementWireEnableHandle);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ctx = ctx;
+ wh->url = TALER_url_join (url,
+ "management/wire",
+ NULL);
+ if (NULL == wh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ payto_uri),
+ GNUNET_JSON_pack_array_incref ("debit_restrictions",
+ (json_t *) debit_restrictions),
+ GNUNET_JSON_pack_array_incref ("credit_restrictions",
+ (json_t *) credit_restrictions),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("conversion_url",
+ conversion_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("bank_label",
+ bank_label)),
+ GNUNET_JSON_pack_int64 ("priority",
+ priority),
+ GNUNET_JSON_pack_data_auto ("master_sig_add",
+ master_sig1),
+ GNUNET_JSON_pack_data_auto ("master_sig_wire",
+ master_sig2),
+ GNUNET_JSON_pack_timestamp ("validity_start",
+ validity_start));
+ eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&wh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ wh->url);
+ wh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ wh->post_ctx.headers,
+ &handle_auditor_enable_finished,
+ wh);
+ if (NULL == wh->job)
+ {
+ TALER_EXCHANGE_management_enable_wire_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_management_enable_wire_cancel (
+ struct TALER_EXCHANGE_ManagementWireEnableHandle *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wh->post_ctx);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c
index d6acf92c7..c2f8cefb7 100644
--- a/src/lib/exchange_api_melt.c
+++ b/src/lib/exchange_api_melt.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2020 Taler Systems SA
+ Copyright (C) 2015-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
@@ -27,6 +27,7 @@
#include <gnunet/gnunet_curl_lib.h>
#include "taler_json_lib.h"
#include "taler_exchange_service.h"
+#include "exchange_api_common.h"
#include "exchange_api_handle.h"
#include "taler_signatures.h"
#include "exchange_api_curl_defaults.h"
@@ -40,9 +41,9 @@ struct TALER_EXCHANGE_MeltHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -50,6 +51,16 @@ struct TALER_EXCHANGE_MeltHandle
char *url;
/**
+ * The exchange base url.
+ */
+ char *exchange_url;
+
+ /**
+ * Curl context.
+ */
+ struct GNUNET_CURL_Context *cctx;
+
+ /**
* Context for #TEH_curl_easy_post(). Keeps the data that must
* persist for Curl to make the upload.
*/
@@ -73,12 +84,53 @@ struct TALER_EXCHANGE_MeltHandle
/**
* Actual information about the melt operation.
*/
- struct MeltData *md;
+ struct MeltData md;
+
+ /**
+ * The secret the entire melt operation is seeded from.
+ */
+ struct TALER_RefreshMasterSecretP rms;
+
+ /**
+ * Details about the characteristics of the requested melt operation.
+ */
+ const struct TALER_EXCHANGE_RefreshData *rd;
+
+ /**
+ * Array of `num_fresh_coins` per-coin values
+ * returned from melt operation.
+ */
+ struct TALER_EXCHANGE_MeltBlindingDetail *mbds;
+
+ /**
+ * Handle for the preflight request, or NULL.
+ */
+ struct TALER_EXCHANGE_CsRMeltHandle *csr;
+
+ /**
+ * Public key of the coin being melted.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Signature affirming the melt.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
/**
* @brief Public information about the coin's denomination key
*/
- struct TALER_EXCHANGE_DenomPublicKey dki;
+ const struct TALER_EXCHANGE_DenomPublicKey *dki;
+
+ /**
+ * Gamma value chosen by the exchange during melt.
+ */
+ uint32_t noreveal_index;
+
+ /**
+ * True if we need to include @e rms in our melt request.
+ */
+ bool send_rms;
};
@@ -86,27 +138,27 @@ struct TALER_EXCHANGE_MeltHandle
* Verify that the signature on the "200 OK" response
* from the exchange is valid.
*
- * @param mh melt handle
+ * @param[in,out] mh melt handle
* @param json json reply with the signature
* @param[out] exchange_pub public key of the exchange used for the signature
- * @param[out] noreveal_index set to the noreveal index selected by the exchange
* @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
*/
-static int
+static enum GNUNET_GenericReturnValue
verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
const json_t *json,
- struct TALER_ExchangePublicKeyP *exchange_pub,
- uint32_t *noreveal_index)
+ struct TALER_ExchangePublicKeyP *exchange_pub)
{
struct TALER_ExchangeSignatureP exchange_sig;
const struct TALER_EXCHANGE_Keys *key_state;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub", exchange_pub),
- GNUNET_JSON_spec_uint32 ("noreveal_index", noreveal_index),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ exchange_pub),
+ GNUNET_JSON_spec_uint32 ("noreveal_index",
+ &mh->noreveal_index),
GNUNET_JSON_spec_end ()
};
- struct TALER_RefreshMeltConfirmationPS confirm;
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
@@ -116,9 +168,8 @@ verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
-
/* check that exchange signing key is permitted */
- key_state = TALER_EXCHANGE_get_keys (mh->exchange);
+ key_state = mh->keys;
if (GNUNET_OK !=
TALER_EXCHANGE_test_signing_key (key_state,
exchange_pub))
@@ -128,23 +179,18 @@ verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
}
/* check that noreveal index is in permitted range */
- if (TALER_CNC_KAPPA <= *noreveal_index)
+ if (TALER_CNC_KAPPA <= mh->noreveal_index)
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- /* verify signature by exchange */
- confirm.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT);
- confirm.purpose.size = htonl (sizeof (struct
- TALER_RefreshMeltConfirmationPS));
- confirm.rc = mh->md->rc;
- confirm.noreveal_index = htonl (*noreveal_index);
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT,
- &confirm.purpose,
- &exchange_sig.eddsa_signature,
- &exchange_pub->eddsa_pub))
+ TALER_exchange_online_melt_confirmation_verify (
+ &mh->md.rc,
+ mh->noreveal_index,
+ exchange_pub,
+ &exchange_sig))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
@@ -154,103 +200,6 @@ verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
/**
- * Verify that the signatures on the "409 CONFLICT" response from the
- * exchange demonstrating customer double-spending are valid.
- *
- * @param mh melt handle
- * @param json json reply with the signature(s) and transaction history
- * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not
- */
-static int
-verify_melt_signature_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
- const json_t *json)
-{
- json_t *history;
- struct TALER_Amount original_value;
- struct TALER_Amount melt_value_with_fee;
- struct TALER_Amount total;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("history", &history),
- GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub),
- TALER_JSON_spec_amount ("original_value", &original_value),
- TALER_JSON_spec_amount ("requested_value", &melt_value_with_fee),
- GNUNET_JSON_spec_end ()
- };
- const struct MeltedCoin *mc;
-
- /* parse JSON reply */
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- /* Find out which coin was deemed problematic by the exchange */
- mc = &mh->md->melted_coin;
-
- /* check basic coin properties */
- if (0 != TALER_amount_cmp (&original_value,
- &mc->original_value))
- {
- /* We disagree on the value of the coin */
- GNUNET_break_op (0);
- json_decref (history);
- return GNUNET_SYSERR;
- }
- if (0 != TALER_amount_cmp (&melt_value_with_fee,
- &mc->melt_amount_with_fee))
- {
- /* We disagree on the value of the coin */
- GNUNET_break_op (0);
- json_decref (history);
- return GNUNET_SYSERR;
- }
-
- /* verify coin history */
- history = json_object_get (json,
- "history");
- if (GNUNET_OK !=
- TALER_EXCHANGE_verify_coin_history (&mh->dki,
- original_value.currency,
- &coin_pub,
- history,
- &total))
- {
- GNUNET_break_op (0);
- json_decref (history);
- return GNUNET_SYSERR;
- }
- json_decref (history);
-
- /* check if melt operation was really too expensive given history */
- if (GNUNET_OK !=
- TALER_amount_add (&total,
- &total,
- &melt_value_with_fee))
- {
- /* clearly not OK if our transaction would have caused
- the overflow... */
- return GNUNET_OK;
- }
-
- if (0 >= TALER_amount_cmp (&total,
- &original_value))
- {
- /* transaction should have still fit */
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
-
- /* everything OK, valid proof of double-spending was provided */
- return GNUNET_OK;
-}
-
-
-/**
* Function called when we're done processing the
* HTTP /coins/$COIN_PUB/melt request.
*
@@ -264,216 +213,182 @@ handle_melt_finished (void *cls,
const void *response)
{
struct TALER_EXCHANGE_MeltHandle *mh = cls;
- uint32_t noreveal_index = TALER_CNC_KAPPA; /* invalid value */
- struct TALER_ExchangePublicKeyP exchange_pub;
const json_t *j = response;
- enum TALER_ErrorCode ec;
+ struct TALER_EXCHANGE_MeltResponse mr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
mh->job = NULL;
switch (response_code)
{
case 0:
- ec = TALER_EC_INVALID_RESPONSE;
+ mr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
verify_melt_signature_ok (mh,
j,
- &exchange_pub,
- &noreveal_index))
+ &mr.details.ok.sign_key))
{
GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
- }
- else
- {
- ec = TALER_EC_NONE;
- }
- if (NULL != mh->melt_cb)
- {
- mh->melt_cb (mh->melt_cb_cls,
- response_code,
- ec,
- noreveal_index,
- (0 == response_code)
- ? NULL
- : &exchange_pub,
- j);
- mh->melt_cb = NULL;
+ mr.hr.http_status = 0;
+ mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
}
+ mr.details.ok.noreveal_index = mh->noreveal_index;
+ mr.details.ok.num_mbds = mh->rd->fresh_pks_len;
+ mr.details.ok.mbds = mh->mbds;
+ mh->melt_cb (mh->melt_cb_cls,
+ &mr);
+ mh->melt_cb = NULL;
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_CONFLICT:
- /* Double spending; check signatures on transaction history */
- if (GNUNET_OK !=
- verify_melt_signature_conflict (mh,
- j))
- {
- GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
- }
- else
- ec = TALER_EC_NONE;
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_FORBIDDEN:
/* Nothing really to verify, exchange says one of the signatures is
invalid; assuming we checked them, this should never happen, we
should pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
- ec = TALER_JSON_get_error_code (j);
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
- ec = TALER_JSON_get_error_code (j);
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
+ "Unexpected response code %u/%d for exchange melt\n",
(unsigned int) response_code,
- ec);
- GNUNET_break (0);
- response_code = 0;
+ mr.hr.ec);
+ GNUNET_break_op (0);
break;
}
if (NULL != mh->melt_cb)
mh->melt_cb (mh->melt_cb_cls,
- response_code,
- ec,
- UINT32_MAX,
- NULL,
- j);
+ &mr);
TALER_EXCHANGE_melt_cancel (mh);
}
/**
- * Submit a melt request to the exchange and get the exchange's
- * response.
- *
- * This API is typically used by a wallet. Note that to ensure that
- * no money is lost in case of hardware failures, the provided
- * argument should have been constructed using
- * #TALER_EXCHANGE_refresh_prepare and committed to persistent storage
- * prior to calling this function.
+ * Start the actual melt operation, now that we have
+ * the exchange's input values.
*
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param refresh_data_length size of the @a refresh_data (returned
- * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare())
- * @param refresh_data the refresh data as returned from
- #TALER_EXCHANGE_refresh_prepare())
- * @param melt_cb the callback to call with the result
- * @param melt_cb_cls closure for @a melt_cb
- * @return a handle for this request; NULL if the argument was invalid.
- * In this case, neither callback will be called.
+ * @param[in,out] mh melt operation to run
+ * @return #GNUNET_OK if we could start the operation
*/
-struct TALER_EXCHANGE_MeltHandle *
-TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
- size_t refresh_data_length,
- const char *refresh_data,
- TALER_EXCHANGE_MeltCallback melt_cb,
- void *melt_cb_cls)
+static enum GNUNET_GenericReturnValue
+start_melt (struct TALER_EXCHANGE_MeltHandle *mh)
{
const struct TALER_EXCHANGE_Keys *key_state;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
json_t *melt_obj;
- struct TALER_EXCHANGE_MeltHandle *mh;
CURL *eh;
- struct GNUNET_CURL_Context *ctx;
- struct MeltData *md;
- struct TALER_CoinSpendSignatureP confirm_sig;
- struct TALER_RefreshMeltCoinAffirmationPS melt;
- struct GNUNET_HashCode h_denom_pub;
char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_ExchangeWithdrawValues alg_values[mh->rd->fresh_pks_len];
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
- md = TALER_EXCHANGE_deserialize_melt_data_ (refresh_data,
- refresh_data_length);
- if (NULL == md)
+ for (unsigned int i = 0; i<mh->rd->fresh_pks_len; i++)
{
- GNUNET_break (0);
- return NULL;
+ if (GNUNET_CRYPTO_BSA_RSA ==
+ mh->rd->fresh_pks[i].key.bsign_pub_key->cipher)
+ alg_values[i] = *TALER_denom_ewv_rsa_singleton ();
+ else
+ alg_values[i] = mh->mbds[i].alg_value;
}
- melt.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
- melt.purpose.size = htonl (sizeof (struct
- TALER_RefreshMeltCoinAffirmationPS));
- melt.rc = md->rc;
- TALER_amount_hton (&melt.amount_with_fee,
- &md->melted_coin.melt_amount_with_fee);
- TALER_amount_hton (&melt.melt_fee,
- &md->melted_coin.fee_melt);
- GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv,
- &melt.coin_pub.eddsa_pub);
- GNUNET_CRYPTO_eddsa_sign (&md->melted_coin.coin_priv.eddsa_priv,
- &melt.purpose,
- &confirm_sig.eddsa_signature);
- GNUNET_CRYPTO_rsa_public_key_hash (md->melted_coin.pub_key.rsa_public_key,
- &h_denom_pub);
- melt_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}",
- "coin_pub",
- GNUNET_JSON_from_data_auto (&melt.coin_pub),
- "denom_pub_hash",
- GNUNET_JSON_from_data_auto (&h_denom_pub),
- "denom_sig",
- GNUNET_JSON_from_rsa_signature (
- md->melted_coin.sig.rsa_signature),
- "confirm_sig",
- GNUNET_JSON_from_data_auto (&confirm_sig),
- "value_with_fee",
- TALER_JSON_from_amount (
- &md->melted_coin.melt_amount_with_fee),
- "rc",
- GNUNET_JSON_from_data_auto (&melt.rc));
- if (NULL == melt_obj)
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_get_melt_data_ (&mh->rms,
+ mh->rd,
+ alg_values,
+ &mh->md))
{
GNUNET_break (0);
- TALER_EXCHANGE_free_melt_data_ (md);
- return NULL;
+ return GNUNET_SYSERR;
}
+ TALER_denom_pub_hash (&mh->md.melted_coin.pub_key,
+ &h_denom_pub);
+ TALER_wallet_melt_sign (
+ &mh->md.melted_coin.melt_amount_with_fee,
+ &mh->md.melted_coin.fee_melt,
+ &mh->md.rc,
+ &h_denom_pub,
+ mh->md.melted_coin.h_age_commitment,
+ &mh->md.melted_coin.coin_priv,
+ &mh->coin_sig);
+ GNUNET_CRYPTO_eddsa_key_get_public (&mh->md.melted_coin.coin_priv.eddsa_priv,
+ &mh->coin_pub.eddsa_pub);
+ melt_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &h_denom_pub),
+ TALER_JSON_pack_denom_sig ("denom_sig",
+ &mh->md.melted_coin.sig),
+ GNUNET_JSON_pack_data_auto ("confirm_sig",
+ &mh->coin_sig),
+ TALER_JSON_pack_amount ("value_with_fee",
+ &mh->md.melted_coin.melt_amount_with_fee),
+ GNUNET_JSON_pack_data_auto ("rc",
+ &mh->md.rc),
+ GNUNET_JSON_pack_allow_null (
+ (NULL != mh->md.melted_coin.h_age_commitment)
+ ? GNUNET_JSON_pack_data_auto ("age_commitment_hash",
+ mh->md.melted_coin.h_age_commitment)
+ : GNUNET_JSON_pack_string ("age_commitment_hash",
+ NULL)),
+ GNUNET_JSON_pack_allow_null (
+ mh->send_rms
+ ? GNUNET_JSON_pack_data_auto ("rms",
+ &mh->rms)
+ : GNUNET_JSON_pack_string ("rms",
+ NULL)));
{
char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
char *end;
- end = GNUNET_STRINGS_data_to_string (&melt.coin_pub,
- sizeof (struct
- TALER_CoinSpendPublicKeyP),
- pub_str,
- sizeof (pub_str));
+ end = GNUNET_STRINGS_data_to_string (
+ &mh->coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ pub_str,
+ sizeof (pub_str));
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/coins/%s/melt",
+ "coins/%s/melt",
pub_str);
}
- key_state = TALER_EXCHANGE_get_keys (exchange);
- dki = TALER_EXCHANGE_get_denomination_key (key_state,
- &md->melted_coin.pub_key);
+ key_state = mh->keys;
+ mh->dki = TALER_EXCHANGE_get_denomination_key (key_state,
+ &mh->md.melted_coin.pub_key);
/* and now we can at last begin the actual request handling */
- mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle);
- mh->exchange = exchange;
- mh->dki = *dki;
- mh->dki.key.rsa_public_key = NULL; /* lifetime not warranted, so better
- not copy the pointer */
- mh->melt_cb = melt_cb;
- mh->melt_cb_cls = melt_cb_cls;
- mh->md = md;
- mh->url = TEAH_path_to_url (exchange,
- arg_str);
+
+ mh->url = TALER_url_join (mh->exchange_url,
+ arg_str,
+ NULL);
+ if (NULL == mh->url)
+ {
+ json_decref (melt_obj);
+ return GNUNET_SYSERR;
+ }
eh = TALER_EXCHANGE_curl_easy_get_ (mh->url);
if ( (NULL == eh) ||
(GNUNET_OK !=
@@ -485,39 +400,201 @@ TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
if (NULL != eh)
curl_easy_cleanup (eh);
json_decref (melt_obj);
- GNUNET_free (mh->url);
- GNUNET_free (mh);
- return NULL;
+ return GNUNET_SYSERR;
}
json_decref (melt_obj);
- ctx = TEAH_handle_to_context (exchange);
- mh->job = GNUNET_CURL_job_add2 (ctx,
+ mh->job = GNUNET_CURL_job_add2 (mh->cctx,
eh,
mh->ctx.headers,
&handle_melt_finished,
mh);
- return mh;
+ return GNUNET_OK;
+}
+
+
+/**
+ * The melt request @a mh failed, return an error to
+ * the application and cancel the operation.
+ *
+ * @param[in] mh melt request that failed
+ * @param ec error code to fail with
+ */
+static void
+fail_mh (struct TALER_EXCHANGE_MeltHandle *mh,
+ enum TALER_ErrorCode ec)
+{
+ struct TALER_EXCHANGE_MeltResponse mr = {
+ .hr.ec = ec
+ };
+
+ mh->melt_cb (mh->melt_cb_cls,
+ &mr);
+ TALER_EXCHANGE_melt_cancel (mh);
}
/**
- * Cancel a melt request. This function cannot be used
- * on a request handle if either callback was already invoked.
+ * Callbacks of this type are used to serve the result of submitting a
+ * CS R request to a exchange.
*
- * @param mh the refresh melt handle
+ * @param cls closure with our `struct TALER_EXCHANGE_MeltHandle *`
+ * @param csrr response details
*/
+static void
+csr_cb (void *cls,
+ const struct TALER_EXCHANGE_CsRMeltResponse *csrr)
+{
+ struct TALER_EXCHANGE_MeltHandle *mh = cls;
+ unsigned int nks_off = 0;
+
+ mh->csr = NULL;
+ if (MHD_HTTP_OK != csrr->hr.http_status)
+ {
+ struct TALER_EXCHANGE_MeltResponse mr = {
+ .hr = csrr->hr
+ };
+
+ mr.hr.hint = "/csr-melt failed";
+ mh->melt_cb (mh->melt_cb_cls,
+ &mr);
+ TALER_EXCHANGE_melt_cancel (mh);
+ return;
+ }
+ for (unsigned int i = 0; i<mh->rd->fresh_pks_len; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
+ &mh->rd->fresh_pks[i];
+ struct TALER_ExchangeWithdrawValues *wv = &mh->mbds[i].alg_value;
+
+ switch (fresh_pk->key.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_break (0);
+ fail_mh (mh,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
+ return;
+ case GNUNET_CRYPTO_BSA_RSA:
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ TALER_denom_ewv_copy (wv,
+ &csrr->details.ok.alg_values[nks_off]);
+ nks_off++;
+ break;
+ }
+ }
+ mh->send_rms = true;
+ if (GNUNET_OK !=
+ start_melt (mh))
+ {
+ GNUNET_break (0);
+ fail_mh (mh,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
+ return;
+ }
+}
+
+
+struct TALER_EXCHANGE_MeltHandle *
+TALER_EXCHANGE_melt (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_EXCHANGE_RefreshData *rd,
+ TALER_EXCHANGE_MeltCallback melt_cb,
+ void *melt_cb_cls)
+{
+ struct TALER_EXCHANGE_NonceKey nks[GNUNET_NZL (rd->fresh_pks_len)];
+ unsigned int nks_off = 0;
+ struct TALER_EXCHANGE_MeltHandle *mh;
+
+ if (0 == rd->fresh_pks_len)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle);
+ mh->noreveal_index = TALER_CNC_KAPPA; /* invalid value */
+ mh->cctx = ctx;
+ mh->exchange_url = GNUNET_strdup (url);
+ mh->rd = rd;
+ mh->rms = *rms;
+ mh->melt_cb = melt_cb;
+ mh->melt_cb_cls = melt_cb_cls;
+ mh->mbds = GNUNET_new_array (rd->fresh_pks_len,
+ struct TALER_EXCHANGE_MeltBlindingDetail);
+ for (unsigned int i = 0; i<rd->fresh_pks_len; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk = &rd->fresh_pks[i];
+
+ switch (fresh_pk->key.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_break (0);
+ GNUNET_free (mh->mbds);
+ GNUNET_free (mh);
+ return NULL;
+ case GNUNET_CRYPTO_BSA_RSA:
+ TALER_denom_ewv_copy (&mh->mbds[i].alg_value,
+ TALER_denom_ewv_rsa_singleton ());
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ nks[nks_off].pk = fresh_pk;
+ nks[nks_off].cnc_num = nks_off;
+ nks_off++;
+ break;
+ }
+ }
+ mh->keys = TALER_EXCHANGE_keys_incref (keys);
+ if (0 != nks_off)
+ {
+ mh->csr = TALER_EXCHANGE_csr_melt (ctx,
+ url,
+ rms,
+ nks_off,
+ nks,
+ &csr_cb,
+ mh);
+ if (NULL == mh->csr)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_melt_cancel (mh);
+ return NULL;
+ }
+ return mh;
+ }
+ if (GNUNET_OK !=
+ start_melt (mh))
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_melt_cancel (mh);
+ return NULL;
+ }
+ return mh;
+}
+
+
void
TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh)
{
+ for (unsigned int i = 0; i<mh->rd->fresh_pks_len; i++)
+ TALER_denom_ewv_free (&mh->mbds[i].alg_value);
if (NULL != mh->job)
{
GNUNET_CURL_job_cancel (mh->job);
mh->job = NULL;
}
- TALER_EXCHANGE_free_melt_data_ (mh->md); /* does not free 'md' itself */
- GNUNET_free (mh->md);
+ if (NULL != mh->csr)
+ {
+ TALER_EXCHANGE_csr_melt_cancel (mh->csr);
+ mh->csr = NULL;
+ }
+ TALER_EXCHANGE_free_melt_data_ (&mh->md); /* does not free 'md' itself */
+ GNUNET_free (mh->mbds);
GNUNET_free (mh->url);
+ GNUNET_free (mh->exchange_url);
TALER_curl_easy_post_finished (&mh->ctx);
+ TALER_EXCHANGE_keys_decref (mh->keys);
GNUNET_free (mh);
}
diff --git a/src/lib/exchange_api_purse_create_with_deposit.c b/src/lib/exchange_api_purse_create_with_deposit.c
new file mode 100644
index 000000000..fff898e57
--- /dev/null
+++ b/src/lib/exchange_api_purse_create_with_deposit.c
@@ -0,0 +1,656 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 lib/exchange_api_purse_create_with_deposit.c
+ * @brief Implementation of the client to create a purse with
+ * an initial set of deposits (and a contract)
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_common.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Information we track per deposited coin.
+ */
+struct Deposit
+{
+ /**
+ * Coin's public key.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Signature made with the coin.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * Coin's denomination.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Age restriction hash for the coin.
+ */
+ struct TALER_AgeCommitmentHash ahac;
+
+ /**
+ * How much did we say the coin contributed.
+ */
+ struct TALER_Amount contribution;
+};
+
+
+/**
+ * @brief A purse create with deposit handle
+ */
+struct TALER_EXCHANGE_PurseCreateDepositHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * The base URL of the exchange.
+ */
+ char *exchange_url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_PurseCreateDepositCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Expected value in the purse after fees.
+ */
+ struct TALER_Amount purse_value_after_fees;
+
+ /**
+ * Our encrypted contract (if we had any).
+ */
+ struct TALER_EncryptedContract econtract;
+
+ /**
+ * Public key of the merge capability.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Signature with the purse key on the request.
+ */
+ struct TALER_PurseContractSignatureP purse_sig;
+
+ /**
+ * Hash over the purse's contract terms.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * When does the purse expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * Array of @e num_deposit deposits.
+ */
+ struct Deposit *deposits;
+
+ /**
+ * How many deposits did we make?
+ */
+ unsigned int num_deposits;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /deposit request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_DepositHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_purse_create_deposit_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_PurseCreateDepositHandle *pch = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_PurseCreateDepositResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+ const struct TALER_EXCHANGE_Keys *keys = pch->keys;
+
+ pch->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_TIME_Timestamp etime;
+ struct TALER_Amount total_deposited;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_spec_timestamp ("exchange_timestamp",
+ &etime),
+ TALER_JSON_spec_amount ("total_deposited",
+ pch->purse_value_after_fees.currency,
+ &total_deposited),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (keys,
+ &exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_purse_created_verify (
+ etime,
+ pch->purse_expiration,
+ &pch->purse_value_after_fees,
+ &total_deposited,
+ &pch->purse_pub,
+ &pch->h_contract_terms,
+ &exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
+ break;
+ }
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ {
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ switch (dr.hr.ec)
+ {
+ case TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA:
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_create_conflict_ (
+ &pch->purse_sig,
+ &pch->purse_pub,
+ j))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
+ /* Nothing to check anymore here, proof needs to be
+ checked in the GET /coins/$COIN_PUB handler */
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
+ // FIXME #7267: write check (add to exchange_api_common! */
+ break;
+ case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA:
+ {
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_AgeCommitmentHash phac;
+ bool found = false;
+
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_coin_conflict_ (
+ &pch->purse_pub,
+ pch->exchange_url,
+ j,
+ &h_denom_pub,
+ &phac,
+ &coin_pub,
+ &coin_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ for (unsigned int i = 0; i<pch->num_deposits; i++)
+ {
+ struct Deposit *deposit = &pch->deposits[i];
+
+ if (0 !=
+ GNUNET_memcmp (&coin_pub,
+ &deposit->coin_pub))
+ continue;
+ if (0 !=
+ GNUNET_memcmp (&deposit->h_denom_pub,
+ &h_denom_pub))
+ {
+ found = true;
+ break;
+ }
+ if (0 !=
+ GNUNET_memcmp (&deposit->ahac,
+ &phac))
+ {
+ found = true;
+ break;
+ }
+ if (0 ==
+ GNUNET_memcmp (&coin_sig,
+ &deposit->coin_sig))
+ {
+ GNUNET_break_op (0);
+ continue;
+ }
+ found = true;
+ break;
+ }
+ if (! found)
+ {
+ /* conflict is for a different coin! */
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA:
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_econtract_conflict_ (
+ &pch->econtract.econtract_sig,
+ &pch->purse_pub,
+ j))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected error code %d for conflcting deposit\n",
+ dr.hr.ec);
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ }
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange deposit\n",
+ (unsigned int) response_code,
+ dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ pch->cb (pch->cb_cls,
+ &dr);
+ TALER_EXCHANGE_purse_create_with_deposit_cancel (pch);
+}
+
+
+struct TALER_EXCHANGE_PurseCreateDepositHandle *
+TALER_EXCHANGE_purse_create_with_deposit (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ const struct TALER_PurseMergePrivateKeyP *merge_priv,
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const json_t *contract_terms,
+ unsigned int num_deposits,
+ const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
+ bool upload_contract,
+ TALER_EXCHANGE_PurseCreateDepositCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_PurseCreateDepositHandle *pch;
+ json_t *create_obj;
+ json_t *deposit_arr;
+ CURL *eh;
+ char arg_str[sizeof (pch->purse_pub) * 2 + 32];
+ uint32_t min_age = 0;
+
+ pch = GNUNET_new (struct TALER_EXCHANGE_PurseCreateDepositHandle);
+ pch->cb = cb;
+ pch->cb_cls = cb_cls;
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("pay_deadline",
+ &pch->purse_expiration),
+ TALER_JSON_spec_amount_any ("amount",
+ &pch->purse_value_after_fees),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("minimum_age",
+ &min_age),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (contract_terms,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ }
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (contract_terms,
+ &pch->h_contract_terms))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
+ &pch->purse_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (pch->purse_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &pch->purse_pub,
+ sizeof (pch->purse_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "purses/%s/create",
+ pub_str);
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
+ &pch->merge_pub.eddsa_pub);
+ pch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == pch->url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ pch->num_deposits = num_deposits;
+ pch->deposits = GNUNET_new_array (num_deposits,
+ struct Deposit);
+ deposit_arr = json_array ();
+ GNUNET_assert (NULL != deposit_arr);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing with URL `%s'\n",
+ url);
+ for (unsigned int i = 0; i<num_deposits; i++)
+ {
+ const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i];
+ const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof;
+ struct Deposit *d = &pch->deposits[i];
+ json_t *jdeposit;
+ struct TALER_AgeCommitmentHash *aghp = NULL;
+ struct TALER_AgeAttestation attest;
+ struct TALER_AgeAttestation *attestp = NULL;
+
+ if (NULL != acp)
+ {
+ TALER_age_commitment_hash (&acp->commitment,
+ &d->ahac);
+ aghp = &d->ahac;
+ if (GNUNET_OK !=
+ TALER_age_commitment_attest (acp,
+ min_age,
+ &attest))
+ {
+ GNUNET_break (0);
+ GNUNET_array_grow (pch->deposits,
+ pch->num_deposits,
+ 0);
+ GNUNET_free (pch->url);
+ json_decref (deposit_arr);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ }
+ d->contribution = deposit->amount;
+ d->h_denom_pub = deposit->h_denom_pub;
+ GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv,
+ &d->coin_pub.eddsa_pub);
+ TALER_wallet_purse_deposit_sign (
+ url,
+ &pch->purse_pub,
+ &deposit->amount,
+ &d->h_denom_pub,
+ &d->ahac,
+ &deposit->coin_priv,
+ &d->coin_sig);
+ jdeposit = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ aghp)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("age_attestation",
+ attestp)),
+ TALER_JSON_pack_amount ("amount",
+ &deposit->amount),
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &deposit->h_denom_pub),
+ TALER_JSON_pack_denom_sig ("ub_sig",
+ &deposit->denom_sig),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &d->coin_sig),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &d->coin_pub));
+ GNUNET_assert (0 ==
+ json_array_append_new (deposit_arr,
+ jdeposit));
+ }
+ TALER_wallet_purse_create_sign (pch->purse_expiration,
+ &pch->h_contract_terms,
+ &pch->merge_pub,
+ min_age,
+ &pch->purse_value_after_fees,
+ purse_priv,
+ &pch->purse_sig);
+ if (upload_contract)
+ {
+ TALER_CRYPTO_contract_encrypt_for_merge (&pch->purse_pub,
+ contract_priv,
+ merge_priv,
+ contract_terms,
+ &pch->econtract.econtract,
+ &pch->econtract.econtract_size);
+ GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
+ &pch->econtract.contract_pub.ecdhe_pub);
+ TALER_wallet_econtract_upload_sign (pch->econtract.econtract,
+ pch->econtract.econtract_size,
+ &pch->econtract.contract_pub,
+ purse_priv,
+ &pch->econtract.econtract_sig);
+ }
+ create_obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("amount",
+ &pch->purse_value_after_fees),
+ GNUNET_JSON_pack_uint64 ("min_age",
+ min_age),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_econtract ("econtract",
+ upload_contract
+ ? &pch->econtract
+ : NULL)),
+ GNUNET_JSON_pack_data_auto ("purse_sig",
+ &pch->purse_sig),
+ GNUNET_JSON_pack_data_auto ("merge_pub",
+ &pch->merge_pub),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &pch->h_contract_terms),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ pch->purse_expiration),
+ GNUNET_JSON_pack_array_steal ("deposits",
+ deposit_arr));
+ GNUNET_assert (NULL != create_obj);
+ eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&pch->ctx,
+ eh,
+ create_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (create_obj);
+ GNUNET_free (pch->econtract.econtract);
+ GNUNET_array_grow (pch->deposits,
+ pch->num_deposits,
+ 0);
+ GNUNET_free (pch->url);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ json_decref (create_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for purse create with deposit: `%s'\n",
+ pch->url);
+ pch->keys = TALER_EXCHANGE_keys_incref (keys);
+ pch->exchange_url = GNUNET_strdup (url);
+ pch->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ pch->ctx.headers,
+ &handle_purse_create_deposit_finished,
+ pch);
+ return pch;
+}
+
+
+void
+TALER_EXCHANGE_purse_create_with_deposit_cancel (
+ struct TALER_EXCHANGE_PurseCreateDepositHandle *pch)
+{
+ if (NULL != pch->job)
+ {
+ GNUNET_CURL_job_cancel (pch->job);
+ pch->job = NULL;
+ }
+ GNUNET_free (pch->econtract.econtract);
+ GNUNET_free (pch->exchange_url);
+ GNUNET_free (pch->url);
+ GNUNET_array_grow (pch->deposits,
+ pch->num_deposits,
+ 0);
+ TALER_EXCHANGE_keys_decref (pch->keys);
+ TALER_curl_easy_post_finished (&pch->ctx);
+ GNUNET_free (pch);
+}
+
+
+/* end of exchange_api_purse_create_with_deposit.c */
diff --git a/src/lib/exchange_api_purse_create_with_merge.c b/src/lib/exchange_api_purse_create_with_merge.c
new file mode 100644
index 000000000..0c8878342
--- /dev/null
+++ b/src/lib/exchange_api_purse_create_with_merge.c
@@ -0,0 +1,580 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 lib/exchange_api_purse_create_with_merge.c
+ * @brief Implementation of the client to create a
+ * purse for an account
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_common.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A purse create with merge handle
+ */
+struct TALER_EXCHANGE_PurseCreateMergeHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * The exchange base URL.
+ */
+ char *exchange_url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_PurseCreateMergeCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * The encrypted contract (if any).
+ */
+ struct TALER_EncryptedContract econtract;
+
+ /**
+ * Expected value in the purse after fees.
+ */
+ struct TALER_Amount purse_value_after_fees;
+
+ /**
+ * Public key of the reserve public key.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Reserve signature affirming our merge.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Merge capability key.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Our merge signature (if any).
+ */
+ struct TALER_PurseMergeSignatureP merge_sig;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Request data we signed over.
+ */
+ struct TALER_PurseContractSignatureP purse_sig;
+
+ /**
+ * Hash over the purse's contrac terms.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * When does the purse expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * When does the purse get merged/created.
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RID/purse request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_PurseCreateMergeHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_purse_create_with_merge_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_PurseCreateMergeResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code,
+ .reserve_sig = &pcm->reserve_sig
+ };
+
+ pcm->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_TIME_Timestamp etime;
+ struct TALER_Amount total_deposited;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("total_deposited",
+ &total_deposited),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_spec_timestamp ("exchange_timestamp",
+ &etime),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (pcm->keys,
+ &exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_purse_created_verify (
+ etime,
+ pcm->purse_expiration,
+ &pcm->purse_value_after_fees,
+ &total_deposited,
+ &pcm->purse_pub,
+ &pcm->h_contract_terms,
+ &exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ switch (dr.hr.ec)
+ {
+ case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA:
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_create_conflict_ (
+ &pcm->purse_sig,
+ &pcm->purse_pub,
+ j))
+ {
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ case TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA:
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_merge_conflict_ (
+ &pcm->merge_sig,
+ &pcm->merge_pub,
+ &pcm->purse_pub,
+ pcm->exchange_url,
+ j))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS:
+ /* nothing to verify */
+ break;
+ case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA:
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_econtract_conflict_ (
+ &pcm->econtract.econtract_sig,
+ &pcm->purse_pub,
+ j))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ default:
+ /* unexpected EC! */
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ } /* end inner (EC) switch */
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &dr.details.unavailable_for_legal_reasons.requirement_row),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange deposit\n",
+ (unsigned int) response_code,
+ dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ pcm->cb (pcm->cb_cls,
+ &dr);
+ TALER_EXCHANGE_purse_create_with_merge_cancel (pcm);
+}
+
+
+struct TALER_EXCHANGE_PurseCreateMergeHandle *
+TALER_EXCHANGE_purse_create_with_merge (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ const struct TALER_PurseMergePrivateKeyP *merge_priv,
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const json_t *contract_terms,
+ bool upload_contract,
+ bool pay_for_purse,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ TALER_EXCHANGE_PurseCreateMergeCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm;
+ json_t *create_with_merge_obj;
+ CURL *eh;
+ char arg_str[sizeof (pcm->reserve_pub) * 2 + 32];
+ uint32_t min_age = 0;
+ struct TALER_Amount purse_fee;
+ enum TALER_WalletAccountMergeFlags flags;
+
+ pcm = GNUNET_new (struct TALER_EXCHANGE_PurseCreateMergeHandle);
+ pcm->cb = cb;
+ pcm->cb_cls = cb_cls;
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (contract_terms,
+ &pcm->h_contract_terms))
+ {
+ GNUNET_break (0);
+ GNUNET_free (pcm);
+ return NULL;
+ }
+ pcm->merge_timestamp = merge_timestamp;
+ GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
+ &pcm->purse_pub.eddsa_pub);
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &pcm->reserve_pub.eddsa_pub);
+ GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
+ &pcm->merge_pub.eddsa_pub);
+
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &pcm->purse_value_after_fees),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("minimum_age",
+ &min_age),
+ NULL),
+ GNUNET_JSON_spec_timestamp ("pay_deadline",
+ &pcm->purse_expiration),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (contract_terms,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ GNUNET_free (pcm);
+ return NULL;
+ }
+ }
+ if (pay_for_purse)
+ {
+ const struct TALER_EXCHANGE_GlobalFee *gf;
+
+ gf = TALER_EXCHANGE_get_global_fee (
+ keys,
+ GNUNET_TIME_timestamp_get ());
+ purse_fee = gf->fees.purse;
+ flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
+ }
+ else
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pcm->purse_value_after_fees.currency,
+ &purse_fee));
+ flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA;
+ }
+
+ {
+ char pub_str[sizeof (pcm->reserve_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &pcm->reserve_pub,
+ sizeof (pcm->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/purse",
+ pub_str);
+ }
+ pcm->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == pcm->url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (pcm);
+ return NULL;
+ }
+ TALER_wallet_purse_create_sign (pcm->purse_expiration,
+ &pcm->h_contract_terms,
+ &pcm->merge_pub,
+ min_age,
+ &pcm->purse_value_after_fees,
+ purse_priv,
+ &pcm->purse_sig);
+ {
+ char *payto_uri;
+
+ payto_uri = TALER_reserve_make_payto (url,
+ &pcm->reserve_pub);
+ TALER_wallet_purse_merge_sign (payto_uri,
+ merge_timestamp,
+ &pcm->purse_pub,
+ merge_priv,
+ &pcm->merge_sig);
+ GNUNET_free (payto_uri);
+ }
+ TALER_wallet_account_merge_sign (merge_timestamp,
+ &pcm->purse_pub,
+ pcm->purse_expiration,
+ &pcm->h_contract_terms,
+ &pcm->purse_value_after_fees,
+ &purse_fee,
+ min_age,
+ flags,
+ reserve_priv,
+ &pcm->reserve_sig);
+ if (upload_contract)
+ {
+ TALER_CRYPTO_contract_encrypt_for_deposit (
+ &pcm->purse_pub,
+ contract_priv,
+ contract_terms,
+ &pcm->econtract.econtract,
+ &pcm->econtract.econtract_size);
+ GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
+ &pcm->econtract.contract_pub.ecdhe_pub);
+ TALER_wallet_econtract_upload_sign (
+ pcm->econtract.econtract,
+ pcm->econtract.econtract_size,
+ &pcm->econtract.contract_pub,
+ purse_priv,
+ &pcm->econtract.econtract_sig);
+ }
+ create_with_merge_obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("purse_value",
+ &pcm->purse_value_after_fees),
+ GNUNET_JSON_pack_uint64 ("min_age",
+ min_age),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_econtract ("econtract",
+ upload_contract
+ ? &pcm->econtract
+ : NULL)),
+ GNUNET_JSON_pack_allow_null (
+ pay_for_purse
+ ? TALER_JSON_pack_amount ("purse_fee",
+ &purse_fee)
+ : GNUNET_JSON_pack_string ("dummy2",
+ NULL)),
+ GNUNET_JSON_pack_data_auto ("merge_pub",
+ &pcm->merge_pub),
+ GNUNET_JSON_pack_data_auto ("merge_sig",
+ &pcm->merge_sig),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &pcm->reserve_sig),
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ &pcm->purse_pub),
+ GNUNET_JSON_pack_data_auto ("purse_sig",
+ &pcm->purse_sig),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &pcm->h_contract_terms),
+ GNUNET_JSON_pack_timestamp ("merge_timestamp",
+ merge_timestamp),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ pcm->purse_expiration));
+ GNUNET_assert (NULL != create_with_merge_obj);
+ eh = TALER_EXCHANGE_curl_easy_get_ (pcm->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&pcm->ctx,
+ eh,
+ create_with_merge_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (create_with_merge_obj);
+ GNUNET_free (pcm->econtract.econtract);
+ GNUNET_free (pcm->url);
+ GNUNET_free (pcm);
+ return NULL;
+ }
+ json_decref (create_with_merge_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for purse create_with_merge: `%s'\n",
+ pcm->url);
+ pcm->keys = TALER_EXCHANGE_keys_incref (keys);
+ pcm->exchange_url = GNUNET_strdup (url);
+ pcm->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ pcm->ctx.headers,
+ &handle_purse_create_with_merge_finished,
+ pcm);
+ return pcm;
+}
+
+
+void
+TALER_EXCHANGE_purse_create_with_merge_cancel (
+ struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm)
+{
+ if (NULL != pcm->job)
+ {
+ GNUNET_CURL_job_cancel (pcm->job);
+ pcm->job = NULL;
+ }
+ GNUNET_free (pcm->url);
+ GNUNET_free (pcm->exchange_url);
+ TALER_curl_easy_post_finished (&pcm->ctx);
+ TALER_EXCHANGE_keys_decref (pcm->keys);
+ GNUNET_free (pcm->econtract.econtract);
+ GNUNET_free (pcm);
+}
+
+
+/* end of exchange_api_purse_create_with_merge.c */
diff --git a/src/lib/exchange_api_purse_delete.c b/src/lib/exchange_api_purse_delete.c
new file mode 100644
index 000000000..6f8ecc381
--- /dev/null
+++ b/src/lib/exchange_api_purse_delete.c
@@ -0,0 +1,243 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+ */
+/**
+ * @file lib/exchange_api_purse_delete.c
+ * @brief Implementation of the client to delete a purse
+ * into an account
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_common.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A purse delete with deposit handle
+ */
+struct TALER_EXCHANGE_PurseDeleteHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_PurseDeleteCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Header with the purse_sig.
+ */
+ struct curl_slist *xhdr;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP DELETE /purse/$PID request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_PurseDeleteHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_purse_delete_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_PurseDeleteHandle *pdh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_PurseDeleteResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ pdh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange deposit\n",
+ (unsigned int) response_code,
+ dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ pdh->cb (pdh->cb_cls,
+ &dr);
+ TALER_EXCHANGE_purse_delete_cancel (pdh);
+}
+
+
+struct TALER_EXCHANGE_PurseDeleteHandle *
+TALER_EXCHANGE_purse_delete (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ TALER_EXCHANGE_PurseDeleteCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_PurseDeleteHandle *pdh;
+ CURL *eh;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PurseContractSignatureP purse_sig;
+ char arg_str[sizeof (purse_pub) * 2 + 32];
+
+ pdh = GNUNET_new (struct TALER_EXCHANGE_PurseDeleteHandle);
+ pdh->cb = cb;
+ pdh->cb_cls = cb_cls;
+ GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
+ &purse_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (purse_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (&purse_pub,
+ sizeof (purse_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "purses/%s",
+ pub_str);
+ }
+ pdh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == pdh->url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (pdh);
+ return NULL;
+ }
+ TALER_wallet_purse_delete_sign (purse_priv,
+ &purse_sig);
+ {
+ char *delete_str;
+ char *xhdr;
+
+ delete_str =
+ GNUNET_STRINGS_data_to_string_alloc (&purse_sig,
+ sizeof (purse_sig));
+ GNUNET_asprintf (&xhdr,
+ "Taler-Purse-Signature: %s",
+ delete_str);
+ GNUNET_free (delete_str);
+ pdh->xhdr = curl_slist_append (NULL,
+ xhdr);
+ GNUNET_free (xhdr);
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (pdh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ curl_slist_free_all (pdh->xhdr);
+ GNUNET_free (pdh->url);
+ GNUNET_free (pdh);
+ return NULL;
+ }
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_CUSTOMREQUEST,
+ MHD_HTTP_METHOD_DELETE));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for purse delete: `%s'\n",
+ pdh->url);
+ pdh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ pdh->xhdr,
+ &handle_purse_delete_finished,
+ pdh);
+ return pdh;
+}
+
+
+void
+TALER_EXCHANGE_purse_delete_cancel (
+ struct TALER_EXCHANGE_PurseDeleteHandle *pdh)
+{
+ if (NULL != pdh->job)
+ {
+ GNUNET_CURL_job_cancel (pdh->job);
+ pdh->job = NULL;
+ }
+ curl_slist_free_all (pdh->xhdr);
+ GNUNET_free (pdh->url);
+ GNUNET_free (pdh);
+}
+
+
+/* end of exchange_api_purse_delete.c */
diff --git a/src/lib/exchange_api_purse_deposit.c b/src/lib/exchange_api_purse_deposit.c
new file mode 100644
index 000000000..9c5fa4e78
--- /dev/null
+++ b/src/lib/exchange_api_purse_deposit.c
@@ -0,0 +1,520 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 lib/exchange_api_purse_deposit.c
+ * @brief Implementation of the client to create a purse with
+ * an initial set of deposits (and a contract)
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Information we track per coin.
+ */
+struct Coin
+{
+ /**
+ * Coin's public key.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Signature made with the coin.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * Coin's denomination.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Age restriction hash for the coin.
+ */
+ struct TALER_AgeCommitmentHash ahac;
+
+ /**
+ * How much did we say the coin contributed.
+ */
+ struct TALER_Amount contribution;
+};
+
+
+/**
+ * @brief A purse create with deposit handle
+ */
+struct TALER_EXCHANGE_PurseDepositHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * The base url of the exchange we are talking to.
+ */
+ char *base_url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_PurseDepositCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Array of @e num_deposits coins we are depositing.
+ */
+ struct Coin *coins;
+
+ /**
+ * Number of coins we are depositing.
+ */
+ unsigned int num_deposits;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /purses/$PID/deposit request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_PurseDepositHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_purse_deposit_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_PurseDepositHandle *pch = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_PurseDepositResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+ const struct TALER_EXCHANGE_Keys *keys = pch->keys;
+
+ pch->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_TIME_Timestamp etime;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &dr.details.ok.h_contract_terms),
+ GNUNET_JSON_spec_timestamp ("exchange_timestamp",
+ &etime),
+ GNUNET_JSON_spec_timestamp ("purse_expiration",
+ &dr.details.ok.purse_expiration),
+ TALER_JSON_spec_amount ("total_deposited",
+ keys->currency,
+ &dr.details.ok.total_deposited),
+ TALER_JSON_spec_amount ("purse_value_after_fees",
+ keys->currency,
+ &dr.details.ok.purse_value_after_fees),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (keys,
+ &exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSE_DEPOSIT_EXCHANGE_SIGNATURE_INVALID;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_purse_created_verify (
+ etime,
+ dr.details.ok.purse_expiration,
+ &dr.details.ok.purse_value_after_fees,
+ &dr.details.ok.total_deposited,
+ &pch->purse_pub,
+ &dr.details.ok.h_contract_terms,
+ &exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSE_DEPOSIT_EXCHANGE_SIGNATURE_INVALID;
+ break;
+ }
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ switch (dr.hr.ec)
+ {
+ case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA:
+ {
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_AgeCommitmentHash phac;
+ bool found = false;
+
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_coin_conflict_ (
+ &pch->purse_pub,
+ pch->base_url,
+ j,
+ &h_denom_pub,
+ &phac,
+ &coin_pub,
+ &coin_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ for (unsigned int i = 0; i<pch->num_deposits; i++)
+ {
+ struct Coin *coin = &pch->coins[i];
+ if (0 != GNUNET_memcmp (&coin_pub,
+ &coin->coin_pub))
+ continue;
+ if (0 !=
+ GNUNET_memcmp (&coin->h_denom_pub,
+ &h_denom_pub))
+ {
+ found = true;
+ break;
+ }
+ if (0 !=
+ GNUNET_memcmp (&coin->ahac,
+ &phac))
+ {
+ found = true;
+ break;
+ }
+ if (0 == GNUNET_memcmp (&coin_sig,
+ &coin->coin_sig))
+ {
+ /* identical signature => not a conflict */
+ continue;
+ }
+ found = true;
+ break;
+ }
+ if (! found)
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ /* meta data conflict is real! */
+ break;
+ }
+ case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
+ /* Nothing to check anymore here, proof needs to be
+ checked in the GET /coins/$COIN_PUB handler */
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
+ break;
+ default:
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ } /* ec switch */
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked or purse expired */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange deposit\n",
+ (unsigned int) response_code,
+ dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ if (TALER_EC_NONE == dr.hr.ec)
+ dr.hr.hint = NULL;
+ else
+ dr.hr.hint = TALER_ErrorCode_get_hint (dr.hr.ec);
+ pch->cb (pch->cb_cls,
+ &dr);
+ TALER_EXCHANGE_purse_deposit_cancel (pch);
+}
+
+
+struct TALER_EXCHANGE_PurseDepositHandle *
+TALER_EXCHANGE_purse_deposit (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *purse_exchange_url,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ uint8_t min_age,
+ unsigned int num_deposits,
+ const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
+ TALER_EXCHANGE_PurseDepositCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_PurseDepositHandle *pch;
+ json_t *create_obj;
+ json_t *deposit_arr;
+ CURL *eh;
+ char arg_str[sizeof (pch->purse_pub) * 2 + 32];
+
+ // FIXME: use purse_exchange_url for wad transfers (#7271)
+ (void) purse_exchange_url;
+ if (0 == num_deposits)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ pch = GNUNET_new (struct TALER_EXCHANGE_PurseDepositHandle);
+ pch->purse_pub = *purse_pub;
+ pch->cb = cb;
+ pch->cb_cls = cb_cls;
+ {
+ char pub_str[sizeof (pch->purse_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &pch->purse_pub,
+ sizeof (pch->purse_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "purses/%s/deposit",
+ pub_str);
+ }
+ pch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == pch->url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ deposit_arr = json_array ();
+ GNUNET_assert (NULL != deposit_arr);
+ pch->base_url = GNUNET_strdup (url);
+ pch->num_deposits = num_deposits;
+ pch->coins = GNUNET_new_array (num_deposits,
+ struct Coin);
+ for (unsigned int i = 0; i<num_deposits; i++)
+ {
+ const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i];
+ const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof;
+ struct Coin *coin = &pch->coins[i];
+ json_t *jdeposit;
+ struct TALER_AgeCommitmentHash *achp = NULL;
+ struct TALER_AgeAttestation attest;
+ struct TALER_AgeAttestation *attestp = NULL;
+
+ if (NULL != acp)
+ {
+ TALER_age_commitment_hash (&acp->commitment,
+ &coin->ahac);
+ achp = &coin->ahac;
+ if (GNUNET_OK !=
+ TALER_age_commitment_attest (acp,
+ min_age,
+ &attest))
+ {
+ GNUNET_break (0);
+ json_decref (deposit_arr);
+ GNUNET_free (pch->base_url);
+ GNUNET_free (pch->coins);
+ GNUNET_free (pch->url);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ attestp = &attest;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv,
+ &coin->coin_pub.eddsa_pub);
+ coin->h_denom_pub = deposit->h_denom_pub;
+ coin->contribution = deposit->amount;
+ TALER_wallet_purse_deposit_sign (
+ pch->base_url,
+ &pch->purse_pub,
+ &deposit->amount,
+ &coin->h_denom_pub,
+ &coin->ahac,
+ &deposit->coin_priv,
+ &coin->coin_sig);
+ jdeposit = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ achp)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("age_attestation",
+ attestp)),
+ TALER_JSON_pack_amount ("amount",
+ &deposit->amount),
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &deposit->h_denom_pub),
+ TALER_JSON_pack_denom_sig ("ub_sig",
+ &deposit->denom_sig),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &coin->coin_pub),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &coin->coin_sig));
+ GNUNET_assert (0 ==
+ json_array_append_new (deposit_arr,
+ jdeposit));
+ }
+ create_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("deposits",
+ deposit_arr));
+ GNUNET_assert (NULL != create_obj);
+ eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&pch->ctx,
+ eh,
+ create_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (create_obj);
+ GNUNET_free (pch->base_url);
+ GNUNET_free (pch->url);
+ GNUNET_free (pch->coins);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ json_decref (create_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for purse deposit: `%s'\n",
+ pch->url);
+ pch->keys = TALER_EXCHANGE_keys_incref (keys);
+ pch->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ pch->ctx.headers,
+ &handle_purse_deposit_finished,
+ pch);
+ return pch;
+}
+
+
+void
+TALER_EXCHANGE_purse_deposit_cancel (
+ struct TALER_EXCHANGE_PurseDepositHandle *pch)
+{
+ if (NULL != pch->job)
+ {
+ GNUNET_CURL_job_cancel (pch->job);
+ pch->job = NULL;
+ }
+ GNUNET_free (pch->base_url);
+ GNUNET_free (pch->url);
+ GNUNET_free (pch->coins);
+ TALER_EXCHANGE_keys_decref (pch->keys);
+ TALER_curl_easy_post_finished (&pch->ctx);
+ GNUNET_free (pch);
+}
+
+
+/* end of exchange_api_purse_deposit.c */
diff --git a/src/lib/exchange_api_purse_merge.c b/src/lib/exchange_api_purse_merge.c
new file mode 100644
index 000000000..c013b29d2
--- /dev/null
+++ b/src/lib/exchange_api_purse_merge.c
@@ -0,0 +1,454 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 lib/exchange_api_purse_merge.c
+ * @brief Implementation of the client to merge a purse
+ * into an account
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_common.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A purse merge with deposit handle
+ */
+struct TALER_EXCHANGE_AccountMergeHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_AccountMergeCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Base URL of the provider hosting the @e reserve_pub.
+ */
+ char *provider_url;
+
+ /**
+ * Signature for our operation.
+ */
+ struct TALER_PurseMergeSignatureP merge_sig;
+
+ /**
+ * Expected value in the purse after fees.
+ */
+ struct TALER_Amount purse_value_after_fees;
+
+ /**
+ * Public key of the reserve public key.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Hash over the purse's contrac terms.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * When does the purse expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * Our merge key.
+ */
+ struct TALER_PurseMergePrivateKeyP merge_priv;
+
+ /**
+ * Reserve signature affirming the merge.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /purse/$PID/merge request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AccountMergeHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_purse_merge_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_AccountMergeHandle *pch = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_AccountMergeResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code,
+ .reserve_sig = &pch->reserve_sig
+ };
+
+ pch->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct TALER_Amount total_deposited;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &dr.details.ok.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &dr.details.ok.exchange_pub),
+ GNUNET_JSON_spec_timestamp ("exchange_timestamp",
+ &dr.details.ok.etime),
+ TALER_JSON_spec_amount ("merge_amount",
+ pch->purse_value_after_fees.currency,
+ &total_deposited),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (pch->keys,
+ &dr.details.ok.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_purse_merged_verify (
+ dr.details.ok.etime,
+ pch->purse_expiration,
+ &pch->purse_value_after_fees,
+ &pch->purse_pub,
+ &pch->h_contract_terms,
+ &pch->reserve_pub,
+ pch->provider_url,
+ &dr.details.ok.exchange_pub,
+ &dr.details.ok.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID;
+ break;
+ }
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ /* purse was not (yet) full */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ {
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&pch->merge_priv.eddsa_priv,
+ &merge_pub.eddsa_pub);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_merge_conflict_ (
+ &pch->merge_sig,
+ &merge_pub,
+ &pch->purse_pub,
+ pch->provider_url,
+ j))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ }
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &dr.details.unavailable_for_legal_reasons.requirement_row),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange deposit\n",
+ (unsigned int) response_code,
+ dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ pch->cb (pch->cb_cls,
+ &dr);
+ TALER_EXCHANGE_account_merge_cancel (pch);
+}
+
+
+struct TALER_EXCHANGE_AccountMergeHandle *
+TALER_EXCHANGE_account_merge (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *reserve_exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePrivateKeyP *merge_priv,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint8_t min_age,
+ const struct TALER_Amount *purse_value_after_fees,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ TALER_EXCHANGE_AccountMergeCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_AccountMergeHandle *pch;
+ json_t *merge_obj;
+ CURL *eh;
+ char arg_str[sizeof (pch->purse_pub) * 2 + 32];
+ char *reserve_url;
+
+ pch = GNUNET_new (struct TALER_EXCHANGE_AccountMergeHandle);
+ pch->merge_priv = *merge_priv;
+ pch->cb = cb;
+ pch->cb_cls = cb_cls;
+ pch->purse_pub = *purse_pub;
+ pch->h_contract_terms = *h_contract_terms;
+ pch->purse_expiration = purse_expiration;
+ pch->purse_value_after_fees = *purse_value_after_fees;
+ if (NULL == reserve_exchange_url)
+ pch->provider_url = GNUNET_strdup (url);
+ else
+ pch->provider_url = GNUNET_strdup (reserve_exchange_url);
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &pch->reserve_pub.eddsa_pub);
+
+ {
+ char pub_str[sizeof (*purse_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ purse_pub,
+ sizeof (*purse_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "purses/%s/merge",
+ pub_str);
+ }
+ reserve_url = TALER_reserve_make_payto (pch->provider_url,
+ &pch->reserve_pub);
+ if (NULL == reserve_url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (pch->provider_url);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ pch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == pch->url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (reserve_url);
+ GNUNET_free (pch->provider_url);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ TALER_wallet_purse_merge_sign (reserve_url,
+ merge_timestamp,
+ purse_pub,
+ merge_priv,
+ &pch->merge_sig);
+ {
+ struct TALER_Amount zero_purse_fee;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (purse_value_after_fees->currency,
+ &zero_purse_fee));
+ TALER_wallet_account_merge_sign (merge_timestamp,
+ purse_pub,
+ purse_expiration,
+ h_contract_terms,
+ purse_value_after_fees,
+ &zero_purse_fee,
+ min_age,
+ TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
+ reserve_priv,
+ &pch->reserve_sig);
+ }
+ merge_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ reserve_url),
+ GNUNET_JSON_pack_data_auto ("merge_sig",
+ &pch->merge_sig),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &pch->reserve_sig),
+ GNUNET_JSON_pack_timestamp ("merge_timestamp",
+ merge_timestamp));
+ GNUNET_assert (NULL != merge_obj);
+ GNUNET_free (reserve_url);
+ eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&pch->ctx,
+ eh,
+ merge_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (merge_obj);
+ GNUNET_free (pch->provider_url);
+ GNUNET_free (pch->url);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ json_decref (merge_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for purse merge: `%s'\n",
+ pch->url);
+ pch->keys = TALER_EXCHANGE_keys_incref (keys);
+ pch->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ pch->ctx.headers,
+ &handle_purse_merge_finished,
+ pch);
+ return pch;
+}
+
+
+void
+TALER_EXCHANGE_account_merge_cancel (
+ struct TALER_EXCHANGE_AccountMergeHandle *pch)
+{
+ if (NULL != pch->job)
+ {
+ GNUNET_CURL_job_cancel (pch->job);
+ pch->job = NULL;
+ }
+ GNUNET_free (pch->url);
+ GNUNET_free (pch->provider_url);
+ TALER_curl_easy_post_finished (&pch->ctx);
+ TALER_EXCHANGE_keys_decref (pch->keys);
+ GNUNET_free (pch);
+}
+
+
+/* end of exchange_api_purse_merge.c */
diff --git a/src/lib/exchange_api_purses_get.c b/src/lib/exchange_api_purses_get.c
new file mode 100644
index 000000000..dc22c75ad
--- /dev/null
+++ b/src/lib/exchange_api_purses_get.c
@@ -0,0 +1,302 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 lib/exchange_api_purses_get.c
+ * @brief Implementation of the /purses/ GET request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Contract Get Handle
+ */
+struct TALER_EXCHANGE_PurseGetHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_PurseGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /purses/$PID GET request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_PurseGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_purse_get_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_PurseGetHandle *pgh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_PurseGetResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ pgh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ bool no_merge = false;
+ bool no_deposit = false;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("merge_timestamp",
+ &dr.details.ok.merge_timestamp),
+ &no_merge),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("deposit_timestamp",
+ &dr.details.ok.deposit_timestamp),
+ &no_deposit),
+ TALER_JSON_spec_amount_any ("balance",
+ &dr.details.ok.balance),
+ GNUNET_JSON_spec_timestamp ("purse_expiration",
+ &dr.details.ok.purse_expiration),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (pgh->keys,
+ &exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_purse_status_verify (
+ dr.details.ok.merge_timestamp,
+ dr.details.ok.deposit_timestamp,
+ &dr.details.ok.balance,
+ &exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
+ }
+ pgh->cb (pgh->cb_cls,
+ &dr);
+ TALER_EXCHANGE_purse_get_cancel (pgh);
+ return;
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Exchange does not know about transaction;
+ we should pass the reply to the application */
+ break;
+ case MHD_HTTP_GONE:
+ /* purse expired */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange GET purses\n",
+ (unsigned int) response_code,
+ (int) dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ pgh->cb (pgh->cb_cls,
+ &dr);
+ TALER_EXCHANGE_purse_get_cancel (pgh);
+}
+
+
+struct TALER_EXCHANGE_PurseGetHandle *
+TALER_EXCHANGE_purse_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Relative timeout,
+ bool wait_for_merge,
+ TALER_EXCHANGE_PurseGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_PurseGetHandle *pgh;
+ CURL *eh;
+ char arg_str[sizeof (*purse_pub) * 2 + 64];
+ unsigned int tms
+ = (unsigned int) timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
+
+ pgh = GNUNET_new (struct TALER_EXCHANGE_PurseGetHandle);
+ pgh->cb = cb;
+ pgh->cb_cls = cb_cls;
+ {
+ char cpub_str[sizeof (*purse_pub) * 2];
+ char *end;
+ char timeout_str[32];
+
+ end = GNUNET_STRINGS_data_to_string (purse_pub,
+ sizeof (*purse_pub),
+ cpub_str,
+ sizeof (cpub_str));
+ *end = '\0';
+ GNUNET_snprintf (timeout_str,
+ sizeof (timeout_str),
+ "%u",
+ tms);
+ if (0 == tms)
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "purses/%s/%s",
+ cpub_str,
+ wait_for_merge ? "merge" : "deposit");
+ else
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "purses/%s/%s?timeout_ms=%s",
+ cpub_str,
+ wait_for_merge ? "merge" : "deposit",
+ timeout_str);
+ }
+ pgh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == pgh->url)
+ {
+ GNUNET_free (pgh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (pgh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (pgh->url);
+ GNUNET_free (pgh);
+ return NULL;
+ }
+ if (0 != tms)
+ {
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 100L)));
+ }
+ pgh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_purse_get_finished,
+ pgh);
+ pgh->keys = TALER_EXCHANGE_keys_incref (keys);
+ return pgh;
+}
+
+
+void
+TALER_EXCHANGE_purse_get_cancel (
+ struct TALER_EXCHANGE_PurseGetHandle *pgh)
+{
+ if (NULL != pgh->job)
+ {
+ GNUNET_CURL_job_cancel (pgh->job);
+ pgh->job = NULL;
+ }
+ GNUNET_free (pgh->url);
+ TALER_EXCHANGE_keys_decref (pgh->keys);
+ GNUNET_free (pgh);
+}
+
+
+/* end of exchange_api_purses_get.c */
diff --git a/src/lib/exchange_api_recoup.c b/src/lib/exchange_api_recoup.c
index a3416b5bc..56499f381 100644
--- a/src/lib/exchange_api_recoup.c
+++ b/src/lib/exchange_api_recoup.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2017 Taler Systems SA
+ Copyright (C) 2017-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
@@ -27,6 +27,7 @@
#include <gnunet/gnunet_curl_lib.h>
#include "taler_json_lib.h"
#include "taler_exchange_service.h"
+#include "exchange_api_common.h"
#include "exchange_api_handle.h"
#include "taler_signatures.h"
#include "exchange_api_curl_defaults.h"
@@ -39,9 +40,9 @@ struct TALER_EXCHANGE_RecoupHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -60,6 +61,11 @@ struct TALER_EXCHANGE_RecoupHandle
struct TALER_EXCHANGE_DenomPublicKey pk;
/**
+ * Our signature requesting the recoup.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
* Handle for the request.
*/
struct GNUNET_CURL_Job *job;
@@ -79,11 +85,6 @@ struct TALER_EXCHANGE_RecoupHandle
*/
struct TALER_CoinSpendPublicKeyP coin_pub;
- /**
- * #GNUNET_YES if the coin was refreshed
- */
- int was_refreshed;
-
};
@@ -95,43 +96,30 @@ struct TALER_EXCHANGE_RecoupHandle
* @return #GNUNET_OK if the signature is valid and we called the callback;
* #GNUNET_SYSERR if not (callback must still be called)
*/
-static int
+static enum GNUNET_GenericReturnValue
process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph,
const json_t *json)
{
- int refreshed;
- struct TALER_ReservePublicKeyP reserve_pub;
- struct TALER_CoinSpendPublicKeyP old_coin_pub;
- struct GNUNET_JSON_Specification spec_withdraw[] = {
- GNUNET_JSON_spec_boolean ("refreshed", &refreshed),
- GNUNET_JSON_spec_fixed_auto ("reserve_pub", &reserve_pub),
- GNUNET_JSON_spec_end ()
+ struct TALER_EXCHANGE_RecoupResponse rr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
};
- struct GNUNET_JSON_Specification spec_refresh[] = {
- GNUNET_JSON_spec_boolean ("refreshed", &refreshed),
- GNUNET_JSON_spec_fixed_auto ("old_coin_pub", &old_coin_pub),
+ struct GNUNET_JSON_Specification spec_withdraw[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &rr.details.ok.reserve_pub),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
- ph->was_refreshed ? spec_refresh : spec_withdraw,
+ spec_withdraw,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (ph->was_refreshed != refreshed)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
ph->cb (ph->cb_cls,
- MHD_HTTP_OK,
- TALER_EC_NONE,
- ph->was_refreshed ? NULL : &reserve_pub,
- ph->was_refreshed ? &old_coin_pub : NULL,
- json);
+ &rr);
return GNUNET_OK;
}
@@ -151,23 +139,25 @@ handle_recoup_finished (void *cls,
{
struct TALER_EXCHANGE_RecoupHandle *ph = cls;
const json_t *j = response;
- enum TALER_ErrorCode ec;
+ struct TALER_EXCHANGE_RecoupResponse rr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
ph->job = NULL;
switch (response_code)
{
case 0:
- ec = TALER_EC_INVALID_RESPONSE;
+ rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
- ec = TALER_EC_NONE;
if (GNUNET_OK !=
process_recoup_response (ph,
j))
{
GNUNET_break_op (0);
- ec = TALER_EC_RECOUP_REPLY_MALFORMED;
- response_code = 0;
+ rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rr.hr.http_status = 0;
break;
}
TALER_EXCHANGE_recoup_cancel (ph);
@@ -175,172 +165,173 @@ handle_recoup_finished (void *cls,
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_CONFLICT:
{
- /* Insufficient funds, proof attached */
- json_t *history;
- struct TALER_Amount total;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
-
- dki = &ph->pk;
- history = json_object_get (j,
- "history");
+ struct TALER_Amount min_key;
+
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
if (GNUNET_OK !=
- TALER_EXCHANGE_verify_coin_history (dki,
- dki->fee_deposit.currency,
- &ph->coin_pub,
- history,
- &total))
+ TALER_EXCHANGE_get_min_denomination_ (ph->keys,
+ &min_key))
{
- GNUNET_break_op (0);
- response_code = 0;
+ GNUNET_break (0);
+ rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rr.hr.http_status = 0;
+ break;
}
- ec = TALER_JSON_get_error_code (j);
- ph->cb (ph->cb_cls,
- response_code,
- ec,
- NULL,
- NULL,
- j);
- TALER_EXCHANGE_recoup_cancel (ph);
- return;
+ break;
}
case MHD_HTTP_FORBIDDEN:
/* Nothing really to verify, exchange says one of the signatures is
invalid; as we checked them, this should never happen, we
should pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_GONE:
/* Kind of normal: the money was already sent to the merchant
(it was too late for the refund). */
- ec = TALER_JSON_get_error_code (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
- ec = TALER_JSON_get_error_code (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
- ec = TALER_JSON_get_error_code (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u\n",
- (unsigned int) response_code);
+ "Unexpected response code %u/%d for exchange recoup\n",
+ (unsigned int) response_code,
+ (int) rr.hr.ec);
GNUNET_break (0);
- response_code = 0;
break;
}
ph->cb (ph->cb_cls,
- response_code,
- ec,
- NULL,
- NULL,
- j);
+ &rr);
TALER_EXCHANGE_recoup_cancel (ph);
}
-/**
- * Ask the exchange to pay back a coin due to the exchange triggering
- * the emergency recoup protocol for a given denomination. The value
- * of the coin will be refunded to the original customer (without fees).
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param pk kind of coin to pay back
- * @param denom_sig signature over the coin by the exchange using @a pk
- * @param ps secret internals of the original planchet
- * @param was_refreshed #GNUNET_YES if the coin in @a ps was refreshed
- * @param recoup_cb the callback to call when the final result for this request is available
- * @param recoup_cb_cls closure for @a recoup_cb
- * @return NULL
- * if the inputs are invalid (i.e. denomination key not with this exchange).
- * In this case, the callback is not called.
- */
struct TALER_EXCHANGE_RecoupHandle *
-TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_PlanchetSecretsP *ps,
- int was_refreshed,
- TALER_EXCHANGE_RecoupResultCallback recoup_cb,
- void *recoup_cb_cls)
+TALER_EXCHANGE_recoup (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_ExchangeWithdrawValues *exchange_vals,
+ const struct TALER_PlanchetMasterSecretP *ps,
+ TALER_EXCHANGE_RecoupResultCallback recoup_cb,
+ void *recoup_cb_cls)
{
struct TALER_EXCHANGE_RecoupHandle *ph;
- struct GNUNET_CURL_Context *ctx;
- struct TALER_RecoupRequestPS pr;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct GNUNET_HashCode h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
json_t *recoup_obj;
CURL *eh;
char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
- pr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP);
- pr.purpose.size = htonl (sizeof (struct TALER_RecoupRequestPS));
- GNUNET_CRYPTO_eddsa_key_get_public (&ps->coin_priv.eddsa_priv,
- &pr.coin_pub.eddsa_pub);
- GNUNET_CRYPTO_rsa_public_key_hash (pk->key.rsa_public_key,
- &h_denom_pub);
- pr.h_denom_pub = pk->h_key;
- pr.coin_blind = ps->blinding_key;
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (&ps->coin_priv.eddsa_priv,
- &pr.purpose,
- &coin_sig.eddsa_signature));
-
- recoup_obj = json_pack ("{s:o, s:o," /* denom pub/sig */
- " s:o," /* sig */
- " s:o, s:o}", /* coin_bks */
- "denom_pub_hash", GNUNET_JSON_from_data_auto (
- &h_denom_pub),
- "denom_sig", GNUNET_JSON_from_rsa_signature (
- denom_sig->rsa_signature),
- "coin_sig", GNUNET_JSON_from_data_auto (&coin_sig),
- "coin_blind_key_secret", GNUNET_JSON_from_data_auto (
- &ps->blinding_key),
- "refreshed", json_boolean (was_refreshed)
- );
- if (NULL == recoup_obj)
+ ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle);
+ TALER_planchet_setup_coin_priv (ps,
+ exchange_vals,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (ps,
+ exchange_vals,
+ &bks);
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
+ &ph->coin_pub.eddsa_pub);
+ TALER_denom_pub_hash (&pk->key,
+ &h_denom_pub);
+ TALER_wallet_recoup_sign (&h_denom_pub,
+ &bks,
+ &coin_priv,
+ &ph->coin_sig);
+ recoup_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &h_denom_pub),
+ TALER_JSON_pack_denom_sig ("denom_sig",
+ denom_sig),
+ TALER_JSON_pack_exchange_withdraw_values ("ewv",
+ exchange_vals),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &ph->coin_sig),
+ GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
+ &bks));
+ switch (denom_sig->unblinded_sig->cipher)
{
+ case GNUNET_CRYPTO_BSA_INVALID:
+ json_decref (recoup_obj);
GNUNET_break (0);
+ GNUNET_free (ph);
return NULL;
+ case GNUNET_CRYPTO_BSA_RSA:
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+
+ /* NOTE: this is not elegant, and as per the note in TALER_coin_ev_hash()
+ it is not strictly clear that the nonce is needed. Best case would be
+ to find a way to include it more 'naturally' somehow, for example with
+ the variant union version of bks! */
+ TALER_cs_withdraw_nonce_derive (ps,
+ &nonce.cs_nonce);
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (recoup_obj,
+ "cs_nonce",
+ GNUNET_JSON_from_data_auto (
+ &nonce)));
+ }
}
{
char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
char *end;
- end = GNUNET_STRINGS_data_to_string (&pr.coin_pub,
- sizeof (struct
- TALER_CoinSpendPublicKeyP),
- pub_str,
- sizeof (pub_str));
+ end = GNUNET_STRINGS_data_to_string (
+ &ph->coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ pub_str,
+ sizeof (pub_str));
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/coins/%s/recoup",
+ "coins/%s/recoup",
pub_str);
}
- ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle);
- ph->coin_pub = pr.coin_pub;
- ph->exchange = exchange;
ph->pk = *pk;
- ph->pk.key.rsa_public_key = NULL; /* zero out, as lifetime cannot be warranted */
+ memset (&ph->pk.key,
+ 0,
+ sizeof (ph->pk.key)); /* zero out, as lifetime cannot be warranted */
ph->cb = recoup_cb;
ph->cb_cls = recoup_cb_cls;
- ph->url = TEAH_path_to_url (exchange,
- arg_str);
- ph->was_refreshed = was_refreshed;
+ ph->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == ph->url)
+ {
+ json_decref (recoup_obj);
+ GNUNET_free (ph);
+ return NULL;
+ }
eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
if ( (NULL == eh) ||
(GNUNET_OK !=
@@ -360,7 +351,7 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for recoup: `%s'\n",
ph->url);
- ctx = TEAH_handle_to_context (exchange);
+ ph->keys = TALER_EXCHANGE_keys_incref (keys);
ph->job = GNUNET_CURL_job_add2 (ctx,
eh,
ph->ctx.headers,
@@ -370,12 +361,6 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
}
-/**
- * Cancel a recoup request. This function cannot be used on a
- * request handle if the callback was already invoked.
- *
- * @param ph the recoup handle
- */
void
TALER_EXCHANGE_recoup_cancel (struct TALER_EXCHANGE_RecoupHandle *ph)
{
@@ -386,6 +371,7 @@ TALER_EXCHANGE_recoup_cancel (struct TALER_EXCHANGE_RecoupHandle *ph)
}
GNUNET_free (ph->url);
TALER_curl_easy_post_finished (&ph->ctx);
+ TALER_EXCHANGE_keys_decref (ph->keys);
GNUNET_free (ph);
}
diff --git a/src/lib/exchange_api_recoup_refresh.c b/src/lib/exchange_api_recoup_refresh.c
new file mode 100644
index 000000000..0c2e21cbf
--- /dev/null
+++ b/src/lib/exchange_api_recoup_refresh.c
@@ -0,0 +1,374 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2017-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_recoup_refresh.c
+ * @brief Implementation of the /recoup-refresh request of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Recoup Handle
+ */
+struct TALER_EXCHANGE_RecoupRefreshHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Denomination key of the coin.
+ */
+ struct TALER_EXCHANGE_DenomPublicKey pk;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_RecoupRefreshResultCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Public key of the coin we are trying to get paid back.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Signature affirming the recoup-refresh operation.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+};
+
+
+/**
+ * Parse a recoup-refresh response. If it is valid, call the callback.
+ *
+ * @param ph recoup handle
+ * @param json json reply with the signature
+ * @return #GNUNET_OK if the signature is valid and we called the callback;
+ * #GNUNET_SYSERR if not (callback must still be called)
+ */
+static enum GNUNET_GenericReturnValue
+process_recoup_response (
+ const struct TALER_EXCHANGE_RecoupRefreshHandle *ph,
+ const json_t *json)
+{
+ struct TALER_EXCHANGE_RecoupRefreshResponse rrr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ struct GNUNET_JSON_Specification spec_refresh[] = {
+ GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
+ &rrr.details.ok.old_coin_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec_refresh,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ ph->cb (ph->cb_cls,
+ &rrr);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /recoup-refresh request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_RecoupRefreshHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_recoup_refresh_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_RecoupRefreshHandle *ph = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_RecoupRefreshResponse rrr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ ph->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ process_recoup_response (ph,
+ j))
+ {
+ GNUNET_break_op (0);
+ rrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rrr.hr.http_status = 0;
+ break;
+ }
+ TALER_EXCHANGE_recoup_refresh_cancel (ph);
+ return;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_GONE:
+ /* Kind of normal: the money was already sent to the merchant
+ (it was too late for the refund). */
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange recoup\n",
+ (unsigned int) response_code,
+ (int) rrr.hr.ec);
+ GNUNET_break (0);
+ break;
+ }
+ ph->cb (ph->cb_cls,
+ &rrr);
+ TALER_EXCHANGE_recoup_refresh_cancel (ph);
+}
+
+
+struct TALER_EXCHANGE_RecoupRefreshHandle *
+TALER_EXCHANGE_recoup_refresh (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_ExchangeWithdrawValues *exchange_vals,
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_PlanchetMasterSecretP *ps,
+ unsigned int idx,
+ TALER_EXCHANGE_RecoupRefreshResultCallback recoup_cb,
+ void *recoup_cb_cls)
+{
+ struct TALER_EXCHANGE_RecoupRefreshHandle *ph;
+ struct TALER_DenominationHashP h_denom_pub;
+ json_t *recoup_obj;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+
+ GNUNET_assert (NULL != recoup_cb);
+ ph = GNUNET_new (struct TALER_EXCHANGE_RecoupRefreshHandle);
+ ph->pk = *pk;
+ memset (&ph->pk.key,
+ 0,
+ sizeof (ph->pk.key)); /* zero out, as lifetime cannot be warranted */
+ ph->cb = recoup_cb;
+ ph->cb_cls = recoup_cb_cls;
+ TALER_planchet_setup_coin_priv (ps,
+ exchange_vals,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (ps,
+ exchange_vals,
+ &bks);
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
+ &ph->coin_pub.eddsa_pub);
+ TALER_denom_pub_hash (&pk->key,
+ &h_denom_pub);
+ TALER_wallet_recoup_refresh_sign (&h_denom_pub,
+ &bks,
+ &coin_priv,
+ &ph->coin_sig);
+ recoup_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &h_denom_pub),
+ TALER_JSON_pack_denom_sig ("denom_sig",
+ denom_sig),
+ TALER_JSON_pack_exchange_withdraw_values ("ewv",
+ exchange_vals),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &ph->coin_sig),
+ GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
+ &bks));
+
+ switch (denom_sig->unblinded_sig->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ json_decref (recoup_obj);
+ GNUNET_break (0);
+ GNUNET_free (ph);
+ return NULL;
+ case GNUNET_CRYPTO_BSA_RSA:
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+
+ /* NOTE: this is not elegant, and as per the note in TALER_coin_ev_hash()
+ it is not strictly clear that the nonce is needed. Best case would be
+ to find a way to include it more 'naturally' somehow, for example with
+ the variant union version of bks! */
+ TALER_cs_refresh_nonce_derive (rms,
+ idx,
+ &nonce.cs_nonce);
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (recoup_obj,
+ "cs_nonce",
+ GNUNET_JSON_from_data_auto (
+ &nonce)));
+ }
+ break;
+ }
+
+ {
+ char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &ph->coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "coins/%s/recoup-refresh",
+ pub_str);
+ }
+
+ ph->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == ph->url)
+ {
+ json_decref (recoup_obj);
+ GNUNET_free (ph);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&ph->ctx,
+ eh,
+ recoup_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (recoup_obj);
+ GNUNET_free (ph->url);
+ GNUNET_free (ph);
+ return NULL;
+ }
+ json_decref (recoup_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for recoup-refresh: `%s'\n",
+ ph->url);
+ ph->keys = TALER_EXCHANGE_keys_incref (keys);
+ ph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ ph->ctx.headers,
+ &handle_recoup_refresh_finished,
+ ph);
+ return ph;
+}
+
+
+void
+TALER_EXCHANGE_recoup_refresh_cancel (
+ struct TALER_EXCHANGE_RecoupRefreshHandle *ph)
+{
+ if (NULL != ph->job)
+ {
+ GNUNET_CURL_job_cancel (ph->job);
+ ph->job = NULL;
+ }
+ GNUNET_free (ph->url);
+ TALER_curl_easy_post_finished (&ph->ctx);
+ TALER_EXCHANGE_keys_decref (ph->keys);
+ GNUNET_free (ph);
+}
+
+
+/* end of exchange_api_recoup_refresh.c */
diff --git a/src/lib/exchange_api_refresh_common.c b/src/lib/exchange_api_refresh_common.c
index f0468dc8c..4369367e4 100644
--- a/src/lib/exchange_api_refresh_common.c
+++ b/src/lib/exchange_api_refresh_common.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2020 Taler Systems SA
+ Copyright (C) 2015-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
@@ -23,604 +23,234 @@
#include "exchange_api_refresh_common.h"
-/**
- * Free all information associated with a melted coin session.
- *
- * @param mc melted coin to release, the pointer itself is NOT
- * freed (as it is typically not allocated by itself)
- */
-static void
-free_melted_coin (struct MeltedCoin *mc)
-{
- if (NULL != mc->pub_key.rsa_public_key)
- GNUNET_CRYPTO_rsa_public_key_free (mc->pub_key.rsa_public_key);
- if (NULL != mc->sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (mc->sig.rsa_signature);
-}
-
-
-/**
- * Free all information associated with a melting session. Note
- * that we allow the melting session to be only partially initialized,
- * as we use this function also when freeing melt data that was not
- * fully initialized (i.e. due to failures in #TALER_EXCHANGE_deserialize_melt_data_()).
- *
- * @param md melting data to release, the pointer itself is NOT
- * freed (as it is typically not allocated by itself)
- */
void
TALER_EXCHANGE_free_melt_data_ (struct MeltData *md)
{
- free_melted_coin (&md->melted_coin);
- if (NULL != md->fresh_pks)
- {
- for (unsigned int i = 0; i<md->num_fresh_coins; i++)
- if (NULL != md->fresh_pks[i].rsa_public_key)
- GNUNET_CRYPTO_rsa_public_key_free (md->fresh_pks[i].rsa_public_key);
- GNUNET_free (md->fresh_pks);
- }
-
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- GNUNET_free_non_null (md->fresh_coins[i]);
- /* Finally, clean up a bit...
- (NOTE: compilers might optimize this away, so this is
- not providing any strong assurances that the key material
- is purged.) */
- memset (md,
- 0,
- sizeof (struct MeltData));
-}
-
-
-/**
- * Serialize information about a coin we are melting.
- *
- * @param mc information to serialize
- * @param buf buffer to write data in, NULL to just compute
- * required size
- * @param off offeset at @a buf to use
- * @return number of bytes written to @a buf at @a off, or if
- * @a buf is NULL, number of bytes required; 0 on error
- */
-static size_t
-serialize_melted_coin (const struct MeltedCoin *mc,
- char *buf,
- size_t off)
-{
- struct MeltedCoinP mcp;
- void *pbuf;
- size_t pbuf_size;
- void *sbuf;
- size_t sbuf_size;
-
- sbuf_size = GNUNET_CRYPTO_rsa_signature_encode (mc->sig.rsa_signature,
- &sbuf);
- pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (mc->pub_key.rsa_public_key,
- &pbuf);
- if (NULL == buf)
- {
- GNUNET_free (sbuf);
- GNUNET_free (pbuf);
- return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size;
- }
- if ( (sbuf_size > UINT16_MAX) ||
- (pbuf_size > UINT16_MAX) )
- {
- GNUNET_break (0);
- return 0;
- }
- mcp.coin_priv = mc->coin_priv;
- TALER_amount_hton (&mcp.melt_amount_with_fee,
- &mc->melt_amount_with_fee);
- TALER_amount_hton (&mcp.fee_melt,
- &mc->fee_melt);
- TALER_amount_hton (&mcp.original_value,
- &mc->original_value);
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- mcp.transfer_priv[i] = mc->transfer_priv[i];
- mcp.expire_deposit = GNUNET_TIME_absolute_hton (mc->expire_deposit);
- mcp.pbuf_size = htons ((uint16_t) pbuf_size);
- mcp.sbuf_size = htons ((uint16_t) sbuf_size);
- memcpy (&buf[off],
- &mcp,
- sizeof (struct MeltedCoinP));
- memcpy (&buf[off + sizeof (struct MeltedCoinP)],
- pbuf,
- pbuf_size);
- memcpy (&buf[off + sizeof (struct MeltedCoinP) + pbuf_size],
- sbuf,
- sbuf_size);
- GNUNET_free (sbuf);
- GNUNET_free (pbuf);
- return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size;
-}
-
-
-/**
- * Deserialize information about a coin we are melting.
- *
- * @param[out] mc information to deserialize
- * @param buf buffer to read data from
- * @param size number of bytes available at @a buf to use
- * @param[out] ok set to #GNUNET_NO to report errors
- * @return number of bytes read from @a buf, 0 on error
- */
-static size_t
-deserialize_melted_coin (struct MeltedCoin *mc,
- const char *buf,
- size_t size,
- int *ok)
-{
- struct MeltedCoinP mcp;
- size_t pbuf_size;
- size_t sbuf_size;
- size_t off;
-
- if (size < sizeof (struct MeltedCoinP))
- {
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
- }
- memcpy (&mcp,
- buf,
- sizeof (struct MeltedCoinP));
- pbuf_size = ntohs (mcp.pbuf_size);
- sbuf_size = ntohs (mcp.sbuf_size);
- if (size < sizeof (struct MeltedCoinP) + pbuf_size + sbuf_size)
- {
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
- }
- off = sizeof (struct MeltedCoinP);
- mc->pub_key.rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_decode (&buf[off],
- pbuf_size);
- off += pbuf_size;
- mc->sig.rsa_signature
- = GNUNET_CRYPTO_rsa_signature_decode (&buf[off],
- sbuf_size);
- off += sbuf_size;
- if ( (NULL == mc->pub_key.rsa_public_key) ||
- (NULL == mc->sig.rsa_signature) )
- {
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
- }
-
- mc->coin_priv = mcp.coin_priv;
- TALER_amount_ntoh (&mc->melt_amount_with_fee,
- &mcp.melt_amount_with_fee);
- TALER_amount_ntoh (&mc->fee_melt,
- &mcp.fee_melt);
- TALER_amount_ntoh (&mc->original_value,
- &mcp.original_value);
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- mc->transfer_priv[i] = mcp.transfer_priv[i];
- mc->expire_deposit = GNUNET_TIME_absolute_ntoh (mcp.expire_deposit);
- return off;
-}
-
-
-/**
- * Serialize information about a denomination key.
- *
- * @param dk information to serialize
- * @param buf buffer to write data in, NULL to just compute
- * required size
- * @param off offeset at @a buf to use
- * @return number of bytes written to @a buf at @a off, or if
- * @a buf is NULL, number of bytes required
- */
-static size_t
-serialize_denomination_key (const struct TALER_DenominationPublicKey *dk,
- char *buf,
- size_t off)
-{
- void *pbuf;
- size_t pbuf_size;
- uint32_t be;
-
- pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (dk->rsa_public_key,
- &pbuf);
- if (NULL == buf)
+ for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++)
{
- GNUNET_free (pbuf);
- return pbuf_size + sizeof (uint32_t);
- }
- be = htonl ((uint32_t) pbuf_size);
- memcpy (&buf[off],
- &be,
- sizeof (uint32_t));
- memcpy (&buf[off + sizeof (uint32_t)],
- pbuf,
- pbuf_size);
- GNUNET_free (pbuf);
- return pbuf_size + sizeof (uint32_t);
-}
+ struct TALER_RefreshCoinData *rcds = md->rcd[i];
-
-/**
- * Deserialize information about a denomination key.
- *
- * @param[out] dk information to deserialize
- * @param buf buffer to read data from
- * @param size number of bytes available at @a buf to use
- * @param[out] ok set to #GNUNET_NO to report errors
- * @return number of bytes read from @a buf, 0 on error
- */
-static size_t
-deserialize_denomination_key (struct TALER_DenominationPublicKey *dk,
- const char *buf,
- size_t size,
- int *ok)
-{
- size_t pbuf_size;
- uint32_t be;
-
- if (size < sizeof (uint32_t))
- {
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
- }
- memcpy (&be,
- buf,
- sizeof (uint32_t));
- pbuf_size = ntohl (be);
- if (size < sizeof (uint32_t) + pbuf_size)
- {
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
+ if (NULL == rcds)
+ continue;
+ for (unsigned int j = 0; j < md->num_fresh_coins; j++)
+ TALER_blinded_planchet_free (&rcds[j].blinded_planchet);
+ GNUNET_free (rcds);
}
- dk->rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_decode (&buf[sizeof (uint32_t)],
- pbuf_size);
- if (NULL == dk->rsa_public_key)
+ TALER_denom_pub_free (&md->melted_coin.pub_key);
+ TALER_denom_sig_free (&md->melted_coin.sig);
+ if (NULL != md->fcds)
{
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
- }
- return sizeof (uint32_t) + pbuf_size;
-}
-
-
-/**
- * Serialize information about a fresh coin we are generating.
- *
- * @param fc information to serialize
- * @param buf buffer to write data in, NULL to just compute
- * required size
- * @param off offeset at @a buf to use
- * @return number of bytes written to @a buf at @a off, or if
- * @a buf is NULL, number of bytes required
- */
-static size_t
-serialize_fresh_coin (const struct TALER_PlanchetSecretsP *fc,
- char *buf,
- size_t off)
-{
- if (NULL != buf)
- memcpy (&buf[off],
- fc,
- sizeof (struct TALER_PlanchetSecretsP));
- return sizeof (struct TALER_PlanchetSecretsP);
-}
-
-
-/**
- * Deserialize information about a fresh coin we are generating.
- *
- * @param[out] fc information to deserialize
- * @param buf buffer to read data from
- * @param size number of bytes available at @a buf to use
- * @param[out] ok set to #GNUNET_NO to report errors
- * @return number of bytes read from @a buf, 0 on error
- */
-static size_t
-deserialize_fresh_coin (struct TALER_PlanchetSecretsP *fc,
- const char *buf,
- size_t size,
- int *ok)
-{
- if (size < sizeof (struct TALER_PlanchetSecretsP))
- {
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
- }
- memcpy (fc,
- buf,
- sizeof (struct TALER_PlanchetSecretsP));
- return sizeof (struct TALER_PlanchetSecretsP);
-}
-
-
-/**
- * Serialize melt data.
- *
- * @param md data to serialize
- * @param[out] res_size size of buffer returned
- * @return serialized melt data
- */
-static char *
-serialize_melt_data (const struct MeltData *md,
- size_t *res_size)
-{
- size_t size;
- size_t asize;
- char *buf;
-
- size = 0;
- asize = (size_t) -1; /* make the compiler happy */
- buf = NULL;
- /* we do 2 iterations, #1 to determine total size, #2 to
- actually construct the buffer */
- do {
- if (0 == size)
- {
- size = sizeof (struct MeltDataP);
- }
- else
+ for (unsigned int j = 0; j<md->num_fresh_coins; j++)
{
- struct MeltDataP *mdp;
+ struct FreshCoinData *fcd = &md->fcds[j];
- buf = GNUNET_malloc (size);
- asize = size; /* just for invariant check later */
- size = sizeof (struct MeltDataP);
- mdp = (struct MeltDataP *) buf;
- mdp->rc = md->rc;
- mdp->num_fresh_coins = htons (md->num_fresh_coins);
+ TALER_denom_pub_free (&fcd->fresh_pk);
+ for (size_t i = 0; i < TALER_CNC_KAPPA; i++)
+ {
+ TALER_age_commitment_proof_free (fcd->age_commitment_proofs[i]);
+ GNUNET_free (fcd->age_commitment_proofs[i]);
+ }
}
- size += serialize_melted_coin (&md->melted_coin,
- buf,
- size);
- for (unsigned int i = 0; i<md->num_fresh_coins; i++)
- size += serialize_denomination_key (&md->fresh_pks[i],
- buf,
- size);
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- for (unsigned int j = 0; j<md->num_fresh_coins; j++)
- size += serialize_fresh_coin (&md->fresh_coins[i][j],
- buf,
- size);
- } while (NULL == buf);
- GNUNET_assert (size == asize);
- *res_size = size;
- return buf;
-}
-
-
-/**
- * Deserialize melt data.
- *
- * @param buf serialized data
- * @param buf_size size of @a buf
- * @return deserialized melt data, NULL on error
- */
-struct MeltData *
-TALER_EXCHANGE_deserialize_melt_data_ (const char *buf,
- size_t buf_size)
-{
- struct MeltData *md;
- struct MeltDataP mdp;
- size_t off;
- int ok;
-
- if (buf_size < sizeof (struct MeltDataP))
- return NULL;
- memcpy (&mdp,
- buf,
- sizeof (struct MeltDataP));
- md = GNUNET_new (struct MeltData);
- md->rc = mdp.rc;
- md->num_fresh_coins = ntohs (mdp.num_fresh_coins);
- md->fresh_pks = GNUNET_new_array (md->num_fresh_coins,
- struct TALER_DenominationPublicKey);
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- md->fresh_coins[i] = GNUNET_new_array (md->num_fresh_coins,
- struct TALER_PlanchetSecretsP);
- off = sizeof (struct MeltDataP);
- ok = GNUNET_YES;
- off += deserialize_melted_coin (&md->melted_coin,
- &buf[off],
- buf_size - off,
- &ok);
- for (unsigned int i = 0; (i<md->num_fresh_coins) && (GNUNET_YES == ok); i++)
- off += deserialize_denomination_key (&md->fresh_pks[i],
- &buf[off],
- buf_size - off,
- &ok);
-
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- for (unsigned int j = 0; (j<md->num_fresh_coins) && (GNUNET_YES == ok); j++)
- off += deserialize_fresh_coin (&md->fresh_coins[i][j],
- &buf[off],
- buf_size - off,
- &ok);
- if (off != buf_size)
- {
- GNUNET_break (0);
- ok = GNUNET_NO;
+ GNUNET_free (md->fcds);
}
- if (GNUNET_YES != ok)
- {
- TALER_EXCHANGE_free_melt_data_ (md);
- GNUNET_free (md);
- return NULL;
- }
- return md;
+ /* Finally, clean up a bit... */
+ GNUNET_CRYPTO_zero_keys (md,
+ sizeof (struct MeltData));
}
-/**
- * Melt (partially spent) coins to obtain fresh coins that are
- * unlinkable to the original coin(s). Note that melting more
- * than one coin in a single request will make those coins linkable,
- * so the safest operation only melts one coin at a time.
- *
- * This API is typically used by a wallet. Note that to ensure that
- * no money is lost in case of hardware failures, this operation does
- * not actually initiate the request. Instead, it generates a buffer
- * which the caller must store before proceeding with the actual call
- * to #TALER_EXCHANGE_melt() that will generate the request.
- *
- * This function does verify that the given request data is internally
- * consistent. However, the @a melts_sigs are NOT verified.
- *
- * Aside from some non-trivial cryptographic operations that might
- * take a bit of CPU time to complete, this function returns
- * its result immediately and does not start any asynchronous
- * processing. This function is also thread-safe.
- *
- * @param melt_priv private key of the coin to melt
- * @param melt_amount amount specifying how much
- * the coin will contribute to the melt (including fee)
- * @param melt_sig signature affirming the
- * validity of the public keys corresponding to the
- * @a melt_priv private key
- * @param melt_pk denomination key information
- * record corresponding to the @a melt_sig
- * validity of the keys
- * @param fresh_pks_len length of the @a pks array
- * @param fresh_pks array of @a pks_len denominations of fresh coins to create
- * @param[out] res_size set to the size of the return value, or 0 on error
- * @return NULL
- * if the inputs are invalid (i.e. denomination key not with this exchange).
- * Otherwise, pointer to a buffer of @a res_size to store persistently
- * before proceeding to #TALER_EXCHANGE_melt().
- * Non-null results should be freed using GNUNET_free().
- */
-char *
-TALER_EXCHANGE_refresh_prepare (
- const struct TALER_CoinSpendPrivateKeyP *melt_priv,
- const struct TALER_Amount *melt_amount,
- const struct TALER_DenominationSignature *melt_sig,
- const struct TALER_EXCHANGE_DenomPublicKey *melt_pk,
- unsigned int fresh_pks_len,
- const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks,
- size_t *res_size)
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_get_melt_data_ (
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_EXCHANGE_RefreshData *rd,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ struct MeltData *md)
{
- struct MeltData md;
- char *buf;
struct TALER_Amount total;
struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA];
- struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA];
+ union GNUNET_CRYPTO_BlindSessionNonce nonces[rd->fresh_pks_len];
+ bool uses_cs = false;
- GNUNET_CRYPTO_eddsa_key_get_public (&melt_priv->eddsa_priv,
+ GNUNET_CRYPTO_eddsa_key_get_public (&rd->melt_priv.eddsa_priv,
&coin_pub.eddsa_pub);
/* build up melt data structure */
- memset (&md, 0, sizeof (md));
- md.num_fresh_coins = fresh_pks_len;
- md.melted_coin.coin_priv = *melt_priv;
- md.melted_coin.melt_amount_with_fee = *melt_amount;
- md.melted_coin.fee_melt = melt_pk->fee_refresh;
- md.melted_coin.original_value = melt_pk->value;
- md.melted_coin.expire_deposit
- = melt_pk->expire_deposit;
+ memset (md,
+ 0,
+ sizeof (*md));
+ md->num_fresh_coins = rd->fresh_pks_len;
+ md->melted_coin.coin_priv = rd->melt_priv;
+ md->melted_coin.melt_amount_with_fee = rd->melt_amount;
+ md->melted_coin.fee_melt = rd->melt_pk.fees.refresh;
+ md->melted_coin.original_value = rd->melt_pk.value;
+ md->melted_coin.expire_deposit = rd->melt_pk.expire_deposit;
+ md->melted_coin.age_commitment_proof = rd->melt_age_commitment_proof;
+ md->melted_coin.h_age_commitment = rd->melt_h_age_commitment;
+
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (melt_amount->currency,
+ TALER_amount_set_zero (rd->melt_amount.currency,
&total));
- md.melted_coin.pub_key.rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_dup (melt_pk->key.rsa_public_key);
- md.melted_coin.sig.rsa_signature
- = GNUNET_CRYPTO_rsa_signature_dup (melt_sig->rsa_signature);
- md.fresh_pks = GNUNET_new_array (fresh_pks_len,
- struct TALER_DenominationPublicKey);
- for (unsigned int i = 0; i<fresh_pks_len; i++)
+ TALER_denom_pub_copy (&md->melted_coin.pub_key,
+ &rd->melt_pk.key);
+ TALER_denom_sig_copy (&md->melted_coin.sig,
+ &rd->melt_sig);
+ md->fcds = GNUNET_new_array (md->num_fresh_coins,
+ struct FreshCoinData);
+ for (unsigned int j = 0; j<rd->fresh_pks_len; j++)
{
- md.fresh_pks[i].rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_dup (fresh_pks[i].key.rsa_public_key);
- if ( (GNUNET_OK !=
+ struct FreshCoinData *fcd = &md->fcds[j];
+
+ TALER_denom_pub_copy (&fcd->fresh_pk,
+ &rd->fresh_pks[j].key);
+ GNUNET_assert (NULL != fcd->fresh_pk.bsign_pub_key);
+ if (alg_values[j].blinding_inputs->cipher !=
+ fcd->fresh_pk.bsign_pub_key->cipher)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_free_melt_data_ (md);
+ return GNUNET_SYSERR;
+ }
+ switch (fcd->fresh_pk.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_break (0);
+ TALER_EXCHANGE_free_melt_data_ (md);
+ return GNUNET_SYSERR;
+ case GNUNET_CRYPTO_BSA_RSA:
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ uses_cs = true;
+ TALER_cs_refresh_nonce_derive (rms,
+ j,
+ &nonces[j].cs_nonce);
+ break;
+ }
+ if ( (0 >
TALER_amount_add (&total,
&total,
- &fresh_pks[i].value)) ||
- (GNUNET_OK !=
+ &rd->fresh_pks[j].value)) ||
+ (0 >
TALER_amount_add (&total,
&total,
- &fresh_pks[i].fee_withdraw)) )
+ &rd->fresh_pks[j].fees.withdraw)) )
{
GNUNET_break (0);
- TALER_EXCHANGE_free_melt_data_ (&md);
- return NULL;
+ TALER_EXCHANGE_free_melt_data_ (md);
+ return GNUNET_SYSERR;
}
}
+
/* verify that melt_amount is above total cost */
if (1 ==
TALER_amount_cmp (&total,
- melt_amount) )
+ &rd->melt_amount) )
{
/* Eh, this operation is more expensive than the
@a melt_amount. This is not OK. */
GNUNET_break (0);
- TALER_EXCHANGE_free_melt_data_ (&md);
- return NULL;
+ TALER_EXCHANGE_free_melt_data_ (md);
+ return GNUNET_SYSERR;
}
/* build up coins */
for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
{
- struct GNUNET_CRYPTO_EcdhePrivateKey *tpk;
+ struct TALER_TransferSecretP trans_sec;
- tpk = GNUNET_CRYPTO_ecdhe_key_create ();
- md.melted_coin.transfer_priv[i].ecdhe_priv = *tpk;
- GNUNET_free (tpk);
+ TALER_planchet_secret_to_transfer_priv (
+ rms,
+ &rd->melt_priv,
+ i,
+ &md->transfer_priv[i]);
GNUNET_CRYPTO_ecdhe_key_get_public (
- &md.melted_coin.transfer_priv[i].ecdhe_priv,
- &rce[i].transfer_pub.ecdhe_pub);
- TALER_link_derive_transfer_secret (melt_priv,
- &md.melted_coin.transfer_priv[i],
- &trans_sec[i]);
- md.fresh_coins[i] = GNUNET_new_array (fresh_pks_len,
- struct TALER_PlanchetSecretsP);
- rce[i].new_coins = GNUNET_new_array (fresh_pks_len,
- struct TALER_RefreshCoinData);
- for (unsigned int j = 0; j<fresh_pks_len; j++)
+ &md->transfer_priv[i].ecdhe_priv,
+ &md->transfer_pub[i].ecdhe_pub);
+
+ TALER_link_derive_transfer_secret (&rd->melt_priv,
+ &md->transfer_priv[i],
+ &trans_sec);
+
+ md->rcd[i] = GNUNET_new_array (rd->fresh_pks_len,
+ struct TALER_RefreshCoinData);
+
+ for (unsigned int j = 0; j<rd->fresh_pks_len; j++)
{
- struct TALER_PlanchetSecretsP *fc = &md.fresh_coins[i][j];
- struct TALER_RefreshCoinData *rcd = &rce[i].new_coins[j];
+ struct FreshCoinData *fcd = &md->fcds[j];
+ struct TALER_CoinSpendPrivateKeyP *coin_priv = &fcd->coin_priv;
+ struct TALER_PlanchetMasterSecretP *ps = &fcd->ps[i];
+ struct TALER_RefreshCoinData *rcd = &md->rcd[i][j];
+ union GNUNET_CRYPTO_BlindingSecretP *bks = &fcd->bks[i];
struct TALER_PlanchetDetail pd;
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_AgeCommitmentHash ach;
+ struct TALER_AgeCommitmentHash *pah = NULL;
+
+ TALER_transfer_secret_to_planchet_secret (&trans_sec,
+ j,
+ ps);
+
+ TALER_planchet_setup_coin_priv (ps,
+ &alg_values[j],
+ coin_priv);
+
+ TALER_planchet_blinding_secret_create (ps,
+ &alg_values[j],
+ bks);
+
+ if (NULL != rd->melt_age_commitment_proof)
+ {
+ fcd->age_commitment_proofs[i] = GNUNET_new (struct
+ TALER_AgeCommitmentProof);
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_age_commitment_derive (
+ md->melted_coin.age_commitment_proof,
+ &trans_sec.key,
+ fcd->age_commitment_proofs[i]));
+
+ TALER_age_commitment_hash (
+ &fcd->age_commitment_proofs[i]->commitment,
+ &ach);
+ pah = &ach;
+ }
- TALER_planchet_setup_refresh (&trans_sec[i],
- j,
- fc);
if (GNUNET_OK !=
- TALER_planchet_prepare (&md.fresh_pks[j],
- fc,
+ TALER_planchet_prepare (&fcd->fresh_pk,
+ &alg_values[j],
+ bks,
+ &nonces[j],
+ coin_priv,
+ pah,
+ &c_hash,
&pd))
{
GNUNET_break_op (0);
- TALER_EXCHANGE_free_melt_data_ (&md);
- return NULL;
+ TALER_EXCHANGE_free_melt_data_ (md);
+ return GNUNET_SYSERR;
}
- rcd->dk = &md.fresh_pks[j];
- rcd->coin_ev = pd.coin_ev;
- rcd->coin_ev_size = pd.coin_ev_size;
+ rcd->blinded_planchet = pd.blinded_planchet;
+ rcd->dk = &fcd->fresh_pk;
}
}
- /* Compute refresh commitment */
- TALER_refresh_get_commitment (&md.rc,
- TALER_CNC_KAPPA,
- fresh_pks_len,
- rce,
- &coin_pub,
- melt_amount);
- /* finally, serialize everything */
- buf = serialize_melt_data (&md,
- res_size);
- for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++)
+ /* Finally, compute refresh commitment */
{
- for (unsigned int j = 0; j < fresh_pks_len; j++)
- GNUNET_free_non_null (rce[i].new_coins[j].coin_ev);
- GNUNET_free_non_null (rce[i].new_coins);
+ struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA];
+
+ for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
+ {
+ rce[i].transfer_pub = md->transfer_pub[i];
+ rce[i].new_coins = md->rcd[i];
+ }
+ TALER_refresh_get_commitment (&md->rc,
+ TALER_CNC_KAPPA,
+ uses_cs
+ ? rms
+ : NULL,
+ rd->fresh_pks_len,
+ rce,
+ &coin_pub,
+ &rd->melt_amount);
}
- TALER_EXCHANGE_free_melt_data_ (&md);
- return buf;
+ return GNUNET_OK;
}
diff --git a/src/lib/exchange_api_refresh_common.h b/src/lib/exchange_api_refresh_common.h
index 9f2715a1f..f596e1e90 100644
--- a/src/lib/exchange_api_refresh_common.h
+++ b/src/lib/exchange_api_refresh_common.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2020 Taler Systems SA
+ Copyright (C) 2015-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
@@ -27,15 +27,10 @@
#include "taler_signatures.h"
-/* structures for committing refresh data to disk before doing the
- network interaction(s) */
-
-GNUNET_NETWORK_STRUCT_BEGIN
-
/**
- * Header of serialized information about a coin we are melting.
+ * Information about a coin we are melting.
*/
-struct MeltedCoinP
+struct MeltedCoin
{
/**
* Private key of the coin.
@@ -45,103 +40,106 @@ struct MeltedCoinP
/**
* Amount this coin contributes to the melt, including fee.
*/
- struct TALER_AmountNBO melt_amount_with_fee;
+ struct TALER_Amount melt_amount_with_fee;
/**
- * The applicable fee for withdrawing a coin of this denomination
+ * The applicable fee for melting a coin of this denomination
*/
- struct TALER_AmountNBO fee_melt;
+ struct TALER_Amount fee_melt;
/**
* The original value of the coin.
*/
- struct TALER_AmountNBO original_value;
+ struct TALER_Amount original_value;
/**
- * Transfer private keys for each cut-and-choose dimension.
+ * The original age commitment, its proof and its hash. MUST be NULL if no
+ * age commitment was set.
*/
- struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA];
+ const struct TALER_AgeCommitmentProof *age_commitment_proof;
+ const struct TALER_AgeCommitmentHash *h_age_commitment;
/**
* Timestamp indicating when coins of this denomination become invalid.
*/
- struct GNUNET_TIME_AbsoluteNBO expire_deposit;
+ struct GNUNET_TIME_Timestamp expire_deposit;
/**
- * Size of the encoded public key that follows.
+ * Denomination key of the original coin.
*/
- uint16_t pbuf_size;
+ struct TALER_DenominationPublicKey pub_key;
/**
- * Size of the encoded signature that follows.
+ * Exchange's signature over the coin.
*/
- uint16_t sbuf_size;
+ struct TALER_DenominationSignature sig;
- /* Followed by serializations of:
- 1) struct TALER_DenominationPublicKey pub_key;
- 2) struct TALER_DenominationSignature sig;
- */
};
/**
- * Header of serialized data about a melt operation, suitable for
- * persisting it on disk.
+ * Data we keep for each fresh coin created in the
+ * melt process.
*/
-struct MeltDataP
+struct FreshCoinData
{
-
/**
- * Hash over the melting session.
+ * Denomination public key of the coin.
*/
- struct TALER_RefreshCommitmentP rc;
+ struct TALER_DenominationPublicKey fresh_pk;
/**
- * Number of coins we are melting, in NBO
+ * Array of planchet secrets for the coins, depending
+ * on the cut-and-choose.
*/
- uint16_t num_melted_coins GNUNET_PACKED;
+ struct TALER_PlanchetMasterSecretP ps[TALER_CNC_KAPPA];
/**
- * Number of coins we are creating, in NBO
+ * Private key of the coin.
*/
- uint16_t num_fresh_coins GNUNET_PACKED;
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
- /* Followed by serializations of:
- 1) struct MeltedCoinP melted_coins[num_melted_coins];
- 2) struct TALER_EXCHANGE_DenomPublicKey fresh_pks[num_fresh_coins];
- 3) TALER_CNC_KAPPA times:
- 3a) struct TALER_PlanchetSecretsP fresh_coins[num_fresh_coins];
- */
-};
+ /**
+ * Arrays of age commitments and proofs to be created, one for each
+ * cut-and-choose dimension. NULL if age restriction is not applicable.
+ */
+ struct TALER_AgeCommitmentProof *age_commitment_proofs[TALER_CNC_KAPPA];
+ /**
+ * Blinding key secrets for the coins, depending on the
+ * cut-and-choose.
+ */
+ union GNUNET_CRYPTO_BlindingSecretP bks[TALER_CNC_KAPPA];
-GNUNET_NETWORK_STRUCT_END
+};
/**
- * Information about a coin we are melting.
+ * Melt data in non-serialized format for convenient processing.
*/
-struct MeltedCoin
+struct MeltData
{
+
/**
- * Private key of the coin.
+ * Hash over the committed data during refresh operation.
*/
- struct TALER_CoinSpendPrivateKeyP coin_priv;
+ struct TALER_RefreshCommitmentP rc;
/**
- * Amount this coin contributes to the melt, including fee.
+ * Information about the melted coin.
*/
- struct TALER_Amount melt_amount_with_fee;
+ struct MeltedCoin melted_coin;
/**
- * The applicable fee for melting a coin of this denomination
+ * Array of length @e num_fresh_coins with information
+ * about each fresh coin.
*/
- struct TALER_Amount fee_melt;
+ struct FreshCoinData *fcds;
/**
- * The original value of the coin.
+ * Transfer secrets, one per cut and choose.
*/
- struct TALER_Amount original_value;
+ struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA];
/**
* Transfer private keys for each cut-and-choose dimension.
@@ -149,77 +147,52 @@ struct MeltedCoin
struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA];
/**
- * Timestamp indicating when coins of this denomination become invalid.
+ * Transfer public key of this commitment.
*/
- struct GNUNET_TIME_Absolute expire_deposit;
+ struct TALER_TransferPublicKeyP transfer_pub[TALER_CNC_KAPPA];
/**
- * Denomination key of the original coin.
+ * Transfer secrets, one per cut and choose.
*/
- struct TALER_DenominationPublicKey pub_key;
+ struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA];
/**
- * Exchange's signature over the coin.
+ * Blinded planchets and denominations of the fresh coins, depending on the cut-and-choose. Array of length
+ * @e num_fresh_coins.
*/
- struct TALER_DenominationSignature sig;
-
-};
-
-
-/**
- * Melt data in non-serialized format for convenient processing.
- */
-struct MeltData
-{
-
- /**
- * Hash over the committed data during refresh operation.
- */
- struct TALER_RefreshCommitmentP rc;
+ struct TALER_RefreshCoinData *rcd[TALER_CNC_KAPPA];
/**
* Number of coins we are creating
*/
uint16_t num_fresh_coins;
- /**
- * Information about the melted coin.
- */
- struct MeltedCoin melted_coin;
-
- /**
- * Array of @e num_fresh_coins denomination keys for the coins to be
- * freshly exchangeed.
- */
- struct TALER_DenominationPublicKey *fresh_pks;
-
- /**
- * Arrays of @e num_fresh_coins with information about the fresh
- * coins to be created, for each cut-and-choose dimension.
- */
- struct TALER_PlanchetSecretsP *fresh_coins[TALER_CNC_KAPPA];
};
/**
- * Deserialize melt data.
+ * Compute the melt data from the refresh data and secret.
*
- * @param buf serialized data
- * @param buf_size size of @a buf
- * @return deserialized melt data, NULL on error
+ * @param rms secret internals of the refresh-reveal operation
+ * @param rd refresh data with the characteristics of the operation
+ * @param alg_values contributions from the exchange into the melt
+ * @param[out] md where to write the derived melt data
*/
-struct MeltData *
-TALER_EXCHANGE_deserialize_melt_data_ (const char *buf,
- size_t buf_size);
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_get_melt_data_ (
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_EXCHANGE_RefreshData *rd,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ struct MeltData *md);
/**
* Free all information associated with a melting session. Note
* that we allow the melting session to be only partially initialized,
* as we use this function also when freeing melt data that was not
- * fully initialized (i.e. due to failures in #TALER_EXCHANGE_deserialize_melt_data_()).
+ * fully initialized.
*
- * @param md melting data to release, the pointer itself is NOT
+ * @param[in] md melting data to release, the pointer itself is NOT
* freed (as it is typically not allocated by itself)
*/
void
diff --git a/src/lib/exchange_api_refreshes_reveal.c b/src/lib/exchange_api_refreshes_reveal.c
index f07dc9047..69c53a6c9 100644
--- a/src/lib/exchange_api_refreshes_reveal.c
+++ b/src/lib/exchange_api_refreshes_reveal.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2020 Taler Systems SA
+ Copyright (C) 2015-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
@@ -40,11 +40,6 @@ struct TALER_EXCHANGE_RefreshesRevealHandle
{
/**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
* The url for this request.
*/
char *url;
@@ -61,6 +56,11 @@ struct TALER_EXCHANGE_RefreshesRevealHandle
struct GNUNET_CURL_Job *job;
/**
+ * Exchange-contributed values to the operation.
+ */
+ struct TALER_ExchangeWithdrawValues *alg_values;
+
+ /**
* Function to call with the result.
*/
TALER_EXCHANGE_RefreshesRevealCallback reveal_cb;
@@ -73,7 +73,7 @@ struct TALER_EXCHANGE_RefreshesRevealHandle
/**
* Actual information about the melt operation.
*/
- struct MeltData *md;
+ struct MeltData md;
/**
* The index selected by the exchange in cut-and-choose to not be revealed.
@@ -84,28 +84,28 @@ struct TALER_EXCHANGE_RefreshesRevealHandle
/**
- * We got a 200 OK response for the /refreshes/$RCH/reveal operation.
- * Extract the coin signatures and return them to the caller.
- * The signatures we get from the exchange is for the blinded value.
- * Thus, we first must unblind them and then should verify their
- * validity.
+ * We got a 200 OK response for the /refreshes/$RCH/reveal operation. Extract
+ * the coin signatures and return them to the caller. The signatures we get
+ * from the exchange is for the blinded value. Thus, we first must unblind
+ * them and then should verify their validity.
*
* If everything checks out, we return the unblinded signatures
* to the application via the callback.
*
* @param rrh operation handle
* @param json reply from the exchange
- * @param[out] sigs array of length `num_fresh_coins`, initialized to contain RSA signatures
+ * @param[out] rcis array of length `num_fresh_coins`, initialized to contain the coin data
* @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
*/
-static int
+static enum GNUNET_GenericReturnValue
refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
const json_t *json,
- struct TALER_DenominationSignature *sigs)
+ struct TALER_EXCHANGE_RevealedCoinInfo *rcis)
{
- json_t *jsona;
+ const json_t *jsona;
struct GNUNET_JSON_Specification outer_spec[] = {
- GNUNET_JSON_spec_json ("ev_sigs", &jsona),
+ GNUNET_JSON_spec_array_const ("ev_sigs",
+ &jsona),
GNUNET_JSON_spec_end ()
};
@@ -117,38 +117,44 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (! json_is_array (jsona))
- {
- /* We expected an array of coins */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (outer_spec);
- return GNUNET_SYSERR;
- }
- if (rrh->md->num_fresh_coins != json_array_size (jsona))
+ if (rrh->md.num_fresh_coins != json_array_size (jsona))
{
/* Number of coins generated does not match our expectation */
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (outer_spec);
return GNUNET_SYSERR;
}
- for (unsigned int i = 0; i<rrh->md->num_fresh_coins; i++)
+ for (unsigned int i = 0; i<rrh->md.num_fresh_coins; i++)
{
- const struct TALER_PlanchetSecretsP *fc;
- struct TALER_DenominationPublicKey *pk;
+ struct TALER_EXCHANGE_RevealedCoinInfo *rci = &rcis[i];
+ const struct FreshCoinData *fcd = &rrh->md.fcds[i];
+ const struct TALER_DenominationPublicKey *pk;
json_t *jsonai;
- struct GNUNET_CRYPTO_RsaSignature *blind_sig;
+ struct TALER_BlindedDenominationSignature blind_sig;
struct TALER_CoinSpendPublicKeyP coin_pub;
- struct GNUNET_HashCode coin_hash;
+ struct TALER_CoinPubHashP coin_hash;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_rsa_signature ("ev_sig", &blind_sig),
+ TALER_JSON_spec_blinded_denom_sig ("ev_sig",
+ &blind_sig),
GNUNET_JSON_spec_end ()
};
struct TALER_FreshCoin coin;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ const struct TALER_AgeCommitmentHash *pah = NULL;
- fc = &rrh->md->fresh_coins[rrh->noreveal_index][i];
- pk = &rrh->md->fresh_pks[i];
+ rci->ps = fcd->ps[rrh->noreveal_index];
+ rci->bks = fcd->bks[rrh->noreveal_index];
+ rci->age_commitment_proof = NULL;
+ pk = &fcd->fresh_pk;
jsonai = json_array_get (jsona, i);
GNUNET_assert (NULL != jsonai);
+ if (NULL != rrh->md.melted_coin.age_commitment_proof)
+ {
+ rci->age_commitment_proof
+ = fcd->age_commitment_proofs[rrh->noreveal_index];
+ TALER_age_commitment_hash (&rci->age_commitment_proof->commitment,
+ &rci->h_age_commitment);
+ pah = &rci->h_age_commitment;
+ }
if (GNUNET_OK !=
GNUNET_JSON_parse (jsonai,
@@ -156,33 +162,41 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
NULL, NULL))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (outer_spec);
return GNUNET_SYSERR;
}
+ TALER_planchet_setup_coin_priv (&rci->ps,
+ &rrh->alg_values[i],
+ &rci->coin_priv);
+ TALER_planchet_blinding_secret_create (&rci->ps,
+ &rrh->alg_values[i],
+ &bks);
/* needed to verify the signature, and we didn't store it earlier,
hence recomputing it here... */
- GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv,
+ GNUNET_CRYPTO_eddsa_key_get_public (&rci->coin_priv.eddsa_priv,
&coin_pub.eddsa_pub);
- GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub,
- sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
- &coin_hash);
+ TALER_coin_pub_hash (
+ &coin_pub,
+ pah,
+ &coin_hash);
if (GNUNET_OK !=
- TALER_planchet_to_coin (pk,
- blind_sig,
- fc,
- &coin_hash,
- &coin))
+ TALER_planchet_to_coin (
+ pk,
+ &blind_sig,
+ &bks,
+ &rci->coin_priv,
+ pah,
+ &coin_hash,
+ &rrh->alg_values[i],
+ &coin))
{
GNUNET_break_op (0);
- GNUNET_CRYPTO_rsa_signature_free (blind_sig);
- GNUNET_JSON_parse_free (outer_spec);
+ GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
- GNUNET_CRYPTO_rsa_signature_free (blind_sig);
- sigs[i] = coin.sig;
+ GNUNET_JSON_parse_free (spec);
+ rci->sig = coin.sig;
}
- GNUNET_JSON_parse_free (outer_spec);
return GNUNET_OK;
}
@@ -202,111 +216,101 @@ handle_refresh_reveal_finished (void *cls,
{
struct TALER_EXCHANGE_RefreshesRevealHandle *rrh = cls;
const json_t *j = response;
- enum TALER_ErrorCode ec;
+ struct TALER_EXCHANGE_RevealResult rr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
rrh->job = NULL;
switch (response_code)
{
case 0:
- ec = TALER_EC_INVALID_RESPONSE;
+ rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
{
- struct TALER_DenominationSignature sigs[rrh->md->num_fresh_coins];
- int ret;
+ struct TALER_EXCHANGE_RevealedCoinInfo rcis[rrh->md.num_fresh_coins];
+ enum GNUNET_GenericReturnValue ret;
- memset (sigs, 0, sizeof (sigs));
+ memset (rcis,
+ 0,
+ sizeof (rcis));
ret = refresh_reveal_ok (rrh,
j,
- sigs);
+ rcis);
if (GNUNET_OK != ret)
{
- response_code = 0;
- ec = TALER_EC_REVEAL_REPLY_MALFORMED;
+ rr.hr.http_status = 0;
+ rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
}
else
{
- ec = TALER_EC_NONE;
+ GNUNET_assert (rrh->noreveal_index < TALER_CNC_KAPPA);
+ rr.details.ok.num_coins = rrh->md.num_fresh_coins;
+ rr.details.ok.coins = rcis;
rrh->reveal_cb (rrh->reveal_cb_cls,
- MHD_HTTP_OK,
- ec,
- rrh->md->num_fresh_coins,
- rrh->md->fresh_coins[rrh->noreveal_index],
- sigs,
- j);
+ &rr);
rrh->reveal_cb = NULL;
}
- for (unsigned int i = 0; i<rrh->md->num_fresh_coins; i++)
- if (NULL != sigs[i].rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature);
+ for (unsigned int i = 0; i<rrh->md.num_fresh_coins; i++)
+ {
+ TALER_denom_sig_free (&rcis[i].sig);
+ TALER_age_commitment_proof_free (rcis[i].age_commitment_proof);
+ }
TALER_EXCHANGE_refreshes_reveal_cancel (rrh);
return;
}
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_CONFLICT:
/* Nothing really to verify, exchange says our reveal is inconsistent
with our commitment, so either side is buggy; we
should pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_GONE:
+ /* Server claims key expired or has been revoked */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
- ec = TALER_JSON_get_error_code (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
+ GNUNET_break_op (0);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u\n",
- (unsigned int) response_code);
- GNUNET_break (0);
- response_code = 0;
- ec = TALER_JSON_get_error_code (j);
+ "Unexpected response code %u/%d for exchange refreshes reveal\n",
+ (unsigned int) response_code,
+ (int) rr.hr.ec);
break;
}
if (NULL != rrh->reveal_cb)
rrh->reveal_cb (rrh->reveal_cb_cls,
- response_code,
- ec,
- 0,
- NULL,
- NULL,
- j);
+ &rr);
TALER_EXCHANGE_refreshes_reveal_cancel (rrh);
}
-/**
- * Submit a /refresh/reval request to the exchange and get the exchange's
- * response.
- *
- * This API is typically used by a wallet. Note that to ensure that
- * no money is lost in case of hardware failures, the provided
- * arguments should have been committed to persistent storage
- * prior to calling this function.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param refresh_data_length size of the @a refresh_data (returned
- * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare())
- * @param refresh_data the refresh data as returned from
- #TALER_EXCHANGE_refresh_prepare())
- * @param noreveal_index response from the exchange to the
- * #TALER_EXCHANGE_melt() invocation
- * @param reveal_cb the callback to call with the final result of the
- * refresh operation
- * @param reveal_cb_cls closure for the above callback
- * @return a handle for this request; NULL if the argument was invalid.
- * In this case, neither callback will be called.
- */
struct TALER_EXCHANGE_RefreshesRevealHandle *
TALER_EXCHANGE_refreshes_reveal (
- struct TALER_EXCHANGE_Handle *exchange,
- size_t refresh_data_length,
- const char *refresh_data,
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_EXCHANGE_RefreshData *rd,
+ unsigned int num_coins,
+ const struct TALER_ExchangeWithdrawValues alg_values[static num_coins],
uint32_t noreveal_index,
TALER_EXCHANGE_RefreshesRevealCallback reveal_cb,
void *reveal_cb_cls)
@@ -317,12 +321,13 @@ TALER_EXCHANGE_refreshes_reveal (
json_t *coin_evs;
json_t *reveal_obj;
json_t *link_sigs;
+ json_t *old_age_commitment = NULL;
CURL *eh;
- struct GNUNET_CURL_Context *ctx;
- struct MeltData *md;
- struct TALER_TransferPublicKeyP transfer_pub;
+ struct MeltData md;
char arg_str[sizeof (struct TALER_RefreshCommitmentP) * 2 + 32];
+ bool send_rms = false;
+ GNUNET_assert (num_coins == rd->fresh_pks_len);
if (noreveal_index >= TALER_CNC_KAPPA)
{
/* We check this here, as it would be really bad to below just
@@ -332,83 +337,59 @@ TALER_EXCHANGE_refreshes_reveal (
GNUNET_break (0);
return NULL;
}
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
- md = TALER_EXCHANGE_deserialize_melt_data_ (refresh_data,
- refresh_data_length);
- if (NULL == md)
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_get_melt_data_ (rms,
+ rd,
+ alg_values,
+ &md))
{
GNUNET_break (0);
return NULL;
}
- /* now transfer_pub */
- GNUNET_CRYPTO_ecdhe_key_get_public (
- &md->melted_coin.transfer_priv[noreveal_index].ecdhe_priv,
- &transfer_pub.ecdhe_pub);
-
/* now new_denoms */
GNUNET_assert (NULL != (new_denoms_h = json_array ()));
GNUNET_assert (NULL != (coin_evs = json_array ()));
GNUNET_assert (NULL != (link_sigs = json_array ()));
- for (unsigned int i = 0; i<md->num_fresh_coins; i++)
+ for (unsigned int i = 0; i<md.num_fresh_coins; i++)
{
- struct GNUNET_HashCode denom_hash;
- struct TALER_PlanchetDetail pd;
-
- GNUNET_CRYPTO_rsa_public_key_hash (md->fresh_pks[i].rsa_public_key,
- &denom_hash);
+ const struct TALER_RefreshCoinData *rcd = &md.rcd[noreveal_index][i];
+ struct TALER_DenominationHashP denom_hash;
+
+ if (GNUNET_CRYPTO_BSA_CS ==
+ md.fcds[i].fresh_pk.bsign_pub_key->cipher)
+ send_rms = true;
+ TALER_denom_pub_hash (&md.fcds[i].fresh_pk,
+ &denom_hash);
GNUNET_assert (0 ==
json_array_append_new (new_denoms_h,
GNUNET_JSON_from_data_auto (
&denom_hash)));
-
- if (GNUNET_OK !=
- TALER_planchet_prepare (&md->fresh_pks[i],
- &md->fresh_coins[noreveal_index][i],
- &pd))
- {
- /* This should have been noticed during the preparation stage. */
- GNUNET_break (0);
- json_decref (new_denoms_h);
- json_decref (coin_evs);
- return NULL;
- }
GNUNET_assert (0 ==
- json_array_append_new (coin_evs,
- GNUNET_JSON_from_data (pd.coin_ev,
- pd.coin_ev_size)));
-
- /* compute link signature */
+ json_array_append_new (
+ coin_evs,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_planchet (
+ NULL,
+ &rcd->blinded_planchet))));
{
struct TALER_CoinSpendSignatureP link_sig;
- struct TALER_LinkDataPS ldp;
-
- ldp.purpose.size = htonl (sizeof (ldp));
- ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK);
- ldp.h_denom_pub = denom_hash;
- GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv,
- &ldp.old_coin_pub.eddsa_pub);
- ldp.transfer_pub = transfer_pub;
- GNUNET_CRYPTO_hash (pd.coin_ev,
- pd.coin_ev_size,
- &ldp.coin_envelope_hash);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (
- &md->melted_coin.coin_priv.eddsa_priv,
- &ldp.purpose,
- &link_sig.eddsa_signature));
+ struct TALER_BlindedCoinHashP bch;
+
+ TALER_coin_ev_hash (&rcd->blinded_planchet,
+ &denom_hash,
+ &bch);
+ TALER_wallet_link_sign (
+ &denom_hash,
+ &md.transfer_pub[noreveal_index],
+ &bch,
+ &md.melted_coin.coin_priv,
+ &link_sig);
GNUNET_assert (0 ==
- json_array_append_new (link_sigs,
- GNUNET_JSON_from_data_auto (
- &link_sig)));
+ json_array_append_new (
+ link_sigs,
+ GNUNET_JSON_from_data_auto (&link_sig)));
}
-
- GNUNET_free (pd.coin_ev);
}
/* build array of transfer private keys */
@@ -417,58 +398,91 @@ TALER_EXCHANGE_refreshes_reveal (
{
if (j == noreveal_index)
{
- /* This is crucial: exclude the transfer key for the
- noreval index! */
+ /* This is crucial: exclude the transfer key for the noreval index! */
continue;
}
GNUNET_assert (0 ==
json_array_append_new (transfer_privs,
GNUNET_JSON_from_data_auto (
- &md->melted_coin.transfer_priv[j])));
+ &md.transfer_priv[j])));
}
- /* build main JSON request */
- reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o}",
- "transfer_pub",
- GNUNET_JSON_from_data_auto (&transfer_pub),
- "transfer_privs",
- transfer_privs,
- "link_sigs",
- link_sigs,
- "new_denoms_h",
- new_denoms_h,
- "coin_evs",
- coin_evs);
- if (NULL == reveal_obj)
+ /* build array of old age commitment, if applicable */
+ if (NULL != rd->melt_age_commitment_proof)
{
- GNUNET_break (0);
- return NULL;
+ GNUNET_assert (NULL != rd->melt_h_age_commitment);
+ GNUNET_assert (NULL != (old_age_commitment = json_array ()));
+
+ for (size_t i = 0; i < rd->melt_age_commitment_proof->commitment.num; i++)
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = json_array_append_new (
+ old_age_commitment,
+ GNUNET_JSON_from_data_auto (
+ &rd->melt_age_commitment_proof->commitment.keys[i]));
+ GNUNET_assert (0 == ret);
+ }
}
+ /* build main JSON request */
+ reveal_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("transfer_pub",
+ &md.transfer_pub[noreveal_index]),
+ GNUNET_JSON_pack_allow_null (
+ send_rms
+ ? GNUNET_JSON_pack_data_auto ("rms",
+ rms)
+ : GNUNET_JSON_pack_string ("rms",
+ NULL)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("old_age_commitment",
+ old_age_commitment)),
+ GNUNET_JSON_pack_array_steal ("transfer_privs",
+ transfer_privs),
+ GNUNET_JSON_pack_array_steal ("link_sigs",
+ link_sigs),
+ GNUNET_JSON_pack_array_steal ("new_denoms_h",
+ new_denoms_h),
+ GNUNET_JSON_pack_array_steal ("coin_evs",
+ coin_evs));
{
char pub_str[sizeof (struct TALER_RefreshCommitmentP) * 2];
char *end;
- end = GNUNET_STRINGS_data_to_string (&md->rc,
- sizeof (struct
- TALER_RefreshCommitmentP),
+ end = GNUNET_STRINGS_data_to_string (&md.rc,
+ sizeof (md.rc),
pub_str,
sizeof (pub_str));
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/refreshes/%s/reveal",
+ "refreshes/%s/reveal",
pub_str);
}
/* finally, we can actually issue the request */
rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshesRevealHandle);
- rrh->exchange = exchange;
rrh->noreveal_index = noreveal_index;
rrh->reveal_cb = reveal_cb;
rrh->reveal_cb_cls = reveal_cb_cls;
rrh->md = md;
- rrh->url = TEAH_path_to_url (rrh->exchange,
- arg_str);
+ rrh->alg_values
+ = GNUNET_new_array (md.num_fresh_coins,
+ struct TALER_ExchangeWithdrawValues);
+ for (unsigned int i = 0; i<md.num_fresh_coins; i++)
+ TALER_denom_ewv_copy (&rrh->alg_values[i],
+ &alg_values[i]);
+ rrh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rrh->url)
+ {
+ json_decref (reveal_obj);
+ TALER_EXCHANGE_free_melt_data_ (&md);
+ GNUNET_free (rrh->alg_values);
+ GNUNET_free (rrh);
+ return NULL;
+ }
eh = TALER_EXCHANGE_curl_easy_get_ (rrh->url);
if ( (NULL == eh) ||
@@ -481,12 +495,13 @@ TALER_EXCHANGE_refreshes_reveal (
if (NULL != eh)
curl_easy_cleanup (eh);
json_decref (reveal_obj);
+ TALER_EXCHANGE_free_melt_data_ (&md);
+ GNUNET_free (rrh->alg_values);
GNUNET_free (rrh->url);
GNUNET_free (rrh);
return NULL;
}
json_decref (reveal_obj);
- ctx = TEAH_handle_to_context (rrh->exchange);
rrh->job = GNUNET_CURL_job_add2 (ctx,
eh,
rrh->ctx.headers,
@@ -496,12 +511,6 @@ TALER_EXCHANGE_refreshes_reveal (
}
-/**
- * Cancel a refresh reveal request. This function cannot be used
- * on a request handle if the callback was already invoked.
- *
- * @param rrh the refresh reval handle
- */
void
TALER_EXCHANGE_refreshes_reveal_cancel (
struct TALER_EXCHANGE_RefreshesRevealHandle *rrh)
@@ -511,10 +520,12 @@ TALER_EXCHANGE_refreshes_reveal_cancel (
GNUNET_CURL_job_cancel (rrh->job);
rrh->job = NULL;
}
+ for (unsigned int i = 0; i<rrh->md.num_fresh_coins; i++)
+ TALER_denom_ewv_free (&rrh->alg_values[i]);
+ GNUNET_free (rrh->alg_values);
GNUNET_free (rrh->url);
TALER_curl_easy_post_finished (&rrh->ctx);
- TALER_EXCHANGE_free_melt_data_ (rrh->md); /* does not free 'md' itself */
- GNUNET_free (rrh->md);
+ TALER_EXCHANGE_free_melt_data_ (&rrh->md);
GNUNET_free (rrh);
}
diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c
index 826c39b17..9159b55f2 100644
--- a/src/lib/exchange_api_refund.c
+++ b/src/lib/exchange_api_refund.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 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
@@ -39,9 +39,9 @@ struct TALER_EXCHANGE_RefundHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -70,9 +70,33 @@ struct TALER_EXCHANGE_RefundHandle
void *cb_cls;
/**
- * Information the exchange should sign in response.
+ * Hash over the proposal data to identify the contract
+ * which is being refunded.
*/
- struct TALER_RefundConfirmationPS depconf;
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * The coin's public key. This is the value that must have been
+ * signed (blindly) by the Exchange.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * The Merchant's public key. Allows the merchant to later refund
+ * the transaction or to inquire about the wire transfer identifier.
+ */
+ struct TALER_MerchantPublicKeyP merchant;
+
+ /**
+ * Merchant-generated transaction ID for the refund.
+ */
+ uint64_t rtransaction_id;
+
+ /**
+ * Amount to be refunded, including refund fee charged by the
+ * exchange to the customer.
+ */
+ struct TALER_Amount refund_amount;
};
@@ -81,21 +105,23 @@ struct TALER_EXCHANGE_RefundHandle
* Verify that the signature on the "200 OK" response
* from the exchange is valid.
*
- * @param rh refund handle
+ * @param[in,out] rh refund handle (refund fee added)
* @param json json reply with the signature
* @param[out] exchange_pub set to the exchange's public key
+ * @param[out] exchange_sig set to the exchange's signature
* @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
*/
-static int
-verify_refund_signature_ok (const struct TALER_EXCHANGE_RefundHandle *rh,
+static enum GNUNET_GenericReturnValue
+verify_refund_signature_ok (struct TALER_EXCHANGE_RefundHandle *rh,
const json_t *json,
- struct TALER_ExchangePublicKeyP *exchange_pub)
+ struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_ExchangeSignatureP *exchange_sig)
{
- struct TALER_ExchangeSignatureP exchange_sig;
- const struct TALER_EXCHANGE_Keys *key_state;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("sig", &exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("pub", exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ exchange_pub),
GNUNET_JSON_spec_end ()
};
@@ -107,19 +133,22 @@ verify_refund_signature_ok (const struct TALER_EXCHANGE_RefundHandle *rh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- key_state = TALER_EXCHANGE_get_keys (rh->exchange);
if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
+ TALER_EXCHANGE_test_signing_key (rh->keys,
exchange_pub))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND,
- &rh->depconf.purpose,
- &exchange_sig.eddsa_signature,
- &exchange_pub->eddsa_pub))
+ TALER_exchange_online_refund_confirmation_verify (
+ &rh->h_contract_terms,
+ &rh->coin_pub,
+ &rh->merchant,
+ rh->rtransaction_id,
+ &rh->refund_amount,
+ exchange_pub,
+ exchange_sig))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
@@ -129,6 +158,102 @@ verify_refund_signature_ok (const struct TALER_EXCHANGE_RefundHandle *rh,
/**
+ * Verify that the information on the "412 Dependency Failed" response
+ * from the exchange is valid and indeed shows that there is a refund
+ * transaction ID reuse going on.
+ *
+ * @param[in,out] rh refund handle (refund fee added)
+ * @param json json reply with the signature
+ * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
+ */
+static enum GNUNET_GenericReturnValue
+verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh,
+ const json_t *json)
+{
+ const json_t *h;
+ json_t *e;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("history",
+ &h),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (1 != json_array_size (h))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ e = json_array_get (h, 0);
+ {
+ struct TALER_Amount amount;
+ const char *type;
+ struct TALER_MerchantSignatureP sig;
+ struct TALER_Amount refund_fee;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ uint64_t rtransaction_id;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &amount),
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ TALER_JSON_spec_amount_any ("refund_fee",
+ &refund_fee),
+ GNUNET_JSON_spec_fixed_auto ("merchant_sig",
+ &sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &merchant_pub),
+ GNUNET_JSON_spec_uint64 ("rtransaction_id",
+ &rtransaction_id),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (e,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_merchant_refund_verify (&rh->coin_pub,
+ &h_contract_terms,
+ rtransaction_id,
+ &amount,
+ &merchant_pub,
+ &sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if ( (rtransaction_id != rh->rtransaction_id) ||
+ (0 != GNUNET_memcmp (&rh->h_contract_terms,
+ &h_contract_terms)) ||
+ (0 != GNUNET_memcmp (&rh->merchant,
+ &merchant_pub)) ||
+ (0 == TALER_amount_cmp (&rh->refund_amount,
+ &amount)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
* Function called when we're done processing the
* HTTP /refund request.
*
@@ -142,264 +267,176 @@ handle_refund_finished (void *cls,
const void *response)
{
struct TALER_EXCHANGE_RefundHandle *rh = cls;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangePublicKeyP *ep = NULL;
const json_t *j = response;
- enum TALER_ErrorCode ec;
+ struct TALER_EXCHANGE_RefundResponse rr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
rh->job = NULL;
switch (response_code)
{
case 0:
- ec = TALER_EC_INVALID_RESPONSE;
+ rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
verify_refund_signature_ok (rh,
j,
- &exchange_pub))
+ &rr.details.ok.exchange_pub,
+ &rr.details.ok.exchange_sig))
{
GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_REFUND_INVALID_SIGNATURE_BY_EXCHANGE;
- }
- else
- {
- ep = &exchange_pub;
- ec = TALER_EC_NONE;
+ rr.hr.http_status = 0;
+ rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE;
}
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ (or API version conflict); also can happen if the currency
+ differs (which we should obviously never support).
+ Just pass JSON reply to the application */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_FORBIDDEN:
/* Nothing really to verify, exchange says one of the signatures is
invalid; as we checked them, this should never happen, we
should pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Requested total refunds exceed deposited amount */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_GONE:
/* Kind of normal: the money was already sent to the merchant
(it was too late for the refund). */
- ec = TALER_JSON_get_error_code (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
- case MHD_HTTP_PRECONDITION_FAILED:
- /* Client request was inconsistent; might be a currency mismatch
- problem. */
- ec = TALER_JSON_get_error_code (j);
+ case MHD_HTTP_FAILED_DEPENDENCY:
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
- case MHD_HTTP_CONFLICT:
- /* Two refund requests were made about the same deposit, but
- carrying different refund transaction ids. */
- ec = TALER_JSON_get_error_code (j);
+ case MHD_HTTP_PRECONDITION_FAILED:
+ if (GNUNET_OK !=
+ verify_failed_dependency_ok (rh,
+ j))
+ {
+ GNUNET_break (0);
+ rr.hr.http_status = 0;
+ rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE;
+ rr.hr.hint = "failed precondition proof returned by exchange is invalid";
+ break;
+ }
+ /* Two different refund requests were made about the same deposit, but
+ carrying identical refund transaction ids. */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
- ec = TALER_JSON_get_error_code (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
- ec = TALER_JSON_get_error_code (j);
+ GNUNET_break_op (0);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
+ "Unexpected response code %u/%d for exchange refund\n",
(unsigned int) response_code,
- ec);
- GNUNET_break (0);
- response_code = 0;
+ rr.hr.ec);
break;
}
rh->cb (rh->cb_cls,
- response_code,
- ec,
- ep,
- j);
+ &rr);
TALER_EXCHANGE_refund_cancel (rh);
}
-/**
- * Submit a refund request to the exchange and get the exchange's
- * response. This API is used by a merchant. Note that
- * while we return the response verbatim to the caller for further
- * processing, we do already verify that the response is well-formed
- * (i.e. that signatures included in the response are all valid). If
- * the exchange's reply is not well-formed, we return an HTTP status code
- * of zero to @a cb.
- *
- * The @a exchange must be ready to operate (i.e. have
- * finished processing the /keys reply). If this check fails, we do
- * NOT initiate the transaction with the exchange and instead return NULL.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param amount the amount to be refunded; must be larger than the refund fee
- * (as that fee is still being subtracted), and smaller than the amount
- * (with deposit fee) of the original deposit contribution of this coin
- * @param refund_fee fee applicable to this coin for the refund
- * @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded
- * @param coin_pub coin’s public key of the coin from the original deposit operation
- * @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation);
- * this is needed as we may first do a partial refund and later a full refund. If both
- * refunds are also over the same amount, we need the @a rtransaction_id to make the disjoint
- * refund requests different (as requests are idempotent and otherwise the 2nd refund might not work).
- * @param merchant_priv the private key of the merchant, used to generate signature for refund request
- * @param cb the callback to call when a reply for this request is available
- * @param cb_cls closure for the above callback
- * @return a handle for this request; NULL if the inputs are invalid (i.e.
- * signatures fail to verify). In this case, the callback is not called.
- */
struct TALER_EXCHANGE_RefundHandle *
-TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_Amount *amount,
- const struct TALER_Amount *refund_fee,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- uint64_t rtransaction_id,
- const struct TALER_MerchantPrivateKeyP *merchant_priv,
- TALER_EXCHANGE_RefundCallback cb,
- void *cb_cls)
+TALER_EXCHANGE_refund (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_Amount *amount,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t rtransaction_id,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ TALER_EXCHANGE_RefundCallback cb,
+ void *cb_cls)
{
- struct TALER_RefundRequestPS rr;
+ struct TALER_MerchantPublicKeyP merchant_pub;
struct TALER_MerchantSignatureP merchant_sig;
-
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
- rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND);
- rr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS));
- rr.h_contract_terms = *h_contract_terms;
- rr.coin_pub = *coin_pub;
- GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
- &rr.merchant.eddsa_pub);
- rr.rtransaction_id = GNUNET_htonll (rtransaction_id);
- TALER_amount_hton (&rr.refund_amount,
- amount);
- TALER_amount_hton (&rr.refund_fee,
- refund_fee);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv,
- &rr.purpose,
- &merchant_sig.eddsa_sig));
- return TALER_EXCHANGE_refund2 (exchange,
- amount,
- refund_fee,
- h_contract_terms,
- coin_pub,
- rtransaction_id,
- &rr.merchant,
- &merchant_sig,
- cb,
- cb_cls);
-}
-
-
-/**
- * Submit a refund request to the exchange and get the exchange's
- * response. This API is used by a merchant. Note that
- * while we return the response verbatim to the caller for further
- * processing, we do already verify that the response is well-formed
- * (i.e. that signatures included in the response are all valid). If
- * the exchange's reply is not well-formed, we return an HTTP status code
- * of zero to @a cb.
- *
- * The @a exchange must be ready to operate (i.e. have
- * finished processing the /keys reply). If this check fails, we do
- * NOT initiate the transaction with the exchange and instead return NULL.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param amount the amount to be refunded; must be larger than the refund fee
- * (as that fee is still being subtracted), and smaller than the amount
- * (with deposit fee) of the original deposit contribution of this coin
- * @param refund_fee fee applicable to this coin for the refund
- * @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded
- * @param coin_pub coin’s public key of the coin from the original deposit operation
- * @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation);
- * this is needed as we may first do a partial refund and later a full refund. If both
- * refunds are also over the same amount, we need the @a rtransaction_id to make the disjoint
- * refund requests different (as requests are idempotent and otherwise the 2nd refund might not work).
- * @param merchant_pub public key of the merchant
- * @param merchant_sig signature affirming the refund from the merchant
- * @param cb the callback to call when a reply for this request is available
- * @param cb_cls closure for the above callback
- * @return a handle for this request; NULL if the inputs are invalid (i.e.
- * signatures fail to verify). In this case, the callback is not called.
- */
-struct TALER_EXCHANGE_RefundHandle *
-TALER_EXCHANGE_refund2 (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_Amount *amount,
- const struct TALER_Amount *refund_fee,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- uint64_t rtransaction_id,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_MerchantSignatureP *merchant_sig,
- TALER_EXCHANGE_RefundCallback cb,
- void *cb_cls)
-{
struct TALER_EXCHANGE_RefundHandle *rh;
- struct GNUNET_CURL_Context *ctx;
json_t *refund_obj;
CURL *eh;
char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
+ GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
+ &merchant_pub.eddsa_pub);
+ TALER_merchant_refund_sign (coin_pub,
+ h_contract_terms,
+ rtransaction_id,
+ amount,
+ merchant_priv,
+ &merchant_sig);
{
char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
char *end;
- end = GNUNET_STRINGS_data_to_string (coin_pub,
- sizeof (struct
- TALER_CoinSpendPublicKeyP),
- pub_str,
- sizeof (pub_str));
+ end = GNUNET_STRINGS_data_to_string (
+ coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ pub_str,
+ sizeof (pub_str));
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/coins/%s/refund",
+ "coins/%s/refund",
pub_str);
}
- refund_obj = json_pack ("{s:o, s:o," /* amount/fee */
- " s:o," /* h_contract_terms */
- " s:I," /* rtransaction id */
- " s:o, s:o}", /* merchant_pub, merchant_sig */
- "refund_amount", TALER_JSON_from_amount (amount),
- "refund_fee", TALER_JSON_from_amount (refund_fee),
- "h_contract_terms", GNUNET_JSON_from_data_auto (
- h_contract_terms),
- "rtransaction_id", (json_int_t) rtransaction_id,
- "merchant_pub", GNUNET_JSON_from_data_auto (
- merchant_pub),
- "merchant_sig", GNUNET_JSON_from_data_auto (
- merchant_sig)
- );
- if (NULL == refund_obj)
- {
- GNUNET_break (0);
- return NULL;
- }
-
+ refund_obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("refund_amount",
+ amount),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ h_contract_terms),
+ GNUNET_JSON_pack_uint64 ("rtransaction_id",
+ rtransaction_id),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ &merchant_pub),
+ GNUNET_JSON_pack_data_auto ("merchant_sig",
+ &merchant_sig));
rh = GNUNET_new (struct TALER_EXCHANGE_RefundHandle);
- rh->exchange = exchange;
rh->cb = cb;
rh->cb_cls = cb_cls;
- rh->url = TEAH_path_to_url (exchange,
- arg_str);
- rh->depconf.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS));
- rh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND);
- rh->depconf.h_contract_terms = *h_contract_terms;
- rh->depconf.coin_pub = *coin_pub;
- rh->depconf.merchant = *merchant_pub;
- rh->depconf.rtransaction_id = GNUNET_htonll (rtransaction_id);
- TALER_amount_hton (&rh->depconf.refund_amount,
- amount);
- TALER_amount_hton (&rh->depconf.refund_fee,
- refund_fee);
-
+ rh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rh->url)
+ {
+ json_decref (refund_obj);
+ GNUNET_free (rh);
+ return NULL;
+ }
+ rh->h_contract_terms = *h_contract_terms;
+ rh->coin_pub = *coin_pub;
+ rh->merchant = merchant_pub;
+ rh->rtransaction_id = rtransaction_id;
+ rh->refund_amount = *amount;
eh = TALER_EXCHANGE_curl_easy_get_ (rh->url);
if ( (NULL == eh) ||
(GNUNET_OK !=
@@ -419,7 +456,7 @@ TALER_EXCHANGE_refund2 (struct TALER_EXCHANGE_Handle *exchange,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for refund: `%s'\n",
rh->url);
- ctx = TEAH_handle_to_context (exchange);
+ rh->keys = TALER_EXCHANGE_keys_incref (keys);
rh->job = GNUNET_CURL_job_add2 (ctx,
eh,
rh->ctx.headers,
@@ -429,12 +466,6 @@ TALER_EXCHANGE_refund2 (struct TALER_EXCHANGE_Handle *exchange,
}
-/**
- * Cancel a refund permission request. This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param refund the refund permission request handle
- */
void
TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund)
{
@@ -445,6 +476,7 @@ TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund)
}
GNUNET_free (refund->url);
TALER_curl_easy_post_finished (&refund->ctx);
+ TALER_EXCHANGE_keys_decref (refund->keys);
GNUNET_free (refund);
}
diff --git a/src/lib/exchange_api_reserves_attest.c b/src/lib/exchange_api_reserves_attest.c
new file mode 100644
index 000000000..d5a867114
--- /dev/null
+++ b/src/lib/exchange_api_reserves_attest.c
@@ -0,0 +1,365 @@
+/*
+ This file is part of TALER
+ 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
+ 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 lib/exchange_api_reserves_attest.c
+ * @brief Implementation of the POST /reserves-attest/$RESERVE_PUB requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP attest codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves-attest/$RID Handle
+ */
+struct TALER_EXCHANGE_ReservesAttestHandle
+{
+
+ /**
+ * The keys of the this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReservesPostAttestCallback cb;
+
+ /**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK attest code. Handle the JSON
+ * response.
+ *
+ * @param rsh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_attest_ok (struct TALER_EXCHANGE_ReservesAttestHandle *rsh,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReservePostAttestResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ const json_t *attributes;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("exchange_timestamp",
+ &rs.details.ok.exchange_time),
+ GNUNET_JSON_spec_timestamp ("expiration_time",
+ &rs.details.ok.expiration_time),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rs.details.ok.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rs.details.ok.exchange_pub),
+ GNUNET_JSON_spec_object_const ("attributes",
+ &attributes),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (rsh->keys,
+ &rs.details.ok.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ rs.details.ok.attributes = attributes;
+ if (GNUNET_OK !=
+ TALER_exchange_online_reserve_attest_details_verify (
+ rs.details.ok.exchange_time,
+ rs.details.ok.expiration_time,
+ &rsh->reserve_pub,
+ attributes,
+ &rs.details.ok.exchange_pub,
+ &rs.details.ok.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves-attest/$RID request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesAttestHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_attest_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReservesAttestHandle *rsh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_ReservePostAttestResult rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ rsh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_reserves_attest_ok (rsh,
+ j))
+ {
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Server doesn't have the requested attributes */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for reserves attest\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec);
+ break;
+ }
+ if (NULL != rsh->cb)
+ {
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ }
+ TALER_EXCHANGE_reserves_attest_cancel (rsh);
+}
+
+
+struct TALER_EXCHANGE_ReservesAttestHandle *
+TALER_EXCHANGE_reserves_attest (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ unsigned int attributes_length,
+ const char *attributes[const static attributes_length],
+ TALER_EXCHANGE_ReservesPostAttestCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ReservesAttestHandle *rsh;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+ struct TALER_ReserveSignatureP reserve_sig;
+ json_t *details;
+ struct GNUNET_TIME_Timestamp ts;
+
+ if (0 == attributes_length)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ details = json_array ();
+ GNUNET_assert (NULL != details);
+ for (unsigned int i = 0; i<attributes_length; i++)
+ {
+ GNUNET_assert (0 ==
+ json_array_append_new (details,
+ json_string (attributes[i])));
+ }
+ rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesAttestHandle);
+ rsh->cb = cb;
+ rsh->cb_cls = cb_cls;
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &rsh->reserve_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &rsh->reserve_pub,
+ sizeof (rsh->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves-attest/%s",
+ pub_str);
+ }
+ rsh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rsh->url)
+ {
+ json_decref (details);
+ GNUNET_free (rsh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ json_decref (details);
+ GNUNET_free (rsh->url);
+ GNUNET_free (rsh);
+ return NULL;
+ }
+ ts = GNUNET_TIME_timestamp_get ();
+ TALER_wallet_reserve_attest_request_sign (ts,
+ details,
+ reserve_priv,
+ &reserve_sig);
+ {
+ json_t *attest_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &reserve_sig),
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ ts),
+ GNUNET_JSON_pack_array_steal ("details",
+ details));
+
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&rsh->post_ctx,
+ eh,
+ attest_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (attest_obj);
+ GNUNET_free (rsh->url);
+ GNUNET_free (rsh);
+ return NULL;
+ }
+ json_decref (attest_obj);
+ }
+ rsh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ rsh->post_ctx.headers,
+ &handle_reserves_attest_finished,
+ rsh);
+ rsh->keys = TALER_EXCHANGE_keys_incref (keys);
+ return rsh;
+}
+
+
+void
+TALER_EXCHANGE_reserves_attest_cancel (
+ struct TALER_EXCHANGE_ReservesAttestHandle *rsh)
+{
+ if (NULL != rsh->job)
+ {
+ GNUNET_CURL_job_cancel (rsh->job);
+ rsh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&rsh->post_ctx);
+ TALER_EXCHANGE_keys_decref (rsh->keys);
+ GNUNET_free (rsh->url);
+ GNUNET_free (rsh);
+}
+
+
+/* end of exchange_api_reserves_attest.c */
diff --git a/src/lib/exchange_api_reserves_close.c b/src/lib/exchange_api_reserves_close.c
new file mode 100644
index 000000000..a3769a22f
--- /dev/null
+++ b/src/lib/exchange_api_reserves_close.c
@@ -0,0 +1,373 @@
+/*
+ This file is part of TALER
+ 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
+ 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 lib/exchange_api_reserves_close.c
+ * @brief Implementation of the POST /reserves/$RESERVE_PUB/close requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP close codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves/$RID/close Handle
+ */
+struct TALER_EXCHANGE_ReservesCloseHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReservesCloseCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Our signature.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * When did we make the request.
+ */
+ struct GNUNET_TIME_Timestamp ts;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK close code. Handle the JSON
+ * response.
+ *
+ * @param rch handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_close_ok (struct TALER_EXCHANGE_ReservesCloseHandle *rch,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReserveCloseResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK,
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("wire_amount",
+ &rs.details.ok.wire_amount),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rch->cb (rch->cb_cls,
+ &rs);
+ rch->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS close code. Handle the JSON
+ * response.
+ *
+ * @param rch handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_close_kyc (struct TALER_EXCHANGE_ReservesCloseHandle *rch,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReserveCloseResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &rs.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &rs.details.unavailable_for_legal_reasons.requirement_row),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rch->cb (rch->cb_cls,
+ &rs);
+ rch->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RID/close request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesCloseHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_close_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReservesCloseHandle *rch = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_ReserveCloseResult rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ rch->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_reserves_close_ok (rch,
+ j))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Insufficient balance to inquire for reserve close */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ if (GNUNET_OK !=
+ handle_reserves_close_kyc (rch,
+ j))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for reserves close\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec);
+ break;
+ }
+ if (NULL != rch->cb)
+ {
+ rch->cb (rch->cb_cls,
+ &rs);
+ rch->cb = NULL;
+ }
+ TALER_EXCHANGE_reserves_close_cancel (rch);
+}
+
+
+struct TALER_EXCHANGE_ReservesCloseHandle *
+TALER_EXCHANGE_reserves_close (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const char *target_payto_uri,
+ TALER_EXCHANGE_ReservesCloseCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ReservesCloseHandle *rch;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+ struct TALER_PaytoHashP h_payto;
+
+ rch = GNUNET_new (struct TALER_EXCHANGE_ReservesCloseHandle);
+ rch->cb = cb;
+ rch->cb_cls = cb_cls;
+ rch->ts = GNUNET_TIME_timestamp_get ();
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &rch->reserve_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &rch->reserve_pub,
+ sizeof (rch->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/close",
+ pub_str);
+ }
+ rch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rch->url)
+ {
+ GNUNET_free (rch);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (rch->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (rch->url);
+ GNUNET_free (rch);
+ return NULL;
+ }
+ if (NULL != target_payto_uri)
+ TALER_payto_hash (target_payto_uri,
+ &h_payto);
+ TALER_wallet_reserve_close_sign (rch->ts,
+ (NULL != target_payto_uri)
+ ? &h_payto
+ : NULL,
+ reserve_priv,
+ &rch->reserve_sig);
+ {
+ json_t *close_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("payto_uri",
+ target_payto_uri)),
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ rch->ts),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &rch->reserve_sig));
+
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&rch->post_ctx,
+ eh,
+ close_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (close_obj);
+ GNUNET_free (rch->url);
+ GNUNET_free (rch);
+ return NULL;
+ }
+ json_decref (close_obj);
+ }
+ rch->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ rch->post_ctx.headers,
+ &handle_reserves_close_finished,
+ rch);
+ return rch;
+}
+
+
+void
+TALER_EXCHANGE_reserves_close_cancel (
+ struct TALER_EXCHANGE_ReservesCloseHandle *rch)
+{
+ if (NULL != rch->job)
+ {
+ GNUNET_CURL_job_cancel (rch->job);
+ rch->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&rch->post_ctx);
+ GNUNET_free (rch->url);
+ GNUNET_free (rch);
+}
+
+
+/* end of exchange_api_reserves_close.c */
diff --git a/src/lib/exchange_api_reserves_get.c b/src/lib/exchange_api_reserves_get.c
index e44eefad6..b6980dd1d 100644
--- a/src/lib/exchange_api_reserves_get.c
+++ b/src/lib/exchange_api_reserves_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
+ Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -39,11 +39,6 @@ struct TALER_EXCHANGE_ReservesGetHandle
{
/**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
* The url for this request.
*/
char *url;
@@ -79,16 +74,17 @@ struct TALER_EXCHANGE_ReservesGetHandle
* @param j JSON response
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh,
const json_t *j)
{
- json_t *history;
- unsigned int len;
- struct TALER_Amount balance;
- struct TALER_Amount balance_from_history;
+ struct TALER_EXCHANGE_ReserveSummary rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK
+ };
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("balance", &balance),
+ TALER_JSON_spec_amount_any ("balance",
+ &rs.details.ok.balance),
GNUNET_JSON_spec_end ()
};
@@ -101,57 +97,9 @@ handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- history = json_object_get (j,
- "history");
- if (NULL == history)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- len = json_array_size (history);
- {
- struct TALER_EXCHANGE_ReserveHistory *rhistory;
-
- rhistory = GNUNET_new_array (len,
- struct TALER_EXCHANGE_ReserveHistory);
- if (GNUNET_OK !=
- TALER_EXCHANGE_parse_reserve_history (rgh->exchange,
- history,
- &rgh->reserve_pub,
- balance.currency,
- &balance_from_history,
- len,
- rhistory))
- {
- GNUNET_break_op (0);
- TALER_EXCHANGE_free_reserve_history (rhistory,
- len);
- return GNUNET_SYSERR;
- }
- if (0 !=
- TALER_amount_cmp (&balance_from_history,
- &balance))
- {
- /* exchange cannot add up balances!? */
- GNUNET_break_op (0);
- TALER_EXCHANGE_free_reserve_history (rhistory,
- len);
- return GNUNET_SYSERR;
- }
- if (NULL != rgh->cb)
- {
- rgh->cb (rgh->cb_cls,
- MHD_HTTP_OK,
- TALER_EC_NONE,
- j,
- &balance,
- len,
- rhistory);
- rgh->cb = NULL;
- }
- TALER_EXCHANGE_free_reserve_history (rhistory,
- len);
- }
+ rgh->cb (rgh->cb_cls,
+ &rs);
+ rgh->cb = NULL;
return GNUNET_OK;
}
@@ -171,119 +119,121 @@ handle_reserves_get_finished (void *cls,
{
struct TALER_EXCHANGE_ReservesGetHandle *rgh = cls;
const json_t *j = response;
- enum TALER_ErrorCode ec;
+ struct TALER_EXCHANGE_ReserveSummary rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
rgh->job = NULL;
switch (response_code)
{
case 0:
- ec = TALER_EC_INVALID_RESPONSE;
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
- ec = TALER_EC_NONE;
if (GNUNET_OK !=
handle_reserves_get_ok (rgh,
j))
{
- response_code = 0;
- ec = TALER_EC_RESERVE_STATUS_REPLY_MALFORMED;
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
}
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
- ec = TALER_JSON_get_error_code (j);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
- ec = TALER_JSON_get_error_code (j);
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u\n",
- (unsigned int) response_code);
- GNUNET_break (0);
- response_code = 0;
+ "Unexpected response code %u/%d for GET %s\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec,
+ rgh->url);
break;
}
if (NULL != rgh->cb)
{
rgh->cb (rgh->cb_cls,
- response_code,
- ec,
- j,
- NULL,
- 0, NULL);
+ &rs);
rgh->cb = NULL;
}
TALER_EXCHANGE_reserves_get_cancel (rgh);
}
-/**
- * Submit a request to obtain the transaction history of a reserve
- * from the exchange. Note that while we return the full response to the
- * caller for further processing, we do already verify that the
- * response is well-formed (i.e. that signatures included in the
- * response are all valid and add up to the balance). If the exchange's
- * reply is not well-formed, we return an HTTP status code of zero to
- * @a cb.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param reserve_pub public key of the reserve to inspect
- * @param cb the callback to call when a reply for this request is available
- * @param cb_cls closure for the above callback
- * @return a handle for this request; NULL if the inputs are invalid (i.e.
- * signatures fail to verify). In this case, the callback is not called.
- */
struct TALER_EXCHANGE_ReservesGetHandle *
-TALER_EXCHANGE_reserves_get (struct TALER_EXCHANGE_Handle *exchange,
- const struct
- TALER_ReservePublicKeyP *reserve_pub,
- TALER_EXCHANGE_ReservesGetCallback cb,
- void *cb_cls)
+TALER_EXCHANGE_reserves_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_EXCHANGE_ReservesGetCallback cb,
+ void *cb_cls)
{
struct TALER_EXCHANGE_ReservesGetHandle *rgh;
- struct GNUNET_CURL_Context *ctx;
CURL *eh;
- char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16];
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16 + 32];
+ unsigned int tms
+ = (unsigned int) timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
{
char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
char *end;
+ char timeout_str[32];
- end = GNUNET_STRINGS_data_to_string (reserve_pub,
- sizeof (struct
- TALER_ReservePublicKeyP),
- pub_str,
- sizeof (pub_str));
+ end = GNUNET_STRINGS_data_to_string (
+ reserve_pub,
+ sizeof (*reserve_pub),
+ pub_str,
+ sizeof (pub_str));
*end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "/reserves/%s",
- pub_str);
+ GNUNET_snprintf (timeout_str,
+ sizeof (timeout_str),
+ "%u",
+ tms);
+ if (0 == tms)
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s",
+ pub_str);
+ else
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s?timeout_ms=%s",
+ pub_str,
+ timeout_str);
}
rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesGetHandle);
- rgh->exchange = exchange;
rgh->cb = cb;
rgh->cb_cls = cb_cls;
rgh->reserve_pub = *reserve_pub;
- rgh->url = TEAH_path_to_url (exchange,
- arg_str);
+ rgh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rgh->url)
+ {
+ GNUNET_free (rgh);
+ return NULL;
+ }
eh = TALER_EXCHANGE_curl_easy_get_ (rgh->url);
if (NULL == eh)
{
@@ -292,25 +242,24 @@ TALER_EXCHANGE_reserves_get (struct TALER_EXCHANGE_Handle *exchange,
GNUNET_free (rgh);
return NULL;
}
- ctx = TEAH_handle_to_context (exchange);
+ if (0 != tms)
+ {
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 100L)));
+ }
rgh->job = GNUNET_CURL_job_add (ctx,
eh,
- GNUNET_NO,
&handle_reserves_get_finished,
rgh);
return rgh;
}
-/**
- * Cancel a reserve status request. This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param rgh the reserve status request handle
- */
void
-TALER_EXCHANGE_reserves_get_cancel (struct
- TALER_EXCHANGE_ReservesGetHandle *rgh)
+TALER_EXCHANGE_reserves_get_cancel (
+ struct TALER_EXCHANGE_ReservesGetHandle *rgh)
{
if (NULL != rgh->job)
{
@@ -322,4 +271,4 @@ TALER_EXCHANGE_reserves_get_cancel (struct
}
-/* end of exchange_api_reserve.c */
+/* end of exchange_api_reserves_get.c */
diff --git a/src/lib/exchange_api_reserves_get_attestable.c b/src/lib/exchange_api_reserves_get_attestable.c
new file mode 100644
index 000000000..f58e0592e
--- /dev/null
+++ b/src/lib/exchange_api_reserves_get_attestable.c
@@ -0,0 +1,276 @@
+/*
+ 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/>
+*/
+/**
+ * @file lib/exchange_api_reserves_get_attestable.c
+ * @brief Implementation of the GET_ATTESTABLE /reserves/$RESERVE_PUB requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves/ GET_ATTESTABLE Handle
+ */
+struct TALER_EXCHANGE_ReservesGetAttestHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReservesGetAttestCallback cb;
+
+ /**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK status code. Handle the JSON
+ * response.
+ *
+ * @param rgah handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_get_attestable_ok (
+ struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReserveGetAttestResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ const json_t *details;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("details",
+ &details),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ unsigned int dlen = json_array_size (details);
+ const char *attributes[GNUNET_NZL (dlen)];
+
+ for (unsigned int i = 0; i<dlen; i++)
+ {
+ json_t *detail = json_array_get (details,
+ i);
+ attributes[i] = json_string_value (detail);
+ if (NULL == attributes[i])
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ rs.details.ok.attributes_length = dlen;
+ rs.details.ok.attributes = attributes;
+ rgah->cb (rgah->cb_cls,
+ &rs);
+ rgah->cb = NULL;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /reserves-attest/$RID request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesGetAttestableHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_get_attestable_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_ReserveGetAttestResult rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ rgah->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_reserves_get_attestable_ok (rgah,
+ j))
+ {
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for reserves get_attestable\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec);
+ break;
+ }
+ if (NULL != rgah->cb)
+ {
+ rgah->cb (rgah->cb_cls,
+ &rs);
+ rgah->cb = NULL;
+ }
+ TALER_EXCHANGE_reserves_get_attestable_cancel (rgah);
+}
+
+
+struct TALER_EXCHANGE_ReservesGetAttestHandle *
+TALER_EXCHANGE_reserves_get_attestable (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ TALER_EXCHANGE_ReservesGetAttestCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+
+ {
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ reserve_pub,
+ sizeof (*reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves-attest/%s",
+ pub_str);
+ }
+ rgah = GNUNET_new (struct TALER_EXCHANGE_ReservesGetAttestHandle);
+ rgah->cb = cb;
+ rgah->cb_cls = cb_cls;
+ rgah->reserve_pub = *reserve_pub;
+ rgah->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rgah->url)
+ {
+ GNUNET_free (rgah);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (rgah->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (rgah->url);
+ GNUNET_free (rgah);
+ return NULL;
+ }
+ rgah->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_reserves_get_attestable_finished,
+ rgah);
+ return rgah;
+}
+
+
+void
+TALER_EXCHANGE_reserves_get_attestable_cancel (
+ struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah)
+{
+ if (NULL != rgah->job)
+ {
+ GNUNET_CURL_job_cancel (rgah->job);
+ rgah->job = NULL;
+ }
+ GNUNET_free (rgah->url);
+ GNUNET_free (rgah);
+}
+
+
+/* end of exchange_api_reserves_get_attestable.c */
diff --git a/src/lib/exchange_api_reserves_history.c b/src/lib/exchange_api_reserves_history.c
new file mode 100644
index 000000000..0654ad837
--- /dev/null
+++ b/src/lib/exchange_api_reserves_history.c
@@ -0,0 +1,1145 @@
+/*
+ This file is part of TALER
+ 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
+ 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 lib/exchange_api_reserves_history.c
+ * @brief Implementation of the POST /reserves/$RESERVE_PUB/history requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP history codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves/$RID/history Handle
+ */
+struct TALER_EXCHANGE_ReservesHistoryHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReservesHistoryCallback cb;
+
+ /**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Where to store the etag (if any).
+ */
+ uint64_t etag;
+
+};
+
+
+/**
+ * Context for history entry helpers.
+ */
+struct HistoryParseContext
+{
+
+ /**
+ * Keys of the exchange we use.
+ */
+ const struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * Our reserve public key.
+ */
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+
+ /**
+ * Array of UUIDs.
+ */
+ struct GNUNET_HashCode *uuids;
+
+ /**
+ * Where to sum up total inbound amounts.
+ */
+ struct TALER_Amount *total_in;
+
+ /**
+ * Where to sum up total outbound amounts.
+ */
+ struct TALER_Amount *total_out;
+
+ /**
+ * Number of entries already used in @e uuids.
+ */
+ unsigned int uuid_off;
+};
+
+
+/**
+ * Type of a function called to parse a reserve history
+ * entry @a rh.
+ *
+ * @param[in,out] rh where to write the result
+ * @param[in,out] uc UUID context for duplicate detection
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+typedef enum GNUNET_GenericReturnValue
+(*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction);
+
+
+/**
+ * Parse "credit" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ const char *wire_uri;
+ uint64_t wire_reference;
+ struct GNUNET_TIME_Timestamp timestamp;
+ struct GNUNET_JSON_Specification withdraw_spec[] = {
+ GNUNET_JSON_spec_uint64 ("wire_reference",
+ &wire_reference),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &timestamp),
+ TALER_JSON_spec_payto_uri ("sender_account_url",
+ &wire_uri),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_CREDIT;
+ if (0 >
+ TALER_amount_add (uc->total_in,
+ uc->total_in,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ withdraw_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rh->details.in_details.sender_url = GNUNET_strdup (wire_uri);
+ rh->details.in_details.wire_reference = wire_reference;
+ rh->details.in_details.timestamp = timestamp;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse "credit" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ struct TALER_ReserveSignatureP sig;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_BlindedCoinHashP bch;
+ struct TALER_Amount withdraw_fee;
+ struct GNUNET_JSON_Specification withdraw_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &sig),
+ TALER_JSON_spec_amount_any ("withdraw_fee",
+ &withdraw_fee),
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_spec_fixed_auto ("h_coin_envelope",
+ &bch),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ withdraw_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* Check that the signature is a valid withdraw request */
+ if (GNUNET_OK !=
+ TALER_wallet_withdraw_verify (&h_denom_pub,
+ &rh->amount,
+ &bch,
+ uc->reserve_pub,
+ &sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ /* check that withdraw fee matches expectations! */
+ {
+ const struct TALER_EXCHANGE_Keys *key_state;
+ const struct TALER_EXCHANGE_DenomPublicKey *dki;
+
+ key_state = uc->keys;
+ dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
+ &h_denom_pub);
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&withdraw_fee,
+ &dki->fees.withdraw)) ||
+ (0 !=
+ TALER_amount_cmp (&withdraw_fee,
+ &dki->fees.withdraw)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ rh->details.withdraw.fee = withdraw_fee;
+ }
+ rh->details.withdraw.out_authorization_sig
+ = json_object_get (transaction,
+ "signature");
+ /* Check check that the same withdraw transaction
+ isn't listed twice by the exchange. We use the
+ "uuid" array to remember the hashes of all
+ signatures, and compare the hashes to find
+ duplicates. */
+ GNUNET_CRYPTO_hash (&sig,
+ sizeof (sig),
+ &uc->uuids[uc->uuid_off]);
+ for (unsigned int i = 0; i<uc->uuid_off; i++)
+ {
+ if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off],
+ &uc->uuids[i]))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ }
+ uc->uuid_off++;
+
+ if (0 >
+ TALER_amount_add (uc->total_out,
+ uc->total_out,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse "recoup" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification recoup_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &rh->details.recoup_details.coin_pub),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rh->details.recoup_details.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rh->details.recoup_details.exchange_pub),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rh->details.recoup_details.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_RECOUP;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ recoup_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ key_state = uc->keys;
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ &rh->details.
+ recoup_details.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_confirm_recoup_verify (
+ rh->details.recoup_details.timestamp,
+ &rh->amount,
+ &rh->details.recoup_details.coin_pub,
+ uc->reserve_pub,
+ &rh->details.recoup_details.exchange_pub,
+ &rh->details.recoup_details.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_add (uc->total_in,
+ uc->total_in,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse "closing" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification closing_spec[] = {
+ TALER_JSON_spec_payto_uri (
+ "receiver_account_details",
+ &rh->details.close_details.receiver_account_details),
+ GNUNET_JSON_spec_fixed_auto ("wtid",
+ &rh->details.close_details.wtid),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rh->details.close_details.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rh->details.close_details.exchange_pub),
+ TALER_JSON_spec_amount_any ("closing_fee",
+ &rh->details.close_details.fee),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rh->details.close_details.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_CLOSING;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ closing_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ key_state = uc->keys;
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (
+ key_state,
+ &rh->details.close_details.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_reserve_closed_verify (
+ rh->details.close_details.timestamp,
+ &rh->amount,
+ &rh->details.close_details.fee,
+ rh->details.close_details.receiver_account_details,
+ &rh->details.close_details.wtid,
+ uc->reserve_pub,
+ &rh->details.close_details.exchange_pub,
+ &rh->details.close_details.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_add (uc->total_out,
+ uc->total_out,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse "merge" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ uint32_t flags32;
+ struct GNUNET_JSON_Specification merge_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &rh->details.merge_details.h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("merge_pub",
+ &rh->details.merge_details.merge_pub),
+ GNUNET_JSON_spec_fixed_auto ("purse_pub",
+ &rh->details.merge_details.purse_pub),
+ GNUNET_JSON_spec_uint32 ("min_age",
+ &rh->details.merge_details.min_age),
+ GNUNET_JSON_spec_uint32 ("flags",
+ &flags32),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rh->details.merge_details.reserve_sig),
+ TALER_JSON_spec_amount_any ("purse_fee",
+ &rh->details.merge_details.purse_fee),
+ GNUNET_JSON_spec_timestamp ("merge_timestamp",
+ &rh->details.merge_details.merge_timestamp),
+ GNUNET_JSON_spec_timestamp ("purse_expiration",
+ &rh->details.merge_details.purse_expiration),
+ GNUNET_JSON_spec_bool ("merged",
+ &rh->details.merge_details.merged),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_MERGE;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ merge_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rh->details.merge_details.flags =
+ (enum TALER_WalletAccountMergeFlags) flags32;
+ if (GNUNET_OK !=
+ TALER_wallet_account_merge_verify (
+ rh->details.merge_details.merge_timestamp,
+ &rh->details.merge_details.purse_pub,
+ rh->details.merge_details.purse_expiration,
+ &rh->details.merge_details.h_contract_terms,
+ &rh->amount,
+ &rh->details.merge_details.purse_fee,
+ rh->details.merge_details.min_age,
+ rh->details.merge_details.flags,
+ uc->reserve_pub,
+ &rh->details.merge_details.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (rh->details.merge_details.merged)
+ {
+ if (0 >
+ TALER_amount_add (uc->total_in,
+ uc->total_in,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ else
+ {
+ if (0 >
+ TALER_amount_add (uc->total_out,
+ uc->total_out,
+ &rh->details.merge_details.purse_fee))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse "open" reserve open entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ struct GNUNET_JSON_Specification open_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rh->details.open_request.reserve_sig),
+ TALER_JSON_spec_amount_any ("open_payment",
+ &rh->details.open_request.reserve_payment),
+ GNUNET_JSON_spec_uint32 ("requested_min_purses",
+ &rh->details.open_request.purse_limit),
+ GNUNET_JSON_spec_timestamp ("request_timestamp",
+ &rh->details.open_request.request_timestamp),
+ GNUNET_JSON_spec_timestamp ("requested_expiration",
+ &rh->details.open_request.reserve_expiration),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_OPEN;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ open_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_open_verify (
+ &rh->amount,
+ rh->details.open_request.request_timestamp,
+ rh->details.open_request.reserve_expiration,
+ rh->details.open_request.purse_limit,
+ uc->reserve_pub,
+ &rh->details.open_request.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_add (uc->total_out,
+ uc->total_out,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse "close" reserve close entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ struct GNUNET_JSON_Specification close_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rh->details.close_request.reserve_sig),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_payto",
+ &rh->details.close_request.
+ target_account_h_payto),
+ NULL),
+ GNUNET_JSON_spec_timestamp ("request_timestamp",
+ &rh->details.close_request.request_timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_CLOSE;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ close_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* force amount to invalid */
+ memset (&rh->amount,
+ 0,
+ sizeof (rh->amount));
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_close_verify (
+ rh->details.close_request.request_timestamp,
+ &rh->details.close_request.target_account_h_payto,
+ uc->reserve_pub,
+ &rh->details.close_request.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+static void
+free_reserve_history (
+ unsigned int len,
+ struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len])
+{
+ for (unsigned int i = 0; i<len; i++)
+ {
+ switch (rhistory[i].type)
+ {
+ case TALER_EXCHANGE_RTT_CREDIT:
+ GNUNET_free (rhistory[i].details.in_details.sender_url);
+ break;
+ case TALER_EXCHANGE_RTT_WITHDRAWAL:
+ break;
+ case TALER_EXCHANGE_RTT_AGEWITHDRAWAL:
+ break;
+ case TALER_EXCHANGE_RTT_RECOUP:
+ break;
+ case TALER_EXCHANGE_RTT_CLOSING:
+ break;
+ case TALER_EXCHANGE_RTT_MERGE:
+ break;
+ case TALER_EXCHANGE_RTT_OPEN:
+ break;
+ case TALER_EXCHANGE_RTT_CLOSE:
+ break;
+ }
+ }
+ GNUNET_free (rhistory);
+}
+
+
+/**
+ * Parse history given in JSON format and return it in binary
+ * format.
+ *
+ * @param keys exchange keys
+ * @param history JSON array with the history
+ * @param reserve_pub public key of the reserve to inspect
+ * @param currency currency we expect the balance to be in
+ * @param[out] total_in set to value of credits to reserve
+ * @param[out] total_out set to value of debits from reserve
+ * @param history_length number of entries in @a history
+ * @param[out] rhistory array of length @a history_length, set to the
+ * parsed history entries
+ * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
+ * were set,
+ * #GNUNET_SYSERR if there was a protocol violation in @a history
+ */
+static enum GNUNET_GenericReturnValue
+parse_reserve_history (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const json_t *history,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *currency,
+ struct TALER_Amount *total_in,
+ struct TALER_Amount *total_out,
+ unsigned int history_length,
+ struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length])
+{
+ const struct
+ {
+ const char *type;
+ ParseHelper helper;
+ } map[] = {
+ { "CREDIT", &parse_credit },
+ { "WITHDRAW", &parse_withdraw },
+ { "RECOUP", &parse_recoup },
+ { "MERGE", &parse_merge },
+ { "CLOSING", &parse_closing },
+ { "OPEN", &parse_open },
+ { "CLOSE", &parse_close },
+ { NULL, NULL }
+ };
+ struct GNUNET_HashCode uuid[history_length];
+ struct HistoryParseContext uc = {
+ .keys = keys,
+ .reserve_pub = reserve_pub,
+ .uuids = uuid,
+ .total_in = total_in,
+ .total_out = total_out
+ };
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (currency,
+ total_in));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (currency,
+ total_out));
+ for (unsigned int off = 0; off<history_length; off++)
+ {
+ struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off];
+ json_t *transaction;
+ struct TALER_Amount amount;
+ const char *type;
+ struct GNUNET_JSON_Specification hist_spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ TALER_JSON_spec_amount_any ("amount",
+ &amount),
+ /* 'wire' and 'signature' are optional depending on 'type'! */
+ GNUNET_JSON_spec_end ()
+ };
+ bool found = false;
+
+ transaction = json_array_get (history,
+ off);
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ hist_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ json_dumpf (transaction,
+ stderr,
+ JSON_INDENT (2));
+ return GNUNET_SYSERR;
+ }
+ rh->amount = amount;
+ if (GNUNET_YES !=
+ TALER_amount_cmp_currency (&amount,
+ total_in))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ for (unsigned int i = 0; NULL != map[i].type; i++)
+ {
+ if (0 == strcasecmp (map[i].type,
+ type))
+ {
+ found = true;
+ if (GNUNET_OK !=
+ map[i].helper (rh,
+ &uc,
+ transaction))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ break;
+ }
+ }
+ if (! found)
+ {
+ /* unexpected 'type', protocol incompatibility, complain! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle HTTP header received by curl.
+ *
+ * @param buffer one line of HTTP header data
+ * @param size size of an item
+ * @param nitems number of items passed
+ * @param userdata our `struct TALER_EXCHANGE_ReservesHistoryHandle *`
+ * @return `size * nitems`
+ */
+static size_t
+handle_header (char *buffer,
+ size_t size,
+ size_t nitems,
+ void *userdata)
+{
+ struct TALER_EXCHANGE_ReservesHistoryHandle *rhh = userdata;
+ size_t total = size * nitems;
+ char *ndup;
+ const char *hdr_type;
+ char *hdr_val;
+ char *sp;
+
+ ndup = GNUNET_strndup (buffer,
+ total);
+ hdr_type = strtok_r (ndup,
+ ":",
+ &sp);
+ if (NULL == hdr_type)
+ {
+ GNUNET_free (ndup);
+ return total;
+ }
+ hdr_val = strtok_r (NULL,
+ "\n\r",
+ &sp);
+ if (NULL == hdr_val)
+ {
+ GNUNET_free (ndup);
+ return total;
+ }
+ if (' ' == *hdr_val)
+ hdr_val++;
+ if (0 == strcasecmp (hdr_type,
+ MHD_HTTP_HEADER_ETAG))
+ {
+ unsigned long long tval;
+ char dummy;
+
+ if (1 !=
+ sscanf (hdr_val,
+ "\"%llu\"%c",
+ &tval,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (ndup);
+ return 0;
+ }
+ rhh->etag = (uint64_t) tval;
+ }
+ GNUNET_free (ndup);
+ return total;
+}
+
+
+/**
+ * We received an #MHD_HTTP_OK history code. Handle the JSON
+ * response.
+ *
+ * @param rsh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh,
+ const json_t *j)
+{
+ const json_t *history;
+ unsigned int len;
+ struct TALER_EXCHANGE_ReserveHistory rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK,
+ .details.ok.etag = rsh->etag
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("balance",
+ &rs.details.ok.balance),
+ GNUNET_JSON_spec_array_const ("history",
+ &history),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ len = json_array_size (history);
+ {
+ struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
+
+ rhistory = GNUNET_new_array (len,
+ struct TALER_EXCHANGE_ReserveHistoryEntry);
+ if (GNUNET_OK !=
+ parse_reserve_history (rsh->keys,
+ history,
+ &rsh->reserve_pub,
+ rs.details.ok.balance.currency,
+ &rs.details.ok.total_in,
+ &rs.details.ok.total_out,
+ len,
+ rhistory))
+ {
+ GNUNET_break_op (0);
+ free_reserve_history (len,
+ rhistory);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ if (NULL != rsh->cb)
+ {
+ rs.details.ok.history = rhistory;
+ rs.details.ok.history_len = len;
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ }
+ free_reserve_history (len,
+ rhistory);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RID/history request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesHistoryHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_history_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReservesHistoryHandle *rsh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_ReserveHistory rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ rsh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_reserves_history_ok (rsh,
+ j))
+ {
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for reserves history\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec);
+ break;
+ }
+ if (NULL != rsh->cb)
+ {
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ }
+ TALER_EXCHANGE_reserves_history_cancel (rsh);
+}
+
+
+struct TALER_EXCHANGE_ReservesHistoryHandle *
+TALER_EXCHANGE_reserves_history (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ uint64_t start_off,
+ TALER_EXCHANGE_ReservesHistoryCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 64];
+ struct curl_slist *job_headers;
+
+ rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle);
+ rsh->cb = cb;
+ rsh->cb_cls = cb_cls;
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &rsh->reserve_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &rsh->reserve_pub,
+ sizeof (rsh->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ if (0 != start_off)
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/history?start=%llu",
+ pub_str,
+ (unsigned long long) start_off);
+ else
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/history",
+ pub_str);
+ }
+ rsh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rsh->url)
+ {
+ GNUNET_free (rsh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (rsh->url);
+ GNUNET_free (rsh);
+ return NULL;
+ }
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERFUNCTION,
+ &handle_header));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERDATA,
+ rsh));
+ {
+ struct TALER_ReserveSignatureP reserve_sig;
+ char *sig_hdr;
+ char *hdr;
+
+ TALER_wallet_reserve_history_sign (start_off,
+ reserve_priv,
+ &reserve_sig);
+
+ sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
+ &reserve_sig,
+ sizeof (reserve_sig));
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
+ sig_hdr);
+ GNUNET_free (sig_hdr);
+ job_headers = curl_slist_append (NULL,
+ hdr);
+ GNUNET_free (hdr);
+ if (NULL == job_headers)
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ return NULL;
+ }
+ }
+
+ rsh->keys = TALER_EXCHANGE_keys_incref (keys);
+ rsh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ job_headers,
+ &handle_reserves_history_finished,
+ rsh);
+ curl_slist_free_all (job_headers);
+ return rsh;
+}
+
+
+void
+TALER_EXCHANGE_reserves_history_cancel (
+ struct TALER_EXCHANGE_ReservesHistoryHandle *rsh)
+{
+ if (NULL != rsh->job)
+ {
+ GNUNET_CURL_job_cancel (rsh->job);
+ rsh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&rsh->post_ctx);
+ GNUNET_free (rsh->url);
+ TALER_EXCHANGE_keys_decref (rsh->keys);
+ GNUNET_free (rsh);
+}
+
+
+/* end of exchange_api_reserves_history.c */
diff --git a/src/lib/exchange_api_reserves_open.c b/src/lib/exchange_api_reserves_open.c
new file mode 100644
index 000000000..36e435685
--- /dev/null
+++ b/src/lib/exchange_api_reserves_open.c
@@ -0,0 +1,567 @@
+/*
+ This file is part of TALER
+ 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
+ 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 lib/exchange_api_reserves_open.c
+ * @brief Implementation of the POST /reserves/$RESERVE_PUB/open requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP open codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Information we keep per coin to validate the reply.
+ */
+struct CoinData
+{
+ /**
+ * Public key of the coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Signature by the coin.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * The hash of the denomination's public key
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * How much did this coin contribute.
+ */
+ struct TALER_Amount contribution;
+};
+
+
+/**
+ * @brief A /reserves/$RID/open Handle
+ */
+struct TALER_EXCHANGE_ReservesOpenHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReservesOpenCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Information we keep per coin to validate the reply.
+ */
+ struct CoinData *coins;
+
+ /**
+ * Length of the @e coins array.
+ */
+ unsigned int num_coins;
+
+ /**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Our signature.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * When did we make the request.
+ */
+ struct GNUNET_TIME_Timestamp ts;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK open code. Handle the JSON
+ * response.
+ *
+ * @param roh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_open_ok (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReserveOpenResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK,
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("open_cost",
+ &rs.details.ok.open_cost),
+ GNUNET_JSON_spec_timestamp ("reserve_expiration",
+ &rs.details.ok.expiration_time),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ roh->cb (roh->cb_cls,
+ &rs);
+ roh->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * We received an #MHD_HTTP_PAYMENT_REQUIRED open code. Handle the JSON
+ * response.
+ *
+ * @param roh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_open_pr (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReserveOpenResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_PAYMENT_REQUIRED,
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("open_cost",
+ &rs.details.payment_required.open_cost),
+ GNUNET_JSON_spec_timestamp ("reserve_expiration",
+ &rs.details.payment_required.expiration_time),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ roh->cb (roh->cb_cls,
+ &rs);
+ roh->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS open code. Handle the JSON
+ * response.
+ *
+ * @param roh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_open_kyc (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReserveOpenResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &rs.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &rs.details.unavailable_for_legal_reasons.requirement_row),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ roh->cb (roh->cb_cls,
+ &rs);
+ roh->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RID/open request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesOpenHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_open_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReservesOpenHandle *roh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_ReserveOpenResult rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ roh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_reserves_open_ok (roh,
+ j))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ if (GNUNET_OK !=
+ handle_reserves_open_pr (roh,
+ j))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ {
+ const struct CoinData *cd = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &rs.details.conflict.coin_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ for (unsigned int i = 0; i<roh->num_coins; i++)
+ {
+ const struct CoinData *cdi = &roh->coins[i];
+
+ if (0 == GNUNET_memcmp (&rs.details.conflict.coin_pub,
+ &cdi->coin_pub))
+ {
+ cd = cdi;
+ break;
+ }
+ }
+ if (NULL == cd)
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ }
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ if (GNUNET_OK !=
+ handle_reserves_open_kyc (roh,
+ j))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for reserves open\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec);
+ break;
+ }
+ if (NULL != roh->cb)
+ {
+ roh->cb (roh->cb_cls,
+ &rs);
+ roh->cb = NULL;
+ }
+ TALER_EXCHANGE_reserves_open_cancel (roh);
+}
+
+
+struct TALER_EXCHANGE_ReservesOpenHandle *
+TALER_EXCHANGE_reserves_open (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_Amount *reserve_contribution,
+ unsigned int coin_payments_length,
+ const struct TALER_EXCHANGE_PurseDeposit coin_payments[
+ static coin_payments_length],
+ struct GNUNET_TIME_Timestamp expiration_time,
+ uint32_t min_purses,
+ TALER_EXCHANGE_ReservesOpenCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ReservesOpenHandle *roh;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+ json_t *cpa;
+
+ roh = GNUNET_new (struct TALER_EXCHANGE_ReservesOpenHandle);
+ roh->cb = cb;
+ roh->cb_cls = cb_cls;
+ roh->ts = GNUNET_TIME_timestamp_get ();
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &roh->reserve_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &roh->reserve_pub,
+ sizeof (roh->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/open",
+ pub_str);
+ }
+ roh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == roh->url)
+ {
+ GNUNET_free (roh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (roh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (roh->url);
+ GNUNET_free (roh);
+ return NULL;
+ }
+ TALER_wallet_reserve_open_sign (reserve_contribution,
+ roh->ts,
+ expiration_time,
+ min_purses,
+ reserve_priv,
+ &roh->reserve_sig);
+ roh->coins = GNUNET_new_array (coin_payments_length,
+ struct CoinData);
+ cpa = json_array ();
+ GNUNET_assert (NULL != cpa);
+ for (unsigned int i = 0; i<coin_payments_length; i++)
+ {
+ const struct TALER_EXCHANGE_PurseDeposit *pd = &coin_payments[i];
+ const struct TALER_AgeCommitmentProof *acp = pd->age_commitment_proof;
+ struct TALER_AgeCommitmentHash ahac;
+ struct TALER_AgeCommitmentHash *achp = NULL;
+ struct CoinData *cd = &roh->coins[i];
+ json_t *cp;
+
+ cd->contribution = pd->amount;
+ cd->h_denom_pub = pd->h_denom_pub;
+ if (NULL != acp)
+ {
+ TALER_age_commitment_hash (&acp->commitment,
+ &ahac);
+ achp = &ahac;
+ }
+ TALER_wallet_reserve_open_deposit_sign (&pd->amount,
+ &roh->reserve_sig,
+ &pd->coin_priv,
+ &cd->coin_sig);
+ GNUNET_CRYPTO_eddsa_key_get_public (&pd->coin_priv.eddsa_priv,
+ &cd->coin_pub.eddsa_pub);
+
+ cp = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ achp)),
+ TALER_JSON_pack_amount ("amount",
+ &pd->amount),
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &pd->h_denom_pub),
+ TALER_JSON_pack_denom_sig ("ub_sig",
+ &pd->denom_sig),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &cd->coin_pub),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &cd->coin_sig));
+ GNUNET_assert (0 ==
+ json_array_append_new (cpa,
+ cp));
+ }
+ {
+ json_t *open_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ roh->ts),
+ GNUNET_JSON_pack_timestamp ("reserve_expiration",
+ expiration_time),
+ GNUNET_JSON_pack_array_steal ("payments",
+ cpa),
+ TALER_JSON_pack_amount ("reserve_payment",
+ reserve_contribution),
+ GNUNET_JSON_pack_uint64 ("purse_limit",
+ min_purses),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &roh->reserve_sig));
+
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&roh->post_ctx,
+ eh,
+ open_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (open_obj);
+ GNUNET_free (roh->coins);
+ GNUNET_free (roh->url);
+ GNUNET_free (roh);
+ return NULL;
+ }
+ json_decref (open_obj);
+ }
+ roh->keys = TALER_EXCHANGE_keys_incref (keys);
+ roh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ roh->post_ctx.headers,
+ &handle_reserves_open_finished,
+ roh);
+ return roh;
+}
+
+
+void
+TALER_EXCHANGE_reserves_open_cancel (
+ struct TALER_EXCHANGE_ReservesOpenHandle *roh)
+{
+ if (NULL != roh->job)
+ {
+ GNUNET_CURL_job_cancel (roh->job);
+ roh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&roh->post_ctx);
+ GNUNET_free (roh->coins);
+ GNUNET_free (roh->url);
+ TALER_EXCHANGE_keys_decref (roh->keys);
+ GNUNET_free (roh);
+}
+
+
+/* end of exchange_api_reserves_open.c */
diff --git a/src/lib/exchange_api_stefan.c b/src/lib/exchange_api_stefan.c
new file mode 100644
index 000000000..226bca82f
--- /dev/null
+++ b/src/lib/exchange_api_stefan.c
@@ -0,0 +1,328 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_stefan.c
+ * @brief calculations on the STEFAN curve
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include <math.h>
+
+
+/**
+ * Determine smallest denomination in @a keys.
+ *
+ * @param keys exchange response to evaluate
+ * @return NULL on error (no denominations)
+ */
+static const struct TALER_Amount *
+get_unit (const struct TALER_EXCHANGE_Keys *keys)
+{
+ const struct TALER_Amount *min = NULL;
+
+ for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dk
+ = &keys->denom_keys[i];
+
+ if ( (NULL == min) ||
+ (1 == TALER_amount_cmp (min,
+ /* > */
+ &dk->value)) )
+ min = &dk->value;
+ }
+ GNUNET_break (NULL != min);
+ return min;
+}
+
+
+/**
+ * Convert amount to double for STEFAN curve evaluation.
+ *
+ * @param a input amount
+ * @return (rounded) amount as a double
+ */
+static double
+amount_to_double (const struct TALER_Amount *a)
+{
+ double d = (double) a->value;
+
+ d += a->fraction / ((double) TALER_AMOUNT_FRAC_BASE);
+ return d;
+}
+
+
+/**
+ * Convert double to amount for STEFAN curve evaluation.
+ *
+ * @param dv input amount
+ * @param currency deisred currency
+ * @param[out] rval (rounded) amount as a double
+ */
+static void
+double_to_amount (double dv,
+ const char *currency,
+ struct TALER_Amount *rval)
+{
+ double rem;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (currency,
+ rval));
+ rval->value = floorl (dv);
+ rem = dv - ((double) rval->value);
+ if (rem < 0.0)
+ rem = 0.0;
+ rem *= TALER_AMOUNT_FRAC_BASE;
+ rval->fraction = floorl (rem);
+ if (rval->fraction >= TALER_AMOUNT_FRAC_BASE)
+ {
+ /* Strange, multiplication overflowed our range,
+ round up value instead */
+ rval->fraction = 0;
+ rval->value += 1;
+ }
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_keys_stefan_b2n (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_Amount *brut,
+ struct TALER_Amount *net)
+{
+ const struct TALER_Amount *min;
+ double log_d = amount_to_double (&keys->stefan_log);
+ double lin_d = keys->stefan_lin;
+ double abs_d = amount_to_double (&keys->stefan_abs);
+ double bru_d = amount_to_double (brut);
+ double min_d;
+ double fee_d;
+ double net_d;
+
+ if (TALER_amount_is_zero (brut))
+ {
+ *net = *brut;
+ return GNUNET_NO;
+ }
+ min = get_unit (keys);
+ if (NULL == min)
+ return GNUNET_SYSERR;
+ if (1.0f <= keys->stefan_lin)
+ {
+ /* This cannot work, linear STEFAN fee estimate always
+ exceed any gross amount. */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ min_d = amount_to_double (min);
+ fee_d = abs_d
+ + log_d * log2 (bru_d / min_d)
+ + lin_d * bru_d;
+ if (fee_d > bru_d)
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (brut->currency,
+ net));
+ return GNUNET_NO;
+ }
+ net_d = bru_d - fee_d;
+ double_to_amount (net_d,
+ brut->currency,
+ net);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Our function
+ * f(x) := ne + ab + lo * log2(x/mi) + li * x - x
+ * for #newton().
+ */
+static double
+eval_f (double mi,
+ double ab,
+ double lo,
+ double li,
+ double ne,
+ double x)
+{
+ return ne + ab + lo * log2 (x / mi) + li * x - x;
+}
+
+
+/**
+ * Our function
+ * f'(x) := lo / log(2) / x + li - 1
+ * for #newton().
+ */
+static double
+eval_fp (double mi,
+ double lo,
+ double li,
+ double ne,
+ double x)
+{
+ return lo / log (2) / x + li - 1;
+}
+
+
+/**
+ * Use Newton's method to find x where f(x)=0.
+ *
+ * @return x where "eval_f(x)==0".
+ */
+static double
+newton (double mi,
+ double ab,
+ double lo,
+ double li,
+ double ne)
+{
+ const double eps = 0.00000001; /* max error allowed */
+ double min_ab = ne + ab; /* result cannot be smaller than this! */
+ /* compute lower bounds by various heuristics */
+ double min_ab_li = min_ab + min_ab * li;
+ double min_ab_li_lo = min_ab_li + log2 (min_ab_li / mi) * lo;
+ double min_ab_lo = min_ab + log2 (min_ab / mi) * lo;
+ double min_ab_lo_li = min_ab_lo + min_ab_lo * li;
+ /* take global lower bound */
+ double x_min = GNUNET_MAX (min_ab_lo_li,
+ min_ab_li_lo);
+ double x = x_min; /* use lower bound as starting point */
+
+ /* Objective: invert
+ ne := br - ab - lo * log2 (br/mi) - li * br
+ to find 'br'.
+ Method: use Newton's method to find root of:
+ f(x) := ne + ab + lo * log2 (x/mi) + li * x - x
+ using also
+ f'(x) := lo / log(2) / x + li - 1
+ */
+ /* Loop to abort in case of divergence;
+ 100 is already very high, 2-4 is normal! */
+ for (unsigned int i = 0; i<100; i++)
+ {
+ double fx = eval_f (mi, ab, lo, li, ne, x);
+ double fxp = eval_fp (mi, lo, li, ne, x);
+ double x_new = x - fx / fxp;
+
+ if (fabs (x - x_new) <= eps)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Needed %u rounds from %f to result BRUT %f => NET: %f\n",
+ i,
+ x_min,
+ x_new,
+ x_new - ab - li * x_new - lo * log2 (x / mi));
+ return x_new;
+ }
+ if (x_new < x_min)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Divergence, obtained very bad estimate %f after %u rounds!\n",
+ x_new,
+ i);
+ return x_min;
+ }
+ x = x_new;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Slow convergence, returning bad estimate %f!\n",
+ x);
+ return x;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_keys_stefan_n2b (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_Amount *net,
+ struct TALER_Amount *brut)
+{
+ const struct TALER_Amount *min;
+ double lin_d = keys->stefan_lin;
+ double log_d = amount_to_double (&keys->stefan_log);
+ double abs_d = amount_to_double (&keys->stefan_abs);
+ double net_d = amount_to_double (net);
+ double min_d;
+ double brut_d;
+
+ if (TALER_amount_is_zero (net))
+ {
+ *brut = *net;
+ return GNUNET_NO;
+ }
+ min = get_unit (keys);
+ if (NULL == min)
+ return GNUNET_SYSERR;
+ if (1.0f <= keys->stefan_lin)
+ {
+ /* This cannot work, linear STEFAN fee estimate always
+ exceed any gross amount. */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ min_d = amount_to_double (min);
+ brut_d = newton (min_d,
+ abs_d,
+ log_d,
+ lin_d,
+ net_d);
+ double_to_amount (brut_d,
+ net->currency,
+ brut);
+ return GNUNET_OK;
+}
+
+
+void
+TALER_EXCHANGE_keys_stefan_round (
+ const struct TALER_EXCHANGE_Keys *keys,
+ struct TALER_Amount *val)
+{
+ const struct TALER_Amount *min;
+ uint32_t mod;
+ uint32_t frac;
+ uint32_t lim;
+
+ if (0 == val->fraction)
+ {
+ /* rounding of non-fractions not supported */
+ return;
+ }
+ min = get_unit (keys);
+ if (NULL == min)
+ return;
+ if (0 == min->fraction)
+ {
+ frac = TALER_AMOUNT_FRAC_BASE;
+ }
+ else
+ {
+ frac = min->fraction;
+ }
+ lim = frac / 2;
+ mod = val->fraction % frac;
+ if (mod < lim)
+ val->fraction -= mod; /* round down */
+ else
+ val->fraction += frac - mod; /* round up */
+}
diff --git a/src/lib/exchange_api_transfers_get.c b/src/lib/exchange_api_transfers_get.c
index d3513fb81..c558fb42e 100644
--- a/src/lib/exchange_api_transfers_get.c
+++ b/src/lib/exchange_api_transfers_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 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
@@ -38,9 +38,9 @@ struct TALER_EXCHANGE_TransfersGetHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -79,30 +79,37 @@ struct TALER_EXCHANGE_TransfersGetHandle
* @return #GNUNET_OK if we are done and all is well,
* #GNUNET_SYSERR if the response was bogus
*/
-static int
+static enum GNUNET_GenericReturnValue
check_transfers_get_response_ok (
struct TALER_EXCHANGE_TransfersGetHandle *wdh,
const json_t *json)
{
- json_t *details_j;
- struct GNUNET_HashCode h_wire;
- struct GNUNET_TIME_Absolute exec_time;
- struct TALER_Amount total_amount;
+ const json_t *details_j;
struct TALER_Amount total_expected;
- struct TALER_Amount wire_fee;
struct TALER_MerchantPublicKeyP merchant_pub;
- unsigned int num_details;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_EXCHANGE_TransfersGetResponse tgr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ struct TALER_EXCHANGE_TransferData *td
+ = &tgr.details.ok.td;
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("total", &total_amount),
- TALER_JSON_spec_amount ("wire_fee", &wire_fee),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub),
- GNUNET_JSON_spec_fixed_auto ("h_wire", &h_wire),
- GNUNET_JSON_spec_absolute_time ("execution_time", &exec_time),
- GNUNET_JSON_spec_json ("deposits", &details_j),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub", &exchange_pub),
+ TALER_JSON_spec_amount_any ("total",
+ &td->total_amount),
+ TALER_JSON_spec_amount_any ("wire_fee",
+ &td->wire_fee),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &merchant_pub),
+ GNUNET_JSON_spec_fixed_auto ("h_payto",
+ &td->h_payto),
+ GNUNET_JSON_spec_timestamp ("execution_time",
+ &td->execution_time),
+ GNUNET_JSON_spec_array_const ("deposits",
+ &details_j),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &td->exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &td->exchange_pub),
GNUNET_JSON_spec_end ()
};
@@ -115,22 +122,30 @@ check_transfers_get_response_ok (
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- TALER_amount_get_zero (total_amount.currency,
+ TALER_amount_set_zero (td->total_amount.currency,
&total_expected))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- num_details = json_array_size (details_j);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (
+ wdh->keys,
+ &td->exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ td->details_length = json_array_size (details_j);
{
- struct TALER_TrackTransferDetails details[num_details];
- unsigned int i;
struct GNUNET_HashContext *hash_context;
- struct TALER_WireDepositDetailP dd;
- struct TALER_WireDepositDataPS wdp;
+ struct TALER_TrackTransferDetails *details;
+ details = GNUNET_new_array (td->details_length,
+ struct TALER_TrackTransferDetails);
+ td->details = details;
hash_context = GNUNET_CRYPTO_hash_context_start ();
- for (i = 0; i<num_details; i++)
+ for (unsigned int i = 0; i<td->details_length; i++)
{
struct TALER_TrackTransferDetails *detail = &details[i];
struct json_t *detail_j = json_array_get (details_j, i);
@@ -138,110 +153,93 @@ check_transfers_get_response_ok (
GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
&detail->h_contract_terms),
GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub),
- TALER_JSON_spec_amount ("deposit_value", &detail->coin_value),
- TALER_JSON_spec_amount ("deposit_fee", &detail->coin_fee),
+ TALER_JSON_spec_amount ("deposit_value",
+ total_expected.currency,
+ &detail->coin_value),
+ TALER_JSON_spec_amount ("deposit_fee",
+ total_expected.currency,
+ &detail->coin_fee),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount ("refund_total",
+ total_expected.currency,
+ &detail->refund_total),
+ NULL),
GNUNET_JSON_spec_end ()
};
- if (GNUNET_OK !=
- GNUNET_JSON_parse (detail_j,
- spec_detail,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- GNUNET_CRYPTO_hash_context_abort (hash_context);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- /* build up big hash for signature checking later */
- dd.h_contract_terms = detail->h_contract_terms;
- dd.execution_time = GNUNET_TIME_absolute_hton (exec_time);
- dd.coin_pub = detail->coin_pub;
- TALER_amount_hton (&dd.deposit_value,
- &detail->coin_value);
- TALER_amount_hton (&dd.deposit_fee,
- &detail->coin_fee);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (td->total_amount.currency,
+ &detail->refund_total));
if ( (GNUNET_OK !=
+ GNUNET_JSON_parse (detail_j,
+ spec_detail,
+ NULL, NULL)) ||
+ (0 >
TALER_amount_add (&total_expected,
&total_expected,
&detail->coin_value)) ||
- (GNUNET_OK !=
+ (0 >
TALER_amount_subtract (&total_expected,
&total_expected,
&detail->coin_fee)) )
{
GNUNET_break_op (0);
GNUNET_CRYPTO_hash_context_abort (hash_context);
- GNUNET_JSON_parse_free (spec);
+ GNUNET_free (details);
return GNUNET_SYSERR;
}
- GNUNET_CRYPTO_hash_context_read (hash_context,
- &dd,
- sizeof (struct
- TALER_WireDepositDetailP));
+ /* build up big hash for signature checking later */
+ TALER_exchange_online_wire_deposit_append (
+ hash_context,
+ &detail->h_contract_terms,
+ td->execution_time,
+ &detail->coin_pub,
+ &detail->coin_value,
+ &detail->coin_fee);
}
/* Check signature */
- wdp.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT);
- wdp.purpose.size = htonl (sizeof (struct TALER_WireDepositDataPS));
- TALER_amount_hton (&wdp.total,
- &total_amount);
- TALER_amount_hton (&wdp.wire_fee,
- &wire_fee);
- wdp.merchant_pub = merchant_pub;
- wdp.h_wire = h_wire;
- GNUNET_CRYPTO_hash_context_finish (hash_context,
- &wdp.h_details);
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (TALER_EXCHANGE_get_keys (
- wdh->exchange),
- &exchange_pub))
{
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify
- (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT,
- &wdp.purpose,
- &exchange_sig.eddsa_signature,
- &exchange_pub.eddsa_pub))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
+ struct GNUNET_HashCode h_details;
+
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &h_details);
+ if (GNUNET_OK !=
+ TALER_exchange_online_wire_deposit_verify (
+ &td->total_amount,
+ &td->wire_fee,
+ &merchant_pub,
+ &td->h_payto,
+ &h_details,
+ &td->exchange_pub,
+ &td->exchange_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (details);
+ return GNUNET_SYSERR;
+ }
}
- if (GNUNET_OK !=
+ if (0 >
TALER_amount_subtract (&total_expected,
&total_expected,
- &wire_fee))
+ &td->wire_fee))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
+ GNUNET_free (details);
return GNUNET_SYSERR;
}
if (0 !=
TALER_amount_cmp (&total_expected,
- &total_amount))
+ &td->total_amount))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
+ GNUNET_free (details);
return GNUNET_SYSERR;
}
wdh->cb (wdh->cb_cls,
- MHD_HTTP_OK,
- TALER_EC_NONE,
- &exchange_pub,
- json,
- &h_wire,
- exec_time,
- &total_amount,
- &wire_fee,
- num_details,
- details);
+ &tgr);
+ GNUNET_free (details);
}
- GNUNET_JSON_parse_free (spec);
- TALER_EXCHANGE_transfers_get_cancel (wdh);
return GNUNET_OK;
}
@@ -261,99 +259,85 @@ handle_transfers_get_finished (void *cls,
{
struct TALER_EXCHANGE_TransfersGetHandle *wdh = cls;
const json_t *j = response;
- enum TALER_ErrorCode ec;
+ struct TALER_EXCHANGE_TransfersGetResponse tgr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
wdh->job = NULL;
switch (response_code)
{
case 0:
- ec = TALER_EC_INVALID_RESPONSE;
+ tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK ==
check_transfers_get_response_ok (wdh,
j))
+ {
+ TALER_EXCHANGE_transfers_get_cancel (wdh);
return;
+ }
GNUNET_break_op (0);
- ec = TALER_EC_TRANSFERS_GET_REPLY_MALFORMED;
- response_code = 0;
+ tgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ tgr.hr.http_status = 0;
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ tgr.hr.ec = TALER_JSON_get_error_code (j);
+ tgr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_FORBIDDEN:
/* Nothing really to verify, exchange says one of the signatures is
invalid; as we checked them, this should never happen, we
should pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ tgr.hr.ec = TALER_JSON_get_error_code (j);
+ tgr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_NOT_FOUND:
/* Exchange does not know about transaction;
we should pass the reply to the application */
- ec = TALER_JSON_get_error_code (j);
+ tgr.hr.ec = TALER_JSON_get_error_code (j);
+ tgr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
- ec = TALER_JSON_get_error_code (j);
+ tgr.hr.ec = TALER_JSON_get_error_code (j);
+ tgr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
- ec = TALER_JSON_get_error_code (j);
+ GNUNET_break_op (0);
+ tgr.hr.ec = TALER_JSON_get_error_code (j);
+ tgr.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u\n",
- (unsigned int) response_code);
- GNUNET_break (0);
- response_code = 0;
+ "Unexpected response code %u/%d for transfers get\n",
+ (unsigned int) response_code,
+ (int) tgr.hr.ec);
break;
}
wdh->cb (wdh->cb_cls,
- response_code,
- ec,
- NULL,
- j,
- NULL,
- GNUNET_TIME_UNIT_ZERO_ABS,
- NULL,
- NULL,
- 0, NULL);
+ &tgr);
TALER_EXCHANGE_transfers_get_cancel (wdh);
}
-/**
- * Query the exchange about which transactions were combined
- * to create a wire transfer.
- *
- * @param exchange exchange to query
- * @param wtid raw wire transfer identifier to get information about
- * @param cb callback to call
- * @param cb_cls closure for @a cb
- * @return handle to cancel operation
- */
struct TALER_EXCHANGE_TransfersGetHandle *
TALER_EXCHANGE_transfers_get (
- struct TALER_EXCHANGE_Handle *exchange,
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
const struct TALER_WireTransferIdentifierRawP *wtid,
TALER_EXCHANGE_TransfersGetCallback cb,
void *cb_cls)
{
struct TALER_EXCHANGE_TransfersGetHandle *wdh;
- struct GNUNET_CURL_Context *ctx;
CURL *eh;
char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32];
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
-
wdh = GNUNET_new (struct TALER_EXCHANGE_TransfersGetHandle);
- wdh->exchange = exchange;
wdh->cb = cb;
wdh->cb_cls = cb_cls;
@@ -369,11 +353,17 @@ TALER_EXCHANGE_transfers_get (
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/transfers/%s",
+ "transfers/%s",
wtid_str);
}
- wdh->url = TEAH_path_to_url (wdh->exchange,
- arg_str);
+ wdh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == wdh->url)
+ {
+ GNUNET_free (wdh);
+ return NULL;
+ }
eh = TALER_EXCHANGE_curl_easy_get_ (wdh->url);
if (NULL == eh)
{
@@ -382,12 +372,11 @@ TALER_EXCHANGE_transfers_get (
GNUNET_free (wdh);
return NULL;
}
- ctx = TEAH_handle_to_context (exchange);
- wdh->job = GNUNET_CURL_job_add (ctx,
- eh,
- GNUNET_YES,
- &handle_transfers_get_finished,
- wdh);
+ wdh->keys = TALER_EXCHANGE_keys_incref (keys);
+ wdh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
+ eh,
+ &handle_transfers_get_finished,
+ wdh);
return wdh;
}
@@ -408,6 +397,7 @@ TALER_EXCHANGE_transfers_get_cancel (
wdh->job = NULL;
}
GNUNET_free (wdh->url);
+ TALER_EXCHANGE_keys_decref (wdh->keys);
GNUNET_free (wdh);
}
diff --git a/src/lib/exchange_api_wire.c b/src/lib/exchange_api_wire.c
deleted file mode 100644
index 3f3998f5b..000000000
--- a/src/lib/exchange_api_wire.c
+++ /dev/null
@@ -1,455 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2018 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 lib/exchange_api_wire.c
- * @brief Implementation of the /wire request of the exchange's HTTP API
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_exchange_service.h"
-#include "taler_json_lib.h"
-#include "taler_signatures.h"
-#include "exchange_api_handle.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A Wire Handle
- */
-struct TALER_EXCHANGE_WireHandle
-{
-
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_WireCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
-};
-
-
-/**
- * List of wire fees by method.
- */
-struct FeeMap
-{
- /**
- * Next entry in list.
- */
- struct FeeMap *next;
-
- /**
- * Wire method this fee structure is for.
- */
- char *method;
-
- /**
- * Array of wire fees, also linked list, but allocated
- * only once.
- */
- struct TALER_EXCHANGE_WireAggregateFees *fee_list;
-};
-
-
-/**
- * Frees @a fm.
- *
- * @param fm memory to release
- */
-static void
-free_fees (struct FeeMap *fm)
-{
- while (NULL != fm)
- {
- struct FeeMap *fe = fm->next;
-
- GNUNET_free (fm->fee_list);
- GNUNET_free (fm->method);
- GNUNET_free (fm);
- fm = fe;
- }
-}
-
-
-/**
- * Parse wire @a fees and return map.
- *
- * @param fees json AggregateTransferFee to parse
- * @return NULL on error
- */
-static struct FeeMap *
-parse_fees (json_t *fees)
-{
- struct FeeMap *fm = NULL;
- const char *key;
- json_t *fee_array;
-
- json_object_foreach (fees, key, fee_array) {
- struct FeeMap *fe = GNUNET_new (struct FeeMap);
- unsigned int len;
- unsigned int idx;
- json_t *fee;
-
- if (0 == (len = json_array_size (fee_array)))
- {
- GNUNET_break_op (0);
- GNUNET_free (fe);
- continue; /* skip */
- }
- fe->method = GNUNET_strdup (key);
- fe->next = fm;
- fe->fee_list = GNUNET_new_array (len,
- struct TALER_EXCHANGE_WireAggregateFees);
- fm = fe;
- json_array_foreach (fee_array, idx, fee)
- {
- struct TALER_EXCHANGE_WireAggregateFees *wa = &fe->fee_list[idx];
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("sig",
- &wa->master_sig),
- TALER_JSON_spec_amount ("wire_fee",
- &wa->wire_fee),
- TALER_JSON_spec_amount ("closing_fee",
- &wa->closing_fee),
- GNUNET_JSON_spec_absolute_time ("start_date",
- &wa->start_date),
- GNUNET_JSON_spec_absolute_time ("end_date",
- &wa->end_date),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (fee,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- free_fees (fm);
- return NULL;
- }
- if (idx + 1 < len)
- wa->next = &fe->fee_list[idx + 1];
- else
- wa->next = NULL;
- }
- }
- return fm;
-}
-
-
-/**
- * Find fee by @a method.
- *
- * @param fm map to look in
- * @param method key to look for
- * @return NULL if fee is not specified in @a fm
- */
-static const struct TALER_EXCHANGE_WireAggregateFees *
-lookup_fee (const struct FeeMap *fm,
- const char *method)
-{
- for (; NULL != fm; fm = fm->next)
- if (0 == strcasecmp (fm->method,
- method))
- return fm->fee_list;
- return NULL;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /wire request.
- *
- * @param cls the `struct TALER_EXCHANGE_WireHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_wire_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_WireHandle *wh = cls;
- enum TALER_ErrorCode ec;
- const json_t *j = response;
-
- TALER_LOG_DEBUG ("Checking raw /wire response\n");
- wh->job = NULL;
- switch (response_code)
- {
- case 0:
- ec = TALER_EC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- json_t *accounts;
- json_t *fees;
- unsigned int num_accounts;
- struct FeeMap *fm;
- const struct TALER_EXCHANGE_Keys *key_state;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("accounts", &accounts),
- GNUNET_JSON_spec_json ("fees", &fees),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_SERVER_JSON_INVALID;
- break;
- }
- if (0 == (num_accounts = json_array_size (accounts)))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- response_code = 0;
- ec = TALER_EC_SERVER_JSON_INVALID;
- break;
- }
- if (NULL == (fm = parse_fees (fees)))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- response_code = 0;
- ec = TALER_EC_SERVER_JSON_INVALID;
- break;
- }
-
- ec = TALER_EC_NONE;
- key_state = TALER_EXCHANGE_get_keys (wh->exchange);
- /* parse accounts */
- {
- struct TALER_EXCHANGE_WireAccount was[num_accounts];
-
- for (unsigned int i = 0; i<num_accounts; i++)
- {
- struct TALER_EXCHANGE_WireAccount *wa = &was[i];
- json_t *account;
- struct GNUNET_JSON_Specification spec_account[] = {
- GNUNET_JSON_spec_string ("payto_uri", &wa->payto_uri),
- GNUNET_JSON_spec_fixed_auto ("master_sig", &wa->master_sig),
- GNUNET_JSON_spec_end ()
- };
- char *method;
-
- account = json_array_get (accounts,
- i);
- if (GNUNET_OK !=
- TALER_JSON_exchange_wire_signature_check (account,
- &key_state->master_pub))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_SERVER_SIGNATURE_INVALID;
- break;
- }
- if (GNUNET_OK !=
- GNUNET_JSON_parse (account,
- spec_account,
- NULL, NULL))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_SERVER_JSON_INVALID;
- break;
- }
- if (NULL == (method = TALER_payto_get_method (wa->payto_uri)))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_SERVER_JSON_INVALID;
- break;
- }
- if (NULL == (wa->fees = lookup_fee (fm,
- method)))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_SERVER_JSON_INVALID;
- GNUNET_free (method);
- break;
- }
- GNUNET_free (method);
- } /* end 'for all accounts */
- if ( (0 != response_code) &&
- (NULL != wh->cb) )
- {
- wh->cb (wh->cb_cls,
- response_code,
- ec,
- num_accounts,
- was,
- j);
- wh->cb = NULL;
- }
- } /* end of 'parse accounts */
- free_fees (fm);
- GNUNET_JSON_parse_free (spec);
- } /* end of MHD_HTTP_OK */
- break;
- case MHD_HTTP_BAD_REQUEST:
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- ec = TALER_JSON_get_error_code (j);
- break;
- default:
- /* unexpected response code */
- ec = TALER_JSON_get_error_code (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) ec);
- GNUNET_break (0);
- response_code = 0;
- break;
- }
- if (NULL != wh->cb)
- wh->cb (wh->cb_cls,
- response_code,
- ec,
- 0,
- NULL,
- j);
- TALER_EXCHANGE_wire_cancel (wh);
-}
-
-
-/**
- * Obtain information about a exchange's wire instructions.
- * A exchange may provide wire instructions for creating
- * a reserve. The wire instructions also indicate
- * which wire formats merchants may use with the exchange.
- * This API is typically used by a wallet for wiring
- * funds, and possibly by a merchant to determine
- * supported wire formats.
- *
- * Note that while we return the (main) response verbatim to the
- * caller for further processing, we do already verify that the
- * response is well-formed (i.e. that signatures included in the
- * response are all valid). If the exchange's reply is not well-formed,
- * we return an HTTP status code of zero to @a cb.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param wire_cb the callback to call when a reply for this request is available
- * @param wire_cb_cls closure for the above callback
- * @return a handle for this request
- */
-struct TALER_EXCHANGE_WireHandle *
-TALER_EXCHANGE_wire (struct TALER_EXCHANGE_Handle *exchange,
- TALER_EXCHANGE_WireCallback wire_cb,
- void *wire_cb_cls)
-{
- struct TALER_EXCHANGE_WireHandle *wh;
- struct GNUNET_CURL_Context *ctx;
- CURL *eh;
-
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
- wh = GNUNET_new (struct TALER_EXCHANGE_WireHandle);
- wh->exchange = exchange;
- wh->cb = wire_cb;
- wh->cb_cls = wire_cb_cls;
- wh->url = TEAH_path_to_url (exchange,
- "/wire");
- eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (wh->url);
- GNUNET_free (wh);
- return NULL;
- }
- ctx = TEAH_handle_to_context (exchange);
- wh->job = GNUNET_CURL_job_add (ctx,
- eh,
- GNUNET_YES,
- &handle_wire_finished,
- wh);
- return wh;
-}
-
-
-/**
- * Cancel a wire information request. This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param wh the wire information request handle
- */
-void
-TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh)
-{
- if (NULL != wh->job)
- {
- GNUNET_CURL_job_cancel (wh->job);
- wh->job = NULL;
- }
- GNUNET_free (wh->url);
- GNUNET_free (wh);
-}
-
-
-/* end of exchange_api_wire.c */
diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c
deleted file mode 100644
index a9fe477eb..000000000
--- a/src/lib/exchange_api_withdraw.c
+++ /dev/null
@@ -1,626 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2020 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 lib/exchange_api_withdraw.c
- * @brief Implementation of the /reserves/$RESERVE_PUB/withdraw requests
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_exchange_service.h"
-#include "taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A Withdraw Handle
- */
-struct TALER_EXCHANGE_WithdrawHandle
-{
-
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Context for #TEH_curl_easy_post(). Keeps the data that must
- * persist for Curl to make the upload.
- */
- struct TALER_CURL_PostContext ctx;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_WithdrawCallback cb;
-
- /**
- * Secrets of the planchet.
- */
- struct TALER_PlanchetSecretsP ps;
-
- /**
- * Denomination key we are withdrawing.
- */
- struct TALER_EXCHANGE_DenomPublicKey pk;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Hash of the public key of the coin we are signing.
- */
- struct GNUNET_HashCode c_hash;
-
- /**
- * Public key of the reserve we are withdrawing from.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
-};
-
-
-/**
- * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation.
- * Extract the coin's signature and return it to the caller. The signature we
- * get from the exchange is for the blinded value. Thus, we first must
- * unblind it and then should verify its validity against our coin's hash.
- *
- * If everything checks out, we return the unblinded signature
- * to the application via the callback.
- *
- * @param wh operation handle
- * @param json reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static int
-reserve_withdraw_ok (struct TALER_EXCHANGE_WithdrawHandle *wh,
- const json_t *json)
-{
- struct GNUNET_CRYPTO_RsaSignature *blind_sig;
- struct TALER_FreshCoin fc;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_rsa_signature ("ev_sig",
- &blind_sig),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_planchet_to_coin (&wh->pk.key,
- blind_sig,
- &wh->ps,
- &wh->c_hash,
- &fc))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- GNUNET_JSON_parse_free (spec);
-
- /* signature is valid, return it to the application */
- wh->cb (wh->cb_cls,
- MHD_HTTP_OK,
- TALER_EC_NONE,
- &fc.sig,
- json);
- /* make sure callback isn't called again after return */
- wh->cb = NULL;
- GNUNET_CRYPTO_rsa_signature_free (fc.sig.rsa_signature);
- return GNUNET_OK;
-}
-
-
-/**
- * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/withdraw operation.
- * Check the signatures on the withdraw transactions in the provided
- * history and that the balances add up. We don't do anything directly
- * with the information, as the JSON will be returned to the application.
- * However, our job is ensuring that the exchange followed the protocol, and
- * this in particular means checking all of the signatures in the history.
- *
- * @param wh operation handle
- * @param json reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static int
-reserve_withdraw_payment_required (
- struct TALER_EXCHANGE_WithdrawHandle *wh,
- const json_t *json)
-{
- struct TALER_Amount balance;
- struct TALER_Amount balance_from_history;
- struct TALER_Amount requested_amount;
- json_t *history;
- size_t len;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("balance", &balance),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- history = json_object_get (json,
- "history");
- if (NULL == history)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- /* go over transaction history and compute
- total incoming and outgoing amounts */
- len = json_array_size (history);
- {
- struct TALER_EXCHANGE_ReserveHistory *rhistory;
-
- /* Use heap allocation as "len" may be very big and thus this may
- not fit on the stack. Use "GNUNET_malloc_large" as a malicious
- exchange may theoretically try to crash us by giving a history
- that does not fit into our memory. */
- rhistory = GNUNET_malloc_large (sizeof (struct
- TALER_EXCHANGE_ReserveHistory)
- * len);
- if (NULL == rhistory)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_parse_reserve_history (wh->exchange,
- history,
- &wh->reserve_pub,
- balance.currency,
- &balance_from_history,
- len,
- rhistory))
- {
- GNUNET_break_op (0);
- TALER_EXCHANGE_free_reserve_history (rhistory,
- len);
- return GNUNET_SYSERR;
- }
- TALER_EXCHANGE_free_reserve_history (rhistory,
- len);
- }
-
- if (0 !=
- TALER_amount_cmp (&balance_from_history,
- &balance))
- {
- /* exchange cannot add up balances!? */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* Compute how much we expected to charge to the reserve */
- if (GNUNET_OK !=
- TALER_amount_add (&requested_amount,
- &wh->pk.value,
- &wh->pk.fee_withdraw))
- {
- /* Overflow here? Very strange, our CPU must be fried... */
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- /* Check that funds were really insufficient */
- if (0 >= TALER_amount_cmp (&requested_amount,
- &balance))
- {
- /* Requested amount is smaller or equal to reported balance,
- so this should not have failed. */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserves/$RESERVE_PUB/withdraw request.
- *
- * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_reserve_withdraw_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
- const json_t *j = response;
- enum TALER_ErrorCode ec;
-
- wh->job = NULL;
- switch (response_code)
- {
- case 0:
- ec = TALER_EC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- reserve_withdraw_ok (wh,
- j))
- {
- GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_WITHDRAW_REPLY_MALFORMED;
- break;
- }
- GNUNET_assert (NULL == wh->cb);
- TALER_EXCHANGE_withdraw_cancel (wh);
- return;
- case MHD_HTTP_BAD_REQUEST:
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
- break;
- case MHD_HTTP_CONFLICT:
- /* The exchange says that the reserve has insufficient funds;
- check the signatures in the history... */
- if (GNUNET_OK !=
- reserve_withdraw_payment_required (wh,
- j))
- {
- GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_WITHDRAW_REPLY_MALFORMED;
- }
- else
- {
- ec = TALER_JSON_get_error_code (j);
- }
- break;
- case MHD_HTTP_FORBIDDEN:
- GNUNET_break (0);
- /* Nothing really to verify, exchange says one of the signatures is
- invalid; as we checked them, this should never happen, we
- should pass the JSON reply to the application */
- ec = TALER_JSON_get_error_code (j);
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, the exchange basically just says
- that it doesn't know this reserve. Can happen if we
- query before the wire transfer went through.
- We should simply pass the JSON reply to the application. */
- ec = TALER_JSON_get_error_code (j);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- ec = TALER_JSON_get_error_code (j);
- break;
- default:
- /* unexpected response code */
- ec = TALER_JSON_get_error_code (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) ec);
- GNUNET_break (0);
- response_code = 0;
- break;
- }
- if (NULL != wh->cb)
- {
- wh->cb (wh->cb_cls,
- response_code,
- ec,
- NULL,
- j);
- wh->cb = NULL;
- }
- TALER_EXCHANGE_withdraw_cancel (wh);
-}
-
-
-/**
- * Helper function for #TALER_EXCHANGE_withdraw2() and
- * #TALER_EXCHANGE_withdraw().
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param pk kind of coin to create
- * @param reserve_sig signature from the reserve authorizing the withdrawal
- * @param reserve_pub public key of the reserve to withdraw from
- * @param ps secrets of the planchet
- * caller must have committed this value to disk before the call (with @a pk)
- * @param pd planchet details matching @a ps
- * @param res_cb the callback to call when the final result for this request is available
- * @param res_cb_cls closure for @a res_cb
- * @return NULL
- * if the inputs are invalid (i.e. denomination key not with this exchange).
- * In this case, the callback is not called.
- */
-struct TALER_EXCHANGE_WithdrawHandle *
-reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct TALER_ReserveSignatureP *reserve_sig,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_PlanchetSecretsP *ps,
- const struct TALER_PlanchetDetail *pd,
- TALER_EXCHANGE_WithdrawCallback res_cb,
- void *res_cb_cls)
-{
- struct TALER_EXCHANGE_WithdrawHandle *wh;
- struct GNUNET_CURL_Context *ctx;
- json_t *withdraw_obj;
- CURL *eh;
- struct GNUNET_HashCode h_denom_pub;
- char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
-
- {
- char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (reserve_pub,
- sizeof (struct
- TALER_ReservePublicKeyP),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "/reserves/%s/withdraw",
- pub_str);
- }
- wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle);
- wh->exchange = exchange;
- wh->cb = res_cb;
- wh->cb_cls = res_cb_cls;
- wh->pk = *pk;
- wh->pk.key.rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_dup (pk->key.rsa_public_key);
- wh->reserve_pub = *reserve_pub;
- wh->c_hash = pd->c_hash;
- GNUNET_CRYPTO_rsa_public_key_hash (pk->key.rsa_public_key,
- &h_denom_pub);
- withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub_hash and coin_ev */
- " s:o}",/* reserve_pub and reserve_sig */
- "denom_pub_hash", GNUNET_JSON_from_data_auto (
- &h_denom_pub),
- "coin_ev", GNUNET_JSON_from_data (pd->coin_ev,
- pd->coin_ev_size),
- "reserve_sig", GNUNET_JSON_from_data_auto (
- reserve_sig));
- if (NULL == withdraw_obj)
- {
- GNUNET_break (0);
- GNUNET_CRYPTO_rsa_public_key_free (wh->pk.key.rsa_public_key);
- GNUNET_free (wh);
- return NULL;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Attempting to withdraw from reserve %s\n",
- TALER_B2S (reserve_pub));
- wh->ps = *ps;
- wh->url = TEAH_path_to_url (exchange,
- arg_str);
- eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&wh->ctx,
- eh,
- withdraw_obj)) )
- {
- GNUNET_break (0);
- if (NULL != eh)
- curl_easy_cleanup (eh);
- json_decref (withdraw_obj);
- GNUNET_free (wh->url);
- GNUNET_CRYPTO_rsa_public_key_free (wh->pk.key.rsa_public_key);
- GNUNET_free (wh);
- return NULL;
- }
- json_decref (withdraw_obj);
- ctx = TEAH_handle_to_context (exchange);
- wh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- wh->ctx.headers,
- &handle_reserve_withdraw_finished,
- wh);
- return wh;
-}
-
-
-/**
- * Withdraw a coin from the exchange using a /reserve/withdraw request. Note
- * that to ensure that no money is lost in case of hardware failures,
- * the caller must have committed (most of) the arguments to disk
- * before calling, and be ready to repeat the request with the same
- * arguments in case of failures.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param pk kind of coin to create
- * @param reserve_priv private key of the reserve to withdraw from
- * @param ps secrets of the planchet
- * caller must have committed this value to disk before the call (with @a pk)
- * @param res_cb the callback to call when the final result for this request is available
- * @param res_cb_cls closure for the above callback
- * @return handle for the operation on success, NULL on error, i.e.
- * if the inputs are invalid (i.e. denomination key not with this exchange).
- * In this case, the callback is not called.
- */
-struct TALER_EXCHANGE_WithdrawHandle *
-TALER_EXCHANGE_withdraw (
- struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_PlanchetSecretsP *ps,
- TALER_EXCHANGE_WithdrawCallback res_cb,
- void *res_cb_cls)
-{
- struct TALER_Amount amount_with_fee;
- struct TALER_ReserveSignatureP reserve_sig;
- struct TALER_WithdrawRequestPS req;
- struct TALER_PlanchetDetail pd;
- struct TALER_EXCHANGE_WithdrawHandle *wh;
-
- GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
- &req.reserve_pub.eddsa_pub);
- req.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS));
- req.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
- if (GNUNET_OK !=
- TALER_amount_add (&amount_with_fee,
- &pk->fee_withdraw,
- &pk->value))
- {
- /* exchange gave us denomination keys that overflow like this!? */
- GNUNET_break_op (0);
- return NULL;
- }
- TALER_amount_hton (&req.amount_with_fee,
- &amount_with_fee);
- TALER_amount_hton (&req.withdraw_fee,
- &pk->fee_withdraw);
- if (GNUNET_OK !=
- TALER_planchet_prepare (&pk->key,
- ps,
- &pd))
- {
- GNUNET_break_op (0);
- return NULL;
- }
- req.h_denomination_pub = pd.denom_pub_hash;
- GNUNET_CRYPTO_hash (pd.coin_ev,
- pd.coin_ev_size,
- &req.h_coin_envelope);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
- &req.purpose,
- &reserve_sig.eddsa_signature));
- wh = reserve_withdraw_internal (exchange,
- pk,
- &reserve_sig,
- &req.reserve_pub,
- ps,
- &pd,
- res_cb,
- res_cb_cls);
- GNUNET_free (pd.coin_ev);
- return wh;
-}
-
-
-/**
- * Withdraw a coin from the exchange using a /reserve/withdraw
- * request. This API is typically used by a wallet to withdraw a tip
- * where the reserve's signature was created by the merchant already.
- *
- * Note that to ensure that no money is lost in case of hardware
- * failures, the caller must have committed (most of) the arguments to
- * disk before calling, and be ready to repeat the request with the
- * same arguments in case of failures.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param pk kind of coin to create
- * @param reserve_sig signature from the reserve authorizing the withdrawal
- * @param reserve_pub public key of the reserve to withdraw from
- * @param ps secrets of the planchet
- * caller must have committed this value to disk before the call (with @a pk)
- * @param res_cb the callback to call when the final result for this request is available
- * @param res_cb_cls closure for @a res_cb
- * @return NULL
- * if the inputs are invalid (i.e. denomination key not with this exchange).
- * In this case, the callback is not called.
- */
-struct TALER_EXCHANGE_WithdrawHandle *
-TALER_EXCHANGE_withdraw2 (
- struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct TALER_ReserveSignatureP *reserve_sig,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_PlanchetSecretsP *ps,
- TALER_EXCHANGE_WithdrawCallback res_cb,
- void *res_cb_cls)
-{
- struct TALER_EXCHANGE_WithdrawHandle *wh;
- struct TALER_PlanchetDetail pd;
-
- if (GNUNET_OK !=
- TALER_planchet_prepare (&pk->key,
- ps,
- &pd))
- {
- GNUNET_break_op (0);
- return NULL;
- }
- wh = reserve_withdraw_internal (exchange,
- pk,
- reserve_sig,
- reserve_pub,
- ps,
- &pd,
- res_cb,
- res_cb_cls);
- GNUNET_free (pd.coin_ev);
- return wh;
-}
-
-
-/**
- * Cancel a withdraw status request. This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param wh the withdraw sign request handle
- */
-void
-TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh)
-{
- if (NULL != wh->job)
- {
- GNUNET_CURL_job_cancel (wh->job);
- wh->job = NULL;
- }
- GNUNET_free (wh->url);
- TALER_curl_easy_post_finished (&wh->ctx);
- GNUNET_CRYPTO_rsa_public_key_free (wh->pk.key.rsa_public_key);
- GNUNET_free (wh);
-}
diff --git a/src/lib/test_stefan.c b/src/lib/test_stefan.c
new file mode 100644
index 000000000..4f7add593
--- /dev/null
+++ b/src/lib/test_stefan.c
@@ -0,0 +1,206 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/test_stefan.c
+ * @brief test calculations on the STEFAN curve
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+
+
+/**
+ * Check if @a a and @a b are numerically close.
+ *
+ * @param a an amount
+ * @param b an amount
+ * @return true if both values are quite close
+ */
+static bool
+amount_close (const struct TALER_Amount *a,
+ const struct TALER_Amount *b)
+{
+ struct TALER_Amount delta;
+
+ switch (TALER_amount_cmp (a,
+ b))
+ {
+ case -1: /* a < b */
+ GNUNET_assert (0 <
+ TALER_amount_subtract (&delta,
+ b,
+ a));
+ break;
+ case 0:
+ /* perfect */
+ return true;
+ case 1: /* a > b */
+ GNUNET_assert (0 <
+ TALER_amount_subtract (&delta,
+ a,
+ b));
+ break;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Rounding error is %s\n",
+ TALER_amount2s (&delta));
+ if (delta.value > 0)
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ if (delta.fraction > 5000)
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ return true; /* let's consider this a rounding error */
+}
+
+
+int
+main (int argc,
+ char **argv)
+{
+ struct TALER_EXCHANGE_DenomPublicKey dk;
+ struct TALER_EXCHANGE_Keys keys = {
+ .denom_keys = &dk,
+ .num_denom_keys = 1
+ };
+ struct TALER_Amount brut;
+ struct TALER_Amount net;
+
+ (void) argc;
+ (void) argv;
+ GNUNET_log_setup ("test-stefan",
+ "INFO",
+ NULL);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:0.00001",
+ &dk.value));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:1",
+ &keys.stefan_abs));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:0.13",
+ &keys.stefan_log));
+ keys.stefan_lin = 1.15;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:4",
+ &brut));
+ GNUNET_log_skip (1,
+ GNUNET_NO);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_EXCHANGE_keys_stefan_b2n (&keys,
+ &brut,
+ &net));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:4",
+ &net));
+ GNUNET_log_skip (1,
+ GNUNET_NO);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_EXCHANGE_keys_stefan_n2b (&keys,
+ &net,
+ &brut));
+ keys.stefan_lin = 1.0;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:4",
+ &brut));
+ GNUNET_log_skip (1,
+ GNUNET_NO);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_EXCHANGE_keys_stefan_b2n (&keys,
+ &brut,
+ &net));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:4",
+ &net));
+ GNUNET_log_skip (1,
+ GNUNET_NO);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_EXCHANGE_keys_stefan_n2b (&keys,
+ &net,
+ &brut));
+ GNUNET_assert (0 == GNUNET_get_log_skip ());
+ keys.stefan_lin = 0.1;
+
+ /* try various values for lin and log STEFAN values */
+ for (unsigned int li = 1; li < 13; li += 1)
+ {
+ keys.stefan_lin = 1.0 * li / 100.0;
+
+ for (unsigned int lx = 1; lx < 100; lx += 1)
+ {
+ keys.stefan_log.fraction = lx * TALER_AMOUNT_FRAC_BASE / 100;
+
+ /* Check brutto-to-netto is stable */
+ for (unsigned int i = 0; i<10; i++)
+ {
+ struct TALER_Amount rval;
+
+ brut.value = i;
+ brut.fraction = i * TALER_AMOUNT_FRAC_BASE / 10;
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_b2n (&keys,
+ &brut,
+ &net));
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_n2b (&keys,
+ &net,
+ &rval));
+ if (TALER_amount_is_zero (&net))
+ GNUNET_assert (TALER_amount_is_zero (&rval));
+ else
+ {
+ GNUNET_assert (amount_close (&brut,
+ &rval));
+ TALER_EXCHANGE_keys_stefan_round (&keys,
+ &rval);
+ GNUNET_assert (amount_close (&brut,
+ &rval));
+ }
+ }
+
+ /* Check netto-to-brutto is stable */
+ for (unsigned int i = 0; i<10; i++)
+ {
+ struct TALER_Amount rval;
+
+ net.value = i;
+ net.fraction = i * TALER_AMOUNT_FRAC_BASE / 10;
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_n2b (&keys,
+ &net,
+ &brut));
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_b2n (&keys,
+ &brut,
+ &rval));
+ GNUNET_assert (amount_close (&net,
+ &rval));
+ TALER_EXCHANGE_keys_stefan_round (&keys,
+ &rval);
+ GNUNET_assert (amount_close (&net,
+ &rval));
+ }
+ }
+ }
+ return 0;
+}
diff --git a/src/mhd/Makefile.am b/src/mhd/Makefile.am
index ec0103f4b..cd3fe7701 100644
--- a/src/mhd/Makefile.am
+++ b/src/mhd/Makefile.am
@@ -13,15 +13,17 @@ libtalermhd_la_SOURCES = \
mhd_config.c \
mhd_legal.c \
mhd_parsing.c \
- mhd_responses.c
+ mhd_responses.c \
+ mhd_run.c
libtalermhd_la_LDFLAGS = \
- -version-info 0:0:0 \
- -export-dynamic -no-undefined
+ -version-info 3:0:3 \
+ -no-undefined
libtalermhd_la_LIBADD = \
- -lgnunetjson \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetjson \
-lgnunetutil \
+ -lmicrohttpd \
-ljansson \
+ -lz \
$(XLIB)
-
diff --git a/src/mhd/mhd_config.c b/src/mhd/mhd_config.c
index d8ae1da75..31ec3e476 100644
--- a/src/mhd/mhd_config.c
+++ b/src/mhd/mhd_config.c
@@ -42,7 +42,7 @@
* @param[out] unix_mode set to the mode to be used for @a unix_path
* @return #GNUNET_OK on success
*/
-int
+enum GNUNET_GenericReturnValue
TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
uint16_t *rport,
@@ -78,7 +78,7 @@ TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg,
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_number (cfg,
section,
- "port",
+ "PORT",
&port))
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
@@ -124,6 +124,7 @@ TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"unixpath `%s' is too long\n",
*unix_path);
+ GNUNET_free (*unix_path);
return GNUNET_SYSERR;
}
@@ -136,6 +137,7 @@ TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg,
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
"UNIXPATH_MODE");
+ GNUNET_free (*unix_path);
return GNUNET_SYSERR;
}
errno = 0;
@@ -147,6 +149,7 @@ TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg,
"UNIXPATH_MODE",
"must be octal number");
GNUNET_free (modestring);
+ GNUNET_free (*unix_path);
return GNUNET_SYSERR;
}
GNUNET_free (modestring);
@@ -252,6 +255,7 @@ TALER_MHD_open_unix_path (const char *unix_path,
GNUNET_free (un);
return -1;
}
+
if (GNUNET_OK !=
GNUNET_NETWORK_socket_bind (nh,
(void *) un,
@@ -319,6 +323,53 @@ TALER_MHD_bind (const struct GNUNET_CONFIGURATION_Handle *cfg,
char *bind_to;
struct GNUNET_NETWORK_Handle *nh;
+ /* try systemd passing first */
+ {
+ const char *listen_pid;
+ const char *listen_fds;
+
+ /* check for systemd-style FD passing */
+ listen_pid = getenv ("LISTEN_PID");
+ listen_fds = getenv ("LISTEN_FDS");
+ if ( (NULL != listen_pid) &&
+ (NULL != listen_fds) &&
+ (getpid () == strtol (listen_pid,
+ NULL,
+ 10)) &&
+ (1 == strtoul (listen_fds,
+ NULL,
+ 10)) )
+ {
+ int fh;
+ int flags;
+
+ fh = 3;
+ flags = fcntl (fh,
+ F_GETFD);
+ if ( (-1 == flags) &&
+ (EBADF == errno) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Bad listen socket passed, ignored\n");
+ fh = -1;
+ }
+ flags |= FD_CLOEXEC;
+ if ( (-1 != fh) &&
+ (0 != fcntl (fh,
+ F_SETFD,
+ flags)) )
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "fcntl");
+ if (-1 != fh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Successfully obtained listen socket from hypervisor\n");
+ return fh;
+ }
+ }
+ }
+
+ /* now try configuration file */
*port = 0;
{
char *serve_unixpath;
@@ -332,8 +383,14 @@ TALER_MHD_bind (const struct GNUNET_CONFIGURATION_Handle *cfg,
&unixpath_mode))
return -1;
if (NULL != serve_unixpath)
- return TALER_MHD_open_unix_path (serve_unixpath,
- unixpath_mode);
+ {
+ int ret;
+
+ ret = TALER_MHD_open_unix_path (serve_unixpath,
+ unixpath_mode);
+ GNUNET_free (serve_unixpath);
+ return ret;
+ }
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
@@ -390,6 +447,18 @@ TALER_MHD_bind (const struct GNUNET_CONFIGURATION_Handle *cfg,
freeaddrinfo (res);
return -1;
}
+ {
+ const int on = 1;
+
+ if (GNUNET_OK !=
+ GNUNET_NETWORK_socket_setsockopt (nh,
+ SOL_SOCKET,
+ SO_REUSEPORT,
+ &on,
+ sizeof(on)))
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "setsockopt");
+ }
if (GNUNET_OK !=
GNUNET_NETWORK_socket_bind (nh,
res->ai_addr,
diff --git a/src/mhd/mhd_legal.c b/src/mhd/mhd_legal.c
index 7b1ba1c11..8353a6901 100644
--- a/src/mhd/mhd_legal.c
+++ b/src/mhd/mhd_legal.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2019 Taler Systems SA
+ Copyright (C) 2019, 2020, 2022, 2024 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
@@ -24,8 +24,14 @@
#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h>
+#include "taler_util.h"
#include "taler_mhd_lib.h"
+/**
+ * How long should browsers/proxies cache the "legal" replies?
+ */
+#define MAX_TERMS_CACHING GNUNET_TIME_UNIT_DAYS
+
/**
* Entry in the terms-of-service array.
@@ -33,12 +39,23 @@
struct Terms
{
/**
+ * Kept in a DLL.
+ */
+ struct Terms *prev;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Terms *next;
+
+ /**
* Mime type of the terms.
*/
const char *mime_type;
/**
- * The terms (NOT 0-terminated!).
+ * The terms (NOT 0-terminated!), mmap()'ed. Do not free,
+ * use munmap() instead.
*/
void *terms;
@@ -48,9 +65,27 @@ struct Terms
char *language;
/**
+ * deflated @e terms, to return if client supports deflate compression.
+ * malloc()'ed. NULL if @e terms does not compress.
+ */
+ void *compressed_terms;
+
+ /**
* Number of bytes in @e terms.
*/
size_t terms_size;
+
+ /**
+ * Number of bytes in @e compressed_terms.
+ */
+ size_t compressed_terms_size;
+
+ /**
+ * Sorting key by format preference in case
+ * everything else is equal. Higher is preferred.
+ */
+ unsigned int priority;
+
};
@@ -61,14 +96,14 @@ struct Terms
struct TALER_MHD_Legal
{
/**
- * Array of terms of service, terminated by NULL/0 value.
+ * DLL of terms of service.
*/
- struct Terms *terms;
+ struct Terms *terms_head;
/**
- * Length of the #terms array.
+ * DLL of terms of service.
*/
- unsigned int terms_len;
+ struct Terms *terms_tail;
/**
* Etag to use for the terms of service (= version).
@@ -80,7 +115,8 @@ struct TALER_MHD_Legal
/**
* Check if @a mime matches the @a accept_pattern.
*
- * @param accept_pattern a mime pattern like text/plain or image/STAR
+ * @param accept_pattern a mime pattern like "text/plain"
+ * or "image/STAR"
* @param mime the mime type to match
* @return true if @a mime matches the @a accept_pattern
*/
@@ -90,10 +126,18 @@ mime_matches (const char *accept_pattern,
{
const char *da = strchr (accept_pattern, '/');
const char *dm = strchr (mime, '/');
+ const char *end;
if ( (NULL == da) ||
(NULL == dm) )
return (0 == strcmp ("*", accept_pattern));
+ // FIXME: use TALER_MHD_check_accept() here!
+ /* FIXME: eventually, we might want to parse the "q=$FLOAT"
+ part after the ';' and figure out which one is the
+ best/preferred match instead of returning a boolean... */
+ end = strchr (da, ';');
+ if (NULL == end)
+ end = &da[strlen (da)];
return
( ( (1 == da - accept_pattern) &&
('*' == *accept_pattern) ) ||
@@ -102,66 +146,60 @@ mime_matches (const char *accept_pattern,
mime,
da - accept_pattern)) ) ) &&
( (0 == strcmp (da, "/*")) ||
- (0 == strcasecmp (da,
- dm)) );
+ (0 == strncasecmp (da,
+ dm,
+ end - da)) );
}
-/**
- * Check if @a lang matches the @a language_pattern, and if so with
- * which preference.
- *
- * @param language_pattern a language preferences string
- * like "fr-CH, fr;q=0.9, en;q=0.8, *;q=0.1"
- * @param lang the 2-digit language to match
- * @return q-weight given for @a lang in @a language_pattern, 1.0 if no weights are given;
- * 0 if @a lang is not in @a language_pattern
- */
-static double
-language_matches (const char *language_pattern,
- const char *lang)
+bool
+TALER_MHD_xmime_matches (const char *accept_pattern,
+ const char *mime)
{
- char *p = GNUNET_strdup (language_pattern);
+ char *ap = GNUNET_strdup (accept_pattern);
char *sptr;
- double r = 0.0;
- for (char *tok = strtok_r (p, ", ", &sptr);
+ for (const char *tok = strtok_r (ap, ",", &sptr);
NULL != tok;
- tok = strtok_r (NULL, ", ", &sptr))
+ tok = strtok_r (NULL, ",", &sptr))
{
- char *sptr2;
- char *lp = strtok_r (tok, ";", &sptr2);
- char *qp = strtok_r (NULL, ";", &sptr2);
- double q = 1.0;
-
- GNUNET_break_op ( (NULL == qp) ||
- (1 == sscanf (qp,
- "q=%lf",
- &q)) );
- if (0 == strcasecmp (lang,
- lp))
- r = GNUNET_MAX (r, q);
+ if (mime_matches (tok,
+ mime))
+ {
+ GNUNET_free (ap);
+ return true;
+ }
}
- GNUNET_free (p);
- return r;
+ GNUNET_free (ap);
+ return false;
}
-/**
- * Generate a response with a legal document in the format and language of the
- * user's choosing.
- *
- * @param conn HTTP connection to handle
- * @param legal legal document to serve
- * @return MHD result code
- */
-int
+MHD_RESULT
TALER_MHD_reply_legal (struct MHD_Connection *conn,
struct TALER_MHD_Legal *legal)
{
struct MHD_Response *resp;
struct Terms *t;
-
+ struct GNUNET_TIME_Absolute a;
+ struct GNUNET_TIME_Timestamp m;
+ char dat[128];
+ char *langs;
+
+ a = GNUNET_TIME_relative_to_absolute (MAX_TERMS_CACHING);
+ m = GNUNET_TIME_absolute_to_timestamp (a);
+ /* Round up to next full day to ensure the expiration
+ time does not become a fingerprint! */
+ a = GNUNET_TIME_absolute_round_down (a,
+ MAX_TERMS_CACHING);
+ a = GNUNET_TIME_absolute_add (a,
+ MAX_TERMS_CACHING);
+ TALER_MHD_get_date_string (m.abs_time,
+ dat);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Setting '%s' header to '%s'\n",
+ MHD_HTTP_HEADER_EXPIRES,
+ dat);
if (NULL != legal)
{
const char *etag;
@@ -174,11 +212,21 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn,
(0 == strcasecmp (etag,
legal->terms_etag)) )
{
- int ret;
+ MHD_RESULT ret;
resp = MHD_create_response_from_buffer (0,
NULL,
MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_EXPIRES,
+ dat));
+
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_ETAG,
+ legal->terms_etag));
ret = MHD_queue_response (conn,
MHD_HTTP_NOT_MODIFIED,
resp);
@@ -189,6 +237,7 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn,
}
t = NULL;
+ langs = NULL;
if (NULL != legal)
{
const char *mime;
@@ -198,7 +247,7 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_ACCEPT);
if (NULL == mime)
- mime = "text/html";
+ mime = "text/plain";
lang = MHD_lookup_connection_value (conn,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
@@ -206,21 +255,36 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn,
lang = "en";
/* Find best match: must match mime type (if possible), and if
mime type matches, ideally also language */
- for (unsigned int i = 0; i < legal->terms_len; i++)
+ for (struct Terms *p = legal->terms_head;
+ NULL != p;
+ p = p->next)
{
- struct Terms *p = &legal->terms[i];
-
if ( (NULL == t) ||
- (mime_matches (mime,
- p->mime_type)) )
+ (TALER_MHD_xmime_matches (mime,
+ p->mime_type)) )
{
+ if (NULL == langs)
+ {
+ langs = GNUNET_strdup (p->language);
+ }
+ else if (NULL == strstr (langs,
+ p->language))
+ {
+ char *tmp = langs;
+
+ GNUNET_asprintf (&langs,
+ "%s,%s",
+ tmp,
+ p->language);
+ GNUNET_free (tmp);
+ }
if ( (NULL == t) ||
- (! mime_matches (mime,
- t->mime_type)) ||
- (language_matches (lang,
- p->language) >
- language_matches (lang,
- t->language) ) )
+ (! TALER_MHD_xmime_matches (mime,
+ t->mime_type)) ||
+ (TALER_language_matches (lang,
+ p->language) >
+ TALER_language_matches (lang,
+ t->language) ) )
t = p;
}
}
@@ -250,29 +314,17 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn,
if (MHD_YES ==
TALER_MHD_can_compress (conn))
{
- void *buf = GNUNET_memdup (t->terms,
- t->terms_size);
- size_t buf_size = t->terms_size;
-
- if (TALER_MHD_body_compress (&buf,
- &buf_size))
- {
- resp = MHD_create_response_from_buffer (buf_size,
- buf,
- MHD_RESPMEM_MUST_FREE);
- if (MHD_NO ==
- MHD_add_response_header (resp,
- MHD_HTTP_HEADER_CONTENT_ENCODING,
- "deflate"))
- {
- GNUNET_break (0);
- MHD_destroy_response (resp);
- resp = NULL;
- }
- }
- else
+ resp = MHD_create_response_from_buffer (t->compressed_terms_size,
+ t->compressed_terms,
+ MHD_RESPMEM_PERSISTENT);
+ if (MHD_NO ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CONTENT_ENCODING,
+ "deflate"))
{
- GNUNET_free (buf);
+ GNUNET_break (0);
+ MHD_destroy_response (resp);
+ resp = NULL;
}
}
if (NULL == resp)
@@ -282,6 +334,31 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn,
(void *) t->terms,
MHD_RESPMEM_PERSISTENT);
}
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_EXPIRES,
+ dat));
+ if (NULL != langs)
+ {
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ "Avail-Languages",
+ langs));
+ GNUNET_free (langs);
+ }
+ /* Set cache control headers: our response varies depending on these headers */
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_VARY,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE ","
+ MHD_HTTP_HEADER_ACCEPT ","
+ MHD_HTTP_HEADER_ACCEPT_ENCODING));
+ /* Information is always public, revalidate after 10 days */
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "public,max-age=864000"));
if (NULL != legal)
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,
@@ -291,8 +368,12 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn,
MHD_add_response_header (resp,
MHD_HTTP_HEADER_CONTENT_TYPE,
t->mime_type));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CONTENT_LANGUAGE,
+ t->language));
{
- int ret;
+ MHD_RESULT ret;
ret = MHD_queue_response (conn,
MHD_HTTP_OK,
@@ -322,21 +403,24 @@ load_terms (struct TALER_MHD_Legal *legal,
{
const char *ext;
const char *mime;
+ unsigned int priority;
} mm[] = {
- { .ext = ".html", .mime = "text/html" },
- { .ext = ".htm", .mime = "text/html" },
- { .ext = ".txt", .mime = "text/plain" },
- { .ext = ".pdf", .mime = "application/pdf" },
+ { .ext = ".txt", .mime = "text/plain", .priority = 150 },
+ { .ext = ".html", .mime = "text/html", .priority = 100 },
+ { .ext = ".htm", .mime = "text/html", .priority = 99 },
+ { .ext = ".md", .mime = "text/markdown", .priority = 50 },
+ { .ext = ".pdf", .mime = "application/pdf", .priority = 25 },
{ .ext = ".jpg", .mime = "image/jpeg" },
{ .ext = ".jpeg", .mime = "image/jpeg" },
{ .ext = ".png", .mime = "image/png" },
{ .ext = ".gif", .mime = "image/gif" },
- { .ext = ".epub", .mime = "application/epub+zip" },
- { .ext = ".xml", .mime = "text/xml" },
+ { .ext = ".epub", .mime = "application/epub+zip", .priority = 10 },
+ { .ext = ".xml", .mime = "text/xml", .priority = 10 },
{ .ext = NULL, .mime = NULL }
};
const char *ext = strrchr (name, '.');
const char *mime;
+ unsigned int priority;
if (NULL == ext)
{
@@ -352,7 +436,7 @@ load_terms (struct TALER_MHD_Legal *legal,
name,
ext - name - 1)) )
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Filename `%s' does not match Etag `%s' in directory `%s/%s'. Ignoring it.\n",
name,
legal->terms_etag,
@@ -366,6 +450,7 @@ load_terms (struct TALER_MHD_Legal *legal,
ext))
{
mime = mm[i].mime;
+ priority = mm[i].priority;
break;
}
if (NULL == mime)
@@ -416,50 +501,70 @@ load_terms (struct TALER_MHD_Legal *legal,
GNUNET_free (fn);
return;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Loading legal information from file `%s'\n",
+ fn);
{
- char *buf;
+ void *buf;
size_t bsize;
- ssize_t ret;
bsize = (size_t) st.st_size;
- buf = GNUNET_malloc_large (bsize);
- if (NULL == buf)
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
- "malloc");
- GNUNET_break (0 == close (fd));
- GNUNET_free (fn);
- return;
- }
- ret = read (fd,
- buf,
- bsize);
- if ( (ret < 0) ||
- (bsize != ((size_t) ret)) )
+ buf = mmap (NULL,
+ bsize,
+ PROT_READ,
+ MAP_SHARED,
+ fd,
+ 0);
+ if (MAP_FAILED == buf)
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
- "read",
+ "mmap",
fn);
GNUNET_break (0 == close (fd));
- GNUNET_free (buf);
GNUNET_free (fn);
return;
}
GNUNET_break (0 == close (fd));
GNUNET_free (fn);
- /* append to global list of terms of service */
+ /* insert into global list of terms of service */
{
- struct Terms t = {
- .mime_type = mime,
- .terms = buf,
- .language = GNUNET_strdup (lang),
- .terms_size = bsize
- };
-
- GNUNET_array_append (legal->terms,
- legal->terms_len,
- t);
+ struct Terms *t;
+
+ t = GNUNET_new (struct Terms);
+ t->mime_type = mime;
+ t->terms = buf;
+ t->language = GNUNET_strdup (lang);
+ t->terms_size = bsize;
+ t->priority = priority;
+ buf = GNUNET_memdup (t->terms,
+ t->terms_size);
+ if (TALER_MHD_body_compress (&buf,
+ &bsize))
+ {
+ t->compressed_terms = buf;
+ t->compressed_terms_size = bsize;
+ }
+ else
+ {
+ GNUNET_free (buf);
+ }
+ {
+ struct Terms *prev = NULL;
+
+ for (struct Terms *pos = legal->terms_head;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (pos->priority < priority)
+ break;
+ prev = pos;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (legal->terms_head,
+ legal->terms_tail,
+ prev,
+ t);
+ }
}
}
}
@@ -499,26 +604,16 @@ load_language (struct TALER_MHD_Legal *legal,
if (fn[0] == '.')
continue;
- load_terms (legal, path, lang, fn);
+ load_terms (legal,
+ path,
+ lang,
+ fn);
}
GNUNET_break (0 == closedir (d));
GNUNET_free (dname);
}
-/**
- * Load set of legal documents as specified in @a cfg in section @a section
- * where the Etag is given under the @param tagoption and the directory under
- * the @a diroption.
- *
- * @param cfg configuration to use
- * @param section section to load values from
- * @param diroption name of the option with the
- * path to the legal documents
- * @param tagoption name of the files to use
- * for the legal documents and the Etag
- * @return NULL on error
- */
struct TALER_MHD_Legal *
TALER_MHD_legal_load (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
@@ -575,7 +670,12 @@ TALER_MHD_legal_load (const struct GNUNET_CONFIGURATION_Handle *cfg,
if (lang[0] == '.')
continue;
- load_language (legal, path, lang);
+ if (0 == strcmp (lang,
+ "locale"))
+ continue;
+ load_language (legal,
+ path,
+ lang);
}
GNUNET_break (0 == closedir (d));
GNUNET_free (path);
@@ -583,26 +683,24 @@ TALER_MHD_legal_load (const struct GNUNET_CONFIGURATION_Handle *cfg,
}
-/**
- * Free set of legal documents
- *
- * @param legal legal documents to free
- */
void
TALER_MHD_legal_free (struct TALER_MHD_Legal *legal)
{
+ struct Terms *t;
if (NULL == legal)
return;
- for (unsigned int i = 0; i<legal->terms_len; i++)
+ while (NULL != (t = legal->terms_head))
{
- struct Terms *t = &legal->terms[i];
-
GNUNET_free (t->language);
- GNUNET_free (t->terms);
+ GNUNET_free (t->compressed_terms);
+ if (0 != munmap (t->terms, t->terms_size))
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "munmap");
+ GNUNET_CONTAINER_DLL_remove (legal->terms_head,
+ legal->terms_tail,
+ t);
+ GNUNET_free (t);
}
- GNUNET_array_grow (legal->terms,
- legal->terms_len,
- 0);
GNUNET_free (legal->terms_etag);
GNUNET_free (legal);
}
diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c
index fca54f3ff..771319bd5 100644
--- a/src/mhd/mhd_parsing.c
+++ b/src/mhd/mhd_parsing.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014--2019 Taler Systems SA
+ Copyright (C) 2014--2024 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
@@ -27,36 +27,7 @@
#include "taler_mhd_lib.h"
-/**
- * Maximum POST request size.
- */
-#define REQUEST_BUFFER_MAX (1024 * 1024)
-
-
-/**
- * Process a POST request containing a JSON object. This function
- * realizes an MHD POST processor that will (incrementally) process
- * JSON data uploaded to the HTTP server. It will store the required
- * state in the @a con_cls, which must be cleaned up using
- * #TALER_MHD_parse_post_cleanup_callback().
- *
- * @param connection the MHD connection
- * @param con_cls the closure (points to a `struct Buffer *`)
- * @param upload_data the POST data
- * @param upload_data_size number of bytes in @a upload_data
- * @param json the JSON object for a completed request
- * @return
- * #GNUNET_YES if json object was parsed or at least
- * may be parsed in the future (call again);
- * `*json` will be NULL if we need to be called again,
- * and non-NULL if we are done.
- * #GNUNET_NO is request incomplete or invalid
- * (error message was generated)
- * #GNUNET_SYSERR on internal error
- * (we could not even queue an error message,
- * close HTTP session with MHD_NO)
- */
-int
+enum GNUNET_GenericReturnValue
TALER_MHD_parse_post_json (struct MHD_Connection *connection,
void **con_cls,
const char *upload_data,
@@ -65,7 +36,7 @@ TALER_MHD_parse_post_json (struct MHD_Connection *connection,
{
enum GNUNET_JSON_PostResult pr;
- pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
+ pr = GNUNET_JSON_post_parser (TALER_MHD_REQUEST_BUFFER_MAX,
connection,
con_cls,
upload_data,
@@ -76,27 +47,27 @@ TALER_MHD_parse_post_json (struct MHD_Connection *connection,
case GNUNET_JSON_PR_OUT_OF_MEMORY:
GNUNET_break (NULL == *json);
return (MHD_NO ==
- TALER_MHD_reply_with_error
- (connection,
+ TALER_MHD_reply_with_error (
+ connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PARSER_OUT_OF_MEMORY,
- "out of memory")) ? GNUNET_SYSERR : GNUNET_NO;
+ TALER_EC_GENERIC_PARSER_OUT_OF_MEMORY,
+ NULL)) ? GNUNET_SYSERR : GNUNET_NO;
case GNUNET_JSON_PR_CONTINUE:
GNUNET_break (NULL == *json);
return GNUNET_YES;
case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
GNUNET_break (NULL == *json);
- return (MHD_NO ==
- TALER_MHD_reply_request_too_large
- (connection)) ? GNUNET_SYSERR : GNUNET_NO;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Closing connection, upload too large\n");
+ return GNUNET_SYSERR;
case GNUNET_JSON_PR_JSON_INVALID:
GNUNET_break (NULL == *json);
return (MHD_YES ==
TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_JSON_INVALID,
- "invalid JSON uploaded"))
+ TALER_EC_GENERIC_JSON_INVALID,
+ NULL))
? GNUNET_NO : GNUNET_SYSERR;
case GNUNET_JSON_PR_SUCCESS:
GNUNET_break (NULL != *json);
@@ -108,13 +79,6 @@ TALER_MHD_parse_post_json (struct MHD_Connection *connection,
}
-/**
- * Function called whenever we are done with a request
- * to clean up our state.
- *
- * @param con_cls value as it was left by
- * #TALER_MHD_parse_post_json(), to be cleaned up
- */
void
TALER_MHD_parse_post_cleanup_callback (void *con_cls)
{
@@ -123,39 +87,39 @@ TALER_MHD_parse_post_cleanup_callback (void *con_cls)
/**
- * Extract base32crockford encoded data from request.
+ * Extract fixed-size base32crockford encoded data from request.
*
- * Queues an error response to the connection if the parameter is
- * missing or invalid.
+ * Queues an error response to the connection if the parameter is missing or
+ * invalid.
*
* @param connection the MHD connection
- * @param param_name the name of the parameter with the key
+ * @param param_name the name of the HTTP key with the value
+ * @param kind whether to extract from header, argument or footer
* @param[out] out_data pointer to store the result
- * @param out_size expected size of data
+ * @param out_size expected size of @a out_data
+ * @param[out] present set to true if argument was found
* @return
* #GNUNET_YES if the the argument is present
* #GNUNET_NO if the argument is absent or malformed
* #GNUNET_SYSERR on internal error (error response could not be sent)
*/
-int
-TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection,
- const char *param_name,
- void *out_data,
- size_t out_size)
+static enum GNUNET_GenericReturnValue
+parse_request_data (struct MHD_Connection *connection,
+ const char *param_name,
+ enum MHD_ValueKind kind,
+ void *out_data,
+ size_t out_size,
+ bool *present)
{
const char *str;
str = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
+ kind,
param_name);
if (NULL == str)
{
- return (MHD_NO ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_PARAMETER_MISSING,
- param_name))
- ? GNUNET_SYSERR : GNUNET_NO;
+ *present = false;
+ return GNUNET_OK;
}
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (str,
@@ -165,33 +129,195 @@ TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection,
return (MHD_NO ==
TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_PARAMETER_MALFORMED,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
param_name))
? GNUNET_SYSERR : GNUNET_NO;
+ *present = true;
return GNUNET_OK;
}
-/**
- * Parse JSON object into components based on the given field
- * specification. Generates error response on parse errors.
- *
- * @param connection the connection to send an error response to
- * @param root the JSON node to start the navigation at.
- * @param[in,out] spec field specification for the parser
- * @return
- * #GNUNET_YES if navigation was successful (caller is responsible
- * for freeing allocated variable-size data using
- * GNUNET_JSON_parse_free() when done)
- * #GNUNET_NO if json is malformed, error response was generated
- * #GNUNET_SYSERR on internal error
- */
-int
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection,
+ const char *param_name,
+ void *out_data,
+ size_t out_size,
+ bool *present)
+{
+ return parse_request_data (connection,
+ param_name,
+ MHD_GET_ARGUMENT_KIND,
+ out_data,
+ out_size,
+ present);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_header_data (struct MHD_Connection *connection,
+ const char *header_name,
+ void *out_data,
+ size_t out_size,
+ bool *present)
+{
+ return parse_request_data (connection,
+ header_name,
+ MHD_HEADER_KIND,
+ out_data,
+ out_size,
+ present);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_timeout (struct MHD_Connection *connection,
+ struct GNUNET_TIME_Absolute *expiration)
+{
+ const char *ts;
+ char dummy;
+ unsigned long long tms;
+
+ ts = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "timeout_ms");
+ if (NULL == ts)
+ {
+ *expiration = GNUNET_TIME_UNIT_ZERO_ABS;
+ return GNUNET_OK;
+ }
+ if (1 !=
+ sscanf (ts,
+ "%llu%c",
+ &tms,
+ &dummy))
+ {
+ MHD_RESULT mret;
+
+ GNUNET_break_op (0);
+ mret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "timeout_ms");
+ return (MHD_YES == mret)
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ *expiration = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+ tms));
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_number (struct MHD_Connection *connection,
+ const char *name,
+ uint64_t *off)
+{
+ const char *ts;
+ char dummy;
+ unsigned long long num;
+
+ ts = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ name);
+ if (NULL == ts)
+ return GNUNET_OK;
+ if (1 !=
+ sscanf (ts,
+ "%llu%c",
+ &num,
+ &dummy))
+ {
+ MHD_RESULT mret;
+
+ GNUNET_break_op (0);
+ mret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ name);
+ return (MHD_YES == mret)
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ *off = (uint64_t) num;
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_snumber (struct MHD_Connection *connection,
+ const char *name,
+ int64_t *val)
+{
+ const char *ts;
+ char dummy;
+ long long num;
+
+ ts = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ name);
+ if (NULL == ts)
+ return GNUNET_OK;
+ if (1 !=
+ sscanf (ts,
+ "%lld%c",
+ &num,
+ &dummy))
+ {
+ MHD_RESULT mret;
+
+ GNUNET_break_op (0);
+ mret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ name);
+ return (MHD_YES == mret)
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ *val = (int64_t) num;
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_amount (struct MHD_Connection *connection,
+ const char *name,
+ struct TALER_Amount *val)
+{
+ const char *ts;
+
+ ts = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ name);
+ if (NULL == ts)
+ return GNUNET_OK;
+ if (GNUNET_OK !=
+ TALER_string_to_amount (ts,
+ val))
+ {
+ MHD_RESULT mret;
+
+ GNUNET_break_op (0);
+ mret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ name);
+ return (MHD_YES == mret)
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
TALER_MHD_parse_json_data (struct MHD_Connection *connection,
const json_t *root,
struct GNUNET_JSON_Specification *spec)
{
- int ret;
+ enum GNUNET_GenericReturnValue ret;
const char *error_json_name;
unsigned int error_line;
@@ -204,15 +330,18 @@ TALER_MHD_parse_json_data (struct MHD_Connection *connection,
if (NULL == error_json_name)
error_json_name = "<no field>";
ret = (MHD_YES ==
- TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_BAD_REQUEST,
- "{s:s, s:I, s:s, s:I}",
- "hint", "JSON parse error",
- "code",
- (json_int_t)
- TALER_EC_JSON_INVALID_WITH_DETAILS,
- "field", error_json_name,
- "line", (json_int_t) error_line))
+ TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ GNUNET_JSON_pack_string ("hint",
+ TALER_ErrorCode_get_hint (
+ TALER_EC_GENERIC_JSON_INVALID)),
+ GNUNET_JSON_pack_uint64 ("code",
+ TALER_EC_GENERIC_JSON_INVALID),
+ GNUNET_JSON_pack_string ("field",
+ error_json_name),
+ GNUNET_JSON_pack_uint64 ("line",
+ error_line)))
? GNUNET_NO : GNUNET_SYSERR;
return ret;
}
@@ -220,28 +349,50 @@ TALER_MHD_parse_json_data (struct MHD_Connection *connection,
}
-/**
- * Parse JSON array into components based on the given field
- * specification. Generates error response on parse errors.
- *
- * @param connection the connection to send an error response to
- * @param root the JSON node to start the navigation at.
- * @param[in,out] spec field specification for the parser
- * @param ... -1-terminated list of array offsets of type 'int'
- * @return
- * #GNUNET_YES if navigation was successful (caller is responsible
- * for freeing allocated variable-size data using
- * GNUNET_JSON_parse_free() when done)
- * #GNUNET_NO if json is malformed, error response was generated
- * #GNUNET_SYSERR on internal error
- */
-int
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_internal_json_data (struct MHD_Connection *connection,
+ const json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ enum GNUNET_GenericReturnValue ret;
+ const char *error_json_name;
+ unsigned int error_line;
+
+ ret = GNUNET_JSON_parse (root,
+ spec,
+ &error_json_name,
+ &error_line);
+ if (GNUNET_SYSERR == ret)
+ {
+ if (NULL == error_json_name)
+ error_json_name = "<no field>";
+ ret = (MHD_YES ==
+ TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ GNUNET_JSON_pack_string ("hint",
+ TALER_ErrorCode_get_hint (
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE)),
+ GNUNET_JSON_pack_uint64 ("code",
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE),
+ GNUNET_JSON_pack_string ("field",
+ error_json_name),
+ GNUNET_JSON_pack_uint64 ("line",
+ error_line)))
+ ? GNUNET_NO : GNUNET_SYSERR;
+ return ret;
+ }
+ return GNUNET_YES;
+}
+
+
+enum GNUNET_GenericReturnValue
TALER_MHD_parse_json_array (struct MHD_Connection *connection,
const json_t *root,
struct GNUNET_JSON_Specification *spec,
...)
{
- int ret;
+ enum GNUNET_GenericReturnValue ret;
const char *error_json_name;
unsigned int error_line;
va_list ap;
@@ -259,14 +410,18 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection,
if (NULL == root)
{
ret = (MHD_YES ==
- TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_BAD_REQUEST,
- "{s:s, s:I, s:I}",
- "hint", "expected array",
- "code",
- (json_int_t)
- TALER_EC_JSON_INVALID_WITH_DETAILS,
- "dimension", dim))
+ TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ GNUNET_JSON_pack_string ("hint",
+ TALER_ErrorCode_get_hint (
+ TALER_EC_GENERIC_JSON_INVALID)),
+ GNUNET_JSON_pack_uint64 ("code",
+ TALER_EC_GENERIC_JSON_INVALID),
+ GNUNET_JSON_pack_string ("detail",
+ "expected array"),
+ GNUNET_JSON_pack_uint64 ("dimension",
+ dim)))
? GNUNET_NO : GNUNET_SYSERR;
return ret;
}
@@ -279,14 +434,18 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection,
if (NULL == error_json_name)
error_json_name = "<no field>";
ret = (MHD_YES ==
- TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_BAD_REQUEST,
- "{s:s, s:I, s:I}",
- "hint", error_json_name,
- "code",
- (json_int_t)
- TALER_EC_JSON_INVALID_WITH_DETAILS,
- "line", (json_int_t) error_line))
+ TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ GNUNET_JSON_pack_string ("detail",
+ error_json_name),
+ GNUNET_JSON_pack_string ("hint",
+ TALER_ErrorCode_get_hint (
+ TALER_EC_GENERIC_JSON_INVALID)),
+ GNUNET_JSON_pack_uint64 ("code",
+ TALER_EC_GENERIC_JSON_INVALID),
+ GNUNET_JSON_pack_uint64 ("line",
+ error_line)))
? GNUNET_NO : GNUNET_SYSERR;
return ret;
}
@@ -294,4 +453,121 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection,
}
+enum GNUNET_GenericReturnValue
+TALER_MHD_check_content_length_ (struct MHD_Connection *connection,
+ unsigned long long max_len)
+{
+ const char *cl;
+ unsigned long long cv;
+ char dummy;
+
+ /* Maybe check for maximum upload size
+ and refuse requests if they are just too big. */
+ cl = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_CONTENT_LENGTH);
+ if (NULL == cl)
+ {
+ return GNUNET_OK;
+#if 0
+ /* wallet currently doesn't always send content-length! */
+ GNUNET_break_op (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ MHD_HTTP_HEADER_CONTENT_LENGTH))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+#endif
+ }
+ if (1 != sscanf (cl,
+ "%llu%c",
+ &cv,
+ &dummy))
+ {
+ /* Not valid HTTP request, just close connection. */
+ GNUNET_break_op (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ MHD_HTTP_HEADER_CONTENT_LENGTH))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (cv > TALER_MHD_REQUEST_BUFFER_MAX)
+ {
+ GNUNET_break_op (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_request_too_large (connection))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+int
+TALER_MHD_check_accept (struct MHD_Connection *connection,
+ const char *header,
+ ...)
+{
+ bool ret = false;
+ const char *accept;
+ char *a;
+ char *saveptr;
+
+ accept = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ header);
+ if (NULL == accept)
+ return -2; /* no Accept header set */
+
+ a = GNUNET_strdup (accept);
+ for (char *t = strtok_r (a, ",", &saveptr);
+ NULL != t;
+ t = strtok_r (NULL, ",", &saveptr))
+ {
+ char *end;
+
+ /* skip leading whitespace */
+ while (isspace ((unsigned char) t[0]))
+ t++;
+ /* trim of ';q=' parameter and everything after space */
+ /* FIXME: eventually, we might want to parse the "q=$FLOAT"
+ part after the ';' and figure out which one is the
+ best/preferred match instead of returning a boolean... */
+ end = strchr (t, ';');
+ if (NULL != end)
+ *end = '\0';
+ end = strchr (t, ' ');
+ if (NULL != end)
+ *end = '\0';
+ {
+ va_list ap;
+ int off = 0;
+ const char *val;
+
+ va_start (ap,
+ header);
+ while (NULL != (val = va_arg (ap,
+ const char *)))
+ {
+ if (0 == strcasecmp (val,
+ t))
+ {
+ ret = off;
+ break;
+ }
+ off++;
+ }
+ va_end (ap);
+ }
+ }
+ GNUNET_free (a);
+ return ret;
+}
+
+
/* end of mhd_parsing.c */
diff --git a/src/mhd/mhd_responses.c b/src/mhd/mhd_responses.c
index 67b9097b8..7dd6824e2 100644
--- a/src/mhd/mhd_responses.c
+++ b/src/mhd/mhd_responses.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
+ 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 Affero General Public License as published by the Free Software
@@ -32,12 +32,6 @@
static enum TALER_MHD_GlobalOptions TM_go;
-/**
- * Set global options for response generation
- * within libtalermhd.
- *
- * @param go global options to use
- */
void
TALER_MHD_setup (enum TALER_MHD_GlobalOptions go)
{
@@ -45,13 +39,6 @@ TALER_MHD_setup (enum TALER_MHD_GlobalOptions go)
}
-/**
- * Add headers we want to return in every response.
- * Useful for testing, like if we want to always close
- * connections.
- *
- * @param response response to modify
- */
void
TALER_MHD_add_global_headers (struct MHD_Response *response)
{
@@ -66,21 +53,15 @@ TALER_MHD_add_global_headers (struct MHD_Response *response)
MHD_add_response_header (response,
MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
"*"));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ /* Not available as MHD constant yet */
+ "Access-Control-Expose-Headers",
+ "*"));
}
-/**
- * Is HTTP body deflate compression supported by the client?
- *
- * @param connection connection to check
- * @return #MHD_YES if 'deflate' compression is allowed
- *
- * Note that right now we're ignoring q-values, which is technically
- * not correct, and also do not support "*" anywhere but in a line by
- * itself. This should eventually be fixed, see also
- * https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
- */
-int
+MHD_RESULT
TALER_MHD_can_compress (struct MHD_Connection *connection)
{
const char *ae;
@@ -111,20 +92,13 @@ TALER_MHD_can_compress (struct MHD_Connection *connection)
}
-/**
- * Try to compress a response body. Updates @a buf and @a buf_size.
- *
- * @param[in,out] buf pointer to body to compress
- * @param[in,out] buf_size pointer to initial size of @a buf
- * @return #MHD_YES if @a buf was compressed
- */
-int
+MHD_RESULT
TALER_MHD_body_compress (void **buf,
size_t *buf_size)
{
Bytef *cbuf;
uLongf cbuf_size;
- int ret;
+ MHD_RESULT ret;
cbuf_size = compressBound (*buf_size);
cbuf = malloc (cbuf_size);
@@ -148,12 +122,6 @@ TALER_MHD_body_compress (void **buf,
}
-/**
- * Make JSON response object.
- *
- * @param json the json object
- * @return MHD response object
- */
struct MHD_Response *
TALER_MHD_make_json (const json_t *json)
{
@@ -185,15 +153,18 @@ TALER_MHD_make_json (const json_t *json)
}
-/**
- * Send JSON object as response.
- *
- * @param connection the MHD connection
- * @param json the json object
- * @param response_code the http response code
- * @return MHD result code
- */
-int
+struct MHD_Response *
+TALER_MHD_make_json_steal (json_t *json)
+{
+ struct MHD_Response *res;
+
+ res = TALER_MHD_make_json (json);
+ json_decref (json);
+ return res;
+}
+
+
+MHD_RESULT
TALER_MHD_reply_json (struct MHD_Connection *connection,
const json_t *json,
unsigned int response_code)
@@ -201,7 +172,7 @@ TALER_MHD_reply_json (struct MHD_Connection *connection,
struct MHD_Response *response;
void *json_str;
size_t json_len;
- int is_compressed;
+ MHD_RESULT is_compressed;
json_str = json_dumps (json,
JSON_INDENT (2));
@@ -253,7 +224,7 @@ TALER_MHD_reply_json (struct MHD_Connection *connection,
}
{
- int ret;
+ MHD_RESULT ret;
ret = MHD_queue_response (connection,
response_code,
@@ -264,14 +235,22 @@ TALER_MHD_reply_json (struct MHD_Connection *connection,
}
-/**
- * Send back a "204 No Content" response with headers
- * for the CORS pre-flight request.
- *
- * @param connection the MHD connection
- * @return MHD result code
- */
-int
+MHD_RESULT
+TALER_MHD_reply_json_steal (struct MHD_Connection *connection,
+ json_t *json,
+ unsigned int response_code)
+{
+ MHD_RESULT ret;
+
+ ret = TALER_MHD_reply_json (connection,
+ json,
+ response_code);
+ json_decref (json);
+ return ret;
+}
+
+
+MHD_RESULT
TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection)
{
struct MHD_Response *response;
@@ -289,9 +268,13 @@ TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection)
/* Not available as MHD constant yet */
"Access-Control-Allow-Headers",
"*"));
-
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ /* Not available as MHD constant yet */
+ "Access-Control-Allow-Methods",
+ "*"));
{
- int ret;
+ MHD_RESULT ret;
ret = MHD_queue_response (connection,
MHD_HTTP_NO_CONTENT,
@@ -302,17 +285,7 @@ TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection)
}
-/**
- * Function to call to handle the request by building a JSON
- * reply from a format string and varargs.
- *
- * @param connection the MHD connection to handle
- * @param response_code HTTP response code to use
- * @param fmt format string for pack
- * @param ... varargs
- * @return MHD result code
- */
-int
+MHD_RESULT
TALER_MHD_reply_json_pack (struct MHD_Connection *connection,
unsigned int response_code,
const char *fmt,
@@ -344,7 +317,7 @@ TALER_MHD_reply_json_pack (struct MHD_Connection *connection,
}
{
- int ret;
+ MHD_RESULT ret;
ret = TALER_MHD_reply_json (connection,
json,
@@ -355,13 +328,6 @@ TALER_MHD_reply_json_pack (struct MHD_Connection *connection,
}
-/**
- * Make JSON response object.
- *
- * @param fmt format string for pack
- * @param ... varargs
- * @return MHD response object
- */
struct MHD_Response *
TALER_MHD_make_json_pack (const char *fmt,
...)
@@ -387,7 +353,7 @@ TALER_MHD_make_json_pack (const char *fmt,
fmt,
jerror.text);
GNUNET_break (0);
- return MHD_NO;
+ return NULL;
}
{
@@ -400,85 +366,65 @@ TALER_MHD_make_json_pack (const char *fmt,
}
-/**
- * Create a response indicating an internal error.
- *
- * @param ec error code to return
- * @param hint hint about the internal error's nature
- * @return a MHD response object
- */
struct MHD_Response *
TALER_MHD_make_error (enum TALER_ErrorCode ec,
- const char *hint)
+ const char *detail)
{
- return TALER_MHD_make_json_pack ("{s:I, s:s}",
- "code", (json_int_t) ec,
- "hint", hint);
+ return TALER_MHD_MAKE_JSON_PACK (
+ TALER_MHD_PACK_EC (ec),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("detail", detail)));
}
-/**
- * Send a response indicating an error.
- *
- * @param connection the MHD connection to use
- * @param ec error code uniquely identifying the error
- * @param http_status HTTP status code to use
- * @param hint human readable hint about the error
- * @return a MHD result code
- */
-int
+MHD_RESULT
TALER_MHD_reply_with_error (struct MHD_Connection *connection,
unsigned int http_status,
enum TALER_ErrorCode ec,
- const char *hint)
+ const char *detail)
{
- return TALER_MHD_reply_json_pack (connection,
- http_status,
- "{s:I, s:s}",
- "code", (json_int_t) ec,
- "hint", hint);
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ http_status,
+ TALER_MHD_PACK_EC (ec),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("detail", detail)));
}
-/**
- * Send a response indicating that the request was too big.
- *
- * @param connection the MHD connection to use
- * @return a MHD result code
- */
-int
-TALER_MHD_reply_request_too_large (struct MHD_Connection *connection)
+MHD_RESULT
+TALER_MHD_reply_with_ec (struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const char *detail)
{
- struct MHD_Response *response;
-
- response = MHD_create_response_from_buffer (0,
- NULL,
- MHD_RESPMEM_PERSISTENT);
- if (NULL == response)
- return MHD_NO;
- TALER_MHD_add_global_headers (response);
+ unsigned int hc = TALER_ErrorCode_get_http_status (ec);
+ if ( (0 == hc) ||
+ (UINT_MAX == hc) )
{
- int ret;
-
- ret = MHD_queue_response (connection,
- MHD_HTTP_REQUEST_ENTITY_TOO_LARGE,
- response);
- MHD_destroy_response (response);
- return ret;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid Taler error code %d provided for response!\n",
+ (int) ec);
+ hc = MHD_HTTP_INTERNAL_SERVER_ERROR;
}
+ return TALER_MHD_reply_with_error (connection,
+ hc,
+ ec,
+ detail);
}
-/**
- * Function to call to handle the request by sending
- * back a redirect to the AGPL source code.
- *
- * @param connection the MHD connection to handle
- * @param url where to redirect for the sources
- * @return MHD result code
- */
-int
+MHD_RESULT
+TALER_MHD_reply_request_too_large (struct MHD_Connection *connection)
+{
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_REQUEST_ENTITY_TOO_LARGE,
+ TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT,
+ NULL);
+}
+
+
+MHD_RESULT
TALER_MHD_reply_agpl (struct MHD_Connection *connection,
const char *url)
{
@@ -510,7 +456,7 @@ TALER_MHD_reply_agpl (struct MHD_Connection *connection,
}
{
- int ret;
+ MHD_RESULT ret;
ret = MHD_queue_response (connection,
MHD_HTTP_FOUND,
@@ -521,18 +467,7 @@ TALER_MHD_reply_agpl (struct MHD_Connection *connection,
}
-/**
- * Function to call to handle the request by sending
- * back static data.
- *
- * @param connection the MHD connection to handle
- * @param http_status status code to return
- * @param mime_type content-type to use
- * @param body response payload
- * @param body_size number of bytes in @a body
- * @return MHD result code
- */
-int
+MHD_RESULT
TALER_MHD_reply_static (struct MHD_Connection *connection,
unsigned int http_status,
const char *mime_type,
@@ -556,7 +491,7 @@ TALER_MHD_reply_static (struct MHD_Connection *connection,
MHD_HTTP_HEADER_CONTENT_TYPE,
mime_type));
{
- int ret;
+ MHD_RESULT ret;
ret = MHD_queue_response (connection,
http_status,
@@ -567,4 +502,49 @@ TALER_MHD_reply_static (struct MHD_Connection *connection,
}
+void
+TALER_MHD_get_date_string (struct GNUNET_TIME_Absolute at,
+ char date[128])
+{
+ static const char *const days[] =
+ { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+ static const char *const mons[] =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
+ "Nov", "Dec"};
+ struct tm now;
+ time_t t;
+#if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \
+ ! defined(HAVE_GMTIME_R)
+ struct tm*pNow;
+#endif
+
+ date[0] = 0;
+ t = (time_t) (at.abs_value_us / 1000LL / 1000LL);
+#if defined(HAVE_C11_GMTIME_S)
+ if (NULL == gmtime_s (&t, &now))
+ return;
+#elif defined(HAVE_W32_GMTIME_S)
+ if (0 != gmtime_s (&now, &t))
+ return;
+#elif defined(HAVE_GMTIME_R)
+ if (NULL == gmtime_r (&t, &now))
+ return;
+#else
+ pNow = gmtime (&t);
+ if (NULL == pNow)
+ return;
+ now = *pNow;
+#endif
+ sprintf (date,
+ "%3s, %02u %3s %04u %02u:%02u:%02u GMT",
+ days[now.tm_wday % 7],
+ (unsigned int) now.tm_mday,
+ mons[now.tm_mon % 12],
+ (unsigned int) (1900 + now.tm_year),
+ (unsigned int) now.tm_hour,
+ (unsigned int) now.tm_min,
+ (unsigned int) now.tm_sec);
+}
+
+
/* end of mhd_responses.c */
diff --git a/src/mhd/mhd_run.c b/src/mhd/mhd_run.c
new file mode 100644
index 000000000..8388fbff6
--- /dev/null
+++ b/src/mhd/mhd_run.c
@@ -0,0 +1,175 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019-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 mhd_run.c
+ * @brief API for running an MHD daemon with the
+ * GNUnet scheduler
+ * @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 "taler_util.h"
+#include "taler_mhd_lib.h"
+
+
+/**
+ * Set to true if we should immediately MHD_run() again.
+ */
+static bool triggered;
+
+/**
+ * Task running the HTTP server.
+ */
+static struct GNUNET_SCHEDULER_Task *mhd_task;
+
+/**
+ * The MHD daemon we are running.
+ */
+static struct MHD_Daemon *mhd;
+
+
+/**
+ * Function that queries MHD's select sets and
+ * starts the task waiting for them.
+ */
+static struct GNUNET_SCHEDULER_Task *
+prepare_daemon (void);
+
+
+/**
+ * Call MHD to process pending requests and then go back
+ * and schedule the next run.
+ *
+ * @param cls NULL
+ */
+static void
+run_daemon (void *cls)
+{
+ (void) cls;
+ mhd_task = NULL;
+ do {
+ triggered = false;
+ GNUNET_assert (MHD_YES ==
+ MHD_run (mhd));
+ } while (triggered);
+ mhd_task = prepare_daemon ();
+}
+
+
+/**
+ * Function that queries MHD's select sets and starts the task waiting for
+ * them.
+ *
+ * @return task handle for the MHD task.
+ */
+static struct GNUNET_SCHEDULER_Task *
+prepare_daemon (void)
+{
+ struct GNUNET_SCHEDULER_Task *ret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ struct GNUNET_NETWORK_FDSet *wrs;
+ struct GNUNET_NETWORK_FDSet *wws;
+ int max;
+ MHD_UNSIGNED_LONG_LONG timeout;
+ int haveto;
+ struct GNUNET_TIME_Relative tv;
+
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ wrs = GNUNET_NETWORK_fdset_create ();
+ wws = GNUNET_NETWORK_fdset_create ();
+ max = -1;
+ GNUNET_assert (MHD_YES ==
+ MHD_get_fdset (mhd,
+ &rs,
+ &ws,
+ &es,
+ &max));
+ haveto = MHD_get_timeout (mhd,
+ &timeout);
+ if (haveto == MHD_YES)
+ tv = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+ timeout);
+ else
+ tv = GNUNET_TIME_UNIT_FOREVER_REL;
+ GNUNET_NETWORK_fdset_copy_native (wrs,
+ &rs,
+ max + 1);
+ GNUNET_NETWORK_fdset_copy_native (wws,
+ &ws,
+ max + 1);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Adding run_daemon select task\n");
+ ret = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
+ tv,
+ wrs,
+ wws,
+ &run_daemon,
+ NULL);
+ GNUNET_NETWORK_fdset_destroy (wrs);
+ GNUNET_NETWORK_fdset_destroy (wws);
+ return ret;
+}
+
+
+void
+TALER_MHD_daemon_start (struct MHD_Daemon *daemon)
+{
+ GNUNET_assert (NULL == mhd);
+ mhd = daemon;
+ mhd_task = prepare_daemon ();
+}
+
+
+struct MHD_Daemon *
+TALER_MHD_daemon_stop (void)
+{
+ struct MHD_Daemon *ret;
+
+ if (NULL != mhd_task)
+ {
+ GNUNET_SCHEDULER_cancel (mhd_task);
+ mhd_task = NULL;
+ }
+ ret = mhd;
+ mhd = NULL;
+ return ret;
+}
+
+
+void
+TALER_MHD_daemon_trigger (void)
+{
+ if (NULL != mhd_task)
+ {
+ GNUNET_SCHEDULER_cancel (mhd_task);
+ mhd_task = GNUNET_SCHEDULER_add_now (&run_daemon,
+ NULL);
+ }
+ else
+ {
+ triggered = true;
+ }
+}
+
+
+/* end of mhd_run.c */
diff --git a/src/pq/Makefile.am b/src/pq/Makefile.am
index 2ade01e0e..4b192d762 100644
--- a/src/pq/Makefile.am
+++ b/src/pq/Makefile.am
@@ -10,25 +10,26 @@ lib_LTLIBRARIES = \
libtalerpq.la
libtalerpq_la_SOURCES = \
+ pq_common.h pq_common.c \
pq_query_helper.c \
pq_result_helper.c
-
libtalerpq_la_LIBADD = \
$(top_builddir)/src/util/libtalerutil.la \
-lgnunetutil -ljansson \
- -lpq $(XLIB)
-
+ -lgnunetpq \
+ -lpq \
+ $(XLIB)
libtalerpq_la_LDFLAGS = \
$(POSTGRESQL_LDFLAGS) \
-version-info 0:0:0 \
- -export-dynamic -no-undefined
-
-TESTS = \
- test_pq
+ -no-undefined
check_PROGRAMS= \
test_pq
+TESTS = \
+ $(check_PROGRAMS)
+
test_pq_SOURCES = \
test_pq.c
test_pq_LDADD = \
@@ -37,4 +38,5 @@ test_pq_LDADD = \
-lgnunetpq \
-lgnunetutil \
-ljansson \
- -lpq $(XLIB)
+ -lpq \
+ $(XLIB)
diff --git a/src/pq/pq_common.c b/src/pq/pq_common.c
new file mode 100644
index 000000000..8b6f8f22c
--- /dev/null
+++ b/src/pq/pq_common.c
@@ -0,0 +1,68 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file pq/pq_common.c
+ * @brief common defines for the pq functions
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "pq_common.h"
+
+struct TALER_PQ_AmountP
+TALER_PQ_make_taler_pq_amount_ (
+ const struct TALER_Amount *amount,
+ uint32_t oid_v,
+ uint32_t oid_f)
+{
+ struct TALER_PQ_AmountP rval = {
+ .cnt = htonl (2),
+ .oid_v = htonl (oid_v),
+ .oid_f = htonl (oid_f),
+ .sz_v = htonl (sizeof((amount)->value)),
+ .sz_f = htonl (sizeof((amount)->fraction)),
+ .v = GNUNET_htonll ((amount)->value),
+ .f = htonl ((amount)->fraction)
+ };
+
+ return rval;
+}
+
+
+size_t
+TALER_PQ_make_taler_pq_amount_currency_ (
+ const struct TALER_Amount *amount,
+ uint32_t oid_v,
+ uint32_t oid_f,
+ uint32_t oid_c,
+ struct TALER_PQ_AmountCurrencyP *rval)
+{
+ size_t clen = strlen (amount->currency);
+
+ GNUNET_assert (clen < TALER_CURRENCY_LEN);
+ rval->cnt = htonl (3);
+ rval->oid_v = htonl (oid_v);
+ rval->oid_f = htonl (oid_f);
+ rval->oid_c = htonl (oid_c);
+ rval->sz_v = htonl (sizeof(amount->value));
+ rval->sz_f = htonl (sizeof(amount->fraction));
+ rval->sz_c = htonl (clen);
+ rval->v = GNUNET_htonll (amount->value);
+ rval->f = htonl (amount->fraction);
+ memcpy (rval->c,
+ amount->currency,
+ clen);
+ return sizeof (*rval) - TALER_CURRENCY_LEN + clen;
+}
diff --git a/src/pq/pq_common.h b/src/pq/pq_common.h
new file mode 100644
index 000000000..3248778a0
--- /dev/null
+++ b/src/pq/pq_common.h
@@ -0,0 +1,148 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file pq/pq_common.h
+ * @brief common defines for the pq functions
+ * @author Özgür Kesim
+ */
+#ifndef TALER_PQ_COMMON_H_
+#define TALER_PQ_COMMON_H_
+
+#include "taler_util.h"
+
+/**
+ * Internal types that are supported as TALER-exchange-specific array types.
+ *
+ * To support a new type,
+ * 1. add a new entry into this list,
+ * 2. for query-support, implement the size calculation and memory copying in
+ * qconv_array() accordingly, in pq_query_helper.c
+ * 3. provide a query-API for arrays of the type, by calling
+ * query_param_array_generic with the appropriate parameters,
+ * in pq_query_helper.c
+ * 4. for result-support, implement memory copying by adding another case
+ * to extract_array_generic, in pq_result_helper.c
+ * 5. provide a result-spec-API for arrays of the type,
+ * in pq_result_helper.c
+ * 6. expose the API's in taler_pq_lib.h
+ */
+enum TALER_PQ_ArrayType
+{
+ TALER_PQ_array_of_blinded_denom_sig,
+ TALER_PQ_array_of_blinded_coin_hash,
+ TALER_PQ_array_of_denom_hash,
+ TALER_PQ_array_of_hash_code,
+
+ /**
+ * Amounts *without* currency.
+ */
+ TALER_PQ_array_of_amount,
+
+ /**
+ * Amounts *with* currency.
+ */
+ TALER_PQ_array_of_amount_currency,
+ TALER_PQ_array_of_MAX, /* must be last */
+};
+
+
+/**
+ * Memory representation of an taler amount record for Postgres.
+ *
+ * All values need to be in network-byte-order.
+ */
+struct TALER_PQ_AmountP
+{
+ uint32_t cnt; /* # elements in the tuple (== 2) */
+ uint32_t oid_v; /* oid of .v */
+ uint32_t sz_v; /* size of .v */
+ uint64_t v; /* value */
+ uint32_t oid_f; /* oid of .f */
+ uint32_t sz_f; /* size of .f */
+ uint32_t f; /* fraction */
+} __attribute__((packed));
+
+
+/**
+ * Memory representation of an taler amount record for Postgres.
+ *
+ * All values need to be in network-byte-order.
+ */
+struct TALER_PQ_AmountNullP
+{
+ uint32_t cnt; /* # elements in the tuple (== 2) */
+ uint32_t oid_v; /* oid of .v */
+ uint32_t sz_v; /* size of .v */
+ uint32_t oid_f; /* oid of .f */
+ uint32_t sz_f; /* size of .f */
+} __attribute__((packed));
+
+
+/**
+ * Memory representation of an taler amount record with currency for Postgres.
+ *
+ * All values need to be in network-byte-order.
+ */
+struct TALER_PQ_AmountCurrencyP
+{
+ uint32_t cnt; /* # elements in the tuple (== 3) */
+ uint32_t oid_v; /* oid of .v */
+ uint32_t sz_v; /* size of .v */
+ uint64_t v; /* value */
+ uint32_t oid_f; /* oid of .f */
+ uint32_t sz_f; /* size of .f */
+ uint32_t f; /* fraction */
+ uint32_t oid_c; /* oid of .c */
+ uint32_t sz_c; /* size of .c */
+ uint8_t c[TALER_CURRENCY_LEN]; /* currency */
+} __attribute__((packed));
+
+
+/**
+ * Create a `struct TALER_PQ_AmountP` for initialization
+ *
+ * @param amount amount of type `struct TALER_Amount *`
+ * @param oid_v OID of the INT8 type in postgres
+ * @param oid_f OID of the INT4 type in postgres
+ */
+struct TALER_PQ_AmountP
+TALER_PQ_make_taler_pq_amount_ (
+ const struct TALER_Amount *amount,
+ uint32_t oid_v,
+ uint32_t oid_f);
+
+
+/**
+ * Create a `struct TALER_PQ_AmountCurrencyP` for initialization
+ *
+ * @param amount amount of type `struct TALER_Amount *`
+ * @param oid_v OID of the INT8 type in postgres
+ * @param oid_f OID of the INT4 type in postgres
+ * @param oid_c OID of the TEXT type in postgres
+ * @param[out] rval set to encoded @a amount
+ * @return actual (useful) size of @a rval for Postgres
+ */
+size_t
+TALER_PQ_make_taler_pq_amount_currency_ (
+ const struct TALER_Amount *amount,
+ uint32_t oid_v,
+ uint32_t oid_f,
+ uint32_t oid_c,
+ struct TALER_PQ_AmountCurrencyP *rval);
+
+
+#endif /* TALER_PQ_COMMON_H_ */
+/* end of pg/pq_common.h */
diff --git a/src/pq/pq_query_helper.c b/src/pq/pq_query_helper.c
index f558cbccf..b1dfd4cf1 100644
--- a/src/pq/pq_query_helper.c
+++ b/src/pq/pq_query_helper.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014, 2015, 2016 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
@@ -21,16 +21,18 @@
* @author Christian Grothoff
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_pq_lib.h>
#include "taler_pq_lib.h"
+#include "pq_common.h"
/**
- * Function called to convert input argument into SQL parameters.
+ * Function called to convert input amount into SQL parameter as tuple.
*
* @param cls closure
- * @param data pointer to input argument, here a `struct TALER_AmountNBO`
+ * @param data pointer to input argument, here a `struct TALER_Amount`
* @param data_len number of bytes in @a data (if applicable)
* @param[out] param_values SQL data to set
* @param[out] param_lengths SQL length data to set
@@ -41,55 +43,84 @@
* @return -1 on error, number of offsets used in @a scratch otherwise
*/
static int
-qconv_amount_nbo (void *cls,
- const void *data,
- size_t data_len,
- void *param_values[],
- int param_lengths[],
- int param_formats[],
- unsigned int param_length,
- void *scratch[],
- unsigned int scratch_length)
+qconv_amount_currency_tuple (void *cls,
+ const void *data,
+ size_t data_len,
+ void *param_values[],
+ int param_lengths[],
+ int param_formats[],
+ unsigned int param_length,
+ void *scratch[],
+ unsigned int scratch_length)
{
- const struct TALER_AmountNBO *amount = data;
- unsigned int off = 0;
+ struct GNUNET_PQ_Context *db = cls;
+ const struct TALER_Amount *amount = data;
+ size_t sz;
- (void) cls;
- (void) scratch;
- (void) scratch_length;
- GNUNET_assert (sizeof (struct TALER_AmountNBO) == data_len);
- GNUNET_assert (2 == param_length);
- param_values[off] = (void *) &amount->value;
- param_lengths[off] = sizeof (amount->value);
- param_formats[off] = 1;
- off++;
- param_values[off] = (void *) &amount->fraction;
- param_lengths[off] = sizeof (amount->fraction);
- param_formats[off] = 1;
- return 0;
+ GNUNET_assert (NULL != db);
+ GNUNET_assert (NULL != amount);
+ GNUNET_assert (1 == param_length);
+ GNUNET_assert (1 <= scratch_length);
+ GNUNET_assert (sizeof (struct TALER_Amount) == data_len);
+ GNUNET_static_assert (sizeof(uint32_t) == sizeof(Oid));
+ {
+ char *out;
+ Oid oid_v;
+ Oid oid_f;
+ Oid oid_c;
+ struct TALER_PQ_AmountCurrencyP d;
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "int8",
+ &oid_v));
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "int4",
+ &oid_f));
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "varchar",
+ &oid_c));
+ sz = TALER_PQ_make_taler_pq_amount_currency_ (amount,
+ oid_v,
+ oid_f,
+ oid_c,
+ &d);
+ out = GNUNET_malloc (sz);
+ memcpy (out,
+ &d,
+ sz);
+ scratch[0] = out;
+ }
+
+ param_values[0] = scratch[0];
+ param_lengths[0] = sz;
+ param_formats[0] = 1;
+
+ return 1;
}
-/**
- * Generate query parameter for a currency, consisting of the three
- * components "value", "fraction" and "currency" in this order. The
- * types must be a 64-bit integer, 32-bit integer and a
- * #TALER_CURRENCY_LEN-sized BLOB/VARCHAR respectively.
- *
- * @param x pointer to the query parameter to pass
- * @return array entry for the query parameters to use
- */
struct GNUNET_PQ_QueryParam
-TALER_PQ_query_param_amount_nbo (const struct TALER_AmountNBO *x)
+TALER_PQ_query_param_amount_with_currency (
+ const struct GNUNET_PQ_Context *db,
+ const struct TALER_Amount *amount)
{
- struct GNUNET_PQ_QueryParam res =
- { &qconv_amount_nbo, NULL, x, sizeof (*x), 2 };
+ struct GNUNET_PQ_QueryParam res = {
+ .conv_cls = (void *) db,
+ .conv = &qconv_amount_currency_tuple,
+ .data = amount,
+ .size = sizeof (*amount),
+ .num_params = 1,
+ };
+
return res;
}
/**
- * Function called to convert input argument into SQL parameters.
+ * Function called to convert input amount into SQL parameter as tuple.
*
* @param cls closure
* @param data pointer to input argument, here a `struct TALER_Amount`
@@ -103,55 +134,76 @@ TALER_PQ_query_param_amount_nbo (const struct TALER_AmountNBO *x)
* @return -1 on error, number of offsets used in @a scratch otherwise
*/
static int
-qconv_amount (void *cls,
- const void *data,
- size_t data_len,
- void *param_values[],
- int param_lengths[],
- int param_formats[],
- unsigned int param_length,
- void *scratch[],
- unsigned int scratch_length)
+qconv_amount_tuple (void *cls,
+ const void *data,
+ size_t data_len,
+ void *param_values[],
+ int param_lengths[],
+ int param_formats[],
+ unsigned int param_length,
+ void *scratch[],
+ unsigned int scratch_length)
{
- const struct TALER_Amount *amount_hbo = data;
- struct TALER_AmountNBO *amount;
+ struct GNUNET_PQ_Context *db = cls;
+ const struct TALER_Amount *amount = data;
+ size_t sz;
+
+ GNUNET_assert (NULL != db);
+ GNUNET_assert (NULL != amount);
+ GNUNET_assert (1 == param_length);
+ GNUNET_assert (1 <= scratch_length);
+ GNUNET_assert (sizeof (struct TALER_Amount) == data_len);
+ GNUNET_static_assert (sizeof(uint32_t) == sizeof(Oid));
+ {
+ char *out;
+ Oid oid_v;
+ Oid oid_f;
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "int8",
+ &oid_v));
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "int4",
+ &oid_f));
+
+ {
+ struct TALER_PQ_AmountP d
+ = TALER_PQ_make_taler_pq_amount_ (amount,
+ oid_v,
+ oid_f);
+
+ sz = sizeof(d);
+ out = GNUNET_malloc (sz);
+ scratch[0] = out;
+ GNUNET_memcpy (out,
+ &d,
+ sizeof(d));
+ }
+ }
+
+ param_values[0] = scratch[0];
+ param_lengths[0] = sz;
+ param_formats[0] = 1;
- (void) cls;
- (void) scratch;
- (void) scratch_length;
- GNUNET_assert (2 == param_length);
- GNUNET_assert (sizeof (struct TALER_AmountNBO) == data_len);
- amount = GNUNET_new (struct TALER_AmountNBO);
- scratch[0] = amount;
- TALER_amount_hton (amount,
- amount_hbo);
- qconv_amount_nbo (cls,
- amount,
- sizeof (struct TALER_AmountNBO),
- param_values,
- param_lengths,
- param_formats,
- param_length,
- &scratch[1],
- scratch_length - 1);
return 1;
}
-/**
- * Generate query parameter for a currency, consisting of the three
- * components "value", "fraction" and "currency" in this order. The
- * types must be a 64-bit integer, 32-bit integer and a
- * #TALER_CURRENCY_LEN-sized BLOB/VARCHAR respectively.
- *
- * @param x pointer to the query parameter to pass
- * @return array entry for the query parameters to use
- */
struct GNUNET_PQ_QueryParam
-TALER_PQ_query_param_amount (const struct TALER_Amount *x)
+TALER_PQ_query_param_amount (
+ const struct GNUNET_PQ_Context *db,
+ const struct TALER_Amount *amount)
{
- struct GNUNET_PQ_QueryParam res =
- { &qconv_amount, NULL, x, sizeof (*x), 2 };
+ struct GNUNET_PQ_QueryParam res = {
+ .conv_cls = (void *) db,
+ .conv = &qconv_amount_tuple,
+ .data = amount,
+ .size = sizeof (*amount),
+ .num_params = 1,
+ };
+
return res;
}
@@ -160,56 +212,192 @@ TALER_PQ_query_param_amount (const struct TALER_Amount *x)
* Function called to convert input argument into SQL parameters.
*
* @param cls closure
- * @param data pointer to input argument, here a `json_t *`
+ * @param data pointer to input argument
* @param data_len number of bytes in @a data (if applicable)
* @param[out] param_values SQL data to set
* @param[out] param_lengths SQL length data to set
* @param[out] param_formats SQL format data to set
* @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
- * @param[out] scratch buffer for dynamic allocations (to be done via GNUNET_malloc()
+ * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
* @param scratch_length number of entries left in @a scratch
* @return -1 on error, number of offsets used in @a scratch otherwise
*/
static int
-qconv_json (void *cls,
- const void *data,
- size_t data_len,
- void *param_values[],
- int param_lengths[],
- int param_formats[],
- unsigned int param_length,
- void *scratch[],
- unsigned int scratch_length)
+qconv_denom_pub (void *cls,
+ const void *data,
+ size_t data_len,
+ void *param_values[],
+ int param_lengths[],
+ int param_formats[],
+ unsigned int param_length,
+ void *scratch[],
+ unsigned int scratch_length)
{
- const json_t *json = data;
- char *str;
+ const struct TALER_DenominationPublicKey *denom_pub = data;
+ const struct GNUNET_CRYPTO_BlindSignPublicKey *bsp = denom_pub->bsign_pub_key;
+ size_t tlen;
+ size_t len;
+ uint32_t be[2];
+ char *buf;
+ void *tbuf;
(void) cls;
(void) data_len;
GNUNET_assert (1 == param_length);
GNUNET_assert (scratch_length > 0);
- str = json_dumps (json, JSON_COMPACT);
- if (NULL == str)
- return -1;
- scratch[0] = str;
- param_values[0] = (void *) str;
- param_lengths[0] = strlen (str);
+ GNUNET_break (NULL == cls);
+ be[0] = htonl ((uint32_t) bsp->cipher);
+ be[1] = htonl (denom_pub->age_mask.bits);
+ switch (bsp->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ tlen = GNUNET_CRYPTO_rsa_public_key_encode (
+ bsp->details.rsa_public_key,
+ &tbuf);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ tlen = sizeof (bsp->details.cs_public_key);
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ len = tlen + sizeof (be);
+ buf = GNUNET_malloc (len);
+ GNUNET_memcpy (buf,
+ be,
+ sizeof (be));
+ switch (bsp->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ tbuf,
+ tlen);
+ GNUNET_free (tbuf);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ &bsp->details.cs_public_key,
+ tlen);
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+
+ scratch[0] = buf;
+ param_values[0] = (void *) buf;
+ param_lengths[0] = len;
param_formats[0] = 1;
return 1;
}
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_denom_pub (
+ const struct TALER_DenominationPublicKey *denom_pub)
+{
+ struct GNUNET_PQ_QueryParam res = {
+ .conv = &qconv_denom_pub,
+ .data = denom_pub,
+ .num_params = 1
+ };
+
+ return res;
+}
+
+
/**
- * Generate query parameter for a JSON object (stored as a string
- * in the DB).
+ * Function called to convert input argument into SQL parameters.
*
- * @param x pointer to the json object to pass
+ * @param cls closure
+ * @param data pointer to input argument
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param[out] param_values SQL data to set
+ * @param[out] param_lengths SQL length data to set
+ * @param[out] param_formats SQL format data to set
+ * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
+ * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
+ * @param scratch_length number of entries left in @a scratch
+ * @return -1 on error, number of offsets used in @a scratch otherwise
*/
+static int
+qconv_denom_sig (void *cls,
+ const void *data,
+ size_t data_len,
+ void *param_values[],
+ int param_lengths[],
+ int param_formats[],
+ unsigned int param_length,
+ void *scratch[],
+ unsigned int scratch_length)
+{
+ const struct TALER_DenominationSignature *denom_sig = data;
+ const struct GNUNET_CRYPTO_UnblindedSignature *ubs = denom_sig->unblinded_sig;
+ size_t tlen;
+ size_t len;
+ uint32_t be[2];
+ char *buf;
+ void *tbuf;
+
+ (void) cls;
+ (void) data_len;
+ GNUNET_assert (1 == param_length);
+ GNUNET_assert (scratch_length > 0);
+ GNUNET_break (NULL == cls);
+ be[0] = htonl ((uint32_t) ubs->cipher);
+ be[1] = htonl (0x00); /* magic marker: unblinded */
+ switch (ubs->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ tlen = GNUNET_CRYPTO_rsa_signature_encode (
+ ubs->details.rsa_signature,
+ &tbuf);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ tlen = sizeof (ubs->details.cs_signature);
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ len = tlen + sizeof (be);
+ buf = GNUNET_malloc (len);
+ GNUNET_memcpy (buf,
+ &be,
+ sizeof (be));
+ switch (ubs->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ tbuf,
+ tlen);
+ GNUNET_free (tbuf);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ &ubs->details.cs_signature,
+ tlen);
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+
+ scratch[0] = buf;
+ param_values[0] = (void *) buf;
+ param_lengths[0] = len;
+ param_formats[0] = 1;
+ return 1;
+}
+
+
struct GNUNET_PQ_QueryParam
-TALER_PQ_query_param_json (const json_t *x)
+TALER_PQ_query_param_denom_sig (
+ const struct TALER_DenominationSignature *denom_sig)
{
- struct GNUNET_PQ_QueryParam res =
- { &qconv_json, NULL, x, 0, 1 };
+ struct GNUNET_PQ_QueryParam res = {
+ .conv = &qconv_denom_sig,
+ .data = denom_sig,
+ .num_params = 1
+ };
+
return res;
}
@@ -229,53 +417,176 @@ TALER_PQ_query_param_json (const json_t *x)
* @return -1 on error, number of offsets used in @a scratch otherwise
*/
static int
-qconv_round_time (void *cls,
- const void *data,
- size_t data_len,
- void *param_values[],
- int param_lengths[],
- int param_formats[],
- unsigned int param_length,
- void *scratch[],
- unsigned int scratch_length)
+qconv_blinded_denom_sig (void *cls,
+ const void *data,
+ size_t data_len,
+ void *param_values[],
+ int param_lengths[],
+ int param_formats[],
+ unsigned int param_length,
+ void *scratch[],
+ unsigned int scratch_length)
{
- const struct GNUNET_TIME_Absolute *at = data;
- struct GNUNET_TIME_Absolute tmp;
- struct GNUNET_TIME_AbsoluteNBO *buf;
+ const struct TALER_BlindedDenominationSignature *denom_sig = data;
+ const struct GNUNET_CRYPTO_BlindedSignature *bs = denom_sig->blinded_sig;
+ size_t tlen;
+ size_t len;
+ uint32_t be[2];
+ char *buf;
+ void *tbuf;
(void) cls;
+ (void) data_len;
GNUNET_assert (1 == param_length);
- GNUNET_assert (sizeof (struct GNUNET_TIME_AbsoluteNBO) == data_len);
GNUNET_assert (scratch_length > 0);
GNUNET_break (NULL == cls);
- tmp = *at;
- GNUNET_assert (GNUNET_OK ==
- GNUNET_TIME_round_abs (&tmp));
- buf = GNUNET_new (struct GNUNET_TIME_AbsoluteNBO);
- *buf = GNUNET_TIME_absolute_hton (tmp);
+ be[0] = htonl ((uint32_t) bs->cipher);
+ be[1] = htonl (0x01); /* magic marker: blinded */
+ switch (bs->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ tlen = GNUNET_CRYPTO_rsa_signature_encode (
+ bs->details.blinded_rsa_signature,
+ &tbuf);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ tlen = sizeof (bs->details.blinded_cs_answer);
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ len = tlen + sizeof (be);
+ buf = GNUNET_malloc (len);
+ GNUNET_memcpy (buf,
+ &be,
+ sizeof (be));
+ switch (bs->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ tbuf,
+ tlen);
+ GNUNET_free (tbuf);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ &bs->details.blinded_cs_answer,
+ tlen);
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+
scratch[0] = buf;
param_values[0] = (void *) buf;
- param_lengths[0] = sizeof (struct GNUNET_TIME_AbsoluteNBO);
+ param_lengths[0] = len;
param_formats[0] = 1;
return 1;
}
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_blinded_denom_sig (
+ const struct TALER_BlindedDenominationSignature *denom_sig)
+{
+ struct GNUNET_PQ_QueryParam res = {
+ .conv = &qconv_blinded_denom_sig,
+ .data = denom_sig,
+ .num_params = 1
+ };
+
+ return res;
+}
+
+
/**
- * Generate query parameter for an absolute time value.
- * In contrast to
- * #GNUNET_PQ_query_param_absolute_time(), this function
- * will abort (!) if the time given is not rounded!
- * The database must store a 64-bit integer.
+ * Function called to convert input argument into SQL parameters.
*
- * @param x pointer to the query parameter to pass
- * @return array entry for the query parameters to use
+ * @param cls closure
+ * @param data pointer to input argument
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param[out] param_values SQL data to set
+ * @param[out] param_lengths SQL length data to set
+ * @param[out] param_formats SQL format data to set
+ * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
+ * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
+ * @param scratch_length number of entries left in @a scratch
+ * @return -1 on error, number of offsets used in @a scratch otherwise
*/
+static int
+qconv_blinded_planchet (void *cls,
+ const void *data,
+ size_t data_len,
+ void *param_values[],
+ int param_lengths[],
+ int param_formats[],
+ unsigned int param_length,
+ void *scratch[],
+ unsigned int scratch_length)
+{
+ const struct TALER_BlindedPlanchet *bp = data;
+ const struct GNUNET_CRYPTO_BlindedMessage *bm = bp->blinded_message;
+ size_t tlen;
+ size_t len;
+ uint32_t be[2];
+ char *buf;
+
+ (void) cls;
+ (void) data_len;
+ GNUNET_assert (1 == param_length);
+ GNUNET_assert (scratch_length > 0);
+ GNUNET_break (NULL == cls);
+ be[0] = htonl ((uint32_t) bm->cipher);
+ be[1] = htonl (0x0100); /* magic marker: blinded */
+ switch (bm->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ tlen = bm->details.rsa_blinded_message.blinded_msg_size;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ tlen = sizeof (bm->details.cs_blinded_message);
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ len = tlen + sizeof (be);
+ buf = GNUNET_malloc (len);
+ GNUNET_memcpy (buf,
+ &be,
+ sizeof (be));
+ switch (bm->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ bm->details.rsa_blinded_message.blinded_msg,
+ tlen);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ &bm->details.cs_blinded_message,
+ tlen);
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ scratch[0] = buf;
+ param_values[0] = (void *) buf;
+ param_lengths[0] = len;
+ param_formats[0] = 1;
+ return 1;
+}
+
+
struct GNUNET_PQ_QueryParam
-TALER_PQ_query_param_absolute_time (const struct GNUNET_TIME_Absolute *x)
+TALER_PQ_query_param_blinded_planchet (
+ const struct TALER_BlindedPlanchet *bp)
{
- struct GNUNET_PQ_QueryParam res =
- { &qconv_round_time, NULL, x, sizeof (*x), 1 };
+ struct GNUNET_PQ_QueryParam res = {
+ .conv = &qconv_blinded_planchet,
+ .data = bp,
+ .num_params = 1
+ };
+
return res;
}
@@ -295,51 +606,734 @@ TALER_PQ_query_param_absolute_time (const struct GNUNET_TIME_Absolute *x)
* @return -1 on error, number of offsets used in @a scratch otherwise
*/
static int
-qconv_round_time_abs (void *cls,
- const void *data,
- size_t data_len,
- void *param_values[],
- int param_lengths[],
- int param_formats[],
- unsigned int param_length,
- void *scratch[],
- unsigned int scratch_length)
+qconv_exchange_withdraw_values (void *cls,
+ const void *data,
+ size_t data_len,
+ void *param_values[],
+ int param_lengths[],
+ int param_formats[],
+ unsigned int param_length,
+ void *scratch[],
+ unsigned int scratch_length)
{
- const struct GNUNET_TIME_AbsoluteNBO *at = data;
- struct GNUNET_TIME_Absolute tmp;
+ const struct TALER_ExchangeWithdrawValues *alg_values = data;
+ const struct GNUNET_CRYPTO_BlindingInputValues *bi =
+ alg_values->blinding_inputs;
+ size_t tlen;
+ size_t len;
+ uint32_t be[2];
+ char *buf;
(void) cls;
- (void) scratch;
- (void) scratch_length;
+ (void) data_len;
GNUNET_assert (1 == param_length);
- GNUNET_assert (sizeof (struct GNUNET_TIME_AbsoluteNBO) == data_len);
+ GNUNET_assert (scratch_length > 0);
GNUNET_break (NULL == cls);
- tmp = GNUNET_TIME_absolute_ntoh (*at);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_TIME_round_abs (&tmp));
- param_values[0] = (void *) at;
- param_lengths[0] = sizeof (struct GNUNET_TIME_AbsoluteNBO);
+ be[0] = htonl ((uint32_t) bi->cipher);
+ be[1] = htonl (0x010000); /* magic marker: EWV */
+ switch (bi->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ tlen = 0;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ tlen = sizeof (struct GNUNET_CRYPTO_CSPublicRPairP);
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ len = tlen + sizeof (be);
+ buf = GNUNET_malloc (len);
+ GNUNET_memcpy (buf,
+ &be,
+ sizeof (be));
+ switch (bi->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ &bi->details.cs_values,
+ tlen);
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ scratch[0] = buf;
+ param_values[0] = (void *) buf;
+ param_lengths[0] = len;
param_formats[0] = 1;
- return 0;
+ return 1;
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_exchange_withdraw_values (
+ const struct TALER_ExchangeWithdrawValues *alg_values)
+{
+ struct GNUNET_PQ_QueryParam res = {
+ .conv = &qconv_exchange_withdraw_values,
+ .data = alg_values,
+ .num_params = 1
+ };
+
+ return res;
}
/**
- * Generate query parameter for an absolute time value.
- * In contrast to
- * #GNUNET_PQ_query_param_absolute_time(), this function
- * will abort (!) if the time given is not rounded!
- * The database must store a 64-bit integer.
+ * Function called to convert input argument into SQL parameters.
*
- * @param x pointer to the query parameter to pass
+ * @param cls closure
+ * @param data pointer to input argument, here a `json_t *`
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param[out] param_values SQL data to set
+ * @param[out] param_lengths SQL length data to set
+ * @param[out] param_formats SQL format data to set
+ * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
+ * @param[out] scratch buffer for dynamic allocations (to be done via GNUNET_malloc()
+ * @param scratch_length number of entries left in @a scratch
+ * @return -1 on error, number of offsets used in @a scratch otherwise
*/
+static int
+qconv_json (void *cls,
+ const void *data,
+ size_t data_len,
+ void *param_values[],
+ int param_lengths[],
+ int param_formats[],
+ unsigned int param_length,
+ void *scratch[],
+ unsigned int scratch_length)
+{
+ const json_t *json = data;
+ char *str;
+
+ (void) cls;
+ (void) data_len;
+ GNUNET_assert (1 == param_length);
+ GNUNET_assert (scratch_length > 0);
+ str = json_dumps (json, JSON_COMPACT);
+ if (NULL == str)
+ return -1;
+ scratch[0] = str;
+ param_values[0] = (void *) str;
+ param_lengths[0] = strlen (str);
+ param_formats[0] = 1;
+ return 1;
+}
+
+
struct GNUNET_PQ_QueryParam
-TALER_PQ_query_param_absolute_time_nbo (const struct GNUNET_TIME_AbsoluteNBO *x)
+TALER_PQ_query_param_json (const json_t *x)
{
- struct GNUNET_PQ_QueryParam res =
- { &qconv_round_time_abs, NULL, x, sizeof (*x), 1 };
+ struct GNUNET_PQ_QueryParam res = {
+ .conv = &qconv_json,
+ .data = x,
+ .num_params = 1
+ };
+
return res;
}
+/** ------------------- Array support -----------------------------------**/
+
+/**
+ * Closure for the array type handlers.
+ *
+ * May contain sizes information for the data, given (and handled) by the
+ * caller.
+ */
+struct qconv_array_cls
+{
+ /**
+ * If not null, contains the array of sizes (the size of the array is the
+ * .size field in the ambient GNUNET_PQ_QueryParam struct). We do not free
+ * this memory.
+ *
+ * If not null, this value has precedence over @a sizes, which MUST be NULL */
+ const size_t *sizes;
+
+ /**
+ * If @a size and @a c_sizes are NULL, this field defines the same size
+ * for each element in the array.
+ */
+ size_t same_size;
+
+ /**
+ * If true, the array parameter to the data pointer to the qconv_array is a
+ * continuous byte array of data, either with @a same_size each or sizes
+ * provided bytes by @a sizes;
+ */
+ bool continuous;
+
+ /**
+ * Type of the array elements
+ */
+ enum TALER_PQ_ArrayType typ;
+
+ /**
+ * Oid of the array elements
+ */
+ Oid oid;
+
+ /**
+ * db context, needed for OID-lookup of basis-types
+ */
+ struct GNUNET_PQ_Context *db;
+};
+
+/**
+ * Callback to cleanup a qconv_array_cls to be used during
+ * GNUNET_PQ_cleanup_query_params_closures
+ */
+static void
+qconv_array_cls_cleanup (void *cls)
+{
+ GNUNET_free (cls);
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters for arrays
+ *
+ * Note: the format for the encoding of arrays for libpq is not very well
+ * documented. We peeked into various sources (postgresql and libpqtypes) for
+ * guidance.
+ *
+ * @param cls Closure of type struct qconv_array_cls*
+ * @param data Pointer to first element in the array
+ * @param data_len Number of _elements_ in array @a data (if applicable)
+ * @param[out] param_values SQL data to set
+ * @param[out] param_lengths SQL length data to set
+ * @param[out] param_formats SQL format data to set
+ * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
+ * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
+ * @param scratch_length number of entries left in @a scratch
+ * @return -1 on error, number of offsets used in @a scratch otherwise
+ */
+static int
+qconv_array (
+ void *cls,
+ const void *data,
+ size_t data_len,
+ void *param_values[],
+ int param_lengths[],
+ int param_formats[],
+ unsigned int param_length,
+ void *scratch[],
+ unsigned int scratch_length)
+{
+ struct qconv_array_cls *meta = cls;
+ size_t num = data_len;
+ size_t total_size;
+ const size_t *sizes;
+ bool same_sized;
+ void *elements = NULL;
+ bool noerror = true;
+ /* needed to capture the encoded rsa signatures */
+ void **buffers = NULL;
+ size_t *buffer_lengths = NULL;
+
+ (void) (param_length);
+ (void) (scratch_length);
+
+ GNUNET_assert (NULL != meta);
+ GNUNET_assert (num < INT_MAX);
+
+ sizes = meta->sizes;
+ same_sized = (0 != meta->same_size);
+
+#define RETURN_UNLESS(cond) \
+ do { \
+ if (! (cond)) \
+ { \
+ GNUNET_break ((cond)); \
+ noerror = false; \
+ goto DONE; \
+ } \
+ } while (0)
+
+ /* Calculate sizes and check bounds */
+ {
+ /* num * length-field */
+ size_t x = sizeof(uint32_t);
+ size_t y = x * num;
+ RETURN_UNLESS ((0 == num) || (y / num == x));
+
+ /* size of header */
+ total_size = x = sizeof(struct GNUNET_PQ_ArrayHeader_P);
+ total_size += y;
+ RETURN_UNLESS (total_size >= x);
+
+ /* sizes of elements */
+ if (same_sized)
+ {
+ x = num * meta->same_size;
+ RETURN_UNLESS ((0 == num) || (x / num == meta->same_size));
+
+ y = total_size;
+ total_size += x;
+ RETURN_UNLESS (total_size >= y);
+ }
+ else /* sizes are different per element */
+ {
+ switch (meta->typ)
+ {
+ case TALER_PQ_array_of_amount_currency:
+ {
+ const struct TALER_Amount *amounts = data;
+ Oid oid_v;
+ Oid oid_f;
+ Oid oid_c;
+
+ buffer_lengths = GNUNET_new_array (num, size_t);
+ /* hoist out of loop? */
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (meta->db,
+ "int8",
+ &oid_v));
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (meta->db,
+ "int4",
+ &oid_f));
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (meta->db,
+ "varchar",
+ &oid_c));
+ for (size_t i = 0; i<num; i++)
+ {
+ struct TALER_PQ_AmountCurrencyP am;
+ size_t len;
+
+ len = TALER_PQ_make_taler_pq_amount_currency_ (
+ &amounts[i],
+ oid_v,
+ oid_f,
+ oid_c,
+ &am);
+ buffer_lengths[i] = len;
+ y = total_size;
+ total_size += len;
+ RETURN_UNLESS (total_size >= y);
+ }
+ sizes = buffer_lengths;
+ break;
+ }
+ case TALER_PQ_array_of_blinded_denom_sig:
+ {
+ const struct TALER_BlindedDenominationSignature *denom_sigs = data;
+ size_t len;
+
+ buffers = GNUNET_new_array (num, void *);
+ buffer_lengths = GNUNET_new_array (num, size_t);
+
+ for (size_t i = 0; i<num; i++)
+ {
+ const struct GNUNET_CRYPTO_BlindedSignature *bs =
+ denom_sigs[i].blinded_sig;
+
+ switch (bs->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ len = GNUNET_CRYPTO_rsa_signature_encode (
+ bs->details.blinded_rsa_signature,
+ &buffers[i]);
+ RETURN_UNLESS (len != 0);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ len = sizeof (bs->details.blinded_cs_answer);
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+
+ /* for the cipher and marker */
+ len += 2 * sizeof(uint32_t);
+ buffer_lengths[i] = len;
+
+ y = total_size;
+ total_size += len;
+ RETURN_UNLESS (total_size >= y);
+ }
+ sizes = buffer_lengths;
+ break;
+ }
+ default:
+ GNUNET_assert (0);
+ }
+ }
+
+ RETURN_UNLESS (INT_MAX > total_size);
+ RETURN_UNLESS (0 != total_size);
+
+ elements = GNUNET_malloc (total_size);
+ }
+
+ /* Write data */
+ {
+ char *out = elements;
+ struct GNUNET_PQ_ArrayHeader_P h = {
+ .ndim = htonl (1), /* We only support one-dimensional arrays */
+ .has_null = htonl (0), /* We do not support NULL entries in arrays */
+ .lbound = htonl (1), /* Default start index value */
+ .dim = htonl (num),
+ .oid = htonl (meta->oid),
+ };
+
+ /* Write header */
+ GNUNET_memcpy (out,
+ &h,
+ sizeof(h));
+ out += sizeof(h);
+
+ /* Write elements */
+ for (size_t i = 0; i < num; i++)
+ {
+ size_t sz = same_sized ? meta->same_size : sizes[i];
+
+ *(uint32_t *) out = htonl (sz);
+ out += sizeof(uint32_t);
+ switch (meta->typ)
+ {
+ case TALER_PQ_array_of_amount:
+ {
+ const struct TALER_Amount *amounts = data;
+ Oid oid_v;
+ Oid oid_f;
+
+ /* hoist out of loop? */
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (meta->db,
+ "int8",
+ &oid_v));
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (meta->db,
+ "int4",
+ &oid_f));
+ {
+ struct TALER_PQ_AmountP am
+ = TALER_PQ_make_taler_pq_amount_ (
+ &amounts[i],
+ oid_v,
+ oid_f);
+
+ GNUNET_memcpy (out,
+ &am,
+ sizeof(am));
+ }
+ break;
+ }
+ case TALER_PQ_array_of_amount_currency:
+ {
+ const struct TALER_Amount *amounts = data;
+ Oid oid_v;
+ Oid oid_f;
+ Oid oid_c;
+
+ /* hoist out of loop? */
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (meta->db,
+ "int8",
+ &oid_v));
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (meta->db,
+ "int4",
+ &oid_f));
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (meta->db,
+ "varchar",
+ &oid_c));
+ {
+ struct TALER_PQ_AmountCurrencyP am;
+ size_t len;
+
+ len = TALER_PQ_make_taler_pq_amount_currency_ (
+ &amounts[i],
+ oid_v,
+ oid_f,
+ oid_c,
+ &am);
+ GNUNET_memcpy (out,
+ &am,
+ len);
+ }
+ break;
+ }
+ case TALER_PQ_array_of_blinded_denom_sig:
+ {
+ const struct TALER_BlindedDenominationSignature *denom_sigs = data;
+ const struct GNUNET_CRYPTO_BlindedSignature *bs =
+ denom_sigs[i].blinded_sig;
+ uint32_t be[2];
+
+ be[0] = htonl ((uint32_t) bs->cipher);
+ be[1] = htonl (0x01); /* magic margker: blinded */
+ GNUNET_memcpy (out,
+ &be,
+ sizeof(be));
+ out += sizeof(be);
+ sz -= sizeof(be);
+
+ switch (bs->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ /* For RSA, 'same_sized' must have been false */
+ GNUNET_assert (NULL != buffers);
+ GNUNET_memcpy (out,
+ buffers[i],
+ sz);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_memcpy (out,
+ &bs->details.blinded_cs_answer,
+ sz);
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ break;
+ }
+ case TALER_PQ_array_of_blinded_coin_hash:
+ {
+ const struct TALER_BlindedCoinHashP *coin_hs = data;
+
+ GNUNET_memcpy (out,
+ &coin_hs[i],
+ sizeof(struct TALER_BlindedCoinHashP));
+
+ break;
+ }
+ case TALER_PQ_array_of_denom_hash:
+ {
+ const struct TALER_DenominationHashP *denom_hs = data;
+
+ GNUNET_memcpy (out,
+ &denom_hs[i],
+ sizeof(struct TALER_DenominationHashP));
+ break;
+ }
+ case TALER_PQ_array_of_hash_code:
+ {
+ const struct GNUNET_HashCode *hashes = data;
+
+ GNUNET_memcpy (out,
+ &hashes[i],
+ sizeof(struct GNUNET_HashCode));
+ break;
+ }
+ default:
+ {
+ GNUNET_assert (0);
+ break;
+ }
+ }
+ out += sz;
+ }
+ }
+ param_values[0] = elements;
+ param_lengths[0] = total_size;
+ param_formats[0] = 1;
+ scratch[0] = elements;
+
+DONE:
+ if (NULL != buffers)
+ {
+ for (size_t i = 0; i<num; i++)
+ GNUNET_free (buffers[i]);
+ GNUNET_free (buffers);
+ }
+ GNUNET_free (buffer_lengths);
+ if (noerror)
+ return 1;
+ return -1;
+}
+
+
+/**
+ * Function to generate a typ specific query parameter and corresponding closure
+ *
+ * @param num Number of elements in @a elements
+ * @param continuous If true, @a elements is an continuous array of data
+ * @param elements Array of @a num elements, either continuous or pointers
+ * @param sizes Array of @a num sizes, one per element, may be NULL
+ * @param same_size If not 0, all elements in @a elements have this size
+ * @param typ Supported internal type of each element in @a elements
+ * @param oid Oid of the type to be used in Postgres
+ * @param[in,out] db our database handle for looking up OIDs
+ * @return Query parameter
+ */
+static struct GNUNET_PQ_QueryParam
+query_param_array_generic (
+ unsigned int num,
+ bool continuous,
+ const void *elements,
+ const size_t *sizes,
+ size_t same_size,
+ enum TALER_PQ_ArrayType typ,
+ Oid oid,
+ struct GNUNET_PQ_Context *db)
+{
+ struct qconv_array_cls *meta = GNUNET_new (struct qconv_array_cls);
+
+ meta->typ = typ;
+ meta->oid = oid;
+ meta->sizes = sizes;
+ meta->same_size = same_size;
+ meta->continuous = continuous;
+ meta->db = db;
+
+ {
+ struct GNUNET_PQ_QueryParam res = {
+ .conv = qconv_array,
+ .conv_cls = meta,
+ .conv_cls_cleanup = qconv_array_cls_cleanup,
+ .data = elements,
+ .size = num,
+ .num_params = 1,
+ };
+
+ return res;
+ }
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_blinded_denom_sig (
+ size_t num,
+ const struct TALER_BlindedDenominationSignature *denom_sigs,
+ struct GNUNET_PQ_Context *db)
+{
+ Oid oid;
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "bytea",
+ &oid));
+ return query_param_array_generic (num,
+ true,
+ denom_sigs,
+ NULL,
+ 0,
+ TALER_PQ_array_of_blinded_denom_sig,
+ oid,
+ NULL);
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_blinded_coin_hash (
+ size_t num,
+ const struct TALER_BlindedCoinHashP *coin_hs,
+ struct GNUNET_PQ_Context *db)
+{
+ Oid oid;
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "bytea",
+ &oid));
+ return query_param_array_generic (num,
+ true,
+ coin_hs,
+ NULL,
+ sizeof(struct TALER_BlindedCoinHashP),
+ TALER_PQ_array_of_blinded_coin_hash,
+ oid,
+ NULL);
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_denom_hash (
+ size_t num,
+ const struct TALER_DenominationHashP *denom_hs,
+ struct GNUNET_PQ_Context *db)
+{
+ Oid oid;
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "bytea",
+ &oid));
+ return query_param_array_generic (num,
+ true,
+ denom_hs,
+ NULL,
+ sizeof(struct TALER_DenominationHashP),
+ TALER_PQ_array_of_denom_hash,
+ oid,
+ NULL);
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_hash_code (
+ size_t num,
+ const struct GNUNET_HashCode *hashes,
+ struct GNUNET_PQ_Context *db)
+{
+ Oid oid;
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db, "gnunet_hashcode", &oid));
+ return query_param_array_generic (num,
+ true,
+ hashes,
+ NULL,
+ sizeof(struct GNUNET_HashCode),
+ TALER_PQ_array_of_hash_code,
+ oid,
+ NULL);
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_amount (
+ size_t num,
+ const struct TALER_Amount *amounts,
+ struct GNUNET_PQ_Context *db)
+{
+ Oid oid;
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "taler_amount",
+ &oid));
+ return query_param_array_generic (
+ num,
+ true,
+ amounts,
+ NULL,
+ sizeof(struct TALER_PQ_AmountP),
+ TALER_PQ_array_of_amount,
+ oid,
+ db);
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_amount_with_currency (
+ size_t num,
+ const struct TALER_Amount *amounts,
+ struct GNUNET_PQ_Context *db)
+{
+ Oid oid;
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "taler_amount_currency",
+ &oid));
+ return query_param_array_generic (
+ num,
+ true,
+ amounts,
+ NULL,
+ 0, /* currency is technically variable length */
+ TALER_PQ_array_of_amount_currency,
+ oid,
+ db);
+}
+
+
/* end of pq/pq_query_helper.c */
diff --git a/src/pq/pq_result_helper.c b/src/pq/pq_result_helper.c
index fdf3dc34c..384200cfb 100644
--- a/src/pq/pq_result_helper.c
+++ b/src/pq/pq_result_helper.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014, 2015, 2016 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
@@ -20,159 +20,144 @@
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
+#include "pq_common.h"
#include "taler_pq_lib.h"
/**
- * Extract a currency amount from a query result according to the
- * given specification.
+ * Extract an amount from a tuple including the currency from a Postgres
+ * database @a result at row @a row.
*
- * @param result the result to extract the amount from
- * @param row which row of the result to extract the amount from (needed as results can have multiple rows)
- * @param currency currency to use for @a r_amount_nbo
- * @param val_name name of the column with the amount's "value", must include the substring "_val".
- * @param frac_name name of the column with the amount's "fractional" value, must include the substring "_frac".
- * @param[out] r_amount_nbo where to store the amount, in network byte order
+ * @param cls closure; not used
+ * @param result where to extract data from
+ * @param row row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
* @return
* #GNUNET_YES if all results could be extracted
* #GNUNET_NO if at least one result was NULL
* #GNUNET_SYSERR if a result was invalid (non-existing field)
*/
-static int
-extract_amount_nbo_helper (PGresult *result,
- int row,
- const char *currency,
- const char *val_name,
- const char *frac_name,
- struct TALER_AmountNBO *r_amount_nbo)
+static enum GNUNET_GenericReturnValue
+extract_amount_currency_tuple (void *cls,
+ PGresult *result,
+ int row,
+ const char *fname,
+ size_t *dst_size,
+ void *dst)
{
- int val_num;
- int frac_num;
- int len;
-
- /* These checks are simply to check that clients obey by our naming
- conventions, and not for any functional reason */
- GNUNET_assert (NULL !=
- strstr (val_name,
- "_val"));
- GNUNET_assert (NULL !=
- strstr (frac_name,
- "_frac"));
- /* Set return value to invalid in case we don't finish */
- memset (r_amount_nbo,
- 0,
- sizeof (struct TALER_AmountNBO));
- val_num = PQfnumber (result,
- val_name);
- frac_num = PQfnumber (result,
- frac_name);
- if (val_num < 0)
+ struct TALER_Amount *r_amount = dst;
+ int col;
+
+ (void) cls;
+ if (sizeof (struct TALER_Amount) != *dst_size)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Field `%s' does not exist in result\n",
- val_name);
+ GNUNET_break (0);
return GNUNET_SYSERR;
}
- if (frac_num < 0)
+
+ /* Set return value to invalid in case we don't finish */
+ memset (r_amount,
+ 0,
+ sizeof (struct TALER_Amount));
+ col = PQfnumber (result,
+ fname);
+ if (col < 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Field `%s' does not exist in result\n",
- frac_name);
+ fname);
return GNUNET_SYSERR;
}
- if ( (PQgetisnull (result,
- row,
- val_num)) ||
- (PQgetisnull (result,
- row,
- frac_num)) )
+ if (PQgetisnull (result,
+ row,
+ col))
{
- GNUNET_break (0);
return GNUNET_NO;
}
- /* Note that Postgres stores value in NBO internally,
- so no conversion needed in this case */
- r_amount_nbo->value = *(uint64_t *) PQgetvalue (result,
- row,
- val_num);
- r_amount_nbo->fraction = *(uint32_t *) PQgetvalue (result,
- row,
- frac_num);
- len = GNUNET_MIN (TALER_CURRENCY_LEN - 1,
- strlen (currency));
- memcpy (r_amount_nbo->currency,
- currency,
- len);
- return GNUNET_OK;
-}
+ /* Parse the tuple */
+ {
+ struct TALER_PQ_AmountCurrencyP ap;
+ const char *in;
+ size_t size;
-/**
- * Extract data from a Postgres database @a result at row @a row.
- *
- * @param cls closure, a `const char *` giving the currency
- * @param result where to extract data from
- * @param row row to extract data from
- * @param fname name (or prefix) of the fields to extract from
- * @param[in,out] dst_size where to store size of result, may be NULL
- * @param[out] dst where to store the result
- * @return
- * #GNUNET_YES if all results could be extracted
- * #GNUNET_NO if at least one result was NULL
- * #GNUNET_SYSERR if a result was invalid (non-existing field)
- */
-static int
-extract_amount_nbo (void *cls,
- PGresult *result,
- int row,
- const char *fname,
- size_t *dst_size,
- void *dst)
-{
- const char *currency = cls;
- char *val_name;
- char *frac_name;
- int ret;
+ size = PQgetlength (result,
+ row,
+ col);
+ if ( (size >= sizeof (ap)) ||
+ (size <= sizeof (ap) - TALER_CURRENCY_LEN) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Incorrect size of binary field `%s' (got %zu, expected (%zu-%zu))\n",
+ fname,
+ size,
+ sizeof (ap) - TALER_CURRENCY_LEN,
+ sizeof (ap));
+ return GNUNET_SYSERR;
+ }
- if (sizeof (struct TALER_AmountNBO) != *dst_size)
+ in = PQgetvalue (result,
+ row,
+ col);
+ memset (&ap.c,
+ 0,
+ TALER_CURRENCY_LEN);
+ memcpy (&ap,
+ in,
+ size);
+ if (3 != ntohl (ap.cnt))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Incorrect number of elements in tuple-field `%s'\n",
+ fname);
+ return GNUNET_SYSERR;
+ }
+ /* TODO[oec]: OID-checks? */
+
+ r_amount->value = GNUNET_ntohll (ap.v);
+ r_amount->fraction = ntohl (ap.f);
+ memcpy (r_amount->currency,
+ ap.c,
+ TALER_CURRENCY_LEN);
+ if ('\0' != r_amount->currency[TALER_CURRENCY_LEN - 1])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid currency (not 0-terminated) in tuple field `%s'\n",
+ fname);
+ /* be sure nobody uses this by accident */
+ memset (r_amount,
+ 0,
+ sizeof (struct TALER_Amount));
+ return GNUNET_SYSERR;
+ }
+ }
+
+ if (r_amount->value >= TALER_AMOUNT_MAX_VALUE)
{
- GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Value in field `%s' exceeds legal range\n",
+ fname);
return GNUNET_SYSERR;
}
- GNUNET_asprintf (&val_name,
- "%s_val",
- fname);
- GNUNET_asprintf (&frac_name,
- "%s_frac",
- fname);
- ret = extract_amount_nbo_helper (result,
- row,
- currency,
- val_name,
- frac_name,
- dst);
- GNUNET_free (val_name);
- GNUNET_free (frac_name);
- return ret;
+ if (r_amount->fraction >= TALER_AMOUNT_FRAC_BASE)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Fraction in field `%s' exceeds legal range\n",
+ fname);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
}
-/**
- * Currency amount expected.
- *
- * @param name name of the field in the table
- * @param currency the currency to use for @a amount
- * @param[out] amount where to store the result
- * @return array entry for the result specification to use
- */
struct GNUNET_PQ_ResultSpec
-TALER_PQ_result_spec_amount_nbo (const char *name,
- const char *currency,
- struct TALER_AmountNBO *amount)
+TALER_PQ_result_spec_amount_with_currency (const char *name,
+ struct TALER_Amount *amount)
{
struct GNUNET_PQ_ResultSpec res = {
- .conv = &extract_amount_nbo,
- .cls = (void *) currency,
+ .conv = &extract_amount_currency_tuple,
.dst = (void *) amount,
.dst_size = sizeof (*amount),
.fname = name
@@ -183,7 +168,7 @@ TALER_PQ_result_spec_amount_nbo (const char *name,
/**
- * Extract data from a Postgres database @a result at row @a row.
+ * Extract an amount from a tuple from a Postgres database @a result at row @a row.
*
* @param cls closure, a `const char *` giving the currency
* @param result where to extract data from
@@ -196,61 +181,136 @@ TALER_PQ_result_spec_amount_nbo (const char *name,
* #GNUNET_NO if at least one result was NULL
* #GNUNET_SYSERR if a result was invalid (non-existing field)
*/
-static int
-extract_amount (void *cls,
- PGresult *result,
- int row,
- const char *fname,
- size_t *dst_size,
- void *dst)
+static enum GNUNET_GenericReturnValue
+extract_amount_tuple (void *cls,
+ PGresult *result,
+ int row,
+ const char *fname,
+ size_t *dst_size,
+ void *dst)
{
- const char *currency = cls;
struct TALER_Amount *r_amount = dst;
- char *val_name;
- char *frac_name;
- struct TALER_AmountNBO amount_nbo;
- int ret;
+ const char *currency = cls;
+ int col;
+ size_t len;
- if (sizeof (struct TALER_AmountNBO) != *dst_size)
+ if (sizeof (struct TALER_Amount) != *dst_size)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
- GNUNET_asprintf (&val_name,
- "%s_val",
- fname);
- GNUNET_asprintf (&frac_name,
- "%s_frac",
+
+ /* Set return value to invalid in case we don't finish */
+ memset (r_amount,
+ 0,
+ sizeof (struct TALER_Amount));
+ col = PQfnumber (result,
fname);
- ret = extract_amount_nbo_helper (result,
- row,
- currency,
- val_name,
- frac_name,
- &amount_nbo);
- TALER_amount_ntoh (r_amount,
- &amount_nbo);
- GNUNET_free (val_name);
- GNUNET_free (frac_name);
- return ret;
+ if (col < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Field `%s' does not exist in result\n",
+ fname);
+ return GNUNET_SYSERR;
+ }
+ if (PQgetisnull (result,
+ row,
+ col))
+ {
+ return GNUNET_NO;
+ }
+
+ /* Parse the tuple */
+ {
+ struct TALER_PQ_AmountP ap;
+ const char *in;
+ size_t size;
+
+ size = PQgetlength (result,
+ row,
+ col);
+ in = PQgetvalue (result,
+ row,
+ col);
+ if (sizeof(struct TALER_PQ_AmountNullP) == size)
+ {
+ struct TALER_PQ_AmountNullP apn;
+
+ memcpy (&apn,
+ in,
+ size);
+ if ( (2 == ntohl (apn.cnt)) &&
+ (-1 == (int32_t) ntohl (apn.sz_v)) &&
+ (-1 == (int32_t) ntohl (apn.sz_f)) )
+ {
+ /* is NULL! */
+ return GNUNET_NO;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Incorrect size of binary field `%s' and not NULL (got %zu, expected %zu)\n",
+ fname,
+ size,
+ sizeof(ap));
+ return GNUNET_SYSERR;
+ }
+ if (sizeof(ap) != size)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Incorrect size of binary field `%s' (got %zu, expected %zu)\n",
+ fname,
+ size,
+ sizeof(ap));
+ return GNUNET_SYSERR;
+ }
+
+ memcpy (&ap,
+ in,
+ size);
+ if (2 != ntohl (ap.cnt))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Incorrect number of elements in tuple-field `%s'\n",
+ fname);
+ return GNUNET_SYSERR;
+ }
+ /* TODO[oec]: OID-checks? */
+
+ r_amount->value = GNUNET_ntohll (ap.v);
+ r_amount->fraction = ntohl (ap.f);
+ }
+
+ if (r_amount->value >= TALER_AMOUNT_MAX_VALUE)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Value in field `%s' exceeds legal range\n",
+ fname);
+ return GNUNET_SYSERR;
+ }
+ if (r_amount->fraction >= TALER_AMOUNT_FRAC_BASE)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Fraction in field `%s' exceeds legal range\n",
+ fname);
+ return GNUNET_SYSERR;
+ }
+
+ len = GNUNET_MIN (TALER_CURRENCY_LEN - 1,
+ strlen (currency));
+
+ GNUNET_memcpy (r_amount->currency,
+ currency,
+ len);
+ return GNUNET_OK;
}
-/**
- * Currency amount expected.
- *
- * @param name name of the field in the table
- * @param currency the currency to use for @a amount
- * @param[out] amount where to store the result
- * @return array entry for the result specification to use
- */
struct GNUNET_PQ_ResultSpec
TALER_PQ_result_spec_amount (const char *name,
const char *currency,
struct TALER_Amount *amount)
{
struct GNUNET_PQ_ResultSpec res = {
- .conv = &extract_amount,
+ .conv = &extract_amount_tuple,
.cls = (void *) currency,
.dst = (void *) amount,
.dst_size = sizeof (*amount),
@@ -275,7 +335,7 @@ TALER_PQ_result_spec_amount (const char *name,
* #GNUNET_NO if at least one result was NULL
* #GNUNET_SYSERR if a result was invalid (non-existing field)
*/
-static int
+static enum GNUNET_GenericReturnValue
extract_json (void *cls,
PGresult *result,
int row,
@@ -349,13 +409,6 @@ clean_json (void *cls,
}
-/**
- * json_t expected.
- *
- * @param name name of the field in the table
- * @param[out] jp where to store the result
- * @return array entry for the result specification to use
- */
struct GNUNET_PQ_ResultSpec
TALER_PQ_result_spec_json (const char *name,
json_t **jp)
@@ -384,20 +437,23 @@ TALER_PQ_result_spec_json (const char *name,
* #GNUNET_YES if all results could be extracted
* #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
*/
-static int
-extract_round_time (void *cls,
- PGresult *result,
- int row,
- const char *fname,
- size_t *dst_size,
- void *dst)
+static enum GNUNET_GenericReturnValue
+extract_denom_pub (void *cls,
+ PGresult *result,
+ int row,
+ const char *fname,
+ size_t *dst_size,
+ void *dst)
{
- struct GNUNET_TIME_Absolute *udst = dst;
- const struct GNUNET_TIME_AbsoluteNBO *res;
- struct GNUNET_TIME_Absolute tmp;
+ struct TALER_DenominationPublicKey *pk = dst;
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bpk;
+ size_t len;
+ const char *res;
int fnum;
+ uint32_t be[2];
(void) cls;
+ (void) dst_size;
fnum = PQfnumber (result,
fname);
if (fnum < 0)
@@ -408,45 +464,233 @@ extract_round_time (void *cls,
if (PQgetisnull (result,
row,
fnum))
+ return GNUNET_NO;
+
+ /* if a field is null, continue but
+ * remember that we now return a different result */
+ len = PQgetlength (result,
+ row,
+ fnum);
+ res = PQgetvalue (result,
+ row,
+ fnum);
+ if (len < sizeof (be))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
- GNUNET_assert (NULL != dst);
- if (sizeof (struct GNUNET_TIME_Absolute) != *dst_size)
+ GNUNET_memcpy (be,
+ res,
+ sizeof (be));
+ res += sizeof (be);
+ len -= sizeof (be);
+ bpk = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
+ bpk->cipher = ntohl (be[0]);
+ bpk->rc = 1;
+ pk->age_mask.bits = ntohl (be[1]);
+ switch (bpk->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ bpk->details.rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_decode (res,
+ len);
+ if (NULL == bpk->details.rsa_public_key)
+ {
+ GNUNET_break (0);
+ GNUNET_free (bpk);
+ return GNUNET_SYSERR;
+ }
+ pk->bsign_pub_key = bpk;
+ GNUNET_CRYPTO_hash (res,
+ len,
+ &bpk->pub_key_hash);
+ return GNUNET_OK;
+ case GNUNET_CRYPTO_BSA_CS:
+ if (sizeof (bpk->details.cs_public_key) != len)
+ {
+ GNUNET_break (0);
+ GNUNET_free (bpk);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_memcpy (&bpk->details.cs_public_key,
+ res,
+ len);
+ pk->bsign_pub_key = bpk;
+ GNUNET_CRYPTO_hash (res,
+ len,
+ &bpk->pub_key_hash);
+ return GNUNET_OK;
+ }
+ GNUNET_break (0);
+ GNUNET_free (bpk);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called to clean up memory allocated
+ * by a #GNUNET_PQ_ResultConverter.
+ *
+ * @param cls closure
+ * @param rd result data to clean up
+ */
+static void
+clean_denom_pub (void *cls,
+ void *rd)
+{
+ struct TALER_DenominationPublicKey *denom_pub = rd;
+
+ (void) cls;
+ TALER_denom_pub_free (denom_pub);
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_denom_pub (const char *name,
+ struct TALER_DenominationPublicKey *denom_pub)
+{
+ struct GNUNET_PQ_ResultSpec res = {
+ .conv = &extract_denom_pub,
+ .cleaner = &clean_denom_pub,
+ .dst = (void *) denom_pub,
+ .fname = name
+ };
+
+ return res;
+}
+
+
+/**
+ * Extract data from a Postgres database @a result at row @a row.
+ *
+ * @param cls closure
+ * @param result where to extract data from
+ * @param row the row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ * #GNUNET_YES if all results could be extracted
+ * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_denom_sig (void *cls,
+ PGresult *result,
+ int row,
+ const char *fname,
+ size_t *dst_size,
+ void *dst)
+{
+ struct TALER_DenominationSignature *sig = dst;
+ struct GNUNET_CRYPTO_UnblindedSignature *ubs;
+ size_t len;
+ const char *res;
+ int fnum;
+ uint32_t be[2];
+
+ (void) cls;
+ (void) dst_size;
+ fnum = PQfnumber (result,
+ fname);
+ if (fnum < 0)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
- res = (struct GNUNET_TIME_AbsoluteNBO *) PQgetvalue (result,
- row,
- fnum);
- tmp = GNUNET_TIME_absolute_ntoh (*res);
- GNUNET_break (GNUNET_OK ==
- GNUNET_TIME_round_abs (&tmp));
- *udst = tmp;
- return GNUNET_OK;
+ if (PQgetisnull (result,
+ row,
+ fnum))
+ return GNUNET_NO;
+
+ /* if a field is null, continue but
+ * remember that we now return a different result */
+ len = PQgetlength (result,
+ row,
+ fnum);
+ res = PQgetvalue (result,
+ row,
+ fnum);
+ if (len < sizeof (be))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_memcpy (&be,
+ res,
+ sizeof (be));
+ if (0x00 != ntohl (be[1]))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ res += sizeof (be);
+ len -= sizeof (be);
+ ubs = GNUNET_new (struct GNUNET_CRYPTO_UnblindedSignature);
+ ubs->rc = 1;
+ ubs->cipher = ntohl (be[0]);
+ switch (ubs->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ ubs->details.rsa_signature
+ = GNUNET_CRYPTO_rsa_signature_decode (res,
+ len);
+ if (NULL == ubs->details.rsa_signature)
+ {
+ GNUNET_break (0);
+ GNUNET_free (ubs);
+ return GNUNET_SYSERR;
+ }
+ sig->unblinded_sig = ubs;
+ return GNUNET_OK;
+ case GNUNET_CRYPTO_BSA_CS:
+ if (sizeof (ubs->details.cs_signature) != len)
+ {
+ GNUNET_break (0);
+ GNUNET_free (ubs);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_memcpy (&ubs->details.cs_signature,
+ res,
+ len);
+ sig->unblinded_sig = ubs;
+ return GNUNET_OK;
+ }
+ GNUNET_break (0);
+ GNUNET_free (ubs);
+ return GNUNET_SYSERR;
}
/**
- * Rounded absolute time expected.
- * In contrast to #GNUNET_PQ_query_param_absolute_time_nbo(),
- * this function ensures that the result is rounded and can
- * be converted to JSON.
+ * Function called to clean up memory allocated
+ * by a #GNUNET_PQ_ResultConverter.
*
- * @param name name of the field in the table
- * @param[out] at where to store the result
- * @return array entry for the result specification to use
+ * @param cls closure
+ * @param rd result data to clean up
*/
+static void
+clean_denom_sig (void *cls,
+ void *rd)
+{
+ struct TALER_DenominationSignature *denom_sig = rd;
+
+ (void) cls;
+ TALER_denom_sig_free (denom_sig);
+}
+
+
struct GNUNET_PQ_ResultSpec
-TALER_PQ_result_spec_absolute_time (const char *name,
- struct GNUNET_TIME_Absolute *at)
+TALER_PQ_result_spec_denom_sig (const char *name,
+ struct TALER_DenominationSignature *denom_sig)
{
struct GNUNET_PQ_ResultSpec res = {
- .conv = &extract_round_time,
- .dst = (void *) at,
- .dst_size = sizeof (struct GNUNET_TIME_Absolute),
+ .conv = &extract_denom_sig,
+ .cleaner = &clean_denom_sig,
+ .dst = (void *) denom_sig,
.fname = name
};
@@ -467,20 +711,23 @@ TALER_PQ_result_spec_absolute_time (const char *name,
* #GNUNET_YES if all results could be extracted
* #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
*/
-static int
-extract_round_time_nbo (void *cls,
- PGresult *result,
- int row,
- const char *fname,
- size_t *dst_size,
- void *dst)
+static enum GNUNET_GenericReturnValue
+extract_blinded_denom_sig (void *cls,
+ PGresult *result,
+ int row,
+ const char *fname,
+ size_t *dst_size,
+ void *dst)
{
- struct GNUNET_TIME_AbsoluteNBO *udst = dst;
- const struct GNUNET_TIME_AbsoluteNBO *res;
- struct GNUNET_TIME_Absolute tmp;
+ struct TALER_BlindedDenominationSignature *sig = dst;
+ struct GNUNET_CRYPTO_BlindedSignature *bs;
+ size_t len;
+ const char *res;
int fnum;
+ uint32_t be[2];
(void) cls;
+ (void) dst_size;
fnum = PQfnumber (result,
fname);
if (fnum < 0)
@@ -491,45 +738,96 @@ extract_round_time_nbo (void *cls,
if (PQgetisnull (result,
row,
fnum))
+ return GNUNET_NO;
+
+ /* if a field is null, continue but
+ * remember that we now return a different result */
+ len = PQgetlength (result,
+ row,
+ fnum);
+ res = PQgetvalue (result,
+ row,
+ fnum);
+ if (len < sizeof (be))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
- GNUNET_assert (NULL != dst);
- if (sizeof (struct GNUNET_TIME_AbsoluteNBO) != *dst_size)
+ GNUNET_memcpy (&be,
+ res,
+ sizeof (be));
+ if (0x01 != ntohl (be[1])) /* magic marker: blinded */
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
- res = (struct GNUNET_TIME_AbsoluteNBO *) PQgetvalue (result,
- row,
- fnum);
- tmp = GNUNET_TIME_absolute_ntoh (*res);
- GNUNET_break (GNUNET_OK ==
- GNUNET_TIME_round_abs (&tmp));
- *udst = GNUNET_TIME_absolute_hton (tmp);
- return GNUNET_OK;
+ res += sizeof (be);
+ len -= sizeof (be);
+ bs = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
+ bs->rc = 1;
+ bs->cipher = ntohl (be[0]);
+ switch (bs->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ bs->details.blinded_rsa_signature
+ = GNUNET_CRYPTO_rsa_signature_decode (res,
+ len);
+ if (NULL == bs->details.blinded_rsa_signature)
+ {
+ GNUNET_break (0);
+ GNUNET_free (bs);
+ return GNUNET_SYSERR;
+ }
+ sig->blinded_sig = bs;
+ return GNUNET_OK;
+ case GNUNET_CRYPTO_BSA_CS:
+ if (sizeof (bs->details.blinded_cs_answer) != len)
+ {
+ GNUNET_break (0);
+ GNUNET_free (bs);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_memcpy (&bs->details.blinded_cs_answer,
+ res,
+ len);
+ sig->blinded_sig = bs;
+ return GNUNET_OK;
+ }
+ GNUNET_break (0);
+ GNUNET_free (bs);
+ return GNUNET_SYSERR;
}
/**
- * Rounded absolute time in network byte order expected.
- * In contrast to #GNUNET_PQ_query_param_absolute_time_nbo(),
- * this function ensures that the result is rounded and can
- * be converted to JSON.
+ * Function called to clean up memory allocated
+ * by a #GNUNET_PQ_ResultConverter.
*
- * @param name name of the field in the table
- * @param[out] at where to store the result
- * @return array entry for the result specification to use
+ * @param cls closure
+ * @param rd result data to clean up
*/
+static void
+clean_blinded_denom_sig (void *cls,
+ void *rd)
+{
+ struct TALER_BlindedDenominationSignature *denom_sig = rd;
+
+ (void) cls;
+ TALER_blinded_denom_sig_free (denom_sig);
+}
+
+
struct GNUNET_PQ_ResultSpec
-TALER_PQ_result_spec_absolute_time_nbo (const char *name,
- struct GNUNET_TIME_AbsoluteNBO *at)
+TALER_PQ_result_spec_blinded_denom_sig (
+ const char *name,
+ struct TALER_BlindedDenominationSignature *denom_sig)
{
struct GNUNET_PQ_ResultSpec res = {
- .conv = &extract_round_time_nbo,
- .dst = (void *) at,
- .dst_size = sizeof (struct GNUNET_TIME_AbsoluteNBO),
+ .conv = &extract_blinded_denom_sig,
+ .cleaner = &clean_blinded_denom_sig,
+ .dst = (void *) denom_sig,
.fname = name
};
@@ -537,4 +835,743 @@ TALER_PQ_result_spec_absolute_time_nbo (const char *name,
}
+/**
+ * Extract data from a Postgres database @a result at row @a row.
+ *
+ * @param cls closure
+ * @param result where to extract data from
+ * @param row the row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ * #GNUNET_YES if all results could be extracted
+ * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_blinded_planchet (void *cls,
+ PGresult *result,
+ int row,
+ const char *fname,
+ size_t *dst_size,
+ void *dst)
+{
+ struct TALER_BlindedPlanchet *bp = dst;
+ struct GNUNET_CRYPTO_BlindedMessage *bm;
+ size_t len;
+ const char *res;
+ int fnum;
+ uint32_t be[2];
+
+ (void) cls;
+ (void) dst_size;
+ fnum = PQfnumber (result,
+ fname);
+ if (fnum < 0)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (PQgetisnull (result,
+ row,
+ fnum))
+ return GNUNET_NO;
+
+ /* if a field is null, continue but
+ * remember that we now return a different result */
+ len = PQgetlength (result,
+ row,
+ fnum);
+ res = PQgetvalue (result,
+ row,
+ fnum);
+ if (len < sizeof (be))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_memcpy (&be,
+ res,
+ sizeof (be));
+ if (0x0100 != ntohl (be[1])) /* magic marker: blinded */
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ res += sizeof (be);
+ len -= sizeof (be);
+ bm = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
+ bm->rc = 1;
+ bm->cipher = ntohl (be[0]);
+ switch (bm->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ bm->details.rsa_blinded_message.blinded_msg_size
+ = len;
+ bm->details.rsa_blinded_message.blinded_msg
+ = GNUNET_memdup (res,
+ len);
+ bp->blinded_message = bm;
+ return GNUNET_OK;
+ case GNUNET_CRYPTO_BSA_CS:
+ if (sizeof (bm->details.cs_blinded_message) != len)
+ {
+ GNUNET_break (0);
+ GNUNET_free (bm);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_memcpy (&bm->details.cs_blinded_message,
+ res,
+ len);
+ bp->blinded_message = bm;
+ return GNUNET_OK;
+ }
+ GNUNET_break (0);
+ GNUNET_free (bm);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called to clean up memory allocated
+ * by a #GNUNET_PQ_ResultConverter.
+ *
+ * @param cls closure
+ * @param rd result data to clean up
+ */
+static void
+clean_blinded_planchet (void *cls,
+ void *rd)
+{
+ struct TALER_BlindedPlanchet *bp = rd;
+
+ (void) cls;
+ TALER_blinded_planchet_free (bp);
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_blinded_planchet (
+ const char *name,
+ struct TALER_BlindedPlanchet *bp)
+{
+ struct GNUNET_PQ_ResultSpec res = {
+ .conv = &extract_blinded_planchet,
+ .cleaner = &clean_blinded_planchet,
+ .dst = (void *) bp,
+ .fname = name
+ };
+
+ return res;
+}
+
+
+/**
+ * Extract data from a Postgres database @a result at row @a row.
+ *
+ * @param cls closure
+ * @param result where to extract data from
+ * @param row row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ * #GNUNET_YES if all results could be extracted
+ * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_exchange_withdraw_values (void *cls,
+ PGresult *result,
+ int row,
+ const char *fname,
+ size_t *dst_size,
+ void *dst)
+{
+ struct TALER_ExchangeWithdrawValues *alg_values = dst;
+ struct GNUNET_CRYPTO_BlindingInputValues *bi;
+ size_t len;
+ const char *res;
+ int fnum;
+ uint32_t be[2];
+
+ (void) cls;
+ (void) dst_size;
+ fnum = PQfnumber (result,
+ fname);
+ if (fnum < 0)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (PQgetisnull (result,
+ row,
+ fnum))
+ return GNUNET_NO;
+
+ /* if a field is null, continue but
+ * remember that we now return a different result */
+ len = PQgetlength (result,
+ row,
+ fnum);
+ res = PQgetvalue (result,
+ row,
+ fnum);
+ if (len < sizeof (be))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_memcpy (&be,
+ res,
+ sizeof (be));
+ if (0x010000 != ntohl (be[1])) /* magic marker: EWV */
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ res += sizeof (be);
+ len -= sizeof (be);
+ bi = GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues);
+ bi->rc = 1;
+ bi->cipher = ntohl (be[0]);
+ switch (bi->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ if (0 != len)
+ {
+ GNUNET_break (0);
+ GNUNET_free (bi);
+ return GNUNET_SYSERR;
+ }
+ alg_values->blinding_inputs = bi;
+ return GNUNET_OK;
+ case GNUNET_CRYPTO_BSA_CS:
+ if (sizeof (bi->details.cs_values) != len)
+ {
+ GNUNET_break (0);
+ GNUNET_free (bi);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_memcpy (&bi->details.cs_values,
+ res,
+ len);
+ alg_values->blinding_inputs = bi;
+ return GNUNET_OK;
+ }
+ GNUNET_break (0);
+ GNUNET_free (bi);
+ return GNUNET_SYSERR;
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_exchange_withdraw_values (
+ const char *name,
+ struct TALER_ExchangeWithdrawValues *ewv)
+{
+ struct GNUNET_PQ_ResultSpec res = {
+ .conv = &extract_exchange_withdraw_values,
+ .dst = (void *) ewv,
+ .fname = name
+ };
+
+ return res;
+}
+
+
+/**
+ * Closure for the array result specifications. Contains type information
+ * for the generic parser extract_array_generic and out-pointers for the results.
+ */
+struct ArrayResultCls
+{
+ /**
+ * Oid of the expected type, must match the oid in the header of the PQResult struct
+ */
+ Oid oid;
+
+ /**
+ * Target type
+ */
+ enum TALER_PQ_ArrayType typ;
+
+ /**
+ * If not 0, defines the expected size of each entry
+ */
+ size_t same_size;
+
+ /**
+ * Out-pointer to write the number of elements in the array
+ */
+ size_t *num;
+
+ /**
+ * Out-pointer. If @a typ is TALER_PQ_array_of_byte and @a same_size is 0,
+ * allocate and put the array of @a num sizes here. NULL otherwise
+ */
+ size_t **sizes;
+
+ /**
+ * DB_connection, needed for OID-lookup for composite types
+ */
+ const struct GNUNET_PQ_Context *db;
+
+ /**
+ * Currency information for amount composites
+ */
+ char currency[TALER_CURRENCY_LEN];
+};
+
+
+/**
+ * Extract data from a Postgres database @a result as array of a specific type
+ * from row @a row. The type information and optionally additional
+ * out-parameters are given in @a cls which is of type array_result_cls.
+ *
+ * @param cls closure of type array_result_cls
+ * @param result where to extract data from
+ * @param row row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ * #GNUNET_YES if all results could be extracted
+ * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_array_generic (
+ void *cls,
+ PGresult *result,
+ int row,
+ const char *fname,
+ size_t *dst_size,
+ void *dst)
+{
+ const struct ArrayResultCls *info = cls;
+ int data_sz;
+ char *data;
+ void *out = NULL;
+ struct GNUNET_PQ_ArrayHeader_P header;
+ int col_num;
+
+ GNUNET_assert (NULL != dst);
+ *((void **) dst) = NULL;
+
+ #define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto FAIL; \
+ } \
+ } while (0)
+
+ col_num = PQfnumber (result, fname);
+ FAIL_IF (0 > col_num);
+
+ data_sz = PQgetlength (result, row, col_num);
+ FAIL_IF (0 > data_sz);
+ FAIL_IF (sizeof(header) > (size_t) data_sz);
+
+ data = PQgetvalue (result, row, col_num);
+ FAIL_IF (NULL == data);
+
+ {
+ struct GNUNET_PQ_ArrayHeader_P *h =
+ (struct GNUNET_PQ_ArrayHeader_P *) data;
+
+ header.ndim = ntohl (h->ndim);
+ header.has_null = ntohl (h->has_null);
+ header.oid = ntohl (h->oid);
+ header.dim = ntohl (h->dim);
+ header.lbound = ntohl (h->lbound);
+
+ FAIL_IF (1 != header.ndim);
+ FAIL_IF (INT_MAX <= header.dim);
+ FAIL_IF (0 != header.has_null);
+ FAIL_IF (1 != header.lbound);
+ FAIL_IF (info->oid != header.oid);
+ }
+
+ if (NULL != info->num)
+ *info->num = header.dim;
+
+ {
+ char *in = data + sizeof(header);
+
+ switch (info->typ)
+ {
+ case TALER_PQ_array_of_amount:
+ {
+ struct TALER_Amount *amounts;
+ if (NULL != dst_size)
+ *dst_size = sizeof(struct TALER_Amount) * (header.dim);
+
+ amounts = GNUNET_new_array (header.dim,
+ struct TALER_Amount);
+ *((void **) dst) = amounts;
+
+ for (uint32_t i = 0; i < header.dim; i++)
+ {
+ struct TALER_PQ_AmountP ap;
+ struct TALER_Amount *amount = &amounts[i];
+ uint32_t val;
+ size_t sz;
+
+ GNUNET_memcpy (&val,
+ in,
+ sizeof(val));
+ sz = ntohl (val);
+ in += sizeof(val);
+
+ /* total size for this array-entry */
+ FAIL_IF (sizeof(ap) != sz);
+
+ GNUNET_memcpy (&ap,
+ in,
+ sz);
+ FAIL_IF (2 != ntohl (ap.cnt));
+
+ amount->value = GNUNET_ntohll (ap.v);
+ amount->fraction = ntohl (ap.f);
+ GNUNET_memcpy (amount->currency,
+ info->currency,
+ TALER_CURRENCY_LEN);
+
+ in += sizeof(struct TALER_PQ_AmountP);
+ }
+ return GNUNET_OK;
+ }
+ case TALER_PQ_array_of_denom_hash:
+ if (NULL != dst_size)
+ *dst_size = sizeof(struct TALER_DenominationHashP) * (header.dim);
+ out = GNUNET_new_array (header.dim,
+ struct TALER_DenominationHashP);
+ *((void **) dst) = out;
+ for (uint32_t i = 0; i < header.dim; i++)
+ {
+ uint32_t val;
+ size_t sz;
+
+ GNUNET_memcpy (&val,
+ in,
+ sizeof(val));
+ sz = ntohl (val);
+ FAIL_IF (sz != sizeof(struct TALER_DenominationHashP));
+ in += sizeof(uint32_t);
+ *(struct TALER_DenominationHashP *) out =
+ *(struct TALER_DenominationHashP *) in;
+ in += sz;
+ out += sz;
+ }
+ return GNUNET_OK;
+
+ case TALER_PQ_array_of_hash_code:
+ if (NULL != dst_size)
+ *dst_size = sizeof(struct GNUNET_HashCode) * (header.dim);
+ out = GNUNET_new_array (header.dim,
+ struct GNUNET_HashCode);
+ *((void **) dst) = out;
+ for (uint32_t i = 0; i < header.dim; i++)
+ {
+ uint32_t val;
+ size_t sz;
+
+ GNUNET_memcpy (&val,
+ in,
+ sizeof(val));
+ sz = ntohl (val);
+ FAIL_IF (sz != sizeof(struct GNUNET_HashCode));
+ in += sizeof(uint32_t);
+ *(struct GNUNET_HashCode *) out =
+ *(struct GNUNET_HashCode *) in;
+ in += sz;
+ out += sz;
+ }
+ return GNUNET_OK;
+
+ case TALER_PQ_array_of_blinded_coin_hash:
+ if (NULL != dst_size)
+ *dst_size = sizeof(struct TALER_BlindedCoinHashP) * (header.dim);
+ out = GNUNET_new_array (header.dim,
+ struct TALER_BlindedCoinHashP);
+ *((void **) dst) = out;
+ for (uint32_t i = 0; i < header.dim; i++)
+ {
+ uint32_t val;
+ size_t sz;
+
+ GNUNET_memcpy (&val,
+ in,
+ sizeof(val));
+ sz = ntohl (val);
+ FAIL_IF (sz != sizeof(struct TALER_BlindedCoinHashP));
+ in += sizeof(uint32_t);
+ *(struct TALER_BlindedCoinHashP *) out =
+ *(struct TALER_BlindedCoinHashP *) in;
+ in += sz;
+ out += sz;
+ }
+ return GNUNET_OK;
+
+ case TALER_PQ_array_of_blinded_denom_sig:
+ {
+ struct TALER_BlindedDenominationSignature *denom_sigs;
+ if (0 == header.dim)
+ {
+ if (NULL != dst_size)
+ *dst_size = 0;
+ break;
+ }
+
+ denom_sigs = GNUNET_new_array (header.dim,
+ struct TALER_BlindedDenominationSignature);
+ *((void **) dst) = denom_sigs;
+
+ /* copy data */
+ for (uint32_t i = 0; i < header.dim; i++)
+ {
+ struct TALER_BlindedDenominationSignature *denom_sig = &denom_sigs[i];
+ struct GNUNET_CRYPTO_BlindedSignature *bs;
+ uint32_t be[2];
+ uint32_t val;
+ size_t sz;
+
+ GNUNET_memcpy (&val,
+ in,
+ sizeof(val));
+ sz = ntohl (val);
+ FAIL_IF (sizeof(be) > sz);
+
+ in += sizeof(val);
+ GNUNET_memcpy (&be,
+ in,
+ sizeof(be));
+ FAIL_IF (0x01 != ntohl (be[1])); /* magic marker: blinded */
+
+ in += sizeof(be);
+ sz -= sizeof(be);
+ bs = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
+ bs->cipher = ntohl (be[0]);
+ bs->rc = 1;
+ switch (bs->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ bs->details.blinded_rsa_signature
+ = GNUNET_CRYPTO_rsa_signature_decode (in,
+ sz);
+ if (NULL == bs->details.blinded_rsa_signature)
+ {
+ GNUNET_free (bs);
+ FAIL_IF (true);
+ }
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ if (sizeof(bs->details.blinded_cs_answer) != sz)
+ {
+ GNUNET_free (bs);
+ FAIL_IF (true);
+ }
+ GNUNET_memcpy (&bs->details.blinded_cs_answer,
+ in,
+ sz);
+ break;
+ default:
+ GNUNET_free (bs);
+ FAIL_IF (true);
+ }
+ denom_sig->blinded_sig = bs;
+ in += sz;
+ }
+ return GNUNET_OK;
+ }
+ default:
+ FAIL_IF (true);
+ }
+ }
+FAIL:
+ GNUNET_free (*(void **) dst);
+ return GNUNET_SYSERR;
+#undef FAIL_IF
+}
+
+
+/**
+ * Cleanup of the data and closure of an array spec.
+ */
+static void
+array_cleanup (void *cls,
+ void *rd)
+{
+ struct ArrayResultCls *info = cls;
+ void **dst = rd;
+
+ if ((0 == info->same_size) &&
+ (NULL != info->sizes))
+ GNUNET_free (*(info->sizes));
+
+ /* Clean up signatures, if applicable */
+ if (TALER_PQ_array_of_blinded_denom_sig == info->typ)
+ {
+ struct TALER_BlindedDenominationSignature *denom_sigs = *dst;
+ GNUNET_assert (NULL != info->num);
+ for (size_t i = 0; i < *info->num; i++)
+ GNUNET_free (denom_sigs[i].blinded_sig);
+ }
+
+ GNUNET_free (cls);
+ GNUNET_free (*dst);
+ *dst = NULL;
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_array_blinded_denom_sig (
+ struct GNUNET_PQ_Context *db,
+ const char *name,
+ size_t *num,
+ struct TALER_BlindedDenominationSignature **denom_sigs)
+{
+ struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls);
+
+ info->num = num;
+ info->typ = TALER_PQ_array_of_blinded_denom_sig;
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "bytea",
+ &info->oid));
+
+ struct GNUNET_PQ_ResultSpec res = {
+ .conv = extract_array_generic,
+ .cleaner = array_cleanup,
+ .dst = (void *) denom_sigs,
+ .fname = name,
+ .cls = info
+ };
+ return res;
+
+};
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_array_blinded_coin_hash (
+ struct GNUNET_PQ_Context *db,
+ const char *name,
+ size_t *num,
+ struct TALER_BlindedCoinHashP **h_coin_evs)
+{
+ struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls);
+
+ info->num = num;
+ info->typ = TALER_PQ_array_of_blinded_coin_hash;
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "bytea",
+ &info->oid));
+
+ struct GNUNET_PQ_ResultSpec res = {
+ .conv = extract_array_generic,
+ .cleaner = array_cleanup,
+ .dst = (void *) h_coin_evs,
+ .fname = name,
+ .cls = info
+ };
+ return res;
+
+};
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_array_denom_hash (
+ struct GNUNET_PQ_Context *db,
+ const char *name,
+ size_t *num,
+ struct TALER_DenominationHashP **denom_hs)
+{
+ struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls);
+
+ info->num = num;
+ info->typ = TALER_PQ_array_of_denom_hash;
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "bytea",
+ &info->oid));
+
+ struct GNUNET_PQ_ResultSpec res = {
+ .conv = extract_array_generic,
+ .cleaner = array_cleanup,
+ .dst = (void *) denom_hs,
+ .fname = name,
+ .cls = info
+ };
+ return res;
+
+};
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_array_amount (
+ struct GNUNET_PQ_Context *db,
+ const char *name,
+ const char *currency,
+ size_t *num,
+ struct TALER_Amount **amounts)
+{
+ struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls);
+
+ info->num = num;
+ info->typ = TALER_PQ_array_of_amount;
+ info->db = db;
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "taler_amount",
+ &info->oid));
+
+ {
+ size_t clen = GNUNET_MIN (TALER_CURRENCY_LEN - 1,
+ strlen (currency));
+ GNUNET_memcpy (&info->currency,
+ currency,
+ clen);
+ }
+
+ struct GNUNET_PQ_ResultSpec res = {
+ .conv = extract_array_generic,
+ .cleaner = array_cleanup,
+ .dst = (void *) amounts,
+ .fname = name,
+ .cls = info,
+ };
+ return res;
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_array_hash_code (
+ struct GNUNET_PQ_Context *db,
+ const char *name,
+ size_t *num,
+ struct GNUNET_HashCode **hashes)
+{
+ struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls);
+
+ info->num = num;
+ info->typ = TALER_PQ_array_of_hash_code;
+ info->db = db;
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_get_oid_by_name (db,
+ "gnunet_hashcode",
+ &info->oid));
+
+ struct GNUNET_PQ_ResultSpec res = {
+ .conv = extract_array_generic,
+ .cleaner = array_cleanup,
+ .dst = (void *) hashes,
+ .fname = name,
+ .cls = info,
+ };
+ return res;
+}
+
/* end of pq_result_helper.c */
diff --git a/src/pq/test_pq.c b/src/pq/test_pq.c
index 52e92b561..0fd2bfddf 100644
--- a/src/pq/test_pq.c
+++ b/src/pq/test_pq.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2015, 2016 Taler Systems SA
+ (C) 2015, 2016, 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
@@ -21,6 +21,7 @@
#include "platform.h"
#include "taler_util.h"
#include "taler_pq_lib.h"
+#include <gnunet/gnunet_pq_lib.h>
/**
@@ -29,29 +30,29 @@
* @param db database handle to initialize
* @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
*/
-static int
+static enum GNUNET_GenericReturnValue
postgres_prepare (struct GNUNET_PQ_Context *db)
{
struct GNUNET_PQ_PreparedStatement ps[] = {
GNUNET_PQ_make_prepare ("test_insert",
"INSERT INTO test_pq ("
- " hamount_val"
- ",hamount_frac"
- ",namount_val"
- ",namount_frac"
+ " tamount"
",json"
+ ",aamount"
+ ",tamountc"
+ ",hash"
+ ",hashes"
") VALUES "
- "($1, $2, $3, $4, $5);",
- 5),
+ "($1, $2, $3, $4, $5, $6);"),
GNUNET_PQ_make_prepare ("test_select",
"SELECT"
- " hamount_val"
- ",hamount_frac"
- ",namount_val"
- ",namount_frac"
+ " tamount"
",json"
- " FROM test_pq;",
- 0),
+ ",aamount"
+ ",tamountc"
+ ",hash"
+ ",hashes"
+ " FROM test_pq;"),
GNUNET_PQ_PREPARED_STATEMENT_END
};
@@ -68,33 +69,64 @@ postgres_prepare (struct GNUNET_PQ_Context *db)
static int
run_queries (struct GNUNET_PQ_Context *conn)
{
- struct TALER_Amount hamount;
- struct TALER_Amount hamount2;
- struct TALER_AmountNBO namount;
- struct TALER_AmountNBO namount2;
- PGresult *result;
- int ret;
+ struct TALER_Amount tamount;
+ struct TALER_Amount aamount[3];
+ struct TALER_Amount tamountc;
+ struct GNUNET_HashCode hc =
+ {{0xdeadbeef,0xdeadbeef,0xdeadbeef,0xdeadbeef,
+ 0xdeadbeef,0xdeadbeef,0xdeadbeef,0xdeadbeef,
+ 0xdeadbeef,0xdeadbeef,0xdeadbeef,0xdeadbeef,
+ 0xdeadbeef,0xdeadbeef,0xdeadbeef,0xdeadbeef, }};
+ struct GNUNET_HashCode hcs[2] =
+ {{{0xc0feec0f,0xc0feec0f,0xc0feec0f,0xc0feec0f,
+ 0xc0feec0f,0xc0feec0f,0xc0feec0f,0xc0feec0f,
+ 0xc0feec0f,0xc0feec0f,0xc0feec0f,0xc0feec0f,
+ 0xc0feec0f,0xc0feec0f,0xc0feec0f,0xc0feec0f,}},
+ {{0xdeadbeaf,0xdeadbeaf,0xdeadbeaf,0xdeadbeaf,
+ 0xdeadbeaf,0xdeadbeaf,0xdeadbeaf,0xdeadbeaf,
+ 0xdeadbeaf,0xdeadbeaf,0xdeadbeaf,0xdeadbeaf,
+ 0xdeadbeaf,0xdeadbeaf,0xdeadbeaf,0xdeadbeaf,}}};
json_t *json;
- json_t *json2;
GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:5.5",
- &hamount));
- TALER_amount_hton (&namount,
- &hamount);
+ TALER_string_to_amount ("EUR:5.3",
+ &aamount[0]));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("EUR:6.4",
+ &aamount[1]));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("EUR:7.5",
+ &aamount[2]));
GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:4.4",
- &hamount));
+ TALER_string_to_amount ("EUR:7.7",
+ &tamount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("FOO:8.7",
+ &tamountc));
json = json_object ();
- json_object_set_new (json, "foo", json_integer (42));
GNUNET_assert (NULL != json);
+ GNUNET_assert (0 ==
+ json_object_set_new (json,
+ "foo",
+ json_integer (42)));
{
struct GNUNET_PQ_QueryParam params_insert[] = {
- TALER_PQ_query_param_amount (&hamount),
- TALER_PQ_query_param_amount_nbo (&namount),
+ TALER_PQ_query_param_amount (conn,
+ &tamount),
TALER_PQ_query_param_json (json),
+ TALER_PQ_query_param_array_amount (3,
+ aamount,
+ conn),
+ TALER_PQ_query_param_amount_with_currency (conn,
+ &tamountc),
+ GNUNET_PQ_query_param_fixed_size (&hc,
+ sizeof (hc)),
+ TALER_PQ_query_param_array_hash_code (2,
+ hcs,
+ conn),
GNUNET_PQ_query_param_end
};
+ PGresult *result;
result = GNUNET_PQ_exec_prepared (conn,
"test_insert",
@@ -108,55 +140,76 @@ run_queries (struct GNUNET_PQ_Context *conn)
return 1;
}
PQclear (result);
+ json_decref (json);
}
{
+ struct TALER_Amount tamount2;
+ struct TALER_Amount tamountc2;
+ struct TALER_Amount *pamount;
+ struct GNUNET_HashCode hc2;
+ struct GNUNET_HashCode *hcs2;
+ size_t npamount;
+ size_t nhcs;
+ json_t *json2;
struct GNUNET_PQ_QueryParam params_select[] = {
GNUNET_PQ_query_param_end
};
+ struct GNUNET_PQ_ResultSpec results_select[] = {
+ TALER_PQ_result_spec_amount ("tamount",
+ "EUR",
+ &tamount2),
+ TALER_PQ_result_spec_json ("json",
+ &json2),
+ TALER_PQ_result_spec_array_amount (conn,
+ "aamount",
+ "EUR",
+ &npamount,
+ &pamount),
+ TALER_PQ_result_spec_amount_with_currency ("tamountc",
+ &tamountc2),
+ GNUNET_PQ_result_spec_auto_from_type ("hash",
+ &hc2),
+ TALER_PQ_result_spec_array_hash_code (conn,
+ "hashes",
+ &nhcs,
+ &hcs2),
+ GNUNET_PQ_result_spec_end
+ };
- result = GNUNET_PQ_exec_prepared (conn,
- "test_select",
- params_select);
if (1 !=
- PQntuples (result))
+ GNUNET_PQ_eval_prepared_singleton_select (conn,
+ "test_select",
+ params_select,
+ results_select))
{
GNUNET_break (0);
- PQclear (result);
return 1;
}
- }
-
- {
- struct GNUNET_PQ_ResultSpec results_select[] = {
- TALER_PQ_result_spec_amount ("hamount", "EUR", &hamount2),
- TALER_PQ_result_spec_amount_nbo ("namount", "EUR", &namount2),
- TALER_PQ_result_spec_json ("json", &json2),
- GNUNET_PQ_result_spec_end
- };
-
- ret = GNUNET_PQ_extract_result (result,
- results_select,
- 0);
- GNUNET_break (0 ==
- TALER_amount_cmp (&hamount,
- &hamount2));
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("EUR:5.5",
- &hamount));
- TALER_amount_ntoh (&hamount2,
- &namount2);
GNUNET_break (0 ==
- TALER_amount_cmp (&hamount,
- &hamount2));
+ TALER_amount_cmp (&tamount,
+ &tamount2));
GNUNET_break (42 ==
- json_integer_value (json_object_get (json2, "foo")));
+ json_integer_value (json_object_get (json2,
+ "foo")));
+ GNUNET_break (3 == npamount);
+ for (size_t i = 0; i < 3; i++)
+ {
+ GNUNET_break (0 ==
+ TALER_amount_cmp (&aamount[i],
+ &pamount[i]));
+ }
+ GNUNET_break (0 ==
+ TALER_amount_cmp (&tamountc,
+ &tamountc2));
+ GNUNET_break (0 == GNUNET_memcmp (&hc,&hc2));
+ for (size_t i = 0; i < 2; i++)
+ {
+ GNUNET_break (0 ==
+ GNUNET_memcmp (&hcs[i],
+ &hcs2[i]));
+ }
GNUNET_PQ_cleanup_result (results_select);
- PQclear (result);
}
- json_decref (json);
- if (GNUNET_OK != ret)
- return 1;
-
return 0;
}
@@ -166,12 +219,37 @@ main (int argc,
const char *const argv[])
{
struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("DO $$ "
+ " BEGIN"
+ " CREATE DOMAIN gnunet_hashcode AS BYTEA"
+ " CHECK(length(VALUE)=64);"
+ " EXCEPTION"
+ " WHEN duplicate_object THEN null;"
+ " END "
+ "$$;"),
+ GNUNET_PQ_make_execute ("DO $$ "
+ " BEGIN"
+ " CREATE TYPE taler_amount AS"
+ " (val INT8, frac INT4);"
+ " EXCEPTION"
+ " WHEN duplicate_object THEN null;"
+ " END "
+ "$$;"),
+ GNUNET_PQ_make_execute ("DO $$ "
+ " BEGIN"
+ " CREATE TYPE taler_amount_currency AS"
+ " (val INT8, frac INT4, curr VARCHAR(12));"
+ " EXCEPTION"
+ " WHEN duplicate_object THEN null;"
+ " END "
+ "$$;"),
GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS test_pq ("
- " hamount_val INT8 NOT NULL"
- ",hamount_frac INT4 NOT NULL"
- ",namount_val INT8 NOT NULL"
- ",namount_frac INT4 NOT NULL"
+ " tamount taler_amount NOT NULL"
",json VARCHAR NOT NULL"
+ ",aamount taler_amount[]"
+ ",tamountc taler_amount_currency"
+ ",hash gnunet_hashcode"
+ ",hashes gnunet_hashcode[]"
")"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
@@ -196,6 +274,7 @@ main (int argc,
GNUNET_PQ_disconnect (conn);
return 1;
}
+
ret = run_queries (conn);
{
struct GNUNET_PQ_ExecuteStatement ds[] = {
diff --git a/src/sq/Makefile.am b/src/sq/Makefile.am
new file mode 100644
index 000000000..f27dec3d3
--- /dev/null
+++ b/src/sq/Makefile.am
@@ -0,0 +1,40 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS) $(SQLITE_CPPFLAGS)
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+ libtalersq.la
+
+libtalersq_la_SOURCES = \
+ sq_query_helper.c \
+ sq_result_helper.c
+libtalersq_la_LIBADD = \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetutil -ljansson \
+ -lsqlite3 \
+ $(XLIB)
+libtalersq_la_LDFLAGS = \
+ $(SQLITE_LDFLAGS) \
+ -version-info 0:0:0 \
+ -no-undefined
+
+check_PROGRAMS= \
+ test_sq
+
+TESTS = \
+ $(check_PROGRAMS)
+
+test_sq_SOURCES = \
+ test_sq.c
+test_sq_LDADD = \
+ libtalersq.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetsq \
+ -lgnunetutil \
+ -ljansson \
+ -lsqlite3 \
+ $(XLIB)
diff --git a/src/sq/sq_query_helper.c b/src/sq/sq_query_helper.c
new file mode 100644
index 000000000..711e63816
--- /dev/null
+++ b/src/sq/sq_query_helper.c
@@ -0,0 +1,175 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 sq/sq_query_helper.c
+ * @brief helper functions for Taler-specific SQLite3 interactions
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <sqlite3.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_sq_lib.h>
+#include "taler_sq_lib.h"
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @param cls closure
+ * @param data pointer to input argument, here a `struct TALER_Amount`
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param stmt sqlite statement to parameters for
+ * @param off offset of the argument to bind in @a stmt, numbered from 1,
+ * so immediately suitable for passing to `sqlite3_bind`-functions.
+ * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+qconv_amount (void *cls,
+ const void *data,
+ size_t data_len,
+ sqlite3_stmt *stmt,
+ unsigned int off)
+{
+ const struct TALER_Amount *amount = data;
+
+ (void) cls;
+ GNUNET_assert (sizeof (struct TALER_Amount) == data_len);
+ if (SQLITE_OK != sqlite3_bind_int64 (stmt,
+ (int) off,
+ (sqlite3_int64) amount->value))
+ return GNUNET_SYSERR;
+ if (SQLITE_OK != sqlite3_bind_int64 (stmt,
+ (int) off + 1,
+ (sqlite3_int64) amount->fraction))
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+struct GNUNET_SQ_QueryParam
+TALER_SQ_query_param_amount (const struct TALER_Amount *x)
+{
+ struct GNUNET_SQ_QueryParam res = {
+ .conv = &qconv_amount,
+ .data = x,
+ .size = sizeof (*x),
+ .num_params = 2
+ };
+
+ return res;
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @param cls closure
+ * @param data pointer to input argument, here a `struct TALER_AmountNBO`
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param stmt sqlite statement to parameters for
+ * @param off offset of the argument to bind in @a stmt, numbered from 1,
+ * so immediately suitable for passing to `sqlite3_bind`-functions.
+ * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+qconv_amount_nbo (void *cls,
+ const void *data,
+ size_t data_len,
+ sqlite3_stmt *stmt,
+ unsigned int off)
+{
+ const struct TALER_AmountNBO *amount = data;
+ struct TALER_Amount amount_hbo;
+
+ (void) cls;
+ (void) data_len;
+ TALER_amount_ntoh (&amount_hbo,
+ amount);
+ return qconv_amount (cls,
+ &amount_hbo,
+ sizeof (struct TALER_Amount),
+ stmt,
+ off);
+}
+
+
+struct GNUNET_SQ_QueryParam
+TALER_SQ_query_param_amount_nbo (const struct TALER_AmountNBO *x)
+{
+ struct GNUNET_SQ_QueryParam res = {
+ .conv = &qconv_amount_nbo,
+ .data = x,
+ .size = sizeof (*x),
+ .num_params = 2
+ };
+
+ return res;
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @param cls closure
+ * @param data pointer to input argument, here a `struct TALER_Amount`
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param stmt sqlite statement to parameters for
+ * @param off offset of the argument to bind in @a stmt, numbered from 1,
+ * so immediately suitable for passing to `sqlite3_bind`-functions.
+ * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+qconv_json (void *cls,
+ const void *data,
+ size_t data_len,
+ sqlite3_stmt *stmt,
+ unsigned int off)
+{
+ const json_t *json = data;
+ char *str;
+
+ (void) cls;
+ (void) data_len;
+ str = json_dumps (json, JSON_COMPACT);
+ if (NULL == str)
+ return GNUNET_SYSERR;
+
+ if (SQLITE_OK != sqlite3_bind_text (stmt,
+ (int) off,
+ str,
+ strlen (str),
+ SQLITE_TRANSIENT))
+ return GNUNET_SYSERR;
+ GNUNET_free (str);
+ return GNUNET_OK;
+}
+
+
+struct GNUNET_SQ_QueryParam
+TALER_SQ_query_param_json (const json_t *x)
+{
+ struct GNUNET_SQ_QueryParam res = {
+ .conv = &qconv_json,
+ .data = x,
+ .size = sizeof (*x),
+ .num_params = 1
+ };
+
+ return res;
+}
+
+
+/* end of sq/sq_query_helper.c */
diff --git a/src/sq/sq_result_helper.c b/src/sq/sq_result_helper.c
new file mode 100644
index 000000000..9d80837bd
--- /dev/null
+++ b/src/sq/sq_result_helper.c
@@ -0,0 +1,237 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 sq/sq_result_helper.c
+ * @brief functions to initialize parameter arrays
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <sqlite3.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_sq_lib.h>
+#include "taler_sq_lib.h"
+#include "taler_util.h"
+
+
+/**
+ * Extract amount data from a SQLite database
+ *
+ * @param cls closure, a `const char *` giving the currency
+ * @param result where to extract data from
+ * @param column column to extract data from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ * #GNUNET_YES if all results could be extracted
+ * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_amount (void *cls,
+ sqlite3_stmt *result,
+ unsigned int column,
+ size_t *dst_size,
+ void *dst)
+{
+ struct TALER_Amount *amount = dst;
+ const char *currency = cls;
+ if ((sizeof (struct TALER_Amount) != *dst_size) ||
+ (SQLITE_INTEGER != sqlite3_column_type (result,
+ (int) column)) ||
+ (SQLITE_INTEGER != sqlite3_column_type (result,
+ (int) column + 1)))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_strlcpy (amount->currency,
+ currency,
+ TALER_CURRENCY_LEN);
+ amount->value = (uint64_t) sqlite3_column_int64 (result,
+ (int) column);
+ uint64_t frac = (uint64_t) sqlite3_column_int64 (result,
+ (int) column + 1);
+ amount->fraction = (uint32_t) frac;
+ return GNUNET_YES;
+}
+
+
+struct GNUNET_SQ_ResultSpec
+TALER_SQ_result_spec_amount (const char *currency,
+ struct TALER_Amount *amount)
+{
+ struct GNUNET_SQ_ResultSpec res = {
+ .conv = &extract_amount,
+ .cls = (void *) currency,
+ .dst = (void *) amount,
+ .dst_size = sizeof (struct TALER_Amount),
+ .num_params = 2
+ };
+
+ return res;
+}
+
+
+/**
+ * Extract amount data from a SQLite database
+ *
+ * @param cls closure, a `const char *` giving the currency
+ * @param result where to extract data from
+ * @param column column to extract data from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ * #GNUNET_YES if all results could be extracted
+ * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_amount_nbo (void *cls,
+ sqlite3_stmt *result,
+ unsigned int column,
+ size_t *dst_size,
+ void *dst)
+{
+ struct TALER_AmountNBO *amount = dst;
+ struct TALER_Amount amount_hbo;
+ size_t amount_hbo_size = sizeof (struct TALER_Amount);
+
+ (void) cls;
+ (void) dst_size;
+ if (GNUNET_YES !=
+ extract_amount (cls,
+ result,
+ column,
+ &amount_hbo_size,
+ &amount_hbo))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ TALER_amount_hton (amount,
+ &amount_hbo);
+ return GNUNET_YES;
+}
+
+
+struct GNUNET_SQ_ResultSpec
+TALER_SQ_result_spec_amount_nbo (const char *currency,
+ struct TALER_AmountNBO *amount)
+{
+ struct GNUNET_SQ_ResultSpec res = {
+ .conv = &extract_amount_nbo,
+ .cls = (void *) currency,
+ .dst = (void *) amount,
+ .dst_size = sizeof (struct TALER_AmountNBO),
+ .num_params = 2
+ };
+
+ return res;
+}
+
+
+/**
+ * Extract amount data from a SQLite database
+ *
+ * @param cls closure
+ * @param result where to extract data from
+ * @param column column to extract data from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ * #GNUNET_YES if all results could be extracted
+ * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_json (void *cls,
+ sqlite3_stmt *result,
+ unsigned int column,
+ size_t *dst_size,
+ void *dst)
+{
+ json_t **j_dst = dst;
+ const char *res;
+ json_error_t json_error;
+ size_t slen;
+
+ (void) cls;
+ (void) dst_size;
+ if (SQLITE_TEXT != sqlite3_column_type (result,
+ column))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ res = (const char *) sqlite3_column_text (result,
+ column);
+ slen = strlen (res);
+ *j_dst = json_loadb (res,
+ slen,
+ JSON_REJECT_DUPLICATES,
+ &json_error);
+ if (NULL == *j_dst)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse JSON result for column %d: %s (%s)\n",
+ column,
+ json_error.text,
+ json_error.source);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called to clean up memory allocated
+ * by a #GNUNET_SQ_ResultConverter.
+ *
+ * @param cls closure
+ */
+static void
+clean_json (void *cls)
+{
+ json_t **dst = cls;
+
+ (void) cls;
+ if (NULL != *dst)
+ {
+ json_decref (*dst);
+ *dst = NULL;
+ }
+}
+
+
+/**
+ * json_t expected.
+ *
+ * @param[out] jp where to store the result
+ * @return array entry for the result specification to use
+ */
+struct GNUNET_SQ_ResultSpec
+TALER_SQ_result_spec_json (json_t **jp)
+{
+ struct GNUNET_SQ_ResultSpec res = {
+ .conv = &extract_json,
+ .cleaner = &clean_json,
+ .dst = (void *) jp,
+ .cls = (void *) jp,
+ .num_params = 1
+ };
+
+ return res;
+}
+
+
+/* end of sq/sq_result_helper.c */
diff --git a/src/sq/test_sq.c b/src/sq/test_sq.c
new file mode 100644
index 000000000..8f464faf3
--- /dev/null
+++ b/src/sq/test_sq.c
@@ -0,0 +1,215 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 sq/test_sq.c
+ * @brief Tests for SQLite3 convenience API
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include "taler_sq_lib.h"
+
+
+/**
+ * Run actual test queries.
+ *
+ * @return 0 on success
+ */
+static int
+run_queries (sqlite3 *db)
+{
+ struct TALER_Amount hamount;
+ struct TALER_AmountNBO namount;
+ json_t *json;
+ sqlite3_stmt *test_insert;
+ sqlite3_stmt *test_select;
+ struct GNUNET_SQ_PrepareStatement ps[] = {
+ GNUNET_SQ_make_prepare ("INSERT INTO test_sq ("
+ " hamount_val"
+ ",hamount_frac"
+ ",namount_val"
+ ",namount_frac"
+ ",json"
+ ") VALUES "
+ "($1, $2, $3, $4, $5)",
+ &test_insert),
+ GNUNET_SQ_make_prepare ("SELECT"
+ " hamount_val"
+ ",hamount_frac"
+ ",namount_val"
+ ",namount_frac"
+ ",json"
+ " FROM test_sq",
+ &test_select),
+ GNUNET_SQ_PREPARE_END
+ };
+ int ret = 0;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("EUR:1.23",
+ &hamount));
+ TALER_amount_hton (&namount,
+ &hamount);
+ json = json_object ();
+ GNUNET_assert (NULL != json);
+ GNUNET_assert (0 ==
+ json_object_set_new (json,
+ "foo",
+ json_integer (42)));
+ GNUNET_assert (NULL != json);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_SQ_prepare (db,
+ ps));
+
+ {
+ struct GNUNET_SQ_QueryParam params_insert[] = {
+ TALER_SQ_query_param_amount (&hamount),
+ TALER_SQ_query_param_amount_nbo (&namount),
+ TALER_SQ_query_param_json (json),
+ GNUNET_SQ_query_param_end
+ };
+ GNUNET_SQ_reset (db,
+ test_insert);
+ GNUNET_assert (GNUNET_OK == GNUNET_SQ_bind (test_insert,
+ params_insert));
+ GNUNET_assert (SQLITE_DONE == sqlite3_step (test_insert));
+ sqlite3_finalize (test_insert);
+ }
+
+ {
+ struct TALER_Amount result_amount;
+ struct TALER_AmountNBO nresult_amount;
+ struct TALER_Amount nresult_amount_converted;
+ json_t *result_json;
+ struct GNUNET_SQ_QueryParam params_select[] = {
+ GNUNET_SQ_query_param_end
+ };
+ struct GNUNET_SQ_ResultSpec results_select[] = {
+ TALER_SQ_result_spec_amount ("EUR",
+ &result_amount),
+ TALER_SQ_result_spec_amount_nbo ("EUR",
+ &nresult_amount),
+ TALER_SQ_result_spec_json (&result_json),
+ GNUNET_SQ_result_spec_end
+ };
+
+ GNUNET_SQ_reset (db,
+ test_select);
+ GNUNET_assert (GNUNET_OK == GNUNET_SQ_bind (test_select,
+ params_select));
+ GNUNET_assert (SQLITE_ROW == sqlite3_step (test_select));
+
+ GNUNET_assert (GNUNET_OK == GNUNET_SQ_extract_result (test_select,
+ results_select));
+ TALER_amount_ntoh (&nresult_amount_converted,
+ &nresult_amount);
+ if ((GNUNET_OK != TALER_amount_cmp_currency (&hamount,
+ &result_amount)) ||
+ (0 != TALER_amount_cmp (&hamount,
+ &result_amount)) ||
+ (GNUNET_OK != TALER_amount_cmp_currency (&hamount,
+ &nresult_amount_converted)) ||
+ (0 != TALER_amount_cmp (&hamount,
+ &nresult_amount_converted)) ||
+ (1 != json_equal (json,
+ result_json)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Result from database doesn't match input\n");
+ ret = 1;
+ }
+ GNUNET_SQ_cleanup_result (results_select);
+ sqlite3_finalize (test_select);
+ }
+ json_decref (json);
+
+ return ret;
+}
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ struct GNUNET_SQ_ExecuteStatement es[] = {
+ GNUNET_SQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS test_sq ("
+ " hamount_val INT8 NOT NULL"
+ ",hamount_frac INT8 NOT NULL"
+ ",namount_val INT8 NOT NULL"
+ ",namount_frac INT8 NOT NULL"
+ ",json VARCHAR NOT NULL"
+ ")"),
+ GNUNET_SQ_EXECUTE_STATEMENT_END
+ };
+ sqlite3 *db;
+ int ret;
+
+ (void) argc;
+ (void) argv;
+ GNUNET_log_setup ("test-pq",
+ "WARNING",
+ NULL);
+
+ if (SQLITE_OK != sqlite3_open ("talercheck.db",
+ &db))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to open SQLite3 database\n");
+ return 77;
+ }
+
+ if (GNUNET_OK != GNUNET_SQ_exec_statements (db,
+ es))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to create new table\n");
+ if ((SQLITE_OK != sqlite3_close (db)) ||
+ (0 != unlink ("talercheck.db")))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to close db or unlink\n");
+ }
+ return 1;
+ }
+
+ ret = run_queries (db);
+
+ if (SQLITE_OK !=
+ sqlite3_exec (db,
+ "DROP TABLE test_sq",
+ NULL, NULL, NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to drop table\n");
+ ret = 1;
+ }
+
+ if (SQLITE_OK != sqlite3_close (db))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to close database\n");
+ ret = 1;
+ }
+ if (0 != unlink ("talercheck.db"))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to unlink test database file\n");
+ ret = 1;
+ }
+ return ret;
+}
+
+
+/* end of sq/test_sq.c */
diff --git a/src/templating/.gitignore b/src/templating/.gitignore
new file mode 100644
index 000000000..9ed2f3ff9
--- /dev/null
+++ b/src/templating/.gitignore
@@ -0,0 +1,3 @@
+test_mustach_jansson
+taler-mustach-tool
+mustach
diff --git a/src/templating/AUTHORS b/src/templating/AUTHORS
new file mode 100644
index 000000000..110b36981
--- /dev/null
+++ b/src/templating/AUTHORS
@@ -0,0 +1,38 @@
+Main author:
+ José Bollo <jobol@nonadev.net>
+
+Contributors:
+ Abhishek Mishra
+ Atlas
+ Ben Beasley
+ Christian Grothoff
+ Dominik Kummer
+ Gabriel Zachmann
+ Harold L Marzan
+ Kurt Jung
+ Lailton Fernando Mariano
+ Lucas Ramage
+ Paramtamtam
+ RekGRpth
+ Ryan Fox
+ Sami Kerola
+ Sijmen J. Mulder
+ Steve-Chavez
+ Tomasz Sieprawski
+
+Packagers:
+ pkgsrc: Sijmen J. Mulder
+ alpine linux: Lucas Ramage
+ RPM & DEB: Marcus Hardt
+
+Thanks to issue submitters:
+ Dante Torres
+ @fabbe
+ Felix von Leitner
+ Johann Oskarsson
+ Mark Bucciarelli
+ Nigel Hathaway
+ Paul Wisehart
+ Tarik @tr001
+ Thierry Fournier
+ SASU OKFT
diff --git a/src/templating/CHANGELOG.md b/src/templating/CHANGELOG.md
new file mode 100644
index 000000000..003652ebf
--- /dev/null
+++ b/src/templating/CHANGELOG.md
@@ -0,0 +1,161 @@
+1.2.7 (2024-03-21)
+------------------
+
+New:
+ - fallback to default when mustach_wrap_get_partial
+ returns MUSTACH_ERROR_PARTIAL_NOT_FOUND
+ - remove at compile time the load of files for templates
+ if MUSTACH_LOAD_TEMPLATE is defined as 0
+ - add compile time flag MUSTACH_SAFE for enforcing
+ safety behaviours
+
+Fix:
+ - selection of subitem by index (#47)
+ - get latest iterated key when getting key name (#52)
+ - allow tests without valgrind
+ - avoid recursive template expansion (#55)
+
+1.2.6 (2024-01-08)
+------------------
+
+Fix:
+ - improve naming (#42)
+ - magical spaces in recursive partials (#43)
+ - installation when tool isn't built
+ - correct detection of falsey values (#45)
+
+Minor:
+ - update to newer reference tests
+
+1.2.5 (2023-02-18)
+------------------
+
+Fix:
+ - Don't override CFLAGS in Makefile
+ - Use of $(INSTALL) in Makefile for setting options
+
+Minor:
+ - Orthograf of 'instantiate'
+
+1.2.4 (2023-01-02)
+------------------
+
+Fix:
+ - Latent SIGSEGV using cJSON
+
+1.2.3 (2022-12-21)
+------------------
+
+New:
+ - Flag Mustach_With_ErrorUndefined (and option --strict for the tool)
+ for returning a requested tag is not defined
+ - Test of specifications in separate directory
+
+Fix:
+ - Version printing is now okay
+ - Compiling libraries on Darwin (no soname but install_name)
+ - Compiling test6 with correct flags
+ - Update test from specifications
+ - Better use of valgrind reports
+
+1.2.2 (2021-10-28)
+------------------
+
+Fix:
+ - SONAME of libmustach-json-c.so
+
+1.2.1 (2021-10-19)
+------------------
+
+New:
+ - Add SONAME in libraries.
+ - Flag Mustach_With_PartialDataFirst to switch the
+ policy of resolving partials.
+
+Fix:
+ - Identification of types in cJSON
+
+1.2.0 (2021-08-24)
+------------------
+
+New:
+ - Add hook 'mustach_wrap_get_partial' for handling partials.
+ - Add test of mustache specifications https://github.com/mustache/spec.
+
+Changes:
+ - Mustach_With_SingleDot is always set.
+ - Mustach_With_IncPartial is always set.
+ - Mustach_With_AllExtensions is changed to use currently known extensions.
+ - Output of tests changed.
+ - Makefile improved.
+ - Partials are first searched as file then in current selection.
+ - Improved management of delimiters.
+
+Fixes:
+ - Improved output accordingly to https://github.com/mustache/spec:
+ - escaping of quote "
+ - interpolating null with empty string
+ - removal of empty lines with standalone tag
+ - don't enter section if null
+ - indentation of partials
+ - comment improved for get of mustach_wrap_itf.
+
+1.1.1 (2021-08-19)
+------------------
+Fixes:
+ - Avoid conflicting with getopt.
+ - Remove unexpected build artifact.
+ - Handle correctly a size of 0.
+
+1.1.0 (2021-05-01)
+------------------
+New:
+ - API refactored to take lengths to ease working with partial or
+ non-NULL-terminated strings. (ABI break)
+
+Fixes:
+ - Use correct int type for jansson (json_int_t instead of int64_t).
+ - JSON output of different backends is now the same.
+
+1.0 (2021-04-28, retracted)
+---------------------------
+Legal:
+ - License changed to ISC.
+
+Fixes:
+ - Possible data leak in memfile_open() by clearing buffers.
+ - Fix build on Solaris-likes by including alloca.h.
+ - Fix Windows build by including malloc.h, using size_t instead of
+ ssize_t, and using the standard ternary operator syntax.
+ - Fix JSON in test3 by using double quote characters.
+ - Fix installation in alternative directories such as
+ /opt/pkg/lib on macOS by setting install_name.
+ - Normalise return values in compare() implementations.
+
+New:
+ - Support for cJSON and jansson libraries.
+ - Version info now embedded at build time and shown with mustach(1)
+ usage.
+ - Versioned so-names (e.g. libxlsx.so.1.0).
+ - BINDIR, LIBDIR and INCLUDEDIR variables in Makefile.
+ - New mustach-wrap.{c,h} to ease implementation new libraries,
+ extracted and refactored from the existing implementations.
+ - Makefile now supports 3 modes: single libmustach (default), split
+ libmustache-core etc, and both.
+ - Any or all backends (json-c, jansson, etc) can be enabled at compile
+ time. By default, all available libraries are used.
+ - mustach(1) can use any JSON backend instead of only json-c.
+ - MUSTACH_COMPATIBLE_0_99 can be defined for backwards source
+ compatibility.
+ - 'No extensions' can now be set Mustach_With_NoExtensions instead of
+ passing 0.
+ - pkgconfig (.pc) file for library.
+ - Manual page for mustach(1).
+
+Changed:
+ - Many renames.
+ - Maximum tag length increased from 1024 to 4096.
+ - Other headers include json-c.h instead of using forward declarations.
+ - mustach(1) reads from /dev/stdin instead of fd 0.
+ - Several structures are now taken as const.
+ - New/changed Makefile targets.
diff --git a/src/templating/LICENSE.txt b/src/templating/LICENSE.txt
new file mode 100644
index 000000000..495aeefd5
--- /dev/null
+++ b/src/templating/LICENSE.txt
@@ -0,0 +1,14 @@
+
+Copyright (c) 2017-2020 by José Bollo
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
+OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
diff --git a/src/templating/Makefile.am b/src/templating/Makefile.am
new file mode 100644
index 000000000..a79b109d1
--- /dev/null
+++ b/src/templating/Makefile.am
@@ -0,0 +1,132 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS)
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+noinst_PROGRAMS = \
+ taler-mustach-tool
+
+taler_mustach_tool_SOURCES = \
+ mustach-tool.c \
+ mustach-jansson.h
+taler_mustach_tool_LDADD = \
+ libmustach.la \
+ -ljansson
+taler_mustach_tool_CFLAGS = \
+ -DTOOL=MUSTACH_TOOL_JANSSON \
+ -DMUSTACH_SAFE=1 \
+ -DMUSTACH_LOAD_TEMPLATE=0
+
+lib_LTLIBRARIES = \
+ libtalertemplating.la
+
+noinst_LTLIBRARIES = \
+ libmustach.la
+
+libtalertemplating_la_SOURCES = \
+ mustach.c mustach.h \
+ mustach-wrap.c mustach-wrap.h \
+ mustach-jansson.c mustach-jansson.h \
+ templating_api.c
+libtalertemplating_la_LIBADD = \
+ $(top_builddir)/src/mhd/libtalermhd.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lmicrohttpd \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+libtalertemplating_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+libtalertemplating_la_CFLAGS = \
+ -DMUSTACH_SAFE=1 \
+ -DMUSTACH_LOAD_TEMPLATE=0
+
+libmustach_la_SOURCES = \
+ mustach.c mustach.h \
+ mustach-wrap.c mustach-wrap.h \
+ mustach-jansson.c mustach-jansson.h
+
+test_mustach_jansson_SOURCES = \
+ test_mustach_jansson.c
+test_mustach_jansson_LDADD = \
+ -lgnunetutil \
+ -ljansson \
+ -lmustach \
+ $(XLIB)
+
+check_PROGRAMS = \
+ test_mustach_jansson
+
+TESTS = $(check_PROGRAMS)
+
+EXTRA_DIST = \
+ $(check_SCRIPTS) \
+ mustach-original-Makefile \
+ mustach.1.gz \
+ mustach.1.scd \
+ meson.build \
+ LICENSE.txt \
+ ORIGIN \
+ pkgcfgs \
+ README.md \
+ dotest.sh \
+ AUTHORS \
+ CHANGELOG.md \
+ mustach-json-c.h \
+ mustach-json-c.c \
+ mustach-cjson.h \
+ mustach-cjson.c \
+ test1/json \
+ test1/Makefile \
+ test1/must \
+ test1/resu.ref \
+ test1/vg.ref \
+ test2/json \
+ test2/Makefile \
+ test2/must \
+ test2/resu.ref \
+ test2/vg.ref \
+ test3/json \
+ test3/Makefile \
+ test3/must \
+ test3/resu.ref \
+ test3/vg.ref \
+ test4/json \
+ test4/Makefile \
+ test4/must \
+ test4/resu.ref \
+ test4/vg.ref \
+ test5/json \
+ test5/Makefile \
+ test5/must \
+ test5/must2 \
+ test5/must2.mustache \
+ test5/must3.mustache \
+ test5/resu.ref \
+ test5/vg.ref \
+ test6/json \
+ test6/Makefile \
+ test6/must \
+ test6/resu.ref \
+ test6/test-custom-write.c \
+ test6/vg.ref \
+ test7/base.mustache \
+ test7/json \
+ test7/Makefile \
+ test7/node.mustache \
+ test7/resu.ref \
+ test7/vg.ref \
+ test8/json \
+ test8/Makefile \
+ test8/must \
+ test8/resu.ref \
+ test8/vg.ref \
+ test-specs/test-specs.c \
+ test-specs/test-specs-cjson.ref \
+ test-specs/test-specs-jansson.ref \
+ test-specs/test-specs-json-c.ref
diff --git a/src/templating/ORIGIN b/src/templating/ORIGIN
new file mode 100644
index 000000000..14902983a
--- /dev/null
+++ b/src/templating/ORIGIN
@@ -0,0 +1,11 @@
+Cloned originally from https://gitlab.com/jobol/mustach/
+
+Changes:
+========
+
+Renamed original Makefile to mustach-original-Makefile
+and wrote Makefile.am for us.
+
+Added run-original-tests.sh shell script as a wrapper around
+mustach-original-Makefile to use the original build process for the test
+suite.
diff --git a/src/templating/README.md b/src/templating/README.md
new file mode 100644
index 000000000..6e7a6c956
--- /dev/null
+++ b/src/templating/README.md
@@ -0,0 +1,320 @@
+# Introduction to Mustach 1.2
+
+`mustach` is a C implementation of the [mustache](http://mustache.github.io "main site for mustache")
+template specification.
+
+The main site for `mustach` is on [gitlab](https://gitlab.com/jobol/mustach).
+
+The simplest way to use mustach is to copy the files **mustach.h** and **mustach.c**
+directly into your project and use it.
+
+If you are using one of the JSON libraries listed below, you can get extended feature
+by also including **mustach-wrap.h**, **mustach-wrap.c**, **mustach-XXX.h** and
+**mustach-XXX.c** in your project (see below for **XXX**)
+
+- [json-c](https://github.com/json-c/json-c): use **XXX** = **json-c**
+- [jansson](http://www.digip.org/jansson/): use **XXX** = **jansson**
+- [cJSON](https://github.com/DaveGamble/cJSON): use **XXX** = **cjson**
+
+Alternatively, make and meson files are provided for building `mustach` and
+`libmustach.so` shared library.
+
+Since version 1.0, the makefile allows to compile and install different
+flavours. See below for details.
+
+## Distributions offering mustach package
+
+### Alpine Linux
+
+```sh
+apk add mustach
+apk add mustach-lib
+apk add mustach-dev
+```
+
+### NetBSD
+
+```sh
+cd devel/mustach
+make
+```
+
+See http://pkgsrc.se/devel/mustach
+
+## Known projects using Mustach
+
+This [wiki page](https://gitlab.com/jobol/mustach/-/wikis/projects-using-mustach)
+lists the known project that are using mustach and that kindly told it.
+
+Don't hesitate to tell us if you are interested to be listed there.
+
+## Using Mustach from sources
+
+The file **mustach.h** is the main documentation. Look at it.
+
+The current source files are:
+
+- **mustach.c** core implementation of mustache in C
+- **mustach.h** header file for core definitions
+- **mustach-wrap.c** generic wrapper of mustach for easier integration
+- **mustach-wrap.h** header file for using mustach-wrap
+- **mustach-json-c.c** tiny json wrapper of mustach using [json-c](https://github.com/json-c/json-c)
+- **mustach-json-c.h** header file for using the tiny json-c wrapper
+- **mustach-cjson.c** tiny json wrapper of mustach using [cJSON](https://github.com/DaveGamble/cJSON)
+- **mustach-cjson.h** header file for using the tiny cJSON wrapper
+- **mustach-jansson.c** tiny json wrapper of mustach using [jansson](https://www.digip.org/jansson/)
+- **mustach-jansson.h** header file for using the tiny jansson wrapper
+- **mustach-tool.c** simple tool for applying template files to one JSON file
+
+The file **mustach-json-c.c** is the historical example of use of **mustach** and
+**mustach-wrap** core and it is also a practical implementation that can be used.
+It uses the library json-c. (NOTE for Mac OS: available through homebrew).
+
+Since version 1.0, the project also provide integration of other JSON libraries:
+**cJSON** and **jansson**.
+
+*If you integrate a new library with* **mustach**, *your contribution will be
+welcome here*.
+
+The tool **mustach** is build using `make`, its usage is:
+
+ mustach json template [template]...
+
+It then outputs the result of applying the templates files to the JSON file.
+
+### Portability
+
+Some system does not provide *open_memstream*. In that case, tell your
+preferred compiler to declare the preprocessor symbol **NO_OPEN_MEMSTREAM**.
+Example:
+
+ CFLAGS=-DNO_OPEN_MEMSTREAM make
+
+### Integration
+
+The files **mustach.h** and **mustach-wrap.h** are the main documentation. Look at it.
+
+The file **mustach-json-c.c** provides a good example of integration.
+
+If you intend to use basic HTML/XML escaping and standard C FILE, the callbacks
+of the interface **mustach_itf** that you have to implement are:
+`enter`, `next`, `leave`, `get`.
+
+If you intend to use specific escaping and/or specific output, the callbacks
+of the interface **mustach_itf** that you have to implement are:
+`enter`, `next`, `leave`, `get` and `emit`.
+
+### Compilation Using Make
+
+Building and installing can be done using make.
+
+Example:
+
+ $ make tool=cjson libs=none PREFIX=/usr/local DESTDIR=/ install
+ $ make tool=jsonc libs=single PREFIX=/ DESTDIR=$HOME/.local install
+
+The makefile knows following switches (\*: default):
+
+ Switch name | Values | Description
+ --------------+---------+-----------------------------------------------
+ jsonc | (unset) | Auto detection of json-c
+ | no | Don't compile for json-c
+ | yes | Compile for json-c that must exist
+ --------------+---------+-----------------------------------------------
+ cjson | (unset) | Auto detection of cJSON
+ | no | Don't compile for cJSON
+ | yes | Compile for cJSON that must exist
+ --------------+---------+-----------------------------------------------
+ jansson | (unset) | Auto detection of jansson
+ | no | Don't compile for jansson
+ | yes | Compile for jansson that must exist
+ --------------+---------+-----------------------------------------------
+ tool | (unset) | Auto detection
+ | cjson | Use cjson library
+ | jsonc | Use jsonc library
+ | jansson | Use jansson library
+ | none | Don't compile the tool
+ --------------+---------+----------------------------------------------
+ libs | (unset) | Like 'all'
+ | all | Like 'single' AND 'split'
+ | single | Only libmustach.so
+ | split | All the possible libmustach-XXX.so ...
+ | none | No library is produced
+
+The libraries that can be produced are:
+
+ Library name | Content
+ --------------------+--------------------------------------------------------
+ libmustach-core | mustach.c mustach-wrap.c
+ libmustach-cjson | mustach.c mustach-wrap.c mustach-cjson.c
+ libmustach-jsonc | mustach.c mustach-wrap.c mustach-json-c.c
+ libmustach-jansson | mustach.c mustach-wrap.c mustach-jansson.c
+ libmustach | mustach.c mustach-wrap.c mustach-{cjson,json-c,jansson}.c
+
+There is no dependencies of a library to an other. This is intended and doesn't
+hurt today because the code is small.
+
+### Testing
+
+The makefile offers the way to execute basic tests. Just type `make test`.
+
+By default, if valgrind is available, tests are using it. It can be disabled
+by typing `make test valgrind=no` or `NOVALGRIND=1 make test`.
+
+## Extensions
+
+The current implementation provides extensions to specifications of **mustache**.
+This extensions can be activated or deactivated using flags.
+
+Here is the summary.
+
+ Flag name | Description
+ -------------------------------+------------------------------------------------
+ Mustach_With_Colon | Explicit tag substitution with colon
+ Mustach_With_EmptyTag | Empty Tag Allowed
+ -------------------------------+------------------------------------------------
+ Mustach_With_Equal | Value Testing Equality
+ Mustach_With_Compare | Value Comparing
+ Mustach_With_JsonPointer | Interpret JSON Pointers
+ Mustach_With_ObjectIter | Iteration On Objects
+ Mustach_With_EscFirstCmp | Escape First Compare
+ Mustach_With_ErrorUndefined | Error when a requested tag is undefined
+ -------------------------------+------------------------------------------------
+ Mustach_With_AllExtensions | Activate all known extensions
+ Mustach_With_NoExtensions | Disable any extension
+
+For the details, see below.
+
+### Explicit Tag Substitution With Colon (Mustach_With_Colon)
+
+In somecases the name of the key used for substitution begins with a
+character reserved for mustach: one of `#`, `^`, `/`, `&`, `{`, `>` and `=`.
+
+This extension introduces the special character `:` to explicitly
+tell mustach to just substitute the value. So `:` becomes a new special
+character.
+
+This is a core extension implemented in file **mustach.c**.
+
+### Empty Tag Allowed (Mustach_With_EmptyTag)
+
+When an empty tag is found, instead of automatically raising the error
+MUSTACH\_ERROR\_EMPTY\_TAG pass it.
+
+This is a core extension implemented in file **mustach.c**.
+
+### Value Testing Equality (Mustach_With_Equal)
+
+This extension allows you to test the value of the selected key.
+It allows to write `key=value` (matching test) or `key=!value`
+(not matching test) in any query.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Value Comparing (Mustach_With_Compare)
+
+These extension extends the extension for testing equality to also
+compare values if greater or lesser.
+Its allows to write `key>value` (greater), `key>=value` (greater or equal),
+`key<value` (lesser) and `key<=value` (lesser or equal).
+
+It the comparator sign appears in the first column it is ignored
+as if it was escaped.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Interpret JSON Pointers (Mustach_With_JsonPointer)
+
+This extension allows to use JSON pointers as defined in IETF RFC 6901.
+If active, any key starting with "/" is a JSON pointer.
+This implies to use the colon to introduce JSON keys.
+
+A special escaping is used for `=`, `<`, `>` signs when
+values comparisons are enabled: `~=` gives `=` in the key.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Iteration On Objects (Mustach_With_ObjectIter)
+
+With this extension, using the pattern `{{#X.*}}...{{/X.*}}`
+allows to iterate on fields of `X`.
+
+Example:
+
+- `{{s.*}} {{*}}:{{.}}{{/s.*}}` applied on `{"s":{"a":1,"b":true}}` produces ` a:1 b:true`
+
+Here the single star `{{*}}` is replaced by the iterated key
+and the single dot `{{.}}` is replaced by its value.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Error when a requested tag is undefined (Mustach_With_ErrorUndefined)
+
+Report the error MUSTACH_ERROR_UNDEFINED_TAG when a requested tag
+is not defined.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Access To Current Value
+
+*this was an extension but is now always enforced*
+
+The value of the current field can be accessed using single dot.
+
+Examples:
+
+- `{{#key}}{{.}}{{/key}}` applied to `{"key":3.14}` produces `3.14`
+- `{{#array}} {{.}}{{/array}}` applied to `{"array":[1,2]}` produces ` 1 2`.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Partial Data First
+
+*this was an extension but is now always enforced*
+
+The default resolution for partial pattern like `{{> name}}`
+is to search for `name` in the current json context and
+as a file named `name` or if not found `name.mustache`.
+
+By default, the order of the search is (1) as a file,
+and if not found, (2) in the current json context.
+
+When this option is set, the order is reverted and content
+of partial is search (1) in the current json context,
+and if not found, (2) as a file.
+
+That option is useful to keep the compatibility with
+versions of *mustach* anteriors to 1.2.0.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Escape First Compare
+
+This extension automatically escapes comparisons appears as
+first characters.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+## Difference with version 0.99 and previous
+
+### Extensions
+
+The extensions can no more be removed at compile time, use
+flags to select your required extension on need.
+
+### Name of functions
+
+Names of functions were improved. Old names remain but are obsolete
+and legacy. Their removal in far future versions is possible.
+
+The table below summarize the changes.
+
+ legacy name | name since version 1.0.0
+ ------------------+-----------------------
+ fmustach | mustach_file
+ fdmustach | mustach_fd
+ mustach | mustach_mem
+ fmustach_json_c | mustach_json_c_file
+ fdmustach_json_c | mustach_json_c_fd
+ mustach_json_c | mustach_json_c_mem
+ mustach_json_c | mustach_json_c_write
diff --git a/src/templating/dotest.sh b/src/templating/dotest.sh
new file mode 100755
index 000000000..32f575c2e
--- /dev/null
+++ b/src/templating/dotest.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# Exit, with error message (hard failure)
+exit_fail() {
+ echo " FAIL: " "$@" >&2
+ exit 1
+}
+
+mustach="${mustach:-../mustach}"
+echo "starting test"
+if ! valgrind --version 2> /dev/null
+then
+ $mustach "$@" > resu.last || exit_fail "ERROR! mustach command failed ($?)!"
+else
+ valgrind $mustach "$@" > resu.last 2> vg.last || exit_fail "ERROR! valgrind + mustach command failed ($?)!"
+ sed -i 's:^==[0-9]*== ::' vg.last
+ awk '/^ *total heap usage: .* allocs, .* frees,.*/{if($$4-$$6)exit(1)}' vg.last || exit_fail "ERROR! Alloc/Free issue"
+fi
+if diff -w resu.ref resu.last
+then
+ echo "result ok"
+else
+ exit_fail "ERROR! Result differs"
+fi
+echo
+exit 0
diff --git a/src/templating/meson.build b/src/templating/meson.build
new file mode 100644
index 000000000..c7ecc8dfc
--- /dev/null
+++ b/src/templating/meson.build
@@ -0,0 +1,12 @@
+project('mustach', 'c',
+ version: '1.0.0'
+)
+
+mustach_inc = include_directories('.')
+mustach_lib = shared_library('mustach',
+ 'mustach.c',
+ include_directories: mustach_inc
+)
+
+mustach_dep = declare_dependency(link_with: mustach_lib,
+ include_directories: mustach_inc)
diff --git a/src/templating/mustach-cjson.c b/src/templating/mustach-cjson.c
new file mode 100644
index 000000000..ee65c8038
--- /dev/null
+++ b/src/templating/mustach-cjson.c
@@ -0,0 +1,258 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "mustach.h"
+#include "mustach-wrap.h"
+#include "mustach-cjson.h"
+
+struct expl {
+ cJSON null;
+ cJSON *root;
+ cJSON *selection;
+ int depth;
+ struct {
+ cJSON *cont;
+ cJSON *obj;
+ cJSON *next;
+ int is_objiter;
+ } stack[MUSTACH_MAX_DEPTH];
+};
+
+static int start(void *closure)
+{
+ struct expl *e = closure;
+ e->depth = 0;
+ memset(&e->null, 0, sizeof e->null);
+ e->null.type = cJSON_NULL;
+ e->selection = &e->null;
+ e->stack[0].cont = NULL;
+ e->stack[0].obj = e->root;
+ return MUSTACH_OK;
+}
+
+static int compare(void *closure, const char *value)
+{
+ struct expl *e = closure;
+ cJSON *o = e->selection;
+ double d;
+
+ if (cJSON_IsNumber(o)) {
+ d = o->valuedouble - atof(value);
+ return d < 0 ? -1 : d > 0 ? 1 : 0;
+ } else if (cJSON_IsString(o)) {
+ return strcmp(o->valuestring, value);
+ } else if (cJSON_IsTrue(o)) {
+ return strcmp("true", value);
+ } else if (cJSON_IsFalse(o)) {
+ return strcmp("false", value);
+ } else if (cJSON_IsNull(o)) {
+ return strcmp("null", value);
+ } else {
+ return 1;
+ }
+}
+
+static int sel(void *closure, const char *name)
+{
+ struct expl *e = closure;
+ cJSON *o;
+ int i, r;
+
+ if (name == NULL) {
+ o = e->stack[e->depth].obj;
+ r = 1;
+ } else {
+ i = e->depth;
+ while (i >= 0 && !(o = cJSON_GetObjectItemCaseSensitive(e->stack[i].obj, name)))
+ i--;
+ if (i >= 0)
+ r = 1;
+ else {
+ o = &e->null;
+ r = 0;
+ }
+ }
+ e->selection = o;
+ return r;
+}
+
+static int subsel(void *closure, const char *name)
+{
+ struct expl *e = closure;
+ cJSON *o = NULL;
+ int r = 0;
+
+ if (cJSON_IsObject(e->selection)) {
+ o = cJSON_GetObjectItemCaseSensitive(e->selection, name);
+ r = o != NULL;
+ }
+ else if (cJSON_IsArray(e->selection) && *name) {
+ char *end;
+ int idx = (int)strtol(name, &end, 10);
+ if (!*end && idx >= 0 && idx < cJSON_GetArraySize(e->selection)) {
+ o = cJSON_GetArrayItem(e->selection, idx);
+ r = 1;
+ }
+ }
+ if (r)
+ e->selection = o;
+ return r;
+}
+
+static int enter(void *closure, int objiter)
+{
+ struct expl *e = closure;
+ cJSON *o;
+
+ if (++e->depth >= MUSTACH_MAX_DEPTH)
+ return MUSTACH_ERROR_TOO_DEEP;
+
+ o = e->selection;
+ e->stack[e->depth].is_objiter = 0;
+ if (objiter) {
+ if (! cJSON_IsObject(o))
+ goto not_entering;
+ if (o->child == NULL)
+ goto not_entering;
+ e->stack[e->depth].obj = o->child;
+ e->stack[e->depth].next = o->child->next;
+ e->stack[e->depth].cont = o;
+ e->stack[e->depth].is_objiter = 1;
+ } else if (cJSON_IsArray(o)) {
+ if (o->child == NULL)
+ goto not_entering;
+ e->stack[e->depth].obj = o->child;
+ e->stack[e->depth].next = o->child->next;
+ e->stack[e->depth].cont = o;
+ } else if ((cJSON_IsObject(o) && o->child != NULL)
+ || cJSON_IsTrue(o)
+ || (cJSON_IsString(o) && cJSON_GetStringValue(o)[0] != '\0')
+ || (cJSON_IsNumber(o) && cJSON_GetNumberValue(o) != 0)) {
+ e->stack[e->depth].obj = o;
+ e->stack[e->depth].cont = NULL;
+ e->stack[e->depth].next = NULL;
+ } else
+ goto not_entering;
+ return 1;
+
+not_entering:
+ e->depth--;
+ return 0;
+}
+
+static int next(void *closure)
+{
+ struct expl *e = closure;
+ cJSON *o;
+
+ if (e->depth <= 0)
+ return MUSTACH_ERROR_CLOSING;
+
+ o = e->stack[e->depth].next;
+ if (o == NULL)
+ return 0;
+
+ e->stack[e->depth].obj = o;
+ e->stack[e->depth].next = o->next;
+ return 1;
+}
+
+static int leave(void *closure)
+{
+ struct expl *e = closure;
+
+ if (e->depth <= 0)
+ return MUSTACH_ERROR_CLOSING;
+
+ e->depth--;
+ return 0;
+}
+
+static int get(void *closure, struct mustach_sbuf *sbuf, int key)
+{
+ struct expl *e = closure;
+ const char *s;
+ int d;
+
+ if (key) {
+ s = "";
+ for (d = e->depth ; d >= 0 ; d--)
+ if (e->stack[d].is_objiter) {
+ s = e->stack[d].obj->string;
+ break;
+ }
+ }
+ else if (cJSON_IsString(e->selection))
+ s = e->selection->valuestring;
+ else if (cJSON_IsNull(e->selection))
+ s = "";
+ else {
+ s = cJSON_PrintUnformatted(e->selection);
+ if (s == NULL)
+ return MUSTACH_ERROR_SYSTEM;
+ sbuf->freecb = cJSON_free;
+ }
+ sbuf->value = s;
+ return 1;
+}
+
+const struct mustach_wrap_itf mustach_cJSON_wrap_itf = {
+ .start = start,
+ .stop = NULL,
+ .compare = compare,
+ .sel = sel,
+ .subsel = subsel,
+ .enter = enter,
+ .next = next,
+ .leave = leave,
+ .get = get
+};
+
+int mustach_cJSON_file(const char *template, size_t length, cJSON *root, int flags, FILE *file)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_file(template, length, &mustach_cJSON_wrap_itf, &e, flags, file);
+}
+
+int mustach_cJSON_fd(const char *template, size_t length, cJSON *root, int flags, int fd)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_fd(template, length, &mustach_cJSON_wrap_itf, &e, flags, fd);
+}
+
+int mustach_cJSON_mem(const char *template, size_t length, cJSON *root, int flags, char **result, size_t *size)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_mem(template, length, &mustach_cJSON_wrap_itf, &e, flags, result, size);
+}
+
+int mustach_cJSON_write(const char *template, size_t length, cJSON *root, int flags, mustach_write_cb_t *writecb, void *closure)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_write(template, length, &mustach_cJSON_wrap_itf, &e, flags, writecb, closure);
+}
+
+int mustach_cJSON_emit(const char *template, size_t length, cJSON *root, int flags, mustach_emit_cb_t *emitcb, void *closure)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_emit(template, length, &mustach_cJSON_wrap_itf, &e, flags, emitcb, closure);
+}
+
diff --git a/src/templating/mustach-cjson.h b/src/templating/mustach-cjson.h
new file mode 100644
index 000000000..e049415f8
--- /dev/null
+++ b/src/templating/mustach-cjson.h
@@ -0,0 +1,96 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _mustach_cJSON_h_included_
+#define _mustach_cJSON_h_included_
+
+/*
+ * mustach-cjson is intended to make integration of cJSON
+ * library by providing integrated functions.
+ */
+
+#include <cjson/cJSON.h>
+#include "mustach-wrap.h"
+
+/**
+ * Wrap interface used internally by mustach cJSON functions.
+ * Can be used for overriding behaviour.
+ */
+extern const struct mustach_wrap_itf mustach_cJSON_wrap_itf;
+
+/**
+ * mustach_cJSON_file - Renders the mustache 'template' in 'file' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_cJSON_file(const char *template, size_t length, cJSON *root, int flags, FILE *file);
+
+/**
+ * mustach_cJSON_fd - Renders the mustache 'template' in 'fd' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_cJSON_fd(const char *template, size_t length, cJSON *root, int flags, int fd);
+
+
+/**
+ * mustach_cJSON_mem - Renders the mustache 'template' in 'result' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_cJSON_mem(const char *template, size_t length, cJSON *root, int flags, char **result, size_t *size);
+
+/**
+ * mustach_cJSON_write - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @writecb: the function that write values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_cJSON_write(const char *template, size_t length, cJSON *root, int flags, mustach_write_cb_t *writecb, void *closure);
+
+/**
+ * mustach_cJSON_emit - Renders the mustache 'template' for 'root' to custom emiter 'emitcb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @emitcb: the function that emit values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_cJSON_emit(const char *template, size_t length, cJSON *root, int flags, mustach_emit_cb_t *emitcb, void *closure);
+
+#endif
+
diff --git a/src/templating/mustach-jansson.c b/src/templating/mustach-jansson.c
new file mode 100644
index 000000000..d9b50b57e
--- /dev/null
+++ b/src/templating/mustach-jansson.c
@@ -0,0 +1,271 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include "mustach.h"
+#include "mustach-wrap.h"
+#include "mustach-jansson.h"
+
+struct expl {
+ json_t *root;
+ json_t *selection;
+ int depth;
+ struct {
+ json_t *cont;
+ json_t *obj;
+ void *iter;
+ int is_objiter;
+ size_t index, count;
+ } stack[MUSTACH_MAX_DEPTH];
+};
+
+static int start(void *closure)
+{
+ struct expl *e = closure;
+ e->depth = 0;
+ e->selection = json_null();
+ e->stack[0].cont = NULL;
+ e->stack[0].obj = e->root;
+ e->stack[0].index = 0;
+ e->stack[0].count = 1;
+ return MUSTACH_OK;
+}
+
+static int compare(void *closure, const char *value)
+{
+ struct expl *e = closure;
+ json_t *o = e->selection;
+ double d;
+ json_int_t i;
+
+ switch (json_typeof(o)) {
+ case JSON_REAL:
+ d = json_number_value(o) - atof(value);
+ return d < 0 ? -1 : d > 0 ? 1 : 0;
+ case JSON_INTEGER:
+ i = (json_int_t)json_integer_value(o) - (json_int_t)atoll(value);
+ return i < 0 ? -1 : i > 0 ? 1 : 0;
+ case JSON_STRING:
+ return strcmp(json_string_value(o), value);
+ case JSON_TRUE:
+ return strcmp("true", value);
+ case JSON_FALSE:
+ return strcmp("false", value);
+ case JSON_NULL:
+ return strcmp("null", value);
+ default:
+ return 1;
+ }
+}
+
+static int sel(void *closure, const char *name)
+{
+ struct expl *e = closure;
+ json_t *o;
+ int i, r;
+
+ if (name == NULL) {
+ o = e->stack[e->depth].obj;
+ r = 1;
+ } else {
+ i = e->depth;
+ while (i >= 0 && !(o = json_object_get(e->stack[i].obj, name)))
+ i--;
+ if (i >= 0)
+ r = 1;
+ else {
+ o = json_null();
+ r = 0;
+ }
+ }
+ e->selection = o;
+ return r;
+}
+
+static int subsel(void *closure, const char *name)
+{
+ struct expl *e = closure;
+ json_t *o = NULL;
+ int r = 0;
+
+ if (json_is_object(e->selection)) {
+ o = json_object_get(e->selection, name);
+ r = o != NULL;
+ }
+ else if (json_is_array(e->selection)) {
+ char *end;
+ size_t idx = (size_t)strtol(name, &end, 10);
+ if (!*end && idx < json_array_size(e->selection)) {
+ o = json_array_get(e->selection, idx);
+ r = 1;
+ }
+ }
+ if (r)
+ e->selection = o;
+ return r;
+}
+
+static int enter(void *closure, int objiter)
+{
+ struct expl *e = closure;
+ json_t *o;
+
+ if (++e->depth >= MUSTACH_MAX_DEPTH)
+ return MUSTACH_ERROR_TOO_DEEP;
+
+ o = e->selection;
+ e->stack[e->depth].is_objiter = 0;
+ if (objiter) {
+ if (!json_is_object(o))
+ goto not_entering;
+ e->stack[e->depth].iter = json_object_iter(o);
+ if (e->stack[e->depth].iter == NULL)
+ goto not_entering;
+ e->stack[e->depth].obj = json_object_iter_value(e->stack[e->depth].iter);
+ e->stack[e->depth].cont = o;
+ e->stack[e->depth].is_objiter = 1;
+ } else if (json_is_array(o)) {
+ e->stack[e->depth].count = json_array_size(o);
+ if (e->stack[e->depth].count == 0)
+ goto not_entering;
+ e->stack[e->depth].cont = o;
+ e->stack[e->depth].obj = json_array_get(o, 0);
+ e->stack[e->depth].index = 0;
+ } else if ((json_is_object(o) && json_object_size(o))
+ || json_is_true(o)
+ || (json_is_string(o) && json_string_length(o) > 0)
+ || (json_is_integer(o) && json_integer_value(o) != 0)
+ || (json_is_real(o) && json_real_value(o) != 0)) {
+ e->stack[e->depth].count = 1;
+ e->stack[e->depth].cont = NULL;
+ e->stack[e->depth].obj = o;
+ e->stack[e->depth].index = 0;
+ } else
+ goto not_entering;
+ return 1;
+
+not_entering:
+ e->depth--;
+ return 0;
+}
+
+static int next(void *closure)
+{
+ struct expl *e = closure;
+
+ if (e->depth <= 0)
+ return MUSTACH_ERROR_CLOSING;
+
+ if (e->stack[e->depth].is_objiter) {
+ e->stack[e->depth].iter = json_object_iter_next(e->stack[e->depth].cont, e->stack[e->depth].iter);
+ if (e->stack[e->depth].iter == NULL)
+ return 0;
+ e->stack[e->depth].obj = json_object_iter_value(e->stack[e->depth].iter);
+ return 1;
+ }
+
+ e->stack[e->depth].index++;
+ if (e->stack[e->depth].index >= e->stack[e->depth].count)
+ return 0;
+
+ e->stack[e->depth].obj = json_array_get(e->stack[e->depth].cont, e->stack[e->depth].index);
+ return 1;
+}
+
+static int leave(void *closure)
+{
+ struct expl *e = closure;
+
+ if (e->depth <= 0)
+ return MUSTACH_ERROR_CLOSING;
+
+ e->depth--;
+ return 0;
+}
+
+static int get(void *closure, struct mustach_sbuf *sbuf, int key)
+{
+ struct expl *e = closure;
+ const char *s;
+ int d;
+
+ if (key) {
+ s = "";
+ for (d = e->depth ; d >= 0 ; d--)
+ if (e->stack[d].is_objiter) {
+ s = json_object_iter_key(e->stack[d].iter);
+ break;
+ }
+ }
+ else if (json_is_string(e->selection))
+ s = json_string_value(e->selection);
+ else if (json_is_null(e->selection))
+ s = "";
+ else {
+ s = json_dumps(e->selection, JSON_ENCODE_ANY | JSON_COMPACT);
+ if (s == NULL)
+ return MUSTACH_ERROR_SYSTEM;
+ sbuf->freecb = free;
+ }
+ sbuf->value = s;
+ return 1;
+}
+
+const struct mustach_wrap_itf mustach_jansson_wrap_itf = {
+ .start = start,
+ .stop = NULL,
+ .compare = compare,
+ .sel = sel,
+ .subsel = subsel,
+ .enter = enter,
+ .next = next,
+ .leave = leave,
+ .get = get
+};
+
+int mustach_jansson_file(const char *template, size_t length, json_t *root, int flags, FILE *file)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_file(template, length, &mustach_jansson_wrap_itf, &e, flags, file);
+}
+
+int mustach_jansson_fd(const char *template, size_t length, json_t *root, int flags, int fd)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_fd(template, length, &mustach_jansson_wrap_itf, &e, flags, fd);
+}
+
+int mustach_jansson_mem(const char *template, size_t length, json_t *root, int flags, char **result, size_t *size)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_mem(template, length, &mustach_jansson_wrap_itf, &e, flags, result, size);
+}
+
+int mustach_jansson_write(const char *template, size_t length, json_t *root, int flags, mustach_write_cb_t *writecb, void *closure)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_write(template, length, &mustach_jansson_wrap_itf, &e, flags, writecb, closure);
+}
+
+int mustach_jansson_emit(const char *template, size_t length, json_t *root, int flags, mustach_emit_cb_t *emitcb, void *closure)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_emit(template, length, &mustach_jansson_wrap_itf, &e, flags, emitcb, closure);
+}
+
diff --git a/src/templating/mustach-jansson.h b/src/templating/mustach-jansson.h
new file mode 100644
index 000000000..8def948e0
--- /dev/null
+++ b/src/templating/mustach-jansson.h
@@ -0,0 +1,96 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _mustach_jansson_h_included_
+#define _mustach_jansson_h_included_
+
+/*
+ * mustach-jansson is intended to make integration of jansson
+ * library by providing integrated functions.
+ */
+
+#include <jansson.h>
+#include "mustach-wrap.h"
+
+/**
+ * Wrap interface used internally by mustach jansson functions.
+ * Can be used for overriding behaviour.
+ */
+extern const struct mustach_wrap_itf mustach_jansson_wrap_itf;
+
+/**
+ * mustach_jansson_file - Renders the mustache 'template' in 'file' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_jansson_file(const char *template, size_t length, json_t *root, int flags, FILE *file);
+
+/**
+ * mustach_jansson_fd - Renders the mustache 'template' in 'fd' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_jansson_fd(const char *template, size_t length, json_t *root, int flags, int fd);
+
+
+/**
+ * mustach_jansson_mem - Renders the mustache 'template' in 'result' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_jansson_mem(const char *template, size_t length, json_t *root, int flags, char **result, size_t *size);
+
+/**
+ * mustach_jansson_write - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @writecb: the function that write values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_jansson_write(const char *template, size_t length, json_t *root, int flags, mustach_write_cb_t *writecb, void *closure);
+
+/**
+ * mustach_jansson_emit - Renders the mustache 'template' for 'root' to custom emiter 'emitcb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @emitcb: the function that emit values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_jansson_emit(const char *template, size_t length, json_t *root, int flags, mustach_emit_cb_t *emitcb, void *closure);
+
+#endif
+
diff --git a/src/templating/mustach-json-c.c b/src/templating/mustach-json-c.c
new file mode 100644
index 000000000..75251c07e
--- /dev/null
+++ b/src/templating/mustach-json-c.c
@@ -0,0 +1,284 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include "mustach.h"
+#include "mustach-wrap.h"
+#include "mustach-json-c.h"
+
+struct expl {
+ struct json_object *root;
+ struct json_object *selection;
+ int depth;
+ struct {
+ struct json_object *cont;
+ struct json_object *obj;
+ struct json_object_iterator iter;
+ struct json_object_iterator enditer;
+ int is_objiter;
+ int index, count;
+ } stack[MUSTACH_MAX_DEPTH];
+};
+
+static int start(void *closure)
+{
+ struct expl *e = closure;
+ e->depth = 0;
+ e->selection = NULL;
+ e->stack[0].cont = NULL;
+ e->stack[0].obj = e->root;
+ e->stack[0].index = 0;
+ e->stack[0].count = 1;
+ return MUSTACH_OK;
+}
+
+static int compare(void *closure, const char *value)
+{
+ struct expl *e = closure;
+ struct json_object *o = e->selection;
+ double d;
+ int64_t i;
+
+ switch (json_object_get_type(o)) {
+ case json_type_double:
+ d = json_object_get_double(o) - atof(value);
+ return d < 0 ? -1 : d > 0 ? 1 : 0;
+ case json_type_int:
+ i = json_object_get_int64(o) - (int64_t)atoll(value);
+ return i < 0 ? -1 : i > 0 ? 1 : 0;
+ default:
+ return strcmp(json_object_get_string(o), value);
+ }
+}
+
+static int sel(void *closure, const char *name)
+{
+ struct expl *e = closure;
+ struct json_object *o;
+ int i, r;
+
+ if (name == NULL) {
+ o = e->stack[e->depth].obj;
+ r = 1;
+ } else {
+ i = e->depth;
+ while (i >= 0 && !json_object_object_get_ex(e->stack[i].obj, name, &o))
+ i--;
+ if (i >= 0)
+ r = 1;
+ else {
+ o = NULL;
+ r = 0;
+ }
+ }
+ e->selection = o;
+ return r;
+}
+
+static int subsel(void *closure, const char *name)
+{
+ struct expl *e = closure;
+ struct json_object *o = NULL;
+ int r = 0;
+
+ if (json_object_is_type(e->selection, json_type_object))
+ r = json_object_object_get_ex(e->selection, name, &o);
+ else if (json_object_is_type(e->selection, json_type_array)) {
+ char *end;
+ size_t idx = (size_t)strtol(name, &end, 10);
+ if (!*end && idx < json_object_array_length(e->selection)) {
+ o = json_object_array_get_idx(e->selection, idx);
+ r = 1;
+ }
+ }
+ if (r)
+ e->selection = o;
+ return r;
+}
+
+static int enter(void *closure, int objiter)
+{
+ struct expl *e = closure;
+ struct json_object *o;
+
+ if (++e->depth >= MUSTACH_MAX_DEPTH)
+ return MUSTACH_ERROR_TOO_DEEP;
+
+ o = e->selection;
+ e->stack[e->depth].is_objiter = 0;
+ if (objiter) {
+ if (!json_object_is_type(o, json_type_object))
+ goto not_entering;
+
+ e->stack[e->depth].iter = json_object_iter_begin(o);
+ e->stack[e->depth].enditer = json_object_iter_end(o);
+ if (json_object_iter_equal(&e->stack[e->depth].iter, &e->stack[e->depth].enditer))
+ goto not_entering;
+ e->stack[e->depth].obj = json_object_iter_peek_value(&e->stack[e->depth].iter);
+ e->stack[e->depth].cont = o;
+ e->stack[e->depth].is_objiter = 1;
+ } else if (json_object_is_type(o, json_type_array)) {
+ e->stack[e->depth].count = json_object_array_length(o);
+ if (e->stack[e->depth].count == 0)
+ goto not_entering;
+ e->stack[e->depth].cont = o;
+ e->stack[e->depth].obj = json_object_array_get_idx(o, 0);
+ e->stack[e->depth].index = 0;
+ } else if ((json_object_is_type(o, json_type_object) && json_object_object_length(o) > 0)
+ || json_object_get_boolean(o)) {
+ e->stack[e->depth].count = 1;
+ e->stack[e->depth].cont = NULL;
+ e->stack[e->depth].obj = o;
+ e->stack[e->depth].index = 0;
+ } else
+ goto not_entering;
+ return 1;
+
+not_entering:
+ e->depth--;
+ return 0;
+}
+
+static int next(void *closure)
+{
+ struct expl *e = closure;
+
+ if (e->depth <= 0)
+ return MUSTACH_ERROR_CLOSING;
+
+ if (e->stack[e->depth].is_objiter) {
+ json_object_iter_next(&e->stack[e->depth].iter);
+ if (json_object_iter_equal(&e->stack[e->depth].iter, &e->stack[e->depth].enditer))
+ return 0;
+ e->stack[e->depth].obj = json_object_iter_peek_value(&e->stack[e->depth].iter);
+ return 1;
+ }
+
+ e->stack[e->depth].index++;
+ if (e->stack[e->depth].index >= e->stack[e->depth].count)
+ return 0;
+
+ e->stack[e->depth].obj = json_object_array_get_idx(e->stack[e->depth].cont, e->stack[e->depth].index);
+ return 1;
+}
+
+static int leave(void *closure)
+{
+ struct expl *e = closure;
+
+ if (e->depth <= 0)
+ return MUSTACH_ERROR_CLOSING;
+
+ e->depth--;
+ return 0;
+}
+
+static int get(void *closure, struct mustach_sbuf *sbuf, int key)
+{
+ struct expl *e = closure;
+ const char *s;
+ int d;
+
+ if (key) {
+ s = "";
+ for (d = e->depth ; d >= 0 ; d--)
+ if (e->stack[d].is_objiter) {
+ s = json_object_iter_peek_name(&e->stack[d].iter);
+ break;
+ }
+ }
+ else
+ switch (json_object_get_type(e->selection)) {
+ case json_type_string:
+ s = json_object_get_string(e->selection);
+ break;
+ case json_type_null:
+ s = "";
+ break;
+ default:
+ s = json_object_to_json_string_ext(e->selection, 0);
+ break;
+ }
+ sbuf->value = s;
+ return 1;
+}
+
+const struct mustach_wrap_itf mustach_json_c_wrap_itf = {
+ .start = start,
+ .stop = NULL,
+ .compare = compare,
+ .sel = sel,
+ .subsel = subsel,
+ .enter = enter,
+ .next = next,
+ .leave = leave,
+ .get = get
+};
+
+int mustach_json_c_file(const char *template, size_t length, struct json_object *root, int flags, FILE *file)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_file(template, length, &mustach_json_c_wrap_itf, &e, flags, file);
+}
+
+int mustach_json_c_fd(const char *template, size_t length, struct json_object *root, int flags, int fd)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_fd(template, length, &mustach_json_c_wrap_itf, &e, flags, fd);
+}
+
+int mustach_json_c_mem(const char *template, size_t length, struct json_object *root, int flags, char **result, size_t *size)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_mem(template, length, &mustach_json_c_wrap_itf, &e, flags, result, size);
+}
+
+int mustach_json_c_write(const char *template, size_t length, struct json_object *root, int flags, mustach_write_cb_t *writecb, void *closure)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_write(template, length, &mustach_json_c_wrap_itf, &e, flags, writecb, closure);
+}
+
+int mustach_json_c_emit(const char *template, size_t length, struct json_object *root, int flags, mustach_emit_cb_t *emitcb, void *closure)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_emit(template, length, &mustach_json_c_wrap_itf, &e, flags, emitcb, closure);
+}
+
+int fmustach_json_c(const char *template, struct json_object *root, FILE *file)
+{
+ return mustach_json_c_file(template, 0, root, -1, file);
+}
+
+int fdmustach_json_c(const char *template, struct json_object *root, int fd)
+{
+ return mustach_json_c_fd(template, 0, root, -1, fd);
+}
+
+int mustach_json_c(const char *template, struct json_object *root, char **result, size_t *size)
+{
+ return mustach_json_c_mem(template, 0, root, -1, result, size);
+}
+
+int umustach_json_c(const char *template, struct json_object *root, mustach_write_cb_t *writecb, void *closure)
+{
+ return mustach_json_c_write(template, 0, root, -1, writecb, closure);
+}
+
+
diff --git a/src/templating/mustach-json-c.h b/src/templating/mustach-json-c.h
new file mode 100644
index 000000000..50846c6cb
--- /dev/null
+++ b/src/templating/mustach-json-c.h
@@ -0,0 +1,160 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _mustach_json_c_h_included_
+#define _mustach_json_c_h_included_
+
+/*
+ * mustach-json-c is intended to make integration of json-c
+ * library by providing integrated functions.
+ */
+
+#include <json-c/json.h>
+#include "mustach-wrap.h"
+
+/**
+ * Wrap interface used internally by mustach json-c functions.
+ * Can be used for overriding behaviour.
+ */
+extern const struct mustach_wrap_itf mustach_json_c_wrap_itf;
+
+/**
+ * mustach_json_c_file - Renders the mustache 'template' in 'file' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_json_c_file(const char *template, size_t length, struct json_object *root, int flags, FILE *file);
+
+/**
+ * mustach_json_c_fd - Renders the mustache 'template' in 'fd' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_json_c_fd(const char *template, size_t length, struct json_object *root, int flags, int fd);
+
+/**
+ * mustach_json_c_mem - Renders the mustache 'template' in 'result' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_json_c_mem(const char *template, size_t length, struct json_object *root, int flags, char **result, size_t *size);
+
+/**
+ * mustach_json_c_write - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @writecb: the function that write values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_json_c_write(const char *template, size_t length, struct json_object *root, int flags, mustach_write_cb_t *writecb, void *closure);
+
+/**
+ * mustach_json_c_emit - Renders the mustache 'template' for 'root' to custom emiter 'emitcb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @emitcb: the function that emit values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_json_c_emit(const char *template, size_t length, struct json_object *root, int flags, mustach_emit_cb_t *emitcb, void *closure);
+
+/***************************************************************************
+* compatibility with version before 1.0
+*/
+
+/**
+ * OBSOLETE use mustach_json_c_file
+ *
+ * fmustach_json_c - Renders the mustache 'template' in 'file' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @root: the root json object to render
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+
+DEPRECATED_MUSTACH(extern int fmustach_json_c(const char *template, struct json_object *root, FILE *file));
+
+/**
+ * OBSOLETE use mustach_json_c_fd
+ *
+ * fdmustach_json_c - Renders the mustache 'template' in 'fd' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @root: the root json object to render
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+
+DEPRECATED_MUSTACH(extern int fdmustach_json_c(const char *template, struct json_object *root, int fd));
+
+/**
+ * OBSOLETE use mustach_json_c_mem
+ *
+ * mustach_json_c - Renders the mustache 'template' in 'result' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @root: the root json object to render
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+
+DEPRECATED_MUSTACH(extern int mustach_json_c(const char *template, struct json_object *root, char **result, size_t *size));
+
+/**
+ * OBSOLETE use mustach_json_c_write
+ *
+ * umustach_json_c - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @root: the root json object to render
+ * @writecb: the function that write values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+typedef mustach_write_cb_t *mustach_json_write_cb;
+DEPRECATED_MUSTACH(extern int umustach_json_c(const char *template, struct json_object *root, mustach_write_cb_t *writecb, void *closure));
+
+#endif
diff --git a/src/templating/mustach-original-Makefile b/src/templating/mustach-original-Makefile
new file mode 100644
index 000000000..aee827583
--- /dev/null
+++ b/src/templating/mustach-original-Makefile
@@ -0,0 +1,305 @@
+# version
+MAJOR := 1
+MINOR := 2
+REVIS := 7
+
+# installation settings
+DESTDIR ?=
+PREFIX ?= /usr/local
+BINDIR ?= $(PREFIX)/bin
+LIBDIR ?= $(PREFIX)/lib
+INCLUDEDIR ?= $(PREFIX)/include
+MANDIR ?= $(PREFIX)/share/man
+PKGDIR ?= $(LIBDIR)/pkgconfig
+
+# Tools (sed must be GNU sed)
+SED ?= sed
+INSTALL ?= install
+
+# initial settings
+VERSION := $(MAJOR).$(MINOR).$(REVIS)
+SOVER := .$(MAJOR)
+SOVEREV := .$(MAJOR).$(MINOR)
+
+HEADERS := mustach.h mustach-wrap.h
+SPLITLIB := libmustach-core.so$(SOVEREV)
+SPLITPC := libmustach-core.pc
+COREOBJS := mustach.o mustach-wrap.o
+SINGLEOBJS := $(COREOBJS)
+SINGLEFLAGS :=
+SINGLELIBS :=
+TESTSPECS :=
+ALL := manuals
+
+# availability of CJSON
+ifneq ($(cjson),no)
+ cjson_cflags := $(shell pkg-config --silence-errors --cflags libcjson)
+ cjson_libs := $(shell pkg-config --silence-errors --libs libcjson)
+ ifdef cjson_libs
+ cjson := yes
+ tool ?= cjson
+ HEADERS += mustach-cjson.h
+ SPLITLIB += libmustach-cjson.so$(SOVEREV)
+ SPLITPC += libmustach-cjson.pc
+ SINGLEOBJS += mustach-cjson.o
+ SINGLEFLAGS += ${cjson_cflags}
+ SINGLELIBS += ${cjson_libs}
+ TESTSPECS += test-specs/test-specs-cjson
+ else
+ ifeq ($(cjson),yes)
+ $(error Can't find required library cjson)
+ endif
+ cjson := no
+ endif
+endif
+
+# availability of JSON-C
+ifneq ($(jsonc),no)
+ jsonc_cflags := $(shell pkg-config --silence-errors --cflags json-c)
+ jsonc_libs := $(shell pkg-config --silence-errors --libs json-c)
+ ifdef jsonc_libs
+ jsonc := yes
+ tool ?= jsonc
+ HEADERS += mustach-json-c.h
+ SPLITLIB += libmustach-json-c.so$(SOVEREV)
+ SPLITPC += libmustach-json-c.pc
+ SINGLEOBJS += mustach-json-c.o
+ SINGLEFLAGS += ${jsonc_cflags}
+ SINGLELIBS += ${jsonc_libs}
+ TESTSPECS += test-specs/test-specs-json-c
+ else
+ ifeq ($(jsonc),yes)
+ $(error Can't find required library json-c)
+ endif
+ jsonc := no
+ endif
+endif
+
+# availability of JANSSON
+ifneq ($(jansson),no)
+ jansson_cflags := $(shell pkg-config --silence-errors --cflags jansson)
+ jansson_libs := $(shell pkg-config --silence-errors --libs jansson)
+ ifdef jansson_libs
+ jansson := yes
+ tool ?= jansson
+ HEADERS += mustach-jansson.h
+ SPLITLIB += libmustach-jansson.so$(SOVEREV)
+ SPLITPC += libmustach-jansson.pc
+ SINGLEOBJS += mustach-jansson.o
+ SINGLEFLAGS += ${jansson_cflags}
+ SINGLELIBS += ${jansson_libs}
+ TESTSPECS += test-specs/test-specs-jansson
+ else
+ ifeq ($(jansson),yes)
+ $(error Can't find required library jansson)
+ endif
+ jansson := no
+ endif
+endif
+
+# tool
+TOOLOBJS = mustach-tool.o $(COREOBJS)
+tool ?= none
+ifneq ($(tool),none)
+ ifeq ($(tool),cjson)
+ TOOLOBJS += mustach-cjson.o
+ TOOLFLAGS := ${cjson_cflags} -DTOOL=MUSTACH_TOOL_CJSON
+ TOOLLIBS := ${cjson_libs}
+ TOOLDEP := mustach-cjson.h
+ else ifeq ($(tool),jsonc)
+ TOOLOBJS += mustach-json-c.o
+ TOOLFLAGS := ${jsonc_cflags} -DTOOL=MUSTACH_TOOL_JSON_C
+ TOOLLIBS := ${jsonc_libs}
+ TOOLDEP := mustach-json-c.h
+ else ifeq ($(tool),jansson)
+ TOOLOBJS += mustach-jansson.o
+ TOOLFLAGS := ${jansson_cflags} -DTOOL=MUSTACH_TOOL_JANSSON
+ TOOLLIBS := ${jansson_libs}
+ TOOLDEP := mustach-jansson.h
+ else
+ $(error Unknown library $(tool) for tool)
+ endif
+ ifneq ($($(tool)),yes)
+ $(error No library found for tool $(tool))
+ endif
+ ALL += mustach
+endif
+
+# compute targets
+libs ?= all
+ifeq (${libs},split)
+ ALL += ${SPLITLIB} ${SPLITPC}
+else ifeq (${libs},single)
+ ALL += libmustach.so$(SOVEREV) libmustach.pc
+else ifeq (${libs},all)
+ ALL += libmustach.so$(SOVEREV) libmustach.pc ${SPLITLIB} ${SPLITPC}
+else ifneq (${libs},none)
+ $(error Unknown libs $(libs))
+endif
+
+# display target
+$(info tool = ${tool})
+$(info libs = ${libs})
+$(info jsonc = ${jsonc})
+$(info jansson = ${jansson})
+$(info cjson = ${cjson})
+
+# settings
+
+EFLAGS = -fPIC -Wall -Wextra -DVERSION=${VERSION}
+
+ifeq ($(shell uname),Darwin)
+ LDFLAGS_single += -install_name $(LIBDIR)/libmustach.so$(SOVEREV)
+ LDFLAGS_core += -install_name $(LIBDIR)/libmustach-core.so$(SOVEREV)
+ LDFLAGS_cjson += -install_name $(LIBDIR)/libmustach-cjson.so$(SOVEREV)
+ LDFLAGS_jsonc += -install_name $(LIBDIR)/libmustach-json-c.so$(SOVEREV)
+ LDFLAGS_jansson += -install_name $(LIBDIR)/libmustach-jansson.so$(SOVEREV)
+else
+ LDFLAGS_single += -Wl,-soname,libmustach.so$(SOVER)
+ LDFLAGS_core += -Wl,-soname,libmustach-core.so$(SOVER)
+ LDFLAGS_cjson += -Wl,-soname,libmustach-cjson.so$(SOVER)
+ LDFLAGS_jsonc += -Wl,-soname,libmustach-json-c.so$(SOVER)
+ LDFLAGS_jansson += -Wl,-soname,libmustach-jansson.so$(SOVER)
+endif
+
+# targets
+
+.PHONY: all
+all: ${ALL}
+
+mustach: $(TOOLOBJS)
+ $(CC) $(LDFLAGS) $(TOOLFLAGS) -o mustach $(TOOLOBJS) $(TOOLLIBS)
+
+libmustach.so$(SOVEREV): $(SINGLEOBJS)
+ $(CC) -shared $(LDFLAGS) $(LDFLAGS_single) -o $@ $^ $(SINGLELIBS)
+
+libmustach-core.so$(SOVEREV): $(COREOBJS)
+ $(CC) -shared $(LDFLAGS) $(LDFLAGS_core) -o $@ $(COREOBJS) $(lib_OBJ)
+
+libmustach-cjson.so$(SOVEREV): $(COREOBJS) mustach-cjson.o
+ $(CC) -shared $(LDFLAGS) $(LDFLAGS_cjson) -o $@ $^ $(cjson_libs)
+
+libmustach-json-c.so$(SOVEREV): $(COREOBJS) mustach-json-c.o
+ $(CC) -shared $(LDFLAGS) $(LDFLAGS_jsonc) -o $@ $^ $(jsonc_libs)
+
+libmustach-jansson.so$(SOVEREV): $(COREOBJS) mustach-jansson.o
+ $(CC) -shared $(LDFLAGS) $(LDFLAGS_jansson) -o $@ $^ $(jansson_libs)
+
+# pkgconfigs
+
+%.pc: pkgcfgs
+ $(SED) -E '/^==.*==$$/{h;d};x;/==$@==/{x;s/VERSION/$(VERSION)/;p;d};x;d' $< > $@
+
+# objects
+
+mustach.o: mustach.c mustach.h
+ $(CC) -c $(EFLAGS) $(CFLAGS) -o $@ $<
+
+mustach-wrap.o: mustach-wrap.c mustach.h mustach-wrap.h
+ $(CC) -c $(EFLAGS) $(CFLAGS) -o $@ $<
+
+mustach-tool.o: mustach-tool.c mustach.h mustach-json-c.h $(TOOLDEP)
+ $(CC) -c $(EFLAGS) $(CFLAGS) $(TOOLFLAGS) -o $@ $<
+
+mustach-cjson.o: mustach-cjson.c mustach.h mustach-wrap.h mustach-cjson.h
+ $(CC) -c $(EFLAGS) $(CFLAGS) $(cjson_cflags) -o $@ $<
+
+mustach-json-c.o: mustach-json-c.c mustach.h mustach-wrap.h mustach-json-c.h
+ $(CC) -c $(EFLAGS) $(CFLAGS) $(jsonc_cflags) -o $@ $<
+
+mustach-jansson.o: mustach-jansson.c mustach.h mustach-wrap.h mustach-jansson.h
+ $(CC) -c $(EFLAGS) $(CFLAGS) $(jansson_cflags) -o $@ $<
+
+# installing
+.PHONY: install
+install: all
+ $(INSTALL) -d $(DESTDIR)$(BINDIR)
+ if test "${tool}" != "none"; then \
+ $(INSTALL) -m0755 mustach $(DESTDIR)$(BINDIR)/; \
+ fi
+ $(INSTALL) -d $(DESTDIR)$(INCLUDEDIR)/mustach
+ $(INSTALL) -m0644 $(HEADERS) $(DESTDIR)$(INCLUDEDIR)/mustach
+ $(INSTALL) -d $(DESTDIR)$(LIBDIR)
+ for x in libmustach*.so$(SOVEREV); do \
+ $(INSTALL) -m0755 $$x $(DESTDIR)$(LIBDIR)/ ;\
+ ln -sf $$x $(DESTDIR)$(LIBDIR)/$${x%.so.*}.so$(SOVER) ;\
+ ln -sf $$x $(DESTDIR)$(LIBDIR)/$${x%.so.*}.so ;\
+ done
+ $(INSTALL) -d $(DESTDIR)/$(PKGDIR)
+ $(INSTALL) -m0644 libmustach*.pc $(DESTDIR)/$(PKGDIR)
+ $(INSTALL) -d $(DESTDIR)/$(MANDIR)/man1
+ $(INSTALL) -m0644 mustach.1.gz $(DESTDIR)/$(MANDIR)/man1
+
+# deinstalling
+.PHONY: uninstall
+uninstall:
+ rm -f $(DESTDIR)$(BINDIR)/mustach
+ rm -f $(DESTDIR)$(LIBDIR)/libmustach*.so*
+ rm -rf $(DESTDIR)$(INCLUDEDIR)/mustach
+
+.PHONY: test test-basic test-specs
+test: basic-tests spec-tests
+
+basic-tests: mustach
+ @$(MAKE) -C test1 test
+ @$(MAKE) -C test2 test
+ @$(MAKE) -C test3 test
+ @$(MAKE) -C test4 test
+ @$(MAKE) -C test5 test
+ @$(MAKE) -C test6 test
+ @$(MAKE) -C test7 test
+ @$(MAKE) -C test8 test
+
+spec-tests: $(TESTSPECS)
+
+test-specs/test-specs-%: test-specs/%-test-specs test-specs/specs
+ ./$< test-specs/spec/specs/[a-z]*.json > $@.last || true
+ diff $@.ref $@.last
+
+test-specs/cjson-test-specs.o: test-specs/test-specs.c mustach.h mustach-wrap.h mustach-cjson.h
+ $(CC) -I. -c $(EFLAGS) $(CFLAGS) $(cjson_cflags) -DTEST=TEST_CJSON -o $@ $<
+
+test-specs/cjson-test-specs: test-specs/cjson-test-specs.o mustach-cjson.o $(COREOBJS)
+ $(CC) $(LDFLAGS) -o $@ $^ $(cjson_libs)
+
+test-specs/json-c-test-specs.o: test-specs/test-specs.c mustach.h mustach-wrap.h mustach-json-c.h
+ $(CC) -I. -c $(EFLAGS) $(CFLAGS) $(jsonc_cflags) -DTEST=TEST_JSON_C -o $@ $<
+
+test-specs/json-c-test-specs: test-specs/json-c-test-specs.o mustach-json-c.o $(COREOBJS)
+ $(CC) $(LDFLAGS) -o $@ $^ $(jsonc_libs)
+
+test-specs/jansson-test-specs.o: test-specs/test-specs.c mustach.h mustach-wrap.h mustach-jansson.h
+ $(CC) -I. -c $(EFLAGS) $(CFLAGS) $(jansson_cflags) -DTEST=TEST_JANSSON -o $@ $<
+
+test-specs/jansson-test-specs: test-specs/jansson-test-specs.o mustach-jansson.o $(COREOBJS)
+ $(CC) $(LDFLAGS) -o $@ $^ $(jansson_libs)
+
+.PHONY: test-specs/specs
+test-specs/specs:
+ if test -d test-specs/spec; then \
+ git -C test-specs/spec pull; \
+ else \
+ git -C test-specs clone https://github.com/mustache/spec.git; \
+ fi
+
+#cleaning
+.PHONY: clean
+clean:
+ rm -f mustach libmustach*.so* *.o *.pc
+ rm -f test-specs/*-test-specs test-specs/test-specs-*.last
+ rm -rf *.gcno *.gcda coverage.info gcov-latest
+ @$(MAKE) -C test1 clean
+ @$(MAKE) -C test2 clean
+ @$(MAKE) -C test3 clean
+ @$(MAKE) -C test4 clean
+ @$(MAKE) -C test5 clean
+ @$(MAKE) -C test6 clean
+ @$(MAKE) -C test7 clean
+ @$(MAKE) -C test8 clean
+
+# manpage
+.PHONY: manuals
+manuals: mustach.1.gz
+
+mustach.1.gz: mustach.1.scd
+ if which scdoc >/dev/null 2>&1; then scdoc < mustach.1.scd | gzip > mustach.1.gz; fi
diff --git a/src/templating/mustach-tool.c b/src/templating/mustach-tool.c
new file mode 100644
index 000000000..5f28c1f58
--- /dev/null
+++ b/src/templating/mustach-tool.c
@@ -0,0 +1,258 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <libgen.h>
+
+#include "mustach-wrap.h"
+
+static const size_t BLOCKSIZE = 8192;
+
+static const char *errors[] = {
+ "??? unreferenced ???",
+ "system",
+ "unexpected end",
+ "empty tag",
+ "tag too long",
+ "bad separators",
+ "too depth",
+ "closing",
+ "bad unescape tag",
+ "invalid interface",
+ "item not found",
+ "partial not found",
+ "undefined tag",
+ "too much template nesting"
+};
+
+static const char *errmsg = 0;
+static int flags = 0;
+static FILE *output = 0;
+
+static void help(char *prog)
+{
+ char *name = basename(prog);
+#define STR_INDIR(x) #x
+#define STR(x) STR_INDIR(x)
+ printf("%s version %s\n", name, STR(VERSION));
+#undef STR
+#undef STR_INDIR
+ printf(
+ "\n"
+ "USAGE:\n"
+ " %s [FLAGS] <json-file> <mustach-templates...>\n"
+ "\n"
+ "FLAGS:\n"
+ " -h, --help Prints help information\n"
+ " -s, --strict Error when a tag is undefined\n"
+ "\n"
+ "ARGS: (if a file is -, read standard input)\n"
+ " <json-file> JSON file with input data\n"
+ " <mustach-templates...> Template files to instantiate\n",
+ name);
+ exit(0);
+}
+
+static char *readfile(const char *filename, size_t *length)
+{
+ int f;
+ struct stat s;
+ char *result;
+ size_t size, pos;
+ ssize_t rc;
+
+ result = NULL;
+ if (filename[0] == '-' && filename[1] == 0)
+ f = dup(0);
+ else
+ f = open(filename, O_RDONLY);
+ if (f < 0) {
+ fprintf(stderr, "Can't open file: %s\n", filename);
+ exit(1);
+ }
+
+ fstat(f, &s);
+ switch (s.st_mode & S_IFMT) {
+ case S_IFREG:
+ size = s.st_size;
+ break;
+ case S_IFSOCK:
+ case S_IFIFO:
+ size = BLOCKSIZE;
+ break;
+ default:
+ fprintf(stderr, "Bad file: %s\n", filename);
+ exit(1);
+ }
+
+ pos = 0;
+ result = malloc(size + 1);
+ do {
+ if (result == NULL) {
+ fprintf(stderr, "Out of memory\n");
+ exit(1);
+ }
+ rc = read(f, &result[pos], (size - pos) + 1);
+ if (rc < 0) {
+ fprintf(stderr, "Error while reading %s\n", filename);
+ exit(1);
+ }
+ if (rc > 0) {
+ pos += (size_t)rc;
+ if (pos > size) {
+ size = pos + BLOCKSIZE;
+ result = realloc(result, size + 1);
+ }
+ }
+ } while(rc > 0);
+
+ close(f);
+ if (length != NULL)
+ *length = pos;
+ result[pos] = 0;
+ return result;
+}
+
+static int load_json(const char *filename);
+static int process(const char *content, size_t length);
+static void close_json();
+
+int main(int ac, char **av)
+{
+ char *t, *f;
+ char *prog = *av;
+ int s;
+ size_t length;
+
+ (void)ac; /* unused */
+ flags = Mustach_With_AllExtensions;
+ output = stdout;
+
+ for( ++av ; av[0] && av[0][0] == '-' && av[0][1] != 0 ; av++) {
+ if (!strcmp(*av, "-h") || !strcmp(*av, "--help"))
+ help(prog);
+ if (!strcmp(*av, "-s") || !strcmp(*av, "--strict"))
+ flags |= Mustach_With_ErrorUndefined;
+ }
+ if (*av) {
+ f = (av[0][0] == '-' && !av[0][1]) ? "/dev/stdin" : av[0];
+ s = load_json(f);
+ if (s < 0) {
+ fprintf(stderr, "Can't load json file %s\n", av[0]);
+ if(errmsg)
+ fprintf(stderr, " reason: %s\n", errmsg);
+ exit(1);
+ }
+ while(*++av) {
+ t = readfile(*av, &length);
+ s = process(t, length);
+ free(t);
+ if (s != MUSTACH_OK) {
+ s = -s;
+ if (s < 1 || s >= (int)(sizeof errors / sizeof * errors))
+ s = 0;
+ fprintf(stderr, "Template error %s (file %s)\n", errors[s], *av);
+ }
+ }
+ close_json();
+ }
+ return 0;
+}
+
+#define MUSTACH_TOOL_JSON_C 1
+#define MUSTACH_TOOL_JANSSON 2
+#define MUSTACH_TOOL_CJSON 3
+
+#if TOOL == MUSTACH_TOOL_JSON_C
+
+#include "mustach-json-c.h"
+
+static struct json_object *o;
+static int load_json(const char *filename)
+{
+ o = json_object_from_file(filename);
+#if JSON_C_VERSION_NUM >= 0x000D00
+ errmsg = json_util_get_last_err();
+ if (errmsg != NULL)
+ return -1;
+#endif
+ if (o == NULL) {
+ errmsg = "null json";
+ return -1;
+ }
+ return 0;
+}
+static int process(const char *content, size_t length)
+{
+ return mustach_json_c_file(content, length, o, flags, output);
+}
+static void close_json()
+{
+ json_object_put(o);
+}
+
+#elif TOOL == MUSTACH_TOOL_JANSSON
+
+#include "mustach-jansson.h"
+
+static json_t *o;
+static json_error_t e;
+static int load_json(const char *filename)
+{
+ o = json_load_file(filename, JSON_DECODE_ANY, &e);
+ if (o == NULL) {
+ errmsg = e.text;
+ return -1;
+ }
+ return 0;
+}
+static int process(const char *content, size_t length)
+{
+ return mustach_jansson_file(content, length, o, flags, output);
+}
+static void close_json()
+{
+ json_decref(o);
+}
+
+#elif TOOL == MUSTACH_TOOL_CJSON
+
+#include "mustach-cjson.h"
+
+static cJSON *o;
+static int load_json(const char *filename)
+{
+ char *t;
+ size_t length;
+
+ t = readfile(filename, &length);
+ o = t ? cJSON_ParseWithLength(t, length) : NULL;
+ free(t);
+ return -!o;
+}
+static int process(const char *content, size_t length)
+{
+ return mustach_cJSON_file(content, length, o, flags, output);
+}
+static void close_json()
+{
+ cJSON_Delete(o);
+}
+
+#else
+#error "no defined json library"
+#endif
diff --git a/src/templating/mustach-wrap.c b/src/templating/mustach-wrap.c
new file mode 100644
index 000000000..2cd00db12
--- /dev/null
+++ b/src/templating/mustach-wrap.c
@@ -0,0 +1,482 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef _WIN32
+#include <malloc.h>
+#endif
+
+#include "mustach.h"
+#include "mustach-wrap.h"
+
+/*
+* It was stated that allowing to include files
+* through template is not safe when the mustache
+* template is open to any value because it could
+* create leaks (example: {{>/etc/passwd}}).
+*/
+#if MUSTACH_SAFE
+# undef MUSTACH_LOAD_TEMPLATE
+#elif !defined(MUSTACH_LOAD_TEMPLATE)
+# define MUSTACH_LOAD_TEMPLATE 1
+#endif
+
+#if !defined(INCLUDE_PARTIAL_EXTENSION)
+# define INCLUDE_PARTIAL_EXTENSION ".mustache"
+#endif
+
+/* global hook for partials */
+int (*mustach_wrap_get_partial)(const char *name, struct mustach_sbuf *sbuf) = NULL;
+
+/* internal structure for wrapping */
+struct wrap {
+ /* original interface */
+ const struct mustach_wrap_itf *itf;
+
+ /* original closure */
+ void *closure;
+
+ /* flags */
+ int flags;
+
+ /* emiter callback */
+ mustach_emit_cb_t *emitcb;
+
+ /* write callback */
+ mustach_write_cb_t *writecb;
+};
+
+/* length given by masking with 3 */
+enum comp {
+ C_no = 0,
+ C_eq = 1,
+ C_lt = 5,
+ C_le = 6,
+ C_gt = 9,
+ C_ge = 10
+};
+
+enum sel {
+ S_none = 0,
+ S_ok = 1,
+ S_objiter = 2,
+ S_ok_or_objiter = S_ok | S_objiter
+};
+
+static enum comp getcomp(char *head, int sflags)
+{
+ return (head[0] == '=' && (sflags & Mustach_With_Equal)) ? C_eq
+ : (head[0] == '<' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_le : C_lt)
+ : (head[0] == '>' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_ge : C_gt)
+ : C_no;
+}
+
+static char *keyval(char *head, int sflags, enum comp *comp)
+{
+ char *w, car, escaped;
+ enum comp k;
+
+ k = C_no;
+ w = head;
+ car = *head;
+ escaped = (sflags & Mustach_With_EscFirstCmp) && (getcomp(head, sflags) != C_no);
+ while (car && (escaped || (k = getcomp(head, sflags)) == C_no)) {
+ if (escaped)
+ escaped = 0;
+ else
+ escaped = ((sflags & Mustach_With_JsonPointer) ? car == '~' : car == '\\')
+ && (getcomp(head + 1, sflags) != C_no);
+ if (!escaped)
+ *w++ = car;
+ head++;
+ car = *head;
+ }
+ *w = 0;
+ *comp = k;
+ return k == C_no ? NULL : &head[k & 3];
+}
+
+static char *getkey(char **head, int sflags)
+{
+ char *result, *iter, *write, car;
+
+ car = *(iter = *head);
+ if (!car)
+ result = NULL;
+ else {
+ result = write = iter;
+ if (sflags & Mustach_With_JsonPointer)
+ {
+ while (car && car != '/') {
+ if (car == '~')
+ switch (iter[1]) {
+ case '1': car = '/'; /*@fallthrough@*/
+ case '0': iter++;
+ }
+ *write++ = car;
+ car = *++iter;
+ }
+ *write = 0;
+ while (car == '/')
+ car = *++iter;
+ }
+ else
+ {
+ while (car && car != '.') {
+ if (car == '\\' && (iter[1] == '.' || iter[1] == '\\'))
+ car = *++iter;
+ *write++ = car;
+ car = *++iter;
+ }
+ *write = 0;
+ while (car == '.')
+ car = *++iter;
+ }
+ *head = iter;
+ }
+ return result;
+}
+
+static enum sel sel(struct wrap *w, const char *name)
+{
+ enum sel result;
+ int i, j, sflags, scmp;
+ char *key, *value;
+ enum comp k;
+
+ /* make a local writeable copy */
+ size_t lenname = 1 + strlen(name);
+ char buffer[lenname];
+ char *copy = buffer;
+ memcpy(copy, name, lenname);
+
+ /* check if matches json pointer selection */
+ sflags = w->flags;
+ if (sflags & Mustach_With_JsonPointer) {
+ if (copy[0] == '/')
+ copy++;
+ else
+ sflags ^= Mustach_With_JsonPointer;
+ }
+
+ /* extract the value, translate the key and get the comparator */
+ if (sflags & (Mustach_With_Equal | Mustach_With_Compare))
+ value = keyval(copy, sflags, &k);
+ else {
+ k = C_no;
+ value = NULL;
+ }
+
+ /* case of . alone if Mustach_With_SingleDot? */
+ if (copy[0] == '.' && copy[1] == 0 /*&& (sflags & Mustach_With_SingleDot)*/)
+ /* yes, select current */
+ result = w->itf->sel(w->closure, NULL) ? S_ok : S_none;
+ else
+ {
+ /* not the single dot, extract the first key */
+ key = getkey(&copy, sflags);
+ if (key == NULL)
+ return 0;
+
+ /* select the root item */
+ if (w->itf->sel(w->closure, key))
+ result = S_ok;
+ else if (key[0] == '*'
+ && !key[1]
+ && !value
+ && !*copy
+ && (w->flags & Mustach_With_ObjectIter)
+ && w->itf->sel(w->closure, NULL))
+ result = S_ok_or_objiter;
+ else
+ result = S_none;
+ if (result == S_ok) {
+ /* iterate the selection of sub items */
+ key = getkey(&copy, sflags);
+ while(result == S_ok && key) {
+ if (w->itf->subsel(w->closure, key))
+ /* nothing */;
+ else if (key[0] == '*'
+ && !key[1]
+ && !value
+ && !*copy
+ && (w->flags & Mustach_With_ObjectIter))
+ result = S_objiter;
+ else
+ result = S_none;
+ key = getkey(&copy, sflags);
+ }
+ }
+ }
+ /* should it be compared? */
+ if (result == S_ok && value) {
+ if (!w->itf->compare)
+ result = S_none;
+ else {
+ i = value[0] == '!';
+ scmp = w->itf->compare(w->closure, &value[i]);
+ switch (k) {
+ case C_eq: j = scmp == 0; break;
+ case C_lt: j = scmp < 0; break;
+ case C_le: j = scmp <= 0; break;
+ case C_gt: j = scmp > 0; break;
+ case C_ge: j = scmp >= 0; break;
+ default: j = i; break;
+ }
+ if (i == j)
+ result = S_none;
+ }
+ }
+ return result;
+}
+
+static int start_callback(void *closure)
+{
+ struct wrap *w = closure;
+ return w->itf->start ? w->itf->start(w->closure) : MUSTACH_OK;
+}
+
+static void stop_callback(void *closure, int status)
+{
+ struct wrap *w = closure;
+ if (w->itf->stop)
+ w->itf->stop(w->closure, status);
+}
+
+static int emit(struct wrap *w, const char *buffer, size_t size, FILE *file)
+{
+ int r;
+
+ if (w->writecb)
+ r = w->writecb(file, buffer, size);
+ else
+ r = fwrite(buffer, 1, size, file) == size ? MUSTACH_OK : MUSTACH_ERROR_SYSTEM;
+ return r;
+}
+
+static int emit_callback(void *closure, const char *buffer, size_t size, int escape, FILE *file)
+{
+ struct wrap *w = closure;
+ int r;
+ size_t s, i;
+ char car;
+
+ if (w->emitcb)
+ r = w->emitcb(file, buffer, size, escape);
+ else if (!escape)
+ r = emit(w, buffer, size, file);
+ else {
+ i = 0;
+ r = MUSTACH_OK;
+ while(i < size && r == MUSTACH_OK) {
+ s = i;
+ while (i < size && (car = buffer[i]) != '<' && car != '>' && car != '&' && car != '"')
+ i++;
+ if (i != s)
+ r = emit(w, &buffer[s], i - s, file);
+ if (i < size && r == MUSTACH_OK) {
+ switch(car) {
+ case '<': r = emit(w, "&lt;", 4, file); break;
+ case '>': r = emit(w, "&gt;", 4, file); break;
+ case '&': r = emit(w, "&amp;", 5, file); break;
+ case '"': r = emit(w, "&quot;", 6, file); break;
+ }
+ i++;
+ }
+ }
+ }
+ return r;
+}
+
+static int enter_callback(void *closure, const char *name)
+{
+ struct wrap *w = closure;
+ enum sel s = sel(w, name);
+ return s == S_none ? 0 : w->itf->enter(w->closure, s & S_objiter);
+}
+
+static int next_callback(void *closure)
+{
+ struct wrap *w = closure;
+ return w->itf->next(w->closure);
+}
+
+static int leave_callback(void *closure)
+{
+ struct wrap *w = closure;
+ return w->itf->leave(w->closure);
+}
+
+static int getoptional(struct wrap *w, const char *name, struct mustach_sbuf *sbuf)
+{
+ enum sel s = sel(w, name);
+ if (!(s & S_ok))
+ return 0;
+ return w->itf->get(w->closure, sbuf, s & S_objiter);
+}
+
+static int get_callback(void *closure, const char *name, struct mustach_sbuf *sbuf)
+{
+ struct wrap *w = closure;
+ if (getoptional(w, name, sbuf) <= 0) {
+ if (w->flags & Mustach_With_ErrorUndefined)
+ return MUSTACH_ERROR_UNDEFINED_TAG;
+ sbuf->value = "";
+ }
+ return MUSTACH_OK;
+}
+
+#if MUSTACH_LOAD_TEMPLATE
+static int get_partial_from_file(const char *name, struct mustach_sbuf *sbuf)
+{
+ static char extension[] = INCLUDE_PARTIAL_EXTENSION;
+ size_t s;
+ long pos;
+ FILE *file;
+ char *path, *buffer;
+
+ /* allocate path */
+ s = strlen(name);
+ path = malloc(s + sizeof extension);
+ if (path == NULL)
+ return MUSTACH_ERROR_SYSTEM;
+
+ /* try without extension first */
+ memcpy(path, name, s + 1);
+ file = fopen(path, "r");
+ if (file == NULL) {
+ memcpy(&path[s], extension, sizeof extension);
+ file = fopen(path, "r");
+ }
+ free(path);
+
+ /* if file opened */
+ if (file == NULL)
+ return MUSTACH_ERROR_PARTIAL_NOT_FOUND;
+
+ /* compute file size */
+ if (fseek(file, 0, SEEK_END) >= 0
+ && (pos = ftell(file)) >= 0
+ && fseek(file, 0, SEEK_SET) >= 0) {
+ /* allocate value */
+ s = (size_t)pos;
+ buffer = malloc(s + 1);
+ if (buffer != NULL) {
+ /* read value */
+ if (1 == fread(buffer, s, 1, file)) {
+ /* force zero at end */
+ sbuf->value = buffer;
+ buffer[s] = 0;
+ sbuf->freecb = free;
+ fclose(file);
+ return MUSTACH_OK;
+ }
+ free(buffer);
+ }
+ }
+ fclose(file);
+ return MUSTACH_ERROR_SYSTEM;
+}
+#endif
+
+static int partial_callback(void *closure, const char *name, struct mustach_sbuf *sbuf)
+{
+ struct wrap *w = closure;
+ int rc;
+ if (mustach_wrap_get_partial != NULL) {
+ rc = mustach_wrap_get_partial(name, sbuf);
+ if (rc != MUSTACH_ERROR_PARTIAL_NOT_FOUND) {
+ if (rc != MUSTACH_OK)
+ sbuf->value = "";
+ return rc;
+ }
+ }
+#if MUSTACH_LOAD_TEMPLATE
+ if (w->flags & Mustach_With_PartialDataFirst) {
+ if (getoptional(w, name, sbuf) > 0)
+ rc = MUSTACH_OK;
+ else
+ rc = get_partial_from_file(name, sbuf);
+ }
+ else {
+ rc = get_partial_from_file(name, sbuf);
+ if (rc != MUSTACH_OK && getoptional(w, name, sbuf) > 0)
+ rc = MUSTACH_OK;
+ }
+#else
+ rc = getoptional(w, name, sbuf) > 0 ? MUSTACH_OK : MUSTACH_ERROR_PARTIAL_NOT_FOUND;
+#endif
+ if (rc != MUSTACH_OK)
+ sbuf->value = "";
+ return MUSTACH_OK;
+}
+
+const struct mustach_itf mustach_wrap_itf = {
+ .start = start_callback,
+ .put = NULL,
+ .enter = enter_callback,
+ .next = next_callback,
+ .leave = leave_callback,
+ .partial = partial_callback,
+ .get = get_callback,
+ .emit = emit_callback,
+ .stop = stop_callback
+};
+
+static void wrap_init(struct wrap *wrap, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, mustach_write_cb_t *writecb)
+{
+ if (flags & Mustach_With_Compare)
+ flags |= Mustach_With_Equal;
+ wrap->closure = closure;
+ wrap->itf = itf;
+ wrap->flags = flags;
+ wrap->emitcb = emitcb;
+ wrap->writecb = writecb;
+}
+
+int mustach_wrap_file(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, FILE *file)
+{
+ struct wrap w;
+ wrap_init(&w, itf, closure, flags, NULL, NULL);
+ return mustach_file(template, length, &mustach_wrap_itf, &w, flags, file);
+}
+
+int mustach_wrap_fd(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, int fd)
+{
+ struct wrap w;
+ wrap_init(&w, itf, closure, flags, NULL, NULL);
+ return mustach_fd(template, length, &mustach_wrap_itf, &w, flags, fd);
+}
+
+int mustach_wrap_mem(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, char **result, size_t *size)
+{
+ struct wrap w;
+ wrap_init(&w, itf, closure, flags, NULL, NULL);
+ return mustach_mem(template, length, &mustach_wrap_itf, &w, flags, result, size);
+}
+
+int mustach_wrap_write(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_write_cb_t *writecb, void *writeclosure)
+{
+ struct wrap w;
+ wrap_init(&w, itf, closure, flags, NULL, writecb);
+ return mustach_file(template, length, &mustach_wrap_itf, &w, flags, writeclosure);
+}
+
+int mustach_wrap_emit(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, void *emitclosure)
+{
+ struct wrap w;
+ wrap_init(&w, itf, closure, flags, emitcb, NULL);
+ return mustach_file(template, length, &mustach_wrap_itf, &w, flags, emitclosure);
+}
+
diff --git a/src/templating/mustach-wrap.h b/src/templating/mustach-wrap.h
new file mode 100644
index 000000000..fedcb9191
--- /dev/null
+++ b/src/templating/mustach-wrap.h
@@ -0,0 +1,235 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _mustach_wrap_h_included_
+#define _mustach_wrap_h_included_
+
+/*
+ * mustach-wrap is intended to make integration of JSON
+ * libraries easier by wrapping mustach extensions in a
+ * single place.
+ *
+ * As before, using mustach and only mustach is possible
+ * (by using only mustach.h) but does not implement high
+ * level features coming with extensions implemented by
+ * this high level wrapper.
+ */
+#include "mustach.h"
+/*
+ * Definition of the writing callbacks for mustach functions
+ * producing output to callbacks.
+ *
+ * Two callback types are defined:
+ *
+ * @mustach_write_cb_t:
+ *
+ * callback receiving the escaped data to be written as 3 parameters:
+ *
+ * 1. the 'closure', the same given to the wmustach_... function
+ * 2. a pointer to a 'buffer' containing the characters to be written
+ * 3. the size in bytes of the data pointed by 'buffer'
+ *
+ * @mustach_emit_cb_t:
+ *
+ * callback receiving the data to be written and a flag indicating
+ * if escaping should be done or not as 4 parameters:
+ *
+ * 1. the 'closure', the same given to the emustach_... function
+ * 2. a pointer to a 'buffer' containing the characters to be written
+ * 3. the size in bytes of the data pointed by 'buffer'
+ * 4. a boolean indicating if 'escape' should be done
+ */
+#ifndef _mustach_output_callbacks_defined_
+#define _mustach_output_callbacks_defined_
+typedef int mustach_write_cb_t(void *closure, const char *buffer, size_t size);
+typedef int mustach_emit_cb_t(void *closure, const char *buffer, size_t size, int escape);
+#endif
+
+/**
+ * Flags specific to mustach wrap
+ */
+#define Mustach_With_SingleDot 4 /* obsolete, always set */
+#define Mustach_With_Equal 8
+#define Mustach_With_Compare 16
+#define Mustach_With_JsonPointer 32
+#define Mustach_With_ObjectIter 64
+#define Mustach_With_IncPartial 128 /* obsolete, always set */
+#define Mustach_With_EscFirstCmp 256
+#define Mustach_With_PartialDataFirst 512
+#define Mustach_With_ErrorUndefined 1024
+
+#undef Mustach_With_AllExtensions
+#define Mustach_With_AllExtensions 1023 /* don't include ErrorUndefined */
+
+/**
+ * mustach_wrap_itf - high level wrap of mustach - interface for callbacks
+ *
+ * The functions sel, subsel, enter and next should return 0 or 1.
+ *
+ * All other functions should normally return MUSTACH_OK (zero).
+ *
+ * If any function returns a negative value, it means an error that
+ * stop the processing and that is reported to the caller. Mustach
+ * also has its own error codes. Using the macros MUSTACH_ERROR_USER
+ * and MUSTACH_IS_ERROR_USER could help to avoid clashes.
+ *
+ * @start: If defined (can be NULL), starts the mustach processing
+ * of the closure, called at the very beginning before any
+ * mustach processing occurs.
+ *
+ * @stop: If defined (can be NULL), stops the mustach processing
+ * of the closure, called at the very end after all mustach
+ * processing occurered. The status returned by the processing
+ * is passed to the stop.
+ *
+ * @compare: If defined (can be NULL), compares the value of the
+ * currently selected item with the given value and returns
+ * a negative value if current value is lesser, a positive
+ * value if the current value is greater or zero when
+ * values are equals.
+ * If 'compare' is NULL, any comparison in mustach
+ * is going to fails.
+ *
+ * @sel: Selects the item of the given 'name'. If 'name' is NULL
+ * Selects the current item. Returns 1 if the selection is
+ * effective or else 0 if the selection failed.
+ *
+ * @subsel: Selects from the currently selected object the value of
+ * the field of given name. Returns 1 if the selection is
+ * effective or else 0 if the selection failed.
+ *
+ * @enter: Enters the section of 'name' if possible.
+ * Musts return 1 if entered or 0 if not entered.
+ * When 1 is returned, the function 'leave' will always be called.
+ * Conversely 'leave' is never called when enter returns 0 or
+ * a negative value.
+ * When 1 is returned, the function must activate the first
+ * item of the section.
+ *
+ * @next: Activates the next item of the section if it exists.
+ * Musts return 1 when the next item is activated.
+ * Musts return 0 when there is no item to activate.
+ *
+ * @leave: Leaves the last entered section
+ *
+ * @get: Returns in 'sbuf' the value of the current selection if 'key'
+ * is zero. Otherwise, when 'key' is not zero, return in 'sbuf'
+ * the name of key of the current selection, or if no such key
+ * exists, the empty string. Must return 1 if possible or
+ * 0 when not possible or an error code.
+ */
+struct mustach_wrap_itf {
+ int (*start)(void *closure);
+ void (*stop)(void *closure, int status);
+ int (*compare)(void *closure, const char *value);
+ int (*sel)(void *closure, const char *name);
+ int (*subsel)(void *closure, const char *name);
+ int (*enter)(void *closure, int objiter);
+ int (*next)(void *closure);
+ int (*leave)(void *closure);
+ int (*get)(void *closure, struct mustach_sbuf *sbuf, int key);
+};
+
+/**
+ * Mustach interface used internally by mustach wrapper functions.
+ * Can be used for overriding behaviour.
+ */
+extern const struct mustach_itf mustach_wrap_itf;
+
+/**
+ * Global hook for providing partials. When set to a not NULL value, the pointed
+ * function replaces the default behaviour and is called to provide the partial
+ * of the given 'name' in 'sbuf'.
+ * The function must return MUSTACH_OK when it filled 'sbuf' with value of partial
+ * or must return an error code if it failed. But if MUSTACH_ERROR_PARTIAL_NOT_FOUND
+ * is returned, the default behavior is evaluated.
+ */
+extern int (*mustach_wrap_get_partial)(const char *name, struct mustach_sbuf *sbuf);
+
+/**
+ * mustach_wrap_file - Renders the mustache 'template' in 'file' for an abstract
+ * wrapper of interface 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface of the abstract wrapper
+ * @closure: the closure of the abstract wrapper
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_wrap_file(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, FILE *file);
+
+/**
+ * mustach_wrap_fd - Renders the mustache 'template' in 'fd' for an abstract
+ * wrapper of interface 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface of the abstract wrapper
+ * @closure: the closure of the abstract wrapper
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_wrap_fd(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, int fd);
+
+/**
+ * mustach_wrap_mem - Renders the mustache 'template' in 'result' for an abstract
+ * wrapper of interface 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface of the abstract wrapper
+ * @closure: the closure of the abstract wrapper
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_wrap_mem(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, char **result, size_t *size);
+
+/**
+ * mustach_wrap_write - Renders the mustache 'template' for an abstract
+ * wrapper of interface 'itf' and 'closure' to custom writer
+ * 'writecb' with 'writeclosure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface of the abstract wrapper
+ * @closure: the closure of the abstract wrapper
+ * @writecb: the function that write values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_wrap_write(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_write_cb_t *writecb, void *writeclosure);
+
+/**
+ * mustach_wrap_emit - Renders the mustache 'template' for an abstract
+ * wrapper of interface 'itf' and 'closure' to custom emiter 'emitcb'
+ * with 'emitclosure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface of the abstract wrapper
+ * @closure: the closure of the abstract wrapper
+ * @emitcb: the function that emit values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_wrap_emit(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, void *emitclosure);
+
+#endif
+
diff --git a/src/templating/mustach.1.gz b/src/templating/mustach.1.gz
new file mode 100644
index 000000000..15b8a9052
--- /dev/null
+++ b/src/templating/mustach.1.gz
Binary files differ
diff --git a/src/templating/mustach.1.scd b/src/templating/mustach.1.scd
new file mode 100644
index 000000000..af4f08ef2
--- /dev/null
+++ b/src/templating/mustach.1.scd
@@ -0,0 +1,60 @@
+mustach(1)
+
+# NAME
+
+mustach - Mustache templating command line engine
+
+# SYNOPSIS
+
+*mustach* [-s|--strict] JSON TEMPLATE...
+
+# DESCRIPTION
+
+Instanciate the TEMPLATE files accordingly to the JSON file.
+
+If one of the given files is *-*, the standard input is used.
+
+Option *--strict* make mustach fail if a tag is not found.
+
+# EXAMPLE
+
+A typical Mustache template file: *temp.must*
+
+```
+Hello {{name}}
+You have just won {{value}} dollars!
+{{#in_ca}}
+Well, {{taxed_value}} dollars, after taxes.
+{{/in_ca}}
+```
+
+Given a JSON file: *inst.json*
+
+```
+{
+ "name": "Chris",
+ "value": 10000,
+ "taxed_value": 6000,
+ "in_ca": true
+}
+```
+
+Calling the command *mustach inst.json temp.must*
+will produce the following output:
+
+```
+Hello Chris
+You have just won 10000 dollars!
+Well, 6000.0 dollars, after taxes.
+```
+
+# LINK
+
+Site of *mustach*, the *C* implementation: https://gitlab.com/jobol/mustach
+
+*Mustache format*: http://mustache.github.io/mustache.5.html
+
+Main site for *Mustache*: http://mustache.github.io/
+
+JSON: https://www.json.org/
+
diff --git a/src/templating/mustach.c b/src/templating/mustach.c
new file mode 100644
index 000000000..9f5af131c
--- /dev/null
+++ b/src/templating/mustach.c
@@ -0,0 +1,561 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#ifdef _WIN32
+#include <malloc.h>
+#endif
+
+#include "mustach.h"
+
+struct iwrap {
+ int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file);
+ void *closure; /* closure for: enter, next, leave, emit, get */
+ int (*put)(void *closure, const char *name, int escape, FILE *file);
+ void *closure_put; /* closure for put */
+ int (*enter)(void *closure, const char *name);
+ int (*next)(void *closure);
+ int (*leave)(void *closure);
+ int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf);
+ int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf);
+ void *closure_partial; /* closure for partial */
+ FILE *file;
+ int flags;
+ int nesting;
+};
+
+struct prefix {
+ size_t len;
+ const char *start;
+ struct prefix *prefix;
+};
+
+#if !defined(NO_OPEN_MEMSTREAM)
+static FILE *memfile_open(char **buffer, size_t *size)
+{
+ return open_memstream(buffer, size);
+}
+static void memfile_abort(FILE *file, char **buffer, size_t *size)
+{
+ fclose(file);
+ free(*buffer);
+ *buffer = NULL;
+ *size = 0;
+}
+static int memfile_close(FILE *file, char **buffer, size_t *size)
+{
+ int rc;
+
+ /* adds terminating null */
+ rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
+ fclose(file);
+ if (rc == 0)
+ /* removes terminating null of the length */
+ (*size)--;
+ else {
+ free(*buffer);
+ *buffer = NULL;
+ *size = 0;
+ }
+ return rc;
+}
+#else
+static FILE *memfile_open(char **buffer, size_t *size)
+{
+ /*
+ * We can't provide *buffer and *size as open_memstream does but
+ * at least clear them so the caller won't get bad data.
+ */
+ *buffer = NULL;
+ *size = 0;
+
+ return tmpfile();
+}
+static void memfile_abort(FILE *file, char **buffer, size_t *size)
+{
+ fclose(file);
+ *buffer = NULL;
+ *size = 0;
+}
+static int memfile_close(FILE *file, char **buffer, size_t *size)
+{
+ int rc;
+ size_t s;
+ char *b;
+
+ s = (size_t)ftell(file);
+ b = malloc(s + 1);
+ if (b == NULL) {
+ rc = MUSTACH_ERROR_SYSTEM;
+ errno = ENOMEM;
+ s = 0;
+ } else {
+ rewind(file);
+ if (1 == fread(b, s, 1, file)) {
+ rc = 0;
+ b[s] = 0;
+ } else {
+ rc = MUSTACH_ERROR_SYSTEM;
+ free(b);
+ b = NULL;
+ s = 0;
+ }
+ }
+ *buffer = b;
+ *size = s;
+ return rc;
+}
+#endif
+
+static inline void sbuf_reset(struct mustach_sbuf *sbuf)
+{
+ sbuf->value = NULL;
+ sbuf->freecb = NULL;
+ sbuf->closure = NULL;
+ sbuf->length = 0;
+}
+
+static inline void sbuf_release(struct mustach_sbuf *sbuf)
+{
+ if (sbuf->releasecb)
+ sbuf->releasecb(sbuf->value, sbuf->closure);
+}
+
+static inline size_t sbuf_length(struct mustach_sbuf *sbuf)
+{
+ size_t length = sbuf->length;
+ if (length == 0 && sbuf->value != NULL)
+ length = strlen(sbuf->value);
+ return length;
+}
+
+static int iwrap_emit(void *closure, const char *buffer, size_t size, int escape, FILE *file)
+{
+ size_t i, j, r;
+
+ (void)closure; /* unused */
+
+ if (!escape)
+ return fwrite(buffer, 1, size, file) != size ? MUSTACH_ERROR_SYSTEM : MUSTACH_OK;
+
+ r = i = 0;
+ while (i < size) {
+ j = i;
+ while (j < size && buffer[j] != '<' && buffer[j] != '>' && buffer[j] != '&' && buffer[j] != '"')
+ j++;
+ if (j != i && fwrite(&buffer[i], j - i, 1, file) != 1)
+ return MUSTACH_ERROR_SYSTEM;
+ if (j < size) {
+ switch(buffer[j++]) {
+ case '<':
+ r = fwrite("&lt;", 4, 1, file);
+ break;
+ case '>':
+ r = fwrite("&gt;", 4, 1, file);
+ break;
+ case '&':
+ r = fwrite("&amp;", 5, 1, file);
+ break;
+ case '"':
+ r = fwrite("&quot;", 6, 1, file);
+ break;
+ }
+ if (r != 1)
+ return MUSTACH_ERROR_SYSTEM;
+ }
+ i = j;
+ }
+ return MUSTACH_OK;
+}
+
+static int iwrap_put(void *closure, const char *name, int escape, FILE *file)
+{
+ struct iwrap *iwrap = closure;
+ int rc;
+ struct mustach_sbuf sbuf;
+ size_t length;
+
+ sbuf_reset(&sbuf);
+ rc = iwrap->get(iwrap->closure, name, &sbuf);
+ if (rc >= 0) {
+ length = sbuf_length(&sbuf);
+ if (length)
+ rc = iwrap->emit(iwrap->closure, sbuf.value, length, escape, file);
+ sbuf_release(&sbuf);
+ }
+ return rc;
+}
+
+static int iwrap_partial(void *closure, const char *name, struct mustach_sbuf *sbuf)
+{
+ struct iwrap *iwrap = closure;
+ int rc;
+ FILE *file;
+ size_t size;
+ char *result;
+
+ result = NULL;
+ file = memfile_open(&result, &size);
+ if (file == NULL)
+ rc = MUSTACH_ERROR_SYSTEM;
+ else {
+ rc = iwrap->put(iwrap->closure_put, name, 0, file);
+ if (rc < 0)
+ memfile_abort(file, &result, &size);
+ else {
+ rc = memfile_close(file, &result, &size);
+ if (rc == 0) {
+ sbuf->value = result;
+ sbuf->freecb = free;
+ sbuf->length = size;
+ }
+ }
+ }
+ return rc;
+}
+
+static int emitprefix(struct iwrap *iwrap, struct prefix *prefix)
+{
+ if (prefix->prefix) {
+ int rc = emitprefix(iwrap, prefix->prefix);
+ if (rc < 0)
+ return rc;
+ }
+ return prefix->len ? iwrap->emit(iwrap->closure, prefix->start, prefix->len, 0, iwrap->file) : 0;
+}
+
+static int process(const char *template, size_t length, struct iwrap *iwrap, struct prefix *prefix)
+{
+ struct mustach_sbuf sbuf;
+ char opstr[MUSTACH_MAX_DELIM_LENGTH], clstr[MUSTACH_MAX_DELIM_LENGTH];
+ char name[MUSTACH_MAX_LENGTH + 1], c;
+ const char *beg, *term, *end;
+ struct { const char *name, *again; size_t length; unsigned enabled: 1, entered: 1; } stack[MUSTACH_MAX_DEPTH];
+ size_t oplen, cllen, len, l;
+ int depth, rc, enabled, stdalone;
+ struct prefix pref;
+
+ pref.prefix = prefix;
+ end = template + (length ? length : strlen(template));
+ opstr[0] = opstr[1] = '{';
+ clstr[0] = clstr[1] = '}';
+ oplen = cllen = 2;
+ stdalone = enabled = 1;
+ depth = pref.len = 0;
+ for (;;) {
+ /* search next openning delimiter */
+ for (beg = template ; ; beg++) {
+ c = beg == end ? '\n' : *beg;
+ if (c == '\n') {
+ l = (beg != end) + (size_t)(beg - template);
+ if (stdalone != 2 && enabled) {
+ if (beg != template /* don't prefix empty lines */) {
+ rc = emitprefix(iwrap, &pref);
+ if (rc < 0)
+ return rc;
+ }
+ rc = iwrap->emit(iwrap->closure, template, l, 0, iwrap->file);
+ if (rc < 0)
+ return rc;
+ }
+ if (beg == end) /* no more mustach */
+ return depth ? MUSTACH_ERROR_UNEXPECTED_END : MUSTACH_OK;
+ template += l;
+ stdalone = 1;
+ pref.len = 0;
+ pref.prefix = prefix;
+ }
+ else if (!isspace(c)) {
+ if (stdalone == 2 && enabled) {
+ rc = emitprefix(iwrap, &pref);
+ if (rc < 0)
+ return rc;
+ pref.len = 0;
+ stdalone = 0;
+ pref.prefix = NULL;
+ }
+ if (c == *opstr && end - beg >= (ssize_t)oplen) {
+ for (l = 1 ; l < oplen && beg[l] == opstr[l] ; l++);
+ if (l == oplen)
+ break;
+ }
+ stdalone = 0;
+ }
+ }
+
+ pref.start = template;
+ pref.len = enabled ? (size_t)(beg - template) : 0;
+ beg += oplen;
+
+ /* search next closing delimiter */
+ for (term = beg ; ; term++) {
+ if (term == end)
+ return MUSTACH_ERROR_UNEXPECTED_END;
+ if (*term == *clstr && end - term >= (ssize_t)cllen) {
+ for (l = 1 ; l < cllen && term[l] == clstr[l] ; l++);
+ if (l == cllen)
+ break;
+ }
+ }
+ template = term + cllen;
+ len = (size_t)(term - beg);
+ c = *beg;
+ switch(c) {
+ case ':':
+ stdalone = 0;
+ if (iwrap->flags & Mustach_With_Colon)
+ goto exclude_first;
+ goto get_name;
+ case '!':
+ case '=':
+ break;
+ case '{':
+ for (l = 0 ; l < cllen && clstr[l] == '}' ; l++);
+ if (l < cllen) {
+ if (!len || beg[len-1] != '}')
+ return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
+ len--;
+ } else {
+ if (term[l] != '}')
+ return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
+ template++;
+ }
+ c = '&';
+ /*@fallthrough@*/
+ case '&':
+ stdalone = 0;
+ /*@fallthrough@*/
+ case '^':
+ case '#':
+ case '/':
+ case '>':
+exclude_first:
+ beg++;
+ len--;
+ goto get_name;
+ default:
+ stdalone = 0;
+get_name:
+ while (len && isspace(beg[0])) { beg++; len--; }
+ while (len && isspace(beg[len-1])) len--;
+ if (len == 0 && !(iwrap->flags & Mustach_With_EmptyTag))
+ return MUSTACH_ERROR_EMPTY_TAG;
+ if (len > MUSTACH_MAX_LENGTH)
+ return MUSTACH_ERROR_TAG_TOO_LONG;
+ memcpy(name, beg, len);
+ name[len] = 0;
+ break;
+ }
+ if (stdalone)
+ stdalone = 2;
+ else if (enabled) {
+ rc = emitprefix(iwrap, &pref);
+ if (rc < 0)
+ return rc;
+ pref.len = 0;
+ pref.prefix = NULL;
+ }
+ switch(c) {
+ case '!':
+ /* comment */
+ /* nothing to do */
+ break;
+ case '=':
+ /* defines delimiters */
+ if (len < 5 || beg[len - 1] != '=')
+ return MUSTACH_ERROR_BAD_SEPARATORS;
+ beg++;
+ len -= 2;
+ while (len && isspace(*beg))
+ beg++, len--;
+ while (len && isspace(beg[len - 1]))
+ len--;
+ for (l = 0; l < len && !isspace(beg[l]) ; l++);
+ if (l == len || l > MUSTACH_MAX_DELIM_LENGTH)
+ return MUSTACH_ERROR_BAD_SEPARATORS;
+ oplen = l;
+ memcpy(opstr, beg, l);
+ while (l < len && isspace(beg[l])) l++;
+ if (l == len || len - l > MUSTACH_MAX_DELIM_LENGTH)
+ return MUSTACH_ERROR_BAD_SEPARATORS;
+ cllen = len - l;
+ memcpy(clstr, beg + l, cllen);
+ break;
+ case '^':
+ case '#':
+ /* begin section */
+ if (depth == MUSTACH_MAX_DEPTH)
+ return MUSTACH_ERROR_TOO_DEEP;
+ rc = enabled;
+ if (rc) {
+ rc = iwrap->enter(iwrap->closure, name);
+ if (rc < 0)
+ return rc;
+ }
+ stack[depth].name = beg;
+ stack[depth].again = template;
+ stack[depth].length = len;
+ stack[depth].enabled = enabled != 0;
+ stack[depth].entered = rc != 0;
+ if ((c == '#') == (rc == 0))
+ enabled = 0;
+ depth++;
+ break;
+ case '/':
+ /* end section */
+ if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len))
+ return MUSTACH_ERROR_CLOSING;
+ rc = enabled && stack[depth].entered ? iwrap->next(iwrap->closure) : 0;
+ if (rc < 0)
+ return rc;
+ if (rc) {
+ template = stack[depth++].again;
+ } else {
+ enabled = stack[depth].enabled;
+ if (enabled && stack[depth].entered)
+ iwrap->leave(iwrap->closure);
+ }
+ break;
+ case '>':
+ /* partials */
+ if (enabled) {
+ if (iwrap->nesting >= MUSTACH_MAX_NESTING)
+ rc = MUSTACH_ERROR_TOO_MUCH_NESTING;
+ else {
+ sbuf_reset(&sbuf);
+ rc = iwrap->partial(iwrap->closure_partial, name, &sbuf);
+ if (rc >= 0) {
+ iwrap->nesting++;
+ rc = process(sbuf.value, sbuf_length(&sbuf), iwrap, &pref);
+ sbuf_release(&sbuf);
+ iwrap->nesting--;
+ }
+ }
+ if (rc < 0)
+ return rc;
+ }
+ break;
+ default:
+ /* replacement */
+ if (enabled) {
+ rc = iwrap->put(iwrap->closure_put, name, c != '&', iwrap->file);
+ if (rc < 0)
+ return rc;
+ }
+ break;
+ }
+ }
+}
+
+int mustach_file(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, FILE *file)
+{
+ int rc;
+ struct iwrap iwrap;
+
+ /* check validity */
+ if (!itf->enter || !itf->next || !itf->leave || (!itf->put && !itf->get))
+ return MUSTACH_ERROR_INVALID_ITF;
+
+ /* init wrap structure */
+ iwrap.closure = closure;
+ if (itf->put) {
+ iwrap.put = itf->put;
+ iwrap.closure_put = closure;
+ } else {
+ iwrap.put = iwrap_put;
+ iwrap.closure_put = &iwrap;
+ }
+ if (itf->partial) {
+ iwrap.partial = itf->partial;
+ iwrap.closure_partial = closure;
+ } else if (itf->get) {
+ iwrap.partial = itf->get;
+ iwrap.closure_partial = closure;
+ } else {
+ iwrap.partial = iwrap_partial;
+ iwrap.closure_partial = &iwrap;
+ }
+ iwrap.emit = itf->emit ? itf->emit : iwrap_emit;
+ iwrap.enter = itf->enter;
+ iwrap.next = itf->next;
+ iwrap.leave = itf->leave;
+ iwrap.get = itf->get;
+ iwrap.file = file;
+ iwrap.flags = flags;
+ iwrap.nesting = 0;
+
+ /* process */
+ rc = itf->start ? itf->start(closure) : 0;
+ if (rc == 0)
+ rc = process(template, length, &iwrap, NULL);
+ if (itf->stop)
+ itf->stop(closure, rc);
+ return rc;
+}
+
+int mustach_fd(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, int fd)
+{
+ int rc;
+ FILE *file;
+
+ file = fdopen(fd, "w");
+ if (file == NULL) {
+ rc = MUSTACH_ERROR_SYSTEM;
+ errno = ENOMEM;
+ } else {
+ rc = mustach_file(template, length, itf, closure, flags, file);
+ fclose(file);
+ }
+ return rc;
+}
+
+int mustach_mem(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, char **result, size_t *size)
+{
+ int rc;
+ FILE *file;
+ size_t s;
+
+ *result = NULL;
+ if (size == NULL)
+ size = &s;
+ file = memfile_open(result, size);
+ if (file == NULL)
+ rc = MUSTACH_ERROR_SYSTEM;
+ else {
+ rc = mustach_file(template, length, itf, closure, flags, file);
+ if (rc < 0)
+ memfile_abort(file, result, size);
+ else
+ rc = memfile_close(file, result, size);
+ }
+ return rc;
+}
+
+int fmustach(const char *template, const struct mustach_itf *itf, void *closure, FILE *file)
+{
+ return mustach_file(template, 0, itf, closure, Mustach_With_AllExtensions, file);
+}
+
+int fdmustach(const char *template, const struct mustach_itf *itf, void *closure, int fd)
+{
+ return mustach_fd(template, 0, itf, closure, Mustach_With_AllExtensions, fd);
+}
+
+int mustach(const char *template, const struct mustach_itf *itf, void *closure, char **result, size_t *size)
+{
+ return mustach_mem(template, 0, itf, closure, Mustach_With_AllExtensions, result, size);
+}
+
diff --git a/src/templating/mustach.h b/src/templating/mustach.h
new file mode 100644
index 000000000..1b44582d5
--- /dev/null
+++ b/src/templating/mustach.h
@@ -0,0 +1,319 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _mustach_h_included_
+#define _mustach_h_included_
+
+struct mustach_sbuf; /* see below */
+
+/**
+ * Current version of mustach and its derivates
+ */
+#define MUSTACH_VERSION 102
+#define MUSTACH_VERSION_MAJOR (MUSTACH_VERSION / 100)
+#define MUSTACH_VERSION_MINOR (MUSTACH_VERSION % 100)
+
+/**
+ * Maximum nested section supported
+ */
+#define MUSTACH_MAX_DEPTH 256
+
+/**
+ * Maximum nested template supported
+ */
+#define MUSTACH_MAX_NESTING 64
+
+/**
+ * Maximum length of tags in mustaches {{...}}
+ */
+#define MUSTACH_MAX_LENGTH 4096
+
+/**
+ * Maximum length of delimitors (2 normally but extended here)
+ */
+#define MUSTACH_MAX_DELIM_LENGTH 8
+
+/**
+ * Flags specific to mustach core
+ */
+#define Mustach_With_NoExtensions 0
+#define Mustach_With_Colon 1
+#define Mustach_With_EmptyTag 2
+#define Mustach_With_AllExtensions 3
+
+/*
+ * Definition of error codes returned by mustach
+ */
+#define MUSTACH_OK 0
+#define MUSTACH_ERROR_SYSTEM -1
+#define MUSTACH_ERROR_UNEXPECTED_END -2
+#define MUSTACH_ERROR_EMPTY_TAG -3
+#define MUSTACH_ERROR_TAG_TOO_LONG -4
+#define MUSTACH_ERROR_BAD_SEPARATORS -5
+#define MUSTACH_ERROR_TOO_DEEP -6
+#define MUSTACH_ERROR_CLOSING -7
+#define MUSTACH_ERROR_BAD_UNESCAPE_TAG -8
+#define MUSTACH_ERROR_INVALID_ITF -9
+#define MUSTACH_ERROR_ITEM_NOT_FOUND -10
+#define MUSTACH_ERROR_PARTIAL_NOT_FOUND -11
+#define MUSTACH_ERROR_UNDEFINED_TAG -12
+#define MUSTACH_ERROR_TOO_MUCH_NESTING -13
+
+/*
+ * You can use definition below for user specific error
+ *
+ * The macro MUSTACH_ERROR_USER is involutive so for any value
+ * value = MUSTACH_ERROR_USER(MUSTACH_ERROR_USER(value))
+ */
+#define MUSTACH_ERROR_USER_BASE -100
+#define MUSTACH_ERROR_USER(x) (MUSTACH_ERROR_USER_BASE-(x))
+#define MUSTACH_IS_ERROR_USER(x) (MUSTACH_ERROR_USER(x) >= 0)
+
+/**
+ * mustach_itf - pure abstract mustach - interface for callbacks
+ *
+ * The functions enter and next should return 0 or 1.
+ *
+ * All other functions should normally return MUSTACH_OK (zero).
+ *
+ * If any function returns a negative value, it means an error that
+ * stop the processing and that is reported to the caller. Mustach
+ * also has its own error codes. Using the macros MUSTACH_ERROR_USER
+ * and MUSTACH_IS_ERROR_USER could help to avoid clashes.
+ *
+ * @start: If defined (can be NULL), starts the mustach processing
+ * of the closure, called at the very beginning before any
+ * mustach processing occurs.
+ *
+ * @put: If defined (can be NULL), writes the value of 'name'
+ * to 'file' with 'escape' or not.
+ * As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be
+ * the empty string. In that later case an implementation can
+ * return MUSTACH_ERROR_EMPTY_TAG to refuse empty names.
+ * If NULL and 'get' NULL the error MUSTACH_ERROR_INVALID_ITF
+ * is returned.
+ *
+ * @enter: Enters the section of 'name' if possible.
+ * Musts return 1 if entered or 0 if not entered.
+ * When 1 is returned, the function 'leave' will always be called.
+ * Conversely 'leave' is never called when enter returns 0 or
+ * a negative value.
+ * When 1 is returned, the function must activate the first
+ * item of the section.
+ *
+ * @next: Activates the next item of the section if it exists.
+ * Musts return 1 when the next item is activated.
+ * Musts return 0 when there is no item to activate.
+ *
+ * @leave: Leaves the last entered section
+ *
+ * @partial: If defined (can be NULL), returns in 'sbuf' the content of the
+ * partial of 'name'. @see mustach_sbuf
+ * If NULL but 'get' not NULL, 'get' is used instead of partial.
+ * If NULL and 'get' NULL and 'put' not NULL, 'put' is called with
+ * a true FILE.
+ *
+ * @emit: If defined (can be NULL), writes the 'buffer' of 'size' with 'escape'.
+ * If NULL the standard function 'fwrite' is used with a true FILE.
+ * If not NULL that function is called instead of 'fwrite' to output
+ * text.
+ * It implies that if you define either 'partial' or 'get' callback,
+ * the meaning of 'FILE *file' is abstract for mustach's process and
+ * then you can use 'FILE*file' pass any kind of pointer (including NULL)
+ * to the function 'fmustach'. An example of a such behaviour is given by
+ * the implementation of 'mustach_json_c_write'.
+ *
+ * @get: If defined (can be NULL), returns in 'sbuf' the value of 'name'.
+ * As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be
+ * the empty string. In that later case an implementation can
+ * return MUSTACH_ERROR_EMPTY_TAG to refuse empty names.
+ * If 'get' is NULL and 'put' NULL the error MUSTACH_ERROR_INVALID_ITF
+ * is returned.
+ *
+ * @stop: If defined (can be NULL), stops the mustach processing
+ * of the closure, called at the very end after all mustach
+ * processing occurered. The status returned by the processing
+ * is passed to the stop.
+ *
+ * The array below summarize status of callbacks:
+ *
+ * FULLY OPTIONAL: start partial
+ * MANDATORY: enter next leave
+ * COMBINATORIAL: put emit get
+ *
+ * Not definig a MANDATORY callback returns error MUSTACH_ERROR_INVALID_ITF.
+ *
+ * For COMBINATORIAL callbacks the array below summarize possible combinations:
+ *
+ * combination : put : emit : get : abstract FILE
+ * -------------+---------+---------+---------+-----------------------
+ * HISTORIC : defined : NULL : NULL : NO: standard FILE
+ * MINIMAL : NULL : NULL : defined : NO: standard FILE
+ * CUSTOM : NULL : defined : defined : YES: abstract FILE
+ * DUCK : defined : NULL : defined : NO: standard FILE
+ * DANGEROUS : defined : defined : any : YES or NO, depends on 'partial'
+ * INVALID : NULL : any : NULL : -
+ *
+ * The DUCK case runs on one leg. 'get' is not used if 'partial' is defined
+ * but is used for 'partial' if 'partial' is NULL. Thus for clarity, do not use
+ * it that way but define 'partial' and let 'get' be NULL.
+ *
+ * The DANGEROUS case is special: it allows abstract FILE if 'partial' is defined
+ * but forbids abstract FILE when 'partial' is NULL.
+ *
+ * The INVALID case returns error MUSTACH_ERROR_INVALID_ITF.
+ */
+struct mustach_itf {
+ int (*start)(void *closure);
+ int (*put)(void *closure, const char *name, int escape, FILE *file);
+ int (*enter)(void *closure, const char *name);
+ int (*next)(void *closure);
+ int (*leave)(void *closure);
+ int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf);
+ int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file);
+ int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf);
+ void (*stop)(void *closure, int status);
+};
+
+/**
+ * mustach_sbuf - Interface for handling zero terminated strings
+ *
+ * That structure is used for returning zero terminated strings -in 'value'-
+ * to mustach. The callee can provide a function for releasing the returned
+ * 'value'. Three methods for releasing the string are possible.
+ *
+ * 1. no release: set either 'freecb' or 'releasecb' with NULL (done by default)
+ * 2. release without closure: set 'freecb' to its expected value
+ * 3. release with closure: set 'releasecb' and 'closure' to their expected values
+ *
+ * @value: The value of the string. That value is not changed by mustach -const-.
+ *
+ * @freecb: The function to call for freeing the value without closure.
+ * For convenience, signature of that callback is compatible with 'free'.
+ * Can be NULL.
+ *
+ * @releasecb: The function to release with closure.
+ * Can be NULL.
+ *
+ * @closure: The closure to use for 'releasecb'.
+ *
+ * @length: Length of the value or zero if unknown and value null terminated.
+ * To return the empty string, let it to zero and let value to NULL.
+ */
+struct mustach_sbuf {
+ const char *value;
+ union {
+ void (*freecb)(void*);
+ void (*releasecb)(const char *value, void *closure);
+ };
+ void *closure;
+ size_t length;
+};
+
+/**
+ * mustach_file - Renders the mustache 'template' in 'file' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface to the functions that mustach calls
+ * @closure: the closure to pass to functions called
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_file(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, FILE *file);
+
+/**
+ * mustach_fd - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface to the functions that mustach calls
+ * @closure: the closure to pass to functions called
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_fd(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, int fd);
+
+/**
+ * mustach_mem - Renders the mustache 'template' in 'result' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface to the functions that mustach calls
+ * @closure: the closure to pass to functions called
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_mem(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, char **result, size_t *size);
+
+/***************************************************************************
+* compatibility with version before 1.0
+*/
+#ifdef __GNUC__
+#define DEPRECATED_MUSTACH(func) func __attribute__ ((deprecated))
+#elif defined(_MSC_VER)
+#define DEPRECATED_MUSTACH(func) __declspec(deprecated) func
+#elif !defined(DEPRECATED_MUSTACH)
+#pragma message("WARNING: You need to implement DEPRECATED_MUSTACH for this compiler")
+#define DEPRECATED_MUSTACH(func) func
+#endif
+/**
+ * OBSOLETE use mustach_file
+ *
+ * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate, null terminated
+ * @itf: the interface to the functions that mustach calls
+ * @closure: the closure to pass to functions called
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+DEPRECATED_MUSTACH(extern int fmustach(const char *template, const struct mustach_itf *itf, void *closure, FILE *file));
+
+/**
+ * OBSOLETE use mustach_fd
+ *
+ * fdmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate, null terminated
+ * @itf: the interface to the functions that mustach calls
+ * @closure: the closure to pass to functions called
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+DEPRECATED_MUSTACH(extern int fdmustach(const char *template, const struct mustach_itf *itf, void *closure, int fd));
+
+/**
+ * OBSOLETE use mustach_mem
+ *
+ * mustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate, null terminated
+ * @itf: the interface to the functions that mustach calls
+ * @closure: the closure to pass to functions called
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+DEPRECATED_MUSTACH(extern int mustach(const char *template, const struct mustach_itf *itf, void *closure, char **result, size_t *size));
+
+#endif
+
diff --git a/src/templating/pkgcfgs b/src/templating/pkgcfgs
new file mode 100644
index 000000000..c3e84dc0e
--- /dev/null
+++ b/src/templating/pkgcfgs
@@ -0,0 +1,35 @@
+==libmustach.pc==
+Name: libmustach
+Version: VERSION
+Description: C Mustach single library
+Cflags: -Imustach
+Libs: -lmustach
+
+==libmustach-core.pc==
+Name: libmustach-core
+Version: VERSION
+Description: C Mustach core library
+Cflags: -Imustach
+Libs: -lmustach-core
+
+==libmustach-cjson.pc==
+Name: libmustach-cjson
+Version: VERSION
+Description: C Mustach library for cJSON
+Cflags: -Imustach
+Libs: -lmustach-cjson
+
+==libmustach-json-c.pc==
+Name: libmustach-json-c
+Version: VERSION
+Description: C Mustach library for json-c
+Cflags: -Imustach
+Libs: -lmustach-json-c
+
+==libmustach-jansson.pc==
+Name: libmustach-jansson
+Version: VERSION
+Description: C Mustach library for jansson
+Cflags: -Imustach
+Libs: -lmustach-jansson
+
diff --git a/src/templating/run-original-tests.sh b/src/templating/run-original-tests.sh
new file mode 100755
index 000000000..21481a286
--- /dev/null
+++ b/src/templating/run-original-tests.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+# This file is in the public domain.
+set -eux
+
+export CFLAGS="-g"
+
+echo "Ensuring clean state on entry to upstream tests ..."
+make clean
+
+# The build fails if libjson-c-dev is not installed.
+# That's OK, we don't otherwise need it and don't
+# even bother testing for it in configure.ac.
+# However, in that case, skip the test suite.
+make -f mustach-original-Makefile mustach mustach-json-c.o || exit 77
+make -f mustach-original-Makefile clean || true
+make -f mustach-original-Makefile basic-tests
+make -f mustach-original-Makefile clean || true
+
+exit 0
diff --git a/src/templating/templating_api.c b/src/templating/templating_api.c
new file mode 100644
index 000000000..88a17c682
--- /dev/null
+++ b/src/templating/templating_api.c
@@ -0,0 +1,524 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 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/>
+*/
+/**
+ * @file templating_api.c
+ * @brief logic to load and complete HTML templates
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_mhd_lib.h"
+#include "taler_templating_lib.h"
+#include "mustach.h"
+#include "mustach-jansson.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * Entry in a key-value array we use to cache templates.
+ */
+struct TVE
+{
+ /**
+ * A name, used as the key. NULL for the last entry.
+ */
+ char *name;
+
+ /**
+ * Language the template is in.
+ */
+ char *lang;
+
+ /**
+ * 0-terminated (!) file data to return for @e name and @e lang.
+ */
+ char *value;
+
+};
+
+
+/**
+ * Array of templates loaded into RAM.
+ */
+static struct TVE *loaded;
+
+/**
+ * Length of the #loaded array.
+ */
+static unsigned int loaded_length;
+
+
+/**
+ * Load Mustach template into memory. Note that we intentionally cache
+ * failures, that is if we ever failed to load a template, we will never try
+ * again.
+ *
+ * @param connection the connection we act upon
+ * @param name name of the template file to load
+ * (MUST be a 'static' string in memory!)
+ * @return NULL on error, otherwise the template
+ */
+static const char *
+lookup_template (struct MHD_Connection *connection,
+ const char *name)
+{
+ struct TVE *best = NULL;
+ const char *lang;
+
+ lang = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == lang)
+ lang = "en";
+ /* find best match by language */
+ for (unsigned int i = 0; i<loaded_length; i++)
+ {
+ if (0 != strcmp (loaded[i].name,
+ name))
+ continue; /* does not match by name */
+ if ( (NULL == best) ||
+ (TALER_language_matches (lang,
+ loaded[i].lang) >
+ TALER_language_matches (lang,
+ best->lang) ) )
+ best = &loaded[i];
+ }
+ if (NULL == best)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No templates found for `%s'\n",
+ name);
+ return NULL;
+ }
+ return best->value;
+}
+
+
+/**
+ * Get the base URL for static resources.
+ *
+ * @param con the MHD connection
+ * @param instance_id the instance ID
+ * @returns the static files base URL, guaranteed
+ * to have a trailing slash.
+ */
+static char *
+make_static_url (struct MHD_Connection *con,
+ const char *instance_id)
+{
+ const char *host;
+ const char *forwarded_host;
+ const char *uri_path;
+ struct GNUNET_Buffer buf = { 0 };
+
+ host = MHD_lookup_connection_value (con,
+ MHD_HEADER_KIND,
+ "Host");
+ forwarded_host = MHD_lookup_connection_value (con,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Host");
+
+ uri_path = MHD_lookup_connection_value (con,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Prefix");
+ if (NULL != forwarded_host)
+ host = forwarded_host;
+
+ if (NULL == host)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ GNUNET_assert (NULL != instance_id);
+
+ if (GNUNET_NO == TALER_mhd_is_https (con))
+ GNUNET_buffer_write_str (&buf,
+ "http://");
+ else
+ GNUNET_buffer_write_str (&buf,
+ "https://");
+ GNUNET_buffer_write_str (&buf,
+ host);
+ if (NULL != uri_path)
+ GNUNET_buffer_write_path (&buf,
+ uri_path);
+ if (0 != strcmp ("default",
+ instance_id))
+ {
+ GNUNET_buffer_write_path (&buf,
+ "instances");
+ GNUNET_buffer_write_path (&buf,
+ instance_id);
+ }
+ GNUNET_buffer_write_path (&buf,
+ "static/");
+ return GNUNET_buffer_reap_str (&buf);
+}
+
+
+int
+TALER_TEMPLATING_fill (const char *tmpl,
+ const json_t *root,
+ void **result,
+ size_t *result_size)
+{
+ int eno;
+
+ if (0 !=
+ (eno = mustach_jansson_mem (tmpl,
+ 0, /* length of tmpl */
+ (json_t *) root,
+ Mustach_With_AllExtensions,
+ (char **) result,
+ result_size)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "mustach failed on template with error %d\n",
+ eno);
+ *result = NULL;
+ *result_size = 0;
+ return eno;
+ }
+ return eno;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TEMPLATING_build (struct MHD_Connection *connection,
+ unsigned int *http_status,
+ const char *template,
+ const char *instance_id,
+ const char *taler_uri,
+ const json_t *root,
+ struct MHD_Response **reply)
+{
+ char *body;
+ size_t body_size;
+
+ {
+ const char *tmpl;
+ int eno;
+
+ tmpl = lookup_template (connection,
+ template);
+ if (NULL == tmpl)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load template `%s'\n",
+ template);
+ *http_status = MHD_HTTP_NOT_ACCEPTABLE;
+ *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_LOAD_TEMPLATE,
+ template);
+ return GNUNET_NO;
+ }
+ /* Add default values to the context */
+ if (NULL != instance_id)
+ {
+ char *static_url = make_static_url (connection,
+ instance_id);
+
+ GNUNET_break (0 ==
+ json_object_set_new ((json_t *) root,
+ "static_url",
+ json_string (static_url)));
+ GNUNET_free (static_url);
+ }
+ if (0 !=
+ (eno = mustach_jansson_mem (tmpl,
+ 0,
+ (json_t *) root,
+ Mustach_With_NoExtensions,
+ &body,
+ &body_size)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "mustach failed on template `%s' with error %d\n",
+ template,
+ eno);
+ *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
+ template);
+ return GNUNET_NO;
+ }
+ }
+
+ /* try to compress reply if client allows it */
+ {
+ bool compressed = false;
+
+ if (MHD_YES ==
+ TALER_MHD_can_compress (connection))
+ {
+ compressed = TALER_MHD_body_compress ((void **) &body,
+ &body_size);
+ }
+ *reply = MHD_create_response_from_buffer (body_size,
+ body,
+ MHD_RESPMEM_MUST_FREE);
+ if (NULL == *reply)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (compressed)
+ {
+ if (MHD_NO ==
+ MHD_add_response_header (*reply,
+ MHD_HTTP_HEADER_CONTENT_ENCODING,
+ "deflate"))
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (*reply);
+ *reply = NULL;
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+
+ /* Add standard headers */
+ if (NULL != taler_uri)
+ GNUNET_break (MHD_NO !=
+ MHD_add_response_header (*reply,
+ "Taler",
+ taler_uri));
+ GNUNET_break (MHD_NO !=
+ MHD_add_response_header (*reply,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/html"));
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TEMPLATING_reply (struct MHD_Connection *connection,
+ unsigned int http_status,
+ const char *template,
+ const char *instance_id,
+ const char *taler_uri,
+ const json_t *root)
+{
+ enum GNUNET_GenericReturnValue res;
+ struct MHD_Response *reply;
+ MHD_RESULT ret;
+
+ res = TALER_TEMPLATING_build (connection,
+ &http_status,
+ template,
+ instance_id,
+ taler_uri,
+ root,
+ &reply);
+ if (GNUNET_SYSERR == res)
+ return res;
+ ret = MHD_queue_response (connection,
+ http_status,
+ reply);
+ MHD_destroy_response (reply);
+ if (MHD_NO == ret)
+ return GNUNET_SYSERR;
+ return (res == GNUNET_OK)
+ ? GNUNET_OK
+ : GNUNET_NO;
+}
+
+
+/**
+ * Function called with a template's filename.
+ *
+ * @param cls closure, NULL
+ * @param filename complete filename (absolute path)
+ * @return #GNUNET_OK to continue to iterate,
+ * #GNUNET_NO to stop iteration with no error,
+ * #GNUNET_SYSERR to abort iteration with error!
+ */
+static enum GNUNET_GenericReturnValue
+load_template (void *cls,
+ const char *filename)
+{
+ char *lang;
+ char *end;
+ int fd;
+ struct stat sb;
+ char *map;
+ const char *name;
+
+ (void) cls;
+ if ('.' == filename[0])
+ return GNUNET_OK;
+ name = strrchr (filename,
+ '/');
+ if (NULL == name)
+ name = filename;
+ else
+ name++;
+ lang = strchr (name,
+ '.');
+ if (NULL == lang)
+ return GNUNET_OK; /* name must include .$LANG */
+ lang++;
+ end = strchr (lang,
+ '.');
+ if ( (NULL == end) ||
+ (0 != strcmp (end,
+ ".must")) )
+ return GNUNET_OK; /* name must end with '.must' */
+
+ /* finally open template */
+ fd = open (filename,
+ O_RDONLY);
+ if (-1 == fd)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ filename);
+
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ fstat (fd,
+ &sb))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "fstat",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ map = GNUNET_malloc_large (sb.st_size + 1);
+ if (NULL == map)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "malloc");
+ GNUNET_break (0 == close (fd));
+ return GNUNET_SYSERR;
+ }
+ if (sb.st_size !=
+ read (fd,
+ map,
+ sb.st_size))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "read",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ GNUNET_break (0 == close (fd));
+ GNUNET_array_grow (loaded,
+ loaded_length,
+ loaded_length + 1);
+ loaded[loaded_length - 1].name = GNUNET_strndup (name,
+ (lang - 1) - name);
+ loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
+ end - lang);
+ loaded[loaded_length - 1].value = map;
+ return GNUNET_OK;
+}
+
+
+MHD_RESULT
+TALER_TEMPLATING_reply_error (
+ struct MHD_Connection *connection,
+ const char *template_basename,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const char *detail)
+{
+ json_t *data;
+ enum GNUNET_GenericReturnValue ret;
+
+ data = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("ec",
+ ec),
+ GNUNET_JSON_pack_string ("hint",
+ TALER_ErrorCode_get_hint (ec)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("detail",
+ detail))
+ );
+ ret = TALER_TEMPLATING_reply (connection,
+ http_status,
+ template_basename,
+ NULL,
+ NULL,
+ data);
+ json_decref (data);
+ switch (ret)
+ {
+ case GNUNET_OK:
+ return MHD_YES;
+ case GNUNET_NO:
+ return MHD_YES;
+ case GNUNET_SYSERR:
+ return MHD_NO;
+ }
+ GNUNET_assert (0);
+ return MHD_NO;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TEMPLATING_init (const char *subsystem)
+{
+ char *dn;
+ int ret;
+
+ {
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_asprintf (&dn,
+ "%s/%s/templates/",
+ path,
+ subsystem);
+ GNUNET_free (path);
+ }
+ ret = GNUNET_DISK_directory_scan (dn,
+ &load_template,
+ NULL);
+ GNUNET_free (dn);
+ if (-1 == ret)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+void
+TALER_TEMPLATING_done (void)
+{
+ for (unsigned int i = 0; i<loaded_length; i++)
+ {
+ GNUNET_free (loaded[i].name);
+ GNUNET_free (loaded[i].lang);
+ GNUNET_free (loaded[i].value);
+ }
+ GNUNET_array_grow (loaded,
+ loaded_length,
+ 0);
+}
+
+
+/* end of templating_api.c */
diff --git a/src/templating/test-specs/test-specs-cjson.ref b/src/templating/test-specs/test-specs-cjson.ref
new file mode 100644
index 000000000..8897c66cc
--- /dev/null
+++ b/src/templating/test-specs/test-specs-cjson.ref
@@ -0,0 +1,425 @@
+
+loading test-specs/spec/specs/comments.json
+processing file test-specs/spec/specs/comments.json
+[0] Inline
+ Comment blocks should be removed from the template.
+ => SUCCESS
+[1] Multiline
+ Multiline comments should be permitted.
+ => SUCCESS
+[2] Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[3] Indented Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[4] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[5] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[6] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[7] Multiline Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[8] Indented Multiline Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[9] Indented Inline
+ Inline comments should not strip whitespace
+ => SUCCESS
+[10] Surrounding Whitespace
+ Comment removal should preserve surrounding whitespace.
+ => SUCCESS
+[11] Variable Name Collision
+ Comments must never render, even if variable with same name exists.
+ => SUCCESS
+
+loading test-specs/spec/specs/delimiters.json
+processing file test-specs/spec/specs/delimiters.json
+[0] Pair Behavior
+ The equals sign (used on both sides) should permit delimiter changes.
+ => SUCCESS
+[1] Special Characters
+ Characters with special meaning regexen should be valid delimiters.
+ => SUCCESS
+[2] Sections
+ Delimiters set outside sections should persist.
+ => SUCCESS
+[3] Inverted Sections
+ Delimiters set outside inverted sections should persist.
+ => SUCCESS
+[4] Partial Inheritence
+ Delimiters set in a parent template should not affect a partial.
+ => SUCCESS
+[5] Post-Partial Behavior
+ Delimiters set in a partial should not affect the parent template.
+ => SUCCESS
+[6] Surrounding Whitespace
+ Surrounding whitespace should be left untouched.
+ => SUCCESS
+[7] Outlying Whitespace (Inline)
+ Whitespace should be left untouched.
+ => SUCCESS
+[8] Standalone Tag
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[9] Indented Standalone Tag
+ Indented standalone lines should be removed from the template.
+ => SUCCESS
+[10] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[11] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[12] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[13] Pair with Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/interpolation.json
+processing file test-specs/spec/specs/interpolation.json
+[0] No Interpolation
+ Mustache-free templates should render as-is.
+ => SUCCESS
+[1] Basic Interpolation
+ Unadorned tags should interpolate content into the template.
+ => SUCCESS
+[2] HTML Escaping
+ Basic interpolation should be HTML escaped.
+ => SUCCESS
+[3] Triple Mustache
+ Triple mustaches should interpolate without HTML escaping.
+ => SUCCESS
+[4] Ampersand
+ Ampersand should interpolate without HTML escaping.
+ => SUCCESS
+[5] Basic Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[6] Triple Mustache Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[7] Ampersand Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[8] Basic Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[9] Triple Mustache Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[10] Ampersand Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[11] Basic Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[12] Triple Mustache Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[13] Ampersand Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[14] Basic Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[15] Triple Mustache Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[16] Ampersand Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[17] Dotted Names - Basic Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[18] Dotted Names - Triple Mustache Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[19] Dotted Names - Ampersand Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[20] Dotted Names - Arbitrary Depth
+ Dotted names should be functional to any level of nesting.
+ => SUCCESS
+[21] Dotted Names - Broken Chains
+ Any falsey value prior to the last part of the name should yield ''.
+ => SUCCESS
+[22] Dotted Names - Broken Chain Resolution
+ Each part of a dotted name should resolve only against its parent.
+ => SUCCESS
+[23] Dotted Names - Initial Resolution
+ The first part of a dotted name should resolve as any other name.
+ => SUCCESS
+[24] Dotted Names - Context Precedence
+ Dotted names should be resolved against former resolutions.
+ => SUCCESS
+[25] Implicit Iterators - Basic Interpolation
+ Unadorned tags should interpolate content into the template.
+ => SUCCESS
+[26] Implicit Iterators - HTML Escaping
+ Basic interpolation should be HTML escaped.
+ => SUCCESS
+[27] Implicit Iterators - Triple Mustache
+ Triple mustaches should interpolate without HTML escaping.
+ => SUCCESS
+[28] Implicit Iterators - Ampersand
+ Ampersand should interpolate without HTML escaping.
+ => SUCCESS
+[29] Implicit Iterators - Basic Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[30] Interpolation - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[31] Triple Mustache - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[32] Ampersand - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[33] Interpolation - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[34] Triple Mustache - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[35] Ampersand - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[36] Interpolation With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+[37] Triple Mustache With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+[38] Ampersand With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/inverted.json
+processing file test-specs/spec/specs/inverted.json
+[0] Falsey
+ Falsey sections should have their contents rendered.
+ => SUCCESS
+[1] Truthy
+ Truthy sections should have their contents omitted.
+ => SUCCESS
+[2] Null is falsey
+ Null is falsey.
+ => SUCCESS
+[3] Context
+ Objects and hashes should behave like truthy values.
+ => SUCCESS
+[4] List
+ Lists should behave like truthy values.
+ => SUCCESS
+[5] Empty List
+ Empty lists should behave like falsey values.
+ => SUCCESS
+[6] Doubled
+ Multiple inverted sections per template should be permitted.
+ => SUCCESS
+[7] Nested (Falsey)
+ Nested falsey sections should have their contents rendered.
+ => SUCCESS
+[8] Nested (Truthy)
+ Nested truthy sections should be omitted.
+ => SUCCESS
+[9] Context Misses
+ Failed context lookups should be considered falsey.
+ => SUCCESS
+[10] Dotted Names - Truthy
+ Dotted names should be valid for Inverted Section tags.
+ => SUCCESS
+[11] Dotted Names - Falsey
+ Dotted names should be valid for Inverted Section tags.
+ => SUCCESS
+[12] Dotted Names - Broken Chains
+ Dotted names that cannot be resolved should be considered falsey.
+ => SUCCESS
+[13] Surrounding Whitespace
+ Inverted sections should not alter surrounding whitespace.
+ => SUCCESS
+[14] Internal Whitespace
+ Inverted should not alter internal whitespace.
+ => SUCCESS
+[15] Indented Inline Sections
+ Single-line sections should not alter surrounding whitespace.
+ => SUCCESS
+[16] Standalone Lines
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[17] Standalone Indented Lines
+ Standalone indented lines should be removed from the template.
+ => SUCCESS
+[18] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[19] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[20] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[21] Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/partials.json
+processing file test-specs/spec/specs/partials.json
+[0] Basic Behavior
+ The greater-than operator should expand to the named partial.
+ => SUCCESS
+[1] Failed Lookup
+ The empty string should be used when the named partial is not found.
+ => SUCCESS
+[2] Context
+ The greater-than operator should operate within the current context.
+ => SUCCESS
+[3] Recursion
+ The greater-than operator should properly recurse.
+ => SUCCESS
+[4] Nested
+ The greater-than operator should work from within partials.
+ => SUCCESS
+[5] Surrounding Whitespace
+ The greater-than operator should not alter surrounding whitespace.
+ => SUCCESS
+[6] Inline Indentation
+ Whitespace should be left untouched.
+ => SUCCESS
+[7] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[8] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[9] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[10] Standalone Indentation
+ Each line of the partial should be indented before rendering.
+ => SUCCESS
+[11] Padding Whitespace
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/sections.json
+processing file test-specs/spec/specs/sections.json
+[0] Truthy
+ Truthy sections should have their contents rendered.
+ => SUCCESS
+[1] Falsey
+ Falsey sections should have their contents omitted.
+ => SUCCESS
+[2] Null is falsey
+ Null is falsey.
+ => SUCCESS
+[3] Context
+ Objects and hashes should be pushed onto the context stack.
+ => SUCCESS
+[4] Parent contexts
+ Names missing in the current context are looked up in the stack.
+ => SUCCESS
+[5] Variable test
+ Non-false sections have their value at the top of context,
+accessible as {{.}} or through the parent context. This gives
+a simple way to display content conditionally if a variable exists.
+
+ => SUCCESS
+[6] List Contexts
+ All elements on the context stack should be accessible within lists.
+ => SUCCESS
+[7] Deeply Nested Contexts
+ All elements on the context stack should be accessible.
+ => SUCCESS
+[8] List
+ Lists should be iterated; list items should visit the context stack.
+ => SUCCESS
+[9] Empty List
+ Empty lists should behave like falsey values.
+ => SUCCESS
+[10] Doubled
+ Multiple sections per template should be permitted.
+ => SUCCESS
+[11] Nested (Truthy)
+ Nested truthy sections should have their contents rendered.
+ => SUCCESS
+[12] Nested (Falsey)
+ Nested falsey sections should be omitted.
+ => SUCCESS
+[13] Context Misses
+ Failed context lookups should be considered falsey.
+ => SUCCESS
+[14] Implicit Iterator - String
+ Implicit iterators should directly interpolate strings.
+ => SUCCESS
+[15] Implicit Iterator - Integer
+ Implicit iterators should cast integers to strings and interpolate.
+ => SUCCESS
+[16] Implicit Iterator - Decimal
+ Implicit iterators should cast decimals to strings and interpolate.
+ => SUCCESS
+[17] Implicit Iterator - Array
+ Implicit iterators should allow iterating over nested arrays.
+ => SUCCESS
+[18] Implicit Iterator - HTML Escaping
+ Implicit iterators with basic interpolation should be HTML escaped.
+ => SUCCESS
+[19] Implicit Iterator - Triple mustache
+ Implicit iterators in triple mustache should interpolate without HTML escaping.
+ => SUCCESS
+[20] Implicit Iterator - Ampersand
+ Implicit iterators in an Ampersand tag should interpolate without HTML escaping.
+ => SUCCESS
+[21] Implicit Iterator - Root-level
+ Implicit iterators should work on root-level lists.
+ => SUCCESS
+[22] Dotted Names - Truthy
+ Dotted names should be valid for Section tags.
+ => SUCCESS
+[23] Dotted Names - Falsey
+ Dotted names should be valid for Section tags.
+ => SUCCESS
+[24] Dotted Names - Broken Chains
+ Dotted names that cannot be resolved should be considered falsey.
+ => SUCCESS
+[25] Surrounding Whitespace
+ Sections should not alter surrounding whitespace.
+ => SUCCESS
+[26] Internal Whitespace
+ Sections should not alter internal whitespace.
+ => SUCCESS
+[27] Indented Inline Sections
+ Single-line sections should not alter surrounding whitespace.
+ => SUCCESS
+[28] Standalone Lines
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[29] Indented Standalone Lines
+ Indented standalone lines should be removed from the template.
+ => SUCCESS
+[30] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[31] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[32] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[33] Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+summary:
+ error 0
+ differ 0
+ success 133
diff --git a/src/templating/test-specs/test-specs-jansson.ref b/src/templating/test-specs/test-specs-jansson.ref
new file mode 100644
index 000000000..a1cef19c1
--- /dev/null
+++ b/src/templating/test-specs/test-specs-jansson.ref
@@ -0,0 +1,429 @@
+
+loading test-specs/spec/specs/comments.json
+processing file test-specs/spec/specs/comments.json
+[0] Inline
+ Comment blocks should be removed from the template.
+ => SUCCESS
+[1] Multiline
+ Multiline comments should be permitted.
+ => SUCCESS
+[2] Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[3] Indented Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[4] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[5] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[6] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[7] Multiline Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[8] Indented Multiline Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[9] Indented Inline
+ Inline comments should not strip whitespace
+ => SUCCESS
+[10] Surrounding Whitespace
+ Comment removal should preserve surrounding whitespace.
+ => SUCCESS
+[11] Variable Name Collision
+ Comments must never render, even if variable with same name exists.
+ => SUCCESS
+
+loading test-specs/spec/specs/delimiters.json
+processing file test-specs/spec/specs/delimiters.json
+[0] Pair Behavior
+ The equals sign (used on both sides) should permit delimiter changes.
+ => SUCCESS
+[1] Special Characters
+ Characters with special meaning regexen should be valid delimiters.
+ => SUCCESS
+[2] Sections
+ Delimiters set outside sections should persist.
+ => SUCCESS
+[3] Inverted Sections
+ Delimiters set outside inverted sections should persist.
+ => SUCCESS
+[4] Partial Inheritence
+ Delimiters set in a parent template should not affect a partial.
+ => SUCCESS
+[5] Post-Partial Behavior
+ Delimiters set in a partial should not affect the parent template.
+ => SUCCESS
+[6] Surrounding Whitespace
+ Surrounding whitespace should be left untouched.
+ => SUCCESS
+[7] Outlying Whitespace (Inline)
+ Whitespace should be left untouched.
+ => SUCCESS
+[8] Standalone Tag
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[9] Indented Standalone Tag
+ Indented standalone lines should be removed from the template.
+ => SUCCESS
+[10] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[11] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[12] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[13] Pair with Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/interpolation.json
+processing file test-specs/spec/specs/interpolation.json
+[0] No Interpolation
+ Mustache-free templates should render as-is.
+ => SUCCESS
+[1] Basic Interpolation
+ Unadorned tags should interpolate content into the template.
+ => SUCCESS
+[2] HTML Escaping
+ Basic interpolation should be HTML escaped.
+ => SUCCESS
+[3] Triple Mustache
+ Triple mustaches should interpolate without HTML escaping.
+ => SUCCESS
+[4] Ampersand
+ Ampersand should interpolate without HTML escaping.
+ => SUCCESS
+[5] Basic Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[6] Triple Mustache Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[7] Ampersand Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[8] Basic Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[9] Triple Mustache Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[10] Ampersand Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[11] Basic Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[12] Triple Mustache Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[13] Ampersand Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[14] Basic Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[15] Triple Mustache Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[16] Ampersand Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[17] Dotted Names - Basic Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[18] Dotted Names - Triple Mustache Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[19] Dotted Names - Ampersand Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[20] Dotted Names - Arbitrary Depth
+ Dotted names should be functional to any level of nesting.
+ => SUCCESS
+[21] Dotted Names - Broken Chains
+ Any falsey value prior to the last part of the name should yield ''.
+ => SUCCESS
+[22] Dotted Names - Broken Chain Resolution
+ Each part of a dotted name should resolve only against its parent.
+ => SUCCESS
+[23] Dotted Names - Initial Resolution
+ The first part of a dotted name should resolve as any other name.
+ => SUCCESS
+[24] Dotted Names - Context Precedence
+ Dotted names should be resolved against former resolutions.
+ => SUCCESS
+[25] Implicit Iterators - Basic Interpolation
+ Unadorned tags should interpolate content into the template.
+ => SUCCESS
+[26] Implicit Iterators - HTML Escaping
+ Basic interpolation should be HTML escaped.
+ => SUCCESS
+[27] Implicit Iterators - Triple Mustache
+ Triple mustaches should interpolate without HTML escaping.
+ => SUCCESS
+[28] Implicit Iterators - Ampersand
+ Ampersand should interpolate without HTML escaping.
+ => SUCCESS
+[29] Implicit Iterators - Basic Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[30] Interpolation - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[31] Triple Mustache - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[32] Ampersand - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[33] Interpolation - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[34] Triple Mustache - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[35] Ampersand - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[36] Interpolation With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+[37] Triple Mustache With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+[38] Ampersand With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/inverted.json
+processing file test-specs/spec/specs/inverted.json
+[0] Falsey
+ Falsey sections should have their contents rendered.
+ => SUCCESS
+[1] Truthy
+ Truthy sections should have their contents omitted.
+ => SUCCESS
+[2] Null is falsey
+ Null is falsey.
+ => SUCCESS
+[3] Context
+ Objects and hashes should behave like truthy values.
+ => SUCCESS
+[4] List
+ Lists should behave like truthy values.
+ => SUCCESS
+[5] Empty List
+ Empty lists should behave like falsey values.
+ => SUCCESS
+[6] Doubled
+ Multiple inverted sections per template should be permitted.
+ => SUCCESS
+[7] Nested (Falsey)
+ Nested falsey sections should have their contents rendered.
+ => SUCCESS
+[8] Nested (Truthy)
+ Nested truthy sections should be omitted.
+ => SUCCESS
+[9] Context Misses
+ Failed context lookups should be considered falsey.
+ => SUCCESS
+[10] Dotted Names - Truthy
+ Dotted names should be valid for Inverted Section tags.
+ => SUCCESS
+[11] Dotted Names - Falsey
+ Dotted names should be valid for Inverted Section tags.
+ => SUCCESS
+[12] Dotted Names - Broken Chains
+ Dotted names that cannot be resolved should be considered falsey.
+ => SUCCESS
+[13] Surrounding Whitespace
+ Inverted sections should not alter surrounding whitespace.
+ => SUCCESS
+[14] Internal Whitespace
+ Inverted should not alter internal whitespace.
+ => SUCCESS
+[15] Indented Inline Sections
+ Single-line sections should not alter surrounding whitespace.
+ => SUCCESS
+[16] Standalone Lines
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[17] Standalone Indented Lines
+ Standalone indented lines should be removed from the template.
+ => SUCCESS
+[18] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[19] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[20] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[21] Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/partials.json
+processing file test-specs/spec/specs/partials.json
+[0] Basic Behavior
+ The greater-than operator should expand to the named partial.
+ => SUCCESS
+[1] Failed Lookup
+ The empty string should be used when the named partial is not found.
+ => SUCCESS
+[2] Context
+ The greater-than operator should operate within the current context.
+ => SUCCESS
+[3] Recursion
+ The greater-than operator should properly recurse.
+ => SUCCESS
+[4] Nested
+ The greater-than operator should work from within partials.
+ => SUCCESS
+[5] Surrounding Whitespace
+ The greater-than operator should not alter surrounding whitespace.
+ => SUCCESS
+[6] Inline Indentation
+ Whitespace should be left untouched.
+ => SUCCESS
+[7] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[8] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[9] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[10] Standalone Indentation
+ Each line of the partial should be indented before rendering.
+ => SUCCESS
+[11] Padding Whitespace
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/sections.json
+processing file test-specs/spec/specs/sections.json
+[0] Truthy
+ Truthy sections should have their contents rendered.
+ => SUCCESS
+[1] Falsey
+ Falsey sections should have their contents omitted.
+ => SUCCESS
+[2] Null is falsey
+ Null is falsey.
+ => SUCCESS
+[3] Context
+ Objects and hashes should be pushed onto the context stack.
+ => SUCCESS
+[4] Parent contexts
+ Names missing in the current context are looked up in the stack.
+ => SUCCESS
+[5] Variable test
+ Non-false sections have their value at the top of context,
+accessible as {{.}} or through the parent context. This gives
+a simple way to display content conditionally if a variable exists.
+
+ => SUCCESS
+[6] List Contexts
+ All elements on the context stack should be accessible within lists.
+ => SUCCESS
+[7] Deeply Nested Contexts
+ All elements on the context stack should be accessible.
+ => SUCCESS
+[8] List
+ Lists should be iterated; list items should visit the context stack.
+ => SUCCESS
+[9] Empty List
+ Empty lists should behave like falsey values.
+ => SUCCESS
+[10] Doubled
+ Multiple sections per template should be permitted.
+ => SUCCESS
+[11] Nested (Truthy)
+ Nested truthy sections should have their contents rendered.
+ => SUCCESS
+[12] Nested (Falsey)
+ Nested falsey sections should be omitted.
+ => SUCCESS
+[13] Context Misses
+ Failed context lookups should be considered falsey.
+ => SUCCESS
+[14] Implicit Iterator - String
+ Implicit iterators should directly interpolate strings.
+ => SUCCESS
+[15] Implicit Iterator - Integer
+ Implicit iterators should cast integers to strings and interpolate.
+ => SUCCESS
+[16] Implicit Iterator - Decimal
+ Implicit iterators should cast decimals to strings and interpolate.
+ => DIFFERS
+ .. DATA[{"list":[1.1000000000000001,2.2000000000000002,3.2999999999999998,4.4000000000000004,5.5]}]
+ .. TEMPLATE["{{#list}}({{.}}){{/list}}"]
+ .. EXPECTED["(1.1)(2.2)(3.3)(4.4)(5.5)"]
+ .. GOT["(1.1000000000000001)(2.2000000000000002)(3.2999999999999998)(4.4000000000000004)(5.5)"]
+[17] Implicit Iterator - Array
+ Implicit iterators should allow iterating over nested arrays.
+ => SUCCESS
+[18] Implicit Iterator - HTML Escaping
+ Implicit iterators with basic interpolation should be HTML escaped.
+ => SUCCESS
+[19] Implicit Iterator - Triple mustache
+ Implicit iterators in triple mustache should interpolate without HTML escaping.
+ => SUCCESS
+[20] Implicit Iterator - Ampersand
+ Implicit iterators in an Ampersand tag should interpolate without HTML escaping.
+ => SUCCESS
+[21] Implicit Iterator - Root-level
+ Implicit iterators should work on root-level lists.
+ => SUCCESS
+[22] Dotted Names - Truthy
+ Dotted names should be valid for Section tags.
+ => SUCCESS
+[23] Dotted Names - Falsey
+ Dotted names should be valid for Section tags.
+ => SUCCESS
+[24] Dotted Names - Broken Chains
+ Dotted names that cannot be resolved should be considered falsey.
+ => SUCCESS
+[25] Surrounding Whitespace
+ Sections should not alter surrounding whitespace.
+ => SUCCESS
+[26] Internal Whitespace
+ Sections should not alter internal whitespace.
+ => SUCCESS
+[27] Indented Inline Sections
+ Single-line sections should not alter surrounding whitespace.
+ => SUCCESS
+[28] Standalone Lines
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[29] Indented Standalone Lines
+ Indented standalone lines should be removed from the template.
+ => SUCCESS
+[30] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[31] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[32] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[33] Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+summary:
+ error 0
+ differ 1
+ success 132
diff --git a/src/templating/test-specs/test-specs-json-c.ref b/src/templating/test-specs/test-specs-json-c.ref
new file mode 100644
index 000000000..8897c66cc
--- /dev/null
+++ b/src/templating/test-specs/test-specs-json-c.ref
@@ -0,0 +1,425 @@
+
+loading test-specs/spec/specs/comments.json
+processing file test-specs/spec/specs/comments.json
+[0] Inline
+ Comment blocks should be removed from the template.
+ => SUCCESS
+[1] Multiline
+ Multiline comments should be permitted.
+ => SUCCESS
+[2] Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[3] Indented Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[4] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[5] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[6] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[7] Multiline Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[8] Indented Multiline Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[9] Indented Inline
+ Inline comments should not strip whitespace
+ => SUCCESS
+[10] Surrounding Whitespace
+ Comment removal should preserve surrounding whitespace.
+ => SUCCESS
+[11] Variable Name Collision
+ Comments must never render, even if variable with same name exists.
+ => SUCCESS
+
+loading test-specs/spec/specs/delimiters.json
+processing file test-specs/spec/specs/delimiters.json
+[0] Pair Behavior
+ The equals sign (used on both sides) should permit delimiter changes.
+ => SUCCESS
+[1] Special Characters
+ Characters with special meaning regexen should be valid delimiters.
+ => SUCCESS
+[2] Sections
+ Delimiters set outside sections should persist.
+ => SUCCESS
+[3] Inverted Sections
+ Delimiters set outside inverted sections should persist.
+ => SUCCESS
+[4] Partial Inheritence
+ Delimiters set in a parent template should not affect a partial.
+ => SUCCESS
+[5] Post-Partial Behavior
+ Delimiters set in a partial should not affect the parent template.
+ => SUCCESS
+[6] Surrounding Whitespace
+ Surrounding whitespace should be left untouched.
+ => SUCCESS
+[7] Outlying Whitespace (Inline)
+ Whitespace should be left untouched.
+ => SUCCESS
+[8] Standalone Tag
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[9] Indented Standalone Tag
+ Indented standalone lines should be removed from the template.
+ => SUCCESS
+[10] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[11] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[12] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[13] Pair with Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/interpolation.json
+processing file test-specs/spec/specs/interpolation.json
+[0] No Interpolation
+ Mustache-free templates should render as-is.
+ => SUCCESS
+[1] Basic Interpolation
+ Unadorned tags should interpolate content into the template.
+ => SUCCESS
+[2] HTML Escaping
+ Basic interpolation should be HTML escaped.
+ => SUCCESS
+[3] Triple Mustache
+ Triple mustaches should interpolate without HTML escaping.
+ => SUCCESS
+[4] Ampersand
+ Ampersand should interpolate without HTML escaping.
+ => SUCCESS
+[5] Basic Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[6] Triple Mustache Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[7] Ampersand Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[8] Basic Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[9] Triple Mustache Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[10] Ampersand Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[11] Basic Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[12] Triple Mustache Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[13] Ampersand Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[14] Basic Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[15] Triple Mustache Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[16] Ampersand Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[17] Dotted Names - Basic Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[18] Dotted Names - Triple Mustache Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[19] Dotted Names - Ampersand Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[20] Dotted Names - Arbitrary Depth
+ Dotted names should be functional to any level of nesting.
+ => SUCCESS
+[21] Dotted Names - Broken Chains
+ Any falsey value prior to the last part of the name should yield ''.
+ => SUCCESS
+[22] Dotted Names - Broken Chain Resolution
+ Each part of a dotted name should resolve only against its parent.
+ => SUCCESS
+[23] Dotted Names - Initial Resolution
+ The first part of a dotted name should resolve as any other name.
+ => SUCCESS
+[24] Dotted Names - Context Precedence
+ Dotted names should be resolved against former resolutions.
+ => SUCCESS
+[25] Implicit Iterators - Basic Interpolation
+ Unadorned tags should interpolate content into the template.
+ => SUCCESS
+[26] Implicit Iterators - HTML Escaping
+ Basic interpolation should be HTML escaped.
+ => SUCCESS
+[27] Implicit Iterators - Triple Mustache
+ Triple mustaches should interpolate without HTML escaping.
+ => SUCCESS
+[28] Implicit Iterators - Ampersand
+ Ampersand should interpolate without HTML escaping.
+ => SUCCESS
+[29] Implicit Iterators - Basic Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[30] Interpolation - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[31] Triple Mustache - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[32] Ampersand - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[33] Interpolation - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[34] Triple Mustache - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[35] Ampersand - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[36] Interpolation With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+[37] Triple Mustache With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+[38] Ampersand With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/inverted.json
+processing file test-specs/spec/specs/inverted.json
+[0] Falsey
+ Falsey sections should have their contents rendered.
+ => SUCCESS
+[1] Truthy
+ Truthy sections should have their contents omitted.
+ => SUCCESS
+[2] Null is falsey
+ Null is falsey.
+ => SUCCESS
+[3] Context
+ Objects and hashes should behave like truthy values.
+ => SUCCESS
+[4] List
+ Lists should behave like truthy values.
+ => SUCCESS
+[5] Empty List
+ Empty lists should behave like falsey values.
+ => SUCCESS
+[6] Doubled
+ Multiple inverted sections per template should be permitted.
+ => SUCCESS
+[7] Nested (Falsey)
+ Nested falsey sections should have their contents rendered.
+ => SUCCESS
+[8] Nested (Truthy)
+ Nested truthy sections should be omitted.
+ => SUCCESS
+[9] Context Misses
+ Failed context lookups should be considered falsey.
+ => SUCCESS
+[10] Dotted Names - Truthy
+ Dotted names should be valid for Inverted Section tags.
+ => SUCCESS
+[11] Dotted Names - Falsey
+ Dotted names should be valid for Inverted Section tags.
+ => SUCCESS
+[12] Dotted Names - Broken Chains
+ Dotted names that cannot be resolved should be considered falsey.
+ => SUCCESS
+[13] Surrounding Whitespace
+ Inverted sections should not alter surrounding whitespace.
+ => SUCCESS
+[14] Internal Whitespace
+ Inverted should not alter internal whitespace.
+ => SUCCESS
+[15] Indented Inline Sections
+ Single-line sections should not alter surrounding whitespace.
+ => SUCCESS
+[16] Standalone Lines
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[17] Standalone Indented Lines
+ Standalone indented lines should be removed from the template.
+ => SUCCESS
+[18] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[19] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[20] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[21] Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/partials.json
+processing file test-specs/spec/specs/partials.json
+[0] Basic Behavior
+ The greater-than operator should expand to the named partial.
+ => SUCCESS
+[1] Failed Lookup
+ The empty string should be used when the named partial is not found.
+ => SUCCESS
+[2] Context
+ The greater-than operator should operate within the current context.
+ => SUCCESS
+[3] Recursion
+ The greater-than operator should properly recurse.
+ => SUCCESS
+[4] Nested
+ The greater-than operator should work from within partials.
+ => SUCCESS
+[5] Surrounding Whitespace
+ The greater-than operator should not alter surrounding whitespace.
+ => SUCCESS
+[6] Inline Indentation
+ Whitespace should be left untouched.
+ => SUCCESS
+[7] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[8] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[9] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[10] Standalone Indentation
+ Each line of the partial should be indented before rendering.
+ => SUCCESS
+[11] Padding Whitespace
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/sections.json
+processing file test-specs/spec/specs/sections.json
+[0] Truthy
+ Truthy sections should have their contents rendered.
+ => SUCCESS
+[1] Falsey
+ Falsey sections should have their contents omitted.
+ => SUCCESS
+[2] Null is falsey
+ Null is falsey.
+ => SUCCESS
+[3] Context
+ Objects and hashes should be pushed onto the context stack.
+ => SUCCESS
+[4] Parent contexts
+ Names missing in the current context are looked up in the stack.
+ => SUCCESS
+[5] Variable test
+ Non-false sections have their value at the top of context,
+accessible as {{.}} or through the parent context. This gives
+a simple way to display content conditionally if a variable exists.
+
+ => SUCCESS
+[6] List Contexts
+ All elements on the context stack should be accessible within lists.
+ => SUCCESS
+[7] Deeply Nested Contexts
+ All elements on the context stack should be accessible.
+ => SUCCESS
+[8] List
+ Lists should be iterated; list items should visit the context stack.
+ => SUCCESS
+[9] Empty List
+ Empty lists should behave like falsey values.
+ => SUCCESS
+[10] Doubled
+ Multiple sections per template should be permitted.
+ => SUCCESS
+[11] Nested (Truthy)
+ Nested truthy sections should have their contents rendered.
+ => SUCCESS
+[12] Nested (Falsey)
+ Nested falsey sections should be omitted.
+ => SUCCESS
+[13] Context Misses
+ Failed context lookups should be considered falsey.
+ => SUCCESS
+[14] Implicit Iterator - String
+ Implicit iterators should directly interpolate strings.
+ => SUCCESS
+[15] Implicit Iterator - Integer
+ Implicit iterators should cast integers to strings and interpolate.
+ => SUCCESS
+[16] Implicit Iterator - Decimal
+ Implicit iterators should cast decimals to strings and interpolate.
+ => SUCCESS
+[17] Implicit Iterator - Array
+ Implicit iterators should allow iterating over nested arrays.
+ => SUCCESS
+[18] Implicit Iterator - HTML Escaping
+ Implicit iterators with basic interpolation should be HTML escaped.
+ => SUCCESS
+[19] Implicit Iterator - Triple mustache
+ Implicit iterators in triple mustache should interpolate without HTML escaping.
+ => SUCCESS
+[20] Implicit Iterator - Ampersand
+ Implicit iterators in an Ampersand tag should interpolate without HTML escaping.
+ => SUCCESS
+[21] Implicit Iterator - Root-level
+ Implicit iterators should work on root-level lists.
+ => SUCCESS
+[22] Dotted Names - Truthy
+ Dotted names should be valid for Section tags.
+ => SUCCESS
+[23] Dotted Names - Falsey
+ Dotted names should be valid for Section tags.
+ => SUCCESS
+[24] Dotted Names - Broken Chains
+ Dotted names that cannot be resolved should be considered falsey.
+ => SUCCESS
+[25] Surrounding Whitespace
+ Sections should not alter surrounding whitespace.
+ => SUCCESS
+[26] Internal Whitespace
+ Sections should not alter internal whitespace.
+ => SUCCESS
+[27] Indented Inline Sections
+ Single-line sections should not alter surrounding whitespace.
+ => SUCCESS
+[28] Standalone Lines
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[29] Indented Standalone Lines
+ Indented standalone lines should be removed from the template.
+ => SUCCESS
+[30] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[31] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[32] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[33] Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+summary:
+ error 0
+ differ 0
+ success 133
diff --git a/src/templating/test-specs/test-specs.c b/src/templating/test-specs/test-specs.c
new file mode 100644
index 000000000..15c94a80e
--- /dev/null
+++ b/src/templating/test-specs/test-specs.c
@@ -0,0 +1,520 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+
+#include "mustach-wrap.h"
+
+#define TEST_JSON_C 1
+#define TEST_JANSSON 2
+#define TEST_CJSON 3
+
+static const char *errors[] = {
+ "??? unreferenced ???",
+ "system",
+ "unexpected end",
+ "empty tag",
+ "tag too long",
+ "bad separators",
+ "too depth",
+ "closing",
+ "bad unescape tag",
+ "invalid interface",
+ "item not found",
+ "partial not found"
+};
+
+const char *mustach_error_string(int status)
+{
+ return status >= 0 ? "no error"
+ : errors[status <= -(int)(sizeof errors / sizeof * errors) ? 0 : -status];
+}
+
+static const char *errmsg = 0;
+static int flags = 0;
+static FILE *output = 0;
+
+static void help(char *prog)
+{
+ char *name = basename(prog);
+#define STR(x) #x
+ printf("%s version %s\n", name, STR(VERSION));
+#undef STR
+ printf("usage: %s test-files...\n", name);
+ exit(0);
+}
+
+#if TEST == TEST_CJSON
+
+static const size_t BLOCKSIZE = 8192;
+
+static char *readfile(const char *filename, size_t *length)
+{
+ int f;
+ struct stat s;
+ char *result;
+ size_t size, pos;
+ ssize_t rc;
+
+ result = NULL;
+ if (filename[0] == '-' && filename[1] == 0)
+ f = dup(0);
+ else
+ f = open(filename, O_RDONLY);
+ if (f < 0) {
+ fprintf(stderr, "Can't open file: %s\n", filename);
+ exit(1);
+ }
+
+ fstat(f, &s);
+ switch (s.st_mode & S_IFMT) {
+ case S_IFREG:
+ size = s.st_size;
+ break;
+ case S_IFSOCK:
+ case S_IFIFO:
+ size = BLOCKSIZE;
+ break;
+ default:
+ fprintf(stderr, "Bad file: %s\n", filename);
+ exit(1);
+ }
+
+ pos = 0;
+ result = malloc(size + 1);
+ do {
+ if (result == NULL) {
+ fprintf(stderr, "Out of memory\n");
+ exit(1);
+ }
+ rc = read(f, &result[pos], (size - pos) + 1);
+ if (rc < 0) {
+ fprintf(stderr, "Error while reading %s\n", filename);
+ exit(1);
+ }
+ if (rc > 0) {
+ pos += (size_t)rc;
+ if (pos > size) {
+ size = pos + BLOCKSIZE;
+ result = realloc(result, size + 1);
+ }
+ }
+ } while(rc > 0);
+
+ close(f);
+ if (length != NULL)
+ *length = pos;
+ result[pos] = 0;
+ return result;
+}
+#endif
+
+typedef struct {
+ unsigned nerror;
+ unsigned ndiffers;
+ unsigned nsuccess;
+ unsigned ninvalid;
+} counters;
+
+static int load_json(const char *filename);
+static int process(counters *c);
+static void close_json();
+static int get_partial(const char *name, struct mustach_sbuf *sbuf);
+
+int main(int ac, char **av)
+{
+ char *f;
+ char *prog = *av;
+ int s;
+ counters c;
+
+ (void)ac; /* unused */
+ flags = Mustach_With_SingleDot | Mustach_With_IncPartial;
+ output = stdout;
+ mustach_wrap_get_partial = get_partial;
+
+ memset(&c, 0, sizeof c);
+ while (*++av) {
+ if (!strcmp(*av, "-h") || !strcmp(*av, "--help"))
+ help(prog);
+ f = (av[0][0] == '-' && !av[0][1]) ? "/dev/stdin" : av[0];
+ fprintf(output, "\nloading %s\n", f);
+ s = load_json(f);
+ if (s < 0) {
+ fprintf(stderr, "error when loading %s!\n", f);
+ if(errmsg)
+ fprintf(stderr, " reason: %s\n", errmsg);
+ exit(1);
+ }
+ fprintf(output, "processing file %s\n", f);
+ s = process(&c);
+ if (s < 0) {
+ fprintf(stderr, "error bad test file %s!\n", f);
+ exit(1);
+ }
+ close_json();
+ }
+ fprintf(output, "\nsummary:\n");
+ if (c.ninvalid)
+ fprintf(output, " invalid %u\n", c.ninvalid);
+ fprintf(output, " error %u\n", c.nerror);
+ fprintf(output, " differ %u\n", c.ndiffers);
+ fprintf(output, " success %u\n", c.nsuccess);
+ if (c.nerror)
+ return 2;
+ if (c.ndiffers)
+ return 1;
+ return 0;
+}
+
+void emit(FILE *f, const char *s)
+{
+ for(;;s++) {
+ switch(*s) {
+ case 0: return;
+ case '\\': fprintf(f, "\\\\"); break;
+ case '\t': fprintf(f, "\\t"); break;
+ case '\n': fprintf(f, "\\n"); break;
+ case '\r': fprintf(f, "\\r"); break;
+ default: fprintf(f, "%c", *s); break;
+ }
+ }
+}
+
+#if TEST == TEST_JSON_C
+
+#include "mustach-json-c.h"
+
+static struct json_object *o;
+
+static struct json_object *partials;
+static int get_partial(const char *name, struct mustach_sbuf *sbuf)
+{
+ struct json_object *x;
+ if (partials == NULL || !json_object_object_get_ex(partials, name, &x))
+ return MUSTACH_ERROR_PARTIAL_NOT_FOUND;
+ sbuf->value = json_object_get_string(x);
+ return MUSTACH_OK;
+}
+
+static int load_json(const char *filename)
+{
+ o = json_object_from_file(filename);
+#if JSON_C_VERSION_NUM >= 0x000D00
+ errmsg = json_util_get_last_err();
+ if (errmsg != NULL)
+ return -1;
+#endif
+ if (o == NULL) {
+ errmsg = "null json";
+ return -1;
+ }
+ return 0;
+}
+static int process(counters *c)
+{
+ const char *t, *e;
+ char *got;
+ unsigned i, n;
+ size_t length;
+ int s;
+ json_object *tests, *unit, *name, *desc, *data, *template, *expected;
+
+ if (!json_object_object_get_ex(o, "tests", &tests) || json_object_get_type(tests) != json_type_array)
+ return -1;
+
+ i = 0;
+ n = (unsigned)json_object_array_length(tests);
+ while (i < n) {
+ unit = json_object_array_get_idx(tests, i);
+ if (json_object_get_type(unit) != json_type_object
+ || !json_object_object_get_ex(unit, "name", &name)
+ || !json_object_object_get_ex(unit, "desc", &desc)
+ || !json_object_object_get_ex(unit, "data", &data)
+ || !json_object_object_get_ex(unit, "template", &template)
+ || !json_object_object_get_ex(unit, "expected", &expected)
+ || json_object_get_type(name) != json_type_string
+ || json_object_get_type(desc) != json_type_string
+ || json_object_get_type(template) != json_type_string
+ || json_object_get_type(expected) != json_type_string) {
+ fprintf(stderr, "invalid test %u\n", i);
+ c->ninvalid++;
+ }
+ else {
+ fprintf(output, "[%u] %s\n", i, json_object_get_string(name));
+ fprintf(output, "\t%s\n", json_object_get_string(desc));
+ if (!json_object_object_get_ex(unit, "partials", &partials))
+ partials = NULL;
+ t = json_object_get_string(template);
+ e = json_object_get_string(expected);
+ s = mustach_json_c_mem(t, 0, data, flags, &got, &length);
+ if (s == 0 && strcmp(got, e) == 0) {
+ fprintf(output, "\t=> SUCCESS\n");
+ c->nsuccess++;
+ }
+ else {
+ if (s < 0) {
+ fprintf(output, "\t=> ERROR %s\n", mustach_error_string(s));
+ c->nerror++;
+ }
+ else {
+ fprintf(output, "\t=> DIFFERS\n");
+ c->ndiffers++;
+ }
+ if (partials)
+ fprintf(output, "\t.. PARTIALS[%s]\n", json_object_to_json_string_ext(partials, 0));
+ fprintf(output, "\t.. DATA[%s]\n", json_object_to_json_string_ext(data, 0));
+ fprintf(output, "\t.. TEMPLATE[");
+ emit(output, t);
+ fprintf(output, "]\n");
+ fprintf(output, "\t.. EXPECTED[");
+ emit(output, e);
+ fprintf(output, "]\n");
+ if (s == 0) {
+ fprintf(output, "\t.. GOT[");
+ emit(output, got);
+ fprintf(output, "]\n");
+ }
+ }
+ free(got);
+ }
+ i++;
+ }
+ return 0;
+}
+static void close_json()
+{
+ json_object_put(o);
+}
+
+#elif TEST == TEST_JANSSON
+
+#include "mustach-jansson.h"
+
+static json_t *o;
+static json_error_t e;
+
+static json_t *partials;
+static int get_partial(const char *name, struct mustach_sbuf *sbuf)
+{
+ json_t *x;
+ if (partials == NULL || !(x = json_object_get(partials, name)))
+ return MUSTACH_ERROR_PARTIAL_NOT_FOUND;
+ sbuf->value = json_string_value(x);
+ return MUSTACH_OK;
+}
+
+static int load_json(const char *filename)
+{
+ o = json_load_file(filename, JSON_DECODE_ANY, &e);
+ if (o == NULL) {
+ errmsg = e.text;
+ return -1;
+ }
+ return 0;
+}
+static int process(counters *c)
+{
+ const char *t, *e;
+ char *got, *tmp;
+ int i, n;
+ size_t length;
+ int s;
+ json_t *tests, *unit, *name, *desc, *data, *template, *expected;
+
+ tests = json_object_get(o, "tests");
+ if (!tests || json_typeof(tests) != JSON_ARRAY)
+ return -1;
+
+ i = 0;
+ n = json_array_size(tests);
+ while (i < n) {
+ unit = json_array_get(tests, i);
+ if (!unit || json_typeof(unit) != JSON_OBJECT
+ || !(name = json_object_get(unit, "name"))
+ || !(desc = json_object_get(unit, "desc"))
+ || !(data = json_object_get(unit, "data"))
+ || !(template = json_object_get(unit, "template"))
+ || !(expected = json_object_get(unit, "expected"))
+ || json_typeof(name) != JSON_STRING
+ || json_typeof(desc) != JSON_STRING
+ || json_typeof(template) != JSON_STRING
+ || json_typeof(expected) != JSON_STRING) {
+ fprintf(stderr, "invalid test %u\n", i);
+ c->ninvalid++;
+ }
+ else {
+ fprintf(output, "[%u] %s\n", i, json_string_value(name));
+ fprintf(output, "\t%s\n", json_string_value(desc));
+ partials = json_object_get(unit, "partials");
+ t = json_string_value(template);
+ e = json_string_value(expected);
+ s = mustach_jansson_mem(t, 0, data, flags, &got, &length);
+ if (s == 0 && strcmp(got, e) == 0) {
+ fprintf(output, "\t=> SUCCESS\n");
+ c->nsuccess++;
+ }
+ else {
+ if (s < 0) {
+ fprintf(output, "\t=> ERROR %s\n", mustach_error_string(s));
+ c->nerror++;
+ }
+ else {
+ fprintf(output, "\t=> DIFFERS\n");
+ c->ndiffers++;
+ }
+ if (partials) {
+ tmp = json_dumps(partials, JSON_ENCODE_ANY | JSON_COMPACT);
+ fprintf(output, "\t.. PARTIALS[%s]\n", tmp);
+ free(tmp);
+ }
+ tmp = json_dumps(data, JSON_ENCODE_ANY | JSON_COMPACT);
+ fprintf(output, "\t.. DATA[%s]\n", tmp);
+ free(tmp);
+ fprintf(output, "\t.. TEMPLATE[");
+ emit(output, t);
+ fprintf(output, "]\n");
+ fprintf(output, "\t.. EXPECTED[");
+ emit(output, e);
+ fprintf(output, "]\n");
+ if (s == 0) {
+ fprintf(output, "\t.. GOT[");
+ emit(output, got);
+ fprintf(output, "]\n");
+ }
+ }
+ free(got);
+ }
+ i++;
+ }
+ return 0;
+}
+static void close_json()
+{
+ json_decref(o);
+}
+
+#elif TEST == TEST_CJSON
+
+#include "mustach-cjson.h"
+
+static cJSON *o;
+static cJSON *partials;
+static int get_partial(const char *name, struct mustach_sbuf *sbuf)
+{
+ cJSON *x;
+ if (partials == NULL || !(x = cJSON_GetObjectItemCaseSensitive(partials, name)))
+ return MUSTACH_ERROR_PARTIAL_NOT_FOUND;
+ sbuf->value = x->valuestring;
+ return MUSTACH_OK;
+}
+
+static int load_json(const char *filename)
+{
+ char *t;
+ size_t length;
+
+ t = readfile(filename, &length);
+ o = t ? cJSON_ParseWithLength(t, length) : NULL;
+ free(t);
+ return -!o;
+}
+static int process(counters *c)
+{
+ const char *t, *e;
+ char *got, *tmp;
+ int i, n;
+ size_t length;
+ int s;
+ cJSON *tests, *unit, *name, *desc, *data, *template, *expected;
+
+ tests = cJSON_GetObjectItemCaseSensitive(o, "tests");
+ if (!tests || tests->type != cJSON_Array)
+ return -1;
+
+ i = 0;
+ n = cJSON_GetArraySize(tests);
+ while (i < n) {
+ unit = cJSON_GetArrayItem(tests, i);
+ if (!unit || unit->type != cJSON_Object
+ || !(name = cJSON_GetObjectItemCaseSensitive(unit, "name"))
+ || !(desc = cJSON_GetObjectItemCaseSensitive(unit, "desc"))
+ || !(data = cJSON_GetObjectItemCaseSensitive(unit, "data"))
+ || !(template = cJSON_GetObjectItemCaseSensitive(unit, "template"))
+ || !(expected = cJSON_GetObjectItemCaseSensitive(unit, "expected"))
+ || name->type != cJSON_String
+ || desc->type != cJSON_String
+ || template->type != cJSON_String
+ || expected->type != cJSON_String) {
+ fprintf(stderr, "invalid test %u\n", i);
+ c->ninvalid++;
+ }
+ else {
+ fprintf(output, "[%u] %s\n", i, name->valuestring);
+ fprintf(output, "\t%s\n", desc->valuestring);
+ partials = cJSON_GetObjectItemCaseSensitive(unit, "partials");
+ t = template->valuestring;
+ e = expected->valuestring;
+ s = mustach_cJSON_mem(t, 0, data, flags, &got, &length);
+ if (s == 0 && strcmp(got, e) == 0) {
+ fprintf(output, "\t=> SUCCESS\n");
+ c->nsuccess++;
+ }
+ else {
+ if (s < 0) {
+ fprintf(output, "\t=> ERROR %s\n", mustach_error_string(s));
+ c->nerror++;
+ }
+ else {
+ fprintf(output, "\t=> DIFFERS\n");
+ c->ndiffers++;
+ }
+ if (partials) {
+ tmp = cJSON_PrintUnformatted(partials);
+ fprintf(output, "\t.. PARTIALS[%s]\n", tmp);
+ free(tmp);
+ }
+ tmp = cJSON_PrintUnformatted(data);
+ fprintf(output, "\t.. DATA[%s]\n", tmp);
+ free(tmp);
+ fprintf(output, "\t.. TEMPLATE[");
+ emit(output, t);
+ fprintf(output, "]\n");
+ fprintf(output, "\t.. EXPECTED[");
+ emit(output, e);
+ fprintf(output, "]\n");
+ if (s == 0) {
+ fprintf(output, "\t.. GOT[");
+ emit(output, got);
+ fprintf(output, "]\n");
+ }
+ }
+ free(got);
+ }
+ i++;
+ }
+ return 0;
+}
+static void close_json()
+{
+ cJSON_Delete(o);
+}
+
+#else
+#error "no defined json library"
+#endif
diff --git a/src/templating/test1/.gitignore b/src/templating/test1/.gitignore
new file mode 100644
index 000000000..4d897daa0
--- /dev/null
+++ b/src/templating/test1/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test1/Makefile b/src/templating/test1/Makefile
new file mode 100644
index 000000000..1a3e57914
--- /dev/null
+++ b/src/templating/test1/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json must
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test1/json b/src/templating/test1/json
new file mode 100644
index 000000000..6562fb064
--- /dev/null
+++ b/src/templating/test1/json
@@ -0,0 +1,23 @@
+{
+ "name": "Chris",
+ "value": 10000,
+ "taxed_value": 6000,
+ "in_ca": true,
+ "person": false,
+ "repo": [
+ { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] },
+ { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] },
+ { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] }
+ ],
+ "person?": { "name": "Jon" },
+ "special": "----{{extra}}----\n",
+ "extra": 3.14159,
+ "#sharp": "#",
+ "!bang": "!",
+ "/slash": "/",
+ "^circ": "^",
+ "=equal": "=",
+ ":colon": ":",
+ ">greater": ">",
+ "~tilde": "~"
+}
diff --git a/src/templating/test1/must b/src/templating/test1/must
new file mode 100644
index 000000000..92d30b0b2
--- /dev/null
+++ b/src/templating/test1/must
@@ -0,0 +1,49 @@
+Hello {{name}}
+You have just won {{value}} dollars!
+{{#in_ca}}
+Well, {{taxed_value}} dollars, after taxes.
+{{/in_ca}}
+Shown.
+{{#person}}
+ Never shown!
+{{/person}}
+{{^person}}
+ No person
+{{/person}}
+
+{{#repo}}
+ <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}}
+{{/repo}}
+
+{{#person?}}
+ Hi {{name}}!
+{{/person?}}
+
+{{=%(% %)%=}}
+=====================================
+%(%! gros commentaire %)%
+%(%#repo%)%
+ <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)%
+%(%/repo%)%
+=====================================
+%(%={{ }}=%)%
+ggggggggg
+{{> special}}
+jjjjjjjjj
+end
+
+{{:#sharp}}
+{{:!bang}}
+{{:~tilde}}
+{{:/~0tilde}}
+{{:/~1slash}} see json pointers IETF RFC 6901
+{{:^circ}}
+{{:\=equal}}
+{{::colon}}
+{{:>greater}}
+
+{{#repo}}
+who 0 {{who.0}}
+who 1 {{who.1}}
+who 2 {{who.2}}
+{{/repo}}
diff --git a/src/templating/test1/resu.ref b/src/templating/test1/resu.ref
new file mode 100644
index 000000000..6cd11bb27
--- /dev/null
+++ b/src/templating/test1/resu.ref
@@ -0,0 +1,41 @@
+Hello Chris
+You have just won 10000 dollars!
+Well, 6000 dollars, after taxes.
+Shown.
+ No person
+
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+
+ Hi Jon!
+
+=====================================
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+=====================================
+ggggggggg
+----3.14159----
+jjjjjjjjj
+end
+
+#
+!
+~
+~
+/ see json pointers IETF RFC 6901
+^
+=
+:
+&gt;
+
+who 0 {&quot;commiter&quot;:&quot;joe&quot;}
+who 1 {&quot;reviewer&quot;:&quot;avrel&quot;}
+who 2 {&quot;commiter&quot;:&quot;william&quot;}
+who 0 {&quot;commiter&quot;:&quot;jack&quot;}
+who 1 {&quot;reviewer&quot;:&quot;avrel&quot;}
+who 2 {&quot;commiter&quot;:&quot;greg&quot;}
+who 0 {&quot;reviewer&quot;:&quot;joe&quot;}
+who 1 {&quot;reviewer&quot;:&quot;jack&quot;}
+who 2 {&quot;commiter&quot;:&quot;greg&quot;}
diff --git a/src/templating/test1/vg.ref b/src/templating/test1/vg.ref
new file mode 100644
index 000000000..d086e59c5
--- /dev/null
+++ b/src/templating/test1/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 111 allocs, 111 frees, 9,702 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test2/.gitignore b/src/templating/test2/.gitignore
new file mode 100644
index 000000000..4d897daa0
--- /dev/null
+++ b/src/templating/test2/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test2/Makefile b/src/templating/test2/Makefile
new file mode 100644
index 000000000..1a3e57914
--- /dev/null
+++ b/src/templating/test2/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json must
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test2/json b/src/templating/test2/json
new file mode 100644
index 000000000..8c668b3b1
--- /dev/null
+++ b/src/templating/test2/json
@@ -0,0 +1,9 @@
+{
+ "header": "Colors",
+ "items": [
+ {"name": "red", "first": true, "url": "#Red"},
+ {"name": "green", "link": true, "url": "#Green"},
+ {"name": "blue", "link": true, "url": "#Blue"}
+ ],
+ "empty": false
+}
diff --git a/src/templating/test2/must b/src/templating/test2/must
new file mode 100644
index 000000000..aa6da7077
--- /dev/null
+++ b/src/templating/test2/must
@@ -0,0 +1,17 @@
+<h1>{{header}}</h1>
+{{#bug}}
+{{/bug}}
+
+{{#items}}
+ {{#first}}
+ <li><strong>{{name}}</strong></li>
+ {{/first}}
+ {{#link}}
+ <li><a href="{{url}}">{{name}}</a></li>
+ {{/link}}
+{{/items}}
+
+{{#empty}}
+ <p>The list is empty.</p>
+{{/empty}}
+
diff --git a/src/templating/test2/resu.ref b/src/templating/test2/resu.ref
new file mode 100644
index 000000000..5a200a9bf
--- /dev/null
+++ b/src/templating/test2/resu.ref
@@ -0,0 +1,7 @@
+<h1>Colors</h1>
+
+ <li><strong>red</strong></li>
+ <li><a href="#Green">green</a></li>
+ <li><a href="#Blue">blue</a></li>
+
+
diff --git a/src/templating/test2/vg.ref b/src/templating/test2/vg.ref
new file mode 100644
index 000000000..e4b4f6d37
--- /dev/null
+++ b/src/templating/test2/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 38 allocs, 38 frees, 5,712 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test3/.gitignore b/src/templating/test3/.gitignore
new file mode 100644
index 000000000..4d897daa0
--- /dev/null
+++ b/src/templating/test3/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test3/Makefile b/src/templating/test3/Makefile
new file mode 100644
index 000000000..1a3e57914
--- /dev/null
+++ b/src/templating/test3/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json must
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test3/json b/src/templating/test3/json
new file mode 100644
index 000000000..792788171
--- /dev/null
+++ b/src/templating/test3/json
@@ -0,0 +1,7 @@
+{
+ "name": "Chris",
+ "company": "<b>GitHub & Co</b>",
+ "names": ["Chris", "Kross"],
+ "skills": ["JavaScript", "PHP", "Java"],
+ "age": 18
+}
diff --git a/src/templating/test3/must b/src/templating/test3/must
new file mode 100644
index 000000000..5c490469b
--- /dev/null
+++ b/src/templating/test3/must
@@ -0,0 +1,15 @@
+* {{name}}
+* {{age}}
+* {{company}}
+* {{&company}}
+* {{{company}}}
+{{=<% %>=}}
+* <%company%>
+* <%&company%>
+* <%{company}%>
+
+<%={{ }}=%>
+* <ul>{{#names}}<li>{{.}}</li>{{/names}}</ul>
+* skills: <ul>{{#skills}}<li>{{.}}</li>{{/skills}}</ul>
+{{#age}}* age: {{.}}{{/age}}
+
diff --git a/src/templating/test3/resu.ref b/src/templating/test3/resu.ref
new file mode 100644
index 000000000..ee6dad3fb
--- /dev/null
+++ b/src/templating/test3/resu.ref
@@ -0,0 +1,13 @@
+* Chris
+* 18
+* &lt;b&gt;GitHub &amp; Co&lt;/b&gt;
+* <b>GitHub & Co</b>
+* <b>GitHub & Co</b>
+* &lt;b&gt;GitHub &amp; Co&lt;/b&gt;
+* <b>GitHub & Co</b>
+* <b>GitHub & Co</b>
+
+* <ul><li>Chris</li><li>Kross</li></ul>
+* skills: <ul><li>JavaScript</li><li>PHP</li><li>Java</li></ul>
+* age: 18
+
diff --git a/src/templating/test3/vg.ref b/src/templating/test3/vg.ref
new file mode 100644
index 000000000..21f7931eb
--- /dev/null
+++ b/src/templating/test3/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 30 allocs, 30 frees, 5,831 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test4/.gitignore b/src/templating/test4/.gitignore
new file mode 100644
index 000000000..4d897daa0
--- /dev/null
+++ b/src/templating/test4/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test4/Makefile b/src/templating/test4/Makefile
new file mode 100644
index 000000000..1a3e57914
--- /dev/null
+++ b/src/templating/test4/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json must
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test4/json b/src/templating/test4/json
new file mode 100644
index 000000000..a10836072
--- /dev/null
+++ b/src/templating/test4/json
@@ -0,0 +1,13 @@
+{
+ "person": { "name": "Jon", "age": 25 },
+ "person.name": "Fred",
+ "person.name=Fred": "The other Fred.",
+ "persons": [
+ { "name": "Jon", "age": 25, "lang": "en" },
+ { "name": "Henry", "age": 27, "lang": "en" },
+ { "name": "Amed", "age": 24, "lang": "fr" } ],
+ "fellows": {
+ "Jon": { "age": 25, "lang": "en" },
+ "Henry": { "age": 27, "lang": "en" },
+ "Amed": { "age": 24, "lang": "fr" } }
+}
diff --git a/src/templating/test4/must b/src/templating/test4/must
new file mode 100644
index 000000000..003b93666
--- /dev/null
+++ b/src/templating/test4/must
@@ -0,0 +1,58 @@
+This are extensions!!
+
+{{person.name}}
+{{person.age}}
+
+{{person\.name}}
+{{person\.name\=Fred}}
+
+{{#person.name=Jon}}
+Hello Jon
+{{/person.name=Jon}}
+
+{{^person.name=Jon}}
+No Jon? Hey Jon...
+{{/person.name=Jon}}
+
+{{^person.name=Harry}}
+No Harry? Hey Calahan...
+{{/person.name=Harry}}
+
+{{#person\.name=Fred}}
+Hello Fred
+{{/person\.name=Fred}}
+
+{{^person\.name=Fred}}
+No Fred? Hey Fred...
+{{/person\.name=Fred}}
+
+{{#person\.name\=Fred=The other Fred.}}
+Hello Fred#2
+{{/person\.name\=Fred=The other Fred.}}
+
+{{^person\.name\=Fred=The other Fred.}}
+No Fred#2? Hey Fred#2...
+{{/person\.name\=Fred=The other Fred.}}
+
+{{#persons}}
+{{#lang=!fr}}Hello {{name}}, {{age}} years{{/lang=!fr}}
+{{#lang=fr}}Salut {{name}}, {{age}} ans{{/lang=fr}}
+{{/persons}}
+
+{{#persons}}
+{{name}}: {{age=24}}/{{age}}/{{age=!27}}
+{{/persons}}
+
+{{#fellows.*}}
+{{*}}: {{age=24}}/{{age}}/{{age=!27}}
+{{/fellows.*}}
+
+{{#*}}
+ (1) {{*}}: {{.}}
+ {{#*}}
+ (2) {{*}}: {{.}}
+ {{#*}}
+ (3) {{*}}: {{.}}
+ {{/*}}
+ {{/*}}
+{{/*}}
diff --git a/src/templating/test4/resu.ref b/src/templating/test4/resu.ref
new file mode 100644
index 000000000..8a71c4e82
--- /dev/null
+++ b/src/templating/test4/resu.ref
@@ -0,0 +1,50 @@
+This are extensions!!
+
+Jon
+25
+
+Fred
+The other Fred.
+
+Hello Jon
+
+
+No Harry? Hey Calahan...
+
+Hello Fred
+
+
+Hello Fred#2
+
+
+Hello Jon, 25 years
+
+Hello Henry, 27 years
+
+
+Salut Amed, 24 ans
+
+Jon: /25/25
+Henry: /27/
+Amed: 24/24/24
+
+Jon: /25/25
+Henry: /27/
+Amed: 24/24/24
+
+ (1) person: {&quot;name&quot;:&quot;Jon&quot;,&quot;age&quot;:25}
+ (2) name: Jon
+ (2) age: 25
+ (1) person.name: Fred
+ (1) person.name=Fred: The other Fred.
+ (1) persons: [{&quot;name&quot;:&quot;Jon&quot;,&quot;age&quot;:25,&quot;lang&quot;:&quot;en&quot;},{&quot;name&quot;:&quot;Henry&quot;,&quot;age&quot;:27,&quot;lang&quot;:&quot;en&quot;},{&quot;name&quot;:&quot;Amed&quot;,&quot;age&quot;:24,&quot;lang&quot;:&quot;fr&quot;}]
+ (1) fellows: {&quot;Jon&quot;:{&quot;age&quot;:25,&quot;lang&quot;:&quot;en&quot;},&quot;Henry&quot;:{&quot;age&quot;:27,&quot;lang&quot;:&quot;en&quot;},&quot;Amed&quot;:{&quot;age&quot;:24,&quot;lang&quot;:&quot;fr&quot;}}
+ (2) Jon: {&quot;age&quot;:25,&quot;lang&quot;:&quot;en&quot;}
+ (3) age: 25
+ (3) lang: en
+ (2) Henry: {&quot;age&quot;:27,&quot;lang&quot;:&quot;en&quot;}
+ (3) age: 27
+ (3) lang: en
+ (2) Amed: {&quot;age&quot;:24,&quot;lang&quot;:&quot;fr&quot;}
+ (3) age: 24
+ (3) lang: fr
diff --git a/src/templating/test4/vg.ref b/src/templating/test4/vg.ref
new file mode 100644
index 000000000..922b0676d
--- /dev/null
+++ b/src/templating/test4/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 121 allocs, 121 frees, 14,608 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test5/.gitignore b/src/templating/test5/.gitignore
new file mode 100644
index 000000000..4d897daa0
--- /dev/null
+++ b/src/templating/test5/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test5/Makefile b/src/templating/test5/Makefile
new file mode 100644
index 000000000..1a3e57914
--- /dev/null
+++ b/src/templating/test5/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json must
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test5/json b/src/templating/test5/json
new file mode 100644
index 000000000..6562fb064
--- /dev/null
+++ b/src/templating/test5/json
@@ -0,0 +1,23 @@
+{
+ "name": "Chris",
+ "value": 10000,
+ "taxed_value": 6000,
+ "in_ca": true,
+ "person": false,
+ "repo": [
+ { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] },
+ { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] },
+ { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] }
+ ],
+ "person?": { "name": "Jon" },
+ "special": "----{{extra}}----\n",
+ "extra": 3.14159,
+ "#sharp": "#",
+ "!bang": "!",
+ "/slash": "/",
+ "^circ": "^",
+ "=equal": "=",
+ ":colon": ":",
+ ">greater": ">",
+ "~tilde": "~"
+}
diff --git a/src/templating/test5/must b/src/templating/test5/must
new file mode 100644
index 000000000..44305df24
--- /dev/null
+++ b/src/templating/test5/must
@@ -0,0 +1,23 @@
+=====================================
+from json
+{{> special}}
+=====================================
+not found
+{{> notfound}}
+=====================================
+without extension first
+{{> must2 }}
+=====================================
+last with extension
+{{> must3 }}
+=====================================
+Ensure must3 didn't change specials
+
+{{#person?}}
+ Hi {{name}}!
+{{/person?}}
+
+%(%#person?%)%
+ Hi %(%name%)%!
+%(%/person?%)%
+
diff --git a/src/templating/test5/must2 b/src/templating/test5/must2
new file mode 100644
index 000000000..d4a1d3783
--- /dev/null
+++ b/src/templating/test5/must2
@@ -0,0 +1,14 @@
+must2 == BEGIN
+Hello {{name}}
+You have just won {{value}} dollars!
+{{#in_ca}}
+Well, {{taxed_value}} dollars, after taxes.
+{{/in_ca}}
+Shown.
+{{#person}}
+ Never shown!
+{{/person}}
+{{^person}}
+ No person
+{{/person}}
+must2 == END
diff --git a/src/templating/test5/must2.mustache b/src/templating/test5/must2.mustache
new file mode 100644
index 000000000..33f1ead38
--- /dev/null
+++ b/src/templating/test5/must2.mustache
@@ -0,0 +1 @@
+must2.mustache ==SHOULD NOT BE SEEN==
diff --git a/src/templating/test5/must3.mustache b/src/templating/test5/must3.mustache
new file mode 100644
index 000000000..67eddb1ef
--- /dev/null
+++ b/src/templating/test5/must3.mustache
@@ -0,0 +1,17 @@
+must3.mustache == BEGIN
+{{#repo}}
+ <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}}
+{{/repo}}
+
+{{#person?}}
+ Hi {{name}}!
+{{/person?}}
+
+{{=%(% %)%=}}
+=====================================
+%(%! big comment %)%
+%(%#repo%)%
+ <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)%
+%(%/repo%)%
+=====================================
+must3.mustache == END
diff --git a/src/templating/test5/resu.ref b/src/templating/test5/resu.ref
new file mode 100644
index 000000000..f2a608568
--- /dev/null
+++ b/src/templating/test5/resu.ref
@@ -0,0 +1,38 @@
+=====================================
+from json
+----3.14159----
+=====================================
+not found
+=====================================
+without extension first
+must2 == BEGIN
+Hello Chris
+You have just won 10000 dollars!
+Well, 6000 dollars, after taxes.
+Shown.
+ No person
+must2 == END
+=====================================
+last with extension
+must3.mustache == BEGIN
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+
+ Hi Jon!
+
+=====================================
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+=====================================
+must3.mustache == END
+=====================================
+Ensure must3 didn't change specials
+
+ Hi Jon!
+
+%(%#person?%)%
+ Hi %(%name%)%!
+%(%/person?%)%
+
diff --git a/src/templating/test5/vg.ref b/src/templating/test5/vg.ref
new file mode 100644
index 000000000..89dc21bcb
--- /dev/null
+++ b/src/templating/test5/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 123 allocs, 123 frees, 20,610 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test6/.gitignore b/src/templating/test6/.gitignore
new file mode 100644
index 000000000..15e6dd5a5
--- /dev/null
+++ b/src/templating/test6/.gitignore
@@ -0,0 +1,3 @@
+resu.last
+vg.last
+test-custom-write
diff --git a/src/templating/test6/Makefile b/src/templating/test6/Makefile
new file mode 100644
index 000000000..ea4f86e79
--- /dev/null
+++ b/src/templating/test6/Makefile
@@ -0,0 +1,12 @@
+.PHONY: test clean
+
+test-custom-write: test-custom-write.c ../mustach-json-c.h ../mustach-json-c.c ../mustach-wrap.c ../mustach.h ../mustach.c
+ @echo building test-custom-write
+ $(CC) $(CFLAGS) $(LDFLAGS) -g -o test-custom-write test-custom-write.c ../mustach.c ../mustach-json-c.c ../mustach-wrap.c -ljson-c
+
+test: test-custom-write
+ @mustach=./test-custom-write ../dotest.sh json -U must -l must -x must
+
+clean:
+ rm -f resu.last vg.last test-custom-write
+
diff --git a/src/templating/test6/json b/src/templating/test6/json
new file mode 100644
index 000000000..6562fb064
--- /dev/null
+++ b/src/templating/test6/json
@@ -0,0 +1,23 @@
+{
+ "name": "Chris",
+ "value": 10000,
+ "taxed_value": 6000,
+ "in_ca": true,
+ "person": false,
+ "repo": [
+ { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] },
+ { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] },
+ { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] }
+ ],
+ "person?": { "name": "Jon" },
+ "special": "----{{extra}}----\n",
+ "extra": 3.14159,
+ "#sharp": "#",
+ "!bang": "!",
+ "/slash": "/",
+ "^circ": "^",
+ "=equal": "=",
+ ":colon": ":",
+ ">greater": ">",
+ "~tilde": "~"
+}
diff --git a/src/templating/test6/must b/src/templating/test6/must
new file mode 100644
index 000000000..6df523669
--- /dev/null
+++ b/src/templating/test6/must
@@ -0,0 +1,43 @@
+Hello {{name}}
+You have just won {{value}} dollars!
+{{#in_ca}}
+Well, {{taxed_value}} dollars, after taxes.
+{{/in_ca}}
+Shown.
+{{#person}}
+ Never shown!
+{{/person}}
+{{^person}}
+ No person
+{{/person}}
+
+{{#repo}}
+ <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}}
+{{/repo}}
+
+{{#person?}}
+ Hi {{name}}!
+{{/person?}}
+
+{{=%(% %)%=}}
+=====================================
+%(%! gros commentaire %)%
+%(%#repo%)%
+ <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)%
+%(%/repo%)%
+=====================================
+%(%={{ }}=%)%
+ggggggggg
+{{> special}}
+jjjjjjjjj
+end
+
+{{:#sharp}}
+{{:!bang}}
+{{:~tilde}}
+{{:/~0tilde}}
+{{:/~1slash}} see json pointers IETF RFC 6901
+{{:^circ}}
+{{:\=equal}}
+{{::colon}}
+{{:>greater}}
diff --git a/src/templating/test6/resu.ref b/src/templating/test6/resu.ref
new file mode 100644
index 000000000..377eb11af
--- /dev/null
+++ b/src/templating/test6/resu.ref
@@ -0,0 +1,93 @@
+HELLO CHRIS
+YOU HAVE JUST WON 10000 DOLLARS!
+WELL, 6000 DOLLARS, AFTER TAXES.
+SHOWN.
+ NO PERSON
+
+ <B>RESQUE</B> REVIEWERS: AVREL COMMITERS: JOE WILLIAM
+ <B>HUB</B> REVIEWERS: AVREL COMMITERS: JACK GREG
+ <B>RIP</B> REVIEWERS: JOE JACK COMMITERS: GREG
+
+ HI JON!
+
+=====================================
+ <B>RESQUE</B> REVIEWERS: AVREL COMMITERS: JOE WILLIAM
+ <B>HUB</B> REVIEWERS: AVREL COMMITERS: JACK GREG
+ <B>RIP</B> REVIEWERS: JOE JACK COMMITERS: GREG
+=====================================
+GGGGGGGGG
+----3.14159----
+JJJJJJJJJ
+END
+
+#
+!
+~
+~
+/ SEE JSON POINTERS IETF RFC 6901
+^
+=
+:
+&GT;
+hello chris
+you have just won 10000 dollars!
+well, 6000 dollars, after taxes.
+shown.
+ no person
+
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+
+ hi jon!
+
+=====================================
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+=====================================
+ggggggggg
+----3.14159----
+jjjjjjjjj
+end
+
+#
+!
+~
+~
+/ see json pointers ietf rfc 6901
+^
+=
+:
+&gt;
+Hello Chris
+You have just won 10000 dollars!
+Well, 6000 dollars, after taxes.
+Shown.
+ No person
+
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+
+ Hi Jon!
+
+=====================================
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+=====================================
+ggggggggg
+----3.14159----
+jjjjjjjjj
+end
+
+#
+!
+~
+~
+/ see json pointers IETF RFC 6901
+^
+=
+:
+&gt;
diff --git a/src/templating/test6/test-custom-write.c b/src/templating/test6/test-custom-write.c
new file mode 100644
index 000000000..20042c1ed
--- /dev/null
+++ b/src/templating/test6/test-custom-write.c
@@ -0,0 +1,149 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <libgen.h>
+
+#include "../mustach-json-c.h"
+
+static const size_t BLOCKSIZE = 8192;
+
+static char *readfile(const char *filename)
+{
+ int f;
+ struct stat s;
+ char *result, *ptr;
+ size_t size, pos;
+ ssize_t rc;
+
+ result = NULL;
+ if (filename[0] == '-' && filename[1] == 0)
+ f = dup(0);
+ else
+ f = open(filename, O_RDONLY);
+ if (f < 0) {
+ fprintf(stderr, "Can't open file: %s\n", filename);
+ exit(1);
+ }
+
+ fstat(f, &s);
+ switch (s.st_mode & S_IFMT) {
+ case S_IFREG:
+ size = s.st_size;
+ break;
+ case S_IFSOCK:
+ case S_IFIFO:
+ size = BLOCKSIZE;
+ break;
+ default:
+ fprintf(stderr, "Bad file: %s\n", filename);
+ exit(1);
+ }
+
+ pos = 0;
+ result = malloc(size + 1);
+ do {
+ if (result == NULL) {
+ fprintf(stderr, "Out of memory\n");
+ exit(1);
+ }
+ rc = read(f, &result[pos], (size - pos) + 1);
+ if (rc < 0) {
+ fprintf(stderr, "Error while reading %s\n", filename);
+ exit(1);
+ }
+ if (rc > 0) {
+ pos += (size_t)rc;
+ if (pos > size) {
+ size = pos + BLOCKSIZE;
+ ptr = realloc(result, size + 1);
+ if (!ptr)
+ free(result);
+ result = ptr;
+ }
+ }
+ } while(rc > 0);
+
+ close(f);
+ result[pos] = 0;
+ return result;
+}
+
+enum { None, Upper, Lower } mode = None;
+
+int uwrite(void *closure, const char *buffer, size_t size)
+{
+ switch(mode) {
+ case None:
+ fwrite(buffer, size, 1, stdout);
+ break;
+ case Upper:
+ while(size--)
+ fputc(toupper(*buffer++), stdout);
+ break;
+ case Lower:
+ while(size--)
+ fputc(tolower(*buffer++), stdout);
+ break;
+ }
+ return 0;
+}
+
+int main(int ac, char **av)
+{
+ struct json_object *o;
+ char *t;
+ char *prog = *av;
+ int s;
+
+ if (*++av) {
+ o = json_object_from_file(av[0]);
+ if (o == NULL) {
+ fprintf(stderr, "Aborted: null json (file %s)\n", av[0]);
+ exit(1);
+ }
+ while(*++av) {
+ if (!strcmp(*av, "-U"))
+ mode = Upper;
+ else if (!strcmp(*av, "-l"))
+ mode = Lower;
+ else if (!strcmp(*av, "-x"))
+ mode = None;
+ else {
+ t = readfile(*av);
+ s = mustach_json_c_write(t, 0, o, Mustach_With_AllExtensions, uwrite, NULL);
+ if (s != 0)
+ fprintf(stderr, "Template error %d\n", s);
+ free(t);
+ }
+ }
+ json_object_put(o);
+ }
+ return 0;
+}
+
diff --git a/src/templating/test6/vg.ref b/src/templating/test6/vg.ref
new file mode 100644
index 000000000..fb0e31bd8
--- /dev/null
+++ b/src/templating/test6/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ./test-custom-write json -U must -l must -x must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 174 allocs, 174 frees, 24,250 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test7/Makefile b/src/templating/test7/Makefile
new file mode 100644
index 000000000..8e3a3b990
--- /dev/null
+++ b/src/templating/test7/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json base.mustache
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test7/base.mustache b/src/templating/test7/base.mustache
new file mode 100644
index 000000000..f701e0c65
--- /dev/null
+++ b/src/templating/test7/base.mustache
@@ -0,0 +1,2 @@
+family:
+{{> node}}
diff --git a/src/templating/test7/json b/src/templating/test7/json
new file mode 100644
index 000000000..c9ee47150
--- /dev/null
+++ b/src/templating/test7/json
@@ -0,0 +1,8 @@
+{ "data": "grandparent", "children": [
+ { "data": "parent", "children": [
+ { "data": "child", "children": [] }
+ ]},
+ { "data": "parent2", "children": [
+ { "data": "child2", "children": [ { "data": "pet", "children": false } ]}
+ ]}
+]}
diff --git a/src/templating/test7/node.mustache b/src/templating/test7/node.mustache
new file mode 100644
index 000000000..4154b12ba
--- /dev/null
+++ b/src/templating/test7/node.mustache
@@ -0,0 +1,4 @@
+<{{data}}>
+{{#children}}
+ {{> node}}
+{{/children}}
diff --git a/src/templating/test7/resu.ref b/src/templating/test7/resu.ref
new file mode 100644
index 000000000..d02b24e11
--- /dev/null
+++ b/src/templating/test7/resu.ref
@@ -0,0 +1,7 @@
+family:
+<grandparent>
+ <parent>
+ <child>
+ <parent2>
+ <child2>
+ <pet>
diff --git a/src/templating/test7/vg.ref b/src/templating/test7/vg.ref
new file mode 100644
index 000000000..032e6c447
--- /dev/null
+++ b/src/templating/test7/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json base.mustache
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 69 allocs, 69 frees, 36,313 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test8/.gitignore b/src/templating/test8/.gitignore
new file mode 100644
index 000000000..4d897daa0
--- /dev/null
+++ b/src/templating/test8/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test8/Makefile b/src/templating/test8/Makefile
new file mode 100644
index 000000000..1a3e57914
--- /dev/null
+++ b/src/templating/test8/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json must
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test8/json b/src/templating/test8/json
new file mode 100644
index 000000000..04a1e4aaa
--- /dev/null
+++ b/src/templating/test8/json
@@ -0,0 +1,8 @@
+{
+"val1": "",
+"val2": 0,
+"val3": false,
+"val4": null,
+"val5": [],
+"val6": 0.0
+}
diff --git a/src/templating/test8/must b/src/templating/test8/must
new file mode 100644
index 000000000..a22374438
--- /dev/null
+++ b/src/templating/test8/must
@@ -0,0 +1,6 @@
+x{{#val1}} {{.}} {{/val1}}x
+x{{#val2}} {{.}} {{/val2}}x
+x{{#val3}} {{.}} {{/val3}}x
+x{{#val4}} {{.}} {{/val4}}x
+x{{#val5}} {{.}} {{/val5}}x
+x{{#val6}} {{.}} {{/val6}}x
diff --git a/src/templating/test8/resu.ref b/src/templating/test8/resu.ref
new file mode 100644
index 000000000..e0c16e49a
--- /dev/null
+++ b/src/templating/test8/resu.ref
@@ -0,0 +1,6 @@
+xx
+xx
+xx
+xx
+xx
+xx
diff --git a/src/templating/test8/vg.ref b/src/templating/test8/vg.ref
new file mode 100644
index 000000000..b5b0235f9
--- /dev/null
+++ b/src/templating/test8/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 17 allocs, 17 frees, 4,832 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test_mustach_jansson.c b/src/templating/test_mustach_jansson.c
new file mode 100644
index 000000000..beb155f6d
--- /dev/null
+++ b/src/templating/test_mustach_jansson.c
@@ -0,0 +1,125 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2020 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 test_mustach_jansson.c
+ * @brief testcase to test the mustach/jansson integration
+ * @author Florian Dold
+ */
+#include "platform.h"
+#include "mustach-jansson.h"
+#include <gnunet/gnunet_util_lib.h>
+
+static void
+assert_template (const char *template,
+ json_t *root,
+ const char *expected)
+{
+ char *r;
+ size_t sz;
+
+ GNUNET_assert (0 == mustach_jansson_mem (template,
+ 0,
+ root,
+ Mustach_With_AllExtensions,
+ &r,
+ &sz));
+ GNUNET_assert (0 == strcmp (r,
+ expected));
+ GNUNET_free (r);
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ json_t *root = json_object ();
+ json_t *arr = json_array ();
+ json_t *obj = json_object ();
+ /* test 1 */
+ const char *t1 = "hello world";
+ const char *x1 = "hello world";
+ /* test 2 */
+ const char *t2 = "hello {{ v1 }}";
+ const char *x2 = "hello world";
+ /* test 3 */
+ const char *t3 = "hello {{ v3.x }}";
+ const char *x3 = "hello baz";
+ /* test 4 */
+ const char *t4 = "hello {{# v2 }}{{ . }}{{/ v2 }}";
+ const char *x4 = "hello foobar";
+ /* test 5 */
+ const char *t5 = "hello {{# v3 }}{{ y }}/{{ x }}{{ z }}{{/ v3 }}";
+ const char *x5 = "hello quux/baz";
+ /* test 8 */
+ const char *t8 = "{{^ v4 }}fallback{{/ v4 }}";
+ const char *x8 = "fallback";
+
+ (void) argc;
+ (void) argv;
+ GNUNET_log_setup ("test-mustach-jansson",
+ "INFO",
+ NULL);
+ GNUNET_assert (NULL != root);
+ GNUNET_assert (NULL != arr);
+ GNUNET_assert (NULL != obj);
+ GNUNET_assert (0 ==
+ json_object_set_new (root,
+ "v1",
+ json_string ("world")));
+ GNUNET_assert (0 ==
+ json_object_set_new (root,
+ "v4",
+ json_array ()));
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ json_string ("foo")));
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ json_string ("bar")));
+ GNUNET_assert (0 ==
+ json_object_set_new (root,
+ "v2",
+ arr));
+ GNUNET_assert (0 ==
+ json_object_set_new (root,
+ "v3",
+ obj));
+ GNUNET_assert (0 ==
+ json_object_set_new (root,
+ "amt",
+ json_string ("EUR:123.00")));
+ GNUNET_assert (0 ==
+ json_object_set_new (obj,
+ "x",
+ json_string ("baz")));
+ GNUNET_assert (0 ==
+ json_object_set_new (obj,
+ "y",
+ json_string ("quux")));
+ assert_template (t1, root, x1);
+ assert_template (t2, root, x2);
+ assert_template (t3, root, x3);
+ assert_template (t4, root, x4);
+ assert_template (t5, root, x5);
+ assert_template (t8, root, x8);
+ json_decref (root);
+ return 0;
+}
diff --git a/src/testing/.gitignore b/src/testing/.gitignore
index 216c632e6..e1075ab16 100644
--- a/src/testing/.gitignore
+++ b/src/testing/.gitignore
@@ -1,9 +1,62 @@
-test_auditor_api_version
+test_auditor_api_version_cs
+test_auditor_api_version_rsa
test_bank_api_with_fakebank
test_bank_api_with_fakebank_twisted
test_bank_api_with_pybank
test_bank_api_with_pybank_twisted
test_taler_exchange_aggregator-postgres
test_taler_exchange_wirewatch-postgres
-test_exchange_api_revocation
+test_exchange_api_revocation_cs
+test_exchange_api_revocation_rsa
+test_exchange_api_age_restriction_cs
+test_exchange_api_age_restriction_rsa
+test_exchange_api_conflicts_cs
+test_exchange_api_conflicts_rsa
report*
+test_exchange_management_api_cs
+test_exchange_management_api_rsa
+test_exchange_api_home/.local/share/taler/crypto-eddsa/
+test_exchange_api_home/.local/share/taler/crypto-rsa/
+test_exchange_api_home/.local/share/taler/exchange/offline-keys/secm_tofus.priv
+test_exchange_api_home/.local/share/taler/taler-exchange-secmod-eddsa/
+test_exchange_api_home/.local/share/taler/taler-exchange-secmod-rsa/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/crypto-eddsa/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/offline-keys/secm_tofus.priv
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/taler-exchange-secmod-eddsa/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/taler-exchange-secmod-rsa/
+test_taler_exchange_httpd_home/.local/share/taler/crypto-eddsa/
+test_taler_exchange_httpd_home/.local/share/taler/crypto-rsa/
+test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/secm_tofus.priv
+test_taler_exchange_httpd_home/.local/share/taler/taler-exchange-secmod-eddsa/
+test_taler_exchange_httpd_home/.local/share/taler/taler-exchange-secmod-rsa/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/crypto-rsa/
+test_exchange_api_home/.local/share/taler/exchange-offline/secm_tofus.pub
+test_exchange_api_home/.local/share/taler/exchange-secmod-cs/
+test_exchange_api_home/.local/share/taler/exchange-secmod-eddsa/
+test_exchange_api_home/.local/share/taler/exchange-secmod-rsa/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-offline/secm_tofus.pub
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-secmod-cs/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-secmod-eddsa/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-secmod-rsa/
+test_taler_exchange_httpd_home/.local/share/taler/exchange-offline/secm_tofus.pub
+test_taler_exchange_httpd_home/.local/share/taler/exchange-secmod-cs/
+test_taler_exchange_httpd_home/.local/share/taler/exchange-secmod-eddsa/
+test_taler_exchange_httpd_home/.local/share/taler/exchange-secmod-rsa/
+test_kyc_api
+test_helper_cs_home/
+test_helper_rsa_home/test_exchange_api_twisted-cs
+test_exchange_api_twisted-rsa
+test_exchange_api_twisted_cs
+test_exchange_api_twisted_rsa
+test_exchange_p2p_cs
+test_exchange_p2p_rsa
+*.edited
+tmp-last-response.*
+test_exchange_api_home/taler/auditor/
+test_exchange_api_home/taler/exchange-offline/secm_tofus.pub
+test_exchange_api_home/taler/exchange-secmod-cs/
+test_exchange_api_home/taler/exchange-secmod-eddsa/
+test_exchange_api_home/taler/exchange-secmod-rsa/
+test_exchange_api_keys_cherry_picking_home/taler/
+test_taler_exchange_httpd_home/taler/
+libeufin-bank.pid
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
index cf645aa38..195ab4c55 100644
--- a/src/testing/Makefile.am
+++ b/src/testing/Makefile.am
@@ -10,6 +10,11 @@ if USE_COVERAGE
XLIB = -lgcov
endif
+clean-local:
+ rm -rf report*
+
+bin_SCRIPTS = \
+ taler-unified-setup.sh
# Libraries
@@ -35,8 +40,11 @@ libtalertesting_la_LDFLAGS = \
-version-info 0:0:0 \
-no-undefined
libtalertesting_la_SOURCES = \
+ testing_api_cmd_age_withdraw.c \
+ testing_api_cmd_auditor_add_denom_sig.c \
+ testing_api_cmd_auditor_add.c \
+ testing_api_cmd_auditor_del.c \
testing_api_cmd_auditor_deposit_confirmation.c \
- testing_api_cmd_auditor_exchanges.c \
testing_api_cmd_auditor_exec_auditor.c \
testing_api_cmd_auditor_exec_auditor_dbinit.c \
testing_api_cmd_bank_admin_add_incoming.c \
@@ -47,64 +55,86 @@ libtalertesting_la_SOURCES = \
testing_api_cmd_bank_history_debit.c \
testing_api_cmd_bank_transfer.c \
testing_api_cmd_batch.c \
- testing_api_cmd_check_keys.c \
+ testing_api_cmd_batch_deposit.c \
+ testing_api_cmd_batch_withdraw.c \
+ testing_api_cmd_check_aml_decision.c \
+ testing_api_cmd_check_aml_decisions.c \
+ testing_api_cmd_coin_history.c \
+ testing_api_cmd_common.c \
+ testing_api_cmd_contract_get.c \
testing_api_cmd_deposit.c \
testing_api_cmd_deposits_get.c \
testing_api_cmd_exec_aggregator.c \
- testing_api_cmd_exec_auditor-sign.c \
+ testing_api_cmd_exec_auditor-offline.c \
testing_api_cmd_exec_closer.c \
- testing_api_cmd_exec_keyup.c \
+ testing_api_cmd_exec_expire.c \
+ testing_api_cmd_exec_router.c \
testing_api_cmd_exec_transfer.c \
+ testing_api_cmd_exec_wget.c \
testing_api_cmd_exec_wirewatch.c \
+ testing_api_cmd_get_auditor.c \
+ testing_api_cmd_get_exchange.c \
+ testing_api_cmd_insert_deposit.c \
+ testing_api_cmd_kyc_check_get.c \
+ testing_api_cmd_kyc_proof.c \
+ testing_api_cmd_kyc_wallet_get.c \
+ testing_api_cmd_oauth.c \
+ testing_api_cmd_offline_sign_global_fees.c \
+ testing_api_cmd_offline_sign_wire_fees.c \
+ testing_api_cmd_offline_sign_keys.c \
+ testing_api_cmd_offline_sign_extensions.c \
+ testing_api_cmd_purse_create_deposit.c \
+ testing_api_cmd_purse_delete.c \
+ testing_api_cmd_purse_deposit.c \
+ testing_api_cmd_purse_get.c \
+ testing_api_cmd_purse_merge.c \
testing_api_cmd_recoup.c \
+ testing_api_cmd_recoup_refresh.c \
testing_api_cmd_refund.c \
testing_api_cmd_refresh.c \
+ testing_api_cmd_reserve_attest.c \
+ testing_api_cmd_reserve_close.c \
+ testing_api_cmd_reserve_get.c \
+ testing_api_cmd_reserve_get_attestable.c \
+ testing_api_cmd_reserve_history.c \
+ testing_api_cmd_reserve_open.c \
+ testing_api_cmd_reserve_purse.c \
testing_api_cmd_revoke.c \
- testing_api_cmd_serialize_keys.c \
+ testing_api_cmd_revoke_denom_key.c \
+ testing_api_cmd_revoke_sign_key.c \
+ testing_api_cmd_run_fakebank.c \
+ testing_api_cmd_set_officer.c \
+ testing_api_cmd_set_wire_fee.c \
testing_api_cmd_signal.c \
testing_api_cmd_sleep.c \
testing_api_cmd_stat.c \
- testing_api_cmd_status.c \
+ testing_api_cmd_system_start.c \
+ testing_api_cmd_take_aml_decision.c \
testing_api_cmd_transfer_get.c \
testing_api_cmd_wait.c \
- testing_api_cmd_wire.c \
+ testing_api_cmd_wire_add.c \
+ testing_api_cmd_wire_del.c \
testing_api_cmd_withdraw.c \
- testing_api_cmd_insert_deposit.c \
- testing_api_helpers_auditor.c \
- testing_api_helpers_bank.c \
- testing_api_helpers_exchange.c \
testing_api_loop.c \
- testing_api_traits.c \
- testing_api_trait_amount.c \
- testing_api_trait_blinding_key.c \
- testing_api_trait_cmd.c \
- testing_api_trait_coin_priv.c \
- testing_api_trait_contract.c \
- testing_api_trait_denom_pub.c \
- testing_api_trait_denom_sig.c \
- testing_api_trait_exchange_pub.c \
- testing_api_trait_exchange_sig.c \
- testing_api_trait_fresh_coin.c \
- testing_api_trait_json.c \
- testing_api_trait_merchant_key.c \
- testing_api_trait_number.c \
- testing_api_trait_process.c \
- testing_api_trait_reserve_history.c \
- testing_api_trait_reserve_pub.c \
- testing_api_trait_reserve_priv.c \
- testing_api_trait_string.c \
- testing_api_trait_time.c \
- testing_api_trait_wtid.c
+ testing_api_misc.c \
+ testing_api_traits.c
+
+
libtalertesting_la_LIBADD = \
+ $(top_builddir)/src/lib/libtalerauditor.la \
$(top_builddir)/src/lib/libtalerexchange.la \
+ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
$(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/mhd/libtalermhd.la \
$(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
$(top_builddir)/src/bank-lib/libtalerbank.la \
$(top_builddir)/src/bank-lib/libtalerfakebank.la \
-lgnunetcurl \
-lgnunetjson \
-lgnunetutil \
-ljansson \
+ -lmicrohttpd \
$(XLIB)
@@ -112,31 +142,64 @@ libtalertesting_la_LIBADD = \
AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
-.NOTPARALLEL:
+
check_PROGRAMS = \
- test_auditor_api \
test_auditor_api_version \
test_bank_api_with_fakebank \
- test_bank_api_with_pybank \
- test_exchange_api \
- test_exchange_api_keys_cherry_picking \
- test_exchange_api_revocation \
- test_exchange_api_overlapping_keys_bug \
+ test_bank_api_with_nexus \
+ test_exchange_api_cs \
+ test_exchange_api_rsa \
+ test_exchange_api_age_restriction_cs \
+ test_exchange_api_age_restriction_rsa \
+ test_exchange_api_conflicts_cs \
+ test_exchange_api_conflicts_rsa \
+ test_exchange_api_keys_cherry_picking_cs \
+ test_exchange_api_keys_cherry_picking_rsa \
+ test_exchange_api_revocation_cs \
+ test_exchange_api_revocation_rsa \
+ test_exchange_api_overlapping_keys_bug_cs \
+ test_exchange_api_overlapping_keys_bug_rsa \
+ test_exchange_management_api_cs \
+ test_exchange_management_api_rsa \
+ test_kyc_api \
test_taler_exchange_aggregator-postgres \
- test_taler_exchange_wirewatch-postgres
+ test_taler_exchange_wirewatch-postgres \
+ test_exchange_p2p_cs \
+ test_exchange_p2p_rsa
if HAVE_TWISTER
check_PROGRAMS += \
- test_exchange_api_twisted \
- test_bank_api_with_fakebank_twisted \
- test_bank_api_with_pybank_twisted
+ test_exchange_api_twisted_cs \
+ test_exchange_api_twisted_rsa \
+ test_bank_api_with_fakebank_twisted
endif
+# Removed for now...
+# test_auditor_api_cs
+# test_auditor_api_rsa
+
+
TESTS = \
$(check_PROGRAMS)
-test_auditor_api_SOURCES = \
+test_auditor_api_cs_SOURCES = \
+ test_auditor_api.c
+test_auditor_api_cs_LDADD = \
+ $(top_builddir)/src/lib/libtalerauditor.la \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+test_auditor_api_rsa_SOURCES = \
test_auditor_api.c
-test_auditor_api_LDADD = \
+test_auditor_api_rsa_LDADD = \
$(top_builddir)/src/lib/libtalerauditor.la \
libtalertesting.la \
$(top_builddir)/src/lib/libtalerexchange.la \
@@ -147,7 +210,9 @@ test_auditor_api_LDADD = \
$(top_builddir)/src/util/libtalerutil.la \
-lgnunetcurl \
-lgnunetutil \
- -ljansson
+ -ljansson \
+ $(XLIB)
+
test_auditor_api_version_SOURCES = \
test_auditor_api_version.c
@@ -158,27 +223,46 @@ test_auditor_api_version_LDADD = \
$(top_builddir)/src/util/libtalerutil.la \
-lgnunetcurl \
-lgnunetutil \
- -ljansson
+ -ljansson \
+ $(XLIB)
+
+test_bank_api_with_nexus_SOURCES = \
+ test_bank_api.c
+test_bank_api_with_nexus_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ -lgnunetutil \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(XLIB)
test_bank_api_with_fakebank_SOURCES = \
test_bank_api.c
test_bank_api_with_fakebank_LDADD = \
libtalertesting.la \
- -ltalerexchange \
+ $(top_builddir)/src/lib/libtalerexchange.la \
-lgnunetutil \
- $(top_builddir)/src/bank-lib/libtalerbank.la
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(XLIB)
-test_bank_api_with_pybank_SOURCES = \
- test_bank_api.c
-test_bank_api_with_pybank_LDADD = \
+test_exchange_api_cs_SOURCES = \
+ test_exchange_api.c
+test_exchange_api_cs_LDADD = \
libtalertesting.la \
$(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
+ -lgnunetcurl \
-lgnunetutil \
- $(top_builddir)/src/bank-lib/libtalerbank.la
+ -ljansson \
+ $(XLIB)
-test_exchange_api_SOURCES = \
+test_exchange_api_rsa_SOURCES = \
test_exchange_api.c
-test_exchange_api_LDADD = \
+test_exchange_api_rsa_LDADD = \
libtalertesting.la \
$(top_builddir)/src/lib/libtalerexchange.la \
$(LIBGCRYPT_LIBS) \
@@ -186,13 +270,95 @@ test_exchange_api_LDADD = \
$(top_builddir)/src/bank-lib/libtalerbank.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
-lgnunetcurl \
-lgnunetutil \
- -ljansson
+ -ljansson \
+ $(XLIB)
-test_exchange_api_revocation_SOURCES = \
- test_exchange_api_revocation.c
-test_exchange_api_revocation_LDADD = \
+test_exchange_api_age_restriction_cs_SOURCES = \
+ test_exchange_api_age_restriction.c
+test_exchange_api_age_restriction_cs_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+test_exchange_api_age_restriction_rsa_SOURCES = \
+ test_exchange_api_age_restriction.c
+test_exchange_api_age_restriction_rsa_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+test_exchange_api_conflicts_cs_SOURCES = \
+ test_exchange_api_conflicts.c
+test_exchange_api_conflicts_cs_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+test_exchange_api_conflicts_rsa_SOURCES = \
+ test_exchange_api_conflicts.c
+test_exchange_api_conflicts_rsa_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+test_exchange_p2p_cs_SOURCES = \
+ test_exchange_p2p.c
+test_exchange_p2p_cs_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+test_exchange_p2p_rsa_SOURCES = \
+ test_exchange_p2p.c
+test_exchange_p2p_rsa_LDADD = \
libtalertesting.la \
$(top_builddir)/src/lib/libtalerexchange.la \
$(LIBGCRYPT_LIBS) \
@@ -200,13 +366,30 @@ test_exchange_api_revocation_LDADD = \
$(top_builddir)/src/bank-lib/libtalerbank.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+
+test_exchange_api_keys_cherry_picking_cs_SOURCES = \
+ test_exchange_api_keys_cherry_picking.c
+test_exchange_api_keys_cherry_picking_cs_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
-lgnunetcurl \
-lgnunetutil \
- -ljansson
+ -ljansson \
+ $(XLIB)
-test_exchange_api_keys_cherry_picking_SOURCES = \
+test_exchange_api_keys_cherry_picking_rsa_SOURCES = \
test_exchange_api_keys_cherry_picking.c
-test_exchange_api_keys_cherry_picking_LDADD = \
+test_exchange_api_keys_cherry_picking_rsa_LDADD = \
libtalertesting.la \
$(top_builddir)/src/lib/libtalerexchange.la \
$(LIBGCRYPT_LIBS) \
@@ -215,11 +398,43 @@ test_exchange_api_keys_cherry_picking_LDADD = \
$(top_builddir)/src/bank-lib/libtalerbank.la \
-lgnunetcurl \
-lgnunetutil \
- -ljansson
+ -ljansson \
+ $(XLIB)
-test_exchange_api_overlapping_keys_bug_SOURCES = \
+test_exchange_api_revocation_cs_SOURCES = \
+ test_exchange_api_revocation.c
+test_exchange_api_revocation_cs_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+test_exchange_api_revocation_rsa_SOURCES = \
+ test_exchange_api_revocation.c
+test_exchange_api_revocation_rsa_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+
+test_exchange_api_overlapping_keys_bug_cs_SOURCES = \
test_exchange_api_overlapping_keys_bug.c
-test_exchange_api_overlapping_keys_bug_LDADD = \
+test_exchange_api_overlapping_keys_bug_cs_LDADD = \
libtalertesting.la \
$(top_builddir)/src/lib/libtalerexchange.la \
$(LIBGCRYPT_LIBS) \
@@ -228,7 +443,41 @@ test_exchange_api_overlapping_keys_bug_LDADD = \
$(top_builddir)/src/bank-lib/libtalerbank.la \
-lgnunetcurl \
-lgnunetutil \
- -ljansson
+ -ljansson \
+ $(XLIB)
+
+test_exchange_api_overlapping_keys_bug_rsa_SOURCES = \
+ test_exchange_api_overlapping_keys_bug.c
+test_exchange_api_overlapping_keys_bug_rsa_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+test_exchange_management_api_cs_SOURCES = \
+ test_exchange_management_api.c
+test_exchange_management_api_cs_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetutil \
+ $(XLIB)
+
+test_exchange_management_api_rsa_SOURCES = \
+ test_exchange_management_api.c
+test_exchange_management_api_rsa_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetutil \
+ $(XLIB)
+
test_taler_exchange_aggregator_postgres_SOURCES = \
test_taler_exchange_aggregator.c
@@ -243,7 +492,8 @@ test_taler_exchange_aggregator_postgres_LDADD = \
-lgnunetutil \
-lgnunetjson \
-ljansson \
- -lpthread
+ -lpthread \
+ $(XLIB)
test_taler_exchange_wirewatch_postgres_SOURCES = \
test_taler_exchange_wirewatch.c
@@ -259,11 +509,12 @@ test_taler_exchange_wirewatch_postgres_LDADD = \
-lgnunetjson \
-lgnunetpq \
-ljansson \
- -lpthread
+ -lpthread \
+ $(XLIB)
-test_exchange_api_twisted_SOURCES = \
+test_exchange_api_twisted_cs_SOURCES = \
test_exchange_api_twisted.c
-test_exchange_api_twisted_LDADD = \
+test_exchange_api_twisted_cs_LDADD = \
$(LIBGCRYPT_LIBS) \
libtalertesting.la \
libtalertwistertesting.la \
@@ -275,25 +526,29 @@ test_exchange_api_twisted_LDADD = \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
- -ljansson
+ -ljansson \
+ $(XLIB)
-test_bank_api_with_fakebank_twisted_SOURCES = \
- test_bank_api_twisted.c
-test_bank_api_with_fakebank_twisted_LDADD = \
+test_exchange_api_twisted_rsa_SOURCES = \
+ test_exchange_api_twisted.c
+test_exchange_api_twisted_rsa_LDADD = \
+ $(LIBGCRYPT_LIBS) \
libtalertesting.la \
- $(top_builddir)/src/bank-lib/libtalerbank.la \
- $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ libtalertwistertesting.la \
$(top_builddir)/src/lib/libtalerexchange.la \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
$(top_builddir)/src/json/libtalerjson.la \
- libtalertwistertesting.la \
+ $(top_builddir)/src/util/libtalerutil.la \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
- -ljansson
+ -ljansson \
+ $(XLIB)
-test_bank_api_with_pybank_twisted_SOURCES = \
+test_bank_api_with_fakebank_twisted_SOURCES = \
test_bank_api_twisted.c
-test_bank_api_with_pybank_twisted_LDADD = \
+test_bank_api_with_fakebank_twisted_LDADD = \
libtalertesting.la \
$(top_builddir)/src/bank-lib/libtalerbank.la \
$(top_builddir)/src/bank-lib/libtalerfakebank.la \
@@ -303,32 +558,60 @@ test_bank_api_with_pybank_twisted_LDADD = \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
- -ljansson
+ -ljansson \
+ $(XLIB)
+test_kyc_api_SOURCES = \
+ test_kyc_api.c
+test_kyc_api_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerauditor.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
# Distribution
EXTRA_DIST = \
+ $(bin_SCRIPTS) \
+ valgrind.h \
+ coins-cs.conf \
+ coins-rsa.conf \
+ test_auditor_api-cs.conf \
+ test_auditor_api-rsa.conf \
+ test_auditor_api_expire_reserve_now-cs.conf \
+ test_auditor_api_expire_reserve_now-rsa.conf \
+ test_bank_api.conf \
test_bank_api_fakebank.conf \
test_bank_api_fakebank_twisted.conf \
- test_bank_api_pybank.conf \
- test_bank_api_pybank_twisted.conf \
- test_auditor_api.conf \
- test_auditor_api_expire_reserve_now.conf \
- test_taler_exchange_httpd_home/.config/taler/account-1.json \
- test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv \
- test_taler_exchange_httpd_home/.local/share/taler/exchange/wirefees/x-taler-bank.fee \
- test_exchange_api_home/.config/taler/account-2.json \
- test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv \
- test_exchange_api_home/.local/share/taler/exchange/wirefees/x-taler-bank.fee \
- test_exchange_api_keys_cherry_picking_home/.config/taler/x-taler-bank.json \
- test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/offline-keys/master.priv \
- test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/wirefees/x-taler-bank.fee \
+ test_bank_api_nexus.conf \
+ test_exchange_api_home/taler/auditor/offline-keys/auditor.priv \
+ test_exchange_api_home/.local/share/taler/exchange-offline/master.priv \
+ test_exchange_api_home/.local/share/taler/auditor/offline-keys/auditor.priv \
test_exchange_api.conf \
- test_exchange_api_twisted.conf \
+ test_exchange_api-cs.conf \
+ test_exchange_api-rsa.conf \
+ test_exchange_api_age_restriction.conf \
+ test_exchange_api_age_restriction-cs.conf \
+ test_exchange_api_age_restriction-rsa.conf \
+ test_exchange_api_conflicts.conf \
+ test_exchange_api_conflicts-cs.conf \
+ test_exchange_api_conflicts-rsa.conf \
+ test_exchange_api-twisted.conf \
+ test_exchange_api_twisted-cs.conf \
+ test_exchange_api_twisted-rsa.conf \
test_exchange_api_keys_cherry_picking.conf \
- test_exchange_api_keys_cherry_picking_extended.conf \
- test_exchange_api_keys_cherry_picking_extended_2.conf \
- test_exchange_api_expire_reserve_now.conf \
+ test_exchange_api_keys_cherry_picking-cs.conf \
+ test_exchange_api_keys_cherry_picking-rsa.conf \
+ test_exchange_api_expire_reserve_now-cs.conf \
+ test_exchange_api_expire_reserve_now-rsa.conf \
test-taler-exchange-aggregator-postgres.conf \
- test-taler-exchange-wirewatch-postgres.conf
+ test-taler-exchange-wirewatch-postgres.conf \
+ test_kyc_api.conf
diff --git a/src/testing/coins-cs.conf b/src/testing/coins-cs.conf
new file mode 100644
index 000000000..92163baac
--- /dev/null
+++ b/src/testing/coins-cs.conf
@@ -0,0 +1,118 @@
+
+# Sections starting with "coin_" specify which denominations
+# the exchange should support (and their respective fee structure)
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_5]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_10]
+value = EUR:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+
+[coin_eur_ct_1_age_restricted]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+age_restricted = YES
+CIPHER = CS
+
+[coin_eur_ct_10_age_restricted]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+age_restricted = YES
+CIPHER = CS
+
+[coin_eur_1_age_restricted]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+age_restricted = YES
+CIPHER = CS
+
+[coin_eur_5_age_restricted]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+age_restricted = YES
+CIPHER = CS
+
+[coin_eur_10_age_restricted]
+value = EUR:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+age_restricted = YES
+CIPHER = CS
diff --git a/src/testing/coins-rsa.conf b/src/testing/coins-rsa.conf
new file mode 100644
index 000000000..7a21a343b
--- /dev/null
+++ b/src/testing/coins-rsa.conf
@@ -0,0 +1,128 @@
+# This file is in the public domain.
+
+# Sections starting with "coin_" specify which denominations
+# the exchange should support (and their respective fee structure)
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_5]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_10]
+value = EUR:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_ct_1_age_restricted]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = YES
+CIPHER = RSA
+
+[coin_eur_ct_10_age_restricted]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = YES
+CIPHER = RSA
+
+[coin_eur_1_age_restricted]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = YES
+CIPHER = RSA
+
+[coin_eur_5_age_restricted]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = YES
+CIPHER = RSA
+
+[coin_eur_10_age_restricted]
+value = EUR:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = YES
+CIPHER = RSA \ No newline at end of file
diff --git a/src/testing/taler-unified-setup.sh b/src/testing/taler-unified-setup.sh
new file mode 100755
index 000000000..d933cc819
--- /dev/null
+++ b/src/testing/taler-unified-setup.sh
@@ -0,0 +1,934 @@
+#!/bin/bash
+#
+# This file is part of TALER
+# Copyright (C) 2023, 2024 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/>
+#
+# Author: Christian Grothoff
+#
+# This script configures and launches various GNU Taler services.
+# Which ones depend on command-line options. Use "-h" to find out.
+# Prints "<<READY>>" on a separate line once all requested services
+# are running. Close STDIN (or input 'NEWLINE') to stop all started
+# services again.
+#
+# shellcheck disable=SC2317
+
+set -eu
+
+# These break TALER_HOME control via TALER_TEST_HOME...
+unset XDG_DATA_HOME
+unset XDG_CONFIG_HOME
+unset XDG_CACHE_HOME
+
+EXIT_STATUS=2
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo " SKIP: " "$@" >&2
+ EXIT_STATUS=77
+ exit "$EXIT_STATUS"
+}
+
+# Exit, with error message (hard failure)
+function exit_fail() {
+ echo " FAIL: " "$@" >&2
+ EXIT_STATUS=1
+ exit "$EXIT_STATUS"
+}
+
+# Cleanup to run whenever we exit
+function cleanup()
+{
+ echo "Taler unified setup terminating at $STAGE!" >&2
+
+ for n in $(jobs -p)
+ do
+ kill "$n" 2> /dev/null || true
+ done
+ wait
+ rm -f libeufin-nexus.pid libeufin-sandbox.pid
+ exit "$EXIT_STATUS"
+}
+
+STAGE="boot"
+
+# Install cleanup handler (except for kill -9)
+trap cleanup EXIT
+
+WAIT_FOR_SIGNAL=0
+START_AUDITOR=0
+START_BACKUP=0
+START_EXCHANGE=0
+START_FAKEBANK=0
+START_DONAU=0
+START_CHALLENGER=0
+START_AGGREGATOR=0
+START_MERCHANT=0
+START_NEXUS=0
+START_BANK=0
+START_TRANSFER=0
+START_WIREWATCH=0
+START_DEPOSITCHECK=0
+START_MERCHANT_EXCHANGE=0
+START_MERCHANT_WIREWATCH=0
+USE_ACCOUNT="exchange-account-1"
+USE_VALGRIND=""
+WIRE_DOMAIN="x-taler-bank"
+CONF_ORIG="$HOME/.config/taler.conf"
+LOGLEVEL="DEBUG"
+DEFAULT_SLEEP="0.2"
+
+# Parse command-line options
+while getopts ':abc:d:DeEfghkL:mMnr:stu:vwWz' OPTION; do
+ case "$OPTION" in
+ a)
+ START_AUDITOR="1"
+ ;;
+ b)
+ START_BANK="1"
+ ;;
+ c)
+ CONF_ORIG="$OPTARG"
+ ;;
+ d)
+ WIRE_DOMAIN="$OPTARG"
+ ;;
+ D)
+ START_DONAU="1"
+ ;;
+ e)
+ START_EXCHANGE="1"
+ ;;
+ E)
+ START_MERCHANT_EXCHANGE="1"
+ ;;
+ f)
+ START_FAKEBANK="1"
+ ;;
+ h)
+ echo 'Supported options:'
+ echo ' -a -- start auditor'
+ echo ' -b -- start bank'
+ # shellcheck disable=SC2016
+ echo ' -c $CONF -- set configuration'
+ # shellcheck disable=SC2016
+ echo ' -d $METHOD -- use wire method (default: x-taler-bank)'
+ echo ' -D -- start donau'
+ echo ' -e -- start exchange'
+ echo ' -E -- start taler-merchant-exchange'
+ echo ' -f -- start fakebank'
+ echo ' -g -- start taler-exchange-aggregator'
+ echo ' -h -- print this help'
+ echo ' -k -- start challenger (KYC service)'
+ # shellcheck disable=SC2016
+ echo ' -L $LOGLEVEL -- set log level'
+ echo ' -m -- start taler-merchant'
+ echo ' -M -- start taler-merchant-depositcheck'
+ echo ' -n -- start nexus'
+ # shellcheck disable=SC2016
+ echo ' -r $MEX -- which exchange to use at the merchant (optional)'
+ echo ' -s -- start backup/sync'
+ echo ' -t -- start taler-exchange-transfer'
+ # shellcheck disable=SC2016
+ echo ' -u $SECTION -- exchange account to use'
+ echo ' -v -- use valgrind'
+ echo ' -w -- start taler-exchange-wirewatch'
+ echo ' -W -- wait for signal'
+ echo ' -z -- start taler-merchant-wirewatch'
+ exit 0
+ ;;
+ g)
+ START_AGGREGATOR="1"
+ ;;
+ k)
+ START_CHALLENGER="1"
+ ;;
+ L)
+ LOGLEVEL="$OPTARG"
+ ;;
+ m)
+ START_MERCHANT="1"
+ ;;
+ M)
+ START_DEPOSITCHECK="1"
+ ;;
+ n)
+ START_NEXUS="1"
+ ;;
+ r)
+ USE_MERCHANT_EXCHANGE="$OPTARG"
+ ;;
+ s)
+ START_BACKUP="1"
+ ;;
+ t)
+ START_TRANSFER="1"
+ ;;
+ u)
+ USE_ACCOUNT="$OPTARG"
+ ;;
+ v)
+ USE_VALGRIND="valgrind --leak-check=yes"
+ DEFAULT_SLEEP="2"
+ ;;
+ w)
+ START_WIREWATCH="1"
+ ;;
+ W)
+ WAIT_FOR_SIGNAL="1"
+ ;;
+ z)
+ START_MERCHANT_WIREWATCH="1"
+ ;;
+ ?)
+ exit_fail "Unrecognized command line option"
+ ;;
+ esac
+done
+
+STAGE="init"
+
+echo "Starting with configuration file at: $CONF_ORIG"
+CONF="$CONF_ORIG.edited"
+cp "${CONF_ORIG}" "${CONF}"
+
+STAGE="checks"
+
+echo -n "Testing for jq"
+jq -h > /dev/null || exit_skip " jq required"
+echo " FOUND"
+
+echo -n "Testing for wget"
+wget --help > /dev/null || exit_skip " wget required"
+echo " FOUND"
+
+if [ "1" = "$START_EXCHANGE" ]
+then
+ echo -n "Testing for Taler exchange"
+ taler-exchange-httpd -h > /dev/null || exit_skip " taler-exchange-httpd required"
+ echo " FOUND"
+fi
+
+if [ "1" = "$START_DONAU" ]
+then
+ echo -n "Testing for Donau"
+ donau-httpd -h > /dev/null || exit_skip " donau-httpd required"
+ echo " FOUND"
+fi
+
+if [ "1" = "$START_MERCHANT" ]
+then
+ echo -n "Testing for Taler merchant"
+ taler-merchant-httpd -h > /dev/null || exit_skip " taler-merchant-httpd required"
+ echo " FOUND"
+fi
+
+if [ "1" = "$START_CHALLENGER" ]
+then
+ echo -n "Testing for Taler challenger"
+ challenger-httpd -h > /dev/null || exit_skip " challenger-httpd required"
+ echo " FOUND"
+fi
+
+if [ "1" = "$START_BACKUP" ]
+then
+ echo -n "Testing for sync-httpd"
+ sync-httpd -h > /dev/null || exit_skip " sync-httpd required"
+ echo " FOUND"
+fi
+
+if [ "1" = "$START_NEXUS" ]
+then
+ echo -n "Testing for libeufin-cli"
+ libeufin-cli --help >/dev/null </dev/null || exit_skip " MISSING"
+ echo " FOUND"
+fi
+
+if [ "1" = "$START_BANK" ]
+then
+ echo -n "Testing for libeufin-bank"
+ libeufin-bank --help >/dev/null </dev/null || exit_skip " MISSING"
+ echo " FOUND"
+fi
+
+STAGE="config"
+
+
+EXCHANGE_URL=$(taler-config -c "$CONF" -s "EXCHANGE" -o "BASE_URL")
+CURRENCY=$(taler-config -c "$CONF" -s "TALER" -o "CURRENCY")
+
+echo "Setting up for $CURRENCY at $EXCHANGE_URL"
+
+register_bank_account() {
+ wget \
+ --http-user="$AUSER" \
+ --http-password="$APASS" \
+ --method=DELETE \
+ -o /dev/null \
+ -O /dev/null \
+ -a wget-delete-account.log \
+ "http://localhost:${BANK_PORT}/accounts/$1" \
+ || true # deletion may fail, that's OK!
+ if [ "$1" = "exchange" ] || [ "$1" = "Exchange" ]
+ then
+ IS_EXCHANGE="true"
+ else
+ IS_EXCHANGE="false"
+ fi
+ MAYBE_IBAN="${4:-}"
+ if [ -n "$MAYBE_IBAN" ]
+ then
+ # shellcheck disable=SC2001
+ ENAME=$(echo "$3" | sed -e "s/ /+/g")
+ # Note: this assumes that $3 has no spaces. Should probably escape in the future..
+ PAYTO="payto://iban/SANDBOXX/${MAYBE_IBAN}?receiver-name=$ENAME"
+ BODY='{"username":"'"$1"'","password":"'"$2"'","is_taler_exchange":'"$IS_EXCHANGE"',"name":"'"$3"'","payto_uri":"'"$PAYTO"'"}'
+ else
+ BODY='{"username":"'"$1"'","password":"'"$2"'","is_taler_exchange":'"$IS_EXCHANGE"',"name":"'"$3"'"}'
+ fi
+ wget \
+ --http-user="$AUSER" \
+ --http-password="$APASS" \
+ --method=POST \
+ --header='Content-type: application/json' \
+ --body-data="${BODY}" \
+ -o /dev/null \
+ -O /dev/null \
+ -a wget-register-account.log \
+ "http://localhost:${BANK_PORT}/accounts"
+}
+
+register_fakebank_account() {
+ if [ "$1" = "exchange" ] || [ "$1" = "Exchange" ]
+ then
+ IS_EXCHANGE="true"
+ else
+ IS_EXCHANGE="false"
+ fi
+ BODY='{"username":"'"$1"'","password":"'"$2"'","name":"'"$1"'","is_taler_exchange":'"$IS_EXCHANGE"'}'
+ wget \
+ --post-data="$BODY" \
+ --header='Content-type: application/json' \
+ --tries=3 \
+ --waitretry=1 \
+ --timeout=30 \
+ "http://localhost:$BANK_PORT/accounts" \
+ -a wget-register-account.log \
+ -o /dev/null \
+ -O /dev/null \
+ >/dev/null
+}
+
+
+if [[ "1" = "$START_BANK" ]]
+then
+ BANK_PORT=$(taler-config -c "$CONF" -s "libeufin-bank" -o "PORT")
+ BANK_URL="http://localhost:${BANK_PORT}/"
+fi
+
+if [[ "1" = "$START_FAKEBANK" ]]
+then
+ BANK_PORT=$(taler-config -c "$CONF" -s "BANK" -o "HTTP_PORT")
+ BANK_URL="http://localhost:${BANK_PORT}/"
+fi
+
+STAGE="bank"
+
+if [ "1" = "$START_BANK" ]
+then
+ echo -n "Setting up bank database ... "
+ libeufin-bank dbinit \
+ -r \
+ -c "$CONF" \
+ &> libeufin-bank-reset.log
+ echo "DONE"
+ echo -n "Launching bank ... "
+ libeufin-bank serve \
+ -c "$CONF" \
+ > libeufin-bank-stdout.log \
+ 2> libeufin-bank-stderr.log &
+ echo $! > libeufin-bank.pid
+ echo "DONE"
+ echo -n "Waiting for Bank ..."
+ OK="0"
+ for n in $(seq 1 100); do
+ echo -n "."
+ sleep "$DEFAULT_SLEEP"
+ wget --timeout=1 \
+ --tries=3 \
+ --waitretry=0 \
+ -a wget-bank-check.log \
+ -o /dev/null \
+ -O /dev/null \
+ "${BANK_URL}config" || continue
+ OK="1"
+ break
+ done
+ if [ "1" != "$OK" ]
+ then
+ exit_skip "Failed to launch services (bank)"
+ fi
+ echo "OK"
+ echo -n "Set admin password..."
+ AUSER="admin"
+ APASS="secret"
+ libeufin-bank \
+ passwd \
+ -c "$CONF" \
+ "$AUSER" "$APASS" \
+ &> libeufin-bank-passwd.log
+ libeufin-bank \
+ edit-account \
+ -c "$CONF" \
+ --debit_threshold="$CURRENCY:1000000" \
+ "$AUSER" \
+ &> libeufin-bank-debit-threshold.log
+ echo " OK"
+fi
+
+if [ "1" = "$START_NEXUS" ]
+then
+ echo "Nexus currently not supported ..."
+fi
+
+if [ "1" = "$START_FAKEBANK" ]
+then
+ echo -n "Setting up fakebank ..."
+ $USE_VALGRIND taler-fakebank-run \
+ -c "$CONF" \
+ -L "$LOGLEVEL" \
+ -n 4 \
+ 2> taler-fakebank-run.log &
+ echo " OK"
+fi
+
+if [[ "1" = "$START_NEXUS" || "1" = "$START_FAKEBANK" ]]
+then
+ echo -n "Waiting for the bank"
+ # Wait for bank to be available (usually the slowest)
+ OK="0"
+ for n in $(seq 1 300)
+ do
+ echo -n "."
+ sleep "$DEFAULT_SLEEP"
+ # bank
+ wget --tries=1 \
+ --waitretry=0 \
+ --timeout=1 \
+ --user admin \
+ --password secret \
+ -a wget-bank-check.log \
+ -o /dev/null \
+ -O /dev/null \
+ "http://localhost:${BANK_PORT}/" || continue
+ OK="1"
+ break
+ done
+ if [ "1" != "$OK" ]
+ then
+ exit_skip "Failed to launch services (bank)"
+ fi
+ echo " OK"
+fi
+
+STAGE="accounts"
+
+if [ "1" = "$START_FAKEBANK" ]
+then
+ echo -n "Register Fakebank users ..."
+ register_fakebank_account fortytwo x
+ register_fakebank_account fortythree x
+ register_fakebank_account exchange x
+ register_fakebank_account tor x
+ register_fakebank_account gnunet x
+ register_fakebank_account tutorial x
+ register_fakebank_account survey x
+ echo " DONE"
+fi
+
+if [ "1" = "$START_BANK" ]
+then
+ echo -n "Register bank users ..."
+ # The specified IBAN and name must match the ones hard-coded into
+ # the C helper for the add-incoming call. Without this value,
+ # libeufin-bank won't find the target account to debit along a /add-incoming
+ # call.
+ register_bank_account fortytwo x "User42" FR7630006000011234567890189
+ register_bank_account fortythree x "Forty Three"
+ register_bank_account exchange x "Exchange Company" DE989651
+ register_bank_account tor x "Tor Project"
+ register_bank_account gnunet x "GNUnet"
+ register_bank_account tutorial x "Tutorial"
+ register_bank_account survey x "Survey"
+ echo " DONE"
+fi
+
+STAGE="exchange"
+
+if [ "1" = "$START_EXCHANGE" ]
+then
+ echo -n "Starting exchange ..."
+ EXCHANGE_PORT=$(taler-config -c "$CONF" -s EXCHANGE -o PORT)
+ SERVE=$(taler-config -c "$CONF" -s EXCHANGE -o SERVE)
+ if [ "${SERVE}" = "unix" ]
+ then
+ EXCHANGE_URL=$(taler-config -c "$CONF" -s EXCHANGE -o BASE_URL)
+ else
+ EXCHANGE_URL="http://localhost:${EXCHANGE_PORT}/"
+ fi
+ MASTER_PRIV_FILE=$(taler-config -f -c "${CONF}" -s "EXCHANGE-OFFLINE" -o "MASTER_PRIV_FILE")
+ MASTER_PRIV_DIR=$(dirname "$MASTER_PRIV_FILE")
+ mkdir -p "${MASTER_PRIV_DIR}"
+ if [ ! -e "$MASTER_PRIV_FILE" ]
+ then
+ gnunet-ecc -g1 "$MASTER_PRIV_FILE" > /dev/null 2> /dev/null
+ echo -n "."
+ fi
+ MASTER_PUB=$(gnunet-ecc -p "${MASTER_PRIV_FILE}")
+ MPUB=$(taler-config -c "$CONF" -s exchange -o MASTER_PUBLIC_KEY)
+ if [ "$MPUB" != "$MASTER_PUB" ]
+ then
+ echo -n " patching master_pub ($MASTER_PUB)..."
+ taler-config -c "$CONF" -s exchange -o MASTER_PUBLIC_KEY -V "$MASTER_PUB"
+ fi
+ taler-exchange-dbinit -c "$CONF" --reset
+ $USE_VALGRIND taler-exchange-secmod-eddsa \
+ -c "$CONF" \
+ -L "$LOGLEVEL" \
+ 2> taler-exchange-secmod-eddsa.log &
+ $USE_VALGRIND taler-exchange-secmod-rsa \
+ -c "$CONF" \
+ -L "$LOGLEVEL" \
+ 2> taler-exchange-secmod-rsa.log &
+ $USE_VALGRIND taler-exchange-secmod-cs \
+ -c "$CONF" \
+ -L "$LOGLEVEL" \
+ 2> taler-exchange-secmod-cs.log &
+ $USE_VALGRIND taler-exchange-httpd \
+ -c "$CONF" \
+ -L "$LOGLEVEL" 2> taler-exchange-httpd.log &
+ echo " DONE"
+fi
+
+STAGE="donau"
+
+if [ "1" = "$START_DONAU" ]
+then
+ echo -n "Starting Donau ..."
+ DONAU_PORT=$(donau-config -c "$CONF" -s DONAU -o PORT)
+ SERVE=$(donau-config -c "$CONF" -s DONAU -o SERVE)
+ if [ "${SERVE}" = "unix" ]
+ then
+ DONAU_URL=$(donau-config -c "$CONF" -s DONAU -o BASE_URL)
+ else
+ DONAU_URL="http://localhost:${DONAU_PORT}/"
+ fi
+ donau-dbinit -c "$CONF" --reset
+ $USE_VALGRIND taler-exchange-secmod-eddsa -c "$CONF" -L "$LOGLEVEL" -s donau 2> donau-secmod-eddsa.log &
+ $USE_VALGRIND taler-exchange-secmod-rsa -c "$CONF" -L "$LOGLEVEL" -s donau 2> donau-secmod-rsa.log &
+ $USE_VALGRIND taler-exchange-secmod-cs -c "$CONF" -L "$LOGLEVEL" -s donau 2> donau-secmod-cs.log &
+ $USE_VALGRIND donau-httpd -c "$CONF" -L "$LOGLEVEL" 2> donau-httpd.log &
+ echo " DONE"
+fi
+
+STAGE="wirewatch"
+
+if [ "1" = "$START_WIREWATCH" ]
+then
+ echo -n "Starting wirewatch ..."
+ $USE_VALGRIND taler-exchange-wirewatch \
+ --account="$USE_ACCOUNT" \
+ -c "$CONF" \
+ -L "$LOGLEVEL" \
+ --longpoll-timeout="60 s" \
+ 2> taler-exchange-wirewatch.log &
+ echo " DONE"
+fi
+
+STAGE="aggregator"
+
+if [ "1" = "$START_AGGREGATOR" ]
+then
+ echo -n "Starting aggregator ..."
+ $USE_VALGRIND taler-exchange-aggregator \
+ -c "$CONF" \
+ -L "$LOGLEVEL" \
+ 2> taler-exchange-aggregator.log &
+ echo " DONE"
+fi
+
+STAGE="transfer"
+
+if [ "1" = "$START_TRANSFER" ]
+then
+ echo -n "Starting transfer ..."
+ $USE_VALGRIND taler-exchange-transfer \
+ -c "$CONF" \
+ -L "$LOGLEVEL" \
+ 2> taler-exchange-transfer.log &
+ echo " DONE"
+fi
+
+STAGE="merchant"
+
+if [ "1" = "$START_MERCHANT" ]
+then
+ echo -n "Starting merchant ..."
+ if [ -n "${USE_MERCHANT_EXCHANGE+x}" ]
+ then
+ MEPUB=$(taler-config -c "$CONF" -s "${USE_MERCHANT_EXCHANGE}" -o MASTER_KEY)
+ MXPUB=${MASTER_PUB:-$(taler-config -c "$CONF" -s exchange -o MASTER_PUBLIC_KEY)}
+ if [ "$MEPUB" != "$MXPUB" ]
+ then
+ echo -n " patching master_pub ($MXPUB)..."
+ taler-config -c "$CONF" -s "${USE_MERCHANT_EXCHANGE}" -o MASTER_KEY -V "$MXPUB"
+ fi
+ fi
+ MERCHANT_TYPE=$(taler-config -c "$CONF" -s MERCHANT -o SERVE)
+ if [ "unix" = "$MERCHANT_TYPE" ]
+ then
+ MERCHANT_URL="$(taler-config -c "$CONF" -s MERCHANT -o BASE_URL)"
+ else
+ MERCHANT_PORT="$(taler-config -c "$CONF" -s MERCHANT -o PORT)"
+ MERCHANT_URL="http://localhost:${MERCHANT_PORT}/"
+ fi
+ taler-merchant-dbinit \
+ -c "$CONF" \
+ --reset &> taler-merchant-dbinit.log
+ $USE_VALGRIND taler-merchant-httpd \
+ -c "$CONF" \
+ -L "$LOGLEVEL" 2> taler-merchant-httpd.log &
+ $USE_VALGRIND taler-merchant-webhook \
+ -c "$CONF" \
+ -L "$LOGLEVEL" 2> taler-merchant-webhook.log &
+ echo " DONE"
+ if [ "1" = "$START_MERCHANT_WIREWATCH" ]
+ then
+ echo -n "Starting taler-merchant-wirewatch ..."
+ $USE_VALGRIND taler-merchant-wirewatch \
+ -c "$CONF" \
+ -L "$LOGLEVEL" \
+ --persist \
+ 2> taler-merchant-wirewatch.log &
+ echo " DONE"
+ fi
+ if [ "1" = "$START_MERCHANT_EXCHANGE" ]
+ then
+ echo -n "Starting taler-merchant-exchange ..."
+ $USE_VALGRIND taler-merchant-exchange \
+ -c "$CONF" \
+ -L "$LOGLEVEL" 2> taler-merchant-exchange.log &
+ echo " DONE"
+ fi
+ if [ "1" = "$START_DEPOSITCHECK" ]
+ then
+ echo -n "Starting taler-merchant-depositcheck ..."
+ $USE_VALGRIND taler-merchant-depositcheck \
+ -c "$CONF" \
+ -L "$LOGLEVEL" 2> taler-merchant-depositcheck.log &
+ echo " DONE"
+ fi
+fi
+
+STAGE="sync"
+
+if [ "1" = "$START_BACKUP" ]
+then
+ echo -n "Starting sync ..."
+ SYNC_PORT=$(taler-config -c "$CONF" -s SYNC -o PORT)
+ SERVE=$(taler-config -c "$CONF" -s SYNC -o SERVE)
+ if [ "${SERVE}" = "unix" ]
+ then
+ SYNC_URL=$(taler-config -c "$CONF" -s SYNC -o BASE_URL)
+ else
+ SYNC_URL="http://localhost:${SYNC_PORT}/"
+ fi
+ sync-dbinit -c "$CONF" --reset
+ $USE_VALGRIND sync-httpd \
+ -c "$CONF" \
+ -L "$LOGLEVEL" \
+ 2> sync-httpd.log &
+ echo " DONE"
+fi
+
+STAGE="challenger"
+
+if [ "1" = "$START_CHALLENGER" ]
+then
+ echo -n "Starting challenger ..."
+ CHALLENGER_PORT=$(challenger-config -c "$CONF" -s CHALLENGER -o PORT)
+ SERVE=$(taler-config -c "$CONF" -s CHALLENGER -o SERVE)
+ if [ "${SERVE}" = "unix" ]
+ then
+ CHALLENGER_URL=$(taler-config -c "$CONF" -s CHALLENGER -o BASE_URL)
+ else
+ CHALLENGER_URL="http://localhost:${CHALLENGER_PORT}/"
+ fi
+ challenger-dbinit \
+ -c "$CONF" \
+ --reset
+ $USE_VALGRIND challenger-httpd \
+ -c "$CONF" \
+ -L "$LOGLEVEL" \
+ 2> challenger-httpd.log &
+ echo " DONE"
+ for SECTION in $(taler-config -c "$CONF" -S | grep kyc-provider)
+ do
+ LOGIC=$(taler-config -c "$CONF" -s "$SECTION" -o "LOGIC")
+ if [ "${LOGIC}" = "oauth2" ]
+ then
+ INFO=$(taler-config -c "$CONF" -s "$SECTION" -o "KYC_OAUTH2_INFO_URL")
+ if [ "${CHALLENGER_URL}info" = "$INFO" ]
+ then
+ echo -n "Enabling Challenger client for $SECTION"
+ CLIENT_SECRET=$(taler-config -c "$CONF" -s "$SECTION" -o "KYC_OAUTH2_CLIENT_SECRET")
+ RFC_8959_PREFIX="secret-token:"
+ if ! echo "${CLIENT_SECRET}" | grep ^${RFC_8959_PREFIX} > /dev/null
+ then
+ exit_fail "Client secret does not begin with '${RFC_8959_PREFIX}'"
+ fi
+ REDIRECT_URI="${EXCHANGE_URL}kyc-proof/kyc-provider-example-challeger"
+ CLIENT_ID=$(challenger-admin --add="${CLIENT_SECRET}" --quiet "${REDIRECT_URI}")
+ taler-config -c "$CONF" -s "$SECTION" -o KYC_OAUTH2_CLIENT_ID -V "$CLIENT_ID"
+ echo " DONE"
+ fi
+ fi
+ done
+fi
+
+STAGE="auditor"
+
+if [ "1" = "$START_AUDITOR" ]
+then
+ echo -n "Starting auditor ..."
+ AUDITOR_URL=$(taler-config -c "$CONF" -s AUDITOR -o BASE_URL)
+ AUDITOR_PRIV_FILE=$(taler-config -f -c "$CONF" -s AUDITOR -o AUDITOR_PRIV_FILE)
+ AUDITOR_PRIV_DIR=$(dirname "$AUDITOR_PRIV_FILE")
+ mkdir -p "$AUDITOR_PRIV_DIR"
+ if [ ! -e "$AUDITOR_PRIV_FILE" ]
+ then
+ gnunet-ecc -g1 "$AUDITOR_PRIV_FILE" > /dev/null 2> /dev/null
+ echo -n "."
+ fi
+ AUDITOR_PUB=$(gnunet-ecc -p "${AUDITOR_PRIV_FILE}")
+ MAPUB=${MASTER_PUB:-$(taler-config -c "$CONF" -s exchange -o MASTER_PUBLIC_KEY)}
+ taler-auditor-dbinit \
+ -c "$CONF" \
+ --reset
+ $USE_VALGRIND taler-auditor-httpd \
+ -L "$LOGLEVEL" \
+ -c "$CONF" 2> taler-auditor-httpd.log &
+# $USE_VALGRIND taler-helper-auditor-deposits \
+# -L "$LOGLEVEL" \
+# -c "$CONF" 2> taler-helper-auditor.log &
+ echo " DONE"
+fi
+
+STAGE="wait"
+
+echo -n "Waiting for Taler services ..."
+# Wait for all other taler services to be available
+E_DONE=0
+M_DONE=0
+S_DONE=0
+K_DONE=0
+A_DONE=0
+for n in $(seq 1 20)
+do
+ sleep "$DEFAULT_SLEEP"
+ OK="0"
+ if [ "0" = "$E_DONE" ] && [ "1" = "$START_EXCHANGE" ]
+ then
+ echo -n "E"
+ wget \
+ --tries=1 \
+ --timeout=1 \
+ "${EXCHANGE_URL}config" \
+ -o /dev/null \
+ -O /dev/null >/dev/null || continue
+ E_DONE=1
+ fi
+ if [ "0" = "$M_DONE" ] && [ "1" = "$START_MERCHANT" ]
+ then
+ echo -n "M"
+ wget \
+ --tries=1 \
+ --timeout=1 \
+ "${MERCHANT_URL}config" \
+ -o /dev/null \
+ -O /dev/null >/dev/null || continue
+ M_DONE=1
+ fi
+ if [ "0" = "$S_DONE" ] && [ "1" = "$START_BACKUP" ]
+ then
+ echo -n "S"
+ wget \
+ --tries=1 \
+ --timeout=1 \
+ "${SYNC_URL}config" \
+ -o /dev/null \
+ -O /dev/null >/dev/null || continue
+ S_DONE=1
+ fi
+ if [ "0" = "$K_DONE" ] && [ "1" = "$START_CHALLENGER" ]
+ then
+ echo -n "K"
+ wget \
+ --tries=1 \
+ --timeout=1 \
+ "${CHALLENGER_URL}config" \
+ -o /dev/null \
+ -O /dev/null >/dev/null || continue
+ K_DONE=1
+ fi
+ if [ "0" = "$A_DONE" ] && [ "1" = "$START_AUDITOR" ]
+ then
+ echo -n "A"
+ wget \
+ --tries=1 \
+ --timeout=1 \
+ "${AUDITOR_URL}config" \
+ -o /dev/null \
+ -O /dev/null >/dev/null || continue
+ A_DONE=1
+ fi
+ OK="1"
+ break
+done
+if [ 1 != "$OK" ]
+then
+ exit_skip "Failed to launch (some) Taler services"
+fi
+echo " OK"
+
+if [ "1" = "$START_EXCHANGE" ]
+then
+ echo -n "Wait for exchange /management/keys to be ready "
+ OK="0"
+ LAST_RESPONSE=$(mktemp tmp-last-response.XXXXXXXX)
+ for n in $(seq 1 10)
+ do
+ echo -n "."
+ sleep "$DEFAULT_SLEEP"
+ # exchange
+ wget \
+ --tries=3 \
+ --waitretry=0 \
+ --timeout=30 \
+ "${EXCHANGE_URL}management/keys"\
+ -o /dev/null \
+ -O "$LAST_RESPONSE" \
+ >/dev/null || continue
+ OK="1"
+ break;
+ done
+ if [ "1" != "$OK" ]
+ then
+ cat "$LAST_RESPONSE"
+ exit_fail "Failed to setup exchange keys, check secmod logs"
+ fi
+ rm "$LAST_RESPONSE"
+ echo " OK"
+
+ echo -n "Setting up exchange keys ..."
+ taler-exchange-offline -c "$CONF" \
+ download \
+ sign \
+ wire-fee now "$WIRE_DOMAIN" "$CURRENCY:0.01" "$CURRENCY:0.01" \
+ global-fee now "$CURRENCY:0.01" "$CURRENCY:0.01" "$CURRENCY:0.0" 1h 1year 5 \
+ upload &> taler-exchange-offline.log
+ echo "OK"
+ ENABLED=$(taler-config -c "$CONF" -s "$USE_ACCOUNT" -o "ENABLE_CREDIT")
+ if [ "YES" = "$ENABLED" ]
+ then
+ echo -n "Configuring bank account $USE_ACCOUNT ..."
+ EXCHANGE_PAYTO_URI=$(taler-config -c "$CONF" -s "$USE_ACCOUNT" -o "PAYTO_URI")
+ taler-exchange-offline -c "$CONF" \
+ enable-account "$EXCHANGE_PAYTO_URI" \
+ upload &> "taler-exchange-offline-account.log"
+ echo " OK"
+ else
+ echo "WARNING: Account ${USE_ACCOUNT} not enabled (set to: '$ENABLED')"
+ fi
+ if [ "1" = "$START_AUDITOR" ]
+ then
+ echo -n "Enabling auditor ..."
+ taler-exchange-offline -c "$CONF" \
+ enable-auditor "$AUDITOR_PUB" "$AUDITOR_URL" "$CURRENCY Auditor" \
+ upload &> taler-exchange-offline-auditor.log
+ echo "OK"
+ fi
+
+ echo -n "Checking /keys "
+ OK="0"
+ LAST_RESPONSE=$(mktemp tmp-last-response.XXXXXXXX)
+ for n in $(seq 1 10)
+ do
+ echo -n "."
+ sleep "$DEFAULT_SLEEP"
+ wget \
+ --tries=1 \
+ --timeout=5 \
+ "${EXCHANGE_URL}keys" \
+ -a wget-keys-check.log \
+ -o /dev/null \
+ -O "$LAST_RESPONSE" \
+ >/dev/null || continue
+ OK="1"
+ break
+ done
+ if [ "1" != "$OK" ]
+ then
+ cat "$LAST_RESPONSE"
+ exit_fail " Failed to fetch ${EXCHANGE_URL}keys"
+ fi
+ rm "$LAST_RESPONSE"
+ echo " OK"
+fi
+
+if [ "1" = "$START_AUDITOR" ]
+then
+ echo -n "Setting up auditor signatures ..."
+ timeout 15 taler-auditor-offline -c "$CONF" \
+ download \
+ sign \
+ upload &> taler-auditor-offline.log
+ echo " OK"
+fi
+
+STAGE="ready"
+
+# Signal caller that we are ready.
+echo "<<READY>>"
+
+if [ "1" = "$WAIT_FOR_SIGNAL" ]
+then
+ while true
+ do
+ sleep 0.1
+ done
+else
+ # Wait until caller stops us.
+ # shellcheck disable=SC2162
+ read
+fi
+
+STAGE="exiting"
+
+echo "Taler unified setup terminating!" >&2
+EXIT_STATUS=0
+exit "$EXIT_STATUS"
diff --git a/src/testing/test-taler-exchange-aggregator-postgres.conf b/src/testing/test-taler-exchange-aggregator-postgres.conf
index d4252e23f..8c3ee4ba5 100644
--- a/src/testing/test-taler-exchange-aggregator-postgres.conf
+++ b/src/testing/test-taler-exchange-aggregator-postgres.conf
@@ -1,98 +1,71 @@
+# This file is in the public domain.
+
[PATHS]
# Persistent data storage for the testcase
TALER_TEST_HOME = test_taler_exchange_httpd_home/
+[taler-exchange-secmod-rsa]
+# Reduce from 1 year to speed up test
+LOOKAHEAD_SIGN = 24 days
+
+[taler-exchange-secmod-eddsa]
+LOOKAHEAD_SIGN = 24 days
+DURATION = 14 days
+
[taler]
-# Currency supported by the exchange (can only be one)
CURRENCY = EUR
CURRENCY_ROUND_UNIT = EUR:0.01
[exchange]
-# The DB plugin to use
+AML_THRESHOLD = EUR:1000000
DB = postgres
-
-# HTTP port the exchange listens to
PORT = 8081
-
-# Master public key used to sign the exchange's various keys
MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
-
-# Expected base URL of the exchange. Used in wire transfers for
-# the tracking API.
-BASE_URL = "https://exchange.taler.net/"
+BASE_URL = "http://localhost:8081/"
[auditor]
BASE_URL = "http://auditor.example.com/"
+PORT = 8083
[auditordb-postgres]
CONFIG = "postgres:///talercheck"
[exchangedb]
-# After how long do we close idle reserves? The exchange
-# and the auditor must agree on this value. We currently
-# expect it to be globally defined for the whole system,
-# as there is no way for wallets to query this value. Thus,
-# it is only configurable for testing, and should be treated
-# as constant in production.
IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
+LEGAL_RESERVE_EXPIRATION_TIME = 7 years
[exchangedb-postgres]
-
-#The connection string the plugin has to use for connecting to the database
CONFIG = postgres:///talercheck
-[exchangedb]
-
-# After how long do we close idle reserves? The exchange
-# and the auditor must agree on this value. We currently
-# expect it to be globally defined for the whole system,
-# as there is no way for wallets to query this value. Thus,
-# it is only configurable for testing, and should be treated
-# as constant in production.
-IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
-
-# After how long do we forget about reserves? Should be above
-# the legal expiration timeframe of withdrawn coins.
-LEGAL_RESERVE_EXPIRATION_TIME = 7 years
-
[exchange-account-1]
-
# What is the account URL?
-PAYTO_URI = "payto://x-taler-bank/localhost/2"
-WIRE_GATEWAY_URL = "http://localhost:8082/2/"
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-1]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
+
+[admin-accountcredentials-1]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
WIRE_GATEWAY_AUTH_METHOD = basic
USERNAME = Exchange
PASSWORD = x
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json
-ENABLE_DEBIT = YES
-ENABLE_CREDIT = YES
-TALER_BANK_AUTH_METHOD = NONE
[bank]
HTTP_PORT = 8082
-[fees-x-taler-bank]
-
-# Fees for the foreseeable future...
-# If you see this after 2018, update to match the next 10 years...
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
-WIRE-FEE-2027 = EUR:0.01
-
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-CLOSING-FEE-2027 = EUR:0.01
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
diff --git a/src/testing/test-taler-exchange-wirewatch-postgres.conf b/src/testing/test-taler-exchange-wirewatch-postgres.conf
index 420a46f1d..4f13077ac 100644
--- a/src/testing/test-taler-exchange-wirewatch-postgres.conf
+++ b/src/testing/test-taler-exchange-wirewatch-postgres.conf
@@ -1,93 +1,71 @@
+# This file is in the public domain.
+
[PATHS]
# Persistent data storage for the testcase
TALER_TEST_HOME = test_taler_exchange_httpd_home/
+[taler-exchange-secmod-rsa]
+LOOKAHEAD_SIGN = 24 days
+
+[taler-exchange-secmod-eddsa]
+LOOKAHEAD_SIGN = 24 days
+DURATION = 14 days
+
[taler]
-# Currency supported by the exchange (can only be one)
CURRENCY = EUR
CURRENCY_ROUND_UNIT = EUR:0.01
[exchange]
-# The DB plugin to use
+AML_THRESHOLD = EUR:1000000
DB = postgres
-
-# HTTP port the exchange listens to
PORT = 8081
-
-# Master public key used to sign the exchange's various keys
MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
-
-# Expected base URL of the exchange.
BASE_URL = "http://localhost:8081/"
[exchangedb]
-# After how long do we close idle reserves? The exchange
-# and the auditor must agree on this value. We currently
-# expect it to be globally defined for the whole system,
-# as there is no way for wallets to query this value. Thus,
-# it is only configurable for testing, and should be treated
-# as constant in production.
-#
# This is THE test that requires a short reserve expiration time!
IDLE_RESERVE_EXPIRATION_TIME = 4 s
[exchangedb-postgres]
-#The connection string the plugin has to use for connecting to the database
-CONFIG = "postgres:///talercheck"
-
-[auditordb-postgres]
CONFIG = "postgres:///talercheck"
[auditor]
BASE_URL = "http://localhost:8083/"
-
-# HTTP port the auditor listens to
PORT = 8083
-[exchange-account-1]
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
+[exchange-account-1]
# What is the account URL?
-PAYTO_URI = "payto://x-taler-bank/localhost/2"
-WIRE_GATEWAY_URL = "http://localhost:8082/2/"
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+[exchange-accountcredentials-1]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
WIRE_GATEWAY_AUTH_METHOD = basic
USERNAME = Exchange
PASSWORD = x
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json
-PLUGIN = "taler_bank"
-ENABLE_DEBIT = YES
-ENABLE_CREDIT = YES
-TALER_BANK_AUTH_METHOD = NONE
+[admin-accountcredentials-1]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
[bank]
HTTP_PORT = 8082
-[fees-x-taler-bank]
-
-# Fees for the foreseeable future...
-# If you see this after 2018, update to match the next 10 years...
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
-WIRE-FEE-2027 = EUR:0.01
-
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-CLOSING-FEE-2027 = EUR:0.01
+[libeufin-bank]
+CURRENCY = EUR
+DEFAULT_CUSTOMER_DEBT_LIMIT = EUR:200
+DEFAULT_ADMIN_DEBT_LIMIT = EUR:2000
+REGISTRATION_BONUS_ENABLED = yes
+REGISTRATION_BONUS = EUR:100
+SUGGESTED_WITHDRAWAL_EXCHANGE = http://localhost:8081/
+SERVE = tcp
+PORT = 8082
# Need at least one coin, otherwise Exchange
# refuses to start.
@@ -100,4 +78,5 @@ fee_withdraw = EUR:0.00
fee_deposit = EUR:0.00
fee_refresh = EUR:0.01
fee_refund = EUR:0.01
+CIPHER = RSA
rsa_keysize = 1024
diff --git a/src/testing/test_auditor_api-cs.conf b/src/testing/test_auditor_api-cs.conf
new file mode 100644
index 000000000..b80696fb2
--- /dev/null
+++ b/src/testing/test_auditor_api-cs.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+#
+@INLINE@ coins-cs.conf
+@INLINE@ test_exchange_api.conf
diff --git a/src/testing/test_auditor_api-rsa.conf b/src/testing/test_auditor_api-rsa.conf
new file mode 100644
index 000000000..671e81108
--- /dev/null
+++ b/src/testing/test_auditor_api-rsa.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+#
+@INLINE@ coins-rsa.conf
+@INLINE@ test_exchange_api.conf
diff --git a/src/testing/test_auditor_api.c b/src/testing/test_auditor_api.c
index 7b6996264..3810c601e 100644
--- a/src/testing/test_auditor_api.c
+++ b/src/testing/test_auditor_api.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 Taler Systems SA
+ Copyright (C) 2014-2020 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
@@ -29,6 +29,7 @@
#include "taler_auditor_service.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_testing_lib.h>
#include <microhttpd.h>
#include "taler_bank_service.h"
#include "taler_fakebank_lib.h"
@@ -39,20 +40,14 @@
* Configuration file we use. One (big) configuration is used
* for the various components for this test.
*/
-#define CONFIG_FILE "test_auditor_api.conf"
+static char *config_file;
-#define CONFIG_FILE_EXPIRE_RESERVE_NOW \
- "test_auditor_api_expire_reserve_now.conf"
+static char *config_file_expire_reserve_now;
/**
- * Exchange configuration data.
+ * Our credentials.
*/
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-/**
- * Bank configuration data.
- */
-static struct TALER_TESTING_BankConfiguration bc;
+static struct TALER_TESTING_Credentials cred;
/**
* Execute the taler-exchange-wirewatch command with
@@ -61,7 +56,7 @@ static struct TALER_TESTING_BankConfiguration bc;
* @param label label to use for the command.
*/
#define CMD_EXEC_WIREWATCH(label) \
- TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE)
+ TALER_TESTING_cmd_exec_wirewatch2 (label, config_file, "exchange-account-2")
/**
* Execute the taler-exchange-aggregator, closer and transfer commands with
@@ -70,8 +65,9 @@ static struct TALER_TESTING_BankConfiguration bc;
* @param label label to use for the command.
*/
#define CMD_EXEC_AGGREGATOR(label) \
- TALER_TESTING_cmd_exec_aggregator (label, CONFIG_FILE), \
- TALER_TESTING_cmd_exec_transfer (label, CONFIG_FILE)
+ TALER_TESTING_cmd_sleep (label "-sleep", 1), \
+ TALER_TESTING_cmd_exec_aggregator (label, config_file), \
+ TALER_TESTING_cmd_exec_transfer (label, config_file)
/**
* Run wire transfer of funds from some user's account to the
@@ -82,8 +78,8 @@ static struct TALER_TESTING_BankConfiguration bc;
*/
#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
TALER_TESTING_cmd_admin_add_incoming (label, amount, \
- &bc.exchange_auth, \
- bc.user42_payto)
+ &cred.ba, \
+ cred.user42_payto)
/**
* Run the taler-auditor.
@@ -91,7 +87,7 @@ static struct TALER_TESTING_BankConfiguration bc;
* @param label label to use for the command.
*/
#define CMD_RUN_AUDITOR(label) \
- TALER_TESTING_cmd_exec_auditor (label, CONFIG_FILE)
+ TALER_TESTING_cmd_exec_auditor (label, config_file)
/**
@@ -115,7 +111,7 @@ run (void *cls,
"EUR:5.01"),
TALER_TESTING_cmd_check_bank_admin_transfer
("check-create-reserve-1",
- "EUR:5.01", bc.user42_payto, bc.exchange_payto,
+ "EUR:5.01", cred.user42_payto, cred.exchange_payto,
"create-reserve-1"),
/**
* Make a reserve exist, according to the previous transfer.
@@ -127,6 +123,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
"create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_end ()
};
@@ -138,7 +135,7 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("deposit-simple",
"withdraw-coin-1",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:5",
@@ -155,7 +152,7 @@ run (void *cls,
"EUR:5.01"),
TALER_TESTING_cmd_check_bank_admin_transfer
("check-refresh-create-reserve-1",
- "EUR:5.01", bc.user42_payto, bc.exchange_payto,
+ "EUR:5.01", cred.user42_payto, cred.exchange_payto,
"refresh-create-reserve-1"),
/**
* Make previous command effective.
@@ -167,6 +164,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1",
"refresh-create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
* Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in
@@ -175,14 +173,15 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("refresh-deposit-partial",
"refresh-withdraw-coin-1",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"ice\",\"value\":\"EUR:1\"}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
MHD_HTTP_OK),
/**
* Melt the rest of the coin's value (EUR:4.00 = 3x EUR:1.03 + 7x
- * EUR:0.13) */
+ * EUR:0.13)
+ */
TALER_TESTING_cmd_melt_double ("refresh-melt-1",
"refresh-withdraw-coin-1",
MHD_HTTP_OK,
@@ -199,7 +198,7 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-1b",
"refresh-reveal-1",
3,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":3}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:0.1",
@@ -213,74 +212,83 @@ run (void *cls,
* happen here, as each deposit operation is run with a
* fresh merchant public key! NOTE: this comment comes
* "verbatim" from the old test-suite, and IMO does not explain
- * a lot!*///
+ * a lot! */
CMD_EXEC_AGGREGATOR ("run-aggregator"),
/**
* Check all the transfers took place.
*/
- TALER_TESTING_cmd_check_bank_transfer
- ("check_bank_transfer-499c", ec.exchange_url,
- "EUR:4.98", bc.exchange_payto, bc.user42_payto),
- TALER_TESTING_cmd_check_bank_transfer
- ("check_bank_transfer-99c1", ec.exchange_url,
- "EUR:0.98", bc.exchange_payto, bc.user42_payto),
- TALER_TESTING_cmd_check_bank_transfer
- ("check_bank_transfer-99c", ec.exchange_url,
- "EUR:0.08", bc.exchange_payto, bc.user43_payto),
+ TALER_TESTING_cmd_check_bank_transfer (
+ "check_bank_transfer-499c",
+ cred.exchange_url,
+ "EUR:4.98",
+ cred.exchange_payto,
+ cred.user42_payto),
+ TALER_TESTING_cmd_check_bank_transfer (
+ "check_bank_transfer-99c1",
+ cred.exchange_url,
+ "EUR:0.98",
+ cred.exchange_payto,
+ cred.user42_payto),
+ TALER_TESTING_cmd_check_bank_transfer (
+ "check_bank_transfer-99c",
+ cred.exchange_url,
+ "EUR:0.08",
+ cred.exchange_payto,
+ cred.user43_payto),
/* The following transactions got originated within
* the "massive deposit confirms" batch. */
- TALER_TESTING_cmd_check_bank_transfer
- ("check-massive-transfer-1",
- ec.exchange_url,
+ TALER_TESTING_cmd_check_bank_transfer (
+ "check-massive-transfer-1",
+ cred.exchange_url,
"EUR:0.98",
- bc.exchange_payto, bc.user43_payto),
+ cred.exchange_payto, cred.user43_payto),
TALER_TESTING_cmd_check_bank_transfer
("check-massive-transfer-2",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.98",
- bc.exchange_payto, bc.user43_payto),
+ cred.exchange_payto, cred.user43_payto),
TALER_TESTING_cmd_check_bank_transfer
("check-massive-transfer-3",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.98",
- bc.exchange_payto, bc.user43_payto),
+ cred.exchange_payto, cred.user43_payto),
TALER_TESTING_cmd_check_bank_transfer
("check-massive-transfer-4",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.98",
- bc.exchange_payto, bc.user43_payto),
+ cred.exchange_payto, cred.user43_payto),
TALER_TESTING_cmd_check_bank_transfer
("check-massive-transfer-5",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.98",
- bc.exchange_payto, bc.user43_payto),
+ cred.exchange_payto, cred.user43_payto),
TALER_TESTING_cmd_check_bank_transfer
("check-massive-transfer-6",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.98",
- bc.exchange_payto, bc.user43_payto),
+ cred.exchange_payto, cred.user43_payto),
TALER_TESTING_cmd_check_bank_transfer
("check-massive-transfer-7",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.98",
- bc.exchange_payto, bc.user43_payto),
+ cred.exchange_payto, cred.user43_payto),
TALER_TESTING_cmd_check_bank_transfer
("check-massive-transfer-8",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.98",
- bc.exchange_payto, bc.user43_payto),
+ cred.exchange_payto, cred.user43_payto),
TALER_TESTING_cmd_check_bank_transfer
("check-massive-transfer-9",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.98",
- bc.exchange_payto, bc.user43_payto),
+ cred.exchange_payto, cred.user43_payto),
TALER_TESTING_cmd_check_bank_transfer
("check-massive-transfer-10",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.98",
- bc.exchange_payto, bc.user43_payto),
+ cred.exchange_payto, cred.user43_payto),
TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"),
TALER_TESTING_cmd_end ()
};
@@ -298,17 +306,18 @@ run (void *cls,
TALER_TESTING_cmd_check_bank_admin_transfer (
"check_bank_transfer-unaggregated",
"EUR:5.01",
- bc.user42_payto,
- bc.exchange_payto,
+ cred.user42_payto,
+ cred.exchange_payto,
"create-reserve-unaggregated"),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated",
"create-reserve-unaggregated",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit ("deposit-unaggregated",
"withdraw-coin-unaggregated",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_relative_multiply
(GNUNET_TIME_UNIT_YEARS,
@@ -336,6 +345,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1",
"create-reserve-r1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
* Spend 5 EUR of the 5 EUR coin (in full). Merchant would
@@ -344,7 +354,7 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("deposit-refund-1",
"withdraw-coin-r1",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"ice\",\"value\":\"EUR:5\"}]}",
GNUNET_TIME_UNIT_MINUTES,
"EUR:5",
@@ -353,7 +363,6 @@ run (void *cls,
TALER_TESTING_cmd_refund ("refund-ok",
MHD_HTTP_OK,
"EUR:5",
- "EUR:0.01",
"deposit-refund-1"),
/**
* Spend 4.99 EUR of the refunded 4.99 EUR coin (1ct gone
@@ -362,7 +371,7 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("deposit-refund-2",
"withdraw-coin-r1",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"more\",\"value\":\"EUR:5\"}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:4.99",
@@ -392,15 +401,15 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1",
"recoup-create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_revoke ("revoke-1",
MHD_HTTP_OK,
"recoup-withdraw-coin-1",
- CONFIG_FILE),
+ config_file),
TALER_TESTING_cmd_recoup ("recoup-1",
MHD_HTTP_OK,
"recoup-withdraw-coin-1",
- NULL,
"EUR:5"),
/**
* Re-withdraw from this reserve
@@ -408,17 +417,20 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2",
"recoup-create-reserve-1",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
* These commands should close the reserve because the aggregator
* is given a config file that overrides the reserve expiration
* time (making it now-ish)
- */CMD_TRANSFER_TO_EXCHANGE ("short-lived-reserve",
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("short-lived-reserve",
"EUR:5.01"),
- TALER_TESTING_cmd_exec_wirewatch ("short-lived-aggregation",
- CONFIG_FILE_EXPIRE_RESERVE_NOW),
+ TALER_TESTING_cmd_exec_wirewatch2 ("short-lived-aggregation",
+ config_file_expire_reserve_now,
+ "exchange-account-2"),
TALER_TESTING_cmd_exec_aggregator ("close-reserves",
- CONFIG_FILE_EXPIRE_RESERVE_NOW),
+ config_file_expire_reserve_now),
/**
* Fill reserve with EUR:2.02, as withdraw fee is 1 ct per
* config, then withdraw two coin, partially spend one, and
@@ -437,6 +449,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2a",
"recoup-create-reserve-2",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
* Withdraw a 1 EUR coin, at fee of 1 ct
@@ -444,11 +457,12 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2b",
"recoup-create-reserve-2",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit ("recoup-deposit-partial",
"recoup-withdraw-coin-2a",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"more ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:0.5",
@@ -456,11 +470,10 @@ run (void *cls,
TALER_TESTING_cmd_revoke ("revoke-2",
MHD_HTTP_OK,
"recoup-withdraw-coin-2a",
- CONFIG_FILE),
+ config_file),
TALER_TESTING_cmd_recoup ("recoup-2",
MHD_HTTP_OK,
"recoup-withdraw-coin-2a",
- NULL,
"EUR:0.5"),
TALER_TESTING_cmd_end ()
};
@@ -473,57 +486,68 @@ run (void *cls,
*/
CMD_TRANSFER_TO_EXCHANGE ("massive-reserve",
"EUR:10.10"),
- TALER_TESTING_cmd_check_bank_admin_transfer
- ("check-massive-transfer",
+ TALER_TESTING_cmd_check_bank_admin_transfer (
+ "check-massive-transfer",
"EUR:10.10",
- bc.user42_payto, bc.exchange_payto,
+ cred.user42_payto,
+ cred.exchange_payto,
"massive-reserve"),
CMD_EXEC_WIREWATCH ("massive-wirewatch"),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-1",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-2",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-3",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-4",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-5",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-6",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-7",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-8",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-9",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-10",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
- TALER_TESTING_cmd_deposit
- ("massive-deposit-1",
+ TALER_TESTING_cmd_deposit (
+ "massive-deposit-1",
"massive-withdraw-1",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
@@ -532,7 +556,7 @@ run (void *cls,
("massive-deposit-2",
"massive-withdraw-2",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
@@ -541,7 +565,7 @@ run (void *cls,
("massive-deposit-3",
"massive-withdraw-3",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
@@ -550,7 +574,7 @@ run (void *cls,
("massive-deposit-4",
"massive-withdraw-4",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
@@ -559,7 +583,7 @@ run (void *cls,
("massive-deposit-5",
"massive-withdraw-5",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
@@ -568,7 +592,7 @@ run (void *cls,
("massive-deposit-6",
"massive-withdraw-6",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
@@ -577,7 +601,7 @@ run (void *cls,
("massive-deposit-7",
"massive-withdraw-7",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
@@ -586,7 +610,7 @@ run (void *cls,
("massive-deposit-8",
"massive-withdraw-8",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
@@ -595,24 +619,23 @@ run (void *cls,
("massive-deposit-9",
"massive-withdraw-9",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
MHD_HTTP_OK),
- TALER_TESTING_cmd_deposit
- ("massive-deposit-10",
+ TALER_TESTING_cmd_deposit (
+ "massive-deposit-10",
"massive-withdraw-10",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit_confirmation ("deposit-confirmation",
- is->auditor,
"massive-deposit-10",
- 0,
+ 1,
"EUR:0.99",
MHD_HTTP_OK),
CMD_RUN_AUDITOR ("massive-auditor"),
@@ -621,10 +644,25 @@ run (void *cls,
};
struct TALER_TESTING_Command commands[] = {
+ TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+ cred.cfg,
+ "exchange-account-2"),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_file,
+ "-u", "exchange-account-2",
+ "-ae",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_get_auditor ("get-auditor",
+ cred.cfg,
+ true),
+ TALER_TESTING_cmd_exec_auditor_offline ("auditor-offline",
+ config_file),
CMD_RUN_AUDITOR ("virgin-auditor"),
- TALER_TESTING_cmd_exchanges_with_url ("check-exchange",
- MHD_HTTP_OK,
- "http://localhost:8081/"),
TALER_TESTING_cmd_batch ("massive-deposit-confirms",
massive_deposit_confirms),
TALER_TESTING_cmd_batch ("withdraw",
@@ -645,9 +683,9 @@ run (void *cls,
TALER_TESTING_cmd_end ()
};
- TALER_TESTING_run_with_fakebank (is,
- commands,
- bc.exchange_auth.wire_gateway_url);
+ (void) cls;
+ TALER_TESTING_run (is,
+ commands);
}
@@ -655,47 +693,28 @@ int
main (int argc,
char *const *argv)
{
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test-auditor-api",
- "INFO",
- NULL);
- /* Check fakebank port is available and get configuration data. */
- if (GNUNET_OK !=
- TALER_TESTING_prepare_fakebank (CONFIG_FILE,
- "exchange-account-2",
- &bc))
- return 77;
- TALER_TESTING_cleanup_files (CONFIG_FILE);
- /* @helpers. Run keyup, create tables, ... Note: it
- * fetches the port number from config in order to see
- * if it's available. */
- switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
- GNUNET_YES,
- &ec))
+ (void) argc;
{
- case GNUNET_SYSERR:
- GNUNET_break (0);
- return 1;
- case GNUNET_NO:
- return 77;
- case GNUNET_OK:
- if (GNUNET_OK !=
- /* Set up event loop and reschedule context, plus
- * start/stop the exchange. It calls TALER_TESTING_setup
- * which creates the 'is' object.
- */
- TALER_TESTING_auditor_setup (&run,
- NULL,
- CONFIG_FILE))
- return 1;
- break;
- default:
- GNUNET_break (0);
- return 1;
+ char *cipher;
+
+ cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+ GNUNET_assert (NULL != cipher);
+ GNUNET_asprintf (&config_file,
+ "test_auditor_api-%s.conf",
+ cipher);
+ GNUNET_asprintf (&config_file_expire_reserve_now,
+ "test_auditor_api_expire_reserve_now-%s.conf",
+ cipher);
+ GNUNET_free (cipher);
}
- return 0;
+ return TALER_TESTING_main (argv,
+ "INFO",
+ config_file,
+ "exchange-account-2",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
}
diff --git a/src/testing/test_auditor_api.conf b/src/testing/test_auditor_api.conf
deleted file mode 100644
index 890812c55..000000000
--- a/src/testing/test_auditor_api.conf
+++ /dev/null
@@ -1,200 +0,0 @@
-
-# This file is in the public domain.
-#
-[PATHS]
-# Persistent data storage for the testcase
-TALER_TEST_HOME = test_exchange_api_home/
-
-[taler]
-# Currency supported by the exchange (can only be one)
-CURRENCY = EUR
-CURRENCY_ROUND_UNIT = EUR:0.01
-
-[auditor]
-BASE_URL = "http://localhost:8083/"
-
-# HTTP port the auditor listens to
-PORT = 8083
-
-TINY_AMOUNT = EUR:0.01
-
-[exchange]
-
-# how long is one signkey valid?
-signkey_duration = 4 weeks
-
-# how long are the signatures with the signkey valid?
-legal_duration = 2 years
-
-# how long do we provide to clients denomination and signing keys
-# ahead of time?
-lookahead_provide = 4 weeks 1 day
-
-# HTTP port the exchange listens to
-PORT = 8081
-
-# Master public key used to sign the exchange's various keys
-MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
-
-# How to access our database
-DB = postgres
-
-# Base URL of the exchange. Must be set to a URL where the
-# exchange (or the twister) is actually listening.
-BASE_URL = "http://localhost:8081/"
-
-# Keep it short so the test runs fast.
-LOOKAHEAD_SIGN = 12 h
-
-[exchangedb-postgres]
-CONFIG = "postgres:///talercheck"
-
-[auditordb-postgres]
-CONFIG = "postgres:///talercheck"
-
-# Sections starting with "exchange-account-" configure the bank accounts
-# of the exchange. The "URL" specifies the account in
-# payto://-format, while the WIRE_JSON specifies the
-# (possibly offline) signed version to be returned in /wire.
-# WIRE_JSON is optional, as not all accounts must be
-# advertised in /wire.
-[exchange-account-1]
-# What is the URL of our account?
-PAYTO_URI = "payto://x-taler-bank/localhost/42"
-WIRE_GATEWAY_URL = "http://localhost:8082/42/"
-# This is the response we give out for the /wire request. It provides
-# wallets with the bank information for transfers to the exchange.
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json
-
-[bank]
-HTTP_PORT = 8082
-
-# ENABLE_CREDIT = YES
-
-[exchange-account-2]
-# What is the bank account (with the "Taler Bank" demo system)?
-WIRE_GATEWAY_URL = "http://localhost:8082/2/"
-PAYTO_URI = "payto://x-taler-bank/localhost/2"
-
-# This is the response we give out for the /wire request. It provides
-# wallets with the bank information for transfers to the exchange.
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-2.json
-
-# Authentication information for basic authentication
-WIRE_GATEWAY_AUTH_METHOD = "basic"
-USERNAME = user
-PASSWORD = pass
-
-ENABLE_DEBIT = YES
-
-ENABLE_CREDIT = YES
-
-
-# Sections starting with "fee-" configure the wire fee for the
-# respective wire method.
-[fees-iban]
-# Fees for the foreseeable future...
-# If you see this after 2017, update to match the next 10 years...
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
-WIRE-FEE-2027 = EUR:0.01
-
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-CLOSING-FEE-2027 = EUR:0.01
-
-[fees-x-taler-bank]
-# Fees for the foreseeable future...
-# If you see this after 2017, update to match the next 10 years...
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
-WIRE-FEE-2027 = EUR:0.01
-
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-CLOSING-FEE-2027 = EUR:0.01
-
-# Sections starting with "coin_" specify which denominations
-# the exchange should support (and their respective fee structure)
-[coin_eur_ct_1]
-value = EUR:0.01
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.00
-fee_deposit = EUR:0.00
-fee_refresh = EUR:0.01
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_ct_10]
-value = EUR:0.10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_1]
-value = EUR:1
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_5]
-value = EUR:5
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_10]
-value = EUR:10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
diff --git a/src/testing/test_auditor_api_expire_reserve_now.conf b/src/testing/test_auditor_api_expire_reserve_now-cs.conf
index c2bf8f479..7277a0dff 100644
--- a/src/testing/test_auditor_api_expire_reserve_now.conf
+++ b/src/testing/test_auditor_api_expire_reserve_now-cs.conf
@@ -1,4 +1,4 @@
-@INLINE@ test_auditor_api.conf
+@INLINE@ test_auditor_api-cs.conf
[exchangedb]
IDLE_RESERVE_EXPIRATION_TIME = 0 s
diff --git a/src/testing/test_exchange_api_expire_reserve_now.conf b/src/testing/test_auditor_api_expire_reserve_now-rsa.conf
index 05bca956b..788cc36f8 100644
--- a/src/testing/test_exchange_api_expire_reserve_now.conf
+++ b/src/testing/test_auditor_api_expire_reserve_now-rsa.conf
@@ -1,4 +1,4 @@
-@INLINE@ test_exchange_api.conf
+@INLINE@ test_auditor_api-rsa.conf
[exchangedb]
IDLE_RESERVE_EXPIRATION_TIME = 0 s
diff --git a/src/testing/test_auditor_api_version.c b/src/testing/test_auditor_api_version.c
index bf1094e38..dcd542ad8 100644
--- a/src/testing/test_auditor_api_version.c
+++ b/src/testing/test_auditor_api_version.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 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
@@ -39,9 +39,9 @@
* Configuration file we use. One (big) configuration is used
* for the various components for this test.
*/
-#define CONFIG_FILE "test_auditor_api.conf"
+#define CONFIG_FILE "test_auditor_api-rsa.conf"
-static struct TALER_AUDITOR_Handle *ah;
+static struct TALER_AUDITOR_GetConfigHandle *ah;
static struct GNUNET_CURL_Context *ctx;
@@ -51,6 +51,7 @@ static int global_ret;
static struct GNUNET_SCHEDULER_Task *tt;
+
static void
do_shutdown (void *cls)
{
@@ -61,7 +62,11 @@ do_shutdown (void *cls)
GNUNET_SCHEDULER_cancel (tt);
tt = NULL;
}
- TALER_AUDITOR_disconnect (ah);
+ if (NULL != ah)
+ {
+ TALER_AUDITOR_get_config_cancel (ah);
+ ah = NULL;
+ }
GNUNET_CURL_fini (ctx);
GNUNET_CURL_gnunet_rc_destroy (rc);
}
@@ -81,16 +86,16 @@ do_timeout (void *cls)
* Function called with information about the auditor.
*
* @param cls closure
- * @param vi basic information about the auditor
- * @param compat protocol compatibility information
+ * @param vr response details
*/
static void
version_cb (void *cls,
- const struct TALER_AUDITOR_VersionInformation *vi,
- enum TALER_AUDITOR_VersionCompatibility compat)
+ const struct TALER_AUDITOR_ConfigResponse *vr)
{
- if ( (NULL != vi) &&
- (TALER_AUDITOR_VC_MATCH == compat) )
+ (void) cls;
+ ah = NULL;
+ if ( (MHD_HTTP_OK == vr->hr.http_status) &&
+ (TALER_AUDITOR_VC_MATCH == vr->details.ok.compat) )
global_ret = 0;
else
global_ret = 2;
@@ -113,10 +118,10 @@ run (void *cls)
ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
&rc);
rc = GNUNET_CURL_gnunet_rc_create (ctx);
- ah = TALER_AUDITOR_connect (ctx,
- auditor_url,
- &version_cb,
- NULL);
+ ah = TALER_AUDITOR_get_config (ctx,
+ auditor_url,
+ &version_cb,
+ NULL);
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
tt = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
@@ -131,27 +136,29 @@ main (int argc,
{
struct GNUNET_OS_Process *proc;
+ (void) argc;
+ (void) argv;
/* These environment variables get in the way... */
unsetenv ("XDG_DATA_HOME");
unsetenv ("XDG_CONFIG_HOME");
GNUNET_log_setup ("test-auditor-api-version",
"INFO",
NULL);
- proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-auditor-httpd",
"taler-auditor-httpd",
"-c", CONFIG_FILE,
+ "-L", "INFO",
NULL);
if (NULL == proc)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to run `taler-auditor-httpd`,"
- " is your PATH correct?\n");
+ "Failed to run `taler-auditor-httpd`, is your PATH correct?\n");
return 77;
}
- if (0 != TALER_TESTING_wait_auditor_ready ("http://localhost:8083/"))
+ global_ret = TALER_TESTING_wait_httpd_ready ("http://localhost:8083/");
+ if (0 != global_ret)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to launch `taler-auditor-httpd`\n");
@@ -161,7 +168,8 @@ main (int argc,
GNUNET_SCHEDULER_run (&run,
NULL);
}
- GNUNET_OS_process_kill (proc, SIGTERM);
+ GNUNET_OS_process_kill (proc,
+ SIGTERM);
GNUNET_OS_process_wait (proc);
GNUNET_OS_process_destroy (proc);
return global_ret;
diff --git a/src/testing/test_bank_api.c b/src/testing/test_bank_api.c
index b0b61a166..8cbc86bbd 100644
--- a/src/testing/test_bank_api.c
+++ b/src/testing/test_bank_api.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2016-2020 Taler Systems SA
+ Copyright (C) 2016-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
@@ -34,23 +34,26 @@
#include "taler_testing_lib.h"
#define CONFIG_FILE_FAKEBANK "test_bank_api_fakebank.conf"
-#define CONFIG_FILE_PYBANK "test_bank_api_pybank.conf"
+
+#define CONFIG_FILE_NEXUS "test_bank_api_nexus.conf"
+
/**
- * Bank configuration data.
+ * Configuration file. It changes based on
+ * whether Nexus or Fakebank are used.
*/
-static struct TALER_TESTING_BankConfiguration bc;
+static const char *cfgfile;
/**
- * Handle to the Py-bank daemon.
+ * Our credentials.
*/
-static struct GNUNET_OS_Process *bankd;
+static struct TALER_TESTING_Credentials cred;
/**
- * Flag indicating whether the test is running against the
- * Fakebank. Set up at runtime.
+ * Which bank is the test running against?
+ * Set up at runtime.
*/
-static int with_fakebank;
+static enum TALER_TESTING_BankSystem bs;
/**
@@ -64,40 +67,76 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct TALER_WireTransferIdentifierRawP wtid;
+ const char *ssoptions;
- memset (&wtid, 42, sizeof (wtid));
+ (void) cls;
+ switch (bs)
+ {
+ case TALER_TESTING_BS_FAKEBANK:
+ ssoptions = "-f";
+ break;
+ case TALER_TESTING_BS_IBAN:
+ ssoptions = "-b";
+ break;
+ default:
+ ssoptions = NULL;
+ break;
+ }
+ memset (&wtid,
+ 42,
+ sizeof (wtid));
{
struct TALER_TESTING_Command commands[] = {
+ TALER_TESTING_cmd_system_start ("start-taler",
+ cfgfile,
+ ssoptions,
+ NULL),
TALER_TESTING_cmd_bank_credits ("history-0",
- &bc.exchange_auth,
+ &cred.ba,
NULL,
1),
TALER_TESTING_cmd_admin_add_incoming ("credit-1",
- "KUDOS:5.01",
- &bc.exchange_auth,
- bc.user42_payto),
+ "EUR:5.01",
+ &cred.ba_admin,
+ cred.user42_payto),
+ /**
+ * This CMD doesn't care about the HTTP response code; that's
+ * because Fakebank and euFin behaves differently when a reserve
+ * pub is duplicate. Fakebank responds with 409, whereas euFin
+ * with 200 but it bounces the payment back to the customer.
+ */
+ TALER_TESTING_cmd_admin_add_incoming_with_ref ("credit-1-fail",
+ "EUR:2.01",
+ &cred.ba_admin,
+ cred.user42_payto,
+ "credit-1",
+ -1),
+ /**
+ * Check that the incoming payment with a duplicate
+ * reserve public key didn't make it to the exchange.
+ */
TALER_TESTING_cmd_bank_credits ("history-1c",
- &bc.exchange_auth,
+ &cred.ba,
NULL,
5),
TALER_TESTING_cmd_bank_debits ("history-1d",
- &bc.exchange_auth,
+ &cred.ba,
NULL,
5),
TALER_TESTING_cmd_admin_add_incoming ("credit-2",
- "KUDOS:3.21",
- &bc.exchange_auth,
- bc.user42_payto),
+ "EUR:3.21",
+ &cred.ba_admin,
+ cred.user42_payto),
TALER_TESTING_cmd_transfer ("debit-1",
- "KUDOS:3.22",
- &bc.exchange_auth,
- bc.exchange_payto,
- bc.user42_payto,
+ "EUR:3.22",
+ &cred.ba,
+ cred.exchange_payto,
+ cred.user42_payto,
&wtid,
"http://exchange.example.com/"),
TALER_TESTING_cmd_bank_debits ("history-2b",
- &bc.exchange_auth,
+ &cred.ba,
NULL,
5),
TALER_TESTING_cmd_end ()
@@ -105,104 +144,53 @@ run (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Bank serves at `%s'\n",
- bc.exchange_auth.wire_gateway_url);
- if (GNUNET_YES == with_fakebank)
- TALER_TESTING_run_with_fakebank (is,
- commands,
- bc.exchange_auth.wire_gateway_url);
- else
- TALER_TESTING_run (is,
- commands);
+ cred.ba.wire_gateway_url);
+ TALER_TESTING_run (is,
+ commands);
}
}
-/**
- * Runs #TALER_TESTING_setup() using the configuration.
- *
- * @param cls unused
- * @param cfg configuration to use
- * @return status code
- */
-static int
-setup_with_cfg (void *cls,
- const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- (void) cls;
- return TALER_TESTING_setup (&run,
- NULL,
- cfg,
- NULL,
- GNUNET_NO);
-}
-
-
int
main (int argc,
char *const *argv)
{
- const char *cfgfilename;
- int rv;
-
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test-bank-api",
- "DEBUG",
- NULL);
- with_fakebank = TALER_TESTING_has_in_name (argv[0],
- "_with_fakebank");
- if (GNUNET_YES == with_fakebank)
+ (void) argc;
+ if (TALER_TESTING_has_in_name (argv[0],
+ "_with_fakebank"))
{
- TALER_LOG_DEBUG ("Running against the Fakebank.\n");
- cfgfilename = CONFIG_FILE_FAKEBANK;
- if (GNUNET_OK !=
- TALER_TESTING_prepare_fakebank (CONFIG_FILE_FAKEBANK,
- "exchange-account-2",
- &bc))
- {
- GNUNET_break (0);
- return 77;
- }
+ bs = TALER_TESTING_BS_FAKEBANK;
+ cfgfile = CONFIG_FILE_FAKEBANK;
}
- else
+ else if (TALER_TESTING_has_in_name (argv[0],
+ "_with_nexus"))
{
- TALER_LOG_DEBUG ("Running against the Pybank.\n");
- cfgfilename = CONFIG_FILE_PYBANK;
- if (GNUNET_OK !=
- TALER_TESTING_prepare_bank (CONFIG_FILE_PYBANK,
- GNUNET_YES,
- "exchange-account-2",
- &bc))
+ bs = TALER_TESTING_BS_IBAN;
+ cfgfile = CONFIG_FILE_NEXUS;
+ if (GNUNET_SYSERR ==
+ GNUNET_OS_check_helper_binary ("libeufin-bank",
+ false,
+ NULL))
{
- GNUNET_break (0);
- return 77;
- }
-
- if (NULL == (bankd = TALER_TESTING_run_bank (CONFIG_FILE_PYBANK,
- bc.exchange_auth.
- wire_gateway_url)))
- {
- GNUNET_break (0);
+ fprintf (stderr,
+ "libeufin-bank not found. Skipping test.\n");
return 77;
}
}
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_parse_and_run (cfgfilename,
- &setup_with_cfg,
- NULL))
- rv = 1;
else
- rv = 0;
- if (GNUNET_NO == with_fakebank)
{
-
- GNUNET_OS_process_kill (bankd,
- SIGKILL);
- GNUNET_OS_process_wait (bankd);
- GNUNET_OS_process_destroy (bankd);
+ /* no bank service was specified. */
+ GNUNET_break (0);
+ return 77;
}
- return rv;
+ return TALER_TESTING_main (argv,
+ "INFO",
+ cfgfile,
+ "exchange-account-2",
+ bs,
+ &cred,
+ &run,
+ NULL);
}
diff --git a/src/testing/test_bank_api.conf b/src/testing/test_bank_api.conf
new file mode 100644
index 000000000..c262ae197
--- /dev/null
+++ b/src/testing/test_bank_api.conf
@@ -0,0 +1,23 @@
+# This file is in the public domain
+
+[PATHS]
+TALER_TEST_HOME = test_exchange_api_home/
+
+[taler]
+currency = EUR
+
+[bank]
+SERVE = http
+HTTP_PORT = 8082
+
+[libeufin-bank]
+CURRENCY = EUR
+WIRE_TYPE = iban
+IBAN_PAYTO_BIC = SANDBOXX=
+DEFAULT_CUSTOMER_DEBT_LIMIT = EUR:200
+DEFAULT_ADMIN_DEBT_LIMIT = EUR:2000
+REGISTRATION_BONUS_ENABLED = yes
+REGISTRATION_BONUS = EUR:100
+SUGGESTED_WITHDRAWAL_EXCHANGE = http://localhost:8081/
+SERVE = tcp
+PORT = 8082
diff --git a/src/testing/test_bank_api_fakebank.conf b/src/testing/test_bank_api_fakebank.conf
index 5d5053599..62fa4cd4c 100644
--- a/src/testing/test_bank_api_fakebank.conf
+++ b/src/testing/test_bank_api_fakebank.conf
@@ -1,16 +1,21 @@
# This file is in the public domain.
+@INLINE@ test_bank_api.conf
-[taler]
-currency = KUDOS
+[exchange-account-1]
+PAYTO_URI = "payto://x-taler-bank/localhost:8082/1?receiver-name=1"
[exchange-account-2]
-PAYTO_URI = payto://x-taler-bank/localhost/2
-WIRE_GATEWAY_URL = "http://localhost:8081/2/"
+PAYTO_URI = "payto://x-taler-bank/localhost:8082/2?receiver-name=2"
+
+[exchange-accountcredentials-2]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
WIRE_GATEWAY_AUTH_METHOD = basic
USERNAME = Exchange
PASSWORD = x
-[bank]
-SERVE = http
-HTTP_PORT = 8081
-DATABASE = postgres:///talercheck
+[admin-accountcredentials-2]
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = basic
+# For now, fakebank still checks against the Exchange account...
+USERNAME = Exchange
+PASSWORD = x
diff --git a/src/testing/test_bank_api_fakebank_twisted.conf b/src/testing/test_bank_api_fakebank_twisted.conf
index 72ab867b6..bc66c0d88 100644
--- a/src/testing/test_bank_api_fakebank_twisted.conf
+++ b/src/testing/test_bank_api_fakebank_twisted.conf
@@ -1,12 +1,15 @@
+# This file is in the public domain.
+
+@INLINE@ test_bank_api_fakebank.conf
[twister]
+
# HTTP listen port for twister
HTTP_PORT = 8888
SERVE = tcp
-
# HTTP Destination for twister. The test-Webserver needs
# to listen on the port used here. Note: no trailing '/'!
-DESTINATION_BASE_URL = "http://localhost:8081"
+DESTINATION_BASE_URL = "http://localhost:8082"
# Control port for TCP
# PORT = 8889
@@ -18,17 +21,3 @@ ACCEPT_FROM6 = ::1;
UNIXPATH = /tmp/taler-service-twister.sock
UNIX_MATCH_UID = NO
UNIX_MATCH_GID = YES
-
-[taler]
-currency = KUDOS
-
-[bank]
-serve = http
-http_port = 8081
-database = postgres:///talercheck
-
-[exchange-account-1]
-PAYTO_URI = payto://x-taler-bank/localhost:8081/1
-
-[exchange-account-2]
-PAYTO_URI = payto://x-taler-bank/localhost:8081/2
diff --git a/src/testing/test_bank_api_nexus.conf b/src/testing/test_bank_api_nexus.conf
new file mode 100644
index 000000000..605c7b00e
--- /dev/null
+++ b/src/testing/test_bank_api_nexus.conf
@@ -0,0 +1,35 @@
+# This file is in the public domain.
+@INLINE@ test_bank_api.conf
+
+[exchange-account-2]
+PAYTO_URI = payto://iban/BIC/ES9121000418450200051332?receiver-name=Exchange
+
+[exchange-accountcredentials-2]
+WIRE_GATEWAY_URL = http://localhost:8082/accounts/exchange/taler-wire-gateway/
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = exchange
+PASSWORD = x
+
+[admin-accountcredentials-2]
+WIRE_GATEWAY_URL = http://localhost:8082/accounts/exchange/taler-wire-gateway/
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = admin
+# 'secret' is from taler-unified-setup.sh
+PASSWORD = secret
+
+[libeufin-bankdb-postgres]
+CONFIG="postgresql:///talercheck"
+
+# libeufin doesn't search our config.d/currencies.conf
+# as it has a different resource path. Thus replicated
+# here.
+[currency-euro]
+ENABLED = YES
+name = "Euro"
+code = "EUR"
+decimal_separator = ","
+fractional_input_digits = 2
+fractional_normal_digits = 2
+fractional_trailing_zero_digits = 2
+is_currency_name_leading = NO
+alt_unit_names = {"0":"€"}
diff --git a/src/testing/test_bank_api_pybank.conf b/src/testing/test_bank_api_pybank.conf
deleted file mode 100644
index 9070e31e5..000000000
--- a/src/testing/test_bank_api_pybank.conf
+++ /dev/null
@@ -1,17 +0,0 @@
-# This file is in the public domain.
-
-[taler]
-currency = KUDOS
-
-[exchange-account-2]
-PAYTO_URI = payto://x-taler-bank/localhost/Exchange
-METHOD = x-taler-bank
-WIRE_GATEWAY_URL = "http://localhost:8081/taler-wire-gateway/Exchange/"
-WIRE_GATEWAY_AUTH_METHOD = basic
-USERNAME = Exchange
-PASSWORD = x
-
-[bank]
-SERVE = http
-HTTP_PORT = 8081
-DATABASE = postgres:///talercheck
diff --git a/src/testing/test_bank_api_twisted.c b/src/testing/test_bank_api_twisted.c
index c2382db96..038ec8a1f 100644
--- a/src/testing/test_bank_api_twisted.c
+++ b/src/testing/test_bank_api_twisted.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 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
@@ -17,7 +17,7 @@
<http://www.gnu.org/licenses/>
*/
/**
- * @file testing/test_bank_api_with_fakebank_twisted.c
+ * @file testing/test_bank_api_twisted.c
* @author Marcello Stanisci
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
* @author Christian Grothoff
@@ -32,7 +32,7 @@
#include "taler_bank_service.h"
#include "taler_fakebank_lib.h"
#include "taler_testing_lib.h"
-#include <taler/taler_twister_testing_lib.h>
+#include "taler_twister_testing_lib.h"
#include <taler/taler_twister_service.h>
/**
@@ -42,19 +42,20 @@
#define CONFIG_FILE_FAKEBANK "test_bank_api_fakebank_twisted.conf"
/**
- * Separate config file for running with the pybank.
+ * Configuration file we use.
*/
-#define CONFIG_FILE_PYBANK "test_bank_api_pybank_twisted.conf"
+static const char *cfgfile;
/**
- * True when the test runs against Fakebank.
+ * Our credentials.
*/
-static int with_fakebank;
+static struct TALER_TESTING_Credentials cred;
/**
- * Bank configuration data.
+ * Which bank is the test running against?
+ * Set up at runtime.
*/
-static struct TALER_TESTING_BankConfiguration bc;
+static enum TALER_TESTING_BankSystem bs;
/**
* (real) Twister URL. Used at startup time to check if it runs.
@@ -66,11 +67,6 @@ static char *twister_url;
*/
static struct GNUNET_OS_Process *twisterd;
-/**
- * Python bank process handle.
- */
-static struct GNUNET_OS_Process *bankd;
-
/**
* Main function that will tell
@@ -82,40 +78,69 @@ static void
run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
+ struct TALER_WireTransferIdentifierRawP wtid;
+ /* Authentication data to route our commands through twister. */
+ struct TALER_BANK_AuthenticationData exchange_auth_twisted;
+ const char *systype = NULL;
+
+ (void) cls;
+ memset (&wtid,
+ 0x5a,
+ sizeof (wtid));
+ GNUNET_memcpy (&exchange_auth_twisted,
+ &cred.ba,
+ sizeof (struct TALER_BANK_AuthenticationData));
+ switch (bs)
+ {
+ case TALER_TESTING_BS_FAKEBANK:
+ exchange_auth_twisted.wire_gateway_url
+ = "http://localhost:8888/accounts/2/taler-wire-gateway/";
+ systype = "-f";
+ break;
+ case TALER_TESTING_BS_IBAN:
+ exchange_auth_twisted.wire_gateway_url
+ = "http://localhost:8888/accounts/Exchange/taler-wire-gateway/";
+ systype = "-b";
+ break;
+ }
+ GNUNET_assert (NULL != systype);
+
+ {
+ struct TALER_TESTING_Command commands[] = {
+ TALER_TESTING_cmd_system_start ("start-taler",
+ cfgfile,
+ systype,
+ NULL),
+ /* Test retrying transfer after failure. */
+ TALER_TESTING_cmd_malform_response ("malform-transfer",
+ cfgfile),
+ TALER_TESTING_cmd_transfer_retry (
+ TALER_TESTING_cmd_transfer ("debit-1",
+ "EUR:3.22",
+ &exchange_auth_twisted,
+ cred.exchange_payto,
+ cred.user42_payto,
+ &wtid,
+ "http://exchange.example.com/")),
+ TALER_TESTING_cmd_end ()
+ };
- struct TALER_TESTING_Command commands[] = {
- /**
- * Can't use the "wait service" CMD here because the
- * fakebank runs inside the same process of the test.
- */
- TALER_TESTING_cmd_wait_service ("wait-service",
- twister_url),
- TALER_TESTING_cmd_bank_credits ("history-0",
- &bc.exchange_auth,
- NULL,
- 5),
- TALER_TESTING_cmd_end ()
- };
-
- if (GNUNET_YES == with_fakebank)
- TALER_TESTING_run_with_fakebank (is,
- commands,
- bc.exchange_auth.wire_gateway_url);
- else
TALER_TESTING_run (is,
commands);
+ }
}
/**
* Kill, wait, and destroy convenience function.
*
- * @param process process to purge.
+ * @param[in] process process to purge.
*/
static void
purge_process (struct GNUNET_OS_Process *process)
{
- GNUNET_OS_process_kill (process, SIGINT);
+ GNUNET_OS_process_kill (process,
+ SIGINT);
GNUNET_OS_process_wait (process);
GNUNET_OS_process_destroy (process);
}
@@ -125,97 +150,47 @@ int
main (int argc,
char *const *argv)
{
- unsigned int ret;
- const char *cfgfilename;
-
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test-bank-api-with-(fake)bank-twisted",
- "DEBUG",
- NULL);
+ int ret;
- with_fakebank = TALER_TESTING_has_in_name (argv[0],
- "_with_fakebank");
-
- if (with_fakebank)
- cfgfilename = CONFIG_FILE_FAKEBANK;
- else
- cfgfilename = CONFIG_FILE_PYBANK;
-
- if (NULL == (twister_url = TALER_TWISTER_prepare_twister
- (cfgfilename)))
+ (void) argc;
+ if (TALER_TESTING_has_in_name (argv[0],
+ "_with_fakebank"))
{
- GNUNET_break (0);
- return 77;
+ bs = TALER_TESTING_BS_FAKEBANK;
+ cfgfile = CONFIG_FILE_FAKEBANK;
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "twister_url is %s\n",
- twister_url);
- if (NULL == (twisterd = TALER_TWISTER_run_twister (cfgfilename)))
+ else if (TALER_TESTING_has_in_name (argv[0],
+ "_with_nexus"))
{
- GNUNET_break (0);
- GNUNET_free (twister_url);
- return 77;
- }
-
- if (GNUNET_YES == with_fakebank)
- {
- TALER_LOG_DEBUG ("Running against the Fakebank.\n");
- if (GNUNET_OK !=
- TALER_TESTING_prepare_fakebank (cfgfilename,
- "exchange-account-2",
- &bc))
- {
- GNUNET_break (0);
- GNUNET_free (twister_url);
- return 77;
- }
+ GNUNET_assert (0); /* FIXME: test with nexus not yet implemented */
+ bs = TALER_TESTING_BS_IBAN;
+ /* cfgfile = CONFIG_FILE_NEXUS; */
}
else
{
- TALER_LOG_DEBUG ("Running against the Pybank.\n");
- if (GNUNET_OK !=
- TALER_TESTING_prepare_bank (cfgfilename,
- GNUNET_YES,
- "exchange-account-2",
- &bc))
- {
- GNUNET_break (0);
- GNUNET_free (twister_url);
- return 77;
- }
-
- if (NULL == (bankd = TALER_TESTING_run_bank (cfgfilename,
- bc.exchange_auth.
- wire_gateway_url)))
- {
- GNUNET_break (0);
- GNUNET_free (twister_url);
- return 77;
- }
+ /* no bank service was specified. */
+ GNUNET_break (0);
+ return 77;
}
- ret = TALER_TESTING_setup (&run,
- NULL,
- cfgfilename,
- NULL,
- GNUNET_NO);
+ /* FIXME: introduce commands for twister! */
+ twister_url = TALER_TWISTER_prepare_twister (cfgfile);
+ if (NULL == twister_url)
+ return 77;
+ twisterd = TALER_TWISTER_run_twister (cfgfile);
+ if (NULL == twisterd)
+ return 77;
+ ret = TALER_TESTING_main (argv,
+ "INFO",
+ cfgfile,
+ "exchange-account-2",
+ bs,
+ &cred,
+ &run,
+ NULL);
purge_process (twisterd);
-
- if (GNUNET_NO == with_fakebank)
- {
- GNUNET_OS_process_kill (bankd,
- SIGKILL);
- GNUNET_OS_process_wait (bankd);
- GNUNET_OS_process_destroy (bankd);
- }
-
GNUNET_free (twister_url);
- if (GNUNET_OK == ret)
- return 0;
-
- return 1;
+ return ret;
}
diff --git a/src/testing/test_exchange_api-cs.conf b/src/testing/test_exchange_api-cs.conf
new file mode 100644
index 000000000..b80696fb2
--- /dev/null
+++ b/src/testing/test_exchange_api-cs.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+#
+@INLINE@ coins-cs.conf
+@INLINE@ test_exchange_api.conf
diff --git a/src/testing/test_exchange_api-rsa.conf b/src/testing/test_exchange_api-rsa.conf
new file mode 100644
index 000000000..351c876d9
--- /dev/null
+++ b/src/testing/test_exchange_api-rsa.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+#
+@INLINE@ coins-rsa.conf
+@INLINE@ test_exchange_api.conf \ No newline at end of file
diff --git a/src/testing/test_bank_api_pybank_twisted.conf b/src/testing/test_exchange_api-twisted.conf
index 9fe0fa1cb..536d36ee4 100644
--- a/src/testing/test_bank_api_pybank_twisted.conf
+++ b/src/testing/test_exchange_api-twisted.conf
@@ -1,3 +1,13 @@
+# This file is in the public domain.
+
+[exchange]
+# Base URL of the exchange ('S PROXY). This URL is where the
+# twister listens at, so that it will be able to get all the
+# connection addressed to the exchange. In fact, the presence
+# of the twister is 100% transparent to the test case, as it
+# only seeks the exchange/BASE_URL URL to connect to the exchange.
+BASE_URL = "http://localhost:8888/"
+
[twister]
# HTTP listen port for twister
HTTP_PORT = 8888
@@ -17,29 +27,3 @@ ACCEPT_FROM6 = ::1;
UNIXPATH = /tmp/taler-service-twister.sock
UNIX_MATCH_UID = NO
UNIX_MATCH_GID = YES
-
-
-[taler]
-currency = KUDOS
-
-
-[bank]
-serve = http
-http_port = 8081
-database = postgres:///talercheck
-
-
-[exchange-account-1]
-PAYTO_URI = payto://x-taler-bank/localhost/1
-
-
-[exchange-account-2]
-PAYTO_URI = payto://x-taler-bank/localhost/Exchange
-WIRE_GATEWAY_URL = "http://localhost:8888/taler-wire-gateway/Exchange/"
-WIRE_GATEWAY_AUTH_METHOD = basic
-USERNAME = Exchange
-PASSWORD = x
-
-
-[bank]
-HTTP_PORT = 8081
diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c
index 0c3f48379..caeec1e76 100644
--- a/src/testing/test_exchange_api.c
+++ b/src/testing/test_exchange_api.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014--2020 Taler Systems SA
+ Copyright (C) 2014--2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -29,31 +29,38 @@
#include "taler_exchange_service.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_testing_lib.h>
#include <microhttpd.h>
#include "taler_bank_service.h"
#include "taler_fakebank_lib.h"
#include "taler_testing_lib.h"
+#include "taler_extensions.h"
/**
* Configuration file we use. One (big) configuration is used
* for the various components for this test.
*/
-#define CONFIG_FILE "test_exchange_api.conf"
-
-#define CONFIG_FILE_EXPIRE_RESERVE_NOW \
- "test_exchange_api_expire_reserve_now.conf"
-
+static char *config_file;
/**
- * Exchange configuration data.
+ * Special configuration file to use when we want reserves
+ * to expire 'immediately'.
*/
-static struct TALER_TESTING_ExchangeConfiguration ec;
+static char *config_file_expire_reserve_now;
/**
- * Bank configuration data.
+ * Our credentials.
*/
-static struct TALER_TESTING_BankConfiguration bc;
+static struct TALER_TESTING_Credentials cred;
+/**
+ * Some tests behave differently when using CS as we cannot
+ * reuse the coin private key for different denominations
+ * due to the derivation of it with the /csr values. Hence
+ * some tests behave differently in CS mode, hence this
+ * flag.
+ */
+static bool uses_cs;
/**
* Execute the taler-exchange-wirewatch command with
@@ -62,7 +69,7 @@ static struct TALER_TESTING_BankConfiguration bc;
* @param label label to use for the command.
*/
#define CMD_EXEC_WIREWATCH(label) \
- TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE)
+ TALER_TESTING_cmd_exec_wirewatch2 (label, config_file, "exchange-account-2")
/**
* Execute the taler-exchange-aggregator, closer and transfer commands with
@@ -71,8 +78,9 @@ static struct TALER_TESTING_BankConfiguration bc;
* @param label label to use for the command.
*/
#define CMD_EXEC_AGGREGATOR(label) \
- TALER_TESTING_cmd_exec_aggregator (label "-aggregator", CONFIG_FILE), \
- TALER_TESTING_cmd_exec_transfer (label "-transfer", CONFIG_FILE)
+ TALER_TESTING_cmd_sleep ("sleep-before-aggregator", 2), \
+ TALER_TESTING_cmd_exec_aggregator (label "-aggregator", config_file), \
+ TALER_TESTING_cmd_exec_transfer (label "-transfer", config_file)
/**
@@ -84,8 +92,8 @@ static struct TALER_TESTING_BankConfiguration bc;
*/
#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
TALER_TESTING_cmd_admin_add_incoming (label, amount, \
- &bc.exchange_auth, \
- bc.user42_payto)
+ &cred.ba, \
+ cred.user42_payto)
/**
* Main function that will tell the interpreter what commands to
@@ -99,21 +107,6 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
/**
- * Checks made against /wire response.
- */
- struct TALER_TESTING_Command wire[] = {
- /**
- * Check if 'x-taler-bank' wire method is offered
- * by the exchange.
- */
- TALER_TESTING_cmd_wire ("wire-taler-bank-1",
- "x-taler-bank",
- NULL,
- MHD_HTTP_OK),
- TALER_TESTING_cmd_end ()
- };
-
- /**
* Test withdrawal plus spending.
*/
struct TALER_TESTING_Command withdraw[] = {
@@ -121,45 +114,63 @@ run (void *cls,
* Move money to the exchange's bank account.
*/
CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1",
- "EUR:4.01"),
+ "EUR:6.02"),
+ TALER_TESTING_cmd_reserve_poll ("poll-reserve-1",
+ "create-reserve-1",
+ "EUR:6.02",
+ GNUNET_TIME_UNIT_MINUTES,
+ MHD_HTTP_OK),
TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-1",
- "EUR:4.01",
- bc.user42_payto,
- bc.exchange_payto,
+ "EUR:6.02",
+ cred.user42_payto,
+ cred.exchange_payto,
"create-reserve-1"),
/**
* Make a reserve exist, according to the previous
* transfer.
*/
CMD_EXEC_WIREWATCH ("wirewatch-1"),
- /**
- * Do another transfer to the same reserve
- */
- TALER_TESTING_cmd_admin_add_incoming_with_ref ("create-reserve-1.2",
- "EUR:1",
- &bc.exchange_auth,
- bc.user42_payto,
- "create-reserve-1"),
- TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-1.2",
- "EUR:1",
- bc.user42_payto,
- bc.exchange_payto,
- "create-reserve-1.2"),
- CMD_EXEC_WIREWATCH ("wirewatch-1.2"),
+ TALER_TESTING_cmd_reserve_poll_finish ("finish-poll-reserve-1",
+ GNUNET_TIME_UNIT_SECONDS,
+ "poll-reserve-1"),
/**
* Withdraw EUR:5.
*/
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
"create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
+ * Withdraw EUR:1 using the SAME private coin key as for the previous coin
+ * (in violation of the specification, to be detected on spending!).
+ * However, note that this does NOT work with 'CS', as for a different
+ * denomination we get different R0/R1 values from the exchange, and
+ * thus will generate a different coin private key as R0/R1 are hashed
+ * into the coin priv. So here, we fail to 'reuse' the key due to the
+ * cryptographic construction!
+ */
+ TALER_TESTING_cmd_withdraw_amount_reuse_key ("withdraw-coin-1x",
+ "create-reserve-1",
+ "EUR:1",
+ 0, /* age restriction off */
+ "withdraw-coin-1",
+ MHD_HTTP_OK),
+ /**
* Check the reserve is depleted.
*/
TALER_TESTING_cmd_status ("status-1",
"create-reserve-1",
"EUR:0",
MHD_HTTP_OK),
+ /*
+ * Try to overdraw.
+ */
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+ "create-reserve-1",
+ "EUR:5",
+ 0, /* age restriction off */
+ MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_end ()
};
@@ -170,28 +181,43 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("deposit-simple",
"withdraw-coin-1",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:5",
MHD_HTTP_OK),
- TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay",
+ TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay-1",
"deposit-simple",
MHD_HTTP_OK),
- /*
- * Try to overdraw.
- */
- TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
- "create-reserve-1",
- "EUR:5",
- MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_sleep ("sleep-before-deposit-replay",
+ 1),
+ TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay-2",
+ "deposit-simple",
+ MHD_HTTP_OK),
+ /* This creates a conflict, as we have the same coin public key (reuse!),
+ but different denomination public keys (which is not allowed).
+ However, note that this does NOT work with 'CS', as for a different
+ denomination we get different R0/R1 values from the exchange, and
+ thus will generate a different coin private key as R0/R1 are hashed
+ into the coin priv. So here, we fail to 'reuse' the key due to the
+ cryptographic construction! */
+ TALER_TESTING_cmd_deposit ("deposit-reused-coin-key-failure",
+ "withdraw-coin-1x",
+ 0,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:1",
+ uses_cs
+ ? MHD_HTTP_OK
+ : MHD_HTTP_CONFLICT),
/**
* Try to double spend using different wire details.
*/
TALER_TESTING_cmd_deposit ("deposit-double-1",
"withdraw-coin-1",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:5",
@@ -205,7 +231,7 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("deposit-double-1",
"withdraw-coin-1",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:5",
@@ -216,7 +242,7 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("deposit-double-2",
"withdraw-coin-1",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":2}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:5",
@@ -225,13 +251,24 @@ run (void *cls,
};
struct TALER_TESTING_Command refresh[] = {
+ /**
+ * Try to melt the coin that shared the private key with another
+ * coin (should fail). Note that in the CS-case, we fail also
+ * with MHD_HTTP_CONFLICT, but for a different reason: here it
+ * is not a denomination conflict, but a double-spending conflict.
+ */
+ TALER_TESTING_cmd_melt ("refresh-melt-reused-coin-key-failure",
+ "withdraw-coin-1x",
+ MHD_HTTP_CONFLICT,
+ NULL),
+
/* Fill reserve with EUR:5, 1ct is for fees. */
CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-1",
"EUR:5.01"),
TALER_TESTING_cmd_check_bank_admin_transfer ("ck-refresh-create-reserve-1",
"EUR:5.01",
- bc.user42_payto,
- bc.exchange_payto,
+ cred.user42_payto,
+ cred.exchange_payto,
"refresh-create-reserve-1"),
/**
* Make previous command effective.
@@ -243,14 +280,16 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1",
"refresh-create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/* Try to partially spend (deposit) 1 EUR of the 5 EUR coin
* (in full) (merchant would receive EUR:0.99 due to 1 ct
- * deposit fee) *///
+ * deposit fee)
+ */
TALER_TESTING_cmd_deposit ("refresh-deposit-partial",
"refresh-withdraw-coin-1",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:1\"}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
@@ -287,7 +326,7 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-1a",
"refresh-reveal-1-idempotency",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":3}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
@@ -298,7 +337,7 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-1b",
"refresh-reveal-1",
3,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":3}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:0.1",
@@ -319,11 +358,74 @@ run (void *cls,
TALER_TESTING_cmd_end ()
};
+ /**
+ * Test withdrawal with age restriction. Success is expected, so it MUST be
+ * called _after_ TALER_TESTING_cmd_exec_offline_sign_extensions is called,
+ * i. e. age restriction is activated in the exchange!
+ *
+ * TODO: create a test that tries to withdraw coins with age restriction but
+ * (expectedly) fails because the exchange doesn't support age restriction
+ * yet.
+ */
+ struct TALER_TESTING_Command withdraw_age[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-age",
+ "EUR:6.01"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age",
+ "EUR:6.01",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "create-reserve-age"),
+ /**
+ * Make a reserve exist, according to the previous
+ * transfer.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-age"),
+ /**
+ * Withdraw EUR:5.
+ */
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-age-1",
+ "create-reserve-age",
+ "EUR:5",
+ 13,
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command spend_age[] = {
+ /**
+ * Spend the coin.
+ */
+ TALER_TESTING_cmd_deposit ("deposit-simple-age",
+ "withdraw-coin-age-1",
+ 0,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:4.99",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay-age",
+ "deposit-simple-age",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay-age-1",
+ "deposit-simple-age",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_sleep ("sleep-before-age-deposit-replay",
+ 1),
+ TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay-age-2",
+ "deposit-simple-age",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+
struct TALER_TESTING_Command track[] = {
/* Try resolving a deposit's WTID, as we never triggered
* execution of transactions, the answer should be that
* the exchange knows about the deposit, but has no WTID yet.
- *///
+ */
TALER_TESTING_cmd_track_transaction ("deposit-wtid-found",
"deposit-simple",
0,
@@ -332,7 +434,7 @@ run (void *cls,
/* Try resolving a deposit's WTID for a failed deposit.
* As the deposit failed, the answer should be that the
* exchange does NOT know about the deposit.
- *///
+ */
TALER_TESTING_cmd_track_transaction ("deposit-wtid-failing",
"deposit-double-2",
0,
@@ -341,40 +443,69 @@ run (void *cls,
/* Try resolving an undefined (all zeros) WTID; this
* should fail as obviously the exchange didn't use that
* WTID value for any transaction.
- *///
+ */
TALER_TESTING_cmd_track_transfer_empty ("wire-deposit-failing",
NULL,
- 0,
MHD_HTTP_NOT_FOUND),
/* Run transfers. Note that _actual_ aggregation will NOT
* happen here, as each deposit operation is run with a
* fresh merchant public key, so the aggregator will treat
* them as "different" merchants and do the wire transfers
- * individually. *///
+ * individually. */
CMD_EXEC_AGGREGATOR ("run-aggregator"),
/**
* Check all the transfers took place.
*/
TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-499c",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:4.98",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-499c2",
+ cred.exchange_url,
+ "EUR:4.97",
+ cred.exchange_payto,
+ cred.user42_payto),
TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c1",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.98",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c2",
- ec.exchange_url,
+ cred.exchange_url,
+ "EUR:0.98",
+ cred.exchange_payto,
+ cred.user42_payto),
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c3",
+ cred.exchange_url,
"EUR:0.98",
- bc.exchange_payto,
- bc.user42_payto),
- TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c",
- ec.exchange_url,
+ cred.exchange_payto,
+ cred.user42_payto),
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c4",
+ cred.exchange_url,
+ "EUR:0.98",
+ cred.exchange_payto,
+ cred.user42_payto),
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-08c",
+ cred.exchange_url,
+ "EUR:0.08",
+ cred.exchange_payto,
+ cred.user43_payto),
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-08c2",
+ cred.exchange_url,
"EUR:0.08",
- bc.exchange_payto,
- bc.user43_payto),
+ cred.exchange_payto,
+ cred.user43_payto),
+ /* In case of CS, one transaction above succeeded that
+ failed for RSA, hence we need to check for an extra transfer here */
+ uses_cs
+ ? TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-98c",
+ cred.exchange_url,
+ "EUR:0.98",
+ cred.exchange_payto,
+ cred.user42_payto)
+ : TALER_TESTING_cmd_sleep ("dummy",
+ 0),
TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"),
TALER_TESTING_cmd_track_transaction ("deposit-wtid-ok",
"deposit-simple",
@@ -383,20 +514,17 @@ run (void *cls,
"check_bank_transfer-499c"),
TALER_TESTING_cmd_track_transfer ("wire-deposit-success-bank",
"check_bank_transfer-99c1",
- 0,
MHD_HTTP_OK,
"EUR:0.98",
"EUR:0.01"),
TALER_TESTING_cmd_track_transfer ("wire-deposits-success-wtid",
"deposit-wtid-ok",
- 0,
MHD_HTTP_OK,
"EUR:4.98",
"EUR:0.01"),
TALER_TESTING_cmd_end ()
};
-
/**
* This block checks whether a wire deadline
* very far in the future does NOT get aggregated now.
@@ -409,18 +537,19 @@ run (void *cls,
TALER_TESTING_cmd_check_bank_admin_transfer (
"check-create-reserve-unaggregated",
"EUR:5.01",
- bc.user42_payto,
- bc.exchange_payto,
+ cred.user42_payto,
+ cred.exchange_payto,
"create-reserve-unaggregated"),
CMD_EXEC_WIREWATCH ("wirewatch-unaggregated"),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated",
"create-reserve-unaggregated",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit ("deposit-unaggregated",
"withdraw-coin-unaggregated",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_relative_multiply (
GNUNET_TIME_UNIT_YEARS,
@@ -429,12 +558,108 @@ run (void *cls,
MHD_HTTP_OK),
CMD_EXEC_AGGREGATOR ("aggregation-attempt"),
- TALER_TESTING_cmd_check_bank_empty
- ("far-future-aggregation-b"),
+ TALER_TESTING_cmd_check_bank_empty (
+ "far-future-aggregation-b"),
TALER_TESTING_cmd_end ()
};
+ struct TALER_TESTING_Command refresh_age[] = {
+ /* Fill reserve with EUR:5, 1ct is for fees. */
+ CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-age-1",
+ "EUR:6.01"),
+ TALER_TESTING_cmd_check_bank_admin_transfer (
+ "ck-refresh-create-reserve-age-1",
+ "EUR:6.01",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "refresh-create-reserve-age-1"),
+ /**
+ * Make previous command effective.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-age-2"),
+ /**
+ * Withdraw EUR:7 with age restriction for age 13.
+ */
+ TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-age-1",
+ "refresh-create-reserve-age-1",
+ "EUR:5",
+ 13,
+ MHD_HTTP_OK),
+ /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin
+ * (in full) (merchant would receive EUR:0.99 due to 1 ct
+ * deposit fee)
+ */
+ TALER_TESTING_cmd_deposit ("refresh-deposit-partial-age",
+ "refresh-withdraw-coin-age-1",
+ 0,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:1\"}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:1",
+ MHD_HTTP_OK),
+ /**
+ * Melt the rest of the coin's value
+ * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */
+ TALER_TESTING_cmd_melt_double ("refresh-melt-age-1",
+ "refresh-withdraw-coin-age-1",
+ MHD_HTTP_OK,
+ NULL),
+ /**
+ * Complete (successful) melt operation, and
+ * withdraw the coins
+ */
+ TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-age-1",
+ "refresh-melt-age-1",
+ MHD_HTTP_OK),
+ /**
+ * Do it again to check idempotency
+ */
+ TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-age-1-idempotency",
+ "refresh-melt-age-1",
+ MHD_HTTP_OK),
+ /**
+ * Test that /refresh/link works
+ */
+ TALER_TESTING_cmd_refresh_link ("refresh-link-age-1",
+ "refresh-reveal-age-1",
+ MHD_HTTP_OK),
+ /**
+ * Try to spend a refreshed EUR:1 coin
+ */
+ TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-age-1a",
+ "refresh-reveal-age-1-idempotency",
+ 0,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":3}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:1",
+ MHD_HTTP_OK),
+ /**
+ * Try to spend a refreshed EUR:0.1 coin
+ */
+ TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-age-1b",
+ "refresh-reveal-age-1",
+ 3,
+ cred.user43_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":3}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:0.1",
+ MHD_HTTP_OK),
+ /* Test running a failing melt operation (same operation
+ * again must fail) */
+ TALER_TESTING_cmd_melt ("refresh-melt-failing-age",
+ "refresh-withdraw-coin-age-1",
+ MHD_HTTP_CONFLICT,
+ NULL),
+ /* Test running a failing melt operation (on a coin that
+ was itself revealed and subsequently deposited) */
+ TALER_TESTING_cmd_melt ("refresh-melt-failing-age-2",
+ "refresh-reveal-age-1",
+ MHD_HTTP_CONFLICT,
+ NULL),
+ TALER_TESTING_cmd_end ()
+ };
/**
* This block exercises the aggretation logic by making two payments
@@ -447,18 +672,19 @@ run (void *cls,
TALER_TESTING_cmd_check_bank_admin_transfer (
"check-create-reserve-aggtest",
"EUR:5.01",
- bc.user42_payto,
- bc.exchange_payto,
+ cred.user42_payto,
+ cred.exchange_payto,
"create-reserve-aggtest"),
CMD_EXEC_WIREWATCH ("wirewatch-aggtest"),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-aggtest",
"create-reserve-aggtest",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit ("deposit-aggtest-1",
"withdraw-coin-aggtest",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:2",
@@ -466,7 +692,7 @@ run (void *cls,
TALER_TESTING_cmd_deposit_with_ref ("deposit-aggtest-2",
"withdraw-coin-aggtest",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"foo bar\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:2",
@@ -474,10 +700,10 @@ run (void *cls,
"deposit-aggtest-1"),
CMD_EXEC_AGGREGATOR ("aggregation-aggtest"),
TALER_TESTING_cmd_check_bank_transfer ("check-bank-transfer-aggtest",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:3.97",
- bc.exchange_payto,
- bc.user43_payto),
+ cred.exchange_payto,
+ cred.user43_payto),
TALER_TESTING_cmd_check_bank_empty ("check-bank-empty-aggtest"),
TALER_TESTING_cmd_end ()
};
@@ -491,8 +717,8 @@ run (void *cls,
"EUR:5.01"),
TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-r1",
"EUR:5.01",
- bc.user42_payto,
- bc.exchange_payto,
+ cred.user42_payto,
+ cred.exchange_payto,
"create-reserve-r1"),
/**
* Run wire-watch to trigger the reserve creation.
@@ -502,6 +728,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1",
"create-reserve-r1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
* Spend 5 EUR of the 5 EUR coin (in full) (merchant would
@@ -510,7 +737,7 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("deposit-refund-1",
"withdraw-coin-r1",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:5\"}]}",
GNUNET_TIME_UNIT_MINUTES,
"EUR:5",
@@ -523,25 +750,37 @@ run (void *cls,
* Note, this operation takes two commands: one to "flush"
* the preliminary transfer (used to withdraw) from the
* fakebank and the second to actually check there are not
- * other transfers around. *///
+ * other transfers around. */
TALER_TESTING_cmd_check_bank_empty ("check_bank_transfer-pre-refund"),
- TALER_TESTING_cmd_refund ("refund-ok",
- MHD_HTTP_OK,
- "EUR:5",
- "EUR:0.01",
- "deposit-refund-1"),
- TALER_TESTING_cmd_refund ("refund-ok-double",
- MHD_HTTP_OK,
- "EUR:5",
- "EUR:0.01",
- "deposit-refund-1"),
+ TALER_TESTING_cmd_refund_with_id ("refund-ok",
+ MHD_HTTP_OK,
+ "EUR:3",
+ "deposit-refund-1",
+ 3),
+ TALER_TESTING_cmd_refund_with_id ("refund-ok-double",
+ MHD_HTTP_OK,
+ "EUR:3",
+ "deposit-refund-1",
+ 3),
/* Previous /refund(s) had id == 0. */
TALER_TESTING_cmd_refund_with_id ("refund-conflicting",
MHD_HTTP_CONFLICT,
"EUR:5",
- "EUR:0.01",
"deposit-refund-1",
1),
+ TALER_TESTING_cmd_deposit ("deposit-refund-insufficient-refund",
+ "withdraw-coin-r1",
+ 0,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:4\"}]}",
+ GNUNET_TIME_UNIT_MINUTES,
+ "EUR:4",
+ MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_refund_with_id ("refund-ok-increase",
+ MHD_HTTP_OK,
+ "EUR:2",
+ "deposit-refund-1",
+ 2),
/**
* Spend 4.99 EUR of the refunded 4.99 EUR coin (1ct gone
* due to refund) (merchant would receive EUR:4.98 due to
@@ -549,7 +788,7 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("deposit-refund-2",
"withdraw-coin-r1",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"more ice cream\",\"value\":\"EUR:5\"}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:4.99",
@@ -563,17 +802,16 @@ run (void *cls,
* Check that deposit did run.
*/
TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-pre-refund",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:4.97",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
/**
* Run failing refund, as past deadline & aggregation.
*/
TALER_TESTING_cmd_refund ("refund-fail",
MHD_HTTP_GONE,
"EUR:4.99",
- "EUR:0.01",
"deposit-refund-2"),
TALER_TESTING_cmd_check_bank_empty ("check-empty-after-refund"),
/**
@@ -584,18 +822,19 @@ run (void *cls,
"EUR:5.01"),
TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-rb",
"EUR:5.01",
- bc.user42_payto,
- bc.exchange_payto,
+ cred.user42_payto,
+ cred.exchange_payto,
"create-reserve-rb"),
CMD_EXEC_WIREWATCH ("wirewatch-rb"),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-rb",
"create-reserve-rb",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit ("deposit-refund-1b",
"withdraw-coin-rb",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:5\"}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:5",
@@ -607,13 +846,12 @@ run (void *cls,
TALER_TESTING_cmd_refund ("refund-ok-fast",
MHD_HTTP_OK,
"EUR:5",
- "EUR:0.01",
"deposit-refund-1b"),
/**
* Run transfers. This will do the transfer as refund deadline
* was 0, except of course because the refund succeeded, the
* transfer should no longer be done.
- *///
+ */
CMD_EXEC_AGGREGATOR ("run-aggregator-3b"),
/* check that aggregator didn't do anything, as expected */
TALER_TESTING_cmd_check_bank_empty ("check-refund-fast-not-run"),
@@ -626,12 +864,12 @@ run (void *cls,
* config.
*/
CMD_TRANSFER_TO_EXCHANGE ("recoup-create-reserve-1",
- "EUR:5.01"),
+ "EUR:15.02"),
TALER_TESTING_cmd_check_bank_admin_transfer (
"recoup-create-reserve-1-check",
- "EUR:5.01",
- bc.user42_payto,
- bc.exchange_payto,
+ "EUR:15.02",
+ cred.user42_payto,
+ cred.exchange_payto,
"recoup-create-reserve-1"),
/**
* Run wire-watch to trigger the reserve creation.
@@ -641,27 +879,104 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1",
"recoup-create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
+ MHD_HTTP_OK),
+ /* Withdraw a 10 EUR coin, at fee of 1 ct */
+ TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1b",
+ "recoup-create-reserve-1",
+ "EUR:10",
+ 0, /* age restriction off */
MHD_HTTP_OK),
- /* Make coin invalid */
+ /* melt 10 EUR coin to get 5 EUR refreshed coin */
+ TALER_TESTING_cmd_melt ("recoup-melt-coin-1b",
+ "recoup-withdraw-coin-1b",
+ MHD_HTTP_OK,
+ "EUR:5",
+ NULL),
+ TALER_TESTING_cmd_refresh_reveal ("recoup-reveal-coin-1b",
+ "recoup-melt-coin-1b",
+ MHD_HTTP_OK),
+ /* Revoke both 5 EUR coins */
TALER_TESTING_cmd_revoke ("revoke-0-EUR:5",
MHD_HTTP_OK,
"recoup-withdraw-coin-1",
- CONFIG_FILE),
- /* Refund coin to bank account */
+ config_file),
+ /* Recoup coin to reserve */
TALER_TESTING_cmd_recoup ("recoup-1",
MHD_HTTP_OK,
"recoup-withdraw-coin-1",
- NULL,
"EUR:5"),
/* Check the money is back with the reserve */
TALER_TESTING_cmd_status ("recoup-reserve-status-1",
"recoup-create-reserve-1",
"EUR:5.0",
MHD_HTTP_OK),
+ /* Recoup-refresh coin to 10 EUR coin */
+ TALER_TESTING_cmd_recoup_refresh ("recoup-1b",
+ MHD_HTTP_OK,
+ "recoup-reveal-coin-1b",
+ "recoup-melt-coin-1b",
+ "EUR:5"),
+ /* melt 10 EUR coin *again* to get 1 EUR refreshed coin */
+ TALER_TESTING_cmd_melt ("recoup-remelt-coin-1a",
+ "recoup-withdraw-coin-1b",
+ MHD_HTTP_OK,
+ "EUR:1",
+ NULL),
+ TALER_TESTING_cmd_refresh_reveal ("recoup-reveal-coin-1a",
+ "recoup-remelt-coin-1a",
+ MHD_HTTP_OK),
+ /* Try melting for more than the residual value to provoke an error */
+ TALER_TESTING_cmd_melt ("recoup-remelt-coin-1b",
+ "recoup-withdraw-coin-1b",
+ MHD_HTTP_OK,
+ "EUR:1",
+ NULL),
+ TALER_TESTING_cmd_melt ("recoup-remelt-coin-1c",
+ "recoup-withdraw-coin-1b",
+ MHD_HTTP_OK,
+ "EUR:1",
+ NULL),
+ TALER_TESTING_cmd_melt ("recoup-remelt-coin-1d",
+ "recoup-withdraw-coin-1b",
+ MHD_HTTP_OK,
+ "EUR:1",
+ NULL),
+ TALER_TESTING_cmd_melt ("recoup-remelt-coin-1e",
+ "recoup-withdraw-coin-1b",
+ MHD_HTTP_OK,
+ "EUR:1",
+ NULL),
+ TALER_TESTING_cmd_melt ("recoup-remelt-coin-1f",
+ "recoup-withdraw-coin-1b",
+ MHD_HTTP_OK,
+ "EUR:1",
+ NULL),
+ TALER_TESTING_cmd_melt ("recoup-remelt-coin-1g",
+ "recoup-withdraw-coin-1b",
+ MHD_HTTP_OK,
+ "EUR:1",
+ NULL),
+ TALER_TESTING_cmd_melt ("recoup-remelt-coin-1h",
+ "recoup-withdraw-coin-1b",
+ MHD_HTTP_OK,
+ "EUR:1",
+ NULL),
+ TALER_TESTING_cmd_melt ("recoup-remelt-coin-1i",
+ "recoup-withdraw-coin-1b",
+ MHD_HTTP_OK,
+ "EUR:1",
+ NULL),
+ TALER_TESTING_cmd_melt ("recoup-remelt-coin-1b-failing",
+ "recoup-withdraw-coin-1b",
+ MHD_HTTP_CONFLICT,
+ "EUR:1",
+ NULL),
/* Re-withdraw from this reserve */
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2",
"recoup-create-reserve-1",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
* This withdrawal will test the logic to create a "recoup"
@@ -670,6 +985,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2-over",
"recoup-create-reserve-1",
"EUR:10",
+ 0, /* age restriction off */
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_status ("recoup-reserve-status-2",
"recoup-create-reserve-1",
@@ -682,18 +998,19 @@ run (void *cls,
"EUR:5.01"),
TALER_TESTING_cmd_check_bank_admin_transfer ("check-short-lived-reserve",
"EUR:5.01",
- bc.user42_payto,
- bc.exchange_payto,
+ cred.user42_payto,
+ cred.exchange_payto,
"short-lived-reserve"),
- TALER_TESTING_cmd_exec_wirewatch ("short-lived-aggregation",
- CONFIG_FILE_EXPIRE_RESERVE_NOW),
+ TALER_TESTING_cmd_exec_wirewatch2 ("short-lived-aggregation",
+ config_file_expire_reserve_now,
+ "exchange-account-2"),
TALER_TESTING_cmd_exec_closer ("close-reserves",
- CONFIG_FILE_EXPIRE_RESERVE_NOW,
+ config_file_expire_reserve_now,
"EUR:5",
"EUR:0.01",
"short-lived-reserve"),
TALER_TESTING_cmd_exec_transfer ("close-reserves-transfer",
- CONFIG_FILE_EXPIRE_RESERVE_NOW),
+ config_file_expire_reserve_now),
TALER_TESTING_cmd_status ("short-lived-status",
"short-lived-reserve",
@@ -702,23 +1019,24 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("expired-withdraw",
"short-lived-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_check_bank_transfer ("check_bank_short-lived_reimburse",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:5",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
/* Fill reserve with EUR:2.02, as withdraw fee is 1 ct per
* config, then withdraw two coin, partially spend one, and
* then have the rest paid back. Check deposit of other coin
* fails. Do not use EUR:5 here as the EUR:5 coin was
- * revoked and we did not bother to create a new one... *///
+ * revoked and we did not bother to create a new one... */
CMD_TRANSFER_TO_EXCHANGE ("recoup-create-reserve-2",
"EUR:2.02"),
TALER_TESTING_cmd_check_bank_admin_transfer ("ck-recoup-create-reserve-2",
"EUR:2.02",
- bc.user42_payto,
- bc.exchange_payto,
+ cred.user42_payto,
+ cred.exchange_payto,
"recoup-create-reserve-2"),
/* Make previous command effective. */
CMD_EXEC_WIREWATCH ("wirewatch-5"),
@@ -726,16 +1044,18 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2a",
"recoup-create-reserve-2",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/* Withdraw a 1 EUR coin, at fee of 1 ct */
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2b",
"recoup-create-reserve-2",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit ("recoup-deposit-partial",
"recoup-withdraw-coin-2a",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"more ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:0.5",
@@ -743,64 +1063,141 @@ run (void *cls,
TALER_TESTING_cmd_revoke ("revoke-1-EUR:1",
MHD_HTTP_OK,
"recoup-withdraw-coin-2a",
- CONFIG_FILE),
+ config_file),
+ /* Check recoup is failing for the coin with the reused coin key
+ (fails either because of denomination conflict (RSA) or
+ double-spending (CS))*/
+ TALER_TESTING_cmd_recoup ("recoup-2x",
+ MHD_HTTP_CONFLICT,
+ "withdraw-coin-1x",
+ "EUR:1"),
TALER_TESTING_cmd_recoup ("recoup-2",
MHD_HTTP_OK,
"recoup-withdraw-coin-2a",
- NULL,
"EUR:0.5"),
/* Idempotency of recoup (withdrawal variant) */
TALER_TESTING_cmd_recoup ("recoup-2b",
MHD_HTTP_OK,
"recoup-withdraw-coin-2a",
- NULL,
- NULL),
+ "EUR:0.5"),
TALER_TESTING_cmd_deposit ("recoup-deposit-revoked",
"recoup-withdraw-coin-2b",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"more ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
- MHD_HTTP_NOT_FOUND),
+ MHD_HTTP_GONE),
/* Test deposit fails after recoup, with proof in recoup */
/* Note that, the exchange will never return the coin's transaction
- * history with recoup data, as we get a 404 on the DK! */
+ * history with recoup data, as we get a 410 on the DK! */
TALER_TESTING_cmd_deposit ("recoup-deposit-partial-after-recoup",
"recoup-withdraw-coin-2a",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"extra ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:0.5",
- MHD_HTTP_NOT_FOUND),
+ MHD_HTTP_GONE),
/* Test that revoked coins cannot be withdrawn */
CMD_TRANSFER_TO_EXCHANGE ("recoup-create-reserve-3",
"EUR:1.01"),
TALER_TESTING_cmd_check_bank_admin_transfer (
"check-recoup-create-reserve-3",
"EUR:1.01",
- bc.user42_payto,
- bc.exchange_payto,
+ cred.user42_payto,
+ cred.exchange_payto,
"recoup-create-reserve-3"),
CMD_EXEC_WIREWATCH ("wirewatch-6"),
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-3-revoked",
"recoup-create-reserve-3",
"EUR:1",
- MHD_HTTP_NOT_FOUND),
+ 0, /* age restriction off */
+ MHD_HTTP_GONE),
/* check that we are empty before the rejection test */
TALER_TESTING_cmd_check_bank_empty ("check-empty-again"),
TALER_TESTING_cmd_end ()
};
+ /**
+ * Test batch withdrawal plus spending.
+ */
+ struct TALER_TESTING_Command batch_withdraw[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-batch-reserve-1",
+ "EUR:6.03"),
+ TALER_TESTING_cmd_reserve_poll ("poll-batch-reserve-1",
+ "create-batch-reserve-1",
+ "EUR:6.03",
+ GNUNET_TIME_UNIT_MINUTES,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-batch-reserve-1",
+ "EUR:6.03",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "create-batch-reserve-1"),
+ /*
+ * Make a reserve exist, according to the previous
+ * transfer.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-batch-1"),
+ TALER_TESTING_cmd_reserve_poll_finish ("finish-poll-batch-reserve-1",
+ GNUNET_TIME_UNIT_SECONDS,
+ "poll-batch-reserve-1"),
+ /**
+ * Withdraw EUR:5 AND EUR:1.
+ */
+ TALER_TESTING_cmd_batch_withdraw ("batch-withdraw-coin-1",
+ "create-batch-reserve-1",
+ 0, /* age restriction off */
+ MHD_HTTP_OK,
+ "EUR:5",
+ "EUR:1",
+ NULL),
+ /**
+ * Check the reserve is (almost) depleted.
+ */
+ TALER_TESTING_cmd_status ("status-batch-1",
+ "create-batch-reserve-1",
+ "EUR:0.01",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_reserve_history ("history-batch-1",
+ "create-batch-reserve-1",
+ "EUR:0.01",
+ MHD_HTTP_OK),
+ /**
+ * Spend the coins.
+ */
+ TALER_TESTING_cmd_batch_deposit ("batch-deposit-1",
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":5}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ MHD_HTTP_OK,
+ "batch-withdraw-coin-1#0",
+ "EUR:5",
+ "batch-withdraw-coin-1#1",
+ "EUR:1",
+ NULL),
+ TALER_TESTING_cmd_coin_history ("coin-history-batch-1",
+ "batch-withdraw-coin-1#0",
+ "EUR:0.0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+
+
#define RESERVE_OPEN_CLOSE_CHUNK 4
#define RESERVE_OPEN_CLOSE_ITERATIONS 3
struct TALER_TESTING_Command reserve_open_close[(RESERVE_OPEN_CLOSE_ITERATIONS
* RESERVE_OPEN_CLOSE_CHUNK)
+ 1];
+
+ (void) cls;
for (unsigned int i = 0;
i < RESERVE_OPEN_CLOSE_ITERATIONS;
i++)
@@ -809,11 +1206,12 @@ run (void *cls,
= CMD_TRANSFER_TO_EXCHANGE ("reserve-open-close-key",
"EUR:20");
reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 1]
- = TALER_TESTING_cmd_exec_wirewatch ("reserve-open-close-wirewatch",
- CONFIG_FILE_EXPIRE_RESERVE_NOW);
+ = TALER_TESTING_cmd_exec_wirewatch2 ("reserve-open-close-wirewatch",
+ config_file_expire_reserve_now,
+ "exchange-account-2");
reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 2]
= TALER_TESTING_cmd_exec_closer ("reserve-open-close-aggregation",
- CONFIG_FILE_EXPIRE_RESERVE_NOW,
+ config_file_expire_reserve_now,
"EUR:19.99",
"EUR:0.01",
"reserve-open-close-key");
@@ -828,14 +1226,30 @@ run (void *cls,
{
struct TALER_TESTING_Command commands[] = {
- TALER_TESTING_cmd_batch ("wire",
- wire),
+ TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+ cred.cfg,
+ "exchange-account-2"),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_file,
+ "-e",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
TALER_TESTING_cmd_batch ("withdraw",
withdraw),
TALER_TESTING_cmd_batch ("spend",
spend),
TALER_TESTING_cmd_batch ("refresh",
refresh),
+ TALER_TESTING_cmd_batch ("withdraw-age",
+ withdraw_age),
+ TALER_TESTING_cmd_batch ("spend-age",
+ spend_age),
+ TALER_TESTING_cmd_batch ("refresh-age",
+ refresh_age),
TALER_TESTING_cmd_batch ("track",
track),
TALER_TESTING_cmd_batch ("unaggregation",
@@ -844,6 +1258,8 @@ run (void *cls,
aggregation),
TALER_TESTING_cmd_batch ("refund",
refund),
+ TALER_TESTING_cmd_batch ("batch-withdraw",
+ batch_withdraw),
TALER_TESTING_cmd_batch ("recoup",
recoup),
TALER_TESTING_cmd_batch ("reserve-open-close",
@@ -852,9 +1268,8 @@ run (void *cls,
TALER_TESTING_cmd_end ()
};
- TALER_TESTING_run_with_fakebank (is,
- commands,
- bc.exchange_auth.wire_gateway_url);
+ TALER_TESTING_run (is,
+ commands);
}
}
@@ -863,47 +1278,30 @@ int
main (int argc,
char *const *argv)
{
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test-exchange-api",
- "INFO",
- NULL);
- /* Check fakebank port is available and get config */
- if (GNUNET_OK !=
- TALER_TESTING_prepare_fakebank (CONFIG_FILE,
- "exchange-account-2",
- &bc))
- return 77;
- TALER_TESTING_cleanup_files (CONFIG_FILE);
- /* @helpers. Run keyup, create tables, ... Note: it
- * fetches the port number from config in order to see
- * if it's available. */
- switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
- GNUNET_YES,
- &ec))
+ (void) argc;
{
- case GNUNET_SYSERR:
- GNUNET_break (0);
- return 1;
- case GNUNET_NO:
- return 77;
- case GNUNET_OK:
- if (GNUNET_OK !=
- /* Set up event loop and reschedule context, plus
- * start/stop the exchange. It calls TALER_TESTING_setup
- * which creates the 'is' object.
- */
- TALER_TESTING_setup_with_exchange (&run,
- NULL,
- CONFIG_FILE))
- return 1;
- break;
- default:
- GNUNET_break (0);
- return 1;
+ char *cipher;
+
+ cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+ GNUNET_assert (NULL != cipher);
+ uses_cs = (0 == strcmp (cipher,
+ "cs"));
+ GNUNET_asprintf (&config_file,
+ "test_exchange_api-%s.conf",
+ cipher);
+ GNUNET_asprintf (&config_file_expire_reserve_now,
+ "test_exchange_api_expire_reserve_now-%s.conf",
+ cipher);
+ GNUNET_free (cipher);
}
- return 0;
+ return TALER_TESTING_main (argv,
+ "INFO",
+ config_file,
+ "exchange-account-2",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
}
diff --git a/src/testing/test_exchange_api.conf b/src/testing/test_exchange_api.conf
index 07d0e6c03..c25f5091a 100644
--- a/src/testing/test_exchange_api.conf
+++ b/src/testing/test_exchange_api.conf
@@ -2,201 +2,117 @@
#
[PATHS]
-# Persistent data storage for the testcase
TALER_TEST_HOME = test_exchange_api_home/
+[libeufin-bank]
+CURRENCY = EUR
+DEFAULT_CUSTOMER_DEBT_LIMIT = EUR:200
+DEFAULT_ADMIN_DEBT_LIMIT = EUR:2000
+REGISTRATION_BONUS_ENABLED = yes
+REGISTRATION_BONUS = EUR:100
+SUGGESTED_WITHDRAWAL_EXCHANGE = http://localhost:8081/
+WIRE_TYPE = iban
+IBAN_PAYTO_BIC = SANDBOXX
+SERVE = tcp
+PORT = 8082
+
+[libeufin-bankdb-postgres]
+CONFIG = postgresql:///talercheck
+
[taler]
-# Currency supported by the exchange (can only be one)
CURRENCY = EUR
CURRENCY_ROUND_UNIT = EUR:0.01
[auditor]
BASE_URL = "http://localhost:8083/"
-
-# HTTP port the auditor listens to
PORT = 8083
+PUBLIC_KEY = 9QZ7CCC5QFMWE9FVF50MGYWV7JR92SFHY5KHT8A1A2VNHM37VCRG
+TINY_AMOUNT = EUR:0.01
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
-[exchange]
-# how long is one signkey valid?
-signkey_duration = 4 weeks
-
-# how long are the signatures with the signkey valid?
-legal_duration = 2 years
-
-# how long do we provide to clients denomination and signing keys
-# ahead of time?
-lookahead_provide = 4 weeks 1 day
+[bank]
+HTTP_PORT = 8082
-# HTTP port the exchange listens to
+[exchange]
+TERMS_ETAG = tos
+PRIVACY_ETAG = 0
+AML_THRESHOLD = EUR:1000000
PORT = 8081
-
-# Master public key used to sign the exchange's various keys
-MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
-
-# How to access our database
+MASTER_PUBLIC_KEY = QD6H521CBJBW0Z7PRN0JTAGH5JCQ97RDZRPPV5TQZSE78NQRT3KG
DB = postgres
-
-# Base URL of the exchange. Must be set to a URL where the
-# exchange (or the twister) is actually listening.
BASE_URL = "http://localhost:8081/"
-
-# Keep it short so the test runs fast.
-LOOKAHEAD_SIGN = 12 h
-
+EXPIRE_SHARD_SIZE ="300 ms"
+EXPIRE_IDLE_SLEEP_INTERVAL ="1 s"
[exchangedb-postgres]
CONFIG = "postgres:///talercheck"
+[taler-exchange-secmod-cs]
+LOOKAHEAD_SIGN = "24 days"
-[auditordb-postgres]
-CONFIG = "postgres:///talercheck"
+[taler-exchange-secmod-rsa]
+LOOKAHEAD_SIGN = "24 days"
-# Sections starting with "exchange-account-" configure the bank accounts
-# of the exchange. The "URL" specifies the account in
-# payto://-format, while the WIRE_JSON specifies the
-# (possibly offline) signed version to be returned in /wire.
-# WIRE_JSON is optional, as not all accounts must be
-# advertised in /wire.
-[exchange-account-1]
-# What is the URL of our account?
-PAYTO_URI = "payto://x-taler-bank/localhost/42"
-# This is the response we give out for the /wire request. It provides
-# wallets with the bank information for transfers to the exchange.
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json
+[taler-exchange-secmod-eddsa]
+LOOKAHEAD_SIGN = "24 days"
+DURATION = "14 days"
-WIRE_GATEWAY_URL = "http://localhost:9081/42/"
-# ENABLE_CREDIT = YES
+[exchange-account-1]
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+[exchange-accountcredentials-1]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
-[exchange-account-2]
-# What is the bank account (with the "Taler Bank" demo system)?
-PAYTO_URI = "payto://x-taler-bank/localhost/2"
+[admin-accountcredentials-1]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
-# This is the response we give out for the /wire request. It provides
-# wallets with the bank information for transfers to the exchange.
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-2.json
+[exchange-account-2]
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+[exchange-accountcredentials-2]
WIRE_GATEWAY_AUTH_METHOD = basic
USERNAME = Exchange
PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
-WIRE_GATEWAY_URL = "http://localhost:9081/2/"
-
-ENABLE_DEBIT = YES
-
-ENABLE_CREDIT = YES
-
-[bank]
-HTTP_PORT = 9081
-
-# Sections starting with "fee-" configure the wire fee for the
-# respective wire method.
-[fees-iban]
-# Fees for the foreseeable future...
-# If you see this after 2017, update to match the next 10 years...
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
-WIRE-FEE-2027 = EUR:0.01
-
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-CLOSING-FEE-2027 = EUR:0.01
-
-[fees-x-taler-bank]
-# Fees for the foreseeable future...
-# If you see this after 2017, update to match the next 10 years...
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
-WIRE-FEE-2027 = EUR:0.01
-
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-CLOSING-FEE-2027 = EUR:0.01
-
-# Sections starting with "coin_" specify which denominations
-# the exchange should support (and their respective fee structure)
-[coin_eur_ct_1]
-value = EUR:0.01
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.00
-fee_deposit = EUR:0.00
-fee_refresh = EUR:0.01
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_ct_10]
-value = EUR:0.10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_1]
-value = EUR:1
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_5]
-value = EUR:5
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_10]
-value = EUR:10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
+[admin-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = basic
+# For now, fakebank still checks against the Exchange account...
+USERNAME = Exchange
+PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+
+
+[kyc-provider-test-oauth2]
+COST = 0
+LOGIC = oauth2
+USER_TYPE = INDIVIDUAL
+PROVIDED_CHECKS = DUMMY
+KYC_OAUTH2_VALIDITY = forever
+KYC_OAUTH2_TOKEN_URL = http://localhost:6666/oauth/v2/token
+KYC_OAUTH2_AUTHORIZE_URL = http://localhost:6666/oauth/v2/login
+KYC_OAUTH2_INFO_URL = http://localhost:6666/api/user/me
+KYC_OAUTH2_CLIENT_ID = taler-exchange
+KYC_OAUTH2_CLIENT_SECRET = exchange-secret
+KYC_OAUTH2_POST_URL = http://example.com/
+KYC_OAUTH2_CONVERTER_HELPER = taler-exchange-kyc-oauth2-test-converter.sh
+
+[kyc-legitimization-close]
+OPERATION_TYPE = CLOSE
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:0
+TIMEFRAME = 1d
+
+[exchange-extension-age_restriction]
+ENABLED = YES
+#AGE_GROUPS = "8:10:12:14:16:18:21"
diff --git a/src/testing/test_exchange_api_age_restriction-cs.conf b/src/testing/test_exchange_api_age_restriction-cs.conf
new file mode 100644
index 000000000..12195f9ba
--- /dev/null
+++ b/src/testing/test_exchange_api_age_restriction-cs.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+#
+@INLINE@ test_exchange_api_age_restriction.conf
+@INLINE@ coins-cs.conf
diff --git a/src/testing/test_exchange_api_age_restriction-rsa.conf b/src/testing/test_exchange_api_age_restriction-rsa.conf
new file mode 100644
index 000000000..30d75090f
--- /dev/null
+++ b/src/testing/test_exchange_api_age_restriction-rsa.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+#
+@INLINE@ test_exchange_api_age_restriction.conf
+@INLINE@ coins-rsa.conf
diff --git a/src/testing/test_exchange_api_age_restriction.c b/src/testing/test_exchange_api_age_restriction.c
new file mode 100644
index 000000000..5ba24a00c
--- /dev/null
+++ b/src/testing/test_exchange_api_age_restriction.c
@@ -0,0 +1,370 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/test_exchange_api_age_restriction.c
+ * @brief testcase to test exchange's age-restrictrition related HTTP API interfaces
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_testing_lib.h>
+#include <microhttpd.h>
+#include "taler_bank_service.h"
+#include "taler_fakebank_lib.h"
+#include "taler_testing_lib.h"
+#include "taler_extensions.h"
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+static char *config_file;
+
+/**
+ * Our credentials.
+ */
+static struct TALER_TESTING_Credentials cred;
+
+/**
+ * Some tests behave differently when using CS as we cannot
+ * reuse the coin private key for different denominations
+ * due to the derivation of it with the /csr values. Hence
+ * some tests behave differently in CS mode, hence this
+ * flag.
+ */
+static bool uses_cs;
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_WIREWATCH(label) \
+ TALER_TESTING_cmd_exec_wirewatch2 (label, config_file, "exchange-account-2")
+
+/**
+ * Execute the taler-exchange-aggregator, closer and transfer commands with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+ TALER_TESTING_cmd_sleep ("sleep-before-aggregator", 2), \
+ TALER_TESTING_cmd_exec_aggregator (label "-aggregator", config_file), \
+ TALER_TESTING_cmd_exec_transfer (label "-transfer", config_file)
+
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ */
+#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
+ TALER_TESTING_cmd_admin_add_incoming (label, amount, \
+ &cred.ba, \
+ cred.user42_payto)
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ * @param is interpreter we use to run commands
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+ (void) cls;
+ /**
+ * Test withdrawal with age restriction. Success is expected (because the
+ * amount is below the kyc threshold ), so it MUST be
+ * called _after_ TALER_TESTING_cmd_exec_offline_sign_extensions is called,
+ * i. e. age restriction is activated in the exchange!
+ *
+ * TODO: create a test that tries to withdraw coins with age restriction but
+ * (expectedly) fails because the exchange doesn't support age restriction
+ * yet.
+ */
+ struct TALER_TESTING_Command withdraw_age[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-age",
+ "EUR:6.01"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age",
+ "EUR:6.01",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "create-reserve-age"),
+ /**
+ * Make a reserve exist, according to the previous
+ * transfer.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-age"),
+ /**
+ * Withdraw EUR:5.
+ */
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-age-1",
+ "create-reserve-age",
+ "EUR:5",
+ 13,
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command spend_age[] = {
+ /**
+ * Spend the coin.
+ */
+ TALER_TESTING_cmd_deposit ("deposit-simple-age",
+ "withdraw-coin-age-1",
+ 0,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:4.99",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay-age",
+ "deposit-simple-age",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+
+
+ struct TALER_TESTING_Command refresh_age[] = {
+ /* Fill reserve with EUR:5, 1ct is for fees. */
+ CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-age-1",
+ "EUR:6.01"),
+ TALER_TESTING_cmd_check_bank_admin_transfer (
+ "ck-refresh-create-reserve-age-1",
+ "EUR:6.01",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "refresh-create-reserve-age-1"),
+ /**
+ * Make previous command effective.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-age-2"),
+ /**
+ * Withdraw EUR:7 with age restriction for age 13.
+ */
+ TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-age-1",
+ "refresh-create-reserve-age-1",
+ "EUR:5",
+ 13,
+ MHD_HTTP_OK),
+ /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin
+ * (in full) (merchant would receive EUR:0.99 due to 1 ct
+ * deposit fee)
+ */
+ TALER_TESTING_cmd_deposit ("refresh-deposit-partial-age",
+ "refresh-withdraw-coin-age-1",
+ 0,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:1\"}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:1",
+ MHD_HTTP_OK),
+ /**
+ * Melt the rest of the coin's value
+ * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */
+ TALER_TESTING_cmd_melt_double ("refresh-melt-age-1",
+ "refresh-withdraw-coin-age-1",
+ MHD_HTTP_OK,
+ NULL),
+ /**
+ * Complete (successful) melt operation, and
+ * withdraw the coins
+ */
+ TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-age-1",
+ "refresh-melt-age-1",
+ MHD_HTTP_OK),
+ /**
+ * Do it again to check idempotency
+ */
+ TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-age-1-idempotency",
+ "refresh-melt-age-1",
+ MHD_HTTP_OK),
+ /**
+ * Test that /refresh/link works
+ */
+ TALER_TESTING_cmd_refresh_link ("refresh-link-age-1",
+ "refresh-reveal-age-1",
+ MHD_HTTP_OK),
+ /**
+ * Try to spend a refreshed EUR:1 coin
+ */
+ TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-age-1a",
+ "refresh-reveal-age-1-idempotency",
+ 0,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":3}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:1",
+ MHD_HTTP_OK),
+ /**
+ * Try to spend a refreshed EUR:0.1 coin
+ */
+ TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-age-1b",
+ "refresh-reveal-age-1",
+ 3,
+ cred.user43_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":3}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:0.1",
+ MHD_HTTP_OK),
+ /* Test running a failing melt operation (same operation
+ * again must fail) */
+ TALER_TESTING_cmd_melt ("refresh-melt-failing-age",
+ "refresh-withdraw-coin-age-1",
+ MHD_HTTP_CONFLICT,
+ NULL),
+ /* Test running a failing melt operation (on a coin that
+ was itself revealed and subsequently deposited) */
+ TALER_TESTING_cmd_melt ("refresh-melt-failing-age-2",
+ "refresh-reveal-age-1",
+ MHD_HTTP_CONFLICT,
+ NULL),
+ TALER_TESTING_cmd_end ()
+ };
+
+ /**
+ * Test with age-withdraw, after kyc process has set a birthdate
+ */
+ struct TALER_TESTING_Command age_withdraw[] = {
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-kyc-1",
+ "EUR:30.02"),
+ TALER_TESTING_cmd_check_bank_admin_transfer (
+ "check-create-reserve-kyc-1",
+ "EUR:30.02",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "create-reserve-kyc-1"),
+ CMD_EXEC_WIREWATCH ("wirewatch-age-withdraw-1"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-lacking-kyc",
+ "create-reserve-kyc-1",
+ "EUR:10",
+ 0, /* age restriction off */
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS),
+ TALER_TESTING_cmd_check_kyc_get ("check-kyc-withdraw",
+ "withdraw-coin-1-lacking-kyc",
+ MHD_HTTP_ACCEPTED),
+ TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-kyc",
+ "withdraw-coin-1-lacking-kyc",
+ "kyc-provider-test-oauth2",
+ "pass",
+ MHD_HTTP_SEE_OTHER),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-with-kyc",
+ "create-reserve-kyc-1",
+ "EUR:10",
+ 0, /* age restriction off */
+ MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_age_withdraw ("age-withdraw-coin-1-too-low",
+ "create-reserve-kyc-1",
+ 18, /* Too high */
+ MHD_HTTP_CONFLICT,
+ "EUR:10",
+ NULL),
+ TALER_TESTING_cmd_age_withdraw ("age-withdraw-coins-1",
+ "create-reserve-kyc-1",
+ 8,
+ MHD_HTTP_OK,
+ "EUR:10",
+ "EUR:10",
+ "EUR:5",
+ NULL),
+ TALER_TESTING_cmd_age_withdraw_reveal ("age-withdraw-coins-reveal-1",
+ "age-withdraw-coins-1",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end (),
+ };
+
+ {
+ struct TALER_TESTING_Command commands[] = {
+ TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+ cred.cfg,
+ "exchange-account-2"),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_file,
+ "-e",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_oauth_with_birthdate ("oauth-service-with-birthdate",
+ "2015-00-00", /* enough for a while */
+ 6666),
+ TALER_TESTING_cmd_batch ("withdraw-age",
+ withdraw_age),
+ TALER_TESTING_cmd_batch ("spend-age",
+ spend_age),
+ TALER_TESTING_cmd_batch ("refresh-age",
+ refresh_age),
+ TALER_TESTING_cmd_batch ("age-withdraw",
+ age_withdraw),
+ /* End the suite. */
+ TALER_TESTING_cmd_end ()
+ };
+
+ TALER_TESTING_run (is,
+ commands);
+ }
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ (void) argc;
+ {
+ char *cipher;
+
+ cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+ GNUNET_assert (NULL != cipher);
+ uses_cs = (0 == strcmp (cipher,
+ "cs"));
+ GNUNET_asprintf (&config_file,
+ "test_exchange_api_age_restriction-%s.conf",
+ cipher);
+ GNUNET_free (cipher);
+ }
+ return TALER_TESTING_main (argv,
+ "INFO",
+ config_file,
+ "exchange-account-2",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
+}
+
+
+/* end of test_exchange_api_age_restriction.c */
diff --git a/src/testing/test_exchange_api_age_restriction.conf b/src/testing/test_exchange_api_age_restriction.conf
new file mode 100644
index 000000000..1345fcb1a
--- /dev/null
+++ b/src/testing/test_exchange_api_age_restriction.conf
@@ -0,0 +1,119 @@
+# This file is in the public domain.
+#
+
+[PATHS]
+TALER_TEST_HOME = test_exchange_api_home/
+
+[taler]
+CURRENCY = EUR
+CURRENCY_ROUND_UNIT = EUR:0.01
+
+[auditor]
+BASE_URL = "http://localhost:8083/"
+PORT = 8083
+PUBLIC_KEY = T0XJ9QZ59YDN7QG3RE40SB2HY7W0ASR1EKF4WZDGZ1G159RSQC80
+TINY_AMOUNT = EUR:0.01
+
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[bank]
+HTTP_PORT = 8082
+
+[exchange]
+TERMS_ETAG = tos
+PRIVACY_ETAG = 0
+AML_THRESHOLD = EUR:10
+PORT = 8081
+MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+DB = postgres
+BASE_URL = "http://localhost:8081/"
+EXPIRE_SHARD_SIZE ="300 ms"
+EXPIRE_IDLE_SLEEP_INTERVAL ="1 s"
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[taler-exchange-secmod-cs]
+LOOKAHEAD_SIGN = "24 days"
+
+[taler-exchange-secmod-rsa]
+LOOKAHEAD_SIGN = "24 days"
+
+[taler-exchange-secmod-eddsa]
+LOOKAHEAD_SIGN = "24 days"
+DURATION = "14 days"
+
+
+[exchange-account-1]
+PAYTO_URI = "payto://x-taler-bank/localhost/42?receiver-name=42"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-1]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/42/taler-wire-gateway/"
+
+[admin-accountcredentials-1]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/42/taler-wire-gateway/"
+
+[exchange-account-2]
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+
+[admin-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+
+
+[kyc-provider-test-oauth2]
+COST = 0
+LOGIC = oauth2
+USER_TYPE = INDIVIDUAL
+PROVIDED_CHECKS = DUMMY
+KYC_OAUTH2_VALIDITY = forever
+KYC_OAUTH2_TOKEN_URL = http://localhost:6666/oauth/v2/token
+KYC_OAUTH2_AUTHORIZE_URL = http://localhost:6666/oauth/v2/login
+KYC_OAUTH2_INFO_URL = http://localhost:6666/api/user/me
+KYC_OAUTH2_CLIENT_ID = taler-exchange
+KYC_OAUTH2_CLIENT_SECRET = exchange-secret
+KYC_OAUTH2_POST_URL = http://example.com/
+KYC_OAUTH2_CONVERTER_HELPER = taler-exchange-kyc-oauth2-test-converter.sh
+
+[kyc-legitimization-balance-high]
+OPERATION_TYPE = BALANCE
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:20
+
+[kyc-legitimization-deposit-any]
+OPERATION_TYPE = DEPOSIT
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:10
+TIMEFRAME = 1d
+
+[kyc-legitimization-withdraw]
+OPERATION_TYPE = WITHDRAW
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:15
+TIMEFRAME = 1d
+
+[kyc-legitimization-merge]
+OPERATION_TYPE = MERGE
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:15
+TIMEFRAME = 1d
+
+
+[exchange-extension-age_restriction]
+ENABLED = YES
+#AGE_GROUPS = "8:10:12:14:16:18:21"
diff --git a/src/testing/test_exchange_api_conflicts-cs.conf b/src/testing/test_exchange_api_conflicts-cs.conf
new file mode 100644
index 000000000..c15d55490
--- /dev/null
+++ b/src/testing/test_exchange_api_conflicts-cs.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+#
+@INLINE@ test_exchange_api_conflicts.conf
+@INLINE@ coins-cs.conf
diff --git a/src/testing/test_exchange_api_conflicts-rsa.conf b/src/testing/test_exchange_api_conflicts-rsa.conf
new file mode 100644
index 000000000..f56111eee
--- /dev/null
+++ b/src/testing/test_exchange_api_conflicts-rsa.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+#
+@INLINE@ test_exchange_api_conflicts.conf
+@INLINE@ coins-rsa.conf
diff --git a/src/testing/test_exchange_api_conflicts.c b/src/testing/test_exchange_api_conflicts.c
new file mode 100644
index 000000000..070809d9d
--- /dev/null
+++ b/src/testing/test_exchange_api_conflicts.c
@@ -0,0 +1,312 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/test_exchange_api_conflicts.c
+ * @brief testcase to test exchange's handling of coin conflicts: same private
+ * keys but different denominations or age restrictions
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_testing_lib.h>
+#include <microhttpd.h>
+#include "taler_bank_service.h"
+#include "taler_fakebank_lib.h"
+#include "taler_testing_lib.h"
+#include "taler_extensions.h"
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+static char *config_file;
+
+/**
+ * Our credentials.
+ */
+static struct TALER_TESTING_Credentials cred;
+
+/**
+ * Some tests behave differently when using CS as we cannot
+ * reuse the coin private key for different denominations
+ * due to the derivation of it with the /csr values. Hence
+ * some tests behave differently in CS mode, hence this
+ * flag.
+ */
+static bool uses_cs;
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_WIREWATCH(label) \
+ TALER_TESTING_cmd_exec_wirewatch2 (label, config_file, \
+ "exchange-account-2")
+
+/**
+ * Execute the taler-exchange-aggregator, closer and transfer commands with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+ TALER_TESTING_cmd_sleep ("sleep-before-aggregator", 2), \
+ TALER_TESTING_cmd_exec_aggregator (label "-aggregator", config_file), \
+ TALER_TESTING_cmd_exec_transfer (label "-transfer", config_file)
+
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ */
+#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
+ TALER_TESTING_cmd_admin_add_incoming (label, amount, \
+ &cred.ba, \
+ cred.user42_payto)
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ * @param is interpreter we use to run commands
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+ (void) cls;
+ /**
+ * Test withdrawal with conflicting coins.
+ */
+ struct TALER_TESTING_Command withdraw_conflict_denom[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-denom",
+ "EUR:21.14"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-denom",
+ "EUR:21.14",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "create-reserve-denom"),
+ /**
+ * Make a reserve exist, according to the previous
+ * transfer.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-conflict-denom"),
+ /**
+ * Withdraw EUR:0.10, EUR:1, EUR:5, EUR:15, but using the same private key each time.
+ */
+ TALER_TESTING_cmd_batch_withdraw_with_conflict ("withdraw-coin-denom-1",
+ "create-reserve-denom",
+ true,
+ 0, /* age */
+ MHD_HTTP_OK,
+ "EUR:1",
+ "EUR:5",
+ "EUR:10",
+ "EUR:0.10",
+ NULL),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command spend_conflict_denom[] = {
+ /**
+ * Spend the coin.
+ */
+ TALER_TESTING_cmd_deposit ("deposit-denom",
+ "withdraw-coin-denom-1",
+ 0,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:0.99",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_deposit ("deposit-denom-conflict",
+ "withdraw-coin-denom-1",
+ 1,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:4.99",
+ /* Note: For CS, even though the master secret is the
+ * same for each coin, their private keys differ due
+ * to the random choice of the nonce by the exchange. */
+ uses_cs ? MHD_HTTP_OK : MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_deposit ("deposit-denom-conflict-2",
+ "withdraw-coin-denom-1",
+ 2,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:9.99",
+ /* Note: For CS, even though the master secret is the
+ * same for each coin, their private keys differ due
+ * to the random choice of the nonce by the exchange. */
+ uses_cs ? MHD_HTTP_OK : MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_deposit ("deposit-denom-conflict-3",
+ "withdraw-coin-denom-1",
+ 3,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:0.09",
+ /* Note: For CS, even though the master secret is the
+ * same for each coin, their private keys differ due
+ * to the random choice of the nonce by the exchange. */
+ uses_cs ? MHD_HTTP_OK : MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command withdraw_conflict_age[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-age",
+ "EUR:3.03"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age",
+ "EUR:3.03",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "create-reserve-age"),
+ /**
+ * Make a reserve exist, according to the previous
+ * transfer.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-conflict-age"),
+ /**
+ * Withdraw EUR:1, EUR:5, EUR:15, but using the same private key each time.
+ */
+ TALER_TESTING_cmd_batch_withdraw_with_conflict ("withdraw-coin-age-1",
+ "create-reserve-age",
+ true,
+ 10, /* age */
+ MHD_HTTP_OK,
+ "EUR:1",
+ "EUR:1",
+ "EUR:1",
+ NULL),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command spend_conflict_age[] = {
+ /**
+ * Spend the coin.
+ */
+ TALER_TESTING_cmd_deposit ("deposit-age",
+ "withdraw-coin-age-1",
+ 0,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:0.99",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_deposit ("deposit-age-conflict",
+ "withdraw-coin-age-1",
+ 1,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:0.99",
+ MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_deposit ("deposit-age-conflict-2",
+ "withdraw-coin-age-1",
+ 2,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:0.99",
+ MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_end ()
+ };
+
+
+ {
+ struct TALER_TESTING_Command commands[] = {
+ TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+ cred.cfg,
+ "exchange-account-2"),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_file,
+ "-e",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_batch ("withdraw-conflict-denom",
+ withdraw_conflict_denom),
+ TALER_TESTING_cmd_batch ("spend-conflict-denom",
+ spend_conflict_denom),
+ TALER_TESTING_cmd_batch ("withdraw-conflict-age",
+ withdraw_conflict_age),
+ TALER_TESTING_cmd_batch ("spend-conflict-age",
+ spend_conflict_age),
+ /* End the suite. */
+ TALER_TESTING_cmd_end ()
+ };
+
+ TALER_TESTING_run (is,
+ commands);
+ }
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ (void) argc;
+ {
+ char *cipher;
+
+ cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+ GNUNET_assert (NULL != cipher);
+ uses_cs = (0 == strcmp (cipher,
+ "cs"));
+ GNUNET_asprintf (&config_file,
+ "test_exchange_api_conflicts-%s.conf",
+ cipher);
+ GNUNET_free (cipher);
+ }
+ return TALER_TESTING_main (argv,
+ "INFO",
+ config_file,
+ "exchange-account-2",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
+}
+
+
+/* end of test_exchange_api_conflicts.c */
diff --git a/src/testing/test_exchange_api_conflicts.conf b/src/testing/test_exchange_api_conflicts.conf
new file mode 100644
index 000000000..d04379f05
--- /dev/null
+++ b/src/testing/test_exchange_api_conflicts.conf
@@ -0,0 +1,81 @@
+# This file is in the public domain.
+#
+
+[PATHS]
+TALER_TEST_HOME = test_exchange_api_home/
+
+[taler]
+CURRENCY = EUR
+CURRENCY_ROUND_UNIT = EUR:0.01
+
+[auditor]
+BASE_URL = "http://localhost:8083/"
+PORT = 8083
+PUBLIC_KEY = T0XJ9QZ59YDN7QG3RE40SB2HY7W0ASR1EKF4WZDGZ1G159RSQC80
+TINY_AMOUNT = EUR:0.01
+
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[bank]
+HTTP_PORT = 8082
+
+[exchange]
+TERMS_ETAG = tos
+PRIVACY_ETAG = 0
+PORT = 8081
+AML_THRESHOLD = "EUR:99999999"
+MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+DB = postgres
+BASE_URL = "http://localhost:8081/"
+EXPIRE_SHARD_SIZE ="300 ms"
+EXPIRE_IDLE_SLEEP_INTERVAL ="1 s"
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[taler-exchange-secmod-cs]
+LOOKAHEAD_SIGN = "24 days"
+
+[taler-exchange-secmod-rsa]
+LOOKAHEAD_SIGN = "24 days"
+
+[taler-exchange-secmod-eddsa]
+LOOKAHEAD_SIGN = "24 days"
+DURATION = "14 days"
+
+
+[exchange-account-1]
+PAYTO_URI = "payto://x-taler-bank/localhost/42?receiver-name=42"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-1]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/42/taler-wire-gateway/"
+
+[admin-accountcredentials-1]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/42/taler-wire-gateway/"
+
+[exchange-account-2]
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+
+[admin-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+
+
+[exchange-extension-age_restriction]
+ENABLED = YES
+#AGE_GROUPS = "8:10:12:14:16:18:21"
diff --git a/src/testing/test_exchange_api_expire_reserve_now-cs.conf b/src/testing/test_exchange_api_expire_reserve_now-cs.conf
new file mode 100644
index 000000000..2cc4e0917
--- /dev/null
+++ b/src/testing/test_exchange_api_expire_reserve_now-cs.conf
@@ -0,0 +1,4 @@
+@INLINE@ test_exchange_api-cs.conf
+
+[exchangedb]
+IDLE_RESERVE_EXPIRATION_TIME = 0 s
diff --git a/src/testing/test_exchange_api_expire_reserve_now-rsa.conf b/src/testing/test_exchange_api_expire_reserve_now-rsa.conf
new file mode 100644
index 000000000..52b829871
--- /dev/null
+++ b/src/testing/test_exchange_api_expire_reserve_now-rsa.conf
@@ -0,0 +1,4 @@
+@INLINE@ test_exchange_api-rsa.conf
+
+[exchangedb]
+IDLE_RESERVE_EXPIRATION_TIME = 0 s
diff --git a/src/testing/test_exchange_api_home/.config/taler/account-2.json b/src/testing/test_exchange_api_home/.config/taler/account-2.json
deleted file mode 100644
index 21a7500b4..000000000
--- a/src/testing/test_exchange_api_home/.config/taler/account-2.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "payto_uri": "payto://x-taler-bank/localhost/2",
- "master_sig": "HEWC1XDS0QZ53YQR451VRKD4N968NXWGZXS30HJ59MJ0PESACK1ZYPYCAT15P08WD58C7D7F6EVN26D59JKA75XEBDQCM8VYFETK82R"
-} \ No newline at end of file
diff --git a/src/testing/test_exchange_api_home/.local/share/taler/auditor/offline-keys/auditor.priv b/src/testing/test_exchange_api_home/.local/share/taler/auditor/offline-keys/auditor.priv
new file mode 100644
index 000000000..5b1c8fa05
--- /dev/null
+++ b/src/testing/test_exchange_api_home/.local/share/taler/auditor/offline-keys/auditor.priv
Binary files differ
diff --git a/src/testing/test_exchange_api_home/.local/share/taler/exchange-offline/master.priv b/src/testing/test_exchange_api_home/.local/share/taler/exchange-offline/master.priv
new file mode 100644
index 000000000..391b6ea73
--- /dev/null
+++ b/src/testing/test_exchange_api_home/.local/share/taler/exchange-offline/master.priv
Binary files differ
diff --git a/src/testing/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv b/src/testing/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv
deleted file mode 100644
index 394926938..000000000
--- a/src/testing/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv
+++ /dev/null
@@ -1 +0,0 @@
-pÚ^ó-Ú33ˆ€XXÁ!ˆ\0qúýµmUþ_‰ˆ \ No newline at end of file
diff --git a/src/testing/test_exchange_api_home/taler/auditor/offline-keys/auditor.priv b/src/testing/test_exchange_api_home/taler/auditor/offline-keys/auditor.priv
new file mode 100644
index 000000000..7e712e483
--- /dev/null
+++ b/src/testing/test_exchange_api_home/taler/auditor/offline-keys/auditor.priv
@@ -0,0 +1 @@
+·G,U²†{ ~#ræ-½Hê ‘¶ÒÆÏâÿñ \ No newline at end of file
diff --git a/src/testing/test_exchange_api_interpreter_on-off.c b/src/testing/test_exchange_api_interpreter_on-off.c
deleted file mode 100644
index e0ef75090..000000000
--- a/src/testing/test_exchange_api_interpreter_on-off.c
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/test_exchange_api_keys_cherry_picking_new.c
- * @brief testcase to test exchange's /keys cherry picking ability
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-
-#include "platform.h"
-#include "taler_util.h"
-#include "taler_signatures.h"
-#include "taler_exchange_service.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler_bank_service.h"
-#include "taler_fakebank_lib.h"
-#include "taler_testing_lib.h"
-
-/**
- * XXX:
- *
- * This test helps in finding a way to use/modify the "normal"
- * cert_cb to handle reconnections from serialized states as well.
- *
- * 1st step: simply turn the interpreter off and on again.
- * 2nd step: turn the interpreter off and give a serial state
- * to reconnect.
- */
-
-/**
- * Configuration file we use. One (big) configuration is used
- * for the various components for this test.
- */
-#define CONFIG_FILE "test_exchange_api_keys_cherry_picking.conf"
-
-/**
- * Exchange base URL; mainly purpose is to make the compiler happy.
- */
-static char *exchange_url;
-
-/**
- * Auditor base URL; mainly purpose is to make the compiler happy.
- */
-static char *auditor_url;
-
-
-/**
- * Main function that will tell the interpreter what commands to
- * run.
- *
- * @param cls closure
- */
-static void
-run (void *cls,
- struct TALER_TESTING_Interpreter *is)
-{
-
- struct TALER_TESTING_Command commands[] = {
-
- TALER_TESTING_cmd_end ()
- };
-
- TALER_TESTING_run (is,
- commands);
-}
-
-
-int
-main (int argc,
- char *const *argv)
-{
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test-exchange-api-cherry-picking-new",
- "DEBUG", NULL);
- TALER_TESTING_cleanup_files (CONFIG_FILE);
- /* @helpers. Run keyup, create tables, ... Note: it
- * fetches the port number from config in order to see
- * if it's available. */
- switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
- &auditor_url,
- &exchange_url))
- {
- case GNUNET_SYSERR:
- GNUNET_break (0);
- return 1;
- case GNUNET_NO:
- return 77;
- case GNUNET_OK:
- if (GNUNET_OK !=
- /* Set up event loop and reschedule context, plus
- * start/stop the exchange. It calls TALER_TESTING_setup
- * which creates the 'is' object.
- */
- TALER_TESTING_setup_with_exchange (&run,
- NULL,
- CONFIG_FILE))
- return 1;
- break;
- default:
- GNUNET_break (0);
- return 1;
- }
- return 0;
-}
-
-
-/* end of test_exchange_api_keys_cherry_picking_new.c */
diff --git a/src/testing/test_exchange_api_keys_cherry_picking-cs.conf b/src/testing/test_exchange_api_keys_cherry_picking-cs.conf
new file mode 100644
index 000000000..d25ef3c00
--- /dev/null
+++ b/src/testing/test_exchange_api_keys_cherry_picking-cs.conf
@@ -0,0 +1,18 @@
+# This file is in the public domain.
+#
+@INLINE@ test_exchange_api_keys_cherry_picking.conf
+
+[taler-exchange-secmod-cs]
+OVERLAP_DURATION = 1 s
+LOOKAHEAD_SIGN = 20 s
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 5 s
+duration_spend = 6 s
+duration_legal = 7 s
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
diff --git a/src/testing/test_exchange_api_keys_cherry_picking-rsa.conf b/src/testing/test_exchange_api_keys_cherry_picking-rsa.conf
new file mode 100644
index 000000000..672639b3f
--- /dev/null
+++ b/src/testing/test_exchange_api_keys_cherry_picking-rsa.conf
@@ -0,0 +1,19 @@
+# This file is in the public domain.
+#
+@INLINE@ test_exchange_api_keys_cherry_picking.conf
+
+[taler-exchange-secmod-rsa]
+OVERLAP_DURATION = 1 s
+LOOKAHEAD_SIGN = 20 s
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 5 s
+duration_spend = 6 s
+duration_legal = 7 s
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
diff --git a/src/testing/test_exchange_api_keys_cherry_picking.c b/src/testing/test_exchange_api_keys_cherry_picking.c
index ed11bd5ed..2919ea8d5 100644
--- a/src/testing/test_exchange_api_keys_cherry_picking.c
+++ b/src/testing/test_exchange_api_keys_cherry_picking.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018 Taler Systems SA
+ Copyright (C) 2020 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 pub
@@ -29,6 +29,7 @@ lished
#include "taler_exchange_service.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_testing_lib.h>
#include <microhttpd.h>
#include "taler_bank_service.h"
#include "taler_fakebank_lib.h"
@@ -39,78 +40,16 @@ lished
* Configuration file we use. One (big) configuration is used
* for the various components for this test.
*/
-#define CONFIG_FILE "test_exchange_api_keys_cherry_picking.conf"
+static char *config_file;
/**
- * Used to increase the number of denomination keys.
+ * Our credentials.
*/
-#define CONFIG_FILE_EXTENDED \
- "test_exchange_api_keys_cherry_picking_extended.conf"
-
-/**
- * Used to increase the number of denomination keys.
- */
-#define CONFIG_FILE_EXTENDED_2 \
- "test_exchange_api_keys_cherry_picking_extended_2.conf"
-
-
-#define NDKS_RIGHT_BEFORE_SERIALIZATION 40
-
-/**
- * Add seconds.
- *
- * @param base absolute time to add seconds to.
- * @param relative number of seconds to add.
- * @return a new absolute time, modified according to @e relative.
- */
-#define ADDSECS(base, secs) \
- GNUNET_TIME_absolute_add \
- (base, \
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, \
- secs))
-
-/**
- * Subtract seconds.
- *
- * @param base absolute time to subtract seconds to.
- * @param secs relative number of _seconds_ to subtract.
- * @return a new absolute time, modified according to @e relative.
- */
-#define SUBSECS(base, secs) \
- GNUNET_TIME_absolute_subtract \
- (base, \
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, \
- secs))
-#define JAN1971 "1971-01-01"
-#define JAN2030 "2030-01-01"
-
-/**
- * Exchange configuration data.
- */
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-
-/**
- * Wrapper around the time parser.
- *
- * @param str human-readable time string.
- * @return the parsed time from @a str.
- */
-static struct GNUNET_TIME_Absolute
-TTH_parse_time (const char *str)
-{
- struct GNUNET_TIME_Absolute ret;
-
- GNUNET_assert
- (GNUNET_OK == GNUNET_STRINGS_fancy_time_to_absolute (str,
- &ret));
- return ret;
-}
+static struct TALER_TESTING_Credentials cred;
/**
- * Main function that will tell the interpreter what commands to
- * run.
+ * Main function that will tell the interpreter what commands to run.
*
* @param cls closure
* @param is[in,out] interpreter state
@@ -119,100 +58,32 @@ static void
run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
- struct TALER_TESTING_Command keys_serialization[] = {
- TALER_TESTING_cmd_serialize_keys
- ("serialize-keys"),
- TALER_TESTING_cmd_connect_with_state
- ("reconnect-with-state",
- "serialize-keys"),
- /**
- * Make sure we have the same keys situation as
- * it was before the serialization.
- */
- TALER_TESTING_cmd_check_keys_with_now
- ("check-keys-after-deserialization",
- 4,
- NDKS_RIGHT_BEFORE_SERIALIZATION,
- /**
- * Pretend 5 seconds passed.
- */
- ADDSECS (TTH_parse_time (JAN2030),
- 5)),
- /**
- * Use one of the deserialized keys.
- */
- TALER_TESTING_cmd_wire
- ("verify-/wire-with-serialized-keys",
- "x-taler-bank",
- NULL,
- MHD_HTTP_OK),
- TALER_TESTING_cmd_end (),
- };
-
- struct TALER_TESTING_Command ordinary_cherry_pick[] = {
- /**
- * 1 DK with 80s withdraw duration, lookahead_sign is 60s
- * => expect 1 DK.
- */
- TALER_TESTING_cmd_check_keys ("check-keys-1",
- 1, /* generation */
- 1),
- /**
- * The far-future now will cause "keyup" to start a fresh
- * key set. The new KS will have only one key, because the
- * current lookahead_sign == 60 seconds and the key's withdraw
- * duration is 80 seconds.
- *///
- TALER_TESTING_cmd_exec_keyup_with_now ("keyup-1",
- CONFIG_FILE,
- TTH_parse_time (JAN2030)),
- /**
- * Should return 1 new key, + the original one. NOTE: the
- * original DK will never be 'cancelled' as for the current
- * libtalerexchange logic, so it must always be counted.
- *///
- TALER_TESTING_cmd_check_keys_with_now ("check-keys-2",
- 2, /* generation */
- 2,
- TTH_parse_time (JAN2030)),
- TALER_TESTING_cmd_exec_keyup_with_now
- ("keyup-3",
- CONFIG_FILE_EXTENDED_2,
- /* Taking care of not using a 'now' that equals the
- * last DK timestamp, otherwise it would get silently
- * overridden. */
- ADDSECS (TTH_parse_time (JAN2030),
- 10)),
- /**
- * Expected number of DK:
- *
- * 3000 (the lookahead_sign time frame, in seconds)
- * - 69 (how many seconds are covered by the latest DK, 79s - 10s already past)
- * ----
- * 2931
- * / 79 (how many seconds each DK will cover, 80-1)
- * ----
- * 38 (rounded up)
- * + 2 (old DKs already stored locally: 1 from the
- * very initial setup, and 1 from the 'keyup-1' CMD)
- * ----
- * 40
- *///
- TALER_TESTING_cmd_check_keys_with_now
- ("check-keys-3",
- 3 /* generation */,
- NDKS_RIGHT_BEFORE_SERIALIZATION,
- TTH_parse_time (JAN2030)),
- TALER_TESTING_cmd_end ()
- };
struct TALER_TESTING_Command commands[] = {
- TALER_TESTING_cmd_batch ("ordinary-cherry-pick",
- ordinary_cherry_pick),
- TALER_TESTING_cmd_batch ("keys-serialization",
- keys_serialization),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_file,
+ "-e",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_sleep ("sleep",
+ 6 /* seconds */),
+ TALER_TESTING_cmd_get_exchange ("get-exchange-1",
+ cred.cfg,
+ "get-exchange",
+ true,
+ true),
+ TALER_TESTING_cmd_get_exchange ("get-exchange-2",
+ cred.cfg,
+ "get-exchange-1",
+ true,
+ true),
TALER_TESTING_cmd_end ()
};
+ (void) cls;
TALER_TESTING_run (is,
commands);
}
@@ -222,41 +93,25 @@ int
main (int argc,
char *const *argv)
{
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test-exchange-api-cherry-picking",
- "DEBUG",
- NULL);
- TALER_TESTING_cleanup_files (CONFIG_FILE);
- /* @helpers. Run keyup, create tables, ... Note: it
- * fetches the port number from config in order to see
- * if it's available. */
- switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
- GNUNET_YES,
- &ec))
+ (void) argc;
{
- case GNUNET_SYSERR:
- GNUNET_break (0);
- return 1;
- case GNUNET_NO:
- return 77;
- case GNUNET_OK:
- if (GNUNET_OK !=
- /* Set up event loop and reschedule context, plus
- * start/stop the exchange. It calls TALER_TESTING_setup
- * which creates the 'is' object.
- */
- TALER_TESTING_setup_with_exchange (&run,
- NULL,
- CONFIG_FILE))
- return 1;
- break;
- default:
- GNUNET_break (0);
- return 1;
+ char *cipher;
+
+ cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+ GNUNET_assert (NULL != cipher);
+ GNUNET_asprintf (&config_file,
+ "test_exchange_api_keys_cherry_picking-%s.conf",
+ cipher);
+ GNUNET_free (cipher);
}
- return 0;
+ return TALER_TESTING_main (argv,
+ "INFO",
+ config_file,
+ "exchange-account-2",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
}
diff --git a/src/testing/test_exchange_api_keys_cherry_picking.conf b/src/testing/test_exchange_api_keys_cherry_picking.conf
index d81f2c96b..142242424 100644
--- a/src/testing/test_exchange_api_keys_cherry_picking.conf
+++ b/src/testing/test_exchange_api_keys_cherry_picking.conf
@@ -4,179 +4,55 @@
# Persistent data storage for the testcase
TALER_TEST_HOME = test_exchange_api_keys_cherry_picking_home/
-# Persistent data storage
-TALER_DATA_HOME = $TALER_HOME/.local/share/taler/
-
-# Configuration files
-TALER_CONFIG_HOME = $TALER_HOME/.config/taler/
-
-# Cached data, no big deal if lost
-TALER_CACHE_HOME = $TALER_HOME/.cache/taler/
-
[taler]
-# Currency supported by the exchange (can only be one)
CURRENCY = EUR
+CURRENCY_ROUND_UNIT = EUR:0.01
-[auditor]
-BASE_URL = "http://localhost:8083/"
-
-# HTTP port the auditor listens to
-PORT = 8083
+[taler-exchange-secmod-eddsa]
+OVERLAP_DURATION = 1 s
+DURATION = 30 s
+LOOKAHEAD_SIGN = 20 s
[exchange]
-
-KEYDIR = ${TALER_TEST_HOME}/.local/share/taler/exchange/live-keys/
-
-# how long is one signkey valid?
-signkey_duration = 5 seconds
-
-# how long are the signatures with the signkey valid?
-legal_duration = 2 years
-
-# This value causes keys to be *RETURNED* in a /keys response.
-# It's a relative time that materializes always in now+itsvalue.
-# We keep it very high, so as to not introduce divergencies between
-# keys that have been created and keys that are returned along /keys.
-lookahead_provide = 10000 seconds
-
-# This value causes keys to be *CREATED*. The rule is that
-# at any given time there are always N keys whose all the withdraw
-# durations sum up to a time window as big as lookahead_sign.
-lookahead_sign = 60 s
-
-# HTTP port the exchange listens to
+AML_THRESHOLD = EUR:1000000
PORT = 8081
-
-# Master public key used to sign the exchange's various keys
MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
-
-# How to access our database
DB = postgres
-
-# Base URL of the exchange. Must be set to a URL where the
-# exchange (or the twister) is actually listening.
BASE_URL = "http://localhost:8081/"
-
[exchangedb-postgres]
CONFIG = "postgres:///talercheck"
[auditordb-postgres]
CONFIG = "postgres:///talercheck"
-
[exchange-account-1]
-# This is the response we give out for the /wire request. It provides
-# wallets with the bank information for transfers to the exchange.
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/iban.json
-
-# What is the URL of our bank account? Must match WIRE_RESPONSE above!
-PAYTO_URI = payto://x-taler-bank/localhost/42
+PAYTO_URI = "payto://x-taler-bank/localhost/42?receiver-name=42"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
-WIRE_GATEWAY_URL = "http://localhost:9082/42/"
+[exchange-accountcredentials-1]
+WIRE_GATEWAY_URL = "http://localhost:9082/accounts/42/taler-wire-gateway/"
+[admin-accountcredentials-1]
+WIRE_GATEWAY_URL = "http://localhost:9082/accounts/42/taler-wire-gateway/"
[exchange-account-2]
-# This is the response we give out for the /wire request. It provides
-# wallets with the bank information for transfers to the exchange.
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/x-taler-bank.json
-
-# What is the URL of our bank account? Must match WIRE_RESPONSE above!
-PAYTO_URI = payto://x-taler-bank/localhost/2
-
-WIRE_GATEWAY_URL = "http://localhost:9082/2/"
-
-# Authentication information for basic authentication
-TALER_BANK_AUTH_METHOD = "basic"
-USERNAME = user
-PASSWORD = pass
-
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
ENABLE_DEBIT = YES
-
ENABLE_CREDIT = YES
-[bank]
-HTTP_PORT=8082
-
-[fees-x-taler-bank]
-# Fees for the foreseeable future...
-# If you see this after 2017, update to match the next 10 years...
-WIRE-FEE-2017 = EUR:0.01
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
-WIRE-FEE-2027 = EUR:0.01
-WIRE-FEE-2028 = EUR:0.01
-WIRE-FEE-2029 = EUR:0.01
-WIRE-FEE-2030 = EUR:0.01
-WIRE-FEE-2031 = EUR:0.01
-WIRE-FEE-2032 = EUR:0.01
-WIRE-FEE-2033 = EUR:0.01
-WIRE-FEE-2034 = EUR:0.01
-WIRE-FEE-2035 = EUR:0.01
-
-CLOSING-FEE-2017 = EUR:0.01
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-CLOSING-FEE-2027 = EUR:0.01
-CLOSING-FEE-2028 = EUR:0.01
-CLOSING-FEE-2029 = EUR:0.01
-CLOSING-FEE-2030 = EUR:0.01
-CLOSING-FEE-2031 = EUR:0.01
-CLOSING-FEE-2032 = EUR:0.01
-CLOSING-FEE-2033 = EUR:0.01
-CLOSING-FEE-2034 = EUR:0.01
-CLOSING-FEE-2035 = EUR:0.01
-
+[exchange-accountcredentials-2]
+WIRE_GATEWAY_URL = "http://localhost:9082/accounts/2/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
-[fees-iban]
-# Fees for the foreseeable future...
-# If you see this after 2017, update to match the next 10 years...
-WIRE-FEE-2017 = EUR:0.01
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
+[admin-accountcredentials-2]
+WIRE_GATEWAY_URL = "http://localhost:9082/accounts/2/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
-CLOSING-FEE-2017 = EUR:0.01
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-
-[exchangedb]
-duration_overlap = 1 s
-
-[coin_eur_1]
-value = EUR:1
-duration_withdraw = 80 s
-duration_spend = 80 s
-duration_legal = 60 s
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
+[bank]
+HTTP_PORT=8082
diff --git a/src/testing/test_exchange_api_keys_cherry_picking_extended.conf b/src/testing/test_exchange_api_keys_cherry_picking_extended.conf
deleted file mode 100644
index c49f1edd4..000000000
--- a/src/testing/test_exchange_api_keys_cherry_picking_extended.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-@INLINE@ test_exchange_api_keys_cherry_picking.conf
-
-[exchange]
-# Lengthen over original value (60 s)
-LOOKAHEAD_SIGN = 90 s
diff --git a/src/testing/test_exchange_api_keys_cherry_picking_extended_2.conf b/src/testing/test_exchange_api_keys_cherry_picking_extended_2.conf
deleted file mode 100644
index 9ba5c6181..000000000
--- a/src/testing/test_exchange_api_keys_cherry_picking_extended_2.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-@INLINE@ test_exchange_api_keys_cherry_picking_extended.conf
-
-[exchange]
-# Lengthen over firstly extended value (90 s)
-LOOKAHEAD_SIGN = 3000 s
diff --git a/src/testing/test_exchange_api_keys_cherry_picking_home/.config/taler/x-taler-bank.json b/src/testing/test_exchange_api_keys_cherry_picking_home/.config/taler/x-taler-bank.json
deleted file mode 100644
index 21a7500b4..000000000
--- a/src/testing/test_exchange_api_keys_cherry_picking_home/.config/taler/x-taler-bank.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "payto_uri": "payto://x-taler-bank/localhost/2",
- "master_sig": "HEWC1XDS0QZ53YQR451VRKD4N968NXWGZXS30HJ59MJ0PESACK1ZYPYCAT15P08WD58C7D7F6EVN26D59JKA75XEBDQCM8VYFETK82R"
-} \ No newline at end of file
diff --git a/src/testing/test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/offline-keys/master.priv b/src/testing/test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/offline-keys/master.priv
deleted file mode 100644
index 394926938..000000000
--- a/src/testing/test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/offline-keys/master.priv
+++ /dev/null
@@ -1 +0,0 @@
-pÚ^ó-Ú33ˆ€XXÁ!ˆ\0qúýµmUþ_‰ˆ \ No newline at end of file
diff --git a/src/testing/test_exchange_api_overlapping_keys_bug.c b/src/testing/test_exchange_api_overlapping_keys_bug.c
index cfe33d51d..33b547a37 100644
--- a/src/testing/test_exchange_api_overlapping_keys_bug.c
+++ b/src/testing/test_exchange_api_overlapping_keys_bug.c
@@ -31,6 +31,7 @@
#include "taler_exchange_service.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_testing_lib.h>
#include <microhttpd.h>
#include "taler_bank_service.h"
#include "taler_fakebank_lib.h"
@@ -40,24 +41,12 @@
* Configuration file we use. One (big) configuration is used
* for the various components for this test.
*/
-#define CONFIG_FILE "test_exchange_api_keys_cherry_picking.conf"
+static char *config_file;
/**
- * Used to increase the number of denomination keys.
+ * Our credentials.
*/
-#define CONFIG_FILE_EXTENDED \
- "test_exchange_api_keys_cherry_picking_extended.conf"
-
-/**
- * Used to increase the number of denomination keys.
- */
-#define CONFIG_FILE_EXTENDED_2 \
- "test_exchange_api_keys_cherry_picking_extended_2.conf"
-
-/**
- * Exchange configuration data.
- */
-static struct TALER_TESTING_ExchangeConfiguration ec;
+static struct TALER_TESTING_Credentials cred;
/**
@@ -71,17 +60,32 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct TALER_TESTING_Command commands[] = {
- TALER_TESTING_cmd_check_keys ("first-download",
- 1,
- 1),
- /* Causes GET /keys?last_denom_issue=0 */
- TALER_TESTING_cmd_check_keys_with_last_denom ("second-download",
- 3,
- 1,
- GNUNET_TIME_UNIT_ZERO_ABS),
+ TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+ cred.cfg,
+ "exchange-account-2"),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_file,
+ "-e",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_get_exchange ("get-exchange-1",
+ cred.cfg,
+ "get-exchange",
+ true,
+ true),
+ TALER_TESTING_cmd_get_exchange ("get-exchange-2",
+ cred.cfg,
+ NULL,
+ true,
+ true),
TALER_TESTING_cmd_end ()
};
+ (void) cls;
TALER_TESTING_run (is,
commands);
}
@@ -91,40 +95,25 @@ int
main (int argc,
char *const *argv)
{
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test-exchange-api-overlapping-keys-bug",
- "DEBUG", NULL);
- TALER_TESTING_cleanup_files (CONFIG_FILE);
- /* @helpers. Run keyup, create tables, ... Note: it
- * fetches the port number from config in order to see
- * if it's available. */
- switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
- GNUNET_YES,
- &ec))
+ (void) argc;
{
- case GNUNET_SYSERR:
- GNUNET_break (0);
- return 1;
- case GNUNET_NO:
- return 77;
- case GNUNET_OK:
- if (GNUNET_OK !=
- /* Set up event loop and reschedule context, plus
- * start/stop the exchange. It calls TALER_TESTING_setup
- * which creates the 'is' object.
- */
- TALER_TESTING_setup_with_exchange (&run,
- NULL,
- CONFIG_FILE))
- return 1;
- break;
- default:
- GNUNET_break (0);
- return 1;
+ char *cipher;
+
+ cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+ GNUNET_assert (NULL != cipher);
+ GNUNET_asprintf (&config_file,
+ "test_exchange_api_keys_cherry_picking-%s.conf",
+ cipher);
+ GNUNET_free (cipher);
}
- return 0;
+ return TALER_TESTING_main (argv,
+ "INFO",
+ config_file,
+ "exchange-account-2",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
}
diff --git a/src/testing/test_exchange_api_revocation.c b/src/testing/test_exchange_api_revocation.c
index 0f3a8910b..92e36a30a 100644
--- a/src/testing/test_exchange_api_revocation.c
+++ b/src/testing/test_exchange_api_revocation.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014--2020 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
@@ -29,6 +29,7 @@
#include "taler_exchange_service.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_testing_lib.h>
#include <microhttpd.h>
#include "taler_bank_service.h"
#include "taler_fakebank_lib.h"
@@ -38,17 +39,12 @@
* Configuration file we use. One (big) configuration is used
* for the various components for this test.
*/
-#define CONFIG_FILE "test_exchange_api.conf"
+static char *config_file;
/**
- * Exchange configuration data.
+ * Our credentials.
*/
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-/**
- * Bank configuration data.
- */
-static struct TALER_TESTING_BankConfiguration bc;
+static struct TALER_TESTING_Credentials cred;
/**
@@ -62,40 +58,55 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct TALER_TESTING_Command revocation[] = {
+ TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+ cred.cfg,
+ "exchange-account-2"),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_file,
+ "-e",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
/**
* Fill reserve with EUR:10.02, as withdraw fee is 1 ct per
* config.
*/
TALER_TESTING_cmd_admin_add_incoming ("create-reserve-1",
"EUR:10.02",
- &bc.exchange_auth,
- bc.user42_payto),
+ &cred.ba,
+ cred.user42_payto),
TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-1",
"EUR:10.02",
- bc.user42_payto,
- bc.exchange_payto,
+ cred.user42_payto,
+ cred.exchange_payto,
"create-reserve-1"),
/**
* Run wire-watch to trigger the reserve creation.
*/
- TALER_TESTING_cmd_exec_wirewatch ("wirewatch-4",
- CONFIG_FILE),
+ TALER_TESTING_cmd_exec_wirewatch2 ("wirewatch-4",
+ config_file,
+ "exchange-account-2"),
/* Withdraw a 5 EUR coin, at fee of 1 ct */
TALER_TESTING_cmd_withdraw_amount ("withdraw-revocation-coin-1",
"create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/* Withdraw another 5 EUR coin, at fee of 1 ct */
TALER_TESTING_cmd_withdraw_amount ("withdraw-revocation-coin-2",
"create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/* Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in full)
* (merchant would receive EUR:0.99 due to 1 ct deposit fee) *///
TALER_TESTING_cmd_deposit ("deposit-partial",
"withdraw-revocation-coin-1",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:1\"}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
@@ -104,14 +115,15 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("deposit-full",
"withdraw-revocation-coin-2",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:5\"}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:5",
MHD_HTTP_OK),
/**
* Melt SOME of the rest of the coin's value
- * (EUR:3.17 = 3x EUR:1.03 + 7x EUR:0.13) */
+ * (EUR:3.17 = 3x EUR:1.03 + 7x EUR:0.13)
+ */
TALER_TESTING_cmd_melt ("refresh-melt-1",
"withdraw-revocation-coin-1",
MHD_HTTP_OK,
@@ -123,64 +135,63 @@ run (void *cls,
"refresh-melt-1",
MHD_HTTP_OK),
/* Try to recoup before it's allowed */
- TALER_TESTING_cmd_recoup ("recoup-not-allowed",
- MHD_HTTP_NOT_FOUND,
- "refresh-reveal-1#0",
- "refresh-melt-1",
- NULL),
+ TALER_TESTING_cmd_recoup_refresh ("recoup-not-allowed",
+ MHD_HTTP_GONE,
+ "refresh-reveal-1#0",
+ "refresh-melt-1",
+ "EUR:0.1"),
/* Make refreshed coin invalid */
TALER_TESTING_cmd_revoke ("revoke-2-EUR:5",
MHD_HTTP_OK,
"refresh-melt-1",
- CONFIG_FILE),
+ config_file),
/* Also make fully spent coin invalid (should be same denom) */
TALER_TESTING_cmd_revoke ("revoke-2-EUR:5",
MHD_HTTP_OK,
"withdraw-revocation-coin-2",
- CONFIG_FILE),
+ config_file),
/* Refund fully spent coin (which should fail) */
TALER_TESTING_cmd_recoup ("recoup-fully-spent",
MHD_HTTP_CONFLICT,
"withdraw-revocation-coin-2",
- NULL,
- NULL),
+ "EUR:0.1"),
/* Refund coin to original coin */
- TALER_TESTING_cmd_recoup ("recoup-1a",
- MHD_HTTP_OK,
- "refresh-reveal-1#0",
- "refresh-melt-1",
- NULL),
- TALER_TESTING_cmd_recoup ("recoup-1b",
- MHD_HTTP_OK,
- "refresh-reveal-1#1",
- "refresh-melt-1",
- NULL),
- TALER_TESTING_cmd_recoup ("recoup-1c",
- MHD_HTTP_OK,
- "refresh-reveal-1#2",
- "refresh-melt-1",
- NULL),
+ TALER_TESTING_cmd_recoup_refresh ("recoup-1a",
+ MHD_HTTP_OK,
+ "refresh-reveal-1#0",
+ "refresh-melt-1",
+ "EUR:1"),
+ TALER_TESTING_cmd_recoup_refresh ("recoup-1b",
+ MHD_HTTP_OK,
+ "refresh-reveal-1#1",
+ "refresh-melt-1",
+ "EUR:1"),
+ TALER_TESTING_cmd_recoup_refresh ("recoup-1c",
+ MHD_HTTP_OK,
+ "refresh-reveal-1#2",
+ "refresh-melt-1",
+ "EUR:1"),
/* Repeat recoup to test idempotency */
- TALER_TESTING_cmd_recoup ("recoup-1c",
- MHD_HTTP_OK,
- "refresh-reveal-1#2",
- "refresh-melt-1",
- NULL),
- TALER_TESTING_cmd_recoup ("recoup-1c",
- MHD_HTTP_OK,
- "refresh-reveal-1#2",
- "refresh-melt-1",
- NULL),
- TALER_TESTING_cmd_recoup ("recoup-1c",
- MHD_HTTP_OK,
- "refresh-reveal-1#2",
- "refresh-melt-1",
- NULL),
- TALER_TESTING_cmd_recoup ("recoup-1c",
- MHD_HTTP_OK,
- "refresh-reveal-1#2",
- "refresh-melt-1",
- NULL),
+ TALER_TESTING_cmd_recoup_refresh ("recoup-1c",
+ MHD_HTTP_OK,
+ "refresh-reveal-1#2",
+ "refresh-melt-1",
+ "EUR:1"),
+ TALER_TESTING_cmd_recoup_refresh ("recoup-1c",
+ MHD_HTTP_OK,
+ "refresh-reveal-1#2",
+ "refresh-melt-1",
+ "EUR:1"),
+ TALER_TESTING_cmd_recoup_refresh ("recoup-1c",
+ MHD_HTTP_OK,
+ "refresh-reveal-1#2",
+ "refresh-melt-1",
+ "EUR:1"),
+ TALER_TESTING_cmd_recoup_refresh ("recoup-1c",
+ MHD_HTTP_OK,
+ "refresh-reveal-1#2",
+ "refresh-melt-1",
+ "EUR:1"),
/* Now we have EUR:3.83 EUR back after 3x EUR:1 in recoups */
/* Melt original coin AGAIN, but only create one 0.1 EUR coin;
This costs EUR:0.03 in refresh and EUR:01 in withdraw fees,
@@ -200,24 +211,23 @@ run (void *cls,
TALER_TESTING_cmd_revoke ("revoke-3-EUR:0.1",
MHD_HTTP_OK,
"refresh-reveal-2",
- CONFIG_FILE),
+ config_file),
/* Revoke also original coin denomination */
TALER_TESTING_cmd_revoke ("revoke-4-EUR:5",
MHD_HTTP_OK,
"withdraw-revocation-coin-1",
- CONFIG_FILE),
+ config_file),
/* Refund coin EUR:0.1 to original coin, creating zombie! */
- TALER_TESTING_cmd_recoup ("recoup-2",
- MHD_HTTP_OK,
- "refresh-reveal-2",
- "refresh-melt-2",
- NULL),
+ TALER_TESTING_cmd_recoup_refresh ("recoup-2",
+ MHD_HTTP_OK,
+ "refresh-reveal-2",
+ "refresh-melt-2",
+ "EUR:0.1"),
/* Due to recoup, original coin is now at EUR:3.79 */
/* Refund original (now zombie) coin to reserve */
TALER_TESTING_cmd_recoup ("recoup-3",
MHD_HTTP_OK,
"withdraw-revocation-coin-1",
- NULL,
"EUR:3.79"),
/* Check the money is back with the reserve */
TALER_TESTING_cmd_status ("recoup-reserve-status-1",
@@ -227,9 +237,9 @@ run (void *cls,
TALER_TESTING_cmd_end ()
};
- TALER_TESTING_run_with_fakebank (is,
- revocation,
- bc.exchange_auth.wire_gateway_url);
+ (void) cls;
+ TALER_TESTING_run (is,
+ revocation);
}
@@ -237,47 +247,25 @@ int
main (int argc,
char *const *argv)
{
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test-exchange-api-revocation",
- "INFO",
- NULL);
- /* Check fakebank port is available and get config */
- if (GNUNET_OK !=
- TALER_TESTING_prepare_fakebank (CONFIG_FILE,
- "exchange-account-2",
- &bc))
- return 77;
- TALER_TESTING_cleanup_files (CONFIG_FILE);
- /* @helpers. Run keyup, create tables, ... Note: it
- * fetches the port number from config in order to see
- * if it's available. */
- switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
- GNUNET_YES,
- &ec))
+ (void) argc;
{
- case GNUNET_SYSERR:
- GNUNET_break (0);
- return 1;
- case GNUNET_NO:
- return 77;
- case GNUNET_OK:
- if (GNUNET_OK !=
- /* Set up event loop and reschedule context, plus
- * start/stop the exchange. It calls TALER_TESTING_setup
- * which creates the 'is' object.
- */
- TALER_TESTING_setup_with_exchange (&run,
- NULL,
- CONFIG_FILE))
- return 1;
- break;
- default:
- GNUNET_break (0);
- return 1;
+ char *cipher;
+
+ cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+ GNUNET_assert (NULL != cipher);
+ GNUNET_asprintf (&config_file,
+ "test_exchange_api-%s.conf",
+ cipher);
+ GNUNET_free (cipher);
}
- return 0;
+ return TALER_TESTING_main (argv,
+ "INFO",
+ config_file,
+ "exchange-account-2",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
}
diff --git a/src/testing/test_exchange_api_twisted-cs.conf b/src/testing/test_exchange_api_twisted-cs.conf
new file mode 100644
index 000000000..ae953e732
--- /dev/null
+++ b/src/testing/test_exchange_api_twisted-cs.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+@INLINE@ test_exchange_api-cs.conf
+@INLINE@ test_exchange_api-twisted.conf
+
diff --git a/src/testing/test_exchange_api_twisted-rsa.conf b/src/testing/test_exchange_api_twisted-rsa.conf
new file mode 100644
index 000000000..3fd8f4ff1
--- /dev/null
+++ b/src/testing/test_exchange_api_twisted-rsa.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+@INLINE@ test_exchange_api-rsa.conf
+@INLINE@ test_exchange_api-twisted.conf
+
diff --git a/src/testing/test_exchange_api_twisted.c b/src/testing/test_exchange_api_twisted.c
index 146e28de6..75ffe1f15 100644
--- a/src/testing/test_exchange_api_twisted.c
+++ b/src/testing/test_exchange_api_twisted.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 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
@@ -29,33 +29,29 @@
#include "taler_exchange_service.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_testing_lib.h>
#include <microhttpd.h>
#include "taler_bank_service.h"
#include "taler_fakebank_lib.h"
#include "taler_testing_lib.h"
-#include <taler/taler_twister_testing_lib.h>
+#include "taler_twister_testing_lib.h"
#include <taler/taler_twister_service.h>
/**
* Configuration file we use. One (big) configuration is used
* for the various components for this test.
*/
-#define CONFIG_FILE "test_exchange_api_twisted.conf"
+static char *config_file;
/**
- * (real) Twister URL. Used at startup time to check if it runs.
- */
-static char *twister_url;
-
-/**
- * Exchange configuration data.
+ * Our credentials.
*/
-static struct TALER_TESTING_ExchangeConfiguration ec;
+static struct TALER_TESTING_Credentials cred;
/**
- * Bank configuration data.
+ * (real) Twister URL. Used at startup time to check if it runs.
*/
-static struct TALER_TESTING_BankConfiguration bc;
+static char *twister_url;
/**
* Twister process.
@@ -70,9 +66,11 @@ static struct GNUNET_OS_Process *twisterd;
* @param label label to use for the command.
*/
static struct TALER_TESTING_Command
-CMD_EXEC_WIREWATCH (char *label)
+CMD_EXEC_WIREWATCH (const char *label)
{
- return TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE);
+ return TALER_TESTING_cmd_exec_wirewatch2 (label,
+ config_file,
+ "exchange-account-2");
}
@@ -85,12 +83,13 @@ CMD_EXEC_WIREWATCH (char *label)
* @param url exchange_url
*/
static struct TALER_TESTING_Command
-CMD_TRANSFER_TO_EXCHANGE (char *label, char *amount)
+CMD_TRANSFER_TO_EXCHANGE (const char *label,
+ const char *amount)
{
return TALER_TESTING_cmd_admin_add_incoming (label,
amount,
- &bc.exchange_auth,
- bc.user42_payto);
+ &cred.ba,
+ cred.user42_payto);
}
@@ -109,54 +108,49 @@ run (void *cls,
* response from a refresh-reveal operation.
*/
struct TALER_TESTING_Command refresh_409_conflict[] = {
- CMD_TRANSFER_TO_EXCHANGE
- ("refresh-create-reserve",
+ CMD_TRANSFER_TO_EXCHANGE (
+ "refresh-create-reserve",
"EUR:5.01"),
/**
* Make previous command effective.
*/
- CMD_EXEC_WIREWATCH
- ("wirewatch"),
+ CMD_EXEC_WIREWATCH ("wirewatch"),
/**
* Withdraw EUR:5.
*/
- TALER_TESTING_cmd_withdraw_amount
- ("refresh-withdraw-coin",
+ TALER_TESTING_cmd_withdraw_amount (
+ "refresh-withdraw-coin",
"refresh-create-reserve",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
-
- TALER_TESTING_cmd_deposit
- ("refresh-deposit-partial",
+ TALER_TESTING_cmd_deposit (
+ "refresh-deposit-partial",
"refresh-withdraw-coin",
0,
- bc.user42_payto,
- "{\"items\":[{\"name\":\"ice cream\",\
- \"value\":\"EUR:1\"}]}",
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:1\"}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
MHD_HTTP_OK),
-
/**
* Melt the rest of the coin's value
* (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */
- TALER_TESTING_cmd_melt
- ("refresh-melt",
+ TALER_TESTING_cmd_melt (
+ "refresh-melt",
"refresh-withdraw-coin",
MHD_HTTP_OK,
NULL),
/* Trigger 409 Conflict. */
- TALER_TESTING_cmd_flip_upload
- ("flip-upload",
- CONFIG_FILE,
+ TALER_TESTING_cmd_flip_upload (
+ "flip-upload",
+ config_file,
"transfer_privs.0"),
- TALER_TESTING_cmd_refresh_reveal
- ("refresh-(flipped-)reveal",
+ TALER_TESTING_cmd_refresh_reveal (
+ "refresh-(flipped-)reveal",
"refresh-melt",
MHD_HTTP_CONFLICT),
-
TALER_TESTING_cmd_end ()
-
};
@@ -164,112 +158,136 @@ run (void *cls,
* NOTE: not all CMDs actually need the twister,
* so it may be better to move those into the "main"
* lib test suite.
- */struct TALER_TESTING_Command refund[] = {
-
- CMD_TRANSFER_TO_EXCHANGE
- ("create-reserve-r1",
+ */
+ struct TALER_TESTING_Command refund[] = {
+ CMD_TRANSFER_TO_EXCHANGE (
+ "create-reserve-r1",
"EUR:5.01"),
- CMD_EXEC_WIREWATCH
- ("wirewatch-r1"),
- TALER_TESTING_cmd_withdraw_amount
- ("withdraw-coin-r1",
+ CMD_EXEC_WIREWATCH ("wirewatch-r1"),
+ TALER_TESTING_cmd_withdraw_amount (
+ "withdraw-coin-r1",
"create-reserve-r1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
- TALER_TESTING_cmd_deposit
- ("deposit-refund-1",
+ TALER_TESTING_cmd_deposit (
+ "deposit-refund-1",
"withdraw-coin-r1",
0,
- bc.user42_payto,
- "{\"items\":[{\"name\":\"ice cream\","
- "\"value\":\"EUR:5\"}]}",
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:5\"}]}",
GNUNET_TIME_UNIT_MINUTES,
"EUR:5",
MHD_HTTP_OK),
- TALER_TESTING_cmd_refund
- ("refund-currency-mismatch",
- MHD_HTTP_PRECONDITION_FAILED,
- "USD:5",
- "USD:0.01",
- "deposit-refund-1"),
- TALER_TESTING_cmd_refund
- ("refund-fee-above-amount",
- MHD_HTTP_BAD_REQUEST,
- "EUR:5",
- "EUR:10",
- "deposit-refund-1"),
- TALER_TESTING_cmd_flip_upload
- ("flip-upload",
- CONFIG_FILE,
- "merchant_sig"),
- TALER_TESTING_cmd_refund
- ("refund-bad-sig",
- MHD_HTTP_FORBIDDEN,
- "EUR:5",
- "EUR:0.01",
- "deposit-refund-1"),
-
+ TALER_TESTING_cmd_refund ("refund-currency-mismatch",
+ MHD_HTTP_BAD_REQUEST,
+ "USD:5",
+ "deposit-refund-1"),
+ TALER_TESTING_cmd_flip_upload ("flip-upload",
+ config_file,
+ "merchant_sig"),
+ TALER_TESTING_cmd_refund ("refund-bad-sig",
+ MHD_HTTP_FORBIDDEN,
+ "EUR:5",
+ "deposit-refund-1"),
/* This next deposit CMD is only used to provide a
* good merchant signature to the next (failing) refund
* operations. */
-
- TALER_TESTING_cmd_deposit
- ("deposit-refund-to-fail",
+ TALER_TESTING_cmd_deposit (
+ "deposit-refund-to-fail",
"withdraw-coin-r1",
- 0, /* coin index. */
- bc.user42_payto,
+ 0, /* coin index. */
+ cred.user42_payto,
/* This parameter will make any comparison about
h_contract_terms fail, when /refund will be handled.
So in other words, this is h_contract mismatch. */
- "{\"items\":[{\"name\":\"ice skate\","
- "\"value\":\"EUR:5\"}]}",
+ "{\"items\":[{\"name\":\"ice skate\",\"value\":\"EUR:5\"}]}",
GNUNET_TIME_UNIT_MINUTES,
"EUR:5",
MHD_HTTP_CONFLICT),
- TALER_TESTING_cmd_refund
- ("refund-deposit-not-found",
- MHD_HTTP_NOT_FOUND,
- "EUR:5",
- "EUR:0.01",
- "deposit-refund-to-fail"),
- TALER_TESTING_cmd_refund
- ("refund-insufficient-funds",
- MHD_HTTP_PRECONDITION_FAILED,
- "EUR:50",
- "EUR:0.01",
- "deposit-refund-1"),
- TALER_TESTING_cmd_refund
- ("refund-fee-too-low",
- MHD_HTTP_BAD_REQUEST,
+ TALER_TESTING_cmd_refund ("refund-deposit-not-found",
+ MHD_HTTP_NOT_FOUND,
+ "EUR:5",
+ "deposit-refund-to-fail"),
+ TALER_TESTING_cmd_refund ("refund-insufficient-funds",
+ MHD_HTTP_CONFLICT,
+ "EUR:50",
+ "deposit-refund-1"),
+ TALER_TESTING_cmd_end ()
+ };
+
+#if 0
+ /**
+ * Test that we don't get errors when the keys from the exchange
+ * are out of date.
+ */
+ struct TALER_TESTING_Command expired_keys[] = {
+ TALER_TESTING_cmd_modify_header_dl (
+ "modify-expiration",
+ config_file,
+ MHD_HTTP_HEADER_EXPIRES,
+ "Wed, 19 Jan 586524 08:01:49 GMT"),
+ TALER_TESTING_cmd_check_keys_pull_all_keys (
+ "check-keys-expiration-0",
+ 2),
+ /**
+ * Run some normal commands after this to make sure everything is fine.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-r2",
+ "EUR:55.01"),
+ CMD_EXEC_WIREWATCH ("wirewatch-r2"),
+ TALER_TESTING_cmd_withdraw_amount (
+ "withdraw-coin-r2",
+ "create-reserve-r2",
"EUR:5",
- "EUR:0.000001",
- "deposit-refund-1"),
+ 0, /* age restriction off */
+ MHD_HTTP_OK),
TALER_TESTING_cmd_end ()
};
+#endif
struct TALER_TESTING_Command commands[] = {
- TALER_TESTING_cmd_batch ("refresh-reveal-409-conflict",
- refresh_409_conflict),
- TALER_TESTING_cmd_batch ("refund",
- refund),
+ TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+ cred.cfg,
+ "exchange-account-2"),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_file,
+ "-e",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_batch (
+ "refresh-reveal-409-conflict",
+ refresh_409_conflict),
+ TALER_TESTING_cmd_batch (
+ "refund",
+ refund),
+#if 0
+ TALER_TESTING_cmd_batch ("expired-keys",
+ expired_keys),
+#endif
TALER_TESTING_cmd_end ()
};
- TALER_TESTING_run_with_fakebank (is,
- commands,
- bc.exchange_auth.wire_gateway_url);
+ (void) cls;
+ TALER_TESTING_run (is,
+ commands);
}
/**
* Kill, wait, and destroy convenience function.
*
- * @param process process to purge.
+ * @param[in] process process to purge.
*/
static void
purge_process (struct GNUNET_OS_Process *process)
{
- GNUNET_OS_process_kill (process, SIGINT);
+ GNUNET_OS_process_kill (process,
+ SIGINT);
GNUNET_OS_process_wait (process);
GNUNET_OS_process_destroy (process);
}
@@ -279,53 +297,37 @@ int
main (int argc,
char *const *argv)
{
- unsigned int ret;
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test-exchange-api-twisted",
- "DEBUG", NULL);
-
- if (GNUNET_OK !=
- TALER_TESTING_prepare_fakebank (CONFIG_FILE,
- "exchange-account-2",
- &bc))
- return 77;
+ int ret;
- if (NULL == (twister_url = TALER_TWISTER_prepare_twister
- (CONFIG_FILE)))
- return 77;
-
- TALER_TESTING_cleanup_files (CONFIG_FILE);
-
- switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
- &ec))
+ (void) argc;
{
- case GNUNET_SYSERR:
- GNUNET_break (0);
- return 1;
- case GNUNET_NO:
- return 77;
-
- case GNUNET_OK:
-
- if (NULL == (twisterd = TALER_TWISTER_run_twister (CONFIG_FILE)))
- return 77;
-
- ret = TALER_TESTING_setup_with_exchange (&run,
- NULL,
- CONFIG_FILE);
- purge_process (twisterd);
- GNUNET_free (twister_url);
-
- if (GNUNET_OK != ret)
- return 1;
- break;
- default:
- GNUNET_break (0);
- return 1;
+ char *cipher;
+
+ cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+ GNUNET_assert (NULL != cipher);
+ GNUNET_asprintf (&config_file,
+ "test_exchange_api_twisted-%s.conf",
+ cipher);
+ GNUNET_free (cipher);
}
- return 0;
+ /* FIXME: introduce commands for twister! */
+ twister_url = TALER_TWISTER_prepare_twister (config_file);
+ if (NULL == twister_url)
+ return 77;
+ twisterd = TALER_TWISTER_run_twister (config_file);
+ if (NULL == twisterd)
+ return 77;
+ ret = TALER_TESTING_main (argv,
+ "INFO",
+ config_file,
+ "exchange-account-2",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
+ purge_process (twisterd);
+ GNUNET_free (twister_url);
+ return ret;
}
diff --git a/src/testing/test_exchange_api_twisted.conf b/src/testing/test_exchange_api_twisted.conf
deleted file mode 100644
index f93fe912a..000000000
--- a/src/testing/test_exchange_api_twisted.conf
+++ /dev/null
@@ -1,189 +0,0 @@
-# This file is in the public domain.
-#
-
-[PATHS]
-# Persistant data storage for the testcase
-TALER_TEST_HOME = test_exchange_api_home/
-
-
-[taler]
-# Currency supported by the exchange (can only be one)
-CURRENCY = EUR
-CURRENCY_ROUND_UNIT = EUR:0.01
-
-
-[exchange]
-
-# how long is one signkey valid?
-SIGNKEY_DURATION = 4 weeks
-
-# how long are the signatures with the signkey valid?
-LEGAL_DURATION = 2 years
-
-# how long do we provide to clients denomination and signing keys
-# ahead of time?
-LOOKAHEAD_PROVIDE = 4 weeks 1 day
-
-# Keep it short so the test runs fast.
-LOOKAHEAD_SIGN = 12 h
-
-# HTTP port the exchange listens to
-PORT = 8081
-
-# Master public key used to sign the exchange's various keys
-MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
-
-# How to access our database
-DB = postgres
-
-# Base URL of the exchange ('S PROXY). This URL is where the
-# twister listens at, so that it will be able to get all the
-# connection addressed to the exchange. In fact, the presence
-# of the twister is 100% transparent to the test case, as it
-# only seeks the exchange/BASE_URL URL to connect to the exchange.
-BASE_URL = "http://localhost:8888/"
-
-
-[exchangedb-postgres]
-CONFIG = "postgres:///talercheck"
-
-
-[auditor]
-BASE_URL = "http://localhost:8083/"
-
-PORT = 8083
-
-
-[auditordb-postgres]
-CONFIG = "postgres:///talercheck"
-
-[exchange-account-1]
-# What is the URL of our account?
-PAYTO_URI = "payto://x-taler-bank/localhost/42"
-# This is the response we give out for the /wire request. It provides
-# wallets with the bank information for transfers to the exchange.
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json
-WIRE_GATEWAY_URL = "http://localhost:9081/42/"
-WIRE_GATEWAY_AUTH_METHOD = NONE
-
-
-[exchange-account-2]
-PAYTO_URI = payto://x-taler-bank/localhost/2
-WIRE_GATEWAY_URL = "http://localhost:8082/2/"
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-2.json
-WIRE_GATEWAY_AUTH_METHOD = BASIC
-USERNAME = user
-PASSWORD = pass
-ENABLE_DEBIT = YES
-ENABLE_CREDIT = YES
-
-
-[bank]
-HTTP_PORT = 8082
-
-
-[twister]
-# HTTP listen port for twister
-HTTP_PORT = 8888
-SERVE = tcp
-
-# HTTP Destination for twister. The test-Webserver needs
-# to listen on the port used here. Note: no trailing '/'!
-DESTINATION_BASE_URL = "http://localhost:8081"
-
-# Control port for TCP
-# PORT = 8889
-HOSTNAME = localhost
-ACCEPT_FROM = 127.0.0.1;
-ACCEPT_FROM6 = ::1;
-
-# Control port for UNIX
-UNIXPATH = /tmp/taler-service-twister.sock
-UNIX_MATCH_UID = NO
-UNIX_MATCH_GID = YES
-
-# Launching of twister by ARM
-# BINARY = taler-service-twister
-# AUTOSTART = NO
-# FORCESTART = NO
-
-
-[fees-x-taler-bank]
-# Fees for the forseeable future...
-# If you see this after 2017, update to match the next 10 years...
-WIRE-FEE-2018 = EUR:0.01
-WIRE-FEE-2019 = EUR:0.01
-WIRE-FEE-2020 = EUR:0.01
-WIRE-FEE-2021 = EUR:0.01
-WIRE-FEE-2022 = EUR:0.01
-WIRE-FEE-2023 = EUR:0.01
-WIRE-FEE-2024 = EUR:0.01
-WIRE-FEE-2025 = EUR:0.01
-WIRE-FEE-2026 = EUR:0.01
-WIRE-FEE-2027 = EUR:0.01
-
-CLOSING-FEE-2018 = EUR:0.01
-CLOSING-FEE-2019 = EUR:0.01
-CLOSING-FEE-2020 = EUR:0.01
-CLOSING-FEE-2021 = EUR:0.01
-CLOSING-FEE-2022 = EUR:0.01
-CLOSING-FEE-2023 = EUR:0.01
-CLOSING-FEE-2024 = EUR:0.01
-CLOSING-FEE-2025 = EUR:0.01
-CLOSING-FEE-2026 = EUR:0.01
-CLOSING-FEE-2027 = EUR:0.01
-
-[coin_eur_ct_1]
-value = EUR:0.01
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.00
-fee_deposit = EUR:0.00
-fee_refresh = EUR:0.01
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_ct_10]
-value = EUR:0.10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_1]
-value = EUR:1
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_5]
-value = EUR:5
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-
-[coin_eur_10]
-value = EUR:10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
diff --git a/src/testing/test_exchange_management_api.c b/src/testing/test_exchange_management_api.c
new file mode 100644
index 000000000..7cce61b55
--- /dev/null
+++ b/src/testing/test_exchange_management_api.c
@@ -0,0 +1,194 @@
+/*
+ This file is part of TALER
+ 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 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 testing/test_exchange_management_api.c
+ * @brief testcase to test exchange's HTTP /management/ API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_exchange_service.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_testing_lib.h>
+#include <microhttpd.h>
+#include "taler_testing_lib.h"
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+static char *config_file;
+
+/**
+ * Our credentials.
+ */
+static struct TALER_TESTING_Credentials cred;
+
+
+/**
+ * Main function that will tell the interpreter what commands to run.
+ *
+ * @param cls closure
+ * @param is interpreter we use to run commands
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TALER_TESTING_Command commands[] = {
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_file,
+ "-u", "exchange-account-2",
+ "-ae",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_get_auditor ("get-auditor",
+ cred.cfg,
+ true),
+ TALER_TESTING_cmd_auditor_del ("del-auditor-FROM-SETUP",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_auditor_add ("add-auditor-BAD-SIG",
+ MHD_HTTP_FORBIDDEN,
+ true),
+ TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_auditor_add ("add-auditor-OK-idempotent",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_auditor_del ("del-auditor-BAD-SIG",
+ MHD_HTTP_FORBIDDEN,
+ true),
+ TALER_TESTING_cmd_auditor_del ("del-auditor-OK",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_auditor_del ("del-auditor-IDEMPOTENT",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_set_wire_fee ("set-fee",
+ "foo-method",
+ "EUR:1",
+ "EUR:5",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_set_wire_fee ("set-fee-conflicting",
+ "foo-method",
+ "EUR:1",
+ "EUR:1",
+ MHD_HTTP_CONFLICT,
+ false),
+ TALER_TESTING_cmd_set_wire_fee ("set-fee-bad-signature",
+ "bar-method",
+ "EUR:1",
+ "EUR:1",
+ MHD_HTTP_FORBIDDEN,
+ true),
+ TALER_TESTING_cmd_set_wire_fee ("set-fee-other-method",
+ "bar-method",
+ "EUR:1",
+ "EUR:1",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_set_wire_fee ("set-fee-idempotent",
+ "bar-method",
+ "EUR:1",
+ "EUR:1",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_wire_add ("add-wire-account",
+ "payto://x-taler-bank/localhost/42?receiver-name=42",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_wire_add ("add-wire-account-idempotent",
+ "payto://x-taler-bank/localhost/42?receiver-name=42",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_wire_add ("add-wire-account-another",
+ "payto://x-taler-bank/localhost/43?receiver-name=43",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_wire_add ("add-wire-account-bad-signature",
+ "payto://x-taler-bank/localhost/44?receiver-name=44",
+ MHD_HTTP_FORBIDDEN,
+ true),
+ TALER_TESTING_cmd_wire_del ("del-wire-account-not-found",
+ "payto://x-taler-bank/localhost/44?receiver-name=44",
+ MHD_HTTP_NOT_FOUND,
+ false),
+ TALER_TESTING_cmd_wire_del ("del-wire-account-bad-signature",
+ "payto://x-taler-bank/localhost/43?receiver-name=43",
+ MHD_HTTP_FORBIDDEN,
+ true),
+ TALER_TESTING_cmd_wire_del ("del-wire-account-ok",
+ "payto://x-taler-bank/localhost/43?receiver-name=43",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_exec_offline_sign_keys ("download-future-keys",
+ config_file),
+ TALER_TESTING_cmd_get_exchange ("get-exchange-1",
+ cred.cfg,
+ "get-exchange",
+ true,
+ true),
+ TALER_TESTING_cmd_get_exchange ("get-exchange-2",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_end ()
+ };
+
+ (void) cls;
+ TALER_TESTING_run (is,
+ commands);
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ (void) argc;
+ {
+ char *cipher;
+
+ cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+ GNUNET_assert (NULL != cipher);
+ GNUNET_asprintf (&config_file,
+ "test_exchange_api-%s.conf",
+ cipher);
+ GNUNET_free (cipher);
+ }
+ return TALER_TESTING_main (argv,
+ "INFO",
+ config_file,
+ "exchange-account-2",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
+}
+
+
+/* end of test_exchange_management_api.c */
diff --git a/src/testing/test_exchange_p2p.c b/src/testing/test_exchange_p2p.c
new file mode 100644
index 000000000..093730ff2
--- /dev/null
+++ b/src/testing/test_exchange_p2p.c
@@ -0,0 +1,557 @@
+/*
+ This file is part of TALER
+ 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 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 testing/test_exchange_p2p.c
+ * @brief testcase to test exchange's P2P payments
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_attributes.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_testing_lib.h>
+#include <microhttpd.h>
+#include "taler_bank_service.h"
+#include "taler_fakebank_lib.h"
+#include "taler_testing_lib.h"
+#include "taler_extensions.h"
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+static char *config_file;
+
+/**
+ * Our credentials.
+ */
+struct TALER_TESTING_Credentials cred;
+
+/**
+ * Some tests behave differently when using CS as we cannot
+ * reuse the coin private key for different denominations
+ * due to the derivation of it with the /csr values. Hence
+ * some tests behave differently in CS mode, hence this
+ * flag.
+ */
+static bool uses_cs;
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_WIREWATCH(label) \
+ TALER_TESTING_cmd_exec_wirewatch2 (label, config_file, "exchange-account-2")
+
+/**
+ * Execute the taler-exchange-aggregator, closer and transfer commands with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+ TALER_TESTING_cmd_sleep ("sleep-before-aggregator", 2), \
+ TALER_TESTING_cmd_exec_aggregator (label "-aggregator", config_file), \
+ TALER_TESTING_cmd_exec_transfer (label "-transfer", config_file)
+
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ */
+#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
+ TALER_TESTING_cmd_admin_add_incoming (label, amount, \
+ &cred.ba, \
+ cred.user42_payto)
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ * @param is interpreter we use to run commands
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+ /**
+ * Test withdrawal plus spending.
+ */
+ struct TALER_TESTING_Command withdraw[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1",
+ "EUR:5.04"),
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-2",
+ "EUR:5.01"),
+ TALER_TESTING_cmd_reserve_poll ("poll-reserve-1",
+ "create-reserve-1",
+ "EUR:5.04",
+ GNUNET_TIME_UNIT_MINUTES,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-1",
+ "EUR:5.04",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "create-reserve-1"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-2",
+ "EUR:5.01",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "create-reserve-2"),
+ /**
+ * Make a reserve exist, according to the previous
+ * transfer.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-1"),
+ TALER_TESTING_cmd_reserve_poll_finish ("finish-poll-reserve-1",
+ GNUNET_TIME_UNIT_SECONDS,
+ "poll-reserve-1"),
+ /**
+ * Withdraw EUR:5.
+ */
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+ "create-reserve-1",
+ "EUR:5",
+ 0, /* age restriction off */
+ MHD_HTTP_OK),
+ /**
+ * Check the reserve is depleted.
+ */
+ TALER_TESTING_cmd_status ("status-1",
+ "create-reserve-1",
+ "EUR:0.03",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+ struct TALER_TESTING_Command push[] = {
+ TALER_TESTING_cmd_purse_create_with_deposit (
+ "purse-with-deposit-for-delete",
+ MHD_HTTP_OK,
+ "{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}",
+ true, /* upload contract */
+ GNUNET_TIME_UNIT_MINUTES, /* expiration */
+ "withdraw-coin-1",
+ "EUR:1.01",
+ NULL),
+ TALER_TESTING_cmd_purse_delete (
+ "purse-with-deposit-delete",
+ MHD_HTTP_NO_CONTENT,
+ "purse-with-deposit-for-delete"),
+ TALER_TESTING_cmd_purse_create_with_deposit (
+ "purse-with-deposit",
+ MHD_HTTP_OK,
+ "{\"amount\":\"EUR:0.99\",\"summary\":\"ice cream\"}",
+ true, /* upload contract */
+ GNUNET_TIME_UNIT_MINUTES, /* expiration */
+ "withdraw-coin-1",
+ "EUR:1.00",
+ NULL),
+ TALER_TESTING_cmd_purse_poll (
+ "push-poll-purse-before-merge",
+ MHD_HTTP_OK,
+ "purse-with-deposit",
+ "EUR:0.99",
+ true,
+ GNUNET_TIME_UNIT_MINUTES),
+ TALER_TESTING_cmd_contract_get (
+ "push-get-contract",
+ MHD_HTTP_OK,
+ true, /* for merge */
+ "purse-with-deposit"),
+ TALER_TESTING_cmd_purse_merge (
+ "purse-merge-into-reserve",
+ MHD_HTTP_OK,
+ "push-get-contract",
+ "create-reserve-1"),
+ TALER_TESTING_cmd_purse_poll_finish (
+ "push-merge-purse-poll-finish",
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_SECONDS,
+ 5),
+ "push-poll-purse-before-merge"),
+ TALER_TESTING_cmd_status (
+ "push-check-post-merge-reserve-balance-get",
+ "create-reserve-1",
+ "EUR:1.02",
+ MHD_HTTP_OK),
+ /* POST history doesn't yet support P2P transfers */
+ TALER_TESTING_cmd_reserve_history (
+ "push-check-post-merge-reserve-balance-post",
+ "create-reserve-1",
+ "EUR:1.02",
+ MHD_HTTP_OK),
+ /* Test conflicting merge */
+ TALER_TESTING_cmd_purse_merge (
+ "purse-merge-into-reserve",
+ MHD_HTTP_CONFLICT,
+ "push-get-contract",
+ "create-reserve-2"),
+
+ TALER_TESTING_cmd_end ()
+ };
+ struct TALER_TESTING_Command pull[] = {
+ TALER_TESTING_cmd_purse_create_with_reserve (
+ "purse-create-with-reserve",
+ MHD_HTTP_OK,
+ "{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}",
+ true /* upload contract */,
+ true /* pay purse fee */,
+ GNUNET_TIME_UNIT_MINUTES, /* expiration */
+ "create-reserve-1"),
+ TALER_TESTING_cmd_contract_get (
+ "pull-get-contract",
+ MHD_HTTP_OK,
+ false, /* for deposit */
+ "purse-create-with-reserve"),
+ TALER_TESTING_cmd_purse_poll (
+ "pull-poll-purse-before-deposit",
+ MHD_HTTP_OK,
+ "purse-create-with-reserve",
+ "EUR:1",
+ false,
+ GNUNET_TIME_UNIT_MINUTES),
+ TALER_TESTING_cmd_purse_deposit_coins (
+ "purse-deposit-coins",
+ MHD_HTTP_OK,
+ 0 /* min age */,
+ "purse-create-with-reserve",
+ "withdraw-coin-1",
+ "EUR:1.01",
+ NULL),
+ TALER_TESTING_cmd_purse_poll_finish (
+ "pull-deposit-purse-poll-finish",
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_SECONDS,
+ 5),
+ "pull-poll-purse-before-deposit"),
+ TALER_TESTING_cmd_status (
+ "pull-check-post-merge-reserve-balance-get",
+ "create-reserve-1",
+ "EUR:2.02",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_reserve_history (
+ "push-check-post-merge-reserve-balance-post",
+ "create-reserve-1",
+ "EUR:2.02",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_purse_deposit_coins (
+ "purse-deposit-coins-idempotent",
+ MHD_HTTP_OK,
+ 0 /* min age */,
+ "purse-create-with-reserve",
+ "withdraw-coin-1",
+ "EUR:1.01",
+ NULL),
+ /* create 2nd purse for a deposit conflict */
+ TALER_TESTING_cmd_purse_create_with_reserve (
+ "purse-create-with-reserve-2",
+ MHD_HTTP_OK,
+ "{\"amount\":\"EUR:4\",\"summary\":\"beer\"}",
+ true /* upload contract */,
+ true /* pay purse fee */,
+ GNUNET_TIME_UNIT_MINUTES, /* expiration */
+ "create-reserve-1"),
+ TALER_TESTING_cmd_purse_deposit_coins (
+ "purse-deposit-coins-conflict",
+ MHD_HTTP_CONFLICT,
+ 0 /* min age */,
+ "purse-create-with-reserve-2",
+ "withdraw-coin-1",
+ "EUR:4.01",
+ NULL),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command expire[] = {
+ TALER_TESTING_cmd_purse_create_with_reserve (
+ "purse-create-with-reserve-expire",
+ MHD_HTTP_OK,
+ "{\"amount\":\"EUR:2\",\"summary\":\"ice cream\"}",
+ true /* upload contract */,
+ true /* pay purse fee */,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_SECONDS,
+ 1), /* expiration */
+ "create-reserve-1"),
+ TALER_TESTING_cmd_purse_poll (
+ "pull-poll-purse-before-expire",
+ MHD_HTTP_GONE,
+ "purse-create-with-reserve-expire",
+ "EUR:1",
+ false,
+ GNUNET_TIME_UNIT_MINUTES),
+ TALER_TESTING_cmd_purse_create_with_deposit (
+ "purse-with-deposit-expire",
+ MHD_HTTP_OK,
+ "{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}",
+ true, /* upload contract */
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_SECONDS,
+ 1), /* expiration */
+ "withdraw-coin-1",
+ "EUR:1.01",
+ NULL),
+ TALER_TESTING_cmd_purse_poll (
+ "push-poll-purse-before-expire",
+ MHD_HTTP_GONE,
+ "purse-with-deposit-expire",
+ "EUR:1",
+ true,
+ GNUNET_TIME_UNIT_MINUTES),
+ /* This should fail, as too much of the coin
+ is already spend / in a purse */
+ TALER_TESTING_cmd_purse_create_with_deposit (
+ "purse-with-deposit-overspending",
+ MHD_HTTP_CONFLICT,
+ "{\"amount\":\"EUR:2\",\"summary\":\"ice cream\"}",
+ true, /* upload contract */
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_SECONDS,
+ 1), /* expiration */
+ "withdraw-coin-1",
+ "EUR:2.01",
+ NULL),
+ TALER_TESTING_cmd_sleep ("sleep",
+ 2 /* seconds */),
+ TALER_TESTING_cmd_exec_expire ("exec-expire",
+ config_file),
+ TALER_TESTING_cmd_purse_poll_finish (
+ "push-merge-purse-poll-finish-expire",
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_SECONDS,
+ 15),
+ "push-poll-purse-before-expire"),
+ TALER_TESTING_cmd_purse_poll_finish (
+ "pull-deposit-purse-poll-expire-finish",
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_SECONDS,
+ 15),
+ "pull-poll-purse-before-expire"),
+ /* coin was refunded, so now this should be OK */
+ /* This should fail, as too much of the coin
+ is already spend / in a purse */
+ TALER_TESTING_cmd_purse_create_with_deposit (
+ "purse-with-deposit-refunded",
+ MHD_HTTP_OK,
+ "{\"amount\":\"EUR:2\",\"summary\":\"ice cream\"}",
+ true, /* upload contract */
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_SECONDS,
+ 1), /* expiration */
+ "withdraw-coin-1",
+ "EUR:2.01",
+ NULL),
+ TALER_TESTING_cmd_end ()
+ };
+ struct TALER_TESTING_Command reserves[] = {
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-100",
+ "EUR:1.04"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-100",
+ "EUR:1.04",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "create-reserve-100"),
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-101",
+ "EUR:1.04"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-101",
+ "EUR:1.04",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "create-reserve-101"),
+ CMD_EXEC_WIREWATCH ("wirewatch-100"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-100",
+ "create-reserve-100",
+ "EUR:1",
+ 0, /* age restriction off */
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_reserve_open ("reserve-open-101-fail",
+ "create-reserve-101",
+ "EUR:0",
+ GNUNET_TIME_UNIT_YEARS,
+ 5, /* min purses */
+ MHD_HTTP_PAYMENT_REQUIRED,
+ NULL,
+ NULL),
+ TALER_TESTING_cmd_reserve_open ("reserve-open-101-ok-a",
+ "create-reserve-101",
+ "EUR:0.01",
+ GNUNET_TIME_UNIT_MONTHS,
+ 1, /* min purses */
+ MHD_HTTP_OK,
+ NULL,
+ NULL),
+ TALER_TESTING_cmd_status ("status-101-open-paid",
+ "create-reserve-101",
+ "EUR:1.03",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_reserve_open ("reserve-open-101-ok-b",
+ "create-reserve-101",
+ "EUR:0",
+ GNUNET_TIME_UNIT_MONTHS,
+ 2, /* min purses */
+ MHD_HTTP_OK,
+ "withdraw-coin-100",
+ "EUR:0.03", /* 0.02 for the reserve open, 0.01 for deposit fee */
+ NULL,
+ NULL),
+ /* Use purse creation with purse quota here */
+ TALER_TESTING_cmd_purse_create_with_reserve (
+ "purse-create-with-reserve-101-a",
+ MHD_HTTP_OK,
+ "{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}",
+ true /* upload contract */,
+ false /* pay purse fee */,
+ GNUNET_TIME_UNIT_MINUTES, /* expiration */
+ "create-reserve-101"),
+ TALER_TESTING_cmd_purse_create_with_reserve (
+ "purse-create-with-reserve-101-b",
+ MHD_HTTP_OK,
+ "{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}",
+ true /* upload contract */,
+ false /* pay purse fee */,
+ GNUNET_TIME_UNIT_MINUTES, /* expiration */
+ "create-reserve-101"),
+ TALER_TESTING_cmd_purse_create_with_reserve (
+ "purse-create-with-reserve-101-fail",
+ MHD_HTTP_CONFLICT,
+ "{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}",
+ true /* upload contract */,
+ false /* pay purse fee */,
+ GNUNET_TIME_UNIT_MINUTES, /* expiration */
+ "create-reserve-101"),
+ TALER_TESTING_cmd_reserve_get_attestable ("reserve-101-attestable",
+ "create-reserve-101",
+ MHD_HTTP_NOT_FOUND,
+ NULL),
+ TALER_TESTING_cmd_reserve_get_attestable ("reserve-101-attest",
+ "create-reserve-101",
+ MHD_HTTP_NOT_FOUND,
+ "nx-attribute-name",
+ NULL),
+ TALER_TESTING_cmd_oauth ("start-oauth-service",
+ 6666),
+ TALER_TESTING_cmd_reserve_close ("reserve-101-close-kyc",
+ "create-reserve-101",
+ /* 42b => not to origin */
+ "payto://x-taler-bank/localhost/42?receiver-name=42b",
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS),
+
+ TALER_TESTING_cmd_check_kyc_get ("check-kyc-close-pending",
+ "reserve-101-close-kyc",
+ MHD_HTTP_ACCEPTED),
+ TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-close-kyc",
+ "reserve-101-close-kyc",
+ "kyc-provider-test-oauth2",
+ "pass",
+ MHD_HTTP_SEE_OTHER),
+ TALER_TESTING_cmd_check_kyc_get ("check-kyc-close-ok",
+ "reserve-101-close-kyc",
+ MHD_HTTP_NO_CONTENT),
+ /* Now it should pass */
+ TALER_TESTING_cmd_reserve_close ("reserve-101-close",
+ "create-reserve-101",
+ /* 42b => not to origin */
+ "payto://x-taler-bank/localhost/42?receiver-name=42b",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_exec_closer ("close-reserves-101",
+ config_file,
+ "EUR:1.02",
+ "EUR:0.01",
+ "create-reserve-101"),
+ TALER_TESTING_cmd_exec_transfer ("close-reserves-101-transfer",
+ config_file),
+ TALER_TESTING_cmd_status ("reserve-101-closed-status",
+ "create-reserve-101",
+ "EUR:0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command commands[] = {
+ TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+ cred.cfg,
+ "exchange-account-2"),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_file,
+ "-e",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_batch ("withdraw",
+ withdraw),
+ TALER_TESTING_cmd_batch ("push",
+ push),
+ TALER_TESTING_cmd_batch ("pull",
+ pull),
+ TALER_TESTING_cmd_batch ("expire",
+ expire),
+ TALER_TESTING_cmd_batch ("reserves",
+ reserves),
+ /* End the suite. */
+ TALER_TESTING_cmd_end ()
+ };
+
+ (void) cls;
+ TALER_TESTING_run (is,
+ commands);
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ (void) argc;
+ {
+ char *cipher;
+
+ cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+ GNUNET_assert (NULL != cipher);
+ uses_cs = (0 == strcmp (cipher, "cs"));
+ GNUNET_asprintf (&config_file,
+ "test_exchange_api-%s.conf",
+ cipher);
+ GNUNET_free (cipher);
+ }
+ return TALER_TESTING_main (argv,
+ "INFO",
+ config_file,
+ "exchange-account-2",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
+}
+
+
+/* end of test_exchange_p2p.c */
diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c
new file mode 100644
index 000000000..0844c5818
--- /dev/null
+++ b/src/testing/test_kyc_api.c
@@ -0,0 +1,581 @@
+/*
+ This file is part of TALER
+ 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 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 testing/test_kyc_api.c
+ * @brief testcase to test the KYC processes
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_attributes.h"
+#include "taler_signatures.h"
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler_bank_service.h"
+#include "taler_fakebank_lib.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+#define CONFIG_FILE "test_kyc_api.conf"
+
+/**
+ * Our credentials.
+ */
+struct TALER_TESTING_Credentials cred;
+
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_WIREWATCH(label) \
+ TALER_TESTING_cmd_exec_wirewatch2 (label, CONFIG_FILE, "exchange-account-2")
+
+/**
+ * Execute the taler-exchange-aggregator, closer and transfer commands with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+ TALER_TESTING_cmd_sleep (label "-sleep", 1), \
+ TALER_TESTING_cmd_exec_aggregator_with_kyc (label, CONFIG_FILE), \
+ TALER_TESTING_cmd_exec_transfer (label, CONFIG_FILE)
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ */
+#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
+ TALER_TESTING_cmd_admin_add_incoming (label, amount, \
+ &cred.ba, \
+ cred.user42_payto)
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TALER_TESTING_Command withdraw[] = {
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1",
+ "EUR:15.02"),
+ TALER_TESTING_cmd_check_bank_admin_transfer (
+ "check-create-reserve-1",
+ "EUR:15.02",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "create-reserve-1"),
+ CMD_EXEC_WIREWATCH ("wirewatch-1"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-no-kyc",
+ "create-reserve-1",
+ "EUR:10",
+ 0, /* age restriction off */
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+ "create-reserve-1",
+ "EUR:5",
+ 0, /* age restriction off */
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+ /**
+ * Test withdraw with KYC.
+ */
+ struct TALER_TESTING_Command withdraw_kyc[] = {
+ CMD_EXEC_WIREWATCH ("wirewatch-1"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-lacking-kyc",
+ "create-reserve-1",
+ "EUR:5",
+ 0, /* age restriction off */
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS),
+ TALER_TESTING_cmd_check_kyc_get ("check-kyc-withdraw",
+ "withdraw-coin-1-lacking-kyc",
+ MHD_HTTP_ACCEPTED),
+ TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-kyc",
+ "withdraw-coin-1-lacking-kyc",
+ "kyc-provider-test-oauth2",
+ "pass",
+ MHD_HTTP_SEE_OTHER),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-with-kyc",
+ "create-reserve-1",
+ "EUR:5",
+ 0, /* age restriction off */
+ MHD_HTTP_OK),
+ /* Attestations above are bound to the originating *bank* account,
+ not to the reserve (!). Hence, they are NOT found here! */
+ TALER_TESTING_cmd_reserve_get_attestable ("reserve-get-attestable",
+ "create-reserve-1",
+ MHD_HTTP_NOT_FOUND,
+ NULL),
+ TALER_TESTING_cmd_end ()
+ };
+ struct TALER_TESTING_Command spend[] = {
+ TALER_TESTING_cmd_deposit (
+ "deposit-simple",
+ "withdraw-coin-1",
+ 0,
+ cred.user43_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_track_transaction (
+ "track-deposit",
+ "deposit-simple",
+ 0,
+ MHD_HTTP_ACCEPTED,
+ NULL),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command track[] = {
+ CMD_EXEC_AGGREGATOR ("run-aggregator-before-kyc"),
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-no-kyc"),
+ TALER_TESTING_cmd_track_transaction (
+ "track-deposit-kyc-ready",
+ "deposit-simple",
+ 0,
+ MHD_HTTP_ACCEPTED,
+ NULL),
+ TALER_TESTING_cmd_check_kyc_get ("check-kyc-deposit",
+ "track-deposit-kyc-ready",
+ MHD_HTTP_ACCEPTED),
+ TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-kyc-no-service",
+ "track-deposit-kyc-ready",
+ "kyc-provider-test-oauth2",
+ "bad",
+ MHD_HTTP_BAD_GATEWAY),
+ TALER_TESTING_cmd_oauth ("start-oauth-service",
+ 6666),
+ TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-kyc-fail",
+ "track-deposit-kyc-ready",
+ "kyc-provider-test-oauth2",
+ "bad",
+ MHD_HTTP_FORBIDDEN),
+ TALER_TESTING_cmd_check_kyc_get ("check-kyc-deposit-again",
+ "track-deposit-kyc-ready",
+ MHD_HTTP_ACCEPTED),
+ TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-kyc-pass",
+ "track-deposit-kyc-ready",
+ "kyc-provider-test-oauth2",
+ "pass",
+ MHD_HTTP_SEE_OTHER),
+ CMD_EXEC_AGGREGATOR ("run-aggregator-after-kyc"),
+ TALER_TESTING_cmd_check_bank_transfer (
+ "check_bank_transfer-499c",
+ cred.exchange_url,
+ "EUR:4.98",
+ cred.exchange_payto,
+ cred.user43_payto),
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command wallet_kyc[] = {
+ TALER_TESTING_cmd_wallet_kyc_get ("wallet-kyc-fail",
+ NULL,
+ "EUR:1000000",
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS),
+ TALER_TESTING_cmd_check_kyc_get ("check-kyc-wallet",
+ "wallet-kyc-fail",
+ MHD_HTTP_ACCEPTED),
+ TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-wallet-kyc",
+ "wallet-kyc-fail",
+ "kyc-provider-test-oauth2",
+ "pass",
+ MHD_HTTP_SEE_OTHER),
+ TALER_TESTING_cmd_check_kyc_get ("wallet-kyc-check",
+ "wallet-kyc-fail",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_reserve_get_attestable ("wallet-get-attestable",
+ "wallet-kyc-fail",
+ MHD_HTTP_OK,
+ TALER_ATTRIBUTE_FULL_NAME,
+ NULL),
+ TALER_TESTING_cmd_reserve_attest ("wallet-get-attest",
+ "wallet-kyc-fail",
+ MHD_HTTP_OK,
+ TALER_ATTRIBUTE_FULL_NAME,
+ NULL),
+ TALER_TESTING_cmd_end ()
+ };
+
+ /**
+ * Test withdrawal for P2P
+ */
+ struct TALER_TESTING_Command p2p_withdraw[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("p2p_create-reserve-1",
+ "EUR:5.04"),
+ CMD_TRANSFER_TO_EXCHANGE ("p2p_create-reserve-2",
+ "EUR:5.01"),
+ CMD_TRANSFER_TO_EXCHANGE ("p2p_create-reserve-3",
+ "EUR:0.03"),
+ TALER_TESTING_cmd_reserve_poll ("p2p_poll-reserve-1",
+ "p2p_create-reserve-1",
+ "EUR:5.04",
+ GNUNET_TIME_UNIT_MINUTES,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("p2p_check-create-reserve-1",
+ "EUR:5.04",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "p2p_create-reserve-1"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("p2p_check-create-reserve-2",
+ "EUR:5.01",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "p2p_create-reserve-2"),
+ /**
+ * Make a reserve exist, according to the previous
+ * transfer.
+ */
+ CMD_EXEC_WIREWATCH ("p2p_wirewatch-1"),
+ TALER_TESTING_cmd_reserve_poll_finish ("p2p_finish-poll-reserve-1",
+ GNUNET_TIME_UNIT_SECONDS,
+ "p2p_poll-reserve-1"),
+ /**
+ * Withdraw EUR:5.
+ */
+ TALER_TESTING_cmd_withdraw_amount ("p2p_withdraw-coin-1",
+ "p2p_create-reserve-1",
+ "EUR:5",
+ 0, /* age restriction off */
+ MHD_HTTP_OK),
+ /**
+ * Check the reserve is depleted.
+ */
+ TALER_TESTING_cmd_status ("p2p_status-1",
+ "p2p_create-reserve-1",
+ "EUR:0.03",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+ struct TALER_TESTING_Command push[] = {
+ TALER_TESTING_cmd_purse_create_with_deposit (
+ "purse-with-deposit",
+ MHD_HTTP_OK,
+ "{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}",
+ true, /* upload contract */
+ GNUNET_TIME_UNIT_MINUTES, /* expiration */
+ "p2p_withdraw-coin-1",
+ "EUR:1.01",
+ NULL),
+ TALER_TESTING_cmd_coin_history ("coin-history-purse-with-deposit",
+ "p2p_withdraw-coin-1#0",
+ "EUR:3.99",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_purse_poll (
+ "push-poll-purse-before-merge",
+ MHD_HTTP_OK,
+ "purse-with-deposit",
+ "EUR:1",
+ true,
+ GNUNET_TIME_UNIT_MINUTES),
+ TALER_TESTING_cmd_contract_get (
+ "push-get-contract",
+ MHD_HTTP_OK,
+ true, /* for merge */
+ "purse-with-deposit"),
+ TALER_TESTING_cmd_purse_merge (
+ "purse-merge-into-reserve",
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+ "push-get-contract",
+ "p2p_create-reserve-1"),
+ TALER_TESTING_cmd_check_kyc_get ("check-kyc-purse-merge",
+ "purse-merge-into-reserve",
+ MHD_HTTP_ACCEPTED),
+ TALER_TESTING_cmd_proof_kyc_oauth2 ("p2p_proof-kyc",
+ "purse-merge-into-reserve",
+ "kyc-provider-test-oauth2",
+ "pass",
+ MHD_HTTP_SEE_OTHER),
+ TALER_TESTING_cmd_purse_merge (
+ "purse-merge-into-reserve",
+ MHD_HTTP_OK,
+ "push-get-contract",
+ "p2p_create-reserve-1"),
+ TALER_TESTING_cmd_purse_poll_finish (
+ "push-merge-purse-poll-finish",
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_SECONDS,
+ 5),
+ "push-poll-purse-before-merge"),
+ TALER_TESTING_cmd_status (
+ "push-check-post-merge-reserve-balance-get",
+ "p2p_create-reserve-1",
+ "EUR:1.03",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_reserve_history (
+ "push-check-post-merge-reserve-balance-post",
+ "p2p_create-reserve-1",
+ "EUR:1.03",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_end ()
+ };
+ struct TALER_TESTING_Command pull[] = {
+ TALER_TESTING_cmd_purse_create_with_reserve (
+ "purse-create-with-reserve",
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+ "{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}",
+ true /* upload contract */,
+ true /* pay purse fee */,
+ GNUNET_TIME_UNIT_MINUTES, /* expiration */
+ "p2p_create-reserve-3"),
+ TALER_TESTING_cmd_check_kyc_get ("check-kyc-purse-create",
+ "purse-create-with-reserve",
+ MHD_HTTP_ACCEPTED),
+ TALER_TESTING_cmd_proof_kyc_oauth2 ("p2p_proof-kyc-pull",
+ "purse-create-with-reserve",
+ "kyc-provider-test-oauth2",
+ "pass",
+ MHD_HTTP_SEE_OTHER),
+ TALER_TESTING_cmd_purse_create_with_reserve (
+ "purse-create-with-reserve",
+ MHD_HTTP_OK,
+ "{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}",
+ true /* upload contract */,
+ true /* pay purse fee */,
+ GNUNET_TIME_UNIT_MINUTES, /* expiration */
+ "p2p_create-reserve-3"),
+ TALER_TESTING_cmd_contract_get (
+ "pull-get-contract",
+ MHD_HTTP_OK,
+ false, /* for deposit */
+ "purse-create-with-reserve"),
+ TALER_TESTING_cmd_purse_poll (
+ "pull-poll-purse-before-deposit",
+ MHD_HTTP_OK,
+ "purse-create-with-reserve",
+ "EUR:1",
+ false,
+ GNUNET_TIME_UNIT_MINUTES),
+ TALER_TESTING_cmd_purse_deposit_coins (
+ "purse-deposit-coins",
+ MHD_HTTP_OK,
+ 0 /* min age */,
+ "purse-create-with-reserve",
+ "p2p_withdraw-coin-1",
+ "EUR:1.01",
+ NULL),
+ TALER_TESTING_cmd_coin_history ("coin-history-purse-pull-deposit",
+ "p2p_withdraw-coin-1#0",
+ "EUR:2.98",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_purse_poll_finish (
+ "pull-deposit-purse-poll-finish",
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_SECONDS,
+ 5),
+ "pull-poll-purse-before-deposit"),
+ TALER_TESTING_cmd_status (
+ "pull-check-post-merge-reserve-balance-get-2",
+ "p2p_create-reserve-3",
+ "EUR:1.03",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_reserve_history (
+ "push-check-post-merge-reserve-balance-post-2",
+ "p2p_create-reserve-3",
+ "EUR:1.03",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+ struct TALER_TESTING_Command aml[] = {
+ /* Trigger something upon which an AML officer could act */
+ TALER_TESTING_cmd_wallet_kyc_get ("wallet-trigger-kyc-for-aml",
+ NULL,
+ "EUR:1000",
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS),
+ TALER_TESTING_cmd_set_officer ("create-aml-officer-1",
+ NULL,
+ "Peter Falk",
+ true,
+ false),
+ TALER_TESTING_cmd_check_aml_decisions ("check-decisions-none-normal",
+ "create-aml-officer-1",
+ TALER_AML_NORMAL,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_check_aml_decisions ("check-decisions-none-pending",
+ "create-aml-officer-1",
+ TALER_AML_PENDING,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_check_aml_decisions ("check-decisions-none-frozen",
+ "create-aml-officer-1",
+ TALER_AML_FROZEN,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_sleep ("sleep-1a",
+ 1),
+ TALER_TESTING_cmd_set_officer ("create-aml-officer-1-disable",
+ "create-aml-officer-1",
+ "Peter Falk",
+ true,
+ true),
+ /* Test that we are not allowed to take AML decisions as our
+ AML staff account is on read-only */
+ TALER_TESTING_cmd_take_aml_decision ("aml-decide-while-disabled",
+ "create-aml-officer-1",
+ "wallet-trigger-kyc-for-aml",
+ "EUR:10000",
+ "party time",
+ TALER_AML_NORMAL,
+ NULL,
+ MHD_HTTP_FORBIDDEN),
+ /* Check that no decision was taken, but that we are allowed
+ to read this information */
+ TALER_TESTING_cmd_check_aml_decision ("check-aml-decision-empty",
+ "create-aml-officer-1",
+ "aml-decide-while-disabled",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_sleep ("sleep-1b",
+ 1),
+ TALER_TESTING_cmd_set_officer ("create-aml-officer-1-enable",
+ "create-aml-officer-1",
+ "Peter Falk",
+ true,
+ false),
+ TALER_TESTING_cmd_take_aml_decision ("aml-decide",
+ "create-aml-officer-1",
+ "wallet-trigger-kyc-for-aml",
+ "EUR:10000",
+ "party time",
+ TALER_AML_NORMAL,
+ NULL,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_check_aml_decisions ("check-decisions-one-normal",
+ "create-aml-officer-1",
+ TALER_AML_NORMAL,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_check_aml_decisions ("check-decisions-zero-frozen",
+ "create-aml-officer-1",
+ TALER_AML_FROZEN,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_check_aml_decision ("check-aml-decision",
+ "create-aml-officer-1",
+ "aml-decide",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_sleep ("sleep-1c",
+ 1),
+ TALER_TESTING_cmd_take_aml_decision ("aml-decide-freeze",
+ "create-aml-officer-1",
+ "wallet-trigger-kyc-for-aml",
+ "EUR:1000",
+ "party over",
+ TALER_AML_FROZEN,
+ NULL,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_check_aml_decisions ("check-decisions-one-frozen",
+ "create-aml-officer-1",
+ TALER_AML_FROZEN,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_check_aml_decisions ("check-decisions-zero-normal",
+ "create-aml-officer-1",
+ TALER_AML_NORMAL,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_sleep ("sleep-1d",
+ 1),
+ TALER_TESTING_cmd_set_officer ("create-aml-officer-1-disable",
+ "create-aml-officer-1",
+ "Peter Falk",
+ false,
+ true),
+ /* Test that we are NOT allowed to read AML decisions now that
+ our AML staff account is disabled */
+ TALER_TESTING_cmd_check_aml_decision ("check-aml-decision-disabled",
+ "create-aml-officer-1",
+ "aml-decide",
+ MHD_HTTP_FORBIDDEN),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command commands[] = {
+ TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+ cred.cfg,
+ "exchange-account-2"),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ CONFIG_FILE,
+ "-e",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_batch ("withdraw",
+ withdraw),
+ TALER_TESTING_cmd_batch ("spend",
+ spend),
+ TALER_TESTING_cmd_batch ("track",
+ track),
+ TALER_TESTING_cmd_batch ("withdraw-kyc",
+ withdraw_kyc),
+ TALER_TESTING_cmd_batch ("wallet-kyc",
+ wallet_kyc),
+ TALER_TESTING_cmd_batch ("p2p_withdraw",
+ p2p_withdraw),
+ TALER_TESTING_cmd_batch ("push",
+ push),
+ TALER_TESTING_cmd_batch ("pull",
+ pull),
+ TALER_TESTING_cmd_batch ("aml",
+ aml),
+ TALER_TESTING_cmd_end ()
+ };
+
+ (void) cls;
+ TALER_TESTING_run (is,
+ commands);
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ (void) argc;
+ return TALER_TESTING_main (argv,
+ "INFO",
+ CONFIG_FILE,
+ "exchange-account-2",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
+}
+
+
+/* end of test_kyc_api.c */
diff --git a/src/testing/test_kyc_api.conf b/src/testing/test_kyc_api.conf
new file mode 100644
index 000000000..b6bfdb055
--- /dev/null
+++ b/src/testing/test_kyc_api.conf
@@ -0,0 +1,42 @@
+# This file is in the public domain.
+#
+@INLINE@ coins-rsa.conf
+@INLINE@ test_exchange_api.conf
+
+[kyc-provider-test-oauth2]
+COST = 0
+LOGIC = oauth2
+USER_TYPE = INDIVIDUAL
+PROVIDED_CHECKS = DUMMY
+KYC_OAUTH2_VALIDITY = forever
+KYC_OAUTH2_TOKEN_URL = http://localhost:6666/oauth/v2/token
+KYC_OAUTH2_AUTHORIZE_URL = http://localhost:6666/oauth/v2/login
+KYC_OAUTH2_INFO_URL = http://localhost:6666/api/user/me
+KYC_OAUTH2_CLIENT_ID = taler-exchange
+KYC_OAUTH2_CLIENT_SECRET = exchange-secret
+KYC_OAUTH2_POST_URL = http://example.com/
+KYC_OAUTH2_CONVERTER_HELPER = taler-exchange-kyc-oauth2-test-converter.sh
+# "{"full_name":"{{last_name}}, {{first_name}}"}"
+
+[kyc-legitimization-balance-high]
+OPERATION_TYPE = BALANCE
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:8
+
+[kyc-legitimization-deposit-any]
+OPERATION_TYPE = DEPOSIT
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:0
+TIMEFRAME = 1d
+
+[kyc-legitimization-withdraw]
+OPERATION_TYPE = WITHDRAW
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:10
+TIMEFRAME = 1d
+
+[kyc-legitimization-merge]
+OPERATION_TYPE = MERGE
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:0
+TIMEFRAME = 1d
diff --git a/src/testing/test_taler_exchange_aggregator.c b/src/testing/test_taler_exchange_aggregator.c
index eaa621cfe..2d7acc6dc 100644
--- a/src/testing/test_taler_exchange_aggregator.c
+++ b/src/testing/test_taler_exchange_aggregator.c
@@ -31,24 +31,9 @@
/**
- * Helper structure to keep exchange configuration values.
+ * Our credentials.
*/
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-/**
- * Bank configuration data.
- */
-static struct TALER_TESTING_BankConfiguration bc;
-
-/**
- * Contains plugin and session.
- */
-static struct TALER_TESTING_DatabaseConnection dbc;
-
-/**
- * Return value from main().
- */
-static int result;
+struct TALER_TESTING_Credentials cred;
/**
* Name of the configuration file to use.
@@ -71,24 +56,6 @@ static char *config_filename;
/**
- * Function run on shutdown to unload the DB plugin.
- *
- * @param cls NULL
- */
-static void
-unload_db (void *cls)
-{
- (void) cls;
- if (NULL != dbc.plugin)
- {
- dbc.plugin->drop_tables (dbc.plugin->cls);
- TALER_EXCHANGEDB_plugin_unload (dbc.plugin);
- dbc.plugin = NULL;
- }
-}
-
-
-/**
* Collects all the tests.
*/
static void
@@ -96,8 +63,13 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct TALER_TESTING_Command all[] = {
-
- // check no aggregation happens on a empty database
+ TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+ cred.cfg,
+ "exchange-account-1"),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_filename,
+ "-e",
+ NULL),
CMD_EXEC_AGGREGATOR ("run-aggregator-on-empty-db",
config_filename),
TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-on-start"),
@@ -105,9 +77,10 @@ run (void *cls,
/* check aggregation happens on the simplest case:
one deposit into the database. */
TALER_TESTING_cmd_insert_deposit ("do-deposit-1",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
"EUR:0.1"),
@@ -115,25 +88,27 @@ run (void *cls,
config_filename),
TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-1",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.89",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-after-1"),
/* check aggregation accumulates well. */
TALER_TESTING_cmd_insert_deposit ("do-deposit-2a",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
"EUR:0.1"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-2b",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
"EUR:0.1"),
@@ -142,31 +117,34 @@ run (void *cls,
config_filename),
TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-2",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:1.79",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-after-2"),
/* check that different merchants stem different aggregations. */
TALER_TESTING_cmd_insert_deposit ("do-deposit-3a",
- &dbc,
+ cred.cfg,
"bob",
"4",
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
"EUR:0.1"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-3b",
- &dbc,
+ cred.cfg,
"bob",
"5",
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
"EUR:0.1"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-3c",
- &dbc,
+ cred.cfg,
"alice",
"4",
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
"EUR:0.1"),
@@ -174,36 +152,38 @@ run (void *cls,
config_filename),
TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-3a",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.89",
- bc.exchange_payto,
- "payto://x-taler-bank/localhost/4"),
+ cred.exchange_payto,
+ "payto://x-taler-bank/localhost/4?receiver-name=4"),
TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-3b",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.89",
- bc.exchange_payto,
- "payto://x-taler-bank/localhost/4"),
+ cred.exchange_payto,
+ "payto://x-taler-bank/localhost/4?receiver-name=4"),
TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-3c",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.89",
- bc.exchange_payto,
- "payto://x-taler-bank/localhost/5"),
+ cred.exchange_payto,
+ "payto://x-taler-bank/localhost/5?receiver-name=5"),
TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-after-3"),
/* checking that aggregator waits for the deadline. */
TALER_TESTING_cmd_insert_deposit ("do-deposit-4a",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_relative_multiply
(GNUNET_TIME_UNIT_SECONDS,
5),
"EUR:0.2",
"EUR:0.1"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-4b",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_relative_multiply
(GNUNET_TIME_UNIT_SECONDS,
5),
@@ -219,16 +199,17 @@ run (void *cls,
CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-4-delayed",
config_filename),
TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-4",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.19",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
// test picking all deposits at earliest deadline
TALER_TESTING_cmd_insert_deposit ("do-deposit-5a",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_relative_multiply
(GNUNET_TIME_UNIT_SECONDS,
10),
@@ -236,9 +217,10 @@ run (void *cls,
"EUR:0.1"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-5b",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_relative_multiply
(GNUNET_TIME_UNIT_SECONDS,
5),
@@ -254,15 +236,16 @@ run (void *cls,
CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-5-delayed",
config_filename),
TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-5",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.19",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
/* Test NEVER running 'tiny' unless they make up minimum unit */
TALER_TESTING_cmd_insert_deposit ("do-deposit-6a",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:0.102",
"EUR:0.1"),
@@ -271,16 +254,18 @@ run (void *cls,
TALER_TESTING_cmd_check_bank_empty (
"expect-empty-transactions-after-6a-tiny"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-6b",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:0.102",
"EUR:0.1"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-6c",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:0.102",
"EUR:0.1"),
@@ -289,9 +274,10 @@ run (void *cls,
TALER_TESTING_cmd_check_bank_empty (
"expect-empty-transactions-after-6c-tiny"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-6d",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:0.102",
"EUR:0.1"),
@@ -300,25 +286,27 @@ run (void *cls,
TALER_TESTING_cmd_check_bank_empty (
"expect-empty-transactions-after-6d-tiny"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-6e",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:0.112",
"EUR:0.1"),
CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-6e",
config_filename),
TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-6",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.01",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
/* Test profiteering if wire deadline is short */
TALER_TESTING_cmd_insert_deposit ("do-deposit-7a",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:0.109",
"EUR:0.1"),
@@ -327,41 +315,44 @@ run (void *cls,
TALER_TESTING_cmd_check_bank_empty (
"expect-empty-transactions-after-7a-tiny"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-7b",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:0.119",
"EUR:0.1"),
CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-7-profit",
config_filename),
TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-7",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.01",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
/* Now check profit was actually taken */
TALER_TESTING_cmd_insert_deposit ("do-deposit-7c",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:0.122",
"EUR:0.1"),
CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-7-loss",
config_filename),
TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-7",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.01",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
/* Test that aggregation would happen fully if wire deadline is long */
TALER_TESTING_cmd_insert_deposit ("do-deposit-8a",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_relative_multiply
(GNUNET_TIME_UNIT_SECONDS,
5),
@@ -372,9 +363,10 @@ run (void *cls,
TALER_TESTING_cmd_check_bank_empty (
"expect-empty-transactions-after-8a-tiny"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-8b",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_relative_multiply
(GNUNET_TIME_UNIT_SECONDS,
5),
@@ -387,25 +379,27 @@ run (void *cls,
/* now trigger aggregate with large transaction and short deadline */
TALER_TESTING_cmd_insert_deposit ("do-deposit-8c",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:0.122",
"EUR:0.1"),
CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-8",
config_filename),
TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-8",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.03",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
/* Test aggregation with fees and rounding profits. */
TALER_TESTING_cmd_insert_deposit ("do-deposit-9a",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_relative_multiply
(GNUNET_TIME_UNIT_SECONDS,
5),
@@ -416,9 +410,10 @@ run (void *cls,
TALER_TESTING_cmd_check_bank_empty (
"expect-empty-transactions-after-9a-tiny"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-9b",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_relative_multiply
(GNUNET_TIME_UNIT_SECONDS,
5),
@@ -431,9 +426,10 @@ run (void *cls,
/* now trigger aggregate with large transaction and short deadline */
TALER_TESTING_cmd_insert_deposit ("do-deposit-9c",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
+ GNUNET_TIME_timestamp_get (),
GNUNET_TIME_UNIT_ZERO,
"EUR:0.112",
"EUR:0.1"),
@@ -441,58 +437,15 @@ run (void *cls,
config_filename),
/* 0.009 + 0.009 + 0.022 - 0.001 - 0.002 - 0.008 = 0.029 => 0.02 */
TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-9",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.01",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
TALER_TESTING_cmd_end ()
};
- GNUNET_SCHEDULER_add_shutdown (&unload_db,
- NULL);
- TALER_TESTING_run_with_fakebank (is,
- all,
- bc.exchange_auth.wire_gateway_url);
-}
-
-
-/**
- * Prepare database an launch the test.
- *
- * @param cls unused
- * @param cfg our configuration
- * @return #GNUNET_NO if database could not be prepared,
- * otherwise #GNUNET_OK
- */
-static int
-prepare_database (void *cls,
- const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- dbc.plugin = TALER_EXCHANGEDB_plugin_load (cfg);
- if (NULL == dbc.plugin)
- {
- GNUNET_break (0);
- result = 77;
- return GNUNET_NO;
- }
- if (GNUNET_OK !=
- dbc.plugin->create_tables (dbc.plugin->cls))
- {
- GNUNET_break (0);
- TALER_EXCHANGEDB_plugin_unload (dbc.plugin);
- dbc.plugin = NULL;
- result = 77;
- return GNUNET_NO;
- }
- dbc.session = dbc.plugin->get_session (dbc.plugin->cls);
- GNUNET_assert (NULL != dbc.session);
-
- result = TALER_TESTING_setup (&run,
- NULL,
- cfg,
- NULL, // no exchange process handle.
- GNUNET_NO); // do not try to connect to the exchange
- return GNUNET_OK;
+ TALER_TESTING_run (is,
+ all);
}
@@ -501,7 +454,6 @@ main (int argc,
char *const argv[])
{
const char *plugin_name;
- char *testname;
if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
{
@@ -509,50 +461,17 @@ main (int argc,
return -1;
}
plugin_name++;
- (void) GNUNET_asprintf (&testname,
- "test-taler-exchange-aggregator-%s",
- plugin_name);
(void) GNUNET_asprintf (&config_filename,
- "%s.conf",
- testname);
-
- GNUNET_log_setup ("test_taler_exchange_aggregator",
- "DEBUG",
- NULL);
-
- /* these might get in the way */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
-
- TALER_TESTING_cleanup_files (config_filename);
-
- if (GNUNET_OK != TALER_TESTING_prepare_exchange (config_filename,
- GNUNET_YES,
- &ec))
- {
- TALER_LOG_WARNING ("Could not prepare the exchange.\n");
- return 77;
- }
-
- if (GNUNET_OK != TALER_TESTING_prepare_fakebank (config_filename,
- "exchange-account-1",
- &bc))
- {
- TALER_LOG_WARNING ("Could not prepare the fakebank\n");
- return 77;
- }
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_parse_and_run (config_filename,
- &prepare_database,
- NULL))
- {
- TALER_LOG_WARNING ("Could not prepare database for tests.\n");
- return result;
- }
- GNUNET_free (config_filename);
- GNUNET_free (testname);
- return GNUNET_OK == result ? 0 : 1;
+ "test-taler-exchange-aggregator-%s.conf",
+ plugin_name);
+ return TALER_TESTING_main (argv,
+ "INFO",
+ config_filename,
+ "exchange-account-1",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
}
diff --git a/src/testing/test_taler_exchange_httpd_home/.config/taler/account-1.json b/src/testing/test_taler_exchange_httpd_home/.config/taler/account-1.json
deleted file mode 100644
index 21a7500b4..000000000
--- a/src/testing/test_taler_exchange_httpd_home/.config/taler/account-1.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "payto_uri": "payto://x-taler-bank/localhost/2",
- "master_sig": "HEWC1XDS0QZ53YQR451VRKD4N968NXWGZXS30HJ59MJ0PESACK1ZYPYCAT15P08WD58C7D7F6EVN26D59JKA75XEBDQCM8VYFETK82R"
-} \ No newline at end of file
diff --git a/src/testing/test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv b/src/testing/test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv
deleted file mode 100644
index 394926938..000000000
--- a/src/testing/test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv
+++ /dev/null
@@ -1 +0,0 @@
-pÚ^ó-Ú33ˆ€XXÁ!ˆ\0qúýµmUþ_‰ˆ \ No newline at end of file
diff --git a/src/testing/test_taler_exchange_wirewatch.c b/src/testing/test_taler_exchange_wirewatch.c
index fbcf742f5..86b616456 100644
--- a/src/testing/test_taler_exchange_wirewatch.c
+++ b/src/testing/test_taler_exchange_wirewatch.c
@@ -34,14 +34,9 @@
/**
- * Bank configuration data.
+ * Our credentials.
*/
-static struct TALER_TESTING_BankConfiguration bc;
-
-/**
- * Helper structure to keep exchange configuration values.
- */
-static struct TALER_TESTING_ExchangeConfiguration ec;
+static struct TALER_TESTING_Credentials cred;
/**
* Name of the configuration file to use.
@@ -66,8 +61,8 @@ transfer_to_exchange (const char *label,
{
return TALER_TESTING_cmd_admin_add_incoming (label,
amount,
- &bc.exchange_auth,
- bc.user42_payto);
+ &cred.ba,
+ cred.user42_payto);
}
@@ -82,6 +77,19 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct TALER_TESTING_Command all[] = {
+ TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+ cred.cfg,
+ "exchange-account-1"),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_filename,
+ "-e",
+ "-u", "exchange-account-1",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-on-start"),
CMD_EXEC_AGGREGATOR ("run-aggregator-on-empty"),
TALER_TESTING_cmd_exec_wirewatch ("run-wirewatch-on-empty",
@@ -96,8 +104,8 @@ run (void *cls,
TALER_TESTING_cmd_check_bank_admin_transfer (
"clear-good-transfer-to-the-exchange",
"EUR:5",
- bc.user42_payto, // debit
- bc.exchange_payto, // credit
+ cred.user42_payto, // debit
+ cred.exchange_payto, // credit
"run-transfer-good-to-exchange"),
TALER_TESTING_cmd_exec_closer ("run-closer-non-expired-reserve",
@@ -120,17 +128,17 @@ run (void *cls,
CMD_EXEC_AGGREGATOR ("run-closer-on-expired-reserve"),
TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-1",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:4.99",
- bc.exchange_payto,
- bc.user42_payto),
+ cred.exchange_payto,
+ cred.user42_payto),
TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-2"),
TALER_TESTING_cmd_end ()
};
- TALER_TESTING_run_with_fakebank (is,
- all,
- bc.exchange_auth.wire_gateway_url);
+ (void) cls;
+ TALER_TESTING_run (is,
+ all);
}
@@ -138,67 +146,29 @@ int
main (int argc,
char *const argv[])
{
- const char *plugin_name;
-
- /* these might get in the way */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test_taler_exchange_wirewatch",
- "DEBUG",
- NULL);
-
- if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ (void) argc;
{
- GNUNET_break (0);
- return -1;
- }
- plugin_name++;
- {
- char *testname;
-
- GNUNET_asprintf (&testname,
- "test-taler-exchange-wirewatch-%s",
- plugin_name);
+ const char *plugin_name;
+
+ plugin_name = strrchr (argv[0], (int) '-');
+ if (NULL == plugin_name)
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ plugin_name++;
GNUNET_asprintf (&config_filename,
- "%s.conf",
- testname);
- GNUNET_free (testname);
- }
- /* check database is working */
- {
- struct GNUNET_PQ_Context *conn;
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- conn = GNUNET_PQ_connect ("postgres:///talercheck",
- NULL,
- es,
- NULL);
- if (NULL == conn)
- return 77;
- GNUNET_PQ_disconnect (conn);
- }
-
- TALER_TESTING_cleanup_files (config_filename);
- if (GNUNET_OK != TALER_TESTING_prepare_exchange (config_filename,
- GNUNET_YES,
- &ec))
- {
- TALER_LOG_INFO ("Could not prepare the exchange\n");
- return 77;
+ "test-taler-exchange-wirewatch-%s.conf",
+ plugin_name);
}
-
- if (GNUNET_OK !=
- TALER_TESTING_prepare_fakebank (config_filename,
- "exchange-account-1",
- &bc))
- return 77;
-
- return
- (GNUNET_OK == TALER_TESTING_setup_with_exchange (&run,
- NULL,
- config_filename)) ? 0 : 1;
+ return TALER_TESTING_main (argv,
+ "INFO",
+ config_filename,
+ "exchange-account-1",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
}
diff --git a/src/testing/testing_api_cmd_age_withdraw.c b/src/testing/testing_api_cmd_age_withdraw.c
new file mode 100644
index 000000000..6ad22809e
--- /dev/null
+++ b/src/testing/testing_api_cmd_age_withdraw.c
@@ -0,0 +1,756 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_age_withdraw.c
+ * @brief implements the age-withdraw command
+ * @author Özgür Kesim
+ */
+
+#include "platform.h"
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_common.h>
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_extensions.h"
+#include "taler_testing_lib.h"
+
+/*
+ * The output state of coin
+ */
+struct CoinOutputState
+{
+
+ /**
+ * The calculated details during "age-withdraw", for the selected coin.
+ */
+ struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details;
+
+ /**
+ * The (wanted) value of the coin, MUST be the same as input.denom_pub.value;
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Reserve history entry that corresponds to this coin.
+ * Will be of type #TALER_EXCHANGE_RTT_AGEWITHDRAWAL.
+ */
+ struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
+};
+
+/**
+ * State for a "age withdraw" CMD:
+ */
+
+struct AgeWithdrawState
+{
+
+ /**
+ * Interpreter state (during command)
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * The age-withdraw handle
+ */
+ struct TALER_EXCHANGE_AgeWithdrawHandle *handle;
+
+ /**
+ * Exchange base URL. Only used as offered trait.
+ */
+ char *exchange_url;
+
+ /**
+ * URI of the reserve we are withdrawing from.
+ */
+ char *reserve_payto_uri;
+
+ /**
+ * Private key of the reserve we are withdrawing from.
+ */
+ struct TALER_ReservePrivateKeyP reserve_priv;
+
+ /**
+ * Public key of the reserve we are withdrawing from.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Which reserve should we withdraw from?
+ */
+ const char *reserve_reference;
+
+ /**
+ * Expected HTTP response code to the request.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Age mask
+ */
+ struct TALER_AgeMask mask;
+
+ /**
+ * The maximum age we commit to
+ */
+ uint8_t max_age;
+
+ /**
+ * Number of coins to withdraw
+ */
+ size_t num_coins;
+
+ /**
+ * The @e num_coins input that is provided to the
+ * `TALER_EXCHANGE_age_withdraw` API.
+ * Each contains kappa secrets, from which we will have
+ * to disclose kappa-1 in a subsequent age-withdraw-reveal operation.
+ */
+ struct TALER_EXCHANGE_AgeWithdrawCoinInput *coin_inputs;
+
+ /**
+ * The output state of @e num_coins coins, calculated during the
+ * "age-withdraw" operation.
+ */
+ struct CoinOutputState *coin_outputs;
+
+ /**
+ * The index returned by the exchange for the "age-withdraw" operation,
+ * of the kappa coin candidates that we do not disclose and keep.
+ */
+ uint8_t noreveal_index;
+
+ /**
+ * The blinded hashes of the non-revealed (to keep) @e num_coins coins.
+ */
+ const struct TALER_BlindedCoinHashP *blinded_coin_hs;
+
+ /**
+ * The hash of the commitment, needed for the reveal step.
+ */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+ /**
+ * Set to the KYC requirement payto hash *if* the exchange replied with a
+ * request for KYC.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Set to the KYC requirement row *if* the exchange replied with
+ * a request for KYC.
+ */
+ uint64_t requirement_row;
+
+};
+
+/**
+ * Callback for the "age-withdraw" ooperation; It checks that the response
+ * code is expected and store the exchange signature in the state.
+ *
+ * @param cls Closure of type `struct AgeWithdrawState *`
+ * @param response Response details
+ */
+static void
+age_withdraw_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_AgeWithdrawResponse *response)
+{
+ struct AgeWithdrawState *aws = cls;
+ struct TALER_TESTING_Interpreter *is = aws->is;
+
+ aws->handle = NULL;
+ if (aws->expected_response_code != response->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status_with_body (is,
+ response->hr.http_status,
+ aws->expected_response_code,
+ response->hr.reply);
+ return;
+ }
+
+ switch (response->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ aws->noreveal_index = response->details.ok.noreveal_index;
+ aws->h_commitment = response->details.ok.h_commitment;
+
+ GNUNET_assert (aws->num_coins == response->details.ok.num_coins);
+ for (size_t n = 0; n < aws->num_coins; n++)
+ {
+ aws->coin_outputs[n].details = response->details.ok.coin_details[n];
+ TALER_age_commitment_proof_deep_copy (
+ &response->details.ok.coin_details[n].age_commitment_proof,
+ &aws->coin_outputs[n].details.age_commitment_proof);
+ }
+ aws->blinded_coin_hs = response->details.ok.blinded_coin_hs;
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_GONE:
+ /* nothing to check */
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* TODO[oec]: Add this to the response-type and handle it here */
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ default:
+ /* Unsupported status code (by test harness) */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "test command for age-withdraw not support status code %u, body:\n"
+ ">>%s<<\n",
+ response->hr.http_status,
+ json_dumps (response->hr.reply, JSON_INDENT (2)));
+ GNUNET_break (0);
+ break;
+ }
+
+ /* We are done with this command, pick the next one */
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command for age-withdraw.
+ */
+static void
+age_withdraw_run (
+ void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AgeWithdrawState *aws = cls;
+ struct TALER_EXCHANGE_Keys *keys = TALER_TESTING_get_keys (is);
+ const struct TALER_ReservePrivateKeyP *rp;
+ const struct TALER_TESTING_Command *create_reserve;
+ const struct TALER_EXCHANGE_DenomPublicKey *dpk;
+
+ aws->is = is;
+
+ /* Prepare the reserve related data */
+ create_reserve
+ = TALER_TESTING_interpreter_lookup_command (
+ is,
+ aws->reserve_reference);
+
+ if (NULL == create_reserve)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_priv (create_reserve,
+ &rp))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (NULL == aws->exchange_url)
+ aws->exchange_url
+ = GNUNET_strdup (TALER_TESTING_get_exchange_url (is));
+ aws->reserve_priv = *rp;
+ GNUNET_CRYPTO_eddsa_key_get_public (&aws->reserve_priv.eddsa_priv,
+ &aws->reserve_pub.eddsa_pub);
+ aws->reserve_payto_uri
+ = TALER_reserve_make_payto (aws->exchange_url,
+ &aws->reserve_pub);
+
+ aws->coin_inputs = GNUNET_new_array (
+ aws->num_coins,
+ struct TALER_EXCHANGE_AgeWithdrawCoinInput);
+
+ for (unsigned int i = 0; i<aws->num_coins; i++)
+ {
+ struct TALER_EXCHANGE_AgeWithdrawCoinInput *input = &aws->coin_inputs[i];
+ struct CoinOutputState *cos = &aws->coin_outputs[i];
+
+ /* randomly create the secrets for the kappa coin-candidates */
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &input->secrets,
+ sizeof(input->secrets));
+ /* Find denomination */
+ dpk = TALER_TESTING_find_pk (keys,
+ &cos->amount,
+ true); /* _always_ use denominations with age-striction */
+ if (NULL == dpk)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to determine denomination key for amount at %s\n",
+ (NULL != cmd) ? cmd->label : "<retried command>");
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ /* We copy the denomination key, as re-querying /keys
+ * would free the old one. */
+ input->denom_pub = TALER_EXCHANGE_copy_denomination_key (dpk);
+ cos->reserve_history.type = TALER_EXCHANGE_RTT_AGEWITHDRAWAL;
+ GNUNET_assert (0 <=
+ TALER_amount_add (&cos->reserve_history.amount,
+ &cos->amount,
+ &input->denom_pub->fees.withdraw));
+ cos->reserve_history.details.withdraw.fee = input->denom_pub->fees.withdraw;
+ }
+
+ /* Execute the age-withdraw protocol */
+ aws->handle =
+ TALER_EXCHANGE_age_withdraw (
+ TALER_TESTING_interpreter_get_context (is),
+ keys,
+ TALER_TESTING_get_exchange_url (is),
+ rp,
+ aws->num_coins,
+ aws->coin_inputs,
+ aws->max_age,
+ &age_withdraw_cb,
+ aws);
+
+ if (NULL == aws->handle)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "age withdraw" CMD, and possibly cancel a
+ * pending operation thereof
+ *
+ * @param cls Closure of type `struct AgeWithdrawState`
+ * @param cmd The command being freed.
+ */
+static void
+age_withdraw_cleanup (
+ void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AgeWithdrawState *aws = cls;
+
+ if (NULL != aws->handle)
+ {
+ TALER_TESTING_command_incomplete (aws->is,
+ cmd->label);
+ TALER_EXCHANGE_age_withdraw_cancel (aws->handle);
+ aws->handle = NULL;
+ }
+
+ if (NULL != aws->coin_inputs)
+ {
+ for (size_t n = 0; n < aws->num_coins; n++)
+ {
+ struct TALER_EXCHANGE_AgeWithdrawCoinInput *in = &aws->coin_inputs[n];
+ struct CoinOutputState *out = &aws->coin_outputs[n];
+
+ if (NULL != in && NULL != in->denom_pub)
+ {
+ TALER_EXCHANGE_destroy_denomination_key (in->denom_pub);
+ in->denom_pub = NULL;
+ }
+ if (NULL != out)
+ TALER_age_commitment_proof_free (&out->details.age_commitment_proof);
+ }
+ GNUNET_free (aws->coin_inputs);
+ }
+ GNUNET_free (aws->coin_outputs);
+ GNUNET_free (aws->exchange_url);
+ GNUNET_free (aws->reserve_payto_uri);
+ GNUNET_free (aws);
+}
+
+
+/**
+ * Offer internal data of a "age withdraw" CMD state to other commands.
+ *
+ * @param cls Closure of type `struct AgeWithdrawState`
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param idx index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+age_withdraw_traits (
+ void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int idx)
+{
+ struct AgeWithdrawState *aws = cls;
+ uint8_t k = aws->noreveal_index;
+ struct TALER_EXCHANGE_AgeWithdrawCoinInput *in = &aws->coin_inputs[idx];
+ struct CoinOutputState *out = &aws->coin_outputs[idx];
+ struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails *details =
+ &aws->coin_outputs[idx].details;
+ struct TALER_TESTING_Trait traits[] = {
+ /* history entry MUST be first due to response code logic below! */
+ TALER_TESTING_make_trait_reserve_history (idx,
+ &out->reserve_history),
+ TALER_TESTING_make_trait_denom_pub (idx,
+ in->denom_pub),
+ TALER_TESTING_make_trait_reserve_priv (&aws->reserve_priv),
+ TALER_TESTING_make_trait_reserve_pub (&aws->reserve_pub),
+ TALER_TESTING_make_trait_amounts (idx,
+ &out->amount),
+ /* TODO[oec]: add legal requirement to response and handle it here, as well
+ TALER_TESTING_make_trait_legi_requirement_row (&aws->requirement_row),
+ TALER_TESTING_make_trait_h_payto (&aws->h_payto),
+ */
+ TALER_TESTING_make_trait_h_blinded_coin (idx,
+ &aws->blinded_coin_hs[idx]),
+ TALER_TESTING_make_trait_payto_uri (aws->reserve_payto_uri),
+ TALER_TESTING_make_trait_exchange_url (aws->exchange_url),
+ TALER_TESTING_make_trait_coin_priv (idx,
+ &details->coin_priv),
+ TALER_TESTING_make_trait_planchet_secrets (idx,
+ &in->secrets[k]),
+ TALER_TESTING_make_trait_blinding_key (idx,
+ &details->blinding_key),
+ TALER_TESTING_make_trait_exchange_wd_value (idx,
+ &details->alg_values),
+ TALER_TESTING_make_trait_age_commitment_proof (
+ idx,
+ &details->age_commitment_proof),
+ TALER_TESTING_make_trait_h_age_commitment (
+ idx,
+ &details->h_age_commitment),
+ };
+
+ if (idx >= aws->num_coins)
+ return GNUNET_NO;
+
+ return TALER_TESTING_get_trait ((aws->expected_response_code == MHD_HTTP_OK)
+ ? &traits[0] /* we have reserve history */
+ : &traits[1], /* skip reserve history */
+ ret,
+ trait,
+ idx);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_age_withdraw (const char *label,
+ const char *reserve_reference,
+ uint8_t max_age,
+ unsigned int expected_response_code,
+ const char *amount,
+ ...)
+{
+ struct AgeWithdrawState *aws;
+ unsigned int cnt;
+ va_list ap;
+
+ aws = GNUNET_new (struct AgeWithdrawState);
+ aws->reserve_reference = reserve_reference;
+ aws->expected_response_code = expected_response_code;
+ aws->mask = TALER_extensions_get_age_restriction_mask ();
+ aws->max_age = TALER_get_lowest_age (&aws->mask, max_age);
+
+ cnt = 1;
+ va_start (ap, amount);
+ while (NULL != (va_arg (ap, const char *)))
+ cnt++;
+ aws->num_coins = cnt;
+ aws->coin_outputs = GNUNET_new_array (cnt,
+ struct CoinOutputState);
+ va_end (ap);
+ va_start (ap, amount);
+
+ for (unsigned int i = 0; i<aws->num_coins; i++)
+ {
+ struct CoinOutputState *out = &aws->coin_outputs[i];
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount,
+ &out->amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %s\n",
+ amount,
+ label);
+ GNUNET_assert (0);
+ }
+ /* move on to next vararg! */
+ amount = va_arg (ap, const char *);
+ }
+
+ GNUNET_assert (NULL == amount);
+ va_end (ap);
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = aws,
+ .label = label,
+ .run = &age_withdraw_run,
+ .cleanup = &age_withdraw_cleanup,
+ .traits = &age_withdraw_traits,
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * The state for the age-withdraw-reveal operation
+ */
+struct AgeWithdrawRevealState
+{
+ /**
+ * The reference to the CMD resembling the previous call to age-withdraw
+ */
+ const char *age_withdraw_reference;
+
+ /**
+ * The state to the previous age-withdraw command
+ */
+ const struct AgeWithdrawState *aws;
+
+ /**
+ * The expected response code from the call to the
+ * age-withdraw-reveal operation
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Interpreter state (during command)
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * The handle to the reveal-operation
+ */
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *handle;
+
+
+ /**
+ * Number of coins, extracted form the age withdraw command
+ */
+ size_t num_coins;
+
+ /**
+ * The signatures of the @e num_coins coins returned
+ */
+ struct TALER_DenominationSignature *denom_sigs;
+
+};
+
+/*
+ * Callback for the reveal response
+ *
+ * @param cls Closure of type `struct AgeWithdrawRevealState`
+ * @param awr The response
+ */
+static void
+age_withdraw_reveal_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_AgeWithdrawRevealResponse *response)
+{
+ struct AgeWithdrawRevealState *awrs = cls;
+ struct TALER_TESTING_Interpreter *is = awrs->is;
+
+ awrs->handle = NULL;
+ if (awrs->expected_response_code != response->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status_with_body (is,
+ response->hr.http_status,
+ awrs->expected_response_code,
+ response->hr.reply);
+ return;
+ }
+ switch (response->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ const struct AgeWithdrawState *aws = awrs->aws;
+ GNUNET_assert (awrs->num_coins == response->details.ok.num_sigs);
+ awrs->denom_sigs = GNUNET_new_array (awrs->num_coins,
+ struct TALER_DenominationSignature);
+ for (size_t n = 0; n < awrs->num_coins; n++)
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (
+ &awrs->denom_sigs[n],
+ &response->details.ok.blinded_denom_sigs[n],
+ &aws->coin_outputs[n].details.blinding_key,
+ &aws->coin_outputs[n].details.h_coin_pub,
+ &aws->coin_outputs[n].details.alg_values,
+ &aws->coin_inputs[n].denom_pub->key));
+ TALER_denom_sig_free (&awrs->denom_sigs[n]);
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "age-withdraw reveal success!\n");
+ GNUNET_free (awrs->denom_sigs);
+ }
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_FORBIDDEN:
+ /* nothing to check */
+ break;
+ /* TODO[oec]: handle more cases !? */
+ default:
+ /* Unsupported status code (by test harness) */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Age withdraw reveal test command does not support status code %u\n",
+ response->hr.http_status);
+ GNUNET_break (0);
+ break;
+ }
+
+ /* We are done with this command, pick the next one */
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command for age-withdraw-reveal
+ */
+static void
+age_withdraw_reveal_run (
+ void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AgeWithdrawRevealState *awrs = cls;
+ const struct TALER_TESTING_Command *age_withdraw_cmd;
+ const struct AgeWithdrawState *aws;
+
+ (void) cmd;
+ awrs->is = is;
+
+ /*
+ * Get the command and state for the previous call to "age witdraw"
+ */
+ age_withdraw_cmd =
+ TALER_TESTING_interpreter_lookup_command (is,
+ awrs->age_withdraw_reference);
+ if (NULL == age_withdraw_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (age_withdraw_cmd->run == age_withdraw_run);
+ aws = age_withdraw_cmd->cls;
+ awrs->aws = aws;
+ awrs->num_coins = aws->num_coins;
+
+ awrs->handle =
+ TALER_EXCHANGE_age_withdraw_reveal (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ aws->num_coins,
+ aws->coin_inputs,
+ aws->noreveal_index,
+ &aws->h_commitment,
+ &aws->reserve_pub,
+ age_withdraw_reveal_cb,
+ awrs);
+}
+
+
+/**
+ * Free the state of a "age-withdraw-reveal" CMD, and possibly
+ * cancel a pending operation thereof
+ *
+ * @param cls Closure of type `struct AgeWithdrawRevealState`
+ * @param cmd The command being freed.
+ */
+static void
+age_withdraw_reveal_cleanup (
+ void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AgeWithdrawRevealState *awrs = cls;
+
+ if (NULL != awrs->handle)
+ {
+ TALER_TESTING_command_incomplete (awrs->is,
+ cmd->label);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrs->handle);
+ awrs->handle = NULL;
+ }
+ GNUNET_free (awrs->denom_sigs);
+ awrs->denom_sigs = NULL;
+ GNUNET_free (awrs);
+}
+
+
+/**
+ * Offer internal data of a "age withdraw reveal" CMD state to other commands.
+ *
+ * @param cls Closure of they `struct AgeWithdrawRevealState`
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param idx index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+age_withdraw_reveal_traits (
+ void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int idx)
+{
+ struct AgeWithdrawRevealState *awrs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_denom_sig (idx,
+ &awrs->denom_sigs[idx]),
+ /* FIXME: shall we provide the traits from the previous
+ * call to "age withdraw" as well? */
+ };
+
+ if (idx >= awrs->num_coins)
+ return GNUNET_NO;
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ idx);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_age_withdraw_reveal (
+ const char *label,
+ const char *age_withdraw_reference,
+ unsigned int expected_response_code)
+{
+ struct AgeWithdrawRevealState *awrs =
+ GNUNET_new (struct AgeWithdrawRevealState);
+
+ awrs->age_withdraw_reference = age_withdraw_reference;
+ awrs->expected_response_code = expected_response_code;
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = awrs,
+ .label = label,
+ .run = age_withdraw_reveal_run,
+ .cleanup = age_withdraw_reveal_cleanup,
+ .traits = age_withdraw_reveal_traits,
+ };
+
+ return cmd;
+}
+
+
+/* end of testing_api_cmd_age_withdraw.c */
diff --git a/src/testing/testing_api_cmd_auditor_add.c b/src/testing/testing_api_cmd_auditor_add.c
new file mode 100644
index 000000000..ac9c2c8fb
--- /dev/null
+++ b/src/testing/testing_api_cmd_auditor_add.c
@@ -0,0 +1,224 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 testing/testing_api_cmd_auditor_add.c
+ * @brief command for testing auditor_add
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "auditor_add" CMD.
+ */
+struct AuditorAddState
+{
+
+ /**
+ * Auditor enable handle while operation is running.
+ */
+ struct TALER_EXCHANGE_ManagementAuditorEnableHandle *dh;
+
+ /**
+ * Our interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Should we make the request with a bad master_sig signature?
+ */
+ bool bad_sig;
+};
+
+
+/**
+ * Callback to analyze the /management/auditors response, just used to check
+ * if the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param aer response details
+ */
+static void
+auditor_add_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementAuditorEnableResponse *aer)
+{
+ struct AuditorAddState *ds = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &aer->hr;
+
+ ds->dh = NULL;
+ if (ds->expected_response_code != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ ds->expected_response_code);
+ return;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+auditor_add_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AuditorAddState *ds = cls;
+ struct GNUNET_TIME_Timestamp now;
+ struct TALER_MasterSignatureP master_sig;
+ const struct TALER_AuditorPublicKeyP *auditor_pub;
+ const struct TALER_TESTING_Command *auditor_cmd;
+ const struct TALER_TESTING_Command *exchange_cmd;
+ const char *exchange_url;
+ const char *auditor_url;
+
+ (void) cmd;
+ now = GNUNET_TIME_timestamp_get ();
+ ds->is = is;
+
+ auditor_cmd = TALER_TESTING_interpreter_get_command (is,
+ "auditor");
+ if (NULL == auditor_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_auditor_pub (auditor_cmd,
+ &auditor_pub));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_auditor_url (auditor_cmd,
+ &auditor_url));
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+
+
+ if (ds->bad_sig)
+ {
+ memset (&master_sig,
+ 42,
+ sizeof (master_sig));
+ }
+ else
+ {
+ const struct TALER_MasterPrivateKeyP *master_priv;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_master_priv (exchange_cmd,
+ &master_priv));
+ TALER_exchange_offline_auditor_add_sign (auditor_pub,
+ auditor_url,
+ now,
+ master_priv,
+ &master_sig);
+ }
+ ds->dh = TALER_EXCHANGE_management_enable_auditor (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ auditor_pub,
+ auditor_url,
+ "test-case auditor", /* human-readable auditor name */
+ now,
+ &master_sig,
+ &auditor_add_cb,
+ ds);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "auditor_add" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct AuditorAddState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+auditor_add_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AuditorAddState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_management_enable_auditor_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ GNUNET_free (ds);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_auditor_add (const char *label,
+ unsigned int expected_http_status,
+ bool bad_sig)
+{
+ struct AuditorAddState *ds;
+
+ ds = GNUNET_new (struct AuditorAddState);
+ ds->expected_response_code = expected_http_status;
+ ds->bad_sig = bad_sig;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &auditor_add_run,
+ .cleanup = &auditor_add_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_auditor_add.c */
diff --git a/src/testing/testing_api_cmd_auditor_add_denom_sig.c b/src/testing/testing_api_cmd_auditor_add_denom_sig.c
new file mode 100644
index 000000000..6b7776896
--- /dev/null
+++ b/src/testing/testing_api_cmd_auditor_add_denom_sig.c
@@ -0,0 +1,254 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 testing/testing_api_cmd_auditor_add_denom_sig.c
+ * @brief command for testing POST to /auditor/$AUDITOR_PUB/$H_DENOM_PUB
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "auditor_add" CMD.
+ */
+struct AuditorAddDenomSigState
+{
+
+ /**
+ * Auditor enable handle while operation is running.
+ */
+ struct TALER_EXCHANGE_AuditorAddDenominationHandle *dh;
+
+ /**
+ * Our interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Reference to command identifying denomination to add.
+ */
+ const char *denom_ref;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Should we make the request with a bad master_sig signature?
+ */
+ bool bad_sig;
+};
+
+
+/**
+ * Callback to analyze the /management/auditor response, just used to check
+ * if the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param adr response details
+ */
+static void
+denom_sig_add_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_AuditorAddDenominationResponse *adr)
+{
+ struct AuditorAddDenomSigState *ds = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &adr->hr;
+
+ ds->dh = NULL;
+ if (ds->expected_response_code != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ ds->expected_response_code);
+ return;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+auditor_add_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AuditorAddDenomSigState *ds = cls;
+ struct TALER_AuditorSignatureP auditor_sig;
+ struct TALER_DenominationHashP h_denom_pub;
+ const struct TALER_EXCHANGE_DenomPublicKey *dk;
+ const struct TALER_AuditorPublicKeyP *auditor_pub;
+ const struct TALER_TESTING_Command *auditor_cmd;
+ const struct TALER_TESTING_Command *exchange_cmd;
+ const char *exchange_url;
+ const char *auditor_url;
+
+ (void) cmd;
+ /* Get denom pub from trait */
+ {
+ const struct TALER_TESTING_Command *denom_cmd;
+
+ denom_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ ds->denom_ref);
+ if (NULL == denom_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_denom_pub (denom_cmd,
+ 0,
+ &dk));
+ }
+ ds->is = is;
+ auditor_cmd = TALER_TESTING_interpreter_get_command (is,
+ "auditor");
+ if (NULL == auditor_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_auditor_pub (auditor_cmd,
+ &auditor_pub));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_auditor_url (auditor_cmd,
+ &auditor_url));
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ if (ds->bad_sig)
+ {
+ memset (&auditor_sig,
+ 42,
+ sizeof (auditor_sig));
+ }
+ else
+ {
+ const struct TALER_MasterPublicKeyP *master_pub;
+ const struct TALER_AuditorPrivateKeyP *auditor_priv;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_master_pub (exchange_cmd,
+ &master_pub));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_auditor_priv (auditor_cmd,
+ &auditor_priv));
+ TALER_auditor_denom_validity_sign (
+ auditor_url,
+ &dk->h_key,
+ master_pub,
+ dk->valid_from,
+ dk->withdraw_valid_until,
+ dk->expire_deposit,
+ dk->expire_legal,
+ &dk->value,
+ &dk->fees,
+ auditor_priv,
+ &auditor_sig);
+ }
+ ds->dh = TALER_EXCHANGE_add_auditor_denomination (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ &h_denom_pub,
+ auditor_pub,
+ &auditor_sig,
+ &denom_sig_add_cb,
+ ds);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "auditor_add" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct AuditorAddDenomSigState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+auditor_add_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AuditorAddDenomSigState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_add_auditor_denomination_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ GNUNET_free (ds);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_auditor_add_denom_sig (const char *label,
+ unsigned int expected_http_status,
+ const char *denom_ref,
+ bool bad_sig)
+{
+ struct AuditorAddDenomSigState *ds;
+
+ ds = GNUNET_new (struct AuditorAddDenomSigState);
+ ds->expected_response_code = expected_http_status;
+ ds->bad_sig = bad_sig;
+ ds->denom_ref = denom_ref;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &auditor_add_run,
+ .cleanup = &auditor_add_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_auditor_add_denom_sig.c */
diff --git a/src/testing/testing_api_cmd_auditor_del.c b/src/testing/testing_api_cmd_auditor_del.c
new file mode 100644
index 000000000..8256bc1c7
--- /dev/null
+++ b/src/testing/testing_api_cmd_auditor_del.c
@@ -0,0 +1,215 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 testing/testing_api_cmd_auditor_del.c
+ * @brief command for testing /management/auditor/disable.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "auditor_del" CMD.
+ */
+struct AuditorDelState
+{
+
+ /**
+ * Auditor enable handle while operation is running.
+ */
+ struct TALER_EXCHANGE_ManagementAuditorDisableHandle *dh;
+
+ /**
+ * Our interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Should we make the request with a bad master_sig signature?
+ */
+ bool bad_sig;
+};
+
+
+/**
+ * Callback to analyze the /management/auditors response, just used to check
+ * if the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param adr response details
+ */
+static void
+auditor_del_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementAuditorDisableResponse *adr)
+
+{
+ struct AuditorDelState *ds = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &adr->hr;
+
+ ds->dh = NULL;
+ if (ds->expected_response_code != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ ds->expected_response_code);
+ return;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+auditor_del_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AuditorDelState *ds = cls;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_TIME_Timestamp now;
+ const struct TALER_AuditorPublicKeyP *auditor_pub;
+ const struct TALER_TESTING_Command *auditor_cmd;
+ const struct TALER_TESTING_Command *exchange_cmd;
+ const char *exchange_url;
+
+ (void) cmd;
+ now = GNUNET_TIME_timestamp_get ();
+ ds->is = is;
+ auditor_cmd = TALER_TESTING_interpreter_get_command (is,
+ "auditor");
+ if (NULL == auditor_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_auditor_pub (auditor_cmd,
+ &auditor_pub));
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ if (ds->bad_sig)
+ {
+ memset (&master_sig,
+ 42,
+ sizeof (master_sig));
+ }
+ else
+ {
+ const struct TALER_MasterPrivateKeyP *master_priv;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_master_priv (exchange_cmd,
+ &master_priv));
+ TALER_exchange_offline_auditor_del_sign (auditor_pub,
+ now,
+ master_priv,
+ &master_sig);
+ }
+ ds->dh = TALER_EXCHANGE_management_disable_auditor (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ auditor_pub,
+ now,
+ &master_sig,
+ &auditor_del_cb,
+ ds);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "auditor_del" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct AuditorDelState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+auditor_del_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AuditorDelState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_management_disable_auditor_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ GNUNET_free (ds);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_auditor_del (const char *label,
+ unsigned int expected_http_status,
+ bool bad_sig)
+{
+ struct AuditorDelState *ds;
+
+ ds = GNUNET_new (struct AuditorDelState);
+ ds->expected_response_code = expected_http_status;
+ ds->bad_sig = bad_sig;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &auditor_del_run,
+ .cleanup = &auditor_del_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_auditor_del.c */
diff --git a/src/testing/testing_api_cmd_auditor_deposit_confirmation.c b/src/testing/testing_api_cmd_auditor_deposit_confirmation.c
index 31c6e850e..9477a5d7e 100644
--- a/src/testing/testing_api_cmd_auditor_deposit_confirmation.c
+++ b/src/testing/testing_api_cmd_auditor_deposit_confirmation.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018 Taler Systems SA
+ Copyright (C) 2018-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
@@ -59,9 +59,9 @@ struct DepositConfirmationState
const char *amount_without_fee;
/**
- * Which coin of the @e deposit_reference should we confirm.
+ * How many coins were there in the @e deposit_reference?
*/
- unsigned int coin_index;
+ unsigned int num_coins;
/**
* DepositConfirmation handle while operation is running.
@@ -69,11 +69,6 @@ struct DepositConfirmationState
struct TALER_AUDITOR_DepositConfirmationHandle *dc;
/**
- * Auditor connection.
- */
- struct TALER_AUDITOR_Handle *auditor;
-
- /**
* Interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
@@ -125,8 +120,7 @@ do_retry (void *cls)
struct DepositConfirmationState *dcs = cls;
dcs->retry_task = NULL;
- dcs->is->commands[dcs->is->ip].last_req_time
- = GNUNET_TIME_absolute_get ();
+ TALER_TESTING_touch_cmd (dcs->is);
deposit_confirmation_run (dcs,
NULL,
dcs->is);
@@ -138,53 +132,46 @@ do_retry (void *cls)
* to check if the response code is acceptable.
*
* @param cls closure.
- * @param http_status HTTP response code.
- * @param ec taler-specific error code.
- * @param obj raw response from the auditor.
+ * @param dcr response details
*/
static void
-deposit_confirmation_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const json_t *obj)
+deposit_confirmation_cb (
+ void *cls,
+ const struct TALER_AUDITOR_DepositConfirmationResponse *dcr)
{
struct DepositConfirmationState *dcs = cls;
+ const struct TALER_AUDITOR_HttpResponse *hr = &dcr->hr;
dcs->dc = NULL;
- if (dcs->expected_response_code != http_status)
+ if (dcs->expected_response_code != hr->http_status)
{
if (0 != dcs->do_retry)
{
dcs->do_retry--;
- if ( (0 == http_status) ||
- (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
- (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
+ if ( (0 == hr->http_status) ||
+ (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec) ||
+ (MHD_HTTP_INTERNAL_SERVER_ERROR == hr->http_status) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Retrying deposit confirmation failed with %u/%d\n",
- http_status,
- (int) ec);
+ hr->http_status,
+ (int) hr->ec);
/* on DB conflicts, do not use backoff */
- if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
+ if (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec)
dcs->backoff = GNUNET_TIME_UNIT_ZERO;
else
dcs->backoff = GNUNET_TIME_randomized_backoff (dcs->backoff,
MAX_BACKOFF);
- dcs->is->commands[dcs->is->ip].num_tries++;
+ TALER_TESTING_inc_tries (dcs->is);
dcs->retry_task = GNUNET_SCHEDULER_add_delayed (dcs->backoff,
&do_retry,
dcs);
return;
}
}
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- http_status,
- dcs->is->commands[dcs->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (obj, stderr, 0);
- TALER_TESTING_interpreter_fail (dcs->is);
+ TALER_TESTING_unexpected_status (dcs->is,
+ hr->http_status,
+ dcs->expected_response_code);
return;
}
TALER_TESTING_interpreter_next (dcs->is);
@@ -203,27 +190,54 @@ deposit_confirmation_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
{
+ static struct TALER_ExtensionPolicyHashP no_h_policy;
struct DepositConfirmationState *dcs = cls;
const struct TALER_TESTING_Command *deposit_cmd;
- struct GNUNET_HashCode h_wire;
- struct GNUNET_HashCode h_contract_terms;
- struct GNUNET_TIME_Absolute timestamp;
- struct GNUNET_TIME_Absolute refund_deadline;
+ struct TALER_MerchantWireHashP h_wire;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ const struct GNUNET_TIME_Timestamp *exchange_timestamp = NULL;
+ struct GNUNET_TIME_Timestamp timestamp;
+ const struct GNUNET_TIME_Timestamp *wire_deadline;
+ struct GNUNET_TIME_Timestamp refund_deadline
+ = GNUNET_TIME_UNIT_ZERO_TS;
struct TALER_Amount amount_without_fee;
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendPublicKeyP coin_pubs[dcs->num_coins];
+ const struct TALER_CoinSpendPublicKeyP *coin_pubps[dcs->num_coins];
+ const struct TALER_CoinSpendSignatureP *coin_sigps[dcs->num_coins];
const struct TALER_MerchantPrivateKeyP *merchant_priv;
struct TALER_MerchantPublicKeyP merchant_pub;
const struct TALER_ExchangePublicKeyP *exchange_pub;
const struct TALER_ExchangeSignatureP *exchange_sig;
const json_t *wire_details;
const json_t *contract_terms;
- const struct TALER_CoinSpendPrivateKeyP *coin_priv;
const struct TALER_EXCHANGE_Keys *keys;
const struct TALER_EXCHANGE_SigningPublicKey *spk;
+ const char *auditor_url;
(void) cmd;
dcs->is = is;
GNUNET_assert (NULL != dcs->deposit_reference);
+ {
+ const struct TALER_TESTING_Command *auditor_cmd;
+
+ auditor_cmd
+ = TALER_TESTING_interpreter_get_command (is,
+ "auditor");
+ if (NULL == auditor_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_auditor_url (auditor_cmd,
+ &auditor_url))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ }
deposit_cmd
= TALER_TESTING_interpreter_lookup_command (is,
dcs->deposit_reference);
@@ -236,52 +250,74 @@ deposit_confirmation_run (void *cls,
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_exchange_pub (deposit_cmd,
- dcs->coin_index,
+ 0,
&exchange_pub));
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_exchange_sig (deposit_cmd,
- dcs->coin_index,
+ 0,
&exchange_sig));
- keys = TALER_EXCHANGE_get_keys (dcs->is->exchange);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_timestamp (deposit_cmd,
+ 0,
+ &exchange_timestamp));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_wire_deadline (deposit_cmd,
+ 0,
+ &wire_deadline));
+ GNUNET_assert (NULL != exchange_timestamp);
+ keys = TALER_TESTING_get_keys (is);
GNUNET_assert (NULL != keys);
spk = TALER_EXCHANGE_get_signing_key_info (keys,
exchange_pub);
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_contract_terms (deposit_cmd,
- dcs->coin_index,
&contract_terms));
/* Very unlikely to fail */
GNUNET_assert (NULL != contract_terms);
GNUNET_assert (GNUNET_OK ==
- TALER_JSON_hash (contract_terms,
- &h_contract_terms));
+ TALER_JSON_contract_hash (contract_terms,
+ &h_contract_terms));
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_wire_details (deposit_cmd,
- dcs->coin_index,
&wire_details));
GNUNET_assert (GNUNET_OK ==
TALER_JSON_merchant_wire_signature_hash (wire_details,
&h_wire));
- GNUNET_assert (GNUNET_OK ==
- TALER_TESTING_get_trait_coin_priv (deposit_cmd,
- dcs->coin_index,
- &coin_priv));
- GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
- &coin_pub.eddsa_pub);
+
+ for (unsigned int i = 0; i<dcs->num_coins; i++)
+ {
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_coin_priv (deposit_cmd,
+ i,
+ &coin_priv));
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+ &coin_pubs[i].eddsa_pub);
+ coin_pubps[i] = &coin_pubs[i];
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_coin_sig (deposit_cmd,
+ i,
+ &coin_sigps[i]));
+ }
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_merchant_priv (deposit_cmd,
- dcs->coin_index,
&merchant_priv));
GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
&merchant_pub.eddsa_pub);
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (dcs->amount_without_fee,
&amount_without_fee));
- /* timestamp is mandatory */
{
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_absolute_time ("timestamp", &timestamp),
+ /* timestamp is mandatory */
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &timestamp),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("refund_deadline",
+ &refund_deadline),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -294,40 +330,32 @@ deposit_confirmation_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
- }
- /* refund deadline is optional, defaults to zero */
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_absolute_time ("refund_deadline", &refund_deadline),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (contract_terms,
- spec,
- NULL, NULL))
- {
+ if (GNUNET_TIME_absolute_is_zero (refund_deadline.abs_time))
refund_deadline = timestamp;
- }
}
- dcs->dc = TALER_AUDITOR_deposit_confirmation
- (dcs->auditor,
- &h_wire,
- &h_contract_terms,
- timestamp,
- refund_deadline,
- &amount_without_fee,
- &coin_pub,
- &merchant_pub,
- exchange_pub,
- exchange_sig,
- &keys->master_pub,
- spk->valid_from,
- spk->valid_until,
- spk->valid_legal,
- &spk->master_sig,
- &deposit_confirmation_cb,
- dcs);
+ dcs->dc = TALER_AUDITOR_deposit_confirmation (
+ TALER_TESTING_interpreter_get_context (is),
+ auditor_url,
+ &h_wire,
+ &no_h_policy,
+ &h_contract_terms,
+ *exchange_timestamp,
+ *wire_deadline,
+ refund_deadline,
+ &amount_without_fee,
+ dcs->num_coins,
+ coin_pubps,
+ coin_sigps,
+ &merchant_pub,
+ exchange_pub,
+ exchange_sig,
+ &keys->master_pub,
+ spk->valid_from,
+ spk->valid_until,
+ spk->valid_legal,
+ &spk->master_sig,
+ &deposit_confirmation_cb,
+ dcs);
if (NULL == dcs->dc)
{
@@ -354,10 +382,8 @@ deposit_confirmation_cleanup (void *cls,
if (NULL != dcs->dc)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- dcs->is->ip,
- cmd->label);
+ TALER_TESTING_command_incomplete (dcs->is,
+ cmd->label);
TALER_AUDITOR_deposit_confirmation_cancel (dcs->dc);
dcs->dc = NULL;
}
@@ -370,61 +396,18 @@ deposit_confirmation_cleanup (void *cls,
}
-/**
- * Offer internal data to other commands.
- *
- * @param cls closure.
- * @param[out] ret set to the wanted data.
- * @param trait name of the trait.
- * @param index index number of the traits to be returned.
- *
- * @return #GNUNET_OK on success
- */
-static int
-deposit_confirmation_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- (void) cls;
- (void) ret;
- (void) trait;
- (void) index;
- /* Must define this function because some callbacks
- * look for certain traits on _all_ the commands. */
- return GNUNET_SYSERR;
-}
-
-
-/**
- * Create a "deposit-confirmation" command.
- *
- * @param label command label.
- * @param auditor auditor connection.
- * @param deposit_reference reference to any operation that can
- * provide a coin.
- * @param coin_index if @a deposit_reference offers an array of
- * coins, this parameter selects which one in that array.
- * This value is currently ignored, as only one-coin
- * deposits are implemented.
- * @param amount_without_fee deposited amount without the fee
- * @param expected_response_code expected HTTP response code.
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_deposit_confirmation (const char *label,
- struct TALER_AUDITOR_Handle *auditor,
const char *deposit_reference,
- unsigned int coin_index,
+ unsigned int num_coins,
const char *amount_without_fee,
unsigned int expected_response_code)
{
struct DepositConfirmationState *dcs;
dcs = GNUNET_new (struct DepositConfirmationState);
- dcs->auditor = auditor;
dcs->deposit_reference = deposit_reference;
- dcs->coin_index = coin_index;
+ dcs->num_coins = num_coins;
dcs->amount_without_fee = amount_without_fee;
dcs->expected_response_code = expected_response_code;
@@ -433,8 +416,7 @@ TALER_TESTING_cmd_deposit_confirmation (const char *label,
.cls = dcs,
.label = label,
.run = &deposit_confirmation_run,
- .cleanup = &deposit_confirmation_cleanup,
- .traits = &deposit_confirmation_traits
+ .cleanup = &deposit_confirmation_cleanup
};
return cmd;
@@ -442,13 +424,6 @@ TALER_TESTING_cmd_deposit_confirmation (const char *label,
}
-/**
- * Modify a deposit confirmation command to enable retries when we get
- * transient errors from the auditor.
- *
- * @param cmd a deposit confirmation command
- * @return the command with retries enabled
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_deposit_confirmation_with_retry (
struct TALER_TESTING_Command cmd)
diff --git a/src/testing/testing_api_cmd_auditor_exchanges.c b/src/testing/testing_api_cmd_auditor_exchanges.c
deleted file mode 100644
index 17099cb89..000000000
--- a/src/testing/testing_api_cmd_auditor_exchanges.c
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_cmd_auditor_exchanges.c
- * @brief command for testing /exchanges of the auditor
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_auditor_service.h"
-#include "taler_testing_lib.h"
-#include "taler_signatures.h"
-#include "backoff.h"
-
-/**
- * How long do we wait AT MOST when retrying?
- */
-#define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_MILLISECONDS, 100)
-
-
-/**
- * How often do we retry before giving up?
- */
-#define NUM_RETRIES 5
-
-
-/**
- * State for a "deposit confirmation" CMD.
- */
-struct ExchangesState
-{
-
- /**
- * Exchanges handle while operation is running.
- */
- struct TALER_AUDITOR_ListExchangesHandle *leh;
-
- /**
- * Auditor connection.
- */
- struct TALER_AUDITOR_Handle *auditor;
-
- /**
- * Interpreter state.
- */
- struct TALER_TESTING_Interpreter *is;
-
- /**
- * Task scheduled to try later.
- */
- struct GNUNET_SCHEDULER_Task *retry_task;
-
- /**
- * How long do we wait until we retry?
- */
- struct GNUNET_TIME_Relative backoff;
-
- /**
- * Expected HTTP response code.
- */
- unsigned int expected_response_code;
-
- /**
- * URL of the exchange expected to be included in the response.
- */
- const char *exchange_url;
-
- /**
- * How often should we retry on (transient) failures?
- */
- unsigned int do_retry;
-
-};
-
-
-/**
- * Run the command.
- *
- * @param cls closure.
- * @param cmd the command to execute.
- * @param is the interpreter state.
- */
-static void
-exchanges_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is);
-
-
-/**
- * Task scheduled to re-try #exchanges_run.
- *
- * @param cls a `struct ExchangesState`
- */
-static void
-do_retry (void *cls)
-{
- struct ExchangesState *es = cls;
-
- es->retry_task = NULL;
- es->is->commands[es->is->ip].last_req_time
- = GNUNET_TIME_absolute_get ();
- exchanges_run (es,
- NULL,
- es->is);
-}
-
-
-/**
- * Callback to analyze the /exchanges response.
- *
- * @param cls closure.
- * @param http_status HTTP response code.
- * @param ec taler-specific error code.
- * @param num_exchanges length of the @a ei array
- * @param ei array with information about the exchanges
- * @param raw_response raw response from the auditor.
- */
-static void
-exchanges_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- unsigned int num_exchanges,
- const struct TALER_AUDITOR_ExchangeInfo *ei,
- const json_t *raw_response)
-{
- struct ExchangesState *es = cls;
-
- es->leh = NULL;
- if (es->expected_response_code != http_status)
- {
- if (0 != es->do_retry)
- {
- es->do_retry--;
- if ( (0 == http_status) ||
- (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
- (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Retrying list exchanges failed with %u/%d\n",
- http_status,
- (int) ec);
- /* on DB conflicts, do not use backoff */
- if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
- es->backoff = GNUNET_TIME_UNIT_ZERO;
- else
- es->backoff = GNUNET_TIME_randomized_backoff (es->backoff,
- MAX_BACKOFF);
- es->is->commands[es->is->ip].num_tries++;
- es->retry_task = GNUNET_SCHEDULER_add_delayed (es->backoff,
- &do_retry,
- es);
- return;
- }
- }
- GNUNET_log
- (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- http_status,
- es->is->commands[es->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (raw_response, stderr, 0);
- TALER_TESTING_interpreter_fail (es->is);
- return;
- }
- if (NULL != es->exchange_url)
- {
- unsigned int found = GNUNET_NO;
-
- for (unsigned int i = 0;
- i<num_exchanges;
- i++)
- if (0 == strcmp (es->exchange_url,
- ei[i].exchange_url))
- found = GNUNET_YES;
- if (GNUNET_NO == found)
- {
- TALER_LOG_ERROR
- ("Exchange '%s' doesn't exist at this auditor\n",
- es->exchange_url);
- TALER_TESTING_interpreter_fail (es->is);
- return;
- }
-
- TALER_LOG_DEBUG ("Exchange '%s' exists at this auditor!\n",
- es->exchange_url);
- }
- TALER_TESTING_interpreter_next (es->is);
-}
-
-
-/**
- * Run the command.
- *
- * @param cls closure.
- * @param cmd the command to execute.
- * @param is the interpreter state.
- */
-static void
-exchanges_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct ExchangesState *es = cls;
-
- (void) cmd;
- es->is = is;
- es->leh = TALER_AUDITOR_list_exchanges
- (is->auditor,
- &exchanges_cb,
- es);
-
- if (NULL == es->leh)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- return;
-}
-
-
-/**
- * Free the state of a "exchanges" CMD, and possibly cancel a
- * pending operation thereof.
- *
- * @param cls closure, a `struct ExchangesState`
- * @param cmd the command which is being cleaned up.
- */
-static void
-exchanges_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct ExchangesState *es = cls;
-
- if (NULL != es->leh)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- es->is->ip,
- cmd->label);
- TALER_AUDITOR_list_exchanges_cancel (es->leh);
- es->leh = NULL;
- }
- if (NULL != es->retry_task)
- {
- GNUNET_SCHEDULER_cancel (es->retry_task);
- es->retry_task = NULL;
- }
- GNUNET_free (es);
-}
-
-
-/**
- * Offer internal data to other commands.
- *
- * @param cls closure.
- * @param[out] ret set to the wanted data.
- * @param trait name of the trait.
- * @param index index number of the traits to be returned.
- * @return #GNUNET_OK on success
- */
-static int
-exchanges_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- (void) cls;
- (void) ret;
- (void) trait;
- (void) index;
- /* Must define this function because some callbacks
- * look for certain traits on _all_ the commands. */
- return GNUNET_SYSERR;
-}
-
-
-/**
- * Create a "list exchanges" command.
- *
- * @param label command label.
- * @param auditor auditor connection.
- * @param expected_response_code expected HTTP response code.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_exchanges (const char *label,
- struct TALER_AUDITOR_Handle *auditor,
- unsigned int expected_response_code)
-{
- struct ExchangesState *es;
-
- es = GNUNET_new (struct ExchangesState);
- es->auditor = auditor;
- es->expected_response_code = expected_response_code;
-
- {
- struct TALER_TESTING_Command cmd = {
- .cls = es,
- .label = label,
- .run = &exchanges_run,
- .cleanup = &exchanges_cleanup,
- .traits = &exchanges_traits
- };
-
- return cmd;
- }
-}
-
-
-/**
- * Create a "list exchanges" command and check whether
- * a particular exchange belongs to the returned bundle.
- *
- * @param label command label.
- * @param expected_response_code expected HTTP response code.
- * @param exchange_url URL of the exchange supposed to
- * be included in the response.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_exchanges_with_url (const char *label,
- unsigned int expected_response_code,
- const char *exchange_url)
-{
- struct ExchangesState *es;
-
- es = GNUNET_new (struct ExchangesState);
- es->expected_response_code = expected_response_code;
- es->exchange_url = exchange_url;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = es,
- .label = label,
- .run = &exchanges_run,
- .cleanup = &exchanges_cleanup,
- .traits = &exchanges_traits
- };
-
- return cmd;
- }
-}
-
-
-/**
- * Modify an exchanges command to enable retries when we get
- * transient errors from the auditor.
- *
- * @param cmd a deposit confirmation command
- * @return the command with retries enabled
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_exchanges_with_retry (struct TALER_TESTING_Command cmd)
-{
- struct ExchangesState *es;
-
- GNUNET_assert (&exchanges_run == cmd.run);
- es = cmd.cls;
- es->do_retry = NUM_RETRIES;
- return cmd;
-}
-
-
-/* end of testing_auditor_api_cmd_exchanges.c */
diff --git a/src/testing/testing_api_cmd_auditor_exec_auditor.c b/src/testing/testing_api_cmd_auditor_exec_auditor.c
index 13cf59df1..588be43d8 100644
--- a/src/testing/testing_api_cmd_auditor_exec_auditor.c
+++ b/src/testing/testing_api_cmd_auditor_exec_auditor.c
@@ -63,12 +63,12 @@ auditor_run (void *cls,
(void) cmd;
ks->auditor_proc
- = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-auditor",
"taler-auditor",
"-c", ks->config_filename,
+ "-I",
NULL);
if (NULL == ks->auditor_proc)
{
@@ -116,7 +116,7 @@ auditor_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success.
*/
-static int
+static enum GNUNET_GenericReturnValue
auditor_traits (void *cls,
const void **ret,
const char *trait,
@@ -124,7 +124,7 @@ auditor_traits (void *cls,
{
struct AuditorState *ks = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0, &ks->auditor_proc),
+ TALER_TESTING_make_trait_process (&ks->auditor_proc),
TALER_TESTING_trait_end ()
};
@@ -135,13 +135,6 @@ auditor_traits (void *cls,
}
-/**
- * Make the "exec-auditor" CMD.
- *
- * @param label command label.
- * @param config_filename configuration filename.
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_exec_auditor (const char *label,
const char *config_filename)
diff --git a/src/testing/testing_api_cmd_auditor_exec_auditor_dbinit.c b/src/testing/testing_api_cmd_auditor_exec_auditor_dbinit.c
index bc807feae..2ab5bda8b 100644
--- a/src/testing/testing_api_cmd_auditor_exec_auditor_dbinit.c
+++ b/src/testing/testing_api_cmd_auditor_exec_auditor_dbinit.c
@@ -63,8 +63,7 @@ auditor_dbinit_run (void *cls,
(void) cmd;
ks->auditor_dbinit_proc
- = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-auditor-dbinit",
"taler-auditor-dbinit",
@@ -125,7 +124,7 @@ auditor_dbinit_traits (void *cls,
{
struct AuditorDbinitState *ks = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0, &ks->auditor_dbinit_proc),
+ TALER_TESTING_make_trait_process (&ks->auditor_dbinit_proc),
TALER_TESTING_trait_end ()
};
@@ -136,13 +135,6 @@ auditor_dbinit_traits (void *cls,
}
-/**
- * Make the "exec-auditor-dbinit" CMD.
- *
- * @param label command label.
- * @param config_filename configuration filename.
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_exec_auditor_dbinit (const char *label,
const char *config_filename)
diff --git a/src/testing/testing_api_cmd_bank_admin_add_incoming.c b/src/testing/testing_api_cmd_bank_admin_add_incoming.c
index 04e6839d1..5c031d0b3 100644
--- a/src/testing/testing_api_cmd_bank_admin_add_incoming.c
+++ b/src/testing/testing_api_cmd_bank_admin_add_incoming.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018-2020 Taler Systems SA
+ Copyright (C) 2018-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
@@ -82,6 +82,11 @@ struct AdminAddIncomingState
struct TALER_ReservePrivateKeyP reserve_priv;
/**
+ * Whether we know the private key or not.
+ */
+ bool reserve_priv_known;
+
+ /**
* Reserve public key matching @e reserve_priv.
*/
struct TALER_ReservePublicKeyP reserve_pub;
@@ -102,7 +107,7 @@ struct AdminAddIncomingState
* the "sender_url" field is set to a 'const char *' and
* MUST NOT be free()'ed.
*/
- struct TALER_EXCHANGE_ReserveHistory reserve_history;
+ struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
/**
* Set to the wire transfer's unique ID.
@@ -110,14 +115,9 @@ struct AdminAddIncomingState
uint64_t serial_id;
/**
- * Set to the wire transfer's row ID in network byte order.
- */
- uint64_t row_id_nbo;
-
- /**
* Timestamp of the transaction (as returned from the bank).
*/
- struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_TIME_Timestamp timestamp;
/**
* Merchant instance. Sometimes used to get the tip reserve
@@ -148,6 +148,11 @@ struct AdminAddIncomingState
* enable retries? If so, how often should we still retry?
*/
unsigned int do_retry;
+
+ /**
+ * Expected HTTP status code.
+ */
+ unsigned int expected_http_status;
};
@@ -175,8 +180,7 @@ do_retry (void *cls)
struct AdminAddIncomingState *fts = cls;
fts->retry_task = NULL;
- fts->is->commands[fts->is->ip].last_req_time
- = GNUNET_TIME_absolute_get ();
+ TALER_TESTING_touch_cmd (fts->is);
admin_add_incoming_run (fts,
NULL,
fts->is);
@@ -189,72 +193,108 @@ do_retry (void *cls)
* acceptable.
*
* @param cls closure with the interpreter state
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for
- * successful status request; 0 if the exchange's reply is
- * bogus (fails to follow the protocol)
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param serial_id unique ID of the wire transfer
- * @param timestamp time stamp of the transaction made.
- * @param json raw response
+ * @param air response details
*/
static void
confirmation_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint64_t serial_id,
- struct GNUNET_TIME_Absolute timestamp,
- const json_t *json)
+ const struct TALER_BANK_AdminAddIncomingResponse *air)
{
struct AdminAddIncomingState *fts = cls;
struct TALER_TESTING_Interpreter *is = fts->is;
- (void) json;
- fts->row_id_nbo = GNUNET_htonll (serial_id);
- fts->reserve_history.details.in_details.timestamp = timestamp;
- fts->reserve_history.details.in_details.wire_reference = &fts->row_id_nbo;
- fts->reserve_history.details.in_details.wire_reference_size
- = sizeof (fts->row_id_nbo);
fts->aih = NULL;
- if (MHD_HTTP_OK != http_status)
+ /**
+ * Test case not caring about the HTTP status code.
+ * That helps when Fakebank and Libeufin diverge in
+ * the response status code. An example is the
+ * /admin/add-incoming: libeufin return ALWAYS '200 OK'
+ * (see note below) whereas the Fakebank responds with
+ * '409 Conflict' upon a duplicate reserve public key.
+ *
+ * Note: this decision aims at avoiding to put Taler
+ * logic into the Sandbox; that's because banks DO allow
+ * their customers to wire the same subject multiple
+ * times. Hence, instead of triggering any error, libeufin
+ * bounces the payment back in the same way it does for
+ * malformed reserve public keys.
+ */
+ if (-1 == (int) fts->expected_http_status)
+ {
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ if (air->http_status != fts->expected_http_status)
+ {
+ TALER_TESTING_unexpected_status (is,
+ air->http_status,
+ fts->expected_http_status);
+ return;
+ }
+ switch (air->http_status)
{
+ case MHD_HTTP_OK:
+ fts->reserve_history.details.in_details.timestamp
+ = air->details.ok.timestamp;
+ fts->reserve_history.details.in_details.wire_reference
+ = air->details.ok.serial_id;
+ fts->serial_id
+ = air->details.ok.serial_id;
+ fts->timestamp
+ = air->details.ok.timestamp;
+ TALER_TESTING_interpreter_next (is);
+ return;
+ case MHD_HTTP_UNAUTHORIZED:
+ switch (fts->auth.method)
+ {
+ case TALER_BANK_AUTH_NONE:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Authentication required, but none configure.\n");
+ break;
+ case TALER_BANK_AUTH_BASIC:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Basic authentication (%s) failed.\n",
+ fts->auth.details.basic.username);
+ break;
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ TALER_TESTING_interpreter_next (is);
+ return;
+ default:
if (0 != fts->do_retry)
{
fts->do_retry--;
- if ( (0 == http_status) ||
- (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
- (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
+ if ( (0 == air->http_status) ||
+ (TALER_EC_GENERIC_DB_SOFT_FAILURE == air->ec) ||
+ (MHD_HTTP_INTERNAL_SERVER_ERROR == air->http_status) )
{
- GNUNET_log
- (GNUNET_ERROR_TYPE_INFO,
+ GNUNET_log (
+ GNUNET_ERROR_TYPE_INFO,
"Retrying fakebank transfer failed with %u/%d\n",
- http_status,
- (int) ec);
+ air->http_status,
+ (int) air->ec);
/* on DB conflicts, do not use backoff */
- if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
+ if (TALER_EC_GENERIC_DB_SOFT_FAILURE == air->ec)
fts->backoff = GNUNET_TIME_UNIT_ZERO;
else
fts->backoff = GNUNET_TIME_randomized_backoff (fts->backoff,
MAX_BACKOFF);
- fts->is->commands[fts->is->ip].num_tries++;
- fts->retry_task = GNUNET_SCHEDULER_add_delayed
- (fts->backoff,
- &do_retry,
- fts);
+ TALER_TESTING_inc_tries (fts->is);
+ fts->retry_task = GNUNET_SCHEDULER_add_delayed (
+ fts->backoff,
+ &do_retry,
+ fts);
return;
}
}
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Fakebank returned HTTP status %u/%d\n",
- http_status,
- (int) ec);
- TALER_TESTING_interpreter_fail (is);
- return;
+ break;
}
-
- fts->serial_id = serial_id;
- fts->timestamp = timestamp;
- TALER_TESTING_interpreter_next (is);
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Fakebank returned HTTP status %u/%d\n",
+ air->http_status,
+ (int) air->ec);
+ TALER_TESTING_interpreter_fail (is);
}
@@ -271,13 +311,16 @@ admin_add_incoming_run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct AdminAddIncomingState *fts = cls;
+ bool have_public = false;
(void) cmd;
+ fts->is = is;
/* Use reserve public key as subject */
if (NULL != fts->reserve_reference)
{
const struct TALER_TESTING_Command *ref;
const struct TALER_ReservePrivateKeyP *reserve_priv;
+ const struct TALER_ReservePublicKeyP *reserve_pub;
ref = TALER_TESTING_interpreter_lookup_command
(is, fts->reserve_reference);
@@ -289,88 +332,39 @@ admin_add_incoming_run (void *cls,
}
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_priv (ref,
- 0,
&reserve_priv))
{
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- fts->reserve_priv.eddsa_priv = reserve_priv->eddsa_priv;
- }
- else
- {
- if (NULL != fts->instance)
- {
- char *section;
- char *keys;
- struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
- struct GNUNET_CONFIGURATION_Handle *cfg;
-
- GNUNET_assert (NULL != fts->config_filename);
- cfg = GNUNET_CONFIGURATION_create ();
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_load (cfg,
- fts->config_filename))
+ if (GNUNET_OK != TALER_TESTING_get_trait_reserve_pub (ref,
+ &reserve_pub))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
-
- GNUNET_asprintf (&section,
- "instance-%s",
- fts->instance);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename
- (cfg,
- section,
- "TIP_RESERVE_PRIV_FILENAME",
- &keys))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Configuration fails to specify reserve"
- " private key filename in section %s\n",
- section);
- GNUNET_free (section);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- priv = GNUNET_CRYPTO_eddsa_key_create_from_file (keys);
- GNUNET_free (keys);
- if (NULL == priv)
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- section,
- "TIP_RESERVE_PRIV_FILENAME",
- "Failed to read private key");
- GNUNET_free (section);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- fts->reserve_priv.eddsa_priv = *priv;
- GNUNET_free (section);
- GNUNET_free (priv);
- GNUNET_CONFIGURATION_destroy (cfg);
+ have_public = true;
+ fts->reserve_pub.eddsa_pub = reserve_pub->eddsa_pub;
+ fts->reserve_priv_known = false;
}
else
{
- /* No referenced reserve, no instance to take priv
- * from, no explicit subject given: create new key! */
- struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
-
- priv = GNUNET_CRYPTO_eddsa_key_create ();
- fts->reserve_priv.eddsa_priv = *priv;
- GNUNET_free (priv);
+ fts->reserve_priv.eddsa_priv = reserve_priv->eddsa_priv;
+ fts->reserve_priv_known = true;
}
}
- GNUNET_CRYPTO_eddsa_key_get_public (&fts->reserve_priv.eddsa_priv,
- &fts->reserve_pub.eddsa_pub);
+ else
+ {
+ /* No referenced reserve, no instance to take priv
+ * from, no explicit subject given: create new key! */
+ GNUNET_CRYPTO_eddsa_key_create (&fts->reserve_priv.eddsa_priv);
+ fts->reserve_priv_known = true;
+ }
+ if (! have_public)
+ GNUNET_CRYPTO_eddsa_key_get_public (&fts->reserve_priv.eddsa_priv,
+ &fts->reserve_pub.eddsa_pub);
fts->reserve_history.type = TALER_EXCHANGE_RTT_CREDIT;
fts->reserve_history.amount = fts->amount;
fts->reserve_history.details.in_details.sender_url
= (char *) fts->payto_debit_account; /* remember to NOT free this one... */
- fts->is = is;
fts->aih
= TALER_BANK_admin_add_incoming (
TALER_TESTING_interpreter_get_context (is),
@@ -404,9 +398,8 @@ admin_add_incoming_cleanup (void *cls,
if (NULL != fts->aih)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %s did not complete\n",
- cmd->label);
+ TALER_TESTING_command_incomplete (fts->is,
+ cmd->label);
TALER_BANK_admin_add_incoming_cancel (fts->aih);
fts->aih = NULL;
}
@@ -429,37 +422,66 @@ admin_add_incoming_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success.
*/
-static int
+static enum GNUNET_GenericReturnValue
admin_add_incoming_traits (void *cls,
const void **ret,
const char *trait,
unsigned int index)
{
struct AdminAddIncomingState *fts = cls;
- struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_bank_row (&fts->serial_id),
- TALER_TESTING_make_trait_payto (TALER_TESTING_PT_DEBIT,
- fts->payto_debit_account),
- /* Used as a marker, content does not matter */
- TALER_TESTING_make_trait_payto (TALER_TESTING_PT_CREDIT,
- "payto://void/the-exchange"),
- TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BANK_ACCOUNT_URL,
- fts->exchange_credit_url),
- TALER_TESTING_make_trait_amount_obj (0, &fts->amount),
- TALER_TESTING_make_trait_absolute_time (0, &fts->timestamp),
- TALER_TESTING_make_trait_reserve_priv (0,
- &fts->reserve_priv),
- TALER_TESTING_make_trait_reserve_pub (0,
- &fts->reserve_pub),
- TALER_TESTING_make_trait_reserve_history (0,
- &fts->reserve_history),
- TALER_TESTING_trait_end ()
- };
+ static const char *void_uri = "payto://void/the-exchange";
- return TALER_TESTING_get_trait (traits,
- ret,
- trait,
- index);
+ if (MHD_HTTP_OK !=
+ fts->expected_http_status)
+ return GNUNET_NO; /* requests that failed generate no history */
+ if (fts->reserve_priv_known)
+ {
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_bank_row (&fts->serial_id),
+ TALER_TESTING_make_trait_debit_payto_uri (fts->payto_debit_account),
+ TALER_TESTING_make_trait_payto_uri (fts->payto_debit_account),
+ /* Used as a marker, content does not matter */
+ TALER_TESTING_make_trait_credit_payto_uri (void_uri),
+ TALER_TESTING_make_trait_exchange_bank_account_url (
+ fts->exchange_credit_url),
+ TALER_TESTING_make_trait_amount (&fts->amount),
+ TALER_TESTING_make_trait_timestamp (0,
+ &fts->timestamp),
+ TALER_TESTING_make_trait_reserve_priv (&fts->reserve_priv),
+ TALER_TESTING_make_trait_reserve_pub (&fts->reserve_pub),
+ TALER_TESTING_make_trait_reserve_history (0,
+ &fts->reserve_history),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+ }
+ else
+ {
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_bank_row (&fts->serial_id),
+ TALER_TESTING_make_trait_debit_payto_uri (fts->payto_debit_account),
+ /* Used as a marker, content does not matter */
+ TALER_TESTING_make_trait_credit_payto_uri (void_uri),
+ TALER_TESTING_make_trait_exchange_bank_account_url (
+ fts->exchange_credit_url),
+ TALER_TESTING_make_trait_amount (&fts->amount),
+ TALER_TESTING_make_trait_timestamp (0,
+ &fts->timestamp),
+ TALER_TESTING_make_trait_reserve_pub (&fts->reserve_pub),
+ TALER_TESTING_make_trait_reserve_history (0,
+ &fts->reserve_history),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+ }
}
@@ -482,6 +504,7 @@ make_fts (const char *amount,
fts->exchange_credit_url = auth->wire_gateway_url;
fts->payto_debit_account = payto_debit_account;
fts->auth = *auth;
+ fts->expected_http_status = MHD_HTTP_OK;
if (GNUNET_OK !=
TALER_string_to_amount (amount,
&fts->amount))
@@ -518,21 +541,12 @@ make_command (const char *label,
}
-/**
- * Create admin/add-incoming command.
- *
- * @param label command label.
- * @param amount amount to transfer.
- * @param payto_debit_account which account sends money.
- * @param auth authentication data
- * @return the command.
- */
struct TALER_TESTING_Command
-TALER_TESTING_cmd_admin_add_incoming (const char *label,
- const char *amount,
- const struct
- TALER_BANK_AuthenticationData *auth,
- const char *payto_debit_account)
+TALER_TESTING_cmd_admin_add_incoming (
+ const char *label,
+ const char *amount,
+ const struct TALER_BANK_AuthenticationData *auth,
+ const char *payto_debit_account)
{
return make_command (label,
make_fts (amount,
@@ -541,27 +555,14 @@ TALER_TESTING_cmd_admin_add_incoming (const char *label,
}
-/**
- * Create "/admin/add-incoming" CMD, letting the caller specify
- * a reference to a command that can offer a reserve private key.
- * This private key will then be used to construct the subject line
- * of the wire transfer.
- *
- * @param label command label.
- * @param amount the amount to transfer.
- * @param payto_debit_account which account sends money
- * @param auth authentication data
- * @param ref reference to a command that can offer a reserve
- * private key.
- * @return the command.
- */
struct TALER_TESTING_Command
-TALER_TESTING_cmd_admin_add_incoming_with_ref
- (const char *label,
+TALER_TESTING_cmd_admin_add_incoming_with_ref (
+ const char *label,
const char *amount,
const struct TALER_BANK_AuthenticationData *auth,
const char *payto_debit_account,
- const char *ref)
+ const char *ref,
+ unsigned int http_status)
{
struct AdminAddIncomingState *fts;
@@ -569,47 +570,7 @@ TALER_TESTING_cmd_admin_add_incoming_with_ref
auth,
payto_debit_account);
fts->reserve_reference = ref;
- return make_command (label,
- fts);
-}
-
-
-/**
- * Create "/admin/add-incoming" CMD, letting the caller specifying
- * the merchant instance. This version is useful when a tip
- * reserve should be topped up, in fact the interpreter will need
- * the "tipping instance" in order to get the instance public key
- * and make a wire transfer subject out of it.
- *
- * @param label command label.
- * @param amount amount to transfer.
- * @param payto_debit_account which account (expressed as a number)
- * gives money
- * @param auth authentication data
- * @param instance the instance that runs the tipping. Under this
- * instance, the configuration file will provide the private
- * key of the tipping reserve. This data will then used to
- * construct the wire transfer subject line.
- * @param config_filename configuration file to use.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_admin_add_incoming_with_instance
- (const char *label,
- const char *amount,
- const struct TALER_BANK_AuthenticationData *auth,
- const char *payto_debit_account,
- const char *instance,
- const char *config_filename)
-{
- struct AdminAddIncomingState *fts;
-
- fts = make_fts (amount,
- auth,
- payto_debit_account);
- fts->instance = instance;
- fts->config_filename = config_filename;
-
+ fts->expected_http_status = http_status;
return make_command (label,
fts);
}
diff --git a/src/testing/testing_api_cmd_bank_admin_check.c b/src/testing/testing_api_cmd_bank_admin_check.c
index 473f3f3f2..6406fe2c2 100644
--- a/src/testing/testing_api_cmd_bank_admin_check.c
+++ b/src/testing/testing_api_cmd_bank_admin_check.c
@@ -82,8 +82,30 @@ check_bank_admin_transfer_run (void *cls,
const char *credit_payto;
const struct TALER_ReservePublicKeyP *reserve_pub;
const struct TALER_TESTING_Command *cmd_ref;
+ struct TALER_FAKEBANK_Handle *fakebank;
(void) cmd;
+ {
+ const struct TALER_TESTING_Command *fakebank_cmd;
+
+ fakebank_cmd
+ = TALER_TESTING_interpreter_get_command (is,
+ "fakebank");
+ if (NULL == fakebank_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_fakebank (fakebank_cmd,
+ &fakebank))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ }
cmd_ref
= TALER_TESTING_interpreter_lookup_command (is,
bcs->reserve_pub_ref);
@@ -95,7 +117,6 @@ check_bank_admin_transfer_run (void *cls,
}
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_pub (cmd_ref,
- 0,
&reserve_pub))
{
GNUNET_break (0);
@@ -103,7 +124,6 @@ check_bank_admin_transfer_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
- TALER_LOG_INFO ("Deposit reference NOT given\n");
debit_payto = bcs->debit_payto;
credit_payto = bcs->credit_payto;
if (GNUNET_OK !=
@@ -111,9 +131,9 @@ check_bank_admin_transfer_run (void *cls,
&amount))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse amount `%s' at %u\n",
+ "Failed to parse amount `%s' at %s\n",
bcs->amount,
- is->ip);
+ TALER_TESTING_interpreter_get_current_label (is));
TALER_TESTING_interpreter_fail (is);
return;
}
@@ -124,7 +144,7 @@ check_bank_admin_transfer_run (void *cls,
debit_payto,
debit_account);
if (GNUNET_OK !=
- TALER_FAKEBANK_check_credit (is->fakebank,
+ TALER_FAKEBANK_check_credit (fakebank,
&amount,
debit_account,
credit_account,
@@ -160,33 +180,6 @@ check_bank_admin_transfer_cleanup (void *cls,
/**
- * Offer internal data from a "bank admin check" CMD state.
- *
- * @param cls closure.
- * @param[out] ret result.
- * @param trait name of the trait.
- * @param index index number of the object to offer.
- * @return #GNUNET_OK on success.
- */
-static int
-check_bank_admin_transfer_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_trait_end ()
- };
-
- (void) cls;
- return TALER_TESTING_get_trait (traits,
- ret,
- trait,
- index);
-}
-
-
-/**
* Make a "bank check" CMD. It checks whether a particular wire transfer to
* the exchange (credit) has been made or not.
*
@@ -217,8 +210,7 @@ TALER_TESTING_cmd_check_bank_admin_transfer
.label = label,
.cls = bcs,
.run = &check_bank_admin_transfer_run,
- .cleanup = &check_bank_admin_transfer_cleanup,
- .traits = &check_bank_admin_transfer_traits
+ .cleanup = &check_bank_admin_transfer_cleanup
};
return cmd;
diff --git a/src/testing/testing_api_cmd_bank_check.c b/src/testing/testing_api_cmd_bank_check.c
index c01bc709f..77d120e09 100644
--- a/src/testing/testing_api_cmd_bank_check.c
+++ b/src/testing/testing_api_cmd_bank_check.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018-2020 Taler Systems SA
+ Copyright (C) 2018-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
@@ -74,6 +74,7 @@ struct BankCheckState
const char *deposit_reference;
};
+
/**
* Run the command.
*
@@ -93,8 +94,30 @@ check_bank_transfer_run (void *cls,
const char *exchange_base_url;
const char *debit_payto;
const char *credit_payto;
+ struct TALER_FAKEBANK_Handle *fakebank;
(void) cmd;
+ {
+ const struct TALER_TESTING_Command *fakebank_cmd;
+
+ fakebank_cmd
+ = TALER_TESTING_interpreter_get_command (is,
+ "fakebank");
+ if (NULL == fakebank_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_fakebank (fakebank_cmd,
+ &fakebank))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ }
if (NULL == bcs->deposit_reference)
{
TALER_LOG_INFO ("Deposit reference NOT given\n");
@@ -107,9 +130,9 @@ check_bank_transfer_run (void *cls,
&amount))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse amount `%s' at %u\n",
+ "Failed to parse amount `%s' at %s\n",
bcs->amount,
- is->ip);
+ TALER_TESTING_interpreter_get_current_label (is));
TALER_TESTING_interpreter_fail (is);
return;
}
@@ -130,41 +153,32 @@ check_bank_transfer_run (void *cls,
if (NULL == deposit_cmd)
TALER_TESTING_FAIL (is);
if ( (GNUNET_OK !=
- TALER_TESTING_get_trait_amount_obj (deposit_cmd,
- 0,
- &amount_ptr)) ||
+ TALER_TESTING_get_trait_amount (deposit_cmd,
+ &amount_ptr)) ||
(GNUNET_OK !=
- TALER_TESTING_get_trait_payto (deposit_cmd,
- TALER_TESTING_PT_DEBIT,
- &debit_payto)) ||
+ TALER_TESTING_get_trait_debit_payto_uri (deposit_cmd,
+ &debit_payto)) ||
(GNUNET_OK !=
- TALER_TESTING_get_trait_payto (deposit_cmd,
- TALER_TESTING_PT_CREDIT,
- &credit_payto)) ||
+ TALER_TESTING_get_trait_credit_payto_uri (deposit_cmd,
+ &credit_payto)) ||
(GNUNET_OK !=
- TALER_TESTING_get_trait_url (deposit_cmd,
- TALER_TESTING_UT_EXCHANGE_BASE_URL,
- &exchange_base_url)) )
+ TALER_TESTING_get_trait_exchange_url (deposit_cmd,
+ &exchange_base_url)) )
TALER_TESTING_FAIL (is);
amount = *amount_ptr;
}
-
-
debit_account = TALER_xtalerbank_account_from_payto (debit_payto);
credit_account = TALER_xtalerbank_account_from_payto (credit_payto);
-
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"converted debit_payto (%s) to debit_account (%s)\n",
debit_payto,
debit_account);
-
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"converted credit_payto (%s) to credit_account (%s)\n",
credit_payto,
credit_account);
-
if (GNUNET_OK !=
- TALER_FAKEBANK_check_debit (is->fakebank,
+ TALER_FAKEBANK_check_debit (fakebank,
&amount,
debit_account,
credit_account,
@@ -209,7 +223,7 @@ check_bank_transfer_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success.
*/
-static int
+static enum GNUNET_GenericReturnValue
check_bank_transfer_traits (void *cls,
const void **ret,
const char *trait,
@@ -218,10 +232,9 @@ check_bank_transfer_traits (void *cls,
struct BankCheckState *bcs = cls;
struct TALER_WireTransferIdentifierRawP *wtid_ptr = &bcs->wtid;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_wtid (0,
- wtid_ptr),
- TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BASE_URL,
- bcs->exchange_base_url),
+ TALER_TESTING_make_trait_wtid (wtid_ptr),
+ TALER_TESTING_make_trait_exchange_url (
+ bcs->exchange_base_url),
TALER_TESTING_trait_end ()
};
@@ -232,19 +245,6 @@ check_bank_transfer_traits (void *cls,
}
-/**
- * Make a "bank check" CMD. It checks whether a
- * particular wire transfer has been made or not.
- *
- * @param label the command label.
- * @param exchange_base_url base url of the exchange involved in
- * the wire transfer.
- * @param amount the amount expected to be transferred.
- * @param debit_payto the account that gave money.
- * @param credit_payto the account that received money.
- *
- * @return the command
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_check_bank_transfer (const char *label,
const char *exchange_base_url,
@@ -274,19 +274,9 @@ TALER_TESTING_cmd_check_bank_transfer (const char *label,
}
-/**
- * Define a "bank check" CMD that takes the input
- * data from another CMD that offers it.
- *
- * @param label command label.
- * @param deposit_reference reference to a CMD that is
- * able to provide the "check bank transfer" operation
- * input data.
- * @return the command.
- */
struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_bank_transfer_with_ref
- (const char *label,
+TALER_TESTING_cmd_check_bank_transfer_with_ref (
+ const char *label,
const char *deposit_reference)
{
struct BankCheckState *bcs;
diff --git a/src/testing/testing_api_cmd_bank_check_empty.c b/src/testing/testing_api_cmd_bank_check_empty.c
index 84976b0b5..60f00fbb4 100644
--- a/src/testing/testing_api_cmd_bank_check_empty.c
+++ b/src/testing/testing_api_cmd_bank_check_empty.c
@@ -58,9 +58,33 @@ check_bank_empty_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
{
+ struct TALER_FAKEBANK_Handle *fakebank;
+
(void) cls;
(void) cmd;
- if (GNUNET_OK != TALER_FAKEBANK_check_empty (is->fakebank))
+ {
+ const struct TALER_TESTING_Command *fakebank_cmd;
+
+ fakebank_cmd
+ = TALER_TESTING_interpreter_get_command (is,
+ "fakebank");
+ if (NULL == fakebank_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_fakebank (fakebank_cmd,
+ &fakebank))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ }
+ if (GNUNET_OK !=
+ TALER_FAKEBANK_check_empty (fakebank))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
diff --git a/src/testing/testing_api_cmd_bank_history_credit.c b/src/testing/testing_api_cmd_bank_history_credit.c
index 0b71c7162..956e6c857 100644
--- a/src/testing/testing_api_cmd_bank_history_credit.c
+++ b/src/testing/testing_api_cmd_bank_history_credit.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018-2020 Taler Systems SA
+ Copyright (C) 2018-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
@@ -83,6 +83,11 @@ struct HistoryState
struct TALER_BANK_CreditHistoryHandle *hh;
/**
+ * The interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
* Authentication data for the operation.
*/
struct TALER_BANK_AuthenticationData auth;
@@ -93,10 +98,10 @@ struct HistoryState
uint64_t results_obtained;
/**
- * Set to GNUNET_YES if the callback detects something
+ * Set to true if the callback detects something
* unexpected.
*/
- int failed;
+ bool failed;
/**
* Expected history.
@@ -112,32 +117,6 @@ struct HistoryState
/**
- * Offer internal data to other commands.
- *
- * @param cls closure.
- * @param[out] ret set to the wanted data.
- * @param trait name of the trait.
- * @param index index number of the traits to be returned.
- *
- * @return #GNUNET_OK on success
- */
-static int
-history_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- (void) cls;
- (void) ret;
- (void) trait;
- (void) index;
- /* Must define this function because some callbacks
- * look for certain traits on _all_ the commands. */
- return GNUNET_SYSERR;
-}
-
-
-/**
* Log which history we expected. Called when an error occurs.
*
* @param h what we expected.
@@ -164,44 +143,167 @@ print_expected (struct History *h,
TALER_amount2s (&h[i].details.amount),
(unsigned long long) h[i].row_id,
TALER_B2S (&h[i].details.reserve_pub),
- h[i].details.debit_account_url);
+ h[i].details.debit_account_uri);
}
}
/**
+ * Closure for command_cb().
+ */
+struct IteratorContext
+{
+ /**
+ * Array of history items to return.
+ */
+ struct History *h;
+
+ /**
+ * Set to the row ID from where on we should actually process history items,
+ * or NULL if we should process all of them.
+ */
+ const uint64_t *row_id_start;
+
+ /**
+ * History state we are working on.
+ */
+ struct HistoryState *hs;
+
+ /**
+ * Current length of the @e h array.
+ */
+ unsigned int total;
+
+ /**
+ * Current write position in @e h array.
+ */
+ unsigned int pos;
+
+ /**
+ * Ok equals True whenever a starting row_id was provided AND was found
+ * among the CMDs, OR no starting row was given in the first place.
+ */
+ bool ok;
+
+};
+
+
+/**
+ * Helper function of build_history() that expands
+ * the history for each relevant command encountered.
+ *
+ * @param[in,out] cls our `struct IteratorContext`
+ * @param cmd a command to process
+ */
+static void
+command_cb (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct IteratorContext *ic = cls;
+ struct HistoryState *hs = ic->hs;
+ const uint64_t *row_id;
+ const char *credit_account;
+ const char *debit_account;
+ const struct TALER_Amount *amount;
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+ const char *exchange_credit_url;
+
+ /**
+ * The following command allows us to skip over those CMDs
+ * that do not offer a "row_id" trait. Such skipped CMDs are
+ * not interesting for building a history.
+ */
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_bank_row (cmd,
+ &row_id)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_credit_payto_uri (cmd,
+ &credit_account)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_debit_payto_uri (cmd,
+ &debit_account)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_amount (cmd,
+ &amount)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_pub (cmd,
+ &reserve_pub)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_exchange_bank_account_url (
+ cmd,
+ &exchange_credit_url)) )
+ return; // Not an interesting event
+
+ /**
+ * Is the interesting event a match with regard to
+ * the row_id value? If yes, store this condition
+ * to the state and analyze the next CMDs.
+ */
+ if ( (NULL != ic->row_id_start) &&
+ (*(ic->row_id_start) == *row_id) &&
+ (! ic->ok) )
+ {
+ ic->ok = true;
+ return;
+ }
+ /**
+ * The interesting event didn't match the wanted
+ * row_id value, analyze the next CMDs. Note: this
+ * branch is relevant only when row_id WAS given.
+ */
+ if (! ic->ok)
+ return;
+ if (0 != strcasecmp (hs->account_url,
+ exchange_credit_url))
+ return; // Account mismatch
+ if (ic->total >= GNUNET_MAX (hs->num_results,
+ -hs->num_results) )
+ {
+ TALER_LOG_DEBUG ("Hit history limit\n");
+ return;
+ }
+ TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
+ debit_account,
+ credit_account,
+ hs->account_url);
+ /* found matching record, make sure we have room */
+ if (ic->pos == ic->total)
+ GNUNET_array_grow (ic->h,
+ ic->total,
+ ic->pos * 2);
+ ic->h[ic->pos].url = GNUNET_strdup (debit_account);
+ ic->h[ic->pos].details.debit_account_uri = ic->h[ic->pos].url;
+ ic->h[ic->pos].details.amount = *amount;
+ ic->h[ic->pos].row_id = *row_id;
+ ic->h[ic->pos].details.reserve_pub = *reserve_pub;
+ ic->pos++;
+}
+
+
+/**
* This function constructs the list of history elements that
* interest the account number of the caller. It has two main
* loops: the first to figure out how many history elements have
* to be allocated, and the second to actually populate every
* element.
*
- * @param is interpreter state (supposedly having the
- * current CMD pointing at a "history" CMD).
+ * @param hs history state
* @param[out] rh history array to initialize.
* @return number of entries in @a rh.
*/
static unsigned int
-build_history (struct TALER_TESTING_Interpreter *is,
+build_history (struct HistoryState *hs,
struct History **rh)
{
- struct HistoryState *hs = is->commands[is->ip].cls;
- unsigned int total;
- unsigned int pos;
- struct History *h;
- const struct TALER_TESTING_Command *add_incoming_cmd;
- int inc;
- unsigned int start;
- unsigned int end;
-
- /* @var turns GNUNET_YES whenever either no 'start' value was
- * given for the history query, or the given value is found
- * in the list of all the CMDs. *///
- int ok;
- const uint64_t *row_id_start = NULL;
+ struct TALER_TESTING_Interpreter *is = hs->is;
+ struct IteratorContext ic = {
+ .hs = hs
+ };
if (NULL != hs->start_row_reference)
{
+ const struct TALER_TESTING_Command *add_incoming_cmd;
+
TALER_LOG_INFO ("`%s': start row given via reference `%s'\n",
TALER_TESTING_interpreter_get_current_label (is),
hs->start_row_reference);
@@ -210,124 +312,92 @@ build_history (struct TALER_TESTING_Interpreter *is,
hs->start_row_reference);
GNUNET_assert (NULL != add_incoming_cmd);
GNUNET_assert (GNUNET_OK ==
- TALER_TESTING_get_trait_uint64 (add_incoming_cmd,
- 0,
- &row_id_start));
+ TALER_TESTING_get_trait_row (add_incoming_cmd,
+ &ic.row_id_start));
}
+ ic.ok = false;
+ if (NULL == ic.row_id_start)
+ ic.ok = true;
+ GNUNET_array_grow (ic.h,
+ ic.total,
+ 4);
GNUNET_assert (0 != hs->num_results);
- if (0 == is->ip)
- {
- TALER_LOG_DEBUG ("Checking history at FIRST transaction (EMPTY)\n");
- *rh = NULL;
- return 0;
- }
+ TALER_TESTING_iterate (is,
+ hs->num_results > 0,
+ &command_cb,
+ &ic);
+ GNUNET_assert (ic.ok);
+ GNUNET_array_grow (ic.h,
+ ic.total,
+ ic.pos);
+ if (0 == ic.pos)
+ TALER_LOG_DEBUG ("Empty credit history computed\n");
+ *rh = ic.h;
+ return ic.pos;
+}
+
- if (hs->num_results > 0)
+/**
+ * Normalize IBAN-based payto URI in @a in.
+ *
+ * @param in input payto://-URI to normalize
+ * @return normalized IBAN for the test
+ */
+static char *
+normalize (const char *in)
+{
+ char *npt;
+ const char *q = strchr (in,
+ '?');
+ const char *mptr;
+ const char *bic;
+ const char *iban;
+
+ if (NULL == q)
+ npt = GNUNET_strdup (in);
+ else
+ npt = GNUNET_strndup (in,
+ q - in);
+ if (0 != strncasecmp (npt,
+ "payto://",
+ strlen ("payto://")))
{
- inc = 1; /* _inc_rement */
- start = 0;
- end = is->ip - 1;
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Invalid payto: %s\n",
+ npt);
+ GNUNET_free (npt);
+ return NULL;
}
- else
+ mptr = npt + strlen ("payto://");
+ bic = strchr (mptr, '/');
+ if (NULL == bic)
{
- inc = -1;
- start = is->ip - 1;
- end = 0;
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Invalid payto: %s\n",
+ npt);
+ GNUNET_free (npt);
+ return NULL;
}
-
- ok = GNUNET_NO;
- if (NULL == row_id_start)
- ok = GNUNET_YES;
- h = NULL;
- total = 0;
- GNUNET_array_grow (h,
- total,
- 4);
- pos = 0;
- for (unsigned int off = start; off != end + inc; off += inc)
+ bic++;
+ iban = strchr (bic, '/');
+ if (NULL != iban)
{
- const struct TALER_TESTING_Command *cmd = &is->commands[off];
- const uint64_t *row_id;
- const char *credit_account;
- const char *debit_account;
- const struct TALER_Amount *amount;
- const struct TALER_ReservePublicKeyP *reserve_pub;
- const char *exchange_credit_url;
-
- /* The following command allows us to skip over those CMDs
- * that do not offer a "row_id" trait. Such skipped CMDs are
- * not interesting for building a history. *///
- if ( (GNUNET_OK !=
- TALER_TESTING_get_trait_bank_row (cmd,
- &row_id)) ||
- (GNUNET_OK !=
- TALER_TESTING_get_trait_payto (cmd,
- TALER_TESTING_PT_CREDIT,
- &credit_account)) ||
- (GNUNET_OK !=
- TALER_TESTING_get_trait_payto (cmd,
- TALER_TESTING_PT_DEBIT,
- &debit_account)) ||
- (GNUNET_OK !=
- TALER_TESTING_get_trait_amount_obj (cmd,
- 0,
- &amount)) ||
- (GNUNET_OK !=
- TALER_TESTING_get_trait_reserve_pub (cmd,
- 0,
- &reserve_pub)) ||
- (GNUNET_OK !=
- TALER_TESTING_get_trait_url (cmd,
- TALER_TESTING_UT_EXCHANGE_BANK_ACCOUNT_URL,
- &exchange_credit_url)) )
- continue; /* not an interesting event */
- /* Seek "/history/incoming" starting row. */
- if ( (NULL != row_id_start) &&
- (*row_id_start == *row_id) &&
- (GNUNET_NO == ok) )
- {
- /* Until here, nothing counted. */
- ok = GNUNET_YES;
- continue;
- }
- /* when 'start' was _not_ given, then ok == GNUNET_YES */
- if (GNUNET_NO == ok)
- continue; /* skip until we find the marker */
- if (0 != strcasecmp (hs->account_url,
- exchange_credit_url))
- continue; /* account mismatch */
- if (total >= GNUNET_MAX (hs->num_results,
- -hs->num_results) )
- {
- TALER_LOG_DEBUG ("Hit history limit\n");
- break;
- }
- TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
- debit_account,
- credit_account,
- hs->account_url);
- /* found matching record, make sure we have room */
- if (pos == total)
- GNUNET_array_grow (h,
- total,
- pos * 2);
- h[pos].url = GNUNET_strdup (debit_account);
- h[pos].details.debit_account_url = h[pos].url;
- h[pos].details.amount = *amount;
- h[pos].row_id = *row_id;
- h[pos].details.reserve_pub = *reserve_pub;
- h[pos].details.credit_account_url = exchange_credit_url;
- pos++;
+ /* need to remove bic */
+ char *n;
+
+ iban++;
+ GNUNET_asprintf (&n,
+ "payto://%.*s/%s",
+ (int) ((bic - mptr) - 1),
+ mptr,
+ iban);
+ GNUNET_free (npt);
+ npt = n;
}
- GNUNET_assert (GNUNET_YES == ok);
- GNUNET_array_grow (h,
- total,
- pos);
- if (0 == pos)
- TALER_LOG_DEBUG ("Empty credit history computed\n");
- *rh = h;
- return total;
+ return npt;
}
@@ -342,12 +412,15 @@ build_history (struct TALER_TESTING_Interpreter *is,
* @param details the expected transaction details.
* @return #GNUNET_OK if the transaction is what we expect.
*/
-static int
+static enum GNUNET_GenericReturnValue
check_result (struct History *h,
unsigned int total,
unsigned int off,
const struct TALER_BANK_CreditDetails *details)
{
+ char *u1;
+ char *u2;
+
if (off >= total)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -360,25 +433,42 @@ check_result (struct History *h,
off);
return GNUNET_SYSERR;
}
+ u1 = normalize (h[off].details.debit_account_uri);
+ if (NULL == u1)
+ return GNUNET_SYSERR;
+ u2 = normalize (details->debit_account_uri);
+ if (NULL == u2)
+ {
+ GNUNET_free (u1);
+ return GNUNET_SYSERR;
+ }
if ( (0 != GNUNET_memcmp (&h[off].details.reserve_pub,
&details->reserve_pub)) ||
(0 != TALER_amount_cmp (&h[off].details.amount,
&details->amount)) ||
- (0 != strcasecmp (h[off].details.debit_account_url,
- details->debit_account_url)) )
+ (0 != strcasecmp (u1,
+ u2)) )
{
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "expected debit_account_url: %s\n",
- details->debit_account_url);
+ "expected debit_account_uri: %s with %s for %s\n",
+ u1,
+ TALER_amount2s (&h[off].details.amount),
+ TALER_B2S (&h[off].details.reserve_pub));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "actual debit_account_url: %s\n",
- h[off].details.debit_account_url);
+ "actual debit_account_uri: %s with %s for %s\n",
+ u2,
+ TALER_amount2s (&details->amount),
+ TALER_B2S (&details->reserve_pub));
print_expected (h,
total,
off);
+ GNUNET_free (u1);
+ GNUNET_free (u2);
return GNUNET_SYSERR;
}
+ GNUNET_free (u1);
+ GNUNET_free (u2);
return GNUNET_OK;
}
@@ -391,89 +481,86 @@ check_result (struct History *h,
* finally check it against what the bank returned.
*
* @param cls closure.
- * @param http_status HTTP response code, #MHD_HTTP_OK (200)
- * for successful status request 0 if the bank's reply is
- * bogus (fails to follow the protocol),
- * #MHD_HTTP_NO_CONTENT if there are no more results; on
- * success the last callback is always of this status
- * (even if `abs(num_results)` were already returned).
- * @param ec taler status code.
- * @param row_id monotonically increasing counter corresponding to
- * the transaction.
- * @param details details about the wire transfer.
- * @param json detailed response from the HTTPD, or NULL if
- * reply was not in JSON.
- * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
+ * @param chr http response details
*/
-static int
+static void
history_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint64_t row_id,
- const struct TALER_BANK_CreditDetails *details,
- const json_t *json)
+ const struct TALER_BANK_CreditHistoryResponse *chr)
{
- struct TALER_TESTING_Interpreter *is = cls;
- struct HistoryState *hs = is->commands[is->ip].cls;
+ struct HistoryState *hs = cls;
+ struct TALER_TESTING_Interpreter *is = hs->is;
- (void) row_id;
- if (NULL == details)
+ hs->hh = NULL;
+ switch (chr->http_status)
{
- hs->hh = NULL;
- if ( (hs->results_obtained != hs->total) ||
- (GNUNET_YES == hs->failed) ||
- (MHD_HTTP_NO_CONTENT != http_status) )
+ case 0:
+ GNUNET_break (0);
+ goto error;
+ case MHD_HTTP_OK:
+ for (unsigned int i = 0; i<chr->details.ok.details_length; i++)
{
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Expected history of length %u, got %llu;"
- " HTTP status code: %u/%d, failed: %d\n",
- hs->total,
- (unsigned long long) hs->results_obtained,
- http_status,
- (int) ec,
- hs->failed);
- print_expected (hs->h,
- hs->total,
- UINT_MAX);
- TALER_TESTING_interpreter_fail (is);
- return GNUNET_SYSERR;
+ const struct TALER_BANK_CreditDetails *cd =
+ &chr->details.ok.details[i];
+
+ /* check current element */
+ if (GNUNET_OK !=
+ check_result (hs->h,
+ hs->total,
+ hs->results_obtained,
+ cd))
+ {
+ GNUNET_break (0);
+ json_dumpf (chr->response,
+ stderr,
+ JSON_COMPACT);
+ hs->failed = true;
+ hs->hh = NULL;
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ hs->results_obtained++;
}
TALER_TESTING_interpreter_next (is);
- return GNUNET_OK;
- }
- if (MHD_HTTP_OK != http_status)
- {
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ if (0 == hs->total)
+ {
+ /* not found is OK for empty history */
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ GNUNET_break (0);
+ goto error;
+ case MHD_HTTP_NOT_FOUND:
+ if (0 == hs->total)
+ {
+ /* not found is OK for empty history */
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ GNUNET_break (0);
+ goto error;
+ default:
hs->hh = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unwanted response code from /history/incoming: %u\n",
- http_status);
+ chr->http_status);
TALER_TESTING_interpreter_fail (is);
- return GNUNET_SYSERR;
- }
-
- /* check current element */
- if (GNUNET_OK != check_result (hs->h,
- hs->total,
- hs->results_obtained,
- details))
- {
- char *acc;
-
- GNUNET_break (0);
- acc = json_dumps (json,
- JSON_COMPACT);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Result %u was `%s'\n",
- (unsigned int) hs->results_obtained++,
- acc);
- if (NULL != acc)
- free (acc);
- hs->failed = GNUNET_YES;
- return GNUNET_SYSERR;
+ return;
}
- hs->results_obtained++;
- return GNUNET_OK;
+error:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected history of length %u, got %llu;"
+ " HTTP status code: %u/%d, failed: %d\n",
+ hs->total,
+ (unsigned long long) hs->results_obtained,
+ chr->http_status,
+ (int) chr->ec,
+ hs->failed ? 1 : 0);
+ print_expected (hs->h,
+ hs->total,
+ UINT_MAX);
+ TALER_TESTING_interpreter_fail (is);
}
@@ -494,35 +581,37 @@ history_run (void *cls,
const uint64_t *row_ptr;
(void) cmd;
+ hs->is = is;
/* Get row_id from trait. */
if (NULL != hs->start_row_reference)
{
const struct TALER_TESTING_Command *history_cmd;
- history_cmd = TALER_TESTING_interpreter_lookup_command
- (is, hs->start_row_reference);
-
+ history_cmd = TALER_TESTING_interpreter_lookup_command (
+ is,
+ hs->start_row_reference);
if (NULL == history_cmd)
TALER_TESTING_FAIL (is);
if (GNUNET_OK !=
- TALER_TESTING_get_trait_uint64 (history_cmd,
- 0,
- &row_ptr))
+ TALER_TESTING_get_trait_row (history_cmd,
+ &row_ptr))
TALER_TESTING_FAIL (is);
else
row_id = *row_ptr;
TALER_LOG_DEBUG ("row id (from trait) is %llu\n",
(unsigned long long) row_id);
}
- hs->total = build_history (is,
+ hs->total = build_history (hs,
&hs->h);
- hs->hh = TALER_BANK_credit_history (is->ctx,
- &hs->auth,
- row_id,
- hs->num_results,
- &history_cb,
- is);
+ hs->hh = TALER_BANK_credit_history (
+ TALER_TESTING_interpreter_get_context (is),
+ &hs->auth,
+ row_id,
+ hs->num_results,
+ GNUNET_TIME_UNIT_ZERO,
+ &history_cb,
+ hs);
GNUNET_assert (NULL != hs->hh);
}
@@ -543,34 +632,24 @@ history_cleanup (void *cls,
(void) cmd;
if (NULL != hs->hh)
{
- TALER_LOG_WARNING ("/history/incoming did not complete\n");
+ TALER_TESTING_command_incomplete (hs->is,
+ cmd->label);
TALER_BANK_credit_history_cancel (hs->hh);
}
GNUNET_free (hs->account_url);
for (unsigned int off = 0; off<hs->total; off++)
GNUNET_free (hs->h[off].url);
- GNUNET_free_non_null (hs->h);
+ GNUNET_free (hs->h);
GNUNET_free (hs);
}
-/**
- * Make a "history" CMD.
- *
- * @param label command label.
- * @param auth authentication data to talk with the wire gateway
- * @param start_row_reference reference to a command that can
- * offer a row identifier, to be used as the starting row
- * to accept in the result.
- * @param num_results how many rows we want in the result.
- * @return the command.
- */
struct TALER_TESTING_Command
-TALER_TESTING_cmd_bank_credits (const char *label,
- const struct
- TALER_BANK_AuthenticationData *auth,
- const char *start_row_reference,
- long long num_results)
+TALER_TESTING_cmd_bank_credits (
+ const char *label,
+ const struct TALER_BANK_AuthenticationData *auth,
+ const char *start_row_reference,
+ long long num_results)
{
struct HistoryState *hs;
@@ -584,8 +663,7 @@ TALER_TESTING_cmd_bank_credits (const char *label,
.label = label,
.cls = hs,
.run = &history_run,
- .cleanup = &history_cleanup,
- .traits = &history_traits
+ .cleanup = &history_cleanup
};
return cmd;
diff --git a/src/testing/testing_api_cmd_bank_history_debit.c b/src/testing/testing_api_cmd_bank_history_debit.c
index ebabf8d94..1cb7320fa 100644
--- a/src/testing/testing_api_cmd_bank_history_debit.c
+++ b/src/testing/testing_api_cmd_bank_history_debit.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018-2020 Taler Systems SA
+ Copyright (C) 2018-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
@@ -92,6 +92,11 @@ struct HistoryState
struct TALER_BANK_DebitHistoryHandle *hh;
/**
+ * Our interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
* Expected number of results (= rows).
*/
uint64_t results_obtained;
@@ -116,32 +121,6 @@ struct HistoryState
/**
- * Offer internal data to other commands.
- *
- * @param cls closure.
- * @param[out] ret set to the wanted data.
- * @param trait name of the trait.
- * @param index index number of the traits to be returned.
- *
- * @return #GNUNET_OK on success
- */
-static int
-history_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- (void) cls;
- (void) ret;
- (void) trait;
- (void) index;
- /* Must define this function because some callbacks
- * look for certain traits on _all_ the commands. */
- return GNUNET_SYSERR;
-}
-
-
-/**
* Log which history we expected. Called when an error occurs.
*
* @param h what we expected.
@@ -167,8 +146,132 @@ print_expected (struct History *h,
TALER_amount2s (&h[i].details.amount),
(unsigned long long) h[i].row_id,
TALER_B2S (&h[i].details.wtid),
- h[i].details.credit_account_url);
+ h[i].details.credit_account_uri);
+ }
+}
+
+
+/**
+ * Closure for command_cb().
+ */
+struct IteratorContext
+{
+ /**
+ * Array of history items to return.
+ */
+ struct History *h;
+
+ /**
+ * Set to the row ID from where on we should actually process history items,
+ * or NULL if we should process all of them.
+ */
+ const uint64_t *row_id_start;
+
+ /**
+ * History state we are working on.
+ */
+ struct HistoryState *hs;
+
+ /**
+ * Current length of the @e h array.
+ */
+ unsigned int total;
+
+ /**
+ * Current write position in @e h array.
+ */
+ unsigned int pos;
+
+ /**
+ * Ok equals True whenever a starting row_id was provided AND was found
+ * among the CMDs, OR no starting row was given in the first place.
+ */
+ bool ok;
+
+};
+
+
+/**
+ * Helper function of build_history() that expands
+ * the history for each relevant command encountered.
+ *
+ * @param[in,out] cls our `struct IteratorContext`
+ * @param cmd a command to process
+ */
+static void
+command_cb (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct IteratorContext *ic = cls;
+ struct HistoryState *hs = ic->hs;
+
+ const uint64_t *row_id;
+ const char *debit_account;
+ const char *credit_account;
+ const struct TALER_Amount *amount;
+ const struct TALER_WireTransferIdentifierRawP *wtid;
+ const char *exchange_base_url;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking if command %s is relevant for debit history\n",
+ cmd->label);
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_bank_row (cmd,
+ &row_id)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_debit_payto_uri (cmd,
+ &debit_account)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_credit_payto_uri (cmd,
+ &credit_account)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_amount (cmd,
+ &amount)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_wtid (cmd,
+ &wtid)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_exchange_url (cmd,
+ &exchange_base_url)) )
+ return; /* not an event we care about */
+ /* Seek "/history/outgoing" starting row. */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Command %s is relevant for debit history!\n",
+ cmd->label);
+ if ( (NULL != ic->row_id_start) &&
+ (*(ic->row_id_start) == *row_id) &&
+ (! ic->ok) )
+ {
+ /* Until here, nothing counted. */
+ ic->ok = true;
+ return;
+ }
+ /* when 'start' was _not_ given, then ok == GNUNET_YES */
+ if (! ic->ok)
+ return; /* skip until we find the marker */
+ if (ic->total >= GNUNET_MAX (hs->num_results,
+ -hs->num_results) )
+ {
+ TALER_LOG_DEBUG ("Hit history limit\n");
+ return;
}
+ TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
+ debit_account,
+ credit_account,
+ hs->account_url);
+ /* found matching record, make sure we have room */
+ if (ic->pos == ic->total)
+ GNUNET_array_grow (ic->h,
+ ic->total,
+ ic->pos * 2);
+ ic->h[ic->pos].c_url = GNUNET_strdup (credit_account);
+ ic->h[ic->pos].d_url = GNUNET_strdup (debit_account);
+ ic->h[ic->pos].details.credit_account_uri = ic->h[ic->pos].c_url;
+ ic->h[ic->pos].details.amount = *amount;
+ ic->h[ic->pos].row_id = *row_id;
+ ic->h[ic->pos].details.wtid = *wtid;
+ ic->h[ic->pos].details.exchange_base_url = exchange_base_url;
+ ic->pos++;
}
@@ -179,163 +282,118 @@ print_expected (struct History *h,
* to be allocated, and the second to actually populate every
* element.
*
- * @param is interpreter state (supposedly having the
- * current CMD pointing at a "history" CMD).
+ * @param hs history state command context
* @param[out] rh history array to initialize.
* @return number of entries in @a rh.
*/
static unsigned int
-build_history (struct TALER_TESTING_Interpreter *is,
+build_history (struct HistoryState *hs,
struct History **rh)
{
- struct HistoryState *hs = is->commands[is->ip].cls;
- unsigned int total;
- unsigned int pos;
- struct History *h;
- const struct TALER_TESTING_Command *add_incoming_cmd;
- int inc;
- int start;
- int end;
- /* #GNUNET_YES whenever either no 'start' value was given for the history
- * query, or the given value is found in the list of all the CMDs. */
- int ok;
- const uint64_t *row_id_start = NULL;
+ struct TALER_TESTING_Interpreter *is = hs->is;
+ struct IteratorContext ic = {
+ .hs = hs
+ };
if (NULL != hs->start_row_reference)
{
- TALER_LOG_INFO
- ("`%s': start row given via reference `%s'\n",
+ const struct TALER_TESTING_Command *add_incoming_cmd;
+
+ TALER_LOG_INFO (
+ "`%s': start row given via reference `%s'\n",
TALER_TESTING_interpreter_get_current_label (is),
hs->start_row_reference);
- add_incoming_cmd = TALER_TESTING_interpreter_lookup_command
- (is, hs->start_row_reference);
+ add_incoming_cmd = TALER_TESTING_interpreter_lookup_command (
+ is,
+ hs->start_row_reference);
GNUNET_assert (NULL != add_incoming_cmd);
GNUNET_assert (GNUNET_OK ==
- TALER_TESTING_get_trait_uint64 (add_incoming_cmd,
- 0,
- &row_id_start));
+ TALER_TESTING_get_trait_row (add_incoming_cmd,
+ &ic.row_id_start));
}
+ ic.ok = false;
+ if (NULL == ic.row_id_start)
+ ic.ok = true;
+ GNUNET_array_grow (ic.h,
+ ic.total,
+ 4);
GNUNET_assert (0 != hs->num_results);
- if (0 == is->ip)
- {
- TALER_LOG_DEBUG ("Checking history at first CMD..\n");
- *rh = NULL;
- return 0;
- }
+ TALER_TESTING_iterate (is,
+ hs->num_results > 0,
+ &command_cb,
+ &ic);
+ GNUNET_assert (ic.ok);
+ GNUNET_array_grow (ic.h,
+ ic.total,
+ ic.pos);
+ if (0 == ic.pos)
+ TALER_LOG_DEBUG ("Empty credit history computed\n");
+ *rh = ic.h;
+ return ic.pos;
+}
- /* AKA 'delta' */
- if (hs->num_results > 0)
- {
- inc = 1; /* _inc_rement: go forwards */
- start = 0;
- end = is->ip;
- }
+
+/**
+ * Normalize IBAN-based payto URI in @a in.
+ *
+ * @param in input payto://-URI to normalize
+ * @return normalized IBAN for the test
+ */
+static char *
+normalize (const char *in)
+{
+ char *npt;
+ const char *q = strchr (in,
+ '?');
+ const char *mptr;
+ const char *bic;
+ const char *iban;
+
+ if (NULL == q)
+ npt = GNUNET_strdup (in);
else
+ npt = GNUNET_strndup (in,
+ q - in);
+ if (0 != strncasecmp (npt,
+ "payto://",
+ strlen ("payto://")))
{
- inc = -1; /* decrement: we go backwards */
- start = is->ip - 1;
- end = -1; /* range is exclusive, do look at 0! */
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Invalid payto: %s\n",
+ npt);
+ GNUNET_free (npt);
+ return NULL;
}
-
- ok = GNUNET_NO;
- if (NULL == row_id_start)
- ok = GNUNET_YES;
- h = NULL;
- total = 0;
- GNUNET_array_grow (h,
- total,
- 4);
- pos = 0;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Checking commands %u to %u for debit history\n",
- start,
- end);
- for (int off = start; off != end; off += inc)
+ mptr = npt + strlen ("payto://");
+ bic = strchr (mptr, '/');
+ if (NULL == bic)
{
- const struct TALER_TESTING_Command *cmd = &is->commands[off];
- const uint64_t *row_id;
- const char *debit_account;
- const char *credit_account;
- const struct TALER_Amount *amount;
- const struct TALER_WireTransferIdentifierRawP *wtid;
- const char *exchange_base_url;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Checking if command %s is relevant for debit history\n",
- cmd->label);
- if ( (GNUNET_OK !=
- TALER_TESTING_get_trait_bank_row (cmd,
- &row_id)) ||
- (GNUNET_OK !=
- TALER_TESTING_get_trait_payto (cmd,
- TALER_TESTING_PT_DEBIT,
- &debit_account)) ||
- (GNUNET_OK !=
- TALER_TESTING_get_trait_payto (cmd,
- TALER_TESTING_PT_CREDIT,
- &credit_account)) ||
- (GNUNET_OK !=
- TALER_TESTING_get_trait_amount_obj (cmd,
- 0,
- &amount)) ||
- (GNUNET_OK !=
- TALER_TESTING_get_trait_wtid (cmd,
- 0,
- &wtid)) ||
- (GNUNET_OK !=
- TALER_TESTING_get_trait_url (cmd,
- TALER_TESTING_UT_EXCHANGE_BASE_URL,
- &exchange_base_url)) )
- continue; /* not an event we care about */
- /* Seek "/history/outgoing" starting row. */
+ GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Command %s is relevant for debit history!\n",
- cmd->label);
- if ( (NULL != row_id_start) &&
- (*row_id_start == *row_id) &&
- (GNUNET_NO == ok) )
- {
- /* Until here, nothing counted. */
- ok = GNUNET_YES;
- continue;
- }
- /* when 'start' was _not_ given, then ok == GNUNET_YES */
- if (GNUNET_NO == ok)
- continue; /* skip until we find the marker */
- if (total >= GNUNET_MAX (hs->num_results,
- -hs->num_results) )
- {
- TALER_LOG_DEBUG ("Hit history limit\n");
- break;
- }
- TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
- debit_account,
- credit_account,
- hs->account_url);
- /* found matching record, make sure we have room */
- if (pos == total)
- GNUNET_array_grow (h,
- total,
- pos * 2);
- h[pos].c_url = GNUNET_strdup (credit_account);
- h[pos].d_url = GNUNET_strdup (debit_account);
- h[pos].details.credit_account_url = h[pos].c_url;
- h[pos].details.debit_account_url = h[pos].d_url;
- h[pos].details.amount = *amount;
- h[pos].row_id = *row_id;
- h[pos].details.wtid = *wtid;
- h[pos].details.exchange_base_url = exchange_base_url;
- pos++;
+ "Invalid payto: %s\n",
+ npt);
+ GNUNET_free (npt);
+ return NULL;
+ }
+ bic++;
+ iban = strchr (bic, '/');
+ if (NULL != iban)
+ {
+ /* need to remove bic */
+ char *n;
+
+ iban++;
+ GNUNET_asprintf (&n,
+ "payto://%.*s/%s",
+ (int) ((bic - mptr) - 1),
+ mptr,
+ iban);
+ GNUNET_free (npt);
+ npt = n;
}
- GNUNET_assert (GNUNET_YES == ok);
- GNUNET_array_grow (h,
- total,
- pos);
- if (0 == pos)
- TALER_LOG_DEBUG ("Empty debit history computed\n");
- *rh = h;
- return total;
+ return npt;
}
@@ -350,12 +408,15 @@ build_history (struct TALER_TESTING_Interpreter *is,
* @param details the expected transaction details.
* @return #GNUNET_OK if the transaction is what we expect.
*/
-static int
+static enum GNUNET_GenericReturnValue
check_result (struct History *h,
uint64_t total,
unsigned int off,
const struct TALER_BANK_DebitDetails *details)
{
+ char *u1;
+ char *u2;
+
if (off >= total)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -368,14 +429,33 @@ check_result (struct History *h,
off);
return GNUNET_SYSERR;
}
+ u1 = normalize (h[off].details.credit_account_uri);
+ if (NULL == u1)
+ return GNUNET_SYSERR;
+ u2 = normalize (details->credit_account_uri);
+ if (NULL == u2)
+ {
+ GNUNET_free (u1);
+ return GNUNET_SYSERR;
+ }
if ( (0 != GNUNET_memcmp (&h[off].details.wtid,
&details->wtid)) ||
(0 != TALER_amount_cmp (&h[off].details.amount,
&details->amount)) ||
- (0 != strcasecmp (h[off].details.credit_account_url,
- details->credit_account_url)) )
+ (0 != strcasecmp (u1,
+ u2)) )
{
GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "expected debit_account_uri: %s with %s for %s\n",
+ u1,
+ TALER_amount2s (&h[off].details.amount),
+ TALER_B2S (&h[off].details.wtid));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "actual debit_account_uri: %s with %s for %s\n",
+ u2,
+ TALER_amount2s (&details->amount),
+ TALER_B2S (&details->wtid));
print_expected (h,
total,
off);
@@ -393,89 +473,86 @@ check_result (struct History *h,
* finally check it against what the bank returned.
*
* @param cls closure.
- * @param http_status HTTP response code, #MHD_HTTP_OK (200)
- * for successful status request 0 if the bank's reply is
- * bogus (fails to follow the protocol),
- * #MHD_HTTP_NO_CONTENT if there are no more results; on
- * success the last callback is always of this status
- * (even if `abs(num_results)` were already returned).
- * @param ec taler status code.
- * @param row_id monotonically increasing counter corresponding to
- * the transaction.
- * @param details details about the wire transfer.
- * @param json detailed response from the HTTPD, or NULL if
- * reply was not in JSON.
- * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
+ * @param dhr http response details
*/
-static int
+static void
history_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint64_t row_id,
- const struct TALER_BANK_DebitDetails *details,
- const json_t *json)
+ const struct TALER_BANK_DebitHistoryResponse *dhr)
{
- struct TALER_TESTING_Interpreter *is = cls;
- struct HistoryState *hs = is->commands[is->ip].cls;
+ struct HistoryState *hs = cls;
+ struct TALER_TESTING_Interpreter *is = hs->is;
- (void) row_id;
- if (NULL == details)
+ hs->hh = NULL;
+ switch (dhr->http_status)
{
- hs->hh = NULL;
- if ( (hs->results_obtained != hs->total) ||
- (GNUNET_YES == hs->failed) ||
- (MHD_HTTP_NO_CONTENT != http_status) )
+ case 0:
+ GNUNET_break (0);
+ goto error;
+ case MHD_HTTP_OK:
+ for (unsigned int i = 0; i<dhr->details.ok.details_length; i++)
{
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Expected history of length %u, got %llu;"
- " HTTP status code: %u/%d, failed: %d\n",
- hs->total,
- (unsigned long long) hs->results_obtained,
- http_status,
- (int) ec,
- hs->failed);
- print_expected (hs->h,
- hs->total,
- UINT_MAX);
- TALER_TESTING_interpreter_fail (is);
- return GNUNET_SYSERR;
+ const struct TALER_BANK_DebitDetails *dd =
+ &dhr->details.ok.details[i];
+
+ /* check current element */
+ if (GNUNET_OK !=
+ check_result (hs->h,
+ hs->total,
+ hs->results_obtained,
+ dd))
+ {
+ GNUNET_break (0);
+ json_dumpf (dhr->response,
+ stderr,
+ JSON_COMPACT);
+ hs->failed = true;
+ hs->hh = NULL;
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ hs->results_obtained++;
}
TALER_TESTING_interpreter_next (is);
- return GNUNET_OK;
- }
- if (MHD_HTTP_OK != http_status)
- {
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ if (0 == hs->total)
+ {
+ /* not found is OK for empty history */
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ GNUNET_break (0);
+ goto error;
+ case MHD_HTTP_NOT_FOUND:
+ if (0 == hs->total)
+ {
+ /* not found is OK for empty history */
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ GNUNET_break (0);
+ goto error;
+ default:
hs->hh = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unwanted response code from /history/outgoing: %u\n",
- http_status);
+ "Unwanted response code from /history/incoming: %u\n",
+ dhr->http_status);
TALER_TESTING_interpreter_fail (is);
- return GNUNET_SYSERR;
+ return;
}
-
- /* check current element */
- if (GNUNET_OK != check_result (hs->h,
- hs->total,
- hs->results_obtained,
- details))
- {
- char *acc;
-
- GNUNET_break (0);
- acc = json_dumps (json,
- JSON_COMPACT);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Result %u was `%s'\n",
- (unsigned int) hs->results_obtained++,
- acc);
- if (NULL != acc)
- free (acc);
- hs->failed = GNUNET_YES;
- return GNUNET_SYSERR;
- }
- hs->results_obtained++;
- return GNUNET_OK;
+error:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected history of length %u, got %llu;"
+ " HTTP status code: %u/%d, failed: %d\n",
+ hs->total,
+ (unsigned long long) hs->results_obtained,
+ dhr->http_status,
+ (int) dhr->ec,
+ hs->failed ? 1 : 0);
+ print_expected (hs->h,
+ hs->total,
+ UINT_MAX);
+ TALER_TESTING_interpreter_fail (is);
}
@@ -496,6 +573,7 @@ history_run (void *cls,
const uint64_t *row_ptr;
(void) cmd;
+ hs->is = is;
/* Get row_id from trait. */
if (NULL != hs->start_row_reference)
{
@@ -508,22 +586,24 @@ history_run (void *cls,
if (NULL == history_cmd)
TALER_TESTING_FAIL (is);
if (GNUNET_OK !=
- TALER_TESTING_get_trait_uint64 (history_cmd,
- 0,
- &row_ptr))
+ TALER_TESTING_get_trait_row (history_cmd,
+ &row_ptr))
TALER_TESTING_FAIL (is);
else
row_id = *row_ptr;
TALER_LOG_DEBUG ("row id (from trait) is %llu\n",
(unsigned long long) row_id);
}
- hs->total = build_history (is, &hs->h);
- hs->hh = TALER_BANK_debit_history (is->ctx,
- &hs->auth,
- row_id,
- hs->num_results,
- &history_cb,
- is);
+ hs->total = build_history (hs,
+ &hs->h);
+ hs->hh = TALER_BANK_debit_history (
+ TALER_TESTING_interpreter_get_context (is),
+ &hs->auth,
+ row_id,
+ hs->num_results,
+ GNUNET_TIME_UNIT_ZERO,
+ &history_cb,
+ hs);
GNUNET_assert (NULL != hs->hh);
}
@@ -544,7 +624,8 @@ history_cleanup (void *cls,
(void) cmd;
if (NULL != hs->hh)
{
- TALER_LOG_WARNING ("/history/outgoing did not complete\n");
+ TALER_TESTING_command_incomplete (hs->is,
+ cmd->label);
TALER_BANK_debit_history_cancel (hs->hh);
}
for (unsigned int off = 0; off<hs->total; off++)
@@ -552,22 +633,11 @@ history_cleanup (void *cls,
GNUNET_free (hs->h[off].c_url);
GNUNET_free (hs->h[off].d_url);
}
- GNUNET_free_non_null (hs->h);
+ GNUNET_free (hs->h);
GNUNET_free (hs);
}
-/**
- * Make a "history" CMD.
- *
- * @param label command label.
- * @param auth login data to use
- * @param start_row_reference reference to a command that can
- * offer a row identifier, to be used as the starting row
- * to accept in the result.
- * @param num_results how many rows we want in the result.
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_bank_debits (const char *label,
const struct TALER_BANK_AuthenticationData *auth,
@@ -587,8 +657,7 @@ TALER_TESTING_cmd_bank_debits (const char *label,
.label = label,
.cls = hs,
.run = &history_run,
- .cleanup = &history_cleanup,
- .traits = &history_traits
+ .cleanup = &history_cleanup
};
return cmd;
diff --git a/src/testing/testing_api_cmd_bank_transfer.c b/src/testing/testing_api_cmd_bank_transfer.c
index 7e7174b24..bfb29e120 100644
--- a/src/testing/testing_api_cmd_bank_transfer.c
+++ b/src/testing/testing_api_cmd_bank_transfer.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018-2020 Taler Systems SA
+ Copyright (C) 2018-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
@@ -97,7 +97,7 @@ struct TransferState
/**
* Timestamp of the transaction (as returned from the bank).
*/
- struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_TIME_Timestamp timestamp;
/**
* Configuration filename. Used to get the tip reserve key
@@ -149,8 +149,7 @@ do_retry (void *cls)
struct TransferState *fts = cls;
fts->retry_task = NULL;
- fts->is->commands[fts->is->ip].last_req_time
- = GNUNET_TIME_absolute_get ();
+ TALER_TESTING_touch_cmd (fts->is);
transfer_run (fts,
NULL,
fts->is);
@@ -163,43 +162,35 @@ do_retry (void *cls)
* acceptable.
*
* @param cls closure with the interpreter state
- * @param http_status HTTP response code, #MHD_HTTP_OK (200) for
- * successful status request; 0 if the exchange's reply is
- * bogus (fails to follow the protocol)
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param serial_id unique ID of the wire transfer
- * @param timestamp time stamp of the transaction made.
+ * @param tr response details
*/
static void
confirmation_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint64_t serial_id,
- struct GNUNET_TIME_Absolute timestamp)
+ const struct TALER_BANK_TransferResponse *tr)
{
struct TransferState *fts = cls;
struct TALER_TESTING_Interpreter *is = fts->is;
fts->weh = NULL;
- if (MHD_HTTP_OK != http_status)
+ if (MHD_HTTP_OK != tr->http_status)
{
if (0 != fts->do_retry)
{
fts->do_retry--;
- if ( (0 == http_status) ||
- (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
- (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
+ if ( (0 == tr->http_status) ||
+ (TALER_EC_GENERIC_DB_SOFT_FAILURE == tr->ec) ||
+ (MHD_HTTP_INTERNAL_SERVER_ERROR == tr->http_status) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Retrying transfer failed with %u/%d\n",
- http_status,
- (int) ec);
+ tr->http_status,
+ (int) tr->ec);
/* on DB conflicts, do not use backoff */
- if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
+ if (TALER_EC_GENERIC_DB_SOFT_FAILURE == tr->ec)
fts->backoff = GNUNET_TIME_UNIT_ZERO;
else
fts->backoff = EXCHANGE_LIB_BACKOFF (fts->backoff);
- fts->is->commands[fts->is->ip].num_tries++;
+ TALER_TESTING_inc_tries (fts->is);
fts->retry_task
= GNUNET_SCHEDULER_add_delayed (fts->backoff,
&do_retry,
@@ -207,17 +198,14 @@ confirmation_cb (void *cls,
return;
}
}
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Fakebank returned HTTP status %u/%d\n",
- http_status,
- (int) ec);
- TALER_TESTING_interpreter_fail (is);
+ TALER_TESTING_unexpected_status (is,
+ tr->http_status,
+ MHD_HTTP_OK);
return;
}
- fts->serial_id = serial_id;
- fts->timestamp = timestamp;
+ fts->serial_id = tr->details.ok.row_id;
+ fts->timestamp = tr->details.ok.timestamp;
TALER_TESTING_interpreter_next (is);
}
@@ -252,8 +240,8 @@ transfer_run (void *cls,
&buf_size);
fts->is = is;
fts->weh
- = TALER_BANK_transfer
- (TALER_TESTING_interpreter_get_context (is),
+ = TALER_BANK_transfer (
+ TALER_TESTING_interpreter_get_context (is),
&fts->auth,
buf,
buf_size,
@@ -284,9 +272,8 @@ transfer_cleanup (void *cls,
if (NULL != fts->weh)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %s did not complete\n",
- cmd->label);
+ TALER_TESTING_command_incomplete (fts->is,
+ cmd->label);
TALER_BANK_transfer_cancel (fts->weh);
fts->weh = NULL;
}
@@ -310,7 +297,7 @@ transfer_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success.
*/
-static int
+static enum GNUNET_GenericReturnValue
transfer_traits (void *cls,
const void **ret,
const char *trait,
@@ -318,17 +305,16 @@ transfer_traits (void *cls,
{
struct TransferState *fts = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BASE_URL,
- fts->exchange_base_url),
+ TALER_TESTING_make_trait_exchange_url (
+ fts->exchange_base_url),
TALER_TESTING_make_trait_bank_row (&fts->serial_id),
- TALER_TESTING_make_trait_payto (TALER_TESTING_PT_CREDIT,
- fts->payto_credit_account),
- TALER_TESTING_make_trait_payto (TALER_TESTING_PT_DEBIT,
- fts->payto_debit_account),
- TALER_TESTING_make_trait_amount_obj (0, &fts->amount),
- TALER_TESTING_make_trait_absolute_time (0, &fts->timestamp),
- TALER_TESTING_make_trait_wtid (0,
- &fts->wtid),
+ TALER_TESTING_make_trait_credit_payto_uri (
+ fts->payto_credit_account),
+ TALER_TESTING_make_trait_debit_payto_uri (
+ fts->payto_debit_account),
+ TALER_TESTING_make_trait_amount (&fts->amount),
+ TALER_TESTING_make_trait_timestamp (0, &fts->timestamp),
+ TALER_TESTING_make_trait_wtid (&fts->wtid),
TALER_TESTING_trait_end ()
};
@@ -339,18 +325,6 @@ transfer_traits (void *cls,
}
-/**
- * Create transfer command.
- *
- * @param label command label.
- * @param amount amount to transfer.
- * @param auth authentication data to use
- * @param payto_debit_account which account sends money.
- * @param payto_credit_account which account receives money.
- * @param wtid wire transfer identifier to use
- * @param exchange_base_url exchange URL to use
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_transfer (const char *label,
const char *amount,
@@ -394,13 +368,6 @@ TALER_TESTING_cmd_transfer (const char *label,
}
-/**
- * Modify a transfer command to enable retries when the reserve is not yet
- * full or we get other transient errors from the bank.
- *
- * @param cmd a fakebank transfer command
- * @return the command with retries enabled
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_transfer_retry (struct TALER_TESTING_Command cmd)
{
diff --git a/src/testing/testing_api_cmd_batch.c b/src/testing/testing_api_cmd_batch.c
index 48ccf55f1..5bb7b974e 100644
--- a/src/testing/testing_api_cmd_batch.c
+++ b/src/testing/testing_api_cmd_batch.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 Taler Systems SA
+ 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
@@ -38,6 +38,11 @@ struct BatchState
struct TALER_TESTING_Command *batch;
/**
+ * My command (the batch command).
+ */
+ const struct TALER_TESTING_Command *cmd;
+
+ /**
* Internal command pointer.
*/
unsigned int batch_ip;
@@ -58,6 +63,7 @@ batch_run (void *cls,
{
struct BatchState *bs = cls;
+ bs->cmd = cmd;
if (NULL != bs->batch[bs->batch_ip].label)
TALER_LOG_INFO ("Running batched command: %s\n",
bs->batch[bs->batch_ip].label);
@@ -97,9 +103,10 @@ batch_cleanup (void *cls,
for (unsigned int i = 0;
NULL != bs->batch[i].label;
i++)
- bs->batch[i].cleanup (bs->batch[i].cls,
- &bs->batch[i]);
- GNUNET_free_non_null (bs->batch);
+ if (NULL != bs->batch[i].cleanup)
+ bs->batch[i].cleanup (bs->batch[i].cls,
+ &bs->batch[i]);
+ GNUNET_free (bs->batch);
GNUNET_free (bs);
}
@@ -113,22 +120,15 @@ batch_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success.
*/
-static int
+static enum GNUNET_GenericReturnValue
batch_traits (void *cls,
const void **ret,
const char *trait,
unsigned int index)
{
-#define CURRENT_CMD_INDEX 0
-#define BATCH_INDEX 1
-
struct BatchState *bs = cls;
-
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_cmd
- (CURRENT_CMD_INDEX, &bs->batch[bs->batch_ip]),
- TALER_TESTING_make_trait_cmd
- (BATCH_INDEX, bs->batch),
+ TALER_TESTING_make_trait_batch_cmds (bs->batch),
TALER_TESTING_trait_end ()
};
@@ -140,18 +140,6 @@ batch_traits (void *cls,
}
-/**
- * Create a "batch" command. Such command takes a
- * end_CMD-terminated array of CMDs and executed them.
- * Once it hits the end CMD, it passes the control
- * to the next top-level CMD, regardless of it being
- * another batch or ordinary CMD.
- *
- * @param label the command label.
- * @param batch array of CMDs to execute.
- *
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_batch (const char *label,
struct TALER_TESTING_Command *batch)
@@ -168,9 +156,9 @@ TALER_TESTING_cmd_batch (const char *label,
bs->batch = GNUNET_new_array (i + 1,
struct TALER_TESTING_Command);
- memcpy (bs->batch,
- batch,
- sizeof (struct TALER_TESTING_Command) * i);
+ GNUNET_memcpy (bs->batch,
+ batch,
+ sizeof (struct TALER_TESTING_Command) * i);
{
struct TALER_TESTING_Command cmd = {
.cls = bs,
@@ -185,48 +173,63 @@ TALER_TESTING_cmd_batch (const char *label,
}
-/**
- * Advance internal pointer to next command.
- *
- * @param is interpreter state.
- */
-void
-TALER_TESTING_cmd_batch_next (struct TALER_TESTING_Interpreter *is)
+bool
+TALER_TESTING_cmd_batch_next (struct TALER_TESTING_Interpreter *is,
+ void *cls)
{
- struct BatchState *bs = is->commands[is->ip].cls;
+ struct BatchState *bs = cls;
+ struct TALER_TESTING_Command *bcmd = &bs->batch[bs->batch_ip];
- if (NULL == bs->batch[bs->batch_ip].label)
+ if (NULL == bcmd->label)
{
- is->commands[is->ip].finish_time = GNUNET_TIME_absolute_get ();
- is->ip++;
- return;
+ /* This batch is done */
+ return true;
}
- bs->batch[bs->batch_ip].finish_time = GNUNET_TIME_absolute_get ();
+ if (TALER_TESTING_cmd_is_batch (bcmd))
+ {
+ if (TALER_TESTING_cmd_batch_next (is,
+ bcmd->cls))
+ {
+ /* sub-batch is done */
+ bcmd->finish_time = GNUNET_TIME_absolute_get ();
+ bs->batch_ip++;
+ return false;
+ }
+ }
+ /* Simple command is done */
+ bcmd->finish_time = GNUNET_TIME_absolute_get ();
bs->batch_ip++;
+ return false;
}
-/**
- * Test if this command is a batch command.
- *
- * @return false if not, true if it is a batch command
- */
-int
+bool
TALER_TESTING_cmd_is_batch (const struct TALER_TESTING_Command *cmd)
{
return cmd->run == &batch_run;
}
-/**
- * Obtain what command the batch is at.
- *
- * @return cmd current batch command
- */
struct TALER_TESTING_Command *
TALER_TESTING_cmd_batch_get_current (const struct TALER_TESTING_Command *cmd)
{
struct BatchState *bs = cmd->cls;
+ GNUNET_assert (cmd->run == &batch_run);
return &bs->batch[bs->batch_ip];
}
+
+
+void
+TALER_TESTING_cmd_batch_set_current (const struct TALER_TESTING_Command *cmd,
+ unsigned int new_ip)
+{
+ struct BatchState *bs = cmd->cls;
+
+ /* sanity checks */
+ GNUNET_assert (cmd->run == &batch_run);
+ for (unsigned int i = 0; i < new_ip; i++)
+ GNUNET_assert (NULL != bs->batch[i].label);
+ /* actual logic */
+ bs->batch_ip = new_ip;
+}
diff --git a/src/testing/testing_api_cmd_batch_deposit.c b/src/testing/testing_api_cmd_batch_deposit.c
new file mode 100644
index 000000000..5139d3524
--- /dev/null
+++ b/src/testing/testing_api_cmd_batch_deposit.c
@@ -0,0 +1,656 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018-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/>
+*/
+/**
+ * @file testing/testing_api_cmd_batch_deposit.c
+ * @brief command for testing /batch-deposit.
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * How often do we retry before giving up?
+ */
+#define NUM_RETRIES 5
+
+/**
+ * How long do we wait AT MOST when retrying?
+ */
+#define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MILLISECONDS, 100)
+
+
+/**
+ * Information per coin in the batch.
+ */
+struct Coin
+{
+
+ /**
+ * Amount to deposit.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Deposit fee.
+ */
+ struct TALER_Amount deposit_fee;
+
+ /**
+ * Our coin signature.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * Reference to any command that is able to provide a coin,
+ * possibly using $LABEL#$INDEX notation.
+ */
+ char *coin_reference;
+
+ /**
+ * Denomination public key of the coin.
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+
+ /**
+ * The command being referenced.
+ */
+ const struct TALER_TESTING_Command *coin_cmd;
+
+ /**
+ * Expected entry in the coin history created by this
+ * coin.
+ */
+ struct TALER_EXCHANGE_CoinHistoryEntry che;
+
+ /**
+ * Index of the coin at @e coin_cmd.
+ */
+ unsigned int coin_idx;
+};
+
+
+/**
+ * State for a "batch deposit" CMD.
+ */
+struct BatchDepositState
+{
+
+ /**
+ * Refund deadline. Zero for no refunds.
+ */
+ struct GNUNET_TIME_Timestamp refund_deadline;
+
+ /**
+ * Wire deadline.
+ */
+ struct GNUNET_TIME_Timestamp wire_deadline;
+
+ /**
+ * Timestamp of the /deposit operation in the wallet (contract signing time).
+ */
+ struct GNUNET_TIME_Timestamp wallet_timestamp;
+
+ /**
+ * How long do we wait until we retry?
+ */
+ struct GNUNET_TIME_Relative backoff;
+
+ /**
+ * When did the exchange receive the deposit?
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
+ * Signing key used by the exchange to sign the
+ * deposit confirmation.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Set (by the interpreter) to a fresh private key. This
+ * key will be used to sign the deposit request.
+ */
+ struct TALER_MerchantPrivateKeyP merchant_priv;
+
+ /**
+ * Deposit handle while operation is running.
+ */
+ struct TALER_EXCHANGE_BatchDepositHandle *dh;
+
+ /**
+ * Array of coins to batch-deposit.
+ */
+ struct Coin *coins;
+
+ /**
+ * Wire details of who is depositing -- this would be merchant
+ * wire details in a normal scenario.
+ */
+ json_t *wire_details;
+
+ /**
+ * JSON string describing what a proposal is about.
+ */
+ json_t *contract_terms;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Task scheduled to try later.
+ */
+ struct GNUNET_SCHEDULER_Task *retry_task;
+
+ /**
+ * Deposit confirmation signature from the exchange.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+ /**
+ * Reference to previous deposit operation.
+ * Only present if we're supposed to replay the previous deposit.
+ */
+ const char *deposit_reference;
+
+ /**
+ * If @e coin_reference refers to an operation that generated
+ * an array of coins, this value determines which coin to pick.
+ */
+ unsigned int num_coins;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Set to true if the /deposit succeeded
+ * and we now can provide the resulting traits.
+ */
+ bool deposit_succeeded;
+
+};
+
+
+/**
+ * Callback to analyze the /batch-deposit response, just used to check if the
+ * response code is acceptable.
+ *
+ * @param cls closure.
+ * @param dr deposit response details
+ */
+static void
+batch_deposit_cb (void *cls,
+ const struct TALER_EXCHANGE_BatchDepositResult *dr)
+{
+ struct BatchDepositState *ds = cls;
+
+ ds->dh = NULL;
+ if (ds->expected_response_code != dr->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ dr->hr.http_status,
+ ds->expected_response_code);
+ return;
+ }
+ if (MHD_HTTP_OK == dr->hr.http_status)
+ {
+ ds->deposit_succeeded = GNUNET_YES;
+ ds->exchange_timestamp = dr->details.ok.deposit_timestamp;
+ ds->exchange_pub = *dr->details.ok.exchange_pub;
+ ds->exchange_sig = *dr->details.ok.exchange_sig;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+batch_deposit_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct BatchDepositState *ds = cls;
+ const struct TALER_DenominationSignature *denom_pub_sig;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ enum TALER_ErrorCode ec;
+ struct TALER_WireSaltP wire_salt;
+ struct TALER_MerchantWireHashP h_wire;
+ const char *payto_uri;
+ struct TALER_EXCHANGE_CoinDepositDetail cdds[ds->num_coins];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &wire_salt),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *exchange_url
+ = TALER_TESTING_get_exchange_url (is);
+
+ (void) cmd;
+ if (NULL == exchange_url)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ memset (cdds,
+ 0,
+ sizeof (cdds));
+ ds->is = is;
+ GNUNET_assert (NULL != ds->wire_details);
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (ds->wire_details,
+ spec,
+ NULL, NULL))
+ {
+ json_dumpf (ds->wire_details,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (ds->contract_terms,
+ &h_contract_terms))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_merchant_wire_signature_hash (ds->wire_details,
+ &h_wire));
+ if (! GNUNET_TIME_absolute_is_zero (ds->refund_deadline.abs_time))
+ {
+ struct GNUNET_TIME_Relative refund_deadline;
+
+ refund_deadline
+ = GNUNET_TIME_absolute_get_remaining (ds->refund_deadline.abs_time);
+ ds->wire_deadline
+ =
+ GNUNET_TIME_relative_to_timestamp (
+ GNUNET_TIME_relative_multiply (refund_deadline,
+ 2));
+ }
+ else
+ {
+ ds->refund_deadline = ds->wallet_timestamp;
+ ds->wire_deadline = GNUNET_TIME_timestamp_get ();
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&ds->merchant_priv.eddsa_priv,
+ &merchant_pub.eddsa_pub);
+
+ for (unsigned int i = 0; i<ds->num_coins; i++)
+ {
+ struct Coin *coin = &ds->coins[i];
+ struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i];
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+ const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
+
+ GNUNET_assert (NULL != coin->coin_reference);
+ cdd->amount = coin->amount;
+ coin->coin_cmd = TALER_TESTING_interpreter_lookup_command (
+ is,
+ coin->coin_reference);
+ if (NULL == coin->coin_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (coin->coin_cmd,
+ coin->coin_idx,
+ &coin_priv)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_age_commitment_proof (coin->coin_cmd,
+ coin->coin_idx,
+ &age_commitment_proof))
+ ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_pub (coin->coin_cmd,
+ coin->coin_idx,
+ &coin->denom_pub)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_sig (coin->coin_cmd,
+ coin->coin_idx,
+ &denom_pub_sig)) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (NULL != age_commitment_proof)
+ {
+ TALER_age_commitment_hash (&age_commitment_proof->commitment,
+ &cdd->h_age_commitment);
+ }
+ coin->deposit_fee = coin->denom_pub->fees.deposit;
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+ &cdd->coin_pub.eddsa_pub);
+ cdd->denom_sig = *denom_pub_sig;
+ cdd->h_denom_pub = coin->denom_pub->h_key;
+ TALER_wallet_deposit_sign (&coin->amount,
+ &coin->denom_pub->fees.deposit,
+ &h_wire,
+ &h_contract_terms,
+ NULL, /* wallet_data_hash */
+ &cdd->h_age_commitment,
+ NULL, /* hash of extensions */
+ &coin->denom_pub->h_key,
+ ds->wallet_timestamp,
+ &merchant_pub,
+ ds->refund_deadline,
+ coin_priv,
+ &cdd->coin_sig);
+ coin->coin_sig = cdd->coin_sig;
+ coin->che.type = TALER_EXCHANGE_CTT_DEPOSIT;
+ coin->che.amount = coin->amount;
+ coin->che.details.deposit.h_wire = h_wire;
+ coin->che.details.deposit.h_contract_terms = h_contract_terms;
+ coin->che.details.deposit.no_h_policy = true;
+ coin->che.details.deposit.no_wallet_data_hash = true;
+ coin->che.details.deposit.wallet_timestamp = ds->wallet_timestamp;
+ coin->che.details.deposit.merchant_pub = merchant_pub;
+ coin->che.details.deposit.refund_deadline = ds->refund_deadline;
+ coin->che.details.deposit.sig = cdd->coin_sig;
+ coin->che.details.deposit.no_hac = GNUNET_is_zero (&cdd->h_age_commitment);
+ coin->che.details.deposit.hac = cdd->h_age_commitment;
+ coin->che.details.deposit.deposit_fee = coin->denom_pub->fees.deposit;
+ }
+
+ GNUNET_assert (NULL == ds->dh);
+ {
+ struct TALER_EXCHANGE_DepositContractDetail dcd = {
+ .wire_deadline = ds->wire_deadline,
+ .merchant_payto_uri = payto_uri,
+ .wire_salt = wire_salt,
+ .h_contract_terms = h_contract_terms,
+ .policy_details = NULL /* FIXME #7270-OEC */,
+ .wallet_timestamp = ds->wallet_timestamp,
+ .merchant_pub = merchant_pub,
+ .refund_deadline = ds->refund_deadline
+ };
+
+ ds->dh = TALER_EXCHANGE_batch_deposit (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ TALER_TESTING_get_keys (is),
+ &dcd,
+ ds->num_coins,
+ cdds,
+ &batch_deposit_cb,
+ ds,
+ &ec);
+ }
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not create deposit with EC %d\n",
+ (int) ec);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "batch-deposit" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct BatchDepositState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+batch_deposit_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct BatchDepositState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_batch_deposit_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ if (NULL != ds->retry_task)
+ {
+ GNUNET_SCHEDULER_cancel (ds->retry_task);
+ ds->retry_task = NULL;
+ }
+ for (unsigned int i = 0; i<ds->num_coins; i++)
+ GNUNET_free (ds->coins[i].coin_reference);
+ GNUNET_free (ds->coins);
+ json_decref (ds->wire_details);
+ json_decref (ds->contract_terms);
+ GNUNET_free (ds);
+}
+
+
+/**
+ * Offer internal data from a "batch-deposit" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+batch_deposit_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct BatchDepositState *ds = cls;
+ const struct Coin *coin = &ds->coins[index];
+ /* Will point to coin cmd internals. */
+ const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv;
+ struct TALER_CoinSpendPublicKeyP coin_spent_pub;
+ const struct TALER_AgeCommitmentProof *age_commitment_proof;
+
+ if (index >= ds->num_coins)
+ {
+ GNUNET_break (0);
+ return GNUNET_NO;
+ }
+ if (NULL == coin->coin_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return GNUNET_NO;
+ }
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (coin->coin_cmd,
+ coin->coin_idx,
+ &coin_spent_priv)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_age_commitment_proof (coin->coin_cmd,
+ coin->coin_idx,
+ &age_commitment_proof)) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return GNUNET_NO;
+ }
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_spent_priv->eddsa_priv,
+ &coin_spent_pub.eddsa_pub);
+
+ {
+ struct TALER_TESTING_Trait traits[] = {
+ /* First two traits are only available if
+ ds->traits is #GNUNET_YES */
+ TALER_TESTING_make_trait_exchange_pub (0,
+ &ds->exchange_pub),
+ TALER_TESTING_make_trait_exchange_sig (0,
+ &ds->exchange_sig),
+ /* These traits are always available */
+ TALER_TESTING_make_trait_wire_details (ds->wire_details),
+ TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
+ TALER_TESTING_make_trait_merchant_priv (&ds->merchant_priv),
+ TALER_TESTING_make_trait_age_commitment_proof (index,
+ age_commitment_proof),
+ TALER_TESTING_make_trait_coin_history (index,
+ &coin->che),
+ TALER_TESTING_make_trait_coin_pub (index,
+ &coin_spent_pub),
+ TALER_TESTING_make_trait_denom_pub (index,
+ coin->denom_pub),
+ TALER_TESTING_make_trait_coin_priv (index,
+ coin_spent_priv),
+ TALER_TESTING_make_trait_coin_sig (index,
+ &coin->coin_sig),
+ TALER_TESTING_make_trait_deposit_amount (index,
+ &coin->amount),
+ TALER_TESTING_make_trait_deposit_fee_amount (index,
+ &coin->deposit_fee),
+ TALER_TESTING_make_trait_timestamp (index,
+ &ds->exchange_timestamp),
+ TALER_TESTING_make_trait_wire_deadline (index,
+ &ds->wire_deadline),
+ TALER_TESTING_make_trait_refund_deadline (index,
+ &ds->refund_deadline),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait ((ds->deposit_succeeded)
+ ? traits
+ : &traits[2],
+ ret,
+ trait,
+ index);
+ }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_batch_deposit (const char *label,
+ const char *target_account_payto,
+ const char *contract_terms,
+ struct GNUNET_TIME_Relative refund_deadline,
+ unsigned int expected_response_code,
+ ...)
+{
+ struct BatchDepositState *ds;
+ va_list ap;
+ unsigned int num_coins = 0;
+ const char *ref;
+
+ va_start (ap,
+ expected_response_code);
+ while (NULL != (ref = va_arg (ap,
+ const char *)))
+ {
+ GNUNET_assert (NULL != va_arg (ap,
+ const char *));
+ num_coins++;
+ }
+ va_end (ap);
+
+ ds = GNUNET_new (struct BatchDepositState);
+ ds->num_coins = num_coins;
+ ds->coins = GNUNET_new_array (num_coins,
+ struct Coin);
+ num_coins = 0;
+ va_start (ap,
+ expected_response_code);
+ while (NULL != (ref = va_arg (ap,
+ const char *)))
+ {
+ struct Coin *coin = &ds->coins[num_coins++];
+ const char *amount = va_arg (ap,
+ const char *);
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_parse_coin_reference (ref,
+ &coin->coin_reference,
+ &coin->coin_idx));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (amount,
+ &coin->amount));
+ }
+ va_end (ap);
+
+ ds->wire_details = TALER_TESTING_make_wire_details (target_account_payto);
+ GNUNET_assert (NULL != ds->wire_details);
+ ds->contract_terms = json_loads (contract_terms,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ GNUNET_CRYPTO_eddsa_key_create (&ds->merchant_priv.eddsa_priv);
+ if (NULL == ds->contract_terms)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse contract terms `%s' for CMD `%s'\n",
+ contract_terms,
+ label);
+ GNUNET_assert (0);
+ }
+ ds->wallet_timestamp = GNUNET_TIME_timestamp_get ();
+ GNUNET_assert (0 ==
+ json_object_set_new (ds->contract_terms,
+ "timestamp",
+ GNUNET_JSON_from_timestamp (
+ ds->wallet_timestamp)));
+ if (! GNUNET_TIME_relative_is_zero (refund_deadline))
+ {
+ ds->refund_deadline = GNUNET_TIME_relative_to_timestamp (refund_deadline);
+ GNUNET_assert (0 ==
+ json_object_set_new (ds->contract_terms,
+ "refund_deadline",
+ GNUNET_JSON_from_timestamp (
+ ds->refund_deadline)));
+ }
+ ds->expected_response_code = expected_response_code;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &batch_deposit_run,
+ .cleanup = &batch_deposit_cleanup,
+ .traits = &batch_deposit_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_batch_deposit.c */
diff --git a/src/testing/testing_api_cmd_batch_withdraw.c b/src/testing/testing_api_cmd_batch_withdraw.c
new file mode 100644
index 000000000..1b056bdbb
--- /dev/null
+++ b/src/testing/testing_api_cmd_batch_withdraw.c
@@ -0,0 +1,557 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018-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/>
+*/
+/**
+ * @file testing/testing_api_cmd_batch_withdraw.c
+ * @brief implements the batch withdraw command
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_extensions.h"
+#include "taler_testing_lib.h"
+
+/**
+ * Information we track per withdrawn coin.
+ */
+struct CoinState
+{
+
+ /**
+ * String describing the denomination value we should withdraw.
+ * A corresponding denomination key must exist in the exchange's
+ * offerings. Can be NULL if @e pk is set instead.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * If @e amount is NULL, this specifies the denomination key to
+ * use. Otherwise, this will be set (by the interpreter) to the
+ * denomination PK matching @e amount.
+ */
+ struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Public key of the coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Blinding key used during the operation.
+ */
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+
+ /**
+ * Values contributed from the exchange during the
+ * withdraw protocol.
+ */
+ struct TALER_ExchangeWithdrawValues exchange_vals;
+
+ /**
+ * Set (by the interpreter) to the exchange's signature over the
+ * coin's public key.
+ */
+ struct TALER_DenominationSignature sig;
+
+ /**
+ * Private key material of the coin, set by the interpreter.
+ */
+ struct TALER_PlanchetMasterSecretP ps;
+
+ /**
+ * If age > 0, put here the corresponding age commitment with its proof and
+ * its hash, respectively.
+ */
+ struct TALER_AgeCommitmentProof age_commitment_proof;
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
+ * Reserve history entry that corresponds to this coin.
+ * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL.
+ */
+ struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
+
+
+};
+
+
+/**
+ * State for a "batch withdraw" CMD.
+ */
+struct BatchWithdrawState
+{
+
+ /**
+ * Which reserve should we withdraw from?
+ */
+ const char *reserve_reference;
+
+ /**
+ * Exchange base URL. Only used as offered trait.
+ */
+ char *exchange_url;
+
+ /**
+ * URI if the reserve we are withdrawing from.
+ */
+ char *reserve_payto_uri;
+
+ /**
+ * Private key of the reserve we are withdrawing from.
+ */
+ struct TALER_ReservePrivateKeyP reserve_priv;
+
+ /**
+ * Public key of the reserve we are withdrawing from.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Interpreter state (during command).
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Withdraw handle (while operation is running).
+ */
+ struct TALER_EXCHANGE_BatchWithdrawHandle *wsh;
+
+ /**
+ * Array of coin states.
+ */
+ struct CoinState *coins;
+
+ /**
+ * Set to the KYC requirement payto hash *if* the exchange replied with a
+ * request for KYC.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Set to the KYC requirement row *if* the exchange replied with
+ * a request for KYC.
+ */
+ uint64_t requirement_row;
+
+ /**
+ * Length of the @e coins array.
+ */
+ unsigned int num_coins;
+
+ /**
+ * Expected HTTP response code to the request.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * An age > 0 signifies age restriction is required.
+ * Same for all coins in the batch.
+ */
+ uint8_t age;
+
+ /**
+ * Force a conflict:
+ */
+ bool force_conflict;
+};
+
+
+/**
+ * "batch withdraw" operation callback; checks that the
+ * response code is expected and store the exchange signature
+ * in the state.
+ *
+ * @param cls closure.
+ * @param wr withdraw response details
+ */
+static void
+reserve_batch_withdraw_cb (void *cls,
+ const struct
+ TALER_EXCHANGE_BatchWithdrawResponse *wr)
+{
+ struct BatchWithdrawState *ws = cls;
+ struct TALER_TESTING_Interpreter *is = ws->is;
+
+ ws->wsh = NULL;
+ if (ws->expected_response_code != wr->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status_with_body (is,
+ wr->hr.http_status,
+ ws->expected_response_code,
+ wr->hr.reply);
+ return;
+ }
+ switch (wr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ for (unsigned int i = 0; i<ws->num_coins; i++)
+ {
+ struct CoinState *cs = &ws->coins[i];
+ const struct TALER_EXCHANGE_PrivateCoinDetails *pcd
+ = &wr->details.ok.coins[i];
+
+ TALER_denom_sig_copy (&cs->sig,
+ &pcd->sig);
+ cs->coin_priv = pcd->coin_priv;
+ GNUNET_CRYPTO_eddsa_key_get_public (&cs->coin_priv.eddsa_priv,
+ &cs->coin_pub.eddsa_pub);
+
+ cs->bks = pcd->bks;
+ TALER_denom_ewv_copy (&cs->exchange_vals,
+ &pcd->exchange_vals);
+ }
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* nothing to check */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* nothing to check */
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* TODO[oec]: Check if age-requirement is the reason */
+ break;
+ case MHD_HTTP_GONE:
+ /* theoretically could check that the key was actually */
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* nothing to check */
+ ws->requirement_row
+ = wr->details.unavailable_for_legal_reasons.requirement_row;
+ ws->h_payto
+ = wr->details.unavailable_for_legal_reasons.h_payto;
+ break;
+ default:
+ /* Unsupported status code (by test harness) */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Batch withdraw test command does not support status code %u\n",
+ wr->hr.http_status);
+ GNUNET_break (0);
+ break;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ */
+static void
+batch_withdraw_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct BatchWithdrawState *ws = cls;
+ const struct TALER_EXCHANGE_Keys *keys = TALER_TESTING_get_keys (is);
+ const struct TALER_ReservePrivateKeyP *rp;
+ const struct TALER_TESTING_Command *create_reserve;
+ const struct TALER_EXCHANGE_DenomPublicKey *dpk;
+ struct TALER_EXCHANGE_WithdrawCoinInput wcis[ws->num_coins];
+ struct TALER_PlanchetMasterSecretP conflict_ps = {0};
+ struct TALER_AgeMask mask = {0};
+
+ (void) cmd;
+ ws->is = is;
+ create_reserve
+ = TALER_TESTING_interpreter_lookup_command (
+ is,
+ ws->reserve_reference);
+
+ if (NULL == create_reserve)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_priv (create_reserve,
+ &rp))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (NULL == ws->exchange_url)
+ ws->exchange_url
+ = GNUNET_strdup (TALER_TESTING_get_exchange_url (is));
+ ws->reserve_priv = *rp;
+ GNUNET_CRYPTO_eddsa_key_get_public (&ws->reserve_priv.eddsa_priv,
+ &ws->reserve_pub.eddsa_pub);
+ ws->reserve_payto_uri
+ = TALER_reserve_make_payto (ws->exchange_url,
+ &ws->reserve_pub);
+
+ if (0 < ws->age)
+ mask = TALER_extensions_get_age_restriction_mask ();
+
+ if (ws->force_conflict)
+ TALER_planchet_master_setup_random (&conflict_ps);
+
+ for (unsigned int i = 0; i<ws->num_coins; i++)
+ {
+ struct CoinState *cs = &ws->coins[i];
+ struct TALER_EXCHANGE_WithdrawCoinInput *wci = &wcis[i];
+
+ if (ws->force_conflict)
+ cs->ps = conflict_ps;
+ else
+ TALER_planchet_master_setup_random (&cs->ps);
+
+ if (0 < ws->age)
+ {
+ struct GNUNET_HashCode seed = {0};
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &seed,
+ sizeof(seed));
+ TALER_age_restriction_commit (&mask,
+ ws->age,
+ &seed,
+ &cs->age_commitment_proof);
+ TALER_age_commitment_hash (&cs->age_commitment_proof.commitment,
+ &cs->h_age_commitment);
+ }
+
+
+ dpk = TALER_TESTING_find_pk (keys,
+ &cs->amount,
+ ws->age > 0);
+ if (NULL == dpk)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to determine denomination key at %s\n",
+ (NULL != cmd) ? cmd->label : "<retried command>");
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ /* We copy the denomination key, as re-querying /keys
+ * would free the old one. */
+ cs->pk = TALER_EXCHANGE_copy_denomination_key (dpk);
+ cs->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;
+ GNUNET_assert (0 <=
+ TALER_amount_add (&cs->reserve_history.amount,
+ &cs->amount,
+ &cs->pk->fees.withdraw));
+ cs->reserve_history.details.withdraw.fee = cs->pk->fees.withdraw;
+
+ wci->pk = cs->pk;
+ wci->ps = &cs->ps;
+ wci->ach = &cs->h_age_commitment;
+ }
+ ws->wsh = TALER_EXCHANGE_batch_withdraw (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ keys,
+ rp,
+ ws->num_coins,
+ wcis,
+ &reserve_batch_withdraw_cb,
+ ws);
+ if (NULL == ws->wsh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "withdraw" CMD, and possibly cancel
+ * a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+batch_withdraw_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct BatchWithdrawState *ws = cls;
+
+ if (NULL != ws->wsh)
+ {
+ TALER_TESTING_command_incomplete (ws->is,
+ cmd->label);
+ TALER_EXCHANGE_batch_withdraw_cancel (ws->wsh);
+ ws->wsh = NULL;
+ }
+ for (unsigned int i = 0; i<ws->num_coins; i++)
+ {
+ struct CoinState *cs = &ws->coins[i];
+
+ TALER_denom_ewv_free (&cs->exchange_vals);
+ TALER_denom_sig_free (&cs->sig);
+ if (NULL != cs->pk)
+ {
+ TALER_EXCHANGE_destroy_denomination_key (cs->pk);
+ cs->pk = NULL;
+ }
+ if (0 < ws->age)
+ TALER_age_commitment_proof_free (&cs->age_commitment_proof);
+ }
+ GNUNET_free (ws->coins);
+ GNUNET_free (ws->exchange_url);
+ GNUNET_free (ws->reserve_payto_uri);
+ GNUNET_free (ws);
+}
+
+
+/**
+ * Offer internal data to a "withdraw" CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+batch_withdraw_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct BatchWithdrawState *ws = cls;
+ struct CoinState *cs = &ws->coins[index];
+ struct TALER_TESTING_Trait traits[] = {
+ /* history entry MUST be first due to response code logic below! */
+ TALER_TESTING_make_trait_reserve_history (index,
+ &cs->reserve_history),
+ TALER_TESTING_make_trait_coin_priv (index,
+ &cs->coin_priv),
+ TALER_TESTING_make_trait_coin_pub (index,
+ &cs->coin_pub),
+ TALER_TESTING_make_trait_planchet_secrets (index,
+ &cs->ps),
+ TALER_TESTING_make_trait_blinding_key (index,
+ &cs->bks),
+ TALER_TESTING_make_trait_exchange_wd_value (index,
+ &cs->exchange_vals),
+ TALER_TESTING_make_trait_denom_pub (index,
+ cs->pk),
+ TALER_TESTING_make_trait_denom_sig (index,
+ &cs->sig),
+ TALER_TESTING_make_trait_reserve_priv (&ws->reserve_priv),
+ TALER_TESTING_make_trait_reserve_pub (&ws->reserve_pub),
+ TALER_TESTING_make_trait_amounts (index,
+ &cs->amount),
+ TALER_TESTING_make_trait_legi_requirement_row (&ws->requirement_row),
+ TALER_TESTING_make_trait_h_payto (&ws->h_payto),
+ TALER_TESTING_make_trait_payto_uri (ws->reserve_payto_uri),
+ TALER_TESTING_make_trait_exchange_url (ws->exchange_url),
+ TALER_TESTING_make_trait_age_commitment_proof (index,
+ ws->age > 0 ?
+ &cs->age_commitment_proof:
+ NULL),
+ TALER_TESTING_make_trait_h_age_commitment (index,
+ ws->age > 0 ?
+ &cs->h_age_commitment :
+ NULL),
+ TALER_TESTING_trait_end ()
+ };
+
+ if (index >= ws->num_coins)
+ return GNUNET_NO;
+ return TALER_TESTING_get_trait ((ws->expected_response_code == MHD_HTTP_OK)
+ ? &traits[0] /* we have reserve history */
+ : &traits[1], /* skip reserve history */
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_batch_withdraw_with_conflict (
+ const char *label,
+ const char *reserve_reference,
+ bool conflict,
+ uint8_t age,
+ unsigned int expected_response_code,
+ const char *amount,
+ ...)
+{
+ struct BatchWithdrawState *ws;
+ unsigned int cnt;
+ va_list ap;
+
+ ws = GNUNET_new (struct BatchWithdrawState);
+ ws->age = age;
+ ws->reserve_reference = reserve_reference;
+ ws->expected_response_code = expected_response_code;
+ ws->force_conflict = conflict;
+
+ cnt = 1;
+ va_start (ap,
+ amount);
+ while (NULL != (va_arg (ap,
+ const char *)))
+ cnt++;
+ ws->num_coins = cnt;
+ ws->coins = GNUNET_new_array (cnt,
+ struct CoinState);
+ va_end (ap);
+ va_start (ap,
+ amount);
+ for (unsigned int i = 0; i<ws->num_coins; i++)
+ {
+ struct CoinState *cs = &ws->coins[i];
+
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount,
+ &cs->amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %s\n",
+ amount,
+ label);
+ GNUNET_assert (0);
+ }
+ /* move on to next vararg! */
+ amount = va_arg (ap,
+ const char *);
+ }
+ GNUNET_assert (NULL == amount);
+ va_end (ap);
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ws,
+ .label = label,
+ .run = &batch_withdraw_run,
+ .cleanup = &batch_withdraw_cleanup,
+ .traits = &batch_withdraw_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_batch_withdraw.c */
diff --git a/src/testing/testing_api_cmd_check_aml_decision.c b/src/testing/testing_api_cmd_check_aml_decision.c
new file mode 100644
index 000000000..fa0981e0d
--- /dev/null
+++ b/src/testing/testing_api_cmd_check_aml_decision.c
@@ -0,0 +1,270 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_check_aml_decision.c
+ * @brief command for testing /management/XXX
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "check_aml_decision" CMD.
+ */
+struct AmlCheckState
+{
+
+ /**
+ * Handle while operation is running.
+ */
+ struct TALER_EXCHANGE_LookupAmlDecision *dh;
+
+ /**
+ * Our interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Reference to command to previous set officer.
+ */
+ const char *ref_officer;
+
+ /**
+ * Reference to command to the previous set AML status operation.
+ */
+ const char *ref_operation;
+
+ /**
+ * Expected HTTP status.
+ */
+ unsigned int expected_http_status;
+
+};
+
+
+/**
+ * Callback to analyze the /aml/$OFFICER_PUB/$decision/$H_PAYTO response, just used to check
+ * if the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param adr response details
+ */
+static void
+check_aml_decision_cb (void *cls,
+ const struct TALER_EXCHANGE_AmlDecisionResponse *adr)
+{
+ struct AmlCheckState *ds = cls;
+
+ ds->dh = NULL;
+ if (ds->expected_http_status != adr->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ adr->hr.http_status,
+ ds->expected_http_status);
+ return;
+ }
+ if (MHD_HTTP_OK == adr->hr.http_status)
+ {
+ const struct TALER_TESTING_Command *ref;
+ const char *justification;
+ enum TALER_AmlDecisionState *new_state;
+ const struct TALER_Amount *amount;
+ const struct TALER_EXCHANGE_AmlDecisionDetail *oldest = NULL;
+
+ ref = TALER_TESTING_interpreter_lookup_command (ds->is,
+ ds->ref_operation);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_aml_justification (ref,
+ &justification));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_aml_decision (ref,
+ &new_state));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_amount (ref,
+ &amount));
+ for (unsigned int i = 0; i<adr->details.ok.aml_history_length; i++)
+ {
+ const struct TALER_EXCHANGE_AmlDecisionDetail *aml_history
+ = &adr->details.ok.aml_history[i];
+
+ if ( (NULL == oldest) ||
+ (0 !=
+ TALER_amount_cmp (amount,
+ &oldest->new_threshold)) ||
+ (GNUNET_TIME_timestamp_cmp (oldest->decision_time,
+ >,
+ aml_history->decision_time)) )
+ oldest = aml_history;
+ }
+ if (NULL == oldest)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ if ( (oldest->new_state != *new_state) ||
+ (0 != strcmp (oldest->justification,
+ justification) ) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+check_aml_decision_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AmlCheckState *ds = cls;
+ const struct TALER_PaytoHashP *h_payto;
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv;
+ const struct TALER_TESTING_Command *ref;
+ const char *exchange_url;
+
+ (void) cmd;
+ ds->is = is;
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ }
+
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ ds->ref_operation);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_h_payto (ref,
+ &h_payto));
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ ds->ref_officer);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_officer_priv (ref,
+ &officer_priv));
+ ds->dh = TALER_EXCHANGE_lookup_aml_decision (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ h_payto,
+ officer_priv,
+ true, /* history */
+ &check_aml_decision_cb,
+ ds);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "check_aml_decision" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct AmlCheckState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+check_aml_decision_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AmlCheckState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_lookup_aml_decision_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ GNUNET_free (ds);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_aml_decision (
+ const char *label,
+ const char *ref_officer,
+ const char *ref_operation,
+ unsigned int expected_http_status)
+{
+ struct AmlCheckState *ds;
+
+ ds = GNUNET_new (struct AmlCheckState);
+ ds->ref_officer = ref_officer;
+ ds->ref_operation = ref_operation;
+ ds->expected_http_status = expected_http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &check_aml_decision_run,
+ .cleanup = &check_aml_decision_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_check_aml_decision.c */
diff --git a/src/testing/testing_api_cmd_check_aml_decisions.c b/src/testing/testing_api_cmd_check_aml_decisions.c
new file mode 100644
index 000000000..c8c2ec3f5
--- /dev/null
+++ b/src/testing/testing_api_cmd_check_aml_decisions.c
@@ -0,0 +1,204 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_check_aml_decisions.c
+ * @brief command for testing /aml/$OFFICER/decisions/$FILTER
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "check_aml_decision" CMD.
+ */
+struct AmlCheckState
+{
+
+ /**
+ * Handle while operation is running.
+ */
+ struct TALER_EXCHANGE_LookupAmlDecisions *dh;
+
+ /**
+ * Our interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Reference to command to previous set officer.
+ */
+ const char *ref_officer;
+
+ /**
+ * Which states to filter by.
+ */
+ enum TALER_AmlDecisionState filter;
+
+ /**
+ * Expected HTTP status.
+ */
+ unsigned int expected_http_status;
+
+};
+
+
+/**
+ * Callback to analyze the /aml/$OFFICER_PUB/$decisions/$FILTER response, just used to check
+ * if the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param adr response details
+ */
+static void
+check_aml_decisions_cb (void *cls,
+ const struct TALER_EXCHANGE_AmlDecisionsResponse *adr)
+{
+ struct AmlCheckState *ds = cls;
+
+ ds->dh = NULL;
+ if (ds->expected_http_status != adr->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ adr->hr.http_status,
+ ds->expected_http_status);
+ return;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+check_aml_decisions_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AmlCheckState *ds = cls;
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv;
+ const struct TALER_TESTING_Command *ref;
+ const char *exchange_url;
+
+ (void) cmd;
+ ds->is = is;
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ }
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ ds->ref_officer);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_officer_priv (ref,
+ &officer_priv));
+ ds->dh = TALER_EXCHANGE_lookup_aml_decisions (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ INT64_MAX,
+ -1, /* return last one for testing */
+ ds->filter,
+ officer_priv,
+ &check_aml_decisions_cb,
+ ds);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "check_aml_decisions" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct AmlCheckState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+check_aml_decisions_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AmlCheckState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_lookup_aml_decisions_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ GNUNET_free (ds);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_aml_decisions (
+ const char *label,
+ const char *ref_officer,
+ enum TALER_AmlDecisionState filter,
+ unsigned int expected_http_status)
+{
+ struct AmlCheckState *ds;
+
+ ds = GNUNET_new (struct AmlCheckState);
+ ds->ref_officer = ref_officer;
+ ds->filter = filter;
+ ds->expected_http_status = expected_http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &check_aml_decisions_run,
+ .cleanup = &check_aml_decisions_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_check_aml_decisions.c */
diff --git a/src/testing/testing_api_cmd_check_keys.c b/src/testing/testing_api_cmd_check_keys.c
deleted file mode 100644
index a0dca8cf9..000000000
--- a/src/testing/testing_api_cmd_check_keys.c
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- This file is part of TALER
- (C) 2018 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 testing/testing_api_cmd_check_keys.c
- * @brief Implementation of "check keys" test command. XXX-NOTE:
- * the number of 'expected keys' is NOT the number of the
- * downloaded keys, but rather the number of keys that the
- * libtalerutil library keeps locally. As for the current
- * design, keys are _never_ discarded by the library,
- * therefore their (expected) number is monotonically
- * ascending.
- *
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_testing_lib.h"
-
-
-/**
- * State for a "check keys" CMD.
- */
-struct CheckKeysState
-{
- /**
- * This number will instruct the CMD interpreter to
- * make sure that /keys was downloaded `generation` times
- * _before_ running the very CMD logic.
- */
- unsigned int generation;
-
- /**
- * How many denomination keys the exchange is
- * supposed to have.
- */
- unsigned int num_denom_keys;
-
- /**
- * If this value is GNUNET_YES, then the "cherry
- * picking" facility is turned off; whole /keys is
- * downloaded.
- */
- unsigned int pull_all_keys;
-
- /**
- * If GNUNET_YES, then the user must specify the
- * last_denom_issue_date manually. This way, it is possible
- * to force whatever X value here (including 0): /keys?last_denom_issue=X.
- */
- unsigned int set_last_denom;
-
- /**
- * Value X to set as the URL parameter:
- * "/keys?last_denom_issue=X" is used only when `set_last_denom'
- * equals #GNUNET_YES.
- */
- struct GNUNET_TIME_Absolute last_denom_date;
-
- /**
- * If #GNUNET_YES, then we'll provide the "/keys" request.
- * with the "now" argument.
- */
- int with_now;
-
- /**
- * Fake now as passed by the user.
- */
- struct GNUNET_TIME_Absolute now;
-
-};
-
-
-/**
- * Run the "check keys" command.
- *
- * @param cls closure.
- * @param cmd the command currently being executed.
- * @param is the interpreter state.
- */
-static void
-check_keys_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct CheckKeysState *cks = cls;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "cmd `%s' (ip: %u), key generation: %d\n",
- cmd->label,
- is->ip,
- is->key_generation);
-
- if (is->key_generation < cks->generation)
- {
- is->working = GNUNET_NO;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Triggering GET /keys, cmd `%s'\n",
- cmd->label);
-
- if (GNUNET_YES == cks->set_last_denom)
- {
- TALER_LOG_DEBUG ("Forcing last_denom_date URL argument\n");
- TALER_EXCHANGE_set_last_denom (is->exchange,
- cks->last_denom_date);
- }
-
- if (GNUNET_YES == cks->with_now)
- TALER_EXCHANGE_set_now (is->exchange,
- cks->now);
- /* Redownload /keys. */
- GNUNET_break
- (0 == TALER_EXCHANGE_check_keys_current
- (is->exchange,
- GNUNET_YES,
- cks->pull_all_keys).abs_value_us);
- return;
- }
-
-#if 0
- /**
- * Not sure this check makes sense: GET /keys is performed on
- * a "maybe" basis, so it can get quite hard to track /keys
- * request. Rather, this CMD should just check if /keys was
- * requested AT LEAST n times before going ahead with checks.
- *///
- if (is->key_generation > cks->generation)
- {
- /* We got /keys too often, strange. Fatal. May theoretically
- happen if somehow we were really unlucky and /keys expired
- "naturally", but obviously with a sane configuration this
- should also not be. */
- GNUNET_break (0);
- TALER_LOG_ERROR ("Acutal- vs expected key"
- " generation: %u vs %u\n",
- is->key_generation,
- cks->generation);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-#endif
- /* "/keys" was updated, let's check they were OK! */
- if (cks->num_denom_keys != is->keys->num_denom_keys)
- {
- /* Did not get the expected number of denomination keys! */
- GNUNET_break (0);
- TALER_LOG_ERROR ("Got %u keys in step %s, expected %u\n",
- is->keys->num_denom_keys,
- cmd->label,
- cks->num_denom_keys);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
- /* Let's unset the fake now before moving on. */
- TALER_EXCHANGE_unset_now (is->exchange);
- TALER_TESTING_interpreter_next (is);
-}
-
-
-/**
- * Cleanup the state.
- *
- * @param cls closure.
- * @param cmd the command which is being cleaned up.
- */
-static void
-check_keys_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct CheckKeysState *cks = cls;
-
- (void) cmd;
- GNUNET_free (cks);
-}
-
-
-/**
- * Make a "check keys" command. This type of command
- * checks whether the number of denomination keys from
- * @a exchange matches @a num_denom_keys. Additionally,
- * it lets the user set a last denom issue date to be
- * used in the request for /keys.
- *
- * @param label command label
- * @param generation when this command is run, exactly @a
- * generation /keys downloads took place. If the number
- * of downloads is less than @a generation, the logic will
- * first make sure that @a generation downloads are done,
- * and _then_ execute the rest of the command.
- * @param num_denom_keys expected number of denomination keys.
- * @param last_denom_date date to be set in the "last_denom_issue"
- * URL parameter of /keys.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys_with_last_denom (const char *label,
- unsigned int generation,
- unsigned int num_denom_keys,
- struct GNUNET_TIME_Absolute
- last_denom_date)
-{
- struct CheckKeysState *cks;
-
- cks = GNUNET_new (struct CheckKeysState);
- cks->generation = generation;
- cks->num_denom_keys = num_denom_keys;
- cks->set_last_denom = GNUNET_YES;
- cks->last_denom_date = last_denom_date;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = cks,
- .label = label,
- .run = &check_keys_run,
- .cleanup = &check_keys_cleanup
- };
-
- return cmd;
- }
-}
-
-
-/**
- * Make a "check keys" command. This type of command
- * checks whether the number of denomination keys from
- * @a exchange matches @a num_denom_keys.
- *
- * @param label command label
- * @param generation when this command is run, exactly @a
- * generation /keys downloads took place. If the number
- * of downloads is less than @a generation, the logic will
- * first make sure that @a generation downloads are done,
- * and _then_ execute the rest of the command.
- * @param num_denom_keys expected number of denomination keys.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys (const char *label,
- unsigned int generation,
- unsigned int num_denom_keys)
-{
- struct CheckKeysState *cks;
-
- cks = GNUNET_new (struct CheckKeysState);
- cks->generation = generation;
- cks->num_denom_keys = num_denom_keys;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = cks,
- .label = label,
- .run = &check_keys_run,
- .cleanup = &check_keys_cleanup
- };
-
- return cmd;
- }
-}
-
-
-/**
- * Make a "check keys" command. This type of command
- * checks whether the number of denomination keys from
- * @a exchange matches @a num_denom_keys.
- *
- * @param label command label
- * @param generation when this command is run, exactly @a
- * generation /keys downloads took place. If the number
- * of downloads is less than @a generation, the logic will
- * first make sure that @a generation downloads are done,
- * and _then_ execute the rest of the command.
- * @param num_denom_keys expected number of denomination keys.
- * @param now timestamp to use when fetching keys
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys_with_now (const char *label,
- unsigned int generation,
- unsigned int num_denom_keys,
- struct GNUNET_TIME_Absolute now)
-{
- struct CheckKeysState *cks;
-
- cks = GNUNET_new (struct CheckKeysState);
- cks->generation = generation;
- cks->num_denom_keys = num_denom_keys;
- cks->now = now;
- cks->with_now = GNUNET_YES;
-
- /* Force to NOT cherry pick, otherwise they conflict. */
- cks->pull_all_keys = GNUNET_YES;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = cks,
- .label = label,
- .run = &check_keys_run,
- .cleanup = &check_keys_cleanup
- };
-
- return cmd;
- }
-}
-
-
-/**
- * Make a "check keys" command that forcedly does NOT cherry pick;
- * just redownload the whole /keys. Then checks whether the number
- * of denomination keys from @a exchange matches @a num_denom_keys.
- *
- * @param label command label
- * @param generation when this command is run, exactly @a
- * generation /keys downloads took place. If the number
- * of downloads is less than @a generation, the logic will
- * first make sure that @a generation downloads are done,
- * and _then_ execute the rest of the command.
- * @param num_denom_keys expected number of denomination keys.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys_pull_all_keys (const char *label,
- unsigned int generation,
- unsigned int num_denom_keys)
-{
- struct CheckKeysState *cks;
-
- cks = GNUNET_new (struct CheckKeysState);
- cks->generation = generation;
- cks->num_denom_keys = num_denom_keys;
- cks->pull_all_keys = GNUNET_YES;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = cks,
- .label = label,
- .run = &check_keys_run,
- .cleanup = &check_keys_cleanup
- };
-
- return cmd;
- }
-}
-
-
-/* end of testing_api_cmd_check_keys.c */
diff --git a/src/testing/testing_api_cmd_coin_history.c b/src/testing/testing_api_cmd_coin_history.c
new file mode 100644
index 000000000..a1345f67f
--- /dev/null
+++ b/src/testing/testing_api_cmd_coin_history.c
@@ -0,0 +1,609 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_coin_history.c
+ * @brief Implement the /coins/$COIN_PUB/history test command.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "history" CMD.
+ */
+struct HistoryState
+{
+
+ /**
+ * Public key of the coin being analyzed.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Label to the command which created the coin to check,
+ * needed to resort the coin key.
+ */
+ const char *coin_reference;
+
+ /**
+ * Handle to the "coin history" operation.
+ */
+ struct TALER_EXCHANGE_CoinsHistoryHandle *rsh;
+
+ /**
+ * Expected coin balance.
+ */
+ const char *expected_balance;
+
+ /**
+ * Private key of the coin being analyzed.
+ */
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+};
+
+
+/**
+ * Closure for analysis_cb().
+ */
+struct AnalysisContext
+{
+ /**
+ * Coin public key we are looking at.
+ */
+ const struct TALER_CoinSpendPublicKeyP *coin_pub;
+
+ /**
+ * Length of the @e history array.
+ */
+ unsigned int history_length;
+
+ /**
+ * Array of history items to match.
+ */
+ const struct TALER_EXCHANGE_CoinHistoryEntry *history;
+
+ /**
+ * Array of @e history_length of matched entries.
+ */
+ bool *found;
+
+ /**
+ * Set to true if an entry could not be found.
+ */
+ bool failure;
+};
+
+
+/**
+ * Compare @a h1 and @a h2.
+ *
+ * @param h1 a history entry
+ * @param h2 a history entry
+ * @return 0 if @a h1 and @a h2 are equal
+ */
+static int
+history_entry_cmp (
+ const struct TALER_EXCHANGE_CoinHistoryEntry *h1,
+ const struct TALER_EXCHANGE_CoinHistoryEntry *h2)
+{
+ if (h1->type != h2->type)
+ return 1;
+ if (0 != TALER_amount_cmp (&h1->amount,
+ &h2->amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Amount mismatch (%s)\n",
+ TALER_amount2s (&h1->amount));
+ return 1;
+ }
+ switch (h1->type)
+ {
+ case TALER_EXCHANGE_CTT_NONE:
+ GNUNET_break (0);
+ break;
+ case TALER_EXCHANGE_CTT_DEPOSIT:
+ if (0 != GNUNET_memcmp (&h1->details.deposit.h_contract_terms,
+ &h2->details.deposit.h_contract_terms))
+ return 1;
+ if (0 != GNUNET_memcmp (&h1->details.deposit.merchant_pub,
+ &h2->details.deposit.merchant_pub))
+ return 1;
+ if (0 != GNUNET_memcmp (&h1->details.deposit.h_wire,
+ &h2->details.deposit.h_wire))
+ return 1;
+ if (0 != GNUNET_memcmp (&h1->details.deposit.sig,
+ &h2->details.deposit.sig))
+ return 1;
+ return 0;
+ case TALER_EXCHANGE_CTT_MELT:
+ if (0 != GNUNET_memcmp (&h1->details.melt.h_age_commitment,
+ &h2->details.melt.h_age_commitment))
+ return 1;
+ /* Note: most other fields are not initialized
+ in the trait as they are hard to extract from
+ the API */
+ return 0;
+ case TALER_EXCHANGE_CTT_REFUND:
+ if (0 != GNUNET_memcmp (&h1->details.refund.sig,
+ &h2->details.refund.sig))
+ return 1;
+ return 0;
+ case TALER_EXCHANGE_CTT_RECOUP:
+ if (0 != GNUNET_memcmp (&h1->details.recoup.coin_sig,
+ &h2->details.recoup.coin_sig))
+ return 1;
+ /* Note: exchange_sig, exchange_pub and timestamp are
+ fundamentally not available in the initiating command */
+ return 0;
+ case TALER_EXCHANGE_CTT_RECOUP_REFRESH:
+ if (0 != GNUNET_memcmp (&h1->details.recoup_refresh.coin_sig,
+ &h2->details.recoup_refresh.coin_sig))
+ return 1;
+ /* Note: exchange_sig, exchange_pub and timestamp are
+ fundamentally not available in the initiating command */
+ return 0;
+ case TALER_EXCHANGE_CTT_OLD_COIN_RECOUP:
+ if (0 != GNUNET_memcmp (&h1->details.old_coin_recoup.new_coin_pub,
+ &h2->details.old_coin_recoup.new_coin_pub))
+ return 1;
+ /* Note: exchange_sig, exchange_pub and timestamp are
+ fundamentally not available in the initiating command */
+ return 0;
+ case TALER_EXCHANGE_CTT_PURSE_DEPOSIT:
+ /* coin_sig is not initialized */
+ if (0 != GNUNET_memcmp (&h1->details.purse_deposit.purse_pub,
+ &h2->details.purse_deposit.purse_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Purse public key mismatch\n");
+ return 1;
+ }
+ if (0 != strcmp (h1->details.purse_deposit.exchange_base_url,
+ h2->details.purse_deposit.exchange_base_url))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Exchange base URL mismatch (%s/%s)\n",
+ h1->details.purse_deposit.exchange_base_url,
+ h2->details.purse_deposit.exchange_base_url);
+ GNUNET_break (0);
+ return 1;
+ }
+ return 0;
+ case TALER_EXCHANGE_CTT_PURSE_REFUND:
+ /* NOTE: not supported yet (trait not returned) */
+ return 0;
+ case TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT:
+ /* NOTE: not supported yet (trait not returned) */
+ if (0 != GNUNET_memcmp (&h1->details.reserve_open_deposit.coin_sig,
+ &h2->details.reserve_open_deposit.coin_sig))
+ return 1;
+ return 0;
+ }
+ GNUNET_assert (0);
+ return -1;
+}
+
+
+/**
+ * Check if @a cmd changed the coin, if so, find the
+ * entry in our history and set the respective index in found
+ * to true. If the entry is not found, set failure.
+ *
+ * @param cls our `struct AnalysisContext *`
+ * @param cmd command to analyze for impact on history
+ */
+static void
+analyze_command (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AnalysisContext *ac = cls;
+ const struct TALER_CoinSpendPublicKeyP *coin_pub = ac->coin_pub;
+ const struct TALER_EXCHANGE_CoinHistoryEntry *history = ac->history;
+ unsigned int history_length = ac->history_length;
+ bool *found = ac->found;
+
+ if (TALER_TESTING_cmd_is_batch (cmd))
+ {
+ struct TALER_TESTING_Command *cur;
+ struct TALER_TESTING_Command *bcmd;
+
+ cur = TALER_TESTING_cmd_batch_get_current (cmd);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_batch_cmds (cmd,
+ &bcmd))
+ {
+ GNUNET_break (0);
+ ac->failure = true;
+ return;
+ }
+ for (unsigned int i = 0; NULL != bcmd[i].label; i++)
+ {
+ struct TALER_TESTING_Command *step = &bcmd[i];
+
+ analyze_command (ac,
+ step);
+ if (ac->failure)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Entry for batch step `%s' missing in history\n",
+ step->label);
+ return;
+ }
+ if (step == cur)
+ break; /* if *we* are in a batch, make sure not to analyze commands past 'now' */
+ }
+ return;
+ }
+
+ for (unsigned int j = 0; true; j++)
+ {
+ const struct TALER_CoinSpendPublicKeyP *rp;
+ const struct TALER_EXCHANGE_CoinHistoryEntry *he;
+ bool matched = false;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_pub (cmd,
+ j,
+ &rp))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Command `%s#%u' has no public key for a coin\n",
+ cmd->label,
+ j);
+ break; /* command does nothing for coins */
+ }
+ if (0 !=
+ GNUNET_memcmp (rp,
+ coin_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Command `%s#%u' is about another coin\n",
+ cmd->label,
+ j);
+ continue; /* command affects some _other_ coin */
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_history (cmd,
+ j,
+ &he))
+ {
+ /* NOTE: only for debugging... */
+ if (0 == j)
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Command `%s' has the coin_pub, but lacks coin history trait\n",
+ cmd->label);
+ return; /* command does nothing for coins */
+ }
+ for (unsigned int i = 0; i<history_length; i++)
+ {
+ if (found[i])
+ continue; /* already found, skip */
+ if (0 ==
+ history_entry_cmp (he,
+ &history[i]))
+ {
+ found[i] = true;
+ matched = true;
+ break;
+ }
+ }
+ if (! matched)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Command `%s' coin history entry #%u not found\n",
+ cmd->label,
+ j);
+ ac->failure = true;
+ return;
+ }
+ }
+}
+
+
+/**
+ * Check that the coin balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param rs HTTP response details
+ */
+static void
+coin_history_cb (void *cls,
+ const struct TALER_EXCHANGE_CoinHistory *rs)
+{
+ struct HistoryState *ss = cls;
+ struct TALER_TESTING_Interpreter *is = ss->is;
+ struct TALER_Amount eb;
+ unsigned int hlen;
+
+ ss->rsh = NULL;
+ if (ss->expected_response_code != rs->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status (ss->is,
+ rs->hr.http_status,
+ ss->expected_response_code);
+ return;
+ }
+ if (MHD_HTTP_OK != rs->hr.http_status)
+ {
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (ss->expected_balance,
+ &eb));
+
+ if (0 != TALER_amount_cmp (&eb,
+ &rs->details.ok.balance))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected balance for coin: %s\n",
+ TALER_amount_to_string (&rs->details.ok.balance));
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected balance of: %s\n",
+ TALER_amount_to_string (&eb));
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ hlen = json_array_size (rs->details.ok.history);
+ {
+ bool found[GNUNET_NZL (hlen)];
+ struct TALER_EXCHANGE_CoinHistoryEntry rhist[GNUNET_NZL (hlen)];
+ struct AnalysisContext ac = {
+ .coin_pub = &ss->coin_pub,
+ .history = rhist,
+ .history_length = hlen,
+ .found = found
+ };
+ const struct TALER_EXCHANGE_DenomPublicKey *dk;
+ struct TALER_Amount total_in;
+ struct TALER_Amount total_out;
+ struct TALER_Amount hbal;
+
+ dk = TALER_EXCHANGE_get_denomination_key_by_hash (
+ TALER_TESTING_get_keys (is),
+ &rs->details.ok.h_denom_pub);
+ memset (found,
+ 0,
+ sizeof (found));
+ memset (rhist,
+ 0,
+ sizeof (rhist));
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_parse_coin_history (
+ TALER_TESTING_get_keys (is),
+ dk,
+ rs->details.ok.history,
+ &ss->coin_pub,
+ &total_in,
+ &total_out,
+ hlen,
+ rhist))
+ {
+ GNUNET_break (0);
+ json_dumpf (rs->hr.reply,
+ stderr,
+ JSON_INDENT (2));
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ if (0 >
+ TALER_amount_subtract (&hbal,
+ &total_in,
+ &total_out))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Coin credits: %s\n",
+ TALER_amount2s (&total_in));
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Coin debits: %s\n",
+ TALER_amount2s (&total_out));
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ if (0 != TALER_amount_cmp (&hbal,
+ &rs->details.ok.balance))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ (void) ac;
+ TALER_TESTING_iterate (is,
+ true,
+ &analyze_command,
+ &ac);
+ if (ac.failure)
+ {
+ json_dumpf (rs->hr.reply,
+ stderr,
+ JSON_INDENT (2));
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+#if 1
+ for (unsigned int i = 0; i<hlen; i++)
+ {
+ if (found[i])
+ continue;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "History entry at index %u of type %d not justified by command history\n",
+ i,
+ rs->details.ok.history[i].type);
+ json_dumpf (rs->hr.reply,
+ stderr,
+ JSON_INDENT (2));
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+#endif
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+history_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct HistoryState *ss = cls;
+ const struct TALER_TESTING_Command *create_coin;
+ char *cref;
+ unsigned int idx;
+
+ ss->is = is;
+ GNUNET_assert (
+ GNUNET_OK ==
+ TALER_TESTING_parse_coin_reference (
+ ss->coin_reference,
+ &cref,
+ &idx));
+ create_coin
+ = TALER_TESTING_interpreter_lookup_command (is,
+ cref);
+ if (NULL == create_coin)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (create_coin,
+ idx,
+ &ss->coin_priv))
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR ("Failed to find coin_priv for history query\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&ss->coin_priv->eddsa_priv,
+ &ss->coin_pub.eddsa_pub);
+ ss->rsh = TALER_EXCHANGE_coins_history (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ ss->coin_priv,
+ 0,
+ &coin_history_cb,
+ ss);
+}
+
+
+/**
+ * Offer internal data from a "history" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+history_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct HistoryState *hs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_coin_pub (0,
+ &hs->coin_pub),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Cleanup the state from a "coin history" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+history_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct HistoryState *ss = cls;
+
+ if (NULL != ss->rsh)
+ {
+ TALER_TESTING_command_incomplete (ss->is,
+ cmd->label);
+ TALER_EXCHANGE_coins_history_cancel (ss->rsh);
+ ss->rsh = NULL;
+ }
+ GNUNET_free (ss);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_coin_history (const char *label,
+ const char *coin_reference,
+ const char *expected_balance,
+ unsigned int expected_response_code)
+{
+ struct HistoryState *ss;
+
+ GNUNET_assert (NULL != coin_reference);
+ ss = GNUNET_new (struct HistoryState);
+ ss->coin_reference = coin_reference;
+ ss->expected_balance = expected_balance;
+ ss->expected_response_code = expected_response_code;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ss,
+ .label = label,
+ .run = &history_run,
+ .cleanup = &history_cleanup,
+ .traits = &history_traits
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_common.c b/src/testing/testing_api_cmd_common.c
new file mode 100644
index 000000000..2d828a2b0
--- /dev/null
+++ b/src/testing/testing_api_cmd_common.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018-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/>
+*/
+/**
+ * @file testing/testing_api_cmd_common.c
+ * @brief common functions for commands
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_testing_lib.h"
+
+
+enum GNUNET_GenericReturnValue
+TALER_TESTING_parse_coin_reference (
+ const char *coin_reference,
+ char **cref,
+ unsigned int *idx)
+{
+ const char *index;
+ char dummy;
+
+ /* We allow command references of the form "$LABEL#$INDEX" or
+ just "$LABEL", which implies the index is 0. Figure out
+ which one it is. */
+ index = strchr (coin_reference, '#');
+ if (NULL == index)
+ {
+ *idx = 0;
+ *cref = GNUNET_strdup (coin_reference);
+ return GNUNET_OK;
+ }
+ *cref = GNUNET_strndup (coin_reference,
+ index - coin_reference);
+ if (1 != sscanf (index + 1,
+ "%u%c",
+ idx,
+ &dummy))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Numeric index (not `%s') required after `#' in command reference of command in %s:%u\n",
+ index,
+ __FILE__,
+ __LINE__);
+ GNUNET_free (*cref);
+ *cref = NULL;
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
diff --git a/src/testing/testing_api_cmd_contract_get.c b/src/testing/testing_api_cmd_contract_get.c
new file mode 100644
index 000000000..fa83d83f7
--- /dev/null
+++ b/src/testing/testing_api_cmd_contract_get.c
@@ -0,0 +1,316 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_contract_get.c
+ * @brief command for testing GET /contracts/$CPUB
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "contract get" CMD.
+ */
+struct ContractGetState
+{
+
+ /**
+ * JSON string describing the resulting contract.
+ */
+ json_t *contract_terms;
+
+ /**
+ * Private key to decrypt the contract.
+ */
+ struct TALER_ContractDiffiePrivateP contract_priv;
+
+ /**
+ * Set to the returned merge key.
+ */
+ struct TALER_PurseMergePrivateKeyP merge_priv;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Reference to the command that uploaded the contract.
+ */
+ const char *contract_ref;
+
+ /**
+ * ContractGet handle while operation is running.
+ */
+ struct TALER_EXCHANGE_ContractsGetHandle *dh;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * True if this is for a 'merge' operation,
+ * 'false' if this is for a 'deposit' operation.
+ */
+ bool merge;
+
+};
+
+
+/**
+ * Callback to analyze the /contracts/$CPUB response, just used to check if
+ * the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param dr get response details
+ */
+static void
+get_cb (void *cls,
+ const struct TALER_EXCHANGE_ContractGetResponse *dr)
+{
+ struct ContractGetState *ds = cls;
+ const struct TALER_TESTING_Command *ref;
+
+ ds->dh = NULL;
+ if (ds->expected_response_code != dr->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ dr->hr.http_status,
+ ds->expected_response_code);
+ return;
+ }
+ ref = TALER_TESTING_interpreter_lookup_command (ds->is,
+ ds->contract_ref);
+ GNUNET_assert (NULL != ref);
+ if (MHD_HTTP_OK == dr->hr.http_status)
+ {
+ const struct TALER_PurseMergePrivateKeyP *mp;
+ const json_t *ct;
+
+ ds->purse_pub = dr->details.ok.purse_pub;
+ if (ds->merge)
+ {
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_merge_priv (ref,
+ &mp))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ ds->contract_terms =
+ TALER_CRYPTO_contract_decrypt_for_merge (
+ &ds->contract_priv,
+ &ds->purse_pub,
+ dr->details.ok.econtract,
+ dr->details.ok.econtract_size,
+ &ds->merge_priv);
+ if (0 !=
+ GNUNET_memcmp (mp,
+ &ds->merge_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ }
+ else
+ {
+ ds->contract_terms =
+ TALER_CRYPTO_contract_decrypt_for_deposit (
+ &ds->contract_priv,
+ dr->details.ok.econtract,
+ dr->details.ok.econtract_size);
+ }
+ if (NULL == ds->contract_terms)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_contract_terms (ref,
+ &ct))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ if (1 != /* 1: equal, 0: not equal */
+ json_equal (ct,
+ ds->contract_terms))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+get_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct ContractGetState *ds = cls;
+ const struct TALER_ContractDiffiePrivateP *contract_priv;
+ const struct TALER_TESTING_Command *ref;
+ const char *exchange_url;
+
+ (void) cmd;
+ ds->is = is;
+ exchange_url = TALER_TESTING_get_exchange_url (is);
+ if (NULL == exchange_url)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ ref = TALER_TESTING_interpreter_lookup_command (ds->is,
+ ds->contract_ref);
+ GNUNET_assert (NULL != ref);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_contract_priv (ref,
+ &contract_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ ds->contract_priv = *contract_priv;
+ ds->dh = TALER_EXCHANGE_contract_get (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ contract_priv,
+ &get_cb,
+ ds);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not GET contract\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "get" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct ContractGetState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+get_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct ContractGetState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_contract_get_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ json_decref (ds->contract_terms);
+ GNUNET_free (ds);
+}
+
+
+/**
+ * Offer internal data from a "get" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+get_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct ContractGetState *ds = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_merge_priv (&ds->merge_priv),
+ TALER_TESTING_make_trait_purse_pub (&ds->purse_pub),
+ TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
+ TALER_TESTING_trait_end ()
+ };
+
+ /* skip 'merge_priv' if we are in 'merge' mode */
+ return TALER_TESTING_get_trait (&traits[ds->merge ? 0 : 1],
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_contract_get (
+ const char *label,
+ unsigned int expected_http_status,
+ bool for_merge,
+ const char *contract_ref)
+{
+ struct ContractGetState *ds;
+
+ ds = GNUNET_new (struct ContractGetState);
+ ds->expected_response_code = expected_http_status;
+ ds->contract_ref = contract_ref;
+ ds->merge = for_merge;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &get_run,
+ .cleanup = &get_cleanup,
+ .traits = &get_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_contract_get.c */
diff --git a/src/testing/testing_api_cmd_deposit.c b/src/testing/testing_api_cmd_deposit.c
index fcba7f270..849c78c70 100644
--- a/src/testing/testing_api_cmd_deposit.c
+++ b/src/testing/testing_api_cmd_deposit.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018-2020 Taler Systems SA
+ Copyright (C) 2018-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
@@ -53,6 +53,11 @@ struct DepositState
struct TALER_Amount amount;
/**
+ * Deposit fee.
+ */
+ struct TALER_Amount deposit_fee;
+
+ /**
* Reference to any command that is able to provide a coin.
*/
const char *coin_reference;
@@ -64,6 +69,11 @@ struct DepositState
unsigned int coin_index;
/**
+ * Our coin signature.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
* Wire details of who is depositing -- this would be merchant
* wire details in a normal scenario.
*/
@@ -77,7 +87,12 @@ struct DepositState
/**
* Refund deadline. Zero for no refunds.
*/
- struct GNUNET_TIME_Absolute refund_deadline;
+ struct GNUNET_TIME_Timestamp refund_deadline;
+
+ /**
+ * Wire deadline.
+ */
+ struct GNUNET_TIME_Timestamp wire_deadline;
/**
* Set (by the interpreter) to a fresh private key. This
@@ -88,12 +103,17 @@ struct DepositState
/**
* Deposit handle while operation is running.
*/
- struct TALER_EXCHANGE_DepositHandle *dh;
+ struct TALER_EXCHANGE_BatchDepositHandle *dh;
/**
- * Timestamp of the /deposit operation.
+ * Denomination public key of the deposited coin.
*/
- struct GNUNET_TIME_Absolute timestamp;
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+
+ /**
+ * Timestamp of the /deposit operation in the wallet (contract signing time).
+ */
+ struct GNUNET_TIME_Timestamp wallet_timestamp;
/**
* Interpreter state.
@@ -121,10 +141,21 @@ struct DepositState
unsigned int do_retry;
/**
- * Set to #GNUNET_YES if the /deposit succeeded
+ * Set to true if the /deposit succeeded
* and we now can provide the resulting traits.
*/
- int deposit_succeeded;
+ bool deposit_succeeded;
+
+ /**
+ * Expected entry in the coin history created by this
+ * operation.
+ */
+ struct TALER_EXCHANGE_CoinHistoryEntry che;
+
+ /**
+ * When did the exchange receive the deposit?
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
/**
* Signing key used by the exchange to sign the
@@ -150,7 +181,7 @@ struct DepositState
* When we're referencing another deposit operation,
* this will only be set after the command has been started.
*/
- int command_initialized;
+ bool command_initialized;
/**
* Reference to fetch the merchant private key from.
@@ -184,8 +215,7 @@ do_retry (void *cls)
struct DepositState *ds = cls;
ds->retry_task = NULL;
- ds->is->commands[ds->is->ip].last_req_time
- = GNUNET_TIME_absolute_get ();
+ TALER_TESTING_touch_cmd (ds->is);
deposit_run (ds,
NULL,
ds->is);
@@ -197,45 +227,36 @@ do_retry (void *cls)
* check if the response code is acceptable.
*
* @param cls closure.
- * @param http_status HTTP response code.
- * @param ec taler-specific error code.
- * @param exchange_sig signature provided by the exchange
- * (NULL on errors)
- * @param exchange_pub public key of the exchange,
- * used for signing the response.
- * @param obj raw response from the exchange.
+ * @param dr deposit response details
*/
static void
deposit_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const json_t *obj)
+ const struct TALER_EXCHANGE_BatchDepositResult *dr)
{
struct DepositState *ds = cls;
ds->dh = NULL;
- if (ds->expected_response_code != http_status)
+ if (ds->expected_response_code != dr->hr.http_status)
{
if (0 != ds->do_retry)
{
ds->do_retry--;
- if ( (0 == http_status) ||
- (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
- (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
+ if ( (0 == dr->hr.http_status) ||
+ (TALER_EC_GENERIC_DB_SOFT_FAILURE == dr->hr.ec) ||
+ (MHD_HTTP_INTERNAL_SERVER_ERROR == dr->hr.http_status) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Retrying deposit failed with %u/%d\n",
- http_status,
- (int) ec);
+ dr->hr.http_status,
+ (int) dr->hr.ec);
/* on DB conflicts, do not use backoff */
- if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
+ if (TALER_EC_GENERIC_DB_SOFT_FAILURE == dr->hr.ec)
ds->backoff = GNUNET_TIME_UNIT_ZERO;
else
ds->backoff = GNUNET_TIME_randomized_backoff (ds->backoff,
MAX_BACKOFF);
- ds->is->commands[ds->is->ip].num_tries++;
+ TALER_TESTING_inc_tries (ds->is);
+ GNUNET_assert (NULL == ds->retry_task);
ds->retry_task
= GNUNET_SCHEDULER_add_delayed (ds->backoff,
&do_retry,
@@ -243,21 +264,20 @@ deposit_cb (void *cls,
return;
}
}
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- http_status,
- ds->is->commands[ds->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (obj, stderr, 0);
- TALER_TESTING_interpreter_fail (ds->is);
+ TALER_TESTING_unexpected_status_with_body (
+ ds->is,
+ dr->hr.http_status,
+ ds->expected_response_code,
+ dr->hr.reply);
+
return;
}
- if (MHD_HTTP_OK == http_status)
+ if (MHD_HTTP_OK == dr->hr.http_status)
{
- ds->deposit_succeeded = GNUNET_YES;
- ds->exchange_pub = *exchange_pub;
- ds->exchange_sig = *exchange_sig;
+ ds->deposit_succeeded = true;
+ ds->exchange_timestamp = dr->details.ok.deposit_timestamp;
+ ds->exchange_pub = *dr->details.ok.exchange_pub;
+ ds->exchange_sig = *dr->details.ok.exchange_sig;
}
TALER_TESTING_interpreter_next (ds->is);
}
@@ -279,15 +299,46 @@ deposit_run (void *cls,
const struct TALER_TESTING_Command *coin_cmd;
const struct TALER_CoinSpendPrivateKeyP *coin_priv;
struct TALER_CoinSpendPublicKeyP coin_pub;
- const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+ const struct TALER_AgeCommitmentHash *phac;
const struct TALER_DenominationSignature *denom_pub_sig;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct GNUNET_TIME_Absolute wire_deadline;
struct TALER_MerchantPublicKeyP merchant_pub;
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ enum TALER_ErrorCode ec;
+ struct TALER_WireSaltP wire_salt;
+ const char *payto_uri;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &wire_salt),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *exchange_url
+ = TALER_TESTING_get_exchange_url (is);
(void) cmd;
+ if (NULL == exchange_url)
+ {
+ GNUNET_break (0);
+ return;
+ }
ds->is = is;
+ if (! GNUNET_TIME_absolute_is_zero (ds->refund_deadline.abs_time))
+ {
+ struct GNUNET_TIME_Relative refund_deadline;
+
+ refund_deadline
+ = GNUNET_TIME_absolute_get_remaining (ds->refund_deadline.abs_time);
+ ds->wire_deadline
+ = GNUNET_TIME_relative_to_timestamp (
+ GNUNET_TIME_relative_multiply (refund_deadline,
+ 2));
+ }
+ else
+ {
+ ds->refund_deadline = ds->wallet_timestamp;
+ ds->wire_deadline = GNUNET_TIME_timestamp_get ();
+ }
if (NULL != ds->deposit_reference)
{
/* We're copying another deposit operation, initialize here. */
@@ -306,12 +357,14 @@ deposit_run (void *cls,
ds->coin_reference = ods->coin_reference;
ds->coin_index = ods->coin_index;
ds->wire_details = json_incref (ods->wire_details);
+ GNUNET_assert (NULL != ds->wire_details);
ds->contract_terms = json_incref (ods->contract_terms);
- ds->timestamp = ods->timestamp;
+ ds->wallet_timestamp = ods->wallet_timestamp;
ds->refund_deadline = ods->refund_deadline;
+ ds->wire_deadline = ods->wire_deadline;
ds->amount = ods->amount;
ds->merchant_priv = ods->merchant_priv;
- ds->command_initialized = GNUNET_YES;
+ ds->command_initialized = true;
}
else if (NULL != ds->merchant_priv_reference)
{
@@ -329,7 +382,6 @@ deposit_run (void *cls,
}
if ( (GNUNET_OK !=
TALER_TESTING_get_trait_merchant_priv (cmd,
- 0,
&merchant_priv)) )
{
GNUNET_break (0);
@@ -338,6 +390,19 @@ deposit_run (void *cls,
}
ds->merchant_priv = *merchant_priv;
}
+ GNUNET_assert (NULL != ds->wire_details);
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (ds->wire_details,
+ spec,
+ NULL, NULL))
+ {
+ json_dumpf (ds->wire_details,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
GNUNET_assert (ds->coin_reference);
coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
ds->coin_reference);
@@ -353,86 +418,104 @@ deposit_run (void *cls,
ds->coin_index,
&coin_priv)) ||
(GNUNET_OK !=
+ TALER_TESTING_get_trait_h_age_commitment (coin_cmd,
+ ds->coin_index,
+ &phac)) ||
+ (GNUNET_OK !=
TALER_TESTING_get_trait_denom_pub (coin_cmd,
ds->coin_index,
- &denom_pub)) ||
+ &ds->denom_pub)) ||
(GNUNET_OK !=
TALER_TESTING_get_trait_denom_sig (coin_cmd,
ds->coin_index,
&denom_pub_sig)) ||
(GNUNET_OK !=
- TALER_JSON_hash (ds->contract_terms,
- &h_contract_terms)) )
+ TALER_JSON_contract_hash (ds->contract_terms,
+ &h_contract_terms)) )
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
+
+ ds->deposit_fee = ds->denom_pub->fees.deposit;
GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
&coin_pub.eddsa_pub);
- if (0 != ds->refund_deadline.abs_value_us)
- {
- struct GNUNET_TIME_Relative refund_deadline;
-
- refund_deadline = GNUNET_TIME_absolute_get_remaining (ds->refund_deadline);
- wire_deadline = GNUNET_TIME_relative_to_absolute
- (GNUNET_TIME_relative_multiply (refund_deadline, 2));
- }
- else
- {
- ds->refund_deadline = ds->timestamp;
- wire_deadline = GNUNET_TIME_relative_to_absolute
- (GNUNET_TIME_UNIT_ZERO);
- }
GNUNET_CRYPTO_eddsa_key_get_public (&ds->merchant_priv.eddsa_priv,
&merchant_pub.eddsa_pub);
-
- (void) GNUNET_TIME_round_abs (&wire_deadline);
-
{
- struct TALER_DepositRequestPS dr;
-
- memset (&dr, 0, sizeof (dr));
- dr.purpose.size = htonl
- (sizeof (struct TALER_DepositRequestPS));
- dr.purpose.purpose = htonl
- (TALER_SIGNATURE_WALLET_COIN_DEPOSIT);
- dr.h_contract_terms = h_contract_terms;
+ struct TALER_MerchantWireHashP h_wire;
+
GNUNET_assert (GNUNET_OK ==
TALER_JSON_merchant_wire_signature_hash (ds->wire_details,
- &dr.h_wire));
- dr.timestamp = GNUNET_TIME_absolute_hton (ds->timestamp);
- dr.refund_deadline = GNUNET_TIME_absolute_hton
- (ds->refund_deadline);
- TALER_amount_hton (&dr.amount_with_fee,
- &ds->amount);
- TALER_amount_hton (&dr.deposit_fee,
- &denom_pub->fee_deposit);
- dr.merchant = merchant_pub;
- dr.coin_pub = coin_pub;
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
- &dr.purpose,
- &coin_sig.eddsa_signature));
+ &h_wire));
+ TALER_wallet_deposit_sign (&ds->amount,
+ &ds->denom_pub->fees.deposit,
+ &h_wire,
+ &h_contract_terms,
+ NULL, /* wallet data hash */
+ phac,
+ NULL, /* hash of extensions */
+ &ds->denom_pub->h_key,
+ ds->wallet_timestamp,
+ &merchant_pub,
+ ds->refund_deadline,
+ coin_priv,
+ &ds->coin_sig);
+ ds->che.type = TALER_EXCHANGE_CTT_DEPOSIT;
+ ds->che.amount = ds->amount;
+ ds->che.details.deposit.h_wire = h_wire;
+ ds->che.details.deposit.h_contract_terms = h_contract_terms;
+ ds->che.details.deposit.no_h_policy = true;
+ ds->che.details.deposit.no_wallet_data_hash = true;
+ ds->che.details.deposit.wallet_timestamp = ds->wallet_timestamp;
+ ds->che.details.deposit.merchant_pub = merchant_pub;
+ ds->che.details.deposit.refund_deadline = ds->refund_deadline;
+ ds->che.details.deposit.sig = ds->coin_sig;
+ ds->che.details.deposit.no_hac = true;
+ ds->che.details.deposit.deposit_fee = ds->denom_pub->fees.deposit;
+ }
+ GNUNET_assert (NULL == ds->dh);
+ {
+ struct TALER_EXCHANGE_CoinDepositDetail cdd = {
+ .amount = ds->amount,
+ .coin_pub = coin_pub,
+ .coin_sig = ds->coin_sig,
+ .denom_sig = *denom_pub_sig,
+ .h_denom_pub = ds->denom_pub->h_key,
+ .h_age_commitment = {{{0}}},
+ };
+ struct TALER_EXCHANGE_DepositContractDetail dcd = {
+ .wire_deadline = ds->wire_deadline,
+ .merchant_payto_uri = payto_uri,
+ .wire_salt = wire_salt,
+ .h_contract_terms = h_contract_terms,
+ .wallet_timestamp = ds->wallet_timestamp,
+ .merchant_pub = merchant_pub,
+ .refund_deadline = ds->refund_deadline
+ };
+
+ if (NULL != phac)
+ cdd.h_age_commitment = *phac;
+
+ ds->dh = TALER_EXCHANGE_batch_deposit (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ TALER_TESTING_get_keys (is),
+ &dcd,
+ 1,
+ &cdd,
+ &deposit_cb,
+ ds,
+ &ec);
}
- ds->dh = TALER_EXCHANGE_deposit (is->exchange,
- &ds->amount,
- wire_deadline,
- ds->wire_details,
- &h_contract_terms,
- &coin_pub,
- denom_pub_sig,
- &denom_pub->key,
- ds->timestamp,
- &merchant_pub,
- ds->refund_deadline,
- &coin_sig,
- &deposit_cb,
- ds);
if (NULL == ds->dh)
{
GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not create deposit with EC %d\n",
+ (int) ec);
TALER_TESTING_interpreter_fail (is);
return;
}
@@ -454,11 +537,9 @@ deposit_cleanup (void *cls,
if (NULL != ds->dh)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- ds->is->ip,
- cmd->label);
- TALER_EXCHANGE_deposit_cancel (ds->dh);
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_batch_deposit_cancel (ds->dh);
ds->dh = NULL;
}
if (NULL != ds->retry_task)
@@ -479,10 +560,9 @@ deposit_cleanup (void *cls,
* @param[out] ret result.
* @param trait name of the trait.
* @param index index number of the object to offer.
- *
* @return #GNUNET_OK on success.
*/
-static int
+static enum GNUNET_GenericReturnValue
deposit_traits (void *cls,
const void **ret,
const char *trait,
@@ -492,8 +572,11 @@ deposit_traits (void *cls,
const struct TALER_TESTING_Command *coin_cmd;
/* Will point to coin cmd internals. */
const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv;
+ struct TALER_CoinSpendPublicKeyP coin_spent_pub;
+ const struct TALER_AgeCommitmentProof *age_commitment_proof;
+ const struct TALER_AgeCommitmentHash *h_age_commitment;
- if (GNUNET_YES != ds->command_initialized)
+ if (! ds->command_initialized)
{
/* No access to traits yet. */
GNUNET_break (0);
@@ -509,34 +592,63 @@ deposit_traits (void *cls,
TALER_TESTING_interpreter_fail (ds->is);
return GNUNET_NO;
}
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_coin_priv (coin_cmd,
- ds->coin_index,
- &coin_spent_priv))
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (coin_cmd,
+ ds->coin_index,
+ &coin_spent_priv)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_age_commitment_proof (coin_cmd,
+ ds->coin_index,
+ &age_commitment_proof)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_h_age_commitment (coin_cmd,
+ ds->coin_index,
+ &h_age_commitment)) )
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (ds->is);
return GNUNET_NO;
}
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_spent_priv->eddsa_priv,
+ &coin_spent_pub.eddsa_pub);
+
{
struct TALER_TESTING_Trait traits[] = {
/* First two traits are only available if
- ds->traits is #GNUNET_YES */
+ ds->traits is true */
TALER_TESTING_make_trait_exchange_pub (0,
&ds->exchange_pub),
TALER_TESTING_make_trait_exchange_sig (0,
&ds->exchange_sig),
/* These traits are always available */
+ TALER_TESTING_make_trait_coin_history (0,
+ &ds->che),
TALER_TESTING_make_trait_coin_priv (0,
coin_spent_priv),
- TALER_TESTING_make_trait_wire_details (0,
- ds->wire_details),
- TALER_TESTING_make_trait_contract_terms (0,
- ds->contract_terms),
- TALER_TESTING_make_trait_merchant_priv (0,
- &ds->merchant_priv),
- TALER_TESTING_make_trait_amount_obj (0,
- &ds->amount),
+ TALER_TESTING_make_trait_coin_pub (0,
+ &coin_spent_pub),
+ TALER_TESTING_make_trait_denom_pub (0,
+ ds->denom_pub),
+ TALER_TESTING_make_trait_coin_sig (0,
+ &ds->coin_sig),
+ TALER_TESTING_make_trait_age_commitment_proof (0,
+ age_commitment_proof),
+ TALER_TESTING_make_trait_h_age_commitment (0,
+ h_age_commitment),
+ TALER_TESTING_make_trait_wire_details (ds->wire_details),
+ TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
+ TALER_TESTING_make_trait_merchant_priv (&ds->merchant_priv),
+ TALER_TESTING_make_trait_deposit_amount (0,
+ &ds->amount),
+ TALER_TESTING_make_trait_deposit_fee_amount (0,
+ &ds->deposit_fee),
+ TALER_TESTING_make_trait_timestamp (0,
+ &ds->exchange_timestamp),
+ TALER_TESTING_make_trait_wire_deadline (0,
+ &ds->wire_deadline),
+ TALER_TESTING_make_trait_refund_deadline (0,
+ &ds->refund_deadline),
TALER_TESTING_trait_end ()
};
@@ -550,53 +662,28 @@ deposit_traits (void *cls,
}
-/**
- * Create a "deposit" command.
- *
- * @param label command label.
- * @param coin_reference reference to any operation that can
- * provide a coin.
- * @param coin_index if @a withdraw_reference offers an array of
- * coins, this parameter selects which one in that array.
- * This value is currently ignored, as only one-coin
- * withdrawals are implemented.
- * @param target_account_payto target account for the "deposit"
- * request.
- * @param contract_terms contract terms to be signed over by the
- * coin.
- * @param refund_deadline refund deadline, zero means 'no refunds'.
- * Note, if time were absolute, then it would have come
- * one day and disrupt tests meaning.
- * @param amount how much is going to be deposited.
- * @param expected_response_code expected HTTP response code.
- *
- * @return the command.
- */
struct TALER_TESTING_Command
-TALER_TESTING_cmd_deposit (const char *label,
- const char *coin_reference,
- unsigned int coin_index,
- const char *target_account_payto,
- const char *contract_terms,
- struct GNUNET_TIME_Relative refund_deadline,
- const char *amount,
- unsigned int expected_response_code)
+TALER_TESTING_cmd_deposit (
+ const char *label,
+ const char *coin_reference,
+ unsigned int coin_index,
+ const char *target_account_payto,
+ const char *contract_terms,
+ struct GNUNET_TIME_Relative refund_deadline,
+ const char *amount,
+ unsigned int expected_response_code)
{
struct DepositState *ds;
- json_t *wire_details;
- struct GNUNET_CRYPTO_EddsaPrivateKey *merchant_priv;
- wire_details = TALER_TESTING_make_wire_details (target_account_payto);
ds = GNUNET_new (struct DepositState);
ds->coin_reference = coin_reference;
ds->coin_index = coin_index;
- ds->wire_details = wire_details;
+ ds->wire_details = TALER_TESTING_make_wire_details (target_account_payto);
+ GNUNET_assert (NULL != ds->wire_details);
ds->contract_terms = json_loads (contract_terms,
JSON_REJECT_DUPLICATES,
NULL);
- merchant_priv = GNUNET_CRYPTO_eddsa_key_create ();
- ds->merchant_priv.eddsa_priv = *merchant_priv;
- GNUNET_free (merchant_priv);
+ GNUNET_CRYPTO_eddsa_key_create (&ds->merchant_priv.eddsa_priv);
if (NULL == ds->contract_terms)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -605,25 +692,26 @@ TALER_TESTING_cmd_deposit (const char *label,
label);
GNUNET_assert (0);
}
- ds->timestamp = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&ds->timestamp);
-
- json_object_set_new (ds->contract_terms,
- "timestamp",
- GNUNET_JSON_from_time_abs (ds->timestamp));
- if (0 != refund_deadline.rel_value_us)
+ ds->wallet_timestamp = GNUNET_TIME_timestamp_get ();
+ GNUNET_assert (0 ==
+ json_object_set_new (ds->contract_terms,
+ "timestamp",
+ GNUNET_JSON_from_timestamp (
+ ds->wallet_timestamp)));
+ if (! GNUNET_TIME_relative_is_zero (refund_deadline))
{
- ds->refund_deadline = GNUNET_TIME_relative_to_absolute (refund_deadline);
- (void) GNUNET_TIME_round_abs (&ds->refund_deadline);
- json_object_set_new (ds->contract_terms,
- "refund_deadline",
- GNUNET_JSON_from_time_abs (ds->refund_deadline));
+ ds->refund_deadline = GNUNET_TIME_relative_to_timestamp (refund_deadline);
+ GNUNET_assert (0 ==
+ json_object_set_new (ds->contract_terms,
+ "refund_deadline",
+ GNUNET_JSON_from_timestamp (
+ ds->refund_deadline)));
}
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (amount,
&ds->amount));
ds->expected_response_code = expected_response_code;
- ds->command_initialized = GNUNET_YES;
+ ds->command_initialized = true;
{
struct TALER_TESTING_Command cmd = {
.cls = ds,
@@ -638,50 +726,26 @@ TALER_TESTING_cmd_deposit (const char *label,
}
-/**
- * Create a "deposit" command that references an existing merchant key.
- *
- * @param label command label.
- * @param coin_reference reference to any operation that can
- * provide a coin.
- * @param coin_index if @a withdraw_reference offers an array of
- * coins, this parameter selects which one in that array.
- * This value is currently ignored, as only one-coin
- * withdrawals are implemented.
- * @param target_account_payto target account for the "deposit"
- * request.
- * @param contract_terms contract terms to be signed over by the
- * coin.
- * @param refund_deadline refund deadline, zero means 'no refunds'.
- * Note, if time were absolute, then it would have come
- * one day and disrupt tests meaning.
- * @param amount how much is going to be deposited.
- * @param expected_response_code expected HTTP response code.
- * @param merchant_priv_reference reference to another operation
- * that has a merchant private key trait
- *
- * @return the command.
- */
struct TALER_TESTING_Command
-TALER_TESTING_cmd_deposit_with_ref (const char *label,
- const char *coin_reference,
- unsigned int coin_index,
- const char *target_account_payto,
- const char *contract_terms,
- struct GNUNET_TIME_Relative refund_deadline,
- const char *amount,
- unsigned int expected_response_code,
- const char *merchant_priv_reference)
+TALER_TESTING_cmd_deposit_with_ref (
+ const char *label,
+ const char *coin_reference,
+ unsigned int coin_index,
+ const char *target_account_payto,
+ const char *contract_terms,
+ struct GNUNET_TIME_Relative refund_deadline,
+ const char *amount,
+ unsigned int expected_response_code,
+ const char *merchant_priv_reference)
{
struct DepositState *ds;
- json_t *wire_details;
- wire_details = TALER_TESTING_make_wire_details (target_account_payto);
ds = GNUNET_new (struct DepositState);
ds->merchant_priv_reference = merchant_priv_reference;
ds->coin_reference = coin_reference;
ds->coin_index = coin_index;
- ds->wire_details = wire_details;
+ ds->wire_details = TALER_TESTING_make_wire_details (target_account_payto);
+ GNUNET_assert (NULL != ds->wire_details);
ds->contract_terms = json_loads (contract_terms,
JSON_REJECT_DUPLICATES,
NULL);
@@ -693,25 +757,26 @@ TALER_TESTING_cmd_deposit_with_ref (const char *label,
label);
GNUNET_assert (0);
}
- ds->timestamp = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&ds->timestamp);
-
- json_object_set_new (ds->contract_terms,
- "timestamp",
- GNUNET_JSON_from_time_abs (ds->timestamp));
+ ds->wallet_timestamp = GNUNET_TIME_timestamp_get ();
+ GNUNET_assert (0 ==
+ json_object_set_new (ds->contract_terms,
+ "timestamp",
+ GNUNET_JSON_from_timestamp (
+ ds->wallet_timestamp)));
if (0 != refund_deadline.rel_value_us)
{
- ds->refund_deadline = GNUNET_TIME_relative_to_absolute (refund_deadline);
- (void) GNUNET_TIME_round_abs (&ds->refund_deadline);
- json_object_set_new (ds->contract_terms,
- "refund_deadline",
- GNUNET_JSON_from_time_abs (ds->refund_deadline));
+ ds->refund_deadline = GNUNET_TIME_relative_to_timestamp (refund_deadline);
+ GNUNET_assert (0 ==
+ json_object_set_new (ds->contract_terms,
+ "refund_deadline",
+ GNUNET_JSON_from_timestamp (
+ ds->refund_deadline)));
}
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (amount,
&ds->amount));
ds->expected_response_code = expected_response_code;
- ds->command_initialized = GNUNET_YES;
+ ds->command_initialized = true;
{
struct TALER_TESTING_Command cmd = {
.cls = ds,
@@ -726,19 +791,11 @@ TALER_TESTING_cmd_deposit_with_ref (const char *label,
}
-/**
- * Create a "deposit" command that repeats an existing
- * deposit command.
- *
- * @param label command label.
- * @param deposit_reference which deposit command should we repeat
- * @param expected_response_code expected HTTP response code.
- * @return the command.
- */
struct TALER_TESTING_Command
-TALER_TESTING_cmd_deposit_replay (const char *label,
- const char *deposit_reference,
- unsigned int expected_response_code)
+TALER_TESTING_cmd_deposit_replay (
+ const char *label,
+ const char *deposit_reference,
+ unsigned int expected_response_code)
{
struct DepositState *ds;
@@ -759,13 +816,6 @@ TALER_TESTING_cmd_deposit_replay (const char *label,
}
-/**
- * Modify a deposit command to enable retries when we get transient
- * errors from the exchange.
- *
- * @param cmd a deposit command
- * @return the command with retries enabled
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_deposit_with_retry (struct TALER_TESTING_Command cmd)
{
diff --git a/src/testing/testing_api_cmd_deposits_get.c b/src/testing/testing_api_cmd_deposits_get.c
index 8eab8f51b..5d4436e2a 100644
--- a/src/testing/testing_api_cmd_deposits_get.c
+++ b/src/testing/testing_api_cmd_deposits_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
+ 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
@@ -40,6 +40,11 @@ struct TrackTransactionState
const char *bank_transfer_reference;
/**
+ * Our command.
+ */
+ const struct TALER_TESTING_Command *cmd;
+
+ /**
* The WTID associated by the transaction being tracked.
*/
struct TALER_WireTransferIdentifierRawP wtid;
@@ -50,12 +55,31 @@ struct TrackTransactionState
unsigned int expected_response_code;
/**
+ * Set to the KYC requirement payto hash *if* the exchange replied with a
+ * request for KYC (#MHD_HTTP_ACCEPTED).
+ * Note: set based on our @e merchant_payto_uri, as
+ * the exchange does not respond with the payto hash.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Set to the KYC requirement row *if* the exchange replied with
+ * a request for KYC (#MHD_HTTP_ACCEPTED).
+ */
+ uint64_t requirement_row;
+
+ /**
* Reference to any operation that can provide a transaction.
* Will be the transaction to track.
*/
const char *transaction_reference;
/**
+ * Payto URI of the merchant receiving the deposit.
+ */
+ char *merchant_payto_uri;
+
+ /**
* Index of the coin involved in the transaction. Recall:
* at the exchange, the tracking is done _per coin_.
*/
@@ -80,61 +104,36 @@ struct TrackTransactionState
* line matches our expectations.
*
* @param cls closure.
- * @param http_status HTTP status code we got.
- * @param ec taler-specific error code.
- * @param exchange_pub public key of the exchange
- * @param json original json reply (may include signatures, those
- * have then been validated already).
- * @param wtid wire transfer identifier, NULL if exchange did not
- * execute the transaction yet.
- * @param execution_time actual or planned execution time for the
- * wire transfer.
- * @param coin_contribution contribution to the total amount of
- * the deposited coin (can be NULL).
+ * @param dr GET deposit response details
*/
static void
deposit_wtid_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const json_t *json,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- struct GNUNET_TIME_Absolute execution_time,
- const struct TALER_Amount *coin_contribution)
+ const struct TALER_EXCHANGE_GetDepositResponse *dr)
{
struct TrackTransactionState *tts = cls;
struct TALER_TESTING_Interpreter *is = tts->is;
- struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
- (void) coin_contribution;
- (void) exchange_pub;
- (void) ec;
- (void) execution_time;
tts->tth = NULL;
- if (tts->expected_response_code != http_status)
+ if (tts->expected_response_code != dr->hr.http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- http_status,
- cmd->label,
- __FILE__,
- __LINE__);
- json_dumpf (json, stderr, 0);
- TALER_TESTING_interpreter_fail (is);
+ TALER_TESTING_unexpected_status (is,
+ dr->hr.http_status,
+ tts->expected_response_code);
return;
}
- switch (http_status)
+ switch (dr->hr.http_status)
{
case MHD_HTTP_OK:
- tts->wtid = *wtid;
+ tts->wtid = dr->details.ok.wtid;
if (NULL != tts->bank_transfer_reference)
{
const struct TALER_TESTING_Command *bank_transfer_cmd;
const struct TALER_WireTransferIdentifierRawP *wtid_want;
/* _this_ wire transfer subject line. */
- bank_transfer_cmd = TALER_TESTING_interpreter_lookup_command
- (is, tts->bank_transfer_reference);
+ bank_transfer_cmd
+ = TALER_TESTING_interpreter_lookup_command (is,
+ tts->bank_transfer_reference);
if (NULL == bank_transfer_cmd)
{
GNUNET_break (0);
@@ -143,8 +142,8 @@ deposit_wtid_cb (void *cls,
}
if (GNUNET_OK !=
- TALER_TESTING_get_trait_wtid
- (bank_transfer_cmd, 0, &wtid_want))
+ TALER_TESTING_get_trait_wtid (bank_transfer_cmd,
+ &wtid_want))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
@@ -152,7 +151,7 @@ deposit_wtid_cb (void *cls,
}
/* Compare that expected and gotten subjects match. */
- if (0 != GNUNET_memcmp (wtid,
+ if (0 != GNUNET_memcmp (&dr->details.ok.wtid,
wtid_want))
{
GNUNET_break (0);
@@ -163,6 +162,10 @@ deposit_wtid_cb (void *cls,
break;
case MHD_HTTP_ACCEPTED:
/* allowed, nothing to check here */
+ TALER_payto_hash (tts->merchant_payto_uri,
+ &tts->h_payto);
+ tts->requirement_row
+ = dr->details.accepted.requirement_row;
break;
case MHD_HTTP_NOT_FOUND:
/* allowed, nothing to check here */
@@ -193,11 +196,11 @@ track_transaction_run (void *cls,
struct TALER_CoinSpendPublicKeyP coin_pub;
const json_t *contract_terms;
const json_t *wire_details;
- struct GNUNET_HashCode h_wire_details;
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_MerchantWireHashP h_wire_details;
+ struct TALER_PrivateContractHashP h_contract_terms;
const struct TALER_MerchantPrivateKeyP *merchant_priv;
- (void) cmd;
+ tts->cmd = cmd;
tts->is = is;
transaction_cmd
= TALER_TESTING_interpreter_lookup_command (tts->is,
@@ -225,17 +228,17 @@ track_transaction_run (void *cls,
/* Get the strings.. */
if (GNUNET_OK !=
TALER_TESTING_get_trait_wire_details (transaction_cmd,
- 0,
&wire_details))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (tts->is);
return;
}
-
+ tts->merchant_payto_uri
+ = GNUNET_strdup (json_string_value (json_object_get (wire_details,
+ "payto_uri")));
if (GNUNET_OK !=
TALER_TESTING_get_trait_contract_terms (transaction_cmd,
- 0,
&contract_terms))
{
GNUNET_break (0);
@@ -257,12 +260,11 @@ track_transaction_run (void *cls,
TALER_JSON_merchant_wire_signature_hash (wire_details,
&h_wire_details)) &&
(GNUNET_OK ==
- TALER_JSON_hash (contract_terms,
- &h_contract_terms)) );
+ TALER_JSON_contract_hash (contract_terms,
+ &h_contract_terms)) );
if (GNUNET_OK !=
TALER_TESTING_get_trait_merchant_priv (transaction_cmd,
- 0,
&merchant_priv))
{
GNUNET_break (0);
@@ -270,13 +272,17 @@ track_transaction_run (void *cls,
return;
}
- tts->tth = TALER_EXCHANGE_deposits_get (is->exchange,
- merchant_priv,
- &h_wire_details,
- &h_contract_terms,
- &coin_pub,
- &deposit_wtid_cb,
- tts);
+ tts->tth = TALER_EXCHANGE_deposits_get (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ merchant_priv,
+ &h_wire_details,
+ &h_contract_terms,
+ &coin_pub,
+ GNUNET_TIME_UNIT_ZERO,
+ &deposit_wtid_cb,
+ tts);
GNUNET_assert (NULL != tts->tth);
}
@@ -296,13 +302,12 @@ track_transaction_cleanup (void *cls,
if (NULL != tts->tth)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- tts->is->ip,
- cmd->label);
+ TALER_TESTING_command_incomplete (tts->is,
+ cmd->label);
TALER_EXCHANGE_deposits_get_cancel (tts->tth);
tts->tth = NULL;
}
+ GNUNET_free (tts->merchant_payto_uri);
GNUNET_free (tts);
}
@@ -316,7 +321,7 @@ track_transaction_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success.
*/
-static int
+static enum GNUNET_GenericReturnValue
track_transaction_traits (void *cls,
const void **ret,
const char *trait,
@@ -324,7 +329,11 @@ track_transaction_traits (void *cls,
{
struct TrackTransactionState *tts = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_wtid (0, &tts->wtid),
+ TALER_TESTING_make_trait_wtid (&tts->wtid),
+ TALER_TESTING_make_trait_legi_requirement_row (
+ &tts->requirement_row),
+ TALER_TESTING_make_trait_h_payto (&tts->h_payto),
+ TALER_TESTING_make_trait_payto_uri (tts->merchant_payto_uri),
TALER_TESTING_trait_end ()
};
@@ -335,19 +344,6 @@ track_transaction_traits (void *cls,
}
-/**
- * Create a "track transaction" command.
- *
- * @param label the command label.
- * @param transaction_reference reference to a deposit operation,
- * will be used to get the input data for the track.
- * @param coin_index index of the coin involved in the transaction.
- * @param expected_response_code expected HTTP response code.
- * @param bank_transfer_reference reference to a command that
- * can offer a WTID so as to check that against what WTID
- * the tracked operation has. Set as NULL if not needed.
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_track_transaction (const char *label,
const char *transaction_reference,
diff --git a/src/testing/testing_api_cmd_exec_aggregator.c b/src/testing/testing_api_cmd_exec_aggregator.c
index f11671888..1f05576ff 100644
--- a/src/testing/testing_api_cmd_exec_aggregator.c
+++ b/src/testing/testing_api_cmd_exec_aggregator.c
@@ -43,6 +43,11 @@ struct AggregatorState
* Configuration file used by the aggregator.
*/
const char *config_filename;
+
+ /**
+ * Run with KYC restrictions on.
+ */
+ bool kyc_on;
};
@@ -62,13 +67,15 @@ aggregator_run (void *cls,
(void) cmd;
as->aggregator_proc
- = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-exchange-aggregator",
"taler-exchange-aggregator",
"-c", as->config_filename,
"-t", /* exit when done */
+ (as->kyc_on)
+ ? NULL
+ : "-y", /* skip KYC */
NULL);
if (NULL == as->aggregator_proc)
{
@@ -116,7 +123,7 @@ aggregator_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
aggregator_traits (void *cls,
const void **ret,
const char *trait,
@@ -124,7 +131,7 @@ aggregator_traits (void *cls,
{
struct AggregatorState *as = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0, &as->aggregator_proc),
+ TALER_TESTING_make_trait_process (&as->aggregator_proc),
TALER_TESTING_trait_end ()
};
@@ -135,14 +142,6 @@ aggregator_traits (void *cls,
}
-/**
- * Make a "aggregator" CMD.
- *
- * @param label command label.
- * @param config_filename configuration file for the
- * aggregator to use.
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_exec_aggregator (const char *label,
const char *config_filename)
@@ -165,4 +164,27 @@ TALER_TESTING_cmd_exec_aggregator (const char *label,
}
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_aggregator_with_kyc (const char *label,
+ const char *config_filename)
+{
+ struct AggregatorState *as;
+
+ as = GNUNET_new (struct AggregatorState);
+ as->config_filename = config_filename;
+ as->kyc_on = true;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = as,
+ .label = label,
+ .run = &aggregator_run,
+ .cleanup = &aggregator_cleanup,
+ .traits = &aggregator_traits
+ };
+
+ return cmd;
+ }
+}
+
+
/* end of testing_api_cmd_exec_aggregator.c */
diff --git a/src/testing/testing_api_cmd_exec_auditor-offline.c b/src/testing/testing_api_cmd_exec_auditor-offline.c
new file mode 100644
index 000000000..1899d5c06
--- /dev/null
+++ b/src/testing/testing_api_cmd_exec_auditor-offline.c
@@ -0,0 +1,163 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 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 testing/testing_api_cmd_exec_auditor-offline.c
+ * @brief run the taler-exchange-auditor-offline command
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "auditor-offline" CMD.
+ */
+struct AuditorOfflineState
+{
+
+ /**
+ * AuditorOffline process.
+ */
+ struct GNUNET_OS_Process *auditor_offline_proc;
+
+ /**
+ * Configuration file used by the auditor-offline.
+ */
+ const char *config_filename;
+
+};
+
+
+/**
+ * Run the command. Use the `taler-exchange-auditor-offline' program.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ * @param is interpreter state.
+ */
+static void
+auditor_offline_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AuditorOfflineState *as = cls;
+
+ (void) cmd;
+ as->auditor_offline_proc
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-auditor-offline",
+ "taler-auditor-offline",
+ "-c", as->config_filename,
+ "-L", "INFO",
+ "download",
+ "sign",
+ "upload",
+ NULL);
+ if (NULL == as->auditor_offline_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "auditor-offline" CMD, and possibly kill its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+auditor_offline_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AuditorOfflineState *as = cls;
+
+ (void) cmd;
+ if (NULL != as->auditor_offline_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (as->auditor_offline_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (as->auditor_offline_proc);
+ GNUNET_OS_process_destroy (as->auditor_offline_proc);
+ as->auditor_offline_proc = NULL;
+ }
+ GNUNET_free (as);
+}
+
+
+/**
+ * Offer "auditor-offline" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+auditor_offline_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct AuditorOfflineState *as = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (&as->auditor_offline_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_auditor_offline (const char *label,
+ const char *config_filename)
+{
+ struct AuditorOfflineState *as;
+
+ as = GNUNET_new (struct AuditorOfflineState);
+ as->config_filename = config_filename;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = as,
+ .label = label,
+ .run = &auditor_offline_run,
+ .cleanup = &auditor_offline_cleanup,
+ .traits = &auditor_offline_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_exec_auditor-offline.c */
diff --git a/src/testing/testing_api_cmd_exec_auditor-sign.c b/src/testing/testing_api_cmd_exec_auditor-sign.c
deleted file mode 100644
index 4c88ccca0..000000000
--- a/src/testing/testing_api_cmd_exec_auditor-sign.c
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_cmd_exec_auditor-sign.c
- * @brief run the taler-exchange-aggregator command
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-
-/**
- * State for a "auditor sign" CMD.
- */
-struct AuditorSignState
-{
-
- /**
- * Handle to the process making the signature.
- */
- struct GNUNET_OS_Process *auditor_sign_proc;
-
- /**
- * Configuration file used by the command.
- */
- const char *config_filename;
-
- /**
- * File name of signed blob.
- */
- char *signed_keys_out;
-};
-
-
-/**
- * Run the command; calls the `taler-auditor-sign' program.
- *
- * @param cls closure.
- * @param cmd the command.
- * @param is interpreter state.
- */
-static void
-auditor_sign_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct AuditorSignState *ass = cls;
- struct GNUNET_CONFIGURATION_Handle *cfg;
- char *test_home_dir;
- char *exchange_master_pub;
- struct GNUNET_TIME_Absolute now;
-
- (void) cmd;
- cfg = GNUNET_CONFIGURATION_create ();
- if (GNUNET_OK != GNUNET_CONFIGURATION_load
- (cfg, ass->config_filename))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (cfg,
- "paths",
- "TALER_TEST_HOME",
- &test_home_dir))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "paths",
- "TALER_TEST_HOME");
- GNUNET_CONFIGURATION_destroy (cfg);
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
- now = GNUNET_TIME_absolute_get ();
- GNUNET_asprintf
- (&ass->signed_keys_out,
- "%s/.local/share/taler/auditors/auditor-%llu.out",
- test_home_dir,
- (unsigned long long) now.abs_value_us);
- GNUNET_free (test_home_dir);
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "exchange",
- "MASTER_PUBLIC_KEY",
- &exchange_master_pub))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "MASTER_PUBLIC_KEY");
- GNUNET_CONFIGURATION_destroy (cfg);
-
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
- GNUNET_CONFIGURATION_destroy (cfg);
-
- ass->auditor_sign_proc = GNUNET_OS_start_process
- (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-auditor-sign",
- "taler-auditor-sign",
- "-c", ass->config_filename,
- "-u", "http://auditor/",
- "-m", exchange_master_pub,
- "-r", "auditor.in",
- "-o", ass->signed_keys_out,
- NULL);
- GNUNET_free (exchange_master_pub);
- if (NULL == ass->auditor_sign_proc)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- TALER_TESTING_wait_for_sigchld (is);
-}
-
-
-/**
- * Free the state of a "auditor sign" CMD, and possibly
- * kill its process if it did not terminate correctly.
- *
- * @param cls closure.
- * @param cmd the command being freed.
- */
-static void
-auditor_sign_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct AuditorSignState *ass = cls;
-
- (void) cmd;
- if (NULL != ass->auditor_sign_proc)
- {
- GNUNET_break (0 == GNUNET_OS_process_kill
- (ass->auditor_sign_proc, SIGKILL));
- GNUNET_OS_process_wait (ass->auditor_sign_proc);
- GNUNET_OS_process_destroy (ass->auditor_sign_proc);
- ass->auditor_sign_proc = NULL;
- }
- GNUNET_free_non_null (ass->signed_keys_out);
- GNUNET_free (ass);
-}
-
-
-/**
- * Offer "auditor sign" CMD internal data to other commands.
- *
- * @param cls closure.
- * @param[out] ret result.
- * @param trait name of the trait.
- * @param index index number of the object to offer.
- * @return #GNUNET_OK on success.
- */
-static int
-auditor_sign_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- struct AuditorSignState *ass = cls;
- struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0, &ass->auditor_sign_proc),
- TALER_TESTING_trait_end ()
- };
-
- return TALER_TESTING_get_trait (traits,
- ret,
- trait,
- index);
-}
-
-
-/**
- * Make a "auditor sign" CMD.
- *
- * @param label command label
- * @param config_filename configuration filename
- *
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_exec_auditor_sign (const char *label,
- const char *config_filename)
-{
- struct AuditorSignState *ass;
-
- ass = GNUNET_new (struct AuditorSignState);
- ass->config_filename = config_filename;
-
- {
- struct TALER_TESTING_Command cmd = {
- .cls = ass,
- .label = label,
- .run = &auditor_sign_run,
- .cleanup = &auditor_sign_cleanup,
- .traits = &auditor_sign_traits
- };
-
- return cmd;
- }
-}
-
-
-/* end of testing_api_cmd_exec_auditor-sign.c */
diff --git a/src/testing/testing_api_cmd_exec_closer.c b/src/testing/testing_api_cmd_exec_closer.c
index 783696996..2501b39a6 100644
--- a/src/testing/testing_api_cmd_exec_closer.c
+++ b/src/testing/testing_api_cmd_exec_closer.c
@@ -49,7 +49,7 @@ struct CloserState
* expect_close is true. Will be of type
* #TALER_EXCHANGE_RTT_RESERVE_CLOSED.
*/
- struct TALER_EXCHANGE_ReserveHistory reserve_history;
+ struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
/**
* If the closer filled a reserve (@e expect_close is set), this is set to
@@ -65,7 +65,7 @@ struct CloserState
/**
* Do we expect the command to actually close a reserve?
*/
- int expect_close;
+ bool expect_close;
};
@@ -91,9 +91,9 @@ closer_run (void *cls,
rcmd = TALER_TESTING_interpreter_lookup_command (is,
as->reserve_ref);
+ GNUNET_assert (NULL != rcmd);
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_pub (rcmd,
- 0,
&reserve_pubp))
{
GNUNET_break (0);
@@ -103,8 +103,7 @@ closer_run (void *cls,
as->reserve_pub = *reserve_pubp;
}
as->closer_proc
- = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-exchange-closer",
"taler-exchange-closer",
@@ -157,7 +156,7 @@ closer_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
closer_traits (void *cls,
const void **ret,
const char *trait,
@@ -165,13 +164,12 @@ closer_traits (void *cls,
{
struct CloserState *as = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0, &as->closer_proc),
+ TALER_TESTING_make_trait_process (&as->closer_proc),
TALER_TESTING_trait_end ()
};
struct TALER_TESTING_Trait xtraits[] = {
- TALER_TESTING_make_trait_process (0, &as->closer_proc),
- TALER_TESTING_make_trait_reserve_pub (0,
- &as->reserve_pub),
+ TALER_TESTING_make_trait_process (&as->closer_proc),
+ TALER_TESTING_make_trait_reserve_pub (&as->reserve_pub),
TALER_TESTING_make_trait_reserve_history (0,
&as->reserve_history),
TALER_TESTING_trait_end ()
@@ -186,22 +184,6 @@ closer_traits (void *cls,
}
-/**
- * Make a "closer" CMD. Note that it is right now not supported to run the
- * closer to close multiple reserves in combination with a subsequent reserve
- * status call, as we cannot generate the traits necessary for multiple closed
- * reserves. You can work around this by using multiple closer commands, one
- * per reserve that is being closed.
- *
- * @param label command label.
- * @param config_filename configuration file for the
- * closer to use.
- * @param expected_amount amount we expect to see wired from a @a expected_reserve_ref
- * @param expected_fee closing fee we expect to see
- * @param expected_reserve_ref reference to a reserve we expect the closer to drain;
- * NULL if we do not expect the closer to do anything
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_exec_closer (const char *label,
const char *config_filename,
@@ -215,7 +197,7 @@ TALER_TESTING_cmd_exec_closer (const char *label,
as->config_filename = config_filename;
if (NULL != expected_reserve_ref)
{
- as->expect_close = GNUNET_YES;
+ as->expect_close = true;
as->reserve_ref = expected_reserve_ref;
if (GNUNET_OK !=
TALER_string_to_amount (expected_amount,
@@ -239,12 +221,12 @@ TALER_TESTING_cmd_exec_closer (const char *label,
}
/* expected amount includes fee, while our argument
gives the amount _without_ the fee. So add the fee. */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&as->reserve_history.amount,
- &as->reserve_history.amount,
- &as->reserve_history.details.close_details.
- fee));
- as->reserve_history.type = TALER_EXCHANGE_RTT_CLOSE;
+ GNUNET_assert (0 <=
+ TALER_amount_add (
+ &as->reserve_history.amount,
+ &as->reserve_history.amount,
+ &as->reserve_history.details.close_details.fee));
+ as->reserve_history.type = TALER_EXCHANGE_RTT_CLOSING;
}
{
struct TALER_TESTING_Command cmd = {
diff --git a/src/testing/testing_api_cmd_exec_expire.c b/src/testing/testing_api_cmd_exec_expire.c
new file mode 100644
index 000000000..aabf37361
--- /dev/null
+++ b/src/testing/testing_api_cmd_exec_expire.c
@@ -0,0 +1,162 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_exec_expire.c
+ * @brief run the taler-exchange-expire command
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "expire" CMD.
+ */
+struct ExpireState
+{
+
+ /**
+ * Process for the expireer.
+ */
+ struct GNUNET_OS_Process *expire_proc;
+
+ /**
+ * Configuration file used by the expireer.
+ */
+ const char *config_filename;
+};
+
+
+/**
+ * Run the command; use the `taler-exchange-expire' program.
+ *
+ * @param cls closure.
+ * @param cmd command currently being executed.
+ * @param is interpreter state.
+ */
+static void
+expire_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct ExpireState *ws = cls;
+
+ (void) cmd;
+ ws->expire_proc
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-exchange-expire",
+ "taler-exchange-expire",
+ "-L", "INFO",
+ "-c", ws->config_filename,
+ "-t", /* exit when done */
+ NULL);
+ if (NULL == ws->expire_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "expire" CMD, and possibly
+ * kills its process if it did not terminate regularly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+expire_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct ExpireState *ws = cls;
+
+ (void) cmd;
+ if (NULL != ws->expire_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ws->expire_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (ws->expire_proc);
+ GNUNET_OS_process_destroy (ws->expire_proc);
+ ws->expire_proc = NULL;
+ }
+ GNUNET_free (ws);
+}
+
+
+/**
+ * Offer "expire" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+expire_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct ExpireState *ws = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (&ws->expire_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_expire (const char *label,
+ const char *config_filename)
+{
+ struct ExpireState *ws;
+
+ ws = GNUNET_new (struct ExpireState);
+ ws->config_filename = config_filename;
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ws,
+ .label = label,
+ .run = &expire_run,
+ .cleanup = &expire_cleanup,
+ .traits = &expire_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_exec_expire.c */
diff --git a/src/testing/testing_api_cmd_exec_keyup.c b/src/testing/testing_api_cmd_exec_keyup.c
deleted file mode 100644
index 4283c7eae..000000000
--- a/src/testing/testing_api_cmd_exec_keyup.c
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_cmd_exec_keyup.c
- * @brief run the taler-exchange-keyup command
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-
-/**
- * State for a "keyup" CMD.
- */
-struct KeyupState
-{
-
- /**
- * Process for the "keyup" command.
- */
- struct GNUNET_OS_Process *keyup_proc;
-
- /**
- * Configuration file used by the command.
- */
- const char *config_filename;
-
- /**
- * If GNUNET_YES, then the fake @e now value will be
- * passed to taler-exchange-keyup via the --time
- * option.
- */
- unsigned int with_now;
-
- /**
- * User-provided fake now.
- */
- struct GNUNET_TIME_Absolute now;
-};
-
-
-/**
- * Run the command; calls the `taler-exchange-keyup' program.
- *
- * @param cls closure.
- * @param cmd the commaind being run.
- * @param is interpreter state.
- */
-static void
-keyup_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct KeyupState *ks = cls;
-
- if (GNUNET_YES == ks->with_now)
- {
- ks->keyup_proc = GNUNET_OS_start_process
- (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-exchange-keyup",
- "taler-exchange-keyup",
- "-c", ks->config_filename,
- "-o", "auditor.in",
- "--time",
- GNUNET_STRINGS_absolute_time_to_string (ks->now),
- NULL);
- }
- else
- ks->keyup_proc = GNUNET_OS_start_process
- (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-exchange-keyup",
- "taler-exchange-keyup",
- "-c", ks->config_filename,
- "-o", "auditor.in",
- NULL);
-
- if (NULL == ks->keyup_proc)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
- /* This function does not tell whether the command
- * succeeded or not! */
- TALER_TESTING_wait_for_sigchld (is);
-}
-
-
-/**
- * Free the state of a "keyup" CMD, and possibly kills its
- * process if it did not terminate correctly.
- *
- * @param cls closure.
- * @param cmd the command being freed.
- */
-static void
-keyup_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct KeyupState *ks = cls;
-
- (void) cmd;
- if (NULL != ks->keyup_proc)
- {
- GNUNET_break (0 ==
- GNUNET_OS_process_kill (ks->keyup_proc,
- SIGKILL));
- GNUNET_OS_process_wait (ks->keyup_proc);
- GNUNET_OS_process_destroy (ks->keyup_proc);
- ks->keyup_proc = NULL;
- }
- GNUNET_free (ks);
-}
-
-
-/**
- * Offer "keyup" CMD internal data to other commands.
- *
- * @param cls closure.
- * @param[out] ret result
- * @param trait name of the trait.
- * @param index index number of the object to offer.
- *
- * @return #GNUNET_OK on success.
- */
-static int
-keyup_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- struct KeyupState *ks = cls;
- struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0, &ks->keyup_proc),
- TALER_TESTING_trait_end ()
- };
-
- return TALER_TESTING_get_trait (traits,
- ret,
- trait,
- index);
-}
-
-
-/**
- * Make the "keyup" CMD, with "--timestamp" option.
- *
- * @param label command label.
- * @param config_filename configuration filename.
- * @param now Unix timestamp representing the fake "now".
- *
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_exec_keyup_with_now
- (const char *label,
- const char *config_filename,
- struct GNUNET_TIME_Absolute now)
-{
- struct KeyupState *ks;
-
- ks = GNUNET_new (struct KeyupState);
- ks->config_filename = config_filename;
- ks->now = now;
- ks->with_now = GNUNET_YES;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = ks,
- .label = label,
- .run = &keyup_run,
- .cleanup = &keyup_cleanup,
- .traits = &keyup_traits
- };
-
- return cmd;
- }
-}
-
-
-/**
- * Make the "keyup" CMD.
- *
- * @param label command label.
- * @param config_filename configuration filename.
- *
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_exec_keyup (const char *label,
- const char *config_filename)
-{
- struct KeyupState *ks;
-
- ks = GNUNET_new (struct KeyupState);
- ks->config_filename = config_filename;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = ks,
- .label = label,
- .run = &keyup_run,
- .cleanup = &keyup_cleanup,
- .traits = &keyup_traits
- };
-
- return cmd;
- }
-}
-
-
-/* end of testing_api_cmd_exec_keyup.c */
diff --git a/src/testing/testing_api_cmd_exec_router.c b/src/testing/testing_api_cmd_exec_router.c
new file mode 100644
index 000000000..d7f3fe265
--- /dev/null
+++ b/src/testing/testing_api_cmd_exec_router.c
@@ -0,0 +1,161 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_exec_router.c
+ * @brief run the taler-exchange-router command
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "router" CMD.
+ */
+struct RouterState
+{
+
+ /**
+ * Process for the routerer.
+ */
+ struct GNUNET_OS_Process *router_proc;
+
+ /**
+ * Configuration file used by the routerer.
+ */
+ const char *config_filename;
+};
+
+
+/**
+ * Run the command; use the `taler-exchange-router' program.
+ *
+ * @param cls closure.
+ * @param cmd command currently being executed.
+ * @param is interpreter state.
+ */
+static void
+router_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RouterState *ws = cls;
+
+ (void) cmd;
+ ws->router_proc
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-exchange-router",
+ "taler-exchange-router",
+ "-c", ws->config_filename,
+ "-t", /* exit when done */
+ NULL);
+ if (NULL == ws->router_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "router" CMD, and possibly
+ * kills its process if it did not terminate regularly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+router_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct RouterState *ws = cls;
+
+ (void) cmd;
+ if (NULL != ws->router_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ws->router_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (ws->router_proc);
+ GNUNET_OS_process_destroy (ws->router_proc);
+ ws->router_proc = NULL;
+ }
+ GNUNET_free (ws);
+}
+
+
+/**
+ * Offer "router" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+router_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct RouterState *ws = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (&ws->router_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_router (const char *label,
+ const char *config_filename)
+{
+ struct RouterState *ws;
+
+ ws = GNUNET_new (struct RouterState);
+ ws->config_filename = config_filename;
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ws,
+ .label = label,
+ .run = &router_run,
+ .cleanup = &router_cleanup,
+ .traits = &router_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_exec_router.c */
diff --git a/src/testing/testing_api_cmd_exec_transfer.c b/src/testing/testing_api_cmd_exec_transfer.c
index c8764f93d..300413b4b 100644
--- a/src/testing/testing_api_cmd_exec_transfer.c
+++ b/src/testing/testing_api_cmd_exec_transfer.c
@@ -60,13 +60,15 @@ transfer_run (void *cls,
{
struct TransferState *as = cls;
+ (void) cmd;
as->transfer_proc
- = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-exchange-transfer",
"taler-exchange-transfer",
"-c", as->config_filename,
+ "-S", "1",
+ "-w", "0",
"-t", /* exit when done */
NULL);
if (NULL == as->transfer_proc)
@@ -92,6 +94,7 @@ transfer_cleanup (void *cls,
{
struct TransferState *as = cls;
+ (void) cmd;
if (NULL != as->transfer_proc)
{
GNUNET_break (0 ==
@@ -114,7 +117,7 @@ transfer_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
transfer_traits (void *cls,
const void **ret,
const char *trait,
@@ -122,7 +125,7 @@ transfer_traits (void *cls,
{
struct TransferState *as = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0, &as->transfer_proc),
+ TALER_TESTING_make_trait_process (&as->transfer_proc),
TALER_TESTING_trait_end ()
};
@@ -133,14 +136,6 @@ transfer_traits (void *cls,
}
-/**
- * Make a "transfer" CMD.
- *
- * @param label command label.
- * @param config_filename configuration file for the
- * transfer to use.
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_exec_transfer (const char *label,
const char *config_filename)
diff --git a/src/testing/testing_api_cmd_exec_wget.c b/src/testing/testing_api_cmd_exec_wget.c
new file mode 100644
index 000000000..67aceca0a
--- /dev/null
+++ b/src/testing/testing_api_cmd_exec_wget.c
@@ -0,0 +1,158 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_exec_wget.c
+ * @brief run a wget command
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "wget" CMD.
+ */
+struct WgetState
+{
+ /**
+ * Process for the wgeter.
+ */
+ struct GNUNET_OS_Process *wget_proc;
+
+ /**
+ * URL to used by the wget.
+ */
+ const char *url;
+};
+
+
+/**
+ * Run the command; use the `wget' program.
+ *
+ * @param cls closure.
+ * @param cmd command currently being executed.
+ * @param is interpreter state.
+ */
+static void
+wget_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct WgetState *ws = cls;
+
+ (void) cmd;
+ ws->wget_proc
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "wget",
+ "wget",
+ ws->url,
+ NULL);
+ if (NULL == ws->wget_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "wget" CMD, and possibly
+ * kills its process if it did not terminate regularly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+wget_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct WgetState *ws = cls;
+
+ (void) cmd;
+ if (NULL != ws->wget_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ws->wget_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (ws->wget_proc);
+ GNUNET_OS_process_destroy (ws->wget_proc);
+ ws->wget_proc = NULL;
+ }
+ GNUNET_free (ws);
+}
+
+
+/**
+ * Offer "wget" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+wget_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct WgetState *ws = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (&ws->wget_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_wget (const char *label,
+ const char *url)
+{
+ struct WgetState *ws;
+
+ ws = GNUNET_new (struct WgetState);
+ ws->url = url;
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ws,
+ .label = label,
+ .run = &wget_run,
+ .cleanup = &wget_cleanup,
+ .traits = &wget_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_exec_wget.c */
diff --git a/src/testing/testing_api_cmd_exec_wirewatch.c b/src/testing/testing_api_cmd_exec_wirewatch.c
index 7a1a27a51..b6ed4f0f1 100644
--- a/src/testing/testing_api_cmd_exec_wirewatch.c
+++ b/src/testing/testing_api_cmd_exec_wirewatch.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018 Taler Systems SA
+ Copyright (C) 2018, 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
@@ -34,7 +34,6 @@
*/
struct WirewatchState
{
-
/**
* Process for the wirewatcher.
*/
@@ -44,8 +43,14 @@ struct WirewatchState
* Configuration file used by the wirewatcher.
*/
const char *config_filename;
+
+ /**
+ * Account section to be used by the wirewatcher.
+ */
+ const char *account_section;
};
+
/**
* Run the command; use the `taler-exchange-wirewatch' program.
*
@@ -60,14 +65,20 @@ wirewatch_run (void *cls,
{
struct WirewatchState *ws = cls;
+ (void) cmd;
ws->wirewatch_proc
- = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-exchange-wirewatch",
"taler-exchange-wirewatch",
"-c", ws->config_filename,
+ "-S", "1",
+ "-w", "0",
"-t", /* exit when done */
+ (NULL == ws->account_section)
+ ? NULL
+ : "-a",
+ ws->account_section,
NULL);
if (NULL == ws->wirewatch_proc)
{
@@ -92,6 +103,7 @@ wirewatch_cleanup (void *cls,
{
struct WirewatchState *ws = cls;
+ (void) cmd;
if (NULL != ws->wirewatch_proc)
{
GNUNET_break (0 ==
@@ -114,7 +126,7 @@ wirewatch_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success.
*/
-static int
+static enum GNUNET_GenericReturnValue
wirewatch_traits (void *cls,
const void **ret,
const char *trait,
@@ -122,8 +134,7 @@ wirewatch_traits (void *cls,
{
struct WirewatchState *ws = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0,
- &ws->wirewatch_proc),
+ TALER_TESTING_make_trait_process (&ws->wirewatch_proc),
TALER_TESTING_trait_end ()
};
@@ -134,22 +145,16 @@ wirewatch_traits (void *cls,
}
-/**
- * Make a "wirewatch" CMD.
- *
- * @param label command label.
- * @param config_filename configuration filename.
- * @return the command.
- */
struct TALER_TESTING_Command
-TALER_TESTING_cmd_exec_wirewatch (const char *label,
- const char *config_filename)
+TALER_TESTING_cmd_exec_wirewatch2 (const char *label,
+ const char *config_filename,
+ const char *account_section)
{
struct WirewatchState *ws;
ws = GNUNET_new (struct WirewatchState);
ws->config_filename = config_filename;
-
+ ws->account_section = account_section;
{
struct TALER_TESTING_Command cmd = {
.cls = ws,
@@ -164,4 +169,14 @@ TALER_TESTING_cmd_exec_wirewatch (const char *label,
}
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_wirewatch (const char *label,
+ const char *config_filename)
+{
+ return TALER_TESTING_cmd_exec_wirewatch2 (label,
+ config_filename,
+ NULL);
+}
+
+
/* end of testing_api_cmd_exec_wirewatch.c */
diff --git a/src/testing/testing_api_cmd_get_auditor.c b/src/testing/testing_api_cmd_get_auditor.c
new file mode 100644
index 000000000..ab4e32665
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_auditor.c
@@ -0,0 +1,286 @@
+/*
+ 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
+ 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 testing/testing_api_cmd_get_auditor.c
+ * @brief Command to get an auditor handle
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "get auditor" CMD.
+ */
+struct GetAuditorState
+{
+
+ /**
+ * Private key of the auditor.
+ */
+ struct TALER_AuditorPrivateKeyP auditor_priv;
+
+ /**
+ * Public key of the auditor.
+ */
+ struct TALER_AuditorPublicKeyP auditor_pub;
+
+ /**
+ * Our interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Auditor handle used to get the configuration.
+ */
+ struct TALER_AUDITOR_GetConfigHandle *auditor;
+
+ /**
+ * URL of the auditor.
+ */
+ char *auditor_url;
+
+ /**
+ * Filename of the master private key of the auditor.
+ */
+ char *priv_file;
+
+};
+
+
+/**
+ * Function called with information about the auditor.
+ *
+ * @param cls closure
+ * @param vr response data
+ */
+static void
+version_cb (
+ void *cls,
+ const struct TALER_AUDITOR_ConfigResponse *vr)
+{
+ struct GetAuditorState *gas = cls;
+
+ gas->auditor = NULL;
+ if (MHD_HTTP_OK != vr->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status (gas->is,
+ vr->hr.http_status,
+ MHD_HTTP_OK);
+ return;
+ }
+ if ( (NULL != gas->priv_file) &&
+ (0 != GNUNET_memcmp (&gas->auditor_pub,
+ &vr->details.ok.vi.auditor_pub)) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (gas->is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (gas->is);
+}
+
+
+/**
+ * Run the "get_auditor" command.
+ *
+ * @param cls closure.
+ * @param cmd the command currently being executed.
+ * @param is the interpreter state.
+ */
+static void
+get_auditor_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct GetAuditorState *gas = cls;
+
+ (void) cmd;
+ if (NULL == gas->auditor_url)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (NULL != gas->priv_file)
+ {
+ if (GNUNET_SYSERR ==
+ GNUNET_CRYPTO_eddsa_key_from_file (gas->priv_file,
+ GNUNET_YES,
+ &gas->auditor_priv.eddsa_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&gas->auditor_priv.eddsa_priv,
+ &gas->auditor_pub.eddsa_pub);
+ }
+ gas->is = is;
+ gas->auditor
+ = TALER_AUDITOR_get_config (TALER_TESTING_interpreter_get_context (is),
+ gas->auditor_url,
+ &version_cb,
+ gas);
+ if (NULL == gas->auditor)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+get_auditor_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct GetAuditorState *gas = cls;
+
+ if (NULL != gas->auditor)
+ {
+ GNUNET_break (0);
+ TALER_AUDITOR_get_config_cancel (gas->auditor);
+ gas->auditor = NULL;
+ }
+ GNUNET_free (gas->priv_file);
+ GNUNET_free (gas->auditor_url);
+ GNUNET_free (gas);
+}
+
+
+/**
+ * Offer internal data to a "get_auditor" CMD state to other commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+get_auditor_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct GetAuditorState *gas = cls;
+ unsigned int off = (NULL == gas->priv_file) ? 2 : 0;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_auditor_priv (&gas->auditor_priv),
+ TALER_TESTING_make_trait_auditor_pub (&gas->auditor_pub),
+ TALER_TESTING_make_trait_auditor_url (gas->auditor_url),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (&traits[off],
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Get the base URL of the auditor from @a cfg.
+ *
+ * @param cfg configuration to evaluate
+ * @return base URL of the auditor according to @a cfg
+ */
+static char *
+get_auditor_base_url (
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ char *auditor_url;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "auditor",
+ "BASE_URL",
+ &auditor_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "auditor",
+ "BASE_URL");
+ return NULL;
+ }
+ return auditor_url;
+}
+
+
+/**
+ * Get the file name of the master private key file of the auditor from @a
+ * cfg.
+ *
+ * @param cfg configuration to evaluate
+ * @return base URL of the auditor according to @a cfg
+ */
+static char *
+get_auditor_priv_file (
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ char *fn;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ "auditor",
+ "AUDITOR_PRIV_FILE",
+ &fn))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "auditor",
+ "AUDITOR_PRIV_FILE");
+ return NULL;
+ }
+ return fn;
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_auditor (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ bool load_auditor_keys)
+{
+ struct GetAuditorState *gas;
+
+ gas = GNUNET_new (struct GetAuditorState);
+ gas->auditor_url = get_auditor_base_url (cfg);
+ if (load_auditor_keys)
+ gas->priv_file = get_auditor_priv_file (cfg);
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gas,
+ .label = label,
+ .run = &get_auditor_run,
+ .cleanup = &get_auditor_cleanup,
+ .traits = &get_auditor_traits,
+ .name = "auditor"
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_get_exchange.c b/src/testing/testing_api_cmd_get_exchange.c
new file mode 100644
index 000000000..69a6e82b0
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_exchange.c
@@ -0,0 +1,411 @@
+/*
+ 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
+ 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 testing/testing_api_cmd_get_exchange.c
+ * @brief Command to get an exchange handle
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "get exchange" CMD.
+ */
+struct GetExchangeState
+{
+
+ /**
+ * Master private key of the exchange.
+ */
+ struct TALER_MasterPrivateKeyP master_priv;
+
+ /**
+ * Our interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Exchange handle we produced.
+ */
+ struct TALER_EXCHANGE_GetKeysHandle *exchange;
+
+ /**
+ * Keys of the exchange.
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * URL of the exchange.
+ */
+ char *exchange_url;
+
+ /**
+ * Filename of the master private key of the exchange.
+ */
+ char *master_priv_file;
+
+ /**
+ * Label of a command to use to obtain existing
+ * keys.
+ */
+ const char *last_keys_ref;
+
+ /**
+ * Last denomination date we received when doing this request.
+ */
+ struct GNUNET_TIME_Timestamp my_denom_date;
+
+ /**
+ * Are we waiting for /keys before continuing?
+ */
+ bool wait_for_keys;
+};
+
+
+/**
+ * Function called with information about who is auditing
+ * a particular exchange and what keys the exchange is using.
+ *
+ * @param cls closure
+ * @param kr response from /keys
+ * @param[in] keys the keys of the exchange
+ */
+static void
+cert_cb (void *cls,
+ const struct TALER_EXCHANGE_KeysResponse *kr,
+ struct TALER_EXCHANGE_Keys *keys)
+{
+ struct GetExchangeState *ges = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &kr->hr;
+ struct TALER_TESTING_Interpreter *is = ges->is;
+
+ ges->exchange = NULL;
+ ges->keys = keys;
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ if (ges->wait_for_keys)
+ {
+ ges->wait_for_keys = false;
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ ges->my_denom_date = kr->details.ok.keys->last_denom_issue_date;
+ return;
+ default:
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "/keys responded with HTTP status %u\n",
+ hr->http_status);
+ if (ges->wait_for_keys)
+ {
+ ges->wait_for_keys = false;
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ return;
+ }
+}
+
+
+/**
+ * Run the "get_exchange" command.
+ *
+ * @param cls closure.
+ * @param cmd the command currently being executed.
+ * @param is the interpreter state.
+ */
+static void
+get_exchange_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct GetExchangeState *ges = cls;
+ struct TALER_EXCHANGE_Keys *xkeys = NULL;
+
+ (void) cmd;
+ if (NULL == ges->exchange_url)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (NULL != ges->last_keys_ref)
+ {
+ const struct TALER_TESTING_Command *state_cmd;
+ struct TALER_EXCHANGE_Keys *old_keys;
+ const char *exchange_url;
+ json_t *s_keys;
+
+ state_cmd
+ = TALER_TESTING_interpreter_lookup_command (is,
+ ges->last_keys_ref);
+ if (NULL == state_cmd)
+ {
+ /* Command providing serialized keys not found. */
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_keys (state_cmd,
+ &old_keys))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (NULL == old_keys)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_exchange_url (state_cmd,
+ &exchange_url))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (0 != strcmp (exchange_url,
+ ges->exchange_url))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ s_keys = TALER_EXCHANGE_keys_to_json (old_keys);
+ if (NULL == s_keys)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ xkeys = TALER_EXCHANGE_keys_from_json (s_keys);
+ if (NULL == xkeys)
+ {
+ GNUNET_break (0);
+ json_dumpf (s_keys,
+ stderr,
+ JSON_INDENT (2));
+ json_decref (s_keys);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ json_decref (s_keys);
+ }
+ if (NULL != ges->master_priv_file)
+ {
+ if (GNUNET_SYSERR ==
+ GNUNET_CRYPTO_eddsa_key_from_file (ges->master_priv_file,
+ GNUNET_YES,
+ &ges->master_priv.eddsa_priv))
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_keys_decref (xkeys);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ }
+ ges->is = is;
+ ges->exchange
+ = TALER_EXCHANGE_get_keys (TALER_TESTING_interpreter_get_context (is),
+ ges->exchange_url,
+ xkeys,
+ &cert_cb,
+ ges);
+ TALER_EXCHANGE_keys_decref (xkeys);
+ if (NULL == ges->exchange)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (! ges->wait_for_keys)
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+get_exchange_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct GetExchangeState *ges = cls;
+
+ if (NULL != ges->exchange)
+ {
+ TALER_EXCHANGE_get_keys_cancel (ges->exchange);
+ ges->exchange = NULL;
+ }
+ TALER_EXCHANGE_keys_decref (ges->keys);
+ ges->keys = NULL;
+ GNUNET_free (ges->master_priv_file);
+ GNUNET_free (ges->exchange_url);
+ GNUNET_free (ges);
+}
+
+
+/**
+ * Offer internal data to a "get_exchange" CMD state to other commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+get_exchange_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct GetExchangeState *ges = cls;
+ unsigned int off = (NULL == ges->master_priv_file) ? 1 : 0;
+
+ if (NULL != ges->keys)
+ {
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_master_priv (&ges->master_priv),
+ TALER_TESTING_make_trait_master_pub (&ges->keys->master_pub),
+ TALER_TESTING_make_trait_keys (ges->keys),
+ TALER_TESTING_make_trait_exchange_url (ges->exchange_url),
+ TALER_TESTING_make_trait_timestamp (0,
+ &ges->my_denom_date),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (&traits[off],
+ ret,
+ trait,
+ index);
+ }
+ else
+ {
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_master_priv (&ges->master_priv),
+ TALER_TESTING_make_trait_exchange_url (ges->exchange_url),
+ TALER_TESTING_make_trait_timestamp (0,
+ &ges->my_denom_date),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (&traits[off],
+ ret,
+ trait,
+ index);
+ }
+}
+
+
+/**
+ * Get the base URL of the exchange from @a cfg.
+ *
+ * @param cfg configuration to evaluate
+ * @return base URL of the exchange according to @a cfg
+ */
+static char *
+get_exchange_base_url (
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ char *exchange_url;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "BASE_URL",
+ &exchange_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ return NULL;
+ }
+ return exchange_url;
+}
+
+
+/**
+ * Get the file name of the master private key file of the exchange from @a
+ * cfg.
+ *
+ * @param cfg configuration to evaluate
+ * @return base URL of the exchange according to @a cfg
+ */
+static char *
+get_exchange_master_priv_file (
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ char *fn;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ "exchange-offline",
+ "MASTER_PRIV_FILE",
+ &fn))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange-offline",
+ "MASTER_PRIV_FILE");
+ return NULL;
+ }
+ return fn;
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_exchange (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *last_keys_ref,
+ bool wait_for_keys,
+ bool load_private_key)
+{
+ struct GetExchangeState *ges;
+
+ ges = GNUNET_new (struct GetExchangeState);
+ ges->exchange_url = get_exchange_base_url (cfg);
+ ges->last_keys_ref = last_keys_ref;
+ if (load_private_key)
+ ges->master_priv_file = get_exchange_master_priv_file (cfg);
+ ges->wait_for_keys = wait_for_keys;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ges,
+ .label = label,
+ .run = &get_exchange_run,
+ .cleanup = &get_exchange_cleanup,
+ .traits = &get_exchange_traits,
+ .name = "exchange"
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_insert_deposit.c b/src/testing/testing_api_cmd_insert_deposit.c
index 356528009..03e704c72 100644
--- a/src/testing/testing_api_cmd_insert_deposit.c
+++ b/src/testing/testing_api_cmd_insert_deposit.c
@@ -29,6 +29,7 @@
#include "taler_signatures.h"
#include "taler_testing_lib.h"
#include "taler_exchangedb_plugin.h"
+#include "taler_exchangedb_lib.h"
/**
@@ -37,9 +38,9 @@
struct InsertDepositState
{
/**
- * Configuration file used by the command.
+ * Database connection we use.
*/
- const struct TALER_TESTING_DatabaseConnection *dbc;
+ struct TALER_EXCHANGEDB_Plugin *plugin;
/**
* Human-readable name of the shop.
@@ -58,6 +59,11 @@ struct InsertDepositState
struct GNUNET_TIME_Relative wire_deadline;
/**
+ * When did the exchange receive the deposit?
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
* Amount to deposit, inclusive of deposit fee.
*/
const char *amount_with_fee;
@@ -66,6 +72,11 @@ struct InsertDepositState
* Deposit fee.
*/
const char *deposit_fee;
+
+ /**
+ * Do we used a cached @e plugin?
+ */
+ bool cached;
};
/**
@@ -74,25 +85,37 @@ struct InsertDepositState
* @param[out] issue information to initialize with "valid" data
*/
static void
-fake_issue (struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue)
+fake_issue (struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
{
- memset (issue, 0, sizeof (struct
- TALER_EXCHANGEDB_DenominationKeyInformationP));
+ struct GNUNET_TIME_Timestamp now;
+
+ memset (issue,
+ 0,
+ sizeof (*issue));
+ now = GNUNET_TIME_timestamp_get ();
+ issue->start
+ = now;
+ issue->expire_withdraw
+ = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MINUTES);
+ issue->expire_deposit
+ = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS);
+ issue->expire_legal
+ = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_DAYS);
GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount_nbo ("EUR:1",
- &issue->properties.value));
+ TALER_string_to_amount ("EUR:1",
+ &issue->value));
GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount_nbo ("EUR:0.1",
- &issue->properties.fee_withdraw));
+ TALER_string_to_amount ("EUR:0.1",
+ &issue->fees.withdraw));
GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount_nbo ("EUR:0.1",
- &issue->properties.fee_deposit));
+ TALER_string_to_amount ("EUR:0.1",
+ &issue->fees.deposit));
GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount_nbo ("EUR:0.1",
- &issue->properties.fee_refresh));
+ TALER_string_to_amount ("EUR:0.1",
+ &issue->fees.refresh));
GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount_nbo ("EUR:0.1",
- &issue->properties.fee_refund));
+ TALER_string_to_amount ("EUR:0.1",
+ &issue->fees.refund));
}
@@ -109,34 +132,50 @@ insert_deposit_run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct InsertDepositState *ids = cls;
- struct TALER_EXCHANGEDB_Deposit deposit;
+ struct TALER_EXCHANGEDB_CoinDepositInformation deposit;
+ struct TALER_EXCHANGEDB_BatchDeposit bd;
struct TALER_MerchantPrivateKeyP merchant_priv;
- struct TALER_EXCHANGEDB_DenominationKeyInformationP issue;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
struct TALER_DenominationPublicKey dpk;
- struct GNUNET_CRYPTO_RsaPrivateKey *denom_priv;
- struct GNUNET_HashCode hc;
+ struct TALER_DenominationPrivateKey denom_priv;
+ char *receiver_wire_account;
- // prepare and store issue first.
+ (void) cmd;
+ if (NULL == ids->plugin)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ids->plugin->preflight (ids->plugin->cls))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
fake_issue (&issue);
- denom_priv = GNUNET_CRYPTO_rsa_private_key_create (1024);
- dpk.rsa_public_key = GNUNET_CRYPTO_rsa_private_key_get_public (denom_priv);
- GNUNET_CRYPTO_rsa_public_key_hash (dpk.rsa_public_key,
- &issue.properties.denom_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_priv_create (&denom_priv,
+ &dpk,
+ GNUNET_CRYPTO_BSA_RSA,
+ 1024));
+ TALER_denom_pub_hash (&dpk,
+ &issue.denom_hash);
if ( (GNUNET_OK !=
- ids->dbc->plugin->start (ids->dbc->plugin->cls,
- ids->dbc->session,
- "talertestinglib: denomination insertion")) ||
+ ids->plugin->start (ids->plugin->cls,
+ "talertestinglib: denomination insertion")) ||
(GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- ids->dbc->plugin->insert_denomination_info (ids->dbc->plugin->cls,
- ids->dbc->session,
- &dpk,
- &issue)) ||
+ ids->plugin->insert_denomination_info (ids->plugin->cls,
+ &dpk,
+ &issue)) ||
(GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- ids->dbc->plugin->commit (ids->dbc->plugin->cls,
- ids->dbc->session)) )
+ ids->plugin->commit (ids->plugin->cls)) )
{
TALER_TESTING_interpreter_fail (is);
+ TALER_denom_pub_free (&dpk);
+ TALER_denom_priv_free (&denom_priv);
return;
}
@@ -144,86 +183,132 @@ insert_deposit_run (void *cls,
memset (&deposit,
0,
sizeof (deposit));
-
- GNUNET_CRYPTO_kdf (&merchant_priv,
- sizeof (struct TALER_MerchantPrivateKeyP),
- "merchant-priv",
- strlen ("merchant-priv"),
- ids->merchant_name,
- strlen (ids->merchant_name),
- NULL,
- 0);
+ memset (&bd,
+ 0,
+ sizeof (bd));
+ bd.cdis = &deposit;
+ bd.num_cdis = 1;
+
+ GNUNET_assert (
+ GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (&merchant_priv,
+ sizeof (struct TALER_MerchantPrivateKeyP),
+ "merchant-priv",
+ strlen ("merchant-priv"),
+ ids->merchant_name,
+ strlen (ids->merchant_name),
+ NULL,
+ 0));
GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv.eddsa_priv,
- &deposit.merchant_pub.eddsa_pub);
+ &bd.merchant_pub.eddsa_pub);
GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK,
- &deposit.h_contract_terms);
- if ( (GNUNET_OK !=
- TALER_string_to_amount (ids->amount_with_fee,
- &deposit.amount_with_fee)) ||
- (GNUNET_OK !=
- TALER_string_to_amount (ids->deposit_fee,
- &deposit.deposit_fee)) )
+ &bd.h_contract_terms.hash);
+ if (GNUNET_OK !=
+ TALER_string_to_amount (ids->amount_with_fee,
+ &deposit.amount_with_fee))
{
TALER_TESTING_interpreter_fail (is);
+ TALER_denom_pub_free (&dpk);
+ TALER_denom_priv_free (&denom_priv);
return;
}
- GNUNET_CRYPTO_rsa_public_key_hash (dpk.rsa_public_key,
- &deposit.coin.denom_pub_hash);
-
- GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK,
- &hc);
- deposit.coin.denom_sig.rsa_signature = GNUNET_CRYPTO_rsa_sign_fdh (denom_priv,
- &hc);
+ TALER_denom_pub_hash (&dpk,
+ &deposit.coin.denom_pub_hash);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &deposit.coin.coin_pub,
+ sizeof (deposit.coin.coin_pub));
{
- char *str;
-
- GNUNET_asprintf (&str,
- "payto://x-taler-bank/localhost/%s",
- ids->merchant_account);
- deposit.receiver_wire_account
- = json_pack ("{s:s, s:s}",
- "salt", "this-is-a-salt-value",
- "payto_uri", str);
- GNUNET_free (str);
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_PlanchetDetail pd;
+ struct TALER_BlindedDenominationSignature bds;
+ struct TALER_PlanchetMasterSecretP ps;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ const struct TALER_ExchangeWithdrawValues *alg_values;
+
+ alg_values = TALER_denom_ewv_rsa_singleton ();
+ TALER_planchet_blinding_secret_create (&ps,
+ alg_values,
+ &bks);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_blind (&dpk,
+ &bks,
+ NULL, /* no age restriction active */
+ NULL, /* no nonce needed */
+ &deposit.coin.coin_pub,
+ alg_values,
+ &c_hash,
+ &pd.blinded_planchet));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sign_blinded (&bds,
+ &denom_priv,
+ false,
+ &pd.blinded_planchet));
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&deposit.coin.denom_sig,
+ &bds,
+ &bks,
+ &c_hash,
+ alg_values,
+ &dpk));
+ TALER_blinded_denom_sig_free (&bds);
}
-
- GNUNET_assert (GNUNET_OK ==
- TALER_JSON_merchant_wire_signature_hash (
- deposit.receiver_wire_account,
- &deposit.h_wire));
- deposit.timestamp = GNUNET_TIME_absolute_get ();
- GNUNET_TIME_round_abs (&deposit.timestamp);
- deposit.wire_deadline = GNUNET_TIME_relative_to_absolute (
+ GNUNET_asprintf (&receiver_wire_account,
+ "payto://x-taler-bank/localhost/%s?receiver-name=%s",
+ ids->merchant_account,
+ ids->merchant_account);
+ bd.receiver_wire_account = receiver_wire_account;
+ TALER_payto_hash (bd.receiver_wire_account,
+ &bd.wire_target_h_payto);
+ memset (&bd.wire_salt,
+ 46,
+ sizeof (bd.wire_salt));
+ bd.wallet_timestamp = GNUNET_TIME_timestamp_get ();
+ bd.wire_deadline = GNUNET_TIME_relative_to_timestamp (
ids->wire_deadline);
- GNUNET_TIME_round_abs (&deposit.wire_deadline);
-
/* finally, actually perform the DB operation */
- if ( (GNUNET_OK !=
- ids->dbc->plugin->start (ids->dbc->plugin->cls,
- ids->dbc->session,
- "libtalertesting: insert deposit")) ||
- (0 >
- ids->dbc->plugin->ensure_coin_known (ids->dbc->plugin->cls,
- ids->dbc->session,
- &deposit.coin)) ||
- (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- ids->dbc->plugin->insert_deposit (ids->dbc->plugin->cls,
- ids->dbc->session,
- &deposit)) ||
- (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- ids->dbc->plugin->commit (ids->dbc->plugin->cls,
- ids->dbc->session)) )
{
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
+ uint64_t known_coin_id;
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+ bool balance_ok;
+ uint32_t bad_index;
+ bool ctr_conflict;
+
+ if ( (GNUNET_OK !=
+ ids->plugin->start (ids->plugin->cls,
+ "libtalertesting: insert deposit")) ||
+ (0 >
+ ids->plugin->ensure_coin_known (ids->plugin->cls,
+ &deposit.coin,
+ &known_coin_id,
+ &dph,
+ &agh)) ||
+ (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ ids->plugin->do_deposit (ids->plugin->cls,
+ &bd,
+ &ids->exchange_timestamp,
+ &balance_ok,
+ &bad_index,
+ &ctr_conflict)) ||
+ (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ ids->plugin->commit (ids->plugin->cls)) )
+ {
+ GNUNET_break (0);
+ ids->plugin->rollback (ids->plugin->cls);
+ GNUNET_free (receiver_wire_account);
+ TALER_denom_pub_free (&dpk);
+ TALER_denom_priv_free (&denom_priv);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
}
- GNUNET_CRYPTO_rsa_signature_free (deposit.coin.denom_sig.rsa_signature);
- GNUNET_CRYPTO_rsa_public_key_free (dpk.rsa_public_key);
- GNUNET_CRYPTO_rsa_private_key_free (denom_priv);
- json_decref (deposit.receiver_wire_account);
-
+ TALER_denom_sig_free (&deposit.coin.denom_sig);
+ TALER_denom_pub_free (&dpk);
+ TALER_denom_priv_free (&denom_priv);
+ GNUNET_free (receiver_wire_account);
TALER_TESTING_interpreter_next (is);
}
@@ -241,62 +326,49 @@ insert_deposit_cleanup (void *cls,
{
struct InsertDepositState *ids = cls;
+ (void) cmd;
+ if ( (NULL != ids->plugin) &&
+ (! ids->cached) )
+ {
+ // FIXME: historically, we also did:
+ // ids->plugin->drop_tables (ids->plugin->cls);
+ TALER_EXCHANGEDB_plugin_unload (ids->plugin);
+ ids->plugin = NULL;
+ }
GNUNET_free (ids);
}
-/**
- * Offer "insert-deposit" CMD internal data to other commands.
- *
- * @param cls closure.
- * @param[out] ret result
- * @param trait name of the trait.
- * @param index index number of the object to offer.
- * @return #GNUNET_OK on success.
- */
-static int
-insert_deposit_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- (void) cls;
- (void) ret;
- (void) trait;
- (void) index;
- return GNUNET_NO;
-}
-
-
-/**
- * Make the "insert-deposit" CMD.
- *
- * @param label command label.
- * @param dbc collects database plugin and session handles.
- * @param merchant_name Human-readable name of the merchant.
- * @param merchant_account merchant's account name (NOT a payto:// URI)
- * @param wire_deadline point in time where the aggregator should have
- * wired money to the merchant.
- * @param amount_with_fee amount to deposit (inclusive of deposit fee)
- * @param deposit_fee deposit fee
- * @return the command.
- */
struct TALER_TESTING_Command
-TALER_TESTING_cmd_insert_deposit (const char *label,
- const struct
- TALER_TESTING_DatabaseConnection *dbc,
- const char *merchant_name,
- const char *merchant_account,
- struct GNUNET_TIME_Relative wire_deadline,
- const char *amount_with_fee,
- const char *deposit_fee)
+TALER_TESTING_cmd_insert_deposit (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *db_cfg,
+ const char *merchant_name,
+ const char *merchant_account,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ struct GNUNET_TIME_Relative wire_deadline,
+ const char *amount_with_fee,
+ const char *deposit_fee)
{
+ static struct TALER_EXCHANGEDB_Plugin *pluginc;
+ static const struct GNUNET_CONFIGURATION_Handle *db_cfgc;
struct InsertDepositState *ids;
ids = GNUNET_new (struct InsertDepositState);
- ids->dbc = dbc;
+ if (db_cfgc == db_cfg)
+ {
+ ids->plugin = pluginc;
+ ids->cached = true;
+ }
+ else
+ {
+ ids->plugin = TALER_EXCHANGEDB_plugin_load (db_cfg);
+ pluginc = ids->plugin;
+ db_cfgc = db_cfg;
+ }
ids->merchant_name = merchant_name;
ids->merchant_account = merchant_account;
+ ids->exchange_timestamp = exchange_timestamp;
ids->wire_deadline = wire_deadline;
ids->amount_with_fee = amount_with_fee;
ids->deposit_fee = deposit_fee;
@@ -306,8 +378,7 @@ TALER_TESTING_cmd_insert_deposit (const char *label,
.cls = ids,
.label = label,
.run = &insert_deposit_run,
- .cleanup = &insert_deposit_cleanup,
- .traits = &insert_deposit_traits
+ .cleanup = &insert_deposit_cleanup
};
return cmd;
diff --git a/src/testing/testing_api_cmd_kyc_check_get.c b/src/testing/testing_api_cmd_kyc_check_get.c
new file mode 100644
index 000000000..25c7e98b8
--- /dev/null
+++ b/src/testing/testing_api_cmd_kyc_check_get.c
@@ -0,0 +1,243 @@
+/*
+ This file is part of TALER
+ 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 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 testing/testing_api_cmd_kyc_check_get.c
+ * @brief Implement the testing CMDs for the /kyc_check/ GET operations.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+/**
+ * State for a "track transaction" CMD.
+ */
+struct KycCheckGetState
+{
+
+ /**
+ * Command to get a reserve private key from.
+ */
+ const char *payment_target_reference;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Set to the KYC URL *if* the exchange replied with
+ * a request for KYC (#MHD_HTTP_ACCEPTED).
+ */
+ char *kyc_url;
+
+ /**
+ * Handle to the "track transaction" pending operation.
+ */
+ struct TALER_EXCHANGE_KycCheckHandle *kwh;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * Handle response to the command.
+ *
+ * @param cls closure.
+ * @param ks GET KYC status response details
+ */
+static void
+check_kyc_cb (void *cls,
+ const struct TALER_EXCHANGE_KycStatus *ks)
+{
+ struct KycCheckGetState *kcg = cls;
+ struct TALER_TESTING_Interpreter *is = kcg->is;
+
+ kcg->kwh = NULL;
+ if (kcg->expected_response_code != ks->http_status)
+ {
+ TALER_TESTING_unexpected_status (is,
+ ks->http_status,
+ kcg->expected_response_code);
+ return;
+ }
+ switch (ks->http_status)
+ {
+ case MHD_HTTP_OK:
+ break;
+ case MHD_HTTP_ACCEPTED:
+ kcg->kyc_url = GNUNET_strdup (ks->details.accepted.kyc_url);
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ default:
+ GNUNET_break (0);
+ break;
+ }
+ TALER_TESTING_interpreter_next (kcg->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+check_kyc_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct KycCheckGetState *kcg = cls;
+ const struct TALER_TESTING_Command *res_cmd;
+ const uint64_t *requirement_row;
+ const struct TALER_PaytoHashP *h_payto;
+
+ (void) cmd;
+ kcg->is = is;
+ res_cmd = TALER_TESTING_interpreter_lookup_command (
+ kcg->is,
+ kcg->payment_target_reference);
+ if (NULL == res_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (kcg->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_legi_requirement_row (res_cmd,
+ &requirement_row))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (kcg->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_h_payto (res_cmd,
+ &h_payto))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (kcg->is);
+ return;
+ }
+ if (0 == *requirement_row)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (kcg->is);
+ return;
+ }
+ kcg->kwh = TALER_EXCHANGE_kyc_check (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ *requirement_row,
+ h_payto,
+ TALER_KYCLOGIC_KYC_UT_INDIVIDUAL,
+ GNUNET_TIME_UNIT_SECONDS,
+ &check_kyc_cb,
+ kcg);
+ GNUNET_assert (NULL != kcg->kwh);
+}
+
+
+/**
+ * Cleanup the state from a "track transaction" CMD, and possibly
+ * cancel a operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+check_kyc_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct KycCheckGetState *kcg = cls;
+
+ if (NULL != kcg->kwh)
+ {
+ TALER_TESTING_command_incomplete (kcg->is,
+ cmd->label);
+ TALER_EXCHANGE_kyc_check_cancel (kcg->kwh);
+ kcg->kwh = NULL;
+ }
+ GNUNET_free (kcg->kyc_url);
+ GNUNET_free (kcg);
+}
+
+
+/**
+ * Offer internal data from a "check KYC" CMD.
+ *
+ * @param cls closure.
+ * @param[out] ret result (could be anything).
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+check_kyc_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct KycCheckGetState *kcg = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_kyc_url (kcg->kyc_url),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_kyc_get (const char *label,
+ const char *payment_target_reference,
+ unsigned int expected_response_code)
+{
+ struct KycCheckGetState *kcg;
+
+ kcg = GNUNET_new (struct KycCheckGetState);
+ kcg->payment_target_reference = payment_target_reference;
+ kcg->expected_response_code = expected_response_code;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = kcg,
+ .label = label,
+ .run = &check_kyc_run,
+ .cleanup = &check_kyc_cleanup,
+ .traits = &check_kyc_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_kyc_check_get.c */
diff --git a/src/testing/testing_api_cmd_kyc_proof.c b/src/testing/testing_api_cmd_kyc_proof.c
new file mode 100644
index 000000000..b079fffce
--- /dev/null
+++ b/src/testing/testing_api_cmd_kyc_proof.c
@@ -0,0 +1,259 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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 testing/testing_api_cmd_kyc_proof.c
+ * @brief Implement the testing CMDs for the /kyc-proof/ operation.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+/**
+ * State for a "track transaction" CMD.
+ */
+struct KycProofGetState
+{
+
+ /**
+ * Command to get a reserve private key from.
+ */
+ const char *payment_target_reference;
+
+ /**
+ * Code to pass.
+ */
+ const char *code;
+
+ /**
+ * Logic section name to pass to `/kyc-proof/` handler.
+ */
+ const char *logic;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Set to the KYC REDIRECT *if* the exchange replied with
+ * success (#MHD_HTTP_OK).
+ */
+ char *redirect_url;
+
+ /**
+ * Handle to the "track transaction" pending operation.
+ */
+ struct TALER_EXCHANGE_KycProofHandle *kph;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * Handle response to the command.
+ *
+ * @param cls closure.
+ * @param kpr KYC proof response details
+ */
+static void
+proof_kyc_cb (void *cls,
+ const struct TALER_EXCHANGE_KycProofResponse *kpr)
+{
+ struct KycProofGetState *kcg = cls;
+ struct TALER_TESTING_Interpreter *is = kcg->is;
+
+ kcg->kph = NULL;
+ if (kcg->expected_response_code != kpr->http_status)
+ {
+ TALER_TESTING_unexpected_status (is,
+ kpr->http_status,
+ kcg->expected_response_code);
+ return;
+ }
+ switch (kpr->http_status)
+ {
+ case MHD_HTTP_SEE_OTHER:
+ kcg->redirect_url = GNUNET_strdup (kpr->details.found.redirect_url);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ break;
+ case MHD_HTTP_BAD_GATEWAY:
+ break;
+ default:
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to /kyc-proof\n",
+ kpr->http_status);
+ break;
+ }
+ TALER_TESTING_interpreter_next (kcg->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+proof_kyc_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct KycProofGetState *kps = cls;
+ const struct TALER_TESTING_Command *res_cmd;
+ const struct TALER_PaytoHashP *h_payto;
+ char *uargs;
+ const char *exchange_url;
+
+ (void) cmd;
+ kps->is = is;
+ exchange_url = TALER_TESTING_get_exchange_url (is);
+ if (NULL == exchange_url)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ res_cmd = TALER_TESTING_interpreter_lookup_command (
+ kps->is,
+ kps->payment_target_reference);
+ if (NULL == res_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (kps->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_h_payto (res_cmd,
+ &h_payto))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (kps->is);
+ return;
+ }
+ if (NULL == kps->code)
+ uargs = NULL;
+ else
+ GNUNET_asprintf (&uargs,
+ "&code=%s",
+ kps->code);
+ kps->kph = TALER_EXCHANGE_kyc_proof (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ h_payto,
+ kps->logic,
+ uargs,
+ &proof_kyc_cb,
+ kps);
+ GNUNET_free (uargs);
+ GNUNET_assert (NULL != kps->kph);
+}
+
+
+/**
+ * Cleanup the state from a "track transaction" CMD, and possibly
+ * cancel a operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+proof_kyc_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct KycProofGetState *kps = cls;
+
+ if (NULL != kps->kph)
+ {
+ TALER_TESTING_command_incomplete (kps->is,
+ cmd->label);
+ TALER_EXCHANGE_kyc_proof_cancel (kps->kph);
+ kps->kph = NULL;
+ }
+ GNUNET_free (kps->redirect_url);
+ GNUNET_free (kps);
+}
+
+
+/**
+ * Offer internal data from a "proof KYC" CMD.
+ *
+ * @param cls closure.
+ * @param[out] ret result (could be anything).
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+proof_kyc_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct KycProofGetState *kps = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_web_url (kps->redirect_url),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_proof_kyc_oauth2 (
+ const char *label,
+ const char *payment_target_reference,
+ const char *logic_section,
+ const char *code,
+ unsigned int expected_response_code)
+{
+ struct KycProofGetState *kps;
+
+ kps = GNUNET_new (struct KycProofGetState);
+ kps->code = code;
+ kps->logic = logic_section;
+ kps->payment_target_reference = payment_target_reference;
+ kps->expected_response_code = expected_response_code;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = kps,
+ .label = label,
+ .run = &proof_kyc_run,
+ .cleanup = &proof_kyc_cleanup,
+ .traits = &proof_kyc_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_kyc_proof.c */
diff --git a/src/testing/testing_api_cmd_kyc_wallet_get.c b/src/testing/testing_api_cmd_kyc_wallet_get.c
new file mode 100644
index 000000000..ffb143ffb
--- /dev/null
+++ b/src/testing/testing_api_cmd_kyc_wallet_get.c
@@ -0,0 +1,290 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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 testing/testing_api_cmd_kyc_wallet_get.c
+ * @brief Implement the testing CMDs for the /kyc_wallet/ GET operations.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+/**
+ * State for a "/kyc-wallet" GET CMD.
+ */
+struct KycWalletGetState
+{
+
+ /**
+ * Private key of the reserve (account).
+ */
+ struct TALER_ReservePrivateKeyP reserve_priv;
+
+ /**
+ * Public key of the reserve (account).
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Payto URI of the reserve of the wallet.
+ */
+ char *reserve_payto_uri;
+
+ /**
+ * Our command.
+ */
+ const struct TALER_TESTING_Command *cmd;
+
+ /**
+ * Command to get a reserve private key from.
+ */
+ const char *reserve_reference;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Set to the KYC requirement payto hash *if* the exchange replied with a
+ * request for KYC (#MHD_HTTP_OK).
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Set to the KYC requirement row *if* the exchange replied with
+ * a request for KYC (#MHD_HTTP_OK).
+ */
+ uint64_t requirement_row;
+
+ /**
+ * Handle to the "track transaction" pending operation.
+ */
+ struct TALER_EXCHANGE_KycWalletHandle *kwh;
+
+ /**
+ * Balance to pass to the exchange.
+ */
+ struct TALER_Amount balance;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * Handle response to the command.
+ *
+ * @param cls closure.
+ * @param wkr GET deposit response details
+ */
+static void
+wallet_kyc_cb (void *cls,
+ const struct TALER_EXCHANGE_WalletKycResponse *wkr)
+{
+ struct KycWalletGetState *kwg = cls;
+ struct TALER_TESTING_Interpreter *is = kwg->is;
+
+ kwg->kwh = NULL;
+ if (kwg->expected_response_code != wkr->http_status)
+ {
+ TALER_TESTING_unexpected_status (is,
+ wkr->http_status,
+ kwg->expected_response_code);
+ return;
+ }
+ switch (wkr->http_status)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ kwg->requirement_row
+ = wkr->details.unavailable_for_legal_reasons.requirement_row;
+ kwg->h_payto
+ = wkr->details.unavailable_for_legal_reasons.h_payto;
+ break;
+ default:
+ GNUNET_break (0);
+ break;
+ }
+ TALER_TESTING_interpreter_next (kwg->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+wallet_kyc_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct KycWalletGetState *kwg = cls;
+ const char *exchange_url;
+
+ kwg->cmd = cmd;
+ kwg->is = is;
+ exchange_url = TALER_TESTING_get_exchange_url (is);
+ if (NULL == exchange_url)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ if (NULL != kwg->reserve_reference)
+ {
+ const struct TALER_TESTING_Command *res_cmd;
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ res_cmd = TALER_TESTING_interpreter_lookup_command (kwg->is,
+ kwg->reserve_reference);
+ if (NULL == res_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (kwg->is);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_priv (res_cmd,
+ &reserve_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (kwg->is);
+ return;
+ }
+ kwg->reserve_priv = *reserve_priv;
+ }
+ else
+ {
+ GNUNET_CRYPTO_eddsa_key_create (&kwg->reserve_priv.eddsa_priv);
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&kwg->reserve_priv.eddsa_priv,
+ &kwg->reserve_pub.eddsa_pub);
+ kwg->reserve_payto_uri
+ = TALER_reserve_make_payto (exchange_url,
+ &kwg->reserve_pub);
+ kwg->kwh = TALER_EXCHANGE_kyc_wallet (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ &kwg->reserve_priv,
+ &kwg->balance,
+ &wallet_kyc_cb,
+ kwg);
+ GNUNET_assert (NULL != kwg->kwh);
+}
+
+
+/**
+ * Cleanup the state from a "track transaction" CMD, and possibly
+ * cancel a operation thereof.
+ *
+ * @param cls closure with our `struct KycWalletGetState`
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+wallet_kyc_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct KycWalletGetState *kwg = cls;
+
+ if (NULL != kwg->kwh)
+ {
+ TALER_TESTING_command_incomplete (kwg->is,
+ cmd->label);
+ TALER_EXCHANGE_kyc_wallet_cancel (kwg->kwh);
+ kwg->kwh = NULL;
+ }
+ GNUNET_free (kwg->reserve_payto_uri);
+ GNUNET_free (kwg);
+}
+
+
+/**
+ * Offer internal data from a "wallet KYC" CMD.
+ *
+ * @param cls closure.
+ * @param[out] ret result (could be anything).
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+wallet_kyc_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct KycWalletGetState *kwg = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_reserve_priv (&kwg->reserve_priv),
+ TALER_TESTING_make_trait_reserve_pub (&kwg->reserve_pub),
+ TALER_TESTING_make_trait_legi_requirement_row (&kwg->requirement_row),
+ TALER_TESTING_make_trait_h_payto (&kwg->h_payto),
+ TALER_TESTING_make_trait_payto_uri (kwg->reserve_payto_uri),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wallet_kyc_get (const char *label,
+ const char *reserve_reference,
+ const char *threshold_balance,
+ unsigned int expected_response_code)
+{
+ struct KycWalletGetState *kwg;
+
+ kwg = GNUNET_new (struct KycWalletGetState);
+ kwg->reserve_reference = reserve_reference;
+ kwg->expected_response_code = expected_response_code;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (threshold_balance,
+ &kwg->balance));
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = kwg,
+ .label = label,
+ .run = &wallet_kyc_run,
+ .cleanup = &wallet_kyc_cleanup,
+ .traits = &wallet_kyc_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_kyc_wallet_get.c */
diff --git a/src/testing/testing_api_cmd_oauth.c b/src/testing/testing_api_cmd_oauth.c
new file mode 100644
index 000000000..80d38e4c8
--- /dev/null
+++ b/src/testing/testing_api_cmd_oauth.c
@@ -0,0 +1,412 @@
+/*
+ This file is part of TALER
+ 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 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 testing/testing_api_cmd_oauth.c
+ * @brief Implement a CMD to run an OAuth service for faking the legitimation service
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_mhd_lib.h"
+
+/**
+ * State for the oauth CMD.
+ */
+struct OAuthState
+{
+
+ /**
+ * Handle to the "oauth" service.
+ */
+ struct MHD_Daemon *mhd;
+
+ /**
+ * Birthdate that the oauth server should return in a response, may be NULL
+ */
+ const char *birthdate;
+
+ /**
+ * Port to listen on.
+ */
+ uint16_t port;
+};
+
+
+struct RequestCtx
+{
+ struct MHD_PostProcessor *pp;
+ char *code;
+ char *client_id;
+ char *redirect_uri;
+ char *client_secret;
+};
+
+
+static void
+append (char **target,
+ const char *data,
+ size_t size)
+{
+ char *tmp;
+
+ if (NULL == *target)
+ {
+ *target = GNUNET_strndup (data,
+ size);
+ return;
+ }
+ GNUNET_asprintf (&tmp,
+ "%s%.*s",
+ *target,
+ (int) size,
+ data);
+ GNUNET_free (*target);
+ *target = tmp;
+}
+
+
+static MHD_RESULT
+handle_post (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *filename,
+ const char *content_type,
+ const char *transfer_encoding,
+ const char *data,
+ uint64_t off,
+ size_t size)
+{
+ struct RequestCtx *rc = cls;
+
+ (void) kind;
+ (void) filename;
+ (void) content_type;
+ (void) transfer_encoding;
+ (void) off;
+ if (0 == strcmp (key,
+ "code"))
+ append (&rc->code,
+ data,
+ size);
+ if (0 == strcmp (key,
+ "client_id"))
+ append (&rc->client_id,
+ data,
+ size);
+ if (0 == strcmp (key,
+ "redirect_uri"))
+ append (&rc->redirect_uri,
+ data,
+ size);
+ if (0 == strcmp (key,
+ "client_secret"))
+ append (&rc->client_secret,
+ data,
+ size);
+ return MHD_YES;
+}
+
+
+/**
+ * A client has requested the given url using the given method
+ * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
+ * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
+ * must call MHD callbacks to provide content to give back to the
+ * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
+ * #MHD_HTTP_NOT_FOUND, etc.).
+ *
+ * @param cls argument given together with the function
+ * pointer when the handler was registered with MHD
+ * @param connection the connection being handled
+ * @param url the requested url
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ * #MHD_HTTP_METHOD_PUT, etc.)
+ * @param version the HTTP version string (i.e.
+ * MHD_HTTP_VERSION_1_1)
+ * @param upload_data the data being uploaded (excluding HEADERS,
+ * for a POST that fits into memory and that is encoded
+ * with a supported encoding, the POST data will NOT be
+ * given in upload_data and is instead available as
+ * part of MHD_get_connection_values(); very large POST
+ * data *will* be made available incrementally in
+ * @a upload_data)
+ * @param[in,out] upload_data_size set initially to the size of the
+ * @a upload_data provided; the method must update this
+ * value to the number of bytes NOT processed;
+ * @param[in,out] con_cls pointer that the callback can set to some
+ * address and that will be preserved by MHD for future
+ * calls for this request; since the access handler may
+ * be called many times (i.e., for a PUT/POST operation
+ * with plenty of upload data) this allows the application
+ * to easily associate some request-specific state.
+ * If necessary, this state can be cleaned up in the
+ * global MHD_RequestCompletedCallback (which
+ * can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
+ * Initially, `*con_cls` will be NULL.
+ * @return #MHD_YES if the connection was handled successfully,
+ * #MHD_NO if the socket must be closed due to a serious
+ * error while handling the request
+ */
+static MHD_RESULT
+handler_cb (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ struct RequestCtx *rc = *con_cls;
+ struct OAuthState *oas = cls;
+ unsigned int hc;
+ json_t *body;
+
+ (void) version;
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_GET))
+ {
+ json_t *data =
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("id",
+ "XXXID12345678"),
+ GNUNET_JSON_pack_string ("first_name",
+ "Bob"),
+ GNUNET_JSON_pack_string ("last_name",
+ "Builder"));
+
+ if (NULL != oas->birthdate)
+ GNUNET_assert (0 ==
+ json_object_set_new (data,
+ "birthdate",
+ json_string_nocheck (
+ oas->birthdate)));
+
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string (
+ "status",
+ "success"),
+ GNUNET_JSON_pack_object_steal (
+ "data", data));
+ return TALER_MHD_reply_json_steal (connection,
+ body,
+ MHD_HTTP_OK);
+ }
+ if (0 != strcasecmp (method,
+ MHD_HTTP_METHOD_POST))
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (NULL == rc)
+ {
+ rc = GNUNET_new (struct RequestCtx);
+ *con_cls = rc;
+ rc->pp = MHD_create_post_processor (connection,
+ 4092,
+ &handle_post,
+ rc);
+ return MHD_YES;
+ }
+ if (0 != *upload_data_size)
+ {
+ MHD_RESULT ret;
+
+ ret = MHD_post_process (rc->pp,
+ upload_data,
+ *upload_data_size);
+ *upload_data_size = 0;
+ return ret;
+ }
+
+
+ /* NOTE: In the future, we MAY want to distinguish between
+ the different URLs and possibly return more information.
+ For now, just do the minimum: implement the main handler
+ that checks the code. */
+ if ( (NULL == rc->code) ||
+ (NULL == rc->client_id) ||
+ (NULL == rc->redirect_uri) ||
+ (NULL == rc->client_secret) )
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Bad request to Oauth faker: `%s' with %s/%s/%s/%s\n",
+ url,
+ rc->code,
+ rc->client_id,
+ rc->redirect_uri,
+ rc->client_secret);
+ return MHD_NO;
+ }
+ if (0 != strcmp (rc->client_id,
+ "taler-exchange"))
+ {
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("error",
+ "unknown_client"),
+ GNUNET_JSON_pack_string ("error_description",
+ "only 'taler-exchange' is allowed"));
+ hc = MHD_HTTP_NOT_FOUND;
+ }
+ else if (0 != strcmp (rc->client_secret,
+ "exchange-secret"))
+ {
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("error",
+ "invalid_client_secret"),
+ GNUNET_JSON_pack_string ("error_description",
+ "only 'exchange-secret' is valid"));
+ hc = MHD_HTTP_FORBIDDEN;
+ }
+ else
+ {
+ if (0 != strcmp (rc->code,
+ "pass"))
+ {
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("error",
+ "invalid_grant"),
+ GNUNET_JSON_pack_string ("error_description",
+ "only 'pass' shall pass"));
+ hc = MHD_HTTP_FORBIDDEN;
+ }
+ else
+ {
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("access_token",
+ "good"),
+ GNUNET_JSON_pack_string ("token_type",
+ "bearer"),
+ GNUNET_JSON_pack_uint64 ("expires_in",
+ 3600),
+ GNUNET_JSON_pack_string ("refresh_token",
+ "better"));
+ hc = MHD_HTTP_OK;
+ }
+ }
+ return TALER_MHD_reply_json_steal (connection,
+ body,
+ hc);
+}
+
+
+static void
+cleanup (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct RequestCtx *rc = *con_cls;
+
+ (void) cls;
+ (void) connection;
+ (void) toe;
+ if (NULL == rc)
+ return;
+ MHD_destroy_post_processor (rc->pp);
+ GNUNET_free (rc->code);
+ GNUNET_free (rc->client_id);
+ GNUNET_free (rc->redirect_uri);
+ GNUNET_free (rc->client_secret);
+ GNUNET_free (rc);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+oauth_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct OAuthState *oas = cls;
+
+ (void) cmd;
+ oas->mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD | MHD_USE_DEBUG,
+ oas->port,
+ NULL, NULL,
+ &handler_cb, oas,
+ MHD_OPTION_NOTIFY_COMPLETED, &cleanup, NULL,
+ NULL);
+ if (NULL == oas->mhd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Cleanup the state from a "oauth" CMD, and possibly cancel a operation
+ * thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+oauth_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct OAuthState *oas = cls;
+
+ (void) cmd;
+ if (NULL != oas->mhd)
+ {
+ MHD_stop_daemon (oas->mhd);
+ oas->mhd = NULL;
+ }
+ GNUNET_free (oas);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_oauth_with_birthdate (const char *label,
+ const char *birthdate,
+ uint16_t port)
+{
+ struct OAuthState *oas;
+
+ oas = GNUNET_new (struct OAuthState);
+ oas->port = port;
+ oas->birthdate = birthdate;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = oas,
+ .label = label,
+ .run = &oauth_run,
+ .cleanup = &oauth_cleanup,
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_oauth.c */
diff --git a/src/testing/testing_api_cmd_offline_sign_extensions.c b/src/testing/testing_api_cmd_offline_sign_extensions.c
new file mode 100644
index 000000000..f39679f97
--- /dev/null
+++ b/src/testing/testing_api_cmd_offline_sign_extensions.c
@@ -0,0 +1,164 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+*/
+
+/**
+ * @file testing/testing_api_cmd_offline_sign_extensions.c
+ * @brief run the taler-exchange-offline command to sign extensions (and therefore activate them)
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "extensionssign" CMD.
+ */
+struct ExtensionsSignState
+{
+
+ /**
+ * Process for the "extensionssign" command.
+ */
+ struct GNUNET_OS_Process *extensionssign_proc;
+
+ /**
+ * Configuration file used by the command.
+ */
+ const char *config_filename;
+
+};
+
+
+/**
+ * Run the command; calls the `taler-exchange-offline' program.
+ *
+ * @param cls closure.
+ * @param cmd the commaind being run.
+ * @param is interpreter state.
+ */
+static void
+extensionssign_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct ExtensionsSignState *ks = cls;
+
+ ks->extensionssign_proc
+ = GNUNET_OS_start_process (
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-exchange-offline",
+ "taler-exchange-offline",
+ "-c", ks->config_filename,
+ "-L", "INFO",
+ "extensions",
+ "sign",
+ "upload",
+ NULL);
+ if (NULL == ks->extensionssign_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "extensionssign" CMD, and possibly kills its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+extensionssign_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct ExtensionsSignState *ks = cls;
+
+ (void) cmd;
+ if (NULL != ks->extensionssign_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ks->extensionssign_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (ks->extensionssign_proc);
+ GNUNET_OS_process_destroy (ks->extensionssign_proc);
+ ks->extensionssign_proc = NULL;
+ }
+ GNUNET_free (ks);
+}
+
+
+/**
+ * Offer "extensionssign" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+extensionssign_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct ExtensionsSignState *ks = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (&ks->extensionssign_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label,
+ const char *config_filename)
+{
+ struct ExtensionsSignState *ks;
+
+ ks = GNUNET_new (struct ExtensionsSignState);
+ ks->config_filename = config_filename;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ks,
+ .label = label,
+ .run = &extensionssign_run,
+ .cleanup = &extensionssign_cleanup,
+ .traits = &extensionssign_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_exec_offline_sign_extensions.c */
diff --git a/src/testing/testing_api_cmd_offline_sign_global_fees.c b/src/testing/testing_api_cmd_offline_sign_global_fees.c
new file mode 100644
index 000000000..db3916258
--- /dev/null
+++ b/src/testing/testing_api_cmd_offline_sign_global_fees.c
@@ -0,0 +1,230 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+*/
+
+/**
+ * @file testing/testing_api_cmd_offline_sign_global_fees.c
+ * @brief run the taler-exchange-offline command to download, sign and upload global fees
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "offlinesign" CMD.
+ */
+struct OfflineSignState
+{
+
+ /**
+ * Process for the "offlinesign" command.
+ */
+ struct GNUNET_OS_Process *offlinesign_proc;
+
+ /**
+ * Configuration file used by the command.
+ */
+ const char *config_filename;
+
+ /**
+ * The history fee to sign.
+ */
+ const char *history_fee_s;
+
+ /**
+ * The account fee to sign.
+ */
+ const char *account_fee_s;
+
+ /**
+ * The purse fee to sign.
+ */
+ const char *purse_fee_s;
+
+ /**
+ * When MUST purses time out?
+ */
+ struct GNUNET_TIME_Relative purse_timeout;
+
+ /**
+ * How long do we keep the history?
+ */
+ struct GNUNET_TIME_Relative history_expiration;
+
+ /**
+ * Number of (free) purses per account.
+ */
+ unsigned int num_purses;
+};
+
+
+/**
+ * Run the command; calls the `taler-exchange-offline' program.
+ *
+ * @param cls closure.
+ * @param cmd the commaind being run.
+ * @param is interpreter state.
+ */
+static void
+offlinesign_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct OfflineSignState *ks = cls;
+ char num_purses[12];
+ char history_expiration[32];
+ char purse_timeout[32];
+
+ GNUNET_snprintf (num_purses,
+ sizeof (num_purses),
+ "%u",
+ ks->num_purses);
+ GNUNET_snprintf (history_expiration,
+ sizeof (history_expiration),
+ "%s",
+ GNUNET_TIME_relative2s (ks->history_expiration,
+ false));
+ GNUNET_snprintf (purse_timeout,
+ sizeof (purse_timeout),
+ "%s",
+ GNUNET_TIME_relative2s (ks->purse_timeout,
+ false));
+ ks->offlinesign_proc
+ = GNUNET_OS_start_process (
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-exchange-offline",
+ "taler-exchange-offline",
+ "-c", ks->config_filename,
+ "-L", "INFO",
+ "global-fee",
+ "now",
+ ks->history_fee_s,
+ ks->account_fee_s,
+ ks->purse_fee_s,
+ purse_timeout,
+ history_expiration,
+ num_purses,
+ "upload",
+ NULL);
+ if (NULL == ks->offlinesign_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "offlinesign" CMD, and possibly kills its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+offlinesign_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct OfflineSignState *ks = cls;
+
+ (void) cmd;
+ if (NULL != ks->offlinesign_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ks->offlinesign_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (ks->offlinesign_proc);
+ GNUNET_OS_process_destroy (ks->offlinesign_proc);
+ ks->offlinesign_proc = NULL;
+ }
+ GNUNET_free (ks);
+}
+
+
+/**
+ * Offer "offlinesign" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+offlinesign_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct OfflineSignState *ks = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (&ks->offlinesign_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_global_fees (
+ const char *label,
+ const char *config_filename,
+ const char *history_fee,
+ const char *account_fee,
+ const char *purse_fee,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ unsigned int num_purses)
+{
+ struct OfflineSignState *ks;
+
+ ks = GNUNET_new (struct OfflineSignState);
+ ks->config_filename = config_filename;
+ ks->history_fee_s = history_fee;
+ ks->account_fee_s = account_fee;
+ ks->purse_fee_s = purse_fee;
+ ks->purse_timeout = purse_timeout;
+ ks->history_expiration = history_expiration;
+ ks->num_purses = num_purses;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ks,
+ .label = label,
+ .run = &offlinesign_run,
+ .cleanup = &offlinesign_cleanup,
+ .traits = &offlinesign_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_exec_offline_sign_global_fees.c */
diff --git a/src/testing/testing_api_cmd_offline_sign_keys.c b/src/testing/testing_api_cmd_offline_sign_keys.c
new file mode 100644
index 000000000..2c99219b6
--- /dev/null
+++ b/src/testing/testing_api_cmd_offline_sign_keys.c
@@ -0,0 +1,165 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 testing/testing_api_cmd_offline_sign_keys.c
+ * @brief run the taler-exchange-offline command to download, sign and upload keys
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "offlinesign" CMD.
+ */
+struct OfflineSignState
+{
+
+ /**
+ * Process for the "offlinesign" command.
+ */
+ struct GNUNET_OS_Process *offlinesign_proc;
+
+ /**
+ * Configuration file used by the command.
+ */
+ const char *config_filename;
+
+};
+
+
+/**
+ * Run the command; calls the `taler-exchange-offline' program.
+ *
+ * @param cls closure.
+ * @param cmd the commaind being run.
+ * @param is interpreter state.
+ */
+static void
+offlinesign_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct OfflineSignState *ks = cls;
+
+ ks->offlinesign_proc
+ = GNUNET_OS_start_process (
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-exchange-offline",
+ "taler-exchange-offline",
+ "-c", ks->config_filename,
+ "-L", "INFO",
+ "download",
+ "sign",
+ "upload",
+ NULL);
+ if (NULL == ks->offlinesign_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "offlinesign" CMD, and possibly kills its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+offlinesign_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct OfflineSignState *ks = cls;
+
+ (void) cmd;
+ if (NULL != ks->offlinesign_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ks->offlinesign_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (ks->offlinesign_proc);
+ GNUNET_OS_process_destroy (ks->offlinesign_proc);
+ ks->offlinesign_proc = NULL;
+ }
+ GNUNET_free (ks);
+}
+
+
+/**
+ * Offer "offlinesign" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+offlinesign_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct OfflineSignState *ks = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (&ks->offlinesign_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_keys (const char *label,
+ const char *config_filename)
+{
+ struct OfflineSignState *ks;
+
+ ks = GNUNET_new (struct OfflineSignState);
+ ks->config_filename = config_filename;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ks,
+ .label = label,
+ .run = &offlinesign_run,
+ .cleanup = &offlinesign_cleanup,
+ .traits = &offlinesign_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_exec_offline_sign_keys.c */
diff --git a/src/testing/testing_api_cmd_offline_sign_wire_fees.c b/src/testing/testing_api_cmd_offline_sign_wire_fees.c
new file mode 100644
index 000000000..0fccbcd0a
--- /dev/null
+++ b/src/testing/testing_api_cmd_offline_sign_wire_fees.c
@@ -0,0 +1,182 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 testing/testing_api_cmd_offline_sign_wire_fees.c
+ * @brief run the taler-exchange-offline command to download, sign and upload wire fees
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "offlinesign" CMD.
+ */
+struct OfflineSignState
+{
+
+ /**
+ * Process for the "offlinesign" command.
+ */
+ struct GNUNET_OS_Process *offlinesign_proc;
+
+ /**
+ * Configuration file used by the command.
+ */
+ const char *config_filename;
+
+ /**
+ * The wire fee to sign.
+ */
+ const char *wire_fee_s;
+
+ /**
+ * The closing fee to sign.
+ */
+ const char *closing_fee_s;
+
+};
+
+
+/**
+ * Run the command; calls the `taler-exchange-offline' program.
+ *
+ * @param cls closure.
+ * @param cmd the commaind being run.
+ * @param is interpreter state.
+ */
+static void
+offlinesign_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct OfflineSignState *ks = cls;
+
+ ks->offlinesign_proc
+ = GNUNET_OS_start_process (
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-exchange-offline",
+ "taler-exchange-offline",
+ "-c", ks->config_filename,
+ "-L", "INFO",
+ "wire-fee",
+ "now",
+ "x-taler-bank",
+ ks->wire_fee_s,
+ ks->closing_fee_s,
+ "upload",
+ NULL);
+ if (NULL == ks->offlinesign_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "offlinesign" CMD, and possibly kills its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+offlinesign_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct OfflineSignState *ks = cls;
+
+ (void) cmd;
+ if (NULL != ks->offlinesign_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ks->offlinesign_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (ks->offlinesign_proc);
+ GNUNET_OS_process_destroy (ks->offlinesign_proc);
+ ks->offlinesign_proc = NULL;
+ }
+ GNUNET_free (ks);
+}
+
+
+/**
+ * Offer "offlinesign" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+offlinesign_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct OfflineSignState *ks = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (&ks->offlinesign_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_fees (const char *label,
+ const char *config_filename,
+ const char *wire_fee,
+ const char *closing_fee)
+{
+ struct OfflineSignState *ks;
+
+ ks = GNUNET_new (struct OfflineSignState);
+ ks->config_filename = config_filename;
+ ks->wire_fee_s = wire_fee;
+ ks->closing_fee_s = closing_fee;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ks,
+ .label = label,
+ .run = &offlinesign_run,
+ .cleanup = &offlinesign_cleanup,
+ .traits = &offlinesign_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_exec_offline_sign_fees.c */
diff --git a/src/testing/testing_api_cmd_purse_create_deposit.c b/src/testing/testing_api_cmd_purse_create_deposit.c
new file mode 100644
index 000000000..4740f9801
--- /dev/null
+++ b/src/testing/testing_api_cmd_purse_create_deposit.c
@@ -0,0 +1,450 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_purse_create_deposit.c
+ * @brief command for testing /purses/$PID/create
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+/**
+ * Information we keep per deposited coin.
+ */
+struct Coin
+{
+ /**
+ * Reference to the respective command.
+ */
+ char *command_ref;
+
+ /**
+ * index of the specific coin in the traits of @e command_ref.
+ */
+ unsigned int coin_index;
+
+ /**
+ * Public key of the deposited coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Amount to deposit (with fee).
+ */
+ struct TALER_Amount deposit_with_fee;
+
+ /**
+ * Entry in the coin's history generated by this operation.
+ */
+ struct TALER_EXCHANGE_CoinHistoryEntry che;
+
+};
+
+
+/**
+ * State for a "purse create deposit" CMD.
+ */
+struct PurseCreateDepositState
+{
+
+ /**
+ * Total purse target amount without fees.
+ */
+ struct TALER_Amount target_amount;
+
+ /**
+ * Reference to any command that is able to provide a coin.
+ */
+ struct Coin *coin_references;
+
+ /**
+ * JSON string describing what a proposal is about.
+ */
+ json_t *contract_terms;
+
+ /**
+ * Purse expiration time.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * Relative purse expiration time.
+ */
+ struct GNUNET_TIME_Relative rel_expiration;
+
+ /**
+ * Set (by the interpreter) to a fresh private key. This
+ * key will be used to create the purse.
+ */
+ struct TALER_PurseContractPrivateKeyP purse_priv;
+
+ /**
+ * Set (by the interpreter) to a fresh private key. This
+ * key will be used to merge the purse.
+ */
+ struct TALER_PurseMergePrivateKeyP merge_priv;
+
+ /**
+ * Set (by the interpreter) to a fresh private key. This
+ * key will be used to decrypt the contract.
+ */
+ struct TALER_ContractDiffiePrivateP contract_priv;
+
+ /**
+ * Signing key used by the exchange to sign the
+ * deposit confirmation.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Signature from the exchange on the
+ * deposit confirmation.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+ /**
+ * Set (by the interpreter) to a public key corresponding
+ * to @e purse_priv.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * PurseCreateDeposit handle while operation is running.
+ */
+ struct TALER_EXCHANGE_PurseCreateDepositHandle *dh;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Length of the @e coin_references array.
+ */
+ unsigned int num_coin_references;
+
+ /**
+ * Should we upload the contract?
+ */
+ bool upload_contract;
+
+};
+
+
+/**
+ * Callback to analyze the /purses/$PID/create response, just used to check if
+ * the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param dr deposit response details
+ */
+static void
+deposit_cb (void *cls,
+ const struct TALER_EXCHANGE_PurseCreateDepositResponse *dr)
+{
+ struct PurseCreateDepositState *ds = cls;
+
+ ds->dh = NULL;
+ if (ds->expected_response_code != dr->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ dr->hr.http_status,
+ ds->expected_response_code);
+ return;
+ }
+ if (MHD_HTTP_OK == dr->hr.http_status)
+ {
+ ds->exchange_pub = dr->details.ok.exchange_pub;
+ ds->exchange_sig = dr->details.ok.exchange_sig;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+deposit_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PurseCreateDepositState *ds = cls;
+ struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references];
+
+ (void) cmd;
+ ds->is = is;
+ GNUNET_CRYPTO_eddsa_key_create (&ds->purse_priv.eddsa_priv);
+ GNUNET_CRYPTO_eddsa_key_create (&ds->merge_priv.eddsa_priv);
+ GNUNET_CRYPTO_ecdhe_key_create (&ds->contract_priv.ecdhe_priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&ds->purse_priv.eddsa_priv,
+ &ds->purse_pub.eddsa_pub);
+
+ for (unsigned int i = 0; i<ds->num_coin_references; i++)
+ {
+ struct Coin *cr = &ds->coin_references[i];
+ struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i];
+ const struct TALER_TESTING_Command *coin_cmd;
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+ const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+ const struct TALER_DenominationSignature *denom_pub_sig;
+
+ coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ cr->command_ref);
+ if (NULL == coin_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (coin_cmd,
+ cr->coin_index,
+ &coin_priv)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_age_commitment_proof (coin_cmd,
+ cr->coin_index,
+ &age_commitment_proof))
+ ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_pub (coin_cmd,
+ cr->coin_index,
+ &denom_pub)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_sig (coin_cmd,
+ cr->coin_index,
+ &denom_pub_sig)) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ pd->age_commitment_proof = age_commitment_proof;
+ pd->denom_sig = *denom_pub_sig;
+ pd->coin_priv = *coin_priv;
+ pd->amount = cr->deposit_with_fee;
+ pd->h_denom_pub = denom_pub->h_key;
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+ &cr->coin_pub.eddsa_pub);
+ cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT;
+ cr->che.amount = cr->deposit_with_fee;
+ GNUNET_CRYPTO_eddsa_key_get_public (
+ &ds->purse_priv.eddsa_priv,
+ &cr->che.details.purse_deposit.purse_pub.eddsa_pub);
+ cr->che.details.purse_deposit.exchange_base_url
+ = TALER_TESTING_get_exchange_url (is);
+ TALER_age_commitment_hash (
+ &age_commitment_proof->commitment,
+ &cr->che.details.purse_deposit.phac);
+ }
+
+ ds->purse_expiration =
+ GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_relative_to_absolute (ds->rel_expiration));
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ ds->contract_terms,
+ "pay_deadline",
+ GNUNET_JSON_from_timestamp (ds->purse_expiration)));
+ ds->dh = TALER_EXCHANGE_purse_create_with_deposit (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ &ds->purse_priv,
+ &ds->merge_priv,
+ &ds->contract_priv,
+ ds->contract_terms,
+ ds->num_coin_references,
+ deposits,
+ ds->upload_contract,
+ &deposit_cb,
+ ds);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not create purse with deposit\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "deposit" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct PurseCreateDepositState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+deposit_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PurseCreateDepositState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_purse_create_with_deposit_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ for (unsigned int i = 0; i<ds->num_coin_references; i++)
+ GNUNET_free (ds->coin_references[i].command_ref);
+ json_decref (ds->contract_terms);
+ GNUNET_free (ds->coin_references);
+ GNUNET_free (ds);
+}
+
+
+/**
+ * Offer internal data from a "deposit" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+deposit_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PurseCreateDepositState *ds = cls;
+ if (index >= ds->num_coin_references)
+ return GNUNET_NO;
+
+ {
+ const struct Coin *co = &ds->coin_references[index];
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_merge_priv (&ds->merge_priv),
+ TALER_TESTING_make_trait_contract_priv (&ds->contract_priv),
+ TALER_TESTING_make_trait_coin_history (index,
+ &co->che),
+ TALER_TESTING_make_trait_coin_pub (index,
+ &co->coin_pub),
+ TALER_TESTING_make_trait_purse_priv (&ds->purse_priv),
+ TALER_TESTING_make_trait_purse_pub (&ds->purse_pub),
+ TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
+ TALER_TESTING_make_trait_deposit_amount (0,
+ &ds->target_amount),
+ TALER_TESTING_make_trait_timestamp (index,
+ &ds->purse_expiration),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+ }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_create_with_deposit (
+ const char *label,
+ unsigned int expected_http_status,
+ const char *contract_terms,
+ bool upload_contract,
+ struct GNUNET_TIME_Relative purse_expiration,
+ ...)
+{
+ struct PurseCreateDepositState *ds;
+
+ ds = GNUNET_new (struct PurseCreateDepositState);
+ ds->rel_expiration = purse_expiration;
+ ds->upload_contract = upload_contract;
+ ds->expected_response_code = expected_http_status;
+ ds->contract_terms = json_loads (contract_terms,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == ds->contract_terms)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse contract terms `%s' for CMD `%s'\n",
+ contract_terms,
+ label);
+ GNUNET_assert (0);
+ }
+ {
+ va_list ap;
+ unsigned int i;
+ const char *ref;
+ const char *val;
+
+ va_start (ap, purse_expiration);
+ while (NULL != (va_arg (ap, const char *)))
+ ds->num_coin_references++;
+ va_end (ap);
+ GNUNET_assert (0 == (ds->num_coin_references % 2));
+ ds->num_coin_references /= 2;
+ ds->coin_references = GNUNET_new_array (ds->num_coin_references,
+ struct Coin);
+ i = 0;
+ va_start (ap, purse_expiration);
+ while (NULL != (ref = va_arg (ap, const char *)))
+ {
+ struct Coin *c = &ds->coin_references[i++];
+
+ GNUNET_assert (NULL != (val = va_arg (ap, const char *)));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_parse_coin_reference (
+ ref,
+ &c->command_ref,
+ &c->coin_index));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (val,
+ &c->deposit_with_fee));
+ }
+ va_end (ap);
+ }
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &deposit_run,
+ .cleanup = &deposit_cleanup,
+ .traits = &deposit_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_purse_create_deposit.c */
diff --git a/src/testing/testing_api_cmd_purse_delete.c b/src/testing/testing_api_cmd_purse_delete.c
new file mode 100644
index 000000000..26037359e
--- /dev/null
+++ b/src/testing/testing_api_cmd_purse_delete.c
@@ -0,0 +1,189 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 testing/testing_api_cmd_purse_delete.c
+ * @brief command for testing /management/purse/disable.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "purse_delete" CMD.
+ */
+struct PurseDeleteState
+{
+
+ /**
+ * Purse delete handle while operation is running.
+ */
+ struct TALER_EXCHANGE_PurseDeleteHandle *pdh;
+
+ /**
+ * Our interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Command that created the purse we now want to
+ * delete.
+ */
+ const char *purse_cmd;
+};
+
+
+/**
+ * Callback to analyze the DELETE /purses/$PID response, just used to check if
+ * the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param pdr HTTP response details
+ */
+static void
+purse_delete_cb (void *cls,
+ const struct TALER_EXCHANGE_PurseDeleteResponse *pdr)
+{
+ struct PurseDeleteState *pds = cls;
+
+ pds->pdh = NULL;
+ if (pds->expected_response_code != pdr->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status (pds->is,
+ pdr->hr.http_status,
+ pds->expected_response_code);
+ return;
+ }
+ TALER_TESTING_interpreter_next (pds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+purse_delete_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PurseDeleteState *pds = cls;
+ const struct TALER_PurseContractPrivateKeyP *purse_priv;
+ const struct TALER_TESTING_Command *ref;
+ const char *exchange_url;
+
+ (void) cmd;
+ exchange_url = TALER_TESTING_get_exchange_url (is);
+ if (NULL == exchange_url)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ pds->purse_cmd);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_purse_priv (ref,
+ &purse_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ pds->is = is;
+ pds->pdh = TALER_EXCHANGE_purse_delete (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ purse_priv,
+ &purse_delete_cb,
+ pds);
+ if (NULL == pds->pdh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "purse_delete" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct PurseDeleteState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+purse_delete_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PurseDeleteState *pds = cls;
+
+ if (NULL != pds->pdh)
+ {
+ TALER_TESTING_command_incomplete (pds->is,
+ cmd->label);
+ TALER_EXCHANGE_purse_delete_cancel (pds->pdh);
+ pds->pdh = NULL;
+ }
+ GNUNET_free (pds);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_delete (const char *label,
+ unsigned int expected_http_status,
+ const char *purse_cmd)
+{
+ struct PurseDeleteState *ds;
+
+ ds = GNUNET_new (struct PurseDeleteState);
+ ds->expected_response_code = expected_http_status;
+ ds->purse_cmd = purse_cmd;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &purse_delete_run,
+ .cleanup = &purse_delete_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_purse_delete.c */
diff --git a/src/testing/testing_api_cmd_purse_deposit.c b/src/testing/testing_api_cmd_purse_deposit.c
new file mode 100644
index 000000000..048c6d736
--- /dev/null
+++ b/src/testing/testing_api_cmd_purse_deposit.c
@@ -0,0 +1,491 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software 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 testing/testing_api_cmd_purse_deposit.c
+ * @brief command for testing /purses/$PID/create
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+/**
+ * Information we keep per deposited coin.
+ */
+struct Coin
+{
+ /**
+ * Reference to the respective command.
+ */
+ char *command_ref;
+
+ /**
+ * Entry in the coin's history generated by this operation.
+ */
+ struct TALER_EXCHANGE_CoinHistoryEntry che;
+
+ /**
+ * Public key of the deposited coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * index of the specific coin in the traits of @e command_ref.
+ */
+ unsigned int coin_index;
+
+ /**
+ * Amount to deposit (with fee).
+ */
+ struct TALER_Amount deposit_with_fee;
+
+};
+
+
+/**
+ * State for a "purse deposit" CMD.
+ */
+struct PurseDepositState
+{
+
+ /**
+ * Total purse target amount without fees.
+ */
+ struct TALER_Amount target_amount;
+
+ /**
+ * Reference to any command that is able to provide a coin.
+ */
+ struct Coin *coin_references;
+
+ /**
+ * The purse's public key.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * The reserve we are being deposited into.
+ * Set as a trait once we know the reserve.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * PurseDeposit handle while operation is running.
+ */
+ struct TALER_EXCHANGE_PurseDepositHandle *dh;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Reference to the command that established the purse.
+ */
+ const char *purse_ref;
+
+ /**
+ * Reserve history entry that corresponds to this operation.
+ * Will be of type #TALER_EXCHANGE_RTT_MERGE.
+ * Only valid if @e purse_complete is true.
+ */
+ struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Length of the @e coin_references array.
+ */
+ unsigned int num_coin_references;
+
+ /**
+ * Minimum age to apply to all deposits.
+ */
+ uint8_t min_age;
+
+ /**
+ * Set to true if this deposit filled the purse.
+ */
+ bool purse_complete;
+};
+
+
+/**
+ * Callback to analyze the /purses/$PID/deposit response, just used to check if
+ * the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param dr deposit response details
+ */
+static void
+deposit_cb (void *cls,
+ const struct TALER_EXCHANGE_PurseDepositResponse *dr)
+{
+ struct PurseDepositState *ds = cls;
+
+ ds->dh = NULL;
+ if (ds->expected_response_code != dr->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ dr->hr.http_status,
+ ds->expected_response_code);
+ return;
+ }
+ if (MHD_HTTP_OK == dr->hr.http_status)
+ {
+ if (-1 !=
+ TALER_amount_cmp (&dr->details.ok.total_deposited,
+ &dr->details.ok.purse_value_after_fees))
+ {
+ const struct TALER_TESTING_Command *purse_cmd;
+ const struct TALER_ReserveSignatureP *reserve_sig;
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+ const struct GNUNET_TIME_Timestamp *merge_timestamp;
+ const struct TALER_PurseMergePublicKeyP *merge_pub;
+
+ purse_cmd = TALER_TESTING_interpreter_lookup_command (ds->is,
+ ds->purse_ref);
+ GNUNET_assert (NULL != purse_cmd);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_sig (purse_cmd,
+ &reserve_sig))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_pub (purse_cmd,
+ &reserve_pub))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_merge_pub (purse_cmd,
+ &merge_pub))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ ds->reserve_pub = *reserve_pub;
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_timestamp (purse_cmd,
+ 0,
+ &merge_timestamp))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+
+ /* Deposits complete, create trait! */
+ ds->reserve_history.type = TALER_EXCHANGE_RTT_MERGE;
+ {
+ struct TALER_EXCHANGE_Keys *keys;
+ const struct TALER_EXCHANGE_GlobalFee *gf;
+
+ keys = TALER_TESTING_get_keys (ds->is);
+ GNUNET_assert (NULL != keys);
+ gf = TALER_EXCHANGE_get_global_fee (keys,
+ *merge_timestamp);
+ GNUNET_assert (NULL != gf);
+
+ /* Note: change when flags below changes! */
+ ds->reserve_history.amount
+ = dr->details.ok.purse_value_after_fees;
+ if (true)
+ {
+ ds->reserve_history.details.merge_details.purse_fee = gf->fees.purse;
+ }
+ else
+ {
+ TALER_amount_set_zero (
+ ds->reserve_history.amount.currency,
+ &ds->reserve_history.details.merge_details.purse_fee);
+ }
+ }
+ ds->reserve_history.details.merge_details.h_contract_terms
+ = dr->details.ok.h_contract_terms;
+ ds->reserve_history.details.merge_details.merge_pub
+ = *merge_pub;
+ ds->reserve_history.details.merge_details.purse_pub
+ = ds->purse_pub;
+ ds->reserve_history.details.merge_details.reserve_sig
+ = *reserve_sig;
+ ds->reserve_history.details.merge_details.merge_timestamp
+ = *merge_timestamp;
+ ds->reserve_history.details.merge_details.purse_expiration
+ = dr->details.ok.purse_expiration;
+ ds->reserve_history.details.merge_details.min_age
+ = ds->min_age;
+ ds->reserve_history.details.merge_details.flags
+ = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
+ ds->purse_complete = true;
+ }
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+deposit_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PurseDepositState *ds = cls;
+ struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references];
+ const struct TALER_PurseContractPublicKeyP *purse_pub;
+ const struct TALER_TESTING_Command *purse_cmd;
+
+ (void) cmd;
+ ds->is = is;
+ purse_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ ds->purse_ref);
+ GNUNET_assert (NULL != purse_cmd);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_purse_pub (purse_cmd,
+ &purse_pub))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ ds->purse_pub = *purse_pub;
+ for (unsigned int i = 0; i<ds->num_coin_references; i++)
+ {
+ struct Coin *cr = &ds->coin_references[i];
+ struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i];
+ const struct TALER_TESTING_Command *coin_cmd;
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+ const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+ const struct TALER_DenominationSignature *denom_pub_sig;
+
+ coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ cr->command_ref);
+ GNUNET_assert (NULL != coin_cmd);
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (coin_cmd,
+ cr->coin_index,
+ &coin_priv)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_age_commitment_proof (coin_cmd,
+ cr->coin_index,
+ &age_commitment_proof))
+ ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_pub (coin_cmd,
+ cr->coin_index,
+ &denom_pub)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_sig (coin_cmd,
+ cr->coin_index,
+ &denom_pub_sig)) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+ &cr->coin_pub.eddsa_pub);
+ cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT;
+ cr->che.amount = cr->deposit_with_fee;
+ cr->che.details.purse_deposit.purse_pub = *purse_pub;
+ cr->che.details.purse_deposit.exchange_base_url
+ = TALER_TESTING_get_exchange_url (is);
+ TALER_age_commitment_hash (
+ &age_commitment_proof->commitment,
+ &cr->che.details.purse_deposit.phac);
+ pd->age_commitment_proof = age_commitment_proof;
+ pd->denom_sig = *denom_pub_sig;
+ pd->coin_priv = *coin_priv;
+ pd->amount = cr->deposit_with_fee;
+ pd->h_denom_pub = denom_pub->h_key;
+ }
+
+ ds->dh = TALER_EXCHANGE_purse_deposit (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ NULL, /* FIXME #7271: WADs support: purse exchange URL */
+ &ds->purse_pub,
+ ds->min_age,
+ ds->num_coin_references,
+ deposits,
+ &deposit_cb,
+ ds);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not deposit into purse\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "deposit" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct PurseDepositState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+deposit_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PurseDepositState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_purse_deposit_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ for (unsigned int i = 0; i<ds->num_coin_references; i++)
+ GNUNET_free (ds->coin_references[i].command_ref);
+ GNUNET_free (ds->coin_references);
+ GNUNET_free (ds);
+}
+
+
+/**
+ * Offer internal data from a "deposit" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+deposit_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PurseDepositState *ds = cls;
+
+ if (index >= ds->num_coin_references)
+ return GNUNET_NO;
+ {
+ const struct Coin *co = &ds->coin_references[index];
+ struct TALER_TESTING_Trait traits[] = {
+ /* history entry MUST be first due to response code logic below! */
+ TALER_TESTING_make_trait_reserve_history (0,
+ &ds->reserve_history),
+ TALER_TESTING_make_trait_coin_history (index,
+ &co->che),
+ TALER_TESTING_make_trait_coin_pub (index,
+ &co->coin_pub),
+ TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub),
+ TALER_TESTING_make_trait_purse_pub (&ds->purse_pub),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (ds->purse_complete
+ ? &traits[0] /* we have reserve history */
+ : &traits[1], /* skip reserve history */
+ ret,
+ trait,
+ index);
+ }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_deposit_coins (
+ const char *label,
+ unsigned int expected_http_status,
+ uint8_t min_age,
+ const char *purse_ref,
+ ...)
+{
+ struct PurseDepositState *ds;
+
+ ds = GNUNET_new (struct PurseDepositState);
+ ds->expected_response_code = expected_http_status;
+ ds->min_age = min_age;
+ ds->purse_ref = purse_ref;
+ {
+ va_list ap;
+ unsigned int i;
+ const char *ref;
+ const char *val;
+
+ va_start (ap, purse_ref);
+ while (NULL != (va_arg (ap, const char *)))
+ ds->num_coin_references++;
+ va_end (ap);
+ GNUNET_assert (0 == (ds->num_coin_references % 2));
+ ds->num_coin_references /= 2;
+ ds->coin_references = GNUNET_new_array (ds->num_coin_references,
+ struct Coin);
+ i = 0;
+ va_start (ap, purse_ref);
+ while (NULL != (ref = va_arg (ap, const char *)))
+ {
+ struct Coin *c = &ds->coin_references[i++];
+
+ GNUNET_assert (NULL != (val = va_arg (ap,
+ const char *)));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_parse_coin_reference (
+ ref,
+ &c->command_ref,
+ &c->coin_index));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (val,
+ &c->deposit_with_fee));
+ }
+ va_end (ap);
+ }
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &deposit_run,
+ .cleanup = &deposit_cleanup,
+ .traits = &deposit_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_purse_deposit.c */
diff --git a/src/testing/testing_api_cmd_purse_get.c b/src/testing/testing_api_cmd_purse_get.c
new file mode 100644
index 000000000..d5246660b
--- /dev/null
+++ b/src/testing/testing_api_cmd_purse_get.c
@@ -0,0 +1,367 @@
+/*
+ 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_purse_get.c
+ * @brief Implement the GET /purse/$RID test command.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "poll" CMD.
+ */
+struct PollState
+{
+
+ /**
+ * How long do we give the exchange to respond?
+ */
+ struct GNUNET_TIME_Relative timeout;
+
+ /**
+ * Label to the command which created the purse to check,
+ * needed to resort the purse key.
+ */
+ const char *poll_reference;
+
+ /**
+ * Timeout to wait for at most.
+ */
+ struct GNUNET_SCHEDULER_Task *tt;
+
+ /**
+ * The interpreter we are using.
+ */
+ struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * State for a "status" CMD.
+ */
+struct StatusState
+{
+
+ /**
+ * How long do we give the exchange to respond?
+ */
+ struct GNUNET_TIME_Relative timeout;
+
+ /**
+ * Poller waiting for us.
+ */
+ struct PollState *ps;
+
+ /**
+ * Label to the command which created the purse to check,
+ * needed to resort the purse key.
+ */
+ const char *purse_reference;
+
+ /**
+ * Handle to the "purse status" operation.
+ */
+ struct TALER_EXCHANGE_PurseGetHandle *pgh;
+
+ /**
+ * Expected purse balance.
+ */
+ const char *expected_balance;
+
+ /**
+ * Public key of the purse being analyzed.
+ */
+ const struct TALER_PurseContractPublicKeyP *purse_pub;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Are we waiting for a merge or a deposit?
+ */
+ bool wait_for_merge;
+
+};
+
+
+/**
+ * Check that the purse balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param rs HTTP response details
+ */
+static void
+purse_status_cb (void *cls,
+ const struct TALER_EXCHANGE_PurseGetResponse *rs)
+{
+ struct StatusState *ss = cls;
+ struct TALER_TESTING_Interpreter *is = ss->is;
+
+ ss->pgh = NULL;
+ if (ss->expected_response_code != rs->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected HTTP response code: %d in %s:%u\n",
+ rs->hr.http_status,
+ __FILE__,
+ __LINE__);
+ json_dumpf (rs->hr.reply,
+ stderr,
+ 0);
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ if (MHD_HTTP_OK == ss->expected_response_code)
+ {
+ struct TALER_Amount eb;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (ss->expected_balance,
+ &eb));
+ if (0 != TALER_amount_cmp (&eb,
+ &rs->details.ok.balance))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected amount in purse: %s\n",
+ TALER_amount_to_string (&rs->details.ok.balance));
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ }
+ if (NULL != ss->ps)
+ {
+ /* force continuation on long poller */
+ GNUNET_SCHEDULER_cancel (ss->ps->tt);
+ ss->ps->tt = NULL;
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ if (GNUNET_TIME_relative_is_zero (ss->timeout))
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+status_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct StatusState *ss = cls;
+ const struct TALER_TESTING_Command *create_purse;
+
+ ss->is = is;
+ create_purse
+ = TALER_TESTING_interpreter_lookup_command (is,
+ ss->purse_reference);
+ GNUNET_assert (NULL != create_purse);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_purse_pub (create_purse,
+ &ss->purse_pub))
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR ("Failed to find purse_pub for status query\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ ss->pgh = TALER_EXCHANGE_purse_get (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ ss->purse_pub,
+ ss->timeout,
+ ss->wait_for_merge,
+ &purse_status_cb,
+ ss);
+ if (! GNUNET_TIME_relative_is_zero (ss->timeout))
+ {
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+}
+
+
+/**
+ * Cleanup the state from a "purse status" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+status_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct StatusState *ss = cls;
+
+ if (NULL != ss->pgh)
+ {
+ TALER_TESTING_command_incomplete (ss->is,
+ cmd->label);
+ TALER_EXCHANGE_purse_get_cancel (ss->pgh);
+ ss->pgh = NULL;
+ }
+ GNUNET_free (ss);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_poll (
+ const char *label,
+ unsigned int expected_http_status,
+ const char *purse_ref,
+ const char *expected_balance,
+ bool wait_for_merge,
+ struct GNUNET_TIME_Relative timeout)
+{
+ struct StatusState *ss;
+
+ GNUNET_assert (NULL != purse_ref);
+ ss = GNUNET_new (struct StatusState);
+ ss->purse_reference = purse_ref;
+ ss->expected_balance = expected_balance;
+ ss->expected_response_code = expected_http_status;
+ ss->timeout = timeout;
+ ss->wait_for_merge = wait_for_merge;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ss,
+ .label = label,
+ .run = &status_run,
+ .cleanup = &status_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * Long poller timed out. Fail the test.
+ *
+ * @param cls a `struct PollState`
+ */
+static void
+finish_timeout (void *cls)
+{
+ struct PollState *ps = cls;
+
+ ps->tt = NULL;
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ps->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+finish_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PollState *ps = cls;
+ const struct TALER_TESTING_Command *poll_purse;
+ struct StatusState *ss;
+
+ ps->is = is;
+ poll_purse
+ = TALER_TESTING_interpreter_lookup_command (is,
+ ps->poll_reference);
+ GNUNET_assert (NULL != poll_purse);
+ GNUNET_assert (poll_purse->run == &status_run);
+ ss = poll_purse->cls;
+ if (NULL == ss->pgh)
+ {
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ GNUNET_assert (NULL == ss->ps);
+ ss->ps = ps;
+ ps->tt = GNUNET_SCHEDULER_add_delayed (ps->timeout,
+ &finish_timeout,
+ ps);
+}
+
+
+/**
+ * Cleanup the state from a "purse finish" CMD.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+finish_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PollState *ps = cls;
+
+ if (NULL != ps->tt)
+ {
+ GNUNET_SCHEDULER_cancel (ps->tt);
+ ps->tt = NULL;
+ }
+ GNUNET_free (ps);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_poll_finish (const char *label,
+ struct GNUNET_TIME_Relative timeout,
+ const char *poll_reference)
+{
+ struct PollState *ps;
+
+ GNUNET_assert (NULL != poll_reference);
+ ps = GNUNET_new (struct PollState);
+ ps->timeout = timeout;
+ ps->poll_reference = poll_reference;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ps,
+ .label = label,
+ .run = &finish_run,
+ .cleanup = &finish_cleanup
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_purse_merge.c b/src/testing/testing_api_cmd_purse_merge.c
new file mode 100644
index 000000000..cf9d4f996
--- /dev/null
+++ b/src/testing/testing_api_cmd_purse_merge.c
@@ -0,0 +1,436 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_purse_merge.c
+ * @brief command for testing /purses/$PID/merge
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "purse create deposit" CMD.
+ */
+struct PurseMergeState
+{
+
+ /**
+ * Merge time.
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+
+ /**
+ * Reserve public key (to be merged into)
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Reserve private key (useful especially if
+ * @e reserve_ref is NULL).
+ */
+ struct TALER_ReservePrivateKeyP reserve_priv;
+
+ /**
+ * Handle while operation is running.
+ */
+ struct TALER_EXCHANGE_AccountMergeHandle *dh;
+
+ /**
+ * Reference to the merge capability.
+ */
+ const char *merge_ref;
+
+ /**
+ * Reference to the reserve, or NULL (!).
+ */
+ const char *reserve_ref;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Hash of the payto://-URI for the reserve we are
+ * merging into.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Set to the KYC requirement row *if* the exchange replied with
+ * a request for KYC.
+ */
+ uint64_t requirement_row;
+
+ /**
+ * Reserve history entry that corresponds to this operation.
+ * Will be of type #TALER_EXCHANGE_RTT_MERGE.
+ */
+ struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Public key of the merge capability.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Contract value.
+ */
+ struct TALER_Amount value_after_fees;
+
+ /**
+ * Hash of the contract.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * When does the purse expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * Minimum age of deposits into the purse.
+ */
+ uint32_t min_age;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+};
+
+
+/**
+ * Callback to analyze the /purses/$PID/merge response, just used to check if
+ * the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param dr merge response details
+ */
+static void
+merge_cb (void *cls,
+ const struct TALER_EXCHANGE_AccountMergeResponse *dr)
+{
+ struct PurseMergeState *ds = cls;
+
+ ds->dh = NULL;
+ switch (dr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ ds->reserve_history.type = TALER_EXCHANGE_RTT_MERGE;
+ ds->reserve_history.amount = ds->value_after_fees;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (
+ ds->value_after_fees.currency,
+ &ds->reserve_history.details.merge_details.purse_fee));
+ ds->reserve_history.details.merge_details.h_contract_terms
+ = ds->h_contract_terms;
+ ds->reserve_history.details.merge_details.merge_pub
+ = ds->merge_pub;
+ ds->reserve_history.details.merge_details.purse_pub
+ = ds->purse_pub;
+ ds->reserve_history.details.merge_details.reserve_sig
+ = *dr->reserve_sig;
+ ds->reserve_history.details.merge_details.merge_timestamp
+ = ds->merge_timestamp;
+ ds->reserve_history.details.merge_details.purse_expiration
+ = ds->purse_expiration;
+ ds->reserve_history.details.merge_details.min_age
+ = ds->min_age;
+ ds->reserve_history.details.merge_details.flags
+ = TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE;
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* KYC required */
+ ds->requirement_row =
+ dr->details.unavailable_for_legal_reasons.requirement_row;
+ break;
+ }
+
+
+ if (ds->expected_response_code != dr->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ dr->hr.http_status,
+ ds->expected_response_code);
+ return;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+merge_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PurseMergeState *ds = cls;
+ const struct TALER_PurseMergePrivateKeyP *merge_priv;
+ const json_t *ct;
+ const struct TALER_TESTING_Command *ref;
+
+ (void) cmd;
+ ds->is = is;
+ ref = TALER_TESTING_interpreter_lookup_command (ds->is,
+ ds->merge_ref);
+ GNUNET_assert (NULL != ref);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_merge_priv (ref,
+ &merge_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ {
+ const struct TALER_PurseContractPublicKeyP *purse_pub;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_purse_pub (ref,
+ &purse_pub))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ ds->purse_pub = *purse_pub;
+ }
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_contract_terms (ref,
+ &ct))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (ct,
+ &ds->h_contract_terms))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("pay_deadline",
+ &ds->purse_expiration),
+ TALER_JSON_spec_amount_any ("amount",
+ &ds->value_after_fees),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("minimum_age",
+ &ds->min_age),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (ct,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ }
+
+ if (NULL == ds->reserve_ref)
+ {
+ GNUNET_CRYPTO_eddsa_key_create (&ds->reserve_priv.eddsa_priv);
+ }
+ else
+ {
+ const struct TALER_ReservePrivateKeyP *rp;
+
+ ref = TALER_TESTING_interpreter_lookup_command (ds->is,
+ ds->reserve_ref);
+ GNUNET_assert (NULL != ref);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_priv (ref,
+ &rp))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ ds->reserve_priv = *rp;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&ds->reserve_priv.eddsa_priv,
+ &ds->reserve_pub.eddsa_pub);
+ {
+ char *payto_uri;
+ const char *exchange_url;
+ const struct TALER_TESTING_Command *exchange_cmd;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ payto_uri = TALER_reserve_make_payto (exchange_url,
+ &ds->reserve_pub);
+ TALER_payto_hash (payto_uri,
+ &ds->h_payto);
+ GNUNET_free (payto_uri);
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
+ &ds->merge_pub.eddsa_pub);
+ ds->merge_timestamp = GNUNET_TIME_timestamp_get ();
+ ds->dh = TALER_EXCHANGE_account_merge (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ NULL, /* no wad */
+ &ds->reserve_priv,
+ &ds->purse_pub,
+ merge_priv,
+ &ds->h_contract_terms,
+ ds->min_age,
+ &ds->value_after_fees,
+ ds->purse_expiration,
+ ds->merge_timestamp,
+ &merge_cb,
+ ds);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not merge purse\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "merge" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct PurseMergeState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+merge_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PurseMergeState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_account_merge_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ GNUNET_free (ds);
+}
+
+
+/**
+ * Offer internal data from a "merge" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+merge_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PurseMergeState *ds = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ /* history entry MUST be first due to response code logic below! */
+ TALER_TESTING_make_trait_reserve_history (0,
+ &ds->reserve_history),
+ TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub),
+ TALER_TESTING_make_trait_timestamp (0,
+ &ds->merge_timestamp),
+ TALER_TESTING_make_trait_legi_requirement_row (&ds->requirement_row),
+ TALER_TESTING_make_trait_h_payto (&ds->h_payto),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait ((ds->expected_response_code == MHD_HTTP_OK)
+ ? &traits[0] /* we have reserve history */
+ : &traits[1], /* skip reserve history */
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_merge (
+ const char *label,
+ unsigned int expected_http_status,
+ const char *merge_ref,
+ const char *reserve_ref)
+{
+ struct PurseMergeState *ds;
+
+ ds = GNUNET_new (struct PurseMergeState);
+ ds->merge_ref = merge_ref;
+ ds->reserve_ref = reserve_ref;
+ ds->expected_response_code = expected_http_status;
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &merge_run,
+ .cleanup = &merge_cleanup,
+ .traits = &merge_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_purse_merge.c */
diff --git a/src/testing/testing_api_cmd_recoup.c b/src/testing/testing_api_cmd_recoup.c
index 3bbda8e75..cefcd96bb 100644
--- a/src/testing/testing_api_cmd_recoup.c
+++ b/src/testing/testing_api_cmd_recoup.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 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
@@ -54,119 +54,67 @@ struct RecoupState
struct TALER_EXCHANGE_RecoupHandle *ph;
/**
- * NULL if coin was not refreshed, otherwise reference
- * to the melt operation underlying @a coin_reference.
+ * If the recoup filled a reserve, this is set to the reserve's public key.
*/
- const char *melt_reference;
+ struct TALER_ReservePublicKeyP reserve_pub;
/**
- * If the recoup filled a reserve, this is set to the reserve's public key.
+ * Entry in the coin's history generated by this operation.
*/
- struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_EXCHANGE_CoinHistoryEntry che;
+
+ /**
+ * Public key of the refunded coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin;
/**
* Reserve history entry, set if this recoup actually filled up a reserve.
* Otherwise `reserve_history.type` will be zero.
*/
- struct TALER_EXCHANGE_ReserveHistory reserve_history;
+ struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
};
/**
- * Parser reference to a coin.
- *
- * @param coin_reference of format $LABEL['#' $INDEX]?
- * @param[out] cref where we return a copy of $LABEL
- * @param[out] idx where we set $INDEX
- * @return #GNUNET_SYSERR if $INDEX is present but not numeric
- */
-static int
-parse_coin_reference (const char *coin_reference,
- char **cref,
- unsigned int *idx)
-{
- const char *index;
-
- /* We allow command references of the form "$LABEL#$INDEX" or
- just "$LABEL", which implies the index is 0. Figure out
- which one it is. */
- index = strchr (coin_reference, '#');
- if (NULL == index)
- {
- *idx = 0;
- *cref = GNUNET_strdup (coin_reference);
- return GNUNET_OK;
- }
- *cref = GNUNET_strndup (coin_reference,
- index - coin_reference);
- if (1 != sscanf (index + 1,
- "%u",
- idx))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Numeric index (not `%s') required after `#' in command reference of command in %s:%u\n",
- index,
- __FILE__,
- __LINE__);
- GNUNET_free (*cref);
- *cref = NULL;
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
* Check the result of the recoup request: checks whether
* the HTTP response code is good, and that the coin that
* was paid back belonged to the right reserve.
*
* @param cls closure
- * @param http_status HTTP response code.
- * @param ec taler-specific error code.
- * @param reserve_pub public key of the reserve receiving the recoup, NULL if refreshed or on error
- * @param old_coin_pub public key of the dirty coin, NULL if not refreshed or on error
- * @param full_response raw response from the exchange.
+ * @param rr response details
*/
static void
recoup_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
- const json_t *full_response)
+ const struct TALER_EXCHANGE_RecoupResponse *rr)
{
struct RecoupState *ps = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
struct TALER_TESTING_Interpreter *is = ps->is;
- struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
const struct TALER_TESTING_Command *reserve_cmd;
char *cref;
unsigned int idx;
ps->ph = NULL;
- if (ps->expected_response_code != http_status)
+ if (ps->expected_response_code != hr->http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- http_status,
- cmd->label,
- __FILE__,
- __LINE__);
- json_dumpf (full_response, stderr, 0);
- fprintf (stderr, "\n");
- TALER_TESTING_interpreter_fail (is);
+ TALER_TESTING_unexpected_status (is,
+ hr->http_status,
+ ps->expected_response_code);
return;
}
if (GNUNET_OK !=
- parse_coin_reference (ps->coin_reference,
- &cref,
- &idx))
+ TALER_TESTING_parse_coin_reference (
+ ps->coin_reference,
+ &cref,
+ &idx))
{
TALER_TESTING_interpreter_fail (is);
return;
}
+ (void) idx; /* do NOT use! We ignore 'idx', must be 0 for melt! */
reserve_cmd = TALER_TESTING_interpreter_lookup_command (is,
cref);
@@ -179,56 +127,15 @@ recoup_cb (void *cls,
return;
}
- switch (http_status)
+ switch (hr->http_status)
{
case MHD_HTTP_OK:
/* check old_coin_pub or reserve_pub, respectively */
- if (NULL != ps->melt_reference)
- {
- const struct TALER_TESTING_Command *melt_cmd;
- const struct TALER_CoinSpendPrivateKeyP *dirty_priv;
- struct TALER_CoinSpendPublicKeyP oc;
-
- melt_cmd = TALER_TESTING_interpreter_lookup_command (is,
- ps->melt_reference);
- if (NULL == melt_cmd)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_coin_priv (melt_cmd,
- 0,
- &dirty_priv))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- GNUNET_CRYPTO_eddsa_key_get_public (&dirty_priv->eddsa_priv,
- &oc.eddsa_pub);
- if (0 != GNUNET_memcmp (&oc,
- old_coin_pub))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- }
- else
{
const struct TALER_ReservePrivateKeyP *reserve_priv;
- if (NULL == reserve_pub)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_priv (reserve_cmd,
- idx,
&reserve_priv))
{
GNUNET_break (0);
@@ -237,7 +144,7 @@ recoup_cb (void *cls,
}
GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
&ps->reserve_pub.eddsa_pub);
- if (0 != GNUNET_memcmp (reserve_pub,
+ if (0 != GNUNET_memcmp (&rr->details.ok.reserve_pub,
&ps->reserve_pub))
{
GNUNET_break (0);
@@ -248,6 +155,7 @@ recoup_cb (void *cls,
TALER_amount_is_valid (&ps->reserve_history.amount))
ps->reserve_history.type = TALER_EXCHANGE_RTT_RECOUP;
/* ps->reserve_history.details.recoup_details.coin_pub; // initialized earlier */
+ ps->che.details.recoup.reserve_pub = ps->reserve_pub;
}
break;
case MHD_HTTP_NOT_FOUND:
@@ -256,8 +164,9 @@ recoup_cb (void *cls,
break;
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unmanaged HTTP status code %u.\n",
- http_status);
+ "Unmanaged HTTP status code %u/%d.\n",
+ hr->http_status,
+ (int) hr->ec);
break;
}
TALER_TESTING_interpreter_next (is);
@@ -279,18 +188,20 @@ recoup_run (void *cls,
struct RecoupState *ps = cls;
const struct TALER_TESTING_Command *coin_cmd;
const struct TALER_CoinSpendPrivateKeyP *coin_priv;
- const struct TALER_DenominationBlindingKeyP *blinding_key;
const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
const struct TALER_DenominationSignature *coin_sig;
- struct TALER_PlanchetSecretsP planchet;
+ const struct TALER_PlanchetMasterSecretP *planchet;
char *cref;
unsigned int idx;
+ const struct TALER_ExchangeWithdrawValues *ewv;
+ struct TALER_DenominationHashP h_denom_pub;
ps->is = is;
if (GNUNET_OK !=
- parse_coin_reference (ps->coin_reference,
- &cref,
- &idx))
+ TALER_TESTING_parse_coin_reference (
+ ps->coin_reference,
+ &cref,
+ &idx))
{
TALER_TESTING_interpreter_fail (is);
return;
@@ -306,7 +217,6 @@ recoup_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
-
if (GNUNET_OK !=
TALER_TESTING_get_trait_coin_priv (coin_cmd,
idx,
@@ -316,18 +226,25 @@ recoup_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
-
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+ &ps->coin.eddsa_pub);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_exchange_wd_value (coin_cmd,
+ idx,
+ &ewv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
if (GNUNET_OK !=
- TALER_TESTING_get_trait_blinding_key (coin_cmd,
- idx,
- &blinding_key))
+ TALER_TESTING_get_trait_planchet_secret (coin_cmd,
+ &planchet))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- planchet.coin_priv = *coin_priv;
- planchet.blinding_key = *blinding_key;
GNUNET_CRYPTO_eddsa_key_get_public (
&coin_priv->eddsa_priv,
&ps->reserve_history.details.recoup_details.coin_pub.eddsa_pub);
@@ -341,7 +258,6 @@ recoup_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
-
if (GNUNET_OK !=
TALER_TESTING_get_trait_denom_sig (coin_cmd,
idx,
@@ -351,18 +267,30 @@ recoup_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
-
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Trying to recoup denomination '%s'\n",
TALER_B2S (&denom_pub->h_key));
-
- ps->ph = TALER_EXCHANGE_recoup (is->exchange,
- denom_pub,
- coin_sig,
- &planchet,
- NULL != ps->melt_reference,
- recoup_cb,
- ps);
+ ps->che.type = TALER_EXCHANGE_CTT_RECOUP;
+ ps->che.amount = ps->reserve_history.amount;
+ TALER_planchet_blinding_secret_create (planchet,
+ ewv,
+ &ps->che.details.recoup.coin_bks);
+ TALER_denom_pub_hash (&denom_pub->key,
+ &h_denom_pub);
+ TALER_wallet_recoup_sign (&h_denom_pub,
+ &ps->che.details.recoup.coin_bks,
+ coin_priv,
+ &ps->che.details.recoup.coin_sig);
+ ps->ph = TALER_EXCHANGE_recoup (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ denom_pub,
+ coin_sig,
+ ewv,
+ planchet,
+ &recoup_cb,
+ ps);
GNUNET_assert (NULL != ps->ph);
}
@@ -398,7 +326,7 @@ recoup_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
recoup_traits (void *cls,
const void **ret,
const char *trait,
@@ -410,10 +338,13 @@ recoup_traits (void *cls,
return GNUNET_SYSERR; /* no traits */
{
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_reserve_pub (0,
- &ps->reserve_pub),
+ TALER_TESTING_make_trait_reserve_pub (&ps->reserve_pub),
TALER_TESTING_make_trait_reserve_history (0,
&ps->reserve_history),
+ TALER_TESTING_make_trait_coin_history (0,
+ &ps->che),
+ TALER_TESTING_make_trait_coin_pub (0,
+ &ps->coin),
TALER_TESTING_trait_end ()
};
@@ -425,22 +356,10 @@ recoup_traits (void *cls,
}
-/**
- * Make a "recoup" command.
- *
- * @param label the command label
- * @param expected_response_code expected HTTP status code
- * @param coin_reference reference to any command which
- * offers a coin & reserve private key.
- * @param melt_reference NULL if coin was not refreshed
- * @param amount how much do we expect to recoup?
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_recoup (const char *label,
unsigned int expected_response_code,
const char *coin_reference,
- const char *melt_reference,
const char *amount)
{
struct RecoupState *ps;
@@ -448,11 +367,9 @@ TALER_TESTING_cmd_recoup (const char *label,
ps = GNUNET_new (struct RecoupState);
ps->expected_response_code = expected_response_code;
ps->coin_reference = coin_reference;
- ps->melt_reference = melt_reference;
- if ( (NULL != amount) &&
- (GNUNET_OK !=
- TALER_string_to_amount (amount,
- &ps->reserve_history.amount)) )
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount,
+ &ps->reserve_history.amount))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to parse amount `%s' at %s\n",
diff --git a/src/testing/testing_api_cmd_recoup_refresh.c b/src/testing/testing_api_cmd_recoup_refresh.c
new file mode 100644
index 000000000..68d267be4
--- /dev/null
+++ b/src/testing/testing_api_cmd_recoup_refresh.c
@@ -0,0 +1,441 @@
+/*
+ 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_recoup_refresh.c
+ * @brief Implement the /recoup-refresh test command.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "pay back" CMD.
+ */
+struct RecoupRefreshState
+{
+ /**
+ * Expected HTTP status code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Command that offers a reserve private key,
+ * plus a coin to be paid back.
+ */
+ const char *coin_reference;
+
+ /**
+ * Entry in the old coin's history generated by this operation.
+ */
+ struct TALER_EXCHANGE_CoinHistoryEntry che_old;
+
+ /**
+ * Entry in the recouped coin's history generated by this operation.
+ */
+ struct TALER_EXCHANGE_CoinHistoryEntry che_new;
+
+ /**
+ * Public key of the refunded coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub_old;
+
+ /**
+ * Public key of the refunded coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub_new;
+
+ /**
+ * Amount to be recouped.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Handle to the ongoing operation.
+ */
+ struct TALER_EXCHANGE_RecoupRefreshHandle *ph;
+
+ /**
+ * NULL if coin was not refreshed, otherwise reference
+ * to the melt operation underlying @a coin_reference.
+ */
+ const char *melt_reference;
+
+};
+
+
+/**
+ * Check the result of the recoup_refresh request: checks whether
+ * the HTTP response code is good, and that the coin that
+ * was paid back belonged to the right old coin.
+ *
+ * @param cls closure
+ * @param rrr response details
+ */
+static void
+recoup_refresh_cb (void *cls,
+ const struct TALER_EXCHANGE_RecoupRefreshResponse *rrr)
+{
+ struct RecoupRefreshState *rrs = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &rrr->hr;
+ struct TALER_TESTING_Interpreter *is = rrs->is;
+ char *cref;
+ unsigned int idx;
+
+ rrs->ph = NULL;
+ if (rrs->expected_response_code != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status (is,
+ hr->http_status,
+ rrs->expected_response_code);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ TALER_TESTING_parse_coin_reference (
+ rrs->coin_reference,
+ &cref,
+ &idx))
+ {
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ (void) idx; /* do NOT use! We ignore 'idx', must be 0 for melt! */
+
+ GNUNET_free (cref);
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ /* check old_coin_pub */
+ {
+ const struct TALER_TESTING_Command *melt_cmd;
+ const struct TALER_CoinSpendPrivateKeyP *dirty_priv;
+ struct TALER_CoinSpendPublicKeyP oc;
+
+ melt_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ rrs->melt_reference);
+ if (NULL == melt_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (melt_cmd,
+ 0,
+ &dirty_priv))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Coin %u not found in command %s\n",
+ 0,
+ rrs->melt_reference);
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&dirty_priv->eddsa_priv,
+ &oc.eddsa_pub);
+ if (0 != GNUNET_memcmp (&oc,
+ &rrr->details.ok.old_coin_pub))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ }
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ break;
+ case MHD_HTTP_CONFLICT:
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unmanaged HTTP status code %u/%d.\n",
+ hr->http_status,
+ (int) hr->ec);
+ break;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+recoup_refresh_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RecoupRefreshState *rrs = cls;
+ const struct TALER_TESTING_Command *coin_cmd;
+ const struct TALER_TESTING_Command *melt_cmd;
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv_old;
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+ const struct TALER_DenominationSignature *coin_sig;
+ const struct TALER_RefreshMasterSecretP *rplanchet;
+ const struct TALER_PlanchetMasterSecretP *planchet;
+ const struct TALER_ExchangeWithdrawValues *ewv;
+ char *cref;
+ unsigned int idx;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ rrs->is = is;
+ if (GNUNET_OK !=
+ TALER_TESTING_parse_coin_reference (
+ rrs->coin_reference,
+ &cref,
+ &idx))
+ {
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ cref);
+ GNUNET_free (cref);
+ if (NULL == coin_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ melt_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ rrs->melt_reference);
+ if (NULL == melt_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (coin_cmd,
+ idx,
+ &coin_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (melt_cmd,
+ 0,
+ &coin_priv_old))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (
+ &coin_priv->eddsa_priv,
+ &rrs->coin_pub_new.eddsa_pub);
+ GNUNET_CRYPTO_eddsa_key_get_public (
+ &coin_priv_old->eddsa_priv,
+ &rrs->coin_pub_old.eddsa_pub);
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_exchange_wd_value (melt_cmd,
+ idx,
+ &ewv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_planchet_secrets (coin_cmd,
+ idx,
+ &planchet))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_refresh_secret (melt_cmd,
+ &rplanchet))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_pub (coin_cmd,
+ idx,
+ &denom_pub))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_sig (coin_cmd,
+ idx,
+ &coin_sig))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to recoup_refresh denomination '%s'\n",
+ TALER_B2S (&denom_pub->h_key));
+ rrs->che_old.type
+ = TALER_EXCHANGE_CTT_OLD_COIN_RECOUP;
+ rrs->che_old.amount
+ = rrs->amount;
+ rrs->che_old.details.old_coin_recoup.new_coin_pub
+ = rrs->coin_pub_new;
+ rrs->che_new.type
+ = TALER_EXCHANGE_CTT_RECOUP_REFRESH;
+ rrs->che_new.amount
+ = rrs->amount;
+ rrs->che_new.details.recoup_refresh.old_coin_pub
+ = rrs->coin_pub_old;
+ TALER_planchet_blinding_secret_create (
+ planchet,
+ ewv,
+ &rrs->che_new.details.recoup_refresh.coin_bks);
+ TALER_denom_pub_hash (&denom_pub->key,
+ &h_denom_pub);
+ TALER_wallet_recoup_refresh_sign (
+ &h_denom_pub,
+ &rrs->che_new.details.recoup_refresh.coin_bks,
+ coin_priv,
+ &rrs->che_new.details.recoup_refresh.coin_sig);
+ rrs->ph = TALER_EXCHANGE_recoup_refresh (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ denom_pub,
+ coin_sig,
+ ewv,
+ rplanchet,
+ planchet,
+ idx,
+ &recoup_refresh_cb,
+ rrs);
+ GNUNET_assert (NULL != rrs->ph);
+}
+
+
+/**
+ * Cleanup the "recoup_refresh" CMD state, and possibly cancel
+ * a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+recoup_refresh_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct RecoupRefreshState *rrs = cls;
+ if (NULL != rrs->ph)
+ {
+ TALER_EXCHANGE_recoup_refresh_cancel (rrs->ph);
+ rrs->ph = NULL;
+ }
+ GNUNET_free (rrs);
+}
+
+
+/**
+ * Offer internal data from a "recoup-refresh" CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+recoup_refresh_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct RecoupRefreshState *rrs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_coin_history (0,
+ &rrs->che_old),
+ TALER_TESTING_make_trait_coin_pub (0,
+ &rrs->coin_pub_old),
+ TALER_TESTING_make_trait_coin_history (1,
+ &rrs->che_new),
+ TALER_TESTING_make_trait_coin_pub (1,
+ &rrs->coin_pub_new),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_recoup_refresh (const char *label,
+ unsigned int expected_response_code,
+ const char *coin_reference,
+ const char *melt_reference,
+ const char *amount)
+{
+ struct RecoupRefreshState *rrs;
+
+ rrs = GNUNET_new (struct RecoupRefreshState);
+ rrs->expected_response_code = expected_response_code;
+ rrs->coin_reference = coin_reference;
+ rrs->melt_reference = melt_reference;
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount,
+ &rrs->amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %s\n",
+ amount,
+ label);
+ GNUNET_assert (0);
+ }
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = rrs,
+ .label = label,
+ .run = &recoup_refresh_run,
+ .cleanup = &recoup_refresh_cleanup,
+ .traits = &recoup_refresh_traits
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c
index cfee28dd1..111e9118f 100644
--- a/src/testing/testing_api_cmd_refresh.c
+++ b/src/testing/testing_api_cmd_refresh.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018 Taler Systems SA
+ Copyright (C) 2018-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
@@ -70,10 +70,17 @@ struct TALER_TESTING_FreshCoinData
*/
struct TALER_CoinSpendPrivateKeyP coin_priv;
+ /*
+ * Fresh age commitment for the coin with proof and its hash, NULL if not
+ * applicable.
+ */
+ struct TALER_AgeCommitmentProof *age_commitment_proof;
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
/**
* The blinding key (needed for recoup operations).
*/
- struct TALER_DenominationBlindingKeyP blinding_key;
+ union GNUNET_CRYPTO_BlindingSecretP blinding_key;
};
@@ -91,9 +98,14 @@ struct RefreshMeltState
const char *coin_reference;
/**
- * "Crypto data" used in the refresh operation.
+ * Data used in the refresh operation.
+ */
+ struct TALER_EXCHANGE_RefreshData refresh_data;
+
+ /**
+ * Our command.
*/
- char *refresh_data;
+ const struct TALER_TESTING_Command *cmd;
/**
* Reference to a previous melt command.
@@ -106,6 +118,12 @@ struct RefreshMeltState
struct TALER_EXCHANGE_MeltHandle *rmh;
/**
+ * Expected entry in the coin history created by this
+ * operation.
+ */
+ struct TALER_EXCHANGE_CoinHistoryEntry che;
+
+ /**
* Interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
@@ -117,11 +135,27 @@ struct RefreshMeltState
struct TALER_EXCHANGE_DenomPublicKey *fresh_pks;
/**
+ * Array of @e num_fresh_coins of results from
+ * the melt operation.
+ */
+ struct TALER_EXCHANGE_MeltBlindingDetail *mbds;
+
+ /**
+ * Entropy seed for the refresh-melt operation.
+ */
+ struct TALER_RefreshMasterSecretP rms;
+
+ /**
* Private key of the dirty coin being melted.
*/
const struct TALER_CoinSpendPrivateKeyP *melt_priv;
/**
+ * Public key of the dirty coin being melted.
+ */
+ struct TALER_CoinSpendPublicKeyP melt_pub;
+
+ /**
* Task scheduled to try later.
*/
struct GNUNET_SCHEDULER_Task *retry_task;
@@ -137,11 +171,6 @@ struct RefreshMeltState
struct GNUNET_TIME_Relative total_backoff;
/**
- * Number of bytes in @e refresh_data.
- */
- size_t refresh_data_length;
-
- /**
* Amounts to be generated during melt.
*/
const char **melt_fresh_amounts;
@@ -167,7 +196,7 @@ struct RefreshMeltState
* exchange to pick any previous /rerfesh/melt operation from
* the database.
*/
- unsigned int double_melt;
+ bool double_melt;
/**
* How often should we retry on (transient) failures?
@@ -197,6 +226,11 @@ struct RefreshRevealState
struct TALER_EXCHANGE_RefreshesRevealHandle *rrh;
/**
+ * Our command.
+ */
+ const struct TALER_TESTING_Command *cmd;
+
+ /**
* Convenience struct to keep in one place all the
* data related to one fresh coin, set by the reveal callback
* as it comes from the exchange.
@@ -204,6 +238,12 @@ struct RefreshRevealState
struct TALER_TESTING_FreshCoinData *fresh_coins;
/**
+ * Array of @e num_fresh_coins planchet secrets derived
+ * from the transfer secret per fresh coin.
+ */
+ struct TALER_PlanchetMasterSecretP *psa;
+
+ /**
* Interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
@@ -254,6 +294,11 @@ struct RefreshLinkState
const char *reveal_reference;
/**
+ * Our command.
+ */
+ const struct TALER_TESTING_Command *cmd;
+
+ /**
* Handle to the ongoing operation.
*/
struct TALER_EXCHANGE_LinkHandle *rlh;
@@ -315,8 +360,7 @@ do_reveal_retry (void *cls)
struct RefreshRevealState *rrs = cls;
rrs->retry_task = NULL;
- rrs->is->commands[rrs->is->ip].last_req_time
- = GNUNET_TIME_absolute_get ();
+ TALER_TESTING_touch_cmd (rrs->is);
refresh_reveal_run (rrs,
NULL,
rrs->is);
@@ -328,88 +372,75 @@ do_reveal_retry (void *cls)
* code is expected and copies into its command's state the data
* coming from the exchange, namely the fresh coins.
*
- * @param cls closure.
- * @param http_status HTTP response code.
- * @param ec taler-specific error code.
- * @param num_coins number of fresh coins created, length of the
- * @a sigs and @a coin_privs arrays, 0 if the operation
- * failed.
- * @param coin_privs array of @a num_coins private keys for the
- * coins that were created, NULL on error.
- * @param sigs array of signature over @a num_coins coins,
- * NULL on error.
- * @param full_response raw exchange response.
+ * @param cls closure, a `struct RefreshRevealState`
+ * @param rr HTTP response details
*/
static void
reveal_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- unsigned int num_coins,
- const struct TALER_PlanchetSecretsP *coin_privs,
- const struct TALER_DenominationSignature *sigs,
- const json_t *full_response)
+ const struct TALER_EXCHANGE_RevealResult *rr)
{
struct RefreshRevealState *rrs = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
const struct TALER_TESTING_Command *melt_cmd;
rrs->rrh = NULL;
- if (rrs->expected_response_code != http_status)
+ if (rrs->expected_response_code != hr->http_status)
{
if (0 != rrs->do_retry)
{
rrs->do_retry--;
- if ( (0 == http_status) ||
- (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
- (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
+ if ( (0 == hr->http_status) ||
+ (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec) ||
+ (MHD_HTTP_INTERNAL_SERVER_ERROR == hr->http_status) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Retrying refresh reveal failed with %u/%d\n",
- http_status,
- (int) ec);
+ hr->http_status,
+ (int) hr->ec);
/* on DB conflicts, do not use backoff */
- if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
+ if (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec)
rrs->backoff = GNUNET_TIME_UNIT_ZERO;
else
rrs->backoff = GNUNET_TIME_randomized_backoff (rrs->backoff,
MAX_BACKOFF);
rrs->total_backoff = GNUNET_TIME_relative_add (rrs->total_backoff,
rrs->backoff);
- rrs->is->commands[rrs->is->ip].num_tries++;
+ TALER_TESTING_inc_tries (rrs->is);
rrs->retry_task = GNUNET_SCHEDULER_add_delayed (rrs->backoff,
&do_reveal_retry,
rrs);
return;
}
}
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d to command %s in %s:%u\n",
- http_status,
- (int) ec,
- rrs->is->commands[rrs->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (full_response, stderr, 0);
- TALER_TESTING_interpreter_fail (rrs->is);
+ TALER_TESTING_unexpected_status (rrs->is,
+ hr->http_status,
+ rrs->expected_response_code);
return;
}
- melt_cmd = TALER_TESTING_interpreter_lookup_command
- (rrs->is, rrs->melt_reference);
+ melt_cmd = TALER_TESTING_interpreter_lookup_command (rrs->is,
+ rrs->melt_reference);
if (NULL == melt_cmd)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (rrs->is);
return;
}
- rrs->num_fresh_coins = num_coins;
- switch (http_status)
+ switch (hr->http_status)
{
case MHD_HTTP_OK:
- rrs->fresh_coins = GNUNET_new_array (num_coins,
+ rrs->num_fresh_coins = rr->details.ok.num_coins;
+ rrs->psa = GNUNET_new_array (rrs->num_fresh_coins,
+ struct TALER_PlanchetMasterSecretP);
+ rrs->fresh_coins = GNUNET_new_array (rrs->num_fresh_coins,
struct TALER_TESTING_FreshCoinData);
- for (unsigned int i = 0; i<num_coins; i++)
+ for (unsigned int i = 0; i<rrs->num_fresh_coins; i++)
{
+ const struct TALER_EXCHANGE_RevealedCoinInfo *coin
+ = &rr->details.ok.coins[i];
struct TALER_TESTING_FreshCoinData *fc = &rrs->fresh_coins[i];
+ rrs->psa[i] = coin->ps;
+ fc->blinding_key = coin->bks;
if (GNUNET_OK !=
TALER_TESTING_get_trait_denom_pub (melt_cmd,
i,
@@ -419,24 +450,32 @@ reveal_cb (void *cls,
TALER_TESTING_interpreter_fail (rrs->is);
return;
}
- fc->coin_priv = coin_privs[i].coin_priv;
- fc->blinding_key = coin_privs[i].blinding_key;
- fc->sig.rsa_signature = GNUNET_CRYPTO_rsa_signature_dup
- (sigs[i].rsa_signature);
+ fc->coin_priv = coin->coin_priv;
+
+ if (NULL != coin->age_commitment_proof)
+ {
+ fc->age_commitment_proof =
+ TALER_age_commitment_proof_duplicate (coin->age_commitment_proof);
+ fc->h_age_commitment = coin->h_age_commitment;
+ }
+
+ TALER_denom_sig_copy (&fc->sig,
+ &coin->sig);
}
if (0 != rrs->total_backoff.rel_value_us)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Total reveal backoff for %s was %s\n",
- rrs->is->commands[rrs->is->ip].label,
+ rrs->cmd->label,
GNUNET_STRINGS_relative_time_to_string (rrs->total_backoff,
- GNUNET_YES));
+ true));
}
break;
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unknown HTTP status %d\n",
- http_status);
+ "Unknown HTTP status %u/%d\n",
+ hr->http_status,
+ (int) hr->ec);
}
TALER_TESTING_interpreter_next (rrs->is);
}
@@ -450,6 +489,19 @@ reveal_cb (void *cls,
* @param is the interpreter state.
*/
static void
+melt_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
refresh_reveal_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
@@ -458,6 +510,7 @@ refresh_reveal_run (void *cls,
struct RefreshMeltState *rms;
const struct TALER_TESTING_Command *melt_cmd;
+ rrs->cmd = cmd;
rrs->is = is;
melt_cmd = TALER_TESTING_interpreter_lookup_command (is,
rrs->melt_reference);
@@ -467,14 +520,24 @@ refresh_reveal_run (void *cls,
TALER_TESTING_interpreter_fail (rrs->is);
return;
}
+ GNUNET_assert (melt_cmd->run == &melt_run);
rms = melt_cmd->cls;
- rrs->rrh = TALER_EXCHANGE_refreshes_reveal (is->exchange,
- rms->refresh_data_length,
- rms->refresh_data,
- rms->noreveal_index,
- &reveal_cb,
- rrs);
-
+ {
+ struct TALER_ExchangeWithdrawValues alg_values[rms->num_fresh_coins];
+
+ for (unsigned int i = 0; i<rms->num_fresh_coins; i++)
+ alg_values[i] = rms->mbds[i].alg_value;
+ rrs->rrh = TALER_EXCHANGE_refreshes_reveal (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ &rms->rms,
+ &rms->refresh_data,
+ rms->num_fresh_coins,
+ alg_values,
+ rms->noreveal_index,
+ &reveal_cb,
+ rrs);
+ }
if (NULL == rrs->rrh)
{
GNUNET_break (0);
@@ -497,12 +560,11 @@ refresh_reveal_cleanup (void *cls,
{
struct RefreshRevealState *rrs = cls;
+ (void) cmd;
if (NULL != rrs->rrh)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- rrs->is->ip,
- cmd->label);
+ TALER_TESTING_command_incomplete (rrs->is,
+ cmd->label);
TALER_EXCHANGE_refreshes_reveal_cancel (rrs->rrh);
rrs->rrh = NULL;
}
@@ -513,10 +575,14 @@ refresh_reveal_cleanup (void *cls,
}
for (unsigned int j = 0; j < rrs->num_fresh_coins; j++)
- GNUNET_CRYPTO_rsa_signature_free (rrs->fresh_coins[j].sig.rsa_signature);
+ {
+ TALER_denom_sig_free (&rrs->fresh_coins[j].sig);
+ TALER_age_commitment_proof_free (rrs->fresh_coins[j].age_commitment_proof);
+ GNUNET_free (rrs->fresh_coins[j].age_commitment_proof);
+ }
- GNUNET_free_non_null (rrs->fresh_coins);
- rrs->fresh_coins = NULL;
+ GNUNET_free (rrs->fresh_coins);
+ GNUNET_free (rrs->psa);
rrs->num_fresh_coins = 0;
GNUNET_free (rrs);
}
@@ -546,8 +612,7 @@ do_link_retry (void *cls)
struct RefreshLinkState *rls = cls;
rls->retry_task = NULL;
- rls->is->commands[rls->is->ip].last_req_time
- = GNUNET_TIME_absolute_get ();
+ TALER_TESTING_touch_cmd (rls->is);
refresh_link_run (rls,
NULL,
rls->is);
@@ -560,80 +625,54 @@ do_link_retry (void *cls)
* withdrawn by the "refresh reveal" CMD.
*
* @param cls closure.
- * @param http_status HTTP response code.
- * @param ec taler-specific error code
- * @param num_coins number of fresh coins created, length of the
- * @a sigs and @a coin_privs arrays, 0 if the operation
- * failed.
- * @param coin_privs array of @a num_coins private keys for the
- * coins that were created, NULL on error.
- * @param sigs array of signature over @a num_coins coins, NULL on
- * error.
- * @param pubs array of public keys for the @a sigs,
- * NULL on error.
- * @param full_response raw response from the exchange.
+ * @param lr HTTP response details
*/
static void
link_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- unsigned int num_coins,
- const struct TALER_CoinSpendPrivateKeyP *coin_privs,
- const struct TALER_DenominationSignature *sigs,
- const struct TALER_DenominationPublicKey *pubs,
- const json_t *full_response)
+ const struct TALER_EXCHANGE_LinkResult *lr)
{
-
struct RefreshLinkState *rls = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &lr->hr;
const struct TALER_TESTING_Command *reveal_cmd;
- struct TALER_TESTING_Command *link_cmd
- = &rls->is->commands[rls->is->ip];
unsigned int found;
const unsigned int *num_fresh_coins;
rls->rlh = NULL;
- if (rls->expected_response_code != http_status)
+ if (rls->expected_response_code != hr->http_status)
{
if (0 != rls->do_retry)
{
rls->do_retry--;
- if ( (0 == http_status) ||
- (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
- (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
+ if ( (0 == hr->http_status) ||
+ (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec) ||
+ (MHD_HTTP_INTERNAL_SERVER_ERROR == hr->http_status) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Retrying refresh link failed with %u/%d\n",
- http_status,
- (int) ec);
+ hr->http_status,
+ (int) hr->ec);
/* on DB conflicts, do not use backoff */
- if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
+ if (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec)
rls->backoff = GNUNET_TIME_UNIT_ZERO;
else
rls->backoff = GNUNET_TIME_randomized_backoff (rls->backoff,
MAX_BACKOFF);
rls->total_backoff = GNUNET_TIME_relative_add (rls->total_backoff,
rls->backoff);
- rls->is->commands[rls->is->ip].num_tries++;
+ TALER_TESTING_inc_tries (rls->is);
rls->retry_task = GNUNET_SCHEDULER_add_delayed (rls->backoff,
&do_link_retry,
rls);
return;
}
}
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d to command %s in %s:%u\n",
- http_status,
- (int) ec,
- link_cmd->label,
- __FILE__,
- __LINE__);
- json_dumpf (full_response, stderr, 0);
- TALER_TESTING_interpreter_fail (rls->is);
+ TALER_TESTING_unexpected_status (rls->is,
+ hr->http_status,
+ rls->expected_response_code);
return;
}
- reveal_cmd = TALER_TESTING_interpreter_lookup_command
- (rls->is, rls->reveal_reference);
-
+ reveal_cmd = TALER_TESTING_interpreter_lookup_command (rls->is,
+ rls->reveal_reference);
if (NULL == reveal_cmd)
{
GNUNET_break (0);
@@ -641,22 +680,23 @@ link_cb (void *cls,
return;
}
- switch (http_status)
+ switch (hr->http_status)
{
case MHD_HTTP_OK:
/* check that number of coins returned matches */
- if (GNUNET_OK != TALER_TESTING_get_trait_uint
- (reveal_cmd, 0, &num_fresh_coins))
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_array_length (reveal_cmd,
+ &num_fresh_coins))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (rls->is);
return;
}
- if (num_coins != *num_fresh_coins)
+ if (lr->details.ok.num_coins != *num_fresh_coins)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected number of fresh coins: %d vs %d in %s:%u\n",
- num_coins,
+ lr->details.ok.num_coins,
*num_fresh_coins,
__FILE__,
__LINE__);
@@ -664,46 +704,60 @@ link_cb (void *cls,
return;
}
/* check that the coins match */
- for (unsigned int i = 0; i<num_coins; i++)
- for (unsigned int j = i + 1; j<num_coins; j++)
- if (0 == GNUNET_memcmp
- (&coin_privs[i], &coin_privs[j]))
+ for (unsigned int i = 0; i<lr->details.ok.num_coins; i++)
+ for (unsigned int j = i + 1; j<lr->details.ok.num_coins; j++)
+ if (0 ==
+ GNUNET_memcmp (&lr->details.ok.coins[i].coin_priv,
+ &lr->details.ok.coins[j].coin_priv))
GNUNET_break (0);
/* Note: coins might be legitimately permutated in here... */
found = 0;
/* Will point to the pointer inside the cmd state. */
- const struct TALER_TESTING_FreshCoinData *fc = NULL;
-
- if (GNUNET_OK != TALER_TESTING_get_trait_fresh_coins
- (reveal_cmd, 0, &fc))
{
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (rls->is);
- return;
- }
+ const struct TALER_TESTING_FreshCoinData **fc = NULL;
- for (unsigned int i = 0; i<num_coins; i++)
- for (unsigned int j = 0; j<num_coins; j++)
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_fresh_coins (reveal_cmd,
+ &fc))
{
- if ( (0 == GNUNET_memcmp
- (&coin_privs[i], &fc[j].coin_priv)) &&
- (0 == GNUNET_CRYPTO_rsa_signature_cmp
- (fc[i].sig.rsa_signature,
- sigs[j].rsa_signature)) &&
- (0 == GNUNET_CRYPTO_rsa_public_key_cmp
- (fc[i].pk->key.rsa_public_key,
- pubs[j].rsa_public_key)) )
- {
- found++;
- break;
- }
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
}
- if (found != num_coins)
+
+ for (unsigned int i = 0; i<lr->details.ok.num_coins; i++)
+ {
+ const struct TALER_EXCHANGE_LinkedCoinInfo *lci_i
+ = &lr->details.ok.coins[i];
+
+ for (unsigned int j = 0; j<lr->details.ok.num_coins; j++)
+ {
+ const struct TALER_TESTING_FreshCoinData *fcj
+ = &(*fc)[j];
+
+ if ( (0 ==
+ GNUNET_memcmp (&fcj->coin_priv,
+ &lci_i->coin_priv)) &&
+ (0 ==
+ TALER_denom_sig_cmp (&fcj->sig,
+ &lci_i->sig)) &&
+ (0 ==
+ TALER_denom_pub_cmp (&fcj->pk->key,
+ &lci_i->pub)) )
+ {
+ found++;
+ break;
+ }
+ } /* for j*/
+ } /* for i */
+ }
+ if (found != lr->details.ok.num_coins)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Only %u/%u coins match expectations\n",
- found, num_coins);
+ found,
+ lr->details.ok.num_coins);
GNUNET_break (0);
TALER_TESTING_interpreter_fail (rls->is);
return;
@@ -712,15 +766,16 @@ link_cb (void *cls,
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Total link backoff for %s was %s\n",
- rls->is->commands[rls->is->ip].label,
+ rls->cmd->label,
GNUNET_STRINGS_relative_time_to_string (rls->total_backoff,
- GNUNET_YES));
+ true));
}
break;
default:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unknown HTTP response code %u.\n",
- http_status);
+ "Unknown HTTP response code %u/%d.\n",
+ hr->http_status,
+ hr->ec);
}
TALER_TESTING_interpreter_next (rls->is);
}
@@ -744,11 +799,18 @@ refresh_link_run (void *cls,
const struct TALER_TESTING_Command *reveal_cmd;
const struct TALER_TESTING_Command *melt_cmd;
const struct TALER_TESTING_Command *coin_cmd;
+ const char *exchange_url;
+ rls->cmd = cmd;
rls->is = is;
- reveal_cmd = TALER_TESTING_interpreter_lookup_command
- (rls->is, rls->reveal_reference);
-
+ exchange_url = TALER_TESTING_get_exchange_url (is);
+ if (NULL == exchange_url)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ reveal_cmd = TALER_TESTING_interpreter_lookup_command (rls->is,
+ rls->reveal_reference);
if (NULL == reveal_cmd)
{
GNUNET_break (0);
@@ -756,9 +818,8 @@ refresh_link_run (void *cls,
return;
}
rrs = reveal_cmd->cls;
- melt_cmd = TALER_TESTING_interpreter_lookup_command
- (rls->is, rrs->melt_reference);
-
+ melt_cmd = TALER_TESTING_interpreter_lookup_command (rls->is,
+ rrs->melt_reference);
if (NULL == melt_cmd)
{
GNUNET_break (0);
@@ -767,21 +828,22 @@ refresh_link_run (void *cls,
}
/* find reserve_withdraw command */
+ GNUNET_assert (melt_cmd->run == &melt_run);
+ rms = melt_cmd->cls;
+ coin_cmd = TALER_TESTING_interpreter_lookup_command (rls->is,
+ rms->coin_reference);
+ if (NULL == coin_cmd)
{
- rms = melt_cmd->cls;
- coin_cmd = TALER_TESTING_interpreter_lookup_command
- (rls->is, rms->coin_reference);
- if (NULL == coin_cmd)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (rls->is);
- return;
- }
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
}
const struct TALER_CoinSpendPrivateKeyP *coin_priv;
- if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv
- (coin_cmd, 0, &coin_priv))
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (coin_cmd,
+ 0,
+ &coin_priv))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (rls->is);
@@ -789,8 +851,13 @@ refresh_link_run (void *cls,
}
/* finally, use private key from withdraw sign command */
- rls->rlh = TALER_EXCHANGE_link
- (is->exchange, coin_priv, &link_cb, rls);
+ rls->rlh = TALER_EXCHANGE_link (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ coin_priv,
+ rms->refresh_data.melt_age_commitment_proof,
+ &link_cb,
+ rls);
if (NULL == rls->rlh)
{
@@ -816,11 +883,8 @@ refresh_link_cleanup (void *cls,
if (NULL != rls->rlh)
{
-
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- rls->is->ip,
- cmd->label);
+ TALER_TESTING_command_incomplete (rls->is,
+ cmd->label);
TALER_EXCHANGE_link_cancel (rls->rlh);
rls->rlh = NULL;
}
@@ -834,19 +898,6 @@ refresh_link_cleanup (void *cls,
/**
- * Run the command.
- *
- * @param cls closure.
- * @param cmd the command to execute.
- * @param is the interpreter state.
- */
-static void
-melt_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is);
-
-
-/**
* Task scheduled to re-try #melt_run.
*
* @param cls a `struct RefreshMeltState`
@@ -857,8 +908,7 @@ do_melt_retry (void *cls)
struct RefreshMeltState *rms = cls;
rms->retry_task = NULL;
- rms->is->commands[rms->is->ip].last_req_time
- = GNUNET_TIME_absolute_get ();
+ TALER_TESTING_touch_cmd (rms->is);
melt_run (rms,
NULL,
rms->is);
@@ -871,82 +921,88 @@ do_melt_retry (void *cls)
* CMD was set to do so.
*
* @param cls closure.
- * @param http_status HTTP response code.
- * @param ec taler-specific error code.
- * @param noreveal_index choice by the exchange in the
- * cut-and-choose protocol, UINT16_MAX on error.
- * @param exchange_pub public key the exchange used for signing.
- * @param full_response raw response body from the exchange.
+ * @param mr melt response details
*/
static void
melt_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- uint32_t noreveal_index,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const json_t *full_response)
+ const struct TALER_EXCHANGE_MeltResponse *mr)
{
struct RefreshMeltState *rms = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &mr->hr;
rms->rmh = NULL;
- if (rms->expected_response_code != http_status)
+ if (rms->expected_response_code != hr->http_status)
{
if (0 != rms->do_retry)
{
rms->do_retry--;
- if ( (0 == http_status) ||
- (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
- (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
+ if ( (0 == hr->http_status) ||
+ (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec) ||
+ (MHD_HTTP_INTERNAL_SERVER_ERROR == hr->http_status) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Retrying refresh melt failed with %u/%d\n",
- http_status,
- (int) ec);
+ hr->http_status,
+ (int) hr->ec);
/* on DB conflicts, do not use backoff */
- if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
+ if (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec)
rms->backoff = GNUNET_TIME_UNIT_ZERO;
else
rms->backoff = GNUNET_TIME_randomized_backoff (rms->backoff,
MAX_BACKOFF);
rms->total_backoff = GNUNET_TIME_relative_add (rms->total_backoff,
rms->backoff);
- rms->is->commands[rms->is->ip].num_tries++;
- rms->retry_task = GNUNET_SCHEDULER_add_delayed
- (rms->backoff,
- &do_melt_retry,
- rms);
+ TALER_TESTING_inc_tries (rms->is);
+ rms->retry_task = GNUNET_SCHEDULER_add_delayed (rms->backoff,
+ &do_melt_retry,
+ rms);
return;
}
}
- GNUNET_log
- (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d to command %s in %s:%u\n",
- http_status,
- (int) ec,
- rms->is->commands[rms->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (full_response, stderr, 0);
- TALER_TESTING_interpreter_fail (rms->is);
+ TALER_TESTING_unexpected_status_with_body (rms->is,
+ hr->http_status,
+ rms->expected_response_code,
+ hr->reply);
return;
}
- rms->noreveal_index = noreveal_index;
+ if (MHD_HTTP_OK == hr->http_status)
+ {
+ rms->noreveal_index = mr->details.ok.noreveal_index;
+ if (mr->details.ok.num_mbds != rms->num_fresh_coins)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+ GNUNET_free (rms->mbds);
+ rms->mbds = GNUNET_new_array (
+ mr->details.ok.num_mbds,
+ struct TALER_EXCHANGE_MeltBlindingDetail);
+ for (unsigned int i = 0; i<mr->details.ok.num_mbds; i++)
+ TALER_denom_ewv_copy (&rms->mbds[i].alg_value,
+ &mr->details.ok.mbds[i].alg_value);
+ }
if (0 != rms->total_backoff.rel_value_us)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Total melt backoff for %s was %s\n",
- rms->is->commands[rms->is->ip].label,
+ rms->cmd->label,
GNUNET_STRINGS_relative_time_to_string (rms->total_backoff,
- GNUNET_YES));
+ true));
}
- if (GNUNET_YES == rms->double_melt)
+ if (rms->double_melt)
{
TALER_LOG_DEBUG ("Doubling the melt (%s)\n",
- rms->is->commands[rms->is->ip].label);
- rms->rmh = TALER_EXCHANGE_melt
- (rms->is->exchange, rms->refresh_data_length,
- rms->refresh_data, &melt_cb, rms);
- rms->double_melt = GNUNET_NO;
+ rms->cmd->label);
+ rms->rmh = TALER_EXCHANGE_melt (
+ TALER_TESTING_interpreter_get_context (rms->is),
+ TALER_TESTING_get_exchange_url (rms->is),
+ TALER_TESTING_get_keys (rms->is),
+ &rms->rms,
+ &rms->refresh_data,
+ &melt_cb,
+ rms);
+ rms->double_melt = false;
return;
}
TALER_TESTING_interpreter_next (rms->is);
@@ -973,36 +1029,53 @@ melt_run (void *cls,
};
const char **melt_fresh_amounts;
+ rms->cmd = cmd;
if (NULL == (melt_fresh_amounts = rms->melt_fresh_amounts))
melt_fresh_amounts = default_melt_fresh_amounts;
rms->is = is;
rms->noreveal_index = UINT16_MAX;
+ TALER_refresh_master_setup_random (&rms->rms);
for (num_fresh_coins = 0;
NULL != melt_fresh_amounts[num_fresh_coins];
num_fresh_coins++)
;
rms->num_fresh_coins = num_fresh_coins;
- rms->fresh_pks = GNUNET_new_array
- (num_fresh_coins,
- struct TALER_EXCHANGE_DenomPublicKey);
+ rms->fresh_pks = GNUNET_new_array (
+ num_fresh_coins,
+ struct TALER_EXCHANGE_DenomPublicKey);
{
struct TALER_Amount melt_amount;
struct TALER_Amount fresh_amount;
+ const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
+ const struct TALER_AgeCommitmentHash *h_age_commitment = NULL;
const struct TALER_DenominationSignature *melt_sig;
const struct TALER_EXCHANGE_DenomPublicKey *melt_denom_pub;
const struct TALER_TESTING_Command *coin_command;
+ bool age_restricted_denom;
if (NULL == (coin_command
- = TALER_TESTING_interpreter_lookup_command
- (is, rms->coin_reference)))
+ = TALER_TESTING_interpreter_lookup_command (
+ is,
+ rms->coin_reference)))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (rms->is);
return;
}
- if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv
- (coin_command, 0, &rms->melt_priv))
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (coin_command,
+ 0,
+ &rms->melt_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_age_commitment_proof (coin_command,
+ 0,
+ &age_commitment_proof))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (rms->is);
@@ -1010,6 +1083,15 @@ melt_run (void *cls,
}
if (GNUNET_OK !=
+ TALER_TESTING_get_trait_h_age_commitment (coin_command,
+ 0,
+ &h_age_commitment))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+ if (GNUNET_OK !=
TALER_TESTING_get_trait_denom_sig (coin_command,
0,
&melt_sig))
@@ -1018,32 +1100,42 @@ melt_run (void *cls,
TALER_TESTING_interpreter_fail (rms->is);
return;
}
- if (GNUNET_OK != TALER_TESTING_get_trait_denom_pub
- (coin_command, 0, &melt_denom_pub))
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_pub (coin_command,
+ 0,
+ &melt_denom_pub))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (rms->is);
return;
}
+
/* Melt amount starts with the melt fee of the old coin; we'll add the
values and withdraw fees of the fresh coins next */
- melt_amount = melt_denom_pub->fee_refresh;
+ melt_amount = melt_denom_pub->fees.refresh;
+ age_restricted_denom = melt_denom_pub->key.age_mask.bits != 0;
+ GNUNET_assert (age_restricted_denom == (NULL != age_commitment_proof));
+ GNUNET_assert ((NULL == age_commitment_proof) ||
+ (0 < age_commitment_proof->commitment.num));
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk;
- if (GNUNET_OK != TALER_string_to_amount
- (melt_fresh_amounts[i], &fresh_amount))
+ if (GNUNET_OK !=
+ TALER_string_to_amount (melt_fresh_amounts[i],
+ &fresh_amount))
{
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to parse amount `%s' at index %u\n",
- melt_fresh_amounts[i], i);
+ melt_fresh_amounts[i],
+ i);
TALER_TESTING_interpreter_fail (rms->is);
return;
}
- fresh_pk = TALER_TESTING_find_pk
- (TALER_EXCHANGE_get_keys (is->exchange), &fresh_amount);
+ fresh_pk = TALER_TESTING_find_pk (TALER_TESTING_get_keys (rms->is),
+ &fresh_amount,
+ age_restricted_denom);
if (NULL == fresh_pk)
{
GNUNET_break (0);
@@ -1051,39 +1143,56 @@ melt_run (void *cls,
TALER_TESTING_interpreter_fail (rms->is);
return;
}
- GNUNET_assert (GNUNET_OK ==
+ GNUNET_assert (0 <=
TALER_amount_add (&melt_amount,
&melt_amount,
&fresh_amount));
- GNUNET_assert (GNUNET_OK ==
+ GNUNET_assert (0 <=
TALER_amount_add (&melt_amount,
&melt_amount,
- &fresh_pk->fee_withdraw));
+ &fresh_pk->fees.withdraw));
rms->fresh_pks[i] = *fresh_pk;
/* Make a deep copy of the RSA key */
- rms->fresh_pks[i].key.rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_dup (fresh_pk->key.rsa_public_key);
- }
- rms->refresh_data
- = TALER_EXCHANGE_refresh_prepare (rms->melt_priv,
- &melt_amount,
- melt_sig,
- melt_denom_pub,
- num_fresh_coins,
- rms->fresh_pks,
- &rms->refresh_data_length);
-
- if (NULL == rms->refresh_data)
+ TALER_denom_pub_copy (&rms->fresh_pks[i].key,
+ &fresh_pk->key);
+ } /* end for */
+
+ rms->refresh_data.melt_priv = *rms->melt_priv;
+ GNUNET_CRYPTO_eddsa_key_get_public (&rms->melt_priv->eddsa_priv,
+ &rms->melt_pub.eddsa_pub);
+ rms->refresh_data.melt_amount = melt_amount;
+ rms->refresh_data.melt_sig = *melt_sig;
+ rms->refresh_data.melt_pk = *melt_denom_pub;
+
+ if (NULL != age_commitment_proof)
{
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (rms->is);
- return;
+ GNUNET_assert (NULL != h_age_commitment);
+ rms->refresh_data.melt_age_commitment_proof = age_commitment_proof;
+ rms->refresh_data.melt_h_age_commitment = h_age_commitment;
}
- rms->rmh = TALER_EXCHANGE_melt (is->exchange,
- rms->refresh_data_length,
- rms->refresh_data,
- &melt_cb,
- rms);
+ rms->refresh_data.fresh_pks = rms->fresh_pks;
+ rms->refresh_data.fresh_pks_len = num_fresh_coins;
+
+ GNUNET_assert (age_restricted_denom ==
+ (NULL != age_commitment_proof));
+ GNUNET_assert ((NULL == age_commitment_proof) ||
+ (0 < age_commitment_proof->commitment.num));
+
+ rms->che.type = TALER_EXCHANGE_CTT_MELT;
+ rms->che.amount = melt_amount;
+ if (NULL != age_commitment_proof)
+ rms->che.details.melt.h_age_commitment = *h_age_commitment;
+ else
+ rms->che.details.melt.no_hac = true;
+
+ rms->rmh = TALER_EXCHANGE_melt (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ &rms->rms,
+ &rms->refresh_data,
+ &melt_cb,
+ rms);
if (NULL == rms->rmh)
{
@@ -1108,11 +1217,11 @@ melt_cleanup (void *cls,
{
struct RefreshMeltState *rms = cls;
+ (void) cmd;
if (NULL != rms->rmh)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- rms->is->ip, rms->is->commands[rms->is->ip].label);
+ TALER_TESTING_command_incomplete (rms->is,
+ cmd->label);
TALER_EXCHANGE_melt_cancel (rms->rmh);
rms->rmh = NULL;
}
@@ -1124,14 +1233,16 @@ melt_cleanup (void *cls,
if (NULL != rms->fresh_pks)
{
for (unsigned int i = 0; i < rms->num_fresh_coins; i++)
- GNUNET_CRYPTO_rsa_public_key_free (rms->fresh_pks[i].key.rsa_public_key);
+ TALER_denom_pub_free (&rms->fresh_pks[i].key);
+ GNUNET_free (rms->fresh_pks);
}
- GNUNET_free_non_null (rms->fresh_pks);
- rms->fresh_pks = NULL;
- GNUNET_free_non_null (rms->refresh_data);
- rms->refresh_data = NULL;
- rms->refresh_data_length = 0;
- GNUNET_free_non_null (rms->melt_fresh_amounts);
+ if (NULL != rms->mbds)
+ {
+ for (unsigned int i = 0; i < rms->num_fresh_coins; i++)
+ TALER_denom_ewv_free (&rms->mbds[i].alg_value);
+ GNUNET_free (rms->mbds);
+ }
+ GNUNET_free (rms->melt_fresh_amounts);
GNUNET_free (rms);
}
@@ -1145,7 +1256,7 @@ melt_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success.
*/
-static int
+static enum GNUNET_GenericReturnValue
melt_traits (void *cls,
const void **ret,
const char *trait,
@@ -1160,8 +1271,25 @@ melt_traits (void *cls,
}
{
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_denom_pub (index, &rms->fresh_pks[index]),
- TALER_TESTING_make_trait_coin_priv (0, rms->melt_priv),
+ TALER_TESTING_make_trait_denom_pub (index,
+ &rms->fresh_pks[index]),
+ TALER_TESTING_make_trait_coin_priv (0,
+ rms->melt_priv),
+ TALER_TESTING_make_trait_coin_pub (0,
+ &rms->melt_pub),
+ TALER_TESTING_make_trait_coin_history (0,
+ &rms->che),
+ TALER_TESTING_make_trait_age_commitment_proof (
+ index,
+ rms->refresh_data.melt_age_commitment_proof),
+ TALER_TESTING_make_trait_h_age_commitment (
+ index,
+ rms->refresh_data.melt_h_age_commitment),
+ TALER_TESTING_make_trait_refresh_secret (&rms->rms),
+ (NULL != rms->mbds)
+ ? TALER_TESTING_make_trait_exchange_wd_value (index,
+ &rms->mbds[index].alg_value)
+ : TALER_TESTING_trait_end (),
TALER_TESTING_trait_end ()
};
@@ -1180,7 +1308,7 @@ melt_traits (void *cls,
* @param ap NULL-termianted list of amounts to be melted (one per fresh coin)
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
parse_amounts (struct RefreshMeltState *rms,
va_list ap)
{
@@ -1223,16 +1351,6 @@ parse_amounts (struct RefreshMeltState *rms,
}
-/**
- * Create a "refresh melt" command.
- *
- * @param label command label.
- * @param coin_reference reference to a command
- * that will provide a coin to refresh.
- * @param expected_response_code expected HTTP code.
- * @param ... NULL-terminated list of amounts to be melted
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_melt (const char *label,
const char *coin_reference,
@@ -1245,7 +1363,8 @@ TALER_TESTING_cmd_melt (const char *label,
rms = GNUNET_new (struct RefreshMeltState);
rms->coin_reference = coin_reference;
rms->expected_response_code = expected_response_code;
- va_start (ap, expected_response_code);
+ va_start (ap,
+ expected_response_code);
GNUNET_assert (GNUNET_OK ==
parse_amounts (rms, ap));
va_end (ap);
@@ -1263,18 +1382,6 @@ TALER_TESTING_cmd_melt (const char *label,
}
-/**
- * Create a "refresh melt" CMD that does TWO /refresh/melt
- * requests. This was needed to test the replay of a valid melt
- * request, see #5312.
- *
- * @param label command label
- * @param coin_reference reference to a command that will provide
- * a coin to refresh
- * @param expected_response_code expected HTTP code
- * @param ... NULL-terminated list of amounts to be melted
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_melt_double (const char *label,
const char *coin_reference,
@@ -1287,8 +1394,9 @@ TALER_TESTING_cmd_melt_double (const char *label,
rms = GNUNET_new (struct RefreshMeltState);
rms->coin_reference = coin_reference;
rms->expected_response_code = expected_response_code;
- rms->double_melt = GNUNET_YES;
- va_start (ap, expected_response_code);
+ rms->double_melt = true;
+ va_start (ap,
+ expected_response_code);
GNUNET_assert (GNUNET_OK ==
parse_amounts (rms, ap));
va_end (ap);
@@ -1306,12 +1414,6 @@ TALER_TESTING_cmd_melt_double (const char *label,
}
-/**
- * Modify a "refresh melt" command to enable retries.
- *
- * @param cmd command
- * @return modified command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_melt_with_retry (struct TALER_TESTING_Command cmd)
{
@@ -1333,65 +1435,54 @@ TALER_TESTING_cmd_melt_with_retry (struct TALER_TESTING_Command cmd)
* @param index index number of the object to offer.
* @return #GNUNET_OK on success.
*/
-static int
+static enum GNUNET_GenericReturnValue
refresh_reveal_traits (void *cls,
const void **ret,
const char *trait,
unsigned int index)
{
struct RefreshRevealState *rrs = cls;
- unsigned int num_coins = rrs->num_fresh_coins;
-#define NUM_TRAITS ((num_coins * 4) + 3)
- struct TALER_TESTING_Trait traits[NUM_TRAITS];
-
- /* Making coin privs traits */
- for (unsigned int i = 0; i<num_coins; i++)
- traits[i] = TALER_TESTING_make_trait_coin_priv
- (i, &rrs->fresh_coins[i].coin_priv);
-
- /* Making denom pubs traits */
- for (unsigned int i = 0; i<num_coins; i++)
- traits[num_coins + i]
- = TALER_TESTING_make_trait_denom_pub
- (i, rrs->fresh_coins[i].pk);
-
- /* Making denom sigs traits */
- for (unsigned int i = 0; i<num_coins; i++)
- traits[(num_coins * 2) + i]
- = TALER_TESTING_make_trait_denom_sig
- (i, &rrs->fresh_coins[i].sig);
- /* blinding key traits */
- for (unsigned int i = 0; i<num_coins; i++)
- traits[(num_coins * 3) + i]
- = TALER_TESTING_make_trait_blinding_key (i,
- &rrs->fresh_coins[i].blinding_key),
-
- /* number of fresh coins */
- traits[(num_coins * 4)] = TALER_TESTING_make_trait_uint
- (0, &rrs->num_fresh_coins);
-
- /* whole array of fresh coins */
- traits[(num_coins * 4) + 1]
- = TALER_TESTING_make_trait_fresh_coins (0, rrs->fresh_coins),
-
- /* end of traits */
- traits[(num_coins * 4) + 2] = TALER_TESTING_trait_end ();
-
- return TALER_TESTING_get_trait (traits,
- ret,
- trait,
- index);
+
+ if (index >= rrs->num_fresh_coins)
+ return GNUNET_SYSERR;
+
+ {
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_coin_priv (
+ index,
+ &rrs->fresh_coins[index].coin_priv),
+ TALER_TESTING_make_trait_age_commitment_proof (
+ index,
+ rrs->fresh_coins[index].age_commitment_proof),
+ TALER_TESTING_make_trait_h_age_commitment (
+ index,
+ &rrs->fresh_coins[index].h_age_commitment),
+ TALER_TESTING_make_trait_denom_pub (
+ index,
+ rrs->fresh_coins[index].pk),
+ TALER_TESTING_make_trait_denom_sig (
+ index,
+ &rrs->fresh_coins[index].sig),
+ TALER_TESTING_make_trait_blinding_key (
+ index,
+ &rrs->fresh_coins[index].blinding_key),
+ TALER_TESTING_make_trait_array_length (
+ &rrs->num_fresh_coins),
+ TALER_TESTING_make_trait_fresh_coins (
+ (const struct TALER_TESTING_FreshCoinData **) &rrs->fresh_coins),
+ TALER_TESTING_make_trait_planchet_secrets (index,
+ &rrs->psa[index]),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+ }
}
-/**
- * Create a "refresh reveal" command.
- *
- * @param label command label.
- * @param melt_reference reference to a "refresh melt" command.
- * @param expected_response_code expected HTTP response code.
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_refresh_reveal (const char *label,
const char *melt_reference,
@@ -1416,12 +1507,6 @@ TALER_TESTING_cmd_refresh_reveal (const char *label,
}
-/**
- * Modify a "refresh reveal" command to enable retries.
- *
- * @param cmd command
- * @return modified command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_refresh_reveal_with_retry (struct TALER_TESTING_Command cmd)
{
@@ -1434,14 +1519,6 @@ TALER_TESTING_cmd_refresh_reveal_with_retry (struct TALER_TESTING_Command cmd)
}
-/**
- * Create a "refresh link" command.
- *
- * @param label command label.
- * @param reveal_reference reference to a "refresh reveal" CMD.
- * @param expected_response_code expected HTTP response code
- * @return the "refresh link" command
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_refresh_link (const char *label,
const char *reveal_reference,
@@ -1465,12 +1542,6 @@ TALER_TESTING_cmd_refresh_link (const char *label,
}
-/**
- * Modify a "refresh link" command to enable retries.
- *
- * @param cmd command
- * @return modified command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_refresh_link_with_retry (struct TALER_TESTING_Command cmd)
{
diff --git a/src/testing/testing_api_cmd_refund.c b/src/testing/testing_api_cmd_refund.c
index 0150086e0..29b68ef08 100644
--- a/src/testing/testing_api_cmd_refund.c
+++ b/src/testing/testing_api_cmd_refund.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 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
@@ -44,11 +44,6 @@ struct RefundState
const char *refund_amount;
/**
- * Expected refund fee.
- */
- const char *refund_fee;
-
- /**
* Reference to any command that can provide a coin to refund.
*/
const char *coin_reference;
@@ -59,9 +54,14 @@ struct RefundState
uint64_t refund_transaction_id;
/**
- * Connection to the exchange.
+ * Entry in the coin's history generated by this operation.
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_CoinHistoryEntry che;
+
+ /**
+ * Public key of the refunded coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin;
/**
* Handle to the refund operation.
@@ -80,37 +80,51 @@ struct RefundState
* response code is acceptable.
*
* @param cls closure
- * @param http_status HTTP response code.
- * @param ec taler-specific error code.
- * @param exchange_pub public key the exchange
- * used for signing @a obj.
- * @param obj response object.
+ * @param rr response details
*/
static void
refund_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const json_t *obj)
+ const struct TALER_EXCHANGE_RefundResponse *rr)
{
-
struct RefundState *rs = cls;
- struct TALER_TESTING_Command *refund_cmd;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
- refund_cmd = &rs->is->commands[rs->is->ip];
rs->rh = NULL;
- if (rs->expected_response_code != http_status)
+ if (rs->expected_response_code != hr->http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- http_status,
- refund_cmd->label,
- __FILE__,
- __LINE__);
- json_dumpf (obj, stderr, 0);
- TALER_TESTING_interpreter_fail (rs->is);
+ TALER_TESTING_unexpected_status (rs->is,
+ hr->http_status,
+ rs->expected_response_code);
return;
}
+ if (MHD_HTTP_OK == hr->http_status)
+ {
+ struct TALER_Amount refund_amount;
+
+ if (GNUNET_OK !=
+ TALER_string_to_amount (rs->refund_amount,
+ &refund_amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s'\n",
+ rs->refund_amount);
+ TALER_TESTING_interpreter_fail (rs->is);
+ return;
+ }
+ if (0 >
+ TALER_amount_subtract (&rs->che.amount,
+ &refund_amount,
+ &rs->che.details.refund.refund_fee))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to subtract %s from %s\n",
+ TALER_amount2s (&rs->che.details.refund.refund_fee),
+ rs->refund_amount);
+ TALER_TESTING_interpreter_fail (rs->is);
+ return;
+ }
+ }
TALER_TESTING_interpreter_next (rs->is);
}
@@ -129,42 +143,25 @@ refund_run (void *cls,
{
struct RefundState *rs = cls;
const struct TALER_CoinSpendPrivateKeyP *coin_priv;
- struct TALER_CoinSpendPublicKeyP coin;
const json_t *contract_terms;
- struct GNUNET_HashCode h_contract_terms;
- struct TALER_Amount refund_fee;
+ struct TALER_PrivateContractHashP h_contract_terms;
struct TALER_Amount refund_amount;
const struct TALER_MerchantPrivateKeyP *merchant_priv;
const struct TALER_TESTING_Command *coin_cmd;
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
- rs->exchange = is->exchange;
rs->is = is;
-
if (GNUNET_OK !=
TALER_string_to_amount (rs->refund_amount,
&refund_amount))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse amount `%s' at %u/%s\n",
+ "Failed to parse amount `%s' at %s\n",
rs->refund_amount,
- is->ip,
- cmd->label);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- if (GNUNET_OK !=
- TALER_string_to_amount (rs->refund_fee,
- &refund_fee))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse amount `%s' at %u/%s\n",
- rs->refund_fee,
- is->ip,
cmd->label);
TALER_TESTING_interpreter_fail (is);
return;
}
-
coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
rs->coin_reference);
if (NULL == coin_cmd)
@@ -175,7 +172,6 @@ refund_run (void *cls,
}
if (GNUNET_OK !=
TALER_TESTING_get_trait_contract_terms (coin_cmd,
- 0,
&contract_terms))
{
GNUNET_break (0);
@@ -183,14 +179,19 @@ refund_run (void *cls,
return;
}
GNUNET_assert (GNUNET_OK ==
- TALER_JSON_hash (contract_terms,
- &h_contract_terms));
+ TALER_JSON_contract_hash (contract_terms,
+ &h_contract_terms));
/* Hunting for a coin .. */
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_coin_priv (coin_cmd,
- 0,
- &coin_priv))
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (coin_cmd,
+ 0,
+ &coin_priv)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_pub (coin_cmd,
+ 0,
+ &denom_pub)) )
+
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
@@ -198,30 +199,76 @@ refund_run (void *cls,
}
GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
- &coin.eddsa_pub);
+ &rs->coin.eddsa_pub);
if (GNUNET_OK !=
TALER_TESTING_get_trait_merchant_priv (coin_cmd,
- 0,
&merchant_priv))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- rs->rh = TALER_EXCHANGE_refund (rs->exchange,
- &refund_amount,
- &refund_fee,
- &h_contract_terms,
- &coin,
- rs->refund_transaction_id,
- merchant_priv,
- &refund_cb,
- rs);
+ rs->che.type = TALER_EXCHANGE_CTT_REFUND;
+ rs->che.details.refund.h_contract_terms = h_contract_terms;
+ GNUNET_CRYPTO_eddsa_key_get_public (
+ &merchant_priv->eddsa_priv,
+ &rs->che.details.refund.merchant_pub.eddsa_pub);
+ rs->che.details.refund.refund_fee = denom_pub->fees.refund;
+ rs->che.details.refund.sig_amount = refund_amount;
+ rs->che.details.refund.rtransaction_id = rs->refund_transaction_id;
+ TALER_merchant_refund_sign (&rs->coin,
+ &h_contract_terms,
+ rs->refund_transaction_id,
+ &refund_amount,
+ merchant_priv,
+ &rs->che.details.refund.sig);
+ rs->rh = TALER_EXCHANGE_refund (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ &refund_amount,
+ &h_contract_terms,
+ &rs->coin,
+ rs->refund_transaction_id,
+ merchant_priv,
+ &refund_cb,
+ rs);
GNUNET_assert (NULL != rs->rh);
}
/**
+ * Offer internal data from a "refund" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+refund_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct RefundState *rs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_coin_history (0,
+ &rs->che),
+ TALER_TESTING_make_trait_coin_pub (0,
+ &rs->coin),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
* Free the state from a "refund" CMD, and possibly cancel
* a pending operation thereof.
*
@@ -236,10 +283,8 @@ refund_cleanup (void *cls,
if (NULL != rs->rh)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- rs->is->ip,
- cmd->label);
+ TALER_TESTING_command_incomplete (rs->is,
+ cmd->label);
TALER_EXCHANGE_refund_cancel (rs->rh);
rs->rh = NULL;
}
@@ -247,39 +292,25 @@ refund_cleanup (void *cls,
}
-/**
- * Create a "refund" command.
- *
- * @param label command label.
- * @param expected_response_code expected HTTP status code.
- * @param refund_amount the amount to ask a refund for.
- * @param refund_fee expected refund fee.
- * @param coin_reference reference to a command that can
- * provide a coin to be refunded.
- *
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_refund (const char *label,
unsigned int expected_response_code,
const char *refund_amount,
- const char *refund_fee,
const char *coin_reference)
{
struct RefundState *rs;
rs = GNUNET_new (struct RefundState);
-
rs->expected_response_code = expected_response_code;
rs->refund_amount = refund_amount;
- rs->refund_fee = refund_fee;
rs->coin_reference = coin_reference;
{
struct TALER_TESTING_Command cmd = {
.cls = rs,
.label = label,
.run = &refund_run,
- .cleanup = &refund_cleanup
+ .cleanup = &refund_cleanup,
+ .traits = &refund_traits
};
return cmd;
@@ -287,27 +318,11 @@ TALER_TESTING_cmd_refund (const char *label,
}
-/**
- * Create a "refund" command, allow to specify refund transaction
- * id. Mainly used to create conflicting requests.
- *
- * @param label command label.
- * @param expected_response_code expected HTTP status code.
- * @param refund_amount the amount to ask a refund for.
- * @param refund_fee expected refund fee.
- * @param coin_reference reference to a command that can
- * provide a coin to be refunded.
- * @param refund_transaction_id transaction id to use
- * in the request.
- *
- * @return the command.
- */
struct TALER_TESTING_Command
-TALER_TESTING_cmd_refund_with_id
- (const char *label,
+TALER_TESTING_cmd_refund_with_id (
+ const char *label,
unsigned int expected_response_code,
const char *refund_amount,
- const char *refund_fee,
const char *coin_reference,
uint64_t refund_transaction_id)
{
@@ -316,7 +331,6 @@ TALER_TESTING_cmd_refund_with_id
rs = GNUNET_new (struct RefundState);
rs->expected_response_code = expected_response_code;
rs->refund_amount = refund_amount;
- rs->refund_fee = refund_fee;
rs->coin_reference = coin_reference;
rs->refund_transaction_id = refund_transaction_id;
{
@@ -324,7 +338,8 @@ TALER_TESTING_cmd_refund_with_id
.cls = rs,
.label = label,
.run = &refund_run,
- .cleanup = &refund_cleanup
+ .cleanup = &refund_cleanup,
+ .traits = &refund_traits
};
return cmd;
diff --git a/src/testing/testing_api_cmd_reserve_attest.c b/src/testing/testing_api_cmd_reserve_attest.c
new file mode 100644
index 000000000..cf4b3a0c2
--- /dev/null
+++ b/src/testing/testing_api_cmd_reserve_attest.c
@@ -0,0 +1,263 @@
+/*
+ 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_reserve_attest.c
+ * @brief Implement the /reserve/$RID/attest test command.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+/**
+ * State for a "attest" CMD.
+ */
+struct AttestState
+{
+ /**
+ * Label to the command which created the reserve to check,
+ * needed to resort the reserve key.
+ */
+ const char *reserve_reference;
+
+ /**
+ * Handle to the "reserve attest" operation.
+ */
+ struct TALER_EXCHANGE_ReservesAttestHandle *rsh;
+
+ /**
+ * Private key of the reserve being analyzed.
+ */
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ /**
+ * Public key of the reserve being analyzed.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Array of attributes to request, of length @e attrs_len.
+ */
+ const char **attrs;
+
+ /**
+ * Length of the @e attrs array.
+ */
+ unsigned int attrs_len;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /* TODO: expose fields below as traits... */
+
+ /**
+ * Attested attributes returned by the exchange.
+ */
+ json_t *attributes;
+
+ /**
+ * Expiration time of the attested attributes.
+ */
+ struct GNUNET_TIME_Timestamp expiration_time;
+
+ /**
+ * Signature by the exchange affirming the attributes.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+ /**
+ * Online signing key used by the exchange.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+};
+
+
+/**
+ * Check that the reserve balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param rs HTTP response details
+ */
+static void
+reserve_attest_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ReservePostAttestResult *rs)
+{
+ struct AttestState *ss = cls;
+ struct TALER_TESTING_Interpreter *is = ss->is;
+
+ ss->rsh = NULL;
+ if (ss->expected_response_code != rs->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected HTTP response code: %d in %s:%u\n",
+ rs->hr.http_status,
+ __FILE__,
+ __LINE__);
+ json_dumpf (rs->hr.reply,
+ stderr,
+ JSON_INDENT (2));
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ if (MHD_HTTP_OK != rs->hr.http_status)
+ {
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ ss->attributes = json_incref ((json_t*) rs->details.ok.attributes);
+ ss->expiration_time = rs->details.ok.expiration_time;
+ ss->exchange_pub = rs->details.ok.exchange_pub;
+ ss->exchange_sig = rs->details.ok.exchange_sig;
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+attest_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AttestState *ss = cls;
+ const struct TALER_TESTING_Command *create_reserve;
+ const char *exchange_url;
+
+ ss->is = is;
+ exchange_url = TALER_TESTING_get_exchange_url (is);
+ if (NULL == exchange_url)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ create_reserve
+ = TALER_TESTING_interpreter_lookup_command (is,
+ ss->reserve_reference);
+
+ if (NULL == create_reserve)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_priv (create_reserve,
+ &ss->reserve_priv))
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR ("Failed to find reserve_priv for attest query\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
+ &ss->reserve_pub.eddsa_pub);
+ ss->rsh = TALER_EXCHANGE_reserves_attest (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ TALER_TESTING_get_keys (is),
+ ss->reserve_priv,
+ ss->attrs_len,
+ ss->attrs,
+ &reserve_attest_cb,
+ ss);
+}
+
+
+/**
+ * Cleanup the state from a "reserve attest" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+attest_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AttestState *ss = cls;
+
+ if (NULL != ss->rsh)
+ {
+ TALER_TESTING_command_incomplete (ss->is,
+ cmd->label);
+ TALER_EXCHANGE_reserves_attest_cancel (ss->rsh);
+ ss->rsh = NULL;
+ }
+ json_decref (ss->attributes);
+ GNUNET_free (ss->attrs);
+ GNUNET_free (ss);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_attest (const char *label,
+ const char *reserve_reference,
+ unsigned int expected_response_code,
+ ...)
+{
+ struct AttestState *ss;
+ unsigned int num_args;
+ const char *ea;
+ va_list ap;
+
+ num_args = 0;
+ va_start (ap, expected_response_code);
+ while (NULL != va_arg (ap, const char *))
+ num_args++;
+ va_end (ap);
+
+ GNUNET_assert (NULL != reserve_reference);
+ ss = GNUNET_new (struct AttestState);
+ ss->reserve_reference = reserve_reference;
+ ss->expected_response_code = expected_response_code;
+ ss->attrs_len = num_args;
+ ss->attrs = GNUNET_new_array (num_args,
+ const char *);
+ num_args = 0;
+ va_start (ap, expected_response_code);
+ while (NULL != (ea = va_arg (ap, const char *)))
+ ss->attrs[num_args++] = ea;
+ va_end (ap);
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ss,
+ .label = label,
+ .run = &attest_run,
+ .cleanup = &attest_cleanup
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_reserve_close.c b/src/testing/testing_api_cmd_reserve_close.c
new file mode 100644
index 000000000..8e272f547
--- /dev/null
+++ b/src/testing/testing_api_cmd_reserve_close.c
@@ -0,0 +1,260 @@
+/*
+ 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_reserve_close.c
+ * @brief Implement the /reserve/$RID/close test command.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "close" CMD.
+ */
+struct CloseState
+{
+ /**
+ * Label to the command which created the reserve to check,
+ * needed to resort the reserve key.
+ */
+ const char *reserve_reference;
+
+ /**
+ * Handle to the "reserve close" operation.
+ */
+ struct TALER_EXCHANGE_ReservesCloseHandle *rsh;
+
+ /**
+ * payto://-URI where to wire the funds.
+ */
+ const char *target_account;
+
+ /**
+ * Private key of the reserve being analyzed.
+ */
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ /**
+ * Public key of the reserve being analyzed.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Set to the KYC requirement payto hash *if* the exchange replied with a
+ * request for KYC.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Set to the KYC requirement row *if* the exchange replied with
+ * a request for KYC.
+ */
+ uint64_t requirement_row;
+};
+
+
+/**
+ * Check that the reserve balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param rs HTTP response details
+ */
+static void
+reserve_close_cb (void *cls,
+ const struct TALER_EXCHANGE_ReserveCloseResult *rs)
+{
+ struct CloseState *ss = cls;
+ struct TALER_TESTING_Interpreter *is = ss->is;
+
+ ss->rsh = NULL;
+ if (ss->expected_response_code != rs->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected HTTP response code: %d in %s:%u\n",
+ rs->hr.http_status,
+ __FILE__,
+ __LINE__);
+ json_dumpf (rs->hr.reply,
+ stderr,
+ JSON_INDENT (2));
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ switch (rs->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* nothing to check */
+ ss->requirement_row
+ = rs->details.unavailable_for_legal_reasons.requirement_row;
+ ss->h_payto
+ = rs->details.unavailable_for_legal_reasons.h_payto;
+ break;
+ default:
+ break;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+close_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct CloseState *ss = cls;
+ const struct TALER_TESTING_Command *create_reserve;
+
+ ss->is = is;
+ create_reserve
+ = TALER_TESTING_interpreter_lookup_command (is,
+ ss->reserve_reference);
+
+ if (NULL == create_reserve)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_priv (create_reserve,
+ &ss->reserve_priv))
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR ("Failed to find reserve_priv for close query\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
+ &ss->reserve_pub.eddsa_pub);
+ ss->rsh = TALER_EXCHANGE_reserves_close (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ ss->reserve_priv,
+ ss->target_account,
+ &reserve_close_cb,
+ ss);
+}
+
+
+/**
+ * Cleanup the state from a "reserve close" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+close_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct CloseState *ss = cls;
+
+ if (NULL != ss->rsh)
+ {
+ TALER_TESTING_command_incomplete (ss->is,
+ cmd->label);
+ TALER_EXCHANGE_reserves_close_cancel (ss->rsh);
+ ss->rsh = NULL;
+ }
+ GNUNET_free (ss);
+}
+
+
+/**
+ * Offer internal data to a "close" CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+close_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct CloseState *cs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_legi_requirement_row (
+ &cs->requirement_row),
+ TALER_TESTING_make_trait_h_payto (
+ &cs->h_payto),
+ TALER_TESTING_trait_end ()
+ };
+
+ if (cs->expected_response_code != MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS)
+ return GNUNET_NO;
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_close (const char *label,
+ const char *reserve_reference,
+ const char *target_account,
+ unsigned int expected_response_code)
+{
+ struct CloseState *ss;
+
+ GNUNET_assert (NULL != reserve_reference);
+ ss = GNUNET_new (struct CloseState);
+ ss->reserve_reference = reserve_reference;
+ ss->target_account = target_account;
+ ss->expected_response_code = expected_response_code;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ss,
+ .label = label,
+ .run = &close_run,
+ .cleanup = &close_cleanup,
+ .traits = &close_traits
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_reserve_get.c b/src/testing/testing_api_cmd_reserve_get.c
new file mode 100644
index 000000000..9a938cf82
--- /dev/null
+++ b/src/testing/testing_api_cmd_reserve_get.c
@@ -0,0 +1,390 @@
+/*
+ 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_reserve_get.c
+ * @brief Implement the GET /reserve/$RID test command.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "poll" CMD.
+ */
+struct PollState
+{
+
+ /**
+ * How long do we give the exchange to respond?
+ */
+ struct GNUNET_TIME_Relative timeout;
+
+ /**
+ * Label to the command which created the reserve to check,
+ * needed to resort the reserve key.
+ */
+ const char *poll_reference;
+
+ /**
+ * Timeout to wait for at most.
+ */
+ struct GNUNET_SCHEDULER_Task *tt;
+
+ /**
+ * The interpreter we are using.
+ */
+ struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * State for a "status" CMD.
+ */
+struct StatusState
+{
+
+ /**
+ * How long do we give the exchange to respond?
+ */
+ struct GNUNET_TIME_Relative timeout;
+
+ /**
+ * Poller waiting for us.
+ */
+ struct PollState *ps;
+
+ /**
+ * Label to the command which created the reserve to check,
+ * needed to resort the reserve key.
+ */
+ const char *reserve_reference;
+
+ /**
+ * Handle to the "reserve status" operation.
+ */
+ struct TALER_EXCHANGE_ReservesGetHandle *rsh;
+
+ /**
+ * Expected reserve balance.
+ */
+ const char *expected_balance;
+
+ /**
+ * Public key of the reserve being analyzed.
+ */
+ const struct TALER_ReservePublicKeyP *reserve_pubp;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+};
+
+
+/**
+ * Check that the reserve balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param rs HTTP response details
+ */
+static void
+reserve_status_cb (void *cls,
+ const struct TALER_EXCHANGE_ReserveSummary *rs)
+{
+ struct StatusState *ss = cls;
+ struct TALER_TESTING_Interpreter *is = ss->is;
+ struct TALER_Amount eb;
+
+ ss->rsh = NULL;
+ if (ss->expected_response_code != rs->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected HTTP response code: %d in %s:%u\n",
+ rs->hr.http_status,
+ __FILE__,
+ __LINE__);
+ json_dumpf (rs->hr.reply,
+ stderr,
+ 0);
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ if (MHD_HTTP_OK == ss->expected_response_code)
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (ss->expected_balance,
+ &eb));
+ if (0 != TALER_amount_cmp (&eb,
+ &rs->details.ok.balance))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected amount %s in reserve, wanted %s\n",
+ TALER_amount_to_string (&rs->details.ok.balance),
+ ss->expected_balance);
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ }
+ if (NULL != ss->ps)
+ {
+ /* force continuation on long poller */
+ GNUNET_SCHEDULER_cancel (ss->ps->tt);
+ ss->ps->tt = NULL;
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ if (GNUNET_TIME_relative_is_zero (ss->timeout))
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+status_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct StatusState *ss = cls;
+ const struct TALER_TESTING_Command *create_reserve;
+ const char *exchange_url;
+
+ ss->is = is;
+ exchange_url = TALER_TESTING_get_exchange_url (is);
+ if (NULL == exchange_url)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ create_reserve
+ = TALER_TESTING_interpreter_lookup_command (is,
+ ss->reserve_reference);
+ GNUNET_assert (NULL != create_reserve);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_pub (create_reserve,
+ &ss->reserve_pubp))
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR ("Failed to find reserve_pub for status query\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ ss->rsh = TALER_EXCHANGE_reserves_get (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ ss->reserve_pubp,
+ ss->timeout,
+ &reserve_status_cb,
+ ss);
+ if (! GNUNET_TIME_relative_is_zero (ss->timeout))
+ {
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+}
+
+
+/**
+ * Cleanup the state from a "reserve status" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+status_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct StatusState *ss = cls;
+
+ if (NULL != ss->rsh)
+ {
+ TALER_TESTING_command_incomplete (ss->is,
+ cmd->label);
+ TALER_EXCHANGE_reserves_get_cancel (ss->rsh);
+ ss->rsh = NULL;
+ }
+ GNUNET_free (ss);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_status (const char *label,
+ const char *reserve_reference,
+ const char *expected_balance,
+ unsigned int expected_response_code)
+{
+ struct StatusState *ss;
+
+ GNUNET_assert (NULL != reserve_reference);
+ ss = GNUNET_new (struct StatusState);
+ ss->reserve_reference = reserve_reference;
+ ss->expected_balance = expected_balance;
+ ss->expected_response_code = expected_response_code;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ss,
+ .label = label,
+ .run = &status_run,
+ .cleanup = &status_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_poll (const char *label,
+ const char *reserve_reference,
+ const char *expected_balance,
+ struct GNUNET_TIME_Relative timeout,
+ unsigned int expected_response_code)
+{
+ struct StatusState *ss;
+
+ GNUNET_assert (NULL != reserve_reference);
+ ss = GNUNET_new (struct StatusState);
+ ss->reserve_reference = reserve_reference;
+ ss->expected_balance = expected_balance;
+ ss->expected_response_code = expected_response_code;
+ ss->timeout = timeout;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ss,
+ .label = label,
+ .run = &status_run,
+ .cleanup = &status_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * Long poller timed out. Fail the test.
+ *
+ * @param cls a `struct PollState`
+ */
+static void
+finish_timeout (void *cls)
+{
+ struct PollState *ps = cls;
+
+ ps->tt = NULL;
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ps->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+finish_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PollState *ps = cls;
+ const struct TALER_TESTING_Command *poll_reserve;
+ struct StatusState *ss;
+
+ ps->is = is;
+ poll_reserve
+ = TALER_TESTING_interpreter_lookup_command (is,
+ ps->poll_reference);
+ GNUNET_assert (NULL != poll_reserve);
+ GNUNET_assert (poll_reserve->run == &status_run);
+ ss = poll_reserve->cls;
+ if (NULL == ss->rsh)
+ {
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ GNUNET_assert (NULL == ss->ps);
+ ss->ps = ps;
+ ps->tt = GNUNET_SCHEDULER_add_delayed (ps->timeout,
+ &finish_timeout,
+ ps);
+}
+
+
+/**
+ * Cleanup the state from a "reserve finish" CMD.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+finish_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PollState *ps = cls;
+
+ if (NULL != ps->tt)
+ {
+ GNUNET_SCHEDULER_cancel (ps->tt);
+ ps->tt = NULL;
+ }
+ GNUNET_free (ps);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_poll_finish (const char *label,
+ struct GNUNET_TIME_Relative timeout,
+ const char *poll_reference)
+{
+ struct PollState *ps;
+
+ GNUNET_assert (NULL != poll_reference);
+ ps = GNUNET_new (struct PollState);
+ ps->timeout = timeout;
+ ps->poll_reference = poll_reference;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ps,
+ .label = label,
+ .run = &finish_run,
+ .cleanup = &finish_cleanup
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_reserve_get_attestable.c b/src/testing/testing_api_cmd_reserve_get_attestable.c
new file mode 100644
index 000000000..ed1eb1355
--- /dev/null
+++ b/src/testing/testing_api_cmd_reserve_get_attestable.c
@@ -0,0 +1,242 @@
+/*
+ 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_reserve_get_attestable.c
+ * @brief Implement the /reserve/$RID/get_attestable test command.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "get_attestable" CMD.
+ */
+struct GetAttestableState
+{
+ /**
+ * Label to the command which created the reserve to check,
+ * needed to resort the reserve key.
+ */
+ const char *reserve_reference;
+
+ /**
+ * Handle to the "reserve get_attestable" operation.
+ */
+ struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah;
+
+ /**
+ * Expected attestable attributes.
+ */
+ const char **expected_attestables;
+
+ /**
+ * Length of the @e expected_attestables array.
+ */
+ unsigned int expected_attestables_length;
+
+ /**
+ * Public key of the reserve being analyzed.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * Check that the reserve balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param rs HTTP response details
+ */
+static void
+reserve_get_attestable_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ReserveGetAttestResult *rs)
+{
+ struct GetAttestableState *ss = cls;
+ struct TALER_TESTING_Interpreter *is = ss->is;
+
+ ss->rgah = NULL;
+ if (ss->expected_response_code != rs->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected HTTP response code: %d in %s:%u\n",
+ rs->hr.http_status,
+ __FILE__,
+ __LINE__);
+ json_dumpf (rs->hr.reply,
+ stderr,
+ JSON_INDENT (2));
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ if (MHD_HTTP_OK != rs->hr.http_status)
+ {
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ // FIXME: check returned list matches expectations!
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+get_attestable_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct GetAttestableState *ss = cls;
+ const struct TALER_TESTING_Command *ref_reserve;
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+ const char *exchange_url;
+
+ ss->is = is;
+ exchange_url = TALER_TESTING_get_exchange_url (is);
+ if (NULL == exchange_url)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ ref_reserve
+ = TALER_TESTING_interpreter_lookup_command (is,
+ ss->reserve_reference);
+
+ if (NULL == ref_reserve)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK ==
+ TALER_TESTING_get_trait_reserve_priv (ref_reserve,
+ &reserve_priv))
+ {
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &ss->reserve_pub.eddsa_pub);
+ }
+ else
+ {
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_pub (ref_reserve,
+ &reserve_pub))
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR (
+ "Failed to find reserve_priv for get_attestable query\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ ss->reserve_pub = *reserve_pub;
+ }
+ ss->rgah = TALER_EXCHANGE_reserves_get_attestable (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ &ss->reserve_pub,
+ &reserve_get_attestable_cb,
+ ss);
+}
+
+
+/**
+ * Cleanup the state from a "reserve get_attestable" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+get_attestable_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct GetAttestableState *ss = cls;
+
+ if (NULL != ss->rgah)
+ {
+ TALER_TESTING_command_incomplete (ss->is,
+ cmd->label);
+ TALER_EXCHANGE_reserves_get_attestable_cancel (ss->rgah);
+ ss->rgah = NULL;
+ }
+ GNUNET_free (ss->expected_attestables);
+ GNUNET_free (ss);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_get_attestable (const char *label,
+ const char *reserve_reference,
+ unsigned int expected_response_code,
+ ...)
+{
+ struct GetAttestableState *ss;
+ va_list ap;
+ unsigned int num_expected;
+ const char *ea;
+
+ num_expected = 0;
+ va_start (ap, expected_response_code);
+ while (NULL != va_arg (ap, const char *))
+ num_expected++;
+ va_end (ap);
+
+ GNUNET_assert (NULL != reserve_reference);
+ ss = GNUNET_new (struct GetAttestableState);
+ ss->reserve_reference = reserve_reference;
+ ss->expected_response_code = expected_response_code;
+ ss->expected_attestables_length = num_expected;
+ ss->expected_attestables = GNUNET_new_array (num_expected,
+ const char *);
+ num_expected = 0;
+ va_start (ap, expected_response_code);
+ while (NULL != (ea = va_arg (ap, const char *)))
+ ss->expected_attestables[num_expected++] = ea;
+ va_end (ap);
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ss,
+ .label = label,
+ .run = &get_attestable_run,
+ .cleanup = &get_attestable_cleanup
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_reserve_history.c b/src/testing/testing_api_cmd_reserve_history.c
new file mode 100644
index 000000000..ecb236a54
--- /dev/null
+++ b/src/testing/testing_api_cmd_reserve_history.c
@@ -0,0 +1,586 @@
+/*
+ This file is part of TALER
+ 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 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 testing/testing_api_cmd_reserve_history.c
+ * @brief Implement the /reserve/history test command.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "history" CMD.
+ */
+struct HistoryState
+{
+
+ /**
+ * Public key of the reserve being analyzed.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Label to the command which created the reserve to check,
+ * needed to resort the reserve key.
+ */
+ const char *reserve_reference;
+
+ /**
+ * Handle to the "reserve history" operation.
+ */
+ struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
+
+ /**
+ * Expected reserve balance.
+ */
+ const char *expected_balance;
+
+ /**
+ * Private key of the reserve being analyzed.
+ */
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+};
+
+
+/**
+ * Closure for analysis_cb().
+ */
+struct AnalysisContext
+{
+ /**
+ * Reserve public key we are looking at.
+ */
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+
+ /**
+ * Length of the @e history array.
+ */
+ unsigned int history_length;
+
+ /**
+ * Array of history items to match.
+ */
+ const struct TALER_EXCHANGE_ReserveHistoryEntry *history;
+
+ /**
+ * Array of @e history_length of matched entries.
+ */
+ bool *found;
+
+ /**
+ * Set to true if an entry could not be found.
+ */
+ bool failure;
+};
+
+
+/**
+ * Compare @a h1 and @a h2.
+ *
+ * @param h1 a history entry
+ * @param h2 a history entry
+ * @return 0 if @a h1 and @a h2 are equal
+ */
+static int
+history_entry_cmp (
+ const struct TALER_EXCHANGE_ReserveHistoryEntry *h1,
+ const struct TALER_EXCHANGE_ReserveHistoryEntry *h2)
+{
+ if (h1->type != h2->type)
+ return 1;
+ switch (h1->type)
+ {
+ case TALER_EXCHANGE_RTT_CREDIT:
+ if ( (0 ==
+ TALER_amount_cmp (&h1->amount,
+ &h2->amount)) &&
+ (0 == strcasecmp (h1->details.in_details.sender_url,
+ h2->details.in_details.sender_url)) &&
+ (h1->details.in_details.wire_reference ==
+ h2->details.in_details.wire_reference) &&
+ (GNUNET_TIME_timestamp_cmp (h1->details.in_details.timestamp,
+ ==,
+ h2->details.in_details.timestamp)) )
+ return 0;
+ return 1;
+ case TALER_EXCHANGE_RTT_WITHDRAWAL:
+ if ( (0 ==
+ TALER_amount_cmp (&h1->amount,
+ &h2->amount)) &&
+ (0 ==
+ TALER_amount_cmp (&h1->details.withdraw.fee,
+ &h2->details.withdraw.fee)) )
+ /* testing_api_cmd_withdraw doesn't set the out_authorization_sig,
+ so we cannot test for it here. but if the amount matches,
+ that should be good enough. */
+ return 0;
+ return 1;
+ case TALER_EXCHANGE_RTT_AGEWITHDRAWAL:
+ /* testing_api_cmd_age_withdraw doesn't set the out_authorization_sig,
+ so we cannot test for it here. but if the amount matches,
+ that should be good enough. */
+ if ( (0 ==
+ TALER_amount_cmp (&h1->amount,
+ &h2->amount)) &&
+ (0 ==
+ TALER_amount_cmp (&h1->details.age_withdraw.fee,
+ &h2->details.age_withdraw.fee)) &&
+ (h1->details.age_withdraw.max_age ==
+ h2->details.age_withdraw.max_age))
+ return 0;
+ return 1;
+ case TALER_EXCHANGE_RTT_RECOUP:
+ /* exchange_sig, exchange_pub and timestamp are NOT available
+ from the original recoup response, hence here NOT check(able/ed) */
+ if ( (0 ==
+ TALER_amount_cmp (&h1->amount,
+ &h2->amount)) &&
+ (0 ==
+ GNUNET_memcmp (&h1->details.recoup_details.coin_pub,
+ &h2->details.recoup_details.coin_pub)) )
+ return 0;
+ return 1;
+ case TALER_EXCHANGE_RTT_CLOSING:
+ /* testing_api_cmd_exec_closer doesn't set the
+ receiver_account_details, exchange_sig, exchange_pub or wtid or timestamp
+ so we cannot test for it here. but if the amount matches,
+ that should be good enough. */
+ if ( (0 ==
+ TALER_amount_cmp (&h1->amount,
+ &h2->amount)) &&
+ (0 ==
+ TALER_amount_cmp (&h1->details.close_details.fee,
+ &h2->details.close_details.fee)) )
+ return 0;
+ return 1;
+ case TALER_EXCHANGE_RTT_MERGE:
+ if ( (0 ==
+ TALER_amount_cmp (&h1->amount,
+ &h2->amount)) &&
+ (0 ==
+ TALER_amount_cmp (&h1->details.merge_details.purse_fee,
+ &h2->details.merge_details.purse_fee)) &&
+ (GNUNET_TIME_timestamp_cmp (h1->details.merge_details.merge_timestamp,
+ ==,
+ h2->details.merge_details.merge_timestamp))
+ &&
+ (GNUNET_TIME_timestamp_cmp (h1->details.merge_details.purse_expiration,
+ ==,
+ h2->details.merge_details.purse_expiration))
+ &&
+ (0 ==
+ GNUNET_memcmp (&h1->details.merge_details.merge_pub,
+ &h2->details.merge_details.merge_pub)) &&
+ (0 ==
+ GNUNET_memcmp (&h1->details.merge_details.h_contract_terms,
+ &h2->details.merge_details.h_contract_terms)) &&
+ (0 ==
+ GNUNET_memcmp (&h1->details.merge_details.purse_pub,
+ &h2->details.merge_details.purse_pub)) &&
+ (0 ==
+ GNUNET_memcmp (&h1->details.merge_details.reserve_sig,
+ &h2->details.merge_details.reserve_sig)) &&
+ (h1->details.merge_details.min_age ==
+ h2->details.merge_details.min_age) &&
+ (h1->details.merge_details.flags ==
+ h2->details.merge_details.flags) )
+ return 0;
+ return 1;
+ case TALER_EXCHANGE_RTT_OPEN:
+ if ( (0 ==
+ TALER_amount_cmp (&h1->amount,
+ &h2->amount)) &&
+ (GNUNET_TIME_timestamp_cmp (
+ h1->details.open_request.request_timestamp,
+ ==,
+ h2->details.open_request.request_timestamp)) &&
+ (GNUNET_TIME_timestamp_cmp (
+ h1->details.open_request.reserve_expiration,
+ ==,
+ h2->details.open_request.reserve_expiration)) &&
+ (h1->details.open_request.purse_limit ==
+ h2->details.open_request.purse_limit) &&
+ (0 ==
+ TALER_amount_cmp (&h1->details.open_request.reserve_payment,
+ &h2->details.open_request.reserve_payment)) &&
+ (0 ==
+ GNUNET_memcmp (&h1->details.open_request.reserve_sig,
+ &h2->details.open_request.reserve_sig)) )
+ return 0;
+ return 1;
+ case TALER_EXCHANGE_RTT_CLOSE:
+ if ( (0 ==
+ TALER_amount_cmp (&h1->amount,
+ &h2->amount)) &&
+ (GNUNET_TIME_timestamp_cmp (
+ h1->details.close_request.request_timestamp,
+ ==,
+ h2->details.close_request.request_timestamp)) &&
+ (0 ==
+ GNUNET_memcmp (&h1->details.close_request.target_account_h_payto,
+ &h2->details.close_request.target_account_h_payto)) &&
+ (0 ==
+ GNUNET_memcmp (&h1->details.close_request.reserve_sig,
+ &h2->details.close_request.reserve_sig)) )
+ return 0;
+ return 1;
+ }
+ GNUNET_assert (0);
+ return 1;
+}
+
+
+/**
+ * Check if @a cmd changed the reserve, if so, find the
+ * entry in our history and set the respective index in found
+ * to true. If the entry is not found, set failure.
+ *
+ * @param cls our `struct AnalysisContext *`
+ * @param cmd command to analyze for impact on history
+ */
+static void
+analyze_command (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AnalysisContext *ac = cls;
+ const struct TALER_ReservePublicKeyP *reserve_pub = ac->reserve_pub;
+ const struct TALER_EXCHANGE_ReserveHistoryEntry *history = ac->history;
+ unsigned int history_length = ac->history_length;
+ bool *found = ac->found;
+
+ if (TALER_TESTING_cmd_is_batch (cmd))
+ {
+ struct TALER_TESTING_Command *cur;
+ struct TALER_TESTING_Command *bcmd;
+
+ cur = TALER_TESTING_cmd_batch_get_current (cmd);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_batch_cmds (cmd,
+ &bcmd))
+ {
+ GNUNET_break (0);
+ ac->failure = true;
+ return;
+ }
+ for (unsigned int i = 0; NULL != bcmd[i].label; i++)
+ {
+ struct TALER_TESTING_Command *step = &bcmd[i];
+
+ analyze_command (ac,
+ step);
+ if (ac->failure)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Entry for batch step `%s' missing in history\n",
+ step->label);
+ return;
+ }
+ if (step == cur)
+ break; /* if *we* are in a batch, make sure not to analyze commands past 'now' */
+ }
+ return;
+ }
+
+ {
+ const struct TALER_ReservePublicKeyP *rp;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_pub (cmd,
+ &rp))
+ return; /* command does nothing for reserves */
+ if (0 !=
+ GNUNET_memcmp (rp,
+ reserve_pub))
+ return; /* command affects some _other_ reserve */
+ for (unsigned int j = 0; true; j++)
+ {
+ const struct TALER_EXCHANGE_ReserveHistoryEntry *he;
+ bool matched = false;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_history (cmd,
+ j,
+ &he))
+ {
+ /* NOTE: only for debugging... */
+ if (0 == j)
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Command `%s' has the reserve_pub, but lacks reserve history trait\n",
+ cmd->label);
+ return; /* command does nothing for reserves */
+ }
+ for (unsigned int i = 0; i<history_length; i++)
+ {
+ if (found[i])
+ continue; /* already found, skip */
+ if (0 ==
+ history_entry_cmp (he,
+ &history[i]))
+ {
+ found[i] = true;
+ matched = true;
+ break;
+ }
+ }
+ if (! matched)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Command `%s' reserve history entry #%u not found\n",
+ cmd->label,
+ j);
+ ac->failure = true;
+ return;
+ }
+ }
+ }
+}
+
+
+/**
+ * Check that the reserve balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param rs HTTP response details
+ */
+static void
+reserve_history_cb (void *cls,
+ const struct TALER_EXCHANGE_ReserveHistory *rs)
+{
+ struct HistoryState *ss = cls;
+ struct TALER_TESTING_Interpreter *is = ss->is;
+ struct TALER_Amount eb;
+
+ ss->rsh = NULL;
+ if (ss->expected_response_code != rs->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected HTTP response code: %d in %s:%u\n",
+ rs->hr.http_status,
+ __FILE__,
+ __LINE__);
+ json_dumpf (rs->hr.reply,
+ stderr,
+ 0);
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ if (MHD_HTTP_OK != rs->hr.http_status)
+ {
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (ss->expected_balance,
+ &eb));
+
+ if (0 != TALER_amount_cmp (&eb,
+ &rs->details.ok.balance))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected amount in reserve: %s\n",
+ TALER_amount_to_string (&rs->details.ok.balance));
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected balance of: %s\n",
+ TALER_amount_to_string (&eb));
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ {
+ bool found[rs->details.ok.history_len];
+ struct AnalysisContext ac = {
+ .reserve_pub = &ss->reserve_pub,
+ .history = rs->details.ok.history,
+ .history_length = rs->details.ok.history_len,
+ .found = found
+ };
+
+ memset (found,
+ 0,
+ sizeof (found));
+ TALER_TESTING_iterate (is,
+ true,
+ &analyze_command,
+ &ac);
+ if (ac.failure)
+ {
+ json_dumpf (rs->hr.reply,
+ stderr,
+ JSON_INDENT (2));
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ for (unsigned int i = 0; i<rs->details.ok.history_len; i++)
+ {
+ if (found[i])
+ continue;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "History entry at index %u of type %d not justified by command history\n",
+ i,
+ rs->details.ok.history[i].type);
+ json_dumpf (rs->hr.reply,
+ stderr,
+ JSON_INDENT (2));
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+history_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct HistoryState *ss = cls;
+ const struct TALER_TESTING_Command *create_reserve;
+
+ ss->is = is;
+ create_reserve
+ = TALER_TESTING_interpreter_lookup_command (is,
+ ss->reserve_reference);
+ if (NULL == create_reserve)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_priv (create_reserve,
+ &ss->reserve_priv))
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR ("Failed to find reserve_priv for history query\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
+ &ss->reserve_pub.eddsa_pub);
+ ss->rsh = TALER_EXCHANGE_reserves_history (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ ss->reserve_priv,
+ 0,
+ &reserve_history_cb,
+ ss);
+}
+
+
+/**
+ * Offer internal data from a "history" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+history_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct HistoryState *hs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_reserve_pub (&hs->reserve_pub),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Cleanup the state from a "reserve history" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+history_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct HistoryState *ss = cls;
+
+ if (NULL != ss->rsh)
+ {
+ TALER_TESTING_command_incomplete (ss->is,
+ cmd->label);
+ TALER_EXCHANGE_reserves_history_cancel (ss->rsh);
+ ss->rsh = NULL;
+ }
+ GNUNET_free (ss);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_history (const char *label,
+ const char *reserve_reference,
+ const char *expected_balance,
+ unsigned int expected_response_code)
+{
+ struct HistoryState *ss;
+
+ GNUNET_assert (NULL != reserve_reference);
+ ss = GNUNET_new (struct HistoryState);
+ ss->reserve_reference = reserve_reference;
+ ss->expected_balance = expected_balance;
+ ss->expected_response_code = expected_response_code;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ss,
+ .label = label,
+ .run = &history_run,
+ .cleanup = &history_cleanup,
+ .traits = &history_traits
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_reserve_open.c b/src/testing/testing_api_cmd_reserve_open.c
new file mode 100644
index 000000000..189d06b26
--- /dev/null
+++ b/src/testing/testing_api_cmd_reserve_open.c
@@ -0,0 +1,349 @@
+/*
+ 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_reserve_open.c
+ * @brief Implement the /reserve/$RID/open test command.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * Information we track per coin used to pay for opening the
+ * reserve.
+ */
+struct CoinDetail
+{
+ /**
+ * Name of the command and index of the coin to use.
+ */
+ const char *name;
+
+ /**
+ * Amount to charge to this coin.
+ */
+ struct TALER_Amount amount;
+};
+
+
+/**
+ * State for a "open" CMD.
+ */
+struct OpenState
+{
+ /**
+ * Label to the command which created the reserve to check,
+ * needed to resort the reserve key.
+ */
+ const char *reserve_reference;
+
+ /**
+ * Requested expiration time.
+ */
+ struct GNUNET_TIME_Relative req_expiration_time;
+
+ /**
+ * Requested minimum number of purses.
+ */
+ uint32_t min_purses;
+
+ /**
+ * Amount to pay for the opening from the reserve balance.
+ */
+ struct TALER_Amount reserve_pay;
+
+ /**
+ * Handle to the "reserve open" operation.
+ */
+ struct TALER_EXCHANGE_ReservesOpenHandle *rsh;
+
+ /**
+ * Expected reserve balance.
+ */
+ const char *expected_balance;
+
+ /**
+ * Length of the @e cd array.
+ */
+ unsigned int cpl;
+
+ /**
+ * Coin details, array of length @e cpl.
+ */
+ struct CoinDetail *cd;
+
+ /**
+ * Private key of the reserve being analyzed.
+ */
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ /**
+ * Public key of the reserve being analyzed.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * Check that the reserve balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param rs HTTP response details
+ */
+static void
+reserve_open_cb (void *cls,
+ const struct TALER_EXCHANGE_ReserveOpenResult *rs)
+{
+ struct OpenState *ss = cls;
+ struct TALER_TESTING_Interpreter *is = ss->is;
+
+ ss->rsh = NULL;
+ if (ss->expected_response_code != rs->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected HTTP response code: %d in %s:%u\n",
+ rs->hr.http_status,
+ __FILE__,
+ __LINE__);
+ json_dumpf (rs->hr.reply,
+ stderr,
+ JSON_INDENT (2));
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ if (MHD_HTTP_OK != rs->hr.http_status)
+ {
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+open_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct OpenState *ss = cls;
+ const struct TALER_TESTING_Command *create_reserve;
+ struct TALER_EXCHANGE_PurseDeposit cp[GNUNET_NZL (ss->cpl)];
+
+ ss->is = is;
+ create_reserve
+ = TALER_TESTING_interpreter_lookup_command (is,
+ ss->reserve_reference);
+
+ if (NULL == create_reserve)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_priv (create_reserve,
+ &ss->reserve_priv))
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR ("Failed to find reserve_priv for open query\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
+ &ss->reserve_pub.eddsa_pub);
+ for (unsigned int i = 0; i<ss->cpl; i++)
+ {
+ struct TALER_EXCHANGE_PurseDeposit *cpi = &cp[i];
+ const struct TALER_TESTING_Command *cmdi;
+ const struct TALER_AgeCommitmentProof *age_commitment_proof;
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+ const struct TALER_DenominationSignature *denom_sig;
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+ char *cref;
+ unsigned int cidx;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_parse_coin_reference (ss->cd[i].name,
+ &cref,
+ &cidx))
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR ("Failed to parse coin reference `%s'\n",
+ ss->cd[i].name);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ cmdi = TALER_TESTING_interpreter_lookup_command (is,
+ cref);
+ GNUNET_free (cref);
+ if (NULL == cmdi)
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR ("Command `%s' not found\n",
+ ss->cd[i].name);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_age_commitment_proof (cmdi,
+ cidx,
+ &age_commitment_proof))
+ ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (cmdi,
+ cidx,
+ &coin_priv)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_sig (cmdi,
+ cidx,
+ &denom_sig)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_pub (cmdi,
+ cidx,
+ &denom_pub)) )
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR ("Coin trait not found in `%s'\n",
+ ss->cd[i].name);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ cpi->age_commitment_proof = age_commitment_proof;
+ cpi->coin_priv = *coin_priv;
+ cpi->denom_sig = *denom_sig;
+ cpi->amount = ss->cd[i].amount;
+ cpi->h_denom_pub = denom_pub->h_key;
+ }
+ ss->rsh = TALER_EXCHANGE_reserves_open (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ ss->reserve_priv,
+ &ss->reserve_pay,
+ ss->cpl,
+ cp,
+ GNUNET_TIME_relative_to_timestamp (ss->req_expiration_time),
+ ss->min_purses,
+ &reserve_open_cb,
+ ss);
+}
+
+
+/**
+ * Cleanup the state from a "reserve open" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+open_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct OpenState *ss = cls;
+
+ if (NULL != ss->rsh)
+ {
+ TALER_TESTING_command_incomplete (ss->is,
+ cmd->label);
+ TALER_EXCHANGE_reserves_open_cancel (ss->rsh);
+ ss->rsh = NULL;
+ }
+ GNUNET_free (ss);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_open (const char *label,
+ const char *reserve_reference,
+ const char *reserve_pay,
+ struct GNUNET_TIME_Relative expiration_time,
+ uint32_t min_purses,
+ unsigned int expected_response_code,
+ ...)
+{
+ struct OpenState *ss;
+ va_list ap;
+ const char *name;
+ unsigned int i;
+
+ GNUNET_assert (NULL != reserve_reference);
+ ss = GNUNET_new (struct OpenState);
+ ss->reserve_reference = reserve_reference;
+ ss->req_expiration_time = expiration_time;
+ ss->min_purses = min_purses;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (reserve_pay,
+ &ss->reserve_pay));
+ ss->expected_response_code = expected_response_code;
+ va_start (ap,
+ expected_response_code);
+ while (NULL != (name = va_arg (ap, const char *)))
+ ss->cpl++;
+ va_end (ap);
+ GNUNET_assert (0 == (ss->cpl % 2));
+ ss->cpl /= 2; /* name and amount per coin */
+ ss->cd = GNUNET_new_array (ss->cpl,
+ struct CoinDetail);
+ i = 0;
+ va_start (ap,
+ expected_response_code);
+ while (NULL != (name = va_arg (ap, const char *)))
+ {
+ struct CoinDetail *cd = &ss->cd[i];
+ cd->name = name;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (va_arg (ap,
+ const char *),
+ &cd->amount));
+ i++;
+ }
+ va_end (ap);
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ss,
+ .label = label,
+ .run = &open_run,
+ .cleanup = &open_cleanup
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_reserve_purse.c b/src/testing/testing_api_cmd_reserve_purse.c
new file mode 100644
index 000000000..ef6964f26
--- /dev/null
+++ b/src/testing/testing_api_cmd_reserve_purse.c
@@ -0,0 +1,371 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_reserve_purse.c
+ * @brief command for testing /reserves/$PID/purse
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "purse create with merge" CMD.
+ */
+struct ReservePurseState
+{
+
+ /**
+ * Merge time (local time when the command was
+ * executed).
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+
+ /**
+ * Reserve private key.
+ */
+ struct TALER_ReservePrivateKeyP reserve_priv;
+
+ /**
+ * Reserve public key.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Reserve signature generated for the request
+ * (client-side).
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Private key of the purse.
+ */
+ struct TALER_PurseContractPrivateKeyP purse_priv;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Private key with the merge capability.
+ */
+ struct TALER_PurseMergePrivateKeyP merge_priv;
+
+ /**
+ * Public key of the merge capability.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Private key to decrypt the contract.
+ */
+ struct TALER_ContractDiffiePrivateP contract_priv;
+
+ /**
+ * Handle while operation is running.
+ */
+ struct TALER_EXCHANGE_PurseCreateMergeHandle *dh;
+
+ /**
+ * When will the purse expire?
+ */
+ struct GNUNET_TIME_Relative expiration_rel;
+
+ /**
+ * When will the purse expire?
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * Hash of the payto://-URI for the reserve we are
+ * merging into.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Set to the KYC requirement row *if* the exchange replied with
+ * a request for KYC.
+ */
+ uint64_t requirement_row;
+
+ /**
+ * Contract terms for the purse.
+ */
+ json_t *contract_terms;
+
+ /**
+ * Reference to the reserve, or NULL (!).
+ */
+ const char *reserve_ref;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * True to pay the purse fee.
+ */
+ bool pay_purse_fee;
+};
+
+
+/**
+ * Callback to analyze the /reserves/$PID/purse response, just used to check if
+ * the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param dr purse response details
+ */
+static void
+purse_cb (void *cls,
+ const struct TALER_EXCHANGE_PurseCreateMergeResponse *dr)
+{
+ struct ReservePurseState *ds = cls;
+
+ ds->dh = NULL;
+ ds->reserve_sig = *dr->reserve_sig;
+ if (ds->expected_response_code != dr->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ dr->hr.http_status,
+ ds->expected_response_code);
+ return;
+ }
+ switch (dr->hr.http_status)
+ {
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* KYC required */
+ ds->requirement_row =
+ dr->details.unavailable_for_legal_reasons.requirement_row;
+ break;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+purse_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct ReservePurseState *ds = cls;
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+ const struct TALER_TESTING_Command *ref;
+
+ (void) cmd;
+ ds->is = is;
+ ref = TALER_TESTING_interpreter_lookup_command (ds->is,
+ ds->reserve_ref);
+ GNUNET_assert (NULL != ref);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_priv (ref,
+ &reserve_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ ds->reserve_priv = *reserve_priv;
+ GNUNET_CRYPTO_eddsa_key_create (&ds->purse_priv.eddsa_priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&ds->purse_priv.eddsa_priv,
+ &ds->purse_pub.eddsa_pub);
+ GNUNET_CRYPTO_eddsa_key_get_public (&ds->reserve_priv.eddsa_priv,
+ &ds->reserve_pub.eddsa_pub);
+ GNUNET_CRYPTO_eddsa_key_create (&ds->merge_priv.eddsa_priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&ds->merge_priv.eddsa_priv,
+ &ds->merge_pub.eddsa_pub);
+ GNUNET_CRYPTO_ecdhe_key_create (&ds->contract_priv.ecdhe_priv);
+ ds->purse_expiration = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_relative_to_absolute (ds->expiration_rel));
+
+ {
+ char *payto_uri;
+ const char *exchange_url;
+ const struct TALER_TESTING_Command *exchange_cmd;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ payto_uri = TALER_reserve_make_payto (exchange_url,
+ &ds->reserve_pub);
+ TALER_payto_hash (payto_uri,
+ &ds->h_payto);
+ GNUNET_free (payto_uri);
+ }
+
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ ds->contract_terms,
+ "pay_deadline",
+ GNUNET_JSON_from_timestamp (ds->purse_expiration)));
+ ds->merge_timestamp = GNUNET_TIME_timestamp_get ();
+ ds->dh = TALER_EXCHANGE_purse_create_with_merge (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ &ds->reserve_priv,
+ &ds->purse_priv,
+ &ds->merge_priv,
+ &ds->contract_priv,
+ ds->contract_terms,
+ true /* upload contract */,
+ ds->pay_purse_fee,
+ ds->merge_timestamp,
+ &purse_cb,
+ ds);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not purse reserve\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "purse" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct ReservePurseState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+purse_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct ReservePurseState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_purse_create_with_merge_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ json_decref (ds->contract_terms);
+ GNUNET_free (ds);
+}
+
+
+/**
+ * Offer internal data from a "purse" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+purse_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct ReservePurseState *ds = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_timestamp (0,
+ &ds->merge_timestamp),
+ TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
+ TALER_TESTING_make_trait_purse_priv (&ds->purse_priv),
+ TALER_TESTING_make_trait_purse_pub (&ds->purse_pub),
+ TALER_TESTING_make_trait_merge_priv (&ds->merge_priv),
+ TALER_TESTING_make_trait_merge_pub (&ds->merge_pub),
+ TALER_TESTING_make_trait_contract_priv (&ds->contract_priv),
+ TALER_TESTING_make_trait_reserve_priv (&ds->reserve_priv),
+ TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub),
+ TALER_TESTING_make_trait_reserve_sig (&ds->reserve_sig),
+ TALER_TESTING_make_trait_legi_requirement_row (&ds->requirement_row),
+ TALER_TESTING_make_trait_h_payto (&ds->h_payto),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_create_with_reserve (
+ const char *label,
+ unsigned int expected_http_status,
+ const char *contract_terms,
+ bool upload_contract,
+ bool pay_purse_fee,
+ struct GNUNET_TIME_Relative expiration,
+ const char *reserve_ref)
+{
+ struct ReservePurseState *ds;
+ json_error_t err;
+
+ ds = GNUNET_new (struct ReservePurseState);
+ ds->expiration_rel = expiration;
+ ds->contract_terms = json_loads (contract_terms,
+ 0 /* flags */,
+ &err);
+ GNUNET_assert (NULL != ds->contract_terms);
+ ds->pay_purse_fee = pay_purse_fee;
+ ds->reserve_ref = reserve_ref;
+ ds->expected_response_code = expected_http_status;
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &purse_run,
+ .cleanup = &purse_cleanup,
+ .traits = &purse_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_reserve_purse.c */
diff --git a/src/testing/testing_api_cmd_revoke.c b/src/testing/testing_api_cmd_revoke.c
index 1006fbc34..f734be1a4 100644
--- a/src/testing/testing_api_cmd_revoke.c
+++ b/src/testing/testing_api_cmd_revoke.c
@@ -79,13 +79,14 @@ revoke_cleanup (void *cls,
if (NULL != rs->revoke_proc)
{
- GNUNET_break (0 == GNUNET_OS_process_kill
- (rs->revoke_proc, SIGKILL));
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (rs->revoke_proc,
+ SIGKILL));
GNUNET_OS_process_wait (rs->revoke_proc);
GNUNET_OS_process_destroy (rs->revoke_proc);
rs->revoke_proc = NULL;
}
- GNUNET_free_non_null (rs->dhks);
+ GNUNET_free (rs->dhks);
GNUNET_free (rs);
}
@@ -99,7 +100,7 @@ revoke_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
revoke_traits (void *cls,
const void **ret,
const char *trait,
@@ -109,8 +110,7 @@ revoke_traits (void *cls,
struct TALER_TESTING_Trait traits[] = {
/* Needed by the handler which waits the proc'
* death and calls the next command */
- TALER_TESTING_make_trait_process (0,
- &rs->revoke_proc),
+ TALER_TESTING_make_trait_process (&rs->revoke_proc),
TALER_TESTING_trait_end ()
};
@@ -122,9 +122,7 @@ revoke_traits (void *cls,
/**
- * Run the "revoke" command. The core of the function
- * is to call the "keyup" utility passing it the base32
- * encoding of the denomination to revoke.
+ * Run the "revoke" command.
*
* @param cls closure.
* @param cmd the command to execute.
@@ -143,14 +141,12 @@ revoke_run (void *cls,
/* Get denom pub from trait */
coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
rs->coin_reference);
-
if (NULL == coin_cmd)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
-
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_denom_pub (coin_cmd,
0,
@@ -163,14 +159,13 @@ revoke_run (void *cls,
rs->dhks = GNUNET_STRINGS_data_to_string_alloc (
&denom_pub->h_key,
sizeof (struct GNUNET_HashCode));
-
- rs->revoke_proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ rs->revoke_proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
- "taler-exchange-keyup",
- "taler-exchange-keyup",
+ "taler-exchange-offline",
+ "taler-exchange-offline",
"-c", rs->config_filename,
- "-r", rs->dhks,
+ "revoke-denomination", rs->dhks,
+ "upload",
NULL);
if (NULL == rs->revoke_proc)
@@ -181,22 +176,10 @@ revoke_run (void *cls,
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Revoke is ongoing..\n");
-
- is->reload_keys = GNUNET_OK;
TALER_TESTING_wait_for_sigchld (is);
}
-/**
- * Make a "revoke" command.
- *
- * @param label the command label.
- * @param expected_response_code expected HTTP status code.
- * @param coin_reference reference to a CMD that will offer the
- * denomination to revoke.
- * @param config_filename configuration file name.
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_revoke (const char *label,
unsigned int expected_response_code,
diff --git a/src/testing/testing_api_cmd_revoke_denom_key.c b/src/testing/testing_api_cmd_revoke_denom_key.c
new file mode 100644
index 000000000..2663c538f
--- /dev/null
+++ b/src/testing/testing_api_cmd_revoke_denom_key.c
@@ -0,0 +1,256 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2020 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 testing/testing_api_cmd_revoke_denom_key.c
+ * @brief Implement the revoke test command.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "revoke" CMD.
+ */
+struct RevokeState
+{
+ /**
+ * Expected HTTP status code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Command that offers a denomination to revoke.
+ */
+ const char *coin_reference;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Handle for the operation.
+ */
+ struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *kh;
+
+ /**
+ * Should we use a bogus signature?
+ */
+ bool bad_sig;
+
+};
+
+
+/**
+ * Function called with information about the post revocation operation result.
+ *
+ * @param cls closure with a `struct RevokeState *`
+ * @param rdr response data
+ */
+static void
+success_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementRevokeDenominationResponse *rdr)
+{
+ struct RevokeState *rs = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &rdr->hr;
+
+ rs->kh = NULL;
+ if (rs->expected_response_code != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status (rs->is,
+ hr->http_status,
+ rs->expected_response_code);
+ return;
+ }
+ TALER_TESTING_interpreter_next (rs->is);
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure, must be a `struct RevokeState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+revoke_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct RevokeState *rs = cls;
+
+ if (NULL != rs->kh)
+ {
+ TALER_EXCHANGE_management_revoke_denomination_key_cancel (rs->kh);
+ rs->kh = NULL;
+ }
+ GNUNET_free (rs);
+}
+
+
+/**
+ * Offer internal data from a "revoke" CMD to other CMDs.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static int
+revoke_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct RevokeState *rs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_trait_end ()
+ };
+
+ (void) rs;
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Run the "revoke" command for a denomination key.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+revoke_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RevokeState *rs = cls;
+ const struct TALER_TESTING_Command *coin_cmd;
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+ struct TALER_MasterSignatureP master_sig;
+ const char *exchange_url;
+
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ }
+ rs->is = is;
+ /* Get denom pub from trait */
+ coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ rs->coin_reference);
+
+ if (NULL == coin_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_denom_pub (coin_cmd,
+ 0,
+ &denom_pub));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to revoke denom '%s..'\n",
+ TALER_B2S (&denom_pub->h_key));
+ if (rs->bad_sig)
+ {
+ memset (&master_sig,
+ 42,
+ sizeof (master_sig));
+ }
+ else
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+ const struct TALER_MasterPrivateKeyP *master_priv;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_master_priv (exchange_cmd,
+ &master_priv));
+ TALER_exchange_offline_denomination_revoke_sign (&denom_pub->h_key,
+ master_priv,
+ &master_sig);
+ }
+ rs->kh = TALER_EXCHANGE_management_revoke_denomination_key (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ &denom_pub->h_key,
+ &master_sig,
+ &success_cb,
+ rs);
+ if (NULL == rs->kh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_revoke_denom_key (
+ const char *label,
+ unsigned int expected_response_code,
+ bool bad_sig,
+ const char *denom_ref)
+{
+ struct RevokeState *rs;
+
+ rs = GNUNET_new (struct RevokeState);
+ rs->expected_response_code = expected_response_code;
+ rs->coin_reference = denom_ref;
+ rs->bad_sig = bad_sig;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = rs,
+ .label = label,
+ .run = &revoke_run,
+ .cleanup = &revoke_cleanup,
+ .traits = &revoke_traits
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_revoke_sign_key.c b/src/testing/testing_api_cmd_revoke_sign_key.c
new file mode 100644
index 000000000..65b80b4c9
--- /dev/null
+++ b/src/testing/testing_api_cmd_revoke_sign_key.c
@@ -0,0 +1,256 @@
+/*
+ This file is part of TALER
+ 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 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 testing/testing_api_cmd_revoke_sign_key.c
+ * @brief Implement the revoke test command.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "revoke" CMD.
+ */
+struct RevokeState
+{
+ /**
+ * Expected HTTP status code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Command that offers a signination to revoke.
+ */
+ const char *coin_reference;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Handle for the operation.
+ */
+ struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *kh;
+
+ /**
+ * Should we use a bogus signature?
+ */
+ bool bad_sig;
+
+};
+
+
+/**
+ * Function called with information about the post revocation operation result.
+ *
+ * @param cls closure with a `struct RevokeState *`
+ * @param rsr response data
+ */
+static void
+success_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse *rsr)
+{
+ struct RevokeState *rs = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &rsr->hr;
+
+ rs->kh = NULL;
+ if (rs->expected_response_code != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status (rs->is,
+ hr->http_status,
+ rs->expected_response_code);
+ return;
+ }
+ TALER_TESTING_interpreter_next (rs->is);
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure, must be a `struct RevokeState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+revoke_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct RevokeState *rs = cls;
+
+ if (NULL != rs->kh)
+ {
+ TALER_EXCHANGE_management_revoke_signing_key_cancel (rs->kh);
+ rs->kh = NULL;
+ }
+ GNUNET_free (rs);
+}
+
+
+/**
+ * Offer internal data from a "revoke" CMD to other CMDs.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static int
+revoke_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct RevokeState *rs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_trait_end ()
+ };
+
+ (void) rs;
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Run the "revoke" command for a signing key.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+revoke_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RevokeState *rs = cls;
+ const struct TALER_TESTING_Command *coin_cmd;
+ const struct TALER_ExchangePublicKeyP *exchange_pub;
+ struct TALER_MasterSignatureP master_sig;
+ const char *exchange_url;
+
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ }
+ rs->is = is;
+ /* Get sign pub from trait */
+ coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ rs->coin_reference);
+
+ if (NULL == coin_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_pub (coin_cmd,
+ 0,
+ &exchange_pub));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to revoke sign '%s..'\n",
+ TALER_B2S (exchange_pub));
+ if (rs->bad_sig)
+ {
+ memset (&master_sig,
+ 42,
+ sizeof (master_sig));
+ }
+ else
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+ const struct TALER_MasterPrivateKeyP *master_priv;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_master_priv (exchange_cmd,
+ &master_priv));
+ TALER_exchange_offline_signkey_revoke_sign (exchange_pub,
+ master_priv,
+ &master_sig);
+ }
+ rs->kh = TALER_EXCHANGE_management_revoke_signing_key (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ exchange_pub,
+ &master_sig,
+ &success_cb,
+ rs);
+ if (NULL == rs->kh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_revoke_sign_key (
+ const char *label,
+ unsigned int expected_response_code,
+ bool bad_sig,
+ const char *sign_ref)
+{
+ struct RevokeState *rs;
+
+ rs = GNUNET_new (struct RevokeState);
+ rs->expected_response_code = expected_response_code;
+ rs->coin_reference = sign_ref;
+ rs->bad_sig = bad_sig;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = rs,
+ .label = label,
+ .run = &revoke_run,
+ .cleanup = &revoke_cleanup,
+ .traits = &revoke_traits
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_run_fakebank.c b/src/testing/testing_api_cmd_run_fakebank.c
new file mode 100644
index 000000000..7739d3c0c
--- /dev/null
+++ b/src/testing/testing_api_cmd_run_fakebank.c
@@ -0,0 +1,214 @@
+/*
+ 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
+ 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 testing/testing_api_cmd_run_fakebank.c
+ * @brief Command to run fakebank in-process
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+/**
+ * State for a "run fakebank" CMD.
+ */
+struct RunFakebankState
+{
+
+ /**
+ * Our interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Handle to the fakebank we are running.
+ */
+ struct TALER_FAKEBANK_Handle *fakebank;
+
+ /**
+ * URL of the bank.
+ */
+ char *bank_url;
+
+ /**
+ * Currency to use.
+ */
+ char *currency;
+
+ /**
+ * Data for access control.
+ */
+ struct TALER_BANK_AuthenticationData ba;
+
+ /**
+ * Port to use.
+ */
+ uint16_t port;
+};
+
+
+/**
+ * Run the "get_exchange" command.
+ *
+ * @param cls closure.
+ * @param cmd the command currently being executed.
+ * @param is the interpreter state.
+ */
+static void
+run_fakebank_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RunFakebankState *rfs = cls;
+
+ (void) cmd;
+ rfs->fakebank = TALER_FAKEBANK_start (rfs->port,
+ rfs->currency);
+ if (NULL == rfs->fakebank)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+run_fakebank_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct RunFakebankState *rfs = cls;
+
+ if (NULL != rfs->fakebank)
+ {
+ TALER_FAKEBANK_stop (rfs->fakebank);
+ rfs->fakebank = NULL;
+ }
+ GNUNET_free (rfs->ba.wire_gateway_url);
+ GNUNET_free (rfs->bank_url);
+ GNUNET_free (rfs->currency);
+ GNUNET_free (rfs);
+}
+
+
+/**
+ * Offer internal data to a "run_fakebank" CMD state to other commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+run_fakebank_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct RunFakebankState *rfs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_bank_auth_data (&rfs->ba),
+ TALER_TESTING_make_trait_fakebank (rfs->fakebank),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_run_fakebank (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *exchange_account_section)
+{
+ struct RunFakebankState *rfs;
+ unsigned long long fakebank_port;
+ char *exchange_payto_uri;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ "BANK",
+ "HTTP_PORT",
+ &fakebank_port))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "BANK",
+ "HTTP_PORT");
+ GNUNET_assert (0);
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ exchange_account_section,
+ "PAYTO_URI",
+ &exchange_payto_uri))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ exchange_account_section,
+ "PAYTO_URI");
+ GNUNET_assert (0);
+ }
+ rfs = GNUNET_new (struct RunFakebankState);
+ rfs->port = (uint16_t) fakebank_port;
+ GNUNET_asprintf (&rfs->bank_url,
+ "http://localhost:%u/",
+ (unsigned int) rfs->port);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_config_get_currency (cfg,
+ &rfs->currency));
+ {
+ char *exchange_xtalerbank_account;
+
+ exchange_xtalerbank_account
+ = TALER_xtalerbank_account_from_payto (exchange_payto_uri);
+ GNUNET_assert (NULL != exchange_xtalerbank_account);
+ GNUNET_asprintf (&rfs->ba.wire_gateway_url,
+ "http://localhost:%u/%s/",
+ (unsigned int) fakebank_port,
+ exchange_xtalerbank_account);
+ GNUNET_free (exchange_xtalerbank_account);
+ GNUNET_free (exchange_payto_uri);
+ }
+ GNUNET_free (exchange_payto_uri);
+ rfs->ba.method = TALER_BANK_AUTH_NONE;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = rfs,
+ .label = label,
+ .run = &run_fakebank_run,
+ .cleanup = &run_fakebank_cleanup,
+ .traits = &run_fakebank_traits,
+ .name = "fakebank"
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_serialize_keys.c b/src/testing/testing_api_cmd_serialize_keys.c
deleted file mode 100644
index 8a723c5ba..000000000
--- a/src/testing/testing_api_cmd_serialize_keys.c
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- This file is part of TALER
- (C) 2018 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 testing/testing_api_cmd_serialize_keys.c
- * @brief Lets tests use the keys serialization API.
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include <jansson.h>
-#include "taler_testing_lib.h"
-
-
-/**
- * Internal state for a serialize-keys CMD.
- */
-struct SerializeKeysState
-{
- /**
- * Serialized keys.
- */
- json_t *keys;
-
- /**
- * Exchange URL. Needed because the exchange gets disconnected
- * from, after keys serialization. This value is then needed by
- * subsequent commands that have to reconnect to the exchagne.
- */
- char *exchange_url;
-};
-
-
-/**
- * Internal state for a connect-with-state CMD.
- */
-struct ConnectWithStateState
-{
-
- /**
- * Reference to a CMD that offers a serialized key-state
- * that will be used in the reconnection.
- */
- const char *state_reference;
-
- /**
- * If set to GNUNET_YES, then the /keys callback has already
- * been passed the control to the next CMD. This is necessary
- * because it is not uncommon that the /keys callback gets
- * invoked multiple times, and without this flag, we would keep
- * going "next" CMD upon every invocation (causing impredictable
- * behaviour as for the instruction pointer.)
- */
- unsigned int consumed;
-
- /**
- * Interpreter state.
- */
- struct TALER_TESTING_Interpreter *is;
-};
-
-
-/**
- * Run the command.
- *
- * @param cls closure.
- * @param cmd the command to execute.
- * @param is the interpreter state.
- */
-static void
-serialize_keys_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct SerializeKeysState *sks = cls;
-
- sks->keys = TALER_EXCHANGE_serialize_data (is->exchange);
- if (NULL == sks->keys)
- TALER_TESTING_interpreter_fail (is);
-
- sks->exchange_url = GNUNET_strdup
- (TALER_EXCHANGE_get_base_url (is->exchange));
- TALER_EXCHANGE_disconnect (is->exchange);
- is->exchange = NULL;
- is->working = GNUNET_NO;
- TALER_TESTING_interpreter_next (is);
-}
-
-
-/**
- * Cleanup the state of a "serialize keys" CMD.
- *
- * @param cls closure.
- * @param cmd the command which is being cleaned up.
- */
-static void
-serialize_keys_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct SerializeKeysState *sks = cls;
-
- if (NULL != sks->keys)
- {
- json_decref (sks->keys);
- }
- GNUNET_free_non_null (sks->exchange_url);
- GNUNET_free (sks);
-}
-
-
-/**
- * Offer serialized keys as trait.
- *
- * @param cls closure.
- * @param[out] ret result.
- * @param trait name of the trait.
- * @param index index number of the object to offer.
- * @return #GNUNET_OK on success.
- */
-static int
-serialize_keys_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- struct SerializeKeysState *sks = cls;
- struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_exchange_keys (0, sks->keys),
- TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BASE_URL,
- sks->exchange_url),
- TALER_TESTING_trait_end ()
- };
-
- return TALER_TESTING_get_trait (traits,
- ret,
- trait,
- index);
-}
-
-
-/**
- * Run the command.
- *
- * @param cls closure.
- * @param cmd the command to execute.
- * @param is the interpreter state.
- */
-static void
-connect_with_state_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct ConnectWithStateState *cwss = cls;
- const struct TALER_TESTING_Command *state_cmd;
- const json_t *serialized_keys;
- const char *exchange_url;
-
- /* This command usually gets rescheduled after serialized
- * reconnection. */
- if (GNUNET_YES == cwss->consumed)
- {
- TALER_TESTING_interpreter_next (is);
- return;
- }
-
- cwss->is = is;
- state_cmd = TALER_TESTING_interpreter_lookup_command
- (is, cwss->state_reference);
-
- /* Command providing serialized keys not found. */
- if (NULL == state_cmd)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
- GNUNET_assert (GNUNET_OK ==
- TALER_TESTING_get_trait_exchange_keys (state_cmd,
- 0,
- &serialized_keys));
- {
- char *dump;
-
- dump = json_dumps (serialized_keys,
- JSON_INDENT (1));
- TALER_LOG_DEBUG ("Serialized key-state: %s\n",
- dump);
- free (dump);
- }
-
- GNUNET_assert (GNUNET_OK ==
- TALER_TESTING_get_trait_url (state_cmd,
- TALER_TESTING_UT_EXCHANGE_BASE_URL,
- &exchange_url));
- is->exchange = TALER_EXCHANGE_connect (is->ctx,
- exchange_url,
- &TALER_TESTING_cert_cb,
- cwss,
- TALER_EXCHANGE_OPTION_DATA,
- serialized_keys,
- TALER_EXCHANGE_OPTION_END);
- cwss->consumed = GNUNET_YES;
-}
-
-
-/**
- * Cleanup the state of a "connect with state" CMD. Just
- * a placeholder to avoid jumping on an invalid address.
- *
- * @param cls closure.
- * @param cmd the command which is being cleaned up.
- */
-static void
-connect_with_state_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct ConnectWithStateState *cwss = cls;
-
- GNUNET_free (cwss);
-}
-
-
-/**
- * Make a serialize-keys CMD. It will ask for
- * keys serialization __and__ disconnect from the
- * exchange.
- *
- * @param label CMD label
- * @return the CMD.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_serialize_keys (const char *label)
-{
- struct SerializeKeysState *sks;
-
- sks = GNUNET_new (struct SerializeKeysState);
- {
- struct TALER_TESTING_Command cmd = {
- .cls = sks,
- .label = label,
- .run = serialize_keys_run,
- .cleanup = serialize_keys_cleanup,
- .traits = serialize_keys_traits
- };
-
- return cmd;
- }
-}
-
-
-/**
- * Make a connect-with-state CMD. This command
- * will use a serialized key state to reconnect
- * to the exchange.
- *
- * @param label command label
- * @param state_reference label of a CMD offering
- * a serialized key state.
- * @return the CMD.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_connect_with_state (const char *label,
- const char *state_reference)
-{
- struct ConnectWithStateState *cwss;
-
- cwss = GNUNET_new (struct ConnectWithStateState);
- cwss->state_reference = state_reference;
- cwss->consumed = GNUNET_NO;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = cwss,
- .label = label,
- .run = connect_with_state_run,
- .cleanup = connect_with_state_cleanup
- };
-
- return cmd;
- }
-}
diff --git a/src/testing/testing_api_cmd_set_officer.c b/src/testing/testing_api_cmd_set_officer.c
new file mode 100644
index 000000000..4fbe5e368
--- /dev/null
+++ b/src/testing/testing_api_cmd_set_officer.c
@@ -0,0 +1,301 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_set_officer.c
+ * @brief command for testing /management/aml-officers
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "set_officer" CMD.
+ */
+struct SetOfficerState
+{
+
+ /**
+ * Update AML officer handle while operation is running.
+ */
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *dh;
+
+ /**
+ * Our interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Reference to command to previous set officer
+ * to update, or NULL.
+ */
+ const char *ref_cmd;
+
+ /**
+ * Name to use for the officer.
+ */
+ const char *name;
+
+ /**
+ * Private key of the AML officer.
+ */
+ struct TALER_AmlOfficerPrivateKeyP officer_priv;
+
+ /**
+ * Public key of the AML officer.
+ */
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+
+ /**
+ * Is the officer supposed to be enabled?
+ */
+ bool is_active;
+
+ /**
+ * Is access supposed to be read-only?
+ */
+ bool read_only;
+
+};
+
+
+/**
+ * Callback to analyze the /management/XXX response, just used to check
+ * if the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param ar response details
+ */
+static void
+set_officer_cb (void *cls,
+ const struct
+ TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse *ar)
+{
+ struct SetOfficerState *ds = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &ar->hr;
+
+ ds->dh = NULL;
+ if (MHD_HTTP_NO_CONTENT != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ MHD_HTTP_NO_CONTENT);
+ return;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+set_officer_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct SetOfficerState *ds = cls;
+ struct GNUNET_TIME_Timestamp now;
+ struct TALER_MasterSignatureP master_sig;
+ const char *exchange_url;
+
+ (void) cmd;
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ }
+ now = GNUNET_TIME_timestamp_get ();
+ ds->is = is;
+ if (NULL == ds->ref_cmd)
+ {
+ GNUNET_CRYPTO_eddsa_key_create (&ds->officer_priv.eddsa_priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&ds->officer_priv.eddsa_priv,
+ &ds->officer_pub.eddsa_pub);
+ }
+ else
+ {
+ const struct TALER_TESTING_Command *ref;
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv;
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub;
+
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ ds->ref_cmd);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_officer_pub (ref,
+ &officer_pub));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_officer_priv (ref,
+ &officer_priv));
+ ds->officer_pub = *officer_pub;
+ ds->officer_priv = *officer_priv;
+ }
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+ const struct TALER_MasterPrivateKeyP *master_priv;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_master_priv (exchange_cmd,
+ &master_priv));
+
+ TALER_exchange_offline_aml_officer_status_sign (&ds->officer_pub,
+ ds->name,
+ now,
+ ds->is_active,
+ ds->read_only,
+ master_priv,
+ &master_sig);
+ }
+ ds->dh = TALER_EXCHANGE_management_update_aml_officer (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ &ds->officer_pub,
+ ds->name,
+ now,
+ ds->is_active,
+ ds->read_only,
+ &master_sig,
+ &set_officer_cb,
+ ds);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "set_officer" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct SetOfficerState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+set_officer_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct SetOfficerState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_management_update_aml_officer_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ GNUNET_free (ds);
+}
+
+
+/**
+ * Offer internal data of a "set officer" CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+set_officer_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct SetOfficerState *ws = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_officer_pub (&ws->officer_pub),
+ TALER_TESTING_make_trait_officer_priv (&ws->officer_priv),
+ TALER_TESTING_make_trait_officer_name (ws->name),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_officer (
+ const char *label,
+ const char *ref_cmd,
+ const char *name,
+ bool is_active,
+ bool read_only)
+{
+ struct SetOfficerState *ds;
+
+ ds = GNUNET_new (struct SetOfficerState);
+ ds->ref_cmd = ref_cmd;
+ ds->name = name;
+ ds->is_active = is_active;
+ ds->read_only = read_only;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &set_officer_run,
+ .cleanup = &set_officer_cleanup,
+ .traits = &set_officer_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_set_officer.c */
diff --git a/src/testing/testing_api_cmd_set_wire_fee.c b/src/testing/testing_api_cmd_set_wire_fee.c
new file mode 100644
index 000000000..460a71e40
--- /dev/null
+++ b/src/testing/testing_api_cmd_set_wire_fee.c
@@ -0,0 +1,258 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 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/>
+*/
+/**
+ * @file testing/testing_api_cmd_set_wire_fee.c
+ * @brief command for testing POST to /management/wire-fees
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "wire_add" CMD.
+ */
+struct WireFeeState
+{
+
+ /**
+ * Wire enable handle while operation is running.
+ */
+ struct TALER_EXCHANGE_ManagementSetWireFeeHandle *dh;
+
+ /**
+ * Our interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Wire method to configure fee for.
+ */
+ const char *wire_method;
+
+ /**
+ * Wire fee amount to use.
+ */
+ const char *wire_fee;
+
+ /**
+ * Closing fee amount to use.
+ */
+ const char *closing_fee;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Should we make the request with a bad master_sig signature?
+ */
+ bool bad_sig;
+};
+
+
+/**
+ * Callback to analyze the /management/wire response, just used to check
+ * if the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param sfr response details
+ */
+static void
+wire_add_cb (void *cls,
+ const struct TALER_EXCHANGE_ManagementSetWireFeeResponse *sfr)
+{
+ struct WireFeeState *ds = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &sfr->hr;
+
+ ds->dh = NULL;
+ if (ds->expected_response_code != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ ds->expected_response_code);
+ return;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+wire_add_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct WireFeeState *ds = cls;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Timestamp start_time;
+ struct GNUNET_TIME_Timestamp end_time;
+ struct TALER_WireFeeSet fees;
+ const char *exchange_url;
+
+ (void) cmd;
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ }
+ ds->is = is;
+ now = GNUNET_TIME_absolute_get ();
+ start_time = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_subtract (now,
+ GNUNET_TIME_UNIT_HOURS));
+ end_time = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (now,
+ GNUNET_TIME_UNIT_HOURS));
+ if ( (GNUNET_OK !=
+ TALER_string_to_amount (ds->closing_fee,
+ &fees.closing)) ||
+ (GNUNET_OK !=
+ TALER_string_to_amount (ds->wire_fee,
+ &fees.wire)) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ if (ds->bad_sig)
+ {
+ memset (&master_sig,
+ 42,
+ sizeof (master_sig));
+ }
+ else
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+ const struct TALER_MasterPrivateKeyP *master_priv;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_master_priv (exchange_cmd,
+ &master_priv));
+ TALER_exchange_offline_wire_fee_sign (ds->wire_method,
+ start_time,
+ end_time,
+ &fees,
+ master_priv,
+ &master_sig);
+ }
+ ds->dh = TALER_EXCHANGE_management_set_wire_fees (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ ds->wire_method,
+ start_time,
+ end_time,
+ &fees,
+ &master_sig,
+ &wire_add_cb,
+ ds);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "wire_add" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct WireFeeState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+wire_add_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct WireFeeState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_management_set_wire_fees_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ GNUNET_free (ds);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_wire_fee (const char *label,
+ const char *wire_method,
+ const char *wire_fee,
+ const char *closing_fee,
+ unsigned int expected_http_status,
+ bool bad_sig)
+{
+ struct WireFeeState *ds;
+
+ ds = GNUNET_new (struct WireFeeState);
+ ds->expected_response_code = expected_http_status;
+ ds->bad_sig = bad_sig;
+ ds->wire_method = wire_method;
+ ds->wire_fee = wire_fee;
+ ds->closing_fee = closing_fee;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &wire_add_run,
+ .cleanup = &wire_add_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_set_wire_fee.c */
diff --git a/src/testing/testing_api_cmd_stat.c b/src/testing/testing_api_cmd_stat.c
index cf6d0b484..8723aac0d 100644
--- a/src/testing/testing_api_cmd_stat.c
+++ b/src/testing/testing_api_cmd_stat.c
@@ -28,44 +28,16 @@
/**
- * Cleanup the state from a "stat service" CMD.
+ * Run a "stat" CMD.
*
* @param cls closure.
- * @param cmd the command which is being cleaned up.
+ * @param cmd the command being run.
+ * @param is the interpreter state.
*/
static void
-stat_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- (void) cls;
- (void) cmd;
- /* nothing to clean. */
- return;
-}
-
-
-/**
- * No traits to offer, just provide a stub to be called when
- * some CMDs iterates through the list of all the commands.
- *
- * @param cls closure.
- * @param[out] ret result.
- * @param trait name of the trait.
- * @param index index number of the trait to return.
- * @return #GNUNET_OK on success.
- */
-static int
-stat_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- (void) cls;
- (void) ret;
- (void) trait;
- (void) index;
- return GNUNET_NO;
-}
+stat_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is);
/**
@@ -81,9 +53,20 @@ stat_cmd (struct TALER_TESTING_Timer *timings,
struct GNUNET_TIME_Relative duration;
struct GNUNET_TIME_Relative lat;
- if (cmd->start_time.abs_value_us > cmd->finish_time.abs_value_us)
+ if (GNUNET_TIME_absolute_cmp (cmd->start_time,
+ >,
+ cmd->finish_time))
{
- GNUNET_break (0);
+ /* This is a problem, except of course for
+ this command itself, as we clearly did not yet
+ finish... */
+ if (cmd->run != &stat_run)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Bad timings for `%s'\n",
+ cmd->label);
+ GNUNET_break (0);
+ }
return;
}
duration = GNUNET_TIME_absolute_get_difference (cmd->start_time,
@@ -115,36 +98,35 @@ stat_cmd (struct TALER_TESTING_Timer *timings,
/**
* Obtain statistics for @a timings of @a cmd
*
- * @param timings what timings to get
+ * @param[in,out] cls what timings to get
* @param cmd command to process
*/
static void
-do_stat (struct TALER_TESTING_Timer *timings,
+do_stat (void *cls,
const struct TALER_TESTING_Command *cmd)
{
+ struct TALER_TESTING_Timer *timings = cls;
+
if (TALER_TESTING_cmd_is_batch (cmd))
{
-#define BATCH_INDEX 1
struct TALER_TESTING_Command *bcmd;
if (GNUNET_OK !=
- TALER_TESTING_get_trait_cmd (cmd,
- BATCH_INDEX,
- &bcmd))
+ TALER_TESTING_get_trait_batch_cmds (cmd,
+ &bcmd))
{
GNUNET_break (0);
return;
}
-
- for (unsigned int j = 0; NULL != bcmd[j].label; j++)
+ for (unsigned int j = 0;
+ NULL != bcmd[j].label;
+ j++)
do_stat (timings,
&bcmd[j]);
+ return;
}
- else
- {
- stat_cmd (timings,
- cmd);
- }
+ stat_cmd (timings,
+ cmd);
}
@@ -162,31 +144,20 @@ stat_run (void *cls,
{
struct TALER_TESTING_Timer *timings = cls;
- for (unsigned int i = 0; NULL != is->commands[i].label; i++)
- {
- if (cmd == &is->commands[i])
- break; /* skip at our current command */
- do_stat (timings,
- &is->commands[i]);
- }
+ TALER_TESTING_iterate (is,
+ true,
+ &do_stat,
+ timings);
TALER_TESTING_interpreter_next (is);
}
-/**
- * Obtain performance data from the interpreter.
- *
- * @param timers what commands (by label) to obtain runtimes for
- * @return the command
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_stat (struct TALER_TESTING_Timer *timers)
{
struct TALER_TESTING_Command cmd = {
.label = "stat",
- .run = stat_run,
- .cleanup = stat_cleanup,
- .traits = stat_traits,
+ .run = &stat_run,
.cls = (void *) timers
};
@@ -194,4 +165,4 @@ TALER_TESTING_cmd_stat (struct TALER_TESTING_Timer *timers)
}
-/* end of testing_api_cmd_sleep.c */
+/* end of testing_api_cmd_stat.c */
diff --git a/src/testing/testing_api_cmd_status.c b/src/testing/testing_api_cmd_status.c
deleted file mode 100644
index bd93fe9b3..000000000
--- a/src/testing/testing_api_cmd_status.c
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2020 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 testing/testing_api_cmd_status.c
- * @brief Implement the /reserve/status test command.
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_testing_lib.h"
-
-
-/**
- * State for a "status" CMD.
- */
-struct StatusState
-{
- /**
- * Label to the command which created the reserve to check,
- * needed to resort the reserve key.
- */
- const char *reserve_reference;
-
- /**
- * Handle to the "reserve status" operation.
- */
- struct TALER_EXCHANGE_ReservesGetHandle *rsh;
-
- /**
- * Expected reserve balance.
- */
- const char *expected_balance;
-
- /**
- * Public key of the reserve being analyzed.
- */
- const struct TALER_ReservePublicKeyP *reserve_pubp;
-
- /**
- * Expected HTTP response code.
- */
- unsigned int expected_response_code;
-
- /**
- * Interpreter state.
- */
- struct TALER_TESTING_Interpreter *is;
-};
-
-
-/**
- * Compare @a h1 and @a h2.
- *
- * @param h1 a history entry
- * @param h2 a history entry
- * @return 0 if @a h1 and @a h2 are equal
- */
-static int
-history_entry_cmp (const struct TALER_EXCHANGE_ReserveHistory *h1,
- const struct TALER_EXCHANGE_ReserveHistory *h2)
-{
- if (h1->type != h2->type)
- return 1;
- switch (h1->type)
- {
- case TALER_EXCHANGE_RTT_CREDIT:
- if ( (0 ==
- TALER_amount_cmp (&h1->amount,
- &h2->amount)) &&
- (h1->details.in_details.wire_reference_size ==
- h2->details.in_details.wire_reference_size) &&
- (0 == strcasecmp (h1->details.in_details.sender_url,
- h2->details.in_details.sender_url)) &&
- (0 == memcmp (h1->details.in_details.wire_reference,
- h2->details.in_details.wire_reference,
- h1->details.in_details.wire_reference_size)) &&
- (h1->details.in_details.timestamp.abs_value_us ==
- h2->details.in_details.timestamp.abs_value_us) )
- return 0;
- return 1;
- case TALER_EXCHANGE_RTT_WITHDRAWAL:
- if ( (0 ==
- TALER_amount_cmp (&h1->amount,
- &h2->amount)) &&
- (0 ==
- TALER_amount_cmp (&h1->details.withdraw.fee,
- &h2->details.withdraw.fee)) )
- /* testing_api_cmd_withdraw doesn't set the out_authorization_sig,
- so we cannot test for it here. but if the amount matches,
- that should be good enough. */
- return 0;
- return 1;
- case TALER_EXCHANGE_RTT_RECOUP:
- /* exchange_sig, exchange_pub and timestamp are NOT available
- from the original recoup response, hence here NOT check(able/ed) */
- if ( (0 ==
- TALER_amount_cmp (&h1->amount,
- &h2->amount)) &&
- (0 ==
- GNUNET_memcmp (&h1->details.recoup_details.coin_pub,
- &h2->details.recoup_details.coin_pub)) )
- return 0;
- return 1;
- case TALER_EXCHANGE_RTT_CLOSE:
- /* testing_api_cmd_exec_closer doesn't set the
- receiver_account_details, exchange_sig, exchange_pub or wtid or timestamp
- so we cannot test for it here. but if the amount matches,
- that should be good enough. */
- if ( (0 ==
- TALER_amount_cmp (&h1->amount,
- &h2->amount)) &&
- (0 ==
- TALER_amount_cmp (&h1->details.close_details.fee,
- &h2->details.close_details.fee)) )
- return 0;
- return 1;
- }
- GNUNET_assert (0);
- return 1;
-}
-
-
-/**
- * Check if @a cmd changed the reserve, if so, find the
- * entry in @a history and set the respective index in @a found
- * to #GNUNET_YES. If the entry is not found, return #GNUNET_SYSERR.
- *
- * @param reserve_pub public key of the reserve for which we have the @a history
- * @param cmd command to analyze for impact on history
- * @param history_length number of entries in @a history and @a found
- * @param history history to check
- * @param[in,out] found array to update
- * @return #GNUNET_OK if @a cmd action on reserve was found in @a history
- */
-static int
-analyze_command (const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_TESTING_Command *cmd,
- unsigned int history_length,
- const struct TALER_EXCHANGE_ReserveHistory *history,
- int *found)
-{
- if (TALER_TESTING_cmd_is_batch (cmd))
- {
-#define BATCH_INDEX 1
- struct TALER_TESTING_Command *cur;
- struct TALER_TESTING_Command *bcmd;
-
- cur = TALER_TESTING_cmd_batch_get_current (cmd);
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_cmd (cmd,
- BATCH_INDEX,
- &bcmd))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- for (unsigned int i = 0; NULL != bcmd[i].label; i++)
- {
- struct TALER_TESTING_Command *step = &bcmd[i];
-
- if (step == cur)
- break; /* if *we* are in a batch, make sure not to analyze commands past 'now' */
- if (GNUNET_OK !=
- analyze_command (reserve_pub,
- step,
- history_length,
- history,
- found))
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
- }
- else
- {
- const struct TALER_ReservePublicKeyP *rp;
- const struct TALER_EXCHANGE_ReserveHistory *he;
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_reserve_pub (cmd,
- 0,
- &rp))
- return GNUNET_OK; /* command does nothing for reserves */
- if (0 !=
- GNUNET_memcmp (rp,
- reserve_pub))
- return GNUNET_OK; /* command affects some _other_ reserve */
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_reserve_history (cmd,
- 0,
- &he))
- {
- /* NOTE: only for debugging... */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Command `%s' has the reserve_pub trait, but does not reserve history trait\n",
- cmd->label);
- return GNUNET_OK; /* command does nothing for reserves */
- }
- for (unsigned int i = 0; i<history_length; i++)
- {
- if (found[i])
- continue; /* already found, skip */
- if (0 ==
- history_entry_cmp (he,
- &history[i]))
- {
- found[i] = GNUNET_YES;
- return GNUNET_OK;
- }
- }
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Command `%s' reserve history entry not found\n",
- cmd->label);
- return GNUNET_SYSERR;
- }
-}
-
-
-/**
- * Check that the reserve balance and HTTP response code are
- * both acceptable.
- *
- * @param cls closure.
- * @param http_status HTTP response code.
- * @param ec taler-specific error code.
- * @param json original JSON response from the exchange
- * @param balance current balance in the reserve, NULL on error.
- * @param history_length number of entries in the transaction
- * history, 0 on error.
- * @param history detailed transaction history, NULL on error.
- */
-static void
-reserve_status_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const json_t *json,
- const struct TALER_Amount *balance,
- unsigned int history_length,
- const struct TALER_EXCHANGE_ReserveHistory *history)
-{
- struct StatusState *ss = cls;
- struct TALER_TESTING_Interpreter *is = ss->is;
- struct TALER_Amount eb;
-
- ss->rsh = NULL;
- if (ss->expected_response_code != http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected HTTP response code: %d in %s:%u\n",
- http_status,
- __FILE__,
- __LINE__);
- json_dumpf (json, stderr, 0);
- TALER_TESTING_interpreter_fail (ss->is);
- return;
- }
-
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (ss->expected_balance,
- &eb));
-
- if (0 != TALER_amount_cmp (&eb,
- balance))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected amount in reserve: %s\n",
- TALER_amount_to_string (balance));
- TALER_TESTING_interpreter_fail (ss->is);
- return;
- }
- {
- int found[history_length];
-
- memset (found, 0, sizeof (found));
- for (unsigned int i = 0; i<=is->ip; i++)
- {
- struct TALER_TESTING_Command *cmd = &is->commands[i];
-
- if (GNUNET_OK !=
- analyze_command (ss->reserve_pubp,
- cmd,
- history_length,
- history,
- found))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Entry for command `%s' missing in history\n",
- cmd->label);
- TALER_TESTING_interpreter_fail (ss->is);
- return;
- }
- }
- for (unsigned int i = 0; i<history_length; i++)
- if (! found[i])
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "History entry at index %u of type %d not justified by command history\n",
- i,
- history[i].type);
- TALER_TESTING_interpreter_fail (ss->is);
- return;
- }
- }
- TALER_TESTING_interpreter_next (is);
-}
-
-
-/**
- * Run the command.
- *
- * @param cls closure.
- * @param cmd the command being executed.
- * @param is the interpreter state.
- */
-static void
-status_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct StatusState *ss = cls;
- const struct TALER_TESTING_Command *create_reserve;
-
- ss->is = is;
- create_reserve
- = TALER_TESTING_interpreter_lookup_command (is,
- ss->reserve_reference);
-
- if (NULL == create_reserve)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_reserve_pub (create_reserve,
- 0,
- &ss->reserve_pubp))
- {
- GNUNET_break (0);
- TALER_LOG_ERROR ("Failed to find reserve_pub for status query\n");
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- ss->rsh = TALER_EXCHANGE_reserves_get (is->exchange,
- ss->reserve_pubp,
- &reserve_status_cb,
- ss);
-}
-
-
-/**
- * Cleanup the state from a "reserve status" CMD, and possibly
- * cancel a pending operation thereof.
- *
- * @param cls closure.
- * @param cmd the command which is being cleaned up.
- */
-static void
-status_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct StatusState *ss = cls;
-
- if (NULL != ss->rsh)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- ss->is->ip,
- cmd->label);
- TALER_EXCHANGE_reserves_get_cancel (ss->rsh);
- ss->rsh = NULL;
- }
- GNUNET_free (ss);
-}
-
-
-/**
- * Create a "reserve status" command.
- *
- * @param label the command label.
- * @param reserve_reference reference to the reserve to check.
- * @param expected_balance expected balance for the reserve.
- * @param expected_response_code expected HTTP response code.
- *
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_status (const char *label,
- const char *reserve_reference,
- const char *expected_balance,
- unsigned int expected_response_code)
-{
- struct StatusState *ss;
-
- GNUNET_assert (NULL != reserve_reference);
- ss = GNUNET_new (struct StatusState);
- ss->reserve_reference = reserve_reference;
- ss->expected_balance = expected_balance;
- ss->expected_response_code = expected_response_code;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = ss,
- .label = label,
- .run = &status_run,
- .cleanup = &status_cleanup
- };
-
- return cmd;
- }
-}
diff --git a/src/testing/testing_api_cmd_system_start.c b/src/testing/testing_api_cmd_system_start.c
new file mode 100644
index 000000000..541ad75c1
--- /dev/null
+++ b/src/testing/testing_api_cmd_system_start.c
@@ -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 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 testing/testing_api_cmd_system_start.c
+ * @brief run taler-benchmark-setup.sh command
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "system" CMD.
+ */
+struct SystemState
+{
+
+ /**
+ * System process.
+ */
+ struct GNUNET_OS_Process *system_proc;
+
+ /**
+ * Input pipe to @e system_proc, used to keep the
+ * process alive until we are done.
+ */
+ struct GNUNET_DISK_PipeHandle *pipe_in;
+
+ /**
+ * Output pipe to @e system_proc, used to find out
+ * when the services are ready.
+ */
+ struct GNUNET_DISK_PipeHandle *pipe_out;
+
+ /**
+ * Task reading from @e pipe_in.
+ */
+ struct GNUNET_SCHEDULER_Task *reader;
+
+ /**
+ * Waiting for child to die.
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * Our interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * NULL-terminated array of command-line arguments.
+ */
+ char **args;
+
+ /**
+ * Current input buffer, 0-terminated. Contains the last 15 bytes of input
+ * so we can search them again for the "<<READY>>" tag.
+ */
+ char ibuf[16];
+
+ /**
+ * Did we find the ready tag?
+ */
+ bool ready;
+
+ /**
+ * Is the child process still running?
+ */
+ bool active;
+};
+
+
+/**
+ * Defines a GNUNET_ChildCompletedCallback which is sent back
+ * upon death or completion of a child process.
+ *
+ * @param cls our `struct SystemState *`
+ * @param type type of the process
+ * @param exit_code status code of the process
+ */
+static void
+setup_terminated (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct SystemState *as = cls;
+
+ as->cwh = NULL;
+ as->active = false;
+ if (NULL != as->reader)
+ {
+ GNUNET_SCHEDULER_cancel (as->reader);
+ as->reader = NULL;
+ }
+ if (! as->ready)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Launching Taler system failed: %d/%llu\n",
+ (int) type,
+ (unsigned long long) exit_code);
+ TALER_TESTING_interpreter_fail (as->is);
+ return;
+ }
+}
+
+
+/**
+ * Start helper to read from stdout of child.
+ *
+ * @param as our system state
+ */
+static void
+start_reader (struct SystemState *as);
+
+
+static void
+read_stdout (void *cls)
+{
+ struct SystemState *as = cls;
+ const struct GNUNET_DISK_FileHandle *fh;
+ char buf[1024 * 10];
+ ssize_t ret;
+ size_t off = 0;
+
+ as->reader = NULL;
+ strcpy (buf,
+ as->ibuf);
+ off = strlen (buf);
+ fh = GNUNET_DISK_pipe_handle (as->pipe_out,
+ GNUNET_DISK_PIPE_END_READ);
+ ret = GNUNET_DISK_file_read (fh,
+ &buf[off],
+ sizeof (buf) - off);
+ if (-1 == ret)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "read");
+ TALER_TESTING_interpreter_fail (as->is);
+ return;
+ }
+ if (0 == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Child closed stdout\n");
+ return;
+ }
+ /* forward log, except single '.' outputs */
+ if ( (1 != ret) ||
+ ('.' != buf[off]) )
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "TUS: %.*s\n",
+ (int) ret,
+ &buf[off]);
+ start_reader (as);
+ off += ret;
+ if (as->ready)
+ {
+ /* already done */
+ return;
+ }
+ if (NULL !=
+ memmem (buf,
+ off,
+ "\n<<READY>>\n",
+ strlen ("\n<<READY>>\n")))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Taler system UP\n");
+ as->ready = true;
+ TALER_TESTING_interpreter_next (as->is);
+ return;
+ }
+
+ {
+ size_t mcpy;
+
+ mcpy = GNUNET_MIN (off,
+ sizeof (as->ibuf) - 1);
+ memcpy (as->ibuf,
+ &buf[off - mcpy],
+ mcpy);
+ as->ibuf[mcpy] = '\0';
+ }
+}
+
+
+static void
+start_reader (struct SystemState *as)
+{
+ const struct GNUNET_DISK_FileHandle *fh;
+
+ GNUNET_assert (NULL == as->reader);
+ fh = GNUNET_DISK_pipe_handle (as->pipe_out,
+ GNUNET_DISK_PIPE_END_READ);
+ as->reader = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
+ fh,
+ &read_stdout,
+ as);
+}
+
+
+/**
+ * Run the command. Use the `taler-exchange-system' program.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ * @param is interpreter state.
+ */
+static void
+system_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct SystemState *as = cls;
+
+ (void) cmd;
+ as->is = is;
+ as->pipe_in = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
+ GNUNET_assert (NULL != as->pipe_in);
+ as->pipe_out = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE);
+ GNUNET_assert (NULL != as->pipe_out);
+ as->system_proc
+ = GNUNET_OS_start_process_vap (
+ GNUNET_OS_INHERIT_STD_ERR,
+ as->pipe_in, as->pipe_out, NULL,
+ "taler-unified-setup.sh",
+ as->args);
+ if (NULL == as->system_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ as->active = true;
+ start_reader (as);
+ as->cwh = GNUNET_wait_child (as->system_proc,
+ &setup_terminated,
+ as);
+}
+
+
+/**
+ * Free the state of a "system" CMD, and possibly kill its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+system_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct SystemState *as = cls;
+
+ (void) cmd;
+ if (NULL != as->cwh)
+ {
+ GNUNET_wait_child_cancel (as->cwh);
+ as->cwh = NULL;
+ }
+ if (NULL != as->reader)
+ {
+ GNUNET_SCHEDULER_cancel (as->reader);
+ as->reader = NULL;
+ }
+ if (NULL != as->system_proc)
+ {
+ if (as->active)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (as->system_proc,
+ SIGTERM));
+ GNUNET_OS_process_wait (as->system_proc);
+ }
+ GNUNET_OS_process_destroy (as->system_proc);
+ as->system_proc = NULL;
+ }
+ if (NULL != as->pipe_in)
+ {
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_pipe_close (as->pipe_in));
+ as->pipe_in = NULL;
+ }
+ if (NULL != as->pipe_out)
+ {
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_pipe_close (as->pipe_out));
+ as->pipe_out = NULL;
+ }
+
+ for (unsigned int i = 0; NULL != as->args[i]; i++)
+ GNUNET_free (as->args[i]);
+ GNUNET_free (as->args);
+ GNUNET_free (as);
+}
+
+
+/**
+ * Offer "system" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+system_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct SystemState *as = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (&as->system_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_system_start (
+ const char *label,
+ const char *config_file,
+ ...)
+{
+ struct SystemState *as;
+ va_list ap;
+ const char *arg;
+ unsigned int cnt;
+
+ as = GNUNET_new (struct SystemState);
+ cnt = 4; /* 0-2 reserved, +1 for NULL termination */
+ va_start (ap,
+ config_file);
+ while (NULL != (arg = va_arg (ap,
+ const char *)))
+ {
+ cnt++;
+ }
+ va_end (ap);
+ as->args = GNUNET_new_array (cnt,
+ char *);
+ as->args[0] = GNUNET_strdup ("taler-unified-setup");
+ as->args[1] = GNUNET_strdup ("-c");
+ as->args[2] = GNUNET_strdup (config_file);
+ cnt = 3;
+ va_start (ap,
+ config_file);
+ while (NULL != (arg = va_arg (ap,
+ const char *)))
+ {
+ as->args[cnt++] = GNUNET_strdup (arg);
+ }
+ va_end (ap);
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = as,
+ .label = label,
+ .run = &system_run,
+ .cleanup = &system_cleanup,
+ .traits = &system_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_system_start.c */
diff --git a/src/testing/testing_api_cmd_take_aml_decision.c b/src/testing/testing_api_cmd_take_aml_decision.c
new file mode 100644
index 000000000..c0e23de22
--- /dev/null
+++ b/src/testing/testing_api_cmd_take_aml_decision.c
@@ -0,0 +1,321 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_take_aml_decision.c
+ * @brief command for testing /aml/$OFFICER_PUB/decision
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "take_aml_decision" CMD.
+ */
+struct AmlDecisionState
+{
+
+ /**
+ * Auditor enable handle while operation is running.
+ */
+ struct TALER_EXCHANGE_AddAmlDecision *dh;
+
+ /**
+ * Our interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Reference to command to previous set officer command that gives
+ * us an officer_priv trait.
+ */
+ const char *officer_ref_cmd;
+
+ /**
+ * Reference to command to previous AML-triggering event that gives
+ * us a payto-hash trait.
+ */
+ const char *account_ref_cmd;
+
+ /**
+ * Payto hash of the account we are manipulating the AML settings for.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * New AML state to use.
+ */
+ enum TALER_AmlDecisionState new_state;
+
+ /**
+ * Justification given.
+ */
+ const char *justification;
+
+ /**
+ * KYC requirement to add.
+ */
+ const char *kyc_requirement;
+
+ /**
+ * Threshold transaction amount.
+ */
+ struct TALER_Amount new_threshold;
+
+ /**
+ * Expected response code.
+ */
+ unsigned int expected_response;
+};
+
+
+/**
+ * Callback to analyze the /aml-decision/$OFFICER_PUB response, just used to check
+ * if the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param adr response details
+ */
+static void
+take_aml_decision_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_AddAmlDecisionResponse *adr)
+{
+ struct AmlDecisionState *ds = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &adr->hr;
+
+ ds->dh = NULL;
+ if (ds->expected_response != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ ds->expected_response);
+ return;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+take_aml_decision_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AmlDecisionState *ds = cls;
+ struct GNUNET_TIME_Timestamp now;
+ const struct TALER_PaytoHashP *h_payto;
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv;
+ const struct TALER_TESTING_Command *ref;
+ json_t *kyc_requirements = NULL;
+ const char *exchange_url;
+
+ (void) cmd;
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ }
+ now = GNUNET_TIME_timestamp_get ();
+ ds->is = is;
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ ds->account_ref_cmd);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_h_payto (ref,
+ &h_payto))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ ds->officer_ref_cmd);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_officer_priv (ref,
+ &officer_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ ds->h_payto = *h_payto;
+ if (NULL != ds->kyc_requirement)
+ {
+ kyc_requirements = json_array ();
+ GNUNET_assert (NULL != kyc_requirements);
+ GNUNET_assert (0 ==
+ json_array_append (kyc_requirements,
+ json_string (ds->kyc_requirement)));
+ }
+
+ ds->dh = TALER_EXCHANGE_add_aml_decision (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ ds->justification,
+ now,
+ &ds->new_threshold,
+ h_payto,
+ ds->new_state,
+ kyc_requirements,
+ officer_priv,
+ &take_aml_decision_cb,
+ ds);
+ json_decref (kyc_requirements);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "take_aml_decision" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct AmlDecisionState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+take_aml_decision_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AmlDecisionState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_add_aml_decision_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ GNUNET_free (ds);
+}
+
+
+/**
+ * Offer internal data of a "AML decision" CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+take_aml_decision_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct AmlDecisionState *ws = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_h_payto (&ws->h_payto),
+ TALER_TESTING_make_trait_aml_justification (ws->justification),
+ TALER_TESTING_make_trait_aml_decision (&ws->new_state),
+ TALER_TESTING_make_trait_amount (&ws->new_threshold),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_take_aml_decision (
+ const char *label,
+ const char *ref_officer,
+ const char *ref_operation,
+ const char *new_threshold,
+ const char *justification,
+ enum TALER_AmlDecisionState new_state,
+ const char *kyc_requirement,
+ unsigned int expected_response)
+{
+ struct AmlDecisionState *ds;
+
+ ds = GNUNET_new (struct AmlDecisionState);
+ ds->officer_ref_cmd = ref_officer;
+ ds->account_ref_cmd = ref_operation;
+ ds->kyc_requirement = kyc_requirement;
+ if (GNUNET_OK !=
+ TALER_string_to_amount (new_threshold,
+ &ds->new_threshold))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %s\n",
+ new_threshold,
+ label);
+ GNUNET_assert (0);
+ }
+ ds->new_state = new_state;
+ ds->justification = justification;
+ ds->expected_response = expected_response;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &take_aml_decision_run,
+ .cleanup = &take_aml_decision_cleanup,
+ .traits = &take_aml_decision_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_take_aml_decision.c */
diff --git a/src/testing/testing_api_cmd_transfer_get.c b/src/testing/testing_api_cmd_transfer_get.c
index 2b133188e..405c8b7f9 100644
--- a/src/testing/testing_api_cmd_transfer_get.c
+++ b/src/testing/testing_api_cmd_transfer_get.c
@@ -44,9 +44,9 @@ struct TrackTransferState
const char *expected_wire_fee;
/**
- * Expected HTTP response code.
+ * Our command.
*/
- unsigned int expected_response_code;
+ const struct TALER_TESTING_Command *cmd;
/**
* Reference to any operation that can provide a WTID.
@@ -70,12 +70,6 @@ struct TrackTransferState
const char *total_amount_reference;
/**
- * Index to the WTID to pick, in case @a wtid_reference has
- * many on offer.
- */
- unsigned int index;
-
- /**
* Handle to a pending "track transfer" operation.
*/
struct TALER_EXCHANGE_TransfersGetHandle *tth;
@@ -84,6 +78,12 @@ struct TrackTransferState
* Interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
};
@@ -103,10 +103,8 @@ track_transfer_cleanup (void *cls,
if (NULL != tts->tth)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- tts->is->ip,
- cmd->label);
+ TALER_TESTING_command_incomplete (tts->is,
+ cmd->label);
TALER_EXCHANGE_transfers_get_cancel (tts->tth);
tts->tth = NULL;
}
@@ -120,199 +118,178 @@ track_transfer_cleanup (void *cls,
* wire fees and hashed wire details as well.
*
* @param cls closure.
- * @param http_status HTTP status code we got.
- * @param ec taler-specific error code.
- * @param exchange_pub public key the exchange used for signing
- * the response.
- * @param json original json reply (may include signatures, those
- * have then been validated already).
- * @param h_wire hash of the wire transfer address the transfer
- * went to, or NULL on error.
- * @param execution_time time when the exchange claims to have
- * performed the wire transfer.
- * @param total_amount total amount of the wire transfer, or NULL
- * if the exchange could not provide any @a wtid (set only
- * if @a http_status is "200 OK").
- * @param wire_fee wire fee that was charged by the exchange.
- * @param details_length length of the @a details array.
- * @param details array with details about the combined
- * transactions.
+ * @param tgr response details
*/
static void
track_transfer_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const json_t *json,
- const struct GNUNET_HashCode *h_wire,
- struct GNUNET_TIME_Absolute execution_time,
- const struct TALER_Amount *total_amount,
- const struct TALER_Amount *wire_fee,
- unsigned int details_length,
- const struct TALER_TrackTransferDetails *details)
+ const struct TALER_EXCHANGE_TransfersGetResponse *tgr)
{
struct TrackTransferState *tts = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &tgr->hr;
struct TALER_TESTING_Interpreter *is = tts->is;
- struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
struct TALER_Amount expected_amount;
- (void) exchange_pub;
tts->tth = NULL;
- if (tts->expected_response_code != http_status)
+ if (tts->expected_response_code != hr->http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- http_status,
- cmd->label,
- __FILE__,
- __LINE__);
- json_dumpf (json, stderr, 0);
- TALER_TESTING_interpreter_fail (is);
+ TALER_TESTING_unexpected_status (is,
+ hr->http_status,
+ tts->expected_response_code);
return;
}
- switch (http_status)
+ switch (hr->http_status)
{
case MHD_HTTP_OK:
- if (NULL == tts->expected_total_amount)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- if (NULL == tts->expected_wire_fee)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
- if (GNUNET_OK !=
- TALER_string_to_amount (tts->expected_total_amount,
- &expected_amount))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- if (0 != TALER_amount_cmp (total_amount,
- &expected_amount))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Total amount mismatch to command %s - "
- "%s vs %s\n",
- cmd->label,
- TALER_amount_to_string (total_amount),
- TALER_amount_to_string (&expected_amount));
- json_dumpf (json, stderr, 0);
- fprintf (stderr, "\n");
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
- if (GNUNET_OK !=
- TALER_string_to_amount (tts->expected_wire_fee,
- &expected_amount))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
- if (0 != TALER_amount_cmp (wire_fee,
- &expected_amount))
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Wire fee mismatch to command %s\n",
- cmd->label);
- json_dumpf (json, stderr, 0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
+ const struct TALER_EXCHANGE_TransferData *ta
+ = &tgr->details.ok.td;
- /**
- * Optionally checking: (1) wire-details for this transfer
- * match the ones from a referenced "deposit" operation -
- * or any operation that could provide wire-details. (2)
- * Total amount for this transfer matches the one from any
- * referenced command that could provide one.
- */if (NULL != tts->wire_details_reference)
- {
- const struct TALER_TESTING_Command *wire_details_cmd;
- const json_t *wire_details;
- struct GNUNET_HashCode h_wire_details;
-
- if (NULL == (wire_details_cmd
- = TALER_TESTING_interpreter_lookup_command
- (is, tts->wire_details_reference)))
+ if (NULL == tts->expected_total_amount)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_wire_details (wire_details_cmd,
- 0,
- &wire_details))
+ if (NULL == tts->expected_wire_fee)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- GNUNET_assert
- (GNUNET_OK ==
- TALER_JSON_merchant_wire_signature_hash (wire_details,
- &h_wire_details));
-
- if (0 != GNUNET_memcmp (&h_wire_details,
- h_wire))
+ if (GNUNET_OK !=
+ TALER_string_to_amount (tts->expected_total_amount,
+ &expected_amount))
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Wire hash missmath to command %s\n",
- cmd->label);
- json_dumpf (json, stderr, 0);
+ GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- }
- if (NULL != tts->total_amount_reference)
- {
- const struct TALER_TESTING_Command *total_amount_cmd;
- const struct TALER_Amount *total_amount_from_reference;
-
- if (NULL == (total_amount_cmd
- = TALER_TESTING_interpreter_lookup_command
- (is, tts->total_amount_reference)))
+ if (0 != TALER_amount_cmp (&ta->total_amount,
+ &expected_amount))
{
- GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Total amount mismatch to command %s - "
+ "%s vs %s\n",
+ tts->cmd->label,
+ TALER_amount_to_string (&ta->total_amount),
+ TALER_amount_to_string (&expected_amount));
+ json_dumpf (hr->reply,
+ stderr,
+ 0);
+ fprintf (stderr, "\n");
TALER_TESTING_interpreter_fail (is);
return;
}
if (GNUNET_OK !=
- TALER_TESTING_get_trait_amount_obj (total_amount_cmd,
- 0,
- &total_amount_from_reference))
+ TALER_string_to_amount (tts->expected_wire_fee,
+ &expected_amount))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- if (0 != TALER_amount_cmp (total_amount,
- total_amount_from_reference))
+ if (0 != TALER_amount_cmp (&ta->wire_fee,
+ &expected_amount))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Amount missmath to command %s\n",
- cmd->label);
- json_dumpf (json, stderr, 0);
+ "Wire fee mismatch to command %s\n",
+ tts->cmd->label);
+ json_dumpf (hr->reply,
+ stderr,
+ 0);
TALER_TESTING_interpreter_fail (is);
return;
}
- }
- }
+
+ /**
+ * Optionally checking: (1) wire-details for this transfer
+ * match the ones from a referenced "deposit" operation -
+ * or any operation that could provide wire-details. (2)
+ * Total amount for this transfer matches the one from any
+ * referenced command that could provide one.
+ */
+ if (NULL != tts->wire_details_reference)
+ {
+ const struct TALER_TESTING_Command *wire_details_cmd;
+ const char *payto_uri;
+ struct TALER_PaytoHashP h_payto;
+
+ wire_details_cmd
+ = TALER_TESTING_interpreter_lookup_command (is,
+ tts->
+ wire_details_reference);
+ if (NULL == wire_details_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_payto_uri (wire_details_cmd,
+ &payto_uri))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_payto_hash (payto_uri,
+ &h_payto);
+ if (0 != GNUNET_memcmp (&h_payto,
+ &ta->h_payto))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Wire hash missmath to command %s\n",
+ tts->cmd->label);
+ json_dumpf (hr->reply,
+ stderr,
+ 0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ }
+ if (NULL != tts->total_amount_reference)
+ {
+ const struct TALER_TESTING_Command *total_amount_cmd;
+ const struct TALER_Amount *total_amount_from_reference;
+
+ total_amount_cmd
+ = TALER_TESTING_interpreter_lookup_command (is,
+ tts->
+ total_amount_reference);
+ if (NULL == total_amount_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_amount (total_amount_cmd,
+ &total_amount_from_reference))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (0 != TALER_amount_cmp (&ta->total_amount,
+ total_amount_from_reference))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Amount mismatch in command %s\n",
+ tts->cmd->label);
+ json_dumpf (hr->reply,
+ stderr,
+ 0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ }
+ break;
+ } /* case OK */
+ } /* switch on status */
TALER_TESTING_interpreter_next (is);
}
@@ -334,19 +311,20 @@ track_transfer_run (void *cls,
struct TALER_WireTransferIdentifierRawP wtid;
const struct TALER_WireTransferIdentifierRawP *wtid_ptr;
+ tts->cmd = cmd;
/* If no reference is given, we'll use a all-zeros
* WTID */
- memset (&wtid, 0, sizeof (wtid));
+ memset (&wtid,
+ 0,
+ sizeof (wtid));
wtid_ptr = &wtid;
-
tts->is = is;
if (NULL != tts->wtid_reference)
{
const struct TALER_TESTING_Command *wtid_cmd;
- wtid_cmd = TALER_TESTING_interpreter_lookup_command
- (tts->is, tts->wtid_reference);
-
+ wtid_cmd = TALER_TESTING_interpreter_lookup_command (tts->is,
+ tts->wtid_reference);
if (NULL == wtid_cmd)
{
GNUNET_break (0);
@@ -354,8 +332,9 @@ track_transfer_run (void *cls,
return;
}
- if (GNUNET_OK != TALER_TESTING_get_trait_wtid
- (wtid_cmd, tts->index, &wtid_ptr))
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_wtid (wtid_cmd,
+ &wtid_ptr))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (tts->is);
@@ -363,41 +342,26 @@ track_transfer_run (void *cls,
}
GNUNET_assert (NULL != wtid_ptr);
}
- tts->tth = TALER_EXCHANGE_transfers_get (is->exchange,
- wtid_ptr,
- &track_transfer_cb,
- tts);
+ tts->tth = TALER_EXCHANGE_transfers_get (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ wtid_ptr,
+ &track_transfer_cb,
+ tts);
GNUNET_assert (NULL != tts->tth);
}
-/**
- * Make a "track transfer" CMD where no "expected"-arguments,
- * except the HTTP response code, are given. The best use case
- * is when what matters to check is the HTTP response code, e.g.
- * when a bogus WTID was passed.
- *
- * @param label the command label
- * @param wtid_reference reference to any command which can provide
- * a wtid. If NULL is given, then a all zeroed WTID is
- * used that will at 99.9999% probability NOT match any
- * existing WTID known to the exchange.
- * @param index index number of the WTID to track, in case there
- * are multiple on offer.
- * @param expected_response_code expected HTTP response code.
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_track_transfer_empty (const char *label,
const char *wtid_reference,
- unsigned int index,
unsigned int expected_response_code)
{
struct TrackTransferState *tts;
tts = GNUNET_new (struct TrackTransferState);
tts->wtid_reference = wtid_reference;
- tts->index = index;
tts->expected_response_code = expected_response_code;
{
struct TALER_TESTING_Command cmd = {
@@ -412,25 +376,9 @@ TALER_TESTING_cmd_track_transfer_empty (const char *label,
}
-/**
- * Make a "track transfer" command, specifying which amount and
- * wire fee are expected.
- *
- * @param label the command label.
- * @param wtid_reference reference to any command which can provide
- * a wtid. Will be the one tracked.
- * @param index in case there are multiple WTID offered, this
- * parameter selects a particular one.
- * @param expected_response_code expected HTTP response code.
- * @param expected_total_amount how much money we expect being moved
- * with this wire-transfer.
- * @param expected_wire_fee expected wire fee.
- * @return the command
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_track_transfer (const char *label,
const char *wtid_reference,
- unsigned int index,
unsigned int expected_response_code,
const char *expected_total_amount,
const char *expected_wire_fee)
@@ -439,7 +387,6 @@ TALER_TESTING_cmd_track_transfer (const char *label,
tts = GNUNET_new (struct TrackTransferState);
tts->wtid_reference = wtid_reference;
- tts->index = index;
tts->expected_response_code = expected_response_code;
tts->expected_total_amount = expected_total_amount;
tts->expected_wire_fee = expected_wire_fee;
diff --git a/src/testing/testing_api_cmd_twister_exec_client.c b/src/testing/testing_api_cmd_twister_exec_client.c
index bfcfe4591..bf83c1f80 100644
--- a/src/testing/testing_api_cmd_twister_exec_client.c
+++ b/src/testing/testing_api_cmd_twister_exec_client.c
@@ -26,7 +26,7 @@
*/
#include "platform.h"
-#include <taler/taler_testing_lib.h>
+#include "taler_testing_lib.h"
#include "taler_twister_testing_lib.h"
@@ -194,7 +194,7 @@ hack_response_code_cleanup
* @param index index number of the object to offer.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
hack_response_code_traits (void *cls,
const void **ret,
const char *trait,
@@ -203,7 +203,7 @@ hack_response_code_traits (void *cls,
struct HackResponseCodeState *hrcs = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0, &hrcs->proc),
+ TALER_TESTING_make_trait_process (&hrcs->proc),
TALER_TESTING_trait_end ()
};
@@ -232,15 +232,14 @@ hack_response_code_run (void *cls,
GNUNET_asprintf (&http_status, "%u",
hrcs->http_status);
- hrcs->proc = GNUNET_OS_start_process
- (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-twister",
- "taler-twister",
- "-c", hrcs->config_filename,
- "--responsecode", http_status,
- NULL);
+ hrcs->proc = GNUNET_OS_start_process (
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-twister",
+ "taler-twister",
+ "-c", hrcs->config_filename,
+ "--responsecode", http_status,
+ NULL);
if (NULL == hrcs->proc)
{
GNUNET_break (0);
@@ -252,16 +251,6 @@ hack_response_code_run (void *cls,
}
-/**
- * Define a "hack response code" CMD. This causes the next
- * response code (from the service proxied by the twister) to
- * be substituted with @a http_status.
- *
- * @param label command label
- * @param config_filename configuration filename.
- * @param http_status new response code to use
- * @return the command
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_hack_response_code (const char *label,
const char *config_filename,
@@ -272,16 +261,17 @@ TALER_TESTING_cmd_hack_response_code (const char *label,
hrcs = GNUNET_new (struct HackResponseCodeState);
hrcs->http_status = http_status;
hrcs->config_filename = config_filename;
-
- struct TALER_TESTING_Command cmd = {
- .label = label,
- .run = &hack_response_code_run,
- .cleanup = &hack_response_code_cleanup,
- .traits = &hack_response_code_traits,
- .cls = hrcs
- };
-
- return cmd;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .label = label,
+ .run = &hack_response_code_run,
+ .cleanup = &hack_response_code_cleanup,
+ .traits = &hack_response_code_traits,
+ .cls = hrcs
+ };
+
+ return cmd;
+ }
}
@@ -321,7 +311,7 @@ delete_object_cleanup
* @param index index number of the object to offer.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
delete_object_traits (void *cls,
const void **ret,
const char *trait,
@@ -330,7 +320,7 @@ delete_object_traits (void *cls,
struct DeleteObjectState *dos = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0, &dos->proc),
+ TALER_TESTING_make_trait_process (&dos->proc),
TALER_TESTING_trait_end ()
};
@@ -355,8 +345,7 @@ delete_object_run (void *cls,
{
struct DeleteObjectState *dos = cls;
- dos->proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ dos->proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-twister",
"taler-twister",
@@ -381,9 +370,8 @@ delete_object_run (void *cls,
* @param cmd the command being cleaned up.
*/
static void
-modify_object_cleanup
- (void *cls,
- const struct TALER_TESTING_Command *cmd)
+modify_object_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
{
struct ModifyObjectState *mos = cls;
@@ -409,7 +397,7 @@ modify_object_cleanup
* @param index index number of the object to offer.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
modify_object_traits (void *cls,
const void **ret,
const char *trait,
@@ -418,7 +406,7 @@ modify_object_traits (void *cls,
struct ModifyObjectState *mos = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0, &mos->proc),
+ TALER_TESTING_make_trait_process (&mos->proc),
TALER_TESTING_trait_end ()
};
@@ -443,8 +431,7 @@ modify_object_dl_run (void *cls,
{
struct ModifyObjectState *mos = cls;
- mos->proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ mos->proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-twister",
"taler-twister",
@@ -476,8 +463,7 @@ modify_object_ul_run (void *cls,
{
struct ModifyObjectState *mos = cls;
- mos->proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ mos->proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-twister",
"taler-twister",
@@ -496,15 +482,37 @@ modify_object_ul_run (void *cls,
/**
- * Create a "delete object" CMD. This command deletes
- * the JSON object pointed by @a path.
+ * Run a "modify header" CMD
*
- * @param label command label
- * @param config_filename configuration filename.
- * @param path object-like path notation to point the object
- * to delete.
- * @return the command
+ * @param cls closure.
+ * @param cmd the command being run.
+ * @param is the interpreter state.
*/
+static void
+modify_header_dl_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct ModifyObjectState *mos = cls;
+
+ mos->proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-twister",
+ "taler-twister",
+ "-H", mos->path,
+ "--value", mos->value,
+ "-c", mos->config_filename,
+ NULL);
+ if (NULL == mos->proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
struct TALER_TESTING_Command
TALER_TESTING_cmd_delete_object (const char *label,
const char *config_filename,
@@ -564,16 +572,15 @@ flip_object_cleanup
* @param index index number of the object to offer.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
flip_object_traits (void *cls,
const void **ret,
const char *trait,
unsigned int index)
{
-
struct FlipObjectState *fos = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0, &fos->proc),
+ TALER_TESTING_make_trait_process (&fos->proc),
TALER_TESTING_trait_end ()
};
@@ -598,8 +605,7 @@ flip_upload_run (void *cls,
{
struct FlipObjectState *fos = cls;
- fos->proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ fos->proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-twister",
"taler-twister",
@@ -630,8 +636,7 @@ flip_download_run (void *cls,
{
struct FlipObjectState *fos = cls;
- fos->proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ fos->proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-twister",
"taler-twister",
@@ -648,15 +653,6 @@ flip_download_run (void *cls,
}
-/**
- * Define a "flip object" command, for objects to upload.
- *
- * @param label command label
- * @param config_filename configuration filename.
- * @param path object-like path notation to point the object
- * to flip.
- * @return the command
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_flip_upload (const char *label,
const char *config_filename,
@@ -667,28 +663,20 @@ TALER_TESTING_cmd_flip_upload (const char *label,
dos = GNUNET_new (struct FlipObjectState);
dos->path = path;
dos->config_filename = config_filename;
-
- struct TALER_TESTING_Command cmd = {
- .label = label,
- .run = &flip_upload_run,
- .cleanup = &flip_object_cleanup,
- .traits = &flip_object_traits,
- .cls = dos
- };
-
- return cmd;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .label = label,
+ .run = &flip_upload_run,
+ .cleanup = &flip_object_cleanup,
+ .traits = &flip_object_traits,
+ .cls = dos
+ };
+
+ return cmd;
+ }
}
-/**
- * Define a "flip object" command, for objects to download.
- *
- * @param label command label
- * @param config_filename configuration filename.
- * @param path object-like path notation to point the object
- * to flip.
- * @return the command
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_flip_download (const char *label,
const char *config_filename,
@@ -699,16 +687,17 @@ TALER_TESTING_cmd_flip_download (const char *label,
dos = GNUNET_new (struct FlipObjectState);
dos->path = path;
dos->config_filename = config_filename;
-
- struct TALER_TESTING_Command cmd = {
- .label = label,
- .run = &flip_download_run,
- .cleanup = &flip_object_cleanup,
- .traits = &flip_object_traits,
- .cls = dos
- };
-
- return cmd;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .label = label,
+ .run = &flip_download_run,
+ .cleanup = &flip_object_cleanup,
+ .traits = &flip_object_traits,
+ .cls = dos
+ };
+
+ return cmd;
+ }
}
@@ -747,7 +736,7 @@ malform_request_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
malform_request_traits (void *cls,
const void **ret,
const char *trait,
@@ -755,7 +744,7 @@ malform_request_traits (void *cls,
{
struct MalformRequestState *mrs = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0, &mrs->proc),
+ TALER_TESTING_make_trait_process (&mrs->proc),
TALER_TESTING_trait_end ()
};
@@ -780,8 +769,7 @@ malform_request_run (void *cls,
{
struct MalformRequestState *mrs = cls;
- mrs->proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ mrs->proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-twister",
"taler-twister",
@@ -806,9 +794,8 @@ malform_request_run (void *cls,
* @param cmd the command being cleaned up.
*/
static void
-malform_response_cleanup
- (void *cls,
- const struct TALER_TESTING_Command *cmd)
+malform_response_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
{
struct MalformResponseState *mrs = cls;
@@ -834,7 +821,7 @@ malform_response_cleanup
* @param index index number of the object to offer.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
malform_response_traits (void *cls,
const void **ret,
const char *trait,
@@ -842,7 +829,7 @@ malform_response_traits (void *cls,
{
struct MalformResponseState *mrs = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_process (0, &mrs->proc),
+ TALER_TESTING_make_trait_process (&mrs->proc),
TALER_TESTING_trait_end ()
};
@@ -867,8 +854,7 @@ malform_response_run (void *cls,
{
struct MalformResponseState *mrs = cls;
- mrs->proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ mrs->proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-twister",
"taler-twister",
@@ -885,14 +871,6 @@ malform_response_run (void *cls,
}
-/**
- * Create a "malform request" CMD. This command makes the
- * next request randomly malformed (by truncating it).
- *
- * @param label command label
- * @param config_filename configuration filename.
- * @return the command
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_malform_request (const char *label,
const char *config_filename)
@@ -901,28 +879,20 @@ TALER_TESTING_cmd_malform_request (const char *label,
mrs = GNUNET_new (struct MalformRequestState);
mrs->config_filename = config_filename;
-
- struct TALER_TESTING_Command cmd = {
- .label = label,
- .run = &malform_request_run,
- .cleanup = &malform_request_cleanup,
- .traits = &malform_request_traits,
- .cls = mrs
- };
-
- return cmd;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .label = label,
+ .run = &malform_request_run,
+ .cleanup = &malform_request_cleanup,
+ .traits = &malform_request_traits,
+ .cls = mrs
+ };
+
+ return cmd;
+ }
}
-/**
- * Create a "malform response" CMD. This command makes
- * the next response randomly malformed (by truncating it).
- *
- * @param label command label
- * @param config_filename configuration filename.
- *
- * @return the command
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_malform_response (const char *label,
const char *config_filename)
@@ -945,19 +915,6 @@ TALER_TESTING_cmd_malform_response (const char *label,
}
-/**
- * Create a "modify object" CMD. This command instructs
- * the twister to modify the next object that is downloaded
- * from the proxied service.
- *
- * @param label command label
- * @param config_filename configuration filename.
- * @param path object-like path notation to point the object
- * to modify.
- * @param value value to put as the object's.
- *
- * @return the command
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_modify_object_dl (const char *label,
const char *config_filename,
@@ -970,31 +927,20 @@ TALER_TESTING_cmd_modify_object_dl (const char *label,
mos->path = path;
mos->value = value;
mos->config_filename = config_filename;
-
- struct TALER_TESTING_Command cmd = {
- .label = label,
- .run = &modify_object_dl_run,
- .cleanup = &modify_object_cleanup,
- .traits = &modify_object_traits,
- .cls = mos
- };
-
- return cmd;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .label = label,
+ .run = &modify_object_dl_run,
+ .cleanup = &modify_object_cleanup,
+ .traits = &modify_object_traits,
+ .cls = mos
+ };
+
+ return cmd;
+ }
}
-/**
- * Create a "modify object" CMD. This command instructs
- * the twister to modify the next object that will be uploaded
- * to the proxied service.
- *
- * @param label command label
- * @param config_filename configuration filename.
- * @param path object-like path notation pointing the object
- * to modify.
- * @param value value to put as the object's.
- * @return the command
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_modify_object_ul (const char *label,
const char *config_filename,
@@ -1007,16 +953,43 @@ TALER_TESTING_cmd_modify_object_ul (const char *label,
mos->path = path;
mos->value = value;
mos->config_filename = config_filename;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .label = label,
+ .run = &modify_object_ul_run,
+ .cleanup = &modify_object_cleanup,
+ .traits = &modify_object_traits,
+ .cls = mos
+ };
+
+ return cmd;
+ }
+}
- struct TALER_TESTING_Command cmd = {
- .label = label,
- .run = &modify_object_ul_run,
- .cleanup = &modify_object_cleanup,
- .traits = &modify_object_traits,
- .cls = mos
- };
- return cmd;
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_modify_header_dl (const char *label,
+ const char *config_filename,
+ const char *path,
+ const char *value)
+{
+ struct ModifyObjectState *mos;
+
+ mos = GNUNET_new (struct ModifyObjectState);
+ mos->path = path;
+ mos->value = value;
+ mos->config_filename = config_filename;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .label = label,
+ .run = &modify_header_dl_run,
+ .cleanup = &modify_object_cleanup,
+ .traits = &modify_object_traits,
+ .cls = mos
+ };
+
+ return cmd;
+ }
}
diff --git a/src/testing/testing_api_cmd_wire.c b/src/testing/testing_api_cmd_wire.c
index c8946bb93..41ff7a978 100644
--- a/src/testing/testing_api_cmd_wire.c
+++ b/src/testing/testing_api_cmd_wire.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018 Taler Systems SA
+ Copyright (C) 2018, 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
@@ -39,6 +39,11 @@ struct WireState
struct TALER_EXCHANGE_WireHandle *wh;
/**
+ * Our command.
+ */
+ const struct TALER_TESTING_Command *cmd;
+
+ /**
* Which wire-method we expect is offered by the exchange.
*/
const char *expected_method;
@@ -72,74 +77,93 @@ struct WireState
* that the wire fee is acceptable too.
*
* @param cls closure.
- * @param http_status HTTP response code.
- * @param ec taler-specific error code.
- * @param accounts_len length of the @a accounts array.
- * @param accounts list of wire accounts of the exchange,
- * NULL on error.
- * @param full_reply the complete response from the exchange
+ * @param wr response details
*/
static void
wire_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- unsigned int accounts_len,
- const struct TALER_EXCHANGE_WireAccount *accounts,
- const json_t *full_reply)
+ const struct TALER_EXCHANGE_WireResponse *wr)
{
struct WireState *ws = cls;
- struct TALER_TESTING_Command *cmd = &ws->is->commands[ws->is->ip];
+ const struct TALER_EXCHANGE_HttpResponse *hr = &wr->hr;
struct TALER_Amount expected_fee;
- (void) ec;
- (void) full_reply;
TALER_LOG_DEBUG ("Checking parsed /wire response\n");
ws->wh = NULL;
- if (ws->expected_response_code != http_status)
+ if (ws->expected_response_code != hr->http_status)
{
- GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Received unexpected status code %u\n",
+ hr->http_status);
TALER_TESTING_interpreter_fail (ws->is);
return;
}
- if (MHD_HTTP_OK == http_status)
+ if (MHD_HTTP_OK == hr->http_status)
{
+ unsigned int accounts_len
+ = wr->details.ok.accounts_len;
+ unsigned int fees_len
+ = wr->details.ok.fees_len;
+ const struct TALER_EXCHANGE_WireAccount *accounts
+ = wr->details.ok.accounts;
+ const struct TALER_EXCHANGE_WireFeesByMethod *fees
+ = wr->details.ok.fees;
+
for (unsigned int i = 0; i<accounts_len; i++)
{
char *method;
method = TALER_payto_get_method (accounts[i].payto_uri);
+ if (NULL == method)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ws->is);
+ return;
+ }
if (0 == strcmp (ws->expected_method,
method))
{
ws->method_found = GNUNET_OK;
- if (NULL != ws->expected_fee)
+ }
+ GNUNET_free (method);
+ }
+ if (NULL != ws->expected_fee)
+ {
+ bool fee_found = false;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (ws->expected_fee,
+ &expected_fee));
+ for (unsigned int i = 0; i<fees_len; i++)
+ {
+ if (0 != strcmp (fees[i].method,
+ ws->expected_method))
+ continue;
+ for (const struct TALER_EXCHANGE_WireAggregateFees *waf
+ = fees[i].fees_head;
+ NULL != waf;
+ waf = waf->next)
{
- GNUNET_assert
- (GNUNET_OK ==
- TALER_string_to_amount (ws->expected_fee,
- &expected_fee));
- const struct TALER_EXCHANGE_WireAggregateFees *waf;
- for (waf = accounts[i].fees;
- NULL != waf;
- waf = waf->next)
+ if (0 != TALER_amount_cmp (&waf->fees.wire,
+ &expected_fee))
{
- if (0 != TALER_amount_cmp (&waf->wire_fee,
- &expected_fee))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Wire fee mismatch to command %s\n",
- cmd->label);
- TALER_TESTING_interpreter_fail (ws->is);
- GNUNET_free (method);
- return;
- }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Wire fee mismatch to command %s\n",
+ ws->cmd->label);
+ TALER_TESTING_interpreter_fail (ws->is);
+ return;
}
+ fee_found = true;
}
}
- TALER_LOG_DEBUG ("Freeing method '%s'\n",
- method);
- GNUNET_free (method);
+ if (! fee_found)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "/wire does not contain expected fee '%s'\n",
+ ws->expected_fee);
+ TALER_TESTING_interpreter_fail (ws->is);
+ return;
+ }
}
if (GNUNET_OK != ws->method_found)
{
@@ -150,7 +174,6 @@ wire_cb (void *cls,
return;
}
}
-
TALER_TESTING_interpreter_next (ws->is);
}
@@ -169,11 +192,14 @@ wire_run (void *cls,
{
struct WireState *ws = cls;
- (void) cmd;
+ ws->cmd = cmd;
ws->is = is;
- ws->wh = TALER_EXCHANGE_wire (is->exchange,
- &wire_cb,
- ws);
+ ws->wh = TALER_EXCHANGE_wire (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ &wire_cb,
+ ws);
}
@@ -192,10 +218,8 @@ wire_cleanup (void *cls,
if (NULL != ws->wh)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- ws->is->ip,
- cmd->label);
+ TALER_TESTING_command_incomplete (ws->is,
+ cmd->label);
TALER_EXCHANGE_wire_cancel (ws->wh);
ws->wh = NULL;
}
@@ -203,17 +227,6 @@ wire_cleanup (void *cls,
}
-/**
- * Create a "wire" command.
- *
- * @param label the command label.
- * @param expected_method which wire-transfer method is expected
- * to be offered by the exchange.
- * @param expected_fee the fee the exchange should charge.
- * @param expected_response_code the HTTP response the exchange
- * should return.
- * @return the command.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_wire (const char *label,
const char *expected_method,
diff --git a/src/testing/testing_api_cmd_wire_add.c b/src/testing/testing_api_cmd_wire_add.c
new file mode 100644
index 000000000..d2a15894a
--- /dev/null
+++ b/src/testing/testing_api_cmd_wire_add.c
@@ -0,0 +1,244 @@
+/*
+ This file is part of TALER
+ 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 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 testing/testing_api_cmd_wire_add.c
+ * @brief command for testing POST to /management/wire
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "wire_add" CMD.
+ */
+struct WireAddState
+{
+
+ /**
+ * Wire enable handle while operation is running.
+ */
+ struct TALER_EXCHANGE_ManagementWireEnableHandle *dh;
+
+ /**
+ * Our interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Account to add.
+ */
+ const char *payto_uri;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Should we make the request with a bad master_sig signature?
+ */
+ bool bad_sig;
+};
+
+
+/**
+ * Callback to analyze the /management/wire response, just used to check
+ * if the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param wer response details
+ */
+static void
+wire_add_cb (void *cls,
+ const struct TALER_EXCHANGE_ManagementWireEnableResponse *wer)
+{
+ struct WireAddState *ds = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &wer->hr;
+
+ ds->dh = NULL;
+ if (ds->expected_response_code != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ ds->expected_response_code);
+ return;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+wire_add_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct WireAddState *ds = cls;
+ struct TALER_MasterSignatureP master_sig1;
+ struct TALER_MasterSignatureP master_sig2;
+ struct GNUNET_TIME_Timestamp now;
+ json_t *credit_rest;
+ json_t *debit_rest;
+ const char *exchange_url;
+
+ (void) cmd;
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ }
+ now = GNUNET_TIME_timestamp_get ();
+ ds->is = is;
+ debit_rest = json_array ();
+ credit_rest = json_array ();
+ if (ds->bad_sig)
+ {
+ memset (&master_sig1,
+ 42,
+ sizeof (master_sig1));
+ memset (&master_sig2,
+ 42,
+ sizeof (master_sig2));
+ }
+ else
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+ const struct TALER_MasterPrivateKeyP *master_priv;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_master_priv (exchange_cmd,
+ &master_priv));
+ TALER_exchange_offline_wire_add_sign (ds->payto_uri,
+ NULL,
+ debit_rest,
+ credit_rest,
+ now,
+ master_priv,
+ &master_sig1);
+ TALER_exchange_wire_signature_make (ds->payto_uri,
+ NULL,
+ debit_rest,
+ credit_rest,
+ master_priv,
+ &master_sig2);
+ }
+ ds->dh = TALER_EXCHANGE_management_enable_wire (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ ds->payto_uri,
+ NULL,
+ debit_rest,
+ credit_rest,
+ now,
+ &master_sig1,
+ &master_sig2,
+ NULL,
+ 0LL,
+ &wire_add_cb,
+ ds);
+ json_decref (debit_rest);
+ json_decref (credit_rest);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "wire_add" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct WireAddState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+wire_add_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct WireAddState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_management_enable_wire_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ GNUNET_free (ds);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wire_add (const char *label,
+ const char *payto_uri,
+ unsigned int expected_http_status,
+ bool bad_sig)
+{
+ struct WireAddState *ds;
+
+ ds = GNUNET_new (struct WireAddState);
+ ds->expected_response_code = expected_http_status;
+ ds->bad_sig = bad_sig;
+ ds->payto_uri = payto_uri;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &wire_add_run,
+ .cleanup = &wire_add_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_wire_add.c */
diff --git a/src/testing/testing_api_cmd_wire_del.c b/src/testing/testing_api_cmd_wire_del.c
new file mode 100644
index 000000000..50ebfc7cb
--- /dev/null
+++ b/src/testing/testing_api_cmd_wire_del.c
@@ -0,0 +1,220 @@
+/*
+ This file is part of TALER
+ 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 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 testing/testing_api_cmd_wire_del.c
+ * @brief command for testing POST to /management/wire
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "wire_del" CMD.
+ */
+struct WireDelState
+{
+
+ /**
+ * Wire enable handle while operation is running.
+ */
+ struct TALER_EXCHANGE_ManagementWireDisableHandle *dh;
+
+ /**
+ * Our interpreter.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Account to del.
+ */
+ const char *payto_uri;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Should we make the request with a bad master_sig signature?
+ */
+ bool bad_sig;
+};
+
+
+/**
+ * Callback to analyze the /management/wire response, just used to check
+ * if the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param wdr response details
+ */
+static void
+wire_del_cb (void *cls,
+ const struct TALER_EXCHANGE_ManagementWireDisableResponse *wdr)
+{
+ struct WireDelState *ds = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &wdr->hr;
+
+ ds->dh = NULL;
+ if (ds->expected_response_code != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ ds->expected_response_code);
+
+ return;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+wire_del_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct WireDelState *ds = cls;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_TIME_Timestamp now;
+ const char *exchange_url;
+
+ (void) cmd;
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ }
+ now = GNUNET_TIME_timestamp_get ();
+ ds->is = is;
+ if (ds->bad_sig)
+ {
+ memset (&master_sig,
+ 42,
+ sizeof (master_sig));
+ }
+ else
+ {
+ const struct TALER_TESTING_Command *exchange_cmd;
+ const struct TALER_MasterPrivateKeyP *master_priv;
+
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_master_priv (exchange_cmd,
+ &master_priv));
+ TALER_exchange_offline_wire_del_sign (ds->payto_uri,
+ now,
+ master_priv,
+ &master_sig);
+ }
+ ds->dh = TALER_EXCHANGE_management_disable_wire (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ ds->payto_uri,
+ now,
+ &master_sig,
+ &wire_del_cb,
+ ds);
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "wire_del" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct WireDelState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+wire_del_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct WireDelState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ TALER_TESTING_command_incomplete (ds->is,
+ cmd->label);
+ TALER_EXCHANGE_management_disable_wire_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ GNUNET_free (ds);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wire_del (const char *label,
+ const char *payto_uri,
+ unsigned int expected_http_status,
+ bool bad_sig)
+{
+ struct WireDelState *ds;
+
+ ds = GNUNET_new (struct WireDelState);
+ ds->expected_response_code = expected_http_status;
+ ds->bad_sig = bad_sig;
+ ds->payto_uri = payto_uri;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &wire_del_run,
+ .cleanup = &wire_del_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_wire_del.c */
diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c
index 995eca676..f8ff0205b 100644
--- a/src/testing/testing_api_cmd_withdraw.c
+++ b/src/testing/testing_api_cmd_withdraw.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018-2020 Taler Systems SA
+ Copyright (C) 2018-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
@@ -27,6 +27,7 @@
#include <microhttpd.h>
#include <gnunet/gnunet_curl_lib.h>
#include "taler_signatures.h"
+#include "taler_extensions.h"
#include "taler_testing_lib.h"
#include "backoff.h"
@@ -60,6 +61,17 @@ struct WithdrawState
const char *reserve_reference;
/**
+ * Reference to a withdraw or reveal operation from which we should
+ * reuse the private coin key, or NULL for regular withdrawal.
+ */
+ const char *reuse_coin_key_ref;
+
+ /**
+ * Our command.
+ */
+ const struct TALER_TESTING_Command *cmd;
+
+ /**
* String describing the denomination value we should withdraw.
* A corresponding denomination key must exist in the exchange's
* offerings. Can be NULL if @e pk is set instead.
@@ -79,6 +91,37 @@ struct WithdrawState
char *exchange_url;
/**
+ * URI if the reserve we are withdrawing from.
+ */
+ char *reserve_payto_uri;
+
+ /**
+ * Private key of the reserve we are withdrawing from.
+ */
+ struct TALER_ReservePrivateKeyP reserve_priv;
+
+ /**
+ * Public key of the reserve we are withdrawing from.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Blinding key used during the operation.
+ */
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+
+ /**
+ * Values contributed from the exchange during the
+ * withdraw protocol.
+ */
+ struct TALER_ExchangeWithdrawValues exchange_vals;
+
+ /**
* Interpreter state (during command).
*/
struct TALER_TESTING_Interpreter *is;
@@ -92,18 +135,30 @@ struct WithdrawState
/**
* Private key material of the coin, set by the interpreter.
*/
- struct TALER_PlanchetSecretsP ps;
+ struct TALER_PlanchetMasterSecretP ps;
+
+ /**
+ * An age > 0 signifies age restriction is required
+ */
+ uint8_t age;
+
+ /**
+ * If age > 0, put here the corresponding age commitment with its proof and
+ * its hash, respectively.
+ */
+ struct TALER_AgeCommitmentProof age_commitment_proof;
+ struct TALER_AgeCommitmentHash h_age_commitment;
/**
* Reserve history entry that corresponds to this operation.
* Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL.
*/
- struct TALER_EXCHANGE_ReserveHistory reserve_history;
+ struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
/**
* Withdraw handle (while operation is running).
*/
- struct TALER_EXCHANGE_WithdrawHandle *wsh;
+ struct TALER_EXCHANGE_BatchWithdrawHandle *wsh;
/**
* Task scheduled to try later.
@@ -121,6 +176,18 @@ struct WithdrawState
struct GNUNET_TIME_Relative total_backoff;
/**
+ * Set to the KYC requirement payto hash *if* the exchange replied with a
+ * request for KYC.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Set to the KYC requirement row *if* the exchange replied with
+ * a request for KYC.
+ */
+ uint64_t requirement_row;
+
+ /**
* Expected HTTP response code to the request.
*/
unsigned int expected_response_code;
@@ -158,8 +225,7 @@ do_retry (void *cls)
struct WithdrawState *ws = cls;
ws->retry_task = NULL;
- ws->is->commands[ws->is->ip].last_req_time
- = GNUNET_TIME_absolute_get ();
+ TALER_TESTING_touch_cmd (ws->is);
withdraw_run (ws,
NULL,
ws->is);
@@ -172,42 +238,36 @@ do_retry (void *cls)
* in the state.
*
* @param cls closure.
- * @param http_status HTTP response code.
- * @param ec taler-specific error code.
- * @param sig signature over the coin, NULL on error.
- * @param full_response raw response.
+ * @param wr withdraw response details
*/
static void
reserve_withdraw_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const struct TALER_DenominationSignature *sig,
- const json_t *full_response)
+ const struct TALER_EXCHANGE_BatchWithdrawResponse *wr)
{
struct WithdrawState *ws = cls;
struct TALER_TESTING_Interpreter *is = ws->is;
ws->wsh = NULL;
- if (ws->expected_response_code != http_status)
+ if (ws->expected_response_code != wr->hr.http_status)
{
if (0 != ws->do_retry)
{
- if (TALER_EC_WITHDRAW_RESERVE_UNKNOWN != ec)
+ if (TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN != wr->hr.ec)
ws->do_retry--; /* we don't count reserve unknown as failures here */
- if ( (0 == http_status) ||
- (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
- (TALER_EC_WITHDRAW_INSUFFICIENT_FUNDS == ec) ||
- (TALER_EC_WITHDRAW_RESERVE_UNKNOWN == ec) ||
- (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
+ if ( (0 == wr->hr.http_status) ||
+ (TALER_EC_GENERIC_DB_SOFT_FAILURE == wr->hr.ec) ||
+ (TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS == wr->hr.ec) ||
+ (TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN == wr->hr.ec) ||
+ (MHD_HTTP_INTERNAL_SERVER_ERROR == wr->hr.http_status) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Retrying withdraw failed with %u/%d\n",
- http_status,
- (int) ec);
+ wr->hr.http_status,
+ (int) wr->hr.ec);
/* on DB conflicts, do not use backoff */
- if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
+ if (TALER_EC_GENERIC_DB_SOFT_FAILURE == wr->hr.ec)
ws->backoff = GNUNET_TIME_UNIT_ZERO;
- else if (TALER_EC_WITHDRAW_RESERVE_UNKNOWN != ec)
+ else if (TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN != wr->hr.ec)
ws->backoff = EXCHANGE_LIB_BACKOFF (ws->backoff);
else
ws->backoff = GNUNET_TIME_relative_max (UNKNOWN_MIN_BACKOFF,
@@ -216,61 +276,62 @@ reserve_withdraw_cb (void *cls,
UNKNOWN_MAX_BACKOFF);
ws->total_backoff = GNUNET_TIME_relative_add (ws->total_backoff,
ws->backoff);
- ws->is->commands[ws->is->ip].num_tries++;
+ TALER_TESTING_inc_tries (ws->is);
ws->retry_task = GNUNET_SCHEDULER_add_delayed (ws->backoff,
&do_retry,
ws);
return;
}
}
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d to command %s in %s:%u\n",
- http_status,
- (int) ec,
- TALER_TESTING_interpreter_get_current_label (is),
- __FILE__,
- __LINE__);
- json_dumpf (full_response,
- stderr,
- 0);
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
+ TALER_TESTING_unexpected_status_with_body (is,
+ wr->hr.http_status,
+ ws->expected_response_code,
+ wr->hr.reply);
return;
}
- switch (http_status)
+ switch (wr->hr.http_status)
{
case MHD_HTTP_OK:
- if (NULL == sig)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- ws->sig.rsa_signature
- = GNUNET_CRYPTO_rsa_signature_dup (sig->rsa_signature);
+ GNUNET_assert (1 == wr->details.ok.num_coins);
+ TALER_denom_sig_copy (&ws->sig,
+ &wr->details.ok.coins[0].sig);
+ ws->coin_priv = wr->details.ok.coins[0].coin_priv;
+ ws->bks = wr->details.ok.coins[0].bks;
+ TALER_denom_ewv_copy (&ws->exchange_vals,
+ &wr->details.ok.coins[0].exchange_vals);
if (0 != ws->total_backoff.rel_value_us)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Total withdraw backoff for %s was %s\n",
- is->commands[is->ip].label,
+ ws->cmd->label,
GNUNET_STRINGS_relative_time_to_string (ws->total_backoff,
- GNUNET_YES));
+ true));
}
break;
case MHD_HTTP_FORBIDDEN:
/* nothing to check */
break;
- case MHD_HTTP_CONFLICT:
+ case MHD_HTTP_NOT_FOUND:
/* nothing to check */
break;
- case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_CONFLICT:
/* nothing to check */
break;
+ case MHD_HTTP_GONE:
+ /* theoretically could check that the key was actually */
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* KYC required */
+ ws->requirement_row =
+ wr->details.unavailable_for_legal_reasons.requirement_row;
+ ws->h_payto
+ = wr->details.unavailable_for_legal_reasons.h_payto;
+ break;
default:
/* Unsupported status code (by test harness) */
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Withdraw test command does not support status code %u\n",
- http_status);
+ wr->hr.http_status);
GNUNET_break (0);
break;
}
@@ -291,7 +352,9 @@ withdraw_run (void *cls,
const struct TALER_TESTING_Command *create_reserve;
const struct TALER_EXCHANGE_DenomPublicKey *dpk;
- (void) cmd;
+ if (NULL != cmd)
+ ws->cmd = cmd;
+ ws->is = is;
create_reserve
= TALER_TESTING_interpreter_lookup_command (
is,
@@ -304,19 +367,53 @@ withdraw_run (void *cls,
}
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_priv (create_reserve,
- 0,
&rp))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- TALER_planchet_setup_random (&ws->ps);
- ws->is = is;
+ if (NULL == ws->exchange_url)
+ ws->exchange_url
+ = GNUNET_strdup (TALER_TESTING_get_exchange_url (is));
+ ws->reserve_priv = *rp;
+ GNUNET_CRYPTO_eddsa_key_get_public (&ws->reserve_priv.eddsa_priv,
+ &ws->reserve_pub.eddsa_pub);
+ ws->reserve_payto_uri
+ = TALER_reserve_make_payto (ws->exchange_url,
+ &ws->reserve_pub);
+
+ if (NULL == ws->reuse_coin_key_ref)
+ {
+ TALER_planchet_master_setup_random (&ws->ps);
+ }
+ else
+ {
+ const struct TALER_PlanchetMasterSecretP *ps;
+ const struct TALER_TESTING_Command *cref;
+ char *cstr;
+ unsigned int index;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_parse_coin_reference (
+ ws->reuse_coin_key_ref,
+ &cstr,
+ &index));
+ cref = TALER_TESTING_interpreter_lookup_command (is,
+ cstr);
+ GNUNET_assert (NULL != cref);
+ GNUNET_free (cstr);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_planchet_secret (cref,
+ &ps));
+ ws->ps = *ps;
+ }
+
if (NULL == ws->pk)
{
- dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange),
- &ws->amount);
+ dpk = TALER_TESTING_find_pk (TALER_TESTING_get_keys (is),
+ &ws->amount,
+ ws->age > 0);
if (NULL == dpk)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -334,18 +431,30 @@ withdraw_run (void *cls,
{
ws->amount = ws->pk->value;
}
+
ws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;
- GNUNET_assert (GNUNET_OK ==
+ GNUNET_assert (0 <=
TALER_amount_add (&ws->reserve_history.amount,
&ws->amount,
- &ws->pk->fee_withdraw));
- ws->reserve_history.details.withdraw.fee = ws->pk->fee_withdraw;
- ws->wsh = TALER_EXCHANGE_withdraw (is->exchange,
- ws->pk,
- rp,
- &ws->ps,
- &reserve_withdraw_cb,
- ws);
+ &ws->pk->fees.withdraw));
+ ws->reserve_history.details.withdraw.fee = ws->pk->fees.withdraw;
+ {
+ struct TALER_EXCHANGE_WithdrawCoinInput wci = {
+ .pk = ws->pk,
+ .ps = &ws->ps,
+ .ach = 0 < ws->age ? &ws->h_age_commitment : NULL
+ };
+
+ ws->wsh = TALER_EXCHANGE_batch_withdraw (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ rp,
+ 1,
+ &wci,
+ &reserve_withdraw_cb,
+ ws);
+ }
if (NULL == ws->wsh)
{
GNUNET_break (0);
@@ -370,10 +479,9 @@ withdraw_cleanup (void *cls,
if (NULL != ws->wsh)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %s did not complete\n",
- cmd->label);
- TALER_EXCHANGE_withdraw_cancel (ws->wsh);
+ TALER_TESTING_command_incomplete (ws->is,
+ cmd->label);
+ TALER_EXCHANGE_batch_withdraw_cancel (ws->wsh);
ws->wsh = NULL;
}
if (NULL != ws->retry_task)
@@ -381,17 +489,17 @@ withdraw_cleanup (void *cls,
GNUNET_SCHEDULER_cancel (ws->retry_task);
ws->retry_task = NULL;
}
- if (NULL != ws->sig.rsa_signature)
- {
- GNUNET_CRYPTO_rsa_signature_free (ws->sig.rsa_signature);
- ws->sig.rsa_signature = NULL;
- }
+ TALER_denom_sig_free (&ws->sig);
+ TALER_denom_ewv_free (&ws->exchange_vals);
if (NULL != ws->pk)
{
TALER_EXCHANGE_destroy_denomination_key (ws->pk);
ws->pk = NULL;
}
- GNUNET_free_non_null (ws->exchange_url);
+ if (ws->age > 0)
+ TALER_age_commitment_proof_free (&ws->age_commitment_proof);
+ GNUNET_free (ws->exchange_url);
+ GNUNET_free (ws->reserve_payto_uri);
GNUNET_free (ws);
}
@@ -406,104 +514,83 @@ withdraw_cleanup (void *cls,
* @param index index number of the object to offer.
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
withdraw_traits (void *cls,
const void **ret,
const char *trait,
unsigned int index)
{
struct WithdrawState *ws = cls;
- const struct TALER_TESTING_Command *reserve_cmd;
- const struct TALER_ReservePrivateKeyP *reserve_priv;
- const struct TALER_ReservePublicKeyP *reserve_pub;
-
- /* We offer the reserve key where these coins were withdrawn
- * from. */
- reserve_cmd = TALER_TESTING_interpreter_lookup_command (ws->is,
- ws->reserve_reference);
-
- if (NULL == reserve_cmd)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (ws->is);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_reserve_priv (reserve_cmd,
- 0,
- &reserve_priv))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (ws->is);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_reserve_pub (reserve_cmd,
- 0,
- &reserve_pub))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (ws->is);
- return GNUNET_SYSERR;
- }
- if (NULL == ws->exchange_url)
- ws->exchange_url
- = GNUNET_strdup (TALER_EXCHANGE_get_base_url (ws->is->exchange));
- {
- struct TALER_TESTING_Trait traits[] = {
- /* history entry MUST be first due to response code logic below! */
- TALER_TESTING_make_trait_reserve_history (0,
- &ws->reserve_history),
- TALER_TESTING_make_trait_coin_priv (0 /* only one coin */,
- &ws->ps.coin_priv),
- TALER_TESTING_make_trait_blinding_key (0 /* only one coin */,
- &ws->ps.blinding_key),
- TALER_TESTING_make_trait_denom_pub (0 /* only one coin */,
- ws->pk),
- TALER_TESTING_make_trait_denom_sig (0 /* only one coin */,
- &ws->sig),
- TALER_TESTING_make_trait_reserve_priv (0,
- reserve_priv),
- TALER_TESTING_make_trait_reserve_pub (0,
- reserve_pub),
- TALER_TESTING_make_trait_amount_obj (0,
- &ws->amount),
- TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BASE_URL,
- ws->exchange_url),
- TALER_TESTING_trait_end ()
- };
-
- return TALER_TESTING_get_trait ((ws->expected_response_code == MHD_HTTP_OK)
- ? &traits[0] /* we have reserve history */
- : &traits[1],/* skip reserve history */
- ret,
- trait,
- index);
- }
+ struct TALER_TESTING_Trait traits[] = {
+ /* history entry MUST be first due to response code logic below! */
+ TALER_TESTING_make_trait_reserve_history (0,
+ &ws->reserve_history),
+ TALER_TESTING_make_trait_coin_priv (0 /* only one coin */,
+ &ws->coin_priv),
+ TALER_TESTING_make_trait_planchet_secret (&ws->ps),
+ TALER_TESTING_make_trait_blinding_key (0 /* only one coin */,
+ &ws->bks),
+ TALER_TESTING_make_trait_exchange_wd_value (0 /* only one coin */,
+ &ws->exchange_vals),
+ TALER_TESTING_make_trait_denom_pub (0 /* only one coin */,
+ ws->pk),
+ TALER_TESTING_make_trait_denom_sig (0 /* only one coin */,
+ &ws->sig),
+ TALER_TESTING_make_trait_reserve_priv (&ws->reserve_priv),
+ TALER_TESTING_make_trait_reserve_pub (&ws->reserve_pub),
+ TALER_TESTING_make_trait_amount (&ws->amount),
+ TALER_TESTING_make_trait_legi_requirement_row (&ws->requirement_row),
+ TALER_TESTING_make_trait_h_payto (&ws->h_payto),
+ TALER_TESTING_make_trait_payto_uri (ws->reserve_payto_uri),
+ TALER_TESTING_make_trait_exchange_url (ws->exchange_url),
+ TALER_TESTING_make_trait_age_commitment_proof (0,
+ 0 < ws->age
+ ? &ws->age_commitment_proof
+ : NULL),
+ TALER_TESTING_make_trait_h_age_commitment (0,
+ 0 < ws->age
+ ? &ws->h_age_commitment
+ : NULL),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait ((ws->expected_response_code == MHD_HTTP_OK)
+ ? &traits[0] /* we have reserve history */
+ : &traits[1], /* skip reserve history */
+ ret,
+ trait,
+ index);
}
-/**
- * Create a withdraw command, letting the caller specify
- * the desired amount as string.
- *
- * @param label command label.
- * @param reserve_reference command providing us with a reserve to withdraw from
- * @param amount how much we withdraw.
- * @param expected_response_code which HTTP response code
- * we expect from the exchange.
- * @return the withdraw command to be executed by the interpreter.
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_withdraw_amount (const char *label,
const char *reserve_reference,
const char *amount,
+ uint8_t age,
unsigned int expected_response_code)
{
struct WithdrawState *ws;
ws = GNUNET_new (struct WithdrawState);
+ ws->age = age;
+ if (0 < age)
+ {
+ struct GNUNET_HashCode seed;
+ struct TALER_AgeMask mask;
+
+ mask = TALER_extensions_get_age_restriction_mask ();
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &seed,
+ sizeof(seed));
+ TALER_age_restriction_commit (&mask,
+ age,
+ &seed,
+ &ws->age_commitment_proof);
+ TALER_age_commitment_hash (&ws->age_commitment_proof.commitment,
+ &ws->h_age_commitment);
+ }
+
ws->reserve_reference = reserve_reference;
if (GNUNET_OK !=
TALER_string_to_amount (amount,
@@ -530,18 +617,31 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
}
-/**
- * Create withdraw command, letting the caller specify the
- * amount by a denomination key.
- *
- * @param label command label.
- * @param reserve_reference reference to the reserve to withdraw
- * from; will provide reserve priv to sign the request.
- * @param dk denomination public key.
- * @param expected_response_code expected HTTP response code.
- *
- * @return the command.
- */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_withdraw_amount_reuse_key (
+ const char *label,
+ const char *reserve_reference,
+ const char *amount,
+ uint8_t age,
+ const char *coin_ref,
+ unsigned int expected_response_code)
+{
+ struct TALER_TESTING_Command cmd;
+
+ cmd = TALER_TESTING_cmd_withdraw_amount (label,
+ reserve_reference,
+ amount,
+ age,
+ expected_response_code);
+ {
+ struct WithdrawState *ws = cmd.cls;
+
+ ws->reuse_coin_key_ref = coin_ref;
+ }
+ return cmd;
+}
+
+
struct TALER_TESTING_Command
TALER_TESTING_cmd_withdraw_denomination (
const char *label,
@@ -576,14 +676,6 @@ TALER_TESTING_cmd_withdraw_denomination (
}
-/**
- * Modify a withdraw command to enable retries when the
- * reserve is not yet full or we get other transient
- * errors from the exchange.
- *
- * @param cmd a withdraw command
- * @return the command with retries enabled
- */
struct TALER_TESTING_Command
TALER_TESTING_cmd_withdraw_with_retry (struct TALER_TESTING_Command cmd)
{
diff --git a/src/testing/testing_api_helpers_auditor.c b/src/testing/testing_api_helpers_auditor.c
deleted file mode 100644
index ccfa5e24e..000000000
--- a/src/testing/testing_api_helpers_auditor.c
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_helpers_auditor.c
- * @brief helper functions
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_testing_lib.h"
-#include "taler_auditor_service.h"
-
-
-/**
- * Closure for #cleanup_auditor.
- */
-struct CleanupContext
-{
- /**
- * Where we find the state to clean up.
- */
- struct TALER_TESTING_Interpreter *is;
-
- /**
- * Next cleanup routine to call, NULL for none.
- */
- GNUNET_SCHEDULER_TaskCallback fcb;
-
- /**
- * Closure for @e fcb
- */
- void *fcb_cls;
-};
-
-
-/**
- * Function to clean up the auditor connection.
- *
- * @param cls a `struct CleanupContext`
- */
-static void
-cleanup_auditor (void *cls)
-{
- struct CleanupContext *cc = cls;
- struct TALER_TESTING_Interpreter *is = cc->is;
-
- TALER_AUDITOR_disconnect (is->auditor);
- is->auditor = NULL;
- if (NULL != cc->fcb)
- cc->fcb (cc->fcb_cls);
- GNUNET_free (cc);
-}
-
-
-/**
- * Closure for #auditor_main_wrapper()
- */
-struct MainWrapperContext
-{
- /**
- * Main function to launch.
- */
- TALER_TESTING_Main main_cb;
-
- /**
- * Closure for @e main_cb.
- */
- void *main_cb_cls;
-
- /**
- * Configuration we use.
- */
- const struct GNUNET_CONFIGURATION_Handle *cfg;
-
- /**
- * Name of the configuration file.
- */
- const char *config_filename;
-
-};
-
-
-/**
- * Function called with information about the auditor.
- *
- * @param cls closure
- * @param vi basic information about the auditor
- * @param compat protocol compatibility information
- */
-static void
-auditor_version_cb (void *cls,
- const struct TALER_AUDITOR_VersionInformation *vi,
- enum TALER_AUDITOR_VersionCompatibility compat)
-{
- struct TALER_TESTING_Interpreter *is = cls;
-
- if (TALER_AUDITOR_VC_MATCH != compat)
- {
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
- is->auditor_working = GNUNET_YES;
-}
-
-
-/**
- * Setup the @a is 'auditor' member before running the main test loop.
- *
- * @param cls must be a `struct MainWrapperContext *`
- * @param[in,out] is interpreter state to setup
- */
-static void
-auditor_main_wrapper (void *cls,
- struct TALER_TESTING_Interpreter *is)
-{
- struct MainWrapperContext *mwc = cls;
- struct CleanupContext *cc;
- char *auditor_base_url;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (mwc->cfg,
- "auditor",
- "BASE_URL",
- &auditor_base_url))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "auditor",
- "BASE_URL");
- return;
- }
-
- is->auditor = TALER_AUDITOR_connect (is->ctx,
- auditor_base_url,
- &auditor_version_cb,
- is);
- GNUNET_free (auditor_base_url);
-
- if (NULL == is->auditor)
- {
- GNUNET_break (0);
- return;
- }
-
- cc = GNUNET_new (struct CleanupContext);
- cc->is = is;
- cc->fcb = is->final_cleanup_cb;
- cc->fcb_cls = is->final_cleanup_cb_cls;
- is->final_cleanup_cb = cleanup_auditor;
- is->final_cleanup_cb_cls = cc;
- mwc->main_cb (mwc->main_cb_cls,
- is);
-}
-
-
-/**
- * Install signal handlers plus schedules the main wrapper
- * around the "run" method.
- *
- * @param cls our `struct MainWrapperContext`
- * @param cfg configuration we use
- * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise.
- * non-GNUNET_OK codes are #GNUNET_SYSERR most of the
- * times.
- */
-static int
-setup_with_cfg (void *cls,
- const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- struct MainWrapperContext *mwc = cls;
- struct TALER_TESTING_SetupContext setup_ctx = {
- .config_filename = mwc->config_filename,
- .main_cb = &auditor_main_wrapper,
- .main_cb_cls = mwc
- };
-
- mwc->cfg = cfg;
- return TALER_TESTING_setup_with_auditor_and_exchange_cfg (&setup_ctx,
- cfg);
-}
-
-
-/**
- * Install signal handlers plus schedules the main wrapper
- * around the "run" method.
- *
- * @param main_cb the "run" method which contains all the
- * commands.
- * @param main_cb_cls a closure for "run", typically NULL.
- * @param config_filename configuration filename.
- * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise.
- * non-GNUNET_OK codes are #GNUNET_SYSERR most of the
- * times.
- */
-int
-TALER_TESTING_auditor_setup (TALER_TESTING_Main main_cb,
- void *main_cb_cls,
- const char *config_filename)
-{
- struct MainWrapperContext mwc = {
- .main_cb = main_cb,
- .main_cb_cls = main_cb_cls,
- .config_filename = config_filename
- };
-
- return GNUNET_CONFIGURATION_parse_and_run (config_filename,
- &setup_with_cfg,
- &mwc);
-}
-
-
-/* end of testing_auditor_api_helpers.c */
diff --git a/src/testing/testing_api_helpers_bank.c b/src/testing/testing_api_helpers_bank.c
deleted file mode 100644
index bdbefe65e..000000000
--- a/src/testing/testing_api_helpers_bank.c
+++ /dev/null
@@ -1,503 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018-2020 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 testing/testing_api_helpers_bank.c
- * @brief convenience functions for bank tests.
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include "taler_testing_lib.h"
-#include "taler_fakebank_lib.h"
-
-#define BANK_FAIL() \
- do {GNUNET_break (0); return NULL; } while (0)
-
-
-/**
- * Runs the Fakebank by guessing / extracting the portnumber
- * from the base URL.
- *
- * @param bank_url bank's base URL.
- * @param currency currency the bank uses
- * @return the fakebank process handle, or NULL if any
- * error occurs.
- */
-struct TALER_FAKEBANK_Handle *
-TALER_TESTING_run_fakebank (const char *bank_url,
- const char *currency)
-{
- const char *port;
- long pnum;
- struct TALER_FAKEBANK_Handle *fakebankd;
-
- port = strrchr (bank_url,
- (unsigned char) ':');
- if (NULL == port)
- pnum = 80;
- else
- pnum = strtol (port + 1, NULL, 10);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Starting Fakebank on port %u (%s)\n",
- (unsigned int) pnum,
- bank_url);
- fakebankd = TALER_FAKEBANK_start ((uint16_t) pnum,
- currency);
- if (NULL == fakebankd)
- {
- GNUNET_break (0);
- return NULL;
- }
- return fakebankd;
-}
-
-
-/**
- * Look for substring in a programs' name.
- *
- * @param prog program's name to look into
- * @param marker chunk to find in @a prog
- * @return #GNUNET_YES if @a marker is present, otherwise #GNUNET_NO
- */
-int
-TALER_TESTING_has_in_name (const char *prog,
- const char *marker)
-{
- size_t name_pos;
- size_t pos;
-
- if (! prog || ! marker)
- return GNUNET_NO;
-
- pos = 0;
- name_pos = 0;
- while (prog[pos])
- {
- if ('/' == prog[pos])
- name_pos = pos + 1;
- pos++;
- }
- if (name_pos == pos)
- return GNUNET_YES;
- return (NULL != strstr (prog + name_pos, marker));
-}
-
-
-/**
- * Start the (Python) bank process. Assume the port
- * is available and the database is clean. Use the "prepare
- * bank" function to do such tasks.
- *
- * @param config_filename configuration filename.
- * @param bank_url base URL of the bank, used by `wget' to check
- * that the bank was started right.
- * @return the process, or NULL if the process could not
- * be started.
- */
-struct GNUNET_OS_Process *
-TALER_TESTING_run_bank (const char *config_filename,
- const char *bank_url)
-{
- struct GNUNET_OS_Process *bank_proc;
- unsigned int iter;
- char *wget_cmd;
- char *database;
- char *serve_cfg;
- char *serve_arg;
- struct GNUNET_CONFIGURATION_Handle *cfg;
-
- cfg = GNUNET_CONFIGURATION_create ();
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_load (cfg,
- config_filename))
- {
- GNUNET_break (0);
- GNUNET_CONFIGURATION_destroy (cfg);
- exit (77);
- }
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "bank",
- "database",
- &database))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
- "bank",
- "database");
- GNUNET_break (0);
- GNUNET_CONFIGURATION_destroy (cfg);
- exit (77);
- }
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "bank",
- "serve",
- &serve_cfg))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
- "bank",
- "serve");
- GNUNET_break (0);
- GNUNET_CONFIGURATION_destroy (cfg);
- GNUNET_free (database);
- exit (77);
- }
- GNUNET_CONFIGURATION_destroy (cfg);
-
- serve_arg = "serve-http";
- if (0 != strcmp ("http", serve_cfg))
- serve_arg = "serve-uwsgi";
- GNUNET_free (serve_cfg);
- bank_proc = GNUNET_OS_start_process
- (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_NONE,
- NULL, NULL, NULL,
- "taler-bank-manage-testing",
- "taler-bank-manage-testing",
- config_filename,
- database,
- serve_arg, NULL);
- GNUNET_free (database);
- if (NULL == bank_proc)
- {
- BANK_FAIL ();
- }
-
- GNUNET_asprintf (&wget_cmd,
- "wget -q -t 2 -T 1 %s -o /dev/null -O /dev/null",
- bank_url);
-
- /* give child time to start and bind against the socket */
- fprintf (stderr,
- "Waiting for `taler-bank-manage' to be ready (via %s)\n", wget_cmd);
- iter = 0;
- do
- {
- if (10 == iter)
- {
- fprintf (
- stderr,
- "Failed to launch `taler-bank-manage' (or `wget')\n");
- GNUNET_OS_process_kill (bank_proc,
- SIGTERM);
- GNUNET_OS_process_wait (bank_proc);
- GNUNET_OS_process_destroy (bank_proc);
- GNUNET_free (wget_cmd);
- BANK_FAIL ();
- }
- fprintf (stderr, ".");
- sleep (1);
- iter++;
- }
- while (0 != system (wget_cmd));
- GNUNET_free (wget_cmd);
- fprintf (stderr, "\n");
-
- return bank_proc;
-
-}
-
-
-/**
- * Prepare the bank execution. Check if the port is available
- * and reset database.
- *
- * @param config_filename configuration file name.
- * @param reset_db should we reset the bank's database
- * @param config_section section of the configuration with the exchange's account
- * @param[out] bc set to the bank's configuration data
- * @return the base url, or NULL upon errors. Must be freed
- * by the caller.
- */
-int
-TALER_TESTING_prepare_bank (const char *config_filename,
- int reset_db,
- const char *config_section,
- struct TALER_TESTING_BankConfiguration *bc)
-{
- struct GNUNET_CONFIGURATION_Handle *cfg;
- unsigned long long port;
- struct GNUNET_OS_Process *dbreset_proc;
- enum GNUNET_OS_ProcessStatusType type;
- unsigned long code;
- char *database;
- char *exchange_payto_uri;
-
- cfg = GNUNET_CONFIGURATION_create ();
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_load (cfg, config_filename))
- {
- GNUNET_CONFIGURATION_destroy (cfg);
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "bank",
- "DATABASE",
- &database))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "bank",
- "DATABASE");
- GNUNET_CONFIGURATION_destroy (cfg);
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- config_section,
- "PAYTO_URI",
- &exchange_payto_uri))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
- config_section,
- "PAYTO_URI");
- GNUNET_CONFIGURATION_destroy (cfg);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_number (cfg,
- "bank",
- "HTTP_PORT",
- &port))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "bank",
- "HTTP_PORT");
- GNUNET_CONFIGURATION_destroy (cfg);
- GNUNET_free (database);
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
- (uint16_t) port))
- {
- fprintf (stderr,
- "Required port %llu not available, skipping.\n",
- port);
- GNUNET_break (0);
- GNUNET_free (database);
- GNUNET_CONFIGURATION_destroy (cfg);
- return GNUNET_SYSERR;
- }
-
- /* DB preparation */
- if (GNUNET_YES == reset_db)
- {
- if (NULL ==
- (dbreset_proc = GNUNET_OS_start_process (
- GNUNET_NO,
- GNUNET_OS_INHERIT_STD_NONE,
- NULL, NULL, NULL,
- "taler-bank-manage",
- "taler-bank-manage",
- "-c", config_filename,
- "--with-db", database,
- "django",
- "flush",
- "--no-input", NULL)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to flush the bank db.\n");
- GNUNET_free (database);
- GNUNET_CONFIGURATION_destroy (cfg);
- return GNUNET_SYSERR;
- }
- GNUNET_free (database);
-
- if (GNUNET_SYSERR ==
- GNUNET_OS_process_wait_status (dbreset_proc,
- &type,
- &code))
- {
- GNUNET_OS_process_destroy (dbreset_proc);
- GNUNET_break (0);
- GNUNET_CONFIGURATION_destroy (cfg);
- return GNUNET_SYSERR;
- }
- if ( (type == GNUNET_OS_PROCESS_EXITED) &&
- (0 != code) )
- {
- fprintf (stderr,
- "Failed to setup database\n");
- GNUNET_break (0);
- GNUNET_CONFIGURATION_destroy (cfg);
- return GNUNET_SYSERR;
- }
- if ( (type != GNUNET_OS_PROCESS_EXITED) ||
- (0 != code) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected error running `taler-bank-manage django flush'!\n");
- GNUNET_break (0);
- GNUNET_CONFIGURATION_destroy (cfg);
- return GNUNET_SYSERR;
- }
- GNUNET_OS_process_destroy (dbreset_proc);
- }
- if (GNUNET_OK !=
- TALER_BANK_auth_parse_cfg (cfg,
- config_section,
- &bc->exchange_auth))
- {
- GNUNET_break (0);
- GNUNET_CONFIGURATION_destroy (cfg);
- return GNUNET_SYSERR;
- }
- GNUNET_CONFIGURATION_destroy (cfg);
- bc->exchange_payto = exchange_payto_uri;
- bc->user42_payto = "payto://x-taler-bank/localhost/42";
- bc->user43_payto = "payto://x-taler-bank/localhost/43";
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Using pybank %s on port %u\n",
- bc->exchange_auth.wire_gateway_url,
- (unsigned int) port);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO, "exchange payto: %s\n",
- bc->exchange_payto);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO, "user42_payto: %s\n",
- bc->user42_payto);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO, "user42_payto: %s\n",
- bc->user43_payto);
- return GNUNET_OK;
-}
-
-
-/**
- * Prepare launching a fakebank. Check that the configuration
- * file has the right option, and that the port is available.
- * If everything is OK, return the configuration data of the fakebank.
- *
- * @param config_filename configuration file to use
- * @param config_section which account to use (must match x-taler-bank)
- * @param[out] bc set to the bank's configuration data
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_prepare_fakebank (const char *config_filename,
- const char *config_section,
- struct TALER_TESTING_BankConfiguration *bc)
-{
- struct GNUNET_CONFIGURATION_Handle *cfg;
- unsigned long long fakebank_port;
- char *exchange_payto_uri;
-
- cfg = GNUNET_CONFIGURATION_create ();
- if (GNUNET_OK != GNUNET_CONFIGURATION_load (cfg,
- config_filename))
- return GNUNET_SYSERR;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_number (cfg,
- "BANK",
- "HTTP_PORT",
- &fakebank_port))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
- "BANK",
- "HTTP_PORT");
- GNUNET_CONFIGURATION_destroy (cfg);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- config_section,
- "PAYTO_URI",
- &exchange_payto_uri))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
- config_section,
- "PAYTO_URI");
- GNUNET_CONFIGURATION_destroy (cfg);
- return GNUNET_SYSERR;
- }
- {
- char *exchange_xtalerbank_account;
-
- exchange_xtalerbank_account
- = TALER_xtalerbank_account_from_payto (exchange_payto_uri);
- if (NULL == exchange_xtalerbank_account)
- {
- GNUNET_break (0);
- GNUNET_free (exchange_payto_uri);
- return GNUNET_SYSERR;
- }
- GNUNET_asprintf (&bc->exchange_auth.wire_gateway_url,
- "http://localhost:%u/%s/",
- (unsigned int) fakebank_port,
- exchange_xtalerbank_account);
- GNUNET_free (exchange_xtalerbank_account);
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Using fakebank %s on port %u\n",
- bc->exchange_auth.wire_gateway_url,
- (unsigned int) fakebank_port);
-
- GNUNET_CONFIGURATION_destroy (cfg);
- if (GNUNET_OK !=
- TALER_TESTING_url_port_free (bc->exchange_auth.wire_gateway_url))
- {
- GNUNET_free (bc->exchange_auth.wire_gateway_url);
- bc->exchange_auth.wire_gateway_url = NULL;
- GNUNET_free (exchange_payto_uri);
- return GNUNET_SYSERR;
- }
- /* Now we know it's the fake bank, for purpose of authentication, we
- * don't have any auth. */
- bc->exchange_auth.method = TALER_BANK_AUTH_NONE;
- bc->exchange_payto = exchange_payto_uri;
- bc->user42_payto = "payto://x-taler-bank/localhost/42";
- bc->user43_payto = "payto://x-taler-bank/localhost/43";
- GNUNET_log (GNUNET_ERROR_TYPE_INFO, "exchange payto: %s\n",
- bc->exchange_payto);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO, "user42_payto: %s\n",
- bc->user42_payto);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO, "user42_payto: %s\n",
- bc->user43_payto);
- return GNUNET_OK;
-}
-
-
-/**
- * Allocate and return a piece of wire-details. Combines
- * a @a payto -URL and adds some salt to create the JSON.
- *
- * @param payto payto://-URL to encapsulate
- * @return JSON describing the account, including the
- * payto://-URL of the account, must be manually decref'd
- */
-json_t *
-TALER_TESTING_make_wire_details (const char *payto)
-{
- return json_pack ("{s:s, s:s}",
- "payto_uri", payto,
- "salt",
- "test-salt (must be constant for aggregation tests)");
-}
-
-
-/* end of testing_api_helpers_bank.c */
diff --git a/src/testing/testing_api_helpers_exchange.c b/src/testing/testing_api_helpers_exchange.c
deleted file mode 100644
index 872d23b9b..000000000
--- a/src/testing/testing_api_helpers_exchange.c
+++ /dev/null
@@ -1,1000 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018-2020 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 testing/testing_api_helpers_exchange.c
- * @brief helper functions
- * @author Christian Grothoff
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-
-/**
- * Remove files from previous runs
- *
- * @param config_name configuration filename.
- */
-void
-TALER_TESTING_cleanup_files (const char *config_name)
-{
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_parse_and_run (config_name,
- &TALER_TESTING_cleanup_files_cfg,
- NULL))
- exit (77);
-}
-
-
-/**
- * Remove files from previous runs
- *
- * @param cls NULL
- * @param cfg configuration
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_cleanup_files_cfg (void *cls,
- const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- char *dir;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (cfg,
- "exchange",
- "KEYDIR",
- &dir))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "KEYDIR");
- return GNUNET_SYSERR;
- }
- if (GNUNET_YES ==
- GNUNET_DISK_directory_test (dir,
- GNUNET_NO))
- GNUNET_break (GNUNET_OK ==
- GNUNET_DISK_directory_remove (dir));
- GNUNET_free (dir);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (cfg,
- "exchange",
- "REVOCATION_DIR",
- &dir))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "REVOCATION_DIR");
- return GNUNET_SYSERR;
- }
- if (GNUNET_YES ==
- GNUNET_DISK_directory_test (dir,
- GNUNET_NO))
- GNUNET_break (GNUNET_OK ==
- GNUNET_DISK_directory_remove (dir));
- GNUNET_free (dir);
- return GNUNET_OK;
-}
-
-
-/**
- * Run `taler-exchange-keyup`.
- *
- * @param config_filename configuration file to use
- * @param output_filename where to write the output for the auditor
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_run_keyup (const char *config_filename,
- const char *output_filename)
-{
- struct GNUNET_OS_Process *proc;
-
- proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-exchange-keyup",
- "taler-exchange-keyup",
- "-c", config_filename,
- "-o", output_filename,
- NULL);
- if (NULL == proc)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to run `taler-exchange-keyup`, is your PATH correct?\n");
- return GNUNET_SYSERR;
- }
- GNUNET_OS_process_wait (proc);
- GNUNET_OS_process_destroy (proc);
- return GNUNET_OK;
-}
-
-
-/**
- * Run `taler-auditor-sign`.
- *
- * @param config_filename configuration file to use
- * @param exchange_master_pub master public key of the exchange
- * @param auditor_base_url what is the base URL of the auditor
- * @param signdata_in where is the information from taler-exchange-keyup
- * @param signdata_out where to write the output for the exchange
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_run_auditor_sign (const char *config_filename,
- const char *exchange_master_pub,
- const char *auditor_base_url,
- const char *signdata_in,
- const char *signdata_out)
-{
- struct GNUNET_OS_Process *proc;
-
- proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-auditor-sign",
- "taler-auditor-sign",
- "-c", config_filename,
- "-u", auditor_base_url,
- "-m", exchange_master_pub,
- "-r", signdata_in,
- "-o", signdata_out,
- NULL);
- if (NULL == proc)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to run `taler-auditor-sign`, is your PATH correct?\n");
- return GNUNET_SYSERR;
- }
- GNUNET_OS_process_wait (proc);
- GNUNET_OS_process_destroy (proc);
- return GNUNET_OK;
-}
-
-
-/**
- * Run `taler-auditor-exchange`.
- *
- * @param config_filename configuration file to use
- * @param exchange_master_pub master public key of the exchange
- * @param exchange_base_url what is the base URL of the exchange
- * @param do_remove #GNUNET_NO to add exchange, #GNUNET_YES to remove
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_run_auditor_exchange (const char *config_filename,
- const char *exchange_master_pub,
- const char *exchange_base_url,
- int do_remove)
-{
- struct GNUNET_OS_Process *proc;
- enum GNUNET_OS_ProcessStatusType type;
- unsigned long code;
-
- TALER_LOG_DEBUG ("Add exchange (%s,%s) to the auditor\n",
- exchange_base_url,
- exchange_master_pub);
-
- proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-auditor-exchange",
- "taler-auditor-exchange",
- "-c", config_filename,
- "-u", exchange_base_url,
- "-m", exchange_master_pub,
- (GNUNET_YES == do_remove)
- ? "-r"
- : NULL,
- NULL);
- if (NULL == proc)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to run `taler-auditor-exchange`, is your PATH correct?\n");
- return GNUNET_SYSERR;
- }
- GNUNET_assert (GNUNET_OK ==
- GNUNET_OS_process_wait_status (proc,
- &type,
- &code));
- GNUNET_OS_process_destroy (proc);
- if ( (0 != code) ||
- (GNUNET_OS_PROCESS_EXITED != type) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "taler-auditor-exchange terminated with error (%d/%d)\n",
- (int) type,
- (int) code);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Run `taler-exchange-dbinit -r` (reset exchange database).
- *
- * @param config_filename configuration file to use
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_exchange_db_reset (const char *config_filename)
-{
- struct GNUNET_OS_Process *proc;
- enum GNUNET_OS_ProcessStatusType type;
- unsigned long code;
-
- proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-exchange-dbinit",
- "taler-exchange-dbinit",
- "-c", config_filename,
- "-r",
- NULL);
- if (NULL == proc)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to run `taler-exchange-dbinit`, is your PATH correct?\n");
- return GNUNET_NO;
- }
- if (GNUNET_SYSERR ==
- GNUNET_OS_process_wait_status (proc,
- &type,
- &code))
- {
- GNUNET_break (0);
- GNUNET_OS_process_destroy (proc);
- return GNUNET_SYSERR;
- }
- GNUNET_OS_process_destroy (proc);
- if ( (type == GNUNET_OS_PROCESS_EXITED) &&
- (0 != code) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to setup (exchange) database, exit code %d\n",
- (int) code);
- return GNUNET_NO;
- }
- if ( (type != GNUNET_OS_PROCESS_EXITED) ||
- (0 != code) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected error (%d/%d) running `taler-exchange-dbinit'!\n",
- (int) type,
- (int) code);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Run `taler-auditor-dbinit -R` (reset auditor database).
- *
- * @param config_filename configuration file to use
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_auditor_db_reset (const char *config_filename)
-{
- struct GNUNET_OS_Process *proc;
- enum GNUNET_OS_ProcessStatusType type;
- unsigned long code;
-
- proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-auditor-dbinit",
- "taler-auditor-dbinit",
- "-c", config_filename,
- "-R",
- NULL);
- if (NULL == proc)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to run `taler-auditor-dbinit`, is your PATH correct?\n");
- return GNUNET_NO;
- }
- if (GNUNET_SYSERR ==
- GNUNET_OS_process_wait_status (proc,
- &type,
- &code))
- {
- GNUNET_break (0);
- GNUNET_OS_process_destroy (proc);
- return GNUNET_SYSERR;
- }
- GNUNET_OS_process_destroy (proc);
- if ( (type == GNUNET_OS_PROCESS_EXITED) &&
- (0 != code) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to setup (auditor) database, exit code %d\n",
- (int) code);
- return GNUNET_NO;
- }
- if ( (type != GNUNET_OS_PROCESS_EXITED) ||
- (0 != code) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected error (%d/%d) running `taler-auditor-dbinit'!\n",
- (int) type,
- (int) code);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Type of closure for
- * #sign_keys_for_exchange.
- */
-struct SignInfo
-{
- /**
- * Members will be set to the exchange configuration.
- */
- struct TALER_TESTING_ExchangeConfiguration *ec;
-
- /**
- * Name of the configuration file to use.
- */
- const char *config_filename;
-
- /**
- * Must be set to input file with the data to be signed before
- * calling #TALER_TESTING_sign_keys_for_exchange.
- */
- const char *auditor_sign_input_filename;
-
- /**
- * Did we reset the database?
- */
- int db_reset;
-};
-
-
-/**
- * Sign the keys for an exchange given configuration @a cfg.
- * The information to be signed must be in a file "auditor.in".
- *
- * @param[in,out] cls a `struct SignInfo` with further parameters
- * @param cfg configuration to use
- * @return #GNUNET_OK on success
- */
-static int
-sign_keys_for_exchange (void *cls,
- const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- struct SignInfo *si = cls;
- char *test_home_dir;
- char *signed_keys_out;
- char *exchange_master_pub;
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "exchange",
- "BASE_URL",
- &si->ec->exchange_url))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
- "exchange",
- "BASE_URL");
- si->ec->exchange_url = NULL;
- return GNUNET_NO;
- }
- if (GNUNET_OK !=
- TALER_TESTING_url_port_free (si->ec->exchange_url))
- {
- GNUNET_free (si->ec->exchange_url);
- si->ec->exchange_url = NULL;
- return GNUNET_NO;
- }
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "auditor",
- "BASE_URL",
- &si->ec->auditor_url))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
- "auditor",
- "BASE_URL");
- GNUNET_free (si->ec->exchange_url);
- si->ec->exchange_url = NULL;
- si->ec->auditor_url = NULL;
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_TESTING_url_port_free (si->ec->auditor_url))
- {
- ret = GNUNET_NO;
- goto fail;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (cfg,
- "paths",
- "TALER_TEST_HOME",
- &test_home_dir))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "paths",
- "TALER_TEST_HOME");
- ret = GNUNET_SYSERR;
- goto fail;
- }
-
- GNUNET_asprintf (&signed_keys_out,
- "%s/.local/share/taler/auditors/auditor.out",
- test_home_dir);
- GNUNET_free (test_home_dir);
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "exchange",
- "MASTER_PUBLIC_KEY",
- &exchange_master_pub))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "MASTER_PUBLIC_KEY");
- GNUNET_free (signed_keys_out);
- ret = GNUNET_SYSERR;
- goto fail;
- }
- if ( (GNUNET_OK !=
- TALER_TESTING_run_auditor_exchange (si->config_filename,
- exchange_master_pub,
- si->ec->exchange_url,
- GNUNET_NO)) &&
- (GNUNET_YES == si->db_reset) )
- {
- GNUNET_free (signed_keys_out);
- ret = GNUNET_NO;
- goto fail;
- }
-
- if ( (GNUNET_OK !=
- TALER_TESTING_run_auditor_sign (si->config_filename,
- exchange_master_pub,
- si->ec->auditor_url,
- si->auditor_sign_input_filename,
- signed_keys_out)) &&
- (GNUNET_YES == si->db_reset) )
- {
- GNUNET_free (signed_keys_out);
- GNUNET_free (exchange_master_pub);
- ret = GNUNET_NO;
- goto fail;
- }
- GNUNET_free (signed_keys_out);
- GNUNET_free (exchange_master_pub);
- return GNUNET_OK;
-fail:
- GNUNET_free (si->ec->exchange_url);
- GNUNET_free (si->ec->auditor_url);
- si->ec->exchange_url = NULL;
- si->ec->auditor_url = NULL;
- return ret;
-}
-
-
-/**
- * Prepare launching an exchange. Checks that the configured
- * port is available, runs taler-exchange-keyup,
- * taler-auditor-sign and taler-exchange-dbinit. Does NOT
- * launch the exchange process itself.
- *
- * @param config_filename configuration file to use
- * @param reset_db should we reset the database?
- * @param[out] ec will be set to the exchange configuration data
- * @return #GNUNET_OK on success, #GNUNET_NO if test should be
- * skipped, #GNUNET_SYSERR on test failure
- */
-int
-TALER_TESTING_prepare_exchange (const char *config_filename,
- int reset_db,
- struct TALER_TESTING_ExchangeConfiguration *ec)
-{
- struct SignInfo si = {
- .config_filename = config_filename,
- .ec = ec,
- .auditor_sign_input_filename = "auditor.in",
- .db_reset = reset_db
- };
-
- if (GNUNET_OK !=
- TALER_TESTING_run_keyup (config_filename,
- si.auditor_sign_input_filename))
- return GNUNET_NO;
- if (GNUNET_YES == reset_db)
- {
- if (GNUNET_OK !=
- TALER_TESTING_exchange_db_reset (config_filename))
- return GNUNET_NO;
- if (GNUNET_OK !=
- TALER_TESTING_auditor_db_reset (config_filename))
- return GNUNET_NO;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_parse_and_run (config_filename,
- &sign_keys_for_exchange,
- &si))
- return GNUNET_NO;
- return GNUNET_OK;
-}
-
-
-/**
- * Find denomination key matching the given amount.
- *
- * @param keys array of keys to search
- * @param amount coin value to look for
- * @return NULL if no matching key was found
- */
-const struct TALER_EXCHANGE_DenomPublicKey *
-TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_Amount *amount)
-{
- struct GNUNET_TIME_Absolute now;
- struct TALER_EXCHANGE_DenomPublicKey *pk;
- char *str;
-
- now = GNUNET_TIME_absolute_get ();
- for (unsigned int i = 0; i<keys->num_denom_keys; i++)
- {
- pk = &keys->denom_keys[i];
- if ( (0 == TALER_amount_cmp (amount,
- &pk->value)) &&
- (now.abs_value_us >= pk->valid_from.abs_value_us) &&
- (now.abs_value_us <
- pk->withdraw_valid_until.abs_value_us) )
- return pk;
- }
- /* do 2nd pass to check if expiration times are to blame for
- * failure */
- str = TALER_amount_to_string (amount);
- for (unsigned int i = 0; i<keys->num_denom_keys; i++)
- {
- pk = &keys->denom_keys[i];
- if ( (0 == TALER_amount_cmp (amount,
- &pk->value)) &&
- ( (now.abs_value_us < pk->valid_from.abs_value_us) ||
- (now.abs_value_us >
- pk->withdraw_valid_until.abs_value_us) ) )
- {
- GNUNET_log
- (GNUNET_ERROR_TYPE_WARNING,
- "Have denomination key for `%s', but with wrong"
- " expiration range %llu vs [%llu,%llu)\n",
- str,
- (unsigned long long) now.abs_value_us,
- (unsigned long long) pk->valid_from.abs_value_us,
- (unsigned long long)
- pk->withdraw_valid_until.abs_value_us);
- GNUNET_free (str);
- return NULL;
- }
- }
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "No denomination key for amount %s found\n",
- str);
- GNUNET_free (str);
- return NULL;
-}
-
-
-/**
- * Wait for the exchange to have started. Waits for at
- * most 10s, after that returns 77 to indicate an error.
- *
- * @param base_url what URL should we expect the exchange
- * to be running at
- * @return 0 on success
- */
-int
-TALER_TESTING_wait_exchange_ready (const char *base_url)
-{
- char *wget_cmd;
- unsigned int iter;
-
- GNUNET_asprintf (&wget_cmd,
- "wget -q -t 1 -T 1 %skeys -o /dev/null -O /dev/null",
- base_url); // make sure ends with '/'
- /* give child time to start and bind against the socket */
- fprintf (stderr,
- "Waiting for `taler-exchange-httpd' to be ready (check with: %s)\n",
- wget_cmd);
- iter = 0;
- do
- {
- if (10 == iter)
- {
- fprintf (stderr,
- "Failed to launch `taler-exchange-httpd' (or `wget')\n");
- GNUNET_free (wget_cmd);
- return 77;
- }
- fprintf (stderr, ".\n");
- sleep (1);
- iter++;
- }
- while (0 != system (wget_cmd));
- GNUNET_free (wget_cmd);
- return 0;
-}
-
-
-/**
- * Wait for the auditor to have started. Waits for at
- * most 10s, after that returns 77 to indicate an error.
- *
- * @param base_url what URL should we expect the auditor
- * to be running at
- * @return 0 on success
- */
-int
-TALER_TESTING_wait_auditor_ready (const char *base_url)
-{
- char *wget_cmd;
- unsigned int iter;
-
- GNUNET_asprintf (&wget_cmd,
- "wget -q -t 1 -T 1 %sversion -o /dev/null -O /dev/null",
- base_url); // make sure ends with '/'
- /* give child time to start and bind against the socket */
- fprintf (stderr,
- "Waiting for `taler-auditor-httpd' to be ready\n");
- iter = 0;
- do
- {
- if (10 == iter)
- {
- fprintf (stderr,
- "Failed to launch `taler-auditor-httpd' (or `wget')\n");
- GNUNET_free (wget_cmd);
- return 77;
- }
- fprintf (stderr, ".\n");
- sleep (1);
- iter++;
- }
- while (0 != system (wget_cmd));
- GNUNET_free (wget_cmd);
- return 0;
-}
-
-
-/**
- * Initialize scheduler loop and curl context for the testcase
- * including starting and stopping the exchange using the given
- * configuration file.
- *
- * @param main_cb routine containing all the commands to run.
- * @param main_cb_cls closure for @a main_cb, typically NULL.
- * @param config_file configuration file for the test-suite.
- * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise.
- * non-#GNUNET_OK codes are #GNUNET_SYSERR most of the
- * time.
- */
-int
-TALER_TESTING_setup_with_exchange (TALER_TESTING_Main main_cb,
- void *main_cb_cls,
- const char *config_file)
-{
- struct TALER_TESTING_SetupContext setup_ctx = {
- .config_filename = config_file,
- .main_cb = main_cb,
- .main_cb_cls = main_cb_cls
- };
- int result;
-
- result =
- GNUNET_CONFIGURATION_parse_and_run (config_file,
- &TALER_TESTING_setup_with_exchange_cfg,
- &setup_ctx);
- if (GNUNET_OK != result)
- return result;
- return GNUNET_OK;
-}
-
-
-/**
- * Initialize scheduler loop and curl context for the test case
- * including starting and stopping the exchange using the given
- * configuration file.
- *
- * @param cls must be a `struct TALER_TESTING_SetupContext *`
- * @param cfg configuration to use.
- * @return #GNUNET_OK if no errors occurred.
- */
-int
-TALER_TESTING_setup_with_exchange_cfg (void *cls,
- const struct
- GNUNET_CONFIGURATION_Handle *cfg)
-{
- const struct TALER_TESTING_SetupContext *setup_ctx = cls;
- struct GNUNET_OS_Process *exchanged;
- unsigned long long port;
- char *serve;
- char *base_url;
- int result;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "exchange",
- "SERVE",
- &serve))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "SERVE");
- return GNUNET_NO;
- }
-
- if (0 == strcmp ("tcp", serve))
- {
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_number (cfg,
- "exchange",
- "PORT",
- &port))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "PORT");
- GNUNET_free (serve);
- return GNUNET_NO;
- }
-
- if (GNUNET_OK !=
- GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
- (uint16_t) port))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Required port %llu not available, skipping.\n",
- port);
- GNUNET_free (serve);
- return GNUNET_NO;
- }
- }
- GNUNET_free (serve);
- exchanged = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-exchange-httpd",
- "taler-exchange-httpd",
- "-a", /* some tests may need timetravel */
- "-c", setup_ctx->config_filename,
- NULL);
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "exchange",
- "BASE_URL",
- &base_url))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "BASE_URL");
- return GNUNET_NO;
- }
-
- if (0 != TALER_TESTING_wait_exchange_ready (base_url))
- {
- GNUNET_free (base_url);
- return 77;
- }
- GNUNET_free (base_url);
-
- /* NOTE: this call blocks. */
- result = TALER_TESTING_setup (setup_ctx->main_cb,
- setup_ctx->main_cb_cls,
- cfg,
- exchanged,
- GNUNET_YES);
- GNUNET_break (0 ==
- GNUNET_OS_process_kill (exchanged,
- SIGTERM));
- GNUNET_break (GNUNET_OK ==
- GNUNET_OS_process_wait (exchanged));
- GNUNET_OS_process_destroy (exchanged);
- return result;
-}
-
-
-/**
- * Initialize scheduler loop and curl context for the test case
- * including starting and stopping the auditor and exchange using the
- * given configuration file.
- *
- * @param cls must be a `struct TALER_TESTING_SetupContext *`
- * @param cfg configuration to use.
- * @return #GNUNET_OK if no errors occurred.
- */
-int
-TALER_TESTING_setup_with_auditor_and_exchange_cfg (void *cls,
- const struct
- GNUNET_CONFIGURATION_Handle *
- cfg)
-{
- const struct TALER_TESTING_SetupContext *setup_ctx = cls;
- struct GNUNET_OS_Process *auditord;
- unsigned long long port;
- char *serve;
- char *base_url;
- int result;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "auditor",
- "SERVE",
- &serve))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "auditor",
- "SERVE");
- return GNUNET_NO;
- }
-
- if (0 == strcmp ("tcp", serve))
- {
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_number (cfg,
- "auditor",
- "PORT",
- &port))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "auditor",
- "PORT");
- GNUNET_free (serve);
- return GNUNET_NO;
- }
-
- if (GNUNET_OK !=
- GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
- (uint16_t) port))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Required port %llu not available, skipping.\n",
- port);
- GNUNET_free (serve);
- return GNUNET_NO;
- }
- }
- GNUNET_free (serve);
- auditord = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-auditor-httpd",
- "taler-auditor-httpd",
- "-c", setup_ctx->config_filename,
- NULL);
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "auditor",
- "BASE_URL",
- &base_url))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "auditor",
- "BASE_URL");
- return GNUNET_NO;
- }
-
- if (0 != TALER_TESTING_wait_auditor_ready (base_url))
- {
- GNUNET_free (base_url);
- GNUNET_break (0 ==
- GNUNET_OS_process_kill (auditord,
- SIGTERM));
- GNUNET_break (GNUNET_OK ==
- GNUNET_OS_process_wait (auditord));
- GNUNET_OS_process_destroy (auditord);
- return 77;
- }
- GNUNET_free (base_url);
-
- /* NOTE: this call blocks. */
- result = TALER_TESTING_setup_with_exchange_cfg ((void *) setup_ctx,
- cfg);
- GNUNET_break (0 ==
- GNUNET_OS_process_kill (auditord,
- SIGTERM));
- GNUNET_break (GNUNET_OK ==
- GNUNET_OS_process_wait (auditord));
- GNUNET_OS_process_destroy (auditord);
- return result;
-}
-
-
-/**
- * Initialize scheduler loop and curl context for the test case
- * including starting and stopping the auditor and exchange using the
- * given configuration file.
- *
- * @param main_cb main method.
- * @param main_cb_cls main method closure.
- * @param config_file configuration file name. Is is used
- * by both this function and the exchange itself. In the
- * first case it gives out the exchange port number and
- * the exchange base URL so as to check whether the port
- * is available and the exchange responds when requested
- * at its base URL.
- * @return #GNUNET_OK if no errors occurred.
- */
-int
-TALER_TESTING_setup_with_auditor_and_exchange (TALER_TESTING_Main main_cb,
- void *main_cb_cls,
- const char *config_file)
-{
- struct TALER_TESTING_SetupContext setup_ctx = {
- .config_filename = config_file,
- .main_cb = main_cb,
- .main_cb_cls = main_cb_cls
- };
-
- return GNUNET_CONFIGURATION_parse_and_run (config_file,
- &
- TALER_TESTING_setup_with_auditor_and_exchange_cfg,
- &setup_ctx);
-}
-
-
-/**
- * Test port in URL string for availability.
- *
- * @param url URL to extract port from, 80 is default
- * @return #GNUNET_OK if the port is free
- */
-int
-TALER_TESTING_url_port_free (const char *url)
-{
- const char *port;
- long pnum;
-
- port = strrchr (url,
- (unsigned char) ':');
- if (NULL == port)
- pnum = 80;
- else
- pnum = strtol (port + 1, NULL, 10);
- if (GNUNET_OK !=
- GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
- pnum))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Port %u not available.\n",
- (unsigned int) pnum);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/* end of testing_api_helpers_exchange.c */
diff --git a/src/testing/testing_api_loop.c b/src/testing/testing_api_loop.c
index 51cd74a23..0f242f7f1 100644
--- a/src/testing/testing_api_loop.c
+++ b/src/testing/testing_api_loop.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018 Taler Systems SA
+ Copyright (C) 2018-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
@@ -26,23 +26,67 @@
#include "platform.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_curl_lib.h>
+#include "taler_extensions.h"
#include "taler_signatures.h"
#include "taler_testing_lib.h"
-#include "taler_fakebank_lib.h"
-/**
- * Pipe used to communicate child death via signal.
- * Must be global, as used in signal handler!
- */
-static struct GNUNET_DISK_PipeHandle *sigpipe;
/**
- * Lookup command by label.
- *
- * @param is interpreter state to search
- * @param label label to look for
- * @return NULL if command was not found
+ * The interpreter and its state
*/
+struct TALER_TESTING_Interpreter
+{
+
+ /**
+ * Commands the interpreter will run.
+ */
+ struct TALER_TESTING_Command *commands;
+
+ /**
+ * Interpreter task (if one is scheduled).
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Handle for the child management.
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * Main execution context for the main loop.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Context for running the CURL event loop.
+ */
+ struct GNUNET_CURL_RescheduleContext *rc;
+
+ /**
+ * Hash map mapping variable names to commands.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *vars;
+
+ /**
+ * Task run on timeout.
+ */
+ struct GNUNET_SCHEDULER_Task *timeout_task;
+
+ /**
+ * Instruction pointer. Tells #interpreter_run() which instruction to run
+ * next. Need (signed) int because it gets -1 when rewinding the
+ * interpreter to the first CMD.
+ */
+ int ip;
+
+ /**
+ * Result of the testcases, #GNUNET_OK on success
+ */
+ enum GNUNET_GenericReturnValue result;
+
+};
+
+
const struct TALER_TESTING_Command *
TALER_TESTING_interpreter_lookup_command (struct TALER_TESTING_Interpreter *is,
const char *label)
@@ -66,28 +110,27 @@ TALER_TESTING_interpreter_lookup_command (struct TALER_TESTING_Interpreter *is,
if (TALER_TESTING_cmd_is_batch (cmd))
{
-#define BATCH_INDEX 1
struct TALER_TESTING_Command *batch;
struct TALER_TESTING_Command *current;
+ struct TALER_TESTING_Command *icmd;
const struct TALER_TESTING_Command *match;
current = TALER_TESTING_cmd_batch_get_current (cmd);
GNUNET_assert (GNUNET_OK ==
- TALER_TESTING_get_trait_cmd (cmd,
- BATCH_INDEX,
- &batch));
+ TALER_TESTING_get_trait_batch_cmds (cmd,
+ &batch));
/* We must do the loop forward, but we can find the last match */
match = NULL;
for (unsigned int j = 0;
- NULL != (cmd = &batch[j])->label;
+ NULL != (icmd = &batch[j])->label;
j++)
{
- if (current == cmd)
+ if (current == icmd)
break; /* do not go past current command */
- if ( (NULL != cmd->label) &&
- (0 == strcmp (cmd->label,
+ if ( (NULL != icmd->label) &&
+ (0 == strcmp (icmd->label,
label)) )
- match = cmd;
+ match = icmd;
}
if (NULL != match)
return match;
@@ -97,62 +140,48 @@ TALER_TESTING_interpreter_lookup_command (struct TALER_TESTING_Interpreter *is,
"Command not found: %s\n",
label);
return NULL;
+}
+
+const struct TALER_TESTING_Command *
+TALER_TESTING_interpreter_get_command (struct TALER_TESTING_Interpreter *is,
+ const char *name)
+{
+ const struct TALER_TESTING_Command *cmd;
+ struct GNUNET_HashCode h_name;
+
+ GNUNET_CRYPTO_hash (name,
+ strlen (name),
+ &h_name);
+ cmd = GNUNET_CONTAINER_multihashmap_get (is->vars,
+ &h_name);
+ if (NULL == cmd)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Command not found by name: %s\n",
+ name);
+ return cmd;
}
-/**
- * Obtain main execution context for the main loop.
- */
struct GNUNET_CURL_Context *
-TALER_TESTING_interpreter_get_context
- (struct TALER_TESTING_Interpreter *is)
+TALER_TESTING_interpreter_get_context (struct TALER_TESTING_Interpreter *is)
{
return is->ctx;
}
-struct TALER_FAKEBANK_Handle *
-TALER_TESTING_interpreter_get_fakebank (struct TALER_TESTING_Interpreter *is)
+void
+TALER_TESTING_touch_cmd (struct TALER_TESTING_Interpreter *is)
{
- return is->fakebank;
+ is->commands[is->ip].last_req_time
+ = GNUNET_TIME_absolute_get ();
}
-/**
- * Run tests starting the "fakebank" first. The "fakebank"
- * is a C minimalist version of the human-oriented Python bank,
- * which is also part of the Taler project.
- *
- * @param is pointer to the interpreter state
- * @param commands the list of commands to execute
- * @param bank_url the url the fakebank is supposed to run on
- */
void
-TALER_TESTING_run_with_fakebank (struct TALER_TESTING_Interpreter *is,
- struct TALER_TESTING_Command *commands,
- const char *bank_url)
+TALER_TESTING_inc_tries (struct TALER_TESTING_Interpreter *is)
{
- char *currency;
-
- if (GNUNET_OK !=
- TALER_config_get_currency (is->cfg,
- &currency))
- {
- is->result = GNUNET_SYSERR;
- return;
- }
- is->fakebank = TALER_TESTING_run_fakebank (bank_url,
- currency);
- GNUNET_free (currency);
- if (NULL == is->fakebank)
- {
- GNUNET_break (0);
- is->result = GNUNET_SYSERR;
- return;
- }
- TALER_TESTING_run (is,
- commands);
+ is->commands[is->ip].num_tries++;
}
@@ -165,9 +194,6 @@ static void
interpreter_run (void *cls);
-/**
- * Current command is done, run the next one.
- */
void
TALER_TESTING_interpreter_next (struct TALER_TESTING_Interpreter *is)
{
@@ -179,7 +205,13 @@ TALER_TESTING_interpreter_next (struct TALER_TESTING_Interpreter *is)
return; /* ignore, we already failed! */
if (TALER_TESTING_cmd_is_batch (cmd))
{
- TALER_TESTING_cmd_batch_next (is);
+ if (TALER_TESTING_cmd_batch_next (is,
+ cmd->cls))
+ {
+ /* batch is done */
+ cmd->finish_time = GNUNET_TIME_absolute_get ();
+ is->ip++;
+ }
}
else
{
@@ -193,7 +225,7 @@ TALER_TESTING_interpreter_next (struct TALER_TESTING_Interpreter *is)
"Interpreter executed 1000 instructions in %s\n",
GNUNET_STRINGS_relative_time_to_string (
GNUNET_TIME_absolute_get_duration (last_report),
- GNUNET_YES));
+ true));
last_report = GNUNET_TIME_absolute_get ();
}
ipc++;
@@ -202,11 +234,6 @@ TALER_TESTING_interpreter_next (struct TALER_TESTING_Interpreter *is)
}
-/**
- * Current command failed, clean up and fail the test case.
- *
- * @param is interpreter of the test
- */
void
TALER_TESTING_interpreter_fail (struct TALER_TESTING_Interpreter *is)
{
@@ -227,27 +254,9 @@ TALER_TESTING_interpreter_fail (struct TALER_TESTING_Interpreter *is)
}
-/**
- * Create command array terminator.
- *
- * @return a end-command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_end (void)
-{
- static struct TALER_TESTING_Command cmd;
- cmd.label = NULL;
-
- return cmd;
-}
-
-
-/**
- * Obtain current label.
- */
const char *
-TALER_TESTING_interpreter_get_current_label (struct
- TALER_TESTING_Interpreter *is)
+TALER_TESTING_interpreter_get_current_label (
+ struct TALER_TESTING_Interpreter *is)
{
struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
@@ -255,11 +264,6 @@ TALER_TESTING_interpreter_get_current_label (struct
}
-/**
- * Run the main interpreter loop that performs exchange operations.
- *
- * @param cls contains the `struct TALER_TESTING_Interpreter`
- */
static void
interpreter_run (void *cls)
{
@@ -267,7 +271,6 @@ interpreter_run (void *cls)
struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
is->task = NULL;
-
if (NULL == cmd->label)
{
@@ -278,13 +281,26 @@ interpreter_run (void *cls)
return;
}
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Running command `%s'\n",
cmd->label);
cmd->start_time
= cmd->last_req_time
= GNUNET_TIME_absolute_get ();
cmd->num_tries = 1;
+ if (NULL != cmd->name)
+ {
+ struct GNUNET_HashCode h_name;
+
+ GNUNET_CRYPTO_hash (cmd->name,
+ strlen (cmd->name),
+ &h_name);
+ (void) GNUNET_CONTAINER_multihashmap_put (
+ is->vars,
+ &h_name,
+ cmd,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE);
+ }
cmd->run (cmd->cls,
cmd,
is);
@@ -307,23 +323,15 @@ do_shutdown (void *cls)
label = is->commands[is->ip].label;
if (NULL == label)
label = "END";
-
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Executing shutdown at `%s'\n",
label);
-
for (unsigned int j = 0;
NULL != (cmd = &is->commands[j])->label;
j++)
- cmd->cleanup (cmd->cls,
- cmd);
-
- if (NULL != is->exchange)
- {
- TALER_LOG_DEBUG ("Disconnecting the exchange\n");
- TALER_EXCHANGE_disconnect (is->exchange);
- is->exchange = NULL;
- }
+ if (NULL != cmd->cleanup)
+ cmd->cleanup (cmd->cls,
+ cmd);
if (NULL != is->task)
{
GNUNET_SCHEDULER_cancel (is->task);
@@ -339,29 +347,29 @@ do_shutdown (void *cls)
GNUNET_CURL_gnunet_rc_destroy (is->rc);
is->rc = NULL;
}
+ if (NULL != is->vars)
+ {
+ GNUNET_CONTAINER_multihashmap_destroy (is->vars);
+ is->vars = NULL;
+ }
if (NULL != is->timeout_task)
{
GNUNET_SCHEDULER_cancel (is->timeout_task);
is->timeout_task = NULL;
}
- if (NULL != is->child_death_task)
- {
- GNUNET_SCHEDULER_cancel (is->child_death_task);
- is->child_death_task = NULL;
- }
- if (NULL != is->fakebank)
+ if (NULL != is->cwh)
{
- TALER_FAKEBANK_stop (is->fakebank);
- is->fakebank = NULL;
+ GNUNET_wait_child_cancel (is->cwh);
+ is->cwh = NULL;
}
- GNUNET_free_non_null (is->commands);
+ GNUNET_free (is->commands);
}
/**
* Function run when the test terminates (good or bad) with timeout.
*
- * @param cls NULL
+ * @param cls the `struct TALER_TESTING_Interpreter *`
*/
static void
do_timeout (void *cls)
@@ -379,114 +387,98 @@ do_timeout (void *cls)
* Task triggered whenever we receive a SIGCHLD (child
* process died).
*
- * @param cls closure
+ * @param cls the `struct TALER_TESTING_Interpreter *`
+ * @param type type of the process
+ * @param code status code of the process
*/
static void
-maint_child_death (void *cls)
+maint_child_death (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int code)
{
struct TALER_TESTING_Interpreter *is = cls;
struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
- const struct GNUNET_DISK_FileHandle *pr;
-
struct GNUNET_OS_Process **processp;
- char c[16];
-
- if (TALER_TESTING_cmd_is_batch (cmd))
- {
- struct TALER_TESTING_Command *batch_cmd;
-
- GNUNET_assert
- (GNUNET_OK == TALER_TESTING_get_trait_cmd
- (cmd, 0, &batch_cmd)); /* bad? */
- cmd = batch_cmd;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ is->cwh = NULL;
+ while (TALER_TESTING_cmd_is_batch (cmd))
+ cmd = TALER_TESTING_cmd_batch_get_current (cmd);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got SIGCHLD for `%s'.\n",
cmd->label);
-
- is->child_death_task = NULL;
- pr = GNUNET_DISK_pipe_handle (sigpipe,
- GNUNET_DISK_PIPE_END_READ);
- GNUNET_break (0 <
- GNUNET_DISK_file_read (pr,
- &c,
- sizeof (c)));
if (GNUNET_OK !=
TALER_TESTING_get_trait_process (cmd,
- 0,
&processp))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
-
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Got the dead child process handle"
- ", waiting for termination ...\n");
-
- GNUNET_OS_process_wait (*processp);
+ "Got the dead child process handle, waiting for termination ...\n");
GNUNET_OS_process_destroy (*processp);
*processp = NULL;
-
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"... definitively terminated\n");
-
- if (GNUNET_OK == is->reload_keys)
+ switch (type)
{
- if (NULL == is->exchanged)
- {
- GNUNET_break (0);
- }
- else
+ case GNUNET_OS_PROCESS_UNKNOWN:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ case GNUNET_OS_PROCESS_RUNNING:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ case GNUNET_OS_PROCESS_STOPPED:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ case GNUNET_OS_PROCESS_EXITED:
+ if (0 != code)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Triggering key state reload at exchange\n");
- GNUNET_break (0 == GNUNET_OS_process_kill
- (is->exchanged, SIGUSR1));
- sleep (5); /* make sure signal was received and processed */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Process exited with unexpected status %u\n",
+ (unsigned int) code);
+ TALER_TESTING_interpreter_fail (is);
+ return;
}
+ break;
+ case GNUNET_OS_PROCESS_SIGNALED:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
}
-
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Dead child, go on with next command.\n");
TALER_TESTING_interpreter_next (is);
}
-/**
- * Wait until we receive SIGCHLD signal.
- * Then obtain the process trait of the current
- * command, wait on the the zombie and continue
- * with the next command.
- */
void
TALER_TESTING_wait_for_sigchld (struct TALER_TESTING_Interpreter *is)
{
- const struct GNUNET_DISK_FileHandle *pr;
+ struct GNUNET_OS_Process **processp;
+ struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
- GNUNET_assert (NULL == is->child_death_task);
- pr = GNUNET_DISK_pipe_handle (sigpipe,
- GNUNET_DISK_PIPE_END_READ);
- is->child_death_task
- = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
- pr,
- &maint_child_death,
- is);
+ while (TALER_TESTING_cmd_is_batch (cmd))
+ cmd = TALER_TESTING_cmd_batch_get_current (cmd);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_process (cmd,
+ &processp))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (NULL == is->cwh);
+ is->cwh
+ = GNUNET_wait_child (*processp,
+ &maint_child_death,
+ is);
}
-/**
- * Run the testsuite. Note, CMDs are copied into
- * the interpreter state because they are _usually_
- * defined into the "run" method that returns after
- * having scheduled the test interpreter.
- *
- * @param is the interpreter state
- * @param commands the list of command to execute
- * @param timeout how long to wait
- */
void
TALER_TESTING_run2 (struct TALER_TESTING_Interpreter *is,
struct TALER_TESTING_Command *commands,
@@ -502,37 +494,36 @@ TALER_TESTING_run2 (struct TALER_TESTING_Interpreter *is,
/* get the number of commands */
for (i = 0; NULL != commands[i].label; i++)
;
- is->commands = GNUNET_new_array (i + 1,
- struct TALER_TESTING_Command);
- memcpy (is->commands,
- commands,
- sizeof (struct TALER_TESTING_Command) * i);
- is->timeout_task = GNUNET_SCHEDULER_add_delayed
- (timeout,
- &do_timeout,
- is);
- GNUNET_SCHEDULER_add_shutdown (&do_shutdown, is);
- is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, is);
+ is->commands = GNUNET_malloc_large ( (i + 1)
+ * sizeof (struct TALER_TESTING_Command));
+ GNUNET_assert (NULL != is->commands);
+ GNUNET_memcpy (is->commands,
+ commands,
+ sizeof (struct TALER_TESTING_Command) * i);
+ is->timeout_task = GNUNET_SCHEDULER_add_delayed (
+ timeout,
+ &do_timeout,
+ is);
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ is);
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
}
-/**
- * Run the testsuite. Note, CMDs are copied into
- * the interpreter state because they are _usually_
- * defined into the "run" method that returns after
- * having scheduled the test interpreter.
- *
- * @param is the interpreter state
- * @param commands the list of command to execute
- */
+#include "valgrind.h"
+
void
TALER_TESTING_run (struct TALER_TESTING_Interpreter *is,
struct TALER_TESTING_Command *commands)
{
TALER_TESTING_run2 (is,
commands,
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES,
- 5));
+ 0 == RUNNING_ON_VALGRIND
+ ? GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES,
+ 5)
+ : GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES,
+ 50));
}
@@ -566,263 +557,446 @@ struct MainContext
/**
- * Signal handler called for SIGCHLD. Triggers the
- * respective handler by writing to the trigger pipe.
+ * Initialize scheduler loop and curl context for the testcase,
+ * and responsible to run the "run" method.
+ *
+ * @param cls closure, typically the "run" method, the
+ * interpreter state and a closure for "run".
*/
static void
-sighandler_child_death (void)
+main_wrapper (void *cls)
+{
+ struct MainContext *main_ctx = cls;
+
+ main_ctx->main_cb (main_ctx->main_cb_cls,
+ main_ctx->is);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TESTING_loop (TALER_TESTING_Main main_cb,
+ void *main_cb_cls)
+{
+ struct TALER_TESTING_Interpreter is;
+ struct MainContext main_ctx = {
+ .main_cb = main_cb,
+ .main_cb_cls = main_cb_cls,
+ /* needed to init the curl ctx */
+ .is = &is,
+ };
+
+ memset (&is,
+ 0,
+ sizeof (is));
+ is.ctx = GNUNET_CURL_init (
+ &GNUNET_CURL_gnunet_scheduler_reschedule,
+ &is.rc);
+ GNUNET_CURL_enable_async_scope_header (is.ctx,
+ "Taler-Correlation-Id");
+ GNUNET_assert (NULL != is.ctx);
+ is.rc = GNUNET_CURL_gnunet_rc_create (is.ctx);
+ is.vars = GNUNET_CONTAINER_multihashmap_create (1024,
+ false);
+ /* Blocking */
+ GNUNET_SCHEDULER_run (&main_wrapper,
+ &main_ctx);
+ return is.result;
+}
+
+
+int
+TALER_TESTING_main (char *const *argv,
+ const char *loglevel,
+ const char *cfg_file,
+ const char *exchange_account_section,
+ enum TALER_TESTING_BankSystem bs,
+ struct TALER_TESTING_Credentials *cred,
+ TALER_TESTING_Main main_cb,
+ void *main_cb_cls)
+{
+ enum GNUNET_GenericReturnValue ret;
+
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ GNUNET_log_setup (argv[0],
+ loglevel,
+ NULL);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_credentials (cfg_file,
+ exchange_account_section,
+ bs,
+ cred))
+ {
+ GNUNET_break (0);
+ return 77;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_cleanup_files_cfg (NULL,
+ cred->cfg))
+ {
+ GNUNET_break (0);
+ return 77;
+ }
+ if (GNUNET_OK !=
+ TALER_extensions_init (cred->cfg))
+ {
+ GNUNET_break (0);
+ return 77;
+ }
+ ret = TALER_TESTING_loop (main_cb,
+ main_cb_cls);
+ /* TODO: should we free 'cred' resources here? */
+ return (GNUNET_OK == ret) ? 0 : 1;
+}
+
+
+/* ************** iterate over commands ********* */
+
+
+void
+TALER_TESTING_iterate (struct TALER_TESTING_Interpreter *is,
+ bool asc,
+ TALER_TESTING_CommandIterator cb,
+ void *cb_cls)
{
- static char c;
- int old_errno = errno; /* back-up errno */
+ unsigned int start;
+ unsigned int end;
+ int inc;
+
+ if (asc)
+ {
+ inc = 1;
+ start = 0;
+ end = is->ip;
+ }
+ else
+ {
+ inc = -1;
+ start = is->ip;
+ end = 0;
+ }
+ for (unsigned int off = start; off != end + inc; off += inc)
+ {
+ const struct TALER_TESTING_Command *cmd = &is->commands[off];
- GNUNET_break (1 == GNUNET_DISK_file_write
- (GNUNET_DISK_pipe_handle (sigpipe,
- GNUNET_DISK_PIPE_END_WRITE),
- &c, sizeof (c)));
- errno = old_errno; /* restore errno */
+ cb (cb_cls,
+ cmd);
+ }
+}
+
+
+/* ************** special commands ********* */
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_end (void)
+{
+ static struct TALER_TESTING_Command cmd;
+ cmd.label = NULL;
+
+ return cmd;
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_var (const char *name,
+ struct TALER_TESTING_Command cmd)
+{
+ cmd.name = name;
+ return cmd;
}
/**
- * "Canonical" cert_cb used when we are connecting to the
- * Exchange.
- *
- * @param cls closure, typically, the "run" method containing
- * all the commands to be run, and a closure for it.
- * @param keys the exchange's keys.
- * @param compat protocol compatibility information.
- * @param ec error code, #TALER_EC_NONE on success
- * @param http_status status returned by /keys, #MHD_HTTP_OK on success
- * @param full_reply JSON body of /keys request, NULL if reply was not in JSON
+ * State for a "rewind" CMD.
*/
-void
-TALER_TESTING_cert_cb (void *cls,
- const struct TALER_EXCHANGE_Keys *keys,
- enum TALER_EXCHANGE_VersionCompatibility compat,
- enum TALER_ErrorCode ec,
- unsigned int http_status,
- const json_t *full_reply)
+struct RewindIpState
{
- struct MainContext *main_ctx = cls;
- struct TALER_TESTING_Interpreter *is = main_ctx->is;
+ /**
+ * Instruction pointer to set into the interpreter.
+ */
+ const char *target_label;
+
+ /**
+ * How many times this set should take place. However, this value lives at
+ * the calling process, and this CMD is only in charge of checking and
+ * decremeting it.
+ */
+ unsigned int counter;
+};
- (void) compat;
- (void) full_reply;
- if (NULL == keys)
+
+/**
+ * Seek for the @a target command in @a batch (and rewind to it
+ * if successful).
+ *
+ * @param is the interpreter state (for failures)
+ * @param cmd batch to search for @a target
+ * @param target command to search for
+ * @return #GNUNET_OK on success, #GNUNET_NO if target was not found,
+ * #GNUNET_SYSERR if target is in the future and we failed
+ */
+static enum GNUNET_GenericReturnValue
+seek_batch (struct TALER_TESTING_Interpreter *is,
+ const struct TALER_TESTING_Command *cmd,
+ const struct TALER_TESTING_Command *target)
+{
+ unsigned int new_ip;
+ struct TALER_TESTING_Command *batch;
+ struct TALER_TESTING_Command *current;
+ struct TALER_TESTING_Command *icmd;
+ struct TALER_TESTING_Command *match;
+
+ current = TALER_TESTING_cmd_batch_get_current (cmd);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_batch_cmds (cmd,
+ &batch));
+ match = NULL;
+ for (new_ip = 0;
+ NULL != (icmd = &batch[new_ip]);
+ new_ip++)
{
- if (GNUNET_NO == is->working)
+ if (current == target)
+ current = NULL;
+ if (icmd == target)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Got NULL response for /keys during startup (%u/%d), retrying!\n",
- http_status,
- (int) ec);
- TALER_EXCHANGE_disconnect (is->exchange);
- GNUNET_assert
- (NULL != (is->exchange = TALER_EXCHANGE_connect
- (is->ctx,
- main_ctx->exchange_url,
- &TALER_TESTING_cert_cb,
- main_ctx,
- TALER_EXCHANGE_OPTION_END)));
- return;
+ match = icmd;
+ break;
}
- else
+ if (TALER_TESTING_cmd_is_batch (icmd))
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Got NULL response for /keys during execution (%u/%d)!\n",
- http_status,
- (int) ec);
+ int ret = seek_batch (is,
+ icmd,
+ target);
+ if (GNUNET_SYSERR == ret)
+ return GNUNET_SYSERR; /* failure! */
+ if (GNUNET_OK == ret)
+ {
+ match = icmd;
+ break;
+ }
}
}
- else
+ if (NULL == current)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Got %d DK from /keys in generation %u\n",
- keys->num_denom_keys,
- is->key_generation + 1);
+ /* refuse to jump forward */
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return GNUNET_SYSERR;
}
- is->key_generation++;
- is->keys = keys;
+ if (NULL == match)
+ return GNUNET_NO; /* not found */
+ TALER_TESTING_cmd_batch_set_current (cmd,
+ new_ip);
+ return GNUNET_OK;
+}
- /* /keys has been called for some reason and
- * the interpreter is already running. */
- if (GNUNET_YES == is->working)
- return;
- is->working = GNUNET_YES;
+/**
+ * Run the "rewind" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being executed now.
+ * @param is the interpreter state.
+ */
+static void
+rewind_ip_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RewindIpState *ris = cls;
+ const struct TALER_TESTING_Command *target;
+ unsigned int new_ip;
- /* Very first start of tests, call "run()" */
- if (1 == is->key_generation)
+ (void) cmd;
+ if (0 == ris->counter)
{
- main_ctx->main_cb (main_ctx->main_cb_cls,
- is);
+ TALER_TESTING_interpreter_next (is);
return;
}
+ target
+ = TALER_TESTING_interpreter_lookup_command (is,
+ ris->target_label);
+ if (NULL == target)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ ris->counter--;
+ for (new_ip = 0;
+ NULL != is->commands[new_ip].label;
+ new_ip++)
+ {
+ const struct TALER_TESTING_Command *cmd = &is->commands[new_ip];
- /* Tests already started, just trigger the
- * next command. */
- TALER_LOG_DEBUG ("Cert_cb, scheduling CMD (ip: %d)\n",
- is->ip);
- GNUNET_SCHEDULER_add_now (&interpreter_run,
- is);
+ if (cmd == target)
+ break;
+ if (TALER_TESTING_cmd_is_batch (cmd))
+ {
+ int ret = seek_batch (is,
+ cmd,
+ target);
+ if (GNUNET_SYSERR == ret)
+ return; /* failure! */
+ if (GNUNET_OK == ret)
+ break;
+ }
+ }
+ if (new_ip > (unsigned int) is->ip)
+ {
+ /* refuse to jump forward */
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ is->ip = new_ip - 1; /* -1 because the next function will advance by one */
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_rewind_ip (const char *label,
+ const char *target_label,
+ unsigned int counter)
+{
+ struct RewindIpState *ris;
+
+ ris = GNUNET_new (struct RewindIpState);
+ ris->target_label = target_label;
+ ris->counter = counter;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ris,
+ .label = label,
+ .run = &rewind_ip_run
+ };
+
+ return cmd;
+ }
}
/**
- * Initialize scheduler loop and curl context for the testcase,
- * and responsible to run the "run" method.
- *
- * @param cls closure, typically the "run" method, the
- * interpreter state and a closure for "run".
+ * State for a "authchange" CMD.
*/
-static void
-main_wrapper_exchange_agnostic (void *cls)
+struct AuthchangeState
{
- struct MainContext *main_ctx = cls;
- main_ctx->main_cb (main_ctx->main_cb_cls,
- main_ctx->is);
-}
+ /**
+ * What is the new authorization token to send?
+ */
+ const char *auth_token;
+
+ /**
+ * Old context, clean up on termination.
+ */
+ struct GNUNET_CURL_Context *old_ctx;
+};
/**
- * Function run when the test is aborted before we launch the actual
- * interpreter. Cleans up our state.
+ * Run the command.
*
- * @param cls the main context
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
*/
static void
-do_abort (void *cls)
+authchange_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
{
- struct MainContext *main_ctx = cls;
- struct TALER_TESTING_Interpreter *is = main_ctx->is;
+ struct AuthchangeState *ss = cls;
- is->timeout_task = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Executing abort prior to interpreter launch\n");
- if (NULL != is->exchange)
- {
- TALER_EXCHANGE_disconnect (is->exchange);
- is->exchange = NULL;
- }
- if (NULL != is->ctx)
- {
- GNUNET_CURL_fini (is->ctx);
- is->ctx = NULL;
- }
+ (void) cmd;
+ ss->old_ctx = is->ctx;
if (NULL != is->rc)
{
GNUNET_CURL_gnunet_rc_destroy (is->rc);
is->rc = NULL;
}
+ is->ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &is->rc);
+ GNUNET_CURL_enable_async_scope_header (is->ctx,
+ "Taler-Correlation-Id");
+ GNUNET_assert (NULL != is->ctx);
+ is->rc = GNUNET_CURL_gnunet_rc_create (is->ctx);
+ if (NULL != ss->auth_token)
+ {
+ char *authorization;
+
+ GNUNET_asprintf (&authorization,
+ "%s: %s",
+ MHD_HTTP_HEADER_AUTHORIZATION,
+ ss->auth_token);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CURL_append_header (is->ctx,
+ authorization));
+ GNUNET_free (authorization);
+ }
+ TALER_TESTING_interpreter_next (is);
}
/**
- * Initialize scheduler loop and curl context for the testcase,
- * and responsible to run the "run" method.
+ * Call GNUNET_CURL_fini(). Done as a separate task to
+ * ensure that all of the command's cleanups have been
+ * executed first. See #7151.
*
- * @param cls a `struct MainContext *`
+ * @param cls a `struct GNUNET_CURL_Context *` to clean up.
*/
static void
-main_wrapper_exchange_connect (void *cls)
+deferred_cleanup_cb (void *cls)
{
- struct MainContext *main_ctx = cls;
- struct TALER_TESTING_Interpreter *is = main_ctx->is;
- char *exchange_url;
+ struct GNUNET_CURL_Context *ctx = cls;
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (is->cfg,
- "exchange",
- "BASE_URL",
- &exchange_url))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "BASE_URL");
- return;
- }
- main_ctx->exchange_url = exchange_url;
- is->timeout_task = GNUNET_SCHEDULER_add_shutdown (&do_abort,
- main_ctx);
- GNUNET_break
- (NULL != (is->exchange =
- TALER_EXCHANGE_connect (is->ctx,
- exchange_url,
- &TALER_TESTING_cert_cb,
- main_ctx,
- TALER_EXCHANGE_OPTION_END)));
+ GNUNET_CURL_fini (ctx);
}
/**
- * Install signal handlers plus schedules the main wrapper
- * around the "run" method.
+ * Cleanup the state from a "authchange" CMD.
*
- * @param main_cb the "run" method which contains all the
- * commands.
- * @param main_cb_cls a closure for "run", typically NULL.
- * @param cfg configuration to use
- * @param exchanged exchange process handle: will be put in the
- * state as some commands - e.g. revoke - need to send
- * signal to it, for example to let it know to reload the
- * key state.. if NULL, the interpreter will run without
- * trying to connect to the exchange first.
- * @param exchange_connect #GNUNET_YES if the test should connect
- * to the exchange, #GNUNET_NO otherwise
- * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise.
- * non-GNUNET_OK codes are #GNUNET_SYSERR most of the
- * times.
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
*/
-int
-TALER_TESTING_setup (TALER_TESTING_Main main_cb,
- void *main_cb_cls,
- const struct GNUNET_CONFIGURATION_Handle *cfg,
- struct GNUNET_OS_Process *exchanged,
- int exchange_connect)
+static void
+authchange_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
{
- struct TALER_TESTING_Interpreter is;
- struct MainContext main_ctx = {
- .main_cb = main_cb,
- .main_cb_cls = main_cb_cls,
- /* needed to init the curl ctx */
- .is = &is,
- };
- struct GNUNET_SIGNAL_Context *shc_chld;
+ struct AuthchangeState *ss = cls;
- memset (&is,
- 0,
- sizeof (is));
- is.exchanged = exchanged;
- is.cfg = cfg;
- sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO,
- GNUNET_NO, GNUNET_NO);
- GNUNET_assert (NULL != sigpipe);
- shc_chld = GNUNET_SIGNAL_handler_install
- (GNUNET_SIGCHLD,
- &sighandler_child_death);
- is.ctx = GNUNET_CURL_init
- (&GNUNET_CURL_gnunet_scheduler_reschedule,
- &is.rc);
- GNUNET_CURL_enable_async_scope_header (is.ctx, "Taler-Correlation-Id");
- GNUNET_assert (NULL != is.ctx);
- is.rc = GNUNET_CURL_gnunet_rc_create (is.ctx);
+ (void) cmd;
+ if (NULL != ss->old_ctx)
+ {
+ (void) GNUNET_SCHEDULER_add_now (&deferred_cleanup_cb,
+ ss->old_ctx);
+ ss->old_ctx = NULL;
+ }
+ GNUNET_free (ss);
+}
- /* Blocking */
- if (GNUNET_YES == exchange_connect)
- GNUNET_SCHEDULER_run (&main_wrapper_exchange_connect,
- &main_ctx);
- else
- GNUNET_SCHEDULER_run (&main_wrapper_exchange_agnostic,
- &main_ctx);
- if (NULL != is.final_cleanup_cb)
- is.final_cleanup_cb (is.final_cleanup_cb_cls);
- GNUNET_free_non_null (main_ctx.exchange_url);
- GNUNET_SIGNAL_handler_uninstall (shc_chld);
- GNUNET_DISK_pipe_close (sigpipe);
- sigpipe = NULL;
- return is.result;
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_authorization (const char *label,
+ const char *auth_token)
+{
+ struct AuthchangeState *ss;
+
+ ss = GNUNET_new (struct AuthchangeState);
+ ss->auth_token = auth_token;
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ss,
+ .label = label,
+ .run = &authchange_run,
+ .cleanup = &authchange_cleanup
+ };
+
+ return cmd;
+ }
}
diff --git a/src/testing/testing_api_misc.c b/src/testing/testing_api_misc.c
new file mode 100644
index 000000000..80ff0b6c8
--- /dev/null
+++ b/src/testing/testing_api_misc.c
@@ -0,0 +1,394 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_misc.c
+ * @brief non-command functions useful for writing tests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_fakebank_lib.h"
+
+
+bool
+TALER_TESTING_has_in_name (const char *prog,
+ const char *marker)
+{
+ size_t name_pos;
+ size_t pos;
+
+ if (! prog || ! marker)
+ return false;
+
+ pos = 0;
+ name_pos = 0;
+ while (prog[pos])
+ {
+ if ('/' == prog[pos])
+ name_pos = pos + 1;
+ pos++;
+ }
+ if (name_pos == pos)
+ return true;
+ return (NULL != strstr (prog + name_pos,
+ marker));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TESTING_get_credentials (
+ const char *cfg_file,
+ const char *exchange_account_section,
+ enum TALER_TESTING_BankSystem bs,
+ struct TALER_TESTING_Credentials *ua)
+{
+ unsigned long long port;
+ char *exchange_payto_uri;
+
+ ua->cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_load (ua->cfg,
+ cfg_file))
+ {
+ GNUNET_break (0);
+ GNUNET_CONFIGURATION_destroy (ua->cfg);
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ strncasecmp (exchange_account_section,
+ "exchange-account-",
+ strlen ("exchange-account-")))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ua->cfg,
+ exchange_account_section,
+ "PAYTO_URI",
+ &exchange_payto_uri))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ exchange_account_section,
+ "PAYTO_URI");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (ua->cfg,
+ "bank",
+ "HTTP_PORT",
+ &port))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "bank",
+ "HTTP_PORT");
+ return GNUNET_SYSERR;
+ }
+ {
+ char *csn;
+
+ GNUNET_asprintf (&csn,
+ "exchange-accountcredentials-%s",
+ &exchange_account_section[strlen ("exchange-account-")]);
+ if (GNUNET_OK !=
+ TALER_BANK_auth_parse_cfg (ua->cfg,
+ csn,
+ &ua->ba))
+ {
+ GNUNET_break (0);
+ GNUNET_free (csn);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (csn);
+ }
+ {
+ char *csn;
+
+ GNUNET_asprintf (&csn,
+ "admin-accountcredentials-%s",
+ &exchange_account_section[strlen ("exchange-account-")]);
+ if (GNUNET_OK !=
+ TALER_BANK_auth_parse_cfg (ua->cfg,
+ csn,
+ &ua->ba_admin))
+ {
+ GNUNET_break (0);
+ GNUNET_free (csn);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (csn);
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ua->cfg,
+ "exchange",
+ "BASE_URL",
+ &ua->exchange_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ua->cfg,
+ "auditor",
+ "BASE_URL",
+ &ua->auditor_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "auditor",
+ "BASE_URL");
+ return GNUNET_SYSERR;
+ }
+
+ switch (bs)
+ {
+ case TALER_TESTING_BS_FAKEBANK:
+ ua->exchange_payto
+ = exchange_payto_uri;
+ ua->user42_payto
+ = GNUNET_strdup ("payto://x-taler-bank/localhost/42?receiver-name=42");
+ ua->user43_payto
+ = GNUNET_strdup ("payto://x-taler-bank/localhost/43?receiver-name=43");
+ break;
+ case TALER_TESTING_BS_IBAN:
+ ua->exchange_payto
+ = exchange_payto_uri;
+ ua->user42_payto
+ = GNUNET_strdup (
+ "payto://iban/SANDBOXX/FR7630006000011234567890189?receiver-name=User42");
+ ua->user43_payto
+ = GNUNET_strdup (
+ "payto://iban/SANDBOXX/GB33BUKB20201555555555?receiver-name=User43");
+ break;
+ }
+ return GNUNET_OK;
+}
+
+
+json_t *
+TALER_TESTING_make_wire_details (const char *payto)
+{
+ struct TALER_WireSaltP salt;
+
+ /* salt must be constant for aggregation tests! */
+ memset (&salt,
+ 47,
+ sizeof (salt));
+ return GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ payto),
+ GNUNET_JSON_pack_data_auto ("salt",
+ &salt));
+}
+
+
+/**
+ * Remove @a option directory from @a section in @a cfg.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+remove_dir (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section,
+ const char *option)
+{
+ char *dir;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ section,
+ option,
+ &dir))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ option);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_YES ==
+ GNUNET_DISK_directory_test (dir,
+ GNUNET_NO))
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_directory_remove (dir));
+ GNUNET_free (dir);
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TESTING_cleanup_files_cfg (
+ void *cls,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ char *dir;
+
+ (void) cls;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ "exchange-offline",
+ "SECM_TOFU_FILE",
+ &dir))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange-offline",
+ "SECM_TOFU_FILE");
+ return GNUNET_SYSERR;
+ }
+ if ( (0 != unlink (dir)) &&
+ (ENOENT != errno) )
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "unlink",
+ dir);
+ GNUNET_free (dir);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (dir);
+ if (GNUNET_OK !=
+ remove_dir (cfg,
+ "taler-exchange-secmod-eddsa",
+ "KEY_DIR"))
+ return GNUNET_SYSERR;
+ if (GNUNET_OK !=
+ remove_dir (cfg,
+ "taler-exchange-secmod-rsa",
+ "KEY_DIR"))
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_TESTING_find_pk (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_Amount *amount,
+ bool age_restricted)
+{
+ struct GNUNET_TIME_Timestamp now;
+ struct TALER_EXCHANGE_DenomPublicKey *pk;
+ char *str;
+
+ now = GNUNET_TIME_timestamp_get ();
+ for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+ {
+ pk = &keys->denom_keys[i];
+ if ( (0 == TALER_amount_cmp (amount,
+ &pk->value)) &&
+ (GNUNET_TIME_timestamp_cmp (now,
+ >=,
+ pk->valid_from)) &&
+ (GNUNET_TIME_timestamp_cmp (now,
+ <,
+ pk->withdraw_valid_until)) &&
+ (age_restricted == (0 != pk->key.age_mask.bits)) )
+ return pk;
+ }
+ /* do 2nd pass to check if expiration times are to blame for
+ * failure */
+ str = TALER_amount_to_string (amount);
+ for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+ {
+ pk = &keys->denom_keys[i];
+ if ( (0 == TALER_amount_cmp (amount,
+ &pk->value)) &&
+ (GNUNET_TIME_timestamp_cmp (now,
+ <,
+ pk->valid_from) ||
+ GNUNET_TIME_timestamp_cmp (now,
+ >,
+ pk->withdraw_valid_until) ) &&
+ (age_restricted == (0 != pk->key.age_mask.bits)) )
+ {
+ GNUNET_log
+ (GNUNET_ERROR_TYPE_WARNING,
+ "Have denomination key for `%s', but with wrong"
+ " expiration range %llu vs [%llu,%llu)\n",
+ str,
+ (unsigned long long) now.abs_time.abs_value_us,
+ (unsigned long long) pk->valid_from.abs_time.abs_value_us,
+ (unsigned long long) pk->withdraw_valid_until.abs_time.abs_value_us);
+ GNUNET_free (str);
+ return NULL;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No denomination key for amount %s found\n",
+ str);
+ GNUNET_free (str);
+ return NULL;
+}
+
+
+int
+TALER_TESTING_wait_httpd_ready (const char *base_url)
+{
+ char *wget_cmd;
+ unsigned int iter;
+
+ GNUNET_asprintf (&wget_cmd,
+ "wget -q -t 1 -T 1 %s -o /dev/null -O /dev/null",
+ base_url); // make sure ends with '/'
+ /* give child time to start and bind against the socket */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Waiting for HTTP service to be ready (check with: %s)\n",
+ wget_cmd);
+ iter = 0;
+ do
+ {
+ if (10 == iter)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to launch HTTP service (or `wget')\n");
+ GNUNET_free (wget_cmd);
+ return 77;
+ }
+ sleep (1);
+ iter++;
+ }
+ while (0 != system (wget_cmd));
+ GNUNET_free (wget_cmd);
+ return 0;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TESTING_url_port_free (const char *url)
+{
+ const char *port;
+ long pnum;
+
+ port = strrchr (url,
+ (unsigned char) ':');
+ if (NULL == port)
+ pnum = 80;
+ else
+ pnum = strtol (port + 1, NULL, 10);
+ if (GNUNET_OK !=
+ GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
+ pnum))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Port %u not available.\n",
+ (unsigned int) pnum);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
diff --git a/src/testing/testing_api_trait_amount.c b/src/testing/testing_api_trait_amount.c
deleted file mode 100644
index 96698b49e..000000000
--- a/src/testing/testing_api_trait_amount.c
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_amount.c
- * @brief offer amounts as traits.
- * @author Marcello Stanisci
- */
-
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_AMOUNT "amount"
-
-/**
- * Obtain an amount from a @a cmd.
- *
- * @param cmd command to extract the amount from.
- * @param index which amount to pick if @a cmd has multiple
- * on offer
- * @param[out] amount set to the amount.
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_get_trait_amount_obj (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_Amount **amount)
-{
- return cmd->traits (cmd->cls,
- (const void **) amount,
- TALER_TESTING_TRAIT_AMOUNT,
- index);
-}
-
-
-/**
- * Offer amount.
- *
- * @param index which amount to offer, in case there are
- * multiple available.
- * @param amount the amount to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_amount_obj (unsigned int index,
- const struct TALER_Amount *amount)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_AMOUNT,
- .ptr = (const void *) amount
- };
-
- return ret;
-}
-
-
-/* end of testing_api_trait_amount.c */
diff --git a/src/testing/testing_api_trait_blinding_key.c b/src/testing/testing_api_trait_blinding_key.c
deleted file mode 100644
index ae1889a1f..000000000
--- a/src/testing/testing_api_trait_blinding_key.c
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_blinding_key.c
- * @brief offer blinding keys as traits.
- * @author Christian Grothoff
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_BLINDING_KEY "blinding-key"
-
-
-/**
- * Obtain a blinding key from a @a cmd.
- *
- * @param cmd command to extract trait from
- * @param index which coin to pick if @a cmd has multiple on offer.
- * @param[out] blinding_key set to the offered blinding key.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_blinding_key
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_DenominationBlindingKeyP **blinding_key)
-{
- return cmd->traits (cmd->cls,
- (const void **) blinding_key,
- TALER_TESTING_TRAIT_BLINDING_KEY,
- index);
-}
-
-
-/**
- * Offer blinding key.
- *
- * @param index index number to associate to the offered key.
- * @param blinding_key blinding key to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_blinding_key
- (unsigned int index,
- const struct TALER_DenominationBlindingKeyP *blinding_key)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_BLINDING_KEY,
- .ptr = (const void *) blinding_key
- };
-
- return ret;
-}
-
-
-/* end of testing_api_trait_blinding_key.c */
diff --git a/src/testing/testing_api_trait_cmd.c b/src/testing/testing_api_trait_cmd.c
deleted file mode 100644
index f24054713..000000000
--- a/src/testing/testing_api_trait_cmd.c
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_cmd.c
- * @brief offers CMDs as traits.
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_CMD "cmd"
-
-
-/**
- * Obtain a command from @a cmd.
- *
- * @param cmd command to extract the command from.
- * @param index always zero. Commands offering this
- * kind of traits do not need this index. For
- * example, a "batch" CMD returns always the
- * CMD currently being executed.
- * @param[out] _cmd where to write the wire details.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_cmd (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- struct TALER_TESTING_Command **_cmd)
-{
- return cmd->traits (cmd->cls,
- (const void **) _cmd,
- TALER_TESTING_TRAIT_CMD,
- index);
-}
-
-
-/**
- * Offer a command in a trait.
- *
- * @param index always zero. Commands offering this
- * kind of traits do not need this index. For
- * example, a "meta" CMD returns always the
- * CMD currently being executed.
- * @param cmd wire details to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_cmd (unsigned int index,
- const struct TALER_TESTING_Command *cmd)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_CMD,
- .ptr = (const struct TALER_TESTING_Command *) cmd
- };
- return ret;
-}
-
-
-/* end of testing_api_trait_cmd.c */
diff --git a/src/testing/testing_api_trait_coin_priv.c b/src/testing/testing_api_trait_coin_priv.c
deleted file mode 100644
index 61a770cf6..000000000
--- a/src/testing/testing_api_trait_coin_priv.c
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_coin_priv.c
- * @brief coin priv traits.
- * @author Christian Grothoff
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_COIN_PRIVATE_KEY "coin-private-key"
-
-
-/**
- * Obtain a coin private key from a @a cmd.
- *
- * @param cmd command to extract trait from.
- * @param index index of the coin priv to obtain.
- * @param[out] coin_priv set to the private key of the coin.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_coin_priv
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_CoinSpendPrivateKeyP **coin_priv)
-{
- return cmd->traits (cmd->cls,
- (const void **) coin_priv,
- TALER_TESTING_TRAIT_COIN_PRIVATE_KEY,
- index);
-}
-
-
-/**
- * Offer coin private key.
- *
- * @param index index number to associate with offered coin priv.
- * @param coin_priv coin private key to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_coin_priv
- (unsigned int index,
- const struct TALER_CoinSpendPrivateKeyP *coin_priv)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_COIN_PRIVATE_KEY,
- .ptr = (const void *) coin_priv
- };
-
- return ret;
-}
-
-
-/* end of testing_api_trait_coin_priv.c */
diff --git a/src/testing/testing_api_trait_contract.c b/src/testing/testing_api_trait_contract.c
deleted file mode 100644
index 1e88cb86f..000000000
--- a/src/testing/testing_api_trait_contract.c
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018-2020 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 testing/testing_api_trait_contract.c
- * @brief offers contract term trait.
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_testing_lib.h"
-
-
-/**
- * Contains a contract terms object as a json_t.
- */
-#define TALER_TESTING_TRAIT_CONTRACT_TERMS "contract-terms"
-
-
-/**
- * Obtain contract terms from @a cmd.
- *
- * @param cmd command to extract the contract terms from.
- * @param index contract terms index number.
- * @param[out] contract_terms where to write the contract terms.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_contract_terms (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const json_t **contract_terms)
-{
- return cmd->traits (cmd->cls,
- (const void **) contract_terms,
- TALER_TESTING_TRAIT_CONTRACT_TERMS,
- index);
-}
-
-
-/**
- * Offer contract terms.
- *
- * @param index contract terms index number.
- * @param contract_terms contract terms to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_contract_terms (unsigned int index,
- const json_t *contract_terms)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_CONTRACT_TERMS,
- .ptr = (const void *) contract_terms
- };
- return ret;
-}
diff --git a/src/testing/testing_api_trait_denom_pub.c b/src/testing/testing_api_trait_denom_pub.c
deleted file mode 100644
index f866588db..000000000
--- a/src/testing/testing_api_trait_denom_pub.c
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_denom_pub.c
- * @brief denom pub traits.
- * @author Christian Grothoff
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_DENOM_PUB "denomination-public-key"
-
-
-/**
- * Obtain a denomination public key from a @a cmd.
- *
- * @param cmd command to extract trait from
- * @param index index number of the denom to obtain.
- * @param[out] denom_pub set to the offered denom pub.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_denom_pub (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct
- TALER_EXCHANGE_DenomPublicKey **denom_pub)
-{
- return cmd->traits (cmd->cls,
- (const void **) denom_pub,
- TALER_TESTING_TRAIT_DENOM_PUB,
- index);
-}
-
-
-/**
- * Make a trait for a denomination public key.
- *
- * @param index index number to associate to the offered denom pub.
- * @param denom_pub denom pub to offer with this trait.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_denom_pub (unsigned int index,
- const struct
- TALER_EXCHANGE_DenomPublicKey *denom_pub)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_DENOM_PUB,
- .ptr = (const void *) denom_pub
- };
-
- return ret;
-}
-
-
-/* end of testing_api_trait_denom_pub.c */
diff --git a/src/testing/testing_api_trait_denom_sig.c b/src/testing/testing_api_trait_denom_sig.c
deleted file mode 100644
index 07e89440e..000000000
--- a/src/testing/testing_api_trait_denom_sig.c
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_denom_sig.c
- * @brief offer denomination signatures as traits
- * @author Christian Grothoff
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_DENOM_SIG "denomination-signature"
-
-
-/**
- * Obtain a denomination signature from a @a cmd.
- *
- * @param cmd command to extract the denom sig from.
- * @param index index number associated with the denom sig.
- * @param[out] denom_sig set to the offered signature.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_denom_sig
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_DenominationSignature **denom_sig)
-{
- return cmd->traits (cmd->cls,
- (const void **) denom_sig,
- TALER_TESTING_TRAIT_DENOM_SIG,
- index);
-}
-
-
-/**
- * Offer denom sig.
- *
- * @param index index number to associate to the signature on
- * offer.
- * @param denom_sig the denom sig on offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_denom_sig
- (unsigned int index,
- const struct TALER_DenominationSignature *denom_sig)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_DENOM_SIG,
- .ptr = (const void *) denom_sig
- };
-
- return ret;
-}
-
-
-/* end of testing_api_trait_denom_sig.c */
diff --git a/src/testing/testing_api_trait_exchange_pub.c b/src/testing/testing_api_trait_exchange_pub.c
deleted file mode 100644
index 8c7027260..000000000
--- a/src/testing/testing_api_trait_exchange_pub.c
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_exchange_pub.c
- * @brief exchange pub traits.
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_EXCHANGE_PUB "exchange-public-key"
-
-
-/**
- * Obtain a exchange public key from a @a cmd.
- *
- * @param cmd command to extract trait from
- * @param index index number of the exchange to obtain.
- * @param[out] exchange_pub set to the offered exchange pub.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_exchange_pub
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_ExchangePublicKeyP **exchange_pub)
-{
- return cmd->traits (cmd->cls,
- (const void **) exchange_pub,
- TALER_TESTING_TRAIT_EXCHANGE_PUB,
- index);
-}
-
-
-/**
- * Make a trait for a exchange public key.
- *
- * @param index index number to associate to the offered exchange pub.
- * @param exchange_pub exchange pub to offer with this trait.
- *
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_exchange_pub
- (unsigned int index,
- const struct TALER_ExchangePublicKeyP *exchange_pub)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_EXCHANGE_PUB,
- .ptr = (const void *) exchange_pub
- };
-
- return ret;
-}
-
-
-/* end of testing_api_trait_exchange_pub.c */
diff --git a/src/testing/testing_api_trait_exchange_sig.c b/src/testing/testing_api_trait_exchange_sig.c
deleted file mode 100644
index 349454ae7..000000000
--- a/src/testing/testing_api_trait_exchange_sig.c
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_exchange_sig.c
- * @brief exchange pub traits.
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_EXCHANGE_SIG "exchange-online-signature"
-
-
-/**
- * Obtain a exchange signature (online sig) from a @a cmd.
- *
- * @param cmd command to extract trait from
- * @param index index number of the exchange to obtain.
- * @param[out] exchange_sig set to the offered exchange signature.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_exchange_sig
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_ExchangeSignatureP **exchange_sig)
-{
- return cmd->traits (cmd->cls,
- (const void **) exchange_sig,
- TALER_TESTING_TRAIT_EXCHANGE_SIG,
- index);
-}
-
-
-/**
- * Make a trait for a exchange signature.
- *
- * @param index index number to associate to the offered exchange pub.
- * @param exchange_sig exchange signature to offer with this trait.
- *
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_exchange_sig
- (unsigned int index,
- const struct TALER_ExchangeSignatureP *exchange_sig)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_EXCHANGE_SIG,
- .ptr = (const void *) exchange_sig
- };
-
- return ret;
-}
-
-
-/* end of testing_api_trait_exchange_sig.c */
diff --git a/src/testing/testing_api_trait_fresh_coin.c b/src/testing/testing_api_trait_fresh_coin.c
deleted file mode 100644
index e5f1c6827..000000000
--- a/src/testing/testing_api_trait_fresh_coin.c
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_fresh_coin.c
- * @brief traits to offer fresh conins (after "melt" operations)
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_FRESH_COINS "fresh-coins"
-
-/**
- * Get a array of fresh coins.
- *
- * @param cmd command to extract the fresh coin from.
- * @param index which array to pick if @a cmd has multiple
- * on offer.
- * @param[out] fresh_coins will point to the offered array.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_fresh_coins
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_TESTING_FreshCoinData **fresh_coins)
-{
- return cmd->traits (cmd->cls,
- (const void **) fresh_coins,
- TALER_TESTING_TRAIT_FRESH_COINS,
- index);
-}
-
-
-/**
- * Offer a _array_ of fresh coins.
- *
- * @param index which array of fresh coins to offer,
- * if there are multiple on offer. Typically passed as
- * zero.
- * @param fresh_coins the array of fresh coins to offer
- * @return the trait,
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_fresh_coins
- (unsigned int index,
- const struct TALER_TESTING_FreshCoinData *fresh_coins)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_FRESH_COINS,
- .ptr = (const void *) fresh_coins
- };
- return ret;
-}
-
-
-/* end of testing_api_trait_fresh_coin.c */
diff --git a/src/testing/testing_api_trait_json.c b/src/testing/testing_api_trait_json.c
deleted file mode 100644
index cbddad53e..000000000
--- a/src/testing/testing_api_trait_json.c
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_json.c
- * @brief offers JSON traits.
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_WIRE_DETAILS "wire-details"
-#define TALER_TESTING_TRAIT_EXCHANGE_KEYS "exchange-keys"
-
-/**
- * Obtain serialized exchange keys from @a cmd.
- *
- * @param cmd command to extract the keys from.
- * @param index index number associate with the keys on offer.
- * @param[out] keys where to write the serialized keys.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_exchange_keys
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const json_t **keys)
-{
- return cmd->traits (cmd->cls,
- (const void **) keys,
- TALER_TESTING_TRAIT_EXCHANGE_KEYS,
- index);
-}
-
-
-/**
- * Offer serialized keys in a trait.
- *
- * @param index index number associate with the serial keys
- * on offer.
- * @param keys serialized keys to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_exchange_keys
- (unsigned int index,
- const json_t *keys)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_EXCHANGE_KEYS,
- .ptr = (const json_t *) keys
- };
- return ret;
-}
-
-
-/**
- * Obtain wire details from @a cmd.
- *
- * @param cmd command to extract the wire details from.
- * @param index index number associate with the wire details
- * on offer; usually zero, as one command sticks to
- * one bank account.
- * @param[out] wire_details where to write the wire details.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_wire_details
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const json_t **wire_details)
-{
- return cmd->traits (cmd->cls,
- (const void **) wire_details,
- TALER_TESTING_TRAIT_WIRE_DETAILS,
- index);
-}
-
-
-/**
- * Offer wire details in a trait.
- *
- * @param index index number associate with the wire details
- * on offer; usually zero, as one command sticks to
- * one bank account.
- * @param wire_details wire details to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_wire_details
- (unsigned int index,
- const json_t *wire_details)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_WIRE_DETAILS,
- .ptr = (const json_t *) wire_details
- };
- return ret;
-}
-
-
-/* end of testing_api_trait_json.c */
diff --git a/src/testing/testing_api_trait_merchant_key.c b/src/testing/testing_api_trait_merchant_key.c
deleted file mode 100644
index 41b6b8883..000000000
--- a/src/testing/testing_api_trait_merchant_key.c
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_merchant_key.c
- * @brief traits to offer peer's (private) keys
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_MERCHANT_PRIV "merchant-priv"
-#define TALER_TESTING_TRAIT_MERCHANT_PUB "merchant-pub-pub"
-
-/**
- * Obtain a private key from a "peer". Used e.g. to obtain
- * a merchant's priv to sign a /track request.
- *
- * @param cmd command that is offering the key.
- * @param index (typically zero) which key to return if there
- * are multiple on offer.
- * @param[out] priv set to the key coming from @a cmd.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_merchant_priv
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_MerchantPrivateKeyP **priv)
-{
- return cmd->traits (cmd->cls,
- (const void **) priv,
- TALER_TESTING_TRAIT_MERCHANT_PRIV,
- index);
-}
-
-
-/**
- * Offer private key, typically done when CMD_1 needs it to
- * sign a request.
- *
- * @param index (typically zero) which key to return if there are
- * multiple on offer.
- * @param priv which object should be offered.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_merchant_priv (unsigned int index,
- const struct
- TALER_MerchantPrivateKeyP *priv)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_MERCHANT_PRIV,
- .ptr = (const void *) priv
- };
-
- return ret;
-}
-
-
-/**
- * Obtain a public key from a "peer". Used e.g. to obtain
- * a merchant's public key to use backend's API.
- *
- * @param cmd command offering the key.
- * @param index (typically zero) which key to return if there
- * are multiple on offer.
- * @param[out] pub set to the key coming from @a cmd.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_merchant_pub
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_MerchantPublicKeyP **pub)
-{
- return cmd->traits (cmd->cls,
- (const void **) pub,
- TALER_TESTING_TRAIT_MERCHANT_PUB,
- index);
-}
-
-
-/**
- * Offer public key.
- *
- * @param index (typically zero) which key to return if there
- * are multiple on offer. NOTE: if one key is offered, it
- * is mandatory to set this as zero.
- * @param pub which object should be returned.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_merchant_pub (unsigned int index,
- const struct
- TALER_MerchantPublicKeyP *pub)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_MERCHANT_PUB,
- .ptr = (const void *) pub
- };
-
- return ret;
-}
-
-
-/* end of testing_api_trait_merchant_key.c */
diff --git a/src/testing/testing_api_trait_number.c b/src/testing/testing_api_trait_number.c
deleted file mode 100644
index 50ce6d8c7..000000000
--- a/src/testing/testing_api_trait_number.c
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018-2020 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 testing/testing_api_trait_number.c
- * @brief traits to offer numbers
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_UINT "uint"
-#define TALER_TESTING_TRAIT_UINT64 "uint-64"
-#define TALER_TESTING_TRAIT_BANK_ROW "bank-transaction-row"
-
-
-/**
- * Obtain a number from @a cmd.
- *
- * @param cmd command to extract the number from.
- * @param index the number's index number.
- * @param[out] n set to the number coming from @a cmd.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_uint (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const unsigned int **n)
-{
- return cmd->traits (cmd->cls,
- (const void **) n,
- TALER_TESTING_TRAIT_UINT,
- index);
-}
-
-
-/**
- * Offer a number.
- *
- * @param index the number's index number.
- * @param n the number to offer.
- * @return #GNUNET_OK on success.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_uint (unsigned int index,
- const unsigned int *n)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_UINT,
- .ptr = (const void *) n
- };
- return ret;
-}
-
-
-/**
- * Obtain a "number" value from @a cmd, 64-bit version.
- *
- * @param cmd command to extract the number from.
- * @param index the number's index number.
- * @param[out] n set to the number coming from @a cmd.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_uint64 (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const uint64_t **n)
-{
- return cmd->traits (cmd->cls,
- (const void **) n,
- TALER_TESTING_TRAIT_UINT64,
- index);
-}
-
-
-/**
- * Offer number trait, 64-bit version.
- *
- * @param index the number's index number.
- * @param n number to offer.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_uint64 (unsigned int index,
- const uint64_t *n)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_UINT64,
- .ptr = (const void *) n
- };
- return ret;
-}
-
-
-/**
- * Obtain a bank transaction row value from @a cmd.
- *
- * @param cmd command to extract the number from.
- * @param[out] row set to the number coming from @a cmd.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_bank_row (const struct TALER_TESTING_Command *cmd,
- const uint64_t **row)
-{
- return cmd->traits (cmd->cls,
- (const void **) row,
- TALER_TESTING_TRAIT_BANK_ROW,
- 0);
-}
-
-
-/**
- * Offer bank transaction row trait.
- *
- * @param row number to offer.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_bank_row (const uint64_t *row)
-{
- struct TALER_TESTING_Trait ret = {
- .index = 0,
- .trait_name = TALER_TESTING_TRAIT_BANK_ROW,
- .ptr = (const void *) row
- };
- return ret;
-}
-
-
-/* end of testing_api_trait_number.c */
diff --git a/src/testing/testing_api_trait_process.c b/src/testing/testing_api_trait_process.c
deleted file mode 100644
index 3d2af31fd..000000000
--- a/src/testing/testing_api_trait_process.c
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_process.c
- * @brief trait offering process handles.
- * @author Christian Grothoff
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_PROCESS "process"
-
-
-/**
- * Obtain location where a command stores a pointer to a process.
- *
- * @param cmd command to extract trait from.
- * @param index which process to pick if @a cmd
- * has multiple on offer.
- * @param[out] processp set to the address of the pointer to the
- * process.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_process
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- struct GNUNET_OS_Process ***processp)
-{
- return cmd->traits (cmd->cls,
- (const void **) processp,
- TALER_TESTING_TRAIT_PROCESS,
- index);
-}
-
-
-/**
- * Offer location where a command stores a pointer to a process.
- *
- * @param index offered location index number, in case there are
- * multiple on offer.
- * @param processp process location to offer.
- *
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_process
- (unsigned int index,
- struct GNUNET_OS_Process **processp)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_PROCESS,
- .ptr = (const void *) processp
- };
-
- return ret;
-}
-
-
-/* end of testing_api_trait_process.c */
diff --git a/src/testing/testing_api_trait_reserve_history.c b/src/testing/testing_api_trait_reserve_history.c
deleted file mode 100644
index b458dbd66..000000000
--- a/src/testing/testing_api_trait_reserve_history.c
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018-2020 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 testing/testing_api_trait_reserve_history.c
- * @brief implements reserve hostry trait
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_RESERVE_HISTORY \
- "reserve-history-entry"
-
-
-/**
- * Obtain a reserve history entry from a @a cmd.
- *
- * @param cmd command to extract the reserve history from.
- * @param index reserve history's index number.
- * @param[out] rhp set to the reserve history.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_reserve_history (
- const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_EXCHANGE_ReserveHistory **rhp)
-{
- return cmd->traits (cmd->cls,
- (const void **) rhp,
- TALER_TESTING_TRAIT_RESERVE_HISTORY,
- index);
-}
-
-
-/**
- * Offer a reserve history entry.
- *
- * @param index reserve pubs's index number.
- * @param rh reserve history entry to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_reserve_history (
- unsigned int index,
- const struct TALER_EXCHANGE_ReserveHistory *rh)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_RESERVE_HISTORY,
- .ptr = (const void *) rh
- };
- return ret;
-}
-
-
-/* end of testing_api_trait_reserve_history.c */
diff --git a/src/testing/testing_api_trait_reserve_priv.c b/src/testing/testing_api_trait_reserve_priv.c
deleted file mode 100644
index f4a4ef500..000000000
--- a/src/testing/testing_api_trait_reserve_priv.c
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_reserve_priv.c
- * @brief implements reserve private key trait
- * @author Christian Grothoff
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_RESERVE_PRIVATE_KEY \
- "reserve-private-key"
-
-/**
- * Obtain a reserve private key from a @a cmd.
- *
- * @param cmd command to extract the reserve priv from.
- * @param index reserve priv's index number.
- * @param[out] reserve_priv set to the reserve priv.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_reserve_priv
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_ReservePrivateKeyP **reserve_priv)
-{
- return cmd->traits (cmd->cls,
- (const void **) reserve_priv,
- TALER_TESTING_TRAIT_RESERVE_PRIVATE_KEY,
- index);
-}
-
-
-/**
- * Offer a reserve private key.
- *
- * @param index reserve priv's index number.
- * @param reserve_priv reserve private key to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_reserve_priv
- (unsigned int index,
- const struct TALER_ReservePrivateKeyP *reserve_priv)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_RESERVE_PRIVATE_KEY,
- .ptr = (const void *) reserve_priv
- };
- return ret;
-}
-
-
-/* end of testing_api_trait_reserve_priv.c */
diff --git a/src/testing/testing_api_trait_reserve_pub.c b/src/testing/testing_api_trait_reserve_pub.c
deleted file mode 100644
index 743a10e96..000000000
--- a/src/testing/testing_api_trait_reserve_pub.c
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018-2020 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 testing/testing_api_trait_reserve_pub.c
- * @brief implements reserve public key trait
- * @author Christian Grothoff
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_RESERVE_PUBLIC_KEY \
- "reserve-public-key"
-
-/**
- * Obtain a reserve public key from a @a cmd.
- *
- * @param cmd command to extract the reserve pub from.
- * @param index reserve pub's index number.
- * @param[out] reserve_pub set to the reserve pub.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_reserve_pub
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_ReservePublicKeyP **reserve_pub)
-{
- if (NULL == cmd->traits)
- return GNUNET_SYSERR;
- return cmd->traits (cmd->cls,
- (const void **) reserve_pub,
- TALER_TESTING_TRAIT_RESERVE_PUBLIC_KEY,
- index);
-}
-
-
-/**
- * Offer a reserve public key.
- *
- * @param index reserve pub's index number.
- * @param reserve_pub reserve public key to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_reserve_pub
- (unsigned int index,
- const struct TALER_ReservePublicKeyP *reserve_pub)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_RESERVE_PUBLIC_KEY,
- .ptr = (const void *) reserve_pub
- };
- return ret;
-}
-
-
-/* end of testing_api_trait_reserve_pub.c */
diff --git a/src/testing/testing_api_trait_string.c b/src/testing/testing_api_trait_string.c
deleted file mode 100644
index 381760113..000000000
--- a/src/testing/testing_api_trait_string.c
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018-2020 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 testing/testing_api_trait_string.c
- * @brief offers strings traits.
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-/**
- * Some string. Avoid, use something more precise!
- */
-#define TALER_TESTING_TRAIT_STRING "string"
-
-/**
- * An HTTP-URL.
- */
-#define TALER_TESTING_TRAIT_URL "url"
-
-/**
- * A PAYTO-URL.
- */
-#define TALER_TESTING_TRAIT_PAYTO "payto"
-
-/**
- * String identifying an order.
- */
-#define TALER_TESTING_TRAIT_ORDER_ID "order-id"
-
-
-/**
- * Obtain a string from @a cmd.
- *
- * @param cmd command to extract the subject from.
- * @param index index number associated with the transfer
- * subject to offer.
- * @param[out] s where to write the offered
- * string
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_string (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const char **s)
-{
- return cmd->traits (cmd->cls,
- (const void **) s,
- TALER_TESTING_TRAIT_STRING,
- index);
-}
-
-
-/**
- * Offer string.
- *
- * @param index index number associated with the transfer
- * subject being offered.
- * @param s transfer subject to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_string (unsigned int index,
- const char *s)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_STRING,
- .ptr = (const void *) s
- };
- return ret;
-}
-
-
-/**
- * Obtain a HTTP url from @a cmd.
- *
- * @param cmd command to extract the url from.
- * @param index which url is to be picked, in case
- * multiple are offered.
- * @param[out] url where to write the url.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_url (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const char **url)
-{
- return cmd->traits (cmd->cls,
- (const void **) url,
- TALER_TESTING_TRAIT_URL,
- index);
-}
-
-
-/**
- * Offer HTTP url in a trait.
- *
- * @param index which url is to be picked,
- * in case multiple are offered.
- * @param url the url to offer.
- *
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_url (unsigned int index,
- const char *url)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_URL,
- .ptr = (const void *) url
- };
-
- GNUNET_assert (0 != strncasecmp (url,
- "payto://",
- strlen ("payto://")));
-
- return ret;
-}
-
-
-/**
- * Obtain a order id from @a cmd.
- *
- * @param cmd command to extract the order id from.
- * @param index which order id is to be picked, in case
- * multiple are offered.
- * @param[out] order_id where to write the order id.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_order_id (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const char **order_id)
-{
- return cmd->traits (cmd->cls,
- (const void **) order_id,
- TALER_TESTING_TRAIT_ORDER_ID,
- index);
-}
-
-
-/**
- * Offer order id in a trait.
- *
- * @param index which order id is to be offered,
- * in case multiple are offered.
- * @param order_id the order id to offer.
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_order_id (unsigned int index,
- const char *order_id)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_ORDER_ID,
- .ptr = (const void *) order_id
- };
- return ret;
-}
-
-
-/**
- * Obtain a PAYTO-url from @a cmd.
- *
- * @param cmd command to extract the url from.
- * @param pt which url is to be picked, in case
- * multiple are offered.
- * @param[out] url where to write the url.
- * @return #GNUNET_OK on success.
- */
-int
-TALER_TESTING_get_trait_payto (const struct TALER_TESTING_Command *cmd,
- enum TALER_TESTING_PaytoType pt,
- const char **url)
-{
- return cmd->traits (cmd->cls,
- (const void **) url,
- TALER_TESTING_TRAIT_PAYTO,
- (unsigned int) pt);
-}
-
-
-/**
- * Offer a "payto" URL reference.
- *
- * @param pt which reference is to be offered,
- * in case multiple are offered.
- * @param payto_uri the payto URI
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_payto (enum TALER_TESTING_PaytoType pt,
- const char *payto_uri)
-{
- struct TALER_TESTING_Trait ret = {
- .index = (unsigned int) pt,
- .trait_name = TALER_TESTING_TRAIT_PAYTO,
- .ptr = (const void *) payto_uri,
- };
-
- GNUNET_assert (0 == strncasecmp (payto_uri,
- "payto://",
- strlen ("payto://")));
- return ret;
-}
-
-
-/* end of testing_api_trait_string.c */
diff --git a/src/testing/testing_api_trait_time.c b/src/testing/testing_api_trait_time.c
deleted file mode 100644
index c77489bfa..000000000
--- a/src/testing/testing_api_trait_time.c
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_time.c
- * @brief traits to offer time stamps.
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_TIME_ABS "time-abs"
-
-/**
- * Obtain a absolute time from @a cmd.
- *
- * @param cmd command to extract trait from
- * @param index which time stamp to pick if
- * @a cmd has multiple on offer.
- * @param[out] time set to the wanted WTID.
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_get_trait_absolute_time
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct GNUNET_TIME_Absolute **time)
-{
- return cmd->traits (cmd->cls,
- (const void **) time,
- TALER_TESTING_TRAIT_TIME_ABS,
- index);
-}
-
-
-/**
- * Offer a absolute time.
- *
- * @param index associate the object with this index
- * @param time which object should be returned
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_absolute_time
- (unsigned int index,
- const struct GNUNET_TIME_Absolute *time)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_TIME_ABS,
- .ptr = (const void *) time
- };
- return ret;
-}
-
-
-/* end of testing_api_trait_time.c */
diff --git a/src/testing/testing_api_trait_wtid.c b/src/testing/testing_api_trait_wtid.c
deleted file mode 100644
index 5c7e7060e..000000000
--- a/src/testing/testing_api_trait_wtid.c
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2018 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 testing/testing_api_trait_number.c
- * @brief traits to offer numbers
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_signatures.h"
-#include "taler_testing_lib.h"
-
-#define TALER_TESTING_TRAIT_WTID "wtid"
-
-/**
- * Obtain a WTID value from @a cmd.
- *
- * @param cmd command to extract trait from
- * @param index which WTID to pick if @a cmd has multiple on
- * offer
- * @param[out] wtid set to the wanted WTID.
- * @return #GNUNET_OK on success
- */
-int
-TALER_TESTING_get_trait_wtid
- (const struct TALER_TESTING_Command *cmd,
- unsigned int index,
- const struct TALER_WireTransferIdentifierRawP **wtid)
-{
- return cmd->traits (cmd->cls,
- (const void **) wtid,
- TALER_TESTING_TRAIT_WTID,
- index);
-}
-
-
-/**
- * Offer a WTID.
- *
- * @param index associate the object with this index
- * @param wtid which object should be returned
- * @return the trait.
- */
-struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_wtid
- (unsigned int index,
- const struct TALER_WireTransferIdentifierRawP *wtid)
-{
- struct TALER_TESTING_Trait ret = {
- .index = index,
- .trait_name = TALER_TESTING_TRAIT_WTID,
- .ptr = (const void *) wtid
- };
- return ret;
-}
-
-
-/* end of testing_api_trait_number.c */
diff --git a/src/testing/testing_api_traits.c b/src/testing/testing_api_traits.c
index 6d623af7a..799ae6718 100644
--- a/src/testing/testing_api_traits.c
+++ b/src/testing/testing_api_traits.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018 Taler Systems SA
+ Copyright (C) 2018, 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
@@ -29,6 +29,11 @@
#include "taler_testing_lib.h"
+TALER_TESTING_SIMPLE_TRAITS (TALER_TESTING_MAKE_IMPL_SIMPLE_TRAIT)
+
+TALER_TESTING_INDEXED_TRAITS (TALER_TESTING_MAKE_IMPL_INDEXED_TRAIT)
+
+
/**
* End a trait array. Usually, commands offer several traits,
* and put them in arrays.
@@ -46,16 +51,7 @@ TALER_TESTING_trait_end ()
}
-/**
- * Pick the chosen trait from the traits array.
- *
- * @param traits the traits array.
- * @param ret where to store the result.
- * @param trait type of the trait to extract.
- * @param index index number of the object to extract.
- * @return #GNUNET_OK if no error occurred, #GNUNET_SYSERR otherwise.
- */
-int
+enum GNUNET_GenericReturnValue
TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
const void **ret,
const char *trait,
@@ -63,19 +59,75 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
{
for (unsigned int i = 0; NULL != traits[i].trait_name; i++)
{
- if ( (0 == strcmp (trait, traits[i].trait_name)) &&
+ if ( (0 == strcmp (trait,
+ traits[i].trait_name)) &&
(index == traits[i].index) )
{
*ret = (void *) traits[i].ptr;
return GNUNET_OK;
}
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Trait %s/%u not found.\n",
- trait, index);
-
+ trait,
+ index);
return GNUNET_SYSERR;
}
+const char *
+TALER_TESTING_get_exchange_url (struct TALER_TESTING_Interpreter *is)
+{
+ const char *exchange_url;
+ const struct TALER_TESTING_Command *exchange_cmd;
+
+ exchange_cmd
+ = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return NULL;
+ }
+ return exchange_url;
+}
+
+
+struct TALER_EXCHANGE_Keys *
+TALER_TESTING_get_keys (
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TALER_EXCHANGE_Keys *keys;
+ const struct TALER_TESTING_Command *exchange_cmd;
+
+ exchange_cmd
+ = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ if (NULL == exchange_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_keys (exchange_cmd,
+ &keys))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return NULL;
+ }
+ return keys;
+}
+
+
/* end of testing_api_traits.c */
diff --git a/src/testing/testing_api_twister_helpers.c b/src/testing/testing_api_twister_helpers.c
index 0406d7987..68fbf0082 100644
--- a/src/testing/testing_api_twister_helpers.c
+++ b/src/testing/testing_api_twister_helpers.c
@@ -44,13 +44,16 @@ TALER_TWISTER_prepare_twister (const char *config_filename)
cfg = GNUNET_CONFIGURATION_create ();
- if (GNUNET_OK != GNUNET_CONFIGURATION_load
- (cfg, config_filename))
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_load (cfg,
+ config_filename))
TWISTER_FAIL ();
- if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number
- (cfg, "twister",
- "HTTP_PORT", &port))
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ "twister",
+ "HTTP_PORT",
+ &port))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"twister",
@@ -61,8 +64,9 @@ TALER_TWISTER_prepare_twister (const char *config_filename)
GNUNET_CONFIGURATION_destroy (cfg);
- if (GNUNET_OK != GNUNET_NETWORK_test_port_free
- (IPPROTO_TCP, (uint16_t) port))
+ if (GNUNET_OK !=
+ GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
+ (uint16_t) port))
{
fprintf (stderr,
"Required port %llu not available, skipping.\n",
@@ -93,8 +97,7 @@ TALER_TWISTER_run_twister (const char *config_filename)
unsigned long code;
enum GNUNET_OS_ProcessStatusType type;
- proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-twister-service",
"taler-twister-service",
@@ -103,8 +106,7 @@ TALER_TWISTER_run_twister (const char *config_filename)
if (NULL == proc)
TWISTER_FAIL ();
- client_proc = GNUNET_OS_start_process (GNUNET_NO,
- GNUNET_OS_INHERIT_STD_ALL,
+ client_proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-twister",
"taler-twister",
@@ -114,18 +116,24 @@ TALER_TWISTER_run_twister (const char *config_filename)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Could not start the taler-twister client\n");
- GNUNET_OS_process_kill (proc, SIGTERM);
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (proc,
+ SIGTERM));
GNUNET_OS_process_wait (proc);
GNUNET_OS_process_destroy (proc);
TWISTER_FAIL ();
}
- if (GNUNET_SYSERR == GNUNET_OS_process_wait_status
- (client_proc, &type, &code))
+ if (GNUNET_SYSERR ==
+ GNUNET_OS_process_wait_status (client_proc,
+ &type,
+ &code))
{
GNUNET_OS_process_destroy (client_proc);
- GNUNET_OS_process_kill (proc, SIGTERM);
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (proc,
+ SIGTERM));
GNUNET_OS_process_wait (proc);
GNUNET_OS_process_destroy (proc);
TWISTER_FAIL ();
@@ -133,9 +141,12 @@ TALER_TWISTER_run_twister (const char *config_filename)
if ( (type == GNUNET_OS_PROCESS_EXITED) &&
(0 != code) )
{
- fprintf (stderr, "Failed to check twister works.\n");
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to check twister works.\n");
GNUNET_OS_process_destroy (client_proc);
- GNUNET_OS_process_kill (proc, SIGTERM);
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (proc,
+ SIGTERM));
GNUNET_OS_process_wait (proc);
GNUNET_OS_process_destroy (proc);
TWISTER_FAIL ();
@@ -143,10 +154,12 @@ TALER_TWISTER_run_twister (const char *config_filename)
if ( (type != GNUNET_OS_PROCESS_EXITED) ||
(0 != code) )
{
- fprintf (stderr, "Unexpected error running"
- " `taler-twister'!\n");
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected error running `taler-twister'!\n");
GNUNET_OS_process_destroy (client_proc);
- GNUNET_OS_process_kill (proc, SIGTERM);
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (proc,
+ SIGTERM));
GNUNET_OS_process_wait (proc);
GNUNET_OS_process_destroy (proc);
TWISTER_FAIL ();
diff --git a/src/testing/valgrind.h b/src/testing/valgrind.h
new file mode 100644
index 000000000..eaf1632e1
--- /dev/null
+++ b/src/testing/valgrind.h
@@ -0,0 +1,7165 @@
+/* -*- c -*-
+ ----------------------------------------------------------------
+
+ Notice that the following BSD-style license applies to this one
+ file (valgrind.h) only. The rest of Valgrind is licensed under the
+ terms of the GNU General Public License, version 2, unless
+ otherwise indicated. See the COPYING file in the source
+ distribution for details.
+
+ ----------------------------------------------------------------
+
+ This file is part of Valgrind, a dynamic binary instrumentation
+ framework.
+
+ Copyright (C) 2000-2017 Julian Seward. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. The origin of this software must not be misrepresented; you must
+ not claim that you wrote the original software. If you use this
+ software in a product, an acknowledgment in the product
+ documentation would be appreciated but is not required.
+
+ 3. Altered source versions must be plainly marked as such, and must
+ not be misrepresented as being the original software.
+
+ 4. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ ----------------------------------------------------------------
+
+ Notice that the above BSD-style license applies to this one file
+ (valgrind.h) only. The entire rest of Valgrind is licensed under
+ the terms of the GNU General Public License, version 2. See the
+ COPYING file in the source distribution for details.
+
+ ----------------------------------------------------------------
+*/
+
+
+/* This file is for inclusion into client (your!) code.
+
+ You can use these macros to manipulate and query Valgrind's
+ execution inside your own programs.
+
+ The resulting executables will still run without Valgrind, just a
+ little bit more slowly than they otherwise would, but otherwise
+ unchanged. When not running on valgrind, each client request
+ consumes very few (eg. 7) instructions, so the resulting performance
+ loss is negligible unless you plan to execute client requests
+ millions of times per second. Nevertheless, if that is still a
+ problem, you can compile with the NVALGRIND symbol defined (gcc
+ -DNVALGRIND) so that client requests are not even compiled in. */
+
+#ifndef __VALGRIND_H
+#define __VALGRIND_H
+
+
+/* ------------------------------------------------------------------ */
+/* VERSION NUMBER OF VALGRIND */
+/* ------------------------------------------------------------------ */
+
+/* Specify Valgrind's version number, so that user code can
+ conditionally compile based on our version number. Note that these
+ were introduced at version 3.6 and so do not exist in version 3.5
+ or earlier. The recommended way to use them to check for "version
+ X.Y or later" is (eg)
+
+#if defined(__VALGRIND_MAJOR__) && defined(__VALGRIND_MINOR__) \
+ && (__VALGRIND_MAJOR__ > 3 \
+ || (__VALGRIND_MAJOR__ == 3 && __VALGRIND_MINOR__ >= 6))
+*/
+#define __VALGRIND_MAJOR__ 3
+#define __VALGRIND_MINOR__ 19
+
+
+#include <stdarg.h>
+
+/* Nb: this file might be included in a file compiled with -ansi. So
+ we can't use C++ style "//" comments nor the "asm" keyword (instead
+ use "__asm__"). */
+
+/* Derive some tags indicating what the target platform is. Note
+ that in this file we're using the compiler's CPP symbols for
+ identifying architectures, which are different to the ones we use
+ within the rest of Valgrind. Note, __powerpc__ is active for both
+ 32 and 64-bit PPC, whereas __powerpc64__ is only active for the
+ latter (on Linux, that is).
+
+ Misc note: how to find out what's predefined in gcc by default:
+ gcc -Wp,-dM somefile.c
+*/
+#undef PLAT_x86_darwin
+#undef PLAT_amd64_darwin
+#undef PLAT_x86_freebsd
+#undef PLAT_amd64_freebsd
+#undef PLAT_x86_win32
+#undef PLAT_amd64_win64
+#undef PLAT_x86_linux
+#undef PLAT_amd64_linux
+#undef PLAT_ppc32_linux
+#undef PLAT_ppc64be_linux
+#undef PLAT_ppc64le_linux
+#undef PLAT_arm_linux
+#undef PLAT_arm64_linux
+#undef PLAT_s390x_linux
+#undef PLAT_mips32_linux
+#undef PLAT_mips64_linux
+#undef PLAT_nanomips_linux
+#undef PLAT_x86_solaris
+#undef PLAT_amd64_solaris
+
+
+#if defined(__APPLE__) && defined(__i386__)
+# define PLAT_x86_darwin 1
+#elif defined(__APPLE__) && defined(__x86_64__)
+# define PLAT_amd64_darwin 1
+#elif defined(__FreeBSD__) && defined(__i386__)
+# define PLAT_x86_freebsd 1
+#elif defined(__FreeBSD__) && defined(__amd64__)
+# define PLAT_amd64_freebsd 1
+#elif (defined(__MINGW32__) && defined(__i386__)) \
+ || defined(__CYGWIN32__) \
+ || (defined(_WIN32) && defined(_M_IX86))
+# define PLAT_x86_win32 1
+#elif (defined(__MINGW32__) && defined(__x86_64__)) \
+ || (defined(_WIN32) && defined(_M_X64))
+/* __MINGW32__ and _WIN32 are defined in 64 bit mode as well. */
+# define PLAT_amd64_win64 1
+#elif defined(__linux__) && defined(__i386__)
+# define PLAT_x86_linux 1
+#elif defined(__linux__) && defined(__x86_64__) && !defined(__ILP32__)
+# define PLAT_amd64_linux 1
+#elif defined(__linux__) && defined(__powerpc__) && !defined(__powerpc64__)
+# define PLAT_ppc32_linux 1
+#elif defined(__linux__) && defined(__powerpc__) && defined(__powerpc64__) && _CALL_ELF != 2
+/* Big Endian uses ELF version 1 */
+# define PLAT_ppc64be_linux 1
+#elif defined(__linux__) && defined(__powerpc__) && defined(__powerpc64__) && _CALL_ELF == 2
+/* Little Endian uses ELF version 2 */
+# define PLAT_ppc64le_linux 1
+#elif defined(__linux__) && defined(__arm__) && !defined(__aarch64__)
+# define PLAT_arm_linux 1
+#elif defined(__linux__) && defined(__aarch64__) && !defined(__arm__)
+# define PLAT_arm64_linux 1
+#elif defined(__linux__) && defined(__s390__) && defined(__s390x__)
+# define PLAT_s390x_linux 1
+#elif defined(__linux__) && defined(__mips__) && (__mips==64)
+# define PLAT_mips64_linux 1
+#elif defined(__linux__) && defined(__mips__) && (__mips==32)
+# define PLAT_mips32_linux 1
+#elif defined(__linux__) && defined(__nanomips__)
+# define PLAT_nanomips_linux 1
+#elif defined(__sun) && defined(__i386__)
+# define PLAT_x86_solaris 1
+#elif defined(__sun) && defined(__x86_64__)
+# define PLAT_amd64_solaris 1
+#else
+/* If we're not compiling for our target platform, don't generate
+ any inline asms. */
+# if !defined(NVALGRIND)
+# define NVALGRIND 1
+# endif
+#endif
+
+
+/* ------------------------------------------------------------------ */
+/* ARCHITECTURE SPECIFICS for SPECIAL INSTRUCTIONS. There is nothing */
+/* in here of use to end-users -- skip to the next section. */
+/* ------------------------------------------------------------------ */
+
+/*
+ * VALGRIND_DO_CLIENT_REQUEST(): a statement that invokes a Valgrind client
+ * request. Accepts both pointers and integers as arguments.
+ *
+ * VALGRIND_DO_CLIENT_REQUEST_STMT(): a statement that invokes a Valgrind
+ * client request that does not return a value.
+
+ * VALGRIND_DO_CLIENT_REQUEST_EXPR(): a C expression that invokes a Valgrind
+ * client request and whose value equals the client request result. Accepts
+ * both pointers and integers as arguments. Note that such calls are not
+ * necessarily pure functions -- they may have side effects.
+ */
+
+#define VALGRIND_DO_CLIENT_REQUEST(_zzq_rlval, _zzq_default, \
+ _zzq_request, _zzq_arg1, _zzq_arg2, \
+ _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ do { (_zzq_rlval) = VALGRIND_DO_CLIENT_REQUEST_EXPR((_zzq_default), \
+ (_zzq_request), (_zzq_arg1), (_zzq_arg2), \
+ (_zzq_arg3), (_zzq_arg4), (_zzq_arg5)); } while (0)
+
+#define VALGRIND_DO_CLIENT_REQUEST_STMT(_zzq_request, _zzq_arg1, \
+ _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ do { (void) VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \
+ (_zzq_request), (_zzq_arg1), (_zzq_arg2), \
+ (_zzq_arg3), (_zzq_arg4), (_zzq_arg5)); } while (0)
+
+#if defined(NVALGRIND)
+
+/* Define NVALGRIND to completely remove the Valgrind magic sequence
+ from the compiled code (analogous to NDEBUG's effects on
+ assert()) */
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ (_zzq_default)
+
+#else /* ! NVALGRIND */
+
+/* The following defines the magic code sequences which the JITter
+ spots and handles magically. Don't look too closely at them as
+ they will rot your brain.
+
+ The assembly code sequences for all architectures is in this one
+ file. This is because this file must be stand-alone, and we don't
+ want to have multiple files.
+
+ For VALGRIND_DO_CLIENT_REQUEST, we must ensure that the default
+ value gets put in the return slot, so that everything works when
+ this is executed not under Valgrind. Args are passed in a memory
+ block, and so there's no intrinsic limit to the number that could
+ be passed, but it's currently five.
+
+ The macro args are:
+ _zzq_rlval result lvalue
+ _zzq_default default value (result returned when running on real CPU)
+ _zzq_request request code
+ _zzq_arg1..5 request params
+
+ The other two macros are used to support function wrapping, and are
+ a lot simpler. VALGRIND_GET_NR_CONTEXT returns the value of the
+ guest's NRADDR pseudo-register and whatever other information is
+ needed to safely run the call original from the wrapper: on
+ ppc64-linux, the R2 value at the divert point is also needed. This
+ information is abstracted into a user-visible type, OrigFn.
+
+ VALGRIND_CALL_NOREDIR_* behaves the same as the following on the
+ guest, but guarantees that the branch instruction will not be
+ redirected: x86: call *%eax, amd64: call *%rax, ppc32/ppc64:
+ branch-and-link-to-r11. VALGRIND_CALL_NOREDIR is just text, not a
+ complete inline asm, since it needs to be combined with more magic
+ inline asm stuff to be useful.
+*/
+
+/* ----------------- x86-{linux,darwin,solaris} ---------------- */
+
+#if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) \
+ || (defined(PLAT_x86_win32) && defined(__GNUC__)) \
+ || defined(PLAT_x86_solaris) || defined(PLAT_x86_freebsd)
+
+typedef
+ struct {
+ unsigned int nraddr; /* where's the code? */
+ }
+ OrigFn;
+
+#define __SPECIAL_INSTRUCTION_PREAMBLE \
+ "roll $3, %%edi ; roll $13, %%edi\n\t" \
+ "roll $29, %%edi ; roll $19, %%edi\n\t"
+
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ __extension__ \
+ ({volatile unsigned int _zzq_args[6]; \
+ volatile unsigned int _zzq_result; \
+ _zzq_args[0] = (unsigned int)(_zzq_request); \
+ _zzq_args[1] = (unsigned int)(_zzq_arg1); \
+ _zzq_args[2] = (unsigned int)(_zzq_arg2); \
+ _zzq_args[3] = (unsigned int)(_zzq_arg3); \
+ _zzq_args[4] = (unsigned int)(_zzq_arg4); \
+ _zzq_args[5] = (unsigned int)(_zzq_arg5); \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %EDX = client_request ( %EAX ) */ \
+ "xchgl %%ebx,%%ebx" \
+ : "=d" (_zzq_result) \
+ : "a" (&_zzq_args[0]), "0" (_zzq_default) \
+ : "cc", "memory" \
+ ); \
+ _zzq_result; \
+ })
+
+#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \
+ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \
+ volatile unsigned int __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %EAX = guest_NRADDR */ \
+ "xchgl %%ecx,%%ecx" \
+ : "=a" (__addr) \
+ : \
+ : "cc", "memory" \
+ ); \
+ _zzq_orig->nraddr = __addr; \
+ }
+
+#define VALGRIND_CALL_NOREDIR_EAX \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* call-noredir *%EAX */ \
+ "xchgl %%edx,%%edx\n\t"
+
+#define VALGRIND_VEX_INJECT_IR() \
+ do { \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ "xchgl %%edi,%%edi\n\t" \
+ : : : "cc", "memory" \
+ ); \
+ } while (0)
+
+#endif /* PLAT_x86_linux || PLAT_x86_darwin || (PLAT_x86_win32 && __GNUC__)
+ || PLAT_x86_solaris */
+
+/* ------------------------- x86-Win32 ------------------------- */
+
+#if defined(PLAT_x86_win32) && !defined(__GNUC__)
+
+typedef
+ struct {
+ unsigned int nraddr; /* where's the code? */
+ }
+ OrigFn;
+
+#if defined(_MSC_VER)
+
+#define __SPECIAL_INSTRUCTION_PREAMBLE \
+ __asm rol edi, 3 __asm rol edi, 13 \
+ __asm rol edi, 29 __asm rol edi, 19
+
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ valgrind_do_client_request_expr((uintptr_t)(_zzq_default), \
+ (uintptr_t)(_zzq_request), (uintptr_t)(_zzq_arg1), \
+ (uintptr_t)(_zzq_arg2), (uintptr_t)(_zzq_arg3), \
+ (uintptr_t)(_zzq_arg4), (uintptr_t)(_zzq_arg5))
+
+static __inline uintptr_t
+valgrind_do_client_request_expr(uintptr_t _zzq_default, uintptr_t _zzq_request,
+ uintptr_t _zzq_arg1, uintptr_t _zzq_arg2,
+ uintptr_t _zzq_arg3, uintptr_t _zzq_arg4,
+ uintptr_t _zzq_arg5)
+{
+ volatile uintptr_t _zzq_args[6];
+ volatile unsigned int _zzq_result;
+ _zzq_args[0] = (uintptr_t)(_zzq_request);
+ _zzq_args[1] = (uintptr_t)(_zzq_arg1);
+ _zzq_args[2] = (uintptr_t)(_zzq_arg2);
+ _zzq_args[3] = (uintptr_t)(_zzq_arg3);
+ _zzq_args[4] = (uintptr_t)(_zzq_arg4);
+ _zzq_args[5] = (uintptr_t)(_zzq_arg5);
+ __asm { __asm lea eax, _zzq_args __asm mov edx, _zzq_default
+ __SPECIAL_INSTRUCTION_PREAMBLE
+ /* %EDX = client_request ( %EAX ) */
+ __asm xchg ebx,ebx
+ __asm mov _zzq_result, edx
+ }
+ return _zzq_result;
+}
+
+#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \
+ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \
+ volatile unsigned int __addr; \
+ __asm { __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %EAX = guest_NRADDR */ \
+ __asm xchg ecx,ecx \
+ __asm mov __addr, eax \
+ } \
+ _zzq_orig->nraddr = __addr; \
+ }
+
+#define VALGRIND_CALL_NOREDIR_EAX ERROR
+
+#define VALGRIND_VEX_INJECT_IR() \
+ do { \
+ __asm { __SPECIAL_INSTRUCTION_PREAMBLE \
+ __asm xchg edi,edi \
+ } \
+ } while (0)
+
+#else
+#error Unsupported compiler.
+#endif
+
+#endif /* PLAT_x86_win32 */
+
+/* ----------------- amd64-{linux,darwin,solaris} --------------- */
+
+#if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) \
+ || defined(PLAT_amd64_solaris) \
+ || defined(PLAT_amd64_freebsd) \
+ || (defined(PLAT_amd64_win64) && defined(__GNUC__))
+
+typedef
+ struct {
+ unsigned long int nraddr; /* where's the code? */
+ }
+ OrigFn;
+
+#define __SPECIAL_INSTRUCTION_PREAMBLE \
+ "rolq $3, %%rdi ; rolq $13, %%rdi\n\t" \
+ "rolq $61, %%rdi ; rolq $51, %%rdi\n\t"
+
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ __extension__ \
+ ({ volatile unsigned long int _zzq_args[6]; \
+ volatile unsigned long int _zzq_result; \
+ _zzq_args[0] = (unsigned long int)(_zzq_request); \
+ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \
+ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \
+ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \
+ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \
+ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %RDX = client_request ( %RAX ) */ \
+ "xchgq %%rbx,%%rbx" \
+ : "=d" (_zzq_result) \
+ : "a" (&_zzq_args[0]), "0" (_zzq_default) \
+ : "cc", "memory" \
+ ); \
+ _zzq_result; \
+ })
+
+#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \
+ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \
+ volatile unsigned long int __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %RAX = guest_NRADDR */ \
+ "xchgq %%rcx,%%rcx" \
+ : "=a" (__addr) \
+ : \
+ : "cc", "memory" \
+ ); \
+ _zzq_orig->nraddr = __addr; \
+ }
+
+#define VALGRIND_CALL_NOREDIR_RAX \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* call-noredir *%RAX */ \
+ "xchgq %%rdx,%%rdx\n\t"
+
+#define VALGRIND_VEX_INJECT_IR() \
+ do { \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ "xchgq %%rdi,%%rdi\n\t" \
+ : : : "cc", "memory" \
+ ); \
+ } while (0)
+
+#endif /* PLAT_amd64_linux || PLAT_amd64_darwin || PLAT_amd64_solaris */
+
+/* ------------------------- amd64-Win64 ------------------------- */
+
+#if defined(PLAT_amd64_win64) && !defined(__GNUC__)
+
+#error Unsupported compiler.
+
+#endif /* PLAT_amd64_win64 */
+
+/* ------------------------ ppc32-linux ------------------------ */
+
+#if defined(PLAT_ppc32_linux)
+
+typedef
+ struct {
+ unsigned int nraddr; /* where's the code? */
+ }
+ OrigFn;
+
+#define __SPECIAL_INSTRUCTION_PREAMBLE \
+ "rlwinm 0,0,3,0,31 ; rlwinm 0,0,13,0,31\n\t" \
+ "rlwinm 0,0,29,0,31 ; rlwinm 0,0,19,0,31\n\t"
+
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ \
+ __extension__ \
+ ({ unsigned int _zzq_args[6]; \
+ unsigned int _zzq_result; \
+ unsigned int* _zzq_ptr; \
+ _zzq_args[0] = (unsigned int)(_zzq_request); \
+ _zzq_args[1] = (unsigned int)(_zzq_arg1); \
+ _zzq_args[2] = (unsigned int)(_zzq_arg2); \
+ _zzq_args[3] = (unsigned int)(_zzq_arg3); \
+ _zzq_args[4] = (unsigned int)(_zzq_arg4); \
+ _zzq_args[5] = (unsigned int)(_zzq_arg5); \
+ _zzq_ptr = _zzq_args; \
+ __asm__ volatile("mr 3,%1\n\t" /*default*/ \
+ "mr 4,%2\n\t" /*ptr*/ \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %R3 = client_request ( %R4 ) */ \
+ "or 1,1,1\n\t" \
+ "mr %0,3" /*result*/ \
+ : "=b" (_zzq_result) \
+ : "b" (_zzq_default), "b" (_zzq_ptr) \
+ : "cc", "memory", "r3", "r4"); \
+ _zzq_result; \
+ })
+
+#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \
+ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \
+ unsigned int __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %R3 = guest_NRADDR */ \
+ "or 2,2,2\n\t" \
+ "mr %0,3" \
+ : "=b" (__addr) \
+ : \
+ : "cc", "memory", "r3" \
+ ); \
+ _zzq_orig->nraddr = __addr; \
+ }
+
+#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* branch-and-link-to-noredir *%R11 */ \
+ "or 3,3,3\n\t"
+
+#define VALGRIND_VEX_INJECT_IR() \
+ do { \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ "or 5,5,5\n\t" \
+ ); \
+ } while (0)
+
+#endif /* PLAT_ppc32_linux */
+
+/* ------------------------ ppc64-linux ------------------------ */
+
+#if defined(PLAT_ppc64be_linux)
+
+typedef
+ struct {
+ unsigned long int nraddr; /* where's the code? */
+ unsigned long int r2; /* what tocptr do we need? */
+ }
+ OrigFn;
+
+#define __SPECIAL_INSTRUCTION_PREAMBLE \
+ "rotldi 0,0,3 ; rotldi 0,0,13\n\t" \
+ "rotldi 0,0,61 ; rotldi 0,0,51\n\t"
+
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ \
+ __extension__ \
+ ({ unsigned long int _zzq_args[6]; \
+ unsigned long int _zzq_result; \
+ unsigned long int* _zzq_ptr; \
+ _zzq_args[0] = (unsigned long int)(_zzq_request); \
+ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \
+ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \
+ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \
+ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \
+ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \
+ _zzq_ptr = _zzq_args; \
+ __asm__ volatile("mr 3,%1\n\t" /*default*/ \
+ "mr 4,%2\n\t" /*ptr*/ \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %R3 = client_request ( %R4 ) */ \
+ "or 1,1,1\n\t" \
+ "mr %0,3" /*result*/ \
+ : "=b" (_zzq_result) \
+ : "b" (_zzq_default), "b" (_zzq_ptr) \
+ : "cc", "memory", "r3", "r4"); \
+ _zzq_result; \
+ })
+
+#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \
+ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \
+ unsigned long int __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %R3 = guest_NRADDR */ \
+ "or 2,2,2\n\t" \
+ "mr %0,3" \
+ : "=b" (__addr) \
+ : \
+ : "cc", "memory", "r3" \
+ ); \
+ _zzq_orig->nraddr = __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %R3 = guest_NRADDR_GPR2 */ \
+ "or 4,4,4\n\t" \
+ "mr %0,3" \
+ : "=b" (__addr) \
+ : \
+ : "cc", "memory", "r3" \
+ ); \
+ _zzq_orig->r2 = __addr; \
+ }
+
+#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* branch-and-link-to-noredir *%R11 */ \
+ "or 3,3,3\n\t"
+
+#define VALGRIND_VEX_INJECT_IR() \
+ do { \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ "or 5,5,5\n\t" \
+ ); \
+ } while (0)
+
+#endif /* PLAT_ppc64be_linux */
+
+#if defined(PLAT_ppc64le_linux)
+
+typedef
+ struct {
+ unsigned long int nraddr; /* where's the code? */
+ unsigned long int r2; /* what tocptr do we need? */
+ }
+ OrigFn;
+
+#define __SPECIAL_INSTRUCTION_PREAMBLE \
+ "rotldi 0,0,3 ; rotldi 0,0,13\n\t" \
+ "rotldi 0,0,61 ; rotldi 0,0,51\n\t"
+
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ \
+ __extension__ \
+ ({ unsigned long int _zzq_args[6]; \
+ unsigned long int _zzq_result; \
+ unsigned long int* _zzq_ptr; \
+ _zzq_args[0] = (unsigned long int)(_zzq_request); \
+ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \
+ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \
+ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \
+ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \
+ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \
+ _zzq_ptr = _zzq_args; \
+ __asm__ volatile("mr 3,%1\n\t" /*default*/ \
+ "mr 4,%2\n\t" /*ptr*/ \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %R3 = client_request ( %R4 ) */ \
+ "or 1,1,1\n\t" \
+ "mr %0,3" /*result*/ \
+ : "=b" (_zzq_result) \
+ : "b" (_zzq_default), "b" (_zzq_ptr) \
+ : "cc", "memory", "r3", "r4"); \
+ _zzq_result; \
+ })
+
+#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \
+ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \
+ unsigned long int __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %R3 = guest_NRADDR */ \
+ "or 2,2,2\n\t" \
+ "mr %0,3" \
+ : "=b" (__addr) \
+ : \
+ : "cc", "memory", "r3" \
+ ); \
+ _zzq_orig->nraddr = __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %R3 = guest_NRADDR_GPR2 */ \
+ "or 4,4,4\n\t" \
+ "mr %0,3" \
+ : "=b" (__addr) \
+ : \
+ : "cc", "memory", "r3" \
+ ); \
+ _zzq_orig->r2 = __addr; \
+ }
+
+#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* branch-and-link-to-noredir *%R12 */ \
+ "or 3,3,3\n\t"
+
+#define VALGRIND_VEX_INJECT_IR() \
+ do { \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ "or 5,5,5\n\t" \
+ ); \
+ } while (0)
+
+#endif /* PLAT_ppc64le_linux */
+
+/* ------------------------- arm-linux ------------------------- */
+
+#if defined(PLAT_arm_linux)
+
+typedef
+ struct {
+ unsigned int nraddr; /* where's the code? */
+ }
+ OrigFn;
+
+#define __SPECIAL_INSTRUCTION_PREAMBLE \
+ "mov r12, r12, ror #3 ; mov r12, r12, ror #13 \n\t" \
+ "mov r12, r12, ror #29 ; mov r12, r12, ror #19 \n\t"
+
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ \
+ __extension__ \
+ ({volatile unsigned int _zzq_args[6]; \
+ volatile unsigned int _zzq_result; \
+ _zzq_args[0] = (unsigned int)(_zzq_request); \
+ _zzq_args[1] = (unsigned int)(_zzq_arg1); \
+ _zzq_args[2] = (unsigned int)(_zzq_arg2); \
+ _zzq_args[3] = (unsigned int)(_zzq_arg3); \
+ _zzq_args[4] = (unsigned int)(_zzq_arg4); \
+ _zzq_args[5] = (unsigned int)(_zzq_arg5); \
+ __asm__ volatile("mov r3, %1\n\t" /*default*/ \
+ "mov r4, %2\n\t" /*ptr*/ \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* R3 = client_request ( R4 ) */ \
+ "orr r10, r10, r10\n\t" \
+ "mov %0, r3" /*result*/ \
+ : "=r" (_zzq_result) \
+ : "r" (_zzq_default), "r" (&_zzq_args[0]) \
+ : "cc","memory", "r3", "r4"); \
+ _zzq_result; \
+ })
+
+#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \
+ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \
+ unsigned int __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* R3 = guest_NRADDR */ \
+ "orr r11, r11, r11\n\t" \
+ "mov %0, r3" \
+ : "=r" (__addr) \
+ : \
+ : "cc", "memory", "r3" \
+ ); \
+ _zzq_orig->nraddr = __addr; \
+ }
+
+#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* branch-and-link-to-noredir *%R4 */ \
+ "orr r12, r12, r12\n\t"
+
+#define VALGRIND_VEX_INJECT_IR() \
+ do { \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ "orr r9, r9, r9\n\t" \
+ : : : "cc", "memory" \
+ ); \
+ } while (0)
+
+#endif /* PLAT_arm_linux */
+
+/* ------------------------ arm64-linux ------------------------- */
+
+#if defined(PLAT_arm64_linux)
+
+typedef
+ struct {
+ unsigned long int nraddr; /* where's the code? */
+ }
+ OrigFn;
+
+#define __SPECIAL_INSTRUCTION_PREAMBLE \
+ "ror x12, x12, #3 ; ror x12, x12, #13 \n\t" \
+ "ror x12, x12, #51 ; ror x12, x12, #61 \n\t"
+
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ \
+ __extension__ \
+ ({volatile unsigned long int _zzq_args[6]; \
+ volatile unsigned long int _zzq_result; \
+ _zzq_args[0] = (unsigned long int)(_zzq_request); \
+ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \
+ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \
+ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \
+ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \
+ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \
+ __asm__ volatile("mov x3, %1\n\t" /*default*/ \
+ "mov x4, %2\n\t" /*ptr*/ \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* X3 = client_request ( X4 ) */ \
+ "orr x10, x10, x10\n\t" \
+ "mov %0, x3" /*result*/ \
+ : "=r" (_zzq_result) \
+ : "r" ((unsigned long int)(_zzq_default)), \
+ "r" (&_zzq_args[0]) \
+ : "cc","memory", "x3", "x4"); \
+ _zzq_result; \
+ })
+
+#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \
+ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \
+ unsigned long int __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* X3 = guest_NRADDR */ \
+ "orr x11, x11, x11\n\t" \
+ "mov %0, x3" \
+ : "=r" (__addr) \
+ : \
+ : "cc", "memory", "x3" \
+ ); \
+ _zzq_orig->nraddr = __addr; \
+ }
+
+#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* branch-and-link-to-noredir X8 */ \
+ "orr x12, x12, x12\n\t"
+
+#define VALGRIND_VEX_INJECT_IR() \
+ do { \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ "orr x9, x9, x9\n\t" \
+ : : : "cc", "memory" \
+ ); \
+ } while (0)
+
+#endif /* PLAT_arm64_linux */
+
+/* ------------------------ s390x-linux ------------------------ */
+
+#if defined(PLAT_s390x_linux)
+
+typedef
+ struct {
+ unsigned long int nraddr; /* where's the code? */
+ }
+ OrigFn;
+
+/* __SPECIAL_INSTRUCTION_PREAMBLE will be used to identify Valgrind specific
+ * code. This detection is implemented in platform specific toIR.c
+ * (e.g. VEX/priv/guest_s390_decoder.c).
+ */
+#define __SPECIAL_INSTRUCTION_PREAMBLE \
+ "lr 15,15\n\t" \
+ "lr 1,1\n\t" \
+ "lr 2,2\n\t" \
+ "lr 3,3\n\t"
+
+#define __CLIENT_REQUEST_CODE "lr 2,2\n\t"
+#define __GET_NR_CONTEXT_CODE "lr 3,3\n\t"
+#define __CALL_NO_REDIR_CODE "lr 4,4\n\t"
+#define __VEX_INJECT_IR_CODE "lr 5,5\n\t"
+
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ __extension__ \
+ ({volatile unsigned long int _zzq_args[6]; \
+ volatile unsigned long int _zzq_result; \
+ _zzq_args[0] = (unsigned long int)(_zzq_request); \
+ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \
+ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \
+ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \
+ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \
+ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \
+ __asm__ volatile(/* r2 = args */ \
+ "lgr 2,%1\n\t" \
+ /* r3 = default */ \
+ "lgr 3,%2\n\t" \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ __CLIENT_REQUEST_CODE \
+ /* results = r3 */ \
+ "lgr %0, 3\n\t" \
+ : "=d" (_zzq_result) \
+ : "a" (&_zzq_args[0]), \
+ "0" ((unsigned long int)_zzq_default) \
+ : "cc", "2", "3", "memory" \
+ ); \
+ _zzq_result; \
+ })
+
+#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \
+ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \
+ volatile unsigned long int __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ __GET_NR_CONTEXT_CODE \
+ "lgr %0, 3\n\t" \
+ : "=a" (__addr) \
+ : \
+ : "cc", "3", "memory" \
+ ); \
+ _zzq_orig->nraddr = __addr; \
+ }
+
+#define VALGRIND_CALL_NOREDIR_R1 \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ __CALL_NO_REDIR_CODE
+
+#define VALGRIND_VEX_INJECT_IR() \
+ do { \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ __VEX_INJECT_IR_CODE); \
+ } while (0)
+
+#endif /* PLAT_s390x_linux */
+
+/* ------------------------- mips32-linux ---------------- */
+
+#if defined(PLAT_mips32_linux)
+
+typedef
+ struct {
+ unsigned int nraddr; /* where's the code? */
+ }
+ OrigFn;
+
+/* .word 0x342
+ * .word 0x742
+ * .word 0xC2
+ * .word 0x4C2*/
+#define __SPECIAL_INSTRUCTION_PREAMBLE \
+ "srl $0, $0, 13\n\t" \
+ "srl $0, $0, 29\n\t" \
+ "srl $0, $0, 3\n\t" \
+ "srl $0, $0, 19\n\t"
+
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ __extension__ \
+ ({ volatile unsigned int _zzq_args[6]; \
+ volatile unsigned int _zzq_result; \
+ _zzq_args[0] = (unsigned int)(_zzq_request); \
+ _zzq_args[1] = (unsigned int)(_zzq_arg1); \
+ _zzq_args[2] = (unsigned int)(_zzq_arg2); \
+ _zzq_args[3] = (unsigned int)(_zzq_arg3); \
+ _zzq_args[4] = (unsigned int)(_zzq_arg4); \
+ _zzq_args[5] = (unsigned int)(_zzq_arg5); \
+ __asm__ volatile("move $11, %1\n\t" /*default*/ \
+ "move $12, %2\n\t" /*ptr*/ \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* T3 = client_request ( T4 ) */ \
+ "or $13, $13, $13\n\t" \
+ "move %0, $11\n\t" /*result*/ \
+ : "=r" (_zzq_result) \
+ : "r" (_zzq_default), "r" (&_zzq_args[0]) \
+ : "$11", "$12", "memory"); \
+ _zzq_result; \
+ })
+
+#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \
+ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \
+ volatile unsigned int __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* %t9 = guest_NRADDR */ \
+ "or $14, $14, $14\n\t" \
+ "move %0, $11" /*result*/ \
+ : "=r" (__addr) \
+ : \
+ : "$11" \
+ ); \
+ _zzq_orig->nraddr = __addr; \
+ }
+
+#define VALGRIND_CALL_NOREDIR_T9 \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* call-noredir *%t9 */ \
+ "or $15, $15, $15\n\t"
+
+#define VALGRIND_VEX_INJECT_IR() \
+ do { \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ "or $11, $11, $11\n\t" \
+ ); \
+ } while (0)
+
+
+#endif /* PLAT_mips32_linux */
+
+/* ------------------------- mips64-linux ---------------- */
+
+#if defined(PLAT_mips64_linux)
+
+typedef
+ struct {
+ unsigned long nraddr; /* where's the code? */
+ }
+ OrigFn;
+
+/* dsll $0,$0, 3
+ * dsll $0,$0, 13
+ * dsll $0,$0, 29
+ * dsll $0,$0, 19*/
+#define __SPECIAL_INSTRUCTION_PREAMBLE \
+ "dsll $0,$0, 3 ; dsll $0,$0,13\n\t" \
+ "dsll $0,$0,29 ; dsll $0,$0,19\n\t"
+
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ __extension__ \
+ ({ volatile unsigned long int _zzq_args[6]; \
+ volatile unsigned long int _zzq_result; \
+ _zzq_args[0] = (unsigned long int)(_zzq_request); \
+ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \
+ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \
+ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \
+ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \
+ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \
+ __asm__ volatile("move $11, %1\n\t" /*default*/ \
+ "move $12, %2\n\t" /*ptr*/ \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* $11 = client_request ( $12 ) */ \
+ "or $13, $13, $13\n\t" \
+ "move %0, $11\n\t" /*result*/ \
+ : "=r" (_zzq_result) \
+ : "r" (_zzq_default), "r" (&_zzq_args[0]) \
+ : "$11", "$12", "memory"); \
+ _zzq_result; \
+ })
+
+#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \
+ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \
+ volatile unsigned long int __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* $11 = guest_NRADDR */ \
+ "or $14, $14, $14\n\t" \
+ "move %0, $11" /*result*/ \
+ : "=r" (__addr) \
+ : \
+ : "$11"); \
+ _zzq_orig->nraddr = __addr; \
+ }
+
+#define VALGRIND_CALL_NOREDIR_T9 \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* call-noredir $25 */ \
+ "or $15, $15, $15\n\t"
+
+#define VALGRIND_VEX_INJECT_IR() \
+ do { \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ "or $11, $11, $11\n\t" \
+ ); \
+ } while (0)
+
+#endif /* PLAT_mips64_linux */
+
+#if defined(PLAT_nanomips_linux)
+
+typedef
+ struct {
+ unsigned int nraddr; /* where's the code? */
+ }
+ OrigFn;
+/*
+ 8000 c04d srl zero, zero, 13
+ 8000 c05d srl zero, zero, 29
+ 8000 c043 srl zero, zero, 3
+ 8000 c053 srl zero, zero, 19
+*/
+
+#define __SPECIAL_INSTRUCTION_PREAMBLE "srl[32] $zero, $zero, 13 \n\t" \
+ "srl[32] $zero, $zero, 29 \n\t" \
+ "srl[32] $zero, $zero, 3 \n\t" \
+ "srl[32] $zero, $zero, 19 \n\t"
+
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ __extension__ \
+ ({ volatile unsigned int _zzq_args[6]; \
+ volatile unsigned int _zzq_result; \
+ _zzq_args[0] = (unsigned int)(_zzq_request); \
+ _zzq_args[1] = (unsigned int)(_zzq_arg1); \
+ _zzq_args[2] = (unsigned int)(_zzq_arg2); \
+ _zzq_args[3] = (unsigned int)(_zzq_arg3); \
+ _zzq_args[4] = (unsigned int)(_zzq_arg4); \
+ _zzq_args[5] = (unsigned int)(_zzq_arg5); \
+ __asm__ volatile("move $a7, %1\n\t" /* default */ \
+ "move $t0, %2\n\t" /* ptr */ \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* $a7 = client_request( $t0 ) */ \
+ "or[32] $t0, $t0, $t0\n\t" \
+ "move %0, $a7\n\t" /* result */ \
+ : "=r" (_zzq_result) \
+ : "r" (_zzq_default), "r" (&_zzq_args[0]) \
+ : "$a7", "$t0", "memory"); \
+ _zzq_result; \
+ })
+
+#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \
+ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \
+ volatile unsigned long int __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* $a7 = guest_NRADDR */ \
+ "or[32] $t1, $t1, $t1\n\t" \
+ "move %0, $a7" /*result*/ \
+ : "=r" (__addr) \
+ : \
+ : "$a7"); \
+ _zzq_orig->nraddr = __addr; \
+ }
+
+#define VALGRIND_CALL_NOREDIR_T9 \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* call-noredir $25 */ \
+ "or[32] $t2, $t2, $t2\n\t"
+
+#define VALGRIND_VEX_INJECT_IR() \
+ do { \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ "or[32] $t3, $t3, $t3\n\t" \
+ ); \
+ } while (0)
+
+#endif
+/* Insert assembly code for other platforms here... */
+
+#endif /* NVALGRIND */
+
+
+/* ------------------------------------------------------------------ */
+/* PLATFORM SPECIFICS for FUNCTION WRAPPING. This is all very */
+/* ugly. It's the least-worst tradeoff I can think of. */
+/* ------------------------------------------------------------------ */
+
+/* This section defines magic (a.k.a appalling-hack) macros for doing
+ guaranteed-no-redirection macros, so as to get from function
+ wrappers to the functions they are wrapping. The whole point is to
+ construct standard call sequences, but to do the call itself with a
+ special no-redirect call pseudo-instruction that the JIT
+ understands and handles specially. This section is long and
+ repetitious, and I can't see a way to make it shorter.
+
+ The naming scheme is as follows:
+
+ CALL_FN_{W,v}_{v,W,WW,WWW,WWWW,5W,6W,7W,etc}
+
+ 'W' stands for "word" and 'v' for "void". Hence there are
+ different macros for calling arity 0, 1, 2, 3, 4, etc, functions,
+ and for each, the possibility of returning a word-typed result, or
+ no result.
+*/
+
+/* Use these to write the name of your wrapper. NOTE: duplicates
+ VG_WRAP_FUNCTION_Z{U,Z} in pub_tool_redir.h. NOTE also: inserts
+ the default behaviour equivalance class tag "0000" into the name.
+ See pub_tool_redir.h for details -- normally you don't need to
+ think about this, though. */
+
+/* Use an extra level of macroisation so as to ensure the soname/fnname
+ args are fully macro-expanded before pasting them together. */
+#define VG_CONCAT4(_aa,_bb,_cc,_dd) _aa##_bb##_cc##_dd
+
+#define I_WRAP_SONAME_FNNAME_ZU(soname,fnname) \
+ VG_CONCAT4(_vgw00000ZU_,soname,_,fnname)
+
+#define I_WRAP_SONAME_FNNAME_ZZ(soname,fnname) \
+ VG_CONCAT4(_vgw00000ZZ_,soname,_,fnname)
+
+/* Use this macro from within a wrapper function to collect the
+ context (address and possibly other info) of the original function.
+ Once you have that you can then use it in one of the CALL_FN_
+ macros. The type of the argument _lval is OrigFn. */
+#define VALGRIND_GET_ORIG_FN(_lval) VALGRIND_GET_NR_CONTEXT(_lval)
+
+/* Also provide end-user facilities for function replacement, rather
+ than wrapping. A replacement function differs from a wrapper in
+ that it has no way to get hold of the original function being
+ called, and hence no way to call onwards to it. In a replacement
+ function, VALGRIND_GET_ORIG_FN always returns zero. */
+
+#define I_REPLACE_SONAME_FNNAME_ZU(soname,fnname) \
+ VG_CONCAT4(_vgr00000ZU_,soname,_,fnname)
+
+#define I_REPLACE_SONAME_FNNAME_ZZ(soname,fnname) \
+ VG_CONCAT4(_vgr00000ZZ_,soname,_,fnname)
+
+/* Derivatives of the main macros below, for calling functions
+ returning void. */
+
+#define CALL_FN_v_v(fnptr) \
+ do { volatile unsigned long _junk; \
+ CALL_FN_W_v(_junk,fnptr); } while (0)
+
+#define CALL_FN_v_W(fnptr, arg1) \
+ do { volatile unsigned long _junk; \
+ CALL_FN_W_W(_junk,fnptr,arg1); } while (0)
+
+#define CALL_FN_v_WW(fnptr, arg1,arg2) \
+ do { volatile unsigned long _junk; \
+ CALL_FN_W_WW(_junk,fnptr,arg1,arg2); } while (0)
+
+#define CALL_FN_v_WWW(fnptr, arg1,arg2,arg3) \
+ do { volatile unsigned long _junk; \
+ CALL_FN_W_WWW(_junk,fnptr,arg1,arg2,arg3); } while (0)
+
+#define CALL_FN_v_WWWW(fnptr, arg1,arg2,arg3,arg4) \
+ do { volatile unsigned long _junk; \
+ CALL_FN_W_WWWW(_junk,fnptr,arg1,arg2,arg3,arg4); } while (0)
+
+#define CALL_FN_v_5W(fnptr, arg1,arg2,arg3,arg4,arg5) \
+ do { volatile unsigned long _junk; \
+ CALL_FN_W_5W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5); } while (0)
+
+#define CALL_FN_v_6W(fnptr, arg1,arg2,arg3,arg4,arg5,arg6) \
+ do { volatile unsigned long _junk; \
+ CALL_FN_W_6W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5,arg6); } while (0)
+
+#define CALL_FN_v_7W(fnptr, arg1,arg2,arg3,arg4,arg5,arg6,arg7) \
+ do { volatile unsigned long _junk; \
+ CALL_FN_W_7W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5,arg6,arg7); } while (0)
+
+/* ----------------- x86-{linux,darwin,solaris} ---------------- */
+
+#if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) \
+ || defined(PLAT_x86_solaris) || defined(PLAT_x86_freebsd)
+
+/* These regs are trashed by the hidden call. No need to mention eax
+ as gcc can already see that, plus causes gcc to bomb. */
+#define __CALLER_SAVED_REGS /*"eax"*/ "ecx", "edx"
+
+/* Macros to save and align the stack before making a function
+ call and restore it afterwards as gcc may not keep the stack
+ pointer aligned if it doesn't realise calls are being made
+ to other functions. */
+
+#define VALGRIND_ALIGN_STACK \
+ "movl %%esp,%%edi\n\t" \
+ "andl $0xfffffff0,%%esp\n\t"
+#define VALGRIND_RESTORE_STACK \
+ "movl %%edi,%%esp\n\t"
+
+/* These CALL_FN_ macros assume that on x86-linux, sizeof(unsigned
+ long) == 4. */
+
+#define CALL_FN_W_v(lval, orig) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[1]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "movl (%%eax), %%eax\n\t" /* target->%eax */ \
+ VALGRIND_CALL_NOREDIR_EAX \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_W(lval, orig, arg1) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[2]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "subl $12, %%esp\n\t" \
+ "pushl 4(%%eax)\n\t" \
+ "movl (%%eax), %%eax\n\t" /* target->%eax */ \
+ VALGRIND_CALL_NOREDIR_EAX \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WW(lval, orig, arg1,arg2) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "subl $8, %%esp\n\t" \
+ "pushl 8(%%eax)\n\t" \
+ "pushl 4(%%eax)\n\t" \
+ "movl (%%eax), %%eax\n\t" /* target->%eax */ \
+ VALGRIND_CALL_NOREDIR_EAX \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[4]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "subl $4, %%esp\n\t" \
+ "pushl 12(%%eax)\n\t" \
+ "pushl 8(%%eax)\n\t" \
+ "pushl 4(%%eax)\n\t" \
+ "movl (%%eax), %%eax\n\t" /* target->%eax */ \
+ VALGRIND_CALL_NOREDIR_EAX \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[5]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "pushl 16(%%eax)\n\t" \
+ "pushl 12(%%eax)\n\t" \
+ "pushl 8(%%eax)\n\t" \
+ "pushl 4(%%eax)\n\t" \
+ "movl (%%eax), %%eax\n\t" /* target->%eax */ \
+ VALGRIND_CALL_NOREDIR_EAX \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[6]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "subl $12, %%esp\n\t" \
+ "pushl 20(%%eax)\n\t" \
+ "pushl 16(%%eax)\n\t" \
+ "pushl 12(%%eax)\n\t" \
+ "pushl 8(%%eax)\n\t" \
+ "pushl 4(%%eax)\n\t" \
+ "movl (%%eax), %%eax\n\t" /* target->%eax */ \
+ VALGRIND_CALL_NOREDIR_EAX \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[7]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "subl $8, %%esp\n\t" \
+ "pushl 24(%%eax)\n\t" \
+ "pushl 20(%%eax)\n\t" \
+ "pushl 16(%%eax)\n\t" \
+ "pushl 12(%%eax)\n\t" \
+ "pushl 8(%%eax)\n\t" \
+ "pushl 4(%%eax)\n\t" \
+ "movl (%%eax), %%eax\n\t" /* target->%eax */ \
+ VALGRIND_CALL_NOREDIR_EAX \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[8]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "subl $4, %%esp\n\t" \
+ "pushl 28(%%eax)\n\t" \
+ "pushl 24(%%eax)\n\t" \
+ "pushl 20(%%eax)\n\t" \
+ "pushl 16(%%eax)\n\t" \
+ "pushl 12(%%eax)\n\t" \
+ "pushl 8(%%eax)\n\t" \
+ "pushl 4(%%eax)\n\t" \
+ "movl (%%eax), %%eax\n\t" /* target->%eax */ \
+ VALGRIND_CALL_NOREDIR_EAX \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[9]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "pushl 32(%%eax)\n\t" \
+ "pushl 28(%%eax)\n\t" \
+ "pushl 24(%%eax)\n\t" \
+ "pushl 20(%%eax)\n\t" \
+ "pushl 16(%%eax)\n\t" \
+ "pushl 12(%%eax)\n\t" \
+ "pushl 8(%%eax)\n\t" \
+ "pushl 4(%%eax)\n\t" \
+ "movl (%%eax), %%eax\n\t" /* target->%eax */ \
+ VALGRIND_CALL_NOREDIR_EAX \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[10]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "subl $12, %%esp\n\t" \
+ "pushl 36(%%eax)\n\t" \
+ "pushl 32(%%eax)\n\t" \
+ "pushl 28(%%eax)\n\t" \
+ "pushl 24(%%eax)\n\t" \
+ "pushl 20(%%eax)\n\t" \
+ "pushl 16(%%eax)\n\t" \
+ "pushl 12(%%eax)\n\t" \
+ "pushl 8(%%eax)\n\t" \
+ "pushl 4(%%eax)\n\t" \
+ "movl (%%eax), %%eax\n\t" /* target->%eax */ \
+ VALGRIND_CALL_NOREDIR_EAX \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[11]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "subl $8, %%esp\n\t" \
+ "pushl 40(%%eax)\n\t" \
+ "pushl 36(%%eax)\n\t" \
+ "pushl 32(%%eax)\n\t" \
+ "pushl 28(%%eax)\n\t" \
+ "pushl 24(%%eax)\n\t" \
+ "pushl 20(%%eax)\n\t" \
+ "pushl 16(%%eax)\n\t" \
+ "pushl 12(%%eax)\n\t" \
+ "pushl 8(%%eax)\n\t" \
+ "pushl 4(%%eax)\n\t" \
+ "movl (%%eax), %%eax\n\t" /* target->%eax */ \
+ VALGRIND_CALL_NOREDIR_EAX \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \
+ arg6,arg7,arg8,arg9,arg10, \
+ arg11) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[12]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "subl $4, %%esp\n\t" \
+ "pushl 44(%%eax)\n\t" \
+ "pushl 40(%%eax)\n\t" \
+ "pushl 36(%%eax)\n\t" \
+ "pushl 32(%%eax)\n\t" \
+ "pushl 28(%%eax)\n\t" \
+ "pushl 24(%%eax)\n\t" \
+ "pushl 20(%%eax)\n\t" \
+ "pushl 16(%%eax)\n\t" \
+ "pushl 12(%%eax)\n\t" \
+ "pushl 8(%%eax)\n\t" \
+ "pushl 4(%%eax)\n\t" \
+ "movl (%%eax), %%eax\n\t" /* target->%eax */ \
+ VALGRIND_CALL_NOREDIR_EAX \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \
+ arg6,arg7,arg8,arg9,arg10, \
+ arg11,arg12) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[13]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ _argvec[12] = (unsigned long)(arg12); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "pushl 48(%%eax)\n\t" \
+ "pushl 44(%%eax)\n\t" \
+ "pushl 40(%%eax)\n\t" \
+ "pushl 36(%%eax)\n\t" \
+ "pushl 32(%%eax)\n\t" \
+ "pushl 28(%%eax)\n\t" \
+ "pushl 24(%%eax)\n\t" \
+ "pushl 20(%%eax)\n\t" \
+ "pushl 16(%%eax)\n\t" \
+ "pushl 12(%%eax)\n\t" \
+ "pushl 8(%%eax)\n\t" \
+ "pushl 4(%%eax)\n\t" \
+ "movl (%%eax), %%eax\n\t" /* target->%eax */ \
+ VALGRIND_CALL_NOREDIR_EAX \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#endif /* PLAT_x86_linux || PLAT_x86_darwin || PLAT_x86_solaris */
+
+/* ---------------- amd64-{linux,darwin,solaris} --------------- */
+
+#if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) \
+ || defined(PLAT_amd64_solaris) || defined(PLAT_amd64_freebsd)
+
+/* ARGREGS: rdi rsi rdx rcx r8 r9 (the rest on stack in R-to-L order) */
+
+/* These regs are trashed by the hidden call. */
+#define __CALLER_SAVED_REGS /*"rax",*/ "rcx", "rdx", "rsi", \
+ "rdi", "r8", "r9", "r10", "r11"
+
+/* This is all pretty complex. It's so as to make stack unwinding
+ work reliably. See bug 243270. The basic problem is the sub and
+ add of 128 of %rsp in all of the following macros. If gcc believes
+ the CFA is in %rsp, then unwinding may fail, because what's at the
+ CFA is not what gcc "expected" when it constructs the CFIs for the
+ places where the macros are instantiated.
+
+ But we can't just add a CFI annotation to increase the CFA offset
+ by 128, to match the sub of 128 from %rsp, because we don't know
+ whether gcc has chosen %rsp as the CFA at that point, or whether it
+ has chosen some other register (eg, %rbp). In the latter case,
+ adding a CFI annotation to change the CFA offset is simply wrong.
+
+ So the solution is to get hold of the CFA using
+ __builtin_dwarf_cfa(), put it in a known register, and add a
+ CFI annotation to say what the register is. We choose %rbp for
+ this (perhaps perversely), because:
+
+ (1) %rbp is already subject to unwinding. If a new register was
+ chosen then the unwinder would have to unwind it in all stack
+ traces, which is expensive, and
+
+ (2) %rbp is already subject to precise exception updates in the
+ JIT. If a new register was chosen, we'd have to have precise
+ exceptions for it too, which reduces performance of the
+ generated code.
+
+ However .. one extra complication. We can't just whack the result
+ of __builtin_dwarf_cfa() into %rbp and then add %rbp to the
+ list of trashed registers at the end of the inline assembly
+ fragments; gcc won't allow %rbp to appear in that list. Hence
+ instead we need to stash %rbp in %r15 for the duration of the asm,
+ and say that %r15 is trashed instead. gcc seems happy to go with
+ that.
+
+ Oh .. and this all needs to be conditionalised so that it is
+ unchanged from before this commit, when compiled with older gccs
+ that don't support __builtin_dwarf_cfa. Furthermore, since
+ this header file is freestanding, it has to be independent of
+ config.h, and so the following conditionalisation cannot depend on
+ configure time checks.
+
+ Although it's not clear from
+ 'defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM)',
+ this expression excludes Darwin.
+ .cfi directives in Darwin assembly appear to be completely
+ different and I haven't investigated how they work.
+
+ For even more entertainment value, note we have to use the
+ completely undocumented __builtin_dwarf_cfa(), which appears to
+ really compute the CFA, whereas __builtin_frame_address(0) claims
+ to but actually doesn't. See
+ https://bugs.kde.org/show_bug.cgi?id=243270#c47
+*/
+#if defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM)
+# define __FRAME_POINTER \
+ ,"r"(__builtin_dwarf_cfa())
+# define VALGRIND_CFI_PROLOGUE \
+ "movq %%rbp, %%r15\n\t" \
+ "movq %2, %%rbp\n\t" \
+ ".cfi_remember_state\n\t" \
+ ".cfi_def_cfa rbp, 0\n\t"
+# define VALGRIND_CFI_EPILOGUE \
+ "movq %%r15, %%rbp\n\t" \
+ ".cfi_restore_state\n\t"
+#else
+# define __FRAME_POINTER
+# define VALGRIND_CFI_PROLOGUE
+# define VALGRIND_CFI_EPILOGUE
+#endif
+
+/* Macros to save and align the stack before making a function
+ call and restore it afterwards as gcc may not keep the stack
+ pointer aligned if it doesn't realise calls are being made
+ to other functions. */
+
+#define VALGRIND_ALIGN_STACK \
+ "movq %%rsp,%%r14\n\t" \
+ "andq $0xfffffffffffffff0,%%rsp\n\t"
+#define VALGRIND_RESTORE_STACK \
+ "movq %%r14,%%rsp\n\t"
+
+/* These CALL_FN_ macros assume that on amd64-linux, sizeof(unsigned
+ long) == 8. */
+
+/* NB 9 Sept 07. There is a nasty kludge here in all these CALL_FN_
+ macros. In order not to trash the stack redzone, we need to drop
+ %rsp by 128 before the hidden call, and restore afterwards. The
+ nastyness is that it is only by luck that the stack still appears
+ to be unwindable during the hidden call - since then the behaviour
+ of any routine using this macro does not match what the CFI data
+ says. Sigh.
+
+ Why is this important? Imagine that a wrapper has a stack
+ allocated local, and passes to the hidden call, a pointer to it.
+ Because gcc does not know about the hidden call, it may allocate
+ that local in the redzone. Unfortunately the hidden call may then
+ trash it before it comes to use it. So we must step clear of the
+ redzone, for the duration of the hidden call, to make it safe.
+
+ Probably the same problem afflicts the other redzone-style ABIs too
+ (ppc64-linux); but for those, the stack is
+ self describing (none of this CFI nonsense) so at least messing
+ with the stack pointer doesn't give a danger of non-unwindable
+ stack. */
+
+#define CALL_FN_W_v(lval, orig) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[1]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ VALGRIND_ALIGN_STACK \
+ "subq $128,%%rsp\n\t" \
+ "movq (%%rax), %%rax\n\t" /* target->%rax */ \
+ VALGRIND_CALL_NOREDIR_RAX \
+ VALGRIND_RESTORE_STACK \
+ VALGRIND_CFI_EPILOGUE \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_W(lval, orig, arg1) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[2]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ VALGRIND_ALIGN_STACK \
+ "subq $128,%%rsp\n\t" \
+ "movq 8(%%rax), %%rdi\n\t" \
+ "movq (%%rax), %%rax\n\t" /* target->%rax */ \
+ VALGRIND_CALL_NOREDIR_RAX \
+ VALGRIND_RESTORE_STACK \
+ VALGRIND_CFI_EPILOGUE \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WW(lval, orig, arg1,arg2) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ VALGRIND_ALIGN_STACK \
+ "subq $128,%%rsp\n\t" \
+ "movq 16(%%rax), %%rsi\n\t" \
+ "movq 8(%%rax), %%rdi\n\t" \
+ "movq (%%rax), %%rax\n\t" /* target->%rax */ \
+ VALGRIND_CALL_NOREDIR_RAX \
+ VALGRIND_RESTORE_STACK \
+ VALGRIND_CFI_EPILOGUE \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[4]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ VALGRIND_ALIGN_STACK \
+ "subq $128,%%rsp\n\t" \
+ "movq 24(%%rax), %%rdx\n\t" \
+ "movq 16(%%rax), %%rsi\n\t" \
+ "movq 8(%%rax), %%rdi\n\t" \
+ "movq (%%rax), %%rax\n\t" /* target->%rax */ \
+ VALGRIND_CALL_NOREDIR_RAX \
+ VALGRIND_RESTORE_STACK \
+ VALGRIND_CFI_EPILOGUE \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[5]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ VALGRIND_ALIGN_STACK \
+ "subq $128,%%rsp\n\t" \
+ "movq 32(%%rax), %%rcx\n\t" \
+ "movq 24(%%rax), %%rdx\n\t" \
+ "movq 16(%%rax), %%rsi\n\t" \
+ "movq 8(%%rax), %%rdi\n\t" \
+ "movq (%%rax), %%rax\n\t" /* target->%rax */ \
+ VALGRIND_CALL_NOREDIR_RAX \
+ VALGRIND_RESTORE_STACK \
+ VALGRIND_CFI_EPILOGUE \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[6]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ VALGRIND_ALIGN_STACK \
+ "subq $128,%%rsp\n\t" \
+ "movq 40(%%rax), %%r8\n\t" \
+ "movq 32(%%rax), %%rcx\n\t" \
+ "movq 24(%%rax), %%rdx\n\t" \
+ "movq 16(%%rax), %%rsi\n\t" \
+ "movq 8(%%rax), %%rdi\n\t" \
+ "movq (%%rax), %%rax\n\t" /* target->%rax */ \
+ VALGRIND_CALL_NOREDIR_RAX \
+ VALGRIND_RESTORE_STACK \
+ VALGRIND_CFI_EPILOGUE \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[7]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ VALGRIND_ALIGN_STACK \
+ "subq $128,%%rsp\n\t" \
+ "movq 48(%%rax), %%r9\n\t" \
+ "movq 40(%%rax), %%r8\n\t" \
+ "movq 32(%%rax), %%rcx\n\t" \
+ "movq 24(%%rax), %%rdx\n\t" \
+ "movq 16(%%rax), %%rsi\n\t" \
+ "movq 8(%%rax), %%rdi\n\t" \
+ "movq (%%rax), %%rax\n\t" /* target->%rax */ \
+ VALGRIND_CALL_NOREDIR_RAX \
+ VALGRIND_RESTORE_STACK \
+ VALGRIND_CFI_EPILOGUE \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[8]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ VALGRIND_ALIGN_STACK \
+ "subq $136,%%rsp\n\t" \
+ "pushq 56(%%rax)\n\t" \
+ "movq 48(%%rax), %%r9\n\t" \
+ "movq 40(%%rax), %%r8\n\t" \
+ "movq 32(%%rax), %%rcx\n\t" \
+ "movq 24(%%rax), %%rdx\n\t" \
+ "movq 16(%%rax), %%rsi\n\t" \
+ "movq 8(%%rax), %%rdi\n\t" \
+ "movq (%%rax), %%rax\n\t" /* target->%rax */ \
+ VALGRIND_CALL_NOREDIR_RAX \
+ VALGRIND_RESTORE_STACK \
+ VALGRIND_CFI_EPILOGUE \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[9]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ VALGRIND_ALIGN_STACK \
+ "subq $128,%%rsp\n\t" \
+ "pushq 64(%%rax)\n\t" \
+ "pushq 56(%%rax)\n\t" \
+ "movq 48(%%rax), %%r9\n\t" \
+ "movq 40(%%rax), %%r8\n\t" \
+ "movq 32(%%rax), %%rcx\n\t" \
+ "movq 24(%%rax), %%rdx\n\t" \
+ "movq 16(%%rax), %%rsi\n\t" \
+ "movq 8(%%rax), %%rdi\n\t" \
+ "movq (%%rax), %%rax\n\t" /* target->%rax */ \
+ VALGRIND_CALL_NOREDIR_RAX \
+ VALGRIND_RESTORE_STACK \
+ VALGRIND_CFI_EPILOGUE \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[10]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ VALGRIND_ALIGN_STACK \
+ "subq $136,%%rsp\n\t" \
+ "pushq 72(%%rax)\n\t" \
+ "pushq 64(%%rax)\n\t" \
+ "pushq 56(%%rax)\n\t" \
+ "movq 48(%%rax), %%r9\n\t" \
+ "movq 40(%%rax), %%r8\n\t" \
+ "movq 32(%%rax), %%rcx\n\t" \
+ "movq 24(%%rax), %%rdx\n\t" \
+ "movq 16(%%rax), %%rsi\n\t" \
+ "movq 8(%%rax), %%rdi\n\t" \
+ "movq (%%rax), %%rax\n\t" /* target->%rax */ \
+ VALGRIND_CALL_NOREDIR_RAX \
+ VALGRIND_RESTORE_STACK \
+ VALGRIND_CFI_EPILOGUE \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[11]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ VALGRIND_ALIGN_STACK \
+ "subq $128,%%rsp\n\t" \
+ "pushq 80(%%rax)\n\t" \
+ "pushq 72(%%rax)\n\t" \
+ "pushq 64(%%rax)\n\t" \
+ "pushq 56(%%rax)\n\t" \
+ "movq 48(%%rax), %%r9\n\t" \
+ "movq 40(%%rax), %%r8\n\t" \
+ "movq 32(%%rax), %%rcx\n\t" \
+ "movq 24(%%rax), %%rdx\n\t" \
+ "movq 16(%%rax), %%rsi\n\t" \
+ "movq 8(%%rax), %%rdi\n\t" \
+ "movq (%%rax), %%rax\n\t" /* target->%rax */ \
+ VALGRIND_CALL_NOREDIR_RAX \
+ VALGRIND_RESTORE_STACK \
+ VALGRIND_CFI_EPILOGUE \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10,arg11) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[12]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ VALGRIND_ALIGN_STACK \
+ "subq $136,%%rsp\n\t" \
+ "pushq 88(%%rax)\n\t" \
+ "pushq 80(%%rax)\n\t" \
+ "pushq 72(%%rax)\n\t" \
+ "pushq 64(%%rax)\n\t" \
+ "pushq 56(%%rax)\n\t" \
+ "movq 48(%%rax), %%r9\n\t" \
+ "movq 40(%%rax), %%r8\n\t" \
+ "movq 32(%%rax), %%rcx\n\t" \
+ "movq 24(%%rax), %%rdx\n\t" \
+ "movq 16(%%rax), %%rsi\n\t" \
+ "movq 8(%%rax), %%rdi\n\t" \
+ "movq (%%rax), %%rax\n\t" /* target->%rax */ \
+ VALGRIND_CALL_NOREDIR_RAX \
+ VALGRIND_RESTORE_STACK \
+ VALGRIND_CFI_EPILOGUE \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10,arg11,arg12) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[13]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ _argvec[12] = (unsigned long)(arg12); \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ VALGRIND_ALIGN_STACK \
+ "subq $128,%%rsp\n\t" \
+ "pushq 96(%%rax)\n\t" \
+ "pushq 88(%%rax)\n\t" \
+ "pushq 80(%%rax)\n\t" \
+ "pushq 72(%%rax)\n\t" \
+ "pushq 64(%%rax)\n\t" \
+ "pushq 56(%%rax)\n\t" \
+ "movq 48(%%rax), %%r9\n\t" \
+ "movq 40(%%rax), %%r8\n\t" \
+ "movq 32(%%rax), %%rcx\n\t" \
+ "movq 24(%%rax), %%rdx\n\t" \
+ "movq 16(%%rax), %%rsi\n\t" \
+ "movq 8(%%rax), %%rdi\n\t" \
+ "movq (%%rax), %%rax\n\t" /* target->%rax */ \
+ VALGRIND_CALL_NOREDIR_RAX \
+ VALGRIND_RESTORE_STACK \
+ VALGRIND_CFI_EPILOGUE \
+ : /*out*/ "=a" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#endif /* PLAT_amd64_linux || PLAT_amd64_darwin || PLAT_amd64_solaris */
+
+/* ------------------------ ppc32-linux ------------------------ */
+
+#if defined(PLAT_ppc32_linux)
+
+/* This is useful for finding out about the on-stack stuff:
+
+ extern int f9 ( int,int,int,int,int,int,int,int,int );
+ extern int f10 ( int,int,int,int,int,int,int,int,int,int );
+ extern int f11 ( int,int,int,int,int,int,int,int,int,int,int );
+ extern int f12 ( int,int,int,int,int,int,int,int,int,int,int,int );
+
+ int g9 ( void ) {
+ return f9(11,22,33,44,55,66,77,88,99);
+ }
+ int g10 ( void ) {
+ return f10(11,22,33,44,55,66,77,88,99,110);
+ }
+ int g11 ( void ) {
+ return f11(11,22,33,44,55,66,77,88,99,110,121);
+ }
+ int g12 ( void ) {
+ return f12(11,22,33,44,55,66,77,88,99,110,121,132);
+ }
+*/
+
+/* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */
+
+/* These regs are trashed by the hidden call. */
+#define __CALLER_SAVED_REGS \
+ "lr", "ctr", "xer", \
+ "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \
+ "r0", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \
+ "r11", "r12", "r13"
+
+/* Macros to save and align the stack before making a function
+ call and restore it afterwards as gcc may not keep the stack
+ pointer aligned if it doesn't realise calls are being made
+ to other functions. */
+
+#define VALGRIND_ALIGN_STACK \
+ "mr 28,1\n\t" \
+ "rlwinm 1,1,0,0,27\n\t"
+#define VALGRIND_RESTORE_STACK \
+ "mr 1,28\n\t"
+
+/* These CALL_FN_ macros assume that on ppc32-linux,
+ sizeof(unsigned long) == 4. */
+
+#define CALL_FN_W_v(lval, orig) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[1]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "lwz 11,0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ VALGRIND_RESTORE_STACK \
+ "mr %0,3" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_W(lval, orig, arg1) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[2]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "lwz 3,4(11)\n\t" /* arg1->r3 */ \
+ "lwz 11,0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ VALGRIND_RESTORE_STACK \
+ "mr %0,3" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WW(lval, orig, arg1,arg2) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "lwz 3,4(11)\n\t" /* arg1->r3 */ \
+ "lwz 4,8(11)\n\t" \
+ "lwz 11,0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ VALGRIND_RESTORE_STACK \
+ "mr %0,3" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[4]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "lwz 3,4(11)\n\t" /* arg1->r3 */ \
+ "lwz 4,8(11)\n\t" \
+ "lwz 5,12(11)\n\t" \
+ "lwz 11,0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ VALGRIND_RESTORE_STACK \
+ "mr %0,3" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[5]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "lwz 3,4(11)\n\t" /* arg1->r3 */ \
+ "lwz 4,8(11)\n\t" \
+ "lwz 5,12(11)\n\t" \
+ "lwz 6,16(11)\n\t" /* arg4->r6 */ \
+ "lwz 11,0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ VALGRIND_RESTORE_STACK \
+ "mr %0,3" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[6]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "lwz 3,4(11)\n\t" /* arg1->r3 */ \
+ "lwz 4,8(11)\n\t" \
+ "lwz 5,12(11)\n\t" \
+ "lwz 6,16(11)\n\t" /* arg4->r6 */ \
+ "lwz 7,20(11)\n\t" \
+ "lwz 11,0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ VALGRIND_RESTORE_STACK \
+ "mr %0,3" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[7]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "lwz 3,4(11)\n\t" /* arg1->r3 */ \
+ "lwz 4,8(11)\n\t" \
+ "lwz 5,12(11)\n\t" \
+ "lwz 6,16(11)\n\t" /* arg4->r6 */ \
+ "lwz 7,20(11)\n\t" \
+ "lwz 8,24(11)\n\t" \
+ "lwz 11,0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ VALGRIND_RESTORE_STACK \
+ "mr %0,3" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[8]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ _argvec[7] = (unsigned long)arg7; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "lwz 3,4(11)\n\t" /* arg1->r3 */ \
+ "lwz 4,8(11)\n\t" \
+ "lwz 5,12(11)\n\t" \
+ "lwz 6,16(11)\n\t" /* arg4->r6 */ \
+ "lwz 7,20(11)\n\t" \
+ "lwz 8,24(11)\n\t" \
+ "lwz 9,28(11)\n\t" \
+ "lwz 11,0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ VALGRIND_RESTORE_STACK \
+ "mr %0,3" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[9]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ _argvec[7] = (unsigned long)arg7; \
+ _argvec[8] = (unsigned long)arg8; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "lwz 3,4(11)\n\t" /* arg1->r3 */ \
+ "lwz 4,8(11)\n\t" \
+ "lwz 5,12(11)\n\t" \
+ "lwz 6,16(11)\n\t" /* arg4->r6 */ \
+ "lwz 7,20(11)\n\t" \
+ "lwz 8,24(11)\n\t" \
+ "lwz 9,28(11)\n\t" \
+ "lwz 10,32(11)\n\t" /* arg8->r10 */ \
+ "lwz 11,0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ VALGRIND_RESTORE_STACK \
+ "mr %0,3" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[10]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ _argvec[7] = (unsigned long)arg7; \
+ _argvec[8] = (unsigned long)arg8; \
+ _argvec[9] = (unsigned long)arg9; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "addi 1,1,-16\n\t" \
+ /* arg9 */ \
+ "lwz 3,36(11)\n\t" \
+ "stw 3,8(1)\n\t" \
+ /* args1-8 */ \
+ "lwz 3,4(11)\n\t" /* arg1->r3 */ \
+ "lwz 4,8(11)\n\t" \
+ "lwz 5,12(11)\n\t" \
+ "lwz 6,16(11)\n\t" /* arg4->r6 */ \
+ "lwz 7,20(11)\n\t" \
+ "lwz 8,24(11)\n\t" \
+ "lwz 9,28(11)\n\t" \
+ "lwz 10,32(11)\n\t" /* arg8->r10 */ \
+ "lwz 11,0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ VALGRIND_RESTORE_STACK \
+ "mr %0,3" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[11]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ _argvec[7] = (unsigned long)arg7; \
+ _argvec[8] = (unsigned long)arg8; \
+ _argvec[9] = (unsigned long)arg9; \
+ _argvec[10] = (unsigned long)arg10; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "addi 1,1,-16\n\t" \
+ /* arg10 */ \
+ "lwz 3,40(11)\n\t" \
+ "stw 3,12(1)\n\t" \
+ /* arg9 */ \
+ "lwz 3,36(11)\n\t" \
+ "stw 3,8(1)\n\t" \
+ /* args1-8 */ \
+ "lwz 3,4(11)\n\t" /* arg1->r3 */ \
+ "lwz 4,8(11)\n\t" \
+ "lwz 5,12(11)\n\t" \
+ "lwz 6,16(11)\n\t" /* arg4->r6 */ \
+ "lwz 7,20(11)\n\t" \
+ "lwz 8,24(11)\n\t" \
+ "lwz 9,28(11)\n\t" \
+ "lwz 10,32(11)\n\t" /* arg8->r10 */ \
+ "lwz 11,0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ VALGRIND_RESTORE_STACK \
+ "mr %0,3" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10,arg11) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[12]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ _argvec[7] = (unsigned long)arg7; \
+ _argvec[8] = (unsigned long)arg8; \
+ _argvec[9] = (unsigned long)arg9; \
+ _argvec[10] = (unsigned long)arg10; \
+ _argvec[11] = (unsigned long)arg11; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "addi 1,1,-32\n\t" \
+ /* arg11 */ \
+ "lwz 3,44(11)\n\t" \
+ "stw 3,16(1)\n\t" \
+ /* arg10 */ \
+ "lwz 3,40(11)\n\t" \
+ "stw 3,12(1)\n\t" \
+ /* arg9 */ \
+ "lwz 3,36(11)\n\t" \
+ "stw 3,8(1)\n\t" \
+ /* args1-8 */ \
+ "lwz 3,4(11)\n\t" /* arg1->r3 */ \
+ "lwz 4,8(11)\n\t" \
+ "lwz 5,12(11)\n\t" \
+ "lwz 6,16(11)\n\t" /* arg4->r6 */ \
+ "lwz 7,20(11)\n\t" \
+ "lwz 8,24(11)\n\t" \
+ "lwz 9,28(11)\n\t" \
+ "lwz 10,32(11)\n\t" /* arg8->r10 */ \
+ "lwz 11,0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ VALGRIND_RESTORE_STACK \
+ "mr %0,3" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10,arg11,arg12) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[13]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ _argvec[7] = (unsigned long)arg7; \
+ _argvec[8] = (unsigned long)arg8; \
+ _argvec[9] = (unsigned long)arg9; \
+ _argvec[10] = (unsigned long)arg10; \
+ _argvec[11] = (unsigned long)arg11; \
+ _argvec[12] = (unsigned long)arg12; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "addi 1,1,-32\n\t" \
+ /* arg12 */ \
+ "lwz 3,48(11)\n\t" \
+ "stw 3,20(1)\n\t" \
+ /* arg11 */ \
+ "lwz 3,44(11)\n\t" \
+ "stw 3,16(1)\n\t" \
+ /* arg10 */ \
+ "lwz 3,40(11)\n\t" \
+ "stw 3,12(1)\n\t" \
+ /* arg9 */ \
+ "lwz 3,36(11)\n\t" \
+ "stw 3,8(1)\n\t" \
+ /* args1-8 */ \
+ "lwz 3,4(11)\n\t" /* arg1->r3 */ \
+ "lwz 4,8(11)\n\t" \
+ "lwz 5,12(11)\n\t" \
+ "lwz 6,16(11)\n\t" /* arg4->r6 */ \
+ "lwz 7,20(11)\n\t" \
+ "lwz 8,24(11)\n\t" \
+ "lwz 9,28(11)\n\t" \
+ "lwz 10,32(11)\n\t" /* arg8->r10 */ \
+ "lwz 11,0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ VALGRIND_RESTORE_STACK \
+ "mr %0,3" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#endif /* PLAT_ppc32_linux */
+
+/* ------------------------ ppc64-linux ------------------------ */
+
+#if defined(PLAT_ppc64be_linux)
+
+/* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */
+
+/* These regs are trashed by the hidden call. */
+#define __CALLER_SAVED_REGS \
+ "lr", "ctr", "xer", \
+ "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \
+ "r0", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \
+ "r11", "r12", "r13"
+
+/* Macros to save and align the stack before making a function
+ call and restore it afterwards as gcc may not keep the stack
+ pointer aligned if it doesn't realise calls are being made
+ to other functions. */
+
+#define VALGRIND_ALIGN_STACK \
+ "mr 28,1\n\t" \
+ "rldicr 1,1,0,59\n\t"
+#define VALGRIND_RESTORE_STACK \
+ "mr 1,28\n\t"
+
+/* These CALL_FN_ macros assume that on ppc64-linux, sizeof(unsigned
+ long) == 8. */
+
+#define CALL_FN_W_v(lval, orig) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+0]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "std 2,-16(11)\n\t" /* save tocptr */ \
+ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \
+ "ld 11, 0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ "mr 11,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(11)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_W(lval, orig, arg1) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+1]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "std 2,-16(11)\n\t" /* save tocptr */ \
+ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(11)\n\t" /* arg1->r3 */ \
+ "ld 11, 0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ "mr 11,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(11)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WW(lval, orig, arg1,arg2) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+2]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "std 2,-16(11)\n\t" /* save tocptr */ \
+ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(11)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(11)\n\t" /* arg2->r4 */ \
+ "ld 11, 0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ "mr 11,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(11)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+3]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "std 2,-16(11)\n\t" /* save tocptr */ \
+ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(11)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(11)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(11)\n\t" /* arg3->r5 */ \
+ "ld 11, 0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ "mr 11,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(11)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+4]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "std 2,-16(11)\n\t" /* save tocptr */ \
+ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(11)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(11)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(11)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(11)\n\t" /* arg4->r6 */ \
+ "ld 11, 0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ "mr 11,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(11)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+5]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "std 2,-16(11)\n\t" /* save tocptr */ \
+ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(11)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(11)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(11)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(11)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(11)\n\t" /* arg5->r7 */ \
+ "ld 11, 0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ "mr 11,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(11)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+6]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "std 2,-16(11)\n\t" /* save tocptr */ \
+ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(11)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(11)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(11)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(11)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(11)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(11)\n\t" /* arg6->r8 */ \
+ "ld 11, 0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ "mr 11,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(11)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+7]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ _argvec[2+7] = (unsigned long)arg7; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "std 2,-16(11)\n\t" /* save tocptr */ \
+ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(11)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(11)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(11)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(11)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(11)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(11)\n\t" /* arg6->r8 */ \
+ "ld 9, 56(11)\n\t" /* arg7->r9 */ \
+ "ld 11, 0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ "mr 11,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(11)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+8]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ _argvec[2+7] = (unsigned long)arg7; \
+ _argvec[2+8] = (unsigned long)arg8; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "std 2,-16(11)\n\t" /* save tocptr */ \
+ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(11)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(11)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(11)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(11)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(11)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(11)\n\t" /* arg6->r8 */ \
+ "ld 9, 56(11)\n\t" /* arg7->r9 */ \
+ "ld 10, 64(11)\n\t" /* arg8->r10 */ \
+ "ld 11, 0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ "mr 11,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(11)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+9]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ _argvec[2+7] = (unsigned long)arg7; \
+ _argvec[2+8] = (unsigned long)arg8; \
+ _argvec[2+9] = (unsigned long)arg9; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "std 2,-16(11)\n\t" /* save tocptr */ \
+ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \
+ "addi 1,1,-128\n\t" /* expand stack frame */ \
+ /* arg9 */ \
+ "ld 3,72(11)\n\t" \
+ "std 3,112(1)\n\t" \
+ /* args1-8 */ \
+ "ld 3, 8(11)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(11)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(11)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(11)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(11)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(11)\n\t" /* arg6->r8 */ \
+ "ld 9, 56(11)\n\t" /* arg7->r9 */ \
+ "ld 10, 64(11)\n\t" /* arg8->r10 */ \
+ "ld 11, 0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ "mr 11,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(11)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+10]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ _argvec[2+7] = (unsigned long)arg7; \
+ _argvec[2+8] = (unsigned long)arg8; \
+ _argvec[2+9] = (unsigned long)arg9; \
+ _argvec[2+10] = (unsigned long)arg10; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "std 2,-16(11)\n\t" /* save tocptr */ \
+ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \
+ "addi 1,1,-128\n\t" /* expand stack frame */ \
+ /* arg10 */ \
+ "ld 3,80(11)\n\t" \
+ "std 3,120(1)\n\t" \
+ /* arg9 */ \
+ "ld 3,72(11)\n\t" \
+ "std 3,112(1)\n\t" \
+ /* args1-8 */ \
+ "ld 3, 8(11)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(11)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(11)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(11)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(11)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(11)\n\t" /* arg6->r8 */ \
+ "ld 9, 56(11)\n\t" /* arg7->r9 */ \
+ "ld 10, 64(11)\n\t" /* arg8->r10 */ \
+ "ld 11, 0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ "mr 11,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(11)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10,arg11) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+11]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ _argvec[2+7] = (unsigned long)arg7; \
+ _argvec[2+8] = (unsigned long)arg8; \
+ _argvec[2+9] = (unsigned long)arg9; \
+ _argvec[2+10] = (unsigned long)arg10; \
+ _argvec[2+11] = (unsigned long)arg11; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "std 2,-16(11)\n\t" /* save tocptr */ \
+ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \
+ "addi 1,1,-144\n\t" /* expand stack frame */ \
+ /* arg11 */ \
+ "ld 3,88(11)\n\t" \
+ "std 3,128(1)\n\t" \
+ /* arg10 */ \
+ "ld 3,80(11)\n\t" \
+ "std 3,120(1)\n\t" \
+ /* arg9 */ \
+ "ld 3,72(11)\n\t" \
+ "std 3,112(1)\n\t" \
+ /* args1-8 */ \
+ "ld 3, 8(11)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(11)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(11)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(11)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(11)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(11)\n\t" /* arg6->r8 */ \
+ "ld 9, 56(11)\n\t" /* arg7->r9 */ \
+ "ld 10, 64(11)\n\t" /* arg8->r10 */ \
+ "ld 11, 0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ "mr 11,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(11)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10,arg11,arg12) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+12]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ _argvec[2+7] = (unsigned long)arg7; \
+ _argvec[2+8] = (unsigned long)arg8; \
+ _argvec[2+9] = (unsigned long)arg9; \
+ _argvec[2+10] = (unsigned long)arg10; \
+ _argvec[2+11] = (unsigned long)arg11; \
+ _argvec[2+12] = (unsigned long)arg12; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 11,%1\n\t" \
+ "std 2,-16(11)\n\t" /* save tocptr */ \
+ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \
+ "addi 1,1,-144\n\t" /* expand stack frame */ \
+ /* arg12 */ \
+ "ld 3,96(11)\n\t" \
+ "std 3,136(1)\n\t" \
+ /* arg11 */ \
+ "ld 3,88(11)\n\t" \
+ "std 3,128(1)\n\t" \
+ /* arg10 */ \
+ "ld 3,80(11)\n\t" \
+ "std 3,120(1)\n\t" \
+ /* arg9 */ \
+ "ld 3,72(11)\n\t" \
+ "std 3,112(1)\n\t" \
+ /* args1-8 */ \
+ "ld 3, 8(11)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(11)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(11)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(11)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(11)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(11)\n\t" /* arg6->r8 */ \
+ "ld 9, 56(11)\n\t" /* arg7->r9 */ \
+ "ld 10, 64(11)\n\t" /* arg8->r10 */ \
+ "ld 11, 0(11)\n\t" /* target->r11 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \
+ "mr 11,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(11)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#endif /* PLAT_ppc64be_linux */
+
+/* ------------------------- ppc64le-linux ----------------------- */
+#if defined(PLAT_ppc64le_linux)
+
+/* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */
+
+/* These regs are trashed by the hidden call. */
+#define __CALLER_SAVED_REGS \
+ "lr", "ctr", "xer", \
+ "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \
+ "r0", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \
+ "r11", "r12", "r13"
+
+/* Macros to save and align the stack before making a function
+ call and restore it afterwards as gcc may not keep the stack
+ pointer aligned if it doesn't realise calls are being made
+ to other functions. */
+
+#define VALGRIND_ALIGN_STACK \
+ "mr 28,1\n\t" \
+ "rldicr 1,1,0,59\n\t"
+#define VALGRIND_RESTORE_STACK \
+ "mr 1,28\n\t"
+
+/* These CALL_FN_ macros assume that on ppc64-linux, sizeof(unsigned
+ long) == 8. */
+
+#define CALL_FN_W_v(lval, orig) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+0]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 12,%1\n\t" \
+ "std 2,-16(12)\n\t" /* save tocptr */ \
+ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \
+ "ld 12, 0(12)\n\t" /* target->r12 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ "mr 12,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(12)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_W(lval, orig, arg1) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+1]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 12,%1\n\t" \
+ "std 2,-16(12)\n\t" /* save tocptr */ \
+ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(12)\n\t" /* arg1->r3 */ \
+ "ld 12, 0(12)\n\t" /* target->r12 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ "mr 12,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(12)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WW(lval, orig, arg1,arg2) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+2]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 12,%1\n\t" \
+ "std 2,-16(12)\n\t" /* save tocptr */ \
+ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(12)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(12)\n\t" /* arg2->r4 */ \
+ "ld 12, 0(12)\n\t" /* target->r12 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ "mr 12,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(12)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+3]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 12,%1\n\t" \
+ "std 2,-16(12)\n\t" /* save tocptr */ \
+ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(12)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(12)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(12)\n\t" /* arg3->r5 */ \
+ "ld 12, 0(12)\n\t" /* target->r12 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ "mr 12,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(12)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+4]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 12,%1\n\t" \
+ "std 2,-16(12)\n\t" /* save tocptr */ \
+ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(12)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(12)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(12)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(12)\n\t" /* arg4->r6 */ \
+ "ld 12, 0(12)\n\t" /* target->r12 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ "mr 12,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(12)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+5]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 12,%1\n\t" \
+ "std 2,-16(12)\n\t" /* save tocptr */ \
+ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(12)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(12)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(12)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(12)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(12)\n\t" /* arg5->r7 */ \
+ "ld 12, 0(12)\n\t" /* target->r12 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ "mr 12,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(12)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+6]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 12,%1\n\t" \
+ "std 2,-16(12)\n\t" /* save tocptr */ \
+ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(12)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(12)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(12)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(12)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(12)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(12)\n\t" /* arg6->r8 */ \
+ "ld 12, 0(12)\n\t" /* target->r12 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ "mr 12,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(12)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+7]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ _argvec[2+7] = (unsigned long)arg7; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 12,%1\n\t" \
+ "std 2,-16(12)\n\t" /* save tocptr */ \
+ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(12)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(12)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(12)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(12)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(12)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(12)\n\t" /* arg6->r8 */ \
+ "ld 9, 56(12)\n\t" /* arg7->r9 */ \
+ "ld 12, 0(12)\n\t" /* target->r12 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ "mr 12,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(12)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+8]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ _argvec[2+7] = (unsigned long)arg7; \
+ _argvec[2+8] = (unsigned long)arg8; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 12,%1\n\t" \
+ "std 2,-16(12)\n\t" /* save tocptr */ \
+ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \
+ "ld 3, 8(12)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(12)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(12)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(12)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(12)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(12)\n\t" /* arg6->r8 */ \
+ "ld 9, 56(12)\n\t" /* arg7->r9 */ \
+ "ld 10, 64(12)\n\t" /* arg8->r10 */ \
+ "ld 12, 0(12)\n\t" /* target->r12 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ "mr 12,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(12)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+9]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ _argvec[2+7] = (unsigned long)arg7; \
+ _argvec[2+8] = (unsigned long)arg8; \
+ _argvec[2+9] = (unsigned long)arg9; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 12,%1\n\t" \
+ "std 2,-16(12)\n\t" /* save tocptr */ \
+ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \
+ "addi 1,1,-128\n\t" /* expand stack frame */ \
+ /* arg9 */ \
+ "ld 3,72(12)\n\t" \
+ "std 3,96(1)\n\t" \
+ /* args1-8 */ \
+ "ld 3, 8(12)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(12)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(12)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(12)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(12)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(12)\n\t" /* arg6->r8 */ \
+ "ld 9, 56(12)\n\t" /* arg7->r9 */ \
+ "ld 10, 64(12)\n\t" /* arg8->r10 */ \
+ "ld 12, 0(12)\n\t" /* target->r12 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ "mr 12,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(12)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+10]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ _argvec[2+7] = (unsigned long)arg7; \
+ _argvec[2+8] = (unsigned long)arg8; \
+ _argvec[2+9] = (unsigned long)arg9; \
+ _argvec[2+10] = (unsigned long)arg10; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 12,%1\n\t" \
+ "std 2,-16(12)\n\t" /* save tocptr */ \
+ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \
+ "addi 1,1,-128\n\t" /* expand stack frame */ \
+ /* arg10 */ \
+ "ld 3,80(12)\n\t" \
+ "std 3,104(1)\n\t" \
+ /* arg9 */ \
+ "ld 3,72(12)\n\t" \
+ "std 3,96(1)\n\t" \
+ /* args1-8 */ \
+ "ld 3, 8(12)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(12)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(12)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(12)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(12)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(12)\n\t" /* arg6->r8 */ \
+ "ld 9, 56(12)\n\t" /* arg7->r9 */ \
+ "ld 10, 64(12)\n\t" /* arg8->r10 */ \
+ "ld 12, 0(12)\n\t" /* target->r12 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ "mr 12,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(12)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10,arg11) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+11]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ _argvec[2+7] = (unsigned long)arg7; \
+ _argvec[2+8] = (unsigned long)arg8; \
+ _argvec[2+9] = (unsigned long)arg9; \
+ _argvec[2+10] = (unsigned long)arg10; \
+ _argvec[2+11] = (unsigned long)arg11; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 12,%1\n\t" \
+ "std 2,-16(12)\n\t" /* save tocptr */ \
+ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \
+ "addi 1,1,-144\n\t" /* expand stack frame */ \
+ /* arg11 */ \
+ "ld 3,88(12)\n\t" \
+ "std 3,112(1)\n\t" \
+ /* arg10 */ \
+ "ld 3,80(12)\n\t" \
+ "std 3,104(1)\n\t" \
+ /* arg9 */ \
+ "ld 3,72(12)\n\t" \
+ "std 3,96(1)\n\t" \
+ /* args1-8 */ \
+ "ld 3, 8(12)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(12)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(12)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(12)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(12)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(12)\n\t" /* arg6->r8 */ \
+ "ld 9, 56(12)\n\t" /* arg7->r9 */ \
+ "ld 10, 64(12)\n\t" /* arg8->r10 */ \
+ "ld 12, 0(12)\n\t" /* target->r12 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ "mr 12,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(12)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10,arg11,arg12) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3+12]; \
+ volatile unsigned long _res; \
+ /* _argvec[0] holds current r2 across the call */ \
+ _argvec[1] = (unsigned long)_orig.r2; \
+ _argvec[2] = (unsigned long)_orig.nraddr; \
+ _argvec[2+1] = (unsigned long)arg1; \
+ _argvec[2+2] = (unsigned long)arg2; \
+ _argvec[2+3] = (unsigned long)arg3; \
+ _argvec[2+4] = (unsigned long)arg4; \
+ _argvec[2+5] = (unsigned long)arg5; \
+ _argvec[2+6] = (unsigned long)arg6; \
+ _argvec[2+7] = (unsigned long)arg7; \
+ _argvec[2+8] = (unsigned long)arg8; \
+ _argvec[2+9] = (unsigned long)arg9; \
+ _argvec[2+10] = (unsigned long)arg10; \
+ _argvec[2+11] = (unsigned long)arg11; \
+ _argvec[2+12] = (unsigned long)arg12; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "mr 12,%1\n\t" \
+ "std 2,-16(12)\n\t" /* save tocptr */ \
+ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \
+ "addi 1,1,-144\n\t" /* expand stack frame */ \
+ /* arg12 */ \
+ "ld 3,96(12)\n\t" \
+ "std 3,120(1)\n\t" \
+ /* arg11 */ \
+ "ld 3,88(12)\n\t" \
+ "std 3,112(1)\n\t" \
+ /* arg10 */ \
+ "ld 3,80(12)\n\t" \
+ "std 3,104(1)\n\t" \
+ /* arg9 */ \
+ "ld 3,72(12)\n\t" \
+ "std 3,96(1)\n\t" \
+ /* args1-8 */ \
+ "ld 3, 8(12)\n\t" /* arg1->r3 */ \
+ "ld 4, 16(12)\n\t" /* arg2->r4 */ \
+ "ld 5, 24(12)\n\t" /* arg3->r5 */ \
+ "ld 6, 32(12)\n\t" /* arg4->r6 */ \
+ "ld 7, 40(12)\n\t" /* arg5->r7 */ \
+ "ld 8, 48(12)\n\t" /* arg6->r8 */ \
+ "ld 9, 56(12)\n\t" /* arg7->r9 */ \
+ "ld 10, 64(12)\n\t" /* arg8->r10 */ \
+ "ld 12, 0(12)\n\t" /* target->r12 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \
+ "mr 12,%1\n\t" \
+ "mr %0,3\n\t" \
+ "ld 2,-16(12)\n\t" /* restore tocptr */ \
+ VALGRIND_RESTORE_STACK \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[2]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#endif /* PLAT_ppc64le_linux */
+
+/* ------------------------- arm-linux ------------------------- */
+
+#if defined(PLAT_arm_linux)
+
+/* These regs are trashed by the hidden call. */
+#define __CALLER_SAVED_REGS "r0", "r1", "r2", "r3","r4", "r12", "r14"
+
+/* Macros to save and align the stack before making a function
+ call and restore it afterwards as gcc may not keep the stack
+ pointer aligned if it doesn't realise calls are being made
+ to other functions. */
+
+/* This is a bit tricky. We store the original stack pointer in r10
+ as it is callee-saves. gcc doesn't allow the use of r11 for some
+ reason. Also, we can't directly "bic" the stack pointer in thumb
+ mode since r13 isn't an allowed register number in that context.
+ So use r4 as a temporary, since that is about to get trashed
+ anyway, just after each use of this macro. Side effect is we need
+ to be very careful about any future changes, since
+ VALGRIND_ALIGN_STACK simply assumes r4 is usable. */
+#define VALGRIND_ALIGN_STACK \
+ "mov r10, sp\n\t" \
+ "mov r4, sp\n\t" \
+ "bic r4, r4, #7\n\t" \
+ "mov sp, r4\n\t"
+#define VALGRIND_RESTORE_STACK \
+ "mov sp, r10\n\t"
+
+/* These CALL_FN_ macros assume that on arm-linux, sizeof(unsigned
+ long) == 4. */
+
+#define CALL_FN_W_v(lval, orig) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[1]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr r4, [%1] \n\t" /* target->r4 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, r0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_W(lval, orig, arg1) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[2]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr r0, [%1, #4] \n\t" \
+ "ldr r4, [%1] \n\t" /* target->r4 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, r0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WW(lval, orig, arg1,arg2) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr r0, [%1, #4] \n\t" \
+ "ldr r1, [%1, #8] \n\t" \
+ "ldr r4, [%1] \n\t" /* target->r4 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, r0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[4]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr r0, [%1, #4] \n\t" \
+ "ldr r1, [%1, #8] \n\t" \
+ "ldr r2, [%1, #12] \n\t" \
+ "ldr r4, [%1] \n\t" /* target->r4 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, r0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[5]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr r0, [%1, #4] \n\t" \
+ "ldr r1, [%1, #8] \n\t" \
+ "ldr r2, [%1, #12] \n\t" \
+ "ldr r3, [%1, #16] \n\t" \
+ "ldr r4, [%1] \n\t" /* target->r4 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, r0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[6]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "sub sp, sp, #4 \n\t" \
+ "ldr r0, [%1, #20] \n\t" \
+ "push {r0} \n\t" \
+ "ldr r0, [%1, #4] \n\t" \
+ "ldr r1, [%1, #8] \n\t" \
+ "ldr r2, [%1, #12] \n\t" \
+ "ldr r3, [%1, #16] \n\t" \
+ "ldr r4, [%1] \n\t" /* target->r4 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, r0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[7]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr r0, [%1, #20] \n\t" \
+ "ldr r1, [%1, #24] \n\t" \
+ "push {r0, r1} \n\t" \
+ "ldr r0, [%1, #4] \n\t" \
+ "ldr r1, [%1, #8] \n\t" \
+ "ldr r2, [%1, #12] \n\t" \
+ "ldr r3, [%1, #16] \n\t" \
+ "ldr r4, [%1] \n\t" /* target->r4 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, r0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[8]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "sub sp, sp, #4 \n\t" \
+ "ldr r0, [%1, #20] \n\t" \
+ "ldr r1, [%1, #24] \n\t" \
+ "ldr r2, [%1, #28] \n\t" \
+ "push {r0, r1, r2} \n\t" \
+ "ldr r0, [%1, #4] \n\t" \
+ "ldr r1, [%1, #8] \n\t" \
+ "ldr r2, [%1, #12] \n\t" \
+ "ldr r3, [%1, #16] \n\t" \
+ "ldr r4, [%1] \n\t" /* target->r4 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, r0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[9]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr r0, [%1, #20] \n\t" \
+ "ldr r1, [%1, #24] \n\t" \
+ "ldr r2, [%1, #28] \n\t" \
+ "ldr r3, [%1, #32] \n\t" \
+ "push {r0, r1, r2, r3} \n\t" \
+ "ldr r0, [%1, #4] \n\t" \
+ "ldr r1, [%1, #8] \n\t" \
+ "ldr r2, [%1, #12] \n\t" \
+ "ldr r3, [%1, #16] \n\t" \
+ "ldr r4, [%1] \n\t" /* target->r4 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, r0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[10]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "sub sp, sp, #4 \n\t" \
+ "ldr r0, [%1, #20] \n\t" \
+ "ldr r1, [%1, #24] \n\t" \
+ "ldr r2, [%1, #28] \n\t" \
+ "ldr r3, [%1, #32] \n\t" \
+ "ldr r4, [%1, #36] \n\t" \
+ "push {r0, r1, r2, r3, r4} \n\t" \
+ "ldr r0, [%1, #4] \n\t" \
+ "ldr r1, [%1, #8] \n\t" \
+ "ldr r2, [%1, #12] \n\t" \
+ "ldr r3, [%1, #16] \n\t" \
+ "ldr r4, [%1] \n\t" /* target->r4 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, r0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[11]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr r0, [%1, #40] \n\t" \
+ "push {r0} \n\t" \
+ "ldr r0, [%1, #20] \n\t" \
+ "ldr r1, [%1, #24] \n\t" \
+ "ldr r2, [%1, #28] \n\t" \
+ "ldr r3, [%1, #32] \n\t" \
+ "ldr r4, [%1, #36] \n\t" \
+ "push {r0, r1, r2, r3, r4} \n\t" \
+ "ldr r0, [%1, #4] \n\t" \
+ "ldr r1, [%1, #8] \n\t" \
+ "ldr r2, [%1, #12] \n\t" \
+ "ldr r3, [%1, #16] \n\t" \
+ "ldr r4, [%1] \n\t" /* target->r4 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, r0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \
+ arg6,arg7,arg8,arg9,arg10, \
+ arg11) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[12]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "sub sp, sp, #4 \n\t" \
+ "ldr r0, [%1, #40] \n\t" \
+ "ldr r1, [%1, #44] \n\t" \
+ "push {r0, r1} \n\t" \
+ "ldr r0, [%1, #20] \n\t" \
+ "ldr r1, [%1, #24] \n\t" \
+ "ldr r2, [%1, #28] \n\t" \
+ "ldr r3, [%1, #32] \n\t" \
+ "ldr r4, [%1, #36] \n\t" \
+ "push {r0, r1, r2, r3, r4} \n\t" \
+ "ldr r0, [%1, #4] \n\t" \
+ "ldr r1, [%1, #8] \n\t" \
+ "ldr r2, [%1, #12] \n\t" \
+ "ldr r3, [%1, #16] \n\t" \
+ "ldr r4, [%1] \n\t" /* target->r4 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, r0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \
+ arg6,arg7,arg8,arg9,arg10, \
+ arg11,arg12) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[13]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ _argvec[12] = (unsigned long)(arg12); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr r0, [%1, #40] \n\t" \
+ "ldr r1, [%1, #44] \n\t" \
+ "ldr r2, [%1, #48] \n\t" \
+ "push {r0, r1, r2} \n\t" \
+ "ldr r0, [%1, #20] \n\t" \
+ "ldr r1, [%1, #24] \n\t" \
+ "ldr r2, [%1, #28] \n\t" \
+ "ldr r3, [%1, #32] \n\t" \
+ "ldr r4, [%1, #36] \n\t" \
+ "push {r0, r1, r2, r3, r4} \n\t" \
+ "ldr r0, [%1, #4] \n\t" \
+ "ldr r1, [%1, #8] \n\t" \
+ "ldr r2, [%1, #12] \n\t" \
+ "ldr r3, [%1, #16] \n\t" \
+ "ldr r4, [%1] \n\t" /* target->r4 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, r0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#endif /* PLAT_arm_linux */
+
+/* ------------------------ arm64-linux ------------------------ */
+
+#if defined(PLAT_arm64_linux)
+
+/* These regs are trashed by the hidden call. */
+#define __CALLER_SAVED_REGS \
+ "x0", "x1", "x2", "x3","x4", "x5", "x6", "x7", "x8", "x9", \
+ "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", \
+ "x18", "x19", "x20", "x30", \
+ "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", \
+ "v10", "v11", "v12", "v13", "v14", "v15", "v16", "v17", \
+ "v18", "v19", "v20", "v21", "v22", "v23", "v24", "v25", \
+ "v26", "v27", "v28", "v29", "v30", "v31"
+
+/* x21 is callee-saved, so we can use it to save and restore SP around
+ the hidden call. */
+#define VALGRIND_ALIGN_STACK \
+ "mov x21, sp\n\t" \
+ "bic sp, x21, #15\n\t"
+#define VALGRIND_RESTORE_STACK \
+ "mov sp, x21\n\t"
+
+/* These CALL_FN_ macros assume that on arm64-linux,
+ sizeof(unsigned long) == 8. */
+
+#define CALL_FN_W_v(lval, orig) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[1]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr x8, [%1] \n\t" /* target->x8 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, x0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_W(lval, orig, arg1) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[2]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr x0, [%1, #8] \n\t" \
+ "ldr x8, [%1] \n\t" /* target->x8 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, x0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WW(lval, orig, arg1,arg2) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr x0, [%1, #8] \n\t" \
+ "ldr x1, [%1, #16] \n\t" \
+ "ldr x8, [%1] \n\t" /* target->x8 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, x0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[4]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr x0, [%1, #8] \n\t" \
+ "ldr x1, [%1, #16] \n\t" \
+ "ldr x2, [%1, #24] \n\t" \
+ "ldr x8, [%1] \n\t" /* target->x8 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, x0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[5]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr x0, [%1, #8] \n\t" \
+ "ldr x1, [%1, #16] \n\t" \
+ "ldr x2, [%1, #24] \n\t" \
+ "ldr x3, [%1, #32] \n\t" \
+ "ldr x8, [%1] \n\t" /* target->x8 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, x0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[6]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr x0, [%1, #8] \n\t" \
+ "ldr x1, [%1, #16] \n\t" \
+ "ldr x2, [%1, #24] \n\t" \
+ "ldr x3, [%1, #32] \n\t" \
+ "ldr x4, [%1, #40] \n\t" \
+ "ldr x8, [%1] \n\t" /* target->x8 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, x0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[7]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr x0, [%1, #8] \n\t" \
+ "ldr x1, [%1, #16] \n\t" \
+ "ldr x2, [%1, #24] \n\t" \
+ "ldr x3, [%1, #32] \n\t" \
+ "ldr x4, [%1, #40] \n\t" \
+ "ldr x5, [%1, #48] \n\t" \
+ "ldr x8, [%1] \n\t" /* target->x8 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, x0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[8]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr x0, [%1, #8] \n\t" \
+ "ldr x1, [%1, #16] \n\t" \
+ "ldr x2, [%1, #24] \n\t" \
+ "ldr x3, [%1, #32] \n\t" \
+ "ldr x4, [%1, #40] \n\t" \
+ "ldr x5, [%1, #48] \n\t" \
+ "ldr x6, [%1, #56] \n\t" \
+ "ldr x8, [%1] \n\t" /* target->x8 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, x0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[9]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "ldr x0, [%1, #8] \n\t" \
+ "ldr x1, [%1, #16] \n\t" \
+ "ldr x2, [%1, #24] \n\t" \
+ "ldr x3, [%1, #32] \n\t" \
+ "ldr x4, [%1, #40] \n\t" \
+ "ldr x5, [%1, #48] \n\t" \
+ "ldr x6, [%1, #56] \n\t" \
+ "ldr x7, [%1, #64] \n\t" \
+ "ldr x8, [%1] \n\t" /* target->x8 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, x0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[10]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "sub sp, sp, #0x20 \n\t" \
+ "ldr x0, [%1, #8] \n\t" \
+ "ldr x1, [%1, #16] \n\t" \
+ "ldr x2, [%1, #24] \n\t" \
+ "ldr x3, [%1, #32] \n\t" \
+ "ldr x4, [%1, #40] \n\t" \
+ "ldr x5, [%1, #48] \n\t" \
+ "ldr x6, [%1, #56] \n\t" \
+ "ldr x7, [%1, #64] \n\t" \
+ "ldr x8, [%1, #72] \n\t" \
+ "str x8, [sp, #0] \n\t" \
+ "ldr x8, [%1] \n\t" /* target->x8 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, x0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[11]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "sub sp, sp, #0x20 \n\t" \
+ "ldr x0, [%1, #8] \n\t" \
+ "ldr x1, [%1, #16] \n\t" \
+ "ldr x2, [%1, #24] \n\t" \
+ "ldr x3, [%1, #32] \n\t" \
+ "ldr x4, [%1, #40] \n\t" \
+ "ldr x5, [%1, #48] \n\t" \
+ "ldr x6, [%1, #56] \n\t" \
+ "ldr x7, [%1, #64] \n\t" \
+ "ldr x8, [%1, #72] \n\t" \
+ "str x8, [sp, #0] \n\t" \
+ "ldr x8, [%1, #80] \n\t" \
+ "str x8, [sp, #8] \n\t" \
+ "ldr x8, [%1] \n\t" /* target->x8 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, x0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10,arg11) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[12]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "sub sp, sp, #0x30 \n\t" \
+ "ldr x0, [%1, #8] \n\t" \
+ "ldr x1, [%1, #16] \n\t" \
+ "ldr x2, [%1, #24] \n\t" \
+ "ldr x3, [%1, #32] \n\t" \
+ "ldr x4, [%1, #40] \n\t" \
+ "ldr x5, [%1, #48] \n\t" \
+ "ldr x6, [%1, #56] \n\t" \
+ "ldr x7, [%1, #64] \n\t" \
+ "ldr x8, [%1, #72] \n\t" \
+ "str x8, [sp, #0] \n\t" \
+ "ldr x8, [%1, #80] \n\t" \
+ "str x8, [sp, #8] \n\t" \
+ "ldr x8, [%1, #88] \n\t" \
+ "str x8, [sp, #16] \n\t" \
+ "ldr x8, [%1] \n\t" /* target->x8 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, x0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10,arg11, \
+ arg12) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[13]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ _argvec[12] = (unsigned long)(arg12); \
+ __asm__ volatile( \
+ VALGRIND_ALIGN_STACK \
+ "sub sp, sp, #0x30 \n\t" \
+ "ldr x0, [%1, #8] \n\t" \
+ "ldr x1, [%1, #16] \n\t" \
+ "ldr x2, [%1, #24] \n\t" \
+ "ldr x3, [%1, #32] \n\t" \
+ "ldr x4, [%1, #40] \n\t" \
+ "ldr x5, [%1, #48] \n\t" \
+ "ldr x6, [%1, #56] \n\t" \
+ "ldr x7, [%1, #64] \n\t" \
+ "ldr x8, [%1, #72] \n\t" \
+ "str x8, [sp, #0] \n\t" \
+ "ldr x8, [%1, #80] \n\t" \
+ "str x8, [sp, #8] \n\t" \
+ "ldr x8, [%1, #88] \n\t" \
+ "str x8, [sp, #16] \n\t" \
+ "ldr x8, [%1, #96] \n\t" \
+ "str x8, [sp, #24] \n\t" \
+ "ldr x8, [%1] \n\t" /* target->x8 */ \
+ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \
+ VALGRIND_RESTORE_STACK \
+ "mov %0, x0" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#endif /* PLAT_arm64_linux */
+
+/* ------------------------- s390x-linux ------------------------- */
+
+#if defined(PLAT_s390x_linux)
+
+/* Similar workaround as amd64 (see above), but we use r11 as frame
+ pointer and save the old r11 in r7. r11 might be used for
+ argvec, therefore we copy argvec in r1 since r1 is clobbered
+ after the call anyway. */
+#if defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM)
+# define __FRAME_POINTER \
+ ,"d"(__builtin_dwarf_cfa())
+# define VALGRIND_CFI_PROLOGUE \
+ ".cfi_remember_state\n\t" \
+ "lgr 1,%1\n\t" /* copy the argvec pointer in r1 */ \
+ "lgr 7,11\n\t" \
+ "lgr 11,%2\n\t" \
+ ".cfi_def_cfa r11, 0\n\t"
+# define VALGRIND_CFI_EPILOGUE \
+ "lgr 11, 7\n\t" \
+ ".cfi_restore_state\n\t"
+#else
+# define __FRAME_POINTER
+# define VALGRIND_CFI_PROLOGUE \
+ "lgr 1,%1\n\t"
+# define VALGRIND_CFI_EPILOGUE
+#endif
+
+/* Nb: On s390 the stack pointer is properly aligned *at all times*
+ according to the s390 GCC maintainer. (The ABI specification is not
+ precise in this regard.) Therefore, VALGRIND_ALIGN_STACK and
+ VALGRIND_RESTORE_STACK are not defined here. */
+
+/* These regs are trashed by the hidden call. Note that we overwrite
+ r14 in s390_irgen_noredir (VEX/priv/guest_s390_irgen.c) to give the
+ function a proper return address. All others are ABI defined call
+ clobbers. */
+#if defined(__VX__) || defined(__S390_VX__)
+#define __CALLER_SAVED_REGS "0", "1", "2", "3", "4", "5", "14", \
+ "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", \
+ "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15", \
+ "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23", \
+ "v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31"
+#else
+#define __CALLER_SAVED_REGS "0", "1", "2", "3", "4", "5", "14", \
+ "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7"
+#endif
+
+/* Nb: Although r11 is modified in the asm snippets below (inside
+ VALGRIND_CFI_PROLOGUE) it is not listed in the clobber section, for
+ two reasons:
+ (1) r11 is restored in VALGRIND_CFI_EPILOGUE, so effectively it is not
+ modified
+ (2) GCC will complain that r11 cannot appear inside a clobber section,
+ when compiled with -O -fno-omit-frame-pointer
+ */
+
+#define CALL_FN_W_v(lval, orig) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[1]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ "aghi 15,-160\n\t" \
+ "lg 1, 0(1)\n\t" /* target->r1 */ \
+ VALGRIND_CALL_NOREDIR_R1 \
+ "aghi 15,160\n\t" \
+ VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
+ : /*out*/ "=d" (_res) \
+ : /*in*/ "d" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+/* The call abi has the arguments in r2-r6 and stack */
+#define CALL_FN_W_W(lval, orig, arg1) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[2]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ "aghi 15,-160\n\t" \
+ "lg 2, 8(1)\n\t" \
+ "lg 1, 0(1)\n\t" \
+ VALGRIND_CALL_NOREDIR_R1 \
+ "aghi 15,160\n\t" \
+ VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
+ : /*out*/ "=d" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WW(lval, orig, arg1, arg2) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ "aghi 15,-160\n\t" \
+ "lg 2, 8(1)\n\t" \
+ "lg 3,16(1)\n\t" \
+ "lg 1, 0(1)\n\t" \
+ VALGRIND_CALL_NOREDIR_R1 \
+ "aghi 15,160\n\t" \
+ VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
+ : /*out*/ "=d" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWW(lval, orig, arg1, arg2, arg3) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[4]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ "aghi 15,-160\n\t" \
+ "lg 2, 8(1)\n\t" \
+ "lg 3,16(1)\n\t" \
+ "lg 4,24(1)\n\t" \
+ "lg 1, 0(1)\n\t" \
+ VALGRIND_CALL_NOREDIR_R1 \
+ "aghi 15,160\n\t" \
+ VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
+ : /*out*/ "=d" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWWW(lval, orig, arg1, arg2, arg3, arg4) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[5]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ "aghi 15,-160\n\t" \
+ "lg 2, 8(1)\n\t" \
+ "lg 3,16(1)\n\t" \
+ "lg 4,24(1)\n\t" \
+ "lg 5,32(1)\n\t" \
+ "lg 1, 0(1)\n\t" \
+ VALGRIND_CALL_NOREDIR_R1 \
+ "aghi 15,160\n\t" \
+ VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
+ : /*out*/ "=d" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_5W(lval, orig, arg1, arg2, arg3, arg4, arg5) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[6]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ "aghi 15,-160\n\t" \
+ "lg 2, 8(1)\n\t" \
+ "lg 3,16(1)\n\t" \
+ "lg 4,24(1)\n\t" \
+ "lg 5,32(1)\n\t" \
+ "lg 6,40(1)\n\t" \
+ "lg 1, 0(1)\n\t" \
+ VALGRIND_CALL_NOREDIR_R1 \
+ "aghi 15,160\n\t" \
+ VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
+ : /*out*/ "=d" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_6W(lval, orig, arg1, arg2, arg3, arg4, arg5, \
+ arg6) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[7]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ "aghi 15,-168\n\t" \
+ "lg 2, 8(1)\n\t" \
+ "lg 3,16(1)\n\t" \
+ "lg 4,24(1)\n\t" \
+ "lg 5,32(1)\n\t" \
+ "lg 6,40(1)\n\t" \
+ "mvc 160(8,15), 48(1)\n\t" \
+ "lg 1, 0(1)\n\t" \
+ VALGRIND_CALL_NOREDIR_R1 \
+ "aghi 15,168\n\t" \
+ VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
+ : /*out*/ "=d" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_7W(lval, orig, arg1, arg2, arg3, arg4, arg5, \
+ arg6, arg7) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[8]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ _argvec[7] = (unsigned long)arg7; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ "aghi 15,-176\n\t" \
+ "lg 2, 8(1)\n\t" \
+ "lg 3,16(1)\n\t" \
+ "lg 4,24(1)\n\t" \
+ "lg 5,32(1)\n\t" \
+ "lg 6,40(1)\n\t" \
+ "mvc 160(8,15), 48(1)\n\t" \
+ "mvc 168(8,15), 56(1)\n\t" \
+ "lg 1, 0(1)\n\t" \
+ VALGRIND_CALL_NOREDIR_R1 \
+ "aghi 15,176\n\t" \
+ VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
+ : /*out*/ "=d" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_8W(lval, orig, arg1, arg2, arg3, arg4, arg5, \
+ arg6, arg7 ,arg8) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[9]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ _argvec[7] = (unsigned long)arg7; \
+ _argvec[8] = (unsigned long)arg8; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ "aghi 15,-184\n\t" \
+ "lg 2, 8(1)\n\t" \
+ "lg 3,16(1)\n\t" \
+ "lg 4,24(1)\n\t" \
+ "lg 5,32(1)\n\t" \
+ "lg 6,40(1)\n\t" \
+ "mvc 160(8,15), 48(1)\n\t" \
+ "mvc 168(8,15), 56(1)\n\t" \
+ "mvc 176(8,15), 64(1)\n\t" \
+ "lg 1, 0(1)\n\t" \
+ VALGRIND_CALL_NOREDIR_R1 \
+ "aghi 15,184\n\t" \
+ VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
+ : /*out*/ "=d" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_9W(lval, orig, arg1, arg2, arg3, arg4, arg5, \
+ arg6, arg7 ,arg8, arg9) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[10]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ _argvec[7] = (unsigned long)arg7; \
+ _argvec[8] = (unsigned long)arg8; \
+ _argvec[9] = (unsigned long)arg9; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ "aghi 15,-192\n\t" \
+ "lg 2, 8(1)\n\t" \
+ "lg 3,16(1)\n\t" \
+ "lg 4,24(1)\n\t" \
+ "lg 5,32(1)\n\t" \
+ "lg 6,40(1)\n\t" \
+ "mvc 160(8,15), 48(1)\n\t" \
+ "mvc 168(8,15), 56(1)\n\t" \
+ "mvc 176(8,15), 64(1)\n\t" \
+ "mvc 184(8,15), 72(1)\n\t" \
+ "lg 1, 0(1)\n\t" \
+ VALGRIND_CALL_NOREDIR_R1 \
+ "aghi 15,192\n\t" \
+ VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
+ : /*out*/ "=d" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_10W(lval, orig, arg1, arg2, arg3, arg4, arg5, \
+ arg6, arg7 ,arg8, arg9, arg10) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[11]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ _argvec[7] = (unsigned long)arg7; \
+ _argvec[8] = (unsigned long)arg8; \
+ _argvec[9] = (unsigned long)arg9; \
+ _argvec[10] = (unsigned long)arg10; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ "aghi 15,-200\n\t" \
+ "lg 2, 8(1)\n\t" \
+ "lg 3,16(1)\n\t" \
+ "lg 4,24(1)\n\t" \
+ "lg 5,32(1)\n\t" \
+ "lg 6,40(1)\n\t" \
+ "mvc 160(8,15), 48(1)\n\t" \
+ "mvc 168(8,15), 56(1)\n\t" \
+ "mvc 176(8,15), 64(1)\n\t" \
+ "mvc 184(8,15), 72(1)\n\t" \
+ "mvc 192(8,15), 80(1)\n\t" \
+ "lg 1, 0(1)\n\t" \
+ VALGRIND_CALL_NOREDIR_R1 \
+ "aghi 15,200\n\t" \
+ VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
+ : /*out*/ "=d" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_11W(lval, orig, arg1, arg2, arg3, arg4, arg5, \
+ arg6, arg7 ,arg8, arg9, arg10, arg11) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[12]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ _argvec[7] = (unsigned long)arg7; \
+ _argvec[8] = (unsigned long)arg8; \
+ _argvec[9] = (unsigned long)arg9; \
+ _argvec[10] = (unsigned long)arg10; \
+ _argvec[11] = (unsigned long)arg11; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ "aghi 15,-208\n\t" \
+ "lg 2, 8(1)\n\t" \
+ "lg 3,16(1)\n\t" \
+ "lg 4,24(1)\n\t" \
+ "lg 5,32(1)\n\t" \
+ "lg 6,40(1)\n\t" \
+ "mvc 160(8,15), 48(1)\n\t" \
+ "mvc 168(8,15), 56(1)\n\t" \
+ "mvc 176(8,15), 64(1)\n\t" \
+ "mvc 184(8,15), 72(1)\n\t" \
+ "mvc 192(8,15), 80(1)\n\t" \
+ "mvc 200(8,15), 88(1)\n\t" \
+ "lg 1, 0(1)\n\t" \
+ VALGRIND_CALL_NOREDIR_R1 \
+ "aghi 15,208\n\t" \
+ VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
+ : /*out*/ "=d" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_12W(lval, orig, arg1, arg2, arg3, arg4, arg5, \
+ arg6, arg7 ,arg8, arg9, arg10, arg11, arg12)\
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[13]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)arg1; \
+ _argvec[2] = (unsigned long)arg2; \
+ _argvec[3] = (unsigned long)arg3; \
+ _argvec[4] = (unsigned long)arg4; \
+ _argvec[5] = (unsigned long)arg5; \
+ _argvec[6] = (unsigned long)arg6; \
+ _argvec[7] = (unsigned long)arg7; \
+ _argvec[8] = (unsigned long)arg8; \
+ _argvec[9] = (unsigned long)arg9; \
+ _argvec[10] = (unsigned long)arg10; \
+ _argvec[11] = (unsigned long)arg11; \
+ _argvec[12] = (unsigned long)arg12; \
+ __asm__ volatile( \
+ VALGRIND_CFI_PROLOGUE \
+ "aghi 15,-216\n\t" \
+ "lg 2, 8(1)\n\t" \
+ "lg 3,16(1)\n\t" \
+ "lg 4,24(1)\n\t" \
+ "lg 5,32(1)\n\t" \
+ "lg 6,40(1)\n\t" \
+ "mvc 160(8,15), 48(1)\n\t" \
+ "mvc 168(8,15), 56(1)\n\t" \
+ "mvc 176(8,15), 64(1)\n\t" \
+ "mvc 184(8,15), 72(1)\n\t" \
+ "mvc 192(8,15), 80(1)\n\t" \
+ "mvc 200(8,15), 88(1)\n\t" \
+ "mvc 208(8,15), 96(1)\n\t" \
+ "lg 1, 0(1)\n\t" \
+ VALGRIND_CALL_NOREDIR_R1 \
+ "aghi 15,216\n\t" \
+ VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
+ : /*out*/ "=d" (_res) \
+ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
+ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+
+#endif /* PLAT_s390x_linux */
+
+/* ------------------------- mips32-linux ----------------------- */
+
+#if defined(PLAT_mips32_linux)
+
+/* These regs are trashed by the hidden call. */
+#define __CALLER_SAVED_REGS "$2", "$3", "$4", "$5", "$6", \
+"$7", "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15", "$24", \
+"$25", "$31"
+
+/* These CALL_FN_ macros assume that on mips-linux, sizeof(unsigned
+ long) == 4. */
+
+#define CALL_FN_W_v(lval, orig) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[1]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ __asm__ volatile( \
+ "subu $29, $29, 8 \n\t" \
+ "sw $28, 0($29) \n\t" \
+ "sw $31, 4($29) \n\t" \
+ "subu $29, $29, 16 \n\t" \
+ "lw $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "addu $29, $29, 16\n\t" \
+ "lw $28, 0($29) \n\t" \
+ "lw $31, 4($29) \n\t" \
+ "addu $29, $29, 8 \n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_W(lval, orig, arg1) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[2]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ __asm__ volatile( \
+ "subu $29, $29, 8 \n\t" \
+ "sw $28, 0($29) \n\t" \
+ "sw $31, 4($29) \n\t" \
+ "subu $29, $29, 16 \n\t" \
+ "lw $4, 4(%1) \n\t" /* arg1*/ \
+ "lw $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "addu $29, $29, 16 \n\t" \
+ "lw $28, 0($29) \n\t" \
+ "lw $31, 4($29) \n\t" \
+ "addu $29, $29, 8 \n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WW(lval, orig, arg1,arg2) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ __asm__ volatile( \
+ "subu $29, $29, 8 \n\t" \
+ "sw $28, 0($29) \n\t" \
+ "sw $31, 4($29) \n\t" \
+ "subu $29, $29, 16 \n\t" \
+ "lw $4, 4(%1) \n\t" \
+ "lw $5, 8(%1) \n\t" \
+ "lw $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "addu $29, $29, 16 \n\t" \
+ "lw $28, 0($29) \n\t" \
+ "lw $31, 4($29) \n\t" \
+ "addu $29, $29, 8 \n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[4]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ __asm__ volatile( \
+ "subu $29, $29, 8 \n\t" \
+ "sw $28, 0($29) \n\t" \
+ "sw $31, 4($29) \n\t" \
+ "subu $29, $29, 16 \n\t" \
+ "lw $4, 4(%1) \n\t" \
+ "lw $5, 8(%1) \n\t" \
+ "lw $6, 12(%1) \n\t" \
+ "lw $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "addu $29, $29, 16 \n\t" \
+ "lw $28, 0($29) \n\t" \
+ "lw $31, 4($29) \n\t" \
+ "addu $29, $29, 8 \n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[5]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ __asm__ volatile( \
+ "subu $29, $29, 8 \n\t" \
+ "sw $28, 0($29) \n\t" \
+ "sw $31, 4($29) \n\t" \
+ "subu $29, $29, 16 \n\t" \
+ "lw $4, 4(%1) \n\t" \
+ "lw $5, 8(%1) \n\t" \
+ "lw $6, 12(%1) \n\t" \
+ "lw $7, 16(%1) \n\t" \
+ "lw $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "addu $29, $29, 16 \n\t" \
+ "lw $28, 0($29) \n\t" \
+ "lw $31, 4($29) \n\t" \
+ "addu $29, $29, 8 \n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[6]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ __asm__ volatile( \
+ "subu $29, $29, 8 \n\t" \
+ "sw $28, 0($29) \n\t" \
+ "sw $31, 4($29) \n\t" \
+ "lw $4, 20(%1) \n\t" \
+ "subu $29, $29, 24\n\t" \
+ "sw $4, 16($29) \n\t" \
+ "lw $4, 4(%1) \n\t" \
+ "lw $5, 8(%1) \n\t" \
+ "lw $6, 12(%1) \n\t" \
+ "lw $7, 16(%1) \n\t" \
+ "lw $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "addu $29, $29, 24 \n\t" \
+ "lw $28, 0($29) \n\t" \
+ "lw $31, 4($29) \n\t" \
+ "addu $29, $29, 8 \n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[7]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ __asm__ volatile( \
+ "subu $29, $29, 8 \n\t" \
+ "sw $28, 0($29) \n\t" \
+ "sw $31, 4($29) \n\t" \
+ "lw $4, 20(%1) \n\t" \
+ "subu $29, $29, 32\n\t" \
+ "sw $4, 16($29) \n\t" \
+ "lw $4, 24(%1) \n\t" \
+ "nop\n\t" \
+ "sw $4, 20($29) \n\t" \
+ "lw $4, 4(%1) \n\t" \
+ "lw $5, 8(%1) \n\t" \
+ "lw $6, 12(%1) \n\t" \
+ "lw $7, 16(%1) \n\t" \
+ "lw $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "addu $29, $29, 32 \n\t" \
+ "lw $28, 0($29) \n\t" \
+ "lw $31, 4($29) \n\t" \
+ "addu $29, $29, 8 \n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[8]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ __asm__ volatile( \
+ "subu $29, $29, 8 \n\t" \
+ "sw $28, 0($29) \n\t" \
+ "sw $31, 4($29) \n\t" \
+ "lw $4, 20(%1) \n\t" \
+ "subu $29, $29, 32\n\t" \
+ "sw $4, 16($29) \n\t" \
+ "lw $4, 24(%1) \n\t" \
+ "sw $4, 20($29) \n\t" \
+ "lw $4, 28(%1) \n\t" \
+ "sw $4, 24($29) \n\t" \
+ "lw $4, 4(%1) \n\t" \
+ "lw $5, 8(%1) \n\t" \
+ "lw $6, 12(%1) \n\t" \
+ "lw $7, 16(%1) \n\t" \
+ "lw $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "addu $29, $29, 32 \n\t" \
+ "lw $28, 0($29) \n\t" \
+ "lw $31, 4($29) \n\t" \
+ "addu $29, $29, 8 \n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[9]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ __asm__ volatile( \
+ "subu $29, $29, 8 \n\t" \
+ "sw $28, 0($29) \n\t" \
+ "sw $31, 4($29) \n\t" \
+ "lw $4, 20(%1) \n\t" \
+ "subu $29, $29, 40\n\t" \
+ "sw $4, 16($29) \n\t" \
+ "lw $4, 24(%1) \n\t" \
+ "sw $4, 20($29) \n\t" \
+ "lw $4, 28(%1) \n\t" \
+ "sw $4, 24($29) \n\t" \
+ "lw $4, 32(%1) \n\t" \
+ "sw $4, 28($29) \n\t" \
+ "lw $4, 4(%1) \n\t" \
+ "lw $5, 8(%1) \n\t" \
+ "lw $6, 12(%1) \n\t" \
+ "lw $7, 16(%1) \n\t" \
+ "lw $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "addu $29, $29, 40 \n\t" \
+ "lw $28, 0($29) \n\t" \
+ "lw $31, 4($29) \n\t" \
+ "addu $29, $29, 8 \n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[10]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ __asm__ volatile( \
+ "subu $29, $29, 8 \n\t" \
+ "sw $28, 0($29) \n\t" \
+ "sw $31, 4($29) \n\t" \
+ "lw $4, 20(%1) \n\t" \
+ "subu $29, $29, 40\n\t" \
+ "sw $4, 16($29) \n\t" \
+ "lw $4, 24(%1) \n\t" \
+ "sw $4, 20($29) \n\t" \
+ "lw $4, 28(%1) \n\t" \
+ "sw $4, 24($29) \n\t" \
+ "lw $4, 32(%1) \n\t" \
+ "sw $4, 28($29) \n\t" \
+ "lw $4, 36(%1) \n\t" \
+ "sw $4, 32($29) \n\t" \
+ "lw $4, 4(%1) \n\t" \
+ "lw $5, 8(%1) \n\t" \
+ "lw $6, 12(%1) \n\t" \
+ "lw $7, 16(%1) \n\t" \
+ "lw $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "addu $29, $29, 40 \n\t" \
+ "lw $28, 0($29) \n\t" \
+ "lw $31, 4($29) \n\t" \
+ "addu $29, $29, 8 \n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[11]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ __asm__ volatile( \
+ "subu $29, $29, 8 \n\t" \
+ "sw $28, 0($29) \n\t" \
+ "sw $31, 4($29) \n\t" \
+ "lw $4, 20(%1) \n\t" \
+ "subu $29, $29, 48\n\t" \
+ "sw $4, 16($29) \n\t" \
+ "lw $4, 24(%1) \n\t" \
+ "sw $4, 20($29) \n\t" \
+ "lw $4, 28(%1) \n\t" \
+ "sw $4, 24($29) \n\t" \
+ "lw $4, 32(%1) \n\t" \
+ "sw $4, 28($29) \n\t" \
+ "lw $4, 36(%1) \n\t" \
+ "sw $4, 32($29) \n\t" \
+ "lw $4, 40(%1) \n\t" \
+ "sw $4, 36($29) \n\t" \
+ "lw $4, 4(%1) \n\t" \
+ "lw $5, 8(%1) \n\t" \
+ "lw $6, 12(%1) \n\t" \
+ "lw $7, 16(%1) \n\t" \
+ "lw $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "addu $29, $29, 48 \n\t" \
+ "lw $28, 0($29) \n\t" \
+ "lw $31, 4($29) \n\t" \
+ "addu $29, $29, 8 \n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \
+ arg6,arg7,arg8,arg9,arg10, \
+ arg11) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[12]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ __asm__ volatile( \
+ "subu $29, $29, 8 \n\t" \
+ "sw $28, 0($29) \n\t" \
+ "sw $31, 4($29) \n\t" \
+ "lw $4, 20(%1) \n\t" \
+ "subu $29, $29, 48\n\t" \
+ "sw $4, 16($29) \n\t" \
+ "lw $4, 24(%1) \n\t" \
+ "sw $4, 20($29) \n\t" \
+ "lw $4, 28(%1) \n\t" \
+ "sw $4, 24($29) \n\t" \
+ "lw $4, 32(%1) \n\t" \
+ "sw $4, 28($29) \n\t" \
+ "lw $4, 36(%1) \n\t" \
+ "sw $4, 32($29) \n\t" \
+ "lw $4, 40(%1) \n\t" \
+ "sw $4, 36($29) \n\t" \
+ "lw $4, 44(%1) \n\t" \
+ "sw $4, 40($29) \n\t" \
+ "lw $4, 4(%1) \n\t" \
+ "lw $5, 8(%1) \n\t" \
+ "lw $6, 12(%1) \n\t" \
+ "lw $7, 16(%1) \n\t" \
+ "lw $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "addu $29, $29, 48 \n\t" \
+ "lw $28, 0($29) \n\t" \
+ "lw $31, 4($29) \n\t" \
+ "addu $29, $29, 8 \n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \
+ arg6,arg7,arg8,arg9,arg10, \
+ arg11,arg12) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[13]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ _argvec[12] = (unsigned long)(arg12); \
+ __asm__ volatile( \
+ "subu $29, $29, 8 \n\t" \
+ "sw $28, 0($29) \n\t" \
+ "sw $31, 4($29) \n\t" \
+ "lw $4, 20(%1) \n\t" \
+ "subu $29, $29, 56\n\t" \
+ "sw $4, 16($29) \n\t" \
+ "lw $4, 24(%1) \n\t" \
+ "sw $4, 20($29) \n\t" \
+ "lw $4, 28(%1) \n\t" \
+ "sw $4, 24($29) \n\t" \
+ "lw $4, 32(%1) \n\t" \
+ "sw $4, 28($29) \n\t" \
+ "lw $4, 36(%1) \n\t" \
+ "sw $4, 32($29) \n\t" \
+ "lw $4, 40(%1) \n\t" \
+ "sw $4, 36($29) \n\t" \
+ "lw $4, 44(%1) \n\t" \
+ "sw $4, 40($29) \n\t" \
+ "lw $4, 48(%1) \n\t" \
+ "sw $4, 44($29) \n\t" \
+ "lw $4, 4(%1) \n\t" \
+ "lw $5, 8(%1) \n\t" \
+ "lw $6, 12(%1) \n\t" \
+ "lw $7, 16(%1) \n\t" \
+ "lw $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "addu $29, $29, 56 \n\t" \
+ "lw $28, 0($29) \n\t" \
+ "lw $31, 4($29) \n\t" \
+ "addu $29, $29, 8 \n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#endif /* PLAT_mips32_linux */
+
+/* ------------------------- nanomips-linux -------------------- */
+
+#if defined(PLAT_nanomips_linux)
+
+/* These regs are trashed by the hidden call. */
+#define __CALLER_SAVED_REGS "$t4", "$t5", "$a0", "$a1", "$a2", \
+"$a3", "$a4", "$a5", "$a6", "$a7", "$t0", "$t1", "$t2", "$t3", \
+"$t8","$t9", "$at"
+
+/* These CALL_FN_ macros assume that on mips-linux, sizeof(unsigned
+ long) == 4. */
+
+#define CALL_FN_W_v(lval, orig) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[1]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_W(lval, orig, arg1) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[2]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WW(lval, orig, arg1,arg2) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[4]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ "lw $a2,12(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[5]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ "lw $a2,12(%1)\n\t" \
+ "lw $a3,16(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[6]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ "lw $a2,12(%1)\n\t" \
+ "lw $a3,16(%1)\n\t" \
+ "lw $a4,20(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[7]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ "lw $a2,12(%1)\n\t" \
+ "lw $a3,16(%1)\n\t" \
+ "lw $a4,20(%1)\n\t" \
+ "lw $a5,24(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[8]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ "lw $a2,12(%1)\n\t" \
+ "lw $a3,16(%1)\n\t" \
+ "lw $a4,20(%1)\n\t" \
+ "lw $a5,24(%1)\n\t" \
+ "lw $a6,28(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[9]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ "lw $a2,12(%1)\n\t" \
+ "lw $a3,16(%1)\n\t" \
+ "lw $a4,20(%1)\n\t" \
+ "lw $a5,24(%1)\n\t" \
+ "lw $a6,28(%1)\n\t" \
+ "lw $a7,32(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[10]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ __asm__ volatile( \
+ "addiu $sp, $sp, -16 \n\t" \
+ "lw $t9,36(%1) \n\t" \
+ "sw $t9, 0($sp) \n\t" \
+ "lw $t9, 0(%1) \n\t" \
+ "lw $a0, 4(%1) \n\t" \
+ "lw $a1, 8(%1) \n\t" \
+ "lw $a2,12(%1) \n\t" \
+ "lw $a3,16(%1) \n\t" \
+ "lw $a4,20(%1) \n\t" \
+ "lw $a5,24(%1) \n\t" \
+ "lw $a6,28(%1) \n\t" \
+ "lw $a7,32(%1) \n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0 \n\t" \
+ "addiu $sp, $sp, 16 \n\t" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[11]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ __asm__ volatile( \
+ "addiu $sp, $sp, -16 \n\t" \
+ "lw $t9,36(%1) \n\t" \
+ "sw $t9, 0($sp) \n\t" \
+ "lw $t9,40(%1) \n\t" \
+ "sw $t9, 4($sp) \n\t" \
+ "lw $t9, 0(%1) \n\t" \
+ "lw $a0, 4(%1) \n\t" \
+ "lw $a1, 8(%1) \n\t" \
+ "lw $a2,12(%1) \n\t" \
+ "lw $a3,16(%1) \n\t" \
+ "lw $a4,20(%1) \n\t" \
+ "lw $a5,24(%1) \n\t" \
+ "lw $a6,28(%1) \n\t" \
+ "lw $a7,32(%1) \n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0 \n\t" \
+ "addiu $sp, $sp, 16 \n\t" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \
+ arg6,arg7,arg8,arg9,arg10, \
+ arg11) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[12]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ __asm__ volatile( \
+ "addiu $sp, $sp, -16 \n\t" \
+ "lw $t9,36(%1) \n\t" \
+ "sw $t9, 0($sp) \n\t" \
+ "lw $t9,40(%1) \n\t" \
+ "sw $t9, 4($sp) \n\t" \
+ "lw $t9,44(%1) \n\t" \
+ "sw $t9, 8($sp) \n\t" \
+ "lw $t9, 0(%1) \n\t" \
+ "lw $a0, 4(%1) \n\t" \
+ "lw $a1, 8(%1) \n\t" \
+ "lw $a2,12(%1) \n\t" \
+ "lw $a3,16(%1) \n\t" \
+ "lw $a4,20(%1) \n\t" \
+ "lw $a5,24(%1) \n\t" \
+ "lw $a6,28(%1) \n\t" \
+ "lw $a7,32(%1) \n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0 \n\t" \
+ "addiu $sp, $sp, 16 \n\t" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \
+ arg6,arg7,arg8,arg9,arg10, \
+ arg11,arg12) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[13]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ _argvec[12] = (unsigned long)(arg12); \
+ __asm__ volatile( \
+ "addiu $sp, $sp, -16 \n\t" \
+ "lw $t9,36(%1) \n\t" \
+ "sw $t9, 0($sp) \n\t" \
+ "lw $t9,40(%1) \n\t" \
+ "sw $t9, 4($sp) \n\t" \
+ "lw $t9,44(%1) \n\t" \
+ "sw $t9, 8($sp) \n\t" \
+ "lw $t9,48(%1) \n\t" \
+ "sw $t9,12($sp) \n\t" \
+ "lw $t9, 0(%1) \n\t" \
+ "lw $a0, 4(%1) \n\t" \
+ "lw $a1, 8(%1) \n\t" \
+ "lw $a2,12(%1) \n\t" \
+ "lw $a3,16(%1) \n\t" \
+ "lw $a4,20(%1) \n\t" \
+ "lw $a5,24(%1) \n\t" \
+ "lw $a6,28(%1) \n\t" \
+ "lw $a7,32(%1) \n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0 \n\t" \
+ "addiu $sp, $sp, 16 \n\t" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#endif /* PLAT_nanomips_linux */
+
+/* ------------------------- mips64-linux ------------------------- */
+
+#if defined(PLAT_mips64_linux)
+
+/* These regs are trashed by the hidden call. */
+#define __CALLER_SAVED_REGS "$2", "$3", "$4", "$5", "$6", \
+"$7", "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15", "$24", \
+"$25", "$31"
+
+/* These CALL_FN_ macros assume that on mips64-linux,
+ sizeof(long long) == 8. */
+
+#define MIPS64_LONG2REG_CAST(x) ((long long)(long)x)
+
+#define CALL_FN_W_v(lval, orig) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long long _argvec[1]; \
+ volatile unsigned long long _res; \
+ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \
+ __asm__ volatile( \
+ "ld $25, 0(%1)\n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "0" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) (long)_res; \
+ } while (0)
+
+#define CALL_FN_W_W(lval, orig, arg1) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long long _argvec[2]; \
+ volatile unsigned long long _res; \
+ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \
+ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \
+ __asm__ volatile( \
+ "ld $4, 8(%1)\n\t" /* arg1*/ \
+ "ld $25, 0(%1)\n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) (long)_res; \
+ } while (0)
+
+#define CALL_FN_W_WW(lval, orig, arg1,arg2) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long long _argvec[3]; \
+ volatile unsigned long long _res; \
+ _argvec[0] = _orig.nraddr; \
+ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \
+ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \
+ __asm__ volatile( \
+ "ld $4, 8(%1)\n\t" \
+ "ld $5, 16(%1)\n\t" \
+ "ld $25, 0(%1)\n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) (long)_res; \
+ } while (0)
+
+
+#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long long _argvec[4]; \
+ volatile unsigned long long _res; \
+ _argvec[0] = _orig.nraddr; \
+ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \
+ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \
+ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \
+ __asm__ volatile( \
+ "ld $4, 8(%1)\n\t" \
+ "ld $5, 16(%1)\n\t" \
+ "ld $6, 24(%1)\n\t" \
+ "ld $25, 0(%1)\n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) (long)_res; \
+ } while (0)
+
+#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long long _argvec[5]; \
+ volatile unsigned long long _res; \
+ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \
+ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \
+ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \
+ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \
+ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \
+ __asm__ volatile( \
+ "ld $4, 8(%1)\n\t" \
+ "ld $5, 16(%1)\n\t" \
+ "ld $6, 24(%1)\n\t" \
+ "ld $7, 32(%1)\n\t" \
+ "ld $25, 0(%1)\n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) (long)_res; \
+ } while (0)
+
+#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long long _argvec[6]; \
+ volatile unsigned long long _res; \
+ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \
+ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \
+ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \
+ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \
+ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \
+ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \
+ __asm__ volatile( \
+ "ld $4, 8(%1)\n\t" \
+ "ld $5, 16(%1)\n\t" \
+ "ld $6, 24(%1)\n\t" \
+ "ld $7, 32(%1)\n\t" \
+ "ld $8, 40(%1)\n\t" \
+ "ld $25, 0(%1)\n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) (long)_res; \
+ } while (0)
+
+#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long long _argvec[7]; \
+ volatile unsigned long long _res; \
+ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \
+ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \
+ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \
+ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \
+ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \
+ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \
+ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \
+ __asm__ volatile( \
+ "ld $4, 8(%1)\n\t" \
+ "ld $5, 16(%1)\n\t" \
+ "ld $6, 24(%1)\n\t" \
+ "ld $7, 32(%1)\n\t" \
+ "ld $8, 40(%1)\n\t" \
+ "ld $9, 48(%1)\n\t" \
+ "ld $25, 0(%1)\n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) (long)_res; \
+ } while (0)
+
+#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long long _argvec[8]; \
+ volatile unsigned long long _res; \
+ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \
+ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \
+ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \
+ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \
+ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \
+ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \
+ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \
+ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \
+ __asm__ volatile( \
+ "ld $4, 8(%1)\n\t" \
+ "ld $5, 16(%1)\n\t" \
+ "ld $6, 24(%1)\n\t" \
+ "ld $7, 32(%1)\n\t" \
+ "ld $8, 40(%1)\n\t" \
+ "ld $9, 48(%1)\n\t" \
+ "ld $10, 56(%1)\n\t" \
+ "ld $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) (long)_res; \
+ } while (0)
+
+#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long long _argvec[9]; \
+ volatile unsigned long long _res; \
+ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \
+ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \
+ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \
+ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \
+ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \
+ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \
+ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \
+ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \
+ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \
+ __asm__ volatile( \
+ "ld $4, 8(%1)\n\t" \
+ "ld $5, 16(%1)\n\t" \
+ "ld $6, 24(%1)\n\t" \
+ "ld $7, 32(%1)\n\t" \
+ "ld $8, 40(%1)\n\t" \
+ "ld $9, 48(%1)\n\t" \
+ "ld $10, 56(%1)\n\t" \
+ "ld $11, 64(%1)\n\t" \
+ "ld $25, 0(%1) \n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) (long)_res; \
+ } while (0)
+
+#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long long _argvec[10]; \
+ volatile unsigned long long _res; \
+ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \
+ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \
+ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \
+ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \
+ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \
+ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \
+ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \
+ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \
+ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \
+ _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \
+ __asm__ volatile( \
+ "dsubu $29, $29, 8\n\t" \
+ "ld $4, 72(%1)\n\t" \
+ "sd $4, 0($29)\n\t" \
+ "ld $4, 8(%1)\n\t" \
+ "ld $5, 16(%1)\n\t" \
+ "ld $6, 24(%1)\n\t" \
+ "ld $7, 32(%1)\n\t" \
+ "ld $8, 40(%1)\n\t" \
+ "ld $9, 48(%1)\n\t" \
+ "ld $10, 56(%1)\n\t" \
+ "ld $11, 64(%1)\n\t" \
+ "ld $25, 0(%1)\n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "daddu $29, $29, 8\n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) (long)_res; \
+ } while (0)
+
+#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long long _argvec[11]; \
+ volatile unsigned long long _res; \
+ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \
+ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \
+ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \
+ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \
+ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \
+ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \
+ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \
+ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \
+ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \
+ _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \
+ _argvec[10] = MIPS64_LONG2REG_CAST(arg10); \
+ __asm__ volatile( \
+ "dsubu $29, $29, 16\n\t" \
+ "ld $4, 72(%1)\n\t" \
+ "sd $4, 0($29)\n\t" \
+ "ld $4, 80(%1)\n\t" \
+ "sd $4, 8($29)\n\t" \
+ "ld $4, 8(%1)\n\t" \
+ "ld $5, 16(%1)\n\t" \
+ "ld $6, 24(%1)\n\t" \
+ "ld $7, 32(%1)\n\t" \
+ "ld $8, 40(%1)\n\t" \
+ "ld $9, 48(%1)\n\t" \
+ "ld $10, 56(%1)\n\t" \
+ "ld $11, 64(%1)\n\t" \
+ "ld $25, 0(%1)\n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "daddu $29, $29, 16\n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) (long)_res; \
+ } while (0)
+
+#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \
+ arg6,arg7,arg8,arg9,arg10, \
+ arg11) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long long _argvec[12]; \
+ volatile unsigned long long _res; \
+ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \
+ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \
+ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \
+ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \
+ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \
+ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \
+ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \
+ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \
+ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \
+ _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \
+ _argvec[10] = MIPS64_LONG2REG_CAST(arg10); \
+ _argvec[11] = MIPS64_LONG2REG_CAST(arg11); \
+ __asm__ volatile( \
+ "dsubu $29, $29, 24\n\t" \
+ "ld $4, 72(%1)\n\t" \
+ "sd $4, 0($29)\n\t" \
+ "ld $4, 80(%1)\n\t" \
+ "sd $4, 8($29)\n\t" \
+ "ld $4, 88(%1)\n\t" \
+ "sd $4, 16($29)\n\t" \
+ "ld $4, 8(%1)\n\t" \
+ "ld $5, 16(%1)\n\t" \
+ "ld $6, 24(%1)\n\t" \
+ "ld $7, 32(%1)\n\t" \
+ "ld $8, 40(%1)\n\t" \
+ "ld $9, 48(%1)\n\t" \
+ "ld $10, 56(%1)\n\t" \
+ "ld $11, 64(%1)\n\t" \
+ "ld $25, 0(%1)\n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "daddu $29, $29, 24\n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) (long)_res; \
+ } while (0)
+
+#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \
+ arg6,arg7,arg8,arg9,arg10, \
+ arg11,arg12) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long long _argvec[13]; \
+ volatile unsigned long long _res; \
+ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \
+ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \
+ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \
+ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \
+ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \
+ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \
+ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \
+ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \
+ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \
+ _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \
+ _argvec[10] = MIPS64_LONG2REG_CAST(arg10); \
+ _argvec[11] = MIPS64_LONG2REG_CAST(arg11); \
+ _argvec[12] = MIPS64_LONG2REG_CAST(arg12); \
+ __asm__ volatile( \
+ "dsubu $29, $29, 32\n\t" \
+ "ld $4, 72(%1)\n\t" \
+ "sd $4, 0($29)\n\t" \
+ "ld $4, 80(%1)\n\t" \
+ "sd $4, 8($29)\n\t" \
+ "ld $4, 88(%1)\n\t" \
+ "sd $4, 16($29)\n\t" \
+ "ld $4, 96(%1)\n\t" \
+ "sd $4, 24($29)\n\t" \
+ "ld $4, 8(%1)\n\t" \
+ "ld $5, 16(%1)\n\t" \
+ "ld $6, 24(%1)\n\t" \
+ "ld $7, 32(%1)\n\t" \
+ "ld $8, 40(%1)\n\t" \
+ "ld $9, 48(%1)\n\t" \
+ "ld $10, 56(%1)\n\t" \
+ "ld $11, 64(%1)\n\t" \
+ "ld $25, 0(%1)\n\t" /* target->t9 */ \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "daddu $29, $29, 32\n\t" \
+ "move %0, $2\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) (long)_res; \
+ } while (0)
+
+#endif /* PLAT_mips64_linux */
+
+/* ------------------------------------------------------------------ */
+/* ARCHITECTURE INDEPENDENT MACROS for CLIENT REQUESTS. */
+/* */
+/* ------------------------------------------------------------------ */
+
+/* Some request codes. There are many more of these, but most are not
+ exposed to end-user view. These are the public ones, all of the
+ form 0x1000 + small_number.
+
+ Core ones are in the range 0x00000000--0x0000ffff. The non-public
+ ones start at 0x2000.
+*/
+
+/* These macros are used by tools -- they must be public, but don't
+ embed them into other programs. */
+#define VG_USERREQ_TOOL_BASE(a,b) \
+ ((unsigned int)(((a)&0xff) << 24 | ((b)&0xff) << 16))
+#define VG_IS_TOOL_USERREQ(a, b, v) \
+ (VG_USERREQ_TOOL_BASE(a,b) == ((v) & 0xffff0000))
+
+/* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !!
+ This enum comprises an ABI exported by Valgrind to programs
+ which use client requests. DO NOT CHANGE THE NUMERIC VALUES OF THESE
+ ENTRIES, NOR DELETE ANY -- add new ones at the end of the most
+ relevant group. */
+typedef
+ enum { VG_USERREQ__RUNNING_ON_VALGRIND = 0x1001,
+ VG_USERREQ__DISCARD_TRANSLATIONS = 0x1002,
+
+ /* These allow any function to be called from the simulated
+ CPU but run on the real CPU. Nb: the first arg passed to
+ the function is always the ThreadId of the running
+ thread! So CLIENT_CALL0 actually requires a 1 arg
+ function, etc. */
+ VG_USERREQ__CLIENT_CALL0 = 0x1101,
+ VG_USERREQ__CLIENT_CALL1 = 0x1102,
+ VG_USERREQ__CLIENT_CALL2 = 0x1103,
+ VG_USERREQ__CLIENT_CALL3 = 0x1104,
+
+ /* Can be useful in regression testing suites -- eg. can
+ send Valgrind's output to /dev/null and still count
+ errors. */
+ VG_USERREQ__COUNT_ERRORS = 0x1201,
+
+ /* Allows the client program and/or gdbserver to execute a monitor
+ command. */
+ VG_USERREQ__GDB_MONITOR_COMMAND = 0x1202,
+
+ /* Allows the client program to change a dynamic command line
+ option. */
+ VG_USERREQ__CLO_CHANGE = 0x1203,
+
+ /* These are useful and can be interpreted by any tool that
+ tracks malloc() et al, by using vg_replace_malloc.c. */
+ VG_USERREQ__MALLOCLIKE_BLOCK = 0x1301,
+ VG_USERREQ__RESIZEINPLACE_BLOCK = 0x130b,
+ VG_USERREQ__FREELIKE_BLOCK = 0x1302,
+ /* Memory pool support. */
+ VG_USERREQ__CREATE_MEMPOOL = 0x1303,
+ VG_USERREQ__DESTROY_MEMPOOL = 0x1304,
+ VG_USERREQ__MEMPOOL_ALLOC = 0x1305,
+ VG_USERREQ__MEMPOOL_FREE = 0x1306,
+ VG_USERREQ__MEMPOOL_TRIM = 0x1307,
+ VG_USERREQ__MOVE_MEMPOOL = 0x1308,
+ VG_USERREQ__MEMPOOL_CHANGE = 0x1309,
+ VG_USERREQ__MEMPOOL_EXISTS = 0x130a,
+
+ /* Allow printfs to valgrind log. */
+ /* The first two pass the va_list argument by value, which
+ assumes it is the same size as or smaller than a UWord,
+ which generally isn't the case. Hence are deprecated.
+ The second two pass the vargs by reference and so are
+ immune to this problem. */
+ /* both :: char* fmt, va_list vargs (DEPRECATED) */
+ VG_USERREQ__PRINTF = 0x1401,
+ VG_USERREQ__PRINTF_BACKTRACE = 0x1402,
+ /* both :: char* fmt, va_list* vargs */
+ VG_USERREQ__PRINTF_VALIST_BY_REF = 0x1403,
+ VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF = 0x1404,
+
+ /* Stack support. */
+ VG_USERREQ__STACK_REGISTER = 0x1501,
+ VG_USERREQ__STACK_DEREGISTER = 0x1502,
+ VG_USERREQ__STACK_CHANGE = 0x1503,
+
+ /* Wine support */
+ VG_USERREQ__LOAD_PDB_DEBUGINFO = 0x1601,
+
+ /* Querying of debug info. */
+ VG_USERREQ__MAP_IP_TO_SRCLOC = 0x1701,
+
+ /* Disable/enable error reporting level. Takes a single
+ Word arg which is the delta to this thread's error
+ disablement indicator. Hence 1 disables or further
+ disables errors, and -1 moves back towards enablement.
+ Other values are not allowed. */
+ VG_USERREQ__CHANGE_ERR_DISABLEMENT = 0x1801,
+
+ /* Some requests used for Valgrind internal, such as
+ self-test or self-hosting. */
+ /* Initialise IR injection */
+ VG_USERREQ__VEX_INIT_FOR_IRI = 0x1901,
+ /* Used by Inner Valgrind to inform Outer Valgrind where to
+ find the list of inner guest threads */
+ VG_USERREQ__INNER_THREADS = 0x1902
+ } Vg_ClientRequest;
+
+#if !defined(__GNUC__)
+# define __extension__ /* */
+#endif
+
+
+/* Returns the number of Valgrinds this code is running under. That
+ is, 0 if running natively, 1 if running under Valgrind, 2 if
+ running under Valgrind which is running under another Valgrind,
+ etc. */
+#define RUNNING_ON_VALGRIND \
+ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* if not */, \
+ VG_USERREQ__RUNNING_ON_VALGRIND, \
+ 0, 0, 0, 0, 0) \
+
+
+/* Discard translation of code in the range [_qzz_addr .. _qzz_addr +
+ _qzz_len - 1]. Useful if you are debugging a JITter or some such,
+ since it provides a way to make sure valgrind will retranslate the
+ invalidated area. Returns no value. */
+#define VALGRIND_DISCARD_TRANSLATIONS(_qzz_addr,_qzz_len) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DISCARD_TRANSLATIONS, \
+ _qzz_addr, _qzz_len, 0, 0, 0)
+
+#define VALGRIND_INNER_THREADS(_qzz_addr) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__INNER_THREADS, \
+ _qzz_addr, 0, 0, 0, 0)
+
+
+/* These requests are for getting Valgrind itself to print something.
+ Possibly with a backtrace. This is a really ugly hack. The return value
+ is the number of characters printed, excluding the "**<pid>** " part at the
+ start and the backtrace (if present). */
+
+#if defined(__GNUC__) || defined(__INTEL_COMPILER) && !defined(_MSC_VER)
+/* Modern GCC will optimize the static routine out if unused,
+ and unused attribute will shut down warnings about it. */
+static int VALGRIND_PRINTF(const char *format, ...)
+ __attribute__((format(__printf__, 1, 2), __unused__));
+#endif
+static int
+#if defined(_MSC_VER)
+__inline
+#endif
+VALGRIND_PRINTF(const char *format, ...)
+{
+#if defined(NVALGRIND)
+ (void)format;
+ return 0;
+#else /* NVALGRIND */
+#if defined(_MSC_VER) || defined(__MINGW64__)
+ uintptr_t _qzz_res;
+#else
+ unsigned long _qzz_res;
+#endif
+ va_list vargs;
+ va_start(vargs, format);
+#if defined(_MSC_VER) || defined(__MINGW64__)
+ _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0,
+ VG_USERREQ__PRINTF_VALIST_BY_REF,
+ (uintptr_t)format,
+ (uintptr_t)&vargs,
+ 0, 0, 0);
+#else
+ _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0,
+ VG_USERREQ__PRINTF_VALIST_BY_REF,
+ (unsigned long)format,
+ (unsigned long)&vargs,
+ 0, 0, 0);
+#endif
+ va_end(vargs);
+ return (int)_qzz_res;
+#endif /* NVALGRIND */
+}
+
+#if defined(__GNUC__) || defined(__INTEL_COMPILER) && !defined(_MSC_VER)
+static int VALGRIND_PRINTF_BACKTRACE(const char *format, ...)
+ __attribute__((format(__printf__, 1, 2), __unused__));
+#endif
+static int
+#if defined(_MSC_VER)
+__inline
+#endif
+VALGRIND_PRINTF_BACKTRACE(const char *format, ...)
+{
+#if defined(NVALGRIND)
+ (void)format;
+ return 0;
+#else /* NVALGRIND */
+#if defined(_MSC_VER) || defined(__MINGW64__)
+ uintptr_t _qzz_res;
+#else
+ unsigned long _qzz_res;
+#endif
+ va_list vargs;
+ va_start(vargs, format);
+#if defined(_MSC_VER) || defined(__MINGW64__)
+ _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0,
+ VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF,
+ (uintptr_t)format,
+ (uintptr_t)&vargs,
+ 0, 0, 0);
+#else
+ _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0,
+ VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF,
+ (unsigned long)format,
+ (unsigned long)&vargs,
+ 0, 0, 0);
+#endif
+ va_end(vargs);
+ return (int)_qzz_res;
+#endif /* NVALGRIND */
+}
+
+
+/* These requests allow control to move from the simulated CPU to the
+ real CPU, calling an arbitrary function.
+
+ Note that the current ThreadId is inserted as the first argument.
+ So this call:
+
+ VALGRIND_NON_SIMD_CALL2(f, arg1, arg2)
+
+ requires f to have this signature:
+
+ Word f(Word tid, Word arg1, Word arg2)
+
+ where "Word" is a word-sized type.
+
+ Note that these client requests are not entirely reliable. For example,
+ if you call a function with them that subsequently calls printf(),
+ there's a high chance Valgrind will crash. Generally, your prospects of
+ these working are made higher if the called function does not refer to
+ any global variables, and does not refer to any libc or other functions
+ (printf et al). Any kind of entanglement with libc or dynamic linking is
+ likely to have a bad outcome, for tricky reasons which we've grappled
+ with a lot in the past.
+*/
+#define VALGRIND_NON_SIMD_CALL0(_qyy_fn) \
+ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \
+ VG_USERREQ__CLIENT_CALL0, \
+ _qyy_fn, \
+ 0, 0, 0, 0)
+
+#define VALGRIND_NON_SIMD_CALL1(_qyy_fn, _qyy_arg1) \
+ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \
+ VG_USERREQ__CLIENT_CALL1, \
+ _qyy_fn, \
+ _qyy_arg1, 0, 0, 0)
+
+#define VALGRIND_NON_SIMD_CALL2(_qyy_fn, _qyy_arg1, _qyy_arg2) \
+ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \
+ VG_USERREQ__CLIENT_CALL2, \
+ _qyy_fn, \
+ _qyy_arg1, _qyy_arg2, 0, 0)
+
+#define VALGRIND_NON_SIMD_CALL3(_qyy_fn, _qyy_arg1, _qyy_arg2, _qyy_arg3) \
+ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \
+ VG_USERREQ__CLIENT_CALL3, \
+ _qyy_fn, \
+ _qyy_arg1, _qyy_arg2, \
+ _qyy_arg3, 0)
+
+
+/* Counts the number of errors that have been recorded by a tool. Nb:
+ the tool must record the errors with VG_(maybe_record_error)() or
+ VG_(unique_error)() for them to be counted. */
+#define VALGRIND_COUNT_ERRORS \
+ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ 0 /* default return */, \
+ VG_USERREQ__COUNT_ERRORS, \
+ 0, 0, 0, 0, 0)
+
+/* Several Valgrind tools (Memcheck, Massif, Helgrind, DRD) rely on knowing
+ when heap blocks are allocated in order to give accurate results. This
+ happens automatically for the standard allocator functions such as
+ malloc(), calloc(), realloc(), memalign(), new, new[], free(), delete,
+ delete[], etc.
+
+ But if your program uses a custom allocator, this doesn't automatically
+ happen, and Valgrind will not do as well. For example, if you allocate
+ superblocks with mmap() and then allocates chunks of the superblocks, all
+ Valgrind's observations will be at the mmap() level and it won't know that
+ the chunks should be considered separate entities. In Memcheck's case,
+ that means you probably won't get heap block overrun detection (because
+ there won't be redzones marked as unaddressable) and you definitely won't
+ get any leak detection.
+
+ The following client requests allow a custom allocator to be annotated so
+ that it can be handled accurately by Valgrind.
+
+ VALGRIND_MALLOCLIKE_BLOCK marks a region of memory as having been allocated
+ by a malloc()-like function. For Memcheck (an illustrative case), this
+ does two things:
+
+ - It records that the block has been allocated. This means any addresses
+ within the block mentioned in error messages will be
+ identified as belonging to the block. It also means that if the block
+ isn't freed it will be detected by the leak checker.
+
+ - It marks the block as being addressable and undefined (if 'is_zeroed' is
+ not set), or addressable and defined (if 'is_zeroed' is set). This
+ controls how accesses to the block by the program are handled.
+
+ 'addr' is the start of the usable block (ie. after any
+ redzone), 'sizeB' is its size. 'rzB' is the redzone size if the allocator
+ can apply redzones -- these are blocks of padding at the start and end of
+ each block. Adding redzones is recommended as it makes it much more likely
+ Valgrind will spot block overruns. `is_zeroed' indicates if the memory is
+ zeroed (or filled with another predictable value), as is the case for
+ calloc().
+
+ VALGRIND_MALLOCLIKE_BLOCK should be put immediately after the point where a
+ heap block -- that will be used by the client program -- is allocated.
+ It's best to put it at the outermost level of the allocator if possible;
+ for example, if you have a function my_alloc() which calls
+ internal_alloc(), and the client request is put inside internal_alloc(),
+ stack traces relating to the heap block will contain entries for both
+ my_alloc() and internal_alloc(), which is probably not what you want.
+
+ For Memcheck users: if you use VALGRIND_MALLOCLIKE_BLOCK to carve out
+ custom blocks from within a heap block, B, that has been allocated with
+ malloc/calloc/new/etc, then block B will be *ignored* during leak-checking
+ -- the custom blocks will take precedence.
+
+ VALGRIND_FREELIKE_BLOCK is the partner to VALGRIND_MALLOCLIKE_BLOCK. For
+ Memcheck, it does two things:
+
+ - It records that the block has been deallocated. This assumes that the
+ block was annotated as having been allocated via
+ VALGRIND_MALLOCLIKE_BLOCK. Otherwise, an error will be issued.
+
+ - It marks the block as being unaddressable.
+
+ VALGRIND_FREELIKE_BLOCK should be put immediately after the point where a
+ heap block is deallocated.
+
+ VALGRIND_RESIZEINPLACE_BLOCK informs a tool about reallocation. For
+ Memcheck, it does four things:
+
+ - It records that the size of a block has been changed. This assumes that
+ the block was annotated as having been allocated via
+ VALGRIND_MALLOCLIKE_BLOCK. Otherwise, an error will be issued.
+
+ - If the block shrunk, it marks the freed memory as being unaddressable.
+
+ - If the block grew, it marks the new area as undefined and defines a red
+ zone past the end of the new block.
+
+ - The V-bits of the overlap between the old and the new block are preserved.
+
+ VALGRIND_RESIZEINPLACE_BLOCK should be put after allocation of the new block
+ and before deallocation of the old block.
+
+ In many cases, these three client requests will not be enough to get your
+ allocator working well with Memcheck. More specifically, if your allocator
+ writes to freed blocks in any way then a VALGRIND_MAKE_MEM_UNDEFINED call
+ will be necessary to mark the memory as addressable just before the zeroing
+ occurs, otherwise you'll get a lot of invalid write errors. For example,
+ you'll need to do this if your allocator recycles freed blocks, but it
+ zeroes them before handing them back out (via VALGRIND_MALLOCLIKE_BLOCK).
+ Alternatively, if your allocator reuses freed blocks for allocator-internal
+ data structures, VALGRIND_MAKE_MEM_UNDEFINED calls will also be necessary.
+
+ Really, what's happening is a blurring of the lines between the client
+ program and the allocator... after VALGRIND_FREELIKE_BLOCK is called, the
+ memory should be considered unaddressable to the client program, but the
+ allocator knows more than the rest of the client program and so may be able
+ to safely access it. Extra client requests are necessary for Valgrind to
+ understand the distinction between the allocator and the rest of the
+ program.
+
+ Ignored if addr == 0.
+*/
+#define VALGRIND_MALLOCLIKE_BLOCK(addr, sizeB, rzB, is_zeroed) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MALLOCLIKE_BLOCK, \
+ addr, sizeB, rzB, is_zeroed, 0)
+
+/* See the comment for VALGRIND_MALLOCLIKE_BLOCK for details.
+ Ignored if addr == 0.
+*/
+#define VALGRIND_RESIZEINPLACE_BLOCK(addr, oldSizeB, newSizeB, rzB) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__RESIZEINPLACE_BLOCK, \
+ addr, oldSizeB, newSizeB, rzB, 0)
+
+/* See the comment for VALGRIND_MALLOCLIKE_BLOCK for details.
+ Ignored if addr == 0.
+*/
+#define VALGRIND_FREELIKE_BLOCK(addr, rzB) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__FREELIKE_BLOCK, \
+ addr, rzB, 0, 0, 0)
+
+/* Create a memory pool. */
+#define VALGRIND_CREATE_MEMPOOL(pool, rzB, is_zeroed) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CREATE_MEMPOOL, \
+ pool, rzB, is_zeroed, 0, 0)
+
+/* Create a memory pool with some flags specifying extended behaviour.
+ When flags is zero, the behaviour is identical to VALGRIND_CREATE_MEMPOOL.
+
+ The flag VALGRIND_MEMPOOL_METAPOOL specifies that the pieces of memory
+ associated with the pool using VALGRIND_MEMPOOL_ALLOC will be used
+ by the application as superblocks to dole out MALLOC_LIKE blocks using
+ VALGRIND_MALLOCLIKE_BLOCK. In other words, a meta pool is a "2 levels"
+ pool : first level is the blocks described by VALGRIND_MEMPOOL_ALLOC.
+ The second level blocks are described using VALGRIND_MALLOCLIKE_BLOCK.
+ Note that the association between the pool and the second level blocks
+ is implicit : second level blocks will be located inside first level
+ blocks. It is necessary to use the VALGRIND_MEMPOOL_METAPOOL flag
+ for such 2 levels pools, as otherwise valgrind will detect overlapping
+ memory blocks, and will abort execution (e.g. during leak search).
+
+ Such a meta pool can also be marked as an 'auto free' pool using the flag
+ VALGRIND_MEMPOOL_AUTO_FREE, which must be OR-ed together with the
+ VALGRIND_MEMPOOL_METAPOOL. For an 'auto free' pool, VALGRIND_MEMPOOL_FREE
+ will automatically free the second level blocks that are contained
+ inside the first level block freed with VALGRIND_MEMPOOL_FREE.
+ In other words, calling VALGRIND_MEMPOOL_FREE will cause implicit calls
+ to VALGRIND_FREELIKE_BLOCK for all the second level blocks included
+ in the first level block.
+ Note: it is an error to use the VALGRIND_MEMPOOL_AUTO_FREE flag
+ without the VALGRIND_MEMPOOL_METAPOOL flag.
+*/
+#define VALGRIND_MEMPOOL_AUTO_FREE 1
+#define VALGRIND_MEMPOOL_METAPOOL 2
+#define VALGRIND_CREATE_MEMPOOL_EXT(pool, rzB, is_zeroed, flags) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CREATE_MEMPOOL, \
+ pool, rzB, is_zeroed, flags, 0)
+
+/* Destroy a memory pool. */
+#define VALGRIND_DESTROY_MEMPOOL(pool) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DESTROY_MEMPOOL, \
+ pool, 0, 0, 0, 0)
+
+/* Associate a piece of memory with a memory pool. */
+#define VALGRIND_MEMPOOL_ALLOC(pool, addr, size) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_ALLOC, \
+ pool, addr, size, 0, 0)
+
+/* Disassociate a piece of memory from a memory pool. */
+#define VALGRIND_MEMPOOL_FREE(pool, addr) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_FREE, \
+ pool, addr, 0, 0, 0)
+
+/* Disassociate any pieces outside a particular range. */
+#define VALGRIND_MEMPOOL_TRIM(pool, addr, size) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_TRIM, \
+ pool, addr, size, 0, 0)
+
+/* Resize and/or move a piece associated with a memory pool. */
+#define VALGRIND_MOVE_MEMPOOL(poolA, poolB) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MOVE_MEMPOOL, \
+ poolA, poolB, 0, 0, 0)
+
+/* Resize and/or move a piece associated with a memory pool. */
+#define VALGRIND_MEMPOOL_CHANGE(pool, addrA, addrB, size) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_CHANGE, \
+ pool, addrA, addrB, size, 0)
+
+/* Return 1 if a mempool exists, else 0. */
+#define VALGRIND_MEMPOOL_EXISTS(pool) \
+ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \
+ VG_USERREQ__MEMPOOL_EXISTS, \
+ pool, 0, 0, 0, 0)
+
+/* Mark a piece of memory as being a stack. Returns a stack id.
+ start is the lowest addressable stack byte, end is the highest
+ addressable stack byte. */
+#define VALGRIND_STACK_REGISTER(start, end) \
+ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \
+ VG_USERREQ__STACK_REGISTER, \
+ start, end, 0, 0, 0)
+
+/* Unmark the piece of memory associated with a stack id as being a
+ stack. */
+#define VALGRIND_STACK_DEREGISTER(id) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__STACK_DEREGISTER, \
+ id, 0, 0, 0, 0)
+
+/* Change the start and end address of the stack id.
+ start is the new lowest addressable stack byte, end is the new highest
+ addressable stack byte. */
+#define VALGRIND_STACK_CHANGE(id, start, end) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__STACK_CHANGE, \
+ id, start, end, 0, 0)
+
+/* Load PDB debug info for Wine PE image_map. */
+#define VALGRIND_LOAD_PDB_DEBUGINFO(fd, ptr, total_size, delta) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__LOAD_PDB_DEBUGINFO, \
+ fd, ptr, total_size, delta, 0)
+
+/* Map a code address to a source file name and line number. buf64
+ must point to a 64-byte buffer in the caller's address space. The
+ result will be dumped in there and is guaranteed to be zero
+ terminated. If no info is found, the first byte is set to zero. */
+#define VALGRIND_MAP_IP_TO_SRCLOC(addr, buf64) \
+ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \
+ VG_USERREQ__MAP_IP_TO_SRCLOC, \
+ addr, buf64, 0, 0, 0)
+
+/* Disable error reporting for this thread. Behaves in a stack like
+ way, so you can safely call this multiple times provided that
+ VALGRIND_ENABLE_ERROR_REPORTING is called the same number of times
+ to re-enable reporting. The first call of this macro disables
+ reporting. Subsequent calls have no effect except to increase the
+ number of VALGRIND_ENABLE_ERROR_REPORTING calls needed to re-enable
+ reporting. Child threads do not inherit this setting from their
+ parents -- they are always created with reporting enabled. */
+#define VALGRIND_DISABLE_ERROR_REPORTING \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CHANGE_ERR_DISABLEMENT, \
+ 1, 0, 0, 0, 0)
+
+/* Re-enable error reporting, as per comments on
+ VALGRIND_DISABLE_ERROR_REPORTING. */
+#define VALGRIND_ENABLE_ERROR_REPORTING \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CHANGE_ERR_DISABLEMENT, \
+ -1, 0, 0, 0, 0)
+
+/* Execute a monitor command from the client program.
+ If a connection is opened with GDB, the output will be sent
+ according to the output mode set for vgdb.
+ If no connection is opened, output will go to the log output.
+ Returns 1 if command not recognised, 0 otherwise. */
+#define VALGRIND_MONITOR_COMMAND(command) \
+ VALGRIND_DO_CLIENT_REQUEST_EXPR(0, VG_USERREQ__GDB_MONITOR_COMMAND, \
+ command, 0, 0, 0, 0)
+
+
+/* Change the value of a dynamic command line option.
+ Note that unknown or not dynamically changeable options
+ will cause a warning message to be output. */
+#define VALGRIND_CLO_CHANGE(option) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CLO_CHANGE, \
+ option, 0, 0, 0, 0)
+
+
+#undef PLAT_x86_darwin
+#undef PLAT_amd64_darwin
+#undef PLAT_x86_win32
+#undef PLAT_amd64_win64
+#undef PLAT_x86_linux
+#undef PLAT_amd64_linux
+#undef PLAT_ppc32_linux
+#undef PLAT_ppc64be_linux
+#undef PLAT_ppc64le_linux
+#undef PLAT_arm_linux
+#undef PLAT_s390x_linux
+#undef PLAT_mips32_linux
+#undef PLAT_mips64_linux
+#undef PLAT_nanomips_linux
+#undef PLAT_x86_solaris
+#undef PLAT_amd64_solaris
+
+#endif /* __VALGRIND_H */
diff --git a/src/util/.gitignore b/src/util/.gitignore
index 648b4b116..d79786ec7 100644
--- a/src/util/.gitignore
+++ b/src/util/.gitignore
@@ -1,2 +1,12 @@
taler-config
test_payto
+taler-exchange-secmod-rsa
+taler-exchange-secmod-cs
+taler-exchange-secmod-eddsa
+test_helper_rsa
+test_helper_rsa_home/
+test_helper_cs
+test_helper_cs_home/
+test_helper_eddsa
+test_helper_eddsa_home/
+test_conversion
diff --git a/src/util/Makefile.am b/src/util/Makefile.am
index 4e22f0a08..d2504588b 100644
--- a/src/util/Makefile.am
+++ b/src/util/Makefile.am
@@ -10,11 +10,24 @@ endif
pkgcfgdir = $(prefix)/share/taler/config.d/
pkgcfg_DATA = \
- paths.conf
+ currencies.conf \
+ paths.conf \
+ taler-exchange-secmod-eddsa.conf \
+ taler-exchange-secmod-rsa.conf \
+ taler-exchange-secmod-cs.conf
EXTRA_DIST = \
- paths.conf \
- taler-config.in
+ $(pkgcfg_DATA) \
+ taler-config.in \
+ test_helper_eddsa.conf \
+ test_helper_rsa.conf \
+ test_helper_cs.conf \
+ test_conversion.sh
+
+bin_PROGRAMS = \
+ taler-exchange-secmod-eddsa \
+ taler-exchange-secmod-rsa \
+ taler-exchange-secmod-cs
bin_SCRIPTS = \
taler-config
@@ -27,42 +40,119 @@ taler-config: taler-config.in
chmod a-w+x $@.tmp && \
mv $@.tmp $@
+CLEANFILES = \
+ taler-config
+
+taler_exchange_secmod_rsa_SOURCES = \
+ taler-exchange-secmod-rsa.c taler-exchange-secmod-rsa.h \
+ secmod_common.c secmod_common.h
+taler_exchange_secmod_rsa_LDADD = \
+ libtalerutil.la \
+ -lgnunetutil \
+ -lpthread \
+ $(LIBGCRYPT_LIBS) \
+ $(XLIB)
+
+taler_exchange_secmod_cs_SOURCES = \
+ taler-exchange-secmod-cs.c taler-exchange-secmod-cs.h \
+ secmod_common.c secmod_common.h
+taler_exchange_secmod_cs_LDADD = \
+ libtalerutil.la \
+ -lgnunetutil \
+ -lpthread \
+ $(LIBGCRYPT_LIBS) \
+ $(XLIB)
+taler_exchange_secmod_eddsa_SOURCES = \
+ taler-exchange-secmod-eddsa.c taler-exchange-secmod-eddsa.h \
+ secmod_common.c secmod_common.h
+taler_exchange_secmod_eddsa_LDADD = \
+ libtalerutil.la \
+ -lgnunetutil \
+ -lpthread \
+ $(LIBGCRYPT_LIBS) \
+ $(XLIB)
lib_LTLIBRARIES = \
libtalerutil.la
libtalerutil_la_SOURCES = \
+ age_restriction.c \
amount.c \
+ aml_signatures.c \
+ auditor_signatures.c \
config.c \
+ conversion.c \
crypto.c \
+ crypto_confirmation.c \
+ crypto_contract.c \
+ crypto_helper_common.c crypto_helper_common.h \
+ crypto_helper_rsa.c \
+ crypto_helper_cs.c \
+ crypto_helper_esign.c \
crypto_wire.c \
+ denom.c \
+ exchange_signatures.c \
getopt.c \
+ lang.c \
+ iban.c \
+ merchant_signatures.c \
mhd.c \
+ offline_signatures.c \
payto.c \
+ secmod_signatures.c \
+ taler_error_codes.c \
url.c \
util.c \
+ wallet_signatures.c \
+ yna.c \
os_installation.c
libtalerutil_la_LIBADD = \
-lgnunetutil \
+ -lgnunetjson \
+ -lsodium \
+ -ljansson \
$(LIBGCRYPT_LIBS) \
- -lmicrohttpd $(XLIB)
+ -lmicrohttpd $(XLIB) \
+ -lunistring \
+ -lz \
+ -lm
libtalerutil_la_LDFLAGS = \
- -version-info 0:0:0 \
- -export-dynamic -no-undefined
+ -version-info 3:3:2 \
+ -no-undefined
+AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
+
check_PROGRAMS = \
+ test_age_restriction \
test_amount \
+ test_conversion \
test_crypto \
+ test_helper_eddsa \
+ test_helper_rsa \
+ test_helper_cs \
test_payto \
test_url
TESTS = \
$(check_PROGRAMS)
+test_age_restriction_SOURCES = \
+ test_age_restriction.c
+test_age_restriction_LDADD = \
+ -lgnunetutil \
+ libtalerutil.la
+
+test_conversion_SOURCES = \
+ test_conversion.c
+test_conversion_LDADD = \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ libtalerutil.la
test_amount_SOURCES = \
test_amount.c
@@ -73,8 +163,9 @@ test_amount_LDADD = \
test_crypto_SOURCES = \
test_crypto.c
test_crypto_LDADD = \
+ libtalerutil.la \
-lgnunetutil \
- libtalerutil.la
+ -ljansson
test_payto_SOURCES = \
test_payto.c
@@ -82,6 +173,24 @@ test_payto_LDADD = \
-lgnunetutil \
libtalerutil.la
+test_helper_eddsa_SOURCES = \
+ test_helper_eddsa.c
+test_helper_eddsa_LDADD = \
+ -lgnunetutil \
+ libtalerutil.la
+
+test_helper_rsa_SOURCES = \
+ test_helper_rsa.c
+test_helper_rsa_LDADD = \
+ -lgnunetutil \
+ libtalerutil.la
+
+test_helper_cs_SOURCES = \
+ test_helper_cs.c
+test_helper_cs_LDADD = \
+ -lgnunetutil \
+ libtalerutil.la
+
test_url_SOURCES = \
test_url.c
test_url_LDADD = \
diff --git a/src/util/age_restriction.c b/src/util/age_restriction.c
new file mode 100644
index 000000000..c2a7fc07c
--- /dev/null
+++ b/src/util/age_restriction.c
@@ -0,0 +1,795 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 util/age_restriction.c
+ * @brief Functions that are used for age restriction
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include <gnunet/gnunet_json_lib.h>
+#include <gcrypt.h>
+#include <stdint.h>
+
+struct
+#ifndef AGE_RESTRICTION_WITH_ECDSA
+GNUNET_CRYPTO_Edx25519PublicKey
+#else
+GNUNET_CRYPTO_EcdsaPublicKey
+#endif
+TALER_age_commitment_base_public_key = {
+ .q_y = { 0x64, 0x41, 0xb9, 0xbd, 0xbf, 0x14, 0x39, 0x8e,
+ 0x46, 0xeb, 0x5c, 0x1d, 0x34, 0xd3, 0x9b, 0x2f,
+ 0x9b, 0x7d, 0xc8, 0x18, 0xeb, 0x9c, 0x09, 0xfb,
+ 0x43, 0xad, 0x16, 0x64, 0xbc, 0x18, 0x49, 0xb5},
+};
+
+void
+TALER_age_commitment_hash (
+ const struct TALER_AgeCommitment *commitment,
+ struct TALER_AgeCommitmentHash *ahash)
+{
+ struct GNUNET_HashContext *hash_context;
+ struct GNUNET_HashCode hash;
+
+ GNUNET_assert (NULL != ahash);
+ if (NULL == commitment)
+ {
+ memset (ahash, 0, sizeof(struct TALER_AgeCommitmentHash));
+ return;
+ }
+
+ GNUNET_assert (__builtin_popcount (commitment->mask.bits) - 1 ==
+ (int) commitment->num);
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+
+ for (size_t i = 0; i < commitment->num; i++)
+ {
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &commitment->keys[i],
+ sizeof(commitment->keys[i]));
+ }
+
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &hash);
+ GNUNET_memcpy (&ahash->shash.bits,
+ &hash.bits,
+ sizeof(ahash->shash.bits));
+}
+
+
+/* To a given age value between 0 and 31, returns the index of the age group
+ * defined by the given mask.
+ */
+uint8_t
+TALER_get_age_group (
+ const struct TALER_AgeMask *mask,
+ uint8_t age)
+{
+ uint32_t m = mask->bits;
+ uint8_t i = 0;
+
+ while (m > 0)
+ {
+ if (0 >= age)
+ break;
+ m = m >> 1;
+ i += m & 1;
+ age--;
+ }
+ return i;
+}
+
+
+uint8_t
+TALER_get_lowest_age (
+ const struct TALER_AgeMask *mask,
+ uint8_t age)
+{
+ uint32_t m = mask->bits;
+ uint8_t group = TALER_get_age_group (mask, age);
+ uint8_t lowest = 0;
+
+ while (group > 0)
+ {
+ m = m >> 1;
+ if (m & 1)
+ group--;
+ lowest++;
+ }
+
+ return lowest;
+}
+
+
+#ifdef AGE_RESTRICTION_WITH_ECDSA
+/**
+ * @brief Helper function to generate a ECDSA private key
+ *
+ * @param seed Input seed
+ * @param size Size of the seed in bytes
+ * @param[out] pkey ECDSA private key
+ */
+static void
+ecdsa_create_from_seed (
+ const void *seed,
+ size_t seed_size,
+ struct GNUNET_CRYPTO_EcdsaPrivateKey *key)
+{
+ enum GNUNET_GenericReturnValue ret;
+
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CRYPTO_kdf (key,
+ sizeof (*key),
+ &seed,
+ seed_size,
+ "age commitment",
+ sizeof ("age commitment") - 1,
+ NULL, 0));
+ /* See GNUNET_CRYPTO_ecdsa_key_create */
+ key->d[0] &= 248;
+ key->d[31] &= 127;
+ key->d[31] |= 64;
+}
+
+
+#endif
+
+
+void
+TALER_age_restriction_commit (
+ const struct TALER_AgeMask *mask,
+ uint8_t age,
+ const struct GNUNET_HashCode *seed,
+ struct TALER_AgeCommitmentProof *ncp)
+{
+ struct GNUNET_HashCode seed_i;
+ uint8_t num_pub;
+ uint8_t num_priv;
+ size_t i;
+
+ GNUNET_assert (NULL != mask);
+ GNUNET_assert (NULL != seed);
+ GNUNET_assert (NULL != ncp);
+ GNUNET_assert (mask->bits & 1); /* first bit must have been set */
+
+ num_pub = __builtin_popcount (mask->bits) - 1;
+ num_priv = TALER_get_age_group (mask, age);
+
+ GNUNET_assert (31 > num_priv);
+ GNUNET_assert (num_priv <= num_pub);
+
+ seed_i = *seed;
+ ncp->commitment.mask.bits = mask->bits;
+ ncp->commitment.num = num_pub;
+ ncp->proof.num = num_priv;
+ ncp->proof.keys = NULL;
+
+ ncp->commitment.keys = GNUNET_new_array (
+ num_pub,
+ struct TALER_AgeCommitmentPublicKeyP);
+
+ if (0 < num_priv)
+ ncp->proof.keys = GNUNET_new_array (
+ num_priv,
+ struct TALER_AgeCommitmentPrivateKeyP);
+
+ /* Create as many private keys as we need and fill the rest of the
+ * public keys with valid curve points.
+ * We need to make sure that the public keys are proper points on the
+ * elliptic curve, so we can't simply fill the struct with random values. */
+ for (i = 0; i < num_pub; i++)
+ {
+ struct TALER_AgeCommitmentPrivateKeyP key = {0};
+ struct TALER_AgeCommitmentPrivateKeyP *pkey = &key;
+
+ /* Only save the private keys for age groups less than num_priv */
+ if (i < num_priv)
+ pkey = &ncp->proof.keys[i];
+
+#ifndef AGE_RESTRICTION_WITH_ECDSA
+ GNUNET_CRYPTO_edx25519_key_create_from_seed (&seed_i,
+ sizeof(seed_i),
+ &pkey->priv);
+ GNUNET_CRYPTO_edx25519_key_get_public (&pkey->priv,
+ &ncp->commitment.keys[i].pub);
+#else
+ ecdsa_create_from_seed (&seed_i,
+ sizeof(seed_i),
+ &pkey->priv);
+ GNUNET_CRYPTO_ecdsa_key_get_public (&pkey->priv,
+ &ncp->commitment.keys[i].pub);
+#endif
+
+ seed_i.bits[0] += 1;
+ }
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_age_commitment_derive (
+ const struct TALER_AgeCommitmentProof *orig,
+ const struct GNUNET_HashCode *salt,
+ struct TALER_AgeCommitmentProof *newacp)
+{
+ GNUNET_assert (NULL != newacp);
+ GNUNET_assert (orig->proof.num <=
+ orig->commitment.num);
+ GNUNET_assert (((int) orig->commitment.num) ==
+ __builtin_popcount (orig->commitment.mask.bits) - 1);
+
+ newacp->commitment.mask = orig->commitment.mask;
+ newacp->commitment.num = orig->commitment.num;
+ newacp->commitment.keys = GNUNET_new_array (
+ newacp->commitment.num,
+ struct TALER_AgeCommitmentPublicKeyP);
+
+ newacp->proof.num = orig->proof.num;
+ newacp->proof.keys = NULL;
+ if (0 != newacp->proof.num)
+ newacp->proof.keys = GNUNET_new_array (
+ newacp->proof.num,
+ struct TALER_AgeCommitmentPrivateKeyP);
+
+#ifndef AGE_RESTRICTION_WITH_ECDSA
+ /* 1. Derive the public keys */
+ for (size_t i = 0; i < orig->commitment.num; i++)
+ {
+ GNUNET_CRYPTO_edx25519_public_key_derive (
+ &orig->commitment.keys[i].pub,
+ salt,
+ sizeof(*salt),
+ &newacp->commitment.keys[i].pub);
+ }
+
+ /* 2. Derive the private keys */
+ for (size_t i = 0; i < orig->proof.num; i++)
+ {
+ GNUNET_CRYPTO_edx25519_private_key_derive (
+ &orig->proof.keys[i].priv,
+ salt,
+ sizeof(*salt),
+ &newacp->proof.keys[i].priv);
+ }
+#else
+ {
+ const char *label = GNUNET_h2s (salt);
+
+ /* 1. Derive the public keys */
+ for (size_t i = 0; i < orig->commitment.num; i++)
+ {
+ GNUNET_CRYPTO_ecdsa_public_key_derive (
+ &orig->commitment.keys[i].pub,
+ label,
+ "age commitment derive",
+ &newacp->commitment.keys[i].pub);
+ }
+
+ /* 2. Derive the private keys */
+ for (size_t i = 0; i < orig->proof.num; i++)
+ {
+ struct GNUNET_CRYPTO_EcdsaPrivateKey *priv;
+ priv = GNUNET_CRYPTO_ecdsa_private_key_derive (
+ &orig->proof.keys[i].priv,
+ label,
+ "age commitment derive");
+ newacp->proof.keys[i].priv = *priv;
+ GNUNET_free (priv);
+ }
+ }
+#endif
+
+ return GNUNET_OK;
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Age group mask in network byte order.
+ */
+struct TALER_AgeMaskNBO
+{
+ uint32_t bits_nbo;
+};
+
+/**
+ * Used for attestation of a particular age
+ */
+struct TALER_AgeAttestationPS
+{
+ /**
+ * Purpose must be #TALER_SIGNATURE_WALLET_AGE_ATTESTATION.
+ * (no GNUNET_PACKED here because the struct is already packed)
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Age mask that defines the underlying age groups
+ */
+ struct TALER_AgeMaskNBO mask GNUNET_PACKED;
+
+ /**
+ * The particular age that this attestation is for.
+ * We use uint32_t here for alignment.
+ */
+ uint32_t age GNUNET_PACKED;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum GNUNET_GenericReturnValue
+TALER_age_commitment_attest (
+ const struct TALER_AgeCommitmentProof *cp,
+ uint8_t age,
+ struct TALER_AgeAttestation *attest)
+{
+ uint8_t group;
+
+ GNUNET_assert (NULL != attest);
+ GNUNET_assert (NULL != cp);
+
+ group = TALER_get_age_group (&cp->commitment.mask,
+ age);
+
+ GNUNET_assert (group < 32);
+
+ if (0 == group)
+ {
+ /* Age group 0 means: no attestation necessary.
+ * We set the signature to zero and communicate success. */
+ memset (attest,
+ 0,
+ sizeof(struct TALER_AgeAttestation));
+ return GNUNET_OK;
+ }
+
+ if (group > cp->proof.num)
+ return GNUNET_NO;
+
+ {
+ struct TALER_AgeAttestationPS at = {
+ .purpose.size = htonl (sizeof(at)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_AGE_ATTESTATION),
+ .mask.bits_nbo = htonl (cp->commitment.mask.bits),
+ .age = htonl (age),
+ };
+
+#ifndef AGE_RESTRICTION_WITH_ECDSA
+ #define sign(a,b,c) GNUNET_CRYPTO_edx25519_sign (a,b,c)
+#else
+ #define sign(a,b,c) GNUNET_CRYPTO_ecdsa_sign (a,b,c)
+#endif
+ sign (&cp->proof.keys[group - 1].priv,
+ &at,
+ &attest->signature);
+ }
+#undef sign
+
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_age_commitment_verify (
+ const struct TALER_AgeCommitment *comm,
+ uint8_t age,
+ const struct TALER_AgeAttestation *attest)
+{
+ uint8_t group;
+
+ GNUNET_assert (NULL != attest);
+ GNUNET_assert (NULL != comm);
+
+ group = TALER_get_age_group (&comm->mask,
+ age);
+
+ GNUNET_assert (group < 32);
+
+ /* Age group 0 means: no attestation necessary. */
+ if (0 == group)
+ return GNUNET_OK;
+
+ if (group > comm->num)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_NO;
+ }
+
+ {
+ struct TALER_AgeAttestationPS at = {
+ .purpose.size = htonl (sizeof(at)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_AGE_ATTESTATION),
+ .mask.bits_nbo = htonl (comm->mask.bits),
+ .age = htonl (age),
+ };
+
+#ifndef AGE_RESTRICTION_WITH_ECDSA
+ #define verify(a,b,c,d) GNUNET_CRYPTO_edx25519_verify ((a),(b),(c),(d))
+#else
+ #define verify(a,b,c,d) GNUNET_CRYPTO_ecdsa_verify ((a),(b),(c),(d))
+#endif
+ return verify (TALER_SIGNATURE_WALLET_AGE_ATTESTATION,
+ &at,
+ &attest->signature,
+ &comm->keys[group - 1].pub);
+ }
+#undef verify
+}
+
+
+void
+TALER_age_commitment_free (
+ struct TALER_AgeCommitment *commitment)
+{
+ if (NULL == commitment)
+ return;
+
+ if (NULL != commitment->keys)
+ {
+ GNUNET_free (commitment->keys);
+ commitment->keys = NULL;
+ }
+ GNUNET_free (commitment);
+}
+
+
+void
+TALER_age_proof_free (
+ struct TALER_AgeProof *proof)
+{
+ if (NULL == proof)
+ return;
+
+ if (NULL != proof->keys)
+ {
+ GNUNET_CRYPTO_zero_keys (
+ proof->keys,
+ sizeof(*proof->keys) * proof->num);
+
+ GNUNET_free (proof->keys);
+ proof->keys = NULL;
+ }
+ GNUNET_free (proof);
+}
+
+
+void
+TALER_age_commitment_proof_free (
+ struct TALER_AgeCommitmentProof *acp)
+{
+ if (NULL == acp)
+ return;
+
+ if (NULL != acp->proof.keys)
+ {
+ GNUNET_CRYPTO_zero_keys (
+ acp->proof.keys,
+ sizeof(*acp->proof.keys) * acp->proof.num);
+
+ GNUNET_free (acp->proof.keys);
+ acp->proof.keys = NULL;
+ }
+
+ if (NULL != acp->commitment.keys)
+ {
+ GNUNET_free (acp->commitment.keys);
+ acp->commitment.keys = NULL;
+ }
+}
+
+
+struct TALER_AgeCommitmentProof *
+TALER_age_commitment_proof_duplicate (
+ const struct TALER_AgeCommitmentProof *acp)
+{
+ struct TALER_AgeCommitmentProof *nacp;
+
+ GNUNET_assert (NULL != acp);
+ GNUNET_assert (__builtin_popcount (acp->commitment.mask.bits) - 1 ==
+ (int) acp->commitment.num);
+
+ nacp = GNUNET_new (struct TALER_AgeCommitmentProof);
+
+ TALER_age_commitment_proof_deep_copy (acp,nacp);
+ return nacp;
+}
+
+
+void
+TALER_age_commitment_proof_deep_copy (
+ const struct TALER_AgeCommitmentProof *acp,
+ struct TALER_AgeCommitmentProof *nacp)
+{
+ GNUNET_assert (NULL != acp);
+ GNUNET_assert (__builtin_popcount (acp->commitment.mask.bits) - 1 ==
+ (int) acp->commitment.num);
+
+ *nacp = *acp;
+ nacp->commitment.keys =
+ GNUNET_new_array (acp->commitment.num,
+ struct TALER_AgeCommitmentPublicKeyP);
+ nacp->proof.keys =
+ GNUNET_new_array (acp->proof.num,
+ struct TALER_AgeCommitmentPrivateKeyP);
+
+ for (size_t i = 0; i < acp->commitment.num; i++)
+ nacp->commitment.keys[i] = acp->commitment.keys[i];
+
+ for (size_t i = 0; i < acp->proof.num; i++)
+ nacp->proof.keys[i] = acp->proof.keys[i];
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_parse_age_groups (const json_t *root,
+ struct TALER_AgeMask *mask)
+{
+ enum GNUNET_GenericReturnValue ret;
+ const char *str;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("age_groups",
+ &str),
+ GNUNET_JSON_spec_end ()
+ };
+
+ ret = GNUNET_JSON_parse (root,
+ spec,
+ NULL,
+ NULL);
+ if (GNUNET_OK == ret)
+ TALER_parse_age_group_string (str, mask);
+
+ GNUNET_JSON_parse_free (spec);
+
+ return ret;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_parse_age_group_string (
+ const char *groups,
+ struct TALER_AgeMask *mask)
+{
+
+ const char *pos = groups;
+ unsigned int prev = 0;
+ unsigned int val = 0;
+ char c;
+
+ /* reset mask */
+ mask->bits = 0;
+
+ while (*pos)
+ {
+ c = *pos++;
+ if (':' == c)
+ {
+ if (prev >= val)
+ return GNUNET_SYSERR;
+
+ mask->bits |= 1 << val;
+ prev = val;
+ val = 0;
+ continue;
+ }
+
+ if ('0'>c || '9'<c)
+ return GNUNET_SYSERR;
+
+ val = 10 * val + c - '0';
+
+ if (0>=val || 32<=val)
+ return GNUNET_SYSERR;
+ }
+
+ if (32<=val || prev>=val)
+ return GNUNET_SYSERR;
+
+ mask->bits |= (1 << val);
+ mask->bits |= 1; // mark zeroth group, too
+
+ return GNUNET_OK;
+}
+
+
+const char *
+TALER_age_mask_to_string (
+ const struct TALER_AgeMask *mask)
+{
+ static char buf[256] = {0};
+ uint32_t bits = mask->bits;
+ unsigned int n = 0;
+ char *pos = buf;
+
+ memset (buf, 0, sizeof(buf));
+
+ while (bits != 0)
+ {
+ bits >>= 1;
+ n++;
+ if (0 == (bits & 1))
+ {
+ continue;
+ }
+
+ if (n > 9)
+ {
+ *(pos++) = '0' + n / 10;
+ }
+ *(pos++) = '0' + n % 10;
+
+ if (0 != (bits >> 1))
+ {
+ *(pos++) = ':';
+ }
+ }
+ return buf;
+}
+
+
+void
+TALER_age_restriction_from_secret (
+ const struct TALER_PlanchetMasterSecretP *secret,
+ const struct TALER_AgeMask *mask,
+ const uint8_t max_age,
+ struct TALER_AgeCommitmentProof *ncp)
+{
+ struct GNUNET_HashCode seed_i = {0};
+ uint8_t num_pub;
+ uint8_t num_priv;
+
+ GNUNET_assert (NULL != mask);
+ GNUNET_assert (NULL != secret);
+ GNUNET_assert (NULL != ncp);
+ GNUNET_assert (mask->bits & 1); /* fist bit must have been set */
+
+ num_pub = __builtin_popcount (mask->bits) - 1;
+ num_priv = TALER_get_age_group (mask, max_age);
+
+ GNUNET_assert (31 > num_priv);
+ GNUNET_assert (num_priv <= num_pub);
+
+ ncp->commitment.mask.bits = mask->bits;
+ ncp->commitment.num = num_pub;
+ ncp->proof.num = num_priv;
+ ncp->proof.keys = NULL;
+ ncp->commitment.keys = GNUNET_new_array (
+ num_pub,
+ struct TALER_AgeCommitmentPublicKeyP);
+ if (0 < num_priv)
+ ncp->proof.keys = GNUNET_new_array (
+ num_priv,
+ struct TALER_AgeCommitmentPrivateKeyP);
+
+ /* Create as many private keys as allow with max_age and derive the
+ * corresponding public keys. The rest of the needed public keys are created
+ * by scalar multiplication with the TALER_age_commitment_base_public_key. */
+ for (size_t i = 0; i < num_pub; i++)
+ {
+ enum GNUNET_GenericReturnValue ret;
+ const char *label = i < num_priv ? "age-commitment" : "age-factor";
+
+ ret = GNUNET_CRYPTO_kdf (&seed_i, sizeof(seed_i),
+ secret, sizeof(*secret),
+ label, strlen (label),
+ &i, sizeof(i),
+ NULL, 0);
+ GNUNET_assert (GNUNET_OK == ret);
+
+ /* Only generate and save the private keys and public keys for age groups
+ * less than num_priv */
+ if (i < num_priv)
+ {
+ struct TALER_AgeCommitmentPrivateKeyP *pkey = &ncp->proof.keys[i];
+
+#ifndef AGE_RESTRICTION_WITH_ECDSA
+ GNUNET_CRYPTO_edx25519_key_create_from_seed (&seed_i,
+ sizeof(seed_i),
+ &pkey->priv);
+ GNUNET_CRYPTO_edx25519_key_get_public (&pkey->priv,
+ &ncp->commitment.keys[i].pub);
+#else
+ ecdsa_create_from_seed (&seed_i,
+ sizeof(seed_i),
+ &pkey->priv);
+ GNUNET_CRYPTO_ecdsa_key_get_public (&pkey->priv,
+ &ncp->commitment.keys[i].pub);
+#endif
+ }
+ else
+ {
+ /* For all indices larger than num_priv, derive a public key from
+ * TALER_age_commitment_base_public_key by scalar multiplication */
+#ifndef AGE_RESTRICTION_WITH_ECDSA
+ GNUNET_CRYPTO_edx25519_public_key_derive (
+ &TALER_age_commitment_base_public_key,
+ &seed_i,
+ sizeof(seed_i),
+ &ncp->commitment.keys[i].pub);
+#else
+
+ GNUNET_CRYPTO_ecdsa_public_key_derive (
+ &TALER_age_commitment_base_public_key,
+ GNUNET_h2s (&seed_i),
+ "age withdraw",
+ &ncp->commitment.keys[i].pub);
+#endif
+ }
+ }
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_parse_coarse_date (
+ const char *in,
+ const struct TALER_AgeMask *mask,
+ uint32_t *out)
+{
+ struct tm date = {0};
+ struct tm limit = {0};
+ time_t seconds;
+
+ if (NULL == in)
+ {
+ /* FIXME[oec]: correct behaviour? */
+ *out = 0;
+ return GNUNET_OK;
+ }
+
+ GNUNET_assert (NULL !=mask);
+ GNUNET_assert (NULL !=out);
+
+ if (NULL == strptime (in, "%Y-%m-%d", &date))
+ {
+ if (NULL == strptime (in, "%Y-%m-00", &date))
+ if (NULL == strptime (in, "%Y-00-00", &date))
+ return GNUNET_SYSERR;
+ /* turns out that the day is off by one in the last two cases */
+ date.tm_mday += 1;
+ }
+
+ seconds = timegm (&date);
+ if (-1 == seconds)
+ return GNUNET_SYSERR;
+
+ /* calculate the limit date for the largest age group */
+ {
+ time_t l = time (NULL);
+ localtime_r (&l, &limit);
+ }
+ limit.tm_year -= TALER_adult_age (mask);
+ GNUNET_assert (-1 != timegm (&limit));
+
+ if ((limit.tm_year < date.tm_year)
+ || ((limit.tm_year == date.tm_year)
+ && (limit.tm_mon < date.tm_mon))
+ || ((limit.tm_year == date.tm_year)
+ && (limit.tm_mon == date.tm_mon)
+ && (limit.tm_mday < date.tm_mday)))
+ *out = seconds / 60 / 60 / 24;
+ else
+ *out = 0;
+
+ return GNUNET_OK;
+}
+
+
+/* end util/age_restriction.c */
diff --git a/src/util/aml_signatures.c b/src/util/aml_signatures.c
new file mode 100644
index 000000000..a61646c0d
--- /dev/null
+++ b/src/util/aml_signatures.c
@@ -0,0 +1,201 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file aml_signatures.c
+ * @brief Utility functions for AML officers
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature on an AML decision.
+ */
+struct TALER_AmlDecisionPS
+{
+ /**
+ * Purpose must be #TALER_SIGNATURE_AML_DECISION.
+ * Used for an EdDSA signature with the `struct TALER_AmlOfficerPublicKeyP`.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash over the justification text.
+ */
+ struct GNUNET_HashCode h_justification GNUNET_PACKED;
+
+ /**
+ * Time when this decision was made.
+ */
+ struct GNUNET_TIME_TimestampNBO decision_time;
+
+ /**
+ * New threshold for triggering possibly a new AML process.
+ */
+ struct TALER_AmountNBO new_threshold;
+
+ /**
+ * Hash of the account identifier to which the decision applies.
+ */
+ struct TALER_PaytoHashP h_payto GNUNET_PACKED;
+
+ /**
+ * Hash over JSON array with KYC requirements that were imposed. All zeros
+ * for none.
+ */
+ struct GNUNET_HashCode h_kyc_requirements;
+
+ /**
+ * What is the new AML status?
+ */
+ uint32_t new_state GNUNET_PACKED;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_officer_aml_decision_sign (
+ const char *justification,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const struct TALER_Amount *new_threshold,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_AmlDecisionState new_state,
+ const json_t *kyc_requirements,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ struct TALER_AmlOfficerSignatureP *officer_sig)
+{
+ struct TALER_AmlDecisionPS ad = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_AML_DECISION),
+ .purpose.size = htonl (sizeof (ad)),
+ .decision_time = GNUNET_TIME_timestamp_hton (decision_time),
+ .h_payto = *h_payto,
+ .new_state = htonl ((uint32_t) new_state)
+ };
+
+ GNUNET_CRYPTO_hash (justification,
+ strlen (justification),
+ &ad.h_justification);
+ TALER_amount_hton (&ad.new_threshold,
+ new_threshold);
+ if (NULL != kyc_requirements)
+ TALER_json_hash (kyc_requirements,
+ &ad.h_kyc_requirements);
+ GNUNET_CRYPTO_eddsa_sign (&officer_priv->eddsa_priv,
+ &ad,
+ &officer_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_officer_aml_decision_verify (
+ const char *justification,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const struct TALER_Amount *new_threshold,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_AmlDecisionState new_state,
+ const json_t *kyc_requirements,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const struct TALER_AmlOfficerSignatureP *officer_sig)
+{
+ struct TALER_AmlDecisionPS ad = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_AML_DECISION),
+ .purpose.size = htonl (sizeof (ad)),
+ .decision_time = GNUNET_TIME_timestamp_hton (decision_time),
+ .h_payto = *h_payto,
+ .new_state = htonl ((uint32_t) new_state)
+ };
+
+ GNUNET_CRYPTO_hash (justification,
+ strlen (justification),
+ &ad.h_justification);
+ TALER_amount_hton (&ad.new_threshold,
+ new_threshold);
+ if (NULL != kyc_requirements)
+ TALER_json_hash (kyc_requirements,
+ &ad.h_kyc_requirements);
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_AML_DECISION,
+ &ad,
+ &officer_sig->eddsa_signature,
+ &officer_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature on any AML query.
+ */
+struct TALER_AmlQueryPS
+{
+ /**
+ * Purpose must be #TALER_SIGNATURE_AML_QUERY.
+ * Used for an EdDSA signature with the `struct TALER_AmlOfficerPublicKeyP`.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_officer_aml_query_sign (
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ struct TALER_AmlOfficerSignatureP *officer_sig)
+{
+ struct TALER_AmlQueryPS aq = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_AML_QUERY),
+ .purpose.size = htonl (sizeof (aq))
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&officer_priv->eddsa_priv,
+ &aq,
+ &officer_sig->eddsa_signature);
+}
+
+
+/**
+ * Verify AML query authorization.
+ *
+ * @param officer_pub public key of AML officer
+ * @param officer_sig signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_officer_aml_query_verify (
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const struct TALER_AmlOfficerSignatureP *officer_sig)
+{
+ struct TALER_AmlQueryPS aq = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_AML_QUERY),
+ .purpose.size = htonl (sizeof (aq))
+ };
+
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_AML_QUERY,
+ &aq,
+ &officer_sig->eddsa_signature,
+ &officer_pub->eddsa_pub);
+}
+
+
+/* end of aml_signatures.c */
diff --git a/src/util/amount.c b/src/util/amount.c
index f96ab9c49..cce84d73a 100644
--- a/src/util/amount.c
+++ b/src/util/amount.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014 Taler Systems SA
+ 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
@@ -24,11 +24,6 @@
#include "platform.h"
#include "taler_util.h"
-/**
- * Maximum legal 'value' for an amount, based on IEEE double (for JavaScript compatibility).
- */
-#define MAX_AMOUNT_VALUE (1LLU << 52)
-
/**
* Set @a a to "invalid".
@@ -44,15 +39,32 @@ invalidate (struct TALER_Amount *a)
}
-/**
- * Parse monetary amount, in the format "T:V.F".
- *
- * @param str amount string
- * @param[out] amount amount to write the result to
- * @return #GNUNET_OK if the string is a valid monetary amount specification,
- * #GNUNET_SYSERR if it is invalid.
- */
-int
+enum GNUNET_GenericReturnValue
+TALER_check_currency (const char *str)
+{
+ if (strlen (str) >= TALER_CURRENCY_LEN)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Currency code name `%s' is too long\n",
+ str);
+ return GNUNET_SYSERR;
+ }
+ /* validate str has only legal characters in it! */
+ for (unsigned int i = 0; '\0' != str[i]; i++)
+ {
+ if ( ('A' > str[i]) || ('Z' < str[i]) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Currency code name `%s' contains illegal characters (only A-Z allowed)\n",
+ str);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
TALER_string_to_amount (const char *str,
struct TALER_Amount *amount)
{
@@ -75,6 +87,7 @@ TALER_string_to_amount (const char *str,
/* parse currency */
colon = strchr (str, (int) ':');
if ( (NULL == colon) ||
+ (colon == str) ||
((colon - str) >= TALER_CURRENCY_LEN) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -85,14 +98,15 @@ TALER_string_to_amount (const char *str,
}
GNUNET_assert (TALER_CURRENCY_LEN > (colon - str));
- memcpy (amount->currency,
- str,
- colon - str);
+ for (unsigned int i = 0; i<colon - str; i++)
+ amount->currency[i] = str[i];
/* 0-terminate *and* normalize buffer by setting everything to '\0' */
memset (&amount->currency [colon - str],
0,
TALER_CURRENCY_LEN - (colon - str));
-
+ if (GNUNET_OK !=
+ TALER_check_currency (amount->currency))
+ return GNUNET_SYSERR;
/* skip colon */
value = colon + 1;
if ('\0' == value[0])
@@ -126,8 +140,10 @@ TALER_string_to_amount (const char *str,
return GNUNET_SYSERR;
}
n = *value - '0';
- if ( (amount->value * 10 + n < amount->value) ||
- (amount->value > MAX_AMOUNT_VALUE) )
+ if ( (amount->value * 10 < amount->value) ||
+ (amount->value * 10 + n < amount->value) ||
+ (amount->value > TALER_AMOUNT_MAX_VALUE) ||
+ (amount->value * 10 + n > TALER_AMOUNT_MAX_VALUE) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Value specified in amount `%s' is too large\n",
@@ -180,16 +196,7 @@ TALER_string_to_amount (const char *str,
}
-/**
- * Parse monetary amount, in the format "T:V.F".
- * The result is stored in network byte order (NBO).
- *
- * @param str amount string
- * @param[out] amount_nbo amount to write the result to
- * @return #GNUNET_OK if the string is a valid amount specification,
- * #GNUNET_SYSERR if it is invalid.
- */
-int
+enum GNUNET_GenericReturnValue
TALER_string_to_amount_nbo (const char *str,
struct TALER_AmountNBO *amount_nbo)
{
@@ -205,12 +212,6 @@ TALER_string_to_amount_nbo (const char *str,
}
-/**
- * Convert amount from host to network representation.
- *
- * @param res where to store amount in network representation
- * @param[out] d amount in host representation
- */
void
TALER_amount_hton (struct TALER_AmountNBO *res,
const struct TALER_Amount *d)
@@ -219,69 +220,79 @@ TALER_amount_hton (struct TALER_AmountNBO *res,
TALER_amount_is_valid (d));
res->value = GNUNET_htonll (d->value);
res->fraction = htonl (d->fraction);
- memcpy (res->currency,
- d->currency,
- TALER_CURRENCY_LEN);
+ for (unsigned int i = 0; i<TALER_CURRENCY_LEN; i++)
+ res->currency[i] = d->currency[i];
}
-/**
- * Convert amount from network to host representation.
- *
- * @param[out] res where to store amount in host representation
- * @param dn amount in network representation
- */
void
TALER_amount_ntoh (struct TALER_Amount *res,
const struct TALER_AmountNBO *dn)
{
res->value = GNUNET_ntohll (dn->value);
res->fraction = ntohl (dn->fraction);
- memcpy (res->currency,
- dn->currency,
- TALER_CURRENCY_LEN);
+ GNUNET_memcpy (res->currency,
+ dn->currency,
+ TALER_CURRENCY_LEN);
GNUNET_assert (GNUNET_YES ==
TALER_amount_is_valid (res));
}
-/**
- * Get the value of "zero" in a particular currency.
- *
- * @param cur currency description
- * @param[out] amount amount to write the result to
- * @return #GNUNET_OK if @a cur is a valid currency specification,
- * #GNUNET_SYSERR if it is invalid.
- */
-int
-TALER_amount_get_zero (const char *cur,
+enum GNUNET_GenericReturnValue
+TALER_amount_set_zero (const char *cur,
struct TALER_Amount *amount)
{
size_t slen;
- slen = strlen (cur);
- if (slen >= TALER_CURRENCY_LEN)
+ if (GNUNET_OK !=
+ TALER_check_currency (cur))
return GNUNET_SYSERR;
+ slen = strlen (cur);
memset (amount,
0,
sizeof (struct TALER_Amount));
- memcpy (amount->currency,
- cur,
- slen);
+ for (unsigned int i = 0; i<slen; i++)
+ amount->currency[i] = cur[i];
return GNUNET_OK;
}
-/**
- * Test if the given amount is valid.
- *
- * @param amount amount to check
- * @return #GNUNET_OK if @a amount is valid
- */
-int
+enum GNUNET_GenericReturnValue
TALER_amount_is_valid (const struct TALER_Amount *amount)
{
- return ('\0' != amount->currency[0]);
+ if (amount->value > TALER_AMOUNT_MAX_VALUE)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return ('\0' != amount->currency[0]) ? GNUNET_OK : GNUNET_NO;
+}
+
+
+bool
+TALER_amount_is_zero (const struct TALER_Amount *amount)
+{
+ if (GNUNET_OK !=
+ TALER_amount_is_valid (amount))
+ return false;
+ return
+ (0 == amount->value) &&
+ (0 == amount->fraction);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_amount_is_currency (const struct TALER_Amount *amount,
+ const char *currency)
+{
+ if (GNUNET_OK !=
+ TALER_amount_is_valid (amount))
+ return GNUNET_SYSERR;
+ return (0 == strcasecmp (currency,
+ amount->currency))
+ ? GNUNET_OK
+ : GNUNET_NO;
}
@@ -292,23 +303,14 @@ TALER_amount_is_valid (const struct TALER_Amount *amount)
* @return #GNUNET_YES if valid,
* #GNUNET_NO if invalid
*/
-static int
+static enum GNUNET_GenericReturnValue
test_valid_nbo (const struct TALER_AmountNBO *a)
{
- return ('\0' != a->currency[0]);
+ return ('\0' != a->currency[0]) ? GNUNET_YES : GNUNET_NO;
}
-/**
- * Test if @a a1 and @a a2 are the same currency.
- *
- * @param a1 amount to test
- * @param a2 amount to test
- * @return #GNUNET_YES if @a a1 and @a a2 are the same currency
- * #GNUNET_NO if the currencies are different,
- * #GNUNET_SYSERR if either amount is invalid
- */
-int
+enum GNUNET_GenericReturnValue
TALER_amount_cmp_currency (const struct TALER_Amount *a1,
const struct TALER_Amount *a2)
{
@@ -322,16 +324,7 @@ TALER_amount_cmp_currency (const struct TALER_Amount *a1,
}
-/**
- * Test if @a a1 and @a a2 are the same currency, NBO variant.
- *
- * @param a1 amount to test
- * @param a2 amount to test
- * @return #GNUNET_YES if @a a1 and @a a2 are the same currency
- * #GNUNET_NO if the currencies are different,
- * #GNUNET_SYSERR if either amount is invalid
- */
-int
+enum GNUNET_GenericReturnValue
TALER_amount_cmp_currency_nbo (const struct TALER_AmountNBO *a1,
const struct TALER_AmountNBO *a2)
{
@@ -345,19 +338,6 @@ TALER_amount_cmp_currency_nbo (const struct TALER_AmountNBO *a1,
}
-/**
- * Compare the value/fraction of two amounts. Does not compare the currency.
- * Comparing amounts of different currencies will cause the program to abort().
- * If unsure, check with #TALER_amount_cmp_currency() first to be sure that
- * the currencies of the two amounts are identical.
- *
- * @param a1 first amount
- * @param a2 second amount
- * @return result of the comparison,
- * -1 if `a1 < a2`
- * 1 if `a1 > a2`
- * 0 if `a1 == a2`.
- */
int
TALER_amount_cmp (const struct TALER_Amount *a1,
const struct TALER_Amount *a2)
@@ -388,18 +368,23 @@ TALER_amount_cmp (const struct TALER_Amount *a1,
}
-/**
- * Perform saturating subtraction of amounts.
- *
- * @param[out] diff where to store (@a a1 - @a a2), or invalid if @a a2 > @a a1
- * @param a1 amount to subtract from
- * @param a2 amount to subtract
- * @return #GNUNET_OK if the subtraction worked,
- * #GNUNET_NO if @a a1 = @a a2
- * #GNUNET_SYSERR if @a a2 > @a a1 or currencies are incompatible;
- * @a diff is set to invalid
- */
int
+TALER_amount_cmp_nbo (const struct TALER_AmountNBO *a1,
+ const struct TALER_AmountNBO *a2)
+{
+ struct TALER_Amount h1;
+ struct TALER_Amount h2;
+
+ TALER_amount_ntoh (&h1,
+ a1);
+ TALER_amount_ntoh (&h2,
+ a2);
+ return TALER_amount_cmp (&h1,
+ &h2);
+}
+
+
+enum TALER_AmountArithmeticResult
TALER_amount_subtract (struct TALER_Amount *diff,
const struct TALER_Amount *a1,
const struct TALER_Amount *a2)
@@ -412,7 +397,7 @@ TALER_amount_subtract (struct TALER_Amount *diff,
a2))
{
invalidate (diff);
- return GNUNET_SYSERR;
+ return TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE;
}
/* make local copies to avoid aliasing problems between
diff and a1/a2 */
@@ -422,7 +407,7 @@ TALER_amount_subtract (struct TALER_Amount *diff,
(GNUNET_SYSERR == TALER_amount_normalize (&n2)) )
{
invalidate (diff);
- return GNUNET_SYSERR;
+ return TALER_AAR_INVALID_NORMALIZATION_FAILED;
}
if (n1.fraction < n2.fraction)
@@ -430,7 +415,7 @@ TALER_amount_subtract (struct TALER_Amount *diff,
if (0 == n1.value)
{
invalidate (diff);
- return GNUNET_SYSERR;
+ return TALER_AAR_INVALID_NEGATIVE_RESULT;
}
n1.fraction += TALER_AMOUNT_FRAC_BASE;
n1.value--;
@@ -438,10 +423,10 @@ TALER_amount_subtract (struct TALER_Amount *diff,
if (n1.value < n2.value)
{
invalidate (diff);
- return GNUNET_SYSERR;
+ return TALER_AAR_INVALID_NEGATIVE_RESULT;
}
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (n1.currency,
+ TALER_amount_set_zero (n1.currency,
diff));
GNUNET_assert (n1.fraction >= n2.fraction);
diff->fraction = n1.fraction - n2.fraction;
@@ -449,21 +434,12 @@ TALER_amount_subtract (struct TALER_Amount *diff,
diff->value = n1.value - n2.value;
if ( (0 == diff->fraction) &&
(0 == diff->value) )
- return GNUNET_NO;
- return GNUNET_OK;
+ return TALER_AAR_RESULT_ZERO;
+ return TALER_AAR_RESULT_POSITIVE;
}
-/**
- * Perform addition of amounts.
- *
- * @param[out] sum where to store @a a1 + @a a2, set to "invalid" on overflow
- * @param a1 first amount to add
- * @param a2 second amount to add
- * @return #GNUNET_OK if the addition worked,
- * #GNUNET_SYSERR on overflow
- */
-int
+enum TALER_AmountArithmeticResult
TALER_amount_add (struct TALER_Amount *sum,
const struct TALER_Amount *a1,
const struct TALER_Amount *a2)
@@ -473,37 +449,40 @@ TALER_amount_add (struct TALER_Amount *sum,
struct TALER_Amount res;
if (GNUNET_YES !=
- TALER_amount_cmp_currency (a1, a2))
+ TALER_amount_cmp_currency (a1,
+ a2))
{
invalidate (sum);
- return GNUNET_SYSERR;
+ return TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE;
}
/* make local copies to avoid aliasing problems between
diff and a1/a2 */
n1 = *a1;
n2 = *a2;
- if ( (GNUNET_SYSERR == TALER_amount_normalize (&n1)) ||
- (GNUNET_SYSERR == TALER_amount_normalize (&n2)) )
+ if ( (GNUNET_SYSERR ==
+ TALER_amount_normalize (&n1)) ||
+ (GNUNET_SYSERR ==
+ TALER_amount_normalize (&n2)) )
{
invalidate (sum);
- return GNUNET_SYSERR;
+ return TALER_AAR_INVALID_NORMALIZATION_FAILED;
}
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (a1->currency,
+ TALER_amount_set_zero (a1->currency,
&res));
res.value = n1.value + n2.value;
if (res.value < n1.value)
{
/* integer overflow */
invalidate (sum);
- return GNUNET_SYSERR;
+ return TALER_AAR_INVALID_RESULT_OVERFLOW;
}
- if (res.value > MAX_AMOUNT_VALUE)
+ if (res.value > TALER_AMOUNT_MAX_VALUE)
{
/* too large to be legal */
invalidate (sum);
- return GNUNET_SYSERR;
+ return TALER_AAR_INVALID_RESULT_OVERFLOW;
}
res.fraction = n1.fraction + n2.fraction;
if (GNUNET_SYSERR ==
@@ -511,22 +490,17 @@ TALER_amount_add (struct TALER_Amount *sum,
{
/* integer overflow via carry from fraction */
invalidate (sum);
- return GNUNET_SYSERR;
+ return TALER_AAR_INVALID_RESULT_OVERFLOW;
}
*sum = res;
- return GNUNET_OK;
+ if ( (0 == sum->fraction) &&
+ (0 == sum->value) )
+ return TALER_AAR_RESULT_ZERO;
+ return TALER_AAR_RESULT_POSITIVE;
}
-/**
- * Normalize the given amount.
- *
- * @param[in,out] amount amount to normalize
- * @return #GNUNET_OK if normalization worked
- * #GNUNET_NO if value was already normalized
- * #GNUNET_SYSERR if value was invalid or could not be normalized
- */
-int
+enum GNUNET_GenericReturnValue
TALER_amount_normalize (struct TALER_Amount *amount)
{
uint32_t overflow;
@@ -539,7 +513,7 @@ TALER_amount_normalize (struct TALER_Amount *amount)
amount->fraction %= TALER_AMOUNT_FRAC_BASE;
amount->value += overflow;
if ( (amount->value < overflow) ||
- (amount->value > MAX_AMOUNT_VALUE) )
+ (amount->value > TALER_AMOUNT_MAX_VALUE) )
{
invalidate (amount);
return GNUNET_SYSERR;
@@ -570,12 +544,6 @@ amount_to_tail (const struct TALER_Amount *amount,
}
-/**
- * Convert amount to string.
- *
- * @param amount amount to convert to string
- * @return freshly allocated string representation
- */
char *
TALER_amount_to_string (const struct TALER_Amount *amount)
{
@@ -610,19 +578,12 @@ TALER_amount_to_string (const struct TALER_Amount *amount)
}
-/**
- * Convert amount to string.
- *
- * @param amount amount to convert to string
- * @return statically allocated buffer with string representation,
- * NULL if the @a amount was invalid
- */
const char *
TALER_amount2s (const struct TALER_Amount *amount)
{
/* 24 is sufficient for a uint64_t value in decimal; 3 is for ":.\0" */
- static GNUNET_THREAD_LOCAL char result[TALER_AMOUNT_FRAC_LEN
- + TALER_CURRENCY_LEN + 3 + 24];
+ static TALER_THREAD_LOCAL char result[TALER_AMOUNT_FRAC_LEN
+ + TALER_CURRENCY_LEN + 3 + 24];
struct TALER_Amount norm;
if (GNUNET_YES != TALER_amount_is_valid (amount))
@@ -655,14 +616,6 @@ TALER_amount2s (const struct TALER_Amount *amount)
}
-/**
- * Divide an amount by a @a divisor. Note that this function
- * may introduce a rounding error!
- *
- * @param[out] result where to store @a dividend / @a divisor
- * @param dividend amount to divide
- * @param divisor by what to divide, must be positive
- */
void
TALER_amount_divide (struct TALER_Amount *result,
const struct TALER_Amount *dividend,
@@ -688,21 +641,110 @@ TALER_amount_divide (struct TALER_Amount *result,
}
-/**
- * Round the amount to something that can be transferred on the wire.
- * The rounding mode is specified via the smallest transferable unit,
- * which must only have a fractional part *or* only a value (either
- * of the two must be zero!).
- *
- * If the @a round_unit given is zero, we do nothing and return #GNUNET_NO.
- *
- * @param[in,out] amount amount to round down
- * @param[in] round_unit unit that should be rounded down to, and
- * either value part or the faction must be zero
- * @return #GNUNET_OK on success, #GNUNET_NO if rounding was unnecessary,
- * #GNUNET_SYSERR if the amount or currency or @a round_unit was invalid
- */
int
+TALER_amount_divide2 (const struct TALER_Amount *dividend,
+ const struct TALER_Amount *divisor)
+{
+ double approx;
+ double d;
+ double r;
+ int ret;
+ struct TALER_Amount tmp;
+ struct TALER_Amount nxt;
+
+ if (GNUNET_YES !=
+ TALER_amount_cmp_currency (dividend,
+ divisor))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ if ( (0 == divisor->fraction) &&
+ (0 == divisor->value) )
+ return INT_MAX;
+ /* first, get rounded approximation */
+ d = ((double) dividend->value) * ((double) TALER_AMOUNT_FRAC_BASE)
+ + ( (double) dividend->fraction);
+ r = ((double) divisor->value) * ((double) TALER_AMOUNT_FRAC_BASE)
+ + ( (double) divisor->fraction);
+ approx = d / r;
+ if (approx > ((double) INT_MAX))
+ return INT_MAX; /* 'infinity' */
+ /* round down */
+ if (approx < 2)
+ ret = 0;
+ else
+ ret = (int) approx - 2;
+ /* Now do *exact* calculation, using well rounded-down factor as starting
+ point to avoid having to do too many steps. */
+ GNUNET_assert (0 <=
+ TALER_amount_multiply (&tmp,
+ divisor,
+ ret));
+ /* in practice, this loop will only run for one or two iterations */
+ while (1)
+ {
+ GNUNET_assert (0 <=
+ TALER_amount_add (&nxt,
+ &tmp,
+ divisor));
+ if (1 ==
+ TALER_amount_cmp (&nxt,
+ dividend))
+ break; /* nxt > dividend */
+ ret++;
+ tmp = nxt;
+ }
+ return ret;
+}
+
+
+enum TALER_AmountArithmeticResult
+TALER_amount_multiply (struct TALER_Amount *result,
+ const struct TALER_Amount *amount,
+ uint32_t factor)
+{
+ struct TALER_Amount in = *amount;
+
+ if (GNUNET_SYSERR ==
+ TALER_amount_normalize (&in))
+ return TALER_AAR_INVALID_NORMALIZATION_FAILED;
+ GNUNET_memcpy (result->currency,
+ amount->currency,
+ TALER_CURRENCY_LEN);
+ if ( (0 == factor) ||
+ ( (0 == in.value) &&
+ (0 == in.fraction) ) )
+ {
+ result->value = 0;
+ result->fraction = 0;
+ return TALER_AAR_RESULT_ZERO;
+ }
+ result->value = in.value * ((uint64_t) factor);
+ if (in.value != result->value / factor)
+ return TALER_AAR_INVALID_RESULT_OVERFLOW;
+ {
+ /* This multiplication cannot overflow since both inputs are 32-bit values */
+ uint64_t tmp = ((uint64_t) factor) * ((uint64_t) in.fraction);
+ uint64_t res;
+
+ res = tmp / TALER_AMOUNT_FRAC_BASE;
+ /* check for overflow */
+ if (result->value + res < result->value)
+ return TALER_AAR_INVALID_RESULT_OVERFLOW;
+ result->value += res;
+ result->fraction = tmp % TALER_AMOUNT_FRAC_BASE;
+ }
+ if (result->value > TALER_AMOUNT_MAX_VALUE)
+ return TALER_AAR_INVALID_RESULT_OVERFLOW;
+ /* This check should be redundant... */
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_amount_normalize (result));
+ return TALER_AAR_RESULT_POSITIVE;
+}
+
+
+enum GNUNET_GenericReturnValue
TALER_amount_round_down (struct TALER_Amount *amount,
const struct TALER_Amount *round_unit)
{
diff --git a/src/util/auditor_signatures.c b/src/util/auditor_signatures.c
new file mode 100644
index 000000000..c35b6f192
--- /dev/null
+++ b/src/util/auditor_signatures.c
@@ -0,0 +1,187 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 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/>
+*/
+/**
+ * @file auditor_signatures.c
+ * @brief Utility functions for Taler auditor signatures
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+
+
+/**
+ * @brief Information signed by an auditor affirming
+ * the master public key and the denomination keys
+ * of a exchange.
+ */
+struct TALER_ExchangeKeyValidityPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash of the auditor's URL (including 0-terminator).
+ */
+ struct GNUNET_HashCode auditor_url_hash;
+
+ /**
+ * The long-term offline master key of the exchange, affirmed by the
+ * auditor.
+ */
+ struct TALER_MasterPublicKeyP master;
+
+ /**
+ * Start time of the validity period for this key.
+ */
+ struct GNUNET_TIME_TimestampNBO start;
+
+ /**
+ * The exchange will sign fresh coins between @e start and this time.
+ * @e expire_withdraw will be somewhat larger than @e start to
+ * ensure a sufficiently large anonymity set, while also allowing
+ * the Exchange to limit the financial damage in case of a key being
+ * compromised. Thus, exchanges with low volume are expected to have a
+ * longer withdraw period (@e expire_withdraw - @e start) than exchanges
+ * with high transaction volume. The period may also differ between
+ * types of coins. A exchange may also have a few denomination keys
+ * with the same value with overlapping validity periods, to address
+ * issues such as clock skew.
+ */
+ struct GNUNET_TIME_TimestampNBO expire_withdraw;
+
+ /**
+ * Coins signed with the denomination key must be spent or refreshed
+ * between @e start and this expiration time. After this time, the
+ * exchange will refuse transactions involving this key as it will
+ * "drop" the table with double-spending information (shortly after)
+ * this time. Note that wallets should refresh coins significantly
+ * before this time to be on the safe side. @e expire_deposit must be
+ * significantly larger than @e expire_withdraw (by months or even
+ * years).
+ */
+ struct GNUNET_TIME_TimestampNBO expire_deposit;
+
+ /**
+ * When do signatures with this denomination key become invalid?
+ * After this point, these signatures cannot be used in (legal)
+ * disputes anymore, as the Exchange is then allowed to destroy its side
+ * of the evidence. @e expire_legal is expected to be significantly
+ * larger than @e expire_deposit (by a year or more).
+ */
+ struct GNUNET_TIME_TimestampNBO expire_legal;
+
+ /**
+ * The value of the coins signed with this denomination key.
+ */
+ struct TALER_AmountNBO value;
+
+ /**
+ * Fees for the coin.
+ */
+ struct TALER_DenomFeeSetNBOP fees;
+
+ /**
+ * Hash code of the denomination public key. (Used to avoid having
+ * the variable-size RSA key in this struct.)
+ */
+ struct TALER_DenominationHashP denom_hash GNUNET_PACKED;
+
+};
+
+
+void
+TALER_auditor_denom_validity_sign (
+ const char *auditor_url,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Timestamp stamp_start,
+ struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+ struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+ struct GNUNET_TIME_Timestamp stamp_expire_legal,
+ const struct TALER_Amount *coin_value,
+ const struct TALER_DenomFeeSet *fees,
+ const struct TALER_AuditorPrivateKeyP *auditor_priv,
+ struct TALER_AuditorSignatureP *auditor_sig)
+{
+ struct TALER_ExchangeKeyValidityPS kv = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS),
+ .purpose.size = htonl (sizeof (kv)),
+ .start = GNUNET_TIME_timestamp_hton (stamp_start),
+ .expire_withdraw = GNUNET_TIME_timestamp_hton (stamp_expire_withdraw),
+ .expire_deposit = GNUNET_TIME_timestamp_hton (stamp_expire_deposit),
+ .expire_legal = GNUNET_TIME_timestamp_hton (stamp_expire_legal),
+ .denom_hash = *h_denom_pub,
+ .master = *master_pub,
+ };
+
+ TALER_amount_hton (&kv.value,
+ coin_value);
+ TALER_denom_fee_set_hton (&kv.fees,
+ fees);
+ GNUNET_CRYPTO_hash (auditor_url,
+ strlen (auditor_url) + 1,
+ &kv.auditor_url_hash);
+ GNUNET_CRYPTO_eddsa_sign (&auditor_priv->eddsa_priv,
+ &kv,
+ &auditor_sig->eddsa_sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_auditor_denom_validity_verify (
+ const char *auditor_url,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Timestamp stamp_start,
+ struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+ struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+ struct GNUNET_TIME_Timestamp stamp_expire_legal,
+ const struct TALER_Amount *coin_value,
+ const struct TALER_DenomFeeSet *fees,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_AuditorSignatureP *auditor_sig)
+{
+ struct TALER_ExchangeKeyValidityPS kv = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS),
+ .purpose.size = htonl (sizeof (kv)),
+ .start = GNUNET_TIME_timestamp_hton (stamp_start),
+ .expire_withdraw = GNUNET_TIME_timestamp_hton (stamp_expire_withdraw),
+ .expire_deposit = GNUNET_TIME_timestamp_hton (stamp_expire_deposit),
+ .expire_legal = GNUNET_TIME_timestamp_hton (stamp_expire_legal),
+ .denom_hash = *h_denom_pub,
+ .master = *master_pub,
+ };
+
+ TALER_amount_hton (&kv.value,
+ coin_value);
+ TALER_denom_fee_set_hton (&kv.fees,
+ fees);
+ GNUNET_CRYPTO_hash (auditor_url,
+ strlen (auditor_url) + 1,
+ &kv.auditor_url_hash);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS,
+ &kv,
+ &auditor_sig->eddsa_sig,
+ &auditor_pub->eddsa_pub);
+}
+
+
+/* end of auditor_signatures.c */
diff --git a/src/util/bench_age_restriction.c b/src/util/bench_age_restriction.c
new file mode 100644
index 000000000..abda9416a
--- /dev/null
+++ b/src/util/bench_age_restriction.c
@@ -0,0 +1,208 @@
+/**
+ * @file util/bench_age_restriction.c
+ * @brief Measure Commit, Attest, Verify, Derive and Compare
+ * @author Özgür Kesim
+ *
+ * compile in exchange/src/util with
+ *
+ * gcc benc_age_restriction.c \
+ * -lgnunetutil -lgnunetjson -lsodium -ljansson \
+ * -L/usr/lib/x86_64-linux-gnu -lmicrohttpd -ltalerutil \
+ * -I../include \
+ * -o bench_age_restriction
+ *
+ */
+#include "platform.h"
+#include <math.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <taler/taler_util.h>
+#include <taler/taler_crypto_lib.h>
+
+static struct TALER_AgeMask
+ age_mask = { .bits = 1
+ | 1 << 8 | 1 << 10 | 1 << 12
+ | 1 << 14 | 1 << 16 | 1 << 18 | 1 << 21 };
+
+extern uint8_t
+get_age_group (
+ const struct TALER_AgeMask *mask,
+ uint8_t age);
+
+/**
+ * Encodes the age mask into a string, like "8:10:12:14:16:18:21"
+ */
+char *
+age_mask_to_string (
+ const struct TALER_AgeMask *m)
+{
+ uint32_t bits = m->bits;
+ unsigned int n = 0;
+ char *buf = GNUNET_malloc (32 * 3); // max characters possible
+ char *pos = buf;
+
+ if (NULL == buf)
+ {
+ return buf;
+ }
+
+ while (bits != 0)
+ {
+ bits >>= 1;
+ n++;
+ if (0 == (bits & 1))
+ {
+ continue;
+ }
+
+ if (n > 9)
+ {
+ *(pos++) = '0' + n / 10;
+ }
+ *(pos++) = '0' + n % 10;
+
+ if (0 != (bits >> 1))
+ {
+ *(pos++) = ':';
+ }
+ }
+ return buf;
+}
+
+
+#define ITER 2000
+
+double
+average (long *times, size_t size)
+{
+ double mean = 0.0;
+ for (int i = 0; i < size; i++)
+ {
+ mean += times[i];
+ }
+ return mean / size;
+}
+
+
+double
+stdev (long *times, size_t size)
+{
+ double mean = average (times, size);
+ double V = 0.0;
+ for (int i = 0; i < size; i++)
+ {
+ double d = times[i] - mean;
+ d *= d;
+ V += d;
+ }
+ return sqrt (V / size);
+}
+
+
+#define pr(n,t, i) printf ("%10s (%dx):\t%.2f ± %.2fµs\n", (n), i, average ( \
+ &t[0], ITER) / 1000, stdev (&t[0], ITER) / 1000); \
+ i = 0;
+
+#define starttime clock_gettime (CLOCK_MONOTONIC, &tstart)
+#define stoptime clock_gettime (CLOCK_MONOTONIC, &tend); \
+ times[i] = ((long) tend.tv_sec * 1000 * 1000 * 1000 + tend.tv_nsec) \
+ - ((long) tstart.tv_sec * 1000 * 1000 * 1000 + tstart.tv_nsec);
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ struct timespec tstart = {0,0}, tend = {0,0};
+ enum GNUNET_GenericReturnValue ret;
+ struct TALER_AgeCommitmentProof acp = {0};
+ uint8_t age = 21;
+ uint8_t age_group = get_age_group (&age_mask, age);
+ struct GNUNET_HashCode seed;
+ long times[ITER] = {0};
+ int i = 0;
+
+ // commit
+ for (; i < ITER; i++)
+ {
+ starttime;
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &seed,
+ sizeof(seed));
+
+ ret = TALER_age_restriction_commit (&age_mask,
+ age,
+ &seed,
+ &acp);
+ stoptime;
+
+ }
+ pr ("commit", times, i);
+
+ // attest
+ for (; i < ITER; i++)
+ {
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &seed,
+ sizeof(seed));
+
+ ret = TALER_age_restriction_commit (&age_mask,
+ age,
+ &seed,
+ &acp);
+
+ starttime;
+ uint8_t min_group = get_age_group (&age_mask, 13);
+ struct TALER_AgeAttestation at = {0};
+ ret = TALER_age_commitment_attest (&acp,
+ 13,
+ &at);
+ stoptime;
+ }
+ pr ("attest", times, i);
+
+ // verify
+ for (; i < ITER; i++)
+ {
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &seed,
+ sizeof(seed));
+
+ ret = TALER_age_restriction_commit (&age_mask,
+ age,
+ &seed,
+ &acp);
+
+ uint8_t min_group = get_age_group (&age_mask, 13);
+ struct TALER_AgeAttestation at = {0};
+
+ ret = TALER_age_commitment_attest (&acp,
+ 13,
+ &at);
+ starttime;
+ ret = TALER_age_commitment_verify (&acp.commitment,
+ 13,
+ &at);
+ stoptime;
+ }
+ pr ("verify", times, i);
+
+ // derive
+ for (; i < ITER; i++)
+ {
+ struct TALER_AgeCommitmentProof acp2 = {0};
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &seed,
+ sizeof(seed));
+ starttime;
+ TALER_age_commitment_derive (&acp,
+ &seed,
+ &acp2);
+ stoptime;
+ }
+ pr ("derive", times, i);
+
+ return 0;
+}
+
+
+/* end of tv_age_restriction.c */
diff --git a/src/util/config.c b/src/util/config.c
index 160d541f0..f5accaad8 100644
--- a/src/util/config.c
+++ b/src/util/config.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 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
@@ -21,18 +21,9 @@
*/
#include "platform.h"
#include "taler_util.h"
+#include <gnunet/gnunet_json_lib.h>
-
-/**
- * Obtain denomination amount from configuration file.
- *
- * @param cfg configuration to use
- * @param section section of the configuration to access
- * @param option option of the configuration to access
- * @param[out] denom set to the amount found in configuration
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
- */
-int
+enum GNUNET_GenericReturnValue
TALER_config_get_amount (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
@@ -45,11 +36,21 @@ TALER_config_get_amount (const struct GNUNET_CONFIGURATION_Handle *cfg,
section,
option,
&str))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ option);
return GNUNET_NO;
- if (GNUNET_OK != TALER_string_to_amount (str,
- denom))
+ }
+ if (GNUNET_OK !=
+ TALER_string_to_amount (str,
+ denom))
{
GNUNET_free (str);
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ option,
+ "invalid amount");
return GNUNET_SYSERR;
}
GNUNET_free (str);
@@ -57,18 +58,80 @@ TALER_config_get_amount (const struct GNUNET_CONFIGURATION_Handle *cfg,
}
-/**
- * Load our currency from the @a cfg (in section [taler]
- * the option "CURRENCY").
- *
- * @param cfg configuration to use
- * @param[out] currency where to write the result
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
- */
-int
+enum GNUNET_GenericReturnValue
+TALER_config_get_denom_fees (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *currency,
+ const char *section,
+ struct TALER_DenomFeeSet *fees)
+{
+ if (GNUNET_OK !=
+ TALER_config_get_amount (cfg,
+ section,
+ "FEE_WITHDRAW",
+ &fees->withdraw))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "Need amount for option `%s' in section `%s'\n",
+ "FEE_WITHDRAW",
+ section);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (cfg,
+ section,
+ "FEE_DEPOSIT",
+ &fees->deposit))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "Need amount for option `%s' in section `%s'\n",
+ "FEE_DEPOSIT",
+ section);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (cfg,
+ section,
+ "FEE_REFRESH",
+ &fees->refresh))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "Need amount for option `%s' in section `%s'\n",
+ "FEE_REFRESH",
+ section);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (cfg,
+ section,
+ "FEE_REFUND",
+ &fees->refund))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "Need amount for option `%s' in section `%s'\n",
+ "FEE_REFUND",
+ section);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_denom_fee_check_currency (currency,
+ fees))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Need fee amounts in section `%s' to use currency `%s'\n",
+ section,
+ currency);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
TALER_config_get_currency (const struct GNUNET_CONFIGURATION_Handle *cfg,
char **currency)
{
+ size_t slen;
+
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
"taler",
@@ -80,15 +143,368 @@ TALER_config_get_currency (const struct GNUNET_CONFIGURATION_Handle *cfg,
"CURRENCY");
return GNUNET_SYSERR;
}
- if (strlen (*currency) >= TALER_CURRENCY_LEN)
+ slen = strlen (*currency);
+ if (slen >= TALER_CURRENCY_LEN)
{
- fprintf (stderr,
- "Currency `%s' longer than the allowed limit of %u characters.",
- *currency,
- (unsigned int) TALER_CURRENCY_LEN);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Currency `%s' longer than the allowed limit of %u characters.",
+ *currency,
+ (unsigned int) TALER_CURRENCY_LEN);
GNUNET_free (*currency);
*currency = NULL;
return GNUNET_SYSERR;
}
+ for (size_t i = 0; i<slen; i++)
+ if (! isalpha ((unsigned char) (*currency)[i]))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Currency `%s' must only use characters from the A-Z range.",
+ *currency);
+ GNUNET_free (*currency);
+ *currency = NULL;
+ return GNUNET_SYSERR;
+ }
return GNUNET_OK;
}
+
+
+/**
+ * Closure for #parse_currencies_cb().
+ */
+struct CurrencyParserContext
+{
+ /**
+ * Current offset in @e cspecs.
+ */
+ unsigned int num_currencies;
+
+ /**
+ * Length of the @e cspecs array.
+ */
+ unsigned int len_cspecs;
+
+ /**
+ * Array of currency specifications (see DD 51).
+ */
+ struct TALER_CurrencySpecification *cspecs;
+
+ /**
+ * Configuration we are parsing.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Set to true if the configuration was malformed.
+ */
+ bool failure;
+};
+
+
+/**
+ * Function to iterate over section.
+ *
+ * @param cls closure with a `struct CurrencyParserContext *`
+ * @param section name of the section
+ */
+static void
+parse_currencies_cb (void *cls,
+ const char *section)
+{
+ struct CurrencyParserContext *cpc = cls;
+ struct TALER_CurrencySpecification *cspec;
+ unsigned long long num;
+ char *str;
+
+ if (cpc->failure)
+ return;
+ if (0 != strncasecmp (section,
+ "currency-",
+ strlen ("currency-")))
+ return; /* not interesting */
+ if (GNUNET_YES !=
+ GNUNET_CONFIGURATION_get_value_yesno (cpc->cfg,
+ section,
+ "ENABLED"))
+ return; /* disabled */
+ if (cpc->len_cspecs == cpc->num_currencies)
+ {
+ GNUNET_array_grow (cpc->cspecs,
+ cpc->len_cspecs,
+ cpc->len_cspecs * 2 + 4);
+ }
+ cspec = &cpc->cspecs[cpc->num_currencies++];
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cpc->cfg,
+ section,
+ "CODE",
+ &str))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "CODE");
+ cpc->failure = true;
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_check_currency (str))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "CODE",
+ "Currency code name given is invalid");
+ cpc->failure = true;
+ GNUNET_free (str);
+ return;
+ }
+ memset (cspec->currency,
+ 0,
+ sizeof (cspec->currency));
+ /* Already checked in TALER_check_currency(), repeated here
+ just to make static analysis happy */
+ GNUNET_assert (strlen (str) < TALER_CURRENCY_LEN);
+ strcpy (cspec->currency,
+ str);
+ GNUNET_free (str);
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cpc->cfg,
+ section,
+ "NAME",
+ &str))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "NAME");
+ cpc->failure = true;
+ return;
+ }
+ cspec->name = str;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cpc->cfg,
+ section,
+ "FRACTIONAL_INPUT_DIGITS",
+ &num))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "FRACTIONAL_INPUT_DIGITS");
+ cpc->failure = true;
+ return;
+ }
+ if (num > TALER_AMOUNT_FRAC_LEN)
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "FRACTIONAL_INPUT_DIGITS",
+ "Number given is too big");
+ cpc->failure = true;
+ return;
+ }
+ cspec->num_fractional_input_digits = num;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cpc->cfg,
+ section,
+ "FRACTIONAL_NORMAL_DIGITS",
+ &num))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "FRACTIONAL_NORMAL_DIGITS");
+ cpc->failure = true;
+ return;
+ }
+ if (num > TALER_AMOUNT_FRAC_LEN)
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "FRACTIONAL_NORMAL_DIGITS",
+ "Number given is too big");
+ cpc->failure = true;
+ return;
+ }
+ cspec->num_fractional_normal_digits = num;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cpc->cfg,
+ section,
+ "FRACTIONAL_TRAILING_ZERO_DIGITS",
+ &num))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "FRACTIONAL_TRAILING_ZERO_DIGITS");
+ cpc->failure = true;
+ return;
+ }
+ if (num > TALER_AMOUNT_FRAC_LEN)
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "FRACTIONAL_TRAILING_ZERO_DIGITS",
+ "Number given is too big");
+ cpc->failure = true;
+ return;
+ }
+ cspec->num_fractional_trailing_zero_digits = num;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cpc->cfg,
+ section,
+ "ALT_UNIT_NAMES",
+ &str))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "ALT_UNIT_NAMES");
+ cpc->failure = true;
+ return;
+ }
+ {
+ json_error_t err;
+
+ cspec->map_alt_unit_names = json_loads (str,
+ JSON_REJECT_DUPLICATES,
+ &err);
+ GNUNET_free (str);
+ if (NULL == cspec->map_alt_unit_names)
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "ALT_UNIT_NAMES",
+ err.text);
+ cpc->failure = true;
+ return;
+ }
+ }
+ if (GNUNET_OK !=
+ TALER_check_currency_scale_map (cspec->map_alt_unit_names))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "ALT_UNIT_NAMES",
+ "invalid map entry detected");
+ cpc->failure = true;
+ json_decref (cspec->map_alt_unit_names);
+ cspec->map_alt_unit_names = NULL;
+ return;
+ }
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_check_currency_scale_map (const json_t *map)
+{
+ /* validate map only maps from decimal numbers to strings! */
+ const char *str;
+ const json_t *val;
+ bool zf = false;
+
+ if (! json_is_object (map))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Object required for currency scale map\n");
+ return GNUNET_SYSERR;
+ }
+ json_object_foreach ((json_t *) map, str, val)
+ {
+ int idx;
+ char dummy;
+
+ if ( (1 != sscanf (str,
+ "%d%c",
+ &idx,
+ &dummy)) ||
+ (idx < -12) ||
+ (idx > 24) ||
+ (! json_is_string (val) ) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid entry `%s' in currency scale map\n",
+ str);
+ return GNUNET_SYSERR;
+ }
+ if (0 == idx)
+ zf = true;
+ }
+ if (! zf)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Entry for 0 missing in currency scale map\n");
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_CONFIG_parse_currencies (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ unsigned int *num_currencies,
+ struct TALER_CurrencySpecification **cspecs)
+{
+ struct CurrencyParserContext cpc = {
+ .cfg = cfg
+ };
+
+ GNUNET_CONFIGURATION_iterate_sections (cfg,
+ &parse_currencies_cb,
+ &cpc);
+ if (cpc.failure)
+ {
+ GNUNET_array_grow (cpc.cspecs,
+ cpc.len_cspecs,
+ 0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_array_grow (cpc.cspecs,
+ cpc.len_cspecs,
+ cpc.num_currencies);
+ *num_currencies = cpc.num_currencies;
+ *cspecs = cpc.cspecs;
+ if (0 == *num_currencies)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No currency formatting specification found! Please check your installation!\n");
+ return GNUNET_NO;
+ }
+ return GNUNET_OK;
+}
+
+
+json_t *
+TALER_CONFIG_currency_specs_to_json (const struct
+ TALER_CurrencySpecification *cspec)
+{
+ return GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("name",
+ cspec->name),
+ /* 'currency' is deprecated as of exchange v18 and merchant v6;
+ remove this line once current-age > 6*/
+ GNUNET_JSON_pack_string ("currency",
+ cspec->currency),
+ GNUNET_JSON_pack_uint64 ("num_fractional_input_digits",
+ cspec->num_fractional_input_digits),
+ GNUNET_JSON_pack_uint64 ("num_fractional_normal_digits",
+ cspec->num_fractional_normal_digits),
+ GNUNET_JSON_pack_uint64 ("num_fractional_trailing_zero_digits",
+ cspec->num_fractional_trailing_zero_digits),
+ GNUNET_JSON_pack_object_incref ("alt_unit_names",
+ cspec->map_alt_unit_names));
+}
+
+
+void
+TALER_CONFIG_free_currencies (
+ unsigned int num_currencies,
+ struct TALER_CurrencySpecification cspecs[static num_currencies])
+{
+ for (unsigned int i = 0; i<num_currencies; i++)
+ {
+ struct TALER_CurrencySpecification *cspec = &cspecs[i];
+
+ GNUNET_free (cspec->name);
+ json_decref (cspec->map_alt_unit_names);
+ }
+ GNUNET_array_grow (cspecs,
+ num_currencies,
+ 0);
+}
diff --git a/src/util/conversion.c b/src/util/conversion.c
new file mode 100644
index 000000000..a7bc63789
--- /dev/null
+++ b/src/util/conversion.c
@@ -0,0 +1,405 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file conversion.c
+ * @brief helper routines to run some external JSON-to-JSON converter
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include <gnunet/gnunet_util_lib.h>
+
+
+struct TALER_JSON_ExternalConversion
+{
+ /**
+ * Callback to call with the result.
+ */
+ TALER_JSON_JsonCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Handle to the helper process.
+ */
+ struct GNUNET_OS_Process *helper;
+
+ /**
+ * Pipe for the stdin of the @e helper.
+ */
+ struct GNUNET_DISK_FileHandle *chld_stdin;
+
+ /**
+ * Pipe for the stdout of the @e helper.
+ */
+ struct GNUNET_DISK_FileHandle *chld_stdout;
+
+ /**
+ * Handle to wait on the child to terminate.
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * Task to read JSON output from the child.
+ */
+ struct GNUNET_SCHEDULER_Task *read_task;
+
+ /**
+ * Task to send JSON input to the child.
+ */
+ struct GNUNET_SCHEDULER_Task *write_task;
+
+ /**
+ * Buffer with data we need to send to the helper.
+ */
+ void *write_buf;
+
+ /**
+ * Buffer for reading data from the helper.
+ */
+ void *read_buf;
+
+ /**
+ * Total length of @e write_buf.
+ */
+ size_t write_size;
+
+ /**
+ * Current write position in @e write_buf.
+ */
+ size_t write_pos;
+
+ /**
+ * Current size of @a read_buf.
+ */
+ size_t read_size;
+
+ /**
+ * Current offset in @a read_buf.
+ */
+ size_t read_pos;
+
+};
+
+
+/**
+ * Function called when we can read more data from
+ * the child process.
+ *
+ * @param cls our `struct TALER_JSON_ExternalConversion *`
+ */
+static void
+read_cb (void *cls)
+{
+ struct TALER_JSON_ExternalConversion *ec = cls;
+
+ ec->read_task = NULL;
+ while (1)
+ {
+ ssize_t ret;
+
+ if (ec->read_size == ec->read_pos)
+ {
+ /* Grow input buffer */
+ size_t ns;
+ void *tmp;
+
+ ns = GNUNET_MAX (2 * ec->read_size,
+ 1024);
+ if (ns > GNUNET_MAX_MALLOC_CHECKED)
+ ns = GNUNET_MAX_MALLOC_CHECKED;
+ if (ec->read_size == ns)
+ {
+ /* Helper returned more than 40 MB of data! Stop reading! */
+ GNUNET_break (0);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_file_close (ec->chld_stdin));
+ return;
+ }
+ tmp = GNUNET_malloc_large (ns);
+ if (NULL == tmp)
+ {
+ /* out of memory, also stop reading */
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "malloc");
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_file_close (ec->chld_stdin));
+ return;
+ }
+ GNUNET_memcpy (tmp,
+ ec->read_buf,
+ ec->read_pos);
+ GNUNET_free (ec->read_buf);
+ ec->read_buf = tmp;
+ ec->read_size = ns;
+ }
+ ret = GNUNET_DISK_file_read (ec->chld_stdout,
+ ec->read_buf + ec->read_pos,
+ ec->read_size - ec->read_pos);
+ if (ret < 0)
+ {
+ if ( (EAGAIN != errno) &&
+ (EWOULDBLOCK != errno) &&
+ (EINTR != errno) )
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "read");
+ return;
+ }
+ break;
+ }
+ if (0 == ret)
+ {
+ /* regular end of stream, good! */
+ return;
+ }
+ GNUNET_assert (ec->read_size >= ec->read_pos + ret);
+ ec->read_pos += ret;
+ }
+ ec->read_task
+ = GNUNET_SCHEDULER_add_read_file (
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ ec->chld_stdout,
+ &read_cb,
+ ec);
+}
+
+
+/**
+ * Function called when we can write more data to
+ * the child process.
+ *
+ * @param cls our `struct TALER_JSON_ExternalConversion *`
+ */
+static void
+write_cb (void *cls)
+{
+ struct TALER_JSON_ExternalConversion *ec = cls;
+ ssize_t ret;
+
+ ec->write_task = NULL;
+ while (ec->write_size > ec->write_pos)
+ {
+ ret = GNUNET_DISK_file_write (ec->chld_stdin,
+ ec->write_buf + ec->write_pos,
+ ec->write_size - ec->write_pos);
+ if (ret < 0)
+ {
+ if ( (EAGAIN != errno) &&
+ (EINTR != errno) )
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "write");
+ break;
+ }
+ if (0 == ret)
+ {
+ GNUNET_break (0);
+ break;
+ }
+ GNUNET_assert (ec->write_size >= ec->write_pos + ret);
+ ec->write_pos += ret;
+ }
+ if ( (ec->write_size > ec->write_pos) &&
+ ( (EAGAIN == errno) ||
+ (EWOULDBLOCK == errno) ||
+ (EINTR == errno) ) )
+ {
+ ec->write_task
+ = GNUNET_SCHEDULER_add_write_file (
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ ec->chld_stdin,
+ &write_cb,
+ ec);
+ }
+ else
+ {
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_file_close (ec->chld_stdin));
+ ec->chld_stdin = NULL;
+ }
+}
+
+
+/**
+ * Defines a GNUNET_ChildCompletedCallback which is sent back
+ * upon death or completion of a child process.
+ *
+ * @param cls handle for the callback
+ * @param type type of the process
+ * @param exit_code status code of the process
+ *
+ */
+static void
+child_done_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct TALER_JSON_ExternalConversion *ec = cls;
+ json_t *j = NULL;
+ json_error_t err;
+
+ ec->cwh = NULL;
+ if (NULL != ec->read_task)
+ {
+ GNUNET_SCHEDULER_cancel (ec->read_task);
+ /* We could get the process termination notification before having drained
+ the read buffer. So drain it now, just in case. */
+ read_cb (ec);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Conversion helper exited with status %d and code %llu after outputting %llu bytes of data\n",
+ (int) type,
+ (unsigned long long) exit_code,
+ (unsigned long long) ec->read_pos);
+ GNUNET_OS_process_destroy (ec->helper);
+ ec->helper = NULL;
+ if (0 != ec->read_pos)
+ {
+ j = json_loadb (ec->read_buf,
+ ec->read_pos,
+ JSON_REJECT_DUPLICATES,
+ &err);
+ if (NULL == j)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse JSON from helper at %d: %s\n",
+ err.position,
+ err.text);
+ }
+ }
+ ec->cb (ec->cb_cls,
+ type,
+ exit_code,
+ j);
+ json_decref (j);
+ TALER_JSON_external_conversion_stop (ec);
+}
+
+
+struct TALER_JSON_ExternalConversion *
+TALER_JSON_external_conversion_start (const json_t *input,
+ TALER_JSON_JsonCallback cb,
+ void *cb_cls,
+ const char *binary,
+ ...)
+{
+ struct TALER_JSON_ExternalConversion *ec;
+ struct GNUNET_DISK_PipeHandle *pipe_stdin;
+ struct GNUNET_DISK_PipeHandle *pipe_stdout;
+ va_list ap;
+
+ ec = GNUNET_new (struct TALER_JSON_ExternalConversion);
+ ec->cb = cb;
+ ec->cb_cls = cb_cls;
+ pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
+ GNUNET_assert (NULL != pipe_stdin);
+ pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE);
+ GNUNET_assert (NULL != pipe_stdout);
+ va_start (ap,
+ binary);
+ ec->helper = GNUNET_OS_start_process_va (GNUNET_OS_INHERIT_STD_ERR,
+ pipe_stdin,
+ pipe_stdout,
+ NULL,
+ binary,
+ ap);
+ va_end (ap);
+ if (NULL == ec->helper)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to run conversion helper `%s'\n",
+ binary);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_pipe_close (pipe_stdin));
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_pipe_close (pipe_stdout));
+ GNUNET_free (ec);
+ return NULL;
+ }
+ ec->chld_stdin =
+ GNUNET_DISK_pipe_detach_end (pipe_stdin,
+ GNUNET_DISK_PIPE_END_WRITE);
+ ec->chld_stdout =
+ GNUNET_DISK_pipe_detach_end (pipe_stdout,
+ GNUNET_DISK_PIPE_END_READ);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_pipe_close (pipe_stdin));
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_pipe_close (pipe_stdout));
+ ec->write_buf = json_dumps (input, JSON_COMPACT);
+ ec->write_size = strlen (ec->write_buf);
+ ec->read_task
+ = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
+ ec->chld_stdout,
+ &read_cb,
+ ec);
+ ec->write_task
+ = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
+ ec->chld_stdin,
+ &write_cb,
+ ec);
+ ec->cwh = GNUNET_wait_child (ec->helper,
+ &child_done_cb,
+ ec);
+ return ec;
+}
+
+
+void
+TALER_JSON_external_conversion_stop (
+ struct TALER_JSON_ExternalConversion *ec)
+{
+ if (NULL != ec->cwh)
+ {
+ GNUNET_wait_child_cancel (ec->cwh);
+ ec->cwh = NULL;
+ }
+ if (NULL != ec->helper)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ec->helper,
+ SIGKILL));
+ GNUNET_OS_process_destroy (ec->helper);
+ }
+ if (NULL != ec->read_task)
+ {
+ GNUNET_SCHEDULER_cancel (ec->read_task);
+ ec->read_task = NULL;
+ }
+ if (NULL != ec->write_task)
+ {
+ GNUNET_SCHEDULER_cancel (ec->write_task);
+ ec->write_task = NULL;
+ }
+ if (NULL != ec->chld_stdin)
+ {
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_file_close (ec->chld_stdin));
+ ec->chld_stdin = NULL;
+ }
+ if (NULL != ec->chld_stdout)
+ {
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_file_close (ec->chld_stdout));
+ ec->chld_stdout = NULL;
+ }
+ GNUNET_free (ec->read_buf);
+ free (ec->write_buf);
+ GNUNET_free (ec);
+}
diff --git a/src/util/crypto.c b/src/util/crypto.c
index 99171ebc7..4735af3b0 100644
--- a/src/util/crypto.c
+++ b/src/util/crypto.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2017 Taler Systems SA
+ Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -20,12 +20,12 @@
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
+ * @author Özgür Kesim
*/
#include "platform.h"
#include "taler_util.h"
#include <gcrypt.h>
-
/**
* Function called by libgcrypt on serious errors.
* Prints an error message and aborts the process.
@@ -69,37 +69,31 @@ TALER_gcrypt_init ()
}
-/**
- * Check if a coin is valid; that is, whether the denomination key exists,
- * is not expired, and the signature is correct.
- *
- * @param coin_public_info the coin public info to check for validity
- * @param denom_pub denomination key, must match @a coin_public_info's `denom_pub_hash`
- * @return #GNUNET_YES if the coin is valid,
- * #GNUNET_NO if it is invalid
- * #GNUNET_SYSERR if an internal error occurred
- */
-int
+enum GNUNET_GenericReturnValue
TALER_test_coin_valid (const struct TALER_CoinPublicInfo *coin_public_info,
const struct TALER_DenominationPublicKey *denom_pub)
{
- struct GNUNET_HashCode c_hash;
+ struct TALER_CoinPubHashP c_hash;
#if ENABLE_SANITY_CHECKS
- struct GNUNET_HashCode d_hash;
+ struct TALER_DenominationHashP d_hash;
- GNUNET_CRYPTO_rsa_public_key_hash (denom_pub->rsa_public_key,
- &d_hash);
+ TALER_denom_pub_hash (denom_pub,
+ &d_hash);
GNUNET_assert (0 ==
GNUNET_memcmp (&d_hash,
&coin_public_info->denom_pub_hash));
#endif
- GNUNET_CRYPTO_hash (&coin_public_info->coin_pub,
- sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
- &c_hash);
+
+ TALER_coin_pub_hash (&coin_public_info->coin_pub,
+ coin_public_info->no_age_commitment
+ ? NULL
+ : &coin_public_info->h_age_commitment,
+ &c_hash);
+
if (GNUNET_OK !=
- GNUNET_CRYPTO_rsa_verify (&c_hash,
- coin_public_info->denom_sig.rsa_signature,
- denom_pub->rsa_public_key))
+ TALER_denom_pub_verify (denom_pub,
+ &coin_public_info->denom_sig,
+ &c_hash))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"coin signature is invalid\n");
@@ -109,17 +103,6 @@ TALER_test_coin_valid (const struct TALER_CoinPublicInfo *coin_public_info,
}
-/**
- * Given the coin and the transfer private keys, compute the
- * transfer secret. (Technically, we only need one of the two
- * private keys, but the caller currently trivially only has
- * the two private keys, so we derive one of the public keys
- * internally to this function.)
- *
- * @param coin_priv coin key
- * @param trans_priv transfer private key
- * @param[out] ts computed transfer secret
- */
void
TALER_link_derive_transfer_secret (
const struct TALER_CoinSpendPrivateKeyP *coin_priv,
@@ -134,18 +117,9 @@ TALER_link_derive_transfer_secret (
GNUNET_CRYPTO_ecdh_eddsa (&trans_priv->ecdhe_priv,
&coin_pub.eddsa_pub,
&ts->key));
-
}
-/**
- * Decrypt the shared @a secret from the information in the
- * @a trans_priv and @a coin_pub.
- *
- * @param trans_priv transfer private key
- * @param coin_pub coin public key
- * @param[out] transfer_secret set to the shared secret
- */
void
TALER_link_reveal_transfer_secret (
const struct TALER_TransferPrivateKeyP *trans_priv,
@@ -159,14 +133,6 @@ TALER_link_reveal_transfer_secret (
}
-/**
- * Decrypt the shared @a secret from the information in the
- * @a trans_priv and @a coin_pub.
- *
- * @param trans_pub transfer private key
- * @param coin_priv coin public key
- * @param[out] transfer_secret set to the shared secret
- */
void
TALER_link_recover_transfer_secret (
const struct TALER_TransferPublicKeyP *trans_pub,
@@ -180,17 +146,31 @@ TALER_link_recover_transfer_secret (
}
-/**
- * Setup information for a fresh coin.
- *
- * @param secret_seed seed to use for KDF to derive coin keys
- * @param coin_num_salt number of the coin to include in KDF
- * @param[out] ps value to initialize
- */
void
-TALER_planchet_setup_refresh (const struct TALER_TransferSecretP *secret_seed,
- uint32_t coin_num_salt,
- struct TALER_PlanchetSecretsP *ps)
+TALER_planchet_master_setup_random (
+ struct TALER_PlanchetMasterSecretP *ps)
+{
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ ps,
+ sizeof (*ps));
+}
+
+
+void
+TALER_refresh_master_setup_random (
+ struct TALER_RefreshMasterSecretP *rms)
+{
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ rms,
+ sizeof (*rms));
+}
+
+
+void
+TALER_transfer_secret_to_planchet_secret (
+ const struct TALER_TransferSecretP *secret_seed,
+ uint32_t coin_num_salt,
+ struct TALER_PlanchetMasterSecretP *ps)
{
uint32_t be_salt = htonl (coin_num_salt);
@@ -207,109 +187,186 @@ TALER_planchet_setup_refresh (const struct TALER_TransferSecretP *secret_seed,
}
-/**
- * Setup information for a fresh coin.
- *
- * @param[out] ps value to initialize
- */
void
-TALER_planchet_setup_random (struct TALER_PlanchetSecretsP *ps)
+TALER_planchet_secret_to_transfer_priv (
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
+ uint32_t cnc_num,
+ struct TALER_TransferPrivateKeyP *tpriv)
{
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
- ps,
- sizeof (*ps));
+ uint32_t be_salt = htonl (cnc_num);
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_kdf (tpriv,
+ sizeof (*tpriv),
+ &be_salt,
+ sizeof (be_salt),
+ old_coin_priv,
+ sizeof (*old_coin_priv),
+ rms,
+ sizeof (*rms),
+ "taler-transfer-priv-derivation",
+ strlen ("taler-transfer-priv-derivation"),
+ NULL, 0));
}
-/**
- * Prepare a planchet for tipping. Creates and blinds a coin.
- *
- * @param dk denomination key for the coin to be created
- * @param ps secret planchet internals (for #TALER_planchet_to_coin)
- * @param[out] pd set to the planchet detail for TALER_MERCHANT_tip_pickup() and
- * other withdraw operations
- * @return #GNUNET_OK on success
- */
-int
+void
+TALER_cs_withdraw_nonce_derive (
+ const struct TALER_PlanchetMasterSecretP *ps,
+ struct GNUNET_CRYPTO_CsSessionNonce *nonce)
+{
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (nonce,
+ sizeof (*nonce),
+ "n",
+ strlen ("n"),
+ ps,
+ sizeof(*ps),
+ NULL,
+ 0));
+}
+
+
+void
+TALER_cs_refresh_nonce_derive (
+ const struct TALER_RefreshMasterSecretP *rms,
+ uint32_t coin_num_salt,
+ struct GNUNET_CRYPTO_CsSessionNonce *nonce)
+{
+ uint32_t be_salt = htonl (coin_num_salt);
+
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (nonce,
+ sizeof (*nonce),
+ &be_salt,
+ sizeof (be_salt),
+ "refresh-n",
+ strlen ("refresh-n"),
+ rms,
+ sizeof(*rms),
+ NULL,
+ 0));
+}
+
+
+void
+TALER_rsa_pub_hash (const struct GNUNET_CRYPTO_RsaPublicKey *rsa,
+ struct TALER_RsaPubHashP *h_rsa)
+{
+ GNUNET_CRYPTO_rsa_public_key_hash (rsa,
+ &h_rsa->hash);
+
+}
+
+
+void
+TALER_cs_pub_hash (const struct GNUNET_CRYPTO_CsPublicKey *cs,
+ struct TALER_CsPubHashP *h_cs)
+{
+ GNUNET_CRYPTO_hash (cs,
+ sizeof(*cs),
+ &h_cs->hash);
+}
+
+
+enum GNUNET_GenericReturnValue
TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,
- const struct TALER_PlanchetSecretsP *ps,
- struct TALER_PlanchetDetail *pd)
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ const union GNUNET_CRYPTO_BlindingSecretP *bks,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ const struct TALER_AgeCommitmentHash *ach,
+ struct TALER_CoinPubHashP *c_hash,
+ struct TALER_PlanchetDetail *pd
+ )
{
struct TALER_CoinSpendPublicKeyP coin_pub;
- GNUNET_CRYPTO_eddsa_key_get_public (&ps->coin_priv.eddsa_priv,
+ GNUNET_assert (alg_values->blinding_inputs->cipher ==
+ dk->bsign_pub_key->cipher);
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
&coin_pub.eddsa_pub);
- GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub,
- sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
- &pd->c_hash);
- if (GNUNET_YES !=
- GNUNET_CRYPTO_rsa_blind (&pd->c_hash,
- &ps->blinding_key.bks,
- dk->rsa_public_key,
- &pd->coin_ev,
- &pd->coin_ev_size))
+ if (GNUNET_OK !=
+ TALER_denom_blind (dk,
+ bks,
+ nonce,
+ ach,
+ &coin_pub,
+ alg_values,
+ c_hash,
+ &pd->blinded_planchet))
{
- GNUNET_break_op (0);
+ GNUNET_break (0);
return GNUNET_SYSERR;
}
- GNUNET_CRYPTO_rsa_public_key_hash (dk->rsa_public_key,
- &pd->denom_pub_hash);
+ TALER_denom_pub_hash (dk,
+ &pd->denom_pub_hash);
return GNUNET_OK;
}
-/**
- * Obtain a coin from the planchet's secrets and the blind signature
- * of the exchange.
- *
- * @param dk denomination key, must match what was given to #TALER_planchet_prepare()
- * @param blind_sig blind signature from the exchange
- * @param ps secrets from #TALER_planchet_prepare()
- * @param c_hash hash of the coin's public key for verification of the signature
- * @param[out] coin set to the details of the fresh coin
- * @return #GNUNET_OK on success
- */
-int
-TALER_planchet_to_coin (const struct TALER_DenominationPublicKey *dk,
- const struct GNUNET_CRYPTO_RsaSignature *blind_sig,
- const struct TALER_PlanchetSecretsP *ps,
- const struct GNUNET_HashCode *c_hash,
- struct TALER_FreshCoin *coin)
+void
+TALER_planchet_detail_free (struct TALER_PlanchetDetail *pd)
{
- struct GNUNET_CRYPTO_RsaSignature *sig;
+ TALER_blinded_planchet_free (&pd->blinded_planchet);
+}
+
- sig = GNUNET_CRYPTO_rsa_unblind (blind_sig,
- &ps->blinding_key.bks,
- dk->rsa_public_key);
+enum GNUNET_GenericReturnValue
+TALER_planchet_to_coin (
+ const struct TALER_DenominationPublicKey *dk,
+ const struct TALER_BlindedDenominationSignature *blind_sig,
+ const union GNUNET_CRYPTO_BlindingSecretP *bks,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ const struct TALER_AgeCommitmentHash *ach,
+ const struct TALER_CoinPubHashP *c_hash,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ struct TALER_FreshCoin *coin)
+{
+ if (dk->bsign_pub_key->cipher !=
+ blind_sig->blinded_sig->cipher)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (dk->bsign_pub_key->cipher !=
+ alg_values->blinding_inputs->cipher)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
if (GNUNET_OK !=
- GNUNET_CRYPTO_rsa_verify (c_hash,
- sig,
- dk->rsa_public_key))
+ TALER_denom_sig_unblind (&coin->sig,
+ blind_sig,
+ bks,
+ c_hash,
+ alg_values,
+ dk))
{
GNUNET_break_op (0);
- GNUNET_CRYPTO_rsa_signature_free (sig);
return GNUNET_SYSERR;
}
- coin->sig.rsa_signature = sig;
- coin->coin_priv = ps->coin_priv;
+ if (GNUNET_OK !=
+ TALER_denom_pub_verify (dk,
+ &coin->sig,
+ c_hash))
+ {
+ GNUNET_break_op (0);
+ TALER_denom_sig_free (&coin->sig);
+ return GNUNET_SYSERR;
+ }
+
+ coin->coin_priv = *coin_priv;
+ coin->h_age_commitment = ach;
return GNUNET_OK;
}
-/**
- * Compute the commitment for a /refresh/melt operation from
- * the respective public inputs.
- *
- * @param[out] rc set to the value the wallet must commit to
- * @param kappa number of transfer public keys involved (must be #TALER_CNC_KAPPA)
- * @param num_new_coins number of new coins to be created
- * @param rcs commitments array of @a kappa commitments
- * @param coin_pub public key of the coin to be melted
- * @param amount_with_fee amount to be melted, including fee
- */
void
TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc,
uint32_t kappa,
+ const struct TALER_RefreshMasterSecretP *rms,
uint32_t num_new_coins,
const struct TALER_RefreshCommitmentEntry *rcs,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
@@ -318,6 +375,10 @@ TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc,
struct GNUNET_HashContext *hash_context;
hash_context = GNUNET_CRYPTO_hash_context_start ();
+ if (NULL != rms)
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ rms,
+ sizeof (*rms));
/* first, iterate over transfer public keys for hash_context */
for (unsigned int i = 0; i<kappa; i++)
{
@@ -329,19 +390,16 @@ TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc,
hash_context */
for (unsigned int i = 0; i<num_new_coins; i++)
{
- void *buf;
- size_t buf_size;
+ struct TALER_DenominationHashP denom_hash;
/* The denomination keys should / must all be identical regardless
of what offset we use, so we use [0]. */
GNUNET_assert (kappa > 0); /* sanity check */
- buf_size = GNUNET_CRYPTO_rsa_public_key_encode (
- rcs[0].new_coins[i].dk->rsa_public_key,
- &buf);
+ TALER_denom_pub_hash (rcs[0].new_coins[i].dk,
+ &denom_hash);
GNUNET_CRYPTO_hash_context_read (hash_context,
- buf,
- buf_size);
- GNUNET_free (buf);
+ &denom_hash,
+ sizeof (denom_hash));
}
/* next, add public key of coin and amount being refreshed */
@@ -367,9 +425,8 @@ TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc,
{
const struct TALER_RefreshCoinData *rcd = &rce->new_coins[j];
- GNUNET_CRYPTO_hash_context_read (hash_context,
- rcd->coin_ev,
- rcd->coin_ev_size);
+ TALER_blinded_planchet_hash_ (&rcd->blinded_planchet,
+ hash_context);
}
}
@@ -379,4 +436,109 @@ TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc,
}
+void
+TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_AgeCommitmentHash *ach,
+ struct TALER_CoinPubHashP *coin_h)
+{
+ if (TALER_AgeCommitmentHash_isNullOrZero (ach))
+ {
+ /* No age commitment was set */
+ GNUNET_CRYPTO_hash (&coin_pub->eddsa_pub,
+ sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
+ &coin_h->hash);
+ }
+ else
+ {
+ /* Coin comes with age commitment. Take the hash of the age commitment
+ * into account */
+ struct GNUNET_HashContext *hash_context;
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+
+ GNUNET_CRYPTO_hash_context_read (
+ hash_context,
+ &coin_pub->eddsa_pub,
+ sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey));
+
+ GNUNET_CRYPTO_hash_context_read (
+ hash_context,
+ ach,
+ sizeof(struct TALER_AgeCommitmentHash));
+
+ GNUNET_CRYPTO_hash_context_finish (
+ hash_context,
+ &coin_h->hash);
+ }
+}
+
+
+void
+TALER_coin_ev_hash (const struct TALER_BlindedPlanchet *blinded_planchet,
+ const struct TALER_DenominationHashP *denom_hash,
+ struct TALER_BlindedCoinHashP *bch)
+{
+ struct GNUNET_HashContext *hash_context;
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ denom_hash,
+ sizeof(*denom_hash));
+ TALER_blinded_planchet_hash_ (blinded_planchet,
+ hash_context);
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &bch->hash);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+/**
+ * Structure we hash to compute the group key for
+ * a denomination group.
+ */
+struct DenominationGroupP
+{
+ /**
+ * Value of coins in this denomination group.
+ */
+ struct TALER_AmountNBO value;
+
+ /**
+ * Fee structure for all coins in the group.
+ */
+ struct TALER_DenomFeeSetNBOP fees;
+
+ /**
+ * Age mask for the denomiation, in NBO.
+ */
+ uint32_t age_mask GNUNET_PACKED;
+
+ /**
+ * Cipher used for the denomination, in NBO.
+ */
+ uint32_t cipher GNUNET_PACKED;
+};
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_denomination_group_get_key (
+ const struct TALER_DenominationGroup *dg,
+ struct GNUNET_HashCode *key)
+{
+ struct DenominationGroupP dgp = {
+ .age_mask = htonl (dg->age_mask.bits),
+ .cipher = htonl (dg->cipher)
+ };
+
+ TALER_amount_hton (&dgp.value,
+ &dg->value);
+ TALER_denom_fee_set_hton (&dgp.fees,
+ &dg->fees);
+ GNUNET_CRYPTO_hash (&dgp,
+ sizeof (dgp),
+ key);
+}
+
+
/* end of crypto.c */
diff --git a/src/util/crypto_confirmation.c b/src/util/crypto_confirmation.c
new file mode 100644
index 000000000..99552f150
--- /dev/null
+++ b/src/util/crypto_confirmation.c
@@ -0,0 +1,293 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file util/crypto_confirmation.c
+ * @brief confirmation computation
+ * @author Christian Grothoff
+ * @author Priscilla Huang
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_db_lib.h>
+#include <gcrypt.h>
+
+/**
+ * How long is a TOTP code valid?
+ */
+#define TOTP_VALIDITY_PERIOD GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_SECONDS, 30)
+
+/**
+ * Range of time we allow (plus-minus).
+ */
+#define TIME_INTERVAL_RANGE 2
+
+
+/**
+ * Compute TOTP code at current time with offset
+ * @a time_off for the @a key.
+ *
+ * @param ts current time
+ * @param time_off offset to apply when computing the code
+ * @param key pos_key in binary
+ * @param key_size number of bytes in @a key
+ */
+static uint64_t
+compute_totp (struct GNUNET_TIME_Timestamp ts,
+ int time_off,
+ const void *key,
+ size_t key_size)
+{
+ struct GNUNET_TIME_Absolute now;
+ time_t t;
+ uint64_t ctr;
+ uint8_t hmac[20]; /* SHA1: 20 bytes */
+
+ now = ts.abs_time;
+ while (time_off < 0)
+ {
+ now = GNUNET_TIME_absolute_subtract (now,
+ TOTP_VALIDITY_PERIOD);
+ time_off++;
+ }
+ while (time_off > 0)
+ {
+ now = GNUNET_TIME_absolute_add (now,
+ TOTP_VALIDITY_PERIOD);
+ time_off--;
+ }
+ t = now.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us;
+ ctr = GNUNET_htonll (t / 30LLU);
+
+ {
+ gcry_md_hd_t md;
+ const unsigned char *mc;
+
+ GNUNET_assert (GPG_ERR_NO_ERROR ==
+ gcry_md_open (&md,
+ GCRY_MD_SHA1,
+ GCRY_MD_FLAG_HMAC));
+ GNUNET_assert (GPG_ERR_NO_ERROR ==
+ gcry_md_setkey (md,
+ key,
+ key_size));
+ gcry_md_write (md,
+ &ctr,
+ sizeof (ctr));
+ mc = gcry_md_read (md,
+ GCRY_MD_SHA1);
+ GNUNET_assert (NULL != mc);
+ GNUNET_memcpy (hmac,
+ mc,
+ sizeof (hmac));
+ gcry_md_close (md);
+ }
+
+ {
+ uint32_t code = 0;
+ int offset;
+
+ offset = hmac[sizeof (hmac) - 1] & 0x0f;
+ for (int count = 0; count < 4; count++)
+ code |= ((uint32_t) hmac[offset + 3 - count]) << (8 * count);
+ code &= 0x7fffffff;
+ /* always use 8 digits (maximum) */
+ code = code % 100000000;
+ return code;
+ }
+}
+
+
+int
+TALER_rfc3548_base32decode (const char *val,
+ size_t val_size,
+ void *key,
+ size_t key_len)
+{
+ /**
+ * 32 characters for decoding, using RFC 3548.
+ */
+ static const char *decTable__ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+ unsigned char *udata = key;
+ unsigned int wpos = 0;
+ unsigned int rpos = 0;
+ unsigned int bits = 0;
+ unsigned int vbit = 0;
+
+ while ((rpos < val_size) || (vbit >= 8))
+ {
+ if ((rpos < val_size) && (vbit < 8))
+ {
+ char c = val[rpos++];
+
+ if (c == '=')
+ {
+ /* padding character */
+ if (rpos == val_size)
+ break; /* Ok, 1x '=' padding is allowed */
+ if ( ('=' == val[rpos]) &&
+ (rpos + 1 == val_size) )
+ break; /* Ok, 2x '=' padding is allowed */
+ return -1; /* invalid padding */
+ }
+ const char *p = strchr (decTable__, toupper (c));
+ if (! p)
+ {
+ /* invalid character */
+ return -1;
+ }
+ bits = (bits << 5) | (p - decTable__);
+ vbit += 5;
+ }
+ if (vbit >= 8)
+ {
+ udata[wpos++] = (bits >> (vbit - 8)) & 0xFF;
+ vbit -= 8;
+ }
+ }
+ return wpos;
+}
+
+
+/**
+ * @brief Builds POS confirmation to verify payment.
+ *
+ * @param h_key opaque key for the totp operation
+ * @param h_key_len size of h_key in bytes
+ * @param ts current time
+ * @return Token on success, NULL of failure
+ */
+static char *
+executive_totp (void *h_key,
+ size_t h_key_len,
+ struct GNUNET_TIME_Timestamp ts)
+{
+ uint64_t code; /* totp code */
+ char *ret;
+ ret = NULL;
+
+ for (int i = -TIME_INTERVAL_RANGE; i<= TIME_INTERVAL_RANGE; i++)
+ {
+ code = compute_totp (ts,
+ i,
+ h_key,
+ h_key_len);
+ if (NULL == ret)
+ {
+ GNUNET_asprintf (&ret,
+ "%08llu",
+ (unsigned long long) code);
+ }
+ else
+ {
+ char *tmp;
+
+ GNUNET_asprintf (&tmp,
+ "%s\n%08llu",
+ ret,
+ (unsigned long long) code);
+ GNUNET_free (ret);
+ ret = tmp;
+ }
+ }
+ return ret;
+
+}
+
+
+char *
+TALER_build_pos_confirmation (const char *pos_key,
+ enum TALER_MerchantConfirmationAlgorithm pos_alg,
+ const struct TALER_Amount *total,
+ struct GNUNET_TIME_Timestamp ts)
+{
+ size_t pos_key_length = strlen (pos_key);
+ void *key; /* pos_key in binary */
+ size_t key_len; /* length of the key */
+ char *ret;
+ int dret;
+
+ if (TALER_MCA_NONE == pos_alg)
+ return NULL;
+ key_len = pos_key_length * 5 / 8;
+ key = GNUNET_malloc (key_len);
+ dret = TALER_rfc3548_base32decode (pos_key,
+ pos_key_length,
+ key,
+ key_len);
+ if (-1 == dret)
+ {
+ GNUNET_free (key);
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ GNUNET_assert (dret <= key_len);
+ key_len = (size_t) dret;
+ switch (pos_alg)
+ {
+ case TALER_MCA_NONE:
+ GNUNET_break (0);
+ GNUNET_free (key);
+ return NULL;
+ case TALER_MCA_WITHOUT_PRICE: /* and 30s */
+ /* Return all T-OTP codes in range separated by new lines, e.g.
+ "12345678
+ 24522552
+ 25262425
+ 42543525
+ 25253552"
+ */
+ ret = executive_totp (key,
+ key_len,
+ ts);
+ GNUNET_free (key);
+ return ret;
+ case TALER_MCA_WITH_PRICE:
+ {
+ struct GNUNET_HashCode hkey;
+ struct TALER_AmountNBO ntotal;
+
+ if ( (NULL == total) ||
+ (GNUNET_YES !=
+ TALER_amount_is_valid (total) ) )
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ TALER_amount_hton (&ntotal,
+ total);
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (&hkey,
+ sizeof (hkey),
+ &ntotal,
+ sizeof (ntotal),
+ key,
+ key_len,
+ NULL,
+ 0));
+ GNUNET_free (key);
+ ret = executive_totp (&hkey,
+ sizeof(hkey),
+ ts);
+ GNUNET_free (key);
+ return ret;
+ }
+ }
+ GNUNET_free (key);
+ GNUNET_break (0);
+ return NULL;
+}
diff --git a/src/util/crypto_contract.c b/src/util/crypto_contract.c
new file mode 100644
index 000000000..bec34c983
--- /dev/null
+++ b/src/util/crypto_contract.c
@@ -0,0 +1,661 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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/>
+*/
+/**
+ * @file util/crypto_contract.c
+ * @brief functions for encrypting and decrypting contracts for P2P payments
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include <zlib.h>
+#include "taler_exchange_service.h"
+
+
+/**
+ * Different types of contracts supported.
+ */
+enum ContractFormats
+{
+ /**
+ * The encrypted contract represents a payment offer. The receiver
+ * can merge it into a reserve/account to accept the contract and
+ * obtain the payment.
+ */
+ TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER = 0,
+
+ /**
+ * The encrypted contract represents a payment request.
+ */
+ TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST = 1
+};
+
+
+/**
+ * Nonce used for encryption, 24 bytes.
+ */
+struct NonceP
+{
+ uint8_t nonce[crypto_secretbox_NONCEBYTES];
+};
+
+/**
+ * Specifies a key used for symmetric encryption, 32 bytes.
+ */
+struct SymKeyP
+{
+ uint32_t key[8];
+};
+
+
+/**
+ * Compute @a key.
+ *
+ * @param key_material key for calculation
+ * @param key_m_len length of key
+ * @param nonce nonce for calculation
+ * @param salt salt value for calculation
+ * @param[out] key where to write the en-/description key
+ */
+static void
+derive_key (const void *key_material,
+ size_t key_m_len,
+ const struct NonceP *nonce,
+ const char *salt,
+ struct SymKeyP *key)
+{
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (key,
+ sizeof (*key),
+ /* salt / XTS */
+ nonce,
+ sizeof (*nonce),
+ /* ikm */
+ key_material,
+ key_m_len,
+ /* info chunks */
+ /* The "salt" passed here is actually not something random,
+ but a protocol-specific identifier string. Thus
+ we pass it as a context info to the HKDF */
+ salt,
+ strlen (salt),
+ NULL,
+ 0));
+}
+
+
+/**
+ * Encryption of data.
+ *
+ * @param nonce value to use for the nonce
+ * @param key key which is used to derive a key/iv pair from
+ * @param key_len length of key
+ * @param data data to encrypt
+ * @param data_size size of the data
+ * @param salt salt value which is used for key derivation
+ * @param[out] res ciphertext output
+ * @param[out] res_size size of the ciphertext
+ */
+static void
+blob_encrypt (const struct NonceP *nonce,
+ const void *key,
+ size_t key_len,
+ const void *data,
+ size_t data_size,
+ const char *salt,
+ void **res,
+ size_t *res_size)
+{
+ size_t ciphertext_size;
+ struct SymKeyP skey;
+
+ derive_key (key,
+ key_len,
+ nonce,
+ salt,
+ &skey);
+ ciphertext_size = crypto_secretbox_NONCEBYTES
+ + crypto_secretbox_MACBYTES
+ + data_size;
+ *res_size = ciphertext_size;
+ *res = GNUNET_malloc (ciphertext_size);
+ GNUNET_memcpy (*res,
+ nonce,
+ crypto_secretbox_NONCEBYTES);
+ GNUNET_assert (0 ==
+ crypto_secretbox_easy (*res + crypto_secretbox_NONCEBYTES,
+ data,
+ data_size,
+ (void *) nonce,
+ (void *) &skey));
+}
+
+
+/**
+ * Decryption of data like encrypted recovery document etc.
+ *
+ * @param key key which is used to derive a key/iv pair from
+ * @param key_len length of key
+ * @param data data to decrypt
+ * @param data_size size of the data
+ * @param salt salt value which is used for key derivation
+ * @param[out] res plaintext output
+ * @param[out] res_size size of the plaintext
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+blob_decrypt (const void *key,
+ size_t key_len,
+ const void *data,
+ size_t data_size,
+ const char *salt,
+ void **res,
+ size_t *res_size)
+{
+ const struct NonceP *nonce;
+ struct SymKeyP skey;
+ size_t plaintext_size;
+
+ if (data_size < crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ nonce = data;
+ derive_key (key,
+ key_len,
+ nonce,
+ salt,
+ &skey);
+ plaintext_size = data_size - (crypto_secretbox_NONCEBYTES
+ + crypto_secretbox_MACBYTES);
+ *res = GNUNET_malloc (plaintext_size);
+ *res_size = plaintext_size;
+ if (0 != crypto_secretbox_open_easy (*res,
+ data + crypto_secretbox_NONCEBYTES,
+ data_size - crypto_secretbox_NONCEBYTES,
+ (void *) nonce,
+ (void *) &skey))
+ {
+ GNUNET_break (0);
+ GNUNET_free (*res);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Header for encrypted contracts.
+ */
+struct ContractHeaderP
+{
+ /**
+ * Type of the contract, in NBO.
+ */
+ uint32_t ctype;
+
+ /**
+ * Length of the encrypted contract, in NBO.
+ */
+ uint32_t clen;
+};
+
+
+/**
+ * Header for encrypted contracts.
+ */
+struct ContractHeaderMergeP
+{
+ /**
+ * Generic header.
+ */
+ struct ContractHeaderP header;
+
+ /**
+ * Private key with the merge capability.
+ */
+ struct TALER_PurseMergePrivateKeyP merge_priv;
+};
+
+
+/**
+ * Salt we use when encrypting contracts for merge.
+ */
+#define MERGE_SALT "p2p-merge-contract"
+
+
+void
+TALER_CRYPTO_contract_encrypt_for_merge (
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const struct TALER_PurseMergePrivateKeyP *merge_priv,
+ const json_t *contract_terms,
+ void **econtract,
+ size_t *econtract_size)
+{
+ struct GNUNET_HashCode key;
+ char *cstr;
+ size_t clen;
+ void *xbuf;
+ struct ContractHeaderMergeP *hdr;
+ struct NonceP nonce;
+ uLongf cbuf_size;
+ int ret;
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv,
+ &purse_pub->eddsa_pub,
+ &key));
+ cstr = json_dumps (contract_terms,
+ JSON_COMPACT | JSON_SORT_KEYS);
+ clen = strlen (cstr);
+ cbuf_size = compressBound (clen);
+ xbuf = GNUNET_malloc (cbuf_size);
+ ret = compress (xbuf,
+ &cbuf_size,
+ (const Bytef *) cstr,
+ clen);
+ GNUNET_assert (Z_OK == ret);
+ free (cstr);
+ hdr = GNUNET_malloc (sizeof (*hdr) + cbuf_size);
+ hdr->header.ctype = htonl (TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER);
+ hdr->header.clen = htonl ((uint32_t) clen);
+ hdr->merge_priv = *merge_priv;
+ GNUNET_memcpy (&hdr[1],
+ xbuf,
+ cbuf_size);
+ GNUNET_free (xbuf);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &nonce,
+ sizeof (nonce));
+ blob_encrypt (&nonce,
+ &key,
+ sizeof (key),
+ hdr,
+ sizeof (*hdr) + cbuf_size,
+ MERGE_SALT,
+ econtract,
+ econtract_size);
+ GNUNET_free (hdr);
+}
+
+
+json_t *
+TALER_CRYPTO_contract_decrypt_for_merge (
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const void *econtract,
+ size_t econtract_size,
+ struct TALER_PurseMergePrivateKeyP *merge_priv)
+{
+ struct GNUNET_HashCode key;
+ void *xhdr;
+ size_t hdr_size;
+ const struct ContractHeaderMergeP *hdr;
+ char *cstr;
+ uLongf clen;
+ json_error_t json_error;
+ json_t *ret;
+
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv,
+ &purse_pub->eddsa_pub,
+ &key))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ blob_decrypt (&key,
+ sizeof (key),
+ econtract,
+ econtract_size,
+ MERGE_SALT,
+ &xhdr,
+ &hdr_size))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ if (hdr_size < sizeof (*hdr))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (xhdr);
+ return NULL;
+ }
+ hdr = xhdr;
+ if (TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER != ntohl (hdr->header.ctype))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (xhdr);
+ return NULL;
+ }
+ clen = ntohl (hdr->header.clen);
+ if (clen >= GNUNET_MAX_MALLOC_CHECKED)
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (xhdr);
+ return NULL;
+ }
+ cstr = GNUNET_malloc (clen + 1);
+ if (Z_OK !=
+ uncompress ((Bytef *) cstr,
+ &clen,
+ (const Bytef *) &hdr[1],
+ hdr_size - sizeof (*hdr)))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (cstr);
+ GNUNET_free (xhdr);
+ return NULL;
+ }
+ *merge_priv = hdr->merge_priv;
+ GNUNET_free (xhdr);
+ ret = json_loadb ((char *) cstr,
+ clen,
+ JSON_DECODE_ANY,
+ &json_error);
+ if (NULL == ret)
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (cstr);
+ return NULL;
+ }
+ GNUNET_free (cstr);
+ return ret;
+}
+
+
+/**
+ * Salt we use when encrypting contracts for merge.
+ */
+#define DEPOSIT_SALT "p2p-deposit-contract"
+
+
+void
+TALER_CRYPTO_contract_encrypt_for_deposit (
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const json_t *contract_terms,
+ void **econtract,
+ size_t *econtract_size)
+{
+ struct GNUNET_HashCode key;
+ char *cstr;
+ size_t clen;
+ void *xbuf;
+ struct ContractHeaderP *hdr;
+ struct NonceP nonce;
+ uLongf cbuf_size;
+ int ret;
+ void *xecontract;
+ size_t xecontract_size;
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv,
+ &purse_pub->eddsa_pub,
+ &key));
+ cstr = json_dumps (contract_terms,
+ JSON_COMPACT | JSON_SORT_KEYS);
+ GNUNET_assert (NULL != cstr);
+ clen = strlen (cstr);
+ cbuf_size = compressBound (clen);
+ xbuf = GNUNET_malloc (cbuf_size);
+ ret = compress (xbuf,
+ &cbuf_size,
+ (const Bytef *) cstr,
+ clen);
+ GNUNET_assert (Z_OK == ret);
+ free (cstr);
+ hdr = GNUNET_malloc (sizeof (*hdr) + cbuf_size);
+ hdr->ctype = htonl (TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST);
+ hdr->clen = htonl ((uint32_t) clen);
+ GNUNET_memcpy (&hdr[1],
+ xbuf,
+ cbuf_size);
+ GNUNET_free (xbuf);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &nonce,
+ sizeof (nonce));
+ blob_encrypt (&nonce,
+ &key,
+ sizeof (key),
+ hdr,
+ sizeof (*hdr) + cbuf_size,
+ DEPOSIT_SALT,
+ &xecontract,
+ &xecontract_size);
+ GNUNET_free (hdr);
+ /* prepend purse_pub */
+ *econtract = GNUNET_malloc (xecontract_size + sizeof (*purse_pub));
+ GNUNET_memcpy (*econtract,
+ purse_pub,
+ sizeof (*purse_pub));
+ GNUNET_memcpy (sizeof (*purse_pub) + *econtract,
+ xecontract,
+ xecontract_size);
+ *econtract_size = xecontract_size + sizeof (*purse_pub);
+ GNUNET_free (xecontract);
+}
+
+
+json_t *
+TALER_CRYPTO_contract_decrypt_for_deposit (
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const void *econtract,
+ size_t econtract_size)
+{
+ const struct TALER_PurseContractPublicKeyP *purse_pub = econtract;
+
+ if (econtract_size < sizeof (*purse_pub))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ struct GNUNET_HashCode key;
+ void *xhdr;
+ size_t hdr_size;
+ const struct ContractHeaderP *hdr;
+ char *cstr;
+ uLongf clen;
+ json_error_t json_error;
+ json_t *ret;
+
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv,
+ &purse_pub->eddsa_pub,
+ &key))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ econtract += sizeof (*purse_pub);
+ econtract_size -= sizeof (*purse_pub);
+ if (GNUNET_OK !=
+ blob_decrypt (&key,
+ sizeof (key),
+ econtract,
+ econtract_size,
+ DEPOSIT_SALT,
+ &xhdr,
+ &hdr_size))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ if (hdr_size < sizeof (*hdr))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (xhdr);
+ return NULL;
+ }
+ hdr = xhdr;
+ if (TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST != ntohl (hdr->ctype))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (xhdr);
+ return NULL;
+ }
+ clen = ntohl (hdr->clen);
+ if (clen >= GNUNET_MAX_MALLOC_CHECKED)
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (xhdr);
+ return NULL;
+ }
+ cstr = GNUNET_malloc (clen + 1);
+ if (Z_OK !=
+ uncompress ((Bytef *) cstr,
+ &clen,
+ (const Bytef *) &hdr[1],
+ hdr_size - sizeof (*hdr)))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (cstr);
+ GNUNET_free (xhdr);
+ return NULL;
+ }
+ GNUNET_free (xhdr);
+ ret = json_loadb ((char *) cstr,
+ clen,
+ JSON_DECODE_ANY,
+ &json_error);
+ if (NULL == ret)
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (cstr);
+ return NULL;
+ }
+ GNUNET_free (cstr);
+ return ret;
+}
+
+
+/**
+ * Salt we use when encrypting KYC attributes.
+ */
+#define ATTRIBUTE_SALT "kyc-attributes"
+
+
+void
+TALER_CRYPTO_kyc_attributes_encrypt (
+ const struct TALER_AttributeEncryptionKeyP *key,
+ const json_t *attr,
+ void **enc_attr,
+ size_t *enc_attr_size)
+{
+ uLongf cbuf_size;
+ char *cstr;
+ uLongf clen;
+ void *xbuf;
+ int ret;
+ uint32_t belen;
+ struct NonceP nonce;
+
+ cstr = json_dumps (attr,
+ JSON_COMPACT | JSON_SORT_KEYS);
+ GNUNET_assert (NULL != cstr);
+ clen = strlen (cstr);
+ GNUNET_assert (clen <= UINT32_MAX);
+ cbuf_size = compressBound (clen);
+ xbuf = GNUNET_malloc (cbuf_size + sizeof (uint32_t));
+ belen = htonl ((uint32_t) clen);
+ GNUNET_memcpy (xbuf,
+ &belen,
+ sizeof (belen));
+ ret = compress (xbuf + 4,
+ &cbuf_size,
+ (const Bytef *) cstr,
+ clen);
+ GNUNET_assert (Z_OK == ret);
+ free (cstr);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &nonce,
+ sizeof (nonce));
+ blob_encrypt (&nonce,
+ key,
+ sizeof (*key),
+ xbuf,
+ cbuf_size + sizeof (uint32_t),
+ ATTRIBUTE_SALT,
+ enc_attr,
+ enc_attr_size);
+ GNUNET_free (xbuf);
+}
+
+
+json_t *
+TALER_CRYPTO_kyc_attributes_decrypt (
+ const struct TALER_AttributeEncryptionKeyP *key,
+ const void *enc_attr,
+ size_t enc_attr_size)
+{
+ void *xhdr;
+ size_t hdr_size;
+ char *cstr;
+ uLongf clen;
+ json_error_t json_error;
+ json_t *ret;
+ uint32_t belen;
+
+ if (GNUNET_OK !=
+ blob_decrypt (key,
+ sizeof (*key),
+ enc_attr,
+ enc_attr_size,
+ ATTRIBUTE_SALT,
+ &xhdr,
+ &hdr_size))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ GNUNET_memcpy (&belen,
+ xhdr,
+ sizeof (belen));
+ clen = ntohl (belen);
+ if (clen >= GNUNET_MAX_MALLOC_CHECKED)
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (xhdr);
+ return NULL;
+ }
+ cstr = GNUNET_malloc (clen + 1);
+ if (Z_OK !=
+ uncompress ((Bytef *) cstr,
+ &clen,
+ (const Bytef *) (xhdr + sizeof (uint32_t)),
+ hdr_size - sizeof (uint32_t)))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (cstr);
+ GNUNET_free (xhdr);
+ return NULL;
+ }
+ GNUNET_free (xhdr);
+ ret = json_loadb ((char *) cstr,
+ clen,
+ JSON_DECODE_ANY,
+ &json_error);
+ if (NULL == ret)
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (cstr);
+ return NULL;
+ }
+ GNUNET_free (cstr);
+ return ret;
+}
diff --git a/src/util/crypto_helper_common.c b/src/util/crypto_helper_common.c
new file mode 100644
index 000000000..9eddb7dcb
--- /dev/null
+++ b/src/util/crypto_helper_common.c
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 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 util/crypto_helper_common.c
+ * @brief Common functions for the exchange security modules
+ * @author Florian Dold <dold@taler.net>
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+
+
+enum GNUNET_GenericReturnValue
+TALER_crypto_helper_send_all (int sock,
+ const void *buf,
+ size_t buf_size)
+{
+ size_t off = 0;
+
+ while (off < buf_size)
+ {
+ ssize_t ret;
+
+ ret = send (sock,
+ buf + off,
+ buf_size - off,
+ 0);
+ if (ret < 0)
+ {
+ if (EINTR == errno)
+ continue;
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (ret > 0);
+ off += ret;
+ }
+ return GNUNET_OK;
+}
diff --git a/src/util/crypto_helper_common.h b/src/util/crypto_helper_common.h
new file mode 100644
index 000000000..cf8b28141
--- /dev/null
+++ b/src/util/crypto_helper_common.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2021 Taler Systems SA
+
+ GNU 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.
+
+ GNU 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 util/crypto_helper_common.h
+ * @brief Common functions for the exchange security modules
+ * @author Florian Dold <dold@taler.net>
+ */
+#ifndef CRYPTO_HELPER_COMMON_H
+#define CRYPTO_HELPER_COMMON_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_network_lib.h>
+
+/**
+ * Send all @a buf_size bytes from @a buf to @a sock.
+ *
+ * @param sock socket to send on
+ * @param buf data to send
+ * @param buf_size number of bytes in @a buf
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_crypto_helper_send_all (int sock,
+ const void *buf,
+ size_t buf_size);
+
+#endif
diff --git a/src/util/crypto_helper_cs.c b/src/util/crypto_helper_cs.c
new file mode 100644
index 000000000..4c4a56feb
--- /dev/null
+++ b/src/util/crypto_helper_cs.c
@@ -0,0 +1,1316 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 2021, 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 util/crypto_helper_cs.c
+ * @brief utility functions for running out-of-process private key operations
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler-exchange-secmod-cs.h"
+#include <poll.h>
+#include "crypto_helper_common.h"
+
+
+struct TALER_CRYPTO_CsDenominationHelper
+{
+ /**
+ * Function to call with updates to available key material.
+ */
+ TALER_CRYPTO_CsDenominationKeyStatusCallback dkc;
+
+ /**
+ * Closure for @e dkc
+ */
+ void *dkc_cls;
+
+ /**
+ * Socket address of the denomination helper process.
+ * Used to reconnect if the connection breaks.
+ */
+ struct sockaddr_un sa;
+
+ /**
+ * The UNIX domain socket, -1 if we are currently not connected.
+ */
+ int sock;
+
+ /**
+ * Have we ever been sync'ed?
+ */
+ bool synced;
+};
+
+
+/**
+ * Disconnect from the helper process. Updates
+ * @e sock field in @a dh.
+ *
+ * @param[in,out] dh handle to tear down connection of
+ */
+static void
+do_disconnect (struct TALER_CRYPTO_CsDenominationHelper *dh)
+{
+ GNUNET_break (0 == close (dh->sock));
+ dh->sock = -1;
+ dh->synced = false;
+}
+
+
+/**
+ * Try to connect to the helper process. Updates
+ * @e sock field in @a dh.
+ *
+ * @param[in,out] dh handle to establish connection for
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+try_connect (struct TALER_CRYPTO_CsDenominationHelper *dh)
+{
+ if (-1 != dh->sock)
+ return GNUNET_OK;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Establishing connection!\n");
+ dh->sock = socket (AF_UNIX,
+ SOCK_STREAM,
+ 0);
+ if (-1 == dh->sock)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "socket");
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ connect (dh->sock,
+ (const struct sockaddr *) &dh->sa,
+ sizeof (dh->sa)))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "connect",
+ dh->sa.sun_path);
+ do_disconnect (dh);
+ return GNUNET_SYSERR;
+ }
+ TALER_CRYPTO_helper_cs_poll (dh);
+ return GNUNET_OK;
+}
+
+
+struct TALER_CRYPTO_CsDenominationHelper *
+TALER_CRYPTO_helper_cs_connect (
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section,
+ TALER_CRYPTO_CsDenominationKeyStatusCallback dkc,
+ void *dkc_cls)
+{
+ struct TALER_CRYPTO_CsDenominationHelper *dh;
+ char *unixpath;
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-cs",
+ section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ secname,
+ "UNIXPATH",
+ &unixpath))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "UNIXPATH");
+ GNUNET_free (secname);
+ return NULL;
+ }
+ /* we use >= here because we want the sun_path to always
+ be 0-terminated */
+ if (strlen (unixpath) >= sizeof (dh->sa.sun_path))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "UNIXPATH",
+ "path too long");
+ GNUNET_free (unixpath);
+ GNUNET_free (secname);
+ return NULL;
+ }
+ GNUNET_free (secname);
+ dh = GNUNET_new (struct TALER_CRYPTO_CsDenominationHelper);
+ dh->dkc = dkc;
+ dh->dkc_cls = dkc_cls;
+ dh->sa.sun_family = AF_UNIX;
+ strncpy (dh->sa.sun_path,
+ unixpath,
+ sizeof (dh->sa.sun_path) - 1);
+ GNUNET_free (unixpath);
+ dh->sock = -1;
+ if (GNUNET_OK !=
+ try_connect (dh))
+ {
+ TALER_CRYPTO_helper_cs_disconnect (dh);
+ return NULL;
+ }
+ return dh;
+}
+
+
+/**
+ * Handle a #TALER_HELPER_CS_MT_AVAIL message from the helper.
+ *
+ * @param dh helper context
+ * @param hdr message that we received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_mt_avail (struct TALER_CRYPTO_CsDenominationHelper *dh,
+ const struct GNUNET_MessageHeader *hdr)
+{
+ const struct TALER_CRYPTO_CsKeyAvailableNotification *kan
+ = (const struct TALER_CRYPTO_CsKeyAvailableNotification *) hdr;
+ const char *buf = (const char *) &kan[1];
+ const char *section_name;
+ uint16_t snl;
+
+ if (sizeof (*kan) > ntohs (hdr->size))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ snl = ntohs (kan->section_name_len);
+ if (ntohs (hdr->size) != sizeof (*kan) + snl)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 == snl)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ section_name = buf;
+ if ('\0' != section_name[snl - 1])
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub;
+ struct TALER_CsPubHashP h_cs;
+
+ bsign_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
+ bsign_pub->cipher = GNUNET_CRYPTO_BSA_CS;
+ bsign_pub->rc = 1;
+ bsign_pub->details.cs_public_key = kan->denom_pub;
+
+ GNUNET_CRYPTO_hash (&bsign_pub->details.cs_public_key,
+ sizeof (bsign_pub->details.cs_public_key),
+ &bsign_pub->pub_key_hash);
+ h_cs.hash = bsign_pub->pub_key_hash;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received CS key %s (%s)\n",
+ GNUNET_h2s (&h_cs.hash),
+ section_name);
+ if (GNUNET_OK !=
+ TALER_exchange_secmod_cs_verify (
+ &h_cs,
+ section_name,
+ GNUNET_TIME_timestamp_ntoh (kan->anchor_time),
+ GNUNET_TIME_relative_ntoh (kan->duration_withdraw),
+ &kan->secm_pub,
+ &kan->secm_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_CRYPTO_blind_sign_pub_decref (bsign_pub);
+ return GNUNET_SYSERR;
+ }
+ dh->dkc (dh->dkc_cls,
+ section_name,
+ GNUNET_TIME_timestamp_ntoh (kan->anchor_time),
+ GNUNET_TIME_relative_ntoh (kan->duration_withdraw),
+ &h_cs,
+ bsign_pub,
+ &kan->secm_pub,
+ &kan->secm_sig);
+ GNUNET_CRYPTO_blind_sign_pub_decref (bsign_pub);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle a #TALER_HELPER_CS_MT_PURGE message from the helper.
+ *
+ * @param dh helper context
+ * @param hdr message that we received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_mt_purge (struct TALER_CRYPTO_CsDenominationHelper *dh,
+ const struct GNUNET_MessageHeader *hdr)
+{
+ const struct TALER_CRYPTO_CsKeyPurgeNotification *pn
+ = (const struct TALER_CRYPTO_CsKeyPurgeNotification *) hdr;
+
+ if (sizeof (*pn) != ntohs (hdr->size))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received revocation of denomination key %s\n",
+ GNUNET_h2s (&pn->h_cs.hash));
+ dh->dkc (dh->dkc_cls,
+ NULL,
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_ZERO,
+ &pn->h_cs,
+ NULL,
+ NULL,
+ NULL);
+ return GNUNET_OK;
+}
+
+
+void
+TALER_CRYPTO_helper_cs_poll (struct TALER_CRYPTO_CsDenominationHelper *dh)
+{
+ char buf[UINT16_MAX];
+ size_t off = 0;
+ unsigned int retry_limit = 3;
+ const struct GNUNET_MessageHeader *hdr
+ = (const struct GNUNET_MessageHeader *) buf;
+
+ if (GNUNET_OK !=
+ try_connect (dh))
+ return; /* give up */
+ while (1)
+ {
+ uint16_t msize;
+ ssize_t ret;
+
+ ret = recv (dh->sock,
+ buf + off,
+ sizeof (buf) - off,
+ (dh->synced && (0 == off))
+ ? MSG_DONTWAIT
+ : 0);
+ if (ret < 0)
+ {
+ if (EINTR == errno)
+ continue;
+ if (EAGAIN == errno)
+ {
+ GNUNET_assert (dh->synced);
+ GNUNET_assert (0 == off);
+ break;
+ }
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "recv");
+ do_disconnect (dh);
+ if (0 == retry_limit)
+ return; /* give up */
+ if (GNUNET_OK !=
+ try_connect (dh))
+ return; /* give up */
+ retry_limit--;
+ continue;
+ }
+ if (0 == ret)
+ {
+ GNUNET_break (0 == off);
+ return;
+ }
+ off += ret;
+more:
+ if (off < sizeof (struct GNUNET_MessageHeader))
+ continue;
+ msize = ntohs (hdr->size);
+ if (off < msize)
+ continue;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received message of type %u and length %u\n",
+ (unsigned int) ntohs (hdr->type),
+ (unsigned int) msize);
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_CS_MT_AVAIL:
+ if (GNUNET_OK !=
+ handle_mt_avail (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return;
+ }
+ break;
+ case TALER_HELPER_CS_MT_PURGE:
+ if (GNUNET_OK !=
+ handle_mt_purge (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return;
+ }
+ break;
+ case TALER_HELPER_CS_SYNCED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Now synchronized with CS helper\n");
+ dh->synced = true;
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Received unexpected message of type %d (len: %u)\n",
+ (unsigned int) ntohs (hdr->type),
+ (unsigned int) msize);
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return;
+ }
+ memmove (buf,
+ &buf[msize],
+ off - msize);
+ off -= msize;
+ goto more;
+ }
+}
+
+
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_cs_sign (
+ struct TALER_CRYPTO_CsDenominationHelper *dh,
+ const struct TALER_CRYPTO_CsSignRequest *req,
+ bool for_melt,
+ struct TALER_BlindedDenominationSignature *bs)
+{
+ enum TALER_ErrorCode ec = TALER_EC_INVALID;
+ const struct TALER_CsPubHashP *h_cs = req->h_cs;
+
+ memset (bs,
+ 0,
+ sizeof (*bs));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting signature process\n");
+ if (GNUNET_OK !=
+ try_connect (dh))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to connect to helper\n");
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting signature\n");
+ {
+ char buf[sizeof (struct TALER_CRYPTO_CsSignRequestMessage)];
+ struct TALER_CRYPTO_CsSignRequestMessage *sr
+ = (struct TALER_CRYPTO_CsSignRequestMessage *) buf;
+
+ sr->header.size = htons (sizeof (buf));
+ sr->header.type = htons (TALER_HELPER_CS_MT_REQ_SIGN);
+ sr->for_melt = htonl (for_melt ? 1 : 0);
+ sr->h_cs = *h_cs;
+ sr->message = *req->blinded_planchet;
+ if (GNUNET_OK !=
+ TALER_crypto_helper_send_all (dh->sock,
+ buf,
+ sizeof (buf)))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "send");
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Awaiting reply\n");
+ {
+ char buf[UINT16_MAX];
+ size_t off = 0;
+ const struct GNUNET_MessageHeader *hdr
+ = (const struct GNUNET_MessageHeader *) buf;
+ bool finished = false;
+
+ while (1)
+ {
+ uint16_t msize;
+ ssize_t ret;
+
+ ret = recv (dh->sock,
+ &buf[off],
+ sizeof (buf) - off,
+ (finished && (0 == off))
+ ? MSG_DONTWAIT
+ : 0);
+ if (ret < 0)
+ {
+ if (EINTR == errno)
+ continue;
+ if (EAGAIN == errno)
+ {
+ GNUNET_assert (finished);
+ GNUNET_assert (0 == off);
+ return ec;
+ }
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "recv");
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ break;
+ }
+ if (0 == ret)
+ {
+ GNUNET_break (0 == off);
+ if (! finished)
+ ec = TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ return ec;
+ }
+ off += ret;
+more:
+ if (off < sizeof (struct GNUNET_MessageHeader))
+ continue;
+ msize = ntohs (hdr->size);
+ if (off < msize)
+ continue;
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_CS_MT_RES_SIGNATURE:
+ if (msize != sizeof (struct TALER_CRYPTO_SignResponse))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ goto end;
+ }
+ if (finished)
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ goto end;
+ }
+ {
+ const struct TALER_CRYPTO_SignResponse *sr =
+ (const struct TALER_CRYPTO_SignResponse *) buf;
+ struct GNUNET_CRYPTO_BlindedSignature *blinded_sig;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received signature\n");
+ ec = TALER_EC_NONE;
+ finished = true;
+ blinded_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
+ blinded_sig->cipher = GNUNET_CRYPTO_BSA_CS;
+ blinded_sig->rc = 1;
+ blinded_sig->details.blinded_cs_answer.b = ntohl (sr->b);
+ blinded_sig->details.blinded_cs_answer.s_scalar = sr->cs_answer;
+ bs->blinded_sig = blinded_sig;
+ break;
+ }
+ case TALER_HELPER_CS_MT_RES_SIGN_FAILURE:
+ if (msize != sizeof (struct TALER_CRYPTO_SignFailure))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ goto end;
+ }
+ {
+ const struct TALER_CRYPTO_SignFailure *sf =
+ (const struct TALER_CRYPTO_SignFailure *) buf;
+
+ ec = (enum TALER_ErrorCode) ntohl (sf->ec);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Signing failed with status %d!\n",
+ ec);
+ finished = true;
+ break;
+ }
+ case TALER_HELPER_CS_MT_AVAIL:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received new key!\n");
+ if (GNUNET_OK !=
+ handle_mt_avail (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ goto end;
+ }
+ break; /* while(1) loop ensures we recvfrom() again */
+ case TALER_HELPER_CS_MT_PURGE:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received revocation!\n");
+ if (GNUNET_OK !=
+ handle_mt_purge (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ goto end;
+ }
+ break; /* while(1) loop ensures we recvfrom() again */
+ case TALER_HELPER_CS_SYNCED:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Synchronized add odd time with CS helper!\n");
+ dh->synced = true;
+ break;
+ default:
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Received unexpected message of type %u\n",
+ ntohs (hdr->type));
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ goto end;
+ }
+ memmove (buf,
+ &buf[msize],
+ off - msize);
+ off -= msize;
+ goto more;
+ } /* while(1) */
+end:
+ if (finished)
+ TALER_blinded_denom_sig_free (bs);
+ return ec;
+ }
+}
+
+
+void
+TALER_CRYPTO_helper_cs_revoke (
+ struct TALER_CRYPTO_CsDenominationHelper *dh,
+ const struct TALER_CsPubHashP *h_cs)
+{
+ struct TALER_CRYPTO_CsRevokeRequest rr = {
+ .header.size = htons (sizeof (rr)),
+ .header.type = htons (TALER_HELPER_CS_MT_REQ_REVOKE),
+ .h_cs = *h_cs
+ };
+
+ if (GNUNET_OK !=
+ try_connect (dh))
+ return; /* give up */
+ if (GNUNET_OK !=
+ TALER_crypto_helper_send_all (dh->sock,
+ &rr,
+ sizeof (rr)))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "send");
+ do_disconnect (dh);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requested revocation of denomination key %s\n",
+ GNUNET_h2s (&h_cs->hash));
+}
+
+
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_cs_r_derive (struct TALER_CRYPTO_CsDenominationHelper *dh,
+ const struct TALER_CRYPTO_CsDeriveRequest *cdr,
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP *crp)
+{
+ enum TALER_ErrorCode ec = TALER_EC_INVALID;
+ const struct TALER_CsPubHashP *h_cs = cdr->h_cs;
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce = cdr->nonce;
+
+ memset (crp,
+ 0,
+ sizeof (*crp));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting R derivation process\n");
+ if (GNUNET_OK !=
+ try_connect (dh))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to connect to helper\n");
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting R\n");
+ {
+ struct TALER_CRYPTO_CsRDeriveRequest rdr = {
+ .header.size = htons (sizeof (rdr)),
+ .header.type = htons (TALER_HELPER_CS_MT_REQ_RDERIVE),
+ .for_melt = htonl (for_melt ? 1 : 0),
+ .h_cs = *h_cs,
+ .nonce = *nonce
+ };
+
+ if (GNUNET_OK !=
+ TALER_crypto_helper_send_all (dh->sock,
+ &rdr,
+ sizeof (rdr)))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "send");
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Awaiting reply\n");
+ {
+ char buf[UINT16_MAX];
+ size_t off = 0;
+ const struct GNUNET_MessageHeader *hdr
+ = (const struct GNUNET_MessageHeader *) buf;
+ bool finished = false;
+
+ while (1)
+ {
+ uint16_t msize;
+ ssize_t ret;
+
+ ret = recv (dh->sock,
+ &buf[off],
+ sizeof (buf) - off,
+ (finished && (0 == off))
+ ? MSG_DONTWAIT
+ : 0);
+ if (ret < 0)
+ {
+ if (EINTR == errno)
+ continue;
+ if (EAGAIN == errno)
+ {
+ GNUNET_assert (finished);
+ GNUNET_assert (0 == off);
+ return ec;
+ }
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "recv");
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+ if (0 == ret)
+ {
+ GNUNET_break (0 == off);
+ if (! finished)
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ return ec;
+ }
+ off += ret;
+more:
+ if (off < sizeof (struct GNUNET_MessageHeader))
+ continue;
+ msize = ntohs (hdr->size);
+ if (off < msize)
+ continue;
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_CS_MT_RES_RDERIVE:
+ if (msize != sizeof (struct TALER_CRYPTO_RDeriveResponse))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ if (finished)
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ {
+ const struct TALER_CRYPTO_RDeriveResponse *rdr =
+ (const struct TALER_CRYPTO_RDeriveResponse *) buf;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received R\n");
+ finished = true;
+ ec = TALER_EC_NONE;
+ *crp = rdr->r_pub;
+ break;
+ }
+ case TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE:
+ if (msize != sizeof (struct TALER_CRYPTO_RDeriveFailure))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ {
+ const struct TALER_CRYPTO_RDeriveFailure *rdf =
+ (const struct TALER_CRYPTO_RDeriveFailure *) buf;
+
+ ec = (enum TALER_ErrorCode) ntohl (rdf->ec);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "R derivation failed!\n");
+ finished = true;
+ break;
+ }
+ case TALER_HELPER_CS_MT_AVAIL:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received new key!\n");
+ if (GNUNET_OK !=
+ handle_mt_avail (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ break; /* while(1) loop ensures we recvfrom() again */
+ case TALER_HELPER_CS_MT_PURGE:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received revocation!\n");
+ if (GNUNET_OK !=
+ handle_mt_purge (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ break; /* while(1) loop ensures we recvfrom() again */
+ case TALER_HELPER_CS_SYNCED:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Synchronized add odd time with CS helper!\n");
+ dh->synced = true;
+ break;
+ default:
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Received unexpected message of type %u\n",
+ ntohs (hdr->type));
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ memmove (buf,
+ &buf[msize],
+ off - msize);
+ off -= msize;
+ goto more;
+ } /* while(1) */
+ }
+}
+
+
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_cs_batch_sign (
+ struct TALER_CRYPTO_CsDenominationHelper *dh,
+ unsigned int reqs_length,
+ const struct TALER_CRYPTO_CsSignRequest reqs[static reqs_length],
+ bool for_melt,
+ struct TALER_BlindedDenominationSignature bss[static reqs_length])
+{
+ enum TALER_ErrorCode ec = TALER_EC_INVALID;
+ unsigned int rpos;
+ unsigned int rend;
+ unsigned int wpos;
+
+ memset (bss,
+ 0,
+ sizeof (*bss) * reqs_length);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting signature process\n");
+ if (GNUNET_OK !=
+ try_connect (dh))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to connect to helper\n");
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting %u signatures\n",
+ reqs_length);
+ rpos = 0;
+ rend = 0;
+ wpos = 0;
+ while (rpos < reqs_length)
+ {
+ unsigned int mlen = sizeof (struct TALER_CRYPTO_BatchSignRequest);
+
+ while ( (rend < reqs_length) &&
+ (mlen + sizeof (struct TALER_CRYPTO_CsSignRequestMessage)
+ < UINT16_MAX) )
+ {
+ mlen += sizeof (struct TALER_CRYPTO_CsSignRequestMessage);
+ rend++;
+ }
+ {
+ char obuf[mlen] GNUNET_ALIGN;
+ struct TALER_CRYPTO_BatchSignRequest *bsr
+ = (struct TALER_CRYPTO_BatchSignRequest *) obuf;
+ void *wbuf;
+
+ bsr->header.type = htons (TALER_HELPER_CS_MT_REQ_BATCH_SIGN);
+ bsr->header.size = htons (mlen);
+ bsr->batch_size = htonl (rend - rpos);
+ wbuf = &bsr[1];
+ for (unsigned int i = rpos; i<rend; i++)
+ {
+ struct TALER_CRYPTO_CsSignRequestMessage *csm = wbuf;
+ const struct TALER_CRYPTO_CsSignRequest *csr = &reqs[i];
+
+ csm->header.size = htons (sizeof (*csm));
+ csm->header.type = htons (TALER_HELPER_CS_MT_REQ_SIGN);
+ csm->for_melt = htonl (for_melt ? 1 : 0);
+ csm->h_cs = *csr->h_cs;
+ csm->message = *csr->blinded_planchet;
+ wbuf += sizeof (*csm);
+ }
+ GNUNET_assert (wbuf == &obuf[mlen]);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Sending batch request [%u-%u)\n",
+ rpos,
+ rend);
+ if (GNUNET_OK !=
+ TALER_crypto_helper_send_all (dh->sock,
+ obuf,
+ sizeof (obuf)))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "send");
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+ } /* end of obuf scope */
+ rpos = rend;
+ {
+ char buf[UINT16_MAX];
+ size_t off = 0;
+ const struct GNUNET_MessageHeader *hdr
+ = (const struct GNUNET_MessageHeader *) buf;
+ bool finished = false;
+
+ while (1)
+ {
+ uint16_t msize;
+ ssize_t ret;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Awaiting reply at %u (up to %u)\n",
+ wpos,
+ rend);
+ ret = recv (dh->sock,
+ &buf[off],
+ sizeof (buf) - off,
+ (finished && (0 == off))
+ ? MSG_DONTWAIT
+ : 0);
+ if (ret < 0)
+ {
+ if (EINTR == errno)
+ continue;
+ if (EAGAIN == errno)
+ {
+ GNUNET_assert (finished);
+ GNUNET_assert (0 == off);
+ break;
+ }
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "recv");
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+ if (0 == ret)
+ {
+ GNUNET_break (0 == off);
+ if (! finished)
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ if (TALER_EC_NONE == ec)
+ break;
+ return ec;
+ }
+ off += ret;
+more:
+ if (off < sizeof (struct GNUNET_MessageHeader))
+ continue;
+ msize = ntohs (hdr->size);
+ if (off < msize)
+ continue;
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_CS_MT_RES_SIGNATURE:
+ if (msize != sizeof (struct TALER_CRYPTO_SignResponse))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ if (finished)
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ {
+ const struct TALER_CRYPTO_SignResponse *sr =
+ (const struct TALER_CRYPTO_SignResponse *) buf;
+ struct GNUNET_CRYPTO_BlindedSignature *blinded_sig;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received %u signature\n",
+ wpos);
+ blinded_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
+ blinded_sig->cipher = GNUNET_CRYPTO_BSA_CS;
+ blinded_sig->rc = 1;
+ blinded_sig->details.blinded_cs_answer.b = ntohl (sr->b);
+ blinded_sig->details.blinded_cs_answer.s_scalar = sr->cs_answer;
+
+ bss[wpos].blinded_sig = blinded_sig;
+ wpos++;
+ if (wpos == rend)
+ {
+ if (TALER_EC_INVALID == ec)
+ ec = TALER_EC_NONE;
+ finished = true;
+ }
+ break;
+ }
+
+ case TALER_HELPER_CS_MT_RES_SIGN_FAILURE:
+ if (msize != sizeof (struct TALER_CRYPTO_SignFailure))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ {
+ const struct TALER_CRYPTO_SignFailure *sf =
+ (const struct TALER_CRYPTO_SignFailure *) buf;
+
+ ec = (enum TALER_ErrorCode) ntohl (sf->ec);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Signing %u failed with status %d!\n",
+ wpos,
+ ec);
+ wpos++;
+ if (wpos == rend)
+ {
+ finished = true;
+ }
+ break;
+ }
+ case TALER_HELPER_CS_MT_AVAIL:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received new key!\n");
+ if (GNUNET_OK !=
+ handle_mt_avail (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ break; /* while(1) loop ensures we recvfrom() again */
+ case TALER_HELPER_CS_MT_PURGE:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received revocation!\n");
+ if (GNUNET_OK !=
+ handle_mt_purge (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ break; /* while(1) loop ensures we recvfrom() again */
+ case TALER_HELPER_CS_SYNCED:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Synchronized add odd time with CS helper!\n");
+ dh->synced = true;
+ break;
+ default:
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Received unexpected message of type %u\n",
+ ntohs (hdr->type));
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ memmove (buf,
+ &buf[msize],
+ off - msize);
+ off -= msize;
+ goto more;
+ } /* while(1) */
+ } /* scope */
+ } /* while (rpos < cdrs_length) */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Existing with %u signatures and status %d\n",
+ wpos,
+ ec);
+ return ec;
+}
+
+
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_cs_r_batch_derive (
+ struct TALER_CRYPTO_CsDenominationHelper *dh,
+ unsigned int cdrs_length,
+ const struct TALER_CRYPTO_CsDeriveRequest cdrs[static cdrs_length],
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP crps[static cdrs_length])
+{
+ enum TALER_ErrorCode ec = TALER_EC_INVALID;
+ unsigned int rpos;
+ unsigned int rend;
+ unsigned int wpos;
+
+ memset (crps,
+ 0,
+ sizeof (*crps) * cdrs_length);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting R derivation process\n");
+ if (GNUNET_OK !=
+ try_connect (dh))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to connect to helper\n");
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting %u R pairs\n",
+ cdrs_length);
+ rpos = 0;
+ rend = 0;
+ wpos = 0;
+ while (rpos < cdrs_length)
+ {
+ unsigned int mlen = sizeof (struct TALER_CRYPTO_BatchDeriveRequest);
+
+ while ( (rend < cdrs_length) &&
+ (mlen + sizeof (struct TALER_CRYPTO_CsRDeriveRequest)
+ < UINT16_MAX) )
+ {
+ mlen += sizeof (struct TALER_CRYPTO_CsRDeriveRequest);
+ rend++;
+ }
+ {
+ char obuf[mlen] GNUNET_ALIGN;
+ struct TALER_CRYPTO_BatchDeriveRequest *bdr
+ = (struct TALER_CRYPTO_BatchDeriveRequest *) obuf;
+ void *wbuf;
+
+ bdr->header.type = htons (TALER_HELPER_CS_MT_REQ_BATCH_RDERIVE);
+ bdr->header.size = htons (mlen);
+ bdr->batch_size = htonl (rend - rpos);
+ wbuf = &bdr[1];
+ for (unsigned int i = rpos; i<rend; i++)
+ {
+ struct TALER_CRYPTO_CsRDeriveRequest *rdr = wbuf;
+ const struct TALER_CRYPTO_CsDeriveRequest *cdr = &cdrs[i];
+
+ rdr->header.size = htons (sizeof (*rdr));
+ rdr->header.type = htons (TALER_HELPER_CS_MT_REQ_RDERIVE);
+ rdr->for_melt = htonl (for_melt ? 1 : 0);
+ rdr->h_cs = *cdr->h_cs;
+ rdr->nonce = *cdr->nonce;
+ wbuf += sizeof (*rdr);
+ }
+ GNUNET_assert (wbuf == &obuf[mlen]);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Sending batch request [%u-%u)\n",
+ rpos,
+ rend);
+ if (GNUNET_OK !=
+ TALER_crypto_helper_send_all (dh->sock,
+ obuf,
+ sizeof (obuf)))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "send");
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+ } /* end of obuf scope */
+ rpos = rend;
+ {
+ char buf[UINT16_MAX];
+ size_t off = 0;
+ const struct GNUNET_MessageHeader *hdr
+ = (const struct GNUNET_MessageHeader *) buf;
+ bool finished = false;
+
+ while (1)
+ {
+ uint16_t msize;
+ ssize_t ret;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Awaiting reply at %u (up to %u)\n",
+ wpos,
+ rend);
+ ret = recv (dh->sock,
+ &buf[off],
+ sizeof (buf) - off,
+ (finished && (0 == off))
+ ? MSG_DONTWAIT
+ : 0);
+ if (ret < 0)
+ {
+ if (EINTR == errno)
+ continue;
+ if (EAGAIN == errno)
+ {
+ GNUNET_assert (finished);
+ GNUNET_assert (0 == off);
+ break;
+ }
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "recv");
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+ if (0 == ret)
+ {
+ GNUNET_break (0 == off);
+ if (! finished)
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ if (TALER_EC_NONE == ec)
+ break;
+ return ec;
+ }
+ off += ret;
+more:
+ if (off < sizeof (struct GNUNET_MessageHeader))
+ continue;
+ msize = ntohs (hdr->size);
+ if (off < msize)
+ continue;
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_CS_MT_RES_RDERIVE:
+ if (msize != sizeof (struct TALER_CRYPTO_RDeriveResponse))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ if (finished)
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ {
+ const struct TALER_CRYPTO_RDeriveResponse *rdr =
+ (const struct TALER_CRYPTO_RDeriveResponse *) buf;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received %u R pair\n",
+ wpos);
+ crps[wpos] = rdr->r_pub;
+ wpos++;
+ if (wpos == rend)
+ {
+ if (TALER_EC_INVALID == ec)
+ ec = TALER_EC_NONE;
+ finished = true;
+ }
+ break;
+ }
+ case TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE:
+ if (msize != sizeof (struct TALER_CRYPTO_RDeriveFailure))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ {
+ const struct TALER_CRYPTO_RDeriveFailure *rdf =
+ (const struct TALER_CRYPTO_RDeriveFailure *) buf;
+
+ ec = (enum TALER_ErrorCode) ntohl (rdf->ec);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "R derivation %u failed with status %d!\n",
+ wpos,
+ ec);
+ wpos++;
+ if (wpos == rend)
+ {
+ finished = true;
+ }
+ break;
+ }
+ case TALER_HELPER_CS_MT_AVAIL:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received new key!\n");
+ if (GNUNET_OK !=
+ handle_mt_avail (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ break; /* while(1) loop ensures we recvfrom() again */
+ case TALER_HELPER_CS_MT_PURGE:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received revocation!\n");
+ if (GNUNET_OK !=
+ handle_mt_purge (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ break; /* while(1) loop ensures we recvfrom() again */
+ case TALER_HELPER_CS_SYNCED:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Synchronized add odd time with CS helper!\n");
+ dh->synced = true;
+ break;
+ default:
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Received unexpected message of type %u\n",
+ ntohs (hdr->type));
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ memmove (buf,
+ &buf[msize],
+ off - msize);
+ off -= msize;
+ goto more;
+ } /* while(1) */
+ } /* scope */
+ } /* while (rpos < cdrs_length) */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Existing with %u signatures and status %d\n",
+ wpos,
+ ec);
+ return ec;
+}
+
+
+void
+TALER_CRYPTO_helper_cs_disconnect (
+ struct TALER_CRYPTO_CsDenominationHelper *dh)
+{
+ if (-1 != dh->sock)
+ do_disconnect (dh);
+ GNUNET_free (dh);
+}
+
+
+/* end of crypto_helper_cs.c */
diff --git a/src/util/crypto_helper_esign.c b/src/util/crypto_helper_esign.c
new file mode 100644
index 000000000..e044d31d1
--- /dev/null
+++ b/src/util/crypto_helper_esign.c
@@ -0,0 +1,555 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 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 util/crypto_helper_esign.c
+ * @brief utility functions for running out-of-process private key operations
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler-exchange-secmod-eddsa.h"
+#include <poll.h>
+#include "crypto_helper_common.h"
+
+
+struct TALER_CRYPTO_ExchangeSignHelper
+{
+ /**
+ * Function to call with updates to available key material.
+ */
+ TALER_CRYPTO_ExchangeKeyStatusCallback ekc;
+
+ /**
+ * Closure for @e ekc
+ */
+ void *ekc_cls;
+
+ /**
+ * Socket address of the denomination helper process.
+ * Used to reconnect if the connection breaks.
+ */
+ struct sockaddr_un sa;
+
+ /**
+ * The UNIX domain socket, -1 if we are currently not connected.
+ */
+ int sock;
+
+ /**
+ * Have we reached the sync'ed state?
+ */
+ bool synced;
+
+};
+
+
+/**
+ * Disconnect from the helper process. Updates
+ * @e sock field in @a esh.
+ *
+ * @param[in,out] esh handle to tear down connection of
+ */
+static void
+do_disconnect (struct TALER_CRYPTO_ExchangeSignHelper *esh)
+{
+ GNUNET_break (0 == close (esh->sock));
+ esh->sock = -1;
+ esh->synced = false;
+}
+
+
+/**
+ * Try to connect to the helper process. Updates
+ * @e sock field in @a esh.
+ *
+ * @param[in,out] esh handle to establish connection for
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+try_connect (struct TALER_CRYPTO_ExchangeSignHelper *esh)
+{
+ if (-1 != esh->sock)
+ return GNUNET_OK;
+ esh->sock = socket (AF_UNIX,
+ SOCK_STREAM,
+ 0);
+ if (-1 == esh->sock)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "socket");
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ connect (esh->sock,
+ (const struct sockaddr *) &esh->sa,
+ sizeof (esh->sa)))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "connect",
+ esh->sa.sun_path);
+ do_disconnect (esh);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+struct TALER_CRYPTO_ExchangeSignHelper *
+TALER_CRYPTO_helper_esign_connect (
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section,
+ TALER_CRYPTO_ExchangeKeyStatusCallback ekc,
+ void *ekc_cls)
+{
+ struct TALER_CRYPTO_ExchangeSignHelper *esh;
+ char *unixpath;
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-eddsa",
+ section);
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ secname,
+ "UNIXPATH",
+ &unixpath))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "UNIXPATH");
+ GNUNET_free (secname);
+ return NULL;
+ }
+ /* we use >= here because we want the sun_path to always
+ be 0-terminated */
+ if (strlen (unixpath) >= sizeof (esh->sa.sun_path))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "UNIXPATH",
+ "path too long");
+ GNUNET_free (unixpath);
+ GNUNET_free (secname);
+ return NULL;
+ }
+ GNUNET_free (secname);
+ esh = GNUNET_new (struct TALER_CRYPTO_ExchangeSignHelper);
+ esh->ekc = ekc;
+ esh->ekc_cls = ekc_cls;
+ esh->sa.sun_family = AF_UNIX;
+ strncpy (esh->sa.sun_path,
+ unixpath,
+ sizeof (esh->sa.sun_path) - 1);
+ GNUNET_free (unixpath);
+ esh->sock = -1;
+ if (GNUNET_OK !=
+ try_connect (esh))
+ {
+ TALER_CRYPTO_helper_esign_disconnect (esh);
+ return NULL;
+ }
+
+ TALER_CRYPTO_helper_esign_poll (esh);
+ return esh;
+}
+
+
+/**
+ * Handle a #TALER_HELPER_EDDSA_MT_AVAIL message from the helper.
+ *
+ * @param esh helper context
+ * @param hdr message that we received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_mt_avail (struct TALER_CRYPTO_ExchangeSignHelper *esh,
+ const struct GNUNET_MessageHeader *hdr)
+{
+ const struct TALER_CRYPTO_EddsaKeyAvailableNotification *kan
+ = (const struct TALER_CRYPTO_EddsaKeyAvailableNotification *) hdr;
+
+ if (sizeof (*kan) != ntohs (hdr->size))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_secmod_eddsa_verify (
+ &kan->exchange_pub,
+ GNUNET_TIME_timestamp_ntoh (kan->anchor_time),
+ GNUNET_TIME_relative_ntoh (kan->duration),
+ &kan->secm_pub,
+ &kan->secm_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ esh->ekc (esh->ekc_cls,
+ GNUNET_TIME_timestamp_ntoh (kan->anchor_time),
+ GNUNET_TIME_relative_ntoh (kan->duration),
+ &kan->exchange_pub,
+ &kan->secm_pub,
+ &kan->secm_sig);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle a #TALER_HELPER_EDDSA_MT_PURGE message from the helper.
+ *
+ * @param esh helper context
+ * @param hdr message that we received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_mt_purge (struct TALER_CRYPTO_ExchangeSignHelper *esh,
+ const struct GNUNET_MessageHeader *hdr)
+{
+ const struct TALER_CRYPTO_EddsaKeyPurgeNotification *pn
+ = (const struct TALER_CRYPTO_EddsaKeyPurgeNotification *) hdr;
+
+ if (sizeof (*pn) != ntohs (hdr->size))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ esh->ekc (esh->ekc_cls,
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_ZERO,
+ &pn->exchange_pub,
+ NULL,
+ NULL);
+ return GNUNET_OK;
+}
+
+
+void
+TALER_CRYPTO_helper_esign_poll (struct TALER_CRYPTO_ExchangeSignHelper *esh)
+{
+ char buf[UINT16_MAX];
+ size_t off = 0;
+ unsigned int retry_limit = 3;
+ const struct GNUNET_MessageHeader *hdr
+ = (const struct GNUNET_MessageHeader *) buf;
+
+ if (GNUNET_OK !=
+ try_connect (esh))
+ return; /* give up */
+ while (1)
+ {
+ uint16_t msize;
+ ssize_t ret;
+
+ ret = recv (esh->sock,
+ buf + off,
+ sizeof (buf) - off,
+ (esh->synced && (0 == off))
+ ? MSG_DONTWAIT
+ : 0);
+ if (ret < 0)
+ {
+ if (EINTR == errno)
+ continue;
+ if (EAGAIN == errno)
+ {
+ GNUNET_assert (esh->synced);
+ GNUNET_assert (0 == off);
+ break;
+ }
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "recv");
+ do_disconnect (esh);
+ if (0 == retry_limit)
+ return; /* give up */
+ if (GNUNET_OK !=
+ try_connect (esh))
+ return; /* give up */
+ retry_limit--;
+ continue;
+ }
+ if (0 == ret)
+ {
+ GNUNET_break (0 == off);
+ return;
+ }
+ off += ret;
+more:
+ if (off < sizeof (struct GNUNET_MessageHeader))
+ continue;
+ msize = ntohs (hdr->size);
+ if (off < msize)
+ continue;
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_EDDSA_MT_AVAIL:
+ if (GNUNET_OK !=
+ handle_mt_avail (esh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (esh);
+ return;
+ }
+ break;
+ case TALER_HELPER_EDDSA_MT_PURGE:
+ if (GNUNET_OK !=
+ handle_mt_purge (esh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (esh);
+ return;
+ }
+ break;
+ case TALER_HELPER_EDDSA_SYNCED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Now synchronized with EdDSA helper\n");
+ esh->synced = true;
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Received unexpected message of type %d (len: %u)\n",
+ (unsigned int) ntohs (hdr->type),
+ (unsigned int) msize);
+ GNUNET_break_op (0);
+ do_disconnect (esh);
+ return;
+ }
+ memmove (buf,
+ &buf[msize],
+ off - msize);
+ off -= msize;
+ goto more;
+ }
+}
+
+
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_esign_sign_ (
+ struct TALER_CRYPTO_ExchangeSignHelper *esh,
+ const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+ struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_ExchangeSignatureP *exchange_sig)
+{
+ uint32_t purpose_size = ntohl (purpose->size);
+
+ if (GNUNET_OK !=
+ try_connect (esh))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to connect to helper\n");
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE;
+ }
+ GNUNET_assert (purpose_size <
+ UINT16_MAX - sizeof (struct TALER_CRYPTO_EddsaSignRequest));
+ {
+ char buf[sizeof (struct TALER_CRYPTO_EddsaSignRequest) + purpose_size
+ - sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose)];
+ struct TALER_CRYPTO_EddsaSignRequest *sr
+ = (struct TALER_CRYPTO_EddsaSignRequest *) buf;
+
+ sr->header.size = htons (sizeof (buf));
+ sr->header.type = htons (TALER_HELPER_EDDSA_MT_REQ_SIGN);
+ sr->reserved = htonl (0);
+ GNUNET_memcpy (&sr->purpose,
+ purpose,
+ purpose_size);
+ if (GNUNET_OK !=
+ TALER_crypto_helper_send_all (esh->sock,
+ buf,
+ sizeof (buf)))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "send",
+ esh->sa.sun_path);
+ do_disconnect (esh);
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE;
+ }
+ }
+
+ {
+ char buf[UINT16_MAX];
+ size_t off = 0;
+ const struct GNUNET_MessageHeader *hdr
+ = (const struct GNUNET_MessageHeader *) buf;
+ bool finished = false;
+ enum TALER_ErrorCode ec = TALER_EC_INVALID;
+
+ while (1)
+ {
+ ssize_t ret;
+ uint16_t msize;
+
+ ret = recv (esh->sock,
+ &buf[off],
+ sizeof (buf) - off,
+ (finished && (0 == off))
+ ? MSG_DONTWAIT
+ : 0);
+ if (ret < 0)
+ {
+ if (EINTR == errno)
+ continue;
+ if (EAGAIN == errno)
+ {
+ GNUNET_assert (finished);
+ GNUNET_assert (0 == off);
+ break;
+ }
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "recv");
+ do_disconnect (esh);
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE;
+ }
+ if (0 == ret)
+ {
+ GNUNET_break (0 == off);
+ if (finished)
+ return TALER_EC_NONE;
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ }
+ off += ret;
+more:
+ if (off < sizeof (struct GNUNET_MessageHeader))
+ continue;
+ msize = ntohs (hdr->size);
+ if (off < msize)
+ continue;
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_EDDSA_MT_RES_SIGNATURE:
+ if (msize != sizeof (struct TALER_CRYPTO_EddsaSignResponse))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (esh);
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ }
+ if (finished)
+ {
+ GNUNET_break_op (0);
+ do_disconnect (esh);
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ }
+ {
+ const struct TALER_CRYPTO_EddsaSignResponse *sr =
+ (const struct TALER_CRYPTO_EddsaSignResponse *) buf;
+ *exchange_sig = sr->exchange_sig;
+ *exchange_pub = sr->exchange_pub;
+ finished = true;
+ ec = TALER_EC_NONE;
+ break;
+ }
+ case TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE:
+ if (msize != sizeof (struct TALER_CRYPTO_EddsaSignFailure))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (esh);
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ }
+ {
+ const struct TALER_CRYPTO_EddsaSignFailure *sf =
+ (const struct TALER_CRYPTO_EddsaSignFailure *) buf;
+
+ finished = true;
+ ec = (enum TALER_ErrorCode) ntohl (sf->ec);
+ break;
+ }
+ case TALER_HELPER_EDDSA_MT_AVAIL:
+ if (GNUNET_OK !=
+ handle_mt_avail (esh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (esh);
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ }
+ break; /* while(1) loop ensures we recv() again */
+ case TALER_HELPER_EDDSA_MT_PURGE:
+ if (GNUNET_OK !=
+ handle_mt_purge (esh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (esh);
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ }
+ break; /* while(1) loop ensures we recv() again */
+ case TALER_HELPER_EDDSA_SYNCED:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Synchronized add odd time with EdDSA helper!\n");
+ esh->synced = true;
+ break;
+ default:
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Received unexpected message of type %u\n",
+ ntohs (hdr->type));
+ do_disconnect (esh);
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ }
+ memmove (buf,
+ &buf[msize],
+ off - msize);
+ off -= msize;
+ goto more;
+ } /* while(1) */
+ return ec;
+ }
+}
+
+
+void
+TALER_CRYPTO_helper_esign_revoke (
+ struct TALER_CRYPTO_ExchangeSignHelper *esh,
+ const struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+ if (GNUNET_OK !=
+ try_connect (esh))
+ return; /* give up */
+ {
+ struct TALER_CRYPTO_EddsaRevokeRequest rr = {
+ .header.size = htons (sizeof (rr)),
+ .header.type = htons (TALER_HELPER_EDDSA_MT_REQ_REVOKE),
+ .exchange_pub = *exchange_pub
+ };
+
+ if (GNUNET_OK !=
+ TALER_crypto_helper_send_all (esh->sock,
+ &rr,
+ sizeof (rr)))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "send");
+ do_disconnect (esh);
+ return;
+ }
+ }
+}
+
+
+void
+TALER_CRYPTO_helper_esign_disconnect (
+ struct TALER_CRYPTO_ExchangeSignHelper *esh)
+{
+ if (-1 != esh->sock)
+ do_disconnect (esh);
+ GNUNET_free (esh);
+}
+
+
+/* end of crypto_helper_esign.c */
diff --git a/src/util/crypto_helper_rsa.c b/src/util/crypto_helper_rsa.c
new file mode 100644
index 000000000..e23e12a88
--- /dev/null
+++ b/src/util/crypto_helper_rsa.c
@@ -0,0 +1,916 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 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 util/crypto_helper_rsa.c
+ * @brief utility functions for running out-of-process private key operations
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler-exchange-secmod-rsa.h"
+#include <poll.h>
+#include "crypto_helper_common.h"
+
+
+struct TALER_CRYPTO_RsaDenominationHelper
+{
+ /**
+ * Function to call with updates to available key material.
+ */
+ TALER_CRYPTO_RsaDenominationKeyStatusCallback dkc;
+
+ /**
+ * Closure for @e dkc
+ */
+ void *dkc_cls;
+
+ /**
+ * Socket address of the denomination helper process.
+ * Used to reconnect if the connection breaks.
+ */
+ struct sockaddr_un sa;
+
+ /**
+ * The UNIX domain socket, -1 if we are currently not connected.
+ */
+ int sock;
+
+ /**
+ * Have we ever been sync'ed?
+ */
+ bool synced;
+};
+
+
+/**
+ * Disconnect from the helper process. Updates
+ * @e sock field in @a dh.
+ *
+ * @param[in,out] dh handle to tear down connection of
+ */
+static void
+do_disconnect (struct TALER_CRYPTO_RsaDenominationHelper *dh)
+{
+ GNUNET_break (0 == close (dh->sock));
+ dh->sock = -1;
+ dh->synced = false;
+}
+
+
+/**
+ * Try to connect to the helper process. Updates
+ * @e sock field in @a dh.
+ *
+ * @param[in,out] dh handle to establish connection for
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+try_connect (struct TALER_CRYPTO_RsaDenominationHelper *dh)
+{
+ if (-1 != dh->sock)
+ return GNUNET_OK;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Establishing connection!\n");
+ dh->sock = socket (AF_UNIX,
+ SOCK_STREAM,
+ 0);
+ if (-1 == dh->sock)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "socket");
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ connect (dh->sock,
+ (const struct sockaddr *) &dh->sa,
+ sizeof (dh->sa)))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "connect",
+ dh->sa.sun_path);
+ do_disconnect (dh);
+ return GNUNET_SYSERR;
+ }
+ TALER_CRYPTO_helper_rsa_poll (dh);
+ return GNUNET_OK;
+}
+
+
+struct TALER_CRYPTO_RsaDenominationHelper *
+TALER_CRYPTO_helper_rsa_connect (
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section,
+ TALER_CRYPTO_RsaDenominationKeyStatusCallback dkc,
+ void *dkc_cls)
+{
+ struct TALER_CRYPTO_RsaDenominationHelper *dh;
+ char *unixpath;
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-rsa",
+ section);
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ secname,
+ "UNIXPATH",
+ &unixpath))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "UNIXPATH");
+ GNUNET_free (secname);
+ return NULL;
+ }
+ /* we use >= here because we want the sun_path to always
+ be 0-terminated */
+ if (strlen (unixpath) >= sizeof (dh->sa.sun_path))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "UNIXPATH",
+ "path too long");
+ GNUNET_free (unixpath);
+ GNUNET_free (secname);
+ return NULL;
+ }
+ GNUNET_free (secname);
+ dh = GNUNET_new (struct TALER_CRYPTO_RsaDenominationHelper);
+ dh->dkc = dkc;
+ dh->dkc_cls = dkc_cls;
+ dh->sa.sun_family = AF_UNIX;
+ strncpy (dh->sa.sun_path,
+ unixpath,
+ sizeof (dh->sa.sun_path) - 1);
+ GNUNET_free (unixpath);
+ dh->sock = -1;
+ if (GNUNET_OK !=
+ try_connect (dh))
+ {
+ TALER_CRYPTO_helper_rsa_disconnect (dh);
+ return NULL;
+ }
+ return dh;
+}
+
+
+/**
+ * Handle a #TALER_HELPER_RSA_MT_AVAIL message from the helper.
+ *
+ * @param dh helper context
+ * @param hdr message that we received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_mt_avail (struct TALER_CRYPTO_RsaDenominationHelper *dh,
+ const struct GNUNET_MessageHeader *hdr)
+{
+ const struct TALER_CRYPTO_RsaKeyAvailableNotification *kan
+ = (const struct TALER_CRYPTO_RsaKeyAvailableNotification *) hdr;
+ const char *buf = (const char *) &kan[1];
+ const char *section_name;
+ uint16_t ps;
+ uint16_t snl;
+
+ if (sizeof (*kan) > ntohs (hdr->size))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ ps = ntohs (kan->pub_size);
+ snl = ntohs (kan->section_name_len);
+ if (ntohs (hdr->size) != sizeof (*kan) + ps + snl)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 == snl)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ section_name = &buf[ps];
+ if ('\0' != section_name[snl - 1])
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub;
+ struct TALER_RsaPubHashP h_rsa;
+
+ bs_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
+ bs_pub->cipher = GNUNET_CRYPTO_BSA_RSA;
+ bs_pub->details.rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_decode (buf,
+ ntohs (kan->pub_size));
+ if (NULL == bs_pub->details.rsa_public_key)
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (bs_pub);
+ return GNUNET_SYSERR;
+ }
+ bs_pub->rc = 1;
+ GNUNET_CRYPTO_rsa_public_key_hash (bs_pub->details.rsa_public_key,
+ &bs_pub->pub_key_hash);
+ h_rsa.hash = bs_pub->pub_key_hash;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received RSA key %s (%s)\n",
+ GNUNET_h2s (&bs_pub->pub_key_hash),
+ section_name);
+ if (GNUNET_OK !=
+ TALER_exchange_secmod_rsa_verify (
+ &h_rsa,
+ section_name,
+ GNUNET_TIME_timestamp_ntoh (kan->anchor_time),
+ GNUNET_TIME_relative_ntoh (kan->duration_withdraw),
+ &kan->secm_pub,
+ &kan->secm_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_CRYPTO_blind_sign_pub_decref (bs_pub);
+ return GNUNET_SYSERR;
+ }
+ dh->dkc (dh->dkc_cls,
+ section_name,
+ GNUNET_TIME_timestamp_ntoh (kan->anchor_time),
+ GNUNET_TIME_relative_ntoh (kan->duration_withdraw),
+ &h_rsa,
+ bs_pub,
+ &kan->secm_pub,
+ &kan->secm_sig);
+ GNUNET_CRYPTO_blind_sign_pub_decref (bs_pub);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle a #TALER_HELPER_RSA_MT_PURGE message from the helper.
+ *
+ * @param dh helper context
+ * @param hdr message that we received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_mt_purge (struct TALER_CRYPTO_RsaDenominationHelper *dh,
+ const struct GNUNET_MessageHeader *hdr)
+{
+ const struct TALER_CRYPTO_RsaKeyPurgeNotification *pn
+ = (const struct TALER_CRYPTO_RsaKeyPurgeNotification *) hdr;
+
+ if (sizeof (*pn) != ntohs (hdr->size))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received revocation of denomination key %s\n",
+ GNUNET_h2s (&pn->h_rsa.hash));
+ dh->dkc (dh->dkc_cls,
+ NULL,
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_ZERO,
+ &pn->h_rsa,
+ NULL,
+ NULL,
+ NULL);
+ return GNUNET_OK;
+}
+
+
+void
+TALER_CRYPTO_helper_rsa_poll (struct TALER_CRYPTO_RsaDenominationHelper *dh)
+{
+ char buf[UINT16_MAX];
+ size_t off = 0;
+ unsigned int retry_limit = 3;
+ const struct GNUNET_MessageHeader *hdr
+ = (const struct GNUNET_MessageHeader *) buf;
+
+ if (GNUNET_OK !=
+ try_connect (dh))
+ return; /* give up */
+ while (1)
+ {
+ uint16_t msize;
+ ssize_t ret;
+
+ ret = recv (dh->sock,
+ buf + off,
+ sizeof (buf) - off,
+ (dh->synced && (0 == off))
+ ? MSG_DONTWAIT
+ : 0);
+ if (ret < 0)
+ {
+ if (EINTR == errno)
+ continue;
+ if (EAGAIN == errno)
+ {
+ GNUNET_assert (dh->synced);
+ GNUNET_assert (0 == off);
+ break;
+ }
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "recv");
+ do_disconnect (dh);
+ if (0 == retry_limit)
+ return; /* give up */
+ if (GNUNET_OK !=
+ try_connect (dh))
+ return; /* give up */
+ retry_limit--;
+ continue;
+ }
+ if (0 == ret)
+ {
+ GNUNET_break (0 == off);
+ return;
+ }
+ off += ret;
+more:
+ if (off < sizeof (struct GNUNET_MessageHeader))
+ continue;
+ msize = ntohs (hdr->size);
+ if (off < msize)
+ continue;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received message of type %u and length %u\n",
+ (unsigned int) ntohs (hdr->type),
+ (unsigned int) msize);
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_RSA_MT_AVAIL:
+ if (GNUNET_OK !=
+ handle_mt_avail (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return;
+ }
+ break;
+ case TALER_HELPER_RSA_MT_PURGE:
+ if (GNUNET_OK !=
+ handle_mt_purge (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return;
+ }
+ break;
+ case TALER_HELPER_RSA_SYNCED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Now synchronized with RSA helper\n");
+ dh->synced = true;
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Received unexpected message of type %d (len: %u)\n",
+ (unsigned int) ntohs (hdr->type),
+ (unsigned int) msize);
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return;
+ }
+ memmove (buf,
+ &buf[msize],
+ off - msize);
+ off -= msize;
+ goto more;
+ }
+}
+
+
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_rsa_sign (
+ struct TALER_CRYPTO_RsaDenominationHelper *dh,
+ const struct TALER_CRYPTO_RsaSignRequest *rsr,
+ struct TALER_BlindedDenominationSignature *bs)
+{
+ enum TALER_ErrorCode ec = TALER_EC_INVALID;
+
+ memset (bs,
+ 0,
+ sizeof (*bs));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting signature process\n");
+ if (GNUNET_OK !=
+ try_connect (dh))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to connect to helper\n");
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting signature\n");
+ {
+ char buf[sizeof (struct TALER_CRYPTO_SignRequest) + rsr->msg_size];
+ struct TALER_CRYPTO_SignRequest *sr
+ = (struct TALER_CRYPTO_SignRequest *) buf;
+
+ sr->header.size = htons (sizeof (buf));
+ sr->header.type = htons (TALER_HELPER_RSA_MT_REQ_SIGN);
+ sr->reserved = htonl (0);
+ sr->h_rsa = *rsr->h_rsa;
+ GNUNET_memcpy (&sr[1],
+ rsr->msg,
+ rsr->msg_size);
+ if (GNUNET_OK !=
+ TALER_crypto_helper_send_all (dh->sock,
+ buf,
+ sizeof (buf)))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "send");
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Awaiting reply\n");
+ {
+ char buf[UINT16_MAX];
+ size_t off = 0;
+ const struct GNUNET_MessageHeader *hdr
+ = (const struct GNUNET_MessageHeader *) buf;
+ bool finished = false;
+
+ while (1)
+ {
+ uint16_t msize;
+ ssize_t ret;
+
+ ret = recv (dh->sock,
+ &buf[off],
+ sizeof (buf) - off,
+ (finished && (0 == off))
+ ? MSG_DONTWAIT
+ : 0);
+ if (ret < 0)
+ {
+ if (EINTR == errno)
+ continue;
+ if (EAGAIN == errno)
+ {
+ GNUNET_assert (finished);
+ GNUNET_assert (0 == off);
+ return ec;
+ }
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "recv");
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ break;
+ }
+ if (0 == ret)
+ {
+ GNUNET_break (0 == off);
+ if (! finished)
+ ec = TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ return ec;
+ }
+ off += ret;
+more:
+ if (off < sizeof (struct GNUNET_MessageHeader))
+ continue;
+ msize = ntohs (hdr->size);
+ if (off < msize)
+ continue;
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_RSA_MT_RES_SIGNATURE:
+ if (msize < sizeof (struct TALER_CRYPTO_SignResponse))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ goto end;
+ }
+ if (finished)
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ goto end;
+ }
+ {
+ const struct TALER_CRYPTO_SignResponse *sr =
+ (const struct TALER_CRYPTO_SignResponse *) buf;
+ struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
+ struct GNUNET_CRYPTO_BlindedSignature *blind_sig;
+
+ rsa_signature = GNUNET_CRYPTO_rsa_signature_decode (
+ &sr[1],
+ msize - sizeof (*sr));
+ if (NULL == rsa_signature)
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ goto end;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received signature\n");
+ ec = TALER_EC_NONE;
+ finished = true;
+ blind_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
+ blind_sig->cipher = GNUNET_CRYPTO_BSA_RSA;
+ blind_sig->rc = 1;
+ blind_sig->details.blinded_rsa_signature = rsa_signature;
+ bs->blinded_sig = blind_sig;
+ break;
+ }
+ case TALER_HELPER_RSA_MT_RES_SIGN_FAILURE:
+ if (msize != sizeof (struct TALER_CRYPTO_SignFailure))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ goto end;
+ }
+ {
+ const struct TALER_CRYPTO_SignFailure *sf =
+ (const struct TALER_CRYPTO_SignFailure *) buf;
+
+ ec = (enum TALER_ErrorCode) ntohl (sf->ec);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Signing failed!\n");
+ finished = true;
+ break;
+ }
+ case TALER_HELPER_RSA_MT_AVAIL:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received new key!\n");
+ if (GNUNET_OK !=
+ handle_mt_avail (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ goto end;
+ }
+ break; /* while(1) loop ensures we recvfrom() again */
+ case TALER_HELPER_RSA_MT_PURGE:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received revocation!\n");
+ if (GNUNET_OK !=
+ handle_mt_purge (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ goto end;
+ }
+ break; /* while(1) loop ensures we recvfrom() again */
+ case TALER_HELPER_RSA_SYNCED:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Synchronized add odd time with RSA helper!\n");
+ dh->synced = true;
+ break;
+ default:
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Received unexpected message of type %u\n",
+ ntohs (hdr->type));
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ goto end;
+ }
+ memmove (buf,
+ &buf[msize],
+ off - msize);
+ off -= msize;
+ goto more;
+ } /* while(1) */
+end:
+ if (finished)
+ TALER_blinded_denom_sig_free (bs);
+ return ec;
+ }
+}
+
+
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_rsa_batch_sign (
+ struct TALER_CRYPTO_RsaDenominationHelper *dh,
+ unsigned int rsrs_length,
+ const struct TALER_CRYPTO_RsaSignRequest rsrs[static rsrs_length],
+ struct TALER_BlindedDenominationSignature bss[static rsrs_length])
+{
+ enum TALER_ErrorCode ec = TALER_EC_INVALID;
+ unsigned int rpos;
+ unsigned int rend;
+ unsigned int wpos;
+
+ memset (bss,
+ 0,
+ sizeof (*bss) * rsrs_length);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting signature process\n");
+ if (GNUNET_OK !=
+ try_connect (dh))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to connect to helper\n");
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting %u signatures\n",
+ rsrs_length);
+ rpos = 0;
+ rend = 0;
+ wpos = 0;
+ while (rpos < rsrs_length)
+ {
+ unsigned int mlen = sizeof (struct TALER_CRYPTO_BatchSignRequest);
+
+ while ( (rend < rsrs_length) &&
+ (mlen
+ + sizeof (struct TALER_CRYPTO_SignRequest)
+ + rsrs[rend].msg_size < UINT16_MAX) )
+ {
+ mlen += sizeof (struct TALER_CRYPTO_SignRequest) + rsrs[rend].msg_size;
+ rend++;
+ }
+ {
+ char obuf[mlen] GNUNET_ALIGN;
+ struct TALER_CRYPTO_BatchSignRequest *bsr
+ = (struct TALER_CRYPTO_BatchSignRequest *) obuf;
+ void *wbuf;
+
+ bsr->header.type = htons (TALER_HELPER_RSA_MT_REQ_BATCH_SIGN);
+ bsr->header.size = htons (mlen);
+ bsr->batch_size = htonl (rend - rpos);
+ wbuf = &bsr[1];
+ for (unsigned int i = rpos; i<rend; i++)
+ {
+ struct TALER_CRYPTO_SignRequest *sr = wbuf;
+ const struct TALER_CRYPTO_RsaSignRequest *rsr = &rsrs[i];
+
+ sr->header.type = htons (TALER_HELPER_RSA_MT_REQ_SIGN);
+ sr->header.size = htons (sizeof (*sr) + rsr->msg_size);
+ sr->reserved = htonl (0);
+ sr->h_rsa = *rsr->h_rsa;
+ GNUNET_memcpy (&sr[1],
+ rsr->msg,
+ rsr->msg_size);
+ wbuf += sizeof (*sr) + rsr->msg_size;
+ }
+ GNUNET_assert (wbuf == &obuf[mlen]);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Sending batch request [%u-%u)\n",
+ rpos,
+ rend);
+ if (GNUNET_OK !=
+ TALER_crypto_helper_send_all (dh->sock,
+ obuf,
+ sizeof (obuf)))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "send");
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ }
+ }
+ rpos = rend;
+ {
+ char buf[UINT16_MAX];
+ size_t off = 0;
+ const struct GNUNET_MessageHeader *hdr
+ = (const struct GNUNET_MessageHeader *) buf;
+ bool finished = false;
+
+ while (1)
+ {
+ uint16_t msize;
+ ssize_t ret;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Awaiting reply at %u (up to %u)\n",
+ wpos,
+ rend);
+ ret = recv (dh->sock,
+ &buf[off],
+ sizeof (buf) - off,
+ (finished && (0 == off))
+ ? MSG_DONTWAIT
+ : 0);
+ if (ret < 0)
+ {
+ if (EINTR == errno)
+ continue;
+ if (EAGAIN == errno)
+ {
+ GNUNET_assert (finished);
+ GNUNET_assert (0 == off);
+ break;
+ }
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "recv");
+ do_disconnect (dh);
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ break;
+ }
+ if (0 == ret)
+ {
+ GNUNET_break (0 == off);
+ if (! finished)
+ ec = TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ if (TALER_EC_NONE == ec)
+ break;
+ return ec;
+ }
+ off += ret;
+more:
+ if (off < sizeof (struct GNUNET_MessageHeader))
+ continue;
+ msize = ntohs (hdr->size);
+ if (off < msize)
+ continue;
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_RSA_MT_RES_SIGNATURE:
+ if (msize < sizeof (struct TALER_CRYPTO_SignResponse))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ if (finished)
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ {
+ const struct TALER_CRYPTO_SignResponse *sr =
+ (const struct TALER_CRYPTO_SignResponse *) buf;
+ struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
+ struct GNUNET_CRYPTO_BlindedSignature *blind_sig;
+
+ rsa_signature = GNUNET_CRYPTO_rsa_signature_decode (
+ &sr[1],
+ msize - sizeof (*sr));
+ if (NULL == rsa_signature)
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received %u signature\n",
+ wpos);
+ blind_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
+ blind_sig->cipher = GNUNET_CRYPTO_BSA_RSA;
+ blind_sig->rc = 1;
+ blind_sig->details.blinded_rsa_signature = rsa_signature;
+ bss[wpos].blinded_sig = blind_sig;
+ wpos++;
+ if (wpos == rend)
+ {
+ if (TALER_EC_INVALID == ec)
+ ec = TALER_EC_NONE;
+ finished = true;
+ }
+ break;
+ }
+ case TALER_HELPER_RSA_MT_RES_SIGN_FAILURE:
+ if (msize != sizeof (struct TALER_CRYPTO_SignFailure))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ {
+ const struct TALER_CRYPTO_SignFailure *sf =
+ (const struct TALER_CRYPTO_SignFailure *) buf;
+
+ ec = (enum TALER_ErrorCode) ntohl (sf->ec);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Signing %u failed with status %d!\n",
+ wpos,
+ ec);
+ wpos++;
+ if (wpos == rend)
+ {
+ finished = true;
+ }
+ break;
+ }
+ case TALER_HELPER_RSA_MT_AVAIL:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received new key!\n");
+ if (GNUNET_OK !=
+ handle_mt_avail (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ break; /* while(1) loop ensures we recvfrom() again */
+ case TALER_HELPER_RSA_MT_PURGE:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received revocation!\n");
+ if (GNUNET_OK !=
+ handle_mt_purge (dh,
+ hdr))
+ {
+ GNUNET_break_op (0);
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ break; /* while(1) loop ensures we recvfrom() again */
+ case TALER_HELPER_RSA_SYNCED:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Synchronized add odd time with RSA helper!\n");
+ dh->synced = true;
+ break;
+ default:
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Received unexpected message of type %u\n",
+ ntohs (hdr->type));
+ do_disconnect (dh);
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ }
+ memmove (buf,
+ &buf[msize],
+ off - msize);
+ off -= msize;
+ goto more;
+ } /* while(1) */
+ } /* scope */
+ } /* while (rpos < rsrs_length) */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Existing with %u signatures and status %d\n",
+ wpos,
+ ec);
+ return ec;
+}
+
+
+void
+TALER_CRYPTO_helper_rsa_revoke (
+ struct TALER_CRYPTO_RsaDenominationHelper *dh,
+ const struct TALER_RsaPubHashP *h_rsa)
+{
+ struct TALER_CRYPTO_RevokeRequest rr = {
+ .header.size = htons (sizeof (rr)),
+ .header.type = htons (TALER_HELPER_RSA_MT_REQ_REVOKE),
+ .h_rsa = *h_rsa
+ };
+
+ if (GNUNET_OK !=
+ try_connect (dh))
+ return; /* give up */
+ if (GNUNET_OK !=
+ TALER_crypto_helper_send_all (dh->sock,
+ &rr,
+ sizeof (rr)))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "send");
+ do_disconnect (dh);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requested revocation of denomination key %s\n",
+ GNUNET_h2s (&h_rsa->hash));
+}
+
+
+void
+TALER_CRYPTO_helper_rsa_disconnect (
+ struct TALER_CRYPTO_RsaDenominationHelper *dh)
+{
+ if (-1 != dh->sock)
+ do_disconnect (dh);
+ GNUNET_free (dh);
+}
+
+
+/* end of crypto_helper_denom.c */
diff --git a/src/util/crypto_wire.c b/src/util/crypto_wire.c
index 6fdf65511..aa504b81e 100644
--- a/src/util/crypto_wire.c
+++ b/src/util/crypto_wire.c
@@ -19,104 +19,20 @@
* @author Christian Grothoff <christian@grothoff.org>
*/
#include "platform.h"
-#include "taler_crypto_lib.h"
+#include "taler_util.h"
#include "taler_signatures.h"
-/**
- * Compute the hash of the given wire details. The resulting
- * hash is what is signed by the master key.
- *
- * @param payto_uri bank account
- * @param[out] hc set to the hash
- */
-void
-TALER_exchange_wire_signature_hash (const char *payto_uri,
- struct GNUNET_HashCode *hc)
-{
- GNUNET_assert (GNUNET_YES ==
- GNUNET_CRYPTO_kdf (hc,
- sizeof (*hc),
- payto_uri,
- strlen (payto_uri) + 1,
- "exchange-wire-signature",
- strlen ("exchange-wire-signature"),
- NULL, 0));
-}
-
-
-/**
- * Check the signature in @a master_sig.
- *
- * @param payto_uri URL that is signed
- * @param master_pub master public key of the exchange
- * @param master_sig signature of the exchange
- * @return #GNUNET_OK if signature is valid
- */
-int
-TALER_exchange_wire_signature_check (
- const char *payto_uri,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct TALER_MasterWireDetailsPS wd;
-
- wd.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_DETAILS);
- wd.purpose.size = htonl (sizeof (wd));
- TALER_exchange_wire_signature_hash (payto_uri,
- &wd.h_wire_details);
- return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_DETAILS,
- &wd.purpose,
- &master_sig->eddsa_signature,
- &master_pub->eddsa_pub);
-}
-
-
-/**
- * Create a signed wire statement for the given account.
- *
- * @param payto_uri account specification
- * @param master_priv private key to sign with
- * @param[out] master_sig where to write the signature
- */
-void
-TALER_exchange_wire_signature_make (
- const char *payto_uri,
- const struct TALER_MasterPrivateKeyP *master_priv,
- struct TALER_MasterSignatureP *master_sig)
-{
- struct TALER_MasterWireDetailsPS wd;
-
- wd.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_DETAILS);
- wd.purpose.size = htonl (sizeof (wd));
- TALER_exchange_wire_signature_hash (payto_uri,
- &wd.h_wire_details);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
- &wd.purpose,
- &master_sig->eddsa_signature));
-}
-
-
-/**
- * Compute the hash of the given wire details. The resulting
- * @a hc is what will be put into the contract between customer
- * and merchant for signing by both parties.
- *
- * @param payto_uri bank account
- * @param salt salt used to eliminate brute-force inversion
- * @param[out] hc set to the hash
- */
void
TALER_merchant_wire_signature_hash (const char *payto_uri,
- const char *salt,
- struct GNUNET_HashCode *hc)
+ const struct TALER_WireSaltP *salt,
+ struct TALER_MerchantWireHashP *hc)
{
GNUNET_assert (GNUNET_YES ==
GNUNET_CRYPTO_kdf (hc,
sizeof (*hc),
salt,
- strlen (salt) + 1,
+ sizeof (*salt),
payto_uri,
strlen (payto_uri) + 1,
"merchant-wire-signature",
@@ -125,71 +41,4 @@ TALER_merchant_wire_signature_hash (const char *payto_uri,
}
-/**
- * Check the signature in @a merch_sig.
- * (Not yet used anywhere.)
- *
- * Expected to be used if/when we get @a merch_pub signed via
- * X.509 *and* have a way for the WebEx wallet to check that the
- * @a merch_pub provided matches that of the X.509 certificate
- * from the Web site. Until then, @a merch_pub cannto be
- * validated (no PKI), and hence there is no point in checking
- * these signatures. (See #5129 and #3946).
- *
- * @param payto_uri URL that is signed
- * @param salt the salt used to salt the @a payto_uri when hashing
- * @param merch_pub master public key of the merchant
- * @param merch_sig signature of the merchant
- * @return #GNUNET_OK if signature is valid
- */
-int
-TALER_merchant_wire_signature_check (
- const char *payto_uri,
- const char *salt,
- const struct TALER_MerchantPublicKeyP *merch_pub,
- const struct TALER_MerchantSignatureP *merch_sig)
-{
- struct TALER_MasterWireDetailsPS wd;
-
- wd.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS);
- wd.purpose.size = htonl (sizeof (wd));
- TALER_merchant_wire_signature_hash (payto_uri,
- salt,
- &wd.h_wire_details);
- return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS,
- &wd.purpose,
- &merch_sig->eddsa_sig,
- &merch_pub->eddsa_pub);
-}
-
-
-/**
- * Create a signed wire statement for the given account. (Not yet used anywhere.)
- *
- * @param payto_uri account specification
- * @param salt the salt used to salt the @a payto_uri when hashing
- * @param merch_priv private key to sign with
- * @param[out] merch_sig where to write the signature
- */
-void
-TALER_merchant_wire_signature_make (
- const char *payto_uri,
- const char *salt,
- const struct TALER_MerchantPrivateKeyP *merch_priv,
- struct TALER_MerchantSignatureP *merch_sig)
-{
- struct TALER_MasterWireDetailsPS wd;
-
- wd.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS);
- wd.purpose.size = htonl (sizeof (wd));
- TALER_merchant_wire_signature_hash (payto_uri,
- salt,
- &wd.h_wire_details);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (&merch_priv->eddsa_priv,
- &wd.purpose,
- &merch_sig->eddsa_sig));
-}
-
-
/* end of crypto_wire.c */
diff --git a/src/util/currencies.conf b/src/util/currencies.conf
new file mode 100644
index 000000000..0fa831bf3
--- /dev/null
+++ b/src/util/currencies.conf
@@ -0,0 +1,89 @@
+[currency-euro]
+ENABLED = YES
+name = "Euro"
+code = "EUR"
+fractional_input_digits = 2
+fractional_normal_digits = 2
+fractional_trailing_zero_digits = 2
+alt_unit_names = {"0":"€"}
+
+[currency-swiss-francs]
+ENABLED = YES
+name = "Swiss Francs"
+code = "CHF"
+fractional_input_digits = 2
+fractional_normal_digits = 2
+fractional_trailing_zero_digits = 2
+alt_unit_names = {"0":"Fr.","-2":"Rp."}
+
+[currency-forint]
+ENABLED = NO
+name = "Hungarian Forint"
+code = "HUF"
+fractional_input_digits = 0
+fractional_normal_digits = 0
+fractional_trailing_zero_digits = 0
+alt_unit_names = {"0":"Ft"}
+
+[currency-us-dollar]
+ENABLED = NO
+name = "US Dollar"
+code = "USD"
+fractional_input_digits = 2
+fractional_normal_digits = 2
+fractional_trailing_zero_digits = 2
+alt_unit_names = {"0":"$"}
+
+[currency-kudos]
+ENABLED = YES
+name = "Kudos (Taler Demonstrator)"
+code = "KUDOS"
+fractional_input_digits = 2
+fractional_normal_digits = 2
+fractional_trailing_zero_digits = 2
+alt_unit_names = {"0":"ク"}
+
+[currency-testkudos]
+ENABLED = YES
+name = "Test-kudos (Taler Demonstrator)"
+code = "TESTKUDOS"
+fractional_input_digits = 2
+fractional_normal_digits = 2
+fractional_trailing_zero_digits = 2
+alt_unit_names = {"0":"テ","3":"kテ","-3":"mテ"}
+
+[currency-japanese-yen]
+ENABLED = NO
+name = "Japanese Yen"
+code = "JPY"
+fractional_input_digits = 2
+fractional_normal_digits = 0
+fractional_trailing_zero_digits = 2
+alt_unit_names = {"0":"Â¥"}
+
+[currency-bitcoin-mainnet]
+ENABLED = NO
+name = "Bitcoin (Mainnet)"
+code = "BITCOINBTC"
+fractional_input_digits = 8
+fractional_normal_digits = 3
+fractional_trailing_zero_digits = 0
+alt_unit_names = {"0":"BTC","-3":"mBTC"}
+
+[currency-ethereum]
+ENABLED = NO
+name = "WAI-ETHER (Ethereum)"
+code = "EthereumWAI"
+fractional_input_digits = 0
+fractional_normal_digits = 0
+fractional_trailing_zero_digits = 0
+alt_unit_names = {"0":"WAI","3":"KWAI","6":"MWAI","9":"GWAI","12":"Szabo","15":"Finney","18":"Ether","21":"KEther","24":"MEther"}
+
+[currency-netzbon]
+ENABLED=YES
+name=NetzBon
+code=NETZBON
+fractional_input_digits=2
+fractional_normal_digits=2
+fractional_trailing_zero_digits=2
+alt_unit_names = {"0":"NETZBON"}
diff --git a/src/util/denom.c b/src/util/denom.c
new file mode 100644
index 000000000..cb232c4a3
--- /dev/null
+++ b/src/util/denom.c
@@ -0,0 +1,473 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021, 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 denom.c
+ * @brief denomination utility functions
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+
+
+enum GNUNET_GenericReturnValue
+TALER_denom_priv_create (struct TALER_DenominationPrivateKey *denom_priv,
+ struct TALER_DenominationPublicKey *denom_pub,
+ enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher,
+ ...)
+{
+ enum GNUNET_GenericReturnValue ret;
+ va_list ap;
+
+ memset (denom_pub,
+ 0,
+ sizeof (*denom_pub));
+ memset (denom_priv,
+ 0,
+ sizeof (*denom_priv));
+ va_start (ap,
+ cipher);
+ ret = GNUNET_CRYPTO_blind_sign_keys_create_va (
+ &denom_priv->bsign_priv_key,
+ &denom_pub->bsign_pub_key,
+ cipher,
+ ap);
+ va_end (ap);
+ return ret;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_denom_sign_blinded (struct TALER_BlindedDenominationSignature *denom_sig,
+ const struct TALER_DenominationPrivateKey *denom_priv,
+ bool for_melt,
+ const struct TALER_BlindedPlanchet *blinded_planchet)
+{
+ denom_sig->blinded_sig
+ = GNUNET_CRYPTO_blind_sign (denom_priv->bsign_priv_key,
+ for_melt ? "rm" : "rw",
+ blinded_planchet->blinded_message);
+ if (NULL == denom_sig->blinded_sig)
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_denom_sig_unblind (
+ struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_BlindedDenominationSignature *bdenom_sig,
+ const union GNUNET_CRYPTO_BlindingSecretP *bks,
+ const struct TALER_CoinPubHashP *c_hash,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ const struct TALER_DenominationPublicKey *denom_pub)
+{
+ denom_sig->unblinded_sig
+ = GNUNET_CRYPTO_blind_sig_unblind (bdenom_sig->blinded_sig,
+ bks,
+ c_hash,
+ sizeof (*c_hash),
+ alg_values->blinding_inputs,
+ denom_pub->bsign_pub_key);
+ if (NULL == denom_sig->unblinded_sig)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+void
+TALER_denom_pub_hash (const struct TALER_DenominationPublicKey *denom_pub,
+ struct TALER_DenominationHashP *denom_hash)
+{
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bsp
+ = denom_pub->bsign_pub_key;
+ uint32_t opt[2] = {
+ htonl (denom_pub->age_mask.bits),
+ htonl ((uint32_t) bsp->cipher)
+ };
+ struct GNUNET_HashContext *hc;
+
+ hc = GNUNET_CRYPTO_hash_context_start ();
+ GNUNET_CRYPTO_hash_context_read (hc,
+ opt,
+ sizeof (opt));
+ switch (bsp->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ {
+ void *buf;
+ size_t blen;
+
+ blen = GNUNET_CRYPTO_rsa_public_key_encode (
+ bsp->details.rsa_public_key,
+ &buf);
+ GNUNET_CRYPTO_hash_context_read (hc,
+ buf,
+ blen);
+ GNUNET_free (buf);
+ }
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_CRYPTO_hash_context_read (hc,
+ &bsp->details.cs_public_key,
+ sizeof(bsp->details.cs_public_key));
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ GNUNET_CRYPTO_hash_context_finish (hc,
+ &denom_hash->hash);
+}
+
+
+const struct TALER_ExchangeWithdrawValues *
+TALER_denom_ewv_rsa_singleton ()
+{
+ static struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = GNUNET_CRYPTO_BSA_RSA
+ };
+ static struct TALER_ExchangeWithdrawValues alg_values = {
+ .blinding_inputs = &bi
+ };
+ return &alg_values;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_denom_blind (
+ const struct TALER_DenominationPublicKey *dk,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_AgeCommitmentHash *ach,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ struct TALER_CoinPubHashP *c_hash,
+ struct TALER_BlindedPlanchet *blinded_planchet)
+{
+ TALER_coin_pub_hash (coin_pub,
+ ach,
+ c_hash);
+ blinded_planchet->blinded_message
+ = GNUNET_CRYPTO_message_blind_to_sign (dk->bsign_pub_key,
+ coin_bks,
+ nonce,
+ c_hash,
+ sizeof (*c_hash),
+ alg_values->blinding_inputs);
+ if (NULL == blinded_planchet->blinded_message)
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_denom_pub_verify (const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_CoinPubHashP *c_hash)
+{
+ return GNUNET_CRYPTO_blind_sig_verify (denom_pub->bsign_pub_key,
+ denom_sig->unblinded_sig,
+ c_hash,
+ sizeof (*c_hash));
+}
+
+
+void
+TALER_denom_pub_free (struct TALER_DenominationPublicKey *denom_pub)
+{
+ if (NULL != denom_pub->bsign_pub_key)
+ {
+ GNUNET_CRYPTO_blind_sign_pub_decref (denom_pub->bsign_pub_key);
+ denom_pub->bsign_pub_key = NULL;
+ }
+}
+
+
+void
+TALER_denom_priv_free (struct TALER_DenominationPrivateKey *denom_priv)
+{
+ if (NULL != denom_priv->bsign_priv_key)
+ {
+ GNUNET_CRYPTO_blind_sign_priv_decref (denom_priv->bsign_priv_key);
+ denom_priv->bsign_priv_key = NULL;
+ }
+}
+
+
+void
+TALER_denom_sig_free (struct TALER_DenominationSignature *denom_sig)
+{
+ if (NULL != denom_sig->unblinded_sig)
+ {
+ GNUNET_CRYPTO_unblinded_sig_decref (denom_sig->unblinded_sig);
+ denom_sig->unblinded_sig = NULL;
+ }
+}
+
+
+void
+TALER_blinded_denom_sig_free (
+ struct TALER_BlindedDenominationSignature *denom_sig)
+{
+ if (NULL != denom_sig->blinded_sig)
+ {
+ GNUNET_CRYPTO_blinded_sig_decref (denom_sig->blinded_sig);
+ denom_sig->blinded_sig = NULL;
+ }
+}
+
+
+void
+TALER_denom_ewv_free (struct TALER_ExchangeWithdrawValues *ewv)
+{
+ if (ewv == TALER_denom_ewv_rsa_singleton ())
+ return;
+ if (ewv->blinding_inputs ==
+ TALER_denom_ewv_rsa_singleton ()->blinding_inputs)
+ {
+ ewv->blinding_inputs = NULL;
+ return;
+ }
+ if (NULL != ewv->blinding_inputs)
+ {
+ GNUNET_CRYPTO_blinding_input_values_decref (ewv->blinding_inputs);
+ ewv->blinding_inputs = NULL;
+ }
+}
+
+
+void
+TALER_denom_ewv_copy (struct TALER_ExchangeWithdrawValues *bi_dst,
+ const struct TALER_ExchangeWithdrawValues *bi_src)
+{
+ if (bi_src == TALER_denom_ewv_rsa_singleton ())
+ {
+ *bi_dst = *bi_src;
+ return;
+ }
+ bi_dst->blinding_inputs
+ = GNUNET_CRYPTO_blinding_input_values_incref (bi_src->blinding_inputs);
+}
+
+
+void
+TALER_denom_pub_copy (struct TALER_DenominationPublicKey *denom_dst,
+ const struct TALER_DenominationPublicKey *denom_src)
+{
+ denom_dst->age_mask = denom_src->age_mask;
+ denom_dst->bsign_pub_key
+ = GNUNET_CRYPTO_bsign_pub_incref (denom_src->bsign_pub_key);
+}
+
+
+void
+TALER_denom_sig_copy (struct TALER_DenominationSignature *denom_dst,
+ const struct TALER_DenominationSignature *denom_src)
+{
+ denom_dst->unblinded_sig
+ = GNUNET_CRYPTO_ub_sig_incref (denom_src->unblinded_sig);
+}
+
+
+void
+TALER_blinded_denom_sig_copy (
+ struct TALER_BlindedDenominationSignature *denom_dst,
+ const struct TALER_BlindedDenominationSignature *denom_src)
+{
+ denom_dst->blinded_sig
+ = GNUNET_CRYPTO_blind_sig_incref (denom_src->blinded_sig);
+}
+
+
+int
+TALER_denom_pub_cmp (const struct TALER_DenominationPublicKey *denom1,
+ const struct TALER_DenominationPublicKey *denom2)
+{
+ if (denom1->bsign_pub_key->cipher !=
+ denom2->bsign_pub_key->cipher)
+ return (denom1->bsign_pub_key->cipher >
+ denom2->bsign_pub_key->cipher) ? 1 : -1;
+ if (denom1->age_mask.bits != denom2->age_mask.bits)
+ return (denom1->age_mask.bits > denom2->age_mask.bits) ? 1 : -1;
+ return GNUNET_CRYPTO_bsign_pub_cmp (denom1->bsign_pub_key,
+ denom2->bsign_pub_key);
+}
+
+
+int
+TALER_denom_sig_cmp (const struct TALER_DenominationSignature *sig1,
+ const struct TALER_DenominationSignature *sig2)
+{
+ return GNUNET_CRYPTO_ub_sig_cmp (sig1->unblinded_sig,
+ sig1->unblinded_sig);
+}
+
+
+int
+TALER_blinded_planchet_cmp (
+ const struct TALER_BlindedPlanchet *bp1,
+ const struct TALER_BlindedPlanchet *bp2)
+{
+ return GNUNET_CRYPTO_blinded_message_cmp (bp1->blinded_message,
+ bp2->blinded_message);
+}
+
+
+int
+TALER_blinded_denom_sig_cmp (
+ const struct TALER_BlindedDenominationSignature *sig1,
+ const struct TALER_BlindedDenominationSignature *sig2)
+{
+ return GNUNET_CRYPTO_blind_sig_cmp (sig1->blinded_sig,
+ sig1->blinded_sig);
+}
+
+
+void
+TALER_blinded_planchet_hash_ (const struct TALER_BlindedPlanchet *bp,
+ struct GNUNET_HashContext *hash_context)
+{
+ const struct GNUNET_CRYPTO_BlindedMessage *bm = bp->blinded_message;
+ uint32_t cipher = htonl (bm->cipher);
+
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &cipher,
+ sizeof (cipher));
+ switch (bm->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_break (0);
+ return;
+ case GNUNET_CRYPTO_BSA_RSA:
+ GNUNET_CRYPTO_hash_context_read (
+ hash_context,
+ bm->details.rsa_blinded_message.blinded_msg,
+ bm->details.rsa_blinded_message.blinded_msg_size);
+ return;
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_CRYPTO_hash_context_read (
+ hash_context,
+ &bm->details.cs_blinded_message,
+ sizeof (bm->details.cs_blinded_message));
+ return;
+ }
+ GNUNET_assert (0);
+}
+
+
+void
+TALER_planchet_blinding_secret_create (
+ const struct TALER_PlanchetMasterSecretP *ps,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ union GNUNET_CRYPTO_BlindingSecretP *bks)
+{
+ const struct GNUNET_CRYPTO_BlindingInputValues *bi =
+ alg_values->blinding_inputs;
+
+ switch (bi->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_break (0);
+ return;
+ case GNUNET_CRYPTO_BSA_RSA:
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (&bks->rsa_bks,
+ sizeof (bks->rsa_bks),
+ "bks",
+ strlen ("bks"),
+ ps,
+ sizeof(*ps),
+ NULL,
+ 0));
+ return;
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (&bks->nonce,
+ sizeof (bks->nonce),
+ "bseed",
+ strlen ("bseed"),
+ ps,
+ sizeof(*ps),
+ &bi->details.cs_values,
+ sizeof(bi->details.cs_values),
+ NULL,
+ 0));
+ return;
+ }
+ GNUNET_assert (0);
+}
+
+
+void
+TALER_planchet_setup_coin_priv (
+ const struct TALER_PlanchetMasterSecretP *ps,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ struct TALER_CoinSpendPrivateKeyP *coin_priv)
+{
+ const struct GNUNET_CRYPTO_BlindingInputValues *bi
+ = alg_values->blinding_inputs;
+
+ switch (bi->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_break (0);
+ memset (coin_priv,
+ 0,
+ sizeof (*coin_priv));
+ return;
+ case GNUNET_CRYPTO_BSA_RSA:
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (coin_priv,
+ sizeof (*coin_priv),
+ "coin",
+ strlen ("coin"),
+ ps,
+ sizeof(*ps),
+ NULL,
+ 0));
+ return;
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (coin_priv,
+ sizeof (*coin_priv),
+ "coin",
+ strlen ("coin"),
+ ps,
+ sizeof(*ps),
+ &bi->details.cs_values,
+ sizeof(bi->details.cs_values),
+ NULL,
+ 0));
+ return;
+ }
+ GNUNET_assert (0);
+}
+
+
+void
+TALER_blinded_planchet_free (struct TALER_BlindedPlanchet *blinded_planchet)
+{
+ if (NULL != blinded_planchet->blinded_message)
+ {
+ GNUNET_CRYPTO_blinded_message_decref (blinded_planchet->blinded_message);
+ blinded_planchet->blinded_message = NULL;
+ }
+}
+
+
+/* end of denom.c */
diff --git a/src/util/do_bench_age_restriction b/src/util/do_bench_age_restriction
new file mode 100755
index 000000000..a65713439
--- /dev/null
+++ b/src/util/do_bench_age_restriction
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+gcc bench_age_restriction.c \
+ -lgnunetutil -lgnunetjson -lsodium -ljansson \
+ -L/usr/lib/x86_64-linux-gnu -lmicrohttpd -ltalerutil -lm \
+ -I../include \
+ -o bench_age_restriction && ./bench_age_restriction
+
diff --git a/src/util/exchange_signatures.c b/src/util/exchange_signatures.c
new file mode 100644
index 000000000..aaefb5cec
--- /dev/null
+++ b/src/util/exchange_signatures.c
@@ -0,0 +1,1894 @@
+/*
+ This file is part of TALER
+ 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 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_signatures.c
+ * @brief Utility functions for Taler security module signatures
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature on a confirmation
+ * from the exchange that a deposit request succeeded.
+ */
+struct TALER_DepositConfirmationPS
+{
+ /**
+ * Purpose must be #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT. Signed
+ * by a `struct TALER_ExchangePublicKeyP` using EdDSA.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash over the contract for which this deposit is made.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;
+
+ /**
+ * Hash over the wiring information of the merchant.
+ */
+ struct TALER_MerchantWireHashP h_wire GNUNET_PACKED;
+
+ /**
+ * Hash over the optional policy extension of the deposit, 0 if there
+ * was no policy.
+ */
+ struct TALER_ExtensionPolicyHashP h_policy GNUNET_PACKED;
+
+ /**
+ * Time when this confirmation was generated / when the exchange received
+ * the deposit request.
+ */
+ struct GNUNET_TIME_TimestampNBO exchange_timestamp;
+
+ /**
+ * By when does the exchange expect to pay the merchant
+ * (as per the merchant's request).
+ */
+ struct GNUNET_TIME_TimestampNBO wire_deadline;
+
+ /**
+ * How much time does the @e merchant have to issue a refund
+ * request? Zero if refunds are not allowed. After this time, the
+ * coin cannot be refunded. Note that the wire transfer will not be
+ * performed by the exchange until the refund deadline. This value
+ * is taken from the original deposit request.
+ */
+ struct GNUNET_TIME_TimestampNBO refund_deadline;
+
+ /**
+ * Amount to be deposited, excluding fee. Calculated from the
+ * amount with fee and the fee from the deposit request.
+ */
+ struct TALER_AmountNBO total_without_fee;
+
+ /**
+ * Hash over all of the coin signatures.
+ */
+ struct GNUNET_HashCode h_coin_sigs;
+
+ /**
+ * The Merchant's public key. Allows the merchant to later refund
+ * the transaction or to inquire about the wire transfer identifier.
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_deposit_confirmation_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ struct GNUNET_TIME_Timestamp wire_deadline,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_Amount *total_without_fee,
+ unsigned int num_coins,
+ const struct TALER_CoinSpendSignatureP *coin_sigs[static num_coins],
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_DepositConfirmationPS dcs = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT),
+ .purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)),
+ .h_contract_terms = *h_contract_terms,
+ .h_wire = *h_wire,
+ .exchange_timestamp = GNUNET_TIME_timestamp_hton (exchange_timestamp),
+ .wire_deadline = GNUNET_TIME_timestamp_hton (wire_deadline),
+ .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
+ .merchant_pub = *merchant_pub,
+ .h_policy = {{{0}}}
+ };
+ struct GNUNET_HashContext *hc;
+
+ hc = GNUNET_CRYPTO_hash_context_start ();
+ for (unsigned int i = 0; i<num_coins; i++)
+ GNUNET_CRYPTO_hash_context_read (hc,
+ coin_sigs[i],
+ sizeof (*coin_sigs[i]));
+ GNUNET_CRYPTO_hash_context_finish (hc,
+ &dcs.h_coin_sigs);
+ if (NULL != h_policy)
+ dcs.h_policy = *h_policy;
+ TALER_amount_hton (&dcs.total_without_fee,
+ total_without_fee);
+ return scb (&dcs.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_deposit_confirmation_verify (
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ struct GNUNET_TIME_Timestamp wire_deadline,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_Amount *total_without_fee,
+ unsigned int num_coins,
+ const struct TALER_CoinSpendSignatureP *coin_sigs[static num_coins],
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig)
+{
+ struct TALER_DepositConfirmationPS dcs = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT),
+ .purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)),
+ .h_contract_terms = *h_contract_terms,
+ .h_wire = *h_wire,
+ .exchange_timestamp = GNUNET_TIME_timestamp_hton (exchange_timestamp),
+ .wire_deadline = GNUNET_TIME_timestamp_hton (wire_deadline),
+ .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
+ .merchant_pub = *merchant_pub
+ };
+ struct GNUNET_HashContext *hc;
+
+ hc = GNUNET_CRYPTO_hash_context_start ();
+ for (unsigned int i = 0; i<num_coins; i++)
+ GNUNET_CRYPTO_hash_context_read (hc,
+ coin_sigs[i],
+ sizeof (*coin_sigs[i]));
+ GNUNET_CRYPTO_hash_context_finish (hc,
+ &dcs.h_coin_sigs);
+ if (NULL != h_policy)
+ dcs.h_policy = *h_policy;
+ TALER_amount_hton (&dcs.total_without_fee,
+ total_without_fee);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT,
+ &dcs,
+ &exchange_sig->eddsa_signature,
+ &exchange_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature on a request to refund
+ * a coin into the account of the customer.
+ */
+struct TALER_RefundConfirmationPS
+{
+ /**
+ * Purpose must be #TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash over the proposal data to identify the contract
+ * which is being refunded.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;
+
+ /**
+ * The coin's public key. This is the value that must have been
+ * signed (blindly) by the Exchange.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * The Merchant's public key. Allows the merchant to later refund
+ * the transaction or to inquire about the wire transfer identifier.
+ */
+ struct TALER_MerchantPublicKeyP merchant;
+
+ /**
+ * Merchant-generated transaction ID for the refund.
+ */
+ uint64_t rtransaction_id GNUNET_PACKED;
+
+ /**
+ * Amount to be refunded, including refund fee charged by the
+ * exchange to the customer.
+ */
+ struct TALER_AmountNBO refund_amount;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_refund_confirmation_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant,
+ uint64_t rtransaction_id,
+ const struct TALER_Amount *refund_amount,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_RefundConfirmationPS rc = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND),
+ .purpose.size = htonl (sizeof (rc)),
+ .h_contract_terms = *h_contract_terms,
+ .coin_pub = *coin_pub,
+ .merchant = *merchant,
+ .rtransaction_id = GNUNET_htonll (rtransaction_id)
+ };
+
+ TALER_amount_hton (&rc.refund_amount,
+ refund_amount);
+ return scb (&rc.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_refund_confirmation_verify (
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant,
+ uint64_t rtransaction_id,
+ const struct TALER_Amount *refund_amount,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_RefundConfirmationPS rc = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND),
+ .purpose.size = htonl (sizeof (rc)),
+ .h_contract_terms = *h_contract_terms,
+ .coin_pub = *coin_pub,
+ .merchant = *merchant,
+ .rtransaction_id = GNUNET_htonll (rtransaction_id)
+ };
+
+ TALER_amount_hton (&rc.refund_amount,
+ refund_amount);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND,
+ &rc,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format of the block signed by the Exchange in response to a successful
+ * "/refresh/melt" request. Hereby the exchange affirms that all of the
+ * coins were successfully melted. This also commits the exchange to a
+ * particular index to not be revealed during the refresh.
+ */
+struct TALER_RefreshMeltConfirmationPS
+{
+ /**
+ * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT. Signed
+ * by a `struct TALER_ExchangePublicKeyP` using EdDSA.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Commitment made in the /refresh/melt.
+ */
+ struct TALER_RefreshCommitmentP rc GNUNET_PACKED;
+
+ /**
+ * Index that the client will not have to reveal, in NBO.
+ * Must be smaller than #TALER_CNC_KAPPA.
+ */
+ uint32_t noreveal_index GNUNET_PACKED;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_melt_confirmation_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_RefreshCommitmentP *rc,
+ uint32_t noreveal_index,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_RefreshMeltConfirmationPS confirm = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT),
+ .purpose.size = htonl (sizeof (confirm)),
+ .rc = *rc,
+ .noreveal_index = htonl (noreveal_index)
+ };
+
+ return scb (&confirm.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_melt_confirmation_verify (
+ const struct TALER_RefreshCommitmentP *rc,
+ uint32_t noreveal_index,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig)
+{
+ struct TALER_RefreshMeltConfirmationPS confirm = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT),
+ .purpose.size = htonl (sizeof (confirm)),
+ .rc = *rc,
+ .noreveal_index = htonl (noreveal_index)
+ };
+
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT,
+ &confirm,
+ &exchange_sig->eddsa_signature,
+ &exchange_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format of the block signed by the Exchange in response to a
+ * successful "/reserves/$RESERVE_PUB/age-withdraw" request. Hereby the
+ * exchange affirms that the commitment along with the maximum age group and
+ * the amount were accepted. This also commits the exchange to a particular
+ * index to not be revealed during the reveal.
+ */
+struct TALER_AgeWithdrawConfirmationPS
+{
+ /**
+ * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW. Signed by a
+ * `struct TALER_ExchangePublicKeyP` using EdDSA.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Commitment made in the /reserves/$RESERVE_PUB/age-withdraw.
+ */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment GNUNET_PACKED;
+
+ /**
+ * Index that the client will not have to reveal, in NBO.
+ * Must be smaller than #TALER_CNC_KAPPA.
+ */
+ uint32_t noreveal_index GNUNET_PACKED;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+enum TALER_ErrorCode
+TALER_exchange_online_age_withdraw_confirmation_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ uint32_t noreveal_index,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+
+ struct TALER_AgeWithdrawConfirmationPS confirm = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW),
+ .purpose.size = htonl (sizeof (confirm)),
+ .h_commitment = *h_commitment,
+ .noreveal_index = htonl (noreveal_index)
+ };
+
+ return scb (&confirm.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_age_withdraw_confirmation_verify (
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ uint32_t noreveal_index,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig)
+{
+ struct TALER_AgeWithdrawConfirmationPS confirm = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW),
+ .purpose.size = htonl (sizeof (confirm)),
+ .h_commitment = *h_commitment,
+ .noreveal_index = htonl (noreveal_index)
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW,
+ &confirm,
+ &exchange_sig->eddsa_signature,
+ &exchange_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/* TODO:oec: add signature for age-withdraw, age-reveal */
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange over the full set of keys, used
+ * to detect cheating exchanges that give out different sets to
+ * different users.
+ */
+struct TALER_ExchangeKeySetPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_EXCHANGE_KEY_SET. Signed
+ * by a `struct TALER_ExchangePublicKeyP` using EdDSA.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Time of the key set issue.
+ */
+ struct GNUNET_TIME_TimestampNBO list_issue_date;
+
+ /**
+ * Hash over the various denomination signing keys returned.
+ */
+ struct GNUNET_HashCode hc GNUNET_PACKED;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_key_set_sign (
+ TALER_ExchangeSignCallback2 scb,
+ void *cls,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct GNUNET_HashCode *hc,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_ExchangeKeySetPS ks = {
+ .purpose.size = htonl (sizeof (ks)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET),
+ .list_issue_date = GNUNET_TIME_timestamp_hton (timestamp),
+ .hc = *hc
+ };
+
+ return scb (cls,
+ &ks.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_key_set_verify (
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct GNUNET_HashCode *hc,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_ExchangeKeySetPS ks = {
+ .purpose.size = htonl (sizeof (ks)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET),
+ .list_issue_date = GNUNET_TIME_timestamp_hton (timestamp),
+ .hc = *hc
+ };
+
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_KEY_SET,
+ &ks,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature by which an exchange affirms that an account
+ * successfully passed the KYC checks.
+ */
+struct TALER_ExchangeAccountSetupSuccessPS
+{
+ /**
+ * Purpose is #TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS. Signed by a
+ * `struct TALER_ExchangePublicKeyP` using EdDSA.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash over the payto for which the signature was made.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Hash over details on *which* KYC obligations were discharged!
+ */
+ struct GNUNET_HashCode h_kyc;
+
+ /**
+ * When was the signature made.
+ */
+ struct GNUNET_TIME_TimestampNBO timestamp;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_account_setup_success_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_PaytoHashP *h_payto,
+ const json_t *kyc,
+ struct GNUNET_TIME_Timestamp timestamp,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_ExchangeAccountSetupSuccessPS kyc_purpose = {
+ .purpose.size = htonl (sizeof (kyc_purpose)),
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS),
+ .h_payto = *h_payto,
+ .timestamp = GNUNET_TIME_timestamp_hton (timestamp)
+ };
+
+ TALER_json_hash (kyc,
+ &kyc_purpose.h_kyc);
+ return scb (&kyc_purpose.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_account_setup_success_verify (
+ const struct TALER_PaytoHashP *h_payto,
+ const json_t *kyc,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_ExchangeAccountSetupSuccessPS kyc_purpose = {
+ .purpose.size = htonl (sizeof (kyc_purpose)),
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS),
+ .h_payto = *h_payto,
+ .timestamp = GNUNET_TIME_timestamp_hton (timestamp)
+ };
+
+ TALER_json_hash (kyc,
+ &kyc_purpose.h_kyc);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS,
+ &kyc_purpose,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format internally used for packing the detailed information
+ * to generate the signature for /track/transfer signatures.
+ */
+struct TALER_WireDepositDetailP
+{
+
+ /**
+ * Hash of the contract
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Time when the wire transfer was performed by the exchange.
+ */
+ struct GNUNET_TIME_TimestampNBO execution_time;
+
+ /**
+ * Coin's public key.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Total value of the coin.
+ */
+ struct TALER_AmountNBO deposit_value;
+
+ /**
+ * Fees charged by the exchange for the deposit.
+ */
+ struct TALER_AmountNBO deposit_fee;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_online_wire_deposit_append (
+ struct GNUNET_HashContext *hash_context,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp execution_time,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *deposit_value,
+ const struct TALER_Amount *deposit_fee)
+{
+ struct TALER_WireDepositDetailP dd = {
+ .h_contract_terms = *h_contract_terms,
+ .execution_time = GNUNET_TIME_timestamp_hton (execution_time),
+ .coin_pub = *coin_pub
+ };
+ TALER_amount_hton (&dd.deposit_value,
+ deposit_value);
+ TALER_amount_hton (&dd.deposit_fee,
+ deposit_fee);
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &dd,
+ sizeof (dd));
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature for /wire/deposit
+ * replies.
+ */
+struct TALER_WireDepositDataPS
+{
+ /**
+ * Purpose header for the signature over the contract with
+ * purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Total amount that was transferred.
+ */
+ struct TALER_AmountNBO total;
+
+ /**
+ * Wire fee that was charged.
+ */
+ struct TALER_AmountNBO wire_fee;
+
+ /**
+ * Public key of the merchant (for all aggregated transactions).
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+
+ /**
+ * Hash of bank account of the merchant.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Hash of the individual deposits that were aggregated,
+ * each in the format of a `struct TALER_WireDepositDetailP`.
+ */
+ struct GNUNET_HashCode h_details;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_wire_deposit_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_Amount *total,
+ const struct TALER_Amount *wire_fee,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const char *payto,
+ const struct GNUNET_HashCode *h_details,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_WireDepositDataPS wdp = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT),
+ .purpose.size = htonl (sizeof (wdp)),
+ .merchant_pub = *merchant_pub,
+ .h_details = *h_details
+ };
+
+ TALER_amount_hton (&wdp.total,
+ total);
+ TALER_amount_hton (&wdp.wire_fee,
+ wire_fee);
+ TALER_payto_hash (payto,
+ &wdp.h_payto);
+ return scb (&wdp.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_wire_deposit_verify (
+ const struct TALER_Amount *total,
+ const struct TALER_Amount *wire_fee,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct GNUNET_HashCode *h_details,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_WireDepositDataPS wdp = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT),
+ .purpose.size = htonl (sizeof (wdp)),
+ .merchant_pub = *merchant_pub,
+ .h_details = *h_details,
+ .h_payto = *h_payto
+ };
+
+ TALER_amount_hton (&wdp.total,
+ total);
+ TALER_amount_hton (&wdp.wire_fee,
+ wire_fee);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT,
+ &wdp,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Details affirmed by the exchange about a wire transfer the exchange
+ * claims to have done with respect to a deposit operation.
+ */
+struct TALER_ConfirmWirePS
+{
+ /**
+ * Purpose header for the signature over the contract with
+ * purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash over the wiring information of the merchant.
+ */
+ struct TALER_MerchantWireHashP h_wire GNUNET_PACKED;
+
+ /**
+ * Hash over the contract for which this deposit is made.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;
+
+ /**
+ * Raw value (binary encoding) of the wire transfer subject.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ /**
+ * The coin's public key. This is the value that must have been
+ * signed (blindly) by the Exchange.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * When did the exchange execute this transfer? Note that the
+ * timestamp may not be exactly the same on the wire, i.e.
+ * because the wire has a different timezone or resolution.
+ */
+ struct GNUNET_TIME_TimestampNBO execution_time;
+
+ /**
+ * The contribution of @e coin_pub to the total transfer volume.
+ * This is the value of the deposit minus the fee.
+ */
+ struct TALER_AmountNBO coin_contribution;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_confirm_wire_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct GNUNET_TIME_Timestamp execution_time,
+ const struct TALER_Amount *coin_contribution,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+
+{
+ struct TALER_ConfirmWirePS cw = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE),
+ .purpose.size = htonl (sizeof (cw)),
+ .h_wire = *h_wire,
+ .h_contract_terms = *h_contract_terms,
+ .wtid = *wtid,
+ .coin_pub = *coin_pub,
+ .execution_time = GNUNET_TIME_timestamp_hton (execution_time)
+ };
+
+ TALER_amount_hton (&cw.coin_contribution,
+ coin_contribution);
+ return scb (&cw.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_confirm_wire_verify (
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct GNUNET_TIME_Timestamp execution_time,
+ const struct TALER_Amount *coin_contribution,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_ConfirmWirePS cw = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE),
+ .purpose.size = htonl (sizeof (cw)),
+ .h_wire = *h_wire,
+ .h_contract_terms = *h_contract_terms,
+ .wtid = *wtid,
+ .coin_pub = *coin_pub,
+ .execution_time = GNUNET_TIME_timestamp_hton (execution_time)
+ };
+
+ TALER_amount_hton (&cw.coin_contribution,
+ coin_contribution);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE,
+ &cw,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it will
+ * refund a coin as part of the emergency /recoup
+ * protocol. The recoup will go back to the bank
+ * account that created the reserve.
+ */
+struct TALER_RecoupConfirmationPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * When did the exchange receive the recoup request?
+ * Indirectly determines when the wire transfer is (likely)
+ * to happen.
+ */
+ struct GNUNET_TIME_TimestampNBO timestamp;
+
+ /**
+ * How much of the coin's value will the exchange transfer?
+ * (Needed in case the coin was partially spent.)
+ */
+ struct TALER_AmountNBO recoup_amount;
+
+ /**
+ * Public key of the coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Public key of the reserve that will receive the recoup.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_confirm_recoup_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_Amount *recoup_amount,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_RecoupConfirmationPS pc = {
+ .purpose.size = htonl (sizeof (pc)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP),
+ .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+ .coin_pub = *coin_pub,
+ .reserve_pub = *reserve_pub
+ };
+
+ TALER_amount_hton (&pc.recoup_amount,
+ recoup_amount);
+ return scb (&pc.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_confirm_recoup_verify (
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_Amount *recoup_amount,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_RecoupConfirmationPS pc = {
+ .purpose.size = htonl (sizeof (pc)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP),
+ .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+ .coin_pub = *coin_pub,
+ .reserve_pub = *reserve_pub
+ };
+
+ TALER_amount_hton (&pc.recoup_amount,
+ recoup_amount);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP,
+ &pc,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it will refund a refreshed coin
+ * as part of the emergency /recoup protocol. The recoup will go back to the
+ * old coin's balance.
+ */
+struct TALER_RecoupRefreshConfirmationPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * When did the exchange receive the recoup request?
+ * Indirectly determines when the wire transfer is (likely)
+ * to happen.
+ */
+ struct GNUNET_TIME_TimestampNBO timestamp;
+
+ /**
+ * How much of the coin's value will the exchange transfer?
+ * (Needed in case the coin was partially spent.)
+ */
+ struct TALER_AmountNBO recoup_amount;
+
+ /**
+ * Public key of the refreshed coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Public key of the old coin that will receive the recoup.
+ */
+ struct TALER_CoinSpendPublicKeyP old_coin_pub;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_confirm_recoup_refresh_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_Amount *recoup_amount,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_RecoupRefreshConfirmationPS pc = {
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH),
+ .purpose.size = htonl (sizeof (pc)),
+ .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+ .coin_pub = *coin_pub,
+ .old_coin_pub = *old_coin_pub
+ };
+
+ TALER_amount_hton (&pc.recoup_amount,
+ recoup_amount);
+ return scb (&pc.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_confirm_recoup_refresh_verify (
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_Amount *recoup_amount,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_RecoupRefreshConfirmationPS pc = {
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH),
+ .purpose.size = htonl (sizeof (pc)),
+ .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+ .coin_pub = *coin_pub,
+ .old_coin_pub = *old_coin_pub
+ };
+
+ TALER_amount_hton (&pc.recoup_amount,
+ recoup_amount);
+
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH,
+ &pc,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it does not
+ * currently know a denomination by the given hash.
+ */
+struct TALER_DenominationUnknownAffirmationPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * When did the exchange sign this message.
+ */
+ struct GNUNET_TIME_TimestampNBO timestamp;
+
+ /**
+ * Hash of the public denomination key we do not know.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_denomination_unknown_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_DenominationUnknownAffirmationPS dua = {
+ .purpose.size = htonl (sizeof (dua)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN),
+ .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+ .h_denom_pub = *h_denom_pub,
+ };
+
+ return scb (&dua.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_denomination_unknown_verify (
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_DenominationUnknownAffirmationPS dua = {
+ .purpose.size = htonl (sizeof (dua)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN),
+ .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+ .h_denom_pub = *h_denom_pub,
+ };
+
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN,
+ &dua,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it does not
+ * currently consider the given denomination to be valid
+ * for the requested operation.
+ */
+struct TALER_DenominationExpiredAffirmationPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * When did the exchange sign this message.
+ */
+ struct GNUNET_TIME_TimestampNBO timestamp;
+
+ /**
+ * Name of the operation that is not allowed at this time. Might NOT be 0-terminated, but is padded with 0s.
+ */
+ char operation[8];
+
+ /**
+ * Hash of the public denomination key we do not know.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_denomination_expired_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const char *op,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_DenominationExpiredAffirmationPS dua = {
+ .purpose.size = htonl (sizeof (dua)),
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED),
+ .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+ .h_denom_pub = *h_denom_pub,
+ };
+
+ /* strncpy would create a compiler warning */
+ GNUNET_memcpy (dua.operation,
+ op,
+ GNUNET_MIN (sizeof (dua.operation),
+ strlen (op)));
+ return scb (&dua.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_denomination_expired_verify (
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const char *op,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_DenominationExpiredAffirmationPS dua = {
+ .purpose.size = htonl (sizeof (dua)),
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED),
+ .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+ .h_denom_pub = *h_denom_pub,
+ };
+
+ /* strncpy would create a compiler warning */
+ GNUNET_memcpy (dua.operation,
+ op,
+ GNUNET_MIN (sizeof (dua.operation),
+ strlen (op)));
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED,
+ &dua,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it has
+ * closed a reserve and send back the funds.
+ */
+struct TALER_ReserveCloseConfirmationPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * When did the exchange initiate the wire transfer.
+ */
+ struct GNUNET_TIME_TimestampNBO timestamp;
+
+ /**
+ * How much did the exchange send?
+ */
+ struct TALER_AmountNBO closing_amount;
+
+ /**
+ * How much did the exchange charge for closing the reserve?
+ */
+ struct TALER_AmountNBO closing_fee;
+
+ /**
+ * Public key of the reserve that was closed.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Hash of the receiver's bank account.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Wire transfer subject.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_reserve_closed_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_Amount *closing_amount,
+ const struct TALER_Amount *closing_fee,
+ const char *payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_ReserveCloseConfirmationPS rcc = {
+ .purpose.size = htonl (sizeof (rcc)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED),
+ .wtid = *wtid,
+ .reserve_pub = *reserve_pub,
+ .timestamp = GNUNET_TIME_timestamp_hton (timestamp)
+ };
+
+ TALER_amount_hton (&rcc.closing_amount,
+ closing_amount);
+ TALER_amount_hton (&rcc.closing_fee,
+ closing_fee);
+ TALER_payto_hash (payto,
+ &rcc.h_payto);
+ return scb (&rcc.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_reserve_closed_verify (
+ struct GNUNET_TIME_Timestamp timestamp,
+ const struct TALER_Amount *closing_amount,
+ const struct TALER_Amount *closing_fee,
+ const char *payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_ReserveCloseConfirmationPS rcc = {
+ .purpose.size = htonl (sizeof (rcc)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED),
+ .wtid = *wtid,
+ .reserve_pub = *reserve_pub,
+ .timestamp = GNUNET_TIME_timestamp_hton (timestamp)
+ };
+
+ TALER_amount_hton (&rcc.closing_amount,
+ closing_amount);
+ TALER_amount_hton (&rcc.closing_fee,
+ closing_fee);
+ TALER_payto_hash (payto,
+ &rcc.h_payto);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED,
+ &rcc,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it has
+ * received funds deposited into a purse.
+ */
+struct TALER_PurseCreateDepositConfirmationPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * When did the exchange receive the deposits.
+ */
+ struct GNUNET_TIME_TimestampNBO exchange_time;
+
+ /**
+ * When will the purse expire?
+ */
+ struct GNUNET_TIME_TimestampNBO purse_expiration;
+
+ /**
+ * How much should the purse ultimately contain.
+ */
+ struct TALER_AmountNBO amount_without_fee;
+
+ /**
+ * How much was deposited so far.
+ */
+ struct TALER_AmountNBO total_deposited;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Hash of the contract of the purse.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_purse_created_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp exchange_time,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_Amount *total_deposited,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_PurseCreateDepositConfirmationPS dc = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION),
+ .purpose.size = htonl (sizeof (dc)),
+ .h_contract_terms = *h_contract_terms,
+ .purse_pub = *purse_pub,
+ .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+ .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time)
+ };
+
+ TALER_amount_hton (&dc.amount_without_fee,
+ amount_without_fee);
+ TALER_amount_hton (&dc.total_deposited,
+ total_deposited);
+ return scb (&dc.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_created_verify (
+ struct GNUNET_TIME_Timestamp exchange_time,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_Amount *total_deposited,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_PurseCreateDepositConfirmationPS dc = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION),
+ .purpose.size = htonl (sizeof (dc)),
+ .h_contract_terms = *h_contract_terms,
+ .purse_pub = *purse_pub,
+ .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+ .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time)
+ };
+
+ TALER_amount_hton (&dc.amount_without_fee,
+ amount_without_fee);
+ TALER_amount_hton (&dc.total_deposited,
+ total_deposited);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION,
+ &dc,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it has
+ * received funds deposited into a purse.
+ */
+struct TALER_CoinPurseRefundConfirmationPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Public key of the coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * How much will be refunded to the purse.
+ */
+ struct TALER_AmountNBO refunded_amount;
+
+ /**
+ * How much was the refund fee.
+ */
+ struct TALER_AmountNBO refund_fee;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_purse_refund_sign (
+ TALER_ExchangeSignCallback scb,
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_Amount *refund_fee,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_CoinPurseRefundConfirmationPS dc = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND),
+ .purpose.size = htonl (sizeof (dc)),
+ .coin_pub = *coin_pub,
+ .purse_pub = *purse_pub,
+ };
+
+ TALER_amount_hton (&dc.refunded_amount,
+ amount_without_fee);
+ TALER_amount_hton (&dc.refund_fee,
+ refund_fee);
+ return scb (&dc.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_refund_verify (
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_Amount *refund_fee,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_CoinPurseRefundConfirmationPS dc = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND),
+ .purpose.size = htonl (sizeof (dc)),
+ .coin_pub = *coin_pub,
+ .purse_pub = *purse_pub,
+ };
+
+ TALER_amount_hton (&dc.refunded_amount,
+ amount_without_fee);
+ TALER_amount_hton (&dc.refund_fee,
+ refund_fee);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND,
+ &dc,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it has
+ * merged a purse into a reserve.
+ */
+struct TALER_PurseMergedConfirmationPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_MERGED
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * When did the exchange receive the deposits.
+ */
+ struct GNUNET_TIME_TimestampNBO exchange_time;
+
+ /**
+ * When will the purse expire?
+ */
+ struct GNUNET_TIME_TimestampNBO purse_expiration;
+
+ /**
+ * How much should the purse ultimately contain.
+ */
+ struct TALER_AmountNBO amount_without_fee;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Public key of the reserve.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Hash of the contract of the purse.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Hash of the provider URL hosting the reserve.
+ */
+ struct GNUNET_HashCode h_provider_url;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_purse_merged_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp exchange_time,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *exchange_url,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_PurseMergedConfirmationPS dc = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_MERGED),
+ .purpose.size = htonl (sizeof (dc)),
+ .h_contract_terms = *h_contract_terms,
+ .purse_pub = *purse_pub,
+ .reserve_pub = *reserve_pub,
+ .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+ .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time)
+ };
+
+ TALER_amount_hton (&dc.amount_without_fee,
+ amount_without_fee);
+ GNUNET_CRYPTO_hash (exchange_url,
+ strlen (exchange_url) + 1,
+ &dc.h_provider_url);
+ return scb (&dc.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_merged_verify (
+ struct GNUNET_TIME_Timestamp exchange_time,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *exchange_url,
+ const struct TALER_ExchangePublicKeyP *pub,
+ const struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_PurseMergedConfirmationPS dc = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_MERGED),
+ .purpose.size = htonl (sizeof (dc)),
+ .h_contract_terms = *h_contract_terms,
+ .purse_pub = *purse_pub,
+ .reserve_pub = *reserve_pub,
+ .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+ .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time)
+ };
+
+ TALER_amount_hton (&dc.amount_without_fee,
+ amount_without_fee);
+ GNUNET_CRYPTO_hash (exchange_url,
+ strlen (exchange_url) + 1,
+ &dc.h_provider_url);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_MERGED,
+ &dc,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature on a purse status
+ * from the exchange.
+ */
+struct TALER_PurseStatusPS
+{
+ /**
+ * Purpose must be #TALER_SIGNATURE_EXCHANGE_PURSE_STATUS. Signed
+ * by a `struct TALER_ExchangePublicKeyP` using EdDSA.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Time when the purse was merged, possibly 'never'.
+ */
+ struct GNUNET_TIME_TimestampNBO merge_timestamp;
+
+ /**
+ * Time when the purse was deposited last, possibly 'never'.
+ */
+ struct GNUNET_TIME_TimestampNBO deposit_timestamp;
+
+ /**
+ * Amount deposited in total in the purse without fees.
+ * May be possibly less than the target amount.
+ */
+ struct TALER_AmountNBO balance;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_purse_status_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
+ const struct TALER_Amount *balance,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_PurseStatusPS dcs = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_PURSE_STATUS),
+ .purpose.size = htonl (sizeof (dcs)),
+ .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
+ .deposit_timestamp = GNUNET_TIME_timestamp_hton (deposit_timestamp)
+ };
+
+ TALER_amount_hton (&dcs.balance,
+ balance);
+ return scb (&dcs.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_status_verify (
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
+ const struct TALER_Amount *balance,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig)
+{
+ struct TALER_PurseStatusPS dcs = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_PURSE_STATUS),
+ .purpose.size = htonl (sizeof (dcs)),
+ .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
+ .deposit_timestamp = GNUNET_TIME_timestamp_hton (deposit_timestamp)
+ };
+
+ TALER_amount_hton (&dcs.balance,
+ balance);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_PURSE_STATUS,
+ &dcs,
+ &exchange_sig->eddsa_signature,
+ &exchange_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by the exchange to affirm that the
+ * owner of a reserve has certain attributes.
+ */
+struct TALER_ExchangeAttestPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Time when the attestation was made.
+ */
+ struct GNUNET_TIME_TimestampNBO attest_timestamp;
+
+ /**
+ * Time when the attestation expires.
+ */
+ struct GNUNET_TIME_TimestampNBO expiration_time;
+
+ /**
+ * Public key of the reserve for which the attributes
+ * are attested.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Hash over the attributes.
+ */
+ struct GNUNET_HashCode h_attributes;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_reserve_attest_details_sign (
+ TALER_ExchangeSignCallback scb,
+ struct GNUNET_TIME_Timestamp attest_timestamp,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *attributes,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_ExchangeAttestPS rap = {
+ .purpose.size = htonl (sizeof (rap)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS),
+ .attest_timestamp = GNUNET_TIME_timestamp_hton (attest_timestamp),
+ .expiration_time = GNUNET_TIME_timestamp_hton (expiration_time),
+ .reserve_pub = *reserve_pub
+ };
+
+ TALER_json_hash (attributes,
+ &rap.h_attributes);
+ return scb (&rap.purpose,
+ pub,
+ sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_reserve_attest_details_verify (
+ struct GNUNET_TIME_Timestamp attest_timestamp,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *attributes,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TALER_ExchangeAttestPS rap = {
+ .purpose.size = htonl (sizeof (rap)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS),
+ .attest_timestamp = GNUNET_TIME_timestamp_hton (attest_timestamp),
+ .expiration_time = GNUNET_TIME_timestamp_hton (expiration_time),
+ .reserve_pub = *reserve_pub
+ };
+
+ TALER_json_hash (attributes,
+ &rap.h_attributes);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS,
+ &rap,
+ &sig->eddsa_signature,
+ &pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/* end of exchange_signatures.c */
diff --git a/src/util/iban.c b/src/util/iban.c
new file mode 100644
index 000000000..c2274d3cb
--- /dev/null
+++ b/src/util/iban.c
@@ -0,0 +1,317 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019-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 iban.c
+ * @brief Common utility function for dealing with IBAN numbers
+ * @author Florian Dold
+ */
+#include "platform.h"
+#include "taler_util.h"
+
+
+/* Country table taken from GNU gettext */
+
+/**
+ * Entry in the country table.
+ */
+struct CountryTableEntry
+{
+ /**
+ * 2-Character international country code.
+ */
+ const char *code;
+
+ /**
+ * Long English name of the country.
+ */
+ const char *english;
+};
+
+
+/* Keep the following table in sync with gettext.
+ WARNING: the entries should stay sorted according to the code */
+/**
+ * List of country codes.
+ */
+static const struct CountryTableEntry country_table[] = {
+ { "AE", "U.A.E." },
+ { "AF", "Afghanistan" },
+ { "AL", "Albania" },
+ { "AM", "Armenia" },
+ { "AN", "Netherlands Antilles" },
+ { "AR", "Argentina" },
+ { "AT", "Austria" },
+ { "AU", "Australia" },
+ { "AZ", "Azerbaijan" },
+ { "BA", "Bosnia and Herzegovina" },
+ { "BD", "Bangladesh" },
+ { "BE", "Belgium" },
+ { "BG", "Bulgaria" },
+ { "BH", "Bahrain" },
+ { "BN", "Brunei Darussalam" },
+ { "BO", "Bolivia" },
+ { "BR", "Brazil" },
+ { "BT", "Bhutan" },
+ { "BY", "Belarus" },
+ { "BZ", "Belize" },
+ { "CA", "Canada" },
+ { "CG", "Congo" },
+ { "CH", "Switzerland" },
+ { "CI", "Cote d'Ivoire" },
+ { "CL", "Chile" },
+ { "CM", "Cameroon" },
+ { "CN", "People's Republic of China" },
+ { "CO", "Colombia" },
+ { "CR", "Costa Rica" },
+ { "CS", "Serbia and Montenegro" },
+ { "CZ", "Czech Republic" },
+ { "DE", "Germany" },
+ { "DK", "Denmark" },
+ { "DO", "Dominican Republic" },
+ { "DZ", "Algeria" },
+ { "EC", "Ecuador" },
+ { "EE", "Estonia" },
+ { "EG", "Egypt" },
+ { "ER", "Eritrea" },
+ { "ES", "Spain" },
+ { "ET", "Ethiopia" },
+ { "FI", "Finland" },
+ { "FO", "Faroe Islands" },
+ { "FR", "France" },
+ { "GB", "United Kingdom" },
+ { "GD", "Caribbean" },
+ { "GE", "Georgia" },
+ { "GL", "Greenland" },
+ { "GR", "Greece" },
+ { "GT", "Guatemala" },
+ { "HK", "Hong Kong" },
+ { "HK", "Hong Kong S.A.R." },
+ { "HN", "Honduras" },
+ { "HR", "Croatia" },
+ { "HT", "Haiti" },
+ { "HU", "Hungary" },
+ { "ID", "Indonesia" },
+ { "IE", "Ireland" },
+ { "IL", "Israel" },
+ { "IN", "India" },
+ { "IQ", "Iraq" },
+ { "IR", "Iran" },
+ { "IS", "Iceland" },
+ { "IT", "Italy" },
+ { "JM", "Jamaica" },
+ { "JO", "Jordan" },
+ { "JP", "Japan" },
+ { "KE", "Kenya" },
+ { "KG", "Kyrgyzstan" },
+ { "KH", "Cambodia" },
+ { "KR", "South Korea" },
+ { "KW", "Kuwait" },
+ { "KZ", "Kazakhstan" },
+ { "LA", "Laos" },
+ { "LB", "Lebanon" },
+ { "LI", "Liechtenstein" },
+ { "LK", "Sri Lanka" },
+ { "LT", "Lithuania" },
+ { "LU", "Luxembourg" },
+ { "LV", "Latvia" },
+ { "LY", "Libya" },
+ { "MA", "Morocco" },
+ { "MC", "Principality of Monaco" },
+ { "MD", "Moldava" },
+ { "MD", "Moldova" },
+ { "ME", "Montenegro" },
+ { "MK", "Former Yugoslav Republic of Macedonia" },
+ { "ML", "Mali" },
+ { "MM", "Myanmar" },
+ { "MN", "Mongolia" },
+ { "MO", "Macau S.A.R." },
+ { "MT", "Malta" },
+ { "MV", "Maldives" },
+ { "MX", "Mexico" },
+ { "MY", "Malaysia" },
+ { "NG", "Nigeria" },
+ { "NI", "Nicaragua" },
+ { "NL", "Netherlands" },
+ { "NO", "Norway" },
+ { "NP", "Nepal" },
+ { "NZ", "New Zealand" },
+ { "OM", "Oman" },
+ { "PA", "Panama" },
+ { "PE", "Peru" },
+ { "PH", "Philippines" },
+ { "PK", "Islamic Republic of Pakistan" },
+ { "PL", "Poland" },
+ { "PR", "Puerto Rico" },
+ { "PT", "Portugal" },
+ { "PY", "Paraguay" },
+ { "QA", "Qatar" },
+ { "RE", "Reunion" },
+ { "RO", "Romania" },
+ { "RS", "Serbia" },
+ { "RU", "Russia" },
+ { "RW", "Rwanda" },
+ { "SA", "Saudi Arabia" },
+ { "SE", "Sweden" },
+ { "SG", "Singapore" },
+ { "SI", "Slovenia" },
+ { "SK", "Slovak" },
+ { "SN", "Senegal" },
+ { "SO", "Somalia" },
+ { "SR", "Suriname" },
+ { "SV", "El Salvador" },
+ { "SY", "Syria" },
+ { "TH", "Thailand" },
+ { "TJ", "Tajikistan" },
+ { "TM", "Turkmenistan" },
+ { "TN", "Tunisia" },
+ { "TR", "Turkey" },
+ { "TT", "Trinidad and Tobago" },
+ { "TW", "Taiwan" },
+ { "TZ", "Tanzania" },
+ { "UA", "Ukraine" },
+ { "US", "United States" },
+ { "UY", "Uruguay" },
+ { "VA", "Vatican" },
+ { "VE", "Venezuela" },
+ { "VN", "Viet Nam" },
+ { "YE", "Yemen" },
+ { "ZA", "South Africa" },
+ { "ZW", "Zimbabwe" }
+};
+
+
+/**
+ * Country code comparator function, for binary search with bsearch().
+ *
+ * @param ptr1 pointer to a `struct table_entry`
+ * @param ptr2 pointer to a `struct table_entry`
+ * @return result of memcmp()'ing the 2-digit country codes of the entries
+ */
+static int
+cmp_country_code (const void *ptr1,
+ const void *ptr2)
+{
+ const struct CountryTableEntry *cc1 = ptr1;
+ const struct CountryTableEntry *cc2 = ptr2;
+
+ return memcmp (cc1->code,
+ cc2->code,
+ 2);
+}
+
+
+char *
+TALER_iban_validate (const char *iban)
+{
+ char cc[2];
+ char ibancpy[35];
+ struct CountryTableEntry cc_entry;
+ unsigned int len;
+ char *nbuf;
+ unsigned long long dividend;
+ unsigned long long remainder;
+ unsigned int i;
+ unsigned int j;
+
+ if (NULL == iban)
+ return GNUNET_strdup ("(null) is not a valid IBAN");
+ len = strlen (iban);
+ if (len < 4)
+ return GNUNET_strdup ("IBAN number too short to be valid");
+ if (len > 34)
+ return GNUNET_strdup ("IBAN number too long to be valid");
+ GNUNET_memcpy (cc, iban, 2);
+ GNUNET_memcpy (ibancpy, iban + 4, len - 4);
+ GNUNET_memcpy (ibancpy + len - 4, iban, 4);
+ ibancpy[len] = '\0';
+ cc_entry.code = cc;
+ cc_entry.english = NULL;
+ if (NULL ==
+ bsearch (&cc_entry,
+ country_table,
+ sizeof (country_table) / sizeof (struct CountryTableEntry),
+ sizeof (struct CountryTableEntry),
+ &cmp_country_code))
+ {
+ char *msg;
+
+ GNUNET_asprintf (&msg,
+ "Country code `%c%c' not supported\n",
+ cc[0],
+ cc[1]);
+ return msg;
+ }
+ nbuf = GNUNET_malloc ((len * 2) + 1);
+ for (i = 0, j = 0; i < len; i++)
+ {
+ if (isalpha ((unsigned char) ibancpy[i]))
+ {
+ if (2 != snprintf (&nbuf[j],
+ 3,
+ "%2u",
+ (ibancpy[i] - 'A' + 10)))
+ {
+ GNUNET_break (0);
+ return GNUNET_strdup ("internal invariant violation");
+ }
+ j += 2;
+ continue;
+ }
+ nbuf[j] = ibancpy[i];
+ j++;
+ }
+ for (j = 0; '\0' != nbuf[j]; j++)
+ {
+ if (! isdigit ( (unsigned char) nbuf[j]))
+ {
+ char *msg;
+
+ GNUNET_asprintf (&msg,
+ "digit expected at `%s'",
+ &nbuf[j]);
+ GNUNET_free (nbuf);
+ return msg;
+ }
+ }
+ GNUNET_assert (sizeof(dividend) >= 8);
+ remainder = 0;
+ for (unsigned int i = 0; i<j; i += 16)
+ {
+ int nread;
+
+ if (1 !=
+ sscanf (&nbuf[i],
+ "%16llu %n",
+ &dividend,
+ &nread))
+ {
+ char *msg;
+
+ GNUNET_asprintf (&msg,
+ "wrong input for checksum calculation at `%s'",
+ &nbuf[i]);
+ GNUNET_free (nbuf);
+ return msg;
+ }
+ if (0 != remainder)
+ dividend += remainder * (pow (10, nread));
+ remainder = dividend % 97;
+ }
+ GNUNET_free (nbuf);
+ if (1 != remainder)
+ return GNUNET_strdup ("IBAN checksum is wrong");
+ return NULL;
+}
diff --git a/src/util/lang.c b/src/util/lang.c
new file mode 100644
index 000000000..ffc50a557
--- /dev/null
+++ b/src/util/lang.c
@@ -0,0 +1,73 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 lang.c
+ * @brief Utility functions for parsing and matching RFC 7231 language strings.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+
+
+/**
+ * Check if @a lang matches the @a language_pattern, and if so with
+ * which preference.
+ * See also: https://tools.ietf.org/html/rfc7231#section-5.3.1
+ *
+ * @param language_pattern a language preferences string
+ * like "fr-CH, fr;q=0.9, en;q=0.8, *;q=0.1"
+ * @param lang the 2-digit language to match
+ * @return q-weight given for @a lang in @a language_pattern, 1.0 if no weights are given;
+ * 0 if @a lang is not in @a language_pattern
+ */
+double
+TALER_language_matches (const char *language_pattern,
+ const char *lang)
+{
+ char *p = GNUNET_strdup (language_pattern);
+ char *sptr;
+ double r = 0.0;
+
+ for (char *tok = strtok_r (p, ",", &sptr);
+ NULL != tok;
+ tok = strtok_r (NULL, ",", &sptr))
+ {
+ char *sptr2;
+ char *lp = strtok_r (tok, ";", &sptr2);
+ char *qp = strtok_r (NULL, ";", &sptr2);
+ double q = 1.0;
+
+ if (NULL == lp)
+ continue; /* should be impossible, but makes static analysis happy */
+ while (isspace ((int) *lp))
+ lp++;
+ if (NULL != qp)
+ while (isspace ((int) *qp))
+ qp++;
+ GNUNET_break_op ( (NULL == qp) ||
+ (1 == sscanf (qp,
+ "q=%lf",
+ &q)) );
+ if (0 == strcasecmp (lang,
+ lp))
+ r = GNUNET_MAX (r, q);
+ }
+ GNUNET_free (p);
+ return r;
+}
+
+
+/* end of lang.c */
diff --git a/src/util/merchant_signatures.c b/src/util/merchant_signatures.c
new file mode 100644
index 000000000..35e0b0e07
--- /dev/null
+++ b/src/util/merchant_signatures.c
@@ -0,0 +1,352 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 merchant_signatures.c
+ * @brief Utility functions for Taler merchant signatures
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature on a request to obtain
+ * the wire transfer identifier associated with a deposit.
+ */
+struct TALER_DepositTrackPS
+{
+ /**
+ * Purpose must be #TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash over the proposal data of the contract for which this deposit is made.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;
+
+ /**
+ * Hash over the wiring information of the merchant.
+ */
+ struct TALER_MerchantWireHashP h_wire GNUNET_PACKED;
+
+ /**
+ * The coin's public key. This is the value that must have been
+ * signed (blindly) by the Exchange.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_merchant_deposit_sign (
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ struct TALER_MerchantSignatureP *merchant_sig)
+{
+ struct TALER_DepositTrackPS dtp = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION),
+ .purpose.size = htonl (sizeof (dtp)),
+ .h_contract_terms = *h_contract_terms,
+ .h_wire = *h_wire,
+ .coin_pub = *coin_pub
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv,
+ &dtp,
+ &merchant_sig->eddsa_sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_merchant_deposit_verify (
+ const struct TALER_MerchantPublicKeyP *merchant,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_MerchantSignatureP *merchant_sig)
+{
+ struct TALER_DepositTrackPS tps = {
+ .purpose.size = htonl (sizeof (tps)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION),
+ .coin_pub = *coin_pub,
+ .h_contract_terms = *h_contract_terms,
+ .h_wire = *h_wire
+ };
+
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION,
+ &tps,
+ &merchant_sig->eddsa_sig,
+ &merchant->eddsa_pub);
+}
+
+
+/**
+ * @brief Format used to generate the signature on a request to refund
+ * a coin into the account of the customer.
+ */
+struct TALER_RefundRequestPS
+{
+ /**
+ * Purpose must be #TALER_SIGNATURE_MERCHANT_REFUND.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash over the proposal data to identify the contract
+ * which is being refunded.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;
+
+ /**
+ * The coin's public key. This is the value that must have been
+ * signed (blindly) by the Exchange.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Merchant-generated transaction ID for the refund.
+ */
+ uint64_t rtransaction_id GNUNET_PACKED;
+
+ /**
+ * Amount to be refunded, including refund fee charged by the
+ * exchange to the customer.
+ */
+ struct TALER_AmountNBO refund_amount;
+};
+
+
+void
+TALER_merchant_refund_sign (
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint64_t rtransaction_id,
+ const struct TALER_Amount *amount,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ struct TALER_MerchantSignatureP *merchant_sig)
+{
+ struct TALER_RefundRequestPS rr = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
+ .purpose.size = htonl (sizeof (rr)),
+ .h_contract_terms = *h_contract_terms,
+ .coin_pub = *coin_pub,
+ .rtransaction_id = GNUNET_htonll (rtransaction_id)
+ };
+
+ TALER_amount_hton (&rr.refund_amount,
+ amount);
+ GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv,
+ &rr,
+ &merchant_sig->eddsa_sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_merchant_refund_verify (
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint64_t rtransaction_id,
+ const struct TALER_Amount *amount,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig)
+{
+ struct TALER_RefundRequestPS rr = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
+ .purpose.size = htonl (sizeof (rr)),
+ .h_contract_terms = *h_contract_terms,
+ .coin_pub = *coin_pub,
+ .rtransaction_id = GNUNET_htonll (rtransaction_id)
+ };
+
+ TALER_amount_hton (&rr.refund_amount,
+ amount);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
+ &rr,
+ &merchant_sig->eddsa_sig,
+ &merchant_pub->eddsa_pub);
+}
+
+
+/**
+ * @brief Information signed by the exchange's master
+ * key affirming the IBAN details for the exchange.
+ */
+struct TALER_MerchantWireDetailsPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_MERCHANT_WIRE_DETAILS.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Salted hash over the account holder's payto:// URL and
+ * the salt, as done by #TALER_merchant_wire_signature_hash().
+ */
+ struct TALER_MerchantWireHashP h_wire_details GNUNET_PACKED;
+
+};
+
+
+enum GNUNET_GenericReturnValue
+TALER_merchant_wire_signature_check (
+ const char *payto_uri,
+ const struct TALER_WireSaltP *salt,
+ const struct TALER_MerchantPublicKeyP *merch_pub,
+ const struct TALER_MerchantSignatureP *merch_sig)
+{
+ struct TALER_MerchantWireDetailsPS wd = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS),
+ .purpose.size = htonl (sizeof (wd))
+ };
+
+ TALER_merchant_wire_signature_hash (payto_uri,
+ salt,
+ &wd.h_wire_details);
+ return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS,
+ &wd,
+ &merch_sig->eddsa_sig,
+ &merch_pub->eddsa_pub);
+}
+
+
+void
+TALER_merchant_wire_signature_make (
+ const char *payto_uri,
+ const struct TALER_WireSaltP *salt,
+ const struct TALER_MerchantPrivateKeyP *merch_priv,
+ struct TALER_MerchantSignatureP *merch_sig)
+{
+ struct TALER_MerchantWireDetailsPS wd = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS),
+ .purpose.size = htonl (sizeof (wd))
+ };
+
+ TALER_merchant_wire_signature_hash (payto_uri,
+ salt,
+ &wd.h_wire_details);
+ GNUNET_CRYPTO_eddsa_sign (&merch_priv->eddsa_priv,
+ &wd,
+ &merch_sig->eddsa_sig);
+}
+
+
+/**
+ * Used by merchants to return signed responses to /pay requests.
+ * Currently only used to return 200 OK signed responses.
+ */
+struct TALER_PaymentResponsePS
+{
+ /**
+ * Set to #TALER_SIGNATURE_MERCHANT_PAYMENT_OK. Note that
+ * unsuccessful payments are usually proven by some exchange's signature.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash of the proposal data associated with this confirmation
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+};
+
+void
+TALER_merchant_pay_sign (
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantPrivateKeyP *merch_priv,
+ struct TALER_MerchantSignatureP *merch_sig)
+{
+ struct TALER_PaymentResponsePS mr = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK),
+ .purpose.size = htonl (sizeof (mr)),
+ .h_contract_terms = *h_contract_terms
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&merch_priv->eddsa_priv,
+ &mr,
+ &merch_sig->eddsa_sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_merchant_pay_verify (
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig)
+{
+ struct TALER_PaymentResponsePS pr = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK),
+ .purpose.size = htonl (sizeof (pr)),
+ .h_contract_terms = *h_contract_terms
+ };
+
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_PAYMENT_OK,
+ &pr,
+ &merchant_sig->eddsa_sig,
+ &merchant_pub->eddsa_pub);
+}
+
+
+/**
+ * The contract sent by the merchant to the wallet.
+ */
+struct TALER_ProposalDataPS
+{
+ /**
+ * Purpose header for the signature over the proposal data
+ * with purpose #TALER_SIGNATURE_MERCHANT_CONTRACT.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash of the JSON contract in UTF-8 including 0-termination,
+ * using JSON_COMPACT | JSON_SORT_KEYS
+ */
+ struct TALER_PrivateContractHashP hash;
+};
+
+void
+TALER_merchant_contract_sign (
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantPrivateKeyP *merch_priv,
+ struct GNUNET_CRYPTO_EddsaSignature *merch_sig)
+{
+ struct TALER_ProposalDataPS pdps = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT),
+ .purpose.size = htonl (sizeof (pdps)),
+ .hash = *h_contract_terms
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&merch_priv->eddsa_priv,
+ &pdps,
+ merch_sig);
+}
+
+
+// NB: "TALER_merchant_contract_verify" not (yet?) needed / not defined.
+
+/* end of merchant_signatures.c */
diff --git a/src/util/mhd.c b/src/util/mhd.c
index 9684f979f..6f6b38d35 100644
--- a/src/util/mhd.c
+++ b/src/util/mhd.c
@@ -31,7 +31,7 @@
* #GNUNET_NO if the MHD connection is using http,
* #GNUNET_SYSERR if the connection type couldn't be determined
*/
-int
+enum GNUNET_GenericReturnValue
TALER_mhd_is_https (struct MHD_Connection *connection)
{
const union MHD_ConnectionInfo *ci;
@@ -42,11 +42,11 @@ TALER_mhd_is_https (struct MHD_Connection *connection)
if (NULL != forwarded_proto)
{
- if (0 == strcmp (forwarded_proto,
- "https"))
+ if (0 == strcasecmp (forwarded_proto,
+ "https"))
return GNUNET_YES;
- if (0 == strcmp (forwarded_proto,
- "http"))
+ if (0 == strcasecmp (forwarded_proto,
+ "http"))
return GNUNET_NO;
GNUNET_break (0);
return GNUNET_SYSERR;
diff --git a/src/util/offline_signatures.c b/src/util/offline_signatures.c
new file mode 100644
index 000000000..fbff850df
--- /dev/null
+++ b/src/util/offline_signatures.c
@@ -0,0 +1,1388 @@
+/*
+ This file is part of TALER
+ 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
+ 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 offline_signatures.c
+ * @brief Utility functions for Taler exchange offline signatures
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange offline key over the information of
+ * an AML officer status change.
+ */
+struct TALER_MasterAmlOfficerStatusPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_AML_KEY. Signed
+ * by a `struct TALER_MasterPublicKeyP` using EdDSA.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Time of the change.
+ */
+ struct GNUNET_TIME_TimestampNBO change_date;
+
+ /**
+ * Public key of the AML officer.
+ */
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+
+ /**
+ * Hash over the AML officer's name.
+ */
+ struct GNUNET_HashCode h_officer_name GNUNET_PACKED;
+
+ /**
+ * Bitmask: 1 if enabled; 2 for read-only access. in NBO.
+ */
+ uint32_t is_active GNUNET_PACKED;
+};
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_aml_officer_status_sign (
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *officer_name,
+ struct GNUNET_TIME_Timestamp change_date,
+ bool is_active,
+ bool read_only,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterAmlOfficerStatusPS as = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_AML_KEY),
+ .purpose.size = htonl (sizeof (as)),
+ .change_date = GNUNET_TIME_timestamp_hton (change_date),
+ .officer_pub = *officer_pub,
+ .is_active = htonl ((is_active ? 1 : 0) + (read_only ? 2 : 0))
+ };
+
+ GNUNET_CRYPTO_hash (officer_name,
+ strlen (officer_name) + 1,
+ &as.h_officer_name);
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &as,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_aml_officer_status_verify (
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *officer_name,
+ struct GNUNET_TIME_Timestamp change_date,
+ bool is_active,
+ bool read_only,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterAmlOfficerStatusPS as = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_AML_KEY),
+ .purpose.size = htonl (sizeof (as)),
+ .change_date = GNUNET_TIME_timestamp_hton (change_date),
+ .officer_pub = *officer_pub,
+ .is_active = htonl ((is_active ? 1 : 0) + (read_only ? 2 : 0))
+ };
+
+ GNUNET_CRYPTO_hash (officer_name,
+ strlen (officer_name) + 1,
+ &as.h_officer_name);
+ return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_AML_KEY,
+ &as,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange offline key over the information of
+ * an auditor to be added to the exchange's set of auditors.
+ */
+struct TALER_MasterAddAuditorPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_ADD_AUDITOR. Signed
+ * by a `struct TALER_MasterPublicKeyP` using EdDSA.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Time of the change.
+ */
+ struct GNUNET_TIME_TimestampNBO start_date;
+
+ /**
+ * Public key of the auditor.
+ */
+ struct TALER_AuditorPublicKeyP auditor_pub;
+
+ /**
+ * Hash over the auditor's URL.
+ */
+ struct GNUNET_HashCode h_auditor_url GNUNET_PACKED;
+};
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_auditor_add_sign (
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ struct GNUNET_TIME_Timestamp start_date,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterAddAuditorPS kv = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_ADD_AUDITOR),
+ .purpose.size = htonl (sizeof (kv)),
+ .start_date = GNUNET_TIME_timestamp_hton (start_date),
+ .auditor_pub = *auditor_pub,
+ };
+
+ GNUNET_CRYPTO_hash (auditor_url,
+ strlen (auditor_url) + 1,
+ &kv.h_auditor_url);
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &kv,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_auditor_add_verify (
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ struct GNUNET_TIME_Timestamp start_date,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterAddAuditorPS aa = {
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_MASTER_ADD_AUDITOR),
+ .purpose.size = htonl (sizeof (aa)),
+ .start_date = GNUNET_TIME_timestamp_hton (start_date),
+ .auditor_pub = *auditor_pub
+ };
+
+ GNUNET_CRYPTO_hash (auditor_url,
+ strlen (auditor_url) + 1,
+ &aa.h_auditor_url);
+ return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_ADD_AUDITOR,
+ &aa,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange offline key over the information of
+ * an auditor to be removed from the exchange's set of auditors.
+ */
+struct TALER_MasterDelAuditorPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_DEL_AUDITOR. Signed
+ * by a `struct TALER_MasterPublicKeyP` using EdDSA.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Time of the change.
+ */
+ struct GNUNET_TIME_TimestampNBO end_date;
+
+ /**
+ * Public key of the auditor.
+ */
+ struct TALER_AuditorPublicKeyP auditor_pub;
+
+};
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_auditor_del_sign (
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterDelAuditorPS kv = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DEL_AUDITOR),
+ .purpose.size = htonl (sizeof (kv)),
+ .end_date = GNUNET_TIME_timestamp_hton (end_date),
+ .auditor_pub = *auditor_pub,
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &kv,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_auditor_del_verify (
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterDelAuditorPS da = {
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_MASTER_DEL_AUDITOR),
+ .purpose.size = htonl (sizeof (da)),
+ .end_date = GNUNET_TIME_timestamp_hton (end_date),
+ .auditor_pub = *auditor_pub
+ };
+
+ return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DEL_AUDITOR,
+ &da,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Message confirming that a denomination key was revoked.
+ */
+struct TALER_MasterDenominationKeyRevocationPS
+{
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash of the denomination key.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_denomination_revoke_sign (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterDenominationKeyRevocationPS rm = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED),
+ .purpose.size = htonl (sizeof (rm)),
+ .h_denom_pub = *h_denom_pub
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &rm,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_denomination_revoke_verify (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterDenominationKeyRevocationPS kr = {
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED),
+ .purpose.size = htonl (sizeof (kr)),
+ .h_denom_pub = *h_denom_pub
+ };
+
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED,
+ &kr,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Message confirming that an exchange online signing key was revoked.
+ */
+struct TALER_MasterSigningKeyRevocationPS
+{
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_SIGNING_KEY_REVOKED.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * The exchange's public key.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_signkey_revoke_sign (
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterSigningKeyRevocationPS kv = {
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_MASTER_SIGNING_KEY_REVOKED),
+ .purpose.size = htonl (sizeof (kv)),
+ .exchange_pub = *exchange_pub
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &kv,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_signkey_revoke_verify (
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterSigningKeyRevocationPS rm = {
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_MASTER_SIGNING_KEY_REVOKED),
+ .purpose.size = htonl (sizeof (rm)),
+ .exchange_pub = *exchange_pub
+ };
+
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_MASTER_SIGNING_KEY_REVOKED,
+ &rm,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Information about a signing key of the exchange. Signing keys are used
+ * to sign exchange messages other than coins, i.e. to confirm that a
+ * deposit was successful or that a refresh was accepted.
+ */
+struct TALER_ExchangeSigningKeyValidityPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * When does this signing key begin to be valid?
+ */
+ struct GNUNET_TIME_TimestampNBO start;
+
+ /**
+ * When does this signing key expire? Note: This is currently when
+ * the Exchange will definitively stop using it. Signatures made with
+ * the key remain valid until @e end. When checking validity periods,
+ * clients should allow for some overlap between keys and tolerate
+ * the use of either key during the overlap time (due to the
+ * possibility of clock skew).
+ */
+ struct GNUNET_TIME_TimestampNBO expire;
+
+ /**
+ * When do signatures with this signing key become invalid? After
+ * this point, these signatures cannot be used in (legal) disputes
+ * anymore, as the Exchange is then allowed to destroy its side of the
+ * evidence. @e end is expected to be significantly larger than @e
+ * expire (by a year or more).
+ */
+ struct GNUNET_TIME_TimestampNBO end;
+
+ /**
+ * The public online signing key that the exchange will use
+ * between @e start and @e expire.
+ */
+ struct TALER_ExchangePublicKeyP signkey_pub;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_signkey_validity_sign (
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Timestamp end_sign,
+ struct GNUNET_TIME_Timestamp end_legal,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_ExchangeSigningKeyValidityPS skv = {
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY),
+ .purpose.size = htonl (sizeof (skv)),
+ .start = GNUNET_TIME_timestamp_hton (start_sign),
+ .expire = GNUNET_TIME_timestamp_hton (end_sign),
+ .end = GNUNET_TIME_timestamp_hton (end_legal),
+ .signkey_pub = *exchange_pub
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &skv,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_signkey_validity_verify (
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Timestamp end_sign,
+ struct GNUNET_TIME_Timestamp end_legal,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_ExchangeSigningKeyValidityPS skv = {
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY),
+ .purpose.size = htonl (sizeof (skv)),
+ .start = GNUNET_TIME_timestamp_hton (start_sign),
+ .expire = GNUNET_TIME_timestamp_hton (end_sign),
+ .end = GNUNET_TIME_timestamp_hton (end_legal),
+ .signkey_pub = *exchange_pub
+ };
+
+ return
+ GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY,
+ &skv,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Information about a denomination key. Denomination keys
+ * are used to sign coins of a certain value into existence.
+ */
+struct TALER_DenominationKeyValidityPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * The long-term offline master key of the exchange that was
+ * used to create @e signature.
+ *
+ * Note: This member is not strictly required, but here for
+ * backwards-compatibility. If we ever again badly break
+ * compatibility, we might want to remove it.
+ */
+ struct TALER_MasterPublicKeyP master;
+
+ /**
+ * Start time of the validity period for this key.
+ */
+ struct GNUNET_TIME_TimestampNBO start;
+
+ /**
+ * The exchange will sign fresh coins between @e start and this time.
+ * @e expire_withdraw will be somewhat larger than @e start to
+ * ensure a sufficiently large anonymity set, while also allowing
+ * the Exchange to limit the financial damage in case of a key being
+ * compromised. Thus, exchanges with low volume are expected to have a
+ * longer withdraw period (@e expire_withdraw - @e start) than exchanges
+ * with high transaction volume. The period may also differ between
+ * types of coins. A exchange may also have a few denomination keys
+ * with the same value with overlapping validity periods, to address
+ * issues such as clock skew.
+ */
+ struct GNUNET_TIME_TimestampNBO expire_withdraw;
+
+ /**
+ * Coins signed with the denomination key must be spent or refreshed
+ * between @e start and this expiration time. After this time, the
+ * exchange will refuse transactions involving this key as it will
+ * "drop" the table with double-spending information (shortly after)
+ * this time. Note that wallets should refresh coins significantly
+ * before this time to be on the safe side. @e expire_deposit must be
+ * significantly larger than @e expire_withdraw (by months or even
+ * years).
+ */
+ struct GNUNET_TIME_TimestampNBO expire_deposit;
+
+ /**
+ * When do signatures with this denomination key become invalid?
+ * After this point, these signatures cannot be used in (legal)
+ * disputes anymore, as the Exchange is then allowed to destroy its side
+ * of the evidence. @e expire_legal is expected to be significantly
+ * larger than @e expire_deposit (by a year or more).
+ */
+ struct GNUNET_TIME_TimestampNBO expire_legal;
+
+ /**
+ * The value of the coins signed with this denomination key.
+ */
+ struct TALER_AmountNBO value;
+
+ /**
+ * Fees for the coin.
+ */
+ struct TALER_DenomFeeSetNBOP fees;
+
+ /**
+ * Hash code of the denomination public key. (Used to avoid having
+ * the variable-size RSA key in this struct.)
+ */
+ struct TALER_DenominationHashP denom_hash GNUNET_PACKED;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_denom_validity_sign (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct GNUNET_TIME_Timestamp stamp_start,
+ struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+ struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+ struct GNUNET_TIME_Timestamp stamp_expire_legal,
+ const struct TALER_Amount *coin_value,
+ const struct TALER_DenomFeeSet *fees,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_DenominationKeyValidityPS issue = {
+ .purpose.purpose
+ = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY),
+ .purpose.size
+ = htonl (sizeof (issue)),
+ .start = GNUNET_TIME_timestamp_hton (stamp_start),
+ .expire_withdraw = GNUNET_TIME_timestamp_hton (stamp_expire_withdraw),
+ .expire_deposit = GNUNET_TIME_timestamp_hton (stamp_expire_deposit),
+ .expire_legal = GNUNET_TIME_timestamp_hton (stamp_expire_legal),
+ .denom_hash = *h_denom_pub
+ };
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&master_priv->eddsa_priv,
+ &issue.master.eddsa_pub);
+ TALER_amount_hton (&issue.value,
+ coin_value);
+ TALER_denom_fee_set_hton (&issue.fees,
+ fees);
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &issue,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_denom_validity_verify (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct GNUNET_TIME_Timestamp stamp_start,
+ struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+ struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+ struct GNUNET_TIME_Timestamp stamp_expire_legal,
+ const struct TALER_Amount *coin_value,
+ const struct TALER_DenomFeeSet *fees,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_DenominationKeyValidityPS dkv = {
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY),
+ .purpose.size = htonl (sizeof (dkv)),
+ .master = *master_pub,
+ .start = GNUNET_TIME_timestamp_hton (stamp_start),
+ .expire_withdraw = GNUNET_TIME_timestamp_hton (stamp_expire_withdraw),
+ .expire_deposit = GNUNET_TIME_timestamp_hton (stamp_expire_deposit),
+ .expire_legal = GNUNET_TIME_timestamp_hton (stamp_expire_legal),
+ .denom_hash = *h_denom_pub
+ };
+
+ TALER_amount_hton (&dkv.value,
+ coin_value);
+ TALER_denom_fee_set_hton (&dkv.fees,
+ fees);
+ return
+ GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY,
+ &dkv,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange offline key over the information of
+ * a payto:// URI to be added to the exchange's set of active wire accounts.
+ */
+struct TALER_MasterAddWirePS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_ADD_WIRE. Signed
+ * by a `struct TALER_MasterPublicKeyP` using EdDSA.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Time of the change.
+ */
+ struct GNUNET_TIME_TimestampNBO start_date;
+
+ /**
+ * Hash over the exchange's payto URI.
+ */
+ struct TALER_PaytoHashP h_payto GNUNET_PACKED;
+
+ /**
+ * Hash over the conversion URL, all zeros if there
+ * is no conversion URL.
+ */
+ struct GNUNET_HashCode h_conversion_url;
+
+ /**
+ * Hash over the debit restrictions.
+ */
+ struct GNUNET_HashCode h_debit_restrictions;
+
+ /**
+ * Hash over the credit restrictions.
+ */
+ struct GNUNET_HashCode h_credit_restrictions;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_wire_add_sign (
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterAddWirePS kv = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_ADD_WIRE),
+ .purpose.size = htonl (sizeof (kv)),
+ .start_date = GNUNET_TIME_timestamp_hton (now),
+ };
+
+ TALER_payto_hash (payto_uri,
+ &kv.h_payto);
+ if (NULL != conversion_url)
+ GNUNET_CRYPTO_hash (conversion_url,
+ strlen (conversion_url) + 1,
+ &kv.h_conversion_url);
+ TALER_json_hash (debit_restrictions,
+ &kv.h_debit_restrictions);
+ TALER_json_hash (credit_restrictions,
+ &kv.h_credit_restrictions);
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &kv,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_wire_add_verify (
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp sign_time,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterAddWirePS aw = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_ADD_WIRE),
+ .purpose.size = htonl (sizeof (aw)),
+ .start_date = GNUNET_TIME_timestamp_hton (sign_time),
+ };
+
+ TALER_payto_hash (payto_uri,
+ &aw.h_payto);
+ if (NULL != conversion_url)
+ GNUNET_CRYPTO_hash (conversion_url,
+ strlen (conversion_url) + 1,
+ &aw.h_conversion_url);
+ TALER_json_hash (debit_restrictions,
+ &aw.h_debit_restrictions);
+ TALER_json_hash (credit_restrictions,
+ &aw.h_credit_restrictions);
+ return
+ GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_MASTER_ADD_WIRE,
+ &aw,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange offline key over the information of
+ * a wire method to be removed to the exchange's set of active accounts.
+ */
+struct TALER_MasterDelWirePS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_DEL_WIRE. Signed
+ * by a `struct TALER_MasterPublicKeyP` using EdDSA.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Time of the change.
+ */
+ struct GNUNET_TIME_TimestampNBO end_date;
+
+ /**
+ * Hash over the exchange's payto URI.
+ */
+ struct TALER_PaytoHashP h_payto GNUNET_PACKED;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_wire_del_sign (
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterDelWirePS kv = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DEL_WIRE),
+ .purpose.size = htonl (sizeof (kv)),
+ .end_date = GNUNET_TIME_timestamp_hton (now),
+ };
+
+ TALER_payto_hash (payto_uri,
+ &kv.h_payto);
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &kv,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_wire_del_verify (
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp sign_time,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterDelWirePS aw = {
+ .purpose.purpose = htonl (
+ TALER_SIGNATURE_MASTER_DEL_WIRE),
+ .purpose.size = htonl (sizeof (aw)),
+ .end_date = GNUNET_TIME_timestamp_hton (sign_time),
+ };
+
+ TALER_payto_hash (payto_uri,
+ &aw.h_payto);
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_MASTER_DEL_WIRE,
+ &aw,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Information signed by the exchange's master
+ * key stating the wire fee to be paid per wire transfer.
+ */
+struct TALER_MasterWireFeePS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_WIRE_FEES.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash over the wire method (yes, H("x-taler-bank") or H("iban")), in lower
+ * case, including 0-terminator. Used to uniquely identify which
+ * wire method these fees apply to.
+ */
+ struct GNUNET_HashCode h_wire_method;
+
+ /**
+ * Start date when the fee goes into effect.
+ */
+ struct GNUNET_TIME_TimestampNBO start_date;
+
+ /**
+ * End date when the fee stops being in effect (exclusive)
+ */
+ struct GNUNET_TIME_TimestampNBO end_date;
+
+ /**
+ * Fees charged for wire transfers using the
+ * given wire method.
+ */
+ struct TALER_WireFeeSetNBOP fees;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_wire_fee_sign (
+ const char *payment_method,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ const struct TALER_WireFeeSet *fees,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterWireFeePS kv = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_FEES),
+ .purpose.size = htonl (sizeof (kv)),
+ .start_date = GNUNET_TIME_timestamp_hton (start_time),
+ .end_date = GNUNET_TIME_timestamp_hton (end_time),
+ };
+
+ GNUNET_CRYPTO_hash (payment_method,
+ strlen (payment_method) + 1,
+ &kv.h_wire_method);
+ TALER_wire_fee_set_hton (&kv.fees,
+ fees);
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &kv,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_wire_fee_verify (
+ const char *payment_method,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ const struct TALER_WireFeeSet *fees,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterWireFeePS wf = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_FEES),
+ .purpose.size = htonl (sizeof (wf)),
+ .start_date = GNUNET_TIME_timestamp_hton (start_time),
+ .end_date = GNUNET_TIME_timestamp_hton (end_time)
+ };
+
+ GNUNET_CRYPTO_hash (payment_method,
+ strlen (payment_method) + 1,
+ &wf.h_wire_method);
+ TALER_wire_fee_set_hton (&wf.fees,
+ fees);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_FEES,
+ &wf,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Global fees charged by the exchange independent of
+ * denomination or wire method.
+ */
+struct TALER_MasterGlobalFeePS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_GLOBAL_FEES.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Start date when the fee goes into effect.
+ */
+ struct GNUNET_TIME_TimestampNBO start_date;
+
+ /**
+ * End date when the fee stops being in effect (exclusive)
+ */
+ struct GNUNET_TIME_TimestampNBO end_date;
+
+ /**
+ * How long does an exchange keep a purse around after a purse
+ * has expired (or been successfully merged)? A 'GET' request
+ * for a purse will succeed until the purse expiration time
+ * plus this value.
+ */
+ struct GNUNET_TIME_RelativeNBO purse_timeout;
+
+ /**
+ * How long will the exchange preserve the account history? After an
+ * account was deleted/closed, the exchange will retain the account history
+ * for legal reasons until this time.
+ */
+ struct GNUNET_TIME_RelativeNBO history_expiration;
+
+ /**
+ * Fee charged to the merchant per wire transfer.
+ */
+ struct TALER_GlobalFeeSetNBOP fees;
+
+ /**
+ * Number of concurrent purses that any
+ * account holder is allowed to create without having
+ * to pay the @e purse_fee. Here given in NBO.
+ */
+ uint32_t purse_account_limit;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_global_fee_sign (
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterGlobalFeePS wf = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_GLOBAL_FEES),
+ .purpose.size = htonl (sizeof (wf)),
+ .start_date = GNUNET_TIME_timestamp_hton (start_time),
+ .end_date = GNUNET_TIME_timestamp_hton (end_time),
+ .purse_timeout = GNUNET_TIME_relative_hton (purse_timeout),
+ .history_expiration = GNUNET_TIME_relative_hton (history_expiration),
+ .purse_account_limit = htonl (purse_account_limit)
+ };
+
+ TALER_global_fee_set_hton (&wf.fees,
+ fees);
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &wf,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_global_fee_verify (
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterGlobalFeePS wf = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_GLOBAL_FEES),
+ .purpose.size = htonl (sizeof (wf)),
+ .start_date = GNUNET_TIME_timestamp_hton (start_time),
+ .end_date = GNUNET_TIME_timestamp_hton (end_time),
+ .purse_timeout = GNUNET_TIME_relative_hton (purse_timeout),
+ .history_expiration = GNUNET_TIME_relative_hton (history_expiration),
+ .purse_account_limit = htonl (purse_account_limit)
+ };
+
+ TALER_global_fee_set_hton (&wf.fees,
+ fees);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_GLOBAL_FEES,
+ &wf,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange offline key over the manifest of
+ * an extension.
+ */
+struct TALER_MasterExtensionManifestPS
+{
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_EXTENSION. Signed
+ * by a `struct TALER_MasterPublicKeyP` using EdDSA.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash of the JSON object that represents the manifests of extensions.
+ */
+ struct TALER_ExtensionManifestsHashP h_manifest GNUNET_PACKED;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_extension_manifests_hash_sign (
+ const struct TALER_ExtensionManifestsHashP *h_manifest,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterExtensionManifestPS ec = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
+ .purpose.size = htonl (sizeof(ec)),
+ .h_manifest = *h_manifest
+ };
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &ec,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_extension_manifests_hash_verify (
+ const struct TALER_ExtensionManifestsHashP *h_manifest,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig
+ )
+{
+ struct TALER_MasterExtensionManifestPS ec = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
+ .purpose.size = htonl (sizeof(ec)),
+ .h_manifest = *h_manifest
+ };
+
+ return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION,
+ &ec,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Information signed by the exchange's master
+ * key affirming the IBAN details for the exchange.
+ */
+struct TALER_MasterWireDetailsPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_WIRE_DETAILS.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash over the account holder's payto:// URL.
+ */
+ struct TALER_PaytoHashP h_wire_details GNUNET_PACKED;
+
+ /**
+ * Hash over the conversion URL, all zeros if there
+ * is no conversion URL.
+ */
+ struct GNUNET_HashCode h_conversion_url;
+
+ /**
+ * Hash over the debit restrictions.
+ */
+ struct GNUNET_HashCode h_debit_restrictions;
+
+ /**
+ * Hash over the credit restrictions.
+ */
+ struct GNUNET_HashCode h_credit_restrictions;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_wire_signature_check (
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterWireDetailsPS wd = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_DETAILS),
+ .purpose.size = htonl (sizeof (wd))
+ };
+
+ TALER_payto_hash (payto_uri,
+ &wd.h_wire_details);
+ if (NULL != conversion_url)
+ GNUNET_CRYPTO_hash (conversion_url,
+ strlen (conversion_url) + 1,
+ &wd.h_conversion_url);
+ TALER_json_hash (debit_restrictions,
+ &wd.h_debit_restrictions);
+ TALER_json_hash (credit_restrictions,
+ &wd.h_credit_restrictions);
+ return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_DETAILS,
+ &wd,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+void
+TALER_exchange_wire_signature_make (
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_MasterWireDetailsPS wd = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_DETAILS),
+ .purpose.size = htonl (sizeof (wd))
+ };
+
+ TALER_payto_hash (payto_uri,
+ &wd.h_wire_details);
+ if (NULL != conversion_url)
+ GNUNET_CRYPTO_hash (conversion_url,
+ strlen (conversion_url) + 1,
+ &wd.h_conversion_url);
+ TALER_json_hash (debit_restrictions,
+ &wd.h_debit_restrictions);
+ TALER_json_hash (credit_restrictions,
+ &wd.h_credit_restrictions);
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &wd,
+ &master_sig->eddsa_signature);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by account to merge a purse into a reserve.
+ */
+struct TALER_PartnerConfigurationPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_PARNTER_DETAILS
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct TALER_MasterPublicKeyP partner_pub;
+ struct GNUNET_TIME_TimestampNBO start_date;
+ struct GNUNET_TIME_TimestampNBO end_date;
+ struct GNUNET_TIME_RelativeNBO wad_frequency;
+ struct TALER_AmountNBO wad_fee;
+ struct GNUNET_HashCode h_url;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_partner_details_sign (
+ const struct TALER_MasterPublicKeyP *partner_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ struct GNUNET_TIME_Relative wad_frequency,
+ const struct TALER_Amount *wad_fee,
+ const char *partner_base_url,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_PartnerConfigurationPS wd = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_PARTNER_DETAILS),
+ .purpose.size = htonl (sizeof (wd)),
+ .partner_pub = *partner_pub,
+ .start_date = GNUNET_TIME_timestamp_hton (start_date),
+ .end_date = GNUNET_TIME_timestamp_hton (end_date),
+ .wad_frequency = GNUNET_TIME_relative_hton (wad_frequency),
+ };
+
+ GNUNET_CRYPTO_hash (partner_base_url,
+ strlen (partner_base_url) + 1,
+ &wd.h_url);
+ TALER_amount_hton (&wd.wad_fee,
+ wad_fee);
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &wd,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_partner_details_verify (
+ const struct TALER_MasterPublicKeyP *partner_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ struct GNUNET_TIME_Relative wad_frequency,
+ const struct TALER_Amount *wad_fee,
+ const char *partner_base_url,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_PartnerConfigurationPS wd = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_PARTNER_DETAILS),
+ .purpose.size = htonl (sizeof (wd)),
+ .partner_pub = *partner_pub,
+ .start_date = GNUNET_TIME_timestamp_hton (start_date),
+ .end_date = GNUNET_TIME_timestamp_hton (end_date),
+ .wad_frequency = GNUNET_TIME_relative_hton (wad_frequency),
+ };
+
+ GNUNET_CRYPTO_hash (partner_base_url,
+ strlen (partner_base_url) + 1,
+ &wd.h_url);
+ TALER_amount_hton (&wd.wad_fee,
+ wad_fee);
+ return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_PARTNER_DETAILS,
+ &wd,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by account to drain profits
+ * from the escrow account of the exchange.
+ */
+struct TALER_DrainProfitPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_MASTER_DRAIN_PROFITS
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct GNUNET_TIME_TimestampNBO date;
+ struct TALER_AmountNBO amount;
+ struct GNUNET_HashCode h_section;
+ struct TALER_PaytoHashP h_payto;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_profit_drain_sign (
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_TIME_Timestamp date,
+ const struct TALER_Amount *amount,
+ const char *account_section,
+ const char *payto_uri,
+ const struct TALER_MasterPrivateKeyP *master_priv,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_DrainProfitPS wd = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DRAIN_PROFIT),
+ .purpose.size = htonl (sizeof (wd)),
+ .wtid = *wtid,
+ .date = GNUNET_TIME_timestamp_hton (date),
+ };
+
+ GNUNET_CRYPTO_hash (account_section,
+ strlen (account_section) + 1,
+ &wd.h_section);
+ TALER_payto_hash (payto_uri,
+ &wd.h_payto);
+ TALER_amount_hton (&wd.amount,
+ amount);
+ GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+ &wd,
+ &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_profit_drain_verify (
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_TIME_Timestamp date,
+ const struct TALER_Amount *amount,
+ const char *account_section,
+ const char *payto_uri,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_DrainProfitPS wd = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DRAIN_PROFIT),
+ .purpose.size = htonl (sizeof (wd)),
+ .wtid = *wtid,
+ .date = GNUNET_TIME_timestamp_hton (date),
+ };
+
+ GNUNET_CRYPTO_hash (account_section,
+ strlen (account_section) + 1,
+ &wd.h_section);
+ TALER_payto_hash (payto_uri,
+ &wd.h_payto);
+ TALER_amount_hton (&wd.amount,
+ amount);
+ return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DRAIN_PROFIT,
+ &wd,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub);
+}
+
+
+/* end of offline_signatures.c */
diff --git a/src/util/os_installation.c b/src/util/os_installation.c
index beea5d701..1cbb9e78a 100644
--- a/src/util/os_installation.c
+++ b/src/util/os_installation.c
@@ -40,7 +40,7 @@ static const struct GNUNET_OS_ProjectData taler_pd = {
.homepage = "http://www.gnu.org/s/taler/",
.config_file = "taler.conf",
.user_config_file = "~/.config/taler.conf",
- .version = PACKAGE_VERSION,
+ .version = PACKAGE_VERSION "-" VCS_VERSION,
.is_gnu = 1,
.gettext_domain = "taler",
.gettext_path = NULL,
diff --git a/src/util/paths.conf b/src/util/paths.conf
index c1d2194d8..f34ccb41e 100644
--- a/src/util/paths.conf
+++ b/src/util/paths.conf
@@ -17,13 +17,13 @@ TALER_HOME = ${TALER_TEST_HOME:-${HOME:-${USERPROFILE}}}
# for how these should be used.
# Persistent data storage
-TALER_DATA_HOME = ${XDG_DATA_HOME:-$TALER_HOME/.local/share}/taler/
+TALER_DATA_HOME = ${XDG_DATA_HOME:-${TALER_HOME}/.local/share}/taler/
# Configuration files
-TALER_CONFIG_HOME = ${XDG_CONFIG_HOME:-$TALER_HOME/.config}/taler/
+TALER_CONFIG_HOME = ${XDG_CONFIG_HOME:-${TALER_HOME}/.config}/taler/
# Cached data, no big deal if lost
-TALER_CACHE_HOME = ${XDG_CACHE_HOME:-$TALER_HOME/.cache}/taler/
+TALER_CACHE_HOME = ${XDG_CACHE_HOME:-${TALER_HOME}/.cache}/taler/
# Runtime data (always lost on system boot)
TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/taler-system-runtime/
diff --git a/src/util/payto.c b/src/util/payto.c
index 3540052c1..6092b73fd 100644
--- a/src/util/payto.c
+++ b/src/util/payto.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2019-2020 Taler Systems SA
+ Copyright (C) 2019-2024 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
@@ -29,13 +29,55 @@
/**
- * Obtain the payment method from a @a payto_uri. The
- * format of a payto URI is 'payto://$METHOD/$SOMETHING'.
- * We return $METHOD.
+ * Extract the value under @a key from the URI parameters.
*
* @param payto_uri the URL to parse
- * @return NULL on error (malformed @a payto_uri)
+ * @param search_key key to look for, including "="
+ * @return NULL if the @a key parameter is not found.
+ * The caller should free the returned value.
*/
+static char *
+payto_get_key (const char *payto_uri,
+ const char *search_key)
+{
+ const char *key;
+ const char *value_start;
+ const char *value_end;
+
+ key = strchr (payto_uri,
+ (unsigned char) '?');
+ if (NULL == key)
+ return NULL;
+
+ do {
+ if (0 == strncasecmp (++key,
+ search_key,
+ strlen (search_key)))
+ {
+ value_start = strchr (key,
+ (unsigned char) '=');
+ if (NULL == value_start)
+ return NULL;
+ value_end = strchrnul (value_start,
+ (unsigned char) '&');
+
+ return GNUNET_strndup (value_start + 1,
+ value_end - value_start - 1);
+ }
+ } while ( (key = strchr (key,
+ (unsigned char) '&')) );
+ return NULL;
+}
+
+
+char *
+TALER_payto_get_subject (const char *payto_uri)
+{
+ return payto_get_key (payto_uri,
+ "subject=");
+}
+
+
char *
TALER_payto_get_method (const char *payto_uri)
{
@@ -56,35 +98,598 @@ TALER_payto_get_method (const char *payto_uri)
}
-/**
- * Obtain the account name from a payto URL. The format
- * of the @a payto URL is 'payto://x-taler-bank/$HOSTNAME/$ACCOUNT[?PARAMS]'.
- * We check the first part matches, skip over the $HOSTNAME
- * and return the $ACCOUNT portion.
- *
- * @param payto an x-taler-bank payto URL
- * @return only the account name from the @a payto URL, NULL if not an x-taler-bank
- * payto URL
- */
char *
TALER_xtalerbank_account_from_payto (const char *payto)
{
+ const char *host;
const char *beg;
+ const char *nxt;
const char *end;
if (0 != strncasecmp (payto,
PAYTO "x-taler-bank/",
strlen (PAYTO "x-taler-bank/")))
+ {
+ GNUNET_break_op (0);
return NULL;
- beg = strchr (&payto[strlen (PAYTO "x-taler-bank/")],
+ }
+ host = &payto[strlen (PAYTO "x-taler-bank/")];
+ beg = strchr (host,
'/');
if (NULL == beg)
+ {
+ GNUNET_break_op (0);
return NULL;
- beg++; /* now points to $ACCOUNT */
+ }
+ beg++; /* now points to $ACCOUNT or $PATH */
+ nxt = strchr (beg,
+ '/');
end = strchr (beg,
'?');
if (NULL == end)
- return GNUNET_strdup (beg); /* optional part is missing */
+ end = &beg[strlen (beg)];
+ while ( (NULL != nxt) &&
+ (end - nxt > 0) )
+ {
+ beg = nxt + 1;
+ nxt = strchr (beg,
+ '/');
+ }
return GNUNET_strndup (beg,
end - beg);
}
+
+
+/**
+ * Validate payto://iban/ account URL (only account information,
+ * wire subject and amount are ignored).
+ *
+ * @param account_url payto URL to parse
+ * @return NULL on success, otherwise an error message
+ * to be freed by the caller
+ */
+static char *
+validate_payto_iban (const char *account_url)
+{
+ const char *iban;
+ const char *q;
+ char *result;
+ char *err;
+
+#define IBAN_PREFIX "payto://iban/"
+ if (0 != strncasecmp (account_url,
+ IBAN_PREFIX,
+ strlen (IBAN_PREFIX)))
+ return NULL; /* not an IBAN */
+ iban = strrchr (account_url, '/') + 1;
+#undef IBAN_PREFIX
+ q = strchr (iban,
+ '?');
+ if (NULL != q)
+ {
+ result = GNUNET_strndup (iban,
+ q - iban);
+ }
+ else
+ {
+ result = GNUNET_strdup (iban);
+ }
+ if (NULL !=
+ (err = TALER_iban_validate (result)))
+ {
+ GNUNET_free (result);
+ return err;
+ }
+ GNUNET_free (result);
+ {
+ char *target;
+
+ target = payto_get_key (account_url,
+ "receiver-name=");
+ if (NULL == target)
+ return GNUNET_strdup ("'receiver-name' parameter missing");
+ GNUNET_free (target);
+ }
+ return NULL;
+}
+
+
+/**
+ * Validate payto://x-taler-bank/ account URL (only account information,
+ * wire subject and amount are ignored).
+ *
+ * @param account_url payto URL to parse
+ * @return NULL on success, otherwise an error message
+ * to be freed by the caller
+ */
+static char *
+validate_payto_xtalerbank (const char *account_url)
+{
+ const char *user;
+ const char *nxt;
+ const char *beg;
+ const char *end;
+ const char *host;
+ bool dot_ok;
+ bool post_colon;
+ bool port_ok;
+
+#define XTALERBANK_PREFIX PAYTO "x-taler-bank/"
+ if (0 != strncasecmp (account_url,
+ XTALERBANK_PREFIX,
+ strlen (XTALERBANK_PREFIX)))
+ return NULL; /* not an IBAN */
+ host = &account_url[strlen (XTALERBANK_PREFIX)];
+#undef XTALERBANK_PREFIX
+ beg = strchr (host,
+ '/');
+ if (NULL == beg)
+ {
+ return GNUNET_strdup ("account name missing");
+ }
+ beg++; /* now points to $ACCOUNT or $PATH */
+ nxt = strchr (beg,
+ '/');
+ end = strchr (beg,
+ '?');
+ if (NULL == end)
+ {
+ return GNUNET_strdup ("'receiver-name' parameter missing");
+ }
+ while ( (NULL != nxt) &&
+ (end - nxt > 0) )
+ {
+ beg = nxt + 1;
+ nxt = strchr (beg,
+ '/');
+ }
+ user = beg;
+ if (user == host + 1)
+ {
+ return GNUNET_strdup ("domain name missing");
+ }
+ if ('-' == host[0])
+ return GNUNET_strdup ("invalid character '-' at start of domain name");
+ dot_ok = false;
+ post_colon = false;
+ port_ok = false;
+ while (host != user)
+ {
+ char c = host[0];
+
+ if ('/' == c)
+ {
+ /* path started, do not care about characters
+ in the path */
+ break;
+ }
+ if (':' == c)
+ {
+ post_colon = true;
+ host++;
+ continue;
+ }
+ if (post_colon)
+ {
+ if (! ( ('0' <= c) && ('9' >= c) ) )
+ {
+ char *err;
+
+ GNUNET_asprintf (&err,
+ "invalid character '%c' in port",
+ c);
+ return err;
+ }
+ port_ok = true;
+ }
+ else
+ {
+ if ('.' == c)
+ {
+ if (! dot_ok)
+ return GNUNET_strdup ("invalid domain name (misplaced '.')");
+ dot_ok = false;
+ }
+ else
+ {
+ if (! ( ('-' == c) ||
+ ( ('0' <= c) && ('9' >= c) ) ||
+ ( ('a' <= c) && ('z' >= c) ) ||
+ ( ('A' <= c) && ('Z' >= c) ) ) )
+ {
+ char *err;
+
+ GNUNET_asprintf (&err,
+ "invalid character '%c' in domain name",
+ c);
+ return err;
+ }
+ dot_ok = true;
+ }
+ }
+ host++;
+ }
+ if (post_colon && (! port_ok) )
+ {
+ return GNUNET_strdup ("port missing after ':'");
+ }
+ {
+ char *target;
+
+ target = payto_get_key (account_url,
+ "receiver-name=");
+ if (NULL == target)
+ return GNUNET_strdup ("'receiver-name' parameter missing");
+ GNUNET_free (target);
+ }
+ return NULL;
+}
+
+
+char *
+TALER_payto_validate (const char *payto_uri)
+{
+ char *ret;
+ const char *start;
+ const char *end;
+
+ if (0 != strncasecmp (payto_uri,
+ PAYTO,
+ strlen (PAYTO)))
+ return GNUNET_strdup ("invalid prefix");
+ for (unsigned int i = 0; '\0' != payto_uri[i]; i++)
+ {
+ /* This is more strict than RFC 8905, alas we do not need to support messages/instructions/etc.,
+ and it is generally better to start with a narrow whitelist; we can be more permissive later ...*/
+#define ALLOWED_CHARACTERS \
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:&?-.,=+%~"
+ if (NULL == strchr (ALLOWED_CHARACTERS,
+ (int) payto_uri[i]))
+ {
+ char *ret;
+
+ GNUNET_asprintf (&ret,
+ "Encountered invalid character `%c' at offset %u in payto URI `%s'",
+ payto_uri[i],
+ i,
+ payto_uri);
+ return ret;
+ }
+#undef ALLOWED_CHARACTERS
+ }
+
+ start = &payto_uri[strlen (PAYTO)];
+ end = strchr (start,
+ (unsigned char) '/');
+ if (NULL == end)
+ return GNUNET_strdup ("missing '/' in payload");
+
+ if (NULL != (ret = validate_payto_iban (payto_uri)))
+ return ret; /* got a definitive answer */
+ if (NULL != (ret = validate_payto_xtalerbank (payto_uri)))
+ return ret; /* got a definitive answer */
+
+ /* Insert other bank account validation methods here later! */
+
+ return NULL;
+}
+
+
+char *
+TALER_payto_get_receiver_name (const char *payto)
+{
+ char *err;
+
+ err = TALER_payto_validate (payto);
+ if (NULL != err)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid payto://-URI `%s': %s\n",
+ payto,
+ err);
+ GNUNET_free (err);
+ return NULL;
+ }
+ return payto_get_key (payto,
+ "receiver-name=");
+}
+
+
+/**
+ * Normalize "payto://x-taler-bank/$HOSTNAME/[$PATH/]$USERNAME"
+ * URI in @a input.
+ *
+ * Converts to lower-case, except for [$PATH/]$USERNAME which
+ * is case-sensitive.
+ *
+ * @param len number of bytes in @a input
+ * @param input input URL
+ * @return NULL on error, otherwise 0-terminated canonicalized URI.
+ */
+static char *
+normalize_payto_x_taler_bank (size_t len,
+ const char input[static len])
+{
+ char *res = GNUNET_malloc (len + 1);
+ unsigned int sc = 0;
+
+ for (unsigned int i = 0; i<len; i++)
+ {
+ char c = input[i];
+
+ if ('/' == c)
+ sc++;
+ if (sc < 4)
+ res[i] = (char) tolower ((int) c);
+ else
+ res[i] = c;
+ }
+ return res;
+}
+
+
+/**
+ * Normalize "payto://iban[/$BIC]/$IBAN"
+ * URI in @a input.
+ *
+ * Removes $BIC (if present) and converts $IBAN to upper-case and prefix to
+ * lower-case.
+ *
+ * @param len number of bytes in @a input
+ * @param input input URL
+ * @return NULL on error, otherwise 0-terminated canonicalized URI.
+ */
+static char *
+normalize_payto_iban (size_t len,
+ const char input[static len])
+{
+ char *res;
+ size_t pos = 0;
+ unsigned int sc = 0;
+ bool have_bic;
+
+ for (unsigned int i = 0; i<len; i++)
+ if ('/' == input[i])
+ sc++;
+ if ( (sc > 4) ||
+ (sc < 3) )
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ have_bic = (4 == sc);
+ res = GNUNET_malloc (len + 1);
+ sc = 0;
+ for (unsigned int i = 0; i<len; i++)
+ {
+ char c = input[i];
+
+ if ('/' == c)
+ sc++;
+ switch (sc)
+ {
+ case 0: /* payto: */
+ case 1: /* / */
+ case 2: /* /iban */
+ res[pos++] = (char) tolower ((int) c);
+ break;
+ case 3: /* /$BIC or /$IBAN */
+ if (have_bic)
+ continue;
+ res[pos++] = (char) toupper ((int) c);
+ break;
+ case 4: /* /$IBAN */
+ res[pos++] = (char) toupper ((int) c);
+ break;
+ }
+ }
+ GNUNET_assert (pos <= len);
+ return res;
+}
+
+
+/**
+ * Normalize "payto://upi/$EMAIL"
+ * URI in @a input.
+ *
+ * Converts to lower-case.
+ *
+ * @param len number of bytes in @a input
+ * @param input input URL
+ * @return NULL on error, otherwise 0-terminated canonicalized URI.
+ */
+static char *
+normalize_payto_upi (size_t len,
+ const char input[static len])
+{
+ char *res = GNUNET_malloc (len + 1);
+
+ for (unsigned int i = 0; i<len; i++)
+ {
+ char c = input[i];
+
+ res[i] = (char) tolower ((int) c);
+ }
+ return res;
+}
+
+
+/**
+ * Normalize "payto://bitcoin/$ADDRESS"
+ * URI in @a input.
+ *
+ * Converts to lower-case, except for $ADDRESS which
+ * is case-sensitive.
+ *
+ * @param len number of bytes in @a input
+ * @param input input URL
+ * @return NULL on error, otherwise 0-terminated canonicalized URI.
+ */
+static char *
+normalize_payto_bitcoin (size_t len,
+ const char input[static len])
+{
+ char *res = GNUNET_malloc (len + 1);
+ unsigned int sc = 0;
+
+ for (unsigned int i = 0; i<len; i++)
+ {
+ char c = input[i];
+
+ if ('/' == c)
+ sc++;
+ if (sc < 3)
+ res[i] = (char) tolower ((int) c);
+ else
+ res[i] = c;
+ }
+ return res;
+}
+
+
+/**
+ * Normalize "payto://ilp/$NAME"
+ * URI in @a input.
+ *
+ * Converts to lower-case.
+ *
+ * @param len number of bytes in @a input
+ * @param input input URL
+ * @return NULL on error, otherwise 0-terminated canonicalized URI.
+ */
+static char *
+normalize_payto_ilp (size_t len,
+ const char input[static len])
+{
+ char *res = GNUNET_malloc (len + 1);
+
+ for (unsigned int i = 0; i<len; i++)
+ {
+ char c = input[i];
+
+ res[i] = (char) tolower ((int) c);
+ }
+ return res;
+}
+
+
+char *
+TALER_payto_normalize (const char *input)
+{
+ char *method;
+ const char *end;
+ char *ret;
+
+ {
+ char *err;
+
+ err = TALER_payto_validate (input);
+ if (NULL != err)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Malformed payto://-URI `%s': %s\n",
+ input,
+ err);
+ GNUNET_free (err);
+ return NULL;
+ }
+ }
+ method = TALER_payto_get_method (input);
+ if (NULL == method)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ end = strchr (input, '?');
+ if (NULL == end)
+ end = &input[strlen (input)];
+ if (0 == strcasecmp (method,
+ "x-taler-bank"))
+ ret = normalize_payto_x_taler_bank (end - input,
+ input);
+ else if (0 == strcasecmp (method,
+ "iban"))
+ ret = normalize_payto_iban (end - input,
+ input);
+ else if (0 == strcasecmp (method,
+ "upi"))
+ ret = normalize_payto_upi (end - input,
+ input);
+ else if (0 == strcasecmp (method,
+ "bitcoin"))
+ ret = normalize_payto_bitcoin (end - input,
+ input);
+ else if (0 == strcasecmp (method,
+ "ilp"))
+ ret = normalize_payto_ilp (end - input,
+ input);
+ else
+ ret = GNUNET_strndup (input,
+ end - input);
+ GNUNET_free (method);
+ return ret;
+}
+
+
+void
+TALER_payto_hash (const char *payto,
+ struct TALER_PaytoHashP *h_payto)
+{
+ struct GNUNET_HashCode sha512;
+
+ GNUNET_CRYPTO_hash (payto,
+ strlen (payto) + 1,
+ &sha512);
+ GNUNET_static_assert (sizeof (sha512) > sizeof (*h_payto));
+ /* truncate */
+ GNUNET_memcpy (h_payto,
+ &sha512,
+ sizeof (*h_payto));
+}
+
+
+char *
+TALER_reserve_make_payto (const char *exchange_url,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+ char pub_str[sizeof (*reserve_pub) * 2];
+ char *end;
+ bool is_http;
+ char *reserve_url;
+
+ end = GNUNET_STRINGS_data_to_string (
+ reserve_pub,
+ sizeof (*reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ if (0 == strncmp (exchange_url,
+ "http://",
+ strlen ("http://")))
+ {
+ is_http = true;
+ exchange_url = &exchange_url[strlen ("http://")];
+ }
+ else if (0 == strncmp (exchange_url,
+ "https://",
+ strlen ("https://")))
+ {
+ is_http = false;
+ exchange_url = &exchange_url[strlen ("https://")];
+ }
+ else
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ /* exchange_url includes trailing '/' */
+ GNUNET_asprintf (&reserve_url,
+ "payto://%s/%s%s",
+ is_http ? "taler-reserve-http" : "taler-reserve",
+ exchange_url,
+ pub_str);
+ return reserve_url;
+}
+
+
+/* end of payto.c */
diff --git a/src/util/secmod_common.c b/src/util/secmod_common.c
new file mode 100644
index 000000000..87ce17e06
--- /dev/null
+++ b/src/util/secmod_common.c
@@ -0,0 +1,586 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 util/secmod_common.c
+ * @brief Common functions for the exchange security modules
+ * @author Florian Dold <dold@taler.net>
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "secmod_common.h"
+#include <poll.h>
+#ifdef __linux__
+#include <sys/eventfd.h>
+#endif
+
+
+/**
+ * Head of DLL of clients connected to us.
+ */
+struct TES_Client *TES_clients_head;
+
+/**
+ * Tail of DLL of clients connected to us.
+ */
+struct TES_Client *TES_clients_tail;
+
+/**
+ * Lock for the client queue.
+ */
+pthread_mutex_t TES_clients_lock;
+
+/**
+ * Private key of this security module. Used to sign denomination key
+ * announcements.
+ */
+struct TALER_SecurityModulePrivateKeyP TES_smpriv;
+
+/**
+ * Public key of this security module.
+ */
+struct TALER_SecurityModulePublicKeyP TES_smpub;
+
+/**
+ * Our listen socket.
+ */
+static struct GNUNET_NETWORK_Handle *unix_sock;
+
+/**
+ * Path where we are listening.
+ */
+static char *unixpath;
+
+/**
+ * Task run to accept new inbound connections.
+ */
+static struct GNUNET_SCHEDULER_Task *listen_task;
+
+/**
+ * Set once we are in shutdown and workers should terminate.
+ */
+static volatile bool in_shutdown;
+
+
+enum GNUNET_GenericReturnValue
+TES_transmit_raw (int sock,
+ size_t end,
+ const void *pos)
+{
+ size_t off = 0;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sending message of length %u\n",
+ (unsigned int) end);
+ while (off < end)
+ {
+ ssize_t ret = send (sock,
+ pos,
+ end - off,
+ 0 /* no flags => blocking! */);
+
+ if ( (-1 == ret) &&
+ ( (EAGAIN == errno) ||
+ (EINTR == errno) ) )
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_DEBUG,
+ "send");
+ continue;
+ }
+ if (-1 == ret)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "send");
+ return GNUNET_SYSERR;
+ }
+ if (0 == ret)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ off += ret;
+ pos += ret;
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TES_transmit (int sock,
+ const struct GNUNET_MessageHeader *hdr)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sending message of type %u and length %u\n",
+ (unsigned int) ntohs (hdr->type),
+ (unsigned int) ntohs (hdr->size));
+ return TES_transmit_raw (sock,
+ ntohs (hdr->size),
+ hdr);
+}
+
+
+struct GNUNET_NETWORK_Handle *
+TES_open_socket (const char *unixpath)
+{
+ int sock;
+ mode_t old_umask;
+ struct GNUNET_NETWORK_Handle *ret = NULL;
+
+ /* Change permissions so that group read/writes are allowed.
+ * We need this for multi-user exchange deployment with privilege
+ * separation, where taler-exchange-httpd is part of a group
+ * that allows it to talk to secmod.
+ */
+ old_umask = umask (S_IROTH | S_IWOTH | S_IXOTH);
+
+ sock = socket (PF_UNIX,
+ SOCK_STREAM,
+ 0);
+ if (-1 == sock)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "socket");
+ goto cleanup;
+ }
+ {
+ struct sockaddr_un un;
+
+ if (GNUNET_OK !=
+ GNUNET_DISK_directory_create_for_file (unixpath))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "mkdir(dirname)",
+ unixpath);
+ }
+ if (0 != unlink (unixpath))
+ {
+ if (ENOENT != errno)
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "unlink",
+ unixpath);
+ }
+ memset (&un,
+ 0,
+ sizeof (un));
+ un.sun_family = AF_UNIX;
+ strncpy (un.sun_path,
+ unixpath,
+ sizeof (un.sun_path) - 1);
+ if (0 != bind (sock,
+ (const struct sockaddr *) &un,
+ sizeof (un)))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "bind",
+ unixpath);
+ GNUNET_break (0 == close (sock));
+ goto cleanup;
+ }
+ ret = GNUNET_NETWORK_socket_box_native (sock);
+ if (GNUNET_OK !=
+ GNUNET_NETWORK_socket_listen (ret,
+ 512))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "listen",
+ unixpath);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_NETWORK_socket_close (ret));
+ ret = NULL;
+ }
+ }
+cleanup:
+ (void) umask (old_umask);
+ return ret;
+}
+
+
+void
+TES_wake_clients (void)
+{
+ uint64_t num = 1;
+
+ GNUNET_assert (0 == pthread_mutex_lock (&TES_clients_lock));
+ for (struct TES_Client *client = TES_clients_head;
+ NULL != client;
+ client = client->next)
+ {
+ GNUNET_assert (sizeof (num) ==
+#ifdef __linux__
+ write (client->esock,
+#else
+ write (client->esock_in,
+#endif
+ &num,
+ sizeof (num)));
+ }
+ GNUNET_assert (0 == pthread_mutex_unlock (&TES_clients_lock));
+}
+
+
+enum GNUNET_GenericReturnValue
+TES_read_work (void *cls,
+ TES_MessageDispatch dispatch)
+{
+ struct TES_Client *client = cls;
+ char *buf = client->iobuf;
+ size_t off = 0;
+ uint16_t msize = 0;
+ const struct GNUNET_MessageHeader *hdr = NULL;
+ enum GNUNET_GenericReturnValue ret;
+
+ do
+ {
+ ssize_t recv_size;
+
+ recv_size = recv (client->csock,
+ &buf[off],
+ sizeof (client->iobuf) - off,
+ 0);
+ if (-1 == recv_size)
+ {
+ if ( (0 == off) &&
+ (EAGAIN == errno) )
+ return GNUNET_NO;
+ if ( (EINTR == errno) ||
+ (EAGAIN == errno) )
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_DEBUG,
+ "recv");
+ continue;
+ }
+ if (ECONNRESET != errno)
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "recv");
+ return GNUNET_SYSERR;
+ }
+ if (0 == recv_size)
+ {
+ /* regular disconnect? */
+ GNUNET_break_op (0 == off);
+ return GNUNET_SYSERR;
+ }
+ off += recv_size;
+more:
+ if (off < sizeof (struct GNUNET_MessageHeader))
+ continue;
+ hdr = (const struct GNUNET_MessageHeader *) buf;
+ msize = ntohs (hdr->size);
+#if 0
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received message of type %u with %u bytes\n",
+ (unsigned int) ntohs (hdr->type),
+ (unsigned int) msize);
+#endif
+ if (msize < sizeof (struct GNUNET_MessageHeader))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ } while (off < msize);
+
+ ret = dispatch (client,
+ hdr);
+ if ( (GNUNET_OK != ret) ||
+ (off == msize) )
+ return ret;
+ memmove (buf,
+ &buf[msize],
+ off - msize);
+ off -= msize;
+ goto more;
+}
+
+
+bool
+TES_await_ready (struct TES_Client *client)
+{
+ /* wait for reply with 1s timeout */
+ struct pollfd pfds[] = {
+ {
+ .fd = client->csock,
+ .events = POLLIN
+ },
+ {
+#ifdef __linux__
+ .fd = client->esock,
+#else
+ .fd = client->esock_out,
+#endif
+ .events = POLLIN
+ },
+ };
+ int ret;
+
+ ret = poll (pfds,
+ 2,
+ -1);
+ if ( (-1 == ret) &&
+ (EINTR != errno) )
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "poll");
+ for (int i = 0; i<2; i++)
+ {
+#ifdef __linux__
+ if ( (pfds[i].fd == client->esock) &&
+#else
+ if ( (pfds[i].fd == client->esock_out) &&
+#endif
+ (POLLIN == pfds[i].revents) )
+ {
+ uint64_t num;
+
+ GNUNET_assert (sizeof (num) ==
+#ifdef __linux__
+ read (client->esock,
+#else
+ read (client->esock_out,
+#endif
+ &num,
+ sizeof (num)));
+ return true;
+ }
+ }
+ return false;
+}
+
+
+void
+TES_free_client (struct TES_Client *client)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&TES_clients_lock));
+ GNUNET_CONTAINER_DLL_remove (TES_clients_head,
+ TES_clients_tail,
+ client);
+ GNUNET_assert (0 == pthread_mutex_unlock (&TES_clients_lock));
+ GNUNET_break (0 == close (client->csock));
+#ifdef __linux__
+ GNUNET_break (0 == close (client->esock));
+#else
+ GNUNET_break (0 == close (client->esock_in));
+ GNUNET_break (0 == close (client->esock_out));
+#endif
+ pthread_detach (client->worker);
+ GNUNET_free (client);
+}
+
+
+/**
+ * Main function of a worker thread that signs.
+ *
+ * @param cls the client we are working on
+ * @return NULL
+ */
+static void *
+sign_worker (void *cls)
+{
+ struct TES_Client *client = cls;
+
+ if (GNUNET_OK !=
+ client->cb.init (client))
+ {
+ GNUNET_break (0);
+ TES_free_client (client);
+ return NULL;
+ }
+ while (! in_shutdown)
+ {
+ if (TES_await_ready (client))
+ {
+ if (GNUNET_OK !=
+ client->cb.updater (client))
+ break;
+ }
+ if (GNUNET_SYSERR ==
+ TES_read_work (client,
+ client->cb.dispatch))
+ break;
+ }
+ TES_free_client (client);
+ return NULL;
+}
+
+
+/**
+ * Task that listens for incoming clients.
+ *
+ * @param cls a `struct TES_Callbacks`
+ */
+static void
+listen_job (void *cls)
+{
+ const struct TES_Callbacks *cb = cls;
+ int s;
+#ifdef __linux__
+ int e;
+#else
+ int e[2];
+#endif
+ struct sockaddr_storage sa;
+ socklen_t sa_len = sizeof (sa);
+
+ listen_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
+ unix_sock,
+ &listen_job,
+ cls);
+ s = accept (GNUNET_NETWORK_get_fd (unix_sock),
+ (struct sockaddr *) &sa,
+ &sa_len);
+ if (-1 == s)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "accept");
+ return;
+ }
+#ifdef __linux__
+ e = eventfd (0,
+ EFD_CLOEXEC);
+ if (-1 == e)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "eventfd");
+ GNUNET_break (0 == close (s));
+ return;
+ }
+#else
+ if (0 != pipe (e))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "pipe");
+ GNUNET_break (0 == close (s));
+ return;
+ }
+#endif
+ {
+ struct TES_Client *client;
+
+ client = GNUNET_new (struct TES_Client);
+ client->cb = *cb;
+ client->csock = s;
+#ifdef __linux__
+ client->esock = e;
+#else
+ client->esock_in = e[1];
+ client->esock_out = e[0];
+#endif
+ GNUNET_assert (0 == pthread_mutex_lock (&TES_clients_lock));
+ GNUNET_CONTAINER_DLL_insert (TES_clients_head,
+ TES_clients_tail,
+ client);
+ GNUNET_assert (0 == pthread_mutex_unlock (&TES_clients_lock));
+ if (0 !=
+ pthread_create (&client->worker,
+ NULL,
+ &sign_worker,
+ client))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "pthread_create");
+ TES_free_client (client);
+ }
+ }
+}
+
+
+int
+TES_listen_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section,
+ const struct TES_Callbacks *cb)
+{
+ {
+ char *pfn;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ section,
+ "SM_PRIV_KEY",
+ &pfn))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "SM_PRIV_KEY");
+ return EXIT_NOTCONFIGURED;
+ }
+ if (GNUNET_SYSERR ==
+ GNUNET_CRYPTO_eddsa_key_from_file (pfn,
+ GNUNET_YES,
+ &TES_smpriv.eddsa_priv))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "SM_PRIV_KEY",
+ "Could not use file to persist private key");
+ GNUNET_free (pfn);
+ return EXIT_NOPERMISSION;
+ }
+ GNUNET_free (pfn);
+ GNUNET_CRYPTO_eddsa_key_get_public (&TES_smpriv.eddsa_priv,
+ &TES_smpub.eddsa_pub);
+ }
+
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ section,
+ "UNIXPATH",
+ &unixpath))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "UNIXPATH");
+ return EXIT_NOTCONFIGURED;
+ }
+ GNUNET_assert (NULL != unixpath);
+ unix_sock = TES_open_socket (unixpath);
+ if (NULL == unix_sock)
+ {
+ GNUNET_free (unixpath);
+ GNUNET_break (0);
+ return EXIT_NOPERMISSION;
+ }
+ /* start job to accept incoming requests on 'sock' */
+ listen_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
+ unix_sock,
+ &listen_job,
+ (void *) cb);
+ return 0;
+}
+
+
+void
+TES_listen_stop (void)
+{
+ if (NULL != listen_task)
+ {
+ GNUNET_SCHEDULER_cancel (listen_task);
+ listen_task = NULL;
+ }
+ if (NULL != unix_sock)
+ {
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_NETWORK_socket_close (unix_sock));
+ unix_sock = NULL;
+ }
+ if (0 != unlink (unixpath))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "unlink",
+ unixpath);
+ }
+ GNUNET_free (unixpath);
+ in_shutdown = true;
+ TES_wake_clients ();
+}
diff --git a/src/util/secmod_common.h b/src/util/secmod_common.h
new file mode 100644
index 000000000..304acebdf
--- /dev/null
+++ b/src/util/secmod_common.h
@@ -0,0 +1,263 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2021 Taler Systems SA
+
+ GNU 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.
+
+ GNU 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 util/secmod_common.h
+ * @brief Common functions for the exchange security modules
+ * @author Florian Dold <dold@taler.net>
+ */
+#ifndef SECMOD_COMMON_H
+#define SECMOD_COMMON_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_network_lib.h>
+#include <pthread.h>
+
+
+/**
+ * Create the listen socket for a secmod daemon.
+ *
+ * This function is not thread-safe, as it changes and
+ * restores the process umask.
+ *
+ * @param unixpath socket path
+ */
+struct GNUNET_NETWORK_Handle *
+TES_open_socket (const char *unixpath);
+
+
+/**
+ * Send a message starting with @a hdr to @a sock.
+ *
+ * @param sock where to send the message
+ * @param hdr beginning of the message, length indicated in size field
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TES_transmit (int sock,
+ const struct GNUNET_MessageHeader *hdr);
+
+
+/**
+ * Transmit @a end bytes from @a pos on @a sock.
+ *
+ * @param sock where to send the data
+ * @param end how many bytes to send
+ * @param pos first address with data
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TES_transmit_raw (int sock,
+ size_t end,
+ const void *pos);
+
+/**
+ * Information we keep for a client connected to us.
+ */
+struct TES_Client;
+
+/**
+ * Function that handles message @a hdr from @a client.
+ *
+ * @param client sender of the message
+ * @param hdr message we received
+ * @return #GNUNET_OK on success
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TES_MessageDispatch)(struct TES_Client *client,
+ const struct GNUNET_MessageHeader *hdr);
+
+
+/**
+ * Function that updates the keys for @a client.
+ *
+ * @param client sender of the message
+ * @return #GNUNET_OK on success
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TES_KeyUpdater)(struct TES_Client *client);
+
+
+/**
+ * Module-specific functions to be used.
+ */
+struct TES_Callbacks
+{
+ /**
+ * Function to handle inbound messages.
+ */
+ TES_MessageDispatch dispatch;
+
+ /**
+ * Function to update key material initially.
+ */
+ TES_KeyUpdater init;
+
+ /**
+ * Function to update key material.
+ */
+ TES_KeyUpdater updater;
+
+};
+
+
+/**
+ * Information we keep for a client connected to us.
+ */
+struct TES_Client
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TES_Client *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TES_Client *prev;
+
+ /**
+ * Callbacks to use for work.
+ */
+ struct TES_Callbacks cb;
+
+ /**
+ * Worker thread for this client.
+ */
+ pthread_t worker;
+
+ /**
+ * Key generation this client is on.
+ */
+ uint64_t key_gen;
+
+ /**
+ * IO-buffer used by @a purpose.
+ */
+ char iobuf[65536];
+
+ /**
+ * Client socket.
+ */
+ int csock;
+
+#ifdef __linux__
+ /**
+ * Event socket.
+ */
+ int esock;
+#else
+ /**
+ * Input end of the event pipe.
+ */
+ int esock_in;
+
+ /**
+ * Output end of the event pipe.
+ */
+ int esock_out;
+#endif
+};
+
+
+/**
+ * Head of DLL of clients connected to us.
+ */
+extern struct TES_Client *TES_clients_head;
+
+/**
+ * Tail of DLL of clients connected to us.
+ */
+extern struct TES_Client *TES_clients_tail;
+
+/**
+ * Lock for the client queue.
+ */
+extern pthread_mutex_t TES_clients_lock;
+
+/**
+ * Private key of this security module. Used to sign denomination key
+ * announcements.
+ */
+extern struct TALER_SecurityModulePrivateKeyP TES_smpriv;
+
+/**
+ * Public key of this security module.
+ */
+extern struct TALER_SecurityModulePublicKeyP TES_smpub;
+
+
+/**
+ * Send a signal to all clients to notify them about a key generation change.
+ */
+void
+TES_wake_clients (void);
+
+
+/**
+ * Read work request from the client.
+ *
+ * @param cls a `struct TES_Client *`
+ * @param dispatch function to call with work requests received
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TES_read_work (void *cls,
+ TES_MessageDispatch dispatch);
+
+
+/**
+ * Wait until the socket is ready to read.
+ *
+ * @param client the client to wait for
+ * @return true if we received an event
+ */
+bool
+TES_await_ready (struct TES_Client *client);
+
+
+/**
+ * Free resources occupied by @a client.
+ *
+ * @param[in] client resources to release
+ */
+void
+TES_free_client (struct TES_Client *client);
+
+
+/**
+ * Start listen task.
+ *
+ * @param cfg configuration to use
+ * @param section configuration section to use
+ * @param cb callback functions to use
+ * @return 0 on success, otherwise return value to return from main()
+ */
+int
+TES_listen_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section,
+ const struct TES_Callbacks *cb);
+
+
+/**
+ * Stop listen task.
+ */
+void
+TES_listen_stop (void);
+
+
+#endif
diff --git a/src/util/secmod_signatures.c b/src/util/secmod_signatures.c
new file mode 100644
index 000000000..3b539d5fe
--- /dev/null
+++ b/src/util/secmod_signatures.c
@@ -0,0 +1,248 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 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 secmod_signatures.c
+ * @brief Utility functions for Taler security module signatures
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+
+
+/**
+ * @brief format used by the signing crypto helper when affirming
+ * that it created an exchange signing key.
+ */
+struct TALER_SigningKeyAnnouncementPS
+{
+
+ /**
+ * Purpose must be #TALER_SIGNATURE_SM_SIGNING_KEY.
+ * Used with an EdDSA signature of a `struct TALER_SecurityModulePublicKeyP`.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Public signing key of the exchange this is about.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * When does the key become available?
+ */
+ struct GNUNET_TIME_TimestampNBO anchor_time;
+
+ /**
+ * How long is the key available after @e anchor_time?
+ */
+ struct GNUNET_TIME_RelativeNBO duration;
+
+};
+
+
+void
+TALER_exchange_secmod_eddsa_sign (
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Relative duration,
+ const struct TALER_SecurityModulePrivateKeyP *secm_priv,
+ struct TALER_SecurityModuleSignatureP *secm_sig)
+{
+ struct TALER_SigningKeyAnnouncementPS ska = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_SM_SIGNING_KEY),
+ .purpose.size = htonl (sizeof (ska)),
+ .exchange_pub = *exchange_pub,
+ .anchor_time = GNUNET_TIME_timestamp_hton (start_sign),
+ .duration = GNUNET_TIME_relative_hton (duration)
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&secm_priv->eddsa_priv,
+ &ska,
+ &secm_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_secmod_eddsa_verify (
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Relative duration,
+ const struct TALER_SecurityModulePublicKeyP *secm_pub,
+ const struct TALER_SecurityModuleSignatureP *secm_sig)
+{
+ struct TALER_SigningKeyAnnouncementPS ska = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_SM_SIGNING_KEY),
+ .purpose.size = htonl (sizeof (ska)),
+ .exchange_pub = *exchange_pub,
+ .anchor_time = GNUNET_TIME_timestamp_hton (start_sign),
+ .duration = GNUNET_TIME_relative_hton (duration)
+ };
+
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_SM_SIGNING_KEY,
+ &ska,
+ &secm_sig->eddsa_signature,
+ &secm_pub->eddsa_pub);
+}
+
+
+/**
+ * @brief format used by the denomination crypto helper when affirming
+ * that it created a denomination key.
+ */
+struct TALER_DenominationKeyAnnouncementPS
+{
+
+ /**
+ * Purpose must be #TALER_SIGNATURE_SM_RSA_DENOMINATION_KEY.
+ * Used with an EdDSA signature of a `struct TALER_SecurityModulePublicKeyP`.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash of the denomination public key.
+ */
+ struct TALER_DenominationHashP h_denom;
+
+ /**
+ * Hash of the section name in the configuration of this denomination.
+ */
+ struct GNUNET_HashCode h_section_name;
+
+ /**
+ * When does the key become available?
+ */
+ struct GNUNET_TIME_TimestampNBO anchor_time;
+
+ /**
+ * How long is the key available after @e anchor_time?
+ */
+ struct GNUNET_TIME_RelativeNBO duration_withdraw;
+
+};
+
+void
+TALER_exchange_secmod_rsa_sign (
+ const struct TALER_RsaPubHashP *h_rsa,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Relative duration,
+ const struct TALER_SecurityModulePrivateKeyP *secm_priv,
+ struct TALER_SecurityModuleSignatureP *secm_sig)
+{
+ struct TALER_DenominationKeyAnnouncementPS dka = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_SM_RSA_DENOMINATION_KEY),
+ .purpose.size = htonl (sizeof (dka)),
+ .h_denom.hash = h_rsa->hash,
+ .anchor_time = GNUNET_TIME_timestamp_hton (start_sign),
+ .duration_withdraw = GNUNET_TIME_relative_hton (duration)
+ };
+
+ GNUNET_CRYPTO_hash (section_name,
+ strlen (section_name) + 1,
+ &dka.h_section_name);
+ GNUNET_CRYPTO_eddsa_sign (&secm_priv->eddsa_priv,
+ &dka,
+ &secm_sig->eddsa_signature);
+
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_secmod_rsa_verify (
+ const struct TALER_RsaPubHashP *h_rsa,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Relative duration,
+ const struct TALER_SecurityModulePublicKeyP *secm_pub,
+ const struct TALER_SecurityModuleSignatureP *secm_sig)
+{
+ struct TALER_DenominationKeyAnnouncementPS dka = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_SM_RSA_DENOMINATION_KEY),
+ .purpose.size = htonl (sizeof (dka)),
+ .h_denom.hash = h_rsa->hash,
+ .anchor_time = GNUNET_TIME_timestamp_hton (start_sign),
+ .duration_withdraw = GNUNET_TIME_relative_hton (duration)
+ };
+
+ GNUNET_CRYPTO_hash (section_name,
+ strlen (section_name) + 1,
+ &dka.h_section_name);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_SM_RSA_DENOMINATION_KEY,
+ &dka,
+ &secm_sig->eddsa_signature,
+ &secm_pub->eddsa_pub);
+}
+
+
+void
+TALER_exchange_secmod_cs_sign (
+ const struct TALER_CsPubHashP *h_cs,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Relative duration,
+ const struct TALER_SecurityModulePrivateKeyP *secm_priv,
+ struct TALER_SecurityModuleSignatureP *secm_sig)
+{
+ struct TALER_DenominationKeyAnnouncementPS dka = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_SM_CS_DENOMINATION_KEY),
+ .purpose.size = htonl (sizeof (dka)),
+ .h_denom.hash = h_cs->hash,
+ .anchor_time = GNUNET_TIME_timestamp_hton (start_sign),
+ .duration_withdraw = GNUNET_TIME_relative_hton (duration)
+ };
+
+ GNUNET_CRYPTO_hash (section_name,
+ strlen (section_name) + 1,
+ &dka.h_section_name);
+ GNUNET_CRYPTO_eddsa_sign (&secm_priv->eddsa_priv,
+ &dka,
+ &secm_sig->eddsa_signature);
+
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_secmod_cs_verify (
+ const struct TALER_CsPubHashP *h_cs,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_sign,
+ struct GNUNET_TIME_Relative duration,
+ const struct TALER_SecurityModulePublicKeyP *secm_pub,
+ const struct TALER_SecurityModuleSignatureP *secm_sig)
+{
+ struct TALER_DenominationKeyAnnouncementPS dka = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_SM_CS_DENOMINATION_KEY),
+ .purpose.size = htonl (sizeof (dka)),
+ .h_denom.hash = h_cs->hash,
+ .anchor_time = GNUNET_TIME_timestamp_hton (start_sign),
+ .duration_withdraw = GNUNET_TIME_relative_hton (duration)
+ };
+
+ GNUNET_CRYPTO_hash (section_name,
+ strlen (section_name) + 1,
+ &dka.h_section_name);
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_SM_CS_DENOMINATION_KEY,
+ &dka,
+ &secm_sig->eddsa_signature,
+ &secm_pub->eddsa_pub);
+}
+
+
+/* end of secmod_signatures.c */
diff --git a/src/util/taler-config.c b/src/util/taler-config.c
new file mode 100644
index 000000000..0e432f852
--- /dev/null
+++ b/src/util/taler-config.c
@@ -0,0 +1,73 @@
+/*
+ This file is part of Taler.
+ Copyright (C) 2012-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 of the License,
+ 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+
+/**
+ * @file util/taler-config.c
+ * @brief tool to access and manipulate Taler configuration files
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util_lib.h"
+
+
+/**
+ * Program to manipulate configuration files.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct GNUNET_CONFIGURATION_ConfigSettings cs = {
+ .api_version = GNUNET_UTIL_VERSION,
+ .global_ret = EXIT_SUCCESS
+ };
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_FAILURE;
+ TALER_OS_init ();
+ ret = GNUNET_PROGRAM_run (argc,
+ argv,
+ "taler-config [OPTIONS]",
+ gettext_noop (
+ "Manipulate Taler configuration files"),
+ options,
+ &GNUNET_CONFIGURATION_config_tool_run,
+ &cs);
+ GNUNET_free_nz ((void *) argv);
+ GNUNET_CONFIGURATION_config_settings_free (&cs);
+ if (GNUNET_NO == ret)
+ return 0;
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ return cs.global_ret;
+}
+
+
+/* end of taler-config.c */
diff --git a/src/util/taler-config.in b/src/util/taler-config.in
index b6561d872..3399aec10 100644
--- a/src/util/taler-config.in
+++ b/src/util/taler-config.in
@@ -7,6 +7,7 @@ if ! type gnunet-config >/dev/null; then
exit 1
fi
-GC=`which gnunet-config`
-export LD_PRELOAD=${LD_PRELOAD:-}:%libdir%/libtalerutil.so
+GC=$(which gnunet-config)
+SO=$(ls %libdir%/libtalerutil.so.* | sort -n | tail -n1)
+export LD_PRELOAD=${LD_PRELOAD:-}:${SO}
exec gnunet-config "$@"
diff --git a/src/util/taler-exchange-secmod-cs.c b/src/util/taler-exchange-secmod-cs.c
new file mode 100644
index 000000000..3e9ba1558
--- /dev/null
+++ b/src/util/taler-exchange-secmod-cs.c
@@ -0,0 +1,2344 @@
+/*
+ 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/>
+*/
+/**
+ * @file util/taler-exchange-secmod-cs.c
+ * @brief Standalone process to perform private key CS operations
+ * @author Christian Grothoff
+ *
+ * Key design points:
+ * - EVERY thread of the exchange will have its own pair of connections to the
+ * crypto helpers. This way, every thread will also have its own /keys state
+ * and avoid the need to synchronize on those.
+ * - auditor signatures and master signatures are to be kept in the exchange DB,
+ * and merged with the public keys of the helper by the exchange HTTPD!
+ * - the main loop of the helper is SINGLE-THREADED, but there are
+ * threads for crypto-workers which do the signing in parallel, one per client.
+ * - thread-safety: signing happens in parallel, thus when REMOVING private keys,
+ * we must ensure that all signers are done before we fully free() the
+ * private key. This is done by reference counting (as work is always
+ * assigned and collected by the main thread).
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler-exchange-secmod-cs.h"
+#include <gcrypt.h>
+#include <pthread.h>
+#include <sys/eventfd.h>
+#include "taler_error_codes.h"
+#include "taler_signatures.h"
+#include "secmod_common.h"
+#include <poll.h>
+
+
+/**
+ * Information we keep per denomination.
+ */
+struct Denomination;
+
+
+/**
+ * One particular denomination key.
+ */
+struct DenominationKey
+{
+
+ /**
+ * Kept in a DLL of the respective denomination. Sorted by anchor time.
+ */
+ struct DenominationKey *next;
+
+ /**
+ * Kept in a DLL of the respective denomination. Sorted by anchor time.
+ */
+ struct DenominationKey *prev;
+
+ /**
+ * Denomination this key belongs to.
+ */
+ struct Denomination *denom;
+
+ /**
+ * Name of the file this key is stored under.
+ */
+ char *filename;
+
+ /**
+ * The private key of the denomination.
+ */
+ struct GNUNET_CRYPTO_CsPrivateKey denom_priv;
+
+ /**
+ * The public key of the denomination.
+ */
+ struct GNUNET_CRYPTO_CsPublicKey denom_pub;
+
+ /**
+ * Message to transmit to clients to introduce this public key.
+ */
+ struct TALER_CRYPTO_CsKeyAvailableNotification *an;
+
+ /**
+ * Hash of this denomination's public key.
+ */
+ struct TALER_CsPubHashP h_cs;
+
+ /**
+ * Time at which this key is supposed to become valid.
+ */
+ struct GNUNET_TIME_Timestamp anchor;
+
+ /**
+ * Generation when this key was created or revoked.
+ */
+ uint64_t key_gen;
+
+ /**
+ * Reference counter. Counts the number of threads that are
+ * using this key at this time.
+ */
+ unsigned int rc;
+
+ /**
+ * Flag set to true if this key has been purged and the memory
+ * must be freed as soon as @e rc hits zero.
+ */
+ bool purge;
+
+};
+
+
+struct Denomination
+{
+
+ /**
+ * Kept in a DLL. Sorted by #denomination_action_time().
+ */
+ struct Denomination *next;
+
+ /**
+ * Kept in a DLL. Sorted by #denomination_action_time().
+ */
+ struct Denomination *prev;
+
+ /**
+ * Head of DLL of actual keys of this denomination.
+ */
+ struct DenominationKey *keys_head;
+
+ /**
+ * Tail of DLL of actual keys of this denomination.
+ */
+ struct DenominationKey *keys_tail;
+
+ /**
+ * How long can coins be withdrawn (generated)? Should be small
+ * enough to limit how many coins will be signed into existence with
+ * the same key, but large enough to still provide a reasonable
+ * anonymity set.
+ */
+ struct GNUNET_TIME_Relative duration_withdraw;
+
+ /**
+ * What is the configuration section of this denomination type? Also used
+ * for the directory name where the denomination keys are stored.
+ */
+ char *section;
+
+};
+
+
+/**
+ * A semaphore.
+ */
+struct Semaphore
+{
+ /**
+ * Mutex for the semaphore.
+ */
+ pthread_mutex_t mutex;
+
+ /**
+ * Condition variable for the semaphore.
+ */
+ pthread_cond_t cv;
+
+ /**
+ * Counter of the semaphore.
+ */
+ unsigned int ctr;
+};
+
+
+/**
+ * Job in a batch sign request.
+ */
+struct BatchJob;
+
+/**
+ * Handle for a thread that does work in batch signing.
+ */
+struct Worker
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct Worker *prev;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Worker *next;
+
+ /**
+ * Job this worker should do next.
+ */
+ struct BatchJob *job;
+
+ /**
+ * Semaphore to signal the worker that a job is available.
+ */
+ struct Semaphore sem;
+
+ /**
+ * Handle for this thread.
+ */
+ pthread_t pt;
+
+ /**
+ * Set to true if the worker should terminate.
+ */
+ bool do_shutdown;
+};
+
+
+/**
+ * Job in a batch sign request.
+ */
+struct BatchJob
+{
+
+ /**
+ * Thread doing the work.
+ */
+ struct Worker *worker;
+
+ /**
+ * Semaphore to signal that the job is finished.
+ */
+ struct Semaphore sem;
+
+ /**
+ * Computation status.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * Which type of request is this?
+ */
+ enum { TYPE_SIGN, TYPE_RDERIVE } type;
+
+ /**
+ * Details depending on @e type.
+ */
+ union
+ {
+
+ /**
+ * Details if @e type is TYPE_SIGN.
+ */
+ struct
+ {
+ /**
+ * Request we are working on.
+ */
+ const struct TALER_CRYPTO_CsSignRequestMessage *sr;
+
+ /**
+ * Result with the signature.
+ */
+ struct GNUNET_CRYPTO_CsBlindSignature cs_answer;
+ } sign;
+
+ /**
+ * Details if type is TYPE_RDERIVE.
+ */
+ struct
+ {
+ /**
+ * Request we are answering.
+ */
+ const struct TALER_CRYPTO_CsRDeriveRequest *rdr;
+
+ /**
+ * Pair of points to return.
+ */
+ struct GNUNET_CRYPTO_CSPublicRPairP rpairp;
+
+ } rderive;
+
+ } details;
+
+};
+
+/**
+ * Head of DLL of workers ready for more work.
+ */
+static struct Worker *worker_head;
+
+/**
+ * Tail of DLL of workers ready for more work.
+ */
+static struct Worker *worker_tail;
+
+/**
+ * Lock for manipulating the worker DLL.
+ */
+static pthread_mutex_t worker_lock;
+
+/**
+ * Total number of workers that were started.
+ */
+static unsigned int workers;
+
+/**
+ * Semaphore used to grab a worker.
+ */
+static struct Semaphore worker_sem;
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * Time when the key update is executed.
+ * Either the actual current time, or a pretended time.
+ */
+static struct GNUNET_TIME_Timestamp now;
+
+/**
+ * The time for the key update, as passed by the user
+ * on the command line.
+ */
+static struct GNUNET_TIME_Timestamp now_tmp;
+
+/**
+ * Where do we store the keys?
+ */
+static char *keydir;
+
+/**
+ * Name of the configuration section prefix to use. Usually either "taler-exchange" or
+ * "donau". The actual configuration section will then be
+ * "$SECTION-secmod-cs".
+ */
+static char *section;
+
+/**
+ * How much should coin creation (@e duration_withdraw) duration overlap
+ * with the next denomination? Basically, the starting time of two
+ * denominations is always @e duration_withdraw - #overlap_duration apart.
+ */
+static struct GNUNET_TIME_Relative overlap_duration;
+
+/**
+ * How long into the future do we pre-generate keys?
+ */
+static struct GNUNET_TIME_Relative lookahead_sign;
+
+/**
+ * All of our denominations, in a DLL. Sorted?
+ */
+static struct Denomination *denom_head;
+
+/**
+ * All of our denominations, in a DLL. Sorted?
+ */
+static struct Denomination *denom_tail;
+
+/**
+ * Map of hashes of public (CS) keys to `struct DenominationKey *`
+ * with the respective private keys.
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *keys;
+
+/**
+ * Task run to generate new keys.
+ */
+static struct GNUNET_SCHEDULER_Task *keygen_task;
+
+/**
+ * Lock for the keys queue.
+ */
+static pthread_mutex_t keys_lock;
+
+/**
+ * Current key generation.
+ */
+static uint64_t key_gen;
+
+/**
+ * Number of workers to launch. Note that connections to
+ * exchanges are NOT workers.
+ */
+static unsigned int max_workers = 16;
+
+
+/**
+ * Generate the announcement message for @a dk.
+ *
+ * @param[in,out] dk denomination key to generate the announcement for
+ */
+static void
+generate_response (struct DenominationKey *dk)
+{
+ struct Denomination *denom = dk->denom;
+ size_t nlen = strlen (denom->section) + 1;
+ struct TALER_CRYPTO_CsKeyAvailableNotification *an;
+ void *p;
+ size_t tlen;
+
+ GNUNET_assert (sizeof(dk->denom_pub) < UINT16_MAX);
+ GNUNET_assert (nlen < UINT16_MAX);
+ tlen = nlen + sizeof (*an);
+ GNUNET_assert (tlen < UINT16_MAX);
+ an = GNUNET_malloc (tlen);
+ an->header.size = htons ((uint16_t) tlen);
+ an->header.type = htons (TALER_HELPER_CS_MT_AVAIL);
+ an->section_name_len = htons ((uint16_t) nlen);
+ an->anchor_time = GNUNET_TIME_timestamp_hton (dk->anchor);
+ an->duration_withdraw = GNUNET_TIME_relative_hton (denom->duration_withdraw);
+ an->denom_pub = dk->denom_pub;
+ TALER_exchange_secmod_cs_sign (&dk->h_cs,
+ denom->section,
+ dk->anchor,
+ denom->duration_withdraw,
+ &TES_smpriv,
+ &an->secm_sig);
+ an->secm_pub = TES_smpub;
+ p = (void *) &an[1];
+ GNUNET_memcpy (p,
+ denom->section,
+ nlen);
+ dk->an = an;
+}
+
+
+/**
+ * Do the actual signing work.
+ *
+ * @param h_cs hash of key to sign with
+ * @param planchet message to sign
+ * @param for_melt true if for melting
+ * @param[out] cs_sigp set to the CS signature
+ * @return #TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+do_sign (const struct TALER_CsPubHashP *h_cs,
+ const struct GNUNET_CRYPTO_CsBlindedMessage *planchet,
+ bool for_melt,
+ struct GNUNET_CRYPTO_CsBlindSignature *cs_sigp)
+{
+ struct GNUNET_CRYPTO_CsRSecret r[2];
+ struct DenominationKey *dk;
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ dk = GNUNET_CONTAINER_multihashmap_get (keys,
+ &h_cs->hash);
+ if (NULL == dk)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing request failed, denomination key %s unknown\n",
+ GNUNET_h2s (&h_cs->hash));
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing request failed, denomination key %s is not yet valid\n",
+ GNUNET_h2s (&h_cs->hash));
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received request to sign over bytes with key %s\n",
+ GNUNET_h2s (&h_cs->hash));
+ GNUNET_assert (dk->rc < UINT_MAX);
+ dk->rc++;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_CRYPTO_cs_r_derive (&planchet->nonce,
+ for_melt ? "rm" : "rw",
+ &dk->denom_priv,
+ r);
+ GNUNET_CRYPTO_cs_sign_derive (&dk->denom_priv,
+ r,
+ planchet,
+ cs_sigp);
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_assert (dk->rc > 0);
+ dk->rc--;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Generate error response that signing failed.
+ *
+ * @param client client to send response to
+ * @param ec error code to include
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+fail_sign (struct TES_Client *client,
+ enum TALER_ErrorCode ec)
+{
+ struct TALER_CRYPTO_SignFailure sf = {
+ .header.size = htons (sizeof (sf)),
+ .header.type = htons (TALER_HELPER_CS_MT_RES_SIGN_FAILURE),
+ .ec = htonl (ec)
+ };
+
+ return TES_transmit (client->csock,
+ &sf.header);
+}
+
+
+/**
+ * Generate error response that deriving failed.
+ *
+ * @param client client to send response to
+ * @param ec error code to include
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+fail_derive (struct TES_Client *client,
+ enum TALER_ErrorCode ec)
+{
+ struct TALER_CRYPTO_RDeriveFailure sf = {
+ .header.size = htons (sizeof (sf)),
+ .header.type = htons (TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE),
+ .ec = htonl (ec)
+ };
+
+ return TES_transmit (client->csock,
+ &sf.header);
+}
+
+
+/**
+ * Generate signature response.
+ *
+ * @param client client to send response to
+ * @param cs_answer signature to send
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+send_signature (struct TES_Client *client,
+ const struct GNUNET_CRYPTO_CsBlindSignature *cs_answer)
+{
+ struct TALER_CRYPTO_SignResponse sres;
+
+ sres.header.size = htons (sizeof (sres));
+ sres.header.type = htons (TALER_HELPER_CS_MT_RES_SIGNATURE);
+ sres.b = htonl (cs_answer->b);
+ sres.cs_answer = cs_answer->s_scalar;
+ return TES_transmit (client->csock,
+ &sres.header);
+}
+
+
+/**
+ * Handle @a client request @a sr to create signature. Create the
+ * signature using the respective key and return the result to
+ * the client.
+ *
+ * @param client the client making the request
+ * @param sr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_sign_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_CsSignRequestMessage *sr)
+{
+ struct GNUNET_CRYPTO_CsBlindSignature cs_answer;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ enum TALER_ErrorCode ec;
+ enum GNUNET_GenericReturnValue ret;
+
+ ec = do_sign (&sr->h_cs,
+ &sr->message,
+ (0 != ntohl (sr->for_melt)),
+ &cs_answer);
+ if (TALER_EC_NONE != ec)
+ {
+ return fail_sign (client,
+ ec);
+ }
+ ret = send_signature (client,
+ &cs_answer);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sent CS signature after %s\n",
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+ return ret;
+}
+
+
+/**
+ * Do the actual deriving work.
+ *
+ * @param h_cs key to sign with
+ * @param nonce nonce to derive from
+ * @param for_melt true if for melting
+ * @param[out] rpairp set to the derived values
+ * @return #TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+do_derive (const struct TALER_CsPubHashP *h_cs,
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce,
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP *rpairp)
+{
+ struct DenominationKey *dk;
+ struct GNUNET_CRYPTO_CSPrivateRPairP r_priv;
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ dk = GNUNET_CONTAINER_multihashmap_get (keys,
+ &h_cs->hash);
+ if (NULL == dk)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "R Derive request failed, denomination key %s unknown\n",
+ GNUNET_h2s (&h_cs->hash));
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "R Derive request failed, denomination key %s is not yet valid\n",
+ GNUNET_h2s (&h_cs->hash));
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received request to derive R with key %s\n",
+ GNUNET_h2s (&h_cs->hash));
+ GNUNET_assert (dk->rc < UINT_MAX);
+ dk->rc++;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_CRYPTO_cs_r_derive (nonce,
+ for_melt ? "rm" : "rw",
+ &dk->denom_priv,
+ r_priv.r);
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_assert (dk->rc > 0);
+ dk->rc--;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_CRYPTO_cs_r_get_public (&r_priv.r[0],
+ &rpairp->r_pub[0]);
+ GNUNET_CRYPTO_cs_r_get_public (&r_priv.r[1],
+ &rpairp->r_pub[1]);
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Generate derivation response.
+ *
+ * @param client client to send response to
+ * @param r_pub public point value pair to send
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+send_derivation (struct TES_Client *client,
+ const struct GNUNET_CRYPTO_CSPublicRPairP *r_pub)
+{
+ struct TALER_CRYPTO_RDeriveResponse rdr = {
+ .header.size = htons (sizeof (rdr)),
+ .header.type = htons (TALER_HELPER_CS_MT_RES_RDERIVE),
+ .r_pub = *r_pub
+ };
+
+ return TES_transmit (client->csock,
+ &rdr.header);
+}
+
+
+/**
+ * Initialize a semaphore @a sem with a value of @a val.
+ *
+ * @param[out] sem semaphore to initialize
+ * @param val initial value of the semaphore
+ */
+static void
+sem_init (struct Semaphore *sem,
+ unsigned int val)
+{
+ GNUNET_assert (0 ==
+ pthread_mutex_init (&sem->mutex,
+ NULL));
+ GNUNET_assert (0 ==
+ pthread_cond_init (&sem->cv,
+ NULL));
+ sem->ctr = val;
+}
+
+
+/**
+ * Decrement semaphore, blocks until this is possible.
+ *
+ * @param[in,out] sem semaphore to decrement
+ */
+static void
+sem_down (struct Semaphore *sem)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex));
+ while (0 == sem->ctr)
+ {
+ pthread_cond_wait (&sem->cv,
+ &sem->mutex);
+ }
+ sem->ctr--;
+ GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex));
+}
+
+
+/**
+ * Increment semaphore, blocks until this is possible.
+ *
+ * @param[in,out] sem semaphore to decrement
+ */
+static void
+sem_up (struct Semaphore *sem)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex));
+ sem->ctr++;
+ GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex));
+ pthread_cond_signal (&sem->cv);
+}
+
+
+/**
+ * Release resources used by @a sem.
+ *
+ * @param[in] sem semaphore to release (except the memory itself)
+ */
+static void
+sem_done (struct Semaphore *sem)
+{
+ GNUNET_break (0 == pthread_cond_destroy (&sem->cv));
+ GNUNET_break (0 == pthread_mutex_destroy (&sem->mutex));
+}
+
+
+/**
+ * Main logic of a worker thread. Grabs work, does it,
+ * grabs more work.
+ *
+ * @param cls a `struct Worker *`
+ * @returns cls
+ */
+static void *
+worker (void *cls)
+{
+ struct Worker *w = cls;
+
+ while (true)
+ {
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ GNUNET_CONTAINER_DLL_insert (worker_head,
+ worker_tail,
+ w);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ sem_up (&worker_sem);
+ sem_down (&w->sem);
+ if (w->do_shutdown)
+ break;
+ {
+ struct BatchJob *bj = w->job;
+
+ switch (bj->type)
+ {
+ case TYPE_SIGN:
+ {
+ const struct TALER_CRYPTO_CsSignRequestMessage *sr
+ = bj->details.sign.sr;
+
+ bj->ec = do_sign (&sr->h_cs,
+ &sr->message,
+ (0 != ntohl (sr->for_melt)),
+ &bj->details.sign.cs_answer);
+ break;
+ }
+ case TYPE_RDERIVE:
+ {
+ const struct TALER_CRYPTO_CsRDeriveRequest *rdr
+ = bj->details.rderive.rdr;
+ bj->ec = do_derive (&rdr->h_cs,
+ &rdr->nonce,
+ (0 != ntohl (rdr->for_melt)),
+ &bj->details.rderive.rpairp);
+ break;
+ }
+ }
+ sem_up (&bj->sem);
+ w->job = NULL;
+ }
+ }
+ return w;
+}
+
+
+/**
+ * Start batch job @a bj to sign @a sr.
+ *
+ * @param sr signature request to answer
+ * @param[out] bj job data structure
+ */
+static void
+start_sign_job (const struct TALER_CRYPTO_CsSignRequestMessage *sr,
+ struct BatchJob *bj)
+{
+ sem_init (&bj->sem,
+ 0);
+ bj->type = TYPE_SIGN;
+ bj->details.sign.sr = sr;
+ sem_down (&worker_sem);
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ bj->worker = worker_head;
+ GNUNET_CONTAINER_DLL_remove (worker_head,
+ worker_tail,
+ bj->worker);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ bj->worker->job = bj;
+ sem_up (&bj->worker->sem);
+}
+
+
+/**
+ * Start batch job @a bj to derive @a rdr.
+ *
+ * @param rdr derivation request to answer
+ * @param[out] bj job data structure
+ */
+static void
+start_derive_job (const struct TALER_CRYPTO_CsRDeriveRequest *rdr,
+ struct BatchJob *bj)
+{
+ sem_init (&bj->sem,
+ 0);
+ bj->type = TYPE_RDERIVE;
+ bj->details.rderive.rdr = rdr;
+ sem_down (&worker_sem);
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ bj->worker = worker_head;
+ GNUNET_CONTAINER_DLL_remove (worker_head,
+ worker_tail,
+ bj->worker);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ bj->worker->job = bj;
+ sem_up (&bj->worker->sem);
+}
+
+
+/**
+ * Finish a job @a bj for a @a client.
+ *
+ * @param client who made the request
+ * @param[in,out] bj job to finish
+ */
+static void
+finish_job (struct TES_Client *client,
+ struct BatchJob *bj)
+{
+ sem_down (&bj->sem);
+ sem_done (&bj->sem);
+ switch (bj->type)
+ {
+ case TYPE_SIGN:
+ if (TALER_EC_NONE != bj->ec)
+ {
+ fail_sign (client,
+ bj->ec);
+ return;
+ }
+ send_signature (client,
+ &bj->details.sign.cs_answer);
+ break;
+ case TYPE_RDERIVE:
+ if (TALER_EC_NONE != bj->ec)
+ {
+ fail_derive (client,
+ bj->ec);
+ return;
+ }
+ send_derivation (client,
+ &bj->details.rderive.rpairp);
+ break;
+ }
+}
+
+
+/**
+ * Handle @a client request @a sr to create a batch of signature. Creates the
+ * signatures using the respective key and return the results to the client.
+ *
+ * @param client the client making the request
+ * @param bsr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_batch_sign_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_BatchSignRequest *bsr)
+{
+ uint32_t bs = ntohl (bsr->batch_size);
+ uint16_t size = ntohs (bsr->header.size) - sizeof (*bsr);
+ const void *off = (const void *) &bsr[1];
+ unsigned int idx = 0;
+ struct BatchJob jobs[GNUNET_NZL (bs)];
+ bool failure = false;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling batch sign request of size %u\n",
+ (unsigned int) bs);
+ if (bs > TALER_MAX_FRESH_COINS)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ while ( (bs > 0) &&
+ (size >= sizeof (struct TALER_CRYPTO_CsSignRequestMessage)) )
+ {
+ const struct TALER_CRYPTO_CsSignRequestMessage *sr = off;
+ uint16_t s = ntohs (sr->header.size);
+
+ if (s > size)
+ {
+ failure = true;
+ bs = idx;
+ break;
+ }
+ start_sign_job (sr,
+ &jobs[idx++]);
+ off += s;
+ size -= s;
+ }
+ GNUNET_break_op (0 == size);
+ bs = GNUNET_MIN (bs,
+ idx);
+ for (unsigned int i = 0; i<bs; i++)
+ finish_job (client,
+ &jobs[i]);
+ if (failure)
+ {
+ struct TALER_CRYPTO_SignFailure sf = {
+ .header.size = htons (sizeof (sf)),
+ .header.type = htons (TALER_HELPER_CS_MT_RES_BATCH_SIGN_FAILURE),
+ .ec = htonl (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE)
+ };
+
+ GNUNET_break (0);
+ return TES_transmit (client->csock,
+ &sf.header);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle @a client request @a sr to create a batch of derivations. Creates the
+ * derivations using the respective key and return the results to the client.
+ *
+ * @param client the client making the request
+ * @param bdr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_batch_derive_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_BatchDeriveRequest *bdr)
+{
+ uint32_t bs = ntohl (bdr->batch_size);
+ uint16_t size = ntohs (bdr->header.size) - sizeof (*bdr);
+ const void *off = (const void *) &bdr[1];
+ unsigned int idx = 0;
+ struct BatchJob jobs[bs];
+ bool failure = false;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling batch derivation request of size %u\n",
+ (unsigned int) bs);
+ if (bs > TALER_MAX_FRESH_COINS)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ while ( (bs > 0) &&
+ (size >= sizeof (struct TALER_CRYPTO_CsRDeriveRequest)) )
+ {
+ const struct TALER_CRYPTO_CsRDeriveRequest *rdr = off;
+ uint16_t s = ntohs (rdr->header.size);
+
+ if ( (s > size) ||
+ (s != sizeof (*rdr)) )
+ {
+ failure = true;
+ bs = idx;
+ break;
+ }
+ start_derive_job (rdr,
+ &jobs[idx++]);
+ off += s;
+ size -= s;
+ }
+ GNUNET_break_op (0 == size);
+ bs = GNUNET_MIN (bs,
+ idx);
+ for (unsigned int i = 0; i<bs; i++)
+ finish_job (client,
+ &jobs[i]);
+ if (failure)
+ {
+ GNUNET_break (0);
+ return fail_derive (client,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Start worker thread for batch processing.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+start_worker (void)
+{
+ struct Worker *w;
+
+ w = GNUNET_new (struct Worker);
+ sem_init (&w->sem,
+ 0);
+ if (0 != pthread_create (&w->pt,
+ NULL,
+ &worker,
+ w))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "pthread_create");
+ GNUNET_free (w);
+ return GNUNET_SYSERR;
+ }
+ workers++;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Stop all worker threads.
+ */
+static void
+stop_workers (void)
+{
+ while (workers > 0)
+ {
+ struct Worker *w;
+ void *result;
+
+ sem_down (&worker_sem);
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ w = worker_head;
+ GNUNET_CONTAINER_DLL_remove (worker_head,
+ worker_tail,
+ w);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ w->do_shutdown = true;
+ sem_up (&w->sem);
+ pthread_join (w->pt,
+ &result);
+ GNUNET_assert (result == w);
+ sem_done (&w->sem);
+ GNUNET_free (w);
+ workers--;
+ }
+}
+
+
+/**
+ * Initialize key material for denomination key @a dk (also on disk).
+ *
+ * @param[in,out] dk denomination key to compute key material for
+ * @param position where in the DLL will the @a dk go
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+setup_key (struct DenominationKey *dk,
+ struct DenominationKey *position)
+{
+ struct Denomination *denom = dk->denom;
+ struct GNUNET_CRYPTO_CsPrivateKey priv;
+ struct GNUNET_CRYPTO_CsPublicKey pub;
+
+ GNUNET_CRYPTO_cs_private_key_generate (&priv);
+ GNUNET_CRYPTO_cs_private_key_get_public (&priv,
+ &pub);
+ GNUNET_CRYPTO_hash (&pub,
+ sizeof (pub),
+ &dk->h_cs.hash);
+ GNUNET_asprintf (&dk->filename,
+ "%s/%s/%llu",
+ keydir,
+ denom->section,
+ (unsigned long long) (dk->anchor.abs_time.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us));
+ if (GNUNET_OK !=
+ GNUNET_DISK_fn_write (dk->filename,
+ &priv,
+ sizeof(priv),
+ GNUNET_DISK_PERM_USER_READ))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "write",
+ dk->filename);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Setup fresh private key %s at %s in `%s' (generation #%llu)\n",
+ GNUNET_h2s (&dk->h_cs.hash),
+ GNUNET_TIME_timestamp2s (dk->anchor),
+ dk->filename,
+ (unsigned long long) key_gen);
+ dk->denom_priv = priv;
+ dk->denom_pub = pub;
+ dk->key_gen = key_gen;
+ generate_response (dk);
+ if (GNUNET_OK !=
+ GNUNET_CONTAINER_multihashmap_put (
+ keys,
+ &dk->h_cs.hash,
+ dk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Duplicate private key created! Terminating.\n");
+ GNUNET_free (dk->filename);
+ GNUNET_free (dk->an);
+ GNUNET_free (dk);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (denom->keys_head,
+ denom->keys_tail,
+ position,
+ dk);
+ return GNUNET_OK;
+}
+
+
+/**
+ * The withdraw period of a key @a dk has expired. Purge it.
+ *
+ * @param[in] dk expired denomination key to purge
+ */
+static void
+purge_key (struct DenominationKey *dk)
+{
+ if (dk->purge)
+ return;
+ if (0 != unlink (dk->filename))
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "unlink",
+ dk->filename);
+ GNUNET_free (dk->filename);
+ dk->purge = true;
+ dk->key_gen = key_gen;
+}
+
+
+/**
+ * A @a client informs us that a key has been revoked.
+ * Check if the key is still in use, and if so replace (!)
+ * it with a fresh key.
+ *
+ * @param client the client making the request
+ * @param rr the revocation request
+ */
+static enum GNUNET_GenericReturnValue
+handle_revoke_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_CsRevokeRequest *rr)
+{
+ struct DenominationKey *dk;
+ struct DenominationKey *ndk;
+ struct Denomination *denom;
+
+ (void) client;
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ dk = GNUNET_CONTAINER_multihashmap_get (keys,
+ &rr->h_cs.hash);
+ if (NULL == dk)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Revocation request ignored, denomination key %s unknown\n",
+ GNUNET_h2s (&rr->h_cs.hash));
+ return GNUNET_OK;
+ }
+ if (dk->purge)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Revocation request ignored, denomination key %s already revoked\n",
+ GNUNET_h2s (&rr->h_cs.hash));
+ return GNUNET_OK;
+ }
+
+ key_gen++;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Revoking key %s, bumping generation to %llu\n",
+ GNUNET_h2s (&rr->h_cs.hash),
+ (unsigned long long) key_gen);
+ purge_key (dk);
+
+ /* Setup replacement key */
+ denom = dk->denom;
+ ndk = GNUNET_new (struct DenominationKey);
+ ndk->denom = denom;
+ ndk->anchor = dk->anchor;
+ if (GNUNET_OK !=
+ setup_key (ndk,
+ dk))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ TES_wake_clients ();
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle @a client request @a rdr to create signature. Create the
+ * signature using the respective key and return the result to
+ * the client.
+ *
+ * @param client the client making the request
+ * @param rdr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_r_derive_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_CsRDeriveRequest *rdr)
+{
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pub;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ enum TALER_ErrorCode ec;
+ enum GNUNET_GenericReturnValue ret;
+
+ ec = do_derive (&rdr->h_cs,
+ &rdr->nonce,
+ (0 != ntohl (rdr->for_melt)),
+ &r_pub);
+ if (TALER_EC_NONE != ec)
+ {
+ return fail_derive (client,
+ ec);
+ }
+
+ ret = send_derivation (client,
+ &r_pub);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sent CS Derived R after %s\n",
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+ return ret;
+}
+
+
+/**
+ * Handle @a hdr message received from @a client.
+ *
+ * @param client the client that received the message
+ * @param hdr message that was received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+cs_work_dispatch (struct TES_Client *client,
+ const struct GNUNET_MessageHeader *hdr)
+{
+ uint16_t msize = ntohs (hdr->size);
+
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_CS_MT_REQ_SIGN:
+ if (msize < sizeof (struct TALER_CRYPTO_CsSignRequestMessage))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_sign_request (
+ client,
+ (const struct TALER_CRYPTO_CsSignRequestMessage *) hdr);
+ case TALER_HELPER_CS_MT_REQ_REVOKE:
+ if (msize != sizeof (struct TALER_CRYPTO_CsRevokeRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_revoke_request (
+ client,
+ (const struct TALER_CRYPTO_CsRevokeRequest *) hdr);
+ case TALER_HELPER_CS_MT_REQ_BATCH_SIGN:
+ if (msize <= sizeof (struct TALER_CRYPTO_BatchSignRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_batch_sign_request (
+ client,
+ (const struct TALER_CRYPTO_BatchSignRequest *) hdr);
+ case TALER_HELPER_CS_MT_REQ_BATCH_RDERIVE:
+ if (msize <= sizeof (struct TALER_CRYPTO_BatchDeriveRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_batch_derive_request (
+ client,
+ (const struct TALER_CRYPTO_BatchDeriveRequest *) hdr);
+ case TALER_HELPER_CS_MT_REQ_RDERIVE:
+ if (msize != sizeof (struct TALER_CRYPTO_CsRDeriveRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_r_derive_request (client,
+ (const struct
+ TALER_CRYPTO_CsRDeriveRequest *) hdr);
+ default:
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+}
+
+
+/**
+ * Send our initial key set to @a client together with the
+ * "sync" terminator.
+ *
+ * @param client the client to inform
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+cs_client_init (struct TES_Client *client)
+{
+ size_t obs = 0;
+ char *buf;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Initializing new client %p\n",
+ client);
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *dk = denom->keys_head;
+ NULL != dk;
+ dk = dk->next)
+ {
+ obs += ntohs (dk->an->header.size);
+ }
+ }
+ buf = GNUNET_malloc (obs);
+ obs = 0;
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *dk = denom->keys_head;
+ NULL != dk;
+ dk = dk->next)
+ {
+ GNUNET_memcpy (&buf[obs],
+ dk->an,
+ ntohs (dk->an->header.size));
+ obs += ntohs (dk->an->header.size);
+ }
+ }
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (GNUNET_OK !=
+ TES_transmit_raw (client->csock,
+ obs,
+ buf))
+ {
+ GNUNET_free (buf);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client %p must have disconnected\n",
+ client);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (buf);
+ {
+ struct GNUNET_MessageHeader synced = {
+ .type = htons (TALER_HELPER_CS_SYNCED),
+ .size = htons (sizeof (synced))
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sending CS SYNCED message to %p\n",
+ client);
+ if (GNUNET_OK !=
+ TES_transmit (client->csock,
+ &synced))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Notify @a client about all changes to the keys since
+ * the last generation known to the @a client.
+ *
+ * @param client the client to notify
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+cs_update_client_keys (struct TES_Client *client)
+{
+ size_t obs = 0;
+ char *buf;
+ enum GNUNET_GenericReturnValue ret;
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *key = denom->keys_head;
+ NULL != key;
+ key = key->next)
+ {
+ if (key->key_gen <= client->key_gen)
+ continue;
+ if (key->purge)
+ obs += sizeof (struct TALER_CRYPTO_CsKeyPurgeNotification);
+ else
+ obs += ntohs (key->an->header.size);
+ }
+ }
+ if (0 == obs)
+ {
+ /* nothing to do */
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ return GNUNET_OK;
+ }
+ buf = GNUNET_malloc (obs);
+ obs = 0;
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *key = denom->keys_head;
+ NULL != key;
+ key = key->next)
+ {
+ if (key->key_gen <= client->key_gen)
+ continue;
+ if (key->purge)
+ {
+ struct TALER_CRYPTO_CsKeyPurgeNotification pn = {
+ .header.type = htons (TALER_HELPER_CS_MT_PURGE),
+ .header.size = htons (sizeof (pn)),
+ .h_cs = key->h_cs
+ };
+
+ GNUNET_memcpy (&buf[obs],
+ &pn,
+ sizeof (pn));
+ GNUNET_assert (obs + sizeof (pn)
+ > obs);
+ obs += sizeof (pn);
+ }
+ else
+ {
+ GNUNET_memcpy (&buf[obs],
+ key->an,
+ ntohs (key->an->header.size));
+ GNUNET_assert (obs + ntohs (key->an->header.size)
+ > obs);
+ obs += ntohs (key->an->header.size);
+ }
+ }
+ }
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ ret = TES_transmit_raw (client->csock,
+ obs,
+ buf);
+ GNUNET_free (buf);
+ return ret;
+}
+
+
+/**
+ * Create a new denomination key (we do not have enough).
+ *
+ * @param denom denomination key to create
+ * @param now current time to use (to get many keys to use the exact same time)
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+create_key (struct Denomination *denom,
+ struct GNUNET_TIME_Timestamp now)
+{
+ struct DenominationKey *dk;
+ struct GNUNET_TIME_Timestamp anchor;
+
+ anchor = now;
+ if (NULL != denom->keys_tail)
+ {
+ struct GNUNET_TIME_Absolute abs;
+
+ abs = GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time,
+ GNUNET_TIME_relative_subtract (
+ denom->duration_withdraw,
+ overlap_duration));
+ if (GNUNET_TIME_absolute_cmp (now.abs_time, <, abs))
+ anchor = GNUNET_TIME_absolute_to_timestamp (abs);
+ }
+ dk = GNUNET_new (struct DenominationKey);
+ dk->denom = denom;
+ dk->anchor = anchor;
+ if (GNUNET_OK !=
+ setup_key (dk,
+ denom->keys_tail))
+ {
+ GNUNET_break (0);
+ GNUNET_free (dk);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * At what time does this denomination require its next action?
+ * Basically, the minimum of the withdraw expiration time of the
+ * oldest denomination key, and the withdraw expiration time of
+ * the newest denomination key minus the #lookahead_sign time.
+ *
+ * @param denom denomination to compute action time for
+ */
+static struct GNUNET_TIME_Absolute
+denomination_action_time (const struct Denomination *denom)
+{
+ struct DenominationKey *head = denom->keys_head;
+ struct DenominationKey *tail = denom->keys_tail;
+ struct GNUNET_TIME_Absolute tt;
+
+ if (NULL == head)
+ return GNUNET_TIME_UNIT_ZERO_ABS;
+ tt = GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (tail->anchor.abs_time,
+ denom->duration_withdraw),
+ lookahead_sign),
+ overlap_duration);
+ if (head->rc > 0)
+ return tt; /* head expiration does not count due to rc > 0 */
+ return GNUNET_TIME_absolute_min (
+ GNUNET_TIME_absolute_add (head->anchor.abs_time,
+ denom->duration_withdraw),
+ tt);
+}
+
+
+/**
+ * Create new keys and expire ancient keys of the given denomination @a denom.
+ * Removes the @a denom from the #denom_head DLL and re-insert its at the
+ * correct location sorted by next maintenance activity.
+ *
+ * @param[in,out] denom denomination to update material for
+ * @param now current time to use (to get many keys to use the exact same time)
+ * @param[in,out] wake set to true if we should wake the clients
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+update_keys (struct Denomination *denom,
+ struct GNUNET_TIME_Timestamp now,
+ bool *wake)
+{
+ /* create new denomination keys */
+ if (NULL != denom->keys_tail)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Updating keys of denomination `%s', last key %s valid for another %s\n",
+ denom->section,
+ GNUNET_h2s (&denom->keys_tail->h_cs.hash),
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (
+ denom->keys_tail->anchor.abs_time,
+ denom->duration_withdraw),
+ overlap_duration)),
+ GNUNET_YES));
+ while ( (NULL == denom->keys_tail) ||
+ GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time,
+ denom->duration_withdraw),
+ lookahead_sign),
+ overlap_duration)) )
+ {
+ if (! *wake)
+ {
+ key_gen++;
+ *wake = true;
+ }
+ if (GNUNET_OK !=
+ create_key (denom,
+ now))
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ }
+ /* remove expired denomination keys */
+ while ( (NULL != denom->keys_head) &&
+ GNUNET_TIME_absolute_is_past
+ (GNUNET_TIME_absolute_add (denom->keys_head->anchor.abs_time,
+ denom->duration_withdraw)) )
+ {
+ struct DenominationKey *key = denom->keys_head;
+ struct DenominationKey *nxt = key->next;
+
+ if (0 != key->rc)
+ break; /* later */
+ GNUNET_CONTAINER_DLL_remove (denom->keys_head,
+ denom->keys_tail,
+ key);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_remove (
+ keys,
+ &key->h_cs.hash,
+ key));
+ if ( (! key->purge) &&
+ (0 != unlink (key->filename)) )
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "unlink",
+ key->filename);
+ GNUNET_free (key->filename);
+ GNUNET_free (key->an);
+ GNUNET_free (key);
+ key = nxt;
+ }
+
+ /* Update position of 'denom' in #denom_head DLL: sort by action time */
+ {
+ struct Denomination *before;
+ struct GNUNET_TIME_Absolute at;
+
+ at = denomination_action_time (denom);
+ GNUNET_CONTAINER_DLL_remove (denom_head,
+ denom_tail,
+ denom);
+ before = NULL;
+ for (struct Denomination *pos = denom_head;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_TIME_absolute_cmp (denomination_action_time (pos), >=, at))
+ break;
+ before = pos;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (denom_head,
+ denom_tail,
+ before,
+ denom);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Task run periodically to expire keys and/or generate fresh ones.
+ *
+ * @param cls NULL
+ */
+static void
+update_denominations (void *cls)
+{
+ struct Denomination *denom;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Timestamp t;
+ bool wake = false;
+
+ (void) cls;
+ keygen_task = NULL;
+ now = GNUNET_TIME_absolute_get ();
+ t = GNUNET_TIME_absolute_to_timestamp (now);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Updating denominations ...\n");
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ do {
+ denom = denom_head;
+ if (GNUNET_OK !=
+ update_keys (denom,
+ t,
+ &wake))
+ return;
+ } while (denom != denom_head);
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Updating denominations finished ...\n");
+ if (wake)
+ TES_wake_clients ();
+ keygen_task = GNUNET_SCHEDULER_add_at (denomination_action_time (denom),
+ &update_denominations,
+ NULL);
+}
+
+
+/**
+ * Parse private key of denomination @a denom in @a buf.
+ *
+ * @param[out] denom denomination of the key
+ * @param filename name of the file we are parsing, for logging
+ * @param priv key material
+ */
+static void
+parse_key (struct Denomination *denom,
+ const char *filename,
+ const struct GNUNET_CRYPTO_CsPrivateKey *priv)
+{
+ char *anchor_s;
+ char dummy;
+ unsigned long long anchor_ll;
+ struct GNUNET_TIME_Timestamp anchor;
+
+ anchor_s = strrchr (filename,
+ '/');
+ if (NULL == anchor_s)
+ {
+ /* File in a directory without '/' in the name, this makes no sense. */
+ GNUNET_break (0);
+ return;
+ }
+ anchor_s++;
+ if (1 != sscanf (anchor_s,
+ "%llu%c",
+ &anchor_ll,
+ &dummy))
+ {
+ /* Filenames in KEYDIR must ONLY be the anchor time in seconds! */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Filename `%s' invalid for key file, skipping\n",
+ filename);
+ return;
+ }
+ anchor.abs_time.abs_value_us
+ = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us;
+ if (anchor_ll != anchor.abs_time.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us)
+ {
+ /* Integer overflow. Bad, invalid filename. */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Filename `%s' invalid for key file, skipping\n",
+ filename);
+ return;
+ }
+ {
+ struct DenominationKey *dk;
+ struct DenominationKey *before;
+
+ dk = GNUNET_new (struct DenominationKey);
+ dk->denom_priv = *priv;
+ dk->denom = denom;
+ dk->anchor = anchor;
+ dk->filename = GNUNET_strdup (filename);
+ GNUNET_CRYPTO_cs_private_key_get_public (priv,
+ &dk->denom_pub);
+ GNUNET_CRYPTO_hash (&dk->denom_pub,
+ sizeof (dk->denom_pub),
+ &dk->h_cs.hash);
+ generate_response (dk);
+ if (GNUNET_OK !=
+ GNUNET_CONTAINER_multihashmap_put (
+ keys,
+ &dk->h_cs.hash,
+ dk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Duplicate private key %s detected in file `%s'. Skipping.\n",
+ GNUNET_h2s (&dk->h_cs.hash),
+ filename);
+ GNUNET_free (dk->an);
+ GNUNET_free (dk);
+ return;
+ }
+ before = NULL;
+ for (struct DenominationKey *pos = denom->keys_head;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_TIME_timestamp_cmp (pos->anchor,
+ >,
+ anchor))
+ break;
+ before = pos;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (denom->keys_head,
+ denom->keys_tail,
+ before,
+ dk);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Imported key %s from `%s'\n",
+ GNUNET_h2s (&dk->h_cs.hash),
+ filename);
+ }
+}
+
+
+/**
+ * Import a private key from @a filename for the denomination
+ * given in @a cls.
+ *
+ * @param[in,out] cls a `struct Denomiantion`
+ * @param filename name of a file in the directory
+ * @return #GNUNET_OK (always, continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+import_key (void *cls,
+ const char *filename)
+{
+ struct Denomination *denom = cls;
+ struct GNUNET_DISK_FileHandle *fh;
+ struct GNUNET_DISK_MapHandle *map;
+ void *ptr;
+ int fd;
+ struct stat sbuf;
+
+ {
+ struct stat lsbuf;
+
+ if (0 != lstat (filename,
+ &lsbuf))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "lstat",
+ filename);
+ return GNUNET_OK;
+ }
+ if (! S_ISREG (lsbuf.st_mode))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "File `%s' is not a regular file, which is not allowed for private keys!\n",
+ filename);
+ return GNUNET_OK;
+ }
+ }
+
+ fd = open (filename,
+ O_RDONLY | O_CLOEXEC);
+ if (-1 == fd)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "open",
+ filename);
+ return GNUNET_OK;
+ }
+ if (0 != fstat (fd,
+ &sbuf))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "stat",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ if (! S_ISREG (sbuf.st_mode))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "File `%s' is not a regular file, which is not allowed for private keys!\n",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ if (0 != (sbuf.st_mode & (S_IWUSR | S_IRWXG | S_IRWXO)))
+ {
+ /* permission are NOT tight, try to patch them up! */
+ if (0 !=
+ fchmod (fd,
+ S_IRUSR))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "fchmod",
+ filename);
+ /* refuse to use key if file has wrong permissions */
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ }
+ fh = GNUNET_DISK_get_handle_from_int_fd (fd);
+ if (NULL == fh)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "open",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ if (sbuf.st_size != sizeof(struct GNUNET_CRYPTO_CsPrivateKey))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "File `%s' too big to be a private key\n",
+ filename);
+ GNUNET_DISK_file_close (fh);
+ return GNUNET_OK;
+ }
+ ptr = GNUNET_DISK_file_map (fh,
+ &map,
+ GNUNET_DISK_MAP_TYPE_READ,
+ (size_t) sbuf.st_size);
+ if (NULL == ptr)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "mmap",
+ filename);
+ GNUNET_DISK_file_close (fh);
+ return GNUNET_OK;
+ }
+ parse_key (denom,
+ filename,
+ (const struct GNUNET_CRYPTO_CsPrivateKey *) ptr);
+ GNUNET_DISK_file_unmap (map);
+ GNUNET_DISK_file_close (fh);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse configuration for denomination type parameters. Also determines
+ * our anchor by looking at the existing denominations of the same type.
+ *
+ * @param cfg configuration to use
+ * @param ct section in the configuration file giving the denomination type parameters
+ * @param[out] denom set to the denomination parameters from the configuration
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if the configuration is invalid
+ */
+static enum GNUNET_GenericReturnValue
+parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *ct,
+ struct Denomination *denom)
+{
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-cs",
+ section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ ct,
+ "DURATION_WITHDRAW",
+ &denom->duration_withdraw))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ ct,
+ "DURATION_WITHDRAW");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_TIME_relative_cmp (overlap_duration,
+ >=,
+ denom->duration_withdraw))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "OVERLAP_DURATION",
+ "Value given must be smaller than value for DURATION_WITHDRAW!");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (secname);
+ denom->section = GNUNET_strdup (ct);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #load_denominations.
+ */
+struct LoadContext
+{
+
+ /**
+ * Configuration to use.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Current time to use.
+ */
+ struct GNUNET_TIME_Timestamp t;
+
+ /**
+ * Status, to be set to #GNUNET_SYSERR on failure
+ */
+ enum GNUNET_GenericReturnValue ret;
+};
+
+
+/**
+ * Generate new denomination signing keys for the denomination type of the given @a
+ * denomination_alias.
+ *
+ * @param cls a `struct LoadContext`, with 'ret' to be set to #GNUNET_SYSERR on failure
+ * @param denomination_alias name of the denomination's section in the configuration
+ */
+static void
+load_denominations (void *cls,
+ const char *denomination_alias)
+{
+ struct LoadContext *ctx = cls;
+ struct Denomination *denom;
+ bool wake = true;
+ char *cipher;
+
+ if ( (0 != strncasecmp (denomination_alias,
+ "coin_",
+ strlen ("coin_"))) &&
+ (0 != strncasecmp (denomination_alias,
+ "coin-",
+ strlen ("coin-"))) )
+ return; /* not a denomination type definition */
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ctx->cfg,
+ denomination_alias,
+ "CIPHER",
+ &cipher))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ denomination_alias,
+ "CIPHER");
+ return;
+ }
+ if (0 != strcmp (cipher, "CS"))
+ {
+ GNUNET_free (cipher);
+ return; /* Ignore denominations of other types than CS*/
+ }
+ GNUNET_free (cipher);
+
+ denom = GNUNET_new (struct Denomination);
+ if (GNUNET_OK !=
+ parse_denomination_cfg (ctx->cfg,
+ denomination_alias,
+ denom))
+ {
+ ctx->ret = GNUNET_SYSERR;
+ GNUNET_free (denom);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Loading keys for denomination %s\n",
+ denom->section);
+ {
+ char *dname;
+
+ GNUNET_asprintf (&dname,
+ "%s/%s",
+ keydir,
+ denom->section);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_directory_create (dname));
+ GNUNET_DISK_directory_scan (dname,
+ &import_key,
+ denom);
+ GNUNET_free (dname);
+ }
+ GNUNET_CONTAINER_DLL_insert (denom_head,
+ denom_tail,
+ denom);
+ update_keys (denom,
+ ctx->t,
+ &wake);
+}
+
+
+/**
+ * Load the various duration values from @a cfg
+ *
+ * @param cfg configuration to use
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-cs",
+ section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "OVERLAP_DURATION",
+ &overlap_duration))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "OVERLAP_DURATION");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "LOOKAHEAD_SIGN",
+ &lookahead_sign))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "LOOKAHEAD_SIGN");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (secname);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function run on shutdown. Stops the various jobs (nicely).
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+ TES_listen_stop ();
+ if (NULL != keygen_task)
+ {
+ GNUNET_SCHEDULER_cancel (keygen_task);
+ keygen_task = NULL;
+ }
+ stop_workers ();
+ sem_done (&worker_sem);
+}
+
+
+/**
+ * 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)
+{
+ static struct TES_Callbacks cb = {
+ .dispatch = &cs_work_dispatch,
+ .updater = &cs_update_client_keys,
+ .init = &cs_client_init
+ };
+ char *secname;
+
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+ if (GNUNET_TIME_timestamp_cmp (now, !=, now_tmp))
+ {
+ /* The user gave "--now", use it! */
+ now = now_tmp;
+ }
+ else
+ {
+ /* get current time again, we may be timetraveling! */
+ now = GNUNET_TIME_timestamp_get ();
+ }
+ GNUNET_asprintf (&secname,
+ "%s-secmod-cs",
+ section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ secname,
+ "KEY_DIR",
+ &keydir))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "KEY_DIR");
+ GNUNET_free (secname);
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ GNUNET_free (secname);
+ if (GNUNET_OK !=
+ load_durations (cfg))
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ {
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-cs",
+ section);
+ global_ret = TES_listen_start (cfg,
+ secname,
+ &cb);
+ GNUNET_free (secname);
+ }
+ if (0 != global_ret)
+ return;
+ sem_init (&worker_sem,
+ 0);
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ if (0 == max_workers)
+ {
+ long lret;
+
+ lret = sysconf (_SC_NPROCESSORS_CONF);
+ if (lret <= 0)
+ lret = 1;
+ max_workers = (unsigned int) lret;
+ }
+ for (unsigned int i = 0; i<max_workers; i++)
+ if (GNUNET_OK !=
+ start_worker ())
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ /* Load denominations */
+ keys = GNUNET_CONTAINER_multihashmap_create (65536,
+ GNUNET_YES);
+ {
+ struct LoadContext lc = {
+ .cfg = cfg,
+ .ret = GNUNET_OK,
+ .t = now
+ };
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_CONFIGURATION_iterate_sections (cfg,
+ &load_denominations,
+ &lc);
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (GNUNET_OK != lc.ret)
+ {
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+ if (NULL == denom_head)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No CS denominations configured\n");
+ TES_wake_clients ();
+ return;
+ }
+ /* start job to keep keys up-to-date; MUST be run before the #listen_task,
+ hence with priority. */
+ keygen_task = GNUNET_SCHEDULER_add_with_priority (
+ GNUNET_SCHEDULER_PRIORITY_URGENT,
+ &update_denominations,
+ NULL);
+}
+
+
+/**
+ * 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_string ('s',
+ "section",
+ "SECTION",
+ "name of the configuration section prefix to use, default is 'taler'",
+ &section),
+ GNUNET_GETOPT_option_timetravel ('T',
+ "timetravel"),
+ GNUNET_GETOPT_option_timestamp ('t',
+ "time",
+ "TIMESTAMP",
+ "pretend it is a different time for the update",
+ &now_tmp),
+ GNUNET_GETOPT_option_uint ('w',
+ "workers",
+ "COUNT",
+ "use COUNT workers for parallel processing of batch requests",
+ &max_workers),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ /* Restrict permissions for the key files that we create. */
+ (void) umask (S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH);
+ section = GNUNET_strdup ("taler-exchange");
+ /* 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 ();
+ now_tmp = now = GNUNET_TIME_timestamp_get ();
+ ret = GNUNET_PROGRAM_run (argc, argv,
+ "taler-exchange-secmod-cs",
+ "Handle private CS key operations for a Taler exchange",
+ options,
+ &run,
+ NULL);
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ return global_ret;
+}
diff --git a/src/util/taler-exchange-secmod-cs.conf b/src/util/taler-exchange-secmod-cs.conf
new file mode 100644
index 000000000..fa3cdba40
--- /dev/null
+++ b/src/util/taler-exchange-secmod-cs.conf
@@ -0,0 +1,23 @@
+[taler-exchange-secmod-cs]
+
+# How long should generated coins overlap in their validity
+# periods. Should be long enough to avoid problems with
+# wallets picking one key and then due to network latency
+# another key being valid. The DURATION_WITHDRAW period
+# must be longer than this value.
+OVERLAP_DURATION = 5 m
+
+# Where do we store the generated private keys.
+KEY_DIR = ${TALER_DATA_HOME}exchange-secmod-cs/keys
+
+# Where does the helper listen for requests?
+UNIXPATH = ${TALER_RUNTIME_DIR}exchange-secmod-cs/server.sock
+
+# Directory for clients.
+CLIENT_DIR = ${TALER_RUNTIME_DIR}exchange-secmod-cs/clients
+
+# Where should the security module store its own private key?
+SM_PRIV_KEY = ${TALER_DATA_HOME}exchange-secmod-cs/secmod-private-key
+
+# For how long into the future do we pre-generate keys?
+LOOKAHEAD_SIGN = 1 year
diff --git a/src/util/taler-exchange-secmod-cs.h b/src/util/taler-exchange-secmod-cs.h
new file mode 100644
index 000000000..0321335da
--- /dev/null
+++ b/src/util/taler-exchange-secmod-cs.h
@@ -0,0 +1,319 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-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/>
+*/
+/**
+ * @file util/taler-exchange-secmod-cs.h
+ * @brief IPC messages for the CS crypto helper.
+ * @author Christian Grothoff
+ * @author Gian Demarmels
+ * @author Lucien Heuzeveldt
+ */
+#ifndef TALER_EXCHANGE_SECMOD_CS_H
+#define TALER_EXCHANGE_SECMOD_CS_H
+
+#define TALER_HELPER_CS_MT_PURGE 1
+#define TALER_HELPER_CS_MT_AVAIL 2
+
+#define TALER_HELPER_CS_MT_REQ_INIT 3
+#define TALER_HELPER_CS_MT_REQ_BATCH_SIGN 4
+#define TALER_HELPER_CS_MT_REQ_SIGN 5
+#define TALER_HELPER_CS_MT_REQ_REVOKE 6
+#define TALER_HELPER_CS_MT_REQ_BATCH_RDERIVE 7
+#define TALER_HELPER_CS_MT_REQ_RDERIVE 8
+
+#define TALER_HELPER_CS_MT_RES_SIGNATURE 9
+#define TALER_HELPER_CS_MT_RES_SIGN_FAILURE 10
+#define TALER_HELPER_CS_MT_RES_BATCH_SIGN_FAILURE 11
+#define TALER_HELPER_CS_MT_RES_RDERIVE 12
+#define TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE 13
+#define TALER_HELPER_CS_MT_RES_BATCH_RDERIVE_FAILURE 14
+
+#define TALER_HELPER_CS_SYNCED 15
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+
+/**
+ * Message sent if a key is available.
+ */
+struct TALER_CRYPTO_CsKeyAvailableNotification
+{
+ /**
+ * Type is #TALER_HELPER_CS_MT_AVAIL
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * Number of bytes of the section name.
+ */
+ uint32_t section_name_len;
+
+ /**
+ * When does the key become available?
+ */
+ struct GNUNET_TIME_TimestampNBO anchor_time;
+
+ /**
+ * How long is the key available after @e anchor_time?
+ */
+ struct GNUNET_TIME_RelativeNBO duration_withdraw;
+
+ /**
+ * Public key used to generate the @e sicm_sig.
+ */
+ struct TALER_SecurityModulePublicKeyP secm_pub;
+
+ /**
+ * Signature affirming the announcement, of
+ * purpose #TALER_SIGNATURE_SM_CS_DENOMINATION_KEY.
+ */
+ struct TALER_SecurityModuleSignatureP secm_sig;
+
+ /**
+ * Denomination Public key
+ */
+ struct GNUNET_CRYPTO_CsPublicKey denom_pub;
+
+ /* followed by @e section_name bytes of the configuration section name
+ of the denomination of this key */
+
+};
+
+
+/**
+ * Message sent if a key was purged.
+ */
+struct TALER_CRYPTO_CsKeyPurgeNotification
+{
+ /**
+ * Type is #TALER_HELPER_CS_MT_PURGE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * For now, always zero.
+ */
+ uint32_t reserved;
+
+ /**
+ * Hash of the public key of the purged CS key.
+ */
+ struct TALER_CsPubHashP h_cs;
+
+};
+
+
+/**
+ * Message sent if a signature is requested.
+ */
+struct TALER_CRYPTO_CsSignRequestMessage
+{
+ /**
+ * Type is #TALER_HELPER_CS_MT_REQ_SIGN.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * 0 for withdraw, 1 for melt, in NBO.
+ */
+ uint32_t for_melt;
+
+ /**
+ * Hash of the public key of the CS key to use for the signature.
+ */
+ struct TALER_CsPubHashP h_cs;
+
+ /**
+ * Message to sign.
+ */
+ struct GNUNET_CRYPTO_CsBlindedMessage message;
+
+};
+
+
+/**
+ * Message sent if a batch of signatures is requested.
+ */
+struct TALER_CRYPTO_BatchSignRequest
+{
+ /**
+ * Type is #TALER_HELPER_CS_MT_REQ_BATCH_SIGN.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * Number of signatures to create, in NBO.
+ */
+ uint32_t batch_size;
+
+ /*
+ * Followed by @e batch_size batch sign requests.
+ */
+
+};
+
+
+/**
+ * Message sent if a signature is requested.
+ */
+struct TALER_CRYPTO_CsRDeriveRequest
+{
+ /**
+ * Type is #TALER_HELPER_CS_MT_REQ_RDERIVE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * 0 for withdraw, 1 for melt, in NBO.
+ */
+ uint32_t for_melt;
+
+ /**
+ * Hash of the public key of the CS key to use for the derivation.
+ */
+ struct TALER_CsPubHashP h_cs;
+
+ /**
+ * Withdraw nonce to derive R from
+ */
+ struct GNUNET_CRYPTO_CsSessionNonce nonce;
+};
+
+
+/**
+ * Message sent if a batch of derivations is requested.
+ */
+struct TALER_CRYPTO_BatchDeriveRequest
+{
+ /**
+ * Type is #TALER_HELPER_CS_MT_REQ_BATCH_RDERIVE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * Number of derivations to create, in NBO.
+ */
+ uint32_t batch_size;
+
+ /*
+ * Followed by @e batch_size derive requests.
+ */
+
+};
+
+
+/**
+ * Message sent if a key was revoked.
+ */
+struct TALER_CRYPTO_CsRevokeRequest
+{
+ /**
+ * Type is #TALER_HELPER_CS_MT_REQ_REVOKE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * For now, always zero.
+ */
+ uint32_t reserved;
+
+ /**
+ * Hash of the public key of the revoked CS key.
+ */
+ struct TALER_CsPubHashP h_cs;
+
+};
+
+
+/**
+ * Message sent if a signature was successfully computed.
+ */
+struct TALER_CRYPTO_SignResponse
+{
+ /**
+ * Type is #TALER_HELPER_CS_MT_RES_SIGNATURE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * The chosen 'b' (0 or 1).
+ */
+ uint32_t b;
+
+ /**
+ * Contains the blindided s.
+ */
+ struct GNUNET_CRYPTO_CsBlindS cs_answer;
+};
+
+/**
+ * Message sent if a R is successfully derived
+ */
+struct TALER_CRYPTO_RDeriveResponse
+{
+ /**
+ * Type is #TALER_HELPER_CS_MT_RES_RDERIVE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * For now, always zero.
+ */
+ uint32_t reserved;
+
+ /**
+ * Pair of derived R values
+ */
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pub;
+};
+
+
+/**
+ * Message sent if signing failed.
+ */
+struct TALER_CRYPTO_SignFailure
+{
+ /**
+ * Type is #TALER_HELPER_CS_MT_RES_SIGN_FAILURE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * If available, Taler error code. In NBO.
+ */
+ uint32_t ec;
+
+};
+
+/**
+ * Message sent if derivation failed.
+ */
+struct TALER_CRYPTO_RDeriveFailure
+{
+ /**
+ * Type is #TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * If available, Taler error code. In NBO.
+ */
+ uint32_t ec;
+
+};
+GNUNET_NETWORK_STRUCT_END
+
+
+#endif
diff --git a/src/util/taler-exchange-secmod-eddsa.c b/src/util/taler-exchange-secmod-eddsa.c
new file mode 100644
index 000000000..0b95447f7
--- /dev/null
+++ b/src/util/taler-exchange-secmod-eddsa.c
@@ -0,0 +1,1213 @@
+/*
+ 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 util/taler-exchange-secmod-eddsa.c
+ * @brief Standalone process to perform private key EDDSA operations
+ * @author Christian Grothoff
+ *
+ * Key design points:
+ * - EVERY thread of the exchange will have its own pair of connections to the
+ * crypto helpers. This way, every threat will also have its own /keys state
+ * and avoid the need to synchronize on those.
+ * - auditor signatures and master signatures are to be kept in the exchange DB,
+ * and merged with the public keys of the helper by the exchange HTTPD!
+ * - the main loop of the helper is SINGLE-THREADED, but there are
+ * threads for crypto-workers which (only) do the signing in parallel,
+ * one per client.
+ * - thread-safety: signing happens in parallel, thus when REMOVING private keys,
+ * we must ensure that all signers are done before we fully free() the
+ * private key. This is done by reference counting (as work is always
+ * assigned and collected by the main thread).
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler-exchange-secmod-eddsa.h"
+#include <gcrypt.h>
+#include <pthread.h>
+#include "taler_error_codes.h"
+#include "taler_signatures.h"
+#include "secmod_common.h"
+#include <poll.h>
+
+
+/**
+ * One particular key.
+ */
+struct Key
+{
+
+ /**
+ * Kept in a DLL. Sorted by anchor time.
+ */
+ struct Key *next;
+
+ /**
+ * Kept in a DLL. Sorted by anchor time.
+ */
+ struct Key *prev;
+
+ /**
+ * Name of the file this key is stored under.
+ */
+ char *filename;
+
+ /**
+ * The private key.
+ */
+ struct TALER_ExchangePrivateKeyP exchange_priv;
+
+ /**
+ * The public key.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Time at which this key is supposed to become valid.
+ */
+ struct GNUNET_TIME_Timestamp anchor;
+
+ /**
+ * Generation when this key was created or revoked.
+ */
+ uint64_t key_gen;
+
+ /**
+ * Reference counter. Counts the number of threads that are
+ * using this key at this time.
+ */
+ unsigned int rc;
+
+ /**
+ * Flag set to true if this key has been purged and the memory
+ * must be freed as soon as @e rc hits zero.
+ */
+ bool purge;
+
+};
+
+
+/**
+ * Head of DLL of actual keys, sorted by anchor.
+ */
+static struct Key *keys_head;
+
+/**
+ * Tail of DLL of actual keys.
+ */
+static struct Key *keys_tail;
+
+/**
+ * How long can a key be used?
+ */
+static struct GNUNET_TIME_Relative duration;
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * Time when the key update is executed.
+ * Either the actual current time, or a pretended time.
+ */
+static struct GNUNET_TIME_Timestamp now;
+
+/**
+ * The time for the key update, as passed by the user
+ * on the command line.
+ */
+static struct GNUNET_TIME_Timestamp now_tmp;
+
+/**
+ * Where do we store the keys?
+ */
+static char *keydir;
+
+/**
+ * Name of the configuration section prefix to use. Usually either "taler-exchange" or
+ * "donau". The actual configuration section will then be
+ * "$SECTION-secmod-eddsa".
+ */
+static char *section;
+
+/**
+ * How much should coin creation duration overlap
+ * with the next key? Basically, the starting time of two
+ * keys is always #duration - #overlap_duration apart.
+ */
+static struct GNUNET_TIME_Relative overlap_duration;
+
+/**
+ * How long into the future do we pre-generate keys?
+ */
+static struct GNUNET_TIME_Relative lookahead_sign;
+
+/**
+ * Task run to generate new keys.
+ */
+static struct GNUNET_SCHEDULER_Task *keygen_task;
+
+/**
+ * Lock for the keys queue.
+ */
+static pthread_mutex_t keys_lock;
+
+/**
+ * Current key generation.
+ */
+static uint64_t key_gen;
+
+
+/**
+ * Notify @a client about @a key becoming available.
+ *
+ * @param[in,out] client the client to notify; possible freed if transmission fails
+ * @param key the key to notify @a client about
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+notify_client_key_add (struct TES_Client *client,
+ const struct Key *key)
+{
+ struct TALER_CRYPTO_EddsaKeyAvailableNotification an = {
+ .header.size = htons (sizeof (an)),
+ .header.type = htons (TALER_HELPER_EDDSA_MT_AVAIL),
+ .anchor_time = GNUNET_TIME_timestamp_hton (key->anchor),
+ .duration = GNUNET_TIME_relative_hton (duration),
+ .exchange_pub = key->exchange_pub,
+ .secm_pub = TES_smpub
+ };
+
+ TALER_exchange_secmod_eddsa_sign (&key->exchange_pub,
+ key->anchor,
+ duration,
+ &TES_smpriv,
+ &an.secm_sig);
+ if (GNUNET_OK !=
+ TES_transmit (client->csock,
+ &an.header))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client %p must have disconnected\n",
+ client);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Notify @a client about @a key being purged.
+ *
+ * @param[in,out] client the client to notify; possible freed if transmission fails
+ * @param key the key to notify @a client about
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+notify_client_key_del (struct TES_Client *client,
+ const struct Key *key)
+{
+ struct TALER_CRYPTO_EddsaKeyPurgeNotification pn = {
+ .header.type = htons (TALER_HELPER_EDDSA_MT_PURGE),
+ .header.size = htons (sizeof (pn)),
+ .exchange_pub = key->exchange_pub
+ };
+
+ if (GNUNET_OK !=
+ TES_transmit (client->csock,
+ &pn.header))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client %p must have disconnected\n",
+ client);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle @a client request @a sr to create signature. Create the
+ * signature using the respective key and return the result to
+ * the client.
+ *
+ * @param client the client making the request
+ * @param sr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_sign_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_EddsaSignRequest *sr)
+{
+ const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose = &sr->purpose;
+ size_t purpose_size = ntohs (sr->header.size) - sizeof (*sr)
+ + sizeof (*purpose);
+ struct Key *key;
+ struct TALER_CRYPTO_EddsaSignResponse sres = {
+ .header.size = htons (sizeof (sres)),
+ .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGNATURE)
+ };
+ enum TALER_ErrorCode ec;
+
+ if (purpose_size != htonl (purpose->size))
+ {
+ struct TALER_CRYPTO_EddsaSignFailure sf = {
+ .header.size = htons (sizeof (sr)),
+ .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE),
+ .ec = htonl (TALER_EC_GENERIC_PARAMETER_MALFORMED)
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing request failed, request malformed\n");
+ return TES_transmit (client->csock,
+ &sf.header);
+ }
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ key = keys_head;
+ while ( (NULL != key) &&
+ (GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_add (key->anchor.abs_time,
+ duration))) )
+ {
+ struct Key *nxt = key->next;
+
+ if (0 != key->rc)
+ break; /* do later */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Deleting past key %s (expired %s ago)\n",
+ TALER_B2S (&nxt->exchange_pub),
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_duration (
+ GNUNET_TIME_absolute_add (key->anchor.abs_time,
+ duration)),
+ GNUNET_YES));
+ GNUNET_CONTAINER_DLL_remove (keys_head,
+ keys_tail,
+ key);
+ if ( (! key->purge) &&
+ (0 != unlink (key->filename)) )
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "unlink",
+ key->filename);
+ GNUNET_free (key->filename);
+ GNUNET_free (key);
+ key = nxt;
+ }
+ if (NULL == key)
+ {
+ GNUNET_break (0);
+ ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+ }
+ else
+ {
+ GNUNET_assert (key->rc < UINT_MAX);
+ key->rc++;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_sign_ (&key->exchange_priv.eddsa_priv,
+ purpose,
+ &sres.exchange_sig.eddsa_signature))
+ ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ else
+ ec = TALER_EC_NONE;
+ sres.exchange_pub = key->exchange_pub;
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_assert (key->rc > 0);
+ key->rc--;
+ }
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (TALER_EC_NONE != ec)
+ {
+ struct TALER_CRYPTO_EddsaSignFailure sf = {
+ .header.size = htons (sizeof (sf)),
+ .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE),
+ .ec = htonl ((uint32_t) ec)
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Signing request %p failed, worker failed to produce signature\n",
+ client);
+ return TES_transmit (client->csock,
+ &sf.header);
+ }
+ return TES_transmit (client->csock,
+ &sres.header);
+}
+
+
+/**
+ * Initialize key material for key @a key (also on disk).
+ *
+ * @param[in,out] key to compute key material for
+ * @param position where in the DLL will the @a key go
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+setup_key (struct Key *key,
+ struct Key *position)
+{
+ struct GNUNET_CRYPTO_EddsaPrivateKey priv;
+ struct GNUNET_CRYPTO_EddsaPublicKey pub;
+
+ GNUNET_CRYPTO_eddsa_key_create (&priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&priv,
+ &pub);
+ GNUNET_asprintf (&key->filename,
+ "%s/%llu",
+ keydir,
+ (unsigned long long) (key->anchor.abs_time.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us));
+ if (GNUNET_OK !=
+ GNUNET_DISK_fn_write (key->filename,
+ &priv,
+ sizeof (priv),
+ GNUNET_DISK_PERM_USER_READ))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "write",
+ key->filename);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Setup fresh private key in `%s'\n",
+ key->filename);
+ key->key_gen = key_gen;
+ key->exchange_priv.eddsa_priv = priv;
+ key->exchange_pub.eddsa_pub = pub;
+ GNUNET_CONTAINER_DLL_insert_after (keys_head,
+ keys_tail,
+ position,
+ key);
+ return GNUNET_OK;
+}
+
+
+/**
+ * The validity period of a key @a key has expired. Purge it.
+ *
+ * @param[in] key expired or revoked key to purge
+ */
+static void
+purge_key (struct Key *key)
+{
+ if (key->purge)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Key %s already purged, skipping\n",
+ TALER_B2S (&key->exchange_pub));
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purging key %s\n",
+ TALER_B2S (&key->exchange_pub));
+ if (0 != unlink (key->filename))
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "unlink",
+ key->filename);
+ key->purge = true;
+ key->key_gen = key_gen;
+ GNUNET_free (key->filename);
+}
+
+
+/**
+ * A @a client informs us that a key has been revoked.
+ * Check if the key is still in use, and if so replace (!)
+ * it with a fresh key.
+ *
+ * @param client the client making the request
+ * @param rr the revocation request
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_revoke_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_EddsaRevokeRequest *rr)
+{
+ struct Key *key;
+ struct Key *nkey;
+
+ (void) client;
+ key = NULL;
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ for (struct Key *pos = keys_head;
+ NULL != pos;
+ pos = pos->next)
+ if (0 == GNUNET_memcmp (&pos->exchange_pub,
+ &rr->exchange_pub))
+ {
+ key = pos;
+ break;
+ }
+ if (NULL == key)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Revocation request ignored, key unknown\n");
+ return GNUNET_OK;
+ }
+ if (key->purge)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Revocation request ignored, key %s already revoked\n",
+ TALER_B2S (&key->exchange_pub));
+ return GNUNET_OK;
+ }
+ key_gen++;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Revoking key %s, bumping generation to %llu\n",
+ TALER_B2S (&key->exchange_pub),
+ (unsigned long long) key_gen);
+ purge_key (key);
+
+ /* Setup replacement key */
+ nkey = GNUNET_new (struct Key);
+ nkey->anchor = key->anchor;
+ if (GNUNET_OK !=
+ setup_key (nkey,
+ key))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ TES_wake_clients ();
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle @a hdr message received from @a client.
+ *
+ * @param client the client that received the message
+ * @param hdr message that was received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+eddsa_work_dispatch (struct TES_Client *client,
+ const struct GNUNET_MessageHeader *hdr)
+{
+ uint16_t msize = ntohs (hdr->size);
+
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_EDDSA_MT_REQ_SIGN:
+ if (msize < sizeof (struct TALER_CRYPTO_EddsaSignRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_sign_request (
+ client,
+ (const struct TALER_CRYPTO_EddsaSignRequest *) hdr);
+ case TALER_HELPER_EDDSA_MT_REQ_REVOKE:
+ if (msize != sizeof (struct TALER_CRYPTO_EddsaRevokeRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_revoke_request (
+ client,
+ (const struct TALER_CRYPTO_EddsaRevokeRequest *) hdr);
+ default:
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+}
+
+
+/**
+ * Send our initial key set to @a client together with the
+ * "sync" terminator.
+ *
+ * @param client the client to inform
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+eddsa_client_init (struct TES_Client *client)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ for (struct Key *key = keys_head;
+ NULL != key;
+ key = key->next)
+ {
+ if (GNUNET_OK !=
+ notify_client_key_add (client,
+ key))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ {
+ struct GNUNET_MessageHeader synced = {
+ .type = htons (TALER_HELPER_EDDSA_SYNCED),
+ .size = htons (sizeof (synced))
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client %p synced\n",
+ client);
+ if (GNUNET_OK !=
+ TES_transmit (client->csock,
+ &synced))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Notify @a client about all changes to the keys since
+ * the last generation known to the @a client.
+ *
+ * @param client the client to notify
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+eddsa_update_client_keys (struct TES_Client *client)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Updating client %p to generation %llu\n",
+ client,
+ (unsigned long long) key_gen);
+ for (struct Key *key = keys_head;
+ NULL != key;
+ key = key->next)
+ {
+ if (key->key_gen <= client->key_gen)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Skipping key %s, no change since generation %llu\n",
+ TALER_B2S (&key->exchange_pub),
+ (unsigned long long) client->key_gen);
+ continue;
+ }
+ if (key->purge)
+ {
+ if (GNUNET_OK !=
+ notify_client_key_del (client,
+ key))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ return GNUNET_SYSERR;
+ }
+ }
+ else
+ {
+ if (GNUNET_OK !=
+ notify_client_key_add (client,
+ key))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ return GNUNET_OK;
+}
+
+
+/**
+ * Create a new key (we do not have enough).
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+create_key (void)
+{
+ struct Key *key;
+ struct GNUNET_TIME_Timestamp anchor;
+
+ anchor = GNUNET_TIME_timestamp_get ();
+ if (NULL != keys_tail)
+ {
+ struct GNUNET_TIME_Absolute abs;
+
+ abs = GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
+ GNUNET_TIME_relative_subtract (
+ duration,
+ overlap_duration));
+ if (GNUNET_TIME_absolute_cmp (anchor.abs_time,
+ <,
+ abs))
+ anchor = GNUNET_TIME_absolute_to_timestamp (abs);
+ }
+ key = GNUNET_new (struct Key);
+ key->anchor = anchor;
+ if (GNUNET_OK !=
+ setup_key (key,
+ keys_tail))
+ {
+ GNUNET_break (0);
+ GNUNET_free (key);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * At what time does the current key set require its next action? Basically,
+ * the minimum of the expiration time of the oldest key, and the expiration
+ * time of the newest key minus the #lookahead_sign time.
+ */
+static struct GNUNET_TIME_Absolute
+key_action_time (void)
+{
+ struct Key *nxt;
+
+ nxt = keys_head;
+ while ( (NULL != nxt) &&
+ (nxt->purge) )
+ nxt = nxt->next;
+ if (NULL == nxt)
+ return GNUNET_TIME_UNIT_ZERO_ABS;
+ return GNUNET_TIME_absolute_min (
+ GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
+ duration),
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
+ duration),
+ lookahead_sign),
+ overlap_duration));
+}
+
+
+/**
+ * Create new keys and expire ancient keys.
+ *
+ * @param cls NULL
+ */
+static void
+update_keys (void *cls)
+{
+ bool wake = false;
+ struct Key *nxt;
+
+ (void) cls;
+ keygen_task = NULL;
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ /* create new keys */
+ while ( (NULL == keys_tail) ||
+ GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
+ duration),
+ lookahead_sign),
+ overlap_duration)) )
+ {
+ if (! wake)
+ {
+ key_gen++;
+ wake = true;
+ }
+ if (GNUNET_OK !=
+ create_key ())
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+ nxt = keys_head;
+ /* purge expired keys */
+ while ( (NULL != nxt) &&
+ GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
+ duration)))
+ {
+ if (! wake)
+ {
+ key_gen++;
+ wake = true;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purging past key %s (expired %s ago)\n",
+ TALER_B2S (&nxt->exchange_pub),
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_duration (
+ GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
+ duration)),
+ GNUNET_YES));
+ purge_key (nxt);
+ nxt = nxt->next;
+ }
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (wake)
+ TES_wake_clients ();
+ keygen_task = GNUNET_SCHEDULER_add_at (key_action_time (),
+ &update_keys,
+ NULL);
+}
+
+
+/**
+ * Parse private key from @a filename in @a buf.
+ *
+ * @param filename name of the file we are parsing, for logging
+ * @param buf key material
+ * @param buf_size number of bytes in @a buf
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_key (const char *filename,
+ const void *buf,
+ size_t buf_size)
+{
+ struct GNUNET_CRYPTO_EddsaPrivateKey priv;
+ char *anchor_s;
+ char dummy;
+ unsigned long long anchor_ll;
+ struct GNUNET_TIME_Timestamp anchor;
+
+ anchor_s = strrchr (filename,
+ '/');
+ if (NULL == anchor_s)
+ {
+ /* File in a directory without '/' in the name, this makes no sense. */
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ anchor_s++;
+ if (1 != sscanf (anchor_s,
+ "%llu%c",
+ &anchor_ll,
+ &dummy))
+ {
+ /* Filenames in KEYDIR must ONLY be the anchor time in seconds! */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Filename `%s' invalid for key file, skipping\n",
+ filename);
+ return GNUNET_SYSERR;
+ }
+ anchor.abs_time.abs_value_us = anchor_ll
+ * GNUNET_TIME_UNIT_SECONDS.rel_value_us;
+ if (anchor_ll != anchor.abs_time.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us)
+ {
+ /* Integer overflow. Bad, invalid filename. */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Filename `%s' invalid for key file, skipping\n",
+ filename);
+ return GNUNET_SYSERR;
+ }
+ if (buf_size != sizeof (priv))
+ {
+ /* Parser failure. */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "File `%s' is malformed, skipping\n",
+ filename);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_memcpy (&priv,
+ buf,
+ buf_size);
+
+ {
+ struct GNUNET_CRYPTO_EddsaPublicKey pub;
+ struct Key *key;
+ struct Key *before;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&priv,
+ &pub);
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ key = GNUNET_new (struct Key);
+ key->exchange_priv.eddsa_priv = priv;
+ key->exchange_pub.eddsa_pub = pub;
+ key->anchor = anchor;
+ key->filename = GNUNET_strdup (filename);
+ key->key_gen = key_gen;
+ before = NULL;
+ for (struct Key *pos = keys_head;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_TIME_timestamp_cmp (pos->anchor, >, anchor))
+ break;
+ before = pos;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (keys_head,
+ keys_tail,
+ before,
+ key);
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Imported key from `%s'\n",
+ filename);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Import a private key from @a filename.
+ *
+ * @param cls NULL
+ * @param filename name of a file in the directory
+ */
+static enum GNUNET_GenericReturnValue
+import_key (void *cls,
+ const char *filename)
+{
+ struct GNUNET_DISK_FileHandle *fh;
+ struct GNUNET_DISK_MapHandle *map;
+ void *ptr;
+ int fd;
+ struct stat sbuf;
+
+ (void) cls;
+ {
+ struct stat lsbuf;
+
+ if (0 != lstat (filename,
+ &lsbuf))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "lstat",
+ filename);
+ return GNUNET_OK;
+ }
+ if (! S_ISREG (lsbuf.st_mode))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "File `%s' is not a regular file, which is not allowed for private keys!\n",
+ filename);
+ return GNUNET_OK;
+ }
+ }
+
+ fd = open (filename,
+ O_RDONLY | O_CLOEXEC);
+ if (-1 == fd)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "open",
+ filename);
+ return GNUNET_OK;
+ }
+ if (0 != fstat (fd,
+ &sbuf))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "stat",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ if (! S_ISREG (sbuf.st_mode))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "File `%s' is not a regular file, which is not allowed for private keys!\n",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ if (0 != (sbuf.st_mode & (S_IWUSR | S_IRWXG | S_IRWXO)))
+ {
+ /* permission are NOT tight, try to patch them up! */
+ if (0 !=
+ fchmod (fd,
+ S_IRUSR))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "fchmod",
+ filename);
+ /* refuse to use key if file has wrong permissions */
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ }
+ fh = GNUNET_DISK_get_handle_from_int_fd (fd);
+ if (NULL == fh)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "open",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ if (sbuf.st_size > 2048)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "File `%s' to big to be a private key\n",
+ filename);
+ GNUNET_DISK_file_close (fh);
+ return GNUNET_OK;
+ }
+ ptr = GNUNET_DISK_file_map (fh,
+ &map,
+ GNUNET_DISK_MAP_TYPE_READ,
+ (size_t) sbuf.st_size);
+ if (NULL == ptr)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "mmap",
+ filename);
+ GNUNET_DISK_file_close (fh);
+ return GNUNET_OK;
+ }
+ (void) parse_key (filename,
+ ptr,
+ (size_t) sbuf.st_size);
+ GNUNET_DISK_file_unmap (map);
+ GNUNET_DISK_file_close (fh);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Load the various duration values from @a kcfg.
+ *
+ * @param cfg configuration to use
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-eddsa",
+ section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "OVERLAP_DURATION",
+ &overlap_duration))
+ {
+ GNUNET_free (secname);
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "OVERLAP_DURATION");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "DURATION",
+ &duration))
+ {
+ GNUNET_free (secname);
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "DURATION");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "LOOKAHEAD_SIGN",
+ &lookahead_sign))
+ {
+ GNUNET_free (secname);
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "LOOKAHEAD_SIGN");
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (secname);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function run on shutdown. Stops the various jobs (nicely).
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+ TES_listen_stop ();
+ if (NULL != keygen_task)
+ {
+ GNUNET_SCHEDULER_cancel (keygen_task);
+ keygen_task = NULL;
+ }
+}
+
+
+/**
+ * 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)
+{
+ static struct TES_Callbacks cb = {
+ .dispatch = eddsa_work_dispatch,
+ .updater = eddsa_update_client_keys,
+ .init = eddsa_client_init
+ };
+ char *secname;
+
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+ if (GNUNET_TIME_timestamp_cmp (now, !=, now_tmp))
+ {
+ /* The user gave "--now", use it! */
+ now = now_tmp;
+ }
+ else
+ {
+ /* get current time again, we may be timetraveling! */
+ now = GNUNET_TIME_timestamp_get ();
+ }
+ GNUNET_asprintf (&secname,
+ "%s-secmod-eddsa",
+ section);
+ if (GNUNET_OK !=
+ load_durations (cfg))
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ secname,
+ "KEY_DIR",
+ &keydir))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "KEY_DIR");
+ GNUNET_free (secname);
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ GNUNET_free (secname);
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ {
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-eddsa",
+ section);
+ global_ret = TES_listen_start (cfg,
+ secname,
+ &cb);
+ GNUNET_free (secname);
+ }
+ if (0 != global_ret)
+ return;
+ /* Load keys */
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_directory_create (keydir));
+ GNUNET_DISK_directory_scan (keydir,
+ &import_key,
+ NULL);
+ if ( (NULL != keys_head) &&
+ (GNUNET_TIME_absolute_is_future (keys_head->anchor.abs_time)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Existing anchor is in %s the future. Refusing to start\n",
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ keys_head->anchor.abs_time),
+ GNUNET_YES));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ /* start job to keep keys up-to-date; MUST be run before the #listen_task,
+ hence with priority. */
+ keygen_task = GNUNET_SCHEDULER_add_with_priority (
+ GNUNET_SCHEDULER_PRIORITY_URGENT,
+ &update_keys,
+ NULL);
+}
+
+
+/**
+ * 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_string ('s',
+ "section",
+ "SECTION",
+ "name of the configuration section prefix to use, default is 'taler'",
+ &section),
+ GNUNET_GETOPT_option_timetravel ('T',
+ "timetravel"),
+ GNUNET_GETOPT_option_timestamp ('t',
+ "time",
+ "TIMESTAMP",
+ "pretend it is a different time for the update",
+ &now_tmp),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ /* Restrict permissions for the key files that we create. */
+ (void) umask (S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH);
+ section = GNUNET_strdup ("taler-exchange");
+ /* 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 ();
+ now_tmp = now = GNUNET_TIME_timestamp_get ();
+ ret = GNUNET_PROGRAM_run (argc,
+ argv,
+ "taler-exchange-secmod-eddsa",
+ "Handle private EDDSA key operations for a Taler exchange",
+ options,
+ &run,
+ NULL);
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ return global_ret;
+}
diff --git a/src/util/taler-exchange-secmod-eddsa.conf b/src/util/taler-exchange-secmod-eddsa.conf
new file mode 100644
index 000000000..0cb4a4ffc
--- /dev/null
+++ b/src/util/taler-exchange-secmod-eddsa.conf
@@ -0,0 +1,26 @@
+[taler-exchange-secmod-eddsa]
+
+# How long should generated coins overlap in their validity
+# periods. Should be long enough to avoid problems with
+# wallets picking one key and then due to network latency
+# another key being valid. The DURATION_WITHDRAW period
+# must be longer than this value.
+OVERLAP_DURATION = 5m
+
+# Where do we store the private keys.
+KEY_DIR = ${TALER_DATA_HOME}exchange-secmod-eddsa/keys
+
+# Where does the helper listen for requests?
+UNIXPATH = ${TALER_RUNTIME_DIR}exchange-secmod-eddsa/server.sock
+
+# Directory for clients.
+CLIENT_DIR = ${TALER_RUNTIME_DIR}exchange-secmod-eddsa/clients
+
+# Where should the security module store its own private key?
+SM_PRIV_KEY = ${TALER_DATA_HOME}exchange-secmod-eddsa/secmod-private-key
+
+# For how long into the future do we pre-generate keys?
+LOOKAHEAD_SIGN = 1 year
+
+# For how long are signing keys valid?
+DURATION = 12 weeks
diff --git a/src/util/taler-exchange-secmod-eddsa.h b/src/util/taler-exchange-secmod-eddsa.h
new file mode 100644
index 000000000..c05d90a6c
--- /dev/null
+++ b/src/util/taler-exchange-secmod-eddsa.h
@@ -0,0 +1,202 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 util/taler-exchange-secmod-eddsa.h
+ * @brief IPC messages for the EDDSA crypto helper.
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_SECMOD_EDDSA_H
+#define TALER_EXCHANGE_SECMOD_EDDSA_H
+
+#define TALER_HELPER_EDDSA_MT_PURGE 11
+#define TALER_HELPER_EDDSA_MT_AVAIL 12
+
+#define TALER_HELPER_EDDSA_MT_REQ_INIT 14
+#define TALER_HELPER_EDDSA_MT_REQ_SIGN 15
+#define TALER_HELPER_EDDSA_MT_REQ_REVOKE 16
+
+#define TALER_HELPER_EDDSA_MT_RES_SIGNATURE 17
+#define TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE 18
+
+#define TALER_HELPER_EDDSA_SYNCED 19
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message sent if a key is available.
+ */
+struct TALER_CRYPTO_EddsaKeyAvailableNotification
+{
+ /**
+ * Type is #TALER_HELPER_EDDSA_MT_AVAIL
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * For now, always zero.
+ */
+ uint32_t reserved;
+
+ /**
+ * When does the key become available?
+ */
+ struct GNUNET_TIME_TimestampNBO anchor_time;
+
+ /**
+ * How long is the key available after @e anchor_time?
+ */
+ struct GNUNET_TIME_RelativeNBO duration;
+
+ /**
+ * Public key used to generate the @e sicm_sig.
+ */
+ struct TALER_SecurityModulePublicKeyP secm_pub;
+
+ /**
+ * Signature affirming the announcement, of
+ * purpose #TALER_SIGNATURE_SM_SIGNING_KEY.
+ */
+ struct TALER_SecurityModuleSignatureP secm_sig;
+
+ /**
+ * The public key.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+};
+
+
+/**
+ * Message sent if a key was purged.
+ */
+struct TALER_CRYPTO_EddsaKeyPurgeNotification
+{
+ /**
+ * Type is #TALER_HELPER_EDDSA_MT_PURGE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * For now, always zero.
+ */
+ uint32_t reserved;
+
+ /**
+ * The public key.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+};
+
+
+/**
+ * Message sent if a signature is requested.
+ */
+struct TALER_CRYPTO_EddsaSignRequest
+{
+ /**
+ * Type is #TALER_HELPER_EDDSA_MT_REQ_SIGN.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * For now, always zero.
+ */
+ uint32_t reserved;
+
+ /**
+ * What should be signed over.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /* followed by rest of data to sign */
+};
+
+
+/**
+ * Message sent if a key was revoked.
+ */
+struct TALER_CRYPTO_EddsaRevokeRequest
+{
+ /**
+ * Type is #TALER_HELPER_EDDSA_MT_REQ_REVOKE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * For now, always zero.
+ */
+ uint32_t reserved;
+
+ /**
+ * The public key to revoke.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+};
+
+
+/**
+ * Message sent if a signature was successfully computed.
+ */
+struct TALER_CRYPTO_EddsaSignResponse
+{
+ /**
+ * Type is #TALER_HELPER_EDDSA_MT_RES_SIGNATURE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * For now, always zero.
+ */
+ uint32_t reserved;
+
+ /**
+ * The public key used for the signature.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * The public key to use for the signature.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+};
+
+
+/**
+ * Message sent if signing failed.
+ */
+struct TALER_CRYPTO_EddsaSignFailure
+{
+ /**
+ * Type is #TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * If available, Taler error code. In NBO.
+ */
+ uint32_t ec;
+
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+#endif
diff --git a/src/util/taler-exchange-secmod-rsa.c b/src/util/taler-exchange-secmod-rsa.c
new file mode 100644
index 000000000..c80e2e3c4
--- /dev/null
+++ b/src/util/taler-exchange-secmod-rsa.c
@@ -0,0 +1,2133 @@
+/*
+ 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/>
+*/
+/**
+ * @file util/taler-exchange-secmod-rsa.c
+ * @brief Standalone process to perform private key RSA operations
+ * @author Christian Grothoff
+ *
+ * Key design points:
+ * - EVERY thread of the exchange will have its own pair of connections to the
+ * crypto helpers. This way, every thread will also have its own /keys state
+ * and avoid the need to synchronize on those.
+ * - auditor signatures and master signatures are to be kept in the exchange DB,
+ * and merged with the public keys of the helper by the exchange HTTPD!
+ * - the main loop of the helper is SINGLE-THREADED, but there are
+ * threads for crypto-workers which do the signing in parallel, one per client.
+ * - thread-safety: signing happens in parallel, thus when REMOVING private keys,
+ * we must ensure that all signers are done before we fully free() the
+ * private key. This is done by reference counting (as work is always
+ * assigned and collected by the main thread).
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler-exchange-secmod-rsa.h"
+#include <gcrypt.h>
+#include <pthread.h>
+#include "taler_error_codes.h"
+#include "taler_signatures.h"
+#include "secmod_common.h"
+#include <poll.h>
+
+
+/**
+ * Information we keep per denomination.
+ */
+struct Denomination;
+
+
+/**
+ * One particular denomination key.
+ */
+struct DenominationKey
+{
+
+ /**
+ * Kept in a DLL of the respective denomination. Sorted by anchor time.
+ */
+ struct DenominationKey *next;
+
+ /**
+ * Kept in a DLL of the respective denomination. Sorted by anchor time.
+ */
+ struct DenominationKey *prev;
+
+ /**
+ * Denomination this key belongs to.
+ */
+ struct Denomination *denom;
+
+ /**
+ * Name of the file this key is stored under.
+ */
+ char *filename;
+
+ /**
+ * The private key of the denomination.
+ */
+ struct GNUNET_CRYPTO_RsaPrivateKey *denom_priv;
+
+ /**
+ * The public key of the denomination.
+ */
+ struct GNUNET_CRYPTO_RsaPublicKey *denom_pub;
+
+ /**
+ * Message to transmit to clients to introduce this public key.
+ */
+ struct TALER_CRYPTO_RsaKeyAvailableNotification *an;
+
+ /**
+ * Hash of this denomination's public key.
+ */
+ struct TALER_RsaPubHashP h_rsa;
+
+ /**
+ * Time at which this key is supposed to become valid.
+ */
+ struct GNUNET_TIME_Timestamp anchor;
+
+ /**
+ * Generation when this key was created or revoked.
+ */
+ uint64_t key_gen;
+
+ /**
+ * Reference counter. Counts the number of threads that are
+ * using this key at this time.
+ */
+ unsigned int rc;
+
+ /**
+ * Flag set to true if this key has been purged and the memory
+ * must be freed as soon as @e rc hits zero.
+ */
+ bool purge;
+
+};
+
+
+struct Denomination
+{
+
+ /**
+ * Kept in a DLL. Sorted by #denomination_action_time().
+ */
+ struct Denomination *next;
+
+ /**
+ * Kept in a DLL. Sorted by #denomination_action_time().
+ */
+ struct Denomination *prev;
+
+ /**
+ * Head of DLL of actual keys of this denomination.
+ */
+ struct DenominationKey *keys_head;
+
+ /**
+ * Tail of DLL of actual keys of this denomination.
+ */
+ struct DenominationKey *keys_tail;
+
+ /**
+ * How long can coins be withdrawn (generated)? Should be small
+ * enough to limit how many coins will be signed into existence with
+ * the same key, but large enough to still provide a reasonable
+ * anonymity set.
+ */
+ struct GNUNET_TIME_Relative duration_withdraw;
+
+ /**
+ * What is the configuration section of this denomination type? Also used
+ * for the directory name where the denomination keys are stored.
+ */
+ char *section;
+
+ /**
+ * Length of (new) RSA keys (in bits).
+ */
+ uint32_t rsa_keysize;
+};
+
+
+/**
+ * A semaphore.
+ */
+struct Semaphore
+{
+ /**
+ * Mutex for the semaphore.
+ */
+ pthread_mutex_t mutex;
+
+ /**
+ * Condition variable for the semaphore.
+ */
+ pthread_cond_t cv;
+
+ /**
+ * Counter of the semaphore.
+ */
+ unsigned int ctr;
+};
+
+
+/**
+ * Job in a batch sign request.
+ */
+struct BatchJob;
+
+/**
+ * Handle for a thread that does work in batch signing.
+ */
+struct Worker
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct Worker *prev;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Worker *next;
+
+ /**
+ * Job this worker should do next.
+ */
+ struct BatchJob *job;
+
+ /**
+ * Semaphore to signal the worker that a job is available.
+ */
+ struct Semaphore sem;
+
+ /**
+ * Handle for this thread.
+ */
+ pthread_t pt;
+
+ /**
+ * Set to true if the worker should terminate.
+ */
+ bool do_shutdown;
+};
+
+
+/**
+ * Job in a batch sign request.
+ */
+struct BatchJob
+{
+ /**
+ * Request we are working on.
+ */
+ const struct TALER_CRYPTO_SignRequest *sr;
+
+ /**
+ * Thread doing the work.
+ */
+ struct Worker *worker;
+
+ /**
+ * Result with the signature.
+ */
+ struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
+
+ /**
+ * Semaphore to signal that the job is finished.
+ */
+ struct Semaphore sem;
+
+ /**
+ * Computation status.
+ */
+ enum TALER_ErrorCode ec;
+
+};
+
+
+/**
+ * Head of DLL of workers ready for more work.
+ */
+static struct Worker *worker_head;
+
+/**
+ * Tail of DLL of workers ready for more work.
+ */
+static struct Worker *worker_tail;
+
+/**
+ * Lock for manipulating the worker DLL.
+ */
+static pthread_mutex_t worker_lock;
+
+/**
+ * Total number of workers that were started.
+ */
+static unsigned int workers;
+
+/**
+ * Semaphore used to grab a worker.
+ */
+static struct Semaphore worker_sem;
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * Time when the key update is executed.
+ * Either the actual current time, or a pretended time.
+ */
+static struct GNUNET_TIME_Timestamp now;
+
+/**
+ * The time for the key update, as passed by the user
+ * on the command line.
+ */
+static struct GNUNET_TIME_Timestamp now_tmp;
+
+/**
+ * Where do we store the keys?
+ */
+static char *keydir;
+
+/**
+ * Name of the configuration section prefix to use. Usually either "taler-exchange" or
+ * "donau". The actual configuration section will then be
+ * "$SECTION-secmod-rsa".
+ */
+static char *section;
+
+/**
+ * How much should coin creation (@e duration_withdraw) duration overlap
+ * with the next denomination? Basically, the starting time of two
+ * denominations is always @e duration_withdraw - #overlap_duration apart.
+ */
+static struct GNUNET_TIME_Relative overlap_duration;
+
+/**
+ * How long into the future do we pre-generate keys?
+ */
+static struct GNUNET_TIME_Relative lookahead_sign;
+
+/**
+ * All of our denominations, in a DLL. Sorted?
+ */
+static struct Denomination *denom_head;
+
+/**
+ * All of our denominations, in a DLL. Sorted?
+ */
+static struct Denomination *denom_tail;
+
+/**
+ * Map of hashes of public (RSA) keys to `struct DenominationKey *`
+ * with the respective private keys.
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *keys;
+
+/**
+ * Task run to generate new keys.
+ */
+static struct GNUNET_SCHEDULER_Task *keygen_task;
+
+/**
+ * Lock for the keys queue.
+ */
+static pthread_mutex_t keys_lock;
+
+/**
+ * Current key generation.
+ */
+static uint64_t key_gen;
+
+/**
+ * Number of workers to launch. Note that connections to
+ * exchanges are NOT workers.
+ */
+static unsigned int max_workers = 16;
+
+
+/**
+ * Generate the announcement message for @a dk.
+ *
+ * @param[in,out] dk denomination key to generate the announcement for
+ */
+static void
+generate_response (struct DenominationKey *dk)
+{
+ struct Denomination *denom = dk->denom;
+ size_t nlen = strlen (denom->section) + 1;
+ struct TALER_CRYPTO_RsaKeyAvailableNotification *an;
+ size_t buf_len;
+ void *buf;
+ void *p;
+ size_t tlen;
+
+ buf_len = GNUNET_CRYPTO_rsa_public_key_encode (dk->denom_pub,
+ &buf);
+ GNUNET_assert (buf_len < UINT16_MAX);
+ GNUNET_assert (nlen < UINT16_MAX);
+ tlen = buf_len + nlen + sizeof (*an);
+ GNUNET_assert (tlen < UINT16_MAX);
+ an = GNUNET_malloc (tlen);
+ an->header.size = htons ((uint16_t) tlen);
+ an->header.type = htons (TALER_HELPER_RSA_MT_AVAIL);
+ an->pub_size = htons ((uint16_t) buf_len);
+ an->section_name_len = htons ((uint16_t) nlen);
+ an->anchor_time = GNUNET_TIME_timestamp_hton (dk->anchor);
+ an->duration_withdraw = GNUNET_TIME_relative_hton (denom->duration_withdraw);
+ TALER_exchange_secmod_rsa_sign (&dk->h_rsa,
+ denom->section,
+ dk->anchor,
+ denom->duration_withdraw,
+ &TES_smpriv,
+ &an->secm_sig);
+ an->secm_pub = TES_smpub;
+ p = (void *) &an[1];
+ GNUNET_memcpy (p,
+ buf,
+ buf_len);
+ GNUNET_free (buf);
+ GNUNET_memcpy (p + buf_len,
+ denom->section,
+ nlen);
+ dk->an = an;
+}
+
+
+/**
+ * Do the actual signing work.
+ *
+ * @param h_rsa key to sign with
+ * @param bm blinded message to sign
+ * @param[out] rsa_signaturep set to the RSA signature
+ * @return #TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+do_sign (const struct TALER_RsaPubHashP *h_rsa,
+ const struct GNUNET_CRYPTO_RsaBlindedMessage *bm,
+ struct GNUNET_CRYPTO_RsaSignature **rsa_signaturep)
+{
+ struct DenominationKey *dk;
+ struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ dk = GNUNET_CONTAINER_multihashmap_get (keys,
+ &h_rsa->hash);
+ if (NULL == dk)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing request failed, denomination key %s unknown\n",
+ GNUNET_h2s (&h_rsa->hash));
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time))
+ {
+ /* it is too early */
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing request failed, denomination key %s is not yet valid\n",
+ GNUNET_h2s (&h_rsa->hash));
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received request to sign over %u bytes with key %s\n",
+ (unsigned int) bm->blinded_msg_size,
+ GNUNET_h2s (&h_rsa->hash));
+ GNUNET_assert (dk->rc < UINT_MAX);
+ dk->rc++;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ rsa_signature
+ = GNUNET_CRYPTO_rsa_sign_blinded (dk->denom_priv,
+ bm);
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_assert (dk->rc > 0);
+ dk->rc--;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (NULL == rsa_signature)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Signing request failed, worker failed to produce signature\n");
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sending RSA signature after %s\n",
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+ *rsa_signaturep = rsa_signature;
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Generate error response that signing failed.
+ *
+ * @param client client to send response to
+ * @param ec error code to include
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+fail_sign (struct TES_Client *client,
+ enum TALER_ErrorCode ec)
+{
+ struct TALER_CRYPTO_SignFailure sf = {
+ .header.size = htons (sizeof (sf)),
+ .header.type = htons (TALER_HELPER_RSA_MT_RES_SIGN_FAILURE),
+ .ec = htonl (ec)
+ };
+
+ return TES_transmit (client->csock,
+ &sf.header);
+}
+
+
+/**
+ * Generate signature response.
+ *
+ * @param client client to send response to
+ * @param[in] rsa_signature signature to send, freed by this function
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+send_signature (struct TES_Client *client,
+ struct GNUNET_CRYPTO_RsaSignature *rsa_signature)
+{
+ struct TALER_CRYPTO_SignResponse *sr;
+ void *buf;
+ size_t buf_size;
+ size_t tsize;
+ enum GNUNET_GenericReturnValue ret;
+
+ buf_size = GNUNET_CRYPTO_rsa_signature_encode (rsa_signature,
+ &buf);
+ GNUNET_CRYPTO_rsa_signature_free (rsa_signature);
+ tsize = sizeof (*sr) + buf_size;
+ GNUNET_assert (tsize < UINT16_MAX);
+ sr = GNUNET_malloc (tsize);
+ sr->header.size = htons (tsize);
+ sr->header.type = htons (TALER_HELPER_RSA_MT_RES_SIGNATURE);
+ GNUNET_memcpy (&sr[1],
+ buf,
+ buf_size);
+ GNUNET_free (buf);
+ ret = TES_transmit (client->csock,
+ &sr->header);
+ GNUNET_free (sr);
+ return ret;
+}
+
+
+/**
+ * Handle @a client request @a sr to create signature. Create the
+ * signature using the respective key and return the result to
+ * the client.
+ *
+ * @param client the client making the request
+ * @param sr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_sign_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_SignRequest *sr)
+{
+ struct GNUNET_CRYPTO_RsaBlindedMessage bm = {
+ .blinded_msg = (void *) &sr[1],
+ .blinded_msg_size = ntohs (sr->header.size) - sizeof (*sr)
+ };
+ struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
+ enum TALER_ErrorCode ec;
+
+ ec = do_sign (&sr->h_rsa,
+ &bm,
+ &rsa_signature);
+ if (TALER_EC_NONE != ec)
+ {
+ return fail_sign (client,
+ ec);
+ }
+ return send_signature (client,
+ rsa_signature);
+}
+
+
+/**
+ * Initialize a semaphore @a sem with a value of @a val.
+ *
+ * @param[out] sem semaphore to initialize
+ * @param val initial value of the semaphore
+ */
+static void
+sem_init (struct Semaphore *sem,
+ unsigned int val)
+{
+ GNUNET_assert (0 ==
+ pthread_mutex_init (&sem->mutex,
+ NULL));
+ GNUNET_assert (0 ==
+ pthread_cond_init (&sem->cv,
+ NULL));
+ sem->ctr = val;
+}
+
+
+/**
+ * Decrement semaphore, blocks until this is possible.
+ *
+ * @param[in,out] sem semaphore to decrement
+ */
+static void
+sem_down (struct Semaphore *sem)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex));
+ while (0 == sem->ctr)
+ {
+ pthread_cond_wait (&sem->cv,
+ &sem->mutex);
+ }
+ sem->ctr--;
+ GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex));
+}
+
+
+/**
+ * Increment semaphore, blocks until this is possible.
+ *
+ * @param[in,out] sem semaphore to decrement
+ */
+static void
+sem_up (struct Semaphore *sem)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex));
+ sem->ctr++;
+ GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex));
+ pthread_cond_signal (&sem->cv);
+}
+
+
+/**
+ * Release resources used by @a sem.
+ *
+ * @param[in] sem semaphore to release (except the memory itself)
+ */
+static void
+sem_done (struct Semaphore *sem)
+{
+ GNUNET_break (0 == pthread_cond_destroy (&sem->cv));
+ GNUNET_break (0 == pthread_mutex_destroy (&sem->mutex));
+}
+
+
+/**
+ * Main logic of a worker thread. Grabs work, does it,
+ * grabs more work.
+ *
+ * @param cls a `struct Worker *`
+ * @returns cls
+ */
+static void *
+worker (void *cls)
+{
+ struct Worker *w = cls;
+
+ while (true)
+ {
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ GNUNET_CONTAINER_DLL_insert (worker_head,
+ worker_tail,
+ w);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ sem_up (&worker_sem);
+ sem_down (&w->sem);
+ if (w->do_shutdown)
+ break;
+ {
+ struct BatchJob *bj = w->job;
+ const struct TALER_CRYPTO_SignRequest *sr = bj->sr;
+ struct GNUNET_CRYPTO_RsaBlindedMessage bm = {
+ .blinded_msg = (void *) &sr[1],
+ .blinded_msg_size = ntohs (sr->header.size) - sizeof (*sr)
+ };
+
+ bj->ec = do_sign (&sr->h_rsa,
+ &bm,
+ &bj->rsa_signature);
+ sem_up (&bj->sem);
+ w->job = NULL;
+ }
+ }
+ return w;
+}
+
+
+/**
+ * Start batch job @a bj to sign @a sr.
+ *
+ * @param sr signature request to answer
+ * @param[out] bj job data structure
+ */
+static void
+start_job (const struct TALER_CRYPTO_SignRequest *sr,
+ struct BatchJob *bj)
+{
+ sem_init (&bj->sem,
+ 0);
+ bj->sr = sr;
+ sem_down (&worker_sem);
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ bj->worker = worker_head;
+ GNUNET_CONTAINER_DLL_remove (worker_head,
+ worker_tail,
+ bj->worker);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ bj->worker->job = bj;
+ sem_up (&bj->worker->sem);
+}
+
+
+/**
+ * Finish a job @a bj for a @a client.
+ *
+ * @param client who made the request
+ * @param[in,out] bj job to finish
+ */
+static void
+finish_job (struct TES_Client *client,
+ struct BatchJob *bj)
+{
+ sem_down (&bj->sem);
+ sem_done (&bj->sem);
+ if (TALER_EC_NONE != bj->ec)
+ {
+ fail_sign (client,
+ bj->ec);
+ return;
+ }
+ GNUNET_assert (NULL != bj->rsa_signature);
+ send_signature (client,
+ bj->rsa_signature);
+ bj->rsa_signature = NULL; /* freed in send_signature */
+}
+
+
+/**
+ * Handle @a client request @a sr to create a batch of signature. Creates the
+ * signatures using the respective key and return the results to the client.
+ *
+ * @param client the client making the request
+ * @param bsr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_batch_sign_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_BatchSignRequest *bsr)
+{
+ uint32_t bs = ntohl (bsr->batch_size);
+ uint16_t size = ntohs (bsr->header.size) - sizeof (*bsr);
+ const void *off = (const void *) &bsr[1];
+ unsigned int idx = 0;
+ struct BatchJob jobs[bs];
+ bool failure = false;
+
+ if (bs > TALER_MAX_FRESH_COINS)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ while ( (bs > 0) &&
+ (size > sizeof (struct TALER_CRYPTO_SignRequest)) )
+ {
+ const struct TALER_CRYPTO_SignRequest *sr = off;
+ uint16_t s = ntohs (sr->header.size);
+
+ if (s > size)
+ {
+ failure = true;
+ bs = idx;
+ break;
+ }
+ start_job (sr,
+ &jobs[idx++]);
+ off += s;
+ size -= s;
+ }
+ GNUNET_break_op (0 == size);
+ bs = GNUNET_MIN (bs,
+ idx);
+ for (unsigned int i = 0; i<bs; i++)
+ finish_job (client,
+ &jobs[i]);
+ if (failure)
+ {
+ struct TALER_CRYPTO_SignFailure sf = {
+ .header.size = htons (sizeof (sf)),
+ .header.type = htons (TALER_HELPER_RSA_MT_RES_BATCH_FAILURE),
+ .ec = htonl (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE)
+ };
+
+ GNUNET_break (0);
+ return TES_transmit (client->csock,
+ &sf.header);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Start worker thread for batch processing.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+start_worker (void)
+{
+ struct Worker *w;
+
+ w = GNUNET_new (struct Worker);
+ sem_init (&w->sem,
+ 0);
+ if (0 != pthread_create (&w->pt,
+ NULL,
+ &worker,
+ w))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "pthread_create");
+ GNUNET_free (w);
+ return GNUNET_SYSERR;
+ }
+ workers++;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Stop all worker threads.
+ */
+static void
+stop_workers (void)
+{
+ while (workers > 0)
+ {
+ struct Worker *w;
+ void *result;
+
+ sem_down (&worker_sem);
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ w = worker_head;
+ GNUNET_CONTAINER_DLL_remove (worker_head,
+ worker_tail,
+ w);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ w->do_shutdown = true;
+ sem_up (&w->sem);
+ pthread_join (w->pt,
+ &result);
+ GNUNET_assert (result == w);
+ sem_done (&w->sem);
+ GNUNET_free (w);
+ workers--;
+ }
+}
+
+
+/**
+ * Initialize key material for denomination key @a dk (also on disk).
+ *
+ * @param[in,out] dk denomination key to compute key material for
+ * @param position where in the DLL will the @a dk go
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+setup_key (struct DenominationKey *dk,
+ struct DenominationKey *position)
+{
+ struct Denomination *denom = dk->denom;
+ struct GNUNET_CRYPTO_RsaPrivateKey *priv;
+ struct GNUNET_CRYPTO_RsaPublicKey *pub;
+ size_t buf_size;
+ void *buf;
+
+ priv = GNUNET_CRYPTO_rsa_private_key_create (denom->rsa_keysize);
+ if (NULL == priv)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ pub = GNUNET_CRYPTO_rsa_private_key_get_public (priv);
+ if (NULL == pub)
+ {
+ GNUNET_break (0);
+ GNUNET_CRYPTO_rsa_private_key_free (priv);
+ return GNUNET_SYSERR;
+ }
+ buf_size = GNUNET_CRYPTO_rsa_private_key_encode (priv,
+ &buf);
+ GNUNET_CRYPTO_rsa_public_key_hash (pub,
+ &dk->h_rsa.hash);
+ GNUNET_asprintf (&dk->filename,
+ "%s/%s/%llu",
+ keydir,
+ denom->section,
+ (unsigned long long) (dk->anchor.abs_time.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us));
+ if (GNUNET_OK !=
+ GNUNET_DISK_fn_write (dk->filename,
+ buf,
+ buf_size,
+ GNUNET_DISK_PERM_USER_READ))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "write",
+ dk->filename);
+ GNUNET_free (buf);
+ GNUNET_CRYPTO_rsa_private_key_free (priv);
+ GNUNET_CRYPTO_rsa_public_key_free (pub);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (buf);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Setup fresh private key %s at %s in `%s' (generation #%llu)\n",
+ GNUNET_h2s (&dk->h_rsa.hash),
+ GNUNET_TIME_timestamp2s (dk->anchor),
+ dk->filename,
+ (unsigned long long) key_gen);
+ dk->denom_priv = priv;
+ dk->denom_pub = pub;
+ dk->key_gen = key_gen;
+ generate_response (dk);
+ if (GNUNET_OK !=
+ GNUNET_CONTAINER_multihashmap_put (
+ keys,
+ &dk->h_rsa.hash,
+ dk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Duplicate private key created! Terminating.\n");
+ GNUNET_CRYPTO_rsa_private_key_free (dk->denom_priv);
+ GNUNET_CRYPTO_rsa_public_key_free (dk->denom_pub);
+ GNUNET_free (dk->filename);
+ GNUNET_free (dk->an);
+ GNUNET_free (dk);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (denom->keys_head,
+ denom->keys_tail,
+ position,
+ dk);
+ return GNUNET_OK;
+}
+
+
+/**
+ * The withdraw period of a key @a dk has expired. Purge it.
+ *
+ * @param[in] dk expired denomination key to purge
+ */
+static void
+purge_key (struct DenominationKey *dk)
+{
+ if (dk->purge)
+ return;
+ if (0 != unlink (dk->filename))
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "unlink",
+ dk->filename);
+ GNUNET_free (dk->filename);
+ dk->purge = true;
+ dk->key_gen = key_gen;
+}
+
+
+/**
+ * A @a client informs us that a key has been revoked.
+ * Check if the key is still in use, and if so replace (!)
+ * it with a fresh key.
+ *
+ * @param client the client making the request
+ * @param rr the revocation request
+ */
+static enum GNUNET_GenericReturnValue
+handle_revoke_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_RevokeRequest *rr)
+{
+ struct DenominationKey *dk;
+ struct DenominationKey *ndk;
+ struct Denomination *denom;
+
+ (void) client;
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ dk = GNUNET_CONTAINER_multihashmap_get (keys,
+ &rr->h_rsa.hash);
+ if (NULL == dk)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Revocation request ignored, denomination key %s unknown\n",
+ GNUNET_h2s (&rr->h_rsa.hash));
+ return GNUNET_OK;
+ }
+ if (dk->purge)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Revocation request ignored, denomination key %s already revoked\n",
+ GNUNET_h2s (&rr->h_rsa.hash));
+ return GNUNET_OK;
+ }
+
+ key_gen++;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Revoking key %s, bumping generation to %llu\n",
+ GNUNET_h2s (&rr->h_rsa.hash),
+ (unsigned long long) key_gen);
+ purge_key (dk);
+
+ /* Setup replacement key */
+ denom = dk->denom;
+ ndk = GNUNET_new (struct DenominationKey);
+ ndk->denom = denom;
+ ndk->anchor = dk->anchor;
+ if (GNUNET_OK !=
+ setup_key (ndk,
+ dk))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ TES_wake_clients ();
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle @a hdr message received from @a client.
+ *
+ * @param client the client that received the message
+ * @param hdr message that was received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+rsa_work_dispatch (struct TES_Client *client,
+ const struct GNUNET_MessageHeader *hdr)
+{
+ uint16_t msize = ntohs (hdr->size);
+
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_RSA_MT_REQ_SIGN:
+ if (msize <= sizeof (struct TALER_CRYPTO_SignRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_sign_request (
+ client,
+ (const struct TALER_CRYPTO_SignRequest *) hdr);
+ case TALER_HELPER_RSA_MT_REQ_REVOKE:
+ if (msize != sizeof (struct TALER_CRYPTO_RevokeRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_revoke_request (
+ client,
+ (const struct TALER_CRYPTO_RevokeRequest *) hdr);
+ case TALER_HELPER_RSA_MT_REQ_BATCH_SIGN:
+ if (msize <= sizeof (struct TALER_CRYPTO_BatchSignRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_batch_sign_request (
+ client,
+ (const struct TALER_CRYPTO_BatchSignRequest *) hdr);
+ default:
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+}
+
+
+/**
+ * Send our initial key set to @a client together with the
+ * "sync" terminator.
+ *
+ * @param client the client to inform
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+rsa_client_init (struct TES_Client *client)
+{
+ size_t obs = 0;
+ char *buf;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Initializing new client %p\n",
+ client);
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *dk = denom->keys_head;
+ NULL != dk;
+ dk = dk->next)
+ {
+ GNUNET_assert (obs + ntohs (dk->an->header.size)
+ > obs);
+ obs += ntohs (dk->an->header.size);
+ }
+ }
+ buf = GNUNET_malloc (obs);
+ obs = 0;
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *dk = denom->keys_head;
+ NULL != dk;
+ dk = dk->next)
+ {
+ GNUNET_memcpy (&buf[obs],
+ dk->an,
+ ntohs (dk->an->header.size));
+ GNUNET_assert (obs + ntohs (dk->an->header.size)
+ > obs);
+ obs += ntohs (dk->an->header.size);
+ }
+ }
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (GNUNET_OK !=
+ TES_transmit_raw (client->csock,
+ obs,
+ buf))
+ {
+ GNUNET_free (buf);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client %p must have disconnected\n",
+ client);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (buf);
+ {
+ struct GNUNET_MessageHeader synced = {
+ .type = htons (TALER_HELPER_RSA_SYNCED),
+ .size = htons (sizeof (synced))
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sending RSA SYNCED message to %p\n",
+ client);
+ if (GNUNET_OK !=
+ TES_transmit (client->csock,
+ &synced))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Notify @a client about all changes to the keys since
+ * the last generation known to the @a client.
+ *
+ * @param client the client to notify
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+rsa_update_client_keys (struct TES_Client *client)
+{
+ size_t obs = 0;
+ char *buf;
+ enum GNUNET_GenericReturnValue ret;
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *key = denom->keys_head;
+ NULL != key;
+ key = key->next)
+ {
+ if (key->key_gen <= client->key_gen)
+ continue;
+ if (key->purge)
+ obs += sizeof (struct TALER_CRYPTO_RsaKeyPurgeNotification);
+ else
+ obs += ntohs (key->an->header.size);
+ }
+ }
+ if (0 == obs)
+ {
+ /* nothing to do */
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ return GNUNET_OK;
+ }
+ buf = GNUNET_malloc (obs);
+ obs = 0;
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *key = denom->keys_head;
+ NULL != key;
+ key = key->next)
+ {
+ if (key->key_gen <= client->key_gen)
+ continue;
+ if (key->purge)
+ {
+ struct TALER_CRYPTO_RsaKeyPurgeNotification pn = {
+ .header.type = htons (TALER_HELPER_RSA_MT_PURGE),
+ .header.size = htons (sizeof (pn)),
+ .h_rsa = key->h_rsa
+ };
+
+ GNUNET_memcpy (&buf[obs],
+ &pn,
+ sizeof (pn));
+ GNUNET_assert (obs + sizeof (pn)
+ > obs);
+ obs += sizeof (pn);
+ }
+ else
+ {
+ GNUNET_memcpy (&buf[obs],
+ key->an,
+ ntohs (key->an->header.size));
+ GNUNET_assert (obs + ntohs (key->an->header.size)
+ > obs);
+ obs += ntohs (key->an->header.size);
+ }
+ }
+ }
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ ret = TES_transmit_raw (client->csock,
+ obs,
+ buf);
+ GNUNET_free (buf);
+ return ret;
+}
+
+
+/**
+ * Create a new denomination key (we do not have enough).
+ *
+ * @param denom denomination key to create
+ * @param now current time to use (to get many keys to use the exact same time)
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+create_key (struct Denomination *denom,
+ struct GNUNET_TIME_Timestamp now)
+{
+ struct DenominationKey *dk;
+ struct GNUNET_TIME_Timestamp anchor;
+
+ anchor = now;
+ // FIXME: round down to multiple of 'anchor_round' value from configuration
+ if (NULL != denom->keys_tail)
+ {
+ struct GNUNET_TIME_Absolute abs;
+
+ abs = GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time,
+ GNUNET_TIME_relative_subtract (
+ denom->duration_withdraw,
+ overlap_duration));
+ if (GNUNET_TIME_absolute_cmp (now.abs_time, <, abs))
+ anchor = GNUNET_TIME_absolute_to_timestamp (abs);
+ }
+ dk = GNUNET_new (struct DenominationKey);
+ dk->denom = denom;
+ dk->anchor = anchor;
+ if (GNUNET_OK !=
+ setup_key (dk,
+ denom->keys_tail))
+ {
+ GNUNET_break (0);
+ GNUNET_free (dk);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * At what time does this denomination require its next action?
+ * Basically, the minimum of the withdraw expiration time of the
+ * oldest denomination key, and the withdraw expiration time of
+ * the newest denomination key minus the #lookahead_sign time.
+ *
+ * @param denom denomination to compute action time for
+ */
+static struct GNUNET_TIME_Absolute
+denomination_action_time (const struct Denomination *denom)
+{
+ struct DenominationKey *head = denom->keys_head;
+ struct DenominationKey *tail = denom->keys_tail;
+ struct GNUNET_TIME_Absolute tt;
+
+ if (NULL == head)
+ return GNUNET_TIME_UNIT_ZERO_ABS;
+ tt = GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (tail->anchor.abs_time,
+ denom->duration_withdraw),
+ lookahead_sign),
+ overlap_duration);
+ if (head->rc > 0)
+ return tt; /* head expiration does not count due to rc > 0 */
+ return GNUNET_TIME_absolute_min (
+ GNUNET_TIME_absolute_add (head->anchor.abs_time,
+ denom->duration_withdraw),
+ tt);
+}
+
+
+/**
+ * Create new keys and expire ancient keys of the given denomination @a denom.
+ * Removes the @a denom from the #denom_head DLL and re-insert its at the
+ * correct location sorted by next maintenance activity.
+ *
+ * @param[in,out] denom denomination to update material for
+ * @param now current time to use (to get many keys to use the exact same time)
+ * @param[in,out] wake set to true if we should wake the clients
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+update_keys (struct Denomination *denom,
+ struct GNUNET_TIME_Timestamp now,
+ bool *wake)
+{
+ /* create new denomination keys */
+ if (NULL != denom->keys_tail)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Updating keys of denomination `%s', last key %s valid for another %s\n",
+ denom->section,
+ GNUNET_h2s (&denom->keys_tail->h_rsa.hash),
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (
+ denom->keys_tail->anchor.abs_time,
+ denom->duration_withdraw),
+ overlap_duration)),
+ GNUNET_YES));
+ while ( (NULL == denom->keys_tail) ||
+ GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time,
+ denom->duration_withdraw),
+ lookahead_sign),
+ overlap_duration)) )
+ {
+ if (! *wake)
+ {
+ key_gen++;
+ *wake = true;
+ }
+ if (GNUNET_OK !=
+ create_key (denom,
+ now))
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ }
+ /* remove expired denomination keys */
+ while ( (NULL != denom->keys_head) &&
+ GNUNET_TIME_absolute_is_past
+ (GNUNET_TIME_absolute_add (denom->keys_head->anchor.abs_time,
+ denom->duration_withdraw)) )
+ {
+ struct DenominationKey *key = denom->keys_head;
+ struct DenominationKey *nxt = key->next;
+
+ if (0 != key->rc)
+ break; /* later */
+ GNUNET_CONTAINER_DLL_remove (denom->keys_head,
+ denom->keys_tail,
+ key);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_remove (
+ keys,
+ &key->h_rsa.hash,
+ key));
+ if ( (! key->purge) &&
+ (0 != unlink (key->filename)) )
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "unlink",
+ key->filename);
+ GNUNET_free (key->filename);
+ GNUNET_CRYPTO_rsa_private_key_free (key->denom_priv);
+ GNUNET_CRYPTO_rsa_public_key_free (key->denom_pub);
+ GNUNET_free (key->an);
+ GNUNET_free (key);
+ key = nxt;
+ }
+
+ /* Update position of 'denom' in #denom_head DLL: sort by action time */
+ {
+ struct Denomination *before;
+ struct GNUNET_TIME_Absolute at;
+
+ at = denomination_action_time (denom);
+ GNUNET_CONTAINER_DLL_remove (denom_head,
+ denom_tail,
+ denom);
+ before = NULL;
+ for (struct Denomination *pos = denom_head;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_TIME_absolute_cmp (denomination_action_time (pos), >=, at))
+ break;
+ before = pos;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (denom_head,
+ denom_tail,
+ before,
+ denom);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Task run periodically to expire keys and/or generate fresh ones.
+ *
+ * @param cls NULL
+ */
+static void
+update_denominations (void *cls)
+{
+ struct Denomination *denom;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Timestamp t;
+ bool wake = false;
+
+ (void) cls;
+ keygen_task = NULL;
+ now = GNUNET_TIME_absolute_get ();
+ t = GNUNET_TIME_absolute_to_timestamp (now);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Updating denominations ...\n");
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ do {
+ denom = denom_head;
+ if (GNUNET_OK !=
+ update_keys (denom,
+ t,
+ &wake))
+ return;
+ } while (denom != denom_head);
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Updating denominations finished ...\n");
+ if (wake)
+ TES_wake_clients ();
+ keygen_task = GNUNET_SCHEDULER_add_at (denomination_action_time (denom),
+ &update_denominations,
+ NULL);
+}
+
+
+/**
+ * Parse private key of denomination @a denom in @a buf.
+ *
+ * @param[out] denom denomination of the key
+ * @param filename name of the file we are parsing, for logging
+ * @param buf key material
+ * @param buf_size number of bytes in @a buf
+ */
+static void
+parse_key (struct Denomination *denom,
+ const char *filename,
+ const void *buf,
+ size_t buf_size)
+{
+ struct GNUNET_CRYPTO_RsaPrivateKey *priv;
+ char *anchor_s;
+ char dummy;
+ unsigned long long anchor_ll;
+ struct GNUNET_TIME_Timestamp anchor;
+
+ anchor_s = strrchr (filename,
+ '/');
+ if (NULL == anchor_s)
+ {
+ /* File in a directory without '/' in the name, this makes no sense. */
+ GNUNET_break (0);
+ return;
+ }
+ anchor_s++;
+ if (1 != sscanf (anchor_s,
+ "%llu%c",
+ &anchor_ll,
+ &dummy))
+ {
+ /* Filenames in KEYDIR must ONLY be the anchor time in seconds! */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Filename `%s' invalid for key file, skipping\n",
+ filename);
+ return;
+ }
+ anchor.abs_time.abs_value_us
+ = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us;
+ if (anchor_ll != anchor.abs_time.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us)
+ {
+ /* Integer overflow. Bad, invalid filename. */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Filename `%s' invalid for key file, skipping\n",
+ filename);
+ return;
+ }
+ priv = GNUNET_CRYPTO_rsa_private_key_decode (buf,
+ buf_size);
+ if (NULL == priv)
+ {
+ /* Parser failure. */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "File `%s' is malformed, skipping\n",
+ filename);
+ return;
+ }
+
+ {
+ struct GNUNET_CRYPTO_RsaPublicKey *pub;
+ struct DenominationKey *dk;
+ struct DenominationKey *before;
+
+ pub = GNUNET_CRYPTO_rsa_private_key_get_public (priv);
+ if (NULL == pub)
+ {
+ GNUNET_break (0);
+ GNUNET_CRYPTO_rsa_private_key_free (priv);
+ return;
+ }
+ dk = GNUNET_new (struct DenominationKey);
+ dk->denom_priv = priv;
+ dk->denom = denom;
+ dk->anchor = anchor;
+ dk->filename = GNUNET_strdup (filename);
+ GNUNET_CRYPTO_rsa_public_key_hash (pub,
+ &dk->h_rsa.hash);
+ dk->denom_pub = pub;
+ generate_response (dk);
+ if (GNUNET_OK !=
+ GNUNET_CONTAINER_multihashmap_put (
+ keys,
+ &dk->h_rsa.hash,
+ dk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Duplicate private key %s detected in file `%s'. Skipping.\n",
+ GNUNET_h2s (&dk->h_rsa.hash),
+ filename);
+ GNUNET_CRYPTO_rsa_private_key_free (priv);
+ GNUNET_CRYPTO_rsa_public_key_free (pub);
+ GNUNET_free (dk->an);
+ GNUNET_free (dk);
+ return;
+ }
+ before = NULL;
+ for (struct DenominationKey *pos = denom->keys_head;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_TIME_timestamp_cmp (pos->anchor,
+ >,
+ anchor))
+ break;
+ before = pos;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (denom->keys_head,
+ denom->keys_tail,
+ before,
+ dk);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Imported key %s from `%s'\n",
+ GNUNET_h2s (&dk->h_rsa.hash),
+ filename);
+ }
+}
+
+
+/**
+ * Import a private key from @a filename for the denomination
+ * given in @a cls.
+ *
+ * @param[in,out] cls a `struct Denomiantion`
+ * @param filename name of a file in the directory
+ * @return #GNUNET_OK (always, continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+import_key (void *cls,
+ const char *filename)
+{
+ struct Denomination *denom = cls;
+ struct GNUNET_DISK_FileHandle *fh;
+ struct GNUNET_DISK_MapHandle *map;
+ void *ptr;
+ int fd;
+ struct stat sbuf;
+
+ {
+ struct stat lsbuf;
+
+ if (0 != lstat (filename,
+ &lsbuf))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "lstat",
+ filename);
+ return GNUNET_OK;
+ }
+ if (! S_ISREG (lsbuf.st_mode))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "File `%s' is not a regular file, which is not allowed for private keys!\n",
+ filename);
+ return GNUNET_OK;
+ }
+ }
+
+ fd = open (filename,
+ O_RDONLY | O_CLOEXEC);
+ if (-1 == fd)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "open",
+ filename);
+ return GNUNET_OK;
+ }
+ if (0 != fstat (fd,
+ &sbuf))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "stat",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ if (! S_ISREG (sbuf.st_mode))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "File `%s' is not a regular file, which is not allowed for private keys!\n",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ if (0 != (sbuf.st_mode & (S_IWUSR | S_IRWXG | S_IRWXO)))
+ {
+ /* permission are NOT tight, try to patch them up! */
+ if (0 !=
+ fchmod (fd,
+ S_IRUSR))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "fchmod",
+ filename);
+ /* refuse to use key if file has wrong permissions */
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ }
+ fh = GNUNET_DISK_get_handle_from_int_fd (fd);
+ if (NULL == fh)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "open",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ if (sbuf.st_size > 16 * 1024)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "File `%s' too big to be a private key\n",
+ filename);
+ GNUNET_DISK_file_close (fh);
+ return GNUNET_OK;
+ }
+ ptr = GNUNET_DISK_file_map (fh,
+ &map,
+ GNUNET_DISK_MAP_TYPE_READ,
+ (size_t) sbuf.st_size);
+ if (NULL == ptr)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "mmap",
+ filename);
+ GNUNET_DISK_file_close (fh);
+ return GNUNET_OK;
+ }
+ parse_key (denom,
+ filename,
+ ptr,
+ (size_t) sbuf.st_size);
+ GNUNET_DISK_file_unmap (map);
+ GNUNET_DISK_file_close (fh);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse configuration for denomination type parameters. Also determines
+ * our anchor by looking at the existing denominations of the same type.
+ *
+ * @param cfg configuration to use
+ * @param ct section in the configuration file giving the denomination type parameters
+ * @param[out] denom set to the denomination parameters from the configuration
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if the configuration is invalid
+ */
+static enum GNUNET_GenericReturnValue
+parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *ct,
+ struct Denomination *denom)
+{
+ unsigned long long rsa_keysize;
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-rsa",
+ section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ ct,
+ "DURATION_WITHDRAW",
+ &denom->duration_withdraw))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ ct,
+ "DURATION_WITHDRAW");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_TIME_relative_cmp (overlap_duration,
+ >=,
+ denom->duration_withdraw))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "OVERLAP_DURATION",
+ "Value given must be smaller than value for DURATION_WITHDRAW!");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ ct,
+ "RSA_KEYSIZE",
+ &rsa_keysize))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ ct,
+ "RSA_KEYSIZE");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ if ( (rsa_keysize > 4 * 2048) ||
+ (rsa_keysize < 1024) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ ct,
+ "RSA_KEYSIZE",
+ "Given RSA keysize outside of permitted range [1024,8192]\n");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (secname);
+ denom->rsa_keysize = (unsigned int) rsa_keysize;
+ denom->section = GNUNET_strdup (ct);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #load_denominations.
+ */
+struct LoadContext
+{
+
+ /**
+ * Configuration to use.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Current time to use.
+ */
+ struct GNUNET_TIME_Timestamp t;
+
+ /**
+ * Status, to be set to #GNUNET_SYSERR on failure
+ */
+ enum GNUNET_GenericReturnValue ret;
+};
+
+
+/**
+ * Generate new denomination signing keys for the denomination type of the given @a
+ * denomination_alias.
+ *
+ * @param cls a `struct LoadContext`, with 'ret' to be set to #GNUNET_SYSERR on failure
+ * @param denomination_alias name of the denomination's section in the configuration
+ */
+static void
+load_denominations (void *cls,
+ const char *denomination_alias)
+{
+ struct LoadContext *ctx = cls;
+ struct Denomination *denom;
+ bool wake = true;
+ char *cipher;
+
+ if ( (0 != strncasecmp (denomination_alias,
+ "coin_",
+ strlen ("coin_"))) &&
+ (0 != strncasecmp (denomination_alias,
+ "coin-",
+ strlen ("coin-"))) )
+ return; /* not a denomination type definition */
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ctx->cfg,
+ denomination_alias,
+ "CIPHER",
+ &cipher))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ denomination_alias,
+ "CIPHER");
+ return;
+ }
+ if (0 != strcmp (cipher, "RSA"))
+ {
+ GNUNET_free (cipher);
+ return; /* Ignore denominations of other types than CS */
+ }
+ GNUNET_free (cipher);
+ denom = GNUNET_new (struct Denomination);
+ if (GNUNET_OK !=
+ parse_denomination_cfg (ctx->cfg,
+ denomination_alias,
+ denom))
+ {
+ ctx->ret = GNUNET_SYSERR;
+ GNUNET_free (denom);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Loading keys for denomination %s\n",
+ denom->section);
+ {
+ char *dname;
+
+ GNUNET_asprintf (&dname,
+ "%s/%s",
+ keydir,
+ denom->section);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_directory_create (dname));
+ GNUNET_DISK_directory_scan (dname,
+ &import_key,
+ denom);
+ GNUNET_free (dname);
+ }
+ GNUNET_CONTAINER_DLL_insert (denom_head,
+ denom_tail,
+ denom);
+ update_keys (denom,
+ ctx->t,
+ &wake);
+}
+
+
+/**
+ * Load the various duration values from @a cfg
+ *
+ * @param cfg configuration to use
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-rsa",
+ section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "OVERLAP_DURATION",
+ &overlap_duration))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "OVERLAP_DURATION");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "LOOKAHEAD_SIGN",
+ &lookahead_sign))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "LOOKAHEAD_SIGN");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (secname);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function run on shutdown. Stops the various jobs (nicely).
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+ TES_listen_stop ();
+ if (NULL != keygen_task)
+ {
+ GNUNET_SCHEDULER_cancel (keygen_task);
+ keygen_task = NULL;
+ }
+ stop_workers ();
+ sem_done (&worker_sem);
+}
+
+
+/**
+ * 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)
+{
+ static struct TES_Callbacks cb = {
+ .dispatch = rsa_work_dispatch,
+ .updater = rsa_update_client_keys,
+ .init = rsa_client_init
+ };
+ char *secname;
+
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+ if (GNUNET_TIME_timestamp_cmp (now, !=, now_tmp))
+ {
+ /* The user gave "--now", use it! */
+ now = now_tmp;
+ }
+ else
+ {
+ /* get current time again, we may be timetraveling! */
+ now = GNUNET_TIME_timestamp_get ();
+ }
+ GNUNET_asprintf (&secname,
+ "%s-secmod-rsa",
+ section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ secname,
+ "KEY_DIR",
+ &keydir))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "KEY_DIR");
+ GNUNET_free (secname);
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ GNUNET_free (secname);
+ if (GNUNET_OK !=
+ load_durations (cfg))
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ {
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-rsa",
+ section);
+ global_ret = TES_listen_start (cfg,
+ secname,
+ &cb);
+ GNUNET_free (secname);
+ }
+ if (0 != global_ret)
+ return;
+ sem_init (&worker_sem,
+ 0);
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ if (0 == max_workers)
+ {
+ long lret;
+
+ lret = sysconf (_SC_NPROCESSORS_CONF);
+ if (lret <= 0)
+ lret = 1;
+ max_workers = (unsigned int) lret;
+ }
+
+ for (unsigned int i = 0; i<max_workers; i++)
+ if (GNUNET_OK !=
+ start_worker ())
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ /* Load denominations */
+ keys = GNUNET_CONTAINER_multihashmap_create (65536,
+ GNUNET_YES);
+ {
+ struct LoadContext lc = {
+ .cfg = cfg,
+ .ret = GNUNET_OK,
+ .t = now
+ };
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_CONFIGURATION_iterate_sections (cfg,
+ &load_denominations,
+ &lc);
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (GNUNET_OK != lc.ret)
+ {
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+ if (NULL == denom_head)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No RSA denominations configured\n");
+ TES_wake_clients ();
+ return;
+ }
+ /* start job to keep keys up-to-date; MUST be run before the #listen_task,
+ hence with priority. */
+ keygen_task = GNUNET_SCHEDULER_add_with_priority (
+ GNUNET_SCHEDULER_PRIORITY_URGENT,
+ &update_denominations,
+ NULL);
+}
+
+
+/**
+ * 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_string ('s',
+ "section",
+ "SECTION",
+ "name of the configuration section prefix to use, default is 'taler'",
+ &section),
+ GNUNET_GETOPT_option_timetravel ('T',
+ "timetravel"),
+ GNUNET_GETOPT_option_timestamp ('t',
+ "time",
+ "TIMESTAMP",
+ "pretend it is a different time for the update",
+ &now_tmp),
+ GNUNET_GETOPT_option_uint ('w',
+ "workers",
+ "COUNT",
+ "use COUNT workers for parallel processing of batch requests",
+ &max_workers),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ /* Restrict permissions for the key files that we create. */
+ (void) umask (S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH);
+ section = GNUNET_strdup ("taler-exchange");
+ /* 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 ();
+ now_tmp = now = GNUNET_TIME_timestamp_get ();
+ ret = GNUNET_PROGRAM_run (argc, argv,
+ "taler-exchange-secmod-rsa",
+ "Handle private RSA key operations for a Taler exchange",
+ options,
+ &run,
+ NULL);
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ return global_ret;
+}
diff --git a/src/util/taler-exchange-secmod-rsa.conf b/src/util/taler-exchange-secmod-rsa.conf
new file mode 100644
index 000000000..978c40258
--- /dev/null
+++ b/src/util/taler-exchange-secmod-rsa.conf
@@ -0,0 +1,26 @@
+[taler-exchange-secmod-rsa]
+
+# How long should generated coins overlap in their validity
+# periods. Should be long enough to avoid problems with
+# wallets picking one key and then due to network latency
+# another key being valid. The DURATION_WITHDRAW period
+# must be longer than this value.
+OVERLAP_DURATION = 0 m
+
+# Where do we store the generated private keys.
+KEY_DIR = ${TALER_DATA_HOME}exchange-secmod-rsa/keys
+
+# Where does the helper listen for requests?
+UNIXPATH = ${TALER_RUNTIME_DIR}exchange-secmod-rsa/server.sock
+
+# Directory for clients.
+CLIENT_DIR = ${TALER_RUNTIME_DIR}exchange-secmod-rsa/clients
+
+# Where should the security module store its own private key?
+SM_PRIV_KEY = ${TALER_DATA_HOME}exchange-secmod-rsa/secmod-private-key
+
+# For how long into the future do we pre-generate keys?
+LOOKAHEAD_SIGN = 1 year
+
+# Round down anchor key start date to multiples of this time.
+ANCHOR_ROUND = 1 ms \ No newline at end of file
diff --git a/src/util/taler-exchange-secmod-rsa.h b/src/util/taler-exchange-secmod-rsa.h
new file mode 100644
index 000000000..ffbceb48e
--- /dev/null
+++ b/src/util/taler-exchange-secmod-rsa.h
@@ -0,0 +1,223 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-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/>
+*/
+/**
+ * @file util/taler-exchange-secmod-rsa.h
+ * @brief IPC messages for the RSA crypto helper.
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_SECMOD_RSA_H
+#define TALER_EXCHANGE_SECMOD_RSA_H
+
+#define TALER_HELPER_RSA_MT_PURGE 1
+#define TALER_HELPER_RSA_MT_AVAIL 2
+
+#define TALER_HELPER_RSA_MT_REQ_BATCH_SIGN 3
+#define TALER_HELPER_RSA_MT_REQ_INIT 4
+#define TALER_HELPER_RSA_MT_REQ_SIGN 5
+#define TALER_HELPER_RSA_MT_REQ_REVOKE 6
+
+#define TALER_HELPER_RSA_MT_RES_SIGNATURE 7
+#define TALER_HELPER_RSA_MT_RES_SIGN_FAILURE 8
+#define TALER_HELPER_RSA_MT_RES_BATCH_FAILURE 9
+
+#define TALER_HELPER_RSA_SYNCED 10
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+
+/**
+ * Message sent if a key is available.
+ */
+struct TALER_CRYPTO_RsaKeyAvailableNotification
+{
+ /**
+ * Type is #TALER_HELPER_RSA_MT_AVAIL
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * Number of bytes of the public key.
+ */
+ uint16_t pub_size;
+
+ /**
+ * Number of bytes of the section name.
+ */
+ uint16_t section_name_len;
+
+ /**
+ * When does the key become available?
+ */
+ struct GNUNET_TIME_TimestampNBO anchor_time;
+
+ /**
+ * How long is the key available after @e anchor_time?
+ */
+ struct GNUNET_TIME_RelativeNBO duration_withdraw;
+
+ /**
+ * Public key used to generate the @e sicm_sig.
+ */
+ struct TALER_SecurityModulePublicKeyP secm_pub;
+
+ /**
+ * Signature affirming the announcement, of
+ * purpose #TALER_SIGNATURE_SM_RSA_DENOMINATION_KEY.
+ */
+ struct TALER_SecurityModuleSignatureP secm_sig;
+
+ /* followed by @e pub_size bytes of the RSA public key */
+
+ /* followed by @e section_name bytes of the configuration section name
+ of the denomination of this key */
+
+};
+
+
+/**
+ * Message sent if a key was purged.
+ */
+struct TALER_CRYPTO_RsaKeyPurgeNotification
+{
+ /**
+ * Type is #TALER_HELPER_RSA_MT_PURGE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * For now, always zero.
+ */
+ uint32_t reserved;
+
+ /**
+ * Hash of the public key of the purged RSA key.
+ */
+ struct TALER_RsaPubHashP h_rsa;
+
+};
+
+
+/**
+ * Message sent if a signature is requested.
+ */
+struct TALER_CRYPTO_SignRequest
+{
+ /**
+ * Type is #TALER_HELPER_RSA_MT_REQ_SIGN.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * For now, always zero.
+ */
+ uint32_t reserved;
+
+ /**
+ * Hash of the public key of the RSA key to use for the signature.
+ */
+ struct TALER_RsaPubHashP h_rsa;
+
+ /* followed by message to sign */
+};
+
+
+/**
+ * Message sent if a batch of signatures is requested.
+ */
+struct TALER_CRYPTO_BatchSignRequest
+{
+ /**
+ * Type is #TALER_HELPER_RSA_MT_REQ_BATCH_SIGN.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * Number of signatures to create, in NBO.
+ */
+ uint32_t batch_size;
+
+ /*
+ * Followed by @e batch_size sign requests.
+ */
+
+};
+
+
+/**
+ * Message sent if a key was revoked.
+ */
+struct TALER_CRYPTO_RevokeRequest
+{
+ /**
+ * Type is #TALER_HELPER_RSA_MT_REQ_REVOKE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * For now, always zero.
+ */
+ uint32_t reserved;
+
+ /**
+ * Hash of the public key of the revoked RSA key.
+ */
+ struct TALER_RsaPubHashP h_rsa;
+
+};
+
+
+/**
+ * Message sent if a signature was successfully computed.
+ */
+struct TALER_CRYPTO_SignResponse
+{
+ /**
+ * Type is #TALER_HELPER_RSA_MT_RES_SIGNATURE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * For now, always zero.
+ */
+ uint32_t reserved;
+
+ /* followed by RSA signature */
+};
+
+
+/**
+ * Message sent if signing failed.
+ */
+struct TALER_CRYPTO_SignFailure
+{
+ /**
+ * Type is #TALER_HELPER_RSA_MT_RES_SIGN_FAILURE.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * If available, Taler error code. In NBO.
+ */
+ uint32_t ec;
+
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+#endif
diff --git a/src/util/test_age_restriction.c b/src/util/test_age_restriction.c
new file mode 100644
index 000000000..61499e5e0
--- /dev/null
+++ b/src/util/test_age_restriction.c
@@ -0,0 +1,442 @@
+/*
+ This file is part of TALER
+ (C) 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/>
+*/
+
+/**
+ * @file util/test_age_restriction.c
+ * @brief Tests for age restriction specific logic
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include <gnunet/gnunet_common.h>
+
+/**
+ * Encodes the age mask into a string, like "8:10:12:14:16:18:21"
+ *
+ * @param mask Age mask
+ * @return String representation of the age mask, allocated by GNUNET_malloc.
+ * Can be used as value in the TALER config.
+ */
+char *
+age_mask_to_string (
+ const struct TALER_AgeMask *m)
+{
+ uint32_t bits = m->bits;
+ unsigned int n = 0;
+ char *buf = GNUNET_malloc (32 * 3); // max characters possible
+ char *pos = buf;
+
+ if (NULL == buf)
+ {
+ return buf;
+ }
+
+ while (bits != 0)
+ {
+ bits >>= 1;
+ n++;
+ if (0 == (bits & 1))
+ {
+ continue;
+ }
+
+ if (n > 9)
+ {
+ *(pos++) = '0' + n / 10;
+ }
+ *(pos++) = '0' + n % 10;
+
+ if (0 != (bits >> 1))
+ {
+ *(pos++) = ':';
+ }
+ }
+ return buf;
+}
+
+
+enum GNUNET_GenericReturnValue
+test_groups (void)
+{
+ struct
+ {
+ uint32_t bits;
+ uint8_t group[33];
+ } test[] = {
+ {
+ .bits =
+ 1 | 1 << 5 | 1 << 13 | 1 << 23,
+
+ .group = { 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }
+
+
+ },
+ {
+ .bits =
+ 1 | 1 << 8 | 1 << 10 | 1 << 12 | 1 << 14 | 1 << 16 | 1 << 18 | 1 << 21,
+ .group = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1,
+ 2, 2,
+ 3, 3,
+ 4, 4,
+ 5, 5,
+ 6, 6, 6,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}
+
+
+ }
+ };
+
+ for (uint8_t t = 0; t < sizeof(test) / sizeof(test[0]); t++)
+ {
+ struct TALER_AgeMask mask = {.bits = test[t].bits};
+
+ for (uint8_t i = 0; i < 32; i++)
+ {
+ uint8_t r = TALER_get_age_group (&mask, i);
+ char *m = age_mask_to_string (&mask);
+
+ printf ("TALER_get_age_group(%s, %2d) = %d vs %d (exp)\n",
+ m,
+ i,
+ r,
+ test[t].group[i]);
+
+ if (test[t].group[i] != r)
+ return GNUNET_SYSERR;
+
+ GNUNET_free (m);
+ }
+ }
+
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+test_dates (void)
+{
+ struct TALER_AgeMask mask = {
+ .bits = 1 | 1 << 5 | 1 << 9 | 1 << 13 | 1 << 17 | 1 << 21
+ };
+ struct
+ {
+ char *date;
+ uint32_t expected;
+ enum GNUNET_GenericReturnValue ret;
+ }
+ test [] = {
+ {.date = "abcd-00-00", .expected = 0, .ret = GNUNET_SYSERR},
+ {.date = "1900-00-01", .expected = 0, .ret = GNUNET_SYSERR},
+ {.date = "19000001", .expected = 0, .ret = GNUNET_SYSERR},
+ {.date = "2001-33-05", .expected = 0, .ret = GNUNET_SYSERR},
+ {.date = "2001-33-35", .expected = 0, .ret = GNUNET_SYSERR},
+
+ {.date = "1900-00-00", .expected = 0, .ret = GNUNET_OK},
+ {.date = "2001-00-00", .expected = 0, .ret = GNUNET_OK},
+ {.date = "2001-03-00", .expected = 0, .ret = GNUNET_OK},
+ {.date = "2001-03-05", .expected = 0, .ret = GNUNET_OK},
+
+ /* These dates should be far enough for the near future so that
+ * the expected values are correct. Will need adjustment in 2044 :) */
+ {.date = "2022-11-26", .expected = 19322, .ret = GNUNET_OK },
+ {.date = "2022-11-27", .expected = 19323, .ret = GNUNET_OK },
+ {.date = "2023-06-26", .expected = 19534, .ret = GNUNET_OK },
+ {.date = "2023-06-01", .expected = 19509, .ret = GNUNET_OK },
+ {.date = "2023-06-00", .expected = 19509, .ret = GNUNET_OK },
+ {.date = "2023-01-01", .expected = 19358, .ret = GNUNET_OK },
+ {.date = "2023-00-00", .expected = 19358, .ret = GNUNET_OK },
+
+ /* Special case: .date == NULL meands birthday == current date, which
+ * should be 21 years in the future. We will set these values below in the
+ * loop */
+ {.date = NULL, .expected = 0, .ret = GNUNET_OK },
+ };
+ char buf[256] = {0};
+
+ for (uint8_t t = 0; t < sizeof(test) / sizeof(test[0]); t++)
+ {
+ uint32_t d;
+ enum GNUNET_GenericReturnValue ret;
+ char *date = test[t].date;
+
+ if (NULL == test[t].date)
+ {
+ /* Special case: We set .date to the current date. */
+ time_t tn;
+ struct tm now;
+
+ time (&tn);
+ localtime_r (&tn, &now);
+ strftime (buf, sizeof(buf), "%Y-%m-%d", &now);
+ date = &buf[0];
+
+ /* The expected value is the number of days since 1970-01-01,
+ * counted simplistically */
+ test[t].expected = timegm (&now) / 60 / 60 / 24;
+ }
+
+ ret = TALER_parse_coarse_date (date,
+ &mask,
+ &d);
+ if (ret != test[t].ret)
+ {
+ printf (
+ "dates[%d] for date `%s` expected parser to return: %d, got: %d\n",
+ t, date, test[t].ret, ret);
+ return GNUNET_SYSERR;
+ }
+
+ if (ret == GNUNET_SYSERR)
+ continue;
+
+ if (d != test[t].expected)
+ {
+ printf (
+ "dates[%d] for date `%s` expected value %d, but got %d\n",
+ t, date, test[t].expected, d);
+ return GNUNET_SYSERR;
+ }
+
+ printf ("dates[%d] for date `%s` got expected value %d\n",
+ t, date, d);
+ }
+
+ printf ("done with dates\n");
+
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+test_lowest (void)
+{
+ struct TALER_AgeMask mask = {
+ .bits = 1 | 1 << 5 | 1 << 9 | 1 << 13 | 1 << 17 | 1 << 21
+ };
+
+ struct { uint8_t age; uint8_t expected; }
+ test [] = {
+ {.age = 1, .expected = 0 },
+ {.age = 2, .expected = 0 },
+ {.age = 3, .expected = 0 },
+ {.age = 4, .expected = 0 },
+ {.age = 5, .expected = 5 },
+ {.age = 6, .expected = 5 },
+ {.age = 7, .expected = 5 },
+ {.age = 8, .expected = 5 },
+ {.age = 9, .expected = 9 },
+ {.age = 10, .expected = 9 },
+ {.age = 11, .expected = 9 },
+ {.age = 12, .expected = 9 },
+ {.age = 13, .expected = 13 },
+ {.age = 14, .expected = 13 },
+ {.age = 15, .expected = 13 },
+ {.age = 16, .expected = 13 },
+ {.age = 17, .expected = 17 },
+ {.age = 18, .expected = 17 },
+ {.age = 19, .expected = 17 },
+ {.age = 20, .expected = 17 },
+ {.age = 21, .expected = 21 },
+ {.age = 22, .expected = 21 },
+ };
+
+ for (uint8_t n = 0; n < sizeof(test) / sizeof(test[0]); n++)
+ {
+ uint8_t l = TALER_get_lowest_age (&mask, test[n].age);
+ printf ("lowest[%d] for age %d, expected lowest: %d, got: %d\n",
+ n, test[n].age, test[n].expected, l);
+ if (test[n].expected != l)
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+test_adult (void)
+{
+ struct { struct TALER_AgeMask mask; uint8_t expected; }
+ test[] = {
+ { .mask = {.bits = 1 | 1 << 2},
+ .expected = 2 },
+ { .mask = {.bits = 1 | 1 << 2 | 1 << 3},
+ .expected = 3 },
+ { .mask = {.bits = 1 | 1 << 3},
+ .expected = 3 },
+ { .mask = {.bits = 1 | 1 << 22},
+ .expected = 22 },
+ { .mask = {.bits = 1 | 1 << 10 | 1 << 16 | 1 << 22},
+ .expected = 22 },
+ };
+ for (uint8_t n = 0; n < sizeof(test) / sizeof(test[0]); n++)
+ {
+ uint8_t l = TALER_adult_age (&test[n].mask);
+ printf ("adult[%d] for mask %s, expected: %d, got: %d\n",
+ n, TALER_age_mask_to_string (&test[n].mask), test[n].expected, l);
+ if (test[n].expected != l)
+ return GNUNET_SYSERR;
+ }
+ printf ("done with adult\n");
+
+ return GNUNET_OK;
+}
+
+
+static struct TALER_AgeMask age_mask = {
+ .bits = 1 | 1 << 8 | 1 << 10 | 1 << 12 | 1 << 14 | 1 << 16 | 1 << 18 | 1 << 21
+};
+
+enum GNUNET_GenericReturnValue
+test_attestation (void)
+{
+ uint8_t age;
+ for (age = 0; age < 33; age++)
+ {
+ enum GNUNET_GenericReturnValue ret;
+ struct TALER_AgeCommitmentProof acp[3] = {0};
+ struct TALER_AgeAttestation at = {0};
+ uint8_t age_group = TALER_get_age_group (&age_mask, age);
+ struct GNUNET_HashCode seed;
+
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &seed,
+ sizeof(seed));
+
+ TALER_age_restriction_commit (&age_mask,
+ age,
+ &seed,
+ &acp[0]);
+ printf (
+ "commit(age:%d); proof.num: %ld; age_group: %d\n",
+ age,
+ acp[0].proof.num,
+ age_group);
+
+ /* Also derive two more commitments right away */
+ for (uint8_t i = 0; i<2; i++)
+ {
+ struct GNUNET_HashCode salt;
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &salt,
+ sizeof (salt));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_age_commitment_derive (&acp[i],
+ &salt,
+ &acp[i + 1]));
+ }
+
+ for (uint8_t i = 0; i < 3; i++)
+ {
+ for (uint8_t min = 0; min < 22; min++)
+ {
+ uint8_t min_group = TALER_get_age_group (&age_mask, min);
+
+ ret = TALER_age_commitment_attest (&acp[i],
+ min,
+ &at);
+
+ printf (
+ "[%s]: attest(min:%d, age:%d) == %d; age_group: %d, min_group: %d\n",
+ i == 0 ? "commit" : "derive",
+ min,
+ age,
+ ret,
+ age_group,
+ min_group);
+
+ if (min_group <= age_group &&
+ GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ ret = GNUNET_SYSERR;
+ }
+
+ if (min_group > age_group &&
+ GNUNET_NO != ret)
+ {
+ GNUNET_break (0);
+ ret = GNUNET_SYSERR;
+ }
+
+ if (min_group > age_group)
+ continue;
+
+ ret = TALER_age_commitment_verify (&acp[i].commitment,
+ min,
+ &at);
+
+ printf (
+ "[%s]: verify(min:%d, age:%d) == %d; age_group:%d, min_group: %d\n",
+ i == 0 ? "commit" : "derive",
+ min,
+ age,
+ ret,
+ age_group,
+ min_group);
+
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ break;
+ }
+ }
+
+ TALER_age_commitment_proof_free (&acp[i]);
+ }
+
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_break (0);
+ return ret;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ (void) argc;
+ (void) argv;
+ GNUNET_log_setup ("test-age-restriction",
+ "INFO",
+ NULL);
+ if (GNUNET_OK != test_groups ())
+ return 1;
+ if (GNUNET_OK != test_lowest ())
+ return 2;
+ if (GNUNET_OK != test_attestation ())
+ {
+ GNUNET_break (0);
+ return 3;
+ }
+ if (GNUNET_OK != test_dates ())
+ return 4;
+ if (GNUNET_OK != test_adult ())
+ return 5;
+ return 0;
+}
+
+
+/* end of test_age_restriction.c */
diff --git a/src/util/test_amount.c b/src/util/test_amount.c
index 03f15cb88..57d73b14f 100644
--- a/src/util/test_amount.c
+++ b/src/util/test_amount.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2015 Taler Systems SA
+ (C) 2015, 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
@@ -21,7 +21,6 @@
*/
#include "platform.h"
#include "taler_util.h"
-#include "taler_amount_lib.h"
int
@@ -79,31 +78,31 @@ main (int argc,
/* test conversion with leading zero in fraction */
GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount ("eur:0.02",
+ TALER_string_to_amount ("EUR:0.02",
&a2));
- GNUNET_assert (0 == strcasecmp ("eur",
+ GNUNET_assert (0 == strcasecmp ("EUR",
a2.currency));
GNUNET_assert (0 == a2.value);
GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 2 == a2.fraction);
c = TALER_amount_to_string (&a2);
- GNUNET_assert (0 == strcmp ("eur:0.02",
- c));
+ GNUNET_assert (0 == strcasecmp ("EUR:0.02",
+ c));
GNUNET_free (c);
/* test conversion with leading space and with fraction */
GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (" eur:4.12",
+ TALER_string_to_amount (" EUR:4.12",
&a2));
- GNUNET_assert (0 == strcasecmp ("eur",
+ GNUNET_assert (0 == strcasecmp ("EUR",
a2.currency));
GNUNET_assert (4 == a2.value);
GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 12 == a2.fraction);
/* test use of local currency */
GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (" *LOCAL:4444.1000",
+ TALER_string_to_amount (" LOCAL:4444.1000",
&a3));
- GNUNET_assert (0 == strcasecmp ("*LOCAL",
+ GNUNET_assert (0 == strcasecmp ("LOCAL",
a3.currency));
GNUNET_assert (4444 == a3.value);
GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 10 == a3.fraction);
@@ -117,7 +116,7 @@ main (int argc,
&a2));
/* test subtraction failure (currency mismatch) */
- GNUNET_assert (GNUNET_SYSERR ==
+ GNUNET_assert (TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE ==
TALER_amount_subtract (&a3,
&a3,
&a2));
@@ -125,7 +124,7 @@ main (int argc,
TALER_amount_normalize (&a3));
/* test subtraction failure (negative result) */
- GNUNET_assert (GNUNET_SYSERR ==
+ GNUNET_assert (TALER_AAR_INVALID_NEGATIVE_RESULT ==
TALER_amount_subtract (&a3,
&a1,
&a2));
@@ -133,11 +132,11 @@ main (int argc,
TALER_amount_normalize (&a3));
/* test subtraction success cases */
- GNUNET_assert (GNUNET_YES ==
+ GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
TALER_amount_subtract (&a3,
&a2,
&a1));
- GNUNET_assert (GNUNET_NO ==
+ GNUNET_assert (TALER_AAR_RESULT_ZERO ==
TALER_amount_subtract (&a3,
&a1,
&a1));
@@ -147,7 +146,7 @@ main (int argc,
TALER_amount_normalize (&a3));
/* test addition success */
- GNUNET_assert (GNUNET_OK ==
+ GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
TALER_amount_add (&a3,
&a3,
&a2));
@@ -186,21 +185,27 @@ main (int argc,
/* test addition with overflow */
a1.fraction = TALER_AMOUNT_FRAC_BASE - 1;
- a1.value = UINT64_MAX - 5;
+ a1.value = TALER_AMOUNT_MAX_VALUE - 5;
a2.fraction = 2;
a2.value = 5;
- GNUNET_assert (GNUNET_SYSERR ==
- TALER_amount_add (&a3, &a1, &a2));
+ GNUNET_assert (TALER_AAR_INVALID_RESULT_OVERFLOW ==
+ TALER_amount_add (&a3,
+ &a1,
+ &a2));
/* test addition with underflow on fraction */
a1.fraction = 1;
- a1.value = UINT64_MAX;
+ a1.value = TALER_AMOUNT_MAX_VALUE;
a2.fraction = 2;
a2.value = 0;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_subtract (&a3, &a1, &a2));
- GNUNET_assert (UINT64_MAX - 1 == a3.value);
- GNUNET_assert (TALER_AMOUNT_FRAC_BASE - 1 == a3.fraction);
+ GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
+ TALER_amount_subtract (&a3,
+ &a1,
+ &a2));
+ GNUNET_assert (TALER_AMOUNT_MAX_VALUE - 1 ==
+ a3.value);
+ GNUNET_assert (TALER_AMOUNT_FRAC_BASE - 1 ==
+ a3.fraction);
/* test division */
GNUNET_assert (GNUNET_OK ==
@@ -288,6 +293,53 @@ main (int argc,
&r));
GNUNET_assert (0 == TALER_amount_cmp (&a1,
&a2));
+
+ /* test multiplication */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("BTC:0",
+ &a1));
+ GNUNET_assert (TALER_AAR_RESULT_ZERO ==
+ TALER_amount_multiply (&a2,
+ &a1,
+ 42));
+ GNUNET_assert (0 == TALER_amount_cmp (&a1,
+ &a2));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("BTC:5.001",
+ &a1));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("BTC:5001",
+ &r));
+ GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
+ TALER_amount_multiply (&a2,
+ &a1,
+ 1000));
+ GNUNET_assert (0 == TALER_amount_cmp (&r,
+ &a2));
+ GNUNET_assert (1000 ==
+ TALER_amount_divide2 (&a2,
+ &a1));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("BTC:5006.00099999",
+ &r));
+ GNUNET_assert (1000 ==
+ TALER_amount_divide2 (&r,
+ &a1));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("BTC:5000.99999999",
+ &r));
+ GNUNET_assert (999 ==
+ TALER_amount_divide2 (&r,
+ &a1));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("BTC:0",
+ &a1));
+ GNUNET_assert (INT_MAX ==
+ TALER_amount_divide2 (&a2,
+ &a1));
+ GNUNET_assert (0 ==
+ TALER_amount_divide2 (&a1,
+ &a2));
return 0;
}
diff --git a/src/util/test_conversion.c b/src/util/test_conversion.c
new file mode 100644
index 000000000..00cb35e72
--- /dev/null
+++ b/src/util/test_conversion.c
@@ -0,0 +1,149 @@
+/*
+ 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 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 util/test_conversion.c
+ * @brief Tests for conversion logic
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include <gnunet/gnunet_json_lib.h>
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * Handle to our helper.
+ */
+static struct TALER_JSON_ExternalConversion *ec;
+
+
+/**
+ * Type of a callback that receives a JSON @a result.
+ *
+ * @param cls closure
+ * @param status_type how did the process die
+ * @apram code termination status code from the process
+ * @param result some JSON result, NULL if we failed to get an JSON output
+ */
+static void
+conv_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType status_type,
+ unsigned long code,
+ const json_t *result)
+{
+ json_t *expect;
+
+ (void) cls;
+ (void) status_type;
+ ec = NULL;
+ global_ret = 3;
+ if (42 != code)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected return value from helper: %u\n",
+ (unsigned int) code);
+ return;
+ }
+ expect = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("foo",
+ "arg")
+ );
+ if (1 == json_equal (expect,
+ result))
+ {
+ global_ret = 0;
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected JSON result\n");
+ json_dumpf (result,
+ stderr,
+ JSON_INDENT (2));
+ global_ret = 4;
+ }
+ json_decref (expect);
+}
+
+
+/**
+ * Function called on shutdown/CTRL-C.
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+ if (NULL != ec)
+ {
+ GNUNET_break (0);
+ global_ret = 2;
+ TALER_JSON_external_conversion_stop (ec);
+ ec = NULL;
+ }
+}
+
+
+/**
+ * Main test function.
+ *
+ * @param cls NULL
+ */
+static void
+run (void *cls)
+{
+ json_t *input;
+
+ (void) cls;
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ input = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("key",
+ "foo")
+ );
+ ec = TALER_JSON_external_conversion_start (input,
+ &conv_cb,
+ NULL,
+ "./test_conversion.sh",
+ "test_conversion.sh",
+ "arg",
+ NULL);
+ json_decref (input);
+ GNUNET_assert (NULL != ec);
+}
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ (void) argc;
+ (void) argv;
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ GNUNET_log_setup ("test-conversion",
+ "WARNING",
+ NULL);
+ GNUNET_OS_init (TALER_project_data_default ());
+ global_ret = 1;
+ GNUNET_SCHEDULER_run (&run,
+ NULL);
+ return global_ret;
+}
diff --git a/src/util/test_conversion.sh b/src/util/test_conversion.sh
new file mode 100755
index 000000000..26e1a36d8
--- /dev/null
+++ b/src/util/test_conversion.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+KEY=$(jq -r .key)
+echo -n "{\"$KEY\":\"$1\"}"
+exit 42
diff --git a/src/util/test_crypto.c b/src/util/test_crypto.c
index de10e567d..2a2090952 100644
--- a/src/util/test_crypto.c
+++ b/src/util/test_crypto.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2015 Taler Systems SA
+ (C) 2015, 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
@@ -21,7 +21,6 @@
*/
#include "platform.h"
#include "taler_util.h"
-#include "taler_crypto_lib.h"
/**
@@ -32,25 +31,32 @@
static int
test_high_level (void)
{
- struct GNUNET_CRYPTO_EddsaPrivateKey *pk;
struct TALER_CoinSpendPrivateKeyP coin_priv;
struct TALER_CoinSpendPublicKeyP coin_pub;
- struct GNUNET_CRYPTO_EcdhePrivateKey *pk2;
struct TALER_TransferPrivateKeyP trans_priv;
struct TALER_TransferPublicKeyP trans_pub;
struct TALER_TransferSecretP secret;
struct TALER_TransferSecretP secret2;
- struct TALER_PlanchetSecretsP fc1;
- struct TALER_PlanchetSecretsP fc2;
+ union GNUNET_CRYPTO_BlindingSecretP bks1;
+ union GNUNET_CRYPTO_BlindingSecretP bks2;
+ struct TALER_CoinSpendPrivateKeyP coin_priv1;
+ struct TALER_CoinSpendPrivateKeyP coin_priv2;
+ struct TALER_PlanchetMasterSecretP ps1;
+ struct TALER_PlanchetMasterSecretP ps2;
+ struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = GNUNET_CRYPTO_BSA_RSA
+ };
+ struct TALER_ExchangeWithdrawValues alg1 = {
+ .blinding_inputs = &bi
+ };
+ struct TALER_ExchangeWithdrawValues alg2 = {
+ .blinding_inputs = &bi
+ };
- pk = GNUNET_CRYPTO_eddsa_key_create ();
- coin_priv.eddsa_priv = *pk;
- GNUNET_free (pk);
+ GNUNET_CRYPTO_eddsa_key_create (&coin_priv.eddsa_priv);
GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
&coin_pub.eddsa_pub);
- pk2 = GNUNET_CRYPTO_ecdhe_key_create ();
- trans_priv.ecdhe_priv = *pk2;
- GNUNET_free (pk2);
+ GNUNET_CRYPTO_ecdhe_key_create (&trans_priv.ecdhe_priv);
GNUNET_CRYPTO_ecdhe_key_get_public (&trans_priv.ecdhe_priv,
&trans_pub.ecdhe_pub);
TALER_link_derive_transfer_secret (&coin_priv,
@@ -68,19 +74,42 @@ test_high_level (void)
GNUNET_assert (0 ==
GNUNET_memcmp (&secret,
&secret2));
- TALER_planchet_setup_refresh (&secret,
- 0,
- &fc1);
- TALER_planchet_setup_refresh (&secret,
- 1,
- &fc2);
+ TALER_transfer_secret_to_planchet_secret (&secret,
+ 0,
+ &ps1);
+ TALER_planchet_setup_coin_priv (&ps1,
+ &alg1,
+ &coin_priv1);
+ TALER_planchet_blinding_secret_create (&ps1,
+ &alg1,
+ &bks1);
+ TALER_transfer_secret_to_planchet_secret (&secret,
+ 1,
+ &ps2);
+ TALER_planchet_setup_coin_priv (&ps2,
+ &alg2,
+ &coin_priv2);
+ TALER_planchet_blinding_secret_create (&ps2,
+ &alg2,
+ &bks2);
GNUNET_assert (0 !=
- GNUNET_memcmp (&fc1,
- &fc2));
+ GNUNET_memcmp (&ps1,
+ &ps2));
+ GNUNET_assert (0 !=
+ GNUNET_memcmp (&coin_priv1,
+ &coin_priv2));
+ GNUNET_assert (0 !=
+ GNUNET_memcmp (&bks1,
+ &bks2));
return 0;
}
+static struct TALER_AgeMask age_mask = {
+ .bits = 1 | 1 << 8 | 1 << 10 | 1 << 12
+ | 1 << 14 | 1 << 16 | 1 << 18 | 1 << 21
+};
+
/**
* Test the basic planchet functionality of creating a fresh planchet
* and extracting the respective signature.
@@ -88,37 +117,398 @@ test_high_level (void)
* @return 0 on success
*/
static int
-test_planchets (void)
+test_planchets_rsa (uint8_t age)
{
- struct TALER_PlanchetSecretsP ps;
+ struct TALER_PlanchetMasterSecretP ps;
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
struct TALER_DenominationPrivateKey dk_priv;
struct TALER_DenominationPublicKey dk_pub;
+ const struct TALER_ExchangeWithdrawValues *alg_values;
struct TALER_PlanchetDetail pd;
- struct GNUNET_CRYPTO_RsaSignature *blind_sig;
+ struct TALER_BlindedDenominationSignature blind_sig;
struct TALER_FreshCoin coin;
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_AgeCommitmentHash *ach = NULL;
+ struct TALER_AgeCommitmentHash ah = {0};
+
+ alg_values = TALER_denom_ewv_rsa_singleton ();
+ if (0 < age)
+ {
+ struct TALER_AgeCommitmentProof acp;
+ struct GNUNET_HashCode seed;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &seed,
+ sizeof(seed));
+ TALER_age_restriction_commit (&age_mask,
+ age,
+ &seed,
+ &acp);
+ TALER_age_commitment_hash (&acp.commitment,
+ &ah);
+ ach = &ah;
+ TALER_age_commitment_proof_free (&acp);
+ }
- dk_priv.rsa_private_key = GNUNET_CRYPTO_rsa_private_key_create (1024);
- dk_pub.rsa_public_key = GNUNET_CRYPTO_rsa_private_key_get_public (
- dk_priv.rsa_private_key);
- TALER_planchet_setup_random (&ps);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ &ps,
+ sizeof (ps));
+ GNUNET_log_skip (1, GNUNET_YES);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_denom_priv_create (&dk_priv,
+ &dk_pub,
+ GNUNET_CRYPTO_BSA_INVALID));
+ GNUNET_log_skip (1, GNUNET_YES);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_denom_priv_create (&dk_priv,
+ &dk_pub,
+ 42));
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_priv_create (&dk_priv,
+ &dk_pub,
+ GNUNET_CRYPTO_BSA_RSA,
+ 1024));
+ TALER_planchet_setup_coin_priv (&ps,
+ alg_values,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (&ps,
+ alg_values,
+ &bks);
GNUNET_assert (GNUNET_OK ==
TALER_planchet_prepare (&dk_pub,
- &ps,
+ alg_values,
+ &bks,
+ NULL,
+ &coin_priv,
+ ach,
+ &c_hash,
&pd));
- blind_sig = GNUNET_CRYPTO_rsa_sign_blinded (dk_priv.rsa_private_key,
- pd.coin_ev,
- pd.coin_ev_size);
- GNUNET_assert (NULL != blind_sig);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sign_blinded (&blind_sig,
+ &dk_priv,
+ false,
+ &pd.blinded_planchet));
+ TALER_planchet_detail_free (&pd);
GNUNET_assert (GNUNET_OK ==
TALER_planchet_to_coin (&dk_pub,
- blind_sig,
- &ps,
- &pd.c_hash,
+ &blind_sig,
+ &bks,
+ &coin_priv,
+ ach,
+ &c_hash,
+ alg_values,
&coin));
- GNUNET_CRYPTO_rsa_signature_free (blind_sig);
- GNUNET_CRYPTO_rsa_signature_free (coin.sig.rsa_signature);
- GNUNET_CRYPTO_rsa_private_key_free (dk_priv.rsa_private_key);
- GNUNET_CRYPTO_rsa_public_key_free (dk_pub.rsa_public_key);
+ TALER_blinded_denom_sig_free (&blind_sig);
+ TALER_denom_sig_free (&coin.sig);
+ TALER_denom_priv_free (&dk_priv);
+ TALER_denom_pub_free (&dk_pub);
+ return 0;
+}
+
+
+/**
+ * Test the basic planchet functionality of creating a fresh planchet with CS denomination
+ * and extracting the respective signature.
+ *
+ * @return 0 on success
+ */
+static int
+test_planchets_cs (uint8_t age)
+{
+ struct TALER_PlanchetMasterSecretP ps;
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct TALER_DenominationPrivateKey dk_priv;
+ struct TALER_DenominationPublicKey dk_pub;
+ struct TALER_PlanchetDetail pd;
+ struct TALER_CoinPubHashP c_hash;
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+ struct TALER_BlindedDenominationSignature blind_sig;
+ struct TALER_FreshCoin coin;
+ struct TALER_ExchangeWithdrawValues alg_values;
+ struct TALER_AgeCommitmentHash *ach = NULL;
+
+ if (0 < age)
+ {
+ struct TALER_AgeCommitmentHash ah = {0};
+ struct TALER_AgeCommitmentProof acp;
+ struct GNUNET_HashCode seed;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &seed,
+ sizeof(seed));
+ TALER_age_restriction_commit (&age_mask,
+ age,
+ &seed,
+ &acp);
+ TALER_age_commitment_hash (&acp.commitment,
+ &ah);
+ ach = &ah;
+ TALER_age_commitment_proof_free (&acp);
+ }
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ &ps,
+ sizeof (ps));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_priv_create (&dk_priv,
+ &dk_pub,
+ GNUNET_CRYPTO_BSA_CS));
+ TALER_cs_withdraw_nonce_derive (
+ &ps,
+ &nonce.cs_nonce);
+ // FIXME: define Taler abstraction for this:
+ alg_values.blinding_inputs
+ = GNUNET_CRYPTO_get_blinding_input_values (dk_priv.bsign_priv_key,
+ &nonce,
+ "rw");
+ TALER_denom_pub_hash (&dk_pub,
+ &pd.denom_pub_hash);
+ TALER_planchet_setup_coin_priv (&ps,
+ &alg_values,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (&ps,
+ &alg_values,
+ &bks);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_planchet_prepare (&dk_pub,
+ &alg_values,
+ &bks,
+ &nonce,
+ &coin_priv,
+ ach,
+ &c_hash,
+ &pd));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sign_blinded (&blind_sig,
+ &dk_priv,
+ false,
+ &pd.blinded_planchet));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_planchet_to_coin (&dk_pub,
+ &blind_sig,
+ &bks,
+ &coin_priv,
+ ach,
+ &c_hash,
+ &alg_values,
+ &coin));
+ TALER_blinded_denom_sig_free (&blind_sig);
+ TALER_denom_sig_free (&coin.sig);
+ TALER_denom_priv_free (&dk_priv);
+ TALER_denom_pub_free (&dk_pub);
+ return 0;
+}
+
+
+/**
+ * Test the basic planchet functionality of creating a fresh planchet
+ * and extracting the respective signature.
+ * Calls test_planchets_rsa and test_planchets_cs
+ *
+ * @return 0 on success
+ */
+static int
+test_planchets (uint8_t age)
+{
+ if (0 != test_planchets_rsa (age))
+ return -1;
+ return test_planchets_cs (age);
+}
+
+
+static int
+test_exchange_sigs (void)
+{
+ const char *pt = "payto://x-taler-bank/localhost/Account";
+ struct TALER_MasterPrivateKeyP priv;
+ struct TALER_MasterPublicKeyP pub;
+ struct TALER_MasterSignatureP sig;
+ json_t *rest;
+
+ GNUNET_CRYPTO_eddsa_key_create (&priv.eddsa_priv);
+ rest = json_array ();
+ GNUNET_assert (NULL != rest);
+ TALER_exchange_wire_signature_make (pt,
+ NULL,
+ rest,
+ rest,
+ &priv,
+ &sig);
+ GNUNET_CRYPTO_eddsa_key_get_public (&priv.eddsa_priv,
+ &pub.eddsa_pub);
+ if (GNUNET_OK !=
+ TALER_exchange_wire_signature_check (pt,
+ NULL,
+ rest,
+ rest,
+ &pub,
+ &sig))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ if (GNUNET_OK ==
+ TALER_exchange_wire_signature_check (
+ "payto://x-taler-bank/localhost/Other",
+ NULL,
+ rest,
+ rest,
+ &pub,
+ &sig))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ if (GNUNET_OK ==
+ TALER_exchange_wire_signature_check (
+ pt,
+ "http://example.com/",
+ rest,
+ rest,
+ &pub,
+ &sig))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ json_decref (rest);
+ return 0;
+}
+
+
+static int
+test_merchant_sigs (void)
+{
+ const char *pt = "payto://x-taler-bank/localhost/Account";
+ struct TALER_WireSaltP salt;
+ struct TALER_MerchantPrivateKeyP priv;
+ struct TALER_MerchantPublicKeyP pub;
+ struct TALER_MerchantSignatureP sig;
+
+ GNUNET_CRYPTO_eddsa_key_create (&priv.eddsa_priv);
+ memset (&salt,
+ 42,
+ sizeof (salt));
+ TALER_merchant_wire_signature_make (pt,
+ &salt,
+ &priv,
+ &sig);
+ GNUNET_CRYPTO_eddsa_key_get_public (&priv.eddsa_priv,
+ &pub.eddsa_pub);
+ if (GNUNET_OK !=
+ TALER_merchant_wire_signature_check (pt,
+ &salt,
+ &pub,
+ &sig))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ if (GNUNET_OK ==
+ TALER_merchant_wire_signature_check (
+ "payto://x-taler-bank/localhost/Other",
+ &salt,
+ &pub,
+ &sig))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ memset (&salt,
+ 43,
+ sizeof (salt));
+ if (GNUNET_OK ==
+ TALER_merchant_wire_signature_check (pt,
+ &salt,
+ &pub,
+ &sig))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ return 0;
+}
+
+
+static int
+test_contracts (void)
+{
+ struct TALER_ContractDiffiePrivateP cpriv;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PurseContractPrivateKeyP purse_priv;
+ void *econtract;
+ size_t econtract_size;
+ struct TALER_PurseMergePrivateKeyP mpriv_in;
+ struct TALER_PurseMergePrivateKeyP mpriv_out;
+ json_t *c;
+
+ GNUNET_CRYPTO_ecdhe_key_create (&cpriv.ecdhe_priv);
+ GNUNET_CRYPTO_eddsa_key_create (&purse_priv.eddsa_priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv.eddsa_priv,
+ &purse_pub.eddsa_pub);
+ memset (&mpriv_in,
+ 42,
+ sizeof (mpriv_in));
+ c = json_pack ("{s:s}", "test", "value");
+ GNUNET_assert (NULL != c);
+ TALER_CRYPTO_contract_encrypt_for_merge (&purse_pub,
+ &cpriv,
+ &mpriv_in,
+ c,
+ &econtract,
+ &econtract_size);
+ json_decref (c);
+ c = TALER_CRYPTO_contract_decrypt_for_merge (&cpriv,
+ &purse_pub,
+ econtract,
+ econtract_size,
+ &mpriv_out);
+ GNUNET_free (econtract);
+ if (NULL == c)
+ return 1;
+ json_decref (c);
+ if (0 != GNUNET_memcmp (&mpriv_in,
+ &mpriv_out))
+ return 1;
+ return 0;
+}
+
+
+static int
+test_attributes (void)
+{
+ struct TALER_AttributeEncryptionKeyP key;
+ void *eattr;
+ size_t eattr_size;
+ json_t *c;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &key,
+ sizeof (key));
+ c = json_pack ("{s:s}", "test", "value");
+ GNUNET_assert (NULL != c);
+ TALER_CRYPTO_kyc_attributes_encrypt (&key,
+ c,
+ &eattr,
+ &eattr_size);
+ json_decref (c);
+ c = TALER_CRYPTO_kyc_attributes_decrypt (&key,
+ eattr,
+ eattr_size);
+ GNUNET_free (eattr);
+ if (NULL == c)
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ GNUNET_assert (0 ==
+ strcmp ("value",
+ json_string_value (json_object_get (c,
+ "test"))));
+ json_decref (c);
return 0;
}
@@ -129,10 +519,23 @@ main (int argc,
{
(void) argc;
(void) argv;
+ GNUNET_log_setup ("test-crypto",
+ "WARNING",
+ NULL);
if (0 != test_high_level ())
return 1;
- if (0 != test_planchets ())
- return 1;
+ if (0 != test_planchets (0))
+ return 2;
+ if (0 != test_planchets (13))
+ return 3;
+ if (0 != test_exchange_sigs ())
+ return 4;
+ if (0 != test_merchant_sigs ())
+ return 5;
+ if (0 != test_contracts ())
+ return 6;
+ if (0 != test_attributes ())
+ return 7;
return 0;
}
diff --git a/src/util/test_helper_cs.c b/src/util/test_helper_cs.c
new file mode 100644
index 000000000..93562e459
--- /dev/null
+++ b/src/util/test_helper_cs.c
@@ -0,0 +1,1177 @@
+/*
+ This file is part of TALER
+ (C) 2020, 2021, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file util/test_helper_cs.c
+ * @brief Tests for CS crypto helper
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+
+/**
+ * Configuration has 1 minute duration and 5 minutes lookahead, but
+ * we do not get 'revocations' for expired keys. So this must be
+ * large enough to deal with key rotation during the runtime of
+ * the benchmark.
+ */
+#define MAX_KEYS 1024
+
+/**
+ * How many random key revocations should we test?
+ */
+#define NUM_REVOKES 3
+
+/**
+ * How many iterations of the successful signing test should we run?
+ */
+#define NUM_SIGN_TESTS 5
+
+/**
+ * How many iterations of the successful signing test should we run
+ * during the benchmark phase?
+ */
+#define NUM_SIGN_PERFS 100
+
+/**
+ * How many parallel clients should we use for the parallel
+ * benchmark? (> 500 may cause problems with the max open FD number limit).
+ */
+#define NUM_CORES 8
+
+/**
+ * Number of keys currently in #keys.
+ */
+static unsigned int num_keys;
+
+/**
+ * Keys currently managed by the helper.
+ */
+struct KeyData
+{
+ /**
+ * Validity start point.
+ */
+ struct GNUNET_TIME_Timestamp start_time;
+
+ /**
+ * Key expires for signing at @e start_time plus this value.
+ */
+ struct GNUNET_TIME_Relative validity_duration;
+
+ /**
+ * Hash of the public key.
+ */
+ struct TALER_CsPubHashP h_cs;
+
+ /**
+ * Full public key.
+ */
+ struct TALER_DenominationPublicKey denom_pub;
+
+ /**
+ * Is this key currently valid?
+ */
+ bool valid;
+
+ /**
+ * Did the test driver revoke this key?
+ */
+ bool revoked;
+};
+
+/**
+ * Array of all the keys we got from the helper.
+ */
+static struct KeyData keys[MAX_KEYS];
+
+
+/**
+ * Release memory occupied by #keys.
+ */
+static void
+free_keys (void)
+{
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ if (keys[i].valid)
+ {
+ TALER_denom_pub_free (&keys[i].denom_pub);
+ keys[i].valid = false;
+ GNUNET_assert (num_keys > 0);
+ num_keys--;
+ }
+}
+
+
+/**
+ * Function called with information about available keys for signing. Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero. Stores the keys
+ * status in #keys.
+ *
+ * @param cls closure, NULL
+ * @param section_name name of the denomination type in the configuration;
+ * NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ * zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ * zero if the key has been revoked or purged
+ * @param h_cs hash of the @a denom_pub that is available (or was purged)
+ * @param bs_pub the public key itself, NULL if the key was revoked or purged
+ * @param sm_pub public key of the security module, NULL if the key was revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was revoked or purged
+ * The signature was already verified against @a sm_pub.
+ */
+static void
+key_cb (void *cls,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Relative validity_duration,
+ const struct TALER_CsPubHashP *h_cs,
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
+ const struct TALER_SecurityModulePublicKeyP *sm_pub,
+ const struct TALER_SecurityModuleSignatureP *sm_sig)
+{
+ (void) cls;
+ (void) sm_pub;
+ (void) sm_sig;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Key notification about key %s in `%s'\n",
+ GNUNET_h2s (&h_cs->hash),
+ section_name);
+ if (0 == validity_duration.rel_value_us)
+ {
+ bool found = false;
+
+ GNUNET_break (NULL == bs_pub);
+ GNUNET_break (NULL == section_name);
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ if (0 == GNUNET_memcmp (h_cs,
+ &keys[i].h_cs))
+ {
+ keys[i].valid = false;
+ keys[i].revoked = false;
+ TALER_denom_pub_free (&keys[i].denom_pub);
+ GNUNET_assert (num_keys > 0);
+ num_keys--;
+ found = true;
+ break;
+ }
+ if (! found)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error: helper announced expiration of unknown key!\n");
+
+ return;
+ }
+
+ GNUNET_break (NULL != bs_pub);
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ if (! keys[i].valid)
+ {
+ keys[i].valid = true;
+ keys[i].h_cs = *h_cs;
+ keys[i].start_time = start_time;
+ keys[i].validity_duration = validity_duration;
+ keys[i].denom_pub.bsign_pub_key
+ = GNUNET_CRYPTO_bsign_pub_incref (bs_pub);
+ num_keys++;
+ return;
+ }
+ /* too many keys! */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error: received %d live keys from the service!\n",
+ MAX_KEYS + 1);
+}
+
+
+/**
+ * Test key revocation logic.
+ *
+ * @param dh handle to the helper
+ * @return 0 on success
+ */
+static int
+test_revocation (struct TALER_CRYPTO_CsDenominationHelper *dh)
+{
+ struct timespec req = {
+ .tv_nsec = 250000000
+ };
+
+ for (unsigned int i = 0; i<NUM_REVOKES; i++)
+ {
+ uint32_t off;
+
+ off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
+ num_keys);
+ /* find index of key to revoke */
+ for (unsigned int j = 0; j < MAX_KEYS; j++)
+ {
+ if (! keys[j].valid)
+ continue;
+ if (0 != off)
+ {
+ off--;
+ continue;
+ }
+ keys[j].revoked = true;
+ fprintf (stderr,
+ "Revoking key %s ...",
+ GNUNET_h2s (&keys[j].h_cs.hash));
+ TALER_CRYPTO_helper_cs_revoke (dh,
+ &keys[j].h_cs);
+ for (unsigned int k = 0; k<1000; k++)
+ {
+ TALER_CRYPTO_helper_cs_poll (dh);
+ if (! keys[j].revoked)
+ break;
+ nanosleep (&req, NULL);
+ fprintf (stderr, ".");
+ }
+ if (keys[j].revoked)
+ {
+ fprintf (stderr,
+ "\nFAILED: timeout trying to revoke key %u\n",
+ j);
+ TALER_CRYPTO_helper_cs_disconnect (dh);
+ return 2;
+ }
+ fprintf (stderr, "\n");
+ break;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Test R derivation logic.
+ *
+ * @param dh handle to the helper
+ * @return 0 on success
+ */
+static int
+test_r_derive (struct TALER_CRYPTO_CsDenominationHelper *dh)
+{
+ enum TALER_ErrorCode ec;
+ bool success = false;
+ struct TALER_PlanchetMasterSecretP ps;
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct TALER_CoinPubHashP c_hash;
+ struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = GNUNET_CRYPTO_BSA_CS
+ };
+ struct TALER_ExchangeWithdrawValues alg_values = {
+ .blinding_inputs = &bi
+ };
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+
+ TALER_planchet_master_setup_random (&ps);
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ {
+ struct TALER_PlanchetDetail pd;
+
+ if (! keys[i].valid)
+ continue;
+ GNUNET_assert (GNUNET_CRYPTO_BSA_CS ==
+ keys[i].denom_pub.bsign_pub_key->cipher);
+ TALER_cs_withdraw_nonce_derive (
+ &ps,
+ &nonce.cs_nonce);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requesting R derivation with key %s\n",
+ GNUNET_h2s (&keys[i].h_cs.hash));
+ {
+ struct TALER_CRYPTO_CsDeriveRequest cdr = {
+ .h_cs = &keys[i].h_cs,
+ .nonce = &nonce.cs_nonce
+ };
+
+ ec = TALER_CRYPTO_helper_cs_r_derive (
+ dh,
+ &cdr,
+ false,
+ &bi.details.cs_values);
+ }
+ switch (ec)
+ {
+ case TALER_EC_NONE:
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining (
+ keys[i].start_time.abs_time),
+ >,
+ GNUNET_TIME_UNIT_SECONDS))
+ {
+ /* key worked too early */
+ GNUNET_break (0);
+ return 4;
+ }
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration (
+ keys[i].start_time.abs_time),
+ >,
+ keys[i].validity_duration))
+ {
+ /* key worked too later */
+ GNUNET_break (0);
+ return 5;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received valid R for key %s\n",
+ GNUNET_h2s (&keys[i].h_cs.hash));
+ TALER_planchet_setup_coin_priv (&ps,
+ &alg_values,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (&ps,
+ &alg_values,
+ &bks);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_planchet_prepare (&keys[i].denom_pub,
+ &alg_values,
+ &bks,
+ &nonce,
+ &coin_priv,
+ NULL, /* no age commitment */
+ &c_hash,
+ &pd));
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Successfully prepared planchet");
+ success = true;
+ break;
+ case TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY:
+ /* This 'failure' is expected, we're testing also for the
+ error handling! */
+ if ( (GNUNET_TIME_relative_is_zero (
+ GNUNET_TIME_absolute_get_remaining (
+ keys[i].start_time.abs_time))) &&
+ (GNUNET_TIME_relative_cmp (
+ GNUNET_TIME_absolute_get_duration (
+ keys[i].start_time.abs_time),
+ <,
+ keys[i].validity_duration)) )
+ {
+ /* key should have worked! */
+ GNUNET_break (0);
+ return 6;
+ }
+ break;
+ default:
+ /* unexpected error */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected error %d\n",
+ ec);
+ return 7;
+ }
+ }
+ if (! success)
+ {
+ /* no valid key for signing found, also bad */
+ GNUNET_break (0);
+ return 16;
+ }
+
+ /* check R derivation does not work if the key is unknown */
+ {
+ struct TALER_CsPubHashP rnd;
+ struct GNUNET_CRYPTO_CSPublicRPairP crp;
+ struct TALER_CRYPTO_CsDeriveRequest cdr = {
+ .h_cs = &rnd,
+ .nonce = &nonce.cs_nonce,
+ };
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &rnd,
+ sizeof (rnd));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &nonce,
+ sizeof (nonce));
+ ec = TALER_CRYPTO_helper_cs_r_derive (dh,
+ &cdr,
+ false,
+ &crp);
+ if (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN != ec)
+ {
+ GNUNET_break (0);
+ return 17;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "R derivation with invalid key %s failed as desired\n",
+ GNUNET_h2s (&rnd.hash));
+ }
+ return 0;
+}
+
+
+/**
+ * Test signing logic.
+ *
+ * @param dh handle to the helper
+ * @return 0 on success
+ */
+static int
+test_signing (struct TALER_CRYPTO_CsDenominationHelper *dh)
+{
+ struct TALER_BlindedDenominationSignature ds;
+ enum TALER_ErrorCode ec;
+ bool success = false;
+ struct TALER_PlanchetMasterSecretP ps;
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct TALER_CoinPubHashP c_hash;
+ struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = GNUNET_CRYPTO_BSA_CS
+ };
+ struct TALER_ExchangeWithdrawValues alg_values = {
+ .blinding_inputs = &bi
+ };
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+
+ TALER_planchet_master_setup_random (&ps);
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ {
+ if (! keys[i].valid)
+ continue;
+ {
+ struct TALER_PlanchetDetail pd;
+ struct TALER_CRYPTO_CsSignRequest csr;
+ struct TALER_CRYPTO_CsDeriveRequest cdr = {
+ .h_cs = &keys[i].h_cs,
+ .nonce = &nonce.cs_nonce
+ };
+
+ TALER_cs_withdraw_nonce_derive (&ps,
+ &nonce.cs_nonce);
+ ec = TALER_CRYPTO_helper_cs_r_derive (
+ dh,
+ &cdr,
+ false,
+ &bi.details.cs_values);
+ if (TALER_EC_NONE != ec)
+ continue;
+ TALER_planchet_setup_coin_priv (&ps,
+ &alg_values,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (&ps,
+ &alg_values,
+ &bks);
+ GNUNET_assert (GNUNET_YES ==
+ TALER_planchet_prepare (&keys[i].denom_pub,
+ &alg_values,
+ &bks,
+ &nonce,
+ &coin_priv,
+ NULL, /* no age commitment */
+ &c_hash,
+ &pd));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requesting signature with key %s\n",
+ GNUNET_h2s (&keys[i].h_cs.hash));
+ csr.h_cs = &keys[i].h_cs;
+ csr.blinded_planchet
+ = &pd.blinded_planchet.blinded_message->details.cs_blinded_message;
+ ec = TALER_CRYPTO_helper_cs_sign (
+ dh,
+ &csr,
+ false,
+ &ds);
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
+ }
+ switch (ec)
+ {
+ case TALER_EC_NONE:
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining (
+ keys[i].start_time.abs_time),
+ >,
+ GNUNET_TIME_UNIT_SECONDS))
+ {
+ /* key worked too early */
+ GNUNET_break (0);
+ TALER_blinded_denom_sig_free (&ds);
+ return 4;
+ }
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration (
+ keys[i].start_time.abs_time),
+ >,
+ keys[i].validity_duration))
+ {
+ /* key worked too later */
+ GNUNET_break (0);
+ TALER_blinded_denom_sig_free (&ds);
+ return 5;
+ }
+ {
+ struct TALER_FreshCoin coin;
+
+ if (GNUNET_OK !=
+ TALER_planchet_to_coin (&keys[i].denom_pub,
+ &ds,
+ &bks,
+ &coin_priv,
+ NULL, /* no age commitment */
+ &c_hash,
+ &alg_values,
+ &coin))
+ {
+ GNUNET_break (0);
+ TALER_blinded_denom_sig_free (&ds);
+ return 6;
+ }
+ TALER_blinded_denom_sig_free (&ds);
+ TALER_denom_sig_free (&coin.sig);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received valid signature for key %s\n",
+ GNUNET_h2s (&keys[i].h_cs.hash));
+ success = true;
+ break;
+ case TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY:
+ /* This 'failure' is expected, we're testing also for the
+ error handling! */
+ if ( (GNUNET_TIME_relative_is_zero (
+ GNUNET_TIME_absolute_get_remaining (
+ keys[i].start_time.abs_time))) &&
+ (GNUNET_TIME_relative_cmp (
+ GNUNET_TIME_absolute_get_duration (
+ keys[i].start_time.abs_time),
+ <,
+ keys[i].validity_duration)) )
+ {
+ /* key should have worked! */
+ GNUNET_break (0);
+ return 6;
+ }
+ break;
+ default:
+ /* unexpected error */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected error %d\n",
+ ec);
+ return 7;
+ }
+ }
+ if (! success)
+ {
+ /* no valid key for signing found, also bad */
+ GNUNET_break (0);
+ return 16;
+ }
+
+ /* check signing does not work if the key is unknown */
+ {
+ struct TALER_PlanchetDetail pd;
+ struct TALER_CsPubHashP rnd;
+ struct TALER_CRYPTO_CsSignRequest csr;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &rnd,
+ sizeof (rnd));
+ GNUNET_assert (GNUNET_YES ==
+ TALER_planchet_prepare (&keys[0].denom_pub,
+ &alg_values,
+ &bks,
+ &nonce,
+ &coin_priv,
+ NULL, /* no age commitment */
+ &c_hash,
+ &pd));
+ csr.h_cs = &rnd;
+ csr.blinded_planchet
+ = &pd.blinded_planchet.blinded_message->details.cs_blinded_message;
+ ec = TALER_CRYPTO_helper_cs_sign (
+ dh,
+ &csr,
+ false,
+ &ds);
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
+ if (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN != ec)
+ {
+ if (TALER_EC_NONE == ec)
+ TALER_blinded_denom_sig_free (&ds);
+ GNUNET_break (0);
+ return 17;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing with invalid key %s failed as desired\n",
+ GNUNET_h2s (&rnd.hash));
+ }
+ return 0;
+}
+
+
+/**
+ * Test batch signing logic.
+ *
+ * @param dh handle to the helper
+ * @param batch_size how large should the batch be
+ * @param check_sigs also check unknown key and signatures
+ * @return 0 on success
+ */
+static int
+test_batch_signing (struct TALER_CRYPTO_CsDenominationHelper *dh,
+ unsigned int batch_size,
+ bool check_sigs)
+{
+ struct TALER_BlindedDenominationSignature ds[batch_size];
+ enum TALER_ErrorCode ec;
+ bool success = false;
+ struct TALER_PlanchetMasterSecretP ps[batch_size];
+ struct TALER_CoinSpendPrivateKeyP coin_priv[batch_size];
+ union GNUNET_CRYPTO_BlindingSecretP bks[batch_size];
+ struct TALER_CoinPubHashP c_hash[batch_size];
+ struct GNUNET_CRYPTO_BlindingInputValues bi[batch_size];
+ struct TALER_ExchangeWithdrawValues alg_values[batch_size];
+ union GNUNET_CRYPTO_BlindSessionNonce nonces[batch_size];
+
+ for (unsigned int i = 0; i<batch_size; i++)
+ TALER_planchet_master_setup_random (&ps[i]);
+ for (unsigned int k = 0; k<MAX_KEYS; k++)
+ {
+ if (! keys[k].valid)
+ continue;
+ {
+ struct TALER_PlanchetDetail pd[batch_size];
+ struct TALER_CRYPTO_CsSignRequest csr[batch_size];
+ struct TALER_CRYPTO_CsDeriveRequest cdr[batch_size];
+ struct GNUNET_CRYPTO_CSPublicRPairP crps[batch_size];
+
+ for (unsigned int i = 0; i<batch_size; i++)
+ {
+ cdr[i].h_cs = &keys[k].h_cs;
+ cdr[i].nonce = &nonces[i].cs_nonce;
+ TALER_cs_withdraw_nonce_derive (
+ &ps[i],
+ &nonces[i].cs_nonce);
+ bi[i].cipher = GNUNET_CRYPTO_BSA_CS;
+ alg_values[i].blinding_inputs = &bi[i];
+ }
+ ec = TALER_CRYPTO_helper_cs_r_batch_derive (
+ dh,
+ batch_size,
+ cdr,
+ false,
+ crps);
+ if (TALER_EC_NONE != ec)
+ continue;
+ for (unsigned int i = 0; i<batch_size; i++)
+ {
+ bi[i].details.cs_values = crps[i];
+ TALER_planchet_setup_coin_priv (&ps[i],
+ &alg_values[i],
+ &coin_priv[i]);
+ TALER_planchet_blinding_secret_create (&ps[i],
+ &alg_values[i],
+ &bks[i]);
+ GNUNET_assert (GNUNET_YES ==
+ TALER_planchet_prepare (&keys[k].denom_pub,
+ &alg_values[i],
+ &bks[i],
+ &nonces[i],
+ &coin_priv[i],
+ NULL, /* no age commitment */
+ &c_hash[i],
+ &pd[i]));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requesting signature with key %s\n",
+ GNUNET_h2s (&keys[k].h_cs.hash));
+ csr[i].h_cs = &keys[k].h_cs;
+ csr[i].blinded_planchet
+ = &pd[i].blinded_planchet.blinded_message->details.cs_blinded_message;
+ }
+ ec = TALER_CRYPTO_helper_cs_batch_sign (
+ dh,
+ batch_size,
+ csr,
+ false,
+ ds);
+ for (unsigned int i = 0; i<batch_size; i++)
+ {
+ TALER_blinded_planchet_free (&pd[i].blinded_planchet);
+ }
+ }
+ switch (ec)
+ {
+ case TALER_EC_NONE:
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining (
+ keys[k].start_time.abs_time),
+ >,
+ GNUNET_TIME_UNIT_SECONDS))
+ {
+ /* key worked too early */
+ GNUNET_break (0);
+ return 4;
+ }
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration (
+ keys[k].start_time.abs_time),
+ >,
+ keys[k].validity_duration))
+ {
+ /* key worked too later */
+ GNUNET_break (0);
+ return 5;
+ }
+ if (check_sigs)
+ {
+ for (unsigned int i = 0; i<batch_size; i++)
+ {
+ struct TALER_FreshCoin coin;
+
+ if (GNUNET_OK !=
+ TALER_planchet_to_coin (&keys[k].denom_pub,
+ &ds[i],
+ &bks[i],
+ &coin_priv[i],
+ NULL, /* no age commitment */
+ &c_hash[i],
+ &alg_values[i],
+ &coin))
+ {
+ GNUNET_break (0);
+ return 6;
+ }
+ TALER_blinded_denom_sig_free (&ds[i]);
+ TALER_denom_sig_free (&coin.sig);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received valid signature for key %s\n",
+ GNUNET_h2s (&keys[k].h_cs.hash));
+ }
+ else
+ {
+ for (unsigned int i = 0; i<batch_size; i++)
+ TALER_blinded_denom_sig_free (&ds[i]);
+ }
+ success = true;
+ break;
+ case TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY:
+ /* This 'failure' is expected, we're testing also for the
+ error handling! */
+ if ( (GNUNET_TIME_relative_is_zero (
+ GNUNET_TIME_absolute_get_remaining (
+ keys[k].start_time.abs_time))) &&
+ (GNUNET_TIME_relative_cmp (
+ GNUNET_TIME_absolute_get_duration (
+ keys[k].start_time.abs_time),
+ <,
+ keys[k].validity_duration)) )
+ {
+ /* key should have worked! */
+ GNUNET_break (0);
+ return 6;
+ }
+ break;
+ default:
+ /* unexpected error */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected error %d\n",
+ ec);
+ return 7;
+ }
+ }
+ if (! success)
+ {
+ /* no valid key for signing found, also bad */
+ GNUNET_break (0);
+ return 16;
+ }
+
+ /* check signing does not work if the key is unknown */
+ if (check_sigs)
+ {
+ struct TALER_PlanchetDetail pd;
+ struct TALER_CsPubHashP rnd;
+ struct TALER_CRYPTO_CsSignRequest csr;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &rnd,
+ sizeof (rnd));
+ GNUNET_assert (GNUNET_YES ==
+ TALER_planchet_prepare (&keys[0].denom_pub,
+ &alg_values[0],
+ &bks[0],
+ &nonces[0],
+ &coin_priv[0],
+ NULL, /* no age commitment */
+ &c_hash[0],
+ &pd));
+ csr.h_cs = &rnd;
+ csr.blinded_planchet
+ = &pd.blinded_planchet.blinded_message->details.cs_blinded_message;
+ ec = TALER_CRYPTO_helper_cs_batch_sign (
+ dh,
+ 1,
+ &csr,
+ false,
+ &ds[0]);
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
+ if (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN != ec)
+ {
+ if (TALER_EC_NONE == ec)
+ TALER_blinded_denom_sig_free (&ds[0]);
+ GNUNET_break (0);
+ return 17;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing with invalid key %s failed as desired\n",
+ GNUNET_h2s (&rnd.hash));
+ }
+ return 0;
+}
+
+
+/**
+ * Benchmark signing logic.
+ *
+ * @param dh handle to the helper
+ * @return 0 on success
+ */
+static int
+perf_signing (struct TALER_CRYPTO_CsDenominationHelper *dh,
+ const char *type)
+{
+ struct TALER_BlindedDenominationSignature ds;
+ enum TALER_ErrorCode ec;
+ struct GNUNET_TIME_Relative duration;
+ struct TALER_PlanchetMasterSecretP ps;
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct GNUNET_CRYPTO_BlindingInputValues bv = {
+ .cipher = GNUNET_CRYPTO_BSA_CS
+ };
+ struct TALER_ExchangeWithdrawValues alg_values = {
+ .blinding_inputs = &bv
+ };
+
+ TALER_planchet_master_setup_random (&ps);
+ duration = GNUNET_TIME_UNIT_ZERO;
+ TALER_CRYPTO_helper_cs_poll (dh);
+ for (unsigned int j = 0; j<NUM_SIGN_PERFS;)
+ {
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ {
+ if (! keys[i].valid)
+ continue;
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining (
+ keys[i].start_time.abs_time),
+ >,
+ GNUNET_TIME_UNIT_SECONDS))
+ continue;
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration (
+ keys[i].start_time.abs_time),
+ >,
+ keys[i].validity_duration))
+ continue;
+ {
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_PlanchetDetail pd;
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+ struct TALER_CRYPTO_CsDeriveRequest cdr = {
+ .h_cs = &keys[i].h_cs,
+ .nonce = &nonce.cs_nonce
+ };
+
+ TALER_cs_withdraw_nonce_derive (
+ &ps,
+ &nonce.cs_nonce);
+ ec = TALER_CRYPTO_helper_cs_r_derive (
+ dh,
+ &cdr,
+ true,
+ &bv.details.cs_values);
+ if (TALER_EC_NONE != ec)
+ continue;
+ TALER_planchet_setup_coin_priv (&ps,
+ &alg_values,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (&ps,
+ &alg_values,
+ &bks);
+ GNUNET_assert (GNUNET_YES ==
+ TALER_planchet_prepare (&keys[i].denom_pub,
+ &alg_values,
+ &bks,
+ &nonce,
+ &coin_priv,
+ NULL, /* no age commitment */
+ &c_hash,
+ &pd));
+ /* use this key as long as it works */
+ while (1)
+ {
+ struct GNUNET_TIME_Absolute start = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Relative delay;
+ struct TALER_CRYPTO_CsSignRequest csr;
+
+ csr.h_cs = &keys[i].h_cs;
+ csr.blinded_planchet
+ = &pd.blinded_planchet.blinded_message->details.cs_blinded_message;
+ ec = TALER_CRYPTO_helper_cs_sign (
+ dh,
+ &csr,
+ true,
+ &ds);
+ if (TALER_EC_NONE != ec)
+ break;
+ delay = GNUNET_TIME_absolute_get_duration (start);
+ duration = GNUNET_TIME_relative_add (duration,
+ delay);
+ TALER_blinded_denom_sig_free (&ds);
+ j++;
+ if (NUM_SIGN_PERFS <= j)
+ break;
+ }
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
+ }
+ } /* for i */
+ } /* for j */
+ fprintf (stderr,
+ "%u (%s) signature operations took %s\n",
+ (unsigned int) NUM_SIGN_PERFS,
+ type,
+ GNUNET_STRINGS_relative_time_to_string (duration,
+ GNUNET_YES));
+ return 0;
+}
+
+
+/**
+ * Parallel signing logic.
+ *
+ * @param esh handle to the helper
+ * @return 0 on success
+ */
+static int
+par_signing (struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ struct GNUNET_TIME_Absolute start;
+ struct GNUNET_TIME_Relative duration;
+ pid_t pids[NUM_CORES];
+ struct TALER_CRYPTO_CsDenominationHelper *dh;
+
+ start = GNUNET_TIME_absolute_get ();
+ for (unsigned int i = 0; i<NUM_CORES; i++)
+ {
+ pids[i] = fork ();
+ num_keys = 0;
+ GNUNET_assert (-1 != pids[i]);
+ if (0 == pids[i])
+ {
+ int ret;
+
+ dh = TALER_CRYPTO_helper_cs_connect (cfg,
+ "taler-exchange",
+ &key_cb,
+ NULL);
+ GNUNET_assert (NULL != dh);
+ ret = perf_signing (dh,
+ "parallel");
+ TALER_CRYPTO_helper_cs_disconnect (dh);
+ free_keys ();
+ exit (ret);
+ }
+ }
+ for (unsigned int i = 0; i<NUM_CORES; i++)
+ {
+ int wstatus;
+
+ GNUNET_assert (pids[i] ==
+ waitpid (pids[i],
+ &wstatus,
+ 0));
+ }
+ duration = GNUNET_TIME_absolute_get_duration (start);
+ fprintf (stderr,
+ "%u (parallel) signature operations took %s (total real time)\n",
+ (unsigned int) NUM_SIGN_PERFS * NUM_CORES,
+ GNUNET_STRINGS_relative_time_to_string (duration,
+ GNUNET_YES));
+ return 0;
+}
+
+
+/**
+ * Main entry point into the test logic with the helper already running.
+ */
+static int
+run_test (void)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+ struct TALER_CRYPTO_CsDenominationHelper *dh;
+ struct timespec req = {
+ .tv_nsec = 250000000
+ };
+ int ret;
+
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_load (cfg,
+ "test_helper_cs.conf"))
+ {
+ GNUNET_break (0);
+ return 77;
+ }
+
+ fprintf (stderr, "Waiting for helper to start ... ");
+ for (unsigned int i = 0; i<100; i++)
+ {
+ nanosleep (&req,
+ NULL);
+ dh = TALER_CRYPTO_helper_cs_connect (cfg,
+ "taler-exchange",
+ &key_cb,
+ NULL);
+ if (NULL != dh)
+ break;
+ fprintf (stderr, ".");
+ }
+ if (NULL == dh)
+ {
+ fprintf (stderr,
+ "\nFAILED: timeout trying to connect to helper\n");
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return 1;
+ }
+ if (0 == num_keys)
+ {
+ fprintf (stderr,
+ "\nFAILED: timeout trying to connect to helper\n");
+ TALER_CRYPTO_helper_cs_disconnect (dh);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return 1;
+ }
+ fprintf (stderr,
+ " Done (%u keys)\n",
+ num_keys);
+ ret = 0;
+ if (0 == ret)
+ ret = test_revocation (dh);
+ if (0 == ret)
+ ret = test_r_derive (dh);
+ if (0 == ret)
+ ret = test_signing (dh);
+ if (0 == ret)
+ ret = test_batch_signing (dh,
+ 2,
+ true);
+ if (0 == ret)
+ ret = test_batch_signing (dh,
+ 256,
+ true);
+ for (unsigned int i = 0; i<5; i++)
+ {
+ static unsigned int batches[] = { 1, 4, 16, 64, 256 };
+ unsigned int batch_size = batches[i];
+ struct GNUNET_TIME_Absolute start;
+ struct GNUNET_TIME_Relative duration;
+
+ start = GNUNET_TIME_absolute_get ();
+ if (0 != ret)
+ break;
+ ret = test_batch_signing (dh,
+ batch_size,
+ false);
+ duration = GNUNET_TIME_absolute_get_duration (start);
+ fprintf (stderr,
+ "%4u (batch) signature operations took %s (total real time)\n",
+ (unsigned int) batch_size,
+ GNUNET_STRINGS_relative_time_to_string (duration,
+ GNUNET_YES));
+ }
+ if (0 == ret)
+ ret = perf_signing (dh,
+ "sequential");
+ TALER_CRYPTO_helper_cs_disconnect (dh);
+ free_keys ();
+ if (0 == ret)
+ ret = par_signing (cfg);
+ /* clean up our state */
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return ret;
+}
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ struct GNUNET_OS_Process *helper;
+ char *libexec_dir;
+ char *binary_name;
+ int ret;
+ enum GNUNET_OS_ProcessStatusType type;
+ unsigned long code;
+ const char *loglev = "WARNING";
+
+ (void) argc;
+ (void) argv;
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ GNUNET_log_setup ("test-helper-cs",
+ loglev,
+ NULL);
+ GNUNET_OS_init (TALER_project_data_default ());
+ libexec_dir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_BINDIR);
+ GNUNET_asprintf (&binary_name,
+ "%s/%s",
+ libexec_dir,
+ "taler-exchange-secmod-cs");
+ GNUNET_free (libexec_dir);
+ helper = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
+ NULL, NULL, NULL,
+ binary_name,
+ binary_name,
+ "-c",
+ "test_helper_cs.conf",
+ "-L",
+ loglev,
+ NULL);
+ if (NULL == helper)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "exec",
+ binary_name);
+ GNUNET_free (binary_name);
+ return 77;
+ }
+ GNUNET_free (binary_name);
+ ret = run_test ();
+
+ GNUNET_OS_process_kill (helper,
+ SIGTERM);
+ if (GNUNET_OK !=
+ GNUNET_OS_process_wait_status (helper,
+ &type,
+ &code))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Helper process did not die voluntarily, killing hard\n");
+ GNUNET_OS_process_kill (helper,
+ SIGKILL);
+ ret = 4;
+ }
+ else if ( (GNUNET_OS_PROCESS_EXITED != type) ||
+ (0 != code) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Helper died with unexpected status %d/%d\n",
+ (int) type,
+ (int) code);
+ ret = 5;
+ }
+ GNUNET_OS_process_destroy (helper);
+ return ret;
+}
+
+
+/* end of test_helper_cs.c */
diff --git a/src/util/test_helper_cs.conf b/src/util/test_helper_cs.conf
new file mode 100644
index 000000000..f3b5b834c
--- /dev/null
+++ b/src/util/test_helper_cs.conf
@@ -0,0 +1,11 @@
+[PATHS]
+# Persistent data storage for the testcase
+TALER_TEST_HOME = test_helper_cs_home/
+
+[coin_1]
+DURATION_WITHDRAW = 1 minute
+CIPHER = CS
+
+[taler-exchange-secmod-cs]
+LOOKAHEAD_SIGN = 5 minutes
+OVERLAP_DURATION = 1 s
diff --git a/src/util/test_helper_eddsa.c b/src/util/test_helper_eddsa.c
new file mode 100644
index 000000000..0119e4278
--- /dev/null
+++ b/src/util/test_helper_eddsa.c
@@ -0,0 +1,557 @@
+/*
+ This file is part of TALER
+ (C) 2020, 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 util/test_helper_eddsa.c
+ * @brief Tests for EDDSA crypto helper
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include <gnunet/gnunet_signatures.h>
+
+/**
+ * Configuration has 1 minute duration and 5 minutes lookahead, so
+ * we should never have more than 6 active keys, plus for during
+ * key expiration / revocation.
+ */
+#define MAX_KEYS 20
+
+/**
+ * How many random key revocations should we test?
+ */
+#define NUM_REVOKES 3
+
+/**
+ * How many iterations of the successful signing test should we run
+ * during the test phase?
+ */
+#define NUM_SIGN_TESTS 3
+
+/**
+ * How many iterations of the successful signing test should we run
+ * during the benchmark phase?
+ */
+#define NUM_SIGN_PERFS 100
+
+/**
+ * How many parallel clients should we use for the parallel
+ * benchmark? (> 500 may cause problems with the max open FD number limit).
+ */
+#define NUM_CORES 8
+
+/**
+ * Number of keys currently in #keys.
+ */
+static unsigned int num_keys;
+
+/**
+ * Keys currently managed by the helper.
+ */
+struct KeyData
+{
+ /**
+ * Validity start point.
+ */
+ struct GNUNET_TIME_Timestamp start_time;
+
+ /**
+ * Key expires for signing at @e start_time plus this value.
+ */
+ struct GNUNET_TIME_Relative validity_duration;
+
+ /**
+ * Full public key.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Is this key currently valid?
+ */
+ bool valid;
+
+ /**
+ * Did the test driver revoke this key?
+ */
+ bool revoked;
+};
+
+/**
+ * Array of all the keys we got from the helper.
+ */
+static struct KeyData keys[MAX_KEYS];
+
+
+/**
+ * Function called with information about available keys for signing. Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero. Stores the keys
+ * status in #keys.
+ *
+ * @param cls closure, NULL
+ * @param start_time when does the key become available for signing;
+ * zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ * zero if the key has been revoked or purged
+ * @param exchange_pub the public key itself
+ * @param sm_pub public key of the security module, NULL if the key was revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was revoked or purged
+ * The signature was already verified against @a sm_pub.
+ */
+static void
+key_cb (void *cls,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Relative validity_duration,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_SecurityModulePublicKeyP *sm_pub,
+ const struct TALER_SecurityModuleSignatureP *sm_sig)
+{
+ (void) cls;
+ (void) sm_pub;
+ (void) sm_sig;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Update on key %s (%s)...",
+ TALER_B2S (exchange_pub),
+ GNUNET_STRINGS_relative_time_to_string (validity_duration,
+ GNUNET_YES));
+
+ if (GNUNET_TIME_relative_is_zero (validity_duration))
+ {
+ bool found = false;
+
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ if (0 == GNUNET_memcmp (exchange_pub,
+ &keys[i].exchange_pub))
+ {
+ keys[i].valid = false;
+ keys[i].revoked = false;
+ GNUNET_assert (num_keys > 0);
+ num_keys--;
+ found = true;
+ break;
+ }
+ if (! found)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error: helper announced expiration of unknown key!\n");
+
+ return;
+ }
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ if (! keys[i].valid)
+ {
+ keys[i].valid = true;
+ keys[i].exchange_pub = *exchange_pub;
+ keys[i].start_time = start_time;
+ keys[i].validity_duration = validity_duration;
+ num_keys++;
+ return;
+ }
+ /* too many keys! */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error: received %d live keys from the service!\n",
+ MAX_KEYS + 1);
+}
+
+
+/**
+ * Test key revocation logic.
+ *
+ * @param esh handle to the helper
+ * @return 0 on success
+ */
+static int
+test_revocation (struct TALER_CRYPTO_ExchangeSignHelper *esh)
+{
+ struct timespec req = {
+ .tv_nsec = 250000000
+ };
+
+ for (unsigned int i = 0; i<NUM_REVOKES; i++)
+ {
+ uint32_t off;
+
+ off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
+ num_keys);
+ /* find index of key to revoke */
+ for (unsigned int j = 0; j < MAX_KEYS; j++)
+ {
+ if (! keys[j].valid)
+ continue;
+ if (0 != off)
+ {
+ off--;
+ continue;
+ }
+ keys[j].revoked = true;
+ fprintf (stderr,
+ "Revoking key %s (%u) ...",
+ TALER_B2S (&keys[j].exchange_pub),
+ j);
+ TALER_CRYPTO_helper_esign_revoke (esh,
+ &keys[j].exchange_pub);
+ for (unsigned int k = 0; k<1000; k++)
+ {
+ TALER_CRYPTO_helper_esign_poll (esh);
+ if ( (! keys[j].revoked) ||
+ (GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_add (keys[j].start_time.abs_time,
+ keys[j].validity_duration))) )
+ {
+ break;
+ }
+ nanosleep (&req, NULL);
+ fprintf (stderr, ".");
+ }
+ if ( (keys[j].revoked) &&
+ (! GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_add (keys[j].start_time.abs_time,
+ keys[j].validity_duration))) )
+ {
+ fprintf (stderr,
+ "\nFAILED: timeout trying to revoke key %u\n",
+ j);
+ TALER_CRYPTO_helper_esign_disconnect (esh);
+ esh = NULL;
+ return 2;
+ }
+ fprintf (stderr, "\n");
+ break;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Test signing logic.
+ *
+ * @param esh handle to the helper
+ * @return 0 on success
+ */
+static int
+test_signing (struct TALER_CRYPTO_ExchangeSignHelper *esh)
+{
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose = {
+ .purpose = htonl (GNUNET_SIGNATURE_PURPOSE_TEST),
+ .size = htonl (sizeof (purpose)),
+ };
+
+ for (unsigned int i = 0; i<NUM_SIGN_TESTS; i++)
+ {
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ enum TALER_ErrorCode ec;
+
+ ec = TALER_CRYPTO_helper_esign_sign_ (esh,
+ &purpose,
+ &exchange_pub,
+ &exchange_sig);
+ switch (ec)
+ {
+ case TALER_EC_NONE:
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify_ (GNUNET_SIGNATURE_PURPOSE_TEST,
+ &purpose,
+ &exchange_sig.eddsa_signature,
+ &exchange_pub.eddsa_pub))
+ {
+ /* signature invalid */
+ GNUNET_break (0);
+ return 17;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received valid signature\n");
+ break;
+ default:
+ /* unexpected error */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected error %d\n",
+ ec);
+ return 7;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Benchmark signing logic.
+ *
+ * @param esh handle to the helper
+ * @return 0 on success
+ */
+static int
+perf_signing (struct TALER_CRYPTO_ExchangeSignHelper *esh,
+ const char *type)
+{
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose = {
+ .purpose = htonl (GNUNET_SIGNATURE_PURPOSE_TEST),
+ .size = htonl (sizeof (purpose)),
+ };
+ struct GNUNET_TIME_Relative duration;
+
+ duration = GNUNET_TIME_UNIT_ZERO;
+ for (unsigned int j = 0; j<NUM_SIGN_PERFS; j++)
+ {
+ struct GNUNET_TIME_Relative delay;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ enum TALER_ErrorCode ec;
+ struct GNUNET_TIME_Absolute start;
+
+ TALER_CRYPTO_helper_esign_poll (esh);
+ start = GNUNET_TIME_absolute_get ();
+ ec = TALER_CRYPTO_helper_esign_sign_ (esh,
+ &purpose,
+ &exchange_pub,
+ &exchange_sig);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ return 42;
+ }
+ delay = GNUNET_TIME_absolute_get_duration (start);
+ duration = GNUNET_TIME_relative_add (duration,
+ delay);
+ } /* for j */
+ fprintf (stderr,
+ "%u (%s) signature operations took %s\n",
+ (unsigned int) NUM_SIGN_PERFS,
+ type,
+ GNUNET_STRINGS_relative_time_to_string (duration,
+ GNUNET_YES));
+ return 0;
+}
+
+
+/**
+ * Parallel signing logic.
+ *
+ * @param esh handle to the helper
+ * @return 0 on success
+ */
+static int
+par_signing (struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ struct GNUNET_TIME_Absolute start;
+ struct GNUNET_TIME_Relative duration;
+ pid_t pids[NUM_CORES];
+ struct TALER_CRYPTO_ExchangeSignHelper *esh;
+
+ memset (keys,
+ 0,
+ sizeof (keys));
+ num_keys = 0;
+ start = GNUNET_TIME_absolute_get ();
+ for (unsigned int i = 0; i<NUM_CORES; i++)
+ {
+ pids[i] = fork ();
+ GNUNET_assert (-1 != pids[i]);
+ if (0 == pids[i])
+ {
+ int ret;
+
+ esh = TALER_CRYPTO_helper_esign_connect (cfg,
+ "taler-exchange",
+ &key_cb,
+ NULL);
+ if (NULL == esh)
+ {
+ GNUNET_break (0);
+ exit (EXIT_FAILURE);
+ }
+ ret = perf_signing (esh,
+ "parallel");
+ TALER_CRYPTO_helper_esign_disconnect (esh);
+ esh = NULL;
+ exit (ret);
+ }
+ }
+ for (unsigned int i = 0; i<NUM_CORES; i++)
+ {
+ int wstatus;
+
+ GNUNET_assert (pids[i] ==
+ waitpid (pids[i],
+ &wstatus,
+ 0));
+ }
+ duration = GNUNET_TIME_absolute_get_duration (start);
+ fprintf (stderr,
+ "%u (parallel) signature operations took %s (total real time)\n",
+ (unsigned int) NUM_SIGN_PERFS * NUM_CORES,
+ GNUNET_STRINGS_relative_time_to_string (duration,
+ GNUNET_YES));
+ return 0;
+}
+
+
+/**
+ * Main entry point into the test logic with the helper already running.
+ */
+static int
+run_test (void)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+ struct TALER_CRYPTO_ExchangeSignHelper *esh;
+ int ret;
+ struct timespec req = {
+ .tv_nsec = 250000000
+ };
+
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_load (cfg,
+ "test_helper_eddsa.conf"))
+ {
+ GNUNET_break (0);
+ return 77;
+ }
+
+ /* wait for helper to start and give us keys */
+ fprintf (stderr, "Waiting for helper to start ... ");
+ for (unsigned int i = 0; i<100; i++)
+ {
+ nanosleep (&req,
+ NULL);
+ esh = TALER_CRYPTO_helper_esign_connect (cfg,
+ "taler-exchange",
+ &key_cb,
+ NULL);
+ if (NULL != esh)
+ break;
+ fprintf (stderr, ".");
+ }
+ if (NULL == esh)
+ {
+ fprintf (stderr,
+ "\nFAILED: timeout trying to connect to helper\n");
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return 1;
+ }
+ if (0 == num_keys)
+ {
+ fprintf (stderr,
+ "\nFAILED: no keys returned by helper\n");
+ TALER_CRYPTO_helper_esign_disconnect (esh);
+ esh = NULL;
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return 1;
+ }
+ fprintf (stderr,
+ " Done (%u keys)\n",
+ num_keys);
+ ret = 0;
+ if (0 == ret)
+ ret = test_revocation (esh);
+ if (0 == ret)
+ ret = test_signing (esh);
+ if (0 == ret)
+ ret = perf_signing (esh,
+ "sequential");
+ if (NULL != esh)
+ {
+ TALER_CRYPTO_helper_esign_disconnect (esh);
+ esh = NULL;
+ }
+ if (0 == ret)
+ ret = par_signing (cfg);
+ /* clean up our state */
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ if (keys[i].valid)
+ {
+ keys[i].valid = false;
+ GNUNET_assert (num_keys > 0);
+ num_keys--;
+ }
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return ret;
+}
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ struct GNUNET_OS_Process *helper;
+ char *libexec_dir;
+ char *binary_name;
+ int ret;
+ enum GNUNET_OS_ProcessStatusType type;
+ unsigned long code;
+
+ (void) argc;
+ (void) argv;
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ GNUNET_log_setup ("test-helper-eddsa",
+ "INFO",
+ NULL);
+ GNUNET_OS_init (TALER_project_data_default ());
+ libexec_dir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_BINDIR);
+ GNUNET_asprintf (&binary_name,
+ "%s/%s",
+ libexec_dir,
+ "taler-exchange-secmod-eddsa");
+ GNUNET_free (libexec_dir);
+ helper = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
+ NULL, NULL, NULL,
+ binary_name,
+ binary_name,
+ "-c",
+ "test_helper_eddsa.conf",
+ "-L",
+ "INFO",
+ NULL);
+ if (NULL == helper)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "exec",
+ binary_name);
+ GNUNET_free (binary_name);
+ return 77;
+ }
+ GNUNET_free (binary_name);
+ ret = run_test ();
+
+ GNUNET_OS_process_kill (helper,
+ SIGTERM);
+ if (GNUNET_OK !=
+ GNUNET_OS_process_wait_status (helper,
+ &type,
+ &code))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Helper process did not die voluntarily, killing hard\n");
+ GNUNET_OS_process_kill (helper,
+ SIGKILL);
+ ret = 4;
+ }
+ else if ( (GNUNET_OS_PROCESS_EXITED != type) ||
+ (0 != code) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Helper died with unexpected status %d/%d\n",
+ (int) type,
+ (int) code);
+ ret = 5;
+ }
+ GNUNET_OS_process_destroy (helper);
+ return ret;
+}
+
+
+/* end of test_helper_eddsa.c */
diff --git a/src/util/test_helper_eddsa.conf b/src/util/test_helper_eddsa.conf
new file mode 100644
index 000000000..a13833c02
--- /dev/null
+++ b/src/util/test_helper_eddsa.conf
@@ -0,0 +1,9 @@
+[PATHS]
+# Persistent data storage for the testcase
+TALER_TEST_HOME = test_helper_eddsa_home/
+
+[taler-exchange-secmod-eddsa]
+CLIENT_DIR = $TALER_RUNTIME_DIR
+LOOKAHEAD_SIGN = 5 minutes
+OVERLAP_DURATION = 1 s
+DURATION = 1 minute
diff --git a/src/util/test_helper_rsa.c b/src/util/test_helper_rsa.c
new file mode 100644
index 000000000..2bc15879f
--- /dev/null
+++ b/src/util/test_helper_rsa.c
@@ -0,0 +1,1002 @@
+/*
+ This file is part of TALER
+ (C) 2020, 2021, 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 util/test_helper_rsa.c
+ * @brief Tests for RSA crypto helper
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+
+/**
+ * Configuration has 1 minute duration and 5 minutes lookahead, but
+ * we do not get 'revocations' for expired keys. So this must be
+ * large enough to deal with key rotation during the runtime of
+ * the benchmark.
+ */
+#define MAX_KEYS 1024
+
+/**
+ * How many random key revocations should we test?
+ */
+#define NUM_REVOKES 3
+
+/**
+ * How many iterations of the successful signing test should we run?
+ */
+#define NUM_SIGN_TESTS 5
+
+/**
+ * How many iterations of the successful signing test should we run
+ * during the benchmark phase?
+ */
+#define NUM_SIGN_PERFS 100
+
+/**
+ * How many parallel clients should we use for the parallel
+ * benchmark? (> 500 may cause problems with the max open FD number limit).
+ */
+#define NUM_CORES 8
+
+/**
+ * Number of keys currently in #keys.
+ */
+static unsigned int num_keys;
+
+/**
+ * Keys currently managed by the helper.
+ */
+struct KeyData
+{
+ /**
+ * Validity start point.
+ */
+ struct GNUNET_TIME_Timestamp start_time;
+
+ /**
+ * Key expires for signing at @e start_time plus this value.
+ */
+ struct GNUNET_TIME_Relative validity_duration;
+
+ /**
+ * Hash of the public key.
+ */
+ struct TALER_RsaPubHashP h_rsa;
+
+ /**
+ * Full public key.
+ */
+ struct TALER_DenominationPublicKey denom_pub;
+
+ /**
+ * Is this key currently valid?
+ */
+ bool valid;
+
+ /**
+ * Did the test driver revoke this key?
+ */
+ bool revoked;
+};
+
+/**
+ * Array of all the keys we got from the helper.
+ */
+static struct KeyData keys[MAX_KEYS];
+
+
+/**
+ * Release memory occupied by #keys.
+ */
+static void
+free_keys (void)
+{
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ if (keys[i].valid)
+ {
+ TALER_denom_pub_free (&keys[i].denom_pub);
+ keys[i].valid = false;
+ GNUNET_assert (num_keys > 0);
+ num_keys--;
+ }
+}
+
+
+/**
+ * Function called with information about available keys for signing. Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero. Stores the keys
+ * status in #keys.
+ *
+ * @param cls closure, NULL
+ * @param section_name name of the denomination type in the configuration;
+ * NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ * zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ * zero if the key has been revoked or purged
+ * @param h_rsa hash of the @a denom_pub that is available (or was purged)
+ * @param bs_pub the public key itself, NULL if the key was revoked or purged
+ * @param sm_pub public key of the security module, NULL if the key was revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was revoked or purged
+ * The signature was already verified against @a sm_pub.
+ */
+static void
+key_cb (void *cls,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Relative validity_duration,
+ const struct TALER_RsaPubHashP *h_rsa,
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
+ const struct TALER_SecurityModulePublicKeyP *sm_pub,
+ const struct TALER_SecurityModuleSignatureP *sm_sig)
+{
+ (void) cls;
+ (void) sm_pub;
+ (void) sm_sig;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Key notification about key %s in `%s'\n",
+ GNUNET_h2s (&h_rsa->hash),
+ section_name);
+ if (0 == validity_duration.rel_value_us)
+ {
+ bool found = false;
+
+ GNUNET_break (NULL == bs_pub);
+ GNUNET_break (NULL == section_name);
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ if (0 == GNUNET_memcmp (h_rsa,
+ &keys[i].h_rsa))
+ {
+ keys[i].valid = false;
+ keys[i].revoked = false;
+ TALER_denom_pub_free (&keys[i].denom_pub);
+ GNUNET_assert (num_keys > 0);
+ num_keys--;
+ found = true;
+ break;
+ }
+ if (! found)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error: helper announced expiration of unknown key!\n");
+
+ return;
+ }
+
+ GNUNET_break (NULL != bs_pub);
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ if (! keys[i].valid)
+ {
+ keys[i].valid = true;
+ keys[i].h_rsa = *h_rsa;
+ keys[i].start_time = start_time;
+ keys[i].validity_duration = validity_duration;
+ keys[i].denom_pub.bsign_pub_key
+ = GNUNET_CRYPTO_bsign_pub_incref (bs_pub);
+ num_keys++;
+ return;
+ }
+ /* too many keys! */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error: received %d live keys from the service!\n",
+ MAX_KEYS + 1);
+}
+
+
+/**
+ * Test key revocation logic.
+ *
+ * @param dh handle to the helper
+ * @return 0 on success
+ */
+static int
+test_revocation (struct TALER_CRYPTO_RsaDenominationHelper *dh)
+{
+ struct timespec req = {
+ .tv_nsec = 250000000
+ };
+
+ for (unsigned int i = 0; i<NUM_REVOKES; i++)
+ {
+ uint32_t off;
+
+ off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
+ num_keys);
+ /* find index of key to revoke */
+ for (unsigned int j = 0; j < MAX_KEYS; j++)
+ {
+ if (! keys[j].valid)
+ continue;
+ if (0 != off)
+ {
+ off--;
+ continue;
+ }
+ keys[j].revoked = true;
+ fprintf (stderr,
+ "Revoking key %s ...",
+ GNUNET_h2s (&keys[j].h_rsa.hash));
+ TALER_CRYPTO_helper_rsa_revoke (dh,
+ &keys[j].h_rsa);
+ for (unsigned int k = 0; k<1000; k++)
+ {
+ TALER_CRYPTO_helper_rsa_poll (dh);
+ if (! keys[j].revoked)
+ break;
+ nanosleep (&req, NULL);
+ fprintf (stderr, ".");
+ }
+ if (keys[j].revoked)
+ {
+ fprintf (stderr,
+ "\nFAILED: timeout trying to revoke key %u\n",
+ j);
+ TALER_CRYPTO_helper_rsa_disconnect (dh);
+ return 2;
+ }
+ fprintf (stderr, "\n");
+ break;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Test signing logic.
+ *
+ * @param dh handle to the helper
+ * @return 0 on success
+ */
+static int
+test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
+{
+ struct TALER_BlindedDenominationSignature ds;
+ enum TALER_ErrorCode ec;
+ bool success = false;
+ struct TALER_PlanchetMasterSecretP ps;
+ const struct TALER_ExchangeWithdrawValues *alg_values
+ = TALER_denom_ewv_rsa_singleton ();
+ struct TALER_AgeCommitmentHash ach;
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ &ps,
+ sizeof (ps));
+ TALER_planchet_setup_coin_priv (&ps,
+ alg_values,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (&ps,
+ alg_values,
+ &bks);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &ach,
+ sizeof(ach));
+
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ {
+ if (! keys[i].valid)
+ continue;
+ if (GNUNET_CRYPTO_BSA_RSA !=
+ keys[i].denom_pub.bsign_pub_key->cipher)
+ continue;
+ {
+ struct TALER_PlanchetDetail pd;
+
+ GNUNET_assert (GNUNET_YES ==
+ TALER_planchet_prepare (&keys[i].denom_pub,
+ alg_values,
+ &bks,
+ NULL,
+ &coin_priv,
+ &ach,
+ &c_hash,
+ &pd));
+ {
+ struct TALER_CRYPTO_RsaSignRequest rsr = {
+ .h_rsa = &keys[i].h_rsa,
+ .msg =
+ pd.blinded_planchet.blinded_message->details.rsa_blinded_message.
+ blinded_msg,
+ .msg_size =
+ pd.blinded_planchet.blinded_message->details.rsa_blinded_message.
+ blinded_msg_size
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requesting signature over %u bytes with key %s\n",
+ (unsigned int) rsr.msg_size,
+ GNUNET_h2s (&rsr.h_rsa->hash));
+ ec = TALER_CRYPTO_helper_rsa_sign (dh,
+ &rsr,
+ &ds);
+ }
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
+ }
+ switch (ec)
+ {
+ case TALER_EC_NONE:
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining (
+ keys[i].start_time.abs_time),
+ >,
+ GNUNET_TIME_UNIT_SECONDS))
+ {
+ /* key worked too early */
+ GNUNET_break (0);
+ return 4;
+ }
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration (
+ keys[i].start_time.abs_time),
+ >,
+ keys[i].validity_duration))
+ {
+ /* key worked too later */
+ GNUNET_break (0);
+ return 5;
+ }
+ {
+ struct TALER_DenominationSignature rs;
+
+ if (GNUNET_OK !=
+ TALER_denom_sig_unblind (&rs,
+ &ds,
+ &bks,
+ &c_hash,
+ alg_values,
+ &keys[i].denom_pub))
+ {
+ GNUNET_break (0);
+ return 6;
+ }
+ TALER_blinded_denom_sig_free (&ds);
+ if (GNUNET_OK !=
+ TALER_denom_pub_verify (&keys[i].denom_pub,
+ &rs,
+ &c_hash))
+ {
+ /* signature invalid */
+ GNUNET_break (0);
+ TALER_denom_sig_free (&rs);
+ return 7;
+ }
+ TALER_denom_sig_free (&rs);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received valid signature for key %s\n",
+ GNUNET_h2s (&keys[i].h_rsa.hash));
+ success = true;
+ break;
+ case TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY:
+ /* This 'failure' is expected, we're testing also for the
+ error handling! */
+ if ( (GNUNET_TIME_relative_is_zero (
+ GNUNET_TIME_absolute_get_remaining (
+ keys[i].start_time.abs_time))) &&
+ (GNUNET_TIME_relative_cmp (
+ GNUNET_TIME_absolute_get_duration (
+ keys[i].start_time.abs_time),
+ <,
+ keys[i].validity_duration)) )
+ {
+ /* key should have worked! */
+ GNUNET_break (0);
+ return 6;
+ }
+ break;
+ default:
+ /* unexpected error */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected error %d at %s:%u\n",
+ ec,
+ __FILE__,
+ __LINE__);
+ return 7;
+ }
+ }
+ if (! success)
+ {
+ /* no valid key for signing found, also bad */
+ GNUNET_break (0);
+ return 16;
+ }
+
+ /* check signing does not work if the key is unknown */
+ {
+ struct TALER_RsaPubHashP rnd;
+ struct TALER_CRYPTO_RsaSignRequest rsr = {
+ .h_rsa = &rnd,
+ .msg = "Hello",
+ .msg_size = strlen ("Hello")
+ };
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &rnd,
+ sizeof (rnd));
+ ec = TALER_CRYPTO_helper_rsa_sign (dh,
+ &rsr,
+ &ds);
+ if (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN != ec)
+ {
+ if (TALER_EC_NONE == ec)
+ TALER_blinded_denom_sig_free (&ds);
+ GNUNET_break (0);
+ return 17;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing with invalid key %s failed as desired\n",
+ GNUNET_h2s (&rnd.hash));
+ }
+ return 0;
+}
+
+
+/**
+ * Test batch signing logic.
+ *
+ * @param dh handle to the helper
+ * @param batch_size how large should the batch be
+ * @param check_sigs also check unknown key and signatures
+ * @return 0 on success
+ */
+static int
+test_batch_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
+ unsigned int batch_size,
+ bool check_sigs)
+{
+ struct TALER_BlindedDenominationSignature ds[batch_size];
+ enum TALER_ErrorCode ec;
+ bool success = false;
+ struct TALER_PlanchetMasterSecretP ps[batch_size];
+ const struct TALER_ExchangeWithdrawValues *alg_values;
+ struct TALER_AgeCommitmentHash ach[batch_size];
+ struct TALER_CoinPubHashP c_hash[batch_size];
+ struct TALER_CoinSpendPrivateKeyP coin_priv[batch_size];
+ union GNUNET_CRYPTO_BlindingSecretP bks[batch_size];
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ &ps,
+ sizeof (ps));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &ach,
+ sizeof(ach));
+ alg_values = TALER_denom_ewv_rsa_singleton ();
+ for (unsigned int i = 0; i<batch_size; i++)
+ {
+ TALER_planchet_setup_coin_priv (&ps[i],
+ alg_values,
+ &coin_priv[i]);
+ TALER_planchet_blinding_secret_create (&ps[i],
+ alg_values,
+ &bks[i]);
+ }
+ for (unsigned int k = 0; k<MAX_KEYS; k++)
+ {
+ if (success && ! check_sigs)
+ break; /* only do one round */
+ if (! keys[k].valid)
+ continue;
+ if (GNUNET_CRYPTO_BSA_RSA !=
+ keys[k].denom_pub.bsign_pub_key->cipher)
+ continue;
+ {
+ struct TALER_PlanchetDetail pd[batch_size];
+ struct TALER_CRYPTO_RsaSignRequest rsr[batch_size];
+
+ for (unsigned int i = 0; i<batch_size; i++)
+ {
+ GNUNET_assert (GNUNET_YES ==
+ TALER_planchet_prepare (&keys[k].denom_pub,
+ alg_values,
+ &bks[i],
+ NULL,
+ &coin_priv[i],
+ &ach[i],
+ &c_hash[i],
+ &pd[i]));
+ rsr[i].h_rsa
+ = &keys[k].h_rsa;
+ rsr[i].msg
+ = pd[i].blinded_planchet.blinded_message->details.rsa_blinded_message.
+ blinded_msg;
+ rsr[i].msg_size
+ = pd[i].blinded_planchet.blinded_message->details.rsa_blinded_message.
+ blinded_msg_size;
+ }
+ ec = TALER_CRYPTO_helper_rsa_batch_sign (dh,
+ batch_size,
+ rsr,
+ ds);
+ for (unsigned int i = 0; i<batch_size; i++)
+ {
+ if (TALER_EC_NONE == ec)
+ GNUNET_break (GNUNET_CRYPTO_BSA_RSA ==
+ ds[i].blinded_sig->cipher);
+ TALER_blinded_planchet_free (&pd[i].blinded_planchet);
+ }
+ }
+ switch (ec)
+ {
+ case TALER_EC_NONE:
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining (
+ keys[k].start_time.abs_time),
+ >,
+ GNUNET_TIME_UNIT_SECONDS))
+ {
+ /* key worked too early */
+ GNUNET_break (0);
+ return 4;
+ }
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration (
+ keys[k].start_time.abs_time),
+ >,
+ keys[k].validity_duration))
+ {
+ /* key worked too later */
+ GNUNET_break (0);
+ return 5;
+ }
+ for (unsigned int i = 0; i<batch_size; i++)
+ {
+ struct TALER_DenominationSignature rs;
+
+ if (check_sigs)
+ {
+ if (GNUNET_OK !=
+ TALER_denom_sig_unblind (&rs,
+ &ds[i],
+ &bks[i],
+ &c_hash[i],
+ alg_values,
+ &keys[k].denom_pub))
+ {
+ GNUNET_break (0);
+ return 6;
+ }
+ }
+ TALER_blinded_denom_sig_free (&ds[i]);
+ if (check_sigs)
+ {
+ if (GNUNET_OK !=
+ TALER_denom_pub_verify (&keys[k].denom_pub,
+ &rs,
+ &c_hash[i]))
+ {
+ /* signature invalid */
+ GNUNET_break (0);
+ TALER_denom_sig_free (&rs);
+ return 7;
+ }
+ TALER_denom_sig_free (&rs);
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received valid signature for key %s\n",
+ GNUNET_h2s (&keys[k].h_rsa.hash));
+ success = true;
+ break;
+ case TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY:
+ /* This 'failure' is expected, we're testing also for the
+ error handling! */
+ for (unsigned int i = 0; i<batch_size; i++)
+ TALER_blinded_denom_sig_free (&ds[i]);
+ if ( (GNUNET_TIME_relative_is_zero (
+ GNUNET_TIME_absolute_get_remaining (
+ keys[k].start_time.abs_time))) &&
+ (GNUNET_TIME_relative_cmp (
+ GNUNET_TIME_absolute_get_duration (
+ keys[k].start_time.abs_time),
+ <,
+ keys[k].validity_duration)) )
+ {
+ /* key should have worked! */
+ GNUNET_break (0);
+ return 6;
+ }
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN:
+ for (unsigned int i = 0; i<batch_size; i++)
+ TALER_blinded_denom_sig_free (&ds[i]);
+ break;
+ default:
+ /* unexpected error */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected error %d at %s:%u\n",
+ ec,
+ __FILE__,
+ __LINE__);
+ for (unsigned int i = 0; i<batch_size; i++)
+ TALER_blinded_denom_sig_free (&ds[i]);
+ return 7;
+ }
+ }
+ if (! success)
+ {
+ /* no valid key for signing found, also bad */
+ GNUNET_break (0);
+ return 16;
+ }
+
+ /* check signing does not work if the key is unknown */
+ if (check_sigs)
+ {
+ struct TALER_RsaPubHashP rnd;
+ struct TALER_CRYPTO_RsaSignRequest rsr = {
+ .h_rsa = &rnd,
+ .msg = "Hello",
+ .msg_size = strlen ("Hello")
+ };
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &rnd,
+ sizeof (rnd));
+ ec = TALER_CRYPTO_helper_rsa_batch_sign (dh,
+ 1,
+ &rsr,
+ ds);
+ if (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN != ec)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Signing with invalid key returned unexpected status %d\n",
+ ec);
+ if (TALER_EC_NONE == ec)
+ TALER_blinded_denom_sig_free (ds);
+ GNUNET_break (0);
+ return 17;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing with invalid key %s failed as desired\n",
+ GNUNET_h2s (&rnd.hash));
+ }
+ return 0;
+}
+
+
+/**
+ * Benchmark signing logic.
+ *
+ * @param dh handle to the helper
+ * @return 0 on success
+ */
+static int
+perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
+ const char *type)
+{
+ struct TALER_BlindedDenominationSignature ds;
+ enum TALER_ErrorCode ec;
+ struct GNUNET_TIME_Relative duration;
+ struct TALER_PlanchetMasterSecretP ps;
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ struct TALER_AgeCommitmentHash ach;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ const struct TALER_ExchangeWithdrawValues *alg_values
+ = TALER_denom_ewv_rsa_singleton ();
+
+ TALER_planchet_master_setup_random (&ps);
+ TALER_planchet_setup_coin_priv (&ps,
+ alg_values,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (&ps,
+ alg_values,
+ &bks);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &ach,
+ sizeof(ach));
+ duration = GNUNET_TIME_UNIT_ZERO;
+ TALER_CRYPTO_helper_rsa_poll (dh);
+ for (unsigned int j = 0; j<NUM_SIGN_PERFS;)
+ {
+ for (unsigned int i = 0; i<MAX_KEYS; i++)
+ {
+ if (! keys[i].valid)
+ continue;
+ if (GNUNET_CRYPTO_BSA_RSA !=
+ keys[i].denom_pub.bsign_pub_key->cipher)
+ continue;
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining (
+ keys[i].start_time.abs_time),
+ >,
+ GNUNET_TIME_UNIT_SECONDS))
+ continue;
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration (
+ keys[i].start_time.abs_time),
+ >,
+ keys[i].validity_duration))
+ continue;
+ {
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_PlanchetDetail pd;
+
+ GNUNET_assert (GNUNET_YES ==
+ TALER_planchet_prepare (&keys[i].denom_pub,
+ alg_values,
+ &bks,
+ NULL,
+ &coin_priv,
+ &ach,
+ &c_hash,
+ &pd));
+ /* use this key as long as it works */
+ while (1)
+ {
+ struct GNUNET_TIME_Absolute start = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Relative delay;
+ struct TALER_CRYPTO_RsaSignRequest rsr = {
+ .h_rsa = &keys[i].h_rsa,
+ .msg =
+ pd.blinded_planchet.blinded_message->details.rsa_blinded_message.
+ blinded_msg,
+ .msg_size =
+ pd.blinded_planchet.blinded_message->details.rsa_blinded_message.
+ blinded_msg_size
+ };
+
+ ec = TALER_CRYPTO_helper_rsa_sign (dh,
+ &rsr,
+ &ds);
+ if (TALER_EC_NONE != ec)
+ break;
+ delay = GNUNET_TIME_absolute_get_duration (start);
+ duration = GNUNET_TIME_relative_add (duration,
+ delay);
+ TALER_blinded_denom_sig_free (&ds);
+ j++;
+ if (NUM_SIGN_PERFS <= j)
+ break;
+ }
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
+ }
+ } /* for i */
+ } /* for j */
+ fprintf (stderr,
+ "%u (%s) signature operations took %s\n",
+ (unsigned int) NUM_SIGN_PERFS,
+ type,
+ GNUNET_STRINGS_relative_time_to_string (duration,
+ GNUNET_YES));
+ return 0;
+}
+
+
+/**
+ * Parallel signing logic.
+ *
+ * @param esh handle to the helper
+ * @return 0 on success
+ */
+static int
+par_signing (struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ struct GNUNET_TIME_Absolute start;
+ struct GNUNET_TIME_Relative duration;
+ pid_t pids[NUM_CORES];
+ struct TALER_CRYPTO_RsaDenominationHelper *dh;
+
+ start = GNUNET_TIME_absolute_get ();
+ for (unsigned int i = 0; i<NUM_CORES; i++)
+ {
+ pids[i] = fork ();
+ num_keys = 0;
+ GNUNET_assert (-1 != pids[i]);
+ if (0 == pids[i])
+ {
+ int ret;
+
+ dh = TALER_CRYPTO_helper_rsa_connect (cfg,
+ "taler-exchange",
+ &key_cb,
+ NULL);
+ GNUNET_assert (NULL != dh);
+ ret = perf_signing (dh,
+ "parallel");
+ TALER_CRYPTO_helper_rsa_disconnect (dh);
+ free_keys ();
+ exit (ret);
+ }
+ }
+ for (unsigned int i = 0; i<NUM_CORES; i++)
+ {
+ int wstatus;
+
+ GNUNET_assert (pids[i] ==
+ waitpid (pids[i],
+ &wstatus,
+ 0));
+ }
+ duration = GNUNET_TIME_absolute_get_duration (start);
+ fprintf (stderr,
+ "%u (parallel) signature operations took %s (total real time)\n",
+ (unsigned int) NUM_SIGN_PERFS * NUM_CORES,
+ GNUNET_STRINGS_relative_time_to_string (duration,
+ GNUNET_YES));
+ return 0;
+}
+
+
+/**
+ * Main entry point into the test logic with the helper already running.
+ */
+static int
+run_test (void)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+ struct TALER_CRYPTO_RsaDenominationHelper *dh;
+ struct timespec req = {
+ .tv_nsec = 250000000
+ };
+ int ret;
+
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_load (cfg,
+ "test_helper_rsa.conf"))
+ {
+ GNUNET_break (0);
+ return 77;
+ }
+
+ fprintf (stderr,
+ "Waiting for helper to start ... ");
+ for (unsigned int i = 0; i<100; i++)
+ {
+ nanosleep (&req,
+ NULL);
+ dh = TALER_CRYPTO_helper_rsa_connect (cfg,
+ "taler-exchange",
+ &key_cb,
+ NULL);
+ if (NULL != dh)
+ break;
+ fprintf (stderr, ".");
+ }
+ if (NULL == dh)
+ {
+ fprintf (stderr,
+ "\nFAILED: timeout trying to connect to helper\n");
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return 1;
+ }
+ if (0 == num_keys)
+ {
+ fprintf (stderr,
+ "\nFAILED: timeout trying to connect to helper\n");
+ TALER_CRYPTO_helper_rsa_disconnect (dh);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return 1;
+ }
+ fprintf (stderr,
+ " Done (%u keys)\n",
+ num_keys);
+ ret = 0;
+ if (0 == ret)
+ ret = test_revocation (dh);
+ if (0 == ret)
+ ret = test_signing (dh);
+ if (0 == ret)
+ ret = test_batch_signing (dh,
+ 2,
+ true);
+ if (0 == ret)
+ ret = test_batch_signing (dh,
+ 256,
+ true);
+ for (unsigned int i = 0; i<5; i++)
+ {
+ static unsigned int batches[] = { 1, 4, 16, 64, 256 };
+ unsigned int batch_size = batches[i];
+ struct GNUNET_TIME_Absolute start;
+ struct GNUNET_TIME_Relative duration;
+
+ start = GNUNET_TIME_absolute_get ();
+ if (0 != ret)
+ break;
+ ret = test_batch_signing (dh,
+ batch_size,
+ false);
+ duration = GNUNET_TIME_absolute_get_duration (start);
+ fprintf (stderr,
+ "%4u (batch) signature operations took %s (total real time)\n",
+ (unsigned int) batch_size,
+ GNUNET_STRINGS_relative_time_to_string (duration,
+ GNUNET_YES));
+ }
+ if (0 == ret)
+ ret = perf_signing (dh,
+ "sequential");
+ TALER_CRYPTO_helper_rsa_disconnect (dh);
+ free_keys ();
+ if (0 == ret)
+ ret = par_signing (cfg);
+ /* clean up our state */
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return ret;
+}
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ struct GNUNET_OS_Process *helper;
+ char *libexec_dir;
+ char *binary_name;
+ int ret;
+ enum GNUNET_OS_ProcessStatusType type;
+ unsigned long code;
+
+ (void) argc;
+ (void) argv;
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ GNUNET_log_setup ("test-helper-rsa",
+ "WARNING",
+ NULL);
+ GNUNET_OS_init (TALER_project_data_default ());
+ libexec_dir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_BINDIR);
+ GNUNET_asprintf (&binary_name,
+ "%s/%s",
+ libexec_dir,
+ "taler-exchange-secmod-rsa");
+ GNUNET_free (libexec_dir);
+ helper = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
+ NULL, NULL, NULL,
+ binary_name,
+ binary_name,
+ "-c",
+ "test_helper_rsa.conf",
+ "-L",
+ "WARNING",
+ NULL);
+ if (NULL == helper)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "exec",
+ binary_name);
+ GNUNET_free (binary_name);
+ return 77;
+ }
+ GNUNET_free (binary_name);
+ ret = run_test ();
+
+ GNUNET_OS_process_kill (helper,
+ SIGTERM);
+ if (GNUNET_OK !=
+ GNUNET_OS_process_wait_status (helper,
+ &type,
+ &code))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Helper process did not die voluntarily, killing hard\n");
+ GNUNET_OS_process_kill (helper,
+ SIGKILL);
+ ret = 4;
+ }
+ else if ( (GNUNET_OS_PROCESS_EXITED != type) ||
+ (0 != code) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Helper died with unexpected status %d/%d\n",
+ (int) type,
+ (int) code);
+ ret = 5;
+ }
+ GNUNET_OS_process_destroy (helper);
+ return ret;
+}
+
+
+/* end of test_helper_rsa.c */
diff --git a/src/util/test_helper_rsa.conf b/src/util/test_helper_rsa.conf
new file mode 100644
index 000000000..d50e64d95
--- /dev/null
+++ b/src/util/test_helper_rsa.conf
@@ -0,0 +1,12 @@
+[PATHS]
+# Persistent data storage for the testcase
+TALER_TEST_HOME = test_helper_rsa_home/
+
+[coin_1]
+DURATION_WITHDRAW = 1 minute
+CIPHER = RSA
+RSA_KEYSIZE = 2048
+
+[taler-exchange-secmod-rsa]
+LOOKAHEAD_SIGN = 5 minutes
+OVERLAP_DURATION = 1 s
diff --git a/src/util/test_payto.c b/src/util/test_payto.c
index 7dc2eb142..62ba7d28e 100644
--- a/src/util/test_payto.c
+++ b/src/util/test_payto.c
@@ -22,14 +22,16 @@
#include "taler_util.h"
#define CHECK(a,b) do { \
- if (0 != strcmp (a,b)) { \
- GNUNET_break (0); \
- fprintf (stderr, "Got %s, wanted %s\n", b, a); \
- GNUNET_free (b); \
- return 1; \
- } else { \
- GNUNET_free (b); \
- } \
+ GNUNET_assert (a != NULL); \
+ GNUNET_assert (b != NULL); \
+ if (0 != strcmp (a,b)) { \
+ GNUNET_break (0); \
+ fprintf (stderr, "Got %s, wanted %s\n", b, a); \
+ GNUNET_free (b); \
+ return 1; \
+ } else { \
+ GNUNET_free (b); \
+ } \
} while (0)
@@ -44,14 +46,71 @@ main (int argc,
GNUNET_log_setup ("test-payto",
"WARNING",
NULL);
+ GNUNET_assert (NULL ==
+ TALER_iban_validate ("FR1420041010050500013M02606"));
+ GNUNET_assert (NULL ==
+ TALER_iban_validate ("DE89370400440532013000"));
+ r = TALER_payto_validate (
+ "payto://x-taler-bank/hostname/username?receiver-name=foo");
+ GNUNET_assert (NULL == r);
+ r = TALER_payto_validate (
+ "payto://x-taler-bank/hostname/~path/username?receiver-name=foo");
+ GNUNET_assert (NULL == r);
+ r = TALER_payto_validate (
+ "payto://x-taler-bank/hostname/~path/username?receiver-name=fo/o");
+ GNUNET_assert (NULL == r);
+ r = TALER_payto_validate (
+ "payto://x-taler-bank/hostname/path/username?receiver-name=foo");
+ GNUNET_assert (NULL == r);
+ r = TALER_payto_validate (
+ "payto://x-taler-bank/https://hostname/username?receiver-name=foo");
+ GNUNET_assert (NULL != r);
+ GNUNET_free (r);
+ r = TALER_payto_validate (
+ "payto://x-taler-bank/hostname:4a2/path/username?receiver-name=foo");
+ GNUNET_assert (NULL != r);
+ GNUNET_free (r);
+ r = TALER_payto_validate (
+ "payto://x-taler-bank/-hostname/username?receiver-name=foo");
+ GNUNET_assert (NULL != r);
+ GNUNET_free (r);
+ r = TALER_payto_validate (
+ "payto://x-taler-bank/domain..name/username?receiver-name=foo");
+ GNUNET_assert (NULL != r);
+ GNUNET_free (r);
+ r = TALER_payto_validate (
+ "payto://x-taler-bank/domain..name/?receiver-name=foo");
+ GNUNET_assert (NULL != r);
+ GNUNET_free (r);
+ r = TALER_payto_validate (
+ "payto://x-taler-bank/domain.name/username");
+ GNUNET_assert (NULL != r);
+ GNUNET_free (r);
r = TALER_xtalerbank_account_from_payto (
"payto://x-taler-bank/localhost:1080/alice");
CHECK ("alice",
r);
r = TALER_xtalerbank_account_from_payto (
+ "payto://x-taler-bank/localhost:1080/path/alice");
+ CHECK ("alice",
+ r);
+ r = TALER_xtalerbank_account_from_payto (
+ "payto://x-taler-bank/localhost:1080/path/alice?receiver-name=ali/cia");
+ CHECK ("alice",
+ r);
+ r = TALER_xtalerbank_account_from_payto (
"payto://x-taler-bank/localhost:1080/alice?subject=hello&amount=EUR:1");
CHECK ("alice",
r);
+
+ r = TALER_payto_get_subject (
+ "payto://x-taler-bank/localhost:1080/alice?subject=hello&amount=EUR:1");
+ CHECK ("hello",
+ r);
+
+ r = TALER_payto_get_subject (
+ "payto://x-taler-bank/localhost:1080/alice");
+ GNUNET_assert (r == NULL);
return 0;
}
diff --git a/src/util/tv_age_restriction.c b/src/util/tv_age_restriction.c
new file mode 100644
index 000000000..9fc2b4823
--- /dev/null
+++ b/src/util/tv_age_restriction.c
@@ -0,0 +1,271 @@
+/**
+ * @file util/tv_age_restriction.c
+ * @brief Generate test vectors for age restriction
+ * @author Özgür Kesim
+ *
+ * compile in exchange/src/util with
+ *
+ * gcc tv_age_restriction.c \
+ * -lgnunetutil -lgnunetjson -lsodium -ljansson \
+ * -L/usr/lib/x86_64-linux-gnu -lmicrohttpd -ltalerutil \
+ * -I../include \
+ * -o tv_age_restriction
+ *
+ */
+#include "platform.h"
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <taler/taler_util.h>
+#include <taler/taler_crypto_lib.h>
+
+static struct TALER_AgeMask age_masks[] = {
+ { .bits = 1
+ | 1 << 8 | 1 << 14 | 1 << 18 },
+ { .bits = 1
+ | 1 << 8 | 1 << 10 | 1 << 12
+ | 1 << 14 | 1 << 16 | 1 << 18 | 1 << 21 },
+};
+
+extern uint8_t
+get_age_group (
+ const struct TALER_AgeMask *mask,
+ uint8_t age);
+
+/**
+ * Encodes the age mask into a string, like "8:10:12:14:16:18:21"
+ */
+char *
+age_mask_to_string (
+ const struct TALER_AgeMask *mask)
+{
+ uint32_t bits = mask->bits;
+ unsigned int n = 0;
+ char *buf = GNUNET_malloc (32 * 3); // max characters possible
+ char *pos = buf;
+
+ if (NULL == buf)
+ {
+ return buf;
+ }
+
+ while (bits != 0)
+ {
+ bits >>= 1;
+ n++;
+ if (0 == (bits & 1))
+ {
+ continue;
+ }
+
+ if (n > 9)
+ {
+ *(pos++) = '0' + n / 10;
+ }
+ *(pos++) = '0' + n % 10;
+
+ if (0 != (bits >> 1))
+ {
+ *(pos++) = ':';
+ }
+ }
+ return buf;
+}
+
+
+static json_t *
+cp_to_j (
+ const struct GNUNET_HashCode *seed,
+ struct TALER_AgeCommitmentProof *acp,
+ uint8_t seq)
+{
+ json_t *j_commitment;
+ json_t *j_proof;
+ json_t *j_pubs;
+ json_t *j_privs;
+ struct TALER_AgeCommitmentHash hac = {0};
+ char buf[256] = {0};
+
+ TALER_age_commitment_hash (&acp->commitment, &hac);
+
+ j_pubs = json_array ();
+ GNUNET_assert (NULL != j_pubs);
+ for (unsigned int i = 0; i < acp->commitment.num; i++)
+ {
+ json_t *j_pub = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto (NULL,
+ &acp->commitment.keys[i]));
+ json_array_append_new (j_pubs, j_pub);
+ }
+
+ j_commitment = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("num", acp->commitment.num),
+ GNUNET_JSON_pack_array_steal ("edx25519_pubs", j_pubs),
+ GNUNET_JSON_pack_data_auto ("h_age_commitment", &hac));
+
+
+ j_privs = json_array ();
+ GNUNET_assert (NULL != j_privs);
+ for (unsigned int i = 0; i < acp->proof.num; i++)
+ {
+ json_t *j_priv = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto (NULL,
+ &acp->proof.keys[i]));
+ json_array_append_new (j_privs, j_priv);
+ }
+ j_proof = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("num", acp->proof.num),
+ GNUNET_JSON_pack_array_steal ("edx25519_privs", j_privs));
+
+ if (0 == seq)
+ {
+ strcpy (buf, "commit()");
+ }
+ else
+ {
+ sprintf (buf,
+ "derive_from(%d)",
+ seq);
+ }
+
+ return GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("generated_by", buf),
+ GNUNET_JSON_pack_data_auto ("seed", seed),
+ GNUNET_JSON_pack_object_steal ("proof", j_proof),
+ GNUNET_JSON_pack_object_steal ("commitment", j_commitment));
+
+};
+
+static json_t *
+generate (
+ struct TALER_AgeMask *mask)
+{
+ uint8_t age;
+ json_t *j_commitproofs;
+ j_commitproofs = json_array ();
+
+ for (age = 0; age < 24; age += 2)
+ {
+ json_t *j_top = json_object ();
+ json_t *j_seq = json_array ();
+ enum GNUNET_GenericReturnValue ret;
+ struct TALER_AgeCommitmentProof acp[3] = {0};
+ uint8_t age_group = get_age_group (mask, age);
+ struct GNUNET_HashCode seed;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &seed,
+ sizeof(seed));
+
+ json_object_set (j_top,
+ "committed_age",
+ json_integer (age));
+
+ ret = TALER_age_restriction_commit (mask,
+ age,
+ &seed,
+ &acp[0]);
+
+ GNUNET_assert (GNUNET_OK == ret);
+
+ /* Also derive two more commitments right away */
+ for (uint8_t i = 0; i<2; i++)
+ {
+ struct GNUNET_HashCode salt;
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &salt,
+ sizeof (salt));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_age_commitment_derive (&acp[i],
+ &salt,
+ &acp[i + 1]));
+ }
+
+ for (uint8_t i = 0; i < 3; i++)
+ {
+ json_t *j_cp = cp_to_j (&seed, &acp[i], i);
+ json_t *j_attestations = json_array ();
+
+ for (uint8_t min = 0; min < 22; min++)
+ {
+ json_t *j_attest = json_object ();
+ json_t *j_reason;
+ uint8_t min_group = get_age_group (mask, min);
+ struct TALER_AgeAttestation at = {0};
+
+ json_object_set (j_attest,
+ "required_minimum_age",
+ json_integer (min));
+ json_object_set (j_attest,
+ "calculated_age_group",
+ json_integer (min_group));
+
+ ret = TALER_age_commitment_attest (&acp[i],
+ min,
+ &at);
+
+
+ if (0 == min_group)
+ j_reason = json_string (
+ "not required: age group is 0");
+ else if (min_group > age_group)
+ j_reason = json_string (
+ "not applicable: committed age too small");
+ else
+ j_reason = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto (NULL, &at));
+
+ json_object_set (j_attest,
+ "attestation",
+ j_reason);
+
+ json_array_append_new (j_attestations,
+ j_attest);
+
+ }
+
+ json_object_set (j_cp, "attestations", j_attestations);
+ json_array_append (j_seq, j_cp);
+
+ TALER_age_commitment_proof_free (&acp[i]);
+ }
+
+ json_object_set (j_top, "commitment_proof_attestation_seq", j_seq);
+ json_array_append_new (j_commitproofs, j_top);
+ }
+
+ return j_commitproofs;
+}
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ (void) argc;
+ (void) argv;
+ json_t *j_data = json_array ();
+ for (unsigned int i = 0; i < 2; i++)
+ {
+ struct TALER_AgeMask mask = age_masks[i];
+ json_t *j_test = json_object ();
+ json_object_set (j_test,
+ "age_groups",
+ json_string (age_mask_to_string (&mask)));
+ json_object_set (j_test,
+ "age_mask",
+ json_integer (mask.bits));
+ json_object_set (j_test,
+ "test_data",
+ generate (&mask));
+ json_array_append_new (j_data, j_test);
+ }
+ printf ("%s\n", json_dumps (j_data, JSON_INDENT (2)
+ | JSON_COMPACT));
+
+ json_decref (j_data);
+ return 0;
+}
+
+
+/* end of tv_age_restriction.c */
diff --git a/src/util/tv_age_restriction.json b/src/util/tv_age_restriction.json
new file mode 100644
index 000000000..e0c9cfc44
--- /dev/null
+++ b/src/util/tv_age_restriction.json
@@ -0,0 +1,9764 @@
+[
+ {
+ "age_groups":"8:14:18",
+ "age_mask":278785,
+ "test_data":[
+ {
+ "commited_age":0,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"5SX8V28APB16XW6YJKNQAS7W6254C8MSCEA0YGEZR7CAM5N9KXPPJERKK6XGFEC21C21568VY1AYHWRS1G41GB9X520D9XZ85AHRRP0",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "HAMP2FPG381SY4E9E14R0SYG3AYMVHPVJ3VPBENJNZ66GC7GF7TG",
+ "915FEJ4C3R4FBFRDQP7E0BVEN52V17Q68SHHMCK2BN1KR3XA7SE0",
+ "Y568AFKGFD7MFBEAN96NCTAFRNJJJ84PX2J5VB837MRMNRDGWRV0"
+ ],
+ "h_age_commitment":"Z5KYVFENM1HV93MDG90Q6XXMAFMZSNNVTABG170VMY9J7Q2832RG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"5SX8V28APB16XW6YJKNQAS7W6254C8MSCEA0YGEZR7CAM5N9KXPPJERKK6XGFEC21C21568VY1AYHWRS1G41GB9X520D9XZ85AHRRP0",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "P9MRXBN21AW7CSFPA03RY4RDD8FCY88ZXG0YENGWN0HEW17Q6X20",
+ "J19MKEY2P1CFAM7QMM9D53FTVVHC7PD17S5QWMXGE2MDJGXN7S8G",
+ "9DJTDHTVDQAD7HHTRAAVZHNMK5P8FQTEQHK1VX1DM5MJY4VS22HG"
+ ],
+ "h_age_commitment":"GW7QDAFFMJTVS5ZV9G8K4RARME3GS5CH9W6Z56QZTT1AVEBBS62G"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"5SX8V28APB16XW6YJKNQAS7W6254C8MSCEA0YGEZR7CAM5N9KXPPJERKK6XGFEC21C21568VY1AYHWRS1G41GB9X520D9XZ85AHRRP0",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "WX2W90VRGY6DA1JPA50HWJ9K7QJNAWG2ZN4EW93PFH09KZSNBTC0",
+ "5K2KHCTCMH33Z1CD8TP4JWY653HS1018SPZ00KAP7CK99XFKEGF0",
+ "08BHPJRAMB0ZWECJJGD422A0F04R9MC6RQRCWABVFM44F0NDBE6G"
+ ],
+ "h_age_commitment":"RZ2JVFN263HCMWCD24GP6KW9WX9W3M7TAP20VGH90ZT3F7E37GV0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":2,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"G38XFGPXHJQVNA326P41RRMB5JDCJM42DMNSJ6CTPKJHCJGVN2QPMRNQ1F365JJAV9SX6GYCEB4JXG45VCAX1ZGDCETGXY1FHS1SPZG",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "BDKGKAP7XYVS6VEY661BD5D29HYXXPVNA6EVSHN5FDK6CCCCBH0G",
+ "0ZJS7N0D91AY1HVP0B41X1C7PGJ66KDPQCE6D9WFCKKE8W1Q9YTG",
+ "QMFSVE4G3JE1DCS4X305W8R89YVSG0YSRKS18BB1RZ3TZX8TGFSG"
+ ],
+ "h_age_commitment":"ZEGNWEVQV6MWAN9HJRX7MN59ZFBZ14WK2M87ZJZ537QC7HD0Z4Q0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"G38XFGPXHJQVNA326P41RRMB5JDCJM42DMNSJ6CTPKJHCJGVN2QPMRNQ1F365JJAV9SX6GYCEB4JXG45VCAX1ZGDCETGXY1FHS1SPZG",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "83K5YRKK1YB8MV2SG43W1152RQA0X06Y01QEGC84AXQF0D6H1H8G",
+ "5A55B3B64E6NX7Q7F0RRPAE1XVVXX6FKKZ29SPKSZ8F74F0WB6P0",
+ "F2W8Y8PXEEPS4392112S2NS7SA8976Z88TRHS6MYW1MGGPD355E0"
+ ],
+ "h_age_commitment":"YY3YFQPJT28QE4Y7RJ5R0V2WKR6A4AMMR6WF5BBKB0704BFP12T0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"G38XFGPXHJQVNA326P41RRMB5JDCJM42DMNSJ6CTPKJHCJGVN2QPMRNQ1F365JJAV9SX6GYCEB4JXG45VCAX1ZGDCETGXY1FHS1SPZG",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "DMR4CZ34HJCWFSK4GC8QRKF6ZSAT3DSCH8T527729HRESBHA57KG",
+ "W814ZAKCH5W3SDCGPP0T4DVVSAKD3XQ6J5DXNPA01KSGR1J2ZTP0",
+ "HEBV9DC4HRC7MMSBJ46WWX6DAGVFQAQPMQM9FNEHET5RKATGV9G0"
+ ],
+ "h_age_commitment":"GSC52EJ6M6JZ1ZF98Y46B5E9FTK6W6DDDFAZHKNE00D0YCB6J8NG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":4,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"1SDTX459ENBEJYKWGPCPWSV10FB3Y10FZHP5W7J93F8GX0FGEQKT9T5W6TW19TZ9QXJHP88M4PZNJDFWFN5J07DA61EN0C9KP9TFMCR",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "KMWRF8YT11TBJFWFY1VD69QMB05H6BGCQ2DNBWW71EDZBN5E0GB0",
+ "0CBYDC8EV13WN5KDCYBZBC8KE1WZNF4H249JPYZCT314252VD800",
+ "8Q0SKZZN69SZF08GJ13XH5EHSKKYF1HWDZC9ZSJSTHG9TYEDQJMG"
+ ],
+ "h_age_commitment":"100D0J1QBVTK6WQAJPBCG2VPECKD1D91Q75CNCHNTZ4YRQ02WTE0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"1SDTX459ENBEJYKWGPCPWSV10FB3Y10FZHP5W7J93F8GX0FGEQKT9T5W6TW19TZ9QXJHP88M4PZNJDFWFN5J07DA61EN0C9KP9TFMCR",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "9MQX7TD4WX28H87B958P83D02T0HB0S4XPV72Z4TXT9RG53QS3F0",
+ "RDVVD4ACSBMX4X44NG19V4E32MG95BHQAJSGM4C08G1KCGFRT44G",
+ "D7TENF4V347FETDKG2VRKK6Z92VPGGWEXECT4A6PCXG6DYTKA1Z0"
+ ],
+ "h_age_commitment":"11FXVH30Z3AJ7WSNF387VT6Y9GCD61N3T13ACDKAXDP4XFAQHA6G"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"1SDTX459ENBEJYKWGPCPWSV10FB3Y10FZHP5W7J93F8GX0FGEQKT9T5W6TW19TZ9QXJHP88M4PZNJDFWFN5J07DA61EN0C9KP9TFMCR",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "NEPX42NXKBBFYKDC3R2WND1N1FJN3M3RPRH5D19JKNSMCG4SABAG",
+ "FN6G98WGS312TH9QD9HNFXDXPKEWRW5YJ2S6YR4XSXFYF9SY3V10",
+ "Q7NPAT6SRTFMAWA3J5AT2JBC88K4VGYSYK45DEDF10VWGQ0YSMJG"
+ ],
+ "h_age_commitment":"T609RE4JCYNRNXWXYTW2M2Z56B3C4NDW7J4FCV1DHEWTQPNS6AW0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":6,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"JVB77H5KTXH6ZEBAMT0HT0EVHKGNPP3B63DXN4H7D39YE5Q0X7RTYZKAF9RA00HEA3JQ4F5CK1R7G4F9DR2RAPHY6K9216YE98KQ1WG",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "H7AV678Q43GCBGYP5KX4JFYXR1T6DQBCJZ8DG709Y51Q8074BM5G",
+ "NCH1N3XP7AEZZ1AGY1RKWPECK7TDDZEH10F1VCDSWE629KTRRMP0",
+ "9906YSKNNQ6MXQX04RX2RASVWF08093A307G53VCRW08RQGJVPPG"
+ ],
+ "h_age_commitment":"S9KTKGPFK7NFZ44QYH0SVTD5478W8TCGDF7FBZK95JZMMND6JF00"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"JVB77H5KTXH6ZEBAMT0HT0EVHKGNPP3B63DXN4H7D39YE5Q0X7RTYZKAF9RA00HEA3JQ4F5CK1R7G4F9DR2RAPHY6K9216YE98KQ1WG",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "H31EPGVQ0TDPFT13983S3HK3RQ9RX3FV1XNJWMB11A8KZEPERHYG",
+ "1RS6XJ7043KXW6NT4KS5ZCXEQVA3E7PWJDEWAGMGKK947DRSZM60",
+ "QH92P2QBJ80C7EJ19F8RH0SJHWWQCB01027SE72Z73NF1AWZCZY0"
+ ],
+ "h_age_commitment":"7SKSRBK1DNYM5SG9T3JE7C0S06DVBCVJNYWEHW7XE0PCFGRFWW7G"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"JVB77H5KTXH6ZEBAMT0HT0EVHKGNPP3B63DXN4H7D39YE5Q0X7RTYZKAF9RA00HEA3JQ4F5CK1R7G4F9DR2RAPHY6K9216YE98KQ1WG",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "DA7F7N1ZWA76ZBV5AV2MESJHFJB37VTQZF5JPQT2V38MBKXSGWH0",
+ "BTBF14ND21MGWJCNHVXN3YS1P7N0QDD273RES9N2R5ZMNRZC3V50",
+ "9R1GS0AYTHDMQPAWT5BTS3MBMXDCNCDDG8GWF6QR7FHJ6F94GJ60"
+ ],
+ "h_age_commitment":"TW2ZRZH19T22ACXQBD0RQY3BPXT7PKWX4W7MQ078JZZ53AYSY3TG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":8,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"6ZK8SX4NQ72VH0EDQCK4PB8V0YM9679CWBE38K80QQ4AMDH51R30EZBTMT6Z2GM5ZFA122GP6MMEW2B1TYQYP7E63E7Z220J1BZ6EDR",
+ "proof":{
+ "num":1,
+ "edx25519_privs":[
+ "R3VAZZN7JPY3GMX7YQWQ7H9Y3BEH00563AWH9WWW0ENYQT7DCSE703RMXZ6JNM3G1918NFCMNZ2PDYJZAYPZ39N5PSBEHZK13GB2VGR"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "RBS57PQ3ARQ8RKBVPV388QRTB0BCEJV962M1G4T4P2X9EE230RJ0",
+ "7DWD7783XKRZSJE1S8E0GG3Y30X4ZJAR117H8JMJ9AG322P9M98G",
+ "KGFKKZXTDQR84Z07N52ESVQEBK7J12SVM9TBMM0898WYK5E4WWDG"
+ ],
+ "h_age_commitment":"PY9JVXMV10JN10XJ2MT0B8S0W66K22866TZTWPK92R9XPGY6W4QG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"9AK9YPWF1KZ3A4WXDZPM0XTHF7WA7GCKJ7B3BR5QTRPMWRPDQV85XY14VWZPC502RYSFY8F3WYQWVBKJVJ9RT0GMX94FJ1S1E6ZP238"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"F71Y4E004GSP2K78V9H5JSSWNHMDEXG660RRZ6M1GXYM2GAMN29REEHMJ043N5T56M98N8DXR0BM67FFGG1AW50H0K5JXTHJ2JZK02G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"7SYQ6HPX7RX35086QSYV6575EYEMB8RAJAT9FB7SBXGGV8A88REZK7K4VZY25H8HP95YYEGMKH9R71P3ECYHC5S15SSNW9VP4P0YR00"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"Y64ZHKWQ5QT7ZJMK43ETF707PG7TVG3F5B4X35739FP96X44WAJY0QW596C50GA3CZFJVNY7EREECAGB51MAP7NZN06MX64H7621J10"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"EYJ1T1T0EHT83N0HAK99GCK5X00TTYE9YTCV7JM4FPZY5QDR45J09M5QW2VGCM2H3H5NY1G2X9DMKTP55T73S578BCTWNHQ904BKC18"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"XT8WT8RQMSE027DFNE97TR8Q6XJFE37ZD9MW5HVS3D9D0RWN5SVVFQ0YHGPA4YTYY63M5C70AYX3EMA4CAKEBH0KKT3506C26RC6T18"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"6ZK8SX4NQ72VH0EDQCK4PB8V0YM9679CWBE38K80QQ4AMDH51R30EZBTMT6Z2GM5ZFA122GP6MMEW2B1TYQYP7E63E7Z220J1BZ6EDR",
+ "proof":{
+ "num":1,
+ "edx25519_privs":[
+ "KBK7289ZG3TNESA30EGHNCE10HT9FKKRMCHTJ1TGR8SRV1JYFW3JPZP836VS9PQPZ0KJT4N6FWJ1A5DCGHRBE62W0TTX8206FHTW7Z0"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "25SP8ESVATA85VZXGZ8SV6922A9CHRBY3MFM1ZH972EQM2N9T29G",
+ "JQ4T1RFJWYBCNXFEPJM8DYB8SDEBPS53BBQ344KKTGJX1VHH8FX0",
+ "0H3GN362YEE3QT7X1E3RRVGTBJD0ZEJGXW0J3M7WEBYNM7N8MVRG"
+ ],
+ "h_age_commitment":"0RR8XMSFN74F2T00JJC4P983WH49HTYZPMBERWS0ZK1C9ZDTFYZ0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"P8X1106VHDDQV5DCZDK4HZPB7G37K5SA5H616T0JX23GFC3PM2VKMM19A611X5X1J42SYPKS3AKRJH4Y8MJXVYC3N1B81Z94KK6G238"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"B8CFK7ZP858YN6198HRBJJ76Z7ZZYW47S7W0BB5CHJM2C8BE3FAA9Z9MQJNPHDYJ1TD95YZMN4JYYS7A6TPNPCPKMAGWVRS6H1H7P3G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"VSHZB4EZMA18YTG3S4N6B58184WSMEJ6WF1JPMFVFH8XJQJGRFS7RF7PS29Y96M9DQCNAXEKZHN18GHNQ899W604T35N640K5TAWM18"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"A97EJSB5VGQ8JEX2DJXDZEVTS4TND8FCQY5SXFB46FN4WG8SRC7HGEE57M5RPNT4PD97VR8EJ7YP76XK54MS5MWX21F6XMWA1MWQP3G"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"1Z1GY6SKNY974HGKEMN9W9G7PMDCECH1JKK91BBD4BCJE8MX2E8NFVTZXGJVQYWCVS93B88G9RRXJDW2PNE5571ZPZK9N108G9G9W08"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"2XVHK83TK3YC6XR7FVWCT0VPDD9421SP71K7WQ13WSASD3EFGZQCB6KR4ZTE3P0R87CGPJQ6316TA18QHNDRNJGJ04EG7XXY70G5J1G"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"6ZK8SX4NQ72VH0EDQCK4PB8V0YM9679CWBE38K80QQ4AMDH51R30EZBTMT6Z2GM5ZFA122GP6MMEW2B1TYQYP7E63E7Z220J1BZ6EDR",
+ "proof":{
+ "num":1,
+ "edx25519_privs":[
+ "BKRMSYWK3RPVBZDVR4A59H6N10QMGA7YPS29S4RBQ9KV8DJYJ81PYD1ZAY95CBDF8B5499SKM6FS3A6M96EGVSRGYSCW1JX1HS1A4W0"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "WQSWC0T9SJNF4HYPKJ290JG7A8ANJV3ERPQQM91PFCNJFWBW3M70",
+ "09JWZM8HWCR50MJVKS0TEAYKCGMCZE895YFM2HWK26SZKPXYTQP0",
+ "863XHWMYR5K00Q1YDBAZVPAKACEZAKZPAJ8KADZHF129H4MY4AY0"
+ ],
+ "h_age_commitment":"2MW1YP4PBCM298JSWSSDFPDXEGFF1FMRCHQZGHS21YTVMF4873HG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"WZDE2MVK4KZK2CV3XMTCZ0YS3E36T2M3YSJHNXTXTEE8DDV9Z6S7N1R7ZTMY3S4NZT4GA3X2SFKRXVT06CV6GX6RX00MX7T5RVVXG30"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"74RX0PXTX8BJXJX5RPD6E1HBK74NH95K6BJ8E644ZS8H45H11Z6A9KB36C19KK2B2Z45DJKDEJFHZ7DX9X4N5CGFMPR5BGZRTVFFE2G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"C8NSNHH2ER4CFDCATN51R6XW247TR1AA94A32VECGJFN9SDHWB7BRNWG9ARSQRBF8XP5S5S2FDNQDHQBTACKQM7Q7425M3E2G30JY20"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"4KF0B1NCRG6RANJ3WMYQNBH7ETG04PK6KS378MB6J8WGFSVX87QX7BZKJZP9A8YGBJ74PSGPK76XKR2854XT9YSYKAG7P71FYQD543G"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"4BXXHAC2RNVCZ6B7J6YVC8KTX886G8AM6ENCWCH3K3CN28X0DTEHDEB3EY7RPZNKP62KYSBYRZVZ1CDHYRKF0BRG5RQB67YJQJVG43G"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"BGN3FY1R4F5YHX0Q0SPX552NJ8WWDZ75WPXDZEVN93EF4YJTKASY5N02B3V7SHN3XAB46F4S8SW80EEE8JK9QMNXD3STYXM7560EE0G"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":10,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"Q0JEPTPFJSTK9KZZ7CB9HGWC52DY5ZX1AWM2YHTWSST63ZBGYAMM1PXRQB676G821A2TKWN0AZ7FXTNXTK0VQ6KCEC4GTFQJ1295Z00",
+ "proof":{
+ "num":1,
+ "edx25519_privs":[
+ "M2CVZGAXGV9TM2HA0N0GYDFT8TY14DPNPRF1FHWBP7CEPFEBZNSJFV98ACW2ZMHZ22ADFM1Q94RVFX0WJEG2PNM2EETFQRY05N4MFA8"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "KRZYBBZ91SQRA6JVBKSRW8EQVAQ2DE64DWQADAAT3AF2TSEEMNBG",
+ "ZVCHKS40C3AZZFZ3NGYRMVPNXCMEJJJQJ659Q6KF95CW4V908ZYG",
+ "VP8ASRJPJ80PKEMYSP7T4TT4CM2KJ37DAB99Q3R4ZYHQZTSTNSX0"
+ ],
+ "h_age_commitment":"2HNPSVKPY2EEBZA62QWV67NP1MAENZV6NWPBYDN2FDAS6HJBF3FG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"E4536V15YMKWNTAZ7W7BVVC9XA6P6WDAB71B466WCMRVRZ7R79S41KZ41RHQYXZ9X7RAN61DQZPSQ9RQYGHR37YSBBPXWB37139K62R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"69BTHTXK0NM74258NV2KZP8F7RX1841Y0QJJ0Q91N67ER3B448RPMGJHCGB4TMFP8R150MPGY9HDWC9E2W1Z1C2A26X5ZR311RAR63R"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"SHT40WBVGSYXNK877QPYF4Y4Q2S42ADT20S8YJE7FJDZ5EK3CDY9E47S6HBXQTATAJE211YZJES26PK5MW5D8D3EC60ER977JFS402R"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"SMS6Q8NX4Q9STHXF6EP8187KC7ARWGFREBB0NQJ56PDWTZQYT911ZMQ1HXJ4RQ2RRAPPECNKV3M2N1BXAZW6SB1KB69ADCVXVEQ6P38"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"EX809P43PWAGXAEZZEAFWXBJ9H1TYKSY37W1XFDJYB6ZFZN2KVR0XP8TYVEQK1J0X49M05NTMXGPTT4DDYNKNNNCQ6QX39SAWXR9P28"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"ZNJ2N85HQF76Z33Q0ZKSSJ9E870ZMJG0DQDN3A11EYT9EP9FFF6GN5Q2QE0XSYTKA6KNN6GMT1TWYH2KP8HN2PP8J7TDY5PXS1E4A38"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"Q0JEPTPFJSTK9KZZ7CB9HGWC52DY5ZX1AWM2YHTWSST63ZBGYAMM1PXRQB676G821A2TKWN0AZ7FXTNXTK0VQ6KCEC4GTFQJ1295Z00",
+ "proof":{
+ "num":1,
+ "edx25519_privs":[
+ "Z61V46HD81N2TASGTQ3N87EKWBZM0YQMMB9R760NKD264MN9Q85R0WCM9TSE8EWM8WV2MAXK8XSE2SH05XAVJD1MHX0MGXWWV48MYS8"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "YHVK7865XSNC4YV8QMWFP2XNWVNNS05EXKBGSENPK6XMGZPJAC40",
+ "FFV670EQRN9J881030ESV232XHQ9DWKFJWE6B80B03QFXHR8T8QG",
+ "TKQ2Y2WJAP48QJABWCK6Z81Z55QY428FXQD0G9XS1RA0BD40Z0FG"
+ ],
+ "h_age_commitment":"E6DE28EZCK0G5NSNG4NW0K1W7CB4C8AAJGEA612GETKH5G8CGQF0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"0FCVB0SWPCEDKNAPEYT8C2270PKGVK7JYFF8MAPQ5S8AH1V8HSCRH9HF0BE4R1QA6CEMCA32J8JF1WMZ4JCCARPRPWM0MJ6NTRGZ43R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"34PX3G9M9DV7SNXV20FMKVAY1SZV3G6F7YWH401SNRXB99KABT93SZ6PP0GWAZJP19T43Q1X8PRN60708BX9C7CBKGK547TK4R3TJ3R"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"6DCFK5P5A9SW5W45KVJ6QP586FGJJ49F19YCX9V8BZFGWF30GZ8TVM9FC9DB0CD8FST39QVR139QM568SHXJT53Y0E1ZMPW3Y9WEC18"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"02DVX0RZ5SBH5S3WQHJD67TTB5TYJK3AT2MQ61VRWF88JR6KWJQTRNVDQD1XJ5J8BXX047RT5XER9Y3C3TQNGCMN9EAVF1ER9Q24A08"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"AVJ97FJKPSMVXB8ER5FRSYEQ5JE7QCDQ33J9GANH8736SKSCS89TSVHFB5GW9T14VYGBC3K3ZVE6DDV9GCYJWFM3G1N6V520QWWRR20"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"YEA8TQG9MV380KFXJSV8W1QHJEWPQAYFZE5BF7MRA0NX57EAPCAP5VMND0S6TME59PBEGW0D5ANZR93HNBCZ61M1CEMQ6AKABAZW818"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"Q0JEPTPFJSTK9KZZ7CB9HGWC52DY5ZX1AWM2YHTWSST63ZBGYAMM1PXRQB676G821A2TKWN0AZ7FXTNXTK0VQ6KCEC4GTFQJ1295Z00",
+ "proof":{
+ "num":1,
+ "edx25519_privs":[
+ "Z7X0Y36GHMVDKCT9WQ03GCKG93HR5ZH55107ZYK8NGW9A0XZB86VVWR99CW0A06WY5CJMHJ2AEST580E43F4RHFMAZ8KMDZ0W3807TG"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "AHYB6311GGXGK4NF3QJQ1ZQ0YAHN9HCMPDDVZM4P6E645SE9GNX0",
+ "B7F4HPZ5CSR8VSE026N0EW57RVZ74M1H6DE1ZF3B1AWMH6VZM2Z0",
+ "CC7Q3DY2EZVFXD1XWWPGA3AMBZ97K6E0H5GT94DTR77047VVE1PG"
+ ],
+ "h_age_commitment":"ZMK69CSFX7KM3F97N220DJAWV621ZPPYR244W7C8Y2RYJNVXZG5G"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"FAWM3FBXJVET1ZEJ6AWA43Y4XX0J7HH4FEQCD69B9RS4MTYP62T8BD2X40Q15W6W8WDHFDH2PHDV6K74HNKMKRD03XS47EW3166PA0R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"N0WRQDZKVX5XVTH52SCPXJ4A29A9CMK240C1JGJ6Z5AF5EA9314PZ65E0XKHC40S8VVEEMR183K4GC06P8B3QEVPPV6YJFVXEB6SA0G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"C0JGXJ5FTJ5QQCTPJ8AY21MQ9KF95VFNTSXAQ980HQRWTEC59HXMFXZ55VAWK7PRG69T8AJHP28TYMY2S3JGH53RDW9JRHJTHCV181G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"F5NT2RR7FDF5PMA8AFKNTQMAVATFZXV16CX58WJQMZDCTVVBV6MD7HP7DA452KXS6G7EQS0ZB2KEDBKJ8WB3MTVQ1XANZY868VMG82R"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"KZCN60V7QAFADZYF3CJQW1CXE8GB8JKSD4CTCKHJZ05Q5ZFTX9R3NTX8EH0YWT1E5CMYKG84BDEFNXPAQ2J6WSR968TR4M9R0S0ZT3G"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"Y1NFRFB7Z3CBZNYX8XXWVMDCMWWY60W1FDSCKM2MGN04S43V7Z7F3JCSGMRQNYX1VHG49AHHYVCNXY2XR04MN6JVZXWNDAQZ8GWMY3R"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":12,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"6Y9RQQEGFEEBG6GRGRWJFEZPZYETRFQGR7CVKFZNA3B7P8AWBREFYTESHQ3VFQ0R1X2QXB04AY184YDN2RHNFDVG8MEPNBSWDCQ12MR",
+ "proof":{
+ "num":1,
+ "edx25519_privs":[
+ "H3JNDE9SEDSYER95C2RA7AZTDNC01W0KP5C7KZR1YZ8D4QWXQ9JSKZ91AFBAQ27WAFZ2DZSY2ESHHKDVQ1DW2E856PN34BPX473RJYR"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "67SR6J69QX640DSQEQ2XQYMCT45K9XH300H6GJXTHDS2F2BNZCZ0",
+ "3E2HA84P6C6WA5DM3SJ3MVFTJRNXECPNBH2ED3NJCPQY84PVQS30",
+ "C4MAJ3PMMQGGQ4Q1N35B3P1GPA9XYCVDJGPAZDTMDXBMB0YKVP0G"
+ ],
+ "h_age_commitment":"SE82ANRSQ2WFYMKM701QE0BWSQKVSP65S5RXQQ5A5VRXJ3284YK0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"F5ZHFTXARJS03G3BZMWFJ7NMBNF7YYCC817YCGW0GKZXVPS2W7C3HYCZ1CG7X5AS5P7S4Q3H9ARCZBWTE9HVHS2CR1QXXJJ16FYZY2G"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"382SXHSRZ9ZCC1W31CHBPYD8DW20WV2ZS6NMN8S3JKETSWYP701K85MBGDH10910Q3BZDC3GZ30S15V8HQKATEBHWQV651F6FS8YY2G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"H2J4NF92Z6F863MYXQVEY4CM9NZ10AFY4WCBFPASK3CDMYCFHB4EYHTC8GZHDF0171Q85VXF1YGD0W4B9ZN5V8DZXP40BBXEPXYK820"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"Y0TGMF01Q7DG2ZNPPWWECJGMWD14BQTDPFB84ETQFXARWN30JEAKH40486E7WMNTWMYS6NMTBRVN4E3XBT8SWJNM01YKJPEWD6ZQE08"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"HA9GTCBV5WE3RVSP0K7EY0BV5MJ9V5ZZ19C8DQS538HCAK9J358TJ3QY9B9EG5GGZVDSYAFWGRP8RTFHWK11CA32PSW2DTVZD0W6J00"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"2CQSJGJBRG517M8X16H5HPZJ6S4NA3NBG7DDK1ZFET45N19VHTWD8NH1NS8R3H2CF5H4BHWZK98M7RHPF4Y6RA5K5ZMW1NYB1GNWW2R"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"6Y9RQQEGFEEBG6GRGRWJFEZPZYETRFQGR7CVKFZNA3B7P8AWBREFYTESHQ3VFQ0R1X2QXB04AY184YDN2RHNFDVG8MEPNBSWDCQ12MR",
+ "proof":{
+ "num":1,
+ "edx25519_privs":[
+ "A0P5W3JC0957F150RR4KSW9F2VPZ5CV8F2TKT85BS70RV7JJ2C5M4KAG4CCH1ZJCEWMS94Q9XGVZ1XPF2JZBTT3NYM0HNEYVWHRJ3GR"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "6HWSKF7H6GW0976PWV41DPDR0WW7MR5NEZNSBV015DYC0J1CJ690",
+ "X08ZETE7DRZA729YC10AWFRN8RC75F63NESJKX3ENKA6NZYSMGC0",
+ "WNTBP10EYSSTCW6B3HBP4V12NFD3FHK194N9Z65SA4NXVCNBFXJG"
+ ],
+ "h_age_commitment":"ZS03GCR79JJE24FNA04AYF3YCMXZPQGGH8QJMXCS7JWEKKQV8RA0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"0PYB9Q5S87R57DCRDB53C96PX15JMXVJJMEK2709PWVVYHG30TT3HBR3Z72DFNRA2MRWQ73HVH1EM5GNH2BY0GYNQF3THE8SQ4GJP30"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"HNXSC5V26RADE95EZ8VHW2M28Y1RX73W7RWCZ1A805NVRAQ5YJ2BR6VT28WK65X0AE0S9DKTMKC5ZBXV0P81K75K08HPKS5NAS98G3R"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"FFEWBJXTS3T4D1KF1HBF547X6W6TS0MMPSZ42AGQ0MR3FERY56G1H2VC40E24XACV1F3BTS7FQ9V3M10J43QXHN9X6GR617FBA41T38"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"YQTD4BY7HVEXMSBX954693TG782QCR7FVNBMSG7VTP9Z9GCVGEPFY5XGK4J36A04CEEW3ZC75FYGMNJ35PTYPJCJF27143TVNAF7E28"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"AH403Z8HYHF4CNNWFB9GK55VCHEDNJMSGMTGAYEMVZFPZ78S99DYXDYPXJW8A2KBVDNQPCEYJ8MNBB4AGJWTF7BDXN30T1YC2C4900G"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"FZBWF6MSZPQD4HBE0PK3TZT17SSNAF8VAG3NACXFRZRAGMAS5J3VK9EVYZKGXBA5JAW8JCGMFGBX6SCWEETXYG7XNGYFZ6C30EV0838"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"6Y9RQQEGFEEBG6GRGRWJFEZPZYETRFQGR7CVKFZNA3B7P8AWBREFYTESHQ3VFQ0R1X2QXB04AY184YDN2RHNFDVG8MEPNBSWDCQ12MR",
+ "proof":{
+ "num":1,
+ "edx25519_privs":[
+ "5SA830Z5MRDD60G3Q4040YE1Y82W4KBS31EYS4T74FGK4P1KR47R4RDWY066KZFRH8PFVH8AYYMFDH4R80SP16NGHT2D2MVEVJKDNSR"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "NEM1QBT64W4XPEDSHT43BADF6K4D9ZFTBVAMZ2YTEJACJEPC8XT0",
+ "MGYBGPEHJH368RW928PD5C8J1S5Q4DASPTY59HDVJWSYXD60YSVG",
+ "9RMX0TE89MWTRCX10BAQK7KR67RK922BCR3HF1Y72C2NW9G87ZV0"
+ ],
+ "h_age_commitment":"DK17MJ14XQANNY5YRB8RVFNTDPSWRPCWWAR1H7DCD4X0TTF9FQC0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"AF06A62WZARAFC123B8YZ7CJ216GYGWAHM6D9SFQTY1V3Z85VP4B3MFR4CEF93RYFMGYH8AC4VJ5MDJ7750VJ7PCR1FS9PPGWQ5W838"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"N60F3AEG18K3CPFE6VE1C33NBXW6KVKSFG3YDS6JJRDBHKPF573ZA94Z2XD63ZGAN86ADTVJ4SWK1BR64M0BTNE4RA6TA35T29ZAP2R"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"DCJJXB8760XREBKS9HR5F2VB8P33BKQRDTGZSY5FD3X5HGGYY5R36KD7X1T51VW75KF6RE7F9MAKFQDPP5R404ZPF51WRVHEM87J60G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"PQYPB60NY713ZZ6SQZ26DBB82HK9QJCW4CP5YX3QFZ03BX714EPKQ6GAWW62W879HNHRWYPQD8BR5W30JKKHXNZF3EBTA7RRM83BG38"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"PCB0RWT5Q3BB275G0Z0809YDFA69483YBEJT4ZRKZX0C42DAGEAKGSCMKMH17WHPFWC5543DVB3HKMG48P1QC816EAPT0BZ077ZE028"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"3TG18DAK40CB9ZW4KCJZ5JNV1SW5E2BZWCQK56NCBZ3QN261NR1T03N8AKAH6SGJRRS1N9GHEP50FKK4S8NCK8ZV1V9645JD73TEJ28"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":14,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"HMP5MGB6ZGK6AFXV1J8NE8FTD6VM44WJ8SHBM623HY92W1KRJHCBGX5T1RVAMY8B63ADV9Y2K33NMREKQD86EPH4B6JVNVCYY5627W8",
+ "proof":{
+ "num":2,
+ "edx25519_privs":[
+ "G07ACSKMZ92B4J5AD4RQRP27CPE1870MFRHBC97M307D393S1NPVKX74GZVS54SVPVTVTWMJP1XS76V7VCPQ3N2SAADXDD19MN52KKR",
+ "M1JDSGWQVSW5YH08Z52GAV3RY0KWGXE55PQPD2GS6JJQ1KH3ZH60TZ0ASS2YWZC2AWE3S05YGNYM2586CF7MP7GBTTR2DV2B7ZHG1RG"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "XA2JBFCKXY1DP3883MEP8WKBRWMYET0CJXPEP078MTJEW1CXV7V0",
+ "88WMYK7H76FNSNA1CQ7ZGDDZ6Z9G0X6M227HAGMZ88MGGKV583P0",
+ "GKPDJD7K23FVV1DJDY0FCRKQT2JEGZJRJ5WFHMW6QBJB2VGWJCSG"
+ ],
+ "h_age_commitment":"2BKMSZZJ1G8D73S2VPWJRA08N84CZQK5DJSCBRT7Y111PT16ASH0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"1073TAG2MX3XC8M4QHKPYG6S153HDBN2CTTF5CDGAPMEDTVNJ2KDDVKYFAY13S19T8P29W3CYSAC4EJJ98G44JEPFT5T7QD2DGDDY1R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"GRKE620VBT2W9SS1PHEZAVDPNWRDGXDJREFYS8HK5VD7AFQAK1EW7P1N45KEPV67Y7AZV62EBVKGRNMJHY24YT7C91YNSWSH047YG38"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"CWYC95M0ECZBXRWJE7THW7FB05H5J7VF2KV7RZ8Y890XFC2F27Y9EVQVEWJVAATDNZRFW5D4YA7F0N97F7DNSE4Y0YKA1A1KBA9A000"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"3FVKYHT5FC8BZPDMNTWN24Y2TZWAVPS4B718WG0J2X64HEAJQ4TD9GKJ1M6G4H8D6DEGH0G5ZFE1A4KQH9JNS3K6Q69AEHPJYBHNT08"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"ESJ5M9FVSCARWM762Q1CFQ449TDZ8FDBHC1P9QHE3HTC03TZE3GJYFYE1J5SMN9660YQXYAJ538GZBBK6AR6HS9VWS530TA027HTC2R"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"QWEHWHRSCB4ZBYF122GPH26RRBHAN7HC628H86SFSXH0TQ1SH2MX2JHF8HD26QHKJWAWH1C17QCMNG34WK03GT01ZBKT8JRAPVJ5E2G"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"TA6WCNRK7HN57Q75EQKEZ2T2EX9Y3YPQ1V0WF6NBXJQZB6MXZNEJS42H44Q0G6ZGPB8NX0BR43AW9R0PN5YDPX52959MM05VE7HT830"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"1ZG1N48C7Y0EEZA94ZXABZVV04QPB7AEBBR0P2KQ7S5HNW141W7KXHJJCNYYDGX8M2B9FN08ACJ8KJV27XNNPN7W608462XK2B7HJ20"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"0NKCKK72PV6ZDV1HA9CGV4N4Z9W6BETM6KJTV13XY682CJE9KEPAKBDAVWTCX5KBH75P44QNMR51J88K19KASQ975G5W6PRRM1C5408"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"TB7KTW3RKSH04BVCGPXXHA41GHNNZRMSTBAGAPNC7J9WG5MNNCP7CR8H72NR3MEW42MCVT1GFJDJNJFXM0JC0H2XWS3Z1STMF6XH83R"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"HMP5MGB6ZGK6AFXV1J8NE8FTD6VM44WJ8SHBM623HY92W1KRJHCBGX5T1RVAMY8B63ADV9Y2K33NMREKQD86EPH4B6JVNVCYY5627W8",
+ "proof":{
+ "num":2,
+ "edx25519_privs":[
+ "S8QY1KNECA3120GC4BPNJHYNXNEP2EQA61A2P4ENAYNTBC5KBR3R3EQ2NG6D7TWRFMN1E1Y8YPHTBM5E695TYMCMJGYG0J5AY2YSRAG",
+ "A62YGSZ6WZ4K3DKB5B74WRQ5ZD4F1FKWYA1K7CF9CJZ58N97JW2MSXPQD03EMMGWJXM68NF7788SBWZX1TWTRDZHW06RHAMF9PWB3XG"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "6TGDG8PDQW2W7SXM2G772RPW9NE14SEQKFVR3WKMFHRQDM42SZ3G",
+ "4XJ8FNW2NERABNY6FNT223FX5EKWXQR3HYAECWMMW6Z8MAQ13XCG",
+ "DHX063DA3NESRWNY39ZW32CXZMH3VGF3CZGR7Z5KRBGK2H54TPPG"
+ ],
+ "h_age_commitment":"XZDK6YE1STZFK89VWEVPTHK1J0Q8JGKXKDT10V561ZZ729DAYG1G"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"PE9RAE0R2JVYNYJZC06P69MAQF38M8KJVFYY5NGGPDTEZG8VT2YVMBW2KGQ1P42H05BKF1N4XY1WQZDXEY2ZEKV0MHZ93CEEAD1RE10"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"ADCP4WJCYHNBQX8TCVX1114M93XP76RM55M7ARG03TJZ5K27H691XNDCY7EHYC9CV0PN2B1TVNYCGE2M9A41KCEV62Q4NJR2JZTX030"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"9GV8CR21C5NFXF0Y1Q2EM2H565CVX853XG8ZWP9Z7G9FTYCFZJV09XZ9W3QYYMBX3Q6MWGYDXPTD33AEJRG2A2Z2KZRSERX6V5QQW20"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"VNJD2PZTGMB1RPY0MX17A2FQ0BGGE3PP1VE19VHR5DRA31VFQN30Q2BN39AQJ29VJF7HB3GY77FKQYJQ2SQ7TVGPS16WJ0N69YA4C3G"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"7AH34Z7HZNZ6PRQ57DGFT6K87W78YD77RY1P1N9FP7EWZTMEQ1HD90H0VXVE7CK3FF702PB1771EHCEYA19JQ2P2R65DA3R99FQZG1R"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"20KHHFF90K8C7EP852V98SYKHJTC8DY1VXMJ8GRWKAWMRAGNDS0PT1PCNDHM06PVZMH9DJ4HSQFMZ03H6EDMM7TVW528NZYAQCA7A2R"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"YQPVZ518KRHVVX5ZWVVBTY5KHYAA4YVVYCWCG7YYJQ26FA4RQM7Y4Z2C5RJ0QJT6S6B9X3B04STYWJV5R7V8Z52HM8RZ6MH3DRPNR2R"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"ZYESYV373KQBANWQZ1VVGG6MWMFPDPJ6QKF9RJRVMZ51S7VR3JM72WZ1KE3BJ51H9JR8J8VY519N9YF3KE02NY71ECDXDDA7ZQ18T28"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"4FCXQWMS5587X35ATQBYP5M8WPNDKBBCGYDZD723B5TRAVD0P6T3ZW9APZ7XZ1FX7AA7CAXS7BYGSN89TRQMB2ZAEMPQS6JV2E0722R"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"99PH8M8VPR3N90YSV2CNVFVP4RG7QE29YDZRSKEMK9ECPZ48YEEAR1KB0R8Y3CGYBHD8KXAWRC8XPE8ZQJNT47Y8P36W5DWYG1RVC10"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"HMP5MGB6ZGK6AFXV1J8NE8FTD6VM44WJ8SHBM623HY92W1KRJHCBGX5T1RVAMY8B63ADV9Y2K33NMREKQD86EPH4B6JVNVCYY5627W8",
+ "proof":{
+ "num":2,
+ "edx25519_privs":[
+ "6TC4ZZMAGDFE0WZFT2YGKA6N4Z9JB5EF6NEQN8ECK570WEPJ807033DAJ5DNVJNYAKEQ982MF8KR8Y0JHB04K550RWDVA9PJ2PGZ16R",
+ "R39Z1Y514R1E1DZGWC19VW0XBWD3BMV1Q4GJF6B6M97402SWCR0NTG47DF29M3YSKZTNXPHNAE98G233762ZNWP63CXTVNSV2PB2E98"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "N82HCGRH9M323E6RD58SK6HK70BTS1FB0SY0BRR8EBA2TF614FNG",
+ "8A63YPGFKNJVX9TX50WGA1KQ4ES9JFDVB23D9NVGVZ2NXKV3JZJG",
+ "RS730M037TE6GTZNZB1Z8P6K9SHCG5WMCJ3N1911R4DQ7S1V2BQ0"
+ ],
+ "h_age_commitment":"6D7BPMC991N72VF6YH13CQAV8GWZBTVFWQNAX0YZNMD8XXS2SJFG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"RBF8QF2ZX1PPXY3FP9DF2YDRR8PF26P1BCCQJK5WW4WXTC3JCP6587G4X84WPYKNNJ432P80F0G40F3B77Q8RTZBW9D92WNGD7HM02G"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"MSAN8Y9D11T5QKSYJSTAVAWDZWPE3XN4FSF93NTD5C792YJ49CMWKX3G1TRR6T0R6NNS442828VX51EC95QT3STTC64D2BC5Z2AZM0G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"676XXK7FJCFAB470S0TR4TJT9QDQMG3FZT02HBJ5HJFYJYF3B9W3DRGJPY2J2QYE5F1GPT1RGNTKYMJ46EHAMRP1PZQN0S4TNG23M3G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"PVZJJWYV66CVSF75P4XSADXG0S35XWQQE6HM16PM4Z3Z9XWV16P6610QJ84XV02SJGQ155J00BM7FFVRZAW51M7741TMC5692BVTR30"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"7RJE8PW0KPTPE29MHVZKETQD3WQ7E3ZYYQXVFNW6CEH1J6RY3254MTH9XFJB6DN53RCGSDPXMEKNK46818X9033GF1310WMDHPZ4Y3G"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"4V4KMK3R03P89JP36R0TFWQRFHGKTW4TZCJVZ2RBR9ZP4XY1DF62NYPGNB3004B2QHAH5D9J27MYVFFWY34WDP6WPE267KQW1TTHP28"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"WWWXR9C235FD03BCFRSN2CDFC2JYM5W9C04JYRHGNDQVHE2JJ3DMV68DRTXHZ02B4SNJ748EPV6VJ9G8C5F3NVF47594RAJ65Z5W430"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"MCBJDPT8S8GRX58TP470Q5DQ22NP5P9PDPBV7KF22775K59R8J6Q58NF1K40HV4B6W4KFAFY30HER8NHK2CCAJWTVFX3K6XFABNG420"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"06PQVS1KM66AAME252JAQ9A591YE4CAR7MQQ7FSCEAM375E80Q7R5N700FW897ZB79MWDKG78MCQP5F53GZXB3Z4P1TGDS0S653GR18"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"8NNDC4HCBPP6Y60EK7G1SXH24Z5NAQB53E4VBH1FFMM8EH98RR36G24RQFQ8RNHP0G3NEV8QCEW052DPT9H6D6D35XMXB6Z5MMQKC00"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":16,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"CTN1YQ3QX3PEHQ8SNGGPH008Y2N0H5RA7WWT7X758EXBMV0NC3BDT41YWSRNFVB9RBKSJ2Z2P3JWC3V2DDHXMF6556TZXH9A79BV95G",
+ "proof":{
+ "num":2,
+ "edx25519_privs":[
+ "305AGMYXP2K3KPH3602E601MJCXHDM09HVSBZ2E1MWG4WZ3JFXXAJ55RZGRND3XC8YABAYF37XK6TDC9KN4HMTQQN71024W7XKEQQ4R",
+ "W0ADSQMQJJH3HEKWDF3NWJ8MBSGP77J3QHH7H7G2G6AWKFWE5SEZRE0QPDR4JNAYR1HBZSQKJ34CYT8KVWF0HP63KPJXNVZATN3GBE8"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "PCBDBBKK1GZ93NNJYKPS3DE2F6G302JC51YHE8NTR4CHTEPQS18G",
+ "4TVDKAR1PRTGHZRVQJRWF3S8MQ9D84G7TCC08F8EH33ZK8GZM14G",
+ "CEBM8Z9SVHDSA10R5QSB14178X00F1NG13YVQDGA316X613ZBCQ0"
+ ],
+ "h_age_commitment":"68EF2803JT7NEFZ8ZGENKCDW90P52J6DCZB43J9B9S5MCF87PC1G"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"G2483N5W14BK5N67F84RGK5E9NH84R2JTTTCGB8ZWRAD7R3TA6YTS0FYM6JDCK1638R49QPV8J0HZNGJP99CSDWM2NMDMZ1Y3SZDG30"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"4990HZ12JNTVN4YXBX728A9PZCQBG55Y6YC946F9W8KBMVWEMF6RMGE9Z6T8WYCT45NWSR62479S2VVKJ24PY2SGQ1BXWX1A0900Y18"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"S88TFY0KQQXW5NV10WQRDCZ8R4ZTRP00S76WED05VSW1NWKYYKW6KN7WET48PXZ1RY0SYZXMDEDNWX5RM928Y6KCV0TZ5JEPVH9NJ20"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"D46P0PCCV3M9KN9FEMJYKT21HSJHQ7TBABREXVBV1SRDZ54EGTJ18BNMAKPJMW59SN09E7QZ5GQ691GA38AA1VP8ZKE520E0S1GKM38"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"JCKP6ANDCPZ9387JJ7DJHRDHAGF8CMAVR2YWPYS3Z76GW5TVGCES944JDSACHJAX4AKNZ8Q96DVB3GCRD0MJWZZTZ6FQ3T8E5QF3030"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"TKHAWYJPQV0HBYK123E8SJZR00F1PMC1J8FQ1D69CGGMA4X69YV5G19GTXGRCP22XS68CY42J8DW2Q6TXZ7PVATY2EYYD52GW8ECC0G"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"52VYQT1DTSYW6BA6NWN4CRB58DHET0C2DA5SKN35NC7H0GW12BW8KN2QVAQ2E9XXRX4NQY99KKFDA01DNT1E5HGKMGWP2R7JN2EFT2R"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"AKF0T06YP37JHTB0TKQQ3C2WXAB3CNWW6SQF37YHFRM4SKEZ0N883HN9Z9PKD6A20N052QA857ERCDEV587CYPXJV2HZ4ZCHKW9D43R"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"67A2WAZR887BTTEXKX2AHG1X634F0PDPASEMND7RSRJXG8AEGAVJT4SE0N2DR9NRQTR1V459VJN163XGV4FEF7E6W12GWBY4JBA7218"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"SC9YNVRFX2QZZ6CG5VGE12TF9M1B7AYPDDN23NMKVBHNBWYFB7YB8CQX387NCAS213D725NMY7BRZB4B1D35KAS33CCQ9HT83276A1R"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"CTN1YQ3QX3PEHQ8SNGGPH008Y2N0H5RA7WWT7X758EXBMV0NC3BDT41YWSRNFVB9RBKSJ2Z2P3JWC3V2DDHXMF6556TZXH9A79BV95G",
+ "proof":{
+ "num":2,
+ "edx25519_privs":[
+ "1169FZD52K4JRWNA6SAF60MPTGFMS4HSTWJCKZCMQ1WJDZT18819WEE8K0K6A1S4TJQ5GSDBCD5PYAYPFV7E4PVF8BVANZRM0X98VP8",
+ "DD8ZN84PTSF0G3S6ZXJ6QGMKE8RNMKC5DQXFNRDX35AEFA14YC03D6ADDPXAVDQHKY5KHD3MH1489WXS242H6WG45F4ZQ7JWWN11KKG"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "39MTEJ76J4AV2C64RAN1VSVDDRFNTK67TEESFN9D602ZFQ0EJ2M0",
+ "QC1XC3E9A40NR53R02XC9QZSBHESRY7MNX80S7KP2GVMTEZHA4N0",
+ "4ZPPQ1WQ0ZACWN69QJ18M76K3G8ZK2D2FYY48W7K3WEDC1YZKY8G"
+ ],
+ "h_age_commitment":"DCHJ07HMTC443CC22ZD8D25WSVJ0GZAV90RGF8J2S15J7Y5KNVD0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"F1DBZ0CHXYTB3PD46BQV6T34KF50VNFWX4C1R2CB5N6MGWY9W32S4F9KRS1BKJHMTK9Z5G7CPA9XWB05TXGPD1MHBBKAMEDDHP01T1G"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"FRX93TDN8BN33EKD31KSDABHFEYSWE7PXN098DMYGH4WHDH4Y6891V89TC3AJ4NN3QR9NEAYRV6H3TVSEGH6XXBDZ9S4BBNBGRJ6G0G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"D3HS18YN74X475WQ4R1EFMZX0CRK0N5E3E4C1JTY77WBVV3DJGGGVW6H5TYA6JZGA5QZ9ND1740P4Y7CN0RRPZDA52PEP6MK7BE9M1G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"N897RRKKQFK7K82SYR2NP21J1J0RE01RW01VN62EGZBNJ51HN77007X72F37VK3X1DJDV0MY4YEA4GMH8RDG9AEFPG37EJ76KD3621G"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"WXCZV9QP74WCTY1Z7V2VP1TDM1MB2ZP8EAFDA7CK2259B72J9VTV88N8BQCXD6PFGEX3XC628Y2XCFDYCNJSRQGGMM0HXTGARZ6X828"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"CT8MZER98PD4CE0XDV72AS7YVQ5RKH0DQEM8V2C9EWBA113PZKKH7MEBZRN7622YGJRHD94EK52JVMX91X0YAJ3VD2QB06ABMB13C1G"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"5PQ43XMFV6VG7PXC91YN21CPF864NMB2GRHZ1GFM7CRJMZ4D2K70PTGFD2ZD4MTM6Q35BHFM61G95Q001N0YRN84P2CZMAGSTVJ0A1G"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"BX9R0SF1HMY340XKQCHFSGPSBM7EC7NVWRRGXC70VFRFRSE5H2X2B1RW308A56BC3PNPGCHEHCEKSRHFS66WM08YJ0RD6DRHNKAMJ08"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"H9MVDHC4C8KFRH82254P7G7CM0X2D03F9B9TP3KCD0R4SNK4TJ0KV7666737HZARBCBNHNFRXK560D6CADS87NRCKH30BP99C88HT00"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"D6J0M40MNTB3Q6ZAGWPE9DGET7FDZXRSPZ8NQCC5V1KP5DHFEJMKGV0HNKZ42TZK4QG0BHEE0RY6MGYYG197J30PSRRCFC43GSH1Y00"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"CTN1YQ3QX3PEHQ8SNGGPH008Y2N0H5RA7WWT7X758EXBMV0NC3BDT41YWSRNFVB9RBKSJ2Z2P3JWC3V2DDHXMF6556TZXH9A79BV95G",
+ "proof":{
+ "num":2,
+ "edx25519_privs":[
+ "5DKXBKDMY0EW5FH9ET8EXM088F0R4H3BF98925EV51M5S70QEM7N20YM2J85CGZMA518R9DAX9X54K9B8XYKNEXCAVPH3JWRHYKXS4R",
+ "C0S15KAXV2BHJA5CS9MA015YMSJWM588NK6QXK8BXFCBD9XTFR0QXXT89KR4XK3A5TETGK1CW7RPP84NDTBSVM2JPRQVFEB2ENPYQ70"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "EV8H6ZAYZFZ7AJW1NCRGGSVW2919M9S9G6EFMGS8J04ET75WDEX0",
+ "SNDTMGM1NV54CRSYDPSANEE7CEYH6K2NR14KN1RDS2184DJFCQBG",
+ "PCSTPW67QG2BARPVWX43T49VNMCFW4M18B7ZQ48WR2AHYHPM6QFG"
+ ],
+ "h_age_commitment":"FV67ZBENQ8CN6P4MENEJ0MSAPSTZ2272SEPJ1JMY3EVTNNW3DFYG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"Q0EHAWXYRA66JAS5G3AQ5A4Y8XCF06XYYSBRCAB5HS5G4Y0XD2MG49BX26ENA0984V2W0SQR4Y9PNMEA1ACHK3ST89G6ZHHFRZYQ830"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"8W7ABETZJFMJZJW2S2MKRVET5FZ0R36HCP3SCBZBQEFA45MRT7PJTPSPW2C5T7SJRGQDJQYTB6VS0E2PD0WJDKWB7Q8KVXVWY1P1M2R"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"86X7NX6PRE0379S5BNQ9G67EA8QAZ6VHB44V9QHE0XK4G0CA2YZNZ5K85KWZFS0V4K9PZATYDJT39HQM7TACADWN9Z0HNBDH7MEWC10"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"4AG6QZBQJBK318EJFKVAEH55SAA4490RZXVTGVQP4ZETWXX0717CM4MNGQQG663S12QERAWEHVAJ0BQ7JZPZNDYJJXX2H870A847228"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"SR360S9QFTMDFX1JJDSCV8PSX97BJXEA00PVAKXZ7KE28EWWJ54JKD97FJV1N9FX91F5P0Z511XV6HR85459MFFN70YXERPTG067W3G"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"P5PCGNVZ8ZY22M15C7VAH05VGCFWQHRZBDC6XY5JM3PS2TTGRZQEYNSYXHF5D8AJQGPYXPH6SHK2F1GB31NX4KHEQCP2C8A1FQ62W1R"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"6SCMC58W99140N1JWEB0B929FGJKJK4Q73DDNA9J7RR4JPN72GGMSV5JQ52S7WP20VA7724P2NJFKRBBN200A9KT4A0PPRPE4ZJNT30"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"ZBCKWHAVSZQB2SED4BM409QRBXVEDNZ2YQYF38PAGPW3M6MQ438NS1YT98PF5A9SDP2F33BTNCHTDPJB52M0QAHMB3BNY9NXQPCFA20"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"BXK3B89VGJEPDRZ1NGBT8B5PJX83V5N7QPP412W489XW9K9RA5NQHYHMHXRMXSQ7P1PFVXA3CWZ4RM50JP4AF16XW05CGVT7J0MTA08"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"QJ80HDPF6KV6E4M4ZJP6JW9RX4Q5F2YS2SRHWZRNFX3NE78FBVNNSTJ6K12BTWK9FDCWPNXS9EAPGJJ5P7T53HZANXV26G8X529X42R"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":18,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"8XPT2709ZZKNP7YQ05DQS8SG405S8CSJ6X41JG8SMMTGGF8DXR8007SZW4H5CRQG7J2ZZQW2J2PETZXA7FED8YVXVN6SEES4T6JT118",
+ "proof":{
+ "num":3,
+ "edx25519_privs":[
+ "707TX6HM99N6SDZA9CYQSPKJAW6JJF7RTWAQ1AK7TRWFBKHQSXVV8FCV0RRCJ8NBQDP0B5A6FXAGJ9736ECGK4CJF10022PK3ZH7T80",
+ "Y01TZB16Z3YNXJE1CCPPDCY06JENP3FZ8J2RDSMTAMZ1XEHHG1XSSJ9EVVWHDF8Y9A1GKXMMPGNKVM45EQYKMQ6RJ9XMKHPBS56PEVG",
+ "81RHHHMPNFYRM0WTQB6P7VJAGR640D2MTJM57FCZWVCQX3YCG1JBE3W3DR2H0Q23P1H8PWW8EENMAS26W88C599G97ZVEQGTREFGRP0"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "2APEV2SVC5PS29HFAXAWJWNQ8HHQ50KQBDH03RGASG24108VKZW0",
+ "A1GTG17DN3D8T00STVYB5QW24R78A1PZCAKVNKR841J5FRB4DACG",
+ "B63J8JKM9YV8TCJAPMBPM1GBPET4XTQQ8ZH380T9BXB61AEK5PV0"
+ ],
+ "h_age_commitment":"1EETB46GHNYZ9F422B38SJTD9BBMMTN2TXA1CCEYZV57729PKE0G"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"5MFQ449SR4R3MV6WB7BVWJ6KB8BVPWTY2MDPV98TATHK525MZYXSRJG5K7BMTC7P899VWWAHJFCYD4RCVPEGR51REN8NGCJ3BDB9R3G"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"T72H8A837MCA377G09MNHKHA8RN5ACFTQPAPPZGK5TVV7B8FZXB240HWH401XQM5T04R590TVFV0TZB9KG1KRQCADAFH27KGAFQE21R"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"YC9K9C7YM7A3SHG2CKPC8B5QYSVCYFJSWSRS3W7EKDWN3AX7JSCEFPQT3QTQ4A21KC098NH8SEFCAAHEAFHHYX9G50B63BK7PXWYY1G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"TWSDQJ6TEGBTN2EY4FEGBQAXB3T6MAM17YZYMM0EGZ0EDQAW4WY884A1307HVTVCTP6QEMC4WZZDY5ZJW9CWS604YF2PJG5X5A66W2G"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"JGQA1A8R5V28YXKA8C4RCRFWVKAFJQ7GCS02PX2VMN6AHZQ5708JDB6YS3288B32SZP5HHMW40S7G8MJ9PMPRYTNKHXFSB6QN053G00"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"4TD5EERZ5A09SC1J42SM1725VPSC01WAGG4KM3SH43FJ47M152WZNAAND5E002F6ZJ2ZY4757GH22X2ACH9HQY2FNX772WBS5WQ6M38"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"AVKJGD4ZGMZ8S1SZ4W53GFDACMGKJRC3MPB3D87BMX8QFP1A2P13ND6NN5K2VB1ZP54BCXJR6H5EBD9FYGGZP4V22CGJ6KAF5GRB82G"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"BP8G07P2NW07RTPFBPXZTFNMWAZJ426XCFJ5257K78MNG5H5BH0WJGNA1J6MWJCWNGRJWHRQ6ZTPHN68X72QVX7WJAK12ZV7WYYQT28"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"0HR7XXB9QCY24WADAVFJZZHYW1J9DVHA8WGXH31X2A4HQVKFYHC9EP2HFZM1E87YTDKRTDDGMMW2YVR6V2184RCW4SJ010793RQ022R"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"QAQD5DWVR606RXH3ZSGTPVJ235BTGXA44DKF33P0DCNKHZT7TYAGE561BR27SP405KT1QXBH2S3VS2MTSWNCZFG50CQXKX7P20WTW10"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"K10KBCZP09AMVHFFJXQSWSHJCN1M9824K5DQC09WJC11QCMMTZMAGC3Q75K9J7NMQSBPFD4VTA7R3Q93TYD8BK21K372ZEHY39TA808"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"0ZS9SMHBK7FNM8YGFM2CRH4V6KGSD60QE89NTV192860N80NQ3DS3MX343M49G9EQZMY32ZCD9EDWKKAGQYBW54MS77P9P4TBS4VY10"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"EE5TRM5FVVMGF04RC1A5ZETRB3RHD33C8QZCJ8YW0FBH0RVHDQW5QPSMTMZJ3D41HXRFET908ZW992K7DHZ12S4HJKKYA9G0GM5V82R"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"WAWES6ESG9HM4ZV26TP2KCPZAY1DR1NN3XDTEJ053D9FKY92WZ1SR7RAYQDM005PYK4P3ZC3CARTA8KSK13ACXM2DFWSME68B92X02G"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"8XPT2709ZZKNP7YQ05DQS8SG405S8CSJ6X41JG8SMMTGGF8DXR8007SZW4H5CRQG7J2ZZQW2J2PETZXA7FED8YVXVN6SEES4T6JT118",
+ "proof":{
+ "num":3,
+ "edx25519_privs":[
+ "2EKRBSCJXGGXNBS8P8J6GWRFDMC92AKH766T6WQ20K25TTQP347XVD5JB87FAPB7FRPSERC1FR929CE25W1T2AJ2VEWB5T03YTRQHA0",
+ "QTQXSXFFCYA6TA5SRBJ11HZMJPS360FHFN2C1DJ5A9EXTZ6G3808V5RK5PHTS7BWYD6933TTRTSQ92J1YQS2WEQQPFQQ3TBDZN83C4R",
+ "133MYMXW2KRNK2GENCDRSCH3776AQYX8Q4C6R9NE1Z8CAGMJY0556NJN0HHZGPD9XB4HW80KD5Y1MN5YS9ETNJVVH0NV68JZFAAV710"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "643CATW17QH7CDB9T9JBM2785WDJDSPB4D5CAQTKTMTMCYYYBM5G",
+ "MG0HW4NEW9SCAV81KHKRMX78BTRH8FJE28MZCT6GJ61TWFH9J55G",
+ "WXWD1KH4W1E1NEHA16E8CBEH2FQKWT94MHH6BT9E22QVMDKGJRD0"
+ ],
+ "h_age_commitment":"2RTXDR6CN0Z91TPV18T7FJGS22VDSAHH1CB6DE2QPDQDNGWXMP4G"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"EVB27PKAZ1TVXVYKTJ8HDE3MKA90M6ERCA9YQ64QSY52QB7SZVY76D9FGTMWN2C3VPMSPZ45JNC3RRXDVP4WHCJNP0CZ9K0QE1B1P38"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"XAJ775QSQSNQ9W112JT6E2YWE2VF9984FDPYJF12FTEQHM3PP6YDM6Y399R2YQWY1JCS2SXMVS13N8QBSQRZFV5K0RYSEC06SS9GA0G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"M785TKJFYW27TJVVBVXQKN3PDJ7ACH938HHTBAM37MEN141DYKB196B80369SNW0FRR4KJ6T6GA96CBDAZY281CFFF7MYN4M2FNN22R"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"E9CV0EMJAQEYKBBGF0MQ4FXH0MRWQ9S11C7K3A0YAAZQX8RZ3PYBZNKJ8Y1QN0YVMJFVP4JNRY5P1JCC1FH5VVCK36SJJAS2MQPV20G"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"TWBJCHKK91P4RAF1889FQTXX91NB5KYHXAED9J7AA3BC49A5SAXT4YA6KXBXD5T0358HA4K24T2QWC7BTNBQ1QG4SMHERJDQQ3ZCR10"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"5SPJWEJFGZ6W8QG57EMACRJ6XXPWJAHFGYNJ86EMH1B0VTTS851KD59GJJAV1C6A83SB4VQEFPQ6JA8YPEN82HQ90HAKZJ70QCCRM38"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"VWZDFVS3RRN9EH054B3ZZX1MCXN2BTCC8WS1C43W04R424GG63R5CZ7P245TGYCJ4V4ERW8RXE96G6330KM4XW826JWT44TWGG5H238"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"6ST93K43E7J1P241QXWEC247ZDPWMN4Q9VDZANTWWB7KSZKPZX3C4PTEYXH34ADZDE760R8F1Z0M35X19Y4VNZNKA7S4ZPKGJFTAR10"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"KKMT2S6VGNPP7YX6CNB9246RPJE4APWT5REY1W1BTNTWDSCZMHGFK4CRSMZ62RRR6TKDGG0QSKZNQMTQ46TD07REJC1Y9VCVD8PCE00"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"87T1EEJ2JE2DQHENGT2NDG96T9TNCP3V3ZA72CQ5HV6BXTE1P5HC43FAFSHBB3WY8ZRNDPATDD40DKGB0MS1ZB5AA6BKDKVFTZST42R"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"7RWSXKEWGQH2ZSXZE2SWR31GSNHSAFYXWRVZAPW5XC2WYM1W158VN9P0AQMR268G39CN3CJCHJCGWZFP6YS7D1EP3SFGE5X63011628"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"FV12MV7BZTCJX7NERQKQZPCDP92NA29FNH6MX54016RBGT1T0BX2D038YEVWB6YHKJMNSH3NSZAQG0JR1DYHA8M6HMJR8R5DCDVTC3G"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"K1Z6ZF6N0E7BHBZ4P03CP3B9XMVA7Z26TJKRVD59TPDC6YFWJ3WFGYW0YBKX961JEXAV65QNY52BE93T4M4EXFS4YX0RQMEBVMTDR10"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"YXNBZTN470GP21VHN2Y4BQ80H0W4QN7MVG1RD60XJCX1RCY89AFB18KRR7M1HEDV9AT29P9HD1AHHGE8EN5BN3XKC33970HVQ3TP02R"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"8XPT2709ZZKNP7YQ05DQS8SG405S8CSJ6X41JG8SMMTGGF8DXR8007SZW4H5CRQG7J2ZZQW2J2PETZXA7FED8YVXVN6SEES4T6JT118",
+ "proof":{
+ "num":3,
+ "edx25519_privs":[
+ "4TS84PS478W3CEDXME1NV4QR89YVPS647ZGKHWEME5XS84MV107DR01RQ7ZGAK2SKG5Y992AK59GJT08VZ5EZFWXJEEV1Q96VTA94V8",
+ "6C94VRNS7ZH010ZCQ11R5XB8S74S77T3AYP2XVXRG9RMMKS558539T5WD32S6C02Z9Z7P47VPA6ETZFHH3BZXJM7YY5505NWNM4F0N8",
+ "XM77B9AP0SWQ8JNXNTJ6WA4873WDMBXXG3QTVZDAMVKST4FCWG0VFGCMZMNHESNZEVDG85T5VH0MQ56YN7QJBJYXSE7TT05RGFG11JR"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "VCEPV1EFNFFNGVKEGTDWVMMX2E2056EMX9T5M2H2YNKK224E84W0",
+ "TNQFVSBB715Z8C2QBVG3NHJZM08D9K7WH1D7Y9700HE4ZY8P93R0",
+ "3QWCF4QTQSJ46XQP2QJPSQT4SNCQPVAQ6W6XBCDHAAV99JSJNGC0"
+ ],
+ "h_age_commitment":"FJGFV1HEJNK8PD8P049Z9YXK4F4HEJ9YBEMCAJ7X0XCJN3ZJ1V20"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"J6H98A8Z02E7R6DWPHVKJEQ2HCK378V7R30T47AST38Q46TJFXR4X9X0BGA3Q8499JFVE630D0XKFDHSHKJD95T22ARVWXTDP5RB81R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"3NMT7BXRCN8MT089TQEZ9B5BZQ2B1HKDTNH86DSEXK0ZR2ETS57JJB97ZH5155881GGCYD80J0H633T7C1HZYRB2WH94H0T36QWW810"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"ZQ7MD1J259PWGF0MV1D2JE2A6QRXEWW72SHQ9ZV0X1CX00YRBXTJDPXGSMFK1D6RAXN521283RY48CY7XBY52RJH8WQ4DQ5XQRH8W30"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"NDNYY9GCEPCTPD0FY78Z5HXWWPG5AM9787BY1C51M08554VX09X6T6PDQ4D8GVZNZASH525R6Z77HC4K6XF26MJP59Q2W6RYJGW9M2R"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"1DGKGYQYB4EPZBNG086VNV2BTS4KP1A189PYK447645ZN6KA707P0RK9NHENNFE84P7QCG2GVDV9VVK5F9X90RCS6FGKF0Y0EZFZ218"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"V6WFAGBMVXY2N1A5W4EDQBM5RRZJ6GSRCRQ89ZED61D30RXXBTQ4PK3M3BVM6WCBCEBGQYX6GEK2SNSY14KKY7SP76CZ5265HTSTY18"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"B6ZKKEM7Z55YP8GNJ72BT41Y3HZE5FX67CVYRNTCJ2Z87HVZZ3Z097712J9YJ90AH0KH3HCC81QX0DKXWRD77R3VX5Y4J1A2KXQ6820"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"Z9XDTFMXRZ33Q5AXSD8MA0A8QF9H8VYTP8RMFEMYZ8NJ9C76XCQEDW24PV7ZR4610VJKC5CX1KCEDFWX1TV8HVWMAYEWDVMX8HA0410"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"8JFPM167HSA42ZHPREK7B98CM08RHMA3EX0KF9TJYPBK0HWF6EQ93SH7RTATNT6VXTQX5N7JT6V9AD9TZ2WHCN2VNZF5WGQKJF1YM38"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"X9JNVHAYYTWDDWE1MN5X46MN9WZF4DWCZYPDNH0Q00ZA51THDXM6NEFBW1KBVK6B5DNXWC5VPBA38AW2F2DT2E73JT4WWX69W1R8E20"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"4T0NKM0XSAQ96HV9E8ZK5GBKYJA1A0QX6ET6SVXZCSM21KA5WSX4BG1NR4XRFMA3J84ESJRZ76VK1QVZX92NM1WW92NTFRMRVA7NG38"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"M1JZBM9WMBVN57RWF7MNXE8VM1NSDZPJFCH42Q3NXBH7HG9VVFRA4BNR5HATYJ4KDT1D5BX4HW92AHW0W3H9N9XPZ0HQEN32VETMP10"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"AESYGKCRS7S1YJJ96JNVE8THEQ9SSCK5YN2DK5VP9758X4B3383N2117ETCV1XR9BANJFB6JFFBCGT38ZAXW2C9677Q360RC0G62R08"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"4PHDGT5S0C4JZQQ9SYMZ05C46EN3WVGPXABA8436057H5DPMYQ1RMYXZ3X7E7Y1EYN14KRJCGPVW5S7SPE2HYR0VA6M2MX4R0WN0P08"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":20,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"TGY14TF3402JC86F70FEM22QTAZFR0AFHZHCWERJJRM0NTQ2803GV10W8WWG061X4PJSMJXAX0VETJ4NQ7KQGMJB82P18M9TYSDVZCR",
+ "proof":{
+ "num":3,
+ "edx25519_privs":[
+ "41DX6Y4TKW1KV57D2NMSW9EANFYNFCK0TAPF8K1P2FN6XMZFEXWHMDVT19P8PKZCZDHVFR7V7AQNJK6PBVB1GCT9MJY72WYFNW8NE4G",
+ "10EDCEAN85D3WTQFD7QRHA13876T5JPY63A5YY9KQPEK8MR33S9MK7NBH6THFPPXH5ERHZT8C9YP5WCQ69AYY6GQ3FHAAG8WVXW8S6G",
+ "03R09BWZW3G2XVSMXSDWXF6TS96VD9D4X4HN03BFQWQQ955SRDSW7720V0163A9GSG7QSS3J1NGJ5K5XAXFSQDBSDD68VWK4Y9T0FK8"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "ZBY02X0EN915A89T2DH4BZ6VNPG72K2ER2KYPBB534KK2TBSFP10",
+ "FJN6P698QMZKTZ73P9B2M7JN4TCN07732ZM33MZ0BD858M2BNAHG",
+ "QHBC3NMCQ565VQA5HR640XD2K9KFN1BA2SFWPV4PNAM51SBWDDNG"
+ ],
+ "h_age_commitment":"NHZA7N6C36K9JSAT1X0P5HJKGZSQSZA24S1PVKXB4XP22YMFKGYG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"AXWMV52MDDAEJCXTF8PPTXNKTCE5QTGF44475SR3PZSEYB9KQBZMA6BGHNJPKSABVCE9KR1C3SFGWF8GR9WFJ9TZFY0NX928S5W7G1R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"4XB7KHBFFQY7PWP5NJBTAJA6KG4GVC33N44GH7Q3W9C56D5YJAH93QED31YK2CTDADVP8H4V99W02F3R9GR6ARV09Y51HDYW2CEQR1G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"0JE7XCTDDTDR7R609EF5VV0XXKQ3E5E60YTFQ5MJ0CBAYPBTYH619FDTPKDBTNHND662QWNKYRK9N7HGJW6A7MJVK9C9S24D9MYY818"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"W588426W5Q9W7KF2AXZTPA89TE7SJA3S6TWV1AW9JM447M9P3MTJDYNXJRE6MYNGR5H3HZZJB4RK93XBYTYE0REGRJAA9D246Y5KA00"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"GYMH8KZ65KPZ99Q536MY6J7ZRQBE4BCPJH2M83K8PVVNCF66SHE62PHST3AS0H570KT08MGDFV6VG1GP68QTCMC5TP3C3A6M823V60G"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"EZKD4XWW72G1ZYE3N1CE81TF914R1SG05K8Q9D3TEQZGXPAFTKH8WAGZAMC1GEQ2WQ1HHS7XQ93KXCH7DEZ5ASRTP2VT7E7PQGBMG28"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"5JMMD0EJ1SC5MXTAD8A1055FE7AZ2TVDX7W0V2RZ9DH4QDN8T2S21JQTP5KS07AAZPT8GXPT4MMZVS2SS2E03RMRPN9GDMDSAPZF83G"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"5K04VMJM1PYD84YC5ZV5F3A3V4FEBE99Y1M24769K830SV19VHPEPYYXGCTK02NXABT05B2326RJFHTR8TZBYGJBCEPKPZS06X7GA2G"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"HWEKX724J0VR0BNB6RC249VPZSZRSBYYJPKPWHSS4C3V6QPP03YKMY8QY5KGADQ4J8SM7T5DTAPT5KN6872WM5VFCGBSVEBC6QXG418"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"ZAXJ3YBVE5GXQ2XXG10KA9R5AXKYE9XB29D2K61NPXW0JJH0YBP0V7K0EDQHVPPF77GDYNN9SYEJEPJ8ZSJVDH4RSMDRTA83Z3NSC2R"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"SDFW1MWTCZSK3S65DY0ZBDYAY88A2XN6D80NC9MMFGJF18TC0QEPWFN8556V71MRMH5JYZCSK3YYM7NJGVES9WXG37CM1X80A01HR0G"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"KV9DWFKAQC041SZ0CE9C34HJ4RY2344JERYA6MMRGAX7QG94ZFGK18DX7QTSGJ72MP1Q7ZQN83EEDR5E5NP9G1DE9YE5JJ52VDYT21R"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"C5984WF8VX0S6MNV7RVA703ZX9J0E4HC9TRJ836Y930R3HD6C0Q2MY1KB3BXBS51S6SW4W9W5R99SKVJNDET5YAJ0846R7VCSCZPW2G"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"5Q1RZ98RKHHAP0J6SV4XX2XKYCC147D2BA02AD6Q4HN2B35AAFXT5X2ERJD50HYPB9T3VV6JJ2BGHB752G8X47DW88EYBYR9XJRP808"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"TGY14TF3402JC86F70FEM22QTAZFR0AFHZHCWERJJRM0NTQ2803GV10W8WWG061X4PJSMJXAX0VETJ4NQ7KQGMJB82P18M9TYSDVZCR",
+ "proof":{
+ "num":3,
+ "edx25519_privs":[
+ "34PVE6CJAJ7Z60QJNFYV2ARDRHY3H5H28GGDN8J12XPPAAF3TR7CE0W90SQBKVREHWDM3B585D7N4YD06QJTVG60GZJC0SB7F6H9Z20",
+ "MJ6TPVE0TYXF9DRA9Z7R9WJ6K9WBBSGC8EBTAK4K1Y82CWH74G5R1F0TFCPFD8BWMCTMDNNJRRX60831F6YQRYEK6GQSNN0Z2ED544G",
+ "V4M3WFSQRGVZPN8QB5PM794Z3T403TV0RZZX393QVAQ1AB9ASR1M5KPRGG8SMXVSJRKYCRC21HFVTVZ0DK6VHRQH40NVZ3VVFWYQXZR"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "FWD7CEQCG64S76PSWH4XS2107W0SMAJKX91AAEY7ZX3A6B6754B0",
+ "W0ZWYCKFJ593YPAVDGNM5M6PB17K8W9SK1AQ3E1HRHKN3Q86ZDA0",
+ "8TWFG524ZNW1TS6J99S34BTMWZ9B2WBQTQWAVX1RM7T70Z35EXQ0"
+ ],
+ "h_age_commitment":"HYD1W8WS3Y5HCZFGZ6Y5Z55MXPSAPQQF7T15NH89STK03AN4K2M0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"KWWNZ3YP35TB5T4PR4RWVD2QV6EMK873YZ0SAJ3SYXD8DEG8ACTZ2AG0MH4FQZSWNG60E98X2ABKSYGNBAXYED2B2KKGHV1NBZB6210"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"XMZD63C8V3HRR8TES3F5TM64NCS4SEMP3TJ5R6MW3334HCWK1PZTZJZAJ3RMXD72KET67TJ50KCNKWKHN7RJ49736WKMRCVKH1E8W2R"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"2J0W9Y5BWG9R7HQ5TGFY5JA7TGFJ870DZD912WH4XA5QZBFNYSF1YX075E8SMMK107E9G2G6ZK9NYTQ81RNNT62SMG0PEJFHSMPSW2G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"FFA5REHB9MXN4QCMXCP4NZSMXMSZ57EDN67SJKY7ETJ7H2XTYYX61DZ365G8JYYWKJ6BQM38ARBSZC61ZY8FZ4VB2CVSWJ555E4D00R"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"S7JWXFKE1VXKJZEDXZZKP967G24ZF04VF7128PGFG82C1VRNQ0QEB0FEV5AC3ZB6R77H33M87FSVNQ6WHYVPRSDNAEJ1JBB1CB7SG0G"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"E0NZZKMPEMH3YYC62EAACWFE0DN5BFWWG6WPNRZFWJY3GAN5D3TF0GM77HYZGG4HD0K5RVV8872MPWMQV9K3DFX08ZS3ZTNAVFSS60G"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"T3FCVFGX9ZXQRSBWT54DN1PRDQ5P9YE2JJ5AVKJVW7D0C0NP9CVAV4CKRQED0PHCT14CY50YV8AFPX6XWA1WTNR5JJQXY436R6RV61G"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"QY37SJNPYJK3M5KH508ZV6HTBE4D2717JE3CXJMT3FH9BYK03SMEH0PEWSMDERA0VHSRZPD5NVPRK2WVJ6Y5MC8EXXWVJM4ZWFHVG1R"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"DRV6GKFV4E8W40CE20NM9WP9BGX6FH8PHKMAAAFP7SCQ3WSSKF8XWWJ016HDHT84F7JMXEHH3BX91RE00ZW2KCWKVSY0HDNEC5N8T2R"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"84DAZS060JYDCYAQ43H3GM13E2X5VFV2XRE42YNHT4DSFD9TWTZ0NPCR6RM9BMGA8D4NS0J9A29DNNPA506AVN5C6Z2H2CKZFSTEA0G"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"J0KS0JGFQ9D0C4WQDAG5HPC2D1PBQ1Q7BAE1JHH39NK7H59MWTXRDD0DGBR2FNCXW2NW1KH660CGHKEPE81F9Z73N9R9PNE4HBWD430"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"EAEKJQA4GZ7VNGGC29VXD62JBNN09VV7394CW1NHAD0VQ17PHDS4E64KFBGNRTRY29269W0GS2JQZQRZS32NDZ3MDQZ85KDA1QSMG28"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"N19BMDVWBTQ86CSE4R3244AMDCW6RZBWG07N1B6VABXF87P385ZDSZCJ744604E7TT9CCV7QR58BB2P3G3N3PMEHWE8GSNTXPHZNA38"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"YQ0VRT6D63N2550RBTQXW7H94BPWTG3JXZZQNHB9WAEDSFC2F9N4HSE3P5CTR7WGRBD6X3J07BE7V4A232E2Y6R6A6W0EX9D7ZV2R10"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"TGY14TF3402JC86F70FEM22QTAZFR0AFHZHCWERJJRM0NTQ2803GV10W8WWG061X4PJSMJXAX0VETJ4NQ7KQGMJB82P18M9TYSDVZCR",
+ "proof":{
+ "num":3,
+ "edx25519_privs":[
+ "FK7P5ZKNRKWC7ZCYQRY59XSBZTPBVX7DGTX59MCY22W9023VRM7TADA2SGXZJ5NPM5RJDY5PT0SM3302RRA7KC7FX4MPVDY6S80M268",
+ "3ZT51K598J0YJV34FJ0YXH4J25CQ39W7BA4F0BMDPYDP3T6ZP8221VKSWMEK051DPZZ1S75G6EFZDRBGZ1EK9KFS038365W1R51BKGR",
+ "0M46EQHXWX49TNA49M0TN985S6BG8H47474QY18DM19SB6NNGG7S3D7GBT87KTJBVK8FBAV8X7CF1ZEHET1W7TS7CXAQKM1S0Q20TQ0"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "30FRMN0GDFYRES703Q185GF86SNKNK0GTW4XVX2CWQW94A3M51D0",
+ "DESCJ2S5542QF05XE4YRH3R0QD31HHMPX27440KDJVBGYE4Y5TN0",
+ "3BPERHQMSHSP32VKJGP3ANVVME6Q0BW5B8FNAWW0SZGDMYDP9JT0"
+ ],
+ "h_age_commitment":"VMWVB3RWJWD0A1EW006N6N39VMWJ49433CS9BG7RVTJMHT2H2TPG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"A3K7ZFPYG7YJMZRWSP1MBBT28Q9XZ88PNFKNZ6BV88Y5MD0XYX4447402RYPRCQVQ2W83Z2YVAFXED698TS8HAHNA9BPDQ44Y6XTJ3R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"JFTKX6Z0BFK9CBEY8006SM0ERKKMKFC7N5EMXX3JA0C999GFVVMHX85RV1TR807QXJZ4BRAVM895YVMQ5GFAKGA14X1M0FXVHHES42G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"K05V1WRT1A7DHTMQHY9NGZTR5DEP716ETEDRBEX1EH7K8VQ4HCPNSC19T6Z354J23CAAAJA47R5WPASZ1MWVZASDX0WQJYT92PBSY2R"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"EB4H7N476SN5ZTY27WHWYTBCPVKZDQXEJEN12Y9PY6GC8Q5SNDWT3R3MEQW1AE3AW2F27ZXZNF7KPEAW39MDPS9NBSPCVYHFZBJG00G"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"QKQSSB38PQXT32F66D2NT6WB18D7HKDSDVVG3ZB4Z9FFR2VWB2WNMAMRJVEF1M8WXF70RRZZ02S8VRATAQTS4XG3F3DWP4NMMN60E00"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"8SPB2R1ASBGC6Q3ZZBZ3JATYDSS01HRNDQCEPZ8C2EB4NTGFYHT8SSQ7NKY7KN8DN6PHMPJTR54VSTTREF1A5ZFW7WKBFFBDH9ZJA28"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"V4969YHWKE79TYZ2FFPKM9X5HBFH1M78M21MY14FWQTRS3NGXPH7CJHNVDPTC1QFEPNZ9QZBTRW79QJK0H49E1YXAR4D717R9EXE608"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"0GDP4XN7MC1T745KJTS29361562XYADRCG27E54X8DE9VY0Y3KWEA5QJKPMKSTATWKY8BGHFTP0DH5WR98ENWP9QS56RC5HG6GP923R"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"X9C77MXG7KYSYBK2DF9Q7Z2A6N6R80RSC6TZHYC0HC87JTN1GJE0DF7VDYP3HWXM8YP13H7PCMQXB77P4AHZRNGEQ4D2YYBHZDJ3P10"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"3A9B2DAR0TYGWSRP9TZRGHYNZGFJFAWKVS4GRW2ZE0ZTS5WN7SW2CK7671TYF9YBTAJMREXGX6M38VZRYQFY0NQR1Z5QZHSJ97H5P2G"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"5WR6XY3G67YJW9ZV8SSCG67WPTMNJTTW1TVZJ1D3NCPQK3JRRG14GN40RW15XYCWEG158YDF98DQQSPTE2NKEJN3ACNHJ4NVXG1W208"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"M7MJDD9FYDGT5PDV6B16V42WYGF1V3DAQTXQSV9JFA9K0JPVDM1Z3FK3N9ES2H8G476TVD5H1WXVQQR0T461MYSGFY9EHE9E8YMV000"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"242XMD04EQ0PQ5K9V78N67YA6Q59M1EGPEF4111W0ZZK4FAEBGMB0BH83ZZ2AAS5BKB2PHGWM6YYA6T7MEVPWJQEK9ACEN97BS4PE3G"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"ASKEM60PG6E14BYYEK93RW50BYQ8ZM8H3KHB48TF5K4BAYKXBYHT8F8C8ZMZEVS1HXXX8A7X5A7Q49438E8E3ATE4JJXH1FSV9BH81R"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":22,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"WD9WBM5P1HKXJF22TCX5B7MBGBTNT679QCV59A9JQS4ZPT2KV516HV3H74GH2K6989W8JGFTZE0A8E31W95RFJD65DTH0HPPSD1V3XG",
+ "proof":{
+ "num":3,
+ "edx25519_privs":[
+ "C3F9BA1BYZDFN4SJ9NW4CYG0EWNZRTD9186SDQZCYC11DSQ8PXGYECQHHEQV8CENF8WJKSAH8ZH7AFCKC35GP70AM531AC2Y9F0JYAR",
+ "V0V1H4BGS2EZ11M142A0XR44G517MJ15FT8MC3ZYCNF0H17ZPXKFMAJ4DVZVMBXF9RWZ98P848X8QP5E49MCEDRTDW98N80QDAEVS0G",
+ "H0WKAPWW3QX53ZH2NXV40YTSEPZN88NBBTT7HTWD72YGVK6SV58RH8G9Z2CXJ5WAFQ0A7ERAQ0A06CPVD7JTS5SZBREMAKYFC4B2AZR"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "T11W62FGSEY2F4XK9PRVNCAA7AMSYTVJZ7461A88GYWT1GS2VSCG",
+ "H7BGRC2EVKBV4KJQ7RBS241Y0G8R2V1PF7ZZF204B7N5GVP23VPG",
+ "36BGED6KGQTVG7MJ1ZRX5V4VN99JY09RVNMK72JAS2H3SK8ZC8B0"
+ ],
+ "h_age_commitment":"S66G4FWPD3FJ29WX4458ZSDAZCZ3Z7B54BJH916Y1WBBJHBB7G30"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"0W9X82GDB3RB5RNZC4Y0V458X1V8TY3AWEN32QDRS8KHX0DSKPGXHY1KBXZ3FZB3PB9NG1HV36PVYV87EH59RC7CFR1V05VG93PQJ3R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"CPAW0J5F7RXT4PKGKR0D20XY081HNYZNJR5DJDN5YAPHSZMQ304YQGP9TPJ3VN7RSSWYC7TC4ES4JBZ9G2Y1BZ7W88JYHSP4G4TZR28"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"ZQTH3BS8EJAV6EPGETH0T1QDNKHSPRQ0S7W0MSBQY1RBA3KMF5F644J2GEGJZY9GNKMKABJT8JBMET30BVNTDP48XDZGK3RS2PCZ01R"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"QKM5M2HSWZ2ZV5H4AS33Y8ZFH1R8YKD9ZWBH4FNBDRK7N535JMMZVTWBKTG48FJWF6JR35HXJDFB6J33D3ATDE876PY81SZQPHT3G00"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"50JFZ5SHH7H7R6EJJ5C0FQ2CGF1TX0NN4EQZWXZGJ18JY8D1VD2BPG6J7ZXVHRJHSRR6GV3EXJFCGF35568BV0MWH5M1DF1TP2AAM0R"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"YHZWASPEPA64WBMEZ4QY2WPECQ8N069NDCNN281N20Q31JMF4626XQWBXY6S4P2BF5D8ND5KCM1273WS638WW1A1QGQ38T9XRYR4C3R"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"NRM8C22S3S3NF2GBPXC6Y9JMV9BYDJK3FAV3ZB7NWYT0Q9N8VTG262W13P2R3YYQ7WNNP261VAQD2Y3BWJ2MMWATT4S1ET9QW1QSY38"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"S7G11MNEETM67XFDJ3VAWJ5HF32TXV0ABGEBA6NGYAWKRSXKH2QRDM0BC9F3TDAV4BJWSYTX13AEYR7RJKQ08FA8VPHAK6GYHG5WT3R"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"8AC02VG8BX32ZDFMX4VTESKR3DQDQGNMC6BBNJK91T9YDCNJ5MBPB14PRAWPACGC3M8GHN6DGT9XQHM34X0Y3YF49SJ7TCHZ19YHC30"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"PR96ZNZQ2WP9H1XK45TKX2K5Z6Z37Z30M4KN721CDDDZQB1T3YWXN0DVBFK5429Q8CD9MSZS4TG2EE1AH0H8SGEJW4XD0YF50DGKJ10"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"10YB0S3J2G2YGCXHT0559ZC7YFG48G76E2PC65FN49N5MKWAQCCF2ZSZEY9N7FR3BY0H5NV2JTS8M21YQZEA0EBN3MGSRPR04PSFG30"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"5KEG14FVHFSSHD383QQB05T8GNDKNYY1FVMAMTTM5KBR0VHP8970M9Z8QH5G6RK8SJMHME8GEV14CE6VS4WWKZJC93F9DD5S2W5PR20"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"WYGR281TK2TXPJE19CT170QM86RYZ3MDWQ66228NEZ9EBGF5F8R9W5Z1X6DBGA3BAJGXDM6DCQMZNAJG9WPCJKCV81RD6S4Q9P0YT38"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"GSVVRGA8D4NKAES6BEK40S3ZJRGQXXQVZJAVJVTX0KBJ9S55YHH9TWPAN4M78WHM7RJGF8157CBSGRKGDPXZNN42YSSZTD0678QZT30"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"WD9WBM5P1HKXJF22TCX5B7MBGBTNT679QCV59A9JQS4ZPT2KV516HV3H74GH2K6989W8JGFTZE0A8E31W95RFJD65DTH0HPPSD1V3XG",
+ "proof":{
+ "num":3,
+ "edx25519_privs":[
+ "20NQ0RZ20XTPVN0VB1A56W80BPBTWR1TW9M7YBZJB60P3VAQE83Z6DK2J73WR1MBWQB0Y0FPFSGNZA87W7HVEMGN59EHHQVAJ8P09W0",
+ "21HZKM0VHPKQRZ39BEJW9K80BQ2ZYRY00HW737C9RH9864PBVM0BQFJAVNTTBR8TF3MT274SVJ83G8AECPXGANKGA70GJ5E1AWBKAW0",
+ "8TR4H3K2ED1MAH97YGMTX2MMPHMG6FSH7BF9A819AW0RYXSYVW7A24B61JAJ15NV1G94KBBKK53HF047NK8KFAJ8WZNFACF64QDCZ0G"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "SXQYBN08JTY6SPZGS54A0QK3GTBTXK1T8BA0KDH9Y36GNP5B1SS0",
+ "ZRY20JYDS73A6BG0Z3ARP1NED9HWHCGDV9ZEAC5T0GYF5CKPRZ7G",
+ "CGZFCJ71ZF1R68RXECTN2YHVC4VWDAAEJH4YGXJWPAKF6K1EDJXG"
+ ],
+ "h_age_commitment":"XXQ1AV54BB0KKP7X11V4FFZMACNHHYA08NMH1SA1RXFW1E8HFP70"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"CVHD002WCFC3V34FE69A7YYWDXFEGH8A8Y93P365ZJEJ3E9YZ5BGWVB6CKPMN2HNN1CAV9RD3SM2HTBCKV0WAKKC4TFJ7K5H61AW420"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"F2WM49F1RW481GAAGBKCQ9YSGGFX2EPY24NS5P97HFNMF60Q4XD7R894Z8QYMV2VC0SWWST2HACWGQ0R9YHC6AJD4AG1ZKJ3PTTKC3G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"K50JN18WR7JRMSWAM9AGZT2HVS03JZ0C35TXRTGE5CH2C97DJCGX6Q02AK4JWPYS2TPTPXTJQKZB43DZY2FCM6QYMH9FQ6ND7CSKM38"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"5NXQ8Z14Z8F458HZZXJ00QTKCCTC2EHG8ZE3KYFMK28M6BJ6GEGPVTM7VRVPG1SE6V80Y6G50D38K7344QPK9PVRCFTMWPADP5X0J3G"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"K51RSG6MSVBVVVZG1ZHTKQ3RSBGP13BJS76ZEF64BH3XWWHB26YKBENNX5F57SY1694N01ANFGQSBD1V7VDX8H8W0D2N3M7SHGFBW30"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"C3DGBN2VNQ1B3JTPKVHDG0VFEZ60GPGZ9S4J68QW2PATY30XBME694GP00KY42FTFENRTEGQZS08DCXVV9MVXC9WVTW61DQJM1TJG08"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"4SWDRCWAQ5R5JWQ9ARKW7KJWS1MHY1AJN120V2VRJESY8SRPDBJV99TXKPKGWFHZ5MFRSCJ0WZHXZJT4HGFZVDG75QAKSCJC20J6R3G"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"ZW5SB33C2EA6Y41Z30TJWBPP8CT929YMBA7H5B13HABVYEQPWBT57JRRVCRK9CEH6J1SCNE2K9RJ78WN5ECE7DA7X3ZYN6P2D277W30"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"GFANXH6ZN79ZDEA8Z95VHXDW020N6N88KX37JA3YGZSABKJSKNNPWSKX8ZZFSKPB9F0WV4YXGVKVS6SQ2V4WK56HAT67YR65CK2T83G"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"3S9PFZ2FKYEGJDENDJFDGNKE1VV4XYY0HP46D95WY63YQ08Z5WVTAZ6X77JQ6DGRGXMV1CZX2165M6F72TESN0MPN3QFFVZNYZNM41R"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"DVFJ3G165T8NYMWF77K32N6WKJN1TYZH0CPZKVQNSFYVRMT37BZG9QP1KA0PKDTC378FN2G0Q6MP8Y510WF28K30FWC68BQNG28CT3R"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"MS1AVQNAXJ5H6Q6H69SWPS6M0PM4WGSHVVSQDTG8AWBXPG5MJS84H50EFXV2DWFJ57F6WJS04YKRPTYS5DRPA3S2P5J2RMWAB1PWM00"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"Q65SMPYCJRBYR5J75MT9K9AAM70413DAT08TEEXKAZCEDGFSBR36KEWC280FKYP37DTPHRXNCDEMFV0MQ8MGMY05RCTN6ANJRM3MC38"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"BYKA65HDVF7ER7V8JC286FRNVBWX4SHDM90MPB68T4M49TFR49ERGZ0A6SZSJXSM5Z6SFXR3CXD9X4F12245DMH8SZ5T880B28EFM30"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"WD9WBM5P1HKXJF22TCX5B7MBGBTNT679QCV59A9JQS4ZPT2KV516HV3H74GH2K6989W8JGFTZE0A8E31W95RFJD65DTH0HPPSD1V3XG",
+ "proof":{
+ "num":3,
+ "edx25519_privs":[
+ "EQCM855500YTHRR0C5XFTG4ZCYV609QP9EHTEH6R312H6TNCN07NRQREKG386XEAPTH1QJDQ84FGGXC4B9HMZX9MZYB13ZHMVHZ4NAR",
+ "W61VPH5B1GA629Q3A19X84GYVWWCAETVGZH82WHGJ0KSP11DCG4NR92WB4KVJ47FCK326SC64N5HEDKTZ0VQ85FK51SY16W13X3WR90",
+ "MHR2R2BNRKVQH38KHSZ98SDGR7BN1ZF1W08MWWRSM4980VBG243BDDG1YZ7MTH6MTJ49QHWS4B70BDRF88NXW5S4ZW8F3QWN4W6PQ1R"
+ ]
+ },
+ "commitment":{
+ "num":3,
+ "edx25519_pubs":[
+ "GYEXK6M5ZS2WS9E6WRTQKMFM2PFG43SXA165F2WRPMV0PA281A20",
+ "XTJ2YQ6EYW6QZ6R4DV51D6DV09JXBRCWYX7FVVYZ781GE1ACHC70",
+ "TEFATTA4NRVZYRCA12D4G73K3NHZMVGVQ8SNZ02YZJF7QGX9MW5G"
+ ],
+ "h_age_commitment":"8H6Y18ST3RNSQ6EESSY9A4X5MY442976FBYQD3X1Z7ZYNMRK691G"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"8MX0R1JH18RECXBWGNQ1E3M8BNMWS35PWRCFPKDSC0XQNQHEXYH39356A2WXKGHK899MBQCH9NB3QTPCD7W747S4KS7VTSAQ5KVSR30"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"R8HJ5S9B3GMMKF4H7Z1MNHW7X79BKHA4J4JQPS5DJ5RBY76Q51Y31VNRDSW3104VRK93VQT4CD9X531S1YBZ6X5WGVJR0SEN2KR9A3R"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":1,
+ "attestation":"V70GRF5EKW6N0CZQ9G0T86VQHWSJEZ5M5DQTHERKYA0WBKY41VESR44YTDT2RXCWB615F1GY18FSH49P9QBDNEC42DFSG1TSXQ36628"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":1,
+ "attestation":"XYRK854945R08935T444NS9YJCWXBQ5V07P6NX44242NE8BSC3P4XPMYSP9CN9GVXE1KBRE128N30WC8EHHD4BCWHRGABJG8C1T2Y30"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":1,
+ "attestation":"CNBP590TKVKFGQ3JK8Z91529VATNF220FBSFSYDKP8SZE674YEFRQDZHGMNFEEMR78CN8BR27E93SX5NYE9CGKYFD0N9Z75Y60MMA18"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":1,
+ "attestation":"N31KF8M09X319QH6DHY7YY0YV1QNZS7A1ZQ85EPKGNN2F0DRMJP6652V08KZA7B3XXN02BMW60P0F6AVWY9RKC7HEJSY68ZN7NPB600"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":2,
+ "attestation":"VZSZ1RWJ6E15FS0CCHMNA7BCEX6FDSA6WA2CPRZ3YM14SVNR3PGX7SMFP4QN64QHFDRSGKM5FZ1NC4XSZCXZA9VEEFGQNGPYMWSAE38"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":2,
+ "attestation":"M65ZYXFRWT39KGP30Y6EPF4MX49446DHFWNAP57P52TS7J91852VFX60TJAR0BKB5T43AYPZXQW55QWTQR5YJKS7XCFE52JF27AQ630"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":2,
+ "attestation":"3YJSY3KTV6TV4E0T1NSHG535Y6HHQVFQBM664ASS96YKCZ1PG818YRER653TM2CZVWFAJPBBGNBVBJ28AACWBDTVKMA0TEAQMBV021R"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":2,
+ "attestation":"8EQJB360DRFBMWHZX6BDH6E9DTKS1DSG2WP95DGCFRK6JMPMZFM8K791R54BZ980P5BVKDJ36128SZFEA4PXNPAVN3WHTFK9YX7T838"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":3,
+ "attestation":"1GECW5055YMFJTHWXFDKCDB9VCZP256FA75KF7V4V1SX6AJDAV34GD0VCMT9G1J1PYS1VWG6Z892GP98JS5C10TGN5E9RM3J96HJC2G"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":3,
+ "attestation":"1XAKQ15WS0BKCH75XBRDK11MCZ1CY9AFY86F4512YPPQQ0WY613BN96XJDFA68T3Y7ZZ93YYECRVJA7GJAXDND9S8B0PXKY1Z5XWE30"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":3,
+ "attestation":"XD6FCYQJNSKXC0DJFNNPWKCTEX6DFAJQMNGNCS9CH9K8ZBABYC09Y56QJSPHQCWN983Z2EYT2DH8TEEF01W2BMQ9G7VYYJM6PR28J30"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":3,
+ "attestation":"7NGCT3MMDED9QYY5FGR0YQNA0S6VG8JG7D3ZWWKDW2BFGH060R9N12SJ2DZ4A0CWZYXP4KDKBK0C18BTXKBD7RPXGAFKEWD7S8QT228"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "age_groups":"8:10:12:14:16:18:21",
+ "age_mask":2446593,
+ "test_data":[
+ {
+ "commited_age":0,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"33KPMQZ5E2DH4P564AW2KQVT3SCCFXR38Z8ZJASGEPV7N7BT51J0BHKWT5QVTXVMK6545949ZREVD5E7GMJZH5FAJ5A8F5X5JTWY8BR",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "BJ587BYMWE9P33GDJ4EYHEWFJSY0NYGR1X18SFAZ6M2FMHKSYZM0",
+ "ND97NHZ10BTAWPQBEBF5KSJHWX5VGYK4Y509P9ZADBM5F6W0YGVG",
+ "3TS8W0RNJ0ZRD94N4NHTAG617QWK3MGMP8YRFX2YW1XP98001YDG",
+ "95QPEEQ3WK14TC0XRZCXN4BF28TB3Q59GQY5M7ZBP90EHQ7EGWP0",
+ "90MEC4AHC0D7REW8RWSZJW2WE2T3SHP67N4KZF93EMV5P6JKETBG",
+ "XH1FSKVQBXYDCXH7XHJJAN0C84YNAB9WKPQ2DSEDS74H9WX1PC4G",
+ "AWMER3A227A7W6M65BJ1C42KSKBKKPZ3ST1CBY19FBR7K6KN6C10"
+ ],
+ "h_age_commitment":"XS27QG909GK9JZTWJTAP926RWBWRVXYPAW5G50KVCQJ45ZY3PF60"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"33KPMQZ5E2DH4P564AW2KQVT3SCCFXR38Z8ZJASGEPV7N7BT51J0BHKWT5QVTXVMK6545949ZREVD5E7GMJZH5FAJ5A8F5X5JTWY8BR",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "G5QXTS9P0K4CBQF5KAJT7T01W67PXG1YDD7TT8MBNGJVF9Z9C8MG",
+ "5BNH9E2CNHXJGH6QXN2Z7XN9RFW6PBF32QSEZ5EKTMZ7XFW87C2G",
+ "0MHNRQ43DG8B53A273PXD9CC26XYDFEFYTT7NNER728FXK9V9EF0",
+ "AEH8BMNYCXE6ASPBQMQDWF0XZCGJ7M7YGGDWYPVXA88V5TJSQBZ0",
+ "PMCPTK64QV4Q4MG0FZ2DNFFJCDD2VCD5W68R1AJFACZN1B4E1940",
+ "EW213CHQWC690Y3J90XQ76MBTQYXJAQARW2SX43MWZ7VW7WW43F0",
+ "HMD8J43VZRDAWE3XRJDK640V5CQPVG827Y348JKEDXHC2YRR0N0G"
+ ],
+ "h_age_commitment":"A92BD3930YASQJWKX5ZGS8AC8191YV13QNRNFVMDWVNT2N4MN6CG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"33KPMQZ5E2DH4P564AW2KQVT3SCCFXR38Z8ZJASGEPV7N7BT51J0BHKWT5QVTXVMK6545949ZREVD5E7GMJZH5FAJ5A8F5X5JTWY8BR",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "QV0C9GHP7HNG02M026G06Y82YZ8TFDK835Q17JYV3S2CQY62WDM0",
+ "N9W0A24QA8XP01W8WWKN4KVGCERHCFKT4MTJJTFA9ZNQY67F33P0",
+ "PK6T07FEP5NH2PDVWMQC3GB59XY2PN0TSSPXZNN3Z7RY1E6DQT9G",
+ "BB4NMS25NMKQNSMTSWM4JV2VY8EFPTGPV6KZEFY32TV4ZR35QCW0",
+ "MKF80QZ1AKK443ANW1H2XAV3NKZV1H51XSBN0CS54SMD6AK4RHRG",
+ "942XJVWCWV1X93C0RX9CF6NHH0746H4TDKN3EKT7DQTNRDXV24YG",
+ "THC6SBYSWBZXK0HM50GQHRC5QVVXK49GC1626FNXP4G1A8B2YGY0"
+ ],
+ "h_age_commitment":"K9SWMQRFB55GKNXFEQ5QJ7YQ3T2PPTKTRKD6D7E3ZX4C4B5R1NMG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":2,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"BWDYZY9B7MJPW7C5487MF4RD6QTTKXXD7N31C4TWNPZDZJS8QNP6PVHAA6QP38QXACS8XY74DHRE0SQ8CACJG4RPHQM2Y7JYTZMGB40",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "A4EN82TNVQD3T1W3P0V4K2SYQMEYTKXHX6Z7N8SM4JJQ8N0AVG3G",
+ "Z0MDPXN40R6DTYFJJH7MX1JHR9PAQ515RAD1TP2FVHW8N442CF10",
+ "P0JEBVN6653X7EFZ2E30X7W0T8WHDHSKJ62G3QJVN9BE7B2DVAMG",
+ "VWZZ8PJFK02AVD3RW1PJQ4N9CHX8D8AP6PDZAMTSYBP1713H965G",
+ "NPQS2A0VFYKGBVCG8NDMENKN71ZER6W76DFA1YZ8CCQBYJM2S7DG",
+ "9JBMSGDJ7NW1QC4WZT7XWWR0E6VFWTTKNR0GA81H8C0XX3YSQMC0",
+ "A0T9BRP13P74A2W8Q62JS0N8JYB5718M2DXNA9NMMR5WXZEKRQC0"
+ ],
+ "h_age_commitment":"S1YWS0RTWKXH2J2N6RTY5C8K2S90NKDNFDHYT8YFEGYVV3T74QHG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"BWDYZY9B7MJPW7C5487MF4RD6QTTKXXD7N31C4TWNPZDZJS8QNP6PVHAA6QP38QXACS8XY74DHRE0SQ8CACJG4RPHQM2Y7JYTZMGB40",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "TYV8X7TK73DHJVFVKR8GFC369MZFESQPXN5MY431MPFAZHPD40H0",
+ "1EZ64RT9ERZRG0KPS7BD14GBX5CD9D4BD0T8XFJEG6WE3ZAHA5TG",
+ "YMK1NQYG4A4Z1A7AFXVGPDC6G18G05J76RV3AR6WBYA0MRSGQJB0",
+ "71F64MKMT1HJ7PER43KYBHR2K7H39HBP9JY5D6MQ27WDCG7PKHD0",
+ "6DVTSKWJWZ4HWXJDWYTTS68RVDJ8GWHYPYRS45R1F7369G4ZQRD0",
+ "7JD5BMDF95SRSP2HQFGZG3E4D9499RH166RM6JPPGN33SCPKW4ZG",
+ "BSAFHS43JNG0NW4R6CJE62N7QFQK9NRC4R841E8B5M5GD2T7W2N0"
+ ],
+ "h_age_commitment":"P6457PP9SAA9E12RJ75Q9FE1C1YK0F601FRFHCXAFG1ANRZJAEVG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"BWDYZY9B7MJPW7C5487MF4RD6QTTKXXD7N31C4TWNPZDZJS8QNP6PVHAA6QP38QXACS8XY74DHRE0SQ8CACJG4RPHQM2Y7JYTZMGB40",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "PJC09XQF8Z5MJ2CXKFGDS7CW5Z1MF1HE28PK49N8876546V3710G",
+ "F473F52RXZP45042ZJGTTPE9HZV78SJBVJT1G3AWAQR15SBHR8X0",
+ "505FMG74H23DGT2RE671R79ZCKZTRAEHD4NPR2CBQYCN2C6CB8GG",
+ "DHBCN2H8R9AHV9QVGRYCFA8HVPRS0MACFVP35E5HMJP417NED2AG",
+ "PAABR7P4CT8XJGM214EN8K5GVDN44MP9VW6XXVX8N9BZK2PAZX8G",
+ "GMV8VMJG468W2W3M1BB0QQE38FZ3BE8ZC95543B5KTJS2B6RM4F0",
+ "69ZSK1EM9W09Y1T1HPE0FGZ611Z30GKEDAC1Z0DSD82BZPES8GC0"
+ ],
+ "h_age_commitment":"05DB864KDHAX3PF9CADSTWRXYAK0Q71YWC9D4BK8WMQNDHN77PHG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":4,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"07ZPG7GTHJWE9SEQYXS4THKN8Y8PFKB8PT6RS9NG6ABKNH14MC7052FV8B6MA33V49H3NF0K1BBF93GPHF2HFPSJ8HTZ7A48PSHH7D0",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "19FTR1JPCS80PAD0NHDVA3HVKQE9545SNBAT7XYRQDGRX2X9CDJG",
+ "CCG9654CCQNT5N2YPFEVQ0RFEEB3PY7GY1G4R9685QV91KBNWSKG",
+ "YWBEGJNQADMWSGEW19TFAACV5P58EEXNRQZ52H7BTCQWQ5N3TW1G",
+ "AKEATDEHS9F6HJSB0FAR8GRGYSQXDYEH16E4JWB519T47Q59FAKG",
+ "BMW1ZEDSMDAGP2X8B9MSWZYYSRFK07VW2VJ8DJH3EA5C5BCV3JKG",
+ "Y98EZYDFC5MGYJ1PK9YHF8XTWPBB60B61SMZXYBS04B4K1SSYZS0",
+ "93218MJH0RJA80NK635N7AJ329928AM8H4TST96W3GNXMZ8JBX70"
+ ],
+ "h_age_commitment":"PA2HFSYH4EEYMPR3QKKDJF6SHMD4AGFFX3WMQ3NRBQBCESN3K4DG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"07ZPG7GTHJWE9SEQYXS4THKN8Y8PFKB8PT6RS9NG6ABKNH14MC7052FV8B6MA33V49H3NF0K1BBF93GPHF2HFPSJ8HTZ7A48PSHH7D0",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "EVTE01Y54CTJY9PQNQD3MKB9AFQAGPMG8KFB8HJ2DBTYWCA0M190",
+ "N5PB3F186H55KBCTVH5REFPGP38MA8949F79V14GTR4TAV2MZXG0",
+ "Y9DKPJDKCY18P3SGKB9HR51JP94E50S93T6WTM931E4CAW0GKRTG",
+ "JPZ2H4Q6EXBCXGC38KMVGJ8QWR1QN27H0DHQ1ZCX1TRKNZPS00G0",
+ "F61CMCAMZK16RSFXJQDW3R1FGR0C98GZS48DJYHPGAXE4DJFQ40G",
+ "6DTT3GVX2TYHR4M7VZ9C7Z8K8SC8SKZ8MN9CAX1ATS9XYRZ1NEWG",
+ "4YA2EXW06NQPWF1YZKH140WCJBB6RCWFY8P4DHDPTT3BXPZ56JDG"
+ ],
+ "h_age_commitment":"QPES8PQJPJJ1A0BW48HS5WWGT59TZRSX4WEXFT3D5D9VZQ1V7YZG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"07ZPG7GTHJWE9SEQYXS4THKN8Y8PFKB8PT6RS9NG6ABKNH14MC7052FV8B6MA33V49H3NF0K1BBF93GPHF2HFPSJ8HTZ7A48PSHH7D0",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "SB2EY21NTWM17A8X64JEVJYYZ98KDENW6CRV2DJTDPWBEPQ7WEX0",
+ "B882DG7R86PSYTYK81NYZJC8GSDEZBY72F3VJX80XC481NMWDDC0",
+ "ZDT45ETN84X3VZX6F6NSAJYSNTM8ZNZTT89JGRPECP2K7D4W5GEG",
+ "3EMJ2GWTAVHJV6N973C2MPTZH2BNF2EBGYG1XX672YBNQB4W9JYG",
+ "KGZ9T9WX3XQYB6EVM7M8RVWG2F8Q6E6NCPBNYVZ0ESVQKE9435SG",
+ "DFRS4B46DDGX5DAJQ4YXC76GF35WR9SMEDHVWK8ZVX2Z02H424KG",
+ "7825CS96XQGA4ZAJPBC2YYVSHMC68171G69ABSRXC8PQREPXVVTG"
+ ],
+ "h_age_commitment":"4RHCGVTQG1BNSEXZA7BXMGAS03VXCZWDJYYWCMAS35J0XWD38WE0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":6,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"NKT41EGZS3TNAKWGDWR8J9AGGMVA2K9DK7TZZ5EM1NRVCFZQ2MFTDWBARKR86PVY0XJMHXS21A4Y9E8K7V7TDD37Q78DD4C6PDDS8XG",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "ENFBQ16MEYB623CA6QN30KE73916NWB697Z1Y3QC7E0S4R277KJ0",
+ "NGZ2GKT4GG0EY8FP9DWFBG8M02QJGM009GX6P7MD466D3K37GD8G",
+ "48K4GPDYB3ZTFFXRBPCBXW5PDAPSPBXWC6G6YP58MD2PFZ334A2G",
+ "WJY5EFH92BZKVJZC6E5T8K1DT9JSSG9HQSTR9BZ9AHAF16KFB1SG",
+ "59NS9XE6E89F54QW80DZBDTYE5YJ3TJE8JFF4059FMRMA7ES09JG",
+ "PPEFR3EPQR6YSS2F27NHXWK2EMNNSZGGSGA6S3NVJY9N2HB3F6D0",
+ "HY81BRTJFGF48EE0BWZ94AVHY7PE02V1AMX5W0VG4ZGWZFHAG7AG"
+ ],
+ "h_age_commitment":"36G95Q9F3DQ73C5PQ7KPYK2SGEQ70729WZ4ZVSBX3FWG266F2TP0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"NKT41EGZS3TNAKWGDWR8J9AGGMVA2K9DK7TZZ5EM1NRVCFZQ2MFTDWBARKR86PVY0XJMHXS21A4Y9E8K7V7TDD37Q78DD4C6PDDS8XG",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "TH8NTWCQJ5GKFHBBPSH51CFGQV8XH5FAVKHDVFPR4BD5DM09MYV0",
+ "2WVF54F4QX9HAZ2GXP923K1ZQ1THNFCAKXJ6VHQR3A9CS2TZE8Y0",
+ "B0C6NQ7RBXG6D517JWPNN5RJ97R2BBESZYFVQ00HDBEZ9Y2N97Z0",
+ "0EKMSJNM5DAD06H4ESSB576TCDPA7B7Y6518B2A8KWQYHGWFJHEG",
+ "W9J4VTKSR5PX1M2DCDGH5SG3ANAVDHFRQ91NPCYHB5MNVHMWMEJ0",
+ "H1EZAVASR33QGW8RKNJ5CQ2RDJKDCQRREVN9D79PCYD01HEYK7P0",
+ "H2KA5SJQ5VYQXK79D8WH9NADHP4H8FR5HFHMF4G5VT9ZJ80FWFXG"
+ ],
+ "h_age_commitment":"Z05RV0VRMQT290YRDY2Z6ZW42DVMHGA34BYKTV7DJ4JFGAGXZ980"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"NKT41EGZS3TNAKWGDWR8J9AGGMVA2K9DK7TZZ5EM1NRVCFZQ2MFTDWBARKR86PVY0XJMHXS21A4Y9E8K7V7TDD37Q78DD4C6PDDS8XG",
+ "proof":{
+ "num":0,
+ "edx25519_privs":[]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "CW3J574VWQ66FX83G62G773X33YVRJ96SSYPC6AS02042YVWRB9G",
+ "M1YJWDEYC9QKNG8VBSBGTBNDRAKRSMD0MWM4DSE134BMGRBBH3MG",
+ "V4M86WKWA1X8GNAM34A7RNCTHSWSWB670ZGZRB9CC5941WNCXC80",
+ "PCSH3VN3BRGEN28718TD1EC23JDARSX7YKJNG6BAXY1N8SVWT3WG",
+ "1RDEX48G1S5BV5PMFCGH3GEJ9SFP66NMQK2KSK6RJWY00Q8FVPMG",
+ "ZVQVNJRTGE4Q8DJ76H57C6G7ZBTVSZMXZSY6ZDPBZMVEM49KJNSG",
+ "1XN6TAFHVA5BJJ8T9T29AFB19VZ9Y1GZB52E3Q7TYKWEHSDERRP0"
+ ],
+ "h_age_commitment":"EYT6JGABFA86RT1KK1AFSVXDNADA7JSWE4K0MRPR0VQ3GTF97YT0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":8,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"WE2XX945BXNF74N69GR0RWFNGTR2JZ2SXGGVQS7WY5MX0YWM076WZDSCTX18FPRY6ZX3YRZ6T5WN2VAH8H1G69MJ5H6MAZM8STP84M8",
+ "proof":{
+ "num":1,
+ "edx25519_privs":[
+ "C29ZEVSA91E8380JBQNKNGVBG3077M9PFXJN127X1HXZV6G4KD17T70C88T3JJ0A2MSPAK80P5PXMJH18CN0AS9YNCGEPPGQJTDXDVG"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "TJR48M7K3Z98950XPV6NCBJ5VFM3FGB32MY9R55VAS2M0ZMDPAD0",
+ "YE0572R01NAVY71VKSQZ1KZPZRP7K286ZDHKSM4G70ADZGMEHWN0",
+ "X94YD6PMA5G82HKJA6BGK60CX23QC8BRCVCJT0KDAH34Q1MPTVWG",
+ "J0E76PEA6CNKRNTQNP37D3EMP2SBVPGK4CDMPRTEM60TBQG03DGG",
+ "Q79HZQPKYRYSY9CRKS689RZDC68WGA07P4GBYDC567YH0F5381KG",
+ "GJ5TFKV4DMY3AEZ07EGJAAF677X45ZQD938BJCCGP60Q72VJ9PBG",
+ "AD5Y6F3M0JPWDH7G84THTZT3B1GFBVGN3WEFA53YDQNVRZ24ZW9G"
+ ],
+ "h_age_commitment":"BDV9SAMKDVZG5TSJX4CYARGWSD14TJ2DSKBPZM2ZNCDXFJ3EZKM0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"XW9E1SN81JJFMJXDSA4X923PVHK8Z8ZAXRD0HFSWZYNY043N7X7BFYBP42NCV31E4E22CG04P7SK3TY55HT1S64ETJXNDKCETHZ7E0R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"QTJPFSQ115P0CGVZTPQ6PC47NNXA38S3Y09S320RE5MCH4GT921Y11BTK3X30EZ2NWCJ8HRF9QVY1RJ9FYJB2XCBKWZ9NFYEXMVPP30"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"WE2XX945BXNF74N69GR0RWFNGTR2JZ2SXGGVQS7WY5MX0YWM076WZDSCTX18FPRY6ZX3YRZ6T5WN2VAH8H1G69MJ5H6MAZM8STP84M8",
+ "proof":{
+ "num":1,
+ "edx25519_privs":[
+ "1KQ84V85VQPY6320K6E3VB12S9JQZ5NMBHGW454X8ACZ2ZG2FG1WFZXC6R2BYVGWKCB19C6J8HMSX1R1H8Q2GJH4ST0WHFZ84RPNHJ8"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "X2A967YFAEXFVX8T1PN7KSGXZX83AE7TCJJX8RHMZ3S15RKS5650",
+ "JKCNYQHHQ54F9X89FTWEPCN6MX52D5TFN1QJRT772KSQDKQSM550",
+ "DZJJ7CW6AX8NP0EVMB3476CJ380P0YN3XGQC2SKJNSF563QBZR50",
+ "PP1PGN9WA7DJC2RA4A3103FZ9QVG9SDDFA707APPRRTPBJAKFS00",
+ "1DRSCJ0FBXHQ979MT9K5DH11WC6X769M2GH3YHEQFM66B0ND79XG",
+ "KB2KHCCD75SX9CWCT8WGE7S626D1HHPPSZ6BMK1D9PSYJRR9MGFG",
+ "QXSF6NJCA55VE7MDG7PSCADBWRX4D3KDY4CT5SCH6M45RM7N6Y5G"
+ ],
+ "h_age_commitment":"N6ST2SHKPC8RCQ58AB38V8XMVAKSNV0N6BTXQQCSK0NJXZXEJ7D0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"EVK3910BKHXC6SSD3JJ4T88JHN5K6NF25XYBGTAK5YJAMDTZ86X4N1T9CZTDWSHK6D1R7KY92MZM78T6CWXPAH4Z6YDDQS2RM2BA63R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"SFP46WT0T18GZBWYXBD1YHV5JSR0P5PNG2YMX4JPTYAS9R3V6DT5G6FG7FRW1HJX2HPFT5A0Q45PX9V09TE1C1R8F8VKWYJ9T876610"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"WE2XX945BXNF74N69GR0RWFNGTR2JZ2SXGGVQS7WY5MX0YWM076WZDSCTX18FPRY6ZX3YRZ6T5WN2VAH8H1G69MJ5H6MAZM8STP84M8",
+ "proof":{
+ "num":1,
+ "edx25519_privs":[
+ "GVV9G6Z91P8A4S0KTHZBN1EGY08AS2GMJ4FJ0XKFKNKKVWESXG3F011MRH8E95JR7YB08WS7E5NWF6Q6XK5A5BZKFEHG0P06EK7JK00"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "KKRDTMDZRP8JSFEYRWT1VMTR7RCMKGMFN0H08G3EP8RQV3RPXTC0",
+ "10KPVT9JXAS42FG4Q2T4AQMXDYZSHNYVA61EQW8P7TY3DXAJAWN0",
+ "917QHWPAXPQ0AJ42PSG2G5NH6P370AXFYXCXHXDCWWW030K9F090",
+ "S87K8AZ12XXA2D6N0FSJT6PW5QCXJ6KNRVJA186FFYHD9ENRYD2G",
+ "78KQESJPR5QHPXA2H7P8ZETJY884HJTYK54VX3CGBBAR68BE4ZB0",
+ "FZZFYJ4SKJG52Y78SPHVQX5K79XEGY52B6GRBP0NNC9BT4EKB53G",
+ "KSY7PBVQY87SNT458FGJXPADX6CPE147EDHTCTSE673CBACCTKP0"
+ ],
+ "h_age_commitment":"75CVY8V1MJBK5P06DCQNGASS94Q8EV19WN47YK8ZDSY8JPBZ2YWG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"31K82SCY20Y7Y8QAHR5H5SHM8YF9XSWGZ7034C3CGDCQZ9CVJFAAAPPDR4S7KRBHT68CZHT79WZBVGKZ28QZ66X85MKN24EVCRBMJ10"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"0ZW0WKB00S76DJHVX1RZ9JATZZY9PBF2EPPW525RGGBP1S79RD16HG71JC70P4TFHC8BA3APED0PEB3C0QA0B6YDH49GPHPRSV37C2R"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":10,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"WJD4GQMZNE0W681CNHQ68WMYEW9XPKRTCYVHASJCYFYZ3Z69T7BH1PW6ZE1ZKHYBFFNW951D206E63R8S6JMTGVRMH7TRFY14XJSVBG",
+ "proof":{
+ "num":2,
+ "edx25519_privs":[
+ "M133QHQ1HCKDCS0H3RRFHSNTYYNSJZFFQMANQKKYXJ3PXQNTTDB2NAEQGRWNAYW0RFC72N4CY3PAZJA8GFP0CWZ24CG1DT8FTGS6BPR",
+ "50DNSEMFZGAV09RJ3Q83HGCBFCEXN7Q4NS0K1FHBZ18KRRC78NWA37RGM6X8NZ76VPVM89JF29S21EDNQD3XYAC1JHW86RVDXYXTZ8R"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "Z89BQZXY5DE21CN4YFKM3BAPEZJS4RJVKF937Q50MSMEDYQN8YZ0",
+ "10EKZNBZ0D90J6XS1C6X60MW367J6V6H18EH2QDXZX4Q8GXPK2RG",
+ "SM213HNM4S4E5KMQJSE740CJTV90XJYV4NFTP15YKNJBRE45HYZG",
+ "MTBB9PFNRE2N2617ZVDMT8S7KHCRHBS478PTZ9T7KWSEH1FY6D0G",
+ "HMWM5GWKE6DHQQ4ZXJ4E4495C653SHW9SS97M9ZNVAMRDB5BF8KG",
+ "T023XHH4FPARTMZ7HSWSQ1QXGAG40ACC71F0ZD7CD0FGP5P0VAEG",
+ "C3CJ8665G711X417BHTH9JJ3ER6N6313XJ1VCD6A9T66WQRQ702G"
+ ],
+ "h_age_commitment":"DNS7JK4WZFXM05WGK6BH4TMHC7CJ8GV5VDRHSG7Z1S54P4MZ3CR0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"ETJKY3K5JT89N7TNVDMFE5X6XG9S8B5VXK54ZH5B58N06P3ZTSX5G9FY06M159HGVFPX4YFEVF9E926FDSY5MTXRT4BJAWAWMTPW02R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"QC6HG3X8YBF0K1END3P5DTN2G2TCPERTBN3QQGV8GWB14EJFKB9H86WRNGTGVNPS5KPHFR0Y5W703EGSFFK636NSXCFMG17QGTZPM2G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"FPKW42VE1MQQYFX687C16N70T0WQP7D35CSN5JTY8FDQKAZSG8P8157H0TW69Z9HSBJVX01M9FKDCR32QT8CCDCHZJVAYN457F9EG2G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"7VP1ZY4CFPVWEGZRVSEP4YGBRCW9D2RA65KD9JDC3T50W40YE1BXTW1B9QEEHRWPM8KVZG81S7H1WP2AYXFHMDA56PZSW67QQ0VF000"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"WJD4GQMZNE0W681CNHQ68WMYEW9XPKRTCYVHASJCYFYZ3Z69T7BH1PW6ZE1ZKHYBFFNW951D206E63R8S6JMTGVRMH7TRFY14XJSVBG",
+ "proof":{
+ "num":2,
+ "edx25519_privs":[
+ "VK07YPE0NK1ZQEHZNC85Q8WZJDQEMG4MEQGQGF5WBCMRRYSFSM6YWXCSVY9XAPP4SPZZFSAR3KGV40J0G1940AP24KMSGX3KPN9SFK8",
+ "T6V974T5B9KWXRPXN3V9S1AQ3SKCPQH52ESA11X3DDPHM1R3DC54M03FV61T39WSG2F4S190NTEXM06B9QG2BD1J6TSWMJRV23CSFRR"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "H7PB4BBSSHWMBXAH5DZ73NQDYQ3WA5R0PBPB0W99Q78HWKBFK2P0",
+ "HPK30HVQK8CXRAF1A6Y5H5W9MTJX9ASF64B2RE790YFH1MVG2D7G",
+ "JQBYTNH01709P479QZN670TPDN51K3VK49Y33CB8XVWJYNSHDBAG",
+ "V1CPG3RXJRRC74KWHSDE5GD9QDBEH9C081N30H926RCHQQGBM4V0",
+ "9WVJYHVTT97V8N3M1A50CCX9DTYK76MWN646JBQ46WW5G1T9SWJ0",
+ "NGRKFN8P83QYDR4ET0ZBXXTY83C8E8JZDVX6M0HFA9V0Z0X6RH60",
+ "6HT1YSKEZ5G6HMZ8S9EXX1W2T1AS82JWAECW3D219KR5W1NWNW3G"
+ ],
+ "h_age_commitment":"4EDERFRQWQS77WA0H7T2HHD0EMX4Q21YB96V04QZ4F9W06QX1DG0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"MV3G6VWKPB46Q1NZSK469B07TCTXQN74KT6BRNF68VP4GFR601675TXZDXN2CCEJEB0SS138K6GSXV8PDAN007PYYYANMSB3DXX7A28"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"JEE9QJGYKV352HJ6S2VVDZMT7PX5X1ZTFT0KFXA8B7CH57D0K27W29VT3ZFCQJ9CTQVJ34Y159XWHAPQYV1G3HCV96BYB0BXX0KNT2G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"CJ6SQ60G80WK3AAD4N9DK4G3EZ6S2E3PS7WB1V1DK55TRB4PP7SDK6AH070CGSGBH39YTBW2NN01YH9DN6171FCQQJJFMQVC86AQT0G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"H2YGZFFRWPPVQSNMQJ2MWBSXCT9NEPTK9VAMFEQP4R4KN7Y8VDNHEVXZJZF8CJSWJDSVFZTPZ814450W1HCTYXVGQXYK3WESEMQ9R30"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"WJD4GQMZNE0W681CNHQ68WMYEW9XPKRTCYVHASJCYFYZ3Z69T7BH1PW6ZE1ZKHYBFFNW951D206E63R8S6JMTGVRMH7TRFY14XJSVBG",
+ "proof":{
+ "num":2,
+ "edx25519_privs":[
+ "714NRGWG84SJQAR3YA4DQ0FEZR80XGK7ANY7DP4FFZGCCJTCMR55V8XDW3A2XQVSNTPXMD6T945JAMKVZ8XCZE1V77Q3R5WMNF7C0V0",
+ "YG28RKKXQ424X4J2PXVS88E4MKN8PDYMPBSCHA3HG82618BZE03XBM9671EBQTEP0VP8Z6W6BP4T4FMD19EMT4BF0WBYWRSSKM09T68"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "JNBQSWVTZQBDA5WG2CWWS7CDQ58ABYGAWMJJNH3Z6G4D0VZZDK10",
+ "W188FFRR1XM82R4KGTX0DR4H78M8HFXYF6E7V1FRA905VTD1DFRG",
+ "SAJ2KY913REJRPADCAE6MSBZCQ5S42KVGM0J32NE0Q5QYK7Z5Y2G",
+ "QY2032YNW20C4AVJZHDRCH31KPES6208MH5BYKCXNZZ9K7VEPJY0",
+ "AJA3V6GYSN05EDWCN4XXWZ5M24B6N0TH8EWX7B2FG9EQEW9ZXDC0",
+ "NMY7V20NA5E10MFGFS1BXV9VHYBECRVV1KY0FVR80FN67N2X5K10",
+ "W3D4368RWZJRE2CX0GYYTJH7XN6A2XQF9RKCGM77P1AD7NNMWXT0"
+ ],
+ "h_age_commitment":"9PFYHDPPHFS8TWFWKD2MQGSAB0M8H2ZE8WNWYGJG715K78DS59Q0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"WKRZA8WKX64J82SSZ47DNP7SAK2HG86TXD326H5V80PBSX77WQ4F7WPXGDY6QNW0YW5PVY0CWWS1BKWF0ABHPM8GACGWEDXXSGEKM2G"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"6NCMP3J7MNNGJ8DVXMVFS028XW72N4QVMC1R28PNH2SZC9SX11NC9E60BFAMXPTHA1E9JNTXEFG9D2P6J4ZF3JH7TAYXC0Z97H7NA0G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"ESRBWM5HGRVX7DC4FZZA19WSDN0ZPP5DR66SCCNXXG4F7YP7SFPHTNBR1HDE41VYA6XSAHHQN6YZS8CF1HFTSQAQFB8C3GSYG3M7W2G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"ZBPSD3KSJFZYDG667Y3DP7GDNDS4TFJ6RE7KS4Q5FVD2BK4M87WTC7EB7DBYFNZ5B10XPPNVRFSY15HEEX3QRD5BHGKTXAKN52ACE0R"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":12,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"4D6T861B48BMYT1PS9AWAJNRJD7VCN7SKZEHDAXRMNXF3RM5WJT430WNRFBGMJX1QVBQVDWKZMNQ5FR49H5RCWPS009JW5P2TH2N0DR",
+ "proof":{
+ "num":3,
+ "edx25519_privs":[
+ "Q0NSG53HR639KFD94RBB8FV7Q6EGBM1EW0PFG546Y3NNAPD2VHF17ZSMG97HWGT13SFGSWHWGECXXW55AJD5FMVNBMNGE0W3W922V5R",
+ "P3VWNJJG79TJ9CMVYG7V5YMJ9Y8566N9ZAC7BXQ1N0EE1TREK9YPESHJKFFE3G998BF8TNWDDAM6NQ4PCJP8NGHV5TE621A17Y4GA7G",
+ "13V5XA1RCEJ4TA8RBHWFJYF787S1TG0QWN23WHEXZSB5RK62W5VK9W4K5TH8YNG7C3AHSC05YXXHQSPSK3PBXP0C24NHMSRS1DKN1K0"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "3H8EG806F0A3G8Z40WJE8KCT47BMY2FJXGYR08ZDDQKSMTC98W6G",
+ "8PHVPTWH5RFVEG5Z79R5GR23Z1MYHYT5PWCNDRXMN6GHGPFVYR00",
+ "878PZMX56KYH72659QG6ANT6A36YF8Z9H833K230KWQBNY5XMRSG",
+ "1CXQ3PPFG7J1RXVZDQKVF32G9TYSX71Z1SXT069MNEXDSAYRJP5G",
+ "KTMSPDT4VV4VW2RR6F3TZRJ1QD6T3GCJDQBN7NQM0R08S52VMV40",
+ "5NW2DQJ6YXAGRB3KSB5J9FEHACG7N5V075Z3MXAKFJ0B9AMRQMA0",
+ "6NZR6NVNWYC5B40F96AGRG233K1W4P065JMKT090N36X328XDGTG"
+ ],
+ "h_age_commitment":"7APR4KBWDQ42VSB1FGNCVS8V9QET9M9FKT420836JKJR8ZXDN7QG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"574BSMEQ9430MESJ16T32CF07QAM8X5RR7ZYGR02W308MGNNQJM957XJRWHFAE779WHYST3378GSMQ76F1RPJQ200QYS3BAW6WD4T3G"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"8YA38DZ5SNP5TM67XK4VY5JXQ16JEWVZ1BY3R714FJHVR6JB34RVTWJ60VA1Z6TMBR4N803A9YMK547ZRZF2N4RPGP8G76H7DKZHC0G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"XFDQ936KWQP50BB2HVTBNSMDVF9ZWTFCN38RCW5FTF7030ZHYXAJEDH1PHC3N21Q04XQ8JCDABQ8QAXCVEHA5T7VATK917Q05P3P838"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"DZS3HD05D5YBYNZXSYSAXJY2T65WC7NBC03CVCPN0NP1M52BY519W711HJN6VHFY46SMCR038CJNWBF7ZV80ABTSPKQDMPTJGKKV02R"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"M2HC2SA5SG645J62188X8DPV3EKDFZ1B9XNBSF8C094P27ZFS40NS9KT3EN9KXQAWA495ATW6424ER71YHRHGNZ7BXZJT85GZN3AC10"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"4PF7ZGRARAY5XZK3PVKGR090FCPDH10X9PKMG31YDAYEMNNE04XW9808ERNK2XPN57RWWXRTT07K9QW5C5ZZ0A0B0RYGXNP4NKE663G"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"4D6T861B48BMYT1PS9AWAJNRJD7VCN7SKZEHDAXRMNXF3RM5WJT430WNRFBGMJX1QVBQVDWKZMNQ5FR49H5RCWPS009JW5P2TH2N0DR",
+ "proof":{
+ "num":3,
+ "edx25519_privs":[
+ "42EC4C6Z5F18CS9AR79SJG09R74GARDCWD3EKX55VSYC57FZVM7ABSMBCM38PA76Q4BDT5ESQVPQPEJ4A7THPPQWENZEZBWX9JRY6M0",
+ "63QM133P2X2NA5FKG58110P1NPR43CA57C46JGRKDAXQ1TVARG7WGK1RJSJV3R7JV5TSS71YEHPAYDYJT7HJNK8RM3A3FQ4EX79ZNK8",
+ "BGNSFS5CQ0VT2CM5Z72G7BDJVFVYKYHF2G7T4FM1QZ041Q90PG1Q3BNC64MPYS3EYTXPVAXF4MAE39SHRS8ZGZWPMAEP5YFWZJTMZWG"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "25WZ3MHW2BFSQXM5KB4VVCFYTTJ7GSFVATVS035HKJKV5NTRNBH0",
+ "JSFFPMKAECRT3B05PFV7KKS2HZ8C8B7SXMT4JT9X9D7DJ3K9ARJ0",
+ "H8XCP1HARRA448V4NGK3A1TK7D21NRZM967BF78SSN4XVH7HJDW0",
+ "VEDW1ZDYBM71KD7MBVJ6NHVS1NV0TTQ6A5EA4NTME01Z47924R30",
+ "4S78Q5XN72QGB9BQNQZM235HV9C9TBS933H5FNV5WVXXX46RB7GG",
+ "SJWGS4ED0N6WZ1EHVADWE3DWF7FX1ZX0SXGE8RVM711WKTWMFKCG",
+ "M0R3FXPAM0Y9PKN8X555X9B586CVR14P8SVDSC4R6BMJ6QHY3CN0"
+ ],
+ "h_age_commitment":"75277R1RTEK2GCDQD1TNCM0C27R44CNXJF2G0M92FV6H55YZJW40"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"A7YCDFA1RH7QPRX0C8NCW6D1N695ASKC5D2Z1P6BET1RN9QXFWFJ0E8Q73TV192DTD7QR2FRH7P6Z0GNTE5AANA7DC8MYHM9S6MV20R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"TNMSWDR0W0PZCSK31V89JRHM3J08FH6JV8XZNF3A3D8QQW3A08XY6WMX13WWRGSSY13Q5EVCY8BEAKHYM71JS9XS9ST3MRFD7VDY428"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"1B7X9PN954NEKBNY1MDSKAE4E2Q873G9FH51EX4DT7TY2M632N9FPGB3VNBYYBEYN99YB17RJVAZ8DNP2THTP1MB4BCFB0DYMRQS428"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"DR4FT76BSW80EGPZKKZW0MHCYZMG9W82VXYEG5CQQDJSP090FM6GH79JQDY85R02V1C3W7C6WJSMHJAS61QWN6BANCN6VETF24QT40R"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"3N2W7Q64RJ0974GK7V6EM9BP3HFKVQTJQAG7QQW60J2BNW15VDD21104FSGM5HDKC3434TS632BFDM9KV7WP6JWBFFXRG23SE0FVR3R"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"J16S8RQT0J655ECDK60JYAW823DY7ZSDK7H8SWMACEPPJJC1R5QRMWCSH546Q42HTJWVN9NMC7BZAVKRAQW2XYJM4NK6QRSHM51CT3G"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"4D6T861B48BMYT1PS9AWAJNRJD7VCN7SKZEHDAXRMNXF3RM5WJT430WNRFBGMJX1QVBQVDWKZMNQ5FR49H5RCWPS009JW5P2TH2N0DR",
+ "proof":{
+ "num":3,
+ "edx25519_privs":[
+ "D9E9JHCFGZDYFHPRZYXGPZXS0Q5AKTB8EXM2T6MWS3GE5K6DVW0YWGMB8GTAT78NSKRSGGHEPC021AQ7MJ2DZ9BQT48M1PRY987DYG0",
+ "7EVVR1ND81YZ9CSQ1K1A1ASZFD93YEZS0YDQGSKK9D2174ZTT44VJDAQGZQGGP55Z8NYC43VR6G0ZW3H0AN589N8VEBC5XTG9K3VM08",
+ "12VGPC42FDRG1FM08RV63NGXZPA8NPDP2WT9JD9NMJN9AV7Y4G60QK4GZF8VB1N0AX2PRWWV6Y12SY0ZQEYR8XES6JXKETG1P538TC8"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "77H8K68NFH9HHTQQAV9PB170F48HGAY2T3WXMTEECZW6VJFYGDA0",
+ "1TGDN6K8SQYK9E1X7V9DZVK0P0NNX4S3QCC0RF78WQAD8QYC574G",
+ "Q9RXQVQ7FK9NG9HYMD7G3AJAKY2AD65VMFAEXDV2ZVM39CM3WEEG",
+ "70NPXN7A7DD1SYSTXTE3R8M3VFE7GE3CJ87V5JCJPFKVGGVZ3M6G",
+ "C5MZ7K9VTC95NWJASZCTQ7JG671A9HBMC701A23TWWRQ7S5TQ05G",
+ "6YGYNA5YE3SZ23HPEX10AMJEGYP9902S0Q2D36H13FRDMJDCEDP0",
+ "HRA652WKRQ0RKEPWKGW9RG08T8BBMX2M0TZD5DJVSTZ9BE039NRG"
+ ],
+ "h_age_commitment":"TQMFGTNAJJ4V3JHA3CFFWPCGFB7H7FTGF17PEYW12R7H9ZG7G57G"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"TSSPJWSPHYBBM9M0G15K4VR4FBM49KARJ0S44YKPP83PD5PEXND6XJPRNM8PY36R2ZCXBBH55YNV7YGW2CMY630QQY1AM7NAEF4A230"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"ZTGP4NYXP3MN035THX879M0NTSP7TTSTFKKDQ6C946ZY5VYTKAFSQ55YD558NZA5YXDBVG2MYM89SS8T6FYDYTTX9T60Q86AS30AY0R"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"VXHJRSHB9EPHRWEW1RNDCQPGNESE87ZPAK0V1TTNPS27TVDZHA4FHXRTEM7GBHCC57VXSZ4VQE66QQW4Y5NXF2YBQAABMCM9ENZXM0G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"XFKZZEQ31T0HK1DH2VCHB19Q6ZDVWE3GD3DGG14TVC2BTXGTKDA833TCDM2RP3ABNPJE7ZG7M7YDK1B95E0WPYZE2B4VC7D0QRBRT28"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"EY5S6YE5CAJ0SYMQM1C7BRVDDAZFA1GQ9Z2QB2YBHDMHARVDD3B0P5TDR2YMX60SAQ1954715W3GZ05TGN93QDBNFA8NTSD8AQ4KA3R"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"BZ45DRD5QMSTPR7FGQ77QYZQ49A433TG4R9XVEWBD5ZVTGZCBWDWJPVXYWWM2YA253QTD2289XGK59DM8P9X7GTP48QQC8HJJZY8E00"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":14,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"4TFDGH0K35WWXN7QGGZ7BS9EWNS5AMC9TM5BGWQBER9DDWYFQPEHWM2SXWP2NXGGV1HWNY6Y5RVEBFFB4GAVB2BS6HQY40DBKJGXC30",
+ "proof":{
+ "num":4,
+ "edx25519_privs":[
+ "C2J1QM0X0DEJYGRBSRYSJDAF3A8HWKBABF7WCSZH3K1CZP74XD2W0G85GDY1NMM2AQKCXTSDRDC4TPM90QBYGCZTR2FP3ENY11JH6GG",
+ "V2YF3RFD50768B9D2Y80K5671DB272A4GMC1HFY1M2VAMNNQYDJFRJQ94HHKS5Y7BFWNK680DJM8JE9DP1MD55QHA88R81H3VXXNJ3R",
+ "C384Z260TM7TDSR6JVBBSP61XECZPJHBT25ZWFJV4NWMF9FVX12Z87Y396JPX3K0R7T91S9BCDPZVEKG3G40E6H986Y7MN090S7REKG",
+ "P1D6ZKQPRFJ4NDKSKP505NAG1HV0VGY5DVNQVVPYETY2EZPNBNRFHAKYV5KPQAK812055T16XF25QGQAZKFG9XSMSAVB50Y325SC6VG"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "JXYSGPQ1VRCRW2KYQNZ9BMDKSZH5XZZD3K41NKH6Q9FAR5M11XN0",
+ "R1HBPSD7SA7H4YFJE7KGPKF1H0EYGACEDFEZ3WJHYRJYJFMVWN9G",
+ "YA1050S5A7PJD7ZEA4EA7J3THQG436DNN5AJVQGVF2XY1GQ9S6N0",
+ "FN74QAH2NGT2T5SMFRE8FJ9MS9P3CBRSF8QRSZZ4GE5KMMX4BDJG",
+ "77ZQQKEAXB5H1J9DSPFPFDBC5G382ZE8AGJ6M97ABTNBRHRHXR5G",
+ "6M222W93EDZXB21WMVB6AB82ZJX8F9JXCZ3NJ7690X3H5JVCJWKG",
+ "YDZ0WKX5D37J3DF4PNQYT8P0ZEJ13Y89S1KB1G8FVR78MJS0DHS0"
+ ],
+ "h_age_commitment":"F57X524K6HAC3JPWMR61VAEZHCH90DHSMTBCKYYZYK34KVRB08MG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"SB5C6JG3YXEX4ZK71XXCR5KXQ8K33N8Z5H1A3X57CSXEPGF2YDB66Q82JFD49ZJ8QZZCRCZZQAA59NHB3ADV2BN7VZ4FXG3NGKSX43R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"VT5ANRX9PGDJ2875M16ZR3AQJ07NS7Y9SM7T6P1DF059VEWDVQS5XSCQKVGZ2DMJJX41N9RQZH9ZAZCHP3GD8A2DZM9A1TBTPKR9W20"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"NJ7ZG7GR3DQA96EE54D56CDXG2CJ5NCCPK4ENHFGTAEBFE8QD2811VSG7W9AAJW6C4H6MASSZ7S56BPKSXSPS0R74805EHVZM473R0G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"62KYPKJ7S6RN3WBQHH7QBX1NM6MD2431SQYFWNDSCF0GNVSZV6DTN205RYV13CPFSB2WB6PV7H5TPC2TZZMFWG8MBR3WNRTT4KXW22R"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"PXM0ZDPBCAYFX00BNJ32JWV93SEH6ZRYH6X9YEZYD2X2JZW16WXYBYBRASJV66W6K98KEXVBQKCQGBF61V6NF6FNSSJVYBFM88XNY20"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"40ZF57RQ1ZXBEXRN7N929BT4QXR51VTHP5RVKG38V27Y5H5JH4MJQ6QR7YMEY1PCSA45XE4E3N22EDJSBB2YZCNBXJWZQDMZPC6TC1R"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"MQ4CMCZZ6EX5XDR0VQ7CDZED2BV7DDED49PD943TVN451DE527YD3TMA0CMGYHR6Z9HFFMAFV5NCH2VSS7AAR8GX8Y0HQ313DCP402R"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"98ZMBA4B3FGV2WHZFGZXPZ5TD43MXM6ZR2D43WFTCRENVZ9K9G8CE2FFF8QWDDMVNGBNPT9CX148KGQBKB7QNND0ADPPX8Z6ES5EM3R"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"4TFDGH0K35WWXN7QGGZ7BS9EWNS5AMC9TM5BGWQBER9DDWYFQPEHWM2SXWP2NXGGV1HWNY6Y5RVEBFFB4GAVB2BS6HQY40DBKJGXC30",
+ "proof":{
+ "num":4,
+ "edx25519_privs":[
+ "AV8Y74SDW6BM4GA27DGMD8F5H1NWGFVETKY191SDNE08PEBS7030RQWWAHA2J6NFXJZT7N0F5VQVVFM55207G0VH5N2DXW5WRM3R1YR",
+ "T6MX6CMWBHGHX43NVEB2DKGKDBQHTZVFMZPF1FDTX0RMK7ZKCG2JREBVM0PNYPKA2JH4PADW1AM96024HK56FM67CA21FHC4NA5JQ7R",
+ "YSC634TSZ6DX0YMB41BYGBCF1Q0R924GPDXDK5RMN2R2VPZXD40M9HGCPWZV3RBV00HWZMT0C85M01A9JYN8NH5ASJSYDF1VHJBT5C0",
+ "9KZZNDJ3YVEBDZXN78R50768EH9N80GJ1RVABG3S153JWPMS8R6FZMSE01W211NH3SRXXB5K0VG49W5K0AMXKN0N5KYTKR2KMXEF43R"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "29DXPCPX8WB9A0FWXNSQFXN0NA0B8A5AQKJMCBKVF5VWVP8WHSZG",
+ "G46MXPEPFHHK612QZV3EG6MWC1TY9CX4EF1GQEXP1Z9GRZ0TA1G0",
+ "WPE8KDT80RPP777PXG4M91ZGY01ET5Q4ETBN5XGSE68PS0SKWP60",
+ "RNXRG8QNNXS8QTCS0Z3KJ8YNGMQ0V1ZV9DH4CBYBF2EG4114QEXG",
+ "FT5BDFG1NYTVHJDAWZYGYX5XAD1MJS0M4P6RVFQ45NJJ8QTXW7K0",
+ "RQA92P1GVP86ZR2485N5HY8B6ZW4HHKQ6TMVSGE37KY554KQ5J80",
+ "E91D0NR5ZZNB50M9HJRPRKBMJBNZ12Y8KG1CJM2YEECVJM6V3950"
+ ],
+ "h_age_commitment":"J0F4KNJ3RDXZEKCZTHVTCJ28YDPMRC7MNGWT5GAAFN0HTJJRTWEG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"Y3V0QGX72M363B4SGY8XVEY4JW2B8ZCRECZDRSHP6977BRFCZ8QF0YAZY8TQ8D1P69A3P56CK7PVTGGRESWYHMQN7GJYSXGMCZK1Y2G"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"MAEQ18Z2BS6893PKSDESZHPWV57QYY6KX2CZYRNX86CQYMPW55CTX3CMHGV70N1AAYTZ3PTZJHEZTD92Z1RXMEGFR9N9F8HE0SF7W30"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"4FYXZDWEJ8D7KCX7T3PKXWP6FWYS9DJT9P01MW6HH7897X0ER0SGVST90GTHAQGKP9897WH74HXCMFPXG4A1K090FE2MNKRMZRQM230"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"Z7MM1PWSF0XAMWYSNDGW4AQX9HDNNAK2MJZDW6P8XR6JYHQC51RXXV05QE75YG4C4QY069RCCZYKKZZG0X92NDB0PY3TXT7SG7E9010"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"HTH457HJNNX7P51CN3NCK9F96N3XEJ7B77NP0Q10KF15DQ24VZXF3H2RQ8P4XS5C06KF2X3XMHBPFJFST9ESD2220J3GWY9CAH0V628"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"24DV2XPWZ5Z0X3ZR61FRRRC60PTS0YF4HH7TV29VPBFT2ZNN3EAM4MWSMM1PZ3M5902W9BWMRE3P6FGRECRPZBVKHNVF4D7WCQVP808"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"B88KTRKA6Q8ZZSDCP1SENPXVAPE4WQ5W2C27C47G6D5CVM67F33DSR58DBHPVRYK34ZDSRSZ4X3SBSTPR6W0K763DAV3TQV8AK09630"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"588FXCK60XCHP7R2A8MQANZKWASY07Y0J2N9T9A1BHEJ3WKNKVF79ZAPMMQDWB5F35FSH27TC2AZP838CB67KYZXMQ1F8Q13VXXMR38"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"4TFDGH0K35WWXN7QGGZ7BS9EWNS5AMC9TM5BGWQBER9DDWYFQPEHWM2SXWP2NXGGV1HWNY6Y5RVEBFFB4GAVB2BS6HQY40DBKJGXC30",
+ "proof":{
+ "num":4,
+ "edx25519_privs":[
+ "S76GDBEWTJBJNYTE5NYWHQG58ZDC2VSQDGHDMHWFYJRHE462HW7AJFE3SK6YC03PW1AVYZ918SS1KXJ9FD13ZCGMFBRK9ZAEH8QGV6R",
+ "JRCFDWGEPXD6X60961956RT7CDG1RJGP8YQ4NK20V4ARCT8KZ80F9BGYC5BVPXKPEKQDJ90JY18RP54K2CH6950J5Y0TK720WDT2ZQR",
+ "4ZM75K359BKSYYE4WKMXMHFFJVVG97ES2JYH69T4G8M4YTT2BM711DKGZ18G6Q4ZJE9D9D31XJS9QJYN8015HSFNTGMWAGSC9HFMR2G",
+ "VD98SRXAZBX0RY9V3ECB65583YG6MBXS34DJ3NRYXC73A5F5C030VMSV1JAS3XZN7XF326AQMC5578BBZHHZG61ZFAABV2DMP2Q2V6R"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "VRBJDFPWP58QC9P83C630CJEY20ED4W4A4F6FKGBN4H2CCNMNKX0",
+ "RK23WEE30QM6JB1XXNBCW3FN8QCCFX1ZAV6K5C29HDFW67A5AB2G",
+ "F6CTRCHQHB1Z7QN752W6BYB46PJH6P5YE6RHWVQJJHDFJBT5KWNG",
+ "G9YAYSVKMEQR9HX40WC95X2R6PQK0DBSHN0906EE25RCDCSCT7K0",
+ "Y91NF7E9ZV9Y12D2JCWZT830D8EAP4WVNENHRS7MPBPSJK34G46G",
+ "W64S5HAKYGQPEFAAJQ8KVTY8NPYXTY4QYRK451AJZ4WARCN2F6F0",
+ "6XG2K6CMC5B8EANMY20PPFZH5391VGQDJVHV3QC9VM4YJXGYRRV0"
+ ],
+ "h_age_commitment":"3SA942ZZRXBX5WMK9HJ74DZPEAGG14SGQT100GV2DSNBD96M1NN0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"WNAGSKC0Z265G5P1ZW996RS5APG26R357D9S7NQ3GB9XRRPGP0YNQAEXWGXKYH10HBW4KJZTGKB7EMMXKWS1ADZWGNJPF6GZKX8BM3R"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"JM4Q9NB7058A3CV4VR2FNAQN40BJ2ABZ8T1N8WQZWJV2G7206RRV9WKR2ZNXDNPPJESVKFRJ1ADBX9KTQ6RZ6M1F0EMQZAD672ZJ828"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"TK8R0WD88D66SE4P87NV79SJRYPPK72ZAS7XZGZ8B1NJSB095GBT638T4DW087472FAX9N05MJXJ7YCQH11JVTSS508GGZA6WHTC81G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"920C6DKCME8Z5WBA23AF6DMEJE43H2VET8P0B9GG472YQ9C05KQDAJ06ASKQMP0A3TBQ1RGM4T95R02ZY5NM6XJ1X4MM99DDVBW723R"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"F2ZM081TPWM5V2QAJ03QAP9GSN4MBMHJ1AM6FWHDSWTZ9RPYKF9S11TF817CX0PXVWGWGY7SRB6VJX0CENBM8GETS7RWVTZAAD2XY08"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"966TQXC6VKE3AR6FS9MC6F87EX8CDW8XDKFCVZ91MACD3GMZ2EGNN26GS65611BVVE9C4VBHB49D4DJD2TMP6P0D9RRA4TCGD8QFY08"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"KZG6WW5C9PJW4XBFJN634CTA60F5GKYDBV5GK7Q0RZTXCE2TV3REDSY9V7SW059G33RGPEVYR3FC17KJFDQRXM3Z41WYXXQ0NV9VC00"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"57E5HRTW65MRCCNXZHGA6VKRE953NJ7P1AWPF38003R81X2CCZKZREZKVJZZM7530AT6163XDTM6NQ5XMMVAFZKAQB3C5ZW2D5P7C28"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":16,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"3N97FP1E3ANRTQFKWNZJR4VR5VKNQ8GTRZVXW3JXKWBE4XCM9JYJDAACSMFQGHT8S9ERQ4YDE9GAD97FZKWP9F2K40GC301X5W4K2YR",
+ "proof":{
+ "num":5,
+ "edx25519_privs":[
+ "B27SDGF9XSZAA0Y625XB5G51J2XWM6F7HZXJ2QSX7D2JS7F2C9HHH0ZQCQ4697HZAZE2E28P7J2B8VHRS236SCSWPJSC2MGXEZP2HXG",
+ "C0T2J754RHC9ZVW5Q6NDPC221KMMA35G0PE3KJ783VTY8FA3YXWHSG2Z8WG36ZN8WBCFNY0RFS8ZDHFMK3WE83S2KA3FZSFANDMX3CG",
+ "F1S0NA77BQX493QYQE73FA563YJDXTJW9YBZGQAGADQB209CPHMNT335WN1W9VEBN9GZKNCG6AND4C370QM6AHEWFP6WX8HJXWH478R",
+ "22RB2HCYT9TG15NKNGQ54ZTVTMHDJ7GXHV6EEQQNHQ99YYKNM9A230KYNB7KETV3FQSEJYWGRAJSP3EC5GRYYGEGDRPXYB3RE36EJQ8",
+ "B2ZNM5BZP092MXWG7K5EDY4A8WF1TWT8W9KRPPMZXMJNA004ZXZ04Q35GKM69J33SD7B0VE6BFHC105M0R46ACK5WQKXAFHVJSE4KZ8"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "QMYDVFG4XXDCN73CJX3BEQCMABXCMX5YRR34EKNKHDHNX8FCKHQ0",
+ "7E2CCRGFBN869VH77MC9GVB5WB61RAY6A865W0EH3RJB3C30ES7G",
+ "K53XP28HN14CWJ02J2DFC59XCSDF3C4M07FH63SFJFNV4F7B4FT0",
+ "6EKEP7M7HYAGTE6NA4VPM8M5AWQVV711W0YRJVEAVBVAMW6ARMG0",
+ "XAN6SFQQDTX3JMGC59RH5567G52WB5DV64E7N921NX4KFTXVXG4G",
+ "8KM689TP1V3H5TKDAE58FBE447Z365H2NT932K0GY2MNSS88B2ZG",
+ "KRBGEVSS40VTDE4NBQR561PMY2STJP5YSTS9NM5NHYKP8MFYZ8B0"
+ ],
+ "h_age_commitment":"JAZVVQZ99F9FXJMC9CS34YAND421TP7FB9SK0JW2S3PWEEJV8T80"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"VYPED5Q80EQJN5H1G83AFHHWY3ZMXHG6Q8BDTP0GNDZ3GFW55F1W1KD84N7S9WG0WBWNZBRMYCZ7X1QCJR6NG11WFSVPTGDMQ0F782G"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"DNM2S96K8YB69NQ0EJKBNQMWCT62KHCB0P7GS18SMJSWFJ326YJR04C5FFEB7JW5MZ22FKTJ1A7QAK133EJ99YFH54SX2R83M7X1A3G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"K1AZV4B6RVDDJMHRFYND5MQSG5JM47M9271D4K1K2C4FQ52XS0Q708G95N5875AYF1XKS4KBXT3MWNYJB25C13PC0YN48NRP9VYM43R"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"3PW45FRR0Z0WKFG0K40GV5P5THMACQC02GK61JKRSM8E4A0DQS65YB9FYQPC723RN6Y82JZ7D1WTDRS9TEXW7R0SWZZPMNBJT6Z8E08"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"T2S561D1GWGN21CWYFD78EWHSC2NKF66YCFEMH03MQAWA5J392CNZANVT2F5AWVCNB5VQTJ89DKFBKCNWT01PYJ4530PS9TJQF87R08"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"8DHJF33TN018TTP72GM0H8RZEGAD0X106EJMG1Z4TNEKDZHGKN0MZWZFRKB0R48PJGS0HNJHN6P3JPPTW3RWVXEJD6MHWTYT9EAT810"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"KRNPMT235KATC7295EM5JP2G47GFY7H3J6JMFRPJA14FPYVNCKS3TXA0FWAF9M3FFT4P632KS07FEZC7MGRTQYJE0KH23W0T7RPVC20"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"0JHJV3SG0YXCKMDEZPAGHNBQ6EWN2JN6KZJT9DHHXHA3KRCJWRJAP3DK03374NJXXYD5JAPJ6MG1F940TEWDRDDWJ4VAC1MKKXEDE18"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"B98GRA39R5DVDJ0CQ72FCK004M4B9B8N7N5KN79XA8WEASP0S4538401PY6N6AXRM1FPDMTGQXD03GPYZG362XHQV5G4K7594HHNM2R"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"K2YXWTTQEYF0CKX3SPFXVSE2ZANZMAVAVPH37VDQ1SSDD6PX49ZJJDBB92CQ0TQW7QRTP8ZR9WAK31YFYT838TFEB3V35W74K0NQ21R"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"3N97FP1E3ANRTQFKWNZJR4VR5VKNQ8GTRZVXW3JXKWBE4XCM9JYJDAACSMFQGHT8S9ERQ4YDE9GAD97FZKWP9F2K40GC301X5W4K2YR",
+ "proof":{
+ "num":5,
+ "edx25519_privs":[
+ "15KHCP2N9V5AKTR4C77J04TRHRC97AEX6XATACEBPAQG3S5QAW4DRQHP5VA7F164VN8M01GCAT923CEBD4RKM4AFRA6BA1QRYYVK8H0",
+ "91G7GHZFEJKRJB6T2E47YM5VTD0T9V14EW8E0AQVQ1KTKW3BA014620ZZPMDTTZP9Y5X89K8DZXQY193D2ZCNQEM7YWBECNJ9HC45S8",
+ "D6DP6WEC8GNB8CYSEA87ASASX693XARG9VARN49FBA4XMEXFVC08RE1AKEXD2PYNMME6EB0NRRQ6SX95DA3P02DS3KWBER7DJCYMTF8",
+ "YXNZBMB87J2VXNT3VNVS3V0FCB3CEK7PC98VZC5T0BN3EZ0H8C3JCD9TS68HMFWMWQRXJXVED5TDT6KGT2988ABG6VVGFZ56HQ9P200",
+ "3SX0955GGDVX2B0FCG1CNHD89QYM5ZDAXK3F6F1E7Y6D98MFBW72Q9CK6205T0QZ3YZCQRMRS6YFCZP7HH4G7115X57EHZ763EK4ZSR"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "932CP2020AFR4VKATJ86JA923HFG5DE4E6CCPK603QVW1H2PGE00",
+ "X7NCGB5ZAYTKDE3KY19B2275SGMCE3KQRMAF2MT6FVASK18FD0A0",
+ "B0MH0ZVRHV7N32J0C7M23WE5G489YJR02MSCWZ38YP9GA7TVVHE0",
+ "WHAWDY6BPR0529086QC82Y05K87G2GYMK7HCPY22QGWP4WQK2KHG",
+ "T9C1XRMDHJ7XRHRS8K678G4SAJVAHP2BCS7Q7PWKSCQQGWR1DJVG",
+ "1RQBAY97EV1KB8CZA84069PK66HEHWS0P78J7Q0277NN5V9SGSRG",
+ "MPM0YA6MZP8Q9PRSJ7EDY6DAH5W45C9RDZVNABPC1E10MPQNWVW0"
+ ],
+ "h_age_commitment":"Z7BFSWE87ZF79ENZ3CM3VAS3TV8ZQRQQ8E0ZE9CK61KJDFTH99XG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"8H31D91M5XB2NA8EDCVBMJPX37X5JKN122CQHVADKDKHQZVR55W50WBTCBVCG32GEGY6PH7HVDKBJFMQH7PSRWBMV0Z6MV2H5YNKP20"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"7378872NGV64QCP5PRYCPK2PW916FTD81AQFTSRNCYRR8B5JMN5RNAJ2JWEANEW5GNA7PNJZTQW28GMTH282ZR4SZ60V4B3ZRSTA63G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"4Y53Z0E8S3W8JMZZT7J3HB2ZV2XQCX0K7KWR2842WA90SGF7DJED9HB04XG1VZCPT5QZNSR7MVJEBZZ1SP6M5X618H2VACB2E2RZ008"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"6NTBQH98B76K3ZKG20P0T3HMTE66GSHS86J1PJCG71FK93TGB011Z33CE01Q3R8R92MQAN44P6VRPX9NP1KS83KBB3788S7JSYVGW28"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"K1ZVJQAT0A7304XZABD55VKSSWZWW4RYMRHTACQY1QCDKZJSW1Y3TRN8PJEGQ5TWTBR8S2C7EBJW92MMJ9ZXCGX7CQBFN2REQDAFJ3R"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"CHRP938VTWPWJ7E00QJR9JJ0JJTXHD1HRH3K1MC3ETNCKJ7816C0V37XAPTKM0KK5N5473ACRFH37YKWWD3MNGCH7FVG8FV32DS8G20"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"P1RK6GG7AMQQ9RKGDPNMA7XVS81E4AQF4W0T92XD89317BXZX6A09BZ1XP8M9S82VC7KM2R4XBAPC6WMNQY2M92BCD1YCPS0EF6NE00"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"EAG5X0GS9H4NB43P9TYT8NFA220MB4BHMMERFTS32B9FPYK8HPXX04RV1B3KGVKA689TVP0XJ51KA1KKMHDPZ11SVYJT9ZZ2S61KR08"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"AQE5XR5RS4T1QAAZ44BHGS9QNHQEG5VS1TC85X12JBBVFRAG204WFHYA1026DF3WQVDQHPZ8ESRWSHVJCHK675YYXD6SW15S802HG20"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"KE9NV1ZV5X19W7MZM2GNHQVKP90TWF343GCXWKZ66FAYQQB9J5A0CYZ6VTXD29BTVFFG78JQMXEKQ0A3Y6F5DNBY8DQ3C6RZDAX3T00"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"3N97FP1E3ANRTQFKWNZJR4VR5VKNQ8GTRZVXW3JXKWBE4XCM9JYJDAACSMFQGHT8S9ERQ4YDE9GAD97FZKWP9F2K40GC301X5W4K2YR",
+ "proof":{
+ "num":5,
+ "edx25519_privs":[
+ "KPDWJ8MYNM6M2BJHPJQS8ZP156JRXFN40S6DB0HKHDYSX4A4KW5R687S8NRRA73YAE3TW4QNTVXGDXVVCVP7PDRE55DBW0HZN2YEJ10",
+ "TXM3RV8NKP6B89HBQ9DBMDXQPDJ5T3ZY3D5QPADV1DJ86GV8RR7T07VVFDB4E2MY53G0X1ZFFGFW1V80XY4736HNZS3G8W4XRZS5CFG",
+ "FRNFABAHY2C9CRN2DCPPH19MY3TAZ1ZFEJMYFY7M7XFP09Y5N41CCF4KJCC9BXNQDBKETAHCX05BX6VZ79PWE4W1X5VB0RVHWSCQPS0",
+ "RQ086WDBGXYX7CTAPKC039M79RHC3DM7NK5AVAH1N2KDV6Q86R062QHNVFH9WT9A8GX2SZPK685JVCTBVQHVG7SA4738W9H7J08PRKG",
+ "1C19VHCBPXHDJS1FHCZNASJ1QGYPZR6PRB56S6DXHJKZF4X5DW27FZMEEV254Z5B7FKPFPYNQ3007ZCKCCFSKAM384JK24MJCZH6QHR"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "VZJ2A3C3YN83F6AAHY3VQNXYKZCVX4QV0M37MF6QDK77QW1Y701G",
+ "XQCYWGZVMJFYFYF5WVQXDQ68EYNKEDR8DE80BZKVW66QQPFM7Q10",
+ "EKG7ZDBSRACCTT8JB3CCVEY2SHNF8GFQS3GRY224HBRHVY65R2NG",
+ "2ZMSN25ME01K9CAW0K2NFMJ4C0N6A83P0GP3R849WSCQ7A0HSGZ0",
+ "CEHG8XRAJBKV2AAY224WV3GRAQ1BHV3VZVR5APQ8PMN90VHM9M10",
+ "9T37PVDPYRMPJQF84G4D96RCCH54F05NAHJV31QK11FKWEC4XKGG",
+ "QD06G36ZRCT58BENXTJ1SEZ6SF6GMKYX4SXAFYPRPZVBNHPKDYZG"
+ ],
+ "h_age_commitment":"E0AW2H3FSA2H62WRHQBF3FYXWSQEM2SA3137VY6TY81ME556FFXG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"Y061X2FQXND7XY81MYZJSD90XTGNRP80DS5DTNB8P01GWDJTV02HSNPTS35SV49NJBSN3GBFF5F9HNJWYE9T5S8PNRFST7YNF7CQ630"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"DSB08W7SEW1JX4F3SP4G0WB7FN27F3V70AKBY0H8BJJSMW0EYKARGJP9J2G8BYFWPSNGHDX4VZ4AK69VHRMKQWQK7V85P08NV1MSM38"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"XCZ101WDMVH8ZCQ6ANAHSFCN39WKAGZN1NZM769E3H8FP6FW7G60KEY37EJWR188YN3YA5NWRZKHV61QB42PRDSBZJCFM33ZXZGPM2R"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"EG6PV3N6XWT672BEYQDRDVD4CR0VNR045KEF9FS7FSWJB7C1JT7T1R1ZB1EX0BVXDPCX2WJFTX1WGCJTS6ER6ZCK0AHA9VXC0BVPJ00"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"VD3Z52FB9FB22TZ2DPW1G3G0NCD5N52AE0NTEZW1PPES92AWPZB77Y4FW8BFV06NXM9GPFB1AAP9VJV18Q7VARNT4C470J0JBT7BJ3G"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"TT8304EQTDSS7CK1QB783DY411HYNVA4JMVCQMKBFEZS1W9C6VPQN55H7JBWSBKEYQGDB0RX0A9C5YTNBGNN974GE4C7MFF7BW1SM1G"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"KQX6CX6WQQZCGQ0YG3FH7WKN14H10HES8PAAKXH7MFSVEBDS5PVWJ4H8X44N5Q0ERYYE8KD22FSVPNBF9PH2GFT8ZSCX4M333PSSJ2R"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"SBKW6CVHT4Q4QPKZPB0T11JQXK86SKR69FC10QNP7TAPTQNYJTH4CY5WZR5Q7F0JR7N5Q2B8R0MZ7QPB2PVHKWD1N94M9EC2VYTFE28"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"KANBG4CV97XD36K601JXPSZKVK7DTDC6FYK9TX00EY8TTZGMDZ51Q1G2YZPED9SRDEJJHDWTN33CGG1KKTFT1ZRA7HBCT62YXXQ302G"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"XXB6NV248YY5QM0F90G0P4D3B95JSC53GD6KJ4227BRTP99KECX20FKB5EJSV6KQMDV5TVJGTMSK6B9SK245S8B6T7J47A4JXYBKG1G"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"not applicable: commited age too small"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":18,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"G4BC8QWW8H135R5WCMRTBB5ZNJ0PBSNABMQE8Y5M6N81YCCE65M5CZNYXZGV7TQDWB21V7HTJSZ9AEMZKCDP1ZBWXGYA0VN6NT0A0Q0",
+ "proof":{
+ "num":6,
+ "edx25519_privs":[
+ "80XN7B94MVDVEATJKB1PNZ9YBX0PGP3R724TG78KQEPRVC2DJHYTAQQSZFM4RMXJF6V23VNFSP4ASHADMPEKDMDTA8QXW89KTZDWA78",
+ "Q03V262DS5Q8C40QW0Y00PGHJA3F20J1T4Y53RVYZNBQMA5AR1DHGNRKEBK007APKCNWCAB5EN677ZCSPG05A70821KKKABH2J86BPR",
+ "02PH2QB82Z7MCG1MXMTGNAB506P519HT6TQA43N06Z054JXKHDTEKT4SR3BNM9FPBQ6F67N96TZQ0Z77FEK54P2RPPVJCDBT3P09D38",
+ "83EP71GNRPDATD2RZ12T4B0Q1EM7NW5JEYAG3DBR6MYDVKK05XD7DKV98KQR01RZ5HD319036SD0QPN9BZHP7AXHAXGBKB31VPGCAJR",
+ "C0N63NT7ZYJXV74XHAY8AE13ZJR8K8PPPT7QHHQ1A7XEPSCMFHEABSZMA67AS4C3JFMGHDBSFKN39GCVHSPT9MJHSKBNKT8Z5TXBA68",
+ "W03XPWAV8S1ZWJYJNK9TBH0H6AERDDENY1YJG6GD147J1NNTHS1N5YRE9KF6YFVTD1P4S0ZZ71VM7YW7RAV9RDQQ38DPZD5QBRG8140"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "MC06CQKCKJ4Z6T0B8P2MSBSHD0CB4D1GFTJFFDH5GH39VF1PET0G",
+ "TPTK5YDW8FRQZJ47AP5D6QG21N7AMPDBGN7580APVK0G2D34TP00",
+ "6VP4VKG15BH779MEZBEQ1XDQCEY94J3B5ZY4N4J1TRBJ7DF749K0",
+ "RB6MSM2V7MZWK6JGNGZGATWN514FGPMYPQXMJ2WE9X1V2CWK9ZMG",
+ "DBSD3WB8GMW0KM9GC01SYZ1Z3XS6RY26Q9JV53VJN30C884QD7T0",
+ "1JF7G1HB22535Q0S6NPKAZM0P5EMZXS8AZNV3EEB890MJ1V4Z240",
+ "5114JWCVGBF0EYZEEQ9EDV8J1ERCN967593TE8KDE5S259EA8S8G"
+ ],
+ "h_age_commitment":"4BRKZJTCHN58Z1AZ8QMNHK7PMSXDX2T7TCHVF4G0X6ZE56220NS0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"6J8AJSMV0XW8M6Z1WGPPWS45A0C1T59AE09XGN2NNHW2A93G9W9E9Q5AFXY18DT8C7CBPMHXGBVQV6FYC91JS8CV45SYCQ0SXZ9Z22G"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"X605VEC3KHB28HZ8DGM5ZHK116NT7FHEDCSYM0MH5KQA3TB8891MH8MWXFAHV00X8CS277Y6XP86V97ZHP098B6ZD09JMAP8EKVR230"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"GHXD9677BHZYTP1JRWTAV2CNKNWHYZQBXS0QARTHYD894DK4XYQ5H2FGPKQD842P4ANTX7AYC66NKRAZH7GG71F2P58BW8W5PW83230"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"AHA0R3SFJTEHQK539BGJ1Q0EFY9Z15BZMET4JESKMMB4A78FFXJYJWTRRA8GENRB1FS6H22VMJF85BP5D7ECY0TKCJB5J4C2NBV2E2R"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"4AKJ1MMCWR7ZX5ZB5FE7XAPHZV8VG7PAFQJKJT3QKVYKZ9E93F2EPKXN6VZBRH4ZMCTQ9XNEEGRK731YF2AGM4HJ4TBHH26C46PCR1R"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"TFGQJDAY36DY1HHPXFASZ10HH9WBSPABC9MA8ZGQJ9GY14NQCA0JTVVADG2CVKZXNF5V5NM68750AJT1V007RTNN9D8X0YNEHQ39A38"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"65NKMCX22G1VEEVS53WYR5AP80QCWEEXB43YQW7YBBAWP8PB5QKHEGGNN0VR6T1FTEMR19KVGJ7RZ8K9QSJXB8ZZBPCEYMDYTZ0RT08"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"8BMJ57JKX8C3B2VG0GSFSHH3D6PQCXSTE5NJR2D468G820F5JSPCCQDSJN0ZRT8GW4FR83RM8HHFSE40A3DD2FE664JQBW6MEAJNG28"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"CW7M3WJYRXD6FBCBMN6J4BC0J5XN9V1TJRNFGWWZSW92Y8E1WKE1DGMJK0C4ENVHS2505PFVB6H5A0ZWWA3TC5WGYDN9YWM3YZTNE1G"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"6JJRYDVVRSG0YJPZMBZXB3X396HMCMPKJQFEE3AHGF5Y92X3VDZ9CTWV5ZNAKNDQ1J6F0SM7Z12ZARAFRE42T8PY5VNNMQ0SYGWSE1G"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"ET132B16V4GQ217V1J9JWCXEBTKEDD664NYPXY2SCEP5JNRXD2709XD8W0R93SZSWAN6AKA4K174SWS27D33HN01H6ACYJRCSRMPM3R"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"X578D8AYXWW377TEVSQVZ4SWYBGA8HC31VVGGC13X136BQPJPMXJC9N6QH78V7XWDYA0WZ1C2Y480AAVQTCHKG33W7KGBR79YYTB418"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"RGMW7W6292JV6CC9K4WQ2RGN74XJRJKK37AZ38PF9AN8XCZ5248CXYMBQXHN9GNWEQDZPVPH9RX9ENG7DF9Y7XAYTYNHTBC63ZF8T0R"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"G4BC8QWW8H135R5WCMRTBB5ZNJ0PBSNABMQE8Y5M6N81YCCE65M5CZNYXZGV7TQDWB21V7HTJSZ9AEMZKCDP1ZBWXGYA0VN6NT0A0Q0",
+ "proof":{
+ "num":6,
+ "edx25519_privs":[
+ "1E728FZPQJXE8K6509RVCTKP0RSQ1JS6J7HJTJXF21DZ43SD700NPGB76KJQXKBF1J9JC5DQ9JCZYHPJQZXNE6Q4WCSDND260E6YM1R",
+ "B54G826CH6837YEA6G3PBZ8PNAQ03WQZR3DR76ZFNTTXKPNXCW0KFR49HQP3T90XQECQH7CDRTNXEG8C9PWV3BFZNKWHFD4D9935VQG",
+ "9DTR0QKCHPGFWBA2FKY4TJXMHNQEDA96K3F00ZJV0QKREAF1FC6X8QCRX9NBWEXPK6RY9QK521KSPBHKMX0VT1NE8X29X1XBVHC7Y8G",
+ "ZY73GQAH9BQ18TX9P0RW2512BQ2TAXPB0ZY0REN38227B1J7ZR3RD359AKEDGYY10E8619H8Y8GSK5HN0VF0SDDM1NYV8A7374FBV50",
+ "X3T2JMTYRCMDZ9P463H78PPGYQSDDESJNH9NAK34W263BRE3D80GMNN6MPRHSPDP9NES7QMV69AVXZ5GWYFDAYJH56QX3T6JZWP3ZT8",
+ "MMSGYVCADF7B4XWWEWNN69AQASE0P0NXSHWXSKA8P08MX02JW43YAVVH582QDK5H5D2YF6BYJTTZQHXGN0QWJBVAG187EGD71GRNY2G"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "5WMM35CW7CQTDEKYG8692NDH91WBA73ES4SC6CYA1VC82F6K91C0",
+ "TVNYY8EXYSRKRG9GRQBFV3BYX0D2A1YQNR7TGC10RPD1QRZ1M820",
+ "1NE7XM0S0K006DVQ7XWDW4ARGNMFTWVJGHCHR41G6F8GCAD36YC0",
+ "Q5CTZEZ7XHMXQ88VMP68J3ASDHA8BG2WX16XEME2DSPH7KSBG95G",
+ "YVZW4RQFKPMVEMGNFKM0KXZD4GZW25VX7NNW7EA1ZJJY7HTSZK0G",
+ "1JKTGA14ZTEP3ZMJ2HMW14RNGEEZQVDYMCSCNNFZSVTDA5VZ5ND0",
+ "NYCNHZ09BETJCYBSGM3MG1B0GDW8269SXGQRDFHCQH6RK9WK6FC0"
+ ],
+ "h_age_commitment":"3RK14CY8Y5SV7832ST7D0NTM160J4MXWXMNQH7Y06JS1Q1DC60KG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"JXS46EQZ6EHS4METGEXV22SBKVK8YK9SW5DM3YCMKYF8NNNRFFNPBEN89YKDPJ6PJGQKBDHD6PHYW04HMGR8CPW39BY71K152R9GT00"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"8PTRQM6DY9H2BZ9X15VD873AKKM3SV0YQEFS22J23WBXWV3RSMP524R5G0VZ114XGSGYJ66Q1X0PPE54ACYJ4GZG6WJY6ZMAMP48T0G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"79CTBS0WJP1MDAN3MMBAE30050T175Q7N93QZY5ZRG1HBJ3SS953TMC3RRWGYP3B6M988YWV6NZVD9TV8XG16257WSD6SS7GB8C0M3R"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"2584W5EC5A7N3N672NPS72551708YBFAQEDDPYM5EWRGDZCKHDJG18VS4Y4X8N7Q87RB5R3SGJAHXB2V9WE7DM6XMHB2E50C0XQ2G18"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"7NKWV8MJYERPV4CVFJTQMK3PDQB3HH3X3J0VWQN1ERMR6KWZPBZF2ZR2QR821D1E75N0YXF1XMW0RZWT9C6TRZGDQAZ29XEV0P86M3G"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"C3MP8KC046TTX8V096G57PZMCM6GCVD16VS2J2N8GQ28TGHN3CCS2EXQ0ARPME1XCT3RGHZNTC1SHNR0G048J8JSP8D3T1X0JEKQT08"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"T6M4ENE8DSEBQ0CYXPNWVZPV8BVZC48D9QH71RSV9ZPMDCVE7YG7PM5FTQGH4RVMDW4R0K103T4Y0R62TGJZNFTV66MEHB2CC57RE20"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"9CZTXABTB813BCWQKN4ACSET441PF5D2X2R9T3V3TY010B5DV84RT8PXR60G35PJ4STZM5JP45KQ29PQH47JG195NX76GEB6E8P0200"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"Z21YBNHCD2RPZTEX489PQ7TFTDR5H9EY7X6R40G0Q0ZS6V899YT4JHDE3Y5CWV8XFMKQM2XPRJZGN6KNTP2R1TYKSGR7P12H1P81R0R"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"R50Z6D5AH6FGQRD8V14YNF62KKZEW03GB7ATAMMVJPEF90KPNTQVCEGTNT54Q78MW6RJDWYJ80NVHWGADNSEHWP0NYATT1J6NR35600"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"9J4V0M7DP5TYBMF0JETYEZP8ZH2WZQVCPR3N2FPN1E8KTMV993YS8KQKBHR6MPVKCC17VSSS9JYQWV6A8AY5AY3NWSD5ZT351703P00"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"BKJ20VJ4W6P6SDGR5TN25WCJGCYRJS4HETDTXP4V707R1A8BHQPND33KHSKWEN31YKD3X5HKT5WWAE2AW00439K9F3A4FA50J92WT00"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"EPEHC69CXNGFW6QHBVTHMT2XFMK8SS6BTD5VC8C4YEBPB85YRM53FSMG20BR68GVHA4RHGNPZX2DTSZKHE93HV4MWMQXGQEQ46H043R"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"G4BC8QWW8H135R5WCMRTBB5ZNJ0PBSNABMQE8Y5M6N81YCCE65M5CZNYXZGV7TQDWB21V7HTJSZ9AEMZKCDP1ZBWXGYA0VN6NT0A0Q0",
+ "proof":{
+ "num":6,
+ "edx25519_privs":[
+ "KPXHYY9D3923BB3K5Z2ZNCENKXDC9V5F0Z3KN39HNTZ5K4N9G02H3HK75384SK4QNG8KMNY88F1KN144JGGW6H92WX2YBVNAJAJ0S5R",
+ "0JBZTBS57QJNYHB6Y50K9TV0BWFZ0T4K00R5H724GRSE7N0CTW6FPTCD6Y9S3W0KDM5Y8YDZSF475JFZNM24C5RMBGE1VRST279PSC0",
+ "JVMG8NJZ7CPTBV92WVP17PDGPHDFMWF5GQFK3BM3PZY9HAQZG8014NGN6ZZS5KHHG3WREV4T8KDDB0THEYP5R4FGFW73TG4SKAHVA6R",
+ "VRM9MVRQKBCV7N546AA8CW95YAXBHH4MYG6E1DXM0QMRCXD9143NB6D4S6RZN1JCKMAGAZ0Z7BZ44EEKVRHSAYDQJCHFFC0XHGZBWJR",
+ "5TN6FF6ACSN66G564NQFW3P1CR1PS94D05MW5JXWVMDCHV5K8054BM7SJEN6YFCSRCMBVA1ZXE0FEK2CG06N4B4APAAEH6NM0S3GHH0",
+ "0M8NC1MX08TJT8W7DSBNFNM9V95FN7CJC087G4X7E7P3AH30RM3ZGJ00BNDAC32AKNWJBKN7BGNKF7WP3VQAT53NWKZJDJZ2E26ATM0"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "N7Y1QYAMSEKEQ4EMF6SD0VGZHQERKGZSQ5N8Y6JD4ZPKVJ1QYMQG",
+ "R1EA6V30HKNR2Z0YVRQXPN6DZ5SXF0R2KSRZ02Z58D0TB282NWJG",
+ "0AZPK2MZDYPC16DCGPBTJQZ0HRMTYAEFS82KSSZX4YE3Y6F8RJ9G",
+ "1G9QGRED2HNDTM74A6E2QR4SCG87DEJTRBFZ80A0NCXEC28RXAA0",
+ "WQA7BSDF6HJ14V6QTMQ3GXPPC6YW23RYPJ5ZFRQ0SBNN3CBVHCFG",
+ "W7XQSPF9C0PQZTV15XC54Z68ZBT8TB3MKJE900VGPYXXKQPVM030",
+ "QZG0YM682MSE44VH3JX5AK4GZTR071C40VT6K99S9D6PPZGKWJVG"
+ ],
+ "h_age_commitment":"WNFYQQ2D8MN1HBGKJX63YQ4DVQXMGRWQZCHXK0X3H3TACYDY90BG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"V2RXW3MYZNGWTSX555AXF9B34FQSKPWXQE5Y8DYTF33C99PRYBQQFBPYFA9FWC9QKSYTMY273QCKJ4CT4Q2GWGFR3QQBSTM1X4S9T10"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"9DJPQC1CDFW7YS7CPQFAX053C1DZ92JFMKR3VZR7ENN75DBQE3FC2METY3M9WEQQ050506JZ3AQSDNGG4YV28A0QJBHAN99XY34DJ3R"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"98X25TF1VDMPZ4QKJT5GXNGC5PSTEQWKNXPJX0YXENN7CVZZNAJW6V9NE8C0XJ4J1E0J08CWB8XPJ6H7Z80P9DDY5E4S3NFPMR3RG0G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"DE9XK0F6KQBNFTT9MHPGY2WJ0FYHXXQDE517X216F3PR3486ZAHAPM7TMCGVXXQQ8YRS3PT8PWB3WM29STYBBZ6G3T9Z8FWNRFN6R00"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"VGQP6882WCJZ5CTHJZ5SBAHV9Y2EM49RCT3GVZYDCHYH07ACHTQDXE08Z8XK4PJJ4CFGG9E2P6CA3H93A3G9EKGZYFQXFN88QK79A0G"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"NRPWKD2QFBP1WEWT54932ZE41Q5F8G7P4CS62SQAWQZ3TA8JDYQ4GSQ5A1J3X2FQSC3B7SZ3Y7ZPWRV53YKWHWRY6NVSR031N9SXM3G"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"KTWT2DKEXP7C5BFD69YNSJ2XBWN5G0Y80XEMYXNRWE09RKZXGTBEJWB0J0818ZQGK1BP4BXHBD9ZWFDGWR7FFMTR7EYMZC92TWH7J1R"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"J56HFW84SQ6GE648N230HD4TYP6R59NYYQ7WKW5B0AMSN3F8C2PHBGQSZK6YVM52H4T039W92MNGCK90Y4CZG8Q4ZMTBXY7BRE51628"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"59AK4VFTGW3Z8RSTWCFBEREYXNFD9J60CJMHG2PRR7QJG08Z1YFSVQZ6RHZRXSB3FD0FV7DR577TSXGH4CT76DXCFSCWNQQTNXEJT18"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"RKDWQWD3BEHCS4E587M5RH2J0N415Z378ARVDGP08E37PPACP31P8EXMZ18DGM5F40MFH4WBTTN8S8ZWR5G49Y1JHKQWWAHZXH6X61R"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"8N7WBS85S7HGEW92ZR22WPD478RF133FHB1XX1CZP36C2XWXF9EVMSE1PANJB382QVV7Z53JQY2YVDP8MRJDR07G7SDDSVGXBJQKA20"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"NE21MAKYPNVP3JDXZFZB57BFP2HV8QYEH80Q666SZ8K9BRXJF712S909QFJ3E52Z6T1CNVXZZEJ08ZFY34NKG1QFNTYXE00XT0ZQT3G"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"QVAMKAA1Q93C3SXNTP1751YAY1K480BVS4Q9RJ0QQCE9GFCK3ZQ80TZNJG1XE74XVDS9M36NJ8TV7DZKAQKQM3E3TNBAKPBP2GB3J00"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":20,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"V224D79TEB8S96HSEQE5D14BF6NCS3YWCBTRN65JRK5N3TDB7SNGMY2CQM1SBM7VPE0YKPC448HY2B5YRJB7NJJY3V2X3HRJ7RBKSRR",
+ "proof":{
+ "num":6,
+ "edx25519_privs":[
+ "T0433VFG624EPQH760W413MAPDAAVYEDCADSYC5TCT5QZW20C1901F2DXG4V4WT9Q7JC68GRM1NEKP42DE94FPWKSCA5V5F6733C230",
+ "J03G8P6DGAKEGKXM4Z1MCNSGMMWXS5HP95AJN0D6A2WZ2BAMSH0ZEC074XNJQRXFX5PJ3H4HXQQ392P90E0G75BK6YFR219G8KW14X8",
+ "V21CC5D214KREN4K0HTGBGKSKK65YGVCQAY0RSYQZPVMYFH4EDKJF5TT1G0QP0Z6Q0X6HYQ450A7G4E8CXYV5FNPY63NG8KTDYVBNRG",
+ "D1B1ET040QYQH77QPAXXRENV27H2R77JDTR90C0S0P8NC3ZPYNJ1PCWJ368XJZ730D0Z8J61RY2YFJVAYV14ECN9R7JRMHZE75Z8NQ0",
+ "P3C90JF84DCQR8D15QMWQJXWD0HPJMJM4EB8SQ3PN1YGY5ZG1SF2GKVSPJF0MEB46MFCJZAQ690RX3SHB477PECY46VGWVEHBRTX6HR",
+ "022936MHW927QM8JAWTSF0KK292P0DTNP8GVPXXKCTHJDV89D1SDG0Z8RW2PDRH1XKV3NJM9ZE2EDCPJ7DMYGA35PRGQSBDQD41G7TR"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "RF4P4WMXB85H2N1BH0ZNCM3R2BSKNZTQHMGTWDWV6MN9SSQWG4P0",
+ "CHCVYKX3V9CT2N3THWY13M0DZCKZRQN0D342FJ5CKE8YFN6GEDBG",
+ "SFYG21A8PVG0HQSXBCB0PJK0SRP1SW7E1E5Z8S9DM8K8EGMD0E3G",
+ "GFFZJE4Z4AB8A9YF4CB9BCKCA3H3YZCJF7ZSNMCTK9N8TJQQX0HG",
+ "0PXVZYMSBVAYD2R08AMN87KF9QJ3JSB9RAVM1TJ5JQ3DYQCAPJS0",
+ "6SWZZQ0C844430KKTEBAHPM6N9HEGDPTRXMQWDQSDVG60FBPRKPG",
+ "03FES7WC1YVMG6J4YB5G6R2JVBY41F4DWV4T5ZB9ZBYWTW5D09DG"
+ ],
+ "h_age_commitment":"HC78197HG8GHRXMPVD2SB7DNWVC7CWY77NP69CSX0WQ23CJRKP50"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"Y7FG6XFK9B7H04XB1NGJA1CWANB4DATRPB9J1J4M4JFBKQNH89YZH7VVE87GES4Q0C6G326Z0VRNMDRSAQTXMN90GN1CEKHKZFPKM08"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"QZMK2ARJYSGX3DYJ5XAVJ1TMGERQKT2DHQBQ7CN7826SSRV44M8XPBEQ9655FDDKE8WNRHVKYP4CD26XD3TXMWQSCP3CTE7AMMQWA00"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"1C668JPJ4PCBQ0VP3PRTJS00DE43J3MZAC1NNTZPV3VDQGZKFN9D3ZPFKCJY3GAY5KFCAYPT5XCWTSQC6ZC1R7DMJS5MKTD30EHD81R"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"11FKMF64YK5DD041KY9ZZ7DVW6EWZJJ59PFEEZ5B62PTK5664VD54AJ2GC3PCEHSERVVPQFXQFMVBQ3B8RRGFNHNAVA8EW7MCSQN82G"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"TNQD495T4EPRVX4FSQVH3RGCEBB906GZY0491GVY7K0XS2MCH5CCC3PA1YRRVSYEPG9XXJ35PH58T0R95R2PQ5SFNAMYG7Q89YMHR0R"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"V7GABXTSM6JE7EHMMBXTM3XY2TFJB0ZWJMV3G5XGSKAHXEEF60XMCXXB41KSWSY4Q8RWTA1QYYZTPVZCN70XP2RSNFV4VCSEKX1YJ00"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"366C8KQ59H38GP12MJTZPA3F43HXCSGBDX5YJQXGEPGZTR9S71KV8DB8E146BJWEV6DCZ1MB0KM64RX1GRZDPTQY0NRBRA81SESMP00"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"589C55MDPE1ZVN2M47DBKWYGJFQ8WV383N9GN9VN73Z5D2P0S8D0X58AMRD933DCVJEPYBJNQFA2VNGVX20WYCNWH5EYXRNW3596208"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"GT64FP7NG7V44SCDH7CT7E3V4BYZYC1HJSM0RC9PQN3FEK7XXDJQ1MPPXTDR9HV9EGXSKVA25JZYCXM7HVCFRFW5MHQ6V4GEXTHWE0G"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"ZD5RHCJZ60K9RPYASK5TC36QCYE75P3TNGN5AAK9WME3GNPNS5EHNW1Z0WEE2G9GAXST1W5BFT20NN74P8DJWPHFRVGMPVY53BCNT00"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"8AWERHA54T67X4AW28FXRKSTSWNSHGVTREBGAR043HTEDGMF6MAGM6HJ5GZYMYH8BTJ60TJEQHWBHKX50SQJ62CDAY019BV6VCBEC1G"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"8GYJMNGPPK8G2H4BR294PX3WGE3W9CZH69J93BH6BMN5EJDBHAGW08R223XGF4EXATH85WY2C6QE3VBDBHE8RFBBW1BCN4S8FMZK03R"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"TF7G69AEKRFFMSKQT5Z1BKASQHGPAD7G8DRX9C1FYEEW64E45633QGCDFWGCKYQ9105XKZN4V6G91CS08RZV82GP3Q6F483TTTDCJ20"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"V224D79TEB8S96HSEQE5D14BF6NCS3YWCBTRN65JRK5N3TDB7SNGMY2CQM1SBM7VPE0YKPC448HY2B5YRJB7NJJY3V2X3HRJ7RBKSRR",
+ "proof":{
+ "num":6,
+ "edx25519_privs":[
+ "PFV6XF8ZABRPJ0YNR99P0P5BARPDK5V9TSG9YCJSDBJSDV7YG8308SF6J7JK84KBBQD6J76ZQVPQ938KVKTPG02X8P2AQRKK8F5PN7G",
+ "P33YMR1GHKFYFQDDVMD71XJE78M322AMZTRSPANSRJEEP0AKAC3HXMFWVDMZTDNS3Y0G9R59NPTN77FY8CH2J991G54070B51S9R16G",
+ "F41R3MYFRB6XKMY1ZVCHFV6JK6JCVE510Q2H7645ST2VFGBTKG639TX9FE767XV1CF56A8J3AF79BW84CRYEYKGSN0BMA73KPHPEACG",
+ "KP7ZTR2A486BC95H8YFW70T29R8GE08V55EZE0AADHDSRDKASW32ZDE4PAZ156S6BMTH0X5S70KHMFWP0WFTKDVMGY0H7F0DK96N6T0",
+ "DSVSZGPNFRP90W2D6204T28F44DY0ES8N8FE2CNPFR49BAV5G821NEAWWNRJDP2V7Q7QF3C8PVWZA8PAG0DP8DHBJ0N53WFCPD43SA0",
+ "4CPZNJZA5B1G6JTC57C1EFK77FRQ0PPQH01Z5NK3M3Z0Z8MJER54T0EFQC79YDZ0NBDZW9YZAZG85WFCQSKCX7DXCX3HAZRKK119CF8"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "RWF6QMYDKRVRWTMXM5BBJZZ3CTTN8PEH957FJ5CHM5K9CHC8CSWG",
+ "3RGDV1XQJTKQCVXCACPGWMV0RQ44RBEY8NAEXS96Z2GMXTE7ZTE0",
+ "XF5B2CYZ2PG5EKH3SH95Y813JE9KPVJZ5SH8S7T7W15QPGEXAC80",
+ "20RP7PJPK9MKM4BWG66QQBC4RH31WMYYVPYWS79KYAS41SG1ESF0",
+ "M02VZCB7CB2DJAESMMRTE35C12DX7ZS7HX71WFRVHBZ0MTD1X080",
+ "R2ZRXHVG21CDR845B4MTFDZX3FA3KG4A5W97WK2Y7R7FRRGFBV6G",
+ "N3EW91XK4A3AT17QVAM84BFEV2CTNQ3MH097911ZT9F4Z5YDHH20"
+ ],
+ "h_age_commitment":"654SMA6G4JWE0CEH1X2DQS7BE9JRGQ9GXX5PJZXM507S72N4V7P0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"0JF0031D331NK6WBBHB3H5Y6347GB8EV72VM14FD1FY854EBQ8Q5JF35ETVBFPATAM08TCGFM3ZJ5EP4871PSHMM6DHVVQ5MB8GC03G"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"CJQEM1XSVDXHM5R6MRSBV2VEMX92HPD0NRK6C8WGQ41733ESZRS2C3H02RCJHXC5A6NK5W40H2RM00FBRYGGCGN4J2M7WVEKMXR6A1G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"83QA28NY9M9K2JH4BPGN48D25Z90A5REA7DKECW0FBN9HBXPVAQZXATKQDVJP472KPX6ZV1DCF39CXTYC47KECH8T1GGZTEVSJXA01G"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"0GS3J4SETR0MR0WD3C59CYYC1PZZT2CVMCGKS56BRPW63HCY4CQJ0M2G0QADTDDNW6TEHM1FG6QJDJ4QB7TM1J1TNJKHCQ4Q8PBZE08"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"Y33FE95QH7EJ4M6D4H4VNQXDEAW59MWDE9H7PS09S81JNEF4XMZAJR8MCJ5S9HHMRM9ZQ1SCPEYJQ9K1X3V8WB3VS46RBMPVZJYQM2R"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"W0EHPB5A7K23099D2E426S8EYMCCWQXGCVXDWC9K3RFTSQVFAYWCHZ5SBYRJ52SEE33EBNSF1GV0XHK79BR073HQ8W8QDNAZ2QK8A2R"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"WYHY73NCHN1CQ9JX4C1DDP6589H66N9HRVWCQ4XGV8TQ76B8NN9CC6WW7QKQWX7KWSH9XXY7BQ9M62NCHGG1S159DJ8Y31M8PNGBC18"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"B8N809D2V8AV2TE1DHS7D7593TGS4B67PV2TXKECHRZSGDW1X6DYGWM88ZWC1791SK6YZD2Q9ZDJ2X6FD9REAEBWC7BAYF0MCETFW2G"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"5MN9X1Q8GB19Q07EK9GMMWK6AEK1XTZ2ZW8T2VP0DEK0E0Q5GSY0KRQV0D0SNDP4YKSKA5SQ63Q76X1Q1GE6JZMCR2YED5BACKSAP1R"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"9VZ5R4511P8G64FX8C27CJAPCE4R8W12VFDBJEGMFTFZM5FYZA35JB1B6ENZ3DS3KVPSNC43JSR103DE51FS39BEABWGJ8EDBPE5P38"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"HQKHSDS1FD4Y9BXT6A0FNREHZTYYHAZ9586YQSVFMH80PESE7FJGM1HG750Q3H6XN7RVWXY66RH2GAENYZV5AZ455VVJEPCKJ8JG400"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"Q4QS9ZXADHNF1HYTF4N5529DTJ2MQV7NNBRE26487BG7R20RFVTMAGQ73YZAB3YGRM38VR1K373X1DK9KVCGVMKT3RAAQ8RMMCK6T3R"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"DPB7EX8D2VT74Z8AWTFDJHT5W3N7XQQHZP7KJEN3QM9S3G4XVVP2QSZJXWYJEFFP1A0NFGB12A4S292WVBGZJWAPQ7YHXN7D1AYW818"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"V224D79TEB8S96HSEQE5D14BF6NCS3YWCBTRN65JRK5N3TDB7SNGMY2CQM1SBM7VPE0YKPC448HY2B5YRJB7NJJY3V2X3HRJ7RBKSRR",
+ "proof":{
+ "num":6,
+ "edx25519_privs":[
+ "7V3WJZ7854SY84P6QRGTXCA8ZK2XEJ9DQE8EVCCH9KFKJHRP546VMZFV8P1Z7H54H23WRK14ZPWJ0KZVV07KC7272GR6WJ9NDS8ZXH8",
+ "Y7MJ6QNKJY02N67EDCVRG9ARW9RETS9R5T42T4STE2V4TKQ6Q829WZ7MFCRXDVYYGYTEMZJ1WH4AHG50W2R6D872N4H9BTG89YXTYVR",
+ "J0YAVF4DDVDMHP6DFFWKTTCTED1F950P0NGNJN8BGQ5MNCZ6Q423A4HDA8E93RFW8XG25CABBC15R49BCEP4TYS09W4X83CZZ5RNQGG",
+ "26JKZRMF2ZY6NXFGBMM9M8RSNN3QZ51VZYAKB1XH4872SP2DXW7J9Z34S4HFQDD5PQTZVV4X59D9RBKEXJXH1BX3PBDT9FSD5DHMXFG",
+ "FWPBNTYBWTXERBTQ2WETE3S3MV0WSYY40AXEXY5C01BDGY9B4W1ZDAZHSJX8JG0YWFM3EWJEWVWV3BHTKVJG688TN8HS5R0AM7ZFG08",
+ "4T5SJCX9H4MF1FC703F29BSBVS7QS2XK4FN7E54PF0H2MPRNM057D2V4DM5TMBJQQPAJT70PT41YS90TE230S5NP70WXGH40VXPVRR8"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "1WPHZARQ449QPHBW81ZKSB078ZZG4YVV5HH1VW27PE1GKPQBPBPG",
+ "W4VF9EX174XNS0RS5JBZ485DS715PBN6K2D4HRM221JY31V0TRX0",
+ "GZXWZQBWHQ1DTJNYFJ810QHEC3GKVX2EMA7P9MJSV94ZP58DW0GG",
+ "BPVAG873KKQ3JFZZKDX0N2EWPD480RW5WEJC4W5TGZHS2E4DT4AG",
+ "5YKS0RJASJS5AGCQN24RYHSBQ3519B2VPTP7CXH0GD76KESX3WH0",
+ "9ZGP8WQMPF3RNTSV65HKH0RHWM6ZAM4CM7PR108GN2HKFNKR6BYG",
+ "F0ERN7PESSQADM5V0T4X3KQYVG2A5MJGGVYHXTKWZD777NHFCWT0"
+ ],
+ "h_age_commitment":"C085K5AJEV9V3JAK3VYY6AQQVTGPWJTBWK111JPQVC14B2113EA0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"K6KCP5WBM7DFERG4MFSQ6A8EJ3YNQ3R3AC5M2SGSNPQ6NQJG2YVQDXQQJJAJW5RNAT7MFMNV13Q25SK0FRXQV5SE3GHQ3PZ6Z8ZRA28"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"BMZ1WSPTGR2P7JB47QDTDRQXV0M9SJ1WMPKTF7C91NP4RXE2ZZVKDPB87R8QVKWFB4V97X53294Z3BRRE6DY3Y6GF6C2MX9Q123B210"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"CJKP66QPS9QG1VX597JHCXP97KSRX95AC67F0V83T3N4DRYW9WV83W4Q98TG4ZQVY0MTRHFPS2ECEMR3P708F7GKR77HFYDWJKJV030"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"SG8V8S81MW1MQ8HQ2SFBWKGFAQGQKFN4H3ZR8YDKMV25PETWBKT6H6QXSAX8JVF21MN63XZNM4DCBCS7J9P10QB245M5WWJRW063A08"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"4NJFVGP5QMR39RPW5S32E4HGS8S9HJ5NT78NQ6ZPHHCH7G5DXDSH2Y4039QQ1HS0ZMPT94VM4E31WT68WZHB38A4DVW82385RX2ZA0G"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"73WC7G8KZNHTSAMC5CCRBZ4PV6S9DS8CFT11GQ5M5RFPVXX2J18PSQT25Y07M07YFRPM9T05SVM6B3H87MTCVCK1N0T4AAJDP1D240G"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"9EMKVHYCCPV23KM3HCA1S0S69N9X6FS78779YA0DWFDPEHB2P7FBA153ZP1K14J0BPFWKXXSX8YEA4A0W6BMC6967G04TXF6S7S4J38"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"8RS9B3Q9RYFBNTRTEXQ1N814SK1BH3QHDYWWMSCH8SG66FBVDVD4KAMGB4FCYE93DT4ATJSH59J2DRCM114E2WZHE5MVJ4P0FPY0W2G"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"G2JG30E2WADZKFJGZE4PHBNXTC09PXGEND5X9NXGYR4R1HE4XZPAH16PQXPCNHBSBZQZ4RBJTC2SKF1P6M35XYQZZ0FH65REPB9AJ30"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"H2742HTNWVBRJ36X3GKZJ9ZPYXT5CC9RXQ1VA1D8QG5GFJA63S63QVV6PVWJXYVH1PAVSQFV0WFJ5XDJZBHBB4AAE47BWHKGR7A022G"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"VEGWQ4CDZJSKJGBVNM3KE0P368F8SE9VWTV7A9SF5RJKEAZZVBZBENEPKG6C4PY03W837RC3GE1RAZNDBN4FXY323HW7HGVHJ09QY28"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"CRKZ8CT8ST7Z75EZPEXN4YKX4VFGKFTR8JK66AKT2VQZ611B6NT9ZSPM0V9WK0CCGNT38HM8T15C0CK9KNMN5840A5B78DNFQ2KG81R"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"S3TKC9431PQWSTB1DK5S73W5VWDGB4CDMNND69Q4N7W88TWA5ZCGYR058MA838MH4PVXWE8T4HQM4SVG8NJTSJKM84S31TV0PZ8SR0R"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"not applicable: commited age too small"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "commited_age":22,
+ "commitment_proof_attestation_seq":[
+ {
+ "generated_by":"commit()",
+ "seed":"TFMYF6Q9WSDMHGXEE0P6720THKZKQFJZDT2VW3Q3M1QRG1DE1ZT7DABMPPRKBNJ9RSGK99WBCX7TJF8WJ28X37S2XDFE2RGW0G06P8G",
+ "proof":{
+ "num":7,
+ "edx25519_privs":[
+ "D3J5T6KYY9FPP3T8ABW6ACKJVVPSCPSYQ5HQX0SBZ7P4AECM91M8YSXFAXN6304AR619DWNB08YJNYZYWZRWKDWXSB60N276VF44VCG",
+ "T09QZB95TX0S5DKSTXCMHA7NFDYYHA5P8Q07775GF5QB6GZ7WN55SY2FBWSASESH0GX9SK6NFWET27KSCJET1EKX66NMD0QW3HBEC4G",
+ "N22DV3PSEZ7HZ91MEGHWN0V82A1A6ZQ76K9EENY4V7M5R0QT7SWBY5JRJSJ1SPVW6W96MJB39G3J1FCR8MJ5TEVRKYYP9KKWT7VKJ00",
+ "536Q6SS1D8GET98R17DW1J88YQ9S0FT6PSMWQ6VWBKM7Y5SSED2E3JGRMT706SD10XD93S5W1Q0ZY0C8RBE78QEV3YCGEJ7KE1DGPDG",
+ "R2JSJ94EVX0DCAA4GNJVPADTS19RWRC1SD3Z93XJT93WPWH6HS1YWQ6D895DD786AKWC4H1AMDG4V544EZ0D8N6NRZ0TJBZQF47NZDG",
+ "A0EHP1ZF4TGMTJJN2XWXR6MSSR593SCMMX21W8GSBAKN3WQPNNVZ5EP9YYMCT3X0FKW5TB0NBPCNF2D0JSERDTVFNJ3CCXFH384DPZ8",
+ "Y1P3RPX4WH064HBQFSRGGCJ4P4PPN3T37P8SWTS1X4BZXEQ3Z11A7M81MBYQHYCD5FTVKK31BT0C2C7TKB8G9K8Q0JY73TH79W70690"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "M5XEE751FGJJ8MGR28YXZ2NZ2FB3QPP49CHA51MAT5XDBTXS20J0",
+ "868HPPPK4AM66S6W8XADWF3PEGB0PMSJBYJVXEXVJB9JW158B430",
+ "ZBC6F8KRFZNTSW2Y6HSH7NWFCYDZFKFQW02E5BNH49V7T4WQT980",
+ "ZJWDC7STZZT411RWD5ACN970511J2HHPCXHMJ7QESDXNWQ4MJGA0",
+ "KGCKFXY5AP8C3F2G2YVAZJN56B5H3DSFAX4R8VK9K3QSRV9JH3TG",
+ "VDB9X20KPGA35GX5MPEWQR4JH6BX24BAFH3RSJ39EGF8F5W43MT0",
+ "280VG77EK80XDN5N3WBJA46GEE6GH0JN7DQFX69MGQF9E1426GG0"
+ ],
+ "h_age_commitment":"8MHAR659DX9SBVKMXC896KDT57WAJPT0C63A8R82TW9DRJFCK0TG"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"E12SP7J3MM5N9KW7BA77KPPFBW5Q6JKPSBDMT8YS5PDH48TVP5Q5S0D8V6RMFQBZ2MT5TA5GPCK34M4S9T0RVHH43FMWKWADG57Q028"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"GQQJ02JGXQA2CF8FDWZAMP6MN98RQAANTZHCC0KYSDW1VKREKJKRQRHJ4J1M8D0Q0HHDCS16B3PAVQYQ9P1425VHEW34EK3NQ98RP0G"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"KTBBSK80END930HWADGCM5ZCZHAANWS923YQ99AP3EX26ESVJ0C1TMJGY1J9R88TV28X37VZS6EKZE1CVEAC5W6PWGRCWRY9HJQ8618"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"TP9FR6JAZ6H903M0VC3VE9K9TXDT0Y9FS88ZWKCJEYG2FA5TGXY64ZC3W9NXAJDTP9B0WCPJBP70YWWRQVWM2ZV0R3XTHHNSZ13QM08"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"E7GZP9WNZYQT8Y5M7R75HFHSBA4EGP5JPPDYHDX1V3J5HMM0B56624E24ZGYEEHA20H0K4CG54XVKQ66BTXF996ZB6WN9Q4CECDY828"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"7RJGGR63VP135SCDWWF0MKDWYN1PNPN07MG4AT3Y1AWBBWVRE9FBT7DVJVKWCTHHDQQ342MEGRHH9ZFFJHE5NWVK1F9VVRYWA30AA28"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"YGGTC4QHVBNV135QEC1B3ZY0T8985T00NNTYBWPD7654M0195AXK4MA2SP3FKFSWG20NZ32G92BCHEQABANSVWF8CXYKV341EJZZJ2G"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"DXSQEBY1GZ6MFCFPYT8E0ANCWH0ZBBNJVYYNAXHPCPA9AA4ZT0KKV51KGV5KNRP5SBPSPMBFW2Z50MGRGRPWN78CW9NQFN3B23VYR08"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"MQN1B5GD7SVP1BWZQJD72KVZPNV3BM8MM15886421HDRS449TTN5VEWHF3QGS6ZNG8S4B3TE1S9YR3JB1QYP3AZVNZ4NNBA8DC4D22G"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"TS2Y86R0C6TD4ZB51F0T3DQJTY3W2FSZESZQJM6MH30WZGWSB4XMSQQZNKEDTKS0G2EVYB5P8Z3W6CG00SBA0V9B1VR2Y4J8S797J08"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"31X3J17Y4X5EVPTXKAPZ2JN0HRTMKFVG4HV0DTCGQ91B9C271Z48A3GBN2DSSSC9PHT6PGAXSWDA595MN5SXTV22F4YZQX5T8238P3G"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"NB187D7N8H8DJ312DV9ZHKX0E71BW8V9RQPGBFSQXNFFK6SNAKQT0F2QV2MB3NBQXJFW5HJ8V0ZAFZ7WBZQ18AJ62AQC3BX24SQHC20"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"E1RGD5H2BSZW63P4XTZ958AVB9055SE1RTQJ6FET4KYXS46ZGMVPNR0BT50PFHQE3D70KC4HF3ZTBEJWX9TNFK3DBD1TB639MFWZM00"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"R6PFM3BQPQRQ5PW2D00ACDBNJ4YSASH6X7D964CXH5VGKW0HHY0WZ9E19D1VF21KR99N6GQZ1XYBTJKJT3WXGKH8KA5HHQSRGF5PT0G"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(1)",
+ "seed":"TFMYF6Q9WSDMHGXEE0P6720THKZKQFJZDT2VW3Q3M1QRG1DE1ZT7DABMPPRKBNJ9RSGK99WBCX7TJF8WJ28X37S2XDFE2RGW0G06P8G",
+ "proof":{
+ "num":7,
+ "edx25519_privs":[
+ "48KWV9WN59E1E0VM8YY3JHVPEH4J2Y70RS9YB2C8J7GAJWJ274562RCJ6Z0K31WQY4QJHRZ3VATV29B497PYAK3CXJ6B3KGNC623A8R",
+ "7WQ379V40AYSH6R28PK9VZBYM7AA5J98R3VRNRW53T7K8RG7ZM7GG7N1Q752JEQS1789R3VQ4W83MXM0A5ZS2N32QBVK93A2MKZ7ZQG",
+ "0FWF6RGN2YCERPSG6GRRA5NKVATV3S93YRSEW68RD7VFAC50WR2JW2JEZM9SKMYAZV5RW6EWWN7GTHQG03VNADZB4C5K3MFYNH1WSNR",
+ "V6B4B8WR33NNHTK41V5EZ007S2B2ZMS5XBP83Y0F69341A7F8442NJZWXFWKWM7EENYF0S3ZQW2N25CJDXWFHXDKYR7BBH5TPTD0110",
+ "5W0NBZ349WCT0NPS350MZ7J4XJTZQDH8JG1WTEQ0ZWRA6KJP38084B2818M1NNZGEJVAVQ5AN1ZN4BPB7NCDSTHYA6BM7RFASQ7KWMG",
+ "227K374E4W1NEQF00T6XMMHCESKAFTH0XK2H2BZKH291AHS25R07PESSM8GK0HFZN13DDQ6PJB4JQZRXMAP0Q0216XJ6MJ0N6PEHKTR",
+ "0ANHE9DNND0CHTQR915384MGS6J880R4055T9H6X9ZA3VRRGTC4SCKMSR0FT53F77232S3YV1MXEFPZTT971BKEDAY3N0VAQBVJ8AYR"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "BN64FXEP3D154S07RD01YHQV88GV445DHE39VXQ8T4XDF6BQ88N0",
+ "JZQQBV60X7KRQA52KW9JWFG4X2GPGEMEN4PEDHRRG8355V0HC0D0",
+ "QYX7ATG18A09PFCPGKV2PCXRWJHNYYJQV73NQYF9NB7GZV953QF0",
+ "3J0CXV9N253F1BTGAWR32G6GHJWPV7N5KZ04K6KVA774FDWHB7QG",
+ "2TF4JK194DXDF0AY5PXN96W969EFM4K8NMMNY3FPP0KPF1HCK2Z0",
+ "0FYTS1HZZ9PY24Y72MZ4JB1P7M8HWJYPEZTT3D1HQ8H4H7GMWD4G",
+ "83TE4YK6JVM3TSBT6B4SGVG2XPN1GVMVW6PMM146T4DA0YD2ZPZG"
+ ],
+ "h_age_commitment":"VH548HRAGQ6EXMDYH6J3PN5N44F39F28W2KB573KMKMVM5E5RJV0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"KPWG0R0X8PAP7CYJS9XYK32TGNMV67FJ5T6DD9004AQ83WKCNYW8AZT7HRRX7D7ZY63K1566ZMASQHERGY6SZHE0B5BMGC7GFRFC81G"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"8XS4FJRH95VFC9H6PRA861CNCEENNBK5JSSRYBAAJ61GS1866ZYFG8J2SJMGYY27ZBA8PV97WME6TM7YEGW5CNFP3JXFWD83BAZJE0R"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"S3F5TC1NASXERW6G1DVJM65G55RV86GFEGJDPD0HJATHQJG3R1M491ZMP264VE9FE6V1D1JHDHVXA1WF1XDTDSET7ES2SXA9X31D200"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"9M4WVEJQM6461Q6K2XYQR6J3CSQK3Y87XXJDWEV6FJHAFHXE9PAFY6BB6PN4R7D6NB4AZ9N78TC2RN2QFN0K7PGGRJ73A2WZ14RQ63R"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"PDSJ1B4YX04W4HWTV269S33ZWV19JK9FSGSKYT6GSBQ0FZ5RKVVYFK5MTC1ZJ1EFWESPZ73XAPHR2MA3MXAJYYG86B3B2SFA2EYVR08"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"M0CVHQX2BV466A03JBHHJ0G675F0GC65P5C58AC32PVWW1Q4JSTKGX0H0B31TAF7R22YM821VQ7XPHTX8JH7JTEZM1C933CQHEA1W30"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"Q6Q4FMWDATN514DZDR6MZFDEVA9FJG58DN6JRHKRVG91V8AJK2V6C2CVAPFKADGJWDXJ9ZK61J0MV05EMXS3S2Y378TS5CEPZDPZA2G"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"776WEDGWA95X8RZZGXPN74P337526KVFSE57JNXQWXPSB4B29J5A57DGS668QHYNXTGBRDPSES4PFK8F8NWMPHMZ9B74JXYY9WBB00G"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"WHV60YQWPX6VY5CKE993KQQGCVPX9DT4X15Z9W6KKK29ZWGJRR9QXDDD7GMNWFPVMF6ZSJGMTEHYWW3HYC2VXNDJTRC63V7D1CEBT20"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"M8KQH3V52ESNTBE66F87B1H6C259PA11ZTASE0MD4CSCEMXHH24KFY6H75BK6C29GMJMRTRJ1DS2GY4WVFFSSS4MEVSM2NGSVVE5A1R"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"360E5TAJWG5X7YQTVMSHFG03RXKYGM89K200NRQ9ZFD800YVK187XASE7FHESNY2N1643Z6Y6ZMZ63QJTN87ZMW6FZTQANSP4RVQJ30"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"E78R1KK0ZN193GDS0HQBX5X35Q50VHMKW0NHK7HQTWEZT5VHEZ5NRRSZAWPMM04W562YF0XHFZEASRMN8HF82CX6JG0W5V63PGMHM28"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"JSN4AY3QMYXZTS293HF9KDB0XVRFBYV9AZXABKGPS53N6GM1YGHE0GZGMBXJQPZ7DQZSJC963CNABYKQ3239DGWRENG892DX3FMPJ00"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"S64EM7EXTBJC8M5EH84AC60NMJX02AE5X9M2C4GB72D8V0RSP4ZYSJA7SX2G2T9FKEBE8KJYNG6B56S6H9BEKASYXSDDDKXPQHK002G"
+ }
+ ]
+ },
+ {
+ "generated_by":"derive_from(2)",
+ "seed":"TFMYF6Q9WSDMHGXEE0P6720THKZKQFJZDT2VW3Q3M1QRG1DE1ZT7DABMPPRKBNJ9RSGK99WBCX7TJF8WJ28X37S2XDFE2RGW0G06P8G",
+ "proof":{
+ "num":7,
+ "edx25519_privs":[
+ "4TC0H9JTH1XQMDW5KW9JNWTKDWDP64BQ2NMNHXVHBAC0AZQTTG28F0AXKPSJ3P8QXHD7FT09DSRJMS1H3A2SVCE3Z2VW17S1P4EKQQ0",
+ "GNVH3040NYWGS5H9N5KHP2RNPGSC0YCW8ADAVGSF47DNY9WY2M1KSY6ZH5D5XXV1REC5ATRBY67FQEF7H95EV728MP2W390XDMWHCG0",
+ "74Y8YKJMJ0R67419VF2WC2TNBG42NYX7H8W0NXKXG1E3ACJFJ81YGQFCGM1TYS35HAF77JFJ7T3TTYB5XFA9HTF07WNAMWA3QVRXXER",
+ "M7BKSS5XCRMX6K66JCRVJD3H6D2RR15MVQT68FBXVW3VZAFK180G43KVSGDNCVRDZ0V8AK4ZK5WEY18F11T5X00K7XA6DZRSJ14K028",
+ "27JY2DXAFHS2NNSWKN1MM538RHFR0RZZZ0JE8K78ZACXHYF4742GGN5R9NRZ991G60MRGN7WB3YBC2JA4C645XQR5DDDH2V08J0JM08",
+ "B2MTEKDCAKX52A72SZZ1VP25TSGCFECC4FBWHDSG9D0HV1EWN46FEDHNF9RT8NJKS77DRBN60ZZ78XHC5RZZ5NKAB4301ZKHSY851EG",
+ "6PGFK1NFN9787FEQ9KYMBJC7V4BK5TBTYQVH1R210DQK0P6M1R30YHG20D3TJGQD3KHGE9Y1ZFN5KEZ0FEKYY5GR3127RTFCNN8R8E0"
+ ]
+ },
+ "commitment":{
+ "num":7,
+ "edx25519_pubs":[
+ "550TDA8Y4WPDGKQTH2M11TRGK4D503ZGT462FERH6Q36JXC6RJSG",
+ "T61NZ80SCKMDY8XKAJHBT5E1HCKV705W8MS8SZ6KBKQ6BWS9KKG0",
+ "0RWAHBP88K9DXKY0T4D32AZVCKHQKQDK7MPHRV5AM34XJ7AAF9RG",
+ "E6R53MJ1XE7MBMJY12EJ0D44GP44KT2FFYK0RM6Y9X8K7YF4QWB0",
+ "4XM0VF2ZB2511PKG9AFTG0QYCVEK1FNJKN0R0FRW8J2BA8SBAK5G",
+ "H3X1A88KSZ143EN7ZYRVF616VWWDMPFCPWR9H8G1HW95EA02NFJ0",
+ "ZFHXD4AYWA1HV677VQ65MTZHD4GBE22K7Z2BNP7BXZCT9XX1BHGG"
+ ],
+ "h_age_commitment":"A4MZ4FXSHK8JPFZDNTRH6QWHCQ5G6ZFMX4NDC0XR7FZJMGRX05V0"
+ },
+ "attestations":[
+ {
+ "required_minimum_age":0,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":1,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":2,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":3,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":4,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":5,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":6,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":7,
+ "calculated_age_group":0,
+ "attestation":"not required: age group is 0"
+ },
+ {
+ "required_minimum_age":8,
+ "calculated_age_group":1,
+ "attestation":"NH8WVEV5WTQNN187CV17B5PBH60J1CCKMQ48YP0Q4RPHNB1V1W1G3ASC8NMCK13B2MC21KF7CB217XN650Z6JRZ8X6QBS6S9HREW800"
+ },
+ {
+ "required_minimum_age":9,
+ "calculated_age_group":1,
+ "attestation":"5C46XKMM7996ZT4PE1618MV55569QS35BPD59PGSESFJVBP9QVJVWJFQD5WP6PZFK8K5G6EV2BECT71FYYJZRSCAXKTM3EBQVYC9A1R"
+ },
+ {
+ "required_minimum_age":10,
+ "calculated_age_group":2,
+ "attestation":"2GHNVPSE9BZ6MEAAN36NBR6KAXSVE9WNBHRJEPNJ857TSTR3XH2BRG4WW54JBEVE8X7YPAA4MFXZ52JZ8J8AYM3F8STTZH85BYKQA20"
+ },
+ {
+ "required_minimum_age":11,
+ "calculated_age_group":2,
+ "attestation":"E6AYVWSH9F6NY79F3B7T8E2CG1SCQEWVX540R1W3Y6FP2HF6KV0M247R71S1SK0YT3SKW4HAKBXPDQXYMQBW65H1DF64N86SRCHGT18"
+ },
+ {
+ "required_minimum_age":12,
+ "calculated_age_group":3,
+ "attestation":"CW7MBBG633Y9MQPSX84FS1Y8AQD7F64DG2FRBHC2339G7C2WE0A06HRMHN851194P2M8N81MDWDXJ6Q26A6FBMA58C2CFX561BT7R2R"
+ },
+ {
+ "required_minimum_age":13,
+ "calculated_age_group":3,
+ "attestation":"DZ9D0QSE9Q4CH48WYPEYMV0FAVK27R9NZB4QBQ4T3X0N5MRCXPKYSR9DMW4SS7HWNQKXYR68H5AFA40DNWQC4BAKFZFJAKQDFJBJ80R"
+ },
+ {
+ "required_minimum_age":14,
+ "calculated_age_group":4,
+ "attestation":"98AA3G7GH39SXX00DAZJBJ5ZAGH7W23SG2EYZFYYDTRZF0D840TQVKK663QM17SXWN6ZSJJEZ38WW7CM0A5VDBNZ3BVQKSMJWSBHC3R"
+ },
+ {
+ "required_minimum_age":15,
+ "calculated_age_group":4,
+ "attestation":"BR1HZEBP4F55HR5CY9X5T62Z60PFPEJBR39341250VWQZVEJ58H84P6TTFMS1CS4WM2ZWC67QN742PQ2TK08Z8FR3XFCKVJTDS12Y38"
+ },
+ {
+ "required_minimum_age":16,
+ "calculated_age_group":5,
+ "attestation":"70SW0VA0E9YV34Q7G9RB3ZT6MV99PERMW488HCHXN6J12WHXWMM0F9MC9CAPR339ET9VKGXA8S274TVHZXWC7JMKK5JSSTT3W1FK21G"
+ },
+ {
+ "required_minimum_age":17,
+ "calculated_age_group":5,
+ "attestation":"FHV3JY9AX31D4K6VH6639Q5SGKJW4JEJM1CJKRW4VTWVR5P80AKABDAYTDZEXX9X5X1CZAGCW5C0V84JE2DBAJM50D6MXS39CZ33E30"
+ },
+ {
+ "required_minimum_age":18,
+ "calculated_age_group":6,
+ "attestation":"P1NKNJ2RD63DQZM7MGW2C340GVSPAM2Y0YZ9VHMBSXWX5022SVBGJT4CAA26TER40G8TZ7YE8RCDKPPJS7XWTY3ZYRCCXCS0ZA00218"
+ },
+ {
+ "required_minimum_age":19,
+ "calculated_age_group":6,
+ "attestation":"83Q4APZ0DHZWBSRY9ZZBA5SGW7ZEC9WKTGT3NTH2HV8ZVE8F67HE4WE14DGF0DS8YT40EHPT9H9P3ABMVHQJF6JVWXAPZ7QJE2FNA00"
+ },
+ {
+ "required_minimum_age":20,
+ "calculated_age_group":6,
+ "attestation":"JVRNBR783423S311E6X9WQGNYXBEK2TVDSF5NJQFBDEVFY3N6N9KNN7ZBQNH0SBD8ZSG1CP4QE4BHS5S44WSJP9V38FG9VRHMREQE08"
+ },
+ {
+ "required_minimum_age":21,
+ "calculated_age_group":7,
+ "attestation":"AGP53972S0KR80JESVH0DVAR3WJ3Q2DAHQ2GSEPF17J459CCZE781BBTWN0S7MH10ZSBVMEB50AQAD9C6FSH9W59AG4GK1WQ729K43G"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/src/util/url.c b/src/util/url.c
index d681fd02b..bf59ba6ec 100644
--- a/src/util/url.c
+++ b/src/util/url.c
@@ -27,8 +27,8 @@
* be urlencoded.
*
* @param c character to look at
- * @return #GNUNET_YES if @a c needs to be urlencoded,
- * #GNUNET_NO otherwise
+ * @return true if @a c needs to be urlencoded,
+ * false otherwise
*/
static bool
is_reserved (char c)
@@ -48,11 +48,11 @@ is_reserved (char c)
case 'P': case 'Q': case 'R': case 'S': case 'T':
case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
case '-': case '.': case '_': case '~':
- return GNUNET_NO;
+ return false;
default:
break;
}
- return GNUNET_YES;
+ return true;
}
@@ -68,7 +68,7 @@ urlencode_len (const char *s)
{
size_t len = 0;
for (; *s != '\0'; len++, s++)
- if (GNUNET_YES == is_reserved (*s))
+ if (is_reserved (*s))
len += 2;
return len;
}
@@ -84,8 +84,12 @@ static void
buffer_write_urlencode (struct GNUNET_Buffer *buf,
const char *s)
{
+ size_t ulen;
+
+ ulen = urlencode_len (s);
+ GNUNET_assert (ulen < ulen + 1);
GNUNET_buffer_ensure_remaining (buf,
- urlencode_len (s) + 1);
+ ulen + 1);
for (size_t i = 0; i < strlen (s); i++)
{
if (GNUNET_YES == is_reserved (s[i]))
@@ -98,12 +102,6 @@ buffer_write_urlencode (struct GNUNET_Buffer *buf,
}
-/**
- * URL-encode a string according to rfc3986.
- *
- * @param s string to encode
- * @returns the urlencoded string, the caller must free it with #GNUNET_free()
- */
char *
TALER_urlencode (const char *s)
{
@@ -139,6 +137,8 @@ calculate_argument_length (va_list args)
{
char *key;
char *value;
+ size_t vlen;
+ size_t klen;
key = va_arg (ap,
char *);
@@ -148,7 +148,12 @@ calculate_argument_length (va_list args)
char *);
if (NULL == value)
continue;
- len += urlencode_len (value) + strlen (key) + 2;
+ vlen = urlencode_len (value);
+ klen = strlen (key);
+ GNUNET_assert ( (len <= len + vlen) &&
+ (len <= len + vlen + klen) &&
+ (len < len + vlen + klen + 2) );
+ len += vlen + klen + 2;
}
va_end (ap);
return len;
@@ -201,89 +206,65 @@ serialize_arguments (struct GNUNET_Buffer *buf,
}
-/**
- * Make an absolute URL with query parameters.
- *
- * If a 'value' is given as NULL, both the key and the value are skipped. Note
- * that a NULL value does not terminate the list, only a NULL key signals the
- * end of the list of arguments.
- *
- * @param base_url absolute base URL to use
- * @param path path of the url
- * @param ... NULL-terminated key-value pairs (char *) for query parameters,
- * the value will be url-encoded
- * @returns the URL (must be freed with #GNUNET_free) or
- * NULL if an error occurred.
- */
char *
TALER_url_join (const char *base_url,
const char *path,
...)
{
struct GNUNET_Buffer buf = { 0 };
- va_list args;
- size_t len;
GNUNET_assert (NULL != base_url);
GNUNET_assert (NULL != path);
if (0 == strlen (base_url))
{
/* base URL can't be empty */
- GNUNET_break (0);
- return NULL;
- }
- if ('/' != base_url[strlen (base_url) - 1])
- {
- /* Must be an actual base URL! */
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Base URL `%s' does not end with '/'\n",
- base_url);
+ "Empty base URL specified\n");
return NULL;
}
- if ('/' == path[0])
+ if ('\0' != path[0])
{
- /* The path must be relative. */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Path `%s' is not relative\n",
- path);
- return NULL;
+ if ('/' != base_url[strlen (base_url) - 1])
+ {
+ /* Must be an actual base URL! */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Base URL `%s' does not end with '/', cannot join with `%s'\n",
+ base_url,
+ path);
+ return NULL;
+ }
+ if ('/' == path[0])
+ {
+ /* The path must be relative. */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Path `%s' is not relative\n",
+ path);
+ return NULL;
+ }
}
- va_start (args,
- path);
-
- len = strlen (base_url) + strlen (path) + 1;
- len += calculate_argument_length (args);
-
- GNUNET_buffer_prealloc (&buf,
- len);
- GNUNET_buffer_write_str (&buf,
- base_url);
- GNUNET_buffer_write_str (&buf,
- path);
- serialize_arguments (&buf,
- args);
- va_end (args);
-
+ {
+ va_list args;
+ size_t len;
+
+ va_start (args,
+ path);
+ len = strlen (base_url) + strlen (path) + 1;
+ len += calculate_argument_length (args);
+ GNUNET_buffer_prealloc (&buf,
+ len);
+ GNUNET_buffer_write_str (&buf,
+ base_url);
+ GNUNET_buffer_write_str (&buf,
+ path);
+ serialize_arguments (&buf,
+ args);
+ va_end (args);
+ }
return GNUNET_buffer_reap_str (&buf);
}
-/**
- * Make an absolute URL for the given parameters.
- *
- * If a 'value' is given as NULL, both the key and the value are skipped. Note
- * that a NULL value does not terminate the list, only a NULL key signals the
- * end of the list of arguments.
- *
- * @param proto protocol for the URL (typically https)
- * @param host hostname for the URL
- * @param prefix prefix for the URL
- * @param path path for the URL
- * @param args NULL-terminated key-value pairs (char *) for query parameters,
- * the value will be url-encoded
- * @returns the URL, must be freed with #GNUNET_free
- */
char *
TALER_url_absolute_raw_va (const char *proto,
const char *host,
@@ -296,7 +277,7 @@ TALER_url_absolute_raw_va (const char *proto,
len += strlen (proto) + strlen ("://") + strlen (host);
len += strlen (prefix) + strlen (path);
- len += calculate_argument_length (args);
+ len += calculate_argument_length (args) + 1; /* 0-terminator */
GNUNET_buffer_prealloc (&buf,
len);
@@ -316,21 +297,6 @@ TALER_url_absolute_raw_va (const char *proto,
}
-/**
- * Make an absolute URL for the given parameters.
- *
- * If a 'value' is given as NULL, both the key and the value are skipped. Note
- * that a NULL value does not terminate the list, only a NULL key signals the
- * end of the list of arguments.
- *
- * @param proto protocol for the URL (typically https)
- * @param host hostname for the URL
- * @param prefix prefix for the URL
- * @param path path for the URL
- * @param ... NULL-terminated key-value pairs (char *) for query parameters,
- * the value will be url-encoded
- * @return the URL, must be freed with #GNUNET_free
- */
char *
TALER_url_absolute_raw (const char *proto,
const char *host,
@@ -353,4 +319,36 @@ TALER_url_absolute_raw (const char *proto,
}
+bool
+TALER_url_valid_charset (const char *url)
+{
+ for (unsigned int i = 0; '\0' != url[i]; i++)
+ {
+#define ALLOWED_CHARACTERS \
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:;&?-.,=_~%+#"
+ if (NULL == strchr (ALLOWED_CHARACTERS,
+ (int) url[i]))
+ return false;
+#undef ALLOWED_CHARACTERS
+ }
+ return true;
+}
+
+
+bool
+TALER_is_web_url (const char *url)
+{
+ if ( (0 != strncasecmp (url,
+ "https://",
+ strlen ("https://"))) &&
+ (0 != strncasecmp (url,
+ "http://",
+ strlen ("http://"))) )
+ return false;
+ if (! TALER_url_valid_charset (url) )
+ return false;
+ return true;
+}
+
+
/* end of url.c */
diff --git a/src/util/util.c b/src/util/util.c
index 19924b89a..da5727487 100644
--- a/src/util/util.c
+++ b/src/util/util.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 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
@@ -19,25 +19,20 @@
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
* @author Florian Dold
* @author Benedikt Mueller
+ * @author Christian Grothoff
*/
#include "platform.h"
#include "taler_util.h"
+#include "taler_attributes.h"
+#include <gnunet/gnunet_json_lib.h>
+#include <unistr.h>
-/**
- * Convert a buffer to an 8-character string representative of the
- * contents. This is used for logging binary data when debugging.
- *
- * @param buf buffer to log
- * @param buf_size number of bytes in @a buf
- * @return text representation of buf, valid until next
- * call to this function
- */
const char *
TALER_b2s (const void *buf,
size_t buf_size)
{
- static GNUNET_THREAD_LOCAL char ret[9];
+ static TALER_THREAD_LOCAL char ret[9];
struct GNUNET_HashCode hc;
char *tmp;
@@ -46,13 +41,415 @@ TALER_b2s (const void *buf,
&hc);
tmp = GNUNET_STRINGS_data_to_string_alloc (&hc,
sizeof (hc));
- memcpy (ret,
- tmp,
- 8);
+ GNUNET_memcpy (ret,
+ tmp,
+ 8);
GNUNET_free (tmp);
ret[8] = '\0';
return ret;
}
+void
+TALER_denom_fee_set_hton (struct TALER_DenomFeeSetNBOP *nbo,
+ const struct TALER_DenomFeeSet *fees)
+{
+ TALER_amount_hton (&nbo->withdraw,
+ &fees->withdraw);
+ TALER_amount_hton (&nbo->deposit,
+ &fees->deposit);
+ TALER_amount_hton (&nbo->refresh,
+ &fees->refresh);
+ TALER_amount_hton (&nbo->refund,
+ &fees->refund);
+}
+
+
+void
+TALER_denom_fee_set_ntoh (struct TALER_DenomFeeSet *fees,
+ const struct TALER_DenomFeeSetNBOP *nbo)
+{
+ TALER_amount_ntoh (&fees->withdraw,
+ &nbo->withdraw);
+ TALER_amount_ntoh (&fees->deposit,
+ &nbo->deposit);
+ TALER_amount_ntoh (&fees->refresh,
+ &nbo->refresh);
+ TALER_amount_ntoh (&fees->refund,
+ &nbo->refund);
+}
+
+
+void
+TALER_global_fee_set_hton (struct TALER_GlobalFeeSetNBOP *nbo,
+ const struct TALER_GlobalFeeSet *fees)
+{
+ TALER_amount_hton (&nbo->history,
+ &fees->history);
+ TALER_amount_hton (&nbo->account,
+ &fees->account);
+ TALER_amount_hton (&nbo->purse,
+ &fees->purse);
+}
+
+
+void
+TALER_global_fee_set_ntoh (struct TALER_GlobalFeeSet *fees,
+ const struct TALER_GlobalFeeSetNBOP *nbo)
+{
+ TALER_amount_ntoh (&fees->history,
+ &nbo->history);
+ TALER_amount_ntoh (&fees->account,
+ &nbo->account);
+ TALER_amount_ntoh (&fees->purse,
+ &nbo->purse);
+}
+
+
+void
+TALER_wire_fee_set_hton (struct TALER_WireFeeSetNBOP *nbo,
+ const struct TALER_WireFeeSet *fees)
+{
+ TALER_amount_hton (&nbo->wire,
+ &fees->wire);
+ TALER_amount_hton (&nbo->closing,
+ &fees->closing);
+}
+
+
+void
+TALER_wire_fee_set_ntoh (struct TALER_WireFeeSet *fees,
+ const struct TALER_WireFeeSetNBOP *nbo)
+{
+ TALER_amount_ntoh (&fees->wire,
+ &nbo->wire);
+ TALER_amount_ntoh (&fees->closing,
+ &nbo->closing);
+}
+
+
+int
+TALER_global_fee_set_cmp (const struct TALER_GlobalFeeSet *f1,
+ const struct TALER_GlobalFeeSet *f2)
+{
+ int ret;
+
+ ret = TALER_amount_cmp (&f1->history,
+ &f2->history);
+ if (0 != ret)
+ return ret;
+ ret = TALER_amount_cmp (&f1->account,
+ &f2->account);
+ if (0 != ret)
+ return ret;
+ ret = TALER_amount_cmp (&f1->purse,
+ &f2->purse);
+ if (0 != ret)
+ return ret;
+ return 0;
+}
+
+
+int
+TALER_wire_fee_set_cmp (const struct TALER_WireFeeSet *f1,
+ const struct TALER_WireFeeSet *f2)
+{
+ int ret;
+
+ ret = TALER_amount_cmp (&f1->wire,
+ &f2->wire);
+ if (0 != ret)
+ return ret;
+ ret = TALER_amount_cmp (&f1->closing,
+ &f2->closing);
+ if (0 != ret)
+ return ret;
+ return 0;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_denom_fee_check_currency (
+ const char *currency,
+ const struct TALER_DenomFeeSet *fees)
+{
+ if (GNUNET_YES !=
+ TALER_amount_is_currency (&fees->withdraw,
+ currency))
+ {
+ GNUNET_break (0);
+ return GNUNET_NO;
+ }
+ if (GNUNET_YES !=
+ TALER_amount_is_currency (&fees->deposit,
+ currency))
+ {
+ GNUNET_break (0);
+ return GNUNET_NO;
+ }
+ if (GNUNET_YES !=
+ TALER_amount_is_currency (&fees->refresh,
+ currency))
+ {
+ GNUNET_break (0);
+ return GNUNET_NO;
+ }
+ if (GNUNET_YES !=
+ TALER_amount_is_currency (&fees->refund,
+ currency))
+ {
+ GNUNET_break (0);
+ return GNUNET_NO;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Dump character in the low range into @a buf
+ * following RFC 8785.
+ *
+ * @param[in,out] buf buffer to modify
+ * @param val value to dump
+ */
+static void
+lowdump (struct GNUNET_Buffer *buf,
+ unsigned char val)
+{
+ char scratch[7];
+
+ switch (val)
+ {
+ case 0x8:
+ GNUNET_buffer_write (buf,
+ "\\b",
+ 2);
+ break;
+ case 0x9:
+ GNUNET_buffer_write (buf,
+ "\\t",
+ 2);
+ break;
+ case 0xA:
+ GNUNET_buffer_write (buf,
+ "\\n",
+ 2);
+ break;
+ case 0xC:
+ GNUNET_buffer_write (buf,
+ "\\f",
+ 2);
+ break;
+ case 0xD:
+ GNUNET_buffer_write (buf,
+ "\\r",
+ 2);
+ break;
+ default:
+ GNUNET_snprintf (scratch,
+ sizeof (scratch),
+ "\\u%04x",
+ (unsigned int) val);
+ GNUNET_buffer_write (buf,
+ scratch,
+ 6);
+ break;
+ }
+}
+
+
+size_t
+TALER_rfc8785encode (char **inp)
+{
+ struct GNUNET_Buffer buf = { 0 };
+ size_t left = strlen (*inp) + 1;
+ size_t olen;
+ char *in = *inp;
+ const char *pos = in;
+
+ GNUNET_buffer_prealloc (&buf,
+ left + 40);
+ buf.warn_grow = 0; /* disable, + 40 is just a wild guess */
+ while (1)
+ {
+ int mbl = u8_mblen ((unsigned char *) pos,
+ left);
+ unsigned char val;
+
+ if (0 == mbl)
+ break;
+ val = (unsigned char) *pos;
+ if ( (1 == mbl) &&
+ (val <= 0x1F) )
+ {
+ /* Should not happen, as input is produced by
+ * JSON stringification */
+ GNUNET_break (0);
+ lowdump (&buf,
+ val);
+ }
+ else if ( (1 == mbl) && ('\\' == *pos) )
+ {
+ switch (*(pos + 1))
+ {
+ case '\\':
+ mbl = 2;
+ GNUNET_buffer_write (&buf,
+ pos,
+ mbl);
+ break;
+ case 'u':
+ {
+ unsigned int num;
+ uint32_t n32;
+ unsigned char res[8];
+ size_t rlen;
+
+ GNUNET_assert ( (1 ==
+ sscanf (pos + 2,
+ "%4x",
+ &num)) ||
+ (1 ==
+ sscanf (pos + 2,
+ "%4X",
+ &num)) );
+ mbl = 6;
+ n32 = (uint32_t) num;
+ rlen = sizeof (res);
+ u32_to_u8 (&n32,
+ 1,
+ res,
+ &rlen);
+ if ( (1 == rlen) &&
+ (res[0] <= 0x1F) )
+ {
+ lowdump (&buf,
+ res[0]);
+ }
+ else
+ {
+ GNUNET_buffer_write (&buf,
+ (const char *) res,
+ rlen);
+ }
+ }
+ break;
+ default:
+ mbl = 2;
+ GNUNET_buffer_write (&buf,
+ pos,
+ mbl);
+ break;
+ }
+ }
+ else
+ {
+ GNUNET_buffer_write (&buf,
+ pos,
+ mbl);
+ }
+ left -= mbl;
+ pos += mbl;
+ }
+
+ /* 0-terminate buffer */
+ GNUNET_buffer_write (&buf,
+ "",
+ 1);
+ GNUNET_free (in);
+ *inp = GNUNET_buffer_reap (&buf,
+ &olen);
+ return olen;
+}
+
+
+/**
+ * Hash normalized @a j JSON object or array and
+ * store the result in @a hc.
+ *
+ * @param j JSON to hash
+ * @param[out] hc where to write the hash
+ */
+void
+TALER_json_hash (const json_t *j,
+ struct GNUNET_HashCode *hc)
+{
+ char *cstr;
+ size_t clen;
+
+ cstr = json_dumps (j,
+ JSON_COMPACT | JSON_SORT_KEYS);
+ GNUNET_assert (NULL != cstr);
+ clen = TALER_rfc8785encode (&cstr);
+ GNUNET_CRYPTO_hash (cstr,
+ clen,
+ hc);
+ GNUNET_free (cstr);
+}
+
+
+#ifdef __APPLE__
+char *
+strchrnul (const char *s,
+ int c)
+{
+ char *value;
+ value = strchr (s,
+ c);
+ if (NULL == value)
+ value = &s[strlen (s)];
+ return value;
+}
+
+
+#endif
+
+
+void
+TALER_CRYPTO_attributes_to_kyc_prox (
+ const json_t *attr,
+ struct GNUNET_ShortHashCode *kyc_prox)
+{
+ const char *name = NULL;
+ const char *birthdate = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string (TALER_ATTRIBUTE_FULL_NAME,
+ &name),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string (TALER_ATTRIBUTE_BIRTHDATE,
+ &birthdate),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (attr,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ memset (kyc_prox,
+ 0,
+ sizeof (*kyc_prox));
+ return;
+ }
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (
+ kyc_prox,
+ sizeof (*kyc_prox),
+ name,
+ (NULL == name)
+ ? 0
+ : strlen (name),
+ birthdate,
+ (NULL == birthdate)
+ ? 0
+ : strlen (birthdate),
+ NULL,
+ 0));
+}
+
+
/* end of util.c */
diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c
new file mode 100644
index 000000000..0b6ab5432
--- /dev/null
+++ b/src/util/wallet_signatures.c
@@ -0,0 +1,1830 @@
+/*
+ This file is part of TALER
+ 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 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 wallet_signatures.c
+ * @brief Utility functions for Taler wallet signatures
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include <gnunet/gnunet_common.h>
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature on a request to deposit
+ * a coin into the account of a merchant.
+ */
+struct TALER_DepositRequestPS
+{
+ /**
+ * Purpose must be #TALER_SIGNATURE_WALLET_COIN_DEPOSIT.
+ * Used for an EdDSA signature with the `struct TALER_CoinSpendPublicKeyP`.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash over the contract for which this deposit is made.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;
+
+ /**
+ * Hash over the age commitment that went into the coin. Maybe all zero, if
+ * age commitment isn't applicable to the denomination.
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED;
+
+ /**
+ * Hash over optional policy extension attributes shared with the exchange.
+ */
+ struct TALER_ExtensionPolicyHashP h_policy GNUNET_PACKED;
+
+ /**
+ * Hash over the wiring information of the merchant.
+ */
+ struct TALER_MerchantWireHashP h_wire GNUNET_PACKED;
+
+ /**
+ * Hash over the denomination public key used to sign the coin.
+ */
+ struct TALER_DenominationHashP h_denom_pub GNUNET_PACKED;
+
+ /**
+ * Time when this request was generated. Used, for example, to
+ * assess when (roughly) the income was achieved for tax purposes.
+ * Note that the Exchange will only check that the timestamp is not "too
+ * far" into the future (i.e. several days). The fact that the
+ * timestamp falls within the validity period of the coin's
+ * denomination key is irrelevant for the validity of the deposit
+ * request, as obviously the customer and merchant could conspire to
+ * set any timestamp. Also, the Exchange must accept very old deposit
+ * requests, as the merchant might have been unable to transmit the
+ * deposit request in a timely fashion (so back-dating is not
+ * prevented).
+ */
+ struct GNUNET_TIME_TimestampNBO wallet_timestamp;
+
+ /**
+ * How much time does the merchant have to issue a refund request?
+ * Zero if refunds are not allowed. After this time, the coin
+ * cannot be refunded.
+ */
+ struct GNUNET_TIME_TimestampNBO refund_deadline;
+
+ /**
+ * Amount to be deposited, including deposit fee charged by the
+ * exchange. This is the total amount that the coin's value at the exchange
+ * will be reduced by.
+ */
+ struct TALER_AmountNBO amount_with_fee;
+
+ /**
+ * Depositing fee charged by the exchange. This must match the Exchange's
+ * denomination key's depositing fee. If the client puts in an
+ * invalid deposit fee (too high or too low) that does not match the
+ * Exchange's denomination key, the deposit operation is invalid and
+ * will be rejected by the exchange. The @e amount_with_fee minus the
+ * @e deposit_fee is the amount that will be transferred to the
+ * account identified by @e h_wire.
+ */
+ struct TALER_AmountNBO deposit_fee;
+
+ /**
+ * The Merchant's public key. Allows the merchant to later refund
+ * the transaction or to inquire about the wire transfer identifier.
+ */
+ struct TALER_MerchantPublicKeyP merchant;
+
+ /**
+ * Hash over a JSON containing data provided by the
+ * wallet to complete the contract upon payment.
+ */
+ struct GNUNET_HashCode wallet_data_hash;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_deposit_sign (
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct GNUNET_HashCode *wallet_data_hash,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct GNUNET_TIME_Timestamp wallet_timestamp,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_DepositRequestPS dr = {
+ .purpose.size = htonl (sizeof (dr)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT),
+ .h_contract_terms = *h_contract_terms,
+ .h_wire = *h_wire,
+ .h_denom_pub = *h_denom_pub,
+ .wallet_timestamp = GNUNET_TIME_timestamp_hton (wallet_timestamp),
+ .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
+ .merchant = *merchant_pub
+ };
+
+ if (NULL != wallet_data_hash)
+ dr.wallet_data_hash = *wallet_data_hash;
+ if (NULL != h_age_commitment)
+ dr.h_age_commitment = *h_age_commitment;
+ if (NULL != h_policy)
+ dr.h_policy = *h_policy;
+ TALER_amount_hton (&dr.amount_with_fee,
+ amount);
+ TALER_amount_hton (&dr.deposit_fee,
+ deposit_fee);
+ GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
+ &dr,
+ &coin_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_deposit_verify (
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct GNUNET_HashCode *wallet_data_hash,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct GNUNET_TIME_Timestamp wallet_timestamp,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_DepositRequestPS dr = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT),
+ .purpose.size = htonl (sizeof (dr)),
+ .h_contract_terms = *h_contract_terms,
+ .h_wire = *h_wire,
+ .h_denom_pub = *h_denom_pub,
+ .wallet_timestamp = GNUNET_TIME_timestamp_hton (wallet_timestamp),
+ .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
+ .merchant = *merchant_pub,
+ };
+
+ if (NULL != wallet_data_hash)
+ dr.wallet_data_hash = *wallet_data_hash;
+ if (NULL != h_age_commitment)
+ dr.h_age_commitment = *h_age_commitment;
+ if (NULL != h_policy)
+ dr.h_policy = *h_policy;
+ TALER_amount_hton (&dr.amount_with_fee,
+ amount);
+ TALER_amount_hton (&dr.deposit_fee,
+ deposit_fee);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT,
+ &dr,
+ &coin_sig->eddsa_signature,
+ &coin_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used for to allow the wallet to authenticate
+ * link data provided by the exchange.
+ */
+struct TALER_LinkDataPS
+{
+
+ /**
+ * Purpose must be #TALER_SIGNATURE_WALLET_COIN_LINK.
+ * Used with an EdDSA signature of a `struct TALER_CoinPublicKeyP`.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash of the denomination public key of the new coin.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Transfer public key (for which the private key was not revealed)
+ */
+ struct TALER_TransferPublicKeyP transfer_pub;
+
+ /**
+ * Hash of the age commitment, if applicable. Can be all zero
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
+ * Hash of the blinded new coin.
+ */
+ struct TALER_BlindedCoinHashP coin_envelope_hash;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_link_sign (const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_TransferPublicKeyP *transfer_pub,
+ const struct TALER_BlindedCoinHashP *bch,
+ const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_LinkDataPS ldp = {
+ .purpose.size = htonl (sizeof (ldp)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),
+ .h_denom_pub = *h_denom_pub,
+ .transfer_pub = *transfer_pub,
+ .coin_envelope_hash = *bch
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&old_coin_priv->eddsa_priv,
+ &ldp,
+ &coin_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_link_verify (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_TransferPublicKeyP *transfer_pub,
+ const struct TALER_BlindedCoinHashP *h_coin_ev,
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_LinkDataPS ldp = {
+ .purpose.size = htonl (sizeof (ldp)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),
+ .h_denom_pub = *h_denom_pub,
+ .transfer_pub = *transfer_pub,
+ .coin_envelope_hash = *h_coin_ev,
+ };
+
+ return
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK,
+ &ldp,
+ &coin_sig->eddsa_signature,
+ &old_coin_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Signed data to request that a coin should be refunded as part of
+ * the "emergency" /recoup protocol. The refund will go back to the bank
+ * account that created the reserve.
+ */
+struct TALER_RecoupRequestPS
+{
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_COIN_RECOUP
+ * or #TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash of the (revoked) denomination public key of the coin.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Blinding factor that was used to withdraw the coin.
+ */
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_recoup_verify (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_RecoupRequestPS pr = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP),
+ .purpose.size = htonl (sizeof (pr)),
+ .h_denom_pub = *h_denom_pub,
+ .coin_blind = *coin_bks
+ };
+
+ return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP,
+ &pr,
+ &coin_sig->eddsa_signature,
+ &coin_pub->eddsa_pub);
+}
+
+
+void
+TALER_wallet_recoup_sign (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_RecoupRequestPS pr = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP),
+ .purpose.size = htonl (sizeof (pr)),
+ .h_denom_pub = *h_denom_pub,
+ .coin_blind = *coin_bks
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
+ &pr,
+ &coin_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_recoup_refresh_verify (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_RecoupRequestPS pr = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH),
+ .purpose.size = htonl (sizeof (pr)),
+ .h_denom_pub = *h_denom_pub,
+ .coin_blind = *coin_bks
+ };
+
+ return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH,
+ &pr,
+ &coin_sig->eddsa_signature,
+ &coin_pub->eddsa_pub);
+}
+
+
+void
+TALER_wallet_recoup_refresh_sign (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_RecoupRequestPS pr = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH),
+ .purpose.size = htonl (sizeof (struct TALER_RecoupRequestPS)),
+ .h_denom_pub = *h_denom_pub,
+ .coin_blind = *coin_bks
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
+ &pr,
+ &coin_sig->eddsa_signature);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Message signed by a coin to indicate that the coin should be
+ * melted.
+ */
+struct TALER_RefreshMeltCoinAffirmationPS
+{
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_COIN_MELT.
+ * Used for an EdDSA signature with the `struct TALER_CoinSpendPublicKeyP`.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Which melt commitment is made by the wallet.
+ */
+ struct TALER_RefreshCommitmentP rc GNUNET_PACKED;
+
+ /**
+ * Hash over the denomination public key used to sign the coin.
+ */
+ struct TALER_DenominationHashP h_denom_pub GNUNET_PACKED;
+
+ /**
+ * If age commitment was provided during the withdrawal of the coin, this is
+ * the hash of the age commitment vector. It must be all zeroes if no age
+ * commitment was provided.
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED;
+
+ /**
+ * How much of the value of the coin should be melted? This amount
+ * includes the fees, so the final amount contributed to the melt is
+ * this value minus the fee for melting the coin. We include the
+ * fee in what is being signed so that we can verify a reserve's
+ * remaining total balance without needing to access the respective
+ * denomination key information each time.
+ */
+ struct TALER_AmountNBO amount_with_fee;
+
+ /**
+ * Melting fee charged by the exchange. This must match the Exchange's
+ * denomination key's melting fee. If the client puts in an invalid
+ * melting fee (too high or too low) that does not match the Exchange's
+ * denomination key, the melting operation is invalid and will be
+ * rejected by the exchange. The @e amount_with_fee minus the @e
+ * melt_fee is the amount that will be credited to the melting
+ * session.
+ */
+ struct TALER_AmountNBO melt_fee;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_melt_sign (
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *melt_fee,
+ const struct TALER_RefreshCommitmentP *rc,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_RefreshMeltCoinAffirmationPS melt = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
+ .purpose.size = htonl (sizeof (melt)),
+ .rc = *rc,
+ .h_denom_pub = *h_denom_pub,
+ .h_age_commitment = {{{0}}},
+ };
+
+ if (NULL != h_age_commitment)
+ melt.h_age_commitment = *h_age_commitment;
+
+
+ TALER_amount_hton (&melt.amount_with_fee,
+ amount_with_fee);
+ TALER_amount_hton (&melt.melt_fee,
+ melt_fee);
+ GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
+ &melt,
+ &coin_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_melt_verify (
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *melt_fee,
+ const struct TALER_RefreshCommitmentP *rc,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_RefreshMeltCoinAffirmationPS melt = {
+ .purpose.size = htonl (sizeof (melt)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
+ .rc = *rc,
+ .h_denom_pub = *h_denom_pub,
+ .h_age_commitment = {{{0}}},
+ };
+
+ if (NULL != h_age_commitment)
+ melt.h_age_commitment = *h_age_commitment;
+
+ TALER_amount_hton (&melt.amount_with_fee,
+ amount_with_fee);
+ TALER_amount_hton (&melt.melt_fee,
+ melt_fee);
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_WALLET_COIN_MELT,
+ &melt,
+ &coin_sig->eddsa_signature,
+ &coin_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+
+/**
+ * @brief Format used for to generate the signature on a request to withdraw
+ * coins from a reserve.
+ */
+struct TALER_WithdrawRequestPS
+{
+
+ /**
+ * Purpose must be #TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW.
+ * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Value of the coin being exchanged (matching the denomination key)
+ * plus the transaction fee. We include this in what is being
+ * signed so that we can verify a reserve's remaining total balance
+ * without needing to access the respective denomination key
+ * information each time.
+ */
+ struct TALER_AmountNBO amount_with_fee;
+
+ /**
+ * Hash of the denomination public key for the coin that is withdrawn.
+ */
+ struct TALER_DenominationHashP h_denomination_pub GNUNET_PACKED;
+
+ /**
+ * Hash of the (blinded) message to be signed by the Exchange.
+ */
+ struct TALER_BlindedCoinHashP h_coin_envelope GNUNET_PACKED;
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_withdraw_sign (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_BlindedCoinHashP *bch,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_WithdrawRequestPS req = {
+ .purpose.size = htonl (sizeof (req)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW),
+ .h_denomination_pub = *h_denom_pub,
+ .h_coin_envelope = *bch
+ };
+
+ TALER_amount_hton (&req.amount_with_fee,
+ amount_with_fee);
+ GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+ &req,
+ &reserve_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_withdraw_verify (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_BlindedCoinHashP *bch,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_WithdrawRequestPS wsrd = {
+ .purpose.size = htonl (sizeof (wsrd)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW),
+ .h_denomination_pub = *h_denom_pub,
+ .h_coin_envelope = *bch
+ };
+
+ TALER_amount_hton (&wsrd.amount_with_fee,
+ amount_with_fee);
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
+ &wsrd,
+ &reserve_sig->eddsa_signature,
+ &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used for to generate the signature on a request to
+ * age-withdraw from a reserve.
+ */
+struct TALER_AgeWithdrawRequestPS
+{
+
+ /**
+ * Purpose must be #TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW.
+ * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * The reserve's public key
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Value of the coin being exchanged (matching the denomination key)
+ * plus the transaction fee. We include this in what is being
+ * signed so that we can verify a reserve's remaining total balance
+ * without needing to access the respective denomination key
+ * information each time.
+ */
+ struct TALER_AmountNBO amount_with_fee;
+
+ /**
+ * Running SHA512 hash of the commitment of n*kappa coins
+ */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+ /**
+ * The mask that defines the age groups. MUST be the same for all denominations.
+ */
+ struct TALER_AgeMask mask;
+
+ /**
+ * Maximum age group that the coins are going to be restricted to.
+ */
+ uint8_t max_age_group;
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_age_withdraw_sign (
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_AgeMask *mask,
+ uint8_t max_age,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_AgeWithdrawRequestPS req = {
+ .purpose.size = htonl (sizeof (req)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW),
+ .h_commitment = *h_commitment,
+ .mask = *mask,
+ .max_age_group = TALER_get_age_group (mask, max_age)
+ };
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &req.reserve_pub.eddsa_pub);
+ TALER_amount_hton (&req.amount_with_fee,
+ amount_with_fee);
+ GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+ &req,
+ &reserve_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_age_withdraw_verify (
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_AgeMask *mask,
+ uint8_t max_age,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_AgeWithdrawRequestPS awsrd = {
+ .purpose.size = htonl (sizeof (awsrd)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW),
+ .reserve_pub = *reserve_pub,
+ .h_commitment = *h_commitment,
+ .mask = *mask,
+ .max_age_group = TALER_get_age_group (mask, max_age)
+ };
+
+ TALER_amount_hton (&awsrd.amount_with_fee,
+ amount_with_fee);
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW,
+ &awsrd,
+ &reserve_sig->eddsa_signature,
+ &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+
+/**
+ * @brief Format used for to generate the signature on a request to withdraw
+ * coins from a reserve.
+ */
+struct TALER_AccountSetupRequestSignaturePS
+{
+
+ /**
+ * Purpose must be #TALER_SIGNATURE_WALLET_ACCOUNT_SETUP.
+ * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Balance threshold the wallet is about to cross.
+ */
+ struct TALER_AmountNBO threshold;
+
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_account_setup_sign (
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_Amount *balance_threshold,
+ struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_AccountSetupRequestSignaturePS asap = {
+ .purpose.size = htonl (sizeof (asap)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_SETUP)
+ };
+
+ TALER_amount_hton (&asap.threshold,
+ balance_threshold);
+ GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+ &asap,
+ &reserve_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_account_setup_verify (
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *balance_threshold,
+ const struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_AccountSetupRequestSignaturePS asap = {
+ .purpose.size = htonl (sizeof (asap)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_SETUP)
+ };
+
+ TALER_amount_hton (&asap.threshold,
+ balance_threshold);
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_WALLET_ACCOUNT_SETUP,
+ &asap,
+ &reserve_sig->eddsa_signature,
+ &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+
+/**
+ * Response by which a wallet requests a reserve history.
+ */
+struct TALER_ReserveHistoryRequestPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_HISTORY
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Which entries to exclude. Only return above this offset.
+ */
+ uint64_t start_off;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_history_verify (
+ uint64_t start_off,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_ReserveHistoryRequestPS rhr = {
+ .purpose.size = htonl (sizeof (rhr)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_HISTORY),
+ .start_off = GNUNET_htonll (start_off)
+ };
+
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_WALLET_RESERVE_HISTORY,
+ &rhr,
+ &reserve_sig->eddsa_signature,
+ &reserve_pub->eddsa_pub);
+}
+
+
+void
+TALER_wallet_reserve_history_sign (
+ uint64_t start_off,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_ReserveHistoryRequestPS rhr = {
+ .purpose.size = htonl (sizeof (rhr)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_HISTORY),
+ .start_off = GNUNET_htonll (start_off)
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+ &rhr,
+ &reserve_sig->eddsa_signature);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which a wallet requests a coin history.
+ */
+struct TALER_CoinHistoryRequestPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_COIN_HISTORY
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Which entries to exclude. Only return above this offset.
+ */
+ uint64_t start_off;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_coin_history_verify (
+ uint64_t start_off,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_CoinHistoryRequestPS rsr = {
+ .purpose.size = htonl (sizeof (rsr)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_HISTORY),
+ .start_off = GNUNET_htonll (start_off)
+ };
+
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_WALLET_COIN_HISTORY,
+ &rsr,
+ &coin_sig->eddsa_signature,
+ &coin_pub->eddsa_pub);
+}
+
+
+void
+TALER_wallet_coin_history_sign (
+ uint64_t start_off,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_CoinHistoryRequestPS rsr = {
+ .purpose.size = htonl (sizeof (rsr)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_HISTORY),
+ .start_off = GNUNET_htonll (start_off)
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
+ &rsr,
+ &coin_sig->eddsa_signature);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed to create a purse (without reserve).
+ */
+struct TALER_PurseCreatePS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_PURSE_CREATE
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Time when the purse will expire if still unmerged or unpaid.
+ */
+ struct GNUNET_TIME_TimestampNBO purse_expiration;
+
+ /**
+ * Total amount (with fees) to be put into the purse.
+ */
+ struct TALER_AmountNBO purse_amount;
+
+ /**
+ * Contract this purse pays for.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Public key identifying the merge capability.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Minimum age required for payments into this purse.
+ */
+ uint32_t min_age GNUNET_PACKED;
+
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_purse_create_sign (
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ uint32_t min_age,
+ const struct TALER_Amount *amount,
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct TALER_PurseCreatePS pm = {
+ .purpose.size = htonl (sizeof (pm)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_CREATE),
+ .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+ .h_contract_terms = *h_contract_terms,
+ .merge_pub = *merge_pub,
+ .min_age = htonl (min_age)
+ };
+
+ TALER_amount_hton (&pm.purse_amount,
+ amount);
+ GNUNET_CRYPTO_eddsa_sign (&purse_priv->eddsa_priv,
+ &pm,
+ &purse_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_create_verify (
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ uint32_t min_age,
+ const struct TALER_Amount *amount,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct TALER_PurseCreatePS pm = {
+ .purpose.size = htonl (sizeof (pm)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_CREATE),
+ .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+ .h_contract_terms = *h_contract_terms,
+ .merge_pub = *merge_pub,
+ .min_age = htonl (min_age)
+ };
+
+ TALER_amount_hton (&pm.purse_amount,
+ amount);
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_WALLET_PURSE_CREATE,
+ &pm,
+ &purse_sig->eddsa_signature,
+ &purse_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed to delete a purse.
+ */
+struct TALER_PurseDeletePS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_PURSE_DELETE
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_purse_delete_sign (
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct TALER_PurseDeletePS pm = {
+ .purpose.size = htonl (sizeof (pm)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DELETE)
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&purse_priv->eddsa_priv,
+ &pm,
+ &purse_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_delete_verify (
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct TALER_PurseDeletePS pm = {
+ .purpose.size = htonl (sizeof (pm)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DELETE)
+ };
+
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_WALLET_PURSE_DELETE,
+ &pm,
+ &purse_sig->eddsa_signature,
+ &purse_pub->eddsa_pub);
+}
+
+
+void
+TALER_wallet_purse_status_sign (
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose = {
+ .size = htonl (sizeof (purpose)),
+ .purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_STATUS)
+ };
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_sign_ (&purse_priv->eddsa_priv,
+ &purpose,
+ &purse_sig->eddsa_signature));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_status_verify (
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose = {
+ .size = htonl (sizeof (purpose)),
+ .purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_STATUS)
+ };
+
+ return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_PURSE_STATUS,
+ &purpose,
+ &purse_sig->eddsa_signature,
+ &purse_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed to deposit a coin into a purse.
+ */
+struct TALER_PurseDepositPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_PURSE_DEPOSIT
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Amount (with deposit fee) to be deposited into the purse.
+ */
+ struct TALER_AmountNBO coin_amount;
+
+ /**
+ * Hash over the denomination public key used to sign the coin.
+ */
+ struct TALER_DenominationHashP h_denom_pub GNUNET_PACKED;
+
+ /**
+ * Hash over the age commitment that went into the coin. Maybe all zero, if
+ * age commitment isn't applicable to the denomination.
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED;
+
+ /**
+ * Purse to deposit funds into.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Hash of the base URL of the exchange hosting the
+ * @e purse_pub.
+ */
+ struct GNUNET_HashCode h_exchange_base_url GNUNET_PACKED;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_purse_deposit_sign (
+ const char *exchange_base_url,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *amount,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_PurseDepositPS pm = {
+ .purpose.size = htonl (sizeof (pm)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DEPOSIT),
+ .purse_pub = *purse_pub,
+ .h_denom_pub = *h_denom_pub,
+ .h_age_commitment = *h_age_commitment
+ };
+
+ GNUNET_CRYPTO_hash (exchange_base_url,
+ strlen (exchange_base_url) + 1,
+ &pm.h_exchange_base_url);
+ TALER_amount_hton (&pm.coin_amount,
+ amount);
+ GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
+ &pm,
+ &coin_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_deposit_verify (
+ const char *exchange_base_url,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *amount,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_PurseDepositPS pm = {
+ .purpose.size = htonl (sizeof (pm)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DEPOSIT),
+ .purse_pub = *purse_pub,
+ .h_denom_pub = *h_denom_pub,
+ .h_age_commitment = *h_age_commitment
+ };
+
+ GNUNET_CRYPTO_hash (exchange_base_url,
+ strlen (exchange_base_url) + 1,
+ &pm.h_exchange_base_url);
+ TALER_amount_hton (&pm.coin_amount,
+ amount);
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_WALLET_PURSE_DEPOSIT,
+ &pm,
+ &coin_sig->eddsa_signature,
+ &coin_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed to merge a purse into a reserve.
+ */
+struct TALER_PurseMergePS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_PURSE_MERGE
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Time when the purse is merged into the reserve.
+ */
+ struct GNUNET_TIME_TimestampNBO merge_timestamp;
+
+ /**
+ * Which purse is being merged?
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Which reserve should the purse be merged with.
+ * Hash of the reserve's payto:// URI.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_purse_merge_sign (
+ const char *reserve_uri,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePrivateKeyP *merge_priv,
+ struct TALER_PurseMergeSignatureP *merge_sig)
+{
+ struct TALER_PurseMergePS pm = {
+ .purpose.size = htonl (sizeof (pm)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_MERGE),
+ .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
+ .purse_pub = *purse_pub
+ };
+
+ GNUNET_assert (0 ==
+ strncasecmp (reserve_uri,
+ "payto://taler-reserve",
+ strlen ("payto://taler-reserve")));
+ TALER_payto_hash (reserve_uri,
+ &pm.h_payto);
+ GNUNET_CRYPTO_eddsa_sign (&merge_priv->eddsa_priv,
+ &pm,
+ &merge_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_merge_verify (
+ const char *reserve_uri,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig)
+{
+ struct TALER_PurseMergePS pm = {
+ .purpose.size = htonl (sizeof (pm)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_MERGE),
+ .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
+ .purse_pub = *purse_pub
+ };
+
+ if (0 !=
+ strncasecmp (reserve_uri,
+ "payto://taler-reserve",
+ strlen ("payto://taler-reserve")))
+ {
+ GNUNET_break (0);
+ return GNUNET_NO;
+ }
+ TALER_payto_hash (reserve_uri,
+ &pm.h_payto);
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_WALLET_PURSE_MERGE,
+ &pm,
+ &merge_sig->eddsa_signature,
+ &merge_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by account to merge a purse into a reserve.
+ */
+struct TALER_AccountMergePS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_ACCOUNT_MERGE
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Time when the purse will expire if still unmerged or unpaid.
+ */
+ struct GNUNET_TIME_TimestampNBO purse_expiration;
+
+ /**
+ * Total amount (with fees) to be put into the purse.
+ */
+ struct TALER_AmountNBO purse_amount;
+
+ /**
+ * Purse creation fee to be paid by the reserve for
+ * this operation.
+ */
+ struct TALER_AmountNBO purse_fee;
+
+ /**
+ * Contract this purse pays for.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Purse to merge.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Time when the purse is merged into the reserve.
+ */
+ struct GNUNET_TIME_TimestampNBO merge_timestamp;
+
+ /**
+ * Minimum age required for payments into this purse,
+ * in NBO.
+ */
+ uint32_t min_age GNUNET_PACKED;
+
+ /**
+ * Flags for the operation, in NBO. See
+ * `enum TALER_WalletAccountMergeFlags`.
+ */
+ uint32_t flags GNUNET_PACKED;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_account_merge_sign (
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *purse_fee,
+ uint32_t min_age,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_AccountMergePS pm = {
+ .purpose.size = htonl (sizeof (pm)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_MERGE),
+ .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
+ .purse_pub = *purse_pub,
+ .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+ .h_contract_terms = *h_contract_terms,
+ .min_age = htonl (min_age),
+ .flags = htonl ((uint32_t) flags)
+ };
+
+ TALER_amount_hton (&pm.purse_amount,
+ amount);
+ TALER_amount_hton (&pm.purse_fee,
+ purse_fee);
+ GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+ &pm,
+ &reserve_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_account_merge_verify (
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *purse_fee,
+ uint32_t min_age,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_AccountMergePS pm = {
+ .purpose.size = htonl (sizeof (pm)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_MERGE),
+ .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
+ .purse_pub = *purse_pub,
+ .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+ .h_contract_terms = *h_contract_terms,
+ .min_age = htonl (min_age),
+ .flags = htonl ((uint32_t) flags)
+ };
+
+ TALER_amount_hton (&pm.purse_amount,
+ amount);
+ TALER_amount_hton (&pm.purse_fee,
+ purse_fee);
+ return GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_WALLET_ACCOUNT_MERGE,
+ &pm,
+ &reserve_sig->eddsa_signature,
+ &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by reserve key.
+ */
+struct TALER_ReserveOpenPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_OPEN
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Amount to be paid from the reserve balance to open
+ * the reserve.
+ */
+ struct TALER_AmountNBO reserve_payment;
+
+ /**
+ * When was the request created.
+ */
+ struct GNUNET_TIME_TimestampNBO request_timestamp;
+
+ /**
+ * For how long should the reserve be kept open.
+ * (Determines amount to be paid.)
+ */
+ struct GNUNET_TIME_TimestampNBO reserve_expiration;
+
+ /**
+ * How many open purses should be included with the
+ * open reserve?
+ * (Determines amount to be paid.)
+ */
+ uint32_t purse_limit GNUNET_PACKED;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_reserve_open_sign (
+ const struct TALER_Amount *reserve_payment,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ struct GNUNET_TIME_Timestamp reserve_expiration,
+ uint32_t purse_limit,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_ReserveOpenPS rop = {
+ .purpose.size = htonl (sizeof (rop)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN),
+ .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp),
+ .reserve_expiration = GNUNET_TIME_timestamp_hton (reserve_expiration),
+ .purse_limit = htonl (purse_limit)
+ };
+
+ TALER_amount_hton (&rop.reserve_payment,
+ reserve_payment);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_sign_ (&reserve_priv->eddsa_priv,
+ &rop.purpose,
+ &reserve_sig->eddsa_signature));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_open_verify (
+ const struct TALER_Amount *reserve_payment,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ struct GNUNET_TIME_Timestamp reserve_expiration,
+ uint32_t purse_limit,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_ReserveOpenPS rop = {
+ .purpose.size = htonl (sizeof (rop)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN),
+ .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp),
+ .reserve_expiration = GNUNET_TIME_timestamp_hton (reserve_expiration),
+ .purse_limit = htonl (purse_limit)
+ };
+
+ TALER_amount_hton (&rop.reserve_payment,
+ reserve_payment);
+ return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_RESERVE_OPEN,
+ &rop.purpose,
+ &reserve_sig->eddsa_signature,
+ &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by
+ */
+struct TALER_ReserveOpenDepositPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Which reserve's opening signature should be paid for?
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Specifies how much of the coin's value should be spent on opening this
+ * reserve.
+ */
+ struct TALER_AmountNBO coin_contribution;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+// FIXME-#7267: add h_age_commitment, h_denom_pub to have proof!
+void
+TALER_wallet_reserve_open_deposit_sign (
+ const struct TALER_Amount *coin_contribution,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_ReserveOpenDepositPS rod = {
+ .purpose.size = htonl (sizeof (rod)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT),
+ .reserve_sig = *reserve_sig
+ };
+
+ TALER_amount_hton (&rod.coin_contribution,
+ coin_contribution);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_sign_ (&coin_priv->eddsa_priv,
+ &rod.purpose,
+ &coin_sig->eddsa_signature));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_open_deposit_verify (
+ const struct TALER_Amount *coin_contribution,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_ReserveOpenDepositPS rod = {
+ .purpose.size = htonl (sizeof (rod)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT),
+ .reserve_sig = *reserve_sig
+ };
+
+ TALER_amount_hton (&rod.coin_contribution,
+ coin_contribution);
+ return GNUNET_CRYPTO_eddsa_verify_ (
+ TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT,
+ &rod.purpose,
+ &coin_sig->eddsa_signature,
+ &coin_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by reserve key.
+ */
+struct TALER_ReserveClosePS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_CLOSE
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * When was the request created.
+ */
+ struct GNUNET_TIME_TimestampNBO request_timestamp;
+
+ /**
+ * Hash of the payto://-URI of the target account
+ * for the closure, or all zeros for the reserve
+ * origin account.
+ */
+ struct TALER_PaytoHashP target_account_h_payto;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_reserve_close_sign (
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_ReserveClosePS rcp = {
+ .purpose.size = htonl (sizeof (rcp)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_CLOSE),
+ .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp)
+ };
+
+ if (NULL != h_payto)
+ rcp.target_account_h_payto = *h_payto;
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_sign_ (&reserve_priv->eddsa_priv,
+ &rcp.purpose,
+ &reserve_sig->eddsa_signature));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_close_verify (
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_ReserveClosePS rcp = {
+ .purpose.size = htonl (sizeof (rcp)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_CLOSE),
+ .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp)
+ };
+
+ if (NULL != h_payto)
+ rcp.target_account_h_payto = *h_payto;
+ return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_RESERVE_CLOSE,
+ &rcp.purpose,
+ &reserve_sig->eddsa_signature,
+ &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by reserve private key.
+ */
+struct TALER_ReserveAttestRequestPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_ATTEST_REQUEST
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * When was the request created.
+ */
+ struct GNUNET_TIME_TimestampNBO request_timestamp;
+
+ /**
+ * Hash over the JSON array of requested attributes.
+ */
+ struct GNUNET_HashCode h_details;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_reserve_attest_request_sign (
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const json_t *details,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_ReserveAttestRequestPS rcp = {
+ .purpose.size = htonl (sizeof (rcp)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_ATTEST_DETAILS),
+ .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp)
+ };
+
+ TALER_json_hash (details,
+ &rcp.h_details);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_sign_ (&reserve_priv->eddsa_priv,
+ &rcp.purpose,
+ &reserve_sig->eddsa_signature));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_attest_request_verify (
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const json_t *details,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct TALER_ReserveAttestRequestPS rcp = {
+ .purpose.size = htonl (sizeof (rcp)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_ATTEST_DETAILS),
+ .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp)
+ };
+
+ TALER_json_hash (details,
+ &rcp.h_details);
+ return GNUNET_CRYPTO_eddsa_verify_ (
+ TALER_SIGNATURE_WALLET_RESERVE_ATTEST_DETAILS,
+ &rcp.purpose,
+ &reserve_sig->eddsa_signature,
+ &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by purse to associate an encrypted contract.
+ */
+struct TALER_PurseContractPS
+{
+
+ /**
+ * Purpose is #TALER_SIGNATURE_WALLET_PURSE_ECONTRACT
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash over the encrypted contract.
+ */
+ struct GNUNET_HashCode h_econtract;
+
+ /**
+ * Public key to decrypt the contract.
+ */
+ struct TALER_ContractDiffiePublicP contract_pub;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_econtract_upload_sign (
+ const void *econtract,
+ size_t econtract_size,
+ const struct TALER_ContractDiffiePublicP *contract_pub,
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct TALER_PurseContractPS pc = {
+ .purpose.size = htonl (sizeof (pc)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_ECONTRACT),
+ .contract_pub = *contract_pub
+ };
+
+ GNUNET_CRYPTO_hash (econtract,
+ econtract_size,
+ &pc.h_econtract);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_sign_ (&purse_priv->eddsa_priv,
+ &pc.purpose,
+ &purse_sig->eddsa_signature));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_econtract_upload_verify2 (
+ const struct GNUNET_HashCode *h_econtract,
+ const struct TALER_ContractDiffiePublicP *contract_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct TALER_PurseContractPS pc = {
+ .purpose.size = htonl (sizeof (pc)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_ECONTRACT),
+ .contract_pub = *contract_pub,
+ .h_econtract = *h_econtract
+ };
+
+ return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_PURSE_ECONTRACT,
+ &pc.purpose,
+ &purse_sig->eddsa_signature,
+ &purse_pub->eddsa_pub);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_econtract_upload_verify (
+ const void *econtract,
+ size_t econtract_size,
+ const struct TALER_ContractDiffiePublicP *contract_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct GNUNET_HashCode h_econtract;
+
+ GNUNET_CRYPTO_hash (econtract,
+ econtract_size,
+ &h_econtract);
+ return TALER_wallet_econtract_upload_verify2 (&h_econtract,
+ contract_pub,
+ purse_pub,
+ purse_sig);
+}
+
+
+/* end of wallet_signatures.c */
diff --git a/src/util/yna.c b/src/util/yna.c
new file mode 100644
index 000000000..849561a5a
--- /dev/null
+++ b/src/util/yna.c
@@ -0,0 +1,88 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 yna.c
+ * @brief Utility functions for yes/no/all filters
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include "taler_util.h"
+
+
+/**
+ * Convert query argument to @a yna value.
+ *
+ * @param connection connection to take query argument from
+ * @param arg argument to try for
+ * @param default_val value to assign if the argument is not present
+ * @param[out] yna value to set
+ * @return true on success, false if the parameter was malformed
+ */
+bool
+TALER_arg_to_yna (struct MHD_Connection *connection,
+ const char *arg,
+ enum TALER_EXCHANGE_YesNoAll default_val,
+ enum TALER_EXCHANGE_YesNoAll *yna)
+{
+ const char *str;
+
+ str = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ arg);
+ if (NULL == str)
+ {
+ *yna = default_val;
+ return true;
+ }
+ if (0 == strcasecmp (str, "yes"))
+ {
+ *yna = TALER_EXCHANGE_YNA_YES;
+ return true;
+ }
+ if (0 == strcasecmp (str, "no"))
+ {
+ *yna = TALER_EXCHANGE_YNA_NO;
+ return true;
+ }
+ if (0 == strcasecmp (str, "all"))
+ {
+ *yna = TALER_EXCHANGE_YNA_ALL;
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Convert YNA value to a string.
+ *
+ * @param yna value to convert
+ * @return string representation ("yes"/"no"/"all").
+ */
+const char *
+TALER_yna_to_string (enum TALER_EXCHANGE_YesNoAll yna)
+{
+ switch (yna)
+ {
+ case TALER_EXCHANGE_YNA_YES:
+ return "yes";
+ case TALER_EXCHANGE_YNA_NO:
+ return "no";
+ case TALER_EXCHANGE_YNA_ALL:
+ return "all";
+ }
+ GNUNET_assert (0);
+}